[
  {
    "path": ".clang-format",
    "content": "Language: Cpp\nAccessModifierOffset: -2\nAlignAfterOpenBracket: Align\nAlignConsecutiveAssignments: false\nAlignConsecutiveDeclarations: false\nAlignOperands: Align\nAlignTrailingComments: true\nAllowAllParametersOfDeclarationOnNextLine: true\nAllowShortBlocksOnASingleLine: Never\nAllowShortCaseLabelsOnASingleLine: false\nAllowShortFunctionsOnASingleLine: All\nAllowShortIfStatementsOnASingleLine: false\nAllowShortLoopsOnASingleLine: false\nAlwaysBreakAfterDefinitionReturnType: None\nAlwaysBreakAfterReturnType: AllDefinitions\nAlwaysBreakBeforeMultilineStrings: false\nAlwaysBreakTemplateDeclarations: Yes\nBinPackArguments: true\nBinPackParameters: true\nBraceWrapping:\n  AfterClass: false\n  AfterControlStatement: false\n  AfterEnum: false\n  AfterFunction: true\n  AfterNamespace: false\n  AfterObjCDeclaration: false\n  AfterStruct: false\n  AfterUnion: false\n  BeforeCatch: false\n  BeforeElse: false\n  IndentBraces: false\nBreakBeforeBinaryOperators: None\nBreakBeforeBraces: Custom\nBreakBeforeTernaryOperators: true\nBreakConstructorInitializersBeforeComma: true\nBreakAfterJavaFieldAnnotations: false\nBreakStringLiterals: true\nColumnLimit: 90\nCommentPragmas: '^ IWYU pragma:'\nConstructorInitializerAllOnOneLineOrOnePerLine: false\nConstructorInitializerIndentWidth: 4\nContinuationIndentWidth: 4\nCpp11BracedListStyle: true\nDerivePointerAlignment: false\nDisableFormat: false\nExperimentalAutoDetectBinPacking: false\nForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]\nIncludeCategories:\n  - Regex: '^\"(llvm|llvm-c|clang|clang-c)/'\n    Priority: 2\n  - Regex: '^(<|\"(gtest|isl|json)/)'\n    Priority: 3\n  - Regex: '.*'\n    Priority: 1\nIncludeIsMainRegex: '$'\nIndentCaseLabels: false\nIndentWidth: 2\nIndentWrappedFunctionNames: false\nKeepEmptyLinesAtTheStartOfBlocks: true\nMacroBlockBegin: ''\nMacroBlockEnd: ''\nMaxEmptyLinesToKeep: 1\nNamespaceIndentation: None\nObjCBlockIndentWidth: 2\nObjCSpaceAfterProperty: false\nObjCSpaceBeforeProtocolList: true\nPenaltyBreakBeforeFirstCallParameter: 19\nPenaltyBreakComment: 300\nPenaltyBreakFirstLessLess: 120\nPenaltyBreakString: 1000\nPenaltyExcessCharacter: 1000000\nPenaltyReturnTypeOnItsOwnLine: 60\nPointerAlignment: Left\nReflowComments: Always\nSortIncludes: true\nSpaceAfterCStyleCast: false\nSpaceBeforeAssignmentOperators: true\nSpaceBeforeParens: ControlStatements\nSpaceInEmptyParentheses: false\nSpacesBeforeTrailingComments: 1\nSpacesInAngles: false\nSpacesInContainerLiterals: true\nSpacesInCStyleCastParentheses: false\nSpacesInParentheses: false\nSpacesInSquareBrackets: false\nStandard: c++17\nTabWidth: 8\nUseTab: Never\nJavaScriptQuotes: Leave\nInsertBraces: true\n"
  },
  {
    "path": ".clang-tidy",
    "content": "---\nChecks: 'clang-diagnostic-*,clang-analyzer-*,-clang-analyzer-alpha*,*,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cert-err61-cpp,-misc-throw-by-value-catch-by-reference,-clang-analyzer-alpha.deadcode.UnreachableCode,-cert-err58-cpp,-clang-analyzer-alpha.*,-clang-analyzer-security.insecureAPI.strcpy,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-pro-type-reinterpret-cast,-google-runtime-int,-modernize-raw-string-literal,-cppcoreguidelines-pro-bounds-constant-array-index,-llvmlibc-*'\nWarningsAsErrors: ''\nHeaderFilterRegex: ''\nAnalyzeTemporaryDtors: false\nCheckOptions:\n  - key: cert-dcl59-cpp.HeaderFileExtensions\n    value: h,hh,hpp,hxx\n  - key: cert-err61-cpp.CheckThrowTemporaries\n    value: '1'\n  - key: cert-oop11-cpp.IncludeStyle\n    value: llvm\n  - key: cert-oop11-cpp.UseCERTSemantics\n    value: '1'\n  - key: cppcoreguidelines-pro-bounds-constant-array-index.GslHeader\n    value: ''\n  - key: cppcoreguidelines-pro-bounds-constant-array-index.IncludeStyle\n    value: '0'\n  - key: cppcoreguidelines-pro-type-member-init.IgnoreArrays\n    value: '0'\n  - key: google-build-namespaces.HeaderFileExtensions\n    value: h,hh,hpp,hxx\n  - key: google-global-names-in-headers.HeaderFileExtensions\n    value: h\n  - key: google-readability-braces-around-statements.ShortStatementLines\n    value: '1'\n  - key: google-readability-function-size.BranchThreshold\n    value: '4294967295'\n  - key: google-readability-function-size.LineThreshold\n    value: '4294967295'\n  - key: google-readability-function-size.StatementThreshold\n    value: '800'\n  - key: google-readability-namespace-comments.ShortNamespaceLines\n    value: '10'\n  - key: google-readability-namespace-comments.SpacesBeforeComments\n    value: '2'\n  - key: google-runtime-int.SignedTypePrefix\n    value: int\n  - key: google-runtime-int.TypeSuffix\n    value: ''\n  - key: google-runtime-int.UnsignedTypePrefix\n    value: uint\n  - key: llvm-namespace-comment.ShortNamespaceLines\n    value: '1'\n  - key: llvm-namespace-comment.SpacesBeforeComments\n    value: '1'\n  - key: misc-assert-side-effect.AssertMacros\n    value: assert\n  - key: misc-assert-side-effect.CheckFunctionCalls\n    value: '0'\n  - key: misc-dangling-handle.HandleClasses\n    value: 'std::basic_string_view;std::experimental::basic_string_view'\n  - key: misc-definitions-in-headers.HeaderFileExtensions\n    value: ',h,hh,hpp,hxx'\n  - key: misc-definitions-in-headers.UseHeaderFileExtension\n    value: '1'\n  - key: misc-misplaced-widening-cast.CheckImplicitCasts\n    value: '1'\n  - key: misc-move-constructor-init.IncludeStyle\n    value: llvm\n  - key: misc-move-constructor-init.UseCERTSemantics\n    value: '0'\n  - key: misc-sizeof-expression.WarnOnSizeOfCompareToConstant\n    value: '1'\n  - key: misc-sizeof-expression.WarnOnSizeOfConstant\n    value: '1'\n  - key: misc-sizeof-expression.WarnOnSizeOfThis\n    value: '1'\n  - key: misc-string-constructor.LargeLengthThreshold\n    value: '8388608'\n  - key: misc-string-constructor.WarnOnLargeLength\n    value: '1'\n  - key: misc-suspicious-missing-comma.MaxConcatenatedTokens\n    value: '5'\n  - key: misc-suspicious-missing-comma.RatioThreshold\n    value: '0.200000'\n  - key: misc-suspicious-missing-comma.SizeThreshold\n    value: '5'\n  - key: misc-suspicious-string-compare.StringCompareLikeFunctions\n    value: ''\n  - key: misc-suspicious-string-compare.WarnOnImplicitComparison\n    value: '1'\n  - key: misc-suspicious-string-compare.WarnOnLogicalNotComparison\n    value: '0'\n  - key: misc-throw-by-value-catch-by-reference.CheckThrowTemporaries\n    value: '1'\n  - key: modernize-loop-convert.MaxCopySize\n    value: '16'\n  - key: modernize-loop-convert.MinConfidence\n    value: reasonable\n  - key: modernize-loop-convert.NamingStyle\n    value: CamelCase\n  - key: modernize-pass-by-value.IncludeStyle\n    value: llvm\n  - key: modernize-replace-auto-ptr.IncludeStyle\n    value: llvm\n  - key: modernize-use-nullptr.NullMacros\n    value: 'NULL'\n  - key: performance-faster-string-find.StringLikeClasses\n    value: 'std::basic_string'\n  - key: performance-for-range-copy.WarnOnAllAutoCopies\n    value: '0'\n  - key: readability-braces-around-statements.ShortStatementLines\n    value: '1'\n  - key: readability-function-size.BranchThreshold\n    value: '4294967295'\n  - key: readability-function-size.LineThreshold\n    value: '4294967295'\n  - key: readability-function-size.StatementThreshold\n    value: '800'\n  - key: readability-identifier-naming.AbstractClassCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.AbstractClassPrefix\n    value: ''\n  - key: readability-identifier-naming.AbstractClassSuffix\n    value: ''\n  - key: readability-identifier-naming.ClassCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.ClassConstantCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.ClassConstantPrefix\n    value: ''\n  - key: readability-identifier-naming.ClassConstantSuffix\n    value: ''\n  - key: readability-identifier-naming.ClassMemberCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.ClassMemberPrefix\n    value: ''\n  - key: readability-identifier-naming.ClassMemberSuffix\n    value: ''\n  - key: readability-identifier-naming.ClassMethodCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.ClassMethodPrefix\n    value: ''\n  - key: readability-identifier-naming.ClassMethodSuffix\n    value: ''\n  - key: readability-identifier-naming.ClassPrefix\n    value: ''\n  - key: readability-identifier-naming.ClassSuffix\n    value: ''\n  - key: readability-identifier-naming.ConstantCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.ConstantMemberCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.ConstantMemberPrefix\n    value: ''\n  - key: readability-identifier-naming.ConstantMemberSuffix\n    value: ''\n  - key: readability-identifier-naming.ConstantParameterCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.ConstantParameterPrefix\n    value: ''\n  - key: readability-identifier-naming.ConstantParameterSuffix\n    value: ''\n  - key: readability-identifier-naming.ConstantPrefix\n    value: ''\n  - key: readability-identifier-naming.ConstantSuffix\n    value: ''\n  - key: readability-identifier-naming.ConstexprFunctionCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.ConstexprFunctionPrefix\n    value: ''\n  - key: readability-identifier-naming.ConstexprFunctionSuffix\n    value: ''\n  - key: readability-identifier-naming.ConstexprMethodCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.ConstexprMethodPrefix\n    value: ''\n  - key: readability-identifier-naming.ConstexprMethodSuffix\n    value: ''\n  - key: readability-identifier-naming.ConstexprVariableCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.ConstexprVariablePrefix\n    value: ''\n  - key: readability-identifier-naming.ConstexprVariableSuffix\n    value: ''\n  - key: readability-identifier-naming.EnumCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.EnumConstantCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.EnumConstantPrefix\n    value: ''\n  - key: readability-identifier-naming.EnumConstantSuffix\n    value: ''\n  - key: readability-identifier-naming.EnumPrefix\n    value: ''\n  - key: readability-identifier-naming.EnumSuffix\n    value: ''\n  - key: readability-identifier-naming.FunctionCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.FunctionPrefix\n    value: ''\n  - key: readability-identifier-naming.FunctionSuffix\n    value: ''\n  - key: readability-identifier-naming.GlobalConstantCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.GlobalConstantPrefix\n    value: ''\n  - key: readability-identifier-naming.GlobalConstantSuffix\n    value: ''\n  - key: readability-identifier-naming.GlobalFunctionCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.GlobalFunctionPrefix\n    value: ''\n  - key: readability-identifier-naming.GlobalFunctionSuffix\n    value: ''\n  - key: readability-identifier-naming.GlobalVariableCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.GlobalVariablePrefix\n    value: ''\n  - key: readability-identifier-naming.GlobalVariableSuffix\n    value: ''\n  - key: readability-identifier-naming.IgnoreFailedSplit\n    value: '0'\n  - key: readability-identifier-naming.InlineNamespaceCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.InlineNamespacePrefix\n    value: ''\n  - key: readability-identifier-naming.InlineNamespaceSuffix\n    value: ''\n  - key: readability-identifier-naming.LocalConstantCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.LocalConstantPrefix\n    value: ''\n  - key: readability-identifier-naming.LocalConstantSuffix\n    value: ''\n  - key: readability-identifier-naming.LocalVariableCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.LocalVariablePrefix\n    value: ''\n  - key: readability-identifier-naming.LocalVariableSuffix\n    value: ''\n  - key: readability-identifier-naming.MemberCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.MemberPrefix\n    value: ''\n  - key: readability-identifier-naming.MemberSuffix\n    value: ''\n  - key: readability-identifier-naming.MethodCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.MethodPrefix\n    value: ''\n  - key: readability-identifier-naming.MethodSuffix\n    value: ''\n  - key: readability-identifier-naming.NamespaceCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.NamespacePrefix\n    value: ''\n  - key: readability-identifier-naming.NamespaceSuffix\n    value: ''\n  - key: readability-identifier-naming.ParameterCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.ParameterPackCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.ParameterPackPrefix\n    value: ''\n  - key: readability-identifier-naming.ParameterPackSuffix\n    value: ''\n  - key: readability-identifier-naming.ParameterPrefix\n    value: ''\n  - key: readability-identifier-naming.ParameterSuffix\n    value: ''\n  - key: readability-identifier-naming.PrivateMemberCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.PrivateMemberPrefix\n    value: ''\n  - key: readability-identifier-naming.PrivateMemberSuffix\n    value: ''\n  - key: readability-identifier-naming.PrivateMethodCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.PrivateMethodPrefix\n    value: ''\n  - key: readability-identifier-naming.PrivateMethodSuffix\n    value: ''\n  - key: readability-identifier-naming.ProtectedMemberCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.ProtectedMemberPrefix\n    value: ''\n  - key: readability-identifier-naming.ProtectedMemberSuffix\n    value: ''\n  - key: readability-identifier-naming.ProtectedMethodCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.ProtectedMethodPrefix\n    value: ''\n  - key: readability-identifier-naming.ProtectedMethodSuffix\n    value: ''\n  - key: readability-identifier-naming.PublicMemberCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.PublicMemberPrefix\n    value: ''\n  - key: readability-identifier-naming.PublicMemberSuffix\n    value: ''\n  - key: readability-identifier-naming.PublicMethodCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.PublicMethodPrefix\n    value: ''\n  - key: readability-identifier-naming.PublicMethodSuffix\n    value: ''\n  - key: readability-identifier-naming.StaticConstantCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.StaticConstantPrefix\n    value: ''\n  - key: readability-identifier-naming.StaticConstantSuffix\n    value: ''\n  - key: readability-identifier-naming.StaticVariableCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.StaticVariablePrefix\n    value: ''\n  - key: readability-identifier-naming.StaticVariableSuffix\n    value: ''\n  - key: readability-identifier-naming.StructCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.StructPrefix\n    value: ''\n  - key: readability-identifier-naming.StructSuffix\n    value: ''\n  - key: readability-identifier-naming.TemplateParameterCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.TemplateParameterPrefix\n    value: ''\n  - key: readability-identifier-naming.TemplateParameterSuffix\n    value: ''\n  - key: readability-identifier-naming.TemplateTemplateParameterCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.TemplateTemplateParameterPrefix\n    value: ''\n  - key: readability-identifier-naming.TemplateTemplateParameterSuffix\n    value: ''\n  - key: readability-identifier-naming.TypeTemplateParameterCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.TypeTemplateParameterPrefix\n    value: ''\n  - key: readability-identifier-naming.TypeTemplateParameterSuffix\n    value: ''\n  - key: readability-identifier-naming.TypedefCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.TypedefPrefix\n    value: ''\n  - key: readability-identifier-naming.TypedefSuffix\n    value: ''\n  - key: readability-identifier-naming.UnionCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.UnionPrefix\n    value: ''\n  - key: readability-identifier-naming.UnionSuffix\n    value: ''\n  - key: readability-identifier-naming.ValueTemplateParameterCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.ValueTemplateParameterPrefix\n    value: ''\n  - key: readability-identifier-naming.ValueTemplateParameterSuffix\n    value: ''\n  - key: readability-identifier-naming.VariableCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.VariablePrefix\n    value: ''\n  - key: readability-identifier-naming.VariableSuffix\n    value: ''\n  - key: readability-identifier-naming.VirtualMethodCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.VirtualMethodPrefix\n    value: ''\n  - key: readability-identifier-naming.VirtualMethodSuffix\n    value: ''\n  - key: readability-simplify-boolean-expr.ChainedConditionalAssignment\n    value: '0'\n  - key: readability-simplify-boolean-expr.ChainedConditionalReturn\n    value: '0'\n"
  },
  {
    "path": ".codeclimate.yml",
    "content": "plugins:\n  cppcheck:\n    enabled: true\n"
  },
  {
    "path": ".ctest/config.cmake",
    "content": "# This file is meant to contain set commands for options\n# you'd normally set at configuration when calling CMake.\n\n# For example, to compile with C++20, uncomment the line below\n# set(CMAKE_CXX_STANDARD 20 CACHE STRING \"C++Standard\")\n"
  },
  {
    "path": ".git-blame-ignore-revs",
    "content": "# This file contains a list of commits that should be ignored by some git commands including `git blame`.\n# You may need to enable this feature in your git configuration by running:\n# - git config blame.ignoreRevsFile .git-blame-ignore-revs\n# Web interfaces of git such as GitHub and GitLab also support this feature.\n# Commits that do not add or change any functionality but only reformat the code or fix typos should be added to this file. This way, when you run `git blame` on a file, you won't see these commits and can focus on the commits that actually changed the code.\n# The following command can be run to check if any of the commits in this file are missing from the history:\n# - for rev in $(grep -vE '^#|^$' .git-blame-ignore-revs); do git rev-parse -q --verify \"${rev}^{commit}\" >/dev/null || echo \"Missing: $rev\"; done\n\nfd828b6155b3caf28676b4568065c0911529618b\nf4b1931dd80ca8708b7802080eb468d5f7a19bda\n683def495afe2567b108fb0de9ad8d3eb1ec43c8\n"
  },
  {
    "path": ".gitignore",
    "content": "*.lo\n*.o\n*.la\n*.pyc\n.libs\n.deps\nMakefile\nMakefile.in\nConsole/eos\nconsole/iam.cfg\nXrdMqOfs/xrdmqclientmaster\nXrdMqOfs/xrdmqclienttest\nXrdMqOfs/xrdmqclientworker\nXrdMqOfs/xrdmqcryptotest\nXrdMqOfs/xrdmqdumper\naclocal.m4\nautom4te.cache\nconfig.guess\nconfig.log\nconfig.status\nconfig.sub\nconfigure\ndepcomp\neos.spec\ninstall-sh\nlibtool\nltmain.sh\nmissing\ndoxy.log\ndoxydoc\n*~\nNamespace/tests/text_runner\nvalgrind.supp\neos-log-repair\n/build*/\n__pycache__\nkineticio-dist.tgz\n# clion specific configs\n/cmake-build*/\n/.idea/\n# eclipse specific configs\n/.settings/\n.cproject\n.project\n.vscode\nnbproject\nmy_clang_cache.cmake\n# ccache specific configs\n/ccache/\n# documentation artifacts\ndoc/_build/\ndoc/html/\ndebian/control\n\n# ApMon specific\nApMon/*.tar.gz\nApMon/rpmbuild*\nApMon/eos-apmon-*\n!ApMon/Makefile\n\n# clangd file\ncompile_commands.json\n.cache\n"
  },
  {
    "path": ".gitlab-ci.yml",
    "content": "# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2023 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nstages:\n  - build:manual\n  - pre-commit\n  - prebuild\n  - build:rpm\n  - build:dockerimage\n  - test\n  - publish\n  - clean\n\nvariables:\n  CODENAME: \"diopside\"\n\ndefault:\n  interruptible: true\n  before_script:\n    - source gitlab-ci/export_commit-type.sh\n    - echo \"Exporting COMMIT_TYPE=${COMMIT_TYPE}\"\n\ninclude:\n  # - template: Code-Quality.gitlab-ci.yml\n  #  - local: /gitlab-ci/.gitlab-ci-test-dock_include.yml @note on the file\n  #  - local: /gitlab-ci/.gitalb-ci-build-macos.yml\n  - local: /gitlab-ci/.gitlab-ci-build-ubuntu.yml\n  - local: /gitlab-ci/.gitlab-ci-test-k8s_include.yml\n  - local: /gitlab-ci/.gitlab-ci-test-helm_include.yml\n\nworkflow:\n  auto_cancel:\n    on_new_commit: interruptible\n\n  rules:\n    - if: $CI_COMMIT_BRANCH\n      variables:\n        #KOJI_SCRATCH: \"--scratch --skip-tag\"\n        KOJI_SCRATCH: \"--scratch\"\n    - if: $CI_COMMIT_TAG\n      variables:\n        KOJI_SCRATCH: \"\"\n#-------------------------------------------------------------------------------\n# Prebuild\n#-------------------------------------------------------------------------------\n.doc-skip:\n  stage: .pre\n  script:\n    - |\n      if git diff --name-only $CI_COMMIT_BEFORE_SHA..$CI_COMMIT_SHA | grep -qv '^docs/'; then\n         echo \"Non doc changes detected, running full pipeline\"\n         exit 0\n      fi\n      echo \"Documentation-only changes detected. Skipping pipeline.\"\n      # In an ideal world this job failing at the top should not trigger builds\n      # we don't live in that world! In order to avoid creating more complex\n      # dependency graphs, we just cancel the pipeline\n      curl -X POST -H \"PRIVATE-TOKEN: $GITLAB_CI_TOKEN\" \"$CI_API_V4_URL/projects/$CI_PROJECT_ID/pipelines/$CI_PIPELINE_ID/cancel\"\n      exit 1\n  allow_failure: true\n  rules:\n    - if: '$CI_PIPELINE_SOURCE == \"schedule\"'\n      when: never\n    - changes:\n        - docs/**/*\n      when: always\n    - when: never\n\n\n.prebuild-template: &prebuild-template_definition\n  stage: prebuild\n  image:\n    name: gcr.io/kaniko-project/executor:debug\n    entrypoint: [\"\"]\n  script:\n    - export DESTINATION=\"gitlab-registry.cern.ch/dss/eos/prebuild-${PREBUILD_NAME}-${CODENAME}\"\n    - export DOCKERFILE=\"$CI_PROJECT_DIR/gitlab-ci/prebuild_OSbase/prebuild-${PREBUILD_NAME}.Dockerfile\"\n    - echo \"{\\\"auths\\\":{\\\"$CI_REGISTRY\\\":{\\\"auth\\\":\\\"$(echo -n $CI_REGISTRY_USER:$CI_REGISTRY_PASSWORD | base64)\\\"}}}\" > /kaniko/.docker/config.json\n    # no need yet for --build-arg PREBUILD_NAME=\"$PREBUILD_NAME\" --build-arg CMAKE_OPTIONS=\"$CMAKE_OPTIONS\" --build-arg CXXFLAGS=\"$CXXFLAGS\"\n    - /kaniko/executor --cache=\"false\" --destination $DESTINATION --dockerfile $DOCKERFILE --context $CI_PROJECT_DIR --build-arg=EOS_CODENAME=\"${CODENAME}\" --compressed-caching=false --use-new-run\n  only:\n    variables:\n      - $PREBUILD_TRIGGER\n\n\nprebuild-el8:\n  extends: .prebuild-template\n  variables:\n    PREBUILD_NAME: el8\n\n\nprebuild-el9:\n  extends: .prebuild-template\n  variables:\n    PREBUILD_NAME: el9\n\n\nprebuild-el10:\n  extends: .prebuild-template\n  variables:\n    PREBUILD_NAME: el10\n\n\nprebuild-el9-arm64:\n  extends: .prebuild-template\n  variables:\n    PREBUILD_NAME: el9-arm64\n  tags:\n    - k8s-arm\n\n\n.prebuild-el9_coverage:\n  extends: .prebuild-template\n  variables:\n    PREBUILD_NAME: el9_coverage\n  only:\n    variables:\n      - $COVERAGE_SCHEDULE\n\n\nclone_docker:\n  stage: build:rpm\n  image: gitlab-registry.cern.ch/linuxsupport/alma9-base\n  script:\n    - dnf install --nogpg -y git\n    - git clone https://gitlab.cern.ch/eos/eos-docker.git\n  artifacts:\n    expire_in: 1 day\n    paths:\n      - eos-docker/\n\n\n#-------------------------------------------------------------------------------\n# Build RPMs\n#-------------------------------------------------------------------------------\n\n.build-template: &build-template_definition\n  stage: build:rpm\n  variables:\n    PKG_MGR: dnf\n    CMAKE_BIN: cmake\n  script:\n    - git submodule sync --recursive && git submodule update --init -f --recursive\n    - mkdir build; cd build; ${CMAKE_BIN} .. -DPACKAGEONLY=1 -DEOS_GRPC_GW=1 -Wno-dev; make srpm; cd ..;\n    - echo -e \"[eos-depend]\\nname=EOS dependencies\\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/${CODENAME}-depend/el-$(rpm --eval '%{rhel}')/$(uname -m)/\\ngpgcheck=0\\nenabled=1\\npriority=2\\n\" >> /etc/yum.repos.d/eos-depend.repo\n    - |\n      if [[ ${PKG_MGR} == \"yum\" ]]; then\n        ${PKG_MGR} remove --nogpgcheck -y eos-xrootd;\n        ${PKG_MGR}-builddep --nogpgcheck -y --setopt=\"cern*.exclude=xrootd*\" build/SRPMS/*;\n      else\n        ${PKG_MGR} builddep --nogpgcheck --allowerasing -y --setopt=\"cern*.exclude=xrootd*\" build/SRPMS/*;\n      fi\n    - |\n      if [[ -n \"$CI_COMMIT_TAG\" ]]; then\n        export CCACHE_DISABLE=1;\n        ${PKG_MGR} install -y gnupg2;\n      else\n        source gitlab-ci/setup_ccache.sh;\n      fi\n    - rpmbuild --rebuild --with server --with eos_grpc_gateway --define \"_rpmdir build/RPMS/\" --define \"_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm\" build/SRPMS/* | (ts 2> /dev/null || true; tee)\n    - ccache -s\n    - if [[ -n \"$CI_COMMIT_TAG\" ]]; then gpg2 --batch --import $STCI_REPO_KEY; printf \"\" | setsid rpmsign --define='%_gpg_name stci@cern.ch' --define='%_signature gpg' --addsign build/RPMS/*.rpm; fi\n    - mkdir ${BUILD_NAME}_artifacts; cp -rv build/*RPMS/ build/eos-*.tar.gz ${BUILD_NAME}_artifacts\n  cache:\n    key: \"$CI_JOB_NAME-$CI_COMMIT_REF_SLUG\"\n    paths:\n      - ccache/\n  artifacts:\n    expire_in: 60 days\n    paths:\n      - ${BUILD_NAME}_artifacts/\n\n\nbuild_el8:\n  image: gitlab-registry.cern.ch/dss/eos/prebuild-el8-${CODENAME}\n  variables:\n    BUILD_NAME: el-8\n  extends: .build-template\n  only:\n    - schedules\n    - tags\n\n\nbuild_el9:\n  image: gitlab-registry.cern.ch/dss/eos/prebuild-el9-${CODENAME}\n  variables:\n    BUILD_NAME: el-9\n  extends: .build-template\n\n\nbuild_el10:\n  image: gitlab-registry.cern.ch/dss/eos/prebuild-el10-${CODENAME}\n  variables:\n    BUILD_NAME: el-10\n  extends: .build-template\n  only:\n    - schedules\n    - tags\n\n\nbuild_el9_arm64:\n  image: gitlab-registry.cern.ch/dss/eos/prebuild-el9-arm64-${CODENAME}\n  variables:\n    BUILD_NAME: el-9-arm64\n  extends: .build-template\n  tags:\n    - k8s-arm\n  only:\n    - schedules\n    - tags\n\n\n#-------------------------------------------------------------------------------\n# EOS client builds for RHEL\n#-------------------------------------------------------------------------------\n\n.build-client-srpm-template: &build-client-srpm-template_definition\n  stage: build:rpm\n  script:\n    - dnf config-manager --add-repo https://linuxsoft.cern.ch/cern/rhel/$(rpm --eval '%{rhel}')/CERN/$(uname -m)/ --set-enabled\n    - dnf install cern-gpg-keys --nogpgcheck -y\n    - rpm --import /etc/pki/rpm-gpg/* || true\n    - dnf install rpm-build cmake gcc-c++ git -y\n    - git submodule sync --recursive && git submodule update --init -f --recursive\n    - mkdir -pv build; cd build;\n    - cmake ../ -DPACKAGEONLY=1 -DCLIENT=1 -Wno-dev\n    - make srpm; cd ..;\n    - mkdir -p ${CI_JOB_NAME}_artifacts\n    - cp -rv build/SRPMS/ ${CI_JOB_NAME}_artifacts\n  artifacts:\n    expire_in: 60 days\n    paths:\n      - ${CI_JOB_NAME}_artifacts\n  only:\n    - schedules\n    - tags\n\n\nrh-8:\n extends: .build-client-srpm-template\n image: gitlab-registry.cern.ch/linuxsupport/ubi8/ubi\n\n\nrh-9:\n extends: .build-client-srpm-template\n image: gitlab-registry.cern.ch/linuxsupport/ubi9/ubi\n\n\nrh-10:\n extends: .build-client-srpm-template\n image: gitlab-registry.cern.ch/linuxsupport/ubi10/ubi\n\n\n#-------------------------------------------------------------------------------\n# Fedora builds\n#-------------------------------------------------------------------------------\n\n.build-fedora-template: &build-fedora-template_definition\n  stage: build:rpm\n  script:\n    - dnf install --nogpg -y gcc-c++ cmake make rpm-build which git tar dnf-plugins-core ccache rpm-sign\n    - git submodule sync --recursive && git submodule update --init -f --recursive\n    - mkdir build; cd build\n    - cmake .. -DPACKAGEONLY=1 -Wno-dev; make srpm; cd ..\n    - echo -e \"[eos-depend]\\nname=EOS dependencies\\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/${CODENAME}-depend/${BUILD_NAME}/x86_64/\\ngpgcheck=0\\nenabled=1\\nexclude=xrootd*\\npriority=4\\n\" > /etc/yum.repos.d/eos-depend.repo\n    - dnf builddep --nogpgcheck --allowerasing -y build/SRPMS/*\n    - if [[ -n \"$CI_COMMIT_TAG\" ]]; then export CCACHE_DISABLE=1; else source gitlab-ci/setup_ccache_fc.sh; fi\n    - rpmbuild --rebuild --with server --define \"_rpmdir build/RPMS/\" --define \"_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm\" build/SRPMS/*\n    - ccache -s\n    - if [[ -n \"$CI_COMMIT_TAG\" ]]; then gpg2 --batch --import $STCI_REPO_KEY; printf \"\" | setsid rpmsign --define='%_gpg_name stci@cern.ch' --define='%_signature gpg' --addsign build/RPMS/*.rpm; fi\n    - mkdir ${BUILD_NAME}_artifacts; cp -R build/SRPMS build/RPMS ${BUILD_NAME}_artifacts\n  cache:\n    key: \"$CI_JOB_NAME-$CI_COMMIT_REF_SLUG\"\n    paths:\n      - ccache/\n  artifacts:\n    expire_in: 60 days\n    paths:\n      - ${BUILD_NAME}_artifacts/\n  allow_failure: true\n\n\nbuild_fedora_38:\n  extends: .build-fedora-template\n  image: registry.fedoraproject.org/fedora:38\n  variables:\n    BUILD_NAME: fc-38\n  only:\n    - schedules\n    - tags\n\n\nbuild_fedora_rawhide:\n  extends: .build-fedora-template\n  image: registry.fedoraproject.org/fedora:rawhide\n  variables:\n    BUILD_NAME: fc-rawhide\n  only:\n    - schedules\n  when: manual\n\n\n#-------------------------------------------------------------------------------\n# Exotic builds\n#-------------------------------------------------------------------------------\n\n.build_exotic-template: &build_exotic-template_definition\n  stage: build:rpm\n  variables:\n    PKG_MGR: dnf\n    CMAKE_CMD: cmake3\n  script:\n    - export DIST=$(rpm --eval '%{rhel}')\n    - ${PKG_MGR} install -y git ccache tar sudo which tar gzip moreutils\n    - git submodule sync --recursive && git submodule update --init -f --recursive\n    - mkdir build; cd build\n    - ${CMAKE_CMD} .. -DPACKAGEONLY=1 ${CMAKE_OPTIONS} -Wno-dev\n    - make srpm; cd ..;\n    - |\n      if [[ \"$RPMBUILD_OPTIONS\" == *asan* ]]; then\n        echo -e \"[eos-asan-depend]\\nname=EOS dependencies\\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/${CODENAME}-depend/el-${DIST}-asan/x86_64/\\ngpgcheck=0\\nenabled=1\\npriority=2\\n\" > /etc/yum.repos.d/eos-depend.repo;\n        # Install the asan enabled dependencies\n        ${PKG_MGR} remove -y eos-xrootd eos-folly eos-grpc eos-rocksdb || true;\n      elif [[ \"$RPMBUILD_OPTIONS\" == *tsan* ]]; then\n        echo -e \"[eos-tsan-depend]\\nname=EOS dependencies\\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/${CODENAME}-depend/el-${DIST}-tsan/x86_64/\\ngpgcheck=0\\nenabled=1\\npriority=2\\n\" > /etc/yum.repos.d/eos-depend.repo;\n        # Install the tsan enabled dependencies\n        ${PKG_MGR} remove -y eos-xrootd eos-folly eos-grpc eos-rocksdb || true;\n      else\n        echo -e \"[eos-depend]\\nname=EOS dependencies\\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/${CODENAME}-depend/el-${DIST}/x86_64/\\ngpgcheck=0\\nenabled=1\\npriority=2\\n\" > /etc/yum.repos.d/eos-depend.repo;\n      fi\n    - |\n      if [[ ${PKG_MGR} == \"yum\" ]]; then\n        ${PKG_MGR}-builddep --nogpgcheck --setopt=\"cern*.exclude=xrootd*\" -y build/SRPMS/*\n      else\n        ${PKG_MGR} install -y dnf-plugins-core\n        ${PKG_MGR} builddep --nogpgcheck --setopt=\"cern*.exclude=xrootd*\" -y build/SRPMS/*\n      fi\n    - mkdir -p ${BUILD_NAME}_artifacts\n    - if [[ -n \"$CI_COMMIT_TAG\" ]]; then export CCACHE_DISABLE=1; else source gitlab-ci/setup_ccache.sh; fi\n    - rpmbuild --rebuild ${RPMBUILD_OPTIONS} --define \"_rpmdir build/RPMS/\" --define \"_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm\" build/SRPMS/* | ts\n    - ccache -s\n    - if [[ -n \"$CI_COMMIT_TAG\" ]]; then gpg2 --batch --import $STCI_REPO_KEY; printf \"\" | setsid rpmsign --define='%_gpg_name stci@cern.ch' --define='%_signature gpg' --addsign build/RPMS/*.rpm; fi\n    - cp -R build/SRPMS/ build/RPMS/ ${BUILD_NAME}_artifacts\n  cache:\n    key: \"$CI_JOB_NAME-$CI_COMMIT_REF_SLUG\"\n    paths:\n      - ccache/\n  artifacts:\n    expire_in: 1 day\n    paths:\n      - ${BUILD_NAME}_artifacts/\n  dependencies: []\n  allow_failure: true\n\n\nbuild_el9_asan:\n  image: gitlab-registry.cern.ch/dss/eos/prebuild-el9-${CODENAME}\n  variables:\n    CMAKE_CMD: cmake\n    BUILD_NAME: el-9-asan\n    CMAKE_OPTIONS: \"-DASAN=1\"\n    RPMBUILD_OPTIONS: \"--with server --with asan\"\n    CXXFLAGS: \"-Wno-parentheses\"    # Avoid boost header compilation errors\n  before_script:\n    - ${PKG_MGR} install -y epel-release libasan cmake gcc gcc-c++ rpmdevtools\n  extends: .build_exotic-template\n  when: manual\n\n\nbuild_client_el9_tsan:\n  image: gitlab-registry.cern.ch/dss/eos/prebuild-el9-${CODENAME}\n  variables:\n    CMAKE_CMD: cmake\n    BUILD_NAME: el-9-tsan\n    CMAKE_OPTIONS: \"-DTSAN=1\"\n    RPMBUILD_OPTIONS: \"--with tsan\"\n    CXXFLAGS: \"-Wno-parentheses\"    # Avoid boost header compilation errors\n  before_script:\n    - ${PKG_MGR} install -y epel-release libtsan cmake gcc gcc-c++ which rpmdevtools\n  extends: .build_exotic-template\n  when: manual\n\n\nbuild_el9_clang:\n  image: gitlab-registry.cern.ch/dss/eos/prebuild-el9-${CODENAME}\n  variables:\n    BUILD_NAME: el-9-clang\n    CMAKE_OPTIONS: \"-DCLANG_BUILD=1\"\n    RPMBUILD_OPTIONS: \"--with clang --with server\"\n  extends: .build_exotic-template\n  only:\n    - schedules\n    - triggers\n\n\n# @note Please contact CTA team / jleduc if you want to change this job\nbuild_cc7_opt_xrootd:\n  image: gitlab-registry.cern.ch/dss/eos/prebuild-cc7-${CODENAME}\n  variables:\n    PKG_MGR: yum\n    BUILD_NAME: cc7_opt_xrootd\n    CMAKE_OPTIONS: \"-DEOS_XROOTD=1\"\n    RPMBUILD_OPTIONS: \"--with eos_xrootd_rh\"\n  before_script:\n    - sed -i \"s/pgm \\/usr\\/bin\\/xrdcp/pgm \\/bin\\/true/g\" misc/etc/xrd.cf.fst\n  except:\n    - tags\n  extends: .build_exotic-template\n  when: manual\n\n\nbuild_el9_coverage:\n  image: gitlab-registry.cern.ch/dss/eos/prebuild-el9-${CODENAME}\n  variables:\n    BUILD_NAME: el9_coverage\n    RPMBUILD_OPTIONS: \"--with coverage\"\n  only:\n    variables:\n      - $COVERAGE_SCHEDULE\n  extends: .build_exotic-template\n\n#-------------------------------------------------------------------------------\n# Build docker images\n#-------------------------------------------------------------------------------\n\n.build_dockerimage-template:\n  stage: build:dockerimage\n  image:\n    name: gcr.io/kaniko-project/executor:debug\n    entrypoint: [\"\"]\n  variables:\n    EXTRA_TAG: \"\"\n  script:\n    # @note keep $CACHE orthogonal to $IMAGE_TAG, do not join the \"if\"s\n    - if [[ -n \"$CI_COMMIT_TAG\" ]] || [[ \"x$CI_PIPELINE_SOURCE\" == \"xschedule\" ]];\n      then CACHE=\"false\";\n      else CACHE=\"true\";\n      fi\n    - if [[ -n \"$CI_COMMIT_TAG\" ]];\n      then IMAGE_TAG=\"$CI_COMMIT_TAG${OS_TAG}${EXTRA_TAG}\";\n      else IMAGE_TAG=\"$CI_COMMIT_SHORT_SHA${OS_TAG}${EXTRA_TAG}\";\n      fi\n    - IMAGE_REPO=\"gitlab-registry.cern.ch/dss/eos/eos-ci\"\n    - DESTINATION=\"${IMAGE_REPO}:${IMAGE_TAG}\"\n    - echo \"CACHE=$CACHE - DESTINATION=$DESTINATION\"\n    - echo \"{\\\"auths\\\":{\\\"$CI_REGISTRY\\\":{\\\"auth\\\":\\\"$(echo -n $CI_REGISTRY_USER:$CI_REGISTRY_PASSWORD | base64)\\\"}}}\" > /kaniko/.docker/config.json\n    - /kaniko/executor --cache=$CACHE --destination $DESTINATION --dockerfile $DOCKERFILE --context $CI_PROJECT_DIR --build-arg=EOS_CODENAME=\"${CODENAME}\" --compressed-caching=false --use-new-run\n  retry: 1\n\n\nel9_docker_image:\n  extends: .build_dockerimage-template\n  variables:\n    DOCKERFILE: eos-docker/Dockerfile_el9\n    OS_TAG: \".el9\"\n  needs:\n    - job: clone_docker\n    - job: build_el9\n\nel10_docker_image:\n  extends: .build_dockerimage-template\n  variables:\n    DOCKERFILE: eos-docker/Dockerfile_el10\n    OS_TAG: \".el10\"\n  needs:\n    - job: clone_docker\n    - job: build_el10\n  only:\n    - schedules\n    - tags\n\n\nel9_asan_docker_image:\n  extends: .build_dockerimage-template\n  variables:\n    DOCKERFILE: eos-docker/Dockerfile_el9_asan\n    EXTRA_TAG: \"_asan\"\n    OS_TAG: \".el9\"\n  needs:\n    - job: clone_docker\n    - job: build_el9_asan\n  when: manual\n  allow_failure: true\n\n\n.el9_coverage_docker_image:\n  extends: .build_dockerimage-template\n  variables:\n    DOCKERFILE: eos-docker/Dockerfile_coverage\n    EXTRA_TAG: \"_coverage\"\n    OS_TAG: \".el9\"\n  needs:\n    - job: clone_docker\n    - job: build_el9_coverage\n  only:\n    variables:\n      - $COVERAGE_SCHEDULE\n  allow_failure: true\n\n\n#-------------------------------------------------------------------------------\n# Code quality, from codeclimate plugins - disabled\n#-------------------------------------------------------------------------------\n\n# .code_quality:\n#   artifacts:\n#     paths: [gl-code-quality-report.json]\n#   rules:\n#     - if: '$CI_PIPELINE_SOURCE == \"schedule\"'\n#       allow_failure: true\n\n# .code_quality_html:\n#   extends: code_quality\n#   variables:\n#     REPORT_FORMAT: html\n#   artifacts:\n#     paths: [gl-code-quality-report.html]\n\n#-------------------------------------------------------------------------------\n# Dock8rnetes testing framework (exec_cmd wraps both docker and k8s!)\n#-------------------------------------------------------------------------------\n\n.dock8s_before_script_template: &dock8s_before_script_template\n  stage: test\n  before_script:\n    - case $CI_JOB_NAME in\n      \"k8s\"*  )\n        source ./gitlab-ci/before_script_k8s_test.sh;\n        source ./gitlab-ci/utilities_func_for_tests.sh --type k8s $K8S_NAMESPACE ;;\n      \"dock\"* )\n        source ./gitlab-ci/before_script_docker_test.sh;\n        source ./gitlab-ci/utilities_func_for_tests.sh --type docker; ;;\n      esac\n  variables:\n    OS_TAG: \".el9\"\n\n\n.dock8s_after_script_template: &dock8s_after_script_template\n  after_script:\n    - case $CI_JOB_NAME in\n      \"k8s\"*  )\n        source ./gitlab-ci/after_script_k8s_test.sh ;;\n      \"dock\"* )\n        source ./gitlab-ci/after_script_docker_test.sh ;;\n      esac\n\n\n.dock8s_system_test_template:\n  extends:\n    - .dock8s_before_script_template\n    - .dock8s_after_script_template\n  script:\n    - date\n    - exec_cmd eos-mgm1 'eos ns mutex --toggleorder'\n    - exec_cmd eos-mgm1 'eos-instance-test-ci'\n    - date\n    - exec_cmd eos-mgm1 'eos-unit-tests-with-instance -n root://localhost//eos/dockertest/'\n    - exec_cmd eos-mgm1 'grep \"RWMutex. Order Checking Error in thread\" /var/log/eos/mgm/xrdlog.mgm && exit 1 || exit 0'\n    - date\n    - cp_to_local_cmd eos-cli1:/usr/sbin/eos-test-utils ./eos-test-utils; chmod +x eos-test-utils\n    - cp_to_local_cmd eos-cli1:/usr/sbin/eos-fst-close-test ./eos-fst-close-test; chmod +x eos-fst-close-test\n    - case $CI_JOB_NAME in\n      \"k8s\"*  )\n        export EOS_MGM_URL=\"root://eos-mgm1.eos-mgm1.$K8S_NAMESPACE.svc.cluster.local\";\n        ./eos-fst-close-test --mgm ${EOS_MGM_URL} --type k8s $K8S_NAMESPACE ;;\n      \"dock\"* )\n        ./eos-fst-close-test --type docker ;;\n      esac\n    - date\n  artifacts:\n    when: on_failure\n    expire_in: 3 days\n    paths:\n      - eos-logs-${CI_JOB_ID}/\n\n\n.dock8s_convert_fsck_recycle_template:\n  extends:\n    - .dock8s_before_script_template\n    - .dock8s_after_script_template\n  script:\n    - cp_to_local_cmd eos-cli1:/usr/sbin/eos-test-utils ./eos-test-utils; chmod +x eos-test-utils\n    - cp_to_local_cmd eos-cli1:/usr/sbin/eos-convert-test ./eos-convert-test; chmod +x eos-convert-test\n    - cp_to_local_cmd eos-cli1:/usr/sbin/eos-fsck-test ./eos-fsck-test; chmod +x eos-fsck-test\n    - cp_to_local_cmd eos-cli1:/usr/sbin/eos-recycle-test ./eos-recycle-test; chmod +x eos-recycle-test\n    - case $CI_JOB_NAME in\n      \"k8s\"*  )\n        ./eos-convert-test --type k8s $K8S_NAMESPACE;\n        ./eos-fsck-test --max-delay 600 --type k8s $K8S_NAMESPACE;\n        ./eos-recycle-test --type k8s $K8S_NAMESPACE;;\n      \"dock\"* )\n        ./eos-convert-test --type docker;\n        ./eos-fsck-test --max-delay 600 --type docker;\n        ./eos-recycle-test --type docker;;\n      esac\n    - rm -rf eos-test-utils\n    - rm -rf eos-convert-test\n    - rm -rf eos-fsck-test\n    - rm -rf eos-recycle-test\n\n  artifacts:\n    when: on_failure\n    expire_in: 3 days\n    paths:\n      - eos-logs-${CI_JOB_ID}/\n\n\n.dock8s_rtb_clone_template:\n  extends:\n    - .dock8s_before_script_template\n    - .dock8s_after_script_template\n  script:\n    # prepare mountpoints\n    - exec_cmd eos-cli1 'atd; at now <<< \"mkdir -p /eos1/ && mount -t fuse eosxd -ofsname=mount-1 /eos1/; mkdir -p /eos2/ && mount -t fuse eosxd -ofsname=mount-2 /eos2/;\"'\n    - exec_cmd eos-cli1 'count=0; while [[ $count -le 10 ]] && ( [[ ! -d /eos1/dockertest/ ]] || [[ ! -d /eos2/dockertest/ ]] ); do echo \"Wait for mount... $count\"; (( count++ )); sleep 1; done;'\n    # download tests repo\n    - exec_cmd eos-cli1 'git clone https://gitlab.cern.ch/dss/eosclient-tests.git'\n    - exec_cmd eos-cli1 'cd /eosclient-tests && pip install -r requirements.txt'\n    # ubuntu releases do not support 'clone' yet, skip its test\n    - case $CI_JOB_NAME in\n      \"ub_focal\"* | \"ub_jammy\"* ) ;;\n      *                          ) exec_cmd eos-cli1 'cd /eosclient-tests; clone_tests/clone_test.sh prepare; rc=$?; exit $rc' ;;\n      esac\n  artifacts:\n    when: on_failure\n    expire_in: 3 days\n    paths:\n      - eos-logs-${CI_JOB_ID}/\n\n\n.dock8s_fusex_test_template:\n  extends:\n    - .dock8s_before_script_template\n    - .dock8s_after_script_template\n  script:\n    # prepare mountpoints\n    - exec_cmd eos-cli1 'atd; at now <<< \"mkdir -p /eos1/ && mount -t fuse eosxd -ofsname=mount-1 /eos1/; mkdir -p /eos2/ && mount -t fuse eosxd -ofsname=mount-2 /eos2/;\"'\n    - exec_cmd eos-cli1 'count=0; while [[ $count -le 10 ]] && ( [[ ! -d /eos1/dockertest/ ]] || [[ ! -d /eos2/dockertest/ ]] ); do echo \"Wait for mount... $count\"; (( count++ )); sleep 1; done;'\n    # fusex functional bindings\n    - exec_cmd eos-cli1 'atd; at now <<< \"mkdir -p /eosfunctionaltest/ && mount -t fuse eosxd -ofsname=eosdockertest /eosfunctionaltest/;\"'\n    - exec_cmd eos-cli1 'count=0; while [[ $count -le 10 ]] && [[ ! -d /eosfunctionaltest/dockertest/ ]] ; do echo \"Wait for mount... $count\"; (( count++ )); sleep 1; done;'\n    - exec_cmd eos-cli1 'su eos-user -c \"mkdir -m 700 -p /eosfunctionaltest/dockertest/credentialtest/ && cd /eosfunctionaltest/dockertest/credentialtest/\"'\n    - exec_cmd eos-cli1 'su eos-user -c \"eos-test-credential-bindings /eosfunctionaltest/dockertest/credentialtest/\"'\n    # fusex benchmark\n    - exec_cmd eos-mgm1 'eos ns mutex --toggleorder'\n    - exec_cmd eos-cli1 'su eos-user -c \"mkdir -p /eos1/dockertest/fusex_tests/ && cd /eos1/dockertest/fusex_tests/ && fusex-benchmark\"'\n    - exec_cmd eos-mgm1 'grep \"RWMutex. Order Checking Error in thread\" /var/log/eos/mgm/xrdlog.mgm && exit 1 || exit 0'\n    # download tests repo\n    - exec_cmd eos-cli1 'git clone https://gitlab.cern.ch/dss/eosclient-tests.git'\n    - exec_cmd eos-cli1 'cd /eosclient-tests && pip install -r requirements.txt'\n    # run the tests\n\n    # @todo(esindril): run \"all\" tests in schedule mode once these are properly supported\n    # if [[ \"$CI_PIPELINE_SOURCE\" == \"schedule\" ]];\n    # then\n    #   exec_cmd eos-mgm1 'eos vid add gateway \"eos-cli1.eos-cli1.${K8S_NAMESPACE}.svc.cluster.local\" unix';\n    #   exec_cmd eos-cli1 'env EOS_FUSE_NO_ROOT_SQUASH=1 python3 /eosclient-tests/run.py --workdir=\"/eos1/dockertest /eos2/dockertest\" ci';\n    # fi\n    # until then just run the \"ci\" tests\n    - exec_cmd eos-cli1 'cd eosclient-tests; for n in prepare/*.sh; do /bin/bash $n prepare; done'\n    - exec_cmd eos-cli1 'su eos-user -c \"python3 /eosclient-tests/run.py --workdir=\\\"/eos1/dockertest /eos2/dockertest\\\" ci\"'\n    - exec_cmd eos-cli1 'cd eosclient-tests; for n in prepare/*.sh; do /bin/bash $n cleanup; done'\n\n    # fusex test SAMBA gateways authentication settings\n    # this will run on the client pod\n    - exec_cmd eos-mgm1 'eos vid enable sss'\n    - exec_cmd eos-mgm1 'eos vid enable unix'\n    - CLI_POD_HOSTNAME=\"$(exec_cmd eos-cli1 'hostname -f')\"\n    - echo ${CLI_POD_HOSTNAME}\n    - exec_cmd eos-mgm1 \"eos vid add gateway ${CLI_POD_HOSTNAME} unix\"\n    - exec_cmd eos-cli1 'eos-fusex-functional-test --samba'\n  artifacts:\n    when: on_failure\n    expire_in: 3 days\n    paths:\n      - eos-logs-${CI_JOB_ID}/\n\n.dock8s_cbox_test_template:\n  extends:\n    - .dock8s_before_script_template\n    - .dock8s_after_script_template\n  script:\n    # enable converter and prepare eoshome folder, cernbox alike\n    - exec_cmd eos-mgm1 'eos convert config set status=on'\n    - exec_cmd eos-mgm1 './eos_create_userhome.sh eos-user'\n    # prepare mountpoints\n    - exec_cmd eos-cli1 'atd; at now <<< \"mkdir -p /eos/ && mount -t fuse eosxd -ofsname=eosdockertest /eos/\"'\n    - exec_cmd eos-cli1 'count=0; while [[ $count -le 10 ]] && ( [[ ! -d /eos/ ]] ); do echo \"Wait for mount... $count\"; (( count++ )); sleep 1; done;'\n    # set krb5 ticket and download tests repo @note the 'export KRB5CCNAME to FILE: type' is a spooky trick, can be made nicer.\n    - exec_cmd eos-cli1 'echo -e \"export KRB5CCNAME=FILE:/tmp/krb5cc_$(id -u eos-user)\" >> ~/.bashrc'\n    - exec_cmd eos-cli1 'su eos-user -c \"kinit eos-user@TEST.EOS -k -t /home/eos-user/eos-user.keytab\"'\n    - exec_cmd eos-cli1 'su eos-user -c \"git clone https://gitlab.cern.ch/dss/eosclient-tests.git /eos/user/e/eos-user/eosclient-tests\"'\n    - exec_cmd eos-cli1 'su eos-user -c \"cd /eos/user/e/eos-user/eosclient-tests && pip install -r requirements.txt\"'\n    # launch the tests\n    - exec_cmd eos-cli1 'su eos-user -c \"cd /eos/user/e/eos-user && python3 ./eosclient-tests/run.py --workdir=/eos/user/e/eos-user ci-eosfuse_release\"'\n    - exec_cmd eos-cli1 'su eos-user -c \"cd /eos/user/e/eos-user && python3 ./eosclient-tests/run.py --workdir=/eos/user/e/eos-user regression\"'\n  artifacts:\n    when: on_failure\n    expire_in: 3 days\n    paths:\n      - eos-logs-${CI_JOB_ID}/\n\n.dock8s_reva_test_template:\n  extends:\n    - .dock8s_before_script_template\n    - .dock8s_after_script_template\n  script:\n    # enable converter and prepare eoshome folder, cernbox alike\n    - exec_cmd eos-mgm1 'eos convert config set status=on'\n    - exec_cmd eos-mgm1 './eos_create_userhome.sh eos-user'\n    # prepare mountpoints\n    - exec_cmd eos-cli1 'atd; at now <<< \"mkdir -p /eos/ && mount -t fuse eosxd -ofsname=eosdockertest /eos/\"'\n    - exec_cmd eos-cli1 'count=0; while [[ $count -le 10 ]] && ( [[ ! -d /eos/ ]] ); do echo \"Wait for mount... $count\"; (( count++ )); sleep 1; done;'\n\n    # install dependencies\n    - exec_cmd eos-mgm1 'yum -y install nodejs npm git make tar'\n\n    # Install go\n    - exec_cmd eos-mgm1 \"export PATH=\\\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH\\\" && /usr/bin/wget https://go.dev/dl/go1.25.1.linux-amd64.tar.gz && /usr/bin/tar -C /usr/local -xzf go1.25.1.linux-amd64.tar.gz && ln -s /usr/local/go/bin/go /usr/bin/go && ln -s /usr/local/go/bin/gofmt /usr/bin/gofmt || echo Failed to install golang\"\n    - exec_cmd eos-mgm1 'npm install -g @intuit/judo'\n\n    # Build reva from latest tag\n    - exec_cmd eos-mgm1 'git clone https://github.com/cs3org/reva.git'\n    - |\n      exec_cmd eos-mgm1 'export PATH=\"$(/usr/bin/go env GOPATH)/bin:$PATH\"; cd reva; git fetch --tags ;\n      export latest_tag=$(git tag --sort=-version:refname | grep -E \"^v[0-9]+\\.[0-9]+\\.[0-9]+$\" | head -1) ;\n      echo \"Building reva tag: $latest_tag\" ;\n      git checkout \"$latest_tag\"; make reva; make cernbox-revad; mkdir -p ../tmp'\n\n    # Put users.json and groups.json in the right place\n    - exec_cmd eos-mgm1 'cp reva/tests/integration/reva-cli/config/users.json tmp/users.json'\n    - exec_cmd eos-mgm1 'cp reva/tests/integration/reva-cli/config/groups.json tmp/groups.json'\n\n    # Put eos path as basepath for tests:\n    - |\n      exec_cmd eos-mgm1 \\\n      \"find reva/tests/integration/reva-cli -type f -name '*.yaml' -exec sed -i 's|^[[:space:]]*BASEDIR: \\\"/localfs\\\"$|  BASEDIR: \\\"/eos/test\\\"|' {} +\"\n\n    # Prepare mgm (add users, set vid mapping, create directory)\n    - exec_cmd eos-mgm1 \"groupadd -g 123 myusers\"\n    - exec_cmd eos-mgm1 \"useradd -u 1255 -g myusers testuser\"\n    - exec_cmd eos-mgm1 \"useradd -u 1256 -g myusers testreceivinguser\"\n    - exec_cmd eos-mgm1 \"eos mkdir /eos/test\"\n    - exec_cmd eos-mgm1 \"eos chown 1255:123 /eos/test\"\n    - exec_cmd eos-mgm1 \"eos acl --sys u:1255=rwx /eos\"\n    - exec_cmd eos-mgm1 \"eos recycle config --add-bin /eos/\"\n    - exec_cmd eos-mgm1 \"eos attr set sys.versioning=\\\"10\\\" /eos/test\"\n    - exec_cmd eos-mgm1 \"eos attr set sys.forced.atomic=\\\"1\\\" /eos/test\"\n    - exec_cmd eos-mgm1 \"eos attr set sys.allow.oc.sync=\\\"1\\\" /eos/test\"\n    - exec_cmd eos-mgm1 \"eos attr ls /eos/test\"\n    - exec_cmd eos-mgm1 \"eos vid add gateway \\\"127.0.0.1\\\" grpc\"\n    - exec_cmd eos-mgm1 \"eos vid add gateway \\\"[:1]\\\" grpc\"\n    - exec_cmd eos-mgm1 \"eos vid add gateway \\\"[::1]\\\" grpc\"\n    - exec_cmd eos-mgm1 \"eos vid add gateway \\\"127.0.0.1\\\" https\"\n    - exec_cmd eos-mgm1 \"eos vid add gateway \\\"[:1]\\\" https\"\n    - exec_cmd eos-mgm1 \"eos vid add gateway \\\"[::1]\\\" https\"\n    - exec_cmd eos-mgm1 \"eos vid set map -grpc key:auth_key vuid:11  vgid:11\"\n    - exec_cmd eos-mgm1 \"eos vid set map -https key:auth_key vuid:11  vgid:11\"\n    - exec_cmd eos-mgm1 \"eos vid set membership 11 +sudo\"\n    - exec_cmd eos-mgm1 \"eos vid set membership 11 -uids 3\"\n    - exec_cmd eos-mgm1 \"eos vid set membership 11 -gids 4\"\n    - exec_cmd eos-mgm1 \"eos access allow group myusers\"\n    - echo ${MGM_POD_HOSTNAME}\n\n    # We connect over https, and the certificate is only valid for the hostname, so replace localhost with the MGM's hostname\n    - exec_cmd eos-mgm1 'sed -i \"s/^\\(master_url = \\\"https:\\/\\/\\)localhost\\(:[0-9][0-9]*\\\"\\)/\\1$(hostname -f)\\2/\" reva/tests/integration/reva-cli/config/revad-eos.toml'\n\n    # Start revad\n    - exec_cmd eos-mgm1 './reva/cmd/revad/revad -c reva/tests/integration/reva-cli/config/revad-eos.toml </dev/null >revad.log 2>&1 & echo $! > revad.pid'\n\n    # Tests and debug output\n    - exec_cmd eos-mgm1 \"cd reva; make test-reva-cli\"\n    - exec_cmd eos-mgm1 \"eos vid ls\"\n    - exec_cmd eos-mgm1 \"eos access ls\"\n    - exec_cmd eos-mgm1 \"cat revad.log\"\n  artifacts:\n    when: on_failure\n    expire_in: 3 days\n    paths:\n      - eos-logs-${CI_JOB_ID}/\n  allow_failure: true\n\n\n.dock8s_flamegraph_test_template:\n  extends:\n    - .dock8s_before_script_template\n    - .dock8s_after_script_template\n  script:\n    - date\n    - echo 0 > /proc/sys/kernel/perf_event_paranoid; cat /proc/sys/kernel/perf_event_paranoid\n    - echo 0 > /proc/sys/kernel/kptr_restrict; cat /proc/sys/kernel/kptr_restrict\n    - exec_cmd eos-mgm1 \"mkdir eos-flamegraph-data; cd eos-flamegraph-data; /usr/sbin/eos-make-flamegraph\"\n  artifacts:\n    expire_in: 1 days\n    paths:\n      - eos-logs-${CI_JOB_ID}/\n\n\n.unit_test_template: &unit_test_template_definition\n  stage: test\n  variables:\n    OS_TAG: \".el9\"\n  script:\n    # generic unit tests\n    - eos-unit-tests\n    - eos-unit-tests-fst\n    - eos-fusex-tests\n    - pip3 install pytest; python3 -m pytest /usr/sbin/test-eos-iam-mapfile.py\n    # namespace specific unit tests\n    - export EOS_QUARKDB_HOSTPORT=localhost:7777\n    - quarkdb-create --path /var/quarkdb/node-0\n    - chown -R daemon:daemon /var/quarkdb/node-0\n    - xrootd -n qdb -c /etc/xrd.cf.quarkdb -l /var/log/eos/xrdlog.qdb -b -Rdaemon\n    - eos-ns-quarkdb-tests\n    - cp /usr/sbin/qclient-tests . && GTEST_DEATH_TEST_USE_FORK=1 ./qclient-tests\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n  retry: 1\n  tags:\n     - docker_node\n     - dock\n\n\nunit_test:tag:\n  extends: .unit_test_template\n  image:\n    name: gitlab-registry.cern.ch/dss/eos/eos-ci:${CI_COMMIT_TAG}${OS_TAG}\n    entrypoint: [\"/bin/bash\", \"-c\"]\n  only:\n    - tags\n\n\nunit_test:\n  extends: .unit_test_template\n  image:\n    name: gitlab-registry.cern.ch/dss/eos/eos-ci:${CI_COMMIT_SHORT_SHA}${OS_TAG}\n    entrypoint: [\"/bin/bash\", \"-c\"]\n  except:\n    - tags\n\n\nunit_test_asan:\n  extends: .unit_test_template\n  image:\n    name: gitlab-registry.cern.ch/dss/eos/eos-ci:${CI_COMMIT_SHORT_SHA}${OS_TAG}${EXTRA_TAG}\n    entrypoint: [\"/bin/bash\", \"-c\"]\n  variables:\n    LSAN_OPTIONS: \"suppressions=/var/eos/test/LeakSanitizer.supp\"  # Suppress known memory leaks. For the generic tests\n    ASAN_OPTIONS: \"fast_unwind_on_malloc=0\"  # Avoid indirect leaks from linked dependencies. For the namespace tests\n    EXTRA_TAG: \"_asan\"\n  needs:\n    - job: el9_asan_docker_image\n      artifacts: false\n  when: manual\n  allow_failure: true\n\n\n#-------------------------------------------------------------------------------\n# RPM publishing\n#-------------------------------------------------------------------------------\n\n.publish_koji_template: &publish_koji_template_definition\n  stage: publish\n  image: gitlab-registry.cern.ch/linuxsupport/rpmci/kojicli\n  script:\n    - yum install --nogpg -y sssd-client\n    - kinit stci@CERN.CH -k -t /stci.krb5/stci.keytab\n    # KOJI_SCRATCH will be set for branches and empty for tags\n    - koji build ${KOJI_SCRATCH} ${TARGET} ${BUILD_NAME}_artifacts/SRPMS/*.src.rpm\n  tags:\n    - docker_node\n    - publish\n  when: manual\n\n\npublish_koji_al8:\n  <<: *publish_koji_template_definition\n  variables:\n    TARGET: \"eos8al\"\n    BUILD_NAME: \"el-8\"\n  only:\n    - schedules\n    - tags\n  needs:\n    - job: build_el8\n      artifacts: true\n\n\npublish_koji_al9:\n  <<: *publish_koji_template_definition\n  variables:\n    TARGET: \"eos9al\"\n    BUILD_NAME: \"el-9\"\n  needs:\n    - job: build_el9\n      artifacts: true\n\n\npublish_koji_al10:\n  <<: *publish_koji_template_definition\n  variables:\n    TARGET: \"eos10al\"\n    BUILD_NAME: \"el-10\"\n  only:\n    - schedules\n    - tags\n  needs:\n    - job: build_el10\n      artifacts: true\n\n\npublish_koji_rh-8:\n  <<: *publish_koji_template_definition\n  variables:\n    TARGET: \"eos8el\"\n    BUILD_NAME: \"rh-8\"\n  only:\n    - schedules\n    - tags\n  needs:\n    - job: rh-8\n\n\npublish_koji_rh-9:\n  <<: *publish_koji_template_definition\n  variables:\n    TARGET: \"eos9el\"\n    BUILD_NAME: \"rh-9\"\n  only:\n    - schedules\n    - tags\n  needs:\n    - job: rh-9\n\n\npublish_koji_rh-10:\n  <<: *publish_koji_template_definition\n  variables:\n    TARGET: \"eos10el\"\n    BUILD_NAME: \"rh-10\"\n  only:\n    - schedules\n    - tags\n  needs:\n    - job: rh-10\n\n\n\nemail_notification:\n   stage: publish\n   image: gitlab-registry.cern.ch/linuxsupport/alma9-base\n   variables:\n     ENV: production\n     TO_ADDRS: project-eos-commits@cern.ch,lxbatch-experts@cern.ch\n   script:\n     - dnf install -y git python pip\n     - git clone https://token:$EOS_REPO_MAILSERVICE_TOKEN@gitlab.cern.ch/eos/eos-mailservices-code-samples.git\n     - cd eos-mailservices-code-samples/Python/oauth2-samples; pip install --no-input -r requirements.txt\n     - export EOS_VERSION=$CI_COMMIT_TAG\n     - python -m oauth2_smtp\n   needs:\n     - job: publish_koji_al9\n   only:\n     - tags\n\n# This job uses CI_JOB_TOKEN to trigger a pipeline in the CTA project.\n# This means that whoever triggers this job, must have the rights to start a pipeline in CTA.\nnotify_cta_project:\n  stage: publish\n  image: gitlab-registry.cern.ch/linuxsupport/alma9-base\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n  variables:\n    CTA_BRANCH: main\n  script:\n    - dnf install --nogpg -y curl gawk jq\n    - CTA_PROJECT_ID=\"139306\"\n    - CTA_PROJECT_API=\"https://gitlab.cern.ch/api/v4/projects/$CTA_PROJECT_ID\"\n    - TRIGGER_URL=\"$CTA_PROJECT_API/trigger/pipeline\"\n    # Construct EOS versions\n    - |\n      if [[ -z \"$CI_COMMIT_TAG\" ]]; then\n        EOS_IMAGE_TAG=\"$CI_COMMIT_SHORT_SHA.el9\";\n      else\n        EOS_IMAGE_TAG=\"$CI_COMMIT_TAG.el9\";\n        EOS_VERSION=$(dnf -q --repofrompath=temprepo,https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/testing/el-9/x86_64/ --repo=temprepo repoquery --qf \"%{version}-%{release}\\n\" eos-client | grep \"${CI_COMMIT_TAG}\" | tail -1);\n        if [[ -z ${EOS_VERSION} ]]; then\n          echo \"ERROR: Could not find EOS RPMs for ${CI_COMMIT_TAG} in storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/testing/el-9/x86_64/\"\n          exit 1\n        fi\n      fi\n    # Extract XRootD version from eos.spec.in\n    - xrd_min=$(grep '^%define xrootd_version_min' eos.spec.in | awk '{print $3}')\n    - XROOTD_VERSION=$(dnf -q --repofrompath=temprepo,https://xrootd.web.cern.ch/repo/testing/el9/x86_64/ --repo=temprepo repoquery --qf \"%{epoch}:%{version}-%{release}\\n\" xrootd | grep \"${xrd_min}\" | tail  -1)\n    # Get the latest stable CTA version\n    - LATEST_CTA_VERSION=$(dnf -q --repofrompath=temprepo,https://cta-public-repo.web.cern.ch/stable/cta-5/el9/cta/x86_64/ --repo=temprepo repoquery --latest-limit=1 --qf \"%{version}-%{release}\" cta-taped)\n    # Against an existing CTA tag, we can only trigger a pipeline where a different EOS image is used\n    - echo \"Triggering pipeline against CTA version $LATEST_CTA_VERSION with EOS image tag $EOS_IMAGE_TAG\"\n    - curl -X POST\n          -F token=$CI_JOB_TOKEN\n          -F ref=$CTA_BRANCH\n          -F \"variables[CUSTOM_EOS_IMAGE_TAG]=$EOS_IMAGE_TAG\"\n          -F \"variables[CUSTOM_CTA_VERSION]=$LATEST_CTA_VERSION\"\n          -F \"variables[PIPELINE_TYPE]=REGR_AGAINST_CTA_VERSION\"\n          $TRIGGER_URL\n    # Against the CTA main branch, we can also test the client EOS and XRootD versions if we are on a (EOS) tag\n    # If we are not on a (EOS) tag, the EOS RPMs are not accessible by the CTA pipeline, so we only test against the image\n    - |\n      if [[ -z \"$CI_COMMIT_TAG\" ]]; then\n        echo \"Triggering pipeline against CTA main branch\"\n        echo \"Using EOS image tag $EOS_IMAGE_TAG and XRootD version $XROOTD_VERSION\"\n        curl -X POST \\\n            -F token=$CI_JOB_TOKEN \\\n            -F ref=$CTA_BRANCH \\\n            -F \"variables[CUSTOM_EOS_IMAGE_TAG]=$EOS_IMAGE_TAG\" \\\n            -F \"variables[CUSTOM_XROOTD_VERSION]=$XROOTD_VERSION\" \\\n            -F \"variables[PIPELINE_TYPE]=REGR_AGAINST_CTA_MAIN\" \\\n            $TRIGGER_URL\n      else\n        echo \"Triggering pipeline against CTA main branch\"\n        echo \"Using EOS image tag $EOS_IMAGE_TAG, EOS client version $EOS_VERSION and XRootD version $XROOTD_VERSION\"\n        curl -X POST \\\n            -F token=$CI_JOB_TOKEN \\\n            -F ref=$CTA_BRANCH \\\n            -F \"variables[CUSTOM_EOS_IMAGE_TAG]=$EOS_IMAGE_TAG\" \\\n            -F \"variables[CUSTOM_EOS_VERSION]=$EOS_VERSION\" \\\n            -F \"variables[CUSTOM_XROOTD_VERSION]=$XROOTD_VERSION\" \\\n            -F \"variables[PIPELINE_TYPE]=REGR_AGAINST_CTA_MAIN\" \\\n            $TRIGGER_URL\n      fi\n  rules:\n    - if: '$CI_COMMIT_TAG'\n      when: on_success\n    - when: manual\n      allow_failure: true\n\nrpm_commit_artifacts:\n  stage: publish\n  image: gitlab-registry.cern.ch/linuxsupport/alma9-base\n  needs:\n    - job: build_el8\n      artifacts: true\n      optional: true\n    - job: build_el9\n      artifacts: true\n    - job: build_el10\n      artifacts: true\n      optional: true\n    - job: build_el9_arm64\n      artifacts: true\n      optional: true\n    - job: build_fedora_38\n      artifacts: true\n      optional: true\n  script:\n    - dnf install --nogpg -y sudo sssd-client createrepo\n    - if [[ -n \"$CI_COMMIT_TAG\" ]]; then echo \"This only works for commits\"; exit 0; else BUILD_TYPE=\"commit\"; fi\n    - sudo -u stci -H ./gitlab-ci/store_artifacts.sh ${CODENAME} ${BUILD_TYPE} /eos/project/s/storage-ci/www/eos\n  tags:\n    - docker_node\n    - publish\n  except:\n    - tags\n  allow_failure: true\n  when: manual\n\n\nrpm_testing_artifacts:\n  stage: publish\n  image: gitlab-registry.cern.ch/linuxsupport/alma9-base\n  script:\n    - dnf install --nogpg -y sudo sssd-client createrepo\n    - if [[ -n \"$CI_COMMIT_TAG\" ]]; then BUILD_TYPE=\"tag/testing\"; else BUILD_TYPE=\"commit\"; fi\n    - sudo -u stci -H ./gitlab-ci/store_artifacts.sh ${CODENAME} ${BUILD_TYPE} /eos/project/s/storage-ci/www/eos\n  tags:\n    - docker_node\n    - publish\n  only:\n    - master\n    - tags\n  retry: 1\n\n\nrpm_stable_artifacts:\n  stage: publish\n  image: gitlab-registry.cern.ch/linuxsupport/alma9-base\n  script:\n    - dnf install --nogpg -y sudo sssd-client createrepo\n    - if [[ -n \"$CI_COMMIT_TAG\" ]]; then BUILD_TYPE=\"tag\"; else echo \"This only works for tags\"; exit 0; fi\n    - ./gitlab-ci/store_artifacts.sh ${CODENAME} ${BUILD_TYPE} /mnt/eos_repositories/eos\n    - sudo -u stci -H ./gitlab-ci/store_stable_artifacts.sh ${CODENAME} /eos/project/s/storage-ci/www/eos ${CI_COMMIT_TAG}\n    - echo ${CI_COMMIT_TAG} | sudo -u stci tee /eos/project/s/storage-ci/www/eos/${CODENAME}/tag/latest_version\n  tags:\n    - docker_node\n    - publish\n  only:\n    - tags\n  dependencies: []\n  when: manual\n\n\n#to be run after the rpm publish\n.publish_dockerimage-template:\n  stage: publish\n  image:\n    name: gcr.io/kaniko-project/executor:debug\n    entrypoint: [\"\"]\n  script:\n    - if [[ -n \"$CI_COMMIT_TAG\" ]]; then\n        export REPOBRANCH=\"tag-testing\";\n        export DESTINATION=\"${IMAGE_REPO}:${CI_COMMIT_TAG}${OS_TAG}\";\n      else\n        export REPOBRANCH=\"commit\";\n        export DESTINATION=\"${IMAGE_REPO}:${CI_COMMIT_SHORT_SHA}${OS_TAG}\";\n      fi\n    - echo \"{\\\"auths\\\":{\\\"$CI_REGISTRY\\\":{\\\"auth\\\":\\\"$(echo -n $CI_REGISTRY_USER:$CI_REGISTRY_PASSWORD | base64)\\\"}}}\" > /kaniko/.docker/config.json\n    - /kaniko/executor --cache=false --destination $DESTINATION --dockerfile $DOCKERFILE --context $CI_PROJECT_DIR --build-arg=EOS_CODENAME=\"${CODENAME}\" --build-arg=REPOBRANCH=\"${REPOBRANCH}\" --compressed-caching=false --use-new-run\n  retry: 1\n\n\nel9_publish_dockerimage_all:\n  extends: .publish_dockerimage-template\n  variables:\n    DOCKERFILE: eos-docker/minimal/el9_minimal.Dockerfile\n    IMAGE_REPO: \"gitlab-registry.cern.ch/dss/eos/eos-all\"\n    OS_TAG: \".el9\"\n  needs:\n    - job: clone_docker\n    - job: build_el9\n  allow_failure: true\n  when: manual\n\n\nel9_publish_dockerimage_fusex:\n  extends: .publish_dockerimage-template\n  variables:\n    DOCKERFILE: eos-docker/minimal/el9_minimal.fusex-only.Dockerfile\n    IMAGE_REPO: \"gitlab-registry.cern.ch/dss/eos/eos-fusex\"\n    OS_TAG: \".el9\"\n  needs:\n    - job: clone_docker\n    - job: build_el9\n  allow_failure: true\n  when: manual\n\n\n#-------------------------------------------------------------------------------\n# RPM cleaning\n#-------------------------------------------------------------------------------\n\nclean_rpm_artifacts:\n  stage: clean\n  image: gitlab-registry.cern.ch/linuxsupport/alma9-base\n  script:\n    - dnf install --nogpg -y sssd-client sudo createrepo\n    - sudo -u stci -H ./gitlab-ci/remove_old_artifacts.sh\n  allow_failure: true\n  only:\n    - triggers\n    - schedules\n  tags:\n    - docker_node\n\n\n# get all the namespaces, filter out the \"mgmt\" ones, delete if older than 30h\nclean_k8s_cluster:\n  stage: clean\n  image: alpine/k8s:1.18.2\n  script:\n    - export KUBECONFIG=$K8S_CONFIG\n    - set +o pipefail\n    - kubectl get namespaces --no-headers | grep -v 'default\\|kube-node-lease\\|kube-public\\|kube-system\\|magnum-tiller' |\n        awk 'match($3,/(([3-9][0-9]|[1-9][0-9][0-9]+)h|[1-9][0-9]*d)/) {print $1}' | xargs --no-run-if-empty kubectl delete namespaces\n  dependencies: []\n  allow_failure: true\n  only:\n    - schedules\n  tags:\n    - docker_node\n    - k8s\n\n\n# @todo cleanup helm leftover for failed / hanging tests. May be merged to 'clean_k8s_cluster'\nclean_helm_cluster:\n  stage: clean\n  image: gitlab-registry.cern.ch/dss/alpine-enhanced:3.13.5\n  script:\n    - export KUBECONFIG=$K8S_CONFIG\n    - echo \"Please, implement me!\"\n  dependencies: []\n  allow_failure: true\n  only:\n    - schedules\n\n\n#-------------------------------------------------------------------------------\n# Manually triggered builds\n#-------------------------------------------------------------------------------\n\n.eos_nginx_build_template:\n  stage: build:manual\n  variables:\n    PKG_MGR: dnf\n  script:\n    - ${PKG_MGR} install --nogpg -y gcc-c++ cmake make rpm-build which git sudo yum-utils createrepo sssd-client\n    - cd nginx\n    - ./makesrpm.sh\n    - |\n      if [[ ${PKG_MGR} == \"yum\" ]]; then\n        ${PKG_MGR}-builddep -y --nogpgcheck *.src.rpm\n      else\n        ${PKG_MGR} install -y dnf-plugins-core\n        ${PKG_MGR} builddep -y --nogpgcheck *.src.rpm\n      fi\n    - mkdir RPMS\n    - rpmbuild --rebuild --define \"_rpmdir RPMS/\" --define \"_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm\" *.src.rpm\n    - STORAGE_PATH=/eos/project/s/storage-ci/www/eos/${CODENAME}-depend/${BUILD_NAME}/x86_64\n    - sudo -u stci -H mkdir -p $STORAGE_PATH\n    - sudo -u stci -H cp -f RPMS/*.rpm $STORAGE_PATH\n    - sudo -u stci -H createrepo --update -q $STORAGE_PATH\n  tags:\n    - docker_node\n  when: manual\n\n\neos_nginx_el-8:\n  extends: .eos_nginx_build_template\n  image: gitlab-registry.cern.ch/linuxsupport/alma8-base\n  variables:\n    BUILD_NAME: el-8\n\n\neos_nginx_el-9:\n  extends: .eos_nginx_build_template\n  image: gitlab-registry.cern.ch/linuxsupport/alma9-base\n  variables:\n    BUILD_NAME: el-9\n\n\n#-------------------------------------------------------------------------------\n# ALICE ApMon builds\n#-------------------------------------------------------------------------------\n\n.build-apmon-template: &build-apmon-template-definition\n  stage: build:manual\n  variables:\n    PKG_MGR: dnf\n  script:\n    - ${PKG_MGR} install --nogpg -y gcc-c++ make rpm-build which git sssd-client sudo createrepo rsync tar gawk\n    - cd ApMon; ./maketar.sh\n    - rpmbuild --define \"_source_filedigest_algorithm md5\" --define \"_binary_filedigest_algorithm md5\" --define \"_topdir ./rpmbuild\" -ts eos-apmon-*.tar.gz\n    - |\n      if [[ ${PKG_MGR} == \"yum\" ]]; then\n        ${PKG_MGR}-builddep -y --nogpgcheck rpmbuild/SRPMS/eos-apmon-*.src.rpm\n      else\n        ${PKG_MGR} install -y dnf-plugins-core\n        ${PKG_MGR} builddep -y --nogpgcheck rpmbuild/SRPMS/eos-apmon-*.src.rpm\n      fi\n    - rpmbuild --rebuild --define \"_rpmdir rpmbuild/RPMS/\" --define \"_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm\" rpmbuild/SRPMS/eos-apmon-*.src.rpm\n    - sudo -u stci -H mkdir -p ${STORAGE_PATH}\n    - sudo -u stci -H cp -f rpmbuild/RPMS/*.rpm ${STORAGE_PATH}\n    - sudo -u stci -H createrepo --update -q ${STORAGE_PATH}\n  tags:\n    - docker_node\n  when: manual\n\n\neos_apmon_el-8:\n  image: gitlab-registry.cern.ch/linuxsupport/alma8-base\n  variables:\n    STORAGE_PATH: /eos/project/s/storage-ci/www/eos/${CODENAME}-depend/el-8/x86_64\n  extends: .build-apmon-template\n\n\neos_apmon_el-9:\n  image: gitlab-registry.cern.ch/linuxsupport/alma9-base\n  variables:\n    STORAGE_PATH: /eos/project/s/storage-ci/www/eos/${CODENAME}-depend/el-9/x86_64\n  extends: .build-apmon-template\n\n\neos_docs:\n  stage: build:manual\n  image: gitlab-registry.cern.ch/linuxsupport/alma9-base\n  script:\n    - yum install --nogpg -y make python3-sphinx sssd-client sudo which git\n    - cd doc\n    - export PYTHONPATH=`pwd`/_themes/\n    - cd diopside\n    - make html\n    - make html\n    - sudo kinit stci@CERN.CH -k -t /stci.krb5/stci.keytab\n    - sudo -u stci -H rm -rf /eos/project/e/eos/www/docs/diopside/*\n    - sudo -u stci -H cp -R _build/html/* /eos/project/e/eos/www/docs/diopside\n  tags:\n    - docker_node\n  rules:\n    - if: '$CI_COMMIT_TAG'\n      when: on_success\n      allow_failure: true\n    - when: manual\n      allow_failure: true\n\n\n.eos_repopackage:\n  stage: build:manual\n  image: gitlab-registry.cern.ch/linuxsupport/cc7-base\n  script:\n    - yum install --nogpg -y rpm-build sssd-client sudo createrepo\n    - mkdir build\n    - cd build\n    - rpmbuild --bb --define \"_rpmdir RPMS/\" --define \"_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm\" ../elrepopackage.spec\n    - STORAGE_PATH=/eos/project/s/storage-ci/www/eos/${CODENAME}/tag/el-7/x86_64\n    - sudo -u stci -H mkdir -p $STORAGE_PATH\n    - sudo -u stci -H cp -f RPMS/*.rpm $STORAGE_PATH\n    - sudo -u stci -H createrepo --update -q $STORAGE_PATH\n  tags:\n    - docker_node\n  when: manual\n\npre_commit:\n  stage: pre-commit\n  image: gitlab-registry.cern.ch/linuxsupport/alma10-base\n  needs: [ ]\n  variables:\n    PIP_NO_CACHE_DIR: \"1\"\n  before_script:\n    - dnf install -y git python python-pip clang-tools-extra\n    - python -m pip install --upgrade pip\n    - pip install pre-commit\n  script:\n    # Run pre-commit against all files starting from a given commit to HEAD\n    # This is done to avoid issues with large diffs when running pre-commit on all files (blocks the CI for ~10 minutes)\n    # When (if) the whole is formatted, we can remove the `--from-ref` and `--to-ref` options to always check all files\n\n    # pre-commit is run but clang-format will not be applied to all files, just to changed lines.\n    # In this stage it will not run since there are no changes, so clang-format here will do nothing\n    - pre-commit run --from-ref $(git merge-base origin/master HEAD) --to-ref HEAD --all-files\n\n    # Now we compute the diff and run clang-format on the diff only, as if we had the hook installed during our commits.\n    # This will modify the files in place, so you can copy-paste this command to fix the formatting issues locally and then commit the changes but if you have the hook installed you should never need to!\n    - git diff -U0 $(git merge-base origin/master HEAD) HEAD | python3 utils/clang-format-diff.py -p1 -i\n\n    # check if the previous command made any changes, if so, fail the job to enforce formatting\n    - git diff --exit-code || (echo \"Code is not properly formatted, please run the above command to fix the formatting issues and commit the changes.\" && exit 1)\n\n  allow_failure: true # we could enable this soon but let's keep it optional for now until people have had time to adapt to the new formatting rules\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"namespace/ns_quarkdb/qclient\"]\n\tpath = namespace/ns_quarkdb/qclient\n\turl = https://gitlab.cern.ch/eos/qclient.git\n[submodule \"mgm/cta_interface\"]\n\tpath = mgm/cta_interface\n\turl = https://gitlab.cern.ch/eos/xrootd-ssi-protobuf-interface.git\n[submodule \"common/backward-cpp\"]\n\tpath = common/backward-cpp\n\turl = https://github.com/bombela/backward-cpp.git\n\tbranch = master\n[submodule \"common/xrootd-ssi-protobuf-interface\"]\n\tpath = common/xrootd-ssi-protobuf-interface\n\turl = https://:@gitlab.cern.ch:8443/eos/xrootd-ssi-protobuf-interface.git\n[submodule \"unit_tests/googletest\"]\n\tpath = unit_tests/googletest\n\turl = https://github.com/google/googletest\n[submodule \"common/grpc-proto\"]\n\tpath = common/grpc-proto\n\turl = https://:@gitlab.cern.ch:8443/eos/grpc-proto.git\n[submodule \"common/jwt-cpp\"]\n\tpath = common/jwt-cpp\n\turl = https://github.com/Thalhammer/jwt-cpp.git\n[submodule \"quarkdb\"]\n\tpath = quarkdb\n\turl = https://gitlab.cern.ch/eos/quarkdb.git\n[submodule \"test/microbenchmarks/benchmark\"]\n\tpath = test/microbenchmarks/benchmark\n\turl = https://github.com/google/benchmark\n[submodule \"common/cppzmq\"]\n\tpath = common/cppzmq\n\turl = https://github.com/zeromq/cppzmq.git\n[submodule \"proto/eos-protobuf-spec\"]\n\tpath = proto/eos-protobuf-spec\n\turl = https://gitlab.cern.ch/eos/eos-protobuf-spec.git\n[submodule \"fst/css_plugin\"]\n\tpath = fst/css_plugin\n\turl = https://gitlab.cern.ch/eos/css_plugin.git\n[submodule \"console/parser\"]\n\tpath = console/parser\n\turl = https://github.com/CLIUtils/CLI11.git\n"
  },
  {
    "path": ".ignore",
    "content": "unit_tests/googletest/\ncommon/fmt/\nnamespace/ns_quarkdb/qclient/src/fmt/\ncommon/sqlite/\nman/man1/\n.vscode\n"
  },
  {
    "path": ".mailmap",
    "content": "Abhishek Lekshmanan <abhishek.lekshmanan@cern.ch> <abhi@pcitstmbpabhi.dyndns.cern.ch>\nAbhishek Lekshmanan <abhishek.lekshmanan@cern.ch> <abhishek.l@cern.ch>\nAndrea Manzi <andrea.manzi@cern.ch>\nAndreas Joachim Peters <andreas.joachim.peters@cern.ch>\nAndreas Joachim Peters <andreas.joachim.peters@cern.ch> <Andreas.Joachim.Jeters@cern.ch>\nAndreas Joachim Peters <andreas.joachim.peters@cern.ch> <Andreas.Joachim.Peters@cern.ch>\nAndreas Joachim Peters <andreas.joachim.peters@cern.ch> <apeters@Andreass-MacBook-Pro-3.local>\nAndreas Joachim Peters <andreas.joachim.peters@cern.ch> <apeters@pb-d-128-141-236-219.cern.ch>\nAndreas Joachim Peters <andreas.joachim.peters@cern.ch> <apeters@pb-d-128-141-237-197.cern.ch>\nAndreas Joachim Peters <andreas.joachim.peters@cern.ch> <apeters@pb-d-128-141-237-94.cern.ch>\nAndreas Joachim Peters <andreas.joachim.peters@cern.ch> <apeters@pcitsmd01.cern.ch>\nAndreas Joachim Peters <andreas.joachim.peters@cern.ch> <root@ajp.cern.ch>\nAndreas Stoeve <andreas.stoeve@cern.ch>\nAndreas Stoeve <andreas.stoeve@cern.ch> <andreas.stove@cern.ch>\nAndreas Stoeve <andreas.stoeve@cern.ch> <andreasstove@outlook.dk>\nAndreas Stoeve <andreas.stoeve@cern.ch> <astoeve@andreas-devbox.cern.ch>\nAndreas Stoeve <andreas.stoeve@cern.ch> <astoeve@cern.ch>\nAndreas Stoeve <andreas.stoeve@cern.ch> <astoeve@intel-nuc-2-007.dyndns.cern.ch>\nAndreea Prigoreanu <andreea.prigoreanu@cern.ch>\nBranko Blagojevic <branko.blagojevic@comtrade.com> <branko@c7-dev.localdomain>\nCristian Contescu <acontesc@cern.ch>\nCrystal Chua <crystal.chua@aarnet.edu.au> <cchua@gdpt-k15.cdn.aarnet.edu.au>\nElvin Alin Sindrilaru <elvin.alin.sindrilaru@cern.ch>\nElvin Alin Sindrilaru <elvin.alin.sindrilaru@cern.ch> <elvin.sindrilaru@cern.ch>\nElvin Alin Sindrilaru <elvin.alin.sindrilaru@cern.ch> <esindril@cern.ch>\nElvin Alin Sindrilaru <elvin.alin.sindrilaru@cern.ch> <esindril@eos.cern.ch>\nGeoffray Adde <geoffray.adde@cern.ch>\nGeoffray Adde <geoffray.adde@cern.ch> <gadde@gaddelaptop.cern.ch>\nGeoffray Adde <geoffray.adde@cern.ch> <gaddelocal@pcitdss1411.cern.ch>\nGeoffray Adde <geoffray.adde@cern.ch> <geoffrayadde@Megans-MacBook-Pro-2.local>\nHerve Rousseau <herve.rousseau@cern.ch> <hroussea@cern.ch>\nHerve Rousseau <herve.rousseau@cern.ch> <munsternet@gmail.com>\nJaroslav Guenther <jaroslav.guenther@cern.ch>\nJaroslav Guenther <jaroslav.guenther@cern.ch> <Jaroslav.Guenther@cern.ch>\nJaroslav Guenther <jaroslav.guenther@cern.ch> <JaroslavGuenther@cern.ch>\nJozsef Makai <jozsef.makai@cern.ch> <jmakai@cern.ch>\nJozsef Makai <jozsef.makai@cern.ch> <jozsef.makai.1992@gmail.com>\nKonstantinos Tsitsimpikos <konstantinos.tsitsimpikos@cern.ch>\nKonstantinos Tsitsimpikos <konstantinos.tsitsimpikos@cern.ch> <ktsitsimpikos@vagabond.cern.ch>\nLukasz Janyst <ljanyst@cern.ch>\nManuel Reis <manuel.b.reis@cern.ch> <root@manuel-dev.cern.ch>\nManuel Reis <manuel.b.reis@cern.ch> <root@manuel-dev4.cern.ch>\nMichal Kamil Simon <michal.simon@cern.ch> <Michal.Simon@cern.ch>\nMr Jenkins <jenkins@pb-d-128-141-194-219.cern.ch>\nPaul Lensing <paul.lensing@cern.ch> <paul.h.lensing@seagate.com>\nPaul Lensing <paul.lensing@cern.ch> <paul.lensing@gmail.com>\nPaul Lensing <paul.lensing@cern.ch> <plensing@engine>\nPaul Lensing <paul.lensing@cern.ch> <root@engine.dyndns.cern.ch>\nSteven Murray <Steven.Murray@cern.ch> <Steve.Murray@cern.ch>\nUnknown <root@alieostest.cern.ch>\nUnknown <root@diamondns.cern.ch>\nUnknown <root@eosdevsrv1.cern.ch>\nUnknown <root@eosdevsrv2.cern.ch>\nUnknown <root@eosplus611.cern.ch>\nUnknown <root@geotreemgm.cern.ch>\nUnknown <root@p05153074617805.cern.ch>\nUnknown <root@slc7.cern.ch>\nUnknown <root@st-srv-100-18594.cern.ch>\nUnknown <root@taishan02.ihep.ac.cn>\nUnknown <root@vmeos02.cern.ch>\nUnknown <root@vmeos03.cern.ch>\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "fail_fast: false\nrepos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v6.0.0\n    hooks:\n      - id: check-added-large-files\n      - id: check-case-conflict\n      - id: check-merge-conflict\n      - id: check-symlinks\n      - id: check-yaml\n      - id: debug-statements\n      - id: end-of-file-fixer\n      - id: mixed-line-ending\n      - id: requirements-txt-fixer\n      - id: trailing-whitespace\n\n  #  # Disable for now and use clang-format-diff.py instead to only format changed lines.\n  #  - repo: https://github.com/pocc/pre-commit-hooks\n  #    rev: v1.3.5\n  #    hooks:\n  #      - id: clang-format\n  #        name: clang-format\n  #        types_or: [ c, c++ ]\n  #        args:\n  #          - -i\n  #    stages: [ manual ]\n\n  - repo: local\n    hooks:\n      - id: clang-format-diff\n        name: clang-format-diff\n        # We pipe 'git diff' to the python script.\n        # -p1 strips the a/ b/ prefixes from the diff so the script finds the files.\n        # -i applies the edits in-place.\n        entry: bash -c 'git diff -U0 --no-color --cached | python3 utils/clang-format-diff.py -p1 -i'\n        language: system\n        types_or: [ c, c++ ]\n        pass_filenames: false\n"
  },
  {
    "path": "AUDIT.md",
    "content": "## EOS Audit Logging\n\n### Overview\n\nEOS implements structured audit logging for successful operations that modify the namespace or file metadata. Audit entries are encoded as JSON (one record per line), written directly into ZSTD-compressed log segments, and rotated every 1 hour by default. A symlink `audit.zstd` always points to the current active segment.\n\nThis document explains what is logged, the record format, where files are written, rotation behavior, how to parse the logs, and where audit hooks are integrated in the codebase.\n\n### Scope: What gets logged\n\n- **Successful namespace-affecting operations by identified users**:\n  - **Files**: CREATE, DELETE, RENAME/MOVE, TRUNCATE, WRITE (commit), UPDATE (open for write without create/truncate)\n  - **Directories**: MKDIR, RMDIR, RENAME/MOVE\n  - **Symlinks**: SYMLINK creation, DELETE\n  - **Metadata**: CHMOD, CHOWN, SET_XATTR, RM_XATTR, SET_ACL\n- **Optional**: READ and LIST can be enabled later (not default; high volume).\n- **Excluded**: Failed attempts, internal non-human activities (e.g. purge/version housekeeping).\n\n### Record format (protobuf → JSON)\n\nEach audit line is a JSON serialization of the `eos.audit.AuditRecord` protobuf (`proto/Audit.proto`). Key elements:\n\n- **Common fields**\n  - `timestamp` (int64): seconds since epoch (server time)\n  - `path` (string): absolute path to object; directory paths end with '/'\n  - `operation` (enum): one of CREATE, DELETE, RENAME, WRITE, TRUNCATE, SET_XATTR, RM_XATTR, SET_ACL, CHMOD, CHOWN, MKDIR, RMDIR, SYMLINK, UPDATE\n  - `client_ip` (string), `account` (string)\n  - `auth` (mechanism string + attributes map)\n  - `authorization` (reasons[])\n  - `trace_id` (string): server trace id\n  - `target` (string): for rename/symlink target path\n  - `uuid` (string): client/session id (empty if placeholder `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`)\n  - `tid` (string): client trace identifier\n  - `app` (string): client application\n  - `svc` (string): emitting service (e.g. \"mgm\")\n\n- **State snapshots**\n  - `before` / `after` (Stat): include `ctime`, `mtime`, `uid`, `gid`, `mode` (uint32), `mode_octal` (string), `size` (uint64), `checksum` (hex string for files)\n  - `attrs` (repeated AttrChange): `{ name, before, after }` for xattr changes (non-system attributes)\n\n- **Nanosecond resolution times**\n  - `Stat.ctime_ns` and `Stat.mtime_ns` provide full-resolution strings in the form `seconds.nanoseconds` (e.g. `1730985600.123456789`).\n\n- **Source and version metadata**\n  - `src_file`, `src_line`: source file and line where the audit call originated\n  - `version`: software version used when emitting the record\n\nExample JSON line (pretty-printed for readability):\n\n```json\n{\n  \"timestamp\": 1730985600,\n  \"path\": \"/eos/user/a/alice/data/file.txt\",\n  \"operation\": \"WRITE\",\n  \"client_ip\": \"192.0.2.10\",\n  \"account\": \"alice\",\n  \"auth\": { \"mechanism\": \"krb5\", \"attributes\": {\"principal\": \"alice@EXAMPLE.ORG\"} },\n  \"authorization\": { \"reasons\": [\"uid-match\"] },\n  \"trace_id\": \"srv-abc123\",\n  \"uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"tid\": \"cli-xyz789\",\n  \"app\": \"eoscp\",\n  \"svc\": \"mgm\",\n  \"before\": { \"ctime\": 1730980000, \"mtime\": 1730981000, \"uid\": 1000, \"gid\": 1000, \"mode\": 420, \"mode_octal\": \"0100644\", \"size\": 1024, \"checksum\": \"a1b2...\" },\n  \"after\":  { \"ctime\": 1730980000, \"mtime\": 1730985600, \"ctime_ns\": \"1730980000.000000000\", \"mtime_ns\": \"1730985600.123456789\", \"uid\": 1000, \"gid\": 1000, \"mode\": 420, \"mode_octal\": \"0100644\", \"size\": 4096, \"checksum\": \"dead...\" },\n  \"src_file\": \"mgm/FuseServer/Server.cc\",\n  \"src_line\": 2600,\n  \"version\": \"<eos-version>\"\n}\n```\n\n### Log files, rotation, and location\n\n- **Location**: `<logdir>/audit/` where `logdir` is derived from `XRDLOGDIR` (see `mgm/XrdMgmOfsConfigure.cc`).\n  - Directory is created on startup if missing; mode 0755; owned appropriately by the service user.\n- **Active segment symlink**: `<logdir>/audit/audit.zstd` points to the current segment file.\n- **Segments**: Files are ZSTD-compressed; rotated every 1 hour by default.\n  - Override the rotation interval via environment variable: `EOS_AUDIT_ROTATION=<seconds>`\n  - Filenames include seconds for uniqueness: `audit-YYYYMMDD-HHMMSS.zst`\n  - On rotation, the symlink is atomically updated to the new segment.\n\n### ZSTD stream and flushing\n\n- On opening a new segment, the ZSTD frame header is flushed immediately to avoid `zstdcat` errors on empty files.\n- Each record is written and flushed so small bursts are visible promptly.\n\n### Implementation details\n\n- `common/Audit.hh`, `common/Audit.cc` implement the audit writer:\n  - Thread-safe writer with internal locking\n  - Base directory configurable via `setBaseDirectory` or during construction\n  - `audit(const AuditRecord&)` and a convenience overload to populate from `VirtualIdentity`, operation, path, etc.\n  - Automatic rotation based on time; symlink management (`audit.zstd`)\n  - Normalizes placeholder UUID to empty string\n\n### READ and LIST auditing (optional)\n\n- **Disabled by default.** Enable only when needed due to potential volume.\n- **Enabling via API** (on `eos::common::Audit`):\n  - `setReadAuditing(true|false)` — enable/disable READ auditing\n  - `setListAuditing(true|false)` — enable/disable directory LIST auditing\n- **Suffix filter for READ auditing**:\n  - By default, READ auditing applies to common document-style files: `txt, pdf, doc, docx, ppt, pptx, xls, xlsx, odt, ods, odp, rtf, csv, json, xml, yaml, yml, md, html, htm`.\n  - Configure at runtime with `setReadAuditSuffixes({\"pdf\",\"docx\",...})`.\n  - If the vector contains `\"*\"`, all files are audited for READ (equivalent to `setReadAuditAll(true)`).\n  - Matching is case-insensitive and based on the file extension of the path being opened.\n- **Where READ/LIST audits are emitted**:\n  - READ: in `mgm/XrdMgmOfsFile.cc::open` for successful read-only opens (including 0-size files served by MGM) when enabled and suffix matches.\n  - LIST: in `mgm/XrdMgmOfsDirectory.cc::_open` on successful directory opens when enabled.\n\n### Default settings in XrdMgmOfs\n\n- The MGM reads environment variables at startup and applies them to the `Audit` instance:\n  - Default mode (`EOS_MGM_AUDIT` unset or `default`):\n    - Audit all modifications (CREATE, DELETE, RENAME, TRUNCATE, WRITE, UPDATE, metadata changes)\n    - Audit READ for the default document-style suffix list\n    - Do not audit LIST\n\n### Per-directory attribute-based auditing (sys.audit)\n\n- When `EOS_MGM_AUDIT=attribute`, global auditing is disabled and auditing is enabled per directory via the extended attribute `sys.audit` set on the parent directory (for files) or the directory itself (for LIST).\n- Valid values for `sys.audit` (case-insensitive):\n  - `none` / `no` / `false` / `off`: disable auditing for that directory\n  - `modifications`: enable modifications only (CREATE/DELETE/RENAME/TRUNCATE/WRITE/UPDATE/metadata)\n  - `default`: enable modifications and READ filtered by the default suffix list; LIST remains off\n  - `detail`: enable modifications and READ for all files; LIST remains off\n  - `all`: enable everything including LIST and READ for all files\n- Evaluation points:\n  - Files: parent directory’s `sys.audit`\n  - LIST: the directory’s own `sys.audit`\n- Notes:\n  - `EOS_MGM_AUDIT=off` disables auditing completely; `sys.audit` is ignored.\n  - In non-`attribute` modes, global settings control auditing; `sys.audit` is not used to override them.\n\n### Environment configuration\n\n- `EOS_MGM_AUDIT` — control overall audit level (parsed in `XrdMgmOfs` and applied during configure):\n  - `none`, `false`, `no`, `off`, or empty: disable all auditing\n  - `default`: audit modifications and READ for default document suffixes (no LIST)\n  - `modifications`: audit only modifications (no LIST, no READ)\n  - `detail`: audit modifications and READ for all files (no LIST)\n  - `all`: audit everything, including LIST and READ for all files\n  - `attribute`: create the audit logger but disable all global auditing; auditing is enabled explicitly via `sys.audit`\n\n- `EOS_MGM_AUDIT_READ_SUFFIX` — override the READ suffix filter:\n  - Comma-separated list, case-insensitive (e.g. `pdf,docx,json`)\n  - Use `*` to audit READ for all files\n  - If unset, the built-in default document-style list is used\n\nNotes:\n- Variables are parsed in `XrdMgmOfs` constructor and applied after the `Audit` instance is created in `XrdMgmOfsConfigure.cc`.\n- Setting `EOS_MGM_AUDIT=attribute` keeps the logger active while relying solely on per-directory `sys.audit` to enable auditing.\n- Setting `EOS_MGM_AUDIT=off` disables the logger entirely (no auditing).\n\n### Integration points (where audits are emitted)\n\n- Core MGM (`mgm/`):\n  - `XrdMgmOfs.hh`: `std::unique_ptr<eos::common::Audit> mAudit` member\n  - `XrdMgmOfsConfigure.cc`: initializes `mAudit` with `<logdir>/audit/`\n  - Operations:\n    - Files: `XrdMgmOfsFile.cc::open` (CREATE, TRUNCATE, UPDATE, READ), `fsctl/Commit.cc` (WRITE)\n    - Directories: `Mkdir.cc` (MKDIR), `Remdir.cc` (RMDIR), `XrdMgmOfsDirectory.cc` (LIST)\n    - Metadata: `Chmod.cc` (CHMOD), `Chown.cc` (CHOWN), `Attr.cc` (SET_XATTR, RM_XATTR)\n    - Symlinks: `Link.cc` (SYMLINK)\n    - Delete: `Rm.cc` (DELETE)\n\n- FUSE server (`mgm/FuseServer/Server.cc`):\n  - Directories: `OpSetDirectory` (MKDIR, UPDATE/RENAME/MOVE; xattr changes), `OpDeleteDirectory` (RMDIR)\n  - Files: `OpSetFile` (CREATE, UPDATE, RENAME/MOVE; CHMOD/CHOWN detection; xattr changes), `OpDeleteFile` (DELETE)\n  - Symlinks: `OpSetLink` (SYMLINK), `OpDeleteLink` (DELETE)\n\n### Directory path convention\n\n- Directory paths in audit entries include a trailing slash `/` for unambiguous parsing.\n\n### Mode representation\n\n- `mode` is stored as an integer (uint32) and `mode_octal` as a string in octal for convenience.\n\n### Parsing and tooling\n\n- Stream current audit records:\n\n```bash\nzstdcat <logdir>/audit/audit.zstd | jq '.'\n```\n\n- Follow audit logs across rotations (like `tail -F`):\n\n```bash\nzstdtail <logdir>/audit/audit.zstd\n# Or with filtering:\nzstdtail <logdir>/audit/audit.zstd -- jq 'select(.operation == \"DELETE\")'\n```\n\n- Historical segments are named `audit-YYYYMMDD-HHMMSS.zst`. Each line is a standalone JSON record; consumers can ingest line-by-line.\n\n### Testing and performance\n\n- Unit tests: `unit_tests/common/AuditTests.cc`\n  - Rotation and symlink behavior\n  - Benchmark: writes 100,000 records and measures elapsed time\n\n### Notes and caveats\n\n- Only successful operations are logged.\n- READ/LIST are intentionally omitted by default due to volume; can be added later.\n- The audit writer flushes after each record for operational visibility; adjust if batching is later desired.\n\n\n"
  },
  {
    "path": "ApMon/AUTHORS",
    "content": ""
  },
  {
    "path": "ApMon/COPYING",
    "content": "\t\t    GNU GENERAL PUBLIC LICENSE\n\t\t       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.\n     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\t\t\t    Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Library General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\f\n\t\t    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\f\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\f\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\f\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n\t\t\t    NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n\t\t     END OF TERMS AND CONDITIONS\n\f\n\t    How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program; if not, write to the Free Software\n    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year  name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Library General\nPublic License instead of this License.\n"
  },
  {
    "path": "ApMon/ChangeLog",
    "content": ""
  },
  {
    "path": "ApMon/INSTALL",
    "content": "Installation Instructions\n*************************\n\nCopyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004 Free\nSoftware Foundation, Inc.\n\nThis file is free documentation; the Free Software Foundation gives\nunlimited permission to copy, distribute and modify it.\n\nBasic Installation\n==================\n\nThese are generic installation instructions.\n\n   The `configure' shell script attempts to guess correct values for\nvarious system-dependent variables used during compilation.  It uses\nthose values to create a `Makefile' in each directory of the package.\nIt may also create one or more `.h' files containing system-dependent\ndefinitions.  Finally, it creates a shell script `config.status' that\nyou can run in the future to recreate the current configuration, and a\nfile `config.log' containing compiler output (useful mainly for\ndebugging `configure').\n\n   It can also use an optional file (typically called `config.cache'\nand enabled with `--cache-file=config.cache' or simply `-C') that saves\nthe results of its tests to speed up reconfiguring.  (Caching is\ndisabled by default to prevent problems with accidental use of stale\ncache files.)\n\n   If you need to do unusual things to compile the package, please try\nto figure out how `configure' could check whether to do them, and mail\ndiffs or instructions to the address given in the `README' so they can\nbe considered for the next release.  If you are using the cache, and at\nsome point `config.cache' contains results you don't want to keep, you\nmay remove or edit it.\n\n   The file `configure.ac' (or `configure.in') is used to create\n`configure' by a program called `autoconf'.  You only need\n`configure.ac' if you want to change it or regenerate `configure' using\na newer version of `autoconf'.\n\nThe simplest way to compile this package is:\n\n  1. `cd' to the directory containing the package's source code and type\n     `./configure' to configure the package for your system.  If you're\n     using `csh' on an old version of System V, you might need to type\n     `sh ./configure' instead to prevent `csh' from trying to execute\n     `configure' itself.\n\n     Running `configure' takes awhile.  While running, it prints some\n     messages telling which features it is checking for.\n\n  2. Type `make' to compile the package.\n\n  3. Optionally, type `make check' to run any self-tests that come with\n     the package.\n\n  4. Type `make install' to install the programs and any data files and\n     documentation.\n\n  5. You can remove the program binaries and object files from the\n     source code directory by typing `make clean'.  To also remove the\n     files that `configure' created (so you can compile the package for\n     a different kind of computer), type `make distclean'.  There is\n     also a `make maintainer-clean' target, but that is intended mainly\n     for the package's developers.  If you use it, you may have to get\n     all sorts of other programs in order to regenerate files that came\n     with the distribution.\n\nCompilers and Options\n=====================\n\nSome systems require unusual options for compilation or linking that the\n`configure' script does not know about.  Run `./configure --help' for\ndetails on some of the pertinent environment variables.\n\n   You can give `configure' initial values for configuration parameters\nby setting variables in the command line or in the environment.  Here\nis an example:\n\n     ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix\n\n   *Note Defining Variables::, for more details.\n\nCompiling For Multiple Architectures\n====================================\n\nYou can compile the package for more than one kind of computer at the\nsame time, by placing the object files for each architecture in their\nown directory.  To do this, you must use a version of `make' that\nsupports the `VPATH' variable, such as GNU `make'.  `cd' to the\ndirectory where you want the object files and executables to go and run\nthe `configure' script.  `configure' automatically checks for the\nsource code in the directory that `configure' is in and in `..'.\n\n   If you have to use a `make' that does not support the `VPATH'\nvariable, you have to compile the package for one architecture at a\ntime in the source code directory.  After you have installed the\npackage for one architecture, use `make distclean' before reconfiguring\nfor another architecture.\n\nInstallation Names\n==================\n\nBy default, `make install' will install the package's files in\n`/usr/local/bin', `/usr/local/man', etc.  You can specify an\ninstallation prefix other than `/usr/local' by giving `configure' the\noption `--prefix=PREFIX'.\n\n   You can specify separate installation prefixes for\narchitecture-specific files and architecture-independent files.  If you\ngive `configure' the option `--exec-prefix=PREFIX', the package will\nuse PREFIX as the prefix for installing programs and libraries.\nDocumentation and other data files will still use the regular prefix.\n\n   In addition, if you use an unusual directory layout you can give\noptions like `--bindir=DIR' to specify different values for particular\nkinds of files.  Run `configure --help' for a list of the directories\nyou can set and what kinds of files go in them.\n\n   If the package supports it, you can cause programs to be installed\nwith an extra prefix or suffix on their names by giving `configure' the\noption `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.\n\nOptional Features\n=================\n\nSome packages pay attention to `--enable-FEATURE' options to\n`configure', where FEATURE indicates an optional part of the package.\nThey may also pay attention to `--with-PACKAGE' options, where PACKAGE\nis something like `gnu-as' or `x' (for the X Window System).  The\n`README' should mention any `--enable-' and `--with-' options that the\npackage recognizes.\n\n   For packages that use the X Window System, `configure' can usually\nfind the X include and library files automatically, but if it doesn't,\nyou can use the `configure' options `--x-includes=DIR' and\n`--x-libraries=DIR' to specify their locations.\n\nSpecifying the System Type\n==========================\n\nThere may be some features `configure' cannot figure out automatically,\nbut needs to determine by the type of machine the package will run on.\nUsually, assuming the package is built to be run on the _same_\narchitectures, `configure' can figure that out, but if it prints a\nmessage saying it cannot guess the machine type, give it the\n`--build=TYPE' option.  TYPE can either be a short name for the system\ntype, such as `sun4', or a canonical name which has the form:\n\n     CPU-COMPANY-SYSTEM\n\nwhere SYSTEM can have one of these forms:\n\n     OS KERNEL-OS\n\n   See the file `config.sub' for the possible values of each field.  If\n`config.sub' isn't included in this package, then this package doesn't\nneed to know the machine type.\n\n   If you are _building_ compiler tools for cross-compiling, you should\nuse the `--target=TYPE' option to select the type of system they will\nproduce code for.\n\n   If you want to _use_ a cross compiler, that generates code for a\nplatform different from the build platform, you should specify the\n\"host\" platform (i.e., that on which the generated programs will\neventually be run) with `--host=TYPE'.\n\nSharing Defaults\n================\n\nIf you want to set default values for `configure' scripts to share, you\ncan create a site shell script called `config.site' that gives default\nvalues for variables like `CC', `cache_file', and `prefix'.\n`configure' looks for `PREFIX/share/config.site' if it exists, then\n`PREFIX/etc/config.site' if it exists.  Or, you can set the\n`CONFIG_SITE' environment variable to the location of the site script.\nA warning: not all `configure' scripts look for a site script.\n\nDefining Variables\n==================\n\nVariables not defined in a site shell script can be set in the\nenvironment passed to `configure'.  However, some packages may run\nconfigure again during the build, and the customized values of these\nvariables may be lost.  In order to avoid this problem, you should set\nthem in the `configure' command line, using `VAR=value'.  For example:\n\n     ./configure CC=/usr/local2/bin/gcc\n\nwill cause the specified gcc to be used as the C compiler (unless it is\noverridden in the site shell script).\n\n`configure' Invocation\n======================\n\n`configure' recognizes the following options to control how it operates.\n\n`--help'\n`-h'\n     Print a summary of the options to `configure', and exit.\n\n`--version'\n`-V'\n     Print the version of Autoconf used to generate the `configure'\n     script, and exit.\n\n`--cache-file=FILE'\n     Enable the cache: use and save the results of the tests in FILE,\n     traditionally `config.cache'.  FILE defaults to `/dev/null' to\n     disable caching.\n\n`--config-cache'\n`-C'\n     Alias for `--cache-file=config.cache'.\n\n`--quiet'\n`--silent'\n`-q'\n     Do not print messages saying which checks are being made.  To\n     suppress all normal output, redirect it to `/dev/null' (any error\n     messages will still be shown).\n\n`--srcdir=DIR'\n     Look for the package's source code in directory DIR.  Usually\n     `configure' can determine that directory automatically.\n\n`configure' also accepts some other, not widely useful, options.  Run\n`configure --help' for more details.\n\n"
  },
  {
    "path": "ApMon/Makefile",
    "content": "SPECFILE = $(shell find . -maxdepth 1 -type f -name '*.spec' )\nDIST    ?= $(shell rpm --eval %{dist})\nRPMBUILD = $(shell pwd)/rpmbuild\n\nPACKAGE  = $(shell awk '$$1 == \"Name:\"     { print $$2 }' $(SPECFILE) )\nVERSION  = $(shell awk '$$1 == \"Version:\"  { print $$2 }' $(SPECFILE) )\n\nPERLDIR  = $(shell perl -V:installsitearch | cut -d \"'\" -f 2)\nINSTALL ?= install\nDESTDIR ?= $(RPMBUILD)/BUILDROOT\n\nclean:\n\trm -rf $(PACKAGE)-$(VERSION)\n\trm -rf eos-apmon-*.tar.gz\n\trm -rf $(RPMBUILD)\n\ndist: clean\n\tmkdir -p $(PACKAGE)-$(VERSION)\n\trsync -aC --exclude '.__afs*' --exclude $(PACKAGE)-$(VERSION) . $(PACKAGE)-$(VERSION)\n\ttar cpfz ./$(PACKAGE)-$(VERSION).tar.gz $(PACKAGE)-$(VERSION)\n\ninstall:\n\tmkdir -p $(DESTDIR)/perl/ApMon/ApMon/\n\tmkdir -p $(DESTDIR)/etc/logrotate.d/\n\tmkdir -p $(DESTDIR)/opt/eos/apmon\n\tmkdir -p $(DESTDIR)/etc/sysconfig/\n\tmkdir -p $(DESTDIR)/var/log/eos\n\tmkdir -p $(DESTDIR)/$(PERLDIR)/ApMon/ApMon\n\tmkdir -p $(DESTDIR)/usr/sbin/\n\tmkdir -p $(DESTDIR)/usr/lib/systemd/system/\n\tmkdir -p $(DESTDIR)/var/log/eos/apmon\n\tcd perl; for name in `find . -type f | grep -v svn`; do $(INSTALL) -m 755 $$name $(DESTDIR)/$(PERLDIR)/$$name; done\n\t$(INSTALL) -m 644 usr/lib/systemd/system/eosapmond.service $(DESTDIR)/usr/lib/systemd/system/\n\t$(INSTALL) -m 755 opt/eos/apmon/eosapmond $(DESTDIR)/opt/eos/apmon/eosapmond\n\t$(INSTALL) -m 644 etc/logrotate.d/eosapmond $(DESTDIR)/etc/logrotate.d/eosapmond\n\t$(INSTALL) -m 755 run.sh $(DESTDIR)/opt/eos/apmon/run.sh\n\nprepare: dist\n\tmkdir -p $(RPMBUILD)/RPMS/$(DIST)\n\tmkdir -p $(RPMBUILD)/SRPMS/\n\tmkdir -p $(RPMBUILD)/SPECS/\n\tmkdir -p $(RPMBUILD)/SOURCES/\n\tmkdir -p $(RPMBUILD)/BUILD/\n\tcp eos-apmon-*.tar.gz $(RPMBUILD)/SOURCES \n\tcp $(SPECFILE) $(RPMBUILD)/SOURCES \n\nsrpm: prepare $(SPECFILE)\n\trpmbuild --define \"_source_filedigest_algorithm md5\" --define \"_binary_filedigest_algorithm md5\" \\\n\t\t--define \"_topdir $(RPMBUILD)\" -ts $(RPMBUILD)/SOURCES/eos-apmon-*.tar.gz\n\nrpm: srpm\n\trpmbuild --rebuild --define \"_rpmdir $(RPMBUILD)/RPMS/\" \\\n\t\t--define \"_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm\" rpmbuild/SRPMS/eos-apmon-*.src.rpm\n"
  },
  {
    "path": "ApMon/NEWS",
    "content": ""
  },
  {
    "path": "ApMon/README",
    "content": ""
  },
  {
    "path": "ApMon/eos-apmon.spec",
    "content": "%{!?perl_sitearch: %define perl_sitearch %(eval \"`%{__perl} -V:installsitearch`\"; echo $installsitearch)}\n%define _unpackaged_files_terminate_build 0\n%define __os_install_post       /bin/true\n%define debug_package %{nil}\n\nSummary: eos-apmon package\nName: eos-apmon\nVersion: 1.1.13\nRelease: 1%{?dist}\nURL: none\nSource0: %{name}-%{version}.tar.gz\nLicense: OpenSource\nGroup: Applications/Eos\n\nBuildRequires: systemd-rpm-macros\n\nRequires: perl\n\n%description\nThis package contains service scripts for ML monitoring in EOS\n\nThe service is started via systemd\nsystemctl start | stop | status | restart eosapmond.service\n\nThe initd scripts were done by Andreas-Joachim Peters [CERN] (EMAIL: andreas.joachim.peters@cern.ch).\n\n%prep\n%setup -q\n\n%install\nrm -rf %{buildroot}\nmkdir -p %{buildroot}\n%{__make} install DESTDIR=%{buildroot}\n\n%post\n%systemd_post eosapmond.service\n\n%preun\n%systemd_preun eosapmond.service\n\n%postun\n%systemd_postun_with_restart eosapmond.service\n\n%files\n%defattr(-,root,root)\n/%{_unitdir}/eosapmond.service\n/etc/logrotate.d/eosapmond\n%{perl_sitearch}/ApMon/\n/opt/eos/apmon/eosapmond\n/opt/eos/apmon/run.sh\n\n%changelog\n* Mon Apr 28 2025 Martin Vala <martin.vala@cern.ch> - 1.1.13-1\n- Xrootd version is parsed from eos-xrootd package\n\n* Wed Mar 19 2025 Gianmaria Del Monte <gianmaria.del.monte@cern.ch> - 1.1.12-1\n- Move to systemd service\n\n* Fri Jan 26 2024  Volodymyr Yurchenko <volodymyr.yurchenko@cern.ch> - 1.1.11-1\n- install systemd unit file compatible with Alma 9\n\n* Wed Aug  4 2021  Elvin Sindrilaru <esindril@cern.ch> - 1.1.10-1\n- move the apmon logs out of the EOS FST owned directory and\n  place them in /var/log/eos/apmon/\n- bump version to 1.1.10\n\n* Fri Dec  6 2019  Cristian Contescu <acontesc@cern.ch> - 1.1.9-1\n- add fix for interface detection (fix traffic reporting)\n\n* Wed Apr  2 2014 root <root@eosdevsrv1.cern.ch> - 1.1.4-1\n- add \"_xrootd_\" to the instance name\n- fix RPM version discovery for EOS and XRootD packages\n\n* Mon Mar 12 2011 root <peters@pcsmd01.cern.ch> - 1.1.0-0\n- Initial build.\n\n"
  },
  {
    "path": "ApMon/etc/logrotate.d/eosapmond",
    "content": "/var/log/eos/apmon/apmon.log {\n\tmissingok\n\tdaily\n        copytruncate\n        create 755 root root\n        dateext\n        rotate 200\n        compress\n}\n"
  },
  {
    "path": "ApMon/jenkins-build.sh",
    "content": "#!/bin/bash\n#-------------------------------------------------------------------------------\n# @author Elvin-Alin Sindrilaru - CERN\n# @brief Script used by Jenkins to build EOS ApMon rpms\n#-------------------------------------------------------------------------------\n\n#************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2016 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************/\n\n#-------------------------------------------------------------------------------\n# Print help\n#-------------------------------------------------------------------------------\nfunction printHelp()\n{\n  echo \"Usage:                                                               \" 1>&2\n  echo \"${0} <branch_or_tag> <xrootd_tag> <build_number> <dst_path>          \" 1>&2\n  echo \"  <branch_or_tag> branch name in the form of \\\"origin/master\\\" or tag\" 1>&2\n  echo \"                  name e.g. 1.0.0 for which to build the project     \" 1>&2\n  echo \"  <xrootd_tag>    XRootD tag version used for this build             \" 1>&2\n  echo \"  <build_number>  build number value passed in by Jenkins            \" 1>&2\n  echo \"  <platform>      build platform e.g. slc-6, el-7, fc-24             \" 1>&2\n  echo \"  <architecture>  build architecture e.g. x86_64, i386               \" 1>&2\n  echo \"  <dst_path>      destination path for the rpms built                \" 1>&2\n}\n\n#-------------------------------------------------------------------------------\n# Get the local branch name and dist tag for the rpms. For example local branch\n# name of branch 'origin/master' is master. The dist tag for Scientific Linux 5\n# can be 'slc5' or 'el5'.\n# Function sets two global variables BRANCH and DIST.\n#-------------------------------------------------------------------------------\nfunction getLocalBranchAndDistTag()\n{\n  if [[ ${#} -ne 2 ]]; then\n    echo \"Usage:                                                               \" 1>&2\n    echo \"${0} <branch_or_tag> <platform>                                      \" 1>&2\n    echo \"  <branch_or_tag> branch name in the form of \\\"origin/master\\\" or tag\" 1>&2\n    echo \"                  name e.g. 1.0.0 for which to build the project     \" 1>&2\n    echo \"  <platform>      build platform e.g. slc-6, el-7, fc-24             \" 1>&2\n    exit 1\n  fi\n\n  local BRANCH_OR_TAG=${1}\n  local PLATORM=${2}\n  local TAG_REGEX=\"^[04]+\\..*$\"\n  local TAG_REGEX_CITRINE=\"^4.*$\"\n\n  # If this is a tag get the branch it belogs to\n  if [[ \"${BRANCH_OR_TAG}\" =~ ${TAG_REGEX} ]]; then\n    if [[ \"${BRANCH_OR_TAG}\" =~ ${TAG_REGEX_CITRINE} ]]; then\n\t    BRANCH=\"citrine\"\n    fi\n  else\n    BRANCH=$(basename ${BRANCH_OR_TAG})\n    if [[ \"${BRANCH}\"  == \"master\" ]]; then\n\t    BRANCH=\"citrine\"\n     fi\n  fi\n\n  # For any other branch use the latest XRootD release\n  XROOTD_TAG=\"v4.3.0\"\n  DIST=\".${PLATFORM}\"\n\n\n  # Remove any \"-\" from the dist tag\n  DIST=\"${DIST//-}\"\n\n  echo \"Local branch:         ${BRANCH}\"\n  echo \"Dist tag:             ${DIST}  \"\n}\n\n#-------------------------------------------------------------------------------\n# Main - when we are called the current BRANCH_OR_TAG is already checked-out and\n#        the script must be run from the **same directory** where it resides.\n#-------------------------------------------------------------------------------\nif [[ ${#} -ne 6 ]]; then\n    printHelp\n    exit 1\nfi\n\nBRANCH_OR_TAG=${1}\nXROOTD_TAG=${2}\nBUILD_NUMBER=${3}\nPLATFORM=${4}\nARCHITECTURE=${5}\nDST_PATH=${6}\n\necho \"Build number:         ${BUILD_NUMBER}\"\necho \"Branch or tag:        ${BRANCH_OR_TAG}\"\necho \"XRootD tag:           ${XROOTD_TAG}\"\necho \"Build platform:       ${PLATFORM}\"\necho \"Build architecture:   ${ARCHITECTURE}\"\necho \"Destination path:     ${DST_PATH}\"\necho \"Running in directory: $(pwd)\"\n\n# Get local branch and dist tag for the RPMS\ngetLocalBranchAndDistTag ${BRANCH_OR_TAG} ${PLATFORM}\n\n# Move to ApMon directory and create the tarball\ncd ApMon\n./maketar.sh\n# Build the source RPM\nrpmbuild --define \"_source_filedigest_algorithm md5\" --define \"_binary_filedigest_algorithm md5\" --define \"_topdir ./rpmbuild\" -ts eos-apmon-*.tar.gz\n# Move the source RPM\nmv rpmbuild/SRPMS/eos-apmon-*.src.rpm .\n# Get the mock configurations from gitlab\ngit clone ssh://git@gitlab.cern.ch:7999/dss/dss-ci-mock.git ../dss-ci-mock\n# Prepare the mock configuration\ncat ../dss-ci-mock/eos-templates/${PLATFORM}-${ARCHITECTURE}.cfg.in | sed \"s/__XROOTD_TAG__/${XROOTD_TAG}/\" | sed \"s/__BUILD_NUMBER__/${BUILD_NUMBER}/\" > eos.cfg\n# Build the RPMs\nmock --yum --init --uniqueext=\"eos-apmon01\" -r ./eos.cfg --rebuild ./eos-apmon-*.src.rpm --resultdir ../rpms -D \"dist ${DIST}\"\n# List of branches for CI YUM repo\nBRANCH_LIST=('citrine')\n\n# If building one of the production branches then push rpms to YUM repo\nif [[ ${BRANCH_LIST[*]} =~ $BRANCH ]] ; then\n  cd ../rpms/\n  # Make sure the directories are created and rebuild the YUM repo\n  YUM_REPO_PATH=\"${DST_PATH}/${BRANCH}/tag/${PLATFORM}/${ARCHITECTURE}\"\n  echo \"Save ApMon RPMs in YUM repo: ${YUM_REPO_PATH}\"\n  aklog\n  mkdir -p ${YUM_REPO_PATH}\n  cp -f *.rpm ${YUM_REPO_PATH}\n  createrepo --update -q ${YUM_REPO_PATH}\nelse\n  echo \"RPMs for branch ${BRANCH} are NOT saved in any YUM repository!\"\nfi\n"
  },
  {
    "path": "ApMon/maketar.sh",
    "content": "#!/bin/sh\n\n# Extract package related information\nspecfile=`find . -maxdepth 1 -name '*.spec' -type f`\nname=`awk '$1 == \"Name:\" { print $2 }' ${specfile}`\nversion=`awk '$1 == \"Version:\" { print $2 }' ${specfile}`\n\n# Create the distribution tarball\nrm -rf ${name}-${version}\nrsync -aC --exclude '.__afs*' . ${name}-${version}\ntar -zcf ${name}-${version}.tar.gz ${name}-${version}\nrm -rf ${name}-${version}\n"
  },
  {
    "path": "ApMon/opt/eos/apmon/eosapmond",
    "content": "#!/usr/bin/perl\n# apmonpl <MONALISAHOST> <LOGFILE> <APMONLOGLEVEL> <NODES-NAME> <HOST-NAME> <XRD-PID>\n\nif (@ARGV != 6) {\n    print \"Usage: $0 <MONALISAHOST> <LOGFILE> <APMONLOGLEVEL> <NODES-NAME> <HOST-NAME> <XRD-PID>\";\n    exit(1);\n}\n\n# Redirect stdout and stderr to log file\nif (!open(STDOUT, \">>\", $ARGV[1])) {\n    print STDERR \"ERROR: cannot stream stdout into $ARGV[1]\\n\";\n    exit(-1);\n}\nSTDOUT->autoflush(1);\n\nif (!open(STDERR, \">>\", $ARGV[1])) {\n    print STDERR \"ERROR: cannot stream stderr into $ARGV[1]\\n\";\n    exit(-1);\n}\nSTDERR->autoflush(1);\n\nuse POSIX qw(setsid);\nmy $sid = setsid();\nif ($sid < 0) {\n    print STDERR \"ERROR: failed to create new session (setsid())\\n\";\n    exit(-1);\n}\n\nuse strict;\nuse warnings;\nuse ApMon;\nmy $apm = new ApMon(0);\nmy $now = `date`;\nchomp $now;\nprintf \"# Starting at $now\\n\";\nselect STDOUT; $| = 1;\nselect STDERR; $| = 1;\n$apm->setLogLevel($ARGV[2]);\n$apm->setDestinations([\"$ARGV[0]\"]);\n$apm->setMonitorClusterNode(\"$ARGV[3]_xrootd_Nodes\", \"$ARGV[4]\");\n$apm->addJobToMonitor($ARGV[5], '', 'xrootd_Services', \"$ARGV[4]\");\n\nwhile(1){\n    $apm->sendBgMonitoring();\n    sleep(120);\n}\n"
  },
  {
    "path": "ApMon/perl/ApMon/ApMon/BgMonitor.pm",
    "content": "package ApMon::BgMonitor;\n\nuse strict;\nuse warnings;\n\nuse ApMon::Common qw(logger);\nuse ApMon::ProcInfo;\nuse Data::Dumper;\nuse Net::Domain;\n\n# Settings for Data::Dumper's dump of last values\n$Data::Dumper::Indent = 1;\n$Data::Dumper::Purity = 1;\n\n# Background Monitor constructor\nsub new {\n\tmy ($type, $cmdPipe, $confFile, $lastValuesFile, $allowBgProcs, $confLoader) = @_;\n\tmy $this = {};\n\tbless $this;\n\t$this->{CMD_PIPE} = $cmdPipe;\n\t$this->{CONF_FILE} = $confFile;\n\t$this->{LAST_VALUES_FILE} = $lastValuesFile;\n\t$this->{ALLOW_BG_PROCESSES} = $allowBgProcs;\n\t$this->{CONFIG_LOADER} = $confLoader;\n\t$this->{LAST_CONF_CHECK_TIME} = 0;\n\t$this->{CONF_RECHECK} = 1;\n\t$this->{CONF_CHECK_INTERVAL} = 20;\n\t$this->{SEND_BG_MONITORING} = 0;\n\t\n\tmy $hostname = Net::Domain::hostfqdn();\n\t$this->{BG_MONITOR_CLUSTER} = \"ApMon_SysMon\";\n\t$this->{BG_MONITOR_NODE} = $hostname;\n\t$this->{JOBS} = {};\n\t\n\n\t$this->{PROC_INFO} = new ApMon::ProcInfo();\n\treturn $this;\n}\n\n# This call will never return!\n# It should be a used just from a child process whose role is just background monitoring.\n# In order to report data, user has to send a bg_enable message to enable this.\nsub run {\n        my $this = shift;\n\tmy $userMsg = \"\";\n\tsleep(1);\n        while(1) {\n\t\t$userMsg = ApMon::Common::readMessage($this->{CMD_PIPE});\n                $this->parseParentMessage($userMsg) if $userMsg; # use $this->{CMD_PIPE} channel to get messages from user\n                $this->sendBgMonitoring() if $this->{SEND_BG_MONITORING};\n                sleep(10);\t# updates sould never be more often than this!\n        }\n}\n\n# Registers another job for monitoring. This can be called by user or from readMessage.\nsub addJobToMonitor {\n\tmy ($this, $pid, $workDir, $clusterName, $nodeName) = @_;\n\n\t$this->{JOBS}->{$pid}->{CLUSTER} = $clusterName;\n\t$this->{JOBS}->{$pid}->{NODE} = $nodeName;\n\t$this->{PROC_INFO}->addJobToMonitor($pid, $workDir);\n}\n\n# Removes a job from the monitored processes. This can be called either by user or by readMessage.\nsub removeJobToMonitor {\n\tmy ($this, $pid) = @_;\n\n\tdelete $this->{JOBS}->{$pid};\n\t$this->{PROC_INFO}->removeJobToMonitor($pid);\n}\n\n# Sets the default cluster and node name for the system-related information.\nsub setMonitorClusterNode {\n\tmy ($this, $cluster, $node) = @_;\n\n\t$this->{BG_MONITOR_CLUSTER} = $cluster;\n\t$this->{BG_MONITOR_NODE} = $node;\n}\n\n# Enables or disables sending of monitoring info\nsub enableBgMonitoring {\n\tmy ($this, $enable) = @_;\n\n\t$this->{SEND_BG_MONITORING} = $enable;\n}\n\n# Sets the log level for BG_MONITOR\nsub setLogLevel {\n\tmy ($this, $level) = @_;\n\tApMon::Common::setLogLevel($level);\n}\n\n# Sets the maximum rate for the messages sent by user\nsub setMaxMsgRate {\n        my ($this, $rate) = @_;\n\n\tApMon::Common::setMaxMsgRate($rate);\n}\n\n# Sets the SI2k meter for this machine\nsub setCpuSI2k {\n\tmy ($this, $si2k) = @_;\n\n\tApMon::Common::setCpuSI2k($si2k);\n}\n\n# Sets the cpu speed as the one detected when probing cpu type for si2k\nsub setCpuMHz {\n\tmy ($this, $mhz) = @_;\n\n\t$ApMon::Common::CpuMHz = $mhz;\n}\n\n# This is used only if BgMonitor is used as a dedicated monitoring process in order to interpret\n# messages from parent process.\nsub parseParentMessage {\n\tmy ($this, $msg) = @_;\n\t\n\tmy ($pid, $workDir, $cluster, $node);\n\tmy @msgs = split(/\\n/, $msg);\n\tfor $msg (@msgs){\n\t\t$this->setLogLevel($1) if $msg =~ /loglevel:(.*)/;\n\t\t$this->setMaxMsgRate($1) if $msg =~ /maxMsgRate:(.*)/;\n\t\t$this->enableBgMonitoring($1) if $msg =~ /bg_enable:(.*)/;\n\t\t$this->setCpuSI2k($1) if $msg =~ /cpu_si2k:(.*)/;\n\t\t$this->setCpuMHz($1) if $msg =~ /cpu_mhz:(.*)/;\n\t\t$pid = $1 if $msg =~ /pid:(.*)/;\n\t\t$this->removeJobToMonitor($1) if $msg =~ /rm_pid:(.*)/;\n\t\t$workDir = $1 if $msg =~ /work_dir:(.*)/;\n\t\t$cluster = $1 if $msg =~ /bg_cluster:(.*)/;\n\t\tif($msg =~ /bg_node:(.*)/){\n\t\t\t$node = $1;\n\t\t\tif(defined $pid){\n\t\t\t\t$this->addJobToMonitor($pid, $workDir, $cluster, $node);\n\t\t\t\tundef $pid;\n\t\t\t\tundef $cluster;\n\t\t\t}\n\t\t\tif(defined $cluster){\n\t\t\t\t$this->setMonitorClusterNode($cluster, $node);\n\t\t\t\tundef $cluster;\n\t\t\t}\n\t\t}\n\t}\n}\n\n# This will send the background information to the interested listeners. It is called either from backgroundMonitor\n# or directly by the user from time to time to avoid having a sepparate process for this task.\n# information is about the system (load, network, memory etc.) and about a number of jobs (PIDs).\n#\n# If $mustSend is != 0, the bgMonitoring data is sent regardles of when it was last time sent. This allows\n# sending a 'last result', just before the end of a job, and which can happen anytime.\nsub sendBgMonitoring {\n        my $this = shift;\n\tmy $mustSend = shift || 0;\n\n        ApMon::Common::updateConfig($this);\n        my (@crtSysParams, @crtJobParams, $now, @sys_results, @job_results, $optsRef, $prevRawData);\n        $now = time;\n\tmy $updatedProcInfo = 0;\n        for my $dest (keys %{$this->{DESTINATIONS}}) {\n                $optsRef = $this->{DESTINATIONS}->{$dest}->{OPTS};\n\t\t$prevRawData = $this->{DESTINATIONS}->{$dest}->{PREV_RAW_DATA};\n                @crtSysParams = ();\n                @crtJobParams = ();\n                # for each destination and its options, check if we have to do any background monitoring\n                if($optsRef->{'sys_monitoring'} and ($mustSend or $optsRef->{'sys_data_sent'} + $optsRef->{'sys_interval'} <= $now)){\n                        for my $param (keys %$optsRef){\n                                if($param =~ /^sys_(.+)/ and $optsRef->{$param}){\n                                        push(@crtSysParams, $1) unless ($1 eq 'monitoring') or ($1 eq 'interval') or ($1 eq 'data_sent');\n                                }\n                        }\n                        $optsRef->{'sys_data_sent'} = $now;\n                }\n                if($optsRef->{'job_monitoring'} and ($mustSend or $optsRef->{'job_data_sent'} + $optsRef->{'job_interval'} <= $now)){\n                        for my $param (keys %$optsRef){\n                                       if($param =~ /^job_(.+)/ and $optsRef->{$param}){\n                                               push(@crtJobParams, \"$1\") unless ($1 eq 'monitoring') or ($1 eq 'interval') or ($1 eq 'data_sent');\n                                       }\n                        }\n                        $optsRef->{'job_data_sent'} = $now;\n                }\n                if($optsRef->{'general_info'} and ($mustSend or $optsRef->{'general_data_sent'} + 2 * $optsRef->{'sys_interval'} <= $now)){\n                        for my $param (keys %$optsRef){\n                                if(!($param =~ /^sys_/) and !($param =~ /^job_/) and ($optsRef->{$param})){\n                                        push(@crtSysParams, $param) unless ($param eq 'general_info') or ($param eq 'general_data_sent');\n                                }\n                        }\n\t\t\t$optsRef->{'general_data_sent'} = $now;\n                }\n\t\tif((! $updatedProcInfo) and (@crtSysParams > 0 or @crtJobParams > 0)){\n\t\t\t$this->{PROC_INFO}->update();\n\t\t\t$updatedProcInfo = 1;\n\t\t}\n\t\t\n\t\t@sys_results = ( @crtSysParams ? $this->{PROC_INFO}->getSystemData(\\@crtSysParams, $prevRawData) : () );\n\t\tif(@sys_results){\n\t\t\tApMon::Common::directSendParameters($dest, $this->{BG_MONITOR_CLUSTER}, $this->{BG_MONITOR_NODE}, -1, \\@sys_results);\n\t\t\t$this->{LAST_VALUES}->{BG_MON_VALUES} = {} if ! $this->{LAST_VALUES}->{BG_MON_VALUES};\n\t\t\t$this->update_hash($this->{LAST_VALUES}->{BG_MON_VALUES}, \\@sys_results);\n\t\t}\n\t\tfor my $pid (keys %{$this->{JOBS}}){\n\t\t\t@job_results = ( @crtJobParams ? $this->{PROC_INFO}->getJobData($pid, \\@crtJobParams) : () );\n\t\t\tif(@job_results){\n\t\t\t\tApMon::Common::directSendParameters($dest, $this->{JOBS}->{$pid}->{CLUSTER},$this->{JOBS}->{$pid}->{NODE},-1,\\@job_results);\n\t\t\t\t$this->{LAST_VALUES}->{JOBS}->{$pid}->{BG_MON_VALUES} = {} if ! $this->{LAST_VALUES}->{JOBS}->{$pid}->{BG_MON_VALUES};\n\t\t\t\t$this->update_hash($this->{LAST_VALUES}->{JOBS}->{$pid}->{BG_MON_VALUES}, \\@job_results);\n\t\t\t}\n\t\t}\n        }\n\tif(open(F, \">$this->{LAST_VALUES_FILE}\")){\n\t\tprint F Dumper($this->{LAST_VALUES});\n\t\tclose F;\n\t\tchmod(0600, $this->{LAST_VALUES_FILE});\n\t}else{\n\t\tlogger(\"WARNING\", \"Cannot save last BgMonitored values to $this->{LAST_VALUES_FILE}\");\n\t}\n}\n\n# update in the given hash the rest of pa\nsub update_hash {\n\tmy $this = shift;\n\tmy $hash = shift || {} ;\n\tmy $params = shift;\n\t@$params & 1 and logger(\"WARNING\", \"Odd number of parameters in update_hash\") and return;\n\twhile(@$params){\n\t\tmy $key = shift(@$params);\n\t\tmy $val = shift(@$params);\n\t\t$hash->{$key} = $val;\n\t}\n}\n\n1;\n\n"
  },
  {
    "path": "ApMon/perl/ApMon/ApMon/Common.pm",
    "content": "package ApMon::Common;\n\nuse strict;\nuse warnings;\n\nrequire Exporter;\nuse Carp qw(cluck);\nuse Socket;\nuse ApMon::XDRUtils;\nuse Data::Dumper;\nuse Sys::Hostname;\n\nuse vars qw(@ISA @EXPORT @EXPORT_OK $APMON_DEFAULT_PORT $VERSION %defaultOptions $KSI2K $CpuMHz);\n\npush @ISA, qw(Exporter);\npush @EXPORT, qw(logger);\npush @EXPORT_OK, qw($APMON_DEFAULT_PORT %defaultOptions);\n\n$VERSION = \"2.2.18\";\n$APMON_DEFAULT_PORT = 8884;\n\nmy @LOG_LEVELS = (\"DEBUG\", \"NOTICE\", \"INFO\", \"WARNING\", \"ERROR\", \"FATAL\");\nmy $CRT_LOGLEVEL = 2;\t# index in the array above\n\nmy $MAX_MSG_RATE = 20;\t# Default value for max nr. of messages that user is allowed to send, per second\n\n$KSI2K = undef;\t\t# kilo spec ints 2k for this machine\n\n$CpuMHz = undef;\t# Cpu Speed when taking the speed for KSI2k\n\n# Default options for background monitoring\n%defaultOptions = (\n        'job_monitoring' => 1,          # perform (or not) job monitoring\n        'job_interval' => 60,           # at this interval (in seconds)\n        'job_data_sent' => 0,           # time from Epoch when job information was sent; don't touch!\n\n        'job_cpu_time' => 1,            # processor time spent running this job in seconds\n\t'job_cpu_ksi2k' => 1,\t\t# used CPU power in ksi2k units (see SpecInt2000 for details);\n        'job_run_time' => 1,            # elapsed time from the start of this job in seconds\n\t'job_run_ksi2k' => 1,\t\t# elapsed time in ksi2k units\n        'job_cpu_usage' => 1,           # current percent of the processor used for this job, as reported by ps\n        'job_virtualmem' => 1,          # size in JB of the virtual memory occupied by the job, as reported by ps\n        'job_rss' => 1,                 # size in KB of the resident image size of the job, as reported by ps\n        'job_mem_usage' => 1,           # percent of the memory occupied by the job, as reported by ps\n        'job_workdir_size' => 1,        # size in MB of the working directory of the job\n        'job_disk_total' => 1,          # size in MB of the total size of the disk partition containing the working directory\n        'job_disk_used' => 1,           # size in MB of the used disk partition containing the working directory\n        'job_disk_free' => 1,           # size in MB of the free disk partition containing the working directory\n        'job_disk_usage' => 1,          # percent of the used disk partition containing the working directory\n\t'job_open_files' => 1,\t\t# number of open file descriptors\n\t'job_page_faults_min' => 1,\t# number of minor page faults in the job\n\t'job_page_faults_maj' => 1,\t# number of major page faults in the job\n\n\n        'sys_monitoring' => 1,          # perform (or not) system monitoring\n        'sys_interval' => 60,           # at this interval (in seconds)\n        'sys_data_sent' => 0,           # time from Epoch when system information was sent; don't touch!\n\n        'sys_cpu_usr' => 1,             # cpu-usage information\n        'sys_cpu_sys' => 1,             # all these will produce coresponding paramas without \"sys_\"\n        'sys_cpu_nice' => 1,\n        'sys_cpu_idle' => 1,\n\t'sys_cpu_iowait' => 1,\n\t'sys_cpu_irq' => 1,\n\t'sys_cpu_softirq' => 1,\n\t'sys_cpu_steal' => 1,\n\t'sys_cpu_guest' => 1,\n        'sys_cpu_usage' => 1,\n\t'sys_interrupts' => 1,\n\t'sys_context_switches' => 1,\n        'sys_load1' => 1,               # system load information\n        'sys_load5' => 1,\n        'sys_load15' => 1,\n        'sys_mem_used' => 1,            # memory usage information\n        'sys_mem_free' => 1,\n\t'sys_mem_actualfree' => 1,\t# actually free memory: free + cached + buffers\n        'sys_mem_usage' => 1,\n\t'sys_mem_buffers' => 1,\n\t'sys_mem_cached' => 1,\n        'sys_blocks_in' => 1,\n        'sys_blocks_out' => 1,\n        'sys_swap_used' => 1,           # swap usage information\n        'sys_swap_free' => 1,\n        'sys_swap_usage' => 1,\n        'sys_swap_in' => 1,\n        'sys_swap_out' => 1,\n        'sys_net_in' => 1,              # network transfer in kBps\n        'sys_net_out' => 1,             # these will produce params called ethX_in, ethX_out, ethX_errs\n        'sys_net_errs' => 1,            # for each eth interface\n\t'sys_net_sockets' => 1,\t\t# number of opened sockets for each proto => sockets_tcp/udp/unix ...\n\t'sys_net_tcp_details' => 1,\t# number of tcp sockets in each state => sockets_tcp_LISTEN, ...\n        'sys_processes' => 1,\t\t# total processes and processs in each state (R, S, D ...)\n\t'sys_uptime' => 1,\t\t# uptime of the machine, in days (float number)\n\n\n        'general_info' => 1,            # send (or not) general host information once every 2 $sys_interval seconds\n        'general_data_sent' => 0,       # time from Epoch when general information was sent; don't touch!\n\n        'hostname' => 1,\n        'ip' => 1,                      # will produce <ifname>_ip params for each physical interface\n\t'ipv6' => 1,                    # will produce <ifname>_ipv6 params for each physical interface\n\t'kernel_version' => 1,\n\t'eos_rpm_version' => 1,\n        'xrootd_rpm_version' => 1, \n\t'platform' => 1,\n\t'os_type' => 1,\n        'cpu_MHz' => 1,\n        'no_CPUs' => 1,                 # number of CPUs\n\t'ksi2k_factor' => 1,\t\t# system's ksi2k factor, if known\n        'total_mem' => 1,\n        'total_swap' => 1,\n\t'cpu_vendor_id' => 1,\n\t'cpu_family' => 1,\n\t'cpu_model' => 1,\n\t'cpu_model_name' => 1,\n\t'cpu_cache' => 1,\n\t'bogomips' => 1);\n\n\n# Create a UDP socket through which all information is sent\nif(! socket(SOCKET, PF_INET, SOCK_DGRAM, getprotobyname(\"udp\"))){\n        logger(\"FATAL\", \"Cannot create UDP socket $@\");\n\tdie;\n}\n\n# Simple logger\nsub logger {\n        my ($level, $msg) = @_;\n        my $i = 0;\n        $i++ while (! ($LOG_LEVELS[$i] eq $level) and ($i < @LOG_LEVELS));\n        if($CRT_LOGLEVEL <= $i and $i < @LOG_LEVELS){\n\t\tmy $now =localtime();\n\t\t$now =~ s/^\\S+\\s((\\S+\\s+){3}).*$/$1/;\n                print $now.\"ApMon[$LOG_LEVELS[$i]]: $msg\\n\";\n        }\n}\n\n# Sets the CRT_LOGLEVEL\nsub setLogLevel {\n\tmy $level = shift;\n\tlogger(\"NOTICE\", \"Setting loglevel to $level\");\n\tif(! defined $level){\n\t\tcluck(\"got undefined level from\");\n\t\treturn;\n\t}\n\tmy $i = 0;\n\t$i++ while (! ($LOG_LEVELS[$i] eq $level) and ($i < @LOG_LEVELS));\n\tif($i < @LOG_LEVELS){\n\t\t$CRT_LOGLEVEL = $i;\n\t}else{\n\t\tlogger(\"WARNING\", \"Unknown log level \\\"$level\\\" - ignoring.\\n\");\n\t}\n}\n\n# Sets the maximum rate for sending messages (see shouldSend subroutine)\nsub setMaxMsgRate {\n\tmy $rate = shift;\n\t\n\t$MAX_MSG_RATE = $rate;\n\tlogger(\"INFO\", \"Setting maxMsgRate to $rate\");\n}\n\n# For each destination, we'll keep a pair (instance_id, seq_nr) that will identify us\nmy $senderRef = {};\nmy $instance_id = getInstanceID();\n\n# This is used internally to send a set of parameters to a given destination.\nsub directSendParameters {\n        my ($destination, $clusterName, $nodeName, $time, $paramsRef) = @_;\n\n        my @params;\n\tif(! defined($paramsRef)){\n\t\tlogger(\"WARNING\", \"Not sending undefined parameters!\");\n\t\treturn;\n\t}\n\tif(! defined($time)){\n\t\tlogger(\"WARNING\", \"Not sending the parameters for an undefined time!\");\n\t\treturn;\n\t}\n\n\tif(! shouldSend()){\n\t\t#logger(\"WARNING\", \"Not sending since the messages are too often!\");\n\t\treturn;\n\t}\n\t\n        if(ref($paramsRef->[0]) eq \"ARRAY\"){\n                @params = @{$paramsRef->[0]};\n        }elsif(ref($paramsRef->[0]) eq \"HASH\"){\n                @params = %{$paramsRef->[0]};\n        }else{\n                @params = @$paramsRef;\n        }\n\n        if(@params == 0){\n                return;\n        }\n\t\n\t$senderRef->{$destination} = {INSTANCE_ID => $instance_id, SEQ_NR => 0} if ! $senderRef->{$destination};\n\tmy $sender = $senderRef->{$destination};\n\t$sender->{INSTANCE_ID} = ($$ << 16) | ($sender->{INSTANCE_ID} && 0xffff);\n\t$sender->{SEQ_NR} = ($sender->{SEQ_NR} + 1) % 2_000_000_000; # wrap around 2 mld\n\t\n        my ($host, $port, $pass) = split(/:/, $destination);\n        logger(\"NOTICE\", \"====> $host|$port|$pass/$clusterName/$nodeName\".($time != -1 ? \" @ $time\" : \"\").\" [$sender->{SEQ_NR} # $sender->{INSTANCE_ID}]\");\n        for(my $i = 0; $i < @params; $i += 2){\n\t\tif(defined($params[$i]) && defined($params[$i+1])){\n\t                logger(\"NOTICE\", \"  ==> $params[$i] = $params[$i+1]\");\n\t\t}else{\n\t\t\tlogger(\"NOTICE\", \"  ==> \".(defined($params[$i]) ? $params[$i] : \"undef name\").\" = \".(defined($params[$i+1]) ? $params[$i+1] : \"undef value\").\" <== ignoring pair\");\n\t\t\tsplice(@params, $i, 2);\n\t\t\t$i-=2;\n\t\t}\n        }\n        my $header = \"v:${VERSION}_plp:$pass\";\n        my $msg = ApMon::XDRUtils::encodeString($header)\n\t\t. ApMon::XDRUtils::encodeINT32($sender->{INSTANCE_ID})\n\t\t. ApMon::XDRUtils::encodeINT32($sender->{SEQ_NR})\n\t\t. ApMon::XDRUtils::encodeParameters($clusterName, $nodeName, $time, @params);\n        my $in_addr = inet_aton($host);\n        my $in_paddr = sockaddr_in($port, $in_addr);\n\tmy $msg_len = length($msg);\n        if(send(SOCKET, $msg, 0, $in_paddr) != $msg_len){\n                logger(\"ERROR\", \"Could not send UDP datagram to $host:$port\");\n        }else{\n\t\tlogger(\"NOTICE\", \"~~~~> Packet sent successfully; total size=$msg_len bytes.\");\n\t}\n}\n\n# This is called by child processes to read messages (if they exist) from the parent.\nsub readMessage {\n        my $PIPE = shift;\n\n        my ($rin, $win, $ein, $rout, $wout, $eout) = ('', '', '');\n        my $retMsg = \"\";\n        vec($rin,fileno($PIPE),1) = 1;\n        $ein = $rin | $win;\n        my ($nfound,$timeleft) = select($rout=$rin, $wout=$win, $eout=$ein, 0);\n        if($nfound){\n                sysread($PIPE, $retMsg, 1024);\n                logger(\"DEBUG\", \"readMessage: $retMsg\");\n\t}\n\treturn $retMsg;\n}\n\n# This is called by main process to send a message to a child that reads form the given pipe\nsub writeMessage {\n\tmy ($PIPE, $msg) = @_;\n\n\tif(defined $PIPE){\n\t\tlogger(\"DEBUG\", \"writeMessage: $msg\");\n                syswrite($PIPE, $msg);\n        }else{\n                logger(\"ERROR\", \"Trying to send '$msg' to child, but the pipe is not defined!\");\n        }\n}\n\n# copy the time when last data was sent\nsub updateLastSentTime {\n\tmy ($srcOpts, $dstOpts) = @_;\n\n\t$dstOpts->{'general_data_sent'} = $srcOpts->{'general_data_sent'} if $srcOpts->{'general_data_sent'};\n\t$dstOpts->{'sys_data_sent'} = $srcOpts->{'sys_data_sent'} if $srcOpts->{'sys_data_sent'};\n\t$dstOpts->{'job_data_sent'} = $srcOpts->{'job_data_sent'} if $srcOpts->{'job_data_sent'};\n}\n\n# This is used to update the configuration for an object that has in it's base hash the following elements\n# DESTINATIONS, CONF_RECHECK, LAST_CONF_CHECK_TIME, CONF_CHECK_INTERVAL and CONF_FILE.\n# In practice, both ApMon and BgMonitor use it to update their configuration.\nsub updateConfig {\n        my $this = shift;\n\n\tif(! $this->{ALLOW_BG_PROCESSES}){\n\t\t$this->{DESTINATIONS} = $this->{CONFIG_LOADER}->{DESTINATIONS};\n\t\treturn;\n\t}\n        my $now = time;\n        if((scalar(keys %{$this->{DESTINATIONS}}) > 0 and $this->{CONF_RECHECK} == 0)\n                        or ($this->{LAST_CONF_CHECK_TIME} + $this->{CONF_CHECK_INTERVAL} > $now)){\n                return;\n        }\n        logger(\"DEBUG\", \"Updating configuration from $this->{CONF_FILE}\");\n        if(open(CONF, \"<$this->{CONF_FILE}\")){\n\t\tmy $prevDest = $this->{DESTINATIONS} || {};\n                $this->{DESTINATIONS} = {};     # clear old destinations first\n                my ($crtDest, $line);\n                while($line = <CONF>){\n                        chomp $line;\n                        if($line =~ /^(\\S+):(\\S+):(\\S*)$/){\n                                # reading a new destination\n                                $crtDest = $line;\n                                my %defOpts = %defaultOptions;  #get a copy of the default options\n                                $this->{DESTINATIONS}->{$crtDest}->{OPTS} = \\%defOpts;\n\t\t\t\tupdateLastSentTime($prevDest->{$crtDest}->{OPTS}, $this->{DESTINATIONS}->{$crtDest}->{OPTS});\n\t\t\t\t$this->{DESTINATIONS}->{$crtDest}->{PREV_RAW_DATA} = \n\t\t\t\t\t($prevDest->{$crtDest}->{PREV_RAW_DATA} ? $prevDest->{$crtDest}->{PREV_RAW_DATA} : {});\n                                logger(\"DEBUG\", \"Adding destination $line\");\n                        }elsif($line =~ /^\\s(\\S+)=(\\S+)/) {\n                                # reading an attribute for the current destination and modify the current options\n                                my ($name, $value) = ($1, $2);\n                                logger(\"DEBUG\", \"Adding $name=$value\");\n\t\t\t\tif($name eq 'loglevel'){\n\t\t\t\t\t$this->setLogLevel($value);\n                                }elsif($name eq 'conf_recheck'){\n                                        $this->{CONF_RECHECK} = $value;\n                                }elsif($name eq 'recheck_interval'){\n                                        $this->{CONF_CHECK_INTERVAL} = $value;\n\t\t\t\t}elsif($name eq 'maxMsgRate'){\n                                        $this->setMaxMsgRate($value);\n\t\t\t\t}else{\n                                        $this->{DESTINATIONS}->{$crtDest}->{OPTS}->{$name} = $value;\n                                }\n                        }else{\n                                logger(\"WARNING\", \"Unknown line in conf file: $line\");\n                        }\n                }\n                close CONF;\n        }else{\n                logger(\"ERROR\", \"Error opening temporary config file $this->{CONF_FILE}. Current config is unchanged.\");\n                return;\n        }\n        $this->{LAST_CONF_CHECK_TIME} = time;\n}\n\n# don't allow a user to send more than MAX_MSG messages per second, in average\nmy $prvTime = 0;\nmy $prvSent = 0;\nmy $prvDrop = 0;\nmy $crtTime = 0;\nmy $crtSent = 0;\nmy $crtDrop = 0;\nmy $hWeight = 0.92;\n\n# Decide if the current datagram should be sent.\n# This decision is based on the number of messages previously sent.\nsub shouldSend {\n\tmy $now = time;\n\n\tif($now != $crtTime){\n\t\t# new time\n\t\t# update previous counters;\n\t\t$prvSent = $hWeight * $prvSent + (1 - $hWeight) * $crtSent / ($now - $crtTime); \n\t\t$prvTime = $crtTime;\n\t\tlogger(\"DEBUG\", \"previously sent: $crtSent; dropped: $crtDrop\");\n\t\t# reset current counter\n\t\t$crtTime = $now;\n\t\t$crtSent = 0;\n\t\t$crtDrop = 0;\n\t}\n\tmy $valSent = $prvSent * $hWeight + $crtSent * (1 - $hWeight); # compute the history\n\t\n\tmy $doSend = 1;\n\tmy $level = $MAX_MSG_RATE - $MAX_MSG_RATE / 10; # when we should start dropping messages\n\t\n\tif($valSent > $MAX_MSG_RATE - $level){\n\t\t$doSend = rand($MAX_MSG_RATE / 10) < ($MAX_MSG_RATE - $valSent);\n\t}\n\t\n\t# counting sent and dropped messages\n\tif($doSend){\n\t\t$crtSent++;\n\t}else{\n\t\t$crtDrop++;\n\t}\n\t\n\treturn $doSend;\n}\n\n# Try to generate a more random instance id. It takes the process ID and\n# combines it with the last digit from the IP addess and a random number\nsub getInstanceID {\n\tmy $pid = $$;\n\tmy $ip = int(rand(256));  # last digit of the ip address\n\tmy $host = hostname();    # from Sys::Hostname\n\tif($host){\n\t\tmy $addr = inet_ntoa(scalar gethostbyname($host));\n\t\t$ip = $1 if $addr =~ /(\\d+)$/;\n\t}\n\tmy $rnd = int(rand(256));\n\tmy $iid = ($pid << 16) | ($ip << 8) | $rnd; # from all this, generate the instance id\n\treturn $iid;\n}\n\n# Try to determine the CPU type. Returns a hash with: cpu_model_name, cpu_MHz, cpu_cache (in KB)\n# TODO: make this work also for Mac.\nsub getCpuType {\n\tmy $cpu_type = {};\n\tif(-r \"/proc/cpuinfo\"){\n\t\tif(open(CPU_INFO, \"</proc/cpuinfo\")){\n\t\t\tmy $line;\n\t\t\twhile($line = <CPU_INFO>){\n\t\t\t\tif($line =~ /cpu MHz\\s+:\\s+(\\d+\\.?\\d*)/){\n\t\t\t\t\t$cpu_type->{\"cpu_MHz\"} = $1;\n\t\t\t\t\t$CpuMHz = $1;\n\t\t\t\t}\n\t\t\t\t$cpu_type->{\"cpu_model_name\"} = $1 if($line =~ /model name\\s+:\\s+(.+)/ || $line =~ /family\\s+:\\s+(.+)/);\n\t\t\t\t$cpu_type->{\"cpu_cache\"} = $1 if($line =~ /cache size\\s+:\\s+(\\d+)/);\n\t\t\t}\n\t\t\tclose(CPU_INFO);\n\t\t}else{\n\t\t\tlogger(\"NOTICE\", \"Cannot open /proc/cpuinfo\");\n\t\t}\n\t}\n\tif(-r \"/proc/pal/cpu0/cache_info\"){\n\t\tif(open(CACHE_INFO, \"</proc/pal/cpu0/cache_info\")){\n\t\t\tmy $line;\n\t\t\tmy $level3params = 0;\n\t\t\twhile($line = <CACHE_INFO>){\n\t\t\t\t$level3params = 1 if($line =~/Cache level 3/);\n\t\t\t\t$cpu_type->{\"cpu_cache\"} = $1 / 1024 if ($level3params && $line =~ /Size\\s+:\\s+(\\d+)/);\n\t\t\t}\n\t\t\tclose(CACHE_INFO);\n\t\t}else{\n\t\t\tlogger(\"NOTICE\", \"Cannot open /proc/pal/cpu0/cache_info\");\n\t\t}\n\t}\n\tif(! scalar(keys(%$cpu_type))){\n\t\tlogger(\"INFO\", \"Cannot get cpu type\");\n\t\treturn undef;\n\t}\n\treturn $cpu_type;\n}\n\n# Set the SI2K performance meter for this machine. If this function is called then parameter\n# cpu_ksi2k will also be reported for the job monitoring with a value computed this way:\n# cpu_ksi2k(job) = cpu_time(job) * ( si2k / 1000)\nsub setCpuSI2k {\n\tmy $si2k = shift;\n\t$KSI2K = $si2k / 1000.0 if($si2k);\n}\n\n1;\n\n"
  },
  {
    "path": "ApMon/perl/ApMon/ApMon/ConfigLoader.pm",
    "content": "package ApMon::ConfigLoader;\n\nuse strict;\nuse warnings;\n\nuse ApMon::Common qw(logger $APMON_DEFAULT_PORT %defaultOptions);\nuse Socket;\nuse Data::Dumper;\nuse Carp qw(cluck);\n\n\n# Config Loader constructor\nsub new {\n        my ($type, $cmdPipe, $confFile) = @_;\n        my $this = {};\n        bless $this;\n        $this->{CMD_PIPE} = $cmdPipe;\n        $this->{CONF_FILE} = $confFile;\n\t$this->{LAST_CONF_CHECK_TIME} = 0;\n        $this->{CONF_RECHECK} = 1;\n        $this->{CONF_CHECK_INTERVAL} = 30;\n\t$this->{DEST_LOCATIONS} = ();\t\t# http/file locations from where to read the config\n\t$this->{DESTINATIONS} = {};\n\treturn $this;\n}\n\n# This call will never return!\n# It should be a used just from a child process whose role is only configuration refreshing.\nsub run {\n\tmy $this = shift;\n\tmy ($wasSuccess, $userMsg) = (0, undef);\n\twhile(1) {\n\t\t$userMsg = ApMon::Common::readMessage($this->{CMD_PIPE});\n\t\t$this->parseParentMessage($userMsg) if $userMsg;\n\t\t$wasSuccess = $this->refreshConfig($wasSuccess) if $this->{CONF_RECHECK};\n\t\tsleep($this->{CONF_CHECK_INTERVAL});\n\t}\n}\n\n# This allows setting the configuration. It can be used with several arguments:\n# - list of strings (URLs and/or files) - the configuration will be read from all\n# - reference to an ARRAY - each element is a destination ML service; for each destination\n#   the default options will be used\n# - reference to a HASH - each key is a destination ML service; for each destination you can\n#   define a set of additional options that will overwrite the default ones.\nsub setDestinations {\n\tmy ($this, @destLocations) = @_;\n\t\n\tmy $prevDest = $this->{DESTINATIONS};\n\t$this->{DESTINATIONS} = {};\n        # determine the way we were instantiated and initalize accordingly\n        if(ref($destLocations[0]) eq \"ARRAY\"){\n                # user gave a reference to an array, each element being a destination (host[:port][ pass])\n                # we will send datagrams to all valid destinations (i.e. host can be resolved), with default options\n                $this->{CONF_RECHECK} = 0;\n                my ($destStr, $dest);\n                for $destStr (@{$destLocations[0]}) {\n                        $dest = $this->parseDestination($destStr);\n                        if($dest){\n                                my %defOptsCopy = %defaultOptions;\n                                logger(\"INFO\", \"Added destination $dest with default options.\");\n                                $this->{DESTINATIONS}->{$dest}->{OPTS} = \\%defOptsCopy;\n\t\t\t\tApMon::Common::updateLastSentTime($prevDest->{$dest}->{OPTS}, $this->{DESTINATIONS}->{$dest}->{OPTS});\n\t\t\t\t$this->{DESTINATIONS}->{$dest}->{PREV_RAW_DATA} = \n\t\t\t\t\t($prevDest->{$dest}->{PREV_RAW_DATA} ? $prevDest->{$dest}->{PREV_RAW_DATA} : {});\n\t\t\t\t$this->{DESTINATIONS}->{$dest}->{OPTS}->{'conf_recheck'} = 0;\n                        }\n                }\n\t\t$this->writeDestinations();\n        }elsif(ref($destLocations[0]) eq \"HASH\"){\n                # user gave a reference to a hash, each key being a destination (host[:port][ pass])\n                # we will send datagrams to all valid destinations (i.e. host can be resolved), overwritting the\n                # default options with the ones passed by user. Options will be named as in the %defaultOptions.\n                $this->{CONF_RECHECK} = 0;\n                my ($destStr, $dest);\n                for $destStr (keys %{$destLocations[0]}){\n                        $dest = $this->parseDestination($destStr);\n                        if($dest){\n                                my %defOptsCopy = %defaultOptions;\n                                $this->{DESTINATIONS}->{$dest}->{OPTS} = \\%defOptsCopy;\n\t\t\t\tApMon::Common::updateLastSentTime($prevDest->{$dest}->{OPTS}, $this->{DESTINATIONS}->{$dest}->{OPTS});\n\t\t\t\t$this->{DESTINATIONS}->{$dest}->{PREV_RAW_DATA} = \n\t\t\t\t\t($prevDest->{$dest}->{PREV_RAW_DATA} ? $prevDest->{$dest}->{PREV_RAW_DATA} : {});\n\t\t\t\t$this->{DESTINATIONS}->{$dest}->{OPTS}->{'conf_recheck'} = 0;\n                                logger(\"INFO\", \"Added destination $dest with the following additional options:\");\n                                # now we have to modify default options with the ones given by user\n                                my ($key, $value);\n                                for $key (keys %{$destLocations[0]->{$destStr}}){\n                                        $value = $destLocations[0]->{$destStr}->{$key};\n                                        logger(\"INFO\", \" -> $key = $value\");\n                                        $this->{DESTINATIONS}->{$dest}->{OPTS}->{$key} = $value;\n                                }\n                        }\n                }\n\t\t$this->writeDestinations();\n        }else{\n\t\t# we got a list of URLs and/or files. Fetch them and get the configuration\n\t\t$this->{DEST_LOCATIONS} = ();\n\t\tpush(@{$this->{DEST_LOCATIONS}}, @destLocations);\n\t\t$this->refreshConfig();\n\t}\n}\n\n# This will fetch all the configuration files and then, if this part was succesful, it \n# will call parseConfig to build the temporary configuration file from which both Main\n# and BgMonitor will read the destinations\nsub refreshConfig {\n\tmy $this = shift;\n\tmy $wasSuccess = shift || 0;\n\n\tif(! $this->{DEST_LOCATIONS} || (! @{$this->{DEST_LOCATIONS}})){\n\t\tlogger(\"NOTICE\", \"No configuration file was given.\");\n\t\treturn $wasSuccess;\n\t}\n\tmy ($error, $linesRef);\n\tlogger(\"DEBUG\", \"Refreshing config from pid $$\");\n\t($error, $linesRef) = $this->fetchConfig(@{$this->{DEST_LOCATIONS}});\n\tif(! $error){ # or ($error and ($wasSuccess < @$linesRef))){\n\t\t# it reading destinations worked ok, or if we had a partial error reading files,\n\t\t# but the configuration size is bigger than earlier, apply those new changes\n\t\t$wasSuccess = @$linesRef;\n\t\t$this->parseConfig($linesRef);\n\t}else{\n\t\tlogger(\"WARNING\", \"Failed reading destination files/urls. Configuration will remain unchanged.\");\n\t}\n\treturn $wasSuccess;\n}\n\t\n# fetch the configuration form all given files/URLs. It returns a pair ($error, $linesRef) where\n# $error contains the number of locations from where the retrieval of the configuration failed.\n# $linesRef is a reference to an array containing all the lines.\nsub fetchConfig {\n        my ($this, @dests) = @_;\n\n        my @lines = ();\n        my $error = 1;\n        for my $dest (@dests){\n                if ( $dest =~ /^http:\\/\\// ) {\n                        logger(\"INFO\", \"Reading config from url: $dest\");\n\t\t\trequire LWP::UserAgent;\n                        my $ua = LWP::UserAgent->new();\n                        $ua->timeout(5);\n                        $ua->env_proxy();\n                        my $response = $ua->get($dest);\n                        if($response->is_success){\n                                push(@lines, split(\"\\n\", $response->content . \"\\nEND_PART\\n\"));\n\t\t\t\t$error = 0;\n                        }else{\n                                logger(\"WARNING\", \"Error reading url: $dest\");\n\t\t\t\tlogger(\"WARNING\", \"Got: \".$response->status_line);\n                        }\n                }else{\n                        logger(\"INFO\", \"Reading config from file: $dest\");\n                        if(open(INFILE, \"<$dest\")){\n                                my @newlines = <INFILE>;\n                                push(@lines, @newlines);\n                                close(INFILE);\n                                push(@lines, split(\"\\n\", \"\\nEND_PART\\n\"));\n\t\t\t\t$error = 0;\n                        }else{\n                                logger(\"WARNING\", \"Error reading file: $dest\");\n                        }\n                }\n        }\n        return ($error, \\@lines);\n}\n\n# This will parse the config lines brought by fetchConfig, creating the local temporary config file\nsub parseConfig {\n        my ($this, $linesRef) = @_;\n\n        my @lines = @$linesRef;\n        my @dests = ();\n        my %opts = ();\n\t\t\n\tmy $prevDest = $this->{DESTINATIONS};\n\t$this->{DESTINATIONS} = {};\n        for my $line (@lines) {\n                chomp $line;\n                next if $line =~ /^\\s*$/;       # skip empty lines\n                next if $line =~ /^\\s*#/;       # skip comments\n                $line =~ s/\\s+/ /g;             # eliminate multiple spaces\n                $line =~ s/^ //;                # remove space at the beginning\n                $line =~ s/ $//;                # remove space at the end\n                if($line =~ /^xApMon_(.*)/){\n                        # set an option for the current destinations\n                        my $opt = $1;\n                        if($opt =~ /(\\S+)\\s?=\\s?(\\S+)/){\n                                my ($name, $value) = ($1, $2);\n                                $value = $value =~ /off/i ? 0 : $value;\n                                $value = $value =~ /on/i ? 1 : $value;\n                                $opts{$name} = $value;\n\t\t\t\t$this->setLogLevel($value) if $name eq \"loglevel\";\n\t\t\t\t$this->setMaxMsgRate($value) if $name eq \"maxMsgRate\";\n                                $this->{CONF_RECHECK} = $value if $name eq \"conf_recheck\";\n                                $this->{CONF_CHECK_INTERVAL} = $value if $name eq \"recheck_interval\";\n                                #logger(\"DEBUG\", \"set option $name <- $value\");\n                        }\n                }elsif($line =~ /END_PART/){\n                        #logger(\"DEBUG\", \"Storing options into the temp conf file\");\n                        for my $dest (@dests){\n\t\t\t\tmy %optsCopy = %opts;\n\t\t\t\t$this->{DESTINATIONS}->{$dest}->{OPTS} = \\%optsCopy;\n\t\t\t\tApMon::Common::updateLastSentTime($prevDest->{$dest}->{OPTS}, $this->{DESTINATIONS}->{$dest}->{OPTS});\n\t\t\t\t$this->{DESTINATIONS}->{$dest}->{PREV_RAW_DATA} = \n\t\t\t\t\t($prevDest->{$dest}->{PREV_RAW_DATA} ? $prevDest->{$dest}->{PREV_RAW_DATA} : {});\n                        }\n                        @dests = ();\n                        %opts = ();\n                }else{\n                        # parse a new destination\n\t\t\tmy $dest = $this->parseDestination($line);\n                        push(@dests, $dest) if $dest;\n                }\n        }\n\t$this->writeDestinations();\n}\n\n# Write the destinations to the temporary config file, to be able to get them also from the\n# other processes\nsub writeDestinations {\n\tmy $this = shift;\n\n        if(open(CONF, \">\".$this->{CONF_FILE}.\".tmp\")) {\n        \tlogger(\"DEBUG\", \"Writting config to $this->{CONF_FILE}\");\n\t\tmy ($dest, $opt, $val);\n\t        for $dest (keys %{$this->{DESTINATIONS}}) {\n\t\t\tprint CONF \"$dest\\n\";\n\t\t\tfor $opt (keys %{$this->{DESTINATIONS}->{$dest}->{OPTS}}) {\n\t\t\t\t$val = $this->{DESTINATIONS}->{$dest}->{OPTS}->{$opt};\n\t\t\t\tprint CONF \" $opt=$val\\n\";\n\t\t\t}\n\t\t}\n\t\tclose(CONF);\n\t\tchmod(0600, $this->{CONF_FILE}.'.tmp');\n\t        # this is done in order to keep the interference between the processes as small as possible\n        \trename($this->{CONF_FILE}.'.tmp', $this->{CONF_FILE});\n\t}else{\n\t\tlogger(\"ERROR\", \"Cannot write destinations to file $this->{CONF_FILE}\");\n\t}\n}\n\n# Given a destination line (i.e. host[:port][ passwd]), it returns an array containing at most one\n# string of the following form: \"ip:port:passwd\"\n# This is what will be used as a destination in sending the directSendParameters.\nsub parseDestination {\n        my ($this, $line) = @_;\n\n        my $dest = \"\";\n        if($line =~ /([\\.\\-a-zA-Z0-9]+)\\s*:?\\s*(\\d+)?\\s*(.*)?/){\n                my ($host, $port, $pass) = ($1, $2, $3);\n                $port = (! defined($port) || $port eq \"\") ? $APMON_DEFAULT_PORT : $port;\n                my ($name,$aliases,$type,$len,$addr) = gethostbyname($host);\n                if (defined($len) and $len == 4) {\n                        my $ip = inet_ntoa($addr);\n                        logger(\"DEBUG\", \"found destination i=$ip, P=$port, p=$pass\");\n                        $dest = \"$ip:$port:$pass\";\n                }else{\n                        logger(\"WARNING\", \"Error resolving host $host\");\n                }\n        }\n        return $dest;\n}\n\n# This will parse the options sent by functions in ApMon\nsub parseParentMessage {\n\tmy ($this, $msg) = @_;\n\tmy @msgs = split(/\\n/, $msg);\n\tmy @dests = ();\n\tlogger(\"DEBUG\", \"Reading messages from user\");\n\tfor $msg (@msgs){\n\t\t$this->setLogLevel($1) if $msg =~ /loglevel:(.*)/;\n\t\t$this->setMaxMsgRate($1) if $msg =~ /maxMsgRate:(.*)/;\n\t\t$this->{CONF_RECHECK} = $1 if $msg =~ /conf_recheck:(.*)/;\n\t\t$this->{CONF_CHECK_INTERVAL} = $1 if $msg =~ /recheck_interval:(.*)/;\n\t\tpush(@dests, $1) if $msg =~/dest:(.*)/;\n\t}\n\t$this->setDestinations(@dests) if @dests;\n}\n\n# Sets the log level for CONFIG_LOADER\nsub setLogLevel {\n        my ($this, $level) = @_;\n\n\tApMon::Common::setLogLevel($level);\n}\n\n# Sets the maximum rate for the messages sent by user\nsub setMaxMsgRate {\n\tmy ($this, $rate) = @_;\n\n\tApMon::Common::setMaxMsgRate($rate);\n}\n\n1;\n\n"
  },
  {
    "path": "ApMon/perl/ApMon/ApMon/ProcInfo.pm",
    "content": "package ApMon::ProcInfo;\n\nuse strict;\nuse warnings;\n\nuse ApMon::Common qw(logger);\nuse Data::Dumper;\nuse Net::Domain;\nuse Time::Local;\nuse Config;\n\n# See the end of this file for a set of interesting methods for other modules.\n\n# ProcInfo constructor\nsub new {\n\tmy $this = {};\n\t$this->{DATA} = {};\t\t# monitored data that is going to be reported\n\t$this->{JOBS} = {};\t\t# jobs that will be monitored \n\t$this->{NETWORKINTERFACES} = {};# network interface names\n\t# names of the months for ps start time of a process\n\t$this->{MONTHS} = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\n\t$this->{readGI} = 0;\t# used to read generic information less often\n\tbless $this;\n\treturn $this;\n}\n\n# this has to be run twice (with the $lastUpdateTime updated) to get some useful results\nsub readStat {\n\tmy $this = shift;\n\t\n\tif ($Config{osname} eq \"solaris\"){\n\t    if (open(VMSTAT, \"vmstat -s |\")){\n\t\tmy $line;\n\t\t\n\t\twhile ($line = <VMSTAT>){\n\t\t    if ($line =~ /(\\d+)\\s+user\\s+cpu/ ){\n\t\t\t$this->{DATA}->{\"raw_cpu_usr\"} = $1;\n\t\t    }\n\t\t\n\t\t    if ($line =~ /(\\d+)\\s+system\\s+cpu/){\n\t\t\t$this->{DATA}->{\"raw_cpu_sys\"} = $1;\n\t\t    }\n\t\t\n\t\t    if ($line =~ /(\\d+)\\s+idle\\s+cpu/){\n\t\t\t$this->{DATA}->{\"raw_cpu_idle\"} = $1;\n\t\t    }\n\t\t    \n\t\t    if ($line =~ /(\\d+)\\s+wait\\s+cpu/){\n\t\t\t$this->{DATA}->{\"raw_cpu_iowait\"} = $1;\n\t\t    }\n\t\t\n\t\t    if ($line =~ /(\\d+)\\s+pages\\s+swapped\\s+in/){\n\t\t\t$this->{DATA}->{\"raw_swap_in\"} = $1;\n\t\t    }\n\t\t\n\t\t    if ($line =~ /(\\d+)\\s+pages\\s+swapped\\s+out/){\n\t\t\t$this->{DATA}->{\"raw_swap_out\"} = $1;\n\t\t    }\n\t\t    \n\t\t    if ($line =~ /(\\d+)\\s+device\\s+interrupts/){\n\t\t\t$this->{DATA}->{\"raw_interrupts\"} = $1;\n\t\t    }\n\t\t\n\t\t    if ($line =~ /(\\d+)\\s+cpu\\s+context\\s+switches/){\n\t\t\t$this->{DATA}->{\"raw_context_switches\"} = $1;\n\t\t    }\n\t\t    \n\t\t}\n\t\t\n\t\tclose VMSTAT;\n\t    }\n\n\t    $this->{DATA}->{\"raw_blocks_in\"} = $this->{DATA}->{\"raw_blocks_out\"} = 0;\n\t\n\t    if (open(IOSTAT, \"iostat -xnI |\")){\n\t\tmy $line = <IOSTAT>;\n\t\t$line = <IOSTAT>;\n\t\t\n\t\tmy $kbR = 0;\n\t\tmy $kbW = 0;\n\t\t\n\t\twhile ($line = <IOSTAT>){\n\t\t    (undef, $kbR, $kbW) = split(/\\s+/, $line);\n\t\t    \n\t\t    $this->{DATA}->{\"raw_blocks_in\"} += $kbR;\n\t\t    $this->{DATA}->{\"raw_blocks_out\"} += $kbW;\n\t\t}\n\t\t\n\t\tclose IOSTAT;\n\t    }\n\t\n\t    return;\n\t}\n\t\n\tif(open(STAT, \"</proc/stat\")){\n\t\tmy $line;\n\t\twhile($line = <STAT>){\n\t\t\tif($line =~ /^cpu\\s/) {\n\t\t\t\t(undef, $this->{DATA}->{\"raw_cpu_usr\"}, $this->{DATA}->{\"raw_cpu_nice\"}, \n\t\t\t\t        $this->{DATA}->{\"raw_cpu_sys\"}, $this->{DATA}->{\"raw_cpu_idle\"},\n\t\t\t\t\t$this->{DATA}->{\"raw_cpu_iowait\"}, $this->{DATA}->{\"raw_cpu_irq\"},\n\t\t\t\t\t$this->{DATA}->{\"raw_cpu_softirq\"}, $this->{DATA}->{\"raw_cpu_steal\"},\n\t\t\t\t\t$this->{DATA}->{\"raw_cpu_guest\"}\n\t\t\t\t) = split(/ +/, $line);\n\t\t\t}\n\t\t\tif($line =~ /^page/) { # this doesn't work for kernel >2.5\n\t\t\t\t(undef, $this->{DATA}->{\"raw_blocks_in\"}, $this->{DATA}->{\"raw_blocks_out\"}) = split(/ +/, $line);\n\t\t\t}\n\t\t\tif($line =~/^swap/) { # this also doesn't work in >2.5\n\t\t\t\t(undef, $this->{DATA}->{\"raw_swap_in\"}, $this->{DATA}->{\"raw_swap_out\"}) = split(/ +/, $line);\n\t\t\t}\n\t\t\t$this->{DATA}->{\"raw_interrupts\"} = $1 if($line =~ /^intr\\s(\\d+)/);\n\t\t\t$this->{DATA}->{\"raw_context_switches\"} = $1 if($line =~ /^ctxt\\s(\\d+)/);\n\t\t}\n\t\tclose STAT;\n\t}else{\n\t\tlogger(\"NOTICE\", \"ProcInfo: cannot open /proc/stat\");\n\t\t#disable ..?\n\t}\n\t# blocks_in/out and swap_in/out are moved to /proc/vmstat in >2.5 kernels\n\tif(-r \"/proc/vmstat\"){\n\t\tif(open(VMSTAT, \"</proc/vmstat\")){\n\t\t\tmy $line;\n\t\t\twhile($line = <VMSTAT>){\n\t\t\t\t$this->{DATA}->{\"raw_blocks_in\"} = $1 if($line =~ /^pgpgin\\s(\\d+)/);\n\t\t\t\t$this->{DATA}->{\"raw_blocks_out\"}= $1 if($line =~ /^pgpgout\\s(\\d+)/);\n\t\t\t\t$this->{DATA}->{\"raw_swap_in\"}  = $1 if($line =~ /^pswpin\\s(\\d+)/);\n\t\t\t\t$this->{DATA}->{\"raw_swap_out\"} = $1 if($line =~ /^pswpout\\s(\\d+)/);\n\t\t\t}\n\t\t\tclose VMSTAT;\n\t\t}else{\n\t\t\tlogger(\"NOTICE\", \"Procinfo: cannot open /proc/vmstat\");\n\t\t}\n\t}\n}\n\n# sizes are reported in MB (except _usage that is in percent).\nsub readMemInfo {\n\tmy $this = shift;\n\t\n\tif ($Config{osname} eq \"solaris\"){\n\t    if (open(MEM_INFO, \"prtconf |\")){\n\t\tmy $line;\n\t\t\n\t\twhile ($line = <MEM_INFO>){\n\t\t    if ($line =~ /^Memory size: (\\d+) (\\w)/){\n\t\t\t$this->{DATA}->{\"total_mem\"} = $1;\n\t\t\t\n\t\t\tif ($2 eq \"G\"){\n\t\t\t    $this->{DATA}->{\"total_mem\"} *= 1024;\n\t\t\t}\n\t\t    }\n\t\t}\n\t\t\n\t\tclose MEM_INFO;\n\t    }\n\t    \n\t    if (open(MEM_INFO, \"vmstat |\")){\n\t\tmy $line;\n\n\t\t# first header line\n\t\t$line = <MEM_INFO>;\n\t\t# second header line\n\t\t$line = <MEM_INFO>;\n\t\t# and the contents \n\t\t$line = <MEM_INFO>;\n\t\t\n\t\tif ($line =~ /^\\s*\\d+\\s+\\d+\\s+\\d+\\s+\\d+\\s+(\\d+)/){\n\t\t    my $memfree = $1 / 1024;\n\t\t\n\t\t    $this->{DATA}->{\"mem_free\"} = $memfree;\n\t\t    $this->{DATA}->{\"mem_actual_free\"} = $memfree;\n\t\t    $this->{DATA}->{\"mem_used\"} = $this->{DATA}->{\"total_mem\"} - $memfree;\n\t\t    $this->{DATA}->{\"mem_usage\"} = $this->{DATA}->{\"mem_used\"} * 100 / $this->{DATA}->{\"total_mem\"} if  $this->{DATA}->{\"total_mem\"};\n\t\t}\t\t\n\t\t\n\t\tclose MEM_INFO;\n\t    }\n\t    \n\t    if (open(MEM_INFO, \"swap -l |\")){\n\t\tmy $line;\n\t\t\n\t\t$line = <MEM_INFO>;\n\t\t\n\t\t$this->{DATA}->{\"total_swap\"} = 0;\n\t\t$this->{DATA}->{\"swap_free\"} = 0;\n\t\t\n\t\twhile ($line = <MEM_INFO>){\n\t\t    if ($line =~ /(\\d+)\\s+(\\d+)$/){\n\t\t\t$this->{DATA}->{\"total_swap\"} += $1 / 2048;\n\t\t\t$this->{DATA}->{\"swap_free\"} += $2 / 2048;\n\t\t    }\n\t\t}\n\t\t\n\t\t$this->{DATA}->{\"swap_used\"} = $this->{DATA}->{\"total_swap\"} - $this->{DATA}->{\"swap_free\"};\n\t\t\n\t\t$this->{DATA}->{\"swap_usage\"} = 100.0 * $this->{DATA}->{\"swap_used\"} / $this->{DATA}->{\"total_swap\"} if $this->{DATA}->{\"total_swap\"};\n\t    }\n\t\n\t    return;\n\t}\n\t\n\tif(open(MEM_INFO, \"</proc/meminfo\")){\n\t\tmy $line;\n\t\twhile($line = <MEM_INFO>){\n\t\t\tif($line =~ /^MemFree:/){\n\t\t\t\tmy (undef, $mem_free) = split(/ +/, $line);\n\t\t\t\t$this->{DATA}->{\"mem_free\"} = $mem_free / 1024.0;\n\t\t\t}\n\t\t\tif($line =~ /^MemTotal:/){\n\t\t\t\tmy (undef, $mem_total) = split(/ +/, $line);\n\t\t\t\t$this->{DATA}->{\"total_mem\"} = $mem_total / 1024.0;\n\t\t\t}\n\t\t\tif($line =~ /^SwapFree:/){\n\t\t\t\tmy (undef, $swap_free) = split(/ +/, $line);\n\t\t\t\t$this->{DATA}->{\"swap_free\"} = $swap_free / 1024.0;\n\t\t\t}\n\t\t\tif($line =~ /^SwapTotal:/){\n\t\t\t\tmy (undef, $swap_total) = split(/ +/, $line);\n\t\t\t\t$this->{DATA}->{\"total_swap\"} = $swap_total / 1024.0;\n\t\t\t}\n\t\t\tif($line =~ /^Buffers:/){\n\t\t\t\tmy (undef, $buffers) = split(/ +/, $line);\n\t\t\t\t$this->{DATA}->{\"mem_buffers\"} = $buffers / 1024.0;\n\t\t\t}\n\t\t\tif($line =~ /^Cached:/){\n\t\t\t\tmy (undef, $cached) = split(/ +/, $line);\n\t\t\t\t$this->{DATA}->{\"mem_cached\"} = $cached / 1024.0;\n\t\t\t}\n\t\t}\n\t\tclose MEM_INFO;\n\t\t$this->{DATA}->{\"mem_actualfree\"} = $this->{DATA}->{\"mem_free\"} + $this->{DATA}->{\"mem_buffers\"} + $this->{DATA}->{\"mem_cached\"}\n\t\t\tif ($this->{DATA}->{\"mem_free\"} && $this->{DATA}->{\"mem_buffers\"} && $this->{DATA}->{\"mem_cached\"});\n\t\t$this->{DATA}->{\"mem_used\"} = $this->{DATA}->{\"total_mem\"} - $this->{DATA}->{\"mem_actualfree\"} \n\t\t\tif ($this->{DATA}->{\"total_mem\"} && $this->{DATA}->{\"mem_actualfree\"});\n\t\t$this->{DATA}->{\"swap_used\"} = $this->{DATA}->{\"total_swap\"} - $this->{DATA}->{\"swap_free\"} if $this->{DATA}->{\"total_swap\"};\n\t\t$this->{DATA}->{\"mem_usage\"} = 100.0 * $this->{DATA}->{\"mem_used\"} / $this->{DATA}->{\"total_mem\"} \n\t\t\tif ($this->{DATA}->{\"total_mem\"} && $this->{DATA}->{\"mem_used\"});\n\t\t$this->{DATA}->{\"swap_usage\"} = 100.0 * $this->{DATA}->{\"swap_used\"} / $this->{DATA}->{\"total_swap\"} if $this->{DATA}->{\"total_swap\"};\n\t}else{\n\t\tlogger(\"NOTICE\", \"ProcInfo: cannot open /proc/meminfo\");\n\t}\n}\n\n# read the number of processes currently running on the system\n# count also the number of runnable, sleeping, zombie, io blocked and traced processes\n# works on Darwin\nsub countProcesses {\n\tmy $this = shift;\n\t\n\tmy $total = 0;\n\tmy %states = ('D' => 0, 'R' => 0, 'S' => 0, 'T' => 0, 'Z' => 0);\n\t\n\tmy $command = \"ps -A -o state |\";\n\t\n\tif ($Config{osname} eq \"solaris\"){\n\t    $command = \"ps -A -o s |\";\n\t}\n\t\n\tif(open(PROC, $command)){\n\t\tmy $state = <PROC>;      # ignore the first line - it's the header\n\t\twhile(<PROC>){\n\t\t\t$state = substr($_, 0, 1);\n\t\t\t$states{$state}++;\n\t\t\t$total++;\n\t\t}\n\n\t\tclose PROC;\n\t\t\n\t\t$this->{DATA}->{\"processes\"} = $total;\n\t\tfor $state (keys %states){\n\t\t\tnext if (($state eq '') || ($state =~ /\\s+/));\n\t\t\t$this->{DATA}->{\"processes_$state\"} = $states{$state};\n\t\t}\n\t}\n\telse{\n\t\tlogger(\"NOTICE\", \"ProcInfo: cannot count the processes using ps.\");\n\t}\n}\n\n#Read information about CPU.\nsub readCPUInfo {\n\tmy $this = shift;\n\t\n\tif ($Config{osname} eq \"solaris\"){\n\t    chomp ($this->{DATA}->{\"no_CPUs\"} = `psrinfo -p`);\n\n\t    return;\n\t}\n\t\n\tif(-r \"/proc/cpuinfo\"){\n\t\tif(open(CPU_INFO, \"</proc/cpuinfo\")){\n\t\t\tmy ($line, $no_cpus);\n\t\t\twhile($line = <CPU_INFO>){\n\t\t\t\tif($line =~ /cpu MHz\\s+:\\s+(\\d+\\.?\\d*)/){\n\t\t\t\t\t$this->{DATA}->{\"cpu_MHz\"} = $1;\n\t\t\t\t\t$no_cpus ++;\n\t\t\t\t}\n\t\t\t\tif($line =~ /vendor_id\\s+:\\s+(.+)/ || $line =~ /vendor\\s+:\\s+(.+)/){\n\t\t\t\t\t$this->{DATA}->{\"cpu_vendor_id\"} = $1;\n\t\t\t\t}\n\t\t\t\tif($line =~ /cpu family\\s+:\\s+(.+)/ || $line =~ /revision\\s+:\\s+(.+)/){\n\t\t\t\t\t$this->{DATA}->{\"cpu_family\"} = $1;\n\t\t\t\t}\n\t\t\t\tif($line =~ /model\\s+:\\s+(.+)/) {\n\t\t\t\t\t$this->{DATA}->{\"cpu_model\"} = $1;\n\t\t\t\t}\n\t\t\t\tif($line =~ /model name\\s+:\\s+(.+)/ || $line =~ /family\\s+:\\s+(.+)/){\n\t\t\t\t\t$this->{DATA}->{\"cpu_model_name\"} = $1;\n\t\t\t\t}\n\t\t\t\tif($line =~ /bogomips\\s+:\\s+(\\d+\\.?\\d*)/ || $line =~ /BogoMIPS\\s+:\\s+(\\d+\\.?\\d*)/){\n\t\t\t\t\t$this->{DATA}->{\"bogomips\"} = $1;\n\t\t\t\t}\n\t\t\t\tif($line =~ /cache size\\s+:\\s+(\\d+)/){\n\t\t\t\t\t$this->{DATA}->{\"cpu_cache\"} = $1;\n\t\t\t\t}\n\t\t\t}\n\t\t\tclose CPU_INFO;\n\t\t\t$this->{DATA}->{\"no_CPUs\"} = $no_cpus;\n\t\t}\n\t}\n\t# this is for Itanium\n\tif(-r \"/proc/pal/cpu0/cache_info\"){\n\t\tif(open(CACHE_INFO, \"</proc/pal/cpu0/cache_info\")){\n\t\t\tmy $line;\n\t\t\tmy $level3params = 0;\n\t\t\twhile($line = <CACHE_INFO>){\n\t\t\t\t$level3params = 1 if ($line =~ /Cache level 3/);\n\t\t\t\t$this->{DATA}->{\"cpu_cache\"} = $1 / 1024 if ($level3params && $line =~ /Size\\s+:\\s+(\\d+)/);\n\t\t\t}\n\t\t\tclose(CACHE_INFO);\n\t\t}\n\t}\n\t# also put the ksi2k factor, if known\n\t$this->{DATA}->{\"ksi2k_factor\"} = $ApMon::Common::KSI2K if $ApMon::Common::KSI2K;\n}\n\n# reads the IP, hostname, cpu_MHz, kernel_version, os_version, platform\nsub readGenericInfo {\n\tmy $this = shift;\n\n\tmy $hostname = Net::Domain::hostfqdn();\n\n\t$this->{DATA}->{\"hostname\"} = $hostname;\n\t\n\tif ($Config{osname} eq \"solaris\"){\n\t    chomp ($this->{DATA}->{\"os_type\"} = `uname -sr`);\n\t    $this->{DATA}->{\"platform\"} = \"solaris\";\n\t    $this->{DATA}->{\"kernel_version\"} = $Config{osvers};\n\t\n\t    if (open(IF_CFG, \"ifconfig -a4 |\")){\n\t\tmy ($eth, $ip, $line);\n\t\t\n\t\twhile ($line = <IF_CFG>){\n\t\t    if ($line =~ /^(\\w+\\d):/){\n\t\t\t$eth = $1;\n\t\t    }\n\t\t    \n\t\t    if (defined($eth) and ($line =~ /\\s+inet\\s+(\\d+\\.\\d+\\.\\d+\\.\\d+)/)){\n\t\t\t$ip = $1;\n\t\t\t\n\t\t\tnext if ($eth =~ /^lo/);\n\n\t\t\t$this->{DATA}->{$eth.\"_ip\"} = $ip;\n\n\t\t\t# fake eth0 on solaris\n\t\t\t$this->{DATA}->{\"eth0_ip\"} = $ip unless $this->{DATA}->{\"eth0_ip\"};\n\t\t    }\n\t\t}\n\t    }\n\t\n\t    return;\n\t}\n\t\n\tif(open(IF_CFG, \"/sbin/ifconfig -a |\")){\n\t\tmy ($eth, $ip, $ipv6, $line);\n\t\twhile($line = <IF_CFG>){\n\t\t\tif($line =~ /^(\\w+):?\\s+/ ){\n\t\t\t\tundef $ip;\n\t\t\t\tif (exists($this->{NETWORKINTERFACES}->{$1})){\n\t\t\t\t    $eth = $1;\n\t\t\t\t    undef $ip;\n\t\t\t\t    undef $ipv6;\n\t\t\t\t}\n\t\t\t\telse{\n\t\t\t\t    undef $eth;\n\t\t\t\t}\n\t\t\t\tnext;\n\t\t\t}\n\t\t\t\n\t\t\tif ($line =~ /^\\w/){\n\t\t\t    undef $eth;\n\t\t\t    undef $ip;\n\t\t\t    undef $ipv6;\n\t\t\t    next;\n\t\t\t}\n\t\t\t\n\t\t\tif(defined($eth) and ($line =~ /\\s+inet( addr:)?\\s*(\\d+\\.\\d+\\.\\d+\\.\\d+)/) and ! defined($ip)){\n\t\t\t\t$ip = $2;\n\t\t\t\t$this->{DATA}->{$eth.\"_ip\"} = $ip;\n\t\t\t\tundef $ipv6;\n\t\t\t}\n\n\t\t\tif(defined($eth) and ($line =~ /\\s+inet6( addr:)?\\s*([0-9a-fA-F:]+).*(Scope:Global|scopeid.*global)/) and ! defined($ipv6)){\n\t\t\t\t$ipv6 = $2;\n\t\t\t\t$this->{DATA}->{$eth.\"_ipv6\"} = $ipv6;\n\t\t\t}\n\t\t}\n\t\tclose IF_CFG;\n\t}else{\n\t\tlogger(\"NOTICE\", \"ProcInfo: couldn't get output from /sbin/ifconfig -a\");\n\t}\n\t# determine the kernel version\n\tmy $line = `uname -r`;\n\tchomp $line;\n\t$this->{DATA}->{\"kernel_version\"} = $line;\n\t\n\t# determine the platform\n\t$line = `uname -m 2>/dev/null || uname`;\n\tchomp $line;\n\t$this->{DATA}->{\"platform\"} = $line;\n\t\n\t# try to determine the OS type\n\tmy $osType = \"\";\n\tif(open(LSB_RELEASE, 'env PATH=$PATH:/bin:/usr/bin lsb_release -d 2>/dev/null |')){\n\t\tmy $line = <LSB_RELEASE>;\n\t\t$osType = $1 if ($line && $line =~ /Description:\\s*(.*)/);\n\t\tclose LSB_RELEASE;\n\t}\n\tif(! $osType){\n\t\tfor my $f (\"/etc/redhat-release\", \"/etc/debian_version\", \"/etc/SuSE-release\", \n\t\t\t   \"/etc/slackware-version\", \"/etc/gentoo-release\", \"/etc/mandrake-release\", \n\t\t\t   \"/etc/mandriva-release\", \"/etc/issue\"){\n\t\t\tif(open(VERF, \"$f\")){\n\t\t\t\t$osType = <VERF>;\n\t\t\t\tchomp $osType;\n\t\t\t\tclose VERF;\n\t\t\t\tlast;\n\t\t\t}\n\t\t}\n\t}\n\tif(! $osType){\n\t\t$osType = `uname -s`;\n\t\tchomp $osType;\n\t}\n\t$this->{DATA}->{\"os_type\"} = $osType;\n}\n\n# read system's uptime and load average. Time is reported as a floating number, in days.\n# It uses the 'uptime' command which's output looks like these:\n# 19:55:37 up 11 days, 18:57,  1 user,  load average: 0.00, 0.00, 0.00\n# 18:42:31 up 87 days, 18:10,  9 users,  load average: 0.64, 0.84, 0.80\n# 6:42pm  up 7 days  3:08,  7 users,  load average: 0.18, 0.14, 0.10\n# 6:42pm  up 33 day(s),  1:54,  1 user,  load average: 0.01, 0.00, 0.00\n# 18:42  up 7 days,  3:45, 2 users, load averages: 1.10 1.11 1.06\n# 18:47:41  up 7 days,  4:35, 19 users,  load average: 0.66, 0.44, 0.41\n# 15:10  up 8 days, 12 mins, 2 users, load averages: 1.46 1.27 1.18\n# 11:57am  up   2:21,  22 users,  load average: 0.59, 0.93, 0.73\nsub readUptimeAndLoadAvg {\n\tmy $this = shift;\n\n\tmy $line = `uptime`;\n\tchomp $line;\n\tif($line =~ /up\\s+((\\d+)\\s+day[ (s),]+)?(\\d+)(:(\\d+))?[^\\d]+(\\d+)[^\\d]+([\\d\\.]+)[^\\d]+([\\d\\.]+)[^\\d]+([\\d\\.]+)/){\n\t\tmy ($days, $hour, $min, $users, $load1, $load5, $load15) = ($2, $3, $5, $6, $7, $8, $9);\n\t\tif(! $min){\n\t\t\t$min = $hour;\n\t\t\t$hour = 0;\n\t\t}\n\t\t$days = 0 if ! $days;\n\t\tmy $uptime = $days + $hour / 24.0 + $min / 1440.0;\n\t\t$this->{DATA}->{\"uptime\"} = $uptime;\n\t\t$this->{DATA}->{\"logged_users\"} = $users;   # this is currently not reported!\n\t\t$this->{DATA}->{\"load1\"} = $load1;\n\t\t$this->{DATA}->{\"load5\"} = $load5;\n\t\t$this->{DATA}->{\"load15\"}= $load15;\n\t}\n\telse{\n\t\tlogger(\"NOTICE\", \"ProcInfo: got unparsable output from uptime: $line\");\n\t}\n}\n\n\nsub readEosDiskValues {\n    my $this = shift;\n    my $storagepath=$ENV{\"APMON_STORAGEPATH\"};\n\n    if ( \"$storagepath\" eq \"\" ) {\n        $storagepath = \"data\";\n    }\n\n    if (open IN, \"df -P -B 1 | grep $storagepath | grep -v Filesystem | awk '{a+=\\$2;b+=\\$3;c+=\\$4;print a,b,c}' | tail -1|\") {\n        my $all = <IN>;\n        if ($all) {\n            my @vals = split (\" \",$all);\n            $this->{DATA}->{\"eos_disk_space\"} = sprintf \"%.03f\",$vals[0]/1024.0/1024.0/1024.0/1024.0;\n            $this->{DATA}->{\"eos_disk_used\"}  = sprintf \"%.03f\",$vals[1]/1024.0/1024.0/1024.0/1024.0;\n            $this->{DATA}->{\"eos_disk_free\"}  = sprintf \"%.03f\",$vals[2]/1024.0/1024.0/1024.0/1024.0;\n            $this->{DATA}->{\"eos_disk_usage\"} = sprintf \"%d\",100.0 *$vals[1]/$vals[0];\n        }\n        close(IN);\n    }\n}\n\nsub readEosRpmValues {\n    my $this = shift;\n    if (open IN, \"rpm -qa eos-xrootd | cut -d '-' -f3 |\") {\n        my $all = <IN>;\n        if ($all) {\n            chomp $all;\n            $all =~ s/xrootd-//;\n            $this->{DATA}->{\"xrootd_rpm_version\"} = 'v'.$all;\n        }\n        close(IN);\n    }\n\n    if (open IN, \"rpm -qa eos-server |\") {\n        my $all = <IN>;\n        if ($all) {\n            chomp $all;\n            $all =~ s/eos-server-//;\n            $this->{DATA}->{\"eos_rpm_version\"} = $all;\n        }\n        close(IN);\n    }\n}\n\nsub show_call_stack {  \n    my ( $path, $line, $subr );  \n    my $max_depth = 30; \n    my $i = 1;  \n\n    while ( (my @call_details = (caller($i++))) && ($i<$max_depth) ) {\n\tprint \"$call_details[1] line $call_details[2] in function $call_details[3]\\n\";\n    }\n}\n\n# do a difference with overflow check and repair\n# the counter is unsigned 32 or 64 bit\nsub diffWithOverflowCheck {\n\tmy ($this, $new, $old) = @_;\n\n\tif($new >= $old){\n\t\treturn $new - $old;\n\t}\n\telse{\n\t\treturn $new;\n\t}\n}\n\n# read network information like transfered kBps and nr. of errors on each interface\n# TODO: find an alternative for MAC OS X\nsub readNetworkInfo {\n\tmy $this = shift;\n\n\t$this->{NETWORKINTERFACES} = {};\n\t\n\tif ($Config{osname} eq \"solaris\"){\n\t    my $ifname;\n\t    my $line; \n\t\n\t    if (open(NET_DEV, \"ifconfig -a4 |\")){\n\t    \twhile ($line = <NET_DEV>){\n\t\t    next if ($ifname);\n\t\t    \n\t\t    if ($line =~ /^(\\w+\\d):\\s+/){\n\t\t\tnext if ($line =~ /^lo/);\n\t\t    \n\t\t\t$ifname = $1;\n\t\t    }\n\t\t}\n\t\t\n\t\tclose NET_DEV;\n\t    }\n\t    \n\t    \n\t    my $bytesIn = 0;\n\t    my $bytesOut = 0;\n\t    \n\t    if (open(NET_DEV,\"netstat -P tcp -s |\")){\n\t\twhile ($line = <NET_DEV>){\n\t\t    if ($line =~ /tcpOut\\w+Bytes\\s*=\\s*(\\d+)/){\n\t\t\t$bytesOut += $1;\n\t\t    }\n\t\t    \n\t\t    if ($line =~ /tcpRetransBytes\\s*=\\s*(\\d+)/){\n\t\t\t$bytesOut += $1;\n\t\t    }\n\t\t    \n\t\t    if ($line =~ /tcpIn\\w+Bytes\\s*=\\s*(\\d+)/){\n\t\t\t$bytesIn += $1;\n\t\t    }\n\t\t}\n\t\t\n\t\tclose NET_DEV;\n\t\t\n\t\t$this->{DATA}->{\"raw_net_\".$ifname.\"_in\"} = $bytesIn;\n\t\t$this->{DATA}->{\"raw_net_\".$ifname.\"_out\"} = $bytesOut;\n\t\t$this->{DATA}->{\"raw_net_\".$ifname.\"_err\"} = 0;\n\t\t\n\t\t#fake eth0 traffic, even if on Solaris the interfaces have weird names\n\t\t#and moreover we cannot tell the traffic per each interface...\n\t\t$this->{DATA}->{\"raw_net_eth0_in\"} = $bytesIn;\n\t\t$this->{DATA}->{\"raw_net_eth0_out\"} = $bytesOut;\n\t\t$this->{DATA}->{\"raw_net_eth0_err\"} = 0;\n\n\t\t$this->{DATA}->{\"raw_net_total_traffic_in\"} = $bytesIn;\n\t\t$this->{DATA}->{\"raw_net_total_traffic_out\"} = $bytesOut;\n\n\t\t$this->{NETWORKINTERFACES}->{\"eth0\"} = \"eth0\";\n\t\t$this->{NETWORKINTERFACES}->{\"total_traffic\"} = \"total_traffic\";\n\t    }\n\t    \n\t    return;\n\t}\n\n\tif (opendir my $dh, \"/sys/class/net\"){\n\t    my @things = grep {$_ ne '.' and $_ ne '..' } readdir $dh;\n\t    foreach my $thing (@things) {\n\t\tmy $link = readlink(\"/sys/class/net/\".$thing);\n\t\tif (defined($link) && index($link, \"/virtual/\")<0){\n\t\t    $this->{NETWORKINTERFACES}->{$thing} = $thing;\n\t\t}\n\t    }\n\t}\n\n\tmy $total_traffic_in=0;\n\tmy $total_traffic_out=0;\n\n\tif(open(NET_DEV, \"</proc/net/dev\")){\n\t\twhile (my $line = <NET_DEV>) {\n\t\t\tif($line =~ /\\s*(\\w+):\\s*(\\d+)\\s+\\d+\\s+(\\d+)\\s+\\d+\\s+\\d+\\s+\\d+\\s+\\d+\\s+\\d+\\s+(\\d+)\\s+\\d+\\s+(\\d+)/){\n\t\t\t\tif ( exists($this->{NETWORKINTERFACES}->{$1}) ){\n\t\t\t\t    $this->{DATA}->{\"raw_net_$1\".\"_in\"} = $2;\n\t\t\t\t    $this->{DATA}->{\"raw_net_$1\".\"_out\"} = $4;\n\t\t\t\t    $this->{DATA}->{\"raw_net_$1\".\"_errs\"} = $3 + $5; # in and out errors\n\n\t\t\t\t    $total_traffic_in += $2;\n\t\t\t\t    $total_traffic_out += $4;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tclose NET_DEV;\n\t}else{\n\t\tlogger(\"NOTICE\", \"ProcInfo: cannot open /proc/net/dev\");\n\t}\n\n\t$this->{DATA}->{\"raw_net_total_traffic_in\" } = $total_traffic_in;\n\t$this->{DATA}->{\"raw_net_total_traffic_out\"} = $total_traffic_out;\n\n\t$this->{NETWORKINTERFACES}->{\"total_traffic\"} = \"total_traffic\";\n}\n\n# run nestat \n# Note: this works on MAC OS X\nsub readNetStat {\n\tmy $this = shift;\n\n\tif(open(NETSTAT, 'env PATH=$PATH:/usr/sbin netstat -an 2>/dev/null |')){\n\t    my %sockets = map { +\"sockets_$_\" => 0 } ('tcp', 'udp', 'unix'); #icm will be auto added on mac\n\t    my %tcp_details = map { +\"sockets_tcp_$_\" => 0 } \n\t\t\t('ESTABLISHED', 'SYN_SENT', 'SYN_RECV', 'FIN_WAIT1', \n\t\t\t 'FIN_WAIT2', 'TIME_WAIT', 'CLOSED', 'CLOSE_WAIT', \n\t\t\t 'LAST_ACK', 'LISTEN', 'CLOSING', 'UNKNOWN');\n\t\n\t    if ($Config{osname} eq \"solaris\"){\n\t\tmy $sockclass;\n\t\t\n\t\twhile (my $line = <NETSTAT>){\n\t\t    if ($line =~ /^UDP:/){\n\t\t\t$sockclass = \"udp\";\n\t\t\t$line = <NETSTAT>;\n\t\t\t$line = <NETSTAT>;\n\t\t\tnext;\n\t\t    }\n\t\t    \n\t\t    if ($line =~ /^TCP:/){\n\t\t\t$sockclass = \"tcp\";\n\t\t\t$line = <NETSTAT>;\n\t\t\t$line = <NETSTAT>;\n\t\t\tnext;\n\t\t    }\n\t\t    \n\t\t    if ($line =~ /^SCTP:/){\n\t\t\t$sockclass = \"sctp\";\n\t\t\t$line = <NETSTAT>;\n\t\t\t$line = <NETSTAT>;\n\t\t\tnext;\n\t\t    }\n\t\t    \n\t\t    if ($line =~ /^Active UNIX domain sockets/){\n\t\t\t$sockclass = \"unix\";\n\t\t\t$line = <NETSTAT>;\n\t\t\tnext;\n\t\t    }\n\t\t    \n\t\t    chomp ($line);\n\t\t    \n\t\t    if (length($line) == 0){\n\t\t\tundef $sockclass;\n\t\t\tnext;\n\t\t    }\n\t\t    \n\t\t    if (defined($sockclass)){\n\t\t\tif ($sockclass eq \"tcp\"){\n\t\t\t    if ($line =~ /\\s+(\\w+)\\s*$/){\n\t\t\t\t$sockets{\"sockets_tcp\"}++;\n\n\t\t\t\tmy $state = uc($1);\n\t\t\t\t\n\t\t\t\tif (not defined($tcp_details{\"sockets_tcp_\".$state})){\n\t\t\t\t    $tcp_details{\"sockets_tcp_\".$state} = 0;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t$tcp_details{\"sockets_tcp_\".$state}++;\n\t\t\t    }\n\t\t\t}\n\t\t\telse{\n\t\t\t    if (not defined($sockets{\"sockets_$sockclass\"})){\n\t\t\t\t$sockets{\"sockets_$sockclass\"} = 0;\n\t\t\t    }\n\t\t\t    \n\t\t\t    $sockets{\"sockets_$sockclass\"}++;\n\t\t\t}\n\t\t    }\n\t\t}\n\t    }\n\t    else{\n\t\twhile (my $line = <NETSTAT>) {\n\t\t\t$line =~ s/\\s+$//;\n\t\t\tmy $proto = ($line =~ /^([^\\s]+)/ ? $1 : \"\");\n\t\t\tmy $state = ($line =~ /([^\\s]+)$/ ? $1 : \"\");\n\t\t\t$proto = \"unix\" if $line =~ /stream/i || $line =~ /dgram/i;\n\t\t\tif($proto =~ /tcp/){\n\t\t\t\t$sockets{\"sockets_tcp\"}++;\n\t\t\t\t$tcp_details{\"sockets_tcp_\".$state}++;\n\t\t\t}elsif($proto =~ /udp/){\n\t\t\t\t$sockets{\"sockets_udp\"}++;\n\t\t\t}elsif($proto =~ /icm/){\n\t\t\t\t$sockets{\"sockets_icm\"}++;\n\t\t\t}elsif($proto =~ /unix/){\n\t\t\t\t$sockets{\"sockets_unix\"}++;\n\t\t\t}\n\t\t}\n\t    }\n\t\n\t    close NETSTAT;\n\t    while(my ($key, $value) = each(%sockets)){ $this->{DATA}->{$key} = $value; }\n\t    while(my ($key, $value) = each(%tcp_details)){ $this->{DATA}->{$key} = $value; }\n\t}\n\telse{\n\t\tlogger(\"NOTICE\", \"ProcInfo: cannot run netstat\");\n\t}\n}\n\n# internal function that gets the full list of children (pids) for a process (pid)\n# it returns an empty list if the process has died\n# Note: This works on MAC OS X\nsub getChildren {\n\tmy ($this, $parent) = @_;\n\tmy @children = ();\n\tmy %pidmap = ();\n\tif(open(PIDS, 'ps -A -o \"pid ppid\" |')){\n\t\t$_ = <PIDS>; # skip header\n\t\twhile(<PIDS>){\n\t\t\tif(/\\s*(\\d+)\\s+(\\d+)/){\n\t\t\t\t$pidmap{$1} = $2;\n\t\t\t\tpush(@children, $parent) if $1 == $parent;\n\t\t\t}\n\t\t}\n\t\tclose(PIDS);\n\t}else{\n\t\tlogger(\"NOTICE\", \"ProcInfo: cannot execute ps -A -o \\\"pid ppid\\\"\");\n\t}\n\tfor(my $i = 0; $i < @children; $i++){\n\t\tmy $prnt = $children[$i];\n\t\twhile( my ($pid, $ppid) = each %pidmap ){\n\t\t\tif($ppid == $prnt){\n\t\t\t\tpush(@children, $pid);\n\t\t\t}\n\t\t}\n\t}\n\treturn @children;\n}\n\n# internal function that parses a time formatted like \"days-hours:min:sec\" and returns the corresponding\n# number of seconds.\nsub parsePSElapsedTime {\n\tmy ($this, $time) = @_;\n\tif($time =~ /(\\d+)-(\\d+):(\\d+):(\\d+)/){\n\t\treturn $1 * 24 * 3600 + $2 * 3600 + $3 * 60 + $4;\n\t}elsif($time =~ /(\\d+):(\\d+):(\\d+)/){\n\t\treturn $1 * 3600 + $2 * 60 + $3;\n\t}elsif($time =~ /(\\d+):(\\d+)/){\n\t\treturn $1 * 60 + $2;\n\t}else{\n\t\treturn 0;\n\t}\n}\n\n# internal function that parses time formatted like \"Tue Feb  7 17:13:17 2006\" and the returns the\n# corresponding number of seconds from EPOCH\nsub parsePSStartTime {\n\tmy ($this, $strTime) = @_;\n\t\n\tif($strTime !~ /\\S+\\s+(\\S+)\\s+(\\d+)\\s+(\\d+):(\\d+):(\\d+)\\s+(\\d+)/){\n\t\treturn 0;\n\t}else{\n\t\tmy ($strMonth, $mday, $hour, $min, $sec, $year) = ($1, $2, $3, $4, $5, $6);\n\t\tmy $mon = 0;\n\t\tfor my $month (@{$this->{MONTHS}}){\n\t\t\tlast if $month eq $strMonth;\n\t\t\t$mon++;\n\t\t}\n\t\treturn timelocal($sec, $min, $hour, $mday, $mon, $year);\n\t}\n}\n\n# read information about this the JOB_PID process\n# memory sizes are given in KB\n# Note: This works on MAC OS X\nsub readJobInfo {\n\tmy ($this, $pid) = @_;\n\treturn unless $pid;\n\tmy @children = $this->getChildren($pid);\n\tlogger(\"DEBUG\", \"ProcInfo: Children for pid=$pid; are @children.\");\n\tif(@children == 0){\n\t\tlogger(\"INFO\", \"ProcInfo: Job with pid=$pid terminated; removing it from monitored jobs.\");\n\t\t$this->removeJobToMonitor($pid);\n\t\treturn;\n\t}\n\tif(open(J_STATUS, 'ps -A -o \"pid lstart time %cpu %mem rsz vsz command\" |')){\n\t\tmy $line = <J_STATUS>; # skip header\n\t\tmy ($etime, $cputime, $pcpu, $pmem, $rsz, $vsz, $comm, $fd, $minflt, $majflt) = (0, 0, 0, 0, 0, 0, 0, undef, 0, 0);\n\t\tmy $cputime_offset = $this->{JOBS}->{$pid}->{DATA}->{'cpu_time_offset'} || 0;\n\t\tmy %mem_cmd_map = ();  # this contains all $rsz_$vsz_$command as keys for every pid\n\t\t# it is used to avoid adding several times processes that have multiple threads and appear in\n\t\t# ps as sepparate processes, occupying exacly the same amount of memory. The reason for not adding\n\t\t# them multiple times is that that memory is shared as they are threads.\n\t\tmy $crtTime = time();\n\t\twhile($line = <J_STATUS>){\n\t\t\tchomp $line; \n\t\t\t$line =~ s/\\s+/ /g; $line =~ s/^\\s+//; $line =~ s/\\s+$//;\n\t\t\t# line looks like: \n\t\t\t# \"PID              STARTED     TIME    %CPU %MEM RSZ VSZ COMMAND\"\n\t\t\t# \"6157 Tue Feb 7 22:15:30 2006 00:00:00 0.0 0.0 428 1452 g++ -O -pipe...\"\n\t\t\tif($line =~ /(\\S+) (\\S+ \\S+ \\S+ \\S+ \\S+) (\\S+) (\\S+) (\\S+) (\\S+) (\\S+) (.+)/){\n\t\t\t\tmy($apid, $stime1, $cputime1, $pcpu1, $pmem1, $rsz1, $vsz1, $comm1) \n\t\t\t\t\t= ($1, $2, $3, abs($4), abs($5), $6, $7, $8); # % can be negative on mac!?!\n\t\t\t\tmy $isChild = 0;\n\t\t\t\tfor my $childPid (@children){\n\t\t\t\t\tif($apid == $childPid){\n\t\t\t\t\t\t$isChild = 1;\n\t\t\t\t\t\tlast;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tnext if(! $isChild);\n\t\t\t\tmy $sec = $crtTime - $this->parsePSStartTime($stime1);\n\t\t\t\t$etime = $sec if $sec > $etime;\t\t# the elapsed time is the maximum of all elapsed\n\t\t\t\t$sec = $this->parsePSElapsedTime($cputime1);   # times corespornding to all child processes.\n\t\t\t\t$cputime += $sec; # total cputime is the sum of cputimes for all processes.\n\t\t\t\t$pcpu += $pcpu1; # total %cpu is the sum of all children %cpu.\n\t\t\t\tif(! $mem_cmd_map{\"$pmem1 $rsz1 $vsz1 $comm1\"} ++){\n\t\t\t\t\t# it's the first thread/process with this memory footprint; add it.\n\t\t\t\t\t$pmem += $pmem1; $rsz += $rsz1; $vsz += $vsz1;\n\t\t\t\t\t# the same is true for the number of opened files\n\t\t\t\t\tmy $thisFD = $this->countOpenFD($apid);\n\t\t\t\t\t$fd += $thisFD if (defined $thisFD);\n\t\t\t\t} # else not adding memory usage.\n\t\t\t\t# Get the number of minor and major page faults\n\t\t\t\tif(open(STAT, \"/proc/$apid/stat\")){\n\t\t\t\t\tmy $line = <STAT>;\n\t\t\t\t\tmy($pid, $exec, $status, $ppid, $pgrp, $sid, $tty, $tty_grp, $flags, $mflt, $cmflt, $jflt, $cjflt)\n\t\t\t\t\t\t= split(/\\s+/, $line);\n\t\t\t\t\t$minflt += $mflt;\n\t\t\t\t\t$majflt += $jflt;\n\t\t\t\t}\n\t\t\t\tclose(STAT);\n\t\t\t}\n\t\t}\n\t\tclose(J_STATUS);\n\t\t$cputime += $cputime_offset;\n\t\tmy $cputime_delta = ($this->{JOBS}->{$pid}->{DATA}->{'cpu_time'} || 0) - $cputime; # note this is the other way around!\n\t\tif($cputime_delta > 0){\n\t\t\t# Current time is lower than previous - one of the forked processes finished and\n\t\t\t# its contribution to the cpu_time disappeared.\n\t\t\t# We have to recalculate the cputime_offset. Note that in this case, we lose the \n\t\t\t# cpu_time of the other processes, consumed between these two reports.\n\t\t\t$cputime_offset += $cputime_delta;\n\t\t\t$cputime += $cputime_delta;\n\t\t}\n\t\t$cputime_delta = $cputime - ($this->{JOBS}->{$pid}->{DATA}->{'cpu_time'} || 0);\t# real cpu time delta\n\t\tmy $etime_delta = $etime - ($this->{JOBS}->{$pid}->{DATA}->{'run_time'} || 0);\t# real elapsed time delta\n\t\tmy $crtCpuSpeed = $this->{DATA}->{'cpu_MHz'} || 1;\n\t\tmy $orgCpuSpeed = $ApMon::Common::CpuMHz || $crtCpuSpeed;\n\t\t#my $freqFact = $crtCpuSpeed / $orgCpuSpeed; # if Cpu speed varies in time, adjust ksi2k factor\n\t\tmy $freqFact = 1;\t\t\n\t\t$this->{JOBS}->{$pid}->{DATA}->{'run_time'} += $etime_delta;\n\t\t$this->{JOBS}->{$pid}->{DATA}->{'run_ksi2k'} += $etime_delta * $freqFact * $ApMon::Common::KSI2K if $ApMon::Common::KSI2K;\n\t\t$this->{JOBS}->{$pid}->{DATA}->{'cpu_time'} += $cputime_delta;\n\t\t$this->{JOBS}->{$pid}->{DATA}->{'cpu_ksi2k'} += $cputime_delta * $freqFact * $ApMon::Common::KSI2K if $ApMon::Common::KSI2K;\n\t\t$this->{JOBS}->{$pid}->{DATA}->{'cpu_time_offset'} = $cputime_offset;\n\t\t$this->{JOBS}->{$pid}->{DATA}->{'cpu_usage'} = $pcpu;\n\t\t$this->{JOBS}->{$pid}->{DATA}->{'mem_usage'} = $pmem;\n\t\t$this->{JOBS}->{$pid}->{DATA}->{'rss'} = $rsz;\n\t\t$this->{JOBS}->{$pid}->{DATA}->{'virtualmem'} = $vsz;\n\t\t$this->{JOBS}->{$pid}->{DATA}->{'open_files'} = $fd if (defined $fd);\n\t\t$this->{JOBS}->{$pid}->{DATA}->{'page_faults_min'} = $minflt;\n\t\t$this->{JOBS}->{$pid}->{DATA}->{'page_faults_maj'} = $majflt;\n\t}else{\n\t\tlogger(\"NOTICE\", \"ProcInfo: cannot run ps to see job's status for job $pid\");\n\t}\n}\n\n# count the number of open files for the given pid\n# TODO: find an equivalent for MAC OS X\nsub countOpenFD {\n\tmy ($this, $pid) = @_;\n\t\n\tif ($Config{osname} eq \"solaris\"){\n\t    return undef;\n\t}\n\n\tif(opendir(DIR, \"/proc/$pid/fd\")){\n\t\tmy @list = readdir(DIR);\n\t\tclosedir DIR;\n\t\tmy $open_files = ($pid == $$ ? @list - 4 : @list - 2);\n\t\tlogger(\"DEBUG\", \"Counting open_files for $pid: |@list| => $open_files\");\n\t\treturn $open_files;\n\t}else{\n\t\tlogger(\"NOTICE\", \"ProcInfo: cannot count the number of opened files for job $pid\");\n\t}\n\treturn undef;\n}\n\n# if there is an work directory defined, then compute the used space in that directory\n# and the free disk space on the partition to which that directory belongs\n# sizes are given in MB\n# Note: this works on MAC OS X\nsub readJobDiskUsage {\n\tmy ($this, $pid) = @_;\n\t\n\tmy $workDir = $this->{JOBS}->{$pid}->{WORKDIR};\n\treturn unless $workDir and -d $workDir;\n\tif(open(DU, \"du -Lsck $workDir | tail -1 | cut -f 1 |\")){\n\t\tmy $line = <DU>;\n\t\tif($line){\n\t\t\tchomp $line;\n\t\t\t$this->{JOBS}->{$pid}->{DATA}->{'workdir_size'} = $line / 1024.0;\n\t\t}else{\n\t\t\tlogger(\"NOTICE\", \"ProcInfo: cannot get du output for job $pid\");\n\t\t}\n\t\tclose(DU);\n\t}else{\n\t\tlogger(\"NOTICE\", \"ProcInfo: cannot run du to get job's disk usage for job $pid\");\n\t}\n\tif(open(DF, \"df -k $workDir | tail -1 |\")){\n\t\tmy $line = <DF>;\n\t\tif($line){\n\t\t\tchomp $line;\n\t\t\tif($line =~ /\\S+\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)%/){\n\t\t\t\t$this->{JOBS}->{$pid}->{DATA}->{'disk_total'} = $1 / 1024.0;\n\t\t\t\t$this->{JOBS}->{$pid}->{DATA}->{'disk_used'} = $2 / 1024.0;\n\t\t\t\t$this->{JOBS}->{$pid}->{DATA}->{'disk_free'} = $3 / 1024.0;\n\t\t\t\t$this->{JOBS}->{$pid}->{DATA}->{'disk_usage'} = $4;\n\t\t\t}\n\t\t}else{\n\t\t\tlogger(\"NOTICE\", \"ProcInfo: cannot get df output for job $pid\");\n\t\t}\n\t\tclose(DF);\n\t}else{\n\t\tlogger(\"NOTICE\", \"ProcInfo: cannot run df to get job's disk usage for job $pid\");\n\t}\n}\n\n# create cummulative parameters based on raw params like cpu_, blocks_, swap_, or ethX_\nsub computeCummulativeParams {\n\tmy ($this, $dataRef, $prevDataRef) = @_;\n\n\tif(scalar(keys %$prevDataRef) == 0){\n\t\tfor my $param (keys %$dataRef){\n\t\t\tnext if $param !~ /^raw_/;\n\t\t\t$prevDataRef->{$param} = $dataRef->{$param};\n\t\t}\n\t\t$prevDataRef->{'TIME'} = $dataRef->{'TIME'};\n\t\treturn;\n\t}\n\n\t# cpu -related params\n\tif(defined($dataRef->{'raw_cpu_usr'}) && defined($prevDataRef->{'raw_cpu_usr'})){\n\t\tmy %diff = ();\n\t\tmy $cpu_sum = 0;\n\t\tfor my $param ('cpu_usr', 'cpu_nice', 'cpu_sys', 'cpu_idle', 'cpu_iowait', 'cpu_irq', 'cpu_softirq', 'cpu_steal', 'cpu_guest') {\n\t\t    if (defined($dataRef->{\"raw_$param\"}) && defined($prevDataRef->{\"raw_$param\"})){\n\t\t\t$diff{$param} = $this->diffWithOverflowCheck($dataRef->{\"raw_$param\"}, $prevDataRef->{\"raw_$param\"});\n\t\t\t$cpu_sum += $diff{$param};\n\t\t    }\n\t\t}\n\t\tfor my $param ('cpu_usr', 'cpu_nice', 'cpu_sys', 'cpu_idle', 'cpu_iowait', 'cpu_irq', 'cpu_softirq', 'cpu_steal', 'cpu_guest') {\n\t\t    if (defined($dataRef->{\"raw_$param\"}) && defined($prevDataRef->{\"raw_$param\"})){\n\t\t\tif($cpu_sum != 0){\n\t\t\t\t$dataRef->{$param} = 100.0 * $diff{$param} / $cpu_sum;\n\t\t\t}else{\n\t\t\t\tdelete $dataRef->{$param};\n\t\t\t}\n\t\t    }\n\t\t}\n\t\t\n\t\tif($cpu_sum != 0){\n\t\t\t$dataRef->{'cpu_usage'} = 100.0 * ($cpu_sum - $diff{'cpu_idle'}) / $cpu_sum;\n\t\t}else{\n\t\t\tdelete $dataRef->{'cpu_usage'};\n\t\t}\n\t\t# add the other parameters\n\t\tfor my $param ('interrupts', 'context_switches'){\n\t\t\tif(defined($dataRef->{\"raw_$param\"}) && defined($prevDataRef->{\"raw_$param\"})){\n\t\t\t\t$dataRef->{$param} = $this->diffWithOverflowCheck($dataRef->{\"raw_$param\"}, $prevDataRef->{\"raw_$param\"});\n\t\t\t}\n\t\t}\n\t}\n\n\t# interrupts, context switches, swap & blocks - related params\n\tmy $interval = $dataRef->{TIME} - $prevDataRef->{TIME};\n\tfor my $param ('blocks_in', 'blocks_out', 'swap_in', 'swap_out', 'interrupts', 'context_switches') {\n\t\tif(defined($dataRef->{\"raw_$param\"}) && defined($prevDataRef->{\"raw_$param\"}) && ($interval != 0)){\n\t\t\tmy $diff = $this->diffWithOverflowCheck($dataRef->{\"raw_$param\"}, $prevDataRef->{\"raw_$param\"});\n\t\t\t$dataRef->{$param.\"_R\"} = $diff / $interval;\n\t\t}else{\n\t\t\tdelete $dataRef->{$param.\"_R\"};\n\t\t}\n\t}\n\n\t# physical network interfaces - related params\n\tfor my $rawParam (keys %$dataRef){\n\t\tnext if $rawParam !~ /^raw_net_/;\n\t\tnext if ! defined($prevDataRef->{$rawParam});\n\t\tmy $param = $1 if($rawParam =~ /raw_net_(.*)/);\n\n\t\tif($interval != 0){\n\t\t\t$dataRef->{$param} = $this->diffWithOverflowCheck($dataRef->{$rawParam}, $prevDataRef->{$rawParam}); # absolute difference\n\t\t\t$dataRef->{$param} = $dataRef->{$param} / $interval / 1024.0 if($param !~ /_errs$/); # if it's _in or _out, compute in KB/sec\n\t\t}else{\n\t\t\tdelete $dataRef->{$param};\n\t\t}\n\t}\n\n\t# copy contents of the current data values to the \n\tfor my $param (keys %$dataRef){\n\t\tnext if $param !~ /^raw_/;\n\t\t$prevDataRef->{$param} = $dataRef->{$param};\n\t}\n\t$prevDataRef->{'TIME'} = $dataRef->{'TIME'};\n}\n\n\n# Return the array image of a hash with the requested parameters (from paramsRef)\n# sorted alphabetically\n# The cummulative parameters are computed based on $prevDataRef\n# As a side effect, prevDataRef is updated to have the values in dataRef.\nsub getFilteredData {\n\tmy ($this, $dataRef, $paramsRef, $prevDataRef, $networkInterfaces) = @_;\n\n\t# we don't do this for jobs\n\t$this->computeCummulativeParams($dataRef, $prevDataRef)\tif($prevDataRef);\n\n\tmy %result = ();\n\tfor my $param (@$paramsRef) {\n\t\tif($param eq \"net_sockets\"){\n\t\t\tfor my $key (keys %$dataRef) {\n\t\t\t\t$result{$key} = $dataRef->{$key} if $key =~ /sockets_[^_]+$/;\n\t\t\t}\n\t\t}elsif($param eq \"net_tcp_details\"){\n\t\t\tfor my $key (keys %$dataRef) {\n\t\t\t\t$result{$key} = $dataRef->{$key} if $key =~ /sockets_tcp_/;\n\t\t\t}\n\t\t}elsif($param =~ /^net_(.*)$/ or $param =~ /^(ip)$/){\n\t\t\tmy $net_param = $1;\n\t\t\tfor my $key (keys %$dataRef) {\n\t\t\t\tif ($key =~ /^(\\w+)_$net_param/ ){\n\t\t\t\t\tif ( exists ($networkInterfaces->{$1}) ){\n\t\t\t\t\t    $result{$key} = $dataRef->{$key};\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}elsif($param eq \"processes\"){\n\t\t\tfor my $key (keys %$dataRef) {\n\t\t\t\t$result{$key} = $dataRef->{$key} if $key =~ /^processes/;\n\t\t\t}\n\t\t}elsif($param =~ /blocks_|swap_|interrupts|context_switches/){\n\t\t\tfor my $key (keys %$dataRef) {\n\t\t\t\t$result{$key} = $dataRef->{$key} if $key =~ /^${param}_R$/;\n\t\t\t}\n\t\t\t$result{$param} = $dataRef->{$param} if($param =~/^swap_/ && defined($dataRef->{$param}));\n\t\t}\n\t\telse{\n\t\t\t$result{$param} = $dataRef->{$param} if defined $dataRef->{$param};\n\t\t}\n\t}\n\tmy @sorted_result = ();\n\tfor my $key (sort (keys %result)) {\n\t\tpush(@sorted_result, $key, $result{$key});\n\t}\n\t\n\treturn @sorted_result;\n}\n\n######################################################################################\n# Interesting functions for other modules:\n\n# This should be called from time to time to update the monitored data, \n# but not more often than once a second because of the resolution of time()\nsub update {\n\tmy $this = shift;\n\tlogger(\"NOTICE\", \"ProcInfo: Collecting backgound and \".keys(%{$this->{JOBS}}).\" PIDs monitoring info.\");\n\t$this->readStat();\n\t$this->readMemInfo();\n\t$this->readUptimeAndLoadAvg();\n\t$this->countProcesses();\n\t$this->readNetworkInfo();\n\t$this->readNetStat();\n\t$this->readEosDiskValues();\n        $this->readEosRpmValues();\n\t$this->{DATA}->{TIME} = time;\n\t$this->readGenericInfo() if (($this->{readGI}++) % 2 == 0);\n\t$this->readCPUInfo();\n\tfor my $pid (keys %{$this->{JOBS}}) {\n\t\t$this->readJobInfo($pid);\n\t\t$this->readJobDiskUsage($pid);\n\t}\n}\n\n# Call this to add another PID to be monitored\nsub addJobToMonitor {\n\tmy ($this, $pid, $workDir) = @_;\n\t\n\t$this->{JOBS}->{$pid}->{WORKDIR} = $workDir;\n\t$this->{JOBS}->{$pid}->{DATA} = {};\n}\n\n# Call this to stop monitoring a PID\nsub removeJobToMonitor {\n\tmy ($this, $pid) = @_;\n\t\n\tdelete $this->{JOBS}->{$pid};\n}\n\n# Return a filtered hash containting the system-related parameters and values\nsub getSystemData {\n\tmy ($this, $paramsRef, $prevDataRef) = @_;\n\n\tmy @ret = $this->getFilteredData($this->{DATA}, $paramsRef, $prevDataRef, $this->{NETWORKINTERFACES});\n\t\n\t#print Dumper(@ret);\n\t\n\treturn @ret;\n}\n\n# Return a filtered hash containing the job-related parameters and values\nsub getJobData {\n\tmy ($this, $pid, $paramsRef) = @_;\n\t\n\treturn $this->getFilteredData($this->{JOBS}->{$pid}->{DATA}, $paramsRef, $this->{NETWORKINTERFACES});\n}\n\n1;\n\n"
  },
  {
    "path": "ApMon/perl/ApMon/ApMon/XDRUtils.pm",
    "content": "package ApMon::XDRUtils;\n\nuse strict;\nuse warnings;\n\nmy $XDR_STRING = 0;\nmy $XDR_INT32 = 2;\nmy $XDR_REAL64 = 5;\n\nmy $MAX_INT = 1<<31;\n\n# Encode a set of parameters in the following format:\n# |clusterName | nodeName | time | #params |\n# | paramName | paramType | paramValue| x #params\n# and time, if != -1\nsub encodeParameters {\n\tmy ($clusterName, $nodeName, $time, @params) = @_;\n\n\tmy $encParams = \"\";\n\tfor(my $i = 0; $i < $#params; $i += 2){\n\t\t$encParams .= encodeParameter($params[$i], $params[$i+1]);\n\t}\n\tmy $encTime = $time == -1 ? \"\" : encodeINT32($time);\n\treturn encodeString($clusterName) . encodeString($nodeName) . \n\t       encodeINT32(@params/2) . $encParams . $encTime;\n}\n\n# Encode a parameter pair (paramName, paramValue)\nsub encodeParameter {\n\tmy ($name, $value) = @_;\n\t\n\tmy $type = getType($value);\n\tmy $encValue;\n\tif ($type == $XDR_INT32) {\n\t\t$encValue = encodeINT32($value);\n\t} elsif ($type == $XDR_REAL64) {\n\t\t$encValue = encodeREAL64($value);\n\t} else {\n\t\t$encValue = encodeString($value);\n\t}\n\treturn encodeString($name).encodeINT32($type).$encValue;\n}\n\n# Return the type for a given value (XDR_INT32, XDR_REAL64, XDR_STRING)\nsub getType {\n        $_ = shift;\n\t\n\treturn $XDR_INT32 if(/^[+-]?\\d+$/ && (abs($_) < $MAX_INT));\n\treturn $XDR_REAL64 if /^([+-]?)(?=\\d|\\.\\d)\\d*(\\.\\d*)?([Ee]([+-]?\\d+))?$/;\n\treturn $XDR_STRING;\n}\n\n# Encode a string in XDR format\nsub encodeString {\n\tmy $str = shift;\n\t\n\tmy $enc = encodeINT32(length($str));\n\twhile (length($str) % 4 != 0){\n\t\t$str .= \"\\0\";\n\t}\n\treturn $enc.$str;\n}\n\n# Encode a 32 bit signed integer in XDR format\nsub encodeINT32 {\n\tmy $val = shift;\n\t\n\treturn pack(\"N\", int($val));\n}\n\n# Encode a 64 bit double in XDR format\nsub encodeREAL64 {\n\tmy $val = shift;\n\t\n\tmy $end = verifyEndian();\n\tif ($end == 0) {\n\t\treturn reverse(pack(\"d\",$val));\n\t} else {\n\t\treturn pack(\"d\",$val);\n\t}\n}\n\n# Verify if machine is big-endian or little-endian\nsub verifyEndian {\n\tmy $foo = pack(\"s2\",1,2);\n\tif ($foo eq \"\\1\\0\\2\\0\" ) {\n\t\treturn 0;\n\t} elsif ( $foo eq \"\\0\\1\\0\\2\" ) {\n\t\treturn 1;\n\t}\n}\n\n1;\n\n"
  },
  {
    "path": "ApMon/perl/ApMon/ApMon.pm",
    "content": "=head1 NAME\n\nApMon - Perl extension for sending application information to MonALISA services.\n\n=head1 SYNOPSIS\n\n  use ApMon;\n  # initialize from a URL or from a file\n  my $apm = new ApMon::ApMon(\"http://some.host.com/destinations.conf\");\n  $apm->sendParameters(\"Cluster\", \"Node\", \"param1\", 14.23e-10, \"param2\", 234);\n\n  # initalize with default xApMon configuration, and send datagrams directly\n  # to the given host.\n  my $apm = ApMon::ApMon->new([\"pcardaab.cern.ch:8884\", \"localhost\"]);\n  $apm->sendParameters(\"Cluster\", \"Node\", {\"x\" => 12, \"y\" => 0.3});\n\n  # given xApMon configuration will overwrite the default values.\n  my $apm = ApMon::ApMon->new({\n\t\"pcardaab.cern.ch:8884\" => \n\t    {\"sys_monitoring\" => 0, \"job_monitoring\" => 1, \"general_info\" => 1}, \n\t\"lcfg.rogrid.pub.ro passwd\" => \n\t    {\"sys_monitoring\" => 1, \"general_info\" => 0}\n        });\n  $apm->sendParameters(\"Cluster\", \"Node\", [\"name\", \"some_name\", \"value\", 23]); \n\n=head1 DESCRIPTION\n\nApMon is an API that can be used by any application to send monitoring\ninformation to MonALISA services (http://monalisa.cacr.caltech.edu). The\nmonitoring data is sent as UDP datagrams to one or more hosts running MonALISA.\nThe MonALISA host may require a password enclosed in each datagram, for\nauthentication purposes. ApMon can also send datagrams that contain monitoring\ninformation regarding the system or the application.\n\n=head1 METHODS\n\n=over\n\n=cut\n\npackage ApMon;\n\nuse strict;\nuse warnings;\n\nuse ApMon::Common qw(logger);\nuse ApMon::ConfigLoader;\nuse ApMon::BgMonitor;\nuse IO::Handle;\nuse POSIX \":sys_wait_h\";\nuse Net::Domain;\nuse Data::Dumper;\n\n# Here it is kept a list of child processes that have to be killed before finishing.\nmy @children = (); \n\n# Temporary files path\nmy $TMPDIR = (defined $ENV{'TMPDIR'}) ? $ENV{'TMPDIR'} : '/tmp';\n\n=item $apm = new ApMon(@destLocations);\n\nThis is the constructor for the ApMon class. It can be used with several types of\narguments: a list of strings (URLs and/or files) - the configuration will be \nread from all; a reference to an ARRAY - each element is a destination ML \nservice; for each destination the default options will be used; a reference \nto a HASH - each key is a destination ML service; for each destination you can\ndefine a set of additional options that will overwrite the default ones. You\ncan also leave it empty and initialize ApMon later using the\n$apm->setDestinations() method. This will create the two background processes\n(for bg monitoring and configuration update). If you don't want these two\nprocesses to be created ever, you can pass the value 0 as single argument.\n\n=cut\n\nsub new {\n\tmy ($type, @destLocations) = @_;\n\t\n        my $this = {};\n\tbless $this;\n\t$this->{CONF_FILE} = \"$TMPDIR/confApMon.$$\"; # temporary name used to transfer config data from refreshConfig process to the others\n\t$this->{LAST_VALUES_FILE} = \"$TMPDIR/valuesApMon.$$\"; #temporary name used to transfer last monitored data from BgMonitor to the main process\n\t$this->{LAST_CONF_CHECK_TIME} = 0;\t# moment when config was checked last time in sec from Epoch\n\t$this->{CONF_RECHECK} = 1;\t\t# do check if configuration has changed\n\t$this->{CONF_CHECK_INTERVAL} = 20;\t# default interval to check for changes in config files\n\t$this->{DESTINATIONS} = {};\n\n\tmy $hostname = Net::Domain::hostfqdn(); \n\t$this->{DEFAULT_CLUSTER} = \"ApMon_UserSend\";\n\t$this->{DEFAULT_NODE} = $hostname;\n\n\t# decide if we will ever have bg processes\n\tif( @destLocations && ref($destLocations[0]) eq \"\" && $destLocations[0] eq \"0\" ){\n\t\t$this->{ALLOW_BG_PROCESSES} = 0;\n\t\t@destLocations = ();\n\t}else{\n\t\t$this->{ALLOW_BG_PROCESSES} = 1;\n\t}\n\n\tpipe($this->{UPD_RDR}, $this->{UPD_WTR}); # open a pipe to send messages to Config Loader\n\t$this->{UPD_WTR}->autoflush(1);\n\t$this->{CONFIG_LOADER} = new ApMon::ConfigLoader($this->{UPD_RDR}, $this->{CONF_FILE});\n\t\n\tpipe($this->{BG_RDR}, $this->{BG_WTR}); # open a pipe to send messages to Background Monitor\n\t$this->{BG_WTR}->autoflush(1);\n\t$this->{BG_MONITOR} = new ApMon::BgMonitor($this->{BG_RDR}, $this->{CONF_FILE}, $this->{LAST_VALUES_FILE}, $this->{ALLOW_BG_PROCESSES}, $this->{CONFIG_LOADER});\n\t\n\t# if the configuration is given in the constructor, load it now\n\t$this->setDestinations(@destLocations) if @destLocations;\n\t$SIG{INT} = \\&catch_zap;\n\t$SIG{TERM} = \\&catch_zap;\n\treturn $this;\n}\n\n=item $apm->setDestinations(@destLocations);\n\nAccept the same parameters as the ApMon constructor\n\n=cut\n\nsub setDestinations {\n\tmy ($this, @destLocations) = @_;\n\t\n\t$this->startBgProcesses();\n\t#logger(\"INFO\", \"\\$destLocations[0]= .$destLocations[0]. ref = .\".ref($destLocations[0]).\".\");\n\tif((ref($destLocations[0]) eq \"ARRAY\") or (ref($destLocations[0]) eq \"HASH\")) {\n\t\t# prevent background Config Loader to change this\n\t\t#logger(\"INFO\", \"Config is HASH or ARRAY\");\n\t\tApMon::Common::writeMessage($this->{UPD_WTR}, \"conf_recheck:0\\n\") if @children;\n\t}else{\n\t\t#logger(\"INFO\", \"Config is string = .@destLocations.\");\n\t\tmy $msg = \"conf_recheck:1\\n\";\n\t\tfor my $dest (@destLocations) {\n\t\t\t$msg .= \"dest:$dest\\n\";\n\t\t}\n\t\t# send this to background Config Loader for later updates\n\t\tApMon::Common::writeMessage($this->{UPD_WTR}, $msg) if @children; \n\t}\n\t# perform the change now, regardless of the existence of background Config Loader\n\t$this->{CONFIG_LOADER}->setDestinations(@destLocations);\n\t$this->enableBgMonitoring(1);\n}\n\n=item $apm->addJobToMonitor($pid, $workDir, $clusterName, $nodeName);\n\nAdd another job to be monitored. A job is a tree of processes, starting from \nthe given PID that has files in workDir directory. If workDir in \"\", no disk \nmeasurements will be performed. All produced parameters will be sent to all \ninterested destinations using the given cluster and node names.\n\n=cut\n\nsub addJobToMonitor {\n\tmy ($this, $pid, $workDir, $clusterName, $nodeName) = @_;\n\n\tApMon::Common::writeMessage($this->{BG_WTR}, \"pid:$pid\\nwork_dir:$workDir\\nbg_cluster:$clusterName\\nbg_node:$nodeName\\n\") if @children;\n\t# also set this to the local copy of the BG_MONITOR in case that user decides to stop background processes\n\t$this->{BG_MONITOR}->addJobToMonitor($pid, $workDir, $clusterName, $nodeName);\n}\n\n=item $apm->removeJobToMonitor($pid);\n\nRemove a tree of processes, starting with PID from being monitored.\n\n=cut\n\nsub removeJobToMonitor {\n\tmy ($this, $pid) = @_;\n\n\tApMon::Common::writeMessage($this->{BG_WTR}, \"rm_pid:$pid\\n\") if @children;\n\t# also set this to the local copy of the BG_MONITOR in case that user decides to stop background processes\n\t$this->{BG_MONITOR}->removeJobToMonitor($pid);\n}\n\n=item $apm->setMonitorClusterNode($clusterName, $nodeName);\n\nThis is used to set the cluster and node name for the system-related monitored\ndata.\n\n=cut\n\nsub setMonitorClusterNode {\n\tmy ($this, $clusterName, $nodeName) = @_;\n\n\tApMon::Common::writeMessage($this->{BG_WTR}, \"bg_cluster:$clusterName\\nbg_node:$nodeName\\n\") if @children;\n\t# also set this to the local copy of the BG_MONITOR in case that user decides to stop background processes\n\t$this->{BG_MONITOR}->setMonitorClusterNode($clusterName, $nodeName);\n}\n\n=item $apm->setConfRecheck($onOff [, $interval]);\n\nCall this function in order to enable or disable the configuration recheck.\nIf you enable it, you may want to pass a second parameter, that specifies the \nnumber of seconds between two configuration rechecks. Note that it makes sense\nto use configuration recheck only if you get the configuration from (a set of) \nfiles and/or URLs.\n\n=cut\n\nsub setConfRecheck {\n\tmy $this = shift;\n\tmy $onOff = shift;\n\tmy $interval = shift || 120;\n\n\t$this->{CONF_RECHECK} = $onOff;\n\t$this->{CONF_CHECK_INTERVAL} = $interval;\n\tApMon::Common::writeMessage($this->{UPD_WTR}, \"conf_recheck:$onOff\\nrecheck_interval:$interval\\n\") if @children;\n}\n\n=item $apm->sendParams(@params);\n\nUse this to send a set of parameters without specifying a cluster and a node \nname. In this case, the default values for cluster and node name will be used. \nSee the sendParameters function for more details.\n\n=cut\n\nsub sendParams {\n\tmy ($this, @params) = @_;\n\n\t$this->sendTimedParams(-1, @params);\n}\n\n=item $apm->sendParameters($clusterName, $nodeName, @params);\n\nUse this to send a set of parameters to all given destinations.\nThe default cluster an node names will be updated with the values given here.\nIf afterwards you want to send more parameters, you can use the shorter version\nof this function, sendParams. The parameters to be sent can be eiter a list, or\na reference to a list. This list should have an even length and should contain \npairs like (paramName, paramValue). paramValue can be a string, an int or a float.\n\n=cut\n\nsub sendParameters {\n\tmy ($this, $clusterName, $nodeName, @params) = @_;\n\n\t$this->sendTimedParameters($clusterName, $nodeName, -1, @params);\n}\n\n=item $apm->sendTimedParams($time, @params);\n\nThis is the short version of the sendTimedParameters that uses the default\ncluster and node name to sent the parameters and allows you to specify a time \n(in seconds from Epoch) for each packet.\n\n=cut\n\nsub sendTimedParams {\n\tmy ($this, $time, @params) = @_;\n\n\t$this->sendTimedParameters($this->{DEFAULT_CLUSTER}, $this->{DEFAULT_NODE}, $time, @params);\n}\n\n=item $apm->sendTimedParameters($clusterName, $nodeName, $time, @params);\n\nUse this instead of sendParameters to set the time for each packet that is sent.\nThe time is in seconds from Epoch. If you use the other function, the time for \nthese parameters will be sent by the MonALISA serice that receives them.\n\n=cut\n\nsub sendTimedParameters {\n\tmy ($this, $clusterName, $nodeName, $time, @params) = @_;\n\n\tApMon::Common::updateConfig($this);\n\tif((! defined($clusterName)) || (! defined($nodeName))){\n\t\tlogger(\"WARNING\", \"ClusterName or NodeName are undefined. Not sending given parameters!\");\n\t\treturn;\n\t}\n\t$this->{DEFAULT_CLUSTER} = $clusterName;\n\t$this->{DEFAULT_NODE} = $nodeName;\n\tif(scalar (keys %{$this->{DESTINATIONS}})){\n\t\tfor my $dest (keys %{$this->{DESTINATIONS}}){\n\t\t\tApMon::Common::directSendParameters($dest, $clusterName, $nodeName, $time, \\@params);\n\t\t}\n\t}else{\n\t\tlogger(\"WARNING\", \"No destinations defined for sending parameters\");\n\t}\n}\n\n=item $apm->sendBgMonitoring();\n\nSend NOW the background monitoring information to the interested destinations. \nNote that this uses the current process and not the background one. So, if you \nstop the background processes you can still use this call to send the \nbackground information (both about system and jobs) whenever you want. If $mustSend is != 0, \nthe bgMonitoring data is sent regardles of when it was last time sent. This allows\nsending a 'last result', just before the end of a job, and which can happen anytime.\n\n=cut\n\nsub sendBgMonitoring {\n\tmy $this = shift;\n\tmy $mustSend = shift || 0;\n\t$this->{BG_MONITOR}->sendBgMonitoring($mustSend);\n}\n\n=item $apm->getSysMonInfo('param_name1', 'param_name2', ...);\n\nIF and ONLY IF sendBgMonitoring() was called before, either called by user or by the BgMonitoring process,\nthe last system monitored values for the requested parameters will be returned. Note that the requested \nparameters must be among the monitored ones. If there is no avaialbe parameter among the requested ones, \nit returns undef.\n\n=cut\n\nsub getSysMonInfo {\n\tmy $this = shift;\n\t\n\t$this->update_last_values();\n\treturn $this->filter_params($this->{LAST_VALUES}->{BG_MON_VALUES}, @_);\n}\n\n=item $apm->getJobMonInfo($pid, 'param_name1', 'param_name2', ...);\n\nIF and ONLY IF sendBgMonitoring() was called before, either called by user or by the BgMonitoring process,\nthe last job monitored values for the given PID will be returned. Note that the requested parameters \nmust be among the monitored ones. If there is no avaialbe parameter among the requested ones, \nit returns undef.\n\n=cut\n\nsub getJobMonInfo {\n\tmy $this = shift;\n\tmy $pid = shift;\n\t\n\t$this->update_last_values();\n\treturn $this->filter_params($this->{LAST_VALUES}->{JOBS}->{$pid}->{BG_MON_VALUES}, @_);\n}\n\n=item $apm->enableBgMonitoring($onOff);\n\nThis allows enabling and disabling of the background monitoring. Note that this\ndoesn't stop the background monitor process; Note also that this is called by \ndefault by setDestinations () to enable the background monitoring once the \ndestination is set. It doesn't make sense to call this if you have stopped \nthe background processes.\n\n=cut\n\nsub enableBgMonitoring {\n\tmy ($this, $onOff) = @_;\n\n\tApMon::Common::writeMessage($this->{BG_WTR}, \"bg_enable:$onOff\\n\") if @children;\n}\n\n=item $apm->refreshConfig();\n\nCall this function to force refreshing the temporary config file and make sure\nthat at the next send, the new configuration will be used. Note that it makes \nsense to use this only if you load the configuration from (a set of) files \nand/or URLs. Also note that fetching the configuration files from an URL might\ntake some time, depending on network conditions.\n\n=cut\n\nsub refreshConfig {\n\tmy $this = shift;\n\t$this->{LAST_CONF_CHECK_TIME} = 0;\n\t$this->{CONFIG_LOADER}->refreshConfig();\n}\n\n=item $apm->startBgProcesses();\n\nThis can be called in order to start the background processes (conf loader \nand bg monitor). It is called by default if configuration is read from a\nfile or from a URL (not when you give a hash or an array for destinations).\n\n=cut\n\nsub startBgProcesses {\n\tmy $this = shift;\n\n\tif(! $this->{ALLOW_BG_PROCESSES}){\n\t\tlogger(\"DEBUG\", \"Not starting bg processes since they are not allowed.\");\n\t\treturn;\n\t}\n\n\tif(@children){\n\t\tlogger(\"INFO\", \"Bg processes already started!\");\n\t\treturn;\n\t}\n\tlogger(\"INFO\", \"starting bg processes\");\n\tmy $pid;\n\t# start the Config Loader process and retrieve the config periodically\n\t$pid = fork();\n\tif(! defined $pid){\n\t\tlogger(\"FATAL\", \"cannot fork: $!\"); die;\n\t}\n\tif ($pid == 0) {\n\t\t# child\n\t\t$this->{CONFIG_LOADER}->run();\n\t\texit(0);\n\t}\n\t# parent\n\tpush(@children, $pid);\n\tundef $pid;\n\t# start the Background Monitoring process\n\t$pid = fork();\n\tif(! defined $pid){\n\t\tlogger(\"FATAL\", \"cannot fork: $!\"); die;\n\t}\n\tif($pid == 0) {\n\t\t# child\n\t\t$this->{BG_MONITOR}->run();\n\t\texit(0);\n\t}\n\t# parent\n\tpush(@children, $pid);\n}\n\n=item $apm->stopBgProcesses();\n\nThis can be called to stop all child processes\n\n=cut\n\nsub stopBgProcesses {\n\tmy $this = shift;\n\tfor my $pid (@children) {\n\t\tkill 1, $pid;\n\t\twaitpid($pid, 0);\n\t}\n\t@children = ();\n}\n\n=item $apm->setLogLevel($level);\n\nThis sets the logging level for all ApMon components.\n$level can be one of: \"DEBUG\", \"NOTICE\", \"INFO\", \"WARNING\", \"ERROR\", \"FATAL\".\nYou can also set the log level from the configuration file by specifying\nxApMon_loglevel = one of the above (without quotes).\n\n=cut\n\nsub setLogLevel {\n\tmy ($this, $level) = @_;\n\tApMon::Common::setLogLevel($level);\n\t\n\tApMon::Common::writeMessage($this->{UPD_WTR}, \"loglevel:$level\\n\") if @children;\n\t$this->{CONFIG_LOADER}->setLogLevel($level);\n\t\n\tApMon::Common::writeMessage($this->{BG_WTR}, \"loglevel:$level\\n\") if @children;\n\t$this->{BG_MONITOR}->setLogLevel($level);\n}\n\n=item $apm->setMaxMsgRate($rate);\n\nThis sets the maxim number of messages that can be sent to a MonALISA service, per second.\nBy default, it is 50. This is a very large number, and the idea is to prevent errors from\nthe user. One can easily put in a for loop, without any sleep, some sendParams calls that\ncan generate a lot of unnecessary network load.\n\n=cut\n\nsub setMaxMsgRate {\n\tmy ($this, $rate) = @_;\n\n\tApMon::Common::setMaxMsgRate($rate);\n\t\n\tApMon::Common::writeMessage($this->{UPD_WTR}, \"maxMsgRate:$rate\\n\") if @children;\n\t$this->{CONFIG_LOADER}->setMaxMsgRate($rate);\n\n\tApMon::Common::writeMessage($this->{BG_WTR}, \"maxMsgRate:$rate\\n\") if @children;\n\t$this->{BG_MONITOR}->setMaxMsgRate($rate);\n}\n\n=item $apm->getCpuType();\n\nThis returns a hash with the cpu type: cpu_model_name, cpu_MHz, cpu_cache (in KB). This call\nis meant to be used together with setCpuSI2k, to establish a SpecInt performance meter.\nIf it cannot get the cpu type, it returns undef\n\n=cut\n\nsub getCpuType {\n\tmy $this = shift;\n\tmy $cpuType = ApMon::Common::getCpuType();\n\tApMon::Common::writeMessage($this->{BG_WTR}, \"cpu_mhz:$ApMon::Common::CpuMHz\\n\") if(@children && $ApMon::Common::CpuMHz);\n\treturn $cpuType;\n}\n\n=item $apm->setCpuSI2k(si2k);\n\nThis sets the SpecINT2000 meter for the current machine. Consequently, jobs will also report\ncpu_ksi2k, based on this value and cpu_time.\n\n=cut\n\nsub setCpuSI2k {\n\tmy ($this, $si2k) = @_;\n\tApMon::Common::setCpuSI2k($si2k);\n\tApMon::Common::writeMessage($this->{BG_WTR}, \"cpu_si2k:$si2k\\n\") if @children;\n}\n\n=item $apm->free();\n\nThis function stops the background processes and removes the temporary file. After this \ncall, the ApMon object must be recreated in order to be used. It is provided for exceptional \ncases when you have to recreate over and over again the ApMon object; you have to free it \nwhen you don't need anymore.\n\n=cut\n\nsub free {\n\tmy $this = shift;\n\t$this->stopBgProcesses();\n\t#close(ApMon::Common::SOCKET);\n\tunlink(\"$TMPDIR/confApMon.$$\");\n\tunlink(\"$TMPDIR/valuesApMon.$$\");\n}\n\n##################################################################################################\n# The following is internal stuff.\n\n# This is called if uses presses CTRL+C or kill is sent to me\nsub catch_zap {\n\tlogger(\"DEBUG\", \"Killed! Removing temp files $TMPDIR/{conf,values}ApMon.$$\") if defined &logger;\n\tunlink(\"$TMPDIR/confApMon.$$\");\n\tunlink(\"$TMPDIR/valuesApMon.$$\");\n\tstopBgProcesses(\"dummy\");\n\texit(0);\n}\n\n# from the given hash, based on the givn list of parameters, build a hash will all available\n# if the resulting list is empty, return undef.\nsub filter_params {\n\tmy $this = shift;\n\tmy $h_src = shift || {};\n\tmy $h_res = {};\n\tfor my $key (@_){\n\t\t$h_res->{$key} = $h_src->{$key} if defined($h_src->{$key});\n\t}\n\treturn (scalar(keys(%$h_res)) == 0 ? undef : $h_res);\n}\n\n# Update the last bg monitoring values hash with the contents of the LAST_VALUES_FILE.\n# Note that this is produced only after sendBgMonitoring was run, either from the main\n# process or the BgMonitor process.\nsub update_last_values {\n\tmy $this = shift;\n\t\n\tmy $now = time;\n\treturn if $this->{LAST_VALUES_TIME} && ($now - $this->{LAST_VALUES_TIME} < 2);\n\tif(open(F, \"<$this->{LAST_VALUES_FILE}\")){\n\t\tmy @lines = <F>;\n\t\tclose F;\n\t\tmy $VAR1;\n\t\t$this->{LAST_VALUES} = eval join(\"\", @lines);\n\t\tlogger(\"ERROR\", \"Error restoring the last bg monitoring values from file $this->{LAST_VALUES_FILE}:\\n$@\") if $@;\n\t\t$this->{LAST_VALUES_TIME} = $now;\n\t}else{\n\t\tlogger(\"WARNING\", \"Cannot read the last bg monitoring values from $this->{LAST_VALUES_FILE}\");\n\t}\n}\n\nEND {\n    unlink(\"$TMPDIR/confApMon.$$\");\n    unlink(\"$TMPDIR/valuesApMon.$$\");\n    stopBgProcesses(\"dummy\");\n}\n\n1;\n\n__END__\n\n=back\n\n=head1 AUTHOR\n\n  Catalin Cirstoiu <Catalin.Cirstoiu@cern.ch>\n\n=head1 COPYRIGHT AND LICENSE\n\nThis module is distributed on an \"AS IS\" basis, WITHOUT WARRANTY OF ANY KIND,\neither expressed or implied. This library is free software; you can\nredistribute or modify it under the same terms as Perl itself.\n\n=cut\n"
  },
  {
    "path": "ApMon/perl/ApMon/sendToML.sh",
    "content": "#!/bin/bash\n\n# Sample shell script that sends the given parameters to the ML service \n# running on the same machine using ApMon.\n#\n# 2007-04-03\n# Catalin.Cirstoiu@cern.ch\n\nif [ $# -lt 2 ] ; then\n\tcat <<EOM\nusage: send_params.sh cluster node [param1 value1] [param2 value2] .. [paramN valueN]\n\tcluster - cluster name as it will appear in the destination's farm configuraion\n\tnode - node name as it will appear in the destination's farm configuration\n\tparamX - parameter X's name\n\tvalueX - parameter X's value\nEOM\n\texit 1\nfi\n\n#First, concatenate all given parameters in a comma-sepparated list\nparams=\"\\\"$1\\\", \\\"$2\\\"\"\nshift; shift\nuntil [ -z \"$1\" -a -z \"$2\" ]\ndo\n  params=\"$params, \\\"$1\\\", $2\"\n  shift; shift\ndone\n\n#echo \"Params = [$params]\"\n\n#Then, choose a destination\n#destination=\"\\\"http://monalisa2.cern.ch/~catac/apmon/destinations.conf\\\"\"\n#destination=\"['pcardaab.cern.ch:8884']\"\n#destination=\"{'pcardaab.cern.ch' => {loglevel => 'NOTICE'}}\"\nMONALISA_HOST=${MONALISA_HOST:-\"localhost\"}\nAPMON_DEBUG_LEVEL=${APMON_DEBUG_LEVEL:-\"WARNING\"}\ndestination=${APMON_CONFIG:-\"['$MONALISA_HOST']\"}\n\n#Finally, run the perl interpreter with a small program that sends all these parameters\nexe=\"use strict;\nuse warnings;\nuse ApMon;\nmy \\$apm = new ApMon(0);\n\\$apm->setLogLevel('$APMON_DEBUG_LEVEL');\n\\$apm->setDestinations($destination);\n\\$apm->sendParameters($params);\"\n\n#echo \"Exe = [$exe]\"\n\nexport PERL5LIB=`dirname $0`\necho $exe | perl\n\n"
  },
  {
    "path": "ApMon/perl/ApMon/servMon.sh",
    "content": "#!/bin/bash\n\n# Sample shell script that provides host (and services) monitoring with ApMon.\n#\n# 2007-06-07\n# Catalin.Cirstoiu@cern.ch\n\nusage(){\n\tcat <<EOM\nusage: servMon.sh [[-f|-k] -p pidfile] hostGroup [serv1Name pid1] ...\n     -p pidfile   - put ApMon's PID in this file and run in background. If\n                    pidfile already contains a running process, just exit.\n\t-f        - kill previous background-running ApMon with the same pidfile\n\t            and will start a new one with the new parameters\n\t-k        - just kill previous background-running ApMon\n\thostGroup - base name for the host monitoring\n\tservXName - name of the X'th service\n\tpidX      - pid of the X'th service\nEOM\n\texit 1\n}\n\n# Get the machine's hostname\nhost=`hostname -f`\n\nif [ $# -eq 0 ] ; then\n  usage\nfi\n\nhostGroup=\nnewline=\"\n\"\nwhile [ $# -gt 0 ] ; do\n\tcase \"$1\" in\n\t\t-f)\n\t\t\tforce=1   # Set the force flag\n\t\t\t;;\n\t\t-k)\n\t\t\tforce=1\t  \n\t\t\tjustKill=1\n\t\t\t;;\n\t\t-p)\n\t\t\tpidfile=$2 # Set the pidfile\n\t\t\tshift\n\t\t\t;;\n\t\t-*)\n\t\t\techo -e \"Invalid parameter '$1'\\n\"\n\t\t\tusage\n\t\t\t;;\n\t\t*)\n\t\t\tif [ -z \"$hostGroup\" ] ; then\n\t\t\t\thostGroup=$1 # First bareword is the host group, for host monitoring\n\t\t\telse\n\t\t\t\tif [ -n \"$2\" ] ; then\n\t\t\t\t\tsrvMonCmds=\"${srvMonCmds}${newline}\\$apm->addJobToMonitor($2, '', '${1}_Services', '$host');\"\n\t\t\t\t\tshift\n\t\t\t\telse\n\t\t\t\t\techo -e \"Service '$1' needs pid number!\\n\"\n  \t\t\t\t\tusage\n\t\t\t\tfi\n\t\t\tfi\n\tesac\n\tshift\ndone\n\n# If pidfile was given, check if there is a running process with that pid\nif [ -n \"$pidfile\" ] ; then\n\tpid=`cat $pidfile 2>/dev/null`\n\tlines=`ps -p $pid 2>/dev/null | wc -l`\n\tif [ \"$lines\" -eq 2 ] ; then\n\t\t# there is a previous ApMon instance running\n\t\tif [ -n \"$force\" ] ; then\n\t\t\techo \"Killing previous ApMon instance with pid $pid ...\"\n\t\t\tkill -s 1 $pid 2>/dev/null ; sleep 1\n\t\t\tlines=`ps -p $pid 2>/dev/null | wc -l`\n\t\t\tif [ \"$lines\" -eq 2 ] ; then\n\t\t\t\techo \"Failed killing ApMon instance with pid $pid! Trying with -9...\"\n\t\t\t\tkill -s 9 $pid 2>/dev/null ; sleep 1\n\t\t\t\tlines=`ps -p $pid 2>/dev/null | wc -l`\n\t\t\t\tif [ \"$lines\" -eq 2 ] ; then\n\t\t\t\t\techo \"Failed killing -9 ApMon instance with pid $pid!!! Aborting.\"\n\t\t\t\t\texit -1\n\t\t\t\tfi\n\t\t\tfi\n\t\telse\n\t\t\t# force flag is not set; just exit\n\t\t\texit 1\n\t\tfi\n\tfi\nfi\n\nif [ -n \"$justKill\" ] ; then\n\texit 0;\nfi\n\n#Set the destination for the monitoring information\n#destination=\"\\\"http://monalisa2.cern.ch/~catac/apmon/destinations.conf\\\"\"\n#destination=\"['pcardaab.cern.ch:8884']\"\n#destination=\"{'pcardaab.cern.ch' => {loglevel => 'NOTICE'}}\"\nMONALISA_HOST=${MONALISA_HOST:-\"localhost\"}\nAPMON_DEBUG_LEVEL=${APMON_DEBUG_LEVEL:-\"WARNING\"}\ndestination=${APMON_CONFIG:-\"['$MONALISA_HOST']\"}\n\n#Finally, run the perl interpreter with a small program that sends all these parameters\nexe=\"use strict;\nuse warnings;\nuse ApMon;\nmy \\$apm = new ApMon(0);\n\\$apm->setLogLevel('$APMON_DEBUG_LEVEL');\n\\$apm->setDestinations($destination);\n\\$apm->setMonitorClusterNode('${hostGroup}_Nodes', '$host');$srvMonCmds\nwhile(1){\n  \\$apm->sendBgMonitoring();\n  sleep(120);\n}\n\"\n\n#echo \"Exe = [$exe]\"\n\nexport PERL5LIB=`dirname $0`\n\nif [ -n \"$pidfile\" ] ; then\n\t# pid file given; run in background\n\tlogfile=\"`dirname $pidfile`/`basename $pidfile .pid`.log\"\n\techo -e \"`date` Starting ApMon in background mode...\\nlogfile in: $logfile\\npidfile in: $pidfile\" | tee $logfile\n\tperl -e \"$exe\" </dev/null >> $logfile 2>&1 &\n\tpid=$!\n\techo $pid > $pidfile\nelse\n\t# pid file not given; run in interactive mode\n\techo -e \"`date` Starting ApMon in interactive mode...\"\n\texec perl -e \"$exe\"\nfi\n\n"
  },
  {
    "path": "ApMon/run.sh",
    "content": "#!/bin/sh\n\n[ -f /etc/sysconfig/eos ] && . /etc/sysconfig/eos\n[ -f /etc/sysconfig/eos_env ] && . /etc/sysconfig/eos_env\n\ncleanup() {\n    # kill all subprocesses\n    for pid in $(ps --ppid $$ --forest -o pid --no-headers); do\n        kill $pid &> /dev/null\n    done\n    exit 0\n}\n\ntrap cleanup SIGINT SIGTERM\n\nif [ -z \"${MONALISAHOST}\" ]; then\n    echo \"error: please configure the MONALISAHOST variable in /etc/sysconfig/eos first!\"\n    exit 1\nfi\n\neosuser=daemon\nxrdpid=$(pgrep -u \"${eosuser}\" xrootd | head -1)\n\nif [ -z \"${xrdpid}\" ]; then\n    xrdpid=999999\nfi\n\n\nexport PERL5LIB=$(perl -V:installsitearch | cut -d \"'\" -f 2)/ApMon\nrunuser -u ${eosuser} -- /opt/eos/apmon/eosapmond ${MONALISAHOST} /var/log/eos/apmon/apmon.log ${APMON_DEBUG_LEVEL:-\"WARNING\"} ${APMON_INSTANCE_NAME:-\"unconfigured\"} ${HOSTNAME} ${xrdpid} &\nwait\n"
  },
  {
    "path": "ApMon/usr/lib/systemd/system/eosapmond.service",
    "content": "# systemd service unit file for eosapmond\n# Author: Gianmaria Del Monte <gianmaria.del.monte@cern.ch>\n# Description: Systemd service to start apmon for monitoring xrootd on EOS\n\n[Unit]\nDescription=Starts apmon to monitor xrootd on EOS\nAfter=network.target\nStartLimitBurst=3\nStartLimitIntervalSec=5\n\n[Service]\nExecStart=/opt/eos/apmon/run.sh\nUser=root\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ncmake_minimum_required (VERSION 3.16...3.30 FATAL_ERROR)\n\n# Set default build type if not set. This must be done before calling project()\nif(NOT CMAKE_BUILD_TYPE AND NOT GENERATOR_IS_MULTI_CONFIG)\n  if(NOT CMAKE_C_FLAGS AND NOT CMAKE_CXX_FLAGS)\n    set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING\n      \"Build type: Debug Release RelWithDebInfo MinSizeRel None(use CMAKE_CXX_FLAGS)\")\n  endif()\nendif()\n\nproject(eos DESCRIPTION \"EOS Open Storage\" LANGUAGES C CXX ASM)\n\n# Insert cmake/ before everything else in the CMake module path\nlist(INSERT CMAKE_MODULE_PATH 0 \"${PROJECT_SOURCE_DIR}/cmake\")\n\noption(CCACHE \"Use ccache for compilation\" ON)\n\nif(CCACHE)\n  find_program(CCACHE_COMMAND ccache ccache-swig)\n  mark_as_advanced(CCACHE_COMMAND ${CCACHE_COMMAND})\n\n  if(EXISTS ${CCACHE_COMMAND})\n    message(VERBOSE \"Found ccache: ${CCACHE_COMMAND}\")\n    set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_COMMAND})\n  else()\n    message(VERBOSE \"Could NOT find ccache\")\n    set(CCACHE OFF CACHE BOOL \"Use ccache for compilation (disabled)\" FORCE)\n  endif()\nendif()\n\n#-------------------------------------------------------------------------------\n# Activate include-what-you-use\n#-------------------------------------------------------------------------------\noption(ENABLE_IWYU \"Enable include-what-you-use tool\" OFF)\n\nif(ENABLE_IWYU)\n  find_program(IWYU_PATH NAMES include-what-you-use iwyu)\n  if(NOT IWYU_PATH)\n    message(FATAL_ERROR \"Could not find include-what-you-use\")\n  endif()\n\n  set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${IWYU_PATH})\n  set(CMAKE_C_INCLUDE_WHAT_YOU_USE ${IWYU_PATH})\nendif()\n\n#-------------------------------------------------------------------------------\n# Include code coverage module\n#-------------------------------------------------------------------------------\noption(COVERAGE \"Build with test coverage reporting\" OFF)\n\nif (COVERAGE)\ninclude(EosCoverage)\nendif()\n\n#-------------------------------------------------------------------------------\n# Include generic functions and compiler definition parameters\n#-------------------------------------------------------------------------------\nif (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)\n  set(CMAKE_INSTALL_PREFIX \"/usr\" CACHE PATH \"Default install prefix: /usr\" FORCE)\nendif ()\n\ninclude(EosUtils)\nEOS_CheckOutOfSourceBuild()\nEOS_GetUidGid(\"daemon\" \"DAEMONUID\" \"DAEMONGID\")\nEOS_GetVersion(\"${VERSION_MAJOR}\" \"${VERSION_MINOR}\" \"${VERSION_PATCH}\" \"${RELEASE}\")\ninclude(EosOSDefaults)\n\nif (NOT PACKAGEONLY)\n  include(EosCompileFlags)\nendif()\n\nset(CMAKE_INSTALL_SYSCONFDIR /etc)\ninclude(EosFindLibs)\ninclude(CTest)\n\n#-------------------------------------------------------------------------------\n# Make gtest / gmock available for all downstream CMakeLists.txt that need it\n#-------------------------------------------------------------------------------\noption(USE_SYSTEM_GTEST \"Use GoogleTest installed in the system if found\" OFF)\n\nif(USE_SYSTEM_GTEST)\n  find_package(GTest REQUIRED)\nelse()\n  add_subdirectory(unit_tests/googletest EXCLUDE_FROM_ALL)\n\n  # Add alias libraries to emulate same behavior as external GoogleTest\n  add_library(GTest::GTest ALIAS gtest)\n  add_library(GTest::Main ALIAS gtest_main)\nendif()\n\n#-------------------------------------------------------------------------------\n# Generate documentation\n#-------------------------------------------------------------------------------\nif (Python3_Interpreter_FOUND AND SPHINX_FOUND)\n  add_custom_target(doc\n    COMMAND python3 generate_docs.py\n    WORKING_DIRECTORY \"${PROJECT_SOURCE_DIR}/doc\"\n    COMMENT \"Build HTML documentation with Sphinx ...\")\nendif ()\n\n#-------------------------------------------------------------------------------\n# Generate man pages\n#-------------------------------------------------------------------------------\nif (BUILD_MANPAGES AND HELP2MAN_FOUND)\n  add_subdirectory(man)\nendif()\n\n#-------------------------------------------------------------------------------\n# Build qclient static library\n#-------------------------------------------------------------------------------\ninclude_directories(${CMAKE_SOURCE_DIR})\nadd_subdirectory(common)\nadd_subdirectory(proto)\nadd_subdirectory(fst)\nadd_subdirectory(console)\nadd_subdirectory(fusex)\nadd_subdirectory(misc)\nadd_subdirectory(test)\nadd_subdirectory(namespace/ns_quarkdb/qclient)\n\nif (NOT CLIENT)\n  add_subdirectory(client)\n  add_subdirectory(mgm)\n  add_subdirectory(namespace)\n  add_subdirectory(utils)\n  add_subdirectory(archive)\n  add_subdirectory(auth_plugin)\n  add_subdirectory(unit_tests)\n  add_subdirectory(quarkdb)\nendif ()\n\n#-------------------------------------------------------------------------------\n# Uninstall target\n#-------------------------------------------------------------------------------\nconfigure_file(\n  \"${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in\"\n  \"${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake\"\n  IMMEDIATE @ONLY)\n\nadd_custom_target(\n   uninstall\n  \"${CMAKE_COMMAND}\" -P \"${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake\")\n\n#-------------------------------------------------------------------------------\n# Packaging\n#-------------------------------------------------------------------------------\nset(CPACK_SOURCE_GENERATOR \"TGZ\")\nset(CPACK_PACKAGE_NAME \"${CMAKE_PROJECT_NAME}\")\nset(CPACK_PACKAGE_VERSION \"${VERSION}\")\nset(CPACK_PACKAGE_VERSION_MAJOR \"${VERSION_MAJOR}\")\nset(CPACK_PACKAGE_VERSION_MINOR \"${VERSION_MINOR}\")\nset(CPACK_PACKAGE_VERSION_PATCH \"${VERSION_PATCH}\")\nset(CPACK_PACKAGE_RELEASE \"${RELEASE}\")\nset(CPACK_SOURCE_PACKAGE_FILE_NAME \"${CMAKE_PROJECT_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_PACKAGE_RELEASE}\")\nset(CPACK_SOURCE_IGNORE_FILES\n\"${CMAKE_CURRENT_BINARY_DIR};/ApMon/;/git/;/gitlab-ci/;/ccache/;/xrootd-dsi/;/nginx/;/dsi/;\\\n;/grpc/eos-grpc.spec;/.deps/;~$;'.'o$;/lib/;/.git/;eos.spec.in;elrepopackage.spec;.tar.gz$;\\\n.tar.bz2$;${CPACK_SOURCE_IGNORE_FILES};\")\n\nset(EOS_TUI_VERSION \"0.2.3\")\n\nconfigure_file(\n  \"${CMAKE_CURRENT_SOURCE_DIR}/cmake/config_spec.cmake.in\"\n  \"${CMAKE_CURRENT_BINARY_DIR}/cmake/config_spec.cmake\" @ONLY IMMEDIATE)\n\nadd_custom_command(\n  OUTPUT \"${CMAKE_CURRENT_SOURCE_DIR}/eos.spec\"\n  COMMAND ${CMAKE_COMMAND} -P \"${CMAKE_CURRENT_BINARY_DIR}/cmake/config_spec.cmake\"\n  DEPENDS \"${CMAKE_CURRENT_BINARY_DIR}/cmake/config_spec.cmake\"\n  \"${CMAKE_CURRENT_SOURCE_DIR}/eos.spec.in\")\n\nadd_custom_target(\n  dist\n  COMMAND ${CMAKE_MAKE_PROGRAM} package_source\n  DEPENDS \"${CMAKE_CURRENT_SOURCE_DIR}/eos.spec\")\n\nadd_custom_command(\n  TARGET dist POST_BUILD\n  COMMAND rm ARGS -rf \"${CMAKE_CURRENT_SOURCE_DIR}/eos.spec\"\n  COMMENT \"Clean generated spec file\")\n\ninclude(CPack)\n\n#-------------------------------------------------------------------------------\n# Source and binary rpms\n#-------------------------------------------------------------------------------\nset(EOS_ARCHIVE \"${CMAKE_PROJECT_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_PACKAGE_RELEASE}.tar.gz\")\nset(SRPM_DEFINE --define \"_source_filedigest_algorithm md5\" --define \"_binary_filedigest_algorithm md5\")\n\nif (CMAKE_CXX_COMPILER_ID MATCHES \"Clang\" OR CLANG_BUILD)\n  LIST(APPEND RPM_OPTIONS --with clang)\nendif()\n\nif (NOT CLIENT)\n  LIST(APPEND RPM_OPTIONS --with server)\n\n  if (EOS_XROOTD)\n    LIST(APPEND RPM_OPTIONS --with eos_xrootd_rh)\n  endif()\nendif()\n\nif (ASAN)\n  LIST(APPEND RPM_OPTIONS --with asan)\nendif()\n\nif (TSAN)\n  LIST(APPEND RPM_OPTIONS --with tsan)\nendif()\n\noption(NO_SSE \"Build without sse instruction set\" OFF)\n\nif (NO_SSE)\n  LIST(APPEND RPM_OPTIONS --with no_sse)\nendif()\n\noption(EOS_GRPC_GW \"Build without eos grpc support\" OFF)\n\nif (EOS_GRPC_GW)\n  LIST(APPEND RPM_OPTIONS --with eos_grpc_gateway)\nendif()\n\nadd_custom_target(\n  srpm\n  COMMAND rpmbuild -ts ${EOS_ARCHIVE} --define \"_topdir ${CMAKE_BINARY_DIR}\" ${SRPM_DEFINE} ${RPM_OPTIONS})\n\nadd_custom_target(\n  rpm\n  COMMAND rpmbuild -tb ${EOS_ARCHIVE} --define \"_topdir ${CMAKE_BINARY_DIR}\" ${RPM_OPTIONS})\n\nadd_dependencies(srpm dist)\nadd_dependencies(rpm dist)\ninclude(EosTui)\n\n#-------------------------------------------------------------------------------\n# Custom target to build on OSX\n#-------------------------------------------------------------------------------\nadd_custom_target(\n  osx\n  COMMAND sudo ../utils/eos-osx-package.sh ${CPACK_PACKAGE_VERSION})\n\n#-------------------------------------------------------------------------------\n# Custom target to build graphviz for all target\n#-------------------------------------------------------------------------------\ninclude(EosGraphviz)\n\n#-------------------------------------------------------------------------------\n# Print project summary\n#-------------------------------------------------------------------------------\ninclude(EosSummary)\n"
  },
  {
    "path": "CTestConfig.cmake",
    "content": "set(CTEST_PROJECT_NAME \"EOS\")\nset(CTEST_NIGHTLY_START_TIME \"00:00:00 UTC\")\nset(CTEST_DROP_SITE_CDASH TRUE)\nset(CTEST_SUBMIT_URL https://my.cdash.org/submit.php?project=EOS)\n"
  },
  {
    "path": "License",
    "content": "//                     GNU GENERAL PUBLIC LICENSE\n//                        Version 3, 29 June 2007\n// \n//  Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n//  Everyone is permitted to copy and distribute verbatim copies\n//  of this license document, but changing it is not allowed.\n// \n//                             Preamble\n// \n//   The GNU General Public License is a free, copyleft license for\n// software and other kinds of works.\n// \n//   The licenses for most software and other practical works are designed\n// to take away your freedom to share and change the works.  By contrast,\n// the GNU General Public License is intended to guarantee your freedom to\n// share and change all versions of a program--to make sure it remains free\n// software for all its users.  We, the Free Software Foundation, use the\n// GNU General Public License for most of our software; it applies also to\n// any other work released this way by its authors.  You can apply it to\n// your programs, too.\n// \n//   When we speak of free software, we are referring to freedom, not\n// price.  Our General Public Licenses are designed to make sure that you\n// have the freedom to distribute copies of free software (and charge for\n// them if you wish), that you receive source code or can get it if you\n// want it, that you can change the software or use pieces of it in new\n// free programs, and that you know you can do these things.\n// \n//   To protect your rights, we need to prevent others from denying you\n// these rights or asking you to surrender the rights.  Therefore, you have\n// certain responsibilities if you distribute copies of the software, or if\n// you modify it: responsibilities to respect the freedom of others.\n// \n//   For example, if you distribute copies of such a program, whether\n// gratis or for a fee, you must pass on to the recipients the same\n// freedoms that you received.  You must make sure that they, too, receive\n// or can get the source code.  And you must show them these terms so they\n// know their rights.\n// \n//   Developers that use the GNU GPL protect your rights with two steps:\n// (1) assert copyright on the software, and (2) offer you this License\n// giving you legal permission to copy, distribute and/or modify it.\n// \n//   For the developers' and authors' protection, the GPL clearly explains\n// that there is no warranty for this free software.  For both users' and\n// authors' sake, the GPL requires that modified versions be marked as\n// changed, so that their problems will not be attributed erroneously to\n// authors of previous versions.\n// \n//   Some devices are designed to deny users access to install or run\n// modified versions of the software inside them, although the manufacturer\n// can do so.  This is fundamentally incompatible with the aim of\n// protecting users' freedom to change the software.  The systematic\n// pattern of such abuse occurs in the area of products for individuals to\n// use, which is precisely where it is most unacceptable.  Therefore, we\n// have designed this version of the GPL to prohibit the practice for those\n// products.  If such problems arise substantially in other domains, we\n// stand ready to extend this provision to those domains in future versions\n// of the GPL, as needed to protect the freedom of users.\n// \n//   Finally, every program is threatened constantly by software patents.\n// States should not allow patents to restrict development and use of\n// software on general-purpose computers, but in those that do, we wish to\n// avoid the special danger that patents applied to a free program could\n// make it effectively proprietary.  To prevent this, the GPL assures that\n// patents cannot be used to render the program non-free.\n// \n//   The precise terms and conditions for copying, distribution and\n// modification follow.\n// \n//                        TERMS AND CONDITIONS\n// \n//   0. Definitions.\n// \n//   \"This License\" refers to version 3 of the GNU General Public License.\n// \n//   \"Copyright\" also means copyright-like laws that apply to other kinds of\n// works, such as semiconductor masks.\n// \n//   \"The Program\" refers to any copyrightable work licensed under this\n// License.  Each licensee is addressed as \"you\".  \"Licensees\" and\n// \"recipients\" may be individuals or organizations.\n// \n//   To \"modify\" a work means to copy from or adapt all or part of the work\n// in a fashion requiring copyright permission, other than the making of an\n// exact copy.  The resulting work is called a \"modified version\" of the\n// earlier work or a work \"based on\" the earlier work.\n// \n//   A \"covered work\" means either the unmodified Program or a work based\n// on the Program.\n// \n//   To \"propagate\" a work means to do anything with it that, without\n// permission, would make you directly or secondarily liable for\n// infringement under applicable copyright law, except executing it on a\n// computer or modifying a private copy.  Propagation includes copying,\n// distribution (with or without modification), making available to the\n// public, and in some countries other activities as well.\n// \n//   To \"convey\" a work means any kind of propagation that enables other\n// parties to make or receive copies.  Mere interaction with a user through\n// a computer network, with no transfer of a copy, is not conveying.\n// \n//   An interactive user interface displays \"Appropriate Legal Notices\"\n// to the extent that it includes a convenient and prominently visible\n// feature that (1) displays an appropriate copyright notice, and (2)\n// tells the user that there is no warranty for the work (except to the\n// extent that warranties are provided), that licensees may convey the\n// work under this License, and how to view a copy of this License.  If\n// the interface presents a list of user commands or options, such as a\n// menu, a prominent item in the list meets this criterion.\n// \n//   1. Source Code.\n// \n//   The \"source code\" for a work means the preferred form of the work\n// for making modifications to it.  \"Object code\" means any non-source\n// form of a work.\n// \n//   A \"Standard Interface\" means an interface that either is an official\n// standard defined by a recognized standards body, or, in the case of\n// interfaces specified for a particular programming language, one that\n// is widely used among developers working in that language.\n// \n//   The \"System Libraries\" of an executable work include anything, other\n// than the work as a whole, that (a) is included in the normal form of\n// packaging a Major Component, but which is not part of that Major\n// Component, and (b) serves only to enable use of the work with that\n// Major Component, or to implement a Standard Interface for which an\n// implementation is available to the public in source code form.  A\n// \"Major Component\", in this context, means a major essential component\n// (kernel, window system, and so on) of the specific operating system\n// (if any) on which the executable work runs, or a compiler used to\n// produce the work, or an object code interpreter used to run it.\n// \n//   The \"Corresponding Source\" for a work in object code form means all\n// the source code needed to generate, install, and (for an executable\n// work) run the object code and to modify the work, including scripts to\n// control those activities.  However, it does not include the work's\n// System Libraries, or general-purpose tools or generally available free\n// programs which are used unmodified in performing those activities but\n// which are not part of the work.  For example, Corresponding Source\n// includes interface definition files associated with source files for\n// the work, and the source code for shared libraries and dynamically\n// linked subprograms that the work is specifically designed to require,\n// such as by intimate data communication or control flow between those\n// subprograms and other parts of the work.\n// \n//   The Corresponding Source need not include anything that users\n// can regenerate automatically from other parts of the Corresponding\n// Source.\n// \n//   The Corresponding Source for a work in source code form is that\n// same work.\n// \n//   2. Basic Permissions.\n// \n//   All rights granted under this License are granted for the term of\n// copyright on the Program, and are irrevocable provided the stated\n// conditions are met.  This License explicitly affirms your unlimited\n// permission to run the unmodified Program.  The output from running a\n// covered work is covered by this License only if the output, given its\n// content, constitutes a covered work.  This License acknowledges your\n// rights of fair use or other equivalent, as provided by copyright law.\n// \n//   You may make, run and propagate covered works that you do not\n// convey, without conditions so long as your license otherwise remains\n// in force.  You may convey covered works to others for the sole purpose\n// of having them make modifications exclusively for you, or provide you\n// with facilities for running those works, provided that you comply with\n// the terms of this License in conveying all material for which you do\n// not control copyright.  Those thus making or running the covered works\n// for you must do so exclusively on your behalf, under your direction\n// and control, on terms that prohibit them from making any copies of\n// your copyrighted material outside their relationship with you.\n// \n//   Conveying under any other circumstances is permitted solely under\n// the conditions stated below.  Sublicensing is not allowed; section 10\n// makes it unnecessary.\n// \n//   3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n// \n//   No covered work shall be deemed part of an effective technological\n// measure under any applicable law fulfilling obligations under article\n// 11 of the WIPO copyright treaty adopted on 20 December 1996, or\n// similar laws prohibiting or restricting circumvention of such\n// measures.\n// \n//   When you convey a covered work, you waive any legal power to forbid\n// circumvention of technological measures to the extent such circumvention\n// is effected by exercising rights under this License with respect to\n// the covered work, and you disclaim any intention to limit operation or\n// modification of the work as a means of enforcing, against the work's\n// users, your or third parties' legal rights to forbid circumvention of\n// technological measures.\n// \n//   4. Conveying Verbatim Copies.\n// \n//   You may convey verbatim copies of the Program's source code as you\n// receive it, in any medium, provided that you conspicuously and\n// appropriately publish on each copy an appropriate copyright notice;\n// keep intact all notices stating that this License and any\n// non-permissive terms added in accord with section 7 apply to the code;\n// keep intact all notices of the absence of any warranty; and give all\n// recipients a copy of this License along with the Program.\n// \n//   You may charge any price or no price for each copy that you convey,\n// and you may offer support or warranty protection for a fee.\n// \n//   5. Conveying Modified Source Versions.\n// \n//   You may convey a work based on the Program, or the modifications to\n// produce it from the Program, in the form of source code under the\n// terms of section 4, provided that you also meet all of these conditions:\n// \n//     a) The work must carry prominent notices stating that you modified\n//     it, and giving a relevant date.\n// \n//     b) The work must carry prominent notices stating that it is\n//     released under this License and any conditions added under section\n//     7.  This requirement modifies the requirement in section 4 to\n//     \"keep intact all notices\".\n// \n//     c) You must license the entire work, as a whole, under this\n//     License to anyone who comes into possession of a copy.  This\n//     License will therefore apply, along with any applicable section 7\n//     additional terms, to the whole of the work, and all its parts,\n//     regardless of how they are packaged.  This License gives no\n//     permission to license the work in any other way, but it does not\n//     invalidate such permission if you have separately received it.\n// \n//     d) If the work has interactive user interfaces, each must display\n//     Appropriate Legal Notices; however, if the Program has interactive\n//     interfaces that do not display Appropriate Legal Notices, your\n//     work need not make them do so.\n// \n//   A compilation of a covered work with other separate and independent\n// works, which are not by their nature extensions of the covered work,\n// and which are not combined with it such as to form a larger program,\n// in or on a volume of a storage or distribution medium, is called an\n// \"aggregate\" if the compilation and its resulting copyright are not\n// used to limit the access or legal rights of the compilation's users\n// beyond what the individual works permit.  Inclusion of a covered work\n// in an aggregate does not cause this License to apply to the other\n// parts of the aggregate.\n// \n//   6. Conveying Non-Source Forms.\n// \n//   You may convey a covered work in object code form under the terms\n// of sections 4 and 5, provided that you also convey the\n// machine-readable Corresponding Source under the terms of this License,\n// in one of these ways:\n// \n//     a) Convey the object code in, or embodied in, a physical product\n//     (including a physical distribution medium), accompanied by the\n//     Corresponding Source fixed on a durable physical medium\n//     customarily used for software interchange.\n// \n//     b) Convey the object code in, or embodied in, a physical product\n//     (including a physical distribution medium), accompanied by a\n//     written offer, valid for at least three years and valid for as\n//     long as you offer spare parts or customer support for that product\n//     model, to give anyone who possesses the object code either (1) a\n//     copy of the Corresponding Source for all the software in the\n//     product that is covered by this License, on a durable physical\n//     medium customarily used for software interchange, for a price no\n//     more than your reasonable cost of physically performing this\n//     conveying of source, or (2) access to copy the\n//     Corresponding Source from a network server at no charge.\n// \n//     c) Convey individual copies of the object code with a copy of the\n//     written offer to provide the Corresponding Source.  This\n//     alternative is allowed only occasionally and noncommercially, and\n//     only if you received the object code with such an offer, in accord\n//     with subsection 6b.\n// \n//     d) Convey the object code by offering access from a designated\n//     place (gratis or for a charge), and offer equivalent access to the\n//     Corresponding Source in the same way through the same place at no\n//     further charge.  You need not require recipients to copy the\n//     Corresponding Source along with the object code.  If the place to\n//     copy the object code is a network server, the Corresponding Source\n//     may be on a different server (operated by you or a third party)\n//     that supports equivalent copying facilities, provided you maintain\n//     clear directions next to the object code saying where to find the\n//     Corresponding Source.  Regardless of what server hosts the\n//     Corresponding Source, you remain obligated to ensure that it is\n//     available for as long as needed to satisfy these requirements.\n// \n//     e) Convey the object code using peer-to-peer transmission, provided\n//     you inform other peers where the object code and Corresponding\n//     Source of the work are being offered to the general public at no\n//     charge under subsection 6d.\n// \n//   A separable portion of the object code, whose source code is excluded\n// from the Corresponding Source as a System Library, need not be\n// included in conveying the object code work.\n// \n//   A \"User Product\" is either (1) a \"consumer product\", which means any\n// tangible personal property which is normally used for personal, family,\n// or household purposes, or (2) anything designed or sold for incorporation\n// into a dwelling.  In determining whether a product is a consumer product,\n// doubtful cases shall be resolved in favor of coverage.  For a particular\n// product received by a particular user, \"normally used\" refers to a\n// typical or common use of that class of product, regardless of the status\n// of the particular user or of the way in which the particular user\n// actually uses, or expects or is expected to use, the product.  A product\n// is a consumer product regardless of whether the product has substantial\n// commercial, industrial or non-consumer uses, unless such uses represent\n// the only significant mode of use of the product.\n// \n//   \"Installation Information\" for a User Product means any methods,\n// procedures, authorization keys, or other information required to install\n// and execute modified versions of a covered work in that User Product from\n// a modified version of its Corresponding Source.  The information must\n// suffice to ensure that the continued functioning of the modified object\n// code is in no case prevented or interfered with solely because\n// modification has been made.\n// \n//   If you convey an object code work under this section in, or with, or\n// specifically for use in, a User Product, and the conveying occurs as\n// part of a transaction in which the right of possession and use of the\n// User Product is transferred to the recipient in perpetuity or for a\n// fixed term (regardless of how the transaction is characterized), the\n// Corresponding Source conveyed under this section must be accompanied\n// by the Installation Information.  But this requirement does not apply\n// if neither you nor any third party retains the ability to install\n// modified object code on the User Product (for example, the work has\n// been installed in ROM).\n// \n//   The requirement to provide Installation Information does not include a\n// requirement to continue to provide support service, warranty, or updates\n// for a work that has been modified or installed by the recipient, or for\n// the User Product in which it has been modified or installed.  Access to a\n// network may be denied when the modification itself materially and\n// adversely affects the operation of the network or violates the rules and\n// protocols for communication across the network.\n// \n//   Corresponding Source conveyed, and Installation Information provided,\n// in accord with this section must be in a format that is publicly\n// documented (and with an implementation available to the public in\n// source code form), and must require no special password or key for\n// unpacking, reading or copying.\n// \n//   7. Additional Terms.\n// \n//   \"Additional permissions\" are terms that supplement the terms of this\n// License by making exceptions from one or more of its conditions.\n// Additional permissions that are applicable to the entire Program shall\n// be treated as though they were included in this License, to the extent\n// that they are valid under applicable law.  If additional permissions\n// apply only to part of the Program, that part may be used separately\n// under those permissions, but the entire Program remains governed by\n// this License without regard to the additional permissions.\n// \n//   When you convey a copy of a covered work, you may at your option\n// remove any additional permissions from that copy, or from any part of\n// it.  (Additional permissions may be written to require their own\n// removal in certain cases when you modify the work.)  You may place\n// additional permissions on material, added by you to a covered work,\n// for which you have or can give appropriate copyright permission.\n// \n//   Notwithstanding any other provision of this License, for material you\n// add to a covered work, you may (if authorized by the copyright holders of\n// that material) supplement the terms of this License with terms:\n// \n//     a) Disclaiming warranty or limiting liability differently from the\n//     terms of sections 15 and 16 of this License; or\n// \n//     b) Requiring preservation of specified reasonable legal notices or\n//     author attributions in that material or in the Appropriate Legal\n//     Notices displayed by works containing it; or\n// \n//     c) Prohibiting misrepresentation of the origin of that material, or\n//     requiring that modified versions of such material be marked in\n//     reasonable ways as different from the original version; or\n// \n//     d) Limiting the use for publicity purposes of names of licensors or\n//     authors of the material; or\n// \n//     e) Declining to grant rights under trademark law for use of some\n//     trade names, trademarks, or service marks; or\n// \n//     f) Requiring indemnification of licensors and authors of that\n//     material by anyone who conveys the material (or modified versions of\n//     it) with contractual assumptions of liability to the recipient, for\n//     any liability that these contractual assumptions directly impose on\n//     those licensors and authors.\n// \n//   All other non-permissive additional terms are considered \"further\n// restrictions\" within the meaning of section 10.  If the Program as you\n// received it, or any part of it, contains a notice stating that it is\n// governed by this License along with a term that is a further\n// restriction, you may remove that term.  If a license document contains\n// a further restriction but permits relicensing or conveying under this\n// License, you may add to a covered work material governed by the terms\n// of that license document, provided that the further restriction does\n// not survive such relicensing or conveying.\n// \n//   If you add terms to a covered work in accord with this section, you\n// must place, in the relevant source files, a statement of the\n// additional terms that apply to those files, or a notice indicating\n// where to find the applicable terms.\n// \n//   Additional terms, permissive or non-permissive, may be stated in the\n// form of a separately written license, or stated as exceptions;\n// the above requirements apply either way.\n// \n//   8. Termination.\n// \n//   You may not propagate or modify a covered work except as expressly\n// provided under this License.  Any attempt otherwise to propagate or\n// modify it is void, and will automatically terminate your rights under\n// this License (including any patent licenses granted under the third\n// paragraph of section 11).\n// \n//   However, if you cease all violation of this License, then your\n// license from a particular copyright holder is reinstated (a)\n// provisionally, unless and until the copyright holder explicitly and\n// finally terminates your license, and (b) permanently, if the copyright\n// holder fails to notify you of the violation by some reasonable means\n// prior to 60 days after the cessation.\n// \n//   Moreover, your license from a particular copyright holder is\n// reinstated permanently if the copyright holder notifies you of the\n// violation by some reasonable means, this is the first time you have\n// received notice of violation of this License (for any work) from that\n// copyright holder, and you cure the violation prior to 30 days after\n// your receipt of the notice.\n// \n//   Termination of your rights under this section does not terminate the\n// licenses of parties who have received copies or rights from you under\n// this License.  If your rights have been terminated and not permanently\n// reinstated, you do not qualify to receive new licenses for the same\n// material under section 10.\n// \n//   9. Acceptance Not Required for Having Copies.\n// \n//   You are not required to accept this License in order to receive or\n// run a copy of the Program.  Ancillary propagation of a covered work\n// occurring solely as a consequence of using peer-to-peer transmission\n// to receive a copy likewise does not require acceptance.  However,\n// nothing other than this License grants you permission to propagate or\n// modify any covered work.  These actions infringe copyright if you do\n// not accept this License.  Therefore, by modifying or propagating a\n// covered work, you indicate your acceptance of this License to do so.\n// \n//   10. Automatic Licensing of Downstream Recipients.\n// \n//   Each time you convey a covered work, the recipient automatically\n// receives a license from the original licensors, to run, modify and\n// propagate that work, subject to this License.  You are not responsible\n// for enforcing compliance by third parties with this License.\n// \n//   An \"entity transaction\" is a transaction transferring control of an\n// organization, or substantially all assets of one, or subdividing an\n// organization, or merging organizations.  If propagation of a covered\n// work results from an entity transaction, each party to that\n// transaction who receives a copy of the work also receives whatever\n// licenses to the work the party's predecessor in interest had or could\n// give under the previous paragraph, plus a right to possession of the\n// Corresponding Source of the work from the predecessor in interest, if\n// the predecessor has it or can get it with reasonable efforts.\n// \n//   You may not impose any further restrictions on the exercise of the\n// rights granted or affirmed under this License.  For example, you may\n// not impose a license fee, royalty, or other charge for exercise of\n// rights granted under this License, and you may not initiate litigation\n// (including a cross-claim or counterclaim in a lawsuit) alleging that\n// any patent claim is infringed by making, using, selling, offering for\n// sale, or importing the Program or any portion of it.\n// \n//   11. Patents.\n// \n//   A \"contributor\" is a copyright holder who authorizes use under this\n// License of the Program or a work on which the Program is based.  The\n// work thus licensed is called the contributor's \"contributor version\".\n// \n//   A contributor's \"essential patent claims\" are all patent claims\n// owned or controlled by the contributor, whether already acquired or\n// hereafter acquired, that would be infringed by some manner, permitted\n// by this License, of making, using, or selling its contributor version,\n// but do not include claims that would be infringed only as a\n// consequence of further modification of the contributor version.  For\n// purposes of this definition, \"control\" includes the right to grant\n// patent sublicenses in a manner consistent with the requirements of\n// this License.\n// \n//   Each contributor grants you a non-exclusive, worldwide, royalty-free\n// patent license under the contributor's essential patent claims, to\n// make, use, sell, offer for sale, import and otherwise run, modify and\n// propagate the contents of its contributor version.\n// \n//   In the following three paragraphs, a \"patent license\" is any express\n// agreement or commitment, however denominated, not to enforce a patent\n// (such as an express permission to practice a patent or covenant not to\n// sue for patent infringement).  To \"grant\" such a patent license to a\n// party means to make such an agreement or commitment not to enforce a\n// patent against the party.\n// \n//   If you convey a covered work, knowingly relying on a patent license,\n// and the Corresponding Source of the work is not available for anyone\n// to copy, free of charge and under the terms of this License, through a\n// publicly available network server or other readily accessible means,\n// then you must either (1) cause the Corresponding Source to be so\n// available, or (2) arrange to deprive yourself of the benefit of the\n// patent license for this particular work, or (3) arrange, in a manner\n// consistent with the requirements of this License, to extend the patent\n// license to downstream recipients.  \"Knowingly relying\" means you have\n// actual knowledge that, but for the patent license, your conveying the\n// covered work in a country, or your recipient's use of the covered work\n// in a country, would infringe one or more identifiable patents in that\n// country that you have reason to believe are valid.\n// \n//   If, pursuant to or in connection with a single transaction or\n// arrangement, you convey, or propagate by procuring conveyance of, a\n// covered work, and grant a patent license to some of the parties\n// receiving the covered work authorizing them to use, propagate, modify\n// or convey a specific copy of the covered work, then the patent license\n// you grant is automatically extended to all recipients of the covered\n// work and works based on it.\n// \n//   A patent license is \"discriminatory\" if it does not include within\n// the scope of its coverage, prohibits the exercise of, or is\n// conditioned on the non-exercise of one or more of the rights that are\n// specifically granted under this License.  You may not convey a covered\n// work if you are a party to an arrangement with a third party that is\n// in the business of distributing software, under which you make payment\n// to the third party based on the extent of your activity of conveying\n// the work, and under which the third party grants, to any of the\n// parties who would receive the covered work from you, a discriminatory\n// patent license (a) in connection with copies of the covered work\n// conveyed by you (or copies made from those copies), or (b) primarily\n// for and in connection with specific products or compilations that\n// contain the covered work, unless you entered into that arrangement,\n// or that patent license was granted, prior to 28 March 2007.\n// \n//   Nothing in this License shall be construed as excluding or limiting\n// any implied license or other defenses to infringement that may\n// otherwise be available to you under applicable patent law.\n// \n//   12. No Surrender of Others' Freedom.\n// \n//   If conditions are imposed on you (whether by court order, agreement or\n// otherwise) that contradict the conditions of this License, they do not\n// excuse you from the conditions of this License.  If you cannot convey a\n// covered work so as to satisfy simultaneously your obligations under this\n// License and any other pertinent obligations, then as a consequence you may\n// not convey it at all.  For example, if you agree to terms that obligate you\n// to collect a royalty for further conveying from those to whom you convey\n// the Program, the only way you could satisfy both those terms and this\n// License would be to refrain entirely from conveying the Program.\n// \n//   13. Use with the GNU Affero General Public License.\n// \n//   Notwithstanding any other provision of this License, you have\n// permission to link or combine any covered work with a work licensed\n// under version 3 of the GNU Affero General Public License into a single\n// combined work, and to convey the resulting work.  The terms of this\n// License will continue to apply to the part which is the covered work,\n// but the special requirements of the GNU Affero General Public License,\n// section 13, concerning interaction through a network will apply to the\n// combination as such.\n// \n//   14. Revised Versions of this License.\n// \n//   The Free Software Foundation may publish revised and/or new versions of\n// the GNU General Public License from time to time.  Such new versions will\n// be similar in spirit to the present version, but may differ in detail to\n// address new problems or concerns.\n// \n//   Each version is given a distinguishing version number.  If the\n// Program specifies that a certain numbered version of the GNU General\n// Public License \"or any later version\" applies to it, you have the\n// option of following the terms and conditions either of that numbered\n// version or of any later version published by the Free Software\n// Foundation.  If the Program does not specify a version number of the\n// GNU General Public License, you may choose any version ever published\n// by the Free Software Foundation.\n// \n//   If the Program specifies that a proxy can decide which future\n// versions of the GNU General Public License can be used, that proxy's\n// public statement of acceptance of a version permanently authorizes you\n// to choose that version for the Program.\n// \n//   Later license versions may give you additional or different\n// permissions.  However, no additional obligations are imposed on any\n// author or copyright holder as a result of your choosing to follow a\n// later version.\n// \n//   15. Disclaimer of Warranty.\n// \n//   THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\n// APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\n// HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\n// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\n// IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\n// ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n// \n//   16. Limitation of Liability.\n// \n//   IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\n// WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\n// THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\n// GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\n// USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\n// DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\n// PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\n// EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\n// SUCH DAMAGES.\n// \n//   17. Interpretation of Sections 15 and 16.\n// \n//   If the disclaimer of warranty and limitation of liability provided\n// above cannot be given local legal effect according to their terms,\n// reviewing courts shall apply local law that most closely approximates\n// an absolute waiver of all civil liability in connection with the\n// Program, unless a warranty or assumption of liability accompanies a\n// copy of the Program in return for a fee.\n// \n//                      END OF TERMS AND CONDITIONS\n\nconst char* license=\" /************************************************************************************\\n \\\n* EOS - the CERN Disk Storage System                                                *\\n \\\n* Copyright (C) 2011 CERN/Switzerland                                               *\\n \\\n*                                                                                   *\\n \\\n* This program is free software: you can redistribute it and/or modify              *\\n \\\n* it under the terms of the GNU General Public License as published by              *\\n \\\n* the Free Software Foundation, either version 3 of the License, or                 *\\n \\\n* (at your option) any later version.                                               *\\n \\\n*                                                                                   *\\n \\\n* This program is distributed in the hope that it will be useful,                   *\\n \\\n* but WITHOUT ANY WARRANTY; without even the implied warranty of                    *\\n \\\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                     *\\n \\\n* GNU General Public License for more details.                                      *\\n \\\n*                                                                                   *\\n \\\n* EOS is based on the XRootD software:                                              *\\n \\\n* ----------------------------------------------------------------------------------*\\n \\\n* Copyright (C) 2005-2010, Board of Trustees of the Leland Stanford, Jr. University.*\\n \\\n* Produced under contract DE-AC02-76-SF00515 with the US Department of Energy.      *\\n \\\n* All rights reserved.                                                              *\\n \\\n* See <http://xrootd.org> for more details.                                         *\\n \\\n*                                                                                   *\\n \\\n* EOS uses crc32c checksum alogrithms from MIT/Intel:                               *\\n \\\n* ----------------------------------------------------------------------------------*\\n \\\n* Copyright 2008,2009,2010 Massachusetts Institute of Technology.                   *\\n \\\n* Implementations adapted from Intel's Slicing By 8 Sourceforge Project             *\\n \\\n* http://sourceforge.net/projects/slicing-by-8/                                     *\\n \\\n* Copyright (c) 2004-2006 Intel Corporation                                         *\\n \\\n************************************************************************************/\\n \\\n\\n \\\n                    GNU GENERAL PUBLIC LICENSE\\n \\\n                       Version 3, 29 June 2007\\n \\\n\\n \\\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\\n \\\n Everyone is permitted to copy and distribute verbatim copies\\n \\\n of this license document, but changing it is not allowed.\\n \\\n\\n \\\n                            Preamble\\n \\\n\\n \\\n  The GNU General Public License is a free, copyleft license for\\n \\\nsoftware and other kinds of works.\\n \\\n\\n \\\n  The licenses for most software and other practical works are designed\\n \\\nto take away your freedom to share and change the works.  By contrast,\\n \\\nthe GNU General Public License is intended to guarantee your freedom to\\n \\\nshare and change all versions of a program--to make sure it remains free\\n \\\nsoftware for all its users.  We, the Free Software Foundation, use the\\n \\\nGNU General Public License for most of our software; it applies also to\\n \\\nany other work released this way by its authors.  You can apply it to\\n \\\nyour programs, too.\\n \\\n\\n \\\n  When we speak of free software, we are referring to freedom, not\\n \\\nprice.  Our General Public Licenses are designed to make sure that you\\n \\\nhave the freedom to distribute copies of free software (and charge for\\n \\\nthem if you wish), that you receive source code or can get it if you\\n \\\nwant it, that you can change the software or use pieces of it in new\\n \\\nfree programs, and that you know you can do these things.\\n \\\n\\n \\\n  To protect your rights, we need to prevent others from denying you\\n \\\nthese rights or asking you to surrender the rights.  Therefore, you have\\n \\\ncertain responsibilities if you distribute copies of the software, or if\\n \\\nyou modify it: responsibilities to respect the freedom of others.\\n \\\n\\n \\\n  For example, if you distribute copies of such a program, whether\\n \\\ngratis or for a fee, you must pass on to the recipients the same\\n \\\nfreedoms that you received.  You must make sure that they, too, receive\\n \\\nor can get the source code.  And you must show them these terms so they\\n \\\nknow their rights.\\n \\\n\\n \\\n  Developers that use the GNU GPL protect your rights with two steps:\\n \\\n(1) assert copyright on the software, and (2) offer you this License\\n \\\ngiving you legal permission to copy, distribute and/or modify it.\\n \\\n\\n \\\n  For the developers' and authors' protection, the GPL clearly explains\\n \\\nthat there is no warranty for this free software.  For both users' and\\n \\\nauthors' sake, the GPL requires that modified versions be marked as\\n \\\nchanged, so that their problems will not be attributed erroneously to\\n \\\nauthors of previous versions.\\n \\\n\\n \\\n  Some devices are designed to deny users access to install or run\\n \\\nmodified versions of the software inside them, although the manufacturer\\n \\\ncan do so.  This is fundamentally incompatible with the aim of\\n \\\nprotecting users' freedom to change the software.  The systematic\\n \\\npattern of such abuse occurs in the area of products for individuals to\\n \\\nuse, which is precisely where it is most unacceptable.  Therefore, we\\n \\\nhave designed this version of the GPL to prohibit the practice for those\\n \\\nproducts.  If such problems arise substantially in other domains, we\\n \\\nstand ready to extend this provision to those domains in future versions\\n \\\nof the GPL, as needed to protect the freedom of users.\\n \\\n\\n \\\n  Finally, every program is threatened constantly by software patents.\\n \\\nStates should not allow patents to restrict development and use of\\n \\\nsoftware on general-purpose computers, but in those that do, we wish to\\n \\\navoid the special danger that patents applied to a free program could\\n \\\nmake it effectively proprietary.  To prevent this, the GPL assures that\\n \\\npatents cannot be used to render the program non-free.\\n \\\n\\n \\\n  The precise terms and conditions for copying, distribution and\\n \\\nmodification follow.\\n \\\n\\n \\\n                       TERMS AND CONDITIONS\\n \\\n\\n \\\n  0. Definitions.\\n \\\n\\n \\\n  \\\"This License\\\" refers to version 3 of the GNU General Public License.\\n \\\n\\n \\\n  \\\"Copyright\\\" also means copyright-like laws that apply to other kinds of\\n \\\nworks, such as semiconductor masks.\\n \\\n\\n \\\n  \\\"The Program\\\" refers to any copyrightable work licensed under this\\n \\\nLicense.  Each licensee is addressed as \\\"you\\\".  \\\"Licensees\\\" and\\n \\\n\\\"recipients\\\" may be individuals or organizations.\\n \\\n\\n \\\n  To \\\"modify\\\" a work means to copy from or adapt all or part of the work\\n \\\nin a fashion requiring copyright permission, other than the making of an\\n \\\nexact copy.  The resulting work is called a \\\"modified version\\\" of the\\n \\\nearlier work or a work \\\"based on\\\" the earlier work.\\n \\\n\\n \\\n  A \\\"covered work\\\" means either the unmodified Program or a work based\\n \\\non the Program.\\n \\\n\\n \\\n  To \\\"propagate\\\" a work means to do anything with it that, without\\n \\\npermission, would make you directly or secondarily liable for\\n \\\ninfringement under applicable copyright law, except executing it on a\\n \\\ncomputer or modifying a private copy.  Propagation includes copying,\\n \\\ndistribution (with or without modification), making available to the\\n \\\npublic, and in some countries other activities as well.\\n \\\n\\n \\\n  To \\\"convey\\\" a work means any kind of propagation that enables other\\n \\\nparties to make or receive copies.  Mere interaction with a user through\\n \\\na computer network, with no transfer of a copy, is not conveying.\\n \\\n\\n \\\n  An interactive user interface displays \\\"Appropriate Legal Notices\\\"\\n \\\nto the extent that it includes a convenient and prominently visible\\n \\\nfeature that (1) displays an appropriate copyright notice, and (2)\\n \\\ntells the user that there is no warranty for the work (except to the\\n \\\nextent that warranties are provided), that licensees may convey the\\n \\\nwork under this License, and how to view a copy of this License.  If\\n \\\nthe interface presents a list of user commands or options, such as a\\n \\\nmenu, a prominent item in the list meets this criterion.\\n \\\n\\n \\\n  1. Source Code.\\n \\\n\\n \\\n  The \\\"source code\\\" for a work means the preferred form of the work\\n \\\nfor making modifications to it.  \\\"Object code\\\" means any non-source\\n \\\nform of a work.\\n \\\n\\n \\\n  A \\\"Standard Interface\\\" means an interface that either is an official\\n \\\nstandard defined by a recognized standards body, or, in the case of\\n \\\ninterfaces specified for a particular programming language, one that\\n \\\nis widely used among developers working in that language.\\n \\\n\\n \\\n  The \\\"System Libraries\\\" of an executable work include anything, other\\n \\\nthan the work as a whole, that (a) is included in the normal form of\\n \\\npackaging a Major Component, but which is not part of that Major\\n \\\nComponent, and (b) serves only to enable use of the work with that\\n \\\nMajor Component, or to implement a Standard Interface for which an\\n \\\nimplementation is available to the public in source code form.  A\\n \\\n\\\"Major Component\\\", in this context, means a major essential component\\n \\\n(kernel, window system, and so on) of the specific operating system\\n \\\n(if any) on which the executable work runs, or a compiler used to\\n \\\nproduce the work, or an object code interpreter used to run it.\\n \\\n\\n \\\n  The \\\"Corresponding Source\\\" for a work in object code form means all\\n \\\nthe source code needed to generate, install, and (for an executable\\n \\\nwork) run the object code and to modify the work, including scripts to\\n \\\ncontrol those activities.  However, it does not include the work's\\n \\\nSystem Libraries, or general-purpose tools or generally available free\\n \\\nprograms which are used unmodified in performing those activities but\\n \\\nwhich are not part of the work.  For example, Corresponding Source\\n \\\nincludes interface definition files associated with source files for\\n \\\nthe work, and the source code for shared libraries and dynamically\\n \\\nlinked subprograms that the work is specifically designed to require,\\n \\\nsuch as by intimate data communication or control flow between those\\n \\\nsubprograms and other parts of the work.\\n \\\n\\n \\\n  The Corresponding Source need not include anything that users\\n \\\ncan regenerate automatically from other parts of the Corresponding\\n \\\nSource.\\n \\\n\\n \\\n  The Corresponding Source for a work in source code form is that\\n \\\nsame work.\\n \\\n\\n \\\n  2. Basic Permissions.\\n \\\n\\n \\\n  All rights granted under this License are granted for the term of\\n \\\ncopyright on the Program, and are irrevocable provided the stated\\n \\\nconditions are met.  This License explicitly affirms your unlimited\\n \\\npermission to run the unmodified Program.  The output from running a\\n \\\ncovered work is covered by this License only if the output, given its\\n \\\ncontent, constitutes a covered work.  This License acknowledges your\\n \\\nrights of fair use or other equivalent, as provided by copyright law.\\n \\\n\\n \\\n  You may make, run and propagate covered works that you do not\\n \\\nconvey, without conditions so long as your license otherwise remains\\n \\\nin force.  You may convey covered works to others for the sole purpose\\n \\\nof having them make modifications exclusively for you, or provide you\\n \\\nwith facilities for running those works, provided that you comply with\\n \\\nthe terms of this License in conveying all material for which you do\\n \\\nnot control copyright.  Those thus making or running the covered works\\n \\\nfor you must do so exclusively on your behalf, under your direction\\n \\\nand control, on terms that prohibit them from making any copies of\\n \\\nyour copyrighted material outside their relationship with you.\\n \\\n\\n \\\n  Conveying under any other circumstances is permitted solely under\\n \\\nthe conditions stated below.  Sublicensing is not allowed; section 10\\n \\\nmakes it unnecessary.\\n \\\n\\n \\\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\\n \\\n\\n \\\n  No covered work shall be deemed part of an effective technological\\n \\\nmeasure under any applicable law fulfilling obligations under article\\n \\\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\\n \\\nsimilar laws prohibiting or restricting circumvention of such\\n \\\nmeasures.\\n \\\n\\n \\\n  When you convey a covered work, you waive any legal power to forbid\\n \\\ncircumvention of technological measures to the extent such circumvention\\n \\\nis effected by exercising rights under this License with respect to\\n \\\nthe covered work, and you disclaim any intention to limit operation or\\n \\\nmodification of the work as a means of enforcing, against the work's\\n \\\nusers, your or third parties' legal rights to forbid circumvention of\\n \\\ntechnological measures.\\n \\\n\\n \\\n  4. Conveying Verbatim Copies.\\n \\\n\\n \\\n  You may convey verbatim copies of the Program's source code as you\\n \\\nreceive it, in any medium, provided that you conspicuously and\\n \\\nappropriately publish on each copy an appropriate copyright notice;\\n \\\nkeep intact all notices stating that this License and any\\n \\\nnon-permissive terms added in accord with section 7 apply to the code;\\n \\\nkeep intact all notices of the absence of any warranty; and give all\\n \\\nrecipients a copy of this License along with the Program.\\n \\\n\\n \\\n  You may charge any price or no price for each copy that you convey,\\n \\\nand you may offer support or warranty protection for a fee.\\n \\\n\\n \\\n  5. Conveying Modified Source Versions.\\n \\\n\\n \\\n  You may convey a work based on the Program, or the modifications to\\n \\\nproduce it from the Program, in the form of source code under the\\n \\\nterms of section 4, provided that you also meet all of these conditions:\\n \\\n\\n \\\n    a) The work must carry prominent notices stating that you modified\\n \\\n    it, and giving a relevant date.\\n \\\n\\n \\\n    b) The work must carry prominent notices stating that it is\\n \\\n    released under this License and any conditions added under section\\n \\\n    7.  This requirement modifies the requirement in section 4 to\\n \\\n    \\\"keep intact all notices\\\".\\n \\\n\\n \\\n    c) You must license the entire work, as a whole, under this\\n \\\n    License to anyone who comes into possession of a copy.  This\\n \\\n    License will therefore apply, along with any applicable section 7\\n \\\n    additional terms, to the whole of the work, and all its parts,\\n \\\n    regardless of how they are packaged.  This License gives no\\n \\\n    permission to license the work in any other way, but it does not\\n \\\n    invalidate such permission if you have separately received it.\\n \\\n\\n \\\n    d) If the work has interactive user interfaces, each must display\\n \\\n    Appropriate Legal Notices; however, if the Program has interactive\\n \\\n    interfaces that do not display Appropriate Legal Notices, your\\n \\\n    work need not make them do so.\\n \\\n\\n \\\n  A compilation of a covered work with other separate and independent\\n \\\nworks, which are not by their nature extensions of the covered work,\\n \\\nand which are not combined with it such as to form a larger program,\\n \\\nin or on a volume of a storage or distribution medium, is called an\\n \\\n\\\"aggregate\\\" if the compilation and its resulting copyright are not\\n \\\nused to limit the access or legal rights of the compilation's users\\n \\\nbeyond what the individual works permit.  Inclusion of a covered work\\n \\\nin an aggregate does not cause this License to apply to the other\\n \\\nparts of the aggregate.\\n \\\n\\n \\\n  6. Conveying Non-Source Forms.\\n \\\n\\n \\\n  You may convey a covered work in object code form under the terms\\n \\\nof sections 4 and 5, provided that you also convey the\\n \\\nmachine-readable Corresponding Source under the terms of this License,\\n \\\nin one of these ways:\\n \\\n\\n \\\n    a) Convey the object code in, or embodied in, a physical product\\n \\\n    (including a physical distribution medium), accompanied by the\\n \\\n    Corresponding Source fixed on a durable physical medium\\n \\\n    customarily used for software interchange.\\n \\\n\\n \\\n    b) Convey the object code in, or embodied in, a physical product\\n \\\n    (including a physical distribution medium), accompanied by a\\n \\\n    written offer, valid for at least three years and valid for as\\n \\\n    long as you offer spare parts or customer support for that product\\n \\\n    model, to give anyone who possesses the object code either (1) a\\n \\\n    copy of the Corresponding Source for all the software in the\\n \\\n    product that is covered by this License, on a durable physical\\n \\\n    medium customarily used for software interchange, for a price no\\n \\\n    more than your reasonable cost of physically performing this\\n \\\n    conveying of source, or (2) access to copy the\\n \\\n    Corresponding Source from a network server at no charge.\\n \\\n\\n \\\n    c) Convey individual copies of the object code with a copy of the\\n \\\n    written offer to provide the Corresponding Source.  This\\n \\\n    alternative is allowed only occasionally and noncommercially, and\\n \\\n    only if you received the object code with such an offer, in accord\\n \\\n    with subsection 6b.\\n \\\n\\n \\\n    d) Convey the object code by offering access from a designated\\n \\\n    place (gratis or for a charge), and offer equivalent access to the\\n \\\n    Corresponding Source in the same way through the same place at no\\n \\\n    further charge.  You need not require recipients to copy the\\n \\\n    Corresponding Source along with the object code.  If the place to\\n \\\n    copy the object code is a network server, the Corresponding Source\\n \\\n    may be on a different server (operated by you or a third party)\\n \\\n    that supports equivalent copying facilities, provided you maintain\\n \\\n    clear directions next to the object code saying where to find the\\n \\\n    Corresponding Source.  Regardless of what server hosts the\\n \\\n    Corresponding Source, you remain obligated to ensure that it is\\n \\\n    available for as long as needed to satisfy these requirements.\\n \\\n\\n \\\n    e) Convey the object code using peer-to-peer transmission, provided\\n \\\n    you inform other peers where the object code and Corresponding\\n \\\n    Source of the work are being offered to the general public at no\\n \\\n    charge under subsection 6d.\\n \\\n\\n \\\n  A separable portion of the object code, whose source code is excluded\\n \\\nfrom the Corresponding Source as a System Library, need not be\\n \\\nincluded in conveying the object code work.\\n \\\n\\n \\\n  A \\\"User Product\\\" is either (1) a \\\"consumer product\\\", which means any\\n \\\ntangible personal property which is normally used for personal, family,\\n \\\nor household purposes, or (2) anything designed or sold for incorporation\\n \\\ninto a dwelling.  In determining whether a product is a consumer product,\\n \\\ndoubtful cases shall be resolved in favor of coverage.  For a particular\\n \\\nproduct received by a particular user, \\\"normally used\\\" refers to a\\n \\\ntypical or common use of that class of product, regardless of the status\\n \\\nof the particular user or of the way in which the particular user\\n \\\nactually uses, or expects or is expected to use, the product.  A product\\n \\\nis a consumer product regardless of whether the product has substantial\\n \\\ncommercial, industrial or non-consumer uses, unless such uses represent\\n \\\nthe only significant mode of use of the product.\\n \\\n\\n \\\n  \\\"Installation Information\\\" for a User Product means any methods,\\n \\\nprocedures, authorization keys, or other information required to install\\n \\\nand execute modified versions of a covered work in that User Product from\\n \\\na modified version of its Corresponding Source.  The information must\\n \\\nsuffice to ensure that the continued functioning of the modified object\\n \\\ncode is in no case prevented or interfered with solely because\\n \\\nmodification has been made.\\n \\\n\\n \\\n  If you convey an object code work under this section in, or with, or\\n \\\nspecifically for use in, a User Product, and the conveying occurs as\\n \\\npart of a transaction in which the right of possession and use of the\\n \\\nUser Product is transferred to the recipient in perpetuity or for a\\n \\\nfixed term (regardless of how the transaction is characterized), the\\n \\\nCorresponding Source conveyed under this section must be accompanied\\n \\\nby the Installation Information.  But this requirement does not apply\\n \\\nif neither you nor any third party retains the ability to install\\n \\\nmodified object code on the User Product (for example, the work has\\n \\\nbeen installed in ROM).\\n \\\n\\n \\\n  The requirement to provide Installation Information does not include a\\n \\\nrequirement to continue to provide support service, warranty, or updates\\n \\\nfor a work that has been modified or installed by the recipient, or for\\n \\\nthe User Product in which it has been modified or installed.  Access to a\\n \\\nnetwork may be denied when the modification itself materially and\\n \\\nadversely affects the operation of the network or violates the rules and\\n \\\nprotocols for communication across the network.\\n \\\n\\n \\\n  Corresponding Source conveyed, and Installation Information provided,\\n \\\nin accord with this section must be in a format that is publicly\\n \\\ndocumented (and with an implementation available to the public in\\n \\\nsource code form), and must require no special password or key for\\n \\\nunpacking, reading or copying.\\n \\\n\\n \\\n  7. Additional Terms.\\n \\\n\\n \\\n  \\\"Additional permissions\\\" are terms that supplement the terms of this\\n \\\nLicense by making exceptions from one or more of its conditions.\\n \\\nAdditional permissions that are applicable to the entire Program shall\\n \\\nbe treated as though they were included in this License, to the extent\\n \\\nthat they are valid under applicable law.  If additional permissions\\n \\\napply only to part of the Program, that part may be used separately\\n \\\nunder those permissions, but the entire Program remains governed by\\n \\\nthis License without regard to the additional permissions.\\n \\\n\\n \\\n  When you convey a copy of a covered work, you may at your option\\n \\\nremove any additional permissions from that copy, or from any part of\\n \\\nit.  (Additional permissions may be written to require their own\\n \\\nremoval in certain cases when you modify the work.)  You may place\\n \\\nadditional permissions on material, added by you to a covered work,\\n \\\nfor which you have or can give appropriate copyright permission.\\n \\\n\\n \\\n  Notwithstanding any other provision of this License, for material you\\n \\\nadd to a covered work, you may (if authorized by the copyright holders of\\n \\\nthat material) supplement the terms of this License with terms:\\n \\\n\\n \\\n    a) Disclaiming warranty or limiting liability differently from the\\n \\\n    terms of sections 15 and 16 of this License; or\\n \\\n\\n \\\n    b) Requiring preservation of specified reasonable legal notices or\\n \\\n    author attributions in that material or in the Appropriate Legal\\n \\\n    Notices displayed by works containing it; or\\n \\\n\\n \\\n    c) Prohibiting misrepresentation of the origin of that material, or\\n \\\n    requiring that modified versions of such material be marked in\\n \\\n    reasonable ways as different from the original version; or\\n \\\n\\n \\\n    d) Limiting the use for publicity purposes of names of licensors or\\n \\\n    authors of the material; or\\n \\\n\\n \\\n    e) Declining to grant rights under trademark law for use of some\\n \\\n    trade names, trademarks, or service marks; or\\n \\\n\\n \\\n    f) Requiring indemnification of licensors and authors of that\\n \\\n    material by anyone who conveys the material (or modified versions of\\n \\\n    it) with contractual assumptions of liability to the recipient, for\\n \\\n    any liability that these contractual assumptions directly impose on\\n \\\n    those licensors and authors.\\n \\\n\\n \\\n  All other non-permissive additional terms are considered \\\"further\\n \\\nrestrictions\\\" within the meaning of section 10.  If the Program as you\\n \\\nreceived it, or any part of it, contains a notice stating that it is\\n \\\ngoverned by this License along with a term that is a further\\n \\\nrestriction, you may remove that term.  If a license document contains\\n \\\na further restriction but permits relicensing or conveying under this\\n \\\nLicense, you may add to a covered work material governed by the terms\\n \\\nof that license document, provided that the further restriction does\\n \\\nnot survive such relicensing or conveying.\\n \\\n\\n \\\n  If you add terms to a covered work in accord with this section, you\\n \\\nmust place, in the relevant source files, a statement of the\\n \\\nadditional terms that apply to those files, or a notice indicating\\n \\\nwhere to find the applicable terms.\\n \\\n\\n \\\n  Additional terms, permissive or non-permissive, may be stated in the\\n \\\nform of a separately written license, or stated as exceptions;\\n \\\nthe above requirements apply either way.\\n \\\n\\n \\\n  8. Termination.\\n \\\n\\n \\\n  You may not propagate or modify a covered work except as expressly\\n \\\nprovided under this License.  Any attempt otherwise to propagate or\\n \\\nmodify it is void, and will automatically terminate your rights under\\n \\\nthis License (including any patent licenses granted under the third\\n \\\nparagraph of section 11).\\n \\\n\\n \\\n  However, if you cease all violation of this License, then your\\n \\\nlicense from a particular copyright holder is reinstated (a)\\n \\\nprovisionally, unless and until the copyright holder explicitly and\\n \\\nfinally terminates your license, and (b) permanently, if the copyright\\n \\\nholder fails to notify you of the violation by some reasonable means\\n \\\nprior to 60 days after the cessation.\\n \\\n\\n \\\n  Moreover, your license from a particular copyright holder is\\n \\\nreinstated permanently if the copyright holder notifies you of the\\n \\\nviolation by some reasonable means, this is the first time you have\\n \\\nreceived notice of violation of this License (for any work) from that\\n \\\ncopyright holder, and you cure the violation prior to 30 days after\\n \\\nyour receipt of the notice.\\n \\\n\\n \\\n  Termination of your rights under this section does not terminate the\\n \\\nlicenses of parties who have received copies or rights from you under\\n \\\nthis License.  If your rights have been terminated and not permanently\\n \\\nreinstated, you do not qualify to receive new licenses for the same\\n \\\nmaterial under section 10.\\n \\\n\\n \\\n  9. Acceptance Not Required for Having Copies.\\n \\\n\\n \\\n  You are not required to accept this License in order to receive or\\n \\\nrun a copy of the Program.  Ancillary propagation of a covered work\\n \\\noccurring solely as a consequence of using peer-to-peer transmission\\n \\\nto receive a copy likewise does not require acceptance.  However,\\n \\\nnothing other than this License grants you permission to propagate or\\n \\\nmodify any covered work.  These actions infringe copyright if you do\\n \\\nnot accept this License.  Therefore, by modifying or propagating a\\n \\\ncovered work, you indicate your acceptance of this License to do so.\\n \\\n\\n \\\n  10. Automatic Licensing of Downstream Recipients.\\n \\\n\\n \\\n  Each time you convey a covered work, the recipient automatically\\n \\\nreceives a license from the original licensors, to run, modify and\\n \\\npropagate that work, subject to this License.  You are not responsible\\n \\\nfor enforcing compliance by third parties with this License.\\n \\\n\\n \\\n  An \\\"entity transaction\\\" is a transaction transferring control of an\\n \\\norganization, or substantially all assets of one, or subdividing an\\n \\\norganization, or merging organizations.  If propagation of a covered\\n \\\nwork results from an entity transaction, each party to that\\n \\\ntransaction who receives a copy of the work also receives whatever\\n \\\nlicenses to the work the party's predecessor in interest had or could\\n \\\ngive under the previous paragraph, plus a right to possession of the\\n \\\nCorresponding Source of the work from the predecessor in interest, if\\n \\\nthe predecessor has it or can get it with reasonable efforts.\\n \\\n\\n \\\n  You may not impose any further restrictions on the exercise of the\\n \\\nrights granted or affirmed under this License.  For example, you may\\n \\\nnot impose a license fee, royalty, or other charge for exercise of\\n \\\nrights granted under this License, and you may not initiate litigation\\n \\\n(including a cross-claim or counterclaim in a lawsuit) alleging that\\n \\\nany patent claim is infringed by making, using, selling, offering for\\n \\\nsale, or importing the Program or any portion of it.\\n \\\n\\n \\\n  11. Patents.\\n \\\n\\n \\\n  A \\\"contributor\\\" is a copyright holder who authorizes use under this\\n \\\nLicense of the Program or a work on which the Program is based.  The\\n \\\nwork thus licensed is called the contributor's \\\"contributor version\\\".\\n \\\n\\n \\\n  A contributor's \\\"essential patent claims\\\" are all patent claims\\n \\\nowned or controlled by the contributor, whether already acquired or\\n \\\nhereafter acquired, that would be infringed by some manner, permitted\\n \\\nby this License, of making, using, or selling its contributor version,\\n \\\nbut do not include claims that would be infringed only as a\\n \\\nconsequence of further modification of the contributor version.  For\\n \\\npurposes of this definition, \\\"control\\\" includes the right to grant\\n \\\npatent sublicenses in a manner consistent with the requirements of\\n \\\nthis License.\\n \\\n\\n \\\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\\n \\\npatent license under the contributor's essential patent claims, to\\n \\\nmake, use, sell, offer for sale, import and otherwise run, modify and\\n \\\npropagate the contents of its contributor version.\\n \\\n\\n \\\n  In the following three paragraphs, a \\\"patent license\\\" is any express\\n \\\nagreement or commitment, however denominated, not to enforce a patent\\n \\\n(such as an express permission to practice a patent or covenant not to\\n \\\nsue for patent infringement).  To \\\"grant\\\" such a patent license to a\\n \\\nparty means to make such an agreement or commitment not to enforce a\\n \\\npatent against the party.\\n \\\n\\n \\\n  If you convey a covered work, knowingly relying on a patent license,\\n \\\nand the Corresponding Source of the work is not available for anyone\\n \\\nto copy, free of charge and under the terms of this License, through a\\n \\\npublicly available network server or other readily accessible means,\\n \\\nthen you must either (1) cause the Corresponding Source to be so\\n \\\navailable, or (2) arrange to deprive yourself of the benefit of the\\n \\\npatent license for this particular work, or (3) arrange, in a manner\\n \\\nconsistent with the requirements of this License, to extend the patent\\n \\\nlicense to downstream recipients.  \\\"Knowingly relying\\\" means you have\\n \\\nactual knowledge that, but for the patent license, your conveying the\\n \\\ncovered work in a country, or your recipient's use of the covered work\\n \\\nin a country, would infringe one or more identifiable patents in that\\n \\\ncountry that you have reason to believe are valid.\\n \\\n\\n \\\n  If, pursuant to or in connection with a single transaction or\\n \\\narrangement, you convey, or propagate by procuring conveyance of, a\\n \\\ncovered work, and grant a patent license to some of the parties\\n \\\nreceiving the covered work authorizing them to use, propagate, modify\\n \\\nor convey a specific copy of the covered work, then the patent license\\n \\\nyou grant is automatically extended to all recipients of the covered\\n \\\nwork and works based on it.\\n \\\n\\n \\\n  A patent license is \\\"discriminatory\\\" if it does not include within\\n \\\nthe scope of its coverage, prohibits the exercise of, or is\\n \\\nconditioned on the non-exercise of one or more of the rights that are\\n \\\nspecifically granted under this License.  You may not convey a covered\\n \\\nwork if you are a party to an arrangement with a third party that is\\n \\\nin the business of distributing software, under which you make payment\\n \\\nto the third party based on the extent of your activity of conveying\\n \\\nthe work, and under which the third party grants, to any of the\\n \\\nparties who would receive the covered work from you, a discriminatory\\n \\\npatent license (a) in connection with copies of the covered work\\n \\\nconveyed by you (or copies made from those copies), or (b) primarily\\n \\\nfor and in connection with specific products or compilations that\\n \\\ncontain the covered work, unless you entered into that arrangement,\\n \\\nor that patent license was granted, prior to 28 March 2007.\\n \\\n\\n \\\n  Nothing in this License shall be construed as excluding or limiting\\n \\\nany implied license or other defenses to infringement that may\\n \\\notherwise be available to you under applicable patent law.\\n \\\n\\n \\\n  12. No Surrender of Others' Freedom.\\n \\\n\\n \\\n  If conditions are imposed on you (whether by court order, agreement or\\n \\\notherwise) that contradict the conditions of this License, they do not\\n \\\nexcuse you from the conditions of this License.  If you cannot convey a\\n \\\ncovered work so as to satisfy simultaneously your obligations under this\\n \\\nLicense and any other pertinent obligations, then as a consequence you may\\n \\\nnot convey it at all.  For example, if you agree to terms that obligate you\\n \\\nto collect a royalty for further conveying from those to whom you convey\\n \\\nthe Program, the only way you could satisfy both those terms and this\\n \\\nLicense would be to refrain entirely from conveying the Program.\\n \\\n\\n \\\n  13. Use with the GNU Affero General Public License.\\n \\\n\\n \\\n  Notwithstanding any other provision of this License, you have\\n \\\npermission to link or combine any covered work with a work licensed\\n \\\nunder version 3 of the GNU Affero General Public License into a single\\n \\\ncombined work, and to convey the resulting work.  The terms of this\\n \\\nLicense will continue to apply to the part which is the covered work,\\n \\\nbut the special requirements of the GNU Affero General Public License,\\n \\\nsection 13, concerning interaction through a network will apply to the\\n \\\ncombination as such.\\n \\\n\\n \\\n  14. Revised Versions of this License.\\n \\\n\\n \\\n  The Free Software Foundation may publish revised and/or new versions of\\n \\\nthe GNU General Public License from time to time.  Such new versions will\\n \\\nbe similar in spirit to the present version, but may differ in detail to\\n \\\naddress new problems or concerns.\\n \\\n\\n \\\n  Each version is given a distinguishing version number.  If the\\n \\\nProgram specifies that a certain numbered version of the GNU General\\n \\\nPublic License \\\"or any later version\\\" applies to it, you have the\\n \\\noption of following the terms and conditions either of that numbered\\n \\\nversion or of any later version published by the Free Software\\n \\\nFoundation.  If the Program does not specify a version number of the\\n \\\nGNU General Public License, you may choose any version ever published\\n \\\nby the Free Software Foundation.\\n \\\n\\n \\\n  If the Program specifies that a proxy can decide which future\\n \\\nversions of the GNU General Public License can be used, that proxy's\\n \\\npublic statement of acceptance of a version permanently authorizes you\\n \\\nto choose that version for the Program.\\n \\\n\\n \\\n  Later license versions may give you additional or different\\n \\\npermissions.  However, no additional obligations are imposed on any\\n \\\nauthor or copyright holder as a result of your choosing to follow a\\n \\\nlater version.\\n \\\n\\n \\\n  15. Disclaimer of Warranty.\\n \\\n\\n \\\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\\n \\\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\\n \\\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \\\"AS IS\\\" WITHOUT WARRANTY\\n \\\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\\n \\\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\\n \\\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\\n \\\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\\n \\\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\\n \\\n\\n \\\n  16. Limitation of Liability.\\n \\\n\\n \\\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\\n \\\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\\n \\\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\\n \\\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\\n \\\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\\n \\\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\\n \\\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\\n \\\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\\n \\\nSUCH DAMAGES.\\n \\\n\\n \\\n  17. Interpretation of Sections 15 and 16.\\n \\\n\\n \\\n  If the disclaimer of warranty and limitation of liability provided\\n \\\nabove cannot be given local legal effect according to their terms,\\n \\\nreviewing courts shall apply local law that most closely approximates\\n \\\nan absolute waiver of all civil liability in connection with the\\n \\\nProgram, unless a warranty or assumption of liability accompanies a\\n \\\ncopy of the Program in return for a fee.\\n \\\n\\n \\\n                     END OF TERMS AND CONDITIONS\\n \\\n\\n\";\n\n \n"
  },
  {
    "path": "README.md",
    "content": "[![build status](https://gitlab.cern.ch/dss/eos/badges/master/build.svg)](https://gitlab.cern.ch/dss/eos/commits/master)\n\n# EOS\n\n## Description\n\n**EOS** is a software solution that aims to provide fast and reliable multi-PB\ndisk-only storage technology for both LHC and non-LHC use-cases at CERN. The\ncore of the implementation is the XRootD framework which provides feature-rich\nremote access protocol. The storage system is running on commodity hardware\nwith disks in JBOD configuration. It is written mostly in C/C++, with some of\nthe extra modules in Python. Files can be accessed via native **XRootD**\nprotocol, a **POSIX-like FUSE** client or **HTTP(S) & WebDav** protocol.\n\n## Documentation\n\nThe most up-to-date documentation can be found at:\n[eos-docs.web.cern.ch/eos-docs](http://eos-docs.web.cern.ch/eos-docs/)\n\nYou will need to install Sphinx, Doxygen and the `solar_theme` (for Sphinx) in order to\ngenerate the docs. For up-to-date information on getting Sphinx refer to the\n[Sphinx docs](https://www.sphinx-doc.org/en/master/usage/installation.html).\n\n```bash\n## RHEL instructions\n# Please choose the relevant python version based on the distro\nsudo yum install python-sphinx doxygen\npip install solar_theme\n\n## Ubuntu 20.04 instructions\nsudo apt install python3-sphinx doxygen\npip3 install solar_theme\n```\n\n**Doxygen** documentation of the API is available in the `./doc` directory\nand can be generated using the following command:\n\n```bash\n# Inside the EOS git clone directory\ncd doc\ndoxygen\n....\n# Documentation generated in the ./html directory, viewable with any browser:\n#   file:///eos_git_clone_dir/doc/html/index.html\n```\n\n**Sphinx** documentation of installation and application is also in the `./doc` directory.\nThis is what is published at https://eos-docs.web.cern.ch.\nDocumentation can be generated using:\n```bash\ncd doc\nmake html\n# Documentation can be found in build/html/index.html (view in a browser).\n# The make interface supports other targets (e.g. latexpdf).\n```\n\n## Project directory structure\n\n- `archive/`: Archive tool implementation in Python\n- `auth_plugin/`: Authorization delegation plugin\n- `authz/`: Authorization capability functionality\n- `client/`: gRPC clients\n- `cmake/`: CMake scripts and functions\n- `common/`: Common helper files and classes\n- `console/`: Command line client implementation\n- `coverage/`: Test coverage config for LCOV\n- `doc/`: Doxygen and Sphinx documentation\n- `etc/`: Log rotation files\n- `fst/`: The Storage Server Plugin (FST)\n- `fusex/`: Next generation bi-directional FUSE mount client with high-end features\n- `man/`: Manual pages\n- `mgm/`: Metadata Namespace and Scheduling Redirector Plugin (MGM)\n- `misc/`: systemd, sysconfig and service scripts\n- `mq/`: Message Queue server plugin\n- `namespace/`: Namespace implementation\n- `nginx/`: Nginx patches for EOS integration\n- `proto/`: Protobuf definitions for various components\n- `test/`: Instance test scripts and dedicated test executables\n- `unit_tests/`: Unit tests for individual modules\n- `utils/`: Utilities and uninstall scripts\n\n## Git submodules\n\nSome components are maintained in separate upstream repositories and brought in as git submodules. Make sure submodules are initialized and kept up-to-date:\n\n```bash\ngit submodule update --init --recursive\n# To refresh later\ngit submodule update --recursive --remote\n```\n\nSubmodules currently used:\n- `quarkdb/`: QuarkDB client/server sources used by MGM for QuarkDB-backed services (e.g., QDB master, metadata/services that rely on QuarkDB).\n- `common/xrootd-ssi-protobuf-interface/`: XRootD SSI + Protobuf interface headers used by EOS gRPC/SSI integrations and CTA-related workflows.\n\nTip: See `.gitmodules` for the authoritative list and remote URLs.\n\n## Dependencies\n\nUse the EOS Diopside dependency repository.\nFollow the official installation instructions here:\n[EOS Diopside Manual – Installation](https://eos-docs.web.cern.ch/diopside/manual/hardware-installation.html#installation).\n\n```bash\nyum install -y git gcc cmake cmake3 readline readline-devel fuse fuse-devel \\\nleveldb leveldb-devel binutils-devel zlib zlib-devel zlib-static \\\nbzip2 bzip2-devel libattr libattr-devel libuuid libuuid-devel \\\nxfsprogs xfsprogs-devel sparsehash-devel e2fsprogs e2fsprogs-devel \\\nopenssl openssl-devel openssl-static eos-folly eos-rocksdb ncurses \\\nncurses-devel ncurses-static protobuf3-devel openldap-devel \\\nhiredis-devel zeromq-devel jsoncpp-devel xrootd xrootd-server-devel \\\nxrootd-client-devel xrootd-private-devel cppzmq-devel libcurl-devel \\\nlibevent-devel jemalloc jemalloc-devel\n```\n## Build\n\nTo build **EOS**, you need **gcc (>=7)** with **C++17 features** and **CMake**\ninstalled on your system. If you can install ninja, **EOS** supports ninja for builds.\n\n```bash\ngit submodule update --init --recursive\n# Create build workdir\nmkdir build-with-ninja\ncd build-with-ninja\n# Run CMake (pass -DCLIENT=1 if you only need the client binaries)\ncmake3 -GNinja ..\n# Build\nninja -j 4\n```\n\nOtherwise, standard Makefile builds are of course possible:\n\n```bash\ngit submodule update --init --recursive\n# Create build workdir\nmkdir build\ncd build\n# Run CMake (pass -DCLIENT=1 if you only need the client binaries)\ncmake3 ..\n# Build\nmake -j 4\n```\n\n## Install/Uninstall\n\nThe default behaviour is to install **EOS** at system level using `CMAKE_INSTALL_PREFIX=/usr`.\nTo change the default install prefix path, do the following:\n\n```bash\n# Modify the default install path\ncmake ../ -DCMAKE_INSTALL_PREFIX=/other_path\n# if using ninja\nninja install\n# Uninstall\nninja uninstall\n\n# Install - might require sudo privileges\nmake install\n# Uninstall\nmake uninstall\n```\n\n## Source/Binary RPM Generation\n\nTo build the source/binary RPMs run:\n\n```bash\n# Create source tarball\nmake dist\n# Create Source RPM\nmake srpm\n# Create RPM\nmake rpm\n```\n\n## Bug Reporting\n\nYou can send **EOS** bug reports to <project-eos@cern.ch>. \nThe preferable way, if you have access, is use the online bug tracking \nsystem [Jira][2] to submit new problem reports or search for existing ones: \nhttps://its.cern.ch/jira/browse/EOS\n\n## EOS Community\n\nFor discussions and help, there is also the eos community which brings together\nusers, developers & collaborators at https://eos-community.web.cern.ch/\n\n## Licence\n\n**EOS - The CERN Disk Storage System**  \n**Copyright (C) 2025 CERN/Switzerland**\nThis program is free software: you can redistribute it and/or modify it under\nthe terms of the GNU General Public License as published by the Free Software\nFoundation, either version 3 of the License, or (at your option) any later\nversion. This program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY\nor FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more\ndetails.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n[1]: http://eos-docs.web.cern.ch/eos-docs/quickstart/setup_repo.html#eos-base-setup-repos\n[2]: https://its.cern.ch/jira/secure/Dashboard.jspa\n"
  },
  {
    "path": "archive/CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Elvin-Alin Sindrilaru - <esindril@cern.ch>\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n\nif(PYTHONSITEPKG_FOUND)\n  install(PROGRAMS eosarchived.py eosarch_run.py eosarch_reconstruct.py\n\t  DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n\t  PERMISSIONS OWNER_READ OWNER_EXECUTE\n\t\t      GROUP_READ GROUP_EXECUTE\n\t\t      WORLD_READ WORLD_EXECUTE)\n\n  install(DIRECTORY eosarch\n\t  DESTINATION ${PYTHONSITEPKG_PATH}\n\t  PATTERN \"tests\" EXCLUDE\n\t  PATTERN \"*~\" EXCLUDE\n\t  PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)\n\n  install(FILES opt-eos-xrootd.pth\n\t  DESTINATION ${PYTHONSITEPKG_PATH}\n\t  PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)\n\n  install(FILES eosarchived.conf\n\t  DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}\n\t  PERMISSIONS OWNER_READ OWNER_WRITE\n\t\t      GROUP_READ\n\t\t      WORLD_READ)\n\n# Installing files depending on service manager (systemd)\nset(SYSTEMD_DIR /usr/lib/systemd/)\n\nif(EXISTS ${SYSTEMD_DIR})\n  install(FILES eosarchived_env.sysconfig\n    DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/sysconfig/\n    RENAME eosarchived_env)\n\n install(FILES eosarchived.service\n   DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/systemd/system/)\nendif()\nendif()\n"
  },
  {
    "path": "archive/eosarch/__init__.py",
    "content": "#!/usr/bin/python3\n# ------------------------------------------------------------------------------\n# File: __init__.py\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ------------------------------------------------------------------------------\n#\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2014 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\"\"\"This module provides access to EOS archives. It also facilitates the operations\n   that are done using such objects.\n\"\"\"\nfrom eosarch.configuration import Configuration\nfrom eosarch.transfer import Transfer\nfrom eosarch.processinfo import ProcessInfo\nfrom eosarch.exceptions import NoErrorException, CheckEntryException\n"
  },
  {
    "path": "archive/eosarch/archivefile.py",
    "content": "#!/usr/bin/python3\n# ------------------------------------------------------------------------------\n# File: archivefile.py\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ------------------------------------------------------------------------------\n#\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2014 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\"\"\" Class modelling an EOS archive file.\n\"\"\"\nfrom __future__ import unicode_literals\nimport logging\nimport json\nfrom XRootD import client\nfrom XRootD.client.flags import QueryCode\nfrom eosarch.utils import is_atomic_version_file, seal_path\nfrom eosarch.utils import exec_cmd, get_entry_info, set_dir_info\nfrom eosarch.exceptions import CheckEntryException\n\n\nclass ArchiveFile(object):\n    \"\"\" Class modelling an EOS archive file.\n\n    Attributes:\n        file: File object pointing to local archive file.\n        d2t: True if operation from disk to tape, otherwise False. For backup\n             operations we consider it as a transfer from tape to disk thus it\n             is False.\n        header: Archive header dictionary.\n    \"\"\"\n\n    def __init__(self, path, d2t):\n        \"\"\"Initialize ArchiveFile object.\n\n        Args:\n            path (str): Local path to archive file.\n            d2t (bool): True if transfer is to be disk to tape.\n\n        Raises:\n            IOError: Failed to open local transfer file.\n        \"\"\"\n        self.logger = logging.getLogger(\"transfer\")\n        self.d2t = d2t\n\n        try:\n            self.file = open(path, 'r')\n        except IOError as __:\n            self.logger.error(\"Failed to open file={0}\".format(path))\n            raise\n\n        line = self.file.readline()\n        self.header = json.loads(line)\n        self.fseek_dir = self.file.tell()  # save start position for dirs\n        pos = self.fseek_dir\n\n        while line:\n            line = self.file.readline()\n            entry = json.loads(line)\n\n            if entry[0] == 'f':\n                self.fseek_file = pos  # save start position for files\n                break\n\n            pos = self.file.tell()\n\n        # Create two XRootD.FileSystem object for source and destination\n        # which are to be reused throughout the transfer.\n        self.fs_src = client.FileSystem(self.header['src'])\n        self.fs_dst = client.FileSystem(self.header['dst'])\n        self.logger.debug(\"fseek_dir={0}, fseek_file={1}\".format(self.fseek_dir,\n                                                                 self.fseek_file))\n\n    def __del__(self):\n        \"\"\"Destructor needs to close the file.\n        \"\"\"\n        try:\n            self.file.close()\n        except ValueError as __:\n            self.logger.warning(\"File={0} already closed\".format(self.file.name))\n\n    def dirs(self):\n        \"\"\"Generator to read directory entries from the archive file.\n\n        Returns:\n            Return a directory entry from the archive file which looks like\n            this: ['d', \"./rel/path/dir\", \"val1\", ,\"val2\" ... ]\n        \"\"\"\n        self.file.seek(self.fseek_dir)\n        line = self.file.readline()\n\n        while line:\n            dentry = json.loads(line)\n\n            if dentry[0] == 'd':\n                yield dentry\n                line = self.file.readline()\n            else:\n                break\n\n    def files(self):\n        \"\"\"Generator to read file entries from the archive file.\n\n        Returns:\n            Return a file entry from the archive file which looks like this:\n            ['f', \"./rel/path/file\", \"val1\", ,\"val2\" ... ]\n        \"\"\"\n        self.file.seek(self.fseek_file)\n        line = self.file.readline()\n\n        while line:\n            fentry = json.loads(line)\n\n            if fentry[0] == 'f':\n                yield fentry\n                line = self.file.readline()\n            else:\n                break\n\n    def entries(self):\n        \"\"\" Generator to read all entries from the archive file.\n\n        Return:\n            A list representing a file or directory entry. See above for the\n            actual format.\n        \"\"\"\n        for dentry in self.dirs():\n            yield dentry\n\n        for fentry in self.files():\n            yield fentry\n\n    def get_fs(self, url):\n        \"\"\" Get XRootD.FileSystem object matching the host in the url.\n\n        Args:\n            url (string): XRootD endpoint URL.\n\n        Returns:\n            FileSystem object to be used or None.\n        \"\"\"\n        if url.startswith(self.header['src']):\n            return self.fs_src\n        elif url.startswith(self.header['dst']):\n            return self.fs_dst\n        else:\n            return None\n\n    def get_endpoints(self, rel_path):\n        \"\"\"Get full source and destination URLs for the given relative path.\n\n        For this use the information from the header. Take into account whether\n        it is a disk to tape transfer or not. The src in header is always the\n        disk and dst is the tape.\n\n        Args:\n            rel_path (str): Entry relative path.\n\n        Returns:\n            Return a tuple of string representing the source and the destination\n            of the transfer.\n        \"\"\"\n        if rel_path == \"./\":\n            rel_path = \"\"\n\n        src = self.header['src'] + rel_path\n        dst = self.header['dst'] + rel_path\n\n        if self.header['svc_class']:\n            dst = ''.join([dst, \"?svcClass=\", self.header['svc_class']])\n\n        return (src, dst) if self.d2t else (dst, src)\n\n    def del_entry(self, rel_path, is_dir, tape_delete):\n        \"\"\" Delete file/dir. For directories it is successful only if the dir\n        is empty. For deleting the subtree rooted in a directory one needs to\n        use the del_subtree method.\n\n        Args:\n            rel_path (str): Entry relative path as stored in the archive file.\n            is_dir (bool): True is entry is dir, otherwise False.\n            tape_delete(bool): If tape_delete is None the delete comes from a\n                PUT or GET operations so we only use the value of self.d2t to\n                decide which entry we will delete. If tape_delete is True we\n                delete tape data, otherwise we purge (delete from disk only).\n\n        Raises:\n            IOError: Deletion could not be performed.\n        \"\"\"\n        src, dst = self.get_endpoints(rel_path)\n\n        if tape_delete is None:\n            surl = dst  # self.d2t is already used inside get_endpoints\n        else:\n            surl = src if tape_delete else dst\n\n        url = client.URL(surl)\n        fs = self.get_fs(surl)\n        self.logger.debug(\"Delete entry={0}\".format(surl))\n\n        if is_dir:\n            st_rm, __ = fs.rmdir((url.path + \"?eos.ruid=0&eos.rgid=0\"))\n        else:\n            st_rm, __ = fs.rm((url.path + \"?eos.ruid=0&eos.rgid=0\"))\n\n        if not st_rm.ok:\n            # Check if entry exists\n            st_stat, __ = fs.stat(url.path)\n\n            if st_stat.ok:\n                err_msg = \"Error removing entry={0}\".format(surl)\n                self.logger.error(err_msg)\n                raise IOError()\n\n            self.logger.warning(\"Entry={0} already removed\".format(surl))\n\n    def del_subtree(self, rel_path, tape_delete):\n        \"\"\" Delete the subtree rooted at the provided path. Walk through all\n        the files and delete them one by one then proceding with the directories\n        from the deepest one to the root.\n\n        Args:\n            rel_path (string): Relative path to the subtree\n            tape_delete (boolean or None): If present and true this is a\n                deletion otherwise is a purge operation\n\n        Raises:\n            IOError: Deletion could not be performed\n        \"\"\"\n        self.logger.debug(\"Del subtree for path={0}\".format(rel_path))\n        lst_dirs = []\n\n        for fentry in self.files():\n            path = fentry[1]\n            # Delete only files rooted in current subtree\n            if path.startswith(rel_path):\n                self.del_entry(path, False, tape_delete)\n\n        for dentry in self.dirs():\n            path = dentry[1]\n\n            if rel_path == \"./\" or path.startswith(rel_path):\n                # Never delete the root path\n                if path != \"./\":\n                    lst_dirs.append(path)\n\n        # Reverse the list so that we start deleting deepest (empty) dirs first\n        lst_dirs.reverse()\n\n        for path in lst_dirs:\n            self.del_entry(path, True, tape_delete)\n\n    def make_mutable(self):\n        \"\"\" Make the EOS sub-tree pointed by header['src'] mutable.\n\n        Raises:\n            IOError when operation fails.\n        \"\"\"\n        url = client.URL(self.header['src'])\n\n        for dentry in self.dirs():\n            dir_path = url.path + dentry[1]\n            fgetattr = ''.join([url.protocol, \"://\", url.hostid, \"//proc/user/\",\n                                \"?mgm.cmd=attr&mgm.subcmd=get&mgm.attr.key=sys.acl\",\n                                \"&mgm.path=\", seal_path(dir_path)])\n            (status, stdout, __) = exec_cmd(fgetattr)\n\n            if not status:\n                warn_msg = \"No xattr sys.acl found for dir={0}\".format(dir_path)\n                self.logger.warning(warn_msg)\n            else:\n                # Remove the 'z:i' rule from the acl list\n                stdout = stdout.replace('\"', '')\n                acl_val = stdout[stdout.find('=') + 1:]\n                rules = acl_val.split(',')\n                new_rules = []\n\n                for rule in rules:\n                    if rule.startswith(\"z:\"):\n                        tag, definition = rule.split(':')\n                        pos = definition.find('i')\n\n                        if pos != -1:\n                            definition = definition[:pos] + definition[pos + 1:]\n\n                            if definition:\n                                new_rules.append(':'.join([tag, definition]))\n\n                            continue\n\n                    new_rules.append(rule)\n\n                acl_val = ','.join(new_rules)\n                self.logger.info(\"new acl: {0}\".format(acl_val))\n\n                if acl_val:\n                    # Set the new sys.acl xattr\n                    fmutable = ''.join([url.protocol, \"://\", url.hostid, \"//proc/user/?\",\n                                        \"mgm.cmd=attr&mgm.subcmd=set&mgm.attr.key=sys.acl\",\n                                        \"&mgm.attr.value=\", acl_val, \"&mgm.path=\", dir_path])\n                    (status, __, stderr) = exec_cmd(fmutable)\n\n                    if not status:\n                        err_msg = \"Error making dir={0} mutable, msg={1}\".format(\n                            dir_path, stderr)\n                        self.logger.error(err_msg)\n                        raise IOError(err_msg)\n                else:\n                    # sys.acl empty, remove it from the xattrs\n                    frmattr = ''.join([url.protocol, \"://\", url.hostid, \"//proc/user/?\",\n                                       \"mgm.cmd=attr&mgm.subcmd=rm&mgm.attr.key=sys.acl\",\n                                       \"&mgm.path=\", dir_path])\n                    (status, __, stderr) = exec_cmd(frmattr)\n\n                    if not status:\n                        err_msg = (\"Error removing xattr=sys.acl for dir={0}, msg={1}\"\n                                   \"\").format(dir_path, stderr)\n                        self.logger.error(err_msg)\n                        raise IOError(err_msg)\n\n    def check_root_dir(self):\n        \"\"\" Do the necessary checks for the destination directory depending on\n        the type of the transfer.\n\n        Raises:\n             IOError: Root dir state inconsistent.\n        \"\"\"\n        root_str = self.header['dst' if self.d2t else 'src']\n        fs = self.get_fs(root_str)\n        url = client.URL(root_str)\n        arg = url.path + \"?eos.ruid=0&eos.rgid=0\"\n        st, __ = fs.stat(arg)\n\n        if self.d2t:\n            if st.ok:\n                # For PUT destination dir must NOT exist\n                err_msg = \"Root PUT dir={0} exists\".format(root_str)\n                self.logger.error(err_msg)\n                raise IOError(err_msg)\n            else:\n                # Make sure the rest of the path exists as for the moment CASTOR\n                # mkdir -p /path/to/file does not work properly\n                pos = url.path.find('/', 1)\n\n                while pos != -1:\n                    dpath = url.path[: pos]\n                    pos = url.path.find('/', pos + 1)\n                    st, __ = fs.stat(dpath)\n\n                    if not st.ok:\n                        st, __ = fs.mkdir(dpath)\n\n                        if not st.ok:\n                            err_msg = (\"Dir={0} failed mkdir errmsg={1}\"\n                                       \"\").format(dpath, st.message)\n                            self.logger.error(err_msg)\n                            raise IOError(err_msg)\n\n        elif not self.d2t:\n            # For GET destination must exist and contain just the archive file\n            if not st.ok:\n                err_msg = \"Root GET dir={0} does NOT exist\".format(root_str)\n                self.logger.error(err_msg)\n                raise IOError(err_msg)\n            else:\n                ffindcount = ''.join([url.protocol, \"://\", url.hostid,\n                                      \"//proc/user/?mgm.cmd=find&mgm.path=\",\n                                      seal_path(url.path), \"&mgm.option=Z\"])\n                (status, stdout, stderr) = exec_cmd(ffindcount)\n\n                if status:\n                    for entry in stdout.split():\n                        tag, num = entry.split('=')\n\n                        if ((tag == 'nfiles' and num not in ['1', '2']) or\n                                (tag == 'ndirectories' and num != '1')):\n                            err_msg = (\"Root GET dir={0} should contain at least \"\n                                       \"one file and at most two - clean up and \"\n                                       \"try again\").format(root_str)\n                            self.logger.error(err_msg)\n                            raise IOError(err_msg)\n                else:\n                    err_msg = (\"Error doing find count on GET destination={0}\"\n                               \", msg={1}\").format(root_str, stderr)\n                    self.logger.error(err_msg)\n                    raise IOError(err_msg)\n\n    def verify(self, best_effort, tx_check_only=False):\n        \"\"\" Check the integrity of the archive either on disk or on tape.\n\n        Args:\n            best_effort (boolean): If True then try to verify all entries even if\n                we get an error during the check. This is used for the backup while\n                for the archive, we return as soon as we find the first error.\n            tx_check_only (boolean): If True then only check the existence of the\n                entry, the size and checksum value. This is done only for archive\n                GET operations.\n\n        Returns:\n            (status, lst_failed) - Status is True if archive is valid, otherwise\n            false. In case the archive has errors return also the first corrupted\n            entry from the archive file, otherwise return an empty list.\n            For BACKUP operations return the status and the list of entries for\n            which the verfication failed in order to provide a summary to the user.\n        \"\"\"\n        self.logger.info(\"Do transfer verification\")\n        status = True\n        lst_failed = []\n\n        for entry in self.entries():\n            try:\n                self._verify_entry(entry, tx_check_only)\n            except CheckEntryException as __:\n                lst_failed.append(entry)\n                status = False\n\n                if best_effort:\n                    continue\n                else:\n                    break\n\n        return (status, lst_failed)\n\n    def _verify_entry(self, entry, tx_check_only):\n        \"\"\" Check that the entry (file/dir) has the proper meta data.\n\n        Args:\n            entry (list): Entry from the arhive file containing all info about\n               this particular file/directory.\n            tx_check_only (boolean): If True then for files only check their\n                existence, size and checksum values.\n\n        Raises:\n            CheckEntryException: if entry verification fails.\n        \"\"\"\n        self.logger.debug(\"Verify entry={0}\".format(entry))\n        is_dir, path = (entry[0] == 'd'), entry[1]\n        __, dst = self.get_endpoints(path)\n        url = client.URL(dst)\n\n        if self.d2t:  # for PUT check entry size and checksum if possible\n            fs = self.get_fs(dst)\n            st, stat_info = fs.stat(url.path)\n\n            if not st.ok:\n                err_msg = \"Entry={0} failed stat\".format(dst)\n                self.logger.error(err_msg)\n                raise CheckEntryException(\"failed stat\")\n\n            if not is_dir:  # check file size match\n                indx = self.header[\"file_meta\"].index(\"size\") + 2\n                orig_size = int(entry[indx])\n\n                if stat_info.size != orig_size:\n                    err_msg = (\"Verify entry={0}, expect_size={1}, size={2}\"\n                               \"\").format(dst, orig_size, stat_info.size)\n                    self.logger.error(err_msg)\n                    raise CheckEntryException(\"failed file size match\")\n\n                # Check checksum only if it is adler32 - only one supported by CASTOR\n                indx = self.header[\"file_meta\"].index(\"xstype\") + 2\n\n                # !!!HACK!!! Check the checksum only if file size is not 0 since\n                # CASTOR does not store any checksum for 0 size files\n                if stat_info.size != 0 and entry[indx] == \"adler\":\n                    indx = self.header[\"file_meta\"].index(\"xs\") + 2\n                    xs = entry[indx]\n                    st, xs_resp = fs.query(QueryCode.CHECKSUM, url.path)\n\n                    if not st.ok:\n                        err_msg = \"Entry={0} failed xs query\".format(dst)\n                        self.logger.error(err_msg)\n                        raise CheckEntryException(\"failed xs query\")\n\n                    # Result has an annoying \\x00 character at the end and it\n                    # contains the xs type (adler32) and the xs value\n                    resp = xs_resp.split(b'\\x00')[0].split()\n\n                    # If checksum value is not 8 char long then we need padding\n                    if len(resp[1]) != 8:\n                        resp[1] = \"{0:0>8}\".format(resp[1])\n\n                    if resp[0] == \"adler32\" and resp[1] != xs:\n                        err_msg = (\"Entry={0} xs value missmatch xs_expected={1} \"\n                                   \"xs_got={2}\").format(dst, xs, resp[1])\n                        self.logger.error(err_msg)\n                        raise CheckEntryException(\"xs value missmatch\")\n\n        else:  # for GET check all metadata\n            if is_dir:\n                tags = self.header['dir_meta']\n            else:\n                tags = self.header['file_meta']\n                try:\n                    if self.header['twindow_type'] and self.header['twindow_val']:\n                        dfile = dict(zip(tags, entry[2:]))\n                        twindow_sec = int(self.header['twindow_val'])\n                        tentry_sec = int(float(dfile[self.header['twindow_type']]))\n\n                        if tentry_sec < twindow_sec:\n                            # No check for this entry\n                            return\n\n                    # This is a backup so don't check atomic version files\n                    if is_atomic_version_file(entry[1]):\n                        return\n                except KeyError as __:\n                    # This is not a backup transfer but an archive one, carry on\n                    pass\n\n            try:\n                meta_info = get_entry_info(url, path, tags, is_dir)\n            except (AttributeError, IOError, KeyError) as __:\n                self.logger.error(\"Failed getting metainfo entry={0}\".format(dst))\n                raise CheckEntryException(\"failed getting metainfo\")\n\n            # Check if we have any excluded xattrs\n            try:\n                excl_xattr = self.header['excl_xattr']\n            except KeyError as __:\n                excl_xattr = list()\n\n            if is_dir and excl_xattr:\n                # For directories and configurations containing excluded xattrs\n                # we refine the checks. If \"*\" in excl_xattr then no check is done.\n                if \"*\" not in excl_xattr:\n                    ref_dict = dict(zip(tags, entry[2:]))\n                    new_dict = dict(zip(tags, meta_info[2:]))\n\n                    for key, val in ref_dict.items():\n                        if not isinstance(val, dict):\n                            if new_dict[key] != val:\n                                err_msg = (\"Verify failed for entry={0} expect={1} got={2}\"\n                                           \" at key={3}\").format(dst, entry, meta_info, key)\n                                self.logger.error(err_msg)\n                                raise CheckEntryException(\"failed metainfo match\")\n                        else:\n                            for kxattr, vxattr in val.items():\n                                if kxattr not in excl_xattr:\n                                    if vxattr != new_dict[key][kxattr]:\n                                        err_msg = (\"Verify failed for entry={0} expect={1} got={2}\"\n                                                   \" at xattr key={3}\").format(dst, entry, meta_info, kxattr)\n                                        self.logger.error(err_msg)\n                                        raise CheckEntryException(\"failed metainfo match\")\n            else:\n                # For files with tx_check_only verification, we refine the checks\n                if tx_check_only and not is_dir:\n                    idx_size = self.header[\"file_meta\"].index(\"size\") + 2\n                    idx_xstype = self.header[\"file_meta\"].index(\"xstype\") + 2\n                    idx_xsval = self.header[\"file_meta\"].index(\"xs\") + 2\n\n                    if (meta_info[idx_size] != entry[idx_size] or\n                        meta_info[idx_xstype] != entry[idx_xstype] or\n                        meta_info[idx_xsval] != entry[idx_xsval]):\n                        err_msg = (\"Partial verify failed for entry={0} expect={1} got={2}\"\n                                   \"\").format(dst, entry, meta_info)\n                        self.logger.error(err_msg)\n                        raise CheckEntryException(\"failed metainfo partial match\")\n                else:\n                    if is_dir:\n                        # Compensate for the removal fo the S_ISGID bit\n                        mask_mode = int(\"02000\", base=8)\n                        val_mode = int(entry[4], base=8)\n                        val_mode |= mask_mode\n                        compat_entry = list(entry)\n                        compat_entry[4] = \"{0:o}\".format(val_mode)\n                    else:\n                        compat_entry = list(entry)\n\n                    if not meta_info == entry and not compat_entry == entry:\n                        err_msg = (\"Verify failed for entry={0} expect={1} got={2}\"\n                                   \"\").format(dst, entry, meta_info)\n                        self.logger.error(err_msg)\n                        raise CheckEntryException(\"failed metainfo match\")\n\n        self.logger.info(\"Entry={0}, status={1}\".format(dst, True))\n\n    def mkdir(self, dentry):\n        \"\"\" Create directory and optionally for GET operations set the\n        metadata information.\n\n        Args:\n            dentry (list): Directory entry as read from the archive file.\n\n        Raises:\n            IOError: Directory creation failed.\n        \"\"\"\n        __, surl = self.get_endpoints(dentry[1])\n        fs = self.get_fs(surl)\n        url = client.URL(surl)\n\n        # Create directory if not already existing\n        st, __ = fs.stat((url.path + \"?eos.ruid=0&eos.rgid=0\"))\n\n        if not st.ok:\n            if not self.d2t:\n                st, __ = fs.mkdir((url.path + \"?eos.ruid=0&eos.rgid=0\"))\n            else:\n                st, __ = fs.mkdir((url.path))\n\n            if not st.ok:\n                err_msg = (\"Dir={0} failed mkdir errmsg={1}, errno={2}, code={3}\"\n                           \"\").format(surl, st.message, st.errno, st.code)\n                self.logger.error(err_msg)\n                raise IOError(err_msg)\n\n        # For GET operations set also the metadata\n        if not self.d2t:\n            dict_dinfo = dict(zip(self.header['dir_meta'], dentry[2:]))\n\n            # Get the list of excluded extended attributes if it exists\n            try:\n                excl_xattr = self.header['excl_xattr']\n            except KeyError as __:\n                excl_xattr = list()\n\n            try:\n                set_dir_info(surl, dict_dinfo, excl_xattr)\n            except IOError as __:\n                err_msg = \"Dir={0} failed setting metadata\".format(surl)\n                self.logger.error(err_msg)\n                raise IOError(err_msg)\n"
  },
  {
    "path": "archive/eosarch/asynchandler.py",
    "content": "#!/usr/bin/python3\n# ------------------------------------------------------------------------------\n# File: asynchandler.py\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ------------------------------------------------------------------------------\n#\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2014 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\"\"\" Objects used for handling asynchronous XRootD requests.\n\"\"\"\n\nimport logging\nfrom threading import Condition\n\n\nclass _MkDirHandler(object):\n    \"\"\" Async mkdir handler which reports to MetaHandler.\n\n    Attributes:\n        path (string): Directory path for which the handler is created.\n        meta_handler (MetaHandler): Meta handler object.\n    \"\"\"\n    def __init__(self, path, meta_handler):\n        self.type = 'mkdir'\n        self.path = path\n        self.meta_handler = meta_handler\n\n    def __call__(self, status, response, hostlist):\n        self.meta_handler.handle(self.type, status, self.path)\n\n\nclass _PrepareHandler(object):\n    \"\"\" Async prepare handler which reports to MetaHandler.\n\n    Attributes:\n        path (string): Directory path for which the handler is created.\n        meta_handler (MetaHandler): Meta handler object.\n    \"\"\"\n    def __init__(self, path, meta_handler):\n        self.type = 'prepare'\n        self.path = path\n        self.meta_handler = meta_handler\n\n    def __call__(self, status, response, hostlist):\n        self.meta_handler.handle(self.type, status, self.path)\n\n\nclass _QueryHandler(object):\n    \"\"\" Async query handler which reports to MetaHandler.\n\n    Attributes:\n        path (string): File path for which the handler is created.\n        meta_handler (MetaHandler): Meta handler object.\n    \"\"\"\n    def __init__(self, path, meta_handler):\n        self.type = 'query'\n        self.path = path\n        self.meta_handler = meta_handler\n\n    def __call__(self, status, response, hostlist):\n        self.meta_handler.handle(self.type, status, self.path)\n\n\nclass MetaHandler(object):\n    \"\"\" Meta handler for different types of async requests.\n\n    Attributes:\n        cond: Condition variable used for synchronization.\n        logger: Logger object.\n        mkdir_failed: List of directories failed to create.\n        mkdir_status: Status of mkdir requests, logical and between individual\n            mkdir commands.\n        mkdir_num: Number of mkdir commands waiting for reply.\n    \"\"\"\n    def __init__(self):\n        list_op = ['mkdir', 'prepare', 'query']\n        self.num, self.status, self.err_msg, self.failed = {}, {}, {}, {}\n        self.handlers = {'mkdir': _MkDirHandler,\n                         'prepare': _PrepareHandler,\n                         'query': _QueryHandler}\n\n        for op in list_op:\n            self.num[op] = 0\n            self.status[op] = True\n            self.err_msg[op] = \"\"\n            self.failed[op] = []\n\n        self.cond = Condition()\n        self.logger = logging.getLogger(\"transfer\")\n\n    def register(self, op, path):\n        \"\"\" Register handler for operation.\n        \"\"\"\n        self.cond.acquire()\n        self.num[op] += 1\n        self.cond.release()\n        return self.handlers[op](path, self)\n\n    def handle(self, op, status, path):\n        \"\"\"Handle incoming response.\n        \"\"\"\n        self.cond.acquire()\n        self.status[op] = self.status[op] and status.ok\n        self.num[op] -= 1\n\n        if not status.ok:\n            self.failed[op].append(path)\n            self.err_msg[op] = status.message\n\n        if self.num[op] == 0:\n            self.cond.notifyAll()\n\n        self.cond.release()\n\n    def wait(self, op):\n        \"\"\"Wait for all responses to arrive.\n        \"\"\"\n        self.cond.acquire()\n\n        while self.num[op] != 0:\n            self.cond.wait()\n\n        if self.failed[op]:\n            self.logger.error((\"List of failed {0} paths is: {1}, err_msg= {2}\"\n                               \"\").format(op, self.failed[op], self.err_msg[op]))\n        else:\n            self.logger.debug(\"All {0} requests were successful\".format(op))\n\n        self.cond.release()\n        return self.status[op]\n"
  },
  {
    "path": "archive/eosarch/configuration.py",
    "content": "# ------------------------------------------------------------------------------\n# File: configuration.py\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ------------------------------------------------------------------------------\n#\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2014 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\"\"\" Class holding information about the configuration parameters used by both\n    the eosarchived daemon and also each individual transfer process.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom __future__ import print_function\nimport os\nimport sys\nimport logging\nimport logging.handlers\n\n\nclass Configuration(object):\n    \"\"\" Configuration class for the archiving daemon and the transfer processes.\n    \"\"\"\n    def __init__(self):\n        \"\"\" Initialize the configuration by reading in all the parameters from\n        the configuration file supplied. First of all, get any environment\n        variables and setup constants based on them.\n\n        Args:\n            fn_conf (string): Path to the configuration file, which in normal\n            conditions should be /etc/eosarchived.conf\n        \"\"\"\n        try:\n            LOG_DIR = os.environ[\"LOG_DIR\"]\n        except KeyError as __:\n            print(\"LOG_DIR env. not found\", file=sys.stderr)\n            raise\n\n        try:\n            self.__dict__['EOS_ARCHIVE_DIR'] = os.environ[\"EOS_ARCHIVE_DIR\"]\n        except KeyError as __:\n            print(\"EOS_ARCHIVE_DIR env. not found\", file=sys.stderr)\n            raise\n\n        try:\n            archive_conf = os.environ[\"EOS_ARCHIVE_CONF\"]\n        except KeyError as __:\n            print(\"EOS_ARCHIVE_CONF env. not found using /etc/eosarchived.conf\", file=sys.stderr)\n            archive_conf = \"/etc/eosarchived.conf\"\n\n        log_dict = {\"debug\":   logging.DEBUG,\n                    \"notice\":  logging.INFO,\n                    \"info\":    logging.INFO,\n                    \"warning\": logging.WARNING,\n                    \"error\":   logging.ERROR,\n                    \"crit\":    logging.CRITICAL,\n                    \"alert\":   logging.CRITICAL}\n\n        self.__dict__['FRONTEND_IPC'] = ''.join([self.__dict__['EOS_ARCHIVE_DIR'],\n                                                 \"archive_frontend.ipc\"])\n        self.__dict__['BACKEND_REQ_IPC'] = ''.join([self.__dict__['EOS_ARCHIVE_DIR'],\n                                                    \"archive_backend_req.ipc\"])\n        self.__dict__['BACKEND_PUB_IPC'] = ''.join([self.__dict__['EOS_ARCHIVE_DIR'],\n                                                    \"archive_backend_pub.ipc\"])\n        self.__dict__['LOG_FILE'] = LOG_DIR + \"eosarchived.log\"\n        self.__dict__['CREATE_OP'] = 'create'\n        self.__dict__['GET_OP'] = 'get'\n        self.__dict__['PUT_OP'] = 'put'\n        self.__dict__['TX_OP'] = 'transfers'\n        self.__dict__['PURGE_OP'] = 'purge'\n        self.__dict__['DELETE_OP'] = 'delete'\n        self.__dict__['KILL_OP'] = 'kill'\n        self.__dict__['BACKUP_OP'] = 'backup'\n        self.__dict__[\"STATS\"] = 'stats'\n        self.__dict__['OPT_RETRY'] = 'retry'\n        self.__dict__['OPT_FORCE'] = 'force'\n        self.__dict__['ARCH_FN'] = \".archive\"\n        self.__dict__['ARCH_INIT'] = \".archive.init\"\n        self.__dict__['ARCHIVE_MAX_TIMEOUT'] = '86400'\n\n        try:\n            with open(archive_conf, 'r') as f:\n                for line in f:\n                    line = line.strip('\\0\\n ')\n\n                    if len(line) and line[0] != '#':\n                        tokens = line.split('=', 1)\n                        # Try to convert to int by default\n                        try:\n                            self.__dict__[tokens[0]] = int(tokens[1])\n                        except ValueError as __:\n                            if tokens[0] == 'LOG_LEVEL':\n                                self.__dict__[tokens[0]] = log_dict[tokens[1]]\n                            else:\n                                self.__dict__[tokens[0]] = tokens[1]\n        except IOError as __:\n            print(\"Unable to open config file: {0}\".format(archive_conf), file=sys.stderr)\n            raise\n\n        # If no loglevel is set use INFO\n        try:\n            self.__dict__['LOG_LEVEL']\n        except KeyError as __:\n            self.__dict__['LOG_LEVEL'] = logging.INFO\n\n        # Mapping between operation type and store path for transfer and log files\n        self.__dict__['DIR'] = {}\n        self.logger, self.handler = None, None\n\n    def start_logging(self, logger_name, log_file, timed_rotating = False):\n        \"\"\" Configure the logging\n\n        Args:\n            logger_name (string): Name of the logger\n            timed_rotating (boolean): If True is a TimedRotatingFileHandler\n        \"\"\"\n        log_format = ('%(asctime)-15s %(name)s[%(process)d] %(filename)s:'\n                      '%(lineno)d LVL=%(levelname)s %(message)s')\n        logging.basicConfig(level=self.__dict__['LOG_LEVEL'], format=log_format)\n        self.__dict__['LOGGER_NAME'] = logger_name\n        self.__dict__['LOG_FILE'] = log_file\n        self.logger = logging.getLogger(self.__dict__['LOGGER_NAME'])\n        formatter = logging.Formatter(log_format)\n        permissions = 0o644;\n\n        if timed_rotating:\n            self.handler = logging.handlers.TimedRotatingFileHandler(\n                self.__dict__['LOG_FILE'], 'midnight', encoding=\"utf-8\")\n        else:\n            self.handler = logging.FileHandler(self.__dict__['LOG_FILE'],\n                                               encoding=\"utf-8\")\n\n\n        try:\n            os.chmod(self.__dict__['LOG_FILE'], permissions)\n        except OSError as ex:\n            # If we don't have access to change the permissions, we need to\n            # rely on the initial file creator having done the chmod\n            pass\n\n        self.handler.setFormatter(formatter)\n        self.logger.addHandler(self.handler)\n        self.logger.propagate = False\n\n    def display(self):\n        \"\"\" Print configuration either to the log file or stderr\n        \"\"\"\n        try:\n            self.logger.info(\"Configuration parameters:\")\n\n            for key, val in self.__dict__.items():\n                if key.isupper():\n                    self.logger.info(\"conf.{0} = {1}\".format(key, val))\n        except AttributeError as __:\n            print(\"Configuration parameters:\", file=sys.stderr)\n\n            for key, val in self.__dict__.items():\n                if key.isupper():\n                    print(\"conf.{0} = {1}\".format(key, val), file=sys.stderr)\n\n    def __setattr__(self, name, value):\n        \"\"\" Set object attribute\n\n        Args:\n            name (string): Attribute name\n            value (string): Attribute value\n        \"\"\"\n        self.__dict__[name] = value\n"
  },
  {
    "path": "archive/eosarch/exceptions.py",
    "content": "# ------------------------------------------------------------------------------\n# File: exceptions.py\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ------------------------------------------------------------------------------\n#\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2014 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\"\"\" Module containing user defined exceptions.\"\"\"\n\nclass NoErrorException(Exception):\n    \"\"\" Exception raised in case we were requested to retry an operation but\n    after the initial check there were no errors found.\n    \"\"\"\n    pass\n\n\nclass CheckEntryException(Exception):\n    \"\"\" Exception raised in cache a verify entry operation failes.\n    \"\"\"\n    pass\n\nclass NotOnTapeException(Exception):\n    \"\"\" Exception raised when a file is not on tape after the maximum\n        configured timeout per entry\n    \"\"\"\n"
  },
  {
    "path": "archive/eosarch/processinfo.py",
    "content": "# ------------------------------------------------------------------------------\n# File: processinfo.py\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ------------------------------------------------------------------------------\n#\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2014 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\"\"\" Class emulating the process information for an archive/backup transfer\n    which is used by the eosarchived daemon to display the status of the\n    ongoing transfers.\n\"\"\"\nimport time\nimport logging\nfrom os import kill\nfrom hashlib import sha256\n\n\nclass ProcessInfo(object):\n    \"\"\" Class containing information about a process. It can also hold information\n    about an orphan process and in this case the self.proc is None.\n\n    Attributes:\n       proc    (Process): Process object\n       uid         (int): UID of transfer owner\n       gid         (int): GID of transfer owner\n       uuid     (string): uuid of transfer\n       pid         (int): PID of process executing the transfer\n       root_dir (string): Root directory in EOS of the archive/backup\n       op       (string): Operation type\n       orig_req   (JSON): JSON object representing the original request\n    \"\"\"\n    def __init__(self, req_json = None):\n        \"\"\" Initializing the process info object\n\n        Args:\n            req_json (JSON): Json object containing the following information:\n                cmd: type of operation\n                src: EOS url to the archive/backup file\n                uid: UID of the user triggering the archiving\n                gid: GID of the user triggering the archiving\n        \"\"\"\n        self.logger = logging.getLogger(\"dispatcher\")\n        self.proc = None\n        self.orig_req = req_json;\n\n        if req_json:\n            # Normal, 'owned' process\n            self.uid = int(req_json['uid'])\n            self.gid = int(req_json['gid'])\n            self.status = \"pending\"\n            self.pid, self.op = 0, req_json['cmd']\n            # Extract the archive/backup root directory path\n            src = req_json['src']\n            pos = src.find(\"//\", src.find(\"//\") + 1) + 1\n            self.root_dir = src[pos : src.rfind('/') + 1]\n            self.uuid = sha256(self.root_dir.encode()).hexdigest()\n            self.timestamp = time.time()\n\n    def update(self, dict_info):\n        \"\"\" Update process information\n\n        Args:\n           dict_info (dict): Dictionary containing the following information\n           about an orphan process: uuid, pid, root_dir, op, status, uid, gid.\n           If this is not the orphan discovery step then we only have the\n           status field.\n        \"\"\"\n        self.status = dict_info['status']\n\n        try:\n            # Update for orphan processes if information present\n            self.uuid = dict_info['uuid']\n            self.root_dir = dict_info['root_dir']\n            self.op = dict_info['op']\n            self.status = dict_info['status']\n            self.pid = int(dict_info['pid'])\n            self.uid = int(dict_info['uid'])\n            self.gid = int(dict_info['gid'])\n            self.timestamp = float(dict_info['timestamp'])\n        except KeyError as __:\n            # This response is only a status update\n            pass\n\n    def is_alive(self):\n        \"\"\" Check if the underlying process is alive. For processes started\n        by the current dispatcher i.e. for which we hold a reference to the\n        Process object we can use is_alive() method, for orphan processes\n        we use the OS functionality and send it a signal to check if it is\n        still running.\n\n        Returns:\n            True if process alive, false otherwise\n        \"\"\"\n        if self.proc:\n            ret = self.proc.poll()\n\n            if ret != None:\n                info_msg = (\"Uuid={0}, pid={1}, op={2}, path={3} has terminated \"\n                            \"returncode={4}\").format(self.uuid, self.pid, self.op,\n                                                     self.root_dir, ret)\n                self.logger.info(info_msg)\n                return False\n        else:\n            try:\n                kill(self.pid, 0)\n            except OSError as __:\n                dbg_msg = (\"Uuid={0}, pid={1}, op={2}, path={3} has terminated - \"\n                           \"no returncode available\").format(self.uuid, self.pid,\n                                                           self.op, self.root_dir)\n                self.logger.debug(dbg_msg)\n                return False\n        return True\n"
  },
  {
    "path": "archive/eosarch/tests/__init__.py",
    "content": "# ------------------------------------------------------------------------------\n# File: __init__.py\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ------------------------------------------------------------------------------\n#\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2014 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n"
  },
  {
    "path": "archive/eosarch/tests/env.py",
    "content": "# ------------------------------------------------------------------------------\n# File: env.py\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ------------------------------------------------------------------------------\n#\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2014 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\nSERVER_URL=\"root://localhost/\"\nEOS_DIR=\"/eos/dev/test/\"\nLOCAL_FILE=\"test_file.dat\"\n\n\n"
  },
  {
    "path": "archive/eosarch/tests/test_archivefile.py",
    "content": "# ------------------------------------------------------------------------------\n# File: test_archivefile.py\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ------------------------------------------------------------------------------\n#\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2014 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\nimport os\nimport unittest\nimport json\nfrom archivefile.utils import exec_cmd\nfrom archivefile.archivefile import ArchiveFile\nfrom XRootD import client\nfrom env import *\n\ndef test_exec_cmd():\n    \"\"\"Check the exec command.\n\n    List directory extended attributes from EOS local instance.\n    \"\"\"\n    url = client.URL(''.join([SERVER_URL, EOS_DIR]))\n    flsattr = ''.join([url.protocol, \"://\", url.hostid, \"//proc/user/\",\n                        \"?mgm.cmd=attr&mgm.subcmd=ls&mgm.path=\", EOS_DIR])\n    (status, stdout, __) = exec_cmd(flsattr)\n    assert(status)\n\n\nclass TestArchiveFile(unittest.TestCase):\n    \"\"\" Unittest class for ArchiveFile.\"\"\"\n\n    def setUp(self):\n        \"\"\" SetUp function.\"\"\"\n        self.local_path = os.getcwd() + '/' + LOCAL_FILE\n        self.d2t = True\n        self.arch = ArchiveFile(self.local_path, self.d2t)\n\n    def tearDown(self):\n        \"\"\"TearDown function.\"\"\"\n        pass\n\n    def test_list_dirs(self):\n        \"\"\"Check generator dir listing.\n        \"\"\"\n        with open(self.local_path, 'r') as farch:\n            _ = farch.readline()  # skip the header\n            for dentry in self.arch.dirs():\n                for line in farch:\n                    entry = json.loads(line)\n                    if entry[0] == 'd':\n                        self.assertEqual(entry, dentry)\n                        break\n\n    def test_list_files(self):\n        \"\"\"Check generator file listing.\n        \"\"\"\n        with open(self.local_path, 'r') as farch:\n            _ = farch.readline()  # skip the header\n            for fentry in self.arch.files():\n                for line in farch:\n                    entry = json.loads(line)\n                    if entry[0] == 'f':\n                        self.assertEqual(entry, fentry)\n                        break\n\n    def test_list_entries(self):\n        \"\"\"Check generator of all entries.\n        \"\"\"\n        with open(self.local_path, 'r') as farch:\n            _ = farch.readline()  # skip the header\n            for aentry in self.arch.entries():\n                for line in farch:\n                    entry = json.loads(line)\n                    self.assertEqual(entry, aentry)\n                    break\n\n    def test_get_endpoints(self):\n        \"\"\"Check endpoints based on transfer type.\n        \"\"\"\n        for aentry in self.arch.entries():\n            src, dst = self.arch.get_endpoints(aentry[1])\n            self.assertTrue(src.find(self.arch.header['src']) == 0)\n            self.assertTrue(dst.find(self.arch.header['dst']) == 0)\n\n        self.d2t = False  # test tape to disk\n        self.arch = ArchiveFile(self.local_path, self.d2t)\n\n        for aentry in self.arch.entries():\n            src, dst = self.arch.get_endpoints(aentry[1])\n            self.assertTrue(src.find(self.arch.header['dst']) == 0)\n            self.assertTrue(dst.find(self.arch.header['src']) == 0)\n"
  },
  {
    "path": "archive/eosarch/transfer.py",
    "content": "# ------------------------------------------------------------------------------\n# File: tranfer.py\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ------------------------------------------------------------------------------\n#\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2014 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\"\"\"Module responsible for executing the transfers.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom __future__ import division\nimport os\nimport time\nimport logging\nimport threading\nimport zmq\nimport ast\nfrom os.path import join\nfrom time import sleep\nfrom random import randrange\nfrom hashlib import sha256\nfrom XRootD import client\nfrom XRootD.client.flags import PrepareFlags, QueryCode, OpenFlags, StatInfoFlags\nfrom eosarch.archivefile import ArchiveFile\nfrom eosarch.utils import exec_cmd, is_version_file\nfrom eosarch.asynchandler import MetaHandler\nfrom eosarch.exceptions import NoErrorException, NotOnTapeException\n\n\nclass ThreadJob(threading.Thread):\n    \"\"\" Job executing a client.CopyProcess in a separate thread. This makes sense\n    since a third-party copy job is mostly waiting for the completion of the\n    job not doing any other operations and therefore not using the GIL too much.\n\n    Attributes:\n        status             (bool): Final status of the job\n        lst_jobs           (list): List of jobs to be executed\n        proc (client.CopyProcess): Copy process which is being executed\n        retires             (int): Number of times this job was retried\n    \"\"\"\n    def __init__(self, jobs, retry=0):\n        \"\"\"Constructor\n\n        Args:\n            jobs (list): List of transfers to be executed\n            retry (int): Number of times this job was retried\n        \"\"\"\n        threading.Thread.__init__(self)\n        self.retries = retry\n        self.xrd_status = None\n        self.lst_jobs = list(jobs)\n\n    def run(self):\n        \"\"\" Run method\n        \"\"\"\n        self.retries += 1\n        proc = client.CopyProcess()\n\n        for job in self.lst_jobs:\n            # If file is 0-size then we do a normal copy, otherwise we enforce\n            # a TPC transfer\n            tpc_flag = \"none\"\n\n            if (int(job[2]) != 0):\n                tpc_flag = \"only\"\n\n            # TODO: use the parallel mode starting with XRootD 4.1\n            proc.add_job(job[0], job[1],\n                         force=True, thirdparty=tpc_flag, tpctimeout=3600)\n\n        self.xrd_status = proc.prepare()\n\n        if self.xrd_status.ok:\n            self.xrd_status, __ = proc.run()\n\n\nclass ThreadStatus(threading.Thread):\n    \"\"\" Thread responsible for replying to any requests comming from the\n    dispatcher process.\n    \"\"\"\n    def __init__(self, transfer):\n        \"\"\" Constructor\n\n        Args:\n            transfer (Transfer): Current transfer object\n        \"\"\"\n        threading.Thread.__init__(self)\n        # TODO: drop the logger as it may interfere with the main thread\n        self.logger = logging.getLogger(\"transfer\")\n        self.transfer = transfer\n        self.run_status = True\n        self.lock = threading.Lock()\n\n    def run(self):\n        \"\"\" Run method\n        \"\"\"\n        self.logger.info(\"Starting the status thread\")\n        ctx = zmq.Context()\n        socket_rr = ctx.socket(zmq.DEALER)\n        socket_rr.connect(\"ipc://\" + self.transfer.config.BACKEND_REQ_IPC)\n        socket_ps = ctx.socket(zmq.SUB)\n        mgr_filter = b\"[MASTER]\"\n        addr = \"ipc://\" + self.transfer.config.BACKEND_PUB_IPC\n        socket_ps.connect(addr)\n        socket_ps.setsockopt(zmq.SUBSCRIBE, mgr_filter)\n\n        while self.keep_running():\n            if socket_ps.poll(5000):\n                try:\n                    [__, msg] = socket_ps.recv_multipart()\n                except zmq.ZMQError as err:\n                    if err.errno == zmq.ETERM:\n                        self.logger.error(\"ETERM error\")\n                        break # shutting down, exit\n                    else:\n                        self.logger.exception(err)\n                        continue\n                except Exception as err:\n                    self.logger.exception(err)\n\n                self.logger.debug(\"RECV_MSG: {0}\".format(msg))\n                dict_cmd = ast.literal_eval(msg.decode())\n\n                if dict_cmd['cmd'] == 'orphan_status':\n                    self.logger.info(\"Reconnect to master ... \")\n                    resp = (\"{{'uuid': '{0}', \"\n                            \"'pid': '{1}', \"\n                            \"'uid': '{2}',\"\n                            \"'gid': '{3}',\"\n                            \"'root_dir': '{4}', \"\n                            \"'op': '{5}',\"\n                            \"'status': '{6}', \"\n                            \"'timestamp': '{7}'\"\n                            \"}}\").format(self.transfer.uuid,\n                                         self.transfer.pid,\n                                         self.transfer.uid,\n                                         self.transfer.gid,\n                                         self.transfer.root_dir,\n                                         self.transfer.oper,\n                                         self.transfer.get_status(),\n                                         self.transfer.timestamp)\n                elif dict_cmd['cmd'] == 'status':\n                    resp = (\"{{'uuid': '{0}', \"\n                            \"'status': '{1}'\"\n                            \"}}\").format(self.transfer.uuid,\n                                         self.transfer.get_status())\n                else:\n                    self.logger.error(\"Unknown command: {0}\".format(dict_cmd))\n                    continue\n\n                self.logger.info(\"Sending response: {0}\".format(resp))\n                socket_rr.send_multipart([resp.encode()], zmq.NOBLOCK)\n\n    def do_finish(self):\n        \"\"\" Set the flag for the status thread to finish execution\n        \"\"\"\n        self.lock.acquire()\n        self.run_status = False\n        self.lock.release()\n\n    def keep_running(self):\n        \"\"\" Check if we continue running - the transfer is ongoing\n\n        Returns:\n            True if status thread should keep running, otherwise False\n        \"\"\"\n        self.lock.acquire()\n        ret = self.run_status\n        self.lock.release()\n        return ret\n\n\nclass Transfer(object):\n    \"\"\" Trasfer archive object\n\n    Attributes:\n        req_json (JSON): Command received from the EOS MGM. Needs to contains the\n            following entries: cmd, src, opt, uid, gid\n        threads (list): List of threads doing partial transfers(CopyProcess jobs)\n    \"\"\"\n    def __init__(self, req_json, config):\n        self.config = config\n        self.oper = req_json['cmd']\n        self.uid, self.gid = req_json['uid'], req_json['gid']\n        self.do_retry = (req_json['opt'] == self.config.OPT_RETRY)\n        self.force = (req_json['opt'] == self.config.OPT_FORCE)\n        self.efile_full = req_json['src']\n        self.efile_root = self.efile_full[:-(len(self.efile_full) - self.efile_full.rfind('/') - 1)]\n        self.root_dir = self.efile_root[self.efile_root.rfind('//') + 1:]\n        self.uuid = sha256(self.root_dir.encode()).hexdigest()\n        local_file = join(self.config.DIR[self.oper], self.uuid)\n        self.tx_file = local_file + \".tx\"\n        self.list_jobs, self.threads = [], []\n        self.pid = os.getpid()\n        self.archive = None\n        # Special case for inital PUT as we need to copy also the archive file\n        self.init_put = self.efile_full.endswith(self.config.ARCH_INIT)\n        self.status = \"initializing\"\n        self.lock_status = threading.Lock()\n        self.timestamp = time.time()\n        self.logger = logging.getLogger(\"transfer\")\n        self.thread_status = ThreadStatus(self)\n\n    def get_status(self):\n        \"\"\" Get current status\n\n        Returns:\n            String representing the status\n        \"\"\"\n        self.lock_status.acquire()\n        ret = self.status\n        self.lock_status.release()\n        return ret\n\n    def set_status(self, msg):\n        \"\"\" Set current status\n\n        Args:\n            msg (string): New status\n        \"\"\"\n        self.lock_status.acquire()\n        self.status = msg\n        self.lock_status.release()\n\n    def run(self):\n        \"\"\" Run requested operation - fist call prepare\n\n        Raises:\n            IOError\n        \"\"\"\n        self.thread_status.start()\n\n        if self.oper in [self.config.PUT_OP, self.config.GET_OP]:\n            self.archive_prepare()\n\n            if self.do_retry:\n                self.do_retry_transfer()\n            else:\n                try:\n                    self.do_transfer()\n                except NotOnTapeException as _:\n                    self.logger.notice(\"Doing transfer re-try due to missing file on tape\")\n                    self.do_retry_transfer()\n        elif self.oper in [self.config.PURGE_OP, self.config.DELETE_OP]:\n            self.archive_prepare()\n            self.do_delete((self.oper == self.config.DELETE_OP))\n        elif self.oper == self.config.BACKUP_OP:\n            self.backup_prepare()\n            self.do_backup()\n\n    def archive_prepare(self):\n        \"\"\" Prepare requested archive operation.\n\n        Raises:\n            IOError: Failed to rename or transfer archive file.\n        \"\"\"\n        # Rename archive file in EOS\n        efile_url = client.URL(self.efile_full)\n        eosf_rename = ''.join([self.efile_root, self.config.ARCH_FN, \".\", self.oper, \".err\"])\n        rename_url = client.URL(eosf_rename)\n        frename = ''.join([rename_url.protocol, \"://\", rename_url.hostid,\n                           \"//proc/user/?mgm.cmd=file&mgm.subcmd=rename\"\n                           \"&mgm.path=\", efile_url.path,\n                           \"&mgm.file.source=\", efile_url.path,\n                           \"&mgm.file.target=\", rename_url.path])\n        (status, __, stderr) = exec_cmd(frename)\n\n        if not status:\n            err_msg = (\"Failed to rename archive file {0} to {1}, msg={2}\"\n                       \"\").format(self.efile_full, rename_url, stderr)\n            self.logger.error(err_msg)\n            raise IOError(err_msg)\n\n        # Copy archive file from EOS to the local disk\n        self.efile_full = eosf_rename\n        eos_fs = client.FileSystem(self.efile_full)\n        st, _ = eos_fs.copy(self.efile_full + \"?eos.ruid=0&eos.rgid=0\",\n                            self.tx_file, True)\n\n        if not st.ok:\n            err_msg = (\"Failed to copy archive file={0} to local disk at={1}\"\n                       \"\").format(self.efile_full, self.tx_file)\n            self.logger.error(err_msg)\n            raise IOError(err_msg)\n\n        # Create the ArchiveFile object\n        d2t = (self.oper == self.config.PUT_OP)\n        self.archive = ArchiveFile(self.tx_file, d2t)\n\n    def do_delete(self, tape_delete):\n        \"\"\" Delete archive either from disk (purge) or from tape (delete)\n\n        Args:\n            tape_delete (boolean): If true delete data from tape, otherwise\n            from disk.\n\n        Raises:\n            IOError: Failed to delete an entry.\n        \"\"\"\n        del_dirs = []\n        self.logger.info(\"Do delete with tape_delete={0}\".format(tape_delete))\n        # Delete also the archive file saved on tape\n        if tape_delete:\n            self.archive.del_entry(self.config.ARCH_INIT, False, tape_delete)\n\n        # First remove all the files and then the directories\n        for fentry in self.archive.files():\n            # d2t is false for both purge and deletion\n            self.archive.del_entry(fentry[1], False, tape_delete)\n\n        for dentry in self.archive.dirs():\n            # Don't remove the root directory when purging\n            if not tape_delete and dentry[1] == './':\n                continue\n\n            del_dirs.append(dentry[1])\n\n        # Remove the directories from bottom up\n        while len(del_dirs):\n            dpath = del_dirs.pop()\n            self.archive.del_entry(dpath, True, tape_delete)\n\n        # Remove immutable flag from the EOS sub-tree\n        if tape_delete:\n            self.archive.make_mutable()\n\n        self.archive_tx_clean(True)\n\n    def do_transfer(self):\n        \"\"\" Execute a put or get operation.\n\n        Raises:\n            IOError when an IO opperations fails.\n        \"\"\"\n        t0 = time.time()\n        indx_dir = 0\n\n        # Create directories\n        for dentry in self.archive.dirs():\n            if dentry[1] == \"./\":\n                self.archive.check_root_dir()\n\n            indx_dir += 1\n            self.archive.mkdir(dentry)\n            msg = \"create dir {0}/{1}\".format(indx_dir, self.archive.header['num_dirs'])\n            self.set_status(msg)\n\n        # For GET issue the Prepare2Get for all the files on tape\n        self.prepare2get()\n\n        # Copy files\n        self.copy_files()\n\n        # For GET set file ownership and permissions\n        self.update_file_access()\n\n        # Verify the transferred entries\n        self.set_status(\"verifying\")\n        check_ok, __ = self.archive.verify(False)\n\n        # For PUT operations wait that all the files are on tape and for GET\n        # send a \"prepare evict\" request to CTA to clear the disk cache\n        if self.archive.d2t:\n            self.set_status(\"wait_on_tape\")\n            self.wait_on_tape()\n        else:\n            self.set_status(\"evict_disk_cache\")\n            try:\n                self.evict_disk_cache()\n            except OverflowError as __:\n                self.logger.warning(\"The XRootD Python bindings do not support \"\n                                    \"the evict flag yet!\")\n\n        self.set_status(\"cleaning\")\n        self.logger.info(\"TIMING_transfer={0} sec\".format(time.time() - t0))\n        self.archive_tx_clean(check_ok)\n\n    def do_retry_transfer(self):\n        \"\"\" Execute a put or get retry operation.\n\n        Raises:\n            IOError when an IO opperations fails.\n        \"\"\"\n        t0 = time.time()\n        indx_dir = 0\n        err_entry = None\n        tx_ok, meta_ok = True, True\n        found_checkpoint = False  # flag set when reaching recovery entry\n\n        # Get the first corrupted entry and the type of corruption\n        (tx_ok, meta_ok, lst_failed) = self.check_previous_tx()\n\n        if not tx_ok or not meta_ok:\n            err_entry = lst_failed[0]\n\n        # Create directories\n        for dentry in self.archive.dirs():\n            # Search for the recovery checkpoint\n            if not found_checkpoint:\n                if dentry != err_entry:\n                    indx_dir += 1\n                    continue\n                else:\n                    found_checkpoint = True\n\n            indx_dir += 1\n            self.archive.mkdir(dentry)\n            msg = \"create dir {0}/{1}\".format(indx_dir, self.archive.header['num_dirs'])\n            self.set_status(msg)\n\n        if not tx_ok:\n            # For GET issue the Prepare2Get for all the files on tape\n            self.prepare2get(err_entry, found_checkpoint)\n\n            # Copy files\n            self.copy_files(err_entry, found_checkpoint)\n\n            # For GET set file ownership and permissions for all entries\n            self.update_file_access(err_entry, found_checkpoint)\n\n        else:\n            # For GET metadata errors set file ownership and permissions only\n            # for entries after the first corrupted one\n            self.update_file_access()\n\n        # Verify the transferred entries\n        self.set_status(\"verifying\")\n        check_ok, __ = self.archive.verify(False)\n\n        # For PUT operations wait that all the files are on tape\n        if self.archive.d2t:\n           self.set_status(\"wait_on_tape\")\n           self.wait_on_tape()\n        else:\n            self.set_status(\"evict_disk_cache\")\n            try:\n                self.evict_disk_cache()\n            except OverflowError as __:\n                self.logger.warning(\"The XRootD Python bindings do not support \"\n                                    \"the evict flag yet!\")\n\n        self.set_status(\"cleaning\")\n        self.logger.info(\"TIMING_transfer={0} sec\".format(time.time() - t0))\n        self.archive_tx_clean(check_ok)\n\n    def tx_clean(self, check_ok):\n        \"\"\" Clean a backup/archive transfer depending on its type.\n        \"\"\"\n        if self.oper == self.config.BACKUP_OP:\n            self.backup_tx_clean()\n        else:\n            self.archive_tx_clean(check_ok)\n\n    def backup_tx_clean(self):\n        \"\"\" Clean after a backup transfer by copying the log file in the same\n        directory as the destiantion of the backup.\n        \"\"\"\n        # Copy local log file to EOS directory\n        eos_log = ''.join([self.efile_root, \".sys.b#.backup.log?eos.ruid=0&eos.rgid=0\"])\n        self.logger.debug(\"Copy log:{0} to {1}\".format(self.config.LOG_FILE, eos_log))\n        self.config.handler.flush()\n        cp_client = client.FileSystem(self.efile_full)\n        st, __ = cp_client.copy(self.config.LOG_FILE, eos_log, force=True)\n\n        if not st.ok:\n            self.logger.error((\"Failed to copy log file {0} to EOS at {1}\"\n                               \"\").format(self.config.LOG_FILE, eos_log))\n        else:\n            # Delete log file if it was successfully copied to EOS\n            try:\n                os.remove(self.config.LOG_FILE)\n            except OSError as __:\n                pass\n\n        # Delete all local files associated with this transfer\n        try:\n            os.remove(self.tx_file)\n        except OSError as __:\n            pass\n\n        # Join async status thread\n        self.thread_status.do_finish()\n        self.thread_status.join()\n\n    def archive_tx_clean(self, check_ok):\n        \"\"\" Clean the transfer by renaming the archive file in EOS adding the\n        following extensions:\n        .done - the transfer was successful\n        .err  - there were errors during the transfer. These are logged in the\n             file .archive.log in the same directory.\n\n        Args:\n            check_ok (bool): True if no error occured during transfer,\n                otherwise false.\n        \"\"\"\n        # Rename arch file in EOS to reflect the status\n        if not check_ok:\n            eosf_rename = ''.join([self.efile_root, self.config.ARCH_FN, \".\", self.oper, \".err\"])\n        else:\n            eosf_rename = ''.join([self.efile_root, self.config.ARCH_FN, \".\", self.oper, \".done\"])\n\n        old_url = client.URL(self.efile_full)\n        new_url = client.URL(eosf_rename)\n        frename = ''.join([old_url.protocol, \"://\", old_url.hostid, \"//proc/user/?\",\n                           \"mgm.cmd=file&mgm.subcmd=rename&mgm.path=\", old_url.path,\n                           \"&mgm.file.source=\", old_url.path,\n                           \"&mgm.file.target=\", new_url.path])\n        (status, __, stderr) = exec_cmd(frename)\n\n        if not status:\n            err_msg = (\"Failed to rename {0} to {1}, msg={2}\"\n                       \"\").format(self.efile_full, eosf_rename, stderr)\n            self.logger.error(err_msg)\n            # TODO: raise IOError\n        else:\n            # For successful delete operations remove also the archive file\n            if self.oper == self.config.DELETE_OP and check_ok:\n                fs = client.FileSystem(self.efile_full)\n                st_rm, __ = fs.rm(new_url.path + \"?eos.ruid=0&eos.rgid=0\")\n\n                if not st_rm.ok:\n                    warn_msg = \"Failed to delete archive {0}\".format(new_url.path)\n                    self.logger.warning(warn_msg)\n\n        # Copy local log file back to EOS directory and set the ownership to the\n        # identity of the client who triggered the archive\n        dir_root = self.efile_root[self.efile_root.rfind('//') + 1:]\n        eos_log = ''.join([old_url.protocol, \"://\", old_url.hostid, \"/\",\n                           dir_root, self.config.ARCH_FN, \".log?eos.ruid=0&eos.rgid=0\"])\n\n        self.logger.debug(\"Copy log:{0} to {1}\".format(self.config.LOG_FILE, eos_log))\n        self.config.handler.flush()\n        cp_client = client.FileSystem(self.efile_full)\n        st, __ = cp_client.copy(self.config.LOG_FILE, eos_log, force=True)\n\n        if not st.ok:\n            self.logger.error((\"Failed to copy log file {0} to EOS at {1}\"\n                               \"\").format(self.config.LOG_FILE, eos_log))\n        else:\n            # User triggering archive operation owns the log file\n            eos_log_url = client.URL(eos_log)\n            fs = client.FileSystem(eos_log)\n            arg = ''.join([eos_log_url.path, \"?eos.ruid=0&eos.rgid=0&mgm.pcmd=chown&uid=\",\n                           self.uid, \"&gid=\", self.gid])\n            xrd_st, __ = fs.query(QueryCode.OPAQUEFILE, arg)\n\n            if not xrd_st.ok:\n                err_msg = (\"Failed setting ownership of the log file in\"\n                           \" EOS: {0}\").format(eos_log)\n                self.logger.error(err_msg)\n                raise IOError(err_msg)\n            else:\n                # Delete log if successfully copied to EOS and changed ownership\n                try:\n                    os.remove(self.config.LOG_FILE)\n                except OSError as __:\n                    pass\n\n        # Delete all local files associated with this transfer\n        try:\n            os.remove(self.tx_file)\n        except OSError as __:\n            pass\n\n        # Join async status thread\n        self.thread_status.do_finish()\n        self.thread_status.join()\n\n    def copy_files(self, err_entry=None, found_checkpoint=False):\n        \"\"\" Copy files.\n\n        Note that when doing PUT the layout is not conserved. Therefore, a file\n        with 3 replicas will end up as just a simple file in the new location.\n\n        Args:\n            err_entry (list): Entry record from the archive file corresponding\n                 to the first file/dir that was corrupted.\n            found_checkpoint (boolean): If True it means the checkpoint was\n                 already found and we don't need to search for it.\n\n        Raises:\n            IOError: Copy request failed.\n        \"\"\"\n        indx_file = 0\n        # For inital PUT copy also the archive file to tape\n        if self.init_put:\n            # The archive init is already renamed to archive.put.err at this\n            # and we need to take this into consideration when trasferring it\n            url = client.URL(self.efile_full)\n            eos_fs = client.FileSystem(self.efile_full)\n            st_stat, resp = eos_fs.stat(url.path)\n\n            if st_stat.ok:\n                __, dst = self.archive.get_endpoints(self.config.ARCH_INIT)\n                self.list_jobs.append((self.efile_full + \"?eos.ruid=0&eos.rgid=0\" +\n                                       \"&eos.app=archive\", dst, resp.size))\n            else:\n                err_msg = ''.join([\"Failed to get init archive file info, msg=\",\n                                   st_stat.message])\n                self.logger.error(err_msg)\n                raise IOError(err_msg)\n\n        # Copy files\n        for fentry in self.archive.files():\n            # Search for the recovery checkpoint\n            if self.do_retry and not found_checkpoint:\n                if fentry != err_entry:\n                    indx_file += 1\n                    continue\n                else:\n                    found_checkpoint = True\n\n            indx_file += 1\n            msg = \"copy file {0}/{1}\".format(indx_file, self.archive.header['num_files'])\n            self.set_status(msg)\n            src, dst = self.archive.get_endpoints(fentry[1])\n            dfile = dict(zip(self.archive.header['file_meta'], fentry[2:]))\n\n            # Copy file\n            if not self.archive.d2t:\n                # For GET we also have the dictionary with the metadata\n\n                dst = ''.join([dst, \"?eos.ctime=\", dfile['ctime'],\n                               \"&eos.mtime=\", dfile['mtime'],\n                               \"&eos.bookingsize=\", dfile['size'],\n                               \"&eos.targetsize=\", dfile['size'],\n                               \"&eos.ruid=0&eos.rgid=0&eos.app=archive\"])\n\n                # If checksum 0 don't enforce it\n                if dfile['xs'] != \"0\":\n                    dst = ''.join([dst, \"&eos.checksum=\", dfile['xs']])\n\n                # For backup we try to read as root from the source\n                if self.oper == self.config.BACKUP_OP:\n                    if '?' in src:\n                        src = ''.join([src, \"&eos.ruid=0&eos.rgid=0&eos.app=archive\"])\n                    else:\n                        src = ''.join([src, \"?eos.ruid=0&eos.rgid=0&eos.app=archive\"])\n\n                    # If this is a version file we save it as a 2-replica layout\n                    if is_version_file(fentry[1]):\n                        dst = ''.join([dst, \"&eos.layout.checksum=\", dfile['xstype'],\n                                       \"&eos.layout.type=replica&eos.layout.nstripes=2\"])\n\n                    # If time window specified then select only the matching entries\n                    if (self.archive.header['twindow_type'] and\n                            self.archive.header['twindow_val']):\n                        twindow_sec = int(self.archive.header['twindow_val'])\n                        tentry_sec = int(float(dfile[self.archive.header['twindow_type']]))\n\n                        if tentry_sec < twindow_sec:\n                            continue\n            else:\n                # For PUT read the files from EOS as root\n                src = ''.join([src, \"?eos.ruid=0&eos.rgid=0&eos.app=archive\"])\n\n            self.logger.info(\"Copying from {0} to {1}\".format(src, dst))\n            self.list_jobs.append((src, dst, dfile['size']))\n\n            if len(self.list_jobs) >= self.config.BATCH_SIZE:\n                st = self.flush_files(False)\n\n                # For archives we fail immediately, for backups it's best-effort\n                if not st and self.oper != self.config.BACKUP_OP:\n                    err_msg = \"Failed to flush files\"\n                    self.logger.error(err_msg)\n                    raise IOError(err_msg)\n\n        # Flush all pending copies and set metadata info for GET operation\n        st = self.flush_files(True)\n\n        if not st and self.oper != self.config.BACKUP_OP:\n            err_msg = \"Failed to flush files\"\n            self.logger.error(err_msg)\n            raise IOError(err_msg)\n\n    def flush_files(self, wait_all):\n        \"\"\" Flush all pending transfers from the list of jobs.\n\n        Args:\n            wait_all (bool): If true wait and collect the status from all\n                executing threads.\n\n        Returns:\n            True if files flushed successfully, otherwise false.\n        \"\"\"\n        status = True\n\n        # Wait until a thread from the pool gets freed if we reached the maximum\n        # allowed number of running threads\n        while len(self.threads) >= self.config.MAX_THREADS:\n            remove_indx, retry_threads = [], []\n\n            for indx, thread in enumerate(self.threads):\n                thread.join(self.config.JOIN_TIMEOUT)\n\n                # If thread finished get the status and mark it for removal\n                if not thread.is_alive():\n                    # If failed then attempt a retry\n                    if (not thread.xrd_status.ok and\n                        thread.retries <= self.config.MAX_RETRIES):\n\n                        self.logger.log(logging.INFO,\n                                        (\"Thread={0} failed, retries={1}\").format\n                                        (thread.ident, thread.retries))\n                        rthread = ThreadJob(thread.lst_jobs, thread.retries)\n                        rthread.start()\n                        retry_threads.append(rthread)\n                        remove_indx.append(indx)\n                        self.logger.log(logging.INFO,(\"New thread={0} doing a retry\").format\n                                        (rthread.ident))\n                        continue\n\n                    status = status and thread.xrd_status.ok\n                    log_level = logging.INFO if thread.xrd_status.ok else logging.ERROR\n                    self.logger.log(log_level,(\"Thread={0} status={1} msg={2}\").format\n                                    (thread.ident, thread.xrd_status.ok,\n                                     thread.xrd_status.message))\n                    remove_indx.append(indx)\n                    break\n\n            # Remove old/finished threads and add retry ones. For removal we\n            # need to start with big indexes first.\n            remove_indx.reverse()\n\n            for indx in remove_indx:\n                del self.threads[indx]\n\n            self.threads.extend(retry_threads)\n            del retry_threads[:]\n            del remove_indx[:]\n\n        # If we still have jobs and previous archive jobs were successful or this\n        # is a backup operartion (best-effort even if we have failed transfers)\n        if (self.list_jobs and ((self.oper != self.config.BACKUP_OP and status) or\n                                (self.oper == self.config.BACKUP_OP))):\n            thread = ThreadJob(self.list_jobs)\n            thread.start()\n            self.threads.append(thread)\n            del self.list_jobs[:]\n\n        # If a previous archive job failed or we need to wait for all jobs to\n        # finish then join the threads and collect their status\n        if (self.oper != self.config.BACKUP_OP and not status) or wait_all:\n            remove_indx, retry_threads = [], []\n\n            while self.threads:\n                for indx, thread in enumerate(self.threads):\n                    thread.join()\n\n                    # If failed then attempt a retry\n                    if (not thread.xrd_status.ok and\n                        thread.retries <= self.config.MAX_RETRIES):\n\n                        self.logger.log(logging.INFO, (\"Thread={0} failed, retries={1}\").format\n                                        (thread.ident, thread.retries))\n                        rthread = ThreadJob(thread.lst_jobs, thread.retries)\n                        rthread.start()\n                        retry_threads.append(rthread)\n                        remove_indx.append(indx)\n                        self.logger.log(logging.INFO,(\"New thread={0} doing a retry\").format\n                                        (rthread.ident))\n                        continue\n\n                    status = status and thread.xrd_status.ok\n                    log_level = logging.INFO if thread.xrd_status.ok else logging.ERROR\n                    self.logger.log(log_level, (\"Thread={0} status={1} msg={2}\").format\n                                    (thread.ident, thread.xrd_status.ok,\n                                     thread.xrd_status.message))\n                    remove_indx.append(indx)\n\n                # Remove old/finished threads and add retry ones. For removal we\n                # need to start with big indexes first.\n                remove_indx.reverse()\n\n                for indx in remove_indx:\n                    del self.threads[indx]\n\n                self.threads.extend(retry_threads)\n                del retry_threads[:]\n                del remove_indx[:]\n\n        return status\n\n    def update_file_access(self, err_entry=None, found_checkpoint=False):\n        \"\"\" Set the ownership and the permissions for the files copied to EOS.\n        This is done only for GET operation i.e. self.archive.d2t == False.\n\n        Args:\n           err_entry (list): Entry record from the archive file corresponding\n               to the first file/dir that was corrupted.\n           found_checkpoint (boolean): If True, it means the checkpoint was\n                 already found and we don't need to search for it i.e. the\n                 corrupted entry is a directory.\n\n        Raises:\n            IOError: chown or chmod operations failed\n        \"\"\"\n        if self.archive.d2t:\n            return\n\n        self.set_status(\"updating file access\")\n        t0 = time.time()\n        oper = 'query'\n        metahandler = MetaHandler()\n        fs = self.archive.fs_src\n\n        for fentry in self.archive.files():\n            # If backup operation and time window specified then update only matching ones\n            if self.oper == self.config.BACKUP_OP:\n                if self.archive.header['twindow_type'] and self.archive.header['twindow_val']:\n                    dfile = dict(zip(self.archive.header['file_meta'], fentry[2:]))\n                    twindow_sec = int(self.archive.header['twindow_val'])\n                    tentry_sec = int(float(dfile[self.archive.header['twindow_type']]))\n\n                    if tentry_sec < twindow_sec:\n                        continue\n\n            # Search for the recovery checkpoint\n            if err_entry and not found_checkpoint:\n                if fentry != err_entry:\n                    continue\n                else:\n                    found_checkpoint = True\n\n            __, surl = self.archive.get_endpoints(fentry[1])\n            url = client.URL(surl)\n            dict_meta = dict(zip(self.archive.header['file_meta'], fentry[2:]))\n\n            # Send the chown async request\n            arg = ''.join([url.path, \"?eos.ruid=0&eos.rgid=0&mgm.pcmd=chown&uid=\",\n                           dict_meta['uid'], \"&gid=\", dict_meta['gid']])\n            xrd_st = fs.query(QueryCode.OPAQUEFILE, arg,\n                              callback=metahandler.register(oper, surl))\n\n            if not xrd_st.ok:\n                __ = metahandler.wait(oper)\n                err_msg = \"Failed query chown for path={0}\".format(surl)\n                self.logger.error(err_msg)\n                raise IOError(err_msg)\n\n            # Send the chmod async request\n            mode = int(dict_meta['mode'], 8) # mode is saved in octal format\n            arg = ''.join([url.path, \"?eos.ruid=0&eos.rgid=0&mgm.pcmd=chmod&mode=\",\n                           str(mode)])\n            xrd_st = fs.query(QueryCode.OPAQUEFILE, arg,\n                              callback=metahandler.register(oper, surl))\n\n            if not xrd_st.ok:\n                __ = metahandler.wait(oper)\n                err_msg = \"Failed query chmod for path={0}\".format(surl)\n                self.logger.error(err_msg)\n                raise IOError(err_msg)\n\n            # Send the utime async request to set the mtime\n            mtime = dict_meta['mtime']\n            mtime_sec, mtime_nsec = mtime.split('.', 1)\n            ctime = dict_meta['ctime']\n            ctime_sec, ctime_nsec = ctime.split('.', 1)\n            arg = ''.join([url.path, \"?eos.ruid=0&eos.rgid=0&mgm.pcmd=utimes\",\n                           \"&tv1_sec=\", ctime_sec, \"&tv1_nsec=\", ctime_nsec,\n                           \"&tv2_sec=\", mtime_sec, \"&tv2_nsec=\", mtime_nsec])\n            xrd_st = fs.query(QueryCode.OPAQUEFILE, arg,\n                              callback=metahandler.register(oper, surl))\n\n            if not xrd_st.ok:\n                __ = metahandler.wait(oper)\n                err_msg = \"Failed query utimes for path={0}\".format(surl)\n                self.logger.error(err_msg)\n                raise IOError(err_msg)\n\n        status = metahandler.wait(oper)\n\n        if status:\n            t1 = time.time()\n            self.logger.info(\"TIMING_update_file_access={0} sec\".format(t1 - t0))\n        else:\n            err_msg = \"Failed update file access\"\n            self.logger.error(err_msg)\n            raise IOError(err_msg)\n\n    def check_previous_tx(self):\n        \"\"\" Find checkpoint for a previous run. There are two types of checks\n        being done:\n        - transfer check = verify that the files exist and have the correct\n                           size and checksum\n        - metadata check = verify that all the entries have the correct meta-\n                           data values set\n\n        Returns:\n           (tx_ok, meta_ok, lst_failed): Tuple holding the status of the\n           different checks and the list of corrupted entries.\n        \"\"\"\n        msg = \"verify last run\"\n        self.set_status(msg)\n        meta_ok = False\n        # Check for existence, file size and checksum\n        tx_ok, lst_failed = self.archive.verify(False, True)\n\n        if tx_ok:\n            meta_ok, lst_failed = self.archive.verify(False, False)\n\n            if meta_ok:\n                self.do_retry = False\n                raise NoErrorException()\n\n        # Delete the corrupted entry if this is a real transfer error\n        if not tx_ok:\n            err_entry = lst_failed[0]\n            is_dir = (err_entry[0] == 'd')\n            self.logger.info(\"Delete corrupted entry={0}\".format(err_entry))\n\n            if is_dir:\n                self.archive.del_subtree(err_entry[1], None)\n            else:\n                self.archive.del_entry(err_entry[1], False, None)\n\n        return (tx_ok, meta_ok, lst_failed)\n\n\n    def prepare2get(self, err_entry=None, found_checkpoint=False):\n        \"\"\"This method is only executed for GET operations and its purpose is\n        to issue the Prepapre2Get commands for the files in the archive which\n        will later on be copied back to EOS.\n\n        Args:\n            err_entry (list): Entry record from the archive file corresponding\n                 to the first file/dir that was corrupted.\n            found_checkpoint (bool): If True it means the checkpoint was\n                 already found and we don't need to search for it.\n\n        Raises:\n            IOError: The Prepare2Get request failed.\n        \"\"\"\n        if self.archive.d2t:\n            return\n\n        count = 0\n        limit = 50  # max files per prepare request\n        oper = 'prepare'\n        self.set_status(\"prepare2get\")\n        t0 = time.time()\n        lpaths = []\n        status = True\n        metahandler = MetaHandler()\n\n        for fentry in self.archive.files():\n            # Find error checkpoint if not already found\n            if err_entry and not found_checkpoint:\n                if fentry != err_entry:\n                    continue\n                else:\n                    found_checkpoint = True\n\n            count += 1\n            surl, __ = self.archive.get_endpoints(fentry[1])\n            lpaths.append(surl[surl.rfind('//') + 1:])\n\n            if len(lpaths) == limit:\n                xrd_st = self.archive.fs_dst.prepare(lpaths, PrepareFlags.STAGE,\n                                                     callback=metahandler.register(oper, surl))\n\n                if not xrd_st.ok:\n                    __ = metahandler.wait(oper)\n                    err_msg = \"Failed prepare2get for path={0}\".format(surl)\n                    self.logger.error(err_msg)\n                    raise IOError(err_msg)\n\n                # Wait for batch to be executed\n                del lpaths[:]\n                status = status and metahandler.wait(oper)\n                self.logger.debug((\"Prepare2get done count={0}/{1}\"\n                                   \"\").format(count, self.archive.header['num_files']))\n\n                if not status:\n                    break\n\n        # Send the remaining requests\n        if lpaths and status:\n            xrd_st = self.archive.fs_dst.prepare(lpaths, PrepareFlags.STAGE,\n                                                 callback=metahandler.register(oper, surl))\n\n            if not xrd_st.ok:\n                __ = metahandler.wait(oper)\n                err_msg = \"Failed prepare2get\"\n                self.logger.error(err_msg)\n                raise IOError(err_msg)\n\n            # Wait for batch to be executed\n            del lpaths[:]\n            status = status and metahandler.wait(oper)\n\n        if status:\n            t1 = time.time()\n            self.logger.info(\"TIMING_prepare2get={0} sec\".format(t1 - t0))\n        else:\n            err_msg = \"Failed prepare2get\"\n            self.logger.error(err_msg)\n            raise IOError(err_msg)\n\n        # Wait for all the files to be on disk\n        for fentry in self.archive.files():\n            surl, __ = self.archive.get_endpoints(fentry[1])\n            url = client.URL(surl)\n\n            while True:\n                st_stat, resp_stat = self.archive.fs_dst.stat(url.path)\n\n                if not st_stat.ok:\n                    err_msg = \"Error stat entry={0}\".format(surl)\n                    self.logger.error(err_msg)\n                    raise IOError()\n\n                # Check if file is on disk\n                if resp_stat.flags & StatInfoFlags.OFFLINE:\n                    self.logger.info(\"Sleep 5 seconds, file not on disk entry={0}\".format(surl))\n                    sleep(5)\n                else:\n                    break\n\n        self.logger.info(\"Finished prepare2get, all files are on disk\")\n\n\n    def evict_disk_cache(self):\n        \"\"\" Send a prepare eviect request to the CTA so that the files are\n        removed from the disk cached of the tape system.\n        \"\"\"\n        batch_size = 100\n        timeout = 10\n        batch = []\n        # @todo(esindril) use the XRootD proived flag once this is\n        # available in the Python interface\n        xrd_prepare_evict_flag = 0x000100000000\n\n        for fentry in self.archive.files():\n            __, dst = self.archive.get_endpoints(fentry[1])\n            url = client.URL(dst)\n            batch.append(url.path)\n\n            if len(batch) == batch_size:\n                fs = self.archive.get_fs(dst)\n                prep_stat, __ = fs.prepare(batch, xrd_prepare_evict_flag, 0, timeout)\n                batch.clear()\n\n                if not prep_stat.ok:\n                    self.logger.warning(\"Failed prepare evit for batch\")\n\n        if len(batch) != 0:\n            fs = self.archive.get_fs(dst)\n            prep_stat, __ = fs.prepare(batch, xrd_prepare_evict_flag, 0, timeout)\n            batch.clear()\n\n            if not prep_stat.ok:\n                self.logger.warning(\"Failed prepare evit for batch\")\n\n        self.logger.info(\"Finished sending all the prepare evict requests\")\n\n\n    def wait_on_tape(self):\n        \"\"\" Check and wait that all the files are on tape, which in our case\n        means checking the \"m\" bit. If a file is not on tape then suspend the\n        current thread for a period of 5 to 60 seconds but abort if the file\n        fails to be archived on tape afte 24h\n        \"\"\"\n        max_timeout_per_entry = int(self.config.ARCHIVE_MAX_TIMEOUT)\n        min_timeout, max_timeout = 5, 60\n\n        for fentry in self.archive.files():\n            start_ts = time.time()\n            __, dst = self.archive.get_endpoints(fentry[1])\n            url = client.URL(dst)\n            file_on_tape = False\n\n            while not file_on_tape:\n                st_stat, resp_stat = self.archive.fs_dst.stat(url.path)\n\n                if not st_stat.ok:\n                    err_msg = \"Error stat entry={0}\".format(dst)\n                    self.logger.error(err_msg)\n                    raise IOError()\n\n                # Check file is on tape\n                if resp_stat.size != 0 and not (resp_stat.flags & StatInfoFlags.BACKUP_EXISTS):\n                    self.logger.debug(\"File {0} is not yet on tape\".format(dst))\n                    timeout = randrange(min_timeout, max_timeout)\n                    self.logger.info(\"Going to sleep for {0} seconds\".format(timeout))\n                    sleep(timeout)\n\n                    if time.time() - start_ts > max_timeout_per_entry:\n                        self.logger.notice(\"Entry not archived within the maximum timeout.\"\n                                           \" entry={0} archive_max_timeout={1}\".format(\n                                               fentry[1], max_timeout_per_entry))\n                        break\n                    else:\n                        file_on_tape = True\n                else:\n                    file_on_tape = True\n\n            if not file_on_tape:\n                # Throw exception to re-try the failed transfer\n                raise NotOnTapeException()\n\n\n    def backup_prepare(self):\n        \"\"\" Prepare requested backup operation.\n\n        Raises:\n            IOError: Failed to transfer backup file.\n        \"\"\"\n        # Copy backup file from EOS to the local disk\n        self.logger.info((\"Prepare backup copy from {0} to {1}\"\n                          \"\").format(self.efile_full, self.tx_file))\n        eos_fs = client.FileSystem(self.efile_full)\n        st, _ = eos_fs.copy((self.efile_full + \"?eos.ruid=0&eos.rgid=0\"),\n                            self.tx_file, True)\n\n        if not st.ok:\n            err_msg = (\"Failed to copy backup file={0} to local disk at={1} err_msg={2}\"\n                       \"\").format(self.efile_full, self.tx_file, st.message)\n            self.logger.error(err_msg)\n            raise IOError(err_msg)\n\n        # Create the ArchiveFile object for the backup which is similar to a\n        # tape to disk transfer\n        self.archive = ArchiveFile(self.tx_file, False)\n\n        # Check that the destination directory exists and has mode 777, if\n        # forced then skip checks\n        if not self.force:\n            surl = self.archive.header['dst']\n            url = client.URL(surl)\n            fs = self.archive.get_fs(surl)\n            st_stat, resp_stat = fs.stat((url.path, + \"?eos.ruid=0&eos.rgid=0\"))\n\n            if st_stat.ok:\n                err_msg = (\"Failed to stat backup destination url={0}\"\n                           \"\").format(surl)\n                self.logger.error(err_msg)\n                raise IOError(err_msg)\n\n            if resp_stat.flags != (client.StatInfoFlags.IS_READABLE |\n                                   client.StatInfoFlags.IS_WRITABLE):\n                err_msg = (\"Backup destination url={0} must have move 777\").format(surl)\n                self.logger.error(err_msg)\n                raise IOError(err_msg)\n\n\n    def do_backup(self):\n        \"\"\" Perform a backup operation using the provided backup file.\n        \"\"\"\n        t0 = time.time()\n        indx_dir = 0\n\n        # Root owns the .sys.b#.backup.file\n        fs = client.FileSystem(self.efile_full)\n        efile_url = client.URL(self.efile_full)\n        arg = ''.join([efile_url.path, \"?eos.ruid=0&eos.rgid=0&mgm.pcmd=chown&uid=0&gid=0\"])\n        xrd_st, __ = fs.query(QueryCode.OPAQUEFILE, arg)\n\n        if not xrd_st.ok:\n            err_msg = \"Failed setting ownership of the backup file: {0}\".format(self.efile_full)\n            self.logger.error(err_msg)\n            raise IOError(err_msg)\n\n        # Create directories\n        for dentry in self.archive.dirs():\n            # Do special checks for root directory\n            #if dentry[1] == \"./\":\n            #    self.archive.check_root_dir()\n\n            indx_dir += 1\n            self.archive.mkdir(dentry)\n            msg = \"create dir {0}/{1}\".format(indx_dir, self.archive.header['num_dirs'])\n            self.set_status(msg)\n\n        # Copy files and set metadata information\n        self.copy_files()\n        self.update_file_access()\n\n        self.set_status(\"verifying\")\n        check_ok, lst_failed = self.archive.verify(True)\n        self.backup_write_status(lst_failed, check_ok)\n\n        self.set_status(\"cleaning\")\n        self.logger.info(\"TIMING_transfer={0} sec\".format(time.time() - t0))\n        self.backup_tx_clean()\n\n    def backup_write_status(self, lst_failed, check_ok):\n        \"\"\" Create backup status file which constains the list of failed files\n            to transfer.\n\n            Args:\n               lst_filed (list): List of failed file transfers\n               check_ok (boolean): True if verification successful, otherwise\n                   false\n        \"\"\"\n        if not check_ok:\n            self.logger.error(\"Failed verification for {0} entries\".format(len(lst_failed)))\n            fn_status = ''.join([self.efile_root, \".sys.b#.backup.err.\", str(len(lst_failed)),\n                                 \"?eos.ruid=0&eos.rgid=0\"])\n        else:\n            self.logger.info(\"Backup successful - no errors detected\")\n            fn_status = ''.join([self.efile_root, \".sys.b#.backup.done?eos.ruid=0&eos.rgid=0\"])\n\n        with client.File() as f:\n            f.open(fn_status, OpenFlags.UPDATE | OpenFlags.DELETE)\n            offset = 0\n\n            for entry in lst_failed:\n                buff = \"Failed entry={0}\\n\".format(entry)\n                f.write(buff, offset, len(buff))\n                offset += len(buff)\n"
  },
  {
    "path": "archive/eosarch/utils.py",
    "content": "# ------------------------------------------------------------------------------\n# File: utils.py\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ------------------------------------------------------------------------------\n#\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2014 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\"\"\"Module containing helper function for the EOS archiver daemon.\"\"\"\n\nfrom __future__ import unicode_literals\nimport logging\nfrom XRootD import client\nfrom XRootD.client.flags import OpenFlags\n\nlogger = logging.getLogger(\"transfer\")\n\ndef seal_path(path, seal_dict={'&': \"#AND#\"}):\n    \"\"\" Seal a path by replacing the key characters in the dictionary with their\n    values so that EOS is happy.\n\n    Args:\n        path (str): Path to be sealed\n        seal (dict): Seal dictionary\n\n    Returns:\n        The path transformed using the dictionary mapping.\n    \"\"\"\n    for key, val in seal_dict.items():\n        path = path.replace(key, val)\n\n    return path\n\n\ndef unseal_path(path, seal_dict={\"#AND#\": '&'}):\n    \"\"\" Unseal a path by replacing the key characters in the dictionary with their\n    values so that we are happy.\n\n    Args:\n        path (str): Path to be unsealed\n        seal (dict): Unseal dictionary\n\n    Returns:\n        The path transformed using the dictionary mapping.\n    \"\"\"\n    for key, val in seal_dict.items():\n        path = path.replace(key, val)\n\n    return path\n\ndef is_version_file(path):\n    \"\"\" Check if this is a version file i.e. contains the following prefix:\n    \".sys.v#\"\n\n    Args:\n        path (string): Relative path\n\n    Returns:\n        True if this is a version file, otherwise false.\n    \"\"\"\n    return path.startswith(\".sys.v#.\") or \"/.sys.v#.\" in path\n\ndef is_atomic_version_file(path):\n    \"\"\" Check if this is a version file i.e. contains the following prefix:\n    \".sys.a#.v#\"\n\n    Args:\n        path (string): Relative path\n\n    Returns:\n        True if this is an atomic version file, otherwise false.\n    \"\"\"\n    return path.startswith(\".sys.a#.v#.\") or \"/.sys.a#.v#.\" in path\n\ndef exec_cmd(cmd):\n    \"\"\" Execute an EOS /proc/user/ command.\n\n    Args:\n      cmd (str): Command to execute.\n\n    Returns:\n      Tuple containing the following elements: (status, stdout, stderr). Status\n      is a boolean value while the rest are string. If data needs to be returned\n      then it's put in stdout and any error messages are in stderr.\n    \"\"\"\n    logger.debug(\"Execute: {0}\".format(cmd))\n    status, retc, stdout, stderr = False, \"0\", \"\", \"\"\n\n    # Execute the command as root if role not already set\n    if cmd.find(\"eos.ruid=\") == -1:\n        if cmd.find('?') == -1:\n            cmd += \"?eos.ruid=0&eos.rgid=0\"\n        else:\n            cmd += \"&eos.ruid=0&eos.rgid=0\"\n\n    with client.File() as f:\n        st, __ = f.open(cmd, OpenFlags.READ)\n\n        if st.ok:\n            # Read the whole response\n            data = \"\"\n            off, sz = 0, 4096\n            st, chunk = f.read(off, sz)\n\n            if st.ok:\n                while st.ok and len(chunk):\n                    off += len(chunk)\n                    try:\n                        data += chunk.decode(\"utf-8\")\n                    except:\n                        print(\"EHEHEHEH not able to decode str... only bytes\")\n                    st, chunk = f.read(off, sz)\n\n                lpairs = data.split('&')\n                for elem in lpairs:\n                    if \"mgm.proc.retc=\" in elem:\n                        retc = elem[(elem.index('=') + 1):].strip()\n                        status = True if (retc == \"0\") else False\n                    elif \"mgm.proc.stdout=\" in elem:\n                        stdout = elem[(elem.index('=') + 1):].strip()\n                        stdout = unseal_path(stdout)\n                    elif \"mgm.proc.stderr=\" in elem:\n                        stderr = elem[(elem.index('=') + 1):].strip()\n                        stderr = unseal_path(stderr)\n            else:\n                stderr = \"error reading response for command: {0}\".format(cmd)\n        else:\n            stderr = \"error sending command: {0}\".format(cmd)\n\n    # logger.debug(\"Return command: {0}\".format((status, stdout, stderr)))\n    return (status, stdout, stderr)\n\n\ndef get_entry_info(url, rel_path, tags, is_dir):\n    \"\"\" Get file/directory metadata information from EOS.\n\n    Args:\n        url  (XRootD.URL): Full URL to EOS location.\n        rel_path (str): Entry's relative path as saved in the archive file.\n        tags (list): List of tags to look for in the fileinfo result.\n        is_dir (bool): If True entry is a directory, otherwise a file.\n\n    Returns:\n        A list containing the info corresponding to the tags supplied in\n        the args.\n\n    Raises:\n        IOError: Fileinfo request can not be submitted.\n        AttributeError: Not all expected tags are provided.\n        KeyError: Extended attribute value is not present.\n    \"\"\"\n    dinfo = []\n    finfo = ''.join([url.protocol, \"://\", url.hostid, \"//proc/user/?\",\n                     \"mgm.cmd=fileinfo&mgm.path=\", seal_path(url.path),\n                     \"&mgm.file.info.option=-m\"])\n    (status, stdout, stderr) = exec_cmd(finfo)\n\n    if not status:\n        err_msg = (\"Path={0} failed fileinfo request, msg={1}\").format(\n            url.path, stderr)\n        logger.error(err_msg)\n        raise IOError(err_msg)\n\n    # Extract the path by using the keylength.file value which represents the\n    # size of the path. This is because the path can contain spaces.\n    size_pair, file_pair, tail = stdout.split(' ', 2)\n    sz_key, sz_val = size_pair.split('=', 1)\n    file_key, file_val = file_pair.split('=', 1)\n\n    if sz_key == \"keylength.file\" and file_key == \"file\" :\n        path = file_val\n        path_size = int(sz_val)\n\n        while path_size > len(path.encode(\"utf-8\")):\n            path_token, tail = tail.split(' ', 1)\n            path += ' '\n            path += path_token\n    else:\n        err_msg = (\"Fileinfo response does not start with keylength.file \"\n                   \"for path\").format(url.path)\n        logger.error(err_msg)\n        raise IOError(err_msg)\n\n    # For the rest we don't expect any surprizes, they shoud be key=val pairs\n    lpairs = tail.split(' ')\n    it_list = iter(lpairs)\n    dict_info, dict_attr = {}, {}\n\n    # Parse output of fileinfo -m keeping only the required keys\n    for elem in it_list:\n        if '=' not in elem:\n            continue\n\n        key, value = elem.split('=', 1)\n\n        if len(value) == 0:\n            continue\n\n        if key in tags:\n            dict_info[key] = value\n        elif key == \"xattrn\" and is_dir:\n            xkey, xval = next(it_list).split('=', 1)\n\n            if xkey != \"xattrv\":\n                err_msg = (\"Dir={0} no value for xattrn={1}\").format(\n                    url.path, value)\n                logger.error(err_msg)\n                raise KeyError(err_msg)\n            else:\n                dict_attr[value] = xval\n\n        # For directories add also the xattr dictionary\n        if is_dir and \"attr\" in tags:\n            dict_info[\"attr\"] = dict_attr\n\n    if len(dict_info) == len(tags):\n        # Dirs  must end with '/' just as the output of EOS fileinfo -d\n        tentry = 'd' if is_dir else 'f'\n        dinfo.extend([tentry, rel_path])\n\n        for tag in tags:\n            dinfo.append(dict_info[tag])\n    else:\n        err_msg = (\"Path={0}, not all expected tags found\").format(url.path)\n        logger.error(err_msg)\n        raise AttributeError(err_msg)\n\n    return dinfo\n\ndef set_dir_info(surl, dict_dinfo, excl_xattr):\n    \"\"\" Set directory metadata information in EOS.\n\n    Args:\n        surl (string): Full URL of directory\n        dict_dinfo (dict): Dictionary containsing meta-data information\n        excl_xattr (list): List of excluded extended attributes\n\n    Raises:\n        IOError: Metadata operation failed.\n    \"\"\"\n    url = client.URL(surl)\n\n    # Change ownership of the directory\n    fsetowner = ''.join([url.protocol, \"://\", url.hostid, \"//proc/user/?\",\n                         \"mgm.cmd=chown&mgm.path=\", seal_path(url.path),\n                         \"&mgm.chown.owner=\", dict_dinfo['uid'], \":\",\n                         dict_dinfo['gid']])\n    (status, stdout, stderr) = exec_cmd(fsetowner)\n\n    if not status:\n        err_msg = \"Dir={0}, error doing chown, msg={1}\".format(url.path, stderr)\n        logger.error(err_msg)\n        raise IOError(err_msg)\n\n    # Set permission on the directory\n    fchmod = ''.join([url.protocol, \"://\", url.hostid, \"//proc/user/?\",\n                      \"mgm.cmd=chmod&mgm.path=\", seal_path(url.path),\n                      \"&mgm.chmod.mode=\", dict_dinfo['mode']])\n    (status, stdout, stderr) = exec_cmd(fchmod)\n\n    if not status:\n        err_msg = \"Dir={0}, error doing chmod, msg={1}\".format(url.path, stderr)\n        logger.error(err_msg)\n        raise IOError(err_msg)\n\n    # Deal with extended attributes. If all are excluded then don't touch them.\n    if \"*\" in excl_xattr:\n        return\n\n    # Get all the current xattrs\n    flsattr = ''.join([url.protocol, \"://\", url.hostid, \"//proc/user/?\",\n                       \"mgm.cmd=attr&mgm.subcmd=ls&mgm.path=\", seal_path(url.path)])\n\n    (status, stdout, stderr) = exec_cmd(flsattr)\n\n    if not status:\n        err_msg = \"Dir={0}, error listing xattrs, msg ={1}\".format(\n            url.path, stderr)\n        logger.error(err_msg)\n        raise IOError(err_msg)\n\n    lattrs = [s.split('=', 1)[0] for s in stdout.splitlines()]\n\n    for attr in lattrs:\n        # Don't remove the excluded xattrs\n        if attr in excl_xattr:\n            continue\n\n        frmattr = ''.join([url.protocol, \"://\", url.hostid, \"//proc/user/?\",\n                           \"mgm.cmd=attr&mgm.subcmd=rm&mgm.attr.key=\", attr,\n                           \"&mgm.path=\", seal_path(url.path)])\n        (status, __, stderr) = exec_cmd(frmattr)\n\n        if not status:\n            err_msg = (\"Dir={0} error while removing attr={1}, msg={2}\"\n                       \"\").format(url.path, attr, stderr)\n            logger.error(err_msg)\n            raise IOError(err_msg)\n\n    # Set the expected extended attributes\n    dict_dattr = dict_dinfo['attr']\n\n    for key, val in dict_dattr.items():\n        # Don't set the excluded xattrs\n        if key in excl_xattr:\n            continue\n\n        if len(val) == 0:\n            continue\n\n        fsetattr = ''.join([url.protocol, \"://\", url.hostid, \"//proc/user/?\",\n                            \"mgm.cmd=attr&mgm.subcmd=set&mgm.attr.key=\", key,\n                            \"&mgm.attr.value=\", val, \"&mgm.path=\", seal_path(url.path)])\n        (status, __, stderr) = exec_cmd(fsetattr)\n\n        if not status:\n            err_msg = \"Dir={0}, error setting attr={1}, msg={2}\".format(\n                url.path, key, stderr)\n            logger.error(err_msg)\n            raise IOError(err_msg)\n"
  },
  {
    "path": "archive/eosarch_reconstruct.py",
    "content": "#!/usr/bin/python3\n# ------------------------------------------------------------------------------\n# File: eosarch_reconstruct.py\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ------------------------------------------------------------------------------\n#\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2014 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\"\"\" This tool can be used to reconstruct an archive file starting from the data\nwhich is actually saved on tape (CASTOR). The tape systems needs to have an\nXRootD interface. The way it works it that the archive file is constructed\nlocally and then is uploaded to the specified EOS root directory which must not\nexist previously. The archive file is copied to EOS using the filename\n.archive.purge.done so that the user is then able to get the data from the\ntape system back into EOS. The UID which is provided when launching the command\nis given permission to execute archive operations on the corresponding EOS\ndirectory.\n\"\"\"\n\nfrom __future__ import print_function\nimport sys\nimport os\nimport ast\nimport errno\nimport stat\nimport time\nimport argparse\nimport tempfile\nfrom eosarch.utils import exec_cmd, set_dir_info\n\ntry:\n    from XRootD import client\n    from XRootD.client.flags import DirListFlags, StatInfoFlags, QueryCode\nexcept ImportError as ierr:\n    print(\"Missing xrootd-python package\", file=sys.stderr)\n\n\nclass EosAccessException(Exception):\n    \"\"\" Exception raised when the current user does not have full sudo rights\n    EOS to perform the necessary operation for the archiving reconstruct.\n    \"\"\"\n    pass\n\nclass TapeAccessException(Exception):\n    \"\"\" Exception raised when the current user can not access information from\n    the tape system.\n    \"\"\"\n    pass\n\n\nclass ArchReconstruct(object):\n    \"\"\" Class responsible for reconstructing the archive file from an already\n    directory subtree from tape.\n    \"\"\"\n    def __init__(self, surl, durl, args):\n        \"\"\" Initialize the ArchReconstruct object\n\n        Args:\n            surl (XRootD.URL): URL to tape backend (CASTOR)\n            durl (XRootD.URL): URL to disk destination (EOS)\n            args  (Namespace): Namespace object containing at least the following\n            attributes: uid (string): UID of archive owner in numeric format\n                        gid (string): GID of archive owner in numeric format\n                        svc_class (string): Service class used for retrieving the\n                            archived data\n                        skip_no_xs (bool): Skip files that don't have a checksum\n        \"\"\"\n        self.src_url = surl\n        self.dst_url = durl\n        self.uid, self.gid = args.uid, args.gid\n        self.svc_class = args.svc_class\n        self.skip_no_xs = args.skip_no_xs\n        self.ffiles = tempfile.TemporaryFile(mode='w+')\n        self.fdirs = tempfile.TemporaryFile(mode='w+')\n        self.farchive = tempfile.NamedTemporaryFile(mode='w+', delete=False)\n        print(\"Temp. archive file saved in: {0}\".format(self.farchive.name),\n              file=sys.stdout)\n\n    def __del__(self):\n        \"\"\" Destructor - make sure we close the temporary files\n        \"\"\"\n        self.ffiles.close()\n        self.fdirs.close()\n        self.farchive.close()\n\n    def breadth_first(self):\n        \"\"\" Traverse the filesystem subtree using breadth-first search and\n        collect the directory information and file information into separate\n        files which will be merged in the end.\n        \"\"\"\n        # Dir format: type, rel_path, uid, gid, mode, attr\n        dir_meta = \"[\\\"uid\\\", \\\"gid\\\", \\\"mode\\\", \\\"attr\\\"]\"\n        dir_format = \"[\\\"d\\\", \\\"{0}\\\", \\\"{1}\\\", \\\"{2}\\\", \\\"{3}\\\", {4}]\"\n        # File format: type, rel_path, size, mtime, ctime, uid, gid, mode, xstype, xs\n        # Fake mtime and ctime subsecond precision\n        file_meta = (\"[\\\"size\\\", \\\"mtime\\\", \\\"ctime\\\", \\\"uid\\\", \\\"gid\\\", \\\"mode\\\", \"\n                     \"\\\"xstype\\\", \\\"xs\\\"]\")\n        file_format = (\"[\\\"f\\\", \\\"{0}\\\", \\\"{1}\\\", \\\"{2}.0\\\", \\\"{3}.0\\\", \\\"{4}\\\", \"\n                       \"\\\"{5}\\\", \\\"{6}\\\", \\\"{7}\\\", \\\"{8}\\\"]\")\n        # Attrs for 2 replica layout in EOS with current user the only one\n        # allowed to trigger archiving operations\n        replica_attr = (\"{{\\\"sys.acl\\\": \\\"u:{0}:a,z:i\\\", \"\n                        \"\\\"sys.forced.blockchecksum\\\": \\\"crc32c\\\", \"\n                        \"\\\"sys.forced.blocksize\\\": \\\"4k\\\", \"\n                        \"\\\"sys.forced.checksum\\\": \\\"adler\\\", \"\n                        \"\\\"sys.forced.layout\\\": \\\"replica\\\", \"\n                        \"\\\"sys.forced.nstripes\\\": \\\"2\\\", \"\n                        \"\\\"sys.forced.space\\\": \\\"default\\\"}}\").format(self.uid)\n        num_files, num_dirs = 0, 0\n        fs = client.FileSystem(str(self.src_url))\n\n        # Add root directory which is a bit special and set its metadata\n        # Dir mode is 42755 and file mode is 0644\n        dir_mode = oct(stat.S_IFDIR | stat.S_ISGID | stat.S_IRWXU\n                   | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)\n        dir_mode = dir_mode[1:] # remove leading 0 used for octal format\n        file_mode = oct(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)\n        print(dir_format.format(\"./\", self.uid, self.gid, dir_mode,\n                                replica_attr), file=self.fdirs)\n        dict_attr = ast.literal_eval(replica_attr)\n        dict_dinfo = dict(zip([\"uid\", \"gid\", \"mode\", \"attr\"],\n                              [self.uid, self.gid, dir_mode, dict_attr]))\n        set_dir_info(str(self.dst_url), dict_dinfo, list())\n        root_path = self.src_url.path\n        lst_dirs = [root_path]\n\n        while lst_dirs:\n            path = lst_dirs.pop(0)\n            st, listing = fs.dirlist(path, DirListFlags.STAT)\n\n            if not st.ok:\n                msg = \"Failed to list dir={0}\".format(self.src_url.path)\n                raise TapeAccessException(msg)\n\n            for elem in listing:\n                if elem.name == \".archive.init\":\n                    msg = (\"Trying to reconstruct an already existing archive \"\n                           \"in directory: {0}\").format(path)\n                    raise TapeAccessException(msg)\n\n                if elem.statinfo.flags & StatInfoFlags.IS_DIR:\n                    num_dirs += 1\n                    full_path = ''.join([path, elem.name, '/'])\n                    rel_path = full_path.replace(root_path, \"\")\n                    lst_dirs.append(full_path)\n                    print(dir_format.format(rel_path, self.uid, self.gid, dir_mode,\n                                            replica_attr), file=self.fdirs)\n                else:\n                    full_path = ''.join([path, elem.name])\n                    rel_path = full_path.replace(root_path, \"\")\n                    st, xs_resp = fs.query(QueryCode.CHECKSUM, full_path)\n\n                    if not st.ok:\n                        # If requested then skip the files that don't have a checksum\n                        if self.skip_no_xs:\n                            continue\n\n                        msg = \"File={0} failed xs query\".format(full_path)\n                        raise TapeAccessException(msg)\n\n                    num_files += 1\n                    # Result has an annoying \\x00 character at the end and it\n                    # contains the xs type (adler32) and the xs value\n                    resp = xs_resp.strip('\\x00\\0\\n ').split()\n\n                    # If checksum value is not 8 char long then we need padding\n                    if len(resp[1]) != 8 :\n                        resp[1] = \"{0:0>8}\".format(resp[1])\n\n                    if resp[0] != \"adler32\":\n                        msg = (\"Unknown checksum type={0} from tape system\"\n                               \"\".format(resp[0]))\n                        raise TapeAccessException(msg)\n\n                    print(file_format.format(rel_path, elem.statinfo.size,\n                                             elem.statinfo.modtime,\n                                             elem.statinfo.modtime,\n                                             self.uid, self.gid, file_mode,\n                                             \"adler\", resp[1]),\n                          file=self.ffiles)\n\n        # Write archive file header\n        header_format = (\"{{\\\"src\\\": \\\"{0}\\\", \"\n                         \"\\\"dst\\\": \\\"{1}\\\", \"\n                         \"\\\"svc_class\\\": \\\"{2}\\\", \"\n                         \"\\\"dir_meta\\\": {3}, \"\n                         \"\\\"file_meta\\\": {4}, \"\n                         \"\\\"num_dirs\\\": {5}, \"\n                         \"\\\"num_files\\\": {6}, \"\n                         \"\\\"uid\\\": \\\"{7}\\\", \"\n                         \"\\\"gid\\\": \\\"{8}\\\", \"\n                         \"\\\"timestamp\\\": \\\"{9}\\\"}}\")\n        print(header_format.format(str(self.dst_url), str(self.src_url),\n                                   self.svc_class, dir_meta, file_meta,\n                                   num_dirs, num_files, self.uid,\n                                   self.gid, time.time()),\n              file=self.farchive, end=\"\\n\")\n        # Rewind to the begining of the tmp files\n        self.fdirs.seek(0)\n        self.ffiles.seek(0)\n\n        # Write directories\n        for line in self.fdirs:\n            print(line, file=self.farchive, end=\"\")\n\n        # Write files\n        for line in self.ffiles:\n            print(line, file=self.farchive, end=\"\")\n\n        self.farchive.close()\n\n    def upload_archive(self):\n        \"\"\" Upload archive file to EOS directory. Note that we save it the the\n        name .archive.purge since this is the only possible operation when we\n        do such a reconstruct.\n        \"\"\"\n        cp = client.CopyProcess()\n        dst = ''.join([str(self.dst_url), \".archive.purge.done?eos.ruid=0&eos.rgid=0\"])\n        cp.add_job(self.farchive.name, dst, force=True)\n        status = cp.prepare()\n\n        if not status.ok:\n            msg = \"Failed while preparing to upload archive file to EOS\"\n            raise EosAccessException(msg)\n\n        status = cp.run()\n\n        if not status.ok:\n            msg = \"Failed while copying the archive file to EOS\"\n            raise EosAccessException(msg)\n        else:\n            # Delete local archive file\n            try:\n                os.remove(self.farchive.name)\n            except OSError as __:\n                pass\n\ndef check_eos_access(url):\n    \"\"\" Check that the current user executing the programm is mapped as root in\n    EOS otherwise he will not be able to set all the necessary attributes for\n    the newly built archive. Make sure also that the root destination does not\n    exist already.\n\n    Args:\n       url (XRootD.URL): EOS URL to the destination path\n\n    Raises:\n       EosAccessException\n    \"\"\"\n    fwhoami = ''.join([url.protocol, \"://\", url.hostid, \"//proc/user/?mgm.cmd=whoami\"])\n    (status, out, __) = exec_cmd(fwhoami)\n\n    if not status:\n        msg = \"Failed to execute EOS whoami command\"\n        raise EosAccessException(msg)\n\n    # Extrach the uid and gid from the response\n    out.strip(\"\\0\\n \")\n    lst = out.split(' ')\n\n    try:\n        for token in lst:\n            if token.startswith(\"uid=\"):\n                uid = int(token[4:])\n            elif token.startswith(\"gid=\"):\n                gid = int(token[4:])\n    except ValueError as __:\n        msg = \"Failed while parsing uid/gid response to EOS whoami command\"\n        raise EosAccessException(msg)\n\n    if uid != 0 or gid != 0:\n        msg = \"User {0} does not have full rights in EOS - aborting\".format(os.getuid())\n        raise EosAccessException(msg)\n\n    # Check that root directory does not exist already\n    fs = client.FileSystem(str(url))\n    st, __ = fs.stat(url.path)\n\n    if st.ok:\n        msg = \"EOS root directory already exists\"\n        raise EosAccessException(msg)\n\n    fmkdir = ''.join([url.protocol, \"://\", url.hostid, \"//proc/user/?mgm.cmd=mkdir&\"\n                      \"mgm.path=\", url.path])\n    (status, __, __) = exec_cmd(fmkdir)\n\n    if not status:\n        msg = \"Failed to create EOS directory: {0}\".format(url.path)\n        raise EosAccessException(msg)\n\n\ndef main():\n    \"\"\" Main function \"\"\"\n    parser = argparse.ArgumentParser(description=\"Tool used to create an archive \"\n                                     \"file from an already existing archvie such \"\n                                     \"that the recall of the files can be done \"\n                                     \"using the EOS archiving tool. The files are \"\n                                     \"copied back to EOS using the 2replica layout.\")\n    parser.add_argument(\"-s\", \"--src\", required=True,\n                        help=\"XRootD URL to archive tape source (CASTOR location)\")\n    parser.add_argument(\"-d\", \"--dst\", required=True,\n                        help=\"XRootD URL to archive disk destination (EOS location)\")\n    parser.add_argument(\"-c\", \"--svc_class\", default=\"default\",\n                        help=\"Service class used for getting the files from tape\")\n    parser.add_argument(\"-u\", \"--uid\", default=\"0\", help=\"User UID (numeric)\")\n    parser.add_argument(\"-g\", \"--gid\", default=\"0\", help=\"User GID (numeric)\")\n    parser.add_argument(\"-x\", \"--skip_no_xs\", default=False, action=\"store_true\",\n                        help=\"Skip files that don't have a checksum\")\n    args = parser.parse_args()\n\n    try:\n        int(args.uid)\n        int(args.gid)\n    except ValueError as __:\n        print(\"Error: UID/GID must be in numeric format\", file=sys.stderr)\n        exit(errno.EINVAL)\n\n    # Make sure the source and destination are directories\n    if args.src[-1] != '/':\n        args.src += '/'\n    if args.dst[-1] != '/':\n        args.dst += '/'\n\n    # Check the source and destiantion are valid XRootD URLs\n    url_dst = client.URL(args.dst)\n    url_src = client.URL(args.src)\n\n    if not url_dst.is_valid() or not url_src.is_valid():\n        print(\"Error: Destination/Source URL is not valid\", file=sys.stderr)\n        exit(errno.EINVAL)\n\n    avoid_local = [\"localhost\", \"localhost4\", \"localhost6\",\n                   \"localhost.localdomain\", \"localhost4.localdomain4\",\n                   \"localhost6.localdomain6\"]\n\n    if url_dst.hostname in avoid_local or url_src.hostname in avoid_local:\n        print(\"Please use FQDNs in the XRootD URLs\", file=sys.stderr)\n        exit(errno.EINVAL)\n\n    try:\n        check_eos_access(url_dst)\n    except EosAccessException as err:\n        print(\"Error: {0}\".format(str(err)), file=sys.stderr)\n        exit(errno.EPERM)\n\n    archr = ArchReconstruct(url_src, url_dst, args)\n\n    try:\n        archr.breadth_first()\n        archr.upload_archive()\n    except (TapeAccessException, IOError) as err:\n        print(\"Error: {0}\".format(str(err)), file=sys.stderr)\n        exit(errno.EIO)\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "archive/eosarch_run.py",
    "content": "#!/usr/bin/python3\n# ------------------------------------------------------------------------------\n# File: eosarch_run.py\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ------------------------------------------------------------------------------\n#\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2014 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\n\"\"\" Script used for starting an archiving transfer in a subprocess which also\n    closes the open file descriptors such that there is no interference between\n    the processes using ZMQ.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom __future__ import print_function\nimport ast\nimport sys\nimport os\nimport logging\nfrom errno import EIO, EINVAL\nfrom hashlib import sha256\n\n# Note: this is to be enabled only when we want to get the logging from the\n# XrdCl - notice that this can grow very big, very fast. We also have to do\n# this here before the XrdCl module gets initialised.\n#os.environ['XRD_LOGLEVEL'] = \"Debug\"\n#os.environ['XRD_LOGFILE'] = \"/tmp/eosarch_xrdcl.log\"\n\nfrom eosarch import Transfer, NoErrorException, Configuration\n\ntry:\n    config = Configuration()\nexcept Exception as err:\n    print(\"Configuration failed, error:{0}\".format(err), file=sys.stderr)\n    raise\n\n# Set location for local transfer files\nfor oper in [config.GET_OP, config.PUT_OP, config.PURGE_OP, config.DELETE_OP, config.BACKUP_OP]:\n    path = config.EOS_ARCHIVE_DIR + oper + '/'\n    config.DIR[oper] = path\n\nreq_dict = ast.literal_eval(sys.argv[1])\nsrc = req_dict['src']\npos = src.find(\"//\", src.find(\"//\") + 1) + 1\nroot_dir = src[pos : src.rfind('/') + 1]\nuuid = sha256(root_dir.encode()).hexdigest()\nlog_file = config.DIR[req_dict['cmd']] + uuid + \".log\"\nconfig.start_logging(\"transfer\", log_file, False)\n\ntry:\n    tx = Transfer(req_dict, config)\nexcept Exception as err:\n    config.logger.exception(err)\n    raise\n\ntry:\n    tx.run()\nexcept IOError as err:\n    print(\"{0}\".format(err), file=sys.stderr)\n    tx.logger.exception(err)\n    tx.tx_clean(False)\n    sys.exit(EIO)\nexcept NoErrorException as err:\n    tx.tx_clean(True)\nexcept Exception as err:\n    print(\"{0}\".format(err), file=sys.stderr)\n    tx.logger.exception(err)\n    tx.tx_clean(False)\n    sys.exit(EINVAL)\n"
  },
  {
    "path": "archive/eosarchived.conf",
    "content": "# ------------------------------------------------------------------------------\n# File: eosarchiverd.conf\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ------------------------------------------------------------------------------\n#\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2014 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\n# Log level can be one of the following (it uses the syslog convention):\n# info, notice, warn/warning, err/error, emerg/panic, debug, crit/critical, alert\nLOG_LEVEL=debug\n\n# Max number of transfer that can run in parallel\nMAX_TRANSFERS=10\n\n# Max number of transfers to be performed by one thread\nBATCH_SIZE=10\n\n# Max number of threads used per transfer process\nMAX_THREADS=5\n\n# Max number of reties for a batch of jobs that have failed. This is used to\n# protect against transient failures, so that the user doesn't have to babysit\n# the entire transfer.\nMAX_RETRIES=5\n\n# Poll timeout in milliseconds - period after which the master requests on its\n# own for an update from the workers if there are no requests in the mean time.\n# This also has the role to join the the worker processes which have finished\n# in the mean time an print their returncode.\nPOLL_TIMEOUT=30000\n\n# Join timeout in seconds for running threads inside a process\nJOIN_TIMEOUT=1\n\n# Maximum timeout value in seconds for a file entry to be migrated to tape.\n# When this timeout expires the transfer process is retried. By default this\n# is 86400 seconds (1 day).\n#ARCHIVE_MAX_TIMEOUT=86400\n"
  },
  {
    "path": "archive/eosarchived.py",
    "content": "#!/usr/bin/python3\n# ------------------------------------------------------------------------------\n# File: eosarchived.py\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ------------------------------------------------------------------------------\n#\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2014 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\n\"\"\"Module running the eosarchiverd daemon which transfers files between EOS\n   and CASTOR.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom __future__ import print_function\nimport os\nimport sys\nimport zmq\nimport stat\nimport subprocess\nimport ast\nimport logging\nimport time\nimport logging.handlers\nfrom eosarch import ProcessInfo, Configuration\n\n\nclass Dispatcher(object):\n    \"\"\" Dispatcher daemon responsible for receiving requests from the clients\n    and then spawning the proper executing process for archiving operations\n\n    Attributes:\n        procs (dict): Dictionary containing the currently running processes\n    \"\"\"\n    def __init__(self, config):\n        self.config = config\n        self.logger = logging.getLogger(\"dispatcher\")\n        self.procs = {}\n        self.pending = {}\n        self.backend_req, self.backend_pub, self.backend_poller = None, None, None\n\n    def run(self):\n        \"\"\" Server entry point which is responsible for spawning worker proceesses\n        that do the actual transfers (put/get).\n        \"\"\"\n        # Set the triggers for different types of commands\n        trigger = {self.config.PUT_OP:    self.start_transfer,\n                   self.config.GET_OP:    self.start_transfer,\n                   self.config.DELETE_OP: self.start_transfer,\n                   self.config.PURGE_OP:  self.start_transfer,\n                   self.config.BACKUP_OP: self.start_transfer,\n                   self.config.TX_OP:     self.do_show_transfers,\n                   self.config.KILL_OP:   self.do_kill,\n                   self.config.STATS:     self.get_stats}\n        ctx = zmq.Context.instance()\n        self.logger.info(\"Started dispatcher process ...\")\n        # Socket used for communication with EOS MGM\n        frontend = ctx.socket(zmq.REP)\n        addr = \"ipc://\" + self.config.FRONTEND_IPC\n        frontend.bind(addr)\n        # Socket used for communication with worker processes\n        self.backend_req = ctx.socket(zmq.ROUTER)\n        addr = \"ipc://\" + self.config.BACKEND_REQ_IPC\n        self.backend_req.bind(addr)\n        self.backend_pub = ctx.socket(zmq.PUB)\n        addr = \"ipc://\" + self.config.BACKEND_PUB_IPC\n        self.backend_pub.bind(addr)\n        self.backend_poller = zmq.Poller()\n        self.backend_poller.register(self.backend_req, zmq.POLLIN)\n        mgm_poller = zmq.Poller()\n        mgm_poller.register(frontend, zmq.POLLIN)\n        time.sleep(1)\n\n        # Attach orphan processes which may be running before starting the daemon\n        self.get_orphans()\n\n        while True:\n            events = dict(mgm_poller.poll(self.config.POLL_TIMEOUT))\n            self.update_status()\n\n            if events and events.get(frontend) == zmq.POLLIN:\n                try:\n                    req_json = frontend.recv_json()\n                except zmq.ZMQError as err:\n                    if err.errno == zmq.ETERM:\n                        break  # shutting down, exit\n                    else:\n                        raise\n                except ValueError as err:\n                    self.logger.error(\"Command in not in JSON format\")\n                    frontend.send(\"ERROR error:command not in JSON format\")\n                    continue\n\n                self.logger.debug(\"Received command: {0}\".format(req_json))\n\n                try:\n                    reply = trigger[req_json['cmd']](req_json)\n                except KeyError as err:\n                    self.logger.error(\"Unknown command type: {0}\".format(req_json['cmd']))\n                    reply = \"ERROR error: operation not supported\"\n                    raise\n\n                frontend.send_string(reply)\n\n    def get_orphans(self):\n        \"\"\" Get orphan transfer processes from previous runs of the daemon\n        \"\"\"\n        self.logger.info(\"Get orphans\")\n        tries = 0\n        num = self.num_processes()\n\n        # Get status for orphan processes\n        while len(self.procs) != num and tries < 10:\n            tries += 1\n            self.procs.clear()\n            num = self.num_processes()\n            self.backend_pub.send_multipart([b\"[MASTER]\", b\"{'cmd': 'orphan_status'}\"])\n\n            while True:\n                events = dict(self.backend_poller.poll(1000))\n\n                if events and events.get(self.backend_req) == zmq.POLLIN:\n                    [__, resp] = self.backend_req.recv_multipart()\n                    self.logger.info(\"Received response: {0}\".format(resp))\n                    # Convert response to python dictionary\n                    dict_resp = ast.literal_eval(resp.decode())\n\n                    if not isinstance(dict_resp, dict):\n                        err_msg = \"Response={0} is not a dictionary\".format(resp)\n                        self.logger.error(err_msg)\n                        continue\n\n                    pinfo = ProcessInfo(None)\n                    pinfo.update(dict_resp)\n\n                    if pinfo.uuid not in self.procs:\n                        self.procs[pinfo.uuid] = pinfo\n                else: # TIMEOUT\n                    self.logger.info(\"Get orphans status timeout\")\n                    break\n\n            self.logger.debug((\"Try={0}, got {1}/{2} orphan processe responses\"\n                              \"\").format(tries, len(self.procs), num))\n\n    def num_processes(self):\n        \"\"\" Get the number of running archive processes on the current system by\n        executing the ps command\n\n        Returns:\n            Number of running processes\n\n        Raises:\n             ValueError in case the output of ps is not a valid pid number\n        \"\"\"\n        pid = os.getpid()\n        # TODO: make the resolution of the eosarch_run.py more elegant\n        exec_fname = \"eosarch_run.py\"\n        ps_proc = subprocess.Popen([(\"ps -eo pid,ppid,comm | egrep \\\"{0}\\$\\\" | \"\n                                     \"awk '{{print $1}}'\").format(exec_fname)],\n                                   stdout=subprocess.PIPE,\n                                   stderr=subprocess.PIPE,\n                                   shell=True)\n        ps_out, __ = ps_proc.communicate()\n\n        if len(ps_out) == 0:\n            return 0\n\n        ps_out = ps_out.strip('\\0\\n')\n        proc_lst = ps_out.split('\\n')\n\n        try:\n            num = len([x for x in proc_lst if pid != int(x)])\n        except ValueError as __:\n            self.logger.error(\"ps output x={0} is not a valid pid value\".format(x))\n            raise\n\n        return num\n\n    def update_status(self):\n        \"\"\" Update the status of the processes\n        \"\"\"\n        self.backend_pub.send_multipart([b\"[MASTER]\", b\"{'cmd': 'status'}\"])\n        recv_uuid = []\n\n        while len(recv_uuid) < len(self.procs):\n            events = dict(self.backend_poller.poll(400))\n\n            if events and events.get(self.backend_req) == zmq.POLLIN:\n                [__, resp] = self.backend_req.recv_multipart()\n                self.logger.debug(\"Received response: {0}\".format(resp))\n                # Convert response to python dictionary\n                dict_resp = ast.literal_eval(resp.decode())\n\n                if not isinstance(dict_resp, dict):\n                    self.logger.error(\"Response is not a dictionary\")\n                    continue\n\n                # Update the local info about the process\n                try:\n                    self.procs[dict_resp['uuid']].update(dict_resp)\n                except KeyError as __:\n                    err_msg = (\"Unknown process response:{0}\").format(dict_resp)\n                    self.logger.error(err_msg)\n\n                recv_uuid.append(dict_resp['uuid'])\n            else: # TIMEOUT\n                self.logger.debug(\"Update status timeout\")\n                break\n\n        # Check if processes that didn't respond are still alive\n        unresp = [proc for (uuid, proc) in self.procs.items()\n                  if uuid not in recv_uuid]\n\n        for pinfo in unresp:\n            if not pinfo.is_alive():\n                del self.procs[pinfo.uuid]\n\n        # Submit any pending transfers while the limit is not exceeded\n        while len(self.procs) < self.config.MAX_TRANSFERS and self.pending:\n            (__, pinfo) = self.pending.popitem() # take the oldest one\n            # Don't pipe stdout and stderr as we log all the output\n            pinfo.proc = subprocess.Popen(['/usr/bin/eosarch_run.py',\n                                           \"{0}\".format(pinfo.orig_req)],\n                                          close_fds=True)\n            pinfo.pid = pinfo.proc.pid\n            self.procs[pinfo.uuid] = pinfo\n\n    def start_transfer(self, req_json):\n        \"\"\" Start new transfer\n\n        Args:\n            req_json (json): New transfer information which must include:\n            {\n              cmd: get/put/delete/purge/backup,\n              src: full URL to archive file in EOS.\n              opt: retry | ''\n              uid: client uid\n              gid: client gid\n            }\n\n        Returns:\n            A message which is sent to the EOS MGM informing about the status\n            of the request.\n        \"\"\"\n        self.logger.debug(\"Start transfer {0}\".format(req_json))\n        pinfo = ProcessInfo(req_json)\n        self.logger.debug(\"Creating job={0}, path={1}\".format(pinfo.uuid, pinfo.root_dir))\n\n        if pinfo.uuid in self.procs:\n            err_msg = \"Job with same uuid={0} already exists\".format(pinfo.uuid)\n            self.logger.error(err_msg)\n            return \"ERROR error: job with same signature exists\"\n\n        if len(self.procs) >= self.config.MAX_TRANSFERS:\n            self.logger.warning(\"Maximum number of concurrent transfers reached, \"\n                                \"adding job={0} to the pending list\".format(pinfo.uuid))\n            self.pending[pinfo.uuid] = pinfo\n            return \"OK Id={0} added to the pending list\".format(pinfo.uuid)\n\n        # Don't pipe stdout and stderr as we log all the output\n        pinfo.proc = subprocess.Popen(['/usr/bin/eosarch_run.py', \"{0}\".format(req_json)],\n                                      close_fds=True)\n        pinfo.pid = pinfo.proc.pid\n        self.procs[pinfo.uuid] = pinfo\n        return \"OK Id={0}\".format(pinfo.uuid)\n\n    def do_show_transfers(self, req_json):\n        \"\"\" Show onging transfers\n\n        Args:\n            req_json (JSON): Command in JSON format include:\n            {\n              cmd:    transfers,\n              opt:    all/get/put/purge/delete/uuid,\n              uid:    uid,\n              gid:    gid\n            }\n\n        Returns:\n            String with the result of the listing\n         \"\"\"\n        msg = \"OK \"\n        row_data, proc_list = [], []\n        ls_type = req_json['opt']\n        self.logger.debug(\"Show transfers type={0}\".format(ls_type))\n\n        if ls_type == \"all\":\n            proc_list = [*self.procs.values(),*self.pending.values()]\n        elif ls_type in self.procs:\n            # ls_type is a transfer uuid\n            proc_list.append(self.procs[ls_type])\n        else:\n            proc_list = [elem for elem in self.procs.values() if elem.op == ls_type]\n            proc_list.extend([elem for elem in self.pending.values() if elem.op == ls_type])\n\n        for proc in proc_list:\n            line = (\"date={0},uuid={1},path={2},op={3},status={4}\".format(\n                    time.asctime(time.localtime(proc.timestamp)), proc.uuid,\n                    proc.orig_req['src'], proc.op, proc.status))\n            msg = '\\n'.join([msg, line])\n\n        return msg\n\n    def do_kill(self, req_json):\n        \"\"\" Kill transfer.\n\n        Args:\n            req_json (JSON command): Arguments for kill command include:\n            {\n              cmd: kill,\n              opt: uuid,\n              uid: uid,\n              gid: gid\n            }\n        \"\"\"\n        msg = \"OK\"\n        job_uuid = req_json['opt']\n        uid, gid = int(req_json['uid']), int(req_json['gid'])\n\n        try:\n            proc = self.procs[job_uuid]\n        except KeyError as __:\n            msg = \"ERROR error: job not found\"\n            return msg\n\n        if (uid == 0 or uid == proc.uid or\n            (uid != proc.uid and gid == proc.gid)):\n\n            self.logger.debug(\"Kill uuid={0} pid={1}\".format(job_uuid, proc.pid))\n            kill_proc = subprocess.Popen(['kill', '-SIGTERM', str(proc.pid)],\n                                         stdout=subprocess.PIPE,\n                                         stderr=subprocess.PIPE)\n            _, err = kill_proc.communicate()\n\n            if kill_proc.returncode:\n                msg = \"ERROR error:\" + err\n        else:\n            self.logger.error((\"User uid/gid={0}/{1} permission denied to kill job \"\n                               \"with uid/gid={2}/{3}\").format(uid, gid,\n                                                              proc.uid, proc.gid))\n            msg = \"ERROR error: Permission denied - you are not owner of the job\"\n\n        self.logger.debug(\"Kill pid={0}, msg={0}\".format(proc.pid, msg))\n        return msg\n\n    def get_stats(self, req_json):\n        \"\"\" Get archive daemon stats info.\n\n        Args:\n            req_json (JSON command): Arguments for stats command include:\n            {\n              cmd: stats,\n              opt: \\\"\\\",\n              uid: uid,\n              gid: gid\n            }\n\n        Returns: string containing information about number of slots\n        \"\"\"\n        return \"OK max={0} running={1} pending={2}\".format(\n            self.config.MAX_TRANSFERS, len(self.procs), len(self.pending))\n\ndef main():\n    \"\"\" Main function \"\"\"\n    try:\n        config = Configuration()\n    except Exception as err:\n        print(\"Configuration failed, error:{0}\".format(err), file=sys.stderr)\n        raise\n\n    config.start_logging(\"dispatcher\", config.LOG_FILE, True)\n    logger = logging.getLogger(\"dispatcher\")\n    config.display()\n    config.DIR = {}\n\n    # Create the local directory structure in /var/eos/archive/\n    # i.e /var/eos/archive/get/, /var/eos/archive/put/ etc.\n    for oper in [config.GET_OP,\n                    config.PUT_OP,\n                    config.PURGE_OP,\n                    config.DELETE_OP,\n                    config.BACKUP_OP]:\n        path = config.EOS_ARCHIVE_DIR + oper + '/'\n        config.DIR[oper] = path\n\n        try:\n            os.mkdir(path)\n        except OSError as __:\n            pass  # directory exists\n\n    # Prepare ZMQ IPC files\n    os.umask(0o002) # set files with 775 by default\n    for ipc_file in [config.FRONTEND_IPC,\n                        config.BACKEND_REQ_IPC,\n                        config.BACKEND_PUB_IPC]:\n        if not os.path.exists(ipc_file):\n            try:\n                open(ipc_file, 'w').close()\n                os.chmod(ipc_file, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)\n            except OSError as err:\n                err_msg = (\"Failed setting permissions on the IPC socket\"\n                            \" file={0}\").format(ipc_file)\n                logger.error(err_msg)\n                raise\n            except IOError as err:\n                err_msg = (\"Failed creating IPC socket file={0}\").format(ipc_file)\n                logger.error(err_msg)\n                raise\n\n    # Create dispatcher object\n    dispatcher = Dispatcher(config)\n\n    try:\n        dispatcher.run()\n    except Exception as err:\n        logger.exception(err)\n\nif __name__ == '__main__':\n    try:\n        main()\n    except ValueError as __:\n        # This is to deal with exceptions thrown when trying to close the log\n        # file which is already deleted manually by an exterior process\n        pass\n"
  },
  {
    "path": "archive/eosarchived.service",
    "content": "# ----------------------------------------------------------------------\n# File: eosarchived.service\n# Author: Elvin Sindrilaru - CERN\n# ----------------------------------------------------------------------\n#\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2018 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n[Unit]\nDescription=EOS archiver daemon\nAfter=network-online.target local-fs.target\nWants=network-online.target local-fs.target\n\n[Service]\nWorkingDirectory=/var/eos/\n#LogsDirectory=eos/archive/\nEnvironmentFile=/etc/sysconfig/eosarchived_env\nExecStart=/usr/bin/eosarchived.py\nType=simple\nUser=eosarchi\nGroup=daemon\nRestart=on-abort\nRestartSec=5\nLimitNOFILE=65000\nKillMode=mixed\nSuccessExitStatus=KILL\n"
  },
  {
    "path": "archive/eosarchived_env.sysconfig",
    "content": "# Options for the eosarchived daemon\n# EOSARCHIVED_OPTIONS=\n\n# Enable core dumping\nDAEMON_COREFILE_LIMIT=\"unlimited\"\n\n# Directory where log files are saved\nLOG_DIR=\"/var/log/eos/archive/\"\n\n# This directory must match the one set in xrd.cf.mgm as it is used for the\n# communication between the MGM and the eosarchived daemon\nEOS_ARCHIVE_DIR=/var/eos/archive/\n\n# Configuration file which can be modified while the daemon is running and\n# whose changes are automatically picked up by new transfers\nEOS_ARCHIVE_CONF=/etc/eosarchived.conf\n\n# This is the location of the archive keytab file containing just **one** entry\n# for the user account under which the eosarchived daemon is running. The same\n# entry need to be present in the eos.keytab file so that the eosarchived can\n# have full access to the EOS.\nXrdSecSSSKT=/etc/archive.keytab\n\n# Make eos-xrootd python bindings higher priority\nPYTHONPATH=/opt/eos/xrootd/lib64/python3.6/site-packages/"
  },
  {
    "path": "archive/opt-eos-xrootd.pth",
    "content": "/opt/eos/xrootd/lib64/python3.6/site-packages/"
  },
  {
    "path": "auth_plugin/CMakeLists.txt",
    "content": "#-------------------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch> CERN\n#-------------------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2013 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n#-------------------------------------------------------------------------------\n# Generate all protocol buffer files\n#-------------------------------------------------------------------------------\nPROTOBUF_GENERATE_CPP(XSE_SRCS XSE_HDRS proto/XrdSecEntity.proto)\nPROTOBUF_GENERATE_CPP(XOEI_SRCS XOEI_HDRS proto/XrdOucErrInfo.proto)\nPROTOBUF_GENERATE_CPP(XSFS_SRCS XSFS_HDRS proto/XrdSfsFSctl.proto)\nPROTOBUF_GENERATE_CPP(STAT_SRCS STAT_HDRS proto/Stat.proto)\nPROTOBUF_GENERATE_CPP(FSCTL1_SRCS FSCTL1_HDRS proto/Fsctl.proto)\nPROTOBUF_GENERATE_CPP(FSCTL2_SRCS FSCTL2_HDRS proto/FS_ctl.proto)\nPROTOBUF_GENERATE_CPP(CHMOD_SRCS CHMOD_HDRS proto/Chmod.proto)\nPROTOBUF_GENERATE_CPP(CHKSUM_SRCS CHKSUM_HDRS proto/Chksum.proto)\nPROTOBUF_GENERATE_CPP(EXISTS_SRCS EXISTS_HDRS proto/Exists.proto)\nPROTOBUF_GENERATE_CPP(MKDIR_SRCS MKDIR_HDRS proto/Mkdir.proto)\nPROTOBUF_GENERATE_CPP(REMDIR_SRCS REMDIR_HDRS proto/Remdir.proto)\nPROTOBUF_GENERATE_CPP(REM_SRCS REM_HDRS proto/Rem.proto)\nPROTOBUF_GENERATE_CPP(RENAME_SRCS RENAME_HDRS proto/Rename.proto)\nPROTOBUF_GENERATE_CPP(XSP_SRCS XSP_HDRS proto/XrdSfsPrep.proto)\nPROTOBUF_GENERATE_CPP(PREPARE_SRCS PREPARE_HDRS proto/Prepare.proto)\nPROTOBUF_GENERATE_CPP(TRUNCATE_SRCS TRUNCATE_HDRS proto/Truncate.proto)\nPROTOBUF_GENERATE_CPP(DOPEN_SRCS DOPEN_HDRS proto/DirOpen.proto)\nPROTOBUF_GENERATE_CPP(DREAD_SRCS DREAD_HDRS proto/DirRead.proto)\nPROTOBUF_GENERATE_CPP(DFNAME_SRCS DFNAME_HDRS proto/DirFname.proto)\nPROTOBUF_GENERATE_CPP(DCLOSE_SRCS DCLOSE_HDRS proto/DirClose.proto)\nPROTOBUF_GENERATE_CPP(FOPEN_SRCS FOPEN_HDRS proto/FileOpen.proto)\nPROTOBUF_GENERATE_CPP(FFNAME_SRCS FFNAME_HDRS proto/FileFname.proto)\nPROTOBUF_GENERATE_CPP(FSTAT_SRCS FSTAT_HDRS proto/FileStat.proto)\nPROTOBUF_GENERATE_CPP(FREAD_SRCS FREAD_HDRS proto/FileRead.proto)\nPROTOBUF_GENERATE_CPP(FWRITE_SRCS FWRITE_HDRS proto/FileWrite.proto)\nPROTOBUF_GENERATE_CPP(FCLOSE_SRCS FCLOSE_HDRS proto/FileClose.proto)\nPROTOBUF_GENERATE_CPP(REQ_SRCS REQ_HDRS proto/Request.proto)\nPROTOBUF_GENERATE_CPP(RESP_SRCS RESP_HDRS proto/Response.proto)\n\nset(AUTH_PROTO_SRCS\n  ${XSE_SRCS}     ${XOEI_SRCS}     ${XSFS_SRCS}   ${STAT_SRCS}\n  ${FSCTL1_SRCS}  ${FSCTL2_SRCS}   ${REQ_SRCS}    ${RESP_SRCS}\n  ${CHMOD_SRCS}   ${CHKSUM_SRCS}   ${EXISTS_SRCS} ${MKDIR_SRCS}\n  ${REMDIR_SRCS}  ${REM_SRCS}      ${RENAME_SRCS} ${XSP_SRCS}\n  ${PREPARE_SRCS} ${TRUNCATE_SRCS} ${DOPEN_SRCS}  ${DREAD_SRCS}\n  ${DFNAME_SRCS}  ${DCLOSE_SRCS}   ${FOPEN_SRCS}  ${FCLOSE_SRCS}\n  ${FFNAME_SRCS}  ${FSTAT_SRCS}    ${FREAD_SRCS}  ${FWRITE_SRCS})\n\nset(AUTH_PROTO_HDRS\n  ${XSE_HDRS}     ${XOEI_HDRS}     ${XSFS_HDRS}   ${STAT_HDRS}\n  ${FSCTL1_HDRS}  ${FSCTL2_HDRS}   ${REQ_HDRS}    ${RESP_HDRS}\n  ${CHMOD_HDRS}   ${CHKSUM_HDRS}   ${EXITS_HDRS}  ${MKDIR_HDRS}\n  ${REMDIR_HDRS}  ${REM_HDRS}      ${RENAME_HDRS} ${XSP_HDRS}\n  ${PREPARE_HDRS} ${TRUNCATE_HDRS} ${DOPEN_HDRS}  ${DREAD_HDRS}\n  ${DFNAME_HDRS}  ${DCLOSE_HDRS}   ${FOPEN_HDRS}  ${FCLOSE_HDRS}\n  ${FFNAME_HDRS}  ${FSTAT_HDRS}    ${FREAD_HDRS}  ${FWRITE_HDRS})\n\nset_source_files_properties(\n  ${AUTH_PROTO_SRCS}\n  ${AUTH_PROTO_HDRS}\n  PROPERTIES GENERATED 1)\n\n#-------------------------------------------------------------------------------\n# EosAuthProto-Objects\n#-------------------------------------------------------------------------------\nadd_library(EosAuthProto-Objects OBJECT\n  ProtoUtils.cc ProtoUtils.hh\n  ${AUTH_PROTO_SRCS} ${AUTH_PROTO_HDRS})\n\ntarget_link_libraries(EosAuthProto-Objects PUBLIC\n  PROTOBUF::PROTOBUF\n  XROOTD::UTILS\n  XROOTD::PRIVATE)\n\ntarget_include_directories(EosAuthProto-Objects PUBLIC\n  $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>\n  $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)\n\nset_target_properties(EosAuthProto-Objects\n  PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\n\n#-------------------------------------------------------------------------------\n# EosAuthOfs library\n#-------------------------------------------------------------------------------\nadd_library(EosAuthOfs-${XRDPLUGIN_SOVERSION} MODULE\n  EosAuthOfs.cc  EosAuthOfs.hh\n  EosAuthOfsFile.cc EosAuthOfsFile.hh\n  EosAuthOfsDirectory.cc EosAuthOfsDirectory.hh)\n\ntarget_link_libraries(\n  EosAuthOfs-${XRDPLUGIN_SOVERSION} PRIVATE\n  EosAuthProto-Objects\n  EosCommon\n  ZMQ::ZMQ\n  XROOTD::PRIVATE)  \n\ninstall(TARGETS EosAuthOfs-${XRDPLUGIN_SOVERSION}\n  LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n  ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\n"
  },
  {
    "path": "auth_plugin/EosAuthOfs.cc",
    "content": "//------------------------------------------------------------------------------\n// File: EosAuthOfs.cc\n// Author: Elvin-Alin Sindrilaru <esindril@cern.ch> CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <cstdio>\n#include <sstream>\n#include <fcntl.h>\n#include <syscall.h>\n#include <sys/time.h>\n#include <zlib.h>\n#include \"EosAuthOfs.hh\"\n#include \"ProtoUtils.hh\"\n#include \"EosAuthOfsDirectory.hh\"\n#include \"EosAuthOfsFile.hh\"\n#include \"common/SymKeys.hh\"\n#include <XrdOuc/XrdOucTrace.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdOss/XrdOssApi.hh>\n#include <XrdSec/XrdSecEntity.hh>\n#include <XrdNet/XrdNetIF.hh>\n#include <XrdNet/XrdNetUtils.hh>\n#include <XrdNet/XrdNetAddr.hh>\n#include <XProtocol/XProtocol.hh>\n#include <XrdVersion.hh>\n#include <google/protobuf/io/zero_copy_stream_impl.h>\n\n// The global OFS handle\neos::auth::EosAuthOfs* eos::auth::gOFS = nullptr;\n\nextern XrdSysError OfsEroute;\nextern XrdOfs* XrdOfsFS;\nXrdVERSIONINFO(XrdSfsGetFileSystem, AuthOfs);\nXrdVERSIONINFO(XrdSfsGetFileSystem2, AuthOfs);\n\n//------------------------------------------------------------------------------\n// Filesystem Plugin factory function\n//------------------------------------------------------------------------------\nextern \"C\"\n{\n  //------------------------------------------------------------------------------\n  //! Filesystem Plugin factory function\n  //!\n  //! @description FileSystem2 version, to allow passing configuration info back\n  //!              to XRootD. Configure with: xrootd.fslib -2 libXrdEosMgm.so\n  //!\n  //! @param native_fs (not used)\n  //! @param lp the logger object\n  //! @param configfn the configuration file name\n  //! @param envP pass configuration information back to XrdXrootd\n  //!\n  //! @returns configures and returns our MgmOfs object\n  //------------------------------------------------------------------------------\n  XrdSfsFileSystem*\n  XrdSfsGetFileSystem2(XrdSfsFileSystem* native_fs,\n                       XrdSysLogger* lp,\n                       const char* configfn,\n                       XrdOucEnv* envP)\n  {\n    if (eos::auth::gOFS) {\n      // File system object already initialized\n      return eos::auth::gOFS;\n    }\n\n    // Do the herald thing\n    OfsEroute.SetPrefix(\"AuthOfs_\");\n    OfsEroute.logger(lp);\n    XrdOucString version = \"AuthOfs (Object Storage File System) \";\n    version += VERSION;\n    OfsEroute.Say(\"++++++ (c) 2013 CERN/IT-DSS \", version.c_str());\n    // Initialize the subsystems\n    eos::auth::gOFS = new eos::auth::EosAuthOfs();\n    eos::auth::gOFS->ConfigFN = (configfn && *configfn ? strdup(configfn) : 0);\n\n    if (eos::auth::gOFS->Configure(OfsEroute, envP)) {\n      return 0;\n    }\n\n    XrdOfsFS = eos::auth::gOFS;\n    return eos::auth::gOFS;\n  }\n\n  //------------------------------------------------------------------------------\n  //! Filesystem Plugin factory function\n  //!\n  //! @param native_fs (not used)\n  //! @param lp the logger object\n  //! @param configfn the configuration file name\n  //!\n  //! @returns configures and returns our MgmOfs object\n  //------------------------------------------------------------------------------\n  XrdSfsFileSystem*\n  XrdSfsGetFileSystem(XrdSfsFileSystem* native_fs,\n                      XrdSysLogger* lp,\n                      const char* configfn)\n  {\n    if (eos::auth::gOFS) {\n      // File system object already initialized\n      OfsEroute.SetPrefix(\"AuthOfs_\");\n      OfsEroute.logger(lp);\n      OfsEroute.Say(\"info=\\\"return already loaded AUTH OFS pointer\\\"\");\n      return eos::auth::gOFS;\n    }\n\n    return XrdSfsGetFileSystem2(native_fs, lp, configfn, nullptr);\n  }\n} // extern \"C\"\n\nEOSAUTHNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nEosAuthOfs::EosAuthOfs():\n  XrdOfs(), eos::common::LogId(),  proxy_tid(0), mFrontend(0),\n  mSizePoolSocket(5), mPort(0), mCollapsePort(0), mLogLevel(LOG_INFO)\n{\n  // Initialise the ZMQ client\n  mZmqContext = new zmq::context_t(1);\n  mBackend = std::make_pair(std::string(\"\"), (zmq::socket_t*)0);\n\n  // Set Logging parameters\n  XrdOucString unit = \"auth@localhost\";\n  // setup the circular in-memory log buffer\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n  g_logging.SetLogPriority(mLogLevel);\n  g_logging.SetUnit(unit.c_str());\n  eos_info(\"info=\\\"logging configured\\\"\");\n}\n\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nEosAuthOfs::~EosAuthOfs()\n{\n  zmq::socket_t* socket;\n\n  // Kill the auth proxy thread\n  if (proxy_tid) {\n    XrdSysThread::Cancel(proxy_tid);\n    XrdSysThread::Join(proxy_tid, 0);\n  }\n\n  // Release memory\n  while (mPoolSocket.try_pop(socket)) {\n    delete socket;\n  }\n\n  delete mFrontend;\n  delete mBackend.second;\n  delete mZmqContext;\n  // Free configuration file name allocated via strdup during initialization\n  if (ConfigFN) {\n    free(ConfigFN);\n    ConfigFN = nullptr;\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Configure routine\n//------------------------------------------------------------------------------\nint\nEosAuthOfs::Configure(XrdSysError& error, XrdOucEnv* envP)\n{\n  int NoGo = 0;\n  int cfgFD;\n  char* var;\n  const char* val;\n  std::string space_tkn;\n  // Configure the basic XrdOfs and exit if not successful\n  NoGo = XrdOfs::Configure(error, envP);\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n\n  if (NoGo) {\n    return NoGo;\n  }\n\n  mPort = myPort;\n  mCollapsePort = mPort; // by default we collapse on the same AUTH service port on a remote machine\n\n  // Get the hostname\n  const char* errtext = 0;\n  const char* host_name = XrdNetUtils::MyHostName(0, &errtext);\n\n  if (!host_name) {\n    error.Emsg(\"Config\", \"hostname is invalid : %s\", host_name);\n    return 1;\n  }\n\n  XrdNetAddr* addrs  = 0;\n  int         nAddrs = 0;\n  const char* err    = XrdNetUtils::GetAddrs(host_name, &addrs, nAddrs,\n                       XrdNetUtils::allIPv64,\n                       XrdNetUtils::NoPortRaw);\n  free(const_cast<char*>(host_name));\n\n  if (err) {\n    error.Emsg(\"Config\", \"hostname is invalid : %s\", err);\n    return 1;\n  }\n\n  if (nAddrs == 0) {\n    error.Emsg(\"Config\", \"hostname is invalid\");\n    return 1;\n  }\n\n  char buffer[64];\n  int length = addrs[0].Format(buffer, sizeof(buffer),\n                               XrdNetAddrInfo::fmtAddr,\n                               XrdNetAddrInfo::noPortRaw);\n  delete [] addrs;\n\n  if (length == 0) {\n    error.Emsg(\"Config\", \"hostname is invalid\");\n    return 1;\n  }\n\n  mManagerIp.assign(buffer, length);\n  // Extract the manager from the config file\n  XrdOucStream Config(&error, getenv(\"XRDINSTANCE\"));\n\n  // Read in the auth configuration from the xrd.cf.auth file\n  if (!ConfigFN || !*ConfigFN) {\n    NoGo = 1;\n    error.Emsg(\"Configure\", \"no configure file\");\n  } else {\n    // Try to open the configuration file.\n    if ((cfgFD = open(ConfigFN, O_RDONLY, 0)) < 0) {\n      return error.Emsg(\"Configure\", errno, \"open config file fn=\", ConfigFN);\n    }\n\n    Config.Attach(cfgFD);\n    std::string auth_tag = \"eosauth.\";\n\n    while ((var = Config.GetMyFirstWord())) {\n      if (!strncmp(var, auth_tag.c_str(), auth_tag.length())) {\n        var += auth_tag.length();\n        // Get EOS instance to which we dispatch requests. Note that the port is the one\n        // waiting for authentication requests and not the usual one i.e 1094. The presence\n        // of the mgm parameter is mandatory.\n        std::string mgm_instance;\n        std::string option_tag = \"mgm\";\n\n        if (!strncmp(var, option_tag.c_str(), option_tag.length())) {\n          if ((val = Config.GetWord())) {\n            mgm_instance = val;\n\n            if (mgm_instance.find(\":\") != std::string::npos) {\n              mBackend = std::make_pair(mgm_instance, (zmq::socket_t*)0);\n            }\n          } else {\n            // This parameter is critical\n            error.Emsg(\"Configure \", \"No EOS mgm instance provided\");\n            NoGo = 1;\n          }\n        }\n\n\t// Get number of sockets in the pool by default 10\n        option_tag = \"collapseport\";\n\n        if (!strncmp(var, option_tag.c_str(), option_tag.length())) {\n          if (!(val = Config.GetWord())) {\n            error.Emsg(\"Configure \", \"No collapseport specified\");\n          } else {\n            mCollapsePort = atoi(val);\n          }\n        }\n\n        // Get number of sockets in the pool by default 10\n        option_tag = \"numsockets\";\n\n        if (!strncmp(var, option_tag.c_str(), option_tag.length())) {\n          if (!(val = Config.GetWord())) {\n            error.Emsg(\"Configure \", \"No number of sockets specified\");\n          } else {\n            mSizePoolSocket = atoi(val);\n          }\n        }\n\n        // Get log level by default LOG_INFO\n        option_tag = \"loglevel\";\n\n        if (!strncmp(var, option_tag.c_str(), option_tag.length())) {\n          if (!(val = Config.GetWord())) {\n            error.Emsg(\"Config\", \"argument for debug level invalid set to ERR.\");\n            mLogLevel = LOG_INFO;\n          } else {\n            std::string str_val(val);\n\n            if (isdigit(str_val[0])) {\n              // The level is given as a number\n              mLogLevel = atoi(val);\n            } else {\n              // The level is given as a string\n              mLogLevel = g_logging.GetPriorityByString(val);\n            }\n\n            error.Say(\"=====> eosauth.loglevel: \",\n                      g_logging.GetPriorityString(mLogLevel), \"\");\n          }\n\n          // Set the new log level\n          g_logging.SetLogPriority(mLogLevel);\n        }\n      }\n    }\n\n    // Check and connect at least to an MGM master\n    if (!mBackend.first.empty()) {\n      if ((XrdSysThread::Run(&proxy_tid, EosAuthOfs::StartAuthProxyThread,\n                             static_cast<void*>(this), 0, \"Auth Proxy Thread\"))) {\n        eos_err(\"cannot start the authentication proxy thread\");\n        NoGo = 1;\n      }\n\n      // Create a pool of sockets connected to the master proxy service\n      for (int i = 0; i < mSizePoolSocket; i++) {\n        // Set socket receive timeout to 5 seconds\n        zmq::socket_t* socket = new zmq::socket_t(*mZmqContext, ZMQ_REQ);\n        int timeout_mili = 5000;\n        socket->set(zmq::sockopt::rcvtimeo, timeout_mili);\n        int socket_linger = 0;\n        socket->set(zmq::sockopt::linger, socket_linger);\n        std::string endpoint = \"inproc://proxyfrontend\";\n\n        // Try in a loop to connect to the proxyfrontend as it can take a while for\n        // the proxy thread to do the binding, therefore connect can fail\n        while (1) {\n          try {\n            socket->connect(endpoint.c_str());\n          } catch (zmq::error_t& err) {\n            eos_warning(\"dealing with connect exception, retrying ...\");\n            continue;\n          }\n\n          break;\n        }\n\n        mPoolSocket.push(socket);\n      }\n    } else {\n      eos_err(\"No master MGM specified e.g. eos.master.cern.ch:15555\");\n      NoGo = 1;\n    }\n\n    close(cfgFD);\n  }\n\n  //----------------------------------------------------------------------------\n  // Build the adler & sha1 checksum of the default keytab file\n  //----------------------------------------------------------------------------\n  XrdOucString keytabcks = \"unaccessible\";\n  std::string keytab_path = \"/etc/eos.keytab\";\n  int fd = ::open(keytab_path.c_str(), O_RDONLY);\n  XrdOucString symkey = \"\";\n\n  if (fd >= 0) {\n    char buffer[65535];\n    char keydigest[SHA_DIGEST_LENGTH + 1];\n    SHA_CTX sha1;\n    SHA1_Init(&sha1);\n    size_t nread = ::read(fd, buffer, sizeof(buffer));\n\n    if (nread > 0) {\n      unsigned int adler;\n      SHA1_Update(&sha1, (const char*) buffer, nread);\n      adler = adler32(0L, Z_NULL, 0);\n      adler = adler32(adler, (const Bytef*) buffer, nread);\n      char sadler[1024];\n      snprintf(sadler, sizeof(sadler) - 1, \"%08x\", adler);\n      keytabcks = sadler;\n    } else {\n      eos_err(\"Failed while readling, error: %s\", strerror(errno));\n      close(fd);\n      return 1;\n    }\n\n    SHA1_Final((unsigned char*) keydigest, &sha1);\n    eos::common::SymKey::Base64Encode(keydigest, SHA_DIGEST_LENGTH, symkey);\n    close(fd);\n  } else {\n    eos_err(\"Failed to open keytab file: %s\", keytab_path.c_str());\n    return 1;\n  }\n\n  eos_notice(\"AUTH_HOST=%s AUTH_PORT=%ld VERSION=%s RELEASE=%s KEYTABADLER=%s\",\n             mManagerIp.c_str(), myPort, VERSION, RELEASE, keytabcks.c_str());\n\n  if (!eos::common::gSymKeyStore.SetKey64(symkey.c_str(), 0)) {\n    eos_crit(\"unable to store the created symmetric key %s\", symkey.c_str());\n    NoGo = 1;\n  }\n\n  return NoGo;\n}\n\n\n//------------------------------------------------------------------------------\n// Authentication proxy thread startup function\n//------------------------------------------------------------------------------\nvoid*\nEosAuthOfs::StartAuthProxyThread(void* pp)\n{\n  EosAuthOfs* ofs = static_cast<EosAuthOfs*>(pp);\n  ofs->AuthProxyThread();\n  return 0;\n}\n\n\n//------------------------------------------------------------------------------\n// Authentication proxt thread which forwards requests form the clients\n// to the proper MGM intance.\n//------------------------------------------------------------------------------\nvoid\nEosAuthOfs::AuthProxyThread()\n{\n  // Bind the client facing socket\n  mFrontend = new zmq::socket_t(*mZmqContext, ZMQ_ROUTER);\n  mFrontend->bind(\"inproc://proxyfrontend\");\n  // Connect sockets facing the MGM nodes - master and slave\n  std::ostringstream sstr;\n  mBackend = std::make_pair(mBackend.first,\n\t\t\t    new zmq::socket_t(*mZmqContext, ZMQ_DEALER));\n  sstr << \"tcp://\" << mBackend.first;\n  mBackend.second->connect(sstr.str().c_str());\n  OfsEroute.Say(\"=====> connected to MGM: \", mBackend.first.c_str());\n\n  // Set the master to point to the master MGM - no need for lock\n  auto master = mBackend.second;\n  int rc = -1;\n  zmq::message_t msg;\n  // Start the proxy using the first entry\n  int more;\n  int poll_size = 2;\n  zmq::pollitem_t items[3] = {\n    { (void*)* mFrontend, 0, ZMQ_POLLIN, 0},\n    { (void*)* mBackend.second, 0, ZMQ_POLLIN, 0}\n  };\n\n  // Main loop in which the proxy thread accepts request from the clients and\n  // then he forwards them to the current master MGM. The master MGM can change\n  // at any point.\n  while (true) {\n    // Wait while there are either requests or replies to process\n    try {\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wdeprecated-declarations\"\n      rc = zmq::poll(&items[0], poll_size, -1);\n#pragma GCC diagnostic pop\n    } catch (zmq::error_t& e) {\n      eos_err(\"Exception thrown: %s\", e.what());\n    }\n\n    if (rc < 0) {\n      eos_err(\"error in poll\");\n      return;\n    }\n\n    // Process a request\n    if (items[0].revents & ZMQ_POLLIN) {\n      eos_debug(\"got frontend event\");\n      zmq::recv_flags rf = zmq::recv_flags::none;\n\n      while (true) {\n        if (!mFrontend->recv(msg, rf).has_value()) {\n          eos_err(\"error while recv on frontend\");\n          return;\n        }\n\n        try {\n          more = mFrontend->get(zmq::sockopt::rcvmore);\n        } catch (zmq::error_t& err) {\n          eos_err(\"exception in getsockopt\");\n          return;\n        }\n\n        // Send request to the MGM\n        {\n          XrdSysMutexHelper scop_lock(mMutexMaster);\n          zmq::send_flags sf = zmq::send_flags::none;\n\n          if (more) {\n            sf = zmq::send_flags::sndmore;\n          }\n\n          if (!master->send(msg, sf)) {\n            eos_err(\"error while sending to master\");\n            return;\n          }\n        }\n\n        if (more == 0) {\n          break;\n        }\n      }\n    }\n\n    // Process a reply from the first MGM\n    if (items[1].revents & ZMQ_POLLIN) {\n      eos_debug(\"got mBackend event\");\n      zmq::recv_flags rf = zmq::recv_flags::none;\n\n      while (true) {\n        if (!mBackend.second->recv(msg, rf).has_value()) {\n          eos_err(\"error while recv on mBackend\");\n          return;\n        }\n\n        try {\n          more = mBackend.second->get(zmq::sockopt::rcvmore);\n        } catch (zmq::error_t& err) {\n          eos_err(\"exception in getsockopt\");\n          return;\n        }\n\n        zmq::send_flags sf = zmq::send_flags::none;\n\n        if (more) {\n          sf = zmq::send_flags::sndmore;\n        }\n\n        if (!mFrontend->send(msg, sf)) {\n          eos_err(\"error while send to frontend(1)\");\n          return;\n        }\n\n        if (more == 0) {\n          break;\n        }\n      }\n    }\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Get directory object\n//------------------------------------------------------------------------------\nXrdSfsDirectory*\nEosAuthOfs::newDir(char* user, int MonID)\n{\n  return static_cast<XrdSfsDirectory*>(new EosAuthOfsDirectory(user, MonID));\n}\n\n\n//------------------------------------------------------------------------------\n// Get file object\n//------------------------------------------------------------------------------\nXrdSfsFile*\nEosAuthOfs::newFile(char* user, int MonID)\n{\n  return static_cast<XrdSfsFile*>(new EosAuthOfsFile(user, MonID));\n}\n\nvoid\nEosAuthOfs::ProcessError(eos::auth::ResponseProto* resp_func, XrdOucErrInfo& error, const char* path)\n{\n  if (resp_func->has_error()) {\n    if (resp_func->collapse() && path && strlen(path)) {\n      // collpase redirection to remote AUTH MGM\n      std::string url = \"root://\";\n      url += resp_func->error().message();;\n      url += \":\";\n      url += std::to_string(mCollapsePort);\n      url += \"/\";\n      url += path;\n      error.setErrInfo(~(~(-1) | kXR_collapseRedir), url.c_str());\n    } else {\n      // one-shot redirection to remote MGM\n      error.setErrInfo(resp_func->error().code(),\n\t\t       resp_func->error().message().c_str());\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n//! Stat method\n//------------------------------------------------------------------------------\nint\nEosAuthOfs::stat(const char* path,\n                 struct stat* buf,\n                 XrdOucErrInfo& error,\n                 const XrdSecEntity* client,\n                 const char* opaque)\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"stat path=%s\", path);\n  // Create request object\n  RequestProto* req_proto = utils::GetStatRequest(RequestProto_OperationType_STAT,\n                            path, error, client, opaque);\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC FS stat\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  mPoolSocket.wait_pop(socket);\n\n  if (SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_stat = static_cast<ResponseProto*>(GetResponse(socket));\n\n    if (resp_stat) {\n      retc = resp_stat->response();\n\n      ProcessError(resp_stat, error, path);\n\n      // We retrieve the struct stat if response is ok\n      if ((retc == SFS_OK) && resp_stat->has_message()) {\n        buf = static_cast<struct stat*>(memcpy((void*)buf,\n                                               resp_stat->message().c_str(),\n                                               sizeof(struct stat)));\n      }\n\n      delete resp_stat;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//--------------------------------------------------------------------------\n// Stat function to retrieve mode\n//--------------------------------------------------------------------------\nint\nEosAuthOfs::stat(const char* path,\n                 mode_t& mode,\n                 XrdOucErrInfo& error,\n                 const XrdSecEntity* client,\n                 const char* opaque)\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"statm path=%s\", path);\n  RequestProto* req_proto = utils::GetStatRequest(\n                              RequestProto_OperationType_STATM,\n                              path, error, client, opaque);\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC FS statm\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  mPoolSocket.wait_pop(socket);\n\n  if (SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_stat = static_cast<ResponseProto*>(GetResponse(socket));\n\n    if (resp_stat) {\n      retc = resp_stat->response();\n\n      ProcessError(resp_stat, error, path);\n\n      // We retrieve the open mode if response if ok\n      if ((retc == SFS_OK) && resp_stat->has_message()) {\n        memcpy((void*)&mode, resp_stat->message().c_str(), sizeof(mode_t));\n      }\n\n      delete resp_stat;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Execute file system command\n//------------------------------------------------------------------------------\nint\nEosAuthOfs::fsctl(const int cmd,\n                  const char* args,\n                  XrdOucErrInfo& error,\n                  const XrdSecEntity* client)\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"fsctl with cmd=%i, args=%s\", cmd, args);\n  int opcode = cmd & SFS_FSCTL_CMD;\n\n  // For the server configuration query we asnwer with the information of the\n  // authentication XRootD server i.e. don't frw it to the real MGM.\n  if (opcode == SFS_FSCTL_LOCATE) {\n    char locResp[4096];\n    char rType[3], *Resp[] = {rType, locResp};\n    rType[0] = 'S';\n    // don't manage writes via global redirection - therefore we mark the files as 'r'\n    rType[1] = 'r';\n    rType[2] = '\\0';\n    sprintf(locResp, \"[::%s]:%d \", (char*) gOFS->mManagerIp.c_str(),\n            gOFS->mPort);\n    error.setErrInfo(strlen(locResp) + 3, (const char**) Resp, 2);\n    return SFS_DATA;\n  }\n\n  RequestProto* req_proto = utils::GetFsctlRequest(cmd, args, error, client);\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC FS fsctl\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  mPoolSocket.wait_pop(socket);\n\n  if (SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_fsctl1 = static_cast<ResponseProto*>(GetResponse(socket));\n\n    if (resp_fsctl1) {\n      retc = resp_fsctl1->response();\n\n      // TODO: we can't collapse without a path ...\n      ProcessError(resp_fsctl1, error, 0);\n\n      delete resp_fsctl1;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Execute file system command !!! FSctl !!!\n//------------------------------------------------------------------------------\nint\nEosAuthOfs::FSctl(const int cmd,\n                  XrdSfsFSctl& args,\n                  XrdOucErrInfo& error,\n                  const XrdSecEntity* client)\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"FSctl with cmd=%i\", cmd);\n  RequestProto* req_proto = utils::GetFSctlRequest(cmd, args, error, client);\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC FS FSctl\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  mPoolSocket.wait_pop(socket);\n\n  if (SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_fsctl2 = static_cast<ResponseProto*>(GetResponse(socket));\n\n    if (resp_fsctl2) {\n      retc = resp_fsctl2->response();\n\n      // TODO: we can't collapse without a path ...\n      ProcessError(resp_fsctl2, error, 0);\n\n      delete resp_fsctl2;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Chmod by client\n//------------------------------------------------------------------------------\nint\nEosAuthOfs::chmod(const char* path,\n                  XrdSfsMode mode,\n                  XrdOucErrInfo& error,\n                  const XrdSecEntity* client,\n                  const char* opaque)\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"chmod path=%s mode=%o\", path, mode);\n  RequestProto* req_proto = utils::GetChmodRequest(path, mode, error, client,\n                            opaque);\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC FS chmod\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  mPoolSocket.wait_pop(socket);\n\n  if (SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_chmod = static_cast<ResponseProto*>(GetResponse(socket));\n\n    if (resp_chmod) {\n      retc = resp_chmod->response();\n\n      ProcessError(resp_chmod, error, path);\n\n      delete resp_chmod;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Chksum by client\n//------------------------------------------------------------------------------\nint\nEosAuthOfs::chksum(csFunc func,\n                   const char* csName,\n                   const char* path,\n                   XrdOucErrInfo& error,\n                   const XrdSecEntity* client,\n                   const char* opaque)\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"chksum path=%s csName=%s\", path, csName);\n  RequestProto* req_proto = utils::GetChksumRequest(func, csName, path, error,\n                            client, opaque);\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC FS chksum\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  mPoolSocket.wait_pop(socket);\n\n  if (SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_chksum = static_cast<ResponseProto*>(GetResponse(socket));\n\n    if (resp_chksum) {\n      retc = resp_chksum->response();\n      eos_debug(\"chksum retc=%i\", retc);\n\n      ProcessError(resp_chksum, error, path);\n\n      delete resp_chksum;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Exists function\n//------------------------------------------------------------------------------\nint\nEosAuthOfs::exists(const char* path,\n                   XrdSfsFileExistence& exists_flag,\n                   XrdOucErrInfo& error,\n                   const XrdSecEntity* client,\n                   const char* opaque)\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"exists path=%s\", path);\n  RequestProto* req_proto = utils::GetExistsRequest(path, error, client, opaque);\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC FS exists\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  mPoolSocket.wait_pop(socket);\n\n  if (SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_exists = static_cast<ResponseProto*>(GetResponse(socket));\n\n    if (resp_exists) {\n      retc = resp_exists->response();\n      eos_debug(\"exists retc=%i\", retc);\n\n      ProcessError(resp_exists, error, path);\n\n      if (resp_exists->has_message()) {\n        exists_flag = (XrdSfsFileExistence)atoi(resp_exists->message().c_str());\n      }\n\n      delete resp_exists;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Create directory\n// Note: the mode set here is actually ignored if the directoy is not the top\n// one. The new directory inherits the mode bits from its parent directory.\n// This is typical only for EOS since in a normal XRootD server the access bits\n// specified in the mkdir command are actually applied as expected.\n//------------------------------------------------------------------------------\nint\nEosAuthOfs::mkdir(const char* path,\n                  XrdSfsMode mode,  // Ignored in EOS if it has a parent dir\n                  XrdOucErrInfo& error,\n                  const XrdSecEntity* client,\n                  const char* opaque)\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"mkdir path=%s mode=%o\", path, mode);\n  RequestProto* req_proto = utils::GetMkdirRequest(path, mode, error, client,\n                            opaque);\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC FS mkdir\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  mPoolSocket.wait_pop(socket);\n\n  if (SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_mkdir = static_cast<ResponseProto*>(GetResponse(socket));\n\n    if (resp_mkdir) {\n      retc = resp_mkdir->response();\n      eos_debug(\"mkdir retc=%i\", retc);\n\n      ProcessError(resp_mkdir, error, path);\n\n      delete resp_mkdir;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Remove directory\n//------------------------------------------------------------------------------\nint\nEosAuthOfs::remdir(const char* path,\n                   XrdOucErrInfo& error,\n                   const XrdSecEntity* client,\n                   const char* opaque)\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"remdir path=%s\", path);\n  RequestProto* req_proto = utils::GetRemdirRequest(path, error, client, opaque);\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC FS remdir\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  mPoolSocket.wait_pop(socket);\n\n  if (SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_remdir = static_cast<ResponseProto*>(GetResponse(socket));\n\n    if (resp_remdir) {\n      retc = resp_remdir->response();\n      eos_debug(\"remdir retc=%i\", retc);\n\n      ProcessError(resp_remdir, error, path);\n\n      delete resp_remdir;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Remove file\n//------------------------------------------------------------------------------\nint\nEosAuthOfs::rem(const char* path,\n                XrdOucErrInfo& error,\n                const XrdSecEntity* client,\n                const char* opaque)\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"rem path=%s\", path);\n  RequestProto* req_proto = utils::GetRemRequest(path, error, client, opaque);\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC FS rem\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  mPoolSocket.wait_pop(socket);\n\n  if (SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_rem = static_cast<ResponseProto*>(GetResponse(socket));\n\n    if (resp_rem) {\n      retc = resp_rem->response();\n      eos_debug(\"rem retc=%i\", retc);\n\n      ProcessError(resp_rem, error, path);\n\n      delete resp_rem;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Rename file\n//------------------------------------------------------------------------------\nint\nEosAuthOfs::rename(const char* oldName,\n                   const char* newName,\n                   XrdOucErrInfo& error,\n                   const XrdSecEntity* client,\n                   const char* opaqueO,\n                   const char* opaqueN)\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"rename oldname=%s newname=%s\", oldName, newName);\n  RequestProto* req_proto = utils::GetRenameRequest(oldName, newName, error,\n                            client, opaqueO, opaqueN);\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC FS rename\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  mPoolSocket.wait_pop(socket);\n\n  if (SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_rename = static_cast<ResponseProto*>(GetResponse(socket));\n\n    if (resp_rename) {\n      retc = resp_rename->response();\n      eos_debug(\"rename retc=%i\", retc);\n\n      ProcessError(resp_rename, error, 0);\n\n      delete resp_rename;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Prepare request\n//------------------------------------------------------------------------------\nint\nEosAuthOfs::prepare(XrdSfsPrep& pargs,\n                    XrdOucErrInfo& error,\n                    const XrdSecEntity* client)\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"prepare\");\n  RequestProto* req_proto = utils::GetPrepareRequest(pargs, error, client);\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC FS prepare\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  mPoolSocket.wait_pop(socket);\n\n  if (SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_prepare = static_cast<ResponseProto*>(GetResponse(socket));\n\n    if (resp_prepare) {\n      retc = resp_prepare->response();\n      eos_debug(\"prepare retc=%i\", retc);\n\n      ProcessError(resp_prepare, error, 0);\n\n      delete resp_prepare;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Truncate file\n//------------------------------------------------------------------------------\nint\nEosAuthOfs::truncate(const char* path,\n                     XrdSfsFileOffset fileOffset,\n                     XrdOucErrInfo& error,\n                     const XrdSecEntity* client,\n                     const char* opaque)\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"truncate\");\n  RequestProto* req_proto = utils::GetTruncateRequest(path, fileOffset, error,\n                            client, opaque);\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC FS truncate\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  mPoolSocket.wait_pop(socket);\n\n  if (SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_truncate = static_cast<ResponseProto*>(GetResponse(socket));\n\n    if (resp_truncate) {\n      retc = resp_truncate->response();\n      eos_debug(\"truncate retc=%i\", retc);\n\n      ProcessError(resp_truncate, error, path);\n\n      delete resp_truncate;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// getStats function - not supported by EOS, fake ok response HERE i.e. do not\n// build and send a request to the real MGM\n//------------------------------------------------------------------------------\nint\nEosAuthOfs::getStats(char* buff, int blen)\n{\n  int retc = SFS_OK;\n  eos_debug(\"getStats\");\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Send ProtocolBuffer object using ZMQ\n//------------------------------------------------------------------------------\nbool\nEosAuthOfs::SendProtoBufRequest(zmq::socket_t* socket,\n                                google::protobuf::Message* message)\n{\n  // Send the request\n  bool sent = false;\n#if GOOGLE_PROTOBUF_VERSION < 3004000\n  int msg_size = message->ByteSize();\n#else\n  int msg_size = message->ByteSizeLong();\n#endif\n  zmq::message_t request(msg_size);\n  google::protobuf::io::ArrayOutputStream aos(request.data(), msg_size);\n\n  if (!message->SerializeToZeroCopyStream(&aos)) {\n    eos_err(\"failed to serialize message\");\n    return sent;\n  }\n\n  zmq::send_flags sf = zmq::send_flags::dontwait;\n  auto r = socket->send(request, sf);\n\n  if (r.has_value()) {\n    sent = true;\n  }\n\n  if (!sent) {\n    eos_err(\"unable to send request using zmq\");\n  }\n\n  return sent;\n}\n\n\n//------------------------------------------------------------------------------\n// Get ProtocolBuffer response object using ZMQ\n//------------------------------------------------------------------------------\ngoogle::protobuf::Message*\nEosAuthOfs::GetResponse(zmq::socket_t*& socket)\n{\n  // It makes no sense to wait more than 1 min since the XRootD client will\n  // timeout by default after 60 seconds.\n  int num_retries = 12; // 1 min = 12 * 5 sec\n  bool done = false;\n  bool reset_socket = false;\n  zmq::message_t reply;\n  ResponseProto* resp = static_cast<ResponseProto*>(0);\n\n  try {\n    zmq::recv_flags rf = zmq::recv_flags::none;\n    zmq::recv_result_t rr;\n\n    do {\n      rr = socket->recv(reply, rf);\n      --num_retries;\n\n      if (!rr.has_value()) {\n        eos_err(\"ptr_socket=%p, num_retries=%i failed receive\", socket,\n                num_retries);\n      } else {\n        done = true;\n      }\n    } while (!rr.has_value() && (num_retries > 0));\n  } catch (zmq::error_t& e) {\n    eos_err(\"socket error: %s\", e.what());\n    reset_socket = true;\n  }\n\n  // We time out while waiting for a response or a fatal error occurent -\n  // then we throw away the socket and create a new one\n  if ((num_retries <= 0) || reset_socket) {\n    eos_err(\"discard current socket and create a new one\");\n    delete socket;\n    socket = new zmq::socket_t(*mZmqContext, ZMQ_REQ);\n    int timeout_mili = 5000;\n    socket->set(zmq::sockopt::rcvtimeo, timeout_mili);\n    int socket_linger = 0;\n    socket->set(zmq::sockopt::linger, socket_linger);\n    std::string endpoint = \"inproc://proxyfrontend\";\n\n    // Try in a loop to connect to the proxyfrontend as it can take a while for\n    // the proxy thread to do the binding, therefore connect can fail\n    while (1) {\n      try {\n        socket->connect(endpoint.c_str());\n      } catch (zmq::error_t& err) {\n        eos_warning(\"dealing with connect exception, retrying ...\");\n        continue;\n      }\n\n      break;\n    }\n  }\n\n  if (done) {\n    std::string resp_str = std::string(static_cast<char*>(reply.data()),\n                                       reply.size());\n    resp = new ResponseProto();\n    resp->ParseFromString(resp_str);\n\n    // If the response is a redirect we redirect to our own port number on the target\n    // - this allows to failover access from one AUTH to another AUTH daemon in an HA MGM setup\n\n    if (resp->response() == SFS_REDIRECT) {\n      if (resp->has_error()) {\n        std::ostringstream sstr;\n        sstr << resp->error().message();\n        std::string redirect_host = sstr.str();\n\n\t// Add redirect collapse\n\tresp->set_collapse(true);\n      } else {\n        eos_err(\"redirect message without error information - change to error\");\n        resp->set_response(SFS_ERROR);\n      }\n    }\n  } else {\n    eos_err(\"socket error/timeout during receive\");\n  }\n\n  return resp;\n}\n\nEOSAUTHNAMESPACE_END\n"
  },
  {
    "path": "auth_plugin/EosAuthOfs.hh",
    "content": "// -----------------------------------------------------------------------------\n// File: EosAuthOfs.hh\n// Author: Elvin-Alin Sindrilaru - CERN\n// -----------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSAUTH_OFS_HH__\n#define __EOSAUTH_OFS_HH__\n\n#include <XrdOfs/XrdOfs.hh>\n#include \"Namespace.hh\"\n#include \"common/ConcurrentQueue.hh\"\n#include <zmq.hpp>\n#include <string>\n\n//! Forward declaration\nclass EosAuthOfsDirectory;\nclass EosAuthOfsFile;\n\nnamespace eos\n{\nnamespace auth\n{\n  class ResponseProto;\n}\n}\n\nnamespace google\n{\nnamespace protobuf\n{\nclass Message;\n}\n}\n\nEOSAUTHNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class EosAuthOfs built on top of XrdOfs\n/*! Decription: The libEosAuthOfs.so is inteded to be used as an OFS library\n    plugin with a vanilla XRootD server. What it does is to connect using ZMQ\n    sockets to onel MGM node.\n    a slave MGM). The endpoint is read from the configuration file, by default\n    it will connect to localhost:1094 !\n    The EosAuthOfs plugin\n    then tries to replay all the requests it receives from the clients to the\n    master MGM node. It does this by marshalling the request and identity of the\n    client using ProtocolBuffers and sends this request using ZMQ to the configured\n    MGM node.\n\n    There are several tunable parameters for this configuration (auth + MGMs):\n\n    AUTH - configuration\n    ====================\n    - eosauth.mgm - contain the hostname and the\n        port to which ZMQ will connect so that it can forward\n        requests and receive responses. \n    - eosauth.numsockets - once a clients wants to send a request the thread\n        allocated to him in XRootD will require a socket to send the request\n        to the MGM node. Therefore, we set up a pool of sockets from the\n        begining which can be used to send/receiver requests/responses.\n        The default size is 10 sockets.\n\n    MGM - configuration\n    ===================\n    - mgmofs.auththreads - since we now receive requests using ZMQ, we no longer\n        use the default thread pool from XRootD and we need threads for dealing\n        with the requests. This parameter sets the thread pool size when starting\n        the MGM node.\n    - mgmofs.authport - this is the endpoint where the MGM listens for ZMQ\n        requests from any EosAuthOfs plugins. This port needs to be opened also\n        in the firewall.\n\n    - mgmofs.localhost true|false - by default the ZMQ endpoint will listen on \n        all interfaces, but often the front-end will run on the same node and\n        for security we want only to have localhost connections\n\n    In case of a master <=> slave switch the EosAuthOfs plugin adapts\n    automatically based on the information provided by the slave MGM which\n    should redirect all clients with write requests to the master node. Care\n    should be taken when specifying the two endpoints since the switch is done\n    ONLY IF the redirection HOST matches one of the two endpoints specified in\n    the configuration  of the authentication plugin (namely eosauth.instance).\n    Once the switch is done all requests be them read or write are sent to the\n    new master MGM node.\n*/\n//------------------------------------------------------------------------------\nclass EosAuthOfs: public XrdOfs, public eos::common::LogId\n{\n  friend class EosAuthOfsDirectory;\n  friend class EosAuthOfsFile;\n\npublic:\n  //--------------------------------------------------------------------------\n  //! Constuctor\n  //--------------------------------------------------------------------------\n  EosAuthOfs();\n\n  //--------------------------------------------------------------------------\n  //! Destructor\n  //--------------------------------------------------------------------------\n  virtual ~EosAuthOfs();\n\n  //--------------------------------------------------------------------------\n  //! Configure routine\n  //--------------------------------------------------------------------------\n  virtual int Configure(XrdSysError& error, XrdOucEnv* envP);\n\n  //--------------------------------------------------------------------------\n  //! Get directory object\n  //--------------------------------------------------------------------------\n  XrdSfsDirectory* newDir(char* user = 0, int MonID = 0);\n\n  //--------------------------------------------------------------------------\n  // Get file object\n  //--------------------------------------------------------------------------\n  XrdSfsFile* newFile(char* user = 0, int MonID = 0);\n\n  //--------------------------------------------------------------------------\n  //! Stat function\n  //--------------------------------------------------------------------------\n  int stat(const char* path,\n           struct stat* buf,\n           XrdOucErrInfo& error,\n           const XrdSecEntity* client,\n           const char* opaque = 0);\n\n  //--------------------------------------------------------------------------\n  //! Stat function to retrieve mode\n  //--------------------------------------------------------------------------\n  int stat(const char* name,\n           mode_t& mode,\n           XrdOucErrInfo& out_error,\n           const XrdSecEntity* client,\n           const char* opaque = 0);\n\n  //--------------------------------------------------------------------------\n  //! Execute file system command !!! fsctl !!!\n  //--------------------------------------------------------------------------\n  int fsctl(const int cmd,\n            const char* args,\n            XrdOucErrInfo& out_error,\n            const XrdSecEntity* client);\n\n  //--------------------------------------------------------------------------\n  //! Execute file system command !!! FSctl !!!\n  //--------------------------------------------------------------------------\n  int FSctl(const int cmd,\n            XrdSfsFSctl& args,\n            XrdOucErrInfo& error,\n            const XrdSecEntity* client = 0);\n\n  //--------------------------------------------------------------------------\n  //! Chmod function\n  //--------------------------------------------------------------------------\n  int chmod(const char* path,\n            XrdSfsMode mopde,\n            XrdOucErrInfo& error,\n            const XrdSecEntity* client,\n            const char* opaque = 0);\n\n  //--------------------------------------------------------------------------\n  //! Chksum function\n  //--------------------------------------------------------------------------\n  int chksum(csFunc func,\n             const char* csName,\n             const char* path,\n             XrdOucErrInfo& error,\n             const XrdSecEntity* client = 0,\n             const char* opaque = 0);\n\n  //--------------------------------------------------------------------------\n  //! Exists function\n  //--------------------------------------------------------------------------\n  int exists(const char* path,\n             XrdSfsFileExistence& exists_flag,\n             XrdOucErrInfo& error,\n             const XrdSecEntity* client,\n             const char* opaque = 0);\n\n  //--------------------------------------------------------------------------\n  //! Create directory\n  //--------------------------------------------------------------------------\n  int mkdir(const char* dirName,\n            XrdSfsMode Mode,\n            XrdOucErrInfo& out_error,\n            const XrdSecEntity* client,\n            const char* opaque = 0);\n\n  //--------------------------------------------------------------------------\n  //! Remove directory\n  //--------------------------------------------------------------------------\n  int remdir(const char* path,\n             XrdOucErrInfo& error,\n             const XrdSecEntity* client,\n             const char* opaque = 0);\n\n  //--------------------------------------------------------------------------\n  //! Rem file\n  //--------------------------------------------------------------------------\n  int rem(const char* path,\n          XrdOucErrInfo& error,\n          const XrdSecEntity* client,\n          const char* opaque = 0);\n\n  //--------------------------------------------------------------------------\n  //! Rename file\n  //--------------------------------------------------------------------------\n  int rename(const char* oldName,\n             const char* newName,\n             XrdOucErrInfo& error,\n             const XrdSecEntity* client,\n             const char* opaqueO = 0,\n             const char* opaqueN = 0);\n\n  //--------------------------------------------------------------------------\n  //! Prepare request\n  //--------------------------------------------------------------------------\n  int prepare(XrdSfsPrep& pargs,\n              XrdOucErrInfo& error,\n              const XrdSecEntity* client = 0);\n\n  //--------------------------------------------------------------------------\n  //! Truncate file\n  //--------------------------------------------------------------------------\n  int truncate(const char* path,\n               XrdSfsFileOffset fileOffset,\n               XrdOucErrInfo& error,\n               const XrdSecEntity* client = 0,\n               const char* opaque = 0);\n\n  //--------------------------------------------------------------------------\n  //! getStats function - fake an ok response HERE i.e. do not build and sent\n  //! a request to the real MGM\n  //--------------------------------------------------------------------------\n  int getStats(char* buff, int blen);\n\n  //--------------------------------------------------------------------------\n  //! Process a proto error response and configure \n  //! a collpasing redirection if requested/possible\n  //--------------------------------------------------------------------------\n  void ProcessError(eos::auth::ResponseProto* resp_func, XrdOucErrInfo& error, const char* path);\n\nprivate:\n\n  pthread_t proxy_tid; ///< id of the proxy thread\n  zmq::context_t* mZmqContext; ///< ZMQ context\n  zmq::socket_t* mFrontend; ///< proxy socket facing the clients\n  XrdSysMutex mMutexMaster;\n  int mSizePoolSocket; ///< maximum size of the client socket pool\n  eos::common::ConcurrentQueue<zmq::socket_t*>\n  mPoolSocket; ///< ZMQ client socket pool\n  ///! MGM endpoints to which requests can be dispatched and the corresponding sockets\n  std::pair<std::string, zmq::socket_t*> mBackend;\n  std::string mManagerIp; ///< auth ip address\n  int mPort;   ///< port on which the current auth server runs\n  int mCollapsePort; ///< port to which a redirect gets collapsed on\n  int mLogLevel; ///< log level value 0 -7 (LOG_EMERG - LOG_DEBUG)\n\n  //--------------------------------------------------------------------------\n  //! Authentication proxy thread which forwards requests form the clients\n  //! to the proper MGM intance.\n  //--------------------------------------------------------------------------\n  void AuthProxyThread();\n\n  //--------------------------------------------------------------------------\n  //! Authentication proxy thread startup function\n  //--------------------------------------------------------------------------\n  static void* StartAuthProxyThread(void* pp);\n\n  //--------------------------------------------------------------------------\n  //! Send ProtocolBuffer object using ZMQ\n  //!\n  //! @param socket ZMQ socket object\n  //! @param object to be sent over the wire\n  //!\n  //! @return true if object sent successfully, otherwise false\n  //!\n  //--------------------------------------------------------------------------\n  bool SendProtoBufRequest(zmq::socket_t* socket,\n                           google::protobuf::Message* message);\n\n  //--------------------------------------------------------------------------\n  //! Get ProtocolBuffer reply object using ZMQ\n  //!\n  //! @param socket ZMQ socket object\n  //!\n  //! @return pointer to received object, the user has the responsibility to\n  //!         delete the obtained object\n  //!\n  //--------------------------------------------------------------------------\n  google::protobuf::Message* GetResponse(zmq::socket_t*& socket);\n\n  //--------------------------------------------------------------------------\n};\n\n//------------------------------------------------------------------------------\n//! Global OFS object\n//------------------------------------------------------------------------------\nextern EosAuthOfs* gOFS;\nEOSAUTHNAMESPACE_END\n#endif //__EOSAUTH_OFS_HH__\n"
  },
  {
    "path": "auth_plugin/EosAuthOfsDirectory.cc",
    "content": "//------------------------------------------------------------------------------\n// File: EosAuthOfsDirectory.cc\n// Author: Elvin-Alin Sindrilau <esindril@cern.ch> CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"EosAuthOfsDirectory.hh\"\n#include \"EosAuthOfs.hh\"\n#include \"ProtoUtils.hh\"\n/*----------------------------------------------------------------------------*/\n#include <sstream>\n/*----------------------------------------------------------------------------*/\n\nEOSAUTHNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nEosAuthOfsDirectory::EosAuthOfsDirectory(char* user, int MonID):\n  XrdSfsDirectory(user, MonID),\n  LogId(),\n  mName(\"\")\n{\n  // empty\n}\n\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nEosAuthOfsDirectory::~EosAuthOfsDirectory()\n{\n  // emtpy\n}\n\n\n//------------------------------------------------------------------------------\n// Open a directory\n//------------------------------------------------------------------------------\nint\nEosAuthOfsDirectory::open(const char* name,\n                          const XrdSecClientName* client,\n                          const char* opaque)\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"dir open name=%s\", name);\n  mName = name; // save only for debugging purposes\n  std::ostringstream sstr;\n  // Add the current machine's IP to the uuid in order to avoid collisions in case\n  // we have multiple auth plugins connecting to the same MGM node \n  sstr << gOFS->mManagerIp << \":\" << this;\n  RequestProto* req_proto = utils::GetDirOpenRequest(sstr.str(), name, client,\n                                   opaque, error.getErrUser(), error.getErrMid());\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto))\n  {\n    eos_err(\"error HMAC dir open\");\n    delete req_proto;\n    return retc;\n  }\n  \n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  gOFS->mPoolSocket.wait_pop(socket);\n\n  if (gOFS->SendProtoBufRequest(socket, req_proto))\n  {\n    ResponseProto* resp_open = static_cast<ResponseProto*>(gOFS->GetResponse(socket));\n\n    if (resp_open)\n    {\n      retc = resp_open->response();\n      eos_debug(\"got response for dir open request\");\n      delete resp_open;\n    }\n  }\n  \n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Get entry of an open directory\n//------------------------------------------------------------------------------\nconst char*\nEosAuthOfsDirectory::nextEntry()\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"dir read name=%s\", mName.c_str());\n  std::ostringstream sstr;\n  sstr << gOFS->mManagerIp << \":\" << this;\n  RequestProto* req_proto = utils::GetDirReadRequest(sstr.str());\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto))\n  {\n    eos_err(\"error HMAC dir nextEntry\");\n    delete req_proto;\n    return static_cast<const char*>(0) ;\n  }\n  \n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  gOFS->mPoolSocket.wait_pop(socket);\n\n  if (gOFS->SendProtoBufRequest(socket, req_proto))\n  {\n    ResponseProto* resp_read = static_cast<ResponseProto*>(gOFS->GetResponse(socket));\n\n    if (resp_read)\n    {\n      retc = resp_read->response();\n      eos_debug(\"got response for dir read request\");\n      \n      if (retc == SFS_OK)\n      {\n        eos_debug(\"next entry is: %s\", resp_read->message().c_str());\n        mNextEntry = resp_read->message();\n      }\n      else \n      {\n        eos_debug(\"no more entries or error on server side\");\n      }\n      \n      delete resp_read;\n    }\n  }\n  \n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return (retc ? static_cast<const char*>(0) : mNextEntry.c_str());\n}\n\n\n//------------------------------------------------------------------------------\n// Close an open directory\n//------------------------------------------------------------------------------\nint\nEosAuthOfsDirectory::close()\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"dir close name=%s\", mName.c_str());\n  std::ostringstream sstr;\n  sstr << gOFS->mManagerIp << \":\" << this;\n  RequestProto* req_proto = utils::GetDirCloseRequest(sstr.str());\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto))\n  {\n    eos_err(\"error dir close\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  gOFS->mPoolSocket.wait_pop(socket);\n\n  if (gOFS->SendProtoBufRequest(socket, req_proto))\n  {\n    ResponseProto* resp_close = static_cast<ResponseProto*>(gOFS->GetResponse(socket));\n\n    if (resp_close)\n    {\n      retc = resp_close->response();\n      eos_debug(\"got response dir close request\");\n      delete resp_close;\n    }\n  }\n  \n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Get name of an open directory\n//------------------------------------------------------------------------------\nconst char*\nEosAuthOfsDirectory::FName()\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"dir fname\");\n  std::ostringstream sstr;\n  sstr << gOFS->mManagerIp << \":\" << this;\n  RequestProto* req_proto = utils::GetDirFnameRequest(sstr.str());\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto))\n  {\n    eos_err(\"error HMAC dir fname\");\n    delete req_proto;\n    return static_cast<const char*>(0) ;\n  }\n  \n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  gOFS->mPoolSocket.wait_pop(socket);\n\n  if (gOFS->SendProtoBufRequest(socket, req_proto))\n  {\n    ResponseProto* resp_fname = static_cast<ResponseProto*>(gOFS->GetResponse(socket));\n\n    if (resp_fname)\n    {\n      retc = resp_fname->response();\n      eos_debug(\"got response for dirfname request\");\n      \n      if (retc == SFS_OK)\n      {\n        eos_debug(\"dir fname is: %s\", resp_fname->message().c_str());\n        mName = resp_fname->message();\n      }\n      else \n      {\n        eos_debug(\"dir fname not found or error on server side\");\n      }\n      \n      delete resp_fname;\n    }\n  }\n  \n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return (retc ? static_cast<const char*>(0) : mName.c_str());\n}\n\nEOSAUTHNAMESPACE_END\n"
  },
  {
    "path": "auth_plugin/EosAuthOfsDirectory.hh",
    "content": "//------------------------------------------------------------------------------\n// File: EosAuthOfsDirectory.hh\n// Author: Elvin-Alin Sindrilau <esindril@cern.ch> CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSAUTH_OFSDIRECTORY__HH__\n#define __EOSAUTH_OFSDIRECTORY__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/Logging.hh\"\n#include \"common/Mapping.hh\"\n#include \"Namespace.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucErrInfo.hh>\n#include <XrdSec/XrdSecEntity.hh>\n#include <XrdSfs/XrdSfsInterface.hh>\n/*----------------------------------------------------------------------------*/\n\nEOSAUTHNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class implementing OFS directories\n//------------------------------------------------------------------------------\nclass EosAuthOfsDirectory: public XrdSfsDirectory, public eos::common::LogId\n{\n  public:\n\n    //--------------------------------------------------------------------------\n    //! Constructor\n    //--------------------------------------------------------------------------\n    EosAuthOfsDirectory(char* user = 0, int MonID = 0);\n\n    //--------------------------------------------------------------------------\n    //! Destructor\n    //--------------------------------------------------------------------------\n    ~EosAuthOfsDirectory();\n\n\n    //--------------------------------------------------------------------------\n    //! Open a directory\n    //--------------------------------------------------------------------------\n    int open(const char* name,\n             const XrdSecClientName* client = 0,\n             const char* opaque = 0);\n\n\n    //--------------------------------------------------------------------------\n    //! Get entry of an open directory\n    //--------------------------------------------------------------------------\n    const char* nextEntry();\n\n\n    //--------------------------------------------------------------------------\n    //! Close directory\n    //--------------------------------------------------------------------------\n    int close();\n\n\n    //--------------------------------------------------------------------------\n    //! Get name of an open directory\n    //--------------------------------------------------------------------------\n    const char* FName();\n\n  private:\n\n    std::string mName; ///< keep directory name just for debugging purposes\n    std::string mNextEntry; ///< next entry value in directory\n};\n\nEOSAUTHNAMESPACE_END\n\n#endif // __EOSAUTH_OFSDIRECTORY_HH__\n"
  },
  {
    "path": "auth_plugin/EosAuthOfsFile.cc",
    "content": "//------------------------------------------------------------------------------\n// File: EosAuthOfsFile.cc\n// Author: Elvin-Alin Sindrilau <esindril@cern.ch> CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"EosAuthOfsFile.hh\"\n#include \"EosAuthOfs.hh\"\n#include \"ProtoUtils.hh\"\n/*----------------------------------------------------------------------------*/\n#include <sstream>\n/*----------------------------------------------------------------------------*/\n\nEOSAUTHNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nEosAuthOfsFile::EosAuthOfsFile(char* user, int MonID):\n  XrdSfsFile(user, MonID),\n  eos::common::LogId(),\n  mName(\"\")\n{\n  // emtpy\n}\n\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nEosAuthOfsFile::~EosAuthOfsFile()\n{\n  // empty\n}\n\n\n//------------------------------------------------------------------------------\n// Open a file\n//------------------------------------------------------------------------------\nint\nEosAuthOfsFile::open(const char* fileName,\n                     XrdSfsFileOpenMode openMode,\n                     mode_t createMode,\n                     const XrdSecEntity* client,\n                     const char* opaque)\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"file open name=%s opaque=%s\", fileName, opaque);\n  mName = fileName;\n  // Save file pointer value which is used as a key on the MGM instance\n  std::ostringstream sstr;\n  // Add the current machine's IP to the uuid in order to avoid collisions in case\n  // we have multiple auth plugins connecting to the same MGM node\n  sstr << gOFS->mManagerIp << \":\" << this;\n  RequestProto* req_proto = utils::GetFileOpenRequest(sstr.str(), fileName,\n                            openMode, createMode, client, opaque,\n                            error.getErrUser(), error.getErrMid());\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC file open\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  gOFS->mPoolSocket.wait_pop(socket);\n\n  if (gOFS->SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_open = static_cast<ResponseProto*>(gOFS->GetResponse(\n                                 socket));\n\n    if (resp_open) {\n      retc = resp_open->response();\n      eos_debug(\"got response for file open request: %i\", retc);\n\n      if (resp_open->has_error()) {\n        error.setErrInfo(resp_open->error().code(),\n                         resp_open->error().message().c_str());\n      }\n\n      delete resp_open;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Read function\n//------------------------------------------------------------------------------\nXrdSfsXferSize\nEosAuthOfsFile::read(XrdSfsFileOffset offset,\n                     char* buffer,\n                     XrdSfsXferSize length)\n{\n  int retc = 0;  // this means read 0 bytes and NOT SFS_OK :)\n  eos_debug(\"read off=%li len=%i\", (long long)offset, (int)length);\n  std::ostringstream sstr;\n  sstr << gOFS->mManagerIp << \":\" << this;\n  eos_debug(\"fptr=%s, off=%li, len=%i\", sstr.str().c_str(), offset, length);\n  RequestProto* req_proto = utils::GetFileReadRequest(sstr.str(), offset, length);\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC file read\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  gOFS->mPoolSocket.wait_pop(socket);\n\n  if (gOFS->SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_fread = static_cast<ResponseProto*>(gOFS->GetResponse(\n                                  socket));\n\n    if (resp_fread) {\n      retc = resp_fread->response();\n\n      if (retc && resp_fread->has_message()) {\n        buffer = static_cast<char*>(memcpy((void*)buffer,\n                                           resp_fread->message().c_str(),\n                                           resp_fread->message().length()));\n      }\n\n      delete resp_fread;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Write function\n//------------------------------------------------------------------------------\nXrdSfsXferSize\nEosAuthOfsFile::write(XrdSfsFileOffset offset,\n                      const char* buffer,\n                      XrdSfsXferSize length)\n{\n  int retc = 0;  // this means written 0 bytes and NOT SFS_OK :)\n  eos_debug(\"write off=%ll len=%i\", offset, length);\n  std::ostringstream sstr;\n  sstr << gOFS->mManagerIp << \":\" << this;\n  eos_debug(\"fptr=%s, off=%li, len=%i\", sstr.str().c_str(), offset, length);\n  RequestProto* req_proto = utils::GetFileWriteRequest(sstr.str(), offset, buffer,\n                            length);\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC file write\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  gOFS->mPoolSocket.wait_pop(socket);\n\n  if (gOFS->SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_fwrite = static_cast<ResponseProto*>(gOFS->GetResponse(\n                                   socket));\n\n    if (resp_fwrite) {\n      retc = resp_fwrite->response();\n      eos_debug(\"got response for file write request\");\n      delete resp_fwrite;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Get name of file\n//------------------------------------------------------------------------------\nconst char*\nEosAuthOfsFile::FName()\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"file fname\");\n  std::ostringstream sstr;\n  sstr << gOFS->mManagerIp << \":\" << this;\n  eos_debug(\"file pointer: %s\", sstr.str().c_str());\n  RequestProto* req_proto = utils::GetFileFnameRequest(sstr.str());\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC file name\");\n    delete req_proto;\n    return \"\";\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  gOFS->mPoolSocket.wait_pop(socket);\n\n  if (gOFS->SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_fname = static_cast<ResponseProto*>(gOFS->GetResponse(\n                                  socket));\n\n    if (resp_fname) {\n      retc = resp_fname->response();\n      eos_debug(\"got response for filefname request\");\n\n      if (retc == SFS_OK) {\n        eos_debug(\"file fname is: %s\", resp_fname->message().c_str());\n        mName = resp_fname->message();\n      } else {\n        eos_debug(\"file fname not found or error on server side\");\n      }\n\n      delete resp_fname;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return (retc ? static_cast<const char*>(0) :\n          (mName.empty() ? \"\" : mName.c_str()));\n}\n\n\n//------------------------------------------------------------------------------\n// Stat function\n//------------------------------------------------------------------------------\nint\nEosAuthOfsFile::stat(struct stat* buf)\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"stat file name=%s\", mName.c_str());\n  std::ostringstream sstr;\n  sstr << gOFS->mManagerIp << \":\" << this;\n  eos_debug(\"file pointer: %s\", sstr.str().c_str());\n  RequestProto* req_proto = utils::GetFileStatRequest(sstr.str());\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC file stat\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  gOFS->mPoolSocket.wait_pop(socket);\n\n  if (gOFS->SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_fstat = static_cast<ResponseProto*>(gOFS->GetResponse(\n                                  socket));\n\n    if (resp_fstat) {\n      retc = resp_fstat->response();\n      buf = static_cast<struct stat*>(memcpy((void*)buf,\n                                             resp_fstat->message().c_str(),\n                                             sizeof(struct stat)));\n      eos_debug(\"got response for fstat request: %i\", retc);\n      delete resp_fstat;\n    }\n  } else {\n    eos_err(\"file stat - unable to send request\");\n    memset(buf, 0, sizeof(struct stat));\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n//! Close file\n//------------------------------------------------------------------------------\nint\nEosAuthOfsFile::close()\n{\n  int retc = SFS_ERROR;\n  eos_debug(\"close\");\n  std::ostringstream sstr;\n  sstr << gOFS->mManagerIp << \":\" << this;\n  eos_debug(\"file pointer: %s\", sstr.str().c_str());\n  RequestProto* req_proto = utils::GetFileCloseRequest(sstr.str());\n\n  // Compute HMAC for request object\n  if (!utils::ComputeHMAC(req_proto)) {\n    eos_err(\"error HMAC file close\");\n    delete req_proto;\n    return retc;\n  }\n\n  // Get a socket object from the pool\n  zmq::socket_t* socket;\n  gOFS->mPoolSocket.wait_pop(socket);\n\n  if (gOFS->SendProtoBufRequest(socket, req_proto)) {\n    ResponseProto* resp_close = static_cast<ResponseProto*>(gOFS->GetResponse(\n                                  socket));\n\n    if (resp_close) {\n      retc = resp_close->response();\n      eos_debug(\"got response for file close request: %i\", retc);\n      delete resp_close;\n    }\n  }\n\n  // Release socket and free memory\n  gOFS->mPoolSocket.push(socket);\n  delete req_proto;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n//!!!!!!!!! THE FOLLOWING OPERATIONS ARE NOT SUPPORTED !!!!!!!!!\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// fctl fakes ok (not supported)\n//------------------------------------------------------------------------------\nint\nEosAuthOfsFile::fctl(int, const char*, XrdOucErrInfo&)\n{\n  return 0;\n}\n\n\n//------------------------------------------------------------------------------\n// Return mmap address (not supported)\n//------------------------------------------------------------------------------\nint\nEosAuthOfsFile::getMmap(void** Addr, off_t& Size)\n{\n  if (Addr) {\n    Addr = 0;\n  }\n\n  Size = 0;\n  return SFS_OK;\n}\n\n\n//------------------------------------------------------------------------------\n// File pre-read fakes ok (not supported)\n//------------------------------------------------------------------------------\nint\nEosAuthOfsFile::read(XrdSfsFileOffset fileOffset, XrdSfsXferSize preread_sz)\n{\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// File read in async mode (not supported)\n//------------------------------------------------------------------------------\nint\nEosAuthOfsFile::read(XrdSfsAio* aioparm)\n{\n  static const char* epname = \"read\";\n  return Emsg(epname, error, EOPNOTSUPP, \"read\", mName.c_str());\n}\n\n\n//------------------------------------------------------------------------------\n// File write in async mode (not supported)\n//------------------------------------------------------------------------------\nint\nEosAuthOfsFile::write(XrdSfsAio* aiop)\n{\n  static const char* epname = \"write\";\n  return Emsg(epname, error, EOPNOTSUPP, \"write\", mName.c_str());\n}\n\n\n//------------------------------------------------------------------------------\n// File sync (not supported)\n//------------------------------------------------------------------------------\nint\nEosAuthOfsFile::sync()\n{\n  static const char* epname = \"sync\";\n  return Emsg(epname, error, EOPNOTSUPP, \"sync\", mName.c_str());\n}\n\n\n//------------------------------------------------------------------------------\n// File async sync (not supported)\n//------------------------------------------------------------------------------\nint\nEosAuthOfsFile::sync(XrdSfsAio* aiop)\n{\n  static const char* epname = \"sync\";\n  return Emsg(epname, error, EOPNOTSUPP, \"sync\", mName.c_str());\n}\n\n\n//------------------------------------------------------------------------------\n// File truncate (not supported)\n//------------------------------------------------------------------------------\nint\nEosAuthOfsFile::truncate(XrdSfsFileOffset flen)\n{\n  static const char* epname = \"trunc\";\n  return Emsg(epname, error, EOPNOTSUPP, \"truncate\", mName.c_str());\n}\n\n\n//------------------------------------------------------------------------------\n// Get checksum info (returns nothing - not supported)\n//------------------------------------------------------------------------------\nint\nEosAuthOfsFile::getCXinfo(char cxtype[4], int& cxrsz)\n{\n  return cxrsz = 0;\n}\n\n\n//------------------------------------------------------------------------------\n// Create error message\n//------------------------------------------------------------------------------\nint\nEosAuthOfsFile::Emsg(const char* pfx,\n                     XrdOucErrInfo& einfo,\n                     int ecode,\n                     const char* op,\n                     const char* target)\n{\n  char* etext, buffer[4096], unkbuff[64];\n\n  // Get the reason for the error\n  if (ecode < 0) {\n    ecode = -ecode;\n  }\n\n  if (!(etext = strerror(ecode))) {\n    sprintf(unkbuff, \"reason unknown (%d)\", ecode);\n    etext = unkbuff;\n  }\n\n  // Format the error message\n  snprintf(buffer, sizeof(buffer), \"Unable to %s %s; %s\", op, target, etext);\n  eos_err(\"Unable to %s %s; %s\", op, target, etext);\n  // Place the error message in the error object and return\n  einfo.setErrInfo(ecode, buffer);\n  return SFS_ERROR;\n}\n\nEOSAUTHNAMESPACE_END\n"
  },
  {
    "path": "auth_plugin/EosAuthOfsFile.hh",
    "content": "//------------------------------------------------------------------------------\n// File: EosAuthOfsFile.hh\n// Author: Elvin-Alin Sindrilau <esindril@cern.ch> CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSAUTH_OFSFILE__HH__\n#define __EOSAUTH_OFSFILE__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/Logging.hh\"\n#include \"common/Mapping.hh\"\n#include \"Namespace.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdSec/XrdSecEntity.hh>\n#include <XrdSfs/XrdSfsInterface.hh>\n/*----------------------------------------------------------------------------*/\n\nEOSAUTHNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class implementing OFS files\n//------------------------------------------------------------------------------\nclass EosAuthOfsFile: public XrdSfsFile, public eos::common::LogId\n{\n  public:\n\n    //--------------------------------------------------------------------------\n    //! Constructor\n    //--------------------------------------------------------------------------\n    EosAuthOfsFile(char* user = 0, int MonID = 0);\n\n\n    //--------------------------------------------------------------------------\n    //! Destructor\n    //--------------------------------------------------------------------------\n    ~EosAuthOfsFile();\n\n\n    //--------------------------------------------------------------------------\n    //! Open a file\n    //--------------------------------------------------------------------------\n    int open(const char* fileName,\n             XrdSfsFileOpenMode openMode,\n             mode_t createMode,\n             const XrdSecEntity* client,\n             const char* opaque = 0);\n\n\n    //--------------------------------------------------------------------------\n    //! Read function\n    //--------------------------------------------------------------------------\n    virtual XrdSfsXferSize read(XrdSfsFileOffset offset,\n                                char* buffer,\n                                XrdSfsXferSize length);\n\n\n    //--------------------------------------------------------------------------\n    //! Write function\n    //--------------------------------------------------------------------------\n    virtual XrdSfsXferSize write(XrdSfsFileOffset offset,\n                                 const char* buffer,\n                                 XrdSfsXferSize length);\n\n\n    //--------------------------------------------------------------------------\n    //! Stat function\n    //--------------------------------------------------------------------------\n    virtual int stat(struct stat* buf);\n\n\n    //--------------------------------------------------------------------------\n    //! Close file\n    //--------------------------------------------------------------------------\n    int close();\n\n\n    //--------------------------------------------------------------------------\n    //! Get name of file\n    //--------------------------------------------------------------------------\n    const char* FName();\n\n\n//------------------------------------------------------------------------------\n//!!!!!!!!! THE FOLLOWING OPERATIONS ARE NOT SUPPORTED !!!!!!!!!\n//------------------------------------------------------------------------------\n\n\n    //--------------------------------------------------------------------------\n    //! fctl fakes ok (not supported)\n    //--------------------------------------------------------------------------\n    int fctl(int, const char*, XrdOucErrInfo&);\n\n\n    //--------------------------------------------------------------------------\n    //! Return mmap address (not supported)\n    //--------------------------------------------------------------------------\n    int getMmap(void** Addr, off_t& Size);\n\n\n    //--------------------------------------------------------------------------\n    //! File pre-read fakes ok (not supported)\n    //--------------------------------------------------------------------------\n    int read(XrdSfsFileOffset fileOffset, XrdSfsXferSize preread_sz);\n\n\n    //--------------------------------------------------------------------------\n    //! File read in async mode (not supported)\n    //--------------------------------------------------------------------------\n    int read(XrdSfsAio* aioparm);\n\n\n    //--------------------------------------------------------------------------\n    //! File write in async mode (not supported)\n    //--------------------------------------------------------------------------\n    int write(XrdSfsAio* aiop);\n\n\n    //--------------------------------------------------------------------------\n    //! File sync (not supported)\n    //--------------------------------------------------------------------------\n    int sync();\n\n\n    //--------------------------------------------------------------------------\n    //! File async sync (not supported)\n    //--------------------------------------------------------------------------\n    int sync(XrdSfsAio* aiop);\n\n\n    //--------------------------------------------------------------------------\n    //! File truncate (not supported)\n    //--------------------------------------------------------------------------\n    int truncate(XrdSfsFileOffset flen);\n\n\n    //--------------------------------------------------------------------------\n    //! get checksum info (returns nothing - not supported)\n    //--------------------------------------------------------------------------\n    int getCXinfo(char cxtype[4], int& cxrsz);\n\n\n  private:\n\n    std::string mName; ///< file name\n\n    //--------------------------------------------------------------------------\n    //! Create an error message for a file object\n    //!\n    //! @param pfx message prefix value\n    //! @param einfo error text/code object\n    //! @param ecode error code\n    //! @param op name of the operation performed\n    //! @param target target of the operation e.g. file name etc.\n    //!\n    //! @return SFS_ERROR in all cases\n    //!\n    //! This routines prints also an error message into the EOS log.\n    //!\n    //--------------------------------------------------------------------------\n    int Emsg(const char* pfx,\n             XrdOucErrInfo& einfo,\n             int ecode,\n             const char* op,\n             const char* target);\n\n};\n\nEOSAUTHNAMESPACE_END\n\n#endif // __EOSAUTH_OFSFILE_HH__\n"
  },
  {
    "path": "auth_plugin/Namespace.hh",
    "content": "//------------------------------------------------------------------------------\n// File: Namespace.hh\n// Author: Elvin-Alin Sindrialru <esindril@cern.ch> CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSAUTH_NAMESPACE_HH__\n#define __EOSAUTH_NAMESPACE_HH__\n\n#define EOSAUTHNAMESPACE_BEGIN namespace eos { namespace auth {\n#define EOSAUTHNAMESPACE_END }}\n\n#endif\n"
  },
  {
    "path": "auth_plugin/ProtoUtils.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ProtoUtils.cc\n// Author: Elvin-Alin Sindrilaru <esindril@cern.ch> CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"ProtoUtils.hh\"\n#include \"common/Logging.hh\"\n#include \"common/SymKeys.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdOuc/XrdOucTList.hh>\n#include <XrdOuc/XrdOucErrInfo.hh>\n#include <XrdSfs/XrdSfsInterface.hh>\n#include <XrdSec/XrdSecEntity.hh>\n#include <google/protobuf/util/json_util.h>\n#include <sstream>\n\nEOSAUTHNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Convert XrdSecEntity object to ProtocolBuffers representation\n//------------------------------------------------------------------------------\nvoid\nutils::ConvertToProtoBuf(const XrdSecEntity* obj,\n                         XrdSecEntityProto*& proto)\n{\n  proto->set_prot(obj->prot);\n\n  if (obj->name) {\n    proto->set_name(obj->name);\n  } else {\n    proto->set_name(\"\");\n  }\n\n  if (obj->host) {\n    proto->set_host(obj->host);\n  } else {\n    proto->set_host(\"\");\n  }\n\n  if (obj->vorg) {\n    proto->set_vorg(obj->vorg);\n  } else {\n    proto->set_vorg(\"\");\n  }\n\n  if (obj->role) {\n    proto->set_role(obj->role);\n  } else {\n    proto->set_role(\"\");\n  }\n\n  if (obj->grps) {\n    proto->set_grps(obj->grps);\n  } else {\n    proto->set_grps(\"\");\n  }\n\n  if (obj->endorsements) {\n    proto->set_endorsements(obj->endorsements);\n  } else {\n    proto->set_endorsements(\"\");\n  }\n\n  if (obj->creds) {\n    proto->set_creds(obj->creds);\n  } else {\n    proto->set_creds(\"\");\n  }\n\n  proto->set_credslen(obj->credslen);\n\n  if (obj->moninfo) {\n    proto->set_moninfo(obj->moninfo);\n  } else {\n    proto->set_moninfo(\"\");\n  }\n\n  if (obj->tident) {\n    proto->set_tident(obj->tident);\n  } else {\n    proto->set_tident(\"\");\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Convert XrdOucErrInfo object to ProtocolBuffers representation\n//------------------------------------------------------------------------------\nvoid\nutils::ConvertToProtoBuf(XrdOucErrInfo* obj,\n                         XrdOucErrInfoProto*& proto)\n{\n  proto->set_user(obj->getErrUser());\n  proto->set_code(obj->getErrInfo());\n  proto->set_message(obj->getErrText());\n}\n\n\n//------------------------------------------------------------------------------\n// Convert XrSfsFSctl object to ProtocolBuffers representation\n//------------------------------------------------------------------------------\nvoid\nutils::ConvertToProtoBuf(const XrdSfsFSctl* obj,\n                         XrdSfsFSctlProto*& proto)\n{\n  if (obj->Arg1) {\n    proto->set_arg1(obj->Arg1);\n  }\n\n  if (obj->Arg2) {\n    proto->set_arg2(obj->Arg2);\n  }\n\n  proto->set_arg1len(obj->Arg1Len);\n  proto->set_arg2len(obj->Arg2Len);\n}\n\n\n//------------------------------------------------------------------------------\n// Convert XrSfsPrep object to ProtocolBuffers representation\n//------------------------------------------------------------------------------\nvoid\nutils::ConvertToProtoBuf(const XrdSfsPrep* obj,\n                         XrdSfsPrepProto*& proto)\n{\n  proto->set_reqid(obj->reqid ?  obj->reqid : \"\");\n  proto->set_notify(obj->notify ? obj->notify : \"\");\n  proto->set_opts(obj->opts);\n  XrdOucTList* next_path = obj->paths;\n  XrdOucTList* next_oinfo = obj->oinfo;\n\n  while (next_path && next_oinfo) {\n    if (next_path->text && next_oinfo->text) {\n      proto->add_paths(next_path->text);\n      next_path = next_path->next;\n      proto->add_oinfo(next_oinfo->text);\n      next_oinfo = next_oinfo->next;\n    }\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Get XrdSecEntity object from protocol buffer object\n//------------------------------------------------------------------------------\nXrdSecEntity*\nutils::GetXrdSecEntity(const XrdSecEntityProto& proto_obj)\n{\n  XrdSecEntity* obj = new XrdSecEntity();\n  strncpy(obj->prot, proto_obj.prot().c_str(), XrdSecPROTOIDSIZE - 1);\n  obj->prot[XrdSecPROTOIDSIZE - 1] = '\\0';\n  obj->name = strdup(proto_obj.name().c_str());\n  obj->host = strdup(proto_obj.host().c_str());\n  obj->vorg = strdup(proto_obj.vorg().c_str());\n  obj->role = strdup(proto_obj.role().c_str());\n  obj->grps = strdup(proto_obj.grps().c_str());\n  obj->endorsements = strdup(proto_obj.endorsements().c_str());\n  obj->creds = strdup(proto_obj.creds().c_str());\n  obj->credslen = proto_obj.credslen();\n  obj->moninfo = strdup(proto_obj.moninfo().c_str());\n  obj->tident = strdup(proto_obj.tident().c_str());\n  return obj;\n}\n\n\n//------------------------------------------------------------------------------\n// Delete XrdSecEntity object\n//------------------------------------------------------------------------------\nvoid\nutils::DeleteXrdSecEntity(XrdSecEntity*& obj)\n{\n  free(obj->name);\n  free(obj->host);\n  free(obj->vorg);\n  free(obj->role);\n  free(obj->grps);\n  free(obj->endorsements);\n  free(obj->creds);\n  free(obj->moninfo);\n  free(const_cast<char*>(obj->tident));\n  delete obj;\n  obj = 0;\n}\n\n//------------------------------------------------------------------------------\n// Get XrdSfsPrep object from protocol buffer object\n//------------------------------------------------------------------------------\nXrdSfsPrep*\nutils::GetXrdSfsPrep(const eos::auth::XrdSfsPrepProto& proto_obj)\n{\n  XrdSfsPrep* obj = new XrdSfsPrep();\n  obj->reqid = ((proto_obj.reqid() == \"\") ? 0 : strdup(\n                  proto_obj.reqid().c_str()));\n  obj->notify = ((proto_obj.notify() == \"\") ? 0 : strdup(\n                   proto_obj.notify().c_str()));\n  obj->opts = proto_obj.opts();\n  obj->paths = obj->oinfo = 0;\n  XrdOucTList*& next_paths = obj->paths;\n  XrdOucTList*& next_oinfo = obj->oinfo;\n\n  // The number of paths and oinfo should match\n  if (proto_obj.paths_size() != proto_obj.oinfo_size()) {\n    return obj;\n  }\n\n  XrdOucTList* previousPath = obj->paths;\n  XrdOucTList* previousOinfo = obj->oinfo;\n\n  for (int i = 0; i < proto_obj.paths_size(); i++) {\n    auto currentPath = new XrdOucTList(proto_obj.paths(i).c_str());\n\n    if (next_paths) {\n      previousPath->next = currentPath;\n    } else {\n      next_paths = currentPath;\n    }\n\n    previousPath = currentPath;\n    currentPath = 0;\n    auto currentOinfo = new XrdOucTList(proto_obj.oinfo(i).c_str());\n\n    if (next_oinfo) {\n      previousOinfo->next = currentOinfo;\n    } else {\n      next_oinfo = currentOinfo;\n    }\n\n    previousOinfo = currentOinfo;\n    currentOinfo = 0;\n  }\n\n  return obj;\n}\n\n//------------------------------------------------------------------------------\n// Delete DeleteXrdSfsPrep object\n//------------------------------------------------------------------------------\nvoid utils::DeleteXrdSfsPrep(XrdSfsPrep*& obj)\n{\n  if (obj->reqid) {\n    free(obj->reqid);\n  }\n\n  if (obj->notify != nullptr) {\n    free(obj->notify);\n  }\n\n  XrdOucTList* currentPath = obj->paths;\n\n  while (currentPath != nullptr) {\n    XrdOucTList* nextPath = currentPath->next;\n    delete currentPath;\n    currentPath = nextPath;\n  }\n\n  XrdOucTList* currentOinfo = obj->oinfo;\n\n  while (currentOinfo != nullptr) {\n    XrdOucTList* nextOinfo = currentOinfo->next;\n    delete currentOinfo;\n    currentOinfo = nextOinfo;\n  }\n\n  delete obj;\n}\n\n\n//------------------------------------------------------------------------------\n// Get XrdOucErrInfo object from protocol buffer object\n//------------------------------------------------------------------------------\nXrdOucErrInfo*\nutils::GetXrdOucErrInfo(const eos::auth::XrdOucErrInfoProto& proto_obj)\n{\n  XrdOucErrInfo* obj = new XrdOucErrInfo(proto_obj.user().c_str());\n  obj->setErrInfo(proto_obj.code(), proto_obj.message().c_str());\n  return obj;\n}\n\n\n//------------------------------------------------------------------------------\n// Get XrdSfsFSctl object from protocol buffer object\n//------------------------------------------------------------------------------\nXrdSfsFSctl*\nutils::GetXrdSfsFSctl(const eos::auth::XrdSfsFSctlProto& proto_obj)\n{\n  XrdSfsFSctl* obj = new XrdSfsFSctl();\n  obj->Arg1 = static_cast<const char*>(0);\n  obj->Arg2 = static_cast<const char*>(0);\n  obj->Arg1Len = proto_obj.arg1len();\n  obj->Arg2Len = proto_obj.arg2len();\n\n  if (proto_obj.has_arg1()) {\n    obj->Arg1 = const_cast<const char*>(strdup(proto_obj.arg1().c_str()));\n  }\n\n  if (proto_obj.has_arg2()) {\n    obj->Arg2 = const_cast<const char*>(strdup(proto_obj.arg2().c_str()));\n  }\n\n  return obj;\n}\n\n\n//------------------------------------------------------------------------------\n// Delete XrdSfsFSctl object\n//------------------------------------------------------------------------------\nvoid\nutils::DeleteXrdSfsFSctl(XrdSfsFSctl*& obj)\n{\n  free((void*)obj->Arg1);\n  free((void*)obj->Arg2);\n  delete obj;\n  obj = 0;\n}\n\n\n//------------------------------------------------------------------------------\n// Compute HMAC value of the RequestProto object and append it to the\n// object using the required field hmac\n//------------------------------------------------------------------------------\nbool\nutils::ComputeHMAC(RequestProto*& req)\n{\n  std::string smsg;\n  req->set_hmac(\"\"); // set it temporarily, we update it later\n\n  if (!req->SerializeToString(&smsg)) {\n    eos_static_err(\"unable to serialize message to string for HMAC computation\");\n    return false;\n  }\n\n  std::string hmac = eos::common::SymKey::HmacSha1(smsg);\n  XrdOucString base64hmac;\n  bool do_encoding = eos::common::SymKey::Base64Encode((char*)hmac.c_str(),\n                     hmac.length(), base64hmac);\n\n  if (!do_encoding) {\n    eos_static_err(\"unable to do base64encoding on HMAC\");\n    return do_encoding;\n  }\n\n  if (EOS_LOGS_DEBUG) {\n    std::string json_out;\n    (void) google::protobuf::util::MessageToJsonString(*req, &json_out);\n    eos_static_debug(\"request=\\\"%s\\\" hmac=\\\"%s\\\" hmac_size=%i\",\n                     json_out.c_str(), base64hmac.c_str(), base64hmac.length());\n  }\n\n  // Update the HMAC value\n  req->set_hmac(base64hmac.c_str());\n  return true;\n}\n\n\n//------------------------------------------------------------------------------\n// Create StatProto object\n//------------------------------------------------------------------------------\nRequestProto*\nutils::GetStatRequest(RequestProto_OperationType type,\n                      const char* path,\n                      XrdOucErrInfo& error,\n                      const XrdSecEntity* client,\n                      const char* opaque)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::StatProto* stat_proto = req_proto->mutable_stat();\n  eos::auth::XrdOucErrInfoProto* xoei_proto = stat_proto->mutable_error();\n  eos::auth::XrdSecEntityProto* xse_proto = stat_proto->mutable_client();\n  stat_proto->set_path(path);\n  ConvertToProtoBuf(&error, xoei_proto);\n  ConvertToProtoBuf(client, xse_proto);\n\n  if (opaque) {\n    stat_proto->set_opaque(opaque);\n  }\n\n  // This can either be a stat to get a struct stat or just to retrieve the\n  // mode of the file/directory\n  req_proto->set_type(type);\n  return req_proto;\n}\n\n\n//------------------------------------------------------------------------------\n// Create fsctl request ProtocolBuffer object\n//------------------------------------------------------------------------------\nRequestProto*\nutils::GetFsctlRequest(const int cmd,\n                       const char* args,\n                       XrdOucErrInfo& error,\n                       const XrdSecEntity* client)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::FsctlProto* fsctl_proto = req_proto->mutable_fsctl1();\n  eos::auth::XrdOucErrInfoProto* xoei_proto = fsctl_proto->mutable_error();\n  eos::auth::XrdSecEntityProto* xse_proto = fsctl_proto->mutable_client();\n  fsctl_proto->set_cmd(cmd);\n  fsctl_proto->set_args(args);\n  ConvertToProtoBuf(&error, xoei_proto);\n  ConvertToProtoBuf(client, xse_proto);\n  req_proto->set_type(RequestProto_OperationType_FSCTL1);\n  return req_proto;\n}\n\n\n//------------------------------------------------------------------------------\n// Create FSctl request ProtocolBuffer object\n//------------------------------------------------------------------------------\nRequestProto*\nutils::GetFSctlRequest(const int cmd,\n                       XrdSfsFSctl& args,\n                       XrdOucErrInfo& error,\n                       const XrdSecEntity* client)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::FSctlProto* fsctl_proto = req_proto->mutable_fsctl2();\n  eos::auth::XrdSfsFSctlProto* args_proto = fsctl_proto->mutable_args();\n  eos::auth::XrdOucErrInfoProto* xoei_proto = fsctl_proto->mutable_error();\n  eos::auth::XrdSecEntityProto* xse_proto = fsctl_proto->mutable_client();\n  fsctl_proto->set_cmd(cmd);\n  ConvertToProtoBuf(&args, args_proto);\n  ConvertToProtoBuf(&error, xoei_proto);\n  ConvertToProtoBuf(client, xse_proto);\n  req_proto->set_type(RequestProto_OperationType_FSCTL2);\n  return req_proto;\n}\n\n\n//------------------------------------------------------------------------------\n// Create chmod request ProtocolBuffer object\n//------------------------------------------------------------------------------\nRequestProto*\nutils::GetChmodRequest(const char* path,\n                       int mode,\n                       XrdOucErrInfo& error,\n                       const XrdSecEntity* client,\n                       const char* opaque)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::ChmodProto* chmod_proto = req_proto->mutable_chmod();\n  eos::auth::XrdOucErrInfoProto* xoei_proto = chmod_proto->mutable_error();\n  eos::auth::XrdSecEntityProto* xse_proto = chmod_proto->mutable_client();\n  chmod_proto->set_path(path);\n  chmod_proto->set_mode(mode);\n  ConvertToProtoBuf(&error, xoei_proto);\n  ConvertToProtoBuf(client, xse_proto);\n\n  if (opaque) {\n    chmod_proto->set_opaque(opaque);\n  }\n\n  req_proto->set_type(RequestProto_OperationType_CHMOD);\n  return req_proto;\n}\n\n\n//------------------------------------------------------------------------------\n// Create chksum request ProtocolBuffer object\n//------------------------------------------------------------------------------\nRequestProto*\nutils::GetChksumRequest(XrdSfsFileSystem::csFunc func,\n                        const char* csname,\n                        const char* inpath,\n                        XrdOucErrInfo& error,\n                        const XrdSecEntity* client,\n                        const char* opaque)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::ChksumProto* chksum_proto = req_proto->mutable_chksum();\n  eos::auth::XrdOucErrInfoProto* xoei_proto = chksum_proto->mutable_error();\n  chksum_proto->set_func(func);\n  chksum_proto->set_csname(csname);\n\n  if (inpath) {\n    chksum_proto->set_path(inpath);\n  } else {\n    chksum_proto->set_path(\"\");\n  }\n\n  ConvertToProtoBuf(&error, xoei_proto);\n\n  if (client) {\n    eos::auth::XrdSecEntityProto* xse_proto = chksum_proto->mutable_client();\n    ConvertToProtoBuf(client, xse_proto);\n  }\n\n  if (opaque) {\n    chksum_proto->set_opaque(opaque);\n  }\n\n  req_proto->set_type(RequestProto_OperationType_CHKSUM);\n  return req_proto;\n}\n\n\n//------------------------------------------------------------------------------\n// Create exitst request ProtocolBuffer object\n//------------------------------------------------------------------------------\nRequestProto*\nutils::GetExistsRequest(const char* path,\n                        XrdOucErrInfo& error,\n                        const XrdSecEntity* client,\n                        const char* opaque)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::ExistsProto* exists_proto = req_proto->mutable_exists();\n  eos::auth::XrdOucErrInfoProto* xoei_proto = exists_proto->mutable_error();\n  eos::auth::XrdSecEntityProto* xse_proto = exists_proto->mutable_client();\n  exists_proto->set_path(path);\n  ConvertToProtoBuf(&error, xoei_proto);\n  ConvertToProtoBuf(client, xse_proto);\n\n  if (opaque) {\n    exists_proto->set_opaque(opaque);\n  }\n\n  req_proto->set_type(RequestProto_OperationType_EXISTS);\n  return req_proto;\n}\n\n\n//------------------------------------------------------------------------------\n// Create mkdir request ProtocolBuffer object\n//------------------------------------------------------------------------------\nRequestProto*\nutils::GetMkdirRequest(const char* path,\n                       int mode,\n                       XrdOucErrInfo& error,\n                       const XrdSecEntity* client,\n                       const char* opaque)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::MkdirProto* mkdir_proto = req_proto->mutable_mkdir();\n  eos::auth::XrdOucErrInfoProto* xoei_proto = mkdir_proto->mutable_error();\n  eos::auth::XrdSecEntityProto* xse_proto = mkdir_proto->mutable_client();\n  mkdir_proto->set_path(path);\n  mkdir_proto->set_mode(mode);\n  ConvertToProtoBuf(&error, xoei_proto);\n  ConvertToProtoBuf(client, xse_proto);\n\n  if (opaque) {\n    mkdir_proto->set_opaque(opaque);\n  }\n\n  req_proto->set_type(RequestProto_OperationType_MKDIR);\n  return req_proto;\n}\n\n\n\n//------------------------------------------------------------------------------\n// Create remdir request ProtocolBuffer object\n//------------------------------------------------------------------------------\nRequestProto*\nutils::GetRemdirRequest(const char* path,\n                        XrdOucErrInfo& error,\n                        const XrdSecEntity* client,\n                        const char* opaque)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::RemdirProto* remdir_proto = req_proto->mutable_remdir();\n  eos::auth::XrdOucErrInfoProto* xoei_proto = remdir_proto->mutable_error();\n  eos::auth::XrdSecEntityProto* xse_proto = remdir_proto->mutable_client();\n  remdir_proto->set_path(path);\n  ConvertToProtoBuf(&error, xoei_proto);\n  ConvertToProtoBuf(client, xse_proto);\n\n  if (opaque) {\n    remdir_proto->set_opaque(opaque);\n  }\n\n  req_proto->set_type(RequestProto_OperationType_REMDIR);\n  return req_proto;\n}\n\n\n//------------------------------------------------------------------------------\n// Create rem request ProtocolBuffer object\n//------------------------------------------------------------------------------\nRequestProto*\nutils::GetRemRequest(const char* path,\n                     XrdOucErrInfo& error,\n                     const XrdSecEntity* client,\n                     const char* opaque)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::RemProto* rem_proto = req_proto->mutable_rem();\n  eos::auth::XrdOucErrInfoProto* xoei_proto = rem_proto->mutable_error();\n  eos::auth::XrdSecEntityProto* xse_proto = rem_proto->mutable_client();\n  rem_proto->set_path(path);\n  ConvertToProtoBuf(&error, xoei_proto);\n  ConvertToProtoBuf(client, xse_proto);\n\n  if (opaque) {\n    rem_proto->set_opaque(opaque);\n  }\n\n  req_proto->set_type(RequestProto_OperationType_REM);\n  return req_proto;\n}\n\n\n//------------------------------------------------------------------------------\n// Create rename request ProtocolBuffer object\n//------------------------------------------------------------------------------\nRequestProto*\nutils::GetRenameRequest(const char* oldName,\n                        const char* newName,\n                        XrdOucErrInfo& error,\n                        const XrdSecEntity* client,\n                        const char* opaqueO,\n                        const char* opaqueN)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::RenameProto* rename_proto = req_proto->mutable_rename();\n  eos::auth::XrdOucErrInfoProto* xoei_proto = rename_proto->mutable_error();\n  eos::auth::XrdSecEntityProto* xse_proto = rename_proto->mutable_client();\n  rename_proto->set_oldname(oldName);\n  rename_proto->set_newname(newName);\n  ConvertToProtoBuf(&error, xoei_proto);\n  ConvertToProtoBuf(client, xse_proto);\n\n  if (opaqueO) {\n    rename_proto->set_opaqueo(opaqueO);\n  }\n\n  if (opaqueN) {\n    rename_proto->set_opaqueo(opaqueN);\n  }\n\n  req_proto->set_type(RequestProto_OperationType_RENAME);\n  return req_proto;\n}\n\n\n//--------------------------------------------------------------------------\n// Create prepare request ProtocolBuffer object\n//--------------------------------------------------------------------------\nRequestProto*\nutils::GetPrepareRequest(XrdSfsPrep& pargs,\n                         XrdOucErrInfo& error,\n                         const XrdSecEntity* client)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::PrepareProto* prepare_proto = req_proto->mutable_prepare();\n  eos::auth::XrdSfsPrepProto* xsp_proto = prepare_proto->mutable_pargs();\n  eos::auth::XrdOucErrInfoProto* xoei_proto = prepare_proto->mutable_error();\n  eos::auth::XrdSecEntityProto* xse_proto = prepare_proto->mutable_client();\n  ConvertToProtoBuf(&pargs, xsp_proto);\n  ConvertToProtoBuf(&error, xoei_proto);\n  ConvertToProtoBuf(client, xse_proto);\n  req_proto->set_type(RequestProto_OperationType_PREPARE);\n  return req_proto;\n}\n\n\n//--------------------------------------------------------------------------\n//! Create truncate request ProtocolBuffer object\n//--------------------------------------------------------------------------\nRequestProto*\nutils::GetTruncateRequest(const char* path,\n                          XrdSfsFileOffset fileOffset,\n                          XrdOucErrInfo& error,\n                          const XrdSecEntity* client,\n                          const char* opaque)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::TruncateProto* truncate_proto = req_proto->mutable_truncate();\n  eos::auth::XrdOucErrInfoProto* xoei_proto = truncate_proto->mutable_error();\n  eos::auth::XrdSecEntityProto* xse_proto = truncate_proto->mutable_client();\n  truncate_proto->set_path(path);\n  truncate_proto->set_fileoffset(fileOffset);\n  ConvertToProtoBuf(&error, xoei_proto);\n  ConvertToProtoBuf(client, xse_proto);\n\n  if (opaque) {\n    truncate_proto->set_opaque(opaque);\n  }\n\n  req_proto->set_type(RequestProto_OperationType_TRUNCATE);\n  return req_proto;\n}\n\n\n//--------------------------------------------------------------------------\n// Create directory open request ProtocolBuffer object\n//--------------------------------------------------------------------------\nRequestProto*\nutils::GetDirOpenRequest(std::string&& uuid,\n                         const char* name,\n                         const XrdSecEntity* client,\n                         const char* opaque,\n                         const char* user,\n                         int monid)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::DirOpenProto* dopen_proto = req_proto->mutable_diropen();\n  eos::auth::XrdSecEntityProto* xse_proto = dopen_proto->mutable_client();\n  // Save the address of the directory object\n  dopen_proto->set_uuid(uuid);\n  dopen_proto->set_name(name);\n  ConvertToProtoBuf(client, xse_proto);\n\n  if (opaque) {\n    dopen_proto->set_opaque(opaque);\n  }\n\n  dopen_proto->set_user(user);\n  dopen_proto->set_monid(monid);\n  req_proto->set_type(RequestProto_OperationType_DIROPEN);\n  return req_proto;\n}\n\n\n//------------------------------------------------------------------------------\n// Create directory next entry request ProtocolBuffer object\n//------------------------------------------------------------------------------\nRequestProto*\nutils::GetDirReadRequest(std::string&& uuid)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::DirReadProto* dread_proto = req_proto->mutable_dirread();\n  dread_proto->set_uuid(uuid);\n  req_proto->set_type(RequestProto_OperationType_DIRREAD);\n  return req_proto;\n}\n\n\n//------------------------------------------------------------------------------\n// Create directory FName request ProtocolBuffer object\n//------------------------------------------------------------------------------\nRequestProto*\nutils::GetDirFnameRequest(std::string&& uuid)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::DirFnameProto* dfname_proto = req_proto->mutable_dirfname();\n  dfname_proto->set_uuid(uuid);\n  req_proto->set_type(RequestProto_OperationType_DIRFNAME);\n  return req_proto;\n}\n\n\n//------------------------------------------------------------------------------\n// Create directory close request ProtocolBuffer object\n//------------------------------------------------------------------------------\nRequestProto*\nutils::GetDirCloseRequest(std::string&& uuid)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::DirCloseProto* dclose_proto = req_proto->mutable_dirclose();\n  dclose_proto->set_uuid(uuid);\n  req_proto->set_type(RequestProto_OperationType_DIRCLOSE);\n  return req_proto;\n}\n\n\n//------------------------------------------------------------------------------\n// Create file open request ProtocolBuffer object\n//------------------------------------------------------------------------------\nRequestProto*\nutils::GetFileOpenRequest(std::string&& uuid,\n                          const char* fileName,\n                          int openMode,\n                          mode_t createMode,\n                          const XrdSecEntity* client,\n                          const char* opaque,\n                          const char* user,\n                          int monid)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::FileOpenProto* fopen_proto = req_proto->mutable_fileopen();\n  eos::auth::XrdSecEntityProto* xse_proto = fopen_proto->mutable_client();\n  // Save the address of the file object\n  fopen_proto->set_uuid(uuid);\n  fopen_proto->set_name(fileName);\n  fopen_proto->set_openmode(openMode);\n  fopen_proto->set_createmode(createMode);\n  ConvertToProtoBuf(client, xse_proto);\n\n  if (opaque) {\n    fopen_proto->set_opaque(opaque);\n  }\n\n  fopen_proto->set_user(user);\n  fopen_proto->set_monid(monid);\n  req_proto->set_type(RequestProto_OperationType_FILEOPEN);\n  return req_proto;\n}\n\n\n//------------------------------------------------------------------------------\n// Create file FName request ProtocolBuffer object\n//------------------------------------------------------------------------------\nRequestProto*\nutils::GetFileFnameRequest(std::string&& uuid)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::FileFnameProto* ffname_proto = req_proto->mutable_filefname();\n  ffname_proto->set_uuid(uuid);\n  req_proto->set_type(RequestProto_OperationType_FILEFNAME);\n  return req_proto;\n}\n\n\n//------------------------------------------------------------------------------\n// Create file stat request ProtocolBuffer object\n//-----------------------------------------------------------------------------\nRequestProto*\nutils::GetFileStatRequest(std::string&& uuid)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::FileStatProto* fstat_proto = req_proto->mutable_filestat();\n  fstat_proto->set_uuid(uuid);\n  req_proto->set_type(RequestProto_OperationType_FILESTAT);\n  return req_proto;\n}\n\n\n//------------------------------------------------------------------------------\n// Create file read request ProtocolBuffer object\n//------------------------------------------------------------------------------\nRequestProto*\nutils::GetFileReadRequest(std::string&& uuid,\n                          long long offset,\n                          int length)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::FileReadProto* fread_proto = req_proto->mutable_fileread();\n  fread_proto->set_uuid(uuid);\n  fread_proto->set_offset(offset);\n  fread_proto->set_length(length);\n  req_proto->set_type(RequestProto_OperationType_FILEREAD);\n  return req_proto;\n}\n\n\n//------------------------------------------------------------------------------\n// Create file write request ProtocolBuffer object\n//-----------------------------------------------------------------------------\nRequestProto*\nutils::GetFileWriteRequest(std::string&& uuid,\n                           long long offset,\n                           const char* buff,\n                           int length)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::FileWriteProto* fwrite_proto = req_proto->mutable_filewrite();\n  fwrite_proto->set_uuid(uuid);\n  fwrite_proto->set_offset(offset);\n  fwrite_proto->set_buff(buff);\n  fwrite_proto->set_length(length);\n  req_proto->set_type(RequestProto_OperationType_FILEWRITE);\n  return req_proto;\n}\n\n\n//------------------------------------------------------------------------------\n// Create file close request ProtocolBuffer object\n//-----------------------------------------------------------------------------\nRequestProto*\nutils::GetFileCloseRequest(std::string&& uuid)\n{\n  eos::auth::RequestProto* req_proto = new eos::auth::RequestProto();\n  eos::auth::FileCloseProto* fclose_proto = req_proto->mutable_fileclose();\n  fclose_proto->set_uuid(uuid);\n  req_proto->set_type(RequestProto_OperationType_FILECLOSE);\n  return req_proto;\n}\n\nEOSAUTHNAMESPACE_END\n"
  },
  {
    "path": "auth_plugin/ProtoUtils.hh",
    "content": "//------------------------------------------------------------------------------\n// File: ProtoUtils.hh\n// Author: Elvin-Alin Sindrilaru <esindril@cern.ch> CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOS_AUTH_PROTOUTILS_HH__\n#define __EOS_AUTH_PROTOUTILS_HH__\n\n#include <string>\n#include \"auth_plugin/Namespace.hh\"\n#include \"auth_plugin/Request.pb.h\"\n#include \"auth_plugin/Response.pb.h\"\n#include \"auth_plugin/XrdSecEntity.pb.h\"\n#include \"auth_plugin/XrdSfsPrep.pb.h\"\n#include \"auth_plugin/XrdSfsFSctl.pb.h\"\n#include <XrdSfs/XrdSfsInterface.hh>\n\n//! Forward declarations\nclass XrdSecEntity;\nclass XrdOucErrInfo;\nstruct XrdSfsFSctl;\n\nEOSAUTHNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! ProtoUtils class which contains helper functions for marshalling and\n//! unmarshalling object to ProtocolBuffer representation\n//------------------------------------------------------------------------------\nnamespace utils\n{\n//----------------------------------------------------------------------------\n//! Convert XrdSecEntity object to ProtocolBuffers representation\n//!\n//! @param obj initial object to convert\n//! @param proto ProtocolBuffer representation\n//----------------------------------------------------------------------------\nvoid ConvertToProtoBuf(const XrdSecEntity* obj,\n                       eos::auth::XrdSecEntityProto*& proto);\n\n//----------------------------------------------------------------------------\n//! Convert XrdOucErrInfo object to ProtocolBuffers representation\n//!\n//! @param obj initial object to convert\n//! @param proto ProtocolBuffer representation\n//----------------------------------------------------------------------------\nvoid ConvertToProtoBuf(XrdOucErrInfo* obj,\n                       eos::auth::XrdOucErrInfoProto*& proto);\n\n//----------------------------------------------------------------------------\n//! Convert XrSfsFsctl object to ProtocolBuffers representation\n//!\n//! @param obj initial object to convert\n//! @param proto ProtocolBuffer representation\n//----------------------------------------------------------------------------\nvoid ConvertToProtoBuf(const XrdSfsFSctl* obj,\n                       eos::auth::XrdSfsFSctlProto*& proto);\n\n//----------------------------------------------------------------------------\n//! Convert XrSfsPrep object to ProtocolBuffers representation\n//!\n//! @param obj initial object to convert\n//! @param proto ProtocolBuffer representation\n//----------------------------------------------------------------------------\nvoid ConvertToProtoBuf(const XrdSfsPrep* obj,\n                       eos::auth::XrdSfsPrepProto*& proto);\n\n//----------------------------------------------------------------------------\n//! Get XrdSecEntity object from protocol buffer object\n//!\n//! @param proto_obj protocol buffer object\n//!\n//! @return converted XrdSecEntiry object\n//----------------------------------------------------------------------------\nXrdSecEntity* GetXrdSecEntity(const eos::auth::XrdSecEntityProto& proto_obj);\n\n//----------------------------------------------------------------------------\n//! Delete XrdSecEntity object\n//!\n//! @param obj object to be deleted\n//----------------------------------------------------------------------------\nvoid DeleteXrdSecEntity(XrdSecEntity*& obj);\n\n//----------------------------------------------------------------------------\n//! Delete XrdSfsPrep object\n//!\n//! @param obj object to be deleted\n//----------------------------------------------------------------------------\nvoid DeleteXrdSfsPrep(XrdSfsPrep*& obj);\n\n//----------------------------------------------------------------------------\n//! Get XrdOucErrInfo object from protocol buffer object\n//!\n//! @param proto_obj protocol buffer object\n//!\n//! @return converted XrdOucErrInfo object\n//----------------------------------------------------------------------------\nXrdOucErrInfo* GetXrdOucErrInfo(const eos::auth::XrdOucErrInfoProto& proto_obj);\n\n//----------------------------------------------------------------------------\n//! Get XrdSfsPrep object from protocol buffer object\n//!\n//! @param proto_obj protocol buffer object\n//!\n//! @return converted XrdSfsPrep object\n//----------------------------------------------------------------------------\nXrdSfsPrep* GetXrdSfsPrep(const eos::auth::XrdSfsPrepProto& proto_obj);\n\n//----------------------------------------------------------------------------\n//! Get XrdSfsFSctl object from protocol buffer object\n//!\n//! @param proto_obj protocol buffer object\n//!\n//! @return converted XrdSfsPrep object\n//----------------------------------------------------------------------------\nXrdSfsFSctl* GetXrdSfsFSctl(const eos::auth::XrdSfsFSctlProto& proto_obj);\n\n//----------------------------------------------------------------------------\n//! Delete XrdSfsFSctl object\n//!\n//! @param obj object to be deleted\n//----------------------------------------------------------------------------\nvoid DeleteXrdSfsFSctl(XrdSfsFSctl*& obj);\n\n//----------------------------------------------------------------------------\n//! Compute HMAC value of the RequestProto object and append it to the\n//! object using the required field hmac.\n//!\n//! @param req RequestProto object\n//!\n//! @return true if computation successful and attribute updated, otherwise\n//!         false\n//----------------------------------------------------------------------------\nbool ComputeHMAC(eos::auth::RequestProto*& req);\n\n//----------------------------------------------------------------------------\n//! Create stat request ProtocolBuffer object\n//!\n//! @param path file path\n//! @param error client security information obj\n//! @param opaque opaque information\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto*\nGetStatRequest(eos::auth::RequestProto_OperationType type,\n               const char* path,\n               XrdOucErrInfo& error,\n               const XrdSecEntity* client,\n               const char* opaque = 0);\n\n//----------------------------------------------------------------------------\n//! Create fsctl request ProtocolBuffer object\n//!\n//! @param cmd command type\n//! @param args command arguments\n//! @param error error information obj\n//! @param client client security information\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto*\nGetFsctlRequest(const int cmd,\n                const char* args,\n                XrdOucErrInfo& error,\n                const XrdSecEntity* client);\n\n//----------------------------------------------------------------------------\n//! Create FSctl request ProtocolBuffer object\n//!\n//! @param cmd command type\n//! @param args command arguments structure\n//! @param error error information obj\n//! @param client client security information\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto*\nGetFSctlRequest(const int cmd,\n                XrdSfsFSctl& args,\n                XrdOucErrInfo& error,\n                const XrdSecEntity* client);\n\n//----------------------------------------------------------------------------\n//! Create chmod request ProtocolBuffer object\n//!\n//! @param path directory path\n//! @param mode mode\n//! @param error error information object\n//! @param client client security information object\n//! @param opaque opaque information\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto*\nGetChmodRequest(const char* path,\n                int mode,\n                XrdOucErrInfo& error,\n                const XrdSecEntity* client,\n                const char* opaque = 0);\n\n//----------------------------------------------------------------------------\n//! Create chksum request ProtocolBuffer object\n//!\n//! @param func checksum function\n//! @param csname checksum name\n//! @param inpath input path\n//! @param error error information object\n//! @param client client security information object\n//! @param opaque opaque information\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto*\nGetChksumRequest(XrdSfsFileSystem::csFunc func,\n                 const char* csname,\n                 const char* inpath,\n                 XrdOucErrInfo& error,\n                 const XrdSecEntity* client = 0,\n                 const char* opaque = 0);\n\n//----------------------------------------------------------------------------\n//! Create exitst request ProtocolBuffer object\n//!\n//! @param path file/directory path\n//! @param error error information object\n//! @param client client security information object\n//! @param opaque opaque information\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto*\nGetExistsRequest(const char* path,\n                 XrdOucErrInfo& error,\n                 const XrdSecEntity* client,\n                 const char* opaque = 0);\n\n//----------------------------------------------------------------------------\n//! Create mkdir request ProtocolBuffer object\n//!\n//! @param path directory path\n//! @param mode mode\n//! @param error error information object\n//! @param client client security information object\n//! @param opaque opaque information\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto*\nGetMkdirRequest(const char* path,\n                int mode,\n                XrdOucErrInfo& error,\n                const XrdSecEntity* client,\n                const char* opaque = 0);\n\n//----------------------------------------------------------------------------\n//! Create remdir request ProtocolBuffer object\n//!\n//! @param path directory path\n//! @param error error information object\n//! @param client client security information object\n//! @param opaque opaque information\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto*\nGetRemdirRequest(const char* path,\n                 XrdOucErrInfo& error,\n                 const XrdSecEntity* client,\n                 const char* opaque = 0);\n\n//----------------------------------------------------------------------------\n//! Create rem request ProtocolBuffer object\n//!\n//! @param path file path\n//! @param error error information object\n//! @param client client security information object\n//! @param opaque opaque information\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto*\nGetRemRequest(const char* path,\n              XrdOucErrInfo& error,\n              const XrdSecEntity* client,\n              const char* opaque = 0);\n\n//----------------------------------------------------------------------------\n//! Create rename request ProtocolBuffer object\n//!\n//! @param oldName old name\n//! @param newName new name\n//! @param error error information object\n//! @param client client security information object\n//! @param opaqueO opaque information for old name\n//! @param opaqueN opaque information for new name\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto*\nGetRenameRequest(const char* oldName,\n                 const char* newName,\n                 XrdOucErrInfo& error,\n                 const XrdSecEntity* client,\n                 const char* opaqueO,\n                 const char* opaqueN);\n\n//----------------------------------------------------------------------------\n//! Create prepare request ProtocolBuffer object\n//!\n//! @param pargs prepare operation arguments\n//! @param error error information object\n//! @param client client security information object\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto*\nGetPrepareRequest(XrdSfsPrep& pargs,\n                  XrdOucErrInfo& error,\n                  const XrdSecEntity* client);\n\n//----------------------------------------------------------------------------\n//! Create truncate request ProtocolBuffer object\n//!\n//! @param path file to be trunacted\n//! @param fileOffset truncate offset value\n//! @param error error information object\n//! @param client client security information object\n//! @param opaque opaque information\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto*\nGetTruncateRequest(const char* path,\n                   XrdSfsFileOffset fileOffset,\n                   XrdOucErrInfo& error,\n                   const XrdSecEntity* client,\n                   const char* opaque);\n\n//--------------------------------------------------------------------------\n//! Create directory open request ProtocolBuffer object\n//!\n//! @param uuid unqiue identifier for the current directory\n//! @param name name of the directory\n//! @param client client security information object\n//! @param opaque opaque information\n//! @param user user name passed initially to the constructor\n//! @param monid MonID value passed initally to the constructor\n//!\n//! @return request ProtoBuffer object\n//--------------------------------------------------------------------------\neos::auth::RequestProto*\nGetDirOpenRequest(std::string&& uuid,\n                  const char* name,\n                  const XrdSecEntity* client,\n                  const char* opaque = 0,\n                  const char* user = 0,\n                  int monid = 0);\n\n//--------------------------------------------------------------------------\n//! Create directory next entry request ProtocolBuffer object\n//!\n//! @param uuid unqiue identifier for the current directory\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto* GetDirReadRequest(std::string&& uuid);\n\n//----------------------------------------------------------------------------\n//! Create directory FName request ProtocolBuffer object\n//!\n//! @param uuid unqiue identifier for the current directory\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto* GetDirFnameRequest(std::string&& uuid);\n\n//----------------------------------------------------------------------------\n//! Create directory close request ProtocolBuffer object\n//!\n//! @param uuid unqiue identifier for the current directory\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto* GetDirCloseRequest(std::string&& uuid);\n\n//----------------------------------------------------------------------------\n//! Create file open request ProtocolBuffer object\n//!\n//! @param uuid unqiue identifier for the current file\n//! @param fileName name of the file\n//! @param openMode open mode flags\n//! @param createMode create mode flag\n//! @param client client security information object\n//! @param opaque opaque information\n//! @param user user name passed initially to the constructor\n//! @param monid MonID value passed initally to the constructor\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto*\nGetFileOpenRequest(std::string&& uuid,\n                   const char* fileName,\n                   int openMode,\n                   mode_t createMode,\n                   const XrdSecEntity* client,\n                   const char* opaque = 0,\n                   const char* user = 0,\n                   int monid = 0);\n\n//----------------------------------------------------------------------------\n//! Create file FName request ProtocolBuffer object\n//!\n//! @param uuid unqiue identifier for the current file\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto* GetFileFnameRequest(std::string&& uuid);\n\n//----------------------------------------------------------------------------\n//! Create file stat request ProtocolBuffer object\n//!\n//! @param uuid unqiue identifier for the current file\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto* GetFileStatRequest(std::string&& uuid);\n\n//----------------------------------------------------------------------------\n//! Create file read request ProtocolBuffer object\n//!\n//! @param uuid unqiue identifier for the current file\n//! @param offset offset in file\n//! @param length lenght of read\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto*\nGetFileReadRequest(std::string&& uuid,\n                   long long offset,\n                   int length);\n\n//----------------------------------------------------------------------------\n//! Create file write request ProtocolBuffer object\n//!\n//! @param uuid unqiue identifier for the current file\n//! @param offset offset in file\n//! @param buff data buffer to be written\n//! @param length lenght of read\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto*\nGetFileWriteRequest(std::string&& uuid,\n                    long long offset,\n                    const char* buff,\n                    int length);\n\n//----------------------------------------------------------------------------\n//! Create file close request ProtocolBuffer object\n//!\n//! @param uuid unqiue identifier for the current directory\n//!\n//! @return request ProtoBuffer object\n//----------------------------------------------------------------------------\neos::auth::RequestProto* GetFileCloseRequest(std::string&& uuid);\n}\n\nEOSAUTHNAMESPACE_END\n\n#endif //__EOS_AUTH_PROTOUTILS_HH__\n"
  },
  {
    "path": "auth_plugin/proto/Chksum.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\nimport \"XrdOucErrInfo.proto\";\nimport \"XrdSecEntity.proto\";\n\n//------------------------------------------------------------------------------\n// Chksum request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage ChksumProto {\n  required int64 func = 1;\n  required string csname = 2;\n  required string path = 3;\n  required XrdOucErrInfoProto error = 4;\n  optional XrdSecEntityProto client = 5 ;\n  optional string opaque = 6 [default = \"\"];\n}\n  \n"
  },
  {
    "path": "auth_plugin/proto/Chmod.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\nimport \"XrdOucErrInfo.proto\";\nimport \"XrdSecEntity.proto\";\n\n//------------------------------------------------------------------------------\n// Chmod request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage ChmodProto {\n  required string path = 1;\n  required int64 mode = 2;\n  required XrdOucErrInfoProto error = 3;\n  required XrdSecEntityProto client = 4;\n  optional string opaque = 5 [default = \"\"];\n}\n  \n"
  },
  {
    "path": "auth_plugin/proto/DirClose.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\n//------------------------------------------------------------------------------\n// DirClose request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage DirCloseProto {\n   required string uuid = 1;  ///< this is the pointer to the local directory object\n}\n"
  },
  {
    "path": "auth_plugin/proto/DirFname.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\n//------------------------------------------------------------------------------\n// DirFname request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage DirFnameProto {\n   required string uuid = 1;  ///< this is the pointer to the local directory object\n}\n"
  },
  {
    "path": "auth_plugin/proto/DirOpen.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\nimport \"XrdSecEntity.proto\";\n\n//------------------------------------------------------------------------------\n// DirOpen request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage DirOpenProto {\n   required string uuid = 1;  ///< this is the pointer to the local directory object\n   required string name = 2;\n   optional XrdSecEntityProto client = 3 ;\n   optional string opaque = 4 [default = \"\"];\n   optional string user = 5 [default = \"\"]; ///< this and the following are the values passed\n   optional int64 monid = 6 [default = 0];  ///< to the constructor of the directory object\n}\n  \n"
  },
  {
    "path": "auth_plugin/proto/DirRead.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\n//------------------------------------------------------------------------------\n// DirRead request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage DirReadProto {\n   required string uuid = 1;  ///< this is the pointer to the local directory object\n}\n"
  },
  {
    "path": "auth_plugin/proto/Exists.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\nimport \"XrdOucErrInfo.proto\";\nimport \"XrdSecEntity.proto\";\n\n//------------------------------------------------------------------------------\n// Exists request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage ExistsProto {\n  required string path = 1;\n  required XrdOucErrInfoProto error = 3;\n  required XrdSecEntityProto client = 4;\n  optional string opaque = 5;\n}\n  \n"
  },
  {
    "path": "auth_plugin/proto/FS_ctl.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\nimport \"XrdOucErrInfo.proto\";\nimport \"XrdSecEntity.proto\";\nimport \"XrdSfsFSctl.proto\";\n\n//------------------------------------------------------------------------------\n// FSctl request protocol buffer message used for FSclt(..) function calls\n//------------------------------------------------------------------------------\n\nmessage FSctlProto {\n  required int64 cmd = 1;\n  required XrdSfsFSctlProto args = 2;\n  required XrdOucErrInfoProto error = 3;\n  required XrdSecEntityProto client = 4;\n}\n  \n"
  },
  {
    "path": "auth_plugin/proto/FileClose.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\n//------------------------------------------------------------------------------\n// FileClose request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage FileCloseProto {\n   required string uuid = 1;  ///< this is the pointer to the local directory object\n}\n  \n"
  },
  {
    "path": "auth_plugin/proto/FileFname.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\n//------------------------------------------------------------------------------\n// FileFname request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage FileFnameProto {\n   required string uuid = 1;  ///< this is the pointer to the local directory object\n}\n"
  },
  {
    "path": "auth_plugin/proto/FileOpen.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\nimport \"XrdSecEntity.proto\";\n\n//------------------------------------------------------------------------------\n// FileOpen request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage FileOpenProto {\n   required string uuid = 1;  ///< this is the pointer to the local directory object\n   required string name = 2;\n   required int64 openmode = 3;\n   required int64 createmode = 4;\n   optional XrdSecEntityProto client = 5 ;\n   optional string opaque = 6 [default = \"\"];\n   optional string user = 7 [default = \"\"]; ///< this and the following are the values passed\n   optional int64 monid = 8 [default = 0];  ///< to the constructor of the directory object\n}\n  \n"
  },
  {
    "path": "auth_plugin/proto/FileRead.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\n//------------------------------------------------------------------------------\n// FileRead request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage FileReadProto {\n   required string uuid  = 1;  ///< this is the pointer to the local directory object\n   required int64 offset = 2;\n   required int64 length = 4;\n}\n  \n"
  },
  {
    "path": "auth_plugin/proto/FileStat.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\n//------------------------------------------------------------------------------\n// FileStat request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage FileStatProto {\n   required string uuid = 1;  ///< this is the pointer to the local directory object\n}\n  \n"
  },
  {
    "path": "auth_plugin/proto/FileWrite.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\n//------------------------------------------------------------------------------\n// FileWrite request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage FileWriteProto {\n   required string uuid  = 1;  ///< this is the pointer to the local directory object\n   required int64 offset = 2;\n   required bytes buff   = 3;\n   required int64 length = 4;\n}\n  \n"
  },
  {
    "path": "auth_plugin/proto/Fsctl.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\nimport \"XrdOucErrInfo.proto\";\nimport \"XrdSecEntity.proto\";\n\n//------------------------------------------------------------------------------\n// Fsctl request protocol buffer message used for Fsctl(..) function calls\n//------------------------------------------------------------------------------\n\nmessage FsctlProto {\n  required int64 cmd = 1;\n  required string args = 2;\n  required XrdOucErrInfoProto error = 3;\n  required XrdSecEntityProto client = 4;\n}\n  \n"
  },
  {
    "path": "auth_plugin/proto/GetStats.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\n//------------------------------------------------------------------------------\n// getStats request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage GetStatsProto {\n  // empty\n}\n"
  },
  {
    "path": "auth_plugin/proto/Mkdir.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\nimport \"XrdOucErrInfo.proto\";\nimport \"XrdSecEntity.proto\";\n\n//------------------------------------------------------------------------------\n// Mkdir request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage MkdirProto {\n  required string path = 1;\n  required int64 mode = 2;\n  required XrdOucErrInfoProto error = 3;\n  required XrdSecEntityProto client = 4;\n  optional string opaque = 5 [default = \"\"];\n}\n"
  },
  {
    "path": "auth_plugin/proto/Prepare.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\nimport \"XrdOucErrInfo.proto\";\nimport \"XrdSecEntity.proto\";\nimport \"XrdSfsPrep.proto\";\n\n//------------------------------------------------------------------------------\n// Prepare request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage PrepareProto {\n  required XrdSfsPrepProto pargs = 1;\n  required XrdOucErrInfoProto error = 2;\n  required XrdSecEntityProto client = 3;\n}\n"
  },
  {
    "path": "auth_plugin/proto/Rem.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\nimport \"XrdOucErrInfo.proto\";\nimport \"XrdSecEntity.proto\";\n\n//------------------------------------------------------------------------------\n// Rem request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage RemProto {\n  required string path = 1;\n  required XrdOucErrInfoProto error = 2;\n  required XrdSecEntityProto client = 3;\n  optional string opaque = 4 [default = \"\"];\n}\n"
  },
  {
    "path": "auth_plugin/proto/Remdir.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\nimport \"XrdOucErrInfo.proto\";\nimport \"XrdSecEntity.proto\";\n\n//------------------------------------------------------------------------------\n// Remdir request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage RemdirProto {\n  required string path = 1;\n  required XrdOucErrInfoProto error = 2;\n  required XrdSecEntityProto client = 3;\n  optional string opaque = 4 [default = \"\"];\n}\n"
  },
  {
    "path": "auth_plugin/proto/Rename.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\nimport \"XrdOucErrInfo.proto\";\nimport \"XrdSecEntity.proto\";\n\n//------------------------------------------------------------------------------\n// Rename request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage RenameProto {\n  required string oldname = 1;\n  required string newname = 2;\n  required XrdOucErrInfoProto error = 3;\n  required XrdSecEntityProto client = 4;\n  optional string opaqueo = 5 [default = \"\"];\n  optional string opaquen = 6 [default = \"\"];\n}\n"
  },
  {
    "path": "auth_plugin/proto/Request.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\nimport \"Stat.proto\";\nimport \"Fsctl.proto\";\nimport \"FS_ctl.proto\";\nimport \"Chmod.proto\";\nimport \"Chksum.proto\";\nimport \"Exists.proto\";\nimport \"Mkdir.proto\";\nimport \"Remdir.proto\";\nimport \"Rem.proto\";\nimport \"Rename.proto\";\nimport \"Prepare.proto\";\nimport \"Truncate.proto\";\nimport \"DirOpen.proto\";\nimport \"DirRead.proto\";\nimport \"DirFname.proto\";\nimport \"DirClose.proto\";\nimport \"FileOpen.proto\";\nimport \"FileFname.proto\";\nimport \"FileStat.proto\";\nimport \"FileRead.proto\";\nimport \"FileWrite.proto\";\nimport \"FileClose.proto\";\n\n//------------------------------------------------------------------------------\n// Request message sent to the server\n//------------------------------------------------------------------------------\n\nmessage RequestProto {\n  enum OperationType {\n     STAT      = 0;  // stat to get struct stat\n     FSCTL1    = 1;  // fsctl\n     FSCTL2    = 2;  // FSctl\n     CHMOD     = 3;\n     CHKSUM    = 4;\n     EXISTS    = 5;\n     STATM     = 6;  // stat mode\n     MKDIR     = 7;\n     REMDIR    = 8;\n     REM       = 9;\n     RENAME    = 10;\n     PREPARE   = 11;\n     TRUNCATE  = 12;\n     DIROPEN   = 13;\n     DIRFNAME  = 14;\n     DIRREAD   = 15;\n     DIRCLOSE  = 16;\n     FILEOPEN  = 17;\n     FILEFNAME = 18;\n     FILESTAT  = 19;\n     FILEREAD  = 20;\n     FILEWRITE = 21;\n     FILECLOSE = 22;\n  }    \t  \n\n  // Identifies which filed is filled in \n  required OperationType type = 1;\n  // Encrypted sha1 of the string representation of the object excluding\n  // the field 'hmac' which is to be considered as empty string (\"\") during\n  // the computation and then updated at the end to the new value\n  required string hmac = 2;\n\n  // One of the following is filled in \n  optional StatProto stat         = 3;\n  optional FsctlProto fsctl1      = 4;\n  optional FSctlProto fsctl2      = 5;\n  optional ChmodProto chmod       = 6;\n  optional ChksumProto chksum     = 7;\n  optional ExistsProto exists     = 8;\n  optional StatProto statm        = 9;\n  optional MkdirProto mkdir       = 10;\n  optional RemdirProto remdir     = 11;\n  optional RemProto rem           = 12;\n  optional RenameProto rename     = 13;\n  optional PrepareProto prepare   = 14;\n  optional TruncateProto truncate = 15;\n  optional DirOpenProto diropen   = 16;\n  optional DirReadProto dirread   = 17;\n  optional DirFnameProto dirfname = 18;\n  optional DirCloseProto dirclose = 19;\n  optional FileOpenProto fileopen = 20;\n  optional FileFnameProto filefname = 21;\n  optional FileStatProto filestat   = 22;\n  optional FileReadProto fileread   = 23;\n  optional FileWriteProto filewrite = 24;\n  optional FileCloseProto fileclose = 25;\n\n\n}\n\n"
  },
  {
    "path": "auth_plugin/proto/Response.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\nimport \"XrdOucErrInfo.proto\";\n\n//------------------------------------------------------------------------------\n// Response object received from the server for most of the requests\n//------------------------------------------------------------------------------\nmessage ResponseProto {\n  required int64 response = 1;\n  optional bytes message = 2;\n  optional XrdOucErrInfoProto error = 3;\n  optional bool  collapse = 4;\n}\n\n"
  },
  {
    "path": "auth_plugin/proto/Stat.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\nimport \"XrdOucErrInfo.proto\";\nimport \"XrdSecEntity.proto\";\n\n//------------------------------------------------------------------------------\n// Stat request protocol buffer message\n//------------------------------------------------------------------------------\n\nmessage StatProto {\t\n  required string path              = 1;\n  required XrdOucErrInfoProto error = 2;\n  required XrdSecEntityProto client = 3;\n  optional string            opaque = 4 [default = \"\"];\n}  \n\n\n \n"
  },
  {
    "path": "auth_plugin/proto/Truncate.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\nimport \"XrdOucErrInfo.proto\";\nimport \"XrdSecEntity.proto\";\n\n//------------------------------------------------------------------------------\n// Truncate request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage TruncateProto {\n  required string path = 1;\n  required int64 fileoffset = 2;\n  required XrdOucErrInfoProto error = 3;\n  optional XrdSecEntityProto client = 4;\n  optional string opaque = 5 [default = \"\"];\n}\n"
  },
  {
    "path": "auth_plugin/proto/XrdOucErrInfo.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\n//------------------------------------------------------------------------------\n// XrdOucErrInfo request protocol buffer message - only required fileds\n//------------------------------------------------------------------------------\n\nmessage XrdOucErrInfoProto {\t\n  required string user = 1;\n  optional int64 code  = 2;\n  optional string message = 3;\n}\n"
  },
  {
    "path": "auth_plugin/proto/XrdSecEntity.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\n//------------------------------------------------------------------------------\n// XrdSecEntity protocol buffer representation\n//------------------------------------------------------------------------------\n\nmessage XrdSecEntityProto {\n  required string prot = 1;         // Protocol used\n  required string name = 2 ;        // Entity's name\n  required string host = 3;         // Entity's host name\n  required string vorg = 4;         // Entity's virtual organization\n  required string role = 5;         // Entity's role\n  required string grps = 6;         // Entity's group names\n  required string endorsements = 7; // Protocol specific endorsements\n  required string creds = 8;        // Raw client credentials or certificate\n  required int64  credslen = 9;     // Length of the 'cert' field\n  required string moninfo = 10;     // Additional information for monitoring \n  required string tident = 11;      // Trace identifier (do not touch)\n}\n"
  },
  {
    "path": "auth_plugin/proto/XrdSfsFSctl.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\n//------------------------------------------------------------------------------\n// XrdSfsFSctl structure protocol buffer representation\n// For info look into XrdSfs/XrdSfsInterface.hh\n//------------------------------------------------------------------------------\n\nmessage XrdSfsFSctlProto {\n  required string Arg1    = 1;\n  required int64  Arg1Len = 2; \n  optional int64  Arg2Len = 3 [default = 0];\n  optional string Arg2    = 4;\n}\n"
  },
  {
    "path": "auth_plugin/proto/XrdSfsPrep.proto",
    "content": "syntax = \"proto2\";\npackage eos.auth;\n\n//------------------------------------------------------------------------------\n// XrdSfsPrep request protocol buffer message \n//------------------------------------------------------------------------------\n\nmessage XrdSfsPrepProto {\n  required string reqid  = 1;\n  required string notify = 2;\n  required int64 opts    = 3;\n  repeated string paths  = 4;\n  repeated string oinfo  = 5;\n}\n"
  },
  {
    "path": "client/CMakeLists.txt",
    "content": "#-------------------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Andreas-Joachim Peters - CERN\n#-------------------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2018 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ninclude_directories(${CMAKE_BINARY_DIR})\n\n#-------------------------------------------------------------------------------\n# Disable -Wsign-compare warnings due to\n# grpcpp/support/proto_buffer_reader.h:157:24: warning: comparison of\n# integer expressions of different signedness: ‘uint64_t’ {aka ‘long\n# unsigned int’} and ‘int’ [-Wsign-compare]\n#-------------------------------------------------------------------------------\nadd_compile_options(-Wno-sign-compare)\n\n#-------------------------------------------------------------------------------\n# eos executable\n#-------------------------------------------------------------------------------\nadd_library(EosGrpcClient-Objects OBJECT\n  grpc/GrpcClient.cc grpc/GrpcClient.hh)\n\ntarget_compile_options(EosGrpcClient-Objects PRIVATE -Wno-sign-compare)\n\ntarget_link_libraries(EosGrpcClient-Objects PUBLIC\n  EosGrpcProto-Objects\n  XROOTD::UTILS)\n\ntarget_compile_definitions(EosGrpcClient-Objects PUBLIC\n  -DDAEMONUID=${DAEMONUID} -DDAEMONGID=${DAEMONGID} -DHAVE_ATOMICS=1)\n\nset_target_properties(EosGrpcClient-Objects\n  PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\n\nadd_executable(eos-grpc-ping grpc/Ping.cc)\nadd_executable(eos-grpc-md grpc/Md.cc)\nadd_executable(eos-grpc-find grpc/Find.cc)\nadd_executable(eos-grpc-ns grpc/Ns.cc)\nadd_executable(eos-grpc-insert grpc/Insert.cc)\nadd_executable(eos-grpc-ns-stat grpc/NsStat.cc)\n\n#-------------------------------------------------------------------------------\n# Add dependency which guarantees that the protocol buffer files are generated\n# when we build the executables\n#-------------------------------------------------------------------------------\ntarget_link_libraries(eos-grpc-ping PUBLIC\n  EosGrpcProto-Objects\n  EosGrpcClient-Objects\n  EosCommon )\n\ntarget_link_libraries(eos-grpc-md PUBLIC\n  EosGrpcProto-Objects\n  EosGrpcClient-Objects\n  EosCommon)\n\ntarget_link_libraries(eos-grpc-find PUBLIC\n  EosGrpcProto-Objects\n  EosGrpcClient-Objects\n  EosCommon )\n\ntarget_link_libraries(eos-grpc-ns PUBLIC\n  EosGrpcProto-Objects\n  EosGrpcClient-Objects\n  EosConsoleHelpers-Objects\n  EosCommon)\n\ntarget_link_libraries(eos-grpc-insert PUBLIC\n  EosGrpcProto-Objects\n  EosGrpcClient-Objects\n  EosCommon )\n\ntarget_link_libraries(eos-grpc-ns-stat PUBLIC\n  EosGrpcProto-Objects\n  EosGrpcClient-Objects\n  EosCommon)\n\ninstall(TARGETS eos-grpc-ping eos-grpc-md eos-grpc-insert \n  eos-grpc-ns eos-grpc-find eos-grpc-ns-stat\n  LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n  ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\n"
  },
  {
    "path": "client/Namespace.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Namespace.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSCLIENT_NAMESPACE_HH__\n#define __EOSCLIENT_NAMESPACE_HH__\n\n#define USE_EOSCLIENTNAMESPACE using namespace eos::client;\n\n#define EOSCLIENTNAMESPACE_BEGIN namespace eos { namespace client {\n#define EOSCLIENTNAMESPACE_END }}\n\n#endif\n"
  },
  {
    "path": "client/grpc/Find.cc",
    "content": "#include <string>\n#include <iostream>\n#include \"client/grpc/GrpcClient.hh\"\n#include <stdio.h>\n#include \"common/StringConversion.hh\"\n\nint usage(const char* prog)\n{\n  fprintf(stderr, \"usage: %s [--key <ssl-key-file> \"\n          \"--cert <ssl-cert-file> \"\n          \"--ca <ca-cert-file>] \"\n          \"[--endpoint <host:port>] [--token <auth-token>] [--export <exportfs>] [--depth <depth>] [--select <filter-string>] [--force-ssl] [-f | -d] <path>\\n\",\n          prog);\n  fprintf(stderr,\n          \" <filter-string> is setup as \\\"key1:val1,key2:val2,key3:val3 ... where keyN:valN is one of \\n\");\n  fprintf(stderr, \"\"\n          \"                    owner-root:1|0\\n\"\n          \"                    group-root:1|0\\n\"\n          \"                    owner:<uid>\\n\"\n          \"                    group:<gid>\\n\"\n          \"                    regex-filename:<regex>\\n\"\n          \"                    regex-dirname:<regex>\\n\"\n          \"                    zero-size:1|0\\n\"\n          \"                    min-size:<min>\\n\"\n          \"                    max-size:<max>\\n\"\n          \"                    min-children:<min>\\n\"\n          \"                    max-children:<max>\\n\"\n          \"                    zero-children:1|0\\n\"\n          \"                    min-locations:<min>\\n\"\n          \"                    max-locations:<max>\\n\"\n          \"                    zero-locations:1|0\\n\"\n          \"                    min-unlinked_locations:<min>\\n\"\n          \"                    max-unlinked_locations:<max\\n\"\n          \"                    zero-unlinked_locations:1|0\\n\"\n          \"                    min-treesize:<min>\\n\"\n          \"                    max-treesize:<max>\\n\"\n          \"                    zero-treesize:1|0\\n\"\n          \"                    min-ctime:<unixtst>\\n\"\n          \"                    max-ctime:<unixtst>\\n\"\n          \"                    zero-ctime:1|0\\n\"\n          \"                    min-mtime:<unixtst>\\n\"\n          \"                    max-mtime:<unixtst>\\n\"\n          \"                    zero-mtime:1|0\\n\"\n          \"                    min-stime:<unixtst>\\n\"\n          \"                    max-stime:<unixtst>\\n\"\n          \"                    zero-stime:1|0\\n\"\n          \"                    layoutid:<layoudid>\\n\"\n          \"                    flags:<flags>\\n\"\n          \"                    symlink:1|0\\n\"\n          \"                    checksum-type:<cksname>\\n\"\n          \"                    checksum-value:<cksvalue>\\n\"\n          \"                    xattr:<key>=<val>\\n\");\n  return -1;\n}\n\nint main(int argc, const char* argv[])\n{\n  std::string endpoint = \"localhost:50051\";\n  std::string token = \"\";\n  std::string key;\n  std::string cert;\n  std::string ca;\n  std::string keyfile;\n  std::string certfile;\n  std::string cafile;\n  std::string path = \"\";\n  std::list<std::string> select;\n  bool files = false;\n  bool dirs  = false;\n  uint64_t depth = 1024;\n  std::string exportfs = \"\";\n  bool force_ssl = false;\n\n  for (auto i = 1; i < argc; ++i) {\n    std::string option = argv[i];\n\n    if (option == \"--key\") {\n      if (argc > i + 1) {\n        keyfile = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--cert\") {\n      if (argc > i + 1) {\n        certfile = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--ca\") {\n      if (argc > i + 1) {\n        cafile = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--endpoint\") {\n      if (argc > i + 1) {\n        endpoint = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--token\") {\n      if (argc > i + 1) {\n        token = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--export\") {\n      if (argc > i + 1) {\n        exportfs = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--depth\") {\n      if (argc > i + 1) {\n        depth = strtoull(argv[i + 1], 0, 10);\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--select\") {\n      if (argc > i + 1) {\n        select.push_back(argv[i + 1]);\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"-f\") {\n      files = true;\n      continue;\n    }\n\n    if (option == \"-d\") {\n      dirs = true;\n      continue;\n    }\n\n    if (option == \"--force-ssl\") {\n      force_ssl = true;\n      continue;\n    }\n\n    path = option;\n\n    if (argc > (i + 1)) {\n      return usage(argv[0]);\n    }\n  }\n\n  if (!files && ! dirs) {\n    files = true;\n    dirs = true;\n  }\n\n  if (keyfile.length() || certfile.length() || cafile.length()) {\n    if (!keyfile.length() || !certfile.length() || !cafile.length()) {\n      return usage(argv[0]);\n    }\n  }\n\n  if (path.empty()) {\n    return usage(argv[0]);\n  }\n\n  if (path.front() != '/') {\n    return usage(argv[0]);\n  }\n\n  std::unique_ptr<eos::client::GrpcClient> eosgrpc =\n    eos::client::GrpcClient::Create(\n      endpoint,\n      token,\n      keyfile,\n      certfile,\n      cafile,\n      force_ssl);\n\n  if (!eosgrpc) {\n    return usage(argv[0]);\n  }\n\n  std::chrono::steady_clock::time_point watch_global =\n    std::chrono::steady_clock::now();\n  std::string reply = eosgrpc->Find(path, select, 0, 0, files, dirs, depth, true,\n                                    exportfs);\n  std::chrono::microseconds elapsed_global =\n    std::chrono::duration_cast<std::chrono::microseconds>\n    (std::chrono::steady_clock::now() - watch_global);\n  std::cout << \"request took \" << elapsed_global.count() <<\n            \" micro seconds\" << std::endl;\n  return 0;\n}\n"
  },
  {
    "path": "client/grpc/GrpcClient.cc",
    "content": "// ----------------------------------------------------------------------\n// File: GrpccLIENT.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifdef EOS_GRPC\n#include \"GrpcClient.hh\"\n#include \"proto/Rpc.grpc.pb.h\"\n#include \"common/StringConversion.hh\"\n#include \"common/Timing.hh\"\n#include \"common/Path.hh\"\n#include <absl/log/absl_check.h>\n#include <grpcpp/grpcpp.h>\n#include <grpc/support/log.h>\n#include <google/protobuf/util/json_util.h>\n#include <sys/stat.h>\n\nEOSCLIENTNAMESPACE_BEGIN\n\nusing grpc::Channel;\nusing grpc::ClientAsyncResponseReader;\nusing grpc::ClientAsyncReader;\nusing grpc::ClientContext;\nusing grpc::CompletionQueue;\nusing grpc::Status;\n\nusing eos::rpc::Eos;\nusing eos::rpc::PingRequest;\nusing eos::rpc::PingReply;\nusing eos::rpc::MDRequest;\nusing eos::rpc::MDResponse;\nusing eos::rpc::FindRequest;\nusing eos::rpc::NSRequest;\nusing eos::rpc::NSResponse;\nusing eos::rpc::NsStatRequest;\nusing eos::rpc::NsStatResponse;\nusing eos::rpc::FileInsertRequest;\nusing eos::rpc::ContainerInsertRequest;\nusing eos::rpc::InsertReply;\nusing eos::rpc::ContainerMdProto;\nusing eos::rpc::FileMdProto;\n\n\nstd::string GrpcClient::Ping(const std::string& payload)\n{\n  PingRequest request;\n  request.set_message(payload);\n  request.set_authkey(token());\n  PingReply reply;\n  ClientContext context;\n  // The producer-consumer queue we use to communicate asynchronously with the\n  // gRPC runtime.\n  CompletionQueue cq;\n  Status status;\n  // stub_->AsyncPing() performs the RPC call, returning an instance we\n  // store in \"rpc\". Because we are using the asynchronous API, we need to\n  // hold on to the \"rpc\" instance in order to get updates on the ongoing RPC.\n  std::unique_ptr<ClientAsyncResponseReader<PingReply> > rpc(\n    stub_->AsyncPing(&context, request, &cq));\n  // Request that, upon completion of the RPC, \"reply\" be updated with the\n  // server's response; \"status\" with the indication of whether the operation\n  // was successful. Tag the request with the integer 1.\n  rpc->Finish(&reply, &status, (void*) 1);\n  void* got_tag;\n  bool ok = false;\n  // Block until the next result is available in the completion queue \"cq\".\n  // The return value of Next should always be checked. This return value\n  // tells us whether there is any kind of event or the cq_ is shutting down.\n  ABSL_CHECK(cq.Next(&got_tag, &ok));\n  // Verify that the result from \"cq\" corresponds, by its tag, our previous\n  // request.\n  ABSL_CHECK(got_tag == (void*) 1);\n  // ... and that the request was completed successfully. Note that \"ok\"\n  // corresponds solely to the request for updates introduced by Finish().\n  ABSL_CHECK(ok);\n\n  // Act upon the status of the actual RPC.\n  if (status.ok()) {\n    return reply.message();\n  } else {\n    return \"\";\n  }\n}\n\nstd::string\nGrpcClient::Md(const std::string& path,\n               uint64_t id,\n               uint64_t ino,\n               bool list,\n               bool printonly)\n{\n  MDRequest request;\n\n  if (list) {\n    request.set_type(eos::rpc::LISTING);\n  } else {\n    request.set_type(eos::rpc::STAT);\n  }\n\n  if (path.length()) {\n    request.mutable_id()->set_path(path);\n  } else if (id) {\n    request.mutable_id()->set_id(id);\n  } else if (ino) {\n    request.mutable_id()->set_ino(ino);\n  } else {\n    return \"\";\n  }\n\n  request.set_authkey(token());\n  MDResponse response;\n  ClientContext context;\n  std::string responsestring;\n  CompletionQueue cq;\n  Status status;\n  std::unique_ptr<ClientAsyncReader<MDResponse> > rpc(\n    stub_->AsyncMD(&context, request, &cq, (void*) 1));\n  void* got_tag;\n  bool ok = false;\n  bool ret = cq.Next(&got_tag, &ok);\n\n  while (1) {\n    rpc->Read(&response, (void*) 1);\n    ok = false;\n    ret = cq.Next(&got_tag, &ok);\n\n    if (!ret || !ok || got_tag != (void*) 1) {\n      break;\n    }\n\n    google::protobuf::util::JsonPrintOptions options;\n    options.add_whitespace = true;\n#if GOOGLE_PROTOBUF_VERSION >= 5027000\n    options.always_print_fields_with_no_presence = true;\n#else\n    options.always_print_primitive_fields = true;\n#endif\n    std::string jsonstring;\n    (void) google::protobuf::util::MessageToJsonString(response,\n        &jsonstring, options);\n\n    if (printonly) {\n      std::cout << jsonstring << std::endl;\n    } else {\n      responsestring += jsonstring;\n    }\n  }\n\n  if (!status.ok()) {\n    std::cerr << \"error: \" << status.error_message() << std::endl;\n  }\n\n  return responsestring;\n}\n\nstd::string\nGrpcClient::Find(const std::string& path,\n                 const std::list<std::string>& filters,\n                 uint64_t id,\n                 uint64_t ino,\n                 bool files,\n                 bool dirs,\n                 uint64_t depth,\n                 bool printonly,\n                 const std::string& exportfs)\n{\n  FindRequest request;\n\n  if (files && !dirs) {\n    // query files\n    request.set_type(eos::rpc::FILE);\n  } else if (dirs && !files) {\n    // query container\n    request.set_type(eos::rpc::CONTAINER);\n  } else {\n    // query files & container\n    request.set_type(eos::rpc::LISTING);\n  }\n\n  if (path.length()) {\n    request.mutable_id()->set_path(path);\n  } else if (id) {\n    request.mutable_id()->set_id(id);\n  } else if (ino) {\n    request.mutable_id()->set_ino(ino);\n  } else {\n    return \"\";\n  }\n\n  if (depth) {\n    request.set_maxdepth(depth);\n  }\n\n  request.set_authkey(token());\n\n  for (const auto& filter : filters) {\n    // enable filtering\n    request.mutable_selection()->set_select(true);\n    std::map<std::string, std::string> filtermap;\n    eos::common::StringConversion::GetKeyValueMap(filter.c_str(),\n        filtermap);\n\n    for (auto const& x : filtermap) {\n      if (x.first == \"owner-root\") {\n        request.mutable_selection()->set_owner_root(strtoul(x.second.c_str(), 0,\n            10) ? true : false);\n      }  else if (x.first == \"group-root\") {\n        request.mutable_selection()->set_group_root(strtoul(x.second.c_str(), 0,\n            10) ? true : false);\n      } else if (x.first == \"owner\") {\n        request.mutable_selection()->set_owner(strtoul(x.second.c_str(), 0, 10));\n      } else if (x.first == \"group\") {\n        request.mutable_selection()->set_group(strtoul(x.second.c_str(), 0, 10));\n      } else if (x.first == \"regex-filename\") {\n        request.mutable_selection()->set_regexp_filename(x.second);\n      } else if (x.first == \"regex-dirname\") {\n        request.mutable_selection()->set_regexp_dirname(x.second);\n      } else if (x.first == \"zero-size\") {\n        request.mutable_selection()->mutable_size()->set_zero(strtoul(x.second.c_str(),\n            0, 10) ? true : false);\n      } else if (x.first == \"min-size\") {\n        request.mutable_selection()->mutable_size()->set_min(strtoul(x.second.c_str(),\n            0, 10));\n      } else if (x.first == \"max-size\") {\n        request.mutable_selection()->mutable_size()->set_max(strtoul(x.second.c_str(),\n            0, 10));\n      } else if (x.first == \"min-children\") {\n        request.mutable_selection()->mutable_children()->set_min(strtoul(\n              x.second.c_str(), 0, 10));\n      } else if (x.first == \"max-children\") {\n        request.mutable_selection()->mutable_children()->set_max(strtoul(\n              x.second.c_str(), 0, 10));\n      } else if (x.first == \"zero-children\") {\n        request.mutable_selection()->mutable_children()->set_zero(strtoul(\n              x.second.c_str(), 0, 10) ? true : false);\n      } else if (x.first == \"min-locations\") {\n        request.mutable_selection()->mutable_locations()->set_min(strtoul(\n              x.second.c_str(), 0, 10));\n      } else if (x.first == \"max-locations\") {\n        request.mutable_selection()->mutable_locations()->set_max(strtoul(\n              x.second.c_str(), 0, 10));\n      } else if (x.first == \"zero-locations\") {\n        request.mutable_selection()->mutable_locations()->set_zero(strtoul(\n              x.second.c_str(), 0, 10) ? true : false);\n      } else if (x.first == \"min-unlinked_locations\") {\n        request.mutable_selection()->mutable_unlinked_locations()->set_min(strtoul(\n              x.second.c_str(), 0, 10));\n      } else if (x.first == \"max-unlinked_locations\") {\n        request.mutable_selection()->mutable_unlinked_locations()->set_max(strtoul(\n              x.second.c_str(), 0, 10));\n      } else if (x.first == \"zero-unlinked_locations\") {\n        request.mutable_selection()->mutable_unlinked_locations()->set_zero(strtoul(\n              x.second.c_str(), 0, 10) ? true : false);\n      } else if (x.first == \"min-treesize\") {\n        request.mutable_selection()->mutable_treesize()->set_min(strtoul(\n              x.second.c_str(), 0, 10));\n      } else if (x.first == \"max-treesize\") {\n        request.mutable_selection()->mutable_treesize()->set_max(strtoul(\n              x.second.c_str(), 0, 10));\n      } else if (x.first == \"zero-treesize\") {\n        request.mutable_selection()->mutable_treesize()->set_zero(strtoul(\n              x.second.c_str(), 0, 10) ? true : false);\n      } else if (x.first == \"min-ctime\") {\n        request.mutable_selection()->mutable_ctime()->set_min(strtoul(x.second.c_str(),\n            0, 10));\n      } else if (x.first == \"max-ctime\") {\n        request.mutable_selection()->mutable_ctime()->set_max(strtoul(x.second.c_str(),\n            0, 10));\n      } else if (x.first == \"zero-ctime\") {\n        request.mutable_selection()->mutable_ctime()->set_zero(strtoul(x.second.c_str(),\n            0, 10) ? true : false);\n      } else if (x.first == \"min-mtime\") {\n        request.mutable_selection()->mutable_mtime()->set_min(strtoul(x.second.c_str(),\n            0, 10));\n      } else if (x.first == \"max-mtime\") {\n        request.mutable_selection()->mutable_mtime()->set_max(strtoul(x.second.c_str(),\n            0, 10));\n      } else if (x.first == \"zero-mtime\") {\n        request.mutable_selection()->mutable_mtime()->set_zero(strtoul(x.second.c_str(),\n            0, 10) ? true : false);\n      } else if (x.first == \"min-stime\") {\n        request.mutable_selection()->mutable_stime()->set_min(strtoul(x.second.c_str(),\n            0, 10));\n      } else if (x.first == \"max-stime\") {\n        request.mutable_selection()->mutable_stime()->set_max(strtoul(x.second.c_str(),\n            0, 10));\n      } else if (x.first == \"zero-stime\") {\n        request.mutable_selection()->mutable_stime()->set_zero(strtoul(x.second.c_str(),\n            0, 10) ? true : false);\n      } else if (x.first == \"layoutid\") {\n        request.mutable_selection()->set_layoutid(strtoull(x.second.c_str(), 0, 10));\n      } else if (x.first == \"flags\") {\n        request.mutable_selection()->set_flags(strtoull(x.second.c_str(), 0, 10));\n      } else if (x.first == \"symlink\") {\n        request.mutable_selection()->set_symlink(strtoul(x.second.c_str(), 0,\n            10) ? true : false);\n      } else if (x.first == \"checksum-type\") {\n        request.mutable_selection()->mutable_checksum()->set_type(x.second);\n      } else if (x.first == \"checksum-value\") {\n        request.mutable_selection()->mutable_checksum()->set_value(x.second);\n      } else if (x.first == \"xattr\") {\n        std::string key;\n        std::string val;\n        eos::common::StringConversion::SplitKeyValue(x.second, key, val, \"=\");\n        (*(request.mutable_selection()->mutable_xattr()))[key] = val;\n      } else {\n        std::cerr << \"error: unknown filter '\" << x.first << \":\" << x.second << \"'\" <<\n                  std::endl;\n        return \"\";\n      }\n    }\n  }\n\n  MDResponse response;\n  ClientContext context;\n  std::string responsestring;\n  CompletionQueue cq;\n  Status status;\n  std::unique_ptr<ClientAsyncReader<MDResponse> > rpc(\n    stub_->AsyncFind(&context, request, &cq, (void*) 1));\n  void* got_tag;\n  bool ok = false;\n  bool ret = cq.Next(&got_tag, &ok);\n\n  while (1) {\n    rpc->Read(&response, (void*) 1);\n    ok = false;\n    ret = cq.Next(&got_tag, &ok);\n\n    if (!ret || !ok || got_tag != (void*) 1) {\n      break;\n    }\n\n    if (!exportfs.empty()) {\n      responsestring = ExportFs(response, exportfs);\n    } else {\n      google::protobuf::util::JsonPrintOptions options;\n      options.add_whitespace = true;\n#if GOOGLE_PROTOBUF_VERSION >= 5027000\n      options.always_print_fields_with_no_presence = true;\n#else\n      options.always_print_primitive_fields = true;\n#endif\n      std::string jsonstring;\n      (void) google::protobuf::util::MessageToJsonString(response,\n          &jsonstring, options);\n\n      if (printonly) {\n        std::cout << jsonstring << std::endl;\n      } else {\n        responsestring += jsonstring;\n      }\n    }\n  }\n\n  if (!status.ok()) {\n    std::cerr << \"error: \" << status.error_message() << std::endl;\n  }\n\n  return responsestring;\n}\n\nint\nGrpcClient::FileInsert(const std::vector<std::string>& paths)\n{\n  FileInsertRequest request;\n  size_t cnt = 0;\n\n  for (auto it : paths) {\n    std::string path = it;\n    struct timespec tsnow;\n    eos::common::Timing::GetTimeSpec(tsnow);\n    uint64_t inode = 0;\n    cnt++;\n    FileMdProto* file = request.add_files();\n\n    if (it.substr(0, 4) == \"ino:\") {\n      // the format is ino:xxxxxxxxxxxxxxxx:<path> where xxxxxxxxxxxxxxxx is a 64bit hex string of the inode\n      path = it.substr(21);\n      inode = std::strtol(it.substr(4, 20).c_str(), 0, 16);\n    }\n\n    if (inode) {\n      file->set_id(inode);\n    }\n\n    file->set_path(path);\n    file->set_uid(2);\n    file->set_gid(2);\n    file->set_size(cnt);\n    file->set_layout_id(0x00100002);\n    file->mutable_checksum()->set_value(\"\\0\\0\\0\\1\", 4);\n    file->set_flags(0);\n    file->mutable_ctime()->set_sec(tsnow.tv_sec);\n    file->mutable_ctime()->set_n_sec(tsnow.tv_nsec);\n    file->mutable_mtime()->set_sec(tsnow.tv_sec);\n    file->mutable_mtime()->set_n_sec(tsnow.tv_nsec);\n    file->mutable_locations()->Add(65535);\n    auto map = file->mutable_xattrs();\n    (*map)[\"sys.acl\"] = \"u:100:rwx\";\n    (*map)[\"sys.cta.id\"] = \"fake\";\n  }\n\n  request.set_authkey(token());\n  InsertReply reply;\n  ClientContext context;\n  // The producer-consumer queue we use to communicate asynchronously with the\n  // gRPC runtime.\n  CompletionQueue cq;\n  Status status;\n  std::unique_ptr<ClientAsyncResponseReader<InsertReply> > rpc(\n    stub_->AsyncFileInsert(&context, request, &cq));\n  // Request that, upon completion of the RPC, \"reply\" be updated with the\n  // server's response; \"status\" with the indication of whether the operation\n  // was successful. Tag the request with the integer 1.\n  rpc->Finish(&reply, &status, (void*) 1);\n  void* got_tag;\n  bool ok = false;\n  // Block until the next result is available in the completion queue \"cq\".\n  // The return value of Next should always be checked. This return value\n  // tells us whether there is any kind of event or the cq_ is shutting down.\n  ABSL_CHECK(cq.Next(&got_tag, &ok));\n  // Verify that the result from \"cq\" corresponds, by its tag, our previous\n  // request.\n  ABSL_CHECK(got_tag == (void*) 1);\n  // ... and that the request was completed successfully. Note that \"ok\"\n  // corresponds solely to the request for updates introduced by Finish().\n  ABSL_CHECK(ok);\n  // Act upon the status of the actual RPC.\n  int retc = 0;\n\n  if (status.ok()) {\n    for (auto it : reply.retc()) {\n      retc |= it;\n    }\n\n    return retc;\n  } else {\n    return -1;\n  }\n}\n\nint\nGrpcClient::ContainerInsert(const std::vector<std::string>& paths)\n{\n  ContainerInsertRequest request;\n\n  for (auto it : paths) {\n    std::string path;\n    struct timespec tsnow;\n    eos::common::Timing::GetTimeSpec(tsnow);\n    uint64_t inode = 0 ;\n\n    if (it.substr(0, 4) == \"ino:\") {\n      // the format is ino:xxxxxxxxxxxxxxxx:<path> where xxxxxxxxxxxxxxxx is a 64bit hex string of the inode\n      path = it.substr(21);\n      inode = std::strtol(it.substr(4, 20).c_str(), 0, 16);\n    }\n\n    ContainerMdProto* container = request.add_container();\n\n    if (inode) {\n      container->set_id(inode);\n    }\n\n    container->set_path(path);\n    container->set_uid(2);\n    container->set_gid(2);\n    container->set_mode(S_IFDIR | S_IRWXU);\n    container->mutable_ctime()->set_sec(tsnow.tv_sec);\n    container->mutable_ctime()->set_n_sec(tsnow.tv_nsec);\n    container->mutable_mtime()->set_sec(tsnow.tv_sec);\n    container->mutable_mtime()->set_n_sec(tsnow.tv_nsec);\n    auto map = container->mutable_xattrs();\n    (*map)[\"sys.acl\"] = \"u:100:rwx\";\n    (*map)[\"sys.forced.checksum\"] = \"adler\";\n    (*map)[\"sys.forced.space\"] = \"default\";\n    (*map)[\"sys.forced.nstripes\"] = \"1\";\n    (*map)[\"sys.forced.layout\"] = \"replica\";\n  }\n\n  request.set_authkey(token());\n  InsertReply reply;\n  ClientContext context;\n  // The producer-consumer queue we use to communicate asynchronously with the\n  // gRPC runtime.\n  CompletionQueue cq;\n  Status status;\n  std::unique_ptr<ClientAsyncResponseReader<InsertReply> > rpc(\n    stub_->AsyncContainerInsert(&context, request, &cq));\n  // Request that, upon completion of the RPC, \"reply\" be updated with the\n  // server's response; \"status\" with the indication of whether the operation\n  // was successful. Tag the request with the integer 1.\n  rpc->Finish(&reply, &status, (void*) 1);\n  void* got_tag;\n  bool ok = false;\n  // Block until the next result is available in the completion queue \"cq\".\n  // The return value of Next should always be checked. This return value\n  // tells us whether there is any kind of event or the cq_ is shutting down.\n  ABSL_CHECK(cq.Next(&got_tag, &ok));\n  // Verify that the result from \"cq\" corresponds, by its tag, our previous\n  // request.\n  ABSL_CHECK(got_tag == (void*) 1);\n  // ... and that the request was completed successfully. Note that \"ok\"\n  // corresponds solely to the request for updates introduced by Finish().\n  ABSL_CHECK(ok);\n  // Act upon the status of the actual RPC.\n  int retc = 0;\n\n  if (status.ok()) {\n    for (auto it : reply.retc()) {\n      retc |= it;\n    }\n\n    return retc;\n  } else {\n    return -1;\n  }\n}\n\n\nstd::unique_ptr<GrpcClient>\nGrpcClient::Create(std::string endpoint, std::string token, std::string keyfile,\n                   std::string certfile, std::string cafile, bool force_ssl)\n{\n  std::string key;\n  std::string cert;\n  std::string ca;\n  bool ssl_cred = false;\n\n  if (keyfile.length() || certfile.length() || cafile.length()) {\n    if (!keyfile.length() || !certfile.length() || !cafile.length()) {\n      return 0;\n    }\n\n    force_ssl = true;\n    ssl_cred = true;\n\n    if (eos::common::StringConversion::LoadFileIntoString(certfile.c_str(),\n        cert) && !cert.length()) {\n      fprintf(stderr, \"error: unable to load ssl certificate file '%s'\\n\",\n              certfile.c_str());\n      return 0;\n    }\n\n    if (eos::common::StringConversion::LoadFileIntoString(keyfile.c_str(),\n        key) && !key.length()) {\n      fprintf(stderr, \"unable to load ssl key file '%s'\\n\", keyfile.c_str());\n      return 0;\n    }\n\n    if (eos::common::StringConversion::LoadFileIntoString(cafile.c_str(),\n        ca) && !ca.length()) {\n      fprintf(stderr, \"unable to load ssl ca file '%s'\\n\", cafile.c_str());\n      return 0;\n    }\n  }\n\n  grpc::SslCredentialsOptions opts;\n\n  if (ssl_cred) {\n    opts.pem_root_certs = ca;\n    opts.pem_private_key = key;\n    opts.pem_cert_chain = cert;\n  }\n\n  std::unique_ptr<eos::client::GrpcClient> p(new eos::client::GrpcClient(\n        grpc::CreateChannel(\n          endpoint,\n          (force_ssl ?\n           grpc::SslCredentials(opts) :\n           grpc::InsecureChannelCredentials()))));\n  p->set_token(token);\n  return p;\n}\n\nint\nGrpcClient::NsStat(const eos::rpc::NsStatRequest& request,\n                   eos::rpc::NsStatResponse& reply)\n{\n  ClientContext context;\n  CompletionQueue cq;\n  Status status;\n  std::unique_ptr<ClientAsyncResponseReader<NsStatResponse>> rpc(\n        stub_->AsyncNsStat(&context, request, &cq));\n  rpc->Finish(&reply, &status, (void*) 1);\n  void* got_tag;\n  bool ok = false;\n  ABSL_CHECK(cq.Next(&got_tag, &ok));\n  ABSL_CHECK(got_tag == (void*) 1);\n  ABSL_CHECK(ok);\n\n  // Act upon the status of the actual RPC\n  if (status.ok()) {\n    return reply.code();\n  } else {\n    return -1;\n  }\n}\n\nint\nGrpcClient::Exec(const eos::rpc::NSRequest& request,\n                 eos::rpc::NSResponse& reply)\n{\n  ClientContext context;\n  CompletionQueue cq;\n  Status status;\n  std::unique_ptr<ClientAsyncResponseReader<NSResponse> > rpc(\n    stub_->AsyncExec(&context, request, &cq));\n  rpc->Finish(&reply, &status, (void*) 1);\n  void* got_tag;\n  bool ok = false;\n  ABSL_CHECK(cq.Next(&got_tag, &ok));\n  ABSL_CHECK(got_tag == (void*) 1);\n  ABSL_CHECK(ok);\n\n  // Act upon the status of the actual RPC.\n  if (status.ok()) {\n    return reply.error().code();\n  } else {\n    return -1;\n  }\n}\n\nstd::string\nGrpcClient::ExportFs(const eos::rpc::MDResponse& response,\n                     const std::string& exportfs)\n{\n  bool first = false;\n\n  if (response.type() == eos::rpc::CONTAINER) {\n    if (!tree.size()) {\n      first = true;\n      tree[response.cmd().id()] = response.cmd().name() + \"/\";\n    } else {\n      first = false;\n      tree[response.cmd().id()] = tree[response.cmd().parent_id()] +\n                                  response.cmd().name() + \"/\";\n    }\n\n    fprintf(stderr, \"%s\\n\", tree[response.cmd().id()].c_str());\n\n    if (!first) {\n      std::string target = exportfs + \"/\" + tree[response.cmd().id()];\n      eos::common::Path cPath(target.c_str());\n\n      if (!cPath.MakeParentPath(755)) {\n        fprintf(stderr, \"error: failed to created '%s'\\n\", cPath.GetParentPath());\n        exit(errno);\n      }\n\n      int rc = mkdir(cPath.GetPath(), 755);\n\n      if (rc) {\n        fprintf(stderr, \"error: failed to created '%s'\\n\", cPath.GetPath());\n        exit(errno);\n      }\n    }\n  }\n\n  if (response.type() == eos::rpc::FILE) {\n    fprintf(stderr, \"%s\\n\", (tree[response.fmd().cont_id()] +\n                             response.fmd().name()).c_str());\n  }\n\n  return \"\";\n}\n\nEOSCLIENTNAMESPACE_END\n#endif\n"
  },
  {
    "path": "client/grpc/GrpcClient.hh",
    "content": "// ----------------------------------------------------------------------\n// File: GrpcClient.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"client/Namespace.hh\"\n#include \"common/AssistedThread.hh\"\n#include <list>\n\n#ifdef EOS_GRPC\n#include <grpc++/grpc++.h>\n#include \"proto/Rpc.grpc.pb.h\"\n\nEOSCLIENTNAMESPACE_BEGIN\n\n/**\n * @file   GrpcClient.hh\n *\n * @brief  This class implements a gRPC client for an EOS grpc server\n *\n */\n\n\nclass GrpcClient\n{\npublic:\n\n  explicit GrpcClient(std::shared_ptr<grpc::Channel> channel)\n    : stub_(eos::rpc::Eos::NewStub(channel)) { }\n\n  // convenience factory function\n  static std::unique_ptr<GrpcClient>\n  Create(std::string endpoint = \"localhost:50051\",\n         std::string token = \"\",\n         std::string keyfile = \"\",\n         std::string certfile = \"\",\n         std::string cafile = \"\",\n         bool force_ssl = false);\n\n  std::string Ping(const std::string& payload);\n\n  std::string Md(const std::string& path, uint64_t id = 0, uint64_t ino = 0,\n                 bool list = false, bool printonly = false);\n\n  std::string Find(const std::string& path,\n                   const std::list<std::string>& find_options, uint64_t id = 0, uint64_t ino = 0,\n                   bool files = true, bool dirs = true, uint64_t depth = 0, bool printonly = false,\n                   const std::string& exportfs = \"\");\n\n  int NsStat(const eos::rpc::NsStatRequest& request,\n             eos::rpc::NsStatResponse& reply);\n\n  int Exec(const eos::rpc::NSRequest& request,\n           eos::rpc::NSResponse& reply);\n\n  std::string ExportFs(const eos::rpc::MDResponse& response,\n                       const std::string& exportfs);\n\n  int FileInsert(const std::vector<std::string>& paths);\n  int ContainerInsert(const std::vector<std::string>& paths);\n\n  void set_token(const std::string& _token)\n  {\n    mToken = _token;\n  }\n\n  std::string token() const\n  {\n    return mToken;\n  }\n\nprivate:\n  std::unique_ptr<eos::rpc::Eos::Stub> stub_;\n  std::string mToken;\n  std::map<uint64_t, std::string> tree;\n};\n\nEOSCLIENTNAMESPACE_END\n#endif\n"
  },
  {
    "path": "client/grpc/GrpcClientAuthProcessor.hh",
    "content": "// ----------------------------------------------------------------------\n// File: GrpcClientAuthProcessor.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n/*----------------------------------------------------------------------------*/\n#include \"client/Namespace.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n#ifdef EOS_GRPC\n#include <grpc++/grpc++.h>\n\n/*----------------------------------------------------------------------------*/\n\nEOSCLIENTNAMESPACE_BEGIN\n\n/**\n * @file   GrpcClientAuthProcessor.hh\n *\n * @brief  This class implements an authentication processor for an EOS GRPC client\n *         allowing to extract the client property name\n *\n */\n\n\nclass GrpcClientProcessor : public grpc::AuthMetadataProcessor\n{\npublic:\n\n  struct Const {\n\n    static const std::string& TokenKeyName()\n    {\n      static std::string _(\"token\");\n      return _;\n    }\n\n    static const std::string& PeerIdentityPropertyName()\n    {\n      static std::string _(\"username\");\n      return _;\n    }\n  };\n\n  grpc::Status Process(const InputMetadata& auth_metadata,\n                       grpc::AuthContext* context, OutputMetadata* consumed_auth_metadata,\n                       OutputMetadata* response_metadata) override\n  {\n    // determine intercepted method\n    std::string dispatch_keyname = \":path\";\n    auto dispatch_kv = auth_metadata.find(dispatch_keyname);\n\n    if (dispatch_kv == auth_metadata.end()) {\n      return grpc::Status(grpc::StatusCode::INTERNAL, \"Internal Error\");\n    }\n\n    // if token metadata not necessary, return early, avoid token checking\n    auto dispatch_value = std::string(dispatch_kv->second.data());\n\n    if (dispatch_value == \"/MyPackage.MyService/Authenticate\") {\n      return grpc::Status::OK;\n    }\n\n    // determine availability of token metadata\n    auto token_kv = auth_metadata.find(Const::TokenKeyName());\n\n    if (token_kv == auth_metadata.end()) {\n      return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, \"Missing Token\");\n    }\n\n    // determine validity of token metadata\n    auto token_value = std::string(token_kv->second.data());\n\n    if (tokens.count(token_value) == 0) {\n      return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, \"Invalid Token\");\n    }\n\n    // once verified, mark as consumed and store user for later retrieval\n    consumed_auth_metadata->insert(std::make_pair(Const::TokenKeyName(),\n                                   token_value)); // required\n    context->AddProperty(Const::PeerIdentityPropertyName(),\n                         tokens[token_value]); // optional\n    context->SetPeerIdentityPropertyName(\n      Const::PeerIdentityPropertyName()); // optional\n    return grpc::Status::OK;\n  }\n\n  std::map<std::string, std::string> tokens;\n};\n\n#endif\n\nEOSCLIENTNAMESPACE_END\n\n"
  },
  {
    "path": "client/grpc/Insert.cc",
    "content": "#include <string>\n#include <iostream>\n#include <fstream>\n#include \"client/grpc/GrpcClient.hh\"\n#include <stdio.h>\n#include \"common/StringConversion.hh\"\n\nint usage(const char* prog)\n{\n  fprintf(stderr, \"usage: %s [--key <ssl-key-file> \"\n          \"--cert <ssl-cert-file> \"\n          \"--ca <ca-cert-file>] \"\n          \"[--endpoint <host:port>] [--token <auth-token>] \"\n          \"[--prefix prefix] \"\n          \"[--treefile <treefile>] \"\n          \"[--force-ssl] \\n\", prog);\n  fprintf(stderr,\n          \"treefile format providing inodes: \\n\"\n          \"----------------------------------\\n\"\n          \"ino:000000000000ffff:/eos/mydir/\\n\"\n          \"ino:000000000000ff01:/eos/mydir/myfile\\n\\n\");\n  fprintf(stderr,\n          \"treefile format without inodes: \\n\"\n          \"----------------------------------\\n\"\n          \"/eos/mydir/\\n\"\n          \"/eos/mydir/myfile\\n\\n\");\n  return -1;\n}\n\nint main(int argc, const char* argv[])\n{\n  std::string endpoint = \"localhost:50051\";\n  std::string token = \"\";\n  std::string key;\n  std::string cert;\n  std::string ca;\n  std::string keyfile;\n  std::string certfile;\n  std::string cafile;\n  std::string prefix = \"/grpc\";\n  std::string treefile = \"namespace.txt\";\n  bool force_ssl = false;\n\n  for (auto i = 1; i < argc; ++i) {\n    std::string option = argv[i];\n\n    if (option == \"--key\") {\n      if (argc > i + 1) {\n        keyfile = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--cert\") {\n      if (argc > i + 1) {\n        certfile = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--ca\") {\n      if (argc > i + 1) {\n        cafile = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--endpoint\") {\n      if (argc > i + 1) {\n        endpoint = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--token\") {\n      if (argc > i + 1) {\n        token = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--prefix\") {\n      if (argc > i + 1) {\n        prefix = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--treefile\") {\n      if (argc > i + 1) {\n        treefile = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--force-ssl\") {\n      force_ssl = true;\n      continue;\n    }\n\n    return usage(argv[0]);\n  }\n\n  if (keyfile.length() || certfile.length() || cafile.length()) {\n    if (!keyfile.length() || !certfile.length() || !cafile.length()) {\n      return usage(argv[0]);\n    }\n  }\n\n  std::unique_ptr<eos::client::GrpcClient> eosgrpc =\n    eos::client::GrpcClient::Create(\n      endpoint,\n      token,\n      keyfile,\n      certfile,\n      cafile,\n      force_ssl);\n\n  if (!eosgrpc) {\n    return usage(argv[0]);\n  }\n\n  std::cout << \"=> settings: prefix=\" << prefix << \" treefile=\" << treefile <<\n            std::endl;\n  std::ifstream input(treefile);\n  size_t n = 0;\n  size_t bulk = 1000;\n  bool dirmode = true;\n  std::vector<std::string> paths;\n  std::chrono::steady_clock::time_point watch_global =\n    std::chrono::steady_clock::now();\n\n  for (std::string line ; std::getline(input, line);) {\n    n++;\n\n    if (line.substr(0, 4) == \"ino:\") {\n      line.insert(21, prefix);\n    } else {\n      line.insert(0, prefix);\n    }\n\n    std::cout << n << \" \" << line << std::endl;\n\n    if (line.back() == '/') {\n      // dir\n      if (dirmode) {\n        paths.push_back(line);\n      } else {\n        // SEND OFF DIRS\n        int retc = eosgrpc->FileInsert(paths);\n        std::cout << \"::send::files\" << \" retc=\" << retc << std::endl;\n        paths.clear();\n        paths.push_back(line);\n        dirmode = true;\n      }\n    } else {\n      // file\n      if (dirmode) {\n        // SEND OFF FILES\n        int retc = eosgrpc->ContainerInsert(paths);\n        std::cout << \"::send::dirs \" << \" retc=\" << retc << std::endl;\n        paths.clear();\n        paths.push_back(line);\n        dirmode = false;\n      } else {\n        paths.push_back(line);\n      }\n    }\n\n    if (paths.size() >= bulk) {\n      if (dirmode) {\n        // SEND OF DIRS\n        int retc = eosgrpc->ContainerInsert(paths);\n        std::cout << \"::send::dirs\" << \" retc=\" << retc << std::endl;\n        paths.clear();\n      } else {\n        // SEND OF FILES\n        int retc = eosgrpc->FileInsert(paths);\n        std::cout << \"::send::files\" << \" retc=\" << retc << std::endl;\n        paths.clear();\n      }\n    }\n  }\n\n  std::chrono::microseconds elapsed_global =\n    std::chrono::duration_cast<std::chrono::microseconds>\n    (std::chrono::steady_clock::now() - watch_global);\n  std::cout << n << \" requests took \" << elapsed_global.count() <<\n            \" micro seconds\" << std::endl;\n  return 0;\n}\n"
  },
  {
    "path": "client/grpc/Md.cc",
    "content": "#include <string>\n#include <iostream>\n#include \"client/grpc/GrpcClient.hh\"\n#include <stdio.h>\n#include \"common/StringConversion.hh\"\n\nint usage(const char* prog)\n{\n  fprintf(stderr, \"usage: %s [--key <ssl-key-file> \"\n          \"--cert <ssl-cert-file> \"\n          \"--ca <ca-cert-file>] \"\n          \"[--endpoint <host:port>] [--token <auth-token>] [-l] [--force-ssl] <path>\\n\",\n          prog);\n  return -1;\n}\n\nint main(int argc, const char* argv[])\n{\n  std::string endpoint = \"localhost:50051\";\n  std::string token = \"\";\n  std::string key;\n  std::string cert;\n  std::string ca;\n  std::string keyfile;\n  std::string certfile;\n  std::string cafile;\n  std::string path = \"\";\n  bool listing = false;\n  bool force_ssl = false;\n\n  for (auto i = 1; i < argc; ++i) {\n    std::string option = argv[i];\n\n    if (option == \"--key\") {\n      if (argc > i + 1) {\n        keyfile = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--cert\") {\n      if (argc > i + 1) {\n        certfile = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--ca\") {\n      if (argc > i + 1) {\n        cafile = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--endpoint\") {\n      if (argc > i + 1) {\n        endpoint = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--token\") {\n      if (argc > i + 1) {\n        token = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"-l\") {\n      listing = true;\n      continue;\n    }\n\n    if (option == \"--force-ssl\") {\n      force_ssl = true;\n      continue;\n    }\n\n    path = option;\n\n    if (argc > (i + 1)) {\n      return usage(argv[0]);\n    }\n  }\n\n  if (keyfile.length() || certfile.length() || cafile.length()) {\n    if (!keyfile.length() || !certfile.length() || !cafile.length()) {\n      return usage(argv[0]);\n    }\n  }\n\n  if (path.empty()) {\n    return usage(argv[0]);\n  }\n\n  std::unique_ptr<eos::client::GrpcClient> eosgrpc =\n    eos::client::GrpcClient::Create(\n      endpoint,\n      token,\n      keyfile,\n      certfile,\n      cafile,\n      force_ssl);\n\n  if (!eosgrpc) {\n    return usage(argv[0]);\n  }\n\n  std::chrono::steady_clock::time_point watch_global =\n    std::chrono::steady_clock::now();\n  std::string reply = eosgrpc->Md(path, 0, 0, listing, true);\n  std::chrono::microseconds elapsed_global =\n    std::chrono::duration_cast<std::chrono::microseconds>\n    (std::chrono::steady_clock::now() - watch_global);\n  std::cout << \"request took \" << elapsed_global.count() <<\n            \" micro seconds\" << std::endl;\n  return 0;\n}\n"
  },
  {
    "path": "client/grpc/Ns.cc",
    "content": "#include <string>\n#include <iostream>\n#include \"client/grpc/GrpcClient.hh\"\n#include <stdio.h>\n#include \"common/StringConversion.hh\"\n#include \"proto/Recycle.pb.h\"\n#include \"console/commands/helpers/RecycleHelper.hh\"\n#include <google/protobuf/util/json_util.h>\n\nint usage(const char* prog)\n{\n  fprintf(stderr, \"usage: %s [--key <ssl-key-file> \"\n          \"--cert <ssl-cert-file> \"\n          \"--ca <ca-cert-file>] \"\n          \"--ca <ca-cert-file>] \"\n          \"[--endpoint <host:port>] [--token <auth-token>] [--xattr <key:val>] [--mode <mode>] [--username <username>] [ [--groupname <groupname>] [--uid <uid>] [--gid <gid>] [--app <app>] [--owner-uid <uid>] [--owner-gid <gid>] [--acl <acl>] [--sysacl] [--norecycle] [--force-ssl] [-r] [--max-version <max-version>] [--target <target>] [--year <year>] [--month <month>] [--day <day>] [--inodes <#>] [--volume <#>] [--quota volume|inode] [--position <position>] [--front] -p <path> <command>\\n\",\n          prog);\n  fprintf(stderr,\n          \"                                [-r] -p <path> mkdir\\n\"\n          \"                                [-r] -p <path> rmdir\\n\"\n          \"                                     -p <path> touch\\n\"\n          \"                       [--norecycle] -p <path> rm\\n\"\n          \"                   --target <target> -p <path> rename\\n\"\n          \"                   --target <target> -p <path> symlink\\n\"\n          \"              [-r] --xattr <key=val> -p <path> setxattr # sets key=val\\n\"\n          \"               [-r]  --xattr <!key=> -p <path> setxattr # deletes key\\n\"\n          \" --owner-uid <uid> --owner-gid <gid> -p <path> chown\\n\"\n          \"                       --mode <mode> -p <path> chmod\\n\"\n          \" [--sysacl] [-r] [--acl <acl>] [--position <pos>] [--front] -p <path> acl\\n\"\n          \"     --ztoken <token> | [--acl] [-r] -p <path> token\\n\"\n          \"                [--max-version <max> -p <path> create-version\\n\"\n          \"                                     -p <path> list-version\\n\"\n          \"                [--max-version <max> -p <path> purge-version\\n\"\n          \"                [--max-version <max> -p <path> purge-version\\n\"\n          \"                [--max-version <max> -p <path> purge-version\\n\"\n          \"                                               old_recycle ls\\n\"\n          \"                                     -p <key>  old_recycle restore\\n\"\n          \" --year <year> [--month <month> [--day <day>]] old_recycle purge\\n\"\n          \"                                     -p <key>  old_recycle purge\\n\"\n          \"                                               recycle ls [<date> [<limit>]] [-m] [-n] [--all] [--rid <val>]\\n\"\n          \"                                               recycle purge [--all] [--uid] [--rid <val>] <date> | -k <key>]\\n\"\n          \"                                               recycle restore [-p] [-f|--force-original-name] [-r|--restore-versions] <recycle-key>\\n\"\n          \"                                               recycle project --path <path> [--acl <val>]\\n\"\n          \"                                               recycle config [--add-bin|--remove-bin <subtree>] [--lifetime <seconds>] [--ratio <ratio>] [--size <size>] [--inodes <inodes>] [--collect-interval <seconds>] [--remove-interval <seconds>] [--dry-run <val>] [--dump]\\n\"\n          \"[--username <u> | --groupname <g>] [-p <path>] quota get\\n\"\n          \"[--username <u> | --groupname <g>] [-p <path>] --inodes <#> --volume <#> --quota user|group|project quota set\\n\"\n          \"[--username <u> | --groupname <g>] [-p <path>] quota rm\\n\"\n          \"                                   [-p <path>] quota rmnode\\n\");\n  return -1;\n}\n\nint ParseRecycleCommand(int argc, const char* argv[], int arg_index,\n                        std::string& subcmd, std::string& path,\n                        eos::rpc::NSRequest& request)\n{\n  std::string command_line;\n\n  if (subcmd.empty()) {\n    command_line = subcmd;\n  } else {\n    for (int i = arg_index; i < argc; i++) {\n      command_line += argv[i];\n      command_line += \" \";\n    }\n\n    // Remove trailing space\n    command_line.pop_back();\n  }\n\n  GlobalOptions opts;\n  RecycleHelper recycle_helper(opts);\n\n  if (recycle_helper.ParseCommand(command_line.c_str())) {\n    request.mutable_recycle()->CopyFrom(recycle_helper.GetRequest().recycle());\n    return 0;\n  } else {\n    std::cerr << \"error: failed to parse recycle command \"\n              << command_line << std::endl;\n    return EINVAL;\n  }\n}\n\nint main(int argc, const char* argv[])\n{\n  std::string endpoint = \"localhost:50051\";\n  std::string token = \"\";\n  std::string key;\n  std::string cert;\n  std::string ca;\n  std::string keyfile;\n  std::string certfile;\n  std::string cafile;\n  std::string cmd = \"\";\n  std::string subcmd = \"\";\n  std::string path = \"\";\n  std::string target = \"\";\n  std::string xattr = \"\";\n  std::string acl = \"\";\n  mode_t mode = 0775;\n  int64_t max_version = -1;\n  uid_t uid = 0;\n  gid_t gid = 0;\n  std::string app;\n  uint32_t day = 0;\n  uint32_t month = 0;\n  uint32_t year = 0;\n  uint64_t inodes = 0;\n  uint64_t volume = 0;\n  std::string qtype;\n  std::string username;\n  std::string groupname;\n  uid_t owner_uid = 0;\n  gid_t owner_gid = 0;\n  bool recursive = false;\n  bool norecycle = false;\n  bool sysacl = false;\n  uint32_t position = 0;\n  std::string eostoken = \"\";\n  bool force_ssl = false;\n  int arg_index = 0;\n\n  for (auto i = 1; i < argc; ++i) {\n    std::string option = argv[i];\n\n    if (option == \"--key\") {\n      if (argc > i + 1) {\n        keyfile = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--cert\") {\n      if (argc > i + 1) {\n        certfile = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--ca\") {\n      if (argc > i + 1) {\n        cafile = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--endpoint\") {\n      if (argc > i + 1) {\n        endpoint = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--token\") {\n      if (argc > i + 1) {\n        token = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--uid\") {\n      if (argc > i + 1) {\n        uid = strtoul(argv[i + 1], 0, 10);\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--gid\") {\n      if (argc > i + 1) {\n        gid = strtoul(argv[i + 1], 0, 10);\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--app\") {\n      if (argc > i + 1) {\n        app = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--inodes\") {\n      if (argc > i + 1) {\n        inodes = strtoul(argv[i + 1], 0, 10);\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--volume\") {\n      if (argc > i + 1) {\n        volume = strtoul(argv[i + 1], 0, 10);\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--quota\") {\n      if (argc > i + 1) {\n        qtype = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--username\") {\n      if (argc > i + 1) {\n        username = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--year\") {\n      if (argc > i + 1) {\n        year = strtoul(argv[i + 1], 0, 10);\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--month\") {\n      if (argc > i + 1) {\n        month = strtoul(argv[i + 1], 0, 10);\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--day\") {\n      if (argc > i + 1) {\n        day = strtoul(argv[i + 1], 0, 10);\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--groupname\") {\n      if (argc > i + 1) {\n        groupname = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--owner-uid\") {\n      if (argc > i + 1) {\n        owner_uid = strtoul(argv[i + 1], 0, 10);\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--owner-gid\") {\n      if (argc > i + 1) {\n        owner_gid = strtoul(argv[i + 1], 0, 10);\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"-p\") {\n      if (argc > i + 1) {\n        path = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--target\") {\n      if (argc > i + 1) {\n        target = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--acl\") {\n      if (argc > i + 1) {\n        acl = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--position\") {\n      if (position) {\n        std::cout << \"Please specify only one of --front or --position\" << std::endl;\n        return usage(argv[0]);\n      }\n\n      if (argc > i + 1) {\n        try {\n          position = std::stoi(argv[i + 1]);\n          ++i;\n        } catch (std::exception& e) {\n          return usage(argv[0]);\n        }\n\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--front\") {\n      if (position) {\n        std::cout << \"Please specify only one of --front or --position\" << std::endl;\n        return usage(argv[0]);\n      }\n\n      position = 1;\n      continue;\n    }\n\n    if (option == \"--mode\") {\n      if (argc > i + 1) {\n        mode = strtol(argv[i + 1], 0, 8);\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--max-version\") {\n      if (argc > i + 1) {\n        max_version = strtol(argv[i + 1], 0, 10);\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--xattr\") {\n      if (argc > i + 1) {\n        xattr = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"-r\") {\n      recursive = true;\n      continue;\n    }\n\n    if (option == \"--sysacl\") {\n      sysacl = true;\n      continue;\n    }\n\n    if (option == \"--norecycle\") {\n      norecycle = true;\n      continue;\n    }\n\n    if (option == \"--ztoken\") {\n      if (argc > i + 1) {\n        eostoken = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--force-ssl\") {\n      force_ssl = true;\n      continue;\n    }\n\n    cmd = option;\n\n    if (argc > (i + 1)) {\n      if (cmd == \"old_recycle\") {\n        subcmd = argv[i + 1];\n\n        if ((subcmd != \"ls\") &&\n            (subcmd != \"restore\") &&\n            (subcmd != \"purge\")) {\n          return usage(argv[0]);\n        }\n\n        break;\n      }\n\n      if (cmd == \"recycle\") {\n        arg_index = i;\n        subcmd = argv[i + 1];\n\n        if ((subcmd != \"ls\") &&\n            (subcmd != \"restore\") &&\n            (subcmd != \"purge\") &&\n            (subcmd != \"config\") &&\n            (subcmd != \"project\")) {\n          return usage(argv[0]);\n        }\n\n        break;\n      }\n\n      if (cmd == \"quota\") {\n        subcmd = argv[i + 1];\n\n        if ((subcmd != \"get\") &&\n            (subcmd != \"set\") &&\n            (subcmd != \"rm\") &&\n            (subcmd != \"rmnode\")) {\n          return usage(argv[0]);\n        }\n\n        break;\n      }\n\n      return usage(argv[0]);\n    }\n  }\n\n  if (keyfile.length() || certfile.length() || cafile.length()) {\n    if (!keyfile.length() || !certfile.length() || !cafile.length()) {\n      return usage(argv[0]);\n    }\n  }\n\n  if (cmd.empty() || ((cmd != \"quota\") && (cmd != \"old_recycle\") &&\n                      (cmd != \"recycle\") && path.empty() &&\n                      eostoken.empty())) {\n    return usage(argv[0]);\n  }\n\n  std::unique_ptr<eos::client::GrpcClient> eosgrpc =\n    eos::client::GrpcClient::Create(\n      endpoint,\n      token,\n      keyfile,\n      certfile,\n      cafile,\n      force_ssl);\n\n  if (!eosgrpc) {\n    return usage(argv[0]);\n  }\n\n  std::chrono::steady_clock::time_point watch_global =\n    std::chrono::steady_clock::now();\n  eos::rpc::NSRequest request;\n  eos::rpc::NSResponse reply;\n  request.set_authkey(token);\n\n  if (uid) {\n    request.mutable_role()->set_uid(uid);\n  }\n\n  if (gid) {\n    request.mutable_role()->set_gid(gid);\n  }\n\n  if (app.length()) {\n    request.mutable_role()->set_app(app);\n  }\n\n  google::protobuf::util::JsonPrintOptions options;\n  options.add_whitespace = true;\n#if GOOGLE_PROTOBUF_VERSION >= 5027000\n  options.always_print_fields_with_no_presence = true;\n#else\n  options.always_print_primitive_fields = true;\n#endif\n  std::string jsonstring;\n\n  if (cmd == \"mkdir\") {\n    request.mutable_mkdir()->mutable_id()->set_path(path);\n\n    if (recursive) {\n      request.mutable_mkdir()->set_recursive(true);\n    }\n\n    request.mutable_mkdir()->set_mode(mode);\n  } else if (cmd == \"rmdir\") {\n    request.mutable_rmdir()->mutable_id()->set_path(path);\n  } else if (cmd == \"touch\") {\n    request.mutable_touch()->mutable_id()->set_path(path);\n  } else if (cmd == \"unlink\") {\n    request.mutable_unlink()->mutable_id()->set_path(path);\n\n    if (norecycle) {\n      request.mutable_unlink()->set_norecycle(norecycle);\n    }\n  } else if (cmd == \"rm\") {\n    request.mutable_rm()->mutable_id()->set_path(path);\n\n    if (norecycle) {\n      request.mutable_rm()->set_norecycle(norecycle);\n    }\n\n    if (recursive) {\n      request.mutable_rm()->set_recursive(recursive);\n    }\n  } else if (cmd == \"rename\") {\n    request.mutable_rename()->mutable_id()->set_path(path);\n    request.mutable_rename()->set_target(target);\n  } else if (cmd == \"symlink\") {\n    request.mutable_symlink()->mutable_id()->set_path(path);\n    request.mutable_symlink()->set_target(target);\n  } else if (cmd == \"setxattr\") {\n    request.mutable_xattr()->set_recursive(recursive);\n    request.mutable_xattr()->mutable_id()->set_path(path);\n    std::string key, val;\n    eos::common::StringConversion::SplitKeyValue(xattr, key, val, \"=\");\n\n    if (key.front() == '!') {\n      // add as a deletion key\n      auto x = request.mutable_xattr()->add_keystodelete();\n      *x = key.substr(1);\n    } else {\n      // add as a new attribute key\n      (*(request.mutable_xattr()->mutable_xattrs()))[key] = val;\n    }\n  } else if (cmd == \"chown\") {\n    // run as root\n    request.mutable_chown()->mutable_id()->set_path(path);\n    request.mutable_chown()->mutable_owner()->set_uid(owner_uid);\n    request.mutable_chown()->mutable_owner()->set_gid(owner_gid);\n  } else if (cmd == \"chmod\") {\n    request.mutable_chmod()->mutable_id()->set_path(path);\n    request.mutable_chmod()->set_mode(mode);\n  } else if (cmd == \"create-version\") {\n    request.mutable_version()->set_cmd(eos::rpc::NSRequest::VersionRequest::CREATE);\n    request.mutable_version()->mutable_id()->set_path(path);\n    request.mutable_version()->set_maxversion(max_version);\n  } else if (cmd == \"list-version\") {\n    request.mutable_version()->set_cmd(eos::rpc::NSRequest::VersionRequest::LIST);\n    request.mutable_version()->mutable_id()->set_path(path);\n  } else if (cmd == \"purge-version\") {\n    request.mutable_version()->set_cmd(eos::rpc::NSRequest::VersionRequest::PURGE);\n    request.mutable_version()->mutable_id()->set_path(path);\n    request.mutable_version()->set_maxversion(max_version);\n  } else if (cmd == \"acl\") {\n    if (acl.empty()) {\n      // list acl\n      request.mutable_acl()->set_cmd(eos::rpc::NSRequest::AclRequest::LIST);\n    } else {\n      // modify acl\n      request.mutable_acl()->set_cmd(eos::rpc::NSRequest::AclRequest::MODIFY);\n      request.mutable_acl()->set_rule(acl);\n    }\n\n    request.mutable_acl()->mutable_id()->set_path(path);\n\n    if (recursive) {\n      request.mutable_acl()->set_recursive(true);\n    }\n\n    if (sysacl) {\n      request.mutable_acl()->set_type(eos::rpc::NSRequest::AclRequest::SYS_ACL);\n    } else {\n      request.mutable_acl()->set_type(eos::rpc::NSRequest::AclRequest::USER_ACL);\n    }\n\n    if (position) {\n      request.mutable_acl()->set_position(position);\n    }\n  } else if (cmd == \"token\") {\n    request.mutable_token()->mutable_token()->mutable_token()->set_expires(time(\n          NULL) + 300);\n\n    if (!path.empty()) {\n      request.mutable_token()->mutable_token()->mutable_token()->set_path(path);\n    }\n\n    if (recursive) {\n      request.mutable_token()->mutable_token()->mutable_token()->set_allowtree(true);\n    }\n\n    if (acl.empty()) {\n      request.mutable_token()->mutable_token()->mutable_token()->set_permission(\"rx\");\n    } else {\n      request.mutable_token()->mutable_token()->mutable_token()->set_permission(acl);\n    }\n\n    if (!eostoken.empty()) {\n      request.mutable_token()->mutable_token()->mutable_token()->set_vtoken(eostoken);\n    }\n  } else if (cmd == \"quota\") {\n    if (username.length()) {\n      request.mutable_quota()->mutable_id()->set_username(username);\n    }\n\n    if (groupname.length()) {\n      request.mutable_quota()->mutable_id()->set_groupname(groupname);\n    }\n\n    request.mutable_quota()->set_path(path);\n\n    if (subcmd == \"get\")  {\n      request.mutable_quota()->set_op(eos::rpc::GET);\n    }\n\n    if (subcmd == \"set\") {\n      request.mutable_quota()->set_op(eos::rpc::SET);\n      request.mutable_quota()->set_maxfiles(inodes);\n      request.mutable_quota()->set_maxbytes(volume);\n    }\n\n    if (subcmd == \"rm\") {\n      request.mutable_quota()->set_op(eos::rpc::RM);\n      request.mutable_quota()->set_entry(eos::rpc::NONE);\n\n      if (qtype == \"volume\") {\n        request.mutable_quota()->set_entry(eos::rpc::VOLUME);\n      }\n\n      if (qtype == \"inode\") {\n        request.mutable_quota()->set_entry(eos::rpc::INODE);\n      }\n    }\n\n    if (subcmd == \"rmnode\") {\n      request.mutable_quota()->set_op(eos::rpc::RMNODE);\n    }\n  } else if (cmd == \"old_recycle\") {\n    if ((subcmd == \"\")  ||\n        (subcmd == \"ls\")) {\n      request.mutable_old_recycle()->set_cmd(\n        eos::rpc::NSRequest::RecycleRequest::LIST);\n    } else if (subcmd == \"purge\") {\n      if (year) {\n        request.mutable_old_recycle()->mutable_purgedate()->set_year(year);\n      }\n\n      if (month) {\n        request.mutable_old_recycle()->mutable_purgedate()->set_month(month);\n      }\n\n      if (day) {\n        request.mutable_old_recycle()->mutable_purgedate()->set_day(day);\n      }\n\n      request.mutable_old_recycle()->set_key(path);\n      request.mutable_old_recycle()->set_cmd(\n        eos::rpc::NSRequest::RecycleRequest::PURGE);\n    } else if (subcmd == \"restore\") {\n      request.mutable_old_recycle()->set_cmd(\n        eos::rpc::NSRequest::RecycleRequest::RESTORE);\n      request.mutable_old_recycle()->set_key(path);\n    } else {\n      std::cerr << \"invalid recycle request\" << std::endl;\n      return EINVAL;\n    }\n  } else if (cmd == \"recycle\") {\n    if (ParseRecycleCommand(argc, argv, arg_index + 1, subcmd, path, request)) {\n      return EINVAL;\n    }\n  }\n\n  (void) google::protobuf::util::MessageToJsonString(request,\n      &jsonstring, options);\n  std::cout << \"request: \" << std::endl << jsonstring << std::endl;\n  int retc = EIO;\n\n  if (eosgrpc->Exec(request, reply)) {\n    std::cerr << \"grpc request failed\" << std::endl;\n  } else {\n    retc = reply.error().code();\n  }\n\n  jsonstring = \"\";\n  (void) google::protobuf::util::MessageToJsonString(reply,\n      &jsonstring, options);\n  std::cout << \"reply: \" << std::endl << jsonstring << std::endl;\n  std::chrono::microseconds elapsed_global =\n    std::chrono::duration_cast<std::chrono::microseconds>\n    (std::chrono::steady_clock::now() - watch_global);\n  std::cout << \"request took \" << elapsed_global.count() <<\n            \" micro seconds\" << std::endl;\n  return retc;\n}\n"
  },
  {
    "path": "client/grpc/NsStat.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <sstream>\n#include <iostream>\n#include <iomanip>\n#include <getopt.h>\n#include \"client/grpc/GrpcClient.hh\"\n\nint usage(const char* name)\n{\n  std::ostringstream oss;\n  oss << \"usage: \" << name\n      << \" [--key <ssl-key-file> --cert <ssl-cert-file> --ca <ca-cert-file>]\"\n      << \" [--token <auth-token>]\"\n      << std::endl << std::setw(strlen(name) + 8) << \"\"\n      << \"[--endpoint <host:port>] [-d|--debug] [-h|--help] [--force-ssl]\"\n      << std::endl;\n  std::cerr << oss.str();\n  return -1;\n}\n\nint main(int argc, char* argv[])\n{\n  using eos::client::GrpcClient;\n  std::string endpoint{\"localhost:50051\"};\n  std::string keyfile;\n  std::string certfile;\n  std::string cafile;\n  std::string token;\n  bool debug = false;\n  bool force_ssl = false;\n\n  while (true) {\n    static struct option long_options[] {\n      {\"key\",      required_argument, 0, 'k'},\n      {\"cert\",     required_argument, 0, 'c'},\n      {\"ca\",       required_argument, 0, 'a'},\n      {\"endpoint\", required_argument, 0, 'e'},\n      {\"token\",    required_argument, 0, 't'},\n      {\"debug\",    no_argument,       0, 'd'},\n      {\"help\",     no_argument,       0, 'h'},\n      {\"force-ssl\", no_argument,      0, 's'},\n      {0, 0,                          0, 0}\n    };\n    int option_index = 0;\n    int c = getopt_long(argc, argv, \"k:c:a:e:t:dhs\", long_options, &option_index);\n\n    // Detect end of the options\n    if (c == -1) {\n      break;\n    }\n\n    switch (c) {\n    case 'k':\n      keyfile = optarg;\n      break;\n\n    case 'c':\n      certfile = optarg;\n      break;\n\n    case 'a':\n      cafile = optarg;\n      break;\n\n    case 'e':\n      endpoint = optarg;\n      break;\n\n    case 't':\n      token = optarg;\n      break;\n\n    case 'd':\n      debug = true;\n      break;\n\n    case 's':\n      force_ssl = true;\n      break;\n\n    case 'h':\n      return usage(argv[0]);\n\n    default:\n      return usage(argv[0]);\n    }\n  }\n\n  // Make sure all elements are present if certificate authentication is used\n  if (keyfile.length() || certfile.length() || cafile.length()) {\n    if (!keyfile.length() || !certfile.length() || !cafile.length()) {\n      return usage(argv[0]);\n    }\n  }\n\n  std::unique_ptr<GrpcClient> eosgrpc =\n    GrpcClient::Create(endpoint, token, keyfile, certfile, cafile, force_ssl);\n\n  if (!eosgrpc) {\n    std::cerr << \"Failed to create grpc client object!\" << std::endl;\n    return -1;\n  }\n\n  auto start_time = std::chrono::steady_clock::now();\n  google::protobuf::util::JsonPrintOptions options;\n#if GOOGLE_PROTOBUF_VERSION >= 5027000\n  options.always_print_fields_with_no_presence = true;\n#else\n  options.always_print_primitive_fields = true;\n#endif\n  options.add_whitespace = true;\n  std::string jsonstring;\n  eos::rpc::NsStatRequest request;\n  eos::rpc::NsStatResponse reply;\n  request.set_authkey(token);\n\n  if (debug) {\n    (void) google::protobuf::util::MessageToJsonString(request, &jsonstring,\n        options);\n    std::cout << \"request: \" << std::endl << jsonstring << std::endl;\n  }\n\n  if (eosgrpc->NsStat(request, reply)) {\n    std::cerr << \"GRPC request field\" << std::endl;\n    debug = true;\n  }\n\n  if (debug) {\n    std::cout << \"reply: \" << std::endl;\n  }\n\n  jsonstring = \"\";\n  (void) google::protobuf::util::MessageToJsonString(reply, &jsonstring, options);\n  std::cout << jsonstring << std::endl;\n  auto elapsed_time = std::chrono::duration_cast<std::chrono::microseconds>(\n                        std::chrono::steady_clock::now() - start_time);\n\n  if (debug) {\n    std::cout << \"request took \" << elapsed_time.count() << \" microseconds\"\n              << std::endl;\n  }\n\n  return reply.code();\n}\n"
  },
  {
    "path": "client/grpc/Ping.cc",
    "content": "#include <string>\n#include <iostream>\n#include \"client/grpc/GrpcClient.hh\"\n#include <stdio.h>\n#include \"common/StringConversion.hh\"\n\nint usage(const char* prog)\n{\n  fprintf(stderr,\n          \"usage: %s [--size pingsize (max 4M)] \\n\"\n          \"         [--key <ssl-key-file>\\n\"\n          \"          --cert <ssl-cert-file>\\n\"\n          \"           --ca <ca-cert-file>]\\n\"\n          \"         [--endpoint <host:port>]\\n\"\n          \"         [--token <auth-token>]\\n\"\n          \"         [--force-ssl]\\n\", prog);\n  return -1;\n}\n\nint main(int argc, const char* argv[])\n{\n  std::string endpoint = \"localhost:50051\";\n  std::string token = \"\";\n  std::string key;\n  std::string cert;\n  std::string ca;\n  std::string keyfile;\n  std::string certfile;\n  std::string cafile;\n  bool force_ssl = false;\n  size_t ping_size = 0 ;\n\n  for (auto i = 1; i < argc; ++i) {\n    std::string option = argv[i];\n\n    if (option == \"--key\") {\n      if (argc > i + 1) {\n        keyfile = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--cert\") {\n      if (argc > i + 1) {\n        certfile = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--ca\") {\n      if (argc > i + 1) {\n        cafile = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--endpoint\") {\n      if (argc > i + 1) {\n        endpoint = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--token\") {\n      if (argc > i + 1) {\n        token = argv[i + 1];\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--size\") {\n      if (argc > i + 1) {\n        ping_size = std::strtoul(argv[i + 1], 0, 10);\n        ++i;\n        continue;\n      } else {\n        return usage(argv[0]);\n      }\n    }\n\n    if (option == \"--force-ssl\") {\n      force_ssl = true;\n      ++i;\n      continue;\n    }\n\n    return usage(argv[0]);\n  }\n\n  if (keyfile.length() || certfile.length() || cafile.length()) {\n    if (!keyfile.length() || !certfile.length() || !cafile.length()) {\n      return usage(argv[0]);\n    }\n  }\n\n  if (ping_size > (4 * 1000000)) {\n    return usage(argv[0]);\n  }\n\n  std::unique_ptr<eos::client::GrpcClient> eosgrpc =\n    eos::client::GrpcClient::Create\n    (endpoint, token, keyfile, certfile, cafile, force_ssl);\n\n  if (!eosgrpc) {\n    return usage(argv[0]);\n  }\n\n  std::string message(\"ping\");\n\n  if (ping_size) {\n    message.resize(ping_size);\n  }\n\n  std::chrono::steady_clock::time_point watch_global =\n    std::chrono::steady_clock::now();\n  int n_requests = 100;\n\n  for (auto i = 0; i < n_requests; ++i) {\n    std::chrono::steady_clock::time_point watch_local =\n      std::chrono::steady_clock::now();\n    std::string reply = eosgrpc->Ping(message);\n\n    if (reply != message) {\n      std::cout << \"request: failed/timeout\" << std::endl;\n    } else {\n      std::chrono::microseconds elapsed_local =\n        std::chrono::duration_cast<std::chrono::microseconds>\n        (std::chrono::steady_clock::now() - watch_local);\n      std::cout << \"request: \" << message.length() << \" reply: \" << reply.length() <<\n                \" timing: \" <<\n                elapsed_local.count() << \" micro seconds\" << std::endl;\n    }\n  }\n\n  std::chrono::microseconds elapsed_global =\n    std::chrono::duration_cast<std::chrono::microseconds>\n    (std::chrono::steady_clock::now() - watch_global);\n  std::cout << n_requests << \" requests took \" << elapsed_global.count() <<\n            \" micro seconds\" << std::endl;\n  return 0;\n}\n"
  },
  {
    "path": "cmake/CPUArchFlags.cmake",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Abhishek Lekshmanan <abhishek.lekshmanan@cern.ch>\n# ----------------------------------------------------------------------\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2021 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\ninclude(CheckCXXCompilerFlag)\n\nif (CMAKE_SYSTEM_PROCESSOR MATCHES \"x86_64|amd64\")\n  # Bypass everything if NO_SSE is set\n  set(AMD64_BUILD ON)\n\n  if (NO_SSE)\n    # Some old hardware does not have sse instructions support, allow switch-off.\n    message(NOTICE \"SSE extensions not enabled\")\n    CHECK_CXX_COMPILER_FLAG(-mcrc32 HAVE_CRC32)\n    if (HAVE_CRC32)\n      set(CPU_ARCH_FLAGS \"-mcrc32\")\n    endif() # HAVE_CRC32\n\n  else()\n    #find cpu features\n    CHECK_CXX_COMPILER_FLAG(-msse4.2 HAVE_SSE42)\n    if (HAVE_SSE42)\n      set(CPU_ARCH_FLAGS \"-msse4.2\")\n    endif() # HAVE_SSE42\n    CHECK_CXX_COMPILER_FLAG(-mavx512f HAVE_AVX512F)\n    CHECK_CXX_COMPILER_FLAG(-mavx512vl HAVE_AVX512L)\n    if(HAVE_AVX512F AND HAVE_AVX512L)\n      set(HAVE_AVX512 1)\n    endif()\n    CHECK_CXX_COMPILER_FLAG(-mavx2 HAVE_AVX2)\n  endif() # NO_SSE\n\nelseif (CMAKE_SYSTEM_PROCESSOR MATCHES \"arm64|aarch64\")\n  set(ARM64_BUILD ON)\n  CHECK_CXX_COMPILER_FLAG(-march=armv8-a+crc+crypto HAVE_ARMV8_CRC_CRYPTO)\n  CHECK_CXX_COMPILER_FLAG(-march=armv8-a+crc HAVE_ARMV8_CRC)\n\n  if (HAVE_ARMV8_CRC_CRYPTO)\n    set(CPU_ARCH_FLAGS \"-march=armv8-a+crc+crypto\")\n  elseif (HAVE_ARMV8_CRC)\n    set(CPU_ARCH_FLAGS \"-march=armv8-a+crc\")\n  endif() # CRC/CRYPTO\n  CHECK_CXX_COMPILER_FLAG(-mfpu=neon HAVE_ARM_NEON)\n\nelse()\n  message(WARNING \"Could not determine platform. No cpu accel. will be used \")\nendif() # SYSTEM_PROCESSOR\n\nadd_compile_options(${CPU_ARCH_FLAGS})\nadd_link_options(${CPU_ARCH_FLAGS})\n"
  },
  {
    "path": "cmake/DownloadProject.CMakeLists.cmake.in",
    "content": "# Distributed under the OSI-approved MIT License.  See accompanying\n# file LICENSE or https://github.com/Crascit/DownloadProject for details.\n\ncmake_minimum_required(VERSION 3.16...3.30 FATAL_ERROR)\n\nproject(${DL_ARGS_PROJ}-download NONE)\n\ninclude(ExternalProject)\nExternalProject_Add(${DL_ARGS_PROJ}-download\n                    ${DL_ARGS_UNPARSED_ARGUMENTS}\n                    SOURCE_DIR          \"${DL_ARGS_SOURCE_DIR}\"\n                    BINARY_DIR          \"${DL_ARGS_BINARY_DIR}\"\n                    CONFIGURE_COMMAND   \"\"\n                    BUILD_COMMAND       \"\"\n                    INSTALL_COMMAND     \"\"\n                    TEST_COMMAND        \"\"\n)\n"
  },
  {
    "path": "cmake/DownloadProject.cmake",
    "content": "# Distributed under the OSI-approved MIT License.  See accompanying\n# file LICENSE or https://github.com/Crascit/DownloadProject for details.\n#\n# MODULE:   DownloadProject\n#\n# PROVIDES:\n#   download_project( PROJ projectName\n#                    [PREFIX prefixDir]\n#                    [DOWNLOAD_DIR downloadDir]\n#                    [SOURCE_DIR srcDir]\n#                    [BINARY_DIR binDir]\n#                    [QUIET]\n#                    ...\n#   )\n#\n#       Provides the ability to download and unpack a tarball, zip file, git repository,\n#       etc. at configure time (i.e. when the cmake command is run). How the downloaded\n#       and unpacked contents are used is up to the caller, but the motivating case is\n#       to download source code which can then be included directly in the build with\n#       add_subdirectory() after the call to download_project(). Source and build\n#       directories are set up with this in mind.\n#\n#       The PROJ argument is required. The projectName value will be used to construct\n#       the following variables upon exit (obviously replace projectName with its actual\n#       value):\n#\n#           projectName_SOURCE_DIR\n#           projectName_BINARY_DIR\n#\n#       The SOURCE_DIR and BINARY_DIR arguments are optional and would not typically\n#       need to be provided. They can be specified if you want the downloaded source\n#       and build directories to be located in a specific place. The contents of\n#       projectName_SOURCE_DIR and projectName_BINARY_DIR will be populated with the\n#       locations used whether you provide SOURCE_DIR/BINARY_DIR or not.\n#\n#       The DOWNLOAD_DIR argument does not normally need to be set. It controls the\n#       location of the temporary CMake build used to perform the download.\n#\n#       The PREFIX argument can be provided to change the base location of the default\n#       values of DOWNLOAD_DIR, SOURCE_DIR and BINARY_DIR. If all of those three arguments\n#       are provided, then PREFIX will have no effect. The default value for PREFIX is\n#       CMAKE_BINARY_DIR.\n#\n#       The QUIET option can be given if you do not want to show the output associated\n#       with downloading the specified project.\n#\n#       In addition to the above, any other options are passed through unmodified to\n#       ExternalProject_Add() to perform the actual download, patch and update steps.\n#       The following ExternalProject_Add() options are explicitly prohibited (they\n#       are reserved for use by the download_project() command):\n#\n#           CONFIGURE_COMMAND\n#           BUILD_COMMAND\n#           INSTALL_COMMAND\n#           TEST_COMMAND\n#\n#       Only those ExternalProject_Add() arguments which relate to downloading, patching\n#       and updating of the project sources are intended to be used. Also note that at\n#       least one set of download-related arguments are required.\n#\n#       If using CMake 3.2 or later, the UPDATE_DISCONNECTED option can be used to\n#       prevent a check at the remote end for changes every time CMake is run\n#       after the first successful download. See the documentation of the ExternalProject\n#       module for more information. It is likely you will want to use this option if it\n#       is available to you. Note, however, that the ExternalProject implementation contains\n#       bugs which result in incorrect handling of the UPDATE_DISCONNECTED option when\n#       using the URL download method or when specifying a SOURCE_DIR with no download\n#       method. Fixes for these have been created, the last of which is scheduled for\n#       inclusion in CMake 3.8.0. Details can be found here:\n#\n#           https://gitlab.kitware.com/cmake/cmake/commit/bdca68388bd57f8302d3c1d83d691034b7ffa70c\n#           https://gitlab.kitware.com/cmake/cmake/issues/16428\n#\n#       If you experience build errors related to the update step, consider avoiding\n#       the use of UPDATE_DISCONNECTED.\n#\n# EXAMPLE USAGE:\n#\n#   include(DownloadProject)\n#   download_project(PROJ                googletest\n#                    GIT_REPOSITORY      https://github.com/google/googletest.git\n#                    GIT_TAG             master\n#                    UPDATE_DISCONNECTED 1\n#                    QUIET\n#   )\n#\n#   add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR})\n#\n#========================================================================================\n\n\nset(_DownloadProjectDir \"${CMAKE_CURRENT_LIST_DIR}\")\n\ninclude(CMakeParseArguments)\n\nfunction(download_project)\n\n    set(options QUIET)\n    set(oneValueArgs\n        PROJ\n        PREFIX\n        DOWNLOAD_DIR\n        SOURCE_DIR\n        BINARY_DIR\n        # Prevent the following from being passed through\n        CONFIGURE_COMMAND\n        BUILD_COMMAND\n        INSTALL_COMMAND\n        TEST_COMMAND\n    )\n    set(multiValueArgs \"\")\n\n    cmake_parse_arguments(DL_ARGS \"${options}\" \"${oneValueArgs}\" \"${multiValueArgs}\" ${ARGN})\n\n    # Hide output if requested\n    if (DL_ARGS_QUIET)\n        set(OUTPUT_QUIET \"OUTPUT_QUIET\")\n    else()\n        unset(OUTPUT_QUIET)\n        message(STATUS \"Downloading/updating ${DL_ARGS_PROJ}\")\n    endif()\n\n    # Set up where we will put our temporary CMakeLists.txt file and also\n    # the base point below which the default source and binary dirs will be.\n    # The prefix must always be an absolute path.\n    if (NOT DL_ARGS_PREFIX)\n        set(DL_ARGS_PREFIX \"${CMAKE_BINARY_DIR}\")\n    else()\n        get_filename_component(DL_ARGS_PREFIX \"${DL_ARGS_PREFIX}\" ABSOLUTE\n                               BASE_DIR \"${CMAKE_CURRENT_BINARY_DIR}\")\n    endif()\n    if (NOT DL_ARGS_DOWNLOAD_DIR)\n        set(DL_ARGS_DOWNLOAD_DIR \"${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-download\")\n    endif()\n\n    # Ensure the caller can know where to find the source and build directories\n    if (NOT DL_ARGS_SOURCE_DIR)\n        set(DL_ARGS_SOURCE_DIR \"${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-src\")\n    endif()\n    if (NOT DL_ARGS_BINARY_DIR)\n        set(DL_ARGS_BINARY_DIR \"${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-build\")\n    endif()\n    set(${DL_ARGS_PROJ}_SOURCE_DIR \"${DL_ARGS_SOURCE_DIR}\" PARENT_SCOPE)\n    set(${DL_ARGS_PROJ}_BINARY_DIR \"${DL_ARGS_BINARY_DIR}\" PARENT_SCOPE)\n\n    # The way that CLion manages multiple configurations, it causes a copy of\n    # the CMakeCache.txt to be copied across due to it not expecting there to\n    # be a project within a project.  This causes the hard-coded paths in the\n    # cache to be copied and builds to fail.  To mitigate this, we simply\n    # remove the cache if it exists before we configure the new project.  It\n    # is safe to do so because it will be re-generated.  Since this is only\n    # executed at the configure step, it should not cause additional builds or\n    # downloads.\n    file(REMOVE \"${DL_ARGS_DOWNLOAD_DIR}/CMakeCache.txt\")\n\n    # Create and build a separate CMake project to carry out the download.\n    # If we've already previously done these steps, they will not cause\n    # anything to be updated, so extra rebuilds of the project won't occur.\n    # Make sure to pass through CMAKE_MAKE_PROGRAM in case the main project\n    # has this set to something not findable on the PATH.\n    configure_file(\"${_DownloadProjectDir}/DownloadProject.CMakeLists.cmake.in\"\n                   \"${DL_ARGS_DOWNLOAD_DIR}/CMakeLists.txt\")\n    execute_process(COMMAND ${CMAKE_COMMAND} -G \"${CMAKE_GENERATOR}\"\n                        -D \"CMAKE_MAKE_PROGRAM:FILE=${CMAKE_MAKE_PROGRAM}\"\n                        .\n                    RESULT_VARIABLE result\n                    ${OUTPUT_QUIET}\n                    WORKING_DIRECTORY \"${DL_ARGS_DOWNLOAD_DIR}\"\n    )\n    if(result)\n        message(FATAL_ERROR \"CMake step for ${DL_ARGS_PROJ} failed: ${result}\")\n    endif()\n    execute_process(COMMAND ${CMAKE_COMMAND} --build .\n                    RESULT_VARIABLE result\n                    ${OUTPUT_QUIET}\n                    WORKING_DIRECTORY \"${DL_ARGS_DOWNLOAD_DIR}\"\n    )\n    if(result)\n        message(FATAL_ERROR \"Build step for ${DL_ARGS_PROJ} failed: ${result}\")\n    endif()\n\nendfunction()\n"
  },
  {
    "path": "cmake/EosCompileFlags.cmake",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Mihai Patrascoiu <mihai.patrascoiu@cern.ch>\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2019 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n#-------------------------------------------------------------------------------\n# Require C++17\n#-------------------------------------------------------------------------------\n\nset(CMAKE_CXX_STANDARD 17 CACHE STRING \"C++ Standard\")\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\nset(CMAKE_CXX_EXTENSIONS FALSE)\n\n# Avoid having CMAKE treat include directories on imported libraries as systems\n# includes. In newer gcc versions the systems includes are added using the\n# \"-isystem\" flag instead of \"-I\". This currently breaks the build on Fedora 36\n# and 37.\nset(CMAKE_NO_SYSTEM_FROM_IMPORTED TRUE)\n\nadd_compile_definitions(EOSCITRINE VERSION=\"${VERSION}\" RELEASE=\"${RELEASE}\")\n\n#-------------------------------------------------------------------------------\n# Compile Options\n#-------------------------------------------------------------------------------\n\nadd_compile_options(-Wall\n  # Keeping this in for OpenSSL the new EVP API is still slower the the old\n  # one so there no point in changing it for the moment\n  # https://github.com/openssl/openssl/issues/25858\n  -Wno-deprecated-declarations\n  -Werror=return-type\n)\n\n#-------------------------------------------------------------------------------\n# CPU architecture flags\n#-------------------------------------------------------------------------------\n\ninclude(CPUArchFlags)\n\n#-------------------------------------------------------------------------------\n# Client-only flags\n#-------------------------------------------------------------------------------\n\nif (CLIENT)\n  add_compile_definitions(CLIENT_ONLY=1)\nendif ()\n\n#-------------------------------------------------------------------------------\n# Compiler specific flags\n#-------------------------------------------------------------------------------\n\nif (NOT MacOSX)\nif (CMAKE_CXX_COMPILER_ID MATCHES \"Clang\")\n  # Clang requires linking with libatomic\n  find_package(Atomic REQUIRED)\n  link_libraries(${ATOMIC_LIBRARIES})\n\n  add_compile_options(\n    -Wno-bitwise-instead-of-logical\n    -Wno-constant-conversion\n    -Wno-dangling-gsl\n    -Wno-deprecated-copy-with-user-provided-copy\n    -Wno-header-guard\n    -Wno-implicit-const-int-float-conversion\n    -Wno-implicit-int-float-conversion\n    -Wno-mismatched-tags\n    -Wno-missing-braces\n    -Wno-pessimizing-move\n    -Wno-uninitialized-const-reference\n    -Wno-unknown-warning-option\n    -Wno-unused-but-set-variable\n    -Wno-unused-lambda-capture\n    -Wno-unused-private-field\n    $<$<COMPILE_LANGUAGE:CXX>:-Wno-delete-non-abstract-non-virtual-dtor>\n    $<$<COMPILE_LANGUAGE:CXX>:-Wno-inconsistent-missing-override>\n    $<$<COMPILE_LANGUAGE:CXX>:-Wno-unqualified-std-cast-call>\n    -Werror=return-type\n    -fclang-abi-compat=17\n  )\nendif()\nendif()\n\n#-------------------------------------------------------------------------------\n# Sanitizer flags\n#-------------------------------------------------------------------------------\n\ninclude(CheckCXXCompilerFlag)\n\nfunction(eos_enable_sanitizer sanitizer var)\n  set(FLAG -fsanitize=${sanitizer})\n  list(APPEND CMAKE_REQUIRED_FLAGS \"${FLAG}\")\n  list(APPEND CMAKE_REQUIRED_LINK_OPTIONS \"${FLAG}\")\n  check_cxx_compiler_flag(\"${FLAG}\" \"${var}\")\n  if (${${var}})\n    # Set required flags in parent scope, as some sanitizers can't be used together\n    set(CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS} PARENT_SCOPE)\n    set(CMAKE_REQUIRED_LINK_OPTIONS ${CMAKE_REQUIRED_LINK_OPTIONS} PARENT_SCOPE)\n    add_compile_options(${FLAG} -fno-omit-frame-pointer)\n\n    add_link_options(${FLAG})\n  else()\n    message(FATAL_ERROR \"Could not enable flag '${FLAG}'.\\n\"\n      \"Configure with --trace-expand to debug.\")\n  endif()\nendfunction()\n\nif(ASAN)\n  eos_enable_sanitizer(address ASAN_SUPPORTED)\nendif()\n\nif(TSAN)\n  eos_enable_sanitizer(thread TSAN_SUPPORTED)\nendif()\n"
  },
  {
    "path": "cmake/EosCoverage.cmake",
    "content": "# ----------------------------------------------------------------------\n# File: EosCoverage.cmake\n# Author: Mihai Patrascoiu - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2019 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n#-------------------------------------------------------------------------------\n# Code coverage compiler flags and definitions\n#-------------------------------------------------------------------------------\n\ninclude(CheckCXXCompilerFlag)\n\nlist(APPEND CMAKE_REQUIRED_FLAGS --coverage)\nlist(APPEND CMAKE_REQUIRED_LINK_OPTIONS --coverage)\ncheck_cxx_compiler_flag(--coverage COVERAGE_SUPPORTED)\n\nif (COVERAGE_SUPPORTED)\n  add_compile_definitions(COVERAGE_BUILD)\n  add_compile_options(--coverage)\n  add_link_options(--coverage)\nelse()\n  message(FATAL_ERROR \"Could not enable coverage. A compiler with '--coverage' support is required.\")\nendif()\n\n# This is needed for correct results in multithreaded applications\nlist(APPEND CMAKE_REQUIRED_FLAGS -fprofile-update=atomic)\ncheck_cxx_compiler_flag(-fprofile-update=atomic COVERAGE_UPDATE_ATOMIC)\n\nif (COVERAGE_UPDATE_ATOMIC)\n  add_compile_options(-fprofile-update=atomic)\nelse()\n  message(WARNING \"Could not enable atomic coverage updates, expect unreliable results.\")\nendif()\n\n#-------------------------------------------------------------------------------\n# Code coverage targets\n#-------------------------------------------------------------------------------\n\nadd_custom_target(\n  raw-code-trace\n  COMMAND lcov --capture\n    --base-directory ${CMAKE_SOURCE_DIR}\n    --directory ${CMAKE_BINARY_DIR}\n    --no-external\n    --config-file ${CMAKE_SOURCE_DIR}/coverage/eoslcov.rc\n    --output-file ${CMAKE_BINARY_DIR}/raw-trace.info\n)\n\nadd_custom_target(\n  filtered-trace-server\n  COMMAND lcov --remove ${CMAKE_BINARY_DIR}/raw-trace.info\n      \"${CMAKE_BINARY_DIR}/\\\\*\"\n      \"${CMAKE_SOURCE_DIR}/common/backward-cpp/\\\\*\"\n      \"${CMAKE_SOURCE_DIR}/common/crc32c/\\\\*\"\n      \"${CMAKE_SOURCE_DIR}/common/eos_cta_pb/\\\\*\"\n      \"${CMAKE_SOURCE_DIR}/common/fmt/\\\\*\"\n      \"${CMAKE_SOURCE_DIR}/common/xrootd-ssi-protobuf-interface/\\\\*\"\n      \"${CMAKE_SOURCE_DIR}/console/\\\\*\"\n      \"${CMAKE_SOURCE_DIR}/fst/tests/\\\\*\"\n      \"${CMAKE_SOURCE_DIR}/namespace/ns_quarkdb/\\\\*\"\n      \"${CMAKE_SOURCE_DIR}/test/\\\\*\"\n      \"${CMAKE_SOURCE_DIR}/unit_tests/\\\\*\"\n    --config-file ${CMAKE_SOURCE_DIR}/coverage/eoslcov.rc\n    --output-file ${CMAKE_BINARY_DIR}/filtered-trace-server.info\n  DEPENDS raw-code-trace\n)\n\nadd_custom_target(\n  filtered-trace-client\n  COMMAND lcov --extract ${CMAKE_BINARY_DIR}/raw-trace.info\n      \"${CMAKE_SOURCE_DIR}/console/\\\\*\"\n    --config-file ${CMAKE_SOURCE_DIR}/coverage/eoslcov.rc\n    --output-file ${CMAKE_BINARY_DIR}/filtered-trace-client.info\n  DEPENDS raw-code-trace\n)\n\nadd_custom_target(\n  coverage-server\n  COMMAND genhtml ${CMAKE_BINARY_DIR}/filtered-trace-server.info\n    --config-file ${CMAKE_SOURCE_DIR}/coverage/eoslcov.rc\n    --output-directory ${CMAKE_BINARY_DIR}/coverage-report/server\n  DEPENDS filtered-trace-server\n)\n\nadd_custom_target(\n  coverage-client\n  COMMAND genhtml ${CMAKE_BINARY_DIR}/filtered-trace-client.info\n    --config-file ${CMAKE_SOURCE_DIR}/coverage/eoslcov.rc\n    --output-directory ${CMAKE_BINARY_DIR}/coverage-report/client\n  DEPENDS filtered-trace-client\n)\n\nadd_custom_target(\n  coverage-report\n  DEPENDS coverage-server coverage-client\n)\n\nif (COV_CROSS_PROFILE)\n  install(\n    DIRECTORY ${CMAKE_BINARY_DIR}\n    DESTINATION \"/var/eos/coverage\"\n    FILES_MATCHING PATTERN \"*.gcno\"\n    PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ\n  )\nendif()\n\n"
  },
  {
    "path": "cmake/EosFindLibs.cmake",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n#-------------------------------------------------------------------------------\n# Search for dependencies\n#-------------------------------------------------------------------------------\noption(PACKAGEONLY \"Build without dependencies\" OFF)\noption(CLIENT \"Build only client packages\" OFF)\noption(BUILD_XRDCL_RAIN_PLUGIN \"Enable XrdCl RAIN plugin\" OFF)\noption(BUILD_CSS_PLUGIN \"Enable CSS plugin\" ON)\n\n# Find default versions of Python with higher priority\nset(Python_FIND_UNVERSIONED_NAMES  \"FIRST\" CACHE STRING \"\")\nset(Python3_FIND_UNVERSIONED_NAMES \"FIRST\" CACHE STRING \"\")\n\nif(NOT PACKAGEONLY)\n  set(THREADS_PREFER_PTHREAD_FLAG TRUE)\n  find_package(Threads REQUIRED)\n  find_package(PythonSitePkg REQUIRED)\n  find_package(CURL REQUIRED)\n  find_package(XRootD REQUIRED)\n  find_package(fuse REQUIRED)\n  find_package(Threads REQUIRED)\n  find_package(ZLIB REQUIRED)\n  find_package(readline REQUIRED)\n  find_package(uuid REQUIRED)\n  find_package(OpenSSL REQUIRED)\n  find_package(ncurses REQUIRED)\n  find_package(ZMQ REQUIRED)\n  find_package(krb5 REQUIRED)\n  find_package(SparseHash REQUIRED)\n  find_package(jsoncpp REQUIRED)\n  find_package(Libevent REQUIRED)\n  find_package(fmt REQUIRED)\n  find_package(bz2 REQUIRED)\n  find_package(absl REQUIRED)\n  find_package(RocksDB REQUIRED)\n  find_package(jemalloc)\n  find_package(EosGrpcGateway)\n  find_package(Sphinx)\n  find_package(fuse3)\n  find_package(isal_crypto)\n  find_package(isal)\n  find_package(xxhash)\n  find_package(libbfd)\n  find_package(davix)\n  find_package(nfs)\n  find_package(Scitokens)\n  find_package(GRPC REQUIRED)\n  find_package(Protobuf3 REQUIRED)\n\n  if (GRPC_FOUND AND XROOTD_FOUND)\n    # Library paths for Protobuf, grpc and xrootd needs to be added to the\n    # RPATH of the libraries and binaries built since they are not installed\n    # in the usual system location.\n    set(CMAKE_SKIP_RPATH FALSE)\n    set(CMAKE_SKIP_BUILD_RPATH FALSE)\n    # TODO: To be removed in the future when CMAKE properly handles RPATH.\n    # Currently without this option the koji builds fail with error:\n    # file RPATH_CHANGE could not write new RPATH\n    set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)\n    set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)\n    get_filename_component(EOS_XROOTD_RPATH ${XROOTD_UTILS_LIBRARY} DIRECTORY)\n    get_filename_component(EOS_GRPC_RPATH ${GRPC_GRPC++_LIBRARY} DIRECTORY)\n    list(APPEND CMAKE_INSTALL_RPATH \"${EOS_GRPC_RPATH};${EOS_XROOTD_RPATH}\")\n    message(STATUS \"Info CMAKE_INSTALL_RPATH=${CMAKE_INSTALL_RPATH}\")\n  else()\n    message(FATAL_ERROR \"One of the mandatory dependecies: GPRC(Protobuf) or XRootD not found\")\n  endif()\n\n  if (Linux)\n    find_package(help2man)\n    find_package(glibc REQUIRED)\n    find_package(xfs REQUIRED)\n    find_package(procps)\n    find_package(libproc2)\n\n    if(NOT (PROCPS_FOUND OR LIBPROC2_FOUND))\n      message(FATAL_ERROR \"Could not find either procps 3.x or libproc2 (procps 4.x). \"\n              \"At least one of them is required.\")\n    endif()\n  else ()\n    # Add dummy targets for APPLE to simplify the cmake file using these targets\n    add_library(GLIBC::DL    INTERFACE IMPORTED)\n    add_library(GLIBC::RT    INTERFACE IMPORTED)\n    add_library(GLIBC::M     INTERFACE IMPORTED)\n  endif()\n\n  # The server build also requires\n  if (NOT CLIENT)\n    find_package(eosfolly REQUIRED)\n    find_package(ldap REQUIRED)\n    find_package(ActiveMQCPP REQUIRED)\n  endif()\nelse()\n  message(STATUS \"Running CMake in package only mode.\")\n  # Fake function for building the SRPMS in build system\n  function(PROTOBUF_GENERATE_CPP SRCS HDRS)\n    # This is just a hack to be able to run cmake >= 3.11 with -DPACKAGEONLY\n    # enabled. Otherwise the protobuf libraries built using add_library will\n    # complain as they have no SOURCE files.\n    set(${SRCS} \"${CMAKE_SOURCE_DIR}/common/Logging.cc\" PARENT_SCOPE)\n    set(${HDRS} \"${CMAKE_SOURCE_DIR}/common/Logging.hh\" PARENT_SCOPE)\n    return()\n  endfunction()\n\n  function(GRPC_GENERATE_CPP SRCS HDRS)\n    # This is just a hack to be able to run cmake >= 3.11 with -DPACKAGEONLY\n    # enabled. Otherwise the protobuf libraries built using add_library will\n    # complain as they have no SOURCE files.\n    set(${SRCS} \"${CMAKE_SOURCE_DIR}/common/Logging.cc\" PARENT_SCOPE)\n    set(${HDRS} \"${CMAKE_SOURCE_DIR}/common/Logging.hh\" PARENT_SCOPE)\n    return()\n  endfunction()\n\n  # Fake targets\n  add_library(ZLIB::ZLIB                   INTERFACE IMPORTED)\n  add_library(UUID::UUID                   INTERFACE IMPORTED)\n  add_library(PROCPS::PROCPS               INTERFACE IMPORTED)\n  add_library(XROOTD::SERVER               INTERFACE IMPORTED)\n  add_library(XROOTD::CL                   INTERFACE IMPORTED)\n  add_library(XROOTD::SSI                  INTERFACE IMPORTED)\n  add_library(XROOTD::HTTP                 INTERFACE IMPORTED)\n  add_library(XROOTD::UTILS                INTERFACE IMPORTED)\n  add_library(XROOTD::POSIX                INTERFACE IMPORTED)\n  add_library(XROOTD::PRIVATE              INTERFACE IMPORTED)\n  add_library(PROTOBUF::PROTOBUF           INTERFACE IMPORTED)\n  add_library(NCURSES::NCURSES             INTERFACE IMPORTED)\n  add_library(NCURSES::NCURSES_STATIC      INTERFACE IMPORTED)\n  add_library(READLINE::READLINE           INTERFACE IMPORTED)\n  add_library(JSONCPP::JSONCPP             INTERFACE IMPORTED)\n  add_library(FOLLY::FOLLY                 INTERFACE IMPORTED)\n  add_library(ZMQ::ZMQ                     INTERFACE IMPORTED)\n  add_library(KRB5::KRB5                   INTERFACE IMPORTED)\n  add_library(OpenSSL::SSL                 INTERFACE IMPORTED)\n  add_library(OpenSSL::Crypto              INTERFACE IMPORTED)\n  add_library(LDAP::LDAP                   INTERFACE IMPORTED)\n  add_library(GRPC::grpc                   INTERFACE IMPORTED)\n  add_library(GRPC::grpc++                 INTERFACE IMPORTED)\n  add_library(GRPC::grpc++_reflection      INTERFACE IMPORTED)\n  add_library(CURL::libcurl                INTERFACE IMPORTED)\n  add_library(ATOMIC::ATOMIC               INTERFACE IMPORTED)\n  add_library(LIBEVENT::LIBEVENT           INTERFACE IMPORTED)\n  add_library(FUSE::FUSE                   INTERFACE IMPORTED)\n  add_library(FUSE3::FUSE3                 INTERFACE IMPORTED)\n  add_library(GLIBC::DL                    INTERFACE IMPORTED)\n  add_library(GLIBC::RT                    INTERFACE IMPORTED)\n  add_library(GLIBC::M                     INTERFACE IMPORTED)\n  add_library(LIBBFD::LIBBFD               INTERFACE IMPORTED)\n  add_library(LIBBFD::IBERTY               INTERFACE IMPORTED)\n  add_library(RICHACL::RICHACL             INTERFACE IMPORTED)\n  add_library(DAVIX::DAVIX                 INTERFACE IMPORTED)\n  add_library(NFS::NFS                     INTERFACE IMPORTED)\n  add_library(ROCKSDB::ROCKSDB             INTERFACE IMPORTED)\n  add_library(SCITOKENS::SCITOKENS         INTERFACE IMPORTED)\n  add_library(ABSL::ABSL                   INTERFACE IMPORTED)\n  add_library(BZ2::BZ2                     INTERFACE IMPORTED)\n  add_library(ZSTD::ZSTD                   INTERFACE IMPORTED)\n  add_library(LZ4::LZ4                     INTERFACE IMPORTED)\n  add_library(Snappy::snappy               INTERFACE IMPORTED)\n  add_library(XFS::XFS                     INTERFACE IMPORTED)\n  add_library(GOOGLE::SPARSEHASH           INTERFACE IMPORTED)\n  add_library(ISAL::ISAL                   INTERFACE IMPORTED)\n  add_library(ISAL::ISAL_CRYPTO            INTERFACE IMPORTED)\n  add_library(XXHASH::XXHASH               INTERFACE IMPORTED)\n  add_library(JEMALLOC::JEMALLOC           INTERFACE IMPORTED)\n  add_library(EosGrpcGateway::EosGrpcGateway INTERFACE IMPORTED)\n  add_library(fmt::fmt-header-only         INTERFACE IMPORTED)\n  add_library(ActiveMQCPP::ActiveMQCPP     INTERFACE IMPORTED)\nendif()\n"
  },
  {
    "path": "cmake/EosGraphviz.cmake",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2019 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n#-------------------------------------------------------------------------------\n# Search for dependencies\n#-------------------------------------------------------------------------------\nfind_program(DOT_EXE \"dot\")\n\nif(DOT_EXE)\n    message(STATUS \"dot found: ${DOT_EXE}\")\nelse()\n    message(STATUS \"dot not found!\")\nendif()\n\nset(DOT_OUTPUT_TYPE \"pdf\" CACHE STRING \"Build a dependency graph. Options are dot output types: ps, png, pdf...\" )\n\nif(DOT_EXE)\n  add_custom_target(dependency-graph\n    COMMAND ${CMAKE_COMMAND} ${CMAKE_SOURCE_DIR} --graphviz=${CMAKE_BINARY_DIR}/graphviz/${PROJECT_NAME}.dot\n    COMMAND ${DOT_EXE} -T${DOT_OUTPUT_TYPE} ${CMAKE_BINARY_DIR}/graphviz/${PROJECT_NAME}.dot -o ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.${DOT_OUTPUT_TYPE}\n    COMMENT \"Dependency graph generated and located at ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.${DOT_OUTPUT_TYPE}\")\nendif()\n"
  },
  {
    "path": "cmake/EosOSDefaults.cmake",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Elvin-Alin Sindrailru <esindril@cern.ch> CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2016 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n#-------------------------------------------------------------------------------\n# Detect the operating system and define variables\n#-------------------------------------------------------------------------------\n# Nothing detected yet\nset(Linux FALSE )\nset(MacOSX FALSE )\nset(Windows FALSE )\nset(OSDEFINE \"\")\n\n# Check if we are on Linux\nif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"Linux\")\n  include(GNUInstallDirs)\n  set(Linux TRUE )\n  set(OSDEFINE \"-D__LINUX__=1\")\nendif()\n\n# Check if we are on MacOSX\nif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"Darwin\" )\n  include(GNUInstallDirs)\n  set(MacOSX TRUE )\n  set(CLIENT TRUE )\n  set(OSDEFINE \"-D__APPLE__=1\")\n  # On MAC we don't link static objects at all\n  set(FUSE_LIBRARY /usr/local/lib/libosxfuse_i64.dylib)\n  set(CMAKE_MACOSX_RPATH TRUE)\n  set(CMAKE_SKIP_BUILD_RPATH FALSE)\n  set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)\n  list(APPEND CMAKE_INSTALL_RPATH \"${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}\")\n  set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)\n  list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES \"${CMAKE_INSTALL_PREFIX}/lib\" isSystemDir)\n\n  if(\"${isSystemDir}\" STREQUAL \"-1\")\n    list(APPEND CMAKE_INSTALL_RPATH \"${CMAKE_INSTALL_PREFIX}/lib\")\n  endif()\nendif()\n\n# Check if we are on Windows\nif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"Windows\")\n  set(Windows TRUE )\n  set(OSDEFINE \"-D__WINDOWS__=1\")\nendif()\n\nadd_compile_definitions(${OSDEFINE})\n"
  },
  {
    "path": "cmake/EosSummary.cmake",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n#-------------------------------------------------------------------------------\n# Print Configuration\n#-------------------------------------------------------------------------------\nmessage( STATUS \"_________________________________________________\" )\nmessage( STATUS \"Version       : eos-\" ${VERSION} \"-\" ${RELEASE} )\nif (CLIENT)\n  message( STATUS \"Modules       : client\" )\nelse ()\n  message( STATUS \"Modules       : client + server\" )\nendif ()\n\nset(FUSE_FLOCK_STATUS \"FUSE_NO_FLOCK_SUPPORT\")\n# We pass -DFUSE_MOUNT_VERSION0 hence the pragma for 290 in llfusexx\nif (FUSE3_FOUND OR\n    (\"${FUSE_MOUNT_VERSION}\" STREQUAL \"29\"))\n  set(FUSE_FLOCK_STATUS \"FUSE_SUPPORTS_FLOCK\")\nendif()\n\nmessage(STATUS \".................................................\" )\nmessage(STATUS \"prefix        : \" ${CMAKE_INSTALL_PREFIX} )\nmessage(STATUS \"bin dir       : \" ${CMAKE_INSTALL_FULL_BINDIR} )\nmessage(STATUS \"sbin dir      : \" ${CMAKE_INSTALL_SBINDIR} )\nmessage(STATUS \"lib dir       : \" ${CMAKE_INSTALL_FULL_LIBDIR} )\nmessage(STATUS \"sysconfig dir : \" ${CMAKE_INSTALL_SYSCONFDIR} )\nmessage(STATUS \".................................................\" )\nmessage(STATUS \"fuse2-build   : ${FUSE_FOUND}\")\nmessage(STATUS \"fuse3-build   : ${FUSE3_FOUND}\")\nmessage(STATUS \"fuse-mount-ver: ${FUSE_MOUNT_VERSION}\")\nmessage(STATUS \"fuse-flock    : ${FUSE_FLOCK_STATUS}\")\nmessage(STATUS \"grpc-build    : ${GRPC_FOUND}\")\nmessage(STATUS \"isa-l_crypto  : ${ISAL_CRYPTO_FOUND}\")\nmessage(STATUS \"isa-l         : ${ISAL_FOUND}\")\nmessage(STATUS \"xxhash        : ${XXHASH_FOUND}\")\nmessage(STATUS \"davix         : ${DAVIX_FOUND}\")\nmessage( STATUS \".................................................\" )\nmessage( STATUS \"C Compiler    : \" ${CMAKE_C_COMPILER} )\nmessage( STATUS \"C++ Compiler  : \" ${CMAKE_CXX_COMPILER} )\nmessage( STATUS \"Protobuf      : EXE \" ${PROTOBUF3_PROTOC_EXECUTABLE} \" INC \" ${PROTOBUF3_INCLUDE_DIR} \" LIB \" ${PROTOBUF3_LIBRARY} )\nmessage( STATUS \"Build type    : \" ${CMAKE_BUILD_TYPE} )\nmessage( STATUS \"Code coverage : ${COVERAGE}\")\nmessage( STATUS \"_________________________________________________\" )\n\nunset(FUSE_FLOCK_STATUS)\n"
  },
  {
    "path": "cmake/EosTui.cmake",
    "content": "# ----------------------------------------------------------------------\n# EOS TUI packaging helpers\n# ----------------------------------------------------------------------\n\nset(EOS_TUI_LICENSE_URL \"https://raw.githubusercontent.com/cern-eos/eos-tui/v${EOS_TUI_VERSION}/LICENSE\")\nset(EOS_TUI_README_URL \"https://raw.githubusercontent.com/cern-eos/eos-tui/v${EOS_TUI_VERSION}/README.md\")\n\nif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\" AND CMAKE_SYSTEM_PROCESSOR MATCHES \"^(x86_64|amd64)$\")\n  set(EOS_TUI_BINARY_NAME \"eos-tui_v${EOS_TUI_VERSION}_linux_amd64\")\n  set(EOS_TUI_BINARY_URL \"https://github.com/cern-eos/eos-tui/releases/download/v${EOS_TUI_VERSION}/${EOS_TUI_BINARY_NAME}\")\nelseif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\" AND CMAKE_SYSTEM_PROCESSOR MATCHES \"^(aarch64|arm64)$\")\n  set(EOS_TUI_BINARY_NAME \"eos-tui_v${EOS_TUI_VERSION}_linux_arm64\")\n  set(EOS_TUI_BINARY_URL \"https://github.com/cern-eos/eos-tui/releases/download/v${EOS_TUI_VERSION}/${EOS_TUI_BINARY_NAME}\")\nendif()\n\nset(EOS_TUI_INSTALL_STAGING_DIR \"${CMAKE_BINARY_DIR}/eos-tui/v${EOS_TUI_VERSION}\")\nset(EOS_TUI_LICENSE_STAGED \"${EOS_TUI_INSTALL_STAGING_DIR}/LICENSE\")\nset(EOS_TUI_README_STAGED \"${EOS_TUI_INSTALL_STAGING_DIR}/README.md\")\n\nif(EOS_TUI_BINARY_NAME)\n  set(EOS_TUI_BINARY_STAGED \"${EOS_TUI_INSTALL_STAGING_DIR}/${EOS_TUI_BINARY_NAME}\")\n  configure_file(\n    \"${CMAKE_CURRENT_SOURCE_DIR}/cmake/EosTuiInstall.cmake.in\"\n    \"${CMAKE_CURRENT_BINARY_DIR}/cmake/EosTuiInstall.cmake\"\n    @ONLY)\n\n  install(SCRIPT \"${CMAKE_CURRENT_BINARY_DIR}/cmake/EosTuiInstall.cmake\")\n  install(PROGRAMS \"${EOS_TUI_BINARY_STAGED}\"\n    DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n    RENAME eos-tui)\n  install(FILES \"${EOS_TUI_LICENSE_STAGED}\"\n    DESTINATION \"${CMAKE_INSTALL_FULL_DATAROOTDIR}/licenses/eos-tui\")\n  install(FILES \"${EOS_TUI_README_STAGED}\"\n    DESTINATION \"${CMAKE_INSTALL_FULL_DATAROOTDIR}/doc/eos-tui\")\nelse()\n  message(WARNING \"EOS TUI install is only configured for Linux x86_64 and aarch64 builds.\")\nendif()\n"
  },
  {
    "path": "cmake/EosTuiInstall.cmake.in",
    "content": "# ----------------------------------------------------------------------\n# EOS TUI install helpers\n# ----------------------------------------------------------------------\n\nfunction(eos_tui_download url destination)\n  if(EXISTS \"${destination}\")\n    return()\n  endif()\n\n  file(DOWNLOAD \"${url}\" \"${destination}\" STATUS download_status TLS_VERIFY ON)\n  list(GET download_status 0 download_code)\n  list(GET download_status 1 download_message)\n\n  if(NOT download_code EQUAL 0)\n    file(REMOVE \"${destination}\")\n    message(FATAL_ERROR \"Failed to download ${url}: ${download_message}\")\n  endif()\nendfunction()\n\nfile(MAKE_DIRECTORY \"@EOS_TUI_INSTALL_STAGING_DIR@\")\neos_tui_download(\"@EOS_TUI_BINARY_URL@\" \"@EOS_TUI_BINARY_STAGED@\")\neos_tui_download(\"@EOS_TUI_LICENSE_URL@\" \"@EOS_TUI_LICENSE_STAGED@\")\neos_tui_download(\"@EOS_TUI_README_URL@\" \"@EOS_TUI_README_STAGED@\")\n"
  },
  {
    "path": "cmake/EosUtils.cmake",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n#-------------------------------------------------------------------------------\n# Get UID/GID for an account\n#-------------------------------------------------------------------------------\nfunction(EOS_GetUidGid USERNAME UIDVARNAME GIDVARNAME)\n    execute_process(\n      COMMAND sh -c \"id -u ${USERNAME}\"\n      OUTPUT_VARIABLE UID\n      OUTPUT_STRIP_TRAILING_WHITESPACE\n      RESULT_VARIABLE RETC)\n\n    execute_process(\n      COMMAND sh -c \"id -g ${USERNAME}\"\n      OUTPUT_VARIABLE GID\n      OUTPUT_STRIP_TRAILING_WHITESPACE\n      RESULT_VARIABLE RETC)\n\n  set(${UIDVARNAME} ${UID} PARENT_SCOPE)\n  set(${GIDVARNAME} ${GID} PARENT_SCOPE)\n\n  if(NOT (\"${RETC}\" STREQUAL \"0\") )\n    message(FATAL_ERROR \"Error calling uid, return code is ${RETC}\")\n  endif()\nendfunction()\n\n#-------------------------------------------------------------------------------\n# Get version\n#-------------------------------------------------------------------------------\nfunction(EOS_GetVersion MAJOR MINOR PATCH RELEASE)\n  if((\"${MAJOR}\" STREQUAL \"\") OR\n     (\"${MINOR}\" STREQUAL \"\") OR\n     (\"${PATCH}\" STREQUAL \"\"))\n      message(VERBOSE \"Determining EOS version with `${CMAKE_SOURCE_DIR}/genversion.sh` script\")\n      execute_process(\n              COMMAND ${CMAKE_SOURCE_DIR}/genversion.sh ${CMAKE_SOURCE_DIR}\n              OUTPUT_VARIABLE VERSION_INFO\n              RESULT_VARIABLE VERSION_INFO_RESULT\n              OUTPUT_STRIP_TRAILING_WHITESPACE\n      )\n\n      if(NOT VERSION_INFO_RESULT EQUAL 0)\n          set(VERSION_ERR_MSG \"Error getting EOS version info using `${CMAKE_SOURCE_DIR}/genversion.sh`\")\n          if(CMAKE_BUILD_TYPE STREQUAL \"Debug\")\n              # for debug builds we take version from cache if available otherwise use a dummy version\n              message(WARNING \"${VERSION_ERR_MSG}\")\n              if(NOT DEFINED VERSION_INFO_FROM_CACHE)\n                  # use a placeholder version (might need to be updated in the future)\n                  set(VERSION_INFO \"5.3.27-unknown\")\n              else()\n                  message(WARNING \"Using cached EOS version info for debug build: ${VERSION_INFO_FROM_CACHE}\")\n                  set(VERSION_INFO \"${VERSION_INFO_FROM_CACHE}\")\n              endif()\n              message(WARNING \"Setting EOS version to ${VERSION_INFO} for debug build\")\n          else()\n              message(FATAL_ERROR \"${VERSION_ERR_MSG}\")\n          endif()\n      else()\n        message(VERBOSE \"Detected EOS version: ${VERSION_INFO}\")\n        if(CMAKE_BUILD_TYPE STREQUAL \"Debug\")\n            set(VERSION_INFO_FROM_CACHE \"${VERSION_INFO}\" CACHE INTERNAL \"Cached EOS version info for debug builds\")\n        endif()\n      endif()\n\n    string(REPLACE \".\" \";\" VERSION_LIST ${VERSION_INFO})\n    list(GET VERSION_LIST 0 MAJOR)\n    list(GET VERSION_LIST 1 MINOR)\n    list(GET VERSION_LIST 2 PATCH)\n\n    # The patch could also contain the RELEASE value if this is a snapshot\n    string(FIND \"${PATCH}\" \"-\" POS)\n\n    if (NOT \"${POS}\" EQUAL \"-1\")\n      string(REPLACE \"-\" \";\" PR_LIST ${PATCH})\n      list(GET PR_LIST 0 PATCH)\n\n      # Set RELEASE on if not already set\n      if (\"${RELEASE}\" STREQUAL \"\")\n        list(GET PR_LIST 1 RELEASE)\n      endif()\n    endif()\n  endif()\n\n  set(VERSION_MAJOR ${MAJOR} PARENT_SCOPE)\n  set(VERSION_MINOR ${MINOR} PARENT_SCOPE)\n  set(VERSION_PATCH ${PATCH} PARENT_SCOPE)\n  set(VERSION \"${MAJOR}.${MINOR}.${PATCH}\" PARENT_SCOPE)\n\n  if(\"${RELEASE}\" STREQUAL \"\")\n    set(RELEASE \"1\")\n  endif()\n\n  set(RELEASE ${RELEASE} PARENT_SCOPE)\nendfunction()\n\n#-------------------------------------------------------------------------------\n# Detect in source builds\n#-------------------------------------------------------------------------------\nmacro(EOS_CheckOutOfSourceBuild)\n  #Check if previous in-source build failed\n  if(EXISTS ${CMAKE_SOURCE_DIR}/CMakeCache.txt OR EXISTS ${CMAKE_SOURCE_DIR}/CMakeFiles)\n    message(FATAL_ERROR \"CMakeCache.txt or CMakeFiles exists in source directory!\")\n    message(FATAL_ERROR \"Please remove them before running cmake .\")\n  endif(EXISTS ${CMAKE_SOURCE_DIR}/CMakeCache.txt OR EXISTS ${CMAKE_SOURCE_DIR}/CMakeFiles)\n\n  # Get real paths of the source and binary directories\n  get_filename_component(srcdir \"${CMAKE_SOURCE_DIR}\" REALPATH)\n  get_filename_component(bindir \"${CMAKE_BINARY_DIR}\" REALPATH)\n\n  # Check for in-source builds\n  if(${srcdir} STREQUAL ${bindir})\n    message(FATAL_ERROR \"EOS cannot be built in-source! Please run cmake <src-dir> outside the source directory\")\n  endif(${srcdir} STREQUAL ${bindir})\nendmacro(EOS_CheckOutOfSourceBuild)\n"
  },
  {
    "path": "cmake/FindActiveMQCPP.cmake",
    "content": "# FindActiveMQCPP.cmake\n\n# Locate the header\nfind_path(ACTIVEMQCPP_INCLUDE_DIR\n    NAMES cms/Connection.h\n    PATH_SUFFIXES\n        activemq-cpp\n        activemq-cpp-3.9.5\n        activemq-cpp-3.9\n        activemq-cpp-3\n    PATHS\n        /usr/include\n        /usr/local/include\n        /opt/include\n)\n\n# Locate the library\nfind_library(ACTIVEMQCPP_LIBRARY\n    NAMES activemq-cpp\n    PATHS\n        /usr/lib /usr/lib64\n        /usr/local/lib /usr/local/lib64\n        /opt/lib /opt/lib64\n)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(ActiveMQCPP\n    REQUIRED_VARS ACTIVEMQCPP_LIBRARY ACTIVEMQCPP_INCLUDE_DIR\n)\n\nif (ACTIVEMQCPP_FOUND AND NOT TARGET ActiveMQCPP::ActiveMQCPP)\n    add_library(ActiveMQCPP::ActiveMQCPP UNKNOWN IMPORTED)\n\n    set_target_properties(ActiveMQCPP::ActiveMQCPP PROPERTIES\n        IMPORTED_LOCATION \"${ACTIVEMQCPP_LIBRARY}\"\n        INTERFACE_INCLUDE_DIRECTORIES \"${ACTIVEMQCPP_INCLUDE_DIR}\"\n    )\nendif()\n"
  },
  {
    "path": "cmake/FindAtomic.cmake",
    "content": "# Try to find libatmoic\n# Once done, this will define\n#\n# ATOMIC_FOUND        - system has libatomic\n# ATOMIC_LIBRARIES    - libraries needed to use libatomic\n#\n# and the following imported target\n# ATOMIC::ATOMIC\n\nfind_library(ATOMIC_LIBRARY\n  NAMES atomic atomic.so.1 libatomic.so.1\n  HINTS ${ATOMIC_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(Atomic\n  DEFAULT_MSG ATOMIC_LIBRARY)\n\nif (ATOMIC_FOUND AND NOT TARGET ATOMIC::ATOMIC)\n  mark_as_advanced(ATOMIC_LIBRARY)\n  add_library(ATOMIC::ATOMIC UNKNOWN IMPORTED)\n  set_target_properties(ATOMIC::ATOMIC PROPERTIES\n    IMPORTED_LOCATION ${ATOMIC_LIBRARY})\nendif()\n\nset(ATOMIC_LIBRARIES ${ATOMIC_LIBRARY})\nunset(ATOMIC_LIBRARY)\n"
  },
  {
    "path": "cmake/FindEosGrpcGateway.cmake",
    "content": "# Try to find eos-grpc-gateway library and header files\n# Once done, this will define\n#\n# EosGrpcGateway_FOUND          - system has grpc gateway library\n# EosGrpcGateway_INCLUDE_DIRS   - gateway include directories\n# EosGrpcGateway_LIBRARY        - gateway library\n#\n\nfind_path(EosGrpcGateway_INCLUDE_DIR\n  NAMES EosGrpcGateway.h\n  HINTS /usr ${EosGrpcGateway_ROOT}\n  PATH_SUFFIXES include)\n\nfind_library(EosGrpcGateway_LIBRARY NAMES libEosGrpcGateway.so\n  HINTS /usr/lib64 ${EosGrpcGateway_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\nif(EosGrpcGateway_LIBRARY)\n  set(EosGrpcGateway_FOUND 1)\n  message (STATUS \"EosGrpcGateway_LIBRARY=${EosGrpcGateway_LIBRARY}\")\nendif()\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(EosGrpcGateway\n  REQUIRED_VARS EosGrpcGateway_LIBRARY EosGrpcGateway_INCLUDE_DIR)\n\nmark_as_advanced(EosGrpcGateway_INCLUDE_DIR EosGrpcGateway_LIBRARY)\nmessage(STATUS \"EosGrpcGateway_INCLUDE_DIR=${EosGrpcGateway_INCLUDE_DIR}\")\n\nif (EosGrpcGateway_FOUND AND NOT TARGET EosGrpcGateway::EosGrpcGateway)\n  add_library(EosGrpcGateway::EosGrpcGateway UNKNOWN IMPORTED)\n  set_target_properties(EosGrpcGateway::EosGrpcGateway PROPERTIES\n    IMPORTED_LOCATION \"${EosGrpcGateway_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${EosGrpcGateway_INCLUDE_DIR}\"\n    INTERFACE_COMPILE_DEFINITIONS EOS_GRPC_GATEWAY=1)\nelse()\n  add_library(EosGrpcGateway::EosGrpcGateway INTERFACE IMPORTED)\nendif ()\n\nunset(EosGrpcGateway_INCLUDE_DIR)\nunset(EosGrpcGateway_LIBRARY)\n"
  },
  {
    "path": "cmake/FindGRPC.cmake",
    "content": "#\n# Locate and configure the GRPC library\n#\n# Adds the following targets:\n#\n#  GRPC::grpc - GRPC library\n#  GRPC::grpc++ - GRPC C++ library\n#  GRPC::grpc++_reflection - GRPC C++ reflection library\n#  GRPC::grpc_cpp_plugin - C++ generator plugin for Protocol Buffers\n#\n\n#\n# Generates C++ sources from the .proto files\n#\n# grpc_generate_cpp (<SRCS> <HDRS> <DEST> [<ARGN>...])\n#\n#  SRCS - variable to define with autogenerated source files\n#  HDRS - variable to define with autogenerated header files\n#  DEST - directory where the source files will be created\n#  ARGN - .proto files\n#\nfunction(GRPC_GENERATE_CPP SRCS HDRS DEST)\n  if(NOT ARGN)\n    message(SEND_ERROR \"Error: GRPC_GENERATE_CPP() called without any proto files\")\n    return()\n  endif()\n\n  if(GRPC_GENERATE_CPP_APPEND_PATH)\n    # Create an include path for each file specified\n    foreach(FIL ${ARGN})\n      get_filename_component(ABS_FIL ${FIL} ABSOLUTE)\n      get_filename_component(ABS_PATH ${ABS_FIL} PATH)\n      list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)\n      if(${_contains_already} EQUAL -1)\n          list(APPEND _protobuf_include_path -I ${ABS_PATH})\n      endif()\n    endforeach()\n  else()\n    set(_protobuf_include_path -I ${CMAKE_CURRENT_SOURCE_DIR})\n  endif()\n\n  if(DEFINED PROTOBUF3_IMPORT_DIRS)\n    foreach(DIR ${PROTOBUF3_IMPORT_DIRS})\n      get_filename_component(ABS_PATH ${DIR} ABSOLUTE)\n      list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)\n      if(${_contains_already} EQUAL -1)\n          list(APPEND _protobuf_include_path -I ${ABS_PATH})\n      endif()\n    endforeach()\n  endif()\n\n  set(${SRCS})\n  set(${HDRS})\n  foreach(FIL ${ARGN})\n    get_filename_component(ABS_FIL ${FIL} ABSOLUTE)\n    get_filename_component(FIL_WE ${FIL} NAME_WE)\n\n    list(APPEND ${SRCS} \"${DEST}/${FIL_WE}.grpc.pb.cc\")\n    list(APPEND ${HDRS} \"${DEST}/${FIL_WE}.grpc.pb.h\")\n\n    add_custom_command(\n      OUTPUT \"${DEST}/${FIL_WE}.grpc.pb.cc\"\n             \"${DEST}/${FIL_WE}.grpc.pb.h\"\n      COMMAND ${CMAKE_COMMAND} -E env\n      \"LD_LIBRARY_PATH=${GRPC_LD_LIBRARY_PATH}:$LD_LIBRARY_PATH\"\n      ${PROTOBUF3_PROTOC_EXECUTABLE}\n      ARGS --grpc_out ${DEST} ${_protobuf_include_path} --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN} ${ABS_FIL}\n      DEPENDS ${ABS_FIL} ${PROTOBUF3_PROTOC_EXECUTABLE} GRPC::grpc_cpp_plugin\n      COMMENT \"Running C++ GRPC compiler on ${FIL}\"\n      VERBATIM )\n  endforeach()\n\n  set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED TRUE)\n  set(${SRCS} ${${SRCS}} PARENT_SCOPE)\n  set(${HDRS} ${${HDRS}} PARENT_SCOPE)\nendfunction()\n\n# By default have GRPC_GENERATE_CPP macro pass -I to protoc\n# for each directory where a proto file is referenced.\nif(NOT DEFINED GRPC_GENERATE_CPP_APPEND_PATH)\n  set(GRPC_GENERATE_CPP_APPEND_PATH TRUE)\nendif()\n\n# Find GRPC include directory\nfind_path(GRPC_INCLUDE_DIR\n  NAMES grpc/grpc.h\n  HINTS ${GRPC_ROOT}\n  PATHS /opt/eos/grpc /usr/local /usr\n  PATH_SUFFIXES include\n  NO_DEFAULT_PATH)\n\nmark_as_advanced(GRPC_INCLUDE_DIR)\n\n# Find GRPC library\nfind_library(GRPC_LIBRARY\n  NAMES grpc\n  HINTS ${GRPC_ROOT}\n  PATHS /opt/eos/grpc /usr/local /usr\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR}\n  NO_DEFAULT_PATH)\n\n# Find GRPC C++ library\nfind_library(GRPC_GRPC++_LIBRARY\n  NAMES grpc++\n  HINTS ${GRPC_ROOT}\n  PATHS /opt/eos/grpc /usr/local /usr\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR}\n  NO_DEFAULT_PATH)\n\n# Find GRPC libgpr\nfind_library(GRPC_LIBGPR_LIBRARY\n  NAMES gpr\n  HINTS ${GRPC_ROOT}\n  PATHS /opt/eos/grpc /usr/local /usr\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR}\n  NO_DEFAULT_PATH)\n\n# Find GRPC C++ reflection library\nfind_library(GRPC_GRPC++_REFLECTION_LIBRARY\n  NAMES grpc++_reflection\n  HINTS ${GRPC_ROOT}\n  PATHS /opt/eos/grpc /usr/local /usr\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR}\n  NO_DEFAULT_PATH)\n\n# Find GRPC CPP generator\nfind_program(GRPC_CPP_PLUGIN\n  NAMES grpc_cpp_plugin\n  HINTS ${GRPC_ROOT}\n  PATHS /opt/eos/grpc /usr/local /usr\n  PATH_SUFFIXES bin\n  NO_DEFAULT_PATH)\n\n# Find libabsl_synchronization\nfind_library(ABSL_SYNCHRONIZATION_LIBRARY\n  NAMES absl_synchronization\n  HINTS ${GRPC_ROOT} ${ABSL_ROOT}\n  PATHS /opt/eos/grpc /usr/local /usr\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR}\n  NO_DEFAULT_PATH)\n\n# Find libabsl_base\nfind_library(ABSL_BASE_LIBRARY\n  NAMES absl_base\n  HINTS ${GRPC_ROOT} ${ABSL_ROOT}\n  PATHS /opt/eos/grpc /usr/local /usr\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR}\n  NO_DEFAULT_PATH)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(GRPC\n  REQUIRED_VARS GRPC_LIBRARY GRPC_INCLUDE_DIR ABSL_BASE_LIBRARY\n                ABSL_SYNCHRONIZATION_LIBRARY GRPC_LIBGPR_LIBRARY\n  GRPC_GRPC++_REFLECTION_LIBRARY GRPC_CPP_PLUGIN)\n\nmark_as_advanced(GRPC_LIBRARY GRPC_GRPC++_LIBRARY\n  GRPC_GRPC++_REFLECTION_LIBRARY GRPC_CPP_PLUGIN)\n\nif (GRPC_FOUND AND NOT TARGET GRPC::grpc AND NOT TARGET GRPC::grpc++)\n  get_filename_component(GRPC_LD_LIBRARY_PATH ${GRPC_GRPC++_LIBRARY} DIRECTORY)\n  message(STATUS \"GRPC library path: ${GRPC_LD_LIBRARY_PATH}\")\n\n  add_library(GRPC::grpc UNKNOWN IMPORTED)\n  set_target_properties(GRPC::grpc PROPERTIES\n    INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}\n    INTERFACE_LINK_LIBRARIES \"-lpthread;-ldl\"\n    IMPORTED_LOCATION ${GRPC_LIBRARY}\n    INTERFACE_COMPILE_DEFINITIONS EOS_GRPC=1)\n\n  add_library(GRPC::GPR UNKNOWN IMPORTED)\n  set_target_properties(GRPC::GPR PROPERTIES\n    INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}\n    IMPORTED_LOCATION ${GRPC_LIBGPR_LIBRARY})\n\n  add_library(GRPC::grpc++ UNKNOWN IMPORTED)\n  set_target_properties(GRPC::grpc++ PROPERTIES\n    INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}\n    INTERFACE_LINK_LIBRARIES \"${GRPC_LIBGPR_LIBRARY};${ABSL_SYNCHRONIZATION_LIBRARY};${ABSL_BASE_LIBRARY}\"\n    IMPORTED_LOCATION ${GRPC_GRPC++_LIBRARY}\n    INTERFACE_COMPILE_DEFINITIONS EOS_GRPC=1)\n\n  add_library(GRPC::grpc++_reflection UNKNOWN IMPORTED)\n  set_target_properties(GRPC::grpc++_reflection PROPERTIES\n    INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}\n    INTERFACE_LINK_LIBRARIES GRPC::grpc++\n    IMPORTED_LOCATION ${GRPC_GRPC++_REFLECTION_LIBRARY})\n\n  add_executable(GRPC::grpc_cpp_plugin IMPORTED)\n  set_target_properties(GRPC::grpc_cpp_plugin PROPERTIES\n    IMPORTED_LOCATION ${GRPC_CPP_PLUGIN})\nendif()\n\nunset(GRPC_INCLUDE_DIR)\nunset(GRPC_LIBRARY)\n"
  },
  {
    "path": "cmake/FindGlobus.cmake",
    "content": "# - Locate Globus libraries\n# Defines:\n#\n#  GLOBUS_FOUND\n#  GLOBUS_INCLUDE_DIR\n#  GLOBUS_INCLUDE_DIRS (not cached)\n#  GLOBUS_LIBRARIES (not cached)\n#  GLOBUS_xxx_LIBRARY\n\nset( headers globus_config globus_gsi_credential )\nforeach( head ${headers} )\n  find_path(GLOBUS_INCLUDE_DIR_${head} NAMES ${head}.h\n          HINTS ${GLOBUS_DIR}/include $ENV{GLOBUS_LOCATION}/include\n                /usr/include/x86_64-linux-gnu /opt/globus/include /usr/include\n          PATH_SUFFIXES gcc32 gcc32dbg gcc32pthr gcc32dbgpthr\n                        gcc64 gcc64dbg gcc64pthr gcc64dbgpthr globus)\n  if(GLOBUS_INCLUDE_DIR_${head})\n    list(APPEND GLOBUS_INCLUDE_DIRS ${GLOBUS_INCLUDE_DIR_${head}})\n  endif()\nendforeach()\n\n#message(\"found GLOBUS include dirs\" ${GLOBUS_INCLUDE_DIRS})\n\nset(libraries gssapi_gsi gss_assist gsi_credential common gsi_callback proxy_ssl\n              gsi_sysconfig openssl_error oldgaa gsi_cert_utils\n              openssl gsi_proxy_core callout)\n\nforeach( lib ${libraries})\n  find_library(GLOBUS_${lib}_LIBRARY NAMES globus_${lib} HINTS \n               ${GLOBUS_DIR}/lib $ENV{GLOBUS_LOCATION}/lib /usr/lib/x86_64-linux-gnu )\n  if(GLOBUS_${lib}_LIBRARY)\n    set(GLOBUS_${lib}_FOUND 1)\n    list(APPEND GLOBUS_LIBRARIES ${GLOBUS_${lib}_LIBRARY})\n    mark_as_advanced(GLOBUS_${lib}_LIBRARY)\n  endif()\nendforeach()\n\n\n# handle the QUIETLY and REQUIRED arguments and set GLOBUS_FOUND to TRUE if\n# all listed variables are TRUE\nINCLUDE(FindPackageHandleStandardArgs)\nFIND_PACKAGE_HANDLE_STANDARD_ARGS(GLOBUS DEFAULT_MSG GLOBUS_INCLUDE_DIR GLOBUS_common_LIBRARY)\n\nmark_as_advanced(GLOBUS_FOUND GLOBUS_INCLUDE_DIR)\n"
  },
  {
    "path": "cmake/FindLibevent.cmake",
    "content": "# Try to find libevent\n# Once done, this will define\n#\n# LIBEVENT_FOUND        - system has libevent\n# LIBEVENT_INCLUDE_DIRS - libevent include directories\n# LIBEVENT_LIBRARIES    - libraries needed to use libevent\n#\n# and the following imported targets\n#\n# LIBEVENT::LIBEVENT\n\nfind_package(PkgConfig)\npkg_check_modules(PC_libevent QUIET libevent)\nset(LIBEVENT_VERSION ${PC_libevent_VERSION})\n\nfind_path(LIBEVENT_INCLUDE_DIR\n  NAMES event.h\n  HINTS ${LIBEVENT_ROOT} ${PC_libevent_INCLUDEDIR} ${PC_libevent_INCLUDE_DIRS} /usr/include/event2)\n\nfind_library(\n  LIBEVENT_LIBRARY\n  NAMES event2 event\n  HINTS ${LIBEVENT_ROOT} ${PC_libevent_LIBDIR} ${PC_libevent_LIBRARY_DIRS}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(Libevent\n  REQUIRED_VARS LIBEVENT_LIBRARY LIBEVENT_INCLUDE_DIR)\n\nif (LIBEVENT_FOUND AND NOT TARGET LIBEVENT::LIBEVENT)\n  mark_as_advanced(LIBEVENT_FOUND LIBEVENT_INCLUDE_DIR LIBEVENT_LIBRARY)\n  add_library(LIBEVENT::LIBEVENT UNKNOWN IMPORTED)\n  set_target_properties(LIBEVENT::LIBEVENT PROPERTIES\n    IMPORTED_LOCATION \"${LIBEVENT_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${LIBEVENT_INCLUDE_DIR}\")\nendif()\n\nset(LIBEVENT_INCLUDE_DIRS ${LIBEVENT_INCLUDE_DIR})\nset(LIBEVENT_LIBRARIES ${LIBEVENT_LIBRARY})\nunset(LIBEVENT_INCLUDE_DIR)\nunset(LIBEVENT_LIBRARY)\n"
  },
  {
    "path": "cmake/FindProtobuf3.cmake",
    "content": "# Try to find PROTOBUF3\n# Once done, this will define\n#\n# PROTOBUF3_FOUND               - system has Protobuf3\n# PROTOBUF3_INCLUDE_DIRS        - Protobuf3 include directories\n# PROTOBUF3_LIBRARIES           - libraries needed to use Protobuf3\n#\n# and the following imported targets\n#\n# PROTOBUF::PROTOBUF\n#\n# PROTOBUF_ROOT may be defined as a hint for where to look\n\nfind_program(PROTOBUF3_PROTOC_EXECUTABLE\n  NAMES protoc\n  HINTS ${PROTOBUF_ROOT}\n  PATHS /opt/eos/grpc/ /opt/eos/ /usr/local /usr /\n  PATH_SUFFIXES bin\n  DOC \"Version 3 of The Google Protocol Buffers Compiler (protoc)\"\n  NO_DEFAULT_PATH)\n\nmessage(STATUS \"Found protoc: ${PROTOBUF3_PROTOC_EXECUTABLE}\")\n\nfind_path(PROTOBUF3_INCLUDE_DIR\n  NAMES google/protobuf/message.h\n  HINTS ${PROTOBUF_ROOT}\n  PATHS /opt/eos/grpc/ /opt/eos/include/protobuf3 /usr/include/protobuf3 /usr/local /usr\n  PATH_SUFFIXES include\n  NO_DEFAULT_PATH)\n\nfind_library(PROTOBUF3_LIBRARY\n  NAME protobuf\n  HINTS ${PROTOBUF_ROOT}\n  PATHS /opt/eos/grpc/ /usr/lib64/protobuf3 /usr/lib/protobuf3\n\t/usr/local /usr /usr/lib/x86_64-linux-gnu\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR} lib64 lib\n  NO_DEFAULT_PATH)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(Protobuf3\n  REQUIRED_VARS PROTOBUF3_LIBRARY PROTOBUF3_INCLUDE_DIR PROTOBUF3_PROTOC_EXECUTABLE)\nmark_as_advanced(PROOBUF3_FOUND PROTOBUF3_INCLUDE_DIR PROTOBUF3_LIBRARY\n  PROTOBUF3_PROTOC_EXECUTABLE)\n\nif (PROTOBUF3_FOUND AND NOT TARGET PROTOBUF::PROTOBUF)\n  # These are set for make the find_package(Protobuf) happy at the end and\n  # at the same time include the PROTOBUF_GENERATE_CPP function\n  set(Protobuf_FOUND ${PROTOBUF3_FOUND})\n  set(Protobuf_INCLUDE_DIR ${PROTOBUF3_INCLUDE_DIR})\n  set(Protobuf_LIBRARY ${PROTOBUF3_LIBRARY})\n  set(Protobuf_PROTOC_EXECUTABLE ${PROTOBUF3_PROTOC_EXECUTABLE})\n\n  add_library(PROTOBUF::PROTOBUF UNKNOWN IMPORTED)\n  set_target_properties(PROTOBUF::PROTOBUF PROPERTIES\n    IMPORTED_LOCATION \"${PROTOBUF3_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${PROTOBUF3_INCLUDE_DIR}\")\n  target_compile_definitions(PROTOBUF::PROTOBUF INTERFACE PROTOBUF_USE_DLLS=1)\n\n  # Overwrite these since they are used in generating the Protobuf files\n  if (NOT TARGET protobuf::protoc)\n    add_executable(protobuf::protoc IMPORTED)\n  endif()\n\n  set_target_properties(protobuf::protoc PROPERTIES\n    IMPORTED_LOCATION ${PROTOBUF3_PROTOC_EXECUTABLE})\n\n  if (NOT TARGET protobuf::libprotobuf)\n    add_library(protobuf::libprotobuf UNKNOWN IMPORTED)\n  endif()\n\n  set_target_properties(protobuf::libprotobuf PROPERTIES\n   INTERFACE_INCLUDE_DIRECTORIES ${PROTOBUF3_INCLUDE_DIR}\n   IMPORTED_LOCATION ${PROTOBUF3_LIBRARY})\nendif ()\n\n# Include Protobuf package for the generation commands like PROTOBUF_GENERATE_CPP\nfind_package(Protobuf)\n"
  },
  {
    "path": "cmake/FindPythonSitePkg.cmake",
    "content": "# Try to find python\n# Once done, this will define\n#\n# PYTHONSITEPKG_FOUND - found python site packages directory\n# PYTHONSITEPKG_PATH  - location where python modules are installed\n\nfind_package (Python3 COMPONENTS Interpreter Development)\n\nif(NOT Python3_Interpreter_FOUND)\n  set(PYTHONSITEPKG_FOUND FALSE)\n  return()\nelse()\n  set(PYTHONSITEPKG_FOUND TRUE)\nendif()\n\nif(Python3_SITELIB)\n  set(PYTHONSITEPKG_FIND_QUIETLY TRUE)\n  set(PYTHONSITEPKG_PATH \"${Python3_SITELIB}\")\n  message(STATUS \"Python Site Path: ${PYTHONSITEPKG_PATH} (site lib found)\")\n\nelse()\n  if((PYTHON_VERSION_MAJOR VERSION_EQUAL \"3\") OR (PYTHON_VERSION_MAJOR VERSION_GREATER \"3\"))\n    set(PY_CMD \"from distutils import sysconfig; print(sysconfig.get_python_lib());\")\n  else()\n    set(PY_CMD \"from distutils import sysconfig; print sysconfig.get_python_lib();\")\n  endif()\n\n  execute_process(\n    COMMAND \"${PYTHON_EXECUTABLE}\" \"-c\" \"${PY_CMD}\"\n    RESULT_VARIABLE _PYTHON_SUCCESS\n    ERROR_VARIABLE _PYTHON_ERROR_VALUE\n    OUTPUT_VARIABLE PYTHONSITEPKG_PATH\n    OUTPUT_STRIP_TRAILING_WHITESPACE)\n\n  if(NOT _PYTHON_SUCCESS MATCHES 0)\n    if(PYTHONSITEPKG_FIND_REQUIRED)\n      message(FATAL_ERROR \"Python config failure:\\n${_PYTHON_ERROR_VALUE}\")\n    else()\n      message(STATUS \"PythonSitePkg was not required\")\n    endif()\n\n    set(PYTHONLIBS_FOUND FALSE)\n    return()\n  endif()\n\n  string(REGEX REPLACE \"\\n\" \"\" PYTHONSITEPKG_PATH ${PYTHONSITEPKG_PATH})\n  message(STATUS \"Python Site Path: ${PYTHONSITEPKG_PATH}\")\n\n  include (FindPackageHandleStandardArgs)\n  find_package_handle_standard_args(\n    Python3SitePkg\n    DEFAULT_MSG PYTHONSITEPKG_PATH)\n\n  mark_as_advanced(PythonSitePkg PYTHONSITEPKG_PATH)\nendif()\n"
  },
  {
    "path": "cmake/FindRocksDB.cmake",
    "content": "# Try to find rocksdb\n# Once done, this will define\n#\n# ROCKSDB_FOUND              - system has rocksdb\n#\n# and the following imported targets\n#\n# ROCKSDB::ROCKSDB\n\nfind_path(ROCKSDB_INCLUDE_DIR\n  NAMES rocksdb/version.h\n  HINTS ${ROCKSDB_ROOT}\n  PATHS /opt/eos/rocksdb/ /usr/local /usr\n  PATH_SUFFIXES include\n  NO_DEFAULT_PATH)\n\nfind_library(ROCKSDB_LIBRARY\n  NAMES rocksdb\n  HINTS ${ROCKSDB_ROOT}\n  PATHS /opt/eos/rocksdb/ /usr/local /usr\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR} lib\n  NO_DEFAULT_PATH)\n\nfind_library(ROCKSDB_TOOLS_LIBRARY\n  NAMES rocksdb_tools\n  HINTS ${ROCKSDB_ROOT}\n  PATHS /opt/eos/rocksdb /usr/local /usr\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR} lib\n  NO_DEFAULT_PATH)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(RocksDB\n  REQUIRED_VARS ROCKSDB_LIBRARY ROCKSDB_INCLUDE_DIR)\nmark_as_advanced(ROCKSDB_FOUND ROCKSDB_LIBRARY ROCKSDB_INCLUDE_DIR)\n\nif (ROCKSDB_FOUND AND NOT TARGET ROCKSDB::ROCKSDB)\n  find_package(zstd REQUIRED)\n  find_package(lz4 REQUIRED)\n  find_package(BZip2 REQUIRED)\n  find_package(Snappy REQUIRED)\n\n  set(ROCKSDB_LIBRARIES \"ZSTD::ZSTD;LZ4::LZ4;BZip2::BZip2;Snappy::snappy\")\n\n  #@note: The ROCKSDB_LIBRARY must be specified again after\n  # the ROCKSDB_TOOLS_LIBRARY since the latter has a symbol\n  # that only the former provides and since these are both\n  # static libraries the linker searches from left to right\n  # and notes unresolved symbols as it goes!!!\n  if (ROCKSDB_TOOLS_LIBRARY)\n    set(ROCKSDB_LIBRARIES \"${ROCKSDB_LIBRARIES};${ROCKSDB_TOOLS_LIBRARY};${ROCKSDB_LIBRARY}\")\n  endif()\n\n  add_library(ROCKSDB::ROCKSDB UNKNOWN IMPORTED)\n  set_target_properties(ROCKSDB::ROCKSDB PROPERTIES\n    IMPORTED_LOCATION \"${ROCKSDB_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${ROCKSDB_INCLUDE_DIR}\"\n    INTERFACE_LINK_LIBRARIES \"${ROCKSDB_LIBRARIES}\"\n    INTERFACE_COMPILE_DEFINITIONS \"HAVE_ROCKSDB=1\")\nelse ()\n  message(WARNING \"Notice: rocksdb not found, no rocksdb support\")\nendif()\n\nunset(ROCKSDB_INCLUDE_DIR)\nunset(ROCKSDB_LIBRARY)\n"
  },
  {
    "path": "cmake/FindScitokens.cmake",
    "content": "# Try to find scitokens\n# Once done, this will define\n#\n# SCITOKENS_FOUND          - system has scitokens\n# SCITOKENS_INCLUDE_DIRS   - scitokens include directories\n#\n# and the following imported targets\n#\n# SCITOKENS::SCITOKENS\n\nfind_path(SCITOKENS_INCLUDE_DIR\n  NAME scitokens/scitokens.h\n  HINTS ${SCITOKENS_ROOT})\n\nfind_library(SCITOKENS_LIBRARY\n  NAME SciTokens\n  HINTS ${SCITOKENS_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(Scitokens\n  REQUIRED_VARS SCITOKENS_LIBRARY SCITOKENS_INCLUDE_DIR)\nmark_as_advanced(SCITOKENS_FOUND SCITOKENS_LIBRARY SCITOKENS_INCLUDE_DIR)\n\nif (SCITOKENS_FOUND AND NOT TARGET SCITOKENS::SCITOKENS)\n  add_library(SCITOKENS::SCITOKENS UNKNOWN IMPORTED)\n  set_target_properties(SCITOKENS::SCITOKENS PROPERTIES\n    IMPORTED_LOCATION \"${SCITOKENS_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${SCITOKENS_INCLUDE_DIR}\"\n    INTERFACE_COMPILE_DEFINITIONS HAVE_SCITOKENS)\nelse()\n  message(WARNING \"Notice: scitokens not found, no scitokens support\")\n  add_library(SCITOKENS::SCITOKENS INTERFACE IMPORTED)\nendif()\n\nunset(SCITOKENS_LIBRARY)\nunset(SCITOKENS_INCLUDE_DIR)\n"
  },
  {
    "path": "cmake/FindSnappy.cmake",
    "content": "# - Find Snappy\n# Find the snappy compression library and includes\n#\n# Snappy_INCLUDE_DIRS - where to find snappy.h, etc.\n# Snappy_LIBRARIES - List of libraries when using snappy.\n# Snappy_FOUND - True if snappy found.\n\nfind_path(Snappy_INCLUDE_DIRS\n  NAMES snappy.h\n  HINTS ${snappy_ROOT_DIR}/include)\n\nfind_library(Snappy_LIBRARIES\n  NAMES snappy\n  HINTS ${snappy_ROOT_DIR}/lib)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(Snappy DEFAULT_MSG Snappy_LIBRARIES Snappy_INCLUDE_DIRS)\n\nmark_as_advanced(\n  Snappy_LIBRARIES\n  Snappy_INCLUDE_DIRS)\n\nif(Snappy_FOUND AND NOT (TARGET Snappy::snappy))\n  add_library (Snappy::snappy UNKNOWN IMPORTED)\n  set_target_properties(Snappy::snappy\n    PROPERTIES\n      IMPORTED_LOCATION ${Snappy_LIBRARIES}\n      INTERFACE_INCLUDE_DIRECTORIES ${Snappy_INCLUDE_DIRS})\nendif()\n"
  },
  {
    "path": "cmake/FindSparseHash.cmake",
    "content": "# Try to find SparseHash\n# Once done, this will define\n#\n# SPARSEHASH_FOUND        - system has SparseHash\n# SPARSEHASH_INCLUDE_DIRS - SparseHash include directories\n#\n# and the following imported tags\n#\n# GOOGLE::SPARSEHASH\n\nfind_path(SPARSEHASH_INCLUDE_DIR\n  NAMES google/sparsehash/sparsehashtable.h\n  HINTS ${SPARSEHASH_ROOT})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(SparseHash\n  REQUIRED_VARS SPARSEHASH_INCLUDE_DIR)\nmark_as_advanced(SPARSEHASH_FOUND SPARSEHASH_INCLUDE_DIR)\n\nif (SPARSEHASH_FOUND AND NOT TARGET GOOGLE::SPARSEHASH)\n  add_library(GOOGLE::SPARSEHASH INTERFACE IMPORTED)\n  set_target_properties(GOOGLE::SPARSEHASH PROPERTIES\n    INTERFACE_INCLUDE_DIRECTORIES \"${SPARSEHASH_INCLUDE_DIR}\")\nendif()\n\nset(SPARSEHASH_INCLUDE_DIRS ${SPARSEHASH_INCLUDE_DIR})\nunset(SPARSEHASH_INCLUDE_DIR)\n"
  },
  {
    "path": "cmake/FindSphinx.cmake",
    "content": "# Try to find then sphinx executable\n# Once done, this will define\n#\n# SPHINX_FOUND      - system has Sphinx\n# SPHINX_EXECUTABLE - Sphinx executable\n\nfind_program(SPHINX_EXECUTABLE NAMES sphinx-build\n    HINTS\n    $ENV{SPHINX_DIR}\n    PATH_SUFFIXES bin\n    DOC \"Sphinx documentation generator\")\n\ninclude(FindPackageHandleStandardArgs)\n\nfind_package_handle_standard_args(Sphinx DEFAULT_MSG\n    SPHINX_EXECUTABLE)\n\nmark_as_advanced(SPHINX_EXECUTABLE)\n"
  },
  {
    "path": "cmake/FindXRootD.cmake",
    "content": "# Try to find XROOTD\n# Once done, this will define\n#\n# XROOTD_FOUND               - system has XRootD\n# XROOTD_INCLUDE_DIRS        - XRootD include directories\n# XROOTD_LIBRARIES           - libraries needed to use XRootD\n# XROOTD_PRIVATE_INCLUDE_DIR - XRootD private include directory\n#\n# XROOTD_ROOT may be defined as a hint for where to look\n\nfind_path(XROOTD_INCLUDE_DIR\n  NAMES XrdVersion.hh\n  HINTS ${XROOTD_ROOT} $ENV{XROOTD_ROOT} /opt/eos/xrootd/\n  PATH_SUFFIXES include/xrootd)\n\nfind_path(XROOTD_PRIVATE_INCLUDE_DIR\n  NAMES XrdOss/XrdOssApi.hh\n  HINTS ${XROOTD_ROOT} $ENV{XROOTD_ROOT} /opt/eos/xrootd/\n  PATH_SUFFIXES include/xrootd/private)\n\nfind_library(XROOTD_UTILS_LIBRARY\n  NAMES XrdUtils\n  HINTS ${XROOTD_ROOT} $ENV{XROOTD_ROOT} /opt/eos/xrootd/\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\nfind_library(XROOTD_SERVER_LIBRARY\n  NAMES XrdServer\n  HINTS ${XROOTD_ROOT} $ENV{XROOTD_ROOT} /opt/eos/xrootd/\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\nfind_library(XROOTD_CL_LIBRARY\n  NAMES XrdCl\n  HINTS ${XROOTD_ROOT} $ENV{XROOTD_ROOT} /opt/eos/xrootd/\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\nfind_library(XROOTD_POSIX_LIBRARY\n  NAMES XrdPosix\n  HINTS ${XROOTD_ROOT} $ENV{XROOTD_ROOT} /opt/eos/xrootd/\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\nfind_library(XROOTD_SSI_LIBRARY\n  NAMES XrdSsiLib\n  HINTS ${XROOTD_ROOT} $ENV{XROOTD_ROOT} /opt/eos/xrootd/\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\nfind_library(\n  XROOTD_HTTP_UTILS_LIBRARY\n  NAMES XrdHttpUtils\n  HINTS ${XROOTD_ROOT} $ENV{XROOTD_ROOT} /opt/eos/xrootd/\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\nset(XROOTD_INCLUDE_DIRS ${XROOTD_INCLUDE_DIR} ${XROOTD_PRIVATE_INCLUDE_DIR})\n\nset(XROOTD_LIBRARIES\n  ${XROOTD_SERVER_LIBRARY}\n  ${XROOTD_CL_LIBRARY}\n  ${XROOTD_UTILS_LIBRARY}\n  ${XROOTD_POSIX_LIBRARY}\n  ${XROOTD_SSI_LIBRARY}\n  ${XROOTD_HHTP_UTILS_LIBRARY})\n\n###############################################################################\n# Figure out what is the plugin version\n###############################################################################\nexecute_process( COMMAND grep \"#define XRDPLUGIN_SOVERSION\" ${XROOTD_INCLUDE_DIR}/XrdVersion.hh\n                 OUTPUT_VARIABLE XRDPLUGIN_SOVERSION )\nstring( REGEX MATCH \"[0123456789]+\" XRDPLUGIN_SOVERSION ${XRDPLUGIN_SOVERSION} )\n\n# Find XRootD dynamically loaded libraries\nfind_library(XROOTD_DL_SECKRB5_LIBRARY\n  NAMES XrdSeckrb5-${XRDPLUGIN_SOVERSION}\n  HINTS ${XROOTD_ROOT} $ENV{XROOTD_ROOT} /opt/eos/xrootd/\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\nfind_library(XROOTD_DL_SECGSI_LIBRARY\n  NAMES XrdSecgsi-${XRDPLUGIN_SOVERSION}\n  HINTS ${XROOTD_ROOT} $ENV{XROOTD_ROOT} /opt/eos/xrootd/\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\nfind_library(XROOTD_DL_SECGSIAUTHZVO_LIBRARY\n  NAMES XrdSecgsiAUTHZVO-${XRDPLUGIN_SOVERSION}\n  HINTS ${XROOTD_ROOT} $ENV{XROOTD_ROOT} /opt/eos/xrootd/\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\nfind_library(XROOTD_DL_SECGSIGMAPDN_LIBRARY\n  NAMES XrdSecgsiGMAPDN-${XRDPLUGIN_SOVERSION}\n  HINTS ${XROOTD_ROOT} $ENV{XROOTD_ROOT} /opt/eos/xrootd/\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\nfind_library(XROOTD_DL_SECPWD_LIBRARY\n  NAMES XrdSecpwd-${XRDPLUGIN_SOVERSION}\n  HINTS ${XROOTD_ROOT} $ENV{XROOTD_ROOT} /opt/eos/xrootd/\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\nfind_library(XROOTD_DL_SECSSS_LIBRARY\n  NAMES XrdSecsss-${XRDPLUGIN_SOVERSION}\n  HINTS ${XROOTD_ROOT} $ENV{XROOTD_ROOT} /opt/eos/xrootd/\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\nfind_library(XROOTD_DL_SECUNIX_LIBRARY\n  NAMES XrdSecunix-${XRDPLUGIN_SOVERSION}\n  HINTS ${XROOTD_ROOT} $ENV{XROOTD_ROOT} /opt/eos/xrootd/\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\nset(XROOTD_DL_LIBRARIES\n  ${XROOTD_DL_SECUNIX_LIBRARY}\n  ${XROOTD_DL_SECSSS_LIBRARY}\n  ${XROOTD_DL_SECPWD_LIBRARY}\n  ${XROOTD_DL_SECGSIGMAPDN_LIBRARY}\n  ${XROOTD_DL_SECGSIAUTHZVO_LIBRARY}\n  ${XROOTD_DL_SECGSI_LIBRARY}\n  ${XROOTD_DL_SECKRB5_LIBRARY})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(XRootD\n  DEFAULT_MSG\n  XROOTD_SERVER_LIBRARY\n  XROOTD_UTILS_LIBRARY\n  XROOTD_CL_LIBRARY\n  XROOTD_POSIX_LIBRARY\n  XROOTD_HTTP_UTILS_LIBRARY\n  XROOTD_DL_SECUNIX_LIBRARY\n  XROOTD_DL_SECSSS_LIBRARY\n  XROOTD_DL_SECPWD_LIBRARY\n  XROOTD_DL_SECGSIGMAPDN_LIBRARY\n  XROOTD_DL_SECGSIAUTHZVO_LIBRARY\n  XROOTD_DL_SECGSI_LIBRARY\n  XROOTD_DL_SECKRB5_LIBRARY\n  XROOTD_INCLUDE_DIR\n  XROOTD_PRIVATE_INCLUDE_DIR)\n\nmark_as_advanced(\n  XROOTD_SERVER_LIBRARY\n  XROOTD_UTILS_LIBRARY\n  XROOTD_HTTP_UTILS_LIBRARY\n  XROOTD_CL_LIBRARY\n  XROOTD_INCLUDE_DIR\n  XROOTD_PRIVATE_INCLUDE_DIR\n  XRDPLUGIN_SOVERSION)\n\nif (XROOTD_FOUND AND NOT TARGET XROOTD::SERVER)\n  add_library(XROOTD::PRIVATE INTERFACE IMPORTED)\n  set_target_properties(XROOTD::PRIVATE PROPERTIES\n    INTERFACE_INCLUDE_DIRECTORIES \"${XROOTD_PRIVATE_INCLUDE_DIR}\"\n    INTERFACE_SYSTEM_INCLUDE_DIRECTORIES \"${XROOTD_PRIVATE_INCLUDE_DIR}\")\n\n  add_library(XROOTD::SERVER UNKNOWN IMPORTED)\n  set_target_properties(XROOTD::SERVER PROPERTIES\n    IMPORTED_LOCATION \"${XROOTD_SERVER_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${XROOTD_INCLUDE_DIR}\"\n    INTERFACE_SYSTEM_INCLUDE_DIRECTORIES \"${XROOTD_INCLUDE_DIR}\")\n\n  add_library(XROOTD::CL UNKNOWN IMPORTED)\n  set_target_properties(XROOTD::CL PROPERTIES\n    IMPORTED_LOCATION \"${XROOTD_CL_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${XROOTD_INCLUDE_DIR}\"\n    INTERFACE_SYSTEM_INCLUDE_DIRECTORIES \"${XROOTD_INCLUDE_DIR}\")\n\n  add_library(XROOTD::UTILS UNKNOWN IMPORTED)\n  set_target_properties(XROOTD::UTILS PROPERTIES\n    IMPORTED_LOCATION \"${XROOTD_UTILS_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${XROOTD_INCLUDE_DIR}\"\n    INTERFACE_SYSTEM_INCLUDE_DIRECTORIES \"${XROOTD_INCLUDE_DIR}\")\n\n  add_library(XROOTD::HTTP UNKNOWN IMPORTED)\n  set_target_properties(XROOTD::HTTP PROPERTIES\n    IMPORTED_LOCATION \"${XROOTD_HTTP_UTILS_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${XROOTD_INCLUDE_DIR}\"\n    INTERFACE_SYSTEM_INCLUDE_DIRECTORIES \"${XROOTD_INCLUDE_DIR}\")\n\n  add_library(XROOTD::SSI UNKNOWN IMPORTED)\n  set_target_properties(XROOTD::SSI PROPERTIES\n    IMPORTED_LOCATION \"${XROOTD_SSI_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${XROOTD_INCLUDE_DIR}\"\n    INTERFACE_SYSTEM_INCLUDE_DIRECTORIES \"${XROOTD_INCLUDE_DIR}\")\n\n  add_library(XROOTD::POSIX UNKNOWN IMPORTED)\n  set_target_properties(XROOTD::POSIX PROPERTIES\n    IMPORTED_LOCATION \"${XROOTD_POSIX_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${XROOTD_INCLUDE_DIR}\"\n    INTERFACE_SYSTEM_INCLUDE_DIRECTORIES \"${XROOTD_INCLUDE_DIR}\")\n\n  unset(XROOTD_INCLUDE_DIR)\n  unset(XROOTD_PRIVATE_INCLUDE_DIR)\nendif ()\n"
  },
  {
    "path": "cmake/FindZMQ.cmake",
    "content": "# Try to find ZMQ\n# Once done, this will define\n#\n#  ZMQ_FOUND           - system has ZMQ\n#  ZMQ_INCLUDE_DIRS    - ZMQ include directories\n#  ZMQ_CPP_INCLUDE_DIR - ZMQ CPP binding i.e. zmq.hpp\n#  ZMQ_LIBRARIES       - libraries needed to use ZMQ\n#\n# and the following imported targets\n#\n# ZMQ::ZMQ\n\nfind_path(ZMQ_INCLUDE_DIR\n  NAMES zmq.h\n  HINTS ${ZMQ_ROOT}\n  PATH_SUFFIXES include)\n\nfind_path(ZMQ_CPP_INCLUDE_DIR\n  NAMES zmq.hpp\n  HINTS ${ZMQ_ROOT} ${CMAKE_SOURCE_DIR}/common/\n  PATH_SUFFIXES cppzmq )\n\nfind_library(ZMQ_LIBRARY\n  NAMES zmq\n  HINTS ${ZMQ_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(ZMQ\n  REQUIRED_VARS ZMQ_LIBRARY ZMQ_INCLUDE_DIR ZMQ_CPP_INCLUDE_DIR)\nmark_as_advanced(ZMQ_LIBRARY ZMQ_INCLUDE_DIR ZMQ_CPP_INCLUDE_DIR)\n\nif (ZMQ_FOUND AND NOT TARGET ZMQ::ZMQ)\n  add_library(ZMQ::ZMQ UNKNOWN IMPORTED)\n  set_target_properties(ZMQ::ZMQ PROPERTIES\n    IMPORTED_LOCATION \"${ZMQ_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${ZMQ_INCLUDE_DIR};${ZMQ_CPP_INCLUDE_DIR}\")\n\n  message(STATUS \"ZMQ_CPP_INCLUDE_DIR=${ZMQ_CPP_INCLUDE_DIR}\")\n\n  # Set variable in case we are using our own ZMQ C++ bindings\n  if(NOT \"${ZMQ_CPP_INCLUDE_DIR}\" STREQUAL \"${CMAKE_SOURCE_DIR}/utils\")\n    set_target_properties(ZMQ::ZMQ PROPERTIES\n      INTERFACE_COMPILE_DEFINITIONS HAVE_DEFAULT_ZMQ)\n  endif()\nendif()\n\nset(ZMQ_INCLUDE_DIRS ${ZMQ_CPP_INCLUDE_DIR})\nset(ZMQ_LIBRARIES ${ZMQ_LIBRARY})\nunset(ZMQ_CPP_INCLUDE_DIR)\nunset(ZMQ_INCLUDE_DIR)\nunset(ZMQ_LIBRARY)\n"
  },
  {
    "path": "cmake/Findabsl.cmake",
    "content": "# Try to find abseil library\n# Once done, this will define\n#\n# ABSL_FOUND          - system has absl library\n# ABSL_INCLUDE_DIRS   - absl include directories\n# ABSL_LIBRARIES      - libraries needed to use absl\n##\nfind_path(ABSL_INCLUDE_DIR\n  NAMES absl/base/config.h\n  HINTS /opt/eos/grpc ${ABSL_ROOT}\n  PATH_SUFFIXES include)\n\nset(libraries absl_synchronization absl_graphcycles_internal absl_stacktrace absl_symbolize absl_time absl_civil_time absl_time_zone\n  absl_malloc_internal absl_debugging_internal absl_demangle_internal absl_strings absl_int128\n  absl_strings_internal absl_base absl_spinlock_wait absl_throw_delegate absl_raw_logging_internal absl_log_severity\n  absl_log_internal_check_op absl_log_internal_message absl_cord_internal absl_cordz_info absl_cordz_sample_token\n  absl_cord absl_cord_functions absl_hash absl_status absl_log_internal_nullguard absl_cordz_functions)\n\nforeach( lib ${libraries})\n  find_library(ABSL_${lib}_LIBRARY\n    NAMES ${lib}\n    HINTS /opt/eos/grpc ${ABSL_ROOT}\n    PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\n  if(ABSL_${lib}_LIBRARY)\n    set(ABSL_${lib}_FOUND 1)\n    list(APPEND ABSL_LIBRARIES ${ABSL_${lib}_LIBRARY})\n    mark_as_advanced(ABSL_${lib}_LIBRARY)\n    message(VERBOSE \"ABSL_${lib}_LIBRARY\")\n  endif()\nendforeach()\n\nstring (REPLACE \";\" \" \" ABSL_LIBRARY \"${ABSL_LIBRARIES}\")\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(absl\n  REQUIRED_VARS ABSL_LIBRARY ABSL_INCLUDE_DIR)\n\nmark_as_advanced(ABSL_INCLUDE_DIR ABSL_LIBRARY)\nmessage(VERBOSE \"Abseil include path: ${ABSL_INCLUDE_DIR}\")\n\nif (ABSL_FOUND AND NOT TARGET ABSL::ABSL)\n  add_library(ABSL::ABSL UNKNOWN IMPORTED)\n  set_target_properties(ABSL::ABSL PROPERTIES\n    IMPORTED_LOCATION \"${ABSL_absl_base_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${ABSL_INCLUDE_DIR}\"\n    INTERFACE_LINK_LIBRARIES \"${ABSL_LIBRARIES}\")\n\n  include(CheckSymbolExists)\n  # Check grpc logging function\n  check_symbol_exists(gpr_set_log_function \"${ABLS_INCLUDE_DIR}\" HAVE_GRPC_LOGGING)\n\n  if (HAVE_GRPC_LOGGING)\n    message(STATUS \"Grpc internal logging!\")\n    targe_compile_definitions(ABLS::ABLS PUBLIC HAVE_GRPC_LOGGING)\n  else()\n    message(STATUS \"Grpc uses abseil logging!\")\n  endif()\nendif ()\n\nunset(ABSL_INCLUDE_DIR)\nunset(ABSL_LIBRARIES)\n"
  },
  {
    "path": "cmake/Findbz2.cmake",
    "content": "# Try to find bz2\n# Once done, this will define\n#\n# BZ2_FOUND          - system has bz2\n# BZ2_INCLUDE_DIRS   - bz2 include directories\n# BZ2_LIBRARIES        - bz2 library\n#\n# and the following imported target\n#\n# BZ2::BZ2\n\nfind_path(BZ2_INCLUDE_DIR\n  NAMES bzlib.h\n  HINTS ${BZ2_ROOT})\n\nfind_library(BZ2_LIBRARY\n  NAMES bz2\n  HINTS ${BZ2_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(bz2\n  DEFAULT_MSG BZ2_LIBRARY BZ2_INCLUDE_DIR)\n\nif (BZ2_FOUND AND NOT TARGET BZ2::BZ2)\n  mark_as_advanced(BZ2_FOUND BZ2_LIBRARY BZ2_INCLUDE_DIR)\n  add_library(BZ2::BZ2 UNKNOWN IMPORTED)\n  set_target_properties(BZ2::BZ2 PROPERTIES\n    IMPORTED_LOCATION \"${BZ2_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${BZ2_INCLUDE_DIR}\")\nendif()\n\nunset(BZ2_INCLUDE_DIR)\nunset(BZ2_LIBRARY)\n"
  },
  {
    "path": "cmake/Finddavix.cmake",
    "content": "# - Locate DAVIX library\n# Defines:\n#\n# DAVIX_FOUND         -  system has davix\n#\n# and the following imported targets\n#\n# DAVIX::DAVIX\n\nfind_path(DAVIX_INCLUDE_DIR\n  NAMES davix.hpp\n  HINTS\n  /usr ${DAVIX_ROOT} $ENV{DAVIX_ROOT}\n  PATH_SUFFIXES include/davix)\n\nfind_library(DAVIX_LIBRARY\n  NAMES davix\n  HINTS /usr ${DAVIX_ROOT} $ENV{DAVIX_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(davix\n  REQUIRED_VARS DAVIX_LIBRARY DAVIX_INCLUDE_DIR)\nmark_as_advanced(DAVIX_FOUND DAVIX_LIBRARY DAVIX_INCLUDE_DIR)\n\nif (DAVIX_FOUND AND NOT TARGET DAVIX::DAVIX)\n  add_library(DAVIX::DAVIX UNKNOWN IMPORTED)\n  set_target_properties(DAVIX::DAVIX PROPERTIES\n    IMPORTED_LOCATION \"${DAVIX_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${DAVIX_INCLUDE_DIR}\"\n    INTERFACE_COMPILE_DEFINITIONS HAVE_DAVIX)\nelse()\n  message(WARNING \"Notice: davix not found, no davix support\")\n  add_library(DAVIX::DAVIX INTERFACE IMPORTED)\nendif()\n\nunset(DAVIX_LIBRARY)\nunset(DAVIX_INCLUDE_DIR)\n"
  },
  {
    "path": "cmake/Findeosfolly.cmake",
    "content": "# Try to find eos folly.\n# Once done, this will define\n#\n# EOS_FOLLY_FOUND            - system has eos-folly\n# EOS_FOLLY_INCLUDE_DIRS     - eos-folly include directories\n# EOS_FOLLY_LIBRARIES        - eos-folly library library\n#\n# EOS_FOLLY_ROOT may be defined as a hint for where to look\n#\n# and the following imported targets\n#\n# FOLLY::FOLLY\n\nfind_path(EOSFOLLY_INCLUDE_DIR\n  NAMES folly/folly-config.h\n  HINTS ${EOSFOLLY_ROOT}\n  PATHS /opt/eos-folly /usr/local /usr\n  PATH_SUFFIXES include)\n\nfind_library(EOSFOLLY_LIBRARY\n  NAMES libfolly.so\n  HINTS ${EOSFOLLY_ROOT}\n  PATHS /opt/eos-folly /usr/local /usr\n  PATH_SUFFIXES lib lib64)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(eosfolly\n  REQUIRED_VARS EOSFOLLY_LIBRARY EOSFOLLY_INCLUDE_DIR)\n\nmark_as_advanced(EOSFOLLY_FOUND EOSFOLLY_LIBRARY EOSFOLLY_INCLUDE_DIR)\n\nif(EOSFOLLY_FOUND AND NOT TARGET FOLLY::FOLLY)\n  # Note: this target is not used for the moment as the folly lib is only\n  # liked directly into qclient.\n  add_library(FOLLY::FOLLY STATIC IMPORTED)\n  set_target_properties(FOLLY::FOLLY PROPERTIES\n    IMPORTED_LOCATION \"${EOSFOLLY_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${EOSFOLLY_INCLUDE_DIR}\"\n    INTERFACE_SYSTEM_INCLUDE_DIRECTORIES \"${EOSFOLLY_INCLUDE_DIR}\"\n    INTERFACE_COMPILE_DEFINITIONS HAVE_FOLLY)\nendif()\n\n# This is done to preserve compatibility with qclient\nset(FOLLY_INCLUDE_DIRS ${EOSFOLLY_INCLUDE_DIR})\nset(FOLLY_LIBRARIES    ${EOSFOLLY_LIBRARY} glog gflags)\nset(FOLLY_FOUND TRUE)\nunset(EOSFOLLY_LIBRARY)\nunset(EOSFOLLY_INCLUDE_DIR)\n"
  },
  {
    "path": "cmake/Findfuse.cmake",
    "content": "# Try to find fuse (devel)\n# Once done, this will define\n#\n# FUSE_FOUND        - system has fuse\n# FUSE_INCLUDE_DIRS - fuse include directories\n# FUSE_LIBRARIES    - libraries need to use fuse\n# FUSE_MOUNT_VERSION - major version reported by fusermount \n#\n# and the following imported target\n# FUSE::FUSE\n\nfind_package(PkgConfig)\npkg_check_modules(PC_fuse QUIET fuse)\nset(FUSE_VERSION ${PC_fuse_VERSION})\n\nfind_path(FUSE_INCLUDE_DIR\n  NAMES fuse/fuse_lowlevel.h\n  HINTS ${FUSE_ROOT} ${PC_fuse_INCLUDEDIR} ${PC_fuse_INCLUDE_DIRS}\n  PATH_SUFFIXES include include/osxfuse)\n\nif(MacOSX)\n  find_library(FUSE_LIBRARY\n    NAMES osxfuse\n    HINTS ${FUSE_ROOT} ${PC_fuse_LIBDIR} ${PC_fuse_LIBRARY_DIRS}\n    PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\nelse()\n  find_library(FUSE_LIBRARY\n    NAMES fuse\n    HINTS ${FUSE_ROOT} ${PC_fuse_LIBDIR} ${PC_fuse_LIBRARY_DIRS}\n    PATH_SUFFIXES$ ${CMAKE_INSTALL_LIBDIR})\n\n  execute_process(\n    COMMAND sh -c \"fusermount --version | cut -d ' ' -f 3 | cut -d '.' -f 1,2 | sed s/'\\\\.'//g\"\n    OUTPUT_VARIABLE FUSE_MOUNT_VERSION\n    OUTPUT_STRIP_TRAILING_WHITESPACE\n    RESULT_VARIABLE RETC)\n  if(NOT (\"${RETC}\" STREQUAL \"0\") )\n    set(${FUSE_MOUNT_VERSION} \"\" PARENT_SCOPE)\n  endif()\n\n  message(STATUS \"Setting FUSE_MOUNT_VERSION: ${FUSE_MOUNT_VERSION}\")  \nendif()\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(fuse\n  REQUIRED_VARS FUSE_LIBRARY FUSE_INCLUDE_DIR\n  VERSION_VAR FUSE_VERSION)\n\nif (FUSE_FOUND AND NOT TARGET FUSE::FUSE)\n  mark_as_advanced(FUSE_INCLUDE_DIR FUSE_LIBRARY)\n  add_library(FUSE::FUSE UNKNOWN IMPORTED)\n  set_target_properties(FUSE::FUSE PROPERTIES\n    IMPORTED_LOCATION \"${FUSE_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${FUSE_INCLUDE_DIR}\")\nendif()\n\nset(FUSE_INCLUDE_DIRS ${FUSE_INCLUDE_DIR})\nset(FUSE_LIBRARIES ${FUSE_LIBRARY})\nunset(FUSE_INCLDUE_DIR)\nunset(FUSE_LIBRARY)\n"
  },
  {
    "path": "cmake/Findfuse3.cmake",
    "content": "# Try to find fuse (devel)\n# Once done, this will define\n#\n# FUSE3_FOUND - system has fuse\n#\n# and the following imported target\n#\n# FUSE3::FUSE3\n\nfind_path(FUSE3_INCLUDE_DIR\n  NAMES fuse3/fuse_lowlevel.h\n  HINTS ${FUSE3_ROOT})\n\nfind_library(FUSE3_LIBRARY\n  NAMES fuse3\n  HINTS ${FUSE3_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(fuse3\n  REQUIRED_VARS FUSE3_LIBRARY FUSE3_INCLUDE_DIR)\n\nif (FUSE3_FOUND AND NOT TARGET FUSE3::FUSE3)\n  mark_as_advanced(FUSE3_INCLUDE_DIR FUSE3_LIBRARY)\n  add_library(FUSE3::FUSE3 UNKNOWN IMPORTED)\n  set_target_properties(FUSE3::FUSE3 PROPERTIES\n    IMPORTED_LOCATION \"${FUSE3_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${FUSE3_INCLUDE_DIR}\"\n    INTERFACE_COMPILE_DEFINITIONS \"USE_FUSE3=1\")\nelse()\n  message(WARNING \"Notice: fuse3 not found, no fuse3 support\")\n  add_library(FUSE3::FUSE3 INTERFACE IMPORTED)\nendif()\n\nunset(FUSE3_INCLUDE_DIR)\nunset(FUSE3_LIBRARY)\n"
  },
  {
    "path": "cmake/Findglibc.cmake",
    "content": "# Try to find glibc-devel\n# Once done, this will define\n#\n# GLIBC_FOUND      - system has glibc-devel\n# and the following imported targets\n#\n# GLIBC::DL\n# GLIBC::RT\n\nfind_library(GLIBC_DL_LIBRARY\n  NAMES dl\n  HINTS ${GLIBC_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\nfind_library(GLIBC_RT_LIBRARY\n  NAMES rt\n  HINTS ${GLIBC_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\n# Math library\nfind_library(GLIBC_M_LIBRARY\n  NAMES m\n  HINTS ${GLIBC_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(glibc\n  REQUIRED_VARS GLIBC_DL_LIBRARY GLIBC_RT_LIBRARY GLIBC_M_LIBRARY)\nmark_as_advanced(GLIBC_DL_LIBRARY GLIBC_RT_LIBRARY GLIBC_M_LIBRARY)\n\nif (GLIBC_FOUND AND NOT TARGET GLIBC::DL)\n  add_library(GLIBC::DL UNKNOWN IMPORTED)\n  set_target_properties(GLIBC::DL PROPERTIES\n    IMPORTED_LOCATION \"${GLIBC_DL_LIBRARY}\")\n  add_library(GLIBC::RT UNKNOWN IMPORTED)\n  set_target_properties(GLIBC::RT PROPERTIES\n    IMPORTED_LOCATION \"${GLIBC_RT_LIBRARY}\")\n  add_library(GLIBC::M UNKNOWN IMPORTED)\n  set_target_properties(GLIBC::M PROPERTIES\n    IMPORTED_LOCATION \"${GLIBC_M_LIBRARY}\")\nendif()\n\nunset(GLIBC_DL_LIBRARY)\nunset(GLIBC_RT_LIBRARY)\nunset(GLIBC_M_LIBRARY)\n"
  },
  {
    "path": "cmake/Findhelp2man.cmake",
    "content": "# Locate help2man executable\n# Defines:\n#\n#  HELP2MAN_FOUND        -  system has help2man\n#  HELP2MAN_EXECUTABLE   -  help2man executable\n\ninclude(FindPackageHandleStandardArgs)\n\nfind_program(HELP2MAN_EXECUTABLE\n  NAMES help2man\n  HINTS /usr $ENV{HELP2MAN_DIR}\n  PATH_SUFFIXES bin)\n\nfind_package_handle_standard_args(\n  help2man\n  DEFAULT_MSG\n  HELP2MAN_EXECUTABLE)\n\nmark_as_advanced(HELP2MAN_EXECUTABLE)\n"
  },
  {
    "path": "cmake/Findisal.cmake",
    "content": "# Try to find libisa-l (devel)\n# Once done, this will define\n#\n# ISAL_FOUND          - system has isa-l\n# ISAL_INCLUDE_DIRS   - isa-l include directories\n# ISAL_LIBRARIES      - isa-l library\n# ISAL_LIBRARY_STATIC - isa-l static library\n#\n# and the following imported targets\n#\n# ISAL::ISAL\n# ISAL::ISAL_STATIC\n\nfind_path(ISAL_INCLUDE_DIR\n  NAMES isa-l.h\n  HINTS ${ISAL_ROOT}\n  PATH_SUFFIXES include)\n\nfind_library(ISAL_LIBRARY\n  NAME isal\n  HINTS ${ISAL_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(isal\n  REQUIRED_VARS ISAL_LIBRARY ISAL_INCLUDE_DIR)\nmark_as_advanced(ISAL_LIBRARY ISAL_INCLUDE_DIR)\n\nif (ISAL_FOUND AND NOT TARGET ISAL::ISAL)\n  add_library(ISAL::ISAL UNKNOWN IMPORTED)\n  set_target_properties(ISAL::ISAL PROPERTIES\n    IMPORTED_LOCATION \"${ISAL_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${ISAL_INCLUDE_DIR}\")\n  target_compile_definitions(ISAL::ISAL INTERFACE ISAL_FOUND)\nelse()\n  message(WARNING \"Notice: ISAL not found, no ISAL support\")\n  add_library(ISAL::ISAL INTERFACE IMPORTED)\nendif()\n\nunset(ISAL_INCLUDE_DIR)\nunset(ISAL_LIBRARY)\n"
  },
  {
    "path": "cmake/Findisal_crypto.cmake",
    "content": "# Try to find libisa-l_crypto (devel)\n# Once done, this will define\n#\n# ISALCRYPTO_FOUND          - system has isa-l_crypto\n# ISALCRYPTO_INCLUDE_DIRS   - the isa-l_crypto include directories\n# ISALCRYPTO_LIBRARIES      - isa-l_crypto libraries directories\n# ISALCRYPTO_LIBRARY_STATIC - isa-l_crypto static library\n#\n# and the following imported targets\n#\n# ISAL::ISAL_CRYPTO\n# ISAL::ISAL_CRYPTO_STATIC\n\nfind_path(ISAL_CRYPTO_INCLUDE_DIR\n  NAMES isa-l_crypto.h\n  HINTS ${ISAL_CRYPTO_ROOT}\n  PATH_SUFFIXES include)\n\nfind_library(ISAL_CRYPTO_LIBRARY\n  NAME isal_crypto\n  HINTS ${ISAL_CRYPTO_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(isal_crypto\n  REQUIRED_VARS ISAL_CRYPTO_LIBRARY ISAL_CRYPTO_INCLUDE_DIR)\nmark_as_advanced(ISAL_CRYPTO_LIBRARY ISAL_CRYPTO_INCLUDE_DIR)\n\nif (ISAL_CRYPTO_FOUND AND NOT TARGET ISAL::ISAL_CRYPTO)\n  add_library(ISAL::ISAL_CRYPTO UNKNOWN IMPORTED)\n  set_target_properties(ISAL::ISAL_CRYPTO PROPERTIES\n    IMPORTED_LOCATION \"${ISAL_CRYPTO_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${ISAL_CRYPTO_INCLUDE_DIR}\")\n  target_compile_definitions(ISAL::ISAL_CRYPTO INTERFACE ISALCRYPTO_FOUND)\nelse()\n  message(WARNING \"Notice: ISAL_CRYPTO not found, no ISAL_CRYPTO support\")\n  add_library(ISAL::ISAL_CRYPTO INTERFACE IMPORTED)\nendif()\n\nunset(ISAL_CRYPTO_INCLUDE_DIR)\nunset(ISAL_CRYPTO_LIBRARY)\n"
  },
  {
    "path": "cmake/Findjemalloc.cmake",
    "content": "# Try to find jemalloc\n# Once done, this will define\n#\n# JEMALLOC_FOUND          - system has jemalloc\n# JEMALLOC_INCLUDE_DIRS   - jemalloc include directories\n# JEMALLOC_LIBRARIES      - libraries needed to use jemalloc\n#\n# and the following imported targets\n#\n# JEMALLOC::JEMALLOC\n#\nfind_path(JEMALLOC_INCLUDE_DIR\n  NAMES jemalloc.h\n  HINTS ${JEMALLOC_ROOT}\n  PATH_SUFFIXES include jemalloc)\n\nfind_library(JEMALLOC_LIBRARY\n  NAME jemalloc\n  HINTS ${JEMALLOC_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(jemalloc\n  REQUIRED_VARS JEMALLOC_LIBRARY JEMALLOC_INCLUDE_DIR)\n\nmark_as_advanced(JEMALLOC_FOUND JEMALLOC_INCLUDE_DIR JEMALLOC_LIBRARY)\nmessage(STATUS \"Jemalloc include path: ${JEMALLOC_INCLUDE_DIR}\")\n\nif (JEMALLOC_FOUND AND NOT TARGET JEMALLOC::JEMALLOC)\n  add_library(JEMALLOC::JEMALLOC UNKNOWN IMPORTED)\n  set_target_properties(JEMALLOC::JEMALLOC PROPERTIES\n    IMPORTED_LOCATION \"${JEMALLOC_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${JEMALLOC_INCLUDE_DIR}\")\nelse()\n  message(WARNING \"Notice: jemalloc not found, no jemalloc support\")\n  add_library(JEMALLOC::JEMALLOC INTERFACE IMPORTED)\nendif()\n"
  },
  {
    "path": "cmake/Findjsoncpp.cmake",
    "content": "# Try to find libjsoncpp\n# Once done, this will define\n#\n# JSONCPP_FOUND         - system has jsoncpp\n# JSONCPP_INCLUDE_DIRS  - the jsoncpp include directories\n# JSONCPP_LIBRARIES     - libaries needed to use jsoncpp\n#\n# and the following imported target\n#\n# JSONCPP::JSONCPP\n\nfind_package(PkgConfig)\npkg_check_modules(PC_JSONCPP QUIET jsoncpp)\nset(JSONCPP_VERSION ${PC_JSONCPP_VERSION})\n\nfind_path(JSONCPP_INCLUDE_DIR\n  NAMES json/json.h\n  HINTS ${JSONCPP_ROOT}\n  PATH_SUFFIXES jsoncpp )\n\nfind_library(JSONCPP_LIBRARY\n  NAMES jsoncpp\n  HINTS ${JSONCPP_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(jsoncpp\n  REQUIRED_VARS JSONCPP_INCLUDE_DIR JSONCPP_LIBRARY\n  VERSION_VAR JSON_VERSION)\nmark_as_advanced(JSONCPP_INCLUDE_DIR JSONCPP_LIBRARY)\n\nif (JSONCPP_FOUND AND NOT TARGET JSONCPP::JSONCPP)\n  add_library(JSONCPP::JSONCPP UNKNOWN IMPORTED)\n  set_target_properties(JSONCPP::JSONCPP PROPERTIES\n    IMPORTED_LOCATION \"${JSONCPP_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${JSONCPP_INCLUDE_DIR}\")\nendif()\n\nset(JSONCPP_INCLUDE_DIRS ${JSONCPP_INCLUDE_DIR})\nset(JSONCPP_LIBRARIES ${JSONCPP_LIBRARY})\nunset(JSONCPP_INCLUDE_DIR)\nunset(JSONCP_LIBRARY)\n"
  },
  {
    "path": "cmake/Findkrb5.cmake",
    "content": "#  Try to find Kerberos5\n#  Check for libkrb5.a\n#\n#  KRB5_FOUND       - True if Kerberos 5 libraries found.\n#  KRB5_INCLUDE_DIR - where to find krb5.h, etc.\n#  KRB5_LIBRARIES   - List of libraries needed to use krb5\n#\n# and the following imported targets\n#\n# KRB5::KRB5\n\nfind_package(PkgConfig)\npkg_check_modules(PC_krb5 QUIET krb5)\n\nfind_path(KRB5_INCLUDE_DIR\n  NAMES krb5/krb5.h\n  HINTS ${KRB5_ROOT} ${PC_krb5_INCLUDEDIR} ${PC_kbr5_INCLUDE_DIRS})\n\nfind_library(KRB5_MIT_LIBRARY\n  NAMES k5crypto\n  HINTS ${KRB5_ROOT} ${PC_krb5_LIBDIR} ${PC_krb5_LIBRARY_DIRS})\n\nfind_library(KRB5_LIBRARY\n  NAMES krb5\n  HINTS ${KRB5_ROOT} ${PC_krb5_LIBDIR} ${PC_krb5_LIBRARY_DIRS})\n\nfind_program(KRB5_INIT NAMES kinit\n  HINTS ${KRB5_ROOT} /usr/bin/ /usr/local/bin)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(krb5\n  REQUIRED_VARS KRB5_LIBRARY KRB5_INCLUDE_DIR KRB5_MIT_LIBRARY)\nmark_as_advanced(KRB5_INCLUDE_DIR KRB5_MIT_LIBRARY KRB4_MIT_LIBRARY)\n\nif (KRB5_FOUND AND NOT TARGET KRB5::KRB5)\n  add_library(KRB5::KRB5 UNKNOWN IMPORTED)\n  set_target_properties(KRB5::KRB5 PROPERTIES\n    IMPORTED_LOCATION \"${KRB5_LIBRARY}\"\n    INTERFACE_LINK_LIBRARIES \"${KRB5_MIT_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${KRB5_INCLUDE_DIR}\")\nendif()\n\nset(KRB5_INCLUDE DIRS ${KRB5_INCLUDE_DIR})\nset(KRB5_LIBRARIES ${KRB5_LIBRARY} ${KRB5_MIT_LIBRARY})\nunset(KRB5_INCLUDE_DIR)\nunset(KRB5_LIBRARY)\nunset(KRB5_MIT_LIBRARY)\n"
  },
  {
    "path": "cmake/Findldap.cmake",
    "content": "# Try to find attr\n# Once done, this will define\n#\n# LDAP_FOUND        - system has libldap\n# LDAP_INCLUDE_DIRS - ldap include directories\n# LDAP_LIBRARIES    - ldap libraries directories\n#\n# and the following imported target\n#\n# LDAP::LDAP\n\nfind_path(LDAP_INCLUDE_DIR\n  NAMES ldap.h\n  HINTS ${LDAP_ROOT}\n  PATH_SUFFIXES include)\n\nfind_library(LDAP_LIBRARY\n  NAMES ldap\n  HINTS ${LDAP_ROOT}\n  PATH_SUFFIEX ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(ldap\n  REQUIRED_VARS LDAP_LIBRARY LDAP_INCLUDE_DIR)\nmark_as_advanced(LDAP_LIBRARY LDAP_INCLUDE_DIR)\n\nif (LDAP_FOUND AND NOT TARGET LDAP::LDAP)\n  add_library(LDAP::LDAP UNKNOWN IMPORTED)\n  set_target_properties(LDAP::LDAP PROPERTIES\n    IMPORTED_LOCATION \"${LDAP_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${LDAP_INCLUDE_DIR}\")\nendif()\n\nset(LDAP_INCLUDE_DIRS ${LDAP_INCLUDE_DIR})\nset(LDAP_LIBRARIES ${LDAP_LIBRARY})\n"
  },
  {
    "path": "cmake/Findlibbfd.cmake",
    "content": "# Try to find libbfd\n# Once done, this will define\n#\n# LIBBFD_FOUND               - system has libbfd\n# LIBBFD_INCLUDE_DIRS        - libbfd include directories\n# LIBBFD_LIBRARIES           - libbfd library\n#\n# LIBBFD_ROOT_DIR may be defined as a hint for where to look\n\nfind_path(LIBBFD_INCLUDE_DIR\n  NAMES bfd.h\n  HINTS /opt/rh/devtoolset-8/root ${LIBBFD_ROOT}\n  PATH_SUFFIXES include usr/include)\n\nfind_library(LIBBFD_LIBRARY\n  NAMES bfd\n  HINTS /opt/rh/devtoolset-8/root ${LIBBFD_ROOT}\n  PATH_SUFFIXES lib lib64)\n\nfind_library(LIBIBERTY_LIBRARY\n  NAMES iberty\n  HINTS /opt/rh/devtoolset-8/root ${LIBBFD_ROOT}\n  PATH_SUFFIXES lib lib64)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(libbfd\n  REQUIRE_VARS LIBBFD_LIBRARY LIBIBERTY_LIBRARY LIBBFD_INCLUDE_DIR)\n\nif (LIBBFD_FOUND AND NOT TARGET LIBBFD::LIBBFD)\n  add_library(LIBBFD::LIBBFD UNKNOWN IMPORTED)\n  set_target_properties(LIBBFD::LIBBFD PROPERTIES\n    IMPORTED_LOCATION \"${LIBBFD_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${LIBBFD_INCLUDE_DIR}\")\n  add_library(LIBBFD::IBERTY UNKNOWN IMPORTED)\n  set_target_properties(LIBBFD::IBERTY PROPERTIES\n    IMPORTED_LOCATION \"${LIBIBERTY_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${LIBBFD_INCLUDE_DIR}\")\nelse()\n  message(WARNING \"Notice: libbfd not found, no libbfd support\")\n  add_library(LIBBFD::LIBBFD INTERFACE IMPORTED)\n  add_library(LIBBFD::IBERTY INTERFACE IMPORTED)\nendif()\n\nunset(LIBBFD_INCLUDE_DIR)\nunset(LIBBFD_LIBRARY)\nunset(LIBIBERTY_LIBRARY)\n"
  },
  {
    "path": "cmake/Findlibproc2.cmake",
    "content": "#.rst:\n# Findlibproc2\n# -------\n#\n# Find libproc2 from procps 4.x.\n#\n# Imported Targets\n# ^^^^^^^^^^^^^^^^\n#\n# This module defines :prop_tgt:`IMPORTED` target:\n#\n# ``procps::libproc2``\n#   The libproc2 library, if found.\n#\n# Result Variables\n# ^^^^^^^^^^^^^^^^\n#\n# This module will set the following variables in your project:\n#\n# ``LIBPROC2_FOUND``\n#   True if libproc2 has been found.\n# ``LIBPROC2_INCLUDE_DIRS``\n#   Where to find libproc2/misc.h, etc.\n# ``LIBPROC2_LIBRARIES``\n#   The libraries to link against to use libproc2.\n# ``LIBPROC2_VERSION``\n#   The version of the libproc2 library found (e.g. 4.0.2)\n#\n\nfind_package(PkgConfig QUIET)\n\nif(PKG_CONFIG_FOUND)\n  if(${libproc2_FIND_REQUIRED})\n    set(LIBPROC2_REQUIRED REQUIRED)\n  endif()\n\n  if(NOT DEFINED LIBPROC2_FIND_VERSION)\n    pkg_check_modules(LIBPROC2 ${LIBPROC2_REQUIRED} libproc2)\n  else()\n    pkg_check_modules(LIBPROC2 ${LIBPROC2_REQUIRED} libproc2>=${LIBPROC2_FIND_VERSION})\n  endif()\n\n  set(LIBPROC2_LIBRARIES ${LIBPROC2_LDFLAGS})\n  set(LIBPROC2_LIBRARY ${LIBPROC2_LIBRARIES})\n  set(LIBPROC2_INCLUDE_DIRS ${LIBPROC2_INCLUDE_DIRS})\n  set(LIBPROC2_INCLUDE_DIR ${LIBPROC2_INCLUDE_DIRS})\nelse ()\n  if (DEFINED LIBPROC2_FIND_VERSION)\n    find_program(PS_BIN ps REQUIRED)\n    message(DEBUG \"info: found ps binary in ${PS_BIN}\")\n\n    if (PS_BIN)\n      execute_process(COMMAND sh -c \"${PS_BIN} -V | cut -d ' ' -f 4\"\n        OUTPUT_VARIABLE LIBPROC2_VER\n        OUTPUT_STRIP_TRAILING_WHITESPACE\n        RESULT_VARIABLE RETC)\n\n      if (NOT (\"${RETC}\" STREQUAL \"0\"))\n        message(FATAL_ERROR \"error: failed while calling ${PS_BIN} to get version\")\n      endif()\n\n      message(DEBUG \"info: ps version is ${LIBPROC2_VER}\")\n\n      if (NOT \"${LIBPROC2_VER}\" VERSION_GREATER_EQUAL \"${LIBPROC2_FIND_VERSION}\")\n        message(FATAL_ERROR\n          \"error: procps version ${LIBPROC2_VER} less than \"\n          \"requested ${LIBPROC_FIND_VERSION}\")\n      endif()\n    else ()\n      message(FATAL_ERROR \"error: could not find ps binary\")\n    endif()\n  endif()\n\n  find_path(LIBPROC2_INCLUDE_DIR\n    NAMES libproc2/pids.h\n    HINTS ${LIBPROC2_ROOT}\n    PATH_SUFFIXES include)\n\n  find_library(LIBPROC2_LIBRARY\n    NAMES proc2\n    HINTS ${LIBPROC2_ROOT}\n    PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\n  set(LIBPROC2_LIBRARIES ${LIBPROC2_LIBRARY})\n  set(LIBPROC2_INCLUDE_DIRS ${LIBPROC2_INCLUDE_DIR})\nendif()\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(libproc2\n  REQUIRED_VARS LIBPROC2_LIBRARY LIBPROC2_INCLUDE_DIR)\n\nmark_as_advanced(LIBPROC2_INCLUDE_DIR LIBPROC2_LIBRARY)\n\nif(LIBPROC2_FOUND AND NOT TARGET procps::libproc2)\n  add_library(procps::libproc2 INTERFACE IMPORTED)\n  set_property(TARGET procps::libproc2 PROPERTY INTERFACE_INCLUDE_DIRECTORIES \"${LIBPROC2_INCLUDE_DIRS}\")\n  set_property(TARGET procps::libproc2 PROPERTY INTERFACE_LINK_LIBRARIES \"${LIBPROC2_LIBRARIES}\")\nendif()\n\nmessage(DEBUG \"LIBPROC2_FOUND = ${LIBPROC2_FOUND}\")\nmessage(DEBUG \"LIBPROC2_VERSION = ${LIBPROC2_VERSION}\")\nmessage(DEBUG \"LIBPROC2_INCLUDE_DIRS = ${LIBPROC2_INCLUDE_DIRS}\")\nmessage(DEBUG \"LIBPROC2_LIBRARIES = ${LIBPROC2_LIBRARIES}\")\nmessage(DEBUG \"libproc2_FIND_REQUIRED = ${libproc2_FIND_REQUIRED}\")\n"
  },
  {
    "path": "cmake/Findlibunwind.cmake",
    "content": "# Find the libunwind library\n#\n#  LIBUNWIND_FOUND       - True if libunwind was found.\n#  LIBUNWIND_LIBRARIES   - The libraries needed to use libunwind\n#  LIBUNWIND_INCLUDE_DIR - Location of unwind.h and libunwind.h\n\n#  INPUT: LIBUNWIND_ROOT - path where include + lib of libunwind installation is located\n \nFIND_PATH(LIBUNWIND_INCLUDE_DIR libunwind.h\n          HINTS\n          /usr/\n          PATH_SUFFIXES include\n          PATHS \"${LIBUNWIND_ROOT}/include\"\n)\n \nFIND_LIBRARY(LIBUNWIND_GENERIC_LIBRARY libunwind.a unwind\n             HINTS\n             /usr/\n             PATH_SUFFIXES lib \n             PATHS \"${LIBUNWIND_ROOT}/lib\"\n)\n\nif (LIBUNWIND_INCLUDE_DIR) \n# nothing\nif (LIBUNWIND_GENERIC_LIBRARY) \n  SET(LIBUNWIND_LIBRARIES ${LIBUNWIND_GENERIC_LIBRARY})\n\n# For some reason, we have to link to two libunwind shared object files:\n# one arch-specific and one not.\nif (CMAKE_SYSTEM_PROCESSOR MATCHES \"^arm\")\n    SET(LIBUNWIND_ARCH \"arm\")\nelseif (CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86_64\" OR CMAKE_SYSTEM_PROCESSOR STREQUAL \"amd64\")\n    SET(LIBUNWIND_ARCH \"x86_64\")\nelseif (CMAKE_SYSTEM_PROCESSOR MATCHES \"^i.86$\")\n    SET(LIBUNWIND_ARCH \"x86\")\nendif()\n\nif (LIBUNWIND_ARCH)\n    FIND_LIBRARY(LIBUNWIND_SPECIFIC_LIBRARY libunwind-${LIBUNWIND_ARCH}.a \"unwind-${LIBUNWIND_ARCH}\"\n                 HINTS\n                 /usr/\n\t         PATH_SUFFIXES lib\n                 PATHS \"${LIBUNWIND_ROOT}\"\n)\n    if (NOT LIBUNWIND_SPECIFIC_LIBRARY)\n        MESSAGE(FATAL_ERROR \"failed to find unwind-${LIBUNWIND_ARCH}\")\n    endif ()\n    SET(LIBUNWIND_LIBRARIES ${LIBUNWIND_LIBRARIES} ${LIBUNWIND_SPECIFIC_LIBRARY})\nendif(LIBUNWIND_ARCH)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(libunwind DEFAULT_MSG LIBUNWIND_INCLUDE_DIR)\n\nMARK_AS_ADVANCED(LIBUNWIND_LIBRARIES LIBUNWIND_INCLUDE_DIR)\n\nelse (LIBUNWIND_GENERIC_LIBRARY)\nMESSAGE(\"-- Could NOT find Libunwind \")\nendif (LIBUNWIND_GENERIC_LIBRARY)\nelse (LIBUNWIND_INCLUDE_DIR)\nMESSAGE(\"-- Could NOT find libunwind.h\")\nendif (LIBUNWIND_INCLUDE_DIR)\n\n"
  },
  {
    "path": "cmake/Findlz4.cmake",
    "content": "# Try to find lz4\n# Once done, this will define\n#\n# LZ4_FOUND          - system has bz2\n# LZ4_INCLUDE_DIRS   - bz2 include directories\n# LZ4_LIBRARIES        - bz2 library\n#\n# and the following imported target\n#\n# LZ4::LZ4\n\nfind_library(LZ4_LIBRARY\n  NAMES liblz4.so.1 liblz4.1.dylib\n  HINTS ${LZ4_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(lz4 DEFAULT_MSG LZ4_LIBRARY)\n\nif (LZ4_FOUND AND NOT TARGET LZ4::LZ4)\n  mark_as_advanced(LZ4_FOUND LZ4_LIBRARY)\n  add_library(LZ4::LZ4 UNKNOWN IMPORTED)\n  set_target_properties(LZ4::LZ4 PROPERTIES\n    IMPORTED_LOCATION \"${LZ4_LIBRARY}\")\nendif()\n\nunset(LZ4_LIBRARY)\n"
  },
  {
    "path": "cmake/Findncurses.cmake",
    "content": "# Try to find libncurses\n# Once done, this will define\n#\n# NCURSES_FOUND           - system has libncurses\n# NCURSES_INCLUDE_DIRS    - libncurses include directories\n# NCURSES_LIBRARIES       - ncurses library\n#\n# and the following imported targets\n#\n# NCURSES::NCURSES\n\nfind_package(PkgConfig)\npkg_check_modules(PC_ncurses QUIET ncurses)\nset(NCURSES_VERSION ${PC_ncurses_VERSION})\n\nfind_path(NCURSES_INCLUDE_DIR\n  NAMES curses.h\n  HINTS ${NCURSES_ROOT} ${PC_ncurses_INCLUDEDIR} ${PC_ncurses_INCLUDE_DIRS}\n  PATH_SUFFIXES include)\n\nfind_library(NCURSES_LIBRARY\n  NAMES ncurses\n  HINTS ${NCURSES_ROOT} ${PC_ncurses_LIBDIR} ${PC_ncurses_LIBRARY_DIRS}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(ncurses\n  REQUIRED_VARS NCURSES_LIBRARY NCURSES_INCLUDE_DIR\n  VERSION_VAR NCURSES_VERSION)\n\nmark_as_advanced(NCURSES_FOUND NCURSES_LIBRARY NCURSES_INCLUDE_DIR)\n\nif (NCURSES_FOUND AND NOT TARGET NCURSES::NCURSES)\n  add_library(NCURSES::NCURSES UNKNOWN IMPORTED)\n  set_target_properties(NCURSES::NCURSES PROPERTIES\n    IMPORTED_LOCATION \"${NCURSES_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${NCURSES_INCLUDE_DIR}\")\nendif()\n\nset(NCURSES_INCLUDE_DIRS ${NCURSES_INCLUDE_DIR})\nset(NCURSES_LIBRARIES ${NCURSES_LIBRARY})\nunset(NCURSES_INCLUDE_DIR)\nunset(NCURSES_LIBRARY)\n"
  },
  {
    "path": "cmake/Findnfs.cmake",
    "content": "# - Locate NFS library\n# Defines:\n#\n# NFS_FOUND         -  system has libnfs\n#\n# and the following imported targets\n#\n# NFS::NFS\n\nfind_path(NFS_INCLUDE_DIR\n  NAMES nfsc/libnfs.h\n  HINTS\n  /usr ${NFS_ROOT} $ENV{NFS_ROOT}\n  PATH_SUFFIXES include)\n\nfind_library(NFS_LIBRARY\n  NAMES nfs\n  HINTS /usr ${NFS_ROOT} $ENV{NFS_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(nfs\n  REQUIRED_VARS NFS_LIBRARY NFS_INCLUDE_DIR)\nmark_as_advanced(nfs_FOUND NFS_LIBRARY NFS_INCLUDE_DIR)\n\nif (nfs_FOUND AND NOT TARGET NFS::NFS)\n  add_library(NFS::NFS UNKNOWN IMPORTED)\n  set_target_properties(NFS::NFS PROPERTIES\n    IMPORTED_LOCATION \"${NFS_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${NFS_INCLUDE_DIR}\"\n    INTERFACE_COMPILE_DEFINITIONS HAVE_NFS)\nelse()\n  message(WARNING \"Notice: libnfs not found, no NFS support\")\n  add_library(NFS::NFS INTERFACE IMPORTED)\nendif()\n\nunset(NFS_LIBRARY)\nunset(NFS_INCLUDE_DIR)\n"
  },
  {
    "path": "cmake/Findprocps.cmake",
    "content": "# Try to find procps\n# Once done, this will define\n#\n# PROCPS_FOUND        - system has uuid\n# PROCPS_INCLUDE_DIRS - uuid include directories\n# PROCPS_LIBRARIES    - libraries needed to use uuid\n#\n# and the following imported target\n#\n# PROCPS::PROCPS\n\nfind_package(PkgConfig)\npkg_check_modules(PC_procps QUIET libprocps)\nset(PROCPS_VERSION ${PC_procps_VERSION})\n\nfind_path(PROCPS_INCLUDE_DIR\n  NAMES readproc.h\n  HINTS ${PROCPS_ROOT} ${PC_procps_INCLUDEDIR} ${PC_procps_INCLUDE_DIRS}\n  PATH_SUFFIXES include proc)\n\nfind_library(PROCPS_LIBRARY\n  NAMES procps\n  HINTS ${PROCPS_ROOT} ${PC_procps_LIBDIR} ${PC_procps_LIBRARY_DIRS}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(procps\n  REQUIRED_VARS PROCPS_LIBRARY PROCPS_INCLUDE_DIR\n  VERSION_VAR PROCPS_VERSION)\n\nif (PROCPS_FOUND AND NOT TARGET PROCPS::PROCPS)\n  mark_as_advanced(PROCPS_FOUND PROCPS_INCLUDE_DIR PROCPS_LIBRARY)\n  add_library(PROCPS::PROCPS UNKNOWN IMPORTED)\n  set_target_properties(PROCPS::PROCPS PROPERTIES\n    IMPORTED_LOCATION \"${PROCPS_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${PROCPS_INCLUDE_DIR}\")\nendif()\n\nunset(PROCPS_INCLUDE_DIR)\nunset(PROCPS_LIBRARY)\n"
  },
  {
    "path": "cmake/Findreadline.cmake",
    "content": "# Try to find libreadline\n# Once done, this will define\n#\n# READLINE_FOUND        - system has readline\n# READLINE_INCLUDE_DIRS - readline include directories\n# READLINE_LIBRARIES    - libraries need to use readline\n#\n# and the following imported targets\n#\n# READLINE::READLINE\n\nfind_path(READLINE_INCLUDE_DIR\n  NAMES readline/readline.h\n  HINTS ${READLINE_ROOT})\n\nfind_library(READLINE_LIBRARY\n  NAMES readline\n  HINTS ${READLINE_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(readline\n  REQUIRED_VARS READLINE_LIBRARY READLINE_INCLUDE_DIR)\n\nmark_as_advanced(READLINE_FOUND READLINE_LIBRARY READLINE_INCLUDE_DIR)\n\nif (READLINE_FOUND AND NOT TARGET READLINE::READLINE)\n  add_library(READLINE::READLINE UNKNOWN IMPORTED)\n  set_target_properties(READLINE::READLINE PROPERTIES\n    IMPORTED_LOCATION \"${READLINE_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${READLINE_INCLUDE_DIR}\")\nendif()\n\nset(READLINE_INCLUDE_DIRS ${READLINE_INCLUDE_DIR})\nset(READLINE_LIBRARIES ${READLINE_LIBRARY})\n"
  },
  {
    "path": "cmake/Finduuid.cmake",
    "content": "# Try to find uuid\n# Once done, this will define\n#\n# UUID_FOUND        - system has uuid\n# UUID_INCLUDE_DIRS - uuid include directories\n# UUID_LIBRARIES    - libraries needed to use uuid\n#\n# and the following imported target\n#\n# UUID::UUID\n\nfind_package(PkgConfig)\npkg_check_modules(PC_uuid QUIET uuid)\nset(UUID_VERSION ${PC_uuid_VERSION})\n\nfind_path(UUID_INCLUDE_DIR\n  NAMES uuid.h\n  HINTS ${UUID_ROOT} ${PC_uuid_INCLUDEDIR} ${PC_uuid_INCLUDE_DIRS}\n  PATH_SUFFIXES include uuid)\n\nfind_library(UUID_LIBRARY\n  NAMES uuid\n  HINTS ${UUID_ROOT} ${PC_uuid_LIBDIR} ${PC_uuid_LIBRARY_DIRS}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(uuid\n  REQUIRED_VARS UUID_LIBRARY UUID_INCLUDE_DIR\n  VERSION_VAR UUID_VERSION)\n\nif (UUID_FOUND AND NOT TARGET UUID::UUID)\n  mark_as_advanced(UUID_FOUND UUID_INCLUDE_DIR UUID_LIBRARY)\n  add_library(UUID::UUID UNKNOWN IMPORTED)\n  set_target_properties(UUID::UUID PROPERTIES\n    IMPORTED_LOCATION \"${UUID_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${UUID_INCLUDE_DIR}\")\nendif()\n\nset(UUID_INCLUDE_DIRS ${UUID_INCLUDE_DIR})\nset(UUID_LIBRARIES ${UUID_LIBRARY})\nunset(UUID_INCLUDE_DIR)\nunset(UUID_LIBRARY)\n"
  },
  {
    "path": "cmake/Findxfs.cmake",
    "content": "# Try to find xfs\n# Once done, this will define\n#\n# XFS_FOUND        - system has xfs\n# XFS_INCLUDE_DIRS - xfs include directories\n#\n# and the following imported target\n#\n# XFS::XFS\n\nfind_path(XFS_INCLUDE_DIR\n  NAMES xfs/xfs.h\n  HINTS ${XFS_ROOT}\n  PATH_SUFFIXES include)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(xfs\n  DEFAULT_MSG XFS_INCLUDE_DIR)\nmark_as_advanced(XFS_INCLUDE_DIR)\n\nif (XFS_FOUND AND NOT TARGET XFS::XFS)\n  add_library(XFS::XFS INTERFACE IMPORTED)\n  set_target_properties(XFS::XFS PROPERTIES\n    INTERFACE_INCLUDE_DIRECTORIES \"${XFS_INCLUDE_DIR}\")\nendif()\n\nset(XFS_INCLUDE_DIRS ${XFS_INCLUDE_DIR})\nunset(XFS_INCLUDE_DIR)\n"
  },
  {
    "path": "cmake/Findxxhash.cmake",
    "content": "# Try to find libxxhash (devel)\n# Once done, this will define\n#\n# XXHASH_FOUND          - system nhas xxhash\n# XXHASH_INCLUDE_DIRS   - xxhash include directories\n# XXHASH_LIBRARIES      - xxhash libraries directories\n# XXHASH_LIBRARY_STATIC - xxhash static library\n#\n# and the following imported target\n#\n# XXHASH::XXHASH\n\nfind_path(XXHASH_INCLUDE_DIR\n  NAMES xxhash.h\n  HINTS ${XXHASH_ROOT}\n  PATH_SUFFIXES include)\n\nfind_library(XXHASH_LIBRARY\n  NAME xxhash\n  HINTS ${XXHASH_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(xxhash\n  REQUIRED_VARS XXHASH_LIBRARY XXHASH_INCLUDE_DIR)\nmark_as_advanced(XXHASH_LIBRARY XXHASH_INCLUDE_DIR)\n\nif (XXHASH_FOUND AND NOT TARGET XXHASH::XXHASH)\n  add_library(XXHASH::XXHASH STATIC IMPORTED)\n  set_target_properties(XXHASH::XXHASH PROPERTIES\n    IMPORTED_LOCATION \"${XXHASH_LIBRARY}\"\n    INTERFACE_INCLUDE_DIRECTORIES \"${XXHASH_INCLUDE_DIR}\")\n  target_compile_definitions(XXHASH::XXHASH INTERFACE XXHASH_FOUND)\nelse()\n  message(WARNING \"Notice: XXHASH not found, no XXHASH support\")\n  add_library(XXHASH::XXHASH INTERFACE IMPORTED)\nendif()\n\nunset(XXHASH_INCLUDE_DIRS)\nunset(XXHASH_LIBRARIES)\n"
  },
  {
    "path": "cmake/Findzstd.cmake",
    "content": "# Try to find zstd\n# Once done, this will define\n#\n# ZSTD_FOUND          - system has zstd\n# ZSTD_LIBRARIES      - zstd library\n#\n# and the following imported target\n#\n# ZSTD::ZSTD\n\nfind_library(ZSTD_LIBRARY\n  NAMES zstd\n  HINTS ${ZSTD_ROOT}\n  PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(zstd DEFAULT_MSG ZSTD_LIBRARY)\n\nif (ZSTD_FOUND AND NOT TARGET ZSTD::ZSTD)\n  mark_as_advanced(ZSTD_FOUND ZSTD_LIBRARY)\n  add_library(ZSTD::ZSTD UNKNOWN IMPORTED)\n  set_target_properties(ZSTD::ZSTD PROPERTIES\n    IMPORTED_LOCATION \"${ZSTD_LIBRARY}\")\nendif()\n\nunset(ZSTD_LIBRARY)\n"
  },
  {
    "path": "cmake/cmake_uninstall.cmake.in",
    "content": "IF(NOT EXISTS \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\")\n  MESSAGE(FATAL_ERROR \"Cannot find install manifest: \\\"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\\\"\")\nENDIF(NOT EXISTS \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\")\n\nFILE(READ \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\" files)\nSTRING(REGEX REPLACE \"\\n\" \";\" files \"${files}\")\nFOREACH(file ${files})\n  SET(file_to_remove \"$ENV{DESTDIR}${file}\")\n  MESSAGE(STATUS \"Uninstalling \\\"${file_to_remove}\\\"\")\n  IF(EXISTS \"${file_to_remove}\" OR IS_SYMLINK \"${file_to_remove}\")\n    EXECUTE_PROCESS(\n      COMMAND @CMAKE_COMMAND@ -E remove \"${file_to_remove}\"\n      OUTPUT_VARIABLE rm_out\n      RESULT_VARIABLE rm_retval\n    )\n    IF(NOT \"${rm_retval}\" STREQUAL \"0\")\n      MESSAGE(FATAL_ERROR \"Problem when removing \\\"${file_to_remove}\\\"\")\n    ENDIF(NOT \"${rm_retval}\" STREQUAL \"0\")\n  ELSE(EXISTS \"${file_to_remove}\" OR IS_SYMLINK \"${file_to_remove}\")\n    MESSAGE(STATUS \"File \\\"${file_to_remove}\\\" does not exist.\")\n  ENDIF(EXISTS \"${file_to_remove}\" OR IS_SYMLINK \"${file_to_remove}\")\nENDFOREACH(file)\nIF(DEFINED file_to_remove)\n  UNSET(file_to_remove)\nENDIF()\n"
  },
  {
    "path": "cmake/config_spec.cmake.in",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nset(SRC_DIR @CMAKE_CURRENT_SOURCE_DIR@)\nset(CPACK_PACKAGE_NAME @CPACK_PACKAGE_NAME@)\nset(CPACK_PACKAGE_VERSION @CPACK_PACKAGE_VERSION@)\nset(CPACK_PACKAGE_RELEASE @CPACK_PACKAGE_RELEASE@)\nset(CPACK_PACKAGE_VERSION_MAJOR @CPACK_PACKAGE_VERSION_MAJOR@)\nconfigure_file(${SRC_DIR}/eos.spec.in ${SRC_DIR}/eos.spec)\n"
  },
  {
    "path": "common/Assert.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Assert.hh\n// Author: Georgios Bitzers - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include <unistd.h>\n\n#define eos_assert(condition) if(!((condition))) { std::cerr << \"assertion violation in \" << __PRETTY_FUNCTION__ << \" at \" << __FILE__ << \":\" << __LINE__ << \", condition is not true: \" << #condition << std::endl; _exit(1); }\n#define SSTR(message) static_cast<std::ostringstream&>(std::ostringstream().flush() << message).str()\n#define DBG(message) std::cerr << __FILE__ << \":\" << __LINE__ << \" -- \" << #message << \" = \" << message << std::endl\n"
  },
  {
    "path": "common/AssistedThread.hh",
    "content": "// ----------------------------------------------------------------------\n// File: AssistedThread.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * quarkdb - a redis-like highly available key-value store              *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include <thread>\n#include <mutex>\n#include <condition_variable>\n#include <functional>\n#include <vector>\n#include <atomic>\n#include <string>\n\n// Thread name size limit from pthread_setname_np manual, while OSX allows for\n// 64 characters, it makes sense to keep it at 16 for portability\nconstexpr auto THREAD_NAME_LIMIT = 15;\n//------------------------------------------------------------------------------\n// C++ threads offer no easy way to stop a thread once it's started. Signalling\n// \"stop\" to a (potentially sleeping) background thread involves a subtle dance\n// involving a mutex, condition variable, and possibly an atomic.\n//\n// Doing this correctly for every thread is a huge pain, which this class\n// tries to alleviate.\n//\n// How to create a thread: Just like std::thread, ie\n// AssistedThread(&SomeClass::SomeFunction, this, some_int_value)\n//\n// The function will receive a thread assistant object as *one extra*\n// parameter *at the end*, for example:\n//\n// void SomeClass::SomeFunction(int some_int_value, ThreadAssistant &assistant)\n//\n// The assistant object can then be used to check if thread termination has been\n// requested, or sleep for a specified amount of time but wake up immediatelly\n// the moment termination is requested.\n//\n// A common pattern for background threads is then:\n// while(!assistant.terminationRequested()) {\n//   doStuff();\n//   assistant.wait_for(std::chrono::seconds(1));\n// }\n//------------------------------------------------------------------------------\nclass AssistedThread;\n\n//------------------------------------------------------------------------------\n//! Class ThreadAssistant\n//------------------------------------------------------------------------------\nclass ThreadAssistant\n{\npublic:\n  void reset()\n  {\n    stopFlag = false;\n    terminationCallbacks.clear();\n  }\n\n  void requestTermination()\n  {\n    std::lock_guard<std::mutex> lock(mtx);\n\n    if (!stopFlag) {\n      stopFlag = true;\n      notifier.notify_all();\n\n      for (size_t i = 0; i < terminationCallbacks.size(); i++) {\n        terminationCallbacks[i]();\n      }\n    }\n  }\n\n  void registerCallback(std::function<void()> callable)\n  {\n    std::lock_guard<std::mutex> lock(mtx);\n    terminationCallbacks.emplace_back(std::move(callable));\n\n    if (stopFlag) {\n      //------------------------------------------------------------------------\n      // Careful here.. This is a race condition where thread termination has\n      // already been requested, even though we're not done yet registering\n      // callbacks, apparently.\n      //\n      // Let's simply call the callback ourselves.\n      //------------------------------------------------------------------------\n      (terminationCallbacks.back())();\n    }\n  }\n\n  void dropCallbacks()\n  {\n    std::lock_guard<std::mutex> lock(mtx);\n    terminationCallbacks.clear();\n  }\n\n  bool terminationRequested()\n  {\n    return stopFlag;\n  }\n\n  template<typename T>\n  void wait_for(T duration)\n  {\n    std::unique_lock<std::mutex> lock(mtx);\n\n    if (stopFlag) {\n      return;\n    }\n\n    notifier.wait_for(lock, duration);\n  }\n\n  template<typename T>\n  void wait_until(T duration)\n  {\n    std::unique_lock<std::mutex> lock(mtx);\n\n    if (stopFlag) {\n      return;\n    }\n\n    notifier.wait_until(lock, duration);\n  }\n\n  //----------------------------------------------------------------------------\n  // Ok, this is a bit weird: Consider an AssistedThread which \"owns\" or\n  // coordinates a bunch of other threads:\n  //\n  // void Coordinator(ThreadAssistant &assistant) {\n  //   AssistedThread worker1( ... );\n  //   AssistedThread worker2( ... );\n  //   AssistedThread worker3( ... );\n  //\n  //   worker1.blockUntilThreadJoins();\n  //   worker2.blockUntilThreadJoins();\n  //   worker3.blockUntilThreadJoins();\n  // }\n  //\n  // We would like that any requests to shut down Coordinator propagate to all\n  // workers. Otherwise, since Coordinator blocks waiting for the workers to\n  // terminate, its own early termination signal would get ignored.\n  //\n  // propagateTerminationSignal does just this. In the above example, call:\n  // assistant.propagateTerminationSignal(worker1);\n  // assistant.propagateTerminationSignal(worker2);\n  // assistant.propagateTerminationSignal(worker3);\n  //\n  // And the moment Coordinator is asked to terminate, all registered threads\n  // will, too.\n  //\n  // NOTE: assistant object must belong to a different thread!\n  //----------------------------------------------------------------------------\n  void propagateTerminationSignal(AssistedThread& thread);\n\n  static void setSelfThreadName(std::string name) {\n#ifndef __APPLE__\n    pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());\n#endif\n  }\n\nprivate:\n  friend class AssistedThread;\n  // Private constructor - only AssistedThread can create such an object.\n  ThreadAssistant(bool flag) : stopFlag(flag) {}\n\n  std::atomic<bool> stopFlag;\n  std::mutex mtx;\n  std::condition_variable notifier;\n  std::vector<std::function<void()>> terminationCallbacks;\n};\n\nclass AssistedThread\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! null constructor, no underlying thread\n  //----------------------------------------------------------------------------\n  AssistedThread() :\n    assistant(new ThreadAssistant(true)), joined(true)\n  {}\n\n  //----------------------------------------------------------------------------\n  // universal references, perfect forwarding, variadic template\n  // (C++ is intensifying)\n  //----------------------------------------------------------------------------\n  template<typename... Args>\n  AssistedThread(Args&& ... args) :\n    assistant(new ThreadAssistant(false)), joined(false),\n    th(std::forward<Args>(args)..., std::ref(*assistant))\n  {}\n\n  // No assignment, no copying\n  AssistedThread& operator=(const AssistedThread&) = delete;\n\n  // Moving is allowed.\n  AssistedThread(AssistedThread&& other)\n  {\n    assistant = std::move(other.assistant);\n    joined = other.joined;\n    th = std::move(other.th);\n    other.joined = true;\n  }\n\n  template<typename... Args>\n  void reset(Args&& ... args)\n  {\n    join();\n    assistant.get()->reset();\n    joined = false;\n    th = std::thread(std::forward<Args>(args)..., std::ref(*assistant));\n  }\n\n  virtual ~AssistedThread()\n  {\n    join();\n  }\n\n  void stop()\n  {\n    if (joined) {\n      return;\n    }\n\n    assistant->requestTermination();\n  }\n\n  void join()\n  {\n    if (joined) {\n      return;\n    }\n\n    stop();\n    blockUntilThreadJoins();\n  }\n\n  // Different meaning than join, which explicitly asks the thread to\n  // terminate. Here, we simply wait until the thread exits on its own.\n  void blockUntilThreadJoins()\n  {\n    if (joined) {\n      return;\n    }\n\n    th.join();\n    joined = true;\n  }\n\n  void registerCallback(std::function<void()> callable)\n  {\n    assistant->registerCallback(std::move(callable));\n  }\n\n  void dropCallbacks()\n  {\n    assistant->dropCallbacks();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set thread name. Useful to have in GDB traces, for example.\n  //----------------------------------------------------------------------------\n  void setName(const std::string& threadName)\n  {\n#ifndef __APPLE__\n    pthread_setname_np(th.native_handle(), threadName.c_str());\n#endif\n  }\n\nprivate:\n  std::unique_ptr<ThreadAssistant> assistant;\n  bool joined;\n  std::thread th;\n};\n\ninline void ThreadAssistant::propagateTerminationSignal(AssistedThread& thread)\n{\n  registerCallback(std::bind(&AssistedThread::stop, &thread));\n}\n"
  },
  {
    "path": "common/Audit.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Audit.cc\n// Author: EOS Team - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Audit.hh\"\n#include \"common/Logging.hh\"\n\n#include \"proto/Audit.pb.h\"\n#include <google/protobuf/util/json_util.h>\n\n#include <zstd.h>\n\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <errno.h>\n#include <string.h>\n#include <time.h>\n\n#include <sstream>\n#include <iomanip>\n#include <vector>\n\nEOSCOMMONNAMESPACE_BEGIN\n\nnamespace {\nstatic inline time_t truncate_to_interval(time_t t, unsigned interval)\n{\n  if (!interval) return t;\n  return t - (t % interval);\n}\n\nstatic inline std::string format_segment_filename(time_t t)\n{\n  struct tm tmval;\n  localtime_r(&t, &tmval);\n  char buf[64];\n  // audit-YYYYmmdd-HHMMSS.zst (include seconds to support sub-minute rotations)\n  if (strftime(buf, sizeof(buf), \"audit-%Y%m%d-%H%M%S.zst\", &tmval) == 0) {\n    return \"audit-unknown.zst\";\n  }\n  return buf;\n}\n\nstatic inline bool mkdir_p(const std::string& path, mode_t mode)\n{\n  if (path.empty()) return false;\n\n  size_t pos = 0;\n  do {\n    pos = path.find('/', pos + 1);\n    std::string sub = path.substr(0, pos);\n    if (sub.empty()) continue;\n    if (mkdir(sub.c_str(), mode) == -1) {\n      if (errno == EEXIST) continue;\n      struct stat st;\n      if (stat(sub.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) continue;\n      return false;\n    }\n  } while (pos != std::string::npos);\n  return true;\n}\n}\n\nAudit::Audit(const std::string& baseDirectory,\n             unsigned rotationSeconds,\n             int compressionLevel)\n: mBaseDir(baseDirectory)\n, mRotationSeconds(rotationSeconds ? rotationSeconds : 3600)\n, mCompressionLevel(compressionLevel)\n, mZstdCctx(nullptr)\n, mFd(-1)\n, mCurrentSegmentStart(0)\n, mAuditRead(false)\n, mAuditList(false)\n, mReadAuditAll(false)\n{\n  // Default: audit common document-style file types for READ\n  const char* defaults[] = {\n    \"txt\", \"pdf\", \"doc\", \"docx\", \"ppt\", \"pptx\",\n    \"xls\", \"xlsx\", \"odt\", \"ods\", \"odp\", \"rtf\",\n    \"csv\", \"json\", \"xml\", \"yaml\", \"yml\", \"md\", \"html\", \"htm\"\n  };\n  mReadAuditSuffixes.assign(std::begin(defaults), std::end(defaults));\n}\n\nAudit::~Audit()\n{\n  std::lock_guard<std::mutex> g(mMutex);\n  closeWriterLocked();\n}\n\nvoid\nAudit::setBaseDirectory(const std::string& baseDirectory)\n{\n  std::lock_guard<std::mutex> g(mMutex);\n  if (mBaseDir == baseDirectory) {\n    return;\n  }\n  mBaseDir = baseDirectory;\n  closeWriterLocked();\n}\n\nvoid\nAudit::setReadAuditSuffixes(const std::vector<std::string>& suffixes)\n{\n  std::lock_guard<std::mutex> g(mMutex);\n  mReadAuditSuffixes.clear();\n  mReadAuditAll = false;\n  for (const auto& s : suffixes) {\n    if (s == \"*\") { mReadAuditAll = true; continue; }\n    std::string ls;\n    ls.reserve(s.size());\n    for (char c : s) ls.push_back(static_cast<char>(std::tolower(static_cast<unsigned char>(c))));\n    // strip leading dot if provided\n    if (!ls.empty() && ls[0] == '.') ls.erase(ls.begin());\n    if (!ls.empty()) mReadAuditSuffixes.push_back(ls);\n  }\n}\n\nvoid\nAudit::setReadAuditAll(bool enable)\n{\n  std::lock_guard<std::mutex> g(mMutex);\n  mReadAuditAll = enable;\n}\n\nbool\nAudit::shouldAuditReadPath(const std::string& path) const\n{\n  if (mReadAuditAll) return true;\n  // find suffix after last '.' ignoring directories\n  std::string::size_type slash = path.find_last_of('/') ;\n  std::string::size_type dot = path.find_last_of('.');\n  if (dot == std::string::npos) return false;\n  if (slash != std::string::npos && dot < slash) return false;\n  std::string ext = path.substr(dot + 1);\n  std::string lext;\n  lext.reserve(ext.size());\n  for (char c : ext) lext.push_back(static_cast<char>(std::tolower(static_cast<unsigned char>(c))));\n  for (const auto& s : mReadAuditSuffixes) {\n    if (lext == s) return true;\n  }\n  return false;\n}\n\nvoid\nAudit::audit(const eos::audit::AuditRecord& record)\n{\n  std::string json;\n  google::protobuf::util::JsonPrintOptions opts;\n  opts.add_whitespace = false;\n  opts.preserve_proto_field_names = true;\n\n  auto status = google::protobuf::util::MessageToJsonString(record, &json, opts);\n  if (!status.ok()) {\n    eos_static_err(\"msg=\\\"failed to serialize audit record to JSON\\\" err=%s\",\n                   status.ToString().c_str());\n    return;\n  }\n  json.push_back('\\n');\n\n  const time_t now = time(nullptr);\n  std::lock_guard<std::mutex> g(mMutex);\n  rotateIfNeededLocked(now);\n\n  if (mFd < 0 || !mZstdCctx) {\n    // Failed to open writer; drop record\n    return;\n  }\n\n  ZSTD_inBuffer in = { json.data(), json.size(), 0 };\n  std::vector<char> outBuf(131072);\n\n  while (in.pos < in.size) {\n    ZSTD_outBuffer out = { outBuf.data(), outBuf.size(), 0 };\n    size_t ret = ZSTD_compressStream2(reinterpret_cast<ZSTD_CCtx*>(mZstdCctx),\n                                      &out, &in, ZSTD_e_continue);\n    if (ZSTD_isError(ret)) {\n      eos_static_err(\"msg=\\\"zstd compress error\\\" code=%s\",\n                     ZSTD_getErrorName(ret));\n      break;\n    }\n    if (out.pos) {\n      ssize_t w = write(mFd, outBuf.data(), out.pos);\n      if (w < 0) {\n        eos_static_err(\"msg=\\\"write error\\\" errno=%d err=\\\"%s\\\"\",\n                       errno, strerror(errno));\n        break;\n      }\n    }\n  }\n\n  // Flush buffered data so small records are visible immediately\n  {\n    ZSTD_inBuffer fin = { nullptr, 0, 0 };\n    size_t fret = 0;\n    do {\n      ZSTD_outBuffer out = { outBuf.data(), outBuf.size(), 0 };\n      fret = ZSTD_compressStream2(reinterpret_cast<ZSTD_CCtx*>(mZstdCctx),\n                                  &out, &fin, ZSTD_e_flush);\n      if (ZSTD_isError(fret)) {\n        eos_static_warning(\"msg=\\\"zstd flush error\\\" code=%s\",\n                           ZSTD_getErrorName(fret));\n        break;\n      }\n      if (out.pos) {\n        (void) ::write(mFd, outBuf.data(), out.pos);\n      }\n    } while (fret != 0);\n  }\n}\n\nvoid\nAudit::audit(eos::audit::Operation operation,\n             const std::string& filename,\n             const eos::common::VirtualIdentity& vid,\n             const std::string& uuid,\n             const std::string& tid,\n             const std::string& svc,\n             const std::string& target,\n             const eos::audit::Stat* before,\n             const eos::audit::Stat* after,\n             const std::string& attr_name,\n             const std::string& attr_before,\n             const std::string& attr_after,\n             const char* src_file,\n             int src_line,\n             const char* version)\n{\n  eos::audit::AuditRecord rec;\n  rec.set_timestamp(time(nullptr));\n  rec.set_path(filename);\n  rec.set_operation(operation);\n  rec.set_client_ip(vid.host);\n  if (vid.name.length()) {\n    rec.set_account(vid.name.c_str());\n  } else if (!vid.uid_string.empty()) {\n    rec.set_account(vid.uid_string);\n  } else {\n    rec.set_account(std::to_string(vid.uid));\n  }\n  rec.mutable_auth()->set_mechanism(vid.prot.length() ? vid.prot.c_str() : \"local\");\n  if (vid.gateway) {\n    (*rec.mutable_auth()->mutable_attributes())[\"gateway\"] = \"1\";\n  }\n  if (vid.token && vid.token->Valid()) {\n    rec.mutable_authorization()->add_reasons(\"token\");\n  } else {\n    rec.mutable_authorization()->add_reasons(\"uidgid\");\n  }\n  if (!uuid.empty() && uuid != \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\") {\n    rec.set_uuid(uuid);\n  }\n  if (!tid.empty()) rec.set_tid(tid);\n  if (!vid.app.empty()) rec.set_app(vid.app);\n  if (!svc.empty()) rec.set_svc(svc);\n  if (!target.empty()) rec.set_target(target);\n  if (before) rec.mutable_before()->CopyFrom(*before);\n  if (after) rec.mutable_after()->CopyFrom(*after);\n  if (!attr_name.empty()) {\n    auto* ac = rec.add_attrs();\n    ac->set_name(attr_name);\n    ac->set_before(attr_before);\n    ac->set_after(attr_after);\n  }\n  if (src_file && *src_file) {\n    const char* basename = strrchr(src_file, '/') ? strrchr(src_file, '/') + 1 : src_file;\n    std::string software = basename;\n    if (src_line > 0) {\n      software += \":\" + std::to_string(src_line);\n    }\n    if (version && *version) {\n      software += \"@\" + std::string(version);\n    }\n    rec.set_software(software);\n  }\n  audit(rec);\n}\n\nvoid\nAudit::rotateIfNeededLocked(time_t now)\n{\n  const time_t seg = truncate_to_interval(now, mRotationSeconds);\n  if (mFd >= 0 && mZstdCctx && seg == mCurrentSegmentStart) {\n    return;\n  }\n\n  // Close current if any\n  closeWriterLocked();\n\n  // Open new\n  (void)openWriterLocked(seg);\n}\n\nbool\nAudit::openWriterLocked(time_t segmentStart)\n{\n  if (mBaseDir.empty()) {\n    return false;\n  }\n\n  ensureDirectoryExistsLocked();\n\n  const std::string filename = makeSegmentPath(segmentStart);\n\n  mFd = ::open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0644);\n  if (mFd < 0) {\n    eos_static_err(\"msg=\\\"cannot open audit file\\\" path=\\\"%s\\\" errno=%d err=\\\"%s\\\"\",\n                   filename.c_str(), errno, strerror(errno));\n    return false;\n  }\n\n  mZstdCctx = ZSTD_createCCtx();\n  if (!mZstdCctx) {\n    eos_static_err(\"msg=\\\"cannot create zstd context\\\"\");\n    ::close(mFd);\n    mFd = -1;\n    return false;\n  }\n\n  if (ZSTD_isError(ZSTD_CCtx_setParameter(reinterpret_cast<ZSTD_CCtx*>(mZstdCctx), ZSTD_c_compressionLevel, mCompressionLevel))) {\n    eos_static_warning(\"msg=\\\"failed to set zstd compression level\\\" level=%d\", mCompressionLevel);\n  }\n\n  // Ensure a valid ZSTD frame header is written so readers like zstdcat don't\n  // fail on an empty, newly-rotated file. Flush pending header with empty input.\n  {\n    std::vector<char> outBuf(16384);\n    ZSTD_inBuffer in = { nullptr, 0, 0 };\n    ZSTD_outBuffer out = { outBuf.data(), outBuf.size(), 0 };\n    size_t ret = ZSTD_compressStream2(reinterpret_cast<ZSTD_CCtx*>(mZstdCctx),\n                                      &out, &in, ZSTD_e_flush);\n    if (ZSTD_isError(ret)) {\n      eos_static_warning(\"msg=\\\"zstd header flush error\\\" code=%s\", ZSTD_getErrorName(ret));\n    }\n    if (out.pos) {\n      (void)::write(mFd, outBuf.data(), out.pos);\n    }\n  }\n\n  // Update symlink audit.zstd -> current file (best-effort)\n  std::string linkPath = mBaseDir + \"/audit.zstd\";\n  (void)::unlink(linkPath.c_str());\n  (void)::symlink(filename.c_str(), linkPath.c_str());\n\n  mCurrentSegmentStart = segmentStart;\n  return true;\n}\n\nvoid\nAudit::closeWriterLocked()\n{\n  if (mFd >= 0 && mZstdCctx) {\n    std::vector<char> outBuf(65536);\n    ZSTD_inBuffer in = { nullptr, 0, 0 };\n    size_t ret = 0;\n    do {\n      ZSTD_outBuffer out = { outBuf.data(), outBuf.size(), 0 };\n      ret = ZSTD_compressStream2(reinterpret_cast<ZSTD_CCtx*>(mZstdCctx),\n                                 &out, &in, ZSTD_e_end);\n      if (ZSTD_isError(ret)) {\n        eos_static_err(\"msg=\\\"zstd endStream error\\\" code=%s\",\n                       ZSTD_getErrorName(ret));\n        break;\n      }\n      if (out.pos) {\n        (void)::write(mFd, outBuf.data(), out.pos);\n      }\n    } while (ret != 0);\n  }\n\n  if (mFd >= 0) {\n    ::close(mFd);\n    mFd = -1;\n  }\n  if (mZstdCctx) {\n    ZSTD_freeCCtx(reinterpret_cast<ZSTD_CCtx*>(mZstdCctx));\n    mZstdCctx = nullptr;\n  }\n  mCurrentSegmentStart = 0;\n}\n\nstd::string\nAudit::makeSegmentPath(time_t segmentStart) const\n{\n  std::ostringstream oss;\n  oss << mBaseDir;\n  if (!mBaseDir.empty() && mBaseDir.back() != '/') {\n    oss << '/';\n  }\n  oss << format_segment_filename(segmentStart);\n  return oss.str();\n}\n\nvoid\nAudit::ensureDirectoryExistsLocked()\n{\n  struct stat st;\n  if (stat(mBaseDir.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {\n    return;\n  }\n  if (!mkdir_p(mBaseDir, 0755)) {\n    eos_static_err(\"msg=\\\"failed to create audit directory\\\" dir=\\\"%s\\\" errno=%d err=\\\"%s\\\"\",\n                   mBaseDir.c_str(), errno, strerror(errno));\n  }\n}\n\nEOSCOMMONNAMESPACE_END\n\n\n"
  },
  {
    "path": "common/Audit.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Audit.hh\n// Author: EOS Team - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   Audit.hh\n *\n * @brief  Audit logging interface writing JSON lines compressed with ZSTD.\n *\n */\n\n#ifndef __EOSCOMMON_AUDIT__HH__\n#define __EOSCOMMON_AUDIT__HH__\n\n#include \"common/Namespace.hh\"\n#include \"common/VirtualIdentity.hh\"\n#include \"proto/Audit.pb.h\"\n#include <string>\n#include <mutex>\n\n// Forward declaration for the generated protobuf\nnamespace eos { namespace audit { class AuditRecord; } }\n\nEOSCOMMONNAMESPACE_BEGIN\n\n/**\n * @class Audit\n * @brief Thread-safe audit logger writing newline-delimited JSON to ZSTD files\n *        with time-based rotation (default 1 hour).\n */\nclass Audit\n{\npublic:\n  /**\n   * @brief Construct an audit logger\n   * @param baseDirectory directory where audit files are created\n   * @param rotationSeconds rotation interval in seconds (default 3600)\n   * @param compressionLevel zstd compression level (default 3)\n   */\n  Audit(const std::string& baseDirectory,\n        unsigned rotationSeconds = 3600,\n        int compressionLevel = 3);\n\n  ~Audit();\n\n  Audit(const Audit&) = delete;\n  Audit& operator=(const Audit&) = delete;\n  Audit(Audit&&) = delete;\n  Audit& operator=(Audit&&) = delete;\n\n  /**\n   * @brief Update base directory for output files. Triggers rotation.\n   */\n  void setBaseDirectory(const std::string& baseDirectory);\n\n  /**\n   * @brief Enable/disable audit logging for specific operations\n   * @param enable true to enable, false to disable\n   */\n  void setReadAuditing(bool enable) { mAuditRead = enable; }\n  void setListAuditing(bool enable) { mAuditList = enable; }\n\n  /**\n   * @brief Check if auditing is enabled for specific operations\n   * @return true if enabled, false otherwise\n   */\n  bool isReadAuditingEnabled() const { return mAuditRead; }\n  bool isListAuditingEnabled() const { return mAuditList; }\n\n  /**\n   * @brief Configure which file suffixes should trigger READ auditing.\n   * If the vector contains \"*\", all files are audited for READ.\n   */\n  void setReadAuditSuffixes(const std::vector<std::string>& suffixes);\n\n  /**\n   * @brief Convenience to enable auditing all files for READ.\n   */\n  void setReadAuditAll(bool enable);\n\n  /**\n   * @brief Check if the given path matches the READ auditing suffix policy.\n   */\n  bool shouldAuditReadPath(const std::string& path) const;\n\n  /**\n   * @brief Append a record to the audit log (JSON line). Thread-safe.\n   */\n  void audit(const eos::audit::AuditRecord& record);\n\n  /**\n   * @brief Convenience overload to build and append an audit record.\n   *        Populates common fields from VirtualIdentity.\n   * @param operation operation type (e.g. DELETE, CREATE)\n   * @param filename affected path\n   * @param vid caller identity (for account, client_ip, mechanism, app, token)\n   * @param uuid unique request id\n   * @param tid trace identifier (short token)\n   * @param svc acting service (e.g. \"mgm\")\n   * @param target optional destination path (for rename/symlink)\n   */\n  void audit(eos::audit::Operation operation,\n             const std::string& filename,\n             const eos::common::VirtualIdentity& vid,\n             const std::string& uuid,\n             const std::string& tid,\n             const std::string& svc,\n             const std::string& target = std::string(),\n             const eos::audit::Stat* before = nullptr,\n             const eos::audit::Stat* after = nullptr,\n             const std::string& attr_name = std::string(),\n             const std::string& attr_before = std::string(),\n             const std::string& attr_after = std::string(),\n             const char* src_file = nullptr,\n             int src_line = 0,\n             const char* version = nullptr);\n\nprivate:\n  void rotateIfNeededLocked(time_t now);\n  bool openWriterLocked(time_t segmentStart);\n  void closeWriterLocked();\n  std::string makeSegmentPath(time_t segmentStart) const;\n  void ensureDirectoryExistsLocked();\n\n  std::mutex mMutex;\n  std::string mBaseDir;\n  unsigned mRotationSeconds;\n  int mCompressionLevel;\n  void* mZstdCctx; // ZSTD_CCtx*\n  int mFd;         // file descriptor for current .zst file\n  time_t mCurrentSegmentStart;\n  bool mAuditRead = false;\n  bool mAuditList = false;\n  bool mReadAuditAll = false;\n  std::vector<std::string> mReadAuditSuffixes; // lowercase suffixes without dot\n};\n\nEOSCOMMONNAMESPACE_END\n\n// Helper macro to capture source location/version for audit calls\n#ifndef VERSION\n#define VERSION \"unknown\"\n#endif\n\n#endif\n\n\n"
  },
  {
    "path": "common/BehaviourConfig.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file BehaviourConfig.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/BehaviourConfig.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n// Check the accepted configuration values per behaviour\n//----------------------------------------------------------------------------\nbool AcceptedValue(BehaviourType behaviour, const std::string& value)\n{\n  if (behaviour == BehaviourType::RainMinFsidEntry) {\n    if ((value != \"on\") && (value != \"off\")) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\n//----------------------------------------------------------------------------\n// Convert string to behaviour type\n//----------------------------------------------------------------------------\nBehaviourType\nBehaviourConfig::ConvertStringToBehaviour(const std::string& input)\n{\n  if (input == \"rain_min_fsid_entry\") {\n    return BehaviourType::RainMinFsidEntry;\n  } else if (input == \"all\") {\n    return BehaviourType::All;\n  } else {\n    return BehaviourType::None;\n  }\n}\n\n//----------------------------------------------------------------------------\n//! Convert behaviour type to string\n//----------------------------------------------------------------------------\nstd::string\nBehaviourConfig::ConvertBehaviourToString(const BehaviourType& btype)\n{\n  if (btype == BehaviourType::RainMinFsidEntry) {\n    return \"rain_min_fsid_entry\";\n  } else {\n    return \"unknown\";\n  }\n}\n\n//------------------------------------------------------------------------------\n// Set behaviour change\n//------------------------------------------------------------------------------\nbool\nBehaviourConfig::Set(BehaviourType behaviour, const std::string& value)\n{\n  if (!AcceptedValue(behaviour, value)) {\n    return false;\n  }\n\n  std::unique_lock<std::mutex> lock(mMutex);\n\n  if (value == \"off\") {\n    mMapBehaviours.erase(behaviour);\n  } else {\n    mMapBehaviours[behaviour] = value;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Get behaviour configuration value\n//------------------------------------------------------------------------------\nstd::string\nBehaviourConfig::Get(const BehaviourType& behaviour) const\n{\n  std::unique_lock<std::mutex> loc(mMutex);\n  auto it = mMapBehaviours.find(behaviour);\n\n  if (it != mMapBehaviours.end()) {\n    return it->second;\n  }\n\n  return std::string();\n}\n\n//------------------------------------------------------------------------------\n// Check if given behaviour exists in the map\n//------------------------------------------------------------------------------\nbool\nBehaviourConfig::Exists(const BehaviourType& behaviour) const\n{\n  std::unique_lock<std::mutex> lock(mMutex);\n  return (mMapBehaviours.find(behaviour) != mMapBehaviours.end());\n}\n\n//------------------------------------------------------------------------------\n// List all configured behaviours\n//------------------------------------------------------------------------------\nstd::map<std::string, std::string>\nBehaviourConfig::List() const\n{\n  std::map<std::string, std::string> output;\n  std::unique_lock<std::mutex> lock(mMutex);\n\n  for (const auto& elem : mMapBehaviours) {\n    output[ConvertBehaviourToString(elem.first)] = elem.second;\n  }\n\n  return output;\n}\n\n//------------------------------------------------------------------------------\n// Clean the given behaviour type\n//------------------------------------------------------------------------------\nvoid\nBehaviourConfig::Clear(const BehaviourType& behaviour)\n{\n  if (behaviour == BehaviourType::None) {\n    return;\n  }\n\n  std::unique_lock<std::mutex> lock(mMutex);\n\n  if (behaviour == BehaviourType::All) {\n    mMapBehaviours.clear();\n  } else {\n    auto it = mMapBehaviours.find(behaviour);\n\n    if (it != mMapBehaviours.end()) {\n      mMapBehaviours.erase(it);\n    }\n  }\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/BehaviourConfig.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file BehaviourConfig.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include <mutex>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//! Type of supported behaviours\nenum struct BehaviourType {\n  None,\n  RainMinFsidEntry,\n  All,\n};\n\n//------------------------------------------------------------------------------\n//! Class MgmBehaviour - object used to store the MGM behaviour changes\n//------------------------------------------------------------------------------\nclass BehaviourConfig: public eos::common::LogId\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Convert string to behaviour type\n  //!\n  //! @param input string representation\n  //!\n  //! @return behaviour type object\n  //----------------------------------------------------------------------------\n  static BehaviourType\n  ConvertStringToBehaviour(const std::string& input);\n\n  //----------------------------------------------------------------------------\n  //! Convert behaviour type to string\n  //!\n  //! @param btype behaviour type object\n  //!\n  //! @return string representation\n  //----------------------------------------------------------------------------\n  static std::string\n  ConvertBehaviourToString(const BehaviourType& btype);\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  BehaviourConfig() = default;\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~BehaviourConfig() = default;\n\n  //----------------------------------------------------------------------------\n  //! Check if there is any behaviour change\n  //!\n  //! @return true if behaviour changes are registered, otherwise false\n  //----------------------------------------------------------------------------\n  bool IsEmpty() const\n  {\n    std::unique_lock<std::mutex> lock(mMutex);\n    return mMapBehaviours.empty();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set behaviour change\n  //!\n  //! @param behaviour type of behaviour\n  //! @param value configuration value\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool Set(BehaviourType behaviour, const std::string& value);\n\n  //----------------------------------------------------------------------------\n  //! Get behaviour configuration value\n  //!\n  //! @param behaviour behaviour type\n  //!\n  //! @return string\n  //----------------------------------------------------------------------------\n  std::string Get(const BehaviourType& behaviour) const;\n\n  //----------------------------------------------------------------------------\n  //! Clean the given behaviour type\n  //!\n  //! @param behaviour type of behaviour\n  //----------------------------------------------------------------------------\n  void Clear(const BehaviourType& behaviour);\n\n  //----------------------------------------------------------------------------\n  //! Check if given behaviour exists in the map. We don't care about its\n  //! configuration value in this case.\n  //!\n  //! @param behaviour behaviour type\n  //!\n  //! @return true if it exists in the map, otherwise false\n  //----------------------------------------------------------------------------\n  bool Exists(const BehaviourType& behaviour) const;\n\n  //----------------------------------------------------------------------------\n  //! List all configured behaviours\n  //!\n  //! @param return map of behaviours\n  //----------------------------------------------------------------------------\n  std::map<std::string, std::string>\n  List() const;\n\nprivate:\n  std::map<BehaviourType, std::string> mMapBehaviours;\n  mutable std::mutex mMutex;\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/BufferManager.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file BufferManager.cc\n//! @author Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/BufferManager.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Get the nearest power of 2 value bigger then the given input but always\n// greater than min\n//------------------------------------------------------------------------------\nuint32_t GetPowerCeil(const uint32_t input, const uint32_t min)\n{\n  uint32_t power = min;\n\n  while (input > power) {\n    power <<= 1;\n  }\n\n  return power;\n}\n\n//------------------------------------------------------------------------------\n// Get amount of system memory\n//------------------------------------------------------------------------------\nuint64_t GetSystemMemorySize()\n{\n  static uint64_t total_size = 0ull;\n\n  if (!total_size) {\n    uint64_t pages = sysconf(_SC_PHYS_PAGES);\n    uint64_t page_size = sysconf(_SC_PAGE_SIZE);\n    total_size = pages * page_size;\n  }\n\n  return total_size;\n}\n\n//------------------------------------------------------------------------------\n// Get OS page size aligned buffer\n//------------------------------------------------------------------------------\nstd::unique_ptr<char, void(*)(void*)>\nGetAlignedBuffer(const size_t size)\n{\n  static long os_pg_size = sysconf(_SC_PAGESIZE);\n  char* raw_buffer = nullptr;\n  std::unique_ptr<char, void(*)(void*)> buffer\n  ((char*) raw_buffer, [](void* ptr) {\n    if (ptr) {\n      free(ptr);\n    }\n  });\n\n  if (posix_memalign((void**) &raw_buffer, os_pg_size, size)) {\n    return buffer;\n  }\n\n  buffer.reset(raw_buffer);\n  return buffer;\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/BufferManager.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file BufferManager.hh\n//! @author Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include \"common/StringConversion.hh\"\n#include <memory>\n#include <mutex>\n#include <vector>\n#include <list>\n#include <atomic>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Get the nearest power of 2 value bigger then the given input but always\n//! greater than given min\n//!\n//! @param input input value\n//! @param min min power of 2 to be used!!!\n//!\n//! @return nearest power of 2 bigger than input\n//------------------------------------------------------------------------------\nuint32_t GetPowerCeil(const uint32_t input, const uint32_t min = 1024);\n\n//------------------------------------------------------------------------------\n//! Get amount of system memory\n//------------------------------------------------------------------------------\nuint64_t GetSystemMemorySize();\n\n//------------------------------------------------------------------------------\n//! Get OS page size aligned buffer\n//!\n//! @param size buffer size to be allocated\n//!\n//! @return unique_ptr to buffer or null if there is any error\n//------------------------------------------------------------------------------\nstd::unique_ptr<char, void(*)(void*)>\nGetAlignedBuffer(const size_t size);\n\n//------------------------------------------------------------------------------\n//! Class Buffer\n//------------------------------------------------------------------------------\nclass Buffer\n{\n  friend class BufferManager;\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  Buffer(uint64_t size):\n    mCapacity(size), mLength(0ull), mData(nullptr, free)\n  {\n    mData = GetAlignedBuffer(mCapacity);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~Buffer() = default;\n\n  //----------------------------------------------------------------------------\n  //! Get pointer to underlying data\n  //----------------------------------------------------------------------------\n  inline char* GetDataPtr()\n  {\n    return mData.get();\n  }\n\n  uint64_t mCapacity; ///< Available size of the buffer\n  uint64_t mLength; ///< Length of the useful data\n  std::unique_ptr<char, void(*)(void*)> mData; ///< Buffer holding the data\n};\n\n\n//------------------------------------------------------------------------------\n//! Class BufferSlot\n//------------------------------------------------------------------------------\nclass BufferSlot\n{\n  friend class BufferManager;\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param size size of buffers allocated by the current slot\n  //----------------------------------------------------------------------------\n  BufferSlot(uint64_t size):\n    mNumBuffers(0), mBuffSize(size)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~BufferSlot()\n  {\n    std::unique_lock<std::mutex> lock(mSlotMutex);\n    mAvailableBuffers.clear();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Move assignment operator\n  //----------------------------------------------------------------------------\n  BufferSlot& operator =(BufferSlot&& other) noexcept\n  {\n    if (this != &other) {\n      mBuffSize = other.mBuffSize.load();\n      mNumBuffers.store(other.mNumBuffers);\n      mAvailableBuffers = other.mAvailableBuffers;\n      other.mAvailableBuffers.clear();\n    }\n\n    return *this;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Move constructor\n  //----------------------------------------------------------------------------\n  BufferSlot(BufferSlot&& other) noexcept\n  {\n    *this = std::move(other);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get buffer\n  //----------------------------------------------------------------------------\n  std::pair<std::shared_ptr<Buffer>, bool> GetBuffer()\n  {\n    bool new_alloc = false;\n    std::unique_lock<std::mutex> lock(mSlotMutex);\n\n    if (!mAvailableBuffers.empty()) {\n      auto buff = mAvailableBuffers.front();\n      mAvailableBuffers.pop_front();\n      return std::make_pair(buff, new_alloc);\n    }\n\n    ++mNumBuffers;\n    new_alloc = true;\n    return std::make_pair(std::make_shared<Buffer>(mBuffSize), new_alloc);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Recycle buffer object\n  //!\n  //! @param buffer buffer object to be recycled\n  //! @param keep true if buffer is to be saved otherwise false\n  //----------------------------------------------------------------------------\n  void Recycle(std::shared_ptr<Buffer> buffer, bool keep)\n  {\n    if (keep) {\n      std::unique_lock<std::mutex> lock(mSlotMutex);\n      mAvailableBuffers.push_back(buffer);\n    } else {\n      --mNumBuffers;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Try to pop a buffer from the list of available ones if possible\n  //----------------------------------------------------------------------------\n  bool Pop()\n  {\n    std::unique_lock<std::mutex> lock(mSlotMutex);\n\n    if (!mAvailableBuffers.empty()) {\n      mAvailableBuffers.pop_front();\n      --mNumBuffers;\n      return true;\n    }\n\n    return false;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get size of the buffer allocated by this buffer slot\n  //!\n  //! @return size of the buffer allocated by this object\n  //----------------------------------------------------------------------------\n  uint64_t GetBufferSize() const\n  {\n    return mBuffSize.load();\n  }\n\nprivate:\n  std::mutex mSlotMutex;\n  std::list<std::shared_ptr<Buffer>> mAvailableBuffers;\n  std::atomic<uint64_t> mNumBuffers;\n  std::atomic<uint64_t> mBuffSize;\n};\n\n\n//------------------------------------------------------------------------------\n//! Class BufferManager\n//------------------------------------------------------------------------------\nclass BufferManager: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param max_size maximum total size of allocated buffers\n  //! @param slots number of slots for different buffer sizes which are power\n  //!        of 2 and multiple of slot_base_size e.g. 1MB\n  //!        slot 0 -> 1MB\n  //!        slot 1 -> 2MB\n  //!        slot 2 -> 4MB\n  //!        ...\n  //!        slot 6 -> 64MB\n  //! @param slot_base_sz size of the blocks in the first slot\n  //----------------------------------------------------------------------------\n  BufferManager(uint64_t max_size = 256 * 1024 * 1024, uint32_t slots = 6,\n                uint64_t slot_base_sz = 1024 * 1024):\n    mMaxSize(max_size), mAllocatedSize(0ull), mNumSlots(slots),\n    mSlotBaseSize(slot_base_sz)\n  {\n    for (uint32_t i = 0u; i <= mNumSlots; ++i) {\n      mSlots.emplace_back((1 << i) * mSlotBaseSize);\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~BufferManager() = default;\n\n  //----------------------------------------------------------------------------\n  //! Get buffer for the given length\n  //!\n  //! @param size minimum size for requested buffer\n  //!\n  //! @return buffer object\n  //----------------------------------------------------------------------------\n  std::shared_ptr<Buffer> GetBuffer(uint64_t size)\n  {\n    // No new buffer if we already hold more than half of system memory\n    if (mAllocatedSize > (GetSystemMemorySize() >> 1)) {\n      return nullptr;\n    }\n\n    uint32_t slot {UINT32_MAX};\n\n    // Find appropriate slot for the given size\n    for (uint32_t i = 0; i <= mNumSlots; ++i) {\n      if (size <= (mSlotBaseSize * std::pow(2, i))) {\n        slot = i;\n        break;\n      }\n    }\n\n    // No slot big enough for the given request\n    if (slot == UINT32_MAX) {\n      // No buffer if size is unreasonably large > 512MB\n      if (size > 512 * eos::common::MB) {\n        return nullptr;\n      }\n\n      mAllocatedSize += size;\n      return std::make_shared<Buffer>(size);\n    }\n\n    std::pair<std::shared_ptr<Buffer>, bool> pair = mSlots[slot].GetBuffer();\n\n    if (pair.second) {\n      mAllocatedSize += pair.first->mCapacity;\n    }\n\n    return pair.first;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Recycle buffer object\n  //!\n  //! @param buffer objec to be recycled\n  //----------------------------------------------------------------------------\n  void Recycle(std::shared_ptr<Buffer> buffer)\n  {\n    if (buffer == nullptr) {\n      return;\n    }\n\n    uint32_t slot {UINT32_MAX};\n\n    // Find appropriate slot for given buffer\n    for (uint32_t i = 0; i <= mNumSlots; ++i) {\n      if (buffer->mCapacity == (mSlotBaseSize * std::pow(2, i))) {\n        slot = i;\n        break;\n      }\n    }\n\n    // Buffer larger then our biggest slot, just deallocate\n    if (slot == UINT32_MAX) {\n      mAllocatedSize -= buffer->mCapacity;\n      buffer.reset();\n      return;\n    }\n\n    uint64_t total_size {0ull};\n    auto sorted_slots = GetSortedSlotSizes(total_size);\n    bool keep = (total_size <= mMaxSize);\n\n    if (!keep) {\n      eos_debug(\"msg=\\\"buffer pool is full\\\" max_size=%s\",\n                eos::common::StringConversion::GetPrettySize(mMaxSize).c_str());\n\n      // Perform clean up for rest of slots depending on their size\n      for (auto it = sorted_slots.rbegin(); it != sorted_slots.rend(); ++it) {\n        if (it->first > slot) {\n          if (mSlots[it->first].Pop()) {\n            mAllocatedSize -= mSlots[it->first].GetBufferSize();\n            break;\n          }\n        }\n\n        if (it->first < slot) {\n          // Free the equivalent of a block from the current slot\n          int free_blocks = 1 << (slot - it->first);\n\n          while (free_blocks) {\n            if (mSlots[it->first].Pop()) {\n              mAllocatedSize -= mSlots[it->first].GetBufferSize();\n              --free_blocks;\n            } else {\n              break;\n            }\n          }\n\n          break;\n        }\n      }\n    }\n\n    mSlots[slot].Recycle(buffer, keep);\n\n    if (!keep) {\n      mAllocatedSize -= buffer->mCapacity;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get sorted distribution of slot sizes from smallest to biggest\n  //!\n  //! @param total_size compute the total size allocated so far\n  //!\n  //! @return sorted vector of pairs of slot ids and size of allocated buffers\n  //!         for that corresponding slot\n  //----------------------------------------------------------------------------\n  std::vector< std::pair<uint32_t, uint64_t> >\n  GetSortedSlotSizes(uint64_t& total_size) const\n  {\n    std::vector< std::pair<uint32_t, uint64_t> > elem;\n    total_size = 0ull;\n\n    for (uint32_t i = 0; i <= mNumSlots; ++i) {\n      elem.push_back(std::make_pair(i,\n                                    (mSlots[i].mNumBuffers * (1 << i) * 1024 * 1024)));\n      total_size += elem.rbegin()->second;\n    }\n\n    auto comparator = [](std::pair<uint32_t, uint64_t> a,\n    std::pair<uint32_t, uint64_t> b) {\n      return (a.second < b.second);\n    };\n    std::sort(elem.begin(), elem.end(), comparator);\n    return elem;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get number of slots handled by the current buffer manager\n  //----------------------------------------------------------------------------\n  uint32_t GetNumSlots() const\n  {\n    return mNumSlots.load();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get max size of buffers stored by buffer manager\n  //----------------------------------------------------------------------------\n  uint64_t GetMaxSize() const\n  {\n    return mMaxSize.load();\n  }\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  std::atomic<uint64_t> mMaxSize;\n  std::atomic<uint64_t> mAllocatedSize;\n  std::atomic<uint32_t> mNumSlots;\n  const uint64_t mSlotBaseSize;\n  std::vector<BufferSlot> mSlots;\n};\n\n//------------------------------------------------------------------------------\n//! Managed buffer which is automatically recycled during destruction\n//------------------------------------------------------------------------------\nclass ManagedBuffer\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ManagedBuffer(BufferManager& mgr, uint64_t size):\n    mMgr(mgr)\n  {\n    mBuff = mMgr.GetBuffer(size);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get underlying buffer\n  //----------------------------------------------------------------------------\n  inline std::shared_ptr<Buffer> GetBuffer()\n  {\n    return mBuff;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~ManagedBuffer()\n  {\n    mMgr.Recycle(mBuff);\n  }\n\nprivate:\n  BufferManager& mMgr;\n  std::shared_ptr<Buffer> mBuff;\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/CLI11.hpp",
    "content": "#pragma once\n\n// CLI11: Version 1.8.0\n// Originally designed by Henry Schreiner\n// https://github.com/CLIUtils/CLI11\n//\n// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts\n// from: v1.8.0\n//\n// From LICENSE:\n//\n// CLI11 1.8 Copyright (c) 2017-2019 University of Cincinnati, developed by Henry\n// Schreiner under NSF AWARD 1414736. All rights reserved.\n//\n// Redistribution and use in source and binary forms of CLI11, with or without\n// modification, are permitted provided that the following conditions are met:\n//\n// 1. Redistributions of source code must retain the above copyright notice, this\n//    list of conditions and the following disclaimer.\n// 2. Redistributions in binary form must reproduce the above copyright notice,\n//    this list of conditions and the following disclaimer in the documentation\n//    and/or other materials provided with the distribution.\n// 3. Neither the name of the copyright holder nor the names of its contributors\n//    may be used to endorse or promote products derived from this software without\n//    specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\n// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\n// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n// Standard combined includes:\n\n#include <algorithm>\n#include <cmath>\n#include <cstdint>\n#include <deque>\n#include <exception>\n#include <fstream>\n#include <functional>\n#include <iomanip>\n#include <iostream>\n#include <istream>\n#include <iterator>\n#include <locale>\n#include <map>\n#include <memory>\n#include <numeric>\n#include <set>\n#include <sstream>\n#include <stdexcept>\n#include <string>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <tuple>\n#include <type_traits>\n#include <utility>\n#include <vector>\n\n\n// Verbatim copy from CLI/Version.hpp:\n\n\n#define CLI11_VERSION_MAJOR 1\n#define CLI11_VERSION_MINOR 8\n#define CLI11_VERSION_PATCH 0\n#define CLI11_VERSION \"1.8.0\"\n\n\n\n\n// Verbatim copy from CLI/Macros.hpp:\n\n\n// The following version macro is very similar to the one in PyBind11\n#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER)\n#if __cplusplus >= 201402L\n#define CLI11_CPP14\n#if __cplusplus >= 201703L\n#define CLI11_CPP17\n#if __cplusplus > 201703L\n#define CLI11_CPP20\n#endif\n#endif\n#endif\n#elif defined(_MSC_VER) && __cplusplus == 199711L\n// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented)\n// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer\n#if _MSVC_LANG >= 201402L\n#define CLI11_CPP14\n#if _MSVC_LANG > 201402L && _MSC_VER >= 1910\n#define CLI11_CPP17\n#if __MSVC_LANG > 201703L && _MSC_VER >= 1910\n#define CLI11_CPP20\n#endif\n#endif\n#endif\n#endif\n\n#if defined(CLI11_CPP14)\n#define CLI11_DEPRECATED(reason) [[deprecated(reason)]]\n#elif defined(_MSC_VER)\n#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason))\n#else\n#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason)))\n#endif\n\n\n\n\n// Verbatim copy from CLI/Optional.hpp:\n\n\n// You can explicitly enable or disable support\n// by defining to 1 or 0. Extra check here to ensure it's in the stdlib too.\n// We nest the check for __has_include and it's usage\n#ifndef CLI11_STD_OPTIONAL\n#ifdef __has_include\n#if defined(CLI11_CPP17) && __has_include(<optional>)\n#define CLI11_STD_OPTIONAL 1\n#else\n#define CLI11_STD_OPTIONAL 0\n#endif\n#else\n#define CLI11_STD_OPTIONAL 0\n#endif\n#endif\n\n#ifndef CLI11_EXPERIMENTAL_OPTIONAL\n#define CLI11_EXPERIMENTAL_OPTIONAL 0\n#endif\n\n#ifndef CLI11_BOOST_OPTIONAL\n#define CLI11_BOOST_OPTIONAL 0\n#endif\n\n#if CLI11_BOOST_OPTIONAL\n#include <boost/version.hpp>\n#if BOOST_VERSION < 106100\n#error \"This boost::optional version is not supported, use 1.61 or better\"\n#endif\n#endif\n\n#if CLI11_STD_OPTIONAL\n#include <optional>\n#endif\n#if CLI11_EXPERIMENTAL_OPTIONAL\n#include <experimental/optional>\n#endif\n#if CLI11_BOOST_OPTIONAL\n#include <boost/optional.hpp>\n#include <boost/optional/optional_io.hpp>\n#endif\n\n\n// From CLI/Version.hpp:\n\n\n\n// From CLI/Macros.hpp:\n\n\n\n// From CLI/Optional.hpp:\n\nnamespace CLI\n{\n\n#if CLI11_STD_OPTIONAL\ntemplate <typename T> std::istream& operator>>(std::istream& in,\n    std::optional<T>& val)\n{\n  T v;\n  in >> v;\n  val = v;\n  return in;\n}\n#endif\n\n#if CLI11_EXPERIMENTAL_OPTIONAL\ntemplate <typename T> std::istream& operator>>(std::istream& in,\n    std::experimental::optional<T>& val)\n{\n  T v;\n  in >> v;\n  val = v;\n  return in;\n}\n#endif\n\n#if CLI11_BOOST_OPTIONAL\ntemplate <typename T> std::istream& operator>>(std::istream& in,\n    boost::optional<T>& val)\n{\n  T v;\n  in >> v;\n  val = v;\n  return in;\n}\n#endif\n\n// Export the best optional to the CLI namespace\n#if CLI11_STD_OPTIONAL\nusing std::optional;\n#elif CLI11_EXPERIMENTAL_OPTIONAL\nusing std::experimental::optional;\n#elif CLI11_BOOST_OPTIONAL\nusing boost::optional;\n#endif\n\n// This is true if any optional is found\n#if CLI11_STD_OPTIONAL || CLI11_EXPERIMENTAL_OPTIONAL || CLI11_BOOST_OPTIONAL\n#define CLI11_OPTIONAL 1\n#endif\n\n} // namespace CLI\n\n// From CLI/StringTools.hpp:\n\nnamespace CLI\n{\n\n/// Include the items in this namespace to get free conversion of enums to/from streams.\n/// (This is available inside CLI as well, so CLI11 will use this without a using statement).\nnamespace enums\n{\n\n/// output streaming for enumerations\ntemplate <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type>\nstd::ostream & operator<<(std::ostream& in, const T& item)\n{\n  // make sure this is out of the detail namespace otherwise it won't be found when needed\n  return in << static_cast<typename std::underlying_type<T>::type>(item);\n}\n\n/// input streaming for enumerations\ntemplate <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type>\nstd::istream & operator>>(std::istream& in, T& item)\n{\n  typename std::underlying_type<T>::type i;\n  in >> i;\n  item = static_cast<T>(i);\n  return in;\n}\n} // namespace enums\n\n/// Export to CLI namespace\nusing namespace enums;\n\nnamespace detail\n{\n\n// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c\n/// Split a string by a delim\ninline std::vector<std::string> split(const std::string& s, char delim)\n{\n  std::vector<std::string> elems;\n\n  // Check to see if empty string, give consistent result\n  if (s.empty()) {\n    elems.emplace_back();\n  } else {\n    std::stringstream ss;\n    ss.str(s);\n    std::string item;\n\n    while (std::getline(ss, item, delim)) {\n      elems.push_back(item);\n    }\n  }\n\n  return elems;\n}\n/// simple utility to convert various types to a string\ntemplate <typename T> inline std::string as_string(const T& v)\n{\n  std::ostringstream s;\n  s << v;\n  return s.str();\n}\n// if the data type is already a string just forward it\ntemplate <typename T, typename = typename std::enable_if<std::is_constructible<std::string, T>::value>::type>\ninline auto as_string(T && v) -> decltype(std::forward<T>(v))\n{\n  return std::forward<T>(v);\n}\n\n/// Simple function to join a string\ntemplate <typename T> std::string join(const T& v, std::string delim = \",\")\n{\n  std::ostringstream s;\n  auto beg = std::begin(v);\n  auto end = std::end(v);\n\n  if (beg != end) {\n    s << *beg++;\n  }\n\n  while (beg != end) {\n    s << delim << *beg++;\n  }\n\n  return s.str();\n}\n\n/// Simple function to join a string from processed elements\ntemplate < typename T,\n           typename Callable,\n           typename = typename std::enable_if <\n             !std::is_constructible<std::string, Callable>::value >::type >\nstd::string join(const T& v, Callable func, std::string delim = \",\")\n{\n  std::ostringstream s;\n  auto beg = std::begin(v);\n  auto end = std::end(v);\n\n  if (beg != end) {\n    s << func(*beg++);\n  }\n\n  while (beg != end) {\n    s << delim << func(*beg++);\n  }\n\n  return s.str();\n}\n\n/// Join a string in reverse order\ntemplate <typename T> std::string rjoin(const T& v, std::string delim = \",\")\n{\n  std::ostringstream s;\n\n  for (size_t start = 0; start < v.size(); start++) {\n    if (start > 0) {\n      s << delim;\n    }\n\n    s << v[v.size() - start - 1];\n  }\n\n  return s.str();\n}\n\n// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string\n\n/// Trim whitespace from left of string\ninline std::string& ltrim(std::string& str)\n{\n  auto it = std::find_if(str.begin(), str.end(), [](char ch) {\n    return !std::isspace<char>(ch, std::locale());\n  });\n  str.erase(str.begin(), it);\n  return str;\n}\n\n/// Trim anything from left of string\ninline std::string& ltrim(std::string& str, const std::string& filter)\n{\n  auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) {\n    return filter.find(ch) == std::string::npos;\n  });\n  str.erase(str.begin(), it);\n  return str;\n}\n\n/// Trim whitespace from right of string\ninline std::string& rtrim(std::string& str)\n{\n  auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) {\n    return !std::isspace<char>(ch, std::locale());\n  });\n  str.erase(it.base(), str.end());\n  return str;\n}\n\n/// Trim anything from right of string\ninline std::string& rtrim(std::string& str, const std::string& filter)\n{\n  auto it =\n  std::find_if(str.rbegin(), str.rend(), [&filter](char ch) {\n    return filter.find(ch) == std::string::npos;\n  });\n  str.erase(it.base(), str.end());\n  return str;\n}\n\n/// Trim whitespace from string\ninline std::string& trim(std::string& str)\n{\n  return ltrim(rtrim(str));\n}\n\n/// Trim anything from string\ninline std::string& trim(std::string& str, const std::string filter)\n{\n  return ltrim(rtrim(str, filter), filter);\n}\n\n/// Make a copy of the string and then trim it\ninline std::string trim_copy(const std::string& str)\n{\n  std::string s = str;\n  return trim(s);\n}\n\n/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered)\ninline std::string trim_copy(const std::string& str, const std::string& filter)\n{\n  std::string s = str;\n  return trim(s, filter);\n}\n/// Print a two part \"help\" string\ninline std::ostream& format_help(std::ostream& out, std::string name,\n                                 std::string description, size_t wid)\n{\n  name = \"  \" + name;\n  out << std::setw(static_cast<int>(wid)) << std::left << name;\n\n  if (!description.empty()) {\n    if (name.length() >= wid) {\n      out << \"\\n\" << std::setw(static_cast<int>(wid)) << \"\";\n    }\n\n    for (const char c : description) {\n      out.put(c);\n\n      if (c == '\\n') {\n        out << std::setw(static_cast<int>(wid)) << \"\";\n      }\n    }\n  }\n\n  out << \"\\n\";\n  return out;\n}\n\n/// Verify the first character of an option\ntemplate <typename T> bool valid_first_char(T c)\n{\n  return std::isalnum(c, std::locale()) || c == '_' || c == '?' || c == '@';\n}\n\n/// Verify following characters of an option\ntemplate <typename T> bool valid_later_char(T c)\n{\n  return valid_first_char(c) || c == '.' || c == '-';\n}\n\n/// Verify an option name\ninline bool valid_name_string(const std::string& str)\n{\n  if (str.empty() || !valid_first_char(str[0])) {\n    return false;\n  }\n\n  for (auto c : str.substr(1))\n    if (!valid_later_char(c)) {\n      return false;\n    }\n\n  return true;\n}\n\n/// Verify that str consists of letters only\ninline bool isalpha(const std::string& str)\n{\n  return std::all_of(str.begin(), str.end(), [](char c) {\n    return std::isalpha(c, std::locale());\n  });\n}\n\n/// Return a lower case version of a string\ninline std::string to_lower(std::string str)\n{\n  std::transform(std::begin(str), std::end(str),\n  std::begin(str), [](const std::string::value_type & x) {\n    return std::tolower(x, std::locale());\n  });\n  return str;\n}\n\n/// remove underscores from a string\ninline std::string remove_underscore(std::string str)\n{\n  str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str));\n  return str;\n}\n\n/// Find and replace a substring with another substring\ninline std::string find_and_replace(std::string str, std::string from,\n                                    std::string to)\n{\n  size_t start_pos = 0;\n\n  while ((start_pos = str.find(from, start_pos)) != std::string::npos) {\n    str.replace(start_pos, from.length(), to);\n    start_pos += to.length();\n  }\n\n  return str;\n}\n\n/// check if the flag definitions has possible false flags\ninline bool has_default_flag_values(const std::string& flags)\n{\n  return (flags.find_first_of(\"{!\") != std::string::npos);\n}\n\ninline void remove_default_flag_values(std::string& flags)\n{\n  auto loc = flags.find_first_of('{');\n\n  while (loc != std::string::npos) {\n    auto finish = flags.find_first_of(\"},\", loc + 1);\n\n    if ((finish != std::string::npos) && (flags[finish] == '}')) {\n      flags.erase(flags.begin() + static_cast<std::ptrdiff_t>(loc),\n                  flags.begin() + static_cast<std::ptrdiff_t>(finish) + 1);\n    }\n\n    loc = flags.find_first_of('{', loc + 1);\n  }\n\n  flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end());\n}\n\n/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores\ninline std::ptrdiff_t find_member(std::string name,\n                                  const std::vector<std::string> names,\n                                  bool ignore_case = false,\n                                  bool ignore_underscore = false)\n{\n  auto it = std::end(names);\n\n  if (ignore_case) {\n    if (ignore_underscore) {\n      name = detail::to_lower(detail::remove_underscore(name));\n      it = std::find_if(std::begin(names),\n      std::end(names), [&name](std::string local_name) {\n        return detail::to_lower(detail::remove_underscore(local_name)) == name;\n      });\n    } else {\n      name = detail::to_lower(name);\n      it = std::find_if(std::begin(names),\n      std::end(names), [&name](std::string local_name) {\n        return detail::to_lower(local_name) == name;\n      });\n    }\n  } else if (ignore_underscore) {\n    name = detail::remove_underscore(name);\n    it = std::find_if(std::begin(names),\n    std::end(names), [&name](std::string local_name) {\n      return detail::remove_underscore(local_name) == name;\n    });\n  } else {\n    it = std::find(std::begin(names), std::end(names), name);\n  }\n\n  return (it != std::end(names)) ? (it - std::begin(names)) : (-1);\n}\n\n/// Find a trigger string and call a modify callable function that takes the current string and starting position of the\n/// trigger and returns the position in the string to search for the next trigger string\ntemplate <typename Callable> inline std::string find_and_modify(std::string str,\n    std::string trigger, Callable modify)\n{\n  size_t start_pos = 0;\n\n  while ((start_pos = str.find(trigger, start_pos)) != std::string::npos) {\n    start_pos = modify(str, start_pos);\n  }\n\n  return str;\n}\n\n/// Split a string '\"one two\" \"three\"' into 'one two', 'three'\n/// Quote characters can be ` ' or \"\ninline std::vector<std::string> split_up(std::string str)\n{\n  const std::string delims(\"\\'\\\"`\");\n  auto find_ws = [](char ch) {\n    return std::isspace<char>(ch, std::locale());\n  };\n  trim(str);\n  std::vector<std::string> output;\n  bool embeddedQuote = false;\n  char keyChar = ' ';\n\n  while (!str.empty()) {\n    if (delims.find_first_of(str[0]) != std::string::npos) {\n      keyChar = str[0];\n      auto end = str.find_first_of(keyChar, 1);\n\n      while ((end != std::string::npos) &&\n             (str[end - 1] == '\\\\')) { // deal with escaped quotes\n        end = str.find_first_of(keyChar, end + 1);\n        embeddedQuote = true;\n      }\n\n      if (end != std::string::npos) {\n        output.push_back(str.substr(1, end - 1));\n        str = str.substr(end + 1);\n      } else {\n        output.push_back(str.substr(1));\n        str = \"\";\n      }\n    } else {\n      auto it = std::find_if(std::begin(str), std::end(str), find_ws);\n\n      if (it != std::end(str)) {\n        std::string value = std::string(str.begin(), it);\n        output.push_back(value);\n        str = std::string(it, str.end());\n      } else {\n        output.push_back(str);\n        str = \"\";\n      }\n    }\n\n    // transform any embedded quotes into the regular character\n    if (embeddedQuote) {\n      output.back() = find_and_replace(output.back(), std::string(\"\\\\\") + keyChar,\n                                       std::string(1, keyChar));\n      embeddedQuote = false;\n    }\n\n    trim(str);\n  }\n\n  return output;\n}\n\n/// Add a leader to the beginning of all new lines (nothing is added\n/// at the start of the first line). `\"; \"` would be for ini files\n///\n/// Can't use Regex, or this would be a subs.\ninline std::string fix_newlines(std::string leader, std::string input)\n{\n  std::string::size_type n = 0;\n\n  while (n != std::string::npos && n < input.size()) {\n    n = input.find('\\n', n);\n\n    if (n != std::string::npos) {\n      input = input.substr(0, n + 1) + leader + input.substr(n + 1);\n      n += leader.size();\n    }\n  }\n\n  return input;\n}\n\n/// This function detects an equal or colon followed by an escaped quote after an argument\n/// then modifies the string to replace the equality with a space.  This is needed\n/// to allow the split up function to work properly and is intended to be used with the find_and_modify function\n/// the return value is the offset+1 which is required by the find_and_modify function.\ninline size_t escape_detect(std::string& str, size_t offset)\n{\n  auto next = str[offset + 1];\n\n  if ((next == '\\\"') || (next == '\\'') || (next == '`')) {\n    auto astart = str.find_last_of(\"-/ \\\"\\'`\", offset - 1);\n\n    if (astart != std::string::npos) {\n      if (str[astart] == ((str[offset] == '=') ? '-' : '/')) {\n        str[offset] = ' ';  // interpret this as a space so the split_up works properly\n      }\n    }\n  }\n\n  return offset + 1;\n}\n\n/// Add quotes if the string contains spaces\ninline std::string& add_quotes_if_needed(std::string& str)\n{\n  if ((str.front() != '\"' && str.front() != '\\'') || str.front() != str.back()) {\n    char quote = str.find('\"') < str.find('\\'') ? '\\'' : '\"';\n\n    if (str.find(' ') != std::string::npos) {\n      str.insert(0, 1, quote);\n      str.append(1, quote);\n    }\n  }\n\n  return str;\n}\n\n} // namespace detail\n\n} // namespace CLI\n\n// From CLI/Error.hpp:\n\nnamespace CLI\n{\n\n// Use one of these on all error classes.\n// These are temporary and are undef'd at the end of this file.\n#define CLI11_ERROR_DEF(parent, name)                                                                                  \\\n  protected:                                                                                                           \\\n    name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {}   \\\n    name(std::string ename, std::string msg, ExitCodes exit_code)                                                      \\\n        : parent(std::move(ename), std::move(msg), exit_code) {}                                                       \\\n                                                                                                                       \\\n  public:                                                                                                              \\\n    name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {}                           \\\n    name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {}\n\n// This is added after the one above if a class is used directly and builds its own message\n#define CLI11_ERROR_SIMPLE(name)                                                                                       \\\n    explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {}\n\n/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut,\n/// int values from e.get_error_code().\nenum class ExitCodes {\n  Success = 0,\n  IncorrectConstruction = 100,\n  BadNameString,\n  OptionAlreadyAdded,\n  FileError,\n  ConversionError,\n  ValidationError,\n  RequiredError,\n  RequiresError,\n  ExcludesError,\n  ExtrasError,\n  ConfigError,\n  InvalidError,\n  HorribleError,\n  OptionNotFound,\n  ArgumentMismatch,\n  BaseClass = 127\n};\n\n// Error definitions\n\n/// @defgroup error_group Errors\n/// @brief Errors thrown by CLI11\n///\n/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors.\n/// @{\n\n/// All errors derive from this one\nclass Error : public std::runtime_error\n{\n  int actual_exit_code;\n  std::string error_name{\"Error\"};\n\npublic:\n  int get_exit_code() const\n  {\n    return actual_exit_code;\n  }\n\n  std::string get_name() const\n  {\n    return error_name;\n  }\n\n  Error(std::string name, std::string msg,\n        int exit_code = static_cast<int>(ExitCodes::BaseClass))\n    : runtime_error(msg), actual_exit_code(exit_code),\n      error_name(std::move(name)) {}\n\n  Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg,\n        static_cast<int>(exit_code)) {}\n};\n\n// Note: Using Error::Error constructors does not work on GCC 4.7\n\n/// Construction errors (not in parsing)\nclass ConstructionError : public Error\n{\n  CLI11_ERROR_DEF(Error, ConstructionError)\n};\n\n/// Thrown when an option is set to conflicting values (non-vector and multi args, for example)\nclass IncorrectConstruction : public ConstructionError\n{\n  CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction)\n  CLI11_ERROR_SIMPLE(IncorrectConstruction)\n  static IncorrectConstruction PositionalFlag(std::string name)\n  {\n    return IncorrectConstruction(name + \": Flags cannot be positional\");\n  }\n  static IncorrectConstruction Set0Opt(std::string name)\n  {\n    return IncorrectConstruction(name +\n                                 \": Cannot set 0 expected, use a flag instead\");\n  }\n  static IncorrectConstruction SetFlag(std::string name)\n  {\n    return IncorrectConstruction(name +\n                                 \": Cannot set an expected number for flags\");\n  }\n  static IncorrectConstruction ChangeNotVector(std::string name)\n  {\n    return IncorrectConstruction(name +\n                                 \": You can only change the expected arguments for vectors\");\n  }\n  static IncorrectConstruction AfterMultiOpt(std::string name)\n  {\n    return IncorrectConstruction(\n             name + \": You can't change expected arguments after you've changed the multi option policy!\");\n  }\n  static IncorrectConstruction MissingOption(std::string name)\n  {\n    return IncorrectConstruction(\"Option \" + name + \" is not defined\");\n  }\n  static IncorrectConstruction MultiOptionPolicy(std::string name)\n  {\n    return IncorrectConstruction(name +\n                                 \": multi_option_policy only works for flags and exact value options\");\n  }\n};\n\n/// Thrown on construction of a bad name\nclass BadNameString : public ConstructionError\n{\n  CLI11_ERROR_DEF(ConstructionError, BadNameString)\n  CLI11_ERROR_SIMPLE(BadNameString)\n  static BadNameString OneCharName(std::string name)\n  {\n    return BadNameString(\"Invalid one char name: \" + name);\n  }\n  static BadNameString BadLongName(std::string name)\n  {\n    return BadNameString(\"Bad long name: \" + name);\n  }\n  static BadNameString DashesOnly(std::string name)\n  {\n    return BadNameString(\"Must have a name, not just dashes: \" + name);\n  }\n  static BadNameString MultiPositionalNames(std::string name)\n  {\n    return BadNameString(\"Only one positional name allowed, remove: \" + name);\n  }\n};\n\n/// Thrown when an option already exists\nclass OptionAlreadyAdded : public ConstructionError\n{\n  CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded)\n  explicit OptionAlreadyAdded(std::string name)\n    : OptionAlreadyAdded(name + \" is already added\",\n                         ExitCodes::OptionAlreadyAdded) {}\n  static OptionAlreadyAdded Requires(std::string name, std::string other)\n  {\n    return OptionAlreadyAdded(name + \" requires \" + other,\n                              ExitCodes::OptionAlreadyAdded);\n  }\n  static OptionAlreadyAdded Excludes(std::string name, std::string other)\n  {\n    return OptionAlreadyAdded(name + \" excludes \" + other,\n                              ExitCodes::OptionAlreadyAdded);\n  }\n};\n\n// Parsing errors\n\n/// Anything that can error in Parse\nclass ParseError : public Error\n{\n  CLI11_ERROR_DEF(Error, ParseError)\n};\n\n// Not really \"errors\"\n\n/// This is a successful completion on parsing, supposed to exit\nclass Success : public ParseError\n{\n  CLI11_ERROR_DEF(ParseError, Success)\n  Success() : Success(\"Successfully completed, should be caught and quit\",\n                        ExitCodes::Success) {}\n};\n\n/// -h or --help on command line\nclass CallForHelp : public ParseError\n{\n  CLI11_ERROR_DEF(ParseError, CallForHelp)\n  CallForHelp() :\n    CallForHelp(\"This should be caught in your main function, see examples\",\n                ExitCodes::Success) {}\n};\n\n/// Usually something like --help-all on command line\nclass CallForAllHelp : public ParseError\n{\n  CLI11_ERROR_DEF(ParseError, CallForAllHelp)\n  CallForAllHelp()\n    : CallForAllHelp(\"This should be caught in your main function, see examples\",\n                     ExitCodes::Success) {}\n};\n\n/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code.\nclass RuntimeError : public ParseError\n{\n  CLI11_ERROR_DEF(ParseError, RuntimeError)\n  explicit RuntimeError(int exit_code = 1) : RuntimeError(\"Runtime error\",\n        exit_code) {}\n};\n\n/// Thrown when parsing an INI file and it is missing\nclass FileError : public ParseError\n{\n  CLI11_ERROR_DEF(ParseError, FileError)\n  CLI11_ERROR_SIMPLE(FileError)\n  static FileError Missing(std::string name)\n  {\n    return FileError(name + \" was not readable (missing?)\");\n  }\n};\n\n/// Thrown when conversion call back fails, such as when an int fails to coerce to a string\nclass ConversionError : public ParseError\n{\n  CLI11_ERROR_DEF(ParseError, ConversionError)\n  CLI11_ERROR_SIMPLE(ConversionError)\n  ConversionError(std::string member, std::string name)\n    : ConversionError(\"The value \" + member + \" is not an allowed value for \" +\n                      name) {}\n  ConversionError(std::string name, std::vector<std::string> results)\n    : ConversionError(\"Could not convert: \" + name + \" = \" + detail::join(\n                        results)) {}\n  static ConversionError TooManyInputsFlag(std::string name)\n  {\n    return ConversionError(name + \": too many inputs for a flag\");\n  }\n  static ConversionError TrueFalse(std::string name)\n  {\n    return ConversionError(name + \": Should be true/false or a number\");\n  }\n};\n\n/// Thrown when validation of results fails\nclass ValidationError : public ParseError\n{\n  CLI11_ERROR_DEF(ParseError, ValidationError)\n  CLI11_ERROR_SIMPLE(ValidationError)\n  explicit ValidationError(std::string name,\n                           std::string msg) : ValidationError(name + \": \" + msg) {}\n};\n\n/// Thrown when a required option is missing\nclass RequiredError : public ParseError\n{\n  CLI11_ERROR_DEF(ParseError, RequiredError)\n  explicit RequiredError(std::string name) : RequiredError(name + \" is required\",\n        ExitCodes::RequiredError) {}\n  static RequiredError Subcommand(size_t min_subcom)\n  {\n    if (min_subcom == 1) {\n      return RequiredError(\"A subcommand\");\n    } else\n      return RequiredError(\"Requires at least \" + std::to_string(\n                             min_subcom) + \" subcommands\",\n                           ExitCodes::RequiredError);\n  }\n  static RequiredError Option(size_t min_option, size_t max_option, size_t used,\n                              const std::string& option_list)\n  {\n    if ((min_option == 1) && (max_option == 1) && (used == 0)) {\n      return RequiredError(\"Exactly 1 option from [\" + option_list + \"]\");\n    } else if ((min_option == 1) && (max_option == 1) && (used > 1))\n      return RequiredError(\"Exactly 1 option from [\" + option_list +\n                           \"] is required and \" + std::to_string(used) +\n                           \" were given\",\n                           ExitCodes::RequiredError);\n    else if ((min_option == 1) && (used == 0)) {\n      return RequiredError(\"At least 1 option from [\" + option_list + \"]\");\n    } else if (used < min_option)\n      return RequiredError(\"Requires at least \" + std::to_string(\n                             min_option) + \" options used and only \" +\n                           std::to_string(used) + \"were given from [\" + option_list + \"]\",\n                           ExitCodes::RequiredError);\n    else if (max_option == 1)\n      return RequiredError(\"Requires at most 1 options be given from [\" + option_list\n                           + \"]\",\n                           ExitCodes::RequiredError);\n    else\n      return RequiredError(\"Requires at most \" + std::to_string(\n                             max_option) + \" options be used and \" +\n                           std::to_string(used) + \"were given from [\" + option_list + \"]\",\n                           ExitCodes::RequiredError);\n  }\n};\n\n/// Thrown when the wrong number of arguments has been received\nclass ArgumentMismatch : public ParseError\n{\n  CLI11_ERROR_DEF(ParseError, ArgumentMismatch)\n  CLI11_ERROR_SIMPLE(ArgumentMismatch)\n  ArgumentMismatch(std::string name, int expected, size_t recieved)\n    : ArgumentMismatch(expected > 0 ? (\"Expected exactly \" + std::to_string(\n                                         expected) + \" arguments to \" + name +\n                                       \", got \" + std::to_string(recieved))\n                       : (\"Expected at least \" + std::to_string(-expected) + \" arguments to \" + name +\n                          \", got \" + std::to_string(recieved)),\n                       ExitCodes::ArgumentMismatch) {}\n\n  static ArgumentMismatch AtLeast(std::string name, int num)\n  {\n    return ArgumentMismatch(name + \": At least \" + std::to_string(\n                              num) + \" required\");\n  }\n  static ArgumentMismatch TypedAtLeast(std::string name, int num,\n                                       std::string type)\n  {\n    return ArgumentMismatch(name + \": \" + std::to_string(num) + \" required \" + type\n                            + \" missing\");\n  }\n  static ArgumentMismatch FlagOverride(std::string name)\n  {\n    return ArgumentMismatch(name + \" was given a disallowed flag override\");\n  }\n};\n\n/// Thrown when a requires option is missing\nclass RequiresError : public ParseError\n{\n  CLI11_ERROR_DEF(ParseError, RequiresError)\n  RequiresError(std::string curname, std::string subname)\n    : RequiresError(curname + \" requires \" + subname, ExitCodes::RequiresError) {}\n};\n\n/// Thrown when an excludes option is present\nclass ExcludesError : public ParseError\n{\n  CLI11_ERROR_DEF(ParseError, ExcludesError)\n  ExcludesError(std::string curname, std::string subname)\n    : ExcludesError(curname + \" excludes \" + subname, ExitCodes::ExcludesError) {}\n};\n\n/// Thrown when too many positionals or options are found\nclass ExtrasError : public ParseError\n{\n  CLI11_ERROR_DEF(ParseError, ExtrasError)\n  explicit ExtrasError(std::vector<std::string> args)\n    : ExtrasError((args.size() > 1 ? \"The following arguments were not expected: \"\n                   : \"The following argument was not expected: \") +\n                  detail::rjoin(args, \" \"),\n                  ExitCodes::ExtrasError) {}\n};\n\n/// Thrown when extra values are found in an INI file\nclass ConfigError : public ParseError\n{\n  CLI11_ERROR_DEF(ParseError, ConfigError)\n  CLI11_ERROR_SIMPLE(ConfigError)\n  static ConfigError Extras(std::string item)\n  {\n    return ConfigError(\"INI was not able to parse \" + item);\n  }\n  static ConfigError NotConfigurable(std::string item)\n  {\n    return ConfigError(item +\n                       \": This option is not allowed in a configuration file\");\n  }\n};\n\n/// Thrown when validation fails before parsing\nclass InvalidError : public ParseError\n{\n  CLI11_ERROR_DEF(ParseError, InvalidError)\n  explicit InvalidError(std::string name)\n    : InvalidError(name +\n                   \": Too many positional arguments with unlimited expected args\",\n                   ExitCodes::InvalidError)\n  {\n  }\n};\n\n/// This is just a safety check to verify selection and parsing match - you should not ever see it\n/// Strings are directly added to this error, but again, it should never be seen.\nclass HorribleError : public ParseError\n{\n  CLI11_ERROR_DEF(ParseError, HorribleError)\n  CLI11_ERROR_SIMPLE(HorribleError)\n};\n\n// After parsing\n\n/// Thrown when counting a non-existent option\nclass OptionNotFound : public Error\n{\n  CLI11_ERROR_DEF(Error, OptionNotFound)\n  explicit OptionNotFound(std::string name) : OptionNotFound(name + \" not found\",\n        ExitCodes::OptionNotFound) {}\n};\n\n#undef CLI11_ERROR_DEF\n#undef CLI11_ERROR_SIMPLE\n\n/// @}\n\n} // namespace CLI\n\n// From CLI/TypeTools.hpp:\n\nnamespace CLI\n{\n\n// Type tools\n\n// Utilities for type enabling\nnamespace detail\n{\n// Based generally on https://rmf.io/cxx11/almost-static-if\n/// Simple empty scoped class\nenum class enabler {};\n\n/// An instance to use in EnableIf\nconstexpr enabler dummy = {};\n} // namespace detail\n\n/// A copy of enable_if_t from C++14, compatible with C++11.\n///\n/// We could check to see if C++14 is being used, but it does not hurt to redefine this\n/// (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h)\n/// It is not in the std namespace anyway, so no harm done.\ntemplate <bool B, class T = void> using enable_if_t = typename\n    std::enable_if<B, T>::type;\n\n/// A copy of std::void_t from C++17 (helper for C++11 and C++14)\ntemplate <typename... Ts> struct make_void {\n  using type = void;\n};\n\n/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine\ntemplate <typename... Ts> using void_t = typename make_void<Ts...>::type;\n\n/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine\ntemplate <bool B, class T, class F> using conditional_t = typename\n    std::conditional<B, T, F>::type;\n\n/// Check to see if something is a vector (fail check by default)\ntemplate <typename T> struct is_vector : std::false_type {};\n\n/// Check to see if something is a vector (true if actually a vector)\ntemplate <class T, class A> struct is_vector<std::vector<T, A>> :\n      std::true_type {};\n\n/// Check to see if something is bool (fail check by default)\ntemplate <typename T> struct is_bool : std::false_type {};\n\n/// Check to see if something is bool (true if actually a bool)\ntemplate <> struct is_bool<bool> : std::true_type {};\n\n/// Check to see if something is a shared pointer\ntemplate <typename T> struct is_shared_ptr : std::false_type {};\n\n/// Check to see if something is a shared pointer (True if really a shared pointer)\ntemplate <typename T> struct is_shared_ptr<std::shared_ptr<T>> :\n      std::true_type {};\n\n/// Check to see if something is a shared pointer (True if really a shared pointer)\ntemplate <typename T> struct is_shared_ptr<const std::shared_ptr<T>> :\n        std::true_type {};\n\n/// Check to see if something is copyable pointer\ntemplate <typename T> struct is_copyable_ptr {\n  static bool const value = is_shared_ptr<T>::value || std::is_pointer<T>::value;\n};\n\n/// This can be specialized to override the type deduction for IsMember.\ntemplate <typename T> struct IsMemberType {\n  using type = T;\n};\n\n/// The main custom type needed here is const char * should be a string.\ntemplate <> struct IsMemberType<const char*> {\n  using type = std::string;\n};\n\nnamespace detail\n{\n\n// These are utilities for IsMember\n\n/// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that\n/// pointer_traits<T> be valid.\ntemplate <typename T> struct element_type {\n  using type =\n    typename std::conditional<is_copyable_ptr<T>::value, typename std::pointer_traits<T>::element_type, T>::type;\n};\n\n/// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of\n/// the container\ntemplate <typename T> struct element_value_type {\n  using type = typename element_type<T>::type::value_type;\n};\n\n/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing.\ntemplate <typename T, typename _ = void> struct pair_adaptor : std::false_type {\n  using value_type = typename T::value_type;\n  using first_type = typename std::remove_const<value_type>::type;\n  using second_type = typename std::remove_const<value_type>::type;\n\n  /// Get the first value (really just the underlying value)\n  template <typename Q> static auto first(Q&& pair_value) -> decltype(\n    std::forward<Q>(pair_value))\n  {\n    return std::forward<Q>(pair_value);\n  }\n  /// Get the second value (really just the underlying value)\n  template <typename Q> static auto second(Q&& pair_value) -> decltype(\n    std::forward<Q>(pair_value))\n  {\n    return std::forward<Q>(pair_value);\n  }\n};\n\n/// Adaptor for map-like structure (true version, must have key_type and mapped_type).\n/// This wraps a mapped container in a few utilities access it in a general way.\ntemplate <typename T>\nstruct pair_adaptor <\n  T,\n  conditional_t<false, void_t<typename T::value_type::first_type, typename T::value_type::second_type>, void\n      >>\n      : std::true_type {\n  using value_type = typename T::value_type;\n  using first_type = typename\n                     std::remove_const<typename value_type::first_type>::type;\n  using second_type = typename\n                      std::remove_const<typename value_type::second_type>::type;\n\n  /// Get the first value (really just the underlying value)\n  template <typename Q> static auto first(Q&& pair_value) -> decltype(std::get<0>\n      (std::forward<Q>(pair_value)))\n  {\n    return std::get<0>(std::forward<Q>(pair_value));\n  }\n  /// Get the second value (really just the underlying value)\n  template <typename Q> static auto second(Q&& pair_value) -> decltype(\n    std::get<1>(std::forward<Q>(pair_value)))\n  {\n    return std::get<1>(std::forward<Q>(pair_value));\n  }\n};\n\n// Check for streamability\n// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream\n\ntemplate <typename S, typename T> class is_streamable\n{\n  template <typename SS, typename TT>\n  static auto test(int) -> decltype(std::declval<SS&>() << std::declval<TT>(),\n                                    std::true_type());\n\n  template <typename, typename> static auto test(...) -> std::false_type;\n\npublic:\n  static const bool value = decltype(test<S, T>(0))::value;\n};\n\n/// Convert an object to a string (directly forward if this can become a string)\ntemplate <typename T, enable_if_t<std::is_constructible<std::string, T>::value, detail::enabler> = detail::dummy>\nauto to_string(T && value) -> decltype(std::forward<T>(value))\n{\n  return std::forward<T>(value);\n}\n\n/// Convert an object to a string (streaming must be supported for that type)\ntemplate < typename T,\n           enable_if_t < !std::is_constructible<std::string, T>::value&&\n                         is_streamable<std::stringstream, T>::value,\n                         detail::enabler > = detail::dummy >\nstd::string to_string(T && value)\n{\n  std::stringstream stream;\n  stream << value;\n  return stream.str();\n}\n\n/// If conversion is not supported, return an empty string (streaming is not supported for that type)\ntemplate < typename T,\n           enable_if_t < !std::is_constructible<std::string, T>::value&&\n                         !is_streamable<std::stringstream, T>::value,\n                         detail::enabler > = detail::dummy >\nstd::string to_string(T&&)\n{\n  return std::string{};\n}\n\n// Type name print\n\n/// Was going to be based on\n///  http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template\n/// But this is cleaner and works better in this case\n\ntemplate < typename T,\n           enable_if_t < std::is_integral<T>::value&& std::is_signed<T>::value,\n                         detail::enabler > = detail::dummy >\nconstexpr const char* type_name()\n{\n  return \"INT\";\n}\n\ntemplate < typename T,\n           enable_if_t < std::is_integral<T>::value&& std::is_unsigned<T>::value,\n                         detail::enabler > = detail::dummy >\nconstexpr const char* type_name()\n{\n  return \"UINT\";\n}\n\ntemplate <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy>\nconstexpr const char* type_name()\n{\n  return \"FLOAT\";\n}\n\n/// This one should not be used, since vector types print the internal type\ntemplate <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>\nconstexpr const char* type_name()\n{\n  return \"VECTOR\";\n}\n/// Print name for enumeration types\ntemplate <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy>\nconstexpr const char* type_name()\n{\n  return \"ENUM\";\n}\n\n/// Print for all other types\ntemplate < typename T,\n           enable_if_t < !std::is_floating_point<T>::value&& !std::is_integral<T>::value&&\n                         !is_vector<T>::value&&\n                         !std::is_enum<T>::value,\n                         detail::enabler > = detail::dummy >\nconstexpr const char* type_name()\n{\n  return \"TEXT\";\n}\n\n// Lexical cast\n\n/// Convert a flag into an integer value  typically binary flags\ninline int64_t to_flag_value(std::string val)\n{\n  static const std::string trueString(\"true\");\n  static const std::string falseString(\"false\");\n\n  if (val == trueString) {\n    return 1;\n  }\n\n  if (val == falseString) {\n    return -1;\n  }\n\n  val = detail::to_lower(val);\n  int64_t ret;\n\n  if (val.size() == 1) {\n    switch (val[0]) {\n    case '0':\n    case 'f':\n    case 'n':\n    case '-':\n      ret = -1;\n      break;\n\n    case '1':\n    case 't':\n    case 'y':\n    case '+':\n      ret = 1;\n      break;\n\n    case '2':\n    case '3':\n    case '4':\n    case '5':\n    case '6':\n    case '7':\n    case '8':\n    case '9':\n      ret = val[0] - '0';\n      break;\n\n    default:\n      throw std::invalid_argument(\"unrecognized character\");\n    }\n\n    return ret;\n  }\n\n  if (val == trueString || val == \"on\" || val == \"yes\" || val == \"enable\") {\n    ret = 1;\n  } else if (val == falseString || val == \"off\" || val == \"no\" ||\n             val == \"disable\") {\n    ret = -1;\n  } else {\n    ret = std::stoll(val);\n  }\n\n  return ret;\n}\n\n/// Signed integers\ntemplate <\n  typename T,\n  enable_if_t < std::is_integral<T>::value&& std::is_signed<T>::value&&\n                !is_bool<T>::value&& !std::is_enum<T>::value,\n                detail::enabler > = detail::dummy >\nbool lexical_cast(std::string input, T& output)\n{\n  try {\n    size_t n = 0;\n    long long output_ll = std::stoll(input, &n, 0);\n    output = static_cast<T>(output_ll);\n    return n == input.size() && static_cast<long long>(output) == output_ll;\n  } catch (const std::invalid_argument&) {\n    return false;\n  } catch (const std::out_of_range&) {\n    return false;\n  }\n}\n\n/// Unsigned integers\ntemplate < typename T,\n           enable_if_t < std::is_integral<T>::value&& std::is_unsigned<T>::value&&\n                         !is_bool<T>::value, detail::enabler > =\n           detail::dummy >\nbool lexical_cast(std::string input, T& output)\n{\n  if (!input.empty() && input.front() == '-') {\n    return false;  // std::stoull happily converts negative values to junk without any errors.\n  }\n\n  try {\n    size_t n = 0;\n    unsigned long long output_ll = std::stoull(input, &n, 0);\n    output = static_cast<T>(output_ll);\n    return n == input.size() &&\n           static_cast<unsigned long long>(output) == output_ll;\n  } catch (const std::invalid_argument&) {\n    return false;\n  } catch (const std::out_of_range&) {\n    return false;\n  }\n}\n\n/// Boolean values\ntemplate <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy>\nbool lexical_cast(std::string input, T& output)\n{\n  try {\n    auto out = to_flag_value(input);\n    output = (out > 0);\n    return true;\n  } catch (const std::invalid_argument&) {\n    return false;\n  }\n}\n\n/// Floats\ntemplate <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy>\nbool lexical_cast(std::string input, T& output)\n{\n  try {\n    size_t n = 0;\n    output = static_cast<T>(std::stold(input, &n));\n    return n == input.size();\n  } catch (const std::invalid_argument&) {\n    return false;\n  } catch (const std::out_of_range&) {\n    return false;\n  }\n}\n\n/// String and similar\ntemplate < typename T,\n           enable_if_t < !std::is_floating_point<T>::value&& !std::is_integral<T>::value&&\n                         std::is_assignable<T&, std::string>::value,\n                         detail::enabler > = detail::dummy >\nbool lexical_cast(std::string input, T& output)\n{\n  output = input;\n  return true;\n}\n\n/// Enumerations\ntemplate <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy>\nbool lexical_cast(std::string input, T& output)\n{\n  typename std::underlying_type<T>::type val;\n  bool retval = detail::lexical_cast(input, val);\n\n  if (!retval) {\n    return false;\n  }\n\n  output = static_cast<T>(val);\n  return true;\n}\n\n/// Non-string parsable\ntemplate < typename T,\n           enable_if_t < !std::is_floating_point<T>::value&& !std::is_integral<T>::value&&\n                         !std::is_assignable<T&, std::string>::value&& !std::is_enum<T>::value,\n                         detail::enabler > = detail::dummy >\nbool lexical_cast(std::string input, T& output)\n{\n  std::istringstream is;\n  is.str(input);\n  is >> output;\n  return !is.fail() && !is.rdbuf()->in_avail();\n}\n\n/// Sum a vector of flag representations\n/// The flag vector produces a series of strings in a vector,  simple true is represented by a \"1\",  simple false is by\n/// \"-1\" an if numbers are passed by some fashion they are captured as well so the function just checks for the most\n/// common true and false strings then uses stoll to convert the rest for summing\ntemplate < typename T,\n           enable_if_t < std::is_integral<T>::value&& std::is_unsigned<T>::value,\n                         detail::enabler > = detail::dummy >\nvoid sum_flag_vector(const std::vector<std::string>& flags, T& output)\n{\n  int64_t count{0};\n\n  for (auto& flag : flags) {\n    count += detail::to_flag_value(flag);\n  }\n\n  output = (count > 0) ? static_cast<T>(count) : T{0};\n}\n\n/// Sum a vector of flag representations\n/// The flag vector produces a series of strings in a vector,  simple true is represented by a \"1\",  simple false is by\n/// \"-1\" an if numbers are passed by some fashion they are captured as well so the function just checks for the most\n/// common true and false strings then uses stoll to convert the rest for summing\ntemplate < typename T,\n           enable_if_t < std::is_integral<T>::value&& std::is_signed<T>::value,\n                         detail::enabler > = detail::dummy >\nvoid sum_flag_vector(const std::vector<std::string>& flags, T& output)\n{\n  int64_t count{0};\n\n  for (auto& flag : flags) {\n    count += detail::to_flag_value(flag);\n  }\n\n  output = static_cast<T>(count);\n}\n\n} // namespace detail\n} // namespace CLI\n\n// From CLI/Split.hpp:\n\nnamespace CLI\n{\nnamespace detail\n{\n\n// Returns false if not a short option. Otherwise, sets opt name and rest and returns true\ninline bool split_short(const std::string& current, std::string& name,\n                        std::string& rest)\n{\n  if (current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) {\n    name = current.substr(1, 1);\n    rest = current.substr(2);\n    return true;\n  } else {\n    return false;\n  }\n}\n\n// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true\ninline bool split_long(const std::string& current, std::string& name,\n                       std::string& value)\n{\n  if (current.size() > 2 && current.substr(0, 2) == \"--\" &&\n      valid_first_char(current[2])) {\n    auto loc = current.find_first_of('=');\n\n    if (loc != std::string::npos) {\n      name = current.substr(2, loc - 2);\n      value = current.substr(loc + 1);\n    } else {\n      name = current.substr(2);\n      value = \"\";\n    }\n\n    return true;\n  } else {\n    return false;\n  }\n}\n\n// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true\ninline bool split_windows_style(const std::string& current, std::string& name,\n                                std::string& value)\n{\n  if (current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) {\n    auto loc = current.find_first_of(':');\n\n    if (loc != std::string::npos) {\n      name = current.substr(1, loc - 1);\n      value = current.substr(loc + 1);\n    } else {\n      name = current.substr(1);\n      value = \"\";\n    }\n\n    return true;\n  } else {\n    return false;\n  }\n}\n\n// Splits a string into multiple long and short names\ninline std::vector<std::string> split_names(std::string current)\n{\n  std::vector<std::string> output;\n  size_t val;\n\n  while ((val = current.find(\",\")) != std::string::npos) {\n    output.push_back(trim_copy(current.substr(0, val)));\n    current = current.substr(val + 1);\n  }\n\n  output.push_back(trim_copy(current));\n  return output;\n}\n\n/// extract default flag values either {def} or starting with a !\ninline std::vector<std::pair<std::string, std::string>> get_default_flag_values(\n      const std::string& str)\n{\n  std::vector<std::string> flags = split_names(str);\n  flags.erase(std::remove_if(flags.begin(),\n                             flags.end(),\n  [](const std::string & name) {\n    return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) &&\n                                  (name.back() == '}')) ||\n                                 (name[0] == '!'))));\n  }),\n  flags.end());\n  std::vector<std::pair<std::string, std::string>> output;\n  output.reserve(flags.size());\n\n  for (auto& flag : flags) {\n    auto def_start = flag.find_first_of('{');\n    std::string defval = \"false\";\n\n    if ((def_start != std::string::npos) && (flag.back() == '}')) {\n      defval = flag.substr(def_start + 1);\n      defval.pop_back();\n      flag.erase(def_start, std::string::npos);\n    }\n\n    flag.erase(0, flag.find_first_not_of(\"-!\"));\n    output.emplace_back(flag, defval);\n  }\n\n  return output;\n}\n\n/// Get a vector of short names, one of long names, and a single name\ninline std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>\nget_names(const std::vector<std::string>& input)\n{\n  std::vector<std::string> short_names;\n  std::vector<std::string> long_names;\n  std::string pos_name;\n\n  for (std::string name : input) {\n    if (name.length() == 0) {\n      continue;\n    } else if (name.length() > 1 && name[0] == '-' && name[1] != '-') {\n      if (name.length() == 2 && valid_first_char(name[1])) {\n        short_names.emplace_back(1, name[1]);\n      } else {\n        throw BadNameString::OneCharName(name);\n      }\n    } else if (name.length() > 2 && name.substr(0, 2) == \"--\") {\n      name = name.substr(2);\n\n      if (valid_name_string(name)) {\n        long_names.push_back(name);\n      } else {\n        throw BadNameString::BadLongName(name);\n      }\n    } else if (name == \"-\" || name == \"--\") {\n      throw BadNameString::DashesOnly(name);\n    } else {\n      if (pos_name.length() > 0) {\n        throw BadNameString::MultiPositionalNames(name);\n      }\n\n      pos_name = name;\n    }\n  }\n\n  return std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>\n         (\n           short_names, long_names, pos_name);\n}\n\n} // namespace detail\n} // namespace CLI\n\n// From CLI/ConfigFwd.hpp:\n\nnamespace CLI\n{\n\nclass App;\n\nnamespace detail\n{\n\n/// Comma separated join, adds quotes if needed\ninline std::string ini_join(std::vector<std::string> args)\n{\n  std::ostringstream s;\n  size_t start = 0;\n\n  for (const auto& arg : args) {\n    if (start++ > 0) {\n      s << \" \";\n    }\n\n    auto it = std::find_if(arg.begin(), arg.end(), [](char ch) {\n      return std::isspace<char>(ch, std::locale());\n    });\n\n    if (it == arg.end()) {\n      s << arg;\n    } else if (arg.find_first_of('\\\"') == std::string::npos) {\n      s << '\\\"' << arg << '\\\"';\n    } else {\n      s << '\\'' << arg << '\\'';\n    }\n  }\n\n  return s.str();\n}\n\n} // namespace detail\n\n/// Holds values to load into Options\nstruct ConfigItem {\n  /// This is the list of parents\n  std::vector<std::string> parents;\n\n  /// This is the name\n  std::string name;\n\n  /// Listing of inputs\n  std::vector<std::string> inputs;\n\n  /// The list of parents and name joined by \".\"\n  std::string fullname() const\n  {\n    std::vector<std::string> tmp = parents;\n    tmp.emplace_back(name);\n    return detail::join(tmp, \".\");\n  }\n};\n\n/// This class provides a converter for configuration files.\nclass Config\n{\nprotected:\n  std::vector<ConfigItem> items;\n\npublic:\n  /// Convert an app into a configuration\n  virtual std::string to_config(const App*, bool, bool, std::string) const = 0;\n\n  /// Convert a configuration into an app\n  virtual std::vector<ConfigItem> from_config(std::istream&) const = 0;\n\n  /// Get a flag value\n  virtual std::string to_flag(const ConfigItem& item) const\n  {\n    if (item.inputs.size() == 1) {\n      return item.inputs.at(0);\n    }\n\n    throw ConversionError::TooManyInputsFlag(item.fullname());\n  }\n\n  /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure\n  std::vector<ConfigItem> from_file(const std::string& name)\n  {\n    std::ifstream input{name};\n\n    if (!input.good()) {\n      throw FileError::Missing(name);\n    }\n\n    return from_config(input);\n  }\n\n  /// Virtual destructor\n  virtual ~Config() = default;\n};\n\n/// This converter works with INI files\nclass ConfigINI : public Config\n{\npublic:\n  std::string to_config(const App*, bool default_also, bool write_description,\n                        std::string prefix) const override;\n\n  std::vector<ConfigItem> from_config(std::istream& input) const override\n  {\n    std::string line;\n    std::string section = \"default\";\n    std::vector<ConfigItem> output;\n\n    while (getline(input, line)) {\n      std::vector<std::string> items_buffer;\n      detail::trim(line);\n      size_t len = line.length();\n\n      if (len > 1 && line[0] == '[' && line[len - 1] == ']') {\n        section = line.substr(1, len - 2);\n      } else if (len > 0 && line[0] != ';') {\n        output.emplace_back();\n        ConfigItem& out = output.back();\n        // Find = in string, split and recombine\n        auto pos = line.find('=');\n\n        if (pos != std::string::npos) {\n          out.name = detail::trim_copy(line.substr(0, pos));\n          std::string item = detail::trim_copy(line.substr(pos + 1));\n          items_buffer = detail::split_up(item);\n        } else {\n          out.name = detail::trim_copy(line);\n          items_buffer = {\"ON\"};\n        }\n\n        if (detail::to_lower(section) != \"default\") {\n          out.parents = {section};\n        }\n\n        if (out.name.find('.') != std::string::npos) {\n          std::vector<std::string> plist = detail::split(out.name, '.');\n          out.name = plist.back();\n          plist.pop_back();\n          out.parents.insert(out.parents.end(), plist.begin(), plist.end());\n        }\n\n        out.inputs.insert(std::end(out.inputs), std::begin(items_buffer),\n                          std::end(items_buffer));\n      }\n    }\n\n    return output;\n  }\n};\n\n} // namespace CLI\n\n// From CLI/Validators.hpp:\n\nnamespace CLI\n{\n\nclass Option;\n\n/// @defgroup validator_group Validators\n\n/// @brief Some validators that are provided\n///\n/// These are simple `std::string(const std::string&)` validators that are useful. They return\n/// a string if the validation fails. A custom struct is provided, as well, with the same user\n/// semantics, but with the ability to provide a new type name.\n/// @{\n\n///\nclass Validator\n{\nprotected:\n  /// This is the description function, if empty the description_ will be used\n  std::function<std::string()> desc_function_{[]()\n  {\n    return std::string{};\n  }};\n\n  /// This it the base function that is to be called.\n  /// Returns a string error message if validation fails.\n  std::function<std::string(std::string&)> func_{[](std::string&)\n  {\n    return std::string{};\n  }};\n  /// The name for search purposes of the Validator\n  std::string name_;\n  /// Enable for Validator to allow it to be disabled if need be\n  bool active_{true};\n  /// specify that a validator should not modify the input\n  bool non_modifying_{false};\n\npublic:\n  Validator() = default;\n  /// Construct a Validator with just the description string\n  explicit Validator(std::string validator_desc) :\n    desc_function_([validator_desc]()\n  {\n    return validator_desc;\n  }) {}\n  // Construct Validator from basic information\n  Validator(std::function<std::string(std::string&)> op,\n            std::string validator_desc, std::string validator_name = \"\")\n    : desc_function_([validator_desc]()\n  {\n    return validator_desc;\n  }), func_(std::move(op)),\n  name_(std::move(validator_name)) {}\n  /// Set the Validator operation function\n  Validator& operation(std::function<std::string(std::string&)> op)\n  {\n    func_ = std::move(op);\n    return *this;\n  }\n  /// This is the required operator for a Validator - provided to help\n  /// users (CLI11 uses the member `func` directly)\n  std::string operator()(std::string& str) const\n  {\n    std::string retstring;\n\n    if (active_) {\n      if (non_modifying_) {\n        std::string value = str;\n        retstring = func_(value);\n      } else {\n        retstring = func_(str);\n      }\n    }\n\n    return retstring;\n  };\n\n  /// This is the required operator for a Validator - provided to help\n  /// users (CLI11 uses the member `func` directly)\n  std::string operator()(const std::string& str) const\n  {\n    std::string value = str;\n    return (active_) ? func_(value) : std::string{};\n  };\n\n  /// Specify the type string\n  Validator& description(std::string validator_desc)\n  {\n    desc_function_ = [validator_desc]() {\n      return validator_desc;\n    };\n    return *this;\n  }\n  /// Generate type description information for the Validator\n  std::string get_description() const\n  {\n    if (active_) {\n      return desc_function_();\n    }\n\n    return std::string{};\n  }\n  /// Specify the type string\n  Validator& name(std::string validator_name)\n  {\n    name_ = std::move(validator_name);\n    return *this;\n  }\n  /// Get the name of the Validator\n  const std::string& get_name() const\n  {\n    return name_;\n  }\n  /// Specify whether the Validator is active or not\n  Validator& active(bool active_val = true)\n  {\n    active_ = active_val;\n    return *this;\n  }\n\n  /// Specify whether the Validator can be modifying or not\n  Validator& non_modifying(bool no_modify = true)\n  {\n    non_modifying_ = no_modify;\n    return *this;\n  }\n\n  /// Get a boolean if the validator is active\n  bool get_active() const\n  {\n    return active_;\n  }\n\n  /// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input\n  bool get_modifying() const\n  {\n    return !non_modifying_;\n  }\n\n  /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the\n  /// same.\n  Validator operator&(const Validator& other) const\n  {\n    Validator newval;\n    newval._merge_description(*this, other, \" AND \");\n    // Give references (will make a copy in lambda function)\n    const std::function<std::string(std::string& filename)>& f1 = func_;\n    const std::function<std::string(std::string& filename)>& f2 = other.func_;\n    newval.func_ = [f1, f2](std::string & input) {\n      std::string s1 = f1(input);\n      std::string s2 = f2(input);\n\n      if (!s1.empty() && !s2.empty()) {\n        return std::string(\"(\") + s1 + \") AND (\" + s2 + \")\";\n      } else {\n        return s1 + s2;\n      }\n    };\n    newval.active_ = (active_ & other.active_);\n    return newval;\n  }\n\n  /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the\n  /// same.\n  Validator operator|(const Validator& other) const\n  {\n    Validator newval;\n    newval._merge_description(*this, other, \" OR \");\n    // Give references (will make a copy in lambda function)\n    const std::function<std::string(std::string&)>& f1 = func_;\n    const std::function<std::string(std::string&)>& f2 = other.func_;\n    newval.func_ = [f1, f2](std::string & input) {\n      std::string s1 = f1(input);\n      std::string s2 = f2(input);\n\n      if (s1.empty() || s2.empty()) {\n        return std::string();\n      } else {\n        return std::string(\"(\") + s1 + \") OR (\" + s2 + \")\";\n      }\n    };\n    newval.active_ = (active_ & other.active_);\n    return newval;\n  }\n\n  /// Create a validator that fails when a given validator succeeds\n  Validator operator!() const\n  {\n    Validator newval;\n    const std::function<std::string()>& dfunc1 = desc_function_;\n    newval.desc_function_ = [dfunc1]() {\n      auto str = dfunc1();\n      return (!str.empty()) ? std::string(\"NOT \") + str : std::string{};\n    };\n    // Give references (will make a copy in lambda function)\n    const std::function<std::string(std::string& res)>& f1 = func_;\n    newval.func_ = [f1, dfunc1](std::string & test) -> std::string {\n      std::string s1 = f1(test);\n\n      if (s1.empty())\n      {\n        return std::string(\"check \") + dfunc1() + \" succeeded improperly\";\n      } else\n        return std::string{};\n    };\n    newval.active_ = active_;\n    return newval;\n  }\n\nprivate:\n  void _merge_description(const Validator& val1, const Validator& val2,\n                          const std::string& merger)\n  {\n    const std::function<std::string()>& dfunc1 = val1.desc_function_;\n    const std::function<std::string()>& dfunc2 = val2.desc_function_;\n    desc_function_ = [ = ]() {\n      std::string f1 = dfunc1();\n      std::string f2 = dfunc2();\n\n      if ((f1.empty()) || (f2.empty())) {\n        return f1 + f2;\n      }\n\n      return std::string(\"(\") + f1 + \")\" + merger + \"(\" + f2 + \")\";\n    };\n  }\n};\n\n/// Class wrapping some of the accessors of Validator\nclass CustomValidator : public Validator\n{\npublic:\n};\n// The implementation of the built in validators is using the Validator class;\n// the user is only expected to use the const (static) versions (since there's no setup).\n// Therefore, this is in detail.\nnamespace detail\n{\n\n/// Check for an existing file (returns error message if check fails)\nclass ExistingFileValidator : public Validator\n{\npublic:\n  ExistingFileValidator() : Validator(\"FILE\")\n  {\n    func_ = [](std::string & filename) {\n      struct stat buffer;\n      bool exist = stat(filename.c_str(), &buffer) == 0;\n      bool is_dir = (buffer.st_mode & S_IFDIR) != 0;\n\n      if (!exist) {\n        return \"File does not exist: \" + filename;\n      } else if (is_dir) {\n        return \"File is actually a directory: \" + filename;\n      }\n\n      return std::string();\n    };\n  }\n};\n\n/// Check for an existing directory (returns error message if check fails)\nclass ExistingDirectoryValidator : public Validator\n{\npublic:\n  ExistingDirectoryValidator() : Validator(\"DIR\")\n  {\n    func_ = [](std::string & filename) {\n      struct stat buffer;\n      bool exist = stat(filename.c_str(), &buffer) == 0;\n      bool is_dir = (buffer.st_mode & S_IFDIR) != 0;\n\n      if (!exist) {\n        return \"Directory does not exist: \" + filename;\n      } else if (!is_dir) {\n        return \"Directory is actually a file: \" + filename;\n      }\n\n      return std::string();\n    };\n  }\n};\n\n/// Check for an existing path\nclass ExistingPathValidator : public Validator\n{\npublic:\n  ExistingPathValidator() : Validator(\"PATH(existing)\")\n  {\n    func_ = [](std::string & filename) {\n      struct stat buffer;\n      bool const exist = stat(filename.c_str(), &buffer) == 0;\n\n      if (!exist) {\n        return \"Path does not exist: \" + filename;\n      }\n\n      return std::string();\n    };\n  }\n};\n\n/// Check for an non-existing path\nclass NonexistentPathValidator : public Validator\n{\npublic:\n  NonexistentPathValidator() : Validator(\"PATH(non-existing)\")\n  {\n    func_ = [](std::string & filename) {\n      struct stat buffer;\n      bool exist = stat(filename.c_str(), &buffer) == 0;\n\n      if (exist) {\n        return \"Path already exists: \" + filename;\n      }\n\n      return std::string();\n    };\n  }\n};\n\n/// Validate the given string is a legal ipv4 address\nclass IPV4Validator : public Validator\n{\npublic:\n  IPV4Validator() : Validator(\"IPV4\")\n  {\n    func_ = [](std::string & ip_addr) {\n      auto result = CLI::detail::split(ip_addr, '.');\n\n      if (result.size() != 4) {\n        return \"Invalid IPV4 address must have four parts \" + ip_addr;\n      }\n\n      int num;\n      bool retval = true;\n\n      for (const auto& var : result) {\n        retval &= detail::lexical_cast(var, num);\n\n        if (!retval) {\n          return \"Failed parsing number \" + var;\n        }\n\n        if (num < 0 || num > 255) {\n          return \"Each IP number must be between 0 and 255 \" + var;\n        }\n      }\n\n      return std::string();\n    };\n  }\n};\n\n/// Validate the argument is a number and greater than or equal to 0\nclass PositiveNumber : public Validator\n{\npublic:\n  PositiveNumber() : Validator(\"POSITIVE\")\n  {\n    func_ = [](std::string & number_str) {\n      int number;\n\n      if (!detail::lexical_cast(number_str, number)) {\n        return \"Failed parsing number \" + number_str;\n      }\n\n      if (number < 0) {\n        return \"Number less then 0 \" + number_str;\n      }\n\n      return std::string();\n    };\n  }\n};\n\n/// Validate the argument is a number and greater than or equal to 0\nclass Number : public Validator\n{\npublic:\n  Number() : Validator(\"NUMBER\")\n  {\n    func_ = [](std::string & number_str) {\n      double number;\n\n      if (!detail::lexical_cast(number_str, number)) {\n        return \"Failed parsing as a number \" + number_str;\n      }\n\n      return std::string();\n    };\n  }\n};\n\n} // namespace detail\n\n// Static is not needed here, because global const implies static.\n\n/// Check for existing file (returns error message if check fails)\nconst detail::ExistingFileValidator ExistingFile;\n\n/// Check for an existing directory (returns error message if check fails)\nconst detail::ExistingDirectoryValidator ExistingDirectory;\n\n/// Check for an existing path\nconst detail::ExistingPathValidator ExistingPath;\n\n/// Check for an non-existing path\nconst detail::NonexistentPathValidator NonexistentPath;\n\n/// Check for an IP4 address\nconst detail::IPV4Validator ValidIPV4;\n\n/// Check for a positive number\nconst detail::PositiveNumber PositiveNumber;\n\n/// Check for a number\nconst detail::Number Number;\n\n/// Produce a range (factory). Min and max are inclusive.\nclass Range : public Validator\n{\npublic:\n  /// This produces a range with min and max inclusive.\n  ///\n  /// Note that the constructor is templated, but the struct is not, so C++17 is not\n  /// needed to provide nice syntax for Range(a,b).\n  template <typename T> Range(T min, T max)\n  {\n    std::stringstream out;\n    out << detail::type_name<T>() << \" in [\" << min << \" - \" << max << \"]\";\n    description(out.str());\n    func_ = [min, max](std::string & input) {\n      T val;\n      bool converted = detail::lexical_cast(input, val);\n\n      if ((!converted) || (val < min || val > max)) {\n        return \"Value \" + input + \" not in range \" + std::to_string(\n                 min) + \" to \" + std::to_string(max);\n      }\n\n      return std::string();\n    };\n  }\n\n  /// Range of one value is 0 to value\n  template <typename T> explicit Range(T max) : Range(static_cast<T>(0), max) {}\n};\n\n/// Produce a bounded range (factory). Min and max are inclusive.\nclass Bound : public Validator\n{\npublic:\n  /// This bounds a value with min and max inclusive.\n  ///\n  /// Note that the constructor is templated, but the struct is not, so C++17 is not\n  /// needed to provide nice syntax for Range(a,b).\n  template <typename T> Bound(T min, T max)\n  {\n    std::stringstream out;\n    out << detail::type_name<T>() << \" bounded to [\" << min << \" - \" << max << \"]\";\n    description(out.str());\n    func_ = [min, max](std::string & input) {\n      T val;\n      bool converted = detail::lexical_cast(input, val);\n\n      if (!converted) {\n        return \"Value \" + input + \" could not be converted\";\n      }\n\n      if (val < min) {\n        input = detail::as_string(min);\n      } else if (val > max) {\n        input = detail::as_string(max);\n      }\n\n      return std::string();\n    };\n  }\n\n  /// Range of one value is 0 to value\n  template <typename T> explicit Bound(T max) : Bound(static_cast<T>(0), max) {}\n};\n\nnamespace detail\n{\ntemplate <typename T,\n          enable_if_t<is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>\nauto smart_deref(T value) -> decltype(*value)\n{\n  return *value;\n}\n\ntemplate <\n  typename T,\n  enable_if_t < !is_copyable_ptr<typename std::remove_reference<T>::type>::value,\n                detail::enabler > = detail::dummy >\ntypename std::remove_reference<T>::type & smart_deref(T& value)\n{\n  return value;\n}\n/// Generate a string representation of a set\ntemplate <typename T> std::string generate_set(const T& set)\n{\n  using element_t = typename detail::element_type<T>::type;\n  using iteration_type_t = typename\n                           detail::pair_adaptor<element_t>::value_type; // the type of the object pair\n  std::string out(1, '{');\n  out.append(detail::join(detail::smart_deref(set),\n  [](const iteration_type_t& v) {\n    return detail::pair_adaptor<element_t>::first(v);\n  },\n  \",\"));\n  out.push_back('}');\n  return out;\n}\n\n/// Generate a string representation of a map\ntemplate <typename T> std::string generate_map(const T& map,\n    bool key_only = false)\n{\n  using element_t = typename detail::element_type<T>::type;\n  using iteration_type_t = typename\n                           detail::pair_adaptor<element_t>::value_type; // the type of the object pair\n  std::string out(1, '{');\n  out.append(detail::join(detail::smart_deref(map),\n  [key_only](const iteration_type_t& v) {\n    auto res = detail::as_string(detail::pair_adaptor<element_t>::first(v));\n\n    if (!key_only) {\n      res += \"->\" + detail::as_string(detail::pair_adaptor<element_t>::second(v));\n    }\n\n    return res;\n  },\n  \",\"));\n  out.push_back('}');\n  return out;\n}\n\ntemplate <typename> struct sfinae_true : std::true_type {};\n/// Function to check for the existence of a member find function which presumably is more efficient than looping over\n/// everything\ntemplate <typename T, typename V>\nstatic auto test_find(int) ->\nsfinae_true<decltype(std::declval<T>().find(std::declval<V>()))>;\ntemplate <typename, typename V> static auto test_find(long) -> std::false_type;\n\ntemplate <typename T, typename V> struct has_find : decltype(test_find<T, V>\n    (0)) {};\n\n/// A search function\ntemplate < typename T, typename V, enable_if_t < !has_find<T, V>::value,\n           detail::enabler > = detail::dummy >\nauto search(const T& set,\n            const V& val) ->\nstd::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {\n  using element_t = typename detail::element_type<T>::type;\n  auto& setref = detail::smart_deref(set);\n  auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v)\n  {\n    return (detail::pair_adaptor<element_t>::first(v) == val);\n  });\n  return {(it != std::end(setref)), it};\n}\n\n/// A search function that uses the built in find function\ntemplate <typename T, typename V, enable_if_t<has_find<T, V>::value, detail::enabler> = detail::dummy>\nauto search(const T& set,\n            const V& val) ->\nstd::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {\n  auto& setref = detail::smart_deref(set);\n  auto it = setref.find(val);\n  return {(it != std::end(setref)), it};\n}\n\n/// A search function with a filter function\ntemplate <typename T, typename V>\nauto search(const T& set, const V& val,\n            const std::function<V(V)>& filter_function)\n-> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {\n  using element_t = typename detail::element_type<T>::type;\n  // do the potentially faster first search\n  auto res = search(set, val);\n\n  if ((res.first) || (!(filter_function)))\n  {\n    return res;\n  }\n\n  // if we haven't found it do the longer linear search with all the element translations\n  auto& setref = detail::smart_deref(set);\n  auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v)\n  {\n    V a = detail::pair_adaptor<element_t>::first(v);\n    a = filter_function(a);\n    return (a == val);\n  });\n  return {(it != std::end(setref)), it};\n}\n\n/// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise.\ntemplate <typename T> typename\nstd::enable_if<std::is_integral<T>::value, bool>::type checked_multiply(T& a,\n    T b)\n{\n  if (a == 0 || b == 0) {\n    a *= b;\n    return true;\n  }\n\n  T c = a * b;\n\n  if (c / a != b) {\n    return false;\n  }\n\n  a = c;\n  return true;\n}\n\n/// Performs a *= b; if it doesn't equal infinity. Returns false otherwise.\ntemplate <typename T>\ntypename std::enable_if<std::is_floating_point<T>::value, bool>::type\nchecked_multiply(T& a, T b)\n{\n  T c = a * b;\n\n  if (std::isinf(c) && !std::isinf(a) && !std::isinf(b)) {\n    return false;\n  }\n\n  a = c;\n  return true;\n}\n\n} // namespace detail\n/// Verify items are in a set\nclass IsMember : public Validator\n{\npublic:\n  using filter_fn_t = std::function<std::string(std::string)>;\n\n  /// This allows in-place construction using an initializer list\n  template <typename T, typename... Args>\n  explicit IsMember(std::initializer_list<T> values, Args&& ... args)\n    : IsMember(std::vector<T>(values), std::forward<Args>(args)...) {}\n\n  /// This checks to see if an item is in a set (empty function)\n  template <typename T> explicit IsMember(T&& set) : IsMember(std::forward<T>\n        (set), nullptr) {}\n\n  /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter\n  /// both sides of the comparison before computing the comparison.\n  template <typename T, typename F> explicit IsMember(T set, F filter_function)\n  {\n    // Get the type of the contained item - requires a container have ::value_type\n    // if the type does not have first_type and second_type, these are both value_type\n    using element_t = typename\n                      detail::element_type<T>::type;            // Removes (smart) pointers if needed\n    using item_t = typename\n                   detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map\n    using local_item_t = typename\n                         IsMemberType<item_t>::type; // This will convert bad types to good ones\n    // (const char * to std::string)\n    // Make a local copy of the filter function, using a std::function if not one already\n    std::function<local_item_t(local_item_t)> filter_fn = filter_function;\n    // This is the type name for help, it will take the current version of the set contents\n    desc_function_ = [set]() {\n      return detail::generate_set(detail::smart_deref(set));\n    };\n    // This is the function that validates\n    // It stores a copy of the set pointer-like, so shared_ptr will stay alive\n    func_ = [set, filter_fn](std::string & input) {\n      local_item_t b;\n\n      if (!detail::lexical_cast(input, b)) {\n        throw ValidationError(input); // name is added later\n      }\n\n      if (filter_fn) {\n        b = filter_fn(b);\n      }\n\n      auto res = detail::search(set, b, filter_fn);\n\n      if (res.first) {\n        // Make sure the version in the input string is identical to the one in the set\n        if (filter_fn) {\n          input = detail::as_string(detail::pair_adaptor<element_t>::first(*\n                                    (res.second)));\n        }\n\n        // Return empty error string (success)\n        return std::string{};\n      }\n\n      // If you reach this point, the result was not found\n      std::string out(\" not in \");\n      out += detail::generate_set(detail::smart_deref(set));\n      return out;\n    };\n  }\n\n  /// You can pass in as many filter functions as you like, they nest (string only currently)\n  template <typename T, typename... Args>\n  IsMember(T&& set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2,\n           Args&& ... other)\n    : IsMember(std::forward<T>(set),\n               [filter_fn_1, filter_fn_2](std::string a)\n  {\n    return filter_fn_2(filter_fn_1(a));\n  },\n  other...) {}\n};\n\n/// definition of the default transformation object\ntemplate <typename T> using TransformPairs =\n  std::vector<std::pair<std::string, T>>;\n\n/// Translate named items to other or a value set\nclass Transformer : public Validator\n{\npublic:\n  using filter_fn_t = std::function<std::string(std::string)>;\n\n  /// This allows in-place construction\n  template <typename... Args>\n  explicit Transformer(std::initializer_list<std::pair<std::string, std::string>>\n                       values, Args&& ... args)\n    : Transformer(TransformPairs<std::string>(values),\n                  std::forward<Args>(args)...) {}\n\n  /// direct map of std::string to std::string\n  template <typename T> explicit Transformer(T&& mapping) : Transformer(\n      std::forward<T>(mapping), nullptr) {}\n\n  /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter\n  /// both sides of the comparison before computing the comparison.\n  template <typename T, typename F> explicit Transformer(T mapping,\n      F filter_function)\n  {\n    static_assert(\n      detail::pair_adaptor<typename detail::element_type<T>::type>::value,\n      \"mapping must produce value pairs\");\n    // Get the type of the contained item - requires a container have ::value_type\n    // if the type does not have first_type and second_type, these are both value_type\n    using element_t = typename\n                      detail::element_type<T>::type;            // Removes (smart) pointers if needed\n    using item_t = typename\n                   detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map\n    using local_item_t = typename\n                         IsMemberType<item_t>::type;            // This will convert bad types to good ones\n    // (const char * to std::string)\n    // Make a local copy of the filter function, using a std::function if not one already\n    std::function<local_item_t(local_item_t)> filter_fn = filter_function;\n    // This is the type name for help, it will take the current version of the set contents\n    desc_function_ = [mapping]() {\n      return detail::generate_map(detail::smart_deref(mapping));\n    };\n    func_ = [mapping, filter_fn](std::string & input) {\n      local_item_t b;\n\n      if (!detail::lexical_cast(input, b)) {\n        return std::string();\n        // there is no possible way we can match anything in the mapping if we can't convert so just return\n      }\n\n      if (filter_fn) {\n        b = filter_fn(b);\n      }\n\n      auto res = detail::search(mapping, b, filter_fn);\n\n      if (res.first) {\n        input = detail::as_string(detail::pair_adaptor<element_t>::second(*res.second));\n      }\n\n      return std::string{};\n    };\n  }\n\n  /// You can pass in as many filter functions as you like, they nest\n  template <typename T, typename... Args>\n  Transformer(T&& mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2,\n              Args&& ... other)\n    : Transformer(std::forward<T>(mapping),\n                  [filter_fn_1, filter_fn_2](std::string a)\n  {\n    return filter_fn_2(filter_fn_1(a));\n  },\n  other...) {}\n};\n\n/// translate named items to other or a value set\nclass CheckedTransformer : public Validator\n{\npublic:\n  using filter_fn_t = std::function<std::string(std::string)>;\n\n  /// This allows in-place construction\n  template <typename... Args>\n  explicit CheckedTransformer(\n    std::initializer_list<std::pair<std::string, std::string>> values,\n    Args&& ... args)\n    : CheckedTransformer(TransformPairs<std::string>(values),\n                         std::forward<Args>(args)...) {}\n\n  /// direct map of std::string to std::string\n  template <typename T> explicit CheckedTransformer(T mapping) :\n    CheckedTransformer(std::move(mapping), nullptr) {}\n\n  /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter\n  /// both sides of the comparison before computing the comparison.\n  template <typename T, typename F> explicit CheckedTransformer(T mapping,\n      F filter_function)\n  {\n    static_assert(\n      detail::pair_adaptor<typename detail::element_type<T>::type>::value,\n      \"mapping must produce value pairs\");\n    // Get the type of the contained item - requires a container have ::value_type\n    // if the type does not have first_type and second_type, these are both value_type\n    using element_t = typename\n                      detail::element_type<T>::type;            // Removes (smart) pointers if needed\n    using item_t = typename\n                   detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map\n    using local_item_t = typename\n                         IsMemberType<item_t>::type;            // This will convert bad types to good ones\n    // (const char * to std::string)\n    using iteration_type_t = typename\n                             detail::pair_adaptor<element_t>::value_type; // the type of the object pair //\n    // the type of the object pair\n    // Make a local copy of the filter function, using a std::function if not one already\n    std::function<local_item_t(local_item_t)> filter_fn = filter_function;\n    auto tfunc = [mapping]() {\n      std::string out(\"value in \");\n      out += detail::generate_map(detail::smart_deref(mapping)) + \" OR {\";\n      out += detail::join(\n               detail::smart_deref(mapping),\n      [](const iteration_type_t& v) {\n        return detail::as_string(detail::pair_adaptor<element_t>::second(v));\n      },\n      \",\");\n      out.push_back('}');\n      return out;\n    };\n    desc_function_ = tfunc;\n    func_ = [mapping, tfunc, filter_fn](std::string & input) {\n      local_item_t b;\n      bool converted = detail::lexical_cast(input, b);\n\n      if (converted) {\n        if (filter_fn) {\n          b = filter_fn(b);\n        }\n\n        auto res = detail::search(mapping, b, filter_fn);\n\n        if (res.first) {\n          input = detail::as_string(detail::pair_adaptor<element_t>::second(*res.second));\n          return std::string{};\n        }\n      }\n\n      for (const auto& v : detail::smart_deref(mapping)) {\n        auto output_string = detail::as_string(detail::pair_adaptor<element_t>::second(\n            v));\n\n        if (output_string == input) {\n          return std::string();\n        }\n      }\n\n      return \"Check \" + input + \" \" + tfunc() + \" FAILED\";\n    };\n  }\n\n  /// You can pass in as many filter functions as you like, they nest\n  template <typename T, typename... Args>\n  CheckedTransformer(T&& mapping, filter_fn_t filter_fn_1,\n                     filter_fn_t filter_fn_2, Args&& ... other)\n    : CheckedTransformer(std::forward<T>(mapping),\n                         [filter_fn_1, filter_fn_2](std::string a)\n  {\n    return filter_fn_2(filter_fn_1(a));\n  },\n  other...) {}\n};\n\n/// Helper function to allow ignore_case to be passed to IsMember or Transform\ninline std::string ignore_case(std::string item)\n{\n  return detail::to_lower(item);\n}\n\n/// Helper function to allow ignore_underscore to be passed to IsMember or Transform\ninline std::string ignore_underscore(std::string item)\n{\n  return detail::remove_underscore(item);\n}\n\n/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform\ninline std::string ignore_space(std::string item)\n{\n  item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item));\n  item.erase(std::remove(std::begin(item), std::end(item), '\\t'), std::end(item));\n  return item;\n}\n\n/// Multiply a number by a factor using given mapping.\n/// Can be used to write transforms for SIZE or DURATION inputs.\n///\n/// Example:\n///   With mapping = `{\"b\"->1, \"kb\"->1024, \"mb\"->1024*1024}`\n///   one can recognize inputs like \"100\", \"12kb\", \"100 MB\",\n///   that will be automatically transformed to 100, 14448, 104857600.\n///\n/// Output number type matches the type in the provided mapping.\n/// Therefore, if it is required to interpret real inputs like \"0.42 s\",\n/// the mapping should be of a type <string, float> or <string, double>.\nclass AsNumberWithUnit : public Validator\n{\npublic:\n  /// Adjust AsNumberWithUnit behavior.\n  /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched.\n  /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError\n  ///   if UNIT_REQUIRED is set and unit literal is not found.\n  enum Options {\n    CASE_SENSITIVE = 0,\n    CASE_INSENSITIVE = 1,\n    UNIT_OPTIONAL = 0,\n    UNIT_REQUIRED = 2,\n    DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL\n  };\n\n  template <typename Number>\n  explicit AsNumberWithUnit(std::map<std::string, Number> mapping,\n                            Options opts = DEFAULT,\n                            const std::string& unit_name = \"UNIT\")\n  {\n    description(generate_description<Number>(unit_name, opts));\n    validate_mapping(mapping, opts);\n    // transform function\n    func_ = [mapping, opts](std::string & input) -> std::string {\n      Number num;\n\n      detail::rtrim(input);\n\n      if (input.empty())\n      {\n        throw ValidationError(\"Input is empty\");\n      }\n\n      // Find split position between number and prefix\n      auto unit_begin = input.end();\n\n      while (unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale()))\n      {\n        --unit_begin;\n      }\n\n      std::string unit{unit_begin, input.end()};\n      input.resize(static_cast<size_t>(std::distance(input.begin(), unit_begin)));\n      detail::trim(input);\n\n      if (opts & UNIT_REQUIRED && unit.empty())\n      {\n        throw ValidationError(\"Missing mandatory unit\");\n      }\n\n      if (opts & CASE_INSENSITIVE)\n      {\n        unit = detail::to_lower(unit);\n      }\n\n      bool converted = detail::lexical_cast(input, num);\n\n      if (!converted)\n      {\n        throw ValidationError(\"Value \" + input + \" could not be converted to \" +\n                              detail::type_name<Number>());\n      }\n\n      if (unit.empty())\n      {\n        // No need to modify input if no unit passed\n        return {};\n      }\n\n      // find corresponding factor\n      auto it = mapping.find(unit);\n\n      if (it == mapping.end())\n      {\n        throw ValidationError(unit +\n                              \" unit not recognized. \"\n                              \"Allowed values: \" +\n                              detail::generate_map(mapping, true));\n      }\n\n      // perform safe multiplication\n      bool ok = detail::checked_multiply(num, it->second);\n\n      if (!ok)\n      {\n        throw ValidationError(detail::as_string(num) + \" multiplied by \" + unit +\n                              \" factor would cause number overflow. Use smaller value.\");\n      }\n\n      input = detail::as_string(num);\n\n      return {};\n    };\n  }\n\nprivate:\n  /// Check that mapping contains valid units.\n  /// Update mapping for CASE_INSENSITIVE mode.\n  template <typename Number> static void validate_mapping(\n    std::map<std::string, Number>& mapping, Options opts)\n  {\n    for (auto& kv : mapping) {\n      if (kv.first.empty()) {\n        throw ValidationError(\"Unit must not be empty.\");\n      }\n\n      if (!detail::isalpha(kv.first)) {\n        throw ValidationError(\"Unit must contain only letters.\");\n      }\n    }\n\n    // make all units lowercase if CASE_INSENSITIVE\n    if (opts & CASE_INSENSITIVE) {\n      std::map<std::string, Number> lower_mapping;\n\n      for (auto& kv : mapping) {\n        auto s = detail::to_lower(kv.first);\n\n        if (lower_mapping.count(s)) {\n          throw ValidationError(\"Several matching lowercase unit representations are found: \"\n                                + s);\n        }\n\n        lower_mapping[detail::to_lower(kv.first)] = kv.second;\n      }\n\n      mapping = std::move(lower_mapping);\n    }\n  }\n\n  /// Generate description like this: NUMBER [UNIT]\n  template <typename Number> static std::string generate_description(\n    const std::string& name, Options opts)\n  {\n    std::stringstream out;\n    out << detail::type_name<Number>() << ' ';\n\n    if (opts & UNIT_REQUIRED) {\n      out << name;\n    } else {\n      out << '[' << name << ']';\n    }\n\n    return out.str();\n  }\n};\n\n/// Converts a human-readable size string (with unit literal) to uin64_t size.\n/// Example:\n///   \"100\" => 100\n///   \"1 b\" => 100\n///   \"10Kb\" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024)\n///   \"10 KB\" => 10240\n///   \"10 kb\" => 10240\n///   \"10 kib\" => 10240 // *i, *ib are always interpreted as *bibyte (*1024)\n///   \"10kb\" => 10240\n///   \"2 MB\" => 2097152\n///   \"2 EiB\" => 2^61 // Units up to exibyte are supported\nclass AsSizeValue : public AsNumberWithUnit\n{\npublic:\n  using result_t = uint64_t;\n\n  /// If kb_is_1000 is true,\n  /// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024\n  /// (same applies to higher order units as well).\n  /// Otherwise, interpret all literals as factors of 1024.\n  /// The first option is formally correct, but\n  /// the second interpretation is more wide-spread\n  /// (see https://en.wikipedia.org/wiki/Binary_prefix).\n  explicit AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(\n          kb_is_1000))\n  {\n    if (kb_is_1000) {\n      description(\"SIZE [b, kb(=1000b), kib(=1024b), ...]\");\n    } else {\n      description(\"SIZE [b, kb(=1024b), ...]\");\n    }\n  }\n\nprivate:\n  /// Get <size unit, factor> mapping\n  static std::map<std::string, result_t> init_mapping(bool kb_is_1000)\n  {\n    std::map<std::string, result_t> m;\n    result_t k_factor = kb_is_1000 ? 1000 : 1024;\n    result_t ki_factor = 1024;\n    result_t k = 1;\n    result_t ki = 1;\n    m[\"b\"] = 1;\n\n    for (std::string p : {\n           \"k\", \"m\", \"g\", \"t\", \"p\", \"e\"\n         }) {\n      k *= k_factor;\n      ki *= ki_factor;\n      m[p] = k;\n      m[p + \"b\"] = k;\n      m[p + \"i\"] = ki;\n      m[p + \"ib\"] = ki;\n    }\n    return m;\n  }\n\n  /// Cache calculated mapping\n  static std::map<std::string, result_t> get_mapping(bool kb_is_1000)\n  {\n    if (kb_is_1000) {\n      static auto m = init_mapping(true);\n      return m;\n    } else {\n      static auto m = init_mapping(false);\n      return m;\n    }\n  }\n};\n\nnamespace detail\n{\n/// Split a string into a program name and command line arguments\n/// the string is assumed to contain a file name followed by other arguments\n/// the return value contains is a pair with the first argument containing the program name and the second\n/// everything else.\ninline std::pair<std::string, std::string> split_program_name(\n  std::string commandline)\n{\n  // try to determine the programName\n  std::pair<std::string, std::string> vals;\n  trim(commandline);\n  auto esp = commandline.find_first_of(' ', 1);\n\n  while (!ExistingFile(commandline.substr(0, esp)).empty()) {\n    esp = commandline.find_first_of(' ', esp + 1);\n\n    if (esp == std::string::npos) {\n      // if we have reached the end and haven't found a valid file just assume the first argument is the\n      // program name\n      esp = commandline.find_first_of(' ', 1);\n      break;\n    }\n  }\n\n  vals.first = commandline.substr(0, esp);\n  rtrim(vals.first);\n  // strip the program name\n  vals.second = (esp != std::string::npos) ? commandline.substr(\n                  esp + 1) : std::string{};\n  ltrim(vals.second);\n  return vals;\n}\n\n} // namespace detail\n/// @}\n\n} // namespace CLI\n\n// From CLI/FormatterFwd.hpp:\n\nnamespace CLI\n{\n\nclass Option;\nclass App;\n\n/// This enum signifies the type of help requested\n///\n/// This is passed in by App; all user classes must accept this as\n/// the second argument.\n\nenum class AppFormatMode {\n  Normal, //< The normal, detailed help\n  All,    //< A fully expanded help\n  Sub,    //< Used when printed as part of expanded subcommand\n};\n\n/// This is the minimum requirements to run a formatter.\n///\n/// A user can subclass this is if they do not care at all\n/// about the structure in CLI::Formatter.\nclass FormatterBase\n{\nprotected:\n  /// @name Options\n  ///@{\n\n  /// The width of the first column\n  size_t column_width_{30};\n\n  /// @brief The required help printout labels (user changeable)\n  /// Values are Needs, Excludes, etc.\n  std::map<std::string, std::string> labels_;\n\n  ///@}\n  /// @name Basic\n  ///@{\n\npublic:\n  FormatterBase() = default;\n  FormatterBase(const FormatterBase&) = default;\n  FormatterBase(FormatterBase&&) = default;\n\n  /// Adding a destructor in this form to work around bug in GCC 4.7\n  virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default)\n\n  /// This is the key method that puts together help\n  virtual std::string make_help(const App*, std::string, AppFormatMode) const = 0;\n\n  ///@}\n  /// @name Setters\n  ///@{\n\n  /// Set the \"REQUIRED\" label\n  void label(std::string key, std::string val)\n  {\n    labels_[key] = val;\n  }\n\n  /// Set the column width\n  void column_width(size_t val)\n  {\n    column_width_ = val;\n  }\n\n  ///@}\n  /// @name Getters\n  ///@{\n\n  /// Get the current value of a name (REQUIRED, etc.)\n  std::string get_label(std::string key) const\n  {\n    if (labels_.find(key) == labels_.end()) {\n      return key;\n    } else {\n      return labels_.at(key);\n    }\n  }\n\n  /// Get the current column width\n  size_t get_column_width() const\n  {\n    return column_width_;\n  }\n\n  ///@}\n};\n\n/// This is a specialty override for lambda functions\nclass FormatterLambda final : public FormatterBase\n{\n  using funct_t =\n    std::function<std::string(const App*, std::string, AppFormatMode)>;\n\n  /// The lambda to hold and run\n  funct_t lambda_;\n\npublic:\n  /// Create a FormatterLambda with a lambda function\n  explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {}\n\n  /// Adding a destructor (mostly to make GCC 4.7 happy)\n  ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default)\n\n  /// This will simply call the lambda function\n  std::string make_help(const App* app, std::string name,\n                        AppFormatMode mode) const override\n  {\n    return lambda_(app, name, mode);\n  }\n};\n\n/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few\n/// overridable methods, to be highly customizable with minimal effort.\nclass Formatter : public FormatterBase\n{\npublic:\n  Formatter() = default;\n  Formatter(const Formatter&) = default;\n  Formatter(Formatter&&) = default;\n\n  /// @name Overridables\n  ///@{\n\n  /// This prints out a group of options with title\n  ///\n  virtual std::string make_group(std::string group, bool is_positional,\n                                 std::vector<const Option*> opts) const;\n\n  /// This prints out just the positionals \"group\"\n  virtual std::string make_positionals(const App* app) const;\n\n  /// This prints out all the groups of options\n  std::string make_groups(const App* app, AppFormatMode mode) const;\n\n  /// This prints out all the subcommands\n  virtual std::string make_subcommands(const App* app, AppFormatMode mode) const;\n\n  /// This prints out a subcommand\n  virtual std::string make_subcommand(const App* sub) const;\n\n  /// This prints out a subcommand in help-all\n  virtual std::string make_expanded(const App* sub) const;\n\n  /// This prints out all the groups of options\n  virtual std::string make_footer(const App* app) const;\n\n  /// This displays the description line\n  virtual std::string make_description(const App* app) const;\n\n  /// This displays the usage line\n  virtual std::string make_usage(const App* app, std::string name) const;\n\n  /// This puts everything together\n  std::string make_help(const App*, std::string, AppFormatMode) const override;\n\n  ///@}\n  /// @name Options\n  ///@{\n\n  /// This prints out an option help line, either positional or optional form\n  virtual std::string make_option(const Option* opt, bool is_positional) const\n  {\n    std::stringstream out;\n    detail::format_help(\n      out, make_option_name(opt, is_positional) + make_option_opts(opt),\n      make_option_desc(opt), column_width_);\n    return out.str();\n  }\n\n  /// @brief This is the name part of an option, Default: left column\n  virtual std::string make_option_name(const Option*, bool) const;\n\n  /// @brief This is the options part of the name, Default: combined into left column\n  virtual std::string make_option_opts(const Option*) const;\n\n  /// @brief This is the description. Default: Right column, on new line if left column too large\n  virtual std::string make_option_desc(const Option*) const;\n\n  /// @brief This is used to print the name on the USAGE line\n  virtual std::string make_option_usage(const Option* opt) const;\n\n  ///@}\n};\n\n} // namespace CLI\n\n// From CLI/Option.hpp:\n\nnamespace CLI\n{\n\nusing results_t = std::vector<std::string>;\nusing callback_t = std::function<bool(results_t)>;\n\nclass Option;\nclass App;\n\nusing Option_p = std::unique_ptr<Option>;\n\nenum class MultiOptionPolicy : char { Throw, TakeLast, TakeFirst, Join };\n\n/// This is the CRTP base class for Option and OptionDefaults. It was designed this way\n/// to share parts of the class; an OptionDefaults can copy to an Option.\ntemplate <typename CRTP> class OptionBase\n{\n  friend App;\n\nprotected:\n  /// The group membership\n  std::string group_ = std::string(\"Options\");\n\n  /// True if this is a required option\n  bool required_{false};\n\n  /// Ignore the case when matching (option, not value)\n  bool ignore_case_{false};\n\n  /// Ignore underscores when matching (option, not value)\n  bool ignore_underscore_{false};\n\n  /// Allow this option to be given in a configuration file\n  bool configurable_{true};\n\n  /// Disable overriding flag values with '=value'\n  bool disable_flag_override_{false};\n\n  /// Specify a delimiter character for vector arguments\n  char delimiter_{'\\0'};\n\n  /// Automatically capture default value\n  bool always_capture_default_{false};\n\n  /// Policy for multiple arguments when `expected_ == 1`  (can be set on bool flags, too)\n  MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};\n\n  /// Copy the contents to another similar class (one based on OptionBase)\n  template <typename T> void copy_to(T* other) const\n  {\n    other->group(group_);\n    other->required(required_);\n    other->ignore_case(ignore_case_);\n    other->ignore_underscore(ignore_underscore_);\n    other->configurable(configurable_);\n    other->disable_flag_override(disable_flag_override_);\n    other->delimiter(delimiter_);\n    other->always_capture_default(always_capture_default_);\n    other->multi_option_policy(multi_option_policy_);\n  }\n\npublic:\n  // setters\n\n  /// Changes the group membership\n  CRTP* group(std::string name)\n  {\n    group_ = name;\n    return static_cast<CRTP*>(this);\n  }\n\n  /// Set the option as required\n  CRTP* required(bool value = true)\n  {\n    required_ = value;\n    return static_cast<CRTP*>(this);\n  }\n\n  /// Support Plumbum term\n  CRTP* mandatory(bool value = true)\n  {\n    return required(value);\n  }\n\n  CRTP* always_capture_default(bool value = true)\n  {\n    always_capture_default_ = value;\n    return static_cast<CRTP*>(this);\n  }\n\n  // Getters\n\n  /// Get the group of this option\n  const std::string& get_group() const\n  {\n    return group_;\n  }\n\n  /// True if this is a required option\n  bool get_required() const\n  {\n    return required_;\n  }\n\n  /// The status of ignore case\n  bool get_ignore_case() const\n  {\n    return ignore_case_;\n  }\n\n  /// The status of ignore_underscore\n  bool get_ignore_underscore() const\n  {\n    return ignore_underscore_;\n  }\n\n  /// The status of configurable\n  bool get_configurable() const\n  {\n    return configurable_;\n  }\n\n  /// The status of configurable\n  bool get_disable_flag_override() const\n  {\n    return disable_flag_override_;\n  }\n\n  /// Get the current delimeter char\n  char get_delimiter() const\n  {\n    return delimiter_;\n  }\n\n  /// Return true if this will automatically capture the default value for help printing\n  bool get_always_capture_default() const\n  {\n    return always_capture_default_;\n  }\n\n  /// The status of the multi option policy\n  MultiOptionPolicy get_multi_option_policy() const\n  {\n    return multi_option_policy_;\n  }\n\n  // Shortcuts for multi option policy\n\n  /// Set the multi option policy to take last\n  CRTP* take_last()\n  {\n    auto self = static_cast<CRTP*>(this);\n    self->multi_option_policy(MultiOptionPolicy::TakeLast);\n    return self;\n  }\n\n  /// Set the multi option policy to take last\n  CRTP* take_first()\n  {\n    auto self = static_cast<CRTP*>(this);\n    self->multi_option_policy(MultiOptionPolicy::TakeFirst);\n    return self;\n  }\n\n  /// Set the multi option policy to take last\n  CRTP* join()\n  {\n    auto self = static_cast<CRTP*>(this);\n    self->multi_option_policy(MultiOptionPolicy::Join);\n    return self;\n  }\n\n  /// Allow in a configuration file\n  CRTP* configurable(bool value = true)\n  {\n    configurable_ = value;\n    return static_cast<CRTP*>(this);\n  }\n\n  /// Allow in a configuration file\n  CRTP* delimiter(char value = '\\0')\n  {\n    delimiter_ = value;\n    return static_cast<CRTP*>(this);\n  }\n};\n\n/// This is a version of OptionBase that only supports setting values,\n/// for defaults. It is stored as the default option in an App.\nclass OptionDefaults : public OptionBase<OptionDefaults>\n{\npublic:\n  OptionDefaults() = default;\n\n  // Methods here need a different implementation if they are Option vs. OptionDefault\n\n  /// Take the last argument if given multiple times\n  OptionDefaults* multi_option_policy(MultiOptionPolicy value =\n                                        MultiOptionPolicy::Throw)\n  {\n    multi_option_policy_ = value;\n    return this;\n  }\n\n  /// Ignore the case of the option name\n  OptionDefaults* ignore_case(bool value = true)\n  {\n    ignore_case_ = value;\n    return this;\n  }\n\n  /// Ignore underscores in the option name\n  OptionDefaults* ignore_underscore(bool value = true)\n  {\n    ignore_underscore_ = value;\n    return this;\n  }\n\n  /// Disable overriding flag values with an '=<value>' segment\n  OptionDefaults* disable_flag_override(bool value = true)\n  {\n    disable_flag_override_ = value;\n    return this;\n  }\n\n  /// set a delimiter character to split up single arguments to treat as multiple inputs\n  OptionDefaults* delimiter(char value = '\\0')\n  {\n    delimiter_ = value;\n    return this;\n  }\n};\n\nclass Option : public OptionBase<Option>\n{\n  friend App;\n\nprotected:\n  /// @name Names\n  ///@{\n\n  /// A list of the short names (`-a`) without the leading dashes\n  std::vector<std::string> snames_;\n\n  /// A list of the long names (`--a`) without the leading dashes\n  std::vector<std::string> lnames_;\n\n  /// A list of the flag names with the appropriate default value, the first part of the pair should be duplicates of\n  /// what is in snames or lnames but will trigger a particular response on a flag\n  std::vector<std::pair<std::string, std::string>> default_flag_values_;\n\n  /// a list of flag names with specified default values;\n  std::vector<std::string> fnames_;\n\n  /// A positional name\n  std::string pname_;\n\n  /// If given, check the environment for this option\n  std::string envname_;\n\n  ///@}\n  /// @name Help\n  ///@{\n\n  /// The description for help strings\n  std::string description_;\n\n  /// A human readable default value, either manually set, captured, or captured by default\n  std::string default_str_;\n\n  /// A human readable type value, set when App creates this\n  ///\n  /// This is a lambda function so \"types\" can be dynamic, such as when a set prints its contents.\n  std::function<std::string()> type_name_{[]()\n  {\n    return std::string();\n  }};\n\n  /// Run this function to capture a default (ignore if empty)\n  std::function<std::string()> default_function_;\n\n  ///@}\n  /// @name Configuration\n  ///@{\n\n  /// The number of arguments that make up one option. -1=unlimited (vector-like), 0=flag, 1=normal option,\n  /// 2=complex/pair, etc. Set only when the option is created; this is intrinsic to the type. Eventually, -2 may mean\n  /// vector of pairs.\n  int type_size_{1};\n\n  /// The number of expected values, type_size_ must be < 0. Ignored for flag. N < 0 means at least -N values.\n  int expected_{1};\n\n  /// A list of validators to run on each value parsed\n  std::vector<Validator> validators_;\n\n  /// A list of options that are required with this option\n  std::set<Option*> needs_;\n\n  /// A list of options that are excluded with this option\n  std::set<Option*> excludes_;\n\n  ///@}\n  /// @name Other\n  ///@{\n\n  /// Remember the parent app\n  App* parent_;\n\n  /// Options store a callback to do all the work\n  callback_t callback_;\n\n  ///@}\n  /// @name Parsing results\n  ///@{\n\n  /// Results of parsing\n  results_t results_;\n\n  /// Whether the callback has run (needed for INI parsing)\n  bool callback_run_{false};\n\n  ///@}\n\n  /// Making an option by hand is not defined, it must be made by the App class\n  Option(std::string option_name,\n         std::string option_description,\n         std::function<bool(results_t)> callback,\n         App* parent)\n    : description_(std::move(option_description)), parent_(parent),\n      callback_(std::move(callback))\n  {\n    std::tie(snames_, lnames_,\n             pname_) = detail::get_names(detail::split_names(option_name));\n  }\n\npublic:\n  /// @name Basic\n  ///@{\n\n  /// Count the total number of times an option was passed\n  size_t count() const\n  {\n    return results_.size();\n  }\n\n  /// True if the option was not passed\n  size_t empty() const\n  {\n    return results_.empty();\n  }\n\n  /// This class is true if option is passed.\n  operator bool() const\n  {\n    return !empty();\n  }\n\n  /// Clear the parsed results (mostly for testing)\n  void clear()\n  {\n    results_.clear();\n  }\n\n  ///@}\n  /// @name Setting options\n  ///@{\n\n  /// Set the number of expected arguments (Flags don't use this)\n  Option* expected(int value)\n  {\n    // Break if this is a flag\n    if (type_size_ == 0) {\n      throw IncorrectConstruction::SetFlag(get_name(true, true));\n    }\n    // Setting 0 is not allowed\n    else if (value == 0) {\n      throw IncorrectConstruction::Set0Opt(get_name());\n    }\n    // No change is okay, quit now\n    else if (expected_ == value) {\n      return this;\n    }\n    // Type must be a vector\n    else if (type_size_ >= 0) {\n      throw IncorrectConstruction::ChangeNotVector(get_name());\n    }\n    // TODO: Can support multioption for non-1 values (except for join)\n    else if (value != 1 && multi_option_policy_ != MultiOptionPolicy::Throw) {\n      throw IncorrectConstruction::AfterMultiOpt(get_name());\n    }\n\n    expected_ = value;\n    return this;\n  }\n\n  /// Adds a Validator with a built in type name\n  Option* check(Validator validator, std::string validator_name = \"\")\n  {\n    validator.non_modifying();\n    validators_.push_back(std::move(validator));\n\n    if (!validator_name.empty()) {\n      validators_.front().name(validator_name);\n    }\n\n    return this;\n  }\n\n  /// Adds a Validator. Takes a const string& and returns an error message (empty if conversion/check is okay).\n  Option* check(std::function<std::string(const std::string&)> validator,\n                std::string validator_description = \"\",\n                std::string validator_name = \"\")\n  {\n    validators_.emplace_back(validator, std::move(validator_description),\n                             std::move(validator_name));\n    validators_.back().non_modifying();\n    return this;\n  }\n\n  /// Adds a transforming validator with a built in type name\n  Option* transform(Validator validator, std::string validator_name = \"\")\n  {\n    validators_.insert(validators_.begin(), std::move(validator));\n\n    if (!validator_name.empty()) {\n      validators_.front().name(validator_name);\n    }\n\n    return this;\n  }\n\n  /// Adds a validator-like function that can change result\n  Option* transform(std::function<std::string(std::string)> func,\n                    std::string transform_description = \"\",\n                    std::string transform_name = \"\")\n  {\n    validators_.insert(validators_.begin(),\n                       Validator(\n    [func](std::string & val) {\n      val = func(val);\n      return std::string{};\n    },\n    std::move(transform_description),\n    std::move(transform_name)));\n    return this;\n  }\n\n  /// Adds a user supplied function to run on each item passed in (communicate though lambda capture)\n  Option* each(std::function<void(std::string)> func)\n  {\n    validators_.emplace_back(\n    [func](std::string & inout) {\n      func(inout);\n      return std::string{};\n    },\n    std::string{});\n    return this;\n  }\n  /// Get a named Validator\n  Validator* get_validator(const std::string& validator_name = \"\")\n  {\n    for (auto& validator : validators_) {\n      if (validator_name == validator.get_name()) {\n        return &validator;\n      }\n    }\n\n    if ((validator_name.empty()) && (!validators_.empty())) {\n      return &(validators_.front());\n    }\n\n    throw OptionNotFound(std::string(\"Validator \") + validator_name + \" Not Found\");\n  }\n  /// Sets required options\n  Option* needs(Option* opt)\n  {\n    auto tup = needs_.insert(opt);\n\n    if (!tup.second) {\n      throw OptionAlreadyAdded::Requires(get_name(), opt->get_name());\n    }\n\n    return this;\n  }\n\n  /// Can find a string if needed\n  template <typename T = App> Option * needs(std::string opt_name)\n  {\n    for (const Option_p& opt : dynamic_cast<T*>(parent_)->options_)\n      if (opt.get() != this && opt->check_name(opt_name)) {\n        return needs(opt.get());\n      }\n\n    throw IncorrectConstruction::MissingOption(opt_name);\n  }\n\n  /// Any number supported, any mix of string and Opt\n  template <typename A, typename B, typename... ARG> Option* needs(A opt, B opt1,\n      ARG... args)\n  {\n    needs(opt);\n    return needs(opt1, args...);\n  }\n\n  /// Remove needs link from an option. Returns true if the option really was in the needs list.\n  bool remove_needs(Option* opt)\n  {\n    auto iterator = std::find(std::begin(needs_), std::end(needs_), opt);\n\n    if (iterator != std::end(needs_)) {\n      needs_.erase(iterator);\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n  /// Sets excluded options\n  Option* excludes(Option* opt)\n  {\n    excludes_.insert(opt);\n    // Help text should be symmetric - excluding a should exclude b\n    opt->excludes_.insert(this);\n    // Ignoring the insert return value, excluding twice is now allowed.\n    // (Mostly to allow both directions to be excluded by user, even though the library does it for you.)\n    return this;\n  }\n\n  /// Can find a string if needed\n  template <typename T = App> Option * excludes(std::string opt_name)\n  {\n    for (const Option_p& opt : dynamic_cast<T*>(parent_)->options_)\n      if (opt.get() != this && opt->check_name(opt_name)) {\n        return excludes(opt.get());\n      }\n\n    throw IncorrectConstruction::MissingOption(opt_name);\n  }\n\n  /// Any number supported, any mix of string and Opt\n  template <typename A, typename B, typename... ARG> Option* excludes(A opt,\n      B opt1, ARG... args)\n  {\n    excludes(opt);\n    return excludes(opt1, args...);\n  }\n\n  /// Remove needs link from an option. Returns true if the option really was in the needs list.\n  bool remove_excludes(Option* opt)\n  {\n    auto iterator = std::find(std::begin(excludes_), std::end(excludes_), opt);\n\n    if (iterator != std::end(excludes_)) {\n      excludes_.erase(iterator);\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n  /// Sets environment variable to read if no option given\n  Option* envname(std::string name)\n  {\n    envname_ = name;\n    return this;\n  }\n\n  /// Ignore case\n  ///\n  /// The template hides the fact that we don't have the definition of App yet.\n  /// You are never expected to add an argument to the template here.\n  template <typename T = App> Option * ignore_case(bool value = true)\n  {\n    ignore_case_ = value;\n    auto* parent = dynamic_cast<T*>(parent_);\n\n    for (const Option_p& opt : parent->options_)\n      if (opt.get() != this && *opt == *this) {\n        throw OptionAlreadyAdded(opt->get_name(true, true));\n      }\n\n    return this;\n  }\n\n  /// Ignore underscores in the option names\n  ///\n  /// The template hides the fact that we don't have the definition of App yet.\n  /// You are never expected to add an argument to the template here.\n  template <typename T = App> Option * ignore_underscore(bool value = true)\n  {\n    ignore_underscore_ = value;\n    auto* parent = dynamic_cast<T*>(parent_);\n\n    for (const Option_p& opt : parent->options_)\n      if (opt.get() != this && *opt == *this) {\n        throw OptionAlreadyAdded(opt->get_name(true, true));\n      }\n\n    return this;\n  }\n\n  /// Take the last argument if given multiple times (or another policy)\n  Option* multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw)\n  {\n    if (get_items_expected() < 0) {\n      throw IncorrectConstruction::MultiOptionPolicy(get_name());\n    }\n\n    multi_option_policy_ = value;\n    return this;\n  }\n\n  /// disable flag overrides\n  Option* disable_flag_override(bool value = true)\n  {\n    disable_flag_override_ = value;\n    return this;\n  }\n  ///@}\n  /// @name Accessors\n  ///@{\n\n  /// The number of arguments the option expects\n  int get_type_size() const\n  {\n    return type_size_;\n  }\n\n  /// The environment variable associated to this value\n  std::string get_envname() const\n  {\n    return envname_;\n  }\n\n  /// The set of options needed\n  std::set<Option*> get_needs() const\n  {\n    return needs_;\n  }\n\n  /// The set of options excluded\n  std::set<Option*> get_excludes() const\n  {\n    return excludes_;\n  }\n\n  /// The default value (for help printing) DEPRECATED Use get_default_str() instead\n  CLI11_DEPRECATED(\"Use get_default_str() instead\")\n  std::string get_defaultval() const\n  {\n    return default_str_;\n  }\n\n  /// The default value (for help printing)\n  std::string get_default_str() const\n  {\n    return default_str_;\n  }\n\n  /// Get the callback function\n  callback_t get_callback() const\n  {\n    return callback_;\n  }\n\n  /// Get the long names\n  const std::vector<std::string> get_lnames() const\n  {\n    return lnames_;\n  }\n\n  /// Get the short names\n  const std::vector<std::string> get_snames() const\n  {\n    return snames_;\n  }\n\n  /// get the flag names with specified default values\n  const std::vector<std::string> get_fnames() const\n  {\n    return fnames_;\n  }\n\n  /// The number of times the option expects to be included\n  int get_expected() const\n  {\n    return expected_;\n  }\n\n  /// \\brief The total number of expected values (including the type)\n  /// This is positive if exactly this number is expected, and negative for at least N values\n  ///\n  /// v = fabs(size_type*expected)\n  /// !MultiOptionPolicy::Throw\n  ///           | Expected < 0  | Expected == 0 | Expected > 0\n  /// Size < 0  |      -v       |       0       |     -v\n  /// Size == 0 |       0       |       0       |      0\n  /// Size > 0  |      -v       |       0       |     -v       // Expected must be 1\n  ///\n  /// MultiOptionPolicy::Throw\n  ///           | Expected < 0  | Expected == 0 | Expected > 0\n  /// Size < 0  |      -v       |       0       |      v\n  /// Size == 0 |       0       |       0       |      0\n  /// Size > 0  |       v       |       0       |      v      // Expected must be 1\n  ///\n  int get_items_expected() const\n  {\n    return std::abs(type_size_ * expected_) *\n           ((multi_option_policy_ != MultiOptionPolicy::Throw || (expected_ < 0 &&\n               type_size_ < 0) ? -1 : 1));\n  }\n\n  /// True if the argument can be given directly\n  bool get_positional() const\n  {\n    return pname_.length() > 0;\n  }\n\n  /// True if option has at least one non-positional name\n  bool nonpositional() const\n  {\n    return (snames_.size() + lnames_.size()) > 0;\n  }\n\n  /// True if option has description\n  bool has_description() const\n  {\n    return description_.length() > 0;\n  }\n\n  /// Get the description\n  const std::string& get_description() const\n  {\n    return description_;\n  }\n\n  /// Set the description\n  Option* description(std::string option_description)\n  {\n    description_ = std::move(option_description);\n    return this;\n  }\n\n  ///@}\n  /// @name Help tools\n  ///@{\n\n  /// \\brief Gets a comma separated list of names.\n  /// Will include / prefer the positional name if positional is true.\n  /// If all_options is false, pick just the most descriptive name to show.\n  /// Use `get_name(true)` to get the positional name (replaces `get_pname`)\n  std::string get_name(bool positional =\n                         false, //<[input] Show the positional name\n                       bool all_options = false //<[input] Show every option\n                      ) const\n  {\n    if (all_options) {\n      std::vector<std::string> name_list;\n\n      /// The all list will never include a positional unless asked or that's the only name.\n      if ((positional && pname_.length()) || (snames_.empty() && lnames_.empty())) {\n        name_list.push_back(pname_);\n      }\n\n      if ((get_items_expected() == 0) && (!fnames_.empty())) {\n        for (const std::string& sname : snames_) {\n          name_list.push_back(\"-\" + sname);\n\n          if (check_fname(sname)) {\n            name_list.back() += \"{\" + get_flag_value(sname, \"\") + \"}\";\n          }\n        }\n\n        for (const std::string& lname : lnames_) {\n          name_list.push_back(\"--\" + lname);\n\n          if (check_fname(lname)) {\n            name_list.back() += \"{\" + get_flag_value(lname, \"\") + \"}\";\n          }\n        }\n      } else {\n        for (const std::string& sname : snames_) {\n          name_list.push_back(\"-\" + sname);\n        }\n\n        for (const std::string& lname : lnames_) {\n          name_list.push_back(\"--\" + lname);\n        }\n      }\n\n      return detail::join(name_list);\n    } else {\n      // This returns the positional name no matter what\n      if (positional) {\n        return pname_;\n      }\n      // Prefer long name\n      else if (!lnames_.empty()) {\n        return std::string(\"--\") + lnames_[0];\n      }\n      // Or short name if no long name\n      else if (!snames_.empty()) {\n        return std::string(\"-\") + snames_[0];\n      }\n      // If positional is the only name, it's okay to use that\n      else {\n        return pname_;\n      }\n    }\n  }\n\n  ///@}\n  /// @name Parser tools\n  ///@{\n\n  /// Process the callback\n  void run_callback()\n  {\n    callback_run_ = true;\n\n    // Run the validators (can change the string)\n    if (!validators_.empty()) {\n      for (std::string& result : results_) {\n        auto err_msg = _validate(result);\n\n        if (!err_msg.empty()) {\n          throw ValidationError(get_name(), err_msg);\n        }\n      }\n    }\n\n    if (!(callback_)) {\n      return;\n    }\n\n    bool local_result;\n    // Num items expected or length of vector, always at least 1\n    // Only valid for a trimming policy\n    int trim_size =\n      std::min<int>(std::max<int>(std::abs(get_items_expected()), 1),\n                    static_cast<int>(results_.size()));\n\n    // Operation depends on the policy setting\n    if (multi_option_policy_ == MultiOptionPolicy::TakeLast) {\n      // Allow multi-option sizes (including 0)\n      results_t partial_result{results_.end() - trim_size, results_.end()};\n      local_result = !callback_(partial_result);\n    } else if (multi_option_policy_ == MultiOptionPolicy::TakeFirst) {\n      results_t partial_result{results_.begin(), results_.begin() + trim_size};\n      local_result = !callback_(partial_result);\n    } else if (multi_option_policy_ == MultiOptionPolicy::Join) {\n      results_t partial_result = {detail::join(results_, \"\\n\")};\n      local_result = !callback_(partial_result);\n    } else {\n      // Exact number required\n      if (get_items_expected() > 0) {\n        if (results_.size() != static_cast<size_t>(get_items_expected())) {\n          throw ArgumentMismatch(get_name(), get_items_expected(), results_.size());\n        }\n\n        // Variable length list\n      } else if (get_items_expected() < 0) {\n        // Require that this be a multiple of expected size and at least as many as expected\n        if (results_.size() < static_cast<size_t>(-get_items_expected()) ||\n            results_.size() % static_cast<size_t>(std::abs(get_type_size())) != 0u) {\n          throw ArgumentMismatch(get_name(), get_items_expected(), results_.size());\n        }\n      }\n\n      local_result = !callback_(results_);\n    }\n\n    if (local_result) {\n      throw ConversionError(get_name(), results_);\n    }\n  }\n\n  /// If options share any of the same names, they are equal (not counting positional)\n  bool operator==(const Option& other) const\n  {\n    for (const std::string& sname : snames_)\n      if (other.check_sname(sname)) {\n        return true;\n      }\n\n    for (const std::string& lname : lnames_)\n      if (other.check_lname(lname)) {\n        return true;\n      }\n\n    if (ignore_case_ ||\n        ignore_underscore_) { // We need to do the inverse, in case we are ignore_case or ignore underscore\n      for (const std::string& sname : other.snames_)\n        if (check_sname(sname)) {\n          return true;\n        }\n\n      for (const std::string& lname : other.lnames_)\n        if (check_lname(lname)) {\n          return true;\n        }\n    }\n\n    return false;\n  }\n\n  /// Check a name. Requires \"-\" or \"--\" for short / long, supports positional name\n  bool check_name(std::string name) const\n  {\n    if (name.length() > 2 && name[0] == '-' && name[1] == '-') {\n      return check_lname(name.substr(2));\n    } else if (name.length() > 1 && name.front() == '-') {\n      return check_sname(name.substr(1));\n    } else {\n      std::string local_pname = pname_;\n\n      if (ignore_underscore_) {\n        local_pname = detail::remove_underscore(local_pname);\n        name = detail::remove_underscore(name);\n      }\n\n      if (ignore_case_) {\n        local_pname = detail::to_lower(local_pname);\n        name = detail::to_lower(name);\n      }\n\n      return name == local_pname;\n    }\n  }\n\n  /// Requires \"-\" to be removed from string\n  bool check_sname(std::string name) const\n  {\n    return (detail::find_member(name, snames_, ignore_case_) >= 0);\n  }\n\n  /// Requires \"--\" to be removed from string\n  bool check_lname(std::string name) const\n  {\n    return (detail::find_member(name, lnames_, ignore_case_,\n                                ignore_underscore_) >= 0);\n  }\n\n  /// Requires \"--\" to be removed from string\n  bool check_fname(std::string name) const\n  {\n    if (fnames_.empty()) {\n      return false;\n    }\n\n    return (detail::find_member(name, fnames_, ignore_case_,\n                                ignore_underscore_) >= 0);\n  }\n\n  std::string get_flag_value(std::string name, std::string input_value) const\n  {\n    static const std::string trueString{\"true\"};\n    static const std::string falseString{\"false\"};\n    static const std::string emptyString{\"{}\"};\n\n    // check for disable flag override_\n    if (disable_flag_override_) {\n      if (!((input_value.empty()) || (input_value == emptyString))) {\n        auto default_ind = detail::find_member(name, fnames_, ignore_case_,\n                                               ignore_underscore_);\n\n        if (default_ind >= 0) {\n          // We can static cast this to size_t because it is more than 0 in this block\n          if (default_flag_values_[static_cast<size_t>(default_ind)].second !=\n              input_value) {\n            throw (ArgumentMismatch::FlagOverride(name));\n          }\n        } else {\n          if (input_value != trueString) {\n            throw (ArgumentMismatch::FlagOverride(name));\n          }\n        }\n      }\n    }\n\n    auto ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);\n\n    if ((input_value.empty()) || (input_value == emptyString)) {\n      return (ind < 0) ? trueString : default_flag_values_[static_cast<size_t>\n             (ind)].second;\n    }\n\n    if (ind < 0) {\n      return input_value;\n    }\n\n    if (default_flag_values_[static_cast<size_t>(ind)].second == falseString) {\n      try {\n        auto val = detail::to_flag_value(input_value);\n        return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(\n                                             -val));\n      } catch (const std::invalid_argument&) {\n        return input_value;\n      }\n    } else {\n      return input_value;\n    }\n  }\n\n  /// Puts a result at the end\n  Option* add_result(std::string s)\n  {\n    _add_result(std::move(s));\n    callback_run_ = false;\n    return this;\n  }\n\n  /// Puts a result at the end and get a count of the number of arguments actually added\n  Option* add_result(std::string s, int& results_added)\n  {\n    results_added = _add_result(std::move(s));\n    callback_run_ = false;\n    return this;\n  }\n\n  /// Puts a result at the end\n  Option* add_result(std::vector<std::string> s)\n  {\n    for (auto& str : s) {\n      _add_result(std::move(str));\n    }\n\n    callback_run_ = false;\n    return this;\n  }\n\n  /// Get a copy of the results\n  std::vector<std::string> results() const\n  {\n    return results_;\n  }\n\n  /// get the results as a particular type\n  template < typename T,\n             enable_if_t < !is_vector<T>::value&& !std::is_const<T>::value,\n                           detail::enabler > = detail::dummy >\n  void results(T& output) const\n  {\n    bool retval;\n\n    if (results_.empty()) {\n      retval = detail::lexical_cast(default_str_, output);\n    } else if (results_.size() == 1) {\n      retval = detail::lexical_cast(results_[0], output);\n    } else {\n      switch (multi_option_policy_) {\n      case MultiOptionPolicy::TakeFirst:\n        retval = detail::lexical_cast(results_.front(), output);\n        break;\n\n      case MultiOptionPolicy::TakeLast:\n      default:\n        retval = detail::lexical_cast(results_.back(), output);\n        break;\n\n      case MultiOptionPolicy::Throw:\n        throw ConversionError(get_name(), results_);\n\n      case MultiOptionPolicy::Join:\n        retval = detail::lexical_cast(detail::join(results_), output);\n        break;\n      }\n    }\n\n    if (!retval) {\n      throw ConversionError(get_name(), results_);\n    }\n  }\n  /// get the results as a vector of a particular type\n  template <typename T> void results(std::vector<T>& output) const\n  {\n    output.clear();\n    bool retval = true;\n\n    for (const auto& elem : results_) {\n      output.emplace_back();\n      retval &= detail::lexical_cast(elem, output.back());\n    }\n\n    if (!retval) {\n      throw ConversionError(get_name(), results_);\n    }\n  }\n\n  /// return the results as a particular type\n  template <typename T> T as() const\n  {\n    T output;\n    results(output);\n    return output;\n  }\n\n  /// See if the callback has been run already\n  bool get_callback_run() const\n  {\n    return callback_run_;\n  }\n\n  ///@}\n  /// @name Custom options\n  ///@{\n\n  /// Set the type function to run when displayed on this option\n  Option* type_name_fn(std::function<std::string()> typefun)\n  {\n    type_name_ = typefun;\n    return this;\n  }\n\n  /// Set a custom option typestring\n  Option* type_name(std::string typeval)\n  {\n    type_name_fn([typeval]() {\n      return typeval;\n    });\n    return this;\n  }\n\n  /// Set a custom option size\n  Option* type_size(int option_type_size)\n  {\n    type_size_ = option_type_size;\n\n    if (type_size_ == 0) {\n      required_ = false;\n    }\n\n    if (option_type_size < 0) {\n      expected_ = -1;\n    }\n\n    return this;\n  }\n\n  /// Set a capture function for the default. Mostly used by App.\n  Option* default_function(const std::function<std::string()>& func)\n  {\n    default_function_ = func;\n    return this;\n  }\n\n  /// Capture the default value from the original value (if it can be captured)\n  Option* capture_default_str()\n  {\n    if (default_function_) {\n      default_str_ = default_function_();\n    }\n\n    return this;\n  }\n\n  /// Set the default value string representation (does not change the contained value)\n  Option* default_str(std::string val)\n  {\n    default_str_ = val;\n    return this;\n  }\n\n  /// Set the default value string representation and evaluate into the bound value\n  Option* default_val(std::string val)\n  {\n    default_str(val);\n    auto old_results = results_;\n    results_ = {val};\n    run_callback();\n    results_ = std::move(old_results);\n    return this;\n  }\n\n  /// Get the full typename for this option\n  std::string get_type_name() const\n  {\n    std::string full_type_name = type_name_();\n\n    if (!validators_.empty()) {\n      for (auto& validator : validators_) {\n        std::string vtype = validator.get_description();\n\n        if (!vtype.empty()) {\n          full_type_name += \":\" + vtype;\n        }\n      }\n    }\n\n    return full_type_name;\n  }\n\nprivate:\n  // run through the validators\n  std::string _validate(std::string& result)\n  {\n    std::string err_msg;\n\n    for (const auto& vali : validators_) {\n      try {\n        err_msg = vali(result);\n      } catch (const ValidationError& err) {\n        err_msg = err.what();\n      }\n\n      if (!err_msg.empty()) {\n        break;\n      }\n    }\n\n    return err_msg;\n  }\n\n  int _add_result(std::string&& result)\n  {\n    int result_count = 0;\n\n    if (delimiter_ == '\\0') {\n      results_.push_back(std::move(result));\n      ++result_count;\n    } else {\n      if ((result.find_first_of(delimiter_) != std::string::npos)) {\n        for (const auto& var : CLI::detail::split(result, delimiter_)) {\n          if (!var.empty()) {\n            results_.push_back(var);\n            ++result_count;\n          }\n        }\n      } else {\n        results_.push_back(std::move(result));\n        ++result_count;\n      }\n    }\n\n    return result_count;\n  }\n};\n\n} // namespace CLI\n\n// From CLI/App.hpp:\n\nnamespace CLI\n{\n\n#ifndef CLI11_PARSE\n#define CLI11_PARSE(app, argc, argv)                                                                                   \\\n    try {                                                                                                              \\\n        (app).parse((argc), (argv));                                                                                   \\\n    } catch(const CLI::ParseError &e) {                                                                                \\\n        return (app).exit(e);                                                                                          \\\n    }\n#endif\n\nnamespace detail\n{\nenum class Classifier { NONE, POSITIONAL_MARK, SHORT, LONG, WINDOWS, SUBCOMMAND, SUBCOMMAND_TERMINATOR };\nstruct AppFriend;\n} // namespace detail\n\nnamespace FailureMessage\n{\nstd::string simple(const App* app, const Error& e);\nstd::string help(const App* app, const Error& e);\n} // namespace FailureMessage\n\nclass App;\n\nusing App_p = std::shared_ptr<App>;\n\nclass Option_group;\n/// Creates a command line program, with very few defaults.\n/** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated\n *  add_option methods make it easy to prepare options. Remember to call `.start` before starting your\n * program, so that the options can be evaluated and the help option doesn't accidentally run your program. */\nclass App\n{\n  friend Option;\n  friend detail::AppFriend;\n\nprotected:\n  // This library follows the Google style guide for member names ending in underscores\n\n  /// @name Basics\n  ///@{\n\n  /// Subcommand name or program name (from parser if name is empty)\n  std::string name_;\n\n  /// Description of the current program/subcommand\n  std::string description_;\n\n  /// If true, allow extra arguments (ie, don't throw an error). INHERITABLE\n  bool allow_extras_{false};\n\n  /// If true, allow extra arguments in the ini file (ie, don't throw an error). INHERITABLE\n  bool allow_config_extras_{false};\n\n  ///  If true, return immediately on an unrecognized option (implies allow_extras) INHERITABLE\n  bool prefix_command_{false};\n\n  /// If set to true the name was automatically generated from the command line vs a user set name\n  bool has_automatic_name_{false};\n\n  /// If set to true the subcommand is required to be processed and used, ignored for main app\n  bool required_{false};\n\n  /// If set to true the subcommand is disabled and cannot be used, ignored for main app\n  bool disabled_{false};\n\n  /// Flag indicating that the pre_parse_callback has been triggered\n  bool pre_parse_called_{false};\n\n  /// Flag indicating that the callback for the subcommand should be executed immediately on parse completion which is\n  /// before help or ini files are processed. INHERITABLE\n  bool immediate_callback_{false};\n\n  /// This is a function that runs prior to the start of parsing\n  std::function<void(size_t)> pre_parse_callback_;\n\n  /// This is a function that runs when complete. Great for subcommands. Can throw.\n  std::function<void()> callback_;\n\n  ///@}\n  /// @name Options\n  ///@{\n\n  /// The default values for options, customizable and changeable INHERITABLE\n  OptionDefaults option_defaults_;\n\n  /// The list of options, stored locally\n  std::vector<Option_p> options_;\n\n  ///@}\n  /// @name Help\n  ///@{\n\n  /// Footer to put after all options in the help output INHERITABLE\n  std::string footer_;\n\n  /// A pointer to the help flag if there is one INHERITABLE\n  Option* help_ptr_{nullptr};\n\n  /// A pointer to the help all flag if there is one INHERITABLE\n  Option* help_all_ptr_{nullptr};\n\n  /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer)\n  std::shared_ptr<FormatterBase> formatter_{new Formatter()};\n\n  /// The error message printing function INHERITABLE\n  std::function<std::string(const App*, const Error& e)> failure_message_ =\n    FailureMessage::simple;\n\n  ///@}\n  /// @name Parsing\n  ///@{\n\n  using missing_t = std::vector<std::pair<detail::Classifier, std::string>>;\n\n  /// Pair of classifier, string for missing options. (extra detail is removed on returning from parse)\n  ///\n  /// This is faster and cleaner than storing just a list of strings and reparsing. This may contain the -- separator.\n  missing_t missing_;\n\n  /// This is a list of pointers to options with the original parse order\n  std::vector<Option*> parse_order_;\n\n  /// This is a list of the subcommands collected, in order\n  std::vector<App*> parsed_subcommands_;\n\n  /// this is a list of subcommands that are exclusionary to this one\n  std::set<App*> exclude_subcommands_;\n\n  /// This is a list of options which are exclusionary to this App, if the options were used this subcommand should\n  /// not be\n  std::set<Option*> exclude_options_;\n\n  ///@}\n  /// @name Subcommands\n  ///@{\n\n  /// Storage for subcommand list\n  std::vector<App_p> subcommands_;\n\n  /// If true, the program name is not case sensitive INHERITABLE\n  bool ignore_case_{false};\n\n  /// If true, the program should ignore underscores INHERITABLE\n  bool ignore_underscore_{false};\n\n  /// Allow subcommand fallthrough, so that parent commands can collect commands after subcommand.  INHERITABLE\n  bool fallthrough_{false};\n\n  /// Allow '/' for options for Windows like options. Defaults to true on Windows, false otherwise. INHERITABLE\n  bool allow_windows_style_options_{\n#ifdef _WIN32\n    true\n#else\n    false\n#endif\n  };\n  /// specify that positional arguments come at the end of the argument sequence not inheritable\n  bool positionals_at_end_{false};\n\n  /// If set to true the subcommand will start each parse disabled\n  bool disabled_by_default_{false};\n  /// If set to true the subcommand will be reenabled at the start of each parse\n  bool enabled_by_default_{false};\n  /// If set to true positional options are validated before assigning INHERITABLE\n  bool validate_positionals_{false};\n  /// A pointer to the parent if this is a subcommand\n  App* parent_{nullptr};\n\n  /// Counts the number of times this command/subcommand was parsed\n  size_t parsed_ = 0;\n\n  /// Minimum required subcommands (not inheritable!)\n  size_t require_subcommand_min_ = 0;\n\n  /// Max number of subcommands allowed (parsing stops after this number). 0 is unlimited INHERITABLE\n  size_t require_subcommand_max_ = 0;\n\n  /// Minimum required options (not inheritable!)\n  size_t require_option_min_ = 0;\n\n  /// Max number of options allowed. 0 is unlimited (not inheritable)\n  size_t require_option_max_ = 0;\n\n  /// The group membership INHERITABLE\n  std::string group_{\"Subcommands\"};\n\n  ///@}\n  /// @name Config\n  ///@{\n\n  /// The name of the connected config file\n  std::string config_name_;\n\n  /// True if ini is required (throws if not present), if false simply keep going.\n  bool config_required_{false};\n\n  /// Pointer to the config option\n  Option* config_ptr_{nullptr};\n\n  /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer)\n  std::shared_ptr<Config> config_formatter_{new ConfigINI()};\n\n  ///@}\n\n  /// Special private constructor for subcommand\n  App(std::string app_description, std::string app_name, App* parent)\n    : name_(std::move(app_name)), description_(std::move(app_description)),\n      parent_(parent)\n  {\n    // Inherit if not from a nullptr\n    if (parent_ != nullptr) {\n      if (parent_->help_ptr_ != nullptr) {\n        set_help_flag(parent_->help_ptr_->get_name(false, true),\n                      parent_->help_ptr_->get_description());\n      }\n\n      if (parent_->help_all_ptr_ != nullptr)\n        set_help_all_flag(parent_->help_all_ptr_->get_name(false, true),\n                          parent_->help_all_ptr_->get_description());\n\n      /// OptionDefaults\n      option_defaults_ = parent_->option_defaults_;\n      // INHERITABLE\n      failure_message_ = parent_->failure_message_;\n      allow_extras_ = parent_->allow_extras_;\n      allow_config_extras_ = parent_->allow_config_extras_;\n      prefix_command_ = parent_->prefix_command_;\n      immediate_callback_ = parent_->immediate_callback_;\n      ignore_case_ = parent_->ignore_case_;\n      ignore_underscore_ = parent_->ignore_underscore_;\n      fallthrough_ = parent_->fallthrough_;\n      validate_positionals_ = parent_->validate_positionals_;\n      allow_windows_style_options_ = parent_->allow_windows_style_options_;\n      group_ = parent_->group_;\n      footer_ = parent_->footer_;\n      formatter_ = parent_->formatter_;\n      config_formatter_ = parent_->config_formatter_;\n      require_subcommand_max_ = parent_->require_subcommand_max_;\n    }\n  }\n\npublic:\n  /// @name Basic\n  ///@{\n\n  /// Create a new program. Pass in the same arguments as main(), along with a help string.\n  explicit App(std::string app_description = \"\", std::string app_name = \"\")\n    : App(app_description, app_name, nullptr)\n  {\n    set_help_flag(\"-h,--help\", \"Print this help message and exit\");\n  }\n\n  /// virtual destructor\n  virtual ~App() = default;\n\n  /// Set a callback for the end of parsing.\n  ///\n  /// Due to a bug in c++11,\n  /// it is not possible to overload on std::function (fixed in c++14\n  /// and backported to c++11 on newer compilers). Use capture by reference\n  /// to get a pointer to App if needed.\n  App* callback(std::function<void()> app_callback)\n  {\n    callback_ = std::move(app_callback);\n    return this;\n  }\n\n  /// Set a callback to execute prior to parsing.\n  ///\n  App* preparse_callback(std::function<void(size_t)> pp_callback)\n  {\n    pre_parse_callback_ = std::move(pp_callback);\n    return this;\n  }\n\n  /// Set a name for the app (empty will use parser to set the name)\n  App* name(std::string app_name = \"\")\n  {\n    name_ = app_name;\n    has_automatic_name_ = false;\n    return this;\n  }\n\n  /// Remove the error when extras are left over on the command line.\n  App* allow_extras(bool allow = true)\n  {\n    allow_extras_ = allow;\n    return this;\n  }\n\n  /// Remove the error when extras are left over on the command line.\n  App* required(bool require = true)\n  {\n    required_ = require;\n    return this;\n  }\n\n  /// Disable the subcommand or option group\n  App* disabled(bool disable = true)\n  {\n    disabled_ = disable;\n    return this;\n  }\n\n  /// Set the subcommand to be disabled by default, so on clear(), at the start of each parse it is disabled\n  App* disabled_by_default(bool disable = true)\n  {\n    disabled_by_default_ = disable;\n    return this;\n  }\n\n  /// Set the subcommand to be enabled by default, so on clear(), at the start of each parse it is enabled (not\n  /// disabled)\n  App* enabled_by_default(bool enable = true)\n  {\n    enabled_by_default_ = enable;\n    return this;\n  }\n\n  /// Set the subcommand callback to be executed immediately on subcommand completion\n  App* immediate_callback(bool immediate = true)\n  {\n    immediate_callback_ = immediate;\n    return this;\n  }\n\n  /// Set the subcommand to validate positional arguments before assigning\n  App* validate_positionals(bool validate = true)\n  {\n    validate_positionals_ = validate;\n    return this;\n  }\n\n  /// Remove the error when extras are left over on the command line.\n  /// Will also call App::allow_extras().\n  App* allow_config_extras(bool allow = true)\n  {\n    allow_extras(allow);\n    allow_config_extras_ = allow;\n    return this;\n  }\n\n  /// Do not parse anything after the first unrecognized option and return\n  App* prefix_command(bool allow = true)\n  {\n    prefix_command_ = allow;\n    return this;\n  }\n\n  /// Ignore case. Subcommands inherit value.\n  App* ignore_case(bool value = true)\n  {\n    ignore_case_ = value;\n\n    if (parent_ != nullptr && !name_.empty()) {\n      for (const auto& subc : parent_->subcommands_) {\n        if (subc.get() != this && (this->check_name(subc->name_) ||\n                                   subc->check_name(this->name_))) {\n          throw OptionAlreadyAdded(subc->name_);\n        }\n      }\n    }\n\n    return this;\n  }\n\n  /// Allow windows style options, such as `/opt`. First matching short or long name used. Subcommands inherit value.\n  App* allow_windows_style_options(bool value = true)\n  {\n    allow_windows_style_options_ = value;\n    return this;\n  }\n\n  /// Specify that the positional arguments are only at the end of the sequence\n  App* positionals_at_end(bool value = true)\n  {\n    positionals_at_end_ = value;\n    return this;\n  }\n\n  /// Ignore underscore. Subcommands inherit value.\n  App* ignore_underscore(bool value = true)\n  {\n    ignore_underscore_ = value;\n\n    if (parent_ != nullptr && !name_.empty()) {\n      for (const auto& subc : parent_->subcommands_) {\n        if (subc.get() != this && (this->check_name(subc->name_) ||\n                                   subc->check_name(this->name_))) {\n          throw OptionAlreadyAdded(subc->name_);\n        }\n      }\n    }\n\n    return this;\n  }\n\n  /// Set the help formatter\n  App* formatter(std::shared_ptr<FormatterBase> fmt)\n  {\n    formatter_ = fmt;\n    return this;\n  }\n\n  /// Set the help formatter\n  App* formatter_fn(\n    std::function<std::string(const App*, std::string, AppFormatMode)> fmt)\n  {\n    formatter_ = std::make_shared<FormatterLambda>(fmt);\n    return this;\n  }\n\n  /// Set the config formatter\n  App* config_formatter(std::shared_ptr<Config> fmt)\n  {\n    config_formatter_ = fmt;\n    return this;\n  }\n\n  /// Check to see if this subcommand was parsed, true only if received on command line.\n  bool parsed() const\n  {\n    return parsed_ > 0;\n  }\n\n  /// Get the OptionDefault object, to set option defaults\n  OptionDefaults* option_defaults()\n  {\n    return &option_defaults_;\n  }\n\n  ///@}\n  /// @name Adding options\n  ///@{\n\n  /// Add an option, will automatically understand the type for common types.\n  ///\n  /// To use, create a variable with the expected type, and pass it in after the name.\n  /// After start is called, you can use count to see if the value was passed, and\n  /// the value will be initialized properly. Numbers, vectors, and strings are supported.\n  ///\n  /// ->required(), ->default, and the validators are options,\n  /// The positional options take an optional number of arguments.\n  ///\n  /// For example,\n  ///\n  ///     std::string filename;\n  ///     program.add_option(\"filename\", filename, \"description of filename\");\n  ///\n  Option* add_option(std::string option_name,\n                     callback_t option_callback,\n                     std::string option_description = \"\",\n                     bool defaulted = false,\n                     std::function<std::string()> func = {})\n  {\n    Option myopt{option_name, option_description, option_callback, this};\n\n    if (std::find_if(std::begin(options_),\n    std::end(options_), [&myopt](const Option_p & v) {\n    return *v == myopt;\n  }) == std::end(options_)) {\n      options_.emplace_back();\n      Option_p& option = options_.back();\n      option.reset(new Option(option_name, option_description, option_callback,\n                              this));\n      // Set the default string capture function\n      option->default_function(func);\n\n      // For compatibility with CLI11 1.7 and before, capture the default string here\n      if (defaulted) {\n        option->capture_default_str();\n      }\n\n      // Transfer defaults to the new option\n      option_defaults_.copy_to(option.get());\n\n      // Don't bother to capture if we already did\n      if (!defaulted && option->get_always_capture_default()) {\n        option->capture_default_str();\n      }\n\n      return option.get();\n    }\n    else {\n      throw OptionAlreadyAdded(myopt.get_name());\n    }\n  }\n\n  /// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`)\n  template < typename T, enable_if_t < !is_vector<T>::value &\n                                       !std::is_const<T>::value, detail::enabler > = detail::dummy >\n  Option * add_option(std::string option_name,\n                      T& variable, ///< The variable to set\n                      std::string option_description = \"\",\n                      bool defaulted = false)\n  {\n    auto fun = [&variable](CLI::results_t res) {\n      return detail::lexical_cast(res[0], variable);\n    };\n    Option* opt = add_option(option_name, fun, option_description,\n    defaulted, [&variable]() {\n      return std::string(CLI::detail::to_string(variable));\n    });\n    opt->type_name(detail::type_name<T>());\n    return opt;\n  }\n\n  /// Add option for a callback of a specific type\n  template < typename T, enable_if_t < !is_vector<T>::value,\n                                       detail::enabler > = detail::dummy >\n  Option * add_option_function(std::string option_name,\n                               const std::function<void(const T&)>& func,  ///< the callback to execute\n                               std::string option_description = \"\")\n  {\n    auto fun = [func](CLI::results_t res) {\n      T variable;\n      bool result = detail::lexical_cast(res[0], variable);\n\n      if (result) {\n        func(variable);\n      }\n\n      return result;\n    };\n    Option* opt = add_option(option_name, std::move(fun), option_description,\n                             false);\n    opt->type_name(detail::type_name<T>());\n    return opt;\n  }\n\n  /// Add option with no description or variable assignment\n  Option* add_option(std::string option_name)\n  {\n    return add_option(option_name, CLI::callback_t(), std::string{}, false);\n  }\n\n  /// Add option with description but with no variable assignment or callback\n  template < typename T,\n             enable_if_t < std::is_const<T>::value&&\n                           std::is_constructible<std::string, T>::value, detail::enabler > =\n             detail::dummy >\n  Option * add_option(std::string option_name, T& option_description)\n  {\n    return add_option(option_name, CLI::callback_t(), option_description, false);\n  }\n\n  /// Add option for vectors\n  template <typename T>\n  Option* add_option(std::string option_name,\n                     std::vector<T>& variable, ///< The variable vector to set\n                     std::string option_description = \"\",\n                     bool defaulted = false)\n  {\n    auto fun = [&variable](CLI::results_t res) {\n      bool retval = true;\n      variable.clear();\n      variable.reserve(res.size());\n\n      for (const auto& elem : res) {\n        variable.emplace_back();\n        retval &= detail::lexical_cast(elem, variable.back());\n      }\n\n      return (!variable.empty()) && retval;\n    };\n    auto default_function = [&variable]() {\n      std::vector<std::string> defaults;\n      defaults.resize(variable.size());\n      std::transform(variable.begin(), variable.end(), defaults.begin(), [](T & val) {\n        return std::string(CLI::detail::to_string(val));\n      });\n      return std::string(\"[\" + detail::join(defaults) + \"]\");\n    };\n    Option* opt = add_option(option_name, fun, option_description, defaulted,\n                             default_function);\n    opt->type_name(detail::type_name<T>())->type_size(-1);\n    return opt;\n  }\n\n  /// Add option for a vector callback of a specific type\n  template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>\n  Option * add_option_function(std::string option_name,\n                               const std::function<void(const T&)>& func,  ///< the callback to execute\n                               std::string option_description = \"\")\n  {\n    CLI::callback_t fun = [func](CLI::results_t res) {\n      T values;\n      bool retval = true;\n      values.reserve(res.size());\n\n      for (const auto& elem : res) {\n        values.emplace_back();\n        retval &= detail::lexical_cast(elem, values.back());\n      }\n\n      if (retval) {\n        func(values);\n      }\n\n      return retval;\n    };\n    Option* opt = add_option(option_name, std::move(fun),\n                             std::move(option_description), false);\n    opt->type_name(detail::type_name<T>())->type_size(-1);\n    return opt;\n  }\n\n  /// Set a help flag, replace the existing one if present\n  Option* set_help_flag(std::string flag_name = \"\",\n                        const std::string& help_description = \"\")\n  {\n    // take flag_description by const reference otherwise add_flag tries to assign to help_description\n    if (help_ptr_ != nullptr) {\n      remove_option(help_ptr_);\n      help_ptr_ = nullptr;\n    }\n\n    // Empty name will simply remove the help flag\n    if (!flag_name.empty()) {\n      help_ptr_ = add_flag(flag_name, help_description);\n      help_ptr_->configurable(false);\n    }\n\n    return help_ptr_;\n  }\n\n  /// Set a help all flag, replaced the existing one if present\n  Option* set_help_all_flag(std::string help_name = \"\",\n                            const std::string& help_description = \"\")\n  {\n    // take flag_description by const reference otherwise add_flag tries to assign to flag_description\n    if (help_all_ptr_ != nullptr) {\n      remove_option(help_all_ptr_);\n      help_all_ptr_ = nullptr;\n    }\n\n    // Empty name will simply remove the help all flag\n    if (!help_name.empty()) {\n      help_all_ptr_ = add_flag(help_name, help_description);\n      help_all_ptr_->configurable(false);\n    }\n\n    return help_all_ptr_;\n  }\n\nprivate:\n  /// Internal function for adding a flag\n  Option* _add_flag_internal(std::string flag_name, CLI::callback_t fun,\n                             std::string flag_description)\n  {\n    Option* opt;\n\n    if (detail::has_default_flag_values(flag_name)) {\n      // check for default values and if it has them\n      auto flag_defaults = detail::get_default_flag_values(flag_name);\n      detail::remove_default_flag_values(flag_name);\n      opt = add_option(std::move(flag_name), std::move(fun),\n                       std::move(flag_description), false);\n\n      for (const auto& fname : flag_defaults) {\n        opt->fnames_.push_back(fname.first);\n      }\n\n      opt->default_flag_values_ = std::move(flag_defaults);\n    } else {\n      opt = add_option(std::move(flag_name), std::move(fun),\n                       std::move(flag_description), false);\n    }\n\n    // flags cannot have positional values\n    if (opt->get_positional()) {\n      auto pos_name = opt->get_name(true);\n      remove_option(opt);\n      throw IncorrectConstruction::PositionalFlag(pos_name);\n    }\n\n    opt->type_size(0);\n    return opt;\n  }\n\npublic:\n  /// Add a flag with no description or variable assignment\n  Option* add_flag(std::string flag_name)\n  {\n    return _add_flag_internal(flag_name, CLI::callback_t(), std::string{});\n  }\n\n  /// Add flag with description but with no variable assignment or callback\n  /// takes a constant string,  if a variable string is passed that variable will be assigned the results from the\n  /// flag\n  template < typename T,\n             enable_if_t < std::is_const<T>::value&&\n                           std::is_constructible<std::string, T>::value, detail::enabler > =\n             detail::dummy >\n  Option * add_flag(std::string flag_name, T& flag_description)\n  {\n    return _add_flag_internal(flag_name, CLI::callback_t(), flag_description);\n  }\n\n  /// Add option for flag with integer result - defaults to allowing multiple passings, but can be forced to one if\n  /// `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used.\n  template < typename T,\n             enable_if_t < std::is_integral<T>::value&& !is_bool<T>::value,\n                           detail::enabler > = detail::dummy >\n  Option * add_flag(std::string flag_name,\n                    T& flag_count, ///< A variable holding the count\n                    std::string flag_description = \"\")\n  {\n    flag_count = 0;\n    CLI::callback_t fun = [&flag_count](CLI::results_t res) {\n      try {\n        detail::sum_flag_vector(res, flag_count);\n      } catch (const std::invalid_argument&) {\n        return false;\n      }\n\n      return true;\n    };\n    return _add_flag_internal(flag_name, std::move(fun),\n                              std::move(flag_description));\n  }\n\n  /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes that\n  /// can be converted from a string\n  template < typename T,\n             enable_if_t < !is_vector<T>::value&& !std::is_const<T>::value&&\n                           (!std::is_integral<T>::value || is_bool<T>::value)&&\n                           !std::is_constructible<std::function<void(int)>, T>::value,\n                           detail::enabler > = detail::dummy >\n  Option * add_flag(std::string flag_name,\n                    T& flag_result, ///< A variable holding true if passed\n                    std::string flag_description = \"\")\n  {\n    CLI::callback_t fun = [&flag_result](CLI::results_t res) {\n      if (res.size() != 1) {\n        return false;\n      }\n\n      return CLI::detail::lexical_cast(res[0], flag_result);\n    };\n    Option* opt = _add_flag_internal(flag_name, std::move(fun),\n                                     std::move(flag_description));\n    opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);\n    return opt;\n  }\n\n  /// Vector version to capture multiple flags.\n  template < typename T,\n             enable_if_t < !std::is_assignable<std::function<void(int64_t)>, T>::value,\n                           detail::enabler > = detail::dummy >\n  Option * add_flag(std::string flag_name,\n                    std::vector<T>& flag_results, ///< A vector of values with the flag results\n                    std::string flag_description = \"\")\n  {\n    CLI::callback_t fun = [&flag_results](CLI::results_t res) {\n      bool retval = true;\n\n      for (const auto& elem : res) {\n        flag_results.emplace_back();\n        retval &= detail::lexical_cast(elem, flag_results.back());\n      }\n\n      return retval;\n    };\n    return _add_flag_internal(flag_name, std::move(fun),\n                              std::move(flag_description));\n  }\n\n  /// Add option for callback that is triggered with a true flag and takes no arguments\n  Option* add_flag_callback(std::string flag_name,\n                            std::function<void(void)> function, ///< A function to call, void(void)\n                            std::string flag_description = \"\")\n  {\n    CLI::callback_t fun = [function](CLI::results_t res) {\n      if (res.size() != 1) {\n        return false;\n      }\n\n      bool trigger;\n      auto result = CLI::detail::lexical_cast(res[0], trigger);\n\n      if (trigger) {\n        function();\n      }\n\n      return result;\n    };\n    Option* opt = _add_flag_internal(flag_name, std::move(fun),\n                                     std::move(flag_description));\n    opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);\n    return opt;\n  }\n\n  /// Add option for callback with an integer value\n  Option* add_flag_function(std::string flag_name,\n                            std::function<void(int64_t)> function, ///< A function to call, void(int)\n                            std::string flag_description = \"\")\n  {\n    CLI::callback_t fun = [function](CLI::results_t res) {\n      int64_t flag_count = 0;\n      detail::sum_flag_vector(res, flag_count);\n      function(flag_count);\n      return true;\n    };\n    return _add_flag_internal(flag_name, std::move(fun),\n                              std::move(flag_description));\n  }\n\n#ifdef CLI11_CPP14\n  /// Add option for callback (C++14 or better only)\n  Option* add_flag(std::string flag_name,\n                   std::function<void(int64_t)> function, ///< A function to call, void(int64_t)\n                   std::string flag_description = \"\")\n  {\n    return add_flag_function(std::move(flag_name), std::move(function),\n                             std::move(flag_description));\n  }\n#endif\n\n  /// Add set of options (No default, temp reference, such as an inline set) DEPRECATED\n  template <typename T>\n  Option* add_set(std::string option_name,\n                  T& member,           ///< The selected member of the set\n                  std::set<T> options, ///< The set of possibilities\n                  std::string option_description = \"\")\n  {\n    Option* opt = add_option(option_name, member, std::move(option_description));\n    opt->check(IsMember{options});\n    return opt;\n  }\n\n  /// Add set of options (No default, set can be changed afterwards - do not destroy the set) DEPRECATED\n  template <typename T>\n  Option* add_mutable_set(std::string option_name,\n                          T& member,                  ///< The selected member of the set\n                          const std::set<T>& options, ///< The set of possibilities\n                          std::string option_description = \"\")\n  {\n    Option* opt = add_option(option_name, member, std::move(option_description));\n    opt->check(IsMember{&options});\n    return opt;\n  }\n\n  /// Add set of options (with default, static set, such as an inline set) DEPRECATED\n  template <typename T>\n  Option* add_set(std::string option_name,\n                  T& member,           ///< The selected member of the set\n                  std::set<T> options, ///< The set of possibilities\n                  std::string option_description,\n                  bool defaulted)\n  {\n    Option* opt = add_option(option_name, member, std::move(option_description),\n                             defaulted);\n    opt->check(IsMember{options});\n    return opt;\n  }\n\n  /// Add set of options (with default, set can be changed afterwards - do not destroy the set) DEPRECATED\n  template <typename T>\n  Option* add_mutable_set(std::string option_name,\n                          T& member,                  ///< The selected member of the set\n                          const std::set<T>& options, ///< The set of possibilities\n                          std::string option_description,\n                          bool defaulted)\n  {\n    Option* opt = add_option(option_name, member, std::move(option_description),\n                             defaulted);\n    opt->check(IsMember{&options});\n    return opt;\n  }\n\n  /// Add set of options, string only, ignore case (no default, static set) DEPRECATED\n  CLI11_DEPRECATED(\"Use ->transform(CLI::IsMember(..., CLI::ignore_case)) instead\")\n  Option* add_set_ignore_case(std::string option_name,\n                              std::string& member,           ///< The selected member of the set\n                              std::set<std::string> options, ///< The set of possibilities\n                              std::string option_description = \"\")\n  {\n    Option* opt = add_option(option_name, member, std::move(option_description));\n    opt->transform(IsMember{options, CLI::ignore_case});\n    return opt;\n  }\n\n  /// Add set of options, string only, ignore case (no default, set can be changed afterwards - do not destroy the\n  /// set) DEPRECATED\n  CLI11_DEPRECATED(\"Use ->transform(CLI::IsMember(..., CLI::ignore_case)) with a (shared) pointer instead\")\n  Option* add_mutable_set_ignore_case(std::string option_name,\n                                      std::string& member,                  ///< The selected member of the set\n                                      const std::set<std::string>& options, ///< The set of possibilities\n                                      std::string option_description = \"\")\n  {\n    Option* opt = add_option(option_name, member, std::move(option_description));\n    opt->transform(IsMember{&options, CLI::ignore_case});\n    return opt;\n  }\n\n  /// Add set of options, string only, ignore case (default, static set) DEPRECATED\n  CLI11_DEPRECATED(\"Use ->transform(CLI::IsMember(..., CLI::ignore_case)) instead\")\n  Option* add_set_ignore_case(std::string option_name,\n                              std::string& member,           ///< The selected member of the set\n                              std::set<std::string> options, ///< The set of possibilities\n                              std::string option_description,\n                              bool defaulted)\n  {\n    Option* opt = add_option(option_name, member, std::move(option_description),\n                             defaulted);\n    opt->transform(IsMember{options, CLI::ignore_case});\n    return opt;\n  }\n\n  /// Add set of options, string only, ignore case (default, set can be changed afterwards - do not destroy the set)\n  /// DEPRECATED\n  CLI11_DEPRECATED(\"Use ->transform(CLI::IsMember(...)) with a (shared) pointer instead\")\n  Option* add_mutable_set_ignore_case(std::string option_name,\n                                      std::string& member,                  ///< The selected member of the set\n                                      const std::set<std::string>& options, ///< The set of possibilities\n                                      std::string option_description,\n                                      bool defaulted)\n  {\n    Option* opt = add_option(option_name, member, std::move(option_description),\n                             defaulted);\n    opt->transform(IsMember{&options, CLI::ignore_case});\n    return opt;\n  }\n\n  /// Add set of options, string only, ignore underscore (no default, static set) DEPRECATED\n  CLI11_DEPRECATED(\"Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) instead\")\n  Option* add_set_ignore_underscore(std::string option_name,\n                                    std::string& member,           ///< The selected member of the set\n                                    std::set<std::string> options, ///< The set of possibilities\n                                    std::string option_description = \"\")\n  {\n    Option* opt = add_option(option_name, member, std::move(option_description));\n    opt->transform(IsMember{options, CLI::ignore_underscore});\n    return opt;\n  }\n\n  /// Add set of options, string only, ignore underscore (no default, set can be changed afterwards - do not destroy\n  /// the set) DEPRECATED\n  CLI11_DEPRECATED(\"Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead\")\n  Option* add_mutable_set_ignore_underscore(std::string option_name,\n      std::string& member,                  ///< The selected member of the set\n      const std::set<std::string>& options, ///< The set of possibilities\n      std::string option_description = \"\")\n  {\n    Option* opt = add_option(option_name, member, std::move(option_description));\n    opt->transform(IsMember{options, CLI::ignore_underscore});\n    return opt;\n  }\n\n  /// Add set of options, string only, ignore underscore (default, static set) DEPRECATED\n  CLI11_DEPRECATED(\"Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) instead\")\n  Option* add_set_ignore_underscore(std::string option_name,\n                                    std::string& member,           ///< The selected member of the set\n                                    std::set<std::string> options, ///< The set of possibilities\n                                    std::string option_description,\n                                    bool defaulted)\n  {\n    Option* opt = add_option(option_name, member, std::move(option_description),\n                             defaulted);\n    opt->transform(IsMember{options, CLI::ignore_underscore});\n    return opt;\n  }\n\n  /// Add set of options, string only, ignore underscore (default, set can be changed afterwards - do not destroy the\n  /// set) DEPRECATED\n  CLI11_DEPRECATED(\"Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead\")\n  Option* add_mutable_set_ignore_underscore(std::string option_name,\n      std::string& member,                  ///< The selected member of the set\n      const std::set<std::string>& options, ///< The set of possibilities\n      std::string option_description,\n      bool defaulted)\n  {\n    Option* opt = add_option(option_name, member, std::move(option_description),\n                             defaulted);\n    opt->transform(IsMember{&options, CLI::ignore_underscore});\n    return opt;\n  }\n\n  /// Add set of options, string only, ignore underscore and case (no default, static set) DEPRECATED\n  CLI11_DEPRECATED(\"Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead\")\n  Option* add_set_ignore_case_underscore(std::string option_name,\n                                         std::string& member,           ///< The selected member of the set\n                                         std::set<std::string> options, ///< The set of possibilities\n                                         std::string option_description = \"\")\n  {\n    Option* opt = add_option(option_name, member, std::move(option_description));\n    opt->transform(IsMember{options, CLI::ignore_underscore, CLI::ignore_case});\n    return opt;\n  }\n\n  /// Add set of options, string only, ignore underscore and case (no default, set can be changed afterwards - do not\n  /// destroy the set) DEPRECATED\n  CLI11_DEPRECATED(\n    \"Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead\")\n  Option* add_mutable_set_ignore_case_underscore(std::string option_name,\n      std::string& member, ///< The selected member of the set\n      const std::set<std::string>& options, ///< The set of possibilities\n      std::string option_description = \"\")\n  {\n    Option* opt = add_option(option_name, member, std::move(option_description));\n    opt->transform(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case});\n    return opt;\n  }\n\n  /// Add set of options, string only, ignore underscore and case (default, static set) DEPRECATED\n  CLI11_DEPRECATED(\"Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead\")\n  Option* add_set_ignore_case_underscore(std::string option_name,\n                                         std::string& member,           ///< The selected member of the set\n                                         std::set<std::string> options, ///< The set of possibilities\n                                         std::string option_description,\n                                         bool defaulted)\n  {\n    Option* opt = add_option(option_name, member, std::move(option_description),\n                             defaulted);\n    opt->transform(IsMember{options, CLI::ignore_underscore, CLI::ignore_case});\n    return opt;\n  }\n\n  /// Add set of options, string only, ignore underscore and case (default, set can be changed afterwards - do not\n  /// destroy the set) DEPRECATED\n  CLI11_DEPRECATED(\n    \"Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead\")\n  Option* add_mutable_set_ignore_case_underscore(std::string option_name,\n      std::string& member, ///< The selected member of the set\n      const std::set<std::string>& options, ///< The set of possibilities\n      std::string option_description,\n      bool defaulted)\n  {\n    Option* opt = add_option(option_name, member, std::move(option_description),\n                             defaulted);\n    opt->transform(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case});\n    return opt;\n  }\n\n  /// Add a complex number\n  template <typename T>\n  Option* add_complex(std::string option_name,\n                      T& variable,\n                      std::string option_description = \"\",\n                      bool defaulted = false,\n                      std::string label = \"COMPLEX\")\n  {\n    std::string simple_name = CLI::detail::split(option_name, ',').at(0);\n    CLI::callback_t fun = [&variable, simple_name, label](results_t res) {\n      if (res[1].back() == 'i') {\n        res[1].pop_back();\n      }\n\n      double x, y;\n      bool worked = detail::lexical_cast(res[0], x) &&\n                    detail::lexical_cast(res[1], y);\n\n      if (worked) {\n        variable = T(x, y);\n      }\n\n      return worked;\n    };\n    auto default_function = [&variable]() {\n      std::stringstream out;\n      out << variable;\n      return out.str();\n    };\n    CLI::Option* opt =\n      add_option(option_name, std::move(fun), std::move(option_description),\n                 defaulted, default_function);\n    opt->type_name(label)->type_size(2);\n    return opt;\n  }\n\n  /// Set a configuration ini file option, or clear it if no name passed\n  Option* set_config(std::string option_name = \"\",\n                     std::string default_filename = \"\",\n                     std::string help_message = \"Read an ini file\",\n                     bool config_required = false)\n  {\n    // Remove existing config if present\n    if (config_ptr_ != nullptr) {\n      remove_option(config_ptr_);\n    }\n\n    // Only add config if option passed\n    if (!option_name.empty()) {\n      config_name_ = default_filename;\n      config_required_ = config_required;\n      config_ptr_ = add_option(option_name, config_name_, help_message,\n                               !default_filename.empty());\n      config_ptr_->configurable(false);\n    }\n\n    return config_ptr_;\n  }\n\n  /// Removes an option from the App. Takes an option pointer. Returns true if found and removed.\n  bool remove_option(Option* opt)\n  {\n    // Make sure no links exist\n    for (Option_p& op : options_) {\n      op->remove_needs(opt);\n      op->remove_excludes(opt);\n    }\n\n    if (help_ptr_ == opt) {\n      help_ptr_ = nullptr;\n    }\n\n    if (help_all_ptr_ == opt) {\n      help_all_ptr_ = nullptr;\n    }\n\n    auto iterator =\n      std::find_if(std::begin(options_),\n    std::end(options_), [opt](const Option_p & v) {\n      return v.get() == opt;\n    });\n\n    if (iterator != std::end(options_)) {\n      options_.erase(iterator);\n      return true;\n    }\n\n    return false;\n  }\n\n  /// creates an option group as part of the given app\n  template <typename T = Option_group>\n  T * add_option_group(std::string group_name,\n                       std::string group_description = \"\")\n  {\n    auto option_group = std::make_shared<T>(std::move(group_description),\n                                            group_name, nullptr);\n    auto ptr = option_group.get();\n    // move to App_p for overload resolution on older gcc versions\n    App_p app_ptr = std::dynamic_pointer_cast<App>(option_group);\n    add_subcommand(std::move(app_ptr));\n    return ptr;\n  }\n\n  ///@}\n  /// @name Subcommmands\n  ///@{\n\n  /// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag\n  App* add_subcommand(std::string subcommand_name = \"\",\n                      std::string subcommand_description = \"\")\n  {\n    CLI::App_p subcom = std::shared_ptr<App>(new App(std::move(\n                          subcommand_description), subcommand_name, this));\n    return add_subcommand(std::move(subcom));\n  }\n\n  /// Add a previously created app as a subcommand\n  App* add_subcommand(CLI::App_p subcom)\n  {\n    if (!subcom) {\n      throw IncorrectConstruction(\"passed App is not valid\");\n    }\n\n    if (!subcom->name_.empty()) {\n      for (const auto& subc : subcommands_)\n        if (subc->check_name(subcom->name_) || subcom->check_name(subc->name_)) {\n          throw OptionAlreadyAdded(subc->name_);\n        }\n    }\n\n    subcom->parent_ = this;\n    subcommands_.push_back(std::move(subcom));\n    return subcommands_.back().get();\n  }\n\n  /// Removes a subcommand from the App. Takes a subcommand pointer. Returns true if found and removed.\n  bool remove_subcommand(App* subcom)\n  {\n    // Make sure no links exist\n    for (App_p& sub : subcommands_) {\n      sub->remove_excludes(subcom);\n    }\n\n    auto iterator = std::find_if(\n    std::begin(subcommands_), std::end(subcommands_), [subcom](const App_p & v) {\n      return v.get() == subcom;\n    });\n\n    if (iterator != std::end(subcommands_)) {\n      subcommands_.erase(iterator);\n      return true;\n    }\n\n    return false;\n  }\n  /// Check to see if a subcommand is part of this command (doesn't have to be in command line)\n  /// returns the first subcommand if passed a nullptr\n  App* get_subcommand(App* subcom) const\n  {\n    if (subcom == nullptr) {\n      throw OptionNotFound(\"nullptr passed\");\n    }\n\n    for (const App_p& subcomptr : subcommands_)\n      if (subcomptr.get() == subcom) {\n        return subcom;\n      }\n\n    throw OptionNotFound(subcom->get_name());\n  }\n\n  /// Check to see if a subcommand is part of this command (text version)\n  App* get_subcommand(std::string subcom) const\n  {\n    auto subc = _find_subcommand(subcom, false, false);\n\n    if (subc == nullptr) {\n      throw OptionNotFound(subcom);\n    }\n\n    return subc;\n  }\n  /// Get a pointer to subcommand by index\n  App* get_subcommand(int index = 0) const\n  {\n    if (index >= 0) {\n      auto uindex = static_cast<unsigned>(index);\n\n      if (uindex < subcommands_.size()) {\n        return subcommands_[uindex].get();\n      }\n    }\n\n    throw OptionNotFound(std::to_string(index));\n  }\n\n  /// Check to see if a subcommand is part of this command and get a shared_ptr to it\n  CLI::App_p get_subcommand_ptr(App* subcom) const\n  {\n    if (subcom == nullptr) {\n      throw OptionNotFound(\"nullptr passed\");\n    }\n\n    for (const App_p& subcomptr : subcommands_)\n      if (subcomptr.get() == subcom) {\n        return subcomptr;\n      }\n\n    throw OptionNotFound(subcom->get_name());\n  }\n\n  /// Check to see if a subcommand is part of this command (text version)\n  CLI::App_p get_subcommand_ptr(std::string subcom) const\n  {\n    for (const App_p& subcomptr : subcommands_)\n      if (subcomptr->check_name(subcom)) {\n        return subcomptr;\n      }\n\n    throw OptionNotFound(subcom);\n  }\n\n  /// Get an owning pointer to subcommand by index\n  CLI::App_p get_subcommand_ptr(int index = 0) const\n  {\n    if (index >= 0) {\n      auto uindex = static_cast<unsigned>(index);\n\n      if (uindex < subcommands_.size()) {\n        return subcommands_[uindex];\n      }\n    }\n\n    throw OptionNotFound(std::to_string(index));\n  }\n\n  /// Check to see if an option group is part of this App\n  App* get_option_group(std::string group_name) const\n  {\n    for (const App_p& app : subcommands_) {\n      if (app->name_.empty() && app->group_ == group_name) {\n        return app.get();\n      }\n    }\n\n    throw OptionNotFound(group_name);\n  }\n\n  /// No argument version of count counts the number of times this subcommand was\n  /// passed in. The main app will return 1. Unnamed subcommands will also return 1 unless\n  /// otherwise modified in a callback\n  size_t count() const\n  {\n    return parsed_;\n  }\n\n  /// Get a count of all the arguments processed in options and subcommands, this excludes arguments which were\n  /// treated as extras.\n  size_t count_all() const\n  {\n    size_t cnt{0};\n\n    for (auto& opt : options_) {\n      cnt += opt->count();\n    }\n\n    for (auto& sub : subcommands_) {\n      cnt += sub->count_all();\n    }\n\n    if (!get_name().empty()) { // for named subcommands add the number of times the subcommand was called\n      cnt += parsed_;\n    }\n\n    return cnt;\n  }\n\n  /// Changes the group membership\n  App* group(std::string group_name)\n  {\n    group_ = group_name;\n    return this;\n  }\n\n  /// The argumentless form of require subcommand requires 1 or more subcommands\n  App* require_subcommand()\n  {\n    require_subcommand_min_ = 1;\n    require_subcommand_max_ = 0;\n    return this;\n  }\n\n  /// Require a subcommand to be given (does not affect help call)\n  /// The number required can be given. Negative values indicate maximum\n  /// number allowed (0 for any number). Max number inheritable.\n  App* require_subcommand(int value)\n  {\n    if (value < 0) {\n      require_subcommand_min_ = 0;\n      require_subcommand_max_ = static_cast<size_t>(-value);\n    } else {\n      require_subcommand_min_ = static_cast<size_t>(value);\n      require_subcommand_max_ = static_cast<size_t>(value);\n    }\n\n    return this;\n  }\n\n  /// Explicitly control the number of subcommands required. Setting 0\n  /// for the max means unlimited number allowed. Max number inheritable.\n  App* require_subcommand(size_t min, size_t max)\n  {\n    require_subcommand_min_ = min;\n    require_subcommand_max_ = max;\n    return this;\n  }\n\n  /// The argumentless form of require option requires 1 or more options be used\n  App* require_option()\n  {\n    require_option_min_ = 1;\n    require_option_max_ = 0;\n    return this;\n  }\n\n  /// Require an option to be given (does not affect help call)\n  /// The number required can be given. Negative values indicate maximum\n  /// number allowed (0 for any number).\n  App* require_option(int value)\n  {\n    if (value < 0) {\n      require_option_min_ = 0;\n      require_option_max_ = static_cast<size_t>(-value);\n    } else {\n      require_option_min_ = static_cast<size_t>(value);\n      require_option_max_ = static_cast<size_t>(value);\n    }\n\n    return this;\n  }\n\n  /// Explicitly control the number of options required. Setting 0\n  /// for the max means unlimited number allowed. Max number inheritable.\n  App* require_option(size_t min, size_t max)\n  {\n    require_option_min_ = min;\n    require_option_max_ = max;\n    return this;\n  }\n\n  /// Stop subcommand fallthrough, so that parent commands cannot collect commands after subcommand.\n  /// Default from parent, usually set on parent.\n  App* fallthrough(bool value = true)\n  {\n    fallthrough_ = value;\n    return this;\n  }\n\n  /// Check to see if this subcommand was parsed, true only if received on command line.\n  /// This allows the subcommand to be directly checked.\n  operator bool() const\n  {\n    return parsed_ > 0;\n  }\n\n  ///@}\n  /// @name Extras for subclassing\n  ///@{\n\n  /// This allows subclasses to inject code before callbacks but after parse.\n  ///\n  /// This does not run if any errors or help is thrown.\n  virtual void pre_callback() {}\n\n  ///@}\n  /// @name Parsing\n  ///@{\n  //\n  /// Reset the parsed data\n  void clear()\n  {\n    parsed_ = 0;\n    pre_parse_called_ = false;\n    missing_.clear();\n    parsed_subcommands_.clear();\n\n    for (const Option_p& opt : options_) {\n      opt->clear();\n    }\n\n    for (const App_p& subc : subcommands_) {\n      subc->clear();\n    }\n  }\n\n  /// Parses the command line - throws errors.\n  /// This must be called after the options are in but before the rest of the program.\n  void parse(int argc, const char* const* argv)\n  {\n    // If the name is not set, read from command line\n    if (name_.empty() || has_automatic_name_) {\n      has_automatic_name_ = true;\n      name_ = argv[0];\n    }\n\n    std::vector<std::string> args;\n    args.reserve(static_cast<size_t>(argc - 1));\n\n    for (int i = argc - 1; i > 0; i--) {\n      args.emplace_back(argv[i]);\n    }\n\n    parse(std::move(args));\n  }\n\n  /// Parse a single string as if it contained command line arguments.\n  /// This function splits the string into arguments then calls parse(std::vector<std::string> &)\n  /// the function takes an optional boolean argument specifying if the programName is included in the string to\n  /// process\n  void parse(std::string commandline, bool program_name_included = false)\n  {\n    if (program_name_included) {\n      auto nstr = detail::split_program_name(commandline);\n\n      if ((name_.empty()) || (has_automatic_name_)) {\n        has_automatic_name_ = true;\n        name_ = nstr.first;\n      }\n\n      commandline = std::move(nstr.second);\n    } else {\n      detail::trim(commandline);\n    }\n\n    // the next section of code is to deal with quoted arguments after an '=' or ':' for windows like operations\n    if (!commandline.empty()) {\n      commandline = detail::find_and_modify(commandline, \"=\", detail::escape_detect);\n\n      if (allow_windows_style_options_) {\n        commandline = detail::find_and_modify(commandline, \":\", detail::escape_detect);\n      }\n    }\n\n    auto args = detail::split_up(std::move(commandline));\n    // remove all empty strings\n    args.erase(std::remove(args.begin(), args.end(), std::string{}), args.end());\n    std::reverse(args.begin(), args.end());\n    parse(std::move(args));\n  }\n\n  /// The real work is done here. Expects a reversed vector.\n  /// Changes the vector to the remaining options.\n  void parse(std::vector<std::string>& args)\n  {\n    // Clear if parsed\n    if (parsed_ > 0) {\n      clear();\n    }\n\n    // parsed_ is incremented in commands/subcommands,\n    // but placed here to make sure this is cleared when\n    // running parse after an error is thrown, even by _validate or _configure.\n    parsed_ = 1;\n    _validate();\n    _configure();\n    // set the parent as nullptr as this object should be the top now\n    parent_ = nullptr;\n    parsed_ = 0;\n    _parse(args);\n    run_callback();\n  }\n\n  /// The real work is done here. Expects a reversed vector.\n  void parse(std::vector<std::string>&& args)\n  {\n    // Clear if parsed\n    if (parsed_ > 0) {\n      clear();\n    }\n\n    // parsed_ is incremented in commands/subcommands,\n    // but placed here to make sure this is cleared when\n    // running parse after an error is thrown, even by _validate or _configure.\n    parsed_ = 1;\n    _validate();\n    _configure();\n    // set the parent as nullptr as this object should be the top now\n    parent_ = nullptr;\n    parsed_ = 0;\n    _parse(std::move(args));\n    run_callback();\n  }\n\n  /// Provide a function to print a help message. The function gets access to the App pointer and error.\n  void failure_message(std::function<std::string(const App*, const Error& e)>\n                       function)\n  {\n    failure_message_ = function;\n  }\n\n  /// Print a nice error message and return the exit code\n  int exit(const Error& e, std::ostream& out = std::cout,\n           std::ostream& err = std::cerr) const\n  {\n    /// Avoid printing anything if this is a CLI::RuntimeError\n    if (dynamic_cast<const CLI::RuntimeError*>(&e) != nullptr) {\n      return e.get_exit_code();\n    }\n\n    if (dynamic_cast<const CLI::CallForHelp*>(&e) != nullptr) {\n      out << help();\n      return e.get_exit_code();\n    }\n\n    if (dynamic_cast<const CLI::CallForAllHelp*>(&e) != nullptr) {\n      out << help(\"\", AppFormatMode::All);\n      return e.get_exit_code();\n    }\n\n    if (e.get_exit_code() != static_cast<int>(ExitCodes::Success)) {\n      if (failure_message_) {\n        err << failure_message_(this, e) << std::flush;\n      }\n    }\n\n    return e.get_exit_code();\n  }\n\n  ///@}\n  /// @name Post parsing\n  ///@{\n\n  /// Counts the number of times the given option was passed.\n  size_t count(std::string option_name) const\n  {\n    return get_option(option_name)->count();\n  }\n\n  /// Get a subcommand pointer list to the currently selected subcommands (after parsing by default, in command\n  /// line order; use parsed = false to get the original definition list.)\n  std::vector<App*> get_subcommands() const\n  {\n    return parsed_subcommands_;\n  }\n\n  /// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all\n  /// subcommands (const)\n  std::vector<const App*> get_subcommands(const std::function<bool(const App*)>&\n                                          filter) const\n  {\n    std::vector<const App*> subcomms(subcommands_.size());\n    std::transform(std::begin(subcommands_), std::end(subcommands_),\n    std::begin(subcomms), [](const App_p & v) {\n      return v.get();\n    });\n\n    if (filter) {\n      subcomms.erase(std::remove_if(std::begin(subcomms),\n                                    std::end(subcomms),\n      [&filter](const App * app) {\n        return !filter(app);\n      }),\n      std::end(subcomms));\n    }\n\n    return subcomms;\n  }\n\n  /// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all\n  /// subcommands\n  std::vector<App*> get_subcommands(const std::function<bool(App*)>& filter)\n  {\n    std::vector<App*> subcomms(subcommands_.size());\n    std::transform(std::begin(subcommands_), std::end(subcommands_),\n    std::begin(subcomms), [](const App_p & v) {\n      return v.get();\n    });\n\n    if (filter) {\n      subcomms.erase(\n      std::remove_if(std::begin(subcomms), std::end(subcomms), [&filter](App * app) {\n        return !filter(app);\n      }),\n      std::end(subcomms));\n    }\n\n    return subcomms;\n  }\n\n  /// Check to see if given subcommand was selected\n  bool got_subcommand(App* subcom) const\n  {\n    // get subcom needed to verify that this was a real subcommand\n    return get_subcommand(subcom)->parsed_ > 0;\n  }\n\n  /// Check with name instead of pointer to see if subcommand was selected\n  bool got_subcommand(std::string subcommand_name) const\n  {\n    return get_subcommand(subcommand_name)->parsed_ > 0;\n  }\n\n  /// Sets excluded options for the subcommand\n  App* excludes(Option* opt)\n  {\n    if (opt == nullptr) {\n      throw OptionNotFound(\"nullptr passed\");\n    }\n\n    exclude_options_.insert(opt);\n    return this;\n  }\n\n  /// Sets excluded subcommands for the subcommand\n  App* excludes(App* app)\n  {\n    if ((app == this) || (app == nullptr)) {\n      throw OptionNotFound(\"nullptr passed\");\n    }\n\n    auto res = exclude_subcommands_.insert(app);\n\n    // subcommand exclusion should be symmetric\n    if (res.second) {\n      app->exclude_subcommands_.insert(this);\n    }\n\n    return this;\n  }\n\n  /// Removes an option from the excludes list of this subcommand\n  bool remove_excludes(Option* opt)\n  {\n    auto iterator = std::find(std::begin(exclude_options_),\n                              std::end(exclude_options_), opt);\n\n    if (iterator != std::end(exclude_options_)) {\n      exclude_options_.erase(iterator);\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n  /// Removes a subcommand from this excludes list of this subcommand\n  bool remove_excludes(App* app)\n  {\n    auto iterator = std::find(std::begin(exclude_subcommands_),\n                              std::end(exclude_subcommands_), app);\n\n    if (iterator != std::end(exclude_subcommands_)) {\n      auto other_app = *iterator;\n      exclude_subcommands_.erase(iterator);\n      other_app->remove_excludes(this);\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n  ///@}\n  /// @name Help\n  ///@{\n\n  /// Set footer.\n  App* footer(std::string footer_string)\n  {\n    footer_ = std::move(footer_string);\n    return this;\n  }\n\n  /// Produce a string that could be read in as a config of the current values of the App. Set default_also to\n  /// include default arguments. Prefix will add a string to the beginning of each option.\n  std::string config_to_str(bool default_also = false,\n                            bool write_description = false) const\n  {\n    return config_formatter_->to_config(this, default_also, write_description, \"\");\n  }\n\n  /// Makes a help message, using the currently configured formatter\n  /// Will only do one subcommand at a time\n  std::string help(std::string prev = \"\",\n                   AppFormatMode mode = AppFormatMode::Normal) const\n  {\n    if (prev.empty()) {\n      prev = get_name();\n    } else {\n      prev += \" \" + get_name();\n    }\n\n    // Delegate to subcommand if needed\n    auto selected_subcommands = get_subcommands();\n\n    if (!selected_subcommands.empty()) {\n      return selected_subcommands.at(0)->help(prev, mode);\n    } else {\n      return formatter_->make_help(this, prev, mode);\n    }\n  }\n\n  ///@}\n  /// @name Getters\n  ///@{\n\n  /// Access the formatter\n  std::shared_ptr<FormatterBase> get_formatter() const\n  {\n    return formatter_;\n  }\n\n  /// Access the config formatter\n  std::shared_ptr<Config> get_config_formatter() const\n  {\n    return config_formatter_;\n  }\n\n  /// Get the app or subcommand description\n  std::string get_description() const\n  {\n    return description_;\n  }\n\n  /// Set the description of the app\n  App* description(std::string app_description)\n  {\n    description_ = std::move(app_description);\n    return this;\n  }\n\n  /// Get the list of options (user facing function, so returns raw pointers), has optional filter function\n  std::vector<const Option*> get_options(const std::function<bool(const Option*)>\n                                         filter = {}) const\n  {\n    std::vector<const Option*> options(options_.size());\n    std::transform(std::begin(options_), std::end(options_),\n    std::begin(options), [](const Option_p & val) {\n      return val.get();\n    });\n\n    if (filter) {\n      options.erase(std::remove_if(std::begin(options),\n                                   std::end(options),\n      [&filter](const Option * opt) {\n        return !filter(opt);\n      }),\n      std::end(options));\n    }\n\n    return options;\n  }\n\n  /// Get an option by name (noexcept non-const version)\n  Option* get_option_no_throw(std::string option_name) noexcept\n  {\n    for (Option_p& opt : options_) {\n      if (opt->check_name(option_name)) {\n        return opt.get();\n      }\n    }\n\n    for (auto& subc : subcommands_) {\n      // also check down into nameless subcommands\n      if (subc->get_name().empty()) {\n        auto opt = subc->get_option_no_throw(option_name);\n\n        if (opt != nullptr) {\n          return opt;\n        }\n      }\n    }\n\n    return nullptr;\n  }\n\n  /// Get an option by name (noexcept const version)\n  const Option* get_option_no_throw(std::string option_name) const noexcept\n  {\n    for (const Option_p& opt : options_) {\n      if (opt->check_name(option_name)) {\n        return opt.get();\n      }\n    }\n\n    for (const auto& subc : subcommands_) {\n      // also check down into nameless subcommands\n      if (subc->get_name().empty()) {\n        auto opt = subc->get_option_no_throw(option_name);\n\n        if (opt != nullptr) {\n          return opt;\n        }\n      }\n    }\n\n    return nullptr;\n  }\n\n  /// Get an option by name\n  const Option* get_option(std::string option_name) const\n  {\n    auto opt = get_option_no_throw(option_name);\n\n    if (opt == nullptr) {\n      throw OptionNotFound(option_name);\n    }\n\n    return opt;\n  }\n\n  /// Get an option by name (non-const version)\n  Option* get_option(std::string option_name)\n  {\n    auto opt = get_option_no_throw(option_name);\n\n    if (opt == nullptr) {\n      throw OptionNotFound(option_name);\n    }\n\n    return opt;\n  }\n\n  /// Shortcut bracket operator for getting a pointer to an option\n  const Option* operator[](const std::string& option_name) const\n  {\n    return get_option(option_name);\n  }\n\n  /// Shortcut bracket operator for getting a pointer to an option\n  const Option* operator[](const char* option_name) const\n  {\n    return get_option(option_name);\n  }\n\n  /// Check the status of ignore_case\n  bool get_ignore_case() const\n  {\n    return ignore_case_;\n  }\n\n  /// Check the status of ignore_underscore\n  bool get_ignore_underscore() const\n  {\n    return ignore_underscore_;\n  }\n\n  /// Check the status of fallthrough\n  bool get_fallthrough() const\n  {\n    return fallthrough_;\n  }\n\n  /// Check the status of the allow windows style options\n  bool get_allow_windows_style_options() const\n  {\n    return allow_windows_style_options_;\n  }\n\n  /// Check the status of the allow windows style options\n  bool get_positionals_at_end() const\n  {\n    return positionals_at_end_;\n  }\n\n  /// Get the group of this subcommand\n  const std::string& get_group() const\n  {\n    return group_;\n  }\n\n  /// Get footer.\n  const std::string& get_footer() const\n  {\n    return footer_;\n  }\n\n  /// Get the required min subcommand value\n  size_t get_require_subcommand_min() const\n  {\n    return require_subcommand_min_;\n  }\n\n  /// Get the required max subcommand value\n  size_t get_require_subcommand_max() const\n  {\n    return require_subcommand_max_;\n  }\n\n  /// Get the required min option value\n  size_t get_require_option_min() const\n  {\n    return require_option_min_;\n  }\n\n  /// Get the required max option value\n  size_t get_require_option_max() const\n  {\n    return require_option_max_;\n  }\n\n  /// Get the prefix command status\n  bool get_prefix_command() const\n  {\n    return prefix_command_;\n  }\n\n  /// Get the status of allow extras\n  bool get_allow_extras() const\n  {\n    return allow_extras_;\n  }\n\n  /// Get the status of required\n  bool get_required() const\n  {\n    return required_;\n  }\n\n  /// Get the status of disabled\n  bool get_disabled() const\n  {\n    return disabled_;\n  }\n\n  /// Get the status of disabled\n  bool get_immediate_callback() const\n  {\n    return immediate_callback_;\n  }\n\n  /// Get the status of disabled by default\n  bool get_disabled_by_default() const\n  {\n    return disabled_by_default_;\n  }\n\n  /// Get the status of disabled by default\n  bool get_enabled_by_default() const\n  {\n    return enabled_by_default_;\n  }\n  /// Get the status of validating positionals\n  bool get_validate_positionals() const\n  {\n    return validate_positionals_;\n  }\n\n  /// Get the status of allow extras\n  bool get_allow_config_extras() const\n  {\n    return allow_config_extras_;\n  }\n\n  /// Get a pointer to the help flag.\n  Option* get_help_ptr()\n  {\n    return help_ptr_;\n  }\n\n  /// Get a pointer to the help flag. (const)\n  const Option* get_help_ptr() const\n  {\n    return help_ptr_;\n  }\n\n  /// Get a pointer to the help all flag. (const)\n  const Option* get_help_all_ptr() const\n  {\n    return help_all_ptr_;\n  }\n\n  /// Get a pointer to the config option.\n  Option* get_config_ptr()\n  {\n    return config_ptr_;\n  }\n\n  /// Get a pointer to the config option. (const)\n  const Option* get_config_ptr() const\n  {\n    return config_ptr_;\n  }\n\n  /// Get the parent of this subcommand (or nullptr if master app)\n  App* get_parent()\n  {\n    return parent_;\n  }\n\n  /// Get the parent of this subcommand (or nullptr if master app) (const version)\n  const App* get_parent() const\n  {\n    return parent_;\n  }\n\n  /// Get the name of the current app\n  std::string get_name() const\n  {\n    return name_;\n  }\n\n  /// Get a display name for an app\n  std::string get_display_name() const\n  {\n    return (!name_.empty()) ? name_ : \"[Option Group: \" + get_group() + \"]\";\n  }\n\n  /// Check the name, case insensitive and underscore insensitive if set\n  bool check_name(std::string name_to_check) const\n  {\n    std::string local_name = name_;\n\n    if (ignore_underscore_) {\n      local_name = detail::remove_underscore(name_);\n      name_to_check = detail::remove_underscore(name_to_check);\n    }\n\n    if (ignore_case_) {\n      local_name = detail::to_lower(name_);\n      name_to_check = detail::to_lower(name_to_check);\n    }\n\n    return local_name == name_to_check;\n  }\n\n  /// Get the groups available directly from this option (in order)\n  std::vector<std::string> get_groups() const\n  {\n    std::vector<std::string> groups;\n\n    for (const Option_p& opt : options_) {\n      // Add group if it is not already in there\n      if (std::find(groups.begin(), groups.end(), opt->get_group()) == groups.end()) {\n        groups.push_back(opt->get_group());\n      }\n    }\n\n    return groups;\n  }\n\n  /// This gets a vector of pointers with the original parse order\n  const std::vector<Option*>& parse_order() const\n  {\n    return parse_order_;\n  }\n\n  /// This returns the missing options from the current subcommand\n  std::vector<std::string> remaining(bool recurse = false) const\n  {\n    std::vector<std::string> miss_list;\n\n    for (const std::pair<detail::Classifier, std::string>& miss : missing_) {\n      miss_list.push_back(std::get<1>(miss));\n    }\n\n    // Get from a subcommand that may allow extras\n    if (recurse) {\n      if (!allow_extras_) {\n        for (const auto& sub : subcommands_) {\n          if (sub->name_.empty() && !sub->missing_.empty()) {\n            for (const std::pair<detail::Classifier, std::string>& miss : sub->missing_) {\n              miss_list.push_back(std::get<1>(miss));\n            }\n          }\n        }\n      }\n\n      // Recurse into subcommands\n\n      for (const App* sub : parsed_subcommands_) {\n        std::vector<std::string> output = sub->remaining(recurse);\n        std::copy(std::begin(output), std::end(output), std::back_inserter(miss_list));\n      }\n    }\n\n    return miss_list;\n  }\n\n  /// This returns the missing options in a form ready for processing by another command line program\n  std::vector<std::string> remaining_for_passthrough(bool recurse = false) const\n  {\n    std::vector<std::string> miss_list = remaining(recurse);\n    std::reverse(std::begin(miss_list), std::end(miss_list));\n    return miss_list;\n  }\n\n  /// This returns the number of remaining options, minus the -- separator\n  size_t remaining_size(bool recurse = false) const\n  {\n    auto remaining_options = static_cast<size_t>(std::count_if(\n                               std::begin(missing_), std::end(missing_), [](const\n    std::pair<detail::Classifier, std::string>& val) {\n      return val.first != detail::Classifier::POSITIONAL_MARK;\n    }));\n\n    if (recurse) {\n      for (const App_p& sub : subcommands_) {\n        remaining_options += sub->remaining_size(recurse);\n      }\n    }\n\n    return remaining_options;\n  }\n\n  ///@}\n\nprotected:\n  /// Check the options to make sure there are no conflicts.\n  ///\n  /// Currently checks to see if multiple positionals exist with -1 args and checks if the min and max options are\n  /// feasible\n  void _validate() const\n  {\n    auto pcount = std::count_if(std::begin(options_),\n    std::end(options_), [](const Option_p & opt) {\n      return opt->get_items_expected() < 0 && opt->get_positional();\n    });\n\n    if (pcount > 1) {\n      throw InvalidError(name_);\n    }\n\n    size_t nameless_subs{0};\n\n    for (const App_p& app : subcommands_) {\n      app->_validate();\n\n      if (app->get_name().empty()) {\n        ++nameless_subs;\n      }\n    }\n\n    if (require_option_min_ > 0) {\n      if (require_option_max_ > 0) {\n        if (require_option_max_ < require_option_min_) {\n          throw (InvalidError(\"Required min options greater than required max options\",\n                              ExitCodes::InvalidError));\n        }\n      }\n\n      if (require_option_min_ > (options_.size() + nameless_subs)) {\n        throw (InvalidError(\"Required min options greater than number of available options\",\n                            ExitCodes::InvalidError));\n      }\n    }\n  }\n\n  /// configure subcommands to enable parsing through the current object\n  /// set the correct fallthrough and prefix for nameless subcommands and manage the automatic enable or disable\n  /// makes sure parent is set correctly\n  void _configure()\n  {\n    if (disabled_by_default_) {\n      disabled_ = true;\n    }\n\n    if (enabled_by_default_) {\n      disabled_ = false;\n    }\n\n    for (const App_p& app : subcommands_) {\n      if (app->has_automatic_name_) {\n        app->name_.clear();\n      }\n\n      if (app->name_.empty()) {\n        app->fallthrough_ =\n          false; // make sure fallthrough_ is false to prevent infinite loop\n        app->prefix_command_ = false;\n      }\n\n      // make sure the parent is set to be this object in preparation for parse\n      app->parent_ = this;\n      app->_configure();\n    }\n  }\n  /// Internal function to run (App) callback, bottom up\n  void run_callback()\n  {\n    pre_callback();\n\n    // run the callbacks for the received subcommands\n    for (App* subc : get_subcommands()) {\n      if (!subc->immediate_callback_) {\n        subc->run_callback();\n      }\n    }\n\n    // now run callbacks for option_groups\n    for (auto& subc : subcommands_) {\n      if (!subc->immediate_callback_ && subc->name_.empty() &&\n          subc->count_all() > 0) {\n        subc->run_callback();\n      }\n    }\n\n    // finally run the main callback\n    if (callback_ && (parsed_ > 0)) {\n      if (!name_.empty() || count_all() > 0) {\n        callback_();\n      }\n    }\n  }\n\n  /// Check to see if a subcommand is valid. Give up immediately if subcommand max has been reached.\n  bool _valid_subcommand(const std::string& current,\n                         bool ignore_used = true) const\n  {\n    // Don't match if max has been reached - but still check parents\n    if (require_subcommand_max_ != 0 &&\n        parsed_subcommands_.size() >= require_subcommand_max_) {\n      return parent_ != nullptr && parent_->_valid_subcommand(current, ignore_used);\n    }\n\n    auto com = _find_subcommand(current, true, ignore_used);\n\n    if (com != nullptr) {\n      return true;\n    }\n\n    // Check parent if exists, else return false\n    return parent_ != nullptr && parent_->_valid_subcommand(current, ignore_used);\n  }\n\n  /// Selects a Classifier enum based on the type of the current argument\n  detail::Classifier _recognize(const std::string& current,\n                                bool ignore_used_subcommands = true) const\n  {\n    std::string dummy1, dummy2;\n\n    if (current == \"--\") {\n      return detail::Classifier::POSITIONAL_MARK;\n    }\n\n    if (_valid_subcommand(current, ignore_used_subcommands)) {\n      return detail::Classifier::SUBCOMMAND;\n    }\n\n    if (detail::split_long(current, dummy1, dummy2)) {\n      return detail::Classifier::LONG;\n    }\n\n    if (detail::split_short(current, dummy1, dummy2)) {\n      return detail::Classifier::SHORT;\n    }\n\n    if ((allow_windows_style_options_) &&\n        (detail::split_windows_style(current, dummy1, dummy2))) {\n      return detail::Classifier::WINDOWS;\n    }\n\n    if ((current == \"++\") && !name_.empty() && parent_ != nullptr) {\n      return detail::Classifier::SUBCOMMAND_TERMINATOR;\n    }\n\n    return detail::Classifier::NONE;\n  }\n\n  // The parse function is now broken into several parts, and part of process\n\n  /// Read and process an ini file (main app only)\n  void _process_ini()\n  {\n    // Process an INI file\n    if (config_ptr_ != nullptr) {\n      if (*config_ptr_) {\n        config_ptr_->run_callback();\n        config_required_ = true;\n      }\n\n      if (!config_name_.empty()) {\n        try {\n          std::vector<ConfigItem> values = config_formatter_->from_file(config_name_);\n          _parse_config(values);\n        } catch (const FileError&) {\n          if (config_required_) {\n            throw;\n          }\n        }\n      }\n    }\n  }\n\n  /// Get envname options if not yet passed. Runs on *all* subcommands.\n  void _process_env()\n  {\n    for (const Option_p& opt : options_) {\n      if (opt->count() == 0 && !opt->envname_.empty()) {\n        char* buffer = nullptr;\n        std::string ename_string;\n#ifdef _MSC_VER\n        // Windows version\n        size_t sz = 0;\n\n        if (_dupenv_s(&buffer, &sz, opt->envname_.c_str()) == 0 && buffer != nullptr) {\n          ename_string = std::string(buffer);\n          free(buffer);\n        }\n\n#else\n        // This also works on Windows, but gives a warning\n        buffer = std::getenv(opt->envname_.c_str());\n\n        if (buffer != nullptr) {\n          ename_string = std::string(buffer);\n        }\n\n#endif\n\n        if (!ename_string.empty()) {\n          opt->add_result(ename_string);\n        }\n      }\n    }\n\n    for (App_p& sub : subcommands_) {\n      if (sub->get_name().empty() || !sub->immediate_callback_) {\n        sub->_process_env();\n      }\n    }\n  }\n\n  /// Process callbacks. Runs on *all* subcommands.\n  void _process_callbacks()\n  {\n    for (App_p& sub : subcommands_) {\n      // process the priority option_groups first\n      if (sub->get_name().empty() && sub->immediate_callback_) {\n        if (sub->count_all() > 0) {\n          sub->_process_callbacks();\n          sub->run_callback();\n        }\n      }\n    }\n\n    for (const Option_p& opt : options_) {\n      if (opt->count() > 0 && !opt->get_callback_run()) {\n        opt->run_callback();\n      }\n    }\n\n    for (App_p& sub : subcommands_) {\n      if (!sub->immediate_callback_) {\n        sub->_process_callbacks();\n      }\n    }\n  }\n\n  /// Run help flag processing if any are found.\n  ///\n  /// The flags allow recursive calls to remember if there was a help flag on a parent.\n  void _process_help_flags(bool trigger_help = false,\n                           bool trigger_all_help = false) const\n  {\n    const Option* help_ptr = get_help_ptr();\n    const Option* help_all_ptr = get_help_all_ptr();\n\n    if (help_ptr != nullptr && help_ptr->count() > 0) {\n      trigger_help = true;\n    }\n\n    if (help_all_ptr != nullptr && help_all_ptr->count() > 0) {\n      trigger_all_help = true;\n    }\n\n    // If there were parsed subcommands, call those. First subcommand wins if there are multiple ones.\n    if (!parsed_subcommands_.empty()) {\n      for (const App* sub : parsed_subcommands_) {\n        sub->_process_help_flags(trigger_help, trigger_all_help);\n      }\n\n      // Only the final subcommand should call for help. All help wins over help.\n    } else if (trigger_all_help) {\n      throw CallForAllHelp();\n    } else if (trigger_help) {\n      throw CallForHelp();\n    }\n  }\n\n  /// Verify required options and cross requirements. Subcommands too (only if selected).\n  void _process_requirements()\n  {\n    // check excludes\n    bool excluded{false};\n    std::string excluder;\n\n    for (auto& opt : exclude_options_) {\n      if (opt->count() > 0) {\n        excluded = true;\n        excluder = opt->get_name();\n      }\n    }\n\n    for (auto& subc : exclude_subcommands_) {\n      if (subc->count_all() > 0) {\n        excluded = true;\n        excluder = subc->get_display_name();\n      }\n    }\n\n    if (excluded) {\n      if (count_all() > 0) {\n        throw ExcludesError(get_display_name(), excluder);\n      }\n\n      // if we are excluded but didn't receive anything, just return\n      return;\n    }\n\n    size_t used_options = 0;\n\n    for (const Option_p& opt : options_) {\n      if (opt->count() != 0) {\n        ++used_options;\n      }\n\n      // Required or partially filled\n      if (opt->get_required() || opt->count() != 0) {\n        // Make sure enough -N arguments parsed (+N is already handled in parsing function)\n        if (opt->get_items_expected() < 0 &&\n            opt->count() < static_cast<size_t>(-opt->get_items_expected())) {\n          throw ArgumentMismatch::AtLeast(opt->get_name(), -opt->get_items_expected());\n        }\n\n        // Required but empty\n        if (opt->get_required() && opt->count() == 0) {\n          throw RequiredError(opt->get_name());\n        }\n      }\n\n      // Requires\n      for (const Option* opt_req : opt->needs_)\n        if (opt->count() > 0 && opt_req->count() == 0) {\n          throw RequiresError(opt->get_name(), opt_req->get_name());\n        }\n\n      // Excludes\n      for (const Option* opt_ex : opt->excludes_)\n        if (opt->count() > 0 && opt_ex->count() != 0) {\n          throw ExcludesError(opt->get_name(), opt_ex->get_name());\n        }\n    }\n\n    // check for the required number of subcommands\n    if (require_subcommand_min_ > 0) {\n      auto selected_subcommands = get_subcommands();\n\n      if (require_subcommand_min_ > selected_subcommands.size()) {\n        throw RequiredError::Subcommand(require_subcommand_min_);\n      }\n    }\n\n    // Max error cannot occur, the extra subcommand will parse as an ExtrasError or a remaining item.\n\n    // run this loop to check how many unnamed subcommands were actually used since they are considered options from\n    // the perspective of an App\n    for (App_p& sub : subcommands_) {\n      if (sub->disabled_) {\n        continue;\n      }\n\n      if (sub->name_.empty() && sub->count_all() > 0) {\n        ++used_options;\n      }\n    }\n\n    if (require_option_min_ > used_options || (require_option_max_ > 0 &&\n        require_option_max_ < used_options)) {\n      auto option_list = detail::join(options_, [](const Option_p & ptr) {\n        return ptr->get_name(false, true);\n      });\n\n      if (option_list.compare(0, 10, \"-h,--help,\") == 0) {\n        option_list.erase(0, 10);\n      }\n\n      auto subc_list = get_subcommands([](App * app) {\n        return ((app->get_name().empty()) && (!app->disabled_));\n      });\n\n      if (!subc_list.empty()) {\n        option_list += \",\" + detail::join(subc_list, [](const App * app) {\n          return app->get_display_name();\n        });\n      }\n\n      throw RequiredError::Option(require_option_min_, require_option_max_,\n                                  used_options, option_list);\n    }\n\n    // now process the requirements for subcommands if needed\n    for (App_p& sub : subcommands_) {\n      if (sub->disabled_) {\n        continue;\n      }\n\n      if (sub->name_.empty() && sub->required_ == false) {\n        if (sub->count_all() == 0) {\n          if (require_option_min_ > 0 && require_option_min_ <= used_options) {\n            continue;\n            // if we have met the requirement and there is nothing in this option group skip checking\n            // requirements\n          }\n\n          if (require_option_max_ > 0 && used_options >= require_option_min_) {\n            continue;\n            // if we have met the requirement and there is nothing in this option group skip checking\n            // requirements\n          }\n        }\n      }\n\n      if (sub->count() > 0 || sub->name_.empty()) {\n        sub->_process_requirements();\n      }\n\n      if (sub->required_ && sub->count_all() == 0) {\n        throw (CLI::RequiredError(sub->get_display_name()));\n      }\n    }\n  }\n\n  /// Process callbacks and such.\n  void _process()\n  {\n    _process_ini();\n    _process_env();\n    _process_callbacks();\n    _process_help_flags();\n    _process_requirements();\n  }\n\n  /// Throw an error if anything is left over and should not be.\n  void _process_extras()\n  {\n    if (!(allow_extras_ || prefix_command_)) {\n      size_t num_left_over = remaining_size();\n\n      if (num_left_over > 0) {\n        throw ExtrasError(remaining(false));\n      }\n    }\n\n    for (App_p& sub : subcommands_) {\n      if (sub->count() > 0) {\n        sub->_process_extras();\n      }\n    }\n  }\n\n  /// Throw an error if anything is left over and should not be.\n  /// Modifies the args to fill in the missing items before throwing.\n  void _process_extras(std::vector<std::string>& args)\n  {\n    if (!(allow_extras_ || prefix_command_)) {\n      size_t num_left_over = remaining_size();\n\n      if (num_left_over > 0) {\n        args = remaining(false);\n        throw ExtrasError(args);\n      }\n    }\n\n    for (App_p& sub : subcommands_) {\n      if (sub->count() > 0) {\n        sub->_process_extras(args);\n      }\n    }\n  }\n\n  /// Internal function to recursively increment the parsed counter on the current app as well unnamed subcommands\n  void increment_parsed()\n  {\n    ++parsed_;\n\n    for (App_p& sub : subcommands_) {\n      if (sub->get_name().empty()) {\n        sub->increment_parsed();\n      }\n    }\n  }\n  /// Internal parse function\n  void _parse(std::vector<std::string>& args)\n  {\n    increment_parsed();\n    _trigger_pre_parse(args.size());\n    bool positional_only = false;\n\n    while (!args.empty()) {\n      if (!_parse_single(args, positional_only)) {\n        break;\n      }\n    }\n\n    if (parent_ == nullptr) {\n      _process();\n      // Throw error if any items are left over (depending on settings)\n      _process_extras(args);\n      // Convert missing (pairs) to extras (string only) ready for processing in another app\n      args = remaining_for_passthrough(false);\n    } else if (immediate_callback_) {\n      _process_env();\n      _process_callbacks();\n      _process_help_flags();\n      _process_requirements();\n      run_callback();\n    }\n  }\n\n  /// Internal parse function\n  void _parse(std::vector<std::string>&& args)\n  {\n    // this can only be called by the top level in which case parent == nullptr by definition\n    // operation is simplified\n    increment_parsed();\n    _trigger_pre_parse(args.size());\n    bool positional_only = false;\n\n    while (!args.empty()) {\n      _parse_single(args, positional_only);\n    }\n\n    _process();\n    // Throw error if any items are left over (depending on settings)\n    _process_extras();\n  }\n\n  /// Parse one config param, return false if not found in any subcommand, remove if it is\n  ///\n  /// If this has more than one dot.separated.name, go into the subcommand matching it\n  /// Returns true if it managed to find the option, if false you'll need to remove the arg manually.\n  void _parse_config(std::vector<ConfigItem>& args)\n  {\n    for (ConfigItem item : args) {\n      if (!_parse_single_config(item) && !allow_config_extras_) {\n        throw ConfigError::Extras(item.fullname());\n      }\n    }\n  }\n\n  /// Fill in a single config option\n  bool _parse_single_config(const ConfigItem& item, size_t level = 0)\n  {\n    if (level < item.parents.size()) {\n      try {\n        auto subcom = get_subcommand(item.parents.at(level));\n        return subcom->_parse_single_config(item, level + 1);\n      } catch (const OptionNotFound&) {\n        return false;\n      }\n    }\n\n    Option* op = get_option_no_throw(\"--\" + item.name);\n\n    if (op == nullptr) {\n      // If the option was not present\n      if (get_allow_config_extras())\n        // Should we worry about classifying the extras properly?\n      {\n        missing_.emplace_back(detail::Classifier::NONE, item.fullname());\n      }\n\n      return false;\n    }\n\n    if (!op->get_configurable()) {\n      throw ConfigError::NotConfigurable(item.fullname());\n    }\n\n    if (op->empty()) {\n      // Flag parsing\n      if (op->get_type_size() == 0) {\n        auto res = config_formatter_->to_flag(item);\n        res = op->get_flag_value(item.name, res);\n        op->add_result(res);\n      } else {\n        op->add_result(item.inputs);\n        op->run_callback();\n      }\n    }\n\n    return true;\n  }\n\n  /// Parse \"one\" argument (some may eat more than one), delegate to parent if fails, add to missing if missing\n  /// from master return false if the parse has failed and needs to return to parent\n  bool _parse_single(std::vector<std::string>& args, bool& positional_only)\n  {\n    bool retval = true;\n    detail::Classifier classifier = positional_only ? detail::Classifier::NONE :\n                                    _recognize(args.back());\n\n    switch (classifier) {\n    case detail::Classifier::POSITIONAL_MARK:\n      args.pop_back();\n      positional_only = true;\n\n      if ((!_has_remaining_positionals()) && (parent_ != nullptr)) {\n        retval = false;\n      } else {\n        _move_to_missing(classifier, \"--\");\n      }\n\n      break;\n\n    case detail::Classifier::SUBCOMMAND_TERMINATOR:\n      // treat this like a positional mark if in the parent app\n      args.pop_back();\n      retval = false;\n      break;\n\n    case detail::Classifier::SUBCOMMAND:\n      retval = _parse_subcommand(args);\n      break;\n\n    case detail::Classifier::LONG:\n    case detail::Classifier::SHORT:\n    case detail::Classifier::WINDOWS:\n      // If already parsed a subcommand, don't accept options_\n      _parse_arg(args, classifier);\n      break;\n\n    case detail::Classifier::NONE:\n      // Probably a positional or something for a parent (sub)command\n      retval = _parse_positional(args);\n\n      if (retval && positionals_at_end_) {\n        positional_only = true;\n      }\n\n      break;\n\n    // LCOV_EXCL_START\n    default:\n      HorribleError(\"unrecognized classifier (you should not see this!)\");\n      // LCOV_EXCL_END\n    }\n\n    return retval;\n  }\n\n  /// Count the required remaining positional arguments\n  size_t _count_remaining_positionals(bool required_only = false) const\n  {\n    size_t retval = 0;\n\n    for (const Option_p& opt : options_)\n      if (opt->get_positional() && (!required_only || opt->get_required()) &&\n          opt->get_items_expected() > 0 &&\n          static_cast<int>(opt->count()) < opt->get_items_expected()) {\n        retval = static_cast<size_t>(opt->get_items_expected()) - opt->count();\n      }\n\n    return retval;\n  }\n\n  /// Count the required remaining positional arguments\n  bool _has_remaining_positionals() const\n  {\n    for (const Option_p& opt : options_)\n      if (opt->get_positional() &&\n          ((opt->get_items_expected() < 0) ||\n           ((static_cast<int>(opt->count()) < opt->get_items_expected())))) {\n        return true;\n      }\n\n    return false;\n  }\n\n  /// Parse a positional, go up the tree to check\n  /// Return true if the positional was used false otherwise\n  bool _parse_positional(std::vector<std::string>& args)\n  {\n    const std::string& positional = args.back();\n\n    for (const Option_p& opt : options_) {\n      // Eat options, one by one, until done\n      if (opt->get_positional() &&\n          (static_cast<int>(opt->count()) < opt->get_items_expected() ||\n           opt->get_items_expected() < 0)) {\n        if (validate_positionals_) {\n          std::string pos = positional;\n          pos = opt->_validate(pos);\n\n          if (!pos.empty()) {\n            continue;\n          }\n        }\n\n        opt->add_result(positional);\n        parse_order_.push_back(opt.get());\n        args.pop_back();\n        return true;\n      }\n    }\n\n    for (auto& subc : subcommands_) {\n      if ((subc->name_.empty()) && (!subc->disabled_)) {\n        if (subc->_parse_positional(args)) {\n          if (!subc->pre_parse_called_) {\n            subc->_trigger_pre_parse(args.size());\n          }\n\n          return true;\n        }\n      }\n    }\n\n    // let the parent deal with it if possible\n    if (parent_ != nullptr && fallthrough_) {\n      return _get_fallthrough_parent()->_parse_positional(args);\n    }\n\n    /// Try to find a local subcommand that is repeated\n    auto com = _find_subcommand(args.back(), true, false);\n\n    if (com != nullptr && (require_subcommand_max_ == 0 ||\n                           require_subcommand_max_ > parsed_subcommands_.size())) {\n      args.pop_back();\n      com->_parse(args);\n      return true;\n    }\n\n    /// now try one last gasp at subcommands that have been executed before, go to root app and try to find a\n    /// subcommand in a broader way, if one exists let the parent deal with it\n    auto parent_app = (parent_ != nullptr) ? _get_fallthrough_parent() : this;\n    com = parent_app->_find_subcommand(args.back(), true, false);\n\n    if (com != nullptr && (com->parent_->require_subcommand_max_ == 0 ||\n                           com->parent_->require_subcommand_max_ >\n                           com->parent_->parsed_subcommands_.size())) {\n      return false;\n    }\n\n    if (positionals_at_end_) {\n      throw CLI::ExtrasError(args);\n    }\n\n    /// If this is an option group don't deal with it\n    if (parent_ != nullptr && name_.empty()) {\n      return false;\n    }\n\n    /// We are out of other options this goes to missing\n    _move_to_missing(detail::Classifier::NONE, positional);\n    args.pop_back();\n\n    if (prefix_command_) {\n      while (!args.empty()) {\n        _move_to_missing(detail::Classifier::NONE, args.back());\n        args.pop_back();\n      }\n    }\n\n    return true;\n  }\n\n  /// Locate a subcommand by name with two conditions, should disabled subcommands be ignored, and should used\n  /// subcommands be ignored\n  App* _find_subcommand(const std::string& subc_name, bool ignore_disabled,\n                        bool ignore_used) const noexcept\n  {\n    for (const App_p& com : subcommands_) {\n      if (com->disabled_ && ignore_disabled) {\n        continue;\n      }\n\n      if (com->get_name().empty()) {\n        auto subc = com->_find_subcommand(subc_name, ignore_disabled, ignore_used);\n\n        if (subc != nullptr) {\n          return subc;\n        }\n      } else if (com->check_name(subc_name)) {\n        if ((!*com) || !ignore_used) {\n          return com.get();\n        }\n      }\n    }\n\n    return nullptr;\n  }\n\n  /// Parse a subcommand, modify args and continue\n  ///\n  /// Unlike the others, this one will always allow fallthrough\n  /// return true if the subcommand was processed false otherwise\n  bool _parse_subcommand(std::vector<std::string>& args)\n  {\n    if (_count_remaining_positionals(/* required */ true) > 0) {\n      _parse_positional(args);\n      return true;\n    }\n\n    auto com = _find_subcommand(args.back(), true, true);\n\n    if (com != nullptr) {\n      args.pop_back();\n      parsed_subcommands_.push_back(com);\n      com->_parse(args);\n      auto parent_app = com->parent_;\n\n      while (parent_app != this) {\n        parent_app->_trigger_pre_parse(args.size());\n        parent_app->parsed_subcommands_.push_back(com);\n        parent_app = parent_app->parent_;\n      }\n\n      return true;\n    }\n\n    if (parent_ == nullptr) {\n      throw HorribleError(\"Subcommand \" + args.back() + \" missing\");\n    }\n\n    return false;\n  }\n\n  /// Parse a short (false) or long (true) argument, must be at the top of the list\n  /// return true if the argument was processed or false if nothing was done\n  bool _parse_arg(std::vector<std::string>& args,\n                  detail::Classifier current_type)\n  {\n    std::string current = args.back();\n    std::string arg_name;\n    std::string value;\n    std::string rest;\n\n    switch (current_type) {\n    case detail::Classifier::LONG:\n      if (!detail::split_long(current, arg_name, value)) {\n        throw HorribleError(\"Long parsed but missing (you should not see this):\" +\n                            args.back());\n      }\n\n      break;\n\n    case detail::Classifier::SHORT:\n      if (!detail::split_short(current, arg_name, rest)) {\n        throw HorribleError(\"Short parsed but missing! You should not see this\");\n      }\n\n      break;\n\n    case detail::Classifier::WINDOWS:\n      if (!detail::split_windows_style(current, arg_name, value)) {\n        throw HorribleError(\"windows option parsed but missing! You should not see this\");\n      }\n\n      break;\n\n    case detail::Classifier::SUBCOMMAND:\n    case detail::Classifier::POSITIONAL_MARK:\n    case detail::Classifier::NONE:\n    default:\n      throw HorribleError(\"parsing got called with invalid option! You should not see this\");\n    }\n\n    auto op_ptr =\n      std::find_if(std::begin(options_), std::end(options_), [arg_name,\n    current_type](const Option_p & opt) {\n      if (current_type == detail::Classifier::LONG) {\n        return opt->check_lname(arg_name);\n      }\n\n      if (current_type == detail::Classifier::SHORT) {\n        return opt->check_sname(arg_name);\n      }\n\n      // this will only get called for detail::Classifier::WINDOWS\n      return opt->check_lname(arg_name) || opt->check_sname(arg_name);\n    });\n\n    // Option not found\n    if (op_ptr == std::end(options_)) {\n      for (auto& subc : subcommands_) {\n        if (subc->name_.empty() && !subc->disabled_) {\n          if (subc->_parse_arg(args, current_type)) {\n            if (!subc->pre_parse_called_) {\n              subc->_trigger_pre_parse(args.size());\n            }\n\n            return true;\n          }\n        }\n      }\n\n      // If a subcommand, try the master command\n      if (parent_ != nullptr && fallthrough_) {\n        return _get_fallthrough_parent()->_parse_arg(args, current_type);\n      }\n\n      // don't capture missing if this is a nameless subcommand\n      if (parent_ != nullptr && name_.empty()) {\n        return false;\n      }\n\n      // Otherwise, add to missing\n      args.pop_back();\n      _move_to_missing(current_type, current);\n      return true;\n    }\n\n    args.pop_back();\n    // Get a reference to the pointer to make syntax bearable\n    Option_p& op = *op_ptr;\n    int num = op->get_items_expected();\n    // Make sure we always eat the minimum for unlimited vectors\n    int collected = 0;\n    int result_count = 0;\n\n    // deal with flag like things\n    if (num == 0) {\n      auto res = op->get_flag_value(arg_name, value);\n      op->add_result(res);\n      parse_order_.push_back(op.get());\n    }\n    // --this=value\n    else if (!value.empty()) {\n      op->add_result(value, result_count);\n      parse_order_.push_back(op.get());\n      collected += result_count;\n\n      // If exact number expected\n      if (num > 0) {\n        num = (num >= result_count) ? num - result_count : 0;\n      }\n\n      // -Trest\n    } else if (!rest.empty()) {\n      op->add_result(rest, result_count);\n      parse_order_.push_back(op.get());\n      rest = \"\";\n      collected += result_count;\n\n      // If exact number expected\n      if (num > 0) {\n        num = (num >= result_count) ? num - result_count : 0;\n      }\n    }\n\n    // Unlimited vector parser\n    if (num < 0) {\n      while (!args.empty() &&\n             _recognize(args.back(), false) == detail::Classifier::NONE) {\n        if (collected >= -num) {\n          // We could break here for allow extras, but we don't\n\n          // If any positionals remain, don't keep eating\n          if (_count_remaining_positionals() > 0) {\n            break;\n          }\n        }\n\n        op->add_result(args.back(), result_count);\n        parse_order_.push_back(op.get());\n        args.pop_back();\n        collected += result_count;\n      }\n\n      // Allow -- to end an unlimited list and \"eat\" it\n      if (!args.empty() &&\n          _recognize(args.back()) == detail::Classifier::POSITIONAL_MARK) {\n        args.pop_back();\n      }\n    } else {\n      while (num > 0 && !args.empty()) {\n        std::string current_ = args.back();\n        args.pop_back();\n        op->add_result(current_, result_count);\n        parse_order_.push_back(op.get());\n        num -= result_count;\n      }\n\n      if (num > 0) {\n        throw ArgumentMismatch::TypedAtLeast(op->get_name(), num, op->get_type_name());\n      }\n    }\n\n    if (!rest.empty()) {\n      rest = \"-\" + rest;\n      args.push_back(rest);\n    }\n\n    return true;\n  }\n\n  /// Trigger the pre_parse callback if needed\n  void _trigger_pre_parse(size_t remaining_args)\n  {\n    if (!pre_parse_called_) {\n      pre_parse_called_ = true;\n\n      if (pre_parse_callback_) {\n        pre_parse_callback_(remaining_args);\n      }\n    } else if (immediate_callback_) {\n      if (!name_.empty()) {\n        auto pcnt = parsed_;\n        auto extras = std::move(missing_);\n        clear();\n        parsed_ = pcnt;\n        pre_parse_called_ = true;\n        missing_ = std::move(extras);\n      }\n    }\n  }\n\n  /// Get the appropriate parent to fallthrough to which is the first one that has a name or the main app\n  App* _get_fallthrough_parent()\n  {\n    if (parent_ == nullptr) {\n      throw (HorribleError(\"No Valid parent\"));\n    }\n\n    auto fallthrough_parent = parent_;\n\n    while ((fallthrough_parent->parent_ != nullptr) &&\n           (fallthrough_parent->get_name().empty())) {\n      fallthrough_parent = fallthrough_parent->parent_;\n    }\n\n    return fallthrough_parent;\n  }\n\n  /// Helper function to place extra values in the most appropriate position\n  void _move_to_missing(detail::Classifier val_type, const std::string& val)\n  {\n    if (allow_extras_ || subcommands_.empty()) {\n      missing_.emplace_back(val_type, val);\n      return;\n    }\n\n    // allow extra arguments to be places in an option group if it is allowed there\n    for (auto& subc : subcommands_) {\n      if (subc->name_.empty() && subc->allow_extras_) {\n        subc->missing_.emplace_back(val_type, val);\n        return;\n      }\n    }\n\n    // if we haven't found any place to put them yet put them in missing\n    missing_.emplace_back(val_type, val);\n  }\n\npublic:\n  /// function that could be used by subclasses of App to shift options around into subcommands\n  void _move_option(Option* opt, App* app)\n  {\n    if (opt == nullptr) {\n      throw OptionNotFound(\"the option is NULL\");\n    }\n\n    // verify that the give app is actually a subcommand\n    bool found = false;\n\n    for (auto& subc : subcommands_) {\n      if (app == subc.get()) {\n        found = true;\n      }\n    }\n\n    if (!found) {\n      throw OptionNotFound(\"The Given app is not a subcommand\");\n    }\n\n    if ((help_ptr_ == opt) || (help_all_ptr_ == opt)) {\n      throw OptionAlreadyAdded(\"cannot move help options\");\n    }\n\n    if (config_ptr_ == opt) {\n      throw OptionAlreadyAdded(\"cannot move config file options\");\n    }\n\n    auto iterator =\n      std::find_if(std::begin(options_),\n    std::end(options_), [opt](const Option_p & v) {\n      return v.get() == opt;\n    });\n\n    if (iterator != std::end(options_)) {\n      const auto& opt_p = *iterator;\n\n      if (std::find_if(std::begin(app->options_),\n      std::end(app->options_), [&opt_p](const Option_p & v) {\n      return (*v == *opt_p);\n      }) == std::end(app->options_)) {\n        // only erase after the insertion was successful\n        app->options_.push_back(std::move(*iterator));\n        options_.erase(iterator);\n      }\n      else {\n        throw OptionAlreadyAdded(opt->get_name());\n      }\n    } else {\n      throw OptionNotFound(\"could not locate the given App\");\n    }\n  }\n};\n\n/// Extension of App to better manage groups of options\nclass Option_group : public App\n{\npublic:\n  Option_group(std::string group_description, std::string group_name, App* parent)\n    : App(std::move(group_description), \"\", parent)\n  {\n    group(group_name);\n    // option groups should have automatic fallthrough\n  }\n  using App::add_option;\n  /// Add an existing option to the Option_group\n  Option* add_option(Option* opt)\n  {\n    if (get_parent() == nullptr) {\n      throw OptionNotFound(\"Unable to locate the specified option\");\n    }\n\n    get_parent()->_move_option(opt, this);\n    return opt;\n  }\n  /// Add an existing option to the Option_group\n  void add_options(Option* opt)\n  {\n    add_option(opt);\n  }\n  /// Add a bunch of options to the group\n  template <typename... Args> void add_options(Option* opt, Args... args)\n  {\n    add_option(opt);\n    add_options(args...);\n  }\n  using App::add_subcommand;\n  /// Add an existing subcommand to be a member of an option_group\n  App* add_subcommand(App* subcom)\n  {\n    App_p subc = subcom->get_parent()->get_subcommand_ptr(subcom);\n    subc->get_parent()->remove_subcommand(subcom);\n    add_subcommand(std::move(subc));\n    return subcom;\n  }\n};\n/// Helper function to enable one option group/subcommand when another is used\ninline void TriggerOn(App* trigger_app, App* app_to_enable)\n{\n  app_to_enable->enabled_by_default(false);\n  app_to_enable->disabled_by_default();\n  trigger_app->preparse_callback([app_to_enable](size_t) {\n    app_to_enable->disabled(false);\n  });\n}\n\n/// Helper function to enable one option group/subcommand when another is used\ninline void TriggerOn(App* trigger_app, std::vector<App*> apps_to_enable)\n{\n  for (auto& app : apps_to_enable) {\n    app->enabled_by_default(false);\n    app->disabled_by_default();\n  }\n\n  trigger_app->preparse_callback([apps_to_enable](size_t) {\n    for (auto& app : apps_to_enable) {\n      app->disabled(false);\n    }\n  });\n}\n\n/// Helper function to disable one option group/subcommand when another is used\ninline void TriggerOff(App* trigger_app, App* app_to_enable)\n{\n  app_to_enable->disabled_by_default(false);\n  app_to_enable->enabled_by_default();\n  trigger_app->preparse_callback([app_to_enable](size_t) {\n    app_to_enable->disabled();\n  });\n}\n\n/// Helper function to disable one option group/subcommand when another is used\ninline void TriggerOff(App* trigger_app, std::vector<App*> apps_to_enable)\n{\n  for (auto& app : apps_to_enable) {\n    app->disabled_by_default(false);\n    app->enabled_by_default();\n  }\n\n  trigger_app->preparse_callback([apps_to_enable](size_t) {\n    for (auto& app : apps_to_enable) {\n      app->disabled();\n    }\n  });\n}\n\nnamespace FailureMessage\n{\n\n/// Printout a clean, simple message on error (the default in CLI11 1.5+)\ninline std::string simple(const App* app, const Error& e)\n{\n  std::string header = std::string(e.what()) + \"\\n\";\n  std::vector<std::string> names;\n\n  // Collect names\n  if (app->get_help_ptr() != nullptr) {\n    names.push_back(app->get_help_ptr()->get_name());\n  }\n\n  if (app->get_help_all_ptr() != nullptr) {\n    names.push_back(app->get_help_all_ptr()->get_name());\n  }\n\n  // If any names found, suggest those\n  if (!names.empty()) {\n    header += \"Run with \" + detail::join(names,\n                                         \" or \") + \" for more information.\\n\";\n  }\n\n  return header;\n}\n\n/// Printout the full help string on error (if this fn is set, the old default for CLI11)\ninline std::string help(const App* app, const Error& e)\n{\n  std::string header = std::string(\"ERROR: \") + e.get_name() + \": \" + e.what() +\n                       \"\\n\";\n  header += app->help();\n  return header;\n}\n\n} // namespace FailureMessage\n\nnamespace detail\n{\n/// This class is simply to allow tests access to App's protected functions\nstruct AppFriend {\n\n  /// Wrap _parse_short, perfectly forward arguments and return\n  template <typename... Args>\n  static auto parse_arg(App* app, Args&& ... args) ->\n  typename std::result_of<decltype(&App::_parse_arg)(App, Args...)>::type {\n    return app->_parse_arg(std::forward<Args>(args)...);\n  }\n\n  /// Wrap _parse_subcommand, perfectly forward arguments and return\n  template <typename... Args>\n  static auto parse_subcommand(App* app, Args&& ... args) ->\n  typename std::result_of<decltype(&App::_parse_subcommand)(App, Args...)>::type {\n    return app->_parse_subcommand(std::forward<Args>(args)...);\n  }\n  /// Wrap the fallthrough parent function to make sure that is working correctly\n  static App* get_fallthrough_parent(App* app)\n  {\n    return app->_get_fallthrough_parent();\n  }\n};\n} // namespace detail\n\n} // namespace CLI\n\n// From CLI/Config.hpp:\n\nnamespace CLI\n{\n\ninline std::string\nConfigINI::to_config(const App* app, bool default_also, bool write_description,\n                     std::string prefix) const\n{\n  std::stringstream out;\n\n  for (const Option* opt : app->get_options( {})) {\n    // Only process option with a long-name and configurable\n    if (!opt->get_lnames().empty() && opt->get_configurable()) {\n      std::string name = prefix + opt->get_lnames()[0];\n      std::string value;\n\n      // Non-flags\n      if (opt->get_type_size() != 0) {\n        // If the option was found on command line\n        if (opt->count() > 0) {\n          value = detail::ini_join(opt->results());\n        }\n        // If the option has a default and is requested by optional argument\n        else if (default_also && !opt->get_default_str().empty()) {\n          value = opt->get_default_str();\n        }\n\n        // Flag, one passed\n      } else if (opt->count() == 1) {\n        value = \"true\";\n        // Flag, multiple passed\n      } else if (opt->count() > 1) {\n        value = std::to_string(opt->count());\n        // Flag, not present\n      } else if (opt->count() == 0 && default_also) {\n        value = \"false\";\n      }\n\n      if (!value.empty()) {\n        if (write_description && opt->has_description()) {\n          if (static_cast<int>(out.tellp()) != 0) {\n            out << std::endl;\n          }\n\n          out << \"; \" << detail::fix_newlines(\"; \", opt->get_description()) << std::endl;\n        }\n\n        // Don't try to quote anything that is not size 1\n        if (opt->get_items_expected() != 1) {\n          out << name << \"=\" << value << std::endl;\n        } else {\n          out << name << \"=\" << detail::add_quotes_if_needed(value) << std::endl;\n        }\n      }\n    }\n  }\n\n  for (const App* subcom : app->get_subcommands( {}))\n  out << to_config(subcom, default_also, write_description,\n                   prefix + subcom->get_name() + \".\");\n  return out.str();\n}\n\n} // namespace CLI\n\n// From CLI/Formatter.hpp:\n\nnamespace CLI\n{\n\ninline std::string\nFormatter::make_group(std::string group, bool is_positional,\n                      std::vector<const Option*> opts) const\n{\n  std::stringstream out;\n  out << \"\\n\" << group << \":\\n\";\n\n  for (const Option* opt : opts) {\n    out << make_option(opt, is_positional);\n  }\n\n  return out.str();\n}\n\ninline std::string Formatter::make_positionals(const App* app) const\n{\n  std::vector<const Option*> opts =\n  app->get_options([](const Option * opt) {\n    return !opt->get_group().empty() && opt->get_positional();\n  });\n\n  if (opts.empty()) {\n    return std::string();\n  } else {\n    return make_group(get_label(\"Positionals\"), true, opts);\n  }\n}\n\ninline std::string Formatter::make_groups(const App* app,\n    AppFormatMode mode) const\n{\n  std::stringstream out;\n  std::vector<std::string> groups = app->get_groups();\n\n  // Options\n  for (const std::string& group : groups) {\n    std::vector<const Option*> opts = app->get_options([app, mode,\n    &group](const Option * opt) {\n      return opt->get_group() ==\n             group                    // Must be in the right group\n             && opt->nonpositional()                      // Must not be a positional\n             && (mode != AppFormatMode::Sub               // If mode is Sub, then\n                 || (app->get_help_ptr() != opt           // Ignore help pointer\n                     && app->get_help_all_ptr() != opt)); // Ignore help all pointer\n    });\n\n    if (!group.empty() && !opts.empty()) {\n      out << make_group(group, false, opts);\n\n      if (group != groups.back()) {\n        out << \"\\n\";\n      }\n    }\n  }\n\n  return out.str();\n}\n\ninline std::string Formatter::make_description(const App* app) const\n{\n  std::string desc = app->get_description();\n  auto min_options = app->get_require_option_min();\n  auto max_options = app->get_require_option_max();\n\n  if (app->get_required()) {\n    desc += \" REQUIRED \";\n  }\n\n  if ((max_options == min_options) && (min_options > 0)) {\n    if (min_options == 1) {\n      desc += \" \\n[Exactly 1 of the following options is required]\";\n    } else {\n      desc += \" \\n[Exactly \" + std::to_string(min_options) +\n              \"options from the following list are required]\";\n    }\n  } else if (max_options > 0) {\n    if (min_options > 0) {\n      desc += \" \\n[Between \" + std::to_string(min_options) + \" and \" + std::to_string(\n                max_options) +\n              \" of the follow options are required]\";\n    } else {\n      desc += \" \\n[At most \" + std::to_string(max_options) +\n              \" of the following options are allowed]\";\n    }\n  } else if (min_options > 0) {\n    desc += \" \\n[At least \" + std::to_string(min_options) +\n            \" of the following options are required]\";\n  }\n\n  return (!desc.empty()) ? desc + \"\\n\" : std::string{};\n}\n\ninline std::string Formatter::make_usage(const App* app,\n    std::string name) const\n{\n  std::stringstream out;\n  out << get_label(\"Usage\") << \":\" << (name.empty() ? \"\" : \" \") << name;\n  std::vector<std::string> groups = app->get_groups();\n  // Print an Options badge if any options exist\n  std::vector<const Option*> non_pos_options =\n  app->get_options([](const Option * opt) {\n    return opt->nonpositional();\n  });\n\n  if (!non_pos_options.empty()) {\n    out << \" [\" << get_label(\"OPTIONS\") << \"]\";\n  }\n\n  // Positionals need to be listed here\n  std::vector<const Option*> positionals = app->get_options([](\n  const Option * opt) {\n    return opt->get_positional();\n  });\n\n  // Print out positionals if any are left\n  if (!positionals.empty()) {\n    // Convert to help names\n    std::vector<std::string> positional_names(positionals.size());\n    std::transform(positionals.begin(), positionals.end(),\n    positional_names.begin(), [this](const Option * opt) {\n      return make_option_usage(opt);\n    });\n    out << \" \" << detail::join(positional_names, \" \");\n  }\n\n  // Add a marker if subcommands are expected or optional\n  if (!app->get_subcommands(\n  [](const CLI::App * subc) {\n  return ((!subc->get_disabled()) && (!subc->get_name().empty()));\n  })\n  .empty()) {\n    out << \" \" << (app->get_require_subcommand_min() == 0 ? \"[\" : \"\")\n        << get_label(app->get_require_subcommand_max() < 2 ||\n                     app->get_require_subcommand_min() > 1 ? \"SUBCOMMAND\"\n                     : \"SUBCOMMANDS\")\n        << (app->get_require_subcommand_min() == 0 ? \"]\" : \"\");\n  }\n  out << std::endl;\n  return out.str();\n}\n\ninline std::string Formatter::make_footer(const App* app) const\n{\n  std::string footer = app->get_footer();\n\n  if (!footer.empty()) {\n    return footer + \"\\n\";\n  } else {\n    return \"\";\n  }\n}\n\ninline std::string Formatter::make_help(const App* app, std::string name,\n                                        AppFormatMode mode) const\n{\n  // This immediately forwards to the make_expanded method. This is done this way so that subcommands can\n  // have overridden formatters\n  if (mode == AppFormatMode::Sub) {\n    return make_expanded(app);\n  }\n\n  std::stringstream out;\n\n  if ((app->get_name().empty()) && (app->get_parent() != nullptr)) {\n    if (app->get_group() != \"Subcommands\") {\n      out << app->get_group() << ':';\n    }\n  }\n\n  out << make_description(app);\n  out << make_usage(app, name);\n  out << make_positionals(app);\n  out << make_groups(app, mode);\n  out << make_subcommands(app, mode);\n  out << make_footer(app);\n  return out.str();\n}\n\ninline std::string Formatter::make_subcommands(const App* app,\n    AppFormatMode mode) const\n{\n  std::stringstream out;\n  std::vector<const App*> subcommands = app->get_subcommands({});\n  // Make a list in definition order of the groups seen\n  std::vector<std::string> subcmd_groups_seen;\n\n  for (const App* com : subcommands) {\n    if (com->get_name().empty()) {\n      out << make_expanded(com);\n      continue;\n    }\n\n    std::string group_key = com->get_group();\n\n    if (!group_key.empty() &&\n        std::find_if(subcmd_groups_seen.begin(),\n    subcmd_groups_seen.end(), [&group_key](std::string a) {\n    return detail::to_lower(a) == detail::to_lower(group_key);\n    }) == subcmd_groups_seen.end())\n    subcmd_groups_seen.push_back(group_key);\n  }\n\n  // For each group, filter out and print subcommands\n  for (const std::string& group : subcmd_groups_seen) {\n    out << \"\\n\" << group << \":\\n\";\n    std::vector<const App*> subcommands_group = app->get_subcommands(\n    [&group](const App * sub_app) {\n      return detail::to_lower(sub_app->get_group()) == detail::to_lower(group);\n    });\n\n    for (const App* new_com : subcommands_group) {\n      if (new_com->get_name().empty()) {\n        continue;\n      }\n\n      if (mode != AppFormatMode::All) {\n        out << make_subcommand(new_com);\n      } else {\n        out << new_com->help(new_com->get_name(), AppFormatMode::Sub);\n        out << \"\\n\";\n      }\n    }\n  }\n\n  return out.str();\n}\n\ninline std::string Formatter::make_subcommand(const App* sub) const\n{\n  std::stringstream out;\n  detail::format_help(out, sub->get_name(), sub->get_description(),\n                      column_width_);\n  return out.str();\n}\n\ninline std::string Formatter::make_expanded(const App* sub) const\n{\n  std::stringstream out;\n  out << sub->get_display_name() << \"\\n\";\n  out << make_description(sub);\n  out << make_positionals(sub);\n  out << make_groups(sub, AppFormatMode::Sub);\n  out << make_subcommands(sub, AppFormatMode::Sub);\n  // Drop blank spaces\n  std::string tmp = detail::find_and_replace(out.str(), \"\\n\\n\", \"\\n\");\n  tmp = tmp.substr(0, tmp.size() - 1); // Remove the final '\\n'\n  // Indent all but the first line (the name)\n  return detail::find_and_replace(tmp, \"\\n\", \"\\n  \") + \"\\n\";\n}\n\ninline std::string Formatter::make_option_name(const Option* opt,\n    bool is_positional) const\n{\n  if (is_positional) {\n    return opt->get_name(true, false);\n  } else {\n    return opt->get_name(false, true);\n  }\n}\n\ninline std::string Formatter::make_option_opts(const Option* opt) const\n{\n  std::stringstream out;\n\n  if (opt->get_type_size() != 0) {\n    if (!opt->get_type_name().empty()) {\n      out << \" \" << get_label(opt->get_type_name());\n    }\n\n    if (!opt->get_default_str().empty()) {\n      out << \"=\" << opt->get_default_str();\n    }\n\n    if (opt->get_expected() > 1) {\n      out << \" x \" << opt->get_expected();\n    }\n\n    if (opt->get_expected() == -1) {\n      out << \" ...\";\n    }\n\n    if (opt->get_required()) {\n      out << \" \" << get_label(\"REQUIRED\");\n    }\n  }\n\n  if (!opt->get_envname().empty()) {\n    out << \" (\" << get_label(\"Env\") << \":\" << opt->get_envname() << \")\";\n  }\n\n  if (!opt->get_needs().empty()) {\n    out << \" \" << get_label(\"Needs\") << \":\";\n\n    for (const Option* op : opt->get_needs()) {\n      out << \" \" << op->get_name();\n    }\n  }\n\n  if (!opt->get_excludes().empty()) {\n    out << \" \" << get_label(\"Excludes\") << \":\";\n\n    for (const Option* op : opt->get_excludes()) {\n      out << \" \" << op->get_name();\n    }\n  }\n\n  return out.str();\n}\n\ninline std::string Formatter::make_option_desc(const Option* opt) const\n{\n  return opt->get_description();\n}\n\ninline std::string Formatter::make_option_usage(const Option* opt) const\n{\n  // Note that these are positionals usages\n  std::stringstream out;\n  out << make_option_name(opt, true);\n\n  if (opt->get_expected() > 1) {\n    out << \"(\" << std::to_string(opt->get_expected()) << \"x)\";\n  } else if (opt->get_expected() < 0) {\n    out << \"...\";\n  }\n\n  return opt->get_required() ? out.str() : \"[\" + out.str() + \"]\";\n}\n\n} // namespace CLI\n\n"
  },
  {
    "path": "common/CMakeLists.txt",
    "content": "#-------------------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Andreas-Joachim Peters - CERN\n#-------------------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ninclude_directories(\n  ${CMAKE_BINARY_DIR}\n  ${CMAKE_SOURCE_DIR}/common/jwt-cpp/include/)\n\n#-------------------------------------------------------------------------------\n# CTA integration related operations\n#-------------------------------------------------------------------------------\nif (NOT CLIENT AND Linux)\n  add_subdirectory(eos_cta_pb)\nendif()\n\n#-------------------------------------------------------------------------------\n# EosCrc32c-Objects library\n#-------------------------------------------------------------------------------\nadd_library(EosCrc32c-Objects OBJECT\n  crc32c/crc32c.cc\n  crc32c/crc32ctables.cc)\n\nset_target_properties(EosCrc32c-Objects PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\n#-------------------------------------------------------------------------------\n# EosSciToken-Objects library\n#-------------------------------------------------------------------------------\nadd_library(EosSciToken-Objects OBJECT\n  token/SciToken.cc)\n\nset_target_properties(EosSciToken-Objects PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\nadd_library(EosSciToken-Static STATIC $<TARGET_OBJECTS:EosSciToken-Objects>)\n\ntarget_link_libraries(EosSciToken-Objects PUBLIC\n  XROOTD::UTILS\n  SCITOKENS::SCITOKENS)\n\n#-------------------------------------------------------------------------------\n# EosBlake3-Objects library\n#-------------------------------------------------------------------------------\nset(BLAKE3_PORTABLE_SRCS\n  blake3/blake3.c\n  blake3/blake3_dispatch.c\n  blake3/blake3_portable.c)\n\n\nif (ARM64_BUILD)\n# For 64 bit arm neon is mandatory, check HAVE_NEON only for arm32\n    set(BLAKE3_NATIVE_SRCS\n      blake3/blake3_neon.c)\n    message(STATUS \"Building Blake3 with ARM NEON intrinsics\")\nelse()\n  if (HAVE_AVX2)\n    set(BLAKE3_AVX_SRCS blake3/blake3_avx2_x86-64_unix.S)\n    if (HAVE_AVX512)\n      set(BLAKE3_AVX_SRCS\n\t\"${BLAKE3_AVX_SRCS}\"\n\tblake3/blake3_avx512_x86-64_unix.S)\n    endif() # AVX_512\n  endif() #AVX2\n\n  if (HAVE_SSE42)\n    set(BLAKE3_SSE_SRCS\n      blake3/blake3_sse2_x86-64_unix.S\n      blake3/blake3_sse41_x86-64_unix.S)\n  endif() # SSE\n\n  message(STATUS \"Building Blake3 on x86_64 with intrinsics AVX2=${HAVE_AVX2} \"\n    \"AVX512=${HAVE_AVX512} SSE42=${HAVE_SSE42}\")\n  set(BLAKE3_NATIVE_SRCS\n    \"${BLAKE3_SSE_SRCS}\"\n    \"${BLAKE3_AVX_SRCS}\")\n  set_property(SOURCE ${BLAKE3_NATIVE_SRCS}\n    PROPERTY LANGUAGE ASM)\nendif()\n\nadd_library(EosBlake3-Objects OBJECT\n  ${BLAKE3_PORTABLE_SRCS}\n  ${BLAKE3_NATIVE_SRCS})\n\nset_target_properties(EosBlake3-Objects PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\nif (NO_SSE)\n  target_compile_definitions(EosBlake3-Objects PUBLIC\n    BLAKE3_NO_SSE2 BLAKE3_NO_SSE41 BLAKE3_NO_AVX2 BLAKE3_NO_AVX512)\nendif()\n\nif (ARM64_BUILD)\n  target_compile_definitions(EosBlake3-Objects PUBLIC BLAKE3_USE_NEON)\nendif()\n\n# FIXME: This is a workaround for clang < 7 see\n# https://bugs.llvm.org/show_bug.cgi?id=39875 &&\n# https://bugs.llvm.org/show_bug.cgi?id=36202 and remove this when we drop\n# clang5 support or we enable this globally for all options\nif (HAVE_AVX512 AND\n    (\"${CMAKE_CXX_COMPILER_ID}\" MATCHES Clang) AND\n    (\"${CMAKE_CXX_COMPILER_VERSION}\" VERSION_LESS \"7.0\"))\n  target_compile_options(EosBlake3-Objects PRIVATE -mavx512f -mavx512vl)\nendif() # CLANG5 hack\n#-------------------------------------------------------------------------------\n# EosCommon-Objects\n#-------------------------------------------------------------------------------\nadd_library(EosCommon-Objects OBJECT\n  Utils.cc\n  BufferManager.cc\n  Fmd.cc\n  SymKeys.cc\n  InstanceName.cc\n  Mapping.cc\n  MutexLatencyWatcher.cc\n  RWMutex.cc\n  RegexWrapper.cc\n  SharedMutex.cc\n  PthreadRWMutex.cc\n  ClockGetTime.cc\n  StacktraceHere.cc\n  Locators.cc\n  Logging.cc\n  StringConversion.cc\n  Statfs.cc\n  Report.cc\n  StringTokenizer.cc\n  CommentLog.cc\n  RateLimit.cc\n  IntervalStopwatch.cc\n  UriCapCipher.cc\n  VirtualIdentity.cc\n  XrdConnPool.cc\n  XrdErrorMap.cc\n  OAuth.cc\n  Glob.cc\n  Strerror_r_wrapper.cc\n  ErrnoToString.cc\n  JeMallocHandler.cc\n  plugin_manager/Plugin.hh\n  plugin_manager/PluginManager.cc\n  plugin_manager/DynamicLibrary.cc\n  table_formatter/TableCell.cc\n  table_formatter/TableFormatterBase.cc\n  config/ConfigParsing.cc\n  token/EosTok.cc\n  Config.cc\n  utils/XrdUtils.cc\n  exception/Exception.cc\n  concurrency/ThreadEpochCounter.cc\n  BehaviourConfig.cc\n  UnixGroupsFetcher.cc\n  Audit.cc\n)\n\n# Avoid warnings related to bfd_get_section* macros being redefined\ntarget_compile_options(EosCommon-Objects PRIVATE -Wno-macro-redefined)\nset_target_properties(EosCommon-Objects PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\ntarget_link_libraries(EosCommon-Objects PUBLIC\n  EosFstProto-Objects\n  EosCliProto-Objects\n  EosAuditProto-Objects\n  CURL::libcurl\n  ZLIB::ZLIB\n  ZSTD::ZSTD\n  JSONCPP::JSONCPP\n  OpenSSL::SSL\n  XROOTD::UTILS\n  GLIBC::DL\n  GLIBC::RT\n  LIBBFD::LIBBFD\n  LIBBFD::IBERTY\n  GOOGLE::SPARSEHASH\n  ABSL::ABSL\n  fmt::fmt-header-only\n  GRPC::grpc++\n  ${CMAKE_THREAD_LIBS_INIT})\n\nset_target_properties(EosCommon-Objects PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\nif (TARGET ZSTD::ZSTD)\n  target_compile_definitions(EosCommon-Objects PUBLIC EOS_HAVE_ZSTD=1)\nendif()\n\ntarget_compile_definitions(EosCommon-Objects PUBLIC\n  DAEMONUID=${DAEMONUID} DAEMONGID=${DAEMONGID})\n\n#-------------------------------------------------------------------------------\n# EosCommon and EosCommon-Static library\n#-------------------------------------------------------------------------------\nadd_library(EosCommon SHARED $<TARGET_OBJECTS:EosCommon-Objects>)\n\ntarget_link_libraries(EosCommon PUBLIC\n  EosCommon-Objects\n  EosFstProto-Objects\n  EosCliProto-Objects\n  EosAuditProto-Objects\n  EosSciToken-Objects\n  EosNsQuarkdbProto-Objects\n  NCURSES::NCURSES\n  UUID::UUID\n  XROOTD::CL)\n\nset_target_properties(EosCommon PROPERTIES\n  VERSION ${VERSION}\n  SOVERSION ${VERSION_MAJOR}\n  MACOSX_RPATH TRUE)\n\nadd_library(EosCommon-Static STATIC $<TARGET_OBJECTS:EosCommon-Objects>)\n\nset(EosCommonLinkLibs\n  EosCommon-Objects\n  EosFstProto-Objects\n  EosCliProto-Objects\n  EosAuditProto-Objects\n  EosSciToken-Objects\n  EosNsQuarkdbProto-Objects\n  XROOTD::CL\n  UUID::UUID\n  ZLIB::ZLIB\n  ABSL::ABSL\n  NCURSES::NCURSES)\n\ntarget_link_libraries(EosCommon-Static PUBLIC\n  ${EosCommonLinkLibs})\n\nset_target_properties(EosCommon-Static PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\ninstall(TARGETS EosCommon\n  LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n  ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\n\n#-------------------------------------------------------------------------------\n# EosCommonServer and EosCommonServer-Static libraries\n#-------------------------------------------------------------------------------\nif (NOT CLIENT AND Linux)\n  add_library(EosCommonServer-Objects OBJECT\n    ShellExecutor.cc\n    ShellCmd.cc\n    FileSystem.cc\n    WebNotify.cc\n    http/HttpServer.cc\n    http/HttpRequest.cc\n    http/HttpResponse.cc\n    http/s3/S3Handler.cc\n    stringencoders/modp_numtoa.c\n    mq/QdbListener.cc                 mq/QdbListener.hh\n    mq/FsChangeListener.cc            mq/FsChangeListener.hh\n    mq/GlobalConfigChangeListener.cc  mq/GlobalConfigChangeListener.hh\n    mq/MessagingRealm.cc              mq/MessagingRealm.hh\n    mq/SharedDequeProvider.cc         mq/SharedDequeProvider.hh\n    mq/SharedHashProvider.cc          mq/SharedHashProvider.hh\n    mq/SharedHashWrapper.cc           mq/SharedHashWrapper.hh\n    mq/LocalHash.cc                   mq/LocalHash.hh\n  )\n\n  add_dependencies(EosCommonServer-Objects EosGrpcProto-Objects)\n\n  target_link_libraries(EosCommonServer-Objects PUBLIC\n    qclient\n    JSONCPP::JSONCPP\n    XROOTD::UTILS\n    EosCliProto-Objects\n    ActiveMQCPP::ActiveMQCPP\n    GRPC::grpc++)\n\n  target_compile_definitions(EosCommonServer-Objects PUBLIC\n    -DSQLITE_NO_SYNC=1)\n\n  set_target_properties(EosCommonServer-Objects PROPERTIES\n    POSITION_INDEPENDENT_CODE TRUE)\n\n  add_library(EosCommonServer SHARED\n    $<TARGET_OBJECTS:EosCommonServer-Objects>)\n\n  target_link_libraries(EosCommonServer PUBLIC\n    EosCommonServer-Objects\n    XrdSsiPbEosCta-Objects\n    EosCliProto-Objects\n    EosGrpcProto-Objects\n    EosCommon)\n\n  target_compile_definitions(EosCommonServer PUBLIC\n    -DSQLITE_NO_SYNC=1)\n\n  set_target_properties(EosCommonServer PROPERTIES\n    VERSION ${VERSION}\n    SOVERSION ${VERSION_MAJOR}\n    MACOSX_RPATH TRUE)\n\n  add_library(EosCommonServer-Static STATIC\n    $<TARGET_OBJECTS:EosCommonServer-Objects>)\n\n  target_link_libraries(EosCommonServer-Static PUBLIC\n    EosCommonServer-Objects\n    XrdSsiPbEosCta-Objects\n    qclient\n    EosCommon-Static)\n\n  target_compile_definitions(EosCommonServer-Static PUBLIC\n    -DSQLITE_NO_SYNC=1)\n\n  install(TARGETS EosCommonServer\n    LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n    RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n    ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\nendif()\n\n#-------------------------------------------------------------------------------\n# Create helper executables\n#-------------------------------------------------------------------------------\nif(NOT CLIENT AND Linux)\n  add_executable(eos-layout-print EosLayoutPrint.cc)\n  target_link_libraries(eos-layout-print PUBLIC EosCommon)\n  add_executable(mutextest\n  mutextest/RWMutexTest.cc RWMutex.cc PthreadRWMutex.cc StacktraceHere.cc)\n  # Avoid warnings related to bfd_get_section* macros being redefined\n  target_compile_options(mutextest PRIVATE -Wno-macro-redefined)\n  target_link_libraries(mutextest PRIVATE\n  EosCommon-Static)\n\n  #-------------------------------------------------------------------------------\n  # Create python module eosscitokenmodule\n  #-------------------------------------------------------------------------------\n  # Define a custom target to build the Python module\n  if(PYTHONSITEPKG_FOUND)\n    add_custom_target(build_python_module\n      COMMAND ${CMAKE_COMMAND} -E echo \"Building Python module...\"\n      COMMAND ${CMAKE_COMMAND} -E env BUILD_BASE=${CMAKE_CURRENT_BINARY_DIR} ${Python3_EXECUTABLE} setup.py build\n\tWORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/token/)\n\n    # Define a custom target to install the Python module\n    add_custom_target(install_python_module\n      COMMAND ${CMAKE_COMMAND} -E echo \"Installing Python module...in \\${DESTDIR}\"\n      COMMAND ${CMAKE_COMMAND} -E env SOURCE_BASE=${CMAKE_CURRENT_SOURCE_DIR}/token/ ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/token/setup.py install --root=/ --single-version-externally-managed --prefix=\\${DESTDIR}/usr/\n      WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})\n\n    # Ensure build_python_module is built after EosCommon\n    add_dependencies(build_python_module EosCommon)\n    # Make install depend on build, so it builds before installing\n    add_dependencies(install_python_module build_python_module)\n\n    # Hook the Python module build into the 'install' target\n    add_custom_command(\n      TARGET build_python_module POST_BUILD\n      COMMAND ${CMAKE_COMMAND} -E echo \"Building Python module after install...\")\n\n    add_custom_command(\n      TARGET install_python_module POST_BUILD\n      COMMAND ${CMAKE_COMMAND} -E echo \"Installing Python module after install...\")\n\n    install(CODE \"execute_process(COMMAND ${CMAKE_COMMAND} --build . --target build_python_module)\")\n    install(CODE \"execute_process(COMMAND ${CMAKE_COMMAND} --build . --target install_python_module)\")\n  endif()\nendif()\n"
  },
  {
    "path": "common/CloExec.hh",
    "content": "// ----------------------------------------------------------------------\n// File: CloExec.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   CloExec.hh\n *\n * @brief  Class setting filedescriptor flags to CLOEXEC\n *\n */\n\n#ifndef __EOSCOMMON__CLOEXEC__HH\n#define __EOSCOMMON__CLOEXEC__HH\n\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdOuc/XrdOucTrace.hh>\n#include \"common/Namespace.hh\"\n#include <fcntl.h>\n#include <sys/time.h>\n\nEOSCOMMONNAMESPACE_BEGIN;\n\n/*----------------------------------------------------------------------------*/\n//! @brief Static Class to set a filedescriptor flag to CLOEXEC\n//!\n//! Example\n//! \\code eos::common::CloExec::Set(fd); \\endcode\n/*----------------------------------------------------------------------------*/\nclass CloExec {\npublic:\n\n    // ---------------------------------------------------------------------------\n    //! Set CLOEXEC on a single fd.\n    // ---------------------------------------------------------------------------\n\n    static int Set(int fd) {\n        int flags = 0;\n        if ((flags = fcntl(fd, F_GETFD)) != -1) {\n            return fcntl(fd, F_SETFD, flags | FD_CLOEXEC);\n        } else {\n            return -1;\n        }\n    }\n};\n\nEOSCOMMONNAMESPACE_END;\n\n#endif\n"
  },
  {
    "path": "common/ClockGetTime.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file ClockGetTime.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/ClockGetTime.hh\"\n\n#ifdef __MACH__\nint _clock_gettime(clockid_t clk_id, struct timespec* t)\n{\n  if (&clock_gettime == 0) {\n    mach_timebase_info_data_t timebase;\n    mach_timebase_info(&timebase);\n    uint64_t time;\n    time = mach_absolute_time();\n    double nseconds = ((double)time * (double)timebase.numer) / ((\n                        double)timebase.denom);\n    double seconds = ((double)time * (double)timebase.numer) / ((\n                       double)timebase.denom * 1e9);\n    t->tv_sec = seconds;\n    t->tv_nsec = nseconds;\n    return 0;\n  } else {\n    return clock_gettime(clk_id, t);\n  }\n}\n#endif\n"
  },
  {
    "path": "common/ClockGetTime.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file ClockGetTime.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include <time.h>\n\n#ifdef __MACH__\n#include <mach/mach_time.h>\n#ifndef CLOCK_REALTIME\n#define CLOCK_REALTIME 0\n#define CLOCK_MONOTONIC 0\n#define CLOCK_REALTIME_COARSE 0\n#define clockid_t int\n#define clock_gettime _clock_gettime\n#endif\n\nint _clock_gettime(clockid_t clk_id, struct timespec* t);\n\n#else\n#define _clock_gettime clock_gettime\n#endif\n"
  },
  {
    "path": "common/CommentLog.cc",
    "content": "// ----------------------------------------------------------------------\n// File: CommentLog.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n/*----------------------------------------------------------------------------*/\n#include \"common/CommentLog.hh\"\n#include \"common/Timing.hh\"\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <string.h>\n#include <unistd.h>\n\n/*----------------------------------------------------------------------------*/\n\nEOSCOMMONNAMESPACE_BEGIN;\n\n\n/*----------------------------------------------------------------------------*/\n/** \n * Open/Create the comment log file\n * \n * @param file path to the comment log file\n */\n\n/*----------------------------------------------------------------------------*/\nCommentLog::CommentLog (const char* file)\n{\n  mName = file;\n  mFd = ::open(file, O_CREAT | O_RDWR | O_APPEND, 0644);\n}\n\n/*----------------------------------------------------------------------------*/\n/** \n * Destructor\n * \n */\n\n/*----------------------------------------------------------------------------*/\nCommentLog::~CommentLog ()\n{\n  if (mFd > 0)\n  {\n    ::close(mFd);\n  }\n}\n\n/*----------------------------------------------------------------------------*/\n/** \n * Check if the comment log file is valid (opened)\n * \n * \n * @return true if valid - false if error\n */\n\n/*----------------------------------------------------------------------------*/\nbool\nCommentLog::IsValid ()\n{\n  return (mFd > 0);\n}\n\n/*----------------------------------------------------------------------------*/\n/** \n * Add a comment log line to the log file\n * \n * @param t timestamp to be displayed\n * @param cmd command to be displayed\n * @param subcmd subcommand to be displayed\n * @param args used for cmd/subcmd to be displayed\n * @param comment to be displayed\n * @param stdErr error returned to the shell for this command to be displayed\n * @param retc return code returned to the shell for this command to be displayed\n * \n * @return true if successful - false if error\n */\n\n/*----------------------------------------------------------------------------*/\nbool\nCommentLog::Add (time_t t, const char* cmd, const char* subcmd, const char* args, const char* comment, const char* stdErr, int retc)\n{\n  XrdOucString out = \"\";\n  out += \"# ==============================================================\\n\";\n  out += \"# \";\n  out += eos::common::Timing::ltime(t).c_str();\n  out += \" \";\n  out += comment;\n  out += \"\\n\";\n  out += \"# --------------------------------------------------------------\\n\";\n  char st[16];\n  snprintf(st, sizeof (st) - 1, \"%u\", (unsigned int) t);\n  out += \"  time=\";\n  out += st;\n  out += \" cmd=\\\"\";\n  out += cmd;\n  out += \"\\\" subcmd=\\\"\";\n  out += subcmd;\n  out += \"\\\" retc=\";\n  out += retc;\n  out += \" comment=\", out += comment;\n  out += \"\\n\";\n  out += \"# ..............................................................\\n\";\n  out += \"# args: \";\n  out += args;\n  out += \"\\n\";\n  XrdOucString sErr = stdErr;\n  if (sErr.length())\n  {\n    while (sErr.replace(\"\\n\", \"__#n#__\"))\n    {\n    }\n    while (sErr.replace(\"__#n#__\", \"\\n# \"))\n    {\n    }\n    sErr.insert(\"# \", 0);\n    if (sErr.endswith(\"#\"))\n    {\n      sErr.erase(sErr.length() - 1);\n    }\n    out += \"# >STDERR\\n\";\n    out += sErr;\n  }\n  if (!out.endswith(\"\\n\"))\n  {\n    out += \"\\n\";\n  }\n  if ((::write(mFd, out.c_str(), out.length())) < 0)\n  {\n    return false;\n  }\n  else\n  {\n    return true;\n  }\n}\n\n\n/*----------------------------------------------------------------------------*/\nEOSCOMMONNAMESPACE_END;\n"
  },
  {
    "path": "common/CommentLog.hh",
    "content": "// ----------------------------------------------------------------------\n// File: CommentLog.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   CommentLog.hh\n * \n * @brief  Class to log all commands which include a comment specified on the EOS shell\n * \n * \n */\n\n#ifndef __EOSCOMMON_COMMENTLOG__HH__\n#define __EOSCOMMON_COMMENTLOG__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/Namespace.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucString.hh>\n/*----------------------------------------------------------------------------*/\n#include <string>\n/*----------------------------------------------------------------------------*/\n\nEOSCOMMONNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\n//! Class to log all commands which include a comment specified on the EOS shell\n/*----------------------------------------------------------------------------*/\n\nclass CommentLog {\nprivate:\n  std::string mName; //< File Name storing the comments\n  int mFd;           //< File Descriptor to comment log file\npublic:\n  // ------------------------------------------------------------------------\n  //! Add a comment with 'exectime','cmd','subcmd','args','comment','stdErr','retc'\n  // ------------------------------------------------------------------------\n  bool Add(time_t, const char*, const char*, const char*, const char*, const char*, int);\n\n  // ------------------------------------------------------------------------\n  //! Check if the comment log file has been created/opened\n  // ------------------------------------------------------------------------\n  bool IsValid();\n\n  // ------------------------------------------------------------------------\n  // Constructor\n  // ------------------------------------------------------------------------\n  CommentLog(const char* file);\n  \n  // ------------------------------------------------------------------------\n  // Destructor\n  // ------------------------------------------------------------------------\n  ~CommentLog();\n};\n\n/*----------------------------------------------------------------------------*/\nEOSCOMMONNAMESPACE_END\n#endif\n\n"
  },
  {
    "path": "common/ConcurrentQueue.hh",
    "content": "// ----------------------------------------------------------------------\n//! @file ConcurrentQueue.hh\n//! @author Elvin-Alin Sindrilaru - CERN\n//! @brief Implementation of a thread-safe queue.\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * CopByright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include <cstdio>\n#include <queue>\n#include <mutex>\n#include <condition_variable>\n#include <common/Logging.hh>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Thread-safe queue implementation using mutexes\n//------------------------------------------------------------------------------\ntemplate <typename Data>\nclass ConcurrentQueue: public LogId\n{\npublic:\n  ConcurrentQueue() = default;\n  ~ConcurrentQueue() = default;\n  ConcurrentQueue(const ConcurrentQueue& other) = delete;\n  ConcurrentQueue& operator=(const ConcurrentQueue& other) = delete;\n  ConcurrentQueue(ConcurrentQueue&& other) = delete;\n  ConcurrentQueue& operator=(ConcurrentQueue&& other) = delete;\n\n  size_t size() const;\n  void push(Data& data);\n  template<typename... Ts>\n  void emplace(Ts&& ... args);\n  bool push_size(Data& data, size_t max_size);\n  bool empty() const;\n  bool try_pop(Data& popped_value);\n  void wait_pop(Data& popped_value);\n  void clear();\n\nprivate:\n  std::queue<Data> queue;\n  mutable std::mutex mMutex;\n  std::condition_variable mCondVar;\n};\n\n//------------------------------------------------------------------------------\n//! Get size of the queue\n//------------------------------------------------------------------------------\ntemplate <typename Data>\nsize_t\nConcurrentQueue<Data>::size() const\n{\n  std::lock_guard<std::mutex> lock(mMutex);\n  return queue.size();\n}\n\n//------------------------------------------------------------------------------\n//! Push data to the queue\n//------------------------------------------------------------------------------\ntemplate <typename Data>\nvoid\nConcurrentQueue<Data>::push(Data& data)\n{\n  {\n    std::lock_guard<std::mutex> lock(mMutex);\n    queue.push(data);\n  }\n  mCondVar.notify_all();\n}\n\n//------------------------------------------------------------------------------\n//! Push data to the queue while constructing the object in place\n//------------------------------------------------------------------------------\ntemplate<typename Data>\ntemplate<typename... Ts>\nvoid ConcurrentQueue<Data>::emplace(Ts&& ... args)\n{\n  {\n    std::lock_guard<std::mutex> lock(mMutex);\n    queue.emplace(std::forward<Ts>(args)...);\n  }\n  mCondVar.notify_all();\n}\n\n//------------------------------------------------------------------------------\n//! Push data to the queue if queue size is less then max_size\n//!\n//! @param data object to be pushed in the queue\n//! @param max_size max size allowed of the queue\n//------------------------------------------------------------------------------\ntemplate <typename Data>\nbool\nConcurrentQueue<Data>::push_size(Data& data, size_t max_size)\n{\n  bool ret_val = false;\n  std::unique_lock<std::mutex> lock(mMutex);\n\n  if (queue.size() <= max_size) {\n    queue.push(data);\n    lock.unlock();\n    mCondVar.notify_all();\n    ret_val = true;\n  }\n\n  return ret_val;\n}\n\n//------------------------------------------------------------------------------\n//! Test if queue is empty\n//------------------------------------------------------------------------------\ntemplate <typename Data>\nbool ConcurrentQueue<Data>::empty() const\n{\n  std::lock_guard<std::mutex> lock(mMutex);\n  return queue.empty();\n}\n\n//------------------------------------------------------------------------------\n//! Try to get data from queue\n//------------------------------------------------------------------------------\ntemplate <typename Data>\nbool\nConcurrentQueue<Data>::try_pop(Data& popped_value)\n{\n  std::lock_guard<std::mutex> lock(mMutex);\n\n  if (queue.empty()) {\n    return false;\n  }\n\n  popped_value = queue.front();\n  queue.pop();\n  return true;\n}\n\n//------------------------------------------------------------------------------\n//! Get data from queue, if empty queue then block until at least one element\n//! is added\n//------------------------------------------------------------------------------\ntemplate <typename Data>\nvoid\nConcurrentQueue<Data>::wait_pop(Data& popped_value)\n{\n  std::unique_lock<std::mutex> lock(mMutex);\n  mCondVar.wait(lock, [&]() {\n    return !queue.empty();\n  });\n  eos_static_debug(\"%s\", \"msg=\\\"wait on concurrent queue signalled\\\"\");\n  popped_value = queue.front();\n  queue.pop();\n}\n\n//------------------------------------------------------------------------------\n//! Remove all elements from the queue\n//------------------------------------------------------------------------------\ntemplate <typename Data>\nvoid\nConcurrentQueue<Data>::clear()\n{\n  std::lock_guard<std::mutex> lock(mMutex);\n\n  while (!queue.empty()) {\n    queue.pop();\n  }\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Config.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file Config.cc\n//! @author Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Config.hh\"\n#include \"common/Logging.hh\"\n#include \"common/StringConversion.hh\"\n#include <sys/types.h>\n#include <sys/stat.h>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n//! Load a configuration file\n//!\n//! @return true if loaded successfully, otherwise set error code/msg and false\n//----------------------------------------------------------------------------\nbool\nConfig::Load(const char* service, const char* name, bool reset)\n{\n  if (reset) {\n    // wipe previous configuration\n    conf.clear();\n    errcode = 0 ;\n    errorMessage = \"\";\n  }\n\n  std::string path = \"/etc/eos/config/\";\n  path += service;\n  path += \"/\";\n  path += name;\n\n  eos_static_info(\"loading configuration from '%s'...\", path.c_str());\n  struct stat buf{};\n\n  if (stat(path.c_str(), &buf)) {\n    errcode = errno;\n    errorMessage = \"error: unable to load '\" + path + \"' : \";\n    errorMessage += strerror(errno);\n    return false;\n  }\n\n  std::string in;\n  eos::common::StringConversion::LoadFileIntoString(path.c_str(), in);\n\n  std::istringstream f(in);\n  std::string line;\n  std::string chapter;\n  while (std::getline(f, line)) {\n    std::string p = ParseChapter(line);\n    if (p.empty()) {\n      p = ParseSection(line);\n      if (!p.empty()) {\n        if (!chapter.empty()) {\n          // store in chapter\n          conf[chapter].push_back(p);\n        } else {\n          errcode = EINVAL;\n          errorMessage = \"error: no chapter header in config file\";\n          return false;\n        }\n      }\n    } else {\n      chapter = p;\n      (void)conf[chapter];\n    }\n  }\n\n  return true;\n}\n\n//----------------------------------------------------------------------------\n//! Parse and possibly return a chapter entry\n//!\n//! @return parsed chapter entry or NULL if not applicable\n//----------------------------------------------------------------------------\nstd::string\nConfig::ParseChapter(const std::string& line)\n{\n  std::string pline = line;\n\n  if (pline.empty()) {\n    return \"\";\n  }\n  // remove new line\n  if (pline.back() == '\\n') {\n    pline.pop_back();\n  }\n\n  // skip comments\n  if (pline.front() == '#') {\n    return \"\";\n  }\n\n  while (pline.front() == ' ') {\n    pline.erase(0,1);\n  }\n\n  while (pline.back() == ' ') {\n    pline.pop_back();\n  }\n\n  if (line.front() == '[') {\n    if (line.back() == ']') {\n      pline.pop_back();\n      pline.erase(0,1);\n      return pline;\n    }\n  }\n  return \"\";\n}\n\n//----------------------------------------------------------------------------\n//! Parse and possibly return a section entry\n//!\n//! @return parsed section entry or NULL if not applicable\n//----------------------------------------------------------------------------\nstd::string\nConfig::ParseSection(const std::string& line)\n{\n  std::string pline = line;\n\n  if (pline.empty()) {\n    return \"\";\n  }\n  // remove new line\n  if (pline.back() == '\\n') {\n    pline.pop_back();\n  }\n\n  // skip comments\n  if (pline.front() == '#') {\n    return \"\";\n  }\n\n  while (pline.front() == ' ') {\n    pline.erase(0,1);\n  }\n\n  while (pline.back() == ' ') {\n    pline.pop_back();\n  }\n\n  return pline;\n}\n\n//----------------------------------------------------------------------------\n//! AsMap\n//!\n//! return a map with the lines matching x=y\n//----------------------------------------------------------------------------\nstd::map<std::string, std::string>\nConfig::AsMap(const char* chapter)\n{\n  std::map<std::string, std::string> map;\n  if (chapter && conf.count(chapter)) {\n    for (const auto& it : conf[chapter]) {\n      if (eos::common::StringConversion::GetKeyValueMap(it.c_str(), map, \"=\", \" \")) {\n        //\n      }\n    }\n  }\n  return map;\n}\n\n\nchar**\nConfig::Env(const char* chapter)\n{\n  std::map<std::string, std::string> map = AsMap(chapter);\n  size_t cnt=0;\n\n  // do variable substitution\n  for (const auto& it : map) {\n    std::string s = it.second;\n    ReplaceFromChapter(s,chapter);\n    map[it.first]=s;\n  }\n\n  for (auto it : map) {\n    std::string kv = it.first + \"=\";\n    kv = it.first + \"=\" + it.second;\n    envv[cnt++] = strdup( kv.c_str() ) ;\n  }\n  envv[cnt] = nullptr;\n  return envv;\n}\n\n//----------------------------------------------------------------------------\n//! Get <value> of line for a given key '<key> <value>'\n//----------------------------------------------------------------------------\nstd::string\nConfig::GetValueByKey(const char* chapter, const char* key)\n{\n  if (Has(chapter)) {\n    for (const auto& it : conf[chapter]) {\n      if ( it.substr(0, std::string(key).length()) == key ) {\n        return it.substr(std::string(key).length() + 1);\n      }\n    }\n  }\n  return \"\";\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Config.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Config.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOSCOMMON_CONFIG_HH\n#define EOSCOMMON_CONFIG_HH\n\n#include \"common/Namespace.hh\"\n#include \"common/StringConversion.hh\"\n#include <sstream>\n#include <string>\n#include <map>\n#include <vector>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// systemd style configuration file support\n//------------------------------------------------------------------------------\nclass Config\n{\npublic:\n  //----------------------------------------------------------------------------\n  // Default constructor\n  //----------------------------------------------------------------------------\n  Config() : errcode(0)\n  {\n    hostname = eos::common::StringConversion::StringFromShellCmd(\"hostname -f\");\n    hostname.pop_back();\n  }\n\n  //----------------------------------------------------------------------------\n  // Loader\n  //----------------------------------------------------------------------------\n\n  bool Load(const char* service, const char* name = \"default\", bool reset = true);\n\n  //----------------------------------------------------------------------------\n  // Parse and possibly return a chapter entry\n  //----------------------------------------------------------------------------\n  std::string ParseChapter(const std::string& line);\n\n  //----------------------------------------------------------------------------\n  // Parse and possibly return a section entry\n  //----------------------------------------------------------------------------\n  std::string ParseSection(const std::string& line);\n\n  //----------------------------------------------------------------------------\n  // Is status ok?\n  //----------------------------------------------------------------------------\n  bool ok() const\n  {\n    return (errcode == 0);\n  }\n\n  //----------------------------------------------------------------------------\n  // Get errorcode\n  //----------------------------------------------------------------------------\n  int getErrc() const\n  {\n    return errcode;\n  }\n\n  //----------------------------------------------------------------------------\n  // Get error message\n  //----------------------------------------------------------------------------\n  std::string getMsg() const\n  {\n    return errorMessage;\n  }\n\n  //----------------------------------------------------------------------------\n  // To string, including error code\n  //----------------------------------------------------------------------------\n  std::string toString() const\n  {\n    std::ostringstream ss;\n    ss << \"(\" << errcode << \"): \" << errorMessage;\n    return ss.str();\n  }\n\n  //----------------------------------------------------------------------------\n  // Implicit conversion to boolean: Same value as ok()\n  //----------------------------------------------------------------------------\n  operator bool() const\n  {\n    return ok();\n  }\n\n  typedef std::vector<std::string> ConfigSection;\n  typedef std::map<std::string, ConfigSection> ConfigChapter;\n\n  //----------------------------------------------------------------------------\n  // Overloaded [] operator\n  //----------------------------------------------------------------------------\n  ConfigSection& operator[](const char* i)\n  {\n    if (conf.count(i)) {\n      return conf[i];\n    } else {\n      static ConfigSection none;\n      return none;\n    }\n  }\n\n  std::string ParseVariable(const std::string& s, size_t& start, size_t& stop)\n  {\n    size_t vstart = s.find(\"$\");\n\n    if (vstart == std::string::npos) {\n      start = stop = 0;\n      return \"\";\n    }\n\n    size_t vstop = 0;\n    std::string v = s;\n\n    if (s.at(vstart + 1) == '{') {\n      vstop = s.find(\"}\");\n\n      if (vstop != std::string::npos) {\n        v.erase(vstop);\n        v.erase(0, vstart + 2);\n        start = vstart;\n        stop = vstop + 1;\n      } else {\n        start = stop = 0;\n        return \"\";\n      }\n    } else {\n      v.erase(0, vstart + 1);\n      start = vstart;\n      vstop = s.find(\" \", vstart + 1);\n\n      if (vstop == std::string::npos) {\n        stop = v.length() + 1;\n      } else {\n        stop = vstop;\n        v.erase(vstop - vstart - 1);\n      }\n    }\n\n    return v;\n  }\n\n  //----------------------------------------------------------------------------\n  // Replace variables from a chapter until they are resolved\n  //----------------------------------------------------------------------------\n  void ReplaceFromChapter(std::string& s, const char* substitute_chapter)\n  {\n    if (Has(substitute_chapter)) {\n      std::map<std::string, std::string> m = AsMap(substitute_chapter);\n      // preset always the EOSHOST variable\n      m[\"EOSHOST\"] = hostname;\n      std::string var;\n      size_t p1, p2;\n\n      while ((var = ParseVariable(s, p1, p2)).length()) {\n        if (!p1 && !p2) {\n          break;\n        }\n\n        if (m.count(var)) {\n          s.erase(p1, p2 - p1);\n          s.insert(p1, m[var]);\n        } else {\n          break;\n        }\n      }\n    } else {\n      return;\n    }\n  }\n\n\n  //----------------------------------------------------------------------------\n  // Replace a string with a sysconfig definition\n  //----------------------------------------------------------------------------\n  std::string Substitute(const std::string& s, bool doit = false,\n                         const char* substitute_chapter = \"sysconfig\")\n  {\n    if (doit) {\n      std::string r = s;\n      ReplaceFromChapter(r, substitute_chapter);\n      return r;\n    }\n\n    return s;\n  }\n\n\n  //----------------------------------------------------------------------------\n  // Config Dumper\n  //----------------------------------------------------------------------------\n\n  std::string Dump(const char* chapter = 0, bool substitute = false,\n                   const char* substitute_chapter = \"sysconfig\")\n  {\n    std::string out;\n\n    if (chapter) {\n      if (conf.count(chapter)) {\n        for (auto it : conf[chapter]) {\n          out += Substitute(it, substitute, substitute_chapter);\n          out += \"\\n\";\n        }\n      }\n    } else {\n      for (auto c : conf) {\n        out += \"[\";\n        out += c.first;;\n        out += \"]\\n\";\n\n        for (auto it : c.second) {\n          out += Substitute(it, substitute, substitute_chapter);\n          out += \"\\n\";\n        }\n      }\n    }\n\n    return out;\n  }\n\n  //----------------------------------------------------------------------------\n  // Test for configuration chapter\n  //----------------------------------------------------------------------------\n  bool Has(const char* chapter) const\n  {\n    return conf.count(chapter);\n  }\n\n  //----------------------------------------------------------------------------\n  // Get <value> of line for a given key '<key> <value>'\n  //----------------------------------------------------------------------------\n  std::string GetValueByKey(const char* chapter, const char* key);\n\n\n  //----------------------------------------------------------------------------\n  // AsMap\n  //----------------------------------------------------------------------------\n  std::map<std::string, std::string> AsMap(const char* chapter);\n\n  //----------------------------------------------------------------------------\n  // Env\n  // ---------------------------------------------------------------------------\n\n  char** Env(const char* chapter);\n\nprivate:\n  int errcode;\n  std::string errorMessage;\n  std::string service;\n  std::string name;\n  std::string hostname;\n  ConfigChapter conf;\n  char* envv[1024];\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/Constants.hh",
    "content": "//------------------------------------------------------------------------------\n// File: Constants.hh\n// Author: Jozsef Makai - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include <sys/types.h>\n\nEOSCOMMONNAMESPACE_BEGIN\n\nstatic constexpr auto TAPE_FS_ID = 65535u;\n//! List of Prepare request IDs for this file\nstatic constexpr auto RETRIEVE_REQID_ATTR_NAME = \"sys.retrieve.req_id\";\n//! Last time the Prepare request was actioned\nstatic constexpr auto RETRIEVE_REQTIME_ATTR_NAME = \"sys.retrieve.req_time\";\n//! Counter for multiple staging requests on same file\nstatic constexpr auto RETRIEVE_EVICT_COUNTER_NAME =\n  \"sys.retrieve.evict_counter\";\n//! Prepare request failure reason\nstatic constexpr auto RETRIEVE_ERROR_ATTR_NAME = \"sys.retrieve.error\";\n//! Archive request failure reason\nstatic constexpr auto ARCHIVE_ERROR_ATTR_NAME = \"sys.archive.error\";\n//! Archive file ID\nstatic constexpr auto ARCHIVE_FILE_ID_ATTR_NAME = \"sys.archive.file_id\";\n//! Archive storage class\nstatic constexpr auto ARCHIVE_STORAGE_CLASS_ATTR_NAME =\n  \"sys.archive.storage_class\";\n//! EOS file btime\nstatic constexpr auto EOS_BTIME = \"sys.eos.btime\";\n//! CTA internal objectsore id for archive requests\nstatic constexpr auto CTA_OBJECTSTORE_ARCHIVE_REQ_ID_NAME =\n  \"sys.cta.archive.objectstore.id\";\n//! CTA internal objectstore id for prepare requests\nstatic constexpr auto CTA_OBJECTSTORE_REQ_ID_NAME = \"sys.cta.objectstore.id\";\n// Workflow names\nstatic constexpr auto RETRIEVE_WRITTEN_WORKFLOW_NAME = \"retrieve_written\";\nstatic constexpr auto RETRIEVE_FAILED_WORKFLOW_NAME = \"sync::retrieve_failed\";\nstatic constexpr auto ARCHIVE_FAILED_WORKFLOW_NAME = \"sync::archive_failed\";\nstatic constexpr auto WF_CUSTOM_ATTRIBUTES_TO_FST_EQUALS = \"=\";\nstatic constexpr auto WF_CUSTOM_ATTRIBUTES_TO_FST_SEPARATOR = \";;;\";\n//! Max rate in MB/s at which the scanner should run\nstatic constexpr auto SCAN_IO_RATE_NAME = \"scanrate\";\n//! Time interval after which a scanned filed is rescanned\nstatic constexpr auto SCAN_ENTRY_INTERVAL_NAME = \"scaninterval\";\n//! Time interval after which a scanned rain filed is rescanned\nstatic constexpr auto SCAN_RAIN_ENTRY_INTERVAL_NAME = \"scan_rain_interval\";\n//! Time interval after which the scanner will rerun\nstatic constexpr auto SCAN_DISK_INTERVAL_NAME = \"scan_disk_interval\";\n//! Maximum ns scan rate when it comes to stat requests done against the\n//! local disks on the FSTs\nstatic constexpr auto SCAN_NS_RATE_NAME = \"scan_ns_rate\";\n//! Time interval after which the ns scanner will rerun\nstatic constexpr auto SCAN_NS_INTERVAL_NAME = \"scan_ns_interval\";\n//! Time interval after which the alternative checksum computing thread will rerun\nstatic constexpr auto SCAN_ALTXS_INTERVAL_NAME = \"scan_altxs_interval\";\n//! If the altxs synchronization metadata is enabled or not\nstatic constexpr auto ALTXS_SYNC = \"altxs_sync\";\n//! Refresh interval for altxs synchronization metadata\nstatic constexpr auto ALTXS_SYNC_INTERVAL = \"altxs_sync_interval\";\n//! Maximum scan rate for statting requests to the MGM for computing the\n//! alternative checksums\nstatic constexpr auto ALTXS_SYNC_RATE = \"altxs_sync_rate\";\n//! Special EOS scheduling group space\nstatic constexpr auto EOS_SPARE_GROUP = \"spare\";\n//! Application lock attribute\nstatic constexpr auto EOS_APP_LOCK_ATTR = \"sys.app.lock\";\n//! Trace attributes\nstatic constexpr auto EOS_DTRACE_ATTR = \"sys.dtrace\";\nstatic constexpr auto EOS_VTRACE_ATTR = \"sys.vtrace\";\nstatic constexpr auto EOS_UTRACE_ATTR = \"sys.utrace\";\n//! FST heartbeat key marker, the \"stat.\" prefix makes it transient\nstatic constexpr auto FST_HEARTBEAT_KEY = \"stat.heartbeat\";\nstatic constexpr auto FST_TRAFFIC_SHAPING_IO_REPORT =\n    \"stat.traffic_shaping.fst_io_report\";\nstatic constexpr auto FST_TRAFFIC_SHAPING_IO_LIMITS =\n    \"stat.traffic_shaping.fst_io_limits\";\nstatic constexpr auto FST_TRAFFIC_SHAPING_ENABLE_TOGGLE =\n    \"stat.traffic_shaping.fst_is_enabled\";\nstatic constexpr auto FST_TRAFFIC_SHAPING_STATS_THREAD_PERIOD =\n    \"stat.traffic_shaping.fst_stats_thread_period\";\n\nstatic constexpr auto TRAFFIC_SHAPING_POLICIES_CONFIG = \"traffic_shaping::policies\";\nstatic constexpr auto TRAFFIC_SHAPING_ENABLE_CONFIG = \"traffic_shaping::is_enabled\";\nstatic constexpr auto TRAFFIC_SHAPING_THREAD_PERIODS = \"traffic_shaping::thread_periods\";\n\n//! ADM uid and gid\nstatic constexpr uid_t ADM_UID = 3;\nstatic constexpr gid_t ADM_GID = 4;\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/CopyProcess.hh",
    "content": "// ----------------------------------------------------------------------\n// File: CopyProcess.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <vector>\n#include <memory>\n#include <atomic>\n#include <cstddef> // for size_t\n#include <mutex>\n\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdCl/XrdClCopyProcess.hh>\n#include <XrdCl/XrdClPropertyList.hh>\n\n/**\n * @file   CopyProcess.hh\n *\n * @brief  Class overcoming XRootD limitation in number of copy jobs\n *\n *\n */\n\n#pragma once\n#include \"common/Namespace.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN;\n\nclass CopyProcess {\npublic:\n  CopyProcess(size_t initialProcesses = 1, size_t jobsPerProc = 8192)\n    : vJobs(initialProcesses), jobCounter(0), jobsPerProcess(jobsPerProc) {\n    for (auto &jobPtr : vJobs) {\n      jobPtr = std::make_shared<XrdCl::CopyProcess>();\n    }\n  }\n\n  // Add a job to the appropriate XrdCopyProcess instance\n  XRootDStatus AddJob(const PropertyList &properties, PropertyList *results) {\n    size_t count = jobCounter.fetch_add(1);\n    size_t index = count / jobsPerProcess;\n\n    {\n      std::lock_guard<std::mutex> lock(resizeMutex);\n      if (index >= vJobs.size()) {\n        vJobs.resize(index + 1);\n        for (size_t i = 0; i <= index; ++i) {\n          if (!vJobs[i]) {\n            vJobs[i] = std::make_shared<XrdCl::CopyProcess>();\n          }\n        }\n      }\n    }\n\n    return vJobs[index]->AddJob(properties, results);\n  }\n\n  // Prepare all copy processes; stop if any fails\n  XRootDStatus Prepare() {\n    for (auto &job : vJobs) {\n      if (job) {\n        XRootDStatus status = job->Prepare();\n        if (!status.IsOK()) {\n          return status;\n        }\n      }\n    }\n    return XRootDStatus(); // OK\n  }\n\n  // Run all copy processes; stop if any fails\n  XRootDStatus Run(CopyProgressHandler *handler) {\n    for (auto &job : vJobs) {\n      if (job) {\n        XRootDStatus status = job->Run(handler);\n        if (!status.IsOK()) {\n          return status;\n        }\n      }\n    }\n    return XRootDStatus(); // OK\n  }\n\n  const size_t Jobs() {\n    return jobCounter;\n  }\n\nprivate:\n  std::vector<std::shared_ptr<XrdCl::CopyProcess>> vJobs;\n  std::atomic<size_t> jobCounter;\n  const size_t jobsPerProcess;\n  std::mutex resizeMutex;\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Counter.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Counter.hh\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_COUNTER_HH\n#define EOS_COUNTER_HH\n\n#include \"common/SteadyClock.hh\"\n\nnamespace eos::common\n{\n\nclass Counter\n{\npublic:\n  Counter() = default;\n\n  Counter(SteadyClock* clock): mCounter(0), mSteadyClock(clock) {}\n\n\n  void Init()\n  {\n    mCounter = 0;\n    mStartTime = GetCurrentTime();\n    mLastTime = mStartTime;\n  }\n\n  void Increment(uint64_t value = 1)\n  {\n    auto curr_time = GetCurrentTime();\n    std::chrono::duration<double, std::milli> ms_elapsed = curr_time - mLastTime;\n\n    if (ms_elapsed.count() > 0) {\n      last_frequency = 1000 * value / ms_elapsed.count();\n    }\n\n    mLastTime = curr_time;\n    mCounter += value;\n    ms_elapsed = curr_time - mStartTime;\n\n    if (ms_elapsed.count() > 0) {\n      frequency = 1000 * mCounter / ms_elapsed.count();\n    }\n  }\n\n  double\n  GetFrequency()\n  {\n    return frequency;\n  }\n\n  double\n  GetLastFrequency()\n  {\n    return last_frequency;\n  }\n\n  std::chrono::steady_clock::time_point GetStartTime()\n  {\n    return mStartTime;\n  }\n\n  uint64_t GetSecondsSinceStart()\n  {\n    using namespace std::chrono;\n    return duration_cast<seconds>(GetCurrentTime() - mStartTime).count();\n  }\n\nprivate:\n  std::chrono::steady_clock::time_point GetCurrentTime()\n  {\n    if (mSteadyClock != nullptr) {\n      return mSteadyClock->GetTime();\n    }\n\n    return std::chrono::steady_clock::now();\n  }\n\n  uint64_t mCounter {0};\n  std::chrono::steady_clock::time_point mLastTime;\n  std::chrono::steady_clock::time_point mStartTime;\n  SteadyClock* mSteadyClock{nullptr}; //<Only used for testing\n  double last_frequency{0};\n  double frequency{0};\n};\n\n} // namespace eos::common\n\n#endif // EOS_COUNTER_HH\n"
  },
  {
    "path": "common/CtaCommon.hh",
    "content": "//-----------------------------------------------------------------------\n//! @file CtaCommon.hh\n//! @author Michael Davis - CERN\n//! @brief CTA to EOS conversion functions shared by MGM and FST\n//-----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include \"LayoutId.hh\"\n#include \"cta_frontend.pb.h\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\nclass CtaCommon {\npublic:\n\n  /*!\n   * Convert EOS checksum to CTA checksum blob protobuf\n   */\n  static void SetChecksum(cta::common::ChecksumBlob::Checksum *cs, int type, const std::string &value) {\n    // Convert LayoutId enum to CTA+EOS enum\n    switch(LayoutId::GetChecksum(type)) {\n      case LayoutId::kNone:   cs->set_type(cta::common::ChecksumBlob::Checksum::NONE);    break;\n      case LayoutId::kAdler:  cs->set_type(cta::common::ChecksumBlob::Checksum::ADLER32); break;\n      case LayoutId::kCRC32:  cs->set_type(cta::common::ChecksumBlob::Checksum::CRC32);   break;\n      case LayoutId::kMD5:    cs->set_type(cta::common::ChecksumBlob::Checksum::MD5);     break;\n      case LayoutId::kSHA1:   cs->set_type(cta::common::ChecksumBlob::Checksum::SHA1);    break;\n      case LayoutId::kCRC32C: cs->set_type(cta::common::ChecksumBlob::Checksum::CRC32C);  break;\n      // Follows the behaviour of LayoutId::GetChecksumString():\n      // unknown enum values set checksum type to None rather than throwing an exception\n      default:                cs->set_type(cta::common::ChecksumBlob::Checksum::NONE);\n    };\n\n    // Validate the length of the supplied hex string\n    auto byteArrayLen = LayoutId::GetChecksumLen(type);\n    if(byteArrayLen == 0) {\n      cs->set_value(\"\");\n      return;\n    } else if((value.length()%2 != 0) || (value.length() > byteArrayLen*2)) {\n      cs->set_value(\"INVALID CHECKSUM LENGTH=\" + std::to_string(value.length()));\n      return;\n    }\n\n    // Convert string hex representation into a little-endian byte array\n    std::string byteArray;\n    for(unsigned int i = 0; i < value.length(); i += 2) {\n      char byte = static_cast<char>(strtol(value.substr(i, 2).c_str(), NULL, 16));\n      byteArray.insert(byteArray.begin(), byte);\n    }\n    cs->set_value(byteArray);\n  }\n\n  /*!\n   * Convert CTA ResponseType to std::string\n   */\n  static std::string ctaResponseCodeToString(cta::xrd::Response::ResponseType rt)\n  {\n    switch(rt) {\n      case cta::xrd::Response::RSP_ERR_CTA:      return \"RSP_ERR_CTA\";\n      case cta::xrd::Response::RSP_ERR_USER:     return \"RSP_ERR_USER\";\n      case cta::xrd::Response::RSP_ERR_PROTOBUF: return \"RSP_ERR_PROTOBUF\";\n      case cta::xrd::Response::RSP_INVALID:      \n      default:                                   return \"RSP_INVALID\";\n    }\n  }\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/DBG.hh",
    "content": "/*****************************************************************************\n\n                                dbg(...) macro\n\nLicense (MIT):\n\n  Copyright (c) 2019 David Peter <mail@david-peter.de>\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy\n  of this software and associated documentation files (the \"Software\"), to\n  deal in the Software without restriction, including without limitation the\n  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n  sell copies of the Software, and to permit persons to whom the Software is\n  furnished to do so, subject to the following conditions:\n\n  The above copyright notice and this permission notice shall be included in\n  all copies or substantial portions of the Software.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n  SOFTWARE.\n\n*****************************************************************************/\n\n#ifndef DBG_MACRO_DBG_H\n#define DBG_MACRO_DBG_H\n\n#ifndef DBG_MACRO_NO_WARNING\n#pragma message(\"WARNING: the 'dbg.h' header is included in your code base\")\n#endif  // DBG_MACRO_NO_WARNING\n\n#include <algorithm>\n#include <ios>\n#include <iostream>\n#include <memory>\n#include <sstream>\n#include <string>\n#include <tuple>\n#include <type_traits>\n#include <vector>\n\n#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))\n#include <unistd.h>\n#endif\n\n#if __cplusplus >= 201703L\n#include <optional>\n#include <variant>\n#endif\n\nnamespace dbg_macro {\n\n#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))\ninline bool isColorizedOutputEnabled() {\n  return isatty(fileno(stderr));\n}\n#else\ninline bool isColorizedOutputEnabled() {\n  return true;\n}\n#endif\n\nnamespace pretty_function {\n\n// Compiler-agnostic version of __PRETTY_FUNCTION__ and constants to\n// extract the template argument in `type_name_impl`\n\n#if defined(__clang__)\n#define DBG_MACRO_PRETTY_FUNCTION __PRETTY_FUNCTION__\nstatic constexpr size_t PREFIX_LENGTH =\n    sizeof(\"const char *dbg_macro::type_name_impl() [T = \") - 1;\nstatic constexpr size_t SUFFIX_LENGTH = sizeof(\"]\") - 1;\n#elif defined(__GNUC__) && !defined(__clang__)\n#define DBG_MACRO_PRETTY_FUNCTION __PRETTY_FUNCTION__\nstatic constexpr size_t PREFIX_LENGTH =\n    sizeof(\"const char* dbg_macro::type_name_impl() [with T = \") - 1;\nstatic constexpr size_t SUFFIX_LENGTH = sizeof(\"]\") - 1;\n#elif defined(_MSC_VER)\n#define DBG_MACRO_PRETTY_FUNCTION __FUNCSIG__\nstatic constexpr size_t PREFIX_LENGTH =\n    sizeof(\"const char *__cdecl dbg_macro::type_name_impl<\") - 1;\nstatic constexpr size_t SUFFIX_LENGTH = sizeof(\">(void)\") - 1;\n#else\n#error \"This compiler is currently not supported by dbg_macro.\"\n#endif\n\n}  // namespace pretty_function\n\n// Implementation of 'type_name<T>()'\n\ntemplate <typename T>\nconst char* type_name_impl() {\n  return DBG_MACRO_PRETTY_FUNCTION;\n}\n\ntemplate <typename T>\nstruct type_tag {};\n\ntemplate <int&... ExplicitArgumentBarrier, typename T>\nstd::string get_type_name(type_tag<T>) {\n  namespace pf = pretty_function;\n\n  std::string type = type_name_impl<T>();\n  return type.substr(pf::PREFIX_LENGTH,\n                     type.size() - pf::PREFIX_LENGTH - pf::SUFFIX_LENGTH);\n}\n\ntemplate <typename T>\nstd::string type_name() {\n  if (std::is_volatile<T>::value) {\n    if (std::is_pointer<T>::value) {\n      return type_name<typename std::remove_volatile<T>::type>() + \" volatile\";\n    } else {\n      return \"volatile \" + type_name<typename std::remove_volatile<T>::type>();\n    }\n  }\n  if (std::is_const<T>::value) {\n    if (std::is_pointer<T>::value) {\n      return type_name<typename std::remove_const<T>::type>() + \" const\";\n    } else {\n      return \"const \" + type_name<typename std::remove_const<T>::type>();\n    }\n  }\n  if (std::is_pointer<T>::value) {\n    return type_name<typename std::remove_pointer<T>::type>() + \"*\";\n  }\n  if (std::is_lvalue_reference<T>::value) {\n    return type_name<typename std::remove_reference<T>::type>() + \"&\";\n  }\n  if (std::is_rvalue_reference<T>::value) {\n    return type_name<typename std::remove_reference<T>::type>() + \"&&\";\n  }\n  return get_type_name(type_tag<T>{});\n}\n\ninline std::string get_type_name(type_tag<short>) {\n  return \"short\";\n}\n\ninline std::string get_type_name(type_tag<unsigned short>) {\n  return \"unsigned short\";\n}\n\ninline std::string get_type_name(type_tag<long>) {\n  return \"long\";\n}\n\ninline std::string get_type_name(type_tag<unsigned long>) {\n  return \"unsigned long\";\n}\n\ninline std::string get_type_name(type_tag<std::string>) {\n  return \"std::string\";\n}\n\ntemplate <typename T>\nstd::string get_type_name(type_tag<std::vector<T, std::allocator<T>>>) {\n  return \"std::vector<\" + type_name<T>() + \">\";\n}\n\n// Implementation of 'is_detected' to specialize for container-like types\n\nnamespace detail_detector {\n\nstruct nonesuch {\n  nonesuch() = delete;\n  ~nonesuch() = delete;\n  nonesuch(nonesuch const&) = delete;\n  void operator=(nonesuch const&) = delete;\n};\n\ntemplate <typename...>\nusing void_t = void;\n\ntemplate <class Default,\n          class AlwaysVoid,\n          template <class...>\n          class Op,\n          class... Args>\nstruct detector {\n  using value_t = std::false_type;\n  using type = Default;\n};\n\ntemplate <class Default, template <class...> class Op, class... Args>\nstruct detector<Default, void_t<Op<Args...>>, Op, Args...> {\n  using value_t = std::true_type;\n  using type = Op<Args...>;\n};\n\n}  // namespace detail_detector\n\ntemplate <template <class...> class Op, class... Args>\nusing is_detected = typename detail_detector::\n    detector<detail_detector::nonesuch, void, Op, Args...>::value_t;\n\nnamespace detail {\n\nnamespace {\nusing std::begin;\nusing std::end;\n#if __cplusplus < 201703L\ntemplate <typename T>\nconstexpr auto size(const T& c) -> decltype(c.size()) {\n  return c.size();\n}\ntemplate <typename T, std::size_t N>\nconstexpr std::size_t size(const T (&)[N]) {\n  return N;\n}\n#else\nusing std::size;\n#endif\n}  // namespace\n\ntemplate <typename T>\nusing detect_begin_t = decltype(detail::begin(std::declval<T>()));\n\ntemplate <typename T>\nusing detect_end_t = decltype(detail::end(std::declval<T>()));\n\ntemplate <typename T>\nusing detect_size_t = decltype(detail::size(std::declval<T>()));\n\ntemplate <typename T>\nstruct has_begin_end_size {\n  static constexpr bool value = is_detected<detect_begin_t, T>::value &&\n                                is_detected<detect_end_t, T>::value &&\n                                is_detected<detect_size_t, T>::value;\n};\n\ntemplate <typename T>\nusing ostream_operator_t =\n    decltype(std::declval<std::ostream&>() << std::declval<T>());\n\ntemplate <typename T>\nstruct has_ostream_operator {\n  static constexpr bool value = is_detected<ostream_operator_t, T>::value;\n};\n\n}  // namespace detail\n\n// Specializations of \"pretty_print\"\n\ntemplate <typename T>\ninline void pretty_print(std::ostream& stream, const T& value, std::true_type) {\n  stream << value;\n}\n\ntemplate <typename T>\ninline void pretty_print(std::ostream&, const T&, std::false_type) {\n  static_assert(detail::has_ostream_operator<const T&>::value,\n                \"Type does not support the << ostream operator\");\n}\n\ntemplate <typename T>\ninline typename std::enable_if<!detail::has_begin_end_size<const T&>::value &&\n                                   !std::is_enum<T>::value,\n                               bool>::type\npretty_print(std::ostream& stream, const T& value) {\n  pretty_print(\n      stream, value,\n      std::integral_constant<bool,\n                             detail::has_ostream_operator<const T&>::value>{});\n  return true;\n}\n\ninline bool pretty_print(std::ostream& stream, const bool& value) {\n  stream << std::boolalpha << value;\n  return true;\n}\n\ninline bool pretty_print(std::ostream& stream, const char& value) {\n  stream << \"'\" << value << \"'\";\n  return true;\n}\n\ntemplate <typename P>\ninline bool pretty_print(std::ostream& stream, P* const& value) {\n  if (value == nullptr) {\n    stream << \"nullptr\";\n  } else {\n    stream << value;\n  }\n  return true;\n}\n\ntemplate <typename T, typename Deleter>\ninline bool pretty_print(std::ostream& stream,\n                         std::unique_ptr<T, Deleter>& value) {\n  pretty_print(stream, value.get());\n  return true;\n}\n\ntemplate <typename T>\ninline bool pretty_print(std::ostream& stream, std::shared_ptr<T>& value) {\n  pretty_print(stream, value.get());\n  stream << \" (use_count = \" << value.use_count() << \")\";\n\n  return true;\n}\n\ntemplate <size_t N>\ninline bool pretty_print(std::ostream& stream, const char (&value)[N]) {\n  stream << value;\n  return false;\n}\n\ntemplate <>\ninline bool pretty_print(std::ostream& stream, const char* const& value) {\n  stream << '\"' << value << '\"';\n  return true;\n}\n\ntemplate <size_t Idx>\nstruct pretty_print_tuple {\n  template <typename... Ts>\n  static void print(std::ostream& stream, const std::tuple<Ts...>& tuple) {\n    pretty_print_tuple<Idx - 1>::print(stream, tuple);\n    stream << \", \";\n    pretty_print(stream, std::get<Idx>(tuple));\n  }\n};\n\ntemplate <>\nstruct pretty_print_tuple<0> {\n  template <typename... Ts>\n  static void print(std::ostream& stream, const std::tuple<Ts...>& tuple) {\n    pretty_print(stream, std::get<0>(tuple));\n  }\n};\n\ntemplate <typename... Ts>\ninline bool pretty_print(std::ostream& stream, const std::tuple<Ts...>& value) {\n  stream << \"{\";\n  pretty_print_tuple<sizeof...(Ts) - 1>::print(stream, value);\n  stream << \"}\";\n\n  return true;\n}\n\ntemplate <>\ninline bool pretty_print(std::ostream& stream, const std::tuple<>&) {\n  stream << \"{}\";\n\n  return true;\n}\n\ntemplate <typename Container>\ninline\n    typename std::enable_if<detail::has_begin_end_size<const Container&>::value,\n                            bool>::type\n    pretty_print(std::ostream& stream, const Container& value) {\n  stream << \"{\";\n  const size_t size = detail::size(value);\n  const size_t n = std::min(size_t{5}, size);\n  size_t i = 0;\n  using std::begin;\n  using std::end;\n  for (auto it = begin(value); it != end(value) && i < n; ++it, ++i) {\n    pretty_print(stream, *it);\n    if (i != n - 1) {\n      stream << \", \";\n    }\n  }\n\n  if (size > n) {\n    stream << \", ...\";\n    stream << \" size:\" << size;\n  }\n\n  stream << \"}\";\n  return true;\n}\n\ntemplate <typename Enum>\ninline typename std::enable_if<std::is_enum<Enum>::value, bool>::type\npretty_print(std::ostream& stream, Enum const& value) {\n  using UnderlyingType = typename std::underlying_type<Enum>::type;\n  stream << static_cast<UnderlyingType>(value);\n\n  return true;\n}\n\ninline bool pretty_print(std::ostream& stream, const std::string& value) {\n  stream << '\"' << value << '\"';\n  return true;\n}\n\n#if __cplusplus >= 201703L\n\ntemplate <typename T>\ninline bool pretty_print(std::ostream& stream, const std::optional<T>& value) {\n  if (value) {\n    stream << '{';\n    pretty_print(stream, *value);\n    stream << '}';\n  } else {\n    stream << \"nullopt\";\n  }\n\n  return true;\n}\n\ntemplate <typename... Ts>\ninline bool pretty_print(std::ostream& stream,\n                         const std::variant<Ts...>& value) {\n  stream << \"{\";\n  std::visit([&stream](auto&& arg) { pretty_print(stream, arg); }, value);\n  stream << \"}\";\n\n  return true;\n}\n\n#endif  // __cplusplus >= 201703L\n\nclass DebugOutput {\n public:\n  DebugOutput(const char* filepath,\n              int line,\n              const char* function_name,\n              const char* expression)\n      : m_use_colorized_output(isColorizedOutputEnabled()),\n        m_filepath(filepath),\n        m_line(line),\n        m_function_name(function_name),\n        m_expression(expression) {\n    const std::size_t path_length = m_filepath.length();\n    if (path_length > MAX_PATH_LENGTH) {\n      m_filepath = \"..\" + m_filepath.substr(path_length - MAX_PATH_LENGTH,\n                                            MAX_PATH_LENGTH);\n    }\n  }\n\n  template <typename T>\n  T&& print(const std::string& type, T&& value) const {\n    const T& ref = value;\n    std::stringstream stream_value;\n    const bool print_expr_and_type = pretty_print(stream_value, ref);\n\n    std::stringstream output;\n    output << ansi(ANSI_DEBUG) << \"[\" << m_filepath << \":\" << m_line << \" (\"\n           << m_function_name << \")] \" << ansi(ANSI_RESET);\n    if (print_expr_and_type) {\n      output << ansi(ANSI_EXPRESSION) << m_expression << ansi(ANSI_RESET)\n             << \" = \";\n    }\n    output << ansi(ANSI_VALUE) << stream_value.str() << ansi(ANSI_RESET);\n    if (print_expr_and_type) {\n      output << \" (\" << ansi(ANSI_TYPE) << type << ansi(ANSI_RESET) << \")\";\n    }\n    output << std::endl;\n    std::cerr << output.str();\n\n    return std::forward<T>(value);\n  }\n\n private:\n  const char* ansi(const char* code) const {\n    if (m_use_colorized_output) {\n      return code;\n    } else {\n      return ANSI_EMPTY;\n    }\n  }\n\n  const bool m_use_colorized_output;\n\n  std::string m_filepath;\n  const int m_line;\n  const std::string m_function_name;\n  const std::string m_expression;\n\n  static constexpr std::size_t MAX_PATH_LENGTH = 20;\n\n  static constexpr const char* const ANSI_EMPTY = \"\";\n  static constexpr const char* const ANSI_DEBUG = \"\\x1b[02m\";\n  static constexpr const char* const ANSI_EXPRESSION = \"\\x1b[36m\";\n  static constexpr const char* const ANSI_VALUE = \"\\x1b[01m\";\n  static constexpr const char* const ANSI_TYPE = \"\\x1b[32m\";\n  static constexpr const char* const ANSI_RESET = \"\\x1b[0m\";\n};\n\n// Identity function to suppress \"-Wunused-value\" warnings in DBG_MACRO_DISABLE\n// mode\ntemplate <typename T>\nT&& identity(T&& t) {\n  return std::forward<T>(t);\n}\n\n}  // namespace dbg_macro\n\n#ifndef DBG_MACRO_DISABLE\n// We use a variadic macro to support commas inside expressions (e.g.\n// initializer lists):\n#define DBG(...)                                                     \\\n  dbg_macro::DebugOutput(__FILE__, __LINE__, __func__, #__VA_ARGS__) \\\n      .print(dbg_macro::type_name<decltype(__VA_ARGS__)>(), (__VA_ARGS__))\n#else\n#define DBG(...) dbg_macro::identity(__VA_ARGS__)\n#endif  // DBG_MACRO_DISABLE\n\n#endif  // DBG_MACRO_DBG_H\n"
  },
  {
    "path": "common/Definitions.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Definitions.hh\n//! @author Elvin Sindrilaru CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n//------------------------------------------------------------------------------\n//! Extension to the persmission capabilities which are shared between the\n//! MGM and the fuse clients\n//------------------------------------------------------------------------------\n#define D_OK     8 ///< Flag for delete persmission\n#define M_OK    16 ///< Flag for chmod permission\n#define C_OK    32 ///< Flag for chown permission\n#define SA_OK   64 ///< Flag for set xattr permission\n#define U_OK   128 ///< Flag for update permission\n#define SU_OK  256 ///< Flag for utime permission\n#define P_OK   512 ///< Flag for workflow/prepare permission\n#define T_OK  1024 ///< Flag for token issuing permission\n"
  },
  {
    "path": "common/EosLayoutPrint.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file EosLayoutPrint.cc\n//! @author Elvin-Alin Sindrilaru - CERN\n//! @brief Tool print in human readable format the layout information\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/LayoutId.hh\"\n#include <ostream>\n\nint main(int argc, char* argv[])\n{\n  using eos::common::LayoutId;\n\n  if (argc != 2) {\n    std::cerr << \"Usage: \" << argv[0] << \" <lid_value>\" << std::endl;\n    return -1;\n  }\n\n  uint64_t lid {0ull};\n  std::string slid = argv[1];\n\n  try {\n    lid = std::stoull(slid, 0, 16);\n  } catch (...) {\n    std::cerr << \"error: failed to convert give layout id\" << std::endl;\n    return -1;\n  }\n\n  std::cout << \"Layout type:        \" << LayoutId::GetLayoutTypeString(\n              lid) << std::endl\n            << \"Checksum type:      \" << LayoutId::GetChecksumString(LayoutId::GetChecksum(\n                  lid)) << std::endl\n            << \"Block checksum:     \" << LayoutId::GetBlockChecksumString(lid) << std::endl\n            << \"Block size:         \" << LayoutId::GetBlockSizeString(lid) << std::endl\n            << \"Total stripes:      \" << LayoutId::GetStripeNumberString(lid) << std::endl\n            << \"Redundancy stripes: \" << LayoutId::GetRedundancyStripeNumber(lid)\n            << std::endl;\n  return 0;\n}\n"
  },
  {
    "path": "common/ErrnoToString.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file Strerror_wrapper.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Strerror_r_wrapper.hh\"\n#include \"common/ErrnoToString.hh\"\n#include <sstream>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Convert error number to string representation\n//------------------------------------------------------------------------------\nstd::string ErrnoToString(int errnum)\n{\n  char buf[128];\n\n  if (!strerror_r(errnum, buf, sizeof(buf))) {\n    return std::string{buf};\n  } else {\n    const int errno_wrapper = errno;\n    std::ostringstream oss;\n\n    switch (errno_wrapper) {\n    case EINVAL:\n      oss << \"Failed to convert errnum to string: Invalid errnum\"\n          \": errnoValue=\" << errnum;\n      break;\n\n    case ERANGE:\n      oss << \"Failed to convert errnoValue to string\"\n          \": Destination buffer for error string is too small\"\n          \": errnum=\" << errnum;\n      break;\n\n    default:\n      oss << \"Failed to convert errnum to string\"\n          \": strerror_r_wrapper failed in an unknown way\"\n          \": errnum=\" << errnum;\n      break;\n    }\n\n    return oss.str();\n  }\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/ErrnoToString.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Strerror_wrapper.hh\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include <string>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Convert error number to string representation\n//!\n//! @param errnum error number\n//!\n//! @return string representation\n//------------------------------------------------------------------------------\nstd::string ErrnoToString(int errnum);\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/ExpiryCache.hh",
    "content": "// ----------------------------------------------------------------------\n// File: common/ExpiryCache.cc\n// Author: Jozsef Makai - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_EXPIRYCACHE_HH\n#define EOS_EXPIRYCACHE_HH\n\n#include \"common/Namespace.hh\"\n#include \"common/RWMutex.hh\"\n#include <chrono>\n#include <future>\n\nEOSCOMMONNAMESPACE_BEGIN\n\nclass UpdateException : public std::runtime_error {\npublic:\n  explicit UpdateException(const std::string& message) : runtime_error(message) {};\n};\n\n//! @brief Configurable cache for a single object with an expiry time frame. It works with expiry and invalidity time frames.\n//!        Before expiry, the data in cache is not updated and request is served from cache instantly.\n//!        After expiry and before invalidity, data is served from cache instantly and asynchronous update is issued.\n//!        After invalidity (or in case of a forced update), update is synchronous, and client waits for it to happen.\n//! @tparam T type of the object to be stored in the cache\ntemplate <typename T>\nclass ExpiryCache {\nprivate:\n  eos::common::RWMutex mObjectLock;\n  eos::common::RWMutex mUpdatePromiseLock;\n  std::atomic_bool mIsUpdatePending{false};\n  std::atomic<std::chrono::seconds> mExpiredAfter;\n  std::atomic<std::chrono::seconds> mInvalidAfter;\n  std::chrono::time_point<std::chrono::steady_clock> mUpdatedAt = std::chrono::steady_clock::now();\n\n  std::shared_future<void> mUpdateFuture;\n  std::unique_ptr<T> mCachedObject = nullptr;\n\n  //! @brief Check if the contained data needs an update and became invalid\n  //! @param forceUpdate forced update is needed ignoring the state of the cache, data is invalidated\n  //! @param isInvalidRet return value to tell whether the data is invalid\n  //! @return whether update is needed or not\n  bool IsUpdateNeeded(bool forceUpdate, bool& isInvalidRet) {\n    bool isInvalid = false;\n    std::chrono::seconds elapsedSinceUpdate;\n    {\n      eos::common::RWMutexReadLock lock(mObjectLock);\n      auto now = std::chrono::steady_clock::now();\n      elapsedSinceUpdate = std::chrono::duration_cast<std::chrono::seconds>(now - mUpdatedAt);\n      isInvalid = !mCachedObject || forceUpdate || elapsedSinceUpdate >= mInvalidAfter.load();\n      isInvalidRet = isInvalid;\n    }\n\n    if(isInvalid || elapsedSinceUpdate >= mExpiredAfter.load()) {\n      if(mIsUpdatePending) {\n        return false;\n      }\n\n      return true;\n    }\n\n    return false;\n  }\n\npublic:\n  //! @brief Construct a cache object\n  //! @param expiredAfter expiry time, after this data is served from cache instantly and asynchronous update is issued\n  //! @param invalidAfter invalidity time, after this the data is no longer served from cache, the client will wait for a synchronous update, never by default\n  explicit ExpiryCache(std::chrono::seconds expiredAfter, std::chrono::seconds invalidAfter = std::chrono::seconds::max())\n    : mExpiredAfter(expiredAfter), mInvalidAfter(invalidAfter > expiredAfter ? invalidAfter : std::chrono::seconds::max())\n  {}\n\n  //! @brief Set the expiry time, only changed if expiry < invalidity relation remains\n  //! @param expiredAfter expiry time\n  void SetExpiredAfter(std::chrono::seconds expiredAfter) {\n    if (mInvalidAfter.load() > expiredAfter)\n      mExpiredAfter.store(expiredAfter);\n  }\n\n  //! @brief Set the invalidity time, only changed if expiry < invalidity relation remains\n  //! @param invalidAfter invalidity time\n  void SetInvalidAfter(std::chrono::seconds invalidAfter) {\n    if (invalidAfter > mExpiredAfter.load())\n      mInvalidAfter.store(invalidAfter);\n  }\n\n  //! @brief Request for the cached data.\n  //! @tparam Functor type of the updating function object\n  //! @tparam ARGS variadic template type for the arguments\n  //! @param forceUpdate tells whether it should be a forced update\n  //! @param produceObject function object to call for an update, it has to return a pointer to the new object\n  //! @param params variadic parameters, will be perfect forwarded to the updating function\n  //! @return the object in the cache\n  template<typename Functor, typename... ARGS>\n  typename std::remove_reference<T>::type getCachedObject(bool forceUpdate, Functor&& produceObject, ARGS&&... params) {\n    bool isInvalid = false;\n    if (IsUpdateNeeded(forceUpdate, isInvalid)) {\n      eos::common::RWMutexWriteLock promiseLock(mUpdatePromiseLock);\n      if (IsUpdateNeeded(forceUpdate, isInvalid)) {\n        mIsUpdatePending = true;\n        mUpdateFuture = std::async(\n          isInvalid ? std::launch::deferred : std::launch::async,\n          [&](ARGS&& ... params) {\n            try {\n              T* updatedObject = produceObject(std::forward<ARGS>(params)...);\n              if (updatedObject != nullptr) {\n                eos::common::RWMutexWriteLock lock(mObjectLock);\n                mCachedObject.reset(updatedObject);\n                mUpdatedAt = std::chrono::steady_clock::now();\n              }\n            } catch (...) {}\n            mIsUpdatePending = false;\n          },\n          std::forward<ARGS>(params)...\n        );\n      }\n    }\n\n    if(isInvalid) {\n      eos::common::RWMutexReadLock promiseLock(mUpdatePromiseLock);\n      mUpdateFuture.get();\n    }\n    eos::common::RWMutexReadLock lock(mObjectLock);\n    return mCachedObject ? *mCachedObject : throw UpdateException(\"Could not update the data, no valid data is present.\");\n  }\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif //EOS_EXPIRYCACHE_HH\n"
  },
  {
    "path": "common/FileId.hh",
    "content": "// ----------------------------------------------------------------------\n// File: FileId.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <chrono>\n#include <cmath>\n#include <cstring>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class to handle file IDs.\n//! Provides conversion functions from/to hex representation and to build path\n//! names from fids and prefixes\n//------------------------------------------------------------------------------\nclass FileId\n{\npublic:\n  typedef unsigned long long fileid_t;\n\n  //----------------------------------------------------------------------------\n  //! Convert a fid into a hexadecimal string\n  //----------------------------------------------------------------------------\n  static std::string Fid2Hex(unsigned long long fid)\n  {\n    char hexbuffer[128];\n    sprintf(hexbuffer, \"%08llx\", fid);\n    return std::string(hexbuffer);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Convert a hex decimal string into a fid\n  //----------------------------------------------------------------------------\n  static unsigned long long Hex2Fid(const char* hexstring)\n  {\n    if (hexstring && strlen(hexstring)) {\n      return strtoll(hexstring, 0, 16);\n    } else {\n      return 0;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Determine which inode encoding to use\n  //----------------------------------------------------------------------------\n  static bool useNewInodes()\n  {\n    static bool initialized = false;\n    static bool useNew = false;\n\n    if (initialized) {\n      return useNew;\n    }\n\n    useNew = getenv(\"EOS_USE_NEW_INODES\") != nullptr &&\n             getenv(\"EOS_USE_NEW_INODES\")[0] == '1';\n    initialized = true;\n    return useNew;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Convert an EOS file id into an inode number. Currently dispatches to the\n  //! legacy implementation.\n  //----------------------------------------------------------------------------\n  static unsigned long long FidToInode(unsigned long long fid)\n  {\n    if (useNewInodes()) {\n      return NewFidToInode(fid);\n    }\n\n    return LegacyFidToInode(fid);\n  }\n\n  static unsigned long long InodeToFid(unsigned long long ino)\n  {\n    if (NewIsFileInode(ino)) {\n      return NewInodeToFid(ino);\n    }\n\n    return LegacyInodeToFid(ino);\n  }\n\n  static bool IsFileInode(unsigned long long ino)\n  {\n    if (useNewInodes()) {\n      return NewIsFileInode(ino);\n    }\n\n    return LegacyIsFileInode(ino);\n  }\n\n  //----------------------------------------------------------------------------\n  //! New encoding - convert an EOS file id into an inode number.\n  //! We mark the last bit with \"1\" for files, and \"0\" for containers.\n  //----------------------------------------------------------------------------\n  static constexpr unsigned long long kLastBitSet = (1ull << 63);\n\n  static unsigned long long NewFidToInode(unsigned long long fid)\n  {\n    return fid | kLastBitSet;\n  }\n\n  static unsigned long long NewInodeToFid(unsigned long long ino)\n  {\n    return ino & (~kLastBitSet);\n  }\n\n  static bool NewIsFileInode(unsigned long long ino)\n  {\n    return (ino & kLastBitSet) != 0ull;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Legacy encoding - convert an EOS file id into an inode number - we shift\n  //! the range by 28 bytes to not overlap with directory inodes.\n  //----------------------------------------------------------------------------\n  static unsigned long long LegacyFidToInode(unsigned long long fid)\n  {\n    return (fid << 28);\n  }\n\n  static unsigned long long LegacyInodeToFid(unsigned long long ino)\n  {\n    return (ino >> 28);\n  }\n\n  static bool LegacyIsFileInode(unsigned long long ino)\n  {\n    return (ino >= (1 << 28));\n  }\n\n  //----------------------------------------------------------------------------\n  //! Compute a path from a fid and localprefix\n  //----------------------------------------------------------------------------\n  static std::string FidPrefix2FullPath(const char* hexstring,\n                                        const char* localprefix)\n  {\n    std::string fullpath;\n\n    if (!hexstring || !localprefix) {\n      return fullpath;\n    }\n\n    unsigned long long fid = Hex2Fid(hexstring);\n    std::string slocalprefix = localprefix;\n\n    if (*slocalprefix.rbegin() != '/') {\n      slocalprefix += \"/\";\n    }\n\n    char sfullpath[16384];\n    sprintf(sfullpath, \"%s%08llx/%s\", slocalprefix.c_str(), fid / 10000, hexstring);\n    fullpath = sfullpath;\n    return fullpath;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Compute a fid from a prefix path\n  //----------------------------------------------------------------------------\n  static unsigned long long PathToFid(const char* path)\n  {\n    XrdOucString hexfid = \"\";\n    hexfid = path;\n    int rpos = hexfid.rfind(\"/\");\n\n    if (rpos > 0) {\n      hexfid.erase(0, rpos + 1);\n    }\n\n    return Hex2Fid(hexfid.c_str());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Estimate TCP transfer timeout based on file size but not shorter than\n  //! 30 min.\n  //!\n  //! @param fsize file size\n  //! @param min_tx average transfer speed in MB/s, default 25 MB/s,\n  //!        if 0 then return the default XRootD 30 min timeout\n  //!\n  //! @return timeout value in seconds\n  //----------------------------------------------------------------------------\n  static std::chrono::seconds\n  EstimateTpcTimeout(const uint64_t fsize, uint64_t min_tx = 25)\n  {\n    const uint64_t default_timeout = 1800;\n    uint64_t timeout = default_timeout;\n\n    if (min_tx) {\n      timeout = fsize / (min_tx * std::pow(2, 20));\n\n      if (timeout < default_timeout) {\n        timeout = default_timeout;\n      }\n    }\n\n    return std::chrono::seconds(timeout);\n  }\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/FileMap.hh",
    "content": "// ----------------------------------------------------------------------\n// File: FileMap.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   FileMap.hh\n *\n * @brief  Class storing a key value map in a append blob\n *\n *\n */\n\n#ifndef __EOSCOMMON_FILEMAP_HH__\n#define __EOSCOMMON_FILEMAP_HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/Namespace.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/StringConversion.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdSys/XrdSysPthread.hh>\n/*----------------------------------------------------------------------------*/\n#include <string>\n#include <map>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\n//! Class storing a key-value map in a append log\n//!\n//! The format of the blob is\n//! when setting a key:\n//! '+ base64(key) base64(value)\\n'\n//! when deleting a key:\n//! '- base64(key) base64(\":\")\\n'\n/*----------------------------------------------------------------------------*/\nclass FileMap\n{\nprivate:\n  std::map<std::string, std::string> mMap;\n  XrdSysMutex mMutex;\n\npublic:\n\n  // ---------------------------------------------------------------------------\n  //! Constructor\n  // ---------------------------------------------------------------------------\n\n  FileMap()\n  {\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Destructor\n  // ---------------------------------------------------------------------------\n\n  ~FileMap()\n  {\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Delete a Key\n  // ---------------------------------------------------------------------------\n  bool Remove(std::string key)\n  {\n    XrdSysMutexHelper mLock(mMutex);\n\n    if (!mMap.count(key)) {\n      return false;\n    }\n\n    mMap.erase(key);\n    return true;\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Get Map\n  // ---------------------------------------------------------------------------\n  std::map<std::string, std::string> GetMap()\n  {\n    XrdSysMutexHelper mLock(mMutex);\n    return mMap;\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Set a Key-Val pair, returns append string\n  // ---------------------------------------------------------------------------\n\n  void Set(std::string key, std::string val)\n  {\n    XrdSysMutexHelper mLock(mMutex);\n    mMap[key] = val;\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Get a Key\n  // ---------------------------------------------------------------------------\n\n  std::string Get(std::string key)\n  {\n    XrdSysMutexHelper mLock(mMutex);\n\n    if (mMap.count(key)) {\n      return mMap[key];\n    }\n\n    return \"\";\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Delete a Key, returns append string\n  // ---------------------------------------------------------------------------\n\n  void Delete(std::string key)\n  {\n    XrdSysMutexHelper mLock(mMutex);\n    mMap.erase(key);\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Fill Map from file blob\n  // ---------------------------------------------------------------------------\n\n  bool Load(std::string blob)\n  {\n    XrdSysMutexHelper mLock(mMutex);\n    mMap.clear();\n    std::istringstream mapStream(blob);\n\n    if (!blob.length()) {\n      return true;\n    }\n\n    while (!mapStream.eof()) {\n      std::string line;\n      getline(mapStream, line);\n      std::vector<std::string> tokens;\n\n      if (!line.length()) {\n        continue;\n      }\n\n      eos::common::StringConversion::Tokenize(line,\n                                              tokens);\n\n      if (tokens.size() != 3) {\n        return false;\n      }\n\n      XrdOucString key64 = tokens[1].c_str();\n      XrdOucString val64 = tokens[2].c_str();\n      char* keyout = 0;\n      char* valout = 0;\n      ssize_t keyout_len = 0;\n      ssize_t valout_len = 0;\n      eos::common::SymKey::Base64Decode(key64, keyout, keyout_len);\n      eos::common::SymKey::Base64Decode(val64, valout, valout_len);\n\n      if (keyout && valout) {\n        std::string key;\n        std::string val;\n        key.assign(keyout, keyout_len);\n        val.assign(valout, valout_len);\n        mMap[key] = val;\n      }\n\n      if (keyout) {\n        free(keyout);\n      }\n\n      if (valout) {\n        free(valout);\n      }\n    }\n\n    return true;\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Return a trimmed blob\n  // ---------------------------------------------------------------------------\n\n  std::string Trim()\n  {\n    XrdSysMutexHelper mLock(mMutex);\n    std::string retval;\n\n    for (auto it = mMap.begin(); it != mMap.end(); ++it) {\n      XrdOucString key64;\n      XrdOucString val64;\n      eos::common::SymKey::Base64Encode((char*) it->first.c_str(), it->first.length(),\n                                        key64);\n      eos::common::SymKey::Base64Encode((char*) it->second.c_str(),\n                                        it->second.length(), val64);\n      std::string append_string = \"+ \";\n      append_string += key64.c_str();\n      append_string += \" \";\n      append_string += val64.c_str();\n      append_string += \"\\n\";\n      retval += append_string;\n    }\n\n    return retval;\n  }\n};\n/*----------------------------------------------------------------------------*/\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/FileSystem.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FileSystem.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Namespace.hh\"\n#include \"common/FileSystem.hh\"\n#include \"common/Logging.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/ParseUtils.hh\"\n#include \"common/Assert.hh\"\n#include \"common/Constants.hh\"\n#include \"common/ParseUtils.hh\"\n#include \"common/StringConversion.hh\"\n#include \"mq/MessagingRealm.hh\"\n#include <list>\n\nEOSCOMMONNAMESPACE_BEGIN;\n\n//------------------------------------------------------------------------------\n// Contains a batch of key-value updates on the attributes of a filesystem.\n//------------------------------------------------------------------------------\nFileSystemUpdateBatch::FileSystemUpdateBatch() {}\n\n//------------------------------------------------------------------------------\n// Set filesystem ID - durable.\n//------------------------------------------------------------------------------\nvoid FileSystemUpdateBatch::setId(fsid_t fsid)\n{\n  setLongLongDurable(\"id\", fsid);\n}\n\n//----------------------------------------------------------------------------\n// Set the draining status - local.\n//----------------------------------------------------------------------------\nvoid FileSystemUpdateBatch::setDrainStatusLocal(DrainStatus status)\n{\n  setStringLocal(\"local.drain\", FileSystem::GetDrainStatusAsString(status));\n}\n\n//------------------------------------------------------------------------------\n// Set durable string.\n//\n// All observers of this filesystem are guaranteed to receive the update\n// eventually, and all updates are guaranteed to be applied in the same\n// order for all observers.\n//------------------------------------------------------------------------------\nvoid FileSystemUpdateBatch::setStringDurable(const std::string& key,\n    const std::string& value)\n{\n  mBatch.SetDurable(key, value);\n}\n\n//------------------------------------------------------------------------------\n// Set transient string. Depending on network instabilities,\n// process restarts, or the phase of the moon, some or all observers of this\n// filesystem may or may not receive the update.\n//\n// Transient updates may be applied out of order, and if multiple subscribers\n// try to modify the same value, it's possible that observers will not all\n// converge on a single consistent value.\n//------------------------------------------------------------------------------\nvoid FileSystemUpdateBatch::setStringTransient(const std::string& key,\n    const std::string& value)\n{\n  mBatch.SetTransient(key, value);\n}\n\n//------------------------------------------------------------------------------\n// Set local string. This node, and only this node will store the value,\n// and only until process restart.\n//------------------------------------------------------------------------------\nvoid FileSystemUpdateBatch::setStringLocal(const std::string& key,\n    const std::string& value)\n{\n  mBatch.SetLocal(key, value);\n}\n\n//------------------------------------------------------------------------------\n// Set durable int64_t - serialize as string automatically.\n//------------------------------------------------------------------------------\nvoid FileSystemUpdateBatch::setLongLongDurable(const std::string& key,\n    int64_t value)\n{\n  return setStringDurable(key, std::to_string(value));\n}\n\n//------------------------------------------------------------------------------\n// Set transient int64_t - serialize as string automatically.\n//------------------------------------------------------------------------------\nvoid FileSystemUpdateBatch::setLongLongTransient(const std::string& key,\n    int64_t value)\n{\n  return setStringTransient(key, std::to_string(value));\n}\n\n//------------------------------------------------------------------------------\n// Set local int64_t - serialize as string automatically.\n//------------------------------------------------------------------------------\nvoid FileSystemUpdateBatch::setLongLongLocal(const std::string& key,\n    int64_t value)\n{\n  return setStringLocal(key, std::to_string(value));\n}\n\n//------------------------------------------------------------------------------\n// Get durable updates map\n//------------------------------------------------------------------------------\nconst mq::SharedHashWrapper::Batch&\nFileSystemUpdateBatch::getBatch() const\n{\n  return mBatch;\n}\n\n//------------------------------------------------------------------------------\n// Empty constructor\n//------------------------------------------------------------------------------\nFstLocator::FstLocator() {}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFstLocator::FstLocator(const std::string& host, int port)\n  : mHost(host), mPort(port) {}\n\n//------------------------------------------------------------------------------\n// Get host\n//------------------------------------------------------------------------------\nstd::string FstLocator::getHost() const\n{\n  return mHost;\n}\n\n//------------------------------------------------------------------------------\n// Get port\n//------------------------------------------------------------------------------\nint FstLocator::getPort() const\n{\n  return mPort;\n}\n\n//------------------------------------------------------------------------------\n// Get fst queuepath\n//------------------------------------------------------------------------------\nstd::string FstLocator::getQueuePath() const\n{\n  return SSTR(\"/eos/\" << mHost << \":\" << mPort << \"/fst\");\n}\n\n//------------------------------------------------------------------------------\n// Get host:port\n//------------------------------------------------------------------------------\nstd::string FstLocator::getHostPort() const\n{\n  return SSTR(mHost << \":\" << mPort);\n}\n\n//------------------------------------------------------------------------------\n// Try to parse from queuepath\n//------------------------------------------------------------------------------\nbool FstLocator::fromQueuePath(const std::string& queuepath, FstLocator& out)\n{\n  std::string queue = queuepath;\n\n  if (!startsWith(queue, \"/eos/\")) {\n    return false;\n  }\n\n  queue.erase(0, 5);\n  //----------------------------------------------------------------------------\n  // Chop /eos/, extract host+port\n  //----------------------------------------------------------------------------\n  size_t slashLocation = queue.find(\"/\");\n\n  if (slashLocation == std::string::npos) {\n    return false;\n  }\n\n  std::string hostPort = std::string(queue.begin(),\n                                     queue.begin() + slashLocation);\n  queue.erase(0, slashLocation);\n  //----------------------------------------------------------------------------\n  // Separate host from port\n  //----------------------------------------------------------------------------\n  size_t separator = hostPort.find(\":\");\n\n  if (separator == std::string::npos) {\n    return false;\n  }\n\n  out.mHost = std::string(hostPort.begin(), hostPort.begin() + separator);\n  hostPort.erase(0, separator + 1);\n  int64_t port;\n\n  if (!ParseInt64(hostPort, port)) {\n    return false;\n  }\n\n  out.mPort = port;\n\n  //----------------------------------------------------------------------------\n  // Chop \"/fst/\"\n  //----------------------------------------------------------------------------\n  if (queue != \"/fst\") {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nGroupLocator::GroupLocator() {}\n\n//------------------------------------------------------------------------------\n// Get group (space.index)\n//------------------------------------------------------------------------------\nstd::string GroupLocator::getGroup() const\n{\n  return mGroup;\n}\n\n//------------------------------------------------------------------------------\n// Get space\n//------------------------------------------------------------------------------\nstd::string GroupLocator::getSpace() const\n{\n  return mSpace;\n}\n\n//------------------------------------------------------------------------------\n// Get index\n//------------------------------------------------------------------------------\nint GroupLocator::getIndex() const\n{\n  return mIndex;\n}\n\n//------------------------------------------------------------------------------\n// Parse full group (space.index)\n//\n// NOTE: In case parsing fails, out will still be filled\n// with \"description.0\" to match legacy behaviour.\n//------------------------------------------------------------------------------\nbool GroupLocator::parseGroup(const std::string& description,\n                              GroupLocator& out)\n{\n  size_t dot = description.find(\".\");\n\n  if (dot == std::string::npos) {\n    out.mGroup = description;\n    out.mSpace = description;\n    out.mIndex = 0;\n\n    if (description != eos::common::EOS_SPARE_GROUP) {\n      eos_static_notice(\"Unable to parse group: %s, assuming index is zero\",\n                        description.c_str());\n      return false;\n    }\n\n    return true;\n  }\n\n  out.mGroup = description;\n  out.mSpace = std::string(description.c_str(), dot);\n  std::string index = std::string(description.begin() + dot + 1,\n                                  description.end());\n  int64_t idx;\n\n  if (!ParseInt64(index, idx)) {\n    eos_static_crit(\"Could not parse integer index in group: %s\",\n                    description.c_str());\n    out.mIndex = 0;\n    return false;\n  }\n\n  out.mIndex = idx;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFileSystemCoreParams::FileSystemCoreParams(uint32_t id,\n    const FileSystemLocator& fsLocator, const GroupLocator& grpLocator,\n    const std::string& uuid, ConfigStatus cfg, const std::string& sharedfs)\n  : mFsId(id), mLocator(fsLocator), mGroup(grpLocator), mUuid(uuid),\n    mConfigStatus(cfg), mSharedFs(sharedfs) {}\n\n//------------------------------------------------------------------------------\n// Get locator\n//------------------------------------------------------------------------------\nconst FileSystemLocator& FileSystemCoreParams::getLocator() const\n{\n  return mLocator;\n}\n\n//------------------------------------------------------------------------------\n//! Get group locator\n//------------------------------------------------------------------------------\nconst GroupLocator& FileSystemCoreParams::getGroupLocator() const\n{\n  return mGroup;\n}\n\n//------------------------------------------------------------------------------\n//! Get id\n//------------------------------------------------------------------------------\nuint32_t FileSystemCoreParams::getId() const\n{\n  return mFsId;\n}\n\n//------------------------------------------------------------------------------\n//! Get uuid\n//------------------------------------------------------------------------------\nstd::string FileSystemCoreParams::getUuid() const\n{\n  return mUuid;\n}\n\n//------------------------------------------------------------------------------\n// Get SharedFs  name\n//------------------------------------------------------------------------------\nstd::string FileSystemCoreParams::getSharedFs() const\n{\n  return mSharedFs;\n}\n\n//------------------------------------------------------------------------------\n// Get current ConfigStatus\n//------------------------------------------------------------------------------\nConfigStatus FileSystemCoreParams::getConfigStatus() const\n{\n  return mConfigStatus;\n}\n\n//------------------------------------------------------------------------------\n// Get queuepath\n//------------------------------------------------------------------------------\nstd::string FileSystemCoreParams::getQueuePath() const\n{\n  return mLocator.getQueuePath();\n}\n\n//------------------------------------------------------------------------------\n// Get host\n//------------------------------------------------------------------------------\nstd::string FileSystemCoreParams::getHost() const\n{\n  return mLocator.getHost();\n}\n\n//------------------------------------------------------------------------------\n// Get hostport\n//------------------------------------------------------------------------------\nstd::string FileSystemCoreParams::getHostPort() const\n{\n  return mLocator.getHostPort();\n}\n\n//------------------------------------------------------------------------------\n// Get FST queue\n//------------------------------------------------------------------------------\nstd::string FileSystemCoreParams::getFSTQueue() const\n{\n  return mLocator.getFSTQueue();\n}\n\n//------------------------------------------------------------------------------\n// Get group (space.index)\n//------------------------------------------------------------------------------\nstd::string FileSystemCoreParams::getGroup() const\n{\n  return mGroup.getGroup();\n}\n\n//------------------------------------------------------------------------------\n// Get space\n//------------------------------------------------------------------------------\nstd::string FileSystemCoreParams::getSpace() const\n{\n  return mGroup.getSpace();\n}\n\n//------------------------------------------------------------------------------\n//! Default constructor for fs_snapshot_t\n//------------------------------------------------------------------------------\nFileSystem::fs_snapshot_t::fs_snapshot_t()\n{\n  mId = 0;\n  mQueue = \"\";\n  mQueuePath = \"\";\n  mGroup = \"\";\n  mPath = \"\";\n  mUuid = \"\";\n  mHost = \"\";\n  mHostPort = \"\";\n  mProxyGroup = \"\";\n  mS3Credentials = \"\";\n  mFileStickyProxyDepth = -1;\n  mPort = 0;\n  mErrMsg = \"\";\n  mGeoTag = \"\";\n  mPublishTimestamp = 0;\n  mStatus = BootStatus::kDown;\n  mConfigStatus = ConfigStatus::kOff;\n  mDrainStatus = DrainStatus::kNoDrain;\n  mHeadRoom = 0;\n  mErrCode = 0;\n  mBootSentTime = 0;\n  mBootDoneTime = 0;\n  mDiskUtilization = 0;\n  mNetEthRateMiB = 0;\n  mNetInRateMiB = 0;\n  mNetOutRateMiB = 0;\n  mDiskWriteRateMb = 0;\n  mDiskReadRateMb = 0;\n  mDiskType = 0;\n  mDiskBsize = 0;\n  mDiskBlocks = 0;\n  mDiskBfree = 0;\n  mDiskBused = 0;\n  mDiskBavail = 0;\n  mDiskFiles = 0;\n  mDiskFfree = 0;\n  mDiskFused = 0;\n  mFiles = 0;\n  mDiskNameLen = 0;\n  mDiskRopen = 0;\n  mDiskWopen = 0;\n  mMaxDiskRopen = 0;\n  mMaxDiskWopen = 0;\n  mScanIoRate = 0;\n  mScanEntryInterval = 0;\n  mScanRainEntryInterval = 0;\n  mScanDiskInterval = 0;\n  mScanNsInterval = 0;\n  mScanNsRate = 0;\n}\n\n//------------------------------------------------------------------------------\n// \"Absorb\" all information contained within coreParams into this object.\n// Fields which are not present in coreParams (ie mNetInRateMiB) remain\n// unchanged.\n//------------------------------------------------------------------------------\nvoid FileSystem::fs_snapshot_t::fillFromCoreParams(const FileSystemCoreParams&\n    coreParams)\n{\n  mId = coreParams.getId();\n  mQueue = coreParams.getFSTQueue();\n  mQueuePath = coreParams.getQueuePath();\n  mGroup = coreParams.getGroup();\n  mPath = coreParams.getLocator().getStoragePath();\n  mUuid = coreParams.getUuid();\n  mHost = coreParams.getHost();\n  mHostPort = coreParams.getHostPort();\n  mPort = coreParams.getLocator().getPort();\n  mConfigStatus = coreParams.getConfigStatus();\n  mSharedFs = coreParams.getSharedFs();\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFileSystem::FileSystem(const FileSystemLocator& locator,\n                       mq::MessagingRealm* realm, bool bc2mgm)\n  : mLocator(locator), mHashLocator(locator, bc2mgm),\n    mRealm(realm), mActStatus(ActiveStatus::kOffline)\n{\n  mInternalBootStatus = BootStatus::kDown;\n  cStatus = BootStatus::kDown;\n  cConfigStatus = ConfigStatus::kOff;\n  cStatusTime = 0;\n  cConfigTime = 0;\n\n  if (mRealm) {\n    mq::SharedHashWrapper::Batch upd_batch;\n    upd_batch.SetDurable(\"queue\", mLocator.getFSTQueue());\n    upd_batch.SetDurable(\"queuepath\", mLocator.getQueuePath());\n    upd_batch.SetDurable(\"path\", mLocator.getStoragePath());\n    upd_batch.SetDurable(\"hostport\", locator.getHostPort());\n    upd_batch.SetDurable(\"host\", locator.getHost());\n    upd_batch.SetDurable(\"port\", std::to_string(locator.getPort()));\n    upd_batch.SetLocal(\"local.drain\", \"nodrain\");\n    mq::SharedHashWrapper(mRealm, mHashLocator).set(upd_batch);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Delete shared hash object corresponding to this file system\n//------------------------------------------------------------------------------\nvoid FileSystem::DeleteSharedHash(bool delete_from_qdb)\n{\n  mq::SharedHashWrapper::deleteHash(mRealm, mHashLocator, delete_from_qdb);\n}\n\n//------------------------------------------------------------------------------\n// Get underlying hash locator\n//------------------------------------------------------------------------------\nSharedHashLocator FileSystem::getHashLocator() const\n{\n  return mHashLocator;\n}\n\n//------------------------------------------------------------------------------\n// Return the given status as a string\n//------------------------------------------------------------------------------\nconst char*\nFileSystem::GetStatusAsString(BootStatus status)\n{\n  if (status == BootStatus::kDown) {\n    return \"down\";\n  }\n\n  if (status == BootStatus::kOpsError) {\n    return \"opserror\";\n  }\n\n  if (status == BootStatus::kBootFailure) {\n    return \"bootfailure\";\n  }\n\n  if (status == BootStatus::kBootSent) {\n    return \"bootsent\";\n  }\n\n  if (status == BootStatus::kBooting) {\n    return \"booting\";\n  }\n\n  if (status == BootStatus::kBooted) {\n    return \"booted\";\n  }\n\n  return \"unknown\";\n}\n\n//------------------------------------------------------------------------------\n// Return given drain status as a string\n//------------------------------------------------------------------------------\nconst char*\nFileSystem::GetDrainStatusAsString(DrainStatus status)\n{\n  if (status == DrainStatus::kNoDrain) {\n    return \"nodrain\";\n  }\n\n  if (status == DrainStatus::kDrainPrepare) {\n    return \"prepare\";\n  }\n\n  if (status == DrainStatus::kDrainWait) {\n    return \"waiting\";\n  }\n\n  if (status == DrainStatus::kDraining) {\n    return \"draining\";\n  }\n\n  if (status == DrainStatus::kDrained) {\n    return \"drained\";\n  }\n\n  if (status == DrainStatus::kDrainStalling) {\n    return \"stalling\";\n  }\n\n  if (status == DrainStatus::kDrainExpired) {\n    return \"expired\";\n  }\n\n  if (status == DrainStatus::kDrainFailed) {\n    return \"failed\";\n  }\n\n  return \"unknown\";\n}\n\n//------------------------------------------------------------------------------\n// Return given configuration status as a string\n//------------------------------------------------------------------------------\nconst char*\nFileSystem::GetConfigStatusAsString(ConfigStatus status)\n{\n  switch (status) {\n  case ConfigStatus::kUnknown: {\n    return \"unknown\";\n  }\n\n  case ConfigStatus::kOff: {\n    return \"off\";\n  }\n\n  case ConfigStatus::kEmpty: {\n    return \"empty\";\n  }\n\n  case ConfigStatus::kDrainDead: {\n    return \"draindead\";\n  }\n\n  case ConfigStatus::kGroupDrain: {\n    return \"groupdrain\";\n  }\n\n  case ConfigStatus::kDrain: {\n    return \"drain\";\n  }\n\n  case ConfigStatus::kRO: {\n    return \"ro\";\n  }\n\n  case ConfigStatus::kWO: {\n    return \"wo\";\n  }\n\n  case ConfigStatus::kRW: {\n    return \"rw\";\n  }\n\n  default: {\n    return \"unknown\";\n  }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Return given Active status as a string\n//------------------------------------------------------------------------------\nconst char*\nFileSystem::GetActiveStatusAsString(ActiveStatus status)\n{\n  if (status == ActiveStatus::kOnline) {\n    return \"online\";\n  } else if (status == ActiveStatus::kOffline) {\n    return \"offline\";\n  } else if (status == ActiveStatus::kOverload) {\n    return \"overload\";\n  } else {\n    return \"undef\";\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get the status from a string representation\n//------------------------------------------------------------------------------\nBootStatus\nFileSystem::GetStatusFromString(const char* ss)\n{\n  if (!ss) {\n    return BootStatus::kDown;\n  }\n\n  if (!strcmp(ss, \"down\")) {\n    return BootStatus::kDown;\n  }\n\n  if (!strcmp(ss, \"opserror\")) {\n    return BootStatus::kOpsError;\n  }\n\n  if (!strcmp(ss, \"bootfailure\")) {\n    return BootStatus::kBootFailure;\n  }\n\n  if (!strcmp(ss, \"bootsent\")) {\n    return BootStatus::kBootSent;\n  }\n\n  if (!strcmp(ss, \"booting\")) {\n    return BootStatus::kBooting;\n  }\n\n  if (!strcmp(ss, \"booted\")) {\n    return BootStatus::kBooted;\n  }\n\n  return BootStatus::kDown;\n}\n\n\n//------------------------------------------------------------------------------\n// Return configuration status from a string representation\n//------------------------------------------------------------------------------\nConfigStatus\nFileSystem::GetConfigStatusFromString(const char* ss)\n{\n  if (!ss) {\n    return ConfigStatus::kOff;\n  }\n\n  if (!strcmp(ss, \"unknown\")) {\n    return ConfigStatus::kUnknown;\n  }\n\n  if (!strcmp(ss, \"off\")) {\n    return ConfigStatus::kOff;\n  }\n\n  if (!strcmp(ss, \"empty\")) {\n    return ConfigStatus::kEmpty;\n  }\n\n  if (!strcmp(ss, \"draindead\")) {\n    return ConfigStatus::kDrainDead;\n  }\n\n  if (!strcmp(ss, \"drain\")) {\n    return ConfigStatus::kDrain;\n  }\n\n  if (!strcmp(ss, \"ro\")) {\n    return ConfigStatus::kRO;\n  }\n\n  if (!strcmp(ss, \"wo\")) {\n    return ConfigStatus::kWO;\n  }\n\n  if (!strcmp(ss, \"rw\")) {\n    return ConfigStatus::kRW;\n  }\n\n  if (!strcmp(ss, \"down\")) {\n    return ConfigStatus::kOff;\n  }\n\n  return ConfigStatus::kUnknown;\n}\n\n//------------------------------------------------------------------------------\n// Return drains status from string representation\n//------------------------------------------------------------------------------\nDrainStatus\nFileSystem::GetDrainStatusFromString(const char* ss)\n{\n  if (!ss) {\n    return DrainStatus::kNoDrain;\n  }\n\n  if (!strcmp(ss, \"nodrain\")) {\n    return DrainStatus::kNoDrain;\n  }\n\n  if (!strcmp(ss, \"prepare\")) {\n    return DrainStatus::kDrainPrepare;\n  }\n\n  if (!strcmp(ss, \"wait\")) {\n    return DrainStatus::kDrainWait;\n  }\n\n  if (!strcmp(ss, \"draining\")) {\n    return DrainStatus::kDraining;\n  }\n\n  if (!strcmp(ss, \"stalling\")) {\n    return DrainStatus::kDrainStalling;\n  }\n\n  if (!strcmp(ss, \"drained\")) {\n    return DrainStatus::kDrained;\n  }\n\n  if (!strcmp(ss, \"expired\")) {\n    return DrainStatus::kDrainExpired;\n  }\n\n  if (!strcmp(ss, \"failed\")) {\n    return DrainStatus::kDrainFailed;\n  }\n\n  return DrainStatus::kNoDrain;\n}\n\n//----------------------------------------------------------------------------\n//! Set the activation status\n//----------------------------------------------------------------------------\nvoid\nFileSystem::SetActiveStatus(ActiveStatus active)\n{\n  if (mActStatus != active) {\n    mActStatus = active;\n    SetString(\"stat.active\", GetActiveStatusAsString(mActStatus), false);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Apply the given batch of updates\n//------------------------------------------------------------------------------\nbool FileSystem::applyBatch(const FileSystemUpdateBatch& batch)\n{\n  return mq::SharedHashWrapper(mRealm, mHashLocator).set(batch.getBatch());\n}\n\n//------------------------------------------------------------------------------\n// Apply the given core parameters\n//------------------------------------------------------------------------------\nbool FileSystem::applyCoreParams(const FileSystemCoreParams& params)\n{\n  FileSystemUpdateBatch batch;\n  batch.setStringDurable(\"uuid\", params.getUuid());\n  batch.setStringDurable(\"schedgroup\", params.getGroupLocator().getGroup());\n  batch.setStringDurable(\"configstatus\",\n                         GetConfigStatusAsString(params.getConfigStatus()));\n  batch.setStringDurable(\"sharedfs\", params.getSharedFs());\n  batch.setId(params.getId());\n  return applyBatch(batch);\n}\n\n//------------------------------------------------------------------------------\n// Set a local long long\n//------------------------------------------------------------------------------\nbool FileSystem::SetLongLongLocal(const std::string& key, int64_t value)\n{\n  common::FileSystemUpdateBatch batch;\n  batch.setLongLongLocal(key, value);\n  return this->applyBatch(batch);\n}\n\n//------------------------------------------------------------------------------\n// Set a key-value pair in a filesystem and evt. broadcast it.\n//------------------------------------------------------------------------------\nbool FileSystem::SetString(const char* key, const char* str, bool broadcast)\n{\n  return mq::SharedHashWrapper(mRealm, mHashLocator).set(key, str, broadcast);\n}\n\n//------------------------------------------------------------------------------\n// Remove a key from a filesystem and evt. broadcast it.\n//------------------------------------------------------------------------------\nbool FileSystem::RemoveKey(const char* key, bool broadcast)\n{\n  return mq::SharedHashWrapper(mRealm, mHashLocator).del(key, broadcast);\n}\n\n//------------------------------------------------------------------------------\n// Get all keys in a vector of strings.\n//------------------------------------------------------------------------------\nbool FileSystem::GetKeys(std::vector<std::string>& keys)\n{\n  return mq::SharedHashWrapper(mRealm, mHashLocator).getKeys(keys);\n}\n\n//------------------------------------------------------------------------------\n// Get the string value by key\n//------------------------------------------------------------------------------\nstd::string FileSystem::GetString(const char* key)\n{\n  std::string skey = key;\n\n  if (skey == \"<n>\") {\n    return \"1\";\n  }\n\n  return mq::SharedHashWrapper(mRealm, mHashLocator).get(key);\n}\n\n//------------------------------------------------------------------------------\n// Get a long long value by key\n//------------------------------------------------------------------------------\nlong long FileSystem::GetLongLong(const char* key)\n{\n  return ParseLongLong(GetString(key));\n}\n\n//------------------------------------------------------------------------------\n// Get a double value by key\n//------------------------------------------------------------------------------\ndouble FileSystem::GetDouble(const char* key)\n{\n  return ParseDouble(GetString(key));\n}\n\n//--------------------------------------------------------------------------\n//! Get used bytes\n//--------------------------------------------------------------------------\nuint64_t\nFileSystem::GetUsedbytes()\n{\n  return GetLongLong(\"stat.statfs.usedbytes\");\n}\n\n//--------------------------------------------------------------------------\n//! Get space name\n//--------------------------------------------------------------------------\nstd::string\nFileSystem::GetSpace()\n{\n  return getCoreParams().getSpace();\n}\n\n//------------------------------------------------------------------------------\n// Get shared file system name\n//------------------------------------------------------------------------------\nstd::string FileSystem::getSharedFs()\n{\n  return getCoreParams().getSharedFs();\n}\n\n//------------------------------------------------------------------------------\n// Serializes hash contents as follows 'key1=val1 key2=val2 ... keyn=valn'\n// but return only keys that don't start with filter_prefix.\n//------------------------------------------------------------------------------\nstd::string\nFileSystem::SerializeWithFilter(const std::map<std::string, std::string>&\n                                contents,\n                                std::list<std::string> filter_prefixes)\n{\n  using eos::common::StringConversion;\n  std::string key;\n  std::string val;\n  std::ostringstream oss;\n  bool filter_out;\n  filter_prefixes.push_back(\"drainstatus\");\n  filter_prefixes.push_back(\"fsck_refresh_interval\");\n\n  for (auto it = contents.begin(); it != contents.end(); it++) {\n    key = it->first.c_str();\n    filter_out = false;\n\n    for (const auto& prefix : filter_prefixes) {\n      if (prefix.length() && (key.find(prefix) == 0)) {\n        filter_out = true;\n        break;\n      }\n    }\n\n    if (filter_out) {\n      eos_static_debug(\"msg=\\\"filter out\\\" key=\\\"%s\\\"\", key.c_str());\n    } else {\n      val = it->second;\n\n      if ((val[0] == '\"') && (val[val.length() - 1] == '\"')) {\n        std::string to_encode = val.substr(1, val.length() - 2);\n        std::string encoded = StringConversion::curl_default_escaped(to_encode);\n\n        if (!encoded.empty()) {\n          val = '\"';\n          val += encoded;\n          val += '\"';\n        }\n      }\n\n      oss << key << \"=\" << val.c_str() << \" \";\n    }\n  }\n\n  return oss.str();\n}\n\n//------------------------------------------------------------------------------\n// Print contents onto a table: Originally implemented in XrdMqSharedHash.\n//\n// Format contents of the hash map to be displayed using the table object.\n//\n// @param table_mq_header table header\n// @param talbe_md_data table data\n// @param format format has to be provided as a chain separated by \"|\" of\n//        the following tags\n// \"key=<key>:width=<width>:format=[+][-][slfo]:unit=<unit>:tag=<tag>:condition=<key>=<val>\"\n// -> to print a key of the attached children\n// \"sep=<seperator>\" -> to put a seperator\n// \"header=1\" -> to put a header with description on top - this must be the\n//               first format tag.\n// \"indent=<n>\" -> indent the output\n// The formats are:\n// 's' : print as string\n// 'S' : print as short string (truncated after .)\n// 'l' : print as long long\n// 'f' : print as double\n// 'o' : print as <key>=<val>\n// '-' : left align the printout\n// '+' : convert numbers into k,M,G,T,P ranges\n// The unit is appended to every number:\n// e.g. 1500 with unit=B would end up as '1.5 kB'\n// \"tag=<tag>\" -> use <tag> instead of the variable name to print the header\n// @param filter to filter out hash content\n//------------------------------------------------------------------------------\nstatic void printOntoTable(mq::SharedHashWrapper& hash,\n                           TableHeader& table_mq_header,\n                           TableData& table_mq_data, std::string format, const std::string& filter)\n{\n  using eos::common::StringConversion;\n  std::vector<std::string> formattoken;\n  StringConversion::Tokenize(format, formattoken, \"|\");\n  table_mq_data.emplace_back();\n\n  for (unsigned int i = 0; i < formattoken.size(); ++i) {\n    std::vector<std::string> tagtoken;\n    std::map<std::string, std::string> formattags;\n    StringConversion::Tokenize(formattoken[i], tagtoken, \":\");\n\n    for (unsigned int j = 0; j < tagtoken.size(); ++j) {\n      std::vector<std::string> keyval;\n      StringConversion::Tokenize(tagtoken[j], keyval, \"=\");\n\n      if (keyval.size() >= 2) {\n        formattags[keyval[0]] = keyval[1];\n      }\n    }\n\n    if (formattags.count(\"format\")) {\n      unsigned int width = atoi(formattags[\"width\"].c_str());\n      std::string format = formattags[\"format\"];\n      std::string unit = formattags[\"unit\"];\n\n      // Normal member printout\n      if (formattags.count(\"key\")) {\n        if (format.find(\"s\") != std::string::npos) {\n          table_mq_data.back().push_back(\n            TableCell(hash.get(formattags[\"key\"]), format));\n        }\n\n        if (format.find(\"S\") != std::string::npos) {\n          std::string shortstring = hash.get(formattags[\"key\"].c_str());\n          const size_t pos = shortstring.find(\".\");\n\n          if (pos != std::string::npos) {\n            shortstring.erase(pos);\n          }\n\n          table_mq_data.back().push_back(TableCell(shortstring, format));\n        }\n\n        if ((format.find(\"l\")) != std::string::npos) {\n          table_mq_data.back().push_back(\n            TableCell(hash.getLongLong(formattags[\"key\"].c_str()), format, unit));\n        }\n\n        if ((format.find(\"f\")) != std::string::npos) {\n          table_mq_data.back().push_back(\n            TableCell(hash.getDouble(formattags[\"key\"].c_str()), format, unit));\n        }\n\n        XrdOucString name = formattags[\"key\"].c_str();\n\n        if (format.find(\"o\") == std::string::npos) {  //only for table output\n          name.replace(\"stat.\", \"\");\n          name.replace(\"stat.statfs.\", \"\");\n          name.replace(\"local.\", \"\");\n\n          if (formattags.count(\"tag\")) {\n            name = formattags[\"tag\"].c_str();\n          }\n        }\n\n        table_mq_header.push_back(std::make_tuple(name.c_str(), width, format));\n      }\n\n      if (formattags.count(\"compute\")) {\n        if (formattags[\"compute\"] == \"usage\") {\n          // compute the percentage usage\n          long long used_bytes = hash.getLongLong(\"stat.statfs.usedbytes\");\n          long long capacity = hash.getLongLong(\"stat.statfs.capacity\");\n          long long headroom = hash.getLongLong(\"headroom\");\n          double usage = 0;\n\n          if (capacity) {\n            usage = 100.0 * (used_bytes + headroom) / (capacity);\n\n            if (usage > 100.0) {\n              usage = 100.0;\n            }\n          }\n\n          table_mq_data.back().push_back(\n            TableCell(usage, format, unit));\n          table_mq_header.push_back(std::make_tuple(\"usage\", width, format));\n        }\n      }\n    }\n  }\n\n  //we check for filters\n  bool toRemove = false;\n\n  if (filter.find(\"d\") != std::string::npos) {\n    std::string drain = hash.get(\"local.drain\");\n\n    // @note there is a bug when initializing local.drain on an fs which is not\n    // propagated to the shared hash therefore, we need to also exclude the\n    // the empty drain status from the list of active drainings\n    if (drain.empty() || (drain == \"nodrain\")) {\n      toRemove = true;\n    }\n  }\n\n  if (filter.find(\"e\") != std::string::npos) {\n    int err = (int) hash.getLongLong(\"stat.errc\");\n\n    if (err == 0) {\n      toRemove = true;\n    }\n  }\n\n  if (toRemove) {\n    table_mq_data.pop_back();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Store a configuration key-val pair.\n// Internally, these keys are not prefixed with 'stat.'\n//------------------------------------------------------------------------------\nvoid\nFileSystem::CreateConfig(std::string& key, std::string& val)\n{\n  key = mLocator.getQueuePath();\n  val.clear();\n  std::map<std::string, std::string> contents;\n  mq::SharedHashWrapper(mRealm, mHashLocator).getContents(contents);\n  val = SerializeWithFilter(contents, {\"stat.\", \"local.\"});\n}\n\n//------------------------------------------------------------------------------\n// Retrieve FileSystem's core parameters\n//------------------------------------------------------------------------------\nFileSystemCoreParams FileSystem::getCoreParams()\n{\n  mq::SharedHashWrapper hash(mRealm, mHashLocator);\n  std::string id;\n\n  if (!hash.get(\"id\", id) || id.empty()) {\n    return FileSystemCoreParams(0, FileSystemLocator(), GroupLocator(), \"\",\n                                ConfigStatus::kOff, \"\");\n  }\n\n  GroupLocator groupLocator;\n  GroupLocator::parseGroup(hash.get(\"schedgroup\"), groupLocator);\n  std::string uuid = hash.get(\"uuid\");\n  ConfigStatus cfg = GetConfigStatusFromString(hash.get(\"configstatus\").c_str());\n  std::string sharedfs = hash.get(\"sharedfs\");\n  return FileSystemCoreParams(atoi(id.c_str()), mLocator, groupLocator, uuid,\n                              cfg, sharedfs);\n}\n\n//------------------------------------------------------------------------------\n// Snapshots all variables of a filesystem into a snapshot struct\n//------------------------------------------------------------------------------\nbool\nFileSystem::SnapShotFileSystem(FileSystem::fs_snapshot_t& fs, bool dolock)\n{\n  mq::SharedHashWrapper hash(mRealm, mHashLocator, dolock, false);\n  std::string tmp;\n\n  if (!hash.get(\"id\", tmp)) {\n    eos_static_err(\"%s\", \"msg=\\\"failed to get file system id\\\"\");\n    fs = {};\n    return false;\n  }\n\n  fs.mId = hash.getLongLong(\"id\");\n  fs.mQueue = mLocator.getFSTQueue();\n  fs.mQueuePath = mLocator.getQueuePath();\n  fs.mGroup = hash.get(\"schedgroup\");\n  fs.mUuid = hash.get(\"uuid\");\n  fs.mHost = mLocator.getHost();\n  fs.mHostPort = mLocator.getHostPort();\n  fs.mProxyGroup = hash.get(\"proxygroup\");\n  fs.mS3Credentials = hash.get(\"s3credentials\");\n  fs.mFileStickyProxyDepth = -1;\n  fs.mSharedFs = hash.get(\"sharedfs\");\n\n  if (hash.get(\"filestickyproxydepth\").size()) {\n    fs.mFileStickyProxyDepth = hash.getLongLong(\"filestickyproxydepth\");\n  }\n\n  fs.mPort = mLocator.getPort();\n  GroupLocator groupLocator;\n  GroupLocator::parseGroup(fs.mGroup, groupLocator);\n  fs.mSpace = groupLocator.getSpace();\n  fs.mGroupIndex = groupLocator.getIndex();\n  fs.mPath = mLocator.getStoragePath();\n  fs.mErrMsg = hash.get(\"stat.errmsg\");\n  fs.mGeoTag = hash.get(\"stat.geotag\");\n  fs.mForceGeoTag.clear();\n\n  if (hash.get(\"forcegeotag\").size()) {\n    std::string forceGeoTag = hash.get(\"forcegeotag\");\n\n    if (forceGeoTag != \"<none>\") {\n      fs.mGeoTag = forceGeoTag;\n      fs.mForceGeoTag = forceGeoTag;\n    }\n  }\n\n  fs.mPublishTimestamp = (size_t) hash.getLongLong(\"stat.publishtimestamp\");\n  fs.mStatus = GetStatusFromString(hash.get(\"stat.boot\").c_str());\n  fs.mConfigStatus = GetConfigStatusFromString(hash.get(\"configstatus\").c_str());\n  fs.mDrainStatus = GetDrainStatusFromString(hash.get(\"local.drain\").c_str());\n  fs.mActiveStatus = mActStatus.load();\n  //headroom can be configured as KMGTP so the string should be properly converted\n  fs.mHeadRoom = StringConversion::GetSizeFromString(hash.get(\"headroom\"));\n  fs.mErrCode = (unsigned int) hash.getLongLong(\"stat.errc\");\n  fs.mBootSentTime = (time_t) hash.getLongLong(\"bootsenttime\");\n  fs.mBootDoneTime = (time_t) hash.getLongLong(\"stat.bootdonetime\");\n  fs.mDiskUtilization = hash.getDouble(\"stat.disk.load\");\n  fs.mNetEthRateMiB = hash.getDouble(\"stat.net.ethratemib\");\n  fs.mNetInRateMiB = hash.getDouble(\"stat.net.inratemib\");\n  fs.mNetOutRateMiB = hash.getDouble(\"stat.net.outratemib\");\n  fs.mDiskWriteRateMb = hash.getDouble(\"stat.disk.writeratemb\");\n  fs.mDiskReadRateMb = hash.getDouble(\"stat.disk.readratemb\");\n  fs.mDiskType = (long) hash.getLongLong(\"stat.statfs.type\");\n  fs.mDiskFreeBytes = hash.getLongLong(\"stat.statfs.freebytes\");\n  fs.mDiskCapacity = hash.getLongLong(\"stat.statfs.capacity\");\n  fs.mDiskBsize = (long) hash.getLongLong(\"stat.statfs.bsize\");\n  fs.mDiskBlocks = (long) hash.getLongLong(\"stat.statfs.blocks\");\n  fs.mDiskBfree = (long) hash.getLongLong(\"stat.statfs.bfree\");\n  fs.mDiskBused = (long) hash.getLongLong(\"stat.statfs.bused\");\n  fs.mDiskBavail = (long) hash.getLongLong(\"stat.statfs.bavail\");\n  fs.mDiskFiles = (long) hash.getLongLong(\"stat.statfs.files\");\n  fs.mDiskFfree = (long) hash.getLongLong(\"stat.statfs.ffree\");\n  fs.mDiskFused = (long) hash.getLongLong(\"stat.statfs.fused\");\n  fs.mDiskFilled = (double) hash.getDouble(\"stat.statfs.filled\");\n  fs.mNominalFilled = (double) hash.getDouble(\"stat.nominal.filled\");\n  fs.mFiles = (long) hash.getLongLong(\"stat.usedfiles\");\n  fs.mDiskNameLen = (long) hash.getLongLong(\"stat.statfs.namelen\");\n  fs.mDiskRopen = (long) hash.getLongLong(\"stat.ropen\");\n  fs.mDiskWopen = (long) hash.getLongLong(\"stat.wopen\");\n  fs.mMaxDiskRopen = (long) hash.getLongLong(\"max.ropen\");\n  fs.mMaxDiskWopen = (long) hash.getLongLong(\"max.wopen\");\n  fs.mScanIoRate = (long) hash.getLongLong(eos::common::SCAN_IO_RATE_NAME);\n  fs.mScanEntryInterval = (long)hash.getLongLong\n                          (eos::common::SCAN_ENTRY_INTERVAL_NAME);\n  fs.mScanRainEntryInterval =\n    (long)hash.getLongLong(eos::common::SCAN_RAIN_ENTRY_INTERVAL_NAME);\n  fs.mScanDiskInterval = (long)hash.getLongLong(\n                           eos::common::SCAN_DISK_INTERVAL_NAME);\n  fs.mScanNsInterval = (long)hash.getLongLong(eos::common::SCAN_NS_INTERVAL_NAME);\n  fs.mScanNsRate = (long)hash.getLongLong(eos::common::SCAN_NS_RATE_NAME);\n  fs.mGracePeriod = (time_t) hash.getLongLong(\"graceperiod\");\n  fs.mDrainPeriod = (time_t) hash.getLongLong(\"drainperiod\");\n  return true;\n}\n\n//----------------------------------------------------------------------------\n// Return the configuration status (via cache)\n//----------------------------------------------------------------------------\nConfigStatus\nFileSystem::GetConfigStatus(bool cached)\n{\n  XrdSysMutexHelper lock(cConfigLock);\n\n  if (cached) {\n    time_t now = time(NULL);\n\n    if (now - cConfigTime) {\n      cConfigTime = now;\n    } else {\n      return cConfigStatus;\n    }\n  }\n\n  cConfigStatus = GetConfigStatusFromString(GetString(\"configstatus\").c_str());\n  return cConfigStatus;\n}\n\n//----------------------------------------------------------------------------\n// Return the filesystem status (via a cache)\n//----------------------------------------------------------------------------\nBootStatus\nFileSystem::GetStatus(bool cached)\n{\n  XrdSysMutexHelper lock(cStatusLock);\n\n  if (cached) {\n    time_t now = time(NULL);\n\n    if (now - cStatusTime) {\n      cStatusTime = now;\n    } else {\n      return cStatus;\n    }\n  }\n\n  cStatus = GetStatusFromString(GetString(\"stat.boot\").c_str());\n  return cStatus;\n}\n\n//----------------------------------------------------------------------------\n// Function printing the file system info to the table\n//----------------------------------------------------------------------------\nvoid\nFileSystem::Print(TableHeader& table_mq_header, TableData& table_mq_data,\n                  std::string listformat, const std::string& filter)\n{\n  mq::SharedHashWrapper hash(mRealm, mHashLocator);\n  printOntoTable(hash, table_mq_header, table_mq_data, listformat, filter);\n}\n\n//------------------------------------------------------------------------------\n// Convert input to file system id\n//------------------------------------------------------------------------------\neos::common::FileSystem::fsid_t\nFileSystem::ConvertToFsid(const std::string& value)\n{\n  eos::common::FileSystem::fsid_t fsid = 0ul;\n\n  try {\n    size_t pos = 0;\n    fsid = std::stoul(value, &pos);\n\n    if (pos != value.length()) {\n      throw std::runtime_error(\"failed fsid conversion\");\n    }\n  } catch (...) {\n    fsid = 0ul;\n  }\n\n  return fsid;\n}\n\nEOSCOMMONNAMESPACE_END;\n"
  },
  {
    "path": "common/FileSystem.hh",
    "content": "//------------------------------------------------------------------------------\n// File: FileSystem.hh\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSCOMMON_FILESYSTEM_HH__\n#define __EOSCOMMON_FILESYSTEM_HH__\n\n#include \"common/Namespace.hh\"\n#include \"common/Locators.hh\"\n#include \"common/table_formatter/TableCell.hh\"\n#include \"mq/SharedHashWrapper.hh\"\n#include <string>\n#include <stdint.h>\n#ifndef __APPLE__\n#include <sys/vfs.h>\n#else\n#include <sys/param.h>\n#include <sys/mount.h>\n#endif\n#include <atomic>\n#include <list>\n\n//! Forward declarations\nnamespace eos\n{\nnamespace mq\n{\nclass MessagingRealm;\n}\n}\n\n//namespace qclient\n//{\n//class SharedManager;\n//}\n\nEOSCOMMONNAMESPACE_BEGIN\n\n\n//! Values for a boot status\nenum class BootStatus : int8_t {\n  kOpsError = -2,\n  kBootFailure = -1,\n  kDown = 0,\n  kBootSent = 1,\n  kBooting = 2,\n  kBooted = 3\n};\n\n//! Values for a drain status\nenum class DrainStatus : int8_t {\n  kNoDrain = 0,\n  kDrainPrepare = 1,\n  kDrainWait = 2,\n  kDraining = 3,\n  kDrained = 4,\n  kDrainStalling = 5,\n  kDrainExpired = 6,\n  kDrainFailed = 7\n};\n\n//! Values describing if a filesystem is online or offline\n//! (combination of multiple conditions)\nenum class ActiveStatus : int8_t {\n  kUndefined = -1,\n  kOffline = 0,\n  kOnline = 1,\n  kOverload = 2\n};\n\n//! Values for a configuration status - stored persistently in the\n//! filesystem configuration\n//! forcing an int8_t as we wouldn't need a larger range of integers for these statuses\nenum class ConfigStatus : int8_t {\n  kUnknown = -1,\n  kOff = 0,\n  kEmpty,\n  kDrainDead,\n  kGroupDrain,\n  kDrain,\n  kRO,\n  kWO,\n  kRW\n};\n\ninline bool operator<(ConfigStatus one, ConfigStatus two)\n{\n  return static_cast<int>(one) < static_cast<int>(two);\n}\n\ninline bool operator<=(ConfigStatus one, ConfigStatus two)\n{\n  return static_cast<int>(one) <= static_cast<int>(two);\n}\n\n#define EOS_TAPE_FSID 65535\n#define EOS_TAPE_MODE_T (0x10000000ll)\n\n//------------------------------------------------------------------------------\n//! Contains a batch of key-value updates on the attributes of a filesystem.\n//! Build up the batch, then submit it through applyBatch.\n//!\n//! Note: If the batch mixes durable, transient, and/or local values, three\n//! \"transactions\" will take place:\n//!\n//! - All durable values will be updated atomically.\n//! - All transient values will be updated atomically.\n//! - All local values will be updated atomically.\n//!\n//! However, the entire operation as a whole will NOT be atomic.\n//------------------------------------------------------------------------------\nclass FileSystemUpdateBatch\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Type definitions\n  //----------------------------------------------------------------------------\n  using fsid_t = uint32_t;\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  FileSystemUpdateBatch();\n\n  //----------------------------------------------------------------------------\n  //! Set filesystem ID - durable.\n  //----------------------------------------------------------------------------\n  void setId(fsid_t fsid);\n\n  //----------------------------------------------------------------------------\n  //! Set the draining status - local.\n  //----------------------------------------------------------------------------\n  void setDrainStatusLocal(DrainStatus status);\n\n  //----------------------------------------------------------------------------\n  //! Set durable string.\n  //!\n  //! All observers of this filesystem are guaranteed to receive the update\n  //! eventually, and all updates are guaranteed to be applied in the same\n  //! order for all observers.\n  //!\n  //! If two racing setStringDurable are attempted on the same key, only one\n  //! will survive, and all observers will agree as to which one survives.\n  //----------------------------------------------------------------------------\n  void setStringDurable(const std::string& key, const std::string& value);\n\n  //----------------------------------------------------------------------------\n  //! Set transient string. Depending on network instabilities,\n  //! process restarts, or the phase of the moon, some or all observers of this\n  //! filesystem may or may not receive the update.\n  //!\n  //! Transient updates may be applied out of order, and if multiple subscribers\n  //! try to modify the same value, it's possible that observers will not all\n  //! converge on a single consistent value.\n  //----------------------------------------------------------------------------\n  void setStringTransient(const std::string& key, const std::string& value);\n\n  //----------------------------------------------------------------------------\n  //! Set local string. This node, and only this node will store the value,\n  //! and only until process restart.\n  //----------------------------------------------------------------------------\n  void setStringLocal(const std::string& key, const std::string& value);\n\n  //----------------------------------------------------------------------------\n  //! Set durable int64_t - serialize as string automatically.\n  //----------------------------------------------------------------------------\n  void setLongLongDurable(const std::string& key, int64_t value);\n\n  //----------------------------------------------------------------------------\n  //! Set transient int64_t - serialize as string automatically.\n  //----------------------------------------------------------------------------\n  void setLongLongTransient(const std::string& key, int64_t value);\n\n  //----------------------------------------------------------------------------\n  //! Set local int64_t - serialize as string automatically.\n  //----------------------------------------------------------------------------\n  void setLongLongLocal(const std::string& key, int64_t value);\n\n  //----------------------------------------------------------------------------\n  //! Get MQ update batch\n  //----------------------------------------------------------------------------\n  const mq::SharedHashWrapper::Batch& getBatch() const;\n\nprivate:\n  mq::SharedHashWrapper::Batch mBatch;\n};\n\n//------------------------------------------------------------------------------\n//! Describes how to locate an FST: host + port.\n//------------------------------------------------------------------------------\nclass FstLocator\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Empty constructor\n  //----------------------------------------------------------------------------\n  FstLocator();\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  FstLocator(const std::string& host, int port);\n\n  //----------------------------------------------------------------------------\n  //! Get host\n  //----------------------------------------------------------------------------\n  std::string getHost() const;\n\n  //----------------------------------------------------------------------------\n  //! Get port\n  //----------------------------------------------------------------------------\n  int getPort() const;\n\n  //----------------------------------------------------------------------------\n  //! Get queuepath\n  //----------------------------------------------------------------------------\n  std::string getQueuePath() const;\n\n  //----------------------------------------------------------------------------\n  //! Get host:port\n  //----------------------------------------------------------------------------\n  std::string getHostPort() const;\n\n  //----------------------------------------------------------------------------\n  //! Try to parse from queuepath\n  //----------------------------------------------------------------------------\n  static bool fromQueuePath(const std::string& queuepath, FstLocator& out);\n\nprivate:\n  std::string mHost;\n  int mPort;\n};\n\n//------------------------------------------------------------------------------\n//! Describes a FileSystem Group:\n//! - Space\n//! - Index\n//------------------------------------------------------------------------------\nclass GroupLocator\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Empty constructor\n  //----------------------------------------------------------------------------\n  GroupLocator();\n\n  //----------------------------------------------------------------------------\n  //! Get group (space.index)\n  //----------------------------------------------------------------------------\n  std::string getGroup() const;\n\n  //----------------------------------------------------------------------------\n  //! Get space\n  //----------------------------------------------------------------------------\n  std::string getSpace() const;\n\n  //----------------------------------------------------------------------------\n  //! Get index\n  //----------------------------------------------------------------------------\n  int getIndex() const;\n\n  //----------------------------------------------------------------------------\n  //! Parse full group (space.index)\n  //!\n  //! NOTE: In case parsing fails, \"out\" will still be filled\n  //! with \"description.0\" to match legacy behaviour.\n  //----------------------------------------------------------------------------\n  static bool parseGroup(const std::string& description, GroupLocator& out);\n\nprivate:\n  std::string mGroup;\n  std::string mSpace;\n  int mIndex = 0;\n};\n\n//------------------------------------------------------------------------------\n//! Describes critical parameters of a FileSystem, which are necessary to have\n//! when registering a filesystem on the MGM.\n//!\n//! A FileSystemLocator can physically locate a FileSystem, but we still can't\n//! operate it on the MGM without knowing more information. (id, group, uuid)\n//------------------------------------------------------------------------------\nclass FileSystemCoreParams\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  FileSystemCoreParams(uint32_t id, const FileSystemLocator& fsLocator,\n                       const GroupLocator& grpLocator, const std::string& uuid,\n                       ConfigStatus cfg, const std::string& sharedfs);\n\n  //----------------------------------------------------------------------------\n  //! Get locator\n  //----------------------------------------------------------------------------\n  const FileSystemLocator& getLocator() const;\n\n  //----------------------------------------------------------------------------\n  //! Get group locator\n  //----------------------------------------------------------------------------\n  const GroupLocator& getGroupLocator() const;\n\n  //----------------------------------------------------------------------------\n  //! Get id\n  //----------------------------------------------------------------------------\n  uint32_t getId() const;\n\n  //----------------------------------------------------------------------------\n  //! Get uuid\n  //----------------------------------------------------------------------------\n  std::string getUuid() const;\n\n  //----------------------------------------------------------------------------\n  //! Get current ConfigStatus\n  //----------------------------------------------------------------------------\n  ConfigStatus getConfigStatus() const;\n\n  //----------------------------------------------------------------------------\n  //! Get queuepath\n  //----------------------------------------------------------------------------\n  std::string getQueuePath() const;\n\n  //----------------------------------------------------------------------------\n  //! Get host\n  //----------------------------------------------------------------------------\n  std::string getHost() const;\n\n  //----------------------------------------------------------------------------\n  //! Get hostport\n  //----------------------------------------------------------------------------\n  std::string getHostPort() const;\n\n  //----------------------------------------------------------------------------\n  //! Get FST queue\n  //----------------------------------------------------------------------------\n  std::string getFSTQueue() const;\n\n  //----------------------------------------------------------------------------\n  //! Get group (space.index)\n  //----------------------------------------------------------------------------\n  std::string getGroup() const;\n\n  //----------------------------------------------------------------------------\n  //! Get space\n  //----------------------------------------------------------------------------\n  std::string getSpace() const;\n\n  //----------------------------------------------------------------------------\n  //! Get shared file system name\n  //----------------------------------------------------------------------------\n  std::string getSharedFs() const;\n\nprivate:\n  uint32_t mFsId;\n  FileSystemLocator mLocator;\n  GroupLocator mGroup;\n  std::string mUuid;\n  ConfigStatus mConfigStatus;\n  std::string mSharedFs;\n};\n\n\n//------------------------------------------------------------------------------\n//! Base Class abstracting the internal representation of a filesystem inside\n//! the MGM and FST\n//------------------------------------------------------------------------------\nclass FileSystem\n{\nprotected:\n  //! This filesystem's locator object\n  FileSystemLocator mLocator;\n  //! Locator for shared hash\n  SharedHashLocator mHashLocator;\n  //! Messaging realm\n  mq::MessagingRealm* mRealm;\n  //! boot status stored inside the object not the hash\n  BootStatus mInternalBootStatus;\n\npublic:\n  //! Struct & Type definitions\n  typedef uint32_t fsid_t; ///< File system ID type\n\n  //! Snapshot Structure of a filesystem\n  struct fs_snapshot_t {\n    fsid_t mId;\n    std::string mQueue;\n    std::string mQueuePath;\n    std::string mPath;\n    std::string mErrMsg;\n    std::string mGroup;\n    std::string mUuid;\n    std::string mHost;\n    std::string mHostPort;\n    std::string mProxyGroup;\n    std::string mSharedFs;\n    std::string mS3Credentials;\n    int8_t      mFileStickyProxyDepth;\n    int32_t mPort;\n    std::string mGeoTag;\n    std::string mForceGeoTag;\n    size_t mPublishTimestamp;\n    int mGroupIndex;\n    std::string mSpace;\n    BootStatus mStatus;\n    ConfigStatus mConfigStatus;\n    DrainStatus mDrainStatus;\n    ActiveStatus mActiveStatus;\n    long long mHeadRoom;\n    unsigned int mErrCode;\n    time_t mBootSentTime;\n    time_t mBootDoneTime;\n    double mDiskUtilization;\n    double mDiskWriteRateMb;\n    double mDiskReadRateMb;\n    double mNetEthRateMiB;\n    double mNetInRateMiB;\n    double mNetOutRateMiB;\n    double mNominalFilled;\n    double mDiskFilled;\n    long long mDiskCapacity;\n    long long mDiskFreeBytes;\n    long mDiskType;\n    long mDiskBsize;\n    long mDiskBlocks;\n    // @todo (esindril) to be dropped\n    long mDiskBused;\n    long mDiskBfree;\n    long mDiskBavail;\n    long mDiskFiles;\n    long mDiskFused;\n    long mDiskFfree;\n    long mFiles;\n    long mDiskNameLen;\n    long mDiskRopen;\n    long mDiskWopen;\n    long mMaxDiskRopen;\n    long mMaxDiskWopen;\n    long mScanIoRate; ///< Maximum scan rate in MB/s\n    long mScanEntryInterval; ///< Time after which a scanned file is rescanned\n    long mScanRainEntryInterval; ///< Time after which a scanned rain file is rescanned\n    long mScanDiskInterval; ///< Time after which the disk scanner runs again\n    long mScanNsInterval; ///< Time after which the disk scanner runs again\n    long mScanNsRate; ///< Max ns scan rate in entries/s\n    time_t mGracePeriod;\n    time_t mDrainPeriod;\n\n    //--------------------------------------------------------------------------\n    //! Empty constructor\n    //--------------------------------------------------------------------------\n    fs_snapshot_t();\n\n    //--------------------------------------------------------------------------\n    //! \"Absorb\" all information contained within coreParams into this object.\n    //! Fields which are not present in coreParams (ie mNetInRateMiB) remain\n    //! unchanged.\n    //--------------------------------------------------------------------------\n    void fillFromCoreParams(const FileSystemCoreParams& coreParams);\n  };\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //! @param queuepath Named Queue to specify the receiver filesystem of\n  //!                  modifications e.g. /eos/<host:port>/fst/<path>\n  //! @param queue     Named Queue to specify the receiver of modifications\n  //!                  e.g. /eos/<host:port>/fst\n  //! @param som       Handle to the shared object manager to store filesystem\n  //!                  key-value pairs\n  //! @param bc2mgm   If true we broad cast to the management server\n  //----------------------------------------------------------------------------\n  FileSystem(const FileSystemLocator& locator, mq::MessagingRealm* realm,\n             bool b2mgm = false);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~FileSystem() = default;\n\n  //----------------------------------------------------------------------------\n  // Get underlying hash locator\n  //----------------------------------------------------------------------------\n  SharedHashLocator getHashLocator() const;\n\n  //----------------------------------------------------------------------------\n  // Enums\n  //----------------------------------------------------------------------------\n\n  //! Value indication the way a boot message should be executed on an FST node\n  enum eBootConfig {\n    kBootOptional = 0,\n    kBootDisk = 1,\n    kBootMgm = 2\n  };\n\n  //----------------------------------------------------------------------------\n  // Get file system status as a string\n  //----------------------------------------------------------------------------\n  static const char* GetStatusAsString(BootStatus status);\n  static const char* GetDrainStatusAsString(DrainStatus status);\n  static const char* GetConfigStatusAsString(ConfigStatus status);\n  static const char* GetActiveStatusAsString(ActiveStatus status);\n\n  //----------------------------------------------------------------------------\n  //! Parse a string status into the enum value\n  //----------------------------------------------------------------------------\n  static BootStatus GetStatusFromString(const char* ss);\n\n  //----------------------------------------------------------------------------\n  //! Parse a drain status into the enum value\n  //----------------------------------------------------------------------------\n  static DrainStatus GetDrainStatusFromString(const char* ss);\n\n  //----------------------------------------------------------------------------\n  //! Parse a configuration status into the enum value\n  //----------------------------------------------------------------------------\n  static ConfigStatus GetConfigStatusFromString(const char*  ss);\n\n  //----------------------------------------------------------------------------\n  //! Convert input to file system id\n  //!\n  //! @param value input string\n  //!\n  //! @return file system id if conversion successful, otherwise 0\n  //----------------------------------------------------------------------------\n  static eos::common::FileSystem::fsid_t\n  ConvertToFsid(const std::string& value);\n\n  //----------------------------------------------------------------------------\n  //! Serializes hash contents as follows 'key1=val1 key2=val2 ... keyn=valn'\n  //! but return only keys that don't start with filter_prefix. If specified,\n  //! the string values will be curl encoded\n  //!\n  //! @param contents key value pairs\n  //! @param filter_prefix prefix used for filtering keys\n  //!\n  //! @return string representation of the content for the hash\n  //----------------------------------------------------------------------------\n  static std::string\n  SerializeWithFilter(const std::map<std::string, std::string>& contents,\n                      std::list<std::string> filter_prefixes);\n\n  //----------------------------------------------------------------------------\n  //! Cache Members\n  //----------------------------------------------------------------------------\n  BootStatus cStatus; ///< cache value of the status\n  time_t cStatusTime; ///< unix time stamp of last update of the cached status\n  XrdSysMutex cStatusLock; ///< lock protecting the cached status\n  std::atomic<ConfigStatus> cConfigStatus; ///< cached value of the config status\n  XrdSysMutex cConfigLock; ///< lock protecting the cached config status\n  time_t cConfigTime; ///< unix time stamp of last update of the cached config status\n\n  //----------------------------------------------------------------------------\n  //! Apply the given batch of updates\n  //----------------------------------------------------------------------------\n  bool applyBatch(const FileSystemUpdateBatch& batch);\n\n  //----------------------------------------------------------------------------\n  //! Apply the given core parameters\n  //----------------------------------------------------------------------------\n  bool applyCoreParams(const FileSystemCoreParams& params);\n\n  //----------------------------------------------------------------------------\n  //! Set a single local long long\n  //----------------------------------------------------------------------------\n  bool SetLongLongLocal(const std::string& key, int64_t value);\n\n  //----------------------------------------------------------------------------\n  //! Set a key-value pair in a filesystem and evt. broadcast it.\n  //----------------------------------------------------------------------------\n  bool SetString(const char* key, const char* str, bool broadcast = true);\n\n  //----------------------------------------------------------------------------\n  //! Set a filesystem ID.\n  //----------------------------------------------------------------------------\n  bool SetId(fsid_t fsid)\n  {\n    return SetString(\"id\", std::to_string(fsid).c_str());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set a double value by name and evt. broadcast it.\n  //----------------------------------------------------------------------------\n  bool SetDouble(const char* key, double f)\n  {\n    return SetString(key, std::to_string(f).c_str());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set a long long value and evt. broadcast it.\n  //----------------------------------------------------------------------------\n  bool SetLongLong(const char* key, long long l, bool broadcast = true)\n  {\n    return SetString(key, std::to_string(l).c_str(), broadcast);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set the filesystem status.\n  //----------------------------------------------------------------------------\n  bool SetStatus(BootStatus status, bool broadcast = true)\n  {\n    mInternalBootStatus = status;\n    return SetString(\"stat.boot\", GetStatusAsString(status), broadcast);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set the draining status\n  //----------------------------------------------------------------------------\n  bool SetDrainStatus(DrainStatus status)\n  {\n    return SetString(\"local.drain\", GetDrainStatusAsString(status), false);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Remove a key from a filesystem and evt. broadcast it.\n  //----------------------------------------------------------------------------\n  bool RemoveKey(const char* key, bool broadcast = true);\n\n  //----------------------------------------------------------------------------\n  //! Set the activation status\n  //----------------------------------------------------------------------------\n  void SetActiveStatus(ActiveStatus active);\n\n  //----------------------------------------------------------------------------\n  //! Get the activation status\n  //----------------------------------------------------------------------------\n  inline ActiveStatus GetActiveStatus() const\n  {\n    return mActStatus;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get all keys in a vector of strings.\n  //----------------------------------------------------------------------------\n  bool GetKeys(std::vector<std::string>& keys);\n\n  //----------------------------------------------------------------------------\n  //! Get the string value by key\n  //----------------------------------------------------------------------------\n  std::string GetString(const char* key);\n\n  //----------------------------------------------------------------------------\n  //! Get a long long value by key\n  //----------------------------------------------------------------------------\n  long long GetLongLong(const char* key);\n\n  //----------------------------------------------------------------------------\n  //! Get a double value by key\n  //----------------------------------------------------------------------------\n  double GetDouble(const char* key);\n\n  //----------------------------------------------------------------------------\n  //! Return the filesystem id.\n  //----------------------------------------------------------------------------\n  fsid_t GetId()\n  {\n    return (fsid_t) GetLongLong(\"id\");\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the filesystem queue path.\n  //----------------------------------------------------------------------------\n  std::string GetQueuePath()\n  {\n    return mLocator.getQueuePath();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the filesystem host\n  //----------------------------------------------------------------------------\n  std::string GetHost()\n  {\n    return mLocator.getHost();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the filesystem queue name\n  //----------------------------------------------------------------------------\n  std::string GetQueue()\n  {\n    return mLocator.getFSTQueue();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the filesystem path\n  //----------------------------------------------------------------------------\n  std::string GetPath()\n  {\n    return mLocator.getStoragePath();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the filesystem status (via a cache)\n  //----------------------------------------------------------------------------\n  BootStatus GetStatus(bool cached = false);\n\n  //----------------------------------------------------------------------------\n  //! Get internal boot status\n  //----------------------------------------------------------------------------\n  BootStatus GetInternalBootStatus()\n  {\n    return mInternalBootStatus;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the drain status\n  //----------------------------------------------------------------------------\n  DrainStatus GetDrainStatus()\n  {\n    return GetDrainStatusFromString(GetString(\"local.drain\").c_str());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the configuration status (via cache)\n  //----------------------------------------------------------------------------\n  ConfigStatus GetConfigStatus(bool cached = false);\n\n  //----------------------------------------------------------------------------\n  //! Retrieve FileSystem's core parameters\n  //----------------------------------------------------------------------------\n  FileSystemCoreParams getCoreParams();\n\n  //----------------------------------------------------------------------------\n  //! Snapshots all variables of a filesystem into a snapshot struct\n  //!\n  //! @param fs snapshot struct to be filled\n  //! @param dolock indicates if the shared hash representing the filesystem has\n  //!               to be locked or not\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool SnapShotFileSystem(FileSystem::fs_snapshot_t& fs, bool dolock = true);\n\n  //----------------------------------------------------------------------------\n  //! Function printing the file system info to the table\n  //----------------------------------------------------------------------------\n  void Print(TableHeader& table_mq_header, TableData& table_mq_data,\n             std::string listformat, const std::string& filter = \"\");\n\n  //----------------------------------------------------------------------------\n  //! Store a configuration key-val pair.\n  //! Internally, these keys are not prefixed with 'stat.'\n  //!\n  //! @param key key string\n  //! @param val value string\n  //----------------------------------------------------------------------------\n  void CreateConfig(std::string& key, std::string& val);\n\n  //----------------------------------------------------------------------------\n  //! Get heartbeatTime\n  //----------------------------------------------------------------------------\n  time_t getLocalHeartbeatTime() const;\n\n  //----------------------------------------------------------------------------\n  //! Get local heartbeat delta\n  //----------------------------------------------------------------------------\n  int getLocalHeartbeatDelta() const;\n\n  //----------------------------------------------------------------------------\n  //! Set heartbeatTime\n  //----------------------------------------------------------------------------\n  void setLocalHeartbeatTime(time_t t);\n\n  //----------------------------------------------------------------------------\n  //! Check if local heartbeat is recent enough\n  //----------------------------------------------------------------------------\n  bool hasHeartbeat() const;\n\n  //----------------------------------------------------------------------------\n  //! Delete shared hash object corresponding to this file system which is\n  //! stored in memory in the SharedHashProvier. If delete_from_qdb is true\n  //! then perform also the deletion of the SharedHash object from QDB and\n  //! broadcast the notification - this should be called only when an explicit\n  //! removal of the file system is request though \"fs rm\"!\n  //!\n  //! @param delete_from_qdb if true delete the backing SharedHash from QDB\n  //----------------------------------------------------------------------------\n  void DeleteSharedHash(bool delete_from_qdb);\n\n  //--------------------------------------------------------------------------\n  //! Get used bytes\n  //--------------------------------------------------------------------------\n  uint64_t GetUsedbytes();\n\n  //--------------------------------------------------------------------------\n  //! Get space name\n  //--------------------------------------------------------------------------\n  std::string GetSpace();\n\n  //----------------------------------------------------------------------------\n  //! Get SharedFs name\n  //----------------------------------------------------------------------------\n  std::string getSharedFs();\n\nprivate:\n  std::atomic<ActiveStatus> mActStatus; ///< File system active status\n};\n\nEOSCOMMONNAMESPACE_END;\n\n#endif\n"
  },
  {
    "path": "common/Fmd.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Fmd.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/StringTokenizer.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Get set of known fsck error strings\n//------------------------------------------------------------------------------\nstd::set<std::string> GetKnownFsckErrs()\n{\n  return {FSCK_M_CX_DIFF, FSCK_M_MEM_SZ_DIFF, FSCK_D_CX_DIFF, FSCK_D_MEM_SZ_DIFF,\n          FSCK_UNREG_N, FSCK_REP_DIFF_N, FSCK_REP_MISSING_N, FSCK_BLOCKXS_ERR,\n          FSCK_ORPHANS_N, FSCK_STRIPE_ERR};\n}\n\n//------------------------------------------------------------------------------\n// Convert string to FsckErr type\n//------------------------------------------------------------------------------\nFsckErr ConvertToFsckErr(const std::string& serr)\n{\n  if (serr == FSCK_M_CX_DIFF) {\n    return FsckErr::MgmXsDiff;\n  } else if (serr == FSCK_M_MEM_SZ_DIFF) {\n    return FsckErr::MgmSzDiff;\n  } else if (serr == FSCK_D_CX_DIFF) {\n    return FsckErr::FstXsDiff;\n  } else if (serr == FSCK_D_MEM_SZ_DIFF) {\n    return FsckErr::FstSzDiff;\n  } else if (serr == FSCK_UNREG_N) {\n    return FsckErr::UnregRepl;\n  } else if (serr == FSCK_REP_DIFF_N) {\n    return FsckErr::DiffRepl;\n  } else if (serr == FSCK_REP_MISSING_N) {\n    return FsckErr::MissRepl;\n  } else if (serr == FSCK_BLOCKXS_ERR) {\n    return FsckErr::BlockxsErr;\n  } else if (serr == FSCK_ORPHANS_N) {\n    return FsckErr::Orphans;\n  } else if (serr == FSCK_STRIPE_ERR) {\n    return FsckErr::StripeErr;\n  } else {\n    return FsckErr::None;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Convert to FsckErr type to string\n//------------------------------------------------------------------------------\nstd::string FsckErrToString(const FsckErr& err)\n{\n  if (err == FsckErr::MgmXsDiff) {\n    return FSCK_M_CX_DIFF;\n  } else if (err == FsckErr::MgmSzDiff) {\n    return FSCK_M_MEM_SZ_DIFF;\n  } else if (err == FsckErr::FstXsDiff) {\n    return FSCK_D_CX_DIFF;\n  } else if (err == FsckErr::FstSzDiff) {\n    return FSCK_D_MEM_SZ_DIFF;\n  } else if (err == FsckErr::UnregRepl) {\n    return FSCK_UNREG_N;\n  } else if (err == FsckErr::DiffRepl) {\n    return FSCK_REP_DIFF_N;\n  } else if (err == FsckErr::MissRepl) {\n    return FSCK_REP_MISSING_N;\n  } else if (err == FsckErr::BlockxsErr) {\n    return FSCK_BLOCKXS_ERR;\n  } else if (err == FsckErr::Orphans) {\n    return FSCK_ORPHANS_N;\n  } else if (err == FsckErr::StripeErr) {\n    return FSCK_STRIPE_ERR;\n  } else {\n    return \"none\";\n  }\n}\n\n//------------------------------------------------------------------------------\n// Convert an FST env representation to an Fmd struct\n//------------------------------------------------------------------------------\nbool EnvToFstFmd(XrdOucEnv& env, FmdHelper& fmd)\n{\n  // Check that all tags are present\n  std::set<std::string> tags {\"id\", \"cid\",  \"ctime\", \"ctime_ns\", \"mtime\",\n                              \"mtime_ns\", \"size\", \"lid\", \"uid\", \"gid\"};\n  //      \"fsid\",  \"disksize\", \"filecxerror\",\n  //      \"blockcxerror\", \"layouterror\", \"locations\"};\n\n  for (const auto& tag : tags) {\n    if (env.Get(tag.c_str()) == nullptr) {\n      int envlen = 0;\n      eos_static_crit(\"msg=\\\"missing fields in fmd encoding\\\" field=%s \"\n                      \"encoding=\\\"%s\\\"\", tag.c_str(), env.Env(envlen));\n      return false;\n    }\n  }\n\n  fmd.mProtoFmd.set_fid(strtoull(env.Get(\"id\"), 0, 10));\n  fmd.mProtoFmd.set_cid(strtoull(env.Get(\"cid\"), 0, 10));\n  fmd.mProtoFmd.set_ctime(strtoul(env.Get(\"ctime\"), 0, 10));\n  fmd.mProtoFmd.set_ctime_ns(strtoul(env.Get(\"ctime_ns\"), 0, 10));\n  fmd.mProtoFmd.set_mtime(strtoul(env.Get(\"mtime\"), 0, 10));\n  fmd.mProtoFmd.set_mtime_ns(strtoul(env.Get(\"mtime_ns\"), 0, 10));\n  fmd.mProtoFmd.set_size(strtoull(env.Get(\"size\"), 0, 10));\n  fmd.mProtoFmd.set_lid(strtoul(env.Get(\"lid\"), 0, 16));\n  fmd.mProtoFmd.set_uid((uid_t) strtoul(env.Get(\"uid\"), 0, 10));\n  fmd.mProtoFmd.set_gid((gid_t) strtoul(env.Get(\"gid\"), 0, 10));\n\n  if (env.Get(\"fsid\")) {\n    fmd.mProtoFmd.set_fsid(strtoull(env.Get(\"fsid\"), 0, 10));\n  }\n\n  if (env.Get(\"disksize\")) {\n    fmd.mProtoFmd.set_disksize(strtoull(env.Get(\"disksize\"), 0, 10));\n  }\n\n  if (env.Get(\"checksum\")) {\n    fmd.mProtoFmd.set_checksum(env.Get(\"checksum\"));\n  }\n\n  if (env.Get(\"filecxerror\")) {\n    fmd.mProtoFmd.set_filecxerror(strtoul(env.Get(\"filecxerror\"), 0, 16));\n  }\n\n  if (env.Get(\"blockcxerror\")) {\n    fmd.mProtoFmd.set_blockcxerror(strtoul(env.Get(\"blockcxerror\"), 0, 16));\n  }\n\n  if (env.Get(\"layouterror\")) {\n    fmd.mProtoFmd.set_layouterror(strtoul(env.Get(\"layouterror\"), 0, 16));\n  }\n\n  if (fmd.mProtoFmd.checksum() == \"none\") {\n    fmd.mProtoFmd.set_checksum(\"\");\n  }\n\n  if (env.Get(\"diskchecksum\")) {\n    fmd.mProtoFmd.set_diskchecksum(env.Get(\"diskchecksum\"));\n  }\n\n  if (fmd.mProtoFmd.diskchecksum() == \"none\") {\n    fmd.mProtoFmd.set_diskchecksum(\"\");\n  }\n\n  if (env.Get(\"mgmchecksum\")) {\n    fmd.mProtoFmd.set_mgmchecksum(env.Get(\"mgmchecksum\"));\n  }\n\n  if (fmd.mProtoFmd.mgmchecksum() == \"none\") {\n    fmd.mProtoFmd.set_mgmchecksum(\"\");\n  }\n\n  if (env.Get(\"locations\")) {\n    fmd.mProtoFmd.set_locations(env.Get(\"locations\"));\n  }\n\n  if (fmd.mProtoFmd.locations() == \"none\") {\n    fmd.mProtoFmd.set_locations(\"\");\n  }\n\n  if (env.Get(\"stripeerror\")) {\n    fmd.mProtoFmd.clear_stripeerror();\n\n    if (std::string(env.Get(\"stripeerror\")) != \"none\") {\n      std::vector<std::string> stripeErrToken;\n      eos::common::StringConversion::Tokenize(env.Get(\"stripeerror\"),\n                                              stripeErrToken, \",\");\n\n      for (const auto& id : stripeErrToken) {\n        try {\n          fmd.mProtoFmd.add_stripeerror(std::stol(id));\n        } catch (...) {\n          eos_static_err(\"msg=\\\"skip non-numeric stripe error\\\" value=\\\"%s\\\"\",\n                         id.c_str());\n        }\n      }\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Populate data structures with any inconsistencies deteced while inspecting\n// the FmdHelper object\n//------------------------------------------------------------------------------\nvoid\nCollectInconsistencies(const FmdHelper& fmd,\n                       const eos::common::FileSystem::fsid_t fsid,\n                       FsckErrsPerFsMap& errs_map)\n{\n  auto& proto_fmd = fmd.mProtoFmd;\n\n  if (proto_fmd.blockcxerror()) {\n    errs_map[\"blockxs_err\"][fsid].insert(proto_fmd.fid());\n  }\n\n  if (proto_fmd.layouterror()) {\n    if (proto_fmd.layouterror() & LayoutId::kOrphan) {\n      errs_map[\"orphans_n\"][fsid].insert(proto_fmd.fid());\n    }\n\n    if (proto_fmd.layouterror() & LayoutId::kUnregistered) {\n      errs_map[\"unreg_n\"][fsid].insert(proto_fmd.fid());\n    }\n\n    if (proto_fmd.layouterror() & LayoutId::kReplicaWrong) {\n      errs_map[\"rep_diff_n\"][fsid].insert(proto_fmd.fid());\n    }\n\n    if (proto_fmd.layouterror() & LayoutId::kMissing) {\n      errs_map[\"rep_missing_n\"][fsid].insert(proto_fmd.fid());\n    }\n  }\n\n  if (proto_fmd.mgmsize() != eos::common::FmdHelper::UNDEF) {\n    if (proto_fmd.size() != eos::common::FmdHelper::UNDEF) {\n      // Report missmatch only for non-rain layout files\n      if (!LayoutId::IsRain(proto_fmd.lid()) &&\n          proto_fmd.size() != proto_fmd.mgmsize()) {\n        errs_map[\"m_mem_sz_diff\"][fsid].insert(proto_fmd.fid());\n      }\n    } else {\n      // RAIN stripes with mgmsize != 0 and disksize == 0 are broken\n      if (LayoutId::IsRain(proto_fmd.lid())) {\n        if (proto_fmd.mgmsize() && (proto_fmd.disksize() == 0)) {\n          errs_map[\"d_mem_sz_diff\"][fsid].insert(proto_fmd.fid());\n        }\n      }\n    }\n  }\n\n  if (proto_fmd.disksize() != eos::common::FmdHelper::UNDEF) {\n    if (proto_fmd.size() != eos::common::FmdHelper::UNDEF) {\n      if (LayoutId::IsRain(proto_fmd.lid())) {\n        if (proto_fmd.disksize() != LayoutId::ExpectedStripeSize(proto_fmd.lid(),\n            proto_fmd.size())) {\n          errs_map[\"d_mem_sz_diff\"][fsid].insert(proto_fmd.fid());\n        }\n      } else {\n        if (proto_fmd.size() != proto_fmd.disksize()) {\n          errs_map[\"d_mem_sz_diff\"][fsid].insert(proto_fmd.fid());\n        }\n      }\n    }\n  }\n\n  if (!proto_fmd.layouterror()) {\n    if (!LayoutId::IsRain(proto_fmd.lid())) {\n      if (proto_fmd.size() &&\n          (proto_fmd.size() != eos::common::FmdHelper::UNDEF) &&\n          proto_fmd.diskchecksum().length() &&\n          (proto_fmd.diskchecksum() != proto_fmd.checksum())) {\n        errs_map[\"d_cx_diff\"][fsid].insert(proto_fmd.fid());\n      }\n\n      if (proto_fmd.size() &&\n          (proto_fmd.size() != eos::common::FmdHelper::UNDEF) &&\n          proto_fmd.mgmchecksum().length() &&\n          (proto_fmd.mgmchecksum() != proto_fmd.checksum())) {\n        errs_map[\"m_cx_diff\"][fsid].insert(proto_fmd.fid());\n      }\n    }\n  }\n\n  if (LayoutId::IsRain(proto_fmd.lid())) {\n    for (auto efsid : proto_fmd.stripeerror()) {\n      errs_map[\"stripe_err\"][efsid].insert(proto_fmd.fid());\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Compute layout error\n//------------------------------------------------------------------------------\nint\nFmdHelper::LayoutError(eos::common::FileSystem::fsid_t fsid)\n{\n  uint32_t lid = mProtoFmd.lid();\n\n  if (lid == 0) {\n    // An orphan has no lid at the MGM e.g. lid=0\n    return eos::common::LayoutId::kOrphan;\n  }\n\n  auto location_set = GetLocations();\n  size_t nstripes = eos::common::LayoutId::GetStripeNumber(lid) + 1;\n  int lerror = 0;\n\n  if (nstripes != location_set.size()) {\n    lerror |= eos::common::LayoutId::kReplicaWrong;\n  }\n\n  if (!location_set.count(fsid)) {\n    lerror |= eos::common::LayoutId::kUnregistered;\n  }\n\n  return lerror;\n}\n\n//---------------------------------------------------------------------------\n// Reset file meta data object\n//---------------------------------------------------------------------------\nvoid\nFmdHelper::Reset()\n{\n  mProtoFmd.set_fid(0);\n  mProtoFmd.set_cid(0);\n  mProtoFmd.set_ctime(0);\n  mProtoFmd.set_ctime_ns(0);\n  mProtoFmd.set_mtime(0);\n  mProtoFmd.set_mtime_ns(0);\n  mProtoFmd.set_atime(0);\n  mProtoFmd.set_atime_ns(0);\n  mProtoFmd.set_checktime(0);\n  mProtoFmd.set_size(UNDEF);\n  mProtoFmd.set_disksize(UNDEF);\n  mProtoFmd.set_mgmsize(UNDEF);\n  mProtoFmd.set_checksum(\"\");\n  mProtoFmd.set_diskchecksum(\"\");\n  mProtoFmd.set_mgmchecksum(\"\");\n  mProtoFmd.set_lid(0);\n  mProtoFmd.set_uid(0);\n  mProtoFmd.set_gid(0);\n  mProtoFmd.set_filecxerror(0);\n  mProtoFmd.set_blockcxerror(0);\n  mProtoFmd.set_layouterror(0);\n  mProtoFmd.set_locations(\"\");\n  mProtoFmd.clear_stripeerror();\n}\n\n//------------------------------------------------------------------------------\n// Get set of valid (not unlinked) locations for the given fmd\n//------------------------------------------------------------------------------\nstd::set<eos::common::FileSystem::fsid_t>\nFmdHelper::GetLocations() const\n{\n  std::vector<std::string> location_vector;\n  eos::common::StringConversion::Tokenize(mProtoFmd.locations(), location_vector,\n                                          \",\");\n  std::set<eos::common::FileSystem::fsid_t> location_set;\n\n  for (size_t i = 0; i < location_vector.size(); i++) {\n    if (location_vector[i].length()) {\n      // Exclude unlinked locations i.e. they have a ! in front\n      if (location_vector[i][0] != '!') {\n        location_set.insert(strtoul(location_vector[i].c_str(), 0, 10));\n      }\n    }\n  }\n\n  return location_set;\n}\n\n//------------------------------------------------------------------------------\n// Check if the given file system identifier is in the list of locations\n// for the current file.\n//------------------------------------------------------------------------------\nbool\nFmdHelper::HasLocation(eos::common::FileSystem::fsid_t fsid) const\n{\n  std::set<eos::common::FileSystem::fsid_t> locations = GetLocations();\n  return (locations.find(fsid) != locations.end());\n}\n\n//-------------------------------------------------------------------------------\n// Convert fmd object to env representation\n//-------------------------------------------------------------------------------\nstd::unique_ptr<XrdOucEnv>\nFmdHelper::FmdToEnv()\n{\n  std::ostringstream oss;\n  oss << \"id=\" << mProtoFmd.fid()\n      << \"&cid=\" << mProtoFmd.cid()\n      << \"&fsid=\" << mProtoFmd.fsid()\n      << \"&ctime=\" << mProtoFmd.ctime()\n      << \"&ctime_ns=\" << mProtoFmd.ctime_ns()\n      << \"&mtime=\" << mProtoFmd.mtime()\n      << \"&mtime_ns=\" << mProtoFmd.mtime_ns()\n      << \"&atime=\" << mProtoFmd.atime()\n      << \"&atime_ns=\" << mProtoFmd.atime_ns()\n      << \"&size=\" << mProtoFmd.size()\n      << \"&disksize=\" << mProtoFmd.disksize()\n      << \"&mgmsize=\" << mProtoFmd.mgmsize()\n      << \"&lid=0x\" << std::hex << mProtoFmd.lid() << std::dec\n      << \"&uid=\" << mProtoFmd.uid()\n      << \"&gid=\" << mProtoFmd.gid()\n      << \"&filecxerror=0x\" << std::hex << mProtoFmd.filecxerror()\n      << \"&blockcxerror=0x\" << mProtoFmd.blockcxerror()\n      << \"&layouterror=0x\" << mProtoFmd.layouterror();\n\n  // Take care at string fields since XrdOucEnv does not deal well with empty\n  // values\n  if (mProtoFmd.checksum().empty()) {\n    oss << \"&checksum=none\";\n  } else {\n    oss << \"&checksum=\" << mProtoFmd.checksum();\n  }\n\n  if (mProtoFmd.diskchecksum().empty()) {\n    oss << \"&diskchecksum=none\";\n  } else {\n    oss << \"&diskchecksum=\" << mProtoFmd.diskchecksum();\n  }\n\n  if (mProtoFmd.mgmchecksum().empty()) {\n    oss << \"&mgmchecksum=none\";\n  } else {\n    oss << \"&mgmchecksum=\" << mProtoFmd.mgmchecksum();\n  }\n\n  if (mProtoFmd.locations().empty()) {\n    oss << \"&locations=none\";\n  } else {\n    oss << \"&locations=\" << std::dec << mProtoFmd.locations();\n  }\n\n  if (mProtoFmd.stripeerror_size() == 0) {\n    oss << \"&stripeerror=none\";\n  } else {\n    oss << \"&stripeerror=\" << eos::common::StringTokenizer::merge(\n          mProtoFmd.stripeerror(), ',');\n  }\n\n  oss << '&';\n  return std::unique_ptr<XrdOucEnv>\n         (new XrdOucEnv(oss.str().c_str()));\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Fmd.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include \"proto/FmdBase.pb.h\"\n#include \"common/FileSystem.hh\"\n#include \"common/Logging.hh\"\n#include \"common/FileId.hh\"\n#include <XrdOuc/XrdOucEnv.hh>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//! Map of fsck errors to map of file system identifiers and file ids\nusing FsckErrsPerFsMap = std::map<std::string,\n      std::map<eos::common::FileSystem::fsid_t,\n      std::set<eos::common::FileId::fileid_t>>>;\n\n//------------------------------------------------------------------------------\n//! Class modelling the file metadata stored on the FST. It wrapps an FmdBase\n//! class generated from the ProtoBuffer specification and adds some extra\n//! functionality for conversion to/from string/env representation.\n//------------------------------------------------------------------------------\nclass FmdHelper : public eos::common::LogId\n{\npublic:\n  static constexpr uint64_t UNDEF = 0xfffffffffff1ULL;\n\n  //---------------------------------------------------------------------------\n  //! Constructor\n  //---------------------------------------------------------------------------\n  FmdHelper(eos::common::FileId::fileid_t fid = 0, int fsid = 0)\n  {\n    Reset();\n    mProtoFmd.set_fid(fid);\n    mProtoFmd.set_fsid(fsid);\n  }\n\n  //---------------------------------------------------------------------------\n  //! Constructor with protobuf object: Move only variant\n  //---------------------------------------------------------------------------\n  FmdHelper(eos::fst::FmdBase&& _mProtoFmd): mProtoFmd(std::move(_mProtoFmd)) {}\n\n  //---------------------------------------------------------------------------\n  //! Constructor with protobuf object\n  //---------------------------------------------------------------------------\n  FmdHelper(const eos::fst::FmdBase& _mProtoFmd): mProtoFmd(_mProtoFmd) {}\n\n  //---------------------------------------------------------------------------\n  //! Destructor\n  //---------------------------------------------------------------------------\n  virtual ~FmdHelper() = default;\n\n  //---------------------------------------------------------------------------\n  //! Compute layout error\n  //!\n  //! @param fsid file system id to check against\n  //!\n  //! @return 0 if there are no errors, otherwise encoded type of layout error\n  //!        stored in the int.\n  //---------------------------------------------------------------------------\n  int LayoutError(eos::common::FileSystem::fsid_t fsid);\n\n  //---------------------------------------------------------------------------\n  //! Reset file meta data object\n  //!\n  //! @param fmd protobuf file meta data\n  //---------------------------------------------------------------------------\n  void Reset();\n\n  //---------------------------------------------------------------------------\n  //! Get set of valid locations (not unlinked) for the given fmd\n  //!\n  //! @return set of file system ids representing the locations\n  //---------------------------------------------------------------------------\n  std::set<eos::common::FileSystem::fsid_t> GetLocations() const;\n\n  //---------------------------------------------------------------------------\n  //! Check if the given file system identifier is in the list of locations\n  //! for the current file.\n  //!\n  //! @return true if fsid is in the list of locations, otherwise false\n  //---------------------------------------------------------------------------\n  bool HasLocation(eos::common::FileSystem::fsid_t fsid) const;\n\n  //---------------------------------------------------------------------------\n  //! Convert fmd object to env representation\n  //!\n  //! @return XrdOucEnv holding information about current object\n  //---------------------------------------------------------------------------\n  std::unique_ptr<XrdOucEnv> FmdToEnv();\n\n  //---------------------------------------------------------------------------\n  //! File meta data object replication function (copy constructor)\n  //---------------------------------------------------------------------------\n  void\n  Replicate(FmdHelper& fmd)\n  {\n    mProtoFmd = fmd.mProtoFmd;\n  }\n\n  eos::fst::FmdBase mProtoFmd; ///< Protobuf file metadata info\n};\n\n//------------------------------------------------------------------------------\n//! Fsck constants used throughout the code\n//------------------------------------------------------------------------------\nstatic constexpr auto FSCK_M_CX_DIFF     = \"m_cx_diff\";\nstatic constexpr auto FSCK_M_MEM_SZ_DIFF = \"m_mem_sz_diff\";\nstatic constexpr auto FSCK_D_CX_DIFF     = \"d_cx_diff\";\nstatic constexpr auto FSCK_D_MEM_SZ_DIFF = \"d_mem_sz_diff\";\nstatic constexpr auto FSCK_UNREG_N       = \"unreg_n\";\nstatic constexpr auto FSCK_REP_DIFF_N    = \"rep_diff_n\";\nstatic constexpr auto FSCK_REP_MISSING_N = \"rep_missing_n\";\nstatic constexpr auto FSCK_BLOCKXS_ERR   = \"blockxs_err\";\nstatic constexpr auto FSCK_ORPHANS_N     = \"orphans_n\";\nstatic constexpr auto FSCK_STRIPE_ERR    = \"stripe_err\";\n\n//------------------------------------------------------------------------------\n//! FsckErr types\n//------------------------------------------------------------------------------\nenum class FsckErr {\n  None = 0x00,\n  MgmXsDiff  = 0x01,\n  FstXsDiff  = 0x02,\n  MgmSzDiff  = 0x03,\n  FstSzDiff  = 0x04,\n  UnregRepl  = 0x05,\n  DiffRepl   = 0x06,\n  MissRepl   = 0x07,\n  BlockxsErr = 0x08,\n  Orphans    = 0x09,\n  StripeErr  = 0x0A\n};\n\n//------------------------------------------------------------------------------\n//! Get set of known fsck error strings\n//------------------------------------------------------------------------------\nstd::set<std::string> GetKnownFsckErrs();\n\n//------------------------------------------------------------------------------\n//! Convert string to FsckErr type\n//!\n//! @param serr string error type\n//!\n//! @return FsckErr type\n//------------------------------------------------------------------------------\nFsckErr ConvertToFsckErr(const std::string& serr);\n\n//------------------------------------------------------------------------------\n//! Convert to FsckErr type to string\n//!\n//! @param fsck_err FsckErr type\n//!\n//! @return string fsck error\n//------------------------------------------------------------------------------\nstd::string FsckErrToString(const FsckErr& err);\n\n//------------------------------------------------------------------------------\n//! Convert an FST env representation to an Fmd struct\n//!\n//! @param env env representation\n//! @param fmd reference to Fmd struct\n//!\n//! @return true if successful, otherwise false\n//------------------------------------------------------------------------------\nbool EnvToFstFmd(XrdOucEnv& env, FmdHelper& fmd);\n\n//------------------------------------------------------------------------------\n//! Populate data structures with any inconsistencies detected while inspecting\n//! the FmdHelper object\n//!\n//! @param fmd file info object\n//! @param fsid file system id\n//! @param map of errors to filesystem id and file identifiers\n//------------------------------------------------------------------------------\nvoid\nCollectInconsistencies(const FmdHelper& fmd,\n                       const eos::common::FileSystem::fsid_t fsid,\n                       FsckErrsPerFsMap& errs_map);\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/FutureWrapper.hh",
    "content": "//------------------------------------------------------------------------------\n// File: FutureWrapper.hh\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include <folly/futures/Future.h>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Wrap a future and its result, allows to transparently use the object without\n//! worrying if it has arrived yet. If it hasn't, we block.\n//!\n//! A lot of times when dealing with std::future, we have to keep track a future\n//! object, and the object itself, and whether the future has arrived, so as to\n//! avoid caling get() twice...\n//!\n//! This class takes care of all that transparently.\n//!\n//! Requires T to have default constructor.\n//! If the future arrives armed with an exception, it is re-thrown __every__ time\n//! this wrapper is accessed for reading the object! Not just the first one.\n//------------------------------------------------------------------------------\ntemplate<typename T>\nclass FutureWrapper\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Empty constructor\n  //----------------------------------------------------------------------------\n  FutureWrapper() : mFut(folly::makeFuture<T>(T())), mArrived(true) {}\n\n  //----------------------------------------------------------------------------\n  //! Constructor, takes an existing future object\n  //!\n  //! @param future object to take ownership\n  //----------------------------------------------------------------------------\n  FutureWrapper(folly::Future<T>&& future) : mFut(std::move(future)) {}\n\n  //----------------------------------------------------------------------------\n  //! Constructor, takes a promise\n  //!\n  //! @param promise promise used to get a future\n  //----------------------------------------------------------------------------\n  FutureWrapper(folly::Promise<T>& promise) : mFut(promise.getFuture()) {}\n\n  //----------------------------------------------------------------------------\n  //! Check if accessing the object might block. Exception safe - if the\n  //! underlying future is hiding an exception, we don't throw here, but during\n  //! first actual access.\n  //----------------------------------------------------------------------------\n  bool ready()\n  {\n    if (mArrived) {\n      return true;\n    }\n\n    return mFut.isReady();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get a reference to the object itself. Unlike std::future::get, we only\n  //! return a reference, not a copy of the object, and you can call this\n  //! function as many times as you like.\n  //!\n  //! @note Beware of exceptions! They will be propagated from the underlying\n  //! future\n  //----------------------------------------------------------------------------\n  T& get()\n  {\n    wait();\n\n    if (mException) {\n      std::rethrow_exception(mException);\n    }\n\n    return mObj;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Convenience function, use -> to access members of the underlying object\n  //!\n  //! @note  Beware of exceptions! They will be propagated from the underlying\n  //! future.\n  //----------------------------------------------------------------------------\n  T* operator->()\n  {\n    wait();\n\n    if (mException) {\n      std::rethrow_exception(mException);\n    }\n\n    return &mObj;\n  }\n\n  const T* operator->() const\n  {\n    wait();\n\n    if (mException) {\n      std::rethrow_exception(mException);\n    }\n\n    return &mObj;\n  }\n\n\n  //----------------------------------------------------------------------------\n  //! Check if future is armed with an exception - will wait to receive result\n  //----------------------------------------------------------------------------\n  bool hasException()\n  {\n    wait();\n    return mException != nullptr;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Method that waits for the underlying future to return\n  //----------------------------------------------------------------------------\n  void wait() const\n  {\n    if (mArrived) {\n      return;\n    }\n\n    mArrived = true;\n\n    try {\n      mObj = std::move(mFut).get();\n    } catch (...) {\n      mException = std::current_exception();\n    }\n  }\n\nprivate:\n\n\n  mutable folly::Future<T> mFut;\n  mutable bool mArrived = false;\n  mutable std::exception_ptr mException;\n  mutable T mObj;\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Glob.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Glob.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//-----------------------------------------------------------------------------\n//! @brief  Class applying bash pattern amtching\n//-----------------------------------------------------------------------------\n#include \"common/Glob.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\neos::common::Glob* eos::common::Glob::gGlob(nullptr);\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Glob.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Glob.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//-----------------------------------------------------------------------------\n//! @brief  Class applying bash pattern amtching\n//-----------------------------------------------------------------------------\n#pragma once\n#include <glob.h>\n#include <mutex>\n#include <string.h>\n#include <vector>\n#include <stdexcept>\n#include <string>\n#include <sstream>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <dirent.h>\n#include \"common/Namespace.hh\"\n#include \"common/Path.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Static Class running bash pattern matching\n//! To use this static functions include this header file\n//! Example (call this for each entry in a listing)\n//!   using eos::common::Glob;\n//!   Glob glob;\n//!   glob.Match(\"asdf*.txt\", \"asdf1.txt\"; (true in this case, otherwise false)\n//------------------------------------------------------------------------------\nclass Glob\n{\npublic:\n  //----------------------------------------------------------------------------\n  //!\n  //----------------------------------------------------------------------------\n\n  Glob() : mIt(0) {}\n\n  virtual ~Glob() {}\n  static void *opendir(const char *name) {\n    return (DIR*) (gGlob);\n  }\n\n  static struct dirent *readdir(void *dirp) {\n    Glob* thisglob = (Glob*) dirp;\n    return thisglob->getEntry();\n  }\n\n  static void closedir(void *dirp) {return;}\n  static int stat(const char *pathname, struct stat *statbuf)  { statbuf->st_mode = S_IFREG; return 0;}\n  static int lstat(const char *pathname, struct stat *statbuf) { statbuf->st_mode = S_IFREG; return 0;}\n\n  struct dirent* getEntry() {\n    if (mIt < mNames.size()) {\n      mEntry.d_ino = mIt+1; // 0 leads to NOMATCH with glibc 2-17!!!\n#ifdef __LINUX__\n      mEntry.d_off = mIt;\n #endif\n      mEntry.d_reclen = 255;\n      mEntry.d_type = DT_REG;;\n      snprintf(mEntry.d_name, mEntry.d_reclen, \"%s\",mNames[mIt++].c_str());\n      return &mEntry;\n    } else {\n      return nullptr;\n    }\n  }\n\n  bool Match(const std::string& pattern, const std::string& path) {\n    static std::mutex g_i_mutex;\n    mIt=0;\n    mNames.clear();\n    std::lock_guard<std::mutex> lock(g_i_mutex);\n    gGlob = this;\n    bool result = false;\n    eos::common::Path cPath(path.c_str());\n    mNames.resize(1);\n    mNames[0] = cPath.GetName();\n    glob_t globbuf;\n    memset(&globbuf,0, sizeof(globbuf));\n    //    globbuf.gl_offs = 2;\n    globbuf.gl_opendir=opendir;\n    globbuf.gl_readdir=readdir;\n    globbuf.gl_closedir=closedir;\n    globbuf.gl_stat=stat;\n    globbuf.gl_lstat=lstat;\n\n    glob(pattern.c_str(), GLOB_ALTDIRFUNC, NULL, &globbuf);\n    if (globbuf.gl_pathc && mIt){ //mIt = 1 if getEntry() was called\n      result = true;\n    } else {\n      result = false;\n    }\n    globfree(&globbuf);\n    return result;\n  }\n\n  static Glob* gGlob;\n\nprivate:\n  std::vector<std::string> mNames;\n  uint64_t mIt;\n  struct dirent mEntry;\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/IRWMutex.hh",
    "content": "//------------------------------------------------------------------------------\n// File: IRWMutex.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include <stdint.h>\n\nEOSCOMMONNAMESPACE_BEGIN\n\nclass IRWMutex\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  // ---------------------------------------------------------------------------\n  IRWMutex() = default;\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~IRWMutex() = default;\n\n  //----------------------------------------------------------------------------\n  //! Lock for read\n  //----------------------------------------------------------------------------\n  virtual int LockRead() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Try lock for read (shared)\n  //!\n  //! @return 0 if lock acquired successfully, otherwise non-zero\n  //----------------------------------------------------------------------------\n  virtual int TryLockRead() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Unlock a read lock\n  //----------------------------------------------------------------------------\n  virtual int UnLockRead() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Try to read lock the mutex within the timeout\n  //!\n  //! @param timeout_ns nano seconds timeout\n  //!\n  //! @return 0 if successful, otherwise error number\n  //----------------------------------------------------------------------------\n  virtual int TimedRdLock(uint64_t timeout_ns) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Lock for write\n  //----------------------------------------------------------------------------\n  virtual int LockWrite() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Try lock for write (exclusive)\n  //!\n  //! @return 0 if lock acquired successfully, otherwise non-zero\n  //----------------------------------------------------------------------------\n  virtual int TryLockWrite() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Unlock a write lock\n  //----------------------------------------------------------------------------\n  virtual int UnLockWrite() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Try to write lock the mutex within the timeout\n  //!\n  //! @param timeout_ns nano seconds timeout\n  //!\n  //! @return 0 if successful, otherwise error number\n  //----------------------------------------------------------------------------\n  virtual int TimedWrLock(uint64_t timeout_ns) = 0;\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/InodeTranslator.hh",
    "content": "//------------------------------------------------------------------------------\n// File: InodeTranslator.hh\n// Author: Georgios Bitzes <georgios.bitzes@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include \"common/FileId.hh\"\n#include \"common/Logging.hh\"\n#include <sstream>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Translate inodes while respecting the initial encoding scheme.\n//! You MUST call InodeToFid BEFORE FidToInode - otherwise, we don't know\n//! which encoding scheme to apply in FidToInode.\n//------------------------------------------------------------------------------\nclass InodeTranslator {\npublic:\n  InodeTranslator() {}\n\n  unsigned long long InodeToFid(unsigned long long inode) {\n    if(inode == 0) return 0; // invalid inode\n\n    if(encodingScheme == EncodingScheme::kUninitialized) {\n      initialize(inode);\n    }\n\n    if(encodingScheme == EncodingScheme::kLegacy && !FileId::LegacyIsFileInode(inode)) {\n      std::string err = SSTR(\"Configured to use legacy encoding scheme, but encountered inode which is not recognized as legacy: \" << inode);\n      eos_static_crit(err.c_str());\n      std::cerr << err << std::endl;\n      std::abort();\n    }\n\n    if(encodingScheme == EncodingScheme::kLegacy && FileId::NewIsFileInode(inode)) {\n      std::string err = SSTR(\"Configured to use legacy encoding scheme, but encountered inode which is recognized as new: \" << inode);\n      eos_static_crit(err.c_str());\n      std::cerr << err << std::endl;\n      std::abort();\n    }\n\n    if(encodingScheme == EncodingScheme::kNew && !FileId::NewIsFileInode(inode)) {\n      std::string err = SSTR(\"Configured to use new encoding scheme, but encountered inode which is not recognized as new: \" << inode);\n      eos_static_crit(err.c_str());\n      std::cerr << err << std::endl;\n      std::abort();\n    }\n\n    return FileId::InodeToFid(inode);\n  }\n\n  unsigned long long FidToInode(unsigned long long fid) {\n    if(encodingScheme == EncodingScheme::kUninitialized) {\n      std::string err = SSTR(\"Attempted to convert from file ID (\" << fid << \") to inode before discovering the inode encoding scheme.\");\n      eos_static_crit(err.c_str());\n      std::cerr << err << std::endl;\n      std::abort();\n    }\n\n    if(encodingScheme == EncodingScheme::kLegacy) {\n      return FileId::LegacyFidToInode(fid);\n    }\n\n    return FileId::NewFidToInode(fid);\n  }\n\nprivate:\n  void initialize(unsigned long long inode) {\n    if(FileId::NewIsFileInode(inode)) {\n      encodingScheme = EncodingScheme::kNew;\n      eos_static_notice(\"Initializing inode translator using new encoding scheme. (seed inode: %llu)\", inode);\n    }\n    else {\n      encodingScheme = EncodingScheme::kLegacy;\n      eos_static_notice(\"Initializing inode translator using legacy encoding scheme. (seed inode: %llu)\", inode);\n    }\n  }\n\n  enum class EncodingScheme {\n    kLegacy,\n    kNew,\n    kUninitialized\n  };\n\n  EncodingScheme encodingScheme = EncodingScheme::kUninitialized;\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/InstanceName.cc",
    "content": "//------------------------------------------------------------------------------\n// File: InstanceName.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/InstanceName.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Assert.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\neos::common::RWMutex InstanceName::mMutex;\nstd::string InstanceName::mInstanceName;\n\n//------------------------------------------------------------------------------\n// Set eos instance name - call this only once\n//------------------------------------------------------------------------------\nvoid InstanceName::set(const std::string& name)\n{\n  eos::common::RWMutexWriteLock wr_lock(mMutex);\n  eos_static_info(\"Setting global instance name => %s\", name.c_str());\n  eos_assert(mInstanceName.empty());\n  eos_assert(!name.empty());\n  mInstanceName = name;\n}\n\n//------------------------------------------------------------------------------\n// Get eos instance name\n//------------------------------------------------------------------------------\nstd::string InstanceName::get()\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  eos_assert(!mInstanceName.empty());\n  return mInstanceName;\n}\n\n//------------------------------------------------------------------------------\n// Get MGM global config queue\n//------------------------------------------------------------------------------\nstd::string InstanceName::getGlobalMgmConfigQueue()\n{\n  return SSTR(\"/config/\" << InstanceName::get() << \"/mgm/\");\n}\n\n//------------------------------------------------------------------------------\n// Has the instance name been set?\n//------------------------------------------------------------------------------\nbool InstanceName::empty()\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  return mInstanceName.empty();\n}\n\n//------------------------------------------------------------------------------\n// Clear stored instance name - used in unit tests\n//------------------------------------------------------------------------------\nvoid InstanceName::clear()\n{\n  eos::common::RWMutexWriteLock wr_lock(mMutex);\n  mInstanceName.clear();\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/InstanceName.hh",
    "content": "//------------------------------------------------------------------------------\n// File: InstanceName.hh\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_COMMON_INSTANCE_NAME_HH\n#define EOS_COMMON_INSTANCE_NAME_HH\n\n#include \"common/Namespace.hh\"\n#include \"common/RWMutex.hh\"\n#include <string>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Simple class to hold the name of the running EOS instance - initialized\n//! during startup.\n//!\n//! Do not use before initialization, and do not initialize twice.\n//------------------------------------------------------------------------------\nclass InstanceName\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Set eos instance name - call this only once\n  //----------------------------------------------------------------------------\n  static void set(const std::string& name);\n\n  //----------------------------------------------------------------------------\n  //! Get eos instance name - do not call before getInstanceName\n  //----------------------------------------------------------------------------\n  static std::string get();\n\n  //----------------------------------------------------------------------------\n  //! Get MGM global config queue\n  //----------------------------------------------------------------------------\n  static std::string getGlobalMgmConfigQueue();\n\n  //----------------------------------------------------------------------------\n  //! Has the instance name been set?\n  //----------------------------------------------------------------------------\n  static bool empty();\n\n  //----------------------------------------------------------------------------\n  //! Clear stored instance name - used in unit tests\n  //----------------------------------------------------------------------------\n  static void clear();\n\nprivate:\n  static std::string mInstanceName;\n  static RWMutex mMutex;\n};\n\nEOSCOMMONNAMESPACE_END\n\n\n#endif\n"
  },
  {
    "path": "common/IntervalStopwatch.cc",
    "content": "// ----------------------------------------------------------------------\n// File: IntervalStopwatch.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/IntervalStopwatch.hh\"\n#include \"common/SteadyClock.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Constructor. It's possible to pass a fake clock for testing - if none is\n//! passed, a default one will be used.\n//!\n//! The first cycle starts as soon as the constructor is called, with the\n//! given duration. (zero by default)\n//------------------------------------------------------------------------------\nIntervalStopwatch::IntervalStopwatch(std::chrono::milliseconds initialCycleDuration,\n  SteadyClock *clock) : mClock(clock) {\n\n  startCycle(initialCycleDuration);\n}\n\n//------------------------------------------------------------------------------\n// Start a cycle from this point onwards, with the given duration.\n// The previously running cycle is discarded.\n//------------------------------------------------------------------------------\nvoid IntervalStopwatch::startCycle(std::chrono::milliseconds duration) {\n  mCycleStart = common::SteadyClock::now(mClock);\n  mCycleDuration = duration;\n}\n\n//------------------------------------------------------------------------------\n// Get timepoint of cycle start\n//------------------------------------------------------------------------------\nstd::chrono::steady_clock::time_point IntervalStopwatch::getCycleStart() const {\n  return mCycleStart;\n}\n\n//------------------------------------------------------------------------------\n// Return how much time has elapsed within this cycle, ie milliseconds since\n// startCycle was called.\n//------------------------------------------------------------------------------\nstd::chrono::milliseconds IntervalStopwatch::timeIntoCycle() const {\n  return std::chrono::duration_cast<std::chrono::milliseconds>(\n    common::SteadyClock::now(mClock) - mCycleStart);\n}\n\n//------------------------------------------------------------------------------\n// Return how much time is remaining in this cycle. If more time has elapsed\n// than the cycle duration, this function returns 0.\n//------------------------------------------------------------------------------\nstd::chrono::milliseconds IntervalStopwatch::timeRemainingInCycle() const {\n  std::chrono::milliseconds elapsed =\n    std::chrono::duration_cast<std::chrono::milliseconds>(\n      common::SteadyClock::now(mClock) - mCycleStart);\n  std::chrono::milliseconds remaining = mCycleDuration - elapsed;\n\n  if(remaining < std::chrono::milliseconds(0)) {\n    remaining = std::chrono::milliseconds(0);\n  }\n\n  return remaining;\n}\n\n//------------------------------------------------------------------------------\n// Convenience function - if the cycle has expired:\n// - Restart from this point on, with the same exact duration.\n// - Return true.\n// Else, return false and do nothing else.\n//------------------------------------------------------------------------------\nbool IntervalStopwatch::restartIfExpired() {\n  if(timeRemainingInCycle() == std::chrono::milliseconds(0)) {\n    startCycle(mCycleDuration);\n    return true;\n  }\n\n  return false;\n}\n\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/IntervalStopwatch.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Georgios Bitzes <georgios.bitzes@cern.ch>\n// desc:   A stopwatch which measures duration of cyclic events\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include <chrono>\n\nEOSCOMMONNAMESPACE_BEGIN\n\nclass SteadyClock;\n\n//------------------------------------------------------------------------------\n//! We often have the following pattern for background threads:\n//! - Start of event\n//! - ....\n//! - End of event\n//! - Sleep\n//!\n//! We want \"event\" to happen every 1 hour, for example. If the duration of\n//! \"event\" was 10 minutes, we would want to sleep for 50 minutes before\n//! starting the cycle once again. This class simplifies the above pattern,\n//! essentially telling you for how long to sleep.\n//------------------------------------------------------------------------------\nclass IntervalStopwatch {\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor. It's possible to pass a fake clock for testing - if none is\n  //! passed, a default one will be used.\n  //!\n  //! The first cycle starts as soon as the constructor is called, with the\n  //! given duration. (zero by default)\n  //----------------------------------------------------------------------------\n  IntervalStopwatch(std::chrono::milliseconds initialCycleDuration = std::chrono::milliseconds(0),\n    SteadyClock *clock = nullptr);\n\n  //----------------------------------------------------------------------------\n  //! Start a cycle from this point onwards, with the given duration.\n  //! The previously running cycle is discarded.\n  //----------------------------------------------------------------------------\n  void startCycle(std::chrono::milliseconds duration);\n\n  //----------------------------------------------------------------------------\n  //! Get timepoint of cycle start\n  //----------------------------------------------------------------------------\n  std::chrono::steady_clock::time_point getCycleStart() const;\n\n  //----------------------------------------------------------------------------\n  //! Return how much time has elapsed within this cycle, ie milliseconds since\n  //! startCycle was called.\n  //----------------------------------------------------------------------------\n  std::chrono::milliseconds timeIntoCycle() const;\n\n  //----------------------------------------------------------------------------\n  //! Return how much time is remaining in this cycle. If more time has elapsed\n  //! than the cycle duration, this function returns 0.\n  //----------------------------------------------------------------------------\n  std::chrono::milliseconds timeRemainingInCycle() const;\n\n  //----------------------------------------------------------------------------\n  //! Convenience function - if the cycle has expired:\n  //! - Restart from this point on, with the same exact duration.\n  //! - Return true.\n  //! Else, return false and do nothing else.\n  //----------------------------------------------------------------------------\n  bool restartIfExpired();\n\nprivate:\n  SteadyClock *mClock; //< The internal clock object of this class, can be null.\n  std::chrono::steady_clock::time_point mCycleStart; //< The point at which the\n                                                     //< current cycle started\n  std::chrono::milliseconds mCycleDuration;          //< The current duration\n};\n\n\nEOSCOMMONNAMESPACE_END\n\n"
  },
  {
    "path": "common/IoPipe.hh",
    "content": "// ----------------------------------------------------------------------\n// File: IoPipe.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   IoPipe.hh\n *\n * @brief  Class providing one-directional pipes for stdout,stderr & retc bridging between processes.\n *\n *\n */\n\n#ifndef __EOSCOMMON_IOPIPE__\n#define __EOSCOMMON_IOPIPE__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/Namespace.hh\"\n#include \"common/Path.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdSys/XrdSysLogger.hh>\n#include <XrdNet/XrdNetOpts.hh>\n#include <XrdNet/XrdNetSocket.hh>\n/*----------------------------------------------------------------------------*/\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <sys/file.h>\n#include <unistd.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <iostream>\n#include <fstream>\n/*----------------------------------------------------------------------------*/\n\nEOSCOMMONNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\n//! Class Implementing communication via local unix pipes.\n//! Each IoPipe uses three pipes to bridge stdout,stderr & return code.\n//! IoPipes need to be locked because there can be only one Producer and one Consumer.\n//! IoPipe is used by console/ConsoleMain.cc to allow persistent connection of the xrootd client.\n/*----------------------------------------------------------------------------*/\n\nclass IoPipe\n{\npublic:\n  XrdOucString lPrefix;   //< directory to store pipes\n  XrdOucString\n  lPipeDir;  //< directory including process uid and parent pid dependent\n  XrdOucString lPipeProducerLock; //< lock for the producer\n  XrdOucString lPipeConsumerLock; //< lock for the consumer\n\n  XrdOucString lStdInName ;\n  XrdOucString lStdOutName;\n  XrdOucString lStdErrName;\n  XrdOucString lRetcName  ;\n\n  XrdNetSocket* stdinsocket;\n  XrdNetSocket* stdoutsocket;\n  XrdNetSocket* stderrsocket;\n  XrdNetSocket* retcsocket;\n\n  int lConsumerFd;\n  int lProducerFd;\n\n  // ---------------------------------------------------------------------------\n  //! Constructor\n  // ---------------------------------------------------------------------------\n  IoPipe(XrdOucString prefix = \"/tmp/eos\")\n  {\n    lPrefix = prefix;\n    lPipeDir = prefix;\n    lPipeDir += \".\";\n    lPipeDir += (int) getuid();\n    lPipeDir += \"/\";\n    lPipeDir += (int) getppid();\n    lPipeDir += \"/\";\n    lPipeProducerLock = lPipeDir + \"producer.lock\";\n    lPipeConsumerLock = lPipeDir + \"consumer.lock\";\n    lStdInName  = \"xstdin\";\n    lStdOutName = \"xstdout\";\n    lStdErrName = \"xstderr\";\n    lRetcName   = \"xretc\";\n    stdinsocket = stdoutsocket = stderrsocket = retcsocket = 0;\n    lProducerFd = lConsumerFd = 0;\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Write a pid into a file\n  // ---------------------------------------------------------------------------\n  void WritePid(const char* path, pid_t pid)\n  {\n    std::ofstream pidfile(path, std::ios::binary);\n    pidfile << pid ;\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Read a pid from a file\n  // ---------------------------------------------------------------------------\n  pid_t ReadPid(const char* path)\n  {\n    pid_t lpid = 0;\n    std::ifstream pidfile(path);\n    pidfile  >> lpid;\n    return lpid;\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Initialize IoPipe creating all needed parent directories\n  // ---------------------------------------------------------------------------\n  bool Init()\n  {\n    XrdOucString dummypipedir = lPipeDir;\n    dummypipedir += \"/dummy\";\n    eos::common::Path dPath(dummypipedir.c_str());\n\n    if (!dPath.MakeParentPath(S_IRWXU)) {\n      return false;\n    }\n\n    return true;\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Write our Pid into the producer pid file\n  // ---------------------------------------------------------------------------\n  bool LockProducer()\n  {\n    int fd = open(lPipeProducerLock.c_str(), O_EXCL | O_CREAT, S_IRWXU);\n\n    if (fd >= 0) {\n      close(fd);\n      WritePid(lPipeProducerLock.c_str(), getpid());\n      return true;\n    }\n\n    return false;\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Check if the stored producer is alive\n  // ---------------------------------------------------------------------------\n  bool CheckProducer()\n  {\n    pid_t pid = ReadPid(lPipeProducerLock.c_str());\n\n    if (pid) {\n      if (!kill(pid, 0)) {\n        return true;\n      }\n    }\n\n    UnLockProducer();\n    return false;\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Kill the current producer\n  // ---------------------------------------------------------------------------\n  bool KillProducer()\n  {\n    pid_t pid = ReadPid(lPipeProducerLock.c_str());\n\n    if (pid) {\n      if (!kill(pid, 3)) {\n        return true;\n      }\n    }\n\n    UnLockProducer();\n    return false;\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Write our Pid into the consumer pid file\n  // ---------------------------------------------------------------------------\n  bool LockConsumer()\n  {\n    do {\n      int fd = open(lPipeConsumerLock.c_str(), O_EXCL | O_CREAT, S_IRWXU);\n\n      if (fd >= 0) {\n        close(fd);\n        WritePid(lPipeConsumerLock.c_str(), getpid());\n        return true;\n      }\n\n      usleep(100000);\n    } while (1);\n\n    return false;\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Remove the producer pid file\n  // ---------------------------------------------------------------------------\n  bool UnLockProducer()\n  {\n    int rc = unlink(lPipeProducerLock.c_str());\n\n    if (!rc) {\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Remove the consumer pid file\n  // ---------------------------------------------------------------------------\n  bool UnLockConsumer()\n  {\n    int rc = unlink(lPipeConsumerLock.c_str());\n\n    if (!rc) {\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n\n  // ---------------------------------------------------------------------------\n  //! Attach to the stdin pipe\n  // ---------------------------------------------------------------------------\n  int AttachStdin(XrdSysError& eDest)\n  {\n    XrdNetSocket* socket    = XrdNetSocket::Create(&eDest, lPipeDir.c_str(),\n                              lStdInName.c_str(),  S_IRWXU, XRDNET_FIFO);\n\n    if (socket) {\n      stdinsocket = socket;\n      return socket->SockNum();\n    } else {\n      return -1;\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Attach to the stdout pipe\n  // ---------------------------------------------------------------------------\n  int AttachStdout(XrdSysError& eDest)\n  {\n    XrdNetSocket* socket = XrdNetSocket::Create(&eDest, lPipeDir.c_str(),\n                           lStdOutName.c_str(), S_IRWXU, XRDNET_FIFO);\n\n    if (socket) {\n      stdoutsocket = socket;\n      return socket->SockNum();\n    } else {\n      return -1;\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Attach to the stderr pipe\n  // ---------------------------------------------------------------------------\n  int AttachStderr(XrdSysError& eDest)\n  {\n    XrdNetSocket* socket = XrdNetSocket::Create(&eDest, lPipeDir.c_str(),\n                           lStdErrName.c_str(), S_IRWXU, XRDNET_FIFO);\n\n    if (socket) {\n      stderrsocket = socket;\n      return socket->SockNum();\n    } else {\n      return -1;\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Attach to the retc pipe\n  // ---------------------------------------------------------------------------\n  int AttachRetc(XrdSysError& eDest)\n  {\n    XrdNetSocket* socket = XrdNetSocket::Create(&eDest, lPipeDir.c_str(),\n                           lRetcName.c_str(), S_IRWXU, XRDNET_FIFO);\n\n    if (socket) {\n      retcsocket = socket;\n      return socket->SockNum();\n    } else {\n      return -1;\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Destructor\n  // ---------------------------------------------------------------------------\n  ~IoPipe() {};\n};\n\n/*----------------------------------------------------------------------------*/\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/JeMallocHandler.cc",
    "content": "// ----------------------------------------------------------------------\n// File: JeMallocHandler.cc\n// Author: Geoffray Adde - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/JeMallocHandler.hh\"\n#include \"common/Logging.hh\"\n#include <dlfcn.h>\n#include <string>\n#include <cstdio>\n#include <XrdOuc/XrdOucString.hh>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nJeMallocHandler::JeMallocHandler():\n  mallctl(0)\n{\n  pJeMallocLoaded = IsJemallocLoader();\n  pCanProfile = pJeMallocLoaded ? IsProfEnabled() : false;\n  pProfRunning = pCanProfile ? IsProfgRunning() : false;\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nJeMallocHandler::~JeMallocHandler()\n{}\n\nbool JeMallocHandler::IsJemallocLoader()\n{\n  bool isloaded = false;\n  void* handle;\n  void* pmallctlnametomib;\n  handle = dlopen(NULL, RTLD_LAZY);\n\n  if (!handle)  {\n    eos_static_err(\"error opening dl symbols : %s. libjemalloc is considered as NOT loaded\",\n                   dlerror());\n    //fputs (dlerror(), stderr);\n    return isloaded;\n  }\n\n  pmallctlnametomib = dlsym(handle, \"mallctlnametomib\");\n\n  if (dlerror() == NULL) {\n    isloaded = true;\n    pmallctlnametomib = dlsym(handle, \"mallctl\");\n\n    if (dlerror() == NULL) {\n      mallctl = reinterpret_cast<int (*)(const char*, void*, size_t*, void*, size_t)>\n                (pmallctlnametomib);\n    } else {\n      isloaded = false;\n    }\n  }\n\n  dlclose(handle);\n  eos_static_notice(\"jemalloc is %sloaded!\", isloaded ? \"\" : \"NOT \");\n  return isloaded;\n}\n\nbool JeMallocHandler::IsProfEnabled()\n{\n  bool b = false;\n  size_t s = sizeof(bool);\n  int errc = 0;\n\n  if ((errc = mallctl(\"opt.prof\", &b, &s, NULL, 0))) {\n    eos_static_err(\"error reading status of opt.prof : b=%d  s=%d  errc=%d\",\n                   (int)b, (int)s, errc);\n  }\n\n  return b;\n}\n\nbool JeMallocHandler::IsProfgRunning()\n{\n  bool b = false;\n  size_t s = sizeof(bool);\n  int errc = 0;\n\n  if ((errc = mallctl(\"prof.active\", &b, &s, NULL, 0))) {\n    eos_static_err(\"error reading status of prof.active : %d\", errc);\n  }\n\n  return b;\n}\n\nbool JeMallocHandler::StartProfiling()\n{\n  bool b = true;\n  return mallctl(\"prof.active\", NULL, NULL, &b, sizeof(bool)) == 0;\n}\nbool JeMallocHandler::StopProfiling()\n{\n  bool b = false;\n  return mallctl(\"prof.active\", NULL, NULL, &b, sizeof(bool)) == 0;\n}\nbool JeMallocHandler::DumpProfile()\n{\n  return mallctl(\"prof.dump\", NULL, NULL, NULL, 0) == 0;\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/JeMallocHandler.hh",
    "content": "// ----------------------------------------------------------------------\n// File: JeMallocHandler.hh\n// Author: Geoffray Adde - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSCOMMON_JEMALLOCHANDLER__HH__\n#define __EOSCOMMON_JEMALLOCHANDLER__HH__\n\n#include \"common/Namespace.hh\"\n#include <cstddef>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n//! Class JeMallocHandler\n//----------------------------------------------------------------------------\nclass JeMallocHandler\n{\n  bool pJeMallocLoaded;\n  bool pCanProfile;\n  bool pProfRunning;\n  int (*mallctl)(const char*, void*, size_t*, void*, size_t);\n\n  bool IsJemallocLoader();\n\n  bool IsProfEnabled();\n\n  bool IsProfgRunning();\n\npublic:\n  JeMallocHandler();\n  ~JeMallocHandler();\n\n  inline bool JeMallocLoaded()\n  {\n    return pJeMallocLoaded;\n  }\n\n  inline bool CanProfile()\n  {\n    return pCanProfile;\n  }\n\n  inline bool ProfRunning()\n  {\n    return IsProfgRunning();\n  }\n\n  bool StartProfiling();\n  bool StopProfiling();\n  bool DumpProfile();\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif  /* __EOSCOMMON_JEMALLOCHANDLER__HH__ */\n\n"
  },
  {
    "path": "common/LOGGING.md",
    "content": "EOS Logging Guide\n=================\n\nThis document explains how the EOS logging facility works and how to use and configure it in your code. The logging API lives in `common/Logging.hh` and is implemented in `common/Logging.cc`.\n\n\nOverview\n--------\n\n- A global singleton `eos::common::Logging` manages logging.\n- You can log from:\n  - Classes that inherit `eos::common::LogId` using `eos_*` macros.\n  - Static/free functions using `eos_static_*` macros.\n  - Thread functions using `eos_thread_*` macros.\n- Messages are formatted and written to:\n  - `stderr` (always),\n  - optionally to syslog,\n  - optionally to per-file fan-out FILE* streams.\n- Log levels are syslog-compatible: DEBUG, INFO, NOTICE, WARNING, ERR, CRIT, ALERT, EMERG, plus a special SILENT channel.\n- A circular in-memory buffer is also maintained per level for recent messages.\n\n\nBasic usage in classes\n----------------------\n\nIf you have a class, inherit from `eos::common::LogId`. This gives you:\n- `char logId[40]`, `char cident[256]` and `VirtualIdentity vid` members,\n- helpers to set them (e.g. `SetLogId`).\n\nThen use the macros which automatically include function, file and line:\n\n```c++\nclass MyService : public eos::common::LogId {\npublic:\n  void run() {\n    SetLogId(\"my-service-logid\", \"my-svc\");  // optional, sets logId and client identifier\n    eos_info(\"starting service port=%d\", 1094);\n    eos_debug(\"internal state x=%d\", 42);\n    eos_warning(\"slow operation took %d ms\", 512);\n    eos_err(\"operation failed code=%d\", rc);\n  }\n};\n```\n\nMacros available for classes inheriting `LogId`:\n- `eos_debug(...)`, `eos_info(...)`, `eos_notice(...)`, `eos_warning(...)`,\n  `eos_err(...)`, `eos_crit(...)`, `eos_alert(...)`, `eos_emerg(...)`,\n  `eos_log(priority, ...)` and `eos_silent(...)`.\n\nNotes:\n- Messages are emitted only if their level is enabled in the current log mask.\n- These macros use your object’s `logId`, `cident` and `vid`.\n\n\nLogging from static/free functions\n----------------------------------\n\nUse the `eos_static_*` macros. These don’t require a `LogId` instance:\n\n```c++\nvoid helper() {\n  eos_static_info(\"helper started\");\n  if (error) eos_static_err(\"failed: %s\", why.c_str());\n}\n```\n\nAvailable macros:\n`eos_static_debug`, `eos_static_info`, `eos_static_notice`, `eos_static_warning`,\n`eos_static_err`, `eos_static_crit`, `eos_static_alert`, `eos_static_emerg`,\n`eos_static_log`, `eos_static_silent`.\n\n\nLogging from thread functions\n-----------------------------\n\nUse the `eos_thread_*` macros. They expect a thread-local `LogId` named\n`tlLogId` and a `VirtualIdentity vid` in scope. Example:\n\n```c++\nvoid threadMain() {\n  eos::common::LogId tlLogId;\n  tlLogId.SetLogId(eos::common::LogId::GenerateLogId().c_str(), \"worker\");\n  eos::common::VirtualIdentity vid; // fill if available\n  eos_thread_info(\"worker up\");\n}\n```\n\nAvailable macros:\n`eos_thread_debug`, `eos_thread_info`, `eos_thread_notice`, `eos_thread_warning`,\n`eos_thread_err`, `eos_thread_crit`, `eos_thread_alert`, `eos_thread_emerg`.\n\n\nEnabling and filtering logs\n---------------------------\n\nSet the global log level (mask) to include all levels up to a priority:\n\n```c++\neos::common::Logging::GetInstance().SetLogPriority(LOG_INFO); // enables INFO and more severe\n```\n\nCheck whether a given level would log (useful for expensive formatting):\n\n```c++\nif (EOS_LOGS_DEBUG) { /* build detailed diagnostics and eos_debug(...) */ }\n```\n\nFunction-level filtering is supported via allow/deny lists:\n\n```c++\n// Deny list (comma-separated): suppress logs from these function names at INFO+\neos::common::Logging::GetInstance().SetFilter(\"noisyFunc,spammyFunc\");\n\n// Pass-through (accept) list: only these functions will log at INFO+.\neos::common::Logging::GetInstance().SetFilter(\"PASS:importantFunc,criticalFunc\");\n```\n\nNotes:\n- Filtering applies to levels INFO and above (to reduce flood).\n- Use raw function identifiers (`__FUNCTION__` names).\n\n\nSyslog duplication\n------------------\n\nDuplicate messages to syslog:\n\n- At runtime:\n  ```c++\n  eos::common::Logging::GetInstance().SetSysLog(true);\n  ```\n- Or via environment variable at process start:\n  ```\n  EOS_LOG_SYSLOG=1    # or \"true\"\n  ```\n\nZSTD-compressed rotating logs (replacement mode)\n------------------------------------------------\n\nLogging can optionally replace stdout/fan-out outputs with ZSTD-compressed, time-rotated files (similar to the audit logger).\n\n- Enable via environment:\n  ```\n  EOS_ZSTD_LOGGING=1            # enable compressed logging (replaces fan-out files)\n  EOS_ZSTD_LOGGING_ROTATION=3600  # rotate every N seconds (default: 3600 = 1 hour)\n  EOS_ZSTD_LOGGING_LEVEL=1        # optional compression level (1..19), default 1\n  ```\n- Location:\n  - Base directory: `$XRDLOGDIR` if set; otherwise `/var/log/eos/<service>`.\n  - Real segments are stored under `<base>/logs/`.\n  - Top-level symlinks remain in `<base>/` and are relative:\n    - Main: `<base>/xrdlog.<service>.zstd -> logs/xrdlog.<service>-YYYYmmdd-HHMMSS.zst`\n    - Per-tag: `<base>/<tag>.zstd -> logs/<tag>-YYYYmmdd-HHMMSS.zst`\n- Behavior:\n  - A ZSTD frame header is flushed immediately when a new segment opens to avoid “unexpected end of file” for empty segments.\n  - Each message is flushed to make the stream tail-able (e.g., with `zstdcat` or a `zstdtail` utility).\n  - Rotation creates fresh segments under `logs/`; per-stream top-level symlinks are atomically updated to relative targets.\n  - When enabled, stdout printing and fan-out FILE* writes are suppressed (compressed streams are authoritative). Stderr is redirected and its lines are written to the main compressed stream.\n  - On startup in ZSTD mode, if a plain `xrdlog.<service>` exists with content (e.g., created by XRootD early), its contents are migrated into the compressed main stream and the plain file is unlinked.\n  - MGM-only: the cluster-wide error-report listener (previously `/var/log/eos/mgm/error.log`) switches to a compressed, rotating stream at `<base>/error.zstd -> logs/error-YYYYmmdd-HHMMSS.zst`. When ZSTD logging is disabled the plain `error.log` behaviour is preserved.\n\nPer-tag file names in ZSTD mode\n-------------------------------\n\n- Only canonical fan-out tags produce per-tag `.zst` streams. The allowed set matches MGM’s fan-out list:\n  `Grpc, Balancer, Converter, DrainJob, ZMQ, MetadataFlusher, Http, Master, Recycle, LRU, WFE, Wnc, WFE::Job, GroupBalancer, GroupDrainer, GeoBalancer, GeoTreeEngine, ReplicationTracker, FileInspector, Mounts, OAuth, TokenCmd`\n- Source module names are mapped to this set using MGM’s aliasing (e.g., `HttpHandler` → `Http`, `Drainer` → `DrainJob`). Non-matching module names are skipped to avoid proliferation of files.\n\n\nFan-out to additional files\n---------------------------\n\nYou can route log lines to additional FILE* streams based on source file tags:\n\n```c++\n// Send all messages to a file\nFILE* all = fopen(\"/var/log/eos/all.log\", \"a\");\neos::common::Logging::GetInstance().AddFanOut(\"*\", all);\n\n// Send messages that don’t match any other tag (besides “*”) to a file\nFILE* other = fopen(\"/var/log/eos/other.log\", \"a\");\neos::common::Logging::GetInstance().AddFanOut(\"#\", other);\n\n// Send messages originating from Foo.cc to a dedicated file (tag is the basename without .cc)\nFILE* foo = fopen(\"/var/log/eos/foo.log\", \"a\");\neos::common::Logging::GetInstance().AddFanOut(\"Foo\", foo);\n```\n\nRules:\n- Tag is the source filename basename without extension (e.g. `Foo` for `Foo.cc`).\n- `*` routes all messages; `#` routes messages not claimed by any other tag.\n- Fan-out writes are flushed after each message.\n\n\nRate limiting\n-------------\n\nTo suppress repetitive bursts (same file/line/priority), enable the rate limiter:\n\n```c++\neos::common::Logging::GetInstance().EnableRateLimiter();\n```\n\nWhen enabled, repeated messages at levels below WARNING (i.e. INFO/NOTICE/DEBUG)\nfrom the same source location within ~5 seconds may be suppressed with a single\n“suppressed” indicator line.\n\n\nCircular in-memory buffer\n-------------------------\n\nFor each priority level, a circular buffer of recent messages is kept internally\n(default size: `EOSCOMMONLOGGING_CIRCULARINDEXSIZE`, 10,000). This is used\ninternally for diagnostics and can be resized:\n\n```c++\neos::common::Logging::GetInstance().SetIndexSize(20000);\n```\n\n\nShort format\n------------\n\nLogging supports a compact “short” format (internal switch) that prints abbreviated\nlines (time, func, level, thread id, and source). The default is the verbose format,\nwhich also includes identifiers (logid, unit, client identity).\n\n\nSpecial channel: SILENT\n-----------------------\n\n`LOG_SILENT` can be used with `eos_silent(...)` or `eos_static_silent(...)` to always\nformat and enqueue a message without level checks. Internally it maps to DEBUG for\nthe circular buffer, but is not filtered by the log mask.\n\n\nInitialization and lifecycle\n----------------------------\n\n- The logging singleton is constructed via a static initializer (`sLoggingInit`)\n  in each translation unit that includes `Logging.hh`. You do not need to create it manually.\n- A background thread prints log buffers to `stderr` and fan-out streams.\n- Call `eos::common::Logging::GetInstance().shutDown(/*gracefully=*/true)` during\n  controlled shutdown if you need a graceful drain of the queue (optional).\n\n\nBest practices\n--------------\n\n- Set the unit name once at process start (used in the verbose format):\n  ```c++\n  eos::common::Logging::GetInstance().SetUnit(\"mgm\");\n  ```\n- For services, derive from `LogId` to benefit from structured fields (`logId`, `cident`, `vid`).\n- Use `EOS_LOGS_DEBUG` checks for heavy debug-only formatting.\n- Prefer function-level filtering (deny or pass lists) to reduce noise.\n- Use fan-out carefully; keep file descriptors open for the process lifetime.\n\n\nEnvironment variables\n---------------------\n\n- `EOS_LOG_SYSLOG=1|true` — duplicate log messages to syslog in addition to `stderr`.\n- `EOS_MGM_LOG_BUFFERS=<N>` — cap the maximum number of internal log buffers (default 2048).\n\n\nAPI reference (selected)\n------------------------\n\n- `Logging::SetLogPriority(int pri)` — set active log levels (e.g. `LOG_INFO`).\n- `Logging::SetFilter(const char* filter)` — set accept or deny filters.\n- `Logging::SetUnit(const char* unit)` — set unit (service) tag.\n- `Logging::SetSysLog(bool onoff)` — enable/disable syslog duplication.\n- `Logging::EnableRateLimiter()` — enable rate limiting of repetitive messages.\n- `Logging::AddFanOut(const char* tag, FILE* fd)` — add a fan-out sink (`*`, `#`, or source file tag).\n- `Logging::AddFanOutAlias(const char* alias, const char* tag)` — alias one tag to another.\n- `Logging::SetIndexSize(size_t size)` — size of per-level circular memory.\n\n\nExamples\n--------\n\nMinimal service setup:\n\n```c++\nint main() {\n  using eos::common::Logging;\n  auto& log = Logging::GetInstance();\n  log.SetUnit(\"mgm\");\n  log.SetLogPriority(LOG_INFO);\n  log.SetSysLog(true);\n  log.EnableRateLimiter();\n  eos_static_info(\"service starting\");\n  // ...\n}\n```\n\nRoute `Server.cc` logs to a separate file:\n\n```c++\nFILE* fx = fopen(\"/var/log/eos/fxserver.log\", \"a\");\neos::common::Logging::GetInstance().AddFanOut(\"Server\", fx); // from FuseServer/Server.cc\n```\n\n\n\n"
  },
  {
    "path": "common/LRU.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Adapted by Andreas-Joachim Peters <andreas.joachim.peters@cern.ch>   *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/************************************************************************/\n/* Github: https://github.com/mohaps/lrucache11                         */\n/* Copyright (c) 2012-22 SAURAV MOHAPATRA <mohaps@gmail.com>            */\n/************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include <algorithm>\n#include <cstdint>\n#include <list>\n#include <mutex>\n#include <stdexcept>\n#include <thread>\n#include <unordered_map>\n\n\nEOSCOMMONNAMESPACE_BEGIN\n\nnamespace LRU {\n\nclass NullLock {\n public:\n  void lock() {}\n  void unlock() {}\n  bool try_lock() { return true; }\n};\n\n/**\n * error raised when a key not in cache is passed to get()\n */\nclass KeyNotFound : public std::invalid_argument {\n public:\n  KeyNotFound() : std::invalid_argument(\"key_not_found\") {}\n};\n\ntemplate <typename K, typename V>\nstruct KeyValuePair {\n public:\n  K key;\n  V value;\n\n  KeyValuePair(const K& k, const V& v) : key(k), value(v) {}\n};\n\n/**\n *\tThe LRU Cache class templated by\n *\t\tKey - key type\n *\t\tValue - value type\n *\t\tMapType - an associative container like std::unordered_map\n *\t\tLockType - a lock type derived from the Lock class (default:\n *NullLock = no synchronization)\n *\n *\tThe default NullLock based template is not thread-safe, however passing\n *Lock=std::mutex will make it\n *\tthread-safe\n */\ntemplate <class Key, class Value, class Lock = NullLock,\n          class Map = std::unordered_map<\n              Key, typename std::list<KeyValuePair<Key, Value>>::iterator>>\nclass Cache {\n public:\n  typedef KeyValuePair<Key, Value> node_type;\n  typedef std::list<KeyValuePair<Key, Value>> list_type;\n  typedef Map map_type;\n  typedef Lock lock_type;\n  using Guard = std::lock_guard<lock_type>;\n  /**\n   * the maxSize is the soft limit of keys and (maxSize + elasticity) is the\n   * hard limit\n   * the cache is allowed to grow till (maxSize + elasticity) and is pruned back\n   * to maxSize keys\n   * set maxSize = 0 for an unbounded cache (but in that case, you're better off\n   * using a std::unordered_map\n   * directly anyway! :)\n   */\n  explicit Cache(size_t maxSize = 64, size_t elasticity = 10)\n      : maxSize_(maxSize), elasticity_(elasticity) {}\n  virtual ~Cache() = default;\n\n  size_t  setMaxSize(size_t maxSize) {\n    maxSize_ = maxSize;\n    return maxSize;\n  }\n\n  size_t size() const {\n    Guard g(lock_);\n    return cache_.size();\n  }\n  bool empty() const {\n    Guard g(lock_);\n    return cache_.empty();\n  }\n  void clear() {\n    Guard g(lock_);\n    cache_.clear();\n    keys_.clear();\n  }\n  void insert(const Key& k, const Value& v) {\n    Guard g(lock_);\n    const auto iter = cache_.find(k);\n    if (iter != cache_.end()) {\n      iter->second->value = v;\n      keys_.splice(keys_.begin(), keys_, iter->second);\n      return;\n    }\n\n    keys_.emplace_front(k, v);\n    cache_[k] = keys_.begin();\n    prune();\n  }\n  bool tryGet(const Key& kIn, Value& vOut) {\n    Guard g(lock_);\n    const auto iter = cache_.find(kIn);\n    if (iter == cache_.end()) {\n      return false;\n    }\n    keys_.splice(keys_.begin(), keys_, iter->second);\n    vOut = iter->second->value;\n    return true;\n  }\n  /**\n   *\tThe const reference returned here is only\n   *    guaranteed to be valid till the next insert/delete\n   */\n  const Value& get(const Key& k) {\n    Guard g(lock_);\n    const auto iter = cache_.find(k);\n    if (iter == cache_.end()) {\n      throw KeyNotFound();\n    }\n    keys_.splice(keys_.begin(), keys_, iter->second);\n    return iter->second->value;\n  }\n  /**\n   * returns a copy of the stored object (if found)\n   */\n  Value getCopy(const Key& k) {\n   return get(k);\n  }\n  bool remove(const Key& k) {\n    Guard g(lock_);\n    auto iter = cache_.find(k);\n    if (iter == cache_.end()) {\n      return false;\n    }\n    keys_.erase(iter->second);\n    cache_.erase(iter);\n    return true;\n  }\n  bool contains(const Key& k) const {\n    Guard g(lock_);\n    return cache_.find(k) != cache_.end();\n  }\n\n  size_t getMaxSize() const { return maxSize_; }\n  size_t getElasticity() const { return elasticity_; }\n  size_t getMaxAllowedSize() const { return maxSize_ + elasticity_; }\n  template <typename F>\n  void cwalk(F& f) const {\n    Guard g(lock_);\n    std::for_each(keys_.begin(), keys_.end(), f);\n  }\n\n protected:\n  size_t prune() {\n    size_t maxAllowed = maxSize_ + elasticity_;\n    if (maxSize_ == 0 || cache_.size() < maxAllowed) {\n      return 0;\n    }\n    size_t count = 0;\n    while (cache_.size() > maxSize_) {\n      cache_.erase(keys_.back().key);\n      keys_.pop_back();\n      ++count;\n    }\n    return count;\n  }\n\n private:\n  // Dissallow copying.\n  Cache(const Cache&) = delete;\n  Cache& operator=(const Cache&) = delete;\n\n  mutable Lock lock_;\n  Map cache_;\n  list_type keys_;\n  size_t maxSize_;\n  size_t elasticity_;\n};\n\n} // end namespace LRU\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/LayoutId.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file LayoutId.hh\n//! @author Andreas-Joachim Peters - CERN\n//! @brief Class with static members helping to deal with layout types\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSCOMMON_LAYOUTID__HH__\n#define __EOSCOMMON_LAYOUTID__HH__\n#include \"common/Namespace.hh\"\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdSfs/XrdSfsInterface.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n#include \"StringConversion.hh\"\n#include <fcntl.h>\n#include <string>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class with static members helping to deal with layout types\n//------------------------------------------------------------------------------\nclass LayoutId\n{\npublic:\n  typedef unsigned long layoutid_t;\n\n  // A layout id is constructed as an xor as defined in GetId()\n  static const uint64_t OssXsBlockSize = 4 * 1024; ///< block xs size 4KB\n\n  //--------------------------------------------------------------------------\n  //! Definition of layout errors\n  //--------------------------------------------------------------------------\n  enum eLayoutError {\n    // this is used on FSTs in the Fmd Synchronization\n    kOrphan = 0x1, ///< layout produces an orphan\n    kUnregistered = 0x2, ///< layout has an unregistered stripe\n    kReplicaWrong = 0x4, ///< layout has the wrong number of replicas\n    kMissing = 0x8 ///< layout has an entry which is missing on disk\n  };\n\n\n  //--------------------------------------------------------------------------\n  //! Definition of checksum types\n  //--------------------------------------------------------------------------\n  enum eChecksum {\n    kNone = 0x1,\n    kAdler = 0x2,\n    kCRC32 = 0x3,\n    kMD5 = 0x4,\n    kSHA1 = 0x5,\n    kCRC32C = 0x6,\n    kCRC64  = 0x7,\n    kSHA256 = 0x8,\n    kXXHASH64 = 0x9,\n    kBLAKE3 = 0xa,\n    kHWH64 = 0xb,\n    kXSmax = kHWH64\n  };\n\n\n  //--------------------------------------------------------------------------\n  //! Definition of file layout types\n  //--------------------------------------------------------------------------\n  enum eLayoutType {\n    kPlain = 0x0,\n    kReplica = 0x1,\n    kArchive = 0x2,\n    kRaidDP = 0x3,\n    kRaid6 = 0x4,\n    kQrain = 0x5,\n    kRaid5 = 0x6,\n  };\n\n\n  //--------------------------------------------------------------------------\n  //! Definition of IO types\n  //--------------------------------------------------------------------------\n  enum eIoType {\n    kLocal = 0x0,\n    kXrdCl = 0x1,\n    kNfs = 0x2,\n    kDavix = 0x4\n  };\n\n\n  //--------------------------------------------------------------------------\n  //! Get type of io access based on the given URL (path)\n  //--------------------------------------------------------------------------\n  static eIoType\n  GetIoType(const char* path)\n  {\n    XrdOucString spath = path;\n\n    if (spath.beginswith(\"root:\")) {\n      return kXrdCl;\n    }\n\n    // Definition of predefined block sizes\n    if (spath.beginswith(\"http:\")) {\n      return kDavix;\n    }\n\n    if (spath.beginswith(\"https:\")) {\n      return kDavix;\n    }\n\n    if (spath.beginswith(\"s3:\")) {\n      return kDavix;\n    }\n\n    if (spath.beginswith(\"s3s:\")) {\n      return kDavix;\n    }\n\n    if (spath.beginswith(\"nfs:\")) {\n      return kNfs;\n    }\n\n    return kLocal;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Definition of predefined block sizes\n  //--------------------------------------------------------------------------\n  enum eBlockSize {\n    k4k = 0x0,\n    k64k = 0x1,\n    k128k = 0x2,\n    k512k = 0x3,\n    k1M = 0x4,\n    k4M = 0x5,\n    k16M = 0x6,\n    k64M = 0x7\n  };\n\n  //--------------------------------------------------------------------------\n  //! Get a reed solomon layout by number of redundancy stripes\n  //--------------------------------------------------------------------------\n  static int\n  GetReedSLayoutByParity(int redundancystripes)\n  {\n    switch (redundancystripes) {\n    case 1:\n      return kRaid5;\n\n    case 2:\n      return kRaid6;\n\n    case 3:\n      return kArchive;\n\n    case 4:\n      return kQrain;\n\n    default:\n      return 0;\n    }\n  }\n\n  //--------------------------------------------------------------------------\n  //! Build a layout id from given parameters\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetId(int layout,\n        int checksum = 1,\n        int stripesize = 1,\n        int stripewidth = 0,\n        int blockchecksum = 1,\n        int excessreplicas = 0,\n        int redundancystripes = 0)\n  {\n    unsigned long id = (checksum |\n                        ((layout & 0xf) << 4) |\n                        (((stripesize - 1) & 0xff) << 8) |\n                        ((stripewidth & 0xf) << 16) |\n                        ((blockchecksum & 0xf) << 20) |\n                        ((excessreplicas & 0xf) << 24));\n\n    // Set the number of parity stripes depending on the layout type if not\n    // already set explicitly\n    if (redundancystripes == 0) {\n      redundancystripes = GetRedundancyFromLayoutType(layout);\n    }\n\n    id |= ((redundancystripes & 0x7) << 28);\n    return id;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Convert the blocksize enum to bytes\n  //--------------------------------------------------------------------------\n  static unsigned long\n  BlockSize(int blocksize)\n  {\n    switch (blocksize) {\n    case k4k:\n      return (4 * 1024);\n\n    case k64k:\n      return (64 * 1024);\n\n    case k128k:\n      return (128 * 1024);\n\n    case k512k:\n      return (512 * 1024);\n\n    case k1M:\n      return (1024 * 1024);\n\n    case k4M:\n      return (4 * 1024 * 1024);\n\n    case k16M:\n      return (16 * 1024 * 1024);\n\n    case k64M:\n      return (64 * 1024 * 1024);\n\n    default:\n      return 0;\n    }\n  }\n\n  //--------------------------------------------------------------------------\n  //! Convert bytes to blocksize enum\n  //--------------------------------------------------------------------------\n  static int\n  BlockSizeEnum(unsigned long blocksize)\n  {\n    switch (blocksize) {\n    case (4*1024):\n      return k4k;\n\n    case (64*1024):\n      return k64k;\n\n    case (128*1024):\n      return k128k;\n\n    case (512*1024):\n      return k512k;\n\n    case (1024 * 1024):\n      return k1M;\n\n    case (4 * 1024 * 1024):\n      return k4M;\n\n    case (16 * 1024 * 1024):\n      return k16M;\n\n    case (64 * 1024 * 1024):\n      return k64M;\n\n    default:\n      return 0;\n    }\n  }\n\n  //--------------------------------------------------------------------------\n  //! Get Checksum enum from given layout\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetChecksum(unsigned long layout)\n  {\n    return (layout & 0xf);\n  }\n\n  //--------------------------------------------------------------------------\n  //! Set checksum in the layout encoding\n  //!\n  //! @param layout input layout encoding\n  //! @param xs_type checksum type\n  //!\n  //! @return new layout encoding\n  //--------------------------------------------------------------------------\n  static unsigned long\n  SetChecksum(unsigned long layout, unsigned long xs_type)\n  {\n    xs_type &= 0x0f;\n\n    if ((xs_type < kNone) || (xs_type > kXSmax)) {\n      xs_type = kNone;\n    }\n\n    // Wipe out the old checksum type\n    unsigned long tmp = layout & 0xfffffff0;\n    // Set the new blockxs value\n    tmp |= xs_type;\n    return tmp;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Get length of Layout checksum in bytes\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetChecksumLen(unsigned long layout)\n  {\n    if ((layout & 0xf) == kAdler) {\n      return 4;\n    }\n\n    if ((layout & 0xf) == kCRC32) {\n      return 4;\n    }\n\n    if ((layout & 0xf) == kCRC32C) {\n      return 4;\n    }\n\n    if ((layout & 0xf) == kMD5) {\n      return 16;\n    }\n\n    if ((layout & 0xf) == kSHA1) {\n      return 20;\n    }\n\n    if ((layout & 0xf) == kCRC64) {\n      return 8;\n    }\n\n    if ((layout & 0xf) == kHWH64) {\n      return 8;\n    }\n\n    if ((layout & 0xf) == kSHA256) {\n      return 32;\n    }\n\n    if ((layout & 0xf) == kXXHASH64) {\n      return 8;\n    }\n\n    if ((layout & 0xf) == kBLAKE3) {\n      return 32;\n    }\n\n    return 0;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Get length of Layout checksum in bytes\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetChecksumLen(const std::string& xs_type)\n  {\n    if (xs_type == \"adler\") {\n      return 4;\n    } else if (xs_type == \"blake3\") {\n      return 32;\n    } else if (xs_type == \"crc32\") {\n      return 4;\n    } else if (xs_type == \"crc32c\") {\n      return 4;\n    } else if (xs_type == \"xxhash64\") {\n      return 8;\n    } else if (xs_type == \"crc64\") {\n      return 8;\n    } else if (xs_type == \"md5\") {\n      return 16;\n    } else if (xs_type == \"sha\") {\n      return 20;\n    } else if (xs_type == \"sha256\") {\n      return 32;\n    } else if (xs_type == \"hwh64\") {\n      return 8;\n    } else {\n      return 0;\n    }\n  }\n\n  //--------------------------------------------------------------------------\n  //! Get hex empty file checksum representation depending on the\n  //! checksum type embedded in the layout\n  //!\n  //! @param layout layout type\n  //!\n  //! @return hex checksum value\n  //--------------------------------------------------------------------------\n  static std::string\n  GetEmptyFileHexChecksum(unsigned long layout)\n  {\n    std::string hex_xs;\n\n    switch ((layout & 0xf)) {\n    case kAdler:\n      hex_xs = \"00000001\";\n      break;\n\n    case kCRC32:\n      hex_xs = \"00000000\";\n      break;\n\n    case kCRC32C:\n      hex_xs = \"00000000\";\n      break;\n\n    case kMD5:\n      hex_xs = \"d41d8cd98f00b204e9800998ecf8427e\";\n      break;\n\n    case kSHA1:\n      hex_xs = \"da39a3ee5e6b4b0d3255bfef95601890afd80709\";\n      break;\n\n    case kBLAKE3:\n      hex_xs =\n        \"af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262\";\n      break;\n\n    case kHWH64:\n    case kCRC64:\n    case kXXHASH64:\n      hex_xs =\n        \"0000000000000000\";\n      break;\n\n    default:\n      hex_xs = \"0000000000000000\";\n      break;\n    }\n\n    return hex_xs;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Get binary empty file checksum representation depending on the\n  //! checksum type embedded in the layout\n  //!\n  //! @param layout layout type\n  //!\n  //! @return binary checksum value\n  //--------------------------------------------------------------------------\n  static std::string\n  GetEmptyFileBinChecksum(unsigned long layout)\n  {\n    std::string bin_xs;\n    bin_xs.resize(40);\n    std::string hex_xs = GetEmptyFileHexChecksum(layout);\n\n    for (unsigned int i = 0; i < hex_xs.length(); i += 2) {\n      // hex2binary conversion\n      char hex[3];\n      hex[0] = hex_xs[i];\n      hex[1] = hex_xs[i + 1];\n      hex[2] = 0;\n      bin_xs[i / 2] = strtol(hex, 0, 16);\n    }\n\n    bin_xs.erase(hex_xs.length() / 2);\n    bin_xs.resize(hex_xs.length() / 2);\n    return bin_xs;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return layout type enum\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetLayoutType(unsigned long layout)\n  {\n    return ((layout >> 4) & 0xf);\n  }\n\n  //--------------------------------------------------------------------------\n  //! Test for RAIN layout e.g. raid6, archive, qrain\n  //--------------------------------------------------------------------------\n  static bool\n  IsRain(unsigned long layout)\n  {\n    // everything but plain and replica\n    return (GetLayoutType(layout) > kReplica);\n  }\n\n  //--------------------------------------------------------------------------\n  //! Test for replica layout\n  //--------------------------------------------------------------------------\n  static bool\n  IsReplica(unsigned long layout)\n  {\n    return (GetLayoutType(layout) == kReplica);\n  }\n\n  //--------------------------------------------------------------------------\n  //! Set file layout type in the layout encoding\n  //!\n  //! @param layout input layout encoding\n  //! @param ftype new file layout type\n  //!\n  //! @return new layout encoding\n  //--------------------------------------------------------------------------\n  static unsigned long\n  SetLayoutType(unsigned long layout, unsigned long ftype)\n  {\n    ftype &= 0xf;\n\n    if ((ftype < kPlain) || (ftype > kRaid6)) {\n      ftype = kPlain;\n    }\n\n    // Wipe out the old file layout type\n    unsigned long tmp = layout & 0xfffff0f;\n    // Shift to the right position\n    ftype <<= 4;\n    // Set the new file layout type\n    tmp |= ftype;\n    return tmp;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return layout stripe enum\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetStripeNumber(unsigned long layout)\n  {\n    return ((layout >> 8) & 0xff);\n  }\n\n  //--------------------------------------------------------------------------\n  //! Modify layout stripe number\n  //--------------------------------------------------------------------------\n  static void\n  SetStripeNumber(unsigned long& layout, int stripes)\n  {\n    unsigned long tmp = stripes & 0xff;\n    tmp <<= 8;\n    tmp &= 0xff00;\n    layout &= 0xffff00ff;\n    layout |= tmp;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return layout blocksize in bytes\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetBlocksize(unsigned long layout)\n  {\n    return BlockSize(((layout >> 16) & 0xf));\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return layout blocksize enum\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetBlocksizeType(unsigned long layout)\n  {\n    return ((layout >> 16) & 0xf);\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return layout checksum enum\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetBlockChecksum(unsigned long layout)\n  {\n    // disable block checksum in replica layouts\n    if (GetLayoutType(layout) == kReplica) {\n      return kNone;\n    }\n\n    return ((layout >> 20) & 0xf);\n  }\n\n  //--------------------------------------------------------------------------\n  //! Set blockchecksum in the layout encoding\n  //!\n  //! @param layout input layout encoding\n  //! @param blockxs block checksum type\n  //!\n  //! @return new layout encoding\n  //--------------------------------------------------------------------------\n  static unsigned long\n  SetBlockChecksum(unsigned long layout, unsigned long blockxs)\n  {\n    blockxs &= 0xf;\n\n    if ((blockxs < kNone) || (blockxs > kXSmax)) {\n      blockxs = kNone;\n    }\n\n    // Wipe out the old blockxs type\n    unsigned long tmp = layout & 0xff0fffff;\n    // Shift the blockxs in the right position\n    blockxs <<= 20;\n    // Set the new blockxs value\n    tmp |= blockxs;\n    return tmp;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return excess replicas\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetExcessStripeNumber(unsigned long layout)\n  {\n    return ((layout >> 24) & 0xf);\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return redundancy stripes\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetRedundancyStripeNumber(unsigned long layout)\n  {\n    return ((layout >> 28) & 0x7);\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return redundancy stripes\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetRedundancy(unsigned long layout, unsigned long locations)\n  {\n    if ((GetLayoutType(layout) == kPlain) ||\n        (GetLayoutType(layout) == kReplica)) {\n      return locations;\n    }\n\n    int num_parity_stripes = GetRedundancyStripeNumber(layout);\n    int num_all_stripes = GetStripeNumber(layout) + 1;\n    int redundancy = num_parity_stripes + 1 + (locations - num_all_stripes);\n\n    if (redundancy < 0) {\n      redundancy = 0;\n    }\n\n    // return the remaining redundancy value for a file\n    return redundancy;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return string that is displayed as part of \"ls -y\" that shows there\n  //! locality of the file. d<n>:t<m> where \"d\" means file has replicas on\n  //! disk, <n> is the redundancy for the disk part and similat to tape.\n  //!\n  //! @param has_tape true if tape location available\n  //! @param redundancy number of replicas including tape\n  //! @param size file size\n  //!\n  //! @return string to be displayed as part of the \"ls -y\" output\n  //--------------------------------------------------------------------------\n  static std::string\n  GetRedundancySymbol(bool has_tape, int redundancy, uint64_t size)\n  {\n    // Default disk redundancy value for 0-size files for CTA\n    static const int zero_size_redundancy = 2;\n    char sbst[256];\n    int disk_redundancy = redundancy;\n\n    if (has_tape) {\n      if (size == 0) {\n        disk_redundancy = zero_size_redundancy;\n      } else {\n        if (disk_redundancy > 0) {\n          --disk_redundancy; // substract the tape replica\n        }\n      }\n    }\n\n    snprintf(sbst, sizeof(sbst), \"d%d::t%i \", disk_redundancy,\n             (has_tape ? 1 : 0));\n    return std::string(sbst);\n  }\n\n\n  //--------------------------------------------------------------------------\n  //! Build block checksum layout from block checksum enum\n  //--------------------------------------------------------------------------\n  static unsigned long\n  MakeBlockChecksum(unsigned long xs)\n  {\n    return (xs << 20);\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return length of checksum\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetBlockChecksumLen(unsigned long layout)\n  {\n    return GetChecksumLen((layout >> 20) & 0xf);\n  }\n\n\n  //--------------------------------------------------------------------------\n  //! Get stripe(replica) file size based on the full size of the file and\n  //! the given layout\n  //!\n  //! @param lid layout id\n  //! @param fsize file size\n  //!\n  //! @return stripe(replica) file size\n  //--------------------------------------------------------------------------\n  static uint64_t\n  GetStripeFileSize(const unsigned long lid, const uint64_t fsize)\n  {\n    if (!IsRain(lid)) {\n      return fsize;\n    }\n\n    int num_data_stripes = 0;\n    int num_all_stripes = GetStripeNumber(lid) + 1;\n    int num_parity_stripes = GetRedundancyStripeNumber(lid);\n\n    // In case values don't make sense just return the full size of the file\n    if (num_parity_stripes >= num_all_stripes) {\n      return fsize;\n    }\n\n    uint64_t block_sz = GetBlocksize(lid);\n    num_data_stripes = num_all_stripes - num_parity_stripes;\n    uint64_t group_sz = block_sz * std::pow(num_data_stripes, 2);\n    return static_cast<uint64_t>(std::ceil((float)fsize / group_sz)) *\n           num_data_stripes * block_sz;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return multiplication factor for a given layout\n  //! E.g. the physical space factor for a given layout\n  //--------------------------------------------------------------------------\n  static double\n  GetSizeFactor(unsigned long layout)\n  {\n    if (GetLayoutType(layout) == kPlain) {\n      return 1.0;\n    }\n\n    if (GetLayoutType(layout) == kReplica) {\n      return 1.0 * (GetStripeNumber(layout) + 1 + GetExcessStripeNumber(layout));\n    }\n\n    if (GetLayoutType(layout) == kRaidDP)\n      return 1.0 * (((1.0 * (GetStripeNumber(layout) + 1)) /\n                     (GetStripeNumber(layout) + 1 - GetRedundancyStripeNumber(\n                        layout))) + GetExcessStripeNumber(layout));\n\n    if (GetLayoutType(layout) == kRaid6)\n      return 1.0 * (((1.0 * (GetStripeNumber(layout) + 1)) /\n                     (GetStripeNumber(layout) + 1 - GetRedundancyStripeNumber(\n                        layout))) + GetExcessStripeNumber(layout));\n\n    if (GetLayoutType(layout) == kArchive)\n      return 1.0 * (((1.0 * (GetStripeNumber(layout) + 1)) /\n                     (GetStripeNumber(layout) + 1 - GetRedundancyStripeNumber(\n                        layout))) + GetExcessStripeNumber(layout));\n\n    return 1.0;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return minimum number of replicas which have to be online for a layout\n  //! to be readable\n  //--------------------------------------------------------------------------\n  static size_t\n  GetMinOnlineReplica(unsigned long layout)\n  {\n    if (GetLayoutType(layout) == kPlain) {\n      return 1;\n    }\n\n    if (GetLayoutType(layout) == kReplica) {\n      return 1;\n    }\n\n    return (1 + GetStripeNumber(layout) - GetRedundancyStripeNumber(layout));\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return number of replicas which have to be online for a layout to be\n  //! immediately writable\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetOnlineStripeNumber(unsigned long layout)\n  {\n    return (GetStripeNumber(layout) + 1);\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return checksum type as string\n  //--------------------------------------------------------------------------\n  static const char*\n  GetChecksumString(unsigned long layout)\n  {\n    if (GetChecksum(layout) == kNone) {\n      return \"none\";\n    }\n\n    if (GetChecksum(layout) == kAdler) {\n      return \"adler\";\n    }\n\n    if (GetChecksum(layout) == kBLAKE3) {\n      return \"blake3\";\n    }\n\n    if (GetChecksum(layout) == kCRC32) {\n      return \"crc32\";\n    }\n\n    if (GetChecksum(layout) == kCRC32C) {\n      return \"crc32c\";\n    }\n\n    if (GetChecksum(layout) == kMD5) {\n      return \"md5\";\n    }\n\n    if (GetChecksum(layout) == kSHA1) {\n      return \"sha\";\n    }\n\n    if (GetChecksum(layout) == kSHA256) {\n      return \"sha256\";\n    }\n\n    if (GetChecksum(layout) == kCRC64) {\n      return \"crc64\";\n    }\n\n    if (GetChecksum(layout) == kXXHASH64) {\n      return \"xxhash64\";\n    }\n\n    if (GetChecksum(layout) == kHWH64) {\n      return \"hwh64\";\n    }\n\n    return \"none\";\n  }\n\n  //--------------------------------------------------------------------------\n  //! Get checksum type from string representation\n  //--------------------------------------------------------------------------\n  static int\n  GetChecksumFromString(const std::string& checksum)\n  {\n    if ((checksum == \"adler\") || (checksum == \"adler32\")) {\n      return kAdler;\n    } else if (checksum == \"blake3\") {\n      return kBLAKE3;\n    } else if (checksum == \"crc32\") {\n      return kCRC32;\n    } else if (checksum == \"crc32c\") {\n      return kCRC32C;\n    } else if (checksum == \"md5\") {\n      return kMD5;\n    } else if ((checksum == \"sha\") || (checksum == \"sha1\")) {\n      return kSHA1;\n    } else if (checksum == \"crc64\") {\n      return kCRC64;\n    } else if (checksum == \"sha256\") {\n      return kSHA256;\n    } else if (checksum == \"xxhash64\") {\n      return kXXHASH64;\n    } else if (checksum == \"hwh64\") {\n      return kHWH64;\n    } else if (checksum == \"none\") {\n      return kNone;\n    }\n\n    return -1;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return checksum type but masking adler as adler32\n  //--------------------------------------------------------------------------\n  static const char*\n  GetChecksumStringReal(unsigned long layout)\n  {\n    if (GetChecksum(layout) == kNone) {\n      return \"none\";\n    }\n\n    if (GetChecksum(layout) == kAdler) {\n      return \"adler32\";\n    }\n\n    if (GetChecksum(layout) == kBLAKE3) {\n      return \"blake3\";\n    }\n\n    if (GetChecksum(layout) == kCRC32) {\n      return \"crc32\";\n    }\n\n    if (GetChecksum(layout) == kCRC32C) {\n      return \"crc32c\";\n    }\n\n    if (GetChecksum(layout) == kMD5) {\n      return \"md5\";\n    }\n\n    if (GetChecksum(layout) == kSHA1) {\n      return \"sha1\";\n    }\n\n    if (GetChecksum(layout) == kSHA256) {\n      return \"sha256\";\n    }\n\n    if (GetChecksum(layout) == kCRC64) {\n      return \"crc64\";\n    }\n\n    if (GetChecksum(layout) == kXXHASH64) {\n      return \"xxhash64\";\n    }\n\n    if (GetChecksum(layout) == kHWH64) {\n      return \"hwh64\";\n    }\n\n    return \"none\";\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return block checksum type as string\n  //--------------------------------------------------------------------------\n  static const char*\n  GetBlockChecksumString(unsigned long layout)\n  {\n    if (GetBlockChecksum(layout) == kNone) {\n      return \"none\";\n    }\n\n    if (GetBlockChecksum(layout) == kAdler) {\n      return \"adler\";\n    }\n\n    if (GetBlockChecksum(layout) == kBLAKE3) {\n      return \"blake3\";\n    }\n\n    if (GetBlockChecksum(layout) == kCRC32) {\n      return \"crc32\";\n    }\n\n    if (GetBlockChecksum(layout) == kCRC32C) {\n      return \"crc32c\";\n    }\n\n    if (GetBlockChecksum(layout) == kMD5) {\n      return \"md5\";\n    }\n\n    if (GetBlockChecksum(layout) == kSHA1) {\n      return \"sha\";\n    }\n\n    if (GetBlockChecksum(layout) == kSHA256) {\n      return \"sha256\";\n    }\n\n    if (GetBlockChecksum(layout) == kCRC64) {\n      return \"crc64\";\n    }\n\n    if (GetBlockChecksum(layout) == kXXHASH64) {\n      return \"xxhash64\";\n    }\n\n    if (GetBlockChecksum(layout) == kHWH64) {\n      return \"hwh64\";\n    }\n\n    return \"none\";\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return blocksize as string\n  //--------------------------------------------------------------------------\n  static const char*\n  GetBlockSizeString(unsigned long layout)\n  {\n    if (GetBlocksizeType(layout) == k4k) {\n      return \"4k\";\n    }\n\n    if (GetBlocksizeType(layout) == k64k) {\n      return \"64k\";\n    }\n\n    if (GetBlocksizeType(layout) == k128k) {\n      return \"128k\";\n    }\n\n    if (GetBlocksizeType(layout) == k512k) {\n      return \"512k\";\n    }\n\n    if (GetBlocksizeType(layout) == k1M) {\n      return \"1M\";\n    }\n\n    if (GetBlocksizeType(layout) == k4M) {\n      return \"4M\";\n    }\n\n    if (GetBlocksizeType(layout) == k16M) {\n      return \"16M\";\n    }\n\n    if (GetBlocksizeType(layout) == k64M) {\n      return \"64M\";\n    }\n\n    return \"illegal\";\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return layout type as string\n  //--------------------------------------------------------------------------\n  static const char*\n  GetLayoutTypeString(unsigned long layout)\n  {\n    if (GetLayoutType(layout) == kPlain) {\n      return \"plain\";\n    }\n\n    if (GetLayoutType(layout) == kReplica) {\n      return \"replica\";\n    }\n\n    if (GetLayoutType(layout) == kRaidDP) {\n      return \"raiddp\";\n    }\n\n    if (GetLayoutType(layout) == kRaid5) {\n      return \"raid5\";\n    }\n\n    if (GetLayoutType(layout) == kRaid6) {\n      return \"raid6\";\n    }\n\n    if (GetLayoutType(layout) == kArchive) {\n      return \"archive\";\n    }\n\n    if (GetLayoutType(layout) == kQrain) {\n      return \"qrain\";\n    }\n\n    return \"none\";\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return layout type from string representation\n  //--------------------------------------------------------------------------\n  static int\n  GetLayoutFromString(const std::string& layout)\n  {\n    if (layout == \"plain\") {\n      return kPlain;\n    } else if (layout == \"replica\") {\n      return kReplica;\n    } else if (layout == \"raiddp\") {\n      return kRaidDP;\n    } else if (layout == \"raid5\") {\n      return kRaid5;\n    } else if (layout == \"raid6\") {\n      return kRaid6;\n    } else if (layout == \"qrain\") {\n      return kQrain;\n    } else if (layout == \"archive\") {\n      return kArchive;\n    }\n\n    return -1;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return layout type enum from env definition\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetLayoutFromEnv(XrdOucEnv& env)\n  {\n    const char* val = 0;\n\n    if ((val = env.Get(\"eos.layout.type\"))) {\n      int layout = GetLayoutFromString(val);\n      return (layout != -1) ? layout : kPlain;\n    }\n\n    return kPlain;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return layout redundancy stripe number from layout type\n  //--------------------------------------------------------------------------\n  static int\n  GetRedundancyFromLayoutType(unsigned long layout_type)\n  {\n    int redundancystripes = 0;\n\n    if (layout_type == kRaid5) {\n      redundancystripes = 1;\n    } else if (layout_type == kRaidDP) {\n      redundancystripes = 2;\n    } else if (layout_type == kRaid6) {\n      redundancystripes = 2;\n    } else if (layout_type == kArchive) {\n      redundancystripes = 3;\n    } else if (layout_type == kQrain) {\n      redundancystripes = 4;\n    }\n\n    return redundancystripes;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return layout redundancy stripe number from layout string\n  //--------------------------------------------------------------------------\n  static int\n  GetRedundancyFromLayoutString(const std::string& layout)\n  {\n    int layout_type = GetLayoutFromString(layout);\n    return GetRedundancyFromLayoutType(layout_type);\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return layout stripe number as string\n  //--------------------------------------------------------------------------\n  static std::string\n  GetStripeNumberString(unsigned long layout)\n  {\n    int n = GetStripeNumber(layout) + 1;\n\n    if (n < 256) {\n      return std::to_string(n);\n    } else {\n      return \"none\";\n    }\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return checksum enum from env definition\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetChecksumFromEnv(XrdOucEnv& env)\n  {\n    const char* val = 0;\n\n    if ((val = env.Get(\"eos.layout.checksum\"))) {\n      XrdOucString xsum = val;\n\n      if (xsum == \"adler\") {\n        return kAdler;\n      }\n\n      if (xsum == \"blake3\") {\n        return kBLAKE3;\n      }\n\n      if (xsum == \"crc32\") {\n        return kCRC32;\n      }\n\n      if (xsum == \"crc32c\") {\n        return kCRC32C;\n      }\n\n      if (xsum == \"md5\") {\n        return kMD5;\n      }\n\n      if (xsum == \"sha\") {\n        return kSHA1;\n      }\n\n      if (xsum == \"sha256\") {\n        return kSHA256;\n      }\n\n      if (xsum == \"crc64\") {\n        return kCRC64;\n      }\n\n      if (xsum == \"xxhash64\") {\n        return kXXHASH64;\n      }\n\n      if (xsum == \"hwh64\") {\n        return kHWH64;\n      }\n    }\n\n    return kNone;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return block checksum enum from env definition\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetBlockChecksumFromEnv(XrdOucEnv& env)\n  {\n    const char* val = 0;\n\n    if ((val = env.Get(\"eos.layout.blockchecksum\"))) {\n      return GetBlockChecksumFromString(val);\n    }\n\n    return kNone;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return bandwidth string from env\n  //--------------------------------------------------------------------------\n  static std::string\n  GetBandwidthFromEnv(XrdOucEnv& env)\n  {\n    const char* val = 0;\n\n    if ((val = env.Get(\"eos.iobw\"))) {\n      return std::string(env.Get(\"eos.iobw\"));\n    }\n\n    return \"\";\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return iotype string from env\n  //--------------------------------------------------------------------------\n  static std::string\n  GetIotypeFromEnv(XrdOucEnv& env)\n  {\n    const char* val = 0;\n\n    if ((val = env.Get(\"eos.iotype\"))) {\n      return std::string(env.Get(\"eos.iotype\"));\n    }\n\n    return \"\";\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return block checksum enum from string representation\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetBlockChecksumFromString(const std::string& bxs_type)\n  {\n    if (bxs_type == \"adler\") {\n      return kAdler;\n    }\n\n    if (bxs_type == \"crc32\") {\n      return kCRC32;\n    }\n\n    if (bxs_type == \"crc32c\") {\n      return kCRC32C;\n    }\n\n    if (bxs_type == \"md5\") {\n      return kMD5;\n    }\n\n    if (bxs_type == \"sha\") {\n      return kSHA1;\n    }\n\n    return kNone;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return blocksize enum from env definition\n  //--------------------------------------------------------------------------\n  static unsigned long\n  GetBlocksizeFromEnv(XrdOucEnv& env)\n  {\n    const char* val = 0;\n\n    if ((val = env.Get(\"eos.layout.blocksize\"))) {\n      XrdOucString bs = val;\n\n      if (bs == \"4k\") {\n        return k4k;\n      }\n\n      if (bs == \"64k\") {\n        return k64k;\n      }\n\n      if (bs == \"128k\") {\n        return k128k;\n      }\n\n      if (bs == \"512k\") {\n        return k512k;\n      }\n\n      if (bs == \"1M\") {\n        return k1M;\n      }\n\n      if (bs == \"4M\") {\n        return k4M;\n      }\n\n      if (bs == \"16M\") {\n        return k16M;\n      }\n\n      if (bs == \"64M\") {\n        return k64M;\n      }\n    }\n\n    return 0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if this is a valid blocksize specification\n  //!\n  //! @param bs block size in human readable format\n  //!\n  //! @return true if valid, otherwise false\n  //----------------------------------------------------------------------------\n  static bool IsValidBlocksize(const std::string& bs)\n  {\n    std::set<std::string> valid {\"4k\", \"64k\", \"128k\", \"512k\", \"1M\", \"4M\",\n                                 \"16M\", \"64M\"};\n    return (valid.find(bs) != valid.end());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return number of stripes enum from env definition\n  //----------------------------------------------------------------------------\n  static unsigned long\n  GetStripeNumberFromEnv(XrdOucEnv& env)\n  {\n    const char* val = 0;\n\n    if ((val = env.Get(\"eos.layout.nstripes\"))) {\n      int n = atoi(val);\n\n      if (((n - 1) >= 0) && ((n - 1) <= 255)) {\n        return n;\n      }\n    }\n\n    return (1);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Print a layout human readable\n  //----------------------------------------------------------------------------\n  static std::string PrintLayoutString(int layout)\n  {\n    std::string dump;\n    dump = GetLayoutTypeString(layout);\n    dump += \":\";\n    dump += GetStripeNumberString(layout).c_str();\n    dump += \"[\";\n    dump += std::to_string(GetStripeNumber(layout) + 1 - GetRedundancyStripeNumber(\n                             layout));\n    dump += \"]\";\n    dump += \":\";\n    dump += GetChecksumString(layout);\n    dump += \":\";\n    dump += GetBlockChecksumString(layout);\n    dump += \"[\";\n    dump += GetBlockSizeString(layout);\n    dump += \"]\";\n    return dump;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Convert a conversion id string to layout id value to be stored directly\n  //! in the IFileMD object\n  //!\n  //! @param conv_id conversion id in the form of <space>#<hex_lid>~<plc_plcy>\n  //----------------------------------------------------------------------------\n  static unsigned long\n  GetLidFromConversionId(const std::string& conv_id)\n  {\n    using eos::common::StringConversion;\n    std::string space;\n    std::string layout;\n    std::string plctplcy;\n\n    if (!StringConversion::SplitKeyValue(conv_id, space, layout, \"#\")) {\n      return 0u;\n    }\n\n    if (layout.find('~') != std::string::npos) {\n      StringConversion::SplitKeyValue(layout, layout, plctplcy, \"~\");\n    }\n\n    unsigned long lid {0ul};\n\n    try {\n      lid = std::stoul(layout, 0, 16);\n    } catch (...) {}\n\n    return lid;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Convert a <space>=<hexadecimal layout id> string to an env representation\n  //----------------------------------------------------------------------------\n  static const char*\n  GetEnvFromConversionIdString(XrdOucString& out,\n                               const char* conversionlayoutidstring)\n  {\n    if (!conversionlayoutidstring) {\n      return NULL;\n    }\n\n    std::string keyval = conversionlayoutidstring;\n    std::string plctplcy;\n\n    // check if this is already a complete env representation\n    if ((keyval.find(\"eos.layout.type\") != std::string::npos) &&\n        (keyval.find(\"eos.layout.nstripes\") != std::string::npos) &&\n        (keyval.find(\"eos.layout.blockchecksum\") != std::string::npos) &&\n        (keyval.find(\"eos.layout.checksum\") != std::string::npos) &&\n        (keyval.find(\"eos.layout.blocksize\") != std::string::npos) &&\n        (keyval.find(\"eos.space\") != std::string::npos)) {\n      out = conversionlayoutidstring;\n      return out.c_str();\n    }\n\n    std::string space;\n    std::string layout;\n\n    if (!eos::common::StringConversion::SplitKeyValue(keyval, space, layout, \"#\")) {\n      return NULL;\n    }\n\n    if (((int)layout.find(\"~\")) != STR_NPOS) {\n      eos::common::StringConversion::SplitKeyValue(layout, layout, plctplcy, \"~\");\n    }\n\n    errno = 0;\n    unsigned long long lid = strtoll(layout.c_str(), 0, 16);\n\n    if (errno) {\n      return NULL;\n    }\n\n    std::string group(\"\");\n    std::string spaceStripped(\"\");\n\n    if (eos::common::StringConversion::SplitKeyValue(space, spaceStripped,\n        group, \".\")) {\n      space = spaceStripped;\n    }\n\n    out = \"eos.layout.type=\";\n    out += GetLayoutTypeString(lid);\n    out += \"&eos.layout.nstripes=\";\n    out += GetStripeNumberString(lid).c_str();\n    out += \"&eos.layout.blockchecksum=\";\n    out += GetBlockChecksumString(lid);\n    out += \"&eos.layout.checksum=\";\n    out += GetChecksumString(lid);\n    out += \"&eos.layout.blocksize=\";\n    out += GetBlockSizeString(lid);\n    out += \"&eos.space=\";\n    out += space.c_str();\n\n    if (plctplcy.length()) {\n      out += \"&eos.placementpolicy=\";\n      out += plctplcy.c_str();\n    }\n\n    if (group != \"\") {\n      out += \"&eos.group=\";\n      out += group.c_str();\n    }\n\n    return out.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Map POSIX-like open flags to SFS open flags - used on the FUSE mount\n  //!\n  //! @param flags_sfs SFS open flags\n  //!\n  //! @return SFS-like open flags\n  //!\n  //----------------------------------------------------------------------------\n  static XrdSfsFileOpenMode\n  MapFlagsPosix2Sfs(int oflags)\n  {\n    XrdSfsFileOpenMode sfs_flags = SFS_O_RDONLY; // 0x0000\n\n    if (oflags & O_CREAT) {\n      sfs_flags |= SFS_O_CREAT;\n    }\n\n    if (oflags & O_RDWR) {\n      sfs_flags |= SFS_O_RDWR;\n    }\n\n    if (oflags & O_TRUNC) {\n      sfs_flags |= SFS_O_TRUNC;\n    }\n\n    if (oflags & O_WRONLY) {\n      sfs_flags |= SFS_O_WRONLY;\n    }\n\n    if (oflags & O_APPEND) {\n      sfs_flags |= SFS_O_RDWR;\n    }\n\n    // !!!\n    // Could also forward O_EXLC as XrdCl::OpenFlags::Flags::New but there is\n    // no corresponding flag in SFS\n    // !!!\n    return sfs_flags;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Map SFS-like open flags to XrdCl open flags\n  //!\n  //! @param flags_sfs SFS open flags\n  //!\n  //! @return XrdCl-like open flags\n  //----------------------------------------------------------------------------\n  static XrdCl::OpenFlags::Flags\n  MapFlagsSfs2XrdCl(XrdSfsFileOpenMode flags_sfs)\n  {\n    XrdCl::OpenFlags::Flags xflags = XrdCl::OpenFlags::None;\n\n    if (flags_sfs & SFS_O_CREAT) {\n      xflags |= XrdCl::OpenFlags::Delete;\n    }\n\n    if (flags_sfs & SFS_O_WRONLY) {\n      xflags |= XrdCl::OpenFlags::Update;\n    }\n\n    if (flags_sfs & SFS_O_RDWR) {\n      xflags |= XrdCl::OpenFlags::Update;\n    }\n\n    if (flags_sfs & SFS_O_TRUNC) {\n      xflags |= XrdCl::OpenFlags::Delete;\n    }\n\n    if ((!(flags_sfs & SFS_O_TRUNC)) &&\n        (!(flags_sfs & SFS_O_WRONLY)) &&\n        (!(flags_sfs & SFS_O_CREAT)) &&\n        (!(flags_sfs & SFS_O_RDWR))) {\n      xflags |= XrdCl::OpenFlags::Read;\n    }\n\n    if (flags_sfs & SFS_O_POSC) {\n      xflags |= XrdCl::OpenFlags::POSC;\n    }\n\n    if (flags_sfs & SFS_O_NOWAIT) {\n      xflags |= XrdCl::OpenFlags::NoWait;\n    }\n\n    if (flags_sfs & SFS_O_RAWIO) {\n      // no idea what to do\n    }\n\n    if (flags_sfs & SFS_O_RESET) {\n      xflags |= XrdCl::OpenFlags::Refresh;\n    }\n\n    if (flags_sfs & SFS_O_REPLICA) {\n      // emtpy\n    }\n\n    if (flags_sfs & SFS_O_MKPTH) {\n      xflags |= XrdCl::OpenFlags::MakePath;\n    }\n\n    return xflags;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Map SFS-like open mode to XrdCl open mode\n  //!\n  //! @param mode_sfs SFS open mode\n  //!\n  //! @return XrdCl-like open mode\n  //----------------------------------------------------------------------------\n  static XrdCl::Access::Mode\n  MapModeSfs2XrdCl(mode_t mode_sfs)\n  {\n    XrdCl::Access::Mode mode_xrdcl = XrdCl::Access::Mode::None;\n\n    if (mode_sfs & S_IRUSR) {\n      mode_xrdcl |= XrdCl::Access::Mode::UR;\n    }\n\n    if (mode_sfs & S_IWUSR) {\n      mode_xrdcl |= XrdCl::Access::Mode::UW;\n    }\n\n    if (mode_sfs & S_IXUSR) {\n      mode_xrdcl |= XrdCl::Access::Mode::UX;\n    }\n\n    if (mode_sfs & S_IRGRP) {\n      mode_xrdcl |= XrdCl::Access::Mode::GR;\n    }\n\n    if (mode_sfs & S_IWGRP) {\n      mode_xrdcl |= XrdCl::Access::Mode::GW;\n    }\n\n    if (mode_sfs & S_IXGRP) {\n      mode_xrdcl |= XrdCl::Access::Mode::GX;\n    }\n\n    if (mode_sfs & S_IROTH) {\n      mode_xrdcl |= XrdCl::Access::Mode::OR;\n    }\n\n    if (mode_sfs & S_IXOTH) {\n      mode_xrdcl |= XrdCl::Access::Mode::OX;\n    }\n\n    return mode_xrdcl;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get expected stripe size for RAIN layouts given the logical size of the\n  //! file\n  //!\n  //! @param lid layout id\n  //! @param file size logical file size\n  //!\n  //! @return expected physical files of the stripes\n  //----------------------------------------------------------------------------\n  static uint64_t ExpectedStripeSize(const uint32_t lid,\n                                     const uint64_t file_size)\n  {\n    if (!IsRain(lid)) {\n      return file_size;\n    }\n\n    uint64_t stripe_size = 0ull;\n    unsigned long layout_type = GetLayoutType(lid);\n    unsigned long block_size = GetBlocksize(lid);\n    unsigned long num_all_stripes = GetStripeNumber(lid) + 1;\n    unsigned long num_parity_stripes = GetRedundancyStripeNumber(lid);\n    unsigned long num_data_stripes = num_all_stripes - num_parity_stripes;\n    unsigned long rain_group_sz = 0ull;\n    unsigned long stripe_group_sz = 0ull;\n\n    if (layout_type == kRaidDP) {\n      rain_group_sz = num_data_stripes * num_data_stripes * block_size;\n      stripe_group_sz = num_data_stripes * block_size;\n    } else {\n      rain_group_sz = num_data_stripes * block_size;\n      stripe_group_sz = block_size;\n    }\n\n    stripe_size = std::ceil(1.0 * file_size / rain_group_sz) * stripe_group_sz;\n    stripe_size += OssXsBlockSize;\n    return stripe_size;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Constructor\n  //--------------------------------------------------------------------------\n  LayoutId() = default;\n\n  //--------------------------------------------------------------------------\n  //! Destructor\n  //--------------------------------------------------------------------------\n  ~LayoutId() = default;\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/LinuxFds.hh",
    "content": "// ----------------------------------------------------------------------\n// File: LinuxFds.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/ASwitzerland                                 *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   LinuxFds.hh\n * \n * @brief  Class counting the current filedescriptor usage\n *  \n */\n\n#ifndef __EOSCOMMON__LINUXFDS__HH\n#define __EOSCOMMON__LINUXFDS__HH\n\n#include \"common/Namespace.hh\" \n#include <sys/types.h>\n#include <dirent.h>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\n//! Static Class to inspect file descriptor usage\n//! \n//! Example: linux_fds_t fds; GetFdUsage(fds);\n//! \n/*----------------------------------------------------------------------------*/\nclass LinuxFds {\npublic:\n  typedef struct {\n    unsigned long long devices,filesystem,sockets,pipes,anon_inode,other,all;\n  } linux_fds_t;\n\n  static bool GetFdUsage(linux_fds_t& result)\n  {\n    std::string link_base = \"/proc/self/fd/\";\n\n    const char* fd_path = \"/proc/self/fd\";\n    result.devices = result.filesystem = result.sockets =\n        result.pipes = result. anon_inode, result.other = result.all = 0;\n\n    DIR *d = opendir(fd_path);\n\n    if (!d) {\n      perror(fd_path);\n      return false;\n    }\n    \n    struct dirent *dent;\n    char linkbuffer[4096];\n    \n    while ((dent = readdir(d)) != NULL) {\n      std::string link_name = link_base;\n      link_name += dent->d_name;\n      ssize_t lsize=0;\n\n      if ((lsize = ::readlink(link_name.c_str(), linkbuffer, sizeof(linkbuffer))) > 0) {\n        result.all++;\n        std::string target(linkbuffer,lsize);\n\n        if (target.substr(0,7) == \"socket:\") {\n          result.sockets++;\n        } else if (target.substr(0,4) == \"/dev/\") {\n          result.devices++;\n        } else if (target.substr(0,1) == \"/\") {\n          result.filesystem++;\n        } else if (target.substr(0,5) == \"pipe:\") {\n          result.pipes++;\n        } else if (target.substr(0,11) == \"anon_inode:\") {\n          result.anon_inode++;\n        } else {\n          result.other++;\n        }\n      } else {\n        // If we have a forked setuid program, we don't have the permission to resolve symlinks,\n        // we just count all fds\n        result.all++;\n      }\n    }\n\n    closedir(d);\n    return true;\n  }\n};\n\nEOSCOMMONNAMESPACE_END\n \n#endif\n"
  },
  {
    "path": "common/LinuxMemConsumption.hh",
    "content": "// ----------------------------------------------------------------------\n// File: LinuxMemConsumption.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   LinuxMemConsumption.hh\n * \n * @brief  Class measuring the current memory usage using the proc interface\n *\n */\n\n#ifndef __EOSCOMMON__LINUXMEMCONSUMPTION__HH\n#define __EOSCOMMON__LINUXMEMCONSUMPTION__HH\n\n#include \"common/Namespace.hh\" \n\nEOSCOMMONNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\n//! Static Class to measure memory consumption\n//! \n//! Example: linux_mem_t mem; GetMemoryFootprint(mem);\n//!\n/*----------------------------------------------------------------------------*/\nclass LinuxMemConsumption {\npublic:\n  typedef struct {\n    unsigned long long vmsize, resident, share, text, lib, data, dt;\n  } linux_mem_t;\n\n  static bool GetMemoryFootprint(linux_mem_t& result)\n  {\n    const char* statm_path = \"/proc/self/statm\";\n    result.vmsize = result.resident = result.share =\n        result.text = result.lib = result.data = result.dt = 0;\n\n    FILE *f = fopen(statm_path,\"r\");\n\n    if (!f) {\n      perror(statm_path);\n      return false;\n    }\n\n    if (7 != fscanf(f,\"%lld %lld %lld %lld %lld %lld %lld\",\n                    &result.vmsize, &result.resident, &result.share,\n\t\t                &result.text, &result.lib, &result.data, &result.dt)) {\n      perror(statm_path);\n      (void) fclose(f);\n      return false;\n    }\n\n    fclose(f);\n\n    // Convert into bytes\n    result.vmsize*=4096;\n    result.resident*=4096;\n    result.share*=4096;\n    result.lib*=4096;\n    result.data*=4096;\n    result.dt*=4096;\n    result.text*=4096;\n    result.lib*=4096;\n    result.data*=4096;\n    result.dt*=4096;\n\n    return true;\n  }\n};\n\nEOSCOMMONNAMESPACE_END\n \n#endif\n"
  },
  {
    "path": "common/LinuxStat.hh",
    "content": "// ----------------------------------------------------------------------\n// File: LinuxStat.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   LinuxStat.hh\n *\n * @brief  Class getting process statistics from the linux proc filesystem\n *\n */\n\n#ifndef __EOSCOMMON__LINUXSTAT__HH\n#define __EOSCOMMON__LINUXSTAT__HH\n\n#include \"common/Namespace.hh\"\n#include <cstdio>\n#include <sys/param.h>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\n//! Static Class to measure memory consumption\n//!\n//! Example: linux_stat_t st; GetStat(st);\n//!\n/*----------------------------------------------------------------------------*/\nclass LinuxStat {\npublic:\n  typedef struct {\n    // TODO! in 2.6. kernels we see 3 more entries (unknown for the meanwhile)\n    unsigned long long pid;\n    char tcomm[PATH_MAX];\n    char state;\n    unsigned long long ppid;\n    unsigned long long pgid;\n    unsigned long long sid;\n    unsigned long long tty_nr;\n    unsigned long long tty_pgrp;\n    unsigned long long flags;\n    unsigned long long min_flt;\n\n    unsigned long long cmin_flt;\n    unsigned long long maj_flt;\n    unsigned long long cmaj_flt;\n    unsigned long long utime;\n    unsigned long long stime;\n    unsigned long long cutime;\n    unsigned long long cstime;\n    unsigned long long priority;\n    unsigned long long nicev;\n    unsigned long long threads;\n\n    unsigned long long it_real_value;\n    unsigned long long start_time;\n    unsigned long long vsize;\n    unsigned long long rss;\n    unsigned long long rsslim;\n    unsigned long long start_code;\n    unsigned long long end_code;\n    unsigned long long start_stack;\n    unsigned long long esp;\n    unsigned long long eip;\n\n    unsigned long long pending;\n    unsigned long long blocked;\n    unsigned long long sigign;\n    unsigned long long sigcatch;\n    unsigned long long wchan;\n    unsigned long long zero1;\n    unsigned long long zero2;\n    unsigned long long exit_signal;\n    unsigned long long cpu;\n    unsigned long long rt_priority;\n\n    unsigned long long policy;\n\n  } linux_stat_t;\n\n  static bool GetStat(linux_stat_t& result) {\n    const char* stat_path = \"/proc/self/stat\";\n    result.tcomm[0] = 0;\n    result.state = 0;\n    result.pid = result.ppid = result.pgid = result.sid = result.tty_nr = result.tty_pgrp = result.flags =\n        result.min_flt = result.cmin_flt = result.maj_flt = result.cmaj_flt = result.utime = result.stime =\n            result.cutime = result.cstime = result.priority = result.nicev = result.threads = result.it_real_value =\n                result.start_time = result.vsize = result.rss = result.rsslim = result.start_code = result.end_code =\n                    result.start_stack = result.esp = result.eip = result.pending = result.blocked = result.sigign =\n                        result.sigcatch = result.wchan = result.zero1 = result.zero2 = result.exit_signal = result.cpu =\n                            result.rt_priority = result.policy = 0;\n    FILE* f = fopen(stat_path, \"r\");\n\n    if (!f) {\n      perror(stat_path);\n      return false;\n    }\n\n    if (41 != fscanf(f,\n                     \"%lld %s %c %lld %lld %lld %lld %lld %lld %lld %lld \"\n                     \"%lld %lld %lld %lld %lld %lld %lld %lld %lld %lld %lld \"\n                     \"%lld %lld %lld %lld %lld %lld %lld %lld %lld %lld %lld \"\n                     \"%lld %lld %lld %lld %lld %lld %lld %lld\\n\",\n                     &result.pid, result.tcomm, &result.state, &result.ppid, &result.pgid, &result.sid, &result.tty_nr,\n                     &result.tty_pgrp, &result.flags, &result.min_flt, &result.cmin_flt, &result.maj_flt,\n                     &result.cmaj_flt, &result.utime, &result.stime, &result.cutime, &result.cstime, &result.priority,\n                     &result.nicev, &result.threads, &result.it_real_value, &result.start_time, &result.vsize,\n                     &result.rss, &result.rsslim, &result.start_code, &result.end_code, &result.start_stack,\n                     &result.esp, &result.eip, &result.pending, &result.blocked, &result.sigign, &result.sigcatch,\n                     &result.wchan, &result.zero1, &result.zero2, &result.exit_signal, &result.cpu, &result.rt_priority,\n                     &result.policy)) {\n      perror(stat_path);\n      fclose(f);\n      return false;\n    }\n\n    result.rss *= 4096; // comes in 4k pages on Linux\n    fclose(f);\n    return true;\n  }\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/LinuxTotalMem.hh",
    "content": "// ----------------------------------------------------------------------\n// File: LinuxTotalMem.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   LinuxTotalMem.hh\n *\n * @brief  Class providing total memory information\n *\n */\n\n#ifndef __EOSCOMMON__LINUXTOTALMEM__HH\n#define __EOSCOMMON__LINUXTOTALMEM__HH\n\n#include \"common/Namespace.hh\"\n#ifdef __APPLE__\n#include <sys/types.h>\n#include <sys/sysctl.h>\n#else\n#include <sys/sysinfo.h>\n#endif\n#include <mutex>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\n//! Static Class to retrieve total memory values\n//!\n//! Example: linux_total_mem_t mem; GetTotalMemory(mem);\n//!\n/*----------------------------------------------------------------------------*/\nclass LinuxTotalMem\n{\npublic:\n#ifdef __APPLE__\n  struct linux_total_mem_t {\n    uint64_t totalram;\n    uint64_t freeram;\n    uint64_t loads[3];\n  };\n\n#else\n  typedef struct sysinfo linux_total_mem_t;\n#endif\n\n\n  LinuxTotalMem()\n  {\n    update();\n  }\n\n  bool update()\n  {\n    std::lock_guard<std::mutex> lock(locker);\n#ifdef __APPLE__\n    int mib[2];\n    // Get load average\n    struct loadavg la;\n    mib[0] = CTL_VM;\n    mib[1] = VM_LOADAVG;\n    size_t length = sizeof(la);\n\n    if (sysctl(mib, 2, &la, &length, nullptr, 0) != 0) {\n      return false;\n    }\n\n    meminfo.loads[0] = la.ldavg[0];\n    // Get physical memory size\n    mib[0] = CTL_HW;\n    mib[1] = HW_PHYSMEM;\n    uint64_t physical_mem {0ull};\n    length = sizeof(physical_mem);\n\n    if (sysctl(mib, 2, &physical_mem, &length, nullptr, 0) != 0) {\n      return false;\n    }\n\n    meminfo.totalram = physical_mem;\n    // No clear correspondent for this\n    meminfo.freeram = physical_mem;\n    return true;\n#else\n\n    if (!sysinfo((struct sysinfo*) &meminfo)) {\n      return true;\n    } else {\n      return false;\n    }\n\n#endif\n  }\n\n  linux_total_mem_t get()\n  {\n    std::lock_guard<std::mutex> lock(locker);\n    return meminfo;\n  }\n\n  std::mutex& mutex()\n  {\n    return locker;\n  }\n  linux_total_mem_t& getref()\n  {\n    return meminfo;\n  }\n\nprivate:\n  linux_total_mem_t meminfo;\n  std::mutex locker;\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/Locators.cc",
    "content": "//------------------------------------------------------------------------------\n// File: Locators.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Namespace.hh\"\n#include \"common/Locators.hh\"\n#include \"common/Logging.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/InstanceName.hh\"\n#include \"common/Assert.hh\"\n#include \"common/FileSystem.hh\"\n#include \"common/ParseUtils.hh\"\n#include \"common/StringUtils.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFileSystemLocator::FileSystemLocator(const std::string& _host, int _port,\n                                     const std::string& _storagepath) : host(_host), port(_port),\n  storagepath(_storagepath)\n{\n  storageType = FileSystemLocator::parseStorageType(_storagepath);\n}\n\n//------------------------------------------------------------------------------\n// Try to parse a \"queuepath\"\n//------------------------------------------------------------------------------\nbool FileSystemLocator::fromQueuePath(const std::string& queuepath,\n                                      FileSystemLocator& out)\n{\n  std::string queue = queuepath;\n\n  if (!startsWith(queue, \"/eos/\")) {\n    return false;\n  }\n\n  queue.erase(0, 5);\n  //----------------------------------------------------------------------------\n  // Chop /eos/, extract host+port\n  //----------------------------------------------------------------------------\n  size_t slashLocation = queue.find(\"/\");\n\n  if (slashLocation == std::string::npos) {\n    return false;\n  }\n\n  std::string hostPort = std::string(queue.begin(),\n                                     queue.begin() + slashLocation);\n  queue.erase(0, slashLocation);\n  //----------------------------------------------------------------------------\n  // Separate host from port\n  //----------------------------------------------------------------------------\n  size_t separator = hostPort.find(\":\");\n\n  if (separator == std::string::npos) {\n    return false;\n  }\n\n  out.host = std::string(hostPort.begin(), hostPort.begin() + separator);\n  hostPort.erase(0, separator + 1);\n  int64_t port;\n\n  if (!ParseInt64(hostPort, port)) {\n    return false;\n  }\n\n  out.port = port;\n\n  //----------------------------------------------------------------------------\n  // Chop \"/fst/\", extract local path\n  //----------------------------------------------------------------------------\n  if (!startsWith(queue, \"/fst\")) {\n    return false;\n  }\n\n  queue.erase(0, 4);\n  out.storagepath = queue;\n\n  if (out.storagepath.size() < 2) {\n    // Empty, or \"/\"? Reject\n    return false;\n  }\n\n  out.storageType = FileSystemLocator::parseStorageType(out.storagepath);\n\n  if (out.storageType == StorageType::Unknown) {\n    return false;\n  }\n\n  return true;\n}\n\n//----------------------------------------------------------------------------\n//! Parse storage type from storage path string\n//----------------------------------------------------------------------------\nFileSystemLocator::StorageType FileSystemLocator::parseStorageType(\n  const std::string& storagepath)\n{\n  if (storagepath.find(\"/\") == 0) {\n    return StorageType::Local;\n  } else if (storagepath.find(\"root://\") == 0) {\n    return StorageType ::Xrd;\n  } else if (storagepath.find(\"s3://\") == 0) {\n    return StorageType::S3;\n  } else if (storagepath.find(\"dav://\") == 0) {\n    return StorageType::WebDav;\n  } else if (storagepath.find(\"http://\") == 0) {\n    return StorageType::HTTP;\n  } else if (storagepath.find(\"https://\") == 0) {\n    return StorageType::HTTPS;\n  } else if (storagepath.find(\"nfs://\") == 0) {\n    return StorageType::NFS;\n  } else {\n    return StorageType::Unknown;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get host\n//------------------------------------------------------------------------------\nstd::string FileSystemLocator::getHost() const\n{\n  return host;\n}\n\n//------------------------------------------------------------------------------\n// Get hostport, concatenated together as \"host:port\"\n//------------------------------------------------------------------------------\nstd::string FileSystemLocator::getHostPort() const\n{\n  return SSTR(host << \":\" << port);\n}\n\n//------------------------------------------------------------------------------\n// Get queuepath\n//------------------------------------------------------------------------------\nstd::string FileSystemLocator::getQueuePath() const\n{\n  return SSTR(\"/eos/\" << host << \":\" << port << \"/fst\" << storagepath);\n}\n\n//------------------------------------------------------------------------------\n// Get \"FST queue\", ie /eos/example.com:3002/fst\n//------------------------------------------------------------------------------\nstd::string FileSystemLocator::getFSTQueue() const\n{\n  return SSTR(\"/eos/\" << host << \":\" << port << \"/fst\");\n}\n\n//------------------------------------------------------------------------------\n// Get port\n//------------------------------------------------------------------------------\nint FileSystemLocator::getPort() const\n{\n  return port;\n}\n\n//------------------------------------------------------------------------------\n// Get storage path\n//------------------------------------------------------------------------------\nstd::string FileSystemLocator::getStoragePath() const\n{\n  return storagepath;\n}\n\n//----------------------------------------------------------------------------\n// Get storage type\n//----------------------------------------------------------------------------\nFileSystemLocator::StorageType FileSystemLocator::getStorageType() const\n{\n  return storageType;\n}\n\n//----------------------------------------------------------------------------\n// Check whether filesystem is local or remote\n//----------------------------------------------------------------------------\nbool FileSystemLocator::isLocal() const\n{\n  return storageType == StorageType::Local;\n}\n\n//------------------------------------------------------------------------------\n// Empty constructor\n//------------------------------------------------------------------------------\nSharedHashLocator::SharedHashLocator()\n{\n  mInitialized = false;\n}\n\n//------------------------------------------------------------------------------\n// Constructor: Pass the EOS instance name, BaseView type, and name.\n//\n// Once we drop the MQ entirely, the instance name can be removed.\n//------------------------------------------------------------------------------\nSharedHashLocator::SharedHashLocator(const std::string& instanceName, Type type,\n                                     const std::string& name)\n  : mInitialized(true), mInstanceName(instanceName), mType(type), mName(name)\n{\n  switch (mType) {\n  case Type::kSpace: {\n    mMqSharedHashPath = SSTR(\"/config/\" << instanceName << \"/space/\" << name);\n    mBroadcastQueue = \"/eos/*/mgm\";\n    break;\n  }\n\n  case Type::kGroup: {\n    mMqSharedHashPath = SSTR(\"/config/\" << instanceName << \"/group/\" << name);\n    mBroadcastQueue = \"/eos/*/mgm\";\n    break;\n  }\n\n  case Type::kNode: {\n    std::string hostPort = eos::common::StringConversion::GetHostPortFromQueue(\n                             name.c_str()).c_str();\n    mMqSharedHashPath = SSTR(\"/config/\" << instanceName << \"/node/\" << hostPort);\n    mBroadcastQueue = SSTR(\"/eos/\" << hostPort << \"/fst\");\n    mName = hostPort;\n    break;\n  }\n\n  case Type::kGlobalConfigHash: {\n    mMqSharedHashPath = SSTR(\"/config/\" << instanceName << \"/mgm/\");\n    mBroadcastQueue = \"/eos/*/mgm\";\n    break;\n  }\n\n  default: {\n    eos_assert(\"should never reach here\");\n  }\n  }\n}\n\n//------------------------------------------------------------------------------\n//! Constructor: Same as above, but auto-discover instance name.\n//------------------------------------------------------------------------------\nSharedHashLocator::SharedHashLocator(Type type, const std::string& name)\n  : SharedHashLocator(InstanceName::get(), type, name) {}\n\n//------------------------------------------------------------------------------\n// Constructor: Special case for FileSystems, as they work a bit differently\n// than the rest.\n//------------------------------------------------------------------------------\nSharedHashLocator::SharedHashLocator(const FileSystemLocator& fsLocator,\n                                     bool bc2mgm):\n  mInitialized(true), mType(Type::kFilesystem)\n{\n  mMqSharedHashPath = fsLocator.getQueuePath();\n  mBroadcastQueue = fsLocator.getFSTQueue();\n  mFilesystemChannel = SSTR(fsLocator.getHostPort() << \"||\" <<\n                            fsLocator.getStoragePath());\n\n  if (bc2mgm) {\n    mBroadcastQueue = \"/eos/*/mgm\";\n  }\n}\n\n//------------------------------------------------------------------------------\n//! Convenience \"Constructors\": Make locator for space, group, node\n//------------------------------------------------------------------------------\nSharedHashLocator SharedHashLocator::makeForSpace(const std::string& name)\n{\n  return SharedHashLocator(Type::kSpace, name);\n}\n\nSharedHashLocator SharedHashLocator::makeForGroup(const std::string& name)\n{\n  return SharedHashLocator(Type::kGroup, name);\n}\n\nSharedHashLocator SharedHashLocator::makeForNode(const std::string& name)\n{\n  return SharedHashLocator(Type::kNode, name);\n}\n\nSharedHashLocator SharedHashLocator::makeForGlobalHash()\n{\n  return SharedHashLocator(Type::kGlobalConfigHash, \"\");\n}\n\n//------------------------------------------------------------------------------\n// Get \"config queue\" for shared hash\n//------------------------------------------------------------------------------\nstd::string SharedHashLocator::getConfigQueue() const\n{\n  return mMqSharedHashPath;\n}\n\n//------------------------------------------------------------------------------\n// Get \"broadcast queue\" for shared hash\n//------------------------------------------------------------------------------\nstd::string SharedHashLocator::getBroadcastQueue() const\n{\n  return mBroadcastQueue;\n}\n\n//----------------------------------------------------------------------------\n// Check if this object is actually pointing to something\n//----------------------------------------------------------------------------\nbool SharedHashLocator::empty() const\n{\n  return !mInitialized;\n}\n\n//------------------------------------------------------------------------------\n// Produce SharedHashLocator by parsing config queue\n//------------------------------------------------------------------------------\nbool SharedHashLocator::fromConfigQueue(const std::string& configQueue,\n                                        SharedHashLocator& out)\n{\n  std::vector<std::string> parts =\n    common::StringTokenizer::split<std::vector<std::string>>(configQueue, '/');\n  std::reverse(parts.begin(), parts.end());\n\n  if (parts.empty() || parts.back() != \"config\") {\n    return false;\n  }\n\n  parts.pop_back();\n\n  if (parts.empty()) {\n    return false;\n  }\n\n  SharedHashLocator::Type type;\n  std::string instanceName = parts.back();\n  parts.pop_back();\n\n  if (parts.empty()) {\n    return false;\n  } else if (parts.back() == \"node\") {\n    type = Type::kNode;\n  } else if (parts.back() == \"space\") {\n    type = Type::kSpace;\n  } else if (parts.back() == \"group\") {\n    type = Type::kGroup;\n  } else if (parts.back() == \"mgm\" && parts.size() == 1u) {\n    type = Type::kGlobalConfigHash;\n    out = SharedHashLocator(instanceName, type, \"\");\n    return true;\n  } else {\n    return false;\n  }\n\n  parts.pop_back();\n\n  if (parts.empty()) {\n    return false;\n  }\n\n  std::string name = parts.back();\n  parts.pop_back();\n\n  if (!parts.empty()) {\n    return false;\n  }\n\n  if (instanceName.empty() || name.empty()) {\n    return false;\n  }\n\n  out = SharedHashLocator(instanceName, type, name);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Get QDB key for this queue\n//------------------------------------------------------------------------------\nstd::string SharedHashLocator::getQDBKey() const\n{\n  switch (mType) {\n  case Type::kSpace: {\n    return SSTR(\"eos-hash||space||\" << mName);\n  }\n\n  case Type::kGroup: {\n    return SSTR(\"eos-hash||group||\" << mName);\n  }\n\n  case Type::kNode: {\n    return SSTR(\"eos-hash||node||\" << mName);\n  }\n\n  case Type::kGlobalConfigHash: {\n    return SSTR(\"eos-global-config-hash\");\n  }\n\n  case Type::kFilesystem: {\n    return SSTR(\"eos-hash||fs||\" << mFilesystemChannel);\n  }\n\n  default: {\n    eos_assert(\"should never reach here\");\n    return \"\";\n  }\n  }\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Locators.hh",
    "content": "//------------------------------------------------------------------------------\n// File: BaseViewLocator.hh\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_COMMON_BASE_VIEW_LOCATOR_HH\n#define EOS_COMMON_BASE_VIEW_LOCATOR_HH\n\n#include \"common/Namespace.hh\"\n#include <string>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Describes how to physically locate a filesystem:\n//!   - Host + port of the corresponding FST\n//!   - Storage path of the filesystem\n//!   - Type of access (remote or local)\n//!\n//! Filesystem locators can also be constructed from a queuepath\n//! which has the following form:\n//! /eos/<host>:<port>/fst<storage_path>\n//------------------------------------------------------------------------------\nclass FileSystemLocator\n{\npublic:\n  //! Storage type of the filesystem\n  enum class StorageType {\n    Local,\n    Xrd,\n    S3,\n    WebDav,\n    HTTP,\n    HTTPS,\n    NFS,\n    Unknown\n  };\n\n  //----------------------------------------------------------------------------\n  //! Empty constructor\n  //----------------------------------------------------------------------------\n  FileSystemLocator() {}\n\n  //----------------------------------------------------------------------------\n  //! Constructor, pass manually individual components\n  //----------------------------------------------------------------------------\n  FileSystemLocator(const std::string& host, int port,\n                    const std::string& storagepath);\n\n  //----------------------------------------------------------------------------\n  //! Try to parse a \"queuepath\"\n  //----------------------------------------------------------------------------\n  static bool fromQueuePath(const std::string& queuepath, FileSystemLocator& out);\n\n  //----------------------------------------------------------------------------\n  //! Parse storage type from storage path string\n  //----------------------------------------------------------------------------\n  static StorageType parseStorageType(const std::string& storagepath);\n\n  //----------------------------------------------------------------------------\n  //! Get host\n  //----------------------------------------------------------------------------\n  std::string getHost() const;\n\n  //----------------------------------------------------------------------------\n  //! Get port\n  //----------------------------------------------------------------------------\n  int getPort() const;\n\n  //----------------------------------------------------------------------------\n  //! Get hostport, concatenated together as \"host:port\"\n  //----------------------------------------------------------------------------\n  std::string getHostPort() const;\n\n  //----------------------------------------------------------------------------\n  //! Get queuepath\n  //----------------------------------------------------------------------------\n  std::string getQueuePath() const;\n\n  //----------------------------------------------------------------------------\n  //! Get \"FST queue\", ie /eos/example.com:3002/fst\n  //----------------------------------------------------------------------------\n  std::string getFSTQueue() const;\n\n  //----------------------------------------------------------------------------\n  //! Get storage path\n  //----------------------------------------------------------------------------\n  std::string getStoragePath() const;\n\n  //----------------------------------------------------------------------------\n  //! Get storage type\n  //----------------------------------------------------------------------------\n  StorageType getStorageType() const;\n\n  //----------------------------------------------------------------------------\n  //! Check whether filesystem is local or remote\n  //----------------------------------------------------------------------------\n  bool isLocal() const;\n\nprivate:\n  std::string host;\n  int32_t port = 0;\n  std::string storagepath;\n  StorageType storageType;\n};\n\n//------------------------------------------------------------------------------\n//! This type helps figure out how to locate the appropriate shared hash for\n//! a given node / group / space.\n//!\n//! Abstracts away the \"config queue\" / \"broadcast queue\" madness.\n//------------------------------------------------------------------------------\nclass SharedHashLocator\n{\npublic:\n  enum class Type {\n    kSpace,\n    kGroup,\n    kNode,\n    kGlobalConfigHash,\n    kFilesystem\n  };\n\n  //----------------------------------------------------------------------------\n  //! Empty constructor\n  //----------------------------------------------------------------------------\n  SharedHashLocator();\n\n  //----------------------------------------------------------------------------\n  //! Constructor: Pass the EOS instance name, BaseView type, and name.\n  //!\n  //! Once we drop the MQ entirely, the instance name can be removed.\n  //----------------------------------------------------------------------------\n  SharedHashLocator(const std::string& instanceName, Type type,\n                    const std::string& name);\n\n  //----------------------------------------------------------------------------\n  //! Constructor: Same as above, but auto-discover instance name.\n  //----------------------------------------------------------------------------\n  SharedHashLocator(Type type, const std::string& name);\n\n  //----------------------------------------------------------------------------\n  //! Constructor: Special case for FileSystems, as they work a bit differently\n  //! than the rest.\n  //----------------------------------------------------------------------------\n  SharedHashLocator(const FileSystemLocator& fsLocator, bool bc2mgm);\n\n  //----------------------------------------------------------------------------\n  //! Convenience \"Constructors\": Make locator for space, group, node\n  //----------------------------------------------------------------------------\n  static SharedHashLocator makeForSpace(const std::string& name);\n  static SharedHashLocator makeForGroup(const std::string& name);\n  static SharedHashLocator makeForNode(const std::string& name);\n  static SharedHashLocator makeForGlobalHash();\n\n  //----------------------------------------------------------------------------\n  //! Get \"config queue\" for shared hash\n  //----------------------------------------------------------------------------\n  std::string getConfigQueue() const;\n\n  //----------------------------------------------------------------------------\n  //! Get \"broadcast queue\" for shared hash\n  //----------------------------------------------------------------------------\n  std::string getBroadcastQueue() const;\n\n  //----------------------------------------------------------------------------\n  //! Check if this object is actually pointing to something\n  //----------------------------------------------------------------------------\n  bool empty() const;\n\n  //----------------------------------------------------------------------------\n  //! Produce SharedHashLocator by parsing config queue\n  //----------------------------------------------------------------------------\n  static bool fromConfigQueue(const std::string& configQueue,\n                              SharedHashLocator& out);\n\n  //----------------------------------------------------------------------------\n  //! Get QDB key for this queue\n  //----------------------------------------------------------------------------\n  std::string getQDBKey() const;\n\n  //----------------------------------------------------------------------------\n  //! Get locator type\n  //----------------------------------------------------------------------------\n  inline SharedHashLocator::Type getType() const\n  {\n    return mType;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get locator name\n  //----------------------------------------------------------------------------\n  inline std::string GetName() const\n  {\n    return mName;\n  }\n\n\nprivate:\n  bool mInitialized;\n  std::string mInstanceName;\n  Type mType;\n  std::string mName;\n  std::string mMqSharedHashPath;\n  std::string mBroadcastQueue;\n  std::string mFilesystemChannel;\n\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif\n\n"
  },
  {
    "path": "common/Logging.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Logging.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Path.hh\"\n#include <grpc/support/log.h>\n#if defined(EOS_HAVE_ZSTD) && EOS_HAVE_ZSTD\n#include <zstd.h>\n#include <fcntl.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#endif\n#include <XrdSys/XrdSysPthread.hh>\n#include <new>\n#include <type_traits>\n#include <atomic>\n#include <map>\n\n//------------------------------------------------------------------------------\n// Anonymous namespace for gRPC logging helpers\n//------------------------------------------------------------------------------\nnamespace\n{\n//------------------------------------------------------------------------------\n// Disable gRPC logging\n//------------------------------------------------------------------------------\nvoid\nDisableGrpcLogging()\n{\n#ifdef HAVE_GRPC_LOGGING\n  auot GrpcLogFunction = []()grpc_log_func_args* args) {\n    // This function intentionally does nothing to suppress gRPC logs\n  };\n  gpr_set_log_function(GrpcLogFunction);\n#else\n\n#endif\n}\n\n//------------------------------------------------------------------------------\n// Enable gRPC logging\n//------------------------------------------------------------------------------\nvoid\nEnableGrpcLogging()\n{\n#ifdef HAVE_GRPC_LOGGING\n  gpr_set_log_function(nullptr);\n#else\n\n#endif\n}\n}\n\nEOSCOMMONNAMESPACE_BEGIN\n\nstatic std::atomic<int> sCounter {0};\nstatic typename std::aligned_storage<sizeof(Logging), alignof(Logging)>::type\nlogging_buf; ///< Memory for the global logging object\nLogging& gLogging = reinterpret_cast<Logging&>(logging_buf);\n\n#if defined(EOS_HAVE_ZSTD) && EOS_HAVE_ZSTD\nstruct ZstdLogState {\n  int fd = -1;\n  ZSTD_CStream* cstream = nullptr;\n  time_t segmentStart = 0;\n  std::string currentPath;\n  std::string symlinkPath;\n};\n#endif\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nLoggingInitializer::LoggingInitializer()\n{\n  if (sCounter++ == 0) {\n    new (&gLogging) Logging(); // placement new\n  }\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nLoggingInitializer::~LoggingInitializer()\n{\n  if (--sCounter == 0) {\n    gLogging.LB->shutDown();\n    (&gLogging)->~Logging();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get singleton instance\n//------------------------------------------------------------------------------\nLogging&\nLogging::GetInstance()\n{\n  return gLogging;\n}\n\n//------------------------------------------------------------------------------\n/// Destructor\n//------------------------------------------------------------------------------\n// Logging::~Logging(){\n// \tif (LB)\n// \t\tdelete LB;\n// }\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nLogging::Logging():\n  gLogMask(0), gPriorityLevel(0), gToSysLog(false),  gUnit(\"none\"),\n  gShortFormat(0), gRateLimiter(false)\n{\n  LB = new LogBuffer;\n  // Initialize the log array and sets the log circular size\n  gLogCircularIndex.resize(LOG_DEBUG + 1);\n  gLogMemory.resize(LOG_DEBUG + 1);\n  gCircularIndexSize = EOSCOMMONLOGGING_CIRCULARINDEXSIZE;\n\n  for (int i = 0; i <= LOG_DEBUG; i++) {\n    gLogCircularIndex[i] = 0;\n    gLogMemory[i].resize(gCircularIndexSize);\n  }\n\n  gZeroVid.name = \"-\";\n  XrdOucString tosyslog;\n\n  if (getenv(\"EOS_LOG_SYSLOG\")) {\n    tosyslog = getenv(\"EOS_LOG_SYSLOG\");\n\n    if ((tosyslog == \"1\" ||\n         (tosyslog == \"true\"))) {\n      gToSysLog = true;\n    }\n  }\n\n#if defined(EOS_HAVE_ZSTD) && EOS_HAVE_ZSTD\n  // ZSTD logging configuration from environment\n  const char* zenv = getenv(\"EOS_ZSTD_LOGGING\");\n\n  if (zenv && (!strcmp(zenv, \"1\") || !strcasecmp(zenv, \"true\") ||\n               !strcasecmp(zenv, \"yes\") || !strcasecmp(zenv, \"on\"))) {\n    gZstdEnable = true;\n  }\n\n  // Prefer renamed variables; fallback to legacy for compatibility\n  const char* zrot = getenv(\"EOS_ZSTD_LOGGING_ROTATION\");\n\n  if ((!zrot || !*zrot)) {\n    zrot = getenv(\"EOS_ZSTD_ROTATION\");\n  }\n\n  if (zrot && *zrot) {\n    int v = atoi(zrot);\n\n    if (v > 0) {\n      gZstdRotationSeconds = v;\n    }\n  }\n\n  const char* lvl = getenv(\"EOS_ZSTD_LOGGING_LEVEL\");\n\n  if ((!lvl || !*lvl)) {\n    lvl = getenv(\"EOS_ZSTD_LEVEL\");\n  }\n\n  if (lvl && *lvl) {\n    int v = atoi(lvl);\n\n    if (v >= 1 && v <= 19) {\n      gZstdLevel = v;\n    }\n  }\n\n  const char* xrd = getenv(\"XRDLOGDIR\");\n  gZstdBaseDir = (xrd && *xrd) ? xrd : \"/var/log/eos\";\n\n  if (gZstdEnable) {\n    // Redirect STDERR into our pipe to capture messages from other components\n    int fds[2];\n\n    if (::pipe(fds) == 0) {\n      gStderrPipeRead = fds[0];\n      gStderrPipeWrite = fds[1];\n      fflush(stderr);\n      ::dup2(gStderrPipeWrite, STDERR_FILENO);\n      gStderrThread = std::thread([this]() {\n        this->stderrReaderLoop();\n      });\n      gStderrThread.detach();\n    }\n\n    // Initialize zstd logging dir and migrate any existing plain main log\n    zstdMaybeInit();\n    zstdMigratePlainMain();\n  }\n\n#endif\n}\n\n//------------------------------------------------------------------------------\n// Should log function\n//------------------------------------------------------------------------------\nbool\nLogging::shouldlog(const char* func, int priority)\n{\n  if (priority == LOG_SILENT) {\n    return true;\n  }\n\n  // short cut if log messages are masked\n  if (!((LOG_MASK(priority) & gLogMask))) {\n    return false;\n  }\n\n  // apply filter to avoid message flooding for debug messages\n  if (priority >= LOG_INFO) {\n    if (gDenyFilter.Num()) {\n      // this is a normal filter by function name\n      if (gDenyFilter.Find(func)) {\n        return false;\n      }\n    }\n  }\n\n  return true;\n}\n\n\n#if LOG_BUFFER_DBG\n\nstatic int cmpPtr(const void* a, const void* b)\n{\n  return (*(intptr_t*)a - * (intptr_t*)b);\n}\n\nstatic void\ncheck_log_buffer_chain(LogBuffer::log_buffer* chain, int maxSize,\n                       const char* name, const char* _FILE, const int _LINE)\n{\n  LogBuffer::log_buffer* buff2, *buff3;\n  LogBuffer::log_buffer** arrAct = (LogBuffer::log_buffer**) malloc(\n                                     maxSize * sizeof(void*));\n  int m;\n\n  for (m = 0, buff2 = chain; buff2 != NULL; buff2 = buff2->h.next, m++) {\n    if (m > 0) {\n      if (buff3 == buff2) {\n        fprintf(stderr,\n                \"%s:%d log_buffer_prb buffer twice (in succesion) on %s chain after %u items, cut\\n\",\n                _FILE, _LINE, name, m);\n        fflush(stderr);\n        buff3->h.next = NULL;\n      }\n    };\n\n    buff3 = buff2;\n\n    if (m >= maxSize) {\n      fprintf(stderr,\n              \"%s:%d log_buffer_prb more (%u) buffers on %s list than total (%u), cut\\n\",\n              _FILE, _LINE, m, name, maxSize);\n      fflush(stderr);\n      buff3->h.next = NULL;\n      break;\n    }\n\n    arrAct[m] = buff2;\n  }\n\n  if (m > 1) {\n    qsort(arrAct, m, sizeof(void*), cmpPtr);\n    int i;\n\n    for (i = 1; i < m; i++)\n      if (arrAct[i - 1] == arrAct[i]) {\n        buff2 = arrAct[i - 1];\n        fprintf(stderr,\n                \"%s:%d log_buffer_prb buffer %p twice on chain %s, cut\\n\",  _FILE, _LINE,\n                buff2, name);\n        fflush(stderr);\n        buff2->h.next = NULL;\n      }\n  }\n\n  free(arrAct);\n  fprintf(stderr, \"%s:%d log_buffer chain %s verified %u elements\\n\",\n          _FILE, _LINE, name, m);\n}\n#endif\n\n\nLogBuffer::log_buffer*\nLogBuffer::log_alloc_buffer()\n{\n  LogBuffer::log_buffer* buff = NULL;\n  std::unique_lock<std::mutex> guard(log_buffer_mutex);\n\n  if (shuttingDown) {\n    return NULL;\n  }\n\n  /* log_buffer_balance is incorrect until we really allocated a buffer! */\n  LogBuffer::log_buffer_balance++;\n\n  while (true) {\n    buff = free_buffers;\n\n    if (buff != NULL) {\n      free_buffers = buff->h.next;\n      log_buffer_free--;\n      break;\n    }\n\n    if (shuttingDown) {\n      return NULL;\n    }\n\n    /* no free buffer, alloc new one if below budget, or wait */\n    if (LogBuffer::log_buffer_total < LogBuffer::max_log_buffers) {\n      buff = (struct log_buffer*) malloc(sizeof(struct log_buffer));\n      log_buffer_total++;\n#if LOG_BUFFER_DBG > 2\n      buff->h.debug1 = 7;\n      {\n        /* consistency checks, to be removed*/\n        int num_in_queue;\n        LogBuffer::log_buffer* bx = active_head, *bbx;\n\n        for (num_in_queue = 0; bx != NULL; num_in_queue++) {\n          bbx = bx->h.next;\n\n          if (bx == bbx) {\n            fprintf(stderr, \"%s:%d active log_buffer_prb!\\n\",\n                    __FILE__, __LINE__);\n            bx->h.next = NULL;\n            break;\n          }\n\n          bx = bbx;\n        }\n\n        if (num_in_queue != log_buffer_in_q)\n          fprintf(stderr, \"%s:%d wrong log_buffer_in_q: %d != %d\\n\",\n                  __FILE__, __LINE__,\n                  num_in_queue, log_buffer_in_q);\n\n        fprintf(stderr,\n                \"\\ntotal_log_buffers: %d balance %d in_q %d free %d waiters %d\\n\",\n                log_buffer_total, log_buffer_balance,\n                log_buffer_in_q,\n                log_buffer_free,\n                log_buffer_waiters);\n      }\n#else\n\n      if ((log_buffer_total & 0x1ff) == 0)\n        fprintf(stderr,\n                \"\\ntotal_log_buffers: %d balance %d in_q %d free %d waiters %d\\n\",\n                log_buffer_total, log_buffer_balance,\n                log_buffer_in_q, log_buffer_free, log_buffer_waiters);\n\n#endif\n      break;\n    }\n\n    /* wait for a free buffer */\n#if LOG_BUFFER_DBG > 2\n    check_log_buffer_chain(active_head, log_buffer_total,\n                           \"active\", __FILE__, __LINE__);\n#endif\n\n    if ((log_buffer_num_waits & 0xfff) == 0)\n      fprintf(stderr,\n              \"log_buffer_shortage #%u with %u waiters, total_log_buffers %u balance %d in_q %u free %u\\n\",\n              log_buffer_num_waits, log_buffer_waiters,\n              LogBuffer::log_buffer_total, LogBuffer::log_buffer_balance,\n              log_buffer_in_q, log_buffer_free);\n\n    log_buffer_num_waits++;\n    LogBuffer::log_buffer_waiters++;  /* this asks for a wake-up call when a buffer is freed */\n    log_buffer_shortage.wait(guard);\n    LogBuffer::log_buffer_waiters--;\n    /* retry... */\n    continue;\n  }\n\n  buff->h.next = NULL;\n  buff->h.fanOutBuffer = NULL;\n#if LOG_BUFFER_DBG\n  buff->h.debug1 = 42;;\n#endif\n  return buff;\n}\n\nvoid\nLogBuffer::log_return_buffers(LogBuffer::log_buffer* buff)\n{\n  LogBuffer::log_buffer* buff2, *buff3;\n  /* count number of buffers returned, find last one (buff2 (='previous')) */\n  int n = 1;\n\n  for (buff2 = buff; (buff3 = buff2->h.next) != NULL; buff2 = buff3) {\n#if LOG_BUFFER_DBG > 1\n\n    if (buff3->h.debug1 != 42) {    /* check the next buffer */\n      fprintf(stderr,\n              \"%s:%d log_buffer_prb returning circular buffer list %p->%p code %d, cut\\n\",\n              __FILE__, __LINE__, buff2, buff3, buff3->h.debug1);\n      buff2->h.next = NULL;\n      break;\n    } else          /*debug*/\n#endif\n#if LOG_BUFFER_DBG > 0\n      buff2->h.debug1 = 52;           /* flag buffer as seen */\n\n#endif\n    n++;\n  }\n\n  std::unique_lock<std::mutex> guard(log_buffer_mutex);\n#if LOG_BUFFER_DBG > 0\n  buff2->h.debug1 =\n    52;                   /* flags the last buffer as well, it has been checked */\n\n  if (log_buffer_free + n >\n      log_buffer_total) {   /* Something's wrong, check all chains thoroughly */\n    fprintf(stderr,\n            \"%s:%d log_buffer_prb log_buffer_free %d > log_buffer_total %d, %d buffers returned\\n\",\n            __FILE__, __LINE__, log_buffer_free, log_buffer_total, n);\n    fflush(stderr);\n\n    if (n > 1) {\n      check_log_buffer_chain(buff, n + 1, \"2Bfreed\", __FILE__, __LINE__);\n    }\n\n    check_log_buffer_chain(active_head, log_buffer_total, \"active\", __FILE__,\n                           __LINE__);\n    return;         /* drop all these buffers */\n  }\n\n#endif\n  buff2->h.next = free_buffers;\n  free_buffers = buff;\n  log_buffer_free += n;\n\n  if (log_buffer_waiters > 0) {   /* This is the condition the CV protects */\n    if (n == 1) {\n      log_buffer_shortage.notify_one();\n    } else {\n      log_buffer_shortage.notify_all();\n    }\n  }\n}\n\nvoid\nLogBuffer::log_queue_buffer(LogBuffer::log_buffer* buff)\n{\n  std::unique_lock<std::mutex> guard(log_buffer_mutex);\n\n  if (shuttingDown) { /* get out quickly, the queues are no longer valid */\n    // free(buff);\n    return;\n  }\n\n  /* this starts the log thread */\n  if ((not log_thread_started) and (not log_suspended)) {\n    resume_int();\n  }\n\n  LogBuffer::log_buffer* prev;\n\n  if (active_tail == NULL) {\n    /* the following works because offset(next) == 0 */\n    prev = (LogBuffer::log_buffer*) &active_tail;\n  } else {\n    prev = active_tail;\n  }\n\n  buff->h.next = NULL;\n  prev->h.next = buff;\n  active_tail = buff;\n\n  if (!active_head) {\n    active_head = buff;\n  }\n\n  log_buffer_in_q++;\n  /* log_buffer_balance designates buffers between intended allocation and print queueing */\n  log_buffer_balance--;\n  log_buffer_cond.notify_all();\n}\n\n\nvoid\nLogBuffer::log_thread()\n{\n  LogBuffer::log_buffer* buff = NULL, *buff_2b_returned = NULL;\n  unsigned int num_buff_2b_returned = 0;\n#if LOG_BUFFER_DBG\n  unsigned int notify_counter = 0;\n  unsigned int old_waits = 0;\n#endif\n  std::unique_lock<std::mutex> guard(log_buffer_mutex);\n\n  while (1) {\n    if (shuttingDown > 0 or active_head == NULL or num_buff_2b_returned > 15 or\n        log_buffer_waiters > 0) {\n      if (buff_2b_returned != NULL) {\n        guard.unlock();\n        log_return_buffers(buff_2b_returned);\n        guard.lock();\n        num_buff_2b_returned = 0;\n        buff_2b_returned = NULL;\n        continue;\n      }\n\n      if (shuttingDown > 0 and (active_head == NULL or shuttingDown > 3)) {\n        /* there is no safe way to dispatch what's still in the queue: the stream pointers\n         * may no longer be valid unless this is a graceful shutdown */\n        shuttingDown = 42;\n        guard.unlock();                      /* Time to get out */\n        return;\n      }\n\n      if (active_head == NULL) {\n#if LOG_BUFFER_DBG\n\n        if ((log_buffer_num_waits > old_waits) and ++notify_counter > 1000) {\n          notify_counter = 0;\n          old_waits = log_buffer_num_waits;\n          fprintf(stderr,\n                  \"\\nlog_buffer queue empty, log_buffer_total: %d balance %d free %d waits %u waiters %d\\n\",\n                  log_buffer_total, log_buffer_balance, log_buffer_free,\n                  log_buffer_num_waits, log_buffer_waiters);\n        }\n\n#endif\n        fflush(stderr);\n        log_buffer_cond.wait(guard);\n\n        if (shuttingDown) {\n          shuttingDown = 41;\n          continue;\n        }\n      }\n    }\n\n    if (active_head) {\n      /* unchain */\n      buff = active_head;\n      active_head = active_head->h.next;\n      bool null_active_head = active_head == NULL;\n\n      if (null_active_head) {\n        active_tail = NULL;\n      }\n\n      log_buffer_in_q--;\n      guard.unlock();                                 /* drop while buffer is printed */\n\n      if (!eos::common::Logging::GetInstance().IsZstdEnabled()) {\n        fprintf(stderr, \"%s\\n\", buff->buffer);\n      }\n\n      if (!eos::common::Logging::GetInstance().IsZstdEnabled()) {\n        if (null_active_head) {\n          fflush(stderr);        /* only flush if there's no other */\n        }\n      }\n\n      if (eos::common::Logging::GetInstance().gToSysLog) {\n        syslog(buff->h.priority, \"%s\", buff->h.ptr);\n      }\n\n      if (eos::common::Logging::GetInstance().IsZstdEnabled()) {\n        // Resolve canonical per-tag name from source/fanout and write compressed\n        std::string tag = eos::common::Logging::GetInstance().ResolveZstdTag(\n                            buff->h.sourceTag,\n                            buff->h.fanOutTag);\n\n        if (!tag.empty()) {\n          eos::common::Logging::GetInstance().WriteZstd(tag.c_str(), buff->buffer);\n        }\n      } else {\n        if (buff->h.fanOutBuffer != NULL) {\n          if (buff->h.fanOutS != NULL) {\n            fputs(buff->h.fanOutBuffer, buff->h.fanOutS);\n            fflush(buff->h.fanOutS);\n          }\n\n          if (buff->h.fanOut != NULL) {\n            fputs(buff->h.fanOutBuffer, buff->h.fanOut);\n            fflush(buff->h.fanOut);\n          }\n        }\n      }\n\n      // Also write the main formatted line into a compressed stream named like xrdlog.<service>\n      if (eos::common::Logging::GetInstance().IsZstdEnabled()) {\n        std::string mainTag = eos::common::Logging::GetInstance().GetMainZstdTag();\n        eos::common::Logging::GetInstance().WriteZstd(mainTag.c_str(), buff->buffer);\n      }\n\n      if (buff_2b_returned != buff) {\n        buff->h.next = buff_2b_returned;\n        buff_2b_returned = buff;\n        num_buff_2b_returned++;\n      } else\n        fprintf(stderr, \"%s.%d log_buffer_prb returning returned log_buffer\\n\",\n                __FILE__, __LINE__);\n\n      guard.lock();\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Logging function\n//------------------------------------------------------------------------------\nconst char*\nLogging::log(const char* func, const char* file, int line, const char* logid,\n             const VirtualIdentity& vid, const char* cident, int priority,\n             const char* msg, ...)\n{\n  bool silent = (priority == LOG_SILENT);\n\n  // short cut if log messages are masked\n  if (!silent && !((LOG_MASK(priority) & gLogMask))) {\n    return \"\";\n  }\n\n  // apply filter to avoid message flooding for debug messages\n  if (!silent && priority >= LOG_INFO) {\n    if (gAllowFilter.Num()) {\n      // if this is a pass-through filter e.g. we want to see exactly this messages\n      if (!gAllowFilter.Find(func)) {\n        return \"\";\n      }\n    } else if (gDenyFilter.Num()) {\n      // this is a normal filter by function name\n      if (gDenyFilter.Find(func)) {\n        return \"\";\n      }\n    }\n  }\n\n  struct LogBuffer::log_buffer* logBuffer = LB->log_alloc_buffer();\n\n  if (logBuffer == NULL) {\n    return \"\";  /* log object being destroyed */\n  }\n\n  char* buffer = logBuffer->buffer;\n  XrdOucString File = file;\n  // we show only one hierarchy directory like Acl (assuming that we have only\n  // file names like *.cc and *.hh\n  File.erase(0, File.rfind(\"/\") + 1);\n  File.erase(File.length() - 3);\n  // Capture source tag (basename without extension) for later zstd tag selection\n  strncpy(logBuffer->h.sourceTag, File.c_str(),\n          sizeof(logBuffer->h.sourceTag) - 1);\n  logBuffer->h.sourceTag[sizeof(logBuffer->h.sourceTag) - 1] = '\\0';\n  time_t current_time;\n  struct timeval tv;\n  struct timezone tz;\n  tm tm;\n  va_list args;\n  va_start(args, msg);\n  gettimeofday(&tv, &tz);\n  current_time = tv.tv_sec;\n  char linen[16];\n  sprintf(linen, \"%d\", line);\n  char fcident[1024];\n  XrdOucString truncname = vid.name;\n\n  // we show only the last 16 bytes of the name\n  if (truncname.length() > 16) {\n    truncname.insert(\"..\", 0);\n    truncname.erase(0, truncname.length() - 16);\n  }\n\n  char sourceline[64];\n\n  if (gShortFormat) {\n    localtime_r(&current_time, &tm);\n    snprintf(sourceline, sizeof(sourceline) - 1, \"%s:%s\", File.c_str(), linen);\n    XrdOucString slog = logid;\n\n    if (slog.beginswith(\"logid:\")) {\n      slog.erase(0, 6);\n      sprintf(buffer,\n              \"%02d%02d%02d %02d:%02d:%02d t=%lu.%06lu f=%-16s l=%s %s s=%-24s \",\n              tm.tm_year - 100, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour,\n              tm.tm_min, tm.tm_sec, current_time, (unsigned long) tv.tv_usec,\n              func, GetPriorityString(priority), slog.c_str(), sourceline);\n    } else {\n      sprintf(buffer,\n              \"%02d%02d%02d %02d:%02d:%02d t=%lu.%06lu f=%-16s l=%s tid=%016lx s=%-24s \",\n              tm.tm_year - 100, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour,\n              tm.tm_min, tm.tm_sec, current_time, (unsigned long) tv.tv_usec,\n              func, GetPriorityString(priority), (unsigned long) XrdSysThread::ID(),\n              sourceline);\n    }\n  } else {\n    sprintf(fcident,\n            \"tident=%s sec=%-5s uid=%d gid=%d name=%s geo=\\\"%s\\\" xt=\\\"%s\\\" ob=\\\"%s\\\"\",\n            cident,\n            vid.prot.c_str(), vid.uid, vid.gid, truncname.c_str(), vid.geolocation.c_str(),\n            vid.trace.c_str(), vid.onbehalf.c_str());\n    localtime_r(&current_time, &tm);\n    snprintf(sourceline, sizeof(sourceline) - 1, \"%s:%s\", File.c_str(), linen);\n    sprintf(buffer,\n            \"%02d%02d%02d %02d:%02d:%02d time=%lu.%06lu func=%-24s level=%s logid=%s unit=%s tid=%016lx source=%-30s %s \",\n            tm.tm_year - 100, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min,\n            tm.tm_sec, current_time, (unsigned long) tv.tv_usec, func,\n            GetPriorityString(priority), logid, gUnit.c_str(),\n            (unsigned long) XrdSysThread::ID(), sourceline, fcident);\n  }\n\n  char* ptr = buffer + strlen(buffer);\n  // limit the length of the output to buffer-1 length\n  vsnprintf(ptr, sizeof(logBuffer->buffer) - (ptr - buffer + 1), msg, args);\n\n  if (!silent) {\n    XrdSysMutexHelper scope_lock(gMutex);\n\n    if (rate_limit(tv, priority, file, line)) {\n      LB->log_return_buffers(logBuffer);\n      va_end(args);\n      return \"\";\n    }\n  }\n\n  logBuffer->h.ptr = ptr;\n\n  if (!silent) {\n    if (gLogFanOut.size()) {\n      logBuffer->h.fanOutS = NULL;\n      logBuffer->h.fanOut = NULL;\n      logBuffer->h.fanOutBuffer = ptr + strlen(ptr) + 1;\n      logBuffer->h.fanOutBufLen = sizeof(logBuffer->buffer) -\n                                  (logBuffer->h.fanOutBuffer - logBuffer->buffer);\n      logBuffer->h.fanOutTag[0] = '\\0';\n\n      // we do log-message fanout\n      if (gLogFanOut.count(\"*\")) {\n        logBuffer->h.fanOutS = gLogFanOut[\"*\"];\n        snprintf(logBuffer->h.fanOutBuffer, logBuffer->h.fanOutBufLen, \"%s\\n\",\n                 logBuffer->buffer);\n        snprintf(logBuffer->h.fanOutTag, sizeof(logBuffer->h.fanOutTag), \"*\");\n      }\n\n      if (gLogFanOut.count(File.c_str())) {\n        logBuffer->buffer[15] = 0;\n        logBuffer->h.fanOut = gLogFanOut[File.c_str()];\n        snprintf(logBuffer->h.fanOutBuffer, logBuffer->h.fanOutBufLen,\n                 \"%s %s%s%s %-30s %s \\n\",\n                 logBuffer->buffer,\n                 GetLogColour(GetPriorityString(priority)),\n                 GetPriorityString(priority),\n                 EOS_TEXTNORMAL,\n                 sourceline,\n                 logBuffer->h.ptr); /* truncation not an issue */\n        logBuffer->buffer[15] = ' ';\n        snprintf(logBuffer->h.fanOutTag, sizeof(logBuffer->h.fanOutTag), \"%s\",\n                 File.c_str());\n      } else {\n        if (gLogFanOut.count(\"#\")) {\n          logBuffer->buffer[15] = 0;\n          logBuffer->h.fanOut = gLogFanOut[\"#\"];\n          snprintf(logBuffer->h.fanOutBuffer, logBuffer->h.fanOutBufLen,\n                   \"%s %s%s%s [%05d/%05d] %16s ::%-16s %s \\n\",\n                   logBuffer->buffer,\n                   GetLogColour(GetPriorityString(priority)),\n                   GetPriorityString(priority),\n                   EOS_TEXTNORMAL,\n                   vid.uid,\n                   vid.gid,\n                   truncname.c_str(),\n                   func,\n                   logBuffer->h.ptr\n                  );\n          logBuffer->buffer[15] = ' ';\n          snprintf(logBuffer->h.fanOutTag, sizeof(logBuffer->h.fanOutTag), \"#\");\n        }\n      }\n    }\n  }\n\n  va_end(args);\n  const char* rptr;\n\n  if (silent) {\n    priority = LOG_DEBUG;\n  }\n\n  // store into global log memory\n  {\n    XrdSysMutexHelper scope_lock(gMutex);\n    /* the following copies the buffer, hence it can be queued and vanish anytime after */\n    gLogMemory[priority][(gLogCircularIndex[priority]) % gCircularIndexSize] =\n      buffer;\n    rptr = gLogMemory[priority][(gLogCircularIndex[priority]) %\n                                gCircularIndexSize].c_str();\n    gLogCircularIndex[priority]++;\n  }\n\n  if (!silent) {\n    logBuffer->h.priority = priority;\n    LB->log_queue_buffer(logBuffer);\n  } else {\n    LB->log_return_buffers(logBuffer);\n  }\n\n  return rptr;\n}\n\nbool\nLogging::rate_limit(struct timeval& tv, int priority, const char* file,\n                    int line)\n{\n  static bool do_limit = false;\n  static std::string last_file = \"\";\n  static int last_line = 0;\n  static int last_priority = priority;\n  static struct timeval last_tv;\n\n  if (!gRateLimiter) {\n    return false;\n  }\n\n  if ((line == last_line) &&\n      (priority == last_priority) &&\n      (last_file == file) &&\n      (priority < LOG_WARNING)) {\n    float elapsed = (1.0 * (tv.tv_sec - last_tv.tv_sec)) - ((\n                      tv.tv_usec - last_tv.tv_usec) / 1000000.0);\n\n    if (elapsed < 5.0) {\n      if (!do_limit) {\n        fprintf(stderr,\n                \"                 ---- high rate error messages suppressed ----\\n\");\n      }\n\n      do_limit = true;\n    } else {\n      do_limit = false;\n    }\n  } else {\n    do_limit = false;\n  }\n\n  if (!do_limit) {\n    last_tv = tv;\n    last_line = line;\n    last_file = file;\n    last_priority = priority;\n  }\n\n  return do_limit;\n}\n\n//------------------------------------------------------------------------------\n// Set the log priority\n//------------------------------------------------------------------------------\nvoid\nLogging::SetLogPriority(int pri)\n{\n  gLogMask = LOG_UPTO(pri);\n  gPriorityLevel = pri;\n\n  if (pri == LOG_DEBUG) {\n    EnableGrpcLogging();\n  } else {\n    DisableGrpcLogging();\n  }\n}\n\n#if defined(EOS_HAVE_ZSTD) && EOS_HAVE_ZSTD\nstd::string\nLogging::GetMainZstdTag() const\n{\n  const std::string& s = gZstdUnitDir.empty() ? gZstdBaseDir : gZstdUnitDir;\n  auto pos = s.find_last_of('/');\n  std::string base = (pos == std::string::npos) ? s : s.substr(pos + 1);\n\n  if (base.empty()) {\n    base = \"mgm\";\n  }\n\n  return std::string(\"xrdlog.\") + base;\n}\n\nstd::string\nLogging::resolveZstdTag(const char* sourceTag, const char* fanOutTag) const\n{\n  auto isAllowed = [&](const std::string & t) -> bool {\n    for (const auto& a : gZstdAllowedTags) if (a == t)\n      {\n        return true;\n      }\n\n    return false;\n  };\n\n  // Prefer explicit fan-out tag if it exists and is allowed\n  if (fanOutTag && *fanOutTag) {\n    std::string t = fanOutTag;\n\n    if (isAllowed(t)) {\n      return t;\n    }\n  }\n\n  // Map source tag via alias table\n  std::string st = (sourceTag ? sourceTag : \"\");\n\n  for (const auto& pr : gZstdAliasPairs) {\n    if (st == pr.first) {\n      if (isAllowed(pr.second)) {\n        return pr.second;\n      }\n    }\n  }\n\n  // If source tag is directly in allowed list, keep it\n  if (isAllowed(st)) {\n    return st;\n  }\n\n  // Otherwise, do not create a separate per-tag stream\n  return std::string();\n}\n#endif\n\n#if defined(EOS_HAVE_ZSTD) && EOS_HAVE_ZSTD\nstatic void write_all(int fd, const void* buf, size_t len)\n{\n  const char* p = static_cast<const char*>(buf);\n\n  while (len > 0) {\n    ssize_t n = ::write(fd, p, len);\n\n    if (n <= 0) {\n      if (errno == EINTR) {\n        continue;\n      }\n\n      break;\n    }\n\n    p += n;\n    len -= (size_t)n;\n  }\n}\n#endif\n\nvoid\nLogging::stderrReaderLoop()\n{\n#if defined(EOS_HAVE_ZSTD) && EOS_HAVE_ZSTD\n  // Read from gStderrPipeRead and forward to main compressed log\n  std::string mainTag = GetMainZstdTag();\n  std::string buf;\n  buf.reserve(8 << 10);\n  char tmp[4096];\n\n  while (true) {\n    ssize_t n = ::read(gStderrPipeRead, tmp, sizeof(tmp));\n\n    if (n <= 0) {\n      if (n < 0 && errno == EINTR) {\n        continue;\n      }\n\n      std::this_thread::sleep_for(std::chrono::milliseconds(10));\n      continue;\n    }\n\n    for (ssize_t i = 0; i < n; ++i) {\n      char c = tmp[i];\n\n      if (c == '\\n') {\n        WriteZstd(mainTag.c_str(), buf.c_str());\n        buf.clear();\n      } else {\n        buf.push_back(c);\n      }\n    }\n  }\n\n#endif\n}\n\nvoid\nLogging::WriteZstd(const char* tag, const char* line)\n{\n#if defined(EOS_HAVE_ZSTD) && EOS_HAVE_ZSTD\n\n  if (!gZstdEnable) {\n    return;\n  }\n\n  if (!line || !tag || !*tag) {\n    return;\n  }\n\n  std::lock_guard<std::mutex> g(gZstdMutex);\n  zstdMaybeInit();\n  time_t now = ::time(nullptr);\n  zstdRotateIfNeededLocked(tag, now);\n  ZstdLogState* st = zstdGetStateLocked(tag);\n\n  if (!st || st->fd < 0 || !st->cstream) {\n    return;\n  }\n\n  // Build a line with newline terminator if missing\n  size_t len = ::strlen(line);\n  char nl = '\\n';\n  // First write the line\n  ZSTD_inBuffer in1 = { line, len, 0 };\n  char outBuf[1 << 14];\n\n  do {\n    ZSTD_outBuffer out = { outBuf, sizeof(outBuf), 0 };\n    size_t r = ZSTD_compressStream2(st->cstream, &out, &in1, ZSTD_e_flush);\n    write_all(st->fd, outBuf, out.pos);\n\n    if (ZSTD_isError(r)) {\n      break;\n    }\n\n    if (in1.pos == in1.size) {\n      break;\n    }\n  } while (true);\n\n  // Then a newline if not already present\n  if (len == 0 || line[len - 1] != '\\n') {\n    ZSTD_inBuffer in2 = { &nl, 1, 0 };\n    char outBuf2[256];\n\n    do {\n      ZSTD_outBuffer out = { outBuf2, sizeof(outBuf2), 0 };\n      size_t r = ZSTD_compressStream2(st->cstream, &out, &in2, ZSTD_e_flush);\n      write_all(st->fd, outBuf2, out.pos);\n\n      if (ZSTD_isError(r)) {\n        break;\n      }\n\n      if (in2.pos == in2.size) {\n        break;\n      }\n    } while (true);\n  }\n\n#else\n  (void)tag;\n  (void)line;\n#endif\n}\n\n#if defined(EOS_HAVE_ZSTD) && EOS_HAVE_ZSTD\nvoid Logging::zstdMaybeInit()\n{\n  if (!gZstdEnable) {\n    return;\n  }\n\n  if (gZstdUnitDir.empty()) {\n    // Place compressed logs directly into the base log directory (no extra subdir)\n    gZstdUnitDir = gZstdBaseDir;\n\n    // Normalize: strip trailing slashes\n    while (gZstdUnitDir.size() > 1 && gZstdUnitDir.back() == '/') {\n      gZstdUnitDir.pop_back();\n    }\n  }\n}\n\nvoid Logging::zstdRotateIfNeededLocked(const std::string& tag, time_t now)\n{\n  ZstdLogState* st = zstdGetStateLocked(tag);\n\n  if (!st || st->fd < 0 || !st->cstream) {\n    zstdOpenLocked(tag, now);\n    return;\n  }\n\n  if ((now - st->segmentStart) >= gZstdRotationSeconds) {\n    zstdCloseLocked(tag);\n    zstdOpenLocked(tag, now);\n  }\n}\n\nstd::string Logging::zstdMakeSegmentPath(const std::string& tag,\n    time_t ts) const\n{\n  char tbuf[64];\n  struct tm tm;\n  localtime_r(&ts, &tm);\n  strftime(tbuf, sizeof(tbuf), \"%Y%m%d-%H%M%S\", &tm);\n  std::string path = gZstdUnitDir;\n  path += \"/logs/\";\n  path += tag;\n  path += \"-\";\n  path += tbuf;\n  path += \".zst\";\n  return path;\n}\n\nvoid Logging::zstdEnsureDir()\n{\n  eos::common::Path p((gZstdUnitDir + \"/.keep\").c_str());\n  (void)p.MakeParentPath(S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);\n  (void)::chmod(gZstdUnitDir.c_str(),\n                S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);\n  // Ensure logs/ subdirectory exists for real files\n  eos::common::Path lp((gZstdUnitDir + \"/logs/.keep\").c_str());\n  (void)lp.MakeParentPath(S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);\n  (void)::chmod((gZstdUnitDir + \"/logs\").c_str(),\n                S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);\n}\n\nZstdLogState* Logging::zstdGetStateLocked(const std::string& tag)\n{\n  auto it = gZstdStates.find(tag);\n\n  if (it != gZstdStates.end()) {\n    return it->second;\n  }\n\n  ZstdLogState* st = new ZstdLogState();\n  st->symlinkPath = gZstdUnitDir + \"/\" + tag + \".zstd\";\n  gZstdStates[tag] = st;\n  return st;\n}\n\nvoid Logging::zstdOpenLocked(const std::string& tag, time_t now)\n{\n  zstdEnsureDir();\n  ZstdLogState* st = zstdGetStateLocked(tag);\n  std::string path = zstdMakeSegmentPath(tag, now);\n  int fd = ::open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC, 0644);\n\n  if (fd < 0) {\n    return;\n  }\n\n  st->fd = fd;\n  st->segmentStart = now;\n  st->currentPath = path;\n\n  if (!st->cstream) {\n    st->cstream = ZSTD_createCStream();\n  }\n\n  ZSTD_CCtx_setParameter(st->cstream, ZSTD_c_compressionLevel, gZstdLevel);\n  (void)ZSTD_initCStream(st->cstream, gZstdLevel);\n  // Emit a header by flushing an empty input\n  char dummy = 0;\n  ZSTD_inBuffer in = { &dummy, 0, 0 };\n  char outBuf[64];\n  ZSTD_outBuffer out = { outBuf, sizeof(outBuf), 0 };\n  (void)ZSTD_compressStream2(st->cstream, &out, &in, ZSTD_e_flush);\n  write_all(st->fd, outBuf, out.pos);\n  // Update symlink\n  ::unlink(st->symlinkPath.c_str());\n  // Create relative symlink pointing into logs/\n  auto slash = path.find_last_of('/');\n  std::string base = (slash == std::string::npos) ? path : path.substr(slash + 1);\n  std::string relTarget = \"logs/\";\n  relTarget += base;\n  ::symlink(relTarget.c_str(), st->symlinkPath.c_str());\n}\n\nvoid Logging::zstdCloseLocked(const std::string& tag)\n{\n  ZstdLogState* st = zstdGetStateLocked(tag);\n\n  if (!st) {\n    return;\n  }\n\n  if (st->cstream) {\n    // End frame gracefully\n    char outBuf[256];\n    ZSTD_inBuffer in = { nullptr, 0, 0 };\n    size_t remaining = 0;\n\n    do {\n      ZSTD_outBuffer out = { outBuf, sizeof(outBuf), 0 };\n      remaining = ZSTD_compressStream2(st->cstream, &out, &in, ZSTD_e_end);\n      write_all(st->fd, outBuf, out.pos);\n    } while (remaining != 0);\n\n    ZSTD_freeCStream(st->cstream);\n    st->cstream = nullptr;\n  }\n\n  if (st->fd >= 0) {\n    ::close(st->fd);\n    st->fd = -1;\n  }\n\n  st->currentPath.clear();\n}\n#endif\n\n#if defined(EOS_HAVE_ZSTD) && EOS_HAVE_ZSTD\nvoid Logging::zstdMigratePlainMain()\n{\n  // Move contents of existing plain main log (e.g. xrdlog.mgm) into the compressed stream, then unlink it\n  std::string tag = GetMainZstdTag(); // e.g., \"xrdlog.mgm\"\n  std::string plainPath = gZstdUnitDir + \"/\" + tag; // direct file in base dir\n  struct stat st {};\n\n  if (::stat(plainPath.c_str(), &st) != 0 || !S_ISREG(st.st_mode) ||\n      st.st_size <= 0) {\n    return;\n  }\n\n  int fd = ::open(plainPath.c_str(), O_RDONLY | O_CLOEXEC);\n\n  if (fd < 0) {\n    return;\n  }\n\n  std::string buf;\n  buf.reserve(1 << 16);\n  char tmp[8192];\n  ssize_t n;\n\n  while ((n = ::read(fd, tmp, sizeof(tmp))) > 0) {\n    for (ssize_t i = 0; i < n; ++i) {\n      char c = tmp[i];\n\n      if (c == '\\n') {\n        if (!buf.empty()) {\n          WriteZstd(tag.c_str(), buf.c_str());\n          buf.clear();\n        } else {\n          // empty line: still log as newline\n          WriteZstd(tag.c_str(), \"\");\n        }\n      } else {\n        buf.push_back(c);\n      }\n    }\n  }\n\n  if (!buf.empty()) {\n    WriteZstd(tag.c_str(), buf.c_str());\n    buf.clear();\n  }\n\n  ::close(fd);\n  ::unlink(plainPath.c_str());\n}\n#endif\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Logging.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Logging.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   Logging.hh\n *\n * @brief  Class for message logging.\n *\n * You can use this class without creating an instance object (it provides a\n * global singleton). All the 'eos_<state>' functions require that the logging\n * class inherits from the 'LogId' class. As an alternative as set of static\n * 'eos_static_<state>' logging functinos are provided. To define the log level\n * one uses the static 'SetLogPriority' function. 'SetFilter' allows to filter\n * out log messages which are identified by their function/method name\n * (__FUNCTION__). If you prefix this comma seperated list with 'PASS:' it is\n * used as an acceptance filter. By default all logging is printed to 'stderr'.\n * You can arrange a log stream filter fan-out using 'AddFanOut'. The fan-out of\n * messages is defined by the source filename the message comes from and mappes\n * to a FILE* where the message is written. If you add a '*' fan-out you can\n * write all messages into this file. If you add a '#' fan-out you can write\n * all messages which are not in any other fan-out (besides '*') into that file.\n * The fan-out functionality assumes that\n * source filenames follow the pattern <fan-out-name>.xx !!!!\n */\n\n#ifndef __EOSCOMMON_LOGGING_HH__\n#define __EOSCOMMON_LOGGING_HH__\n\n#include \"common/Namespace.hh\"\n#include \"common/Mapping.hh\"\n#include <XrdOuc/XrdOucHash.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdSys/XrdSysPthread.hh>\n#include <XrdSec/XrdSecEntity.hh>\n#include <string.h>\n#include <sys/syslog.h>\n#include <sys/time.h>\n#include <uuid/uuid.h>\n#include <string>\n#include <vector>\n#include <sstream>\n#include <thread>\n#include <chrono>\n#include <mutex>\n#include <condition_variable>\n\n#define SSTR(message) static_cast<std::ostringstream&>(std::ostringstream().flush() << message).str()\n\nEOSCOMMONNAMESPACE_BEGIN\n\n#ifdef __has_include\n#  if __has_include(<zstd.h>)\n#    define EOS_HAVE_ZSTD 1\n#  endif\n#endif\n\n#if defined(EOS_HAVE_ZSTD) && EOS_HAVE_ZSTD\nstruct ZstdLogState;\n#endif\n\n#define EOS_TEXTNORMAL \"\\033[0m\"\n#define EOS_TEXTBLACK  \"\\033[49;30m\"\n#define EOS_TEXTRED    \"\\033[49;31m\"\n#define EOS_TEXTREDERROR \"\\033[47;31m\\e[5m\"\n#define EOS_TEXTBLUEERROR \"\\033[47;34m\\e[5m\"\n#define EOS_TEXTGREEN  \"\\033[49;32m\"\n#define EOS_TEXTYELLOW \"\\033[49;33m\"\n#define EOS_TEXTBLUE   \"\\033[49;34m\"\n#define EOS_TEXTBOLD   \"\\033[1m\"\n#define EOS_TEXTUNBOLD \"\\033[0m\"\n#define LOG_SILENT 0xffff\n\n//------------------------------------------------------------------------------\n//! Log Macros usable in objects inheriting from the logId Class\n//------------------------------------------------------------------------------\n#define eos_log(__EOSCOMMON_LOG_PRIORITY__ , ...) \\\n  eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, this->logId, \\\n                                          vid, this->cident, __EOSCOMMON_LOG_PRIORITY__, __VA_ARGS__)\n#define eos_debug(...) \\\n  if ((LOG_MASK(LOG_DEBUG) & eos::common::Logging::GetInstance().GetLogMask()) != 0) { \\\n    eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, this->logId, \\\n                                            vid, this->cident, (LOG_DEBUG), __VA_ARGS__); \\\n  }\n#define eos_info(...) \\\n  if ((LOG_MASK(LOG_INFO) & eos::common::Logging::GetInstance().GetLogMask()) != 0) { \\\n  eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, this->logId, \\\n                                          vid, this->cident, (LOG_INFO), __VA_ARGS__); \\\n  }\n#define eos_notice(...) \\\n  if ((LOG_MASK(LOG_NOTICE) & eos::common::Logging::GetInstance().GetLogMask()) != 0) { \\\n  eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, this->logId, \\\n                                          vid, this->cident, (LOG_NOTICE), __VA_ARGS__); \\\n  }\n#define eos_warning(...) \\\n  if ((LOG_MASK(LOG_WARNING) & eos::common::Logging::GetInstance().GetLogMask()) != 0) { \\\n    eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, this->logId, \\\n                                            vid, this->cident, (LOG_WARNING), __VA_ARGS__); \\\n  }\n#define eos_err(...)                                                    \\\n  if ((LOG_MASK(LOG_ERR) & eos::common::Logging::GetInstance().GetLogMask()) != 0) { \\\n    eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, this->logId, \\\n                                            vid, this->cident, (LOG_ERR) , __VA_ARGS__); \\\n  }\n#define eos_crit(...) \\\n  if ((LOG_MASK(LOG_CRIT) & eos::common::Logging::GetInstance().GetLogMask()) != 0) { \\\n    eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, this->logId, \\\n                                            vid, this->cident, (LOG_CRIT), __VA_ARGS__); \\\n  }\n#define eos_alert(...) \\\n  if ((LOG_MASK(LOG_ALERT) & eos::common::Logging::GetInstance().GetLogMask()) != 0) { \\\n    eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, this->logId, \\\n                                            vid, this->cident, (LOG_ALERT)  , __VA_ARGS__); \\\n  }\n#define eos_emerg(...) \\\n  if ((LOG_MASK(LOG_EMERG) & eos::common::Logging::GetInstance().GetLogMask()) != 0) { \\\n    eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, this->logId, \\\n                                            vid, this->cident, (LOG_EMERG)  , __VA_ARGS__); \\\n  }\n#define eos_silent(...) \\\n  if ((LOG_MASK(LOG_SILENT) & eos::common::Logging::GetInstance().GetLogMask()) != 0) { \\\n    eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, this->logId, \\\n                                            vid, this->cident, (LOG_SILENT)  , __VA_ARGS__); \\\n  }\n\n//------------------------------------------------------------------------------\n//! Log Macros usable in singleton objects used by individual threads\n//! You should define locally LodId tlLogId in the thread function\n//------------------------------------------------------------------------------\n#define eos_thread_debug(...) \\\n  eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, tlLogId.logId, \\\n                                          vid, tlLogId.cident, (LOG_DEBUG)  , __VA_ARGS__)\n#define eos_thread_info(...) \\\n  eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, tlLogId.logId, \\\n                                          vid, tlLogId.cident, (LOG_INFO)   , __VA_ARGS__)\n#define eos_thread_notice(...) \\\n  eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, tlLogId.logId, \\\n                                          vid, tlLogId.cident, (LOG_NOTICE) , __VA_ARGS__)\n#define eos_thread_warning(...) \\\n  eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, tlLogId.logId, \\\n                                          vid, tlLogId.cident, (LOG_WARNING), __VA_ARGS__)\n#define eos_thread_err(...) \\\n  eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, tlLogId.logId, \\\n                                          vid, tlLogId.cident, (LOG_ERR)    , __VA_ARGS__)\n#define eos_thread_crit(...) \\\n  eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, tlLogId.logId, \\\n                                          vid, tlLogId.cident, (LOG_CRIT)   , __VA_ARGS__)\n#define eos_thread_alert(...) \\\n  eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, tlLogId.logId, \\\n                                          vid, tlLogId.cident, (LOG_ALERT)  , __VA_ARGS__)\n#define eos_thread_emerg(...) \\\n   eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, tlLogId.logId, \\\n                                           vid, tlLogId.cident, (LOG_EMERG)  , __VA_ARGS__)\n\n//------------------------------------------------------------------------------\n//! Log Macros usable from static member functions without LogId object\n//------------------------------------------------------------------------------\n#define eos_static_log(__EOSCOMMON_LOG_PRIORITY__ , ...) \\\n  eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, \\\n                                          \"static..............................\", \\\n                                          eos::common::gLogging.gZeroVid, \"\", \\\n                                          (__EOSCOMMON_LOG_PRIORITY__) , \\\n                                          __VA_ARGS__)\n#define eos_static_debug(...)                                           \\\n  if ((LOG_MASK(LOG_DEBUG) & eos::common::Logging::GetInstance().GetLogMask()) != 0) { \\\n    eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, \\\n                                            \"static..............................\", \\\n                                            eos::common::gLogging.gZeroVid, \"\", \\\n                                            (LOG_DEBUG), __VA_ARGS__);  \\\n  }\n#define eos_static_info(...) \\\n  if ((LOG_MASK(LOG_INFO) & eos::common::Logging::GetInstance().GetLogMask()) != 0) { \\\n    eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, \"static..............................\", \\\n                                            eos::common::gLogging.gZeroVid, \"\", (LOG_INFO), __VA_ARGS__); \\\n  }\n#define eos_static_notice(...) \\\n  if ((LOG_MASK(LOG_NOTICE) & eos::common::Logging::GetInstance().GetLogMask()) != 0) { \\\n    eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, \"static..............................\", \\\n                                            eos::common::gLogging.gZeroVid, \"\", (LOG_NOTICE), __VA_ARGS__); \\\n  }\n#define eos_static_warning(...) \\\n  if ((LOG_MASK(LOG_WARNING) & eos::common::Logging::GetInstance().GetLogMask()) != 0) { \\\n    eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, \"static..............................\", \\\n                                            eos::common::gLogging.gZeroVid, \"\", (LOG_WARNING), __VA_ARGS__); \\\n  }\n#define eos_static_err(...) \\\n   if ((LOG_MASK(LOG_ERR) & eos::common::Logging::GetInstance().GetLogMask()) != 0) { \\\n     eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, \"static..............................\", \\\n                                             eos::common::gLogging.gZeroVid, \"\", (LOG_ERR), __VA_ARGS__); \\\n   }\n#define eos_static_crit(...)                                            \\\n  if ((LOG_MASK(LOG_CRIT) & eos::common::Logging::GetInstance().GetLogMask()) != 0) { \\\n    eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, \"static..............................\", \\\n                                            eos::common::gLogging.gZeroVid, \"\", (LOG_CRIT), __VA_ARGS__); \\\n  }\n#define eos_static_alert(...)                                           \\\n  if ((LOG_MASK(LOG_ALERT) & eos::common::Logging::GetInstance().GetLogMask()) != 0) { \\\n    eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, \"static..............................\", \\\n                                            eos::common::gLogging.gZeroVid, \"\", (LOG_ALERT)  , __VA_ARGS__); \\\n  }\n#define eos_static_emerg(...) \\\n  if ((LOG_MASK(LOG_EMERG) & eos::common::Logging::GetInstance().GetLogMask()) != 0) { \\\n    eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, \"static..............................\", \\\n                                            eos::common::gLogging.gZeroVid,\"\", (LOG_EMERG)  , __VA_ARGS__); \\\n  }\n#define eos_static_silent(...) \\\n  eos::common::Logging::GetInstance().log(__FUNCTION__,__FILE__, __LINE__, \"static..............................\", \\\n                                            eos::common::gLogging.gZeroVid,\"\", (LOG_SILENT)  , __VA_ARGS__)\n\n//------------------------------------------------------------------------------\n//! Log Macros to check if a function would log in a certain log level\n//------------------------------------------------------------------------------\n#define EOS_LOGS_DEBUG   eos::common::Logging::GetInstance().shouldlog(__FUNCTION__,(LOG_DEBUG)  )\n#define EOS_LOGS_INFO    eos::common::Logging::GetInstance().shouldlog(__FUNCTION__,(LOG_INFO)   )\n#define EOS_LOGS_NOTICE  eos::common::Logging::GetInstance().shouldlog(__FUNCTION__,(LOG_NOTICE) )\n#define EOS_LOGS_WARNING eos::common::Logging::GetInstance().shouldlog(__FUNCTION__,(LOG_WARNING))\n#define EOS_LOGS_ERR     eos::common::Logging::GetInstance().shouldlog(__FUNCTION__,(LOG_ERR)    )\n#define EOS_LOGS_CRIT    eos::common::Logging::GetInstance().shouldlog(__FUNCTION__,(LOG_CRIT)   )\n#define EOS_LOGS_ALERT   eos::common::Logging::GetInstance().shouldlog(__FUNCTION__,(LOG_ALERT)  )\n#define EOS_LOGS_EMERG   eos::common::Logging::GetInstance().shouldlog(__FUNCTION__,(LOG_EMERG)  )\n#define EOS_LOGS_SILENT   eos::common::Logging::GetInstance().shouldlog(__FUNCTION__,(LOG_SILENT)  )\n\n#define EOSCOMMONLOGGING_CIRCULARINDEXSIZE 10000\n\n//------------------------------------------------------------------------------\n//! Log Macros providing a third party code location in static functions\n//------------------------------------------------------------------------------\n#define eos_third_party_warning(function,file,line,...)   \\\n  eos::common::Logging::GetInstance().log((function),(file), (line), \"static..............................\", \\\n                                          eos::common::gLogging.gZeroVid, \"\", (LOG_WARNING), __VA_ARGS__)\n\n//------------------------------------------------------------------------------\n//! Class implementing EOS logging\n//------------------------------------------------------------------------------\nclass LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  LogId()\n  {\n    uuid_t uuid;\n    uuid_generate_time(uuid);\n    uuid_unparse(uuid, logId);\n    sprintf(cident, \"<service>\");\n    vid.uid = getuid();\n    vid.gid = getgid();\n    vid.name = \"\";\n    vid.tident = \"\";\n    vid.prot = \"\";\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~LogId() = default;\n\n  //----------------------------------------------------------------------------\n  //! Generate log id value\n  //----------------------------------------------------------------------------\n  static std::string GenerateLogId()\n  {\n    char log_id[40];\n    uuid_t uuid;\n    uuid_generate_time(uuid);\n    uuid_unparse(uuid, log_id);\n    return log_id;\n  }\n\n  //----------------------------------------------------------------------------\n  //! For calls which are not client initiated this function set's a unique\n  //! dummy log id\n  //----------------------------------------------------------------------------\n  void\n  SetSingleShotLogId(const char* td = \"<single-exec>\")\n  {\n    snprintf(logId, sizeof(logId), \"%s\", \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\");\n    snprintf(cident, sizeof(cident), \"%s\", td);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set's the logid\n  //----------------------------------------------------------------------------\n  void\n  SetLogId(const char* newlogid)\n  {\n    if (newlogid && (strncmp(newlogid, logId, sizeof(logId) - 1) != 0)) {\n      // ensure bounded copy; accept truncation\n      strncpy(logId, newlogid, sizeof(logId) - 1);\n      logId[sizeof(logId) - 1] = '\\0';\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set's the logid and trace identifier\n  //----------------------------------------------------------------------------\n  void\n  SetLogId(const char* newlogid, const char* td)\n  {\n    if (newlogid && (newlogid != logId)) {\n      strncpy(logId, newlogid, sizeof(logId) - 1);\n      logId[sizeof(logId) - 1] = '\\0';\n    }\n\n    if (td) {\n      snprintf(cident, sizeof(cident), \"%s\", td);\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set's the logid, vid and trace identifier\n  //----------------------------------------------------------------------------\n  void\n  SetLogId(const char* newlogid,\n           const XrdSecEntity* client,\n           const char* td = \"<service>\")\n  {\n    if (newlogid) {\n      SetLogId(newlogid, td);\n    }\n\n    if (client) {\n      vid.name = client->name;\n      vid.host = (client->host) ? client->host : \"?\";\n      vid.prot = client->prot;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set's the logid, vid and trace identifier\n  //----------------------------------------------------------------------------\n  void\n  SetLogId(const char* newlogid, const VirtualIdentity& vid_in,\n           const char* td = \"\")\n  {\n    vid = vid_in;\n    snprintf(cident, sizeof(cident), \"%s\", td);\n\n    if (vid.token && vid.token->Valid()) {\n      // use the voucher as logging ID\n      snprintf(logId, sizeof(logId), \"%s\", vid.token->Voucher().c_str());\n    } else {\n      if (newlogid != logId) {\n        snprintf(logId, sizeof(logId), \"%s\", newlogid);\n      }\n    }\n  }\n\n  char logId[40]; //< the log Id for message printout\n  char cident[256]; //< the client identifier\n  VirtualIdentity vid; //< the client identity\n};\n\n#define LOG_BUFFER_DBG 0\n\nclass LogBuffer\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  LogBuffer()\n  {\n    char* s = getenv(\"EOS_MGM_LOG_BUFFERS\");\n\n    if (s) {\n      int val = atoi(s);\n\n      if (val > 0) {\n        max_log_buffers = val;\n      }\n    }\n  }\n\n\n  /* Suspend/resume is for forkers - threads aren't carried over into children */\n  void suspend()\n  {\n    std::unique_lock<std::mutex> guard(log_buffer_mutex);\n    log_suspended = true;\n  }\n\n  void resume()\n  {\n    std::unique_lock<std::mutex> guard(log_buffer_mutex);\n    resume_int();\n  }\n\n  void resume_int()\n  {\n    if (!log_thread_started) {\n      log_suspended = false;\n      log_thread_p = std::thread([this] { log_thread(); });\n      log_thread_started = true;\n    }\n  }\n\n  void\n  shutDown(bool gracefully = false)\n  {\n    LogBuffer::log_buffer* buff;\n    std::unique_lock<std::mutex> guard(log_buffer_mutex);\n\n    if (shuttingDown > 0) {\n      return;\n    }\n\n    /* Stop allocating buffers, tell thread to stop, perhaps gracefully */\n    /* many things here  should only be modified under a lock: the log\n       thread set shuttingDown it to indicate it finishes */\n    shuttingDown = (gracefully) ? 1 : 4;\n\n    if (log_thread_started) {\n      while (true) {\n        int old_q = log_buffer_in_q;\n        log_buffer_cond.notify_all();\n        guard.unlock();      /* let log_thread run for a bit */\n        std::this_thread::sleep_for(std::chrono::milliseconds(1000));\n        guard.lock();\n\n        if (shuttingDown > 38) {\n          break;\n        }\n\n        if (log_buffer_in_q == old_q) {\n          shuttingDown++;\n        }\n      }\n\n      if (shuttingDown < 99) {\n        log_thread_p.join();\n      }\n    }\n\n    while ((buff = free_buffers) != NULL) {\n      free_buffers = buff->h.next;\n      --log_buffer_free;\n      guard.unlock();\n#if LOG_BUFFER_DBG\n\n      if (buff->h.debug1 != 52) {\n        fprintf(stderr, \"*** log shutdown: free buffer %#p has debug1=%#x free=%d\\n\",\n                buff, buff->h.debug1, log_buffer_free);\n        break;\n      }\n\n#endif\n      free(buff);\n      guard.lock();\n    }\n  }\n\n  struct log_buffer;\n  struct log_buffer_hdr {\n    struct log_buffer* next;\n#if LOG_BUFFER_DBG\n    int debug1;         /* for debugging only */\n#endif\n    char* ptr;\n    char* fanOutBuffer;\n    FILE* fanOutS;\n    FILE* fanOut;\n    int priority;\n    int fanOutBufLen;\n    char fanOutTag[64]; /* tag name for fan-out (source basename, '*' or '#'), empty if none */\n    char sourceTag[64]; /* source file basename tag */\n\n  };\n\n  struct log_buffer {\n    struct log_buffer_hdr h;\n    char buffer[(8 * 1024 - sizeof(struct log_buffer_hdr))];\n  };\n\n  struct log_buffer* free_buffers = NULL;\n  struct log_buffer* active_head = NULL;\n  struct log_buffer* active_tail = NULL;\n  int shuttingDown = 0;\n  int log_buffer_waiters = 0;     /* protected by log_buffer_shortage_mutex */\n\n  /* limit number of log_buffers */\n  int log_buffer_total = 0;       /* protected by log_mutex */\n  int max_log_buffers = 2048;     /* reasonable: 2048 */;\n\n  /* the following are info only, could be junked */\n  int log_buffer_balance = 0;    /* between \"requested\" and \"queued\" */\n  int log_buffer_free = 0;\n  int log_buffer_in_q = 0;\n  unsigned int log_buffer_num_waits = 0;\n\n  bool log_suspended = false;\n  bool log_thread_started = false;\n\n  std::thread log_thread_p;\n  std::mutex log_buffer_mutex;\n  std::condition_variable log_buffer_cond;\n  std::condition_variable log_buffer_shortage;\n\n  struct log_buffer* log_alloc_buffer();\n  void log_return_buffers(struct log_buffer*);\n  void log_queue_buffer(struct log_buffer*);\n  void log_thread();\n};\n\n\n//------------------------------------------------------------------------------\n//! Class wrapping global singleton objects for logging\n//------------------------------------------------------------------------------\nclass Logging\n{\npublic:\n  //! Typedef for circular index pointing to the next message position int he log array\n  typedef std::vector< unsigned long > LogCircularIndex;\n  //! Typdef for log message array\n  typedef std::vector< std::vector <XrdOucString> > LogArray;\n  VirtualIdentity gZeroVid; ///< Root vid\n  LogCircularIndex gLogCircularIndex; //< global circular index\n  LogArray gLogMemory; //< global logging memory\n  unsigned long gCircularIndexSize; //< global circular index size\n  std::atomic<int> gLogMask; //< log mask\n  std::atomic<int> gPriorityLevel; //< log priority\n  bool gToSysLog; //< duplicate into syslog\n  XrdSysMutex gMutex; //< global mutex\n  XrdOucString gUnit; //< global unit name\n  //! Global list of function names allowed to log\n  XrdOucHash<const char*> gAllowFilter;\n  //! Global list of function names denied to log\n  XrdOucHash<const char*> gDenyFilter;\n  int gShortFormat; //< indiciating if the log-output is in short format\n  //! Here one can define log fan-out to different file descriptors than stderr\n  std::map<std::string, FILE*> gLogFanOut;\n\n  bool gRateLimiter; //< indicating to apply message rate limiting\n\n  LogBuffer* LB;\n\n  //----------------------------------------------------------------------------\n  //! Get singleton instance\n  //----------------------------------------------------------------------------\n  static Logging& GetInstance();\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  Logging();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~Logging() = default;\n  // ~Logging();\n\n  void\n  shutDown(bool gracefully = false)\n  {\n    if (LB) {\n      LB->shutDown(gracefully);\n    }\n  }\n\n\n  //----------------------------------------------------------------------------\n  //! Get current loglevel\n  //----------------------------------------------------------------------------\n  void\n  EnableRateLimiter()\n  {\n    gRateLimiter = true;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get current loglevel\n  //----------------------------------------------------------------------------\n  int\n  GetLogMask() const\n  {\n    return gLogMask;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set the log priority\n  //----------------------------------------------------------------------------\n  void SetLogPriority(int pri);\n\n  //----------------------------------------------------------------------------\n  //! Set the log unit name\n  //----------------------------------------------------------------------------\n  void\n  SetUnit(const char* unit)\n  {\n    gUnit = unit;\n  }\n\n  void\n  SetSysLog(bool onoff)\n  {\n    gToSysLog = onoff;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set index size\n  //----------------------------------------------------------------------------\n  void\n  SetIndexSize(size_t size)\n  {\n    gCircularIndexSize = size;\n\n    for (int i = 0; i <= LOG_DEBUG; i++) {\n      gLogCircularIndex[i] = 0;\n      gLogMemory[i].resize(size);\n      gLogMemory[i].shrink_to_fit();\n    }\n  }\n\n\n  //----------------------------------------------------------------------------\n  //! Set the log filter\n  //----------------------------------------------------------------------------\n  void\n  SetFilter(const char* filter)\n  {\n    int pos = 0;\n    char del = ',';\n    XrdOucString token;\n    XrdOucString pass_tag = \"PASS:\";\n    XrdOucString sfilter = filter;\n    // Clear both maps\n    gDenyFilter.Purge();\n    gAllowFilter.Purge();\n\n    if ((pos = sfilter.find(pass_tag)) != STR_NPOS) {\n      // Extract the function names which are allowed to log\n      pos += pass_tag.length();\n\n      while ((pos = sfilter.tokenize(token, pos, del)) != -1) {\n        gAllowFilter.Add(token.c_str(), NULL, 0, Hash_data_is_key);\n      }\n    } else {\n      // Extract the function names which are denied to log\n      pos = 0;\n\n      try {\n        while ((pos = sfilter.tokenize(token, pos, del)) != -1) {\n          gDenyFilter.Add(token.c_str(), NULL, 0, Hash_data_is_key);\n        }\n      } catch (int& err) {}\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return priority as string\n  //----------------------------------------------------------------------------\n  const char*\n  GetPriorityString(int pri)\n  {\n    if (pri == (LOG_INFO)) {\n      return \"INFO \";\n    }\n\n    if (pri == (LOG_DEBUG)) {\n      return \"DEBUG\";\n    }\n\n    if (pri == (LOG_ERR)) {\n      return \"ERROR\";\n    }\n\n    if (pri == (LOG_EMERG)) {\n      return \"EMERG\";\n    }\n\n    if (pri == (LOG_ALERT)) {\n      return \"ALERT\";\n    }\n\n    if (pri == (LOG_CRIT)) {\n      return \"CRIT \";\n    }\n\n    if (pri == (LOG_WARNING)) {\n      return \"WARN \";\n    }\n\n    if (pri == (LOG_NOTICE)) {\n      return \"NOTE \";\n    }\n\n    if (pri == (LOG_SILENT)) {\n      return \"\";\n    }\n\n    return \"NONE \";\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return priority int from string\n  //----------------------------------------------------------------------------\n  int\n  GetPriorityByString(const char* pri)\n  {\n    if (!strcmp(pri, \"info\")) {\n      return LOG_INFO;\n    }\n\n    if (!strcmp(pri, \"debug\")) {\n      return LOG_DEBUG;\n    }\n\n    if (!strcmp(pri, \"err\")) {\n      return LOG_ERR;\n    }\n\n    if (!strcmp(pri, \"emerg\")) {\n      return LOG_EMERG;\n    }\n\n    if (!strcmp(pri, \"alert\")) {\n      return LOG_ALERT;\n    }\n\n    if (!strcmp(pri, \"crit\")) {\n      return LOG_CRIT;\n    }\n\n    if (!strcmp(pri, \"warning\")) {\n      return LOG_WARNING;\n    }\n\n    if (!strcmp(pri, \"notice\")) {\n      return LOG_NOTICE;\n    }\n\n    if (!strcmp(pri, \"silent\")) {\n      return LOG_SILENT;\n    }\n\n    return -1;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Add a tag fanout filedescriptor to the logging module\n  //----------------------------------------------------------------------------\n  void\n  AddFanOut(const char* tag, FILE* fd)\n  {\n    gLogFanOut[tag] = fd;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Add a tag fanout alias to the logging module\n  //----------------------------------------------------------------------------\n  void\n  AddFanOutAlias(const char* alias, const char* tag)\n  {\n    if (gLogFanOut.count(tag)) {\n      gLogFanOut[alias] = gLogFanOut[tag];\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get a color for a given logging level\n  //----------------------------------------------------------------------------\n  const char*\n  GetLogColour(const char* loglevel)\n  {\n    if (!strcmp(loglevel, \"INFO \")) {\n      return EOS_TEXTGREEN;\n    }\n\n    if (!strcmp(loglevel, \"ERROR\")) {\n      return EOS_TEXTRED;\n    }\n\n    if (!strcmp(loglevel, \"WARN \")) {\n      return EOS_TEXTYELLOW;\n    }\n\n    if (!strcmp(loglevel, \"NOTE \")) {\n      return EOS_TEXTBLUE;\n    }\n\n    if (!strcmp(loglevel, \"CRIT \")) {\n      return EOS_TEXTREDERROR;\n    }\n\n    if (!strcmp(loglevel, \"EMERG\")) {\n      return EOS_TEXTBLUEERROR;\n    }\n\n    if (!strcmp(loglevel, \"ALERT\")) {\n      return EOS_TEXTREDERROR;\n    }\n\n    if (!strcmp(loglevel, \"DEBUG\")) {\n      return \"\";\n    }\n\n    return \"\";\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if we should log in the defined level/filter\n  //!\n  //! @param func name of the calling function\n  //! @param priority priority level of the message\n  //!\n  //----------------------------------------------------------------------------\n  bool shouldlog(const char* func, int priority);\n\n  //----------------------------------------------------------------------------\n  //! Log a message into the global buffer\n  //!\n  //! @param func name of the calling function\n  //! @param file name of the source file calling\n  //! @param line line in the source file\n  //! @param logid log message identifier\n  //! @param vid virtual id of the caller\n  //! @param cident client identifier\n  //! @param priority priority level of the message\n  //! @param msg the actual log message\n  //!\n  //! @return pointer to the log message\n  //----------------------------------------------------------------------------\n  const char* log(const char* func, const char* file, int line,\n                  const char* logid, const VirtualIdentity& vid,\n                  const char* cident, int priority, const char* msg, ...);\n\n\n  //----------------------------------------------------------------------------\n  //! estimates log message distance and similiary to suppress log messages\n  //!\n  //! @param time of the message\n  //! @param priority of the message\n  //! @param source file name\n  //! @param line in source file\n  //!\n  //! @return true if it should be suppressed, otherwise false\n  //---------------------------------------------------------------------------\n\n  bool rate_limit(struct timeval& tv, int priority, const char* file, int line);\n  //----------------------------------------------------------------------------\n  //! Write a single already-formatted line to optional ZSTD log (if enabled)\n  //----------------------------------------------------------------------------\n  void WriteZstd(const char* tag, const char* line);\n  //----------------------------------------------------------------------------\n  //! Query if ZSTD logging is enabled\n  //----------------------------------------------------------------------------\n  bool IsZstdEnabled() const\n  {\n    return gZstdEnable;\n  }\n  //----------------------------------------------------------------------------\n  //! Compute the main ZSTD tag (e.g. \"xrdlog.mgm\") based on configured dir\n  //----------------------------------------------------------------------------\n  std::string GetMainZstdTag() const;\n  //----------------------------------------------------------------------------\n  //! Public resolver for canonical per-tag names (maps aliases, filters to allowed tags)\n  //----------------------------------------------------------------------------\n  std::string ResolveZstdTag(const char* sourceTag, const char* fanOutTag) const\n  {\n    return resolveZstdTag(sourceTag, fanOutTag);\n  }\nprivate:\n#if defined(EOS_HAVE_ZSTD) && EOS_HAVE_ZSTD\n  // ZSTD logging helpers\n  void zstdMaybeInit();\n  void zstdRotateIfNeededLocked(const std::string& tag, time_t now);\n  void zstdOpenLocked(const std::string& tag, time_t now);\n  void zstdCloseLocked(const std::string& tag);\n  std::string zstdMakeSegmentPath(const std::string& tag, time_t ts) const;\n  void zstdEnsureDir();\n  void zstdMigratePlainMain();\n#endif\n\n  // Configuration/state for ZSTD writer\n  bool gZstdEnable = false;\n  bool gZstdSuppressStdErr = true; // when enabled, prefer compressed logs\n  int gZstdRotationSeconds = 3600; // default 1 hour\n  int gZstdLevel = 1;\n  std::string\n  gZstdBaseDir;   // base directory for logs (XRDLOGDIR or /var/log/eos)\n  std::string gZstdUnitDir;   // derived from gUnit at open time\n\n  std::mutex gZstdMutex;\n  // Per-tag ZSTD state (main line tag and fan-out tags)\n  struct ZstdLogState* zstdGetStateLocked(const std::string& tag);\n  std::map<std::string, struct ZstdLogState*> gZstdStates; // owned pointers\n\n  // STDERR redirection into main compressed log\n  int gStderrPipeRead = -1;\n  int gStderrPipeWrite = -1;\n  std::thread gStderrThread;\n  void stderrReaderLoop();\n\n  // Canonical tag resolution (restrict to configured fan-out list)\n  std::string resolveZstdTag(const char* sourceTag, const char* fanOutTag) const;\n  const std::vector<std::string> gZstdAllowedTags {\n    \"Grpc\", \"Balancer\", \"Converter\", \"DrainJob\", \"ZMQ\", \"MetadataFlusher\", \"Http\",\n    \"Master\", \"Recycle\", \"LRU\", \"WFE\", \"Wnc\", \"WFE::Job\", \"GroupBalancer\", \"GroupDrainer\",\n    \"GeoBalancer\", \"GeoTreeEngine\", \"ReplicationTracker\", \"FileInspector\", \"Mounts\", \"OAuth\", \"TokenCmd\"\n  };\n  const std::vector<std::pair<const char*, const char*>> gZstdAliasPairs {\n    {\"HttpHandler\", \"Http\"}, {\"HttpServer\", \"Http\"}, {\"GrpcServer\", \"Grpc\"}, {\"GrpcWncServer\", \"Wnc\"},\n    {\"ProtocolHandler\", \"Http\"}, {\"PropFindResponse\", \"Http\"}, {\"WebDAV\", \"Http\"},\n    {\"WebDAVHandler\", \"Http\"}, {\"WebDAVReponse\", \"Http\"}, {\"S3\", \"Http\"}, {\"S3Store\", \"Http\"},\n    {\"S3Handler\", \"Http\"},\n    {\"DrainTransferJob\", \"DrainJob\"}, {\"DrainFs\", \"DrainJob\"}, {\"Drainer\", \"DrainJob\"},\n    {\"Clients\", \"Mounts\"},\n    {\"ConversionInfo\", \"Converter\"}, {\"ConversionJob\", \"Converter\"}, {\"ConverterEngine\", \"Converter\"}\n  };\n};\n\nextern Logging& gLogging; ///< Global logging object\n\n//------------------------------------------------------------------------------\n//! Static Logging initializer\n//------------------------------------------------------------------------------\nstatic struct LoggingInitializer {\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  LoggingInitializer();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~LoggingInitializer();\n} sLoggingInit; ///< Static initializer for every translation unit\n\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/Macros.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Macros.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   Macros.hh\n *\n * @brief  Convenience macros for 64-bit network byte order conversion\n *\n *\n */\n\n#ifndef __EOSCOMMON_MACROS_HH__\n#define __EOSCOMMON_MACROS_HH__\n\n#include <arpa/inet.h>\n\ninline unsigned long long h_tonll(unsigned long long n)\n{\n#if __BYTE_ORDER == __BIG_ENDIAN\n  return n;\n#else\n  return (((unsigned long long)htonl(n)) << 32) + htonl(n >> 32);\n#endif\n}\n\ninline unsigned long long n_tohll(unsigned long long n)\n{\n#if __BYTE_ORDER == __BIG_ENDIAN\n  return n;\n#else\n  return (((unsigned long long)ntohl(n)) << 32) + ntohl(n >> 32);\n#endif\n}\n\n#endif // #ifndef __EOSCOMMON_MACROS_HH__\n"
  },
  {
    "path": "common/Mapping.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Mapping.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Mapping.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Macros.hh\"\n#include \"common/Namespace.hh\"\n#include \"common/SecEntity.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/token/EosTok.hh\"\n#include \"jwt-cpp/jwt.h\"\n#include <XrdAcc/XrdAccAuthorize.hh>\n#include <XrdNet/XrdNetAddr.hh>\n#include <XrdNet/XrdNetUtils.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdSec/XrdSecEntityAttr.hh>\n#include <grp.h>\n#include <pwd.h>\n#include <sys/stat.h>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\n// global mapping objects\n/*----------------------------------------------------------------------------*/\nRWMutex Mapping::gMapMutex;\n\nMapping::UserRoleMap_t Mapping::gUserRoleVector;\nMapping::GroupRoleMap_t Mapping::gGroupRoleVector;\nMapping::VirtualUserMap_t Mapping::gVirtualUidMap;\nMapping::VirtualGroupMap_t Mapping::gVirtualGidMap;\nMapping::SudoerMap_t Mapping::gSudoerMap;\nstd::atomic<bool> Mapping::gRootSquash = true;\nstd::atomic<bool> Mapping::gSecondaryGroups = false;\nstd::atomic<int> Mapping::gTokenSudo = Mapping::kAlways;\n\nMapping::GeoLocationMap_t Mapping::gGeoMap;\nint Mapping::gNobodyAccessTreeDeepness(1024);\n\nMapping::AllowedTidentMatches_t Mapping::gAllowedTidentMatches;\n\nShardedCache<std::string, Mapping::id_pair> Mapping::gShardedPhysicalUidCache(\n  8);\nShardedCache<std::string, Mapping::gid_set> Mapping::gShardedPhysicalGidCache(\n  8);\nShardedCache<uid_t, std::string> Mapping::gShardedNegativeUserNameCache(8);\nShardedCache<gid_t, std::string> Mapping::gShardedNegativeGroupNameCache(8);\nShardedCache<std::string, bool> Mapping::gShardedNegativePhysicalUidCache(8);\nShardedCache<std::string, time_t> Mapping::ActiveTidentsSharded(16);\nShardedCache<uid_t, size_t> Mapping::ActiveUidsSharded(16);\n\nstd::mutex Mapping::gPhysicalUserNameCacheMutex;\nstd::mutex Mapping::gPhysicalGroupNameCacheMutex;\nstd::map<uid_t, std::string> Mapping::gPhysicalUserNameCache;\nstd::map<gid_t, std::string> Mapping::gPhysicalGroupNameCache;\nstd::map<std::string, uid_t> Mapping::gPhysicalUserIdCache;\nstd::map<std::string, gid_t> Mapping::gPhysicalGroupIdCache;\n\nMapping::ip_cache Mapping::gIpCache(300);\n\nstd::unique_ptr<UnixGroupsFetcher> Mapping::gGroupsFetcher(\n  new UnixGroupListFetcher());\n\nOAuth Mapping::gOAuth;\n\nstatic std::string g_pwd_key = \"\\\"<pwd>\\\"\";\nstatic std::string g_pwd_uid_key = g_pwd_key + \":uid\";\nstatic std::string g_pwd_gid_key = g_pwd_key + \":gid\";\nstatic std::string g_https_uid_key = \"https:\" + g_pwd_uid_key;\nstatic std::string g_https_gid_key = \"https:\" + g_pwd_gid_key;\nstatic std::string g_sss_uid_key = \"sss:\" + g_pwd_uid_key;\nstatic std::string g_sss_gid_key = \"sss:\" + g_pwd_gid_key;\nstatic std::string g_unix_uid_key = \"unix:\" + g_pwd_uid_key;\nstatic std::string g_unix_gid_key = \"unix:\" + g_pwd_gid_key;\nstatic std::string g_gsi_uid_key = \"gsi:\" + g_pwd_uid_key;\nstatic std::string g_gsi_gid_key = \"gsi:\" + g_pwd_gid_key;\nstatic std::string g_krb_uid_key = \"krb5:\" + g_pwd_uid_key;\nstatic std::string g_krb_gid_key = \"krb5:\" + g_pwd_gid_key;\nstatic std::string g_oauth2_uid_key = \"oauth2:\" + g_pwd_uid_key;\nstatic std::string g_oauth2_gid_key = \"oauth2:\" + g_pwd_gid_key; // not used yet\nstatic std::string g_ztn_uid_key = \"ztn:\" + g_pwd_uid_key;\nstatic std::string g_ztn_gid_key = \"ztn:\" + g_pwd_gid_key;\n\n// flag to indicate whether the mapping is initialized\nstd::once_flag g_cache_map_init;\n\n//------------------------------------------------------------------------------\n// Initialize static maps\n//------------------------------------------------------------------------------\nvoid\nMapping::Init()\n{\n  // allow FUSE client access as root via env variable\n  if (getenv(\"EOS_FUSE_NO_ROOT_SQUASH\") &&\n      !strcmp(\"1\", getenv(\"EOS_FUSE_NO_ROOT_SQUASH\"))) {\n    gRootSquash = false;\n  }\n\n  if (getenv(\"EOS_SECONDARY_GROUPS\") &&\n      !strcmp(\"1\", getenv(\"EOS_SECONDARY_GROUPS\"))) {\n    gSecondaryGroups = true;\n\n    if (getenv(\"EOS_SECONDARY_GROUPS_GRENT\") &&\n        !strcmp(\"1\", getenv(\"EOS_SECONDARY_GROUPS_GRENT\"))) {\n      gGroupsFetcher.reset(new UnixGrentFetcher());\n    }\n  }\n\n  gOAuth.Init();\n\n  try {\n    std::call_once(g_cache_map_init, []() {\n      // Force expiry of UID/GID cache every 2 cycles\n      gShardedPhysicalUidCache.set_force_expiry(true, 2);\n      gShardedPhysicalUidCache.reset_cleanup_thread(3600 * 1000,\n          \"UidCacheGC\");\n      gShardedPhysicalGidCache.set_force_expiry(true, 2);\n      gShardedPhysicalGidCache.reset_cleanup_thread(3600 * 1000,\n          \"GidCacheGC\");\n      gShardedNegativeUserNameCache.set_force_expiry(true, 8);\n      gShardedNegativeUserNameCache.reset_cleanup_thread(3600 * 1000,\n          \"NegUserNameGC\");\n      gShardedNegativeGroupNameCache.set_force_expiry(true, 8);\n      gShardedNegativeGroupNameCache.reset_cleanup_thread(3600 * 1000,\n          \"NegGroupNameGC\");\n      gShardedNegativePhysicalUidCache.set_force_expiry(true, 2);\n      gShardedNegativePhysicalUidCache.reset_cleanup_thread(3600 * 1000,\n          \"NegUidGC\");\n      ActiveUidsSharded.reset_cleanup_thread(300 * 1000,\n                                             \"ActiveUidsSharded\");\n      ActiveTidentsSharded.reset_cleanup_thread(300 * 1000,\n          \"ActiveTidentsGC\");\n    });\n  } catch (...) {\n    // we can't log here as the logging system is not initialized yet\n  }\n}\n\n//------------------------------------------------------------------------------\n// Reset\n//------------------------------------------------------------------------------\n\nvoid\nMapping::Reset()\n{\n  {\n    std::scoped_lock lock{gPhysicalUserNameCacheMutex, gPhysicalGroupNameCacheMutex};\n    gPhysicalUserNameCache.clear();\n    gPhysicalGroupNameCache.clear();\n    gPhysicalGroupIdCache.clear();\n    gPhysicalUserIdCache.clear();\n    gShardedPhysicalUidCache.clear();\n    gShardedPhysicalGidCache.clear();\n    gShardedNegativeUserNameCache.clear();\n    gShardedNegativeGroupNameCache.clear();\n    gShardedNegativePhysicalUidCache.clear();\n  }\n  ActiveTidentsSharded.clear();\n  ActiveUidsSharded.clear();\n}\n\n//------------------------------------------------------------------------------\n// Map a client to its virtual identity\n//------------------------------------------------------------------------------\nvoid\nMapping::IdMap(const XrdSecEntity* client, const char* env, const char* tident,\n               VirtualIdentity& vid, XrdAccAuthorize* authz_obj,\n               Access_Operation acc_op, std::string path, bool log)\n{\n  if (!client) {\n    return;\n  }\n\n  eos_static_debug(\"msg=\\\"XrdSecEntity client\\\" name=\\\"%s\\\" role=\\\"%s\\\" \"\n                   \"group=\\\"%s\\\" tident=\\\"%s\\\" cred=\\\"%s\\\"\",\n                   (client->name ? client->name : \"null\"),\n                   (client->role ? client->role : \"null\"),\n                   (client->grps ? client->grps : \"null\"),\n                   (client->tident ? client->tident : \"null\"),\n                   (client->creds ? client->creds : \"null\"));\n  // We start as 'nobody'\n  vid = VirtualIdentity::Nobody();\n  XrdOucEnv Env(env);\n  std::string authz = (Env.Get(\"authz\") ? Env.Get(\"authz\") : \"\");\n  vid.name = (client->name ? client->name : \"\");\n  vid.tident = tident;\n  vid.sudoer = false;\n  vid.gateway = false;\n  // first map by alias\n  XrdOucString useralias = client->prot;\n  useralias += \":\";\n  useralias += \"\\\"\";\n  useralias += (client->name ? client->name : \"\");\n  useralias += \"\\\"\";\n  useralias += \":\";\n  XrdOucString groupalias = useralias;\n  useralias += \"uid\";\n  groupalias += \"gid\";\n  RWMutexReadLock lock(gMapMutex);\n  vid.prot = client->prot;\n\n  // @todo (esindril) this is just a workaround for the fact that XrdHttp\n  // does not properly populate the prot field in the XrdSecEntity object.\n  // See https://github.com/xrootd/xrootd/issues/1122\n  if ((strlen(client->tident) == 4) &&\n      (strcmp(client->tident, \"http\") == 0)) {\n    vid.prot = \"https\";\n  }\n\n  // HTTPS, SSS and GRPC might contain a key embedded in the endorsements field\n  if ((vid.prot == \"sss\") || (vid.prot == \"grpc\") || (vid.prot == \"https\")) {\n    vid.key = (client->endorsements ? client->endorsements : \"\");\n    eos_static_debug(\"msg=\\\"client endorsement\\\" key=\\\"%s\\\"\", vid.key.c_str());\n  }\n\n  // KRB5 mapping\n  if ((vid.prot == \"krb5\")) {\n    eos_static_debug(\"%s\", \"msg=\\\"krb5 mapping\\\"\");\n\n    // Use physical mapping for kerberos names\n    if (gVirtualUidMap.count(g_krb_uid_key)) {\n      Mapping::getPhysicalUids(client->name, vid);\n    }\n\n    if (gVirtualGidMap.count(g_krb_gid_key)) {\n      Mapping::getPhysicalGids(client->name, vid);\n    }\n  }\n\n  // GSI mapping\n  if ((vid.prot == \"gsi\")) {\n    eos_static_debug(\"%s\", \"msg=\\\"gsi mapping\\\"\");\n\n    // Use physical mapping for gsi names\n    if (gVirtualUidMap.count(g_gsi_uid_key)) {\n      Mapping::getPhysicalUids(client->name, vid);\n    }\n\n    if (gVirtualGidMap.count(g_gsi_gid_key)) {\n      Mapping::getPhysicalGids(client->name, vid);\n    }\n\n    HandleVOMS(client, vid);\n  }\n\n  // HTTPS mapping\n  if (vid.prot == \"https\") {\n    eos_static_debug(\"%s\", \"msg=\\\"https mapping\\\"\");\n\n    // Handle bearer token authorization\n    if (authz_obj && !authz.empty() && (authz.find(\"Bearer%20\") == 0)) {\n      if (authz_obj->Access(client, path.c_str(), acc_op, &Env) ==\n          XrdAccPriv_None) {\n        vid = VirtualIdentity::Nobody();\n        std::string nobearer = authz.substr(9);\n        eos_static_err(\"msg=\\\"failed token authz\\\" path=\\\"%s\\\" opaque=\\\"%s\\\" \"\n                       \"jwt={%s}[%s]\", path.c_str(), env,\n                       PrintJWT(nobearer).c_str(), nobearer.c_str());\n        return;\n      }\n    }\n\n    // Check if we have the request.name in the attributes of the XrdSecEntity\n    // object which is the client username according to the authz mapping.\n    std::string client_username;\n    std::string user_value;\n    static const std::string user_key = \"request.name\";\n\n    if (client->eaAPI->Get(user_key, user_value)) {\n      client_username = user_value;\n    } else {\n      if (client->name) {\n        client_username = client->name;\n      }\n    }\n\n    HandleUidGidMapping(client_username.c_str(), vid,\n                        g_https_uid_key, g_https_gid_key);\n    HandleVOMS(client, vid);\n    HandleKEYS(client, vid);\n  }\n\n  // ZTN mapping\n  if ((vid.prot == \"ztn\") && client->creds) {\n    // Handle bearer token authorization\n    eos_static_debug(\"msg=\\\"dumping client credentials/token\\\" creds=\\\"%s\\\"\",\n                     client->creds);\n\n    if (authz_obj) {\n      authz = \"&authz=\";\n      authz += client->creds;\n      XrdOucEnv op_env(authz.c_str());\n\n      if (authz_obj->Access(client, path.c_str(), acc_op, &op_env) ==\n          XrdAccPriv_None) {\n        vid = VirtualIdentity::Nobody();\n        eos_static_err(\"msg=\\\"failed token authz\\\" path=\\\"%s\\\" opaque=\\\"%s\\\" \"\n                       \"authz=\\\"%s\\\" jwt={%s}\", path.c_str(), env,\n                       authz.c_str(),\n                       PrintJWT(std::string(client->creds)).c_str());;\n        return;\n      }\n\n      // Check if we have the request.name in the attributes of the XrdSecEntity\n      // object which is the client username according to the authz mapping.\n      std::string client_username;\n      std::string user_value;\n      static const std::string user_key = \"request.name\";\n\n      if (client->eaAPI->Get(user_key, user_value)) {\n        // we got a user name from the token\n        client_username = user_value;\n      } else {\n        if (client->name) {\n          client_username = client->name;\n        }\n      }\n\n      HandleUidGidMapping(client_username.c_str(), vid,\n                          g_ztn_uid_key, g_ztn_gid_key);\n    } else {\n      // add the ZTN credential if there is not another one provided\n      if (authz.empty()) {\n        authz = client->creds;\n      }\n    }\n  }\n\n  // sss mapping\n  if ((vid.prot == \"sss\")) {\n    HandleUidGidMapping(client->name, vid, g_sss_uid_key, g_sss_gid_key);\n  }\n\n  // unix mapping\n  if ((vid.prot == \"unix\")) {\n    if (authz_obj && authz.length()) {\n      if (authz_obj->Access(client, path.c_str(), acc_op, &Env) == XrdAccPriv_None) {\n        // In principle we will never get here if XrdMgmAuthz is chained since\n        // it says ok if there is a user name defined\n        vid = VirtualIdentity::Nobody();\n        eos_static_err(\"msg=\\\"failed token authz\\\" path=\\\"%s\\\" opaque=\\\"%s\\\" \"\n                       \"authz=\\\"%s\\\" jwt={%s}\",  path.c_str(), env,\n                       authz.c_str(),\n                       PrintJWT(Env.Get(\"authz\") ?\n                                std::string(Env.Get(\"authz\")) :\n                                std::string(\"\")).c_str());\n        return;\n      }\n\n      // Check if we have the request.name in the attributes of the XrdSecEntity\n      // object which is the client username according to the authz mapping.\n      std::string client_username = \"nobody\";\n      std::string user_value;\n      static const std::string user_key = \"request.name\";\n      bool force_mapping = false;\n\n      if (client->eaAPI->Get(user_key, user_value)) {\n        force_mapping = true;\n        client_username = user_value;\n      } else {\n        // No user from the token, we are 'anonymous'\n        client_username = \"nobody\";\n      }\n\n      HandleUidGidMapping(client_username.c_str(), vid,\n                          g_unix_uid_key, g_unix_gid_key, force_mapping);\n    } else {\n      HandleUidGidMapping(client->name, vid, g_unix_uid_key, g_unix_gid_key);\n    }\n  }\n\n  // tident mapping\n  XrdOucString mytident = \"\";\n  XrdOucString myrole = \"\";\n  XrdOucString wildcardtident = \"\";\n  XrdOucString host = \"\";\n  XrdOucString stident = \"tident:\";\n  stident += \"\\\"\";\n  stident += ReduceTident(vid.tident, wildcardtident, mytident, host);\n\n  if (host == \"127.0.0.1\") {\n    host = \"localhost\";\n  }\n\n  myrole = mytident;\n  myrole.erase(mytident.find(\"@\"));\n  // FUSE selects now the role via <uid>[:connectionid]\n  // the connection id is already removed by ReduceTident\n  myrole.erase(myrole.find(\".\"));\n  XrdOucString swctident = \"tident:\";\n  swctident += \"\\\"\";\n  swctident += wildcardtident;\n  XrdOucString suidtident = stident;\n  suidtident += \"\\\":uid\";\n  XrdOucString sgidtident = stident;\n  sgidtident += \"\\\":gid\";\n  XrdOucString swcuidtident = swctident;\n  swcuidtident += \"\\\":uid\";\n  XrdOucString swcgidtident = swctident;\n  swcgidtident += \"\\\":gid\";\n  XrdOucString sprotuidtident = swcuidtident;\n  XrdOucString sprotgidtident = swcgidtident;\n  // there can be a protocol specific rule like sss:@<host>:uid...\n  sprotuidtident.replace(\"*\", vid.prot);\n  // there can be a protocol specific rule like sss:@<host>:gid...\n  sprotgidtident.replace(\"*\", vid.prot);\n  eos_static_debug(\"swcuidtident=%s sprotuidtident=%s myrole=%s\",\n                   swcuidtident.c_str(), sprotuidtident.c_str(), myrole.c_str());\n\n  if (auto kv = gVirtualUidMap.find(suidtident.c_str());\n      kv != gVirtualUidMap.end()) {\n    vid.uid = kv->second;\n    vid.allowed_uids.insert(vid.uid);\n    vid.allowed_uids.insert(VirtualIdentity::kNobodyUid);\n  }\n\n  if (auto kv = gVirtualGidMap.find(sgidtident.c_str());\n      kv != gVirtualGidMap.end()) {\n    vid.gid = kv->second;\n    vid.allowed_gids.insert(vid.gid);\n    vid.allowed_gids.insert(VirtualIdentity::kNobodyGid);\n  }\n\n  // Wildcard tidents/protocol tidents - one can define mapping entries like\n  // '*@host:uid=>0' e.g. for fuse mounts or only for a certain protocol\n  // like 'sss@host:uid=>0'\n  XrdOucString tuid = \"\";\n  XrdOucString tgid = \"\";\n\n  if (gVirtualUidMap.count(swcuidtident.c_str())) {\n    // there is an entry like \"*@<host:uid\" matching all protocols\n    tuid = swcuidtident.c_str();\n  } else {\n    if (gVirtualUidMap.count(sprotuidtident.c_str())) {\n      // there is a protocol specific entry \"<prot>@<host>:uid\"\n      tuid = sprotuidtident.c_str();\n    } else {\n      if (gAllowedTidentMatches.size()) {\n        std::string sprot = vid.prot.c_str();\n\n        for (auto it = gAllowedTidentMatches.begin(); it != gAllowedTidentMatches.end();\n             ++it) {\n          if (sprot != it->first.c_str()) {\n            continue;\n          }\n\n          if (host.matches(it->second.c_str())) {\n            sprotuidtident.replace(host.c_str(), it->second.c_str());\n\n            if (gVirtualUidMap.count(sprotuidtident.c_str())) {\n              tuid = sprotuidtident.c_str();\n              break;\n            }\n          }\n        }\n      }\n    }\n  }\n\n  if (gVirtualGidMap.count(swcgidtident.c_str())) {\n    // there is an entry like \"*@<host>:gid\" matching all protocols\n    tgid = swcgidtident.c_str();\n  } else {\n    if (gVirtualGidMap.count(sprotgidtident.c_str())) {\n      // there is a protocol specific entry \"<prot>@<host>:uid\"\n      tgid = sprotgidtident.c_str();\n    } else {\n      if (gAllowedTidentMatches.size()) {\n        std::string sprot = vid.prot.c_str();\n\n        for (auto it = gAllowedTidentMatches.begin();\n             it != gAllowedTidentMatches.end(); ++it) {\n          if (sprot != it->first.c_str()) {\n            continue;\n          }\n\n          if (host.matches(it->second.c_str())) {\n            sprotgidtident.replace(host.c_str(), it->second.c_str());\n\n            if (gVirtualGidMap.count(sprotgidtident.c_str())) {\n              tgid = sprotgidtident.c_str();\n              break;\n            }\n          }\n        }\n      }\n    }\n  }\n\n  eos_static_debug(\"tuid=%s tgid=%s\", tuid.c_str(), tgid.c_str());\n\n  if (gVirtualUidMap.count(tuid.c_str())) {\n    if (!gVirtualUidMap[tuid.c_str()]) {\n      if (gRootSquash && (host != \"localhost\") && (host != \"localhost.localdomain\") &&\n          (host != \"localhost6.localdomain6\") && (vid.name == \"root\") &&\n          (myrole == \"root\")) {\n        eos_static_debug(\"%s\", \"msg=\\\"tident root uid squash\\\"\");\n        vid.allowed_uids.clear();\n        vid.allowed_uids.insert(DAEMONUID);\n        vid.uid = DAEMONUID;\n        vid.allowed_gids.clear();\n        vid.gid = DAEMONGID;\n        vid.allowed_gids.insert(DAEMONGID);\n      } else {\n        eos_static_debug(\"msg=\\\"tident uid mapping\\\" prot=%s name=%s\",\n                         vid.prot.c_str(), vid.name.c_str());\n        vid.allowed_uids.clear();\n\n        // use physical mapping\n        // unix protocol maps to the role if the client is the root account\n        // otherwise it maps to the unix ID on the client host\n        if (((vid.prot == \"unix\") && (vid.name == \"root\")) ||\n            ((vid.prot == \"sss\") && (vid.name == \"daemon\"))) {\n          Mapping::getPhysicalIdShards(myrole.c_str(), vid);\n        } else {\n          if (client->name != nullptr) {\n            Mapping::getPhysicalIdShards(client->name, vid);\n          }\n        }\n\n        vid.gateway = true;\n      }\n    } else {\n      eos_static_debug(\"%s\", \"msg=\\\"tident uid forced mapping\\\"\");\n      // map to the requested id\n      vid.allowed_uids.clear();\n      vid.uid = gVirtualUidMap[tuid.c_str()];\n      vid.allowed_uids.insert(vid.uid);\n      vid.allowed_uids.insert(VirtualIdentity::kNobodyUid);\n      vid.allowed_gids.clear();\n      vid.gid = VirtualIdentity::kNobodyGid;\n      vid.allowed_gids.insert(vid.gid);\n    }\n  }\n\n  if (gVirtualGidMap.count(tgid.c_str())) {\n    if (!gVirtualGidMap[tgid.c_str()]) {\n      if (gRootSquash && (host != \"localhost\") && (host != \"localhost.localdomain\") &&\n          (vid.name == \"root\") && (myrole == \"root\")) {\n        eos_static_debug(\"%s\", \"msg=\\\"tident root gid squash\\\"\");\n        vid.allowed_gids.clear();\n        vid.allowed_gids.insert(DAEMONGID);\n        vid.gid = DAEMONGID;\n      } else {\n        eos_static_debug(\"%s\", \"msg=\\\"tident gid mapping\\\"\");\n        uid_t uid = vid.uid;\n\n        if (((vid.prot == \"unix\") && (vid.name == \"root\")) ||\n            ((vid.prot == \"sss\") && (vid.name == \"daemon\"))) {\n          Mapping::getPhysicalIdShards(myrole.c_str(), vid);\n        } else {\n          if (client->name != nullptr) {\n            Mapping::getPhysicalIdShards(client->name, vid);\n          }\n        }\n\n        vid.uid = uid;\n        vid.allowed_uids.clear();\n        vid.allowed_uids.insert(uid);\n        vid.allowed_uids.insert(VirtualIdentity::kNobodyUid);\n        vid.gateway = true;\n      }\n    } else {\n      eos_static_debug(\"%s\", \"msg=\\\"tident gid forced mapping\\\"\");\n      // map to the requested id\n      vid.allowed_gids.clear();\n      vid.gid = gVirtualGidMap[tgid.c_str()];\n      vid.allowed_gids.insert(vid.gid);\n    }\n  }\n\n  eos_static_debug(\"suidtident:%s sgidtident:%s\", suidtident.c_str(),\n                   sgidtident.c_str());\n\n  // Configuration door for localhost clients adds always the adm/adm vid's\n  if ((suidtident == \"tident:\\\"root@localhost.localdomain\\\":uid\") ||\n      (suidtident == \"tident:\\\"root@localhost\\\":uid\")) {\n    vid.sudoer = true;\n    vid.uid = 3;\n    vid.gid = 4;\n    vid.allowed_uids.insert(vid.uid);\n    vid.allowed_gids.insert(vid.gid);\n  }\n\n  // GRPC key mapping\n  if ((vid.prot == \"grpc\") && vid.key.length()) {\n    std::string keyname = vid.key.c_str();\n\n    if (keyname.substr(0, 8) == \"zteos64:\") {\n      // this is an eos token\n      authz = vid.key;\n      vid = VirtualIdentity::Nobody();\n    }  else {\n      std::string maptident = \"tident:\\\"grpc@\";\n      std::string wildcardmaptident = \"tident:\\\"grpc@*\\\":uid\";\n      std::vector<std::string> vtident;\n      eos::common::StringConversion::Tokenize(client->tident, vtident, \"@\");\n\n      if (vtident.size() == 2) {\n        maptident += vtident[1];\n      }\n\n      maptident += \"\\\":uid\";\n      eos_static_info(\"%d %s %s %s\", vtident.size(), client->tident,\n                      maptident.c_str(), wildcardmaptident.c_str());\n\n      if (gVirtualUidMap.count(maptident.c_str()) ||\n          gVirtualUidMap.count(wildcardmaptident.c_str())) {\n        // if this is an allowed gateway, map according to client name or authkey\n        std::string uidkey = \"grpc:\\\"\";\n        uidkey += \"key:\";\n        uidkey += keyname;\n        uidkey += \"\\\":uid\";\n        vid.uid = VirtualIdentity::kNobodyUid;\n        vid.allowed_uids.clear();\n        vid.allowed_uids.insert(VirtualIdentity::kNobodyUid);\n        vid.gateway = true;\n\n        if (gVirtualUidMap.count(uidkey.c_str())) {\n          vid.uid = gVirtualUidMap[uidkey.c_str()];\n          vid.allowed_uids.insert(vid.uid);\n        }\n\n        std::string gidkey = \"grpc:\\\"\";\n        gidkey += \"key:\";\n        gidkey += keyname;\n        gidkey += \"\\\":gid\";\n        vid.gid = VirtualIdentity::kNobodyGid;\n        vid.allowed_gids.clear();\n        vid.allowed_gids.insert(VirtualIdentity::kNobodyGid);\n\n        if (gVirtualGidMap.count(gidkey.c_str())) {\n          vid.gid = gVirtualGidMap[gidkey.c_str()];\n          vid.allowed_gids.insert(vid.gid);\n        }\n      } else {\n        // we are nobody if we are not an authorized host\n        vid = VirtualIdentity::Nobody();\n      }\n    }\n  }\n\n  // Environment selected roles\n  XrdOucString ruid = Env.Get(\"eos.ruid\");\n  XrdOucString rgid = Env.Get(\"eos.rgid\");\n  XrdOucString rapp = Env.Get(\"eos.app\");\n\n  // SSS key mapping\n  if ((vid.prot == \"sss\") && vid.key.length()) {\n    std::string keyname = vid.key;\n    std::string maptident = \"tident:\\\"sss@\";\n    std::string wildcardmaptident = \"tident:\\\"sss@*\\\":uid\";\n    std::vector<std::string> vtident;\n    eos::common::StringConversion::Tokenize(client->tident, vtident, \"@\");\n\n    // token provided as key\n    if (keyname.substr(0, 8) == \"zteos64:\") {\n      // this is an eos token\n      authz = vid.key;\n    } else {\n      // try oauth2\n      std::string oauthname;\n      bool oauth2_enabled = (gVirtualUidMap.find(g_oauth2_uid_key) !=\n                             gVirtualUidMap.end());\n\n      if (oauth2_enabled) {\n        // Release the map mutex to avoid any inteference with a queued up\n        // write lock and an oauth callout being slow\n        lock.Release();\n        oauthname = gOAuth.Handle(keyname, vid);\n        lock.Grab(gMapMutex);\n      }\n\n      // Check for OAuth contents\n      if (oauthname.empty() || !oauth2_enabled) {\n        // Treat as mapping key\n        if (vtident.size() == 2) {\n          maptident += vtident[1];\n        }\n\n        maptident += \"\\\":uid\";\n        eos_static_info(\"%d %s %s %s\", vtident.size(), client->tident,\n                        maptident.c_str(), wildcardmaptident.c_str());\n\n        if (gVirtualUidMap.count(maptident) ||\n            gVirtualUidMap.count(wildcardmaptident)) {\n          vid.gateway = true;\n          // if this is an allowed gateway, map according to client name or authkey\n          std::string uidkey = \"sss:\\\"\";\n          uidkey += \"key:\";\n          uidkey += keyname;\n          uidkey += \"\\\":uid\";\n          vid.uid = VirtualIdentity::kNobodyUid;\n          vid.allowed_uids.clear();\n          vid.allowed_uids.insert(VirtualIdentity::kNobodyUid);\n\n          if (auto kv = gVirtualUidMap.find(uidkey);\n              kv != gVirtualUidMap.end()) {\n            vid.uid = kv->second;\n            vid.allowed_uids.insert(vid.uid);\n          }\n\n          std::string gidkey = \"sss:\\\"\";\n          gidkey += \"key:\";\n          gidkey += keyname;\n          gidkey += \"\\\":gid\";\n          vid.gid = VirtualIdentity::kNobodyGid;\n          vid.allowed_gids.clear();\n          vid.allowed_gids.insert(VirtualIdentity::kNobodyGid);\n\n          if (auto kv = gVirtualGidMap.find(gidkey);\n              kv != gVirtualGidMap.end()) {\n            vid.gid = kv->second;\n            vid.allowed_gids.insert(vid.gid);\n          }\n        } else {\n          // we are nobody if we are not an authorized host\n          vid = VirtualIdentity::Nobody();\n          vid.prot = \"sss\";\n        }\n      } else {\n        int errc = 0;\n        std::string uidkey = \"oauth2:\\\"\";\n        uidkey += \"sub:\";\n        uidkey += oauthname;\n        uidkey += \"\\\":uid\";\n\n        if (auto kv = gVirtualUidMap.find(uidkey);\n            kv != gVirtualUidMap.end()) {\n          // map oauthname from static sub mapping\n          oauthname = UidToUserName(kv->second, errc);\n        }\n\n        if (errc) {\n          // we have no mapping for this uid\n          Mapping::getPhysicalIdShards(\"nobody\", vid);\n        } else {\n          // map oauthname\n          Mapping::getPhysicalIdShards(oauthname.c_str(), vid);\n        }\n\n        vid.prot = \"oauth2\";\n      }\n    }\n  }\n\n  // Explicit virtual mapping overrules physical mappings - the second one\n  // comes from the physical mapping before\n  {\n    auto userkey = gVirtualUidMap.find(useralias.c_str());\n    vid.uid = userkey != gVirtualUidMap.end() ? userkey->second : vid.uid;\n    vid.allowed_uids.insert(vid.uid);\n  }\n  {\n    auto groupkey = gVirtualGidMap.find(groupalias.c_str());\n    vid.gid = groupkey != gVirtualGidMap.end() ? groupkey->second : vid.gid;\n    vid.allowed_gids.insert(vid.gid);\n  }\n\n  // Add virtual user and group roles - if any\n  if (gUserRoleVector.count(vid.uid)) {\n    for (auto it = gUserRoleVector[vid.uid].cbegin();\n         it != gUserRoleVector[vid.uid].cend(); ++it) {\n      vid.allowed_uids.insert(*it);\n    }\n  }\n\n  if (gGroupRoleVector.count(vid.uid)) {\n    for (auto it = gGroupRoleVector[vid.uid].cbegin();\n         it != gGroupRoleVector[vid.uid].cend(); ++it) {\n      vid.allowed_gids.insert(*it);\n    }\n  }\n\n  bool token_sudo = false;\n\n  // Handle token based mapping\n  if (!authz.empty()) {\n    static const std::string http_enc_tag = \"Bearer%20\";\n    static const std::string http_tag = \"Bearer \";\n\n    // Remove extra characters and decode when passed as a bearer\n    // authorization HTTPS header\n    if (authz.find(http_enc_tag) == 0) {\n      authz.erase(0, http_enc_tag.size());\n      authz = StringConversion::curl_default_unescaped(authz);\n    } else {\n      if (authz.find(http_tag) == 0) {\n        authz.erase(0, http_tag.size());\n      }\n    }\n\n    if (authz.substr(0, 8) == \"zteos64:\") {\n      // This is an eos token\n      eos::common::SymKey* symkey = eos::common::gSymKeyStore.GetCurrentKey();\n      std::string key = symkey ? symkey->GetKey64() : \"0123457890defaultkey\";\n      bool skip_key = false;\n\n      if (getenv(\"EOS_MGM_TOKEN_KEYFILE\")) {\n        struct stat buf;\n\n        if (::stat(getenv(\"EOS_MGM_TOKEN_KEYFILE\"), &buf)) {\n          eos_static_err(\"msg=\\\"token keyfile does not exist\\\" location=\\\"%s\\\"\",\n                         getenv(\"EOS_MGM_TOKEN_KEYFILE\"));\n          skip_key = true;\n        } else {\n          if ((buf.st_uid != DAEMONUID) ||\n              (buf.st_mode != 0100400)) {\n            skip_key = true;\n            eos_static_err(\"msg=\\\"token keyfile mode bit\\\" mode=%o\", buf.st_mode);\n          }\n        }\n\n        if (!skip_key) {\n          key = eos::common::StringConversion::LoadFileIntoString(\n                  getenv(\"EOS_MGM_TOKEN_KEYFILE\"), key);\n        }\n      }\n\n      int rc = 0;\n      vid.token = std::make_shared<EosTok>();\n\n      if ((rc = vid.token->Read(authz, key, eos::common::EosTok::sTokenGeneration,\n                                false))) {\n        vid.token->Reset();\n        eos_static_err(\"msg=\\\"failed to decode token\\\" tident=\\\"%s\\\" token=\\\"%s\\\" errno=%d\",\n                       tident, authz.c_str(), -rc);\n      } else {\n        bool validated = true;\n\n        if (path.length() && path.substr(0, 6) != \"/proc/\") {\n          if (vid.token->ValidatePath(path)) {\n            eos_static_err(\"msg=\\\"token path validation failed\\\" path=\\\"%s\\\"\",\n                           path.c_str());\n            validated = false;\n          }\n        }\n\n        // if the path is screened we change owner/group\n        if (validated && !vid.token->Owner().empty()) {\n          token_sudo = true;\n          ruid = vid.token->Owner().c_str();\n        }\n\n        if (validated && !vid.token->Group().empty()) {\n          token_sudo = true;\n          rgid = vid.token->Group().c_str();\n        }\n\n        if (EOS_LOGS_INFO) {\n          std::string dump;\n          vid.token->Dump(dump, true, true);\n          eos_static_info(\"%s {tokensudo:%d (%d)}\", dump.c_str(), token_sudo,\n                          gTokenSudo.load());\n        }\n      }\n    } else {\n      eos_static_debug(\"jwt={%s}\", PrintJWT(Env.Get(\"authz\") ?\n                                            std::string(Env.Get(\"authz\")) :\n                                            std::string(\"\")).c_str());\n    }\n  }\n\n  // apply policy if a token can change the identity (authenticate)\n  if (gTokenSudo != Mapping::kAlways) {\n    if (gTokenSudo == kNever) {\n      token_sudo = false;\n    } else {\n      if (gTokenSudo == Mapping::kEncrypted) {\n        if ((vid.prot != \"sss\") &&\n            (vid.prot != \"https\") &&\n            (vid.prot != \"ztn\") &&\n            (vid.prot != \"grpc\")) {\n          token_sudo = false;\n        }\n      } else {\n        if (gTokenSudo == Mapping::kStrong) {\n          if (vid.prot == \"unix\") {\n            token_sudo = false;\n          }\n        }\n      }\n    }\n  }\n\n  uid_t sel_uid = vid.uid;\n  uid_t sel_gid = vid.gid;\n\n  if (ruid.length()) {\n    if (!IsUid(ruid, sel_uid)) {\n      int errc = 0;\n      // try alias conversion\n      std::string luid = ruid.c_str();\n      sel_uid = (gVirtualUidMap.count(ruid.c_str())) ? gVirtualUidMap[ruid.c_str() ] :\n                VirtualIdentity::kNobodyUid;\n\n      if (sel_uid == VirtualIdentity::kNobodyUid) {\n        sel_uid = UserNameToUid(luid, errc);\n      }\n\n      if (errc) {\n        sel_uid = VirtualIdentity::kNobodyUid;\n      }\n    }\n  }\n\n  if (rgid.length()) {\n    if (!IsGid(rgid, sel_gid)) {\n      int errc = 0;\n      // try alias conversion\n      std::string lgid = rgid.c_str();\n      sel_gid = (gVirtualGidMap.count(rgid.c_str())) ? gVirtualGidMap[rgid.c_str()] :\n                VirtualIdentity::kNobodyGid;\n\n      if (sel_gid == VirtualIdentity::kNobodyGid) {\n        sel_gid = GroupNameToGid(lgid, errc);\n      }\n\n      if (errc) {\n        sel_gid = VirtualIdentity::kNobodyGid;\n      }\n    }\n  }\n\n  // Sudoer flag setting\n  if (gSudoerMap.count(vid.uid)) {\n    vid.sudoer = true;\n  }\n\n  // Check if we are allowed to take sel_uid & sel_gid\n  if (!vid.sudoer && !token_sudo) {\n    // if we are not a sudore, scan the allowed ids\n    if (vid.hasUid(sel_uid)) {\n      vid.uid = sel_uid;\n    } else {\n      vid.uid = VirtualIdentity::kNobodyUid;\n    }\n\n    if (vid.hasGid(sel_gid)) {\n      vid.gid = sel_gid;\n    } else {\n      vid.gid = VirtualIdentity::kNobodyGid;\n    }\n  } else {\n    vid.uid = sel_uid;\n    vid.gid = sel_gid;\n\n    if (ruid.length() || rgid.length()) {\n      vid.allowed_gids.insert(sel_gid);\n      vid.allowed_uids.insert(sel_uid);\n    }\n  }\n\n  if (client->host) {\n    vid.host = client->host;\n  } else {\n    vid.host = host.c_str();\n  }\n\n  size_t dotpos = vid.host.find(\".\");\n\n  // remove hostname\n  if (dotpos != std::string::npos) {\n    vid.domain = vid.host.substr(dotpos + 1);\n  } else {\n    vid.domain = \"localdomain\";\n  }\n\n  {\n    int errc = 0;\n\n    // add the uid/gid as strings\n    if (vid.uid_string.empty()) {\n      vid.uid_string = UidToUserName(vid.uid, errc);\n    }\n\n    if (vid.gid_string.empty()) {\n      vid.gid_string = GidToGroupName(vid.gid, errc);\n    }\n  }\n\n  // verify origin\n  if (vid.token) {\n    if (vid.token->Valid()) {\n      if (vid.token->VerifyOrigin(vid.host, vid.uid_string,\n                                  std::string(vid.prot.c_str()))) {\n        // invalidate this token\n        eos_static_err(\"msg=\\\"invalid token due to origin mismatch\\\" \"\n                       \"\\\"%s#%s#%s\\\"\", vid.host.c_str(),\n                       vid.uid_string.c_str(), vid.prot.c_str());\n        vid.token->Reset();\n        // reset the vid to nobody if the origin does not match\n        vid.toNobody();\n      }\n    } else {\n      eos_static_debug(\"msg=\\\"token invalid\\\" host=\\\"%s\\\" uid=\\\"%s\\\" prot=\\\"%s\\\"\",\n                       vid.host.c_str(), vid.uid_string.c_str(), vid.prot.c_str());\n    }\n  }\n\n  if (rapp.length()) {\n    vid.app = rapp.c_str();\n  }\n\n  // Check the Geo Location\n  if ((!vid.geolocation.length()) && (gGeoMap.size())) {\n    // if the geo location was not set externally and we have some recipe we try\n    // to translate the host name and match a rule\n\n    // if we have a default geo location we assume that a client in that one\n    if (auto kv = gGeoMap.find(\"default\");\n        kv != gGeoMap.end()) {\n      vid.geolocation = kv->second;\n    }\n\n    std::string ipstring = gIpCache.GetIp(host.c_str());\n\n    if (ipstring.length()) {\n      std::string sipstring = ipstring;\n      GeoLocationMap_t::const_iterator it;\n      GeoLocationMap_t::const_iterator longuestmatch = gGeoMap.end();\n\n      // we use the geo location with the longest name match\n      for (it = gGeoMap.begin(); it != gGeoMap.end(); ++it) {\n        // If we have a previously matched geoloc and if it's longer that the\n        // current one, try the next one\n        if (longuestmatch != gGeoMap.end() &&\n            it->first.length() <= longuestmatch->first.length()) {\n          continue;\n        }\n\n        if (sipstring.compare(0, it->first.length(), it->first) == 0) {\n          vid.geolocation = it->second;\n          longuestmatch = it;\n        }\n      }\n    }\n  }\n\n  char actident[1024];\n  snprintf(actident, sizeof(actident) - 1, \"%d^%s^%s^%s^%s\", vid.uid,\n           mytident.c_str(), vid.prot.c_str(), vid.host.c_str(), vid.app.c_str());\n  std::string intident = actident;\n\n  if (!ActiveTidentsSharded.contains(intident)) {\n    ActiveUidsSharded.fetch_add(vid.uid, 1);\n  }\n\n  ActiveTidentsSharded.store(intident, std::make_unique<time_t>(time(NULL)));\n  eos_static_debug(\"selected %d %d [%s %s]\", vid.uid, vid.gid, ruid.c_str(),\n                   rgid.c_str());\n\n  if (log) {\n    eos_static_info(\"%s sec.tident=\\\"%s\\\" vid.uid=%d vid.gid=%d sudo=%d gateway=%d\",\n                    eos::common::SecEntity::ToString(client, Env.Get(\"eos.app\")).c_str(),\n                    tident, vid.uid, vid.gid, vid.sudoer, vid.gateway);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Handle VOMS mapping\n//------------------------------------------------------------------------------\nvoid\nMapping::HandleVOMS(const XrdSecEntity* client, VirtualIdentity& vid)\n{\n  // No VOMS info available\n  if ((client->grps == nullptr) || (strlen(client->grps) == 0)) {\n    return;\n  }\n\n  std::string group = client->grps;\n  size_t g_pos = group.find(\" \");\n\n  if (g_pos != std::string::npos) {\n    group.erase(g_pos);\n  }\n\n  // VOMS mapping\n  std::string vomsstring = \"voms:\\\"\";\n  vomsstring += group;\n  vomsstring += \":\";\n  vid.grps = group;\n\n  if (client->role && strlen(client->role) &&\n      (strncmp(client->role, \"NULL\", 4) != 0)) {\n    // the role might be NULL\n    std::string role = client->role;\n    size_t r_pos = role.find(\" \");\n\n    if (r_pos != std::string::npos) {\n      role.erase(r_pos);\n    }\n\n    vomsstring += role;\n    vid.role = role;\n  }\n\n  vomsstring += \"\\\"\";\n  std::string vomsuidstring = vomsstring;\n  std::string vomsgidstring = vomsstring;\n  vomsuidstring += \":uid\";\n  vomsgidstring += \":gid\";\n\n  // Mapping to user\n  if (gVirtualUidMap.count(vomsuidstring)) {\n    vid.allowed_uids.clear();\n    vid.allowed_gids.clear();\n    // Use physical mapping for VOMS roles, convert mapped uid to user name\n    int errc = 0;\n    std::string cname = Mapping::UidToUserName(gVirtualUidMap[vomsuidstring], errc);\n\n    if (!errc) {\n      Mapping::getPhysicalIdShards(cname.c_str(), vid);\n    } else {\n      vid = VirtualIdentity::Nobody();\n      eos_static_err(\"voms-mapping: cannot translate uid=%d to user name with \"\n                     \"the password db\", (int) gVirtualUidMap[vomsuidstring]);\n    }\n  }\n\n  // Mapping to group\n  if (gVirtualGidMap.count(vomsgidstring)) {\n    // se group mapping for VOMS roles\n    vid.allowed_gids.clear();\n    vid.gid = gVirtualGidMap[vomsgidstring];\n    vid.allowed_gids.insert(vid.gid);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Handle HTTPS authz keys mapping\n//------------------------------------------------------------------------------\nvoid\nMapping::HandleKEYS(const XrdSecEntity* client, VirtualIdentity& vid)\n{\n  // No VOMS info available\n  if (vid.key.empty()) {\n    return;\n  }\n\n  std::string uidkey = \"https:\\\"\";\n  uidkey += \"key:\";\n  uidkey += vid.key;\n  uidkey += \"\\\":uid\";\n\n  if (gVirtualUidMap.count(uidkey.c_str())) {\n    vid.uid = VirtualIdentity::kNobodyUid;\n    vid.allowed_uids.clear();\n    vid.allowed_uids.insert(VirtualIdentity::kNobodyUid);\n    vid.uid = gVirtualUidMap[uidkey.c_str()];\n    vid.allowed_uids.insert(vid.uid);\n    vid.gateway = true;\n  }\n\n  std::string gidkey = \"https:\\\"\";\n  gidkey += \"key:\";\n  gidkey += vid.key;\n  gidkey += \"\\\":gid\";\n\n  if (gVirtualGidMap.count(gidkey.c_str())) {\n    vid.gid = VirtualIdentity::kNobodyGid;\n    vid.allowed_gids.clear();\n    vid.allowed_gids.insert(VirtualIdentity::kNobodyGid);\n    vid.gid = gVirtualGidMap[gidkey.c_str()];\n    vid.allowed_gids.insert(vid.gid);\n    vid.gateway = true;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print the current mappings\n//------------------------------------------------------------------------------\nvoid\nMapping::Print(XrdOucString& stdOut, XrdOucString option)\n{\n  bool translateids = true;\n\n  if (option.find(\"n\") != STR_NPOS) {\n    translateids = false;\n    option.replace(\"n\", \"\");\n  }\n\n  if ((!option.length()) || ((option.find(\"u\")) != STR_NPOS)) {\n    for (auto it = gUserRoleVector.cbegin(); it != gUserRoleVector.cend(); ++it) {\n      char iuid[4096];\n      sprintf(iuid, \"%d\", it->first);\n      char suid[4096];\n      sprintf(suid, \"%-6s\", iuid);\n\n      if (translateids) {\n        int errc = 0;\n        std::string username = UidToUserName(it->first, errc);\n\n        if (!errc) {\n          sprintf(suid, \"%-12s\", username.c_str());\n        }\n      }\n\n      stdOut += \"membership uid: \";\n      stdOut += suid;\n      stdOut += \" => uids(\";\n\n      for (const auto& uid : it->second) {\n        if (translateids) {\n          int errc = 0;\n          std::string username = UidToUserName(uid, errc);\n\n          if (!errc) {\n            stdOut += username.c_str();\n          } else {\n            stdOut += (int)uid;\n          }\n        } else {\n          stdOut += (int)uid;\n        }\n\n        stdOut += \",\";\n      }\n\n      if (!it->second.empty()) {\n        stdOut.erasefromend(1);\n      }\n\n      stdOut += \")\\n\";\n    }\n  }\n\n  if ((!option.length()) || ((option.find(\"g\")) != STR_NPOS)) {\n    for (auto it = gGroupRoleVector.cbegin(); it != gGroupRoleVector.cend(); ++it) {\n      char iuid[4096];\n      sprintf(iuid, \"%d\", it->first);\n      char suid[4096];\n      sprintf(suid, \"%-6s\", iuid);\n\n      if (translateids) {\n        int errc = 0;\n        std::string username = UidToUserName(it->first, errc);\n\n        if (!errc) {\n          sprintf(suid, \"%-12s\", username.c_str());\n        }\n      }\n\n      stdOut += \"membership uid: \";\n      stdOut += suid;\n      stdOut += \" => gids(\";\n\n      for (const auto& gid : it->second) {\n        if (translateids) {\n          int errc = 0;\n          std::string grpname = GidToGroupName(gid, errc);\n\n          if (!errc) {\n            stdOut += grpname.c_str();\n          } else {\n            stdOut += (int)gid;\n          }\n        } else {\n          stdOut += (int)gid;\n        }\n\n        stdOut += \",\";\n      }\n\n      if (!it->second.empty()) {\n        stdOut.erasefromend(1);\n      }\n\n      stdOut += \")\\n\";\n    }\n  }\n\n  if ((!option.length()) || ((option.find(\"s\")) != STR_NPOS)) {\n    // print sudoer line\n    stdOut += \"sudoer                 => uids(\";\n\n    for (auto it = gSudoerMap.cbegin(); it != gSudoerMap.cend(); ++it) {\n      if (it->second) {\n        int errc = 0;\n        std::string username = UidToUserName(it->first, errc);\n\n        if (!errc && translateids) {\n          stdOut += username.c_str();\n        } else {\n          stdOut += (int)(it->first);\n        }\n\n        stdOut += \",\";\n      }\n    }\n\n    if (stdOut.endswith(\",\")) {\n      stdOut.erase(stdOut.length() - 1);\n    }\n\n    stdOut += \")\\n\";\n    stdOut += \"tokensudo              => \";\n\n    if (gTokenSudo == Mapping::kAlways) {\n      stdOut += \"always\";\n    } else if (gTokenSudo == Mapping::kEncrypted) {\n      stdOut += \"encrypted\";\n    } else if (gTokenSudo == Mapping::kStrong) {\n      stdOut += \"strong\";\n    } else if (gTokenSudo == Mapping::kNever) {\n      stdOut += \"never\";\n    } else {\n      stdOut += \"inval\";\n    }\n\n    stdOut += \"\\n\";\n  }\n\n  if ((!option.length()) || ((option.find(\"U\")) != STR_NPOS)) {\n    for (auto it = gVirtualUidMap.cbegin(); it != gVirtualUidMap.cend(); ++it) {\n      stdOut += it->first.c_str();\n      stdOut += \" => \";\n      int errc = 0;\n      std::string username = UidToUserName(it->second, errc);\n\n      if (!errc && translateids) {\n        stdOut += username.c_str();\n      } else {\n        stdOut += (int) it->second;\n      }\n\n      stdOut += \"\\n\";\n    }\n  }\n\n  if ((!option.length()) || ((option.find(\"G\")) != STR_NPOS)) {\n    for (auto it = gVirtualGidMap.cbegin(); it != gVirtualGidMap.cend(); ++it) {\n      stdOut += it->first.c_str();\n      stdOut += \" => \";\n      int errc = 0;\n      std::string groupname = GidToGroupName(it->second, errc);\n\n      if (!errc && translateids) {\n        stdOut += groupname.c_str();\n      } else {\n        stdOut += (int) it->second;\n      }\n\n      stdOut += \"\\n\";\n    }\n  }\n\n  if ((!option.length()) || ((option.find(\"N\")) != STR_NPOS)) {\n    char sline[1024];\n    snprintf(sline, sizeof(sline), \"publicaccesslevel: => %d\\n\",\n             gNobodyAccessTreeDeepness);\n    stdOut += sline;\n  }\n\n  if ((!option.length()) || ((option.find(\"l\")) != STR_NPOS)) {\n    for (auto it = gGeoMap.cbegin(); it != gGeoMap.cend(); ++it) {\n      char sline[1024];\n      snprintf(sline, sizeof(sline) - 1, \"geotag:\\\"%s\\\" => \\\"%s\\\"\\n\",\n               it->first.c_str(), it->second.c_str());\n      stdOut += sline;\n    }\n  }\n\n  if ((!option.length())) {\n    for (auto it = gAllowedTidentMatches.cbegin();\n         it != gAllowedTidentMatches.cend(); ++it) {\n      char sline[1024];\n      snprintf(sline, sizeof(sline) - 1, \"hostmatch:\\\"protocol=%s pattern=%s\\n\",\n               it->first.c_str(), it->second.c_str());\n      stdOut += sline;\n    }\n  }\n\n  if ((option.find(\"y\") != STR_NPOS)) {\n    for (auto it = gVirtualUidMap.cbegin(); it != gVirtualUidMap.cend(); ++it) {\n      std::string authmethod = it->first.c_str();\n\n      if (authmethod.find(\"tident:\") != 0) {\n        continue;\n      }\n\n      // Get the gid mapping for the current key\n      std::string sgid = \"n/a\";\n      std::string gid_key = authmethod;\n      gid_key.replace(gid_key.find(\":uid\"), 4, \":gid\");\n      auto it_gid = gVirtualGidMap.find(gid_key);\n\n      if (it_gid != gVirtualGidMap.end()) {\n        sgid = std::to_string(it_gid->second);\n      }\n\n      authmethod.erase(0, 7); // delete \"tident:\"\n      // delete all the \" characters\n      authmethod.erase(std::remove(authmethod.begin(), authmethod.end(), '\"'),\n                       authmethod.end());\n      auto dpos = authmethod.find(\"@\");\n\n      if ((dpos != std::string::npos) && (authmethod.length() > dpos + 1)) {\n        std::string protocol = authmethod.substr(0, dpos);\n        protocol = ((protocol == \"*\") ? \"all\" : protocol);\n        auto cpos = authmethod.rfind(':');\n        std::string hostname = authmethod.substr(dpos + 1, cpos - dpos - 1);\n        std::ostringstream oss;\n        oss << \"gateway=\" << hostname << \" auth=\" << protocol\n            << \" uid=\" << it->second << \" gid=\" << sgid << std::endl;\n        stdOut += oss.str().c_str();\n      }\n    }\n  }\n\n  if ((option.find(\"a\") != STR_NPOS)) {\n    for (auto it = gVirtualUidMap.cbegin(); it != gVirtualUidMap.cend(); ++it) {\n      if (it->second == 0) {\n        XrdOucString authmethod = it->first.c_str();\n\n        if (authmethod.beginswith(\"tident:\")) {\n          continue;\n        }\n\n        int dpos = authmethod.find(\":\");\n        authmethod.erase(dpos);\n        stdOut += \"auth=\";\n        stdOut += authmethod;\n        stdOut += \"\\n\";\n      }\n    }\n  }\n}\n\n\n/*----------------------------------------------------------------------------*/\n/**\n * Convert uid to user name\n *\n * @param uid unix user id\n * @param errc 0 if success, EINVAL if does not exist\n *\n * @return user name as string\n */\n\n/*----------------------------------------------------------------------------*/\nstd::string\nMapping::UidToUserName(uid_t uid, int& errc)\n{\n  errc = 0;\n  {\n    std::scoped_lock lock(gPhysicalUserNameCacheMutex);\n    auto kv = gPhysicalUserNameCache.find(uid);\n\n    if (kv != gPhysicalUserNameCache.end()) {\n      return kv->second;\n    }\n  }\n\n  if (auto user_ptr = gShardedNegativeUserNameCache.retrieve(uid)) {\n    return *user_ptr;\n  }\n\n  char buffer[131072];\n  int buflen = sizeof(buffer);\n  std::string uid_string = \"\";\n  struct passwd pwbuf;\n  struct passwd* pwbufp = 0;\n  (void) getpwuid_r(uid, &pwbuf, buffer, buflen, &pwbufp);\n\n  if (pwbufp == NULL) {\n    char buffer[131072];\n    int buflen = sizeof(buffer);\n    std::string uid_string = \"\";\n    struct passwd pwbuf;\n    struct passwd* pwbufp = 0;\n    {\n      if (getpwuid_r(uid, &pwbuf, buffer, buflen, &pwbufp) || (!pwbufp)) {\n        char suid[1024];\n        snprintf(suid, sizeof(suid) - 1, \"%u\", uid);\n        uid_string = suid;\n        errc = EINVAL;\n        gShardedNegativeUserNameCache.store(uid,\n                                            std::make_unique<std::string>(uid_string));\n        return uid_string;\n      } else {\n        uid_string = pwbuf.pw_name;\n        errc = 0;\n      }\n    }\n    cacheUserIds(uid, uid_string);\n    return uid_string;\n  } else {\n    uid_string = pwbuf.pw_name;\n    errc = 0;\n  }\n\n  cacheUserIds(uid, uid_string);\n  return uid_string;\n}\n\n/*----------------------------------------------------------------------------*/\n/**\n * Convert gid to group name\n *\n * @param gid unix group id\n * @param errc 0 if success, EINVAL if does not exist\n *\n * @return user name as string\n */\n\n/*----------------------------------------------------------------------------*/\nstd::string\nMapping::GidToGroupName(gid_t gid, int& errc, size_t buffersize)\n{\n  errc = 0;\n  {\n    std::scoped_lock lock(gPhysicalGroupNameCacheMutex);\n    auto kv = gPhysicalGroupNameCache.find(gid);\n\n    if (kv != gPhysicalGroupNameCache.end()) {\n      return kv->second;\n    }\n  }\n\n  if (auto group_ptr = gShardedNegativeGroupNameCache.retrieve(gid)) {\n    return *group_ptr;\n  }\n\n  {\n    char buffer[buffersize];\n    int buflen = sizeof(buffer);\n    struct group grbuf;\n    struct group* grbufp = 0;\n    std::string gid_string = \"\";\n\n    if (getgrgid_r(gid, &grbuf, buffer, buflen, &grbufp) || (!grbufp)) {\n      if (errno == ERANGE) {\n        if (buffersize < (16 * 1024 * 1024)) {\n          // try doubling the buffer\n          return GidToGroupName(gid, errc, 2 * buffersize);\n        }\n\n        // just give up here\n      }\n\n      // cannot translate this name\n      char sgid[1024];\n      snprintf(sgid, sizeof(sgid) - 1, \"%u\", gid);\n      gid_string = sgid;\n      errc = EINVAL;\n      gShardedNegativeGroupNameCache.store(gid,\n                                           std::make_unique<std::string>(gid_string));\n      return gid_string;\n    } else {\n      gid_string = grbuf.gr_name;\n      errc = 0;\n    }\n\n    cacheGroupIds(gid, gid_string);\n    return gid_string;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\n/**\n * Convert string name to uid\n *\n * @param username name as string\n * @param errc 0 if success, EINVAL if does not exist\n *\n * @return user id\n */\n\n/*----------------------------------------------------------------------------*/\nuid_t\nMapping::UserNameToUid(const std::string& username, int& errc)\n{\n  {\n    std::scoped_lock lock(gPhysicalUserNameCacheMutex);\n\n    if (auto kv = gPhysicalUserIdCache.find(username);\n        kv != gPhysicalUserIdCache.end()) {\n      return kv->second;\n    }\n  }\n  char buffer[131072];\n  int buflen = sizeof(buffer);\n  uid_t uid = VirtualIdentity::kNobodyUid;\n  struct passwd pwbuf;\n  struct passwd* pwbufp = 0;\n  errc = 0;\n  (void) getpwnam_r(username.c_str(), &pwbuf, buffer, buflen, &pwbufp);\n\n  if (pwbufp == NULL) {\n    bool is_number = true;\n\n    for (size_t i = 0; i < username.length(); i++) {\n      if (!isdigit(username[i])) {\n        is_number = false;\n        break;\n      }\n    }\n\n    uid = atoi(username.c_str());\n\n    if ((uid != 0) && (is_number)) {\n      errc = 0;\n      return uid;\n    } else {\n      errc = EINVAL;\n      uid = VirtualIdentity::kNobodyUid;\n      return uid;\n    }\n  } else {\n    uid = pwbuf.pw_uid;\n    errc = 0;\n  }\n\n  if (!errc) {\n    cacheUserIds(uid, username);\n  }\n\n  return uid;\n}\n\n/*----------------------------------------------------------------------------*/\n/**\n * Convert string name to gid\n *\n * @param groupname name as string\n * @param errc 0 if success, EINVAL if does not exist\n *\n * @return group id\n */\n\n/*----------------------------------------------------------------------------*/\ngid_t\nMapping::GroupNameToGid(const std::string& groupname, int& errc)\n{\n  {\n    std::scoped_lock lock(gPhysicalGroupNameCacheMutex);\n\n    if (auto kv = gPhysicalGroupIdCache.find(groupname);\n        kv != gPhysicalGroupIdCache.end()) {\n      return kv->second;\n    }\n  }\n  char buffer[131072];\n  int buflen = sizeof(buffer);\n  struct group grbuf;\n  struct group* grbufp = 0;\n  gid_t gid = VirtualIdentity::kNobodyGid;\n  errc = 0;\n  (void) getgrnam_r(groupname.c_str(), &grbuf, buffer, buflen, &grbufp);\n\n  if (!grbufp) {\n    bool is_number = true;\n\n    for (size_t i = 0; i < groupname.length(); i++) {\n      if (!isdigit(groupname[i])) {\n        is_number = false;\n        break;\n      }\n    }\n\n    gid = atoi(groupname.c_str());\n\n    if ((gid != 0) && (is_number)) {\n      errc = 0;\n      return gid;\n    } else {\n      errc = EINVAL;\n      gid = VirtualIdentity::kNobodyGid;\n    }\n  } else {\n    gid = grbuf.gr_gid;\n    errc = 0;\n  }\n\n  if (!errc) {\n    cacheGroupIds(gid, groupname);\n  }\n\n  return gid;\n}\n\n/*----------------------------------------------------------------------------*/\n/**\n * Convert string name to gid\n *\n * @param groupname name as string\n * @param errc 0 if success, EINVAL if does not exist\n *\n * @return group id\n */\n\n/*----------------------------------------------------------------------------*/\n\nstd::string\nMapping::ip_cache::GetIp(const char* hostname)\n{\n  time_t now = time(NULL);\n  {\n    // check for an existing translation\n    RWMutexReadLock guard(mLocker);\n\n    if (mIp2HostMap.count(hostname) &&\n        mIp2HostMap[hostname].first > now) {\n      eos_static_debug(\"status=cached host=%s ip=%s\", hostname,\n                       mIp2HostMap[hostname].second.c_str());\n      // give cached entry\n      return mIp2HostMap[hostname].second;\n    }\n  }\n  {\n    // refresh an entry\n    XrdNetAddr* addrs  = 0;\n    int         nAddrs = 0;\n    const char* err    = XrdNetUtils::GetAddrs(hostname, &addrs, nAddrs,\n                         XrdNetUtils::allIPv64,\n                         XrdNetUtils::NoPortRaw);\n\n    if (err || nAddrs == 0) {\n      return \"\";\n    }\n\n    char buffer[64];\n    int hostlen = addrs[0].Format(buffer, sizeof(buffer),\n                                  XrdNetAddrInfo::fmtAddr,\n                                  XrdNetAddrInfo::noPortRaw);\n    delete [] addrs;\n\n    if (hostlen > 0) {\n      RWMutexWriteLock guard(mLocker);\n      std::string sip(buffer, hostlen);\n      mIp2HostMap[hostname] = std::make_pair(now + mLifeTime, sip);\n      eos_static_debug(\"status=refresh host=%s ip=%s\", hostname,\n                       mIp2HostMap[hostname].second.c_str());\n      return sip;\n    }\n\n    return \"\";\n  }\n}\n\n//------------------------------------------------------------------------------\n// Convert a comma separated uid string to a vector uid list\n//------------------------------------------------------------------------------\nvoid\nMapping::CommaListToUidSet(const char* list, std::set<uid_t>& uids_set)\n{\n  XrdOucString slist = list;\n  XrdOucString number = \"\";\n  int kommapos;\n\n  if (!slist.endswith(\",\")) {\n    slist += \",\";\n  }\n\n  do {\n    kommapos = slist.find(\",\");\n\n    if (kommapos != STR_NPOS) {\n      number.assign(slist, 0, kommapos - 1);\n      std::string username = number.c_str();\n      int errc = 0;\n      uid_t uid ;\n\n      if (std::find_if(username.begin(), username.end(),\n      [](unsigned char c) {\n      return std::isalpha(c);\n      })\n      != username.end()) {\n        uid = eos::common::Mapping::UserNameToUid(username, errc);\n      } else {\n        try {\n          uid = std::stoul(username);\n        } catch (const std::exception& e) {\n          uid = VirtualIdentity::kNobodyUid;\n        }\n      }\n\n      if (!errc) {\n        uids_set.insert(uid);\n      }\n\n      slist.erase(0, kommapos + 1);\n    }\n  } while (kommapos != STR_NPOS);\n}\n\n//------------------------------------------------------------------------------\n// Convert a komma separated gid string to a vector gid list\n//------------------------------------------------------------------------------\nvoid\nMapping::CommaListToGidSet(const char* list, std::set<gid_t>& gids_set)\n{\n  XrdOucString slist = list;\n  XrdOucString number = \"\";\n  int kommapos;\n\n  if (!slist.endswith(\",\")) {\n    slist += \",\";\n  }\n\n  do {\n    kommapos = slist.find(\",\");\n\n    if (kommapos != STR_NPOS) {\n      number.assign(slist, 0, kommapos - 1);\n      int errc = 0;\n      std::string groupname = number.c_str();\n      gid_t gid = GroupNameToGid(groupname, errc);\n\n      if (!errc) {\n        gids_set.insert(gid);\n      }\n\n      slist.erase(0, kommapos + 1);\n    }\n  } while (kommapos != STR_NPOS);\n}\n\n\n// -----------------------------------------------------------------------------\n//! Compare a uid with the string representation\n// -----------------------------------------------------------------------------\n\nbool Mapping::IsUid(XrdOucString idstring, uid_t& id)\n{\n  id = strtoul(idstring.c_str(), 0, 10);\n  char revid[1024];\n  sprintf(revid, \"%lu\", (unsigned long) id);\n  XrdOucString srevid = revid;\n\n  if (idstring == srevid) {\n    return true;\n  }\n\n  return false;\n}\n\n// -----------------------------------------------------------------------------\n//! Compare a gid with the string representation\n// -----------------------------------------------------------------------------\n\nbool Mapping::IsGid(XrdOucString idstring, gid_t& id)\n{\n  id = strtoul(idstring.c_str(), 0, 10);\n  char revid[1024];\n  sprintf(revid, \"%lu\", (unsigned long) id);\n  XrdOucString srevid = revid;\n\n  if (idstring == srevid) {\n    return true;\n  }\n\n  return false;\n}\n\n// -----------------------------------------------------------------------------\n//! Reduce the trace identifier information to user@host\n// -----------------------------------------------------------------------------\n\nconst char* Mapping::ReduceTident(XrdOucString& tident,\n                                  XrdOucString& wildcardtident, XrdOucString& mytident, XrdOucString& myhost)\n{\n  int dotpos = tident.find(\".\");\n  int addpos = tident.find(\"@\");\n  wildcardtident = tident;\n  mytident = tident;\n  mytident.erase(dotpos, addpos - dotpos);\n  myhost = mytident;\n  dotpos = mytident.find(\"@\");\n  myhost.erase(0, dotpos + 1);\n  wildcardtident = mytident;\n  addpos = wildcardtident.find(\"@\");\n  wildcardtident.erase(0, addpos);\n  wildcardtident = \"*\" + wildcardtident;\n  return mytident.c_str();\n}\n\nstd::string Mapping::ReduceTident(std::string_view tident,\n                                  std::string& wildcardtident, std::string& myhost)\n{\n  auto dotpos = tident.find(\".\");\n  auto addpos = tident.find(\"@\");\n  std::string mytident{tident};\n  mytident.erase(dotpos, addpos - dotpos);\n  myhost = tident.substr(addpos + 1);\n  wildcardtident = \"*@\" + myhost;\n  return mytident;\n}\n\n// -----------------------------------------------------------------------------\n//! Convert a uid into a string\n// -----------------------------------------------------------------------------\nstd::string Mapping::UidAsString(uid_t uid)\n{\n  std::string uidstring = \"\";\n  char suid[1024];\n  snprintf(suid, sizeof(suid) - 1, \"%u\", uid);\n  uidstring = suid;\n  return uidstring;\n}\n\n// -----------------------------------------------------------------------------\n//! Convert a gid into a string\n// -----------------------------------------------------------------------------\n\nstd::string Mapping::GidAsString(gid_t gid)\n{\n  std::string gidstring = \"\";\n  char sgid[1024];\n  snprintf(sgid, sizeof(sgid) - 1, \"%u\", gid);\n  gidstring = sgid;\n  return gidstring;\n}\n\n//------------------------------------------------------------------------------\n//! Function converting vid frin a string representation\n//------------------------------------------------------------------------------\n\nbool Mapping::VidFromString(VirtualIdentity& vid,\n                            const char* vidstring)\n{\n  std::string svid = vidstring;\n  std::vector<std::string> tokens;\n  eos::common::StringConversion::EmptyTokenize(\n    vidstring,\n    tokens,\n    \":\");\n\n  if (tokens.size() != 7) {\n    return false;\n  }\n\n  vid.uid = strtoul(tokens[0].c_str(), 0, 10);\n  vid.gid = strtoul(tokens[1].c_str(), 0, 10);\n  vid.uid_string = tokens[2].c_str();\n  vid.gid_string = tokens[3].c_str();\n  vid.name = tokens[4].c_str();\n  vid.prot = tokens[5].c_str();\n  vid.tident = tokens[6].c_str();\n  return true;\n}\n\n//----------------------------------------------------------------------------\n//! Function converting vid to a string representation\n//----------------------------------------------------------------------------\n\nstd::string Mapping::VidToString(VirtualIdentity& vid)\n{\n  char vids[4096];\n  snprintf(vids, sizeof(vids), \"%u:%u:%s:%s:%s:%s:%s\",\n           vid.uid,\n           vid.gid,\n           vid.uid_string.c_str(),\n           vid.gid_string.c_str(),\n           vid.name.c_str(),\n           vid.prot.c_str(),\n           vid.tident.c_str());\n  return std::string(vids);\n}\n\n//------------------------------------------------------------------------------\n//! Function returning a VID from a name\n//------------------------------------------------------------------------------\nVirtualIdentity Mapping::Someone(const std::string& name)\n{\n  VirtualIdentity vid;\n  vid = VirtualIdentity::Nobody();\n  int errc = 0;\n  uid_t uid = UserNameToUid(name, errc);\n\n  if (!errc) {\n    vid.uid = uid;\n    vid.uid_string = name;\n    vid.name = name.c_str();\n    vid.tident = std::string(name + \"@grpc\").c_str();\n  }\n\n  return vid;\n}\n\n//------------------------------------------------------------------------------\n//! Function returning a VID from a uid/gid pair\n//------------------------------------------------------------------------------\nVirtualIdentity Mapping::Someone(uid_t uid, gid_t gid)\n{\n  VirtualIdentity vid;\n  vid = VirtualIdentity::Nobody();\n  int errc = 0;\n  vid.uid = uid;\n  vid.gid = gid;\n  vid.allowed_uids = {uid, VirtualIdentity::kNobodyUid};\n  vid.allowed_gids = {gid, VirtualIdentity::kNobodyGid};\n  vid.sudoer = false;\n  vid.gateway = false;\n  vid.uid_string = UidToUserName(uid, errc);\n\n  if (!errc) {\n    vid.name = vid.uid_string.c_str();\n  } else {\n    vid.name = UidAsString(uid).c_str();\n  }\n\n  vid.gid_string = GidToGroupName(gid, errc);\n  vid.tident = std::string(vid.uid_string + \"@grpc\").c_str();\n  return vid;\n}\n\n//------------------------------------------------------------------------------\n//! Function testing if an OAUTH2 resource is allowed by the configuration\n//------------------------------------------------------------------------------\nbool\nMapping::IsOAuth2Resource(const std::string& resource)\n{\n  eos::common::RWMutexReadLock lock(eos::common::Mapping::gMapMutex);\n  std::string uidkey = \"oauth2:\\\"\";\n  uidkey += \"key:\";\n  uidkey += resource;\n  uidkey += \"\\\":uid\";\n  return gVirtualUidMap.count(uidkey);\n}\n\n//------------------------------------------------------------------------------\n//! Decode the uid from a trace ID string\n//------------------------------------------------------------------------------\nuid_t\nMapping::UidFromTident(const std::string& tident)\n{\n  std::vector<std::string> tokens;\n  std::string delimiter = \"^\";\n  eos::common::StringConversion::Tokenize(tident, tokens, delimiter);\n\n  if (tokens.size()) {\n    return atoi(tokens[0].c_str());\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n//! Return number of active sessions for a given uid\n//------------------------------------------------------------------------------\nsize_t\nMapping::ActiveSessions(uid_t uid)\n{\n  //XrdSysMutexHelper mLock(ActiveLock);\n  if (auto n = ActiveUidsSharded.retrieve(uid)) {\n    return *n;\n  }\n\n  return 0;\n}\n\n\nsize_t\nMapping::ActiveSessions()\n{\n  return ActiveTidentsSharded.num_entries();\n}\n\nvoid\nMapping::addSecondaryGroups(VirtualIdentity& vid, const std::string& name,\n                            gid_t gid)\n{\n  if (!gSecondaryGroups) {\n    return;\n  }\n\n  populateGroups(name, gid, vid, gGroupsFetcher.get());\n}\n\n\n\nvoid\nMapping::getPhysicalIdShards(const std::string& name, VirtualIdentity& vid)\n{\n  if (name.empty()) {\n    return;\n  }\n\n  struct passwd passwdinfo;\n\n  char buffer[131072];\n\n  size_t buflen = sizeof(buffer);\n\n  memset(&passwdinfo, 0, sizeof(passwdinfo));\n\n  eos_static_debug(\"find in uid cache %s cache shard=%d\", name.c_str(),\n                   gShardedPhysicalUidCache.calculateShard(name));\n\n  std::unique_ptr<id_pair> idp {nullptr};\n\n  bool in_uid_cache {false};\n\n  if (auto id_ptr = gShardedPhysicalUidCache.retrieve(name)) {\n    vid.uid = id_ptr->uid;\n    vid.gid = id_ptr->gid;\n    // FIXME use a value type!\n    idp.reset(new id_pair(vid.uid, vid.gid));\n    in_uid_cache = true;\n    eos_static_debug(\"msg=\\\"found in uid cache\\\" name=%s\", name.c_str());\n  } else {\n    eos_static_debug(\"msg=\\\"not found in uid cache\\\" name=%s\", name.c_str());\n    bool use_pw = true;\n\n    if (name.length() == 8) {\n      bool known_tident = false;\n\n      if (startsWith(name, \"*\") || startsWith(name, \"~\") || startsWith(name, \"_\")) {\n        known_tident = true;\n        vid.allowed_uids.clear();\n        vid.allowed_gids.clear();\n        // that is a new base-64 encoded id following the format '*1234567'\n        // where 1234567 is the base64 encoded 42-bit value of 20-bit uid |\n        // 16-bit gid | 6-bit session id.\n        std::string b64name = name;\n        b64name.erase(0, 1);\n        // Decoden '_' -> '/', '-' -> '+' that was done to ensure the validity\n        // of the XRootD URL.\n        std::replace(b64name.begin(), b64name.end(), '_', '/');\n        std::replace(b64name.begin(), b64name.end(), '-', '+');\n        b64name += \"=\";\n        unsigned long long bituser = 0;\n        char* out = 0;\n        ssize_t outlen;\n\n        if (eos::common::SymKey::Base64Decode(b64name.c_str(), out, outlen)) {\n          if (outlen <= 8) {\n            memcpy((((char*) &bituser)) + 8 - outlen, out, outlen);\n            eos_static_debug(\"msg=\\\"decoded base-64 uid/gid/sid\\\" val=%llx val=%llx\",\n                             bituser, n_tohll(bituser));\n          } else {\n            eos_static_err(\"msg=\\\"decoded base-64 uid/gid/sid too long\\\" len=%d\", outlen);\n            return;\n          }\n\n          bituser = n_tohll(bituser);\n\n          if (out) {\n            free(out);\n          }\n\n          if (startsWith(name, \"*\") || startsWith(name, \"_\")) {\n            idp.reset(new id_pair((bituser >> 22) & 0xfffff, (bituser >> 6) & 0xffff));\n            struct passwd* pwbufp = 0;\n\n            if (getpwuid_r(idp->uid, &passwdinfo, buffer, buflen, &pwbufp) || (!pwbufp)) {\n              return;\n            }\n\n            cacheUserIds(passwdinfo.pw_uid, passwdinfo.pw_name);\n            vid.uid_string = passwdinfo.pw_name;\n\n            if (idp->gid != passwdinfo.pw_gid) {\n              // add the primary group if it is not the desired one\n              vid.allowed_gids.insert(passwdinfo.pw_gid);\n            }\n          } else {\n            // only user id got forwarded, we retrieve the corresponding group\n            uid_t ruid = (bituser >> 6) & 0xfffffffff;\n            struct passwd* pwbufp = 0;\n\n            if (getpwuid_r(ruid, &passwdinfo, buffer, buflen, &pwbufp) || (!pwbufp)) {\n              return;\n            }\n\n            idp.reset(new id_pair(passwdinfo.pw_uid, passwdinfo.pw_gid));\n            vid.uid_string = passwdinfo.pw_name;\n            cacheUserIds(passwdinfo.pw_uid, passwdinfo.pw_name);\n          }\n\n          eos_static_debug(\"using base64 mapping %s %d %d\", name.c_str(), idp->uid,\n                           idp->gid);\n        } else {\n          eos_static_err(\"msg=\\\"failed to decoded base-64 uid/gid/sid\\\" id=%s\",\n                         name.c_str());\n          return;\n        }\n      }\n\n      if (known_tident) {\n        // unlikely as all the code paths here should have populated idp, but\n        // just defensive programming\n        if (!idp) {\n          eos_static_err(\"msg=\\\"failed to retrieve id for\\\" name=%s\",\n                         name.c_str());\n          return;\n        }\n\n        if (gRootSquash && idp && (!idp->uid || !idp->gid)) {\n          return;\n        }\n\n        vid.uid = idp->uid;\n        vid.gid = idp->gid;\n        vid.allowed_uids.insert(vid.uid);\n        vid.allowed_gids.insert(vid.gid);\n        vid.allowed_uids.insert(VirtualIdentity::kNobodyUid);\n        vid.allowed_gids.insert(VirtualIdentity::kNobodyGid);\n\n        // If uid_string empty try best effort with the given \"name\"\n        if (vid.uid_string.empty()) {\n          addSecondaryGroups(vid, name, idp->gid);\n        } else {\n          addSecondaryGroups(vid, vid.uid_string, idp->gid);\n        }\n\n        auto gs = std::make_unique<gid_set>(vid.allowed_gids);\n        eos_static_debug(\"adding to cache uid=%u gid=%u\", idp->uid, idp->gid);\n        gShardedPhysicalUidCache.store(name, std::move(idp));\n        gShardedPhysicalGidCache.store(name, std::move(gs));\n        return;\n      }\n    }\n\n    if (use_pw) {\n      if (auto ptr = gShardedNegativePhysicalUidCache.retrieve(name)) {\n        eos_static_debug(\"msg=\\\"found in negative user name cache\\\" name=%s\",\n                         name.c_str());\n        return;\n      }\n\n      struct passwd* pwbufp = 0;\n\n      {\n        if (getpwnam_r(name.c_str(), &passwdinfo, buffer, buflen, &pwbufp) ||\n            (!pwbufp)) {\n          gShardedNegativePhysicalUidCache.store(name, std::make_unique<bool>(true));\n          return;\n        }\n      }\n\n      idp.reset(new id_pair(passwdinfo.pw_uid, passwdinfo.pw_gid));\n      vid.uid = idp->uid;\n      vid.gid = idp->gid;\n      vid.uid_string = passwdinfo.pw_name;\n      cacheUserIds(passwdinfo.pw_uid, passwdinfo.pw_name);\n    }\n  }\n\n  if (auto gv = gShardedPhysicalGidCache.retrieve(name)) {\n    vid.allowed_uids.insert(idp->uid);\n    vid.allowed_gids = *gv;\n    vid.uid = idp->uid;\n    vid.gid = idp->gid;\n    eos_static_debug(\"msg=\\\"returning\\\" uid=%u gid=%u\", idp->uid, idp->gid);\n\n    if (!in_uid_cache) {\n      eos_static_debug(\"msg=\\\"adding to cache\\\" uid=%u gid=%u\", idp->uid, idp->gid);\n      gShardedPhysicalUidCache.store(name, std::move(idp));\n    }\n\n    return;\n  }\n\n  // If uid_string empty try best effort with the given \"name\"\n  if (vid.uid_string.empty()) {\n    addSecondaryGroups(vid, name, idp->gid);\n  } else {\n    addSecondaryGroups(vid, vid.uid_string, idp->gid);\n  }\n\n  // add to the cache\n  if (!in_uid_cache) {\n    eos_static_debug(\"msg=\\\"adding to cache\\\" uid=%u gid=%u\", idp->uid, idp->gid);\n    gShardedPhysicalUidCache.store(name, std::move(idp));\n  }\n\n  gShardedPhysicalGidCache.store(name,\n                                 std::make_unique<gid_set>(vid.allowed_gids));\n  return;\n}\n\nvoid\nMapping::getPhysicalUids(const char* name, VirtualIdentity& vid)\n{\n  Mapping::getPhysicalIdShards(name, vid);\n  vid.gid = VirtualIdentity::kNobodyGid;\n  vid.allowed_gids.clear();\n  vid.allowed_gids.insert(vid.gid);\n}\n\nvoid\nMapping::getPhysicalGids(const char* name, VirtualIdentity& vid)\n{\n  uid_t uid = vid.uid;\n  Mapping::getPhysicalIdShards(name, vid);\n  vid.uid = uid;\n  vid.allowed_uids.clear();\n  vid.allowed_uids.insert(uid);\n  vid.allowed_uids.insert(VirtualIdentity::kNobodyUid);\n}\n\nvoid\nMapping::getPhysicalUidGids(const char* name, VirtualIdentity& vid)\n{\n  Mapping::getPhysicalIdShards(name, vid);\n  vid.allowed_uids.clear();\n  vid.allowed_gids.clear();\n  vid.allowed_uids.insert(vid.uid);\n  vid.allowed_gids.insert(vid.gid);\n  vid.allowed_uids.insert(VirtualIdentity::kNobodyUid);\n  vid.allowed_gids.insert(VirtualIdentity::kNobodyGid);\n}\n\nvoid\nMapping::cacheUserIds(uid_t uid, const std::string& username)\n{\n  std::scoped_lock lock(gPhysicalUserNameCacheMutex);\n  gPhysicalUserIdCache[username] = uid;\n  gPhysicalUserNameCache[uid] = username;\n}\n\nvoid\nMapping::cacheGroupIds(gid_t gid, const std::string& groupname)\n{\n  std::scoped_lock lock(gPhysicalGroupNameCacheMutex);\n  gPhysicalGroupIdCache[groupname] = gid;\n  gPhysicalGroupNameCache[gid] = groupname;\n}\n\nvoid\nMapping::HandleUidGidMapping(const char* name, VirtualIdentity& vid,\n                             const std::string& uid_key_name,\n                             const std::string& gid_key_name, bool force)\n{\n  eos_static_debug(\"msg=\\\"handle uid gid mapping\\\" name=%s prot=%s\",\n                   name, vid.prot.c_str());\n  auto kv_uid = gVirtualUidMap.find(uid_key_name);\n  auto kv_gid = gVirtualGidMap.find(gid_key_name);\n  bool uid_mapped = kv_uid != gVirtualUidMap.end();\n  bool gid_mapped = kv_gid != gVirtualGidMap.end();\n\n  if (force || (uid_mapped && gid_mapped &&\n                (kv_uid->second == 0) && (kv_gid->second == 0))) {\n    eos_static_debug(\"msg=\\\"%s uid/gid mapping\\\"\", vid.prot.c_str());\n    Mapping::getPhysicalUidGids(name, vid);\n    return;\n  }\n\n  if (uid_mapped) {\n    if (kv_uid->second == 0) {\n      eos_static_debug(\"msg=\\\"%s uid mapping\\\"\", vid.prot.c_str());\n      Mapping::getPhysicalUids(name, vid);\n    } else {\n      eos_static_debug(\"msg=\\\"%s uid forced mapping\\\"\", vid.prot.c_str());\n      vid.uid = kv_uid->second;\n      vid.allowed_uids.clear();\n      vid.allowed_uids.insert(vid.uid);\n      vid.allowed_uids.insert(VirtualIdentity::kNobodyUid);\n      vid.gid = VirtualIdentity::kNobodyGid;\n      vid.allowed_gids.clear();\n      vid.allowed_gids.insert(vid.gid);\n    }\n  }\n\n  if (gid_mapped) {\n    if (kv_gid->second == 0) {\n      eos_static_debug(\"msg=\\\"%s gid mapping\\\"\", vid.prot.c_str());\n      Mapping::getPhysicalGids(name, vid);\n    } else {\n      eos_static_debug(\"msg=\\\"%s forced gid mapping\\\"\", vid.prot.c_str());\n      vid.allowed_gids.clear();\n      vid.gid = kv_gid->second;\n      vid.allowed_gids.insert(vid.gid);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print JWT token\n//------------------------------------------------------------------------------\nstd::string\nMapping::PrintJWT(const std::string accesstoken, bool dense)\n{\n  std::stringstream ss;\n\n  try {\n    auto decoded = jwt::decode(accesstoken);\n\n    try {\n      if (dense) {\n        ss << \"issuer:\" << decoded.get_issuer() << \",\";\n      } else {\n        ss << std::left << std::setw(20) << \"issuer: \" << decoded.get_issuer() <<\n           std::endl;\n      }\n    } catch (...) {\n      if (!dense) {\n        ss << std::endl;\n      }\n    }\n\n    try {\n      if (dense) {\n        ss << \"subject:\" << decoded.get_subject() << \",\";\n      } else {\n        ss << std::left << std::setw(20) << \"subject: \" << decoded.get_subject() <<\n           std::endl;\n      }\n    } catch (...) {\n      if (!dense) {\n        ss << std::endl;\n      }\n    }\n\n    try {\n      if (dense) {\n        ss << \"audience:[\";\n\n        for (auto i : decoded.get_audience()) {\n          ss << i << \",\";\n        }\n\n        ss.seekp(-1, ss.cur);\n        ss << \"],\";\n      } else {\n        ss << std::setw(20) << \"audience: \";\n        ss << \"[\";\n\n        for (auto i : decoded.get_audience()) {\n          ss << i << \",\";\n        }\n\n        ss.seekp(-1, ss.cur);\n        ss << \"]\" << std::endl;\n      }\n    } catch (...) {\n      if (!dense) {\n        ss << std::endl;\n      }\n    }\n\n    if (dense) {\n      ss << \"claims:[\";\n\n      try {\n        for (auto& e : decoded.get_payload_json()) {\n          ss << e.first << \":\" << e.second << \",\";\n        }\n      } catch (...) {}\n\n      ss.seekp(-1, ss.cur);\n      ss << \"]\";\n    } else {\n      ss << std::left << std::setw(20) << \"claims: \";\n      ss << \"{\" << std::endl;;\n\n      try {\n        for (auto& e : decoded.get_payload_json()) {\n          ss << std::left << std::setw(22) << \" \" << e.first << \":\" << e.second << \",\" <<\n             std::endl;\n        }\n      } catch (...) {\n        ss << std::endl;\n      }\n\n      ss.seekp(-1, ss.cur);\n      ss << std::endl;\n      ss << std::left << std::setw(20) << \" \" << \"}\" << std::endl;\n    }\n  } catch (...) {\n    return \"<!jwt>\";\n  }\n\n  return ss.str();\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Mapping.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Mapping.hh\n//! @brief Class implementing virtual ID mapping.\n//! @author Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include \"common/RWMutex.hh\"\n#include \"common/ShardedCache.hh\"\n#include \"common/OAuth.hh\"\n#include \"common/VirtualIdentity.hh\"\n#include \"common/UnixGroupsFetcher.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdAcc/XrdAccAuthorize.hh>\n#include <map>\n#include <set>\n#include <string>\n\n//! Forward declaration\nclass XrdSecEntity;\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class Mapping\n//------------------------------------------------------------------------------\nclass Mapping\n{\npublic:\n\n  //! Typedef of list storing valid uids of a user\n  typedef std::set<uid_t> uid_set;\n  //! Typedef of list storing valid gids of a user\n  typedef std::set<gid_t> gid_set;\n  //! Typedef of map storing uid set per uid\n  typedef std::map<uid_t, uid_set > UserRoleMap_t;\n  //! Typedef of map storing gid set per gid\n  typedef std::map<uid_t, gid_set > GroupRoleMap_t;\n  //! Typedef of map storing translation rules from auth methods to uids\n  typedef std::map<std::string, uid_t> VirtualUserMap_t;\n  //! Typedef of map storing translation rules from auth methods to gids\n  typedef std::map<std::string, gid_t> VirtualGroupMap_t;\n  //! Typedef of map storing members of the suid group\n  typedef std::map<uid_t, bool > SudoerMap_t;\n  //! Typedef of map storing translation of string(IP) => geo location string\n  typedef std::map<std::string, std::string> GeoLocationMap_t;\n  //! Typedef of set storing all host patterns which are allowed to use tident mapping\n  typedef std::set<std::pair<std::string, std::string>>  AllowedTidentMatches_t;\n\n  //----------------------------------------------------------------------------\n  //! Class wrapping an uid/gid pari\n  //----------------------------------------------------------------------------\n  class id_pair\n  {\n  public:\n    uid_t uid;\n    gid_t gid;\n\n    id_pair(uid_t iuid, gid_t igid)\n    {\n      uid = iuid;\n      gid = igid;\n    }\n\n    ~id_pair() = default;\n  };\n\n  class ip_cache\n  {\n  public:\n    // IP host entry and last resolution time pair\n    typedef std::pair<time_t, std::string> entry_t;\n    // Constructor\n\n    ip_cache(int lifetime = 300)\n    {\n      mLifeTime = lifetime;\n    }\n\n    // Destructor\n    virtual ~ip_cache() = default;\n\n    // Getter translates host name to IP string\n    std::string GetIp(const char* hostname);\n\n  private:\n    std::map<std::string, entry_t> mIp2HostMap;\n    RWMutex mLocker;\n    int mLifeTime;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Function converting vid to a string representation\n  //----------------------------------------------------------------------------\n  static std::string VidToString(VirtualIdentity& vid);\n\n  //----------------------------------------------------------------------------\n  //! Function converting vid from a string representation\n  //----------------------------------------------------------------------------\n  static bool VidFromString(VirtualIdentity& vid, const char* vidstring);\n\n  //---------------------------------------------------------------------------\n  //! Map a client to its virtual identity\n  //!\n  //! @param client xrootd client authenticatino object\n  //! @param env opaque information containing role selection like\n  //!            'eos.ruid' and 'eos.rgid'\n  //! @param tident trace identifier of the client\n  //! @param vid returned virtual identity\n  //! @param authz_obj authorization library (or chain of authz libs)\n  //! @param acc_op access operation type, the default should always be\n  //!        overwritten by the caller when it matters. Otherwise it's put\n  //!        to the default value AOP_Stat for convenience.\n  //! @param path path of the request\n  //! @param log if true map final mapping info, otherwise false\n  //---------------------------------------------------------------------------\n  static void IdMap(const XrdSecEntity* client, const char* env,\n                    const char* tident, VirtualIdentity& vid,\n                    XrdAccAuthorize* authz_obj = nullptr,\n                    Access_Operation acc_op = AOP_Stat,\n                    std::string path = \"\", bool log = true);\n\n  // ---------------------------------------------------------------------------\n  //! Map describing which virtual user roles a user with a given uid has\n  // ---------------------------------------------------------------------------\n  static UserRoleMap_t gUserRoleVector;\n\n  // ---------------------------------------------------------------------------\n  //! Map describing which virtual group roles a user with a given uid has\n  // ---------------------------------------------------------------------------\n  static GroupRoleMap_t gGroupRoleVector;\n\n  // ---------------------------------------------------------------------------\n  //! Map describing auth info to virtual uid mapping rules\n  // ---------------------------------------------------------------------------\n  static VirtualUserMap_t gVirtualUidMap;\n\n  // ---------------------------------------------------------------------------\n  //! Map describing auth info to virtual gid mapping rules\n  // ---------------------------------------------------------------------------\n  static VirtualGroupMap_t gVirtualGidMap;\n\n  // ---------------------------------------------------------------------------\n  //! Map keeping the super user members\n  // ---------------------------------------------------------------------------\n  static SudoerMap_t gSudoerMap;\n\n  // ---------------------------------------------------------------------------\n  //! Map keeping the geo location HostName=>GeoTag translation\n  // ---------------------------------------------------------------------------\n  static GeoLocationMap_t gGeoMap;\n\n  // ---------------------------------------------------------------------------\n  //! 0: don't allow 1: allow with encryption 2: allow always\n  // ---------------------------------------------------------------------------\n  enum TOKEN_SUDO { kAlways = 0, kStrong, kEncrypted, kNever };\n  static std::atomic<int> gTokenSudo;\n\n  // ---------------------------------------------------------------------------\n  //! Max. subdirectory deepness where anonymous access is allowed\n  // ---------------------------------------------------------------------------\n  static int gNobodyAccessTreeDeepness;\n\n  // ---------------------------------------------------------------------------\n  //! Vector having pattern matches of hosts which are allowed to use tident mapping\n  // ---------------------------------------------------------------------------\n  static AllowedTidentMatches_t gAllowedTidentMatches;\n\n  // ---------------------------------------------------------------------------\n  //! A cache for physical user id caching (e.g. from user name to uid)\n  static ShardedCache<std::string, id_pair> gShardedPhysicalUidCache;\n  static ShardedCache<std::string, bool> gShardedNegativePhysicalUidCache;\n\n  // ---------------------------------------------------------------------------\n  //! A cache for physical group id caching (e.g. from group name to gid)\n  // ---------------------------------------------------------------------------\n  static ShardedCache<std::string, gid_set> gShardedPhysicalGidCache;\n\n  // ---------------------------------------------------------------------------\n  //! A mutex protecting the physical id->name caches\n  // ---------------------------------------------------------------------------\n  static std::mutex\n  gPhysicalUserNameCacheMutex; //< Mutex protecting UserId& UserName cache\n  static std::mutex\n  gPhysicalGroupNameCacheMutex; //< Mutex protecting GroupId& GroupName cache\n\n  // ---------------------------------------------------------------------------\n  //! A cache for physical user name caching (e.g. from uid to name)\n  static std::map<uid_t, std::string> gPhysicalUserNameCache;\n  static std::map<std::string, uid_t> gPhysicalUserIdCache;\n  static ShardedCache<uid_t, std::string> gShardedNegativeUserNameCache;\n  // ---------------------------------------------------------------------------\n  //! A cache for physical group id caching (e.g. from gid name to name)\n  // ---------------------------------------------------------------------------\n  static std::map<gid_t, std::string> gPhysicalGroupNameCache;\n  static std::map<std::string, gid_t> gPhysicalGroupIdCache;\n  static ShardedCache<gid_t, std::string> gShardedNegativeGroupNameCache;\n\n  // ---------------------------------------------------------------------------\n  //! RWMutex protecting all global hash maps\n  // ---------------------------------------------------------------------------\n  static RWMutex gMapMutex;\n\n  // ---------------------------------------------------------------------------\n  //! Cache for host to ip translation used by geo mapping\n  // ---------------------------------------------------------------------------\n  static ip_cache gIpCache;\n\n  // ---------------------------------------------------------------------------\n  //! OAuth interface\n  // ---------------------------------------------------------------------------\n  static OAuth gOAuth;\n\n  // ---------------------------------------------------------------------------\n  //! Function initializing static maps\n  // ---------------------------------------------------------------------------\n  static void Init();\n\n  // ---------------------------------------------------------------------------\n  //! Map storing the client identifiers and last usage time\n  // ---------------------------------------------------------------------------\n  static ShardedCache<std::string, time_t> ActiveTidentsSharded;\n\n  // ---------------------------------------------------------------------------\n  //! Map storing the active client uids\n  // ---------------------------------------------------------------------------\n  static ShardedCache<uid_t, size_t> ActiveUidsSharded;\n\n  // ---------------------------------------------------------------------------\n  //! Fetcher for user's groups\n  // ---------------------------------------------------------------------------\n  static std::unique_ptr<UnixGroupsFetcher> gGroupsFetcher;\n  // ---------------------------------------------------------------------------\n  //! Retrieve the user ID from a trace identifier\n  // ---------------------------------------------------------------------------\n  static uid_t UidFromTident(const std::string& tident);\n\n  // ---------------------------------------------------------------------------\n  //! Get the number of active sessions (for a given uid)\n  static size_t ActiveSessions(uid_t uid);\n  static size_t ActiveSessions();\n\n  // ---------------------------------------------------------------------------\n  //! Variable to forbid remote root mounts - by default true\n  // ---------------------------------------------------------------------------\n  static std::atomic<bool> gRootSquash;\n\n  static std::atomic<bool> gSecondaryGroups;\n  // ---------------------------------------------------------------------------\n  //! Reset clears all cached information\n  // ---------------------------------------------------------------------------\n  static void Reset();\n\n  // ---------------------------------------------------------------------------\n  //! Convert a komma separated uid string to a vector uid list\n  // ---------------------------------------------------------------------------\n  static void\n  CommaListToUidSet(const char* list, std::set<uid_t>& uids_set);\n\n  // ---------------------------------------------------------------------------\n  //! Convert a komma separated gid string to a vector gid list\n  // ---------------------------------------------------------------------------\n  static void\n  CommaListToGidSet(const char* list, std::set<gid_t>& gids_set);\n\n  //----------------------------------------------------------------------------\n  //! Printout mapping in the format specified by option\n  //!\n  //! @param stdOut string stat stores the output\n  //! @param option can be:\n  //!         'u' for user role mappings\n  //!         'g' for group role mappings\n  //!         's' for sudoer list\n  //!         'U' for user alias mapping\n  //!         'G' for group alias mapping\n  //!         'y' for gateway mappings (tidents)\n  //!         'a' for authentication mapping rules\n  //!         'l' for geo location rules\n  //!         'n' for the anonymous access deepness of user nobody\n  //----------------------------------------------------------------------------\n  static void Print(XrdOucString& stdOut, XrdOucString option = \"\");\n\n  static void getPhysicalIdShards(const std::string& name, VirtualIdentity& vid);\n  // ---------------------------------------------------------------------------\n  //! Compare a uid with the string representation\n  // ---------------------------------------------------------------------------\n  static bool IsUid(XrdOucString idstring, uid_t& id);\n\n  // ---------------------------------------------------------------------------\n  //! Compare a gid with the string representation\n  // ---------------------------------------------------------------------------\n  static bool IsGid(XrdOucString idstring, gid_t& id);\n\n  // ---------------------------------------------------------------------------\n  //! Reduce the trace identifier information to user@host\n  // ---------------------------------------------------------------------------\n  static const char* ReduceTident(XrdOucString& tident,\n                                  XrdOucString& wildcardtident,\n                                  XrdOucString& mytident, XrdOucString& myhost);\n\n  static std::string ReduceTident(std::string_view tident,\n                                  std::string& wildcardtident, std::string& myhost);\n  // ---------------------------------------------------------------------------\n  //! Convert a uid to a user name\n  // ---------------------------------------------------------------------------\n  static std::string UidToUserName(uid_t uid, int& errc);\n\n  // ---------------------------------------------------------------------------\n  //! Convert a gid to a group name\n  // ---------------------------------------------------------------------------\n  static std::string GidToGroupName(uid_t gid, int& errc,\n                                    size_t buffersize = 128 * 1024);\n\n  // ---------------------------------------------------------------------------\n  //! Convert a user name to a uid\n  // ---------------------------------------------------------------------------\n  static uid_t UserNameToUid(const std::string& username, int& errc);\n\n  // ---------------------------------------------------------------------------\n  //! Convert a group name to a gid\n  // ---------------------------------------------------------------------------\n  static gid_t GroupNameToGid(const std::string& groupname, int& errc);\n\n  // ---------------------------------------------------------------------------\n  //! Convert a uid into a string\n  // ---------------------------------------------------------------------------\n  static std::string UidAsString(uid_t uid);\n\n  // ---------------------------------------------------------------------------\n  //! Convert a gid into a string\n  // ---------------------------------------------------------------------------\n  static std::string GidAsString(gid_t gid);\n\n  static int GetPublicAccessLevel()\n  {\n    RWMutexReadLock lock(gMapMutex);\n    return Mapping::gNobodyAccessTreeDeepness;\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Get a VID from a name\n  // ---------------------------------------------------------------------------\n  static VirtualIdentity Someone(const std::string& name);\n\n  // ---------------------------------------------------------------------------\n  //! Get a VID from a uid/gid pari\n  // ---------------------------------------------------------------------------\n  static VirtualIdentity Someone(uid_t uid, gid_t gid);\n\n  // ---------------------------------------------------------------------------\n  //! Check if a resource is allowed for OAUTH2\n  // ---------------------------------------------------------------------------\n  static bool IsOAuth2Resource(const std::string& resource);\n\n  // ---------------------------------------------------------------------------\n  //! Print JWT token\n  // ---------------------------------------------------------------------------\n  static std::string PrintJWT(const std::string token, bool dense = true);\n\n  // ---------------------------------------------------------------------------\n  //! Helper functions to populate vid.uid/gid from the Phyiscal Cache/passwd\n  // ---------------------------------------------------------------------------\n  static void getPhysicalUids(const char* name, VirtualIdentity& vid);\n  static void getPhysicalGids(const char* name, VirtualIdentity& vid);\n\n  static void getPhysicalUidGids(const char* name, VirtualIdentity& vid);\n\n  static void HandleUidGidMapping(const char* name, VirtualIdentity& vid,\n                                  const std::string& uid_key_name,\n                                  const std::string& gid_key_name,\n                                  bool force = false);\n\n  static void cacheUserIds(uid_t uid, const std::string& username);\n  static void cacheGroupIds(gid_t gid, const std::string& groupname);\n\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  //----------------------------------------------------------------------------\n  //! Handle VOMS mapping\n  //!\n  //! @param client XrdSecEntity object\n  //! @parma vid virtual identity\n  //! @note needs to be called with the gMapMutex locked\n  //----------------------------------------------------------------------------\n  static void HandleVOMS(const XrdSecEntity* client, VirtualIdentity& vid);\n\n  //----------------------------------------------------------------------------\n  //! Add secondary groups if configured\n  //!\n  //! @param vid where to append groups\n  //! @param idp containing uid to resolved gropus\n  //! @note appends secondary groups to vid.allowed_gids\n  //----------------------------------------------------------------------------\n  static void addSecondaryGroups(VirtualIdentity& vid, const std::string& name,\n                                 gid_t gid);\n\n  //----------------------------------------------------------------------------\n  //! Handle HTTPS authz key mapping\n  //!\n  //! @param client XrdSecEntity object\n  //! @parma vid virtual identity\n  //! @note needs to be called with the gMapMutex locked\n  //----------------------------------------------------------------------------\n  static void HandleKEYS(const XrdSecEntity* client, VirtualIdentity& vid);\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Murmur3.hh",
    "content": "#pragma once\n#include \"common/utils/RandUtils.hh\"\n\nnamespace Murmur3\n{\n  // Poisoned generic specialization, which cannot be used, where poisoned means\n  // the same thing as with std::hash -> https://en.cppreference.com/w/cpp/utility/hash\n  template<typename T>\n  struct MurmurHasher {\n  };\n\n  // uint64_t specialization\n  template<>\n  struct MurmurHasher<uint64_t> {\n    size_t operator()(uint64_t key) const noexcept\n    {\n      key ^= key >> 33;\n      key *= 0xff51afd7ed558ccd;\n      key ^= key >> 33;\n      key *= 0xc4ceb9fe1a85ec53;\n      key ^= key >> 33;\n      return key;\n    }\n  };\n\n  // std::string specialization\n  template<>\n  struct MurmurHasher<std::string> {\n    size_t operator()(const std::string& key) const noexcept\n    {\n      static const size_t seed = eos::common::getRandom64();\n      static const uint32_t c1 = 0xcc9e2d51;\n      static const uint32_t c2 = 0x1b873593;\n      static const uint64_t c3 = 0xff51afd7ed558ccd;\n      size_t hash = seed;\n      auto data = key.c_str();\n      auto chunk = (const uint32_t*) data;\n      auto lengthInBytes = key.size() * sizeof(char);\n      auto blocks = lengthInBytes / 4;\n\n      for (size_t i = 0; i < blocks; i++) {\n        uint32_t k = *chunk;\n        k *= c1;\n        k = (k << 15) | (k >> 17);\n        k *= c2;\n        hash ^= k;\n        hash ^= hash >> 33;\n        hash *= c3;\n        chunk++;\n      }\n\n      auto tail = (const uint8_t*)(data + blocks * 4);\n      uint32_t k = 0;\n\n      switch (lengthInBytes & 3) {\n      case 3:\n        k ^= tail[2] << 16;\n        /* fallthrough */\n\n      case 2:\n        k ^= tail[1] << 8;\n        /* fallthrough */\n\n      case 1:\n        k ^= tail[0];\n        k *= c1;\n        k = (k << 15) | (k >> 17);\n        k *= c2;\n        hash ^= k;\n        hash ^= hash >> 33;\n        hash *= c3;\n      };\n\n      return hash;\n    }\n  };\n};\n"
  },
  {
    "path": "common/MutexLatencyWatcher.cc",
    "content": "//------------------------------------------------------------------------------\n// File: MutexLatencyWatcher.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/MutexLatencyWatcher.hh\"\n#include \"common/Logging.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\nstatic constexpr auto MTX_LATENCY_WATCHER_THREAD_NAME = \"MtxLatencyWatcher\";\n//------------------------------------------------------------------------------\n// Empty constructor\n//------------------------------------------------------------------------------\nMutexLatencyWatcher::MutexLatencyWatcher() { aStart = 0; }\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nMutexLatencyWatcher::MutexLatencyWatcher(RWMutex& mutex, const std::string &friendlyName) {\n  activate(mutex, friendlyName);\n}\n\n//------------------------------------------------------------------------------\n// Custom \"constructor\"\n//------------------------------------------------------------------------------\nvoid MutexLatencyWatcher::activate(RWMutex& mutex, const std::string &friendlyName) {\n  mMutex = &mutex;\n  mFriendlyName = friendlyName;\n  mThread.reset(&MutexLatencyWatcher::main, this);\n}\n\n//------------------------------------------------------------------------------\n// Main thread loop\n//------------------------------------------------------------------------------\nvoid MutexLatencyWatcher::main(ThreadAssistant &assistant) {\n  ThreadAssistant::setSelfThreadName(MTX_LATENCY_WATCHER_THREAD_NAME);\n  while(!assistant.terminationRequested()) {\n    Datapoint point;\n    aStart = time(NULL);\n    point.start = std::chrono::system_clock::now();\n    mMutex->LockWrite();\n    aStart = 0;\n    mMutex->UnLockWrite();\n    point.end = std::chrono::system_clock::now();\n\n    if(point.getMilli() > std::chrono::milliseconds(200)) {\n      eos_static_warning(\"acquisition of mutex %s took %d milliseconds\", mFriendlyName.c_str(), point.getMilli().count());\n    }\n\n    appendDatapoint(point);\n    assistant.wait_for(std::chrono::seconds(2));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Append datapoint\n//------------------------------------------------------------------------------\nvoid MutexLatencyWatcher::appendDatapoint(const Datapoint &point) {\n  std::lock_guard<std::mutex> lock(mDataMutex);\n  mData.push_back(point);\n\n  if(mData.size() > 200) {\n    mData.pop_front();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get latency spikes\n//------------------------------------------------------------------------------\nMutexLatencyWatcher::LatencySpikes MutexLatencyWatcher::getLatencySpikes() const {\n  LatencySpikes spikes;\n  std::chrono::system_clock::time_point now = std::chrono::system_clock::now();\n\n  std::lock_guard<std::mutex> lock(mDataMutex);\n\n  for(auto it = mData.begin(); it != mData.end(); it++) {\n    if(now - it->end <=  std::chrono::minutes(1)) {\n      spikes.lastMinute = std::max(spikes.lastMinute, it->getMilli());\n    }\n\n    if(now - it->end <= std::chrono::minutes(2)) {\n      spikes.last2Minutes = std::max(spikes.last2Minutes, it->getMilli());\n    }\n\n    if(now - it->end <= std::chrono::minutes(5)) {\n      spikes.last5Minutes = std::max(spikes.last5Minutes, it->getMilli());\n    }\n  }\n\n  if(!mData.empty()) {\n    spikes.last = mData.back().getMilli();\n  }\n\n  return spikes;\n}\n\n//------------------------------------------------------------------------------\n//  Append datapoint\n//------------------------------------------------------------------------------\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/MutexLatencyWatcher.hh",
    "content": "//------------------------------------------------------------------------------\n// File: MutexLatencyWatcher.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/RWMutex.hh\"\n#include \"common/AssistedThread.hh\"\n#include <list>\n#include <chrono>\n#include <atomic>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Thread to watch how long it takes to acquire a mutex -- used to debug\n//! latency spikes.\n//------------------------------------------------------------------------------\nclass MutexLatencyWatcher {\npublic:\n  //----------------------------------------------------------------------------\n  //! Datapoint struct\n  //----------------------------------------------------------------------------\n  struct Datapoint {\n    std::chrono::system_clock::time_point start;\n    std::chrono::system_clock::time_point end;\n\n    std::chrono::milliseconds getMilli() const {\n      return std::chrono::duration_cast<std::chrono::milliseconds>(end - start);\n    }\n  };\n\n  std::atomic<time_t> aStart;\n\n  bool isLockedUp() const {\n    if (!aStart) {\n      return false;\n    } else {\n      return ((time(NULL) - aStart) >= 5);\n    }\n  }\n\n  int hangingSince() const {\n    if (!aStart) {\n      return 0;\n    }\n    return ((time(NULL) - aStart));\n  }\n\n  //----------------------------------------------------------------------------\n  //! LatencySpikes struct\n  //----------------------------------------------------------------------------\n  struct LatencySpikes {\n    std::chrono::milliseconds last = std::chrono::milliseconds(0);\n    std::chrono::milliseconds lastMinute = std::chrono::milliseconds(0);\n    std::chrono::milliseconds last2Minutes = std::chrono::milliseconds(0);\n    std::chrono::milliseconds last5Minutes = std::chrono::milliseconds(0);\n  };\n\n  //----------------------------------------------------------------------------\n  //! Empty constructor\n  //----------------------------------------------------------------------------\n  MutexLatencyWatcher();\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  MutexLatencyWatcher(RWMutex& mutex, const std::string &friendlyName);\n  void activate(RWMutex& mutex, const std::string &friendlyName);\n\n  //----------------------------------------------------------------------------\n  //! Main thread loop\n  //----------------------------------------------------------------------------\n  void main(ThreadAssistant &assistant);\n\n  //----------------------------------------------------------------------------\n  //! Get latency spikes\n  //----------------------------------------------------------------------------\n  LatencySpikes getLatencySpikes() const;\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Append datapoint\n  //----------------------------------------------------------------------------\n  void appendDatapoint(const Datapoint &point);\n\n\n  eos::common::RWMutex *mMutex;\n  std::string mFriendlyName;\n  AssistedThread mThread;\n\n  mutable std::mutex mDataMutex;\n  std::list<Datapoint> mData;\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Namespace.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Namespace.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   Namespace.hh\n * \n * @brief  Macros defining eos:common namespace.\n * \n * \n */\n\n\n/*----------------------------------------------------------------------------*/\n//! Macros defining the comman namespace\n/*----------------------------------------------------------------------------*/\n\n#ifndef __EOSCOMMON_NAMESPACE_HH__\n#define __EOSCOMMON_NAMESPACE_HH__\n\n#define EOSCOMMONNAMESPACE_BEGIN   namespace eos { /** @namespace eos project namespace*/  namespace common { /** @namespace @brief common classes*/ \n\n#define EOSCOMMONNAMESPACE_END }}\n\n#endif\n"
  },
  {
    "path": "common/OAuth.cc",
    "content": "//------------------------------------------------------------------------------\n// File: OAuth.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"common/Namespace.hh\"\n#include \"common/OAuth.hh\"\n#include \"common/StringConversion.hh\"\n#include \"jwt-cpp/jwt.h\"\n#include <iostream>\n#include <memory>\n#include <curl/curl.h>\n#include <curl/easy.h>\n#include <json/json.h>\n\n\n#include \"common/Murmur3.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN;\n\nvoid\nOAuth::Init()\n{\n  return;\n}\n\n\nstd::size_t\nOAuth::callback(const char* in, std::size_t size,\n                std::size_t num, std::string* out)\n{\n  const std::size_t totalBytes(size * num);\n  out->append(in, totalBytes);\n  return totalBytes;\n}\n\n\n\nvoid\nOAuth::PurgeCache(time_t& now)\n{\n  static time_t last_purge = time(NULL);\n  eos::common::RWMutexWriteLock lock(mOAuthCacheMutex);\n\n  // purge every 5 min if we have more than 64k entries or every hour for less\n  if (((mOAuthInfo.size() > 65536) && (now - last_purge) > 300) ||\n      ((now - last_purge) > 3600)) {\n    for (auto it = mOAuthInfo.begin(); it != mOAuthInfo.end();) {\n      time_t ctime = strtoull(it->second[\"ctime\"].c_str(), 0, 10);\n      time_t etime = strtoull(it->second[\"etime\"].c_str(), 0, 10);\n\n      if (\n        (etime && (etime < now)) ||\n        ((now - ctime) > cache_validity_time)) {\n        it = mOAuthInfo.erase(it);\n      } else {\n        ++it;\n      }\n    }\n  }\n  last_purge = time(NULL);\n}\n\nint\nOAuth::Validate(OAuth::AuthInfo& info, const std::string& accesstoken,\n                std::string resource, const std::string& refreshtoken, time_t& expires)\n{\n  time_t now = time(NULL);\n\n  if (expires && (expires < now)) {\n    eos_static_debug(\"token='%s' has expired=%lu\", accesstoken.c_str(), expires);\n    return ETIME;\n  }\n\n  std::stringstream s;\n  std::string cache_key;\n  try {\n    // screen the audience\n    auto decoded = jwt::decode(accesstoken);\n    auto audiences = decoded.get_audience();\n    auto exp = decoded.get_expires_at();\n    auto iss = decoded.get_issuer();\n    auto signature = decoded.get_signature();\n    cache_key = signature;\n\n    std::string iss_resource = iss + \"/protocol/openid-connect/userinfo\";\n\n    if (resource.empty()) {\n      // if we have a plain token without oauth2:token:resource wrapping, we build the resource from iss\n      resource = iss_resource;\n    }\n\n    expires = std::chrono::system_clock::to_time_t(exp);\n\n    if (expires && (expires < now)) {\n      eos_static_debug(\"token='%s' has expired=%lu\", accesstoken.c_str(), expires);\n      return ETIME;\n    }\n\n    bool audience_match = false;\n\n    for (auto& e : decoded.get_payload_json()) {\n      s << e.first << \"=\" << e.second << \" \";\n    }\n\n    eos_static_info(\"token='%s...' claims=[ %s ]\",\n                    accesstoken.substr(0, 20).c_str(),\n                    s.str().c_str());\n\n    if (Mapping::IsOAuth2Resource(resource)) {\n      // no audience require\n      audience_match = true;\n    } else {\n      for (auto it = audiences.begin(); it != audiences.end(); ++it) {\n        std::string audience_resource = resource + \"@\";\n        audience_resource += *it;\n\n        if (Mapping::IsOAuth2Resource(audience_resource)) {\n          audience_match = true;\n          break;\n        }\n      }\n    }\n\n    if (!audience_match) {\n      eos_static_err(\"msg=\\\"rejecing - no audience matches\\\"\");\n      return EPERM;\n    }\n  } catch (...) {\n    eos_static_err(\"msg=\\\"rejecting - token decoding failed\");\n    return EPERM;\n  }\n\n  PurgeCache(now);\n  {\n    eos::common::RWMutexReadLock lock(mOAuthCacheMutex);\n    auto cache = mOAuthInfo.find(cache_key);\n\n    if (cache != mOAuthInfo.end()) {\n      time_t ctime = strtoull(cache->second[\"ctime\"].c_str(), 0, 10);\n      time_t etime = strtoull(cache->second[\"etime\"].c_str(), 0, 10);\n\n      if ((!etime) || (etime > now)) {\n        if ((now - ctime) < cache_validity_time) {\n          info = cache->second;\n          return 0;\n        }\n      }\n    }\n  }\n\n  eos_static_info(\"msg=\\\"userinfo endpoint upcall\\\" claims=[ %s ]\", s.str().c_str());\n\n  auto curl = curl_easy_init();\n\n  if (curl) {\n    std::string httpsresource = std::string(\"https://\") + resource;\n    curl_easy_setopt(curl, CURLOPT_URL, httpsresource.c_str());\n\n    if (getenv(\"EOS_MGM_OIDC_INSECURE\")) {\n      curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);\n      curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);\n    }\n\n    long httpCode(0);\n    std::unique_ptr<std::string> httpData(new std::string());\n    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback);\n    curl_easy_setopt(curl, CURLOPT_WRITEDATA, httpData.get());\n    std::string auth = \"Authorization: Bearer \";\n    auth += accesstoken;\n    auto chunk = curl_slist_append(NULL, auth.c_str());\n    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);\n\n    if (EOS_LOGS_DEBUG) {\n      curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);\n    }\n\n    curl_easy_perform(curl);\n    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);\n    curl_easy_cleanup(curl);\n\n    if (httpCode == 200) {\n      Json::Value jsonData;\n      std::string errs;\n      Json::CharReaderBuilder reader;\n      std::unique_ptr<Json::CharReader> jsonReader;\n      jsonReader.reset(reader.newCharReader());\n\n      if (jsonReader->parse((*httpData.get()).c_str(),\n                            (*httpData.get()).c_str() + (*httpData.get()).length(), &jsonData, &errs)) {\n        if (EOS_LOGS_DEBUG) {\n          std::cerr << \"Successfully parsed JSON data\" << std::endl;\n          std::cerr << \"\\nJSON data received:\" << std::endl;\n          std::cerr << jsonData.toStyledString() << std::endl;\n        }\n\n        if (jsonData.isMember(\"name\")) {\n          info[\"name\"] = jsonData[\"name\"].asString();\n        }\n\n        if (jsonData.isMember(\"username\")) {\n          // OAuth style\n          info[\"username\"] = jsonData[\"username\"].asString();\n        } else {\n          if (getenv(\"EOS_MGM_OIDC_MAP_FIELD\") &&\n              jsonData.isMember(getenv(\"EOS_MGM_OIDC_MAP_FIELD\"))) {\n            // configuration overwrite of field to map\n            info[\"username\"] = jsonData[getenv(\"EOS_MGM_OIDC_MAP_FIELD\")].asString();\n          } else {\n            // OIDC style\n            if (jsonData.isMember(\"sub\")) {\n              info[\"username\"] = jsonData[\"sub\"].asString();\n            } else {\n              // we need to have this field to map someone\n\t      eos_static_err(\"msg=\\\"missing sub entry for OIDC style token\\\" claims=[ %s ]\", s.str().c_str());\n              return EINVAL;\n            }\n          }\n        }\n\n        if (jsonData.isMember(\"email\")) {\n          info[\"email\"] = jsonData[\"email\"].asString();\n        }\n\n        if (jsonData.isMember(\"federation\")) {\n          info[\"federation\"] = jsonData[\"federation\"].asString();\n        }\n\n        // cache this entry\n        info[\"ctime\"] = std::to_string(time(NULL));\n        info[\"etime\"] = expires ? std::to_string(expires) : std::to_string(\n                          now + cache_validity_time);\n        eos::common::RWMutexWriteLock lock(mOAuthCacheMutex);\n        mOAuthInfo[cache_key] = info;\n        return 0;\n      } else {\n\teos_static_err(\"msg=\\\"invalid response from userinfo endpoint\\\" claims=[ %s ]\", s.str().c_str());\n        return EINVAL;\n      }\n    } else {\n      return (int)httpCode;\n    }\n  }\n\n  return EFAULT;\n}\n\nstd::string\nOAuth::Handle(const std::string& info, eos::common::VirtualIdentity& vid)\n{\n  std::vector<std::string> tokens;\n  eos::common::StringConversion::Tokenize(info, tokens, \":\");\n\n  // this function handles now tokens of the form oauth2:<token>:[...] or just <token>\n  if (tokens.size() > 1) {\n    if (tokens[0] == \"oauth2\") {\n      if (tokens.size() < 3) {\n        tokens.push_back(\"\");\n      }\n\n      if (tokens.size() < 4) {\n        tokens.push_back(\"0\");\n      }\n\n      if (tokens.size() < 5) {\n        tokens.push_back(\"\");\n      }\n    } else {\n      eos_static_err(\"msg=\\\"invalid oauth token provided\\\" in=\\\"%s\\\"\", info.c_str());\n      return \"\";\n    }\n  } else {\n    tokens.resize(5);\n    tokens[0] = \"oauth2\";\n    tokens[1] = info;\n    tokens[2] = \"\";\n    tokens[3] = \"0\";\n    tokens[4] = \"\";\n  }\n\n  OAuth::AuthInfo oinfo;\n\n  time_t expires = strtoull(tokens[3].c_str(), 0, 10);\n\n  if (!Validate(oinfo, tokens[1], tokens[2], tokens[4], expires)) {\n    // valid token, now map the user name\n    eos_static_info(\"username='%s' name='%s' federation='%s' email='%s' expires=%llu\",\n                    oinfo[\"username\"].c_str(),\n                    oinfo[\"name\"].c_str(),\n                    oinfo[\"federation\"].c_str(),\n                    oinfo[\"email\"].c_str(),\n                    expires);\n    vid.federation = oinfo[\"federation\"];\n    vid.email = oinfo[\"email\"];\n    vid.fullname = oinfo[\"name\"];\n    return oinfo[\"username\"];\n  }\n\n  return \"\";\n}\n\nEOSCOMMONNAMESPACE_END;\n"
  },
  {
    "path": "common/OAuth.hh",
    "content": "// ----------------------------------------------------------------------\n// File: OAuth.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   OAuth.hh\n *\n * @brief  Class handling oauth requests\n *\n *\n */\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include \"common/VirtualIdentity.hh\"\n#include \"common/RWMutex.hh\"\n#include <map>\n#include <string>\n\nEOSCOMMONNAMESPACE_BEGIN;\n\nclass OAuth {\n\npublic:\n\n  typedef std::map<std::string, std::string> AuthInfo;\n\n  OAuth() { cache_validity_time = 600; }\n\n  void Init();\n  virtual ~OAuth() {}\n\n  int Validate(AuthInfo& info, const std::string& accesstoken, std::string resource, const std::string& refreshtoken, time_t& expires);\n  std::string Handle(const std::string& info, eos::common::VirtualIdentity& vid);\n\n  void PurgeCache(time_t& now);\n\n  static std::size_t callback(\n\t\t\t      const char* in,\n\t\t\t      std::size_t size,\n\t\t\t      std::size_t num,\n\t\t\t      std::string* out);\n\nprivate:\n\n  int cache_validity_time;\n  eos::common::RWMutex mOAuthCacheMutex;\n  std::map<std::string, AuthInfo> mOAuthInfo;\n};\n\nEOSCOMMONNAMESPACE_END;\n"
  },
  {
    "path": "common/ObserverMgr.hh",
    "content": "//------------------------------------------------------------------------------\n// File: ObserverMgr.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#pragma once\n#include \"common/SharedCallbackList.hh\"\n#include \"common/Logging.hh\"\n#include \"common/ThreadPool.hh\"\n#include \"common/utils/BindArguments.hh\"\n\nnamespace eos::common\n{\n/*!\n * A mediator like class that basically holds a list of observers and\n * notifies all of them of changes\n * @tparam Args - the list of args that you'd need the callbacks to accept\n */\n// Exposing this here as it'll be hard to use typename ObserverMgr<Args..>::tag\nusing observer_tag_t = shared_callback_slot_t;\n\ntemplate <typename... Args>\nclass ObserverMgr\n{\npublic:\n\n  ObserverMgr() : mThreadPool(2) {}\n\n  ObserverMgr(size_t min_threads,\n              size_t max_threads = std::thread::hardware_concurrency()) :\n    mThreadPool(min_threads, max_threads)\n  {}\n\n  virtual ~ObserverMgr()\n  {\n    syncAllNotifications();\n  }\n\n  // reap all notifications, blocks the calling thread; not meant to be called often\n  void syncAllNotifications()\n  {\n    std::scoped_lock lock(async_completions_mtx);\n    for (auto it = async_completions.begin();\n         it != async_completions.end();) {\n      it->wait();\n      it = async_completions.erase(it);\n    }\n  }\n\n\n  /*!\n   *\n   * @tparam F the type of function, will be inferred from the arg\n   * @param f the actual callable object that will be invoked on notification,\n   * can be a lambda or bind_like object that a std::function accepts\n   * @return a tag (uint32_t) that should be supplied when removing th observer\n   */\n  template <typename F>\n  [[nodiscard]] observer_tag_t\n  addObserver(F&& f)\n  {\n    return mObservers.addCallback(std::forward<F>(f));\n  }\n\n  void\n  rmObserver(observer_tag_t tag)\n  {\n    mObservers.rmCallback(tag);\n  }\n\n  /*!\n   * Synchronously notify all the listeners of the changes, note that this will\n   * block the calling thread, so only meant to be called if it can be ensured\n   * that the callbacks would be really small to affect the calling thread\n   * @param args arguments to be provided for each callback\n   */\n\n  void\n  notifyChangeSync(Args... args)\n  {\n    auto callbacks = mObservers.getCallbacks();\n\n    for (auto callback : callbacks) {\n      if (auto shared_fn = callback.lock()) {\n        std::invoke(*shared_fn, args...);\n      }\n    }\n  }\n\n  /*!\n   * Asynchronously notify all the listeners of the changes, this job runs\n   * in the ObserverMgr Threadpool and hence doesn't block the calling thread\n   * @param args arguments to be provided for each callback\n   */\n  void\n  notifyChange(Args... args)\n  {\n    auto callbacks = mObservers.getCallbacks();\n\n    std::scoped_lock lock(async_completions_mtx);\n    for (auto callback : callbacks) {\n      if (auto shared_fn = callback.lock()) {\n        async_completions.emplace_back(mThreadPool.PushTask(std::make_shared <\n                                       std::packaged_task<void(void) >> (bindArgs(*shared_fn,\n                                           args...))));\n      }\n    }\n\n    // reap the finished completions every time!\n    async_completions.erase(std::remove_if(async_completions.begin(),\n                                           async_completions.end(),\n    [](std::future<void>& fut) {\n      return (fut.wait_for(std::chrono::seconds(0)) ==\n              std::future_status::ready);\n    }),\n    async_completions.end());\n  }\n\nprivate:\n  mutable eos::common::ThreadPool mThreadPool;\n  mutable std::vector<std::future<void>> async_completions;\n  mutable std::mutex async_completions_mtx;\n  SharedCallbackList<Args...> mObservers;\n};\n\n} // eos::common\n"
  },
  {
    "path": "common/Parallel.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Parallel.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   Parallel.hh\n *\n * @brief  Class providing a parallel for function using lambda functions/threads\n *\n *\n */\n\n#include <thread>\n#include <mutex>\n#include <vector>\n#include <cmath>\n#include <features.h>\n#ifndef __EOSCOMMON__PARALLEL__HH\n#define __EOSCOMMON__PARALLEL__HH\n\nEOSCOMMONNAMESPACE_BEGIN\n\n#if __GNUC_PREREQ(4,8) || defined(__clang__)\n\nclass Parallel\n{\n\npublic:\n\n  template<typename Index, typename Callable>\n  static void For(Index start, Index end, Callable func)\n  {\n    // Estimate number of threads in the pool\n    const static unsigned nb_threads_hint = std::thread::hardware_concurrency();\n    const static unsigned nb_threads = (nb_threads_hint == 0u ? 8u :\n                                        nb_threads_hint);\n    // Size of a slice for the range functions\n    Index n = end - start + 1;\n    Index slice = (Index) std::round(n / static_cast<double>(nb_threads));\n    slice = std::max(slice, Index(1));\n    // [Helper] Inner loop\n    auto launchRange = [&func](int k1, int k2) {\n      for (Index k = k1; k < k2; k++) {\n        func(k);\n      }\n    };\n    // Create pool and launch jobs\n    std::vector<std::thread> pool;\n    pool.reserve(nb_threads);\n    Index i1 = start;\n    Index i2 = std::min(start + slice, end);\n\n    for (unsigned i = 0; i + 1 < nb_threads && i1 < end; ++i) {\n      pool.emplace_back(launchRange, i1, i2);\n      i1 = i2;\n      i2 = std::min(i2 + slice, end);\n    }\n\n    if (i1 < end) {\n      pool.emplace_back(launchRange, i1, end);\n    }\n\n    // Wait for jobs to finish\n    for (std::thread& t : pool) {\n      if (t.joinable()) {\n        t.join();\n      }\n    }\n  }\n\n  // Serial version for easy comparison\n  template<typename Index, typename Callable>\n  static void SequentialFor(Index start, Index end, Callable func)\n  {\n    for (Index i = start; i < end; i++) {\n      func(i);\n    }\n  }\n};\n\n#endif\n\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/ParseUtils.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ParseUtils.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Parse utilities with proper error checking\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/SymKeys.hh\"\n#include <string>\n#include <limits>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Parse an int64 encoded in the given numerical base, return true if parsing\n//! was successful, false otherwise.\n//------------------------------------------------------------------------------\ninline bool ParseInt64(const std::string& str, int64_t& ret, int base = 10)\n{\n  char* endptr = NULL;\n  ret = std::strtoll(str.c_str(), &endptr, base);\n\n  if (endptr != str.c_str() + str.size() ||\n      ret == std::numeric_limits<long long>::min() ||\n      ret == std::numeric_limits<long long>::max()) {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n//! Parse an uint64 encoded in the given numerical base, return true if parsing\n//! was successful, false otherwise.\n//------------------------------------------------------------------------------\ninline bool ParseUInt64(const std::string& str, uint64_t& ret, int base = 10)\n{\n  char* endptr = NULL;\n  ret = std::strtoull(str.c_str(), &endptr, base);\n\n  if (endptr != str.c_str() + str.size() ||\n      ret == std::numeric_limits<unsigned long long>::min() ||\n      ret == std::numeric_limits<unsigned long long>::max()) {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n//! Parse a long long - behave exactly the same as old XrdMq \"GetLongLong\".\n//------------------------------------------------------------------------------\ninline long long ParseLongLong(const std::string& str)\n{\n  if (str.length()) {\n    errno = 0;\n    long long ret = strtoll(str.c_str(), 0, 10);\n\n    if (!errno) {\n      return ret;\n    }\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n//! Parse a long long - behave exactly the same as old XrdMq \"GetDouble\".\n//------------------------------------------------------------------------------\ninline double ParseDouble(const std::string& str)\n{\n  if (str.length()) {\n    return atof(str.c_str());\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n//! Parse hostname and port from input string. If no port value present then\n//! default to 1094.\n//!\n//! @param input input string e.g. hostname.cern.ch:port\n//! @param host parsed hostname\n//! @parma port parsed port\n//!\n//! @return true if parsing succeeded, otherwise false\n//------------------------------------------------------------------------------\ninline bool ParseHostNamePort(const std::string& input, std::string& host,\n                              int& port)\n{\n  if (input.empty()) {\n    return false;\n  }\n\n  size_t pos = input.find(':');\n\n  if ((pos == std::string::npos) || (pos == input.length())) {\n    host = input;\n    port = 1094;\n  } else {\n    host = input.substr(0, pos);\n    int64_t ret = 0ll;\n\n    if (!ParseInt64(input.substr(pos + 1), ret)) {\n      return false;\n    }\n\n    port = ret;\n  }\n\n  return true;\n}\n\n\n//------------------------------------------------------------------------------\n//! Check that the given string is a valid hostname or IP specification\n//!\n//! @param input hostname host.cern.ch or IP address\n//------------------------------------------------------------------------------\ninline bool ValidHostnameOrIP(const std::string& input)\n{\n  for (const auto& c : input) {\n    if (!std::isalnum(c) && (c != '.') && (c != '-') && (c != ':')) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/PasswordHandler.hh",
    "content": "// ----------------------------------------------------------------------\n// File: PasswordFileReader.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_COMMON_PASSWORD_HANDLER_HH\n#define EOS_COMMON_PASSWORD_HANDLER_HH\n\n#include <stdio.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include \"common/Namespace.hh\"\n#include \"common/Logging.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Helper class to perform common operations related to passwords.\n//------------------------------------------------------------------------------\nclass PasswordHandler {\npublic:\n\n  //----------------------------------------------------------------------------\n  // Check if file permissions are secure.\n  //----------------------------------------------------------------------------\n  static bool areFilePermissionsSecure(mode_t mode)\n  {\n    if ((mode & 0077) != 0) {\n      // Should disallow access to other users/groups\n      return false;\n    }\n\n    if ((mode & 0700) != 0400) {\n      // Just read access for user\n      return false;\n    }\n\n    return true;\n  }\n\n  //----------------------------------------------------------------------------\n  // Right trim password, remove whitespace\n  //----------------------------------------------------------------------------\n  static void rightTrimWhitespace(std::string &src)\n  {\n    src.erase(src.find_last_not_of(\" \\t\\n\\r\\f\\v\") + 1);\n  }\n\n  //----------------------------------------------------------------------------\n  // Read a password file, while taking the following into account:\n  // - Permissions must be 400 - refuse to do anything otherwise.\n  // - Ending newlines are discarded.\n  //----------------------------------------------------------------------------\n  static bool readPasswordFile(const std::string &path, std::string &contents) {\n    FILE *in = fopen(path.c_str(), \"rb\");\n    if(!in) {\n      eos_static_crit(\"Could not read pasword file: %s\", path.c_str());\n      return false;\n    }\n\n    // Ensure file permissions are 400.\n    struct stat sb;\n    if(fstat(fileno(in), &sb) != 0) {\n      fclose(in);\n      eos_static_crit(\"Could not fstat %s after opening (should never happen?!)\", path.c_str());\n      return false;\n    }\n\n    if(!areFilePermissionsSecure(sb.st_mode)) {\n      eos_static_crit(\"Refusing to read %s, bad file permissions, should be 0400.\", path.c_str());\n      fclose(in);\n      return false;\n    }\n\n    // Do actual read...\n    std::ostringstream ss;\n\n    const int BUFFER_SIZE = 1024;\n    char buffer[BUFFER_SIZE];\n\n    bool retvalue = true;\n    while(true) {\n      size_t bytesRead = fread(buffer, 1, BUFFER_SIZE, in);\n\n      if(bytesRead > 0) {\n        ss.write(buffer, bytesRead);\n      }\n\n      // end of file\n      if(bytesRead != BUFFER_SIZE) {\n        retvalue = feof(in);\n        break;\n      }\n    }\n\n    fclose(in);\n    contents = ss.str();\n\n    rightTrimWhitespace(contents);\n    return retvalue;\n  }\n\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/Path.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Path.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSCOMMON_PATH__\n#define __EOSCOMMON_PATH__\n\n#include \"common/Namespace.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <vector>\n#include <string>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <errno.h>\n#include <uuid/uuid.h>\n#include <string.h>\n#include <cstdint>\n\n#define EOS_COMMON_PATH_VERSION_PREFIX \"/.sys.v#.\"\n#define EOS_COMMON_PATH_VERSION_FILE_PREFIX \".sys.v#.\"\n#define EOS_COMMON_PATH_ATOMIC_FILE_PREFIX \".sys.a#.\"\n#define EOS_COMMON_PATH_ATOMIC_FILE_VERSION_PREFIX \".sys.a#.v#\"\n#define EOS_COMMON_PATH_BACKUP_FILE_PREFIX \".sys.b#.\"\n#define EOS_COMMON_PATH_SQUASH_SUFFIX \".sqsh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class providing some comfortable functions on path names\n//------------------------------------------------------------------------------\nclass Path\n{\npublic:\n  constexpr static uint32_t MAX_LEVELS = 255;\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  Path(const char* path = \"\", bool multipath=false)\n  {\n    Init(path, multipath);\n  }\n\n  Path(std::string path, bool multipath=false)\n  {\n    Init(path.c_str(), multipath);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~Path() = default;\n\n  //----------------------------------------------------------------------------\n  //! Assignment\n  //----------------------------------------------------------------------------\n  Path& operator=(std::string other)\n  {\n    this->Init(other.c_str());\n    return *this;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return basename/filename\n  //----------------------------------------------------------------------------\n  const char*\n  GetName()\n  {\n    return lastPath.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return if file is an atomic filename\n  //----------------------------------------------------------------------------\n  bool\n  isAtomicFile()\n  {\n    return lastPath.beginswith(EOS_COMMON_PATH_ATOMIC_FILE_PREFIX);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return if path belongs to versioning\n  //----------------------------------------------------------------------------\n  bool\n  isVersionPath()\n  {\n    return (fullPath.find(EOS_COMMON_PATH_VERSION_FILE_PREFIX) != STR_NPOS);\n  }\n\n\n  //----------------------------------------------------------------------------\n  //! Return if file is a sqaush package file\n  //----------------------------------------------------------------------------\n  bool\n  isSquashFile()\n  {\n    return (lastPath.beginswith(\".\") &&\n            lastPath.endswith(EOS_COMMON_PATH_SQUASH_SUFFIX));\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return full path\n  //----------------------------------------------------------------------------\n  const char*\n  GetPath()\n  {\n    return fullPath.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return path of the parent directory\n  //----------------------------------------------------------------------------\n  const char*\n  GetParentPath()\n  {\n    return parentPath.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return constracted path replacing all '/' with '::'\n  //----------------------------------------------------------------------------\n  std::string\n  GetContractedPath()\n  {\n    XrdOucString contractedpath = GetPath();\n\n    while (contractedpath.replace(\"/\", \"..\")) {}\n\n    return contractedpath.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return version directory path\n  //----------------------------------------------------------------------------\n  const char*\n  GetVersionDirectory()\n  {\n    versionDir = GetParentPath();\n    versionDir += EOS_COMMON_PATH_VERSION_PREFIX;\n    versionDir += GetName();\n    versionDir += \"/\";\n\n    while (versionDir.replace(\"//\", \"/\")) {\n    }\n\n    return versionDir.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return full path\n  //----------------------------------------------------------------------------\n  XrdOucString&\n  GetFullPath()\n  {\n    return fullPath;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return atomic path\n  //----------------------------------------------------------------------------\n  const char*\n  GetAtomicPath(bool versioning, XrdOucString externuuid = \"\")\n  {\n    if (atomicPath.length()) {\n      return atomicPath.c_str();\n    } else {\n      return MakeAtomicPath(versioning, externuuid);\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return a unique atomic version of that path\n  //----------------------------------------------------------------------------\n  const char* MakeAtomicPath(bool versioning, XrdOucString externuuid = \"\")\n  {\n    // create from <dirname>/<basename> => <dirname>/<ATOMIC|VERSION_PREFIX><basename>.<uuid>\n    char suuid[40];\n    uuid_t uuid;\n    uuid_generate_time(uuid);\n    uuid_unparse(uuid, suuid);\n\n    // skip modification of already atomic paths\n    if (!lastPath.beginswith(EOS_COMMON_PATH_ATOMIC_FILE_PREFIX)) {\n      atomicPath = GetParentPath();\n\n      if (!versioning) {\n        atomicPath += EOS_COMMON_PATH_ATOMIC_FILE_PREFIX;\n      } else {\n        atomicPath += EOS_COMMON_PATH_ATOMIC_FILE_VERSION_PREFIX;\n      }\n\n      atomicPath += GetName();\n      atomicPath += \".\";\n\n      // for chunk paths we have to use the same UUID for all chunks\n      if (!externuuid.length()) {\n        atomicPath += suuid;\n      } else {\n        atomicPath += externuuid;\n      }\n    } else {\n      atomicPath = GetPath();\n    }\n\n    return atomicPath.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Decode an atomic path\n  //----------------------------------------------------------------------------\n  const char* DecodeAtomicPath(bool& isVersioning)\n  {\n    // create from <dirname>/<ATOMIC|VERSION_PREFIX> <basename>.<uuid>\n    //  => <dirname>/<basename>\n    if ((lastPath.beginswith(EOS_COMMON_PATH_ATOMIC_FILE_PREFIX))  &&\n        (lastPath.length() > 37) &&\n        (lastPath[lastPath.length() - 37] == '.')) {\n      atomicPath = fullPath;\n      lastPath.erase(lastPath.length() - 37);\n\n      if (lastPath.beginswith(EOS_COMMON_PATH_ATOMIC_FILE_VERSION_PREFIX)) {\n        lastPath.erase(0, strlen(EOS_COMMON_PATH_ATOMIC_FILE_VERSION_PREFIX));\n        isVersioning = true;\n      } else {\n        lastPath.erase(0, strlen(EOS_COMMON_PATH_ATOMIC_FILE_PREFIX));\n        isVersioning = false;\n      }\n\n      fullPath = parentPath + lastPath;\n    }\n\n    return fullPath.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return sub path with depth i (0 is / 1 is /eos/ aso....)\n  //----------------------------------------------------------------------------\n  const char*\n  GetSubPath(unsigned int i)\n  {\n    if (i < subPath.size()) {\n      return subPath[i].c_str();\n    } else {\n      return 0;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return number of sub paths stored\n  //----------------------------------------------------------------------------\n  unsigned int\n  GetSubPathSize()\n  {\n    return subPath.size();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Initialization\n  //----------------------------------------------------------------------------\n  void\n  Init(const char* path, bool multipath=false)\n  {\n    fullPath = path;\n\n    if (multipath) {\n      while (fullPath.replace(\"://:\",\":/@/@/:\"));\n    }\n\n    while (fullPath.replace(\"//\", \"/\")) {}\n\n    if (multipath) {\n      while (fullPath.replace(\":/@/@/:\", \"://:\"));\n    }\n\n    parentPath = \"/\";\n    lastPath = \"\";\n\n    if ((fullPath == \"/\") ||\n        (fullPath == \"/.\") ||\n        (fullPath == \"/..\") ||\n        (fullPath == \"/./\") ||\n        (fullPath == \"/../\")) {\n      fullPath = \"/\";\n      return;\n    }\n\n    if (fullPath.endswith('/')) {\n      fullPath.erase(fullPath.length() - 1);\n    }\n\n    // remove  /.$\n    if (fullPath.endswith(\"/.\")) {\n      fullPath.erase(fullPath.length() - 2);\n    }\n\n    // recompute /..$\n    if (fullPath.endswith(\"/..\")) {\n      fullPath += \"/\";\n    }\n\n    if (!fullPath.beginswith(\"/\")) {\n      lastPath = fullPath;\n      return;\n    }\n\n    int bppos;\n\n    // convert /./\n    while ((bppos = fullPath.find(\"/./\")) != STR_NPOS) {\n      fullPath.erase(bppos, 2);\n    }\n\n    // convert /..\n    while ((bppos = fullPath.find(\"/../\")) != STR_NPOS) {\n      // Erase beginning /../\n      if (bppos == 0) {\n        fullPath.erase(0, 3);\n        continue;\n      }\n\n      int spos = fullPath.rfind(\"/\", bppos - 1);\n\n      if (spos != STR_NPOS) {\n        fullPath.erase(bppos, 4);\n        fullPath.erase(spos + 1, bppos - spos - 1);\n      } else {\n        // Should not reach this as there will be\n        // at least the starting '/'\n        fullPath = \"/\";\n        break;\n      }\n    }\n\n    if (!fullPath.length()) {\n      fullPath = \"/\";\n    }\n\n    int lastpos = 0;\n    int pos = 0;\n\n    do {\n      pos = fullPath.find(\"/\", pos);\n      std::string subpath;\n\n      if (pos != STR_NPOS) {\n        subpath.assign(fullPath.c_str(), pos + 1);\n        subPath.push_back(subpath);\n        lastpos = pos;\n        pos++;\n      }\n    } while (pos != STR_NPOS);\n\n    parentPath.assign(fullPath, 0, lastpos);\n    lastPath.assign(fullPath, lastpos + 1);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Convenience function to auto-create all needed parent paths for this\n  //! path object with mode\n  //----------------------------------------------------------------------------\n  bool\n  MakeParentPath(mode_t mode)\n  {\n    int retc = 0;\n    struct stat buf;\n\n    if (stat(GetParentPath(), &buf)) {\n      for (int i = GetSubPathSize() - 1; i >= 0; i--) {\n        // go backwards until the directory exists\n        if (!stat(GetSubPath(i), &buf)) {\n          // this exists\n          for (int j = i + 1; j < (int) GetSubPathSize(); j++) {\n            retc |= (mkdir(GetSubPath(j), mode) ? ((errno == EEXIST) ? 0 : -1) : 0);\n          }\n\n          break;\n        }\n      }\n    }\n\n    if (retc) {\n      return false;\n    }\n\n    return true;\n  }\n\n  bool Globbing()\n  {\n    std::string name = lastPath.c_str();\n    size_t index = 0;\n\n    while ((index = name.find('*', index)) != std::string::npos) {\n      if ((index == 0) || ((index > 0)  && name[index - 1] != '\\\\')) {\n        return true;\n      }\n\n      index++;\n    }\n\n    index = 0;\n\n    while ((index = name.find('?', index)) != std::string::npos) {\n      if ((index == 0) || ((index > 0)  && name[index - 1] != '\\\\')) {\n        return true;\n      }\n\n      index++;\n    }\n\n    index = 0;\n\n    while ((index = name.find('[', index)) != std::string::npos) {\n      if ((index == 0) || ((index > 0)  && name[index - 1] != '\\\\')) {\n        return true;\n      }\n\n      index++;\n    }\n\n    return false;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if path is refering to a version file/directory\n  //!\n  //! @param path input path\n  //!\n  //! @return true if path refers to a version entry, otherwise false\n  //----------------------------------------------------------------------------\n  static bool IsVersion(const std::string& path)\n  {\n    return (path.find(EOS_COMMON_PATH_VERSION_PREFIX) != std::string::npos);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if path is refering to an atomic file (simple or version atomic)\n  //!\n  //! @param path input path\n  //!\n  //! @return true if path refers to an atomic entry, otherwise false\n  //----------------------------------------------------------------------------\n  static bool IsAtomic(const std::string& path)\n  {\n    return (path.find(EOS_COMMON_PATH_ATOMIC_FILE_PREFIX) != std::string::npos);\n  }\n\n  // get the shared prefix for two paths\n  static std::string Overlap(const char* a, const char* b)\n  {\n    eos::common::Path apath(a);\n    eos::common::Path bpath(b);\n    std::string ol = \"/\";\n\n    for (size_t i = 0 ; i < apath.GetSubPathSize(); ++i) {\n      std::string ta = apath.GetSubPath(i);\n      const char* pb = bpath.GetSubPath(i);\n      std::string tb = pb ? pb : \"\";\n\n      if (ta == tb) {\n        ol = ta;\n      } else {\n        return ol;\n      }\n    }\n\n    if (std::string(apath.GetPath()) == std::string(bpath.GetPath())) {\n      return apath.GetPath();\n    }\n\n    return ol;\n  }\n\nprotected:\n  XrdOucString fullPath; //< the full path stored\n  XrdOucString parentPath; //< path of the parent directory\n  XrdOucString lastPath; //< the base name/file name\n  //! temporary version of a path e.g. basename => .basename.<uuid>\n  XrdOucString atomicPath;\n  XrdOucString versionDir; //< directory name storing versions for a file path\n  std::vector<std::string> subPath; //< a vector with all partial sub-path\n};\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/PthreadRWMutex.cc",
    "content": "//------------------------------------------------------------------------------\n// File: PthreadRWMutex.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/PthreadRWMutex.hh\"\n#include \"common/Timing.hh\"\n#include <stdio.h>\n#include <exception>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nPthreadRWMutex::PthreadRWMutex(bool prefer_readers)\n{\n  int retc = 0;\n  pthread_rwlockattr_init(&mAttr);\n#ifndef __APPLE__\n\n  if (prefer_readers) {\n    // Readers go ahead of writers and are reentrant\n    if ((retc = pthread_rwlockattr_setkind_np(&mAttr,\n                PTHREAD_RWLOCK_PREFER_WRITER_NP))) {\n      fprintf(stderr, \"%s Failed to set readers priority: %s\\n\", __FUNCTION__,\n              strerror(retc));\n      std::terminate();\n    }\n  } else {\n    // Readers don't go ahead of writers!\n    if ((retc = pthread_rwlockattr_setkind_np(&mAttr,\n                PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP))) {\n      fprintf(stderr, \"%s Failed to set writers priority: %s\\n\", __FUNCTION__,\n              strerror(retc));\n      std::terminate();\n    }\n  }\n\n#endif\n\n  if ((retc = pthread_rwlockattr_setpshared(&mAttr, PTHREAD_PROCESS_SHARED))) {\n    fprintf(stderr, \"%s Failed to set process shared mutex: %s\\n\",\n            __FUNCTION__, strerror(retc));\n    std::terminate();\n  }\n\n  if ((retc = pthread_rwlock_init(&mMutex, &mAttr))) {\n    fprintf(stderr, \"%s Failed to initialize mutex: %s\\n\",\n            __FUNCTION__, strerror(retc));\n    std::terminate();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Lock for read\n//------------------------------------------------------------------------------\nint\nPthreadRWMutex::LockRead()\n{\n  return pthread_rwlock_rdlock(&mMutex);\n}\n\n//----------------------------------------------------------------------------\n// Try lock for read (shared)\n//----------------------------------------------------------------------------\nint\nPthreadRWMutex::TryLockRead()\n{\n  return pthread_rwlock_tryrdlock(&mMutex);\n}\n\n//------------------------------------------------------------------------------\n// Try to read lock the mutex within the timeout\n//------------------------------------------------------------------------------\nint\nPthreadRWMutex::TimedRdLock(uint64_t timeout_ns)\n{\n  int retc = 0;\n  struct timespec timeout{};\n  _clock_gettime(CLOCK_REALTIME, &timeout);\n\n  if (timeout_ns) {\n    if (timeout_ns > 1e9) {\n      timeout.tv_sec += (timeout_ns / 1e9);\n    }\n\n    timeout.tv_nsec += (timeout_ns % (unsigned long long)1e9);\n  }\n\n#ifdef __APPLE__\n  // Mac does not support timed mutexes\n  retc = pthread_rwlock_rdlock(&mMutex);\n#else\n  retc = pthread_rwlock_timedrdlock(&mMutex, &timeout);\n#endif\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Unlock a read lock\n//------------------------------------------------------------------------------\nint\nPthreadRWMutex::UnLockRead()\n{\n  return pthread_rwlock_unlock(&mMutex);\n}\n\n//------------------------------------------------------------------------------\n// Lock for write\n//------------------------------------------------------------------------------\nint\nPthreadRWMutex::LockWrite()\n{\n  return pthread_rwlock_wrlock(&mMutex);\n}\n\n//----------------------------------------------------------------------------\n// Try lock for write (exclusive)\n//----------------------------------------------------------------------------\nint\nPthreadRWMutex::TryLockWrite()\n{\n  return pthread_rwlock_trywrlock(&mMutex);\n}\n\n//------------------------------------------------------------------------------\n// Unlock a write lock\n//------------------------------------------------------------------------------\nint\nPthreadRWMutex::UnLockWrite()\n{\n  return pthread_rwlock_unlock(&mMutex);\n}\n\n//------------------------------------------------------------------------------\n// Try to write lock the mutex within the timeout\n//------------------------------------------------------------------------------\nint\nPthreadRWMutex::TimedWrLock(uint64_t timeout_ns)\n{\n  int retc = 0;\n  struct timespec timeout{};\n  _clock_gettime(CLOCK_REALTIME, &timeout);\n\n  if (timeout_ns) {\n    if (timeout_ns > 1e9) {\n      timeout.tv_sec += (timeout_ns / 1e9);\n    }\n\n    timeout.tv_nsec += (timeout_ns % (unsigned long long)1e9);\n  }\n\n#ifdef __APPLE__\n  // Mac does not support timed mutexes\n  retc = pthread_rwlock_wrlock(&mMutex);\n#else\n  retc = pthread_rwlock_timedwrlock(&mMutex, &timeout);\n#endif\n  return retc;\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/PthreadRWMutex.hh",
    "content": "//------------------------------------------------------------------------------\n// File: PthreadRWMutex.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/IRWMutex.hh\"\n#include <XrdSys/XrdSysPthread.hh>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class PthreadRWMutex\n//------------------------------------------------------------------------------\nclass PthreadRWMutex: public IRWMutex\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  // ---------------------------------------------------------------------------\n  PthreadRWMutex(bool prefer_readers = false);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~PthreadRWMutex() = default;\n\n  // //----------------------------------------------------------------------------\n  // //! Copy constructor\n  // //----------------------------------------------------------------------------\n  // PthreadRWMutex(const PthreadRWMutex&) = delete;\n\n  // //----------------------------------------------------------------------------\n  // //! Copy assignment operator\n  // //----------------------------------------------------------------------------\n  // PthreadRWMutex& operator=(const PthreadRWMutex&) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Lock for read\n  //----------------------------------------------------------------------------\n  int LockRead() override;\n\n  //----------------------------------------------------------------------------\n  //! Try lock for read (shared)\n  //!\n  //! @return 0 if lock acquired successfully, otherwise non-zero\n  //----------------------------------------------------------------------------\n  int TryLockRead() override;\n\n  //----------------------------------------------------------------------------\n  //! Try to read lock the mutex within the timeout\n  //!\n  //! @param timeout_ns nano seconds timeout\n  //!\n  //! @return 0 if successful, otherwise error number\n  //----------------------------------------------------------------------------\n  int TimedRdLock(uint64_t timeout_ns) override;\n\n  //----------------------------------------------------------------------------\n  //! Unlock a read lock\n  //----------------------------------------------------------------------------\n  int UnLockRead() override;\n\n  //----------------------------------------------------------------------------\n  //! Lock for write\n  //----------------------------------------------------------------------------\n  int LockWrite() override;\n\n  //----------------------------------------------------------------------------\n  //! Try lock for write (exclusive)\n  //!\n  //! @return 0 if lock acquired successfully, otherwise non-zero\n  //----------------------------------------------------------------------------\n  int TryLockWrite() override;\n\n  //----------------------------------------------------------------------------\n  //! Unlock a write lock\n  //----------------------------------------------------------------------------\n  int UnLockWrite() override;\n\n  //----------------------------------------------------------------------------\n  //! Try to write lock the mutex within the timeout\n  //!\n  //! @param timeout_ns nano seconds timeout\n  //!\n  //! @return 0 if successful, otherwise error number\n  //----------------------------------------------------------------------------\n  int TimedWrLock(uint64_t timeout_ns) override;\n\nprivate:\n  pthread_rwlock_t mMutex;\n  pthread_rwlockattr_t mAttr;\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/QuarkDBHealthParser.hh",
    "content": ""
  },
  {
    "path": "common/RWMutex.cc",
    "content": "//------------------------------------------------------------------------------\n// File: RWMutex.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StacktraceHere.hh\"\n#include \"common/Timing.hh\"\n#include \"common/Logging.hh\"\n#include \"common/RWMutex.hh\"\n#include \"common/PthreadRWMutex.hh\"\n#include \"common/SharedMutex.hh\"\n#include <sys/syscall.h>\n#include <sstream>\n#include <exception>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n#ifdef EOS_INSTRUMENTED_RWMUTEX\nstd::atomic<uint64_t> RWMutex::mRdCumulatedWait_static {0};\nstd::atomic<uint64_t> RWMutex::mWrCumulatedWait_static {0};\nstd::atomic<uint64_t> RWMutex::mRdLockCounterSample_static {0};\nstd::atomic<uint64_t> RWMutex::mWrLockCounterSample_static {0};\nstd::atomic<uint64_t> RWMutex::mRdMaxWait_static {0};\nstd::atomic<uint64_t> RWMutex::mWrMaxWait_static {0};\nstd::atomic<uint64_t> RWMutex::mRdMinWait_static {std::numeric_limits<uint64_t>::max()};\nstd::atomic<uint64_t> RWMutex::mWrMinWait_static {std::numeric_limits<uint64_t>::max()};\nsize_t RWMutex::timingCompensation = 0;\nsize_t RWMutex::timingLatency = 0;\nsize_t RWMutex::orderCheckingLatency = 0;\nsize_t RWMutex::lockUnlockDuration = 0;\nint RWMutex::sSamplingModulo = (int)(0.01 * RAND_MAX);\nbool RWMutex::staticInitialized = false;\nbool RWMutex::sEnableGlobalTiming = false;\nbool RWMutex::sEnableGlobalDeadlockCheck = false;\nbool RWMutex::sEnableGlobalOrderCheck = false;\nRWMutex::rules_t* RWMutex::rules_static = NULL;\nstd::map<unsigned char, std::string>* RWMutex::ruleIndex2Name_static =\n  NULL;\nstd::map<std::string, unsigned char>* RWMutex::ruleName2Index_static =\n  NULL;\nthread_local bool* RWMutex::orderCheckReset_staticthread = NULL;\nthread_local unsigned long\nRWMutex::ordermask_staticthread[EOS_RWMUTEX_ORDER_NRULES];\nstd::map<pthread_t, bool>* RWMutex::threadOrderCheckResetFlags_static =\n  NULL;\npthread_rwlock_t RWMutex::mOrderChkLock;\n\nstd::mutex RWMutex::sOpMutex;\nRWMutex::MapMutexNameT RWMutex::sMtxNameMap;\nRWMutex::MapMutexOpT RWMutex::sTidMtxOpMap;\n\nconst char* RWMutex::LOCK_STATE[] = {\"N\", \"wLR\", \"wULR\", \"LR\", \"wLW\", \"wULW\", \"LW\", NULL};\n\n#define EOS_RWMUTEX_CHECKORDER_LOCK if(sEnableGlobalOrderCheck) CheckAndLockOrder();\n#define EOS_RWMUTEX_CHECKORDER_UNLOCK if(sEnableGlobalOrderCheck) CheckAndUnlockOrder();\n\n#define EOS_RWMUTEX_TIMER_START                                             \\\n  uint64_t l1stamp = Timing::GetNowInNs();                                  \\\n  bool issampled = false; uint64_t tstamp = 0;                              \\\n  if (mEnableTiming || sEnableGlobalTiming) {                               \\\n    issampled = mEnableSampling ? (!((++mCounter)%mSamplingModulo)) : true; \\\n    if (issampled) tstamp = Timing::GetNowInNs();                           \\\n  }\n\n// what = mRd or mWr\n#define EOS_RWMUTEX_TIMER_STOP_AND_UPDATE(what)                                \\\n  ++(what##LockCounter);                                                       \\\n  if(issampled) {                                                              \\\n    tstamp = Timing::GetNowInNs() - tstamp;                                    \\\n    if(mEnableTiming) {                                                        \\\n      ++(what##LockCounterSample);                                             \\\n      what##CumulatedWait += tstamp;                                           \\\n      bool needloop=true;                                                      \\\n      do {size_t mymax = what##MaxWait.load();                                 \\\n        if (tstamp > mymax)                                                    \\\n          needloop = !(what##MaxWait).compare_exchange_strong(mymax, tstamp);  \\\n        else needloop = false;                                                 \\\n      } while(needloop);                                                       \\\n      do {size_t mymin = what##MinWait.load();                                 \\\n        if (tstamp < mymin)                                                    \\\n          needloop = !(what##MinWait).compare_exchange_strong(mymin, tstamp);  \\\n        else needloop = false;                                                 \\\n      } while(needloop);                                                       \\\n    }                                                                          \\\n    if(sEnableGlobalTiming) {                                                  \\\n      ++(what##LockCounterSample_static);                                      \\\n      what##CumulatedWait_static += tstamp;                                    \\\n      bool needloop = true;                                                    \\\n      do {size_t mymax = what##MaxWait_static.load();                          \\\n        if (tstamp > mymax)                                                    \\\n          needloop = !(what##MaxWait_static).compare_exchange_strong(mymax, tstamp); \\\n        else needloop = false;                                                 \\\n       } while(needloop);                                                      \\\n      do {size_t mymin = what##MinWait_static.load();                          \\\n        if (tstamp < mymin)                                                    \\\n          needloop = !(what##MinWait_static).compare_exchange_strong(mymin, tstamp); \\\n        else needloop = false;                                                 \\\n      } while(needloop);                                                       \\\n    }                                                                          \\\n  }                        \\\n  uint64_t l2stamp = Timing::GetNowInNs();               \\\n  (what##LockLeadTime) += (l2stamp-l1stamp);\n\n#else\n#define EOS_RWMUTEX_CHECKORDER_LOCK\n#define EOS_RWMUTEX_CHECKORDER_UNLOCK\n#define EOS_RWMUTEX_TIMER_START\n#define EOS_RWMUTEX_TIMER_STOP_AND_UPDATE(what) ++(what##LockCounter);\n#endif\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nRWMutex::RWMutex(bool prefer_rd):\n  mBlocking(false), mMutexImpl(nullptr), mRdLockCounter(0), mWrLockCounter(0),\n  mPreferRd(prefer_rd), mName(\"unnamed\")\n{\n  // Try to get write lock in 5 seconds, then release quickly and retry\n  wlocktime.tv_sec = 5;\n  wlocktime.tv_nsec = 0;\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n  mSamplingModulo = 300;\n\n  if (!staticInitialized) {\n    staticInitialized = true;\n    InitializeClass();\n  }\n\n  mCounter = 0;\n  mEnableTiming = false;\n  mEnableSampling = false;\n  mEnableDeadlockCheck = false;\n  mTransientDeadlockCheck = false;\n  nrules = 0;\n  mCollectionMutex = PTHREAD_MUTEX_INITIALIZER;\n  ResetTimingStatistics();\n#endif\n\n  if (getenv(\"EOS_USE_PTHREAD_MUTEX\")) {\n    mMutexImpl = new PthreadRWMutex(prefer_rd);\n  } else {\n    mMutexImpl = new SharedMutex();\n  }\n\n  mBlockedForInterval = 10000;\n  mBlockedStackTracing = false;\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nRWMutex::~RWMutex()\n{\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n  pthread_rwlock_rdlock(&mOrderChkLock);\n  std::map<std::string, std::vector<RWMutex*> >* rules = NULL;\n\n  for (auto rit = rules_static->begin(); rit != rules_static->end(); rit++) {\n    // for each rule\n    for (auto it = rit->second.begin(); it != rit->second.end(); it++) {\n      // for each RWMutex involved in that rule\n      if ((*it) == dynamic_cast<RWMutex*>(this)) {\n        if (rules == NULL) {\n          rules = new std::map<std::string, std::vector<RWMutex*> >(*rules_static);\n        }\n\n        rules->erase(rit->first); // remove the rule if it contains this\n      }\n    }\n  }\n\n  pthread_rwlock_unlock(&mOrderChkLock);\n\n  if (rules != NULL) {\n    ResetOrderRule();\n\n    // Inserts the remaining rules\n    for (auto it = rules->begin(); it != rules->end(); it++) {\n      AddOrderRule(it->first, it->second);\n    }\n\n    delete rules;\n  }\n\n#endif\n  delete mMutexImpl;\n}\n\n\n//------------------------------------------------------------------------------\n// Move assignment operator\n//------------------------------------------------------------------------------\nRWMutex&\nRWMutex::operator=(RWMutex&& other) noexcept\n{\n  if (this != &other) {\n    this->mMutexImpl = other.mMutexImpl;\n    other.mMutexImpl = nullptr;\n    this->mBlocking = other.mBlocking;\n  }\n\n  return *this;\n}\n\n//------------------------------------------------------------------------------\n// Move constructor\n//------------------------------------------------------------------------------\nRWMutex::RWMutex(RWMutex&& other) noexcept\n{\n  *this = std::move(other);\n}\n\n\n//------------------------------------------------------------------------------\n// Try to read lock the mutex within the timeout value\n//------------------------------------------------------------------------------\nbool\nRWMutex::TimedRdLock(uint64_t timeout_ns)\n{\n  EOS_RWMUTEX_CHECKORDER_LOCK;\n  EOS_RWMUTEX_TIMER_START;\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n\n  if (sEnableGlobalDeadlockCheck) {\n    mTransientDeadlockCheck = true;\n  }\n\n  if (mEnableDeadlockCheck || mTransientDeadlockCheck) {\n    EnterCheckDeadlock(true);\n  }\n\n#endif\n  int retc = mMutexImpl->TimedRdLock(timeout_ns);\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n\n  if (retc && (mEnableDeadlockCheck || mTransientDeadlockCheck)) {\n    ExitCheckDeadlock(true);\n  }\n\n#endif\n  EOS_RWMUTEX_TIMER_STOP_AND_UPDATE(mRd);\n\n  if (retc) {\n    EOS_RWMUTEX_CHECKORDER_UNLOCK;\n  }\n\n  return (retc == 0);\n}\n\n//------------------------------------------------------------------------------\n// Lock for read\n//------------------------------------------------------------------------------\nvoid\nRWMutex::LockRead()\n{\n  EOS_RWMUTEX_CHECKORDER_LOCK;\n  EOS_RWMUTEX_TIMER_START;\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n\n  if (sEnableGlobalDeadlockCheck) {\n    mTransientDeadlockCheck = true;\n  }\n\n  if (mEnableDeadlockCheck || mTransientDeadlockCheck) {\n    EnterCheckDeadlock(true);\n  }\n\n#endif\n  int retc = 0;\n\n  if ((retc = mMutexImpl->LockRead())) {\n    fprintf(stderr, \"%s Failed to read-lock: %s\\n\", __FUNCTION__,\n            strerror(retc));\n    std::terminate();\n  }\n\n  EOS_RWMUTEX_TIMER_STOP_AND_UPDATE(mRd);\n}\n\n//----------------------------------------------------------------------------\n// Try lock for read (shared)\n//----------------------------------------------------------------------------\nint\nRWMutex::TryLockRead()\n{\n  return mMutexImpl->TryLockRead();\n}\n\n//------------------------------------------------------------------------------\n// Unlock a read lock\n//------------------------------------------------------------------------------\nvoid\nRWMutex::UnLockRead()\n{\n  EOS_RWMUTEX_CHECKORDER_UNLOCK;\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n\n  if (mEnableDeadlockCheck || mTransientDeadlockCheck) {\n    ExitCheckDeadlock(true);\n  }\n\n#endif\n  int retc = 0;\n\n  if ((retc = mMutexImpl->UnLockRead())) {\n    fprintf(stderr, \"%s Failed to read-unlock: %s\\n\", __FUNCTION__,\n            strerror(retc));\n    std::terminate();\n  }\n\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n\n  if (!sEnableGlobalDeadlockCheck) {\n    mTransientDeadlockCheck = false;\n  }\n\n  if (!mEnableDeadlockCheck && !mTransientDeadlockCheck) {\n    DropDeadlockCheck();\n  }\n\n#endif\n}\n\n//------------------------------------------------------------------------------\n// Lock for write\n//------------------------------------------------------------------------------\nvoid\nRWMutex::LockWrite()\n{\n  EOS_RWMUTEX_CHECKORDER_LOCK;\n  EOS_RWMUTEX_TIMER_START;\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n\n  if (sEnableGlobalDeadlockCheck) {\n    mTransientDeadlockCheck = true;\n  }\n\n  if (mEnableDeadlockCheck || mTransientDeadlockCheck) {\n    EnterCheckDeadlock(false);\n  }\n\n#endif\n  int retc = 0;\n\n  if (mBlocking) {\n    // A blocking mutex is just a normal lock for write\n    if ((retc = mMutexImpl->LockWrite())) {\n      fprintf(stderr, \"%s Failed to write-lock: %s\\n\", __FUNCTION__,\n              strerror(retc));\n      std::terminate();\n    }\n  } else {\n#ifdef __APPLE__\n\n    // Mac does not support timed mutexes\n    if ((retc = mMutexImpl->LockWrite())) {\n      fprintf(stderr, \"%s Failed to write-lock: %s\\n\", __FUNCTION__,\n              strerror(retc));\n      std::terminate();\n    }\n\n#else\n\n    // A non-blocking mutex tries for few seconds to write lock, then releases.\n    // It has the side effect, that it allows dead locked readers to jump ahead\n    // the lock queue.\n    while (true) {\n      uint64_t timeout_ns = wlocktime.tv_nsec + wlocktime.tv_sec * 1e9;\n      int rc = mMutexImpl->TimedWrLock(timeout_ns);\n\n      if (rc) {\n        if (rc != ETIMEDOUT) {\n          fprintf(stderr, \"=== WRITE LOCK EXCEPTION == TID=%llu OBJECT=%llx rc=%d\\n\",\n                  (unsigned long long) XrdSysThread::ID(), (unsigned long long) this, rc);\n          std::terminate();\n        } else {\n          // fprintf(stderr,\"==== WRITE LOCK PENDING ==== TID=%llu OBJECT=%llx\\n\",\n          //        (unsigned long long)XrdSysThread::ID(), (unsigned long long)this);\n          std::this_thread::sleep_for(std::chrono::milliseconds(500));\n        }\n      } else {\n        // fprintf(stderr,\"=== WRITE LOCK ACQUIRED  ==== TID=%llu OBJECT=%llx\\n\",\n        // (unsigned long long)XrdSysThread::ID(), (unsigned long long)this);\n        break;\n      }\n    }\n\n#endif\n  }\n\n  EOS_RWMUTEX_TIMER_STOP_AND_UPDATE(mWr);\n}\n\n//----------------------------------------------------------------------------\n// Try lock for write (exclusive)\n//----------------------------------------------------------------------------\nint\nRWMutex::TryLockWrite()\n{\n  return mMutexImpl->TryLockWrite();\n}\n\n//------------------------------------------------------------------------------\n// Unlock a write lock\n//------------------------------------------------------------------------------\nvoid\nRWMutex::UnLockWrite()\n{\n  EOS_RWMUTEX_CHECKORDER_UNLOCK;\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n\n  if (mEnableDeadlockCheck || mTransientDeadlockCheck) {\n    ExitCheckDeadlock(false);\n  }\n\n#endif\n  int retc = 0;\n\n  if ((retc = mMutexImpl->UnLockWrite())) {\n    fprintf(stderr, \"%s Failed to write-unlock: %s\\n\", __FUNCTION__,\n            strerror(retc));\n    std::terminate();\n  }\n\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n\n  if (!sEnableGlobalDeadlockCheck) {\n    mTransientDeadlockCheck = false;\n\n    if (!mEnableDeadlockCheck) {\n      DropDeadlockCheck();\n    }\n  }\n\n#endif\n}\n\n//------------------------------------------------------------------------------\n// Lock for write but give up after wlocktime\n//------------------------------------------------------------------------------\nbool\nRWMutex::TimedWrLock(uint64_t timeout_ns)\n{\n  EOS_RWMUTEX_CHECKORDER_LOCK;\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n\n  if (sEnableGlobalDeadlockCheck) {\n    mTransientDeadlockCheck = true;\n  }\n\n  if (mEnableDeadlockCheck || mTransientDeadlockCheck) {\n    EnterCheckDeadlock(false);\n  }\n\n#endif\n  int retc = mMutexImpl->TimedWrLock(timeout_ns);\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n\n  if (retc && (mEnableDeadlockCheck || mTransientDeadlockCheck)) {\n    ExitCheckDeadlock(false);\n  }\n\n#endif\n\n  if (retc == 0) {\n  } else {\n    EOS_RWMUTEX_CHECKORDER_UNLOCK;\n  }\n\n  return (retc == 0);\n}\n\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n\n//------------------------------------------------------------------------------\n// Performs the initialization of the class\n//------------------------------------------------------------------------------\nvoid\nRWMutex::InitializeClass()\n{\n  int retc = 0;\n\n  if ((retc = pthread_rwlock_init(&mOrderChkLock, NULL))) {\n    fprintf(stderr, \"%s Failed to initialize order check lock: %s\\n\",\n            __FUNCTION__, strerror(retc));\n    std::terminate();\n  }\n\n  rules_static = new RWMutex::rules_t();\n  RWMutex::ruleIndex2Name_static = new\n  std::map<unsigned char, std::string>;\n  RWMutex::ruleName2Index_static = new\n  std::map<std::string, unsigned char>;\n  RWMutex::threadOrderCheckResetFlags_static = new\n  std::map<pthread_t, bool>;\n}\n\n//------------------------------------------------------------------------------\n// Reset statistics at the instance level\n//------------------------------------------------------------------------------\nvoid\nRWMutex::ResetTimingStatistics()\n{\n  // Might need a mutex or at least a flag!!!\n  mRdMaxWait.store(std::numeric_limits<uint64_t>::min());\n  mWrMaxWait.store(std::numeric_limits<uint64_t>::min());\n  mRdMinWait.store(std::numeric_limits<uint64_t>::max());\n  mWrMinWait.store(std::numeric_limits<uint64_t>::max());\n  mRdLockCounterSample.store(0);\n  mWrLockCounterSample.store(0);\n  mRdCumulatedWait.store(0);\n  mWrCumulatedWait.store(0);\n}\n\n//-----------------------------------------------------------------------------\n// Reset statistics at the class level\n//------------------------------------------------------------------------------\nvoid\nRWMutex::ResetTimingStatisticsGlobal()\n{\n  mRdMaxWait_static.store(std::numeric_limits<uint64_t>::min());\n  mWrMaxWait_static.store(std::numeric_limits<uint64_t>::min());\n  mRdMinWait_static.store(std::numeric_limits<uint64_t>::max());\n  mWrMinWait_static.store(std::numeric_limits<uint64_t>::max());\n  mRdLockCounterSample_static.store(0);\n  mWrLockCounterSample_static.store(0);\n  mRdCumulatedWait_static.store(0);\n  mWrCumulatedWait_static.store(0);\n}\n\n#ifdef __APPLE__\nint\nRWMutex::round(double number)\n{\n  return (number < 0.0 ? ceil(number - 0.5) : floor(number + 0.5));\n}\n#endif\n\n//------------------------------------------------------------------------------\n// Check for deadlocks\n//------------------------------------------------------------------------------\nvoid\nRWMutex::EnterCheckDeadlock(bool rd_lock)\n{\n  std::thread::id tid = std::this_thread::get_id();\n  pthread_mutex_lock(&mCollectionMutex);\n\n  if (rd_lock) {\n    auto it = mThreadsRdLock.find(tid);\n\n    if (it != mThreadsRdLock.end()) {\n      ++it->second;\n\n      // For non-preferred rd lock - since is a re-entrant read lock, if there\n      // is any write lock pending then this will deadlock\n      if (!mPreferRd && mThreadsWrLock.size()) {\n        std::cerr << eos::common::getStacktrace();\n        pthread_mutex_unlock(&mCollectionMutex);\n        throw std::runtime_error(\"double read lock during write lock\");\n      }\n    } else {\n      mThreadsRdLock.insert(std::make_pair(tid, 1));\n    }\n  } else {\n    if (mThreadsWrLock.find(tid) != mThreadsWrLock.end()) {\n      // This is a case of double write lock\n      std::cerr << eos::common::getStacktrace();\n      pthread_mutex_unlock(&mCollectionMutex);\n      throw std::runtime_error(\"double write lock\");\n    }\n\n    mThreadsWrLock.insert(tid);\n  }\n\n  pthread_mutex_unlock(&mCollectionMutex);\n}\n\n//------------------------------------------------------------------------------\n// Helper function to check for deadlocks\n//------------------------------------------------------------------------------\nvoid\nRWMutex::ExitCheckDeadlock(bool rd_lock)\n{\n  std::thread::id tid = std::this_thread::get_id();\n  pthread_mutex_lock(&mCollectionMutex);\n\n  if (rd_lock) {\n    auto it = mThreadsRdLock.find(tid);\n\n    if (it == mThreadsRdLock.end()) {\n      fprintf(stderr, \"%s Extra read unlock\\n\", __FUNCTION__);\n      pthread_mutex_unlock(&mCollectionMutex);\n      throw std::runtime_error(\"extra read unlock\");\n    }\n\n    if (--it->second == 0) {\n      mThreadsRdLock.erase(it);\n    }\n  } else {\n    auto it = mThreadsWrLock.find(tid);\n\n    if (it == mThreadsWrLock.end()) {\n      fprintf(stderr, \"%s Extra write unlock\\n\", __FUNCTION__);\n      pthread_mutex_unlock(&mCollectionMutex);\n      throw std::runtime_error(\"extra write unlock\");\n    }\n\n    mThreadsWrLock.erase(it);\n  }\n\n  pthread_mutex_unlock(&mCollectionMutex);\n}\n\n//------------------------------------------------------------------------------\n// Clear the data structures used for detecting deadlocks\n//------------------------------------------------------------------------------\nvoid\nRWMutex::DropDeadlockCheck()\n{\n  pthread_mutex_lock(&mCollectionMutex);\n  mThreadsRdLock.clear();\n  mThreadsWrLock.clear();\n  pthread_mutex_unlock(&mCollectionMutex);\n}\n\n//------------------------------------------------------------------------------\n// Enable sampling of timings\n//------------------------------------------------------------------------------\nvoid\nRWMutex::SetSampling(bool on, float rate)\n{\n  mEnableSampling = on;\n  ResetTimingStatistics();\n\n  if (rate < 0) {\n    mSamplingModulo = sSamplingModulo;\n  } else\n#ifdef __APPLE__\n    mSamplingModulo = std::min(RAND_MAX, std::max(0, (int) round(1.0 / rate)));\n\n#else\n    mSamplingModulo = std::min(RAND_MAX, std::max(0, (int) std::round(1.0 / rate)));\n#endif\n}\n\n//------------------------------------------------------------------------------\n//   Return the timing sampling rate/status\n//------------------------------------------------------------------------------\nfloat\nRWMutex::GetSampling()\n{\n  if (!mEnableSampling) {\n    return -1.0;\n  } else {\n    return 1.0 / mSamplingModulo;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Compute the SamplingRate corresponding to a given CPU overhead\n//------------------------------------------------------------------------------\nfloat\nRWMutex::GetSamplingRateFromCPUOverhead(const double& overhead)\n{\n  RWMutex mutex;\n  bool entimglobbak = RWMutex::GetTimingGlobal();\n  mutex.SetTiming(true);\n  mutex.SetSampling(true, 1.0);\n  RWMutex::SetTimingGlobal(true);\n  size_t monitoredTiming = Timing::GetNowInNs();\n\n  for (int k = 0; k < 1e6; k++) {\n    mutex.LockWrite();\n    mutex.UnLockWrite();\n  }\n\n  monitoredTiming = Timing::GetNowInNs() - monitoredTiming;\n  mutex.SetTiming(false);\n  mutex.SetSampling(false);\n  RWMutex::SetTimingGlobal(false);\n  size_t unmonitoredTiming = Timing::GetNowInNs();\n\n  for (int k = 0; k < 1e6; k++) {\n    mutex.LockWrite();\n    mutex.UnLockWrite();\n  }\n\n  unmonitoredTiming = Timing::GetNowInNs() - unmonitoredTiming;\n  RWMutex::SetTimingGlobal(entimglobbak);\n  float mutexShare = unmonitoredTiming;\n  float timingShare = monitoredTiming - unmonitoredTiming;\n  float samplingRate = std::min(1.0, std::max(0.0,\n                                overhead * mutexShare / timingShare));\n  RWMutex::sSamplingModulo = (int)(1.0 / samplingRate);\n  return samplingRate;\n}\n\n//------------------------------------------------------------------------------\n// Reset order checking rules\n//------------------------------------------------------------------------------\nvoid\nRWMutex::ResetOrderRule()\n{\n  bool sav = sEnableGlobalOrderCheck;\n  sEnableGlobalOrderCheck = false;\n  // Leave some time to all the threads to finish their book keeping activity\n  // regarding order checking\n  usleep(100000);\n  pthread_rwlock_wrlock(&mOrderChkLock);\n  // Remove all the dead threads from the map\n  // ~~~~~~~~~~~~~~~~~~~~~~~~~~ NOTICE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n  // !!! THIS DOESN'T WORK SO DEAD THREADS ARE NOT REMOVED FROM THE MAP\n  // !!! THIS IS BECAUSE THERE IS NO RELIABLE WAY TO CHECK IF A THREAD IS STILL\n  // !!! RUNNING. THIS IS NOT A PROBLEM FOR EOS BECAUSE XROOTD REUSES ITS THREADS\n  // !!! SO THE MAP DOESN'T GO INTO AN INFINITE GROWTH.\n  // ~~~~~~~~~~~~~~~~~~~~~~~~~~ NOTICE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n#if 0\n\n  for (auto it = threadOrderCheckResetFlags_static.begin();\n       it != threadOrderCheckResetFlags_static.end(); ++it) {\n    if (XrdSysThread::Signal(it->first, 0)) {\n      // this line crashes when the threads is no more valid.\n      threadOrderCheckResetFlags_static.erase(it);\n      it = threadOrderCheckResetFlags_static.begin();\n    }\n  }\n\n#endif\n\n  // Tell the threads to reset the states of the order mask (because it's thread-local)\n  for (auto it = threadOrderCheckResetFlags_static->begin();\n       it != threadOrderCheckResetFlags_static->end(); ++it) {\n    it->second = true;\n  }\n\n  // Tell all the RWMutex that they are not involved in any order checking anymore\n  for (auto rit = rules_static->begin(); rit != rules_static->end(); ++rit) {\n    for (auto it = rit->second.begin(); it != rit->second.end(); ++it) {\n      // For each RWMutex involved in that rule\n      static_cast<RWMutex*>(*it)->nrules = 0; // no rule involved\n    }\n  }\n\n  // Clear the manager side.\n  ruleName2Index_static->clear();\n  ruleIndex2Name_static->clear();\n  rules_static->clear();\n  pthread_rwlock_unlock(&mOrderChkLock);\n  sEnableGlobalOrderCheck = sav;\n}\n\n//------------------------------------------------------------------------------\n// Remove an order checking rule\n//------------------------------------------------------------------------------\nint\nRWMutex::RemoveOrderRule(const std::string& rulename)\n{\n  // Make a local copy of the rules and remove the required rule\n  std::map<std::string, std::vector<RWMutex*> > rules = (*rules_static);\n\n  if (!rules.erase(rulename)) {\n    return 0;\n  }\n\n  // Reset the rules\n  ResetOrderRule();\n\n  // Add all the rules but the removed one\n  for (auto it = rules.begin(); it != rules.end(); ++it) {\n    AddOrderRule(it->first, it->second);\n  }\n\n  return 1;\n}\n\n//------------------------------------------------------------------------------\n// Compute the cost in time of taking timings so that it can be compensated in\n// the statistics\n//------------------------------------------------------------------------------\nsize_t\nRWMutex::EstimateTimingCompensation(size_t loopsize)\n{\n  size_t t = Timing::GetNowInNs();\n\n  for (unsigned long k = 0; k < loopsize; k++) {\n    struct timespec ts;\n    eos::common::Timing::GetTimeSpec(ts);\n  }\n\n  t = Timing::GetNowInNs() - t;\n  return size_t(double(t) / loopsize);\n}\n\n//------------------------------------------------------------------------------\n// Compute the speed for lock/unlock cycle\n//------------------------------------------------------------------------------\nsize_t\nRWMutex::EstimateLockUnlockDuration(size_t loopsize)\n{\n  RWMutex mutex;\n  bool sav = RWMutex::GetTimingGlobal();\n  bool sav2 = RWMutex::GetOrderCheckingGlobal();\n  RWMutex::SetTimingGlobal(false);\n  RWMutex::SetOrderCheckingGlobal(false);\n  mutex.SetTiming(false);\n  mutex.SetSampling(false);\n  size_t t = Timing::GetNowInNs();\n\n  for (size_t k = 0; k < loopsize; k++) {\n    mutex.LockWrite();\n    mutex.UnLockWrite();\n  }\n\n  t = Timing::GetNowInNs() - t;\n  RWMutex::SetTimingGlobal(sav);\n  RWMutex::SetOrderCheckingGlobal(sav2);\n  return size_t(double(t) / loopsize);\n}\n\n//------------------------------------------------------------------------------\n// Compute the latency introduced by taking timings\n//------------------------------------------------------------------------------\nsize_t\nRWMutex::EstimateTimingAddedLatency(size_t loopsize, bool globaltiming)\n{\n  RWMutex mutex;\n  bool sav = RWMutex::GetTimingGlobal();\n  bool sav2 = RWMutex::GetOrderCheckingGlobal();\n  RWMutex::SetTimingGlobal(globaltiming);\n  RWMutex::SetOrderCheckingGlobal(false);\n  mutex.SetTiming(true);\n  mutex.SetSampling(true, 1.0);\n  size_t t = Timing::GetNowInNs();\n\n  for (size_t k = 0; k < loopsize; k++) {\n    mutex.LockWrite();\n    mutex.UnLockWrite();\n  }\n\n  size_t s = Timing::GetNowInNs() - t;\n  RWMutex::SetTimingGlobal(false);\n  mutex.SetTiming(false);\n  mutex.SetSampling(false);\n  t = Timing::GetNowInNs();\n\n  for (size_t k = 0; k < loopsize; k++) {\n    mutex.LockWrite();\n    mutex.UnLockWrite();\n  }\n\n  t = Timing::GetNowInNs() - t;\n  RWMutex::SetTimingGlobal(sav);\n  RWMutex::SetOrderCheckingGlobal(sav2);\n  return size_t(double(s - t) / loopsize);\n}\n\n//------------------------------------------------------------------------------\n// Compute the latency introduced by checking the mutexes locking orders\n//------------------------------------------------------------------------------\nsize_t\nRWMutex::EstimateOrderCheckingAddedLatency(size_t nmutexes,\n    size_t loopsize)\n{\n  std::vector<RWMutex*> mutexes;\n  mutexes.reserve(nmutexes);\n\n  for (size_t i = 0; i < nmutexes; ++i) {\n    mutexes.push_back(new RWMutex());\n  }\n\n  std::vector<RWMutex*> order;\n  order.reserve(nmutexes);\n\n  for (auto& mtx : mutexes) {\n    mtx->SetTiming(false);\n    mtx->SetSampling(false);\n    order.push_back(mtx);\n  }\n\n  RWMutex::AddOrderRule(\"estimaterule\", order);\n  bool sav = RWMutex::GetTimingGlobal();\n  bool sav2 = RWMutex::GetOrderCheckingGlobal();\n  RWMutex::SetTimingGlobal(false);\n  RWMutex::SetOrderCheckingGlobal(true);\n  size_t t = Timing::GetNowInNs();\n\n  for (size_t k = 0; k < loopsize; k++) {\n    for (auto it = mutexes.begin(); it != mutexes.end(); ++it) {\n      (*it)->LockWrite();\n    }\n\n    for (auto it = mutexes.rbegin(); it != mutexes.rend(); ++it) {\n      (*it)->UnLockWrite();\n    }\n  }\n\n  size_t s = Timing::GetNowInNs() - t;\n  RWMutex::SetOrderCheckingGlobal(false);\n  t = Timing::GetNowInNs();\n\n  for (size_t k = 0; k < loopsize; ++k) {\n    for (auto it = mutexes.begin(); it != mutexes.end(); ++it) {\n      (*it)->LockWrite();\n    }\n\n    for (auto it = mutexes.rbegin(); it != mutexes.rend(); ++it) {\n      (*it)->UnLockWrite();\n    }\n  }\n\n  t = Timing::GetNowInNs() - t;\n  RWMutex::SetTimingGlobal(sav);\n  RWMutex::SetOrderCheckingGlobal(sav2);\n  RemoveOrderRule(\"estimaterule\");\n\n  for (size_t i = 0; i < nmutexes; ++i) {\n    delete mutexes[i];\n  }\n\n  return size_t(double(s - t) / (loopsize * nmutexes));\n}\n\n//-------------------------------------------------------------------------------\n// Estimate latencies and compensation\n//------------------------------------------------------------------------------\nvoid\nRWMutex::EstimateLatenciesAndCompensation(size_t loopsize)\n{\n  timingCompensation = EstimateTimingCompensation(loopsize);\n  timingLatency = EstimateTimingAddedLatency(loopsize);\n  orderCheckingLatency = EstimateOrderCheckingAddedLatency(3, loopsize);\n  lockUnlockDuration = EstimateLockUnlockDuration(loopsize);\n  std::cerr << \" timing compensation = \" << timingCompensation << std::endl;\n  std::cerr << \" timing latency = \" << timingLatency << std::endl;\n  std::cerr << \" order  latency = \" << orderCheckingLatency << std::endl;\n  std::cerr << \" lock/unlock duration = \" << lockUnlockDuration << std::endl;\n}\n\n//------------------------------------------------------------------------------\n// Get the timing statistics at the instance level\n//------------------------------------------------------------------------------\nvoid\nRWMutex::GetTimingStatistics(TimingStats& stats, bool compensate)\n{\n  size_t compensation = (compensate ? timingCompensation : 0);\n  stats.readLockCounterSample.store(mRdLockCounterSample.load());\n  stats.writeLockCounterSample.store(mWrLockCounterSample.load());\n  stats.averagewaitread = 0;\n\n  if (mRdLockCounterSample.load() != 0) {\n    double avg = (double(mRdCumulatedWait.load()) /\n                  mRdLockCounterSample.load() - compensation);\n\n    if (avg > 0) {\n      stats.averagewaitread = avg;\n    }\n  }\n\n  stats.averagewaitwrite = 0;\n\n  if (mWrLockCounterSample.load() != 0) {\n    double avg = (double(mWrCumulatedWait.load()) /\n                  mWrLockCounterSample.load() - compensation);\n\n    if (avg > 0) {\n      stats.averagewaitwrite = avg;\n    }\n  }\n\n  if (mRdMinWait.load() != std::numeric_limits<size_t>::max()) {\n    long long compensated = mRdMinWait.load() - compensation;\n\n    if (compensated > 0) {\n      stats.minwaitread = compensated;\n    } else {\n      stats.minwaitread = 0;\n    }\n  } else {\n    stats.minwaitread = std::numeric_limits<long long>::max();\n  }\n\n  if (mRdMaxWait.load() != std::numeric_limits<size_t>::min()) {\n    long long compensated = mRdMaxWait.load() - compensation;\n\n    if (compensated > 0) {\n      stats.maxwaitread = compensated;\n    } else {\n      stats.maxwaitread = 0;\n    }\n  } else {\n    stats.maxwaitread = std::numeric_limits<size_t>::min();\n  }\n\n  if (mWrMinWait.load() != std::numeric_limits<size_t>::max()) {\n    long long compensated = mWrMinWait.load() - compensation;\n\n    if (compensated > 0) {\n      stats.minwaitwrite = compensated;\n    } else {\n      stats.minwaitwrite = 0;\n    }\n  } else {\n    stats.minwaitwrite = std::numeric_limits<long long>::max();\n  }\n\n  if (mWrMaxWait.load() != std::numeric_limits<size_t>::min()) {\n    long long compensated = mWrMaxWait.load() - compensation;\n\n    if (compensated > 0) {\n      stats.maxwaitwrite = compensated;\n    } else {\n      stats.maxwaitwrite = 0;\n    }\n  } else {\n    stats.maxwaitwrite = std::numeric_limits<size_t>::min();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check the order defined by the rules and update\n//------------------------------------------------------------------------------\nvoid\nRWMutex::OrderViolationMessage(unsigned char rule,\n                               const std::string& message)\n{\n  void* array[10];\n  unsigned long threadid = XrdSysThread::Num();\n  // Get void*'s for all entries on the stack\n  size_t size = backtrace(array, 10);\n  const std::string& rulename =\n    (*ruleIndex2Name_static)[ruleLocalIndexToGlobalIndex[rule]];\n  fprintf(stderr, \"RWMutex: Order Checking Error in thread %lu\\n %s\\n in rule \"\n          \"%s :\\nLocking Order should be:\\n\", threadid, message.c_str(),\n          rulename.c_str());\n  std::vector<RWMutex*> order = (*rules_static)[rulename];\n\n  for (auto ito = order.begin(); ito != order.end(); ++ito) {\n    fprintf(stderr, \"\\t%12s (%p)\",\n            static_cast<RWMutex*>(*ito)->mName.c_str(), (*ito));\n  }\n\n  fprintf(stderr, \"\\nThe lock states of these mutexes are (before the violating\"\n          \" lock/unlock) :\\n\");\n\n  for (unsigned char k = 0; k < order.size(); k++) {\n    unsigned long int mask = (1 << k);\n    fprintf(stderr, \"\\t%d\", int((ordermask_staticthread[rule] & mask) != 0));\n  }\n\n  fprintf(stderr, \"\\n\");\n  backtrace_symbols_fd(array, size, 2);\n}\n\n//------------------------------------------------------------------------------\n// Check the orders defined by the rules and update for a lock\n//------------------------------------------------------------------------------\nvoid\nRWMutex::CheckAndLockOrder()\n{\n  // Initialize the thread local ordermask if not already done\n  if (orderCheckReset_staticthread == NULL) {\n    ResetCheckOrder();\n  }\n\n  if (*orderCheckReset_staticthread) {\n    ResetCheckOrder();\n    *orderCheckReset_staticthread = false;\n  }\n\n  for (unsigned char k = 0; k < nrules; k++) {\n    unsigned long int mask = (1 << rankinrule[k]);\n\n    // Check if following mutex is already locked in the same thread\n    if (ordermask_staticthread[k] >= mask) {\n      char strmess[1024];\n      sprintf(strmess, \"locking %s at address %p\", mName.c_str(), this);\n      OrderViolationMessage(k, strmess);\n    }\n\n    ordermask_staticthread[k] |= mask;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check the orders defined by the rules and update for an unlock\n//------------------------------------------------------------------------------\nvoid\nRWMutex::CheckAndUnlockOrder()\n{\n  // Initialize the thread local ordermask if not already done\n  if (orderCheckReset_staticthread == NULL) {\n    ResetCheckOrder();\n  }\n\n  if (*orderCheckReset_staticthread) {\n    ResetCheckOrder();\n    *orderCheckReset_staticthread = false;\n  }\n\n  for (unsigned char k = 0; k < nrules; k++) {\n    unsigned long int mask = (1 << rankinrule[k]);\n\n    if (0) {\n      // we don't care about unlocking order violations, there is no problem with that\n      // check if following mutex is already locked in the same thread\n      if (ordermask_staticthread[k] >= (mask << 1)) {\n        char strmess[1024];\n        sprintf(strmess, \"unlocking %s at address %p\", mName.c_str(), this);\n        OrderViolationMessage(k, strmess);\n      }\n    }\n\n    ordermask_staticthread[k] &= (~mask);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get the timing statistics at the class level\n//------------------------------------------------------------------------------\nvoid\nRWMutex::GetTimingStatisticsGlobal(TimingStats& stats, bool compensate)\n{\n  size_t compensation = compensate ? timingCompensation : 0;\n  stats.readLockCounterSample.store(mRdLockCounterSample_static.load());\n  stats.writeLockCounterSample.store(mWrLockCounterSample_static.load());\n  stats.averagewaitread = 0;\n\n  if (mRdLockCounterSample_static.load() != 0) {\n    double avg = (double(mRdCumulatedWait_static.load()) /\n                  mRdLockCounterSample_static.load() - compensation);\n\n    if (avg > 0) {\n      stats.averagewaitread = avg;\n    }\n  }\n\n  stats.averagewaitwrite = 0;\n\n  if (mWrLockCounterSample_static.load() != 0) {\n    double avg = (double(mWrCumulatedWait_static.load()) /\n                  mWrLockCounterSample_static.load() - compensation);\n\n    if (avg > 0) {\n      stats.averagewaitwrite = avg;\n    }\n  }\n\n  if (mRdMinWait_static.load() != std::numeric_limits<size_t>::max()) {\n    long long compensated = mRdMinWait_static.load() - compensation;\n\n    if (compensated > 0) {\n      stats.minwaitread = compensated;\n    } else {\n      stats.minwaitread = 0;\n    }\n  } else {\n    stats.minwaitread = std::numeric_limits<long long>::max();\n  }\n\n  if (mWrMaxWait_static.load() != std::numeric_limits<size_t>::min()) {\n    long long compensated = mWrMaxWait_static.load() - compensation;\n\n    if (compensated > 0) {\n      stats.maxwaitread = compensated;\n    } else {\n      stats.maxwaitread = 0;\n    }\n  } else {\n    stats.maxwaitread = std::numeric_limits<size_t>::min();\n  }\n\n  if (mWrMinWait_static.load() != std::numeric_limits<size_t>::max()) {\n    long long compensated = mWrMinWait_static.load() - compensation;\n\n    if (compensated > 0) {\n      stats.minwaitwrite = compensated;\n    } else {\n      stats.minwaitwrite = 0;\n    }\n  } else {\n    stats.minwaitwrite = std::numeric_limits<long long>::max();\n  }\n\n  if (mWrMaxWait_static.load() != std::numeric_limits<size_t>::min()) {\n    long long compensated = mWrMaxWait_static.load() - compensation;\n\n    if (compensated > 0) {\n      stats.maxwaitwrite = compensated;\n    } else {\n      stats.maxwaitwrite = 0;\n    }\n  } else {\n    stats.maxwaitwrite = std::numeric_limits<size_t>::min();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Add or overwrite an order checking rule\n//------------------------------------------------------------------------------\nint\nRWMutex::AddOrderRule(const std::string& rulename,\n                      const std::vector<RWMutex*>& order)\n{\n  bool sav = sEnableGlobalOrderCheck;\n  sEnableGlobalOrderCheck = false;\n  // Leave time to all the threads to finish their book-keeping activity\n  // regarding order checking\n  usleep(100000);\n  pthread_rwlock_wrlock(&mOrderChkLock);\n\n  // If we reached the max number of rules, ignore\n  if (rules_static->size() == EOS_RWMUTEX_ORDER_NRULES || order.size() > 63) {\n    sEnableGlobalOrderCheck = sav;\n    pthread_rwlock_unlock(&mOrderChkLock);\n    return -1;\n  }\n\n  (*rules_static)[rulename] = order;\n  int ruleIdx = rules_static->size() - 1;\n  // update the maps\n  (*ruleName2Index_static)[rulename] = ruleIdx;\n  (*ruleIndex2Name_static)[ruleIdx] = rulename;\n  // update each object\n  unsigned char count = 0;\n\n  for (auto it = order.begin(); it != order.end(); it++) {\n    // ruleIdx begin at 0\n    // Each RWMutex has its own number of rules, they are all <= than\n    // EOS_RWMUTEX_ORDER_NRULES\n    static_cast<RWMutex*>\n    (*it)->rankinrule[static_cast<RWMutex*>\n                      (*it)->nrules] = count;\n    static_cast<RWMutex*>\n    (*it)->ruleLocalIndexToGlobalIndex[static_cast<RWMutex*>\n                                       (*it)->nrules++] = ruleIdx;\n    count++;\n  }\n\n  pthread_rwlock_unlock(&mOrderChkLock);\n  sEnableGlobalOrderCheck = sav;\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Reset the order checking mechanism for the current thread\n//------------------------------------------------------------------------------\nvoid\nRWMutex::ResetCheckOrder()\n{\n  // Reset the order mask\n  for (int k = 0; k < EOS_RWMUTEX_ORDER_NRULES; k++) {\n    ordermask_staticthread[k] = 0;\n  }\n\n  // Update orderCheckReset_staticthread, this memory should be specific to\n  // this thread\n  pthread_t tid = XrdSysThread::ID();\n  pthread_rwlock_rdlock(&mOrderChkLock);\n\n  if (threadOrderCheckResetFlags_static->find(tid) ==\n      threadOrderCheckResetFlags_static->end()) {\n    pthread_rwlock_unlock(&mOrderChkLock);\n    pthread_rwlock_wrlock(&mOrderChkLock);\n    (*threadOrderCheckResetFlags_static)[tid] = false;\n  }\n\n  orderCheckReset_staticthread = &(*threadOrderCheckResetFlags_static)[tid];\n  pthread_rwlock_unlock(&mOrderChkLock);\n}\n\n#endif\n\n//------------------------------------------------------------------------------\n// Record mutex operation type\n//------------------------------------------------------------------------------\nvoid\nRWMutex::RecordMutexOp(uint64_t ptr_val, LOCK_T op)\n{\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n\n  // Only record info about the named mutexes\n  if (sMtxNameMap.find(ptr_val) == sMtxNameMap.end()) {\n    return;\n  }\n\n  std::thread::id tid = std::this_thread::get_id();\n  std::unique_lock lock(sOpMutex);\n  auto& mtx_op_map = sTidMtxOpMap[tid];\n  mtx_op_map[ptr_val] = op;\n#endif // EOS_INSTRUMENTED_MUTEX\n}\n\n\n//------------------------------------------------------------------------------\n// Print the status of the mutex locks for the calling thread id\n//------------------------------------------------------------------------------\nvoid\nRWMutex::PrintMutexOps(std::ostringstream& oss)\n{\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n  std::thread::id tid = std::this_thread::get_id();\n  std::unique_lock lock(sOpMutex);\n  const auto it = sTidMtxOpMap.find(tid);\n\n  if (it == sTidMtxOpMap.end()) {\n    return;\n  }\n\n  for (const auto& elem : it->second) {\n    std::string name;\n\n    if (RWMutex::sMtxNameMap.count(elem.first)) {\n      oss << RWMutex::sMtxNameMap[elem.first] << \": \"\n          << eos::common::RWMutex::LOCK_STATE[(int)elem.second] << \" \";\n    } else {\n      oss << elem.first << \": \"\n          << eos::common::RWMutex::LOCK_STATE[(int)elem.second] << \" \";\n    }\n  }\n\n#endif // EOS_INSTRUMENTED_MUTEX\n}\n\n\n//------------------------------------------------------------------------------\n//                      ***** Class RWMutexWriteLock *****\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nRWMutexWriteLock::RWMutexWriteLock(RWMutex& mutex, const char* function,\n                                   const char* file, int line):\n  mWrMutex(nullptr)\n{\n  Grab(mutex, function, file, line);\n}\n\n//----------------------------------------------------------------------------\n// Grab mutex and write lock it\n//----------------------------------------------------------------------------\nvoid\nRWMutexWriteLock::Grab(RWMutex& mutex, const char* function,\n                       const char* file, int line)\n{\n  mFunction = function;\n  mFile = file;\n  mLine = line;\n\n  if (mWrMutex) {\n    throw std::runtime_error(\"already holding a mutex\");\n  }\n\n  mWrMutex = &mutex;\n  RWMutex::RecordMutexOp((uint64_t)mWrMutex->GetRawPtr(),\n                         RWMutex::LOCK_T::eWantLockWrite);\n  mWrMutex->LockWrite();\n  RWMutex::RecordMutexOp((uint64_t)mWrMutex->GetRawPtr(),\n                         RWMutex::LOCK_T::eLockWrite);\n  mAcquiredAt = std::chrono::steady_clock::now();\n  mAcquiredAtSystem = std::chrono::system_clock::now();\n}\n\n\n//----------------------------------------------------------------------------\n// Release the write lock after grab\n//----------------------------------------------------------------------------\nvoid\nRWMutexWriteLock::Release()\n{\n  if (mWrMutex) {\n    RWMutex::RecordMutexOp((uint64_t)mWrMutex->GetRawPtr(),\n                           RWMutex::LOCK_T::eWantUnLockWrite);\n    mWrMutex->UnLockWrite();\n    RWMutex::RecordMutexOp((uint64_t)mWrMutex->GetRawPtr(), RWMutex::LOCK_T::eNone);\n    int64_t blockedinterval = mWrMutex->BlockedForMsInterval();\n    mWrMutex->AddWriteLockTime(blockedinterval);\n    bool blockedtracing = mWrMutex->BlockedStackTracing();\n    mReleasedAt = std::chrono::steady_clock::now();\n    mReleasedAtSystem = std::chrono::system_clock::now();\n    mWrMutex->addBlockingTimeInfos(mAcquiredAtSystem, mReleasedAtSystem);\n    std::chrono::milliseconds blockedFor =\n      std::chrono::duration_cast<std::chrono::milliseconds> (mReleasedAt -\n          mAcquiredAt);\n\n    if (blockedFor.count() > blockedinterval) {\n      std::ostringstream ss;\n      ss << \"write lock [ \" << mWrMutex->getName() << \" ] held for \"\n         << blockedFor.count() <<  \" milliseconds\";\n\n      if (blockedtracing) {\n        ss << \" : \";\n        ss << eos::common::getStacktrace();\n      }\n\n      eos_third_party_warning(mFunction, mFile, mLine, \"%s\", ss.str().c_str());\n    }\n\n    mWrMutex = nullptr;\n  }\n}\n\n//------------------------------------------------------------------------------\n//                      ***** Class RWMutexReadLock *****\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nRWMutexReadLock::RWMutexReadLock(RWMutex& mutex, const char* function,\n                                 const char* file, int line):\n  mRdMutex(nullptr)\n{\n  Grab(mutex, function, file, line);\n}\n\n//----------------------------------------------------------------------------\n// Grab mutex and write lock it\n//----------------------------------------------------------------------------\nvoid\nRWMutexReadLock::Grab(RWMutex& mutex, const char* function,\n                      const char* file, int line)\n{\n  mFunction = function;\n  mLine = line;\n  mFile = file;\n\n  if (mRdMutex) {\n    throw std::runtime_error(\"already holding a mutex\");\n  }\n\n  mRdMutex = &mutex;\n  RWMutex::RecordMutexOp((uint64_t)mRdMutex->GetRawPtr(),\n                         RWMutex::LOCK_T::eWantLockRead);\n  mRdMutex->LockRead();\n  RWMutex::RecordMutexOp((uint64_t)mRdMutex->GetRawPtr(),\n                         RWMutex::LOCK_T::eLockRead);\n  // mAcquiredAt must be updated _after_ we get the lock, since LockRead\n  // may take a long time to complete\n  mAcquiredAt = std::chrono::steady_clock::now();\n}\n\n//------------------------------------------------------------------------------\n// Release the read lock after grab\n//------------------------------------------------------------------------------\nvoid\nRWMutexReadLock::Release()\n{\n  if (mRdMutex) {\n    RWMutex::RecordMutexOp((uint64_t)mRdMutex->GetRawPtr(),\n                           RWMutex::LOCK_T::eWantUnLockRead);\n    mRdMutex->UnLockRead();\n    RWMutex::RecordMutexOp((uint64_t)mRdMutex->GetRawPtr(), RWMutex::LOCK_T::eNone);\n    int64_t blockedinterval = mRdMutex->BlockedForMsInterval();\n    mRdMutex->AddReadLockTime(blockedinterval);\n    bool blockedtracing = mRdMutex->BlockedStackTracing();\n    std::chrono::milliseconds blockedFor =\n      std::chrono::duration_cast<std::chrono::milliseconds>\n      (std::chrono::steady_clock::now() - mAcquiredAt);\n\n    if (blockedFor.count() > blockedinterval) {\n      std::ostringstream ss;\n      ss << \"read lock [ \" << mRdMutex->getName() << \" ] held for \"\n         << blockedFor.count() << \" milliseconds\";\n\n      if (blockedtracing) {\n        ss << \" : \" << eos::common::getStacktrace();\n      }\n\n      eos_third_party_warning(mFunction, mFile, mLine, \"%s\", ss.str().c_str());\n    }\n\n    mRdMutex = nullptr;\n  }\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/RWMutex.hh",
    "content": "//------------------------------------------------------------------------------\n// File: RWMutex.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @brief Class implementing a fair read-write Mutex.\n//!\n//! @description When compiled with EOS_INSTRUMENTED_RWMUTEX, this class\n//! provides also timing features. The timing can be taken exhaustively or it\n//! can be sampled. Then some basics statistics are available.\n//! The statistics are available at the instance granularity or at the class\n//! granularity. If the timing is turned off (at both levels), the instrumented\n//! class is approximately as fast as the native class.\n//! Note that taking the timing of a mutex lock multiply the lock exec time by\n//! a factor from 4 up to 6. An estimation of the added latency is provided as\n//! well as a mechanism for timing compensation.\n//!\n//! Locking/unlocking order checking features\n//! Rules can be defined. Then, each time a mutex is involved in one of these\n//! rules is locked/unlocked. There is bookkeeping for each thread. If a thread\n//! doesn't respect the order when locking/unlocking, a message is issued to\n//! std err.\n//! For performance reasons, the maximum number of rules is static and defined\n//! in the macro EOS_RWMUTEX_ORDER_NRULES (default 4).\n//! A rule is defined by a locking order (a sequence of pointers to RWMutex\n//! instances). The maximum length of this sequence is 63.\n//! The added latency by order checking for 3 mutexes and 1 rule is about 15%\n//! of the locking/unlocking execution time. An estimation of this added latency\n//! is provided.\n//------------------------------------------------------------------------------\n\n#pragma once\n#ifndef __APPLE__\n#define EOS_INSTRUMENTED_RWMUTEX\n#endif\n\n#include \"common/Namespace.hh\"\n#include \"common/IRWMutex.hh\"\n#include \"common/concurrency/AlignedArray.hh\"\n#include <XrdSys/XrdSysPthread.hh>\n#include <stdio.h>\n#include <stdint.h>\n#include <atomic>\n#include <array>\n#include <chrono>\n#include <map>\n#include <string>\n#include <ostream>\n#include <fstream>\n#include <iostream>\n#include <cmath>\n#include <vector>\n#include <execinfo.h>\n#include <limits>\n#include <set>\n#include <thread>\n#include <mutex>\n\n#define _MULTI_THREADED\n#include <pthread.h>\n\n#define EOS_RWMUTEX_ORDER_NRULES 4\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! struct TimingArray aligned to a cacheline\n//------------------------------------------------------------------------------\nstruct TimingArray {\n  AlignedAtomicArray<int64_t, 4> items;\n  TimingArray() : items{} {}\n};\n//------------------------------------------------------------------------------\n//! Class RWMutex\n//------------------------------------------------------------------------------\nclass RWMutex\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  // ---------------------------------------------------------------------------\n  RWMutex(bool prefer_rd = false);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~RWMutex();\n\n  //----------------------------------------------------------------------------\n  //! Get raw ptr\n  //----------------------------------------------------------------------------\n  IRWMutex* GetRawPtr()\n  {\n    return mMutexImpl;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Move constructor\n  //----------------------------------------------------------------------------\n  RWMutex(RWMutex&& other) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Move assignment operator\n  //----------------------------------------------------------------------------\n  RWMutex& operator=(RWMutex&& other) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Copy constructor\n  //----------------------------------------------------------------------------\n  RWMutex(const RWMutex&) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Copy assignment operator\n  //----------------------------------------------------------------------------\n  RWMutex& operator=(const RWMutex&) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Set the write lock to blocking or not blocking\n  //!\n  //! @param block blocking mode\n  //----------------------------------------------------------------------------\n  inline void SetBlocking(bool block)\n  {\n    mBlocking = block;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set the time interval when to stacktrace a long lasting lock\n  //!\n  //! @param blockedfor time in ms\n  //----------------------------------------------------------------------------\n  inline void SetBlockedForMsInterval(int64_t blockedfor)\n  {\n    mBlockedForInterval = blockedfor;\n  }\n\n  int64_t BlockedForMsInterval() const\n  {\n    return mBlockedForInterval;\n  }\n\n  //----------------------------------------------------------------------------\n  //! En-/Disable stack tracing of locks lasting longer then the interval\n  //!\n  //! @param onoff true=enable false=disable\n  inline void SetBlockedStackTracing(bool onoff)\n  {\n    mBlockedStackTracing = onoff;\n  }\n\n  bool BlockedStackTracing() const\n  {\n    return mBlockedStackTracing;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Lock for read\n  //----------------------------------------------------------------------------\n  void LockRead();\n\n  //----------------------------------------------------------------------------\n  //! Unlock a read lock\n  //----------------------------------------------------------------------------\n  void UnLockRead();\n\n  //----------------------------------------------------------------------------\n  //! Try to read lock the mutex within the timeout\n  //!\n  //! @param timeout_ns nanoseconds timeout\n  //!\n  //! @return true if lock acquired successfully, otherwise false\n  //----------------------------------------------------------------------------\n  bool TimedRdLock(uint64_t timeout_ns);\n\n  //----------------------------------------------------------------------------\n  //! Try lock for read (shared)\n  //!\n  //! @return 0 if lock acquired successfully, otherwise non-zero\n  //----------------------------------------------------------------------------\n  int TryLockRead();\n\n  //----------------------------------------------------------------------------\n  //! Lock for write\n  //----------------------------------------------------------------------------\n  void LockWrite();\n\n  //----------------------------------------------------------------------------\n  //! Try lock for write (exclusive)\n  //!\n  //! @return 0 if lock acquired successfully, otherwise non-zero\n  //----------------------------------------------------------------------------\n  int TryLockWrite();\n\n  //----------------------------------------------------------------------------\n  //! Unlock a write lock\n  //----------------------------------------------------------------------------\n  void UnLockWrite();\n\n  //----------------------------------------------------------------------------\n  //! Try to write lock the mutex within the timeout\n  //!\n  //! @param timeout_ns nanoseconds timeout\n  //!\n  //! @return true if lock acquired successfully, otherwise false\n  //----------------------------------------------------------------------------\n  bool TimedWrLock(uint64_t timeout_ns);\n\n  //----------------------------------------------------------------------------\n  //! Get Readlock Counter\n  //----------------------------------------------------------------------------\n  inline uint64_t GetReadLockCounter()\n  {\n    return mRdLockCounter.load();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get Writelock Counter\n  //----------------------------------------------------------------------------\n  inline uint64_t GetWriteLockCounter()\n  {\n    return mWrLockCounter.load();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get Readlock Time\n  //----------------------------------------------------------------------------\n  inline uint64_t GetReadLockTime()\n  {\n    uint64_t rlt = mRdLockTime.load();\n    mRdLockTime = 0;\n    return rlt;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get Writelock Time\n  //----------------------------------------------------------------------------\n  inline uint64_t GetWriteLockTime()\n  {\n    uint64_t wlt = mWrLockTime.load();\n    mWrLockTime = 0;\n    return wlt;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get Readlock Time\n  //----------------------------------------------------------------------------\n  inline uint64_t GetReadLockLeadTime()\n  {\n    uint64_t rlt = mRdLockLeadTime.load();\n    mRdLockLeadTime = 0;\n    return rlt;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get Writelock Time\n  //----------------------------------------------------------------------------\n  inline uint64_t GetWriteLockLeadTime()\n  {\n    uint64_t wlt = mWrLockLeadTime.load();\n    mWrLockLeadTime = 0;\n    return wlt;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Add Readlock Time\n  //----------------------------------------------------------------------------\n  inline void AddReadLockTime(uint64_t t)\n  {\n    mRdLockTime += t;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Add Writelock Time\n  //----------------------------------------------------------------------------\n  inline void AddWriteLockTime(uint64_t t)\n  {\n    mWrLockTime += t;\n  }\n\n  enum class LOCK_T { eNone, eWantLockRead, eWantUnLockRead, eLockRead, eWantLockWrite, eWantUnLockWrite, eLockWrite };\n\n  //----------------------------------------------------------------------------\n  //! Record mutex operation type\n  //!\n  //! @param ptr_val pointer value of the mutex concerned\n  //! @param op type of operation on the given mutex\n  //----------------------------------------------------------------------------\n  static void RecordMutexOp(uint64_t ptr_val, LOCK_T op);\n\n  //----------------------------------------------------------------------------\n  //! Print the status of the mutex locks for the calling thread id\n  //!\n  //! @param out output string\n  //----------------------------------------------------------------------------\n  static void PrintMutexOps(std::ostringstream& oss);\n\n  //----------------------------------------------------------------------------\n  //! Get the name\n  //----------------------------------------------------------------------------\n  std::string getName() const\n  {\n    return mName;\n  }\n\n  void addBlockingTimeInfos(std::chrono::system_clock::time_point acquiredAt,\n                            std::chrono::system_clock::time_point releasedAt)\n  {\n    auto acquiredAtSinceEpoch = acquiredAt.time_since_epoch();\n    auto releasedAtSinceEpoch = releasedAt.time_since_epoch();\n    auto releasedAtSecondsSinceEpoch =\n      std::chrono::duration_cast<std::chrono::seconds>(releasedAtSinceEpoch).count();\n    auto acquiredAtMsSinceEpoch =\n      std::chrono::duration_cast<std::chrono::milliseconds>\n      (acquiredAtSinceEpoch).count();\n    auto releasedAtMsSinceEpoch =\n      std::chrono::duration_cast<std::chrono::milliseconds>\n      (releasedAtSinceEpoch).count();\n    auto blockedForTimepoint = releasedAt - acquiredAt;\n    auto blockedForMs = std::chrono::duration_cast<std::chrono::milliseconds>\n                        (blockedForTimepoint).count();\n    auto blockedForSeconds = std::chrono::duration_cast<std::chrono::seconds>\n                             (blockedForTimepoint).count();\n\n    if (blockedForSeconds >= 2) {\n      //The second before the current one, the mutex was locked the entire time\n      mNbMsMutexLocked.items[(releasedAtSecondsSinceEpoch - 1) % 4].fetch_add(1000);\n      //We add to the current second the amount of milliseconds between the start of the current second and the releasedAt milliseconds\n      mNbMsMutexLocked.items[releasedAtSecondsSinceEpoch % 4].fetch_add(\n        std::chrono::milliseconds(releasedAtMsSinceEpoch -\n                                  (releasedAtSecondsSinceEpoch * 1000)).count());\n    } else if (blockedForSeconds >= 1) {\n      //The lock time is overlapping between the previous second and the current second\n      //compute lock time during last second to add it to last second\n      mNbMsMutexLocked.items[(releasedAtSecondsSinceEpoch - 1) % 4].fetch_add(\n        std::chrono::milliseconds((releasedAtSecondsSinceEpoch * 1000) -\n                                  acquiredAtMsSinceEpoch).count());\n      //Compute lock time during the current second and add it to the current second\n      mNbMsMutexLocked.items[(releasedAtSecondsSinceEpoch) % 4].fetch_add(\n        std::chrono::milliseconds(releasedAtMsSinceEpoch -\n                                  (releasedAtSecondsSinceEpoch * 1000)).count());\n    } else {\n      //The lock was acquired and released within the current second, just add the amount of milliseconds\n      mNbMsMutexLocked.items[(releasedAtSecondsSinceEpoch) % 4].fetch_add(\n        blockedForMs);\n    }\n\n    //Reset the next second lock time\n    mNbMsMutexLocked.items[(releasedAtSecondsSinceEpoch + 1) % 4].store(0);\n  }\n\n  const std::chrono::milliseconds\n  getNbMsMutexWriteLockedPenultimateSecond()\n  {\n    auto now = std::chrono::duration_cast<std::chrono::seconds>\n               (std::chrono::system_clock::now().time_since_epoch()).count();\n    //We take the amount of milliseconds the mutex was locked 2 seconds before the current second\n    return std::chrono::milliseconds(mNbMsMutexLocked.items[(now - 2) % 4].load(\n                                       std::memory_order_relaxed));\n  }\n\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n  typedef std::map<uint64_t, std::string> MapMutexNameT;\n  typedef std::map<std::thread::id, std::map<uint64_t, LOCK_T>> MapMutexOpT;\n  static const char* LOCK_STATE[];\n  static std::mutex sOpMutex;\n  static MapMutexNameT sMtxNameMap;\n  static MapMutexOpT sTidMtxOpMap;\n\n  struct TimingStats {\n    double averagewaitread;\n    double averagewaitwrite;\n    double minwaitwrite, maxwaitwrite;\n    double minwaitread, maxwaitread;\n    std::atomic<uint64_t> readLockCounterSample, writeLockCounterSample;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Performs the initialization of the class\n  //----------------------------------------------------------------------------\n  static void InitializeClass();\n\n  //----------------------------------------------------------------------------\n  //! Reset statistics at the class level\n  //----------------------------------------------------------------------------\n  static void ResetTimingStatisticsGlobal();\n\n  //----------------------------------------------------------------------------\n  //! Turn on/off timings at the class level\n  //----------------------------------------------------------------------------\n  inline static void SetTimingGlobal(bool on)\n  {\n    sEnableGlobalTiming = on;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the timing status at the class level\n  //----------------------------------------------------------------------------\n  inline static bool GetTimingGlobal()\n  {\n    return sEnableGlobalTiming;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Turn on/off order checking at the class level\n  //----------------------------------------------------------------------------\n  inline static void SetOrderCheckingGlobal(bool on)\n  {\n    sEnableGlobalOrderCheck = on;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the order checking status at the class level\n  //----------------------------------------------------------------------------\n  inline static bool GetOrderCheckingGlobal()\n  {\n    return sEnableGlobalOrderCheck;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Turn on/off deadlock checking\n  //----------------------------------------------------------------------------\n  inline static void SetDeadlockCheckingGlobal(bool on)\n  {\n    sEnableGlobalDeadlockCheck = on;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the global deadlock check status\n  //----------------------------------------------------------------------------\n  inline static bool GetDeadlockCheckingGlobal()\n  {\n    return sEnableGlobalDeadlockCheck;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the timing statistics at the class level\n  //----------------------------------------------------------------------------\n  static void GetTimingStatisticsGlobal(TimingStats& stats,\n                                        bool compensate = true);\n\n  //----------------------------------------------------------------------------\n  //! Compute the SamplingRate corresponding to a given CPU overhead\n  //!\n  //! @param overhead the ratio between the timing cost and the mutexing cost\n  //!\n  //! @return sampling rate (the ratio of mutex to time so that the argument\n  //!         value is not violated)\n  //----------------------------------------------------------------------------\n  static float GetSamplingRateFromCPUOverhead(const double& overhead);\n\n  //----------------------------------------------------------------------------\n  //! Compute the cost in time of taking timings so that it can be compensated\n  //! in the statistics\n  //!\n  //! @param loopsize size of the loop to estimate the compensation\n  //!\n  //! @return the compensation in nanoseconds\n  //----------------------------------------------------------------------------\n  static size_t EstimateTimingCompensation(size_t loopsize = 1e6);\n\n  //----------------------------------------------------------------------------\n  //! Compute the speed for lock/unlock cycle\n  //!\n  //! @param loopsize size of the loop to estimate the compensation\n  //!\n  //! @return duration of the cycle in nanoseconds\n  //----------------------------------------------------------------------------\n  static size_t EstimateLockUnlockDuration(size_t loopsize = 1e6);\n\n  //----------------------------------------------------------------------------\n  //! Compute the latency introduced by taking timings\n  //!\n  //! @param loopsize size of the loop to estimate the compensation\n  //! @param globaltiming enable global timing in the estimation\n  //!\n  //! @return latency in nanoseconds\n  //----------------------------------------------------------------------------\n  static size_t EstimateTimingAddedLatency(size_t loopsize = 1e6,\n      bool globaltiming = false);\n\n  //----------------------------------------------------------------------------\n  //! Compute the latency introduced by checking the mutexes locking orders\n  //! @param mutexes the number of nested mutexes in the loop\n  //! @param loopsize the size of the loop to estimate the compensation\n  //!\n  //! @return the latency in nanoseconds\n  //----------------------------------------------------------------------------\n  static size_t EstimateOrderCheckingAddedLatency(size_t nmutexes = 3,\n      size_t loopsize = 1e6);\n\n  //----------------------------------------------------------------------------\n  //! Remove an order checking rule\n  //! @param rulename name of the rule\n  //!\n  //! @return the number of rules removed (0 or 1)\n  //----------------------------------------------------------------------------\n  static int RemoveOrderRule(const std::string& rulename);\n\n  //----------------------------------------------------------------------------\n  //! Estimate latencies and compensation\n  //!\n  //! @param loopsize number of lock/unlock operations\n  //----------------------------------------------------------------------------\n  static void EstimateLatenciesAndCompensation(size_t loopsize = 1e6);\n\n  inline static size_t GetTimingCompensation()\n  {\n    return timingCompensation; // in nsec\n  }\n\n  inline static size_t GetOrderCheckingLatency()\n  {\n    return orderCheckingLatency; // in nsec\n  }\n\n  inline static size_t GetTimingLatency()\n  {\n    return timingLatency; // in nsec\n  }\n\n  static size_t GetLockUnlockDuration()\n  {\n    return lockUnlockDuration; // in nsec\n  }\n\n  //----------------------------------------------------------------------------\n  //! Add or overwrite an order checking rule\n  //! @param rulename  name of the rule\n  //! @param order vector containing the address of the RWMutex instances in the\n  //! locking order\n  //!\n  //! @return 0 if successful, otherwise -1\n  //----------------------------------------------------------------------------\n  static int AddOrderRule(const std::string& rulename,\n                          const std::vector<RWMutex*>& order);\n\n  //----------------------------------------------------------------------------\n  //! Reset order checking rules\n  //----------------------------------------------------------------------------\n  static void ResetOrderRule();\n\n  //----------------------------------------------------------------------------\n  //! Reset statistics at the instance level\n  //----------------------------------------------------------------------------\n  void ResetTimingStatistics();\n\n  //----------------------------------------------------------------------------\n  //! Turn on/off timings at the instance level\n  //----------------------------------------------------------------------------\n  inline void SetTiming(bool on)\n  {\n    mEnableTiming = on;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the timing status at the class level\n  //----------------------------------------------------------------------------\n  inline bool GetTiming()\n  {\n    return mEnableTiming;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set the name\n  //----------------------------------------------------------------------------\n  inline void SetDebugName(const std::string& name)\n  {\n    mName = name;\n    std::unique_lock<std::mutex> lock(sOpMutex);\n    sMtxNameMap[(uint64_t) GetRawPtr()] = name;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Enable sampling of timings\n  //! @param $first turns on or off the sampling\n  //! @param $second sampling between 0 and 1 (if <0, use the precomputed level\n  //! for the class, see GetSamplingRateFromCPUOverhead)\n  //----------------------------------------------------------------------------\n  void SetSampling(bool on, float rate = -1.0);\n\n  //----------------------------------------------------------------------------\n  //! Return the timing sampling rate/status\n  // @return the sample rate if the sampling is turned on, -1.0 if the sampling is off\n  //----------------------------------------------------------------------------\n  float GetSampling();\n\n  //----------------------------------------------------------------------------\n  //! Get the timing statistics at the instance level\n  //----------------------------------------------------------------------------\n  void GetTimingStatistics(TimingStats& stats, bool compensate = true);\n\n  //----------------------------------------------------------------------------\n  //! Check the orders defined by the rules and update\n  //----------------------------------------------------------------------------\n  void OrderViolationMessage(unsigned char rule, const std::string& message = \"\");\n\n  //----------------------------------------------------------------------------\n  //! Check the orders defined by the rules and update for a lock\n  //----------------------------------------------------------------------------\n  void CheckAndLockOrder();\n\n  //----------------------------------------------------------------------------\n  //!  Check the orders defined by the rules and update for an unlock\n  //----------------------------------------------------------------------------\n  void CheckAndUnlockOrder();\n\n  //----------------------------------------------------------------------------\n  //! Reset the order checking mechanism for the current thread\n  //----------------------------------------------------------------------------\n  void ResetCheckOrder();\n\n  //----------------------------------------------------------------------------\n  //! Enable/disable deadlock check\n  //!\n  //! @param on if true then enable, otherwise disable\n  //----------------------------------------------------------------------------\n  inline void SetDeadlockCheck(bool status)\n  {\n    mEnableDeadlockCheck = status;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check for deadlocks\n  //!\n  //! @param rd_lock true if this is about to take a read lock, otherwise false\n  //! @note it throws an exception if it causes a deadlock\n  //----------------------------------------------------------------------------\n  void EnterCheckDeadlock(bool rd_lock);\n\n  //----------------------------------------------------------------------------\n  //! Exit check for deadlocks\n  //!\n  //! @param rd_lock true if this is was read lock, otherwise false\n  //----------------------------------------------------------------------------\n  void ExitCheckDeadlock(bool rd_lock);\n\n  //----------------------------------------------------------------------------\n  //! Clear the data structures used for detecting deadlocks\n  //----------------------------------------------------------------------------\n  void DropDeadlockCheck();\n\n#ifdef __APPLE__\n  static int round(double number);\n#endif\n\n#endif\n\n\nprotected:\n  bool mBlocking;\n\nprivate:\n  IRWMutex* mMutexImpl;\n  struct timespec wlocktime;\n  std::atomic<uint64_t> mRdLockCounter;\n  std::atomic<uint64_t> mWrLockCounter;\n  std::atomic<uint64_t> mRdLockTime;\n  std::atomic<uint64_t> mWrLockTime;\n  std::atomic<uint64_t> mRdLockLeadTime;\n  std::atomic<uint64_t> mWrLockLeadTime;\n  bool mPreferRd; ///< If true reads go ahead of wr and are reentrant\n  int64_t mBlockedForInterval; // interval in ms after which we might stacktrace a long-lasted mutex\n  bool mBlockedStackTracing; // en-disable stacktracing long-lasted mutexes\n\n  /**\n   * Array of 4 elements representing the amount of milliseconds the mutex\n   * was locked per second\n   * This array therefore represent 4 seconds of run.\n   * - Second 3: represents the current second of run\n   * - Second 2: the last second before the current one\n   * - Second 1: the second that will be displayed by eos ns stat\n   * - Second 0: Will be set to 0 to reset the counter because this second\n   * will be the one to be considered after the current one\n   */\n  TimingArray mNbMsMutexLocked;\n\n  std::string mName;\n\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n\n  int mCounter;\n  int mSamplingModulo;\n  std::atomic<bool> mEnableTiming, mEnableSampling;\n  //! Specific type of counters\n  std::atomic<uint64_t> mRdMaxWait, mWrMaxWait, mRdMinWait, mWrMinWait;\n  std::atomic<uint64_t> mRdCumulatedWait, mWrCumulatedWait;\n  std::atomic<uint64_t> mRdLockCounterSample, mWrLockCounterSample;\n\n  std::map<std::thread::id, int> mThreadsRdLock; ///< Threads holding a read lock\n  std::set<std::thread::id> mThreadsWrLock; ///< Threads holding a write lock\n  pthread_mutex_t mCollectionMutex; ///< Mutex protecting the sets above\n  bool mEnableDeadlockCheck; ///< Check for deadlocks\n  std::atomic<bool> mTransientDeadlockCheck; ///< Enabled by the global flag\n\n  static bool staticInitialized;\n  static bool sEnableGlobalTiming;\n  static int sSamplingModulo;\n  static size_t timingCompensation, timingLatency, lockUnlockDuration;\n  static std::atomic<uint64_t> mRdMaxWait_static, mWrMaxWait_static;\n  static std::atomic<uint64_t> mRdMinWait_static, mWrMinWait_static;\n  static std::atomic<uint64_t> mRdLockCounterSample_static,\n         mWrLockCounterSample_static;\n  static std::atomic<uint64_t> mRdCumulatedWait_static, mWrCumulatedWait_static;\n\n  // Actual order checking\n  // Pointers referring to a memory location not thread specific so that if the\n  // thread terminates, this location is still valid. This flag is triggered by\n  // the class management and indicates to each thread to reset some thread\n  // specific stuff.\n  static thread_local bool* orderCheckReset_staticthread;\n  // Map containing the previously referenced flag for reset\n  static std::map<pthread_t, bool>* threadOrderCheckResetFlags_static;\n  // Each unsigned long is used as 64 bit flags to trace the lock status of\n  // mutexes for a given rule\n  static thread_local unsigned long\n  ordermask_staticthread[EOS_RWMUTEX_ORDER_NRULES];\n  // A mutex can be associated to up to EOS_RWMUTEX_ORDER_NRULES and the\n  // following array gives the locking rank for \"this\" RWMutex\n  unsigned char rankinrule[EOS_RWMUTEX_ORDER_NRULES];\n  // the number of rules \"this\" RWMutex\n  unsigned char nrules;\n\n  // ******** Order Rules management  *******\n  // Order rules management\n  // to issue the message and to manage the rules. Not involved in the online checking.\n  static std::map<unsigned char, std::string>* ruleIndex2Name_static;\n  static std::map<std::string, unsigned char>* ruleName2Index_static;\n  unsigned char ruleLocalIndexToGlobalIndex[EOS_RWMUTEX_ORDER_NRULES];\n  // rulename -> order\n  typedef std::map< std::string, std::vector<RWMutex*> > rules_t;\n  static rules_t* rules_static;\n  // lock to guarantee unique access to rules management\n  static pthread_rwlock_t mOrderChkLock;\n  static bool sEnableGlobalOrderCheck;\n  static bool sEnableGlobalDeadlockCheck;\n  static size_t orderCheckingLatency;\n#endif\n};\n\n// undefine the timer stuff\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n#undef EOS_RWMUTEX_TIMER_START\n#undef EOS_RWMUTEX_TIMER_STOP_AND_UPDATE\n#endif\n\n// For old clang avoid the use of builtin functions\n#if defined(__clang__) && defined(__clang_major__) && (__clang_major__ < 12)\n#define EOS_FUNCTION __FUNCTION__\n#define EOS_FILE     __FILE__\n#define EOS_LINE     __LINE__\n#else\n#define EOS_FUNCTION __builtin_FUNCTION()\n#define EOS_FILE     __builtin_FILE()\n#define EOS_LINE     __builtin_LINE()\n#endif\n\n//------------------------------------------------------------------------------\n//! Class RWMutexReadLock\n//------------------------------------------------------------------------------\nclass RWMutexWriteLock\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  RWMutexWriteLock():\n    mWrMutex(nullptr), mFile(\"unknown\"), mFunction(\"unknown\"), mLine(0)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param mutex mutex to lock for write\n  //! @param function caller function name, or empty string if not in the scope\n  //!        of a function\n  //! @param file caller file name, or empty string if not in the scope of a\n  //!        function\n  //! @param line caller line number in file\n  //----------------------------------------------------------------------------\n  RWMutexWriteLock(RWMutex& mutex,\n                   const char* function = EOS_FUNCTION,\n                   const char* file = EOS_FILE,\n                   int line = EOS_LINE);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~RWMutexWriteLock()\n  {\n    Release();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Grab mutex and write lock it\n  //!\n  //! @param mutex mutex to lock for write\n  //! @param function caller function name, or empty string if not in the scope\n  //!        of a function\n  //! @param file caller file name, or empty string if not in the scope of a\n  //!        function\n  //! @param line caller line number in file\n  //----------------------------------------------------------------------------\n  void Grab(RWMutex& mutex,\n            const char* function = EOS_FUNCTION,\n            const char* file = EOS_FILE,\n            int line = EOS_LINE);\n\n  //----------------------------------------------------------------------------\n  //! Release the write lock after grab\n  //----------------------------------------------------------------------------\n  void Release();\n\nprivate:\n  std::chrono::steady_clock::time_point mAcquiredAt;\n  std::chrono::system_clock::time_point mAcquiredAtSystem;\n  std::chrono::steady_clock::time_point mReleasedAt;\n  std::chrono::system_clock::time_point mReleasedAtSystem;\n  RWMutex* mWrMutex {nullptr};\n  const char* mFile;\n  const char* mFunction;\n  int mLine;\n};\n\n//------------------------------------------------------------------------------\n//! Class RWMutexReadLock\n//------------------------------------------------------------------------------\nclass RWMutexReadLock\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  RWMutexReadLock():\n    mRdMutex(nullptr), mFunction(\"unknown\"), mFile(\"unknown\"), mLine(0)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param mutex mutex to lock for read\n  //! @param function caller function name, or empty string if not in the scope\n  //!        of a function\n  //! @param file caller file name, or empty string if not in the scope of a\n  //!        function\n  //! @param line caller line number in file\n  //----------------------------------------------------------------------------\n  RWMutexReadLock(RWMutex& mutex,\n                  const char* function = EOS_FUNCTION,\n                  const char* file = EOS_FILE,\n                  int line = EOS_LINE);\n\n  //----------------------------------------------------------------------------\n  //! Grab mutex and read lock it\n  //!\n  //! @param mutex mutex to lock for read\n  //! @param function caller function name, or empty string if not in the scope\n  //!        of a function\n  //! @param file caller file name, or empty string if not in the scope of a\n  //!        function\n  //! @param line caller line number in file\n  //----------------------------------------------------------------------------\n  void Grab(RWMutex& mutex,\n            const char* function = EOS_FUNCTION,\n            const char* file = EOS_FILE,\n            int line = EOS_LINE);\n\n  //----------------------------------------------------------------------------\n  //! Release the read lock after grab\n  //----------------------------------------------------------------------------\n  void Release();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~RWMutexReadLock()\n  {\n    Release();\n  }\n\nprivate:\n  std::chrono::steady_clock::time_point mAcquiredAt;\n  RWMutex* mRdMutex {nullptr};\n  const char* mFunction;\n  const char* mFile;\n  int mLine;\n};\n\n//------------------------------------------------------------------------------\n//! RW Mutex prefereing the reader\n//------------------------------------------------------------------------------\nclass RWMutexR : public RWMutex\n{\npublic:\n  RWMutexR() : RWMutex(true) { }\n  virtual ~RWMutexR() {}\n};\n\n//------------------------------------------------------------------------------\n//! RW Mutex prefereing the writerr\n//------------------------------------------------------------------------------\nclass RWMutexW : public RWMutex\n{\npublic:\n  RWMutexW() : RWMutex(false) { }\n  virtual ~RWMutexW() {}\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/RateLimit.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file RateLimit.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/RateLimit.hh\"\n#include <chrono>\n#include <iostream>\n#include <stdexcept>\n\nusing namespace std::chrono;\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n//! Get delay for the current request\n//!\n//! @return delay in microseconds for the current requestor\n//----------------------------------------------------------------------------\nmicroseconds\nRequestRateLimit::GetDelay()\n{\n  // If no rate limit enforced then no delay\n  if (mRate == 0ull) {\n    return microseconds(0);\n  }\n\n  std::lock_guard<std::mutex> lock(mMutex);\n  // Compute expire timestamp which is one second back in time\n  uint64_t now_us = duration_cast<microseconds>\n                    (mClock.GetTime().time_since_epoch()).count();\n  // Round to the beginning of the interval\n  now_us = (now_us / mRateIntervalUs) * mRateIntervalUs;\n  uint64_t expire_us = now_us - 1000000;\n\n  // Try to expire old entries if any present\n  if (!mSetReqTimestamps.empty()) {\n    auto it = mSetReqTimestamps.lower_bound(expire_us);\n\n    if (it != mSetReqTimestamps.end()) {\n      // If current entry is after the expire timestamp we have to go one\n      // back unless this is the beginning\n      if ((it != mSetReqTimestamps.begin()) && (*it > expire_us)) {\n        --it;\n      }\n    }\n\n    if (it == mSetReqTimestamps.begin()) {\n      if (*it <= expire_us) {\n        (void) mSetReqTimestamps.erase(it);\n      }\n    } else {\n      // Delete all the entries before this iterator i.e. expired ones\n      for (auto it_del = mSetReqTimestamps.begin(); it_del != it; /*no inc*/) {\n        it_del = mSetReqTimestamps.erase(it_del);\n      }\n    }\n  }\n\n  uint64_t new_entry_us {now_us};\n  uint64_t num_requests = mSetReqTimestamps.size();\n  bool has_delay = false;\n\n  if (num_requests >= mRate) {\n    has_delay =  true;\n    // We need to wait for the first entry in the set to expire\n    new_entry_us = *mSetReqTimestamps.begin() + 1000000;\n    auto it = mSetReqTimestamps.lower_bound(new_entry_us);\n\n    // If that slot is taken already then move on to the next one\n    while (it != mSetReqTimestamps.end()) {\n      new_entry_us += mRateIntervalUs;\n      it = mSetReqTimestamps.lower_bound(new_entry_us);\n    }\n  } else {\n    // If the current slot is taken then try the next one\n    while (mSetReqTimestamps.find(new_entry_us) != mSetReqTimestamps.end()) {\n      new_entry_us += mRateIntervalUs;\n    }\n  }\n\n  mSetReqTimestamps.insert(new_entry_us);\n  uint64_t delay {0ull};\n\n  if (has_delay) {\n    delay = new_entry_us - now_us;\n  }\n\n  // This is for testing purposes only\n  if (mLastTimestampUs < new_entry_us) {\n    mLastTimestampUs = new_entry_us;\n  }\n\n  return microseconds(delay);\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/RateLimit.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file RateLimit.hh\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include \"common/SteadyClock.hh\"\n#include \"common/Logging.hh\"\n#include <atomic>\n#include <thread>\n#include <set>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Rate limit interface\n//------------------------------------------------------------------------------\nclass IRateLimit\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  IRateLimit(bool fake_clock = false):\n    mRate(0ull), mClock(fake_clock)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~IRateLimit() = default;\n\n  //----------------------------------------------------------------------------\n  //! Get rate\n  //!\n  //! @return enforced rate\n  //----------------------------------------------------------------------------\n  virtual unsigned long long GetRatePerSecond() const\n  {\n    return mRate;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set rate\n  //----------------------------------------------------------------------------\n  virtual void SetRatePerSecond(unsigned long long rate)\n  {\n    mRate = rate;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Allow method that might delay the current thread until it can proceed\n  //! processing prmits.\n  //!\n  //! @param permits number of permits requested\n  //!\n  //! @return microseconds that the current thread was delayed\n  //----------------------------------------------------------------------------\n  virtual uint64_t Allow(uint64_t permits = 1) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get clock reference for testing purposes\n  //----------------------------------------------------------------------------\n  inline eos::common::SteadyClock& GetClock()\n  {\n    return mClock;\n  }\n\nprotected:\n  std::atomic<unsigned long long> mRate; ///< Rate per second\n  eos::common::SteadyClock mClock; ///< Clock wrapper also used for testing\n};\n\n\n//------------------------------------------------------------------------------\n//! Requests per second rate limiter\n//------------------------------------------------------------------------------\nclass RequestRateLimit: public IRateLimit, public LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  RequestRateLimit(bool fake_clock = false):\n    IRateLimit(fake_clock), mLastTimestampUs(0ull)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~RequestRateLimit() = default;\n\n  //----------------------------------------------------------------------------\n  //! Set rate\n  //----------------------------------------------------------------------------\n  void SetRatePerSecond(unsigned long long rate)\n  {\n    if (rate > 1000000) {\n      eos_static_err(\"msg=\\\"attempt to set very high rate discarded\\\"\"\n                     \" current_rate=%llu failed_rate=%llu\", mRate.load(), rate);\n      return;\n    }\n\n    if (rate < 1) {\n      rate = 1;\n    }\n\n    mRate = rate;\n    mRateIntervalUs = 1000000 / rate;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Allow submitting a request\n  //!\n  //! @return milliseconds that the current thread was delayed\n  //----------------------------------------------------------------------------\n  uint64_t Allow(uint64_t permits = 1)\n  {\n    auto wait = GetDelay();\n\n    // When testing skip the actual sleepp\n    if (!mClock.IsFake()) {\n      std::this_thread::sleep_for(wait);\n    }\n\n    return wait.count();\n  }\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  //----------------------------------------------------------------------------\n  //! Get delay for the current request\n  //!\n  //! @return delay in microseconds for the current requestor\n  //----------------------------------------------------------------------------\n  std::chrono::microseconds GetDelay();\n\n  std::mutex mMutex; ///< Mutex protecting the set of timestamps\n  ///< Set of request timestamps in microsec for each request\n  std::set<uint64_t> mSetReqTimestamps;\n  uint64_t mRateIntervalUs; ///< Interval in microsec corresp. to the given rate\n  ///< Last timestamp of an entry added to the set - only for testing\n  std::atomic<uint64_t> mLastTimestampUs;\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/RegexWrapper.cc",
    "content": "//------------------------------------------------------------------------------\n// File: RegexWrapper.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/RegexWrapper.hh\"\n#include \"common/Logging.hh\"\n#include <mutex>\n#include <iostream>\n#include <memory>\n#include <regex.h>\n\nnamespace\n{\n//! Custom deleter for regex_t std::unique_ptr object\nauto regex_deleter = [](regex_t* ptr)\n{\n  regfree(ptr);\n  free(ptr);\n};\n//! unique_regex_t which is a unique pointer to a regex_t object\nusing unique_regex_t = std::unique_ptr<regex_t, decltype(regex_deleter)>;\nstd::mutex sRegexMutex; ///< Mutex protecting access to the map below\n//! Map string regex to regex_t objects\nstd::map<std::string, unique_regex_t> sMapRegex;\n\n//------------------------------------------------------------------------------\n//! Get regex expression based on the input string regex. The result is either\n//! computed on the fly or it's taken from the cache\n//!\n//! @param in_regex input string regex\n//!\n//! @return compiled regex expression or std::null_ptr in case of error\n//------------------------------------------------------------------------------\nregex_t* eos_get_regex(const std::string& in_regex)\n{\n  {\n    // This is the most common code path\n    std::unique_lock<std::mutex> lock(sRegexMutex);\n    auto it = sMapRegex.find(in_regex);\n\n    if (it != sMapRegex.end()) {\n      return it->second.get();\n    }\n  }\n  regex_t* re = static_cast<regex_t*>(malloc(sizeof(regex_t)));\n\n  if (regcomp(re, in_regex.c_str(), REG_EXTENDED | REG_NOSUB) != 0) {\n    eos_static_err(\"msg=\\\"failed to compile regex\\\" sregex=\\\"%s\\\"\",\n                   in_regex.c_str());\n    return nullptr; // error\n  }\n\n  unique_regex_t uniq_re(re, regex_deleter);\n  std::unique_lock<std::mutex> lock(sRegexMutex);\n  return sMapRegex.emplace(in_regex, std::move(uniq_re)).first->second.get();\n}\n}\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Wrapper for std::regex_match taking regular expression as std::string\n//------------------------------------------------------------------------------\nbool eos_regex_match(const std::string& input, const std::string& regex)\n{\n  if (regex.empty()) {\n    return false;\n  }\n\n  std::string lregex = regex;\n\n  // Make sure the regex pattern asks for a full match!\n  if (*lregex.begin() != '^') {\n    lregex.insert(0, \"^\");\n  }\n\n  if (*lregex.rbegin() != '$') {\n    lregex.append(\"$\");\n  }\n\n  regex_t* re = eos_get_regex(lregex);\n\n  if ((re == nullptr) ||\n      (regexec(re, input.c_str(), (size_t) 0, nullptr, 0) != 0)) {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Wrapper for std::regex_search taking regular expression as std::string\n//------------------------------------------------------------------------------\nbool eos_regex_search(const std::string& input, const std::string& regex)\n{\n  if (regex.empty()) {\n    return false;\n  }\n\n  regex_t re;\n\n  if (regcomp(&re, regex.c_str(), REG_EXTENDED | REG_NOSUB) != 0) {\n    return false;\n  }\n\n  int status = regexec(&re, input.c_str(), 0, nullptr, 0);\n  regfree(&re);\n\n  if (status != 0) {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Check if given input is a valid regex\n//------------------------------------------------------------------------------\nbool eos_regex_valid(const std::string& regex)\n{\n  regex_t re;\n\n  if (regcomp(&re, regex.c_str(), REG_EXTENDED | REG_NOSUB) != 0) {\n    return false;\n  }\n\n  regfree(&re);\n  return true;\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/RegexWrapper.hh",
    "content": "//------------------------------------------------------------------------------\n// File: RegexWrapper.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! Wrapper around std::regex to make sure all the std::regex_ calls are\n//! thread safe\n//------------------------------------------------------------------------------\n#pragma once\n#include \"common/Namespace.hh\"\n#include <regex>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Wrapper for std::regex_match taking regular expression as std::string\n//!\n//! @param input input string\n//! @param regex regular expression given as std::string\n//!\n//! @return true if there is a full match, otherwise false\n//------------------------------------------------------------------------------\nbool eos_regex_match(const std::string& input, const std::string& regex);\n\n//------------------------------------------------------------------------------\n//! Wrapper for std::regex_search taking regular expression as std::string\n//!\n//! @param input input string\n//! @param regex regular expression given as std::string\n//!\n//! @return true if there is any match inside input and any of its substrings,\n//!         otherwise false\n//------------------------------------------------------------------------------\nbool eos_regex_search(const std::string& input, const std::string& regex);\n\n//------------------------------------------------------------------------------\n//! Check if given input is a valid regex\n//!\n//! @param regex input that should represent a regex\n//!\n//! @return true if valid, otherwise false\n//------------------------------------------------------------------------------\nbool eos_regex_valid(const std::string& regex);\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Report.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Report.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Namespace.hh\"\n#include \"common/Report.hh\"\n#include \"common/RegexWrapper.hh\"\n\nnamespace\n{\nstd::string sLxplusRegex  {\"(lxplus)(.*)(.cern.ch)\"};\nstd::string sLxbatchRegex {\"(b)[789](.*)(.cern.ch)\"};\nstd::string sIPv4Regex {\"(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\"};\n}\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//!\n//! Create a Report object based on a report env representation\n//!\n//!  @param report\n//!\n//------------------------------------------------------------------------------\nReport::Report(XrdOucEnv& report)\n{\n  ots = report.Get(\"ots\") ? strtoull(report.Get(\"ots\"), 0, 10) : 0;\n  cts = report.Get(\"cts\") ? strtoull(report.Get(\"cts\"), 0, 10) : 0;\n  otms = report.Get(\"otms\") ? strtoull(report.Get(\"otms\"), 0, 10) : 0;\n  ctms = report.Get(\"ctms\") ? strtoull(report.Get(\"ctms\"), 0, 10) : 0;\n  logid = report.Get(\"log\") ? report.Get(\"log\") : \"\";\n  path = report.Get(\"path\") ? report.Get(\"path\") : \"\";\n  uid = (uid_t) atoi(report.Get(\"ruid\") ? report.Get(\"ruid\") : \"0\");\n  gid = (gid_t) atoi(report.Get(\"rgid\") ? report.Get(\"rgid\") : \"0\");\n  td = report.Get(\"td\") ? report.Get(\"td\") : \"none\";\n  host = report.Get(\"host\") ? report.Get(\"host\") : \"none\";\n  server_name = host;\n  server_domain = host;\n  auto dpos = host.find('.');\n\n  if (dpos != std::string::npos) {\n    server_name.erase(dpos);\n    server_domain.erase(0, dpos + 1);\n  }\n\n  lid = strtoul(report.Get(\"lid\") ? report.Get(\"lid\") : \"0\", 0, 10);\n  fid = strtoull(report.Get(\"fid\") ? report.Get(\"fid\") : \"0\", 0, 16);\n  fsid = strtoul(report.Get(\"fsid\") ? report.Get(\"fsid\") : \"0\", 0, 10);\n  rb = strtoull(report.Get(\"rb\") ? report.Get(\"rb\") : \"0\", 0, 10);\n  rb_min = strtoull(report.Get(\"rb_min\") ? report.Get(\"rb_min\") : \"0\", 0, 10);\n  rb_max = strtoull(report.Get(\"rb_max\") ? report.Get(\"rb_max\") : \"0\", 0, 10);\n  rb_sigma = strtoull(report.Get(\"rb_sigma\") ? report.Get(\"rb_sigma\") : \"0\", 0,\n                      10);\n  rv_op = strtoull(report.Get(\"rv_op\") ? report.Get(\"rv_op\") : \"0\", 0, 10);\n  rvb_min = strtoull(report.Get(\"rvb_min\") ? report.Get(\"rvb_min\") : \"0\", 0, 10);\n  rvb_max = strtoull(report.Get(\"rvb_max\") ? report.Get(\"rvb_max\") : \"0\", 0, 10);\n  rvb_sum = strtoull(report.Get(\"rvb_sum\") ? report.Get(\"rvb_sum\") : \"0\", 0, 10);\n  rvb_sigma = strtoull(report.Get(\"rvb_sigma\") ? report.Get(\"rvb_sigma\") : \"0\", 0,\n                       10);\n  rs_op = strtoull(report.Get(\"rs_op\") ? report.Get(\"rs_op\") : \"0\", 0, 10);\n  rsb_min = strtoull(report.Get(\"rsb_min\") ? report.Get(\"rsb_min\") : \"0\", 0, 10);\n  rsb_max = strtoull(report.Get(\"rsb_max\") ? report.Get(\"rsb_max\") : \"0\", 0, 10);\n  rsb_sum = strtoull(report.Get(\"rsb_sum\") ? report.Get(\"rsb_sum\") : \"0\", 0, 10);\n  rsb_sigma = strtoull(report.Get(\"rsb_sigma\") ? report.Get(\"rsb_sigma\") : \"0\", 0,\n                       10);\n  rc_min = strtoul(report.Get(\"rc_min\") ? report.Get(\"rc_min\") : \"0\", 0, 10);\n  rc_max = strtoul(report.Get(\"rc_max\") ? report.Get(\"rc_max\") : \"0\", 0, 10);\n  rc_sum = strtoul(report.Get(\"rc_sum\") ? report.Get(\"rc_sum\") : \"0\", 0, 10);\n  rc_sigma = strtoul(report.Get(\"rc_sigma\") ? report.Get(\"rc_sigma\") : \"0\", 0,\n                     10);\n  wb = strtoull(report.Get(\"wb\") ? report.Get(\"wb\") : \"0\", 0, 10);\n  wb_min = strtoull(report.Get(\"wb_min\") ? report.Get(\"wb_min\") : \"0\", 0, 10);\n  wb_max = strtoull(report.Get(\"wb_max\") ? report.Get(\"wb_max\") : \"0\", 0, 10);\n  wb_sigma = strtod(report.Get(\"wb_sigma\") ? report.Get(\"wb_sigma\") : \"0\", 0);\n  sfwdb = strtoull(report.Get(\"sfwdb\") ? report.Get(\"sfwdb\") : \"0\", 0, 10);\n  sbwdb = strtoull(report.Get(\"sbwdb\") ? report.Get(\"sbwdb\") : \"0\", 0, 10);\n  sxlfwdb = strtoull(report.Get(\"sxlfwd\") ? report.Get(\"sxlfwd\") : \"0\", 0, 10);\n  sxlbwdb = strtoull(report.Get(\"sxlbwd\") ? report.Get(\"sxlbwd\") : \"0\", 0, 10);\n  nrc = strtoull(report.Get(\"nrc\") ? report.Get(\"nrc\") : \"0\", 0, 10);\n  nwc = strtoull(report.Get(\"nwc\") ? report.Get(\"nwc\") : \"0\", 0, 10);\n  nfwds = strtoull(report.Get(\"nfwds\") ? report.Get(\"nfwds\") : \"0\", 0, 10);\n  nbwds = strtoull(report.Get(\"nbwds\") ? report.Get(\"nbwds\") : \"0\", 0, 10);\n  nxlfwds = strtoull(report.Get(\"nxlfwds\") ? report.Get(\"nxlfwds\") : \"0\", 0, 10);\n  nxlbwds = strtoull(report.Get(\"nxlbwds\") ? report.Get(\"nxlbwds\") : \"0\", 0, 10);\n  rt = atof(report.Get(\"rt\") ? report.Get(\"rt\") : \"0.0\");\n  rvt = atof(report.Get(\"rvt\") ? report.Get(\"rvt\") : \"0.0\");\n  wt = atof(report.Get(\"wt\") ? report.Get(\"wt\") : \"0.0\");\n  osize = strtoull(report.Get(\"osize\") ? report.Get(\"osize\") : \"0\", 0, 10);\n  csize = strtoull(report.Get(\"csize\") ? report.Get(\"csize\") : \"0\", 0, 10);\n  // sec extensions\n  sec_prot = report.Get(\"sec.prot\") ? report.Get(\"sec.prot\") : \"\";\n  sec_name = report.Get(\"sec.name\") ? report.Get(\"sec.name\") : \"\";\n  sec_host = report.Get(\"sec.host\") ? report.Get(\"sec.host\") : \"\";\n  sec_domain = report.Get(\"sec.host\") ? report.Get(\"sec.host\") : \"\";\n  dpos = sec_host.find(\".\");\n\n  if (!sec_host.empty() && sec_host.front() == '[' && sec_host.back() == ']') {\n    // ipv6\n    sec_domain = \"other-ipv6\";\n  } else {\n    if (eos_regex_match(sec_host, sIPv4Regex)) {\n      // ipv4\n      sec_domain = \"other-ipv4\";\n    } else {\n      if (eos_regex_match(sec_host, sLxbatchRegex)) {\n        // cern-batch\n        sec_domain = \"cern-batch\";\n        sec_host.erase(dpos);\n      } else {\n        if (eos_regex_match(sec_host, sLxplusRegex)) {\n          // cern-lxplus\n          sec_domain = \"cern-lxplus\";\n          sec_host.erase(dpos);\n        } else {\n          if (dpos != std::string::npos) {\n            // regular (sub-)domains\n            sec_domain.erase(0, dpos + 1);\n            sec_host.erase(dpos);\n          } else {\n            sec_domain = \"other\";\n          }\n        }\n      }\n    }\n  }\n\n  sec_vorg = report.Get(\"sec.vorg\") ? report.Get(\"sec.vorg\") : \"\";\n  sec_role = report.Get(\"sec.role\") ? report.Get(\"sec.role\") : \"\";\n  sec_info = report.Get(\"sec.info\") ? report.Get(\"sec.info\") : \"\";\n  sec_app = report.Get(\"sec.app\") ? report.Get(\"sec.app\") : \"\";\n\n  if (sec_app.find('?') != std::string::npos) {\n    sec_app.erase(sec_app.find('?'));\n  }\n\n  // tpc extensions\n  tpc_src = report.Get(\"tpc.src\") ? report.Get(\"tpc.src\") : \"\";\n  tpc_dst = report.Get(\"tpc.dst\") ? report.Get(\"tpc.dst\") : \"\";\n  tpc_src_lfn = report.Get(\"tpc.src_lfn\") ? report.Get(\"tpc.src_lfn\") : \"\";\n  // deletion specific entries\n  dsize = strtoull(report.Get(\"dsize\") ? report.Get(\"dsize\") : \"0\", 0, 10);\n  dc_tns = report.Get(\"dc_tns\") ? strtoull(report.Get(\"dc_tns\"), 0, 10) : 0;\n  dm_tns = report.Get(\"dm_tns\") ? strtoull(report.Get(\"dm_tns\"), 0, 10) : 0;\n  da_tns = report.Get(\"da_tns\") ? strtoull(report.Get(\"da_tns\"), 0, 10) : 0;\n  dc_ts = report.Get(\"dc_t\") ? strtoull(report.Get(\"dc_t\"), 0, 10) : 0;\n  dm_ts = report.Get(\"dm_t\") ? strtoull(report.Get(\"dm_t\"), 0, 10) : 0;\n  da_ts = report.Get(\"da_t\") ? strtoull(report.Get(\"da_tns\"), 0, 10) : 0;\n}\n\n\n//------------------------------------------------------------------------------\n//!\n//! Dump the report contents into a string in human readable key=value format\n//!\n//! @param out string containing the report\n//! @param dumpsec if true dump also sec info\n//!\n//------------------------------------------------------------------------------\nvoid\nReport::Dump(XrdOucString& out, bool dumpsec, bool dumptpc)\n{\n  char dumpline[16384];\n  snprintf(dumpline, sizeof(dumpline) - 1,\n           \"uid=%d gid=%d rb=%llu rb_min=%llu rb_max=%llu rb_sigma=%.02f \"\n           \"rv_op=%llu rvb_min=%llu rvb_max=%llu rvb_sum=%llu rvb_sigma=%.02f \"\n           \"rs_op=%llu rsb_min=%llu rsb_max=%llu rsb_sum=%llu rsb_sigma=%.02f \"\n           \"rc_min=%lu rc_max=%lu rc_sum=%lu rc_sigma=%.02f \"\n           \"wb=%llu wb_min=%llu wb_max=%llu wb_sigma=%.02f sfwdb=%llu \"\n           \"sbwdb=%llu sxlfwdb=%llu sxlbwdb=%llu nrc=%llu nwc=%llu \"\n           \"nfwds=%llu nbwds=%llu nxlfwds=%llu nxlbwds=%llu rt=%.02f rvt=%.02f\"\n           \"wt=%.02f osize=%llu csize=%llu ots=%llu.%llu cts=%llu.%llu \"\n           \"td=%s host=%s logid=%s\",\n           uid, gid, rb, rb_min, rb_max, rb_sigma,\n           rv_op, rvb_min, rvb_max, rvb_sum, rvb_sigma,\n           rs_op, rsb_min, rsb_max, rsb_sum, rsb_sigma,\n           rc_min, rc_max, rc_sum, rc_sigma,\n           wb, wb_min, wb_max, wb_sigma, sfwdb,\n           sbwdb, sxlfwdb, sxlbwdb, nrc, nwc,\n           nfwds, nbwds, nxlfwds, nxlbwds, rt, rvt,\n           wt, osize, csize, ots, otms, cts, ctms,\n           td.c_str(), host.c_str(), logid.c_str());\n  out += dumpline;\n\n  if (dumpsec) {\n    snprintf(dumpline, sizeof(dumpline) - 1,\n             \" sec_prot=\\\"%s\\\" sec_name=\\\"%s\\\" sec_host=\\\"%s\\\" \"\n             \"sec_vorg=\\\"%s\\\" sec_grps=\\\"%s\\\" sec_role=\\\"%s\\\" \"\n             \"sec_info=\\\"%s\\\" sec_app=\\\"%s\\\"\",\n             sec_prot.c_str(), sec_name.c_str(), sec_host.c_str(),\n             sec_vorg.c_str(), sec_grps.c_str(), sec_role.c_str(),\n             sec_info.c_str(), sec_app.c_str());\n    out += dumpline;\n  }\n\n  if (dumptpc) {\n    snprintf(dumpline, sizeof(dumpline) - 1,\n             \" tpc_src=\\\"%s\\\" tpc_dst=\\\"%s\\\" tpc_src_lfn=\\\"%s\\\"\",\n             tpc_src.c_str(), tpc_dst.c_str(), tpc_src_lfn.c_str());\n    out += dumpline;\n  }\n\n  out += \"\\n\";\n}\n/*----------------------------------------------------------------------------*/\n\nEOSCOMMONNAMESPACE_END\n\n"
  },
  {
    "path": "common/Report.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Report.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSCOMMON_REPORT__\n#define __EOSCOMMON_REPORT__\n\n#include \"common/Namespace.hh\"\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <vector>\n#include <string>\n\nclass XrdOucEnv;\nclass XrdOucString;\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class storing file transaction reports constructed by a FST report string\n//------------------------------------------------------------------------------\n\nclass Report\n{\n  // ---------------------------------------------------------------------------\n  // the creator of the XrdOucEnv input is defined in XrdFstOfsFile.hh MakeReportEnv\n  // ---------------------------------------------------------------------------\nprivate:\n\npublic:\n  unsigned long long ots;  //< timestamp of open\n  unsigned long long cts;  //< timestamp of close\n  unsigned long long otms; //< ms of open\n  unsigned long long ctms; //< ms of close\n  std::string logid;       //< logid\n  std::string path;        //< logical path or replicate:<fid>\n  uid_t uid;               //< user id\n  gid_t gid;               //< group id\n  std::string td;          //< trace identifier\n  std::string host;        //< server host\n  std::string server_name; //< server name without domain\n  std::string server_domain;//< server domain without server name\n  unsigned long lid;       //< layout id\n  unsigned long long fid;  //< file id\n  unsigned long fsid;      //< filesystem id\n  unsigned long long rb;   //< bytes read\n  unsigned long long rb_min;    //< bytes read min\n  unsigned long long rb_max;    //< bytes read max\n  double             rb_sigma;  //< bytes read sigma\n  unsigned long long rv_op;     ///< number of readv operations\n  unsigned long long rvb_min;   ///< readv min bytes\n  unsigned long long rvb_max;   ///< readv max bytes\n  unsigned long long rvb_sum;   ///< total readv bytes requested\n  double             rvb_sigma; ///< sigma readv bytes\n  unsigned long long rs_op;     ///< number of single read op from readv req.\n  unsigned long long rsb_min;   ///< single read min bytes\n  unsigned long long rsb_max;   ///< single read max bytes\n  unsigned long long rsb_sum;   ///< total single read bytes\n  double             rsb_sigma; ///< sigma single reads requested\n  unsigned long      rc_min;    ///< min number of reads in a readv request\n  unsigned long      rc_max;    ///< max number of reads in a readv request\n  unsigned long      rc_sum;    ///< total number of reads from readv req.\n  double             rc_sigma;  ///< sigma number of reads from read req.\n  unsigned long long wb;       //< bytes written\n  unsigned long long wb_min;   //< bytes written min\n  unsigned long long wb_max;   //< bytes written max\n  double             wb_sigma; //< bytes written sigma\n  unsigned long long sfwdb;  //< seeked bytes forward\n  unsigned long long sbwdb;  //< seeked bytes backward\n  unsigned long long sxlfwdb;  //< seeked bytes forward in seeks >4M\n  unsigned long long sxlbwdb;  //< seeked bytes backward in seeks >4M\n  unsigned long long nrc;  //< number of read calls\n  unsigned long long nwc;  //< number of write calls\n  unsigned long long nfwds;  //< number of forward seeks\n  unsigned long long nbwds;  //< number of backwards seeks\n  unsigned long long nxlfwds;  //< number of large forward seeks\n  unsigned long long nxlbwds;  //< number of large backwards eeks\n  float rt;                ///< disk time spent for read\n  float rvt;               ///< disk time spent for readv\n  float wt;                ///< disk time spent for write\n  unsigned long long osize;//< size when file was opened\n  unsigned long long csize;//< size when file was closed\n\n  // deletion specific entries\n  unsigned long long dsize; //< size of a delete file\n  unsigned long long dc_ts;  //< timestamp of change time\n  unsigned long long dc_tns;  //< timestamp of change time\n  unsigned long long dm_ts;  //< timestamp of access time\n  unsigned long long dm_tns;  //< timestamp of access time\n  unsigned long long da_ts;  //< timestamp of access time\n  unsigned long long da_tns;  //< timestamp of access time\n\n  // SecEntity fields\n  std::string sec_prot;    //< auth protocol\n  std::string sec_name;    //< auth name\n  std::string sec_host;    //< auth client host\n  std::string sec_domain;  //< auth domain\n  std::string sec_vorg;    //< auth vorg\n  std::string sec_grps;    //< auth grps\n  std::string sec_role;    //< auth role\n  std::string sec_info;    //< auth info (=dn if moninfo configured in GSI plugin)\n  std::string sec_app;     //< auth application\n\n  // TPC fields\n  std::string tpc_src;      //< source of the TPC transfer\n  std::string tpc_dst;      //< destination of the TPC transfer\n  std::string tpc_src_lfn;  //< source logical file name\n\n  // ---------------------------------------------------------------------------\n  //! Constructor by report env\n  // ---------------------------------------------------------------------------\n  Report(XrdOucEnv& report);\n\n  // ---------------------------------------------------------------------------\n  //! Destructor\n  // ---------------------------------------------------------------------------\n  ~Report() {};\n\n  // ---------------------------------------------------------------------------\n  //! Dump the report contents into a string\n  // ---------------------------------------------------------------------------\n  void Dump(XrdOucString& out, bool dumpsec = false, bool dumptpc = false);\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/SecEntity.hh",
    "content": "// ----------------------------------------------------------------------\n// File: SecEntity.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSCOMMON_SECENTITY__HH__\n#define __EOSCOMMON_SECENTITY__HH__\n\n#include \"common/Namespace.hh\"\n#include \"common/StringConversion.hh\"\n#include <XrdSec/XrdSecEntity.hh>\n#include <map>\n#include <string>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Convenience Class to serialize/deserialize sec entity information\n//------------------------------------------------------------------------------\nclass SecEntity\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Convert the fields of a sec entity into val1|val2|val3 ...\n  //----------------------------------------------------------------------------\n  static std::string ToKey(const XrdSecEntity* entity, const char* app)\n  {\n    std::string s = \"\";\n\n    if (entity) {\n      // @todo(esindril) Yet another workaround for XrdTpc in pull mode which\n      // does not populate the \"prot\" field of the XrdSecEntity object - to be\n      // reviewed in XRootD5\n      if (strlen(entity->prot) == 0) {\n        s += \"https\";\n      } else {\n        s += entity->prot;\n      }\n\n      s += \"|\";\n      s += (entity->name) ? entity->name : \"\";\n      s += \"|\";\n      s += (entity->host) ? entity->host : \"\";\n      s += \"|\";\n      s += (entity->vorg) ? entity->vorg : \"\";\n      s += \"|\";\n      s += (entity->grps) ? entity->grps : \"\";\n      s += \"|\";\n      s += (entity->role) ? entity->role : \"\";\n      s += \"|\";\n      s += (entity->moninfo) ? entity->moninfo : \"\";\n      s += \"|\";\n    } else {\n      s += \"sss|eos|eos|-|-|-|-|\";\n    }\n\n    s += (app) ? app : \"\";\n    return s;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Convert the fields of a sec entity into a nice debug string ...\n  //----------------------------------------------------------------------------\n  static std::string ToString(const XrdSecEntity* entity, const char* app)\n  {\n    std::string s = \"sec.prot=\";\n\n    if (entity) {\n      // @todo(esindril) Yet another workaround for XrdTpc in pull mode which\n      // does not populate the \"prot\" field of the XrdSecEntity object - to be\n      // reviewed in XRootD5\n      if (strlen(entity->prot) == 0) {\n        s += \"https\";\n      } else {\n        s += entity->prot;\n      }\n\n      s += \" sec.name=\\\"\";\n      s += (entity->name) ? entity->name : \"\";\n      s += \"\\\" sec.host=\\\"\";\n      s += (entity->host) ? entity->host : \"\";\n      s += \"\\\" sec.vorg=\\\"\";\n      s += (entity->vorg) ? entity->vorg : \"\";\n      s += \"\\\" sec.grps=\\\"\";\n      s += (entity->grps) ? entity->grps : \"\";\n      s += \"\\\" sec.role=\\\"\";\n      s += (entity->role) ? entity->role : \"\";\n      s += \"\\\" sec.info=\\\"\";\n      s += (entity->moninfo) ? entity->moninfo : \"\";\n      s += \"\\\"\";\n    } else {\n      s += \"sec.name=\\\"<none>\\\"\";\n    }\n\n    s += \" sec.app=\\\"\";\n    s += (app) ? app : \"\";\n    s += \"\\\"\";\n    return s;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Convert val1|val2|val3... to a map with key/val pairs\n  //----------------------------------------------------------------------------\n  static std::map<std::string, std::string> KeyToMap(std::string entitystring)\n  {\n    std::vector<std::string> tokens;\n    eos::common::StringConversion::EmptyTokenize(entitystring, tokens, \"|\");;\n    std::map<std::string, std::string> mp;\n    mp[\"prot\"] = tokens[0];\n    mp[\"name\"] = tokens[1];\n    mp[\"host\"] = tokens[2];\n    mp[\"vorg\"] = tokens[3];\n    mp[\"grps\"] = tokens[4];\n    mp[\"role\"] = tokens[5];\n    mp[\"info\"] = tokens[6];\n    mp[\"app\"] = tokens[7];\n    return mp;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Convert val1|val2|val3... to an env string, optional sec_app allows to\n  //! overwrite the sec.info field\n  //!\n  //! @param s input sec entity encoded by the ToKey method above\n  //! @param is_tpc flag to signal TPC transfer\n  //----------------------------------------------------------------------------\n  static std::string ToEnv(const char* s, bool is_tpc = false)\n  {\n    if (!s) {\n      return \"\";\n    }\n\n    std::vector<std::string> tokens;\n    std::string entitystring = s;\n    eos::common::StringConversion::EmptyTokenize(entitystring, tokens, \"|\");\n    std::string rs = \"sec.prot=\";\n\n    if (tokens.size() > 7) {\n      rs += tokens[0];\n      rs += \"&sec.name=\";\n      rs += tokens[1];\n      rs += \"&sec.host=\";\n      rs += tokens[2];\n      rs += \"&sec.vorg=\";\n      rs += tokens[3];\n      rs += \"&sec.grps=\";\n      rs += tokens[4];\n      rs += \"&sec.role=\";\n      rs += tokens[5];\n      rs += \"&sec.info=\";\n      rs += tokens[6];\n      rs += \"&sec.app=\";\n\n      if ((tokens[7].empty() || tokens[7] == \"-\") && is_tpc) {\n        rs += \"tpc\";\n      } else {\n        rs += tokens[7];\n      }\n    } else {\n      fprintf(stderr, \"[eos::common::SecEntity::ToEnv] error: %s has illegal \"\n              \"contents [%d]\\n\", s, (int)tokens.size());\n    }\n\n    return rs;\n  }\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/ShardedCache.hh",
    "content": "//----------------------------------------------------------------------\n// File: ShardedCache.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef SHARDED_CACHE__HH__\n#define SHARDED_CACHE__HH__\n\n#include \"common/AssistedThread.hh\"\n#include <algorithm>\n#include <memory>\n#include <vector>\n#include <mutex>\n#include <map>\n#include <unordered_map>\n#include <numeric>\n\nusing Milliseconds = int64_t;\n\n// A generic copy-on-write sharded cache with configurable hash function, and\n// automatic garbage collection.\n//\n// That's a lot of buzzwords.\n// 1. Sharding: Concurrent clients can perform operations at the same time\n//    without blocking each other, as long as they're hitting different shards.\n// 2. Copy-on-write: Clients always get an immutable snapshot of the data in the\n//    form of a shared pointer. No need to worry about locks or races after\n//    acquiring such a snapshot.\n// 3. Hashing: You can specify a custom hashing function to map from Key -> shard id.\n// 4. Garbage collection: Thanks to shared pointers, we can keep track of how many\n//    references currently exist for each element in the cache by calling use_count.\n//\n//    Garbage collection is done in two passes.\n//    - Every N seconds, we go through the entire contents. If an element exists\n//      only in our cache, we mark it as unused, but we don't remove it yet.\n//    - If this element is retrieved after that, we unset the mark.\n//    - If during the next pass the mark is still there, it means it hasn't been\n//      used for at least N seconds, so we evict it.\n//     In case a ttl is not supplied at start, the GC thread is not started. This\n//     way this can just function as regular non expiring concurrent map\n//\n//     By default, we choose the underlying map to be unordered_map, but you can\n//     force std::map by passing the 4th template parameter to true.\n\ntemplate<typename Key>\nstruct IdentityHash {\n  static uint64_t hash(const Key& key)\n  {\n    return key;\n  }\n};\n\ntemplate <typename Key>\nstruct DefaultHash {\n  static uint64_t hash(const Key& key)\n  {\n    return std::hash<Key>()(key);\n  }\n};\n\ntemplate<typename Key, typename Value, typename Hash = DefaultHash<Key>,\n         bool isUnordered = true>\nclass ShardedCache\n{\nprivate:\n  class ShardGuard\n  {\n  public:\n    ShardGuard(ShardedCache* cache, const Key& key)\n    {\n      shardId = cache->calculateShard(key);\n      mtx = &cache->mMutexes[shardId];\n      mtx->lock();\n    }\n\n    int64_t getShard() const\n    {\n      return shardId;\n    }\n\n    ~ShardGuard()\n    {\n      mtx->unlock();\n    }\n  private:\n    std::mutex* mtx;\n    int64_t shardId;\n  };\n\n  struct CacheEntry {\n    std::shared_ptr<Value> value;\n    bool marked;\n\n    CacheEntry() = default;\n\n    explicit CacheEntry(Value value_) : value(std::make_shared<Value>(value_)),\n                                        marked(false) {}\n  };\n\npublic:\n\n  template <typename... Args>\n  using MapT = typename std::conditional_t<isUnordered,\n        std::unordered_map<Args...>,\n        std::map<Args...>>;\n\n  using shard_map_t = MapT<Key, CacheEntry>;\n  using key_type = Key;\n  using value_type = CacheEntry;\n\n  int64_t calculateShard(const Key& key) const\n  {\n    return Hash::hash(key) % mNumShards;\n  }\n\n  // ShardedCache without a GC thread!\n  explicit ShardedCache(uint8_t shardBits_) : mNumShards(1UL << shardBits_),\n    mMutexes(mNumShards),\n    mContents(mNumShards)\n  {\n  }\n\n  // TTL is approximate. An element can stay while unused from [ttl, 2*ttl]\n  ShardedCache(uint8_t shardBits_, Milliseconds ttl_,\n               std::string_view name_ = \"ShardedCacheGC\")\n    :  mNumShards(1UL << shardBits_), mTTL(ttl_),\n       mMutexes(mNumShards), mContents(mNumShards),\n       mThreadName(name_.substr(0, 15))\n  {\n    mCleanupThread.reset(&ShardedCache::garbageCollector, this);\n  }\n\n  void reset_cleanup_thread(Milliseconds ttl_,\n                            std::string_view name_ = \"ShardedCacheGC\")\n  {\n    mTTL = ttl_;\n    mThreadName = name_.substr(0, 15);\n    mCleanupThread.reset(&ShardedCache::garbageCollector, this);\n  }\n\n  ~ShardedCache()\n  {\n    mCleanupThread.join();\n  }\n\n  // Retrieves an item from the cache. If there isn't any, return a null shared_ptr.\n  std::shared_ptr<Value> retrieve(const Key& key)\n  {\n    ShardGuard guard(this, key);\n    auto it = mContents[guard.getShard()].find(key);\n\n    if (it == mContents[guard.getShard()].end()) {\n      return std::shared_ptr<Value>();\n    }\n\n    // if(it->first == 4) std::cerr << \"erasing \" << it->first << std::endl;\n    it->second.marked = false;\n    return it->second.value;\n  }\n\n  bool contains(const Key& key)\n  {\n    ShardGuard guard(this, key);\n    auto it = mContents[guard.getShard()].find(key);\n    return it != mContents[guard.getShard()].end();\n  }\n\n  // Calling this function means giving up ownership of the pointer.\n  // Don't use it anymore and don't call delete on it!\n  // Return value: whether insertion was successful.\n  bool store(const Key& key, std::unique_ptr<Value> value, std::shared_ptr<Value>\n             & retval, bool replace = true)\n  {\n    CacheEntry entry;\n    entry.marked = false;\n    entry.value = std::move(value);\n    ShardGuard guard(this, key);\n\n    if (replace) {\n      mContents[guard.getShard()][key] = entry;\n      retval = entry.value;\n      return true;\n    }\n\n    auto status = mContents[guard.getShard()].insert(std::pair<Key, CacheEntry>(key,\n                  entry));\n    retval = status.first->second.value;\n    return status.second;\n  }\n\n  // store overload without retval\n  bool store(const Key& key, std::unique_ptr<Value> value, bool replace = true)\n  {\n    std::shared_ptr<Value> val;\n    return store(key, std::move(value), val, replace);\n  }\n\n  // store overload with const retval.\n  bool store(const Key& key, std::unique_ptr<Value> value,\n             std::shared_ptr<const Value>& retval, bool replace = true)\n  {\n    std::shared_ptr<Value> val;\n    bool status = store(key, std::move(value), val, replace);\n    retval = val;\n    return status;\n  }\n\n  /**\n   * @brief      Increment the value by a given argument safely. In case\n   * the key exists, we increment by the given argument, otherwise\n   * we create a key with supplied value.\n   * @param[in]  key: key to retrieve value from.\n   * @param[in]  inc_val: incremental value to add to the existing value or\n   *             the value in case key doesn't exist. We assume the value type\n   *             supporting + would be a simple type to copy, so only the value\n   *             overload is provided atm instead of the reference overloads.\n   * @return     The old value before increment\n   */\n  Value fetch_add(const Key& key, Value inc_val)\n  {\n    ShardGuard guard(this, key);\n    auto shard = guard.getShard();\n    Value old_val{};\n    auto it = mContents[shard].find(key);\n    if (it != mContents[shard].end()) {\n      Value* value = it->second.value.get();\n      old_val = *value;\n      *value += inc_val;\n    } else {\n      mContents[shard].emplace(key, CacheEntry(inc_val));\n    }\n    return old_val;\n  }\n\n  // Removes an element from the cache. Return value is whether the key existed.\n  // If you want to replace an entry, just call store with replace set to false.\n  bool invalidate(const Key& key)\n  {\n    ShardGuard guard(this, key);\n    auto it = mContents[guard.getShard()].find(key);\n\n    if (it == mContents[guard.getShard()].end()) {\n      return false;\n    }\n\n    mContents[guard.getShard()].erase(it);\n    return true;\n  }\n\n  void clear()\n  {\n    for (size_t i = 0; i < mContents.size(); ++i) {\n      std::lock_guard guard(mMutexes[i]);\n      mContents[i].clear();\n    }\n  }\n\n  // Some observer functions for validation, and in cases where we need to know\n  // cache sizes\n  size_t num_shards() const\n  {\n    return mNumShards;\n  }\n\n  size_t num_entries() const\n  {\n    size_t count = 0;\n\n    for (size_t i = 0; i < mContents.size(); ++i) {\n      std::lock_guard guard(mMutexes[i]);\n      count += mContents[i].size();\n    }\n\n    return count;\n  }\n\n  size_t num_content_shards() const\n  {\n    return mContents.size();\n  }\n\n  /**\n   * @brief      Get a copy of contents of a given shard\n   * @param[in]  shard number\n   * @return     A map with the values copied out of their shared_ptr, so lifetimes\n   * will not be affected! The map type is the same as the underlying map type,\n   * which could be unordered or std::map depending on the template parameter.\n   */\n  MapT<Key, Value> get_shard(size_t shard) const\n  {\n    if (shard >= mContents.size()) {\n      throw std::out_of_range(\"trying to access non-existent shard\");\n    }\n\n    MapT<Key, Value> ret;\n    std::lock_guard guard(mMutexes[shard]);\n    std::transform(mContents[shard].begin(), mContents[shard].end(),\n                   std::inserter(ret, ret.end()),\n    [](const auto & pair) {\n      return std::make_pair(pair.first, *pair.second.value);\n    });\n    return ret;\n  }\n\n  // Note this must be called before reset_cleanup_thread\n  void set_force_expiry(bool force_expiry, uint16_t force_expiry_cycles = 2)\n  {\n    mForceExpiryCycles = force_expiry_cycles;\n    mForceExpiry.store(force_expiry, std::memory_order_release);\n  }\n\nprivate:\n  size_t mNumShards;\n  Milliseconds mTTL;\n  mutable std::vector<std::mutex> mMutexes;\n  std::vector<MapT<Key, CacheEntry>> mContents;\n  std::string mThreadName;\n  std::atomic<bool> mForceExpiry {false};\n  uint16_t mForceExpiryCycles {2};\n  uint16_t mForceExpiryCounter {0};\n  AssistedThread mCleanupThread;\n\n  // Sweep through all entries in all shards to either mark them as unused or\n  // remove them\n  void collectorPass()\n  {\n    for (size_t i = 0; i < mNumShards; i++) {\n      std::lock_guard<std::mutex> lock(mMutexes[i]);\n\n      for (auto iterator = mContents[i].begin();\n           iterator != mContents[i].end(); /* no increment */) {\n        if (iterator->second.marked) {\n          iterator = mContents[i].erase(iterator);\n          continue;\n        }\n\n        if (iterator->second.value.use_count() == 1) {\n          iterator->second.marked = true;\n        }\n\n        iterator++;\n      }\n    }\n  }\n\n  void garbageCollector(ThreadAssistant& assistant)\n  {\n    ThreadAssistant::setSelfThreadName(mThreadName);\n\n    while (!assistant.terminationRequested()) {\n      assistant.wait_for(std::chrono::milliseconds(mTTL));\n\n      if (assistant.terminationRequested()) {\n        return;\n      }\n\n      collectorPass();\n      if (mForceExpiry.load(std::memory_order_acquire)) {\n        if (++mForceExpiryCounter == mForceExpiryCycles) {\n          clear();\n          mForceExpiryCounter = 0;\n        }\n      }\n    }\n  }\n};\n\n#endif\n"
  },
  {
    "path": "common/SharedCallbackList.hh",
    "content": "//------------------------------------------------------------------------------\n// File: SharedCallbackList.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n#include <memory>\n#include <functional>\n#include <mutex>\n#include <map>\n#include <vector>\n\n/*\n * @brief a Class to hold a list of std::function<Ret<Args...>> objects, this\n * class is thread safe to call from multiple threads, the member function\n * getCallbacks returns a vector of weak_ptrs to the callbacks, in this way\n * we don't need to expose the internal list sync. mutex outside,\n * since you'd use the shared_ptr's internal lock to realize the weak_ptr at callsite\n\n */\n// slot index of where we store the callback, this is indexed from 1;\nusing shared_callback_slot_t = uint32_t;\n\ntemplate <typename Ret, typename... Args>\nclass SharedRetCallbackList\n{\npublic:\n\n  using callable_t = std::function<Ret(Args...)>;\n\n  virtual ~SharedRetCallbackList() = default;\n\n  /*!\n   * add a callback function to the list of functions we hold\n   * @tparam F the type of callable\n   * @param f actual callable\n   * @return a tag that you'd need to erase a callback\n   */\n  template <typename F>\n  [[nodiscard]] shared_callback_slot_t\n  addCallback(F&& f)\n  {\n    std::lock_guard lg{mtx};\n    callables.emplace(++index,\n                      std::make_shared<callable_t>(std::forward<F>(f)));\n    return index;\n  }\n\n  /*!\n   * Get all the callbacks stored in the list\n   * @return a vector of weak_ptr to a std::function object, at the callsite\n   * you'd do a weak_ptr.lock() to ensure that the shared_ptr is still valid,\n   * this allows for invoking callbacks without holding the internal callables mutex\n   * as this mutex is only for protecting the list itself. We also cross check and reap\n   * any invalid callables while building this vector. Since we no longer iterate\n   * the internal map, this vector should be threadsafe to call\n   * regardless of addition/deletion of callbacks\n   *\n   */\n  std::vector<std::weak_ptr<callable_t>>\n                                      getCallbacks()\n  {\n    std::vector<std::weak_ptr<callable_t>> result;\n    {\n      std::lock_guard lg{mtx};\n\n      for (auto it = callables.begin(), last = callables.end(); it != last;) {\n        std::weak_ptr<callable_t> cb = it->second;\n\n        if (auto sp = cb.lock()) {\n          result.emplace_back(cb);\n          ++it;\n        } else {\n          it = callables.erase(it);\n        }\n      }\n    }\n    return result;\n  }\n\n  void\n  rmCallback(shared_callback_slot_t slot)\n  {\n    std::lock_guard lg{mtx};\n    callables.erase(slot);\n  }\n\nprivate:\n  mutable std::mutex mtx;\n  shared_callback_slot_t index {0};\n  std::map<shared_callback_slot_t, std::shared_ptr<callable_t>> callables;\n};\n\n\n// Alias for void return type functions\ntemplate <typename... Args>\nusing SharedCallbackList = SharedRetCallbackList<void, Args...>;"
  },
  {
    "path": "common/SharedMutex.cc",
    "content": "//------------------------------------------------------------------------------\n// File: SharedMutex.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/SharedMutex.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Lock for read\n//------------------------------------------------------------------------------\nint\nSharedMutex::LockRead()\n{\n  mSharedMutex.ReaderLock();\n  return 0;\n}\n\n//----------------------------------------------------------------------------\n// Try lock for read (shared)\n//----------------------------------------------------------------------------\nint\nSharedMutex::TryLockRead()\n{\n  return (mSharedMutex.ReaderTryLock() ? 0 : EBUSY);\n}\n\n//------------------------------------------------------------------------------\n// Unlock a read lock\n//-----------------------------------------------------------------------------\nint\nSharedMutex::UnLockRead()\n{\n  mSharedMutex.ReaderUnlock();\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Try to read lock the mutex within the timeout\n//------------------------------------------------------------------------------\nint\nSharedMutex::TimedRdLock(uint64_t timeout_ns)\n{\n  mSharedMutex.ReaderLock();\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Lock for write\n//------------------------------------------------------------------------------\nint\nSharedMutex::LockWrite()\n{\n  mSharedMutex.Lock();\n  return 0;\n}\n\n//----------------------------------------------------------------------------\n// Try lock for write (exclusive)\n//----------------------------------------------------------------------------\nint SharedMutex::TryLockWrite()\n{\n  return (mSharedMutex.WriterTryLock() ? 0 : EBUSY);\n}\n\n//------------------------------------------------------------------------------\n// Unlock a write lock\n//------------------------------------------------------------------------------\nint\nSharedMutex::UnLockWrite()\n{\n  mSharedMutex.Unlock();\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Try to write lock the mutex within the timeout\n//------------------------------------------------------------------------------\nint\nSharedMutex::TimedWrLock(uint64_t timeout_ns)\n{\n  mSharedMutex.Lock();\n  return 0;\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/SharedMutex.hh",
    "content": "//------------------------------------------------------------------------------\n// File: SharedMutex.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include \"common/IRWMutex.hh\"\n#include \"absl/synchronization/mutex.h\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n\n//------------------------------------------------------------------------------\n//! Class SharedMutex - wrapper around std::shared_timed_mutex\n//------------------------------------------------------------------------------\nclass SharedMutex: public IRWMutex\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  // ---------------------------------------------------------------------------\n  SharedMutex() = default;\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~SharedMutex() = default;\n\n  //----------------------------------------------------------------------------\n  //! Move constructor\n  //----------------------------------------------------------------------------\n  SharedMutex(SharedMutex&& other) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Move assignment operator\n  //----------------------------------------------------------------------------\n  SharedMutex& operator=(SharedMutex&& other) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Copy constructor\n  //----------------------------------------------------------------------------\n  SharedMutex(const SharedMutex&) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Copy assignment operator\n  //----------------------------------------------------------------------------\n  SharedMutex& operator=(const SharedMutex&) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Lock for read\n  //----------------------------------------------------------------------------\n  int LockRead() override;\n\n  //----------------------------------------------------------------------------\n  //! Try lock for read (shared)\n  //!\n  //! @return 0 if lock acquired successfully, otherwise non-zero\n  //----------------------------------------------------------------------------\n  int TryLockRead() override;\n\n  //----------------------------------------------------------------------------\n  //! Unlock a read lock\n  //----------------------------------------------------------------------------\n  int UnLockRead() override;\n\n  //----------------------------------------------------------------------------\n  //! Try to read lock the mutex within the timeout\n  //!\n  //! @param timeout_ns nano seconds timeout\n  //!\n  //! @return 0 if successful, otherwise error number\n  //----------------------------------------------------------------------------\n  int TimedRdLock(uint64_t timeout_ns) override;\n\n  //----------------------------------------------------------------------------\n  //! Lock for write\n  //----------------------------------------------------------------------------\n  int LockWrite() override;\n\n  //----------------------------------------------------------------------------\n  //! Try lock for write (exclusive)\n  //!\n  //! @return 0 if lock acquired successfully, otherwise non-zero\n  //----------------------------------------------------------------------------\n  int TryLockWrite() override;\n\n  //----------------------------------------------------------------------------\n  //! Unlock a write lock\n  //----------------------------------------------------------------------------\n  int UnLockWrite() override;\n\n  //----------------------------------------------------------------------------\n  //! Try to write lock the mutex within the timeout\n  //!\n  //! @param timeout_ns nano seconds timeout\n  //!\n  //! @return 0 if successful, otherwise error number\n  //----------------------------------------------------------------------------\n  int TimedWrLock(uint64_t timeout_ns) override;\n\nprivate:\n  //  std::mutex mSharedMutex;\n  //  sf::contention_free_shared_mutex<> mSharedMutex;\n  //  folly::SharedMutex mSharedMutex;\n  absl::Mutex mSharedMutex;\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/ShellCmd.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ShellExecutor.cc\n// Author: Michal Kamin Simon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Namespace.hh\"\n#include \"common/ShellCmd.hh\"\n#include <sys/wait.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/ptrace.h>\n#include <sys/syscall.h>\n#include <uuid/uuid.h>\n#include <fcntl.h>\n#include <stdio.h>\n#include <unistd.h>\n#include <errno.h>\n\n#ifdef __APPLE__\n#define EOS_PTRACE_CONTINUE PT_CONTINUE\n#define EOS_PTRACE_ATTACH   PT_ATTACHEXC\n#else\n#define EOS_PTRACE_CONTINUE PTRACE_CONT\n#define EOS_PTRACE_ATTACH   PTRACE_ATTACH\n#endif //__APPLE__\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nShellCmd::ShellCmd(std::string const& cmd):\n  cmd(cmd), monitor_active(false), monitor_joined(false)\n{\n  // Generate the 'uuid' for the 'fifos'\n  uuid_t uu;\n  uuid_generate_time(uu);\n  uuid_unparse(uu, uuid);\n  // create a 'fifo' for 'stdout'\n  stdout_name = ShellExecutor::fifo_name(uuid, ShellExecutor::stdout);\n  (void) mkfifo(stdout_name.c_str(), 0666);\n  // create a 'fifo' for 'stderr'\n  stderr_name = ShellExecutor::fifo_name(uuid, ShellExecutor::stderr);\n  (void) mkfifo(stderr_name.c_str(), 0666);\n  // create a 'fifo' for 'stdin'\n  stdin_name = ShellExecutor::fifo_name(uuid, ShellExecutor::stdin);\n  (void) mkfifo(stdin_name.c_str(), 0666);\n\n  // execute the command\n  try {\n    pid = ShellExecutor::instance().execute(cmd, uuid);\n    // Start the monitor thread\n    monitor_thread = std::thread(&ShellCmd::monitor, this);\n  } catch (const eos::common::ShellException& e) {\n    // There was an exception thrown while executing the command\n    cmd_stat.exited = true;\n    cmd_stat.exit_code = ESRCH;\n    cmd_stat.signaled = false;\n    cmd_stat.signo = 0;\n    cmd_stat.status = ESRCH;;\n  }\n\n  //----------------------------------------------------------------------------\n  // open the 'fifos'\n  // (the order is not random: it has to match the order in\n  // 'ShellExecutor' otherwise the two process will deadlock)\n  //----------------------------------------------------------------------------\n  outfd = open(stdout_name.c_str(), O_RDONLY);\n  infd = open(stdin_name.c_str(), O_WRONLY);\n  errfd = open(stderr_name.c_str(), O_RDONLY);\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nShellCmd::~ShellCmd()\n{\n  // Close file descriptors\n  (void) close(outfd);\n  (void) close(errfd);\n  (void) close(infd);\n  // Delete 'fifos'\n  (void) remove(stdout_name.c_str());\n  (void) remove(stderr_name.c_str());\n  (void) remove(stdin_name.c_str());\n\n  // kill the 'cmd' if active\n  if (is_active()) {\n    (void) kill();\n  }\n\n  // Wait for the monitor thread to exit gracefully\n  // (make sure the thread is joined to avoid memory leaks)\n  if (monitor_active || !monitor_joined) {\n    monitor_thread.join();\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nShellCmd::monitor() noexcept\n{\n  // set the active flag\n  monitor_active = true;\n  // switch this thread to root to be able to attach\n#ifdef __APPLE__\n\n  if (setreuid(-1, 0) < 0) {\n    perror(\"failed while calling setreuid\\n\");\n    return;\n  }\n\n#else\n  syscall(SYS_setresuid, 0, 0, 0);\n#endif\n\n  // Trace the 'command' process (without stopping it), in this way the given\n  //process becomes its parent  and can use 'waitpid' for waiting\n  if ((ptrace(EOS_PTRACE_ATTACH, pid, 0, 0)) == -1) {\n    // ptrace attach failed, we cannot proceed, but we block until the child terminated\n    perror(\"error: failed to attach to forked process\");\n\n    while (is_active()) {\n      std::this_thread::sleep_for(std::chrono::milliseconds(250));\n    }\n\n    cmd_stat.exited = false;\n    cmd_stat.exit_code = EPERM;\n    cmd_stat.signaled = false;\n    cmd_stat.signo = 0;\n    cmd_stat.status = 0;\n    // reset the active flag\n    monitor_active = false;\n    return;\n  }\n\n  // wait for the 'command' process\n  int status = 0;\n\n  // wait for the process to terminate\n  while (true) {\n    // wait for a change in the process status\n    if (waitpid(pid, &status, 0) == pid) {\n      // if the process has been stopped (not terminated)\n      // resume it and keep waiting\n      if (status && WIFSTOPPED(status)) {\n        ptrace(EOS_PTRACE_CONTINUE, pid, 0, 0);\n        continue;\n      }\n\n      // if the process has been just resumed keep waiting\n      if (status && WIFCONTINUED(status)) {\n        continue;\n      }\n\n      // otherwise the process is terminated and we are done with waiting\n      break;\n    } else {\n      perror(\"error: failed to waitpid for attached process\");\n\n      if (!is_active()) {\n        break;\n      }\n\n      // Prevent tight loops\n      std::this_thread::sleep_for(std::chrono::milliseconds(250));\n    }\n  }\n\n  // the status of the 'command' process\n  cmd_stat.exited = WIFEXITED(status);\n  cmd_stat.exit_code = WEXITSTATUS(status);\n  cmd_stat.signaled = WIFSIGNALED(status);\n  cmd_stat.signo = WTERMSIG(status);\n  cmd_stat.status = status;\n  // reset the active flag\n  monitor_active = false;\n}\n\n/*----------------------------------------------------------------------------*/\ncmd_status\nShellCmd::wait()\n{\n  if (monitor_active) {\n    monitor_joined = true;\n    monitor_thread.join();\n  }\n\n  return cmd_stat;\n}\n\n/*----------------------------------------------------------------------------*/\ncmd_status\nShellCmd::wait(size_t timeout)\n{\n  size_t exp_sleep = 1;\n\n  for (size_t i = 0; i < timeout + 9; ++i) {\n    if (!is_active()) {\n      break;\n    }\n\n    std::this_thread::sleep_for(std::chrono::milliseconds(exp_sleep));\n\n    if (exp_sleep < 512) {\n      exp_sleep *= 2;\n    } else {\n      exp_sleep = 1000;\n    }\n  }\n\n  // stop it if the timeout is exceeded\n  if (is_active()) {\n    kill();\n  }\n\n  if (monitor_active) {\n    monitor_joined = true;\n    monitor_thread.join();\n  }\n\n  return cmd_stat;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nShellCmd::kill(int sig) const\n{\n  ::kill(pid, sig);\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nShellCmd::is_active() const\n{\n  //----------------------------------------------------------------------------\n  // send the null signal to check if the process exists\n  // if not 'errno' will be set to 'ESRCH'\n  //----------------------------------------------------------------------------\n  if (::kill(pid, 0) == -1) {\n    return errno != ESRCH;\n  }\n\n  return true;\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/ShellCmd.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ShellCmd.hh\n// Author: Michal Kamin Simon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSCOMMON_SHELLCMD__HH__\n#define __EOSCOMMON_SHELLCMD__HH__\n\n#include \"common/Namespace.hh\"\n#include \"common/ShellExecutor.hh\"\n#include <signal.h>\n#include <string>\n#include <atomic>\n#include <thread>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\n/*                                                                            */\n/*----------------------------------------------------------------------------*/\nstruct cmd_status {\n\n  cmd_status() :\n    exited(false), exit_code(0), signaled(false), signo(0),\n    status(0), timed_out(false)\n  { }\n\n  bool exited;\n  int exit_code;\n  bool signaled;\n  int signo;\n  int status;\n  bool timed_out;\n};\n\nclass ShellCmd\n{\npublic:\n  //----------------------------------------------------------------------------\n  // constructor\n  //----------------------------------------------------------------------------\n  ShellCmd(std::string const& cmd);\n  //----------------------------------------------------------------------------\n  // destructor\n  //----------------------------------------------------------------------------\n  ~ShellCmd();\n\n  //----------------------------------------------------------------------------\n  // waits until the 'command' process terminates\n  //----------------------------------------------------------------------------\n  cmd_status wait();\n\n  //----------------------------------------------------------------------------\n  // waits until the 'command' process terminates or the timeout has passed\n  //----------------------------------------------------------------------------\n  cmd_status wait(size_t timeout);\n\n\n  //----------------------------------------------------------------------------\n  // kills the 'command' process\n  //----------------------------------------------------------------------------\n  void kill(int sig = SIGKILL) const;\n\n  //----------------------------------------------------------------------------\n  // checks if the 'command' process is active\n  //----------------------------------------------------------------------------\n  bool is_active() const;\n\n  //----------------------------------------------------------------------------\n  // the pid of the 'command' process\n  //----------------------------------------------------------------------------\n\n  pid_t get_pid()\n  {\n    return pid;\n  }\n\n  //----------------------------------------------------------------------------\n  // file descriptors of the 'command' process\n  //----------------------------------------------------------------------------\n  int outfd; //< 'stdout' of the command\n  int errfd; //< 'stderr' of the command\n  int infd; //< 'stdin'  of the command\n\nprivate:\n\n  void monitor() noexcept;\n\n  std::string cmd;\n  ShellExecutor::fifo_uuid_t uuid;\n  pid_t pid;\n  std::string stdout_name;\n  std::string stderr_name;\n  std::string stdin_name;\n\n  std::thread monitor_thread;\n  std::atomic<bool> monitor_active;\n  bool monitor_joined;\n  cmd_status cmd_stat;\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif  /* SHELL_CMD_H */\n"
  },
  {
    "path": "common/ShellExecutor.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ShellExecutor.cc\n// Author: Michal Kamin Simon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/ShellExecutor.hh\"\n#include <XrdSys/XrdSysPthread.hh>\n#include <sys/wait.h>\n#include <unistd.h>\n#include <string.h>\n#include <fcntl.h>\n#include <sstream>\n\n#ifdef __APPLE__\n#ifndef TEMP_FAILURE_RETRY\n#define TEMP_FAILURE_RETRY(exp)                 \\\n  ({                                            \\\n    decltype(exp) _rc;                          \\\n    do {                                        \\\n      _rc = (exp);                              \\\n    } while ((_rc == -1) && (errno == EINTR));  \\\n    _rc;                                        \\\n  })\n#endif // TEMP_FAILURE_RETRY\n#endif // __APPLE__\n\nEOSCOMMONNAMESPACE_BEGIN\n\nconst std::string ShellExecutor::stdout = \"stdout\";\nconst std::string ShellExecutor::stderr = \"stderr\";\nconst std::string ShellExecutor::stdin = \"stdin\";\n\n/*----------------------------------------------------------------------------*/\nShellExecutor::ShellExecutor()\n{\n  outfd[0] = outfd[1] = -1;\n  infd[0] = infd[1] = -1;\n\n  // create a pipe for IPC\n  if (pipe(outfd) == -1 || pipe(infd) == -1) {\n    throw ShellException(\"Not able to create a pipe!\");\n  }\n\n  // fork the worker process\n  pid_t pid;\n\n  if ((pid = fork()) < 0) {\n    throw ShellException(\"Not able to fork!\");\n  }\n\n  // handle forking\n  if (pid == 0) {\n    // run the child code\n    run_child();\n  } else {\n    // close the 'read-end' of output pipe on parent side\n    close(outfd[0]);\n    // close the 'write-end' of input pipe on parent side\n    close(infd[1]);\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nShellExecutor::~ShellExecutor()\n{\n  // ---------------------------------------------------------------------------\n  // close the 'write-end' of output pipe on parent side\n  // reader will receive EOF\n  // ---------------------------------------------------------------------------\n  close(outfd[1]);\n  // wait for the child process the exit\n  wait(0);\n  // close the 'read-end' of input pipe on parent side\n  close(infd[0]);\n}\n\n/*----------------------------------------------------------------------------*/\npid_t\nShellExecutor::execute(const std::string& cmd, fifo_uuid_t uuid) const\n{\n  static XrdSysMutex executeMutex;\n  XrdSysMutexHelper lLock(&executeMutex);\n  // Offset in case we need to send several messages to cover the command string\n  size_t offset = 0;\n  // Message for the child process\n  msg_t msg(uuid);\n\n  // send the command to child process\n  while (!msg.complete) {\n    // calculate the size of the message\n    size_t size = ((cmd.size() - offset < msg_t::max_size) ?\n                   cmd.size() - offset : msg_t::max_size - 1);\n    memset(msg.buff, 0, sizeof(msg.buff));\n    // copy the command\n    strncpy(msg.buff, cmd.c_str() + offset, size);\n    // terminate the string\n    msg.buff[size] = 0;\n    // set the complete flag\n    msg.complete = (cmd.size() <= offset + size);\n\n    // send the command to child process\n    if (write(outfd[1], &msg, sizeof(msg_t)) < 0) {\n      throw ShellException(\"Not able to send message to child process\");\n    }\n\n    // update offset\n    offset += size;\n  }\n\n  // the child will respond with pid of the 'command' process\n  pid_t pid = 0;\n  TEMP_FAILURE_RETRY(read(infd[0], &pid, sizeof(pid_t)));\n  return pid;\n}\n\nvoid\nShellExecutor::alarm(int signal)\n{\n  if (kill(getppid(), 0)) {\n    throw ShellException(\"Parent died - aborting\");\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nShellExecutor::run_child() const\n{\n  // close the 'write-end' of input pipe on child side\n  close(outfd[1]);\n  // close the 'read-end' of output pipe on child side\n  close(infd[0]);\n  // make sure there are no zombie 'command' processes\n  struct sigaction sigchld_action;\n  memset(&sigchld_action, 0, sizeof sigchld_action);\n  sigchld_action.sa_handler = SIG_DFL;\n  sigchld_action.sa_flags = SA_NOCLDWAIT;\n  sigaction(SIGCHLD, &sigchld_action, 0);\n  // Install an alarm handler to check for the death of our parent process\n  struct sigaction sa;\n  sa.sa_handler = &ShellExecutor::alarm;\n  sa.sa_flags = 0;\n  sigfillset(&sa.sa_mask);\n  sigaction(SIGALRM, &sa, NULL);\n  // The message received from the parent\n  msg_t msg;\n  // the command to be executed\n  std::string cmd;\n  // check every 5 seconds for parent death\n  alarm(5);\n  size_t nread = 0;\n  off_t off = 0;\n\n  while ((nread = TEMP_FAILURE_RETRY(read(outfd[0], (char*)&msg + off,\n                                          sizeof(msg) - off))) > 0) {\n    alarm(0);\n    off += nread;\n\n    if (off == sizeof(msg)) {\n      cmd += msg.buff;\n      off = 0;\n\n      if (msg.complete) {\n        // execute the command\n        pid_t pid = system(cmd.c_str(), msg.uuid);\n        // respond with 'command' pid\n        (void) !write(infd[1], &pid, sizeof(pid_t));\n        // clean up\n        msg.complete = false;\n        cmd.erase();\n      }\n    }\n\n    alarm(5);\n  }\n\n  // close the 'read-end' of input pipe on child side\n  close(outfd[0]);\n  // close the 'write-end' of output pipe on child side\n  close(infd[1]);\n  // exit from the child process\n  _exit(0);\n}\n\n/*----------------------------------------------------------------------------*/\npid_t\nShellExecutor::system(char const* cmd, fifo_uuid_t uuid) const\n{\n  pid_t pid;\n\n  if ((pid = fork()) == 0) {\n    // file descriptor for redirecting 'stdout'\n    int stdout_fd = -1, stderr_fd = -1, stdin_fd = -1;\n\n    // if a timestamp has been given redirect\n    // the 'stdout', 'stderr' and 'stdin' to a named pipes\n    if (uuid != 0 && strlen(uuid) != 0) {\n      // -----------------------------------------------------------------------\n      // the order in which 'fifos' are opened is not random!\n      // it has to match the order in 'shell_cmd'\n      // otherwise the two process will deadlock)\n      // redirect 'stdout'\n      // -----------------------------------------------------------------------\n      std::string stdout_name = fifo_name(uuid, stdout);\n      stdout_fd = open(stdout_name.c_str(), O_WRONLY);\n\n      if (stdout_fd < 0) {\n        throw ShellException(\"Unable to open stdout file\");\n      }\n\n      if (dup2(stdout_fd, STDOUT_FILENO) != STDOUT_FILENO) {\n        throw ShellException(\"Not able to redirect the 'sdtout' to FIFO!\");\n      }\n\n      // redirect 'stdin'\n      std::string stdin_name = fifo_name(uuid, stdin);\n      stdin_fd = open(stdin_name.c_str(), O_RDONLY);\n\n      if (stdin_fd < 0) {\n        throw ShellException(\"Unable to open stdin file\");\n      }\n\n      if (dup2(stdin_fd, STDIN_FILENO) != STDIN_FILENO) {\n        throw ShellException(\"Not able to redirect the 'sdtin' to FIFO!\");\n      }\n\n      // redirect 'stderr'\n      std::string stderr_name = fifo_name(uuid, stderr);\n      stderr_fd = open(stderr_name.c_str(), O_WRONLY);\n\n      if (stderr_fd < 0) {\n        throw ShellException(\"Unalbe to open stderr file\");\n      }\n\n      if (dup2(stderr_fd, STDERR_FILENO) != STDERR_FILENO) {\n        throw ShellException(\"Not able to redirect the 'sdterr' to FIFO!\");\n      }\n    }\n\n    // -------------------------------------------------------------------------\n    // execute the command\n    // -------------------------------------------------------------------------\n    execl(\"/bin/sh\", \"sh\", \"-c\", cmd, (char*) 0);\n\n    // close file descriptors if necessary\n    if (stdout_fd != -1) {\n      close(stdout_fd);\n    }\n\n    if (stdout_fd != -1) {\n      close(stdin_fd);\n    }\n\n    if (stdout_fd != -1) {\n      close(stderr_fd);\n    }\n\n    // exit\n    _exit(127);\n  }\n\n  return pid;\n}\n\n/*----------------------------------------------------------------------------*/\nShellExecutor::msg_t::msg_t () : complete(false)\n{\n  memset(uuid, 0, sizeof(fifo_uuid_t));\n  memset(buff, 0, max_size);\n}\n\n/*----------------------------------------------------------------------------*/\nShellExecutor::msg_t::msg_t (fifo_uuid_t uuid) : complete(false)\n{\n  // if the UUID is a NULL pointer\n  if (uuid == 0) {\n    // make it an empty string\n    memset(this->uuid, 0, sizeof(fifo_uuid_t));\n  } else {\n    // otherwise copy the UUID\n    strncpy(this->uuid, uuid, 36);\n    this->uuid[36] = 0;\n  }\n\n  memset(buff, 0, max_size);\n}\n\n/*----------------------------------------------------------------------------*/\nstd::string\nShellExecutor::fifo_name(fifo_uuid_t uuid, std::string const& sufix)\n{\n  return \"/tmp/cmd-fifo-\" + std::string(uuid) + \"-\" + sufix;\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/ShellExecutor.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ShellExecutor.hh\n// Author: Michal Kamin Simon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSCOMMON_SHELLEXECUTOR__HH__\n#define __EOSCOMMON_SHELLEXECUTOR__HH__\n\n#include \"common/Namespace.hh\"\n#include <string>\n#include <exception>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class ShellException\n//------------------------------------------------------------------------------\nclass ShellException : public std::exception\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ShellException(std::string const& msg) : msg(msg)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~ShellException()  = default;\n\n  //----------------------------------------------------------------------------\n  //! Get message\n  //----------------------------------------------------------------------------\n  char const* what() const noexcept\n  {\n    return msg.c_str();\n  }\n\nprivate:\n  std::string const msg;\n};\n\n//! Forward declration\nclass ShellCmd;\n\n//------------------------------------------------------------------------------\n//! Class ShellExecutor\n//------------------------------------------------------------------------------\nclass ShellExecutor\n{\n  friend class ShellCmd;\n\npublic:\n  // ---------------------------------------------------------------------------\n  // typedef for UUID in 'C string'\n  // ---------------------------------------------------------------------------\n  typedef char fifo_uuid_t[37];\n\n  // ---------------------------------------------------------------------------\n  // get an instance\n  // ---------------------------------------------------------------------------\n  static ShellExecutor& instance()\n  {\n    static ShellExecutor executor;\n    return executor;\n  }\n\n  // ---------------------------------------------------------------------------\n  // destructor\n  // ---------------------------------------------------------------------------\n  virtual ~ShellExecutor();\n\n  // ---------------------------------------------------------------------------\n  // alarm handler to terminate this process if the parent disappears\n  // ---------------------------------------------------------------------------\n  static void alarm(int signal);\n\n\n  // ---------------------------------------------------------------------------\n  // execute a shell command\n  // ---------------------------------------------------------------------------\n  pid_t execute(std::string const& cmd, fifo_uuid_t uuid = 0) const;\n\n  // ---------------------------------------------------------------------------\n  // generate fifo name\n  // ---------------------------------------------------------------------------\n  static std::string fifo_name(fifo_uuid_t uuid, std::string const& sufix);\n\n  // ---------------------------------------------------------------------------\n  // 'stdout', 'stderr' and 'stdin' file descriptors of the 'command' process\n  // ---------------------------------------------------------------------------\n  static const std::string stdout;\n  static const std::string stderr;\n  static const std::string stdin;\n\nprivate:\n\n  // ---------------------------------------------------------------------------\n  // constructor\n  // ---------------------------------------------------------------------------\n  ShellExecutor();\n\n  // ---------------------------------------------------------------------------\n  // not implemented\n  // ---------------------------------------------------------------------------\n  ShellExecutor(const ShellExecutor& orig);\n\n  // ---------------------------------------------------------------------------\n  // not implemented\n  // ---------------------------------------------------------------------------\n  ShellExecutor& operator= (const ShellExecutor& orig);\n\n  // ---------------------------------------------------------------------------\n  // message for parent-child communication\n  // ---------------------------------------------------------------------------\n\n  struct msg_t {\n    // size of the buffer\n    static const size_t max_size = 1024;\n    // default constructor\n    msg_t ();\n    // initialize with UUID\n    msg_t (fifo_uuid_t uuid);\n\n    char buff[max_size];\n    bool complete;\n    fifo_uuid_t uuid;\n  };\n\n  void run_child() const;\n\n  pid_t system(char const* cmd, fifo_uuid_t uuid) const;\n\n  // ---------------------------------------------------------------------------\n  /// pipes for IPC\n  // ---------------------------------------------------------------------------\n  int outfd[2];\n  int infd[2];\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif  /* __EOSCOMMON_SHELLEXECUTOR__HH__ */\n"
  },
  {
    "path": "common/StackTrace.hh",
    "content": "// ----------------------------------------------------------------------\n// File: StackTrace.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//-----------------------------------------------------------------------------\n//! @brief  Class providing readable stack traces using GDB\n//-----------------------------------------------------------------------------\n#ifndef __EOSCOMMON__STACKTRACE__HH\n#define __EOSCOMMON__STACKTRACE__HH\n\n#include \"common/ShellCmd.hh\"\n#include \"common/Timing.hh\"\n#include \"common/StringConversion.hh\"\n#include <unistd.h>\n\nEOSCOMMONNAMESPACE_BEGIN\n\nstatic const std::string EOS_DEFAULT_STACKTRACE_PATH = \"/var/eos/md/stacktrace\";\n\n//------------------------------------------------------------------------------\n//! Static Class implementing comfortable readable stack traces\n//! To use this static functions include this header file\n//------------------------------------------------------------------------------\nclass StackTrace\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Construct gdb command\n  //----------------------------------------------------------------------------\n  static std::string constructGdbCommand()\n  {\n    struct stat statbuf;\n\n    if (stat(\"/opt/rh/devtoolset-8/root/usr/bin/gdb\", &statbuf) == 0) {\n      return \"/opt/rh/devtoolset-8/root/usr/bin/gdb\";\n    }\n\n    if (stat(\"/opt/rh/devtoolset-7/root/usr/bin/gdb\", &statbuf) == 0) {\n      return \"/opt/rh/devtoolset-7/root/usr/bin/gdb\";\n    }\n\n    if (stat(\"/opt/rh/devtoolset-6/root/usr/bin/gdb\", &statbuf) == 0) {\n      return \"/opt/rh/devtoolset-6/root/usr/bin/gdb\";\n    }\n\n    return \"gdb\";\n  }\n\n  //----------------------------------------------------------------------------\n  //! Create a readable back trace using gdb\n  //----------------------------------------------------------------------------\n  static void GdbTrace(const char* executable, pid_t pid, const char* what,\n                       std::string file = EOS_DEFAULT_STACKTRACE_PATH,\n                       std::string* ret_dump = 0)\n  {\n    std::string exe;\n\n    // Append timestamp to easily distinguish multiple failures\n    if (file == EOS_DEFAULT_STACKTRACE_PATH) {\n      auto now = std::time(nullptr);\n      file += \"-\";\n      file += eos::common::Timing::UnixTimestamp_to_ISO8601(now);\n    }\n\n    if (!executable) {\n      std::string procentry = \"/proc/\";\n      procentry += std::to_string(pid);\n      procentry += \"/exe\";\n      char buf[4096];\n      ssize_t size_link = ::readlink(procentry.c_str(), buf, sizeof(buf));\n\n      if (size_link > 0) {\n        exe.assign(buf, size_link);\n      }\n    } else {\n      exe = executable;\n    }\n\n    fprintf(stderr, \"#########################################################\"\n            \"################\\n\");\n    fprintf(stderr, \"# stack trace exec=%s pid=%u what='%s'\\n\", exe.c_str(),\n            (unsigned int) pid, what);\n    fprintf(stderr, \"#########################################################\"\n            \"################\\n\");\n    XrdOucString  gdbline = \"ulimit -v 10000000000; \";\n    gdbline += constructGdbCommand().c_str();\n    gdbline += \" --quiet \";\n    gdbline += exe.c_str();\n    gdbline += \" -p \";\n    gdbline += (int) pid;\n    gdbline += \" <<< \";\n    gdbline += \"\\\"\";\n    gdbline += what;\n    gdbline += \"\\\" >&\" ;\n    gdbline += file.c_str();\n    eos::common::ShellCmd shelltrace(gdbline.c_str());\n    shelltrace.wait(120);\n    std::string cat = \"cat \";\n    cat += file.c_str();\n    std::string gdbdump = StringConversion::StringFromShellCmd\n                          (cat.c_str());\n\n    if (ret_dump) {\n      *ret_dump = gdbdump;\n    }\n\n    fprintf(stderr, \"%s\\n\", gdbdump.c_str());\n\n    if (!strcmp(\"thread apply all bt\", what)) {\n      if (!ret_dump) {\n        // We can extract the signal thread from all thread back traces\n        GdbSignaledTrace(gdbdump);\n      }\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Extract the thread stack trace creating responsible signal\n  //----------------------------------------------------------------------------\n  static void GdbSignaledTrace(std::string& trace)\n  {\n    // Analyze the trace until we find '<signal handler called>' and extract\n    // this trace\n    std::vector<std::string> lines;\n    eos::common::StringConversion::Tokenize(trace, lines, \"\\n\");\n    size_t thread_start = 0;\n    size_t thread_stop = 0;\n    size_t trace_start = 0;\n\n    for (size_t i = 0; i < lines.size(); i++) {\n      if (lines[i].substr(0, 6) == \"Thread\") {\n        if (thread_start && trace_start) {\n          thread_stop = i - 1;\n          break;\n        }\n\n        thread_start = i;\n      }\n\n      if (lines[i].length() < 2) {\n        thread_stop = i;\n\n        if (trace_start) {\n          break;\n        }\n      }\n\n      if (lines[i].find(\"<signal handler called>\") != std::string::npos) {\n        trace_start = i;\n      }\n    }\n\n    if (!thread_stop) {\n      thread_stop = lines.size() - 1;\n    }\n\n    if ((thread_start < trace_start) && (trace_start < thread_stop)) {\n      fprintf(stderr, \"#######################################################\"\n              \"##################\\n\");\n      fprintf(stderr, \"# -----------------------------------------------------\"\n              \"------------------\\n\");\n      fprintf(stderr, \"# Responsible thread =>\\n\");\n      fprintf(stderr, \"# -----------------------------------------------------\"\n              \"------------------\\n\");\n      fprintf(stderr, \"# %s\\n\", lines[thread_start].c_str());\n      fprintf(stderr, \"#######################################################\"\n              \"##################\\n\");\n\n      for (size_t l = trace_start; l <= thread_stop; ++l) {\n        fprintf(stderr, \"%s\\n\", lines[l].c_str());\n      }\n    } else {\n      fprintf(stderr, \"#######################################################\"\n              \"##################\\n\");\n      fprintf(stderr,\n              \"# warning: failed to parse the thread responsible for signal [%u %u %u]\\n\",\n              (unsigned int)thread_start, (unsigned int)trace_start,\n              (unsigned int)thread_stop);\n      fprintf(stderr, \"#######################################################\"\n              \"##################\\n\");\n    }\n  }\n};\n\nEOSCOMMONNAMESPACE_END\n#endif\n"
  },
  {
    "path": "common/StacktraceHere.cc",
    "content": "// ----------------------------------------------------------------------\n// File: StacktraceHere.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/ASwitzerland                                 *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StacktraceHere.hh\"\n\n#ifndef __APPLE__\n#include <execinfo.h>\n#define BACKWARD_HAS_BFD 1\n#if !defined(bfd_get_section_flags)\n#define bfd_get_section_flags(ptr, section) bfd_section_flags(section)\n#endif /* !defined(bfd_get_section_flags) */\n#if !defined(bfd_get_section_size)\n#define bfd_get_section_size(section) bfd_section_size(section)\n#endif /* !defined(bfd_get_section_size) */\n#if !defined(bfd_get_section_vma)\n#define bfd_get_section_vma(ptr, section) bfd_section_vma(section)\n#endif /* !defined(bfd_get_section_size) */\n#include \"common/backward-cpp/backward.hpp\"\n#endif\n\nEOSCOMMONNAMESPACE_BEGIN\n\n#ifdef __APPLE__\nstd::string getStacktrace()\n{\n  return \"No stacktrack available on this platform\";\n}\n#else\nstd::string getStacktrace()\n{\n  if (getenv(\"EOS_ENABLE_BACKWARD_STACKTRACE\")) {\n    // Very heavy-weight stacktrace, only use during development.\n    std::ostringstream ss;\n    backward::StackTrace st;\n    st.load_here(128);\n    backward::Printer p;\n    p.object = true;\n    p.address = true;\n    p.print(st, ss);\n    return ss.str();\n  }\n\n  std::ostringstream o;\n  void* array[24];\n  int size = backtrace(array, 24);\n  char** messages = backtrace_symbols(array, size);\n\n  // skip first stack frame (points here)\n  for (int i = 1; i < size && messages != NULL; ++i) {\n    char* mangled_name = 0, *offset_begin = 0, *offset_end = 0;\n\n    // find parantheses and +address offset surrounding mangled name\n    for (char* p = messages[i]; *p; ++p) {\n      if (!p) {\n        break;\n      }\n\n      if (*p == '(') {\n        mangled_name = p;\n      } else if (*p == '+') {\n        offset_begin = p;\n      } else if (*p == ')') {\n        offset_end = p;\n        break;\n      }\n    }\n\n    // if the line could be processed, attempt to demangle the symbol\n    if (mangled_name && offset_begin && offset_end &&\n        mangled_name < offset_begin) {\n      *mangled_name++ = '\\0';\n      *offset_begin++ = '\\0';\n      *offset_end++ = '\\0';\n      int status;\n      char* real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);\n\n      // if demangling is successful, output the demangled function name\n      if (status == 0) {\n        o << \"[bt]: (\" << i << \") \" << messages[i] << \" : \"\n          << real_name << \"+\" << offset_begin << offset_end\n          << \" \" << std::endl;\n      }\n      // otherwise, output the mangled function name\n      else {\n        o << \"[bt]: (\" << i << \") \" << messages[i] << \" : \"\n          << mangled_name << \"+\" << offset_begin << offset_end\n          << \" \" << std::endl;\n      }\n\n      free(real_name);\n    }\n    // otherwise, print the whole line\n    else {\n      o << \"[bt]: (\" << i << \") \" << messages[i] << \" \" << std::endl;\n    }\n  }\n\n  free(messages);\n  return o.str();\n}\n#endif\n\n\n#ifdef __APPLE__\nvoid handleSignal(int sig, siginfo_t* si, void* ctx)\n{\n}\n#else\nvoid handleSignal(int sig, siginfo_t* si, void* ctx)\n{\n  if (!getenv(\"EOS_ENABLE_BACKWARD_STACKTRACE\")) {\n    return;\n  }\n\n  backward::SignalHandling::handleSignal(sig, si, ctx);\n}\n#endif\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/StacktraceHere.hh",
    "content": "// ----------------------------------------------------------------------\n// File: StacktraceHere.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/ASwitzerland                                 *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOSCOMMON_STACKTRACE_HERE_HH\n#define EOSCOMMON_STACKTRACE_HERE_HH\n\n#include <signal.h>\n#include \"common/Namespace.hh\"\n#include <string>\n\nEOSCOMMONNAMESPACE_BEGIN\n\nstd::string getStacktrace();\nvoid handleSignal(int sig, siginfo_t* si, void* ctx);\n\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/Statfs.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Statfs.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"common/Namespace.hh\"\n#include \"common/Statfs.hh\"\n/*----------------------------------------------------------------------------*/\n\nEOSCOMMONNAMESPACE_BEGIN\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Statfs.hh",
    "content": "//------------------------------------------------------------------------------\n// File: Statfs.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSCOMMON_STATFS_HH__\n#define __EOSCOMMON_STATFS_HH__\n\n#include \"common/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include <XrdOuc/XrdOucHash.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdSys/XrdSysPthread.hh>\n#ifndef __APPLE__\n#include <sys/vfs.h>\n#else\n#include <sys/param.h>\n#include <sys/mount.h>\n#endif\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class storing a statfs struct and providing some convenience functions to\n//! convert into an env representation\n//------------------------------------------------------------------------------\nclass Statfs: public LogId\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Empty constructor, empty contents\n  //----------------------------------------------------------------------------\n  Statfs() {\n    memset(&statFs, 0, sizeof(struct statfs));\n  }\n\n  //----------------------------------------------------------------------------\n  //! Constructor absorbing the raw statfs struct\n  //----------------------------------------------------------------------------\n  Statfs(struct statfs raw) {\n    resetContents(raw);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return reference to the internal statfs struct\n  //----------------------------------------------------------------------------\n  struct statfs* GetStatfs()\n  {\n    return &statFs;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return reference to the internal environment serialization\n  //----------------------------------------------------------------------------\n  const char* GetEnv()\n  {\n    return env.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Reset internal statfs contents with the given ones,\n  //! recalculate environment\n  //----------------------------------------------------------------------------\n  void resetContents(struct statfs contents) {\n    statFs = contents;\n    recalculateEnv();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Recalculate \"environment variable\" based on current statfs\n  //! struct contents\n  //----------------------------------------------------------------------------\n  void recalculateEnv() {\n    char s[1024];\n    sprintf(s,\n            \"statfs.type=%ld&statfs.bsize=%ld&statfs.blocks=%ld&\"\n            \"statfs.bfree=%ld&statfs.bavail=%ld&statfs.files=%ld&statfs.ffree=%ld\",\n            (long) statFs.f_type, (long) statFs.f_bsize, (long) statFs.f_blocks,\n            (long) statFs.f_bfree, (long) statFs.f_bavail, (long) statFs.f_files,\n            (long) statFs.f_ffree);\n    env = s;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Execute the statfs function on the given path and build the env\n  //! representation.\n  //----------------------------------------------------------------------------\n  int perform(const std::string &path)\n  {\n    env = \"\";\n    int retc = 0;\n\n    retc = ::statfs(path.c_str(), (struct statfs*) &statFs);\n\n    if (!retc) {\n      recalculateEnv();\n    } else {\n      eos_err(\"failed statfs path=%s, errno=%i, strerrno=%s\", path.c_str(),\n              errno, strerror(errno));\n    }\n\n    return retc;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Static function do add a statfs struct for path to the global statfs hash\n  //----------------------------------------------------------------------------\n  static std::unique_ptr<Statfs> DoStatfs(const char* path) {\n    std::unique_ptr<Statfs> sfs(new Statfs());\n    if (!sfs->perform(path)) {\n      return sfs;\n    } else {\n      return {};\n    }\n  }\n\nprivate:\n  struct statfs statFs; //< the stored statfs struct\n  XrdOucString env; //< env representation of the contents\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/Statistics.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Statistics.hh\n// Author: Andreas Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                 *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOSCOMMON_STATISTICS_HH\n#define EOSCOMMON_STATISTICS_HH\n\n#include \"common/Namespace.hh\"\n#include <set>\n#include <cmath>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Base Statistics Functions\n//------------------------------------------------------------------------------\nclass Statistics {\npublic:\n  static double max(std::multiset<float>& s)\n  {\n    double lmax=0;\n    for (auto it : s) {\n      if (it > lmax) {\n\tlmax = it;\n      }\n    }\n    return lmax;\n  }\n\n  static double avg(std::multiset<float>& s)\n  {\n    double sum=0;\n    if (!s.size())\n      return 0;\n\n    for (auto it : s) {\n      sum += it;\n    }\n    return s.size()?sum/s.size():0;\n  }\n\n  static double sig(std::multiset<float>& s)\n  {\n    double average = avg(s);\n    double sum=0;\n    for (auto it : s) {\n      sum += (it - average) * (it - average);\n    }\n    return s.size()?sqrt( sum / s.size()):0;\n  }\n\n  static double nperc(std::multiset<float>& s, double perc=99.0)\n  {\n    size_t n = s.size();\n    size_t n_perc = (size_t)(n * perc / 100.0);\n    size_t i=0;\n    double nperc=0;\n    for (auto it : s) {\n      i++;\n      if ( i >= n_perc ) {\n\tnperc = it;\n\tbreak;\n      }\n    }\n    return nperc;\n  }\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/Status.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Status.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/ASwitzerland                                 *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include <sstream>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Status object for operations which may fail\n//------------------------------------------------------------------------------\nclass Status\n{\npublic:\n  //----------------------------------------------------------------------------\n  // Default constructor - status is OK, no error message.\n  //----------------------------------------------------------------------------\n  Status() : errcode(0) {}\n\n  //----------------------------------------------------------------------------\n  // Constructor with an error\n  //----------------------------------------------------------------------------\n  Status(int err, const std::string& msg) : errcode(err), errorMessage(msg) {}\n\n  //----------------------------------------------------------------------------\n  // Is status ok?\n  //----------------------------------------------------------------------------\n  bool ok() const\n  {\n    return (errcode == 0);\n  }\n\n  //----------------------------------------------------------------------------\n  // Get errorcode\n  //----------------------------------------------------------------------------\n  int getErrc() const\n  {\n    return errcode;\n  }\n\n  //----------------------------------------------------------------------------\n  // Get error message\n  //----------------------------------------------------------------------------\n  std::string getMsg() const\n  {\n    return errorMessage;\n  }\n\n  //----------------------------------------------------------------------------\n  // To string, including error code\n  //----------------------------------------------------------------------------\n  std::string toString() const\n  {\n    std::ostringstream ss;\n    ss << \"(\" << errcode << \"): \" << errorMessage;\n    return ss.str();\n  }\n\n  //----------------------------------------------------------------------------\n  // Implicit conversion to boolean: Same value as ok()\n  //----------------------------------------------------------------------------\n  operator bool() const\n  {\n    return ok();\n  }\n\nprivate:\n  int errcode;\n  std::string errorMessage;\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/SteadyClock.hh",
    "content": "// ----------------------------------------------------------------------\n// File: SteadyClock.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include <chrono>\n#include <mutex>\n#include \"common/Namespace.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! A clock which behaves similarly to std::chrono::steady_clock, but can be\n//! faked. During faking, you can advance time manually.\n//------------------------------------------------------------------------------\nclass SteadyClock\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor: Specify whether we're faking time, or not.\n  //----------------------------------------------------------------------------\n  SteadyClock(bool fake_) : mFake(fake_) {}\n\n  //----------------------------------------------------------------------------\n  //! Default constructor - Sets fake to false\n  //----------------------------------------------------------------------------\n  SteadyClock() : mFake(false) {}\n\n  //----------------------------------------------------------------------------\n  //! Static now function - it's also possible to pass a nullptr\n  //----------------------------------------------------------------------------\n  static std::chrono::steady_clock::time_point now(SteadyClock* clock)\n  {\n    if (clock == nullptr) {\n      return std::chrono::steady_clock::now();\n    }\n\n    return clock->GetTime();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get current time.\n  //----------------------------------------------------------------------------\n  std::chrono::steady_clock::time_point GetTime() const\n  {\n    if (mFake) {\n      std::lock_guard<std::mutex> lock(mtx);\n      return fakeTimepoint;\n    }\n\n    return std::chrono::steady_clock::now();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Advance current time - only call if you're faking the clock, otherwise\n  //! has no effect...\n  //----------------------------------------------------------------------------\n  template<typename T>\n  void advance(T duration)\n  {\n    std::lock_guard<std::mutex> lock(mtx);\n    fakeTimepoint += duration;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Utility function to convert a time_point to seconds since epoch\n  //----------------------------------------------------------------------------\n  static std::chrono::seconds SecondsSinceEpoch(\n    std::chrono::steady_clock::time_point point)\n  {\n    return std::chrono::duration_cast<std::chrono::seconds>(\n             point.time_since_epoch());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if this is a \"fake\" clock\n  //----------------------------------------------------------------------------\n  inline bool IsFake() const\n  {\n    return mFake;\n  }\n\nprivate:\n  bool mFake;\n  mutable std::mutex mtx;\n  std::chrono::steady_clock::time_point fakeTimepoint;\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Strerror_r_wrapper.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file Strerror_r_wrapper.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Strerror_r_wrapper.hh\"\n// Undefine _GNU_SOURCE and define _XOPEN_SOURCE as being 600 so that the\n// XSI compliant version of strerror_r() will be used\n#undef _GNU_SOURCE\n#define _XOPEN_SOURCE 600\n#include <string.h>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// XSI-compliant strerror_r call\n//------------------------------------------------------------------------------\nint strerror_r(int errnum, char* buf, size_t buflen)\n{\n  return ::strerror_r(errnum, buf, buflen);\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Strerror_r_wrapper.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Strerror_r_wrapper.hh\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include <stddef.h>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! XSI-compliant strerror_r call\n//------------------------------------------------------------------------------\nint strerror_r(int errnum, char* buf, size_t buflen);\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/StringConversion.cc",
    "content": "// ----------------------------------------------------------------------\n// File: StringConversion.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"StringConversion.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Timing.hh\"\n#include <XrdOuc/XrdOucTokenizer.hh>\n#include \"curl/curl.h\"\n#include <pthread.h>\n\nEOSCOMMONNAMESPACE_BEGIN\n\nstatic std::atomic<int> sCounter {0};\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nCurlGlobalInitializer::CurlGlobalInitializer()\n{\n  if (sCounter++ == 0) {\n    curl_global_init(CURL_GLOBAL_DEFAULT);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nCurlGlobalInitializer::~CurlGlobalInitializer()\n{\n  if (--sCounter == 0) {\n    curl_global_cleanup();\n  }\n}\n\nchar StringConversion::pAscii2HexLkup[256];\nchar StringConversion::pHex2AsciiLkup[16];\n\n//------------------------------------------------------------------------------\n// Tokenize a string by a set of delimiters\n//------------------------------------------------------------------------------\nvoid\nStringConversion::Tokenize(const std::string& str,\n                           std::vector<std::string>& tokens,\n                           const std::string& delimiters)\n{\n  // Skip delimiters at the beginning\n  std::string::size_type lastPos = str.find_first_not_of(delimiters, 0);\n  // Find first \"non-delimiter\"\n  std::string::size_type pos = str.find_first_of(delimiters, lastPos);\n\n  while (std::string::npos != pos || std::string::npos != lastPos) {\n    // Found a token, add it to the vector.\n    tokens.push_back(str.substr(lastPos, pos - lastPos));\n    // Skip delimiters.  Note the \"not_of\"\n    lastPos = str.find_first_not_of(delimiters, pos);\n    // Find next \"non-delimiter\"\n    pos = str.find_first_of(delimiters, lastPos);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Tokenize a string respecting quoted strings and escapes\n//------------------------------------------------------------------------------\nvoid\nStringConversion::TokenizeQuoted(const std::string& str, std::vector<std::string>& tokens,\n                                 const std::string& delimiters)\n{\n  if (str.empty()) {\n    return;\n  }\n\n  size_t pos = 0;\n  const size_t len = str.length();\n  bool in_quotes = false;\n  bool escaped = false;\n  std::string current_token;\n\n  while (pos < len) {\n    char ch = str[pos++];\n\n    if (escaped) {\n      // Previous char was '\\', add this char literally (e.g., \\\" becomes \")\n      current_token += ch;\n      escaped = false;\n    } else if (ch == '\\\\') {\n      // escape next character\n      escaped = true;\n    } else if (ch == '\"') {\n      // Toggle quote state\n      in_quotes = !in_quotes;\n    } else if (!in_quotes && delimiters.find(ch) != std::string::npos) {\n      // Found delimiter outside quotes - end current token\n      if (!current_token.empty()) {\n        tokens.push_back(std::move(current_token));\n        current_token.clear();\n      }\n    } else {\n      // Regular character or delimiter inside quotes\n      current_token += ch;\n    }\n  }\n\n  // Add final token if any\n  if (!current_token.empty()) {\n    tokens.push_back(std::move(current_token));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Tokenize a string seperated by one single charactor or multichar string\n//------------------------------------------------------------------------------\n\nvoid\nStringConversion::MulticharTokenize(const std::string& str,\n\t\t       std::vector<std::string>& tokens,\n\t\t       const std::string& delimiter)\n{\n  // Edge cases\n  if (str.empty()) return;\n  if (delimiter.empty()) {           // no delimiter => whole string\n    tokens.push_back(str);\n    return;\n  }\n\n  const std::size_t dlen = delimiter.size();\n  std::size_t lastPos = 0;\n\n  // Skip delimiters at the beginning (like the original)\n  while (lastPos <= str.size() - dlen &&\n         str.compare(lastPos, dlen, delimiter) == 0) {\n    lastPos += dlen;\n  }\n\n  while (lastPos <= str.size()) {\n    // Find next delimiter occurrence starting from lastPos\n    std::size_t pos = str.find(delimiter, lastPos);\n\n    if (pos == std::string::npos) {\n      // No more delimiters: take the rest (if any)\n      if (lastPos < str.size()) {\n        tokens.emplace_back(str.substr(lastPos));\n      }\n      break;\n    }\n\n    // Push token between lastPos and pos (may be empty if we didn’t skip,\n    // but we DO skip consecutive delimiters below, so it won’t be)\n    if (pos > lastPos) {\n      tokens.emplace_back(str.substr(lastPos, pos - lastPos));\n    }\n\n    // Move past this delimiter\n    lastPos = pos + dlen;\n\n    // Skip any *consecutive* delimiters (collapse them)\n    while (lastPos <= str.size() - dlen &&\n           str.compare(lastPos, dlen, delimiter) == 0) {\n      lastPos += dlen;\n    }\n\n    // If we ended exactly at the end (i.e., trailing delimiter(s)), stop\n    if (lastPos >= str.size()) break;\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Tokenize a string accepting also empty members e.g. a||b returns 3 fields\n//------------------------------------------------------------------------------\nvoid\nStringConversion::EmptyTokenize(const std::string& str,\n                                std::vector<std::string>& tokens,\n                                const std::string& delimiters)\n{\n  // Skip delimiters at beginning.\n  std::string::size_type lastPos = str.find_first_not_of(delimiters, 0);\n  // Find first \"non-delimiter\".\n  std::string::size_type pos = str.find_first_of(delimiters, lastPos);\n\n  while (std::string::npos != pos || std::string::npos != lastPos) {\n    // Found a token, add it to the vector.\n    tokens.push_back(str.substr(lastPos, pos - lastPos));\n    // Skip delimiters.  Note the \"not_of\"\n    lastPos = str.find_first_of(delimiters, pos);\n\n    if (lastPos != std::string::npos) {\n      lastPos++;\n    }\n\n    // Find next \"non-delimiter\"\n    pos = str.find_first_of(delimiters, lastPos);\n  }\n}\n\nstd::string StringConversion::Join(const std::vector<std::string>& tokens,\n                                   const std::string& separator)\n{\n  if (tokens.empty()) {\n    return \"\";\n  }\n\n  // calculate total size needed\n  size_t total = 0;\n\n  for (const auto& tkn : tokens) {\n    total += tkn.size();\n  }\n\n  total += separator.size() * (tokens.size() - 1);\n  std::string joined;\n  joined.reserve(total);\n  // do the actual join\n  joined.append(tokens[0]);\n\n  for (size_t i = 1; i < tokens.size(); ++i) {\n    joined.append(separator);\n    joined.append(tokens[i]);\n  }\n\n  return joined;\n}\n\n//------------------------------------------------------------------------------\n// Convert a long long value into time s,m,h,d,y  scale\n//------------------------------------------------------------------------------\nconst char*\nStringConversion::GetReadableAgeString(XrdOucString& sizestring,\n                                       unsigned long long age)\n{\n  char formsize[1024];\n\n  if (age > 31536000) {\n    sprintf(formsize, \"%lluy\", age / 31536000);\n  } else if (age > 86400) {\n    sprintf(formsize, \"%llud\", age / 86400);\n  } else if (age > 3600) {\n    sprintf(formsize, \"%lluh\", age / 3600);\n  } else if (age > 60) {\n    sprintf(formsize, \"%llum\", age / 60);\n  } else {\n    sprintf(formsize, \"%llus\", age);\n  }\n\n  sizestring = formsize;\n  return sizestring.c_str();\n}\n\n\n\n\nstd::string\nStringConversion::GetFixedDouble(double value, size_t width, size_t precision)\n{\n  char dline[1024];\n  char format[16];\n\n  if (precision == 1) {\n    snprintf(dline, sizeof(dline), \"%.01f\", value);\n  } else if (precision == 2) {\n    snprintf(dline, sizeof(dline), \"%.02f\", value);\n  } else if (precision == 3) {\n    snprintf(dline, sizeof(dline), \"%.03f\", value);\n  } else if (precision == 4) {\n    snprintf(dline, sizeof(dline), \"%.04f\", value);\n  } else {\n    snprintf(dline, sizeof(dline), \"%f\", value);\n  }\n\n  char wline[1024];\n  snprintf(format, sizeof(format), \"%%-%lus\", width);\n  snprintf(wline, sizeof(wline), format, dline);\n  return std::string(wline);\n}\n\n\n//----------------------------------------------------------------------------\n//! Convert a long long value into K,M,G,T,P,E byte scale\n//----------------------------------------------------------------------------\nstd::string\nStringConversion::GetReadableSizeString(unsigned long long insize,\n                                        const char* unit, size_t si)\n{\n  char formsize[1024];\n\n  if (insize >= 10 * si) {\n    if (insize >= (si * si)) {\n      if (insize >= (si * si * si)) {\n        if (insize >= (si * si * si * si)) {\n          if (insize >= (si * si * si * si * si)) {\n            if (insize >= (si * si * si * si * si * si)) {\n              // EB\n              sprintf(formsize, \"%.02f E%s\",\n                      insize * 1.0 / (si * si * si * si * si * si), unit);\n            } else {\n              // PB\n              sprintf(formsize, \"%.02f P%s\",\n                      insize * 1.0 / (si * si * si * si * si), unit);\n            }\n          } else {\n            // TB\n            sprintf(formsize, \"%.02f T%s\",\n                    insize * 1.0 / (si * si * si * si), unit);\n          }\n        } else {\n          // GB\n          sprintf(formsize, \"%.02f G%s\", insize * 1.0 / (si * si * si), unit);\n        }\n      } else {\n        // MB\n        sprintf(formsize, \"%.02f M%s\", insize * 1.0 / (si * si), unit);\n      }\n    } else {\n      sprintf(formsize, \"%.02f k%s\", insize * 1.0 / (si), unit);\n    }\n  } else {\n    if (strlen(unit)) {\n      sprintf(formsize, \"%llu %s\", insize, unit);\n    } else {\n      sprintf(formsize, \"%llu\", insize);\n    }\n  }\n\n  return std::string(formsize);\n}\n\n//------------------------------------------------------------------------------\n// Convert a long long value into K,M,G,T,P,E byte scale\n//------------------------------------------------------------------------------\nconst char*\nStringConversion::GetReadableSizeString(XrdOucString& sizestring,\n                                        unsigned long long insize,\n                                        const char* unit)\n{\n  sizestring = GetReadableSizeString(insize, unit).c_str();\n  return sizestring.c_str();\n}\n\n//------------------------------------------------------------------------------\n// Convert a long long value into K,M,G,T,P,E byte scale\n//------------------------------------------------------------------------------\nconst char*\nStringConversion::GetReadableSizeString(std::string& sizestring,\n                                        unsigned long long insize,\n                                        const char* unit)\n{\n  sizestring = GetReadableSizeString(insize, unit);\n  return sizestring.c_str();\n}\n\n//------------------------------------------------------------------------------\n// Convert a string containing binary characters to its hex representation\n//------------------------------------------------------------------------------\nstd::string\nStringConversion::string_to_hex(const std::string& input)\n{\n  static const char* const lut = \"0123456789ABCDEF\";\n  size_t len = input.length();\n  std::string output;\n  output.reserve(2 * len);\n\n  for (size_t i = 0; i < len; ++i) {\n    const unsigned char c = input[i];\n    output.push_back(lut[c >> 4]);\n    output.push_back(lut[c & 15]);\n  }\n\n  return output;\n}\n\n//------------------------------------------------------------------------------\n// Convert a char to its hex representation\n//------------------------------------------------------------------------------\nstd::string\nStringConversion::char_to_hex(const char input)\n{\n  static const char* const lut = \"0123456789abcdef\";\n  std::string output;\n  output.resize(2);\n  const unsigned char c = input;\n  output[0] = lut[c >> 4];\n  output[1] = lut[c & 15];\n  return output;\n}\n\n//------------------------------------------------------------------------------\n// Convert binary string given as a char* and length to hex string representation\n//------------------------------------------------------------------------------\nstd::string\nStringConversion::BinData2HexString(const char* buf, const size_t buf_len,\n                                    const size_t nominal_len,\n                                    const char separator)\n{\n  std::string out;\n\n  if (buf_len == 0) {\n    return out;\n  }\n\n  char hb[4];\n\n  for (size_t i = 0; i < nominal_len; ++i) {\n    unsigned char target = 0x00;\n\n    if (i < buf_len) {\n      target = buf[i];\n    }\n\n    if ((separator != 0x00) && (i != (nominal_len - 1))) {\n      sprintf(hb, \"%02x%c\", target, separator);\n    } else {\n      sprintf(hb, \"%02x\", target);\n    }\n\n    out += hb;\n  }\n\n  return out;\n}\n\n//------------------------------------------------------------------------------\n// Convert checksum hex representation to binary string\n//------------------------------------------------------------------------------\nstd::unique_ptr<char[]>\nStringConversion::Hex2BinDataChar(const std::string& shex, size_t& out_size,\n                                  const size_t nominal_len)\n{\n  out_size = 0;\n  std::unique_ptr<char[]> buf {new char[nominal_len]};\n\n  if ((buf == nullptr) || shex.empty()) {\n    return nullptr;\n  }\n\n  memset(buf.get(), 0, nominal_len);\n  char hex[3];\n\n  for (size_t i = 0;\n       ((i < shex.length() - 1) && (i / 2 < nominal_len)); i += 2) {\n    hex[0] = shex.at(i);\n    hex[1] = shex.at(i + 1);\n    hex[2] = '\\0';\n    buf.get()[i / 2] = std::stol(hex, 0, 16);\n    ++out_size;\n  }\n\n  return buf;\n}\n\n//----------------------------------------------------------------------------\n//! Get size from the given string, return true if parsing was successful,\n//! false otherwise\n//----------------------------------------------------------------------------\nbool\nStringConversion::GetSizeFromString(const std::string& sizestring,\n                                    uint64_t& out)\n{\n  out = GetSizeFromString(sizestring.c_str());\n  return (errno == 0);\n}\n\n//------------------------------------------------------------------------------\n// Convert a readable string into a number\n//------------------------------------------------------------------------------\nunsigned long long\nStringConversion::GetSizeFromString(const char* instring)\n{\n  if (!instring || !*instring) {\n    errno = EINVAL;\n    return 0;\n  }\n\n  XrdOucString sizestring = instring;\n  unsigned long long convfactor = 1ll;\n  errno = 0;\n\n  if (sizestring.endswith(\"B\") || sizestring.endswith(\"b\")) {\n    sizestring.erase(sizestring.length() - 1);\n  }\n\n  if (sizestring.endswith(\"E\") || sizestring.endswith(\"e\")) {\n    convfactor = 1000ll * 1000ll * 1000ll * 1000ll * 1000ll * 1000ll;\n  }\n\n  if (sizestring.endswith(\"P\") || sizestring.endswith(\"p\")) {\n    convfactor = 1000ll * 1000ll * 1000ll * 1000ll * 1000ll;\n  }\n\n  if (sizestring.endswith(\"T\") || sizestring.endswith(\"t\")) {\n    convfactor = 1000ll * 1000ll * 1000ll * 1000ll;\n  }\n\n  if (sizestring.endswith(\"G\") || sizestring.endswith(\"g\")) {\n    convfactor = 1000ll * 1000ll * 1000ll;\n  }\n\n  if (sizestring.endswith(\"M\") || sizestring.endswith(\"m\")) {\n    convfactor = 1000ll * 1000ll;\n  }\n\n  if (sizestring.endswith(\"K\") || sizestring.endswith(\"k\")) {\n    convfactor = 1000ll;\n  }\n\n  if (sizestring.endswith(\"S\") || sizestring.endswith(\"s\")) {\n    convfactor = 1ll;\n  }\n\n  if ((sizestring.length() > 3) && (sizestring.endswith(\"MIN\")\n                                    || sizestring.endswith(\"min\"))) {\n    convfactor = 60ll;\n  }\n\n  if (sizestring.endswith(\"H\") || sizestring.endswith(\"h\")) {\n    convfactor = 3600ll;\n  }\n\n  if (sizestring.endswith(\"D\") || sizestring.endswith(\"d\")) {\n    convfactor = 86400ll;\n  }\n\n  if (sizestring.endswith(\"W\") || sizestring.endswith(\"w\")) {\n    convfactor = 7 * 86400ll;\n  }\n\n  if ((sizestring.length() > 2) && (sizestring.endswith(\"MO\")\n                                    || sizestring.endswith(\"mo\"))) {\n    convfactor = 31 * 86400ll;\n  }\n\n  if (sizestring.endswith(\"Y\") || sizestring.endswith(\"y\")) {\n    convfactor = 365 * 86400ll;\n  }\n\n  if (convfactor > 1) {\n    sizestring.erase(sizestring.length() - 1);\n  }\n\n  if ((sizestring.find(\".\")) != STR_NPOS) {\n    return ((unsigned long long)(strtod(sizestring.c_str(), NULL) * convfactor));\n  } else {\n    return (strtoll(sizestring.c_str(), 0, 10) * convfactor);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Convert a readable string into a number\n//------------------------------------------------------------------------------\nunsigned long long\nStringConversion::GetDataSizeFromString(const char* instring)\n{\n  if (!instring || !*instring) {\n    errno = EINVAL;\n    return 0ll;\n  }\n\n  XrdOucString sizestring = instring;\n  unsigned long long convfactor = 1ll;\n  errno = 0;\n\n  if (sizestring.endswith(\"B\") || sizestring.endswith(\"b\")) {\n    sizestring.erase(sizestring.length() - 1);\n  }\n\n  if (sizestring.endswith(\"E\") || sizestring.endswith(\"e\")) {\n    convfactor = 1000ll * 1000ll * 1000ll * 1000ll * 1000ll * 1000ll;\n  } else if (sizestring.endswith(\"P\") || sizestring.endswith(\"p\")) {\n    convfactor = 1000ll * 1000ll * 1000ll * 1000ll * 1000ll;\n  } else if (sizestring.endswith(\"T\") || sizestring.endswith(\"t\")) {\n    convfactor = 1000ll * 1000ll * 1000ll * 1000ll;\n  } else if (sizestring.endswith(\"G\") || sizestring.endswith(\"g\")) {\n    convfactor = 1000ll * 1000ll * 1000ll;\n  } else if (sizestring.endswith(\"M\") || sizestring.endswith(\"m\")) {\n    convfactor = 1000ll * 1000ll;\n  } else if (sizestring.endswith(\"K\") || sizestring.endswith(\"k\")) {\n    convfactor = 1000ll;\n  }\n\n  if (convfactor > 1ll) {\n    sizestring.erase(sizestring.length() - 1);\n  }\n\n  if ((sizestring.find(\".\")) != STR_NPOS) {\n    return ((unsigned long long)(strtod(sizestring.c_str(), NULL) * convfactor));\n  } else {\n    return (strtoll(sizestring.c_str(), 0, 10) * convfactor);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Split a 'key:value' definition into key + value\n//------------------------------------------------------------------------------\nbool\nStringConversion::SplitKeyValue(std::string keyval, std::string& key,\n                                std::string& value, std::string split)\n{\n  auto equalpos = keyval.find(split);\n\n  if (equalpos != std::string::npos) {\n    key.assign(keyval, 0, equalpos);\n    value.assign(keyval, equalpos + 1, keyval.length() - (equalpos + 1));\n    return true;\n  } else {\n    key = value = \"\";\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Split a 'key:value' definition into key + value\n//------------------------------------------------------------------------------\nbool\nStringConversion::SplitKeyValue(XrdOucString keyval, XrdOucString& key,\n                                XrdOucString& value, XrdOucString split)\n{\n  int equalpos = keyval.find(split.c_str());\n\n  if (equalpos != STR_NPOS) {\n    key.assign(keyval, 0, equalpos - 1);\n    value.assign(keyval, equalpos + 1);\n    return true;\n  } else {\n    key = value = \"\";\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Split a comma separated key:val list and fill it into a map\n//------------------------------------------------------------------------------\nbool\nStringConversion::GetKeyValueMap(const char* mapstring,\n                                 std::map<std::string, std::string>& map,\n                                 const char* split,\n                                 const char* sdelimiter,\n                                 std::vector<std::string>* keyvector)\n{\n  if (!mapstring) {\n    return false;\n  }\n\n  std::string is = mapstring;\n  std::string delimiter = sdelimiter;\n  std::vector<std::string> slist;\n  TokenizeQuoted(is, slist, delimiter);\n\n  if (!slist.size()) {\n    return false;\n  }\n\n  size_t keyvectorindex = 0;\n\n  for (auto it = slist.begin(); it != slist.end(); it++) {\n    std::string key;\n    std::string val;\n\n    if (SplitKeyValue(*it, key, val, split)) {\n      if (keyvector && !map.count(key)) {\n        if (std::find(keyvector->begin(), keyvector->end(), key) == keyvector->end()) {\n          std::vector<std::string>::iterator it = keyvector->begin();\n          std::advance(it, keyvectorindex);\n          keyvector->insert(it, key);\n        }\n      }\n\n      keyvectorindex++;\n      map[key] = val;\n    } else {\n      return false;\n    }\n  }\n\n  return true;\n}\n\n\n//------------------------------------------------------------------------------\n// Split a comma separated key:val list and fill it into a map with special path treatment\n//------------------------------------------------------------------------------\nbool\nStringConversion::GetSpecialKeyValueMap(const char* mapstring,\n                                        std::map<std::string, std::string>& map,\n                                        const char* split,\n                                        const char* sdelimiter,\n                                        std::vector<std::string>* keyvector,\n                                        const char* pathKey,\n                                        const char* stopKey)\n{\n  if (!mapstring || !split || !sdelimiter || !pathKey || !stopKey) {\n    return false;\n  }\n\n  std::string input(mapstring);\n  std::string delimiter(sdelimiter);\n  std::string splitter(split);\n  std::string pathPrefix = std::string(pathKey) + splitter;\n  std::string stopPrefix = std::string(stopKey) + splitter;\n  size_t pos = 0;\n  size_t start = 0;\n\n  while (start < input.length()) {\n    pos = input.find(delimiter, start);\n    std::string token;\n\n    if (pos == std::string::npos) {\n      token = input.substr(start);\n      start = input.length();\n    } else {\n      token = input.substr(start, pos - start);\n      start = pos + delimiter.length();\n    }\n\n    if (token.find(pathPrefix) == 0) {\n      std::string key = pathKey;\n      std::string value = token.substr(pathPrefix.length());\n\n      while (start < input.length()) {\n        pos = input.find(delimiter, start);\n        std::string next_token;\n\n        if (pos == std::string::npos) {\n          next_token = input.substr(start);\n          start = input.length();\n        } else {\n          next_token = input.substr(start, pos - start);\n          start = pos + delimiter.length();\n        }\n\n        if (next_token.find(stopPrefix) == 0) {\n          start -= (next_token.length() + delimiter.length());\n          break;\n        } else {\n          value += delimiter + next_token;\n        }\n      }\n\n      map[key] = value;\n\n      if (keyvector) {\n        keyvector->push_back(key);\n      }\n    } else {\n      size_t split_pos = token.find(splitter);\n\n      if (split_pos == std::string::npos) {\n        continue;\n      }\n\n      std::string key = token.substr(0, split_pos);\n      std::string value = token.substr(split_pos + splitter.length());\n      map[key] = value;\n\n      if (keyvector) {\n        keyvector->push_back(key);\n      }\n    }\n  }\n\n  return true;\n}\n\n\n//------------------------------------------------------------------------------\n// Specialized splitting function returning the host part out of a queue name\n//------------------------------------------------------------------------------\nXrdOucString\nStringConversion::GetHostPortFromQueue(const char* queue)\n{\n  XrdOucString hostport = queue;\n  int pos = hostport.find(\"/\", 2);\n\n  if (pos != STR_NPOS) {\n    hostport.erase(0, pos + 1);\n    pos = hostport.find(\"/\");\n\n    if (pos != STR_NPOS) {\n      hostport.erase(pos);\n    }\n  }\n\n  return hostport;\n}\n\n//------------------------------------------------------------------------------\n// Specialized splitting function returning the host:port part out of a queue name\n//------------------------------------------------------------------------------\nstd::string\nStringConversion::GetStringHostPortFromQueue(const char* queue)\n{\n  std::string hostport = queue;\n  int pos = hostport.find(\"/\", 2);\n\n  if (pos != STR_NPOS) {\n    hostport.erase(0, pos + 1);\n    pos = hostport.find(\"/\");\n\n    if (pos != STR_NPOS) {\n      hostport.erase(pos);\n    }\n  }\n\n  return hostport;\n}\n\n//------------------------------------------------------------------------------\n// Split 'a.b' into a and b\n//------------------------------------------------------------------------------\nvoid\nStringConversion::SplitByPoint(std::string in, std::string& pre,\n                               std::string& post)\n{\n  std::string::size_type dpos = 0;\n  pre = in;\n  post = in;\n\n  if ((dpos = in.find(\".\")) != std::string::npos) {\n    std::string s = in;\n    post.erase(0, dpos + 1);\n    pre.erase(dpos);\n  } else {\n    post = \"\";\n  }\n}\n\n//------------------------------------------------------------------------------\n// Load a text file <name> into a string\n//------------------------------------------------------------------------------\nconst char*\nStringConversion::LoadFileIntoString(const char* filename, std::string& out)\n{\n  std::ifstream load(filename);\n  std::stringstream buffer;\n  buffer << load.rdbuf();\n  out = buffer.str();\n  return out.c_str();\n}\n\n// ---------------------------------------------------------------------------\n// Save a string into  a text file <name>\n// ---------------------------------------------------------------------------\nbool\nStringConversion::SaveStringIntoFile(const char* filename,\n                                     const std::string& in)\n{\n  std::ofstream save(filename);\n  save.write(in.c_str(), in.size());\n  return true;\n}\n\n\n//------------------------------------------------------------------------------\n// Read a long long number as output of a shell command - this is not usefull\n// in multi-threaded environments.\n//------------------------------------------------------------------------------\nlong long\nStringConversion::LongLongFromShellCmd(const char* shellcommand)\n{\n  FILE* fd = popen(shellcommand, \"r\");\n\n  if (fd) {\n    char buffer[1025];\n    buffer[0] = 0;\n    int nread = fread((void*) buffer, 1, 1024, fd);\n    pclose(fd);\n\n    if ((nread > 0) && (nread < 1024)) {\n      buffer[nread] = 0;\n      return strtoll(buffer, 0, 10);\n    }\n  }\n\n  return LLONG_MAX;\n}\n\n//------------------------------------------------------------------------------\n// Read a string as output of a shell command - this is not usefull in\n// multi-threaded environments.\n//------------------------------------------------------------------------------\nstd::string\nStringConversion::StringFromShellCmd(const char* shellcommand)\n{\n  FILE* fd = popen(shellcommand, \"r\");\n  std::string shellstring;\n\n  if (fd) {\n    char buffer[1025];\n    buffer[0] = 0;\n    int nread = 0;\n\n    while ((nread = fread((void*) buffer, 1, 1024, fd)) > 0) {\n      buffer[nread] = 0;\n      shellstring += buffer;\n\n      if (nread != 1024) {\n        break;\n      }\n    }\n\n    pclose(fd);\n    return shellstring;\n  }\n\n  return \"<none>\";\n}\n\n//------------------------------------------------------------------------------\n// Return the time as <seconds>.<nanoseconds> in a string\n//------------------------------------------------------------------------------\nconst char*\nStringConversion::TimeNowAsString(XrdOucString& stime)\n{\n  struct timespec ts;\n  eos::common::Timing::GetTimeSpec(ts);\n  char tb[128];\n  snprintf(tb, sizeof(tb) - 1, \"%lu.%lu\", ts.tv_sec, ts.tv_nsec);\n  stime = tb;\n  return stime.c_str();\n}\n\n//------------------------------------------------------------------------------\n// Mask a tag 'key=val' as 'key=<...>' in an opaque string\n//------------------------------------------------------------------------------\nconst char*\nStringConversion::MaskTag(XrdOucString& line, const char* tag)\n{\n  XrdOucString smask = tag;\n  smask += \"=\";\n  int spos = line.find(smask.c_str());\n  int epos = line.find(\"&\", spos + 1);\n\n  if (spos != STR_NPOS) {\n    if (epos != STR_NPOS) {\n      line.erase(spos, epos - spos);\n    } else {\n      line.erase(spos);\n    }\n\n    smask += \"<...>\";\n    line.insert(smask.c_str(), spos);\n  }\n\n  return line.c_str();\n}\n\n//------------------------------------------------------------------------------\n// Parse a string as an URL (does not deal with opaque information)\n//------------------------------------------------------------------------------\nconst char*\nStringConversion::ParseUrl(const char* url, XrdOucString& protocol,\n                           XrdOucString& hostport)\n{\n  protocol = url;\n  hostport = url;\n  int ppos = protocol.find(\":/\");\n\n  if (ppos != STR_NPOS) {\n    protocol.erase(ppos);\n  } else {\n    if (protocol.beginswith(\"as3:\")) {\n      protocol = \"as3\";\n    } else {\n      protocol = \"file\";\n    }\n  }\n\n  if (protocol == \"file\") {\n    if (hostport.beginswith(\"file:\")) {\n      hostport = \"\";\n      return (url + 5);\n    } else {\n      hostport = \"\";\n      return url;\n    }\n  }\n\n  if (protocol == \"root\") {\n    int spos = hostport.find(\"//\", ppos + 2);\n\n    if (spos == STR_NPOS) {\n      return 0;\n    }\n\n    hostport.erase(spos);\n    hostport.erase(0, 7);\n    return (url + spos + 1);\n  }\n\n  if (protocol == \"as3\") {\n    if (hostport.beginswith(\"as3://\")) {\n      // as3://<hostname>/<bucketname>/<filename> like in ROOT\n      int spos = hostport.find(\"/\", 6);\n\n      if (spos != STR_NPOS) {\n        hostport.erase(spos);\n        hostport.erase(0, 6);\n        return (url + spos + 1);\n      } else {\n        return 0;\n      }\n    } else {\n      // as3:<bucketname>/<filename>\n      hostport = \"\";\n      return (url + 4);\n    }\n  }\n\n  if (protocol == \"http\") {\n    // http://<hostname><path>\n    int spos = hostport.find(\"/\", 7);\n\n    if (spos == STR_NPOS) {\n      return 0;\n    }\n\n    hostport.erase(spos);\n    hostport.erase(0, 7);\n    return (url + spos);\n  }\n\n  if (protocol == \"https\") {\n    // https://<hostname><path>\n    int spos = hostport.find(\"/\", 8);\n\n    if (spos == STR_NPOS) {\n      return 0;\n    }\n\n    hostport.erase(spos);\n    hostport.erase(0, 8);\n    return (url + spos);\n  }\n\n  if (protocol == \"gsiftp\") {\n    // gsiftp://<hostname><path>\n    int spos = hostport.find(\"/\", 9);\n\n    if (spos == STR_NPOS) {\n      return 0;\n    }\n\n    hostport.erase(spos);\n    hostport.erase(0, 9);\n    return (url + spos);\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Create an Url\n//------------------------------------------------------------------------------\nconst char*\nStringConversion::CreateUrl(const char* protocol, const char* hostport,\n                            const char* path, XrdOucString& url)\n{\n  if (!strcmp(protocol, \"file\")) {\n    url = path;\n    return url.c_str();\n  }\n\n  if (!strcmp(protocol, \"root\")) {\n    url = \"root://\";\n    url += hostport;\n    url += \"/\";\n    url += path;\n    return url.c_str();\n  }\n\n  if (!strcmp(protocol, \"as3\")) {\n    if (hostport && strlen(hostport)) {\n      url = \"as3://\";\n      url += hostport;\n      url += path;\n      return url.c_str();\n    } else {\n      url = \"as3:\";\n      url += path;\n      return url.c_str();\n    }\n  }\n\n  if (!strcmp(protocol, \"http\")) {\n    url = \"http://\";\n    url += hostport;\n    url += path;\n    return url.c_str();\n  }\n\n  if (!strcmp(protocol, \"gsiftp\")) {\n    url = \"gsiftp://\";\n    url += hostport;\n    url += path;\n    return url.c_str();\n  }\n\n  url = \"\";\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Check if string is hex number\n//------------------------------------------------------------------------------\nbool\nStringConversion::IsHexNumber(const char* hexstring, const char* format)\n{\n  if (!hexstring) {\n    return false;\n  }\n\n  unsigned long long number = strtoull(hexstring, 0, 16);\n  char controlstring[256];\n  snprintf(controlstring, sizeof(controlstring) - 1, format, number);\n  return !strcmp(hexstring, controlstring);\n}\n\n//------------------------------------------------------------------------------\n// Check if string is a decimal number\n//------------------------------------------------------------------------------\nbool\nStringConversion::IsDecimalNumber(const std::string& str)\n{\n  return !str.empty() && std::all_of(str.begin(), str.end(), ::isdigit);\n}\n\n//------------------------------------------------------------------------------\n// Check if a string is a double\n//------------------------------------------------------------------------------\nbool\nStringConversion::IsDouble(const std::string& s)\n{\n  char* end = nullptr;\n  double val = strtod(s.c_str(), &end);\n  return ((end != s.c_str()) && (*end == '\\0') && (val != HUGE_VAL));\n}\n\n//------------------------------------------------------------------------------\n// Convert numeric value to string in a pretty way using KB, MB etc. symbols\n//------------------------------------------------------------------------------\nstd::string\nStringConversion::GetPrettySize(float size)\n{\n  float fsize = 0;\n  std::string ret_str;\n  std::string size_unit;\n\n  if ((fsize = size / EB) >= 1) {\n    size_unit = \"EB\";\n  } else if ((fsize = size / PB) >= 1) {\n    size_unit = \"PB\";\n  } else if ((fsize = size / TB) >= 1) {\n    size_unit = \"TB\";\n  } else if ((fsize = size / MB) >= 1) {\n    size_unit = \"MB\";\n  } else {\n    fsize = size / KB;\n    size_unit = \"KB\";\n  }\n\n  char msg[30];\n  sprintf(msg, \"%.1f %s\", fsize, size_unit.c_str());\n  ret_str = msg;\n  return ret_str;\n}\n\n//------------------------------------------------------------------------------\n// CURL init/cleanup using local storage\n//------------------------------------------------------------------------------\nvoid StringConversion::tlCurlFree(void* arg)\n{\n  eos_static_debug(\"destroying thread specific CURL session\");\n  // delete the buffer\n  curl_easy_cleanup((CURL*)arg);\n}\n\nCURL* StringConversion::tlCurlInit()\n{\n  eos_static_debug(\"allocating thread specific CURL session\");\n  CURL* buf = curl_easy_init();\n\n  if (!buf) {\n    eos_static_crit(\"error initialising CURL easy session\");\n  }\n\n  if (buf && pthread_setspecific(sPthreadKey, buf)) {\n    eos_static_crit(\"error registering thread-local buffer located at %p for \"\n                    \"cleaning up : memory will be leaked when thread is \"\n                    \"terminated\", buf);\n  }\n\n  return buf;\n}\n\nvoid StringConversion::tlInitThreadKey()\n{\n  pthread_key_create(&sPthreadKey, StringConversion::tlCurlFree);\n}\n\nthread_local CURL* StringConversion::curl = NULL;\npthread_key_t  StringConversion::sPthreadKey;\npthread_once_t StringConversion::sInit = PTHREAD_ONCE_INIT;\n\n//------------------------------------------------------------------------------\n// Escape string using CURL\n//------------------------------------------------------------------------------\nstd::string\nStringConversion::curl_default_escaped(const std::string& str)\n{\n  pthread_once(&sInit, tlInitThreadKey);\n  std::string ret_str;\n\n  // encode the key\n  if (!curl) {\n    curl = tlCurlInit();\n  }\n\n  if (curl) {\n    char* output = curl_easy_escape(curl, str.c_str(), str.length());\n\n    if (output) {\n      ret_str = output;\n      curl_free(output);\n    }\n  }\n\n  return ret_str;\n}\n\n//------------------------------------------------------------------------------\n// Return an escaped URI\n//------------------------------------------------------------------------------\nstd::string\nStringConversion::curl_default_escaped(const char* ptr)\n{\n  pthread_once(&sInit, tlInitThreadKey);\n  std::string ret_str;\n\n  if (ptr) {\n    if (!curl) {\n      curl = tlCurlInit();\n    }\n\n    if (curl) {\n      char* output = curl_easy_escape(curl, ptr, strlen(ptr));\n\n      if (output) {\n        ret_str = output;\n        curl_free(output);\n      }\n    }\n  }\n\n  return ret_str;\n}\n\n//------------------------------------------------------------------------------\n// Escape path using CURL\n//------------------------------------------------------------------------------\nstd::string\nStringConversion::curl_path_escaped(const std::string& str)\n{\n  XrdOucString escaped = curl_default_escaped(str).c_str();\n\n  while (escaped.replace(\"%2F\", \"/\")) {}\n\n  return std::string(escaped.c_str());\n}\n\n\n//------------------------------------------------------------------------------\n// Unescape string using CURL\n//------------------------------------------------------------------------------\nstd::string\nStringConversion::curl_default_unescaped(const std::string& str)\n{\n  pthread_once(&sInit, tlInitThreadKey);\n  std::string ret_str;\n\n  // encode the key\n  if (!curl) {\n    curl = tlCurlInit();\n  }\n\n  if (curl) {\n    char* output = curl_easy_unescape(curl, str.c_str(), str.length(), 0);\n\n    if (output) {\n      ret_str = output;\n      curl_free(output);\n    }\n  }\n\n  return ret_str;\n}\n\n//------------------------------------------------------------------------------\n// Return an unescaped URI\n//------------------------------------------------------------------------------\nstd::string\nStringConversion::curl_default_unescaped(const char* ptr)\n{\n  pthread_once(&sInit, tlInitThreadKey);\n  std::string ret_str;\n\n  if (ptr) {\n    // encode the key\n    if (!curl) {\n      curl = tlCurlInit();\n    }\n\n    if (curl) {\n      char* output = curl_easy_unescape(curl, ptr, strlen(ptr), 0);\n\n      if (output) {\n        ret_str = output;\n        curl_free(output);\n      }\n    }\n  }\n\n  return ret_str;\n}\n\n//------------------------------------------------------------------------------\n// Escape string using CURL and add speical /#curl# prefix to the string\n//------------------------------------------------------------------------------\nstd::string\nStringConversion::curl_escaped(const std::string& str)\n{\n  pthread_once(&sInit, tlInitThreadKey);\n  std::string ret_str = \"<no-encoding>\";\n\n  // encode the key\n  if (!curl) {\n    curl = tlCurlInit();\n  }\n\n  if (curl) {\n    char* output = curl_easy_escape(curl, str.c_str(), str.length());\n\n    if (output) {\n      ret_str = output;\n      curl_free(output);\n      // don't escape '/'\n      XrdOucString no_slash = ret_str.c_str();\n\n      while (no_slash.replace(\"%2F\", \"/\")) {}\n\n      // this is a hack to avoid decoding a pathname twice\n      no_slash.insert(\"/#curl#\", 0);\n      ret_str = no_slash.c_str();\n    }\n  }\n\n  return ret_str;\n}\n\n//------------------------------------------------------------------------------\n// Unescape string using CURL that contains special /#curl# prefix\n//------------------------------------------------------------------------------\nstd::string\nStringConversion::curl_unescaped(const std::string& str)\n{\n  pthread_once(&sInit, tlInitThreadKey);\n  std::string ret_str = \"<no-encoding>\";\n\n  // encode the key\n  if (!curl) {\n    curl = tlCurlInit();\n  }\n\n  if (curl) {\n    if (strncmp(str.c_str(), \"/#curl#\", 7)) {\n      // the string has already been decoded\n      return str;\n    }\n\n    char* output = curl_easy_unescape(curl, str.c_str() + 7, str.length() - 7, 0);\n\n    if (output) {\n      ret_str = output;\n      curl_free(output);\n    }\n  }\n\n  return ret_str;\n}\n\n// ---------------------------------------------------------------------------\n// Escape JSON string\n// ---------------------------------------------------------------------------\nstd::string\nStringConversion::json_encode(const std::string& s)\n{\n  std::string output;\n  output.reserve(s.length());\n\n  for (size_t i = 0; i != s.length(); i++) {\n    char c = s.at(i);\n\n    switch (c) {\n    case '\"':\n      output += \"\\\\\\\"\";\n      break;\n\n    case '/':\n      output += \"\\\\/\";\n      break;\n\n    case '\\b':\n      output += \"\\\\b\";\n      break;\n\n    case '\\f':\n      output += \"\\\\f\";\n      break;\n\n    case '\\n':\n      output += \"\\\\n\";\n      break;\n\n    case '\\r':\n      output += \"\\\\r\";\n      break;\n\n    case '\\t':\n      output += \"\\\\t\";\n      break;\n\n    case '\\\\':\n      output += \"\\\\\\\\\";\n      break;\n\n    default:\n      output += c;\n      break;\n    }\n  }\n\n  return output;\n}\n\nvoid StringConversion::html_escape(std::string& str) {\n  std::string escaped;\n  escaped.reserve(str.size());\n  for (char c : str) {\n    switch (c) {\n      case '&': escaped += \"&amp;\"; break;\n      case '<': escaped += \"&lt;\"; break;\n      case '>': escaped += \"&gt;\"; break;\n      case '\"': escaped += \"&quot;\"; break;\n      case '\\'': escaped += \"&apos;\"; break;\n      default: escaped += c; break;\n    }\n  }\n  str.swap(escaped);\n}\n\n//------------------------------------------------------------------------------\n// Create random uuid string\n//------------------------------------------------------------------------------\nstd::string\nStringConversion::random_uuidstring()\n{\n  char id[40];\n  uuid_t uuid;\n  uuid_generate_time(uuid);\n  uuid_unparse(uuid, id);\n  return id;\n}\n\n//------------------------------------------------------------------------------\n// Create time-based uuid string\n//------------------------------------------------------------------------------\nstd::string StringConversion::timebased_uuidstring()\n{\n  uuid_t uuid;\n  //The uuid_unparse function converts the supplied UUID from the binary representation into a 36-byte string + '\\0' trailing\n  char uuid_str[37];\n  uuid_generate_time(uuid);\n  uuid_unparse(uuid, uuid_str);\n  return std::string(uuid_str);\n}\n\n//------------------------------------------------------------------------------\n// Sort lines alphabetically in-place\n//------------------------------------------------------------------------------\nvoid\nStringConversion::SortLines(XrdOucString& data)\n{\n  XrdOucString sorts = \"\";\n  std::vector<std::string> vec;\n  XrdOucTokenizer linizer((char*)data.c_str());\n  char* val = 0;\n\n  while ((val = linizer.GetLine())) {\n    vec.push_back(val);\n  }\n\n  std::sort(vec.begin(), vec.end());\n\n  for (unsigned int i = 0; i < vec.size(); ++i) {\n    sorts += vec[i].c_str();\n    sorts += \"\\n\";\n  }\n\n  data = sorts;\n}\n\n\n//------------------------------------------------------------------------------\n// Check if a string is valid UTF\n//------------------------------------------------------------------------------\n\nbool\nStringConversion::Valid_UTF8(const std::string& string)\n{\n  int c, i, ix, n, j;\n\n  for (i = 0, ix = string.length(); i < ix; i++) {\n    c = (unsigned char) string[i];\n\n    //if (c==0x09 || c==0x0a || c==0x0d || (0x20 <= c && c <= 0x7e) ) n = 0; // is_printable_ascii\n    if (0x00 <= c && c <= 0x7f) {\n      n = 0;  // 0bbbbbbb\n    } else if ((c & 0xE0) == 0xC0) {\n      n = 1;  // 110bbbbb\n    } else if (c == 0xed && i < (ix - 1) &&\n               ((unsigned char)string[i + 1] & 0xa0) == 0xa0) {\n      return false;  //U+d800 to U+dfff\n    } else if ((c & 0xF0) == 0xE0) {\n      n = 2;  // 1110bbbb\n    } else if ((c & 0xF8) == 0xF0) {\n      n = 3;  // 11110bbb\n    }\n    //else if (($c & 0xFC) == 0xF8) n=4; // 111110bb //byte 5, unnecessary in 4 byte UTF-8\n    //else if (($c & 0xFE) == 0xFC) n=5; // 1111110b //byte 6, unnecessary in 4 byte UTF-8\n    else {\n      return false;\n    }\n\n    for (j = 0; j < n &&\n         i < ix; j++) { // n bytes matching 10bbbbbb follow ?\n      if ((++i == ix) || (((unsigned char)string[i] & 0xC0) != 0x80)) {\n        return false;\n      }\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// CURL encode invalid UTF strings with SHA256\n//------------------------------------------------------------------------------\n\nstd::string\nStringConversion::EncodeInvalidUTF8(const std::string& key)\n{\n  if (!Valid_UTF8(key)) {\n    return curl_escaped(key);\n  } else {\n    return key;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Seal xrootd path i.e. replace any & with #AND#\n//------------------------------------------------------------------------------\nstd::string\nStringConversion::SealXrdPath(const std::string& input)\n{\n  std::string sealed(input);\n  ReplaceStringInPlace(sealed, \"&\", \"#AND#\");\n  return sealed;\n}\n\n//------------------------------------------------------------------------------\n// Unseal xrootd path i.e. replace any #AND# with &\n//------------------------------------------------------------------------------\nstd::string\nStringConversion::UnsealXrdPath(const std::string& input)\n{\n  std::string unsealed(input);\n  ReplaceStringInPlace(unsealed, \"#AND#\", \"&\");\n  return unsealed;\n}\n\n//------------------------------------------------------------------------------\n// Seal xrootd path in place i.e. replace any & with #AND#\n//------------------------------------------------------------------------------\nconst char*\nStringConversion::SealXrdPath(XrdOucString& input)\n{\n  const std::string seal = \"#AND#\";\n\n  while (input.replace(\"&\", seal.c_str())) {};\n\n  return input.c_str();\n}\n\n//------------------------------------------------------------------------------\n// Unseal xrootd path in place i.e. replace any #AND# with &\n//------------------------------------------------------------------------------\nconst char*\nStringConversion::UnsealXrdPath(XrdOucString& input)\n{\n  const std::string seal = \"#AND#\";\n\n  while (input.replace(seal.c_str(), \"&\")) {};\n\n  return input.c_str();\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/StringConversion.hh",
    "content": "// ----------------------------------------------------------------------\n// File: StringConversion.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   StringConversion.hh\n * @brief  Convenience class to deal with strings.\n */\n\n#ifndef __EOSCOMMON_STRINGCONVERSION__\n#define __EOSCOMMON_STRINGCONVERSION__\n\n#include \"common/Namespace.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n#include \"fmt/format.h\"\n#include <string>\n#include <vector>\n#include <algorithm>\n#include <map>\n#include <set>\n#include <memory>\n#include <stdio.h>\n#include <limits.h>\n#include <errno.h>\n#include <string.h>\n#include <fstream>\n#include <sstream>\n#include <openssl/sha.h>\n\ntypedef void CURL;\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//! Constants used throughout the code\nconst uint64_t KB = 1024;\nconst uint64_t MB = 1024 * KB;\nconst uint64_t GB = 1024 * MB;\nconst uint64_t TB = 1024 * GB;\nconst uint64_t PB = 1024 * TB;\nconst uint64_t EB = 1024 * PB;\n\n#define LC_STRING(x) eos::common::StringConversion::ToLower((x))\n\n//------------------------------------------------------------------------------\n//! Static helper class with convenience functions for string tokenizing,\n//! value2string and split functions.\n//------------------------------------------------------------------------------\nclass StringConversion\n{\npublic:\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Tokenize a string\n   *\n   * @param str string to be tokenized\n   * @param tokens  returned list of separated string tokens\n   * @param delimiters delimiter used for tokenizing\n   */\n  // ----------------------------------------------------------------------------\n  static void Tokenize(const std::string& str,\n                       std::vector<std::string>& tokens,\n                       const std::string& delimiters = \" \");\n\n  /**\n   * Tokenize a string respecting quoted strings\n   * Quotes and escape sequences are handled properly\n   */\n  static void TokenizeQuoted(const std::string& str, std::vector<std::string>& tokens,\n                             const std::string& delimiters);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Tokenize a string\n   *\n   * @param str string to be tokenized\n   * @param tokens  returned list of separated string tokens\n   * @param multi-char delimiter used for tokenizing\n   */\n  // ----------------------------------------------------------------------------\n  static void MulticharTokenize(const std::string& str,\n\t\t\t\tstd::vector<std::string>& tokens,\n\t\t\t\tconst std::string& delimiter = \" \");\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Tokenize a string accepting also empty members e.g. a||b returns 3 fields\n   *\n   * @param str string to be tokenized\n   * @param tokens  returned list of separated string tokens\n   * @param delimiters delimiter used for tokenizing\n   */\n  // ---------------------------------------------------------------------------\n  static void EmptyTokenize(const std::string& str,\n                            std::vector<std::string>& tokens,\n                            const std::string& delimiters = \" \");\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Join concatenates the elements of the vector to create a single string.\n   * The separator is placed between the elements in the result string.\n   *\n   * @param tokens the elements to concatenate\n   * @param separator the string to place between the elements\n   *\n   * @return joined string\n   */\n  // ---------------------------------------------------------------------------\n  static std::string Join(const std::vector<std::string>& tokens,\n                          const std::string& separator = \"\");\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Convert a string buffer to a hex dump string\n   *\n   * @param string to dump\n   *\n   * @return hex dumped string\n   */\n  // ---------------------------------------------------------------------------\n  static std::string string_to_hex(const std::string& input);\n  static std::string char_to_hex(const char input);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Convert an integer value to a hex dump string\n   *\n   * @param integer value to dump\n   *\n   * @return hex dumped integer\n   */\n  // ---------------------------------------------------------------------------\n  template<typename T, typename = std::enable_if_t<std::is_integral<T>::value>>\n  static std::string integral_to_hex(const T input)\n  {\n    std::ostringstream hex_oss;\n    hex_oss << std::hex << input;\n    return hex_oss.str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Convert binary string given as a char* and length to hex string\n  //! representation\n  //!\n  //! @param buf buffer holding the checksum in binary format\n  //! @param buf_len size of the checksum buffer\n  //! @param nominal_len expected size of the checksum in bytes\n  //! @param separator possible separator for the display of the converted\n  //!        string\n  //! @param return string holding the hex representation of the checksum or\n  //!        empty strin if nothing is converted\n  //----------------------------------------------------------------------------\n  static std::string\n  BinData2HexString(const char* buf, const size_t buf_len,\n                    const size_t nominal_len = SHA_DIGEST_LENGTH,\n                    const char separator = 0x00);\n\n\n  //----------------------------------------------------------------------------\n  //! Convert checksum hex representation to binary string\n  //!\n  //! @param shex string hex representation of checksum\n  //! @param out_size buffer size of the binary data\n  //!\n  //! @return array of chars holding the binary data representaion of the\n  //!         checksum or nullptr if there were any errors\n  //----------------------------------------------------------------------------\n  static std::unique_ptr<char[]>\n  Hex2BinDataChar(const std::string& shex, size_t& out_size,\n                  const size_t nominal_len = SHA_DIGEST_LENGTH);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Convert a long long value into time s,m,h,d  scale\n   *\n   * @param sizestring returned XrdOuc string representation\n   * @param seconds number to convert\n   *\n   * @return sizestring.c_str()\n   */\n  // ---------------------------------------------------------------------------\n  static const char*\n  GetReadableAgeString(XrdOucString& sizestring,\n                       unsigned long long age);\n\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Get a string with a width and precision formatted double value\n   * @param value - the double to print\n   * @param width - the total width of the printout\n   * @param percision - the precision to show after the .\n   *\n   * @return formated string showing double value\n   **/\n\n  static std::string\n  GetFixedDouble(double value, size_t width, size_t precision);\n\n  //----------------------------------------------------------------------------\n  //! Convert a long long value into K,M,G,T,P,E byte scale\n  //!\n  //! @param insize number to convert\n  //! @param unit unit to display e.g. B for bytes\n  //!\n  //! @return string representation\n  //----------------------------------------------------------------------------\n  static std::string\n  GetReadableSizeString(unsigned long long insize, const char* unit = \"\",\n                        size_t si = 1000);\n\n  //----------------------------------------------------------------------------\n  //! Convert a long long value into K,M,G,T,P,E byte scale\n  //!\n  //! @param sizestring returned XrdOucString representation\n  //! @param insize number to convert\n  //! @param unit unit to display e.g. B for bytes\n  //!\n  //! @return sizestring.c_str()\n  //----------------------------------------------------------------------------\n  static const char*\n  GetReadableSizeString(XrdOucString& sizestring,\n                        unsigned long long insize,\n                        const char* unit);\n\n  // ---------------------------------------------------------------------------\n  //! Convert a long long value into K,M,G,T,P,E byte scale\n  //!\n  //! @param sizestring returned std::string representation\n  //! @param insize number to convert\n  //! @param unit unit to display e.g. B for bytes\n  //!\n  //! @return sizestring.c_str()\n  // ---------------------------------------------------------------------------\n  static const char*\n  GetReadableSizeString(std::string& sizestring,\n                        unsigned long long insize,\n                        const char* unit);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Convert a readable string into a number\n   *\n   * @param sizestring readable string like 4KB or 1000GB or 1s,1d,1y\n   *\n   * @return number\n   */\n  // ----------------------------------------------------------------------------\n  static unsigned long long\n  GetSizeFromString(const char* sizestring);\n\n  static unsigned long long\n  GetSizeFromString(const XrdOucString& sizestring)\n  {\n    return GetSizeFromString(sizestring.c_str());\n  }\n\n  static unsigned long long\n  GetSizeFromString(const std::string& sizestring)\n  {\n    return GetSizeFromString(sizestring.c_str());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get size from the given string, return true if parsing was successful,\n  //! false otherwise\n  //----------------------------------------------------------------------------\n  static bool GetSizeFromString(const std::string& sizestring, uint64_t& out);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Convert a readable string into a number, only for data\n   *\n   * @param sizestring readable string like 4KB or 1000GB\n   *\n   * @return number\n   */\n  // ----------------------------------------------------------------------------\n  static unsigned long long\n  GetDataSizeFromString(const char* sizestring);\n\n  static unsigned long long\n  GetDataSizeFromString(const XrdOucString& sizestring)\n  {\n    return GetDataSizeFromString(sizestring.c_str());\n  }\n\n  static unsigned long long\n  GetDataSizeFromString(const std::string& sizestring)\n  {\n    return GetDataSizeFromString(sizestring.c_str());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Convert a numeric input to the string representation\n  //!\n  //! @param insize numeric input size\n  //!\n  //! @return string representation of numerical value\n  //----------------------------------------------------------------------------\n  template<typename T>\n  static std::string GetSizeString(T insize)\n  {\n    std::string format;\n\n    if (std::is_floating_point<T>::value) {\n      format = \"%.02f\";\n    } else if (std::is_integral<T>::value) {\n      format = \"%llu\";\n    } else {\n      return std::string();\n    }\n\n    char buffer[1024];\n    snprintf(buffer, sizeof(buffer) - 1, format.c_str(), insize);\n    return std::string(buffer);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Convert a numeric input into a XrdOucString\n  //!\n  //! @param sizestring returned string\n  //! @param insize numeric input\n  //!\n  //! @return point to output string\n  //----------------------------------------------------------------------------\n  template<typename T>\n  static inline const char*\n  GetSizeString(XrdOucString& sizestring, T insize)\n  {\n    sizestring = GetSizeString(insize).c_str();\n    return sizestring.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Convert a numeric input to a std::string\n  //!\n  //! @param sizestring returned string\n  //! @param insize numeric input\n  //!\n  //! @return sizestring.c_str()\n  // ---------------------------------------------------------------------------\n  template <typename T>\n  static inline const char*\n  GetSizeString(std::string& sizestring, T insize)\n  {\n    sizestring = GetSizeString(insize);\n    return sizestring.c_str();\n  }\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Split a 'key:value' definition into key + value\n   *\n   * @param keyval key-val string 'key:value'\n   * @param key returned key\n   * @param split split character\n   * @param value return value\n   *\n   * @return true if parsing ok, false if wrong format\n   */\n  // ---------------------------------------------------------------------------\n  static bool\n  SplitKeyValue(std::string keyval, std::string& key, std::string& value,\n                std::string split = \":\");\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Split a 'key:value' definition into key + value\n   *\n   * @param keyval key-val string 'key:value'\n   * @param key returned key\n   * @param split split character\n   * @param value return value\n   *\n   * @return true if parsing ok, false if wrong format\n   */\n  // ---------------------------------------------------------------------------\n  static bool\n  SplitKeyValue(XrdOucString keyval, XrdOucString& key, XrdOucString& value,\n                XrdOucString split = \":\");\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Split a comma separated key:val list and fill it into a map\n   *\n   * @param mapstring map string to parse\n   * @param map return map after parsing if ok\n   * @param split separator used to separate key from value default \":\"\n   * @param delimiter separator used to separate individual key value pairs\n   * @param keyvector returns optional the order of the keys in a vector\n   * @return true if format ok, otherwise false\n   */\n  // ---------------------------------------------------------------------------\n  static bool\n  GetKeyValueMap(const char* mapstring,\n                 std::map<std::string, std::string>& map,\n                 const char* split = \":\",\n                 const char* delimiter = \",\",\n                 std::vector<std::string>* keyvector = 0);\n\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Split a comma separated key:val list and fill it into a map\n   *\n   * @param mapstring map string to parse\n   * @param map return map after parsing if ok\n   * @param split separator used to separate key from value default \":\"\n   * @param delimiter separator used to separate individual key value pairs\n   * @param keyvector returns optional the order of the keys in a vector\n   * @param pathkey returns the name which indicates the beginning of a path, after which everything is read a s part of the path until stopKey\n   * @param stopKey marks the end of the previous pathkey\n   * @return true if format ok, otherwise false\n   */\n  // ---------------------------------------------------------------------------\n  static bool GetSpecialKeyValueMap(const char* mapstring,\n                                    std::map<std::string, std::string>& map,\n                                    const char* split,\n                                    const char* sdelimiter,\n                                    std::vector<std::string>* keyvector,\n                                    const char* pathKey = \"path\",\n                                    const char* stopKey = \"fstpath\");\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Replace a key in a string,string map\n   *\n   * @return true if replaced\n   */\n  // ---------------------------------------------------------------------------\n  static bool\n  ReplaceMapKey(std::map<std::string, std::string>& map, const char* oldk,\n                const char* newk)\n  {\n    if (map.count(oldk)) {\n      map[newk] = map[oldk];\n      map.erase(oldk);\n      return true;\n    }\n\n    return false;\n  }\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Specialized splitting function returning the host part out of a queue name\n   *\n   * @param queue name of a queue e.g. /eos/host:port/role\n   *\n   * @return string containing the host\n   */\n  // ---------------------------------------------------------------------------\n  static XrdOucString\n  GetHostPortFromQueue(const char* queue);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Specialized splitting function returning the host:port part out of a queue name\n   *\n   * @param queue name of a queue e.g. /eos/host:port/role\n   *\n   * @return string containing host:port\n   */\n  // ---------------------------------------------------------------------------\n  static std::string\n  GetStringHostPortFromQueue(const char* queue);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Split 'a.b' into a and b\n   *\n   * @param in 'a.b'\n   * @param pre string before .\n   * @param post string after .\n   */\n  // ---------------------------------------------------------------------------\n  static void\n  SplitByPoint(std::string in, std::string& pre, std::string& post);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Load a text file <name> into a string\n   *\n   * @param filename from where to load the contents\n   * @param out string where to inject the file contents\n   * @return (const char*) pointer to loaded string\n   */\n  // ---------------------------------------------------------------------------\n  static const char*\n  LoadFileIntoString(const char* filename, std::string& out);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Save a string into  a text file <name>\n   *\n   * @param filename where to save the contents\n   * @param in string with the contents\n   * @return (const char*) pointer to loaded string\n   */\n  // ---------------------------------------------------------------------------\n  static bool\n  SaveStringIntoFile(const char* filename, const std::string& in);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Read a long long number as output of a shell command - this is not useful\n   * in multi-threaded environments.\n   *\n   * @param shellcommand to execute\n   * @return long long value of converted shell output\n   */\n  // ---------------------------------------------------------------------------\n  static long long\n  LongLongFromShellCmd(const char* shellcommand);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Read a string as output of a shell command - this is not useful in\n   * multi-threaded environments.\n   *\n   * @param shellcommand to execute\n   * @return XrdOucString\n   */\n  // ---------------------------------------------------------------------------\n  static std::string\n  StringFromShellCmd(const char* shellcommand);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Return the time as <seconds>.<nanoseconds> in a string\n   *\n   * @param stime XrdOucString where to store the time as text\n   * @return const char* to XrdOucString object passed\n   */\n  // ---------------------------------------------------------------------------\n  static const char*\n  TimeNowAsString(XrdOucString& stime);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Mask a tag 'key=val' as 'key=<...>' in an opaque string\n   *\n   * @param XrdOucString where to mask\n   * @return pointer to string where the masked string is stored\n   */\n  // ---------------------------------------------------------------------------\n  static const char*\n  MaskTag(XrdOucString& line, const char* tag);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Parse a string as an URL (does not deal with opaque information)\n   *\n   * @param url string to parse\n   * @param &protocol - return of the protocol identifier\n   * @param &hostport - return of the host(port) identifier\n   * @return pointer to file path inside the url\n   */\n  // ---------------------------------------------------------------------------\n  static const char*\n  ParseUrl(const char* url, XrdOucString& protocol, XrdOucString& hostport);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Convert numeric value to string in a pretty way using KB, MB or GB symbols\n   *\n   * @param size size in KB to be processed\n   * @return string representation of the value in a pretty format\n   */\n  // ---------------------------------------------------------------------------\n  static std::string\n  GetPrettySize(float size);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Create an URL\n   *\n   * @param protocol - name of the protocol\n   * @param hostport - host[+port]\n   * @param path     - path name\n   * @param @url     - returned URL string\n   * @return char* to returned URL string\n   */\n  // ---------------------------------------------------------------------------\n  static const char*\n  CreateUrl(const char* protocol, const char* hostport, const char* path,\n            XrdOucString& url);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Check if a string is a hexadecimal number\n   *\n   * @param hexstring - hexadecimal string\n   * @param format - format used for printing e.g. %08x\n   * @return true if it is a converted hex number otherwise false\n   */\n  // ---------------------------------------------------------------------------\n  static bool\n  IsHexNumber(const char* hexstring, const char* format = \"%08x\");\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Check if a string is a decimal number\n   *\n   * @param s\n   * @return true if it is a decimal number\n   */\n  // ---------------------------------------------------------------------------\n\n  static bool\n  IsDecimalNumber(const std::string& s);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Check if a string is a double\n   *\n   * @param s\n   * @return true if it is\n   */\n  // ---------------------------------------------------------------------------\n\n  static bool\n  IsDouble(const std::string& s);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * UnQuote a string if it starts with a \" only ...\n   *\n   * @param string\n   * @return string without external quotes\n   */\n  // ---------------------------------------------------------------------------\n  static std::string\n  UnQuote(std::string s)\n  {\n    if (s.front() == '\\\"') {\n      s.erase(0, 1);\n\n      if (s.back() == '\\\"') {\n        s.pop_back();\n      }\n    }\n\n    return s;\n  }\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Return a lower case string\n   *\n   * @param input - input string\n   * @return lower case string\n   */\n  // ---------------------------------------------------------------------------\n  static std::string\n  ToLower(std::string is)\n  {\n    std::transform(is.begin(), is.end(), is.begin(), ::tolower);\n    return is;\n  }\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Return a lower case string\n   *\n   * @param input - input string\n   * @return lower case string\n   */\n  // ---------------------------------------------------------------------------\n  static std::string\n  ToLower(const char* s_is)\n  {\n    std::string is = s_is;\n    std::transform(is.begin(), is.end(), is.begin(), ::tolower);\n    return is;\n  }\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Return an octal string\n   * @param number - integer\n   * @param minimum format length\n   * @return octal number as string\n   */\n  // ---------------------------------------------------------------------------\n  static std::string\n  IntToOctal(int number, int digits = 4)\n  {\n    char format[16];\n    snprintf(format, sizeof(format), \"%%0%do\", digits);\n    char octal[32];\n    snprintf(octal, sizeof(octal), format, number);\n    return std::string(octal);\n  }\n\n  static void InitLookupTables()\n  {\n    for (int i = 0; i < 10; i++) {\n      pAscii2HexLkup['0' + i] = i;\n      pHex2AsciiLkup[i] = '0' + i;\n    }\n\n    for (int i = 0; i < 6; i++) {\n      pAscii2HexLkup['a' + i] = 10 + i;\n      pHex2AsciiLkup[10 + i] = 'a' + i;\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  /**\n   * @param u templated unsigned number to be converted in hexadecimal\n   * @param s buffer to write the result to\n   *\n   * @return the address of the last character written in the buffer + 1\n   */\n  // ---------------------------------------------------------------------------\n  template <typename UnsignedType> static char*\n  FastUnsignedToAsciiHex(UnsignedType u, char* s)\n  {\n    if (!u) {\n      *s = '0';\n      return s + 1;\n    }\n\n    int nchar = 0;\n    const int size = 2 * sizeof(UnsignedType);\n\n    for (int j = 1; j <= size; j++) {\n      int digit = (u >> ((size - j) << 2)) & 15;\n\n      if (!nchar && !digit) {\n        continue;\n      }\n\n      s[nchar++] = pHex2AsciiLkup[digit];\n    }\n\n    return s + nchar;\n  }\n\n  // ---------------------------------------------------------------------------\n  /**\n   * @param u templated unsigned number to be converted in hexadecimal\n   *\n   * @return the hex number as string\n   */\n  // ---------------------------------------------------------------------------\n  template <typename UnsignedType> static std::string\n  FastUnsignedToAsciiHex(UnsignedType u)\n  {\n    std::ostringstream oss;\n\n    if (!u) {\n      oss << '0';\n      return oss.str();\n    }\n\n    const int size = 2 * sizeof(UnsignedType);\n    bool hasChars = false;\n\n    for (int j = 1; j <= size; j++) {\n      int digit = (u >> ((size - j) << 2)) & 15;\n\n      if (hasChars || digit != 0) {\n        oss << pHex2AsciiLkup[digit];\n        hasChars = true;\n      }\n    }\n\n    return oss.str();\n  }\n\n  // ---------------------------------------------------------------------------\n  /**\n   * @param the buffer to read the ascii representation from.\n   * @param templated unsigned pointer to write the result to\n   * @param templated len to parse in the buffer (go until null character)\n   */\n  // ---------------------------------------------------------------------------\n  template <typename UnsignedType> static void\n  FastAsciiHexToUnsigned(char* s, UnsignedType* u, int len = -1)\n  {\n    *u = 0;\n\n    for (int j = 0; s[j] != 0 && j != len; j++) {\n      (*u) <<= 4;\n      (*u) += pAscii2HexLkup[static_cast<int>(s[j])];\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return an escaped URI\n  //!\n  //! @param str URI to escape\n  //!\n  //! @return escaped URI string\n  //----------------------------------------------------------------------------\n  static std::string\n  curl_default_escaped(const std::string& str);\n\n  //----------------------------------------------------------------------------\n  //! Return an escaped URI\n  //!\n  //! @param ptr - pointer to data that should be null terminated\n  //!\n  //! @return escaped URI string\n  //----------------------------------------------------------------------------\n  static std::string\n  curl_default_escaped(const char* ptr);\n\n  //----------------------------------------------------------------------------\n  //! Return an escaped path\n  //!\n  //! @param path to escape\n  //!\n  //! @return escaped path string\n  //----------------------------------------------------------------------------\n  static std::string\n  curl_path_escaped(const std::string& str);\n\n  //----------------------------------------------------------------------------\n  //! Return an unescaped URI\n  //!\n  //! @param str - uri to unescape\n  //! @return unescaped URI string\n  //----------------------------------------------------------------------------\n  static std::string\n  curl_default_unescaped(const std::string& str);\n\n  //----------------------------------------------------------------------------\n  //! Return an unescaped URI\n  //!\n  //! @param ptr pointer to data that should be null terminated\n  //!\n  //! @return unescaped URI string\n  //----------------------------------------------------------------------------\n  static std::string\n  curl_default_unescaped(const char* ptr);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Return an unescaped URI\n   *\n   * @param str - uri to unescape\n   * @return unescaped URI string\n   */\n  // ---------------------------------------------------------------------------\n  static std::string\n  curl_unescaped(const std::string& str);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Return an escaped URI\n   *\n   * @param str - uri to escape\n   * @return escaped URI string\n   */\n  // ---------------------------------------------------------------------------\n  static std::string\n  curl_escaped(const std::string& str);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Return an enocded json string\n   * @param str - string to escape\n   * @return escaped string\n   */\n  // ---------------------------------------------------------------------------\n\n  static std::string\n  json_encode(const std::string& str);\n\n  /**\n   * HTML-escape the string. This escapes '&', '<', '>','\"',' (single quote) and replaces\n   * those characters with their HTML-corresponding version\n   * @param str the string to html-escape (content will be swapped with the escaped string)\n   */\n  static void html_escape(std::string & str);\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Return a random generated uuid\n   * @return uuid string\n   */\n  // ---------------------------------------------------------------------------\n  static std::string\n  random_uuidstring();\n\n  //----------------------------------------------------------------------------\n  /**\n   * Returns a time-based generated uuid\n   *\n   * @return uuid string\n   */\n  //----------------------------------------------------------------------------\n  static std::string timebased_uuidstring();\n\n  // ---------------------------------------------------------------------------\n  /**\n   * Sort lines alphabetically in-place\n   * @param data input data\n   */\n  // ---------------------------------------------------------------------------\n  static void SortLines(XrdOucString& data);\n\n\n  //------------------------------------------------------------------------------\n  //! Fast convert element to string representation\n  //!\n  //! @param elem element to be converted\n  //!\n  //! @return string representation\n  //------------------------------------------------------------------------------\n  template <typename T>\n  static std::string stringify(const T& elem)\n  {\n    return fmt::to_string(elem);\n  }\n\n  //------------------------------------------------------------------------------\n  //! Replace a substring with another substring in a string\n  //------------------------------------------------------------------------------\n  static void ReplaceStringInPlace(std::string& subject,\n                                   const std::string& search,\n                                   const std::string& replace)\n  {\n    if (subject.empty() || search.empty()) {\n      return;\n    }\n\n    size_t pos = 0;\n\n    while ((pos = subject.find(search, pos)) != std::string::npos) {\n      subject.replace(pos, search.length(), replace);\n      pos += replace.length();\n    }\n  }\n\n\n  //------------------------------------------------------------------------------\n  //! Replace multiple characters in a string\n  //------------------------------------------------------------------------------\n  static void Replace(std::string& subject,\n                      const char a,\n                      const char b)\n  {\n    if (subject.empty()) {\n      return;\n    }\n\n    for (size_t i = 0; i < subject.size(); ++i) {\n      if (subject[i] == a) {\n        subject[i] = b;\n      }\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if a string is a valid UTF-8 string\n  //----------------------------------------------------------------------------\n  static bool Valid_UTF8(const std::string& string);\n\n  //----------------------------------------------------------------------------\n  //! CGI encode invalid UTF8 strings, valid just pass through\n  //----------------------------------------------------------------------------\n  static std::string EncodeInvalidUTF8(const std::string& string);\n\n  //----------------------------------------------------------------------------\n  //! CGI decode invalid UTF8 strings, valid just pass through\n  //----------------------------------------------------------------------------\n  static std::string DecodeInvalidUTF8(const std::string& string)\n  {\n    return curl_unescaped(string);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Seal xrootd path i.e. replace any & with #AND#\n  //!\n  //! @param input string to be sealed\n  //!\n  //! @return newly sealed string\n  //----------------------------------------------------------------------------\n  static std::string SealXrdPath(const std::string& input);\n\n  //----------------------------------------------------------------------------\n  //! Unseal xrootd path i.e. replace any #AND# with &\n  //!\n  //! @param input string to be unsealed\n  //!\n  //! @return newly unsealed string\n  //----------------------------------------------------------------------------\n  static std::string UnsealXrdPath(const std::string& input);\n\n  //----------------------------------------------------------------------------\n  //! Seal xrootd path in placei.e. replace any & with #AND#\n  //!\n  //! @param input string to be sealed\n  //!\n  //! @return sealed string\n  //----------------------------------------------------------------------------\n  static const char* SealXrdPath(XrdOucString& input);\n\n  //----------------------------------------------------------------------------\n  //! Unseal xrootd path in place i.e. replace any #AND# with &\n  //!\n  //! @param input string to be unsealed\n  //!\n  //! @return unsealed string\n  //----------------------------------------------------------------------------\n  static const char* UnsealXrdPath(XrdOucString& input);\n\n  //----------------------------------------------------------------------------\n  //! Seal string by replacing & with the desired seal\n  //! @note: to be used for opaque data\n  //!\n  //! @param s input string\n  //! @param seal type of seal to use\n  //!\n  //! @return pointer to the sealed string\n  //----------------------------------------------------------------------------\n  static const char* Seal(XrdOucString& s, const char* seal = \"#and#\")\n  {\n    while (s.replace(\"&\", seal)) {};\n\n    return s.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Un-seal string by replacing seal with &\n  //! @note: to be used for opaque data\n  //!\n  //! @param s input string\n  //! @param seal type of seal to use\n  //!\n  //! @return pointer to the un-sealed string\n  //----------------------------------------------------------------------------\n  static const char* UnSeal(XrdOucString& s, const char* seal = \"#and#\")\n  {\n    // Ensure compatibility with old clients using #AND#\n    const char* other_seal = \"#AND#\";\n\n    if (s.find(other_seal) != STR_NPOS) {\n      while (s.replace(other_seal, \"&\")) {};\n    } else {\n      while (s.replace(seal, \"&\")) {};\n    }\n\n    return s.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! ReduceString\n  //!\n  //! @param input string\n  //! @param maxsize\n  //!\n  //! @return reduced string - if the string is longer than maxsize it is\n  //!         truncated to half and a '|>' marker added in the beginning\n  //----------------------------------------------------------------------------\n  static std::string ReduceString(const std::string& input, size_t max = 127)\n  {\n    std::string rs = input;\n\n    if (rs.length() > max) {\n      rs.erase(0, max / 2);\n      rs.insert(0, \"|>\");\n    }\n\n    return rs;\n  }\n\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  StringConversion() = default;\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  // ---------------------------------------------------------------------------\n  ~StringConversion() = default;\n\nprivate:\n  //! Lookup Table for Hex Ascii Conversion\n  static char pAscii2HexLkup[256];\n  static char pHex2AsciiLkup[16];\n  static thread_local CURL* curl;\n  //! Thread-local storage management\n  static pthread_key_t sPthreadKey;\n  static pthread_once_t sInit;\n  static void tlCurlFree(void* arg);\n  static CURL* tlCurlInit();\n  static void tlInitThreadKey();\n};\n\n\n//------------------------------------------------------------------------------\n//! Static cURL global initializer\n//------------------------------------------------------------------------------\nstatic struct CurlGlobalInitializer {\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  CurlGlobalInitializer();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~CurlGlobalInitializer();\n} sCurlGlobalInit; ///< Static initializer for every translation unit\n\nEOSCOMMONNAMESPACE_END\n#endif\n"
  },
  {
    "path": "common/StringSplit.hh",
    "content": "// ----------------------------------------------------------------------\n// File: StringSplit.hh\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include <string_view>\n#include <type_traits>\n#include <algorithm>\n#include <vector>\n\nnamespace eos::common\n{\nnamespace detail\n{\n// A simple type checker to decide between types having a (const) iterator\ntemplate <typename T, typename = void>\nstruct has_const_iter : std::false_type {};\n\ntemplate <typename T>\nstruct has_const_iter<T, std::void_t<decltype(std::declval<T>().cbegin(),\n    std::declval<T>().cend())>>\n                             : std::true_type {};\n\ntemplate <typename T>\nbool constexpr has_const_iter_v = has_const_iter<T>::value;\n\n// A helper function to find the next position of a delimiter in a sequence\n// given a starting position\n// While tested for strings, this should work for any\n// iterator sequence which requires a subset match of a delimiter pattern.\n// Technically these can take univ. references if you decay the template\n// arguments and do a enable_if_t on the delim_t, given we don't have a lot of\n// variants the less generic version is a bit more readable\ntemplate <typename str_t, typename delim_t>\nauto get_delim_p(const str_t& str, const delim_t& delim,\n                 typename str_t::size_type start_pos) -> typename str_t::size_type {\n  static_assert(has_const_iter_v<delim_t>, \"delimiter must implement a const iterator!\");\n  auto p = std::find_first_of(str.cbegin() + start_pos, str.cend(),\n  delim.cbegin(), delim.cend());\n  return std::distance(str.cbegin(), p);\n}\n\ntemplate <typename str_t>\nauto get_delim_p(const str_t& str, char delim,\n                 typename str_t::size_type start_pos) -> typename str_t::size_type {\n  return str.find(delim, start_pos);\n}\n\n\n// simple overloads for distinguishing vector/deque types vs set types - technically\n// is_assoc_container_v should already get the job done, but a future map vs set\n// distinction would require has_emplace_back type functionality anyway as is_assoc\n// would be true for a map type\ntemplate <typename T, typename = void>\nstruct has_emplace_back : std::false_type {};\n\ntemplate <typename T>\nstruct has_emplace_back<T, std::void_t<decltype(std::declval<T>().emplace_back())>> :\n   std::true_type {};\n\ntemplate <typename T>\nbool constexpr has_emplace_back_v = has_emplace_back<T>::value;\n} // detail\n\n\n// A non owning iterator for splitting a string with delimiters As far as the\n// pointed string is valid, this is a really fast way to iterate over split parts\n//\n//   for (std::string_view part : StringSplit(input,\"/\")) {\n//\n//  ...}\n//\n// The current implementation is similar to boost::split and the like in the sense of\n// Given a string of delimiters, presence of any of them will trigger a match. For eg.\n//\n// StringSplit(\"ab,cd\\nde,gh\", \",\\n\") -> [\"ab\",\"cd\",\"de\", \"gh\"]\n//\n// Though it is easy enough to modify this to use find instead of find_first_of\n// for a pattern by using a different detail::get_delim_p function (This can be\n// an additional template param/tag which is currently not implemented though\n// fairly easy to do)\n//\n// For copying onto a container the same code above can be used for eg.\n// std::vector<std::string> v\n// for (std::string_view part : StringSplit(input,delim)) {\n//      v.emplace_back(part) }\n//\n// Though if it is sure that the parent string is in scope, using either the iterator\n// directly or using a vector<std::string_view> would yield the most performant results\n// A helper is provided which will move these to a desired container Use the\n// StringSplit or CharSplit aliases for most cases, we do need an explicit\n// std::string_view template specification in other cases as by default the\n// compiler will try to default to const char* which is not desirable\ntemplate <typename str_type = std::string_view,\n          typename delim_type = std::string_view>\nclass LazySplit\n{\npublic:\n  LazySplit(str_type s, delim_type d) : str(s), delim(d) {}\n\n  class iterator\n  {\n\n  public:\n    // A base declaration of the underlying string type so that we don't have to\n    // decay every time, this is to ensure that we correctly have a reference type\n    // when we hold a const std::string&,\n    using base_string_type = typename std::decay<str_type>::type;\n\n    // Basic iterator definition member types\n    using iterator_category = std::forward_iterator_tag;\n    using value_type = str_type;\n    using difference_type =\n      std::string_view::difference_type; // basically std::ptrdiff_t\n    using pointer = std::add_pointer_t<base_string_type>;\n    using const_pointer = std::add_const_t<pointer>;\n    using reference = std::add_lvalue_reference_t<base_string_type>;\n    using const_reference = std::add_const_t<reference>;\n    using size_type = std::string_view::size_type;\n\n    iterator(str_type s, delim_type d): str(s), delim(d), segment(next(0)) {}\n    iterator(size_type sz) : pos(sz) {}\n\n    iterator& operator++()\n    {\n      segment = next(pos);\n      return *this;\n    }\n\n    iterator operator++(int)\n    {\n      iterator curr = *this;\n      segment = next(pos);\n      return curr;\n    }\n\n    reference operator*()\n    {\n      return segment;\n    }\n    pointer operator->()\n    {\n      return &segment;\n    }\n\n    friend bool operator==(const iterator& a, const iterator& b)\n    {\n      return a.segment == b.segment;\n    }\n\n    friend bool operator!=(const iterator& a, const iterator& b)\n    {\n      return !(a == b);\n    }\n\n  private:\n    // we need to collapse the reference here, hence we have to return by value We\n    // have a special variant accepting char delimiters, this is useful for\n    // functions which split on nullbyte etc. For allmost everything else the\n    // other string_view splitter is more preferred as it allows for\n    // multicharacter splits while still maintaining speed. The member function*\n    // find_first_of is slightly slower than the std::find_first_of with iterators\n    base_string_type next(size_type start_pos)\n    {\n      // this loop is needed to advance past empty delims\n      while (start_pos < str.size()) {\n        pos = detail::get_delim_p(str, delim, start_pos);\n\n        // check if we are at the end or at a delim\n        if (pos != start_pos) {\n          return str.substr(start_pos, pos - start_pos);\n        }\n\n        start_pos = pos + 1;\n      }\n\n      return {};\n    }\n\n    size_type pos {0};\n    str_type str;\n    delim_type delim;\n    str_type segment;\n\n  };\n\n  using const_iterator = iterator;\n  iterator begin() const\n  {\n    return {str, delim};\n  }\n  const_iterator cbegin() const\n  {\n    return {str, delim};\n  }\n\n  iterator end() const\n  {\n    return { std::string::npos };\n  }\n  const_iterator cend() const\n  {\n    return { std::string::npos };\n  }\nprivate:\n  str_type str;\n  delim_type delim;\n};\n\ntemplate <typename C, typename K, typename V>\nbool operator==(const LazySplit<K, V>& split, const C& cont)\n{\n  return std::equal(split.begin(), split.end(),\n                    cont.begin(), cont.end());\n}\ntemplate <typename C, typename K, typename V>\nbool operator==(const C& cont, const LazySplit<K, V>& split)\n{\n  return std::equal(split.begin(), split.end(),\n                    cont.begin(), cont.end());\n}\n\nusing StringSplitIt = LazySplit<std::string_view, std::string_view>;\nusing CharSplitIt = LazySplit<std::string_view, char>;\n\ntemplate <typename C = std::vector<std::string_view>>\nauto StringSplit(std::string_view input, std::string_view delim)\n  -> std::enable_if_t<detail::has_emplace_back_v<C>, C>\n{\n  C c;\n  auto split_iter = StringSplitIt(input, delim);\n\n  for (std::string_view part : split_iter) {\n    c.emplace_back(part);\n  }\n\n  return c;\n}\n\n// Not defaulting this type as it will mean an additional include of an\n// unordered/ordered type which is unnecessary\ntemplate <typename C>\nauto StringSplit(std::string_view input, std::string_view delim)\n  -> std::enable_if_t<!detail::has_emplace_back_v<C>, C>\n{\n  C c;\n  auto split_iter = StringSplitIt(input, delim);\n\n  for (std::string_view part : split_iter) {\n    c.emplace(part);\n  }\n\n  return c;\n}\n\ntemplate <typename C = std::vector<std::string>>\nC SplitPath(std::string_view input)\n{\n  return StringSplit<C>(input, \"/\");\n}\n\ninline std::string\nGetRootPath(std::string_view path)\n{\n  using namespace std::string_view_literals;\n  auto it = StringSplitIt(path, \"/\"sv).begin();\n  return std::string(*it);\n}\n\n} // namespace eos::common\n"
  },
  {
    "path": "common/StringTokenizer.cc",
    "content": "// ----------------------------------------------------------------------\n// File: StringTokenizer.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringTokenizer.hh\"\n#include <cstring>\n#include <sstream>\n#include <iomanip>\n#include <algorithm>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nStringTokenizer::StringTokenizer(const char* s):\n  fCurrentLine(-1), fCurrentArg(-1)\n{\n  // the constructor just parses lines not token's within a line\n  if (s) {\n    fBuffer = strdup(s);\n  } else {\n    fBuffer = 0;\n    return;\n  }\n\n  bool inquote = false;\n\n  if (fBuffer[0] != 0) {\n    // set the first pointer to offset 0\n    fLineStart.push_back(0);\n  }\n\n  // intelligent parsing considering quoting\n  for (size_t i = 0; i < std::strlen(fBuffer); i++) {\n    if ((fBuffer[i] == '\"') &&\n        ((i == 0) ||\n         ((fBuffer[i - 1] != '\\\\')))) {\n      if (inquote) {\n        inquote = false;\n      } else {\n        inquote = true;\n      }\n    }\n\n    if ((!inquote) && fBuffer[i] == '\\n') {\n      fLineStart.push_back(i + 1);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nStringTokenizer::~StringTokenizer()\n{\n  if (fBuffer) {\n    free(fBuffer);\n    fBuffer = 0;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Return the next parsed line\n//------------------------------------------------------------------------------\nconst char*\nStringTokenizer::GetLine()\n{\n  fCurrentLine++;\n\n  if (fCurrentLine < (int) fLineStart.size()) {\n    char* line = fBuffer + fLineStart[fCurrentLine];\n    char* wordptr = line;\n    bool inquote = false;\n    size_t len = strlen(line) + 1;\n\n    for (size_t i = 0; i < len; i++) {\n      if ((line[i] == '\"') &&\n          ((i == 0) ||\n           ((line[i - 1] != '\\\\')))) {\n        if (inquote) {\n          inquote = false;\n        } else {\n          inquote = true;\n        }\n      }\n\n      if ((line[i] == ' ') || (line[i] == 0) || (line[i] == '\\n')) {\n        if (!inquote) {\n          if ((i > 1) && (line[i - 1] == '\\\\')) {\n            // don't start a new word here\n          } else {\n            char val = line[i];\n            line[i] = 0;\n            fLineArgs.push_back(wordptr);\n            line[i] = val;\n\n            // start a new word here\n            wordptr = line + i + 1;\n          }\n        }\n      }\n\n      if ((!inquote) && (line[i] == '\\n')) {\n        line[i] = 0;\n      }\n    }\n\n    return line;\n  } else {\n    return 0;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Return next parsed space separated token taking into account escaped\n// blanks and quoted strings.\n//\n// Note: Quotes enclosing the token are removed, but other type of quotes\n//       are left untouched\n//------------------------------------------------------------------------------\nconst char*\nStringTokenizer::GetToken(bool escapeand)\n{\n  fCurrentArg++;\n\n  if (fCurrentArg < (int) fLineArgs.size()) {\n    // patch out quotes\n    XrdOucString item = fLineArgs[fCurrentArg].c_str();\n\n    if (item.beginswith(\"\\\"\")) {\n      item.erase(0, 1);\n    }\n\n    if (item.endswith(\"\\\"\") &&\n        (!item.endswith(\"\\\\\\\"\"))) {\n      item.erase(item.length() - 1);\n    }\n\n    if (escapeand) {\n      int pos = 0;\n\n      while ((pos = item.find(\"&\", pos)) != STR_NPOS) {\n        if ((pos == 0) || (item[pos - 1] != '\\\\')) {\n          item.erase(pos, 1);\n          item.insert(\"#AND#\", pos);\n        }\n\n        pos++;\n      }\n    }\n\n    fLineArgs[fCurrentArg] = item.c_str();\n    return fLineArgs[fCurrentArg].c_str();\n  } else {\n    return 0;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Return next parsed space separated token taking into account escaped\n// blanks and quoted strings.\n//\n// Note: Quotes enclosing the token are removed, while any other\n//       type of quotes will be unescaped\n//------------------------------------------------------------------------------\nconst char*\nStringTokenizer::GetTokenUnquoted(bool escapeand)\n{\n  fCurrentArg++;\n\n  if (fCurrentArg < (int) fLineArgs.size()) {\n    std::string token;\n    std::stringstream ss;\n\n    // Dequote token\n    ss << fLineArgs[fCurrentArg].c_str();\n    ss >> std::quoted(token);\n\n    if (escapeand) {\n      size_t pos = 0;\n\n      while ((pos = token.find(\"&\", pos)) != std::string::npos) {\n        if ((pos == 0) || (token[pos - 1] != '\\\\')) {\n          token.replace(pos, 1, \"#AND#\");\n        }\n\n        pos++;\n      }\n    }\n\n    fLineArgs[fCurrentArg] = token.c_str();\n    return fLineArgs[fCurrentArg].c_str();\n  } else {\n    return 0;\n  }\n}\n\nbool\nStringTokenizer::IsUnsignedNumber(const std::string& str)\n{\n  return !str.empty() &&\n         str.find_first_not_of(\"0123456789\") == std::string::npos &&\n         (str.front() != '0' || str.size() == 1);\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/StringTokenizer.hh",
    "content": "// ----------------------------------------------------------------------\n// File: StringTokenizer.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <string>\n#include <vector>\n#include <sstream>\n#include <stdio.h>\n#include <errno.h>\n#include \"common/StringSplit.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Utility class with convenience functions for string command line parsing.\n//!\n//! Works like XrdOucTokenizer but wants each argument in \" \".\n//! When returned, each token will have the enclosing quotes removed.\n//!\n//! Additional options:\n//!   - Replace & with #AND# in tokens\n//!   - Fully unescape quotes within the token\n//------------------------------------------------------------------------------\nclass StringTokenizer\n{\n  char* fBuffer;\n  int fCurrentLine;\n  int fCurrentArg;\n  std::vector<size_t> fLineStart;\n  std::vector<std::string> fLineArgs;\n\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  StringTokenizer(const char* s);\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  StringTokenizer(XrdOucString s) : StringTokenizer(s.c_str()) {}\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  StringTokenizer(std::string s) : StringTokenizer(s.c_str()) {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~StringTokenizer();\n\n  //----------------------------------------------------------------------------\n  //! Get next parsed line separated by \\n\n  //!\n  //! @return next line\n  //----------------------------------------------------------------------------\n  const char* GetLine();\n\n  //----------------------------------------------------------------------------\n  //! Return next parsed space separated token taking into account escaped\n  //! blanks and quoted strings.\n  //!\n  //! Note: Quotes enclosing the token are removed, but other type of quotes\n  //!       are left untouched\n  //!\n  //! @param escapeand if true escape & with #AND# !! UGLY!!\n  //!\n  //! @return next token or null if no token found\n  //----------------------------------------------------------------------------\n  const char* GetToken(bool escapeand = true);\n\n  //----------------------------------------------------------------------------\n  //! Return next parsed space separated token taking into account escaped\n  //! blanks and quoted strings.\n  //!\n  //! Note: Quotes enclosing the token are removed, while any other\n  //!       type of quotes will be unescaped\n  //!\n  //! @param escapeand if true escape & with #AND#\n  //!\n  //! @return next token or null if no token found\n  //----------------------------------------------------------------------------\n  const char* GetTokenUnquoted(bool escapeand = true);\n\n  //----------------------------------------------------------------------------\n  //! Get next token and return it in the supplied StringType.\n  //!\n  //! Note: We use the StringType template to support both\n  //! std::string and XrdOucString.\n  //!\n  //! @param token the next token or empty string if nothing found\n  //! @param escapeand if true escape & with #AND#\n  //!\n  //! @return true if token retrieved, otherwise false\n  //----------------------------------------------------------------------------\n  template <typename StringType>\n  bool NextToken(StringType& token, bool escapeand = true);\n\n  //----------------------------------------------------------------------------\n  //! Split given string based on the delimiter\n  //!\n  //! @param str given string\n  //! @param delimiter delimiter\n  //!\n  //! @return vector of tokens\n  //----------------------------------------------------------------------------\n  template<typename C>\n  static C split(std::string_view str, const char delimiter);\n\n  //----------------------------------------------------------------------------\n  //! Merge vector's contents using the provided delimiter\n  //!\n  //! @param container container of tokens\n  //! @param delimiter delimiter\n  //!\n  //! @return string obtained from concatenating the tokens using the delimiter\n  //----------------------------------------------------------------------------\n  template<typename C>\n  static std::string merge(const C& container, const char delimiter);\n\n  //----------------------------------------------------------------------------\n  //! Check if string represents unsigned number - could be dropped ?!\n  //----------------------------------------------------------------------------\n  static bool IsUnsignedNumber(const std::string& str);\n};\n\n//------------------------------------------------------------------------------\n// Get next token and return it in the supplied StringType\n//------------------------------------------------------------------------------\ntemplate <typename StringType>\ninline bool StringTokenizer::NextToken(StringType& token, bool escapeand)\n{\n  const char* tmp = GetToken(escapeand);\n\n  if (tmp == nullptr) {\n    token = \"\";\n    return false;\n  }\n\n  token = tmp;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Split given string based on the delimiter\n//------------------------------------------------------------------------------\ntemplate<typename C>\nC StringTokenizer::split(std::string_view str, char delimiter)\n{\n  C container;\n  for (std::string_view part: CharSplitIt(str, delimiter)) {\n    container.emplace_back(part);\n  }\n\n  return container;\n}\n\n//------------------------------------------------------------------------------\n// Merge container's contents using the provided delimiter\n//------------------------------------------------------------------------------\ntemplate<typename C>\nstd::string\nStringTokenizer::merge(const C& container, char delimiter)\n{\n  std::ostringstream oss;\n\n  for (const auto& elem : container) {\n    oss << elem << delimiter;\n  }\n\n  std::string output = oss.str();\n\n  if (output.length()) {\n    output.resize(output.length() - 1);\n  }\n\n  return output;\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/StringUtils.hh",
    "content": "// ----------------------------------------------------------------------\n// File: StringUtils.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include <algorithm>\n#include <iomanip>\n#include <map>\n#include <sstream>\n#include <string>\n#include <charconv>\n#include <cstdint>\n#include <cstddef>\n#include <string_view>\n\n#include \"common/Namespace.hh\"\n#include \"common/utils/TypeTraits.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\ninline bool startsWith(std::string_view str,  std::string_view prefix)\n{\n  if (prefix.size() > str.size()) {\n    return false;\n  }\n\n  for (size_t i = 0; i < prefix.size(); i++) {\n    if (str[i] != prefix[i]) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\ninline bool endsWith(std::string_view str, std::string_view suffix)\n{\n  return (str.size() >= suffix.size()) &&\n         (str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0);\n}\n\n//------------------------------------------------------------------------------\n//! Left trim string in-place\n//------------------------------------------------------------------------------\nstatic inline void ltrim(std::string& s)\n{\n  s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {\n    return !std::isspace(ch);\n  }));\n}\n\n//------------------------------------------------------------------------------\n//! Rigth trim string in-place\n//------------------------------------------------------------------------------\nstatic inline void rtrim(std::string& s)\n{\n  s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {\n    return !std::isspace(ch);\n  }).base(), s.end());\n}\n\n//------------------------------------------------------------------------------\n//! Trim from both ends in-place\n//------------------------------------------------------------------------------\nstatic inline void trim(std::string& s)\n{\n  ltrim(s);\n  rtrim(s);\n}\n\n//------------------------------------------------------------------------------\n//! Bool to string\n//------------------------------------------------------------------------------\nstatic inline std::string boolToString(bool b)\n{\n  if (b) {\n    return \"true\";\n  }\n\n  return \"false\";\n}\n\n//------------------------------------------------------------------------------\n//! Join map\n//------------------------------------------------------------------------------\nstatic inline std::string joinMap(const std::map<std::string, std::string>& m,\n                                  const std::string& delim)\n{\n  std::ostringstream ss;\n  auto it = m.begin();\n\n  while (it != m.end()) {\n    ss << it->first << \"=\" << it->second;\n    it++;\n\n    if (it != m.end()) {\n      ss << delim;\n    }\n  }\n\n  return ss.str();\n}\n\nstatic inline std::string stringToHex(const std::string& in,\n                                      const char filler = '0', int width = 2, const std::string& delimiter = \"\")\n{\n  std::ostringstream ss;\n  ss << std::hex << std::setfill(filler);\n\n  for (size_t i = 0; in.length() > i; ++i) {\n    ss << std::setw(width > 0 ? width : 2) << static_cast<unsigned int>\n       (static_cast<unsigned char>(in[i])) << delimiter;\n  }\n\n  return ss.str();\n}\n\nstatic inline std::string hexToString(const std::string& in)\n{\n  std::string output;\n\n  // The caller must be aware and check the pair ( output, in.lenght() )\n  if ((in.length() % 2) != 0) {\n    return \"\";\n  }\n\n  size_t cnt = in.length() / 2;\n\n  for (size_t i = 0; cnt > i; ++i) {\n    uint32_t s = 0;\n    std::stringstream ss;\n    ss << std::hex << in.substr(i * 2, 2);\n    ss >> s;\n    output.push_back(static_cast<unsigned char>(s));\n  }\n\n  return output;\n}\n\n//----------------------------------------------------------------------------\n//! Get a Numeric Value from String supports all unsigned + integer types,\n//! can set a default value on failure if supplied.\n//! Also fills a string for log_msg if supplied, On Linux this is usually\n//! faster than atoi and friends, no allocation is promised in case the StrT\n//! doesn't allocate, the template arguments are inferred based on the given\n//! value of value. Unfortunately gcc < 11 does not support floating point\n//! conversions just yet, to save from long template compilation failures we\n//! SFINAE for this and fallback to stod and friends for float, this can be\n//! removed once we move to gcc11 everywhere, where std::from_chars natively\n//! does the right thing and also does some neat optimisations\n//!\n//! @tparam StrT A string like container type, std::string/string_view or\n//!              a container of chars will also work, however const char* will\n//!              not as we need a start and end position (use operator \"\"s/sv for\n//!              arguments in case you're doing strings at compile time)\n//! @tparam NumT The numeric type, will be inferred from the given arguments\n//! @param key the string key which needs to be converted to a number\n//! @param value the value that will be filled on conversion\n//! @param default_val a default in case conversion fails (Set to Integer Default)\n//! @param log_msg a string log msg that can be later used for logging\n//!\n//! @return bool indicating successful conversion\n//----------------------------------------------------------------------------\ntemplate <typename StrT, typename NumT>\nauto StringToNumeric(const StrT& key, NumT& value,\n                     NumT default_val = {},\n                     std::string* log_msg = nullptr) noexcept\n-> std::enable_if_t<detail::is_charconv_numeric_v<NumT>, bool> {\n  NumT result;\n\n  static_assert(detail::has_data_t<StrT>::value,\n  \"StringToNumeric requires a string like container with data(),\"\n  \"consider wrapping a string_view or operator sv for string literals\");\n\n  auto ret = std::from_chars(key.data(), key.data() + key.size(), result);\n\n  if (ret.ec != std::errc())\n  {\n    value = default_val;\n\n    if (log_msg != nullptr) {\n      auto _ec  = std::make_error_condition(ret.ec);\n      // Obligatory gripe about the std; since we can not concat str_view + str\n      // doing it this way so that it will work for any str like types\n      log_msg->append(\"\\\"msg=Failed Numeric conversion\\\" key=\");\n      log_msg->append(key);\n      log_msg->append(\" error_msg=\");\n      log_msg->append(_ec.message());\n    }\n\n    return false;\n  }\n\n  value = result;\n  return true;\n}\n\n#if __cpp_lib_to_chars < 201611\n// A floating point version of StringToNumeric, that iterates through\n// the various stod and friends depending on the type supplied\n// Currently the str overload will only work for const str& type\n// or anything convertible to std::string that stod understands\n// TODO: Remove this whenever we update to gcc11!!\ntemplate <typename StrT, typename NumT>\nauto StringToNumeric(const StrT& key, NumT& value,\n                     NumT default_val = {},\n                     std::string* log_msg = nullptr) noexcept\n-> std::enable_if_t<std::is_floating_point_v<NumT>, bool> {\n  // Not super nice, but gets the job done, a lazy way to iterate through\n  // the different fp types, since the evaluation hapens at compile time\n  // only the relevant branch will be compiled.\n  try {\n    if constexpr(std::is_same_v<NumT, float>)\n    {\n      value = std::stof(key);\n    } else if constexpr(std::is_same_v<NumT, double>)\n    {\n      value = std::stod(key);\n    } else if constexpr(std::is_same_v<NumT, long double>)\n    {\n      value = std::stold(key);\n    }\n  } catch (std::exception& ec)\n  {\n    value = default_val;\n\n    if (log_msg != nullptr) {\n      // Slightly tweak the error message, can be used in tests to identify\n      // that no silly float -> int conversion took place and this function\n      // was only selected for floats\n      log_msg->append(\"\\\"msg=Failed float conversion\\\" key=\");\n      log_msg->append(key);\n      log_msg->append(\" error_msg=\");\n      log_msg->append(ec.what());\n    }\n\n    return false;\n  }\n\n  return true;\n}\n\n#endif // __cpp_lib_to_chars\n\n\n// an XrdOucString inspired replace function that replaces\n// all occurences of s1 with s2 for a given str, in place\n// The Original function returned the signed size of total\n// length modification, however this doesn't really tell\n// us if any replacement happened since s1.size() == s2.size()\n// would mean the str size remains the same regardless of replacement\n// so we don't really return the difference.\nstatic inline void replace_all(std::string& str,\n                 std::string_view s1, std::string_view s2,\n                 size_t from=0, size_t to=std::string::npos)\n{\n  const size_t orig_str_size = str.size();\n  if (str.empty() || s1.empty() || from >= orig_str_size || from > to) {\n    return;\n  }\n\n  to = std::min(to, str.size() - 1);\n\n  // Run 2 passes of the replace function, the first pass\n  // determines the total delta, this allows to calculate\n  // upfront our target size and hence reduce regrowing the\n  // string at every interval\n  const size_t l1 = s1.size();\n  const size_t l2 = s2.size();\n\n  if (l1 > to - from + 1) {\n    return;\n  }\n\n  // Check if the orig string will need to be expanded, this occurs\n  // when the replacement target is bigger. We'd need to reallocate\n  // do this once!\n  if (l2 > l1) {\n    size_t match_count {0};\n    size_t pos {from};\n    size_t end_pos {to - l1 + 1};\n    size_t delta_per {l2 - l1};\n    while (pos <= end_pos) {\n      pos = str.find(s1, pos);\n      if (pos == std::string::npos || pos > end_pos) {\n        break;\n      }\n      ++match_count;\n      pos += l1;\n    }\n\n    if (match_count == 0) {\n      return;\n    }\n\n    str.reserve(orig_str_size + (match_count * delta_per));\n  }\n\n\n  size_t end_pos {to - l1 + 1};\n  size_t curr_pos {from};\n  const std::ptrdiff_t delta_per = static_cast<std::ptrdiff_t>(l2) - static_cast<std::ptrdiff_t>(l1);\n  while (curr_pos != std::string::npos) {\n    size_t match_pos = str.find(s1, curr_pos);\n\n    if (match_pos == std::string::npos || match_pos > end_pos) {\n      return;\n    }\n\n    str.replace(match_pos, l1, s2);\n\n    if (delta_per != 0) {\n      if (delta_per > 0) {\n        to += static_cast<size_t>(delta_per);\n      } else {\n        const size_t abs_diff = static_cast<size_t>(-delta_per);\n        if (abs_diff <= to) {\n          to -= abs_diff;\n        } else {\n          // Clamp to 0 if the contraction exceeds the 'to' index\n          to = 0;\n        }\n      }\n\n      if (to < l1) {\n        break;\n      }\n\n      end_pos = to - l1 + 1;\n\n    }\n    curr_pos = match_pos + l2;\n  }\n\n}\n\n\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/SymKeys.cc",
    "content": "// ----------------------------------------------------------------------\n// File: SymKeys.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <sstream>\n#include <iomanip>\n#include <openssl/evp.h>\n#include <openssl/opensslv.h>\n#if OPENSSL_VERSION_NUMBER >= 0x30000000L\n#include <openssl/provider.h>\n#endif\n#include <openssl/rsa.h>\n#include <openssl/x509.h>\n#include <openssl/engine.h>\n#include <openssl/hmac.h>\n#include <openssl/crypto.h>\n#include \"common/Namespace.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/UriCapCipher.hh\"\n#include \"google/protobuf/io/zero_copy_stream_impl.h\"\n#include \"zlib.h\"\n\n#ifdef __APPLE__\n#define ENOKEY 126\n#define EKEYREJECTED 129\n#endif\n\nEOSCOMMONNAMESPACE_BEGIN\n\nSymKeyStore gSymKeyStore; //< global SymKey store singleton\nXrdSysMutex SymKey::msMutex;\n\nvoid\nUriCapCipherDeleter::operator()(UriCapCipher* ptr) const\n{\n  delete ptr;\n}\n\nUriCapCipher&\nSymKey::GetUriCapCipher()\n{\n  std::lock_guard<std::mutex> lock(mUriCapMutex);\n  if (!mUriCapCipher) {\n    std::string password(key, SHA_DIGEST_LENGTH);\n    mUriCapCipher = std::unique_ptr<UriCapCipher, UriCapCipherDeleter>(\n        new UriCapCipher(UriCapCipher::PasswordTag{},\n                         UriCapCipher::FixedSaltTag{},\n                         std::move(password)));\n  }\n  return *mUriCapCipher;\n}\n\n// Add compatibility methods present in OpenSSL >= 1.1.0 if we use an older\n// version of OpenSSL\n#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined (LIBRESSL_VERSION_NUMBER)\n#define EVP_MD_CTX_new EVP_MD_CTX_create\n#define EVP_MD_CTX_free EVP_MD_CTX_destroy\n#define ASN1_STRING_get0_data(x) ASN1_STRING_data(x)\n\nstatic HMAC_CTX* HMAC_CTX_new(void)\n{\n  HMAC_CTX* ctx = (HMAC_CTX*)OPENSSL_malloc(sizeof(*ctx));\n\n  if (ctx != NULL) {\n    HMAC_CTX_init(ctx);\n  }\n\n  return ctx;\n}\n\nstatic void HMAC_CTX_free(HMAC_CTX* ctx)\n{\n  if (ctx != NULL) {\n    HMAC_CTX_cleanup(ctx);\n    OPENSSL_free(ctx);\n  }\n}\n#endif\n\n#if OPENSSL_VERSION_NUMBER >= 0x30000000L\nstatic void openssl3provider()\n{\n  static XrdSysMutex initMtx;\n  XrdSysMutexHelper iMtx(initMtx);\n  static bool init = 0;\n\n  if (!init) {\n    EVP_MD* mdp = EVP_MD_fetch(NULL, \"SHA2-256\", NULL);\n\n    if (mdp) {\n      EVP_MD_free(mdp);\n    }\n\n    (void) OSSL_PROVIDER_load(NULL, \"legacy\");\n  }\n}\n#endif\n\n\n//----------------------------------------------------------------------------\n// Constructor for a symmetric key\n//----------------------------------------------------------------------------\nSymKey::SymKey(const char* inkey, time_t invalidity)\n{\n  key64 = \"\";\n  memcpy(key, inkey, SHA_DIGEST_LENGTH);\n  SymKey::Base64Encode(key, SHA_DIGEST_LENGTH, key64);\n  mValidity = invalidity;\n  SHA_CTX sha1;\n  SHA1_Init(&sha1);\n  SHA1_Update(&sha1, (const char*) inkey, SHA_DIGEST_LENGTH);\n  SHA1_Final((unsigned char*) keydigest, &sha1);\n  XrdOucString skeydigest64 = \"\";\n  Base64Encode(keydigest, SHA_DIGEST_LENGTH, skeydigest64);\n  strncpy(keydigest64, skeydigest64.c_str(), (SHA_DIGEST_LENGTH * 2) - 1);\n}\n\n//------------------------------------------------------------------------------\n// Compute the HMAC SHA-256 value\n//------------------------------------------------------------------------------\nstd::string\nSymKey::HmacSha256(const std::string& key,\n                   const std::string& data,\n                   unsigned int blockSize,\n                   unsigned int resultSize)\n{\n  HMAC_CTX* ctx = HMAC_CTX_new();\n  std::string result;\n  unsigned int data_len = data.length();\n  unsigned int key_len = key.length();\n  unsigned char* pKey = (unsigned char*)key.c_str();\n  unsigned char* pData = (unsigned char*)data.data();\n  result.resize(resultSize);\n  unsigned char* presult = (unsigned char*)result.c_str();\n  HMAC_Init_ex(ctx, pKey, key_len, EVP_sha256(), NULL);\n\n  while (data_len > blockSize) {\n    HMAC_Update(ctx, pData, blockSize);\n    data_len -= blockSize;\n    pData += blockSize;\n  }\n\n  if (data_len) {\n    HMAC_Update(ctx, pData, data_len);\n  }\n\n  HMAC_Final(ctx, presult, &resultSize);\n  HMAC_CTX_free(ctx);\n  return result;\n}\n\n//------------------------------------------------------------------------------\n// Compute binary SHA-1 value\n//------------------------------------------------------------------------------\nstd::string\nSymKey::BinarySha1(const std::string& data)\n{\n  unsigned int data_len = data.length();\n  unsigned char* pdata = (unsigned char*)data.data();\n  std::string result;\n  result.resize(EVP_MAX_MD_SIZE);\n  unsigned char* presult = (unsigned char*)result.c_str();\n  unsigned int sz_result;\n  {\n    XrdSysMutexHelper scope_lock(msMutex);\n    EVP_MD_CTX* md_ctx = EVP_MD_CTX_new();\n    EVP_DigestInit_ex(md_ctx, EVP_sha1(), NULL);\n    EVP_DigestUpdate(md_ctx, pdata, data_len);\n    EVP_DigestFinal_ex(md_ctx, presult, &sz_result);\n    EVP_MD_CTX_free(md_ctx);\n  }\n  result.resize(sz_result);\n  return result;\n}\n\n//------------------------------------------------------------------------------\n// Compute hex digest of SHA-1 value\n//------------------------------------------------------------------------------\nstd::string\nSymKey::HexSha1(const std::string& data)\n{\n  const std::string binary_sha1 = BinarySha1(data);\n  unsigned char* presult = (unsigned char*)binary_sha1.data();\n  unsigned int sz_result = binary_sha1.size();\n  std::ostringstream oss;\n  oss.fill('0');\n  oss << std::hex;\n\n  for (unsigned int i = 0; i < sz_result; ++i) {\n    oss << std::setw(2) << (unsigned int) *presult;\n    ++presult;\n  }\n\n  return oss.str();\n}\n\n//------------------------------------------------------------------------------\n// Compute hexdigest of the SHA-256 value\n//------------------------------------------------------------------------------\nstd::string\nSymKey::HexSha256(const std::string& data, unsigned int blockSize)\n{\n  unsigned int data_len = data.length();\n  unsigned char* pdata = (unsigned char*)data.data();\n  std::string result;\n  result.resize(EVP_MAX_MD_SIZE);\n  unsigned char* presult = (unsigned char*)result.data();\n  unsigned int sz_result;\n  {\n    XrdSysMutexHelper scope_lock(msMutex);\n    EVP_MD_CTX* md_ctx = EVP_MD_CTX_new();\n    EVP_DigestInit_ex(md_ctx, EVP_sha256(), NULL);\n\n    while (data_len > blockSize) {\n      EVP_DigestUpdate(md_ctx, pdata, blockSize);\n      data_len -= blockSize;\n      pdata += blockSize;\n    }\n\n    if (data_len) {\n      EVP_DigestUpdate(md_ctx, pdata, data_len);\n    }\n\n    EVP_DigestFinal_ex(md_ctx, presult, &sz_result);\n    EVP_MD_CTX_free(md_ctx);\n  }\n  // Return the hexdigest of the SHA256 value\n  std::ostringstream oss;\n  oss.fill('0');\n  oss << std::hex;\n  presult = (unsigned char*)result.data();\n\n  for (unsigned int i = 0; i < sz_result; ++i) {\n    oss << std::setw(2) << (unsigned int) *presult;\n    ++presult;\n  }\n\n  return oss.str();\n}\n\n//------------------------------------------------------------------------------\n// Compute the HMAC SHA-1 value according to AWS standard\n//------------------------------------------------------------------------------\nstd::string\nSymKey::HmacSha1(std::string& data, const char* key)\n{\n  std::string result(EVP_MAX_MD_SIZE, '\\0');\n  unsigned int result_size = 0;\n  unsigned int data_len = data.length();\n\n  // If no key specifed used the default key provided by the SymKeyStore\n  if (!key) {\n    key = gSymKeyStore.GetCurrentKey()->GetKey64();\n  }\n\n  unsigned int key_len = strlen(key);\n  unsigned char* pdata = (unsigned char*)data.data();\n  unsigned char* presult = (unsigned char*)result.c_str();\n  presult = HMAC(EVP_sha1(), (void*)key, key_len, pdata, data_len,\n                 presult, &result_size);\n  result.resize(result_size + 1);\n  return result;\n}\n\n//------------------------------------------------------------------------------\n// Base64 encoding function - base function\n//------------------------------------------------------------------------------\nbool\nSymKey::Base64Encode(const char* decoded_bytes, ssize_t decoded_length,\n                     std::string& out)\n{\n  BIO* b64 = BIO_new(BIO_f_base64());\n\n  if (!b64) {\n    return false;\n  }\n\n  BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);\n  BIO* bmem = BIO_new(BIO_s_mem());\n\n  if (!bmem) {\n    return false;\n  }\n\n  b64 = BIO_push(b64, bmem);\n  BIO_write(b64, decoded_bytes, decoded_length);\n\n  if (BIO_flush(b64) != 1) {\n    BIO_free_all(b64);\n    return false;\n  }\n\n  BUF_MEM* bptr;\n  BIO_get_mem_ptr(b64, &bptr);\n  out.assign(bptr->data, bptr->length);\n  BIO_free_all(b64);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Base64 encoding function - returning an XrdOucString object\n//------------------------------------------------------------------------------\nbool\nSymKey::Base64Encode(const char* in, unsigned int inlen, XrdOucString& out)\n{\n  std::string encoded;\n\n  if (Base64Encode(in, inlen, encoded)) {\n    out = encoded.c_str();\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Base64 decoding function - base function\n//------------------------------------------------------------------------------\nbool\nSymKey::Base64Decode(const char* encoded_bytes, char*& decoded_bytes,\n                     ssize_t& decoded_length)\n{\n  BIO* bmem = BIO_new_mem_buf((void*)encoded_bytes, -1);\n\n  if (!bmem) {\n    return false;\n  }\n\n  BIO* b64 = BIO_new(BIO_f_base64());\n\n  if (!b64) {\n    return false;\n  }\n\n  BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);\n  bmem = BIO_push(b64, bmem);\n  ssize_t buffer_length = BIO_get_mem_data(bmem, NULL);\n  decoded_bytes = (char*) malloc(buffer_length + 1);\n  decoded_length = BIO_read(bmem, decoded_bytes, buffer_length);\n  decoded_bytes[decoded_length] = '\\0';\n  BIO_free_all(bmem);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Base64 decoding of input given as XrdOucString\n//------------------------------------------------------------------------------\nbool\nSymKey::Base64Decode(const char* in, std::string& out)\n{\n  BIO* bmem = BIO_new_mem_buf((void*)in, -1);\n\n  if (!bmem) {\n    return false;\n  }\n\n  BIO* b64 = BIO_new(BIO_f_base64());\n\n  if (!b64) {\n    return false;\n  }\n\n  BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);\n  bmem = BIO_push(b64, bmem);\n  size_t buffer_length = BIO_get_mem_data(bmem, NULL);\n  out.resize(buffer_length, '\\0');\n  int nread = BIO_read(bmem, (char*)out.data(), buffer_length);\n  out.resize(nread);\n  BIO_free_all(bmem);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Base64 decoding of input given as XrdOucString\n//------------------------------------------------------------------------------\nbool\nSymKey::Base64Decode(XrdOucString& in, char*& out, ssize_t& outlen)\n{\n  return Base64Decode(in.c_str(), out, outlen);\n}\n\n//------------------------------------------------------------------------------\n// Encode a base64: prefixed string - XrdOucString as input\n//------------------------------------------------------------------------------\nbool\nSymKey::Base64(XrdOucString& in, XrdOucString& out)\n{\n  if (in.beginswith(\"base64:\")) {\n    out = in;\n    return false;\n  }\n\n  bool done = Base64Encode((char*) in.c_str(), in.length(), out);\n\n  if (done) {\n    out.insert(\"base64:\", 0);\n    return true;\n  } else {\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Encode a base64: prefixed string - std::string as input\n//------------------------------------------------------------------------------\nbool\nSymKey::Base64(std::string& in, std::string& out)\n{\n  if (in.substr(0, 7) == \"base64:\") {\n    out = in;\n    return false;\n  }\n\n  XrdOucString sout;\n  bool done = Base64Encode((char*) in.c_str(), in.length(), sout);\n\n  if (done) {\n    out = \"base64:\";\n    out.append(sout.c_str());\n    return true;\n  } else {\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Decode a base64: prefixed string - XrdOucString as input\n//------------------------------------------------------------------------------\nbool\nSymKey::DeBase64(XrdOucString& in, XrdOucString& out)\n{\n  if (!in.beginswith(\"base64:\")) {\n    out = in;\n    return true;\n  }\n\n  XrdOucString in64 = in;\n  in64.erase(0, 7);\n  char* valout = 0;\n  ssize_t valout_len = 0;\n\n  if (Base64Decode(in64, valout, valout_len)) {\n    std::string s;\n    s.assign(valout, 0, valout_len);\n    out = s.c_str();\n    free(valout);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Decode a base64: prefixed string - std::string as input\n//------------------------------------------------------------------------------\nbool\nSymKey::DeBase64(const std::string& in, std::string& out)\n{\n  if (in.substr(0, 7) != \"base64:\") {\n    out = in;\n    return true;\n  }\n\n  XrdOucString in64 = in.c_str();\n  in64.erase(0, 7);\n  char* valout = 0;\n  ssize_t valout_len = 0;\n  Base64Decode(in64, valout, valout_len);\n\n  if (valout) {\n    out.assign(valout, valout_len);\n    free(valout);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Encode a base64: prefixed string - std::string as input\n//------------------------------------------------------------------------------\nbool\nSymKey::ZBase64(std::string& in, std::string& out)\n{\n  char desthex[9];\n  sprintf(desthex, \"%08lx\", in.size());\n  std::vector<char> destbuffer;\n  destbuffer.resize(in.size() + 128);\n  destbuffer.reserve(in.size() + 128);\n  uLongf destLen = destbuffer.size() - 8;\n  sprintf(&(destbuffer[0]), \"%08lx\", in.size());\n\n  if (compress((Bytef*) & (destbuffer[8]), &destLen, (const Bytef*)in.c_str(),\n               in.size())) {\n    return false;\n  }\n\n  XrdOucString sout;\n  bool done = Base64Encode((char*) & (destbuffer[0]), destLen + 8, sout);\n\n  if (done) {\n    out = \"zbase64:\";\n    out.append(sout.c_str());\n    return true;\n  } else {\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Decode a zbase64: prefixed string - std::string as input\n//------------------------------------------------------------------------------\nbool\nSymKey::ZDeBase64(std::string& in, std::string& out)\n{\n  if (in.substr(0, 8) != \"zbase64:\") {\n    out = in;\n    return true;\n  }\n\n  XrdOucString in64 = in.c_str();\n  in64.erase(0, 8);\n  char* valout = 0;\n  ssize_t valout_len = 0;\n  Base64Decode(in64, valout, valout_len);\n\n  if (valout) {\n    // first 8 bytes are the length of the decompressed data in hext\n    std::string desthex;\n    desthex.assign(valout, 8);\n    // now decompress the b64 buffer\n    unsigned long destLen = strtoul(desthex.c_str(), 0, 16);\n    std::vector<char> destbuffer;\n    destbuffer.reserve(destLen);\n    destbuffer.resize(destLen);\n    uLongf dstLen = destbuffer.size();\n\n    if (uncompress((Bytef*) & (destbuffer[0]), &dstLen, (const Bytef*)valout + 8,\n                   valout_len - 8)) {\n      free(valout);\n      return false;\n    } else {\n      free(valout);\n\n      if (dstLen == destLen) {\n        out.assign(&(destbuffer[0]), dstLen);\n        return true;\n      } else {\n        return false;\n      }\n    }\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Obfucate a buffer based on offset and hmac\n//------------------------------------------------------------------------------\n\nvoid\nSymKey::ObfuscateBuffer(char* dst, const char* src, size_t size, off_t offset,\n                        SymKey::hmac_t& hmac)\n{\n  const char* cipher = hmac.key.c_str();\n  size_t len = hmac.key.length();\n  size_t ilen = offset % len;\n  bool overwrite = (dst == src);\n\n  if ((!ilen) && (!(len % 8)) && (!(size % len)) &&\n      (!((unsigned long long)(dst) % 8)) && (!((unsigned long long)(src) % 8))) {\n    // fast case\n    uint64_t* pbuf = (uint64_t*) dst;\n    uint64_t* sbuf = (uint64_t*) src;\n\n    for (size_t i = 0; i < size / len; ++i) {\n      uint64_t* cipher64 = (uint64_t*)(cipher);\n\n      for (size_t k = 0; k < len / 8; ++k) {\n        if (overwrite) {\n          *pbuf++ ^= *cipher64++;\n        } else {\n          *pbuf++ = *sbuf++ ^ *cipher64++;\n        }\n      }\n    }\n  } else {\n    // slow case\n    if (dst == src) {\n      for (size_t i = 0; i < size; ++i) {\n        *dst++ ^= cipher[(offset + i) % len];\n      }\n    } else {\n      for (size_t i = 0; i < size; ++i) {\n        *dst++ = *src++ ^ cipher[(offset + i) % len];\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Unobfucate a buffer based on offset and hmac\n//------------------------------------------------------------------------------\nvoid\nSymKey::UnobfuscateBuffer(char* buf, size_t size, off_t offset,\n                          SymKey::hmac_t& hmac)\n{\n  const char* cipher = hmac.key.c_str();\n  size_t len = hmac.key.length();\n  size_t ilen = offset % len;\n\n  if ((!ilen) && (!(len % 8)) && (!(size % len)) &&\n      (!((unsigned long long)(buf) % 8))) {\n    // fast case\n    uint64_t* pbuf = (uint64_t*) buf;\n\n    for (size_t i = 0; i < size / len; ++i) {\n      uint64_t* cipher64 = (uint64_t*)(cipher);\n\n      for (size_t k = 0; k < len / 8; ++k) {\n        *pbuf++ ^= *cipher64++;\n      }\n    }\n  } else {\n    // slow case\n    char* pbuf = buf;\n\n    for (size_t i = 0; i < size; ++i) {\n      *pbuf++ ^= cipher[(offset + i) % len];\n    }\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Serialise a Google Protobuf object and base64 encode the result\n//------------------------------------------------------------------------------\nbool\nSymKey::ProtobufBase64Encode(const google::protobuf::Message* msg,\n                             std::string& output)\n{\n#if GOOGLE_PROTOBUF_VERSION < 3004000\n  auto sz = msg->ByteSize();\n#else\n  auto sz = msg->ByteSizeLong();\n#endif\n  std::string buffer(sz, '\\0');\n  google::protobuf::io::ArrayOutputStream aos((void*)buffer.data(), sz);\n\n  if (!msg->SerializeToZeroCopyStream(&aos)) {\n    return false;\n  }\n\n  return Base64Encode(buffer.data(), buffer.size(), output);\n}\n\n//------------------------------------------------------------------------------\n// Set a key providing its base64 encoded representation and validity\n//------------------------------------------------------------------------------\nSymKey*\nSymKeyStore::SetKey64(const char* inkey64, time_t invalidity)\n{\n  if (!inkey64) {\n    return 0;\n  }\n\n  char* binarykey = 0;\n  ssize_t outlen = 0;\n  XrdOucString key64 = inkey64;\n\n  if (!SymKey::Base64Decode(key64, binarykey, outlen)) {\n    return 0;\n  }\n\n  if (outlen != SHA_DIGEST_LENGTH) {\n    free(binarykey);\n    return 0;\n  }\n\n  return SetKey(binarykey, invalidity);\n}\n\n\n//------------------------------------------------------------------------------\n// Set a key providing it's binary representation and validity\n//------------------------------------------------------------------------------\nSymKey*\nSymKeyStore::SetKey(const char* inkey, time_t invalidity)\n{\n  if (!inkey) {\n    return 0;\n  }\n\n  std::unique_lock<std::mutex> scope_lock(mMutex);\n  SymKey* key = SymKey::Create(inkey, invalidity);\n  free((void*) inkey);\n\n  if (!key) {\n    return 0;\n  }\n\n  // check if it exists\n  SymKey* existkey = Store.Find(key->GetDigest64());\n\n  // if it exists we remove it add it with the new validity time\n  // if it exists we remove it add it with the new validity time\n  if (existkey) {\n    Store.Del(existkey->GetDigest64());\n  }\n\n  Store.Add(key->GetDigest64(), key,\n            invalidity ? (invalidity + EOSCOMMONSYMKEYS_DELETIONOFFSET) : 0);\n  // point the current key to last added\n  currentKey = key;\n  return key;\n}\n\n//------------------------------------------------------------------------------\n// Retrieve key by keydigest in base64 format\n//------------------------------------------------------------------------------\nSymKey*\nSymKeyStore::GetKey(const char* inkeydigest64)\n{\n  std::unique_lock<std::mutex> scope_lock(mMutex);\n  SymKey* key = Store.Find(inkeydigest64);\n  // if it exists we remove it add it with the new validity time\n  return key;\n}\n\n//------------------------------------------------------------------------------\n// Retrieve last added valid key from the store\n//------------------------------------------------------------------------------\nSymKey*\nSymKeyStore::GetCurrentKey()\n{\n  if (currentKey) {\n    if (currentKey->IsValid()) {\n      return currentKey;\n    }\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Create EOS specific capability and append to the output env object\n//------------------------------------------------------------------------------\nint\nSymKey::CreateCapability(XrdOucEnv* inenv, XrdOucEnv*& outenv,\n                         SymKey* key, std::chrono::seconds validity)\n{\n  if (!key) {\n    return ENOKEY;\n  }\n\n  if (!inenv) {\n    return EINVAL;\n  }\n\n  if (outenv) {\n    delete outenv;\n    outenv = nullptr;\n  }\n\n  int envlen;\n  XrdOucString toencrypt = inenv->Env(envlen);\n  // Add the validity time\n  toencrypt += \"&cap.valid=\";\n  char svalidity[32];\n  snprintf(svalidity, 32, \"%llu\",\n           (long long unsigned int)(time(NULL) + validity.count()));\n  toencrypt += svalidity;\n  std::string cgi;\n  try {\n    cgi = key->GetUriCapCipher().encryptToCgiFields(toencrypt.c_str());\n  } catch (...) {\n    return EKEYREJECTED;\n  }\n\n  XrdOucString encenv = \"\";\n  encenv += \"cap.key=\";\n  encenv += key->GetDigest64();\n  encenv += \"&\";\n  encenv += \"cap.format=AEAD\";\n  encenv += \"&\";\n  encenv += cgi.c_str();\n\n  while (encenv.replace('\\n', '#')) {};\n\n  outenv = new XrdOucEnv(encenv.c_str());\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Extract EOS specific capability encoded in the env object\n//------------------------------------------------------------------------------\nint\nSymKey::ExtractCapability(XrdOucEnv* inenv, XrdOucEnv*& outenv)\n{\n  if (outenv) {\n    delete outenv;\n    outenv = nullptr;\n  }\n\n  if (!inenv) {\n    return EINVAL;\n  }\n\n  int envlen;\n  XrdOucString instring = inenv->Env(envlen);\n\n  while (instring.replace('#', '\\n')) {};\n\n  XrdOucEnv fixedenv(instring.c_str());\n\n  const char* keydigest = fixedenv.Get(\"cap.key\");\n  const char* capformat = fixedenv.Get(\"cap.format\");\n  const char* symkey = fixedenv.Get(\"cap.sym\");\n  const char* symmsg = fixedenv.Get(\"cap.msg\");\n\n  //  fprintf(stderr,\"%s\\n%s\\n\", symkey, symmsg);\n  if ((!symkey) || (!symmsg)) {\n    return EINVAL;\n  }\n\n  eos::common::SymKey* key {nullptr};\n  const char* lookup = keydigest ? keydigest : symkey;\n\n  if (!(key = eos::common::gSymKeyStore.GetKey(lookup))) {\n    return ENOKEY;\n  }\n\n  std::string decrypted;\n  if (capformat && (std::string(capformat) == \"AEAD\")) {\n    try {\n      decrypted = key->GetUriCapCipher().decryptFromCgiFields(instring.c_str());\n    } catch (...) {\n      return EKEYREJECTED;\n    }\n  } else {\n    // Legacy format (cap.sym holds digest, cap.msg is base64 ciphertext)\n    XrdOucString todecrypt = symmsg;\n    XrdOucString legacy = \"\";\n    if (!SymmetricStringDecrypt(todecrypt, legacy, (char*)key->GetKey())) {\n      return EKEYREJECTED;\n    }\n    decrypted = legacy.c_str();\n  }\n\n  if (decrypted.empty()) {\n    return EKEYREJECTED;\n  }\n\n  outenv = new XrdOucEnv(decrypted.c_str());\n\n  // Check time validity\n  if (!outenv->Get(\"cap.valid\")) {\n    // validity missing\n    return EINVAL;\n  } else {\n    time_t now = time(NULL);\n    time_t capnow = atoi(outenv->Get(\"cap.valid\"));\n\n    // Capability expired!!!\n    if (capnow < now) {\n      return ETIME;\n    }\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Cipher encrypt\n//------------------------------------------------------------------------------\nbool\nSymKey::CipherEncrypt(const char* data, ssize_t data_length,\n                      char*& encrypted_data, ssize_t& encrypted_length,\n                      char* key)\n{\n  // Set the initialization vector so that the encrypted text is unique\n  uint_fast8_t iv[EVP_MAX_IV_LENGTH];\n  sprintf((char*)iv, \"$KJh#(}q\");\n  const EVP_CIPHER* cipher = EVP_des_cbc();\n#if OPENSSL_VERSION_NUMBER >= 0x30000000L\n  openssl3provider();\n#endif\n  // This is slow, but we really don't care here for small messages\n  int buff_capacity = data_length + EVP_CIPHER_block_size(cipher);\n  char* encrypt_buff = (char*) malloc(buff_capacity);\n\n  if (!encrypt_buff) {\n    return false;\n  }\n\n  uint_fast8_t* fast_ptr = (uint_fast8_t*)encrypt_buff;\n  encrypted_length = 0;\n  EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();\n  EVP_CIPHER_CTX_init(ctx);\n  EVP_EncryptInit_ex(ctx, cipher, 0, (const unsigned char*)key, iv);\n\n  if (!(EVP_EncryptUpdate(ctx, fast_ptr, (int*)&encrypted_length,\n                          (uint_fast8_t*)data, data_length))) {\n    EVP_CIPHER_CTX_free(ctx);\n    free(encrypt_buff);\n    return false;\n  }\n\n  if (encrypted_length < 0) {\n    EVP_CIPHER_CTX_free(ctx);\n    free(encrypt_buff);\n    return false;\n  }\n\n  fast_ptr += encrypted_length;\n  int tmplen = 0;\n\n  if (!(EVP_EncryptFinal(ctx, fast_ptr, &tmplen))) {\n    EVP_CIPHER_CTX_free(ctx);\n    free(encrypt_buff);\n    return false;\n  }\n\n  encrypted_length += tmplen;\n\n  if (encrypted_length > buff_capacity) {\n    EVP_CIPHER_CTX_free(ctx);\n    free(encrypt_buff);\n    return false;\n  }\n\n  encrypted_data = encrypt_buff;\n  EVP_CIPHER_CTX_free(ctx);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Cipher decrypt\n//------------------------------------------------------------------------------\nbool\nSymKey::CipherDecrypt(char* encrypted_data, ssize_t encrypted_length,\n                      char*& data, ssize_t& data_length, char* key, bool noerror)\n{\n  // Set the initialization vector\n  uint_fast8_t iv[EVP_MAX_IV_LENGTH];\n  sprintf((char*)iv, \"$KJh#(}q\");\n  const EVP_CIPHER* cipher = EVP_des_cbc();\n\n  if (!cipher) {\n    return false;\n  }\n\n#if OPENSSL_VERSION_NUMBER >= 0x30000000L\n  openssl3provider();\n#endif\n  // This is slow, but we really don't care here for small messages. We're\n  // going to null terminate the text under the assumption it's non-null\n  // terminated ASCII text.\n  int buff_capacity = encrypted_length + EVP_CIPHER_block_size(cipher) + 1;\n  data = (char*) malloc(buff_capacity);\n\n  if (!data) {\n    return false;\n  }\n\n  uint_fast8_t* fast_ptr = (uint_fast8_t*)data;\n  data_length = 0;\n  EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();\n  EVP_CIPHER_CTX_init(ctx);\n  EVP_DecryptInit_ex(ctx, cipher, 0, (const unsigned char*) key, iv);\n  int decrypt_len = 0;\n\n  if (!EVP_DecryptUpdate(ctx, fast_ptr, &decrypt_len,\n                         (uint_fast8_t*)encrypted_data, encrypted_length)) {\n    EVP_CIPHER_CTX_free(ctx);\n    free(data);\n    return false;\n  }\n\n  if (decrypt_len < 0) {\n    EVP_CIPHER_CTX_free(ctx);\n    free(data);\n    return false;\n  }\n\n  fast_ptr += decrypt_len;\n  int tmplen = 0;\n\n  if (!EVP_DecryptFinal(ctx, fast_ptr, &tmplen)) {\n    if (!noerror) {\n      std::cerr << __FUNCTION__ << \"errno=\" <<  EINVAL\n                << \" msg=\\\"Unable to finalize cipher block\\\"\" << std::endl;\n    }\n\n    EVP_CIPHER_CTX_free(ctx);\n    free(data);\n    return false;\n  }\n\n  data_length = decrypt_len + tmplen;\n\n  if (data_length > buff_capacity) {\n    EVP_CIPHER_CTX_free(ctx);\n    free(data);\n    return false;\n  }\n\n  // Null terminate the decrypted buffer\n  data[data_length] = 0;\n  EVP_CIPHER_CTX_free(ctx);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Encrypt string and base64 encode it\n//------------------------------------------------------------------------------\nbool\nSymKey::SymmetricStringEncrypt(XrdOucString& in, XrdOucString& out, char* key)\n{\n  char* tmpbuf = 0;\n  ssize_t tmpbuflen = 0;\n\n  if (!CipherEncrypt(in.c_str(), in.length(), tmpbuf, tmpbuflen, key)) {\n    return false;\n  }\n\n  std::string b64out;\n\n  if (!Base64Encode(tmpbuf, tmpbuflen, b64out)) {\n    free(tmpbuf);\n    return false;\n  }\n\n  out = b64out.c_str();\n  free(tmpbuf);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Decrypt base64 encoded string\n//------------------------------------------------------------------------------\nbool\nSymKey::SymmetricStringDecrypt(XrdOucString& in, XrdOucString& out, char* key)\n{\n  char* tmpbuf = 0;\n  ssize_t tmpbuflen;\n\n  if (!Base64Decode((char*)in.c_str(), tmpbuf, tmpbuflen)) {\n    free(tmpbuf);\n    return false;\n  }\n\n  char* data;\n  ssize_t data_len;\n\n  if (!CipherDecrypt(tmpbuf, tmpbuflen, data, data_len, key, true)) {\n    free(tmpbuf);\n    return false;\n  }\n\n  out = data;\n  free(tmpbuf);\n  free(data);\n  return true;\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/SymKeys.hh",
    "content": "//------------------------------------------------------------------------------\n// File: SymKeys.hh\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @file SymKeys.hh\n//! @author Andreas-Joachim Peters\n//! @brief Classs implementing a symmetric key store and CODEC facility\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include <XrdOuc/XrdOucHash.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdSys/XrdSysPthread.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n#include \"google/protobuf/message.h\"\n#include <openssl/sha.h>\n#include <time.h>\n#include <string.h>\n#include <memory>\n#include <mutex>\n#include <uuid/uuid.h>\n#define EOSCOMMONSYMKEYS_GRACEPERIOD 5\n#define EOSCOMMONSYMKEYS_DELETIONOFFSET 60\n\nEOSCOMMONNAMESPACE_BEGIN\n\nclass UriCapCipher;\nstruct UriCapCipherDeleter {\n  void operator()(UriCapCipher* ptr) const;\n};\n\n///-----------------------------------------------------------------------------\n//! Class wrapping a symmetric key object and its encoding/decoding methods\n///-----------------------------------------------------------------------------\nclass SymKey\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Cipher encrypt using provided key\n  //!\n  //! @param data data to be encrypted\n  //! @param data_length length of the data\n  //! @param encrypted_data output encrypted data. It's not necessarily null\n  //!        terminated and could contain embedded nulls.\n  //! @param encrypted_length output data length\n  //! @param key cipher key whose length must be SHA_DIGEST_LENGTH (20)\n  //!\n  //! @return true if encryption successful, otherwise false\n  //----------------------------------------------------------------------------\n  static bool CipherEncrypt(const char* data, ssize_t data_length,\n                            char*& encrypted_data, ssize_t& encrypted_length,\n                            char* key);\n\n  //----------------------------------------------------------------------------\n  //! Cipher decrypt using provided key\n  //!\n  //! @param encrypted_data input encrypted data\n  //! @param encrypted_length input data length\n  //! @param data decrypted data pointer for which the caller takes ownership\n  //! @param data_length length of the decrypted data\n  //! @param key cipher key whose length must be SHA_DIGEST_LENGTH (20)\n  //! @param noerror flag - if true disable error printing in the function\n  //!\n  //! @return true if decryption successful, otherwise false\n  //----------------------------------------------------------------------------\n  static bool CipherDecrypt(char* encrypted_data, ssize_t encrypted_length,\n                            char*& data, ssize_t& data_length, char* key, bool noerror = false);\n\n  //----------------------------------------------------------------------------\n  //! Encrypt string and base64 encode it\n  //!\n  //! @param in input string\n  //! @param out output string\n  //! @param key symmetric key used for encryption\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  static bool SymmetricStringEncrypt(XrdOucString& in, XrdOucString& out,\n                                     char* key);\n\n  //----------------------------------------------------------------------------\n  //! Decrypt base64 encoded string\n  //!\n  //! @param in base64 encoded encrypted string\n  //! @param out decoded and decrypted string\n  //! @param key symmetric key used for decryption\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  static bool SymmetricStringDecrypt(XrdOucString& in, XrdOucString& out,\n                                     char* key);\n\n  //----------------------------------------------------------------------------\n  //! Create EOS specific capability and append to the output env object\n  //!\n  //! @param inenv input env object\n  //! @param outenv output env object\n  //! @param key key object used for encrypting the capability\n  //! @param validity duration for which the capability is valid\n  //!\n  //! @return 0 if successful, otherwise errno\n  //----------------------------------------------------------------------------\n  static int CreateCapability(XrdOucEnv* inenv, XrdOucEnv*& outenv,\n                              SymKey* key, std::chrono::seconds validity);\n\n  //----------------------------------------------------------------------------\n  //! Extract EOS specific capability encoded in the env object\n  //!\n  //! @param inenv input env object\n  //! @param outenv output env object\n  //!\n  //! @return 0 if successful, otherwise errno\n  //----------------------------------------------------------------------------\n  static int ExtractCapability(XrdOucEnv* inenv, XrdOucEnv*& outenv);\n\n  //----------------------------------------------------------------------------\n  //! Compute the HMAC SHA-1 value of the data passed as input\n  //!\n  //! @param data the message to be used as input\n  //! @param key the key to be used in the encryption process\n  //!\n  //! @return hash-based message authentication code\n  //----------------------------------------------------------------------------\n  static std::string HmacSha1(std::string& data, const char* key = NULL);\n\n  //----------------------------------------------------------------------------\n  //! Compute the HMAC SHA-256 value of the data passed as input\n  //!\n  //! @param key the key to be used in the encryption process\n  //! @param data the message to be used as input\n  //! @param blockSize the size in which the input is divided before the\n  //!                  cryptographic function is applied ( 512 bits recommended )\n  //! @param resultSize the size of the result ( the size recommended by the\n  //!                  OpenSSL library is 256 bits = 32 bytes )\n  //!\n  //! @return hash-based message authentication code\n  //----------------------------------------------------------------------------\n  static std::string HmacSha256(const std::string& key,\n                                const std::string& data,\n                                unsigned int blockSize = 64,\n                                unsigned int resultSize = 32);\n\n  //----------------------------------------------------------------------------\n  //! Compute hex digest of the SHA-256 value of the data passed as input\n  //!\n  //! @param data input data\n  //! @param blockSize the size in which the input is divided before the\n  //!                  hash function is applied ( 512 bits recommended )\n  //!\n  //! @return hex digest fo SHA-256\n  //----------------------------------------------------------------------------\n  static std::string HexSha256(const std::string& data,\n                               unsigned int blockSize = 32);\n\n  //----------------------------------------------------------------------------\n  //! Compute binary SHA-1 value of the data passed as input\n  //!\n  //! @param data input data\n  //!\n  //! @return binary sha1\n  //----------------------------------------------------------------------------\n  static std::string BinarySha1(const std::string& data);\n\n  //----------------------------------------------------------------------------\n  //! Compute hex digest of SHA-1 value of the data passed as input\n  //!\n  //! @param data input data\n  //!\n  //! @return sha1 hex digest\n  //----------------------------------------------------------------------------\n  static std::string HexSha1(const std::string& data);\n\n  //----------------------------------------------------------------------------\n  //! Base64 encode a string - base function\n  //!\n  //! @param decoded_bytes input data\n  //! @param decoded_length input data length\n  //! @param out encoded data in std::string\n  //!\n  //! @return true if succesful, otherwise false\n  //----------------------------------------------------------------------------\n  static bool Base64Encode(const char* decoded_bytes, ssize_t decoded_length,\n                           std::string& out);\n\n  //----------------------------------------------------------------------------\n  //! Base64 encode a string - returning an XrdOucString object\n  //!\n  //! @param in input data\n  //! @param inline input data length\n  //! @param out encoded data\n  //!\n  //! @return true if succesful, otherwise false\n  //----------------------------------------------------------------------------\n  static bool Base64Encode(const char* in, unsigned int inlen, XrdOucString& out);\n\n  //----------------------------------------------------------------------------\n  //! Base64 decode data, output as char* and length\n  //!\n  //! @param in input data\n  //! @param out decoded data\n  //! @param outlen decoded data length\n  //----------------------------------------------------------------------------\n  static bool Base64Decode(const char* encoded_bytes, char*& decoded_bytes,\n                           ssize_t& decoded_length);\n\n  //----------------------------------------------------------------------------\n  //! Base64 decode data, output as string\n  //!\n  //! @param in input data\n  //! @param out decoded data given as std::string\n  //----------------------------------------------------------------------------\n  static bool Base64Decode(const char* in, std::string& out);\n\n  //----------------------------------------------------------------------------\n  //! Base64 decode data stored in XrdOucString\n  //!\n  //! @param in input data\n  //! @param out decoded data\n  //! @param outlen decoded data length\n  //----------------------------------------------------------------------------\n  static bool Base64Decode(XrdOucString& in, char*& out, ssize_t& outlen);\n\n  //----------------------------------------------------------------------------\n  //! Decode a base64: prefixed string\n  //----------------------------------------------------------------------------\n  static bool DeBase64(XrdOucString& in, XrdOucString& out);\n\n  static bool DeBase64(const std::string& in, std::string& out);\n\n  //----------------------------------------------------------------------------\n  //! Encode a base64: prefixed string\n  //----------------------------------------------------------------------------\n  static bool Base64(XrdOucString& in, XrdOucString& out);\n\n  static bool Base64(std::string& in, std::string& out);\n\n  //----------------------------------------------------------------------------\n  //! Decode a zbase64: prefixed string\n  //----------------------------------------------------------------------------\n  static bool ZDeBase64(std::string& in, std::string& out);\n\n  //----------------------------------------------------------------------------\n  //! Encode a zbase64: prefixed string\n  //----------------------------------------------------------------------------\n  static bool ZBase64(std::string& in, std::string& out);\n\n  struct hmac_t {\n    void set(const std::string& secret, const std::string& key)\n    {\n      if (secret.length()) {\n        this->hmac = HmacSha256(secret, key);\n        this->key = hmac;\n      } else {\n        this->key = key;\n      }\n    }\n\n    hmac_t(const std::string& secret, const std::string& key)\n    {\n      set(secret, key);\n    }\n    hmac_t() {}\n\n    std::string key;\n    std::string hmac;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Obfuscate a buffer based on offset and hmac\n  //----------------------------------------------------------------------------\n  static void ObfuscateBuffer(char* dst, const char* src, size_t size,\n                              off_t offset, hmac_t& hmac);\n\n  //----------------------------------------------------------------------------\n  //! Unbfuscate a buffer based on offset and hmac\n  //----------------------------------------------------------------------------\n  static void UnobfuscateBuffer(char* buf, size_t size, off_t offset,\n                                hmac_t& hmac);\n\n  //----------------------------------------------------------------------------\n  //! Retrieve a random cipher fitting input key <key>\n  //----------------------------------------------------------------------------\n  static std::string RandomCipher(const std::string& key)\n  {\n    size_t keyblocks = 1;\n\n    if (key.length() > 36) {\n      keyblocks = key.length() / 36 + 1;\n    }\n\n    std::string skey;\n\n    for (size_t i = 0; i < keyblocks; i++) {\n      // create a random uuid\n      char suuid[40];\n      uuid_t uuid;\n      uuid_generate_random(uuid);\n      uuid_unparse(uuid, suuid);\n\n      for (size_t n = 0; n < strlen(suuid); n++) {\n        if (suuid[n] != '-') {\n          skey += suuid[n];\n        }\n      }\n    }\n\n    return skey;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Serialise a Google Protobuf object and base64 encode the result\n  //!\n  //! @param msg generic Google Protobuf object\n  //! @param output protobuf serialised and base64 encoded\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  static bool\n  ProtobufBase64Encode(const google::protobuf::Message* msg,\n                       std::string& output);\n\n  //----------------------------------------------------------------------------\n  //!\n  //! Constructor for a symmetric key\n  //!\n  //! @param inkey binary key of SHA_DIGEST_LENGTH\n  //! @param invalidity unix time stamp when the key becomes invalid\n  //----------------------------------------------------------------------------\n  SymKey(const char* inkey, time_t invalidity);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~SymKey() = default;\n\n  //----------------------------------------------------------------------------\n  //! Output a key and it's digest to stderr\n  //----------------------------------------------------------------------------\n  void\n  Print()\n  {\n    fprintf(stderr, \"symkey: \");\n\n    for (int i = 0; i < SHA_DIGEST_LENGTH; i++) {\n      fprintf(stderr, \"%x \", (unsigned char) key[i]);\n    }\n\n    fprintf(stderr, \"digest: %s\", keydigest64);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the binary key\n  //----------------------------------------------------------------------------\n  inline const char* GetKey()\n  {\n    return key;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the base64 encoded key\n  //----------------------------------------------------------------------------\n  inline const char* GetKey64()\n  {\n    return key64.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the binary key digest\n  //----------------------------------------------------------------------------\n  inline const char* GetDigest()\n  {\n    return keydigest;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the base64 encoded digest\n  //----------------------------------------------------------------------------\n  inline const char* GetDigest64()\n  {\n    return keydigest64;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return a cached UriCapCipher instance for this key (thread-safe)\n  //----------------------------------------------------------------------------\n  UriCapCipher& GetUriCapCipher();\n\n  //----------------------------------------------------------------------------\n  //! Return the expiration timestamp of the key\n  //----------------------------------------------------------------------------\n  inline time_t GetValidity()\n  {\n    return mValidity;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if the key is still valid\n  //----------------------------------------------------------------------------\n  bool\n  IsValid()\n  {\n    if (!mValidity) {\n      return true;\n    } else {\n      return ((time(0) + EOSCOMMONSYMKEYS_GRACEPERIOD) > mValidity);\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Factory function to create a SymKey Object\n  //----------------------------------------------------------------------------\n  static SymKey*\n  Create(const char* inkey, time_t validity)\n  {\n    return new SymKey(inkey, validity);\n  }\n\nprivate:\n  static XrdSysMutex msMutex; ///< mutex for protecting the access to OpenSSL\n  char key[SHA_DIGEST_LENGTH + 1]; //< the symmetric key in binary format\n  //! the digest of the key in binary format\n  char keydigest[SHA_DIGEST_LENGTH + 1];\n  //! the digest of the key in base64 format\n  char keydigest64[SHA_DIGEST_LENGTH * 2];\n  XrdOucString key64; //< the key in base64 format\n  time_t mValidity; //< unix time when the validity of the key stops\n  std::mutex mUriCapMutex;\n  std::unique_ptr<UriCapCipher, UriCapCipherDeleter> mUriCapCipher;\n};\n\n//------------------------------------------------------------------------------\n//! Class providing a keystore for symmetric keys\n//------------------------------------------------------------------------------\nclass SymKeyStore\n{\nprivate:\n  std::mutex mMutex;\n  XrdOucHash<SymKey> Store;\n  SymKey* currentKey;\npublic:\n  //-----------------------------------------------------------------------------\n  //! Constructor\n  //-----------------------------------------------------------------------------\n  SymKeyStore(): currentKey(nullptr) {}\n\n  //-----------------------------------------------------------------------------\n  //! Destructor\n  //-----------------------------------------------------------------------------\n  ~SymKeyStore()\n  {\n    std::unique_lock<std::mutex> scope_lock(mMutex);\n    Store.Purge();\n  }\n\n  //-----------------------------------------------------------------------------\n  //! Set a binary key and it's validity\n  //-----------------------------------------------------------------------------\n  SymKey* SetKey(const char* key, time_t validity);\n\n  //-----------------------------------------------------------------------------\n  //! Set a base64 key and it's validity\n  //-----------------------------------------------------------------------------\n  SymKey* SetKey64(const char* key64, time_t validity);\n\n  //-----------------------------------------------------------------------------\n  //! Get a base64 encoded key by digest from the store\n  //-----------------------------------------------------------------------------\n  SymKey* GetKey(const char* keydigest64);\n\n  //-----------------------------------------------------------------------------\n  //! Get last added valid key from the store\n  //-----------------------------------------------------------------------------\n  SymKey* GetCurrentKey();\n};\n\nextern SymKeyStore gSymKeyStore; //< Global SymKey store singleton\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/SyncAll.hh",
    "content": "//------------------------------------------------------------------------------\n// File: SyncAll.hh\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSCOMMON__CLOEXEC__HH\n#define __EOSCOMMON__CLOEXEC__HH\n\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdOuc/XrdOucTrace.hh>\n#include \"common/Namespace.hh\"\n#include <fcntl.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <unistd.h>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Static class to sync(+close) all file descriptors\n//!\n//! Example\n//! eos::common::SyncAll::All();\n//------------------------------------------------------------------------------\nclass SyncAll\n{\npublic:\n  static void All()\n  {\n    for (size_t i = getdtablesize(); i-- > 3;) {\n      fsync(i);\n    }\n  }\n\n  static void AllandClose()\n  {\n    for (size_t i = getdtablesize(); i-- > 3;) {\n      fsync(i);\n      close(i);\n    }\n  }\n\n  static void AllandCloseFileSocks()\n  {\n    for (size_t i = getdtablesize(); i-- > 3;) {\n      int v;\n      socklen_t vs = sizeof(v);\n      if(!fsync(i) || !getsockopt(i, SOL_SOCKET, SO_TYPE, &v, &vs))\n        close(i);\n    }\n  }\n};\n\nEOSCOMMONNAMESPACE_END\n#endif\n"
  },
  {
    "path": "common/SystemClock.hh",
    "content": "// ----------------------------------------------------------------------\n// File: SystemClock.hh\n// Author: Elvin Sindrilaru - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include <chrono>\n#include <mutex>\n#include \"common/Namespace.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! A clock which behaves similarly to std::chrono::system_clock, but can be\n//! faked. During faking, you can advance time manually.\n//------------------------------------------------------------------------------\nclass SystemClock\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor: Specify whether we're faking time, or not.\n  //----------------------------------------------------------------------------\n  SystemClock(bool fake_) : mFake(fake_) {}\n\n  //----------------------------------------------------------------------------\n  //! Default constructor - Sets fake to false\n  //----------------------------------------------------------------------------\n  SystemClock() : mFake(false) {}\n\n  //----------------------------------------------------------------------------\n  //! Static now function - it's also possible to pass a nullptr\n  //----------------------------------------------------------------------------\n  static std::chrono::system_clock::time_point now(SystemClock* clock)\n  {\n    if (clock == nullptr) {\n      return std::chrono::system_clock::now();\n    }\n\n    return clock->GetTime();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get current time.\n  //----------------------------------------------------------------------------\n  std::chrono::system_clock::time_point GetTime() const\n  {\n    if (mFake) {\n      std::lock_guard<std::mutex> lock(mtx);\n      return fakeTimepoint;\n    }\n\n    return std::chrono::system_clock::now();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Advance current time - only call if you're faking the clock, otherwise\n  //! has no effect...\n  //----------------------------------------------------------------------------\n  template<typename T>\n  void advance(T duration)\n  {\n    std::lock_guard<std::mutex> lock(mtx);\n    fakeTimepoint += duration;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Utility function to convert a time_point to seconds since epoch\n  //----------------------------------------------------------------------------\n  static std::chrono::seconds SecondsSinceEpoch(\n    std::chrono::system_clock::time_point point)\n  {\n    return std::chrono::duration_cast<std::chrono::seconds>(\n             point.time_since_epoch());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if this is a \"fake\" clock\n  //----------------------------------------------------------------------------\n  inline bool IsFake() const\n  {\n    return mFake;\n  }\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  bool mFake;\n  mutable std::mutex mtx;\n  std::chrono::system_clock::time_point fakeTimepoint;\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/ThreadPool.hh",
    "content": "//------------------------------------------------------------------------------\n// File: common/ThreadPool.cc\n// Author: Jozsef Makai - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include \"common/ConcurrentQueue.hh\"\n#include <future>\n#include <sstream>\n#include <iomanip>\n\n#ifdef __APPLE__\n#include <cmath>\n#endif\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------------\n//! @brief Dynamically scaling pool of threads which will asynchronously execute tasks\n//------------------------------------------------------------------------------------\nclass ThreadPool\n{\npublic:\n  //----------------------------------------------------------------------------------\n  //! @brief Create a new thread pool\n  //!\n  //! @param threadsMin the minimum and starting number of allocated threads,\n  //!        defaults to hardware concurrency\n  //! @param threadsMax the maximum number of allocated threads,\n  //!        defaults to hardware concurrency\n  //! @param samplingInterval sampling interval in seconds for the waiting jobs,\n  //!        required for dynamic scaling, defaults to 10 seconds\n  //! @param samplingNumber number of samples to collect before making a scaling\n  //!        decision, scaling decision will be made after samplingInterval *\n  //!        samplingNumber seconds\n  //! @param averageWaitingJobsPerNewThread the average number of waiting jobs per which\n  //!        one new thread should be started, defaults to 10,\n  //!        e.g. if in average 27.8 jobs were waiting for execution, then 2 new\n  //!        threads will be added to the pool\n  //! @param name identifier for the thread pool\n  //----------------------------------------------------------------------------------\n  explicit ThreadPool(unsigned int threadsMin =\n                        std::thread::hardware_concurrency(),\n                      unsigned int threadsMax = std::thread::hardware_concurrency(),\n                      unsigned int samplingInterval = 10,\n                      unsigned int samplingNumber = 12,\n                      unsigned int averageWaitingJobsPerNewThread = 10,\n                      const std::string& identifier = \"defaulttp\"):\n    mThreadsMin(threadsMin),\n    mThreadsMax(threadsMin > threadsMax ? threadsMin : threadsMax),\n    mPoolSize(0ul), mId(identifier)\n  {\n    auto threadPoolFunc = [this] {\n      bool toContinue = true;\n\n      do\n      {\n        std::pair<bool, std::shared_ptr<std::function<void(void)>>> task;\n        mTasks.wait_pop(task);\n        toContinue = task.first;\n\n        // Termination is signalled by false\n        if (toContinue) {\n          (*(task.second))();\n        }\n      } while (toContinue);\n    };\n\n    for (auto i = 0u; i < std::max(mThreadsMin.load(), 1u); ++i) {\n      try {\n        mThreadPool.emplace_back(std::async(std::launch::async, threadPoolFunc));\n      } catch (const std::exception& e) {\n        std::cerr << \"error: std::async couldn't start a new thread \"\n                  << \"and threw an exception: \" << e.what() << std::endl;\n        continue;\n      }\n\n      ++mThreadCount;\n    }\n\n    mPoolSize = mThreadPool.size();\n\n    if (mThreadsMax > mThreadsMin) {\n      auto maintainerThreadFunc = [this, threadPoolFunc, samplingInterval,\n            samplingNumber, averageWaitingJobsPerNewThread] {\n        setSelfThreadName(mId);\n        auto rounds = 0u, sumQueueSize = 0u;\n        auto signalFuture = mMaintainerSignal.get_future();\n\n        while (true)\n        {\n          if (signalFuture.valid()) {\n            if (signalFuture.wait_for(std::chrono::seconds(samplingInterval)) ==\n                std::future_status::ready) {\n              break;\n            }\n          } else {\n            break;\n          }\n\n          // Check first if we have finished, removable threads/futures and remove them\n          mThreadPool.erase(\n            std::remove_if(mThreadPool.begin(), mThreadPool.end(),\n          [](std::future<void>& future) {\n            return (future.wait_for(std::chrono::seconds(0)) ==\n                    std::future_status::ready);\n          }),\n          mThreadPool.end());\n          sumQueueSize += mTasks.size();\n\n          if (++rounds == samplingNumber) {\n            auto averageQueueSize = (double) sumQueueSize / rounds;\n\n            if ((averageQueueSize > mThreadCount) && (mThreadCount <= mThreadsMax)) {\n              auto threadsToAdd =\n                std::min((unsigned int) floor(averageQueueSize /\n                                              averageWaitingJobsPerNewThread),\n                         mThreadsMax - mThreadCount);\n\n              while (threadsToAdd > 0) {\n                try {\n                  mThreadPool.emplace_back(std::async(std::launch::async,\n                                                      threadPoolFunc));\n                } catch (const std::exception& e) {\n                  std::cerr << \"error: std::async couldn't start a new thread \"\n                            << \"and threw an exception: \" << e.what() << std::endl;\n                  continue;\n                }\n\n                ++mThreadCount;\n                --threadsToAdd;\n              }\n            } else {\n              unsigned int threadsToRemove = 0ull;\n\n              if (mThreadCount > mThreadsMax) {\n                threadsToRemove = mThreadCount - mThreadsMax;\n              } else {\n                threadsToRemove = mThreadCount -\n                                  std::max((unsigned int) floor(averageQueueSize), mThreadsMin.load());\n              }\n\n              // Push in fake tasks for each thread to be stopped so threads can wake up and\n              // notice that they should terminate. Termination is signalled with false.\n              for (auto i = 0u; i < threadsToRemove; ++i) {\n                auto fake_task = std::make_pair(false, std::make_shared\n                                                <std::function<void(void)>> ([] {}));\n                mTasks.push(fake_task);\n              }\n\n              mThreadCount -= threadsToRemove;\n            }\n\n            sumQueueSize = 0u;\n            rounds = 0u;\n          }\n\n          mPoolSize = mThreadPool.size();\n        }\n      };\n      mMaintainerThread.reset(new std::thread(maintainerThreadFunc));\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! @brief Push a task for execution, the task can have a return type but\n  //! inputs should be either captured in case of lambdas or bound using\n  //! std::bind in case of regular functions.\n  //!\n  //! @param Ret return type of the task\n  //! @param func the function for the task to execute\n  //!\n  //! @return future of the return type to communicate with your task\n  //----------------------------------------------------------------------------\n  template<typename Ret>\n  std::future<Ret> PushTask(std::function<Ret(void)> func)\n  {\n    auto task = std::make_shared<std::packaged_task<Ret(void)>>(func);\n    auto taskFunc =\n      std::make_pair(true,\n    std::make_shared<std::function<void(void)>>([task] {\n      (*task)();\n    }));\n    mTasks.push(taskFunc);\n    return task->get_future();\n  }\n\n  template <typename Ret>\n  std::future<Ret> PushTask(std::shared_ptr<std::packaged_task<Ret(void)>>&& task)\n  {\n    auto taskFunc =\n      std::make_pair(true,\n    std::make_shared<std::function<void(void)>>([task] {\n      (*task)();\n    }));\n    mTasks.push(taskFunc);\n    return task->get_future();\n  }\n  //----------------------------------------------------------------------------\n  //! @brief Stop the thread pool. All threads will be stopped and the pool\n  //! cannot be used again.\n  //----------------------------------------------------------------------------\n  void Stop()\n  {\n    if (mMaintainerThread && mMaintainerThread->joinable()) {\n      mMaintainerSignal.set_value();\n      mMaintainerThread->join();\n    }\n\n    // Push in fake tasks for each threads so all waiting can wake up and\n    // notice that running is over. Termination is signalled with false.\n    for (auto i = 0u; i < mThreadPool.size(); ++i) {\n      auto fake_task = std::make_pair(false, std::make_shared\n                                      <std::function<void(void)>> ([] {}));\n      mTasks.push(fake_task);\n    }\n\n    for (auto& future : mThreadPool) {\n      if (future.valid()) {\n        future.get();\n      }\n    }\n\n    mTasks.clear();\n    mThreadPool.clear();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~ThreadPool()\n  {\n    Stop();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get thread pool information\n  //----------------------------------------------------------------------------\n  std::string GetInfo() const\n  {\n    std::ostringstream oss;\n    oss <<  \"pool=\" << std::setw(14) << std::left << mId\n        << \" min=\" << std::setw(3) << std::left << mThreadsMin\n        << \" max=\" << std::setw(4) << std::left << mThreadsMax\n        << \" size=\" << std::setw(4) << std::left << mPoolSize\n        << \" queue_sz=\" << mTasks.size();\n    return oss.str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set min number of threads. If the new minimum is greater than the current\n  //! max value then this one is also updated.\n  //!\n  //! @param num new min number of threads\n  //----------------------------------------------------------------------------\n  void SetMinThreads(unsigned int num)\n  {\n    mThreadsMin = num;\n\n    if (mThreadsMax < num) {\n      mThreadsMax = num;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set max number of threads. If the new maximum is smaller than the current\n  //! min value then this one is also updated.\n  //!\n  //! @param num new max number of threads > 0\n  //----------------------------------------------------------------------------\n  void SetMaxThreads(unsigned int num)\n  {\n    if (num == 0) {\n      return;\n    }\n\n    mThreadsMax = num;\n\n    if (mThreadsMin > num) {\n      mThreadsMin = num;\n    }\n  }\n\n  //---------------------------------------------------------------------------\n  //! Get maximum number of threads in the pool\n  //---------------------------------------------------------------------------\n  unsigned int GetMaxThreads() const\n  {\n    return mThreadsMax;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get size of thread pool\n  //----------------------------------------------------------------------------\n  unsigned int GetSize()\n  {\n    return mPoolSize;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get size of the queue of jobs\n  //----------------------------------------------------------------------------\n  size_t GetQueueSize() const\n  {\n    return mTasks.size();\n  }\n\n  static void setSelfThreadName(const std::string& name)\n  {\n#ifdef __LINUX__\n    pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());\n#endif\n  }\n\n  // Disable copy/move constructors and assignment operators\n  ThreadPool(const ThreadPool&) = delete;\n  ThreadPool(ThreadPool&&) = delete;\n  ThreadPool& operator=(const ThreadPool&) = delete;\n  ThreadPool& operator=(ThreadPool&&) = delete;\n\nprivate:\n  std::vector<std::future<void>> mThreadPool;\n  eos::common::ConcurrentQueue<std::pair<bool, std::shared_ptr<std::function<void(void)>>>>\n  mTasks;\n  std::unique_ptr<std::thread> mMaintainerThread;\n  std::promise<void> mMaintainerSignal;\n  std::atomic_uint mThreadCount {0};\n  std::atomic_uint mThreadsMin, mThreadsMax, mPoolSize;\n  std::string mId; ///< Thread pool identifier\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Timing.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Timing.hh\n//! @author Andreas-Joachim Peters - CERN\n//! @brief Class providing real-time code measurements\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSCOMMON__TIMING__HH\n#define __EOSCOMMON__TIMING__HH\n\n#include <XrdOuc/XrdOucString.hh>\n#include \"common/Namespace.hh\"\n#include \"common/ClockGetTime.hh\"\n#include <sys/time.h>\n#include <time.h>\n#include <stdio.h>\n#include <string.h>\n#include <string>\n#include <chrono>\n#include <sstream>\n#include <iomanip>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Convert a system_clock timepoint into milliseconds since epoch\n//------------------------------------------------------------------------------\ninline std::chrono::milliseconds timepointToMillisecondsSinceEpoch(\n  std::chrono::system_clock::time_point tp)\n{\n  return std::chrono::duration_cast<std::chrono::milliseconds>(\n           tp.time_since_epoch()\n         );\n}\n\n//------------------------------------------------------------------------------\n//! Get system time in milliseconds since epoch\n//------------------------------------------------------------------------------\ninline std::chrono::milliseconds getEpochInMilliseconds()\n{\n  return timepointToMillisecondsSinceEpoch(std::chrono::system_clock::now());\n}\n\n/*----------------------------------------------------------------------------*/\n//! Class implementing comfortable time measurements through methods/functions\n//!\n//! Example\n//! eos::common::Timing tm(\"Test\");\n//! COMMONTIMING(\"START\",&tm);\n//! ...\n//! COMMONTIMING(\"CHECKPOINT1\",&tm);\n//! ...\n//! COMMONTIMING(\"CHECKPOINT2\",&tm);\n//! ...\n//! COMMONTIMING(\"STOP\", &tm);\n//! tm.Print();\n//! fprintf(stdout,\"realtime = %.02f\", tm.RealTime());\n/*----------------------------------------------------------------------------*/\nclass Timing\n{\npublic:\n  struct timeval tv;\n  XrdOucString tag;\n  XrdOucString maintag;\n  Timing* next;\n  Timing* ptr;\n\n  //----------------------------------------------------------------------------\n  //! Constructor - used only internally\n  //----------------------------------------------------------------------------\n  Timing(const char* name, struct timeval& i_tv):\n    tv{}, tag(name), next(0)\n  {\n    memcpy(&tv, &i_tv, sizeof(struct timeval));\n    ptr = this;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Constructor - tag is used as the name for the measurement in Print\n  //----------------------------------------------------------------------------\n  Timing(const char* i_maintag):\n    tv{}, tag(\"BEGIN\"), maintag(i_maintag), next(0)\n  {\n    ptr = this;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get time elapsed between the two tags in milliseconds\n  //----------------------------------------------------------------------------\n  float\n  GetTagTimelapse(const std::string& tagBegin, const std::string& tagEnd)\n  {\n    float time_elapsed = 0;\n    Timing* ptr = this->next;\n    Timing* ptrBegin = 0;\n    Timing* ptrEnd = 0;\n\n    while (ptr) {\n      if (tagBegin.compare(ptr->tag.c_str()) == 0) {\n        ptrBegin = ptr;\n      }\n\n      if (tagEnd.compare(ptr->tag.c_str()) == 0) {\n        ptrEnd = ptr;\n      }\n\n      if (ptrBegin && ptrEnd) {\n        break;\n      }\n\n      ptr = ptr->next;\n    }\n\n    if (ptrBegin && ptrEnd) {\n      time_elapsed = static_cast<float>(((ptrEnd->tv.tv_sec - ptrBegin->tv.tv_sec) *\n                                         1000000 +\n                                         (ptrEnd->tv.tv_usec - ptrBegin->tv.tv_usec)) / 1000.0);\n    }\n\n    return time_elapsed;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get current year as a string\n  //----------------------------------------------------------------------------\n  static std::string GetCurrentYear()\n  {\n    auto now = std::chrono::system_clock::now();\n    std::time_t now_t = std::chrono::system_clock::to_time_t(now);\n    tm nowtimeparts;\n    localtime_r(&now_t, &nowtimeparts);\n    return std::to_string(1900 + nowtimeparts.tm_year);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get current time in nanoseconds\n  //----------------------------------------------------------------------------\n  static long long\n  GetNowInNs()\n  {\n    struct timespec ts;\n    GetTimeSpec(ts);\n    return (1000000000 * ts.tv_sec + ts.tv_nsec);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get current time in seconds\n  //----------------------------------------------------------------------------\n  static long long\n  GetNowInSec()\n  {\n    struct timespec ts;\n    GetTimeSpec(ts);\n    return ts.tv_sec;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the age of a timespec\n  //----------------------------------------------------------------------------\n  static long long\n  GetAgeInNs(const struct timespec* ts, const struct timespec* now = NULL)\n  {\n    struct timespec tsn;\n\n    if (!now) {\n      GetTimeSpec(tsn);\n      now = &tsn;\n    }\n\n    return (now->tv_sec - ts->tv_sec) * 1000000000 + (now->tv_nsec - ts->tv_nsec);\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Return the coarse age of a timespec\n  // ---------------------------------------------------------------------------\n  static long long\n  GetCoarseAgeInNs(const struct timespec* ts, const struct timespec* now = NULL)\n  {\n    struct timespec tsn;\n\n    if (!now) {\n      GetTimeSpec(tsn, true);\n      now = &tsn;\n    }\n\n    return (now->tv_sec - ts->tv_sec) * 1000000000 + (now->tv_nsec - ts->tv_nsec);\n  }\n\n\n  // ---------------------------------------------------------------------------\n  //! Return the age of a ns timestamp\n  //----------------------------------------------------------------------------\n  static long long\n  GetAgeInNs(long long ts, const struct timespec* now = NULL)\n  {\n    struct timespec tsn;\n\n    if (!now) {\n      GetTimeSpec(tsn);\n      now = &tsn;\n    }\n\n    return (now->tv_sec * 1000000000 + now->tv_nsec) - ts;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the coarse age of a ns timestamp\n  //----------------------------------------------------------------------------\n  static long long\n  GetCoarseAgeInNs(long long ts, const struct timespec* now = NULL)\n  {\n    struct timespec tsn;\n\n    if (!now) {\n      GetTimeSpec(tsn, true);\n      now = &tsn;\n    }\n\n    return (now->tv_sec * 1000000000 + now->tv_nsec) - ts;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Print method to display measurements on STDERR\n  //----------------------------------------------------------------------------\n  void\n  Print()\n  {\n    std::cerr << std::endl << Dump() << std::endl;\n  }\n\n  std::string\n  Dump(bool pretty = false)\n  {\n    std::ostringstream ss;\n    char msg[512];\n    Timing* n;\n    Timing* p = next;\n    size_t cnt = 0;\n\n    if (p == nullptr) {\n      return \"\";\n    }\n\n    while ((n = p->next)) {\n      cnt++;\n\n      if (pretty) {\n        snprintf(msg, sizeof(msg), \" #%04lu : %s::%-20s %.03f ms\\n\", cnt,\n                 maintag.c_str(),\n                 n->tag.c_str(), (float)((n->tv.tv_sec - p->tv.tv_sec) * 1000000 +\n                                         (n->tv.tv_usec - p->tv.tv_usec)) / 1000.0);\n      } else {\n        snprintf(msg, sizeof(msg), \"%s=%.03fms \",\n                 n->tag.c_str(), (float)((n->tv.tv_sec - p->tv.tv_sec) * 1000000 +\n                                         (n->tv.tv_usec - p->tv.tv_usec)) / 1000.0);\n      }\n\n      ss << msg;\n      p = n;\n    }\n\n    n = p;\n    p = next;\n\n    if (pretty) {\n      snprintf(msg, sizeof(msg), \" #==== : %s::%-20s %.03f ms\\n\", maintag.c_str(),\n               \"total\",\n               (float)((n->tv.tv_sec - p->tv.tv_sec) * 1000000 + (n->tv.tv_usec -\n                       p->tv.tv_usec)) / 1000.0);\n    } else {\n      snprintf(msg, sizeof(msg), \"%s=%.03fms\", maintag.c_str(),\n               (float)((n->tv.tv_sec - p->tv.tv_sec) * 1000000 + (n->tv.tv_usec -\n                       p->tv.tv_usec)) / 1000.0);\n    }\n\n    ss << msg;\n    return ss.str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return total Realtime\n  //----------------------------------------------------------------------------\n  double\n  RealTime()\n  {\n    Timing* p = this->next;\n    Timing* n;\n\n    while (p && (n = p->next)) {\n      p = n;\n    }\n\n    n = p;\n    p = this->next;\n    return (double)((n->tv.tv_sec - p->tv.tv_sec) * 1000000 +\n                    (n->tv.tv_usec - p->tv.tv_usec)) / 1000.0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual\n  ~Timing()\n  {\n    Timing* n = next;\n\n    if (n) {\n      delete n;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Time Conversion Function for timestamp time strings\n  //----------------------------------------------------------------------------\n  static std::string\n  UnixTimestamp_to_Day(time_t when)\n  {\n    struct tm now;\n    localtime_r(&when, &now);\n    std::string year;\n    std::string month;\n    std::string day;\n    char sDay[4096];\n    snprintf(sDay, sizeof(sDay), \"%04u%02u%02u\",\n             (unsigned int)(now.tm_year + 1900),\n             (unsigned int)(now.tm_mon + 1),\n             (unsigned int)(now.tm_mday));\n    return sDay;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Time Conversion Function for strings to unix time\n  //----------------------------------------------------------------------------\n  static time_t\n  Day_to_UnixTimestamp(std::string day)\n  {\n    tzset();\n    struct tm ctime;\n    memset(&ctime, 0, sizeof(struct tm));\n    strptime(day.c_str(), \"%Y%m%d\", &ctime);\n    time_t ts = mktime(&ctime) - timezone;\n    return ts;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Wrapper Function to hide difference between Apple and Linux\n  //----------------------------------------------------------------------------\n  static void\n  GetTimeSpec(struct timespec& ts, bool coarse = false)\n  {\n#ifdef __APPLE__\n    struct timeval tv;\n    gettimeofday(&tv, 0);\n    ts.tv_sec = tv.tv_sec;\n    ts.tv_nsec = tv.tv_usec * 1000;\n#else\n\n    if (coarse) {\n#ifdef CLOCK_REALTIME_COARSE\n      _clock_gettime(CLOCK_REALTIME_COARSE, &ts);\n#else\n      _clock_gettime(CLOCK_REALTIME, &ts);\n#endif\n    } else {\n      _clock_gettime(CLOCK_REALTIME, &ts);\n    }\n\n#endif\n  }\n\n  //----------------------------------------------------------------------------\n  //! Extract timespec from a timespec string representation.\n  //! Failed extraction will set the timespec structure to 0.\n  //! Timespec string format: tv_sec.tv_nsec\n  //!\n  //! Returns 0 for successful conversion, -1 otherwise\n  //!\n  //! Note: the function resets the value of errno\n  //----------------------------------------------------------------------------\n  static int\n  Timespec_from_TimespecStr(const std::string& timespec_str, struct timespec& ts)\n  {\n    size_t pos = timespec_str.find(\".\");\n    struct timespec lts {\n      0, 0\n    };\n    ts.tv_sec = ts.tv_nsec = 0;\n\n    try {\n      // long tv_nsec nanoseconds (valid values are [0, 999999999])\n      lts.tv_sec = std::stoull(timespec_str.substr(0, pos));\n    } catch (...) {\n      return -1;\n    }\n\n    if (pos != std::string::npos) {\n      try {\n        lts.tv_nsec = std::stoull(timespec_str.substr(pos + 1, 9));\n      } catch (...) {\n        return -1;\n      }\n    }\n\n    ts = lts;\n    return 0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Convert timespec struct to timespec string representation. Take care\n  //! particularly for leading 0s.\n  //!\n  //! @param ts timespec structure\n  //!\n  //! @return timespec string representation \"<seconds>.<nanoseconds>\"\n  //----------------------------------------------------------------------------\n  static std::string\n  TimespecToString(const struct timespec& ts)\n  {\n    std::ostringstream oss;\n    oss << ts.tv_sec << \".\" << std::setfill('0') << std::setw(9) << ts.tv_nsec;\n    return oss.str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Extract nanoseconds from a timespec string representation.\n  //! Timespec string format: tv_sec.tv_nsec\n  //!\n  //! Returns time in nanoseconds if successful, -1 otherwise\n  //!\n  //! Note: the function resets the value of errno\n  //----------------------------------------------------------------------------\n  static long long\n  Ns_from_TimespecStr(std::string timespec_str)\n  {\n    struct timespec ts;\n    int rc = Timespec_from_TimespecStr(timespec_str, ts);\n    return (rc == 0) ? (ts.tv_sec * 1000000000 + ts.tv_nsec) : -1;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Time Conversion Function for ISO8601 time strings\n  //----------------------------------------------------------------------------\n  static std::string\n  UnixTimestamp_to_ISO8601(time_t now)\n  {\n    struct tm* utctime;\n    char str[21];\n    struct tm utc;\n    utctime = gmtime_r(&now, &utc);\n\n    if (!utctime) {\n      now = 0;\n      utctime = gmtime_r(&now, &utc);\n    }\n\n    strftime(str, 21, \"%Y-%m-%dT%H:%M:%SZ\", utctime);\n    return str;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Time Conversion Function for strings to ISO8601 time\n  //----------------------------------------------------------------------------\n  static time_t\n  ISO8601_to_UnixTimestamp(std::string iso)\n  {\n    tzset();\n    char temp[64];\n    memset(temp, 0, sizeof(temp));\n    strncpy(temp, iso.c_str(), (iso.length() < 64) ? iso.length() : 64);\n    struct tm ctime;\n    memset(&ctime, 0, sizeof(struct tm));\n    strptime(temp, \"%FT%T%z\", &ctime);\n    time_t ts = mktime(&ctime) - timezone;\n    return ts;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Covert time to UTC (Coordinated Universal Time)\n  //----------------------------------------------------------------------------\n  static\n  std::string utctime(time_t ttime)\n  {\n    struct tm utc;\n\n    if (!gmtime_r(&ttime, &utc)) {\n      time_t zt = 0;\n      gmtime_r(&zt, &utc);\n    }\n\n    static const char wday_name[][4] = {\n      \"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"\n    };\n    static const char mon_name[][4] = {\n      \"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n      \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"\n    };\n    char result[40];\n    sprintf(result, \"%.3s, %02d %.3s %d %.2d:%.2d:%.2d GMT\",\n            wday_name[utc.tm_wday], utc.tm_mday, mon_name[utc.tm_mon],\n            1900 + utc.tm_year, utc.tm_hour, utc.tm_min, utc.tm_sec);\n    return std::string(result);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Covert time local time\n  //----------------------------------------------------------------------------\n  static\n  std::string ltime(const time_t& t)\n  {\n    char a_time[32];\n    a_time[0] = 0;\n    struct tm timeinfo;\n    localtime_r(&t, &timeinfo);\n\n    if (asctime_r(&timeinfo, a_time) == nullptr) {\n      return \"N/A\";\n    }\n\n    if (strlen(a_time) > 2) {\n      a_time[strlen(a_time) - 1] = '\\0';\n    }\n\n    return a_time;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Covert time GMT time\n  //----------------------------------------------------------------------------\n  static\n  std::string gtime(time_t& t)\n  {\n    char a_time[32];\n    a_time[0] = 0;\n    struct tm timeinfo;\n    gmtime_r(&t, &timeinfo);\n\n    if (asctime_r(&timeinfo, a_time) == nullptr) {\n      return \"N/A\";\n    }\n\n    if (strlen(a_time) > 2) {\n      a_time[strlen(a_time) - 1] = '\\0';\n    }\n\n    return a_time;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Format time value for display when doing \"ls -l\"\n  //----------------------------------------------------------------------------\n  static\n  std::string ToLsFormat(struct tm* tm)\n  {\n    static char const* long_time_format[2] = {\n      /* strftime format for non-recent files (older than 6 months), in\n         -l output.  This should contain the year, month and day (at\n         least), in an order that is understood by people in your\n         locale's territory.  Please try to keep the number of used\n         screen columns small, because many people work in windows with\n         only 80 columns.  But make this as wide as the other string\n         below, for recent files.  */\n      /* TRANSLATORS: ls output needs to be aligned for ease of reading,\n         so be wary of using variable width fields from the locale.\n         Note %b is handled specially by ls and aligned correctly.\n         Note also that specifying a width as in %5b is erroneous as strftime\n         will count bytes rather than characters in multibyte locales.  */\n      \"%b %e  %Y\",\n      /* strftime format for recent files (younger than 6 months), in -l\n         output.  This should contain the month, day and time (at\n         least), in an order that is understood by people in your\n         locale's territory.  Please try to keep the number of used\n         screen columns small, because many people work in windows with\n         only 80 columns.  But make this as wide as the other string\n         above, for non-recent files.  */\n      /* TRANSLATORS: ls output needs to be aligned for ease of reading,\n         so be wary of using variable width fields from the locale.\n         Note %b is handled specially by ls and aligned correctly.\n         Note also that specifying a width as in %5b is erroneous as strftime\n         will count bytes rather than characters in multibyte locales.  */\n      \"%b %e %H:%M\"\n    };\n    time_t when_time = mktime(tm);\n    time_t current_time = time(0);\n    /* Consider a time to be recent if it is within the past six months.\n       A Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952 seconds\n       on the average.  Write this value as an integer constant to\n       avoid floating point hassles.  */\n    int recent = ((difftime(current_time, when_time) >= (31556952 / 2)) ? 0 : 1);\n    size_t max_len = 64;\n    std::string out(max_len, '\\0');\n    size_t len = strftime(const_cast<char*>(out.c_str()), max_len,\n                          long_time_format[recent], tm);\n    out.resize(len + 1);\n    return out;\n  }\n};\n\n//------------------------------------------------------------------------------\n//! Macro to place a measurement throughout the code\n//------------------------------------------------------------------------------\n#define COMMONTIMING( __ID__,__LIST__)    \\\n  do {                                    \\\n    struct timeval tp = {};               \\\n    struct timezone tz = {};              \\\n    gettimeofday(&tp, &tz);                                   \\\n    (__LIST__)->ptr->next=new eos::common::Timing(__ID__,tp); \\\n    (__LIST__)->ptr = (__LIST__)->ptr->next;                  \\\n  } while(false);\n\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/UnixGroupsFetcher.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/UnixGroupsFetcher.hh\"\n#include \"common/VirtualIdentity.hh\"\n#include \"common/Logging.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\nstd::vector<gid_t> UnixGrentFetcher::getGroups(const std::string &username, gid_t gid)\n{\n\n    std::vector<gid_t> groups;\n    std::unique_lock<std::mutex> lock(mtx);\n    setgrent();\n    group *gr {nullptr};\n\n    while ((gr = getgrent())) {\n        int cnt = 0;\n        if (gr->gr_gid == gid) {\n            groups.emplace_back(gr->gr_gid);\n        }\n\n        while (gr->gr_mem[cnt] != nullptr) {\n            if (!strcmp(gr->gr_mem[cnt], username.c_str())) {\n                groups.emplace_back(gr->gr_gid);\n            }\n        }\n    }\n\n    return groups;\n}\n\nstd::vector<gid_t> UnixGroupListFetcher::getGroups(const std::string &username, gid_t gid)\n{\n    std::vector<gid_t> groups(kDefaultMaxGroupSize);\n    int ngroups = kDefaultMaxGroupSize;\n#ifdef __LINUX__\n    if (getgrouplist(username.c_str(), gid, (gid_t*)groups.data(), &ngroups) == -1) {\n#else\n    if (getgrouplist(username.c_str(), gid, (int*)groups.data(), &ngroups) == -1) {\n#endif\n      groups.resize(ngroups);\n\n#ifdef __LINUX__\n      if (getgrouplist(username.c_str(), gid, (gid_t*)groups.data(), &ngroups) == -1) {\n#else\n      if (getgrouplist(username.c_str(), gid, (int*)groups.data(), &ngroups) == -1) {\n#endif\n        // This is very unlikely, we can do this in a tight loop to avoid this, but very\n        // much an overkill\n        eos_static_err(\n            \"msg=\\\"Groups resized while fetching groupinfo\\\" uid=%s ngroups=%d\",\n            username.c_str(), ngroups);\n        return groups; // do not resize again as we have lesser groups!\n      }\n    }\n    groups.resize(ngroups);\n    return groups;\n}\n\nvoid populateGroups(const std::string& username, gid_t gid,\n                    VirtualIdentity &vid,\n                    UnixGroupsFetcher * const fetcher)\n{\n    if (fetcher == nullptr) {\n        eos_static_crit(\"msg=\\\"Cannot populate groups information! Uninitialized Fetcher\\\"\");\n        return;\n    }\n\n    auto group_list = fetcher->getGroups(username,gid);\n    if (group_list.empty()) {\n        eos_static_err(\"msg=\\\"No groups found for user\\\" name=\\\"%s\\\" gid=%d\", username.c_str(), gid);\n        return;\n    }\n\n    vid.allowed_gids.insert(group_list.begin(), group_list.end());\n}\n\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/UnixGroupsFetcher.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ***********************************************************************/\n\n#ifndef EOS_UNIXGROUPSFETCHER_HH\n#define EOS_UNIXGROUPSFETCHER_HH\n\n#include \"common/Namespace.hh\"\n#include <grp.h>\n#include <string>\n#include <vector>\n#include <mutex>\n\nEOSCOMMONNAMESPACE_BEGIN\n\nstruct VirtualIdentity;\n// constants\nstatic constexpr int kDefaultMaxGroupSize = 16;\n\nstruct UnixGroupsFetcher\n{\n    virtual ~UnixGroupsFetcher() = default;\n    virtual std::vector<gid_t> getGroups(const std::string& username, gid_t gid) = 0;\n};\n\nclass UnixGrentFetcher: public UnixGroupsFetcher {\npublic:\n    std::vector<gid_t> getGroups(const std::string& username, gid_t gid) override;\nprivate:\n    std::mutex mtx;  //< mutex to protect the access to the getgrent() function\n};\n\nstruct UnixGroupListFetcher: public UnixGroupsFetcher {\n    std::vector<gid_t> getGroups(const std::string& username, gid_t gid) override;\n};\n\n\nvoid populateGroups(const std::string& username, gid_t gid,\n                    VirtualIdentity& vid, UnixGroupsFetcher * const  fetcher);\n\nEOSCOMMONNAMESPACE_END\n\n#endif //UNIXGROUPSFETCHER_HH\n"
  },
  {
    "path": "common/Untraceable.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Untraceable.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include \"common/Namespace.hh\"\n#include <stdio.h>\n#include <stdlib.h>\n#ifdef APPLE\n#include <sys/ptrace.h>\n#else\n#include <sys/prctl.h>\n#include <sys/ptrace.h>\n#endif\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class Untraceable\n//------------------------------------------------------------------------------\nclass Untraceable \n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  Untraceable() {\n#ifdef APPLE\n    if (ptrace(PT_DENY_ATTACH, 0, 0, 0) == -1 ) {\n      fprintf(stderr,\"error: failed to make the process untraceable\\n\");\n      exit(-1);\n    }\n#else\n\n    if (ptrace(PTRACE_TRACEME,0,0,0) || (prctl(PR_SET_DUMPABLE, 0) != 0 )) {\n      fprintf(stderr,\"error: failed to make the process untraceable\\n\");\n      exit(-1);\n    } else {\n    }\n#endif\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~Untraceable() {\n#ifdef APPLE\n    // nothing\n#else\n    prctl(PR_SET_DUMPABLE, 1);\n#endif\n  }\n\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/UriCapCipher.cc",
    "content": "//------------------------------------------------------------------------------\n// File: UriCapCipher.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/UriCapCipher.hh\"\n\n#include <openssl/evp.h>\n#include <openssl/rand.h>\n#include <openssl/err.h>\n#include <openssl/sha.h>\n\n#include <algorithm>\n#include <cstring>\n#include <fstream>\n#include <iterator>\n#include <sstream>\n#include <stdexcept>\n\nEOSCOMMONNAMESPACE_BEGIN\n\nUriCapCipher::UriCapCipher(const std::string& secret_file_path)\n  : pw_(compute_password_from_file(secret_file_path))\n{\n}\n\nUriCapCipher::UriCapCipher(PasswordTag, std::string password)\n  : pw_(std::move(password))\n{\n}\n\nUriCapCipher::UriCapCipher(PasswordTag, FixedSaltTag, std::string password)\n  : pw_(std::move(password))\n{\n  unsigned char digest[SHA256_DIGEST_LENGTH];\n  SHA256(reinterpret_cast<const unsigned char*>(pw_.data()), pw_.size(), digest);\n  std::memcpy(cached_salt_.data(), digest, cached_salt_.size());\n  std::vector<uint8_t> salt_vec(cached_salt_.begin(), cached_salt_.end());\n  cached_key_ = kdf_scrypt(pw_, salt_vec, kN, kR, kP, 32);\n  has_cached_key_ = true;\n}\n\nstd::string\nUriCapCipher::encryptToCgiFields(const std::string& plaintext) const\n{\n  // 1) Build header: fixed binary\n  Header h{};\n  h.v = 1;\n  h.kdf = 1;   // 1 = scrypt\n  h.aead = 1;  // 1 = chacha20-poly1305\n  h.N = kN;\n  h.r = kR;\n  h.p = kP;\n\n  const std::vector<uint8_t>* key_ptr = nullptr;\n  std::vector<uint8_t> derived_key;\n  bool wipe_key = false;\n\n  if (has_cached_key_) {\n    std::memcpy(h.salt, cached_salt_.data(), cached_salt_.size());\n    key_ptr = &cached_key_;\n  } else {\n    if (RAND_bytes(h.salt, sizeof(h.salt)) != 1) {\n      throw_openssl(\"RAND_bytes(salt)\");\n    }\n    std::vector<uint8_t> salt_vec(h.salt, h.salt + sizeof(h.salt));\n    derived_key = kdf_scrypt(pw_, salt_vec, h.N, h.r, h.p, 32);\n    key_ptr = &derived_key;\n    wipe_key = true;\n  }\n  if (RAND_bytes(h.nonce, sizeof(h.nonce)) != 1) {\n    throw_openssl(\"RAND_bytes(nonce)\");\n  }\n\n  // Serialize header -> cap.sym bytes\n  std::vector<uint8_t> sym_bytes = serializeHeader(h);\n  std::string cap_sym = b64url_encode(sym_bytes);\n\n  // 2) AEAD encrypt with AAD = exact cap.sym string bytes\n  std::vector<uint8_t> nonce_vec(h.nonce, h.nonce + sizeof(h.nonce));\n  std::vector<uint8_t> pt_vec(plaintext.begin(), plaintext.end());\n  std::vector<uint8_t> ct, tag;\n\n  aead_encrypt_chacha20poly1305(*key_ptr, nonce_vec, cap_sym, pt_vec, ct, tag);\n\n  // 4) cap.msg = b64url(ciphertext || tag)\n  std::vector<uint8_t> msg_bytes;\n  msg_bytes.reserve(ct.size() + tag.size());\n  msg_bytes.insert(msg_bytes.end(), ct.begin(), ct.end());\n  msg_bytes.insert(msg_bytes.end(), tag.begin(), tag.end());\n  std::string cap_msg = b64url_encode(msg_bytes);\n\n  // Optional: wipe key material\n  if (wipe_key) {\n    OPENSSL_cleanse(derived_key.data(), derived_key.size());\n  }\n\n  // 5) Emit CGI fields (base64url is URI-safe; you can still percent-encode externally if desired)\n  return std::string(\"cap.sym=\") + cap_sym + \"&cap.msg=\" + cap_msg;\n}\n\nstd::string\nUriCapCipher::decryptFromCgiFields(const std::string& cgi) const\n{\n  try {\n    // Parse query-like string\n    std::string cap_sym = getQueryValue(cgi, \"cap.sym\");\n    std::string cap_msg = getQueryValue(cgi, \"cap.msg\");\n    if (cap_sym.empty() || cap_msg.empty()) {\n      return \"\";\n    }\n\n    // URL-decode in case your framework percent-encoded the query values\n    cap_sym = url_percent_decode(cap_sym);\n    cap_msg = url_percent_decode(cap_msg);\n\n    // Decode and parse header\n    std::vector<uint8_t> sym_bytes = b64url_decode(cap_sym);\n    Header h = deserializeHeader(sym_bytes);\n\n    // Basic sanity checks\n    if (h.v != 1) return \"\";\n    if (h.kdf != 1) return \"\";\n    if (h.aead != 1) return \"\";\n    if (h.N < 2 || (h.N & (h.N - 1)) != 0) return \"\";\n    if (h.r == 0 || h.p == 0) return \"\";\n\n    // Decode cap.msg -> ciphertext||tag\n    std::vector<uint8_t> msg_bytes = b64url_decode(cap_msg);\n    if (msg_bytes.size() < kTagLen) return \"\";\n\n    const size_t ct_len = msg_bytes.size() - kTagLen;\n    std::vector<uint8_t> ct(msg_bytes.begin(), msg_bytes.begin() + ct_len);\n    std::vector<uint8_t> tag(msg_bytes.begin() + ct_len, msg_bytes.end());\n\n    // Derive key (or reuse cached key if header salt matches)\n    const std::vector<uint8_t>* key_ptr = nullptr;\n    std::vector<uint8_t> derived_key;\n    bool wipe_key = false;\n\n    if (has_cached_key_ &&\n        std::memcmp(h.salt, cached_salt_.data(), cached_salt_.size()) == 0) {\n      key_ptr = &cached_key_;\n    } else {\n      std::vector<uint8_t> salt_vec(h.salt, h.salt + sizeof(h.salt));\n      derived_key = kdf_scrypt(pw_, salt_vec, h.N, h.r, h.p, 32);\n      key_ptr = &derived_key;\n      wipe_key = true;\n    }\n\n    // Decrypt with AAD = exact cap.sym string bytes (must match encryption)\n    std::vector<uint8_t> nonce_vec(h.nonce, h.nonce + sizeof(h.nonce));\n    std::vector<uint8_t> pt = aead_decrypt_chacha20poly1305(\n        *key_ptr, nonce_vec, cap_sym, ct, tag);\n\n    if (wipe_key) {\n      OPENSSL_cleanse(derived_key.data(), derived_key.size());\n    }\n\n    return std::string(pt.begin(), pt.end());\n  } catch (...) {\n    return \"\";\n  }\n}\n\nvoid\nUriCapCipher::throw_openssl(const char* what)\n{\n  unsigned long e = ERR_get_error();\n  char buf[256];\n  ERR_error_string_n(e, buf, sizeof(buf));\n  throw std::runtime_error(std::string(what) + \": \" + buf);\n}\n\nstd::string\nUriCapCipher::compute_password_from_file(const std::string& path)\n{\n  std::ifstream f(path, std::ios::binary);\n  if (!f) {\n    throw std::runtime_error(\"Failed to open secret file: \" + path);\n  }\n\n  std::vector<uint8_t> data((std::istreambuf_iterator<char>(f)),\n                            std::istreambuf_iterator<char>());\n  if (data.empty()) {\n    throw std::runtime_error(\"Secret file is empty: \" + path);\n  }\n\n  // pw_ = SHA256(file_bytes) (32 bytes binary)\n  unsigned char digest[SHA256_DIGEST_LENGTH];\n  SHA256(data.data(), data.size(), digest);\n\n  return std::string(reinterpret_cast<const char*>(digest),\n                     SHA256_DIGEST_LENGTH);\n}\n\nstd::string\nUriCapCipher::b64url_encode(const std::vector<uint8_t>& in)\n{\n  if (in.empty()) return \"\";\n\n  std::string b64;\n  b64.resize(4 * ((in.size() + 2) / 3));\n  int outlen = EVP_EncodeBlock(reinterpret_cast<unsigned char*>(&b64[0]),\n                               in.data(), (int)in.size());\n  b64.resize(outlen);\n\n  for (char& c : b64) {\n    if (c == '+') c = '-';\n    else if (c == '/') c = '_';\n  }\n  while (!b64.empty() && b64.back() == '=') b64.pop_back();\n  return b64;\n}\n\nstd::vector<uint8_t>\nUriCapCipher::b64url_decode(const std::string& in)\n{\n  if (in.empty()) return {};\n\n  std::string b64 = in;\n  for (char& c : b64) {\n    if (c == '-') c = '+';\n    else if (c == '_') c = '/';\n  }\n  while (b64.size() % 4 != 0) b64.push_back('=');\n\n  std::vector<uint8_t> out(3 * (b64.size() / 4));\n  int len = EVP_DecodeBlock(out.data(),\n                            reinterpret_cast<const unsigned char*>(b64.data()),\n                            (int)b64.size());\n  if (len < 0) throw std::runtime_error(\"Base64 decode failed\");\n\n  size_t padding = 0;\n  if (!b64.empty() && b64.back() == '=') padding++;\n  if (b64.size() >= 2 && b64[b64.size() - 2] == '=') padding++;\n  out.resize((size_t)len - padding);\n  return out;\n}\n\nint\nUriCapCipher::hexval(char c)\n{\n  if (c >= '0' && c <= '9') return c - '0';\n  if (c >= 'a' && c <= 'f') return 10 + (c - 'a');\n  if (c >= 'A' && c <= 'F') return 10 + (c - 'A');\n  return -1;\n}\n\nstd::string\nUriCapCipher::url_percent_decode(const std::string& s)\n{\n  std::string out;\n  out.reserve(s.size());\n  for (size_t i = 0; i < s.size(); ++i) {\n    if (s[i] == '%' && i + 2 < s.size()) {\n      int h1 = hexval(s[i + 1]);\n      int h2 = hexval(s[i + 2]);\n      if (h1 >= 0 && h2 >= 0) {\n        out.push_back(char((h1 << 4) | h2));\n        i += 2;\n      } else {\n        out.push_back(s[i]);\n      }\n    } else if (s[i] == '+') {\n      out.push_back(' ');\n    } else {\n      out.push_back(s[i]);\n    }\n  }\n  return out;\n}\n\nstd::string\nUriCapCipher::getQueryValue(const std::string& input, const std::string& key)\n{\n  size_t start = 0;\n  while (start <= input.size()) {\n    size_t amp = input.find('&', start);\n    if (amp == std::string::npos) amp = input.size();\n    std::string_view part(input.data() + start, amp - start);\n\n    size_t eq = part.find('=');\n    if (eq != std::string_view::npos) {\n      std::string_view k = part.substr(0, eq);\n      std::string_view v = part.substr(eq + 1);\n      if (k == key) return std::string(v);\n    }\n    start = amp + 1;\n  }\n  return \"\";\n}\n\nvoid\nUriCapCipher::put_u64_le(std::vector<uint8_t>& out, uint64_t x)\n{\n  for (int i = 0; i < 8; ++i) out.push_back((uint8_t)((x >> (8 * i)) & 0xFF));\n}\n\nuint64_t\nUriCapCipher::get_u64_le(const uint8_t* p)\n{\n  uint64_t x = 0;\n  for (int i = 0; i < 8; ++i) x |= (uint64_t)p[i] << (8 * i);\n  return x;\n}\n\nstd::vector<uint8_t>\nUriCapCipher::serializeHeader(const Header& h)\n{\n  std::vector<uint8_t> out;\n  out.reserve(1 + 1 + 1 + 1 + 8 + 8 + 8 + kSaltLen + kNonceLen);\n\n  out.push_back(h.v);\n  out.push_back(h.kdf);\n  out.push_back(h.aead);\n  out.push_back(h.reserved);\n\n  put_u64_le(out, h.N);\n  put_u64_le(out, h.r);\n  put_u64_le(out, h.p);\n\n  out.insert(out.end(), h.salt, h.salt + kSaltLen);\n  out.insert(out.end(), h.nonce, h.nonce + kNonceLen);\n  return out;\n}\n\nUriCapCipher::Header\nUriCapCipher::deserializeHeader(const std::vector<uint8_t>& in)\n{\n  const size_t need = 1 + 1 + 1 + 1 + 8 + 8 + 8 + kSaltLen + kNonceLen;\n  if (in.size() != need) {\n    throw std::runtime_error(\"cap.sym header wrong length\");\n  }\n\n  Header h{};\n  size_t off = 0;\n  h.v = in[off++];\n  h.kdf = in[off++];\n  h.aead = in[off++];\n  h.reserved = in[off++];\n\n  h.N = get_u64_le(&in[off]); off += 8;\n  h.r = get_u64_le(&in[off]); off += 8;\n  h.p = get_u64_le(&in[off]); off += 8;\n\n  std::memcpy(h.salt, &in[off], kSaltLen); off += kSaltLen;\n  std::memcpy(h.nonce, &in[off], kNonceLen); off += kNonceLen;\n\n  return h;\n}\n\nstd::vector<uint8_t>\nUriCapCipher::kdf_scrypt(const std::string& password_bytes,\n                         const std::vector<uint8_t>& salt,\n                         uint64_t N, uint64_t r, uint64_t p,\n                         size_t key_len)\n{\n  std::vector<uint8_t> key(key_len);\n  const uint64_t needed = (128ull * r * N) + (128ull * r * p) + (256ull * r);\n  const uint64_t maxmem = std::max<uint64_t>(needed, 256ull * 1024 * 1024);\n  if (EVP_PBE_scrypt(password_bytes.data(), password_bytes.size(),\n                     salt.data(), salt.size(),\n                     N, r, p,\n                     maxmem,\n                     key.data(), key.size()) != 1) {\n    throw_openssl(\"EVP_PBE_scrypt\");\n  }\n  return key;\n}\n\nvoid\nUriCapCipher::aead_encrypt_chacha20poly1305(\n    const std::vector<uint8_t>& key,\n    const std::vector<uint8_t>& nonce12,\n    const std::string& aad,\n    const std::vector<uint8_t>& plaintext,\n    std::vector<uint8_t>& ciphertext_out,\n    std::vector<uint8_t>& tag16_out)\n{\n  if (nonce12.size() != kNonceLen) {\n    throw std::runtime_error(\"nonce must be 12 bytes\");\n  }\n\n  tag16_out.assign(kTagLen, 0);\n  ciphertext_out.assign(plaintext.size(), 0);\n\n  EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();\n  if (!ctx) throw std::runtime_error(\"EVP_CIPHER_CTX_new failed\");\n\n  int len = 0;\n  if (EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), nullptr, nullptr, nullptr) != 1) {\n    throw_openssl(\"EncryptInit\");\n  }\n\n  if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, (int)nonce12.size(), nullptr) != 1) {\n    throw_openssl(\"SET_IVLEN\");\n  }\n\n  if (EVP_EncryptInit_ex(ctx, nullptr, nullptr, key.data(), nonce12.data()) != 1) {\n    throw_openssl(\"EncryptInit key/iv\");\n  }\n\n  if (!aad.empty()) {\n    if (EVP_EncryptUpdate(ctx, nullptr, &len,\n                          reinterpret_cast<const unsigned char*>(aad.data()),\n                          (int)aad.size()) != 1) {\n      throw_openssl(\"EncryptUpdate AAD\");\n    }\n  }\n\n  if (!plaintext.empty()) {\n    if (EVP_EncryptUpdate(ctx, ciphertext_out.data(), &len,\n                          plaintext.data(), (int)plaintext.size()) != 1) {\n      throw_openssl(\"EncryptUpdate PT\");\n    }\n  }\n\n  int finallen = 0;\n  if (EVP_EncryptFinal_ex(ctx, ciphertext_out.data() + len, &finallen) != 1) {\n    throw_openssl(\"EncryptFinal\");\n  }\n\n  if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, (int)kTagLen,\n                          tag16_out.data()) != 1) {\n    throw_openssl(\"GET_TAG\");\n  }\n\n  EVP_CIPHER_CTX_free(ctx);\n}\n\nstd::vector<uint8_t>\nUriCapCipher::aead_decrypt_chacha20poly1305(\n    const std::vector<uint8_t>& key,\n    const std::vector<uint8_t>& nonce12,\n    const std::string& aad,\n    const std::vector<uint8_t>& ciphertext,\n    const std::vector<uint8_t>& tag16)\n{\n  if (nonce12.size() != kNonceLen) {\n    throw std::runtime_error(\"nonce must be 12 bytes\");\n  }\n  if (tag16.size() != kTagLen) {\n    throw std::runtime_error(\"tag must be 16 bytes\");\n  }\n\n  std::vector<uint8_t> plaintext(ciphertext.size());\n\n  EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();\n  if (!ctx) throw std::runtime_error(\"EVP_CIPHER_CTX_new failed\");\n\n  int len = 0;\n  if (EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), nullptr, nullptr, nullptr) != 1) {\n    throw_openssl(\"DecryptInit\");\n  }\n\n  if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, (int)nonce12.size(), nullptr) != 1) {\n    throw_openssl(\"SET_IVLEN\");\n  }\n\n  if (EVP_DecryptInit_ex(ctx, nullptr, nullptr, key.data(), nonce12.data()) != 1) {\n    throw_openssl(\"DecryptInit key/iv\");\n  }\n\n  if (!aad.empty()) {\n    if (EVP_DecryptUpdate(ctx, nullptr, &len,\n                          reinterpret_cast<const unsigned char*>(aad.data()),\n                          (int)aad.size()) != 1) {\n      throw_openssl(\"DecryptUpdate AAD\");\n    }\n  }\n\n  if (!ciphertext.empty()) {\n    if (EVP_DecryptUpdate(ctx, plaintext.data(), &len,\n                          ciphertext.data(), (int)ciphertext.size()) != 1) {\n      throw_openssl(\"DecryptUpdate CT\");\n    }\n  }\n\n  if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, (int)kTagLen,\n                          (void*)tag16.data()) != 1) {\n    throw_openssl(\"SET_TAG\");\n  }\n\n  int finallen = 0;\n  int ok = EVP_DecryptFinal_ex(ctx, plaintext.data() + len, &finallen);\n  EVP_CIPHER_CTX_free(ctx);\n\n  if (ok != 1) throw std::runtime_error(\"Auth failed\");\n\n  plaintext.resize((size_t)len + (size_t)finallen);\n  return plaintext;\n}\n\nEOSCOMMONNAMESPACE_END\n\n"
  },
  {
    "path": "common/UriCapCipher.hh",
    "content": "//------------------------------------------------------------------------------\n// File: UriCapCipher.hh\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @file UriCapCipher.hh\n//! @author Andreas-Joachim Peters\n//! @brief URI capability cipher helper\n//!\n//! Usage overview:\n//! - Construct with a secret file path (password derived from file contents),\n//!   or provide a password string directly using PasswordTag.\n//! - encryptToCgiFields() returns \"cap.sym=...&cap.msg=...\" where cap.sym holds\n//!   the header (version/KDF params/salt/nonce) and cap.msg holds ciphertext+tag.\n//! - Encryption uses AEAD (chacha20-poly1305), so the tag authenticates the\n//!   ciphertext and the associated data; cap.sym is bound as AAD, meaning any\n//!   tampering of either cap.sym or cap.msg is detected during decryption.\n//! - decryptFromCgiFields() parses those fields and returns the plaintext or \"\"\n//!   on failure.\n//!\n//! Performance notes:\n//! - Default mode generates a fresh random salt per message and runs scrypt for\n//!   each encrypt/decrypt.\n//! - The FixedSaltTag constructor caches the derived key using a deterministic\n//!   salt derived from the password; encryption still uses a fresh nonce per\n//!   message. Decryption reuses the cached key when the message salt matches.\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include <array>\n#include <cstdint>\n#include <string>\n#include <string_view>\n#include <vector>\n\nEOSCOMMONNAMESPACE_BEGIN\n\nclass UriCapCipher\n{\npublic:\n  struct PasswordTag {\n    explicit PasswordTag() = default;\n  };\n  struct FixedSaltTag {\n    explicit FixedSaltTag() = default;\n  };\n\n  explicit UriCapCipher(const std::string& secret_file_path);\n  explicit UriCapCipher(PasswordTag, std::string password);\n  explicit UriCapCipher(PasswordTag, FixedSaltTag, std::string password);\n\n  // Encrypt arbitrary string into \"cap.sym=...&cap.msg=...\"\n  std::string encryptToCgiFields(const std::string& plaintext) const;\n\n  // Decrypt from a string containing cap.sym=... and cap.msg=...\n  // Returns decrypted string, or \"\" on any failure.\n  std::string decryptFromCgiFields(const std::string& cgi) const;\n\nprivate:\n  // ----- Tunables -----\n  static constexpr uint64_t kN = 1ull << 15; // scrypt N (CPU/mem cost)\n  static constexpr uint64_t kR = 8;\n  static constexpr uint64_t kP = 1;\n\n  static constexpr size_t kSaltLen = 16;\n  static constexpr size_t kNonceLen = 12;\n  static constexpr size_t kTagLen = 16;\n\n  // Fixed binary header (packed manually; do not rely on struct packing)\n  struct Header {\n    uint8_t v;       // version\n    uint8_t kdf;     // 1=scrypt\n    uint8_t aead;    // 1=chacha20-poly1305\n    uint8_t reserved;// future use\n    uint64_t N;      // scrypt N\n    uint64_t r;      // scrypt r\n    uint64_t p;      // scrypt p\n    uint8_t salt[kSaltLen];\n    uint8_t nonce[kNonceLen];\n  };\n\n  std::string pw_; // 32 bytes (binary) derived from file or provided directly\n  bool has_cached_key_ = false;\n  std::array<uint8_t, kSaltLen> cached_salt_{};\n  std::vector<uint8_t> cached_key_;\n\n  // ----- OpenSSL error helper -----\n  static void throw_openssl(const char* what);\n\n  // ----- Password derivation from file -----\n  static std::string compute_password_from_file(const std::string& path);\n\n  // ----- base64url encode/decode -----\n  static std::string b64url_encode(const std::vector<uint8_t>& in);\n  static std::vector<uint8_t> b64url_decode(const std::string& in);\n\n  // ----- URL percent decode (minimal) -----\n  static int hexval(char c);\n  static std::string url_percent_decode(const std::string& s);\n\n  // ----- Query parsing -----\n  static std::string getQueryValue(const std::string& input,\n                                   const std::string& key);\n\n  // ----- Little-endian helpers -----\n  static void put_u64_le(std::vector<uint8_t>& out, uint64_t x);\n  static uint64_t get_u64_le(const uint8_t* p);\n\n  // ----- Header serialize/deserialize -----\n  static std::vector<uint8_t> serializeHeader(const Header& h);\n  static Header deserializeHeader(const std::vector<uint8_t>& in);\n\n  // ----- scrypt KDF -----\n  static std::vector<uint8_t> kdf_scrypt(const std::string& password_bytes,\n                                         const std::vector<uint8_t>& salt,\n                                         uint64_t N, uint64_t r, uint64_t p,\n                                         size_t key_len);\n\n  // ----- AEAD encrypt/decrypt -----\n  static void aead_encrypt_chacha20poly1305(\n      const std::vector<uint8_t>& key,\n      const std::vector<uint8_t>& nonce12,\n      const std::string& aad,\n      const std::vector<uint8_t>& plaintext,\n      std::vector<uint8_t>& ciphertext_out,\n      std::vector<uint8_t>& tag16_out);\n\n  static std::vector<uint8_t> aead_decrypt_chacha20poly1305(\n      const std::vector<uint8_t>& key,\n      const std::vector<uint8_t>& nonce12,\n      const std::string& aad,\n      const std::vector<uint8_t>& ciphertext,\n      const std::vector<uint8_t>& tag16);\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Utils.cc",
    "content": "//------------------------------------------------------------------------------\n// File: Utils.hh\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Utils.hh\"\n#include \"common/Logging.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/StringTokenizer.hh\"\n#include <iomanip>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include \"zlib.h\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Open random temporary file in /tmp/\n//------------------------------------------------------------------------------\nstd::string MakeTemporaryFile(std::string& pattern)\n{\n  int tmp_fd = mkstemp((char*)pattern.c_str());\n\n  if (tmp_fd == -1) {\n    eos_static_crit(\"%s\", \"msg=\\\"failed to create temporary file!\\\"\");\n    return \"\";\n  }\n\n  (void) close(tmp_fd);\n  return pattern;\n}\n\n//-----------------------------------------------------------------------------\n// Make sure that geotag contains only alphanumeric segments which\n// are no longer than 8 characters, in <tag1>::<tag2>::...::<tagN> format.\n//-----------------------------------------------------------------------------\nstd::string SanitizeGeoTag(const std::string& geotag)\n{\n  if (geotag.empty()) {\n    return std::string(\"Error: empty geotag\");\n  }\n\n  if (geotag == \"<none>\") {\n    return geotag;\n  }\n\n  std::string tmp_tag(geotag);\n  auto segments = eos::common::StringTokenizer::split<std::vector<std::string>>\n                  (tmp_tag, ':');\n  tmp_tag.clear();\n\n  for (const auto& segment : segments) {\n    if (segment.empty()) {\n      continue;\n    }\n\n    if (segment.length() > 8) {\n      return std::string(\"Error: geotag segment '\" + segment +\n                         \"' is longer than 8 chars\");\n    }\n\n    for (const auto& c : segment) {\n      if (!std::isalnum(c)) {\n        return std::string(\"Error: geotag segment '\" + segment + \"' \"\n                           \"contains non-alphanumeric char '\" + c + \"'\");\n      }\n    }\n\n    tmp_tag += segment;\n    tmp_tag += \"::\";\n  }\n\n  if (tmp_tag.length() <= 2) {\n    return std::string(\"Error: empty geotag\");\n  }\n\n  tmp_tag.erase(tmp_tag.length() - 2);\n\n  if (tmp_tag != geotag) {\n    return std::string(\"Error: invalid geotag format '\" + geotag + \"'\");\n  }\n\n  return tmp_tag;\n}\n\n//------------------------------------------------------------------------------\n// Get (keytab) file adler checksum\n//------------------------------------------------------------------------------\nbool\nGetFileAdlerXs(std::string& adler_xs, const std::string& fn)\n{\n  int fd = ::open(fn.c_str(), O_RDONLY);\n\n  if (fd >= 0) {\n    char buffer[65535];\n    size_t nread = ::read(fd, buffer, sizeof(buffer));\n    unsigned int adler_val = adler32(0L, Z_NULL, 0);\n\n    while (nread > 0) {\n      adler_val = adler32(adler_val, (const Bytef*) buffer, nread);\n      nread = ::read(fd, buffer, sizeof(buffer));\n    }\n\n    (void) close(fd);\n    char result[1024];\n    snprintf(result, sizeof(result) - 1, \"%08x\", adler_val);\n    adler_xs = result;\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Get binary SHA1 of given (keytab) File\n//------------------------------------------------------------------------------\nbool\nGetFileBinarySha1(std::string& bin_sha1, const std::string& fn)\n{\n  std::ifstream file(fn);\n\n  if (file.is_open()) {\n    uint64_t fsize = file.tellg();\n\n    if (fsize > 1024 * 1024) {\n      eos_static_err(\"msg=\\\"file too big >1MB\\\", path=\\\"%s\\\"\", fn.c_str());\n      return false;\n    }\n\n    std::stringstream ss;\n    ss << file.rdbuf();\n    bin_sha1 = eos::common::SymKey::BinarySha1(ss.str());\n    return true;\n  }\n\n  return false;\n}\n\n\n//------------------------------------------------------------------------------\n// Get SHA-1 hex digest of given (keytab) file\n//------------------------------------------------------------------------------\nbool\nGetFileHexSha1(std::string& hex_sha1, const std::string& fn)\n{\n  std::string bin_sha1;\n\n  if (!GetFileBinarySha1(bin_sha1, fn)) {\n    return false;\n  }\n\n  unsigned char* ptr = (unsigned char*)bin_sha1.data();\n  unsigned int sz_result = bin_sha1.size();\n  std::ostringstream oss;\n  oss.fill('0');\n  oss << std::hex;\n\n  for (unsigned int i = 0; i < sz_result; ++i) {\n    oss << std::setw(2) << (unsigned int) *ptr;\n    ++ptr;\n  }\n\n  hex_sha1 = oss.str();\n  return true;\n}\n\nvoid ComputeSize(uint64_t & size, int64_t delta) {\n  // Avoid negative size\n  if ((delta < 0) && (static_cast<uint64_t>(std::llabs(delta)) > size)) {\n    size = 0;\n  } else {\n    size += delta;\n  }\n}\n\nvoid AddEosApp(std::string & pathOrOpaque, const std::string & protocol)\n{\n  constexpr std::string_view eosAppPrefix = \"eos.app=\";\n  const size_t eosAppPrefixLen = eosAppPrefix.size();\n\n  // Remove the last character if it is an '&' or a '?' --> we will add it if eos.app does not exist,\n  // we will replace the value of eos.app otherwise\n  if(!pathOrOpaque.empty() && (pathOrOpaque.back() == '&' || pathOrOpaque.back() == '?')) {\n    pathOrOpaque.pop_back();\n  }\n\n  // Only get the last eos.app of the opaque query as\n  // explained in the comment of this function!\n  size_t eosAppPos = pathOrOpaque.rfind(eosAppPrefix.data());\n  // eos.app not found, set it to protocol\n  if (eosAppPos == std::string::npos) {\n    if(!pathOrOpaque.empty()) {\n      // Only add a question mark if the pathOrOpaque provided is a path (starts with '/') and there's no question mark anywhere\n      const bool needQuestion = (pathOrOpaque.front() == '/' &&\n                                 pathOrOpaque.find('?') == std::string::npos);\n      pathOrOpaque.append(needQuestion ? \"?\" : \"&\");\n    }\n    pathOrOpaque.append(eosAppPrefix)\n                .append(protocol);\n    return;\n  }\n\n  // eos.app is found, ensure it is either equal to protocol otherwise prepend it with \"protocol/\"\n  // Extract eos.app value\n  size_t eosAppEndValuePos =\n      pathOrOpaque.find('&', eosAppPos + eosAppPrefixLen);\n  std::string eosAppValue =\n      pathOrOpaque.substr(eosAppPos + eosAppPrefixLen, eosAppEndValuePos - (eosAppPos + eosAppPrefixLen));\n  size_t eosAppValueLen = eosAppValue.size();\n  size_t protocolLen = protocol.size();\n  int startsWithProtocol = eosAppValue.compare(0, protocolLen, protocol);\n  if (startsWithProtocol != 0) {\n    eosAppValue.insert(0, protocol + \"/\");\n  } else {\n    // eos.app value starts with protocol\n    if (eosAppValueLen > protocolLen) {\n      // the eos.app value starts with protocol but has stuff behind\n      // is it a \"/\" ?\n      size_t slashPos = eosAppValue.find('/', protocolLen);\n      if (slashPos == std::string::npos) {\n        // No slash found, prepend protocol + \"/\"\n        eosAppValue.insert(0, protocol + \"/\");\n      } else {\n        // Slash found\n        if (slashPos == eosAppValueLen - 1) {\n          // There's nothing after the slash, delete it\n          // e.g: /eos/test/fic.txt?eos.app=protocol/ --> /eos/test/fic.txt?eos.app=protocol\n          eosAppValue.erase(slashPos);\n        } else {\n          // There's something after the slash,\n          // nothing to do with the eos.app value\n          return;\n        }\n      }\n    }\n  }\n  // Replace the previous value of eos.app\n  pathOrOpaque.erase(eosAppPos + eosAppPrefixLen, eosAppValueLen);\n  pathOrOpaque.insert(eosAppPos + eosAppPrefixLen, eosAppValue);\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/Utils.hh",
    "content": "//------------------------------------------------------------------------------\n// File: Utils.hh\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include <cstdint>\n#include <string>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Open random temporary file\n//!\n//! @param pattern location pattern to by used by mkstemp\n//!\n//! @return string temporary file path, if open failed return empty string\n//------------------------------------------------------------------------------\nstd::string MakeTemporaryFile(std::string& pattern);\n\n//-----------------------------------------------------------------------------\n//! Make sure that geotag contains only alphanumeric segments which\n//! are no longer than 8 characters, in <tag1>::<tag2>::...::<tagN> format.\n//!\n//! @param geotag input value\n//!\n//! @return error message if geotag is not valid, otherwise geotag\n//-----------------------------------------------------------------------------\nstd::string SanitizeGeoTag(const std::string& geotag);\n\n//------------------------------------------------------------------------------\n//! Get (keytab) file adler checksum\n//!\n//! @param adler_xs output computed checksum\n//! @param fn ketab file to use as input\n//!\n//! @return true if successful, otherwise false\n//------------------------------------------------------------------------------\nbool GetFileAdlerXs(std::string& adler_xs,\n                    const std::string& fn = \"/etc/eos.keytab\");\n\n//------------------------------------------------------------------------------\n//! Get binary SHA1 of given (keytab) file\n//!\n//! @param bin_sha1 binary sha1 result\n//! @param fn ketab file to use as input\n//!\n//! @return true if successful, otherwise false\n//------------------------------------------------------------------------------\nbool GetFileBinarySha1(std::string& bin_sha1,\n                       const std::string& fn = \"/etc/eos.keytab\");\n\n//------------------------------------------------------------------------------\n//! Get SHA-1 hex digest of given (keytab) file\n//!\n//! @param hex_sha1 SHA-1 hex digest\n//! @param fn ketab file to use as input\n//!\n//! @return true if successful, otherwise false\n//------------------------------------------------------------------------------\nbool GetFileHexSha1(std::string& hex_sha1,\n                    const std::string& fn = \"/etc/eos.keytab\");\n\n\nvoid ComputeSize(uint64_t & size, int64_t delta);\n\n//------------------------------------------------------------------------------\n//! Adds eos.app=protocol opaque info to the path or opaque infos provided by pathOrOpaque\n//! if eos.app is already present in the pathOrOpaque, the value will be prepended\n//! by protocol/. If the value is equal to protocol, then nothing will be done\n//! If the value is equal to protocol/xyz, then nothing will be done\n//! e.g: pathOrOpaque=/eos/test/fic.txt?eos.app=hello --> pathOrOpaque will be equal to\n//! /eos/test/fic.txt?eos.app=protocol/hello\n//! e.g2: pathOrOpaque= test=test1&eos.app=protocol --> pathOpaque will be left untouched\n//! e.g3: pathOrOpaque=/eos/test/fic.txt?eos.app=http/s3 --> pathOpaque will be left untouched\n//! if eos.app is not already present in the pathOrOpaque, the value of eos.app\n//! will be eos.app=protocol\n//! if eos.app is provided twice in the pathOrOpaque, only the last occurence of eos.app will be\n//! considered. Indeed, usually the opaque will be transformed into a XrdOucEnv and it\n//! is implemented using a hash --> the second occurence will overwrite the first one!\n//!\n//! @param pathOrOpaque the pathOrOpaque to add the eos.app opaque info\n//! @param protocol the protocol of the eos.app opaque parameter\n//------------------------------------------------------------------------------\nvoid AddEosApp(std::string & pathOrOpaque, const std::string & protocol);\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/VirtualIdentity.cc",
    "content": "// ----------------------------------------------------------------------\n// File: VirtualIdentity.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/VirtualIdentity.hh\"\n#include \"common/Timing.hh\"\n#include <iostream>\n#include <sstream>\n#include <cstring>\n#include <pwd.h>\n\nnamespace\n{\n//! Method to get the uid/gid values for user 'nobody'\nstd::pair<uid_t, gid_t> GetNobodyUidGid()\n{\n  struct passwd pw_info;\n  memset(&pw_info, 0, sizeof(pw_info));\n  char buffer[131072];\n  size_t buflen = sizeof(buffer);\n  struct passwd* pw_prt = 0;\n  const std::string name = \"nobody\";\n\n  if (getpwnam_r(name.c_str(), &pw_info, buffer, buflen, &pw_prt) ||\n      (!pw_prt)) {\n    std::terminate();\n  }\n\n  return std::make_pair(pw_info.pw_uid, pw_info.pw_gid);\n};\n}\n\n\nEOSCOMMONNAMESPACE_BEGIN\n\nuid_t VirtualIdentity::kNobodyUid = GetNobodyUidGid().first;\ngid_t VirtualIdentity::kNobodyGid = GetNobodyUidGid().second;\n\n//----------------------------------------------------------------------------\n//! Constructor - assign to \"nobody\" by default\n//----------------------------------------------------------------------------\nVirtualIdentity::VirtualIdentity() :\n  uid(kNobodyUid), gid(kNobodyGid), sudoer(false), gateway(false)\n{}\n\n//------------------------------------------------------------------------------\n// \"Constructor\" - return Root identity\n//------------------------------------------------------------------------------\nVirtualIdentity VirtualIdentity::Root()\n{\n  VirtualIdentity vid;\n  vid.uid = 0;\n  vid.gid = 0;\n  vid.allowed_uids = {0};\n  vid.allowed_gids = {0};\n  vid.name = \"root\";\n  vid.prot = \"local\";\n  vid.tident = \"service@localhost\";\n  vid.sudoer = false;\n  vid.host = \"localhost\";\n  vid.gateway = false;\n  return vid;\n}\n\n//------------------------------------------------------------------------------\n// \"Constructor\" - return Nobody identity\n//------------------------------------------------------------------------------\nVirtualIdentity VirtualIdentity::Nobody()\n{\n  VirtualIdentity vid;\n  vid.uid = kNobodyUid;\n  vid.gid = kNobodyGid;\n  vid.allowed_uids = {kNobodyUid};\n  vid.allowed_gids = {kNobodyGid};\n  vid.name = \"nobody\";\n  vid.sudoer = false;\n  vid.gateway = false;\n  vid.tident = \"nobody@unknown\";\n  return vid;\n}\n\n//------------------------------------------------------------------------------\n// Check if this client is coming from localhost\n//------------------------------------------------------------------------------\nbool VirtualIdentity::isLocalhost() const\n{\n  if (host == \"localhost\"               ||\n      host == \"localhost.localdomain\"  ||\n      host == \"localhost6\"             ||\n      host == \"localhost6.localdomain6\") {\n    return true;\n  }\n\n  return false;\n}\n\n\n//----------------------------------------------------------------------------\n// Return user@domain string\n//----------------------------------------------------------------------------\nstd::string\nVirtualIdentity::getUserAtDomain() const\n{\n  return uid_string + \"@\" + domain;\n}\n\n//----------------------------------------------------------------------------\n// Return group@domain string\n//----------------------------------------------------------------------------\nstd::string\nVirtualIdentity::getGroupAtDomain() const\n{\n  return gid_string + \"@\" + domain;\n}\n\n//----------------------------------------------------------------------------\n// Return vid trace string\n//----------------------------------------------------------------------------\n\nstd::string\nVirtualIdentity::getTrace(bool compact) const\n{\n  std::stringstream ss;\n  time_t now = time(NULL);\n\n  if (compact) {\n    ss << \"{uid:\" << uid << \",gid:\" << gid << \",tident:\" << tident << \",prot:\" <<\n       prot << \",app:\" << app << \",host:\" << host << \",domain:\" << domain << \"trace:\"\n       << trace << \",onbehalf:\" << onbehalf << \"}\";\n    return ss.str();\n  } else {\n    ss << \"[\" << eos::common::Timing::ltime(now) << \"] uid:\" << uid << \"[\" <<\n       uid_string << \"] gid:\" << gid << \"[\" << gid_string << \"] tident:\" <<\n       tident.c_str() << \" name:\" << name << \" dn:\" << dn << \" prot:\" << prot <<\n       \" app:\" << app << \" host:\" << host << \" domain:\" << domain << \" geo:\" <<\n       geolocation << \" sudo:\"\n       << sudoer << \" trace:\" << trace << \" onbehalf:\" << onbehalf;\n    return ss.str();\n  }\n}\n\n//----------------------------------------------------------------------------\n// Set user/group to nobody\n//----------------------------------------------------------------------------\nvoid VirtualIdentity::toNobody()\n{\n  uid = kNobodyUid;\n  gid = kNobodyGid;\n  allowed_uids = {kNobodyUid};\n  allowed_gids = {kNobodyGid};\n  name = \"nobody\";\n  sudoer = false;\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/VirtualIdentity.hh",
    "content": "// ----------------------------------------------------------------------\n// File: VirtualIdentity.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Namespace.hh\"\n#include \"common/token/Token.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <vector>\n#include <memory>\n#include <string>\n#include <set>\n#include <cstdint>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n#pragma once\n//------------------------------------------------------------------------------\n//! Struct defining the virtual identity of a client e.g. their memberships and\n//! authentication information\n//------------------------------------------------------------------------------\nstruct VirtualIdentity {\n  static uid_t kNobodyUid;\n  static gid_t kNobodyGid;\n  uid_t uid;\n  gid_t gid;\n  std::string uid_string;\n  std::string gid_string;\n  std::set<uid_t> allowed_uids;\n  std::set<gid_t> allowed_gids;\n  XrdOucString tident;\n  XrdOucString name;\n  XrdOucString prot;\n  std::string host;\n  std::string domain;\n  std::string grps;\n  std::string role;\n  std::string dn;\n  std::string geolocation;\n  std::string app;\n  std::string key;\n  std::string email;\n  std::string fullname;\n  std::string federation;\n  std::string scope;\n  std::string trace;\n  std::string onbehalf;\n  bool sudoer;\n  bool gateway;\n  std::shared_ptr<Token> token;\n\n  //----------------------------------------------------------------------------\n  //! Constructor - assign to \"nobody\" by default\n  //----------------------------------------------------------------------------\n  VirtualIdentity();\n\n  //----------------------------------------------------------------------------\n  //! \"Constructor\" - return Root identity\n  //----------------------------------------------------------------------------\n  static VirtualIdentity Root();\n\n  //----------------------------------------------------------------------------\n  //! \"Constructor\" - return Nobody identity\n  //----------------------------------------------------------------------------\n  static VirtualIdentity Nobody();\n\n  //----------------------------------------------------------------------------\n  //! Check if the uid vector contains has the requested uid\n  //----------------------------------------------------------------------------\n  inline bool hasUid(uid_t uid) const\n  {\n    return (allowed_uids.find(uid) != allowed_uids.end());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if the gid vector contains has the requested gid\n  //----------------------------------------------------------------------------\n  bool hasGid(gid_t gid) const\n  {\n    return (allowed_gids.find(gid) != allowed_gids.end());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if this client is coming from localhost\n  //----------------------------------------------------------------------------\n  bool isLocalhost() const;\n\n  //----------------------------------------------------------------------------\n  //! Check if this client is coming from localhost\n  //----------------------------------------------------------------------------\n  bool isGateway() { return gateway; }\n\n  //----------------------------------------------------------------------------\n  //! Return user@domain string\n  //----------------------------------------------------------------------------\n  std::string getUserAtDomain() const;\n\n  //----------------------------------------------------------------------------\n  //! Return group@domain string\n  //----------------------------------------------------------------------------\n  std::string getGroupAtDomain() const;\n\n  //----------------------------------------------------------------------------\n  //! Return a vid trace string\n  //----------------------------------------------------------------------------\n  std::string getTrace(bool compact=false) const;\n\n  //----------------------------------------------------------------------------\n  //! Set uid/gid to nobody\n  //----------------------------------------------------------------------------\n  void toNobody();\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/WFEClient.hh",
    "content": "// ----------------------------------------------------------------------\n// File: WFEClient.hh\n// Author: Konstantina Skovola - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"XrdSsiPbConfig.hpp\"\n#include \"common/Logging.hh\"\n#include \"cta_frontend.grpc.pb.h\"\n#include \"cta_frontend.pb.h\"\n#include \"jwt-cpp/jwt.h\"\n#include \"xrootd-ssi-protobuf-interface/eos_cta/include/CtaFrontendApi.hpp\"\n#include <XrdSsiPbIStreamBuffer.hpp>\n#include <grpc++/grpc++.h>\n#include <grpcpp/security/credentials.h>\n\nclass WFEClient {\npublic:\n  virtual cta::xrd::Response::ResponseType send(const cta::xrd::Request& request, cta::xrd::Response& response) = 0;\n  virtual ~WFEClient() = default;\n};\n\nclass WFEGrpcClient : public WFEClient {\npublic:\n  WFEGrpcClient(const std::string& endpoint_str, std::optional<std::string> root_certs, const std::string& token_path_str, bool protowfusegrpctls) {\n    endpoint = endpoint_str;\n    token_path = token_path_str;\n\n    std::shared_ptr<grpc::ChannelCredentials> credentials;\n    grpc::SslCredentialsOptions ssl_options;\n\n    if (protowfusegrpctls) {\n      if (root_certs.has_value()) {\n        std::string root_certs_contents;\n        eos::common::StringConversion::LoadFileIntoString(root_certs.value().c_str(), root_certs_contents);\n        ssl_options.pem_root_certs = root_certs_contents;\n      } else {\n        ssl_options.pem_root_certs = \"\";\n      }\n      eos_static_info(\"value used in pem_root_certs is %s\", ssl_options.pem_root_certs.c_str());\n      credentials = grpc::SslCredentials(ssl_options);\n    } else {\n      credentials = grpc::InsecureChannelCredentials();\n    }\n    // Create a channel with SSL credentials\n    std::shared_ptr<grpc::Channel> channel = grpc::CreateChannel(endpoint_str, credentials);\n    client_stub = cta::xrd::CtaRpc::NewStub(channel);\n  }\n\n  // for gRPC the default is to retry a failed request (see GRPC_ARG_ENABLE_RETRIES)\n  cta::xrd::Response::ResponseType send(const cta::xrd::Request& request, cta::xrd::Response& response) override {\n    grpc::ClientContext context;\n    grpc::Status status;\n\n    std::string token_contents;\n    // read the token from the path\n    eos::common::StringConversion::LoadFileIntoString(token_path.c_str(), token_contents);\n\n    // before adding the metadata, ensure that the token contents are not\n    // malformed: if decoding works, it should be a valid JWT\n    try {\n      auto decoded = jwt::decode(token_contents);\n    } catch (std::invalid_argument &ex) {\n      throw std::runtime_error(std::string(\"Token is not in correct format:\") + ex.what());\n    } catch (std::runtime_error &ex) {\n      throw;\n    }\n\n    context.AddMetadata(\"authorization\", \"Bearer \" + token_contents);\n    eos_static_debug(\"msg=\\\"successfully attached call credentials\\\" token=\\\"%s\\\"\",\n                     token_contents.c_str());\n\n    switch (request.notification().wf().event()) {\n      // this is prepare\n      case cta::eos::Workflow::CREATE:\n        status = client_stub->Create(&context, request, &response);\n        break;\n      case cta::eos::Workflow::CLOSEW:\n        status = client_stub->Archive(&context, request, &response);\n        break;\n      case cta::eos::Workflow::PREPARE:\n        status = client_stub->Retrieve(&context, request, &response);\n        break;\n      case cta::eos::Workflow::ABORT_PREPARE:\n        status = client_stub->CancelRetrieve(&context, request, &response);\n        break;\n      case cta::eos::Workflow::DELETE:\n        status = client_stub->Delete(&context, request, &response);\n        break;\n      case cta::eos::Workflow::OPENW:\n        // this does nothing and we don't have a gRPC method for it\n        /* fallthrough */\n      case cta::eos::Workflow::UPDATE_FID:\n        /* fallthrough */\n      default:\n        // should probably have an error here that we don't have a gRPC method for this\n        status = grpc::Status(grpc::StatusCode::UNIMPLEMENTED, \"gRPC method not implemented for \" + cta::eos::Workflow_EventType_Name(request.notification().wf().event()));\n        break;\n    }\n    if (status.ok()){\n      return cta::xrd::Response::RSP_SUCCESS;\n    } else {\n      switch (status.error_code()) {\n        // user-code (CTA) generated errors,\n        // we need to do response.set_message_txt here because apparently, gRPC does not\n        // guarantee that the protobuf fields will be filled in, in case of error\n        case grpc::StatusCode::INVALID_ARGUMENT:\n          response.set_message_txt(status.error_message());\n          return cta::xrd::Response::RSP_ERR_PROTOBUF;\n        case grpc::StatusCode::ABORTED:\n          response.set_message_txt(status.error_message());\n          return cta::xrd::Response::RSP_ERR_USER;\n        case grpc::StatusCode::FAILED_PRECONDITION:\n          response.set_message_txt(status.error_message());\n          return cta::xrd::Response::RSP_ERR_CTA;\n        case grpc::StatusCode::UNAUTHENTICATED:\n          response.set_message_txt(status.error_message());\n          return cta::xrd::Response::RSP_ERR_USER;\n        // something went wrong in the gRPC code, throw an exception\n        default:\n          throw std::runtime_error(\"gRPC call failed internally. Error code: \" + std::to_string(status.error_code()) + \" Error message: \" + status.error_message());\n      }\n    }\n  }\nprivate:\n  std::string endpoint;\n  std::unique_ptr<cta::xrd::CtaRpc::Stub> client_stub;\n  std::string token_path;\n};\n\nclass WFEXrdClient : public WFEClient {\npublic:\n  WFEXrdClient(std::string endpoint, std::string resource, XrdSsiPb::Config &config) : service(XrdSsiPbServiceType(endpoint, resource, config)) {}\n  cta::xrd::Response::ResponseType send(const cta::xrd::Request& request, cta::xrd::Response& response) override {\n    try {\n      service.Send(request, response, false);\n      return response.type();\n    } catch (std::runtime_error& err) {\n      eos_static_err(\"Could not send request to outside service. Retrying with DNS cache refresh.\");\n      service.Send(request, response, true);\n      return response.type();\n    }\n  }\nprivate:\n  XrdSsiPbServiceType service;\n};\n\nstd::unique_ptr<WFEClient>\nCreateRequestSender(bool protowfusegrpc, std::string endpoint, std::string ssi_resource, std::optional<std::string> root_certs, std::string token_path, bool protowfusegrpctls) {\n  if (protowfusegrpc) {\n    return std::make_unique<WFEGrpcClient>(endpoint, root_certs, token_path, protowfusegrpctls);\n  } else {\n    XrdSsiPb::Config config;\n\n    if (getenv(\"XRDDEBUG\")) {\n      config.set(\"log\", \"all\");\n    } else {\n      config.set(\"log\", \"info\");\n    }\n\n    config.set(\"request_timeout\", \"120\");\n    return std::make_unique<WFEXrdClient>(endpoint, ssi_resource, config);\n  }\n}\n"
  },
  {
    "path": "common/WaitInterval.hh",
    "content": "// ----------------------------------------------------------------------\n// File: WaitInterval.hh\n// Author: Gianmaria Del Monte - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/AssistedThread.hh\"\n#include \"common/utils/RandUtils.hh\"\n#include <chrono>\n#include <condition_variable>\n#include <mutex>\n\n// A common pattern in the EOS code for long running threads is:\n//   while (!assistant.terminationRequested()) {\n//     doSomeOperation();\n//     assistant.wait_for(sleep_time);\n//   }\n//\n// Another thread can modify the sleep_time.\n// Let elapsed_time be the time elapsed when it was first called\n// the sleep time, with elapsed_time <= sleep_time.\n//\n// In this case the wanted behavior would be:\n// - if new_sleep_time > elapsed_time\n//   wait only the new_sleep_time - elapsed_time\n// - if new_sleep_time <= elapsed_time\n//   no need to wait\n//\n// This class can then be used as:\n// Thread 1:\n//   while (!assistant.terminationRequested()) {\n//     doSomeOperation();\n//     interval.wait(assistant);\n//   }\n//\n// Thread 2:\n// interval.set(new_value)\n\nclass WaitInterval\n{\npublic:\n  WaitInterval(const uint64_t interval_sec) : mIntervalSec(\n      interval_sec)\n  {\n  };\n\n  WaitInterval(const WaitInterval&) = delete;\n\n  WaitInterval(WaitInterval&&) noexcept = delete;\n\n  WaitInterval& operator=(const WaitInterval&) = delete;\n\n  WaitInterval& operator=(WaitInterval&&) noexcept = delete;\n\n  uint64_t get() const\n  {\n    const std::lock_guard lock(mMutex);\n    return mIntervalSec;\n  }\n\n  void set(const uint64_t new_value_sec)\n  {\n    const std::lock_guard lock(mMutex);\n\n    if (mIntervalSec != new_value_sec) {\n      mIntervalSec = new_value_sec;\n      mCv.notify_all();\n    }\n  }\n\n  //------------------------------------------------------------------------------\n  //! wait pauses the execution of the current thread until an amout of <interval>\n  //! seconds has passed or the thread has been terminated.\n  //! The wait time <interval> can be changed while one thread is sleeping. If\n  //! this happens, the wait time will change accordingly.\n  //! If the <interval> value is zero and the flag <zero_forever> is true\n  //! the thread will sleep forever. If the flag is false (default bahaviour),\n  //! the thread will not sleep.\n  //!\n  //! @param assistant thread running the job\n  //! @param zero_forever if true indicates that if the <interval> value is 0\n  //!                     the sleep time is forever\n  //------------------------------------------------------------------------------\n  void wait(ThreadAssistant& assistant, const bool zero_forever = false) const\n  {\n    registerNotifyCallback(assistant);\n    std::unique_lock lock(mMutex);\n    wait_time(assistant, lock, mIntervalSec, zero_forever);\n  }\n\n  //------------------------------------------------------------------------------\n  //! wait_if_zero pauses the execution of the current thread forever. The only\n  //! way of resuming the thread is to set a value for the interval different\n  //! from zero, or requesting the termination of the thread.\n  //!\n  //! @param assistant thread running the job\n  //! @return true if the thread waited, false if it didn't wait\n  //------------------------------------------------------------------------------\n  bool wait_if_zero(ThreadAssistant& assistant) const\n  {\n    registerNotifyCallback(assistant);\n    std::unique_lock lock(mMutex);\n\n    if (mIntervalSec == 0) {\n      mCv.wait(lock);\n      return true;\n    }\n\n    return false;\n  }\n\n  //------------------------------------------------------------------------------\n  //! random_wait is like wait, but it waits a random time from 1 to <interval>\n  //! seconds.\n  //!\n  //! @param assistant thread running the job\n  //! @param zero_forever if true indicates that if the <interval> value is 0\n  //!                     the sleep time is forever\n  //------------------------------------------------------------------------------\n  void random_wait(ThreadAssistant& assistant,\n                   const bool zero_forever = false,\n                   const std::function<void(uint64_t)> callback = nullptr) const\n  {\n    registerNotifyCallback(assistant);\n    uint64_t random_sleep_time = 0;\n    std::unique_lock lock(mMutex);\n\n    if (mIntervalSec != 0) {\n      random_sleep_time = eos::common::getRandom(0ul, mIntervalSec);\n    }\n\n    if (callback) {\n      callback(random_sleep_time);\n    }\n\n    wait_time(assistant, lock, random_sleep_time, zero_forever);\n  }\n\nprivate:\n  uint64_t mIntervalSec;\n  mutable std::condition_variable mCv;\n  mutable std::mutex mMutex;\n  mutable std::once_flag mRegistered;\n\n  void registerNotifyCallback(ThreadAssistant& assistant) const\n  {\n    std::call_once(mRegistered, [this](ThreadAssistant & assistant) {\n      assistant.registerCallback([this]() {\n        mCv.notify_all();\n      });\n    }, assistant);\n  }\n\n  void wait_time(ThreadAssistant& assistant, std::unique_lock<std::mutex>& lock,\n                 uint64_t time,\n                 bool zero_forever = false) const\n  {\n    while (true) {\n      if (time == 0) {\n        if (zero_forever) {\n          mCv.wait(lock);\n          time = mIntervalSec;\n        } else {\n          return;\n        }\n      }\n\n      const auto start = std::chrono::steady_clock::now();\n      uint64_t remaining = time;\n\n      while (remaining > 0) {\n        auto status = mCv.wait_for(lock, std::chrono::seconds(remaining));\n\n        if (status == std::cv_status::timeout || assistant.terminationRequested()) {\n          return;\n        }\n\n        // At this time the internal value mIntervalSec was changed\n        // by another thread\n        time = mIntervalSec;\n\n        if (time == 0) {\n          break;\n        }\n\n        const auto elapsed = std::chrono::steady_clock::now() - start;\n        remaining = time - std::chrono::duration_cast<std::chrono::seconds>\n                    (elapsed).count();\n      }\n\n      if (time > 0) {\n        return;\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "common/WebNotify.cc",
    "content": "//------------------------------------------------------------------------------\n// File: WebNotify.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"common/Namespace.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/WebNotify.hh\"\n#include <iostream>\n#include <memory>\n\n// curl\n#include <curl/curl.h>\n#include <curl/easy.h>\n#include <json/json.h>\n\n// active MQ\n#include <cms/Connection.h>\n#include <cms/ConnectionFactory.h>\n#include <cms/Session.h>\n#include <cms/TextMessage.h>\n#include <cms/MessageProducer.h>\n#include <decaf/lang/Thread.h>\n#include <decaf/util/UUID.h>\n#include <decaf/internal/util/concurrent/Threading.h>\n#include <activemq/library/ActiveMQCPP.h>\n\n// grpc\n#ifdef EOS_GRPC\n\n// These pragmas are to suppress warnings from grpc headers coming from upstream code\n// These pragmas can be removed once the warning disappears\n// Please do not include any new `#include` in this block!\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n\n#include <grpc++/grpc++.h>\n#include \"proto/Rpc.grpc.pb.h\"\n\n#pragma GCC diagnostic pop\n\nusing eos::rpc::Eos;\nusing eos::rpc::NotificationRequest;\nusing eos::rpc::NotificationResponse;\n/*#include <grpc/grpc.h>\n#include <grpc/grpc_security.h>\n#include <grpcpp/grpcpp.h>\n#include <grpcpp/channel.h>\n#include <grpcpp/client_context.h>\n#include <grpcpp/create_channel.h>\n#include <grpcpp/support/channel_arguments.h>\n#include <grpc/impl/codegen/grpc_types.h>\n*/\n#endif\n\n// QClient\n#include <qclient/QClient.hh>\n\nusing namespace cms;\nusing namespace decaf::lang;\nusing namespace std;\n\nEOSCOMMONNAMESPACE_BEGIN;\n\n\nbool WebNotify::Notify(const std::string& protocol,\n                       const std::string& uri,\n                       const std::string& sport,\n                       const std::string& channel,\n                       const std::string& message,\n                       const std::string& stimeout)\n{\n  WebNotify notify;\n  try {\n    int timeoutMs = stimeout.empty() ? 0 : std::stoi(stimeout);\n    int port = sport.empty() ? 0 : std::stoi(sport);\n    eos_static_debug(\"protocol='%s'\", protocol.c_str());\n    if (protocol == \"http\")\n      return notify.sendHttpPostNotification(uri, message, timeoutMs);\n    if (protocol == \"grpc\")\n      return notify.sendGrpcNotification(uri, message, timeoutMs);\n    if (protocol == \"redis\")\n      return notify.sendQClientNotification(uri, port, channel, message, timeoutMs, true);\n    if (protocol == \"qclient\")\n      return notify.sendQClientNotification(uri, port, channel, message, timeoutMs, false);\n    if (protocol == \"amq\")\n      return notify.sendActiveMQNotification(uri, channel, message, timeoutMs);\n    eos_static_err(\"msg=\\\"unsupported notification protocol specified\\\" protocol=\\\"%s\\\"\", protocol.c_str());\n  } catch (const std::exception& e) {\n    eos_static_err(\"msg=\\\"invalid numeric input\\\" error=\\\"%s\\\"\", e.what());\n  }\n  \n  return false;\n}\n\nbool WebNotify::sendHttpPostNotification(const std::string& url, const std::string& message, long timeoutMs) {\n  CURL* curl = curl_easy_init();\n  if (!curl) return false;\n  \n  struct curl_slist* headers = nullptr;\n  headers = curl_slist_append(headers, \"Content-Type: application/json\");\n  \n  std::string jsonPayload;\n  if (!message.empty() && message.front() == '{') {\n    jsonPayload = message;\n  } else {\n    jsonPayload = \"{\\\"message\\\": \\\"\" + message + \"\\\"}\";\n  }\n  curl_easy_setopt(curl, CURLOPT_URL, url.c_str());\n  curl_easy_setopt(curl, CURLOPT_POST, 1L);\n  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonPayload.c_str());\n  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);\n  curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeoutMs);\n  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NoOpCallback);\n\n  CURLcode res = curl_easy_perform(curl);\n  curl_slist_free_all(headers);\n  curl_easy_cleanup(curl);\n  \n  return (res == CURLE_OK);\n}\n\nbool WebNotify::sendActiveMQNotification(const std::string& brokerURI, const std::string& queueName, const std::string& messageText, int timeoutMs) {\n  static std::once_flag initFlag;\n  \n  std::call_once(initFlag, [] {\n\t\t\t     activemq::library::ActiveMQCPP::initializeLibrary();\n\t\t\t   });\n  \n  try {\n    // Construct broker URI with connection timeout\n    std::ostringstream fullBrokerURI;\n    fullBrokerURI << brokerURI;\n    if (brokerURI.find('?') == std::string::npos) {\n      fullBrokerURI << \"?\";\n    } else {\n      fullBrokerURI << \"&\";\n    }\n    fullBrokerURI\n      << \"connection.requestTimeout=\" << timeoutMs\n      << \"&wireFormat.maxInactivityDuration=\" << timeoutMs\n      << \"&wireFormat.maxInactivityDurationInitialDelay=\" << timeoutMs \n      << \"&transport.maxReconnectAttempts=0\";\n    // Create a ConnectionFactory\n    std::unique_ptr<ConnectionFactory> connectionFactory(ConnectionFactory::createCMSConnectionFactory(fullBrokerURI.str()));\n    \n    // Create a Connection\n    std::unique_ptr<Connection> connection(connectionFactory->createConnection());\n    connection->start();\n    \n    // Create a Session\n    std::unique_ptr<Session> session(connection->createSession(Session::AUTO_ACKNOWLEDGE));\n    \n    // Create the Destination (queue)\n    std::unique_ptr<Destination> destination(session->createQueue(queueName));\n    \n    // Create a MessageProducer from the Session to the Topic or Queue\n    std::unique_ptr<MessageProducer> producer(session->createProducer(destination.get()));\n    producer->setDeliveryMode(DeliveryMode::NON_PERSISTENT);\n    \n    // Create the message and send it\n        std::unique_ptr<TextMessage> message(session->createTextMessage(messageText));\n        producer->send(message.get());\n        return true;\n  } catch (const CMSException& e) {\n    std::cerr << \"CMSException: \";\n    e.printStackTrace();\n    return false;\n  } catch (const std::exception& e) {\n    eos_static_err(\"exception='%s'\", e.what());\n    return false;\n  } catch (...) {\n    eos_static_err(\"Unknown exception occurred while sending ActiveMQ notification.\");\n    return false;\n  }\n}\n\nbool WebNotify::sendGrpcNotification(const std::string& target, const std::string& message, int timeoutMs)\n{\n#ifdef EOS_GRPC\n    grpc::ChannelArguments ch_args;\n    // This is the key: set connection timeout (in milliseconds)\n    ch_args.SetInt(\"grpc.client_channel_backup_poll_interval_ms\", timeoutMs);\n    ch_args.SetInt(GRPC_ARG_MAX_RECEIVE_MESSAGE_LENGTH, -1); // Unlimited if needed\n\n    auto deadline = std::chrono::system_clock::now() + std::chrono::milliseconds(timeoutMs);\n    std::shared_ptr<grpc::Channel> channel = grpc::CreateCustomChannel(\n        target,\n        grpc::InsecureChannelCredentials(),\n        ch_args\n    );\n\n    auto stub = eos::rpc::Eos::NewStub(channel);\n\n    NotificationRequest request;\n    request.set_message(message);\n\n    grpc::ClientContext context;\n    context.set_deadline(deadline);\n\n    NotificationResponse response;\n    grpc::Status status = stub->Notify(&context, request, &response);\n    if (status.ok()) {\n      eos_static_debug(\"gRPC call succeeded\");\n      return response.success();\n    } else {\n      eos_static_err(\"msg=\\\"gRPC call failed\\\" errc=%d errmsg='%s'\", status.error_code(), status.error_message().c_str());\n      return false;\n    }\n#else\n    return false;\n#endif\n}\n\nbool WebNotify::sendQClientNotification(const std::string& hostname, int port,\n                                        const std::string& channel,\n                                        const std::string& message,\n                                        int timeoutMs,\n\t\t\t\t\tbool push) {\n  using namespace qclient;\n\n    try {\n        // Connect with socket timeout\n        QClient client{hostname, port, {}};\n   \n        // Send PUBLISH command\n\tstd::string method = push ?\"RPUSH\":\"PUBLISH\";\n\tauto publish = client.exec(method, channel, message);\n\tqclient::redisReplyPtr reply = publish.get();\n\tif (reply && reply->type == REDIS_REPLY_INTEGER && reply->integer != 0) {\n\t  eos_static_debug(\"msg=\\\"%s\\\" %s=%d\", push ? \"pushed to list\" : \"published\", push ? \"length\" : \"subscribers\", reply->integer);\n\t  return true;\n\t} else {\n\t  eos_static_err(\"msg=\\\"unexpected or null reply from QuarkDB/REDIS\")\n\t  return false;\n\t}\n    } catch (const std::exception& ex) {\n      eos_static_err(\"msg=\\\"QuarkDB/REDIS connection or command error\\\" msg='%s'\", ex.what());\n      return false;\n    }\n}\n\nEOSCOMMONNAMESPACE_END;\n"
  },
  {
    "path": "common/WebNotify.hh",
    "content": "// ----------------------------------------------------------------------\n// File: WebNotify.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   WebNotify.hh\n *\n * @brief  Class handling web notification requests\n *\n *\n */\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include \"common/VirtualIdentity.hh\"\n#include \"common/RWMutex.hh\"\n#include <map>\n#include <string>\n\nEOSCOMMONNAMESPACE_BEGIN;\n\nclass WebNotify {\n\npublic:\n\n  WebNotify() { }\n\n  static bool Notify(const std::string& protocol,\n\t\t     const std::string& uri,\n\t\t     const std::string& port,\n\t\t     const std::string& channel,\n\t\t     const std::string& message,\n\t\t     const std::string& timeout);\n  \n  virtual ~WebNotify() {}\n  static size_t NoOpCallback(void* contents, size_t size, size_t nmemb, void* userp) {\n    return size * nmemb;  // Tell libcurl we \"handled\" the data\n  }\n  bool sendHttpPostNotification(const std::string& url, const std::string& message, long timeoutMs = 2000);\n  bool sendActiveMQNotification(const std::string& brokerURI, const std::string& queueName, const std::string& messageText, int timeoutMs = 2000);\n  bool sendGrpcNotification(const std::string& target, const std::string& message, int timeoutMs = 2000);\n  bool sendQClientNotification(const std::string& hostname, int port, const std::string& channel, const std::string& message, int timeoutMs = 2000, bool push=false);\n  \nprivate:\n};\n\nEOSCOMMONNAMESPACE_END;\n"
  },
  {
    "path": "common/XattrCompat.hh",
    "content": "// ----------------------------------------------------------------------\n//! @file: XattrCompat.hh\n//! @author: Georgios Bitzes <georgios.bitzes@cern.ch>\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#ifndef ENOATTR\n#define ENOATTR ENODATA\n#endif\n\n#if __has_include(<sys/xattr.h>)\n#include <sys/xattr.h>\n#elif __has_include(<attr/xattr.h>)\n#include <attr/xattr.h>\n#else\n#error \"Could not find xattr.h header!\"\n#endif\n"
  },
  {
    "path": "common/XrdConnPool.cc",
    "content": "//------------------------------------------------------------------------------\n// File: XrdConnPool.cc\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/XrdConnPool.hh\"\n#include <limits>\n#include <sstream>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nXrdConnPool::XrdConnPool(bool is_enabled, uint32_t max_size):\n  mIsEnabled(is_enabled), mMaxSize(max_size)\n{\n  if (!mIsEnabled && getenv(\"EOS_XRD_USE_CONNECTION_POOL\")) {\n    mIsEnabled = true;\n\n    if (getenv(\"EOS_XRD_CONNECTION_POOL_SIZE\")) {\n      max_size = strtoul(getenv(\"EOS_XRD_CONNECTION_POOL_SIZE\"), 0, 10);\n    }\n\n    if (max_size < 1) {\n      eos_warning(\"%s\", \"msg=\\\"wrong EOS_XRD_CONNECTION_POOL_SIZE, forcing \"\n                  \"max size to 1\\\"\");\n      max_size = 1;\n    }\n\n    if (max_size > 1024) {\n      eos_warning(\"%s\", \"msg=\\\"too big EOS_XRD_CONNECTION_POOL_SIZE, forcing \"\n                  \"max size to 1024\\\"\");\n      max_size = 1024;\n    }\n\n    mMaxSize = max_size;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Assign new connection from the pool to the given URL. What this actually\n// means is updating the username used in the URL when connecting to the\n// XRootD server.\n//------------------------------------------------------------------------------\nuint32_t\nXrdConnPool::AssignConnection(XrdCl::URL& url)\n{\n  uint32_t conn_id {0ull};\n\n  if (!mIsEnabled) {\n    return conn_id;\n  }\n\n  bool found {false};\n  uint32_t best_conn_id {1};\n  uint32_t best_conn_val {std::numeric_limits<uint32_t>::max()};\n  std::string target_host = url.GetHostName();\n  std::unique_lock<std::mutex> scope_lock(mPoolMutex);\n  // Map of connection id and score for current host\n  auto& map_id_score = mConnPool[target_host];\n\n  for (auto& elem : map_id_score) {\n    if (elem.second < best_conn_val) {\n      best_conn_id = elem.first;\n      best_conn_val = elem.second;\n    }\n\n    if (elem.second == 0) {\n      ++elem.second;\n      conn_id = elem.first;\n      found = true;\n      break;\n    }\n  }\n\n  if (!found) {\n    if (map_id_score.size() >= mMaxSize) {\n      // Share the least busy connection\n      ++map_id_score[best_conn_id];\n      conn_id = best_conn_id;\n      eos_static_debug(\"msg=\\\"connection pool limit reached - using %u/%u \"\n                       \"connections\\\"\", map_id_score.size(), mMaxSize);\n    } else {\n      conn_id = map_id_score.size() + 1;\n      map_id_score[conn_id] = 1;\n    }\n  }\n\n  if (conn_id) {\n    url.SetUserName(std::to_string(conn_id));\n  }\n\n  return conn_id;\n}\n\n//------------------------------------------------------------------------------\n// Release a connection and update the status of the pool\n//------------------------------------------------------------------------------\nvoid\nXrdConnPool::ReleaseConnection(const XrdCl::URL& url)\n{\n  if (!mIsEnabled) {\n    return;\n  }\n\n  uint32_t conn_id {0ull};\n\n  try {\n    conn_id = std::stoul(url.GetUserName());\n  } catch (const std::exception& e) {\n    // ignore\n  }\n\n  if (conn_id) {\n    std::unique_lock<std::mutex> scope_lock(mPoolMutex);\n    auto it = mConnPool.find(url.GetHostName());\n\n    if (it != mConnPool.end()) {\n      auto& map_id_score = it->second;\n\n      if (map_id_score[conn_id] >= 1) {\n        --map_id_score[conn_id];\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Dump the status of the connection pool to the given string\n//------------------------------------------------------------------------------\nvoid\nXrdConnPool::Dump(std::string& out) const\n{\n  std::ostringstream oss;\n  oss << \"[connection-pool-dump]\" << std::endl;\n\n  for (auto it = mConnPool.begin(); it != mConnPool.end(); ++it) {\n    for (auto fit = it->second.begin(); fit != it->second.end(); ++fit) {\n      oss << \"[connection-pool] host=\" << it->first << \" id=\"\n          << fit->first << \" usage=\" << fit->second << std::endl;\n    }\n  }\n\n  out = oss.str();\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/XrdConnPool.hh",
    "content": "//------------------------------------------------------------------------------\n// File: XrdConnPool.hh\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include <XrdCl/XrdClURL.hh>\n#include <mutex>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class XrdConnPool help in creating a pool of xrootd connections that can\n//! be reused and allocate the least congested connection to a new request.\n//------------------------------------------------------------------------------\nclass XrdConnPool: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param is_enabled if true connection pool is enabled\n  //! @param max_size default max_size\n  //----------------------------------------------------------------------------\n  XrdConnPool(bool is_enabled = false, uint32_t max_size = 1024);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~XrdConnPool() = default;\n\n  //----------------------------------------------------------------------------\n  //! Assign new connection from the pool to the given URL. What this actually\n  //! means is updating the username used in the URL when connecting to the\n  //! XRootD server.\n  //!\n  //! @param url given url\n  //!\n  //! @return 0 if no connection id assigned, otherwise the value of the id\n  //----------------------------------------------------------------------------\n  uint32_t AssignConnection(XrdCl::URL& url);\n\n  //----------------------------------------------------------------------------\n  //! Release a connection and update the status of the pool\n  //!\n  //! @param url given url\n  //----------------------------------------------------------------------------\n  void ReleaseConnection(const XrdCl::URL& url);\n\n  //----------------------------------------------------------------------------\n  //! Dump the status of the connection pool to the given string\n  //!\n  //! @param out string containing the result\n  //----------------------------------------------------------------------------\n  void Dump(std::string& out) const;\n\nprivate:\n  bool mIsEnabled; ///< Mark if connection pool is enabled\n  uint32_t mMaxSize; ///< Maximum size of the connection pool\n  std::map<std::string, std::map<uint32_t, uint32_t>> mConnPool;\n  std::mutex mPoolMutex; ///< Mutex protecting access to the pool\n};\n\n//------------------------------------------------------------------------------\n//! Class XrdConnIdHelper RAAI helper to automatically assign and release\n//! connection ids to the pool.\n//! @note Needs to have the same lifetime as the XrdCl::File object that uses\n//! the url.\n//------------------------------------------------------------------------------\nclass XrdConnIdHelper final\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  XrdConnIdHelper(XrdConnPool& pool, XrdCl::URL& url):\n    mPool(pool)\n  {\n    mConnId = mPool.AssignConnection(url);\n    mUrl = url;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~XrdConnIdHelper()\n  {\n    if (mConnId) {\n      mPool.ReleaseConnection(mUrl);\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if new connection allocated to URL\n  //----------------------------------------------------------------------------\n  bool HasNewConnection() const\n  {\n    return (mConnId != 0ull);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get allocated connection id\n  //----------------------------------------------------------------------------\n  uint32_t GetId() const\n  {\n    return mConnId;\n  }\n\nprivate:\n  uint32_t mConnId; ///< Allocated connection id, 0 if none allocated\n  XrdConnPool& mPool; ///< Reference to connection pool\n  XrdCl::URL mUrl; ///< URL corresponding to the connection id\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/XrdErrorMap.cc",
    "content": "// ----------------------------------------------------------------------\n//! @file: XrdErrMap.cc\n//! @author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------\n//! @brief  XRootD error to errno translation\n//------------------------------------------------------------------------\n#include \"common/XrdErrorMap.hh\"\n#include \"XProtocol/XProtocol.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\nint\nerror_retc_map(int retc)\n{\n  if( retc >= kXR_ArgInvalid /*the lowest xrootd error code*/ )\n    errno = XProtocol::toErrno( retc );\n  else\n    errno = retc;\n\n  if (retc) {\n    return -1;\n  }\n\n  return 0;\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/XrdErrorMap.hh",
    "content": "// ----------------------------------------------------------------------\n//! @file: XrdErrMap.hh\n//! @author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------\n//! @brief  XRootD error to errno translation\n//------------------------------------------------------------------------\n#pragma once\n#include \"common/Namespace.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\nint error_retc_map(int retc);\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/async/ExecutorMgr.hh",
    "content": "// ----------------------------------------------------------------------\n// File: OpaqueFuture.hh\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n#include <variant>\n\n#include \"common/async/OpaqueFuture.hh\"\n#include \"common/ThreadPool.hh\"\n#include <folly/executors/IOThreadPoolExecutor.h>\n#include <folly/executors/CPUThreadPoolExecutor.h>\n\nnamespace eos::common\n{\n\nnamespace detail\n{\n\n// A function that runs a given function via a given folly executor. We wrap the\n// the result type in a type erased OpaqueFuture to allow for interop with std::future\n// and folly::Future, we also transform the type tag folly::Unit to a void to interop\n// with std::future\ntemplate <typename F>\nauto\nexecVia(folly::ThreadPoolExecutor* executor, F&& f)\n-> std::enable_if_t < !folly::isFuture<std::invoke_result_t<F>>::value,\nOpaqueFuture<std::invoke_result_t<F> >> {\n  // Folly's void futures are mapped to a folly::Unit empty type\n  // since this is not void, do this mapping where in we return\n  // an OpaqueFuture of <void> in case the function returns a\n  // void instead of a folly::Unit which we cannot work with.\n  using ResultType = std::invoke_result_t<F>;\n  // a type holding either result of F or folly::Unit if void\n  using follyType = folly::lift_unit_t<ResultType>;\n\n  folly::Promise<follyType> promise;\n  auto fut = promise.getFuture();\n  executor->add([promise = std::move(promise),\n  f = std::move(f)]() mutable {\n    promise.setWith(std::move(f));\n  });\n  return OpaqueFuture<ResultType>(std::move(fut));\n}\n\n// A function that runs a given function via eos::common::Threadpool and returns an opaque future\n// The task is wrapped as packeged_task over the std::function<void> variant as\n// lambdas don't decompose to this signature. Since the folly variant of this\n// function takes a folly::Function, we use the packaged task and transfer ownership.\n// We wrap the the result type in a type erased OpaqueFuture to allow for interop with folly::future\ntemplate <typename F>\nauto\nexecVia(eos::common::ThreadPool* threadpool, F&& f)\n-> OpaqueFuture<std::invoke_result_t<F>> {\n  using ResultType = std::invoke_result_t<F>;\n  auto task = std::make_shared<std::packaged_task<ResultType()>>(std::move(f));\n  auto fut = threadpool->PushTask(std::move(task));\n  return OpaqueFuture<ResultType>(std::move(fut));\n}\n\ninline void ShutdownExecutor(folly::ThreadPoolExecutor* executor)\n{\n  executor->stop();\n}\n\ninline void ShutdownExecutor(eos::common::ThreadPool* threadpool)\n{\n  threadpool->Stop();\n}\n\ninline size_t GetQueueSize(folly::ThreadPoolExecutor* executor)\n{\n  return executor->getPendingTaskCount();\n}\n\ninline size_t GetQueueSize(eos::common::ThreadPool* threadpool)\n{\n  return threadpool->GetQueueSize();\n}\n\n} // detail\n\nenum class ExecutorType {\n  kThreadPool,\n  kFollyExecutor,\n  kFollyIOExecutor,\n};\n\ninline constexpr ExecutorType\nGetExecutorType(std::string_view exec_type)\n{\n  if (exec_type == \"folly\" || exec_type == \"follyCPU\") {\n    return ExecutorType::kFollyExecutor;\n  } else if (exec_type == \"follyIO\") {\n    return ExecutorType::kFollyIOExecutor;\n  }\n\n  // std is the default\n  return ExecutorType::kThreadPool;\n}\n\n/*\n * A class to hold folly or eos::common::threadpool executors\n * while it would have been easy to inherit from folly::ThreadPoolExecutor and make our\n * threadpool use this, we are exposed to potential folly impl bugs. This is to\n * get around that fact. Also we have two disjoint executor like implementations, which\n * doesn't make that much sense to combine under a single one.\n * folly::executors take a folly::function which\n * is a non copyable type in contrast to std::function. So we can avoid this by\n * templating on the function type, so that the various executors can be their\n * own variant of a callable/function/packaged_task etc.\n*/\nstatic constexpr unsigned int MIN_THREADPOOL_SIZE = 2;\n\nclass ExecutorMgr\n{\npublic:\n\n  template <typename F>\n  using future_result_t = OpaqueFuture<std::invoke_result_t<F>>;\n\n  template <typename F>\n  auto\n  PushTask(F&& f) -> future_result_t<F> {\n\n    if (auto executor = std::get_if<std::shared_ptr<folly::ThreadPoolExecutor>>(&mExecutor))\n    {\n      return detail::execVia(executor->get(), std::forward<F>(f));\n    } else if (auto threadpool = std::get_if<std::shared_ptr<eos::common::ThreadPool>>(&mExecutor))\n    {\n      return detail::execVia(threadpool->get(), f);\n    } else {\n      throw std::runtime_error(\"Invalid executor type\");\n    }\n\n  }\n\n  void Shutdown()\n  {\n    std::visit([](auto && executor) {\n      detail::ShutdownExecutor(executor.get());\n    }, mExecutor);\n  }\n\n  size_t GetQueueSize() const\n  {\n    return std::visit([](auto && executor) {\n      return detail::GetQueueSize(executor.get());\n    }, mExecutor);\n  }\n\n  template <typename T>\n  constexpr bool holdsType() const\n  {\n    return std::holds_alternative<T>(mExecutor);\n  }\n\n  constexpr bool IsFollyExecutor() const\n  {\n    return holdsType<std::shared_ptr<folly::ThreadPoolExecutor>>();\n  }\n\n  constexpr bool IsThreadPool() const\n  {\n    return holdsType<std::shared_ptr<eos::common::ThreadPool>>();\n  }\n\n  ExecutorMgr(ExecutorType type, size_t num_threads)\n  {\n    switch (type) {\n    case ExecutorType::kThreadPool:\n      mExecutor = std::make_shared<eos::common::ThreadPool>(MIN_THREADPOOL_SIZE,\n                  num_threads);\n      break;\n\n    case ExecutorType::kFollyExecutor:\n      mExecutor = std::make_shared<folly::CPUThreadPoolExecutor>(num_threads);\n      break;\n\n    case ExecutorType::kFollyIOExecutor:\n      mExecutor = std::make_shared<folly::IOThreadPoolExecutor>(num_threads);\n    }\n  }\n\n  template <typename... Args>\n  ExecutorMgr(ExecutorType type, size_t min_threads, Args... args)\n  {\n    switch (type) {\n    case ExecutorType::kThreadPool:\n      mExecutor = std::make_shared<eos::common::ThreadPool>(min_threads, args...);\n      break;\n\n    default:\n      ExecutorMgr(type, min_threads);\n    }\n  }\n\n  template <typename... Args>\n  ExecutorMgr(std::string_view executor_type, size_t num_threads, Args... args) :\n    ExecutorMgr(GetExecutorType(executor_type), num_threads, args...) {}\n\n  ExecutorMgr(std::shared_ptr<folly::ThreadPoolExecutor> executor) :\n    mExecutor(executor) {}\n\n  ExecutorMgr(std::shared_ptr<eos::common::ThreadPool> threadpool) :\n    mExecutor(threadpool) {}\n\n  ~ExecutorMgr() = default;\n\nprivate:\n\n  std::variant<std::shared_ptr<folly::ThreadPoolExecutor>,\n      std::shared_ptr<eos::common::ThreadPool>> mExecutor;\n};\n\n\n} // eos::common\n"
  },
  {
    "path": "common/async/OpaqueFuture.hh",
    "content": "// ----------------------------------------------------------------------\n// File: OpaqueFuture.hh\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n#include <folly/futures/Future.h>\n#include <future>\n#include <type_traits>\n\nnamespace eos::common\n{\n\nnamespace detail\n{\ntemplate <typename, typename = void>\nstruct has_isReady : std::false_type {};\n\ntemplate <typename Fut>\nstruct has_isReady<Fut, std::void_t<decltype(std::declval<Fut>().isReady())>> :\n    std::true_type {};\n\ntemplate <typename, typename = void>\nstruct has_cancel : std::false_type {};\n\ntemplate <typename Fut>\nstruct has_cancel<Fut, std::void_t<decltype(std::declval<Fut>().cancel())>> :\n    std::true_type {};\n\n// some tests to assert this works as expected; these will fail compilation in\n// case our assertions are wrong but are thrown out from the actual object code\nstatic_assert(has_isReady<folly::Future<int>>::value,\n              \"folly::Future implements isReady\");\nstatic_assert(has_isReady<folly::SemiFuture<int>>::value,\n              \"folly::SemiFuture implements isReady\");\nstatic_assert(!has_isReady<std::future<int>>::value,\n              \"std::future doesn't implement isReady\");\n}\n\n/* A type erased future holder to help interop std::future\n * and folly::Future types. This mainly allows for holding a vector\n * of futures or in situations like classes with virtual Functions\n * which cannot be templated\n */\ntemplate <typename T>\nclass OpaqueFuture\n{\npublic:\n  T\n  getValue()\n  {\n    return fut_holder->getValue();\n  }\n\n  bool ready()\n  {\n    return fut_holder->ready();\n  }\n\n  bool valid()\n  {\n    return fut_holder->valid();\n  }\n\n  void wait()\n  {\n    return fut_holder->wait();\n  }\n\n  void cancel()\n  {\n    return fut_holder->cancel();\n  }\n\n  template <typename F>\n  OpaqueFuture(F&& fut) : fut_holder(std::make_unique<future_holder<F>>(std::move(\n                                         fut))) {}\n\nprivate:\n  struct base_future_holder {\n    virtual ~base_future_holder() = default;\n    virtual T getValue() = 0;\n    virtual bool valid() = 0;\n    virtual bool ready() = 0;\n    virtual void wait() = 0;\n    virtual void cancel() = 0;\n  };\n\n  template <typename F>\n  struct future_holder : public base_future_holder {\n    future_holder(F&& f) : fut_(std::move(f)) {}\n    T getValue() override\n    {\n      // This is a hack for the fact that folly::Future<Unit> is\n      // not a void type but we're behaving as though it is!\n      // So we need to realize the future but throw away the unit future return\n      if constexpr(std::is_same_v<T, void>) {\n        std::move(fut_).get();\n      } else {\n        return std::move(fut_).get();\n      }\n    }\n\n    bool valid() override\n    {\n      return fut_.valid();\n    }\n\n    void wait() override\n    {\n      fut_.wait();\n    }\n\n\n    bool ready() override\n    {\n      if constexpr(detail::has_isReady<F>::value) {\n        return fut_.isReady();\n      } else if constexpr(std::is_same_v<F, std::future<T>>) {\n        return fut_.wait_for(std::chrono::seconds(0)) == std::future_status::ready;\n      }\n    }\n\n    void cancel() override\n    {\n      if constexpr(detail::has_cancel<F>::value) {\n        fut_.cancel();\n      }\n    }\n\n    F fut_;\n  };\n\n  std::unique_ptr<base_future_holder> fut_holder;\n};\n\n\n\n} // eos::common\n\n\n"
  },
  {
    "path": "common/blake3/README.md",
    "content": "# Taken from https://github.com/BLAKE3-team/BLAKE3\n\nThe official C implementation of BLAKE3.\n\n# Example\n\nAn example program that hashes bytes from standard input and prints the\nresult:\n\n```c\n#include \"blake3.h\"\n#include <stdio.h>\n#include <unistd.h>\n\nint main() {\n  // Initialize the hasher.\n  blake3_hasher hasher;\n  blake3_hasher_init(&hasher);\n\n  // Read input bytes from stdin.\n  unsigned char buf[65536];\n  ssize_t n;\n  while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {\n    blake3_hasher_update(&hasher, buf, n);\n  }\n\n  // Finalize the hash. BLAKE3_OUT_LEN is the default output length, 32 bytes.\n  uint8_t output[BLAKE3_OUT_LEN];\n  blake3_hasher_finalize(&hasher, output, BLAKE3_OUT_LEN);\n\n  // Print the hash as hexadecimal.\n  for (size_t i = 0; i < BLAKE3_OUT_LEN; i++) {\n    printf(\"%02x\", output[i]);\n  }\n  printf(\"\\n\");\n  return 0;\n}\n```\n\nThe code above is included in this directory as `example.c`. If you're\non x86\\_64 with a Unix-like OS, you can compile a working binary like\nthis:\n\n```bash\ngcc -O3 -o example example.c blake3.c blake3_dispatch.c blake3_portable.c \\\n    blake3_sse2_x86-64_unix.S blake3_sse41_x86-64_unix.S blake3_avx2_x86-64_unix.S \\\n    blake3_avx512_x86-64_unix.S\n```\n\n# API\n\n## The Struct\n\n```c\ntypedef struct {\n  // private fields\n} blake3_hasher;\n```\n\nAn incremental BLAKE3 hashing state, which can accept any number of\nupdates. This implementation doesn't allocate any heap memory, but\n`sizeof(blake3_hasher)` itself is relatively large, currently 1912 bytes\non x86-64. This size can be reduced by restricting the maximum input\nlength, as described in Section 5.4 of [the BLAKE3\nspec](https://github.com/BLAKE3-team/BLAKE3-specs/blob/master/blake3.pdf),\nbut this implementation doesn't currently support that strategy.\n\n## Common API Functions\n\n```c\nvoid blake3_hasher_init(\n  blake3_hasher *self);\n```\n\nInitialize a `blake3_hasher` in the default hashing mode.\n\n---\n\n```c\nvoid blake3_hasher_update(\n  blake3_hasher *self,\n  const void *input,\n  size_t input_len);\n```\n\nAdd input to the hasher. This can be called any number of times.\n\n---\n\n```c\nvoid blake3_hasher_finalize(\n  const blake3_hasher *self,\n  uint8_t *out,\n  size_t out_len);\n```\n\nFinalize the hasher and emit an output of any length. This doesn't\nmodify the hasher itself, and it's possible to finalize again after\nadding more input. The constant `BLAKE3_OUT_LEN` provides the default\noutput length, 32 bytes.\n\n## Less Common API Functions\n\n```c\nvoid blake3_hasher_init_keyed(\n  blake3_hasher *self,\n  const uint8_t key[BLAKE3_KEY_LEN]);\n```\n\nInitialize a `blake3_hasher` in the keyed hashing mode. The key must be\nexactly 32 bytes.\n\n---\n\n```c\nvoid blake3_hasher_init_derive_key(\n  blake3_hasher *self,\n  const char *context);\n```\n\nInitialize a `blake3_hasher` in the key derivation mode. The context\nstring is given as an initialization parameter, and afterwards input key\nmaterial should be given with `blake3_hasher_update`. The context string\nis a null-terminated C string which should be **hardcoded, globally\nunique, and application-specific**. The context string should not\ninclude any dynamic input like salts, nonces, or identifiers read from a\ndatabase at runtime. A good default format for the context string is\n`\"[application] [commit timestamp] [purpose]\"`, e.g., `\"example.com\n2019-12-25 16:18:03 session tokens v1\"`.\n\nThis function is intended for application code written in C. For\nlanguage bindings, see `blake3_hasher_init_derive_key_raw` below.\n\n---\n\n```c\nvoid blake3_hasher_init_derive_key_raw(\n  blake3_hasher *self,\n  const void *context,\n  size_t context_len);\n```\n\nAs `blake3_hasher_init_derive_key` above, except that the context string\nis given as a pointer to an array of arbitrary bytes with a provided\nlength. This is intended for writing language bindings, where C string\nconversion would add unnecessary overhead and new error cases. Unicode\nstrings should be encoded as UTF-8.\n\nApplication code in C should prefer `blake3_hasher_init_derive_key`,\nwhich takes the context as a C string. If you need to use arbitrary\nbytes as a context string in application code, consider whether you're\nviolating the requirement that context strings should be hardcoded.\n\n---\n\n```c\nvoid blake3_hasher_finalize_seek(\n  const blake3_hasher *self,\n  uint64_t seek,\n  uint8_t *out,\n  size_t out_len);\n```\n\nThe same as `blake3_hasher_finalize`, but with an additional `seek`\nparameter for the starting byte position in the output stream. To\nefficiently stream a large output without allocating memory, call this\nfunction in a loop, incrementing `seek` by the output length each time.\n\n# Building\n\nThis implementation is just C and assembly files. It doesn't include a\npublic-facing build system. (The `Makefile` in this directory is only\nfor testing.) Instead, the intention is that you can include these files\nin whatever build system you're already using. This section describes\nthe commands your build system should execute, or which you can execute\nby hand. Note that these steps may change in future versions.\n\n## x86\n\nDynamic dispatch is enabled by default on x86. The implementation will\nquery the CPU at runtime to detect SIMD support, and it will use the\nwidest instruction set available. By default, `blake3_dispatch.c`\nexpects to be linked with code for five different instruction sets:\nportable C, SSE2, SSE4.1, AVX2, and AVX-512.\n\nFor each of the x86 SIMD instruction sets, four versions are available:\nthree flavors of assembly (Unix, Windows MSVC, and Windows GNU) and one\nversion using C intrinsics. The assembly versions are generally\npreferred. They perform better, they perform more consistently across\ndifferent compilers, and they build more quickly. On the other hand, the\nassembly versions are x86\\_64-only, and you need to select the right\nflavor for your target platform.\n\nHere's an example of building a shared library on x86\\_64 Linux using\nthe assembly implementations:\n\n```bash\ngcc -shared -O3 -o libblake3.so blake3.c blake3_dispatch.c blake3_portable.c \\\n    blake3_sse2_x86-64_unix.S blake3_sse41_x86-64_unix.S blake3_avx2_x86-64_unix.S \\\n    blake3_avx512_x86-64_unix.S\n```\n\nWhen building the intrinsics-based implementations, you need to build\neach implementation separately, with the corresponding instruction set\nexplicitly enabled in the compiler. Here's the same shared library using\nthe intrinsics-based implementations:\n\n```bash\ngcc -c -fPIC -O3 -msse2 blake3_sse2.c -o blake3_sse2.o\ngcc -c -fPIC -O3 -msse4.1 blake3_sse41.c -o blake3_sse41.o\ngcc -c -fPIC -O3 -mavx2 blake3_avx2.c -o blake3_avx2.o\ngcc -c -fPIC -O3 -mavx512f -mavx512vl blake3_avx512.c -o blake3_avx512.o\ngcc -shared -O3 -o libblake3.so blake3.c blake3_dispatch.c blake3_portable.c \\\n    blake3_avx2.o blake3_avx512.o blake3_sse41.o blake3_sse2.o\n```\n\nNote above that building `blake3_avx512.c` requires both `-mavx512f` and\n`-mavx512vl` under GCC and Clang. Under MSVC, the single `/arch:AVX512`\nflag is sufficient. The MSVC equivalent of `-mavx2` is `/arch:AVX2`.\nMSVC enables SSE2 and SSE4.1 by defaut, and it doesn't have a\ncorresponding flag.\n\nIf you want to omit SIMD code entirely, you need to explicitly disable\neach instruction set. Here's an example of building a shared library on\nx86 with only portable code:\n\n```bash\ngcc -shared -O3 -o libblake3.so -DBLAKE3_NO_SSE2 -DBLAKE3_NO_SSE41 -DBLAKE3_NO_AVX2 \\\n    -DBLAKE3_NO_AVX512 blake3.c blake3_dispatch.c blake3_portable.c\n```\n\n## ARM NEON\n\nThe NEON implementation is not enabled by default on ARM, since not all\nARM targets support it. To enable it, set `BLAKE3_USE_NEON=1`. Here's an\nexample of building a shared library on ARM Linux with NEON support:\n\n```bash\ngcc -shared -O3 -o libblake3.so -DBLAKE3_USE_NEON blake3.c blake3_dispatch.c \\\n    blake3_portable.c blake3_neon.c\n```\n\nNote that on some targets (ARMv7 in particular), extra flags may be\nrequired to activate NEON support in the compiler. If you see an error\nlike...\n\n```\n/usr/lib/gcc/armv7l-unknown-linux-gnueabihf/9.2.0/include/arm_neon.h:635:1: error: inlining failed\nin call to always_inline ‘vaddq_u32’: target specific option mismatch\n```\n\n...then you may need to add something like `-mfpu=neon-vfpv4\n-mfloat-abi=hard`.\n\n## Other Platforms\n\nThe portable implementation should work on most other architectures. For\nexample:\n\n```bash\ngcc -shared -O3 -o libblake3.so blake3.c blake3_dispatch.c blake3_portable.c\n```\n\n# Multithreading\n\nUnlike the Rust implementation, the C implementation doesn't currently support\nmultithreading. A future version of this library could add support by taking an\noptional dependency on OpenMP or similar. Alternatively, we could expose a\nlower-level API to allow callers to implement concurrency themselves. The\nformer would be more convenient and less error-prone, but the latter would give\ncallers the maximum possible amount of control. The best choice here depends on\nthe specific use case, so if you have a use case for multithreaded hashing in\nC, please file a GitHub issue and let us know.\n"
  },
  {
    "path": "common/blake3/blake3.c",
    "content": "#include <assert.h>\n#include <stdbool.h>\n#include <string.h>\n\n#include \"blake3.h\"\n#include \"blake3_impl.h\"\n\nconst char *blake3_version(void) { return BLAKE3_VERSION_STRING; }\n\nINLINE void chunk_state_init(blake3_chunk_state *self, const uint32_t key[8],\n                             uint8_t flags) {\n  memcpy(self->cv, key, BLAKE3_KEY_LEN);\n  self->chunk_counter = 0;\n  memset(self->buf, 0, BLAKE3_BLOCK_LEN);\n  self->buf_len = 0;\n  self->blocks_compressed = 0;\n  self->flags = flags;\n}\n\nINLINE void chunk_state_reset(blake3_chunk_state *self, const uint32_t key[8],\n                              uint64_t chunk_counter) {\n  memcpy(self->cv, key, BLAKE3_KEY_LEN);\n  self->chunk_counter = chunk_counter;\n  self->blocks_compressed = 0;\n  memset(self->buf, 0, BLAKE3_BLOCK_LEN);\n  self->buf_len = 0;\n}\n\nINLINE size_t chunk_state_len(const blake3_chunk_state *self) {\n  return (BLAKE3_BLOCK_LEN * (size_t)self->blocks_compressed) +\n         ((size_t)self->buf_len);\n}\n\nINLINE size_t chunk_state_fill_buf(blake3_chunk_state *self,\n                                   const uint8_t *input, size_t input_len) {\n  size_t take = BLAKE3_BLOCK_LEN - ((size_t)self->buf_len);\n  if (take > input_len) {\n    take = input_len;\n  }\n  uint8_t *dest = self->buf + ((size_t)self->buf_len);\n  memcpy(dest, input, take);\n  self->buf_len += (uint8_t)take;\n  return take;\n}\n\nINLINE uint8_t chunk_state_maybe_start_flag(const blake3_chunk_state *self) {\n  if (self->blocks_compressed == 0) {\n    return CHUNK_START;\n  } else {\n    return 0;\n  }\n}\n\ntypedef struct {\n  uint32_t input_cv[8];\n  uint64_t counter;\n  uint8_t block[BLAKE3_BLOCK_LEN];\n  uint8_t block_len;\n  uint8_t flags;\n} output_t;\n\nINLINE output_t make_output(const uint32_t input_cv[8],\n                            const uint8_t block[BLAKE3_BLOCK_LEN],\n                            uint8_t block_len, uint64_t counter,\n                            uint8_t flags) {\n  output_t ret;\n  memcpy(ret.input_cv, input_cv, 32);\n  memcpy(ret.block, block, BLAKE3_BLOCK_LEN);\n  ret.block_len = block_len;\n  ret.counter = counter;\n  ret.flags = flags;\n  return ret;\n}\n\n// Chaining values within a given chunk (specifically the compress_in_place\n// interface) are represented as words. This avoids unnecessary bytes<->words\n// conversion overhead in the portable implementation. However, the hash_many\n// interface handles both user input and parent node blocks, so it accepts\n// bytes. For that reason, chaining values in the CV stack are represented as\n// bytes.\nINLINE void output_chaining_value(const output_t *self, uint8_t cv[32]) {\n  uint32_t cv_words[8];\n  memcpy(cv_words, self->input_cv, 32);\n  blake3_compress_in_place(cv_words, self->block, self->block_len,\n                           self->counter, self->flags);\n  store_cv_words(cv, cv_words);\n}\n\nINLINE void output_root_bytes(const output_t *self, uint64_t seek, uint8_t *out,\n                              size_t out_len) {\n  uint64_t output_block_counter = seek / 64;\n  size_t offset_within_block = seek % 64;\n  uint8_t wide_buf[64];\n  while (out_len > 0) {\n    blake3_compress_xof(self->input_cv, self->block, self->block_len,\n                        output_block_counter, self->flags | ROOT, wide_buf);\n    size_t available_bytes = 64 - offset_within_block;\n    size_t memcpy_len;\n    if (out_len > available_bytes) {\n      memcpy_len = available_bytes;\n    } else {\n      memcpy_len = out_len;\n    }\n    memcpy(out, wide_buf + offset_within_block, memcpy_len);\n    out += memcpy_len;\n    out_len -= memcpy_len;\n    output_block_counter += 1;\n    offset_within_block = 0;\n  }\n}\n\nINLINE void chunk_state_update(blake3_chunk_state *self, const uint8_t *input,\n                               size_t input_len) {\n  if (self->buf_len > 0) {\n    size_t take = chunk_state_fill_buf(self, input, input_len);\n    input += take;\n    input_len -= take;\n    if (input_len > 0) {\n      blake3_compress_in_place(\n          self->cv, self->buf, BLAKE3_BLOCK_LEN, self->chunk_counter,\n          self->flags | chunk_state_maybe_start_flag(self));\n      self->blocks_compressed += 1;\n      self->buf_len = 0;\n      memset(self->buf, 0, BLAKE3_BLOCK_LEN);\n    }\n  }\n\n  while (input_len > BLAKE3_BLOCK_LEN) {\n    blake3_compress_in_place(self->cv, input, BLAKE3_BLOCK_LEN,\n                             self->chunk_counter,\n                             self->flags | chunk_state_maybe_start_flag(self));\n    self->blocks_compressed += 1;\n    input += BLAKE3_BLOCK_LEN;\n    input_len -= BLAKE3_BLOCK_LEN;\n  }\n\n  size_t take = chunk_state_fill_buf(self, input, input_len);\n  input += take;\n  input_len -= take;\n}\n\nINLINE output_t chunk_state_output(const blake3_chunk_state *self) {\n  uint8_t block_flags =\n      self->flags | chunk_state_maybe_start_flag(self) | CHUNK_END;\n  return make_output(self->cv, self->buf, self->buf_len, self->chunk_counter,\n                     block_flags);\n}\n\nINLINE output_t parent_output(const uint8_t block[BLAKE3_BLOCK_LEN],\n                              const uint32_t key[8], uint8_t flags) {\n  return make_output(key, block, BLAKE3_BLOCK_LEN, 0, flags | PARENT);\n}\n\n// Given some input larger than one chunk, return the number of bytes that\n// should go in the left subtree. This is the largest power-of-2 number of\n// chunks that leaves at least 1 byte for the right subtree.\nINLINE size_t left_len(size_t content_len) {\n  // Subtract 1 to reserve at least one byte for the right side. content_len\n  // should always be greater than BLAKE3_CHUNK_LEN.\n  size_t full_chunks = (content_len - 1) / BLAKE3_CHUNK_LEN;\n  return round_down_to_power_of_2(full_chunks) * BLAKE3_CHUNK_LEN;\n}\n\n// Use SIMD parallelism to hash up to MAX_SIMD_DEGREE chunks at the same time\n// on a single thread. Write out the chunk chaining values and return the\n// number of chunks hashed. These chunks are never the root and never empty;\n// those cases use a different codepath.\nINLINE size_t compress_chunks_parallel(const uint8_t *input, size_t input_len,\n                                       const uint32_t key[8],\n                                       uint64_t chunk_counter, uint8_t flags,\n                                       uint8_t *out) {\n#if defined(BLAKE3_TESTING)\n  assert(0 < input_len);\n  assert(input_len <= MAX_SIMD_DEGREE * BLAKE3_CHUNK_LEN);\n#endif\n\n  const uint8_t *chunks_array[MAX_SIMD_DEGREE];\n  size_t input_position = 0;\n  size_t chunks_array_len = 0;\n  while (input_len - input_position >= BLAKE3_CHUNK_LEN) {\n    chunks_array[chunks_array_len] = &input[input_position];\n    input_position += BLAKE3_CHUNK_LEN;\n    chunks_array_len += 1;\n  }\n\n  blake3_hash_many(chunks_array, chunks_array_len,\n                   BLAKE3_CHUNK_LEN / BLAKE3_BLOCK_LEN, key, chunk_counter,\n                   true, flags, CHUNK_START, CHUNK_END, out);\n\n  // Hash the remaining partial chunk, if there is one. Note that the empty\n  // chunk (meaning the empty message) is a different codepath.\n  if (input_len > input_position) {\n    uint64_t counter = chunk_counter + (uint64_t)chunks_array_len;\n    blake3_chunk_state chunk_state;\n    chunk_state_init(&chunk_state, key, flags);\n    chunk_state.chunk_counter = counter;\n    chunk_state_update(&chunk_state, &input[input_position],\n                       input_len - input_position);\n    output_t output = chunk_state_output(&chunk_state);\n    output_chaining_value(&output, &out[chunks_array_len * BLAKE3_OUT_LEN]);\n    return chunks_array_len + 1;\n  } else {\n    return chunks_array_len;\n  }\n}\n\n// Use SIMD parallelism to hash up to MAX_SIMD_DEGREE parents at the same time\n// on a single thread. Write out the parent chaining values and return the\n// number of parents hashed. (If there's an odd input chaining value left over,\n// return it as an additional output.) These parents are never the root and\n// never empty; those cases use a different codepath.\nINLINE size_t compress_parents_parallel(const uint8_t *child_chaining_values,\n                                        size_t num_chaining_values,\n                                        const uint32_t key[8], uint8_t flags,\n                                        uint8_t *out) {\n#if defined(BLAKE3_TESTING)\n  assert(2 <= num_chaining_values);\n  assert(num_chaining_values <= 2 * MAX_SIMD_DEGREE_OR_2);\n#endif\n\n  const uint8_t *parents_array[MAX_SIMD_DEGREE_OR_2];\n  size_t parents_array_len = 0;\n  while (num_chaining_values - (2 * parents_array_len) >= 2) {\n    parents_array[parents_array_len] =\n        &child_chaining_values[2 * parents_array_len * BLAKE3_OUT_LEN];\n    parents_array_len += 1;\n  }\n\n  blake3_hash_many(parents_array, parents_array_len, 1, key,\n                   0, // Parents always use counter 0.\n                   false, flags | PARENT,\n                   0, // Parents have no start flags.\n                   0, // Parents have no end flags.\n                   out);\n\n  // If there's an odd child left over, it becomes an output.\n  if (num_chaining_values > 2 * parents_array_len) {\n    memcpy(&out[parents_array_len * BLAKE3_OUT_LEN],\n           &child_chaining_values[2 * parents_array_len * BLAKE3_OUT_LEN],\n           BLAKE3_OUT_LEN);\n    return parents_array_len + 1;\n  } else {\n    return parents_array_len;\n  }\n}\n\n// The wide helper function returns (writes out) an array of chaining values\n// and returns the length of that array. The number of chaining values returned\n// is the dyanmically detected SIMD degree, at most MAX_SIMD_DEGREE. Or fewer,\n// if the input is shorter than that many chunks. The reason for maintaining a\n// wide array of chaining values going back up the tree, is to allow the\n// implementation to hash as many parents in parallel as possible.\n//\n// As a special case when the SIMD degree is 1, this function will still return\n// at least 2 outputs. This guarantees that this function doesn't perform the\n// root compression. (If it did, it would use the wrong flags, and also we\n// wouldn't be able to implement exendable ouput.) Note that this function is\n// not used when the whole input is only 1 chunk long; that's a different\n// codepath.\n//\n// Why not just have the caller split the input on the first update(), instead\n// of implementing this special rule? Because we don't want to limit SIMD or\n// multi-threading parallelism for that update().\nstatic size_t blake3_compress_subtree_wide(const uint8_t *input,\n                                           size_t input_len,\n                                           const uint32_t key[8],\n                                           uint64_t chunk_counter,\n                                           uint8_t flags, uint8_t *out) {\n  // Note that the single chunk case does *not* bump the SIMD degree up to 2\n  // when it is 1. If this implementation adds multi-threading in the future,\n  // this gives us the option of multi-threading even the 2-chunk case, which\n  // can help performance on smaller platforms.\n  if (input_len <= blake3_simd_degree() * BLAKE3_CHUNK_LEN) {\n    return compress_chunks_parallel(input, input_len, key, chunk_counter, flags,\n                                    out);\n  }\n\n  // With more than simd_degree chunks, we need to recurse. Start by dividing\n  // the input into left and right subtrees. (Note that this is only optimal\n  // as long as the SIMD degree is a power of 2. If we ever get a SIMD degree\n  // of 3 or something, we'll need a more complicated strategy.)\n  size_t left_input_len = left_len(input_len);\n  size_t right_input_len = input_len - left_input_len;\n  const uint8_t *right_input = &input[left_input_len];\n  uint64_t right_chunk_counter =\n      chunk_counter + (uint64_t)(left_input_len / BLAKE3_CHUNK_LEN);\n\n  // Make space for the child outputs. Here we use MAX_SIMD_DEGREE_OR_2 to\n  // account for the special case of returning 2 outputs when the SIMD degree\n  // is 1.\n  uint8_t cv_array[2 * MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN];\n  size_t degree = blake3_simd_degree();\n  if (left_input_len > BLAKE3_CHUNK_LEN && degree == 1) {\n    // The special case: We always use a degree of at least two, to make\n    // sure there are two outputs. Except, as noted above, at the chunk\n    // level, where we allow degree=1. (Note that the 1-chunk-input case is\n    // a different codepath.)\n    degree = 2;\n  }\n  uint8_t *right_cvs = &cv_array[degree * BLAKE3_OUT_LEN];\n\n  // Recurse! If this implementation adds multi-threading support in the\n  // future, this is where it will go.\n  size_t left_n = blake3_compress_subtree_wide(input, left_input_len, key,\n                                               chunk_counter, flags, cv_array);\n  size_t right_n = blake3_compress_subtree_wide(\n      right_input, right_input_len, key, right_chunk_counter, flags, right_cvs);\n\n  // The special case again. If simd_degree=1, then we'll have left_n=1 and\n  // right_n=1. Rather than compressing them into a single output, return\n  // them directly, to make sure we always have at least two outputs.\n  if (left_n == 1) {\n    memcpy(out, cv_array, 2 * BLAKE3_OUT_LEN);\n    return 2;\n  }\n\n  // Otherwise, do one layer of parent node compression.\n  size_t num_chaining_values = left_n + right_n;\n  return compress_parents_parallel(cv_array, num_chaining_values, key, flags,\n                                   out);\n}\n\n// Hash a subtree with compress_subtree_wide(), and then condense the resulting\n// list of chaining values down to a single parent node. Don't compress that\n// last parent node, however. Instead, return its message bytes (the\n// concatenated chaining values of its children). This is necessary when the\n// first call to update() supplies a complete subtree, because the topmost\n// parent node of that subtree could end up being the root. It's also necessary\n// for extended output in the general case.\n//\n// As with compress_subtree_wide(), this function is not used on inputs of 1\n// chunk or less. That's a different codepath.\nINLINE void compress_subtree_to_parent_node(\n    const uint8_t *input, size_t input_len, const uint32_t key[8],\n    uint64_t chunk_counter, uint8_t flags, uint8_t out[2 * BLAKE3_OUT_LEN]) {\n#if defined(BLAKE3_TESTING)\n  assert(input_len > BLAKE3_CHUNK_LEN);\n#endif\n\n  uint8_t cv_array[MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN];\n  size_t num_cvs = blake3_compress_subtree_wide(input, input_len, key,\n                                                chunk_counter, flags, cv_array);\n\n  // If MAX_SIMD_DEGREE is greater than 2 and there's enough input,\n  // compress_subtree_wide() returns more than 2 chaining values. Condense\n  // them into 2 by forming parent nodes repeatedly.\n  uint8_t out_array[MAX_SIMD_DEGREE_OR_2 * BLAKE3_OUT_LEN / 2];\n  while (num_cvs > 2) {\n    num_cvs =\n        compress_parents_parallel(cv_array, num_cvs, key, flags, out_array);\n    memcpy(cv_array, out_array, num_cvs * BLAKE3_OUT_LEN);\n  }\n  memcpy(out, cv_array, 2 * BLAKE3_OUT_LEN);\n}\n\nINLINE void hasher_init_base(blake3_hasher *self, const uint32_t key[8],\n                             uint8_t flags) {\n  memcpy(self->key, key, BLAKE3_KEY_LEN);\n  chunk_state_init(&self->chunk, key, flags);\n  self->cv_stack_len = 0;\n}\n\nvoid blake3_hasher_init(blake3_hasher *self) { hasher_init_base(self, IV, 0); }\n\nvoid blake3_hasher_init_keyed(blake3_hasher *self,\n                              const uint8_t key[BLAKE3_KEY_LEN]) {\n  uint32_t key_words[8];\n  load_key_words(key, key_words);\n  hasher_init_base(self, key_words, KEYED_HASH);\n}\n\nvoid blake3_hasher_init_derive_key_raw(blake3_hasher *self, const void *context,\n                                       size_t context_len) {\n  blake3_hasher context_hasher;\n  hasher_init_base(&context_hasher, IV, DERIVE_KEY_CONTEXT);\n  blake3_hasher_update(&context_hasher, context, context_len);\n  uint8_t context_key[BLAKE3_KEY_LEN];\n  blake3_hasher_finalize(&context_hasher, context_key, BLAKE3_KEY_LEN);\n  uint32_t context_key_words[8];\n  load_key_words(context_key, context_key_words);\n  hasher_init_base(self, context_key_words, DERIVE_KEY_MATERIAL);\n}\n\nvoid blake3_hasher_init_derive_key(blake3_hasher *self, const char *context) {\n  blake3_hasher_init_derive_key_raw(self, context, strlen(context));\n}\n\n// As described in hasher_push_cv() below, we do \"lazy merging\", delaying\n// merges until right before the next CV is about to be added. This is\n// different from the reference implementation. Another difference is that we\n// aren't always merging 1 chunk at a time. Instead, each CV might represent\n// any power-of-two number of chunks, as long as the smaller-above-larger stack\n// order is maintained. Instead of the \"count the trailing 0-bits\" algorithm\n// described in the spec, we use a \"count the total number of 1-bits\" variant\n// that doesn't require us to retain the subtree size of the CV on top of the\n// stack. The principle is the same: each CV that should remain in the stack is\n// represented by a 1-bit in the total number of chunks (or bytes) so far.\nINLINE void hasher_merge_cv_stack(blake3_hasher *self, uint64_t total_len) {\n  size_t post_merge_stack_len = (size_t)popcnt(total_len);\n  while (self->cv_stack_len > post_merge_stack_len) {\n    uint8_t *parent_node =\n        &self->cv_stack[(self->cv_stack_len - 2) * BLAKE3_OUT_LEN];\n    output_t output = parent_output(parent_node, self->key, self->chunk.flags);\n    output_chaining_value(&output, parent_node);\n    self->cv_stack_len -= 1;\n  }\n}\n\n// In reference_impl.rs, we merge the new CV with existing CVs from the stack\n// before pushing it. We can do that because we know more input is coming, so\n// we know none of the merges are root.\n//\n// This setting is different. We want to feed as much input as possible to\n// compress_subtree_wide(), without setting aside anything for the chunk_state.\n// If the user gives us 64 KiB, we want to parallelize over all 64 KiB at once\n// as a single subtree, if at all possible.\n//\n// This leads to two problems:\n// 1) This 64 KiB input might be the only call that ever gets made to update.\n//    In this case, the root node of the 64 KiB subtree would be the root node\n//    of the whole tree, and it would need to be ROOT finalized. We can't\n//    compress it until we know.\n// 2) This 64 KiB input might complete a larger tree, whose root node is\n//    similarly going to be the the root of the whole tree. For example, maybe\n//    we have 196 KiB (that is, 128 + 64) hashed so far. We can't compress the\n//    node at the root of the 256 KiB subtree until we know how to finalize it.\n//\n// The second problem is solved with \"lazy merging\". That is, when we're about\n// to add a CV to the stack, we don't merge it with anything first, as the\n// reference impl does. Instead we do merges using the *previous* CV that was\n// added, which is sitting on top of the stack, and we put the new CV\n// (unmerged) on top of the stack afterwards. This guarantees that we never\n// merge the root node until finalize().\n//\n// Solving the first problem requires an additional tool,\n// compress_subtree_to_parent_node(). That function always returns the top\n// *two* chaining values of the subtree it's compressing. We then do lazy\n// merging with each of them separately, so that the second CV will always\n// remain unmerged. (That also helps us support extendable output when we're\n// hashing an input all-at-once.)\nINLINE void hasher_push_cv(blake3_hasher *self, uint8_t new_cv[BLAKE3_OUT_LEN],\n                           uint64_t chunk_counter) {\n  hasher_merge_cv_stack(self, chunk_counter);\n  memcpy(&self->cv_stack[self->cv_stack_len * BLAKE3_OUT_LEN], new_cv,\n         BLAKE3_OUT_LEN);\n  self->cv_stack_len += 1;\n}\n\nvoid blake3_hasher_update(blake3_hasher *self, const void *input,\n                          size_t input_len) {\n  // Explicitly checking for zero avoids causing UB by passing a null pointer\n  // to memcpy. This comes up in practice with things like:\n  //   std::vector<uint8_t> v;\n  //   blake3_hasher_update(&hasher, v.data(), v.size());\n  if (input_len == 0) {\n    return;\n  }\n\n  const uint8_t *input_bytes = (const uint8_t *)input;\n\n  // If we have some partial chunk bytes in the internal chunk_state, we need\n  // to finish that chunk first.\n  if (chunk_state_len(&self->chunk) > 0) {\n    size_t take = BLAKE3_CHUNK_LEN - chunk_state_len(&self->chunk);\n    if (take > input_len) {\n      take = input_len;\n    }\n    chunk_state_update(&self->chunk, input_bytes, take);\n    input_bytes += take;\n    input_len -= take;\n    // If we've filled the current chunk and there's more coming, finalize this\n    // chunk and proceed. In this case we know it's not the root.\n    if (input_len > 0) {\n      output_t output = chunk_state_output(&self->chunk);\n      uint8_t chunk_cv[32];\n      output_chaining_value(&output, chunk_cv);\n      hasher_push_cv(self, chunk_cv, self->chunk.chunk_counter);\n      chunk_state_reset(&self->chunk, self->key, self->chunk.chunk_counter + 1);\n    } else {\n      return;\n    }\n  }\n\n  // Now the chunk_state is clear, and we have more input. If there's more than\n  // a single chunk (so, definitely not the root chunk), hash the largest whole\n  // subtree we can, with the full benefits of SIMD (and maybe in the future,\n  // multi-threading) parallelism. Two restrictions:\n  // - The subtree has to be a power-of-2 number of chunks. Only subtrees along\n  //   the right edge can be incomplete, and we don't know where the right edge\n  //   is going to be until we get to finalize().\n  // - The subtree must evenly divide the total number of chunks up until this\n  //   point (if total is not 0). If the current incomplete subtree is only\n  //   waiting for 1 more chunk, we can't hash a subtree of 4 chunks. We have\n  //   to complete the current subtree first.\n  // Because we might need to break up the input to form powers of 2, or to\n  // evenly divide what we already have, this part runs in a loop.\n  while (input_len > BLAKE3_CHUNK_LEN) {\n    size_t subtree_len = round_down_to_power_of_2(input_len);\n    uint64_t count_so_far = self->chunk.chunk_counter * BLAKE3_CHUNK_LEN;\n    // Shrink the subtree_len until it evenly divides the count so far. We know\n    // that subtree_len itself is a power of 2, so we can use a bitmasking\n    // trick instead of an actual remainder operation. (Note that if the caller\n    // consistently passes power-of-2 inputs of the same size, as is hopefully\n    // typical, this loop condition will always fail, and subtree_len will\n    // always be the full length of the input.)\n    //\n    // An aside: We don't have to shrink subtree_len quite this much. For\n    // example, if count_so_far is 1, we could pass 2 chunks to\n    // compress_subtree_to_parent_node. Since we'll get 2 CVs back, we'll still\n    // get the right answer in the end, and we might get to use 2-way SIMD\n    // parallelism. The problem with this optimization, is that it gets us\n    // stuck always hashing 2 chunks. The total number of chunks will remain\n    // odd, and we'll never graduate to higher degrees of parallelism. See\n    // https://github.com/BLAKE3-team/BLAKE3/issues/69.\n    while ((((uint64_t)(subtree_len - 1)) & count_so_far) != 0) {\n      subtree_len /= 2;\n    }\n    // The shrunken subtree_len might now be 1 chunk long. If so, hash that one\n    // chunk by itself. Otherwise, compress the subtree into a pair of CVs.\n    uint64_t subtree_chunks = subtree_len / BLAKE3_CHUNK_LEN;\n    if (subtree_len <= BLAKE3_CHUNK_LEN) {\n      blake3_chunk_state chunk_state;\n      chunk_state_init(&chunk_state, self->key, self->chunk.flags);\n      chunk_state.chunk_counter = self->chunk.chunk_counter;\n      chunk_state_update(&chunk_state, input_bytes, subtree_len);\n      output_t output = chunk_state_output(&chunk_state);\n      uint8_t cv[BLAKE3_OUT_LEN];\n      output_chaining_value(&output, cv);\n      hasher_push_cv(self, cv, chunk_state.chunk_counter);\n    } else {\n      // This is the high-performance happy path, though getting here depends\n      // on the caller giving us a long enough input.\n      uint8_t cv_pair[2 * BLAKE3_OUT_LEN];\n      compress_subtree_to_parent_node(input_bytes, subtree_len, self->key,\n                                      self->chunk.chunk_counter,\n                                      self->chunk.flags, cv_pair);\n      hasher_push_cv(self, cv_pair, self->chunk.chunk_counter);\n      hasher_push_cv(self, &cv_pair[BLAKE3_OUT_LEN],\n                     self->chunk.chunk_counter + (subtree_chunks / 2));\n    }\n    self->chunk.chunk_counter += subtree_chunks;\n    input_bytes += subtree_len;\n    input_len -= subtree_len;\n  }\n\n  // If there's any remaining input less than a full chunk, add it to the chunk\n  // state. In that case, also do a final merge loop to make sure the subtree\n  // stack doesn't contain any unmerged pairs. The remaining input means we\n  // know these merges are non-root. This merge loop isn't strictly necessary\n  // here, because hasher_push_chunk_cv already does its own merge loop, but it\n  // simplifies blake3_hasher_finalize below.\n  if (input_len > 0) {\n    chunk_state_update(&self->chunk, input_bytes, input_len);\n    hasher_merge_cv_stack(self, self->chunk.chunk_counter);\n  }\n}\n\nvoid blake3_hasher_finalize(const blake3_hasher *self, uint8_t *out,\n                            size_t out_len) {\n  blake3_hasher_finalize_seek(self, 0, out, out_len);\n}\n\nvoid blake3_hasher_finalize_seek(const blake3_hasher *self, uint64_t seek,\n                                 uint8_t *out, size_t out_len) {\n  // Explicitly checking for zero avoids causing UB by passing a null pointer\n  // to memcpy. This comes up in practice with things like:\n  //   std::vector<uint8_t> v;\n  //   blake3_hasher_finalize(&hasher, v.data(), v.size());\n  if (out_len == 0) {\n    return;\n  }\n\n  // If the subtree stack is empty, then the current chunk is the root.\n  if (self->cv_stack_len == 0) {\n    output_t output = chunk_state_output(&self->chunk);\n    output_root_bytes(&output, seek, out, out_len);\n    return;\n  }\n  // If there are any bytes in the chunk state, finalize that chunk and do a\n  // roll-up merge between that chunk hash and every subtree in the stack. In\n  // this case, the extra merge loop at the end of blake3_hasher_update\n  // guarantees that none of the subtrees in the stack need to be merged with\n  // each other first. Otherwise, if there are no bytes in the chunk state,\n  // then the top of the stack is a chunk hash, and we start the merge from\n  // that.\n  output_t output;\n  size_t cvs_remaining;\n  if (chunk_state_len(&self->chunk) > 0) {\n    cvs_remaining = self->cv_stack_len;\n    output = chunk_state_output(&self->chunk);\n  } else {\n    // There are always at least 2 CVs in the stack in this case.\n    cvs_remaining = self->cv_stack_len - 2;\n    output = parent_output(&self->cv_stack[cvs_remaining * 32], self->key,\n                           self->chunk.flags);\n  }\n  while (cvs_remaining > 0) {\n    cvs_remaining -= 1;\n    uint8_t parent_block[BLAKE3_BLOCK_LEN];\n    memcpy(parent_block, &self->cv_stack[cvs_remaining * 32], 32);\n    output_chaining_value(&output, &parent_block[32]);\n    output = parent_output(parent_block, self->key, self->chunk.flags);\n  }\n  output_root_bytes(&output, seek, out, out_len);\n}\n"
  },
  {
    "path": "common/blake3/blake3.h",
    "content": "#ifndef BLAKE3_H\n#define BLAKE3_H\n\n#include <stddef.h>\n#include <stdint.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#define BLAKE3_VERSION_STRING \"0.3.7\"\n#define BLAKE3_KEY_LEN 32\n#define BLAKE3_OUT_LEN 32\n#define BLAKE3_BLOCK_LEN 64\n#define BLAKE3_CHUNK_LEN 1024\n#define BLAKE3_MAX_DEPTH 54\n\n// This struct is a private implementation detail. It has to be here because\n// it's part of blake3_hasher below.\ntypedef struct {\n  uint32_t cv[8];\n  uint64_t chunk_counter;\n  uint8_t buf[BLAKE3_BLOCK_LEN];\n  uint8_t buf_len;\n  uint8_t blocks_compressed;\n  uint8_t flags;\n} blake3_chunk_state;\n\ntypedef struct {\n  uint32_t key[8];\n  blake3_chunk_state chunk;\n  uint8_t cv_stack_len;\n  // The stack size is MAX_DEPTH + 1 because we do lazy merging. For example,\n  // with 7 chunks, we have 3 entries in the stack. Adding an 8th chunk\n  // requires a 4th entry, rather than merging everything down to 1, because we\n  // don't know whether more input is coming. This is different from how the\n  // reference implementation does things.\n  uint8_t cv_stack[(BLAKE3_MAX_DEPTH + 1) * BLAKE3_OUT_LEN];\n} blake3_hasher;\n\nconst char *blake3_version(void);\nvoid blake3_hasher_init(blake3_hasher *self);\nvoid blake3_hasher_init_keyed(blake3_hasher *self,\n                              const uint8_t key[BLAKE3_KEY_LEN]);\nvoid blake3_hasher_init_derive_key(blake3_hasher *self, const char *context);\nvoid blake3_hasher_init_derive_key_raw(blake3_hasher *self, const void *context,\n                                       size_t context_len);\nvoid blake3_hasher_update(blake3_hasher *self, const void *input,\n                          size_t input_len);\nvoid blake3_hasher_finalize(const blake3_hasher *self, uint8_t *out,\n                            size_t out_len);\nvoid blake3_hasher_finalize_seek(const blake3_hasher *self, uint64_t seek,\n                                 uint8_t *out, size_t out_len);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* BLAKE3_H */\n"
  },
  {
    "path": "common/blake3/blake3_avx2.c",
    "content": "#include \"blake3_impl.h\"\n\n#include <immintrin.h>\n\n#define DEGREE 8\n\nINLINE __m256i loadu(const uint8_t src[32]) {\n  return _mm256_loadu_si256((const __m256i *)src);\n}\n\nINLINE void storeu(__m256i src, uint8_t dest[16]) {\n  _mm256_storeu_si256((__m256i *)dest, src);\n}\n\nINLINE __m256i addv(__m256i a, __m256i b) { return _mm256_add_epi32(a, b); }\n\n// Note that clang-format doesn't like the name \"xor\" for some reason.\nINLINE __m256i xorv(__m256i a, __m256i b) { return _mm256_xor_si256(a, b); }\n\nINLINE __m256i set1(uint32_t x) { return _mm256_set1_epi32((int32_t)x); }\n\nINLINE __m256i rot16(__m256i x) {\n  return _mm256_shuffle_epi8(\n      x, _mm256_set_epi8(13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2,\n                         13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2));\n}\n\nINLINE __m256i rot12(__m256i x) {\n  return _mm256_or_si256(_mm256_srli_epi32(x, 12), _mm256_slli_epi32(x, 32 - 12));\n}\n\nINLINE __m256i rot8(__m256i x) {\n  return _mm256_shuffle_epi8(\n      x, _mm256_set_epi8(12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1,\n                         12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1));\n}\n\nINLINE __m256i rot7(__m256i x) {\n  return _mm256_or_si256(_mm256_srli_epi32(x, 7), _mm256_slli_epi32(x, 32 - 7));\n}\n\nINLINE void round_fn(__m256i v[16], __m256i m[16], size_t r) {\n  v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][0]]);\n  v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][2]]);\n  v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][4]]);\n  v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][6]]);\n  v[0] = addv(v[0], v[4]);\n  v[1] = addv(v[1], v[5]);\n  v[2] = addv(v[2], v[6]);\n  v[3] = addv(v[3], v[7]);\n  v[12] = xorv(v[12], v[0]);\n  v[13] = xorv(v[13], v[1]);\n  v[14] = xorv(v[14], v[2]);\n  v[15] = xorv(v[15], v[3]);\n  v[12] = rot16(v[12]);\n  v[13] = rot16(v[13]);\n  v[14] = rot16(v[14]);\n  v[15] = rot16(v[15]);\n  v[8] = addv(v[8], v[12]);\n  v[9] = addv(v[9], v[13]);\n  v[10] = addv(v[10], v[14]);\n  v[11] = addv(v[11], v[15]);\n  v[4] = xorv(v[4], v[8]);\n  v[5] = xorv(v[5], v[9]);\n  v[6] = xorv(v[6], v[10]);\n  v[7] = xorv(v[7], v[11]);\n  v[4] = rot12(v[4]);\n  v[5] = rot12(v[5]);\n  v[6] = rot12(v[6]);\n  v[7] = rot12(v[7]);\n  v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][1]]);\n  v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][3]]);\n  v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][5]]);\n  v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][7]]);\n  v[0] = addv(v[0], v[4]);\n  v[1] = addv(v[1], v[5]);\n  v[2] = addv(v[2], v[6]);\n  v[3] = addv(v[3], v[7]);\n  v[12] = xorv(v[12], v[0]);\n  v[13] = xorv(v[13], v[1]);\n  v[14] = xorv(v[14], v[2]);\n  v[15] = xorv(v[15], v[3]);\n  v[12] = rot8(v[12]);\n  v[13] = rot8(v[13]);\n  v[14] = rot8(v[14]);\n  v[15] = rot8(v[15]);\n  v[8] = addv(v[8], v[12]);\n  v[9] = addv(v[9], v[13]);\n  v[10] = addv(v[10], v[14]);\n  v[11] = addv(v[11], v[15]);\n  v[4] = xorv(v[4], v[8]);\n  v[5] = xorv(v[5], v[9]);\n  v[6] = xorv(v[6], v[10]);\n  v[7] = xorv(v[7], v[11]);\n  v[4] = rot7(v[4]);\n  v[5] = rot7(v[5]);\n  v[6] = rot7(v[6]);\n  v[7] = rot7(v[7]);\n\n  v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][8]]);\n  v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][10]]);\n  v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][12]]);\n  v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][14]]);\n  v[0] = addv(v[0], v[5]);\n  v[1] = addv(v[1], v[6]);\n  v[2] = addv(v[2], v[7]);\n  v[3] = addv(v[3], v[4]);\n  v[15] = xorv(v[15], v[0]);\n  v[12] = xorv(v[12], v[1]);\n  v[13] = xorv(v[13], v[2]);\n  v[14] = xorv(v[14], v[3]);\n  v[15] = rot16(v[15]);\n  v[12] = rot16(v[12]);\n  v[13] = rot16(v[13]);\n  v[14] = rot16(v[14]);\n  v[10] = addv(v[10], v[15]);\n  v[11] = addv(v[11], v[12]);\n  v[8] = addv(v[8], v[13]);\n  v[9] = addv(v[9], v[14]);\n  v[5] = xorv(v[5], v[10]);\n  v[6] = xorv(v[6], v[11]);\n  v[7] = xorv(v[7], v[8]);\n  v[4] = xorv(v[4], v[9]);\n  v[5] = rot12(v[5]);\n  v[6] = rot12(v[6]);\n  v[7] = rot12(v[7]);\n  v[4] = rot12(v[4]);\n  v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][9]]);\n  v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][11]]);\n  v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][13]]);\n  v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][15]]);\n  v[0] = addv(v[0], v[5]);\n  v[1] = addv(v[1], v[6]);\n  v[2] = addv(v[2], v[7]);\n  v[3] = addv(v[3], v[4]);\n  v[15] = xorv(v[15], v[0]);\n  v[12] = xorv(v[12], v[1]);\n  v[13] = xorv(v[13], v[2]);\n  v[14] = xorv(v[14], v[3]);\n  v[15] = rot8(v[15]);\n  v[12] = rot8(v[12]);\n  v[13] = rot8(v[13]);\n  v[14] = rot8(v[14]);\n  v[10] = addv(v[10], v[15]);\n  v[11] = addv(v[11], v[12]);\n  v[8] = addv(v[8], v[13]);\n  v[9] = addv(v[9], v[14]);\n  v[5] = xorv(v[5], v[10]);\n  v[6] = xorv(v[6], v[11]);\n  v[7] = xorv(v[7], v[8]);\n  v[4] = xorv(v[4], v[9]);\n  v[5] = rot7(v[5]);\n  v[6] = rot7(v[6]);\n  v[7] = rot7(v[7]);\n  v[4] = rot7(v[4]);\n}\n\nINLINE void transpose_vecs(__m256i vecs[DEGREE]) {\n  // Interleave 32-bit lanes. The low unpack is lanes 00/11/44/55, and the high\n  // is 22/33/66/77.\n  __m256i ab_0145 = _mm256_unpacklo_epi32(vecs[0], vecs[1]);\n  __m256i ab_2367 = _mm256_unpackhi_epi32(vecs[0], vecs[1]);\n  __m256i cd_0145 = _mm256_unpacklo_epi32(vecs[2], vecs[3]);\n  __m256i cd_2367 = _mm256_unpackhi_epi32(vecs[2], vecs[3]);\n  __m256i ef_0145 = _mm256_unpacklo_epi32(vecs[4], vecs[5]);\n  __m256i ef_2367 = _mm256_unpackhi_epi32(vecs[4], vecs[5]);\n  __m256i gh_0145 = _mm256_unpacklo_epi32(vecs[6], vecs[7]);\n  __m256i gh_2367 = _mm256_unpackhi_epi32(vecs[6], vecs[7]);\n\n  // Interleave 64-bit lates. The low unpack is lanes 00/22 and the high is\n  // 11/33.\n  __m256i abcd_04 = _mm256_unpacklo_epi64(ab_0145, cd_0145);\n  __m256i abcd_15 = _mm256_unpackhi_epi64(ab_0145, cd_0145);\n  __m256i abcd_26 = _mm256_unpacklo_epi64(ab_2367, cd_2367);\n  __m256i abcd_37 = _mm256_unpackhi_epi64(ab_2367, cd_2367);\n  __m256i efgh_04 = _mm256_unpacklo_epi64(ef_0145, gh_0145);\n  __m256i efgh_15 = _mm256_unpackhi_epi64(ef_0145, gh_0145);\n  __m256i efgh_26 = _mm256_unpacklo_epi64(ef_2367, gh_2367);\n  __m256i efgh_37 = _mm256_unpackhi_epi64(ef_2367, gh_2367);\n\n  // Interleave 128-bit lanes.\n  vecs[0] = _mm256_permute2x128_si256(abcd_04, efgh_04, 0x20);\n  vecs[1] = _mm256_permute2x128_si256(abcd_15, efgh_15, 0x20);\n  vecs[2] = _mm256_permute2x128_si256(abcd_26, efgh_26, 0x20);\n  vecs[3] = _mm256_permute2x128_si256(abcd_37, efgh_37, 0x20);\n  vecs[4] = _mm256_permute2x128_si256(abcd_04, efgh_04, 0x31);\n  vecs[5] = _mm256_permute2x128_si256(abcd_15, efgh_15, 0x31);\n  vecs[6] = _mm256_permute2x128_si256(abcd_26, efgh_26, 0x31);\n  vecs[7] = _mm256_permute2x128_si256(abcd_37, efgh_37, 0x31);\n}\n\nINLINE void transpose_msg_vecs(const uint8_t *const *inputs,\n                               size_t block_offset, __m256i out[16]) {\n  out[0] = loadu(&inputs[0][block_offset + 0 * sizeof(__m256i)]);\n  out[1] = loadu(&inputs[1][block_offset + 0 * sizeof(__m256i)]);\n  out[2] = loadu(&inputs[2][block_offset + 0 * sizeof(__m256i)]);\n  out[3] = loadu(&inputs[3][block_offset + 0 * sizeof(__m256i)]);\n  out[4] = loadu(&inputs[4][block_offset + 0 * sizeof(__m256i)]);\n  out[5] = loadu(&inputs[5][block_offset + 0 * sizeof(__m256i)]);\n  out[6] = loadu(&inputs[6][block_offset + 0 * sizeof(__m256i)]);\n  out[7] = loadu(&inputs[7][block_offset + 0 * sizeof(__m256i)]);\n  out[8] = loadu(&inputs[0][block_offset + 1 * sizeof(__m256i)]);\n  out[9] = loadu(&inputs[1][block_offset + 1 * sizeof(__m256i)]);\n  out[10] = loadu(&inputs[2][block_offset + 1 * sizeof(__m256i)]);\n  out[11] = loadu(&inputs[3][block_offset + 1 * sizeof(__m256i)]);\n  out[12] = loadu(&inputs[4][block_offset + 1 * sizeof(__m256i)]);\n  out[13] = loadu(&inputs[5][block_offset + 1 * sizeof(__m256i)]);\n  out[14] = loadu(&inputs[6][block_offset + 1 * sizeof(__m256i)]);\n  out[15] = loadu(&inputs[7][block_offset + 1 * sizeof(__m256i)]);\n  for (size_t i = 0; i < 8; ++i) {\n    _mm_prefetch(&inputs[i][block_offset + 256], _MM_HINT_T0);\n  }\n  transpose_vecs(&out[0]);\n  transpose_vecs(&out[8]);\n}\n\nINLINE void load_counters(uint64_t counter, bool increment_counter,\n                          __m256i *out_lo, __m256i *out_hi) {\n  const __m256i mask = _mm256_set1_epi32(-(int32_t)increment_counter);\n  const __m256i add0 = _mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0);\n  const __m256i add1 = _mm256_and_si256(mask, add0);\n  __m256i l = _mm256_add_epi32(_mm256_set1_epi32(counter), add1);\n  __m256i carry = _mm256_cmpgt_epi32(_mm256_xor_si256(add1, _mm256_set1_epi32(0x80000000)),\n\t\t\t\t     _mm256_xor_si256(   l, _mm256_set1_epi32(0x80000000)));\n  __m256i h = _mm256_sub_epi32(_mm256_set1_epi32(counter >> 32), carry);\n  *out_lo = l;\n  *out_hi = h;\n}\n\nvoid blake3_hash8_avx2(const uint8_t *const *inputs, size_t blocks,\n                       const uint32_t key[8], uint64_t counter,\n                       bool increment_counter, uint8_t flags,\n                       uint8_t flags_start, uint8_t flags_end, uint8_t *out) {\n  __m256i h_vecs[8] = {\n      set1(key[0]), set1(key[1]), set1(key[2]), set1(key[3]),\n      set1(key[4]), set1(key[5]), set1(key[6]), set1(key[7]),\n  };\n  __m256i counter_low_vec, counter_high_vec;\n  load_counters(counter, increment_counter, &counter_low_vec,\n                &counter_high_vec);\n  uint8_t block_flags = flags | flags_start;\n\n  for (size_t block = 0; block < blocks; block++) {\n    if (block + 1 == blocks) {\n      block_flags |= flags_end;\n    }\n    __m256i block_len_vec = set1(BLAKE3_BLOCK_LEN);\n    __m256i block_flags_vec = set1(block_flags);\n    __m256i msg_vecs[16];\n    transpose_msg_vecs(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs);\n\n    __m256i v[16] = {\n        h_vecs[0],       h_vecs[1],        h_vecs[2],     h_vecs[3],\n        h_vecs[4],       h_vecs[5],        h_vecs[6],     h_vecs[7],\n        set1(IV[0]),     set1(IV[1]),      set1(IV[2]),   set1(IV[3]),\n        counter_low_vec, counter_high_vec, block_len_vec, block_flags_vec,\n    };\n    round_fn(v, msg_vecs, 0);\n    round_fn(v, msg_vecs, 1);\n    round_fn(v, msg_vecs, 2);\n    round_fn(v, msg_vecs, 3);\n    round_fn(v, msg_vecs, 4);\n    round_fn(v, msg_vecs, 5);\n    round_fn(v, msg_vecs, 6);\n    h_vecs[0] = xorv(v[0], v[8]);\n    h_vecs[1] = xorv(v[1], v[9]);\n    h_vecs[2] = xorv(v[2], v[10]);\n    h_vecs[3] = xorv(v[3], v[11]);\n    h_vecs[4] = xorv(v[4], v[12]);\n    h_vecs[5] = xorv(v[5], v[13]);\n    h_vecs[6] = xorv(v[6], v[14]);\n    h_vecs[7] = xorv(v[7], v[15]);\n\n    block_flags = flags;\n  }\n\n  transpose_vecs(h_vecs);\n  storeu(h_vecs[0], &out[0 * sizeof(__m256i)]);\n  storeu(h_vecs[1], &out[1 * sizeof(__m256i)]);\n  storeu(h_vecs[2], &out[2 * sizeof(__m256i)]);\n  storeu(h_vecs[3], &out[3 * sizeof(__m256i)]);\n  storeu(h_vecs[4], &out[4 * sizeof(__m256i)]);\n  storeu(h_vecs[5], &out[5 * sizeof(__m256i)]);\n  storeu(h_vecs[6], &out[6 * sizeof(__m256i)]);\n  storeu(h_vecs[7], &out[7 * sizeof(__m256i)]);\n}\n\n#if !defined(BLAKE3_NO_SSE41)\nvoid blake3_hash_many_sse41(const uint8_t *const *inputs, size_t num_inputs,\n                            size_t blocks, const uint32_t key[8],\n                            uint64_t counter, bool increment_counter,\n                            uint8_t flags, uint8_t flags_start,\n                            uint8_t flags_end, uint8_t *out);\n#else\nvoid blake3_hash_many_portable(const uint8_t *const *inputs, size_t num_inputs,\n                               size_t blocks, const uint32_t key[8],\n                               uint64_t counter, bool increment_counter,\n                               uint8_t flags, uint8_t flags_start,\n                               uint8_t flags_end, uint8_t *out);\n#endif\n\nvoid blake3_hash_many_avx2(const uint8_t *const *inputs, size_t num_inputs,\n                           size_t blocks, const uint32_t key[8],\n                           uint64_t counter, bool increment_counter,\n                           uint8_t flags, uint8_t flags_start,\n                           uint8_t flags_end, uint8_t *out) {\n  while (num_inputs >= DEGREE) {\n    blake3_hash8_avx2(inputs, blocks, key, counter, increment_counter, flags,\n                      flags_start, flags_end, out);\n    if (increment_counter) {\n      counter += DEGREE;\n    }\n    inputs += DEGREE;\n    num_inputs -= DEGREE;\n    out = &out[DEGREE * BLAKE3_OUT_LEN];\n  }\n#if !defined(BLAKE3_NO_SSE41)\n  blake3_hash_many_sse41(inputs, num_inputs, blocks, key, counter,\n                         increment_counter, flags, flags_start, flags_end, out);\n#else\n  blake3_hash_many_portable(inputs, num_inputs, blocks, key, counter,\n                            increment_counter, flags, flags_start, flags_end,\n                            out);\n#endif\n}\n"
  },
  {
    "path": "common/blake3/blake3_avx2_x86-64_unix.S",
    "content": "#if defined(__ELF__) && defined(__linux__)\n.section .note.GNU-stack,\"\",%progbits\n#endif\n\n#if defined(__ELF__) && defined(__CET__) && defined(__has_include)\n#if __has_include(<cet.h>)\n#include <cet.h>\n#endif\n#endif\n\n#if !defined(_CET_ENDBR)\n#define _CET_ENDBR\n#endif\n\n.intel_syntax noprefix\n.global _blake3_hash_many_avx2\n.global blake3_hash_many_avx2\n#ifdef __APPLE__\n.text\n#else\n.section .text\n#endif\n        .p2align  6\n_blake3_hash_many_avx2:\nblake3_hash_many_avx2:\n        _CET_ENDBR\n        push    r15\n        push    r14\n        push    r13\n        push    r12\n        push    rbx\n        push    rbp\n        mov     rbp, rsp\n        sub     rsp, 680\n        and     rsp, 0xFFFFFFFFFFFFFFC0\n        neg     r9d\n        vmovd   xmm0, r9d\n        vpbroadcastd ymm0, xmm0\n        vmovdqa ymmword ptr [rsp+0x280], ymm0\n        vpand   ymm1, ymm0, ymmword ptr [ADD0+rip]\n        vpand   ymm2, ymm0, ymmword ptr [ADD1+rip]\n        vmovdqa ymmword ptr [rsp+0x220], ymm2\n        vmovd   xmm2, r8d\n        vpbroadcastd ymm2, xmm2\n        vpaddd  ymm2, ymm2, ymm1\n        vmovdqa ymmword ptr [rsp+0x240], ymm2\n        vpxor   ymm1, ymm1, ymmword ptr [CMP_MSB_MASK+rip]\n        vpxor   ymm2, ymm2, ymmword ptr [CMP_MSB_MASK+rip]\n        vpcmpgtd ymm2, ymm1, ymm2\n        shr     r8, 32\n        vmovd   xmm3, r8d\n        vpbroadcastd ymm3, xmm3\n        vpsubd  ymm3, ymm3, ymm2\n        vmovdqa ymmword ptr [rsp+0x260], ymm3\n        shl     rdx, 6\n        mov     qword ptr [rsp+0x2A0], rdx\n        cmp     rsi, 8\n        jc      3f\n2:\n        vpbroadcastd ymm0, dword ptr [rcx]\n        vpbroadcastd ymm1, dword ptr [rcx+0x4]\n        vpbroadcastd ymm2, dword ptr [rcx+0x8]\n        vpbroadcastd ymm3, dword ptr [rcx+0xC]\n        vpbroadcastd ymm4, dword ptr [rcx+0x10]\n        vpbroadcastd ymm5, dword ptr [rcx+0x14]\n        vpbroadcastd ymm6, dword ptr [rcx+0x18]\n        vpbroadcastd ymm7, dword ptr [rcx+0x1C]\n        mov     r8, qword ptr [rdi]\n        mov     r9, qword ptr [rdi+0x8]\n        mov     r10, qword ptr [rdi+0x10]\n        mov     r11, qword ptr [rdi+0x18]\n        mov     r12, qword ptr [rdi+0x20]\n        mov     r13, qword ptr [rdi+0x28]\n        mov     r14, qword ptr [rdi+0x30]\n        mov     r15, qword ptr [rdi+0x38]\n        movzx   eax, byte ptr [rbp+0x38]\n        movzx   ebx, byte ptr [rbp+0x40]\n        or      eax, ebx\n        xor     edx, edx\n.p2align  5\n9:\n        movzx   ebx, byte ptr [rbp+0x48]\n        or      ebx, eax\n        add     rdx, 64\n        cmp     rdx, qword ptr [rsp+0x2A0]\n        cmove   eax, ebx\n        mov     dword ptr [rsp+0x200], eax\n        vmovups xmm8, xmmword ptr [r8+rdx-0x40]\n        vinsertf128 ymm8, ymm8, xmmword ptr [r12+rdx-0x40], 0x01\n        vmovups xmm9, xmmword ptr [r9+rdx-0x40]\n        vinsertf128 ymm9, ymm9, xmmword ptr [r13+rdx-0x40], 0x01\n        vunpcklpd ymm12, ymm8, ymm9\n        vunpckhpd ymm13, ymm8, ymm9\n        vmovups xmm10, xmmword ptr [r10+rdx-0x40]\n        vinsertf128 ymm10, ymm10, xmmword ptr [r14+rdx-0x40], 0x01\n        vmovups xmm11, xmmword ptr [r11+rdx-0x40]\n        vinsertf128 ymm11, ymm11, xmmword ptr [r15+rdx-0x40], 0x01\n        vunpcklpd ymm14, ymm10, ymm11\n        vunpckhpd ymm15, ymm10, ymm11\n        vshufps ymm8, ymm12, ymm14, 136\n        vmovaps ymmword ptr [rsp], ymm8\n        vshufps ymm9, ymm12, ymm14, 221\n        vmovaps ymmword ptr [rsp+0x20], ymm9\n        vshufps ymm10, ymm13, ymm15, 136\n        vmovaps ymmword ptr [rsp+0x40], ymm10\n        vshufps ymm11, ymm13, ymm15, 221\n        vmovaps ymmword ptr [rsp+0x60], ymm11\n        vmovups xmm8, xmmword ptr [r8+rdx-0x30]\n        vinsertf128 ymm8, ymm8, xmmword ptr [r12+rdx-0x30], 0x01\n        vmovups xmm9, xmmword ptr [r9+rdx-0x30]\n        vinsertf128 ymm9, ymm9, xmmword ptr [r13+rdx-0x30], 0x01\n        vunpcklpd ymm12, ymm8, ymm9\n        vunpckhpd ymm13, ymm8, ymm9\n        vmovups xmm10, xmmword ptr [r10+rdx-0x30]\n        vinsertf128 ymm10, ymm10, xmmword ptr [r14+rdx-0x30], 0x01\n        vmovups xmm11, xmmword ptr [r11+rdx-0x30]\n        vinsertf128 ymm11, ymm11, xmmword ptr [r15+rdx-0x30], 0x01\n        vunpcklpd ymm14, ymm10, ymm11\n        vunpckhpd ymm15, ymm10, ymm11\n        vshufps ymm8, ymm12, ymm14, 136\n        vmovaps ymmword ptr [rsp+0x80], ymm8\n        vshufps ymm9, ymm12, ymm14, 221\n        vmovaps ymmword ptr [rsp+0xA0], ymm9\n        vshufps ymm10, ymm13, ymm15, 136\n        vmovaps ymmword ptr [rsp+0xC0], ymm10\n        vshufps ymm11, ymm13, ymm15, 221\n        vmovaps ymmword ptr [rsp+0xE0], ymm11\n        vmovups xmm8, xmmword ptr [r8+rdx-0x20]\n        vinsertf128 ymm8, ymm8, xmmword ptr [r12+rdx-0x20], 0x01\n        vmovups xmm9, xmmword ptr [r9+rdx-0x20]\n        vinsertf128 ymm9, ymm9, xmmword ptr [r13+rdx-0x20], 0x01\n        vunpcklpd ymm12, ymm8, ymm9\n        vunpckhpd ymm13, ymm8, ymm9\n        vmovups xmm10, xmmword ptr [r10+rdx-0x20]\n        vinsertf128 ymm10, ymm10, xmmword ptr [r14+rdx-0x20], 0x01\n        vmovups xmm11, xmmword ptr [r11+rdx-0x20]\n        vinsertf128 ymm11, ymm11, xmmword ptr [r15+rdx-0x20], 0x01\n        vunpcklpd ymm14, ymm10, ymm11\n        vunpckhpd ymm15, ymm10, ymm11\n        vshufps ymm8, ymm12, ymm14, 136\n        vmovaps ymmword ptr [rsp+0x100], ymm8\n        vshufps ymm9, ymm12, ymm14, 221\n        vmovaps ymmword ptr [rsp+0x120], ymm9\n        vshufps ymm10, ymm13, ymm15, 136\n        vmovaps ymmword ptr [rsp+0x140], ymm10\n        vshufps ymm11, ymm13, ymm15, 221\n        vmovaps ymmword ptr [rsp+0x160], ymm11\n        vmovups xmm8, xmmword ptr [r8+rdx-0x10]\n        vinsertf128 ymm8, ymm8, xmmword ptr [r12+rdx-0x10], 0x01\n        vmovups xmm9, xmmword ptr [r9+rdx-0x10]\n        vinsertf128 ymm9, ymm9, xmmword ptr [r13+rdx-0x10], 0x01\n        vunpcklpd ymm12, ymm8, ymm9\n        vunpckhpd ymm13, ymm8, ymm9\n        vmovups xmm10, xmmword ptr [r10+rdx-0x10]\n        vinsertf128 ymm10, ymm10, xmmword ptr [r14+rdx-0x10], 0x01\n        vmovups xmm11, xmmword ptr [r11+rdx-0x10]\n        vinsertf128 ymm11, ymm11, xmmword ptr [r15+rdx-0x10], 0x01\n        vunpcklpd ymm14, ymm10, ymm11\n        vunpckhpd ymm15, ymm10, ymm11\n        vshufps ymm8, ymm12, ymm14, 136\n        vmovaps ymmword ptr [rsp+0x180], ymm8\n        vshufps ymm9, ymm12, ymm14, 221\n        vmovaps ymmword ptr [rsp+0x1A0], ymm9\n        vshufps ymm10, ymm13, ymm15, 136\n        vmovaps ymmword ptr [rsp+0x1C0], ymm10\n        vshufps ymm11, ymm13, ymm15, 221\n        vmovaps ymmword ptr [rsp+0x1E0], ymm11\n        vpbroadcastd ymm15, dword ptr [rsp+0x200]\n        prefetcht0 [r8+rdx+0x80]\n        prefetcht0 [r12+rdx+0x80]\n        prefetcht0 [r9+rdx+0x80]\n        prefetcht0 [r13+rdx+0x80]\n        prefetcht0 [r10+rdx+0x80]\n        prefetcht0 [r14+rdx+0x80]\n        prefetcht0 [r11+rdx+0x80]\n        prefetcht0 [r15+rdx+0x80]\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x40]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x80]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0xC0]\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxor   ymm12, ymm0, ymmword ptr [rsp+0x240]\n        vpxor   ymm13, ymm1, ymmword ptr [rsp+0x260]\n        vpxor   ymm14, ymm2, ymmword ptr [BLAKE3_BLOCK_LEN+rip]\n        vpxor   ymm15, ymm3, ymm15\n        vbroadcasti128 ymm8, xmmword ptr [ROT16+rip]\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpshufb ymm15, ymm15, ymm8\n        vpaddd  ymm8, ymm12, ymmword ptr [BLAKE3_IV_0+rip]\n        vpaddd  ymm9, ymm13, ymmword ptr [BLAKE3_IV_1+rip]\n        vpaddd  ymm10, ymm14, ymmword ptr [BLAKE3_IV_2+rip]\n        vpaddd  ymm11, ymm15, ymmword ptr [BLAKE3_IV_3+rip]\n        vpxor   ymm4, ymm4, ymm8\n        vpxor   ymm5, ymm5, ymm9\n        vpxor   ymm6, ymm6, ymm10\n        vpxor   ymm7, ymm7, ymm11\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm4, 12\n        vpslld  ymm4, ymm4, 20\n        vpor    ymm4, ymm4, ymm8\n        vpsrld  ymm8, ymm5, 12\n        vpslld  ymm5, ymm5, 20\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 12\n        vpslld  ymm6, ymm6, 20\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 12\n        vpslld  ymm7, ymm7, 20\n        vpor    ymm7, ymm7, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x20]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x60]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0xA0]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0xE0]\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxor   ymm12, ymm12, ymm0\n        vpxor   ymm13, ymm13, ymm1\n        vpxor   ymm14, ymm14, ymm2\n        vpxor   ymm15, ymm15, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT8+rip]\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpshufb ymm15, ymm15, ymm8\n        vpaddd  ymm8, ymm12, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxor   ymm4, ymm4, ymm8\n        vpxor   ymm5, ymm5, ymm9\n        vpxor   ymm6, ymm6, ymm10\n        vpxor   ymm7, ymm7, ymm11\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm4, 7\n        vpslld  ymm4, ymm4, 25\n        vpor    ymm4, ymm4, ymm8\n        vpsrld  ymm8, ymm5, 7\n        vpslld  ymm5, ymm5, 25\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 7\n        vpslld  ymm6, ymm6, 25\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 7\n        vpslld  ymm7, ymm7, 25\n        vpor    ymm7, ymm7, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x100]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x140]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x180]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x1C0]\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxor   ymm15, ymm15, ymm0\n        vpxor   ymm12, ymm12, ymm1\n        vpxor   ymm13, ymm13, ymm2\n        vpxor   ymm14, ymm14, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT16+rip]\n        vpshufb ymm15, ymm15, ymm8\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm13, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm14\n        vpxor   ymm5, ymm5, ymm10\n        vpxor   ymm6, ymm6, ymm11\n        vpxor   ymm7, ymm7, ymm8\n        vpxor   ymm4, ymm4, ymm9\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm5, 12\n        vpslld  ymm5, ymm5, 20\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 12\n        vpslld  ymm6, ymm6, 20\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 12\n        vpslld  ymm7, ymm7, 20\n        vpor    ymm7, ymm7, ymm8\n        vpsrld  ymm8, ymm4, 12\n        vpslld  ymm4, ymm4, 20\n        vpor    ymm4, ymm4, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x120]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x160]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x1A0]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x1E0]\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxor   ymm15, ymm15, ymm0\n        vpxor   ymm12, ymm12, ymm1\n        vpxor   ymm13, ymm13, ymm2\n        vpxor   ymm14, ymm14, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT8+rip]\n        vpshufb ymm15, ymm15, ymm8\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm13, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm14\n        vpxor   ymm5, ymm5, ymm10\n        vpxor   ymm6, ymm6, ymm11\n        vpxor   ymm7, ymm7, ymm8\n        vpxor   ymm4, ymm4, ymm9\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm5, 7\n        vpslld  ymm5, ymm5, 25\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 7\n        vpslld  ymm6, ymm6, 25\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 7\n        vpslld  ymm7, ymm7, 25\n        vpor    ymm7, ymm7, ymm8\n        vpsrld  ymm8, ymm4, 7\n        vpslld  ymm4, ymm4, 25\n        vpor    ymm4, ymm4, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x40]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x60]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0xE0]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x80]\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxor   ymm12, ymm12, ymm0\n        vpxor   ymm13, ymm13, ymm1\n        vpxor   ymm14, ymm14, ymm2\n        vpxor   ymm15, ymm15, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT16+rip]\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpshufb ymm15, ymm15, ymm8\n        vpaddd  ymm8, ymm12, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxor   ymm4, ymm4, ymm8\n        vpxor   ymm5, ymm5, ymm9\n        vpxor   ymm6, ymm6, ymm10\n        vpxor   ymm7, ymm7, ymm11\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm4, 12\n        vpslld  ymm4, ymm4, 20\n        vpor    ymm4, ymm4, ymm8\n        vpsrld  ymm8, ymm5, 12\n        vpslld  ymm5, ymm5, 20\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 12\n        vpslld  ymm6, ymm6, 20\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 12\n        vpslld  ymm7, ymm7, 20\n        vpor    ymm7, ymm7, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0xC0]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x140]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x1A0]\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxor   ymm12, ymm12, ymm0\n        vpxor   ymm13, ymm13, ymm1\n        vpxor   ymm14, ymm14, ymm2\n        vpxor   ymm15, ymm15, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT8+rip]\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpshufb ymm15, ymm15, ymm8\n        vpaddd  ymm8, ymm12, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxor   ymm4, ymm4, ymm8\n        vpxor   ymm5, ymm5, ymm9\n        vpxor   ymm6, ymm6, ymm10\n        vpxor   ymm7, ymm7, ymm11\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm4, 7\n        vpslld  ymm4, ymm4, 25\n        vpor    ymm4, ymm4, ymm8\n        vpsrld  ymm8, ymm5, 7\n        vpslld  ymm5, ymm5, 25\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 7\n        vpslld  ymm6, ymm6, 25\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 7\n        vpslld  ymm7, ymm7, 25\n        vpor    ymm7, ymm7, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x20]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x180]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x120]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x1E0]\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxor   ymm15, ymm15, ymm0\n        vpxor   ymm12, ymm12, ymm1\n        vpxor   ymm13, ymm13, ymm2\n        vpxor   ymm14, ymm14, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT16+rip]\n        vpshufb ymm15, ymm15, ymm8\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm13, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm14\n        vpxor   ymm5, ymm5, ymm10\n        vpxor   ymm6, ymm6, ymm11\n        vpxor   ymm7, ymm7, ymm8\n        vpxor   ymm4, ymm4, ymm9\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm5, 12\n        vpslld  ymm5, ymm5, 20\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 12\n        vpslld  ymm6, ymm6, 20\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 12\n        vpslld  ymm7, ymm7, 20\n        vpor    ymm7, ymm7, ymm8\n        vpsrld  ymm8, ymm4, 12\n        vpslld  ymm4, ymm4, 20\n        vpor    ymm4, ymm4, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x160]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0xA0]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x1C0]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x100]\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxor   ymm15, ymm15, ymm0\n        vpxor   ymm12, ymm12, ymm1\n        vpxor   ymm13, ymm13, ymm2\n        vpxor   ymm14, ymm14, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT8+rip]\n        vpshufb ymm15, ymm15, ymm8\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm13, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm14\n        vpxor   ymm5, ymm5, ymm10\n        vpxor   ymm6, ymm6, ymm11\n        vpxor   ymm7, ymm7, ymm8\n        vpxor   ymm4, ymm4, ymm9\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm5, 7\n        vpslld  ymm5, ymm5, 25\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 7\n        vpslld  ymm6, ymm6, 25\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 7\n        vpslld  ymm7, ymm7, 25\n        vpor    ymm7, ymm7, ymm8\n        vpsrld  ymm8, ymm4, 7\n        vpslld  ymm4, ymm4, 25\n        vpor    ymm4, ymm4, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x60]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x140]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x1A0]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0xE0]\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxor   ymm12, ymm12, ymm0\n        vpxor   ymm13, ymm13, ymm1\n        vpxor   ymm14, ymm14, ymm2\n        vpxor   ymm15, ymm15, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT16+rip]\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpshufb ymm15, ymm15, ymm8\n        vpaddd  ymm8, ymm12, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxor   ymm4, ymm4, ymm8\n        vpxor   ymm5, ymm5, ymm9\n        vpxor   ymm6, ymm6, ymm10\n        vpxor   ymm7, ymm7, ymm11\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm4, 12\n        vpslld  ymm4, ymm4, 20\n        vpor    ymm4, ymm4, ymm8\n        vpsrld  ymm8, ymm5, 12\n        vpslld  ymm5, ymm5, 20\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 12\n        vpslld  ymm6, ymm6, 20\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 12\n        vpslld  ymm7, ymm7, 20\n        vpor    ymm7, ymm7, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x80]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x180]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x40]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x1C0]\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxor   ymm12, ymm12, ymm0\n        vpxor   ymm13, ymm13, ymm1\n        vpxor   ymm14, ymm14, ymm2\n        vpxor   ymm15, ymm15, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT8+rip]\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpshufb ymm15, ymm15, ymm8\n        vpaddd  ymm8, ymm12, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxor   ymm4, ymm4, ymm8\n        vpxor   ymm5, ymm5, ymm9\n        vpxor   ymm6, ymm6, ymm10\n        vpxor   ymm7, ymm7, ymm11\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm4, 7\n        vpslld  ymm4, ymm4, 25\n        vpor    ymm4, ymm4, ymm8\n        vpsrld  ymm8, ymm5, 7\n        vpslld  ymm5, ymm5, 25\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 7\n        vpslld  ymm6, ymm6, 25\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 7\n        vpslld  ymm7, ymm7, 25\n        vpor    ymm7, ymm7, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0xC0]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x120]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x160]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x100]\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxor   ymm15, ymm15, ymm0\n        vpxor   ymm12, ymm12, ymm1\n        vpxor   ymm13, ymm13, ymm2\n        vpxor   ymm14, ymm14, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT16+rip]\n        vpshufb ymm15, ymm15, ymm8\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm13, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm14\n        vpxor   ymm5, ymm5, ymm10\n        vpxor   ymm6, ymm6, ymm11\n        vpxor   ymm7, ymm7, ymm8\n        vpxor   ymm4, ymm4, ymm9\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm5, 12\n        vpslld  ymm5, ymm5, 20\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 12\n        vpslld  ymm6, ymm6, 20\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 12\n        vpslld  ymm7, ymm7, 20\n        vpor    ymm7, ymm7, ymm8\n        vpsrld  ymm8, ymm4, 12\n        vpslld  ymm4, ymm4, 20\n        vpor    ymm4, ymm4, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0xA0]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x1E0]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x20]\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxor   ymm15, ymm15, ymm0\n        vpxor   ymm12, ymm12, ymm1\n        vpxor   ymm13, ymm13, ymm2\n        vpxor   ymm14, ymm14, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT8+rip]\n        vpshufb ymm15, ymm15, ymm8\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm13, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm14\n        vpxor   ymm5, ymm5, ymm10\n        vpxor   ymm6, ymm6, ymm11\n        vpxor   ymm7, ymm7, ymm8\n        vpxor   ymm4, ymm4, ymm9\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm5, 7\n        vpslld  ymm5, ymm5, 25\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 7\n        vpslld  ymm6, ymm6, 25\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 7\n        vpslld  ymm7, ymm7, 25\n        vpor    ymm7, ymm7, ymm8\n        vpsrld  ymm8, ymm4, 7\n        vpslld  ymm4, ymm4, 25\n        vpor    ymm4, ymm4, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x140]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x180]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x1C0]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x1A0]\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxor   ymm12, ymm12, ymm0\n        vpxor   ymm13, ymm13, ymm1\n        vpxor   ymm14, ymm14, ymm2\n        vpxor   ymm15, ymm15, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT16+rip]\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpshufb ymm15, ymm15, ymm8\n        vpaddd  ymm8, ymm12, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxor   ymm4, ymm4, ymm8\n        vpxor   ymm5, ymm5, ymm9\n        vpxor   ymm6, ymm6, ymm10\n        vpxor   ymm7, ymm7, ymm11\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm4, 12\n        vpslld  ymm4, ymm4, 20\n        vpor    ymm4, ymm4, ymm8\n        vpsrld  ymm8, ymm5, 12\n        vpslld  ymm5, ymm5, 20\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 12\n        vpslld  ymm6, ymm6, 20\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 12\n        vpslld  ymm7, ymm7, 20\n        vpor    ymm7, ymm7, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0xE0]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x120]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x60]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x1E0]\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxor   ymm12, ymm12, ymm0\n        vpxor   ymm13, ymm13, ymm1\n        vpxor   ymm14, ymm14, ymm2\n        vpxor   ymm15, ymm15, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT8+rip]\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpshufb ymm15, ymm15, ymm8\n        vpaddd  ymm8, ymm12, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxor   ymm4, ymm4, ymm8\n        vpxor   ymm5, ymm5, ymm9\n        vpxor   ymm6, ymm6, ymm10\n        vpxor   ymm7, ymm7, ymm11\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm4, 7\n        vpslld  ymm4, ymm4, 25\n        vpor    ymm4, ymm4, ymm8\n        vpsrld  ymm8, ymm5, 7\n        vpslld  ymm5, ymm5, 25\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 7\n        vpslld  ymm6, ymm6, 25\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 7\n        vpslld  ymm7, ymm7, 25\n        vpor    ymm7, ymm7, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x80]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x160]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0xA0]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x20]\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxor   ymm15, ymm15, ymm0\n        vpxor   ymm12, ymm12, ymm1\n        vpxor   ymm13, ymm13, ymm2\n        vpxor   ymm14, ymm14, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT16+rip]\n        vpshufb ymm15, ymm15, ymm8\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm13, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm14\n        vpxor   ymm5, ymm5, ymm10\n        vpxor   ymm6, ymm6, ymm11\n        vpxor   ymm7, ymm7, ymm8\n        vpxor   ymm4, ymm4, ymm9\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm5, 12\n        vpslld  ymm5, ymm5, 20\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 12\n        vpslld  ymm6, ymm6, 20\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 12\n        vpslld  ymm7, ymm7, 20\n        vpor    ymm7, ymm7, ymm8\n        vpsrld  ymm8, ymm4, 12\n        vpslld  ymm4, ymm4, 20\n        vpor    ymm4, ymm4, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x40]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x100]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0xC0]\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxor   ymm15, ymm15, ymm0\n        vpxor   ymm12, ymm12, ymm1\n        vpxor   ymm13, ymm13, ymm2\n        vpxor   ymm14, ymm14, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT8+rip]\n        vpshufb ymm15, ymm15, ymm8\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm13, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm14\n        vpxor   ymm5, ymm5, ymm10\n        vpxor   ymm6, ymm6, ymm11\n        vpxor   ymm7, ymm7, ymm8\n        vpxor   ymm4, ymm4, ymm9\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm5, 7\n        vpslld  ymm5, ymm5, 25\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 7\n        vpslld  ymm6, ymm6, 25\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 7\n        vpslld  ymm7, ymm7, 25\n        vpor    ymm7, ymm7, ymm8\n        vpsrld  ymm8, ymm4, 7\n        vpslld  ymm4, ymm4, 25\n        vpor    ymm4, ymm4, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x180]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x120]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x1E0]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x1C0]\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxor   ymm12, ymm12, ymm0\n        vpxor   ymm13, ymm13, ymm1\n        vpxor   ymm14, ymm14, ymm2\n        vpxor   ymm15, ymm15, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT16+rip]\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpshufb ymm15, ymm15, ymm8\n        vpaddd  ymm8, ymm12, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxor   ymm4, ymm4, ymm8\n        vpxor   ymm5, ymm5, ymm9\n        vpxor   ymm6, ymm6, ymm10\n        vpxor   ymm7, ymm7, ymm11\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm4, 12\n        vpslld  ymm4, ymm4, 20\n        vpor    ymm4, ymm4, ymm8\n        vpsrld  ymm8, ymm5, 12\n        vpslld  ymm5, ymm5, 20\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 12\n        vpslld  ymm6, ymm6, 20\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 12\n        vpslld  ymm7, ymm7, 20\n        vpor    ymm7, ymm7, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x1A0]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x160]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x140]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x100]\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxor   ymm12, ymm12, ymm0\n        vpxor   ymm13, ymm13, ymm1\n        vpxor   ymm14, ymm14, ymm2\n        vpxor   ymm15, ymm15, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT8+rip]\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpshufb ymm15, ymm15, ymm8\n        vpaddd  ymm8, ymm12, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxor   ymm4, ymm4, ymm8\n        vpxor   ymm5, ymm5, ymm9\n        vpxor   ymm6, ymm6, ymm10\n        vpxor   ymm7, ymm7, ymm11\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm4, 7\n        vpslld  ymm4, ymm4, 25\n        vpor    ymm4, ymm4, ymm8\n        vpsrld  ymm8, ymm5, 7\n        vpslld  ymm5, ymm5, 25\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 7\n        vpslld  ymm6, ymm6, 25\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 7\n        vpslld  ymm7, ymm7, 25\n        vpor    ymm7, ymm7, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0xE0]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0xA0]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0xC0]\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxor   ymm15, ymm15, ymm0\n        vpxor   ymm12, ymm12, ymm1\n        vpxor   ymm13, ymm13, ymm2\n        vpxor   ymm14, ymm14, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT16+rip]\n        vpshufb ymm15, ymm15, ymm8\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm13, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm14\n        vpxor   ymm5, ymm5, ymm10\n        vpxor   ymm6, ymm6, ymm11\n        vpxor   ymm7, ymm7, ymm8\n        vpxor   ymm4, ymm4, ymm9\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm5, 12\n        vpslld  ymm5, ymm5, 20\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 12\n        vpslld  ymm6, ymm6, 20\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 12\n        vpslld  ymm7, ymm7, 20\n        vpor    ymm7, ymm7, ymm8\n        vpsrld  ymm8, ymm4, 12\n        vpslld  ymm4, ymm4, 20\n        vpor    ymm4, ymm4, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x40]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x60]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x20]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x80]\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxor   ymm15, ymm15, ymm0\n        vpxor   ymm12, ymm12, ymm1\n        vpxor   ymm13, ymm13, ymm2\n        vpxor   ymm14, ymm14, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT8+rip]\n        vpshufb ymm15, ymm15, ymm8\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm13, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm14\n        vpxor   ymm5, ymm5, ymm10\n        vpxor   ymm6, ymm6, ymm11\n        vpxor   ymm7, ymm7, ymm8\n        vpxor   ymm4, ymm4, ymm9\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm5, 7\n        vpslld  ymm5, ymm5, 25\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 7\n        vpslld  ymm6, ymm6, 25\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 7\n        vpslld  ymm7, ymm7, 25\n        vpor    ymm7, ymm7, ymm8\n        vpsrld  ymm8, ymm4, 7\n        vpslld  ymm4, ymm4, 25\n        vpor    ymm4, ymm4, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x120]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x160]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x100]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x1E0]\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxor   ymm12, ymm12, ymm0\n        vpxor   ymm13, ymm13, ymm1\n        vpxor   ymm14, ymm14, ymm2\n        vpxor   ymm15, ymm15, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT16+rip]\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpshufb ymm15, ymm15, ymm8\n        vpaddd  ymm8, ymm12, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxor   ymm4, ymm4, ymm8\n        vpxor   ymm5, ymm5, ymm9\n        vpxor   ymm6, ymm6, ymm10\n        vpxor   ymm7, ymm7, ymm11\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm4, 12\n        vpslld  ymm4, ymm4, 20\n        vpor    ymm4, ymm4, ymm8\n        vpsrld  ymm8, ymm5, 12\n        vpslld  ymm5, ymm5, 20\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 12\n        vpslld  ymm6, ymm6, 20\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 12\n        vpslld  ymm7, ymm7, 20\n        vpor    ymm7, ymm7, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x1C0]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0xA0]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x180]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x20]\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxor   ymm12, ymm12, ymm0\n        vpxor   ymm13, ymm13, ymm1\n        vpxor   ymm14, ymm14, ymm2\n        vpxor   ymm15, ymm15, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT8+rip]\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpshufb ymm15, ymm15, ymm8\n        vpaddd  ymm8, ymm12, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxor   ymm4, ymm4, ymm8\n        vpxor   ymm5, ymm5, ymm9\n        vpxor   ymm6, ymm6, ymm10\n        vpxor   ymm7, ymm7, ymm11\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm4, 7\n        vpslld  ymm4, ymm4, 25\n        vpor    ymm4, ymm4, ymm8\n        vpsrld  ymm8, ymm5, 7\n        vpslld  ymm5, ymm5, 25\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 7\n        vpslld  ymm6, ymm6, 25\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 7\n        vpslld  ymm7, ymm7, 25\n        vpor    ymm7, ymm7, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x1A0]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x40]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x80]\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxor   ymm15, ymm15, ymm0\n        vpxor   ymm12, ymm12, ymm1\n        vpxor   ymm13, ymm13, ymm2\n        vpxor   ymm14, ymm14, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT16+rip]\n        vpshufb ymm15, ymm15, ymm8\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm13, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm14\n        vpxor   ymm5, ymm5, ymm10\n        vpxor   ymm6, ymm6, ymm11\n        vpxor   ymm7, ymm7, ymm8\n        vpxor   ymm4, ymm4, ymm9\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm5, 12\n        vpslld  ymm5, ymm5, 20\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 12\n        vpslld  ymm6, ymm6, 20\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 12\n        vpslld  ymm7, ymm7, 20\n        vpor    ymm7, ymm7, ymm8\n        vpsrld  ymm8, ymm4, 12\n        vpslld  ymm4, ymm4, 20\n        vpor    ymm4, ymm4, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x60]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x140]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0xC0]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0xE0]\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxor   ymm15, ymm15, ymm0\n        vpxor   ymm12, ymm12, ymm1\n        vpxor   ymm13, ymm13, ymm2\n        vpxor   ymm14, ymm14, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT8+rip]\n        vpshufb ymm15, ymm15, ymm8\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm13, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm14\n        vpxor   ymm5, ymm5, ymm10\n        vpxor   ymm6, ymm6, ymm11\n        vpxor   ymm7, ymm7, ymm8\n        vpxor   ymm4, ymm4, ymm9\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm5, 7\n        vpslld  ymm5, ymm5, 25\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 7\n        vpslld  ymm6, ymm6, 25\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 7\n        vpslld  ymm7, ymm7, 25\n        vpor    ymm7, ymm7, ymm8\n        vpsrld  ymm8, ymm4, 7\n        vpslld  ymm4, ymm4, 25\n        vpor    ymm4, ymm4, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x160]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0xA0]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x20]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x100]\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxor   ymm12, ymm12, ymm0\n        vpxor   ymm13, ymm13, ymm1\n        vpxor   ymm14, ymm14, ymm2\n        vpxor   ymm15, ymm15, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT16+rip]\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpshufb ymm15, ymm15, ymm8\n        vpaddd  ymm8, ymm12, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxor   ymm4, ymm4, ymm8\n        vpxor   ymm5, ymm5, ymm9\n        vpxor   ymm6, ymm6, ymm10\n        vpxor   ymm7, ymm7, ymm11\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm4, 12\n        vpslld  ymm4, ymm4, 20\n        vpor    ymm4, ymm4, ymm8\n        vpsrld  ymm8, ymm5, 12\n        vpslld  ymm5, ymm5, 20\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 12\n        vpslld  ymm6, ymm6, 20\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 12\n        vpslld  ymm7, ymm7, 20\n        vpor    ymm7, ymm7, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x1E0]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x120]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0xC0]\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxor   ymm12, ymm12, ymm0\n        vpxor   ymm13, ymm13, ymm1\n        vpxor   ymm14, ymm14, ymm2\n        vpxor   ymm15, ymm15, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT8+rip]\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpshufb ymm15, ymm15, ymm8\n        vpaddd  ymm8, ymm12, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxor   ymm4, ymm4, ymm8\n        vpxor   ymm5, ymm5, ymm9\n        vpxor   ymm6, ymm6, ymm10\n        vpxor   ymm7, ymm7, ymm11\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm4, 7\n        vpslld  ymm4, ymm4, 25\n        vpor    ymm4, ymm4, ymm8\n        vpsrld  ymm8, ymm5, 7\n        vpslld  ymm5, ymm5, 25\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 7\n        vpslld  ymm6, ymm6, 25\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 7\n        vpslld  ymm7, ymm7, 25\n        vpor    ymm7, ymm7, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x1C0]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x40]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x60]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0xE0]\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxor   ymm15, ymm15, ymm0\n        vpxor   ymm12, ymm12, ymm1\n        vpxor   ymm13, ymm13, ymm2\n        vpxor   ymm14, ymm14, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT16+rip]\n        vpshufb ymm15, ymm15, ymm8\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm13, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm14\n        vpxor   ymm5, ymm5, ymm10\n        vpxor   ymm6, ymm6, ymm11\n        vpxor   ymm7, ymm7, ymm8\n        vpxor   ymm4, ymm4, ymm9\n        vmovdqa ymmword ptr [rsp+0x200], ymm8\n        vpsrld  ymm8, ymm5, 12\n        vpslld  ymm5, ymm5, 20\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 12\n        vpslld  ymm6, ymm6, 20\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 12\n        vpslld  ymm7, ymm7, 20\n        vpor    ymm7, ymm7, ymm8\n        vpsrld  ymm8, ymm4, 12\n        vpslld  ymm4, ymm4, 20\n        vpor    ymm4, ymm4, ymm8\n        vpaddd  ymm0, ymm0, ymmword ptr [rsp+0x140]\n        vpaddd  ymm1, ymm1, ymmword ptr [rsp+0x180]\n        vpaddd  ymm2, ymm2, ymmword ptr [rsp+0x80]\n        vpaddd  ymm3, ymm3, ymmword ptr [rsp+0x1A0]\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxor   ymm15, ymm15, ymm0\n        vpxor   ymm12, ymm12, ymm1\n        vpxor   ymm13, ymm13, ymm2\n        vpxor   ymm14, ymm14, ymm3\n        vbroadcasti128 ymm8, xmmword ptr [ROT8+rip]\n        vpshufb ymm15, ymm15, ymm8\n        vpshufb ymm12, ymm12, ymm8\n        vpshufb ymm13, ymm13, ymm8\n        vpshufb ymm14, ymm14, ymm8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm13, ymmword ptr [rsp+0x200]\n        vpaddd  ymm9, ymm9, ymm14\n        vpxor   ymm5, ymm5, ymm10\n        vpxor   ymm6, ymm6, ymm11\n        vpxor   ymm7, ymm7, ymm8\n        vpxor   ymm4, ymm4, ymm9\n        vpxor   ymm0, ymm0, ymm8\n        vpxor   ymm1, ymm1, ymm9\n        vpxor   ymm2, ymm2, ymm10\n        vpxor   ymm3, ymm3, ymm11\n        vpsrld  ymm8, ymm5, 7\n        vpslld  ymm5, ymm5, 25\n        vpor    ymm5, ymm5, ymm8\n        vpsrld  ymm8, ymm6, 7\n        vpslld  ymm6, ymm6, 25\n        vpor    ymm6, ymm6, ymm8\n        vpsrld  ymm8, ymm7, 7\n        vpslld  ymm7, ymm7, 25\n        vpor    ymm7, ymm7, ymm8\n        vpsrld  ymm8, ymm4, 7\n        vpslld  ymm4, ymm4, 25\n        vpor    ymm4, ymm4, ymm8\n        vpxor   ymm4, ymm4, ymm12\n        vpxor   ymm5, ymm5, ymm13\n        vpxor   ymm6, ymm6, ymm14\n        vpxor   ymm7, ymm7, ymm15\n        movzx   eax, byte ptr [rbp+0x38]\n        jne     9b\n        mov     rbx, qword ptr [rbp+0x50]\n        vunpcklps ymm8, ymm0, ymm1\n        vunpcklps ymm9, ymm2, ymm3\n        vunpckhps ymm10, ymm0, ymm1\n        vunpcklps ymm11, ymm4, ymm5\n        vunpcklps ymm0, ymm6, ymm7\n        vshufps ymm12, ymm8, ymm9, 78\n        vblendps ymm1, ymm8, ymm12, 0xCC\n        vshufps ymm8, ymm11, ymm0, 78\n        vunpckhps ymm13, ymm2, ymm3\n        vblendps ymm2, ymm11, ymm8, 0xCC\n        vblendps ymm3, ymm12, ymm9, 0xCC\n        vperm2f128 ymm12, ymm1, ymm2, 0x20\n        vmovups ymmword ptr [rbx], ymm12\n        vunpckhps ymm14, ymm4, ymm5\n        vblendps ymm4, ymm8, ymm0, 0xCC\n        vunpckhps ymm15, ymm6, ymm7\n        vperm2f128 ymm7, ymm3, ymm4, 0x20\n        vmovups ymmword ptr [rbx+0x20], ymm7\n        vshufps ymm5, ymm10, ymm13, 78\n        vblendps ymm6, ymm5, ymm13, 0xCC\n        vshufps ymm13, ymm14, ymm15, 78\n        vblendps ymm10, ymm10, ymm5, 0xCC\n        vblendps ymm14, ymm14, ymm13, 0xCC\n        vperm2f128 ymm8, ymm10, ymm14, 0x20\n        vmovups ymmword ptr [rbx+0x40], ymm8\n        vblendps ymm15, ymm13, ymm15, 0xCC\n        vperm2f128 ymm13, ymm6, ymm15, 0x20\n        vmovups ymmword ptr [rbx+0x60], ymm13\n        vperm2f128 ymm9, ymm1, ymm2, 0x31\n        vperm2f128 ymm11, ymm3, ymm4, 0x31\n        vmovups ymmword ptr [rbx+0x80], ymm9\n        vperm2f128 ymm14, ymm10, ymm14, 0x31\n        vperm2f128 ymm15, ymm6, ymm15, 0x31\n        vmovups ymmword ptr [rbx+0xA0], ymm11\n        vmovups ymmword ptr [rbx+0xC0], ymm14\n        vmovups ymmword ptr [rbx+0xE0], ymm15\n        vmovdqa ymm0, ymmword ptr [rsp+0x220]\n        vpaddd  ymm1, ymm0, ymmword ptr [rsp+0x240]\n        vmovdqa ymmword ptr [rsp+0x240], ymm1\n        vpxor   ymm0, ymm0, ymmword ptr [CMP_MSB_MASK+rip]\n        vpxor   ymm2, ymm1, ymmword ptr [CMP_MSB_MASK+rip]\n        vpcmpgtd ymm2, ymm0, ymm2\n        vmovdqa ymm0, ymmword ptr [rsp+0x260]\n        vpsubd  ymm2, ymm0, ymm2\n        vmovdqa ymmword ptr [rsp+0x260], ymm2\n        add     rdi, 64\n        add     rbx, 256\n        mov     qword ptr [rbp+0x50], rbx\n        sub     rsi, 8\n        cmp     rsi, 8\n        jnc     2b\n        test    rsi, rsi\n        jnz     3f\n4:\n        vzeroupper\n        mov     rsp, rbp\n        pop     rbp\n        pop     rbx\n        pop     r12\n        pop     r13\n        pop     r14\n        pop     r15\n        ret\n.p2align  5\n3:\n        mov     rbx, qword ptr [rbp+0x50]\n        mov     r15, qword ptr [rsp+0x2A0]\n        movzx   r13d, byte ptr [rbp+0x38]\n        movzx   r12d, byte ptr [rbp+0x48]\n        test    rsi, 0x4\n        je      3f\n        vbroadcasti128 ymm0, xmmword ptr [rcx]\n        vbroadcasti128 ymm1, xmmword ptr [rcx+0x10]\n        vmovdqa ymm8, ymm0\n        vmovdqa ymm9, ymm1\n        vbroadcasti128 ymm12, xmmword ptr [rsp+0x240]\n        vbroadcasti128 ymm13, xmmword ptr [rsp+0x260]\n        vpunpckldq ymm14, ymm12, ymm13\n        vpunpckhdq ymm15, ymm12, ymm13\n        vpermq  ymm14, ymm14, 0x50\n        vpermq  ymm15, ymm15, 0x50\n        vbroadcasti128 ymm12, xmmword ptr [BLAKE3_BLOCK_LEN+rip]\n        vpblendd ymm14, ymm14, ymm12, 0x44\n        vpblendd ymm15, ymm15, ymm12, 0x44\n        vmovdqa ymmword ptr [rsp], ymm14\n        vmovdqa ymmword ptr [rsp+0x20], ymm15\n        mov     r8, qword ptr [rdi]\n        mov     r9, qword ptr [rdi+0x8]\n        mov     r10, qword ptr [rdi+0x10]\n        mov     r11, qword ptr [rdi+0x18]\n        movzx   eax, byte ptr [rbp+0x40]\n        or      eax, r13d\n        xor     edx, edx\n.p2align  5\n2:\n        mov     r14d, eax\n        or      eax, r12d\n        add     rdx, 64\n        cmp     rdx, r15\n        cmovne  eax, r14d\n        mov     dword ptr [rsp+0x200], eax\n        vmovups ymm2, ymmword ptr [r8+rdx-0x40]\n        vinsertf128 ymm2, ymm2, xmmword ptr [r9+rdx-0x40], 0x01\n        vmovups ymm3, ymmword ptr [r8+rdx-0x30]\n        vinsertf128 ymm3, ymm3, xmmword ptr [r9+rdx-0x30], 0x01\n        vshufps ymm4, ymm2, ymm3, 136\n        vshufps ymm5, ymm2, ymm3, 221\n        vmovups ymm2, ymmword ptr [r8+rdx-0x20]\n        vinsertf128 ymm2, ymm2, xmmword ptr [r9+rdx-0x20], 0x01\n        vmovups ymm3, ymmword ptr [r8+rdx-0x10]\n        vinsertf128 ymm3, ymm3, xmmword ptr [r9+rdx-0x10], 0x01\n        vshufps ymm6, ymm2, ymm3, 136\n        vshufps ymm7, ymm2, ymm3, 221\n        vpshufd ymm6, ymm6, 0x93\n        vpshufd ymm7, ymm7, 0x93\n        vmovups ymm10, ymmword ptr [r10+rdx-0x40]\n        vinsertf128 ymm10, ymm10, xmmword ptr [r11+rdx-0x40], 0x01\n        vmovups ymm11, ymmword ptr [r10+rdx-0x30]\n        vinsertf128 ymm11, ymm11, xmmword ptr [r11+rdx-0x30], 0x01\n        vshufps ymm12, ymm10, ymm11, 136\n        vshufps ymm13, ymm10, ymm11, 221\n        vmovups ymm10, ymmword ptr [r10+rdx-0x20]\n        vinsertf128 ymm10, ymm10, xmmword ptr [r11+rdx-0x20], 0x01\n        vmovups ymm11, ymmword ptr [r10+rdx-0x10]\n        vinsertf128 ymm11, ymm11, xmmword ptr [r11+rdx-0x10], 0x01\n        vshufps ymm14, ymm10, ymm11, 136\n        vshufps ymm15, ymm10, ymm11, 221\n        vpshufd ymm14, ymm14, 0x93\n        vpshufd ymm15, ymm15, 0x93\n        prefetcht0 [r8+rdx+0x80]\n        prefetcht0 [r9+rdx+0x80]\n        prefetcht0 [r10+rdx+0x80]\n        prefetcht0 [r11+rdx+0x80]\n        vpbroadcastd ymm2, dword ptr [rsp+0x200]\n        vmovdqa ymm3, ymmword ptr [rsp]\n        vmovdqa ymm11, ymmword ptr [rsp+0x20]\n        vpblendd ymm3, ymm3, ymm2, 0x88\n        vpblendd ymm11, ymm11, ymm2, 0x88\n        vbroadcasti128 ymm2, xmmword ptr [BLAKE3_IV+rip]\n        vmovdqa ymm10, ymm2\n        mov     al, 7\n9:\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm8, ymm8, ymm12\n        vmovdqa ymmword ptr [rsp+0x40], ymm4\n        nop\n        vmovdqa ymmword ptr [rsp+0x60], ymm12\n        nop\n        vpaddd  ymm0, ymm0, ymm1\n        vpaddd  ymm8, ymm8, ymm9\n        vpxor   ymm3, ymm3, ymm0\n        vpxor   ymm11, ymm11, ymm8\n        vbroadcasti128 ymm4, xmmword ptr [ROT16+rip]\n        vpshufb ymm3, ymm3, ymm4\n        vpshufb ymm11, ymm11, ymm4\n        vpaddd  ymm2, ymm2, ymm3\n        vpaddd  ymm10, ymm10, ymm11\n        vpxor   ymm1, ymm1, ymm2\n        vpxor   ymm9, ymm9, ymm10\n        vpsrld  ymm4, ymm1, 12\n        vpslld  ymm1, ymm1, 20\n        vpor    ymm1, ymm1, ymm4\n        vpsrld  ymm4, ymm9, 12\n        vpslld  ymm9, ymm9, 20\n        vpor    ymm9, ymm9, ymm4\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm8, ymm8, ymm13\n        vpaddd  ymm0, ymm0, ymm1\n        vpaddd  ymm8, ymm8, ymm9\n        vmovdqa ymmword ptr [rsp+0x80], ymm5\n        vmovdqa ymmword ptr [rsp+0xA0], ymm13\n        vpxor   ymm3, ymm3, ymm0\n        vpxor   ymm11, ymm11, ymm8\n        vbroadcasti128 ymm4, xmmword ptr [ROT8+rip]\n        vpshufb ymm3, ymm3, ymm4\n        vpshufb ymm11, ymm11, ymm4\n        vpaddd  ymm2, ymm2, ymm3\n        vpaddd  ymm10, ymm10, ymm11\n        vpxor   ymm1, ymm1, ymm2\n        vpxor   ymm9, ymm9, ymm10\n        vpsrld  ymm4, ymm1, 7\n        vpslld  ymm1, ymm1, 25\n        vpor    ymm1, ymm1, ymm4\n        vpsrld  ymm4, ymm9, 7\n        vpslld  ymm9, ymm9, 25\n        vpor    ymm9, ymm9, ymm4\n        vpshufd ymm0, ymm0, 0x93\n        vpshufd ymm8, ymm8, 0x93\n        vpshufd ymm3, ymm3, 0x4E\n        vpshufd ymm11, ymm11, 0x4E\n        vpshufd ymm2, ymm2, 0x39\n        vpshufd ymm10, ymm10, 0x39\n        vpaddd  ymm0, ymm0, ymm6\n        vpaddd  ymm8, ymm8, ymm14\n        vpaddd  ymm0, ymm0, ymm1\n        vpaddd  ymm8, ymm8, ymm9\n        vpxor   ymm3, ymm3, ymm0\n        vpxor   ymm11, ymm11, ymm8\n        vbroadcasti128 ymm4, xmmword ptr [ROT16+rip]\n        vpshufb ymm3, ymm3, ymm4\n        vpshufb ymm11, ymm11, ymm4\n        vpaddd  ymm2, ymm2, ymm3\n        vpaddd  ymm10, ymm10, ymm11\n        vpxor   ymm1, ymm1, ymm2\n        vpxor   ymm9, ymm9, ymm10\n        vpsrld  ymm4, ymm1, 12\n        vpslld  ymm1, ymm1, 20\n        vpor    ymm1, ymm1, ymm4\n        vpsrld  ymm4, ymm9, 12\n        vpslld  ymm9, ymm9, 20\n        vpor    ymm9, ymm9, ymm4\n        vpaddd  ymm0, ymm0, ymm7\n        vpaddd  ymm8, ymm8, ymm15\n        vpaddd  ymm0, ymm0, ymm1\n        vpaddd  ymm8, ymm8, ymm9\n        vpxor   ymm3, ymm3, ymm0\n        vpxor   ymm11, ymm11, ymm8\n        vbroadcasti128 ymm4, xmmword ptr [ROT8+rip]\n        vpshufb ymm3, ymm3, ymm4\n        vpshufb ymm11, ymm11, ymm4\n        vpaddd  ymm2, ymm2, ymm3\n        vpaddd  ymm10, ymm10, ymm11\n        vpxor   ymm1, ymm1, ymm2\n        vpxor   ymm9, ymm9, ymm10\n        vpsrld  ymm4, ymm1, 7\n        vpslld  ymm1, ymm1, 25\n        vpor    ymm1, ymm1, ymm4\n        vpsrld  ymm4, ymm9, 7\n        vpslld  ymm9, ymm9, 25\n        vpor    ymm9, ymm9, ymm4\n        vpshufd ymm0, ymm0, 0x39\n        vpshufd ymm8, ymm8, 0x39\n        vpshufd ymm3, ymm3, 0x4E\n        vpshufd ymm11, ymm11, 0x4E\n        vpshufd ymm2, ymm2, 0x93\n        vpshufd ymm10, ymm10, 0x93\n        dec     al\n        je      9f\n        vmovdqa ymm4, ymmword ptr [rsp+0x40]\n        vmovdqa ymm5, ymmword ptr [rsp+0x80]\n        vshufps ymm12, ymm4, ymm5, 214\n        vpshufd ymm13, ymm4, 0x0F\n        vpshufd ymm4, ymm12, 0x39\n        vshufps ymm12, ymm6, ymm7, 250\n        vpblendd ymm13, ymm13, ymm12, 0xAA\n        vpunpcklqdq ymm12, ymm7, ymm5\n        vpblendd ymm12, ymm12, ymm6, 0x88\n        vpshufd ymm12, ymm12, 0x78\n        vpunpckhdq ymm5, ymm5, ymm7\n        vpunpckldq ymm6, ymm6, ymm5\n        vpshufd ymm7, ymm6, 0x1E\n        vmovdqa ymmword ptr [rsp+0x40], ymm13\n        vmovdqa ymmword ptr [rsp+0x80], ymm12\n        vmovdqa ymm12, ymmword ptr [rsp+0x60]\n        vmovdqa ymm13, ymmword ptr [rsp+0xA0]\n        vshufps ymm5, ymm12, ymm13, 214\n        vpshufd ymm6, ymm12, 0x0F\n        vpshufd ymm12, ymm5, 0x39\n        vshufps ymm5, ymm14, ymm15, 250\n        vpblendd ymm6, ymm6, ymm5, 0xAA\n        vpunpcklqdq ymm5, ymm15, ymm13\n        vpblendd ymm5, ymm5, ymm14, 0x88\n        vpshufd ymm5, ymm5, 0x78\n        vpunpckhdq ymm13, ymm13, ymm15\n        vpunpckldq ymm14, ymm14, ymm13\n        vpshufd ymm15, ymm14, 0x1E\n        vmovdqa ymm13, ymm6\n        vmovdqa ymm14, ymm5\n        vmovdqa ymm5, ymmword ptr [rsp+0x40]\n        vmovdqa ymm6, ymmword ptr [rsp+0x80]\n        jmp     9b\n9:\n        vpxor   ymm0, ymm0, ymm2\n        vpxor   ymm1, ymm1, ymm3\n        vpxor   ymm8, ymm8, ymm10\n        vpxor   ymm9, ymm9, ymm11\n        mov     eax, r13d\n        cmp     rdx, r15\n        jne     2b\n        vmovdqu xmmword ptr [rbx], xmm0\n        vmovdqu xmmword ptr [rbx+0x10], xmm1\n        vextracti128 xmmword ptr [rbx+0x20], ymm0, 0x01\n        vextracti128 xmmword ptr [rbx+0x30], ymm1, 0x01\n        vmovdqu xmmword ptr [rbx+0x40], xmm8\n        vmovdqu xmmword ptr [rbx+0x50], xmm9\n        vextracti128 xmmword ptr [rbx+0x60], ymm8, 0x01\n        vextracti128 xmmword ptr [rbx+0x70], ymm9, 0x01\n        vmovaps xmm8, xmmword ptr [rsp+0x280]\n        vmovaps xmm0, xmmword ptr [rsp+0x240]\n        vmovaps xmm1, xmmword ptr [rsp+0x250]\n        vmovaps xmm2, xmmword ptr [rsp+0x260]\n        vmovaps xmm3, xmmword ptr [rsp+0x270]\n        vblendvps xmm0, xmm0, xmm1, xmm8\n        vblendvps xmm2, xmm2, xmm3, xmm8\n        vmovaps xmmword ptr [rsp+0x240], xmm0\n        vmovaps xmmword ptr [rsp+0x260], xmm2\n        add     rbx, 128\n        add     rdi, 32\n        sub     rsi, 4\n3:\n        test    rsi, 0x2\n        je      3f\n        vbroadcasti128 ymm0, xmmword ptr [rcx]\n        vbroadcasti128 ymm1, xmmword ptr [rcx+0x10]\n        vmovd   xmm13, dword ptr [rsp+0x240]\n        vpinsrd xmm13, xmm13, dword ptr [rsp+0x260], 1\n        vpinsrd xmm13, xmm13, dword ptr [BLAKE3_BLOCK_LEN+rip], 2\n        vmovd   xmm14, dword ptr [rsp+0x244]\n        vpinsrd xmm14, xmm14, dword ptr [rsp+0x264], 1\n        vpinsrd xmm14, xmm14, dword ptr [BLAKE3_BLOCK_LEN+rip], 2\n        vinserti128 ymm13, ymm13, xmm14, 0x01\n        vbroadcasti128 ymm14, xmmword ptr [ROT16+rip]\n        vbroadcasti128 ymm15, xmmword ptr [ROT8+rip]\n        mov     r8, qword ptr [rdi]\n        mov     r9, qword ptr [rdi+0x8]\n        movzx   eax, byte ptr [rbp+0x40]\n        or      eax, r13d\n        xor     edx, edx\n.p2align  5\n2:\n        mov     r14d, eax\n        or      eax, r12d\n        add     rdx, 64\n        cmp     rdx, r15\n        cmovne  eax, r14d\n        mov     dword ptr [rsp+0x200], eax\n        vbroadcasti128 ymm2, xmmword ptr [BLAKE3_IV+rip]\n        vpbroadcastd ymm8, dword ptr [rsp+0x200]\n        vpblendd ymm3, ymm13, ymm8, 0x88\n        vmovups ymm8, ymmword ptr [r8+rdx-0x40]\n        vinsertf128 ymm8, ymm8, xmmword ptr [r9+rdx-0x40], 0x01\n        vmovups ymm9, ymmword ptr [r8+rdx-0x30]\n        vinsertf128 ymm9, ymm9, xmmword ptr [r9+rdx-0x30], 0x01\n        vshufps ymm4, ymm8, ymm9, 136\n        vshufps ymm5, ymm8, ymm9, 221\n        vmovups ymm8, ymmword ptr [r8+rdx-0x20]\n        vinsertf128 ymm8, ymm8, xmmword ptr [r9+rdx-0x20], 0x01\n        vmovups ymm9, ymmword ptr [r8+rdx-0x10]\n        vinsertf128 ymm9, ymm9, xmmword ptr [r9+rdx-0x10], 0x01\n        vshufps ymm6, ymm8, ymm9, 136\n        vshufps ymm7, ymm8, ymm9, 221\n        vpshufd ymm6, ymm6, 0x93\n        vpshufd ymm7, ymm7, 0x93\n        mov     al, 7\n9:\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm0, ymm0, ymm1\n        vpxor   ymm3, ymm3, ymm0\n        vpshufb ymm3, ymm3, ymm14\n        vpaddd  ymm2, ymm2, ymm3\n        vpxor   ymm1, ymm1, ymm2\n        vpsrld  ymm8, ymm1, 12\n        vpslld  ymm1, ymm1, 20\n        vpor    ymm1, ymm1, ymm8\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm0, ymm0, ymm1\n        vpxor   ymm3, ymm3, ymm0\n        vpshufb ymm3, ymm3, ymm15\n        vpaddd  ymm2, ymm2, ymm3\n        vpxor   ymm1, ymm1, ymm2\n        vpsrld  ymm8, ymm1, 7\n        vpslld  ymm1, ymm1, 25\n        vpor    ymm1, ymm1, ymm8\n        vpshufd ymm0, ymm0, 0x93\n        vpshufd ymm3, ymm3, 0x4E\n        vpshufd ymm2, ymm2, 0x39\n        vpaddd  ymm0, ymm0, ymm6\n        vpaddd  ymm0, ymm0, ymm1\n        vpxor   ymm3, ymm3, ymm0\n        vpshufb ymm3, ymm3, ymm14\n        vpaddd  ymm2, ymm2, ymm3\n        vpxor   ymm1, ymm1, ymm2\n        vpsrld  ymm8, ymm1, 12\n        vpslld  ymm1, ymm1, 20\n        vpor    ymm1, ymm1, ymm8\n        vpaddd  ymm0, ymm0, ymm7\n        vpaddd  ymm0, ymm0, ymm1\n        vpxor   ymm3, ymm3, ymm0\n        vpshufb ymm3, ymm3, ymm15\n        vpaddd  ymm2, ymm2, ymm3\n        vpxor   ymm1, ymm1, ymm2\n        vpsrld  ymm8, ymm1, 7\n        vpslld  ymm1, ymm1, 25\n        vpor    ymm1, ymm1, ymm8\n        vpshufd ymm0, ymm0, 0x39\n        vpshufd ymm3, ymm3, 0x4E\n        vpshufd ymm2, ymm2, 0x93\n        dec     al\n        jz      9f\n        vshufps ymm8, ymm4, ymm5, 214\n        vpshufd ymm9, ymm4, 0x0F\n        vpshufd ymm4, ymm8, 0x39\n        vshufps ymm8, ymm6, ymm7, 250\n        vpblendd ymm9, ymm9, ymm8, 0xAA\n        vpunpcklqdq ymm8, ymm7, ymm5\n        vpblendd ymm8, ymm8, ymm6, 0x88\n        vpshufd ymm8, ymm8, 0x78\n        vpunpckhdq ymm5, ymm5, ymm7\n        vpunpckldq ymm6, ymm6, ymm5\n        vpshufd ymm7, ymm6, 0x1E\n        vmovdqa ymm5, ymm9\n        vmovdqa ymm6, ymm8\n        jmp     9b\n9:\n        vpxor   ymm0, ymm0, ymm2\n        vpxor   ymm1, ymm1, ymm3\n        mov     eax, r13d\n        cmp     rdx, r15\n        jne     2b\n        vmovdqu xmmword ptr [rbx], xmm0\n        vmovdqu xmmword ptr [rbx+0x10], xmm1\n        vextracti128 xmmword ptr [rbx+0x20], ymm0, 0x01\n        vextracti128 xmmword ptr [rbx+0x30], ymm1, 0x01\n        vmovaps ymm8, ymmword ptr [rsp+0x280]\n        vmovaps ymm0, ymmword ptr [rsp+0x240]\n        vmovups ymm1, ymmword ptr [rsp+0x248]\n        vmovaps ymm2, ymmword ptr [rsp+0x260]\n        vmovups ymm3, ymmword ptr [rsp+0x268]\n        vblendvps ymm0, ymm0, ymm1, ymm8\n        vblendvps ymm2, ymm2, ymm3, ymm8\n        vmovaps ymmword ptr [rsp+0x240], ymm0\n        vmovaps ymmword ptr [rsp+0x260], ymm2\n        add     rbx, 64\n        add     rdi, 16\n        sub     rsi, 2\n3:\n        test    rsi, 0x1\n        je      4b\n        vmovdqu xmm0, xmmword ptr [rcx]\n        vmovdqu xmm1, xmmword ptr [rcx+0x10]\n        vmovd   xmm3, dword ptr [rsp+0x240]\n        vpinsrd xmm3, xmm3, dword ptr [rsp+0x260], 1\n        vpinsrd xmm13, xmm3, dword ptr [BLAKE3_BLOCK_LEN+rip], 2\n        vmovdqa xmm14, xmmword ptr [ROT16+rip]\n        vmovdqa xmm15, xmmword ptr [ROT8+rip]\n        mov     r8, qword ptr [rdi]\n        movzx   eax, byte ptr [rbp+0x40]\n        or      eax, r13d\n        xor     edx, edx\n.p2align  5\n2:\n        mov     r14d, eax\n        or      eax, r12d\n        add     rdx, 64\n        cmp     rdx, r15\n        cmovne  eax, r14d\n        vmovdqa xmm2, xmmword ptr [BLAKE3_IV+rip]\n        vmovdqa xmm3, xmm13\n        vpinsrd xmm3, xmm3, eax, 3\n        vmovups xmm8, xmmword ptr [r8+rdx-0x40]\n        vmovups xmm9, xmmword ptr [r8+rdx-0x30]\n        vshufps xmm4, xmm8, xmm9, 136\n        vshufps xmm5, xmm8, xmm9, 221\n        vmovups xmm8, xmmword ptr [r8+rdx-0x20]\n        vmovups xmm9, xmmword ptr [r8+rdx-0x10]\n        vshufps xmm6, xmm8, xmm9, 136\n        vshufps xmm7, xmm8, xmm9, 221\n        vpshufd xmm6, xmm6, 0x93\n        vpshufd xmm7, xmm7, 0x93\n        mov     al, 7\n9:\n        vpaddd  xmm0, xmm0, xmm4\n        vpaddd  xmm0, xmm0, xmm1\n        vpxor   xmm3, xmm3, xmm0\n        vpshufb xmm3, xmm3, xmm14\n        vpaddd  xmm2, xmm2, xmm3\n        vpxor   xmm1, xmm1, xmm2\n        vpsrld  xmm8, xmm1, 12\n        vpslld  xmm1, xmm1, 20\n        vpor    xmm1, xmm1, xmm8\n        vpaddd  xmm0, xmm0, xmm5\n        vpaddd  xmm0, xmm0, xmm1\n        vpxor   xmm3, xmm3, xmm0\n        vpshufb xmm3, xmm3, xmm15\n        vpaddd  xmm2, xmm2, xmm3\n        vpxor   xmm1, xmm1, xmm2\n        vpsrld  xmm8, xmm1, 7\n        vpslld  xmm1, xmm1, 25\n        vpor    xmm1, xmm1, xmm8\n        vpshufd xmm0, xmm0, 0x93\n        vpshufd xmm3, xmm3, 0x4E\n        vpshufd xmm2, xmm2, 0x39\n        vpaddd  xmm0, xmm0, xmm6\n        vpaddd  xmm0, xmm0, xmm1\n        vpxor   xmm3, xmm3, xmm0\n        vpshufb xmm3, xmm3, xmm14\n        vpaddd  xmm2, xmm2, xmm3\n        vpxor   xmm1, xmm1, xmm2\n        vpsrld  xmm8, xmm1, 12\n        vpslld  xmm1, xmm1, 20\n        vpor    xmm1, xmm1, xmm8\n        vpaddd  xmm0, xmm0, xmm7\n        vpaddd  xmm0, xmm0, xmm1\n        vpxor   xmm3, xmm3, xmm0\n        vpshufb xmm3, xmm3, xmm15\n        vpaddd  xmm2, xmm2, xmm3\n        vpxor   xmm1, xmm1, xmm2\n        vpsrld  xmm8, xmm1, 7\n        vpslld  xmm1, xmm1, 25\n        vpor    xmm1, xmm1, xmm8\n        vpshufd xmm0, xmm0, 0x39\n        vpshufd xmm3, xmm3, 0x4E\n        vpshufd xmm2, xmm2, 0x93\n        dec     al\n        jz      9f\n        vshufps xmm8, xmm4, xmm5, 214\n        vpshufd xmm9, xmm4, 0x0F\n        vpshufd xmm4, xmm8, 0x39\n        vshufps xmm8, xmm6, xmm7, 250\n        vpblendd xmm9, xmm9, xmm8, 0xAA\n        vpunpcklqdq xmm8, xmm7, xmm5\n        vpblendd xmm8, xmm8, xmm6, 0x88\n        vpshufd xmm8, xmm8, 0x78\n        vpunpckhdq xmm5, xmm5, xmm7\n        vpunpckldq xmm6, xmm6, xmm5\n        vpshufd xmm7, xmm6, 0x1E\n        vmovdqa xmm5, xmm9\n        vmovdqa xmm6, xmm8\n        jmp     9b\n9:\n        vpxor   xmm0, xmm0, xmm2\n        vpxor   xmm1, xmm1, xmm3\n        mov     eax, r13d\n        cmp     rdx, r15\n        jne     2b\n        vmovdqu xmmword ptr [rbx], xmm0\n        vmovdqu xmmword ptr [rbx+0x10], xmm1\n        jmp     4b\n\n\n#ifdef __APPLE__\n.static_data\n#else\n.section .rodata\n#endif\n.p2align  6\nADD0:\n        .long  0, 1, 2, 3, 4, 5, 6, 7\nADD1:\n        .long  8, 8, 8, 8, 8, 8, 8, 8\nBLAKE3_IV_0:\n        .long  0x6A09E667, 0x6A09E667, 0x6A09E667, 0x6A09E667\n        .long  0x6A09E667, 0x6A09E667, 0x6A09E667, 0x6A09E667\nBLAKE3_IV_1:\n        .long  0xBB67AE85, 0xBB67AE85, 0xBB67AE85, 0xBB67AE85\n        .long  0xBB67AE85, 0xBB67AE85, 0xBB67AE85, 0xBB67AE85\nBLAKE3_IV_2:\n        .long  0x3C6EF372, 0x3C6EF372, 0x3C6EF372, 0x3C6EF372\n        .long  0x3C6EF372, 0x3C6EF372, 0x3C6EF372, 0x3C6EF372\nBLAKE3_IV_3:\n        .long  0xA54FF53A, 0xA54FF53A, 0xA54FF53A, 0xA54FF53A\n        .long  0xA54FF53A, 0xA54FF53A, 0xA54FF53A, 0xA54FF53A\nBLAKE3_BLOCK_LEN:\n        .long  0x00000040, 0x00000040, 0x00000040, 0x00000040\n        .long  0x00000040, 0x00000040, 0x00000040, 0x00000040\nROT16:\n        .byte  2, 3, 0, 1, 6, 7, 4, 5, 10, 11, 8, 9, 14, 15, 12, 13\nROT8:\n        .byte  1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8, 13, 14, 15, 12\nCMP_MSB_MASK:\n        .long  0x80000000, 0x80000000, 0x80000000, 0x80000000\n        .long  0x80000000, 0x80000000, 0x80000000, 0x80000000\nBLAKE3_IV:\n        .long  0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A\n"
  },
  {
    "path": "common/blake3/blake3_avx512.c",
    "content": "#include \"blake3_impl.h\"\n\n#include <immintrin.h>\n\n#define _mm_shuffle_ps2(a, b, c)                                               \\\n  (_mm_castps_si128(                                                           \\\n      _mm_shuffle_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b), (c))))\n\nINLINE __m128i loadu_128(const uint8_t src[16]) {\n  return _mm_loadu_si128((const __m128i *)src);\n}\n\nINLINE __m256i loadu_256(const uint8_t src[32]) {\n  return _mm256_loadu_si256((const __m256i *)src);\n}\n\nINLINE __m512i loadu_512(const uint8_t src[64]) {\n  return _mm512_loadu_si512((const __m512i *)src);\n}\n\nINLINE void storeu_128(__m128i src, uint8_t dest[16]) {\n  _mm_storeu_si128((__m128i *)dest, src);\n}\n\nINLINE void storeu_256(__m256i src, uint8_t dest[16]) {\n  _mm256_storeu_si256((__m256i *)dest, src);\n}\n\nINLINE __m128i add_128(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n\nINLINE __m256i add_256(__m256i a, __m256i b) { return _mm256_add_epi32(a, b); }\n\nINLINE __m512i add_512(__m512i a, __m512i b) { return _mm512_add_epi32(a, b); }\n\nINLINE __m128i xor_128(__m128i a, __m128i b) { return _mm_xor_si128(a, b); }\n\nINLINE __m256i xor_256(__m256i a, __m256i b) { return _mm256_xor_si256(a, b); }\n\nINLINE __m512i xor_512(__m512i a, __m512i b) { return _mm512_xor_si512(a, b); }\n\nINLINE __m128i set1_128(uint32_t x) { return _mm_set1_epi32((int32_t)x); }\n\nINLINE __m256i set1_256(uint32_t x) { return _mm256_set1_epi32((int32_t)x); }\n\nINLINE __m512i set1_512(uint32_t x) { return _mm512_set1_epi32((int32_t)x); }\n\nINLINE __m128i set4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {\n  return _mm_setr_epi32((int32_t)a, (int32_t)b, (int32_t)c, (int32_t)d);\n}\n\nINLINE __m128i rot16_128(__m128i x) { return _mm_ror_epi32(x, 16); }\n\nINLINE __m256i rot16_256(__m256i x) { return _mm256_ror_epi32(x, 16); }\n\nINLINE __m512i rot16_512(__m512i x) { return _mm512_ror_epi32(x, 16); }\n\nINLINE __m128i rot12_128(__m128i x) { return _mm_ror_epi32(x, 12); }\n\nINLINE __m256i rot12_256(__m256i x) { return _mm256_ror_epi32(x, 12); }\n\nINLINE __m512i rot12_512(__m512i x) { return _mm512_ror_epi32(x, 12); }\n\nINLINE __m128i rot8_128(__m128i x) { return _mm_ror_epi32(x, 8); }\n\nINLINE __m256i rot8_256(__m256i x) { return _mm256_ror_epi32(x, 8); }\n\nINLINE __m512i rot8_512(__m512i x) { return _mm512_ror_epi32(x, 8); }\n\nINLINE __m128i rot7_128(__m128i x) { return _mm_ror_epi32(x, 7); }\n\nINLINE __m256i rot7_256(__m256i x) { return _mm256_ror_epi32(x, 7); }\n\nINLINE __m512i rot7_512(__m512i x) { return _mm512_ror_epi32(x, 7); }\n\n/*\n * ----------------------------------------------------------------------------\n * compress_avx512\n * ----------------------------------------------------------------------------\n */\n\nINLINE void g1(__m128i *row0, __m128i *row1, __m128i *row2, __m128i *row3,\n               __m128i m) {\n  *row0 = add_128(add_128(*row0, m), *row1);\n  *row3 = xor_128(*row3, *row0);\n  *row3 = rot16_128(*row3);\n  *row2 = add_128(*row2, *row3);\n  *row1 = xor_128(*row1, *row2);\n  *row1 = rot12_128(*row1);\n}\n\nINLINE void g2(__m128i *row0, __m128i *row1, __m128i *row2, __m128i *row3,\n               __m128i m) {\n  *row0 = add_128(add_128(*row0, m), *row1);\n  *row3 = xor_128(*row3, *row0);\n  *row3 = rot8_128(*row3);\n  *row2 = add_128(*row2, *row3);\n  *row1 = xor_128(*row1, *row2);\n  *row1 = rot7_128(*row1);\n}\n\n// Note the optimization here of leaving row1 as the unrotated row, rather than\n// row0. All the message loads below are adjusted to compensate for this. See\n// discussion at https://github.com/sneves/blake2-avx2/pull/4\nINLINE void diagonalize(__m128i *row0, __m128i *row2, __m128i *row3) {\n  *row0 = _mm_shuffle_epi32(*row0, _MM_SHUFFLE(2, 1, 0, 3));\n  *row3 = _mm_shuffle_epi32(*row3, _MM_SHUFFLE(1, 0, 3, 2));\n  *row2 = _mm_shuffle_epi32(*row2, _MM_SHUFFLE(0, 3, 2, 1));\n}\n\nINLINE void undiagonalize(__m128i *row0, __m128i *row2, __m128i *row3) {\n  *row0 = _mm_shuffle_epi32(*row0, _MM_SHUFFLE(0, 3, 2, 1));\n  *row3 = _mm_shuffle_epi32(*row3, _MM_SHUFFLE(1, 0, 3, 2));\n  *row2 = _mm_shuffle_epi32(*row2, _MM_SHUFFLE(2, 1, 0, 3));\n}\n\nINLINE void compress_pre(__m128i rows[4], const uint32_t cv[8],\n                         const uint8_t block[BLAKE3_BLOCK_LEN],\n                         uint8_t block_len, uint64_t counter, uint8_t flags) {\n  rows[0] = loadu_128((uint8_t *)&cv[0]);\n  rows[1] = loadu_128((uint8_t *)&cv[4]);\n  rows[2] = set4(IV[0], IV[1], IV[2], IV[3]);\n  rows[3] = set4(counter_low(counter), counter_high(counter),\n                 (uint32_t)block_len, (uint32_t)flags);\n\n  __m128i m0 = loadu_128(&block[sizeof(__m128i) * 0]);\n  __m128i m1 = loadu_128(&block[sizeof(__m128i) * 1]);\n  __m128i m2 = loadu_128(&block[sizeof(__m128i) * 2]);\n  __m128i m3 = loadu_128(&block[sizeof(__m128i) * 3]);\n\n  __m128i t0, t1, t2, t3, tt;\n\n  // Round 1. The first round permutes the message words from the original\n  // input order, into the groups that get mixed in parallel.\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(2, 0, 2, 0)); //  6  4  2  0\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 3, 1)); //  7  5  3  1\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(2, 0, 2, 0)); // 14 12 10  8\n  t2 = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2, 1, 0, 3));   // 12 10  8 14\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 1, 3, 1)); // 15 13 11  9\n  t3 = _mm_shuffle_epi32(t3, _MM_SHUFFLE(2, 1, 0, 3));   // 13 11  9 15\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 2. This round and all following rounds apply a fixed permutation\n  // to the message words from the round before.\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = _mm_blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = _mm_blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 3\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = _mm_blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = _mm_blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 4\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = _mm_blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = _mm_blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 5\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = _mm_blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = _mm_blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 6\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = _mm_blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = _mm_blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 7\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = _mm_blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = _mm_blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n}\n\nvoid blake3_compress_xof_avx512(const uint32_t cv[8],\n                                const uint8_t block[BLAKE3_BLOCK_LEN],\n                                uint8_t block_len, uint64_t counter,\n                                uint8_t flags, uint8_t out[64]) {\n  __m128i rows[4];\n  compress_pre(rows, cv, block, block_len, counter, flags);\n  storeu_128(xor_128(rows[0], rows[2]), &out[0]);\n  storeu_128(xor_128(rows[1], rows[3]), &out[16]);\n  storeu_128(xor_128(rows[2], loadu_128((uint8_t *)&cv[0])), &out[32]);\n  storeu_128(xor_128(rows[3], loadu_128((uint8_t *)&cv[4])), &out[48]);\n}\n\nvoid blake3_compress_in_place_avx512(uint32_t cv[8],\n                                     const uint8_t block[BLAKE3_BLOCK_LEN],\n                                     uint8_t block_len, uint64_t counter,\n                                     uint8_t flags) {\n  __m128i rows[4];\n  compress_pre(rows, cv, block, block_len, counter, flags);\n  storeu_128(xor_128(rows[0], rows[2]), (uint8_t *)&cv[0]);\n  storeu_128(xor_128(rows[1], rows[3]), (uint8_t *)&cv[4]);\n}\n\n/*\n * ----------------------------------------------------------------------------\n * hash4_avx512\n * ----------------------------------------------------------------------------\n */\n\nINLINE void round_fn4(__m128i v[16], __m128i m[16], size_t r) {\n  v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][0]]);\n  v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][2]]);\n  v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][4]]);\n  v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][6]]);\n  v[0] = add_128(v[0], v[4]);\n  v[1] = add_128(v[1], v[5]);\n  v[2] = add_128(v[2], v[6]);\n  v[3] = add_128(v[3], v[7]);\n  v[12] = xor_128(v[12], v[0]);\n  v[13] = xor_128(v[13], v[1]);\n  v[14] = xor_128(v[14], v[2]);\n  v[15] = xor_128(v[15], v[3]);\n  v[12] = rot16_128(v[12]);\n  v[13] = rot16_128(v[13]);\n  v[14] = rot16_128(v[14]);\n  v[15] = rot16_128(v[15]);\n  v[8] = add_128(v[8], v[12]);\n  v[9] = add_128(v[9], v[13]);\n  v[10] = add_128(v[10], v[14]);\n  v[11] = add_128(v[11], v[15]);\n  v[4] = xor_128(v[4], v[8]);\n  v[5] = xor_128(v[5], v[9]);\n  v[6] = xor_128(v[6], v[10]);\n  v[7] = xor_128(v[7], v[11]);\n  v[4] = rot12_128(v[4]);\n  v[5] = rot12_128(v[5]);\n  v[6] = rot12_128(v[6]);\n  v[7] = rot12_128(v[7]);\n  v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][1]]);\n  v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][3]]);\n  v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][5]]);\n  v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][7]]);\n  v[0] = add_128(v[0], v[4]);\n  v[1] = add_128(v[1], v[5]);\n  v[2] = add_128(v[2], v[6]);\n  v[3] = add_128(v[3], v[7]);\n  v[12] = xor_128(v[12], v[0]);\n  v[13] = xor_128(v[13], v[1]);\n  v[14] = xor_128(v[14], v[2]);\n  v[15] = xor_128(v[15], v[3]);\n  v[12] = rot8_128(v[12]);\n  v[13] = rot8_128(v[13]);\n  v[14] = rot8_128(v[14]);\n  v[15] = rot8_128(v[15]);\n  v[8] = add_128(v[8], v[12]);\n  v[9] = add_128(v[9], v[13]);\n  v[10] = add_128(v[10], v[14]);\n  v[11] = add_128(v[11], v[15]);\n  v[4] = xor_128(v[4], v[8]);\n  v[5] = xor_128(v[5], v[9]);\n  v[6] = xor_128(v[6], v[10]);\n  v[7] = xor_128(v[7], v[11]);\n  v[4] = rot7_128(v[4]);\n  v[5] = rot7_128(v[5]);\n  v[6] = rot7_128(v[6]);\n  v[7] = rot7_128(v[7]);\n\n  v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][8]]);\n  v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][10]]);\n  v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][12]]);\n  v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][14]]);\n  v[0] = add_128(v[0], v[5]);\n  v[1] = add_128(v[1], v[6]);\n  v[2] = add_128(v[2], v[7]);\n  v[3] = add_128(v[3], v[4]);\n  v[15] = xor_128(v[15], v[0]);\n  v[12] = xor_128(v[12], v[1]);\n  v[13] = xor_128(v[13], v[2]);\n  v[14] = xor_128(v[14], v[3]);\n  v[15] = rot16_128(v[15]);\n  v[12] = rot16_128(v[12]);\n  v[13] = rot16_128(v[13]);\n  v[14] = rot16_128(v[14]);\n  v[10] = add_128(v[10], v[15]);\n  v[11] = add_128(v[11], v[12]);\n  v[8] = add_128(v[8], v[13]);\n  v[9] = add_128(v[9], v[14]);\n  v[5] = xor_128(v[5], v[10]);\n  v[6] = xor_128(v[6], v[11]);\n  v[7] = xor_128(v[7], v[8]);\n  v[4] = xor_128(v[4], v[9]);\n  v[5] = rot12_128(v[5]);\n  v[6] = rot12_128(v[6]);\n  v[7] = rot12_128(v[7]);\n  v[4] = rot12_128(v[4]);\n  v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][9]]);\n  v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][11]]);\n  v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][13]]);\n  v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][15]]);\n  v[0] = add_128(v[0], v[5]);\n  v[1] = add_128(v[1], v[6]);\n  v[2] = add_128(v[2], v[7]);\n  v[3] = add_128(v[3], v[4]);\n  v[15] = xor_128(v[15], v[0]);\n  v[12] = xor_128(v[12], v[1]);\n  v[13] = xor_128(v[13], v[2]);\n  v[14] = xor_128(v[14], v[3]);\n  v[15] = rot8_128(v[15]);\n  v[12] = rot8_128(v[12]);\n  v[13] = rot8_128(v[13]);\n  v[14] = rot8_128(v[14]);\n  v[10] = add_128(v[10], v[15]);\n  v[11] = add_128(v[11], v[12]);\n  v[8] = add_128(v[8], v[13]);\n  v[9] = add_128(v[9], v[14]);\n  v[5] = xor_128(v[5], v[10]);\n  v[6] = xor_128(v[6], v[11]);\n  v[7] = xor_128(v[7], v[8]);\n  v[4] = xor_128(v[4], v[9]);\n  v[5] = rot7_128(v[5]);\n  v[6] = rot7_128(v[6]);\n  v[7] = rot7_128(v[7]);\n  v[4] = rot7_128(v[4]);\n}\n\nINLINE void transpose_vecs_128(__m128i vecs[4]) {\n  // Interleave 32-bit lates. The low unpack is lanes 00/11 and the high is\n  // 22/33. Note that this doesn't split the vector into two lanes, as the\n  // AVX2 counterparts do.\n  __m128i ab_01 = _mm_unpacklo_epi32(vecs[0], vecs[1]);\n  __m128i ab_23 = _mm_unpackhi_epi32(vecs[0], vecs[1]);\n  __m128i cd_01 = _mm_unpacklo_epi32(vecs[2], vecs[3]);\n  __m128i cd_23 = _mm_unpackhi_epi32(vecs[2], vecs[3]);\n\n  // Interleave 64-bit lanes.\n  __m128i abcd_0 = _mm_unpacklo_epi64(ab_01, cd_01);\n  __m128i abcd_1 = _mm_unpackhi_epi64(ab_01, cd_01);\n  __m128i abcd_2 = _mm_unpacklo_epi64(ab_23, cd_23);\n  __m128i abcd_3 = _mm_unpackhi_epi64(ab_23, cd_23);\n\n  vecs[0] = abcd_0;\n  vecs[1] = abcd_1;\n  vecs[2] = abcd_2;\n  vecs[3] = abcd_3;\n}\n\nINLINE void transpose_msg_vecs4(const uint8_t *const *inputs,\n                                size_t block_offset, __m128i out[16]) {\n  out[0] = loadu_128(&inputs[0][block_offset + 0 * sizeof(__m128i)]);\n  out[1] = loadu_128(&inputs[1][block_offset + 0 * sizeof(__m128i)]);\n  out[2] = loadu_128(&inputs[2][block_offset + 0 * sizeof(__m128i)]);\n  out[3] = loadu_128(&inputs[3][block_offset + 0 * sizeof(__m128i)]);\n  out[4] = loadu_128(&inputs[0][block_offset + 1 * sizeof(__m128i)]);\n  out[5] = loadu_128(&inputs[1][block_offset + 1 * sizeof(__m128i)]);\n  out[6] = loadu_128(&inputs[2][block_offset + 1 * sizeof(__m128i)]);\n  out[7] = loadu_128(&inputs[3][block_offset + 1 * sizeof(__m128i)]);\n  out[8] = loadu_128(&inputs[0][block_offset + 2 * sizeof(__m128i)]);\n  out[9] = loadu_128(&inputs[1][block_offset + 2 * sizeof(__m128i)]);\n  out[10] = loadu_128(&inputs[2][block_offset + 2 * sizeof(__m128i)]);\n  out[11] = loadu_128(&inputs[3][block_offset + 2 * sizeof(__m128i)]);\n  out[12] = loadu_128(&inputs[0][block_offset + 3 * sizeof(__m128i)]);\n  out[13] = loadu_128(&inputs[1][block_offset + 3 * sizeof(__m128i)]);\n  out[14] = loadu_128(&inputs[2][block_offset + 3 * sizeof(__m128i)]);\n  out[15] = loadu_128(&inputs[3][block_offset + 3 * sizeof(__m128i)]);\n  for (size_t i = 0; i < 4; ++i) {\n    _mm_prefetch(&inputs[i][block_offset + 256], _MM_HINT_T0);\n  }\n  transpose_vecs_128(&out[0]);\n  transpose_vecs_128(&out[4]);\n  transpose_vecs_128(&out[8]);\n  transpose_vecs_128(&out[12]);\n}\n\nINLINE void load_counters4(uint64_t counter, bool increment_counter,\n                           __m128i *out_lo, __m128i *out_hi) {\n  uint64_t mask = (increment_counter ? ~0 : 0);\n  __m256i mask_vec = _mm256_set1_epi64x(mask);\n  __m256i deltas = _mm256_setr_epi64x(0, 1, 2, 3);\n  deltas = _mm256_and_si256(mask_vec, deltas);\n  __m256i counters =\n      _mm256_add_epi64(_mm256_set1_epi64x((int64_t)counter), deltas);\n  *out_lo = _mm256_cvtepi64_epi32(counters);\n  *out_hi = _mm256_cvtepi64_epi32(_mm256_srli_epi64(counters, 32));\n}\n\nvoid blake3_hash4_avx512(const uint8_t *const *inputs, size_t blocks,\n                         const uint32_t key[8], uint64_t counter,\n                         bool increment_counter, uint8_t flags,\n                         uint8_t flags_start, uint8_t flags_end, uint8_t *out) {\n  __m128i h_vecs[8] = {\n      set1_128(key[0]), set1_128(key[1]), set1_128(key[2]), set1_128(key[3]),\n      set1_128(key[4]), set1_128(key[5]), set1_128(key[6]), set1_128(key[7]),\n  };\n  __m128i counter_low_vec, counter_high_vec;\n  load_counters4(counter, increment_counter, &counter_low_vec,\n                 &counter_high_vec);\n  uint8_t block_flags = flags | flags_start;\n\n  for (size_t block = 0; block < blocks; block++) {\n    if (block + 1 == blocks) {\n      block_flags |= flags_end;\n    }\n    __m128i block_len_vec = set1_128(BLAKE3_BLOCK_LEN);\n    __m128i block_flags_vec = set1_128(block_flags);\n    __m128i msg_vecs[16];\n    transpose_msg_vecs4(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs);\n\n    __m128i v[16] = {\n        h_vecs[0],       h_vecs[1],        h_vecs[2],       h_vecs[3],\n        h_vecs[4],       h_vecs[5],        h_vecs[6],       h_vecs[7],\n        set1_128(IV[0]), set1_128(IV[1]),  set1_128(IV[2]), set1_128(IV[3]),\n        counter_low_vec, counter_high_vec, block_len_vec,   block_flags_vec,\n    };\n    round_fn4(v, msg_vecs, 0);\n    round_fn4(v, msg_vecs, 1);\n    round_fn4(v, msg_vecs, 2);\n    round_fn4(v, msg_vecs, 3);\n    round_fn4(v, msg_vecs, 4);\n    round_fn4(v, msg_vecs, 5);\n    round_fn4(v, msg_vecs, 6);\n    h_vecs[0] = xor_128(v[0], v[8]);\n    h_vecs[1] = xor_128(v[1], v[9]);\n    h_vecs[2] = xor_128(v[2], v[10]);\n    h_vecs[3] = xor_128(v[3], v[11]);\n    h_vecs[4] = xor_128(v[4], v[12]);\n    h_vecs[5] = xor_128(v[5], v[13]);\n    h_vecs[6] = xor_128(v[6], v[14]);\n    h_vecs[7] = xor_128(v[7], v[15]);\n\n    block_flags = flags;\n  }\n\n  transpose_vecs_128(&h_vecs[0]);\n  transpose_vecs_128(&h_vecs[4]);\n  // The first four vecs now contain the first half of each output, and the\n  // second four vecs contain the second half of each output.\n  storeu_128(h_vecs[0], &out[0 * sizeof(__m128i)]);\n  storeu_128(h_vecs[4], &out[1 * sizeof(__m128i)]);\n  storeu_128(h_vecs[1], &out[2 * sizeof(__m128i)]);\n  storeu_128(h_vecs[5], &out[3 * sizeof(__m128i)]);\n  storeu_128(h_vecs[2], &out[4 * sizeof(__m128i)]);\n  storeu_128(h_vecs[6], &out[5 * sizeof(__m128i)]);\n  storeu_128(h_vecs[3], &out[6 * sizeof(__m128i)]);\n  storeu_128(h_vecs[7], &out[7 * sizeof(__m128i)]);\n}\n\n/*\n * ----------------------------------------------------------------------------\n * hash8_avx512\n * ----------------------------------------------------------------------------\n */\n\nINLINE void round_fn8(__m256i v[16], __m256i m[16], size_t r) {\n  v[0] = add_256(v[0], m[(size_t)MSG_SCHEDULE[r][0]]);\n  v[1] = add_256(v[1], m[(size_t)MSG_SCHEDULE[r][2]]);\n  v[2] = add_256(v[2], m[(size_t)MSG_SCHEDULE[r][4]]);\n  v[3] = add_256(v[3], m[(size_t)MSG_SCHEDULE[r][6]]);\n  v[0] = add_256(v[0], v[4]);\n  v[1] = add_256(v[1], v[5]);\n  v[2] = add_256(v[2], v[6]);\n  v[3] = add_256(v[3], v[7]);\n  v[12] = xor_256(v[12], v[0]);\n  v[13] = xor_256(v[13], v[1]);\n  v[14] = xor_256(v[14], v[2]);\n  v[15] = xor_256(v[15], v[3]);\n  v[12] = rot16_256(v[12]);\n  v[13] = rot16_256(v[13]);\n  v[14] = rot16_256(v[14]);\n  v[15] = rot16_256(v[15]);\n  v[8] = add_256(v[8], v[12]);\n  v[9] = add_256(v[9], v[13]);\n  v[10] = add_256(v[10], v[14]);\n  v[11] = add_256(v[11], v[15]);\n  v[4] = xor_256(v[4], v[8]);\n  v[5] = xor_256(v[5], v[9]);\n  v[6] = xor_256(v[6], v[10]);\n  v[7] = xor_256(v[7], v[11]);\n  v[4] = rot12_256(v[4]);\n  v[5] = rot12_256(v[5]);\n  v[6] = rot12_256(v[6]);\n  v[7] = rot12_256(v[7]);\n  v[0] = add_256(v[0], m[(size_t)MSG_SCHEDULE[r][1]]);\n  v[1] = add_256(v[1], m[(size_t)MSG_SCHEDULE[r][3]]);\n  v[2] = add_256(v[2], m[(size_t)MSG_SCHEDULE[r][5]]);\n  v[3] = add_256(v[3], m[(size_t)MSG_SCHEDULE[r][7]]);\n  v[0] = add_256(v[0], v[4]);\n  v[1] = add_256(v[1], v[5]);\n  v[2] = add_256(v[2], v[6]);\n  v[3] = add_256(v[3], v[7]);\n  v[12] = xor_256(v[12], v[0]);\n  v[13] = xor_256(v[13], v[1]);\n  v[14] = xor_256(v[14], v[2]);\n  v[15] = xor_256(v[15], v[3]);\n  v[12] = rot8_256(v[12]);\n  v[13] = rot8_256(v[13]);\n  v[14] = rot8_256(v[14]);\n  v[15] = rot8_256(v[15]);\n  v[8] = add_256(v[8], v[12]);\n  v[9] = add_256(v[9], v[13]);\n  v[10] = add_256(v[10], v[14]);\n  v[11] = add_256(v[11], v[15]);\n  v[4] = xor_256(v[4], v[8]);\n  v[5] = xor_256(v[5], v[9]);\n  v[6] = xor_256(v[6], v[10]);\n  v[7] = xor_256(v[7], v[11]);\n  v[4] = rot7_256(v[4]);\n  v[5] = rot7_256(v[5]);\n  v[6] = rot7_256(v[6]);\n  v[7] = rot7_256(v[7]);\n\n  v[0] = add_256(v[0], m[(size_t)MSG_SCHEDULE[r][8]]);\n  v[1] = add_256(v[1], m[(size_t)MSG_SCHEDULE[r][10]]);\n  v[2] = add_256(v[2], m[(size_t)MSG_SCHEDULE[r][12]]);\n  v[3] = add_256(v[3], m[(size_t)MSG_SCHEDULE[r][14]]);\n  v[0] = add_256(v[0], v[5]);\n  v[1] = add_256(v[1], v[6]);\n  v[2] = add_256(v[2], v[7]);\n  v[3] = add_256(v[3], v[4]);\n  v[15] = xor_256(v[15], v[0]);\n  v[12] = xor_256(v[12], v[1]);\n  v[13] = xor_256(v[13], v[2]);\n  v[14] = xor_256(v[14], v[3]);\n  v[15] = rot16_256(v[15]);\n  v[12] = rot16_256(v[12]);\n  v[13] = rot16_256(v[13]);\n  v[14] = rot16_256(v[14]);\n  v[10] = add_256(v[10], v[15]);\n  v[11] = add_256(v[11], v[12]);\n  v[8] = add_256(v[8], v[13]);\n  v[9] = add_256(v[9], v[14]);\n  v[5] = xor_256(v[5], v[10]);\n  v[6] = xor_256(v[6], v[11]);\n  v[7] = xor_256(v[7], v[8]);\n  v[4] = xor_256(v[4], v[9]);\n  v[5] = rot12_256(v[5]);\n  v[6] = rot12_256(v[6]);\n  v[7] = rot12_256(v[7]);\n  v[4] = rot12_256(v[4]);\n  v[0] = add_256(v[0], m[(size_t)MSG_SCHEDULE[r][9]]);\n  v[1] = add_256(v[1], m[(size_t)MSG_SCHEDULE[r][11]]);\n  v[2] = add_256(v[2], m[(size_t)MSG_SCHEDULE[r][13]]);\n  v[3] = add_256(v[3], m[(size_t)MSG_SCHEDULE[r][15]]);\n  v[0] = add_256(v[0], v[5]);\n  v[1] = add_256(v[1], v[6]);\n  v[2] = add_256(v[2], v[7]);\n  v[3] = add_256(v[3], v[4]);\n  v[15] = xor_256(v[15], v[0]);\n  v[12] = xor_256(v[12], v[1]);\n  v[13] = xor_256(v[13], v[2]);\n  v[14] = xor_256(v[14], v[3]);\n  v[15] = rot8_256(v[15]);\n  v[12] = rot8_256(v[12]);\n  v[13] = rot8_256(v[13]);\n  v[14] = rot8_256(v[14]);\n  v[10] = add_256(v[10], v[15]);\n  v[11] = add_256(v[11], v[12]);\n  v[8] = add_256(v[8], v[13]);\n  v[9] = add_256(v[9], v[14]);\n  v[5] = xor_256(v[5], v[10]);\n  v[6] = xor_256(v[6], v[11]);\n  v[7] = xor_256(v[7], v[8]);\n  v[4] = xor_256(v[4], v[9]);\n  v[5] = rot7_256(v[5]);\n  v[6] = rot7_256(v[6]);\n  v[7] = rot7_256(v[7]);\n  v[4] = rot7_256(v[4]);\n}\n\nINLINE void transpose_vecs_256(__m256i vecs[8]) {\n  // Interleave 32-bit lanes. The low unpack is lanes 00/11/44/55, and the high\n  // is 22/33/66/77.\n  __m256i ab_0145 = _mm256_unpacklo_epi32(vecs[0], vecs[1]);\n  __m256i ab_2367 = _mm256_unpackhi_epi32(vecs[0], vecs[1]);\n  __m256i cd_0145 = _mm256_unpacklo_epi32(vecs[2], vecs[3]);\n  __m256i cd_2367 = _mm256_unpackhi_epi32(vecs[2], vecs[3]);\n  __m256i ef_0145 = _mm256_unpacklo_epi32(vecs[4], vecs[5]);\n  __m256i ef_2367 = _mm256_unpackhi_epi32(vecs[4], vecs[5]);\n  __m256i gh_0145 = _mm256_unpacklo_epi32(vecs[6], vecs[7]);\n  __m256i gh_2367 = _mm256_unpackhi_epi32(vecs[6], vecs[7]);\n\n  // Interleave 64-bit lates. The low unpack is lanes 00/22 and the high is\n  // 11/33.\n  __m256i abcd_04 = _mm256_unpacklo_epi64(ab_0145, cd_0145);\n  __m256i abcd_15 = _mm256_unpackhi_epi64(ab_0145, cd_0145);\n  __m256i abcd_26 = _mm256_unpacklo_epi64(ab_2367, cd_2367);\n  __m256i abcd_37 = _mm256_unpackhi_epi64(ab_2367, cd_2367);\n  __m256i efgh_04 = _mm256_unpacklo_epi64(ef_0145, gh_0145);\n  __m256i efgh_15 = _mm256_unpackhi_epi64(ef_0145, gh_0145);\n  __m256i efgh_26 = _mm256_unpacklo_epi64(ef_2367, gh_2367);\n  __m256i efgh_37 = _mm256_unpackhi_epi64(ef_2367, gh_2367);\n\n  // Interleave 128-bit lanes.\n  vecs[0] = _mm256_permute2x128_si256(abcd_04, efgh_04, 0x20);\n  vecs[1] = _mm256_permute2x128_si256(abcd_15, efgh_15, 0x20);\n  vecs[2] = _mm256_permute2x128_si256(abcd_26, efgh_26, 0x20);\n  vecs[3] = _mm256_permute2x128_si256(abcd_37, efgh_37, 0x20);\n  vecs[4] = _mm256_permute2x128_si256(abcd_04, efgh_04, 0x31);\n  vecs[5] = _mm256_permute2x128_si256(abcd_15, efgh_15, 0x31);\n  vecs[6] = _mm256_permute2x128_si256(abcd_26, efgh_26, 0x31);\n  vecs[7] = _mm256_permute2x128_si256(abcd_37, efgh_37, 0x31);\n}\n\nINLINE void transpose_msg_vecs8(const uint8_t *const *inputs,\n                                size_t block_offset, __m256i out[16]) {\n  out[0] = loadu_256(&inputs[0][block_offset + 0 * sizeof(__m256i)]);\n  out[1] = loadu_256(&inputs[1][block_offset + 0 * sizeof(__m256i)]);\n  out[2] = loadu_256(&inputs[2][block_offset + 0 * sizeof(__m256i)]);\n  out[3] = loadu_256(&inputs[3][block_offset + 0 * sizeof(__m256i)]);\n  out[4] = loadu_256(&inputs[4][block_offset + 0 * sizeof(__m256i)]);\n  out[5] = loadu_256(&inputs[5][block_offset + 0 * sizeof(__m256i)]);\n  out[6] = loadu_256(&inputs[6][block_offset + 0 * sizeof(__m256i)]);\n  out[7] = loadu_256(&inputs[7][block_offset + 0 * sizeof(__m256i)]);\n  out[8] = loadu_256(&inputs[0][block_offset + 1 * sizeof(__m256i)]);\n  out[9] = loadu_256(&inputs[1][block_offset + 1 * sizeof(__m256i)]);\n  out[10] = loadu_256(&inputs[2][block_offset + 1 * sizeof(__m256i)]);\n  out[11] = loadu_256(&inputs[3][block_offset + 1 * sizeof(__m256i)]);\n  out[12] = loadu_256(&inputs[4][block_offset + 1 * sizeof(__m256i)]);\n  out[13] = loadu_256(&inputs[5][block_offset + 1 * sizeof(__m256i)]);\n  out[14] = loadu_256(&inputs[6][block_offset + 1 * sizeof(__m256i)]);\n  out[15] = loadu_256(&inputs[7][block_offset + 1 * sizeof(__m256i)]);\n  for (size_t i = 0; i < 8; ++i) {\n    _mm_prefetch(&inputs[i][block_offset + 256], _MM_HINT_T0);\n  }\n  transpose_vecs_256(&out[0]);\n  transpose_vecs_256(&out[8]);\n}\n\nINLINE void load_counters8(uint64_t counter, bool increment_counter,\n                           __m256i *out_lo, __m256i *out_hi) {\n  uint64_t mask = (increment_counter ? ~0 : 0);\n  __m512i mask_vec = _mm512_set1_epi64(mask);\n  __m512i deltas = _mm512_setr_epi64(0, 1, 2, 3, 4, 5, 6, 7);\n  deltas = _mm512_and_si512(mask_vec, deltas);\n  __m512i counters =\n      _mm512_add_epi64(_mm512_set1_epi64((int64_t)counter), deltas);\n  *out_lo = _mm512_cvtepi64_epi32(counters);\n  *out_hi = _mm512_cvtepi64_epi32(_mm512_srli_epi64(counters, 32));\n}\n\nvoid blake3_hash8_avx512(const uint8_t *const *inputs, size_t blocks,\n                         const uint32_t key[8], uint64_t counter,\n                         bool increment_counter, uint8_t flags,\n                         uint8_t flags_start, uint8_t flags_end, uint8_t *out) {\n  __m256i h_vecs[8] = {\n      set1_256(key[0]), set1_256(key[1]), set1_256(key[2]), set1_256(key[3]),\n      set1_256(key[4]), set1_256(key[5]), set1_256(key[6]), set1_256(key[7]),\n  };\n  __m256i counter_low_vec, counter_high_vec;\n  load_counters8(counter, increment_counter, &counter_low_vec,\n                 &counter_high_vec);\n  uint8_t block_flags = flags | flags_start;\n\n  for (size_t block = 0; block < blocks; block++) {\n    if (block + 1 == blocks) {\n      block_flags |= flags_end;\n    }\n    __m256i block_len_vec = set1_256(BLAKE3_BLOCK_LEN);\n    __m256i block_flags_vec = set1_256(block_flags);\n    __m256i msg_vecs[16];\n    transpose_msg_vecs8(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs);\n\n    __m256i v[16] = {\n        h_vecs[0],       h_vecs[1],        h_vecs[2],       h_vecs[3],\n        h_vecs[4],       h_vecs[5],        h_vecs[6],       h_vecs[7],\n        set1_256(IV[0]), set1_256(IV[1]),  set1_256(IV[2]), set1_256(IV[3]),\n        counter_low_vec, counter_high_vec, block_len_vec,   block_flags_vec,\n    };\n    round_fn8(v, msg_vecs, 0);\n    round_fn8(v, msg_vecs, 1);\n    round_fn8(v, msg_vecs, 2);\n    round_fn8(v, msg_vecs, 3);\n    round_fn8(v, msg_vecs, 4);\n    round_fn8(v, msg_vecs, 5);\n    round_fn8(v, msg_vecs, 6);\n    h_vecs[0] = xor_256(v[0], v[8]);\n    h_vecs[1] = xor_256(v[1], v[9]);\n    h_vecs[2] = xor_256(v[2], v[10]);\n    h_vecs[3] = xor_256(v[3], v[11]);\n    h_vecs[4] = xor_256(v[4], v[12]);\n    h_vecs[5] = xor_256(v[5], v[13]);\n    h_vecs[6] = xor_256(v[6], v[14]);\n    h_vecs[7] = xor_256(v[7], v[15]);\n\n    block_flags = flags;\n  }\n\n  transpose_vecs_256(h_vecs);\n  storeu_256(h_vecs[0], &out[0 * sizeof(__m256i)]);\n  storeu_256(h_vecs[1], &out[1 * sizeof(__m256i)]);\n  storeu_256(h_vecs[2], &out[2 * sizeof(__m256i)]);\n  storeu_256(h_vecs[3], &out[3 * sizeof(__m256i)]);\n  storeu_256(h_vecs[4], &out[4 * sizeof(__m256i)]);\n  storeu_256(h_vecs[5], &out[5 * sizeof(__m256i)]);\n  storeu_256(h_vecs[6], &out[6 * sizeof(__m256i)]);\n  storeu_256(h_vecs[7], &out[7 * sizeof(__m256i)]);\n}\n\n/*\n * ----------------------------------------------------------------------------\n * hash16_avx512\n * ----------------------------------------------------------------------------\n */\n\nINLINE void round_fn16(__m512i v[16], __m512i m[16], size_t r) {\n  v[0] = add_512(v[0], m[(size_t)MSG_SCHEDULE[r][0]]);\n  v[1] = add_512(v[1], m[(size_t)MSG_SCHEDULE[r][2]]);\n  v[2] = add_512(v[2], m[(size_t)MSG_SCHEDULE[r][4]]);\n  v[3] = add_512(v[3], m[(size_t)MSG_SCHEDULE[r][6]]);\n  v[0] = add_512(v[0], v[4]);\n  v[1] = add_512(v[1], v[5]);\n  v[2] = add_512(v[2], v[6]);\n  v[3] = add_512(v[3], v[7]);\n  v[12] = xor_512(v[12], v[0]);\n  v[13] = xor_512(v[13], v[1]);\n  v[14] = xor_512(v[14], v[2]);\n  v[15] = xor_512(v[15], v[3]);\n  v[12] = rot16_512(v[12]);\n  v[13] = rot16_512(v[13]);\n  v[14] = rot16_512(v[14]);\n  v[15] = rot16_512(v[15]);\n  v[8] = add_512(v[8], v[12]);\n  v[9] = add_512(v[9], v[13]);\n  v[10] = add_512(v[10], v[14]);\n  v[11] = add_512(v[11], v[15]);\n  v[4] = xor_512(v[4], v[8]);\n  v[5] = xor_512(v[5], v[9]);\n  v[6] = xor_512(v[6], v[10]);\n  v[7] = xor_512(v[7], v[11]);\n  v[4] = rot12_512(v[4]);\n  v[5] = rot12_512(v[5]);\n  v[6] = rot12_512(v[6]);\n  v[7] = rot12_512(v[7]);\n  v[0] = add_512(v[0], m[(size_t)MSG_SCHEDULE[r][1]]);\n  v[1] = add_512(v[1], m[(size_t)MSG_SCHEDULE[r][3]]);\n  v[2] = add_512(v[2], m[(size_t)MSG_SCHEDULE[r][5]]);\n  v[3] = add_512(v[3], m[(size_t)MSG_SCHEDULE[r][7]]);\n  v[0] = add_512(v[0], v[4]);\n  v[1] = add_512(v[1], v[5]);\n  v[2] = add_512(v[2], v[6]);\n  v[3] = add_512(v[3], v[7]);\n  v[12] = xor_512(v[12], v[0]);\n  v[13] = xor_512(v[13], v[1]);\n  v[14] = xor_512(v[14], v[2]);\n  v[15] = xor_512(v[15], v[3]);\n  v[12] = rot8_512(v[12]);\n  v[13] = rot8_512(v[13]);\n  v[14] = rot8_512(v[14]);\n  v[15] = rot8_512(v[15]);\n  v[8] = add_512(v[8], v[12]);\n  v[9] = add_512(v[9], v[13]);\n  v[10] = add_512(v[10], v[14]);\n  v[11] = add_512(v[11], v[15]);\n  v[4] = xor_512(v[4], v[8]);\n  v[5] = xor_512(v[5], v[9]);\n  v[6] = xor_512(v[6], v[10]);\n  v[7] = xor_512(v[7], v[11]);\n  v[4] = rot7_512(v[4]);\n  v[5] = rot7_512(v[5]);\n  v[6] = rot7_512(v[6]);\n  v[7] = rot7_512(v[7]);\n\n  v[0] = add_512(v[0], m[(size_t)MSG_SCHEDULE[r][8]]);\n  v[1] = add_512(v[1], m[(size_t)MSG_SCHEDULE[r][10]]);\n  v[2] = add_512(v[2], m[(size_t)MSG_SCHEDULE[r][12]]);\n  v[3] = add_512(v[3], m[(size_t)MSG_SCHEDULE[r][14]]);\n  v[0] = add_512(v[0], v[5]);\n  v[1] = add_512(v[1], v[6]);\n  v[2] = add_512(v[2], v[7]);\n  v[3] = add_512(v[3], v[4]);\n  v[15] = xor_512(v[15], v[0]);\n  v[12] = xor_512(v[12], v[1]);\n  v[13] = xor_512(v[13], v[2]);\n  v[14] = xor_512(v[14], v[3]);\n  v[15] = rot16_512(v[15]);\n  v[12] = rot16_512(v[12]);\n  v[13] = rot16_512(v[13]);\n  v[14] = rot16_512(v[14]);\n  v[10] = add_512(v[10], v[15]);\n  v[11] = add_512(v[11], v[12]);\n  v[8] = add_512(v[8], v[13]);\n  v[9] = add_512(v[9], v[14]);\n  v[5] = xor_512(v[5], v[10]);\n  v[6] = xor_512(v[6], v[11]);\n  v[7] = xor_512(v[7], v[8]);\n  v[4] = xor_512(v[4], v[9]);\n  v[5] = rot12_512(v[5]);\n  v[6] = rot12_512(v[6]);\n  v[7] = rot12_512(v[7]);\n  v[4] = rot12_512(v[4]);\n  v[0] = add_512(v[0], m[(size_t)MSG_SCHEDULE[r][9]]);\n  v[1] = add_512(v[1], m[(size_t)MSG_SCHEDULE[r][11]]);\n  v[2] = add_512(v[2], m[(size_t)MSG_SCHEDULE[r][13]]);\n  v[3] = add_512(v[3], m[(size_t)MSG_SCHEDULE[r][15]]);\n  v[0] = add_512(v[0], v[5]);\n  v[1] = add_512(v[1], v[6]);\n  v[2] = add_512(v[2], v[7]);\n  v[3] = add_512(v[3], v[4]);\n  v[15] = xor_512(v[15], v[0]);\n  v[12] = xor_512(v[12], v[1]);\n  v[13] = xor_512(v[13], v[2]);\n  v[14] = xor_512(v[14], v[3]);\n  v[15] = rot8_512(v[15]);\n  v[12] = rot8_512(v[12]);\n  v[13] = rot8_512(v[13]);\n  v[14] = rot8_512(v[14]);\n  v[10] = add_512(v[10], v[15]);\n  v[11] = add_512(v[11], v[12]);\n  v[8] = add_512(v[8], v[13]);\n  v[9] = add_512(v[9], v[14]);\n  v[5] = xor_512(v[5], v[10]);\n  v[6] = xor_512(v[6], v[11]);\n  v[7] = xor_512(v[7], v[8]);\n  v[4] = xor_512(v[4], v[9]);\n  v[5] = rot7_512(v[5]);\n  v[6] = rot7_512(v[6]);\n  v[7] = rot7_512(v[7]);\n  v[4] = rot7_512(v[4]);\n}\n\n// 0b10001000, or lanes a0/a2/b0/b2 in little-endian order\n#define LO_IMM8 0x88\n\nINLINE __m512i unpack_lo_128(__m512i a, __m512i b) {\n  return _mm512_shuffle_i32x4(a, b, LO_IMM8);\n}\n\n// 0b11011101, or lanes a1/a3/b1/b3 in little-endian order\n#define HI_IMM8 0xdd\n\nINLINE __m512i unpack_hi_128(__m512i a, __m512i b) {\n  return _mm512_shuffle_i32x4(a, b, HI_IMM8);\n}\n\nINLINE void transpose_vecs_512(__m512i vecs[16]) {\n  // Interleave 32-bit lanes. The _0 unpack is lanes\n  // 0/0/1/1/4/4/5/5/8/8/9/9/12/12/13/13, and the _2 unpack is lanes\n  // 2/2/3/3/6/6/7/7/10/10/11/11/14/14/15/15.\n  __m512i ab_0 = _mm512_unpacklo_epi32(vecs[0], vecs[1]);\n  __m512i ab_2 = _mm512_unpackhi_epi32(vecs[0], vecs[1]);\n  __m512i cd_0 = _mm512_unpacklo_epi32(vecs[2], vecs[3]);\n  __m512i cd_2 = _mm512_unpackhi_epi32(vecs[2], vecs[3]);\n  __m512i ef_0 = _mm512_unpacklo_epi32(vecs[4], vecs[5]);\n  __m512i ef_2 = _mm512_unpackhi_epi32(vecs[4], vecs[5]);\n  __m512i gh_0 = _mm512_unpacklo_epi32(vecs[6], vecs[7]);\n  __m512i gh_2 = _mm512_unpackhi_epi32(vecs[6], vecs[7]);\n  __m512i ij_0 = _mm512_unpacklo_epi32(vecs[8], vecs[9]);\n  __m512i ij_2 = _mm512_unpackhi_epi32(vecs[8], vecs[9]);\n  __m512i kl_0 = _mm512_unpacklo_epi32(vecs[10], vecs[11]);\n  __m512i kl_2 = _mm512_unpackhi_epi32(vecs[10], vecs[11]);\n  __m512i mn_0 = _mm512_unpacklo_epi32(vecs[12], vecs[13]);\n  __m512i mn_2 = _mm512_unpackhi_epi32(vecs[12], vecs[13]);\n  __m512i op_0 = _mm512_unpacklo_epi32(vecs[14], vecs[15]);\n  __m512i op_2 = _mm512_unpackhi_epi32(vecs[14], vecs[15]);\n\n  // Interleave 64-bit lates. The _0 unpack is lanes\n  // 0/0/0/0/4/4/4/4/8/8/8/8/12/12/12/12, the _1 unpack is lanes\n  // 1/1/1/1/5/5/5/5/9/9/9/9/13/13/13/13, the _2 unpack is lanes\n  // 2/2/2/2/6/6/6/6/10/10/10/10/14/14/14/14, and the _3 unpack is lanes\n  // 3/3/3/3/7/7/7/7/11/11/11/11/15/15/15/15.\n  __m512i abcd_0 = _mm512_unpacklo_epi64(ab_0, cd_0);\n  __m512i abcd_1 = _mm512_unpackhi_epi64(ab_0, cd_0);\n  __m512i abcd_2 = _mm512_unpacklo_epi64(ab_2, cd_2);\n  __m512i abcd_3 = _mm512_unpackhi_epi64(ab_2, cd_2);\n  __m512i efgh_0 = _mm512_unpacklo_epi64(ef_0, gh_0);\n  __m512i efgh_1 = _mm512_unpackhi_epi64(ef_0, gh_0);\n  __m512i efgh_2 = _mm512_unpacklo_epi64(ef_2, gh_2);\n  __m512i efgh_3 = _mm512_unpackhi_epi64(ef_2, gh_2);\n  __m512i ijkl_0 = _mm512_unpacklo_epi64(ij_0, kl_0);\n  __m512i ijkl_1 = _mm512_unpackhi_epi64(ij_0, kl_0);\n  __m512i ijkl_2 = _mm512_unpacklo_epi64(ij_2, kl_2);\n  __m512i ijkl_3 = _mm512_unpackhi_epi64(ij_2, kl_2);\n  __m512i mnop_0 = _mm512_unpacklo_epi64(mn_0, op_0);\n  __m512i mnop_1 = _mm512_unpackhi_epi64(mn_0, op_0);\n  __m512i mnop_2 = _mm512_unpacklo_epi64(mn_2, op_2);\n  __m512i mnop_3 = _mm512_unpackhi_epi64(mn_2, op_2);\n\n  // Interleave 128-bit lanes. The _0 unpack is\n  // 0/0/0/0/8/8/8/8/0/0/0/0/8/8/8/8, the _1 unpack is\n  // 1/1/1/1/9/9/9/9/1/1/1/1/9/9/9/9, and so on.\n  __m512i abcdefgh_0 = unpack_lo_128(abcd_0, efgh_0);\n  __m512i abcdefgh_1 = unpack_lo_128(abcd_1, efgh_1);\n  __m512i abcdefgh_2 = unpack_lo_128(abcd_2, efgh_2);\n  __m512i abcdefgh_3 = unpack_lo_128(abcd_3, efgh_3);\n  __m512i abcdefgh_4 = unpack_hi_128(abcd_0, efgh_0);\n  __m512i abcdefgh_5 = unpack_hi_128(abcd_1, efgh_1);\n  __m512i abcdefgh_6 = unpack_hi_128(abcd_2, efgh_2);\n  __m512i abcdefgh_7 = unpack_hi_128(abcd_3, efgh_3);\n  __m512i ijklmnop_0 = unpack_lo_128(ijkl_0, mnop_0);\n  __m512i ijklmnop_1 = unpack_lo_128(ijkl_1, mnop_1);\n  __m512i ijklmnop_2 = unpack_lo_128(ijkl_2, mnop_2);\n  __m512i ijklmnop_3 = unpack_lo_128(ijkl_3, mnop_3);\n  __m512i ijklmnop_4 = unpack_hi_128(ijkl_0, mnop_0);\n  __m512i ijklmnop_5 = unpack_hi_128(ijkl_1, mnop_1);\n  __m512i ijklmnop_6 = unpack_hi_128(ijkl_2, mnop_2);\n  __m512i ijklmnop_7 = unpack_hi_128(ijkl_3, mnop_3);\n\n  // Interleave 128-bit lanes again for the final outputs.\n  vecs[0] = unpack_lo_128(abcdefgh_0, ijklmnop_0);\n  vecs[1] = unpack_lo_128(abcdefgh_1, ijklmnop_1);\n  vecs[2] = unpack_lo_128(abcdefgh_2, ijklmnop_2);\n  vecs[3] = unpack_lo_128(abcdefgh_3, ijklmnop_3);\n  vecs[4] = unpack_lo_128(abcdefgh_4, ijklmnop_4);\n  vecs[5] = unpack_lo_128(abcdefgh_5, ijklmnop_5);\n  vecs[6] = unpack_lo_128(abcdefgh_6, ijklmnop_6);\n  vecs[7] = unpack_lo_128(abcdefgh_7, ijklmnop_7);\n  vecs[8] = unpack_hi_128(abcdefgh_0, ijklmnop_0);\n  vecs[9] = unpack_hi_128(abcdefgh_1, ijklmnop_1);\n  vecs[10] = unpack_hi_128(abcdefgh_2, ijklmnop_2);\n  vecs[11] = unpack_hi_128(abcdefgh_3, ijklmnop_3);\n  vecs[12] = unpack_hi_128(abcdefgh_4, ijklmnop_4);\n  vecs[13] = unpack_hi_128(abcdefgh_5, ijklmnop_5);\n  vecs[14] = unpack_hi_128(abcdefgh_6, ijklmnop_6);\n  vecs[15] = unpack_hi_128(abcdefgh_7, ijklmnop_7);\n}\n\nINLINE void transpose_msg_vecs16(const uint8_t *const *inputs,\n                                 size_t block_offset, __m512i out[16]) {\n  out[0] = loadu_512(&inputs[0][block_offset]);\n  out[1] = loadu_512(&inputs[1][block_offset]);\n  out[2] = loadu_512(&inputs[2][block_offset]);\n  out[3] = loadu_512(&inputs[3][block_offset]);\n  out[4] = loadu_512(&inputs[4][block_offset]);\n  out[5] = loadu_512(&inputs[5][block_offset]);\n  out[6] = loadu_512(&inputs[6][block_offset]);\n  out[7] = loadu_512(&inputs[7][block_offset]);\n  out[8] = loadu_512(&inputs[8][block_offset]);\n  out[9] = loadu_512(&inputs[9][block_offset]);\n  out[10] = loadu_512(&inputs[10][block_offset]);\n  out[11] = loadu_512(&inputs[11][block_offset]);\n  out[12] = loadu_512(&inputs[12][block_offset]);\n  out[13] = loadu_512(&inputs[13][block_offset]);\n  out[14] = loadu_512(&inputs[14][block_offset]);\n  out[15] = loadu_512(&inputs[15][block_offset]);\n  for (size_t i = 0; i < 16; ++i) {\n    _mm_prefetch(&inputs[i][block_offset + 256], _MM_HINT_T0);\n  }\n  transpose_vecs_512(out);\n}\n\nINLINE void load_counters16(uint64_t counter, bool increment_counter,\n                            __m512i *out_lo, __m512i *out_hi) {\n  const __m512i mask = _mm512_set1_epi32(-(int32_t)increment_counter);\n  const __m512i add0 = _mm512_set_epi32(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);\n  const __m512i add1 = _mm512_and_si512(mask, add0);\n  __m512i l = _mm512_add_epi32(_mm512_set1_epi32(counter), add1);\n  __mmask16 carry = _mm512_cmp_epu32_mask(l, add1, _MM_CMPINT_LT);\n  __m512i h = _mm512_mask_add_epi32(_mm512_set1_epi32(counter >> 32), carry, _mm512_set1_epi32(counter >> 32), _mm512_set1_epi32(1));\n  *out_lo = l;\n  *out_hi = h;\n}\n\nvoid blake3_hash16_avx512(const uint8_t *const *inputs, size_t blocks,\n                          const uint32_t key[8], uint64_t counter,\n                          bool increment_counter, uint8_t flags,\n                          uint8_t flags_start, uint8_t flags_end,\n                          uint8_t *out) {\n  __m512i h_vecs[8] = {\n      set1_512(key[0]), set1_512(key[1]), set1_512(key[2]), set1_512(key[3]),\n      set1_512(key[4]), set1_512(key[5]), set1_512(key[6]), set1_512(key[7]),\n  };\n  __m512i counter_low_vec, counter_high_vec;\n  load_counters16(counter, increment_counter, &counter_low_vec,\n                  &counter_high_vec);\n  uint8_t block_flags = flags | flags_start;\n\n  for (size_t block = 0; block < blocks; block++) {\n    if (block + 1 == blocks) {\n      block_flags |= flags_end;\n    }\n    __m512i block_len_vec = set1_512(BLAKE3_BLOCK_LEN);\n    __m512i block_flags_vec = set1_512(block_flags);\n    __m512i msg_vecs[16];\n    transpose_msg_vecs16(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs);\n\n    __m512i v[16] = {\n        h_vecs[0],       h_vecs[1],        h_vecs[2],       h_vecs[3],\n        h_vecs[4],       h_vecs[5],        h_vecs[6],       h_vecs[7],\n        set1_512(IV[0]), set1_512(IV[1]),  set1_512(IV[2]), set1_512(IV[3]),\n        counter_low_vec, counter_high_vec, block_len_vec,   block_flags_vec,\n    };\n    round_fn16(v, msg_vecs, 0);\n    round_fn16(v, msg_vecs, 1);\n    round_fn16(v, msg_vecs, 2);\n    round_fn16(v, msg_vecs, 3);\n    round_fn16(v, msg_vecs, 4);\n    round_fn16(v, msg_vecs, 5);\n    round_fn16(v, msg_vecs, 6);\n    h_vecs[0] = xor_512(v[0], v[8]);\n    h_vecs[1] = xor_512(v[1], v[9]);\n    h_vecs[2] = xor_512(v[2], v[10]);\n    h_vecs[3] = xor_512(v[3], v[11]);\n    h_vecs[4] = xor_512(v[4], v[12]);\n    h_vecs[5] = xor_512(v[5], v[13]);\n    h_vecs[6] = xor_512(v[6], v[14]);\n    h_vecs[7] = xor_512(v[7], v[15]);\n\n    block_flags = flags;\n  }\n\n  // transpose_vecs_512 operates on a 16x16 matrix of words, but we only have 8\n  // state vectors. Pad the matrix with zeros. After transposition, store the\n  // lower half of each vector.\n  __m512i padded[16] = {\n      h_vecs[0],   h_vecs[1],   h_vecs[2],   h_vecs[3],\n      h_vecs[4],   h_vecs[5],   h_vecs[6],   h_vecs[7],\n      set1_512(0), set1_512(0), set1_512(0), set1_512(0),\n      set1_512(0), set1_512(0), set1_512(0), set1_512(0),\n  };\n  transpose_vecs_512(padded);\n  _mm256_mask_storeu_epi32(&out[0 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[0]));\n  _mm256_mask_storeu_epi32(&out[1 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[1]));\n  _mm256_mask_storeu_epi32(&out[2 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[2]));\n  _mm256_mask_storeu_epi32(&out[3 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[3]));\n  _mm256_mask_storeu_epi32(&out[4 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[4]));\n  _mm256_mask_storeu_epi32(&out[5 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[5]));\n  _mm256_mask_storeu_epi32(&out[6 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[6]));\n  _mm256_mask_storeu_epi32(&out[7 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[7]));\n  _mm256_mask_storeu_epi32(&out[8 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[8]));\n  _mm256_mask_storeu_epi32(&out[9 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[9]));\n  _mm256_mask_storeu_epi32(&out[10 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[10]));\n  _mm256_mask_storeu_epi32(&out[11 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[11]));\n  _mm256_mask_storeu_epi32(&out[12 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[12]));\n  _mm256_mask_storeu_epi32(&out[13 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[13]));\n  _mm256_mask_storeu_epi32(&out[14 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[14]));\n  _mm256_mask_storeu_epi32(&out[15 * sizeof(__m256i)], (__mmask8)-1, _mm512_castsi512_si256(padded[15]));\n}\n\n/*\n * ----------------------------------------------------------------------------\n * hash_many_avx512\n * ----------------------------------------------------------------------------\n */\n\nINLINE void hash_one_avx512(const uint8_t *input, size_t blocks,\n                            const uint32_t key[8], uint64_t counter,\n                            uint8_t flags, uint8_t flags_start,\n                            uint8_t flags_end, uint8_t out[BLAKE3_OUT_LEN]) {\n  uint32_t cv[8];\n  memcpy(cv, key, BLAKE3_KEY_LEN);\n  uint8_t block_flags = flags | flags_start;\n  while (blocks > 0) {\n    if (blocks == 1) {\n      block_flags |= flags_end;\n    }\n    blake3_compress_in_place_avx512(cv, input, BLAKE3_BLOCK_LEN, counter,\n                                    block_flags);\n    input = &input[BLAKE3_BLOCK_LEN];\n    blocks -= 1;\n    block_flags = flags;\n  }\n  memcpy(out, cv, BLAKE3_OUT_LEN);\n}\n\nvoid blake3_hash_many_avx512(const uint8_t *const *inputs, size_t num_inputs,\n                             size_t blocks, const uint32_t key[8],\n                             uint64_t counter, bool increment_counter,\n                             uint8_t flags, uint8_t flags_start,\n                             uint8_t flags_end, uint8_t *out) {\n  while (num_inputs >= 16) {\n    blake3_hash16_avx512(inputs, blocks, key, counter, increment_counter, flags,\n                         flags_start, flags_end, out);\n    if (increment_counter) {\n      counter += 16;\n    }\n    inputs += 16;\n    num_inputs -= 16;\n    out = &out[16 * BLAKE3_OUT_LEN];\n  }\n  while (num_inputs >= 8) {\n    blake3_hash8_avx512(inputs, blocks, key, counter, increment_counter, flags,\n                        flags_start, flags_end, out);\n    if (increment_counter) {\n      counter += 8;\n    }\n    inputs += 8;\n    num_inputs -= 8;\n    out = &out[8 * BLAKE3_OUT_LEN];\n  }\n  while (num_inputs >= 4) {\n    blake3_hash4_avx512(inputs, blocks, key, counter, increment_counter, flags,\n                        flags_start, flags_end, out);\n    if (increment_counter) {\n      counter += 4;\n    }\n    inputs += 4;\n    num_inputs -= 4;\n    out = &out[4 * BLAKE3_OUT_LEN];\n  }\n  while (num_inputs > 0) {\n    hash_one_avx512(inputs[0], blocks, key, counter, flags, flags_start,\n                    flags_end, out);\n    if (increment_counter) {\n      counter += 1;\n    }\n    inputs += 1;\n    num_inputs -= 1;\n    out = &out[BLAKE3_OUT_LEN];\n  }\n}\n"
  },
  {
    "path": "common/blake3/blake3_avx512_x86-64_unix.S",
    "content": "#if defined(__ELF__) && defined(__linux__)\n.section .note.GNU-stack,\"\",%progbits\n#endif\n\n#if defined(__ELF__) && defined(__CET__) && defined(__has_include)\n#if __has_include(<cet.h>)\n#include <cet.h>\n#endif\n#endif\n\n#if !defined(_CET_ENDBR)\n#define _CET_ENDBR\n#endif\n\n.intel_syntax noprefix\n.global _blake3_hash_many_avx512\n.global blake3_hash_many_avx512\n.global blake3_compress_in_place_avx512\n.global _blake3_compress_in_place_avx512\n.global blake3_compress_xof_avx512\n.global _blake3_compress_xof_avx512\n\n#ifdef __APPLE__\n.text\n#else\n.section .text\n#endif\n.p2align  6\n_blake3_hash_many_avx512:\nblake3_hash_many_avx512:\n        _CET_ENDBR\n        push    r15\n        push    r14\n        push    r13\n        push    r12\n        push    rbx\n        push    rbp\n        mov     rbp, rsp\n        sub     rsp, 144\n        and     rsp, 0xFFFFFFFFFFFFFFC0\n        neg     r9\n        kmovw   k1, r9d\n        vmovd   xmm0, r8d\n        vpbroadcastd ymm0, xmm0\n        shr     r8, 32\n        vmovd   xmm1, r8d\n        vpbroadcastd ymm1, xmm1\n        vmovdqa ymm4, ymm1\n        vmovdqa ymm5, ymm1\n        vpaddd  ymm2, ymm0, ymmword ptr [ADD0+rip]\n        vpaddd  ymm3, ymm0, ymmword ptr [ADD0+32+rip]\n        vpcmpltud k2, ymm2, ymm0\n        vpcmpltud k3, ymm3, ymm0\n        vpaddd  ymm4 {k2}, ymm4, dword ptr [ADD1+rip] {1to8}\n        vpaddd  ymm5 {k3}, ymm5, dword ptr [ADD1+rip] {1to8}\n        knotw   k2, k1\n        vmovdqa32 ymm2 {k2}, ymm0\n        vmovdqa32 ymm3 {k2}, ymm0\n        vmovdqa32 ymm4 {k2}, ymm1\n        vmovdqa32 ymm5 {k2}, ymm1\n        vmovdqa ymmword ptr [rsp], ymm2\n        vmovdqa ymmword ptr [rsp+0x1*0x20], ymm3\n        vmovdqa ymmword ptr [rsp+0x2*0x20], ymm4\n        vmovdqa ymmword ptr [rsp+0x3*0x20], ymm5\n        shl     rdx, 6\n        mov     qword ptr [rsp+0x80], rdx\n        cmp     rsi, 16\n        jc      3f\n2:\n        vpbroadcastd zmm0, dword ptr [rcx]\n        vpbroadcastd zmm1, dword ptr [rcx+0x1*0x4]\n        vpbroadcastd zmm2, dword ptr [rcx+0x2*0x4]\n        vpbroadcastd zmm3, dword ptr [rcx+0x3*0x4]\n        vpbroadcastd zmm4, dword ptr [rcx+0x4*0x4]\n        vpbroadcastd zmm5, dword ptr [rcx+0x5*0x4]\n        vpbroadcastd zmm6, dword ptr [rcx+0x6*0x4]\n        vpbroadcastd zmm7, dword ptr [rcx+0x7*0x4]\n        movzx   eax, byte ptr [rbp+0x38]\n        movzx   ebx, byte ptr [rbp+0x40]\n        or      eax, ebx\n        xor     edx, edx\n.p2align 5\n9:\n        movzx   ebx, byte ptr [rbp+0x48]\n        or      ebx, eax\n        add     rdx, 64\n        cmp     rdx, qword ptr [rsp+0x80]\n        cmove   eax, ebx\n        mov     dword ptr [rsp+0x88], eax\n        mov     r8, qword ptr [rdi]\n        mov     r9, qword ptr [rdi+0x8]\n        mov     r10, qword ptr [rdi+0x10]\n        mov     r11, qword ptr [rdi+0x18]\n        mov     r12, qword ptr [rdi+0x40]\n        mov     r13, qword ptr [rdi+0x48]\n        mov     r14, qword ptr [rdi+0x50]\n        mov     r15, qword ptr [rdi+0x58]\n        vmovdqu32 ymm16, ymmword ptr [rdx+r8-0x2*0x20]\n        vinserti64x4 zmm16, zmm16, ymmword ptr [rdx+r12-0x2*0x20], 0x01\n        vmovdqu32 ymm17, ymmword ptr [rdx+r9-0x2*0x20]\n        vinserti64x4 zmm17, zmm17, ymmword ptr [rdx+r13-0x2*0x20], 0x01\n        vpunpcklqdq zmm8, zmm16, zmm17\n        vpunpckhqdq zmm9, zmm16, zmm17\n        vmovdqu32 ymm18, ymmword ptr [rdx+r10-0x2*0x20]\n        vinserti64x4 zmm18, zmm18, ymmword ptr [rdx+r14-0x2*0x20], 0x01\n        vmovdqu32 ymm19, ymmword ptr [rdx+r11-0x2*0x20]\n        vinserti64x4 zmm19, zmm19, ymmword ptr [rdx+r15-0x2*0x20], 0x01\n        vpunpcklqdq zmm10, zmm18, zmm19\n        vpunpckhqdq zmm11, zmm18, zmm19\n        mov     r8, qword ptr [rdi+0x20]\n        mov     r9, qword ptr [rdi+0x28]\n        mov     r10, qword ptr [rdi+0x30]\n        mov     r11, qword ptr [rdi+0x38]\n        mov     r12, qword ptr [rdi+0x60]\n        mov     r13, qword ptr [rdi+0x68]\n        mov     r14, qword ptr [rdi+0x70]\n        mov     r15, qword ptr [rdi+0x78]\n        vmovdqu32 ymm16, ymmword ptr [rdx+r8-0x2*0x20]\n        vinserti64x4 zmm16, zmm16, ymmword ptr [rdx+r12-0x2*0x20], 0x01\n        vmovdqu32 ymm17, ymmword ptr [rdx+r9-0x2*0x20]\n        vinserti64x4 zmm17, zmm17, ymmword ptr [rdx+r13-0x2*0x20], 0x01\n        vpunpcklqdq zmm12, zmm16, zmm17\n        vpunpckhqdq zmm13, zmm16, zmm17\n        vmovdqu32 ymm18, ymmword ptr [rdx+r10-0x2*0x20]\n        vinserti64x4 zmm18, zmm18, ymmword ptr [rdx+r14-0x2*0x20], 0x01\n        vmovdqu32 ymm19, ymmword ptr [rdx+r11-0x2*0x20]\n        vinserti64x4 zmm19, zmm19, ymmword ptr [rdx+r15-0x2*0x20], 0x01\n        vpunpcklqdq zmm14, zmm18, zmm19\n        vpunpckhqdq zmm15, zmm18, zmm19\n        vmovdqa32 zmm27, zmmword ptr [INDEX0+rip]\n        vmovdqa32 zmm31, zmmword ptr [INDEX1+rip]\n        vshufps zmm16, zmm8, zmm10, 136\n        vshufps zmm17, zmm12, zmm14, 136\n        vmovdqa32 zmm20, zmm16\n        vpermt2d zmm16, zmm27, zmm17\n        vpermt2d zmm20, zmm31, zmm17\n        vshufps zmm17, zmm8, zmm10, 221\n        vshufps zmm30, zmm12, zmm14, 221\n        vmovdqa32 zmm21, zmm17\n        vpermt2d zmm17, zmm27, zmm30\n        vpermt2d zmm21, zmm31, zmm30\n        vshufps zmm18, zmm9, zmm11, 136\n        vshufps zmm8, zmm13, zmm15, 136\n        vmovdqa32 zmm22, zmm18\n        vpermt2d zmm18, zmm27, zmm8\n        vpermt2d zmm22, zmm31, zmm8\n        vshufps zmm19, zmm9, zmm11, 221\n        vshufps zmm8, zmm13, zmm15, 221\n        vmovdqa32 zmm23, zmm19\n        vpermt2d zmm19, zmm27, zmm8\n        vpermt2d zmm23, zmm31, zmm8\n        mov     r8, qword ptr [rdi]\n        mov     r9, qword ptr [rdi+0x8]\n        mov     r10, qword ptr [rdi+0x10]\n        mov     r11, qword ptr [rdi+0x18]\n        mov     r12, qword ptr [rdi+0x40]\n        mov     r13, qword ptr [rdi+0x48]\n        mov     r14, qword ptr [rdi+0x50]\n        mov     r15, qword ptr [rdi+0x58]\n        vmovdqu32 ymm24, ymmword ptr [r8+rdx-0x1*0x20]\n        vinserti64x4 zmm24, zmm24, ymmword ptr [r12+rdx-0x1*0x20], 0x01\n        vmovdqu32 ymm25, ymmword ptr [r9+rdx-0x1*0x20]\n        vinserti64x4 zmm25, zmm25, ymmword ptr [r13+rdx-0x1*0x20], 0x01\n        vpunpcklqdq zmm8, zmm24, zmm25\n        vpunpckhqdq zmm9, zmm24, zmm25\n        vmovdqu32 ymm24, ymmword ptr [r10+rdx-0x1*0x20]\n        vinserti64x4 zmm24, zmm24, ymmword ptr [r14+rdx-0x1*0x20], 0x01\n        vmovdqu32 ymm25, ymmword ptr [r11+rdx-0x1*0x20]\n        vinserti64x4 zmm25, zmm25, ymmword ptr [r15+rdx-0x1*0x20], 0x01\n        vpunpcklqdq zmm10, zmm24, zmm25\n        vpunpckhqdq zmm11, zmm24, zmm25\n        prefetcht0 [r8+rdx+0x80]\n        prefetcht0 [r12+rdx+0x80]\n        prefetcht0 [r9+rdx+0x80]\n        prefetcht0 [r13+rdx+0x80]\n        prefetcht0 [r10+rdx+0x80]\n        prefetcht0 [r14+rdx+0x80]\n        prefetcht0 [r11+rdx+0x80]\n        prefetcht0 [r15+rdx+0x80]\n        mov     r8, qword ptr [rdi+0x20]\n        mov     r9, qword ptr [rdi+0x28]\n        mov     r10, qword ptr [rdi+0x30]\n        mov     r11, qword ptr [rdi+0x38]\n        mov     r12, qword ptr [rdi+0x60]\n        mov     r13, qword ptr [rdi+0x68]\n        mov     r14, qword ptr [rdi+0x70]\n        mov     r15, qword ptr [rdi+0x78]\n        vmovdqu32 ymm24, ymmword ptr [r8+rdx-0x1*0x20]\n        vinserti64x4 zmm24, zmm24, ymmword ptr [r12+rdx-0x1*0x20], 0x01\n        vmovdqu32 ymm25, ymmword ptr [r9+rdx-0x1*0x20]\n        vinserti64x4 zmm25, zmm25, ymmword ptr [r13+rdx-0x1*0x20], 0x01\n        vpunpcklqdq zmm12, zmm24, zmm25\n        vpunpckhqdq zmm13, zmm24, zmm25\n        vmovdqu32 ymm24, ymmword ptr [r10+rdx-0x1*0x20]\n        vinserti64x4 zmm24, zmm24, ymmword ptr [r14+rdx-0x1*0x20], 0x01\n        vmovdqu32 ymm25, ymmword ptr [r11+rdx-0x1*0x20]\n        vinserti64x4 zmm25, zmm25, ymmword ptr [r15+rdx-0x1*0x20], 0x01\n        vpunpcklqdq zmm14, zmm24, zmm25\n        vpunpckhqdq zmm15, zmm24, zmm25\n        prefetcht0 [r8+rdx+0x80]\n        prefetcht0 [r12+rdx+0x80]\n        prefetcht0 [r9+rdx+0x80]\n        prefetcht0 [r13+rdx+0x80]\n        prefetcht0 [r10+rdx+0x80]\n        prefetcht0 [r14+rdx+0x80]\n        prefetcht0 [r11+rdx+0x80]\n        prefetcht0 [r15+rdx+0x80]\n        vshufps zmm24, zmm8, zmm10, 136\n        vshufps zmm30, zmm12, zmm14, 136\n        vmovdqa32 zmm28, zmm24\n        vpermt2d zmm24, zmm27, zmm30\n        vpermt2d zmm28, zmm31, zmm30\n        vshufps zmm25, zmm8, zmm10, 221\n        vshufps zmm30, zmm12, zmm14, 221\n        vmovdqa32 zmm29, zmm25\n        vpermt2d zmm25, zmm27, zmm30\n        vpermt2d zmm29, zmm31, zmm30\n        vshufps zmm26, zmm9, zmm11, 136\n        vshufps zmm8, zmm13, zmm15, 136\n        vmovdqa32 zmm30, zmm26\n        vpermt2d zmm26, zmm27, zmm8\n        vpermt2d zmm30, zmm31, zmm8\n        vshufps zmm8, zmm9, zmm11, 221\n        vshufps zmm10, zmm13, zmm15, 221\n        vpermi2d zmm27, zmm8, zmm10\n        vpermi2d zmm31, zmm8, zmm10\n        vpbroadcastd zmm8, dword ptr [BLAKE3_IV_0+rip]\n        vpbroadcastd zmm9, dword ptr [BLAKE3_IV_1+rip]\n        vpbroadcastd zmm10, dword ptr [BLAKE3_IV_2+rip]\n        vpbroadcastd zmm11, dword ptr [BLAKE3_IV_3+rip]\n        vmovdqa32 zmm12, zmmword ptr [rsp]\n        vmovdqa32 zmm13, zmmword ptr [rsp+0x1*0x40]\n        vpbroadcastd zmm14, dword ptr [BLAKE3_BLOCK_LEN+rip]\n        vpbroadcastd zmm15, dword ptr [rsp+0x22*0x4]\n        vpaddd  zmm0, zmm0, zmm16\n        vpaddd  zmm1, zmm1, zmm18\n        vpaddd  zmm2, zmm2, zmm20\n        vpaddd  zmm3, zmm3, zmm22\n        vpaddd  zmm0, zmm0, zmm4\n        vpaddd  zmm1, zmm1, zmm5\n        vpaddd  zmm2, zmm2, zmm6\n        vpaddd  zmm3, zmm3, zmm7\n        vpxord  zmm12, zmm12, zmm0\n        vpxord  zmm13, zmm13, zmm1\n        vpxord  zmm14, zmm14, zmm2\n        vpxord  zmm15, zmm15, zmm3\n        vprord  zmm12, zmm12, 16\n        vprord  zmm13, zmm13, 16\n        vprord  zmm14, zmm14, 16\n        vprord  zmm15, zmm15, 16\n        vpaddd  zmm8, zmm8, zmm12\n        vpaddd  zmm9, zmm9, zmm13\n        vpaddd  zmm10, zmm10, zmm14\n        vpaddd  zmm11, zmm11, zmm15\n        vpxord  zmm4, zmm4, zmm8\n        vpxord  zmm5, zmm5, zmm9\n        vpxord  zmm6, zmm6, zmm10\n        vpxord  zmm7, zmm7, zmm11\n        vprord  zmm4, zmm4, 12\n        vprord  zmm5, zmm5, 12\n        vprord  zmm6, zmm6, 12\n        vprord  zmm7, zmm7, 12\n        vpaddd  zmm0, zmm0, zmm17\n        vpaddd  zmm1, zmm1, zmm19\n        vpaddd  zmm2, zmm2, zmm21\n        vpaddd  zmm3, zmm3, zmm23\n        vpaddd  zmm0, zmm0, zmm4\n        vpaddd  zmm1, zmm1, zmm5\n        vpaddd  zmm2, zmm2, zmm6\n        vpaddd  zmm3, zmm3, zmm7\n        vpxord  zmm12, zmm12, zmm0\n        vpxord  zmm13, zmm13, zmm1\n        vpxord  zmm14, zmm14, zmm2\n        vpxord  zmm15, zmm15, zmm3\n        vprord  zmm12, zmm12, 8\n        vprord  zmm13, zmm13, 8\n        vprord  zmm14, zmm14, 8\n        vprord  zmm15, zmm15, 8\n        vpaddd  zmm8, zmm8, zmm12\n        vpaddd  zmm9, zmm9, zmm13\n        vpaddd  zmm10, zmm10, zmm14\n        vpaddd  zmm11, zmm11, zmm15\n        vpxord  zmm4, zmm4, zmm8\n        vpxord  zmm5, zmm5, zmm9\n        vpxord  zmm6, zmm6, zmm10\n        vpxord  zmm7, zmm7, zmm11\n        vprord  zmm4, zmm4, 7\n        vprord  zmm5, zmm5, 7\n        vprord  zmm6, zmm6, 7\n        vprord  zmm7, zmm7, 7\n        vpaddd  zmm0, zmm0, zmm24\n        vpaddd  zmm1, zmm1, zmm26\n        vpaddd  zmm2, zmm2, zmm28\n        vpaddd  zmm3, zmm3, zmm30\n        vpaddd  zmm0, zmm0, zmm5\n        vpaddd  zmm1, zmm1, zmm6\n        vpaddd  zmm2, zmm2, zmm7\n        vpaddd  zmm3, zmm3, zmm4\n        vpxord  zmm15, zmm15, zmm0\n        vpxord  zmm12, zmm12, zmm1\n        vpxord  zmm13, zmm13, zmm2\n        vpxord  zmm14, zmm14, zmm3\n        vprord  zmm15, zmm15, 16\n        vprord  zmm12, zmm12, 16\n        vprord  zmm13, zmm13, 16\n        vprord  zmm14, zmm14, 16\n        vpaddd  zmm10, zmm10, zmm15\n        vpaddd  zmm11, zmm11, zmm12\n        vpaddd  zmm8, zmm8, zmm13\n        vpaddd  zmm9, zmm9, zmm14\n        vpxord  zmm5, zmm5, zmm10\n        vpxord  zmm6, zmm6, zmm11\n        vpxord  zmm7, zmm7, zmm8\n        vpxord  zmm4, zmm4, zmm9\n        vprord  zmm5, zmm5, 12\n        vprord  zmm6, zmm6, 12\n        vprord  zmm7, zmm7, 12\n        vprord  zmm4, zmm4, 12\n        vpaddd  zmm0, zmm0, zmm25\n        vpaddd  zmm1, zmm1, zmm27\n        vpaddd  zmm2, zmm2, zmm29\n        vpaddd  zmm3, zmm3, zmm31\n        vpaddd  zmm0, zmm0, zmm5\n        vpaddd  zmm1, zmm1, zmm6\n        vpaddd  zmm2, zmm2, zmm7\n        vpaddd  zmm3, zmm3, zmm4\n        vpxord  zmm15, zmm15, zmm0\n        vpxord  zmm12, zmm12, zmm1\n        vpxord  zmm13, zmm13, zmm2\n        vpxord  zmm14, zmm14, zmm3\n        vprord  zmm15, zmm15, 8\n        vprord  zmm12, zmm12, 8\n        vprord  zmm13, zmm13, 8\n        vprord  zmm14, zmm14, 8\n        vpaddd  zmm10, zmm10, zmm15\n        vpaddd  zmm11, zmm11, zmm12\n        vpaddd  zmm8, zmm8, zmm13\n        vpaddd  zmm9, zmm9, zmm14\n        vpxord  zmm5, zmm5, zmm10\n        vpxord  zmm6, zmm6, zmm11\n        vpxord  zmm7, zmm7, zmm8\n        vpxord  zmm4, zmm4, zmm9\n        vprord  zmm5, zmm5, 7\n        vprord  zmm6, zmm6, 7\n        vprord  zmm7, zmm7, 7\n        vprord  zmm4, zmm4, 7\n        vpaddd  zmm0, zmm0, zmm18\n        vpaddd  zmm1, zmm1, zmm19\n        vpaddd  zmm2, zmm2, zmm23\n        vpaddd  zmm3, zmm3, zmm20\n        vpaddd  zmm0, zmm0, zmm4\n        vpaddd  zmm1, zmm1, zmm5\n        vpaddd  zmm2, zmm2, zmm6\n        vpaddd  zmm3, zmm3, zmm7\n        vpxord  zmm12, zmm12, zmm0\n        vpxord  zmm13, zmm13, zmm1\n        vpxord  zmm14, zmm14, zmm2\n        vpxord  zmm15, zmm15, zmm3\n        vprord  zmm12, zmm12, 16\n        vprord  zmm13, zmm13, 16\n        vprord  zmm14, zmm14, 16\n        vprord  zmm15, zmm15, 16\n        vpaddd  zmm8, zmm8, zmm12\n        vpaddd  zmm9, zmm9, zmm13\n        vpaddd  zmm10, zmm10, zmm14\n        vpaddd  zmm11, zmm11, zmm15\n        vpxord  zmm4, zmm4, zmm8\n        vpxord  zmm5, zmm5, zmm9\n        vpxord  zmm6, zmm6, zmm10\n        vpxord  zmm7, zmm7, zmm11\n        vprord  zmm4, zmm4, 12\n        vprord  zmm5, zmm5, 12\n        vprord  zmm6, zmm6, 12\n        vprord  zmm7, zmm7, 12\n        vpaddd  zmm0, zmm0, zmm22\n        vpaddd  zmm1, zmm1, zmm26\n        vpaddd  zmm2, zmm2, zmm16\n        vpaddd  zmm3, zmm3, zmm29\n        vpaddd  zmm0, zmm0, zmm4\n        vpaddd  zmm1, zmm1, zmm5\n        vpaddd  zmm2, zmm2, zmm6\n        vpaddd  zmm3, zmm3, zmm7\n        vpxord  zmm12, zmm12, zmm0\n        vpxord  zmm13, zmm13, zmm1\n        vpxord  zmm14, zmm14, zmm2\n        vpxord  zmm15, zmm15, zmm3\n        vprord  zmm12, zmm12, 8\n        vprord  zmm13, zmm13, 8\n        vprord  zmm14, zmm14, 8\n        vprord  zmm15, zmm15, 8\n        vpaddd  zmm8, zmm8, zmm12\n        vpaddd  zmm9, zmm9, zmm13\n        vpaddd  zmm10, zmm10, zmm14\n        vpaddd  zmm11, zmm11, zmm15\n        vpxord  zmm4, zmm4, zmm8\n        vpxord  zmm5, zmm5, zmm9\n        vpxord  zmm6, zmm6, zmm10\n        vpxord  zmm7, zmm7, zmm11\n        vprord  zmm4, zmm4, 7\n        vprord  zmm5, zmm5, 7\n        vprord  zmm6, zmm6, 7\n        vprord  zmm7, zmm7, 7\n        vpaddd  zmm0, zmm0, zmm17\n        vpaddd  zmm1, zmm1, zmm28\n        vpaddd  zmm2, zmm2, zmm25\n        vpaddd  zmm3, zmm3, zmm31\n        vpaddd  zmm0, zmm0, zmm5\n        vpaddd  zmm1, zmm1, zmm6\n        vpaddd  zmm2, zmm2, zmm7\n        vpaddd  zmm3, zmm3, zmm4\n        vpxord  zmm15, zmm15, zmm0\n        vpxord  zmm12, zmm12, zmm1\n        vpxord  zmm13, zmm13, zmm2\n        vpxord  zmm14, zmm14, zmm3\n        vprord  zmm15, zmm15, 16\n        vprord  zmm12, zmm12, 16\n        vprord  zmm13, zmm13, 16\n        vprord  zmm14, zmm14, 16\n        vpaddd  zmm10, zmm10, zmm15\n        vpaddd  zmm11, zmm11, zmm12\n        vpaddd  zmm8, zmm8, zmm13\n        vpaddd  zmm9, zmm9, zmm14\n        vpxord  zmm5, zmm5, zmm10\n        vpxord  zmm6, zmm6, zmm11\n        vpxord  zmm7, zmm7, zmm8\n        vpxord  zmm4, zmm4, zmm9\n        vprord  zmm5, zmm5, 12\n        vprord  zmm6, zmm6, 12\n        vprord  zmm7, zmm7, 12\n        vprord  zmm4, zmm4, 12\n        vpaddd  zmm0, zmm0, zmm27\n        vpaddd  zmm1, zmm1, zmm21\n        vpaddd  zmm2, zmm2, zmm30\n        vpaddd  zmm3, zmm3, zmm24\n        vpaddd  zmm0, zmm0, zmm5\n        vpaddd  zmm1, zmm1, zmm6\n        vpaddd  zmm2, zmm2, zmm7\n        vpaddd  zmm3, zmm3, zmm4\n        vpxord  zmm15, zmm15, zmm0\n        vpxord  zmm12, zmm12, zmm1\n        vpxord  zmm13, zmm13, zmm2\n        vpxord  zmm14, zmm14, zmm3\n        vprord  zmm15, zmm15, 8\n        vprord  zmm12, zmm12, 8\n        vprord  zmm13, zmm13, 8\n        vprord  zmm14, zmm14, 8\n        vpaddd  zmm10, zmm10, zmm15\n        vpaddd  zmm11, zmm11, zmm12\n        vpaddd  zmm8, zmm8, zmm13\n        vpaddd  zmm9, zmm9, zmm14\n        vpxord  zmm5, zmm5, zmm10\n        vpxord  zmm6, zmm6, zmm11\n        vpxord  zmm7, zmm7, zmm8\n        vpxord  zmm4, zmm4, zmm9\n        vprord  zmm5, zmm5, 7\n        vprord  zmm6, zmm6, 7\n        vprord  zmm7, zmm7, 7\n        vprord  zmm4, zmm4, 7\n        vpaddd  zmm0, zmm0, zmm19\n        vpaddd  zmm1, zmm1, zmm26\n        vpaddd  zmm2, zmm2, zmm29\n        vpaddd  zmm3, zmm3, zmm23\n        vpaddd  zmm0, zmm0, zmm4\n        vpaddd  zmm1, zmm1, zmm5\n        vpaddd  zmm2, zmm2, zmm6\n        vpaddd  zmm3, zmm3, zmm7\n        vpxord  zmm12, zmm12, zmm0\n        vpxord  zmm13, zmm13, zmm1\n        vpxord  zmm14, zmm14, zmm2\n        vpxord  zmm15, zmm15, zmm3\n        vprord  zmm12, zmm12, 16\n        vprord  zmm13, zmm13, 16\n        vprord  zmm14, zmm14, 16\n        vprord  zmm15, zmm15, 16\n        vpaddd  zmm8, zmm8, zmm12\n        vpaddd  zmm9, zmm9, zmm13\n        vpaddd  zmm10, zmm10, zmm14\n        vpaddd  zmm11, zmm11, zmm15\n        vpxord  zmm4, zmm4, zmm8\n        vpxord  zmm5, zmm5, zmm9\n        vpxord  zmm6, zmm6, zmm10\n        vpxord  zmm7, zmm7, zmm11\n        vprord  zmm4, zmm4, 12\n        vprord  zmm5, zmm5, 12\n        vprord  zmm6, zmm6, 12\n        vprord  zmm7, zmm7, 12\n        vpaddd  zmm0, zmm0, zmm20\n        vpaddd  zmm1, zmm1, zmm28\n        vpaddd  zmm2, zmm2, zmm18\n        vpaddd  zmm3, zmm3, zmm30\n        vpaddd  zmm0, zmm0, zmm4\n        vpaddd  zmm1, zmm1, zmm5\n        vpaddd  zmm2, zmm2, zmm6\n        vpaddd  zmm3, zmm3, zmm7\n        vpxord  zmm12, zmm12, zmm0\n        vpxord  zmm13, zmm13, zmm1\n        vpxord  zmm14, zmm14, zmm2\n        vpxord  zmm15, zmm15, zmm3\n        vprord  zmm12, zmm12, 8\n        vprord  zmm13, zmm13, 8\n        vprord  zmm14, zmm14, 8\n        vprord  zmm15, zmm15, 8\n        vpaddd  zmm8, zmm8, zmm12\n        vpaddd  zmm9, zmm9, zmm13\n        vpaddd  zmm10, zmm10, zmm14\n        vpaddd  zmm11, zmm11, zmm15\n        vpxord  zmm4, zmm4, zmm8\n        vpxord  zmm5, zmm5, zmm9\n        vpxord  zmm6, zmm6, zmm10\n        vpxord  zmm7, zmm7, zmm11\n        vprord  zmm4, zmm4, 7\n        vprord  zmm5, zmm5, 7\n        vprord  zmm6, zmm6, 7\n        vprord  zmm7, zmm7, 7\n        vpaddd  zmm0, zmm0, zmm22\n        vpaddd  zmm1, zmm1, zmm25\n        vpaddd  zmm2, zmm2, zmm27\n        vpaddd  zmm3, zmm3, zmm24\n        vpaddd  zmm0, zmm0, zmm5\n        vpaddd  zmm1, zmm1, zmm6\n        vpaddd  zmm2, zmm2, zmm7\n        vpaddd  zmm3, zmm3, zmm4\n        vpxord  zmm15, zmm15, zmm0\n        vpxord  zmm12, zmm12, zmm1\n        vpxord  zmm13, zmm13, zmm2\n        vpxord  zmm14, zmm14, zmm3\n        vprord  zmm15, zmm15, 16\n        vprord  zmm12, zmm12, 16\n        vprord  zmm13, zmm13, 16\n        vprord  zmm14, zmm14, 16\n        vpaddd  zmm10, zmm10, zmm15\n        vpaddd  zmm11, zmm11, zmm12\n        vpaddd  zmm8, zmm8, zmm13\n        vpaddd  zmm9, zmm9, zmm14\n        vpxord  zmm5, zmm5, zmm10\n        vpxord  zmm6, zmm6, zmm11\n        vpxord  zmm7, zmm7, zmm8\n        vpxord  zmm4, zmm4, zmm9\n        vprord  zmm5, zmm5, 12\n        vprord  zmm6, zmm6, 12\n        vprord  zmm7, zmm7, 12\n        vprord  zmm4, zmm4, 12\n        vpaddd  zmm0, zmm0, zmm21\n        vpaddd  zmm1, zmm1, zmm16\n        vpaddd  zmm2, zmm2, zmm31\n        vpaddd  zmm3, zmm3, zmm17\n        vpaddd  zmm0, zmm0, zmm5\n        vpaddd  zmm1, zmm1, zmm6\n        vpaddd  zmm2, zmm2, zmm7\n        vpaddd  zmm3, zmm3, zmm4\n        vpxord  zmm15, zmm15, zmm0\n        vpxord  zmm12, zmm12, zmm1\n        vpxord  zmm13, zmm13, zmm2\n        vpxord  zmm14, zmm14, zmm3\n        vprord  zmm15, zmm15, 8\n        vprord  zmm12, zmm12, 8\n        vprord  zmm13, zmm13, 8\n        vprord  zmm14, zmm14, 8\n        vpaddd  zmm10, zmm10, zmm15\n        vpaddd  zmm11, zmm11, zmm12\n        vpaddd  zmm8, zmm8, zmm13\n        vpaddd  zmm9, zmm9, zmm14\n        vpxord  zmm5, zmm5, zmm10\n        vpxord  zmm6, zmm6, zmm11\n        vpxord  zmm7, zmm7, zmm8\n        vpxord  zmm4, zmm4, zmm9\n        vprord  zmm5, zmm5, 7\n        vprord  zmm6, zmm6, 7\n        vprord  zmm7, zmm7, 7\n        vprord  zmm4, zmm4, 7\n        vpaddd  zmm0, zmm0, zmm26\n        vpaddd  zmm1, zmm1, zmm28\n        vpaddd  zmm2, zmm2, zmm30\n        vpaddd  zmm3, zmm3, zmm29\n        vpaddd  zmm0, zmm0, zmm4\n        vpaddd  zmm1, zmm1, zmm5\n        vpaddd  zmm2, zmm2, zmm6\n        vpaddd  zmm3, zmm3, zmm7\n        vpxord  zmm12, zmm12, zmm0\n        vpxord  zmm13, zmm13, zmm1\n        vpxord  zmm14, zmm14, zmm2\n        vpxord  zmm15, zmm15, zmm3\n        vprord  zmm12, zmm12, 16\n        vprord  zmm13, zmm13, 16\n        vprord  zmm14, zmm14, 16\n        vprord  zmm15, zmm15, 16\n        vpaddd  zmm8, zmm8, zmm12\n        vpaddd  zmm9, zmm9, zmm13\n        vpaddd  zmm10, zmm10, zmm14\n        vpaddd  zmm11, zmm11, zmm15\n        vpxord  zmm4, zmm4, zmm8\n        vpxord  zmm5, zmm5, zmm9\n        vpxord  zmm6, zmm6, zmm10\n        vpxord  zmm7, zmm7, zmm11\n        vprord  zmm4, zmm4, 12\n        vprord  zmm5, zmm5, 12\n        vprord  zmm6, zmm6, 12\n        vprord  zmm7, zmm7, 12\n        vpaddd  zmm0, zmm0, zmm23\n        vpaddd  zmm1, zmm1, zmm25\n        vpaddd  zmm2, zmm2, zmm19\n        vpaddd  zmm3, zmm3, zmm31\n        vpaddd  zmm0, zmm0, zmm4\n        vpaddd  zmm1, zmm1, zmm5\n        vpaddd  zmm2, zmm2, zmm6\n        vpaddd  zmm3, zmm3, zmm7\n        vpxord  zmm12, zmm12, zmm0\n        vpxord  zmm13, zmm13, zmm1\n        vpxord  zmm14, zmm14, zmm2\n        vpxord  zmm15, zmm15, zmm3\n        vprord  zmm12, zmm12, 8\n        vprord  zmm13, zmm13, 8\n        vprord  zmm14, zmm14, 8\n        vprord  zmm15, zmm15, 8\n        vpaddd  zmm8, zmm8, zmm12\n        vpaddd  zmm9, zmm9, zmm13\n        vpaddd  zmm10, zmm10, zmm14\n        vpaddd  zmm11, zmm11, zmm15\n        vpxord  zmm4, zmm4, zmm8\n        vpxord  zmm5, zmm5, zmm9\n        vpxord  zmm6, zmm6, zmm10\n        vpxord  zmm7, zmm7, zmm11\n        vprord  zmm4, zmm4, 7\n        vprord  zmm5, zmm5, 7\n        vprord  zmm6, zmm6, 7\n        vprord  zmm7, zmm7, 7\n        vpaddd  zmm0, zmm0, zmm20\n        vpaddd  zmm1, zmm1, zmm27\n        vpaddd  zmm2, zmm2, zmm21\n        vpaddd  zmm3, zmm3, zmm17\n        vpaddd  zmm0, zmm0, zmm5\n        vpaddd  zmm1, zmm1, zmm6\n        vpaddd  zmm2, zmm2, zmm7\n        vpaddd  zmm3, zmm3, zmm4\n        vpxord  zmm15, zmm15, zmm0\n        vpxord  zmm12, zmm12, zmm1\n        vpxord  zmm13, zmm13, zmm2\n        vpxord  zmm14, zmm14, zmm3\n        vprord  zmm15, zmm15, 16\n        vprord  zmm12, zmm12, 16\n        vprord  zmm13, zmm13, 16\n        vprord  zmm14, zmm14, 16\n        vpaddd  zmm10, zmm10, zmm15\n        vpaddd  zmm11, zmm11, zmm12\n        vpaddd  zmm8, zmm8, zmm13\n        vpaddd  zmm9, zmm9, zmm14\n        vpxord  zmm5, zmm5, zmm10\n        vpxord  zmm6, zmm6, zmm11\n        vpxord  zmm7, zmm7, zmm8\n        vpxord  zmm4, zmm4, zmm9\n        vprord  zmm5, zmm5, 12\n        vprord  zmm6, zmm6, 12\n        vprord  zmm7, zmm7, 12\n        vprord  zmm4, zmm4, 12\n        vpaddd  zmm0, zmm0, zmm16\n        vpaddd  zmm1, zmm1, zmm18\n        vpaddd  zmm2, zmm2, zmm24\n        vpaddd  zmm3, zmm3, zmm22\n        vpaddd  zmm0, zmm0, zmm5\n        vpaddd  zmm1, zmm1, zmm6\n        vpaddd  zmm2, zmm2, zmm7\n        vpaddd  zmm3, zmm3, zmm4\n        vpxord  zmm15, zmm15, zmm0\n        vpxord  zmm12, zmm12, zmm1\n        vpxord  zmm13, zmm13, zmm2\n        vpxord  zmm14, zmm14, zmm3\n        vprord  zmm15, zmm15, 8\n        vprord  zmm12, zmm12, 8\n        vprord  zmm13, zmm13, 8\n        vprord  zmm14, zmm14, 8\n        vpaddd  zmm10, zmm10, zmm15\n        vpaddd  zmm11, zmm11, zmm12\n        vpaddd  zmm8, zmm8, zmm13\n        vpaddd  zmm9, zmm9, zmm14\n        vpxord  zmm5, zmm5, zmm10\n        vpxord  zmm6, zmm6, zmm11\n        vpxord  zmm7, zmm7, zmm8\n        vpxord  zmm4, zmm4, zmm9\n        vprord  zmm5, zmm5, 7\n        vprord  zmm6, zmm6, 7\n        vprord  zmm7, zmm7, 7\n        vprord  zmm4, zmm4, 7\n        vpaddd  zmm0, zmm0, zmm28\n        vpaddd  zmm1, zmm1, zmm25\n        vpaddd  zmm2, zmm2, zmm31\n        vpaddd  zmm3, zmm3, zmm30\n        vpaddd  zmm0, zmm0, zmm4\n        vpaddd  zmm1, zmm1, zmm5\n        vpaddd  zmm2, zmm2, zmm6\n        vpaddd  zmm3, zmm3, zmm7\n        vpxord  zmm12, zmm12, zmm0\n        vpxord  zmm13, zmm13, zmm1\n        vpxord  zmm14, zmm14, zmm2\n        vpxord  zmm15, zmm15, zmm3\n        vprord  zmm12, zmm12, 16\n        vprord  zmm13, zmm13, 16\n        vprord  zmm14, zmm14, 16\n        vprord  zmm15, zmm15, 16\n        vpaddd  zmm8, zmm8, zmm12\n        vpaddd  zmm9, zmm9, zmm13\n        vpaddd  zmm10, zmm10, zmm14\n        vpaddd  zmm11, zmm11, zmm15\n        vpxord  zmm4, zmm4, zmm8\n        vpxord  zmm5, zmm5, zmm9\n        vpxord  zmm6, zmm6, zmm10\n        vpxord  zmm7, zmm7, zmm11\n        vprord  zmm4, zmm4, 12\n        vprord  zmm5, zmm5, 12\n        vprord  zmm6, zmm6, 12\n        vprord  zmm7, zmm7, 12\n        vpaddd  zmm0, zmm0, zmm29\n        vpaddd  zmm1, zmm1, zmm27\n        vpaddd  zmm2, zmm2, zmm26\n        vpaddd  zmm3, zmm3, zmm24\n        vpaddd  zmm0, zmm0, zmm4\n        vpaddd  zmm1, zmm1, zmm5\n        vpaddd  zmm2, zmm2, zmm6\n        vpaddd  zmm3, zmm3, zmm7\n        vpxord  zmm12, zmm12, zmm0\n        vpxord  zmm13, zmm13, zmm1\n        vpxord  zmm14, zmm14, zmm2\n        vpxord  zmm15, zmm15, zmm3\n        vprord  zmm12, zmm12, 8\n        vprord  zmm13, zmm13, 8\n        vprord  zmm14, zmm14, 8\n        vprord  zmm15, zmm15, 8\n        vpaddd  zmm8, zmm8, zmm12\n        vpaddd  zmm9, zmm9, zmm13\n        vpaddd  zmm10, zmm10, zmm14\n        vpaddd  zmm11, zmm11, zmm15\n        vpxord  zmm4, zmm4, zmm8\n        vpxord  zmm5, zmm5, zmm9\n        vpxord  zmm6, zmm6, zmm10\n        vpxord  zmm7, zmm7, zmm11\n        vprord  zmm4, zmm4, 7\n        vprord  zmm5, zmm5, 7\n        vprord  zmm6, zmm6, 7\n        vprord  zmm7, zmm7, 7\n        vpaddd  zmm0, zmm0, zmm23\n        vpaddd  zmm1, zmm1, zmm21\n        vpaddd  zmm2, zmm2, zmm16\n        vpaddd  zmm3, zmm3, zmm22\n        vpaddd  zmm0, zmm0, zmm5\n        vpaddd  zmm1, zmm1, zmm6\n        vpaddd  zmm2, zmm2, zmm7\n        vpaddd  zmm3, zmm3, zmm4\n        vpxord  zmm15, zmm15, zmm0\n        vpxord  zmm12, zmm12, zmm1\n        vpxord  zmm13, zmm13, zmm2\n        vpxord  zmm14, zmm14, zmm3\n        vprord  zmm15, zmm15, 16\n        vprord  zmm12, zmm12, 16\n        vprord  zmm13, zmm13, 16\n        vprord  zmm14, zmm14, 16\n        vpaddd  zmm10, zmm10, zmm15\n        vpaddd  zmm11, zmm11, zmm12\n        vpaddd  zmm8, zmm8, zmm13\n        vpaddd  zmm9, zmm9, zmm14\n        vpxord  zmm5, zmm5, zmm10\n        vpxord  zmm6, zmm6, zmm11\n        vpxord  zmm7, zmm7, zmm8\n        vpxord  zmm4, zmm4, zmm9\n        vprord  zmm5, zmm5, 12\n        vprord  zmm6, zmm6, 12\n        vprord  zmm7, zmm7, 12\n        vprord  zmm4, zmm4, 12\n        vpaddd  zmm0, zmm0, zmm18\n        vpaddd  zmm1, zmm1, zmm19\n        vpaddd  zmm2, zmm2, zmm17\n        vpaddd  zmm3, zmm3, zmm20\n        vpaddd  zmm0, zmm0, zmm5\n        vpaddd  zmm1, zmm1, zmm6\n        vpaddd  zmm2, zmm2, zmm7\n        vpaddd  zmm3, zmm3, zmm4\n        vpxord  zmm15, zmm15, zmm0\n        vpxord  zmm12, zmm12, zmm1\n        vpxord  zmm13, zmm13, zmm2\n        vpxord  zmm14, zmm14, zmm3\n        vprord  zmm15, zmm15, 8\n        vprord  zmm12, zmm12, 8\n        vprord  zmm13, zmm13, 8\n        vprord  zmm14, zmm14, 8\n        vpaddd  zmm10, zmm10, zmm15\n        vpaddd  zmm11, zmm11, zmm12\n        vpaddd  zmm8, zmm8, zmm13\n        vpaddd  zmm9, zmm9, zmm14\n        vpxord  zmm5, zmm5, zmm10\n        vpxord  zmm6, zmm6, zmm11\n        vpxord  zmm7, zmm7, zmm8\n        vpxord  zmm4, zmm4, zmm9\n        vprord  zmm5, zmm5, 7\n        vprord  zmm6, zmm6, 7\n        vprord  zmm7, zmm7, 7\n        vprord  zmm4, zmm4, 7\n        vpaddd  zmm0, zmm0, zmm25\n        vpaddd  zmm1, zmm1, zmm27\n        vpaddd  zmm2, zmm2, zmm24\n        vpaddd  zmm3, zmm3, zmm31\n        vpaddd  zmm0, zmm0, zmm4\n        vpaddd  zmm1, zmm1, zmm5\n        vpaddd  zmm2, zmm2, zmm6\n        vpaddd  zmm3, zmm3, zmm7\n        vpxord  zmm12, zmm12, zmm0\n        vpxord  zmm13, zmm13, zmm1\n        vpxord  zmm14, zmm14, zmm2\n        vpxord  zmm15, zmm15, zmm3\n        vprord  zmm12, zmm12, 16\n        vprord  zmm13, zmm13, 16\n        vprord  zmm14, zmm14, 16\n        vprord  zmm15, zmm15, 16\n        vpaddd  zmm8, zmm8, zmm12\n        vpaddd  zmm9, zmm9, zmm13\n        vpaddd  zmm10, zmm10, zmm14\n        vpaddd  zmm11, zmm11, zmm15\n        vpxord  zmm4, zmm4, zmm8\n        vpxord  zmm5, zmm5, zmm9\n        vpxord  zmm6, zmm6, zmm10\n        vpxord  zmm7, zmm7, zmm11\n        vprord  zmm4, zmm4, 12\n        vprord  zmm5, zmm5, 12\n        vprord  zmm6, zmm6, 12\n        vprord  zmm7, zmm7, 12\n        vpaddd  zmm0, zmm0, zmm30\n        vpaddd  zmm1, zmm1, zmm21\n        vpaddd  zmm2, zmm2, zmm28\n        vpaddd  zmm3, zmm3, zmm17\n        vpaddd  zmm0, zmm0, zmm4\n        vpaddd  zmm1, zmm1, zmm5\n        vpaddd  zmm2, zmm2, zmm6\n        vpaddd  zmm3, zmm3, zmm7\n        vpxord  zmm12, zmm12, zmm0\n        vpxord  zmm13, zmm13, zmm1\n        vpxord  zmm14, zmm14, zmm2\n        vpxord  zmm15, zmm15, zmm3\n        vprord  zmm12, zmm12, 8\n        vprord  zmm13, zmm13, 8\n        vprord  zmm14, zmm14, 8\n        vprord  zmm15, zmm15, 8\n        vpaddd  zmm8, zmm8, zmm12\n        vpaddd  zmm9, zmm9, zmm13\n        vpaddd  zmm10, zmm10, zmm14\n        vpaddd  zmm11, zmm11, zmm15\n        vpxord  zmm4, zmm4, zmm8\n        vpxord  zmm5, zmm5, zmm9\n        vpxord  zmm6, zmm6, zmm10\n        vpxord  zmm7, zmm7, zmm11\n        vprord  zmm4, zmm4, 7\n        vprord  zmm5, zmm5, 7\n        vprord  zmm6, zmm6, 7\n        vprord  zmm7, zmm7, 7\n        vpaddd  zmm0, zmm0, zmm29\n        vpaddd  zmm1, zmm1, zmm16\n        vpaddd  zmm2, zmm2, zmm18\n        vpaddd  zmm3, zmm3, zmm20\n        vpaddd  zmm0, zmm0, zmm5\n        vpaddd  zmm1, zmm1, zmm6\n        vpaddd  zmm2, zmm2, zmm7\n        vpaddd  zmm3, zmm3, zmm4\n        vpxord  zmm15, zmm15, zmm0\n        vpxord  zmm12, zmm12, zmm1\n        vpxord  zmm13, zmm13, zmm2\n        vpxord  zmm14, zmm14, zmm3\n        vprord  zmm15, zmm15, 16\n        vprord  zmm12, zmm12, 16\n        vprord  zmm13, zmm13, 16\n        vprord  zmm14, zmm14, 16\n        vpaddd  zmm10, zmm10, zmm15\n        vpaddd  zmm11, zmm11, zmm12\n        vpaddd  zmm8, zmm8, zmm13\n        vpaddd  zmm9, zmm9, zmm14\n        vpxord  zmm5, zmm5, zmm10\n        vpxord  zmm6, zmm6, zmm11\n        vpxord  zmm7, zmm7, zmm8\n        vpxord  zmm4, zmm4, zmm9\n        vprord  zmm5, zmm5, 12\n        vprord  zmm6, zmm6, 12\n        vprord  zmm7, zmm7, 12\n        vprord  zmm4, zmm4, 12\n        vpaddd  zmm0, zmm0, zmm19\n        vpaddd  zmm1, zmm1, zmm26\n        vpaddd  zmm2, zmm2, zmm22\n        vpaddd  zmm3, zmm3, zmm23\n        vpaddd  zmm0, zmm0, zmm5\n        vpaddd  zmm1, zmm1, zmm6\n        vpaddd  zmm2, zmm2, zmm7\n        vpaddd  zmm3, zmm3, zmm4\n        vpxord  zmm15, zmm15, zmm0\n        vpxord  zmm12, zmm12, zmm1\n        vpxord  zmm13, zmm13, zmm2\n        vpxord  zmm14, zmm14, zmm3\n        vprord  zmm15, zmm15, 8\n        vprord  zmm12, zmm12, 8\n        vprord  zmm13, zmm13, 8\n        vprord  zmm14, zmm14, 8\n        vpaddd  zmm10, zmm10, zmm15\n        vpaddd  zmm11, zmm11, zmm12\n        vpaddd  zmm8, zmm8, zmm13\n        vpaddd  zmm9, zmm9, zmm14\n        vpxord  zmm5, zmm5, zmm10\n        vpxord  zmm6, zmm6, zmm11\n        vpxord  zmm7, zmm7, zmm8\n        vpxord  zmm4, zmm4, zmm9\n        vprord  zmm5, zmm5, 7\n        vprord  zmm6, zmm6, 7\n        vprord  zmm7, zmm7, 7\n        vprord  zmm4, zmm4, 7\n        vpaddd  zmm0, zmm0, zmm27\n        vpaddd  zmm1, zmm1, zmm21\n        vpaddd  zmm2, zmm2, zmm17\n        vpaddd  zmm3, zmm3, zmm24\n        vpaddd  zmm0, zmm0, zmm4\n        vpaddd  zmm1, zmm1, zmm5\n        vpaddd  zmm2, zmm2, zmm6\n        vpaddd  zmm3, zmm3, zmm7\n        vpxord  zmm12, zmm12, zmm0\n        vpxord  zmm13, zmm13, zmm1\n        vpxord  zmm14, zmm14, zmm2\n        vpxord  zmm15, zmm15, zmm3\n        vprord  zmm12, zmm12, 16\n        vprord  zmm13, zmm13, 16\n        vprord  zmm14, zmm14, 16\n        vprord  zmm15, zmm15, 16\n        vpaddd  zmm8, zmm8, zmm12\n        vpaddd  zmm9, zmm9, zmm13\n        vpaddd  zmm10, zmm10, zmm14\n        vpaddd  zmm11, zmm11, zmm15\n        vpxord  zmm4, zmm4, zmm8\n        vpxord  zmm5, zmm5, zmm9\n        vpxord  zmm6, zmm6, zmm10\n        vpxord  zmm7, zmm7, zmm11\n        vprord  zmm4, zmm4, 12\n        vprord  zmm5, zmm5, 12\n        vprord  zmm6, zmm6, 12\n        vprord  zmm7, zmm7, 12\n        vpaddd  zmm0, zmm0, zmm31\n        vpaddd  zmm1, zmm1, zmm16\n        vpaddd  zmm2, zmm2, zmm25\n        vpaddd  zmm3, zmm3, zmm22\n        vpaddd  zmm0, zmm0, zmm4\n        vpaddd  zmm1, zmm1, zmm5\n        vpaddd  zmm2, zmm2, zmm6\n        vpaddd  zmm3, zmm3, zmm7\n        vpxord  zmm12, zmm12, zmm0\n        vpxord  zmm13, zmm13, zmm1\n        vpxord  zmm14, zmm14, zmm2\n        vpxord  zmm15, zmm15, zmm3\n        vprord  zmm12, zmm12, 8\n        vprord  zmm13, zmm13, 8\n        vprord  zmm14, zmm14, 8\n        vprord  zmm15, zmm15, 8\n        vpaddd  zmm8, zmm8, zmm12\n        vpaddd  zmm9, zmm9, zmm13\n        vpaddd  zmm10, zmm10, zmm14\n        vpaddd  zmm11, zmm11, zmm15\n        vpxord  zmm4, zmm4, zmm8\n        vpxord  zmm5, zmm5, zmm9\n        vpxord  zmm6, zmm6, zmm10\n        vpxord  zmm7, zmm7, zmm11\n        vprord  zmm4, zmm4, 7\n        vprord  zmm5, zmm5, 7\n        vprord  zmm6, zmm6, 7\n        vprord  zmm7, zmm7, 7\n        vpaddd  zmm0, zmm0, zmm30\n        vpaddd  zmm1, zmm1, zmm18\n        vpaddd  zmm2, zmm2, zmm19\n        vpaddd  zmm3, zmm3, zmm23\n        vpaddd  zmm0, zmm0, zmm5\n        vpaddd  zmm1, zmm1, zmm6\n        vpaddd  zmm2, zmm2, zmm7\n        vpaddd  zmm3, zmm3, zmm4\n        vpxord  zmm15, zmm15, zmm0\n        vpxord  zmm12, zmm12, zmm1\n        vpxord  zmm13, zmm13, zmm2\n        vpxord  zmm14, zmm14, zmm3\n        vprord  zmm15, zmm15, 16\n        vprord  zmm12, zmm12, 16\n        vprord  zmm13, zmm13, 16\n        vprord  zmm14, zmm14, 16\n        vpaddd  zmm10, zmm10, zmm15\n        vpaddd  zmm11, zmm11, zmm12\n        vpaddd  zmm8, zmm8, zmm13\n        vpaddd  zmm9, zmm9, zmm14\n        vpxord  zmm5, zmm5, zmm10\n        vpxord  zmm6, zmm6, zmm11\n        vpxord  zmm7, zmm7, zmm8\n        vpxord  zmm4, zmm4, zmm9\n        vprord  zmm5, zmm5, 12\n        vprord  zmm6, zmm6, 12\n        vprord  zmm7, zmm7, 12\n        vprord  zmm4, zmm4, 12\n        vpaddd  zmm0, zmm0, zmm26\n        vpaddd  zmm1, zmm1, zmm28\n        vpaddd  zmm2, zmm2, zmm20\n        vpaddd  zmm3, zmm3, zmm29\n        vpaddd  zmm0, zmm0, zmm5\n        vpaddd  zmm1, zmm1, zmm6\n        vpaddd  zmm2, zmm2, zmm7\n        vpaddd  zmm3, zmm3, zmm4\n        vpxord  zmm15, zmm15, zmm0\n        vpxord  zmm12, zmm12, zmm1\n        vpxord  zmm13, zmm13, zmm2\n        vpxord  zmm14, zmm14, zmm3\n        vprord  zmm15, zmm15, 8\n        vprord  zmm12, zmm12, 8\n        vprord  zmm13, zmm13, 8\n        vprord  zmm14, zmm14, 8\n        vpaddd  zmm10, zmm10, zmm15\n        vpaddd  zmm11, zmm11, zmm12\n        vpaddd  zmm8, zmm8, zmm13\n        vpaddd  zmm9, zmm9, zmm14\n        vpxord  zmm5, zmm5, zmm10\n        vpxord  zmm6, zmm6, zmm11\n        vpxord  zmm7, zmm7, zmm8\n        vpxord  zmm4, zmm4, zmm9\n        vprord  zmm5, zmm5, 7\n        vprord  zmm6, zmm6, 7\n        vprord  zmm7, zmm7, 7\n        vprord  zmm4, zmm4, 7\n        vpxord  zmm0, zmm0, zmm8\n        vpxord  zmm1, zmm1, zmm9\n        vpxord  zmm2, zmm2, zmm10\n        vpxord  zmm3, zmm3, zmm11\n        vpxord  zmm4, zmm4, zmm12\n        vpxord  zmm5, zmm5, zmm13\n        vpxord  zmm6, zmm6, zmm14\n        vpxord  zmm7, zmm7, zmm15\n        movzx   eax, byte ptr [rbp+0x38]\n        jne     9b\n        mov     rbx, qword ptr [rbp+0x50]\n        vpunpckldq zmm16, zmm0, zmm1\n        vpunpckhdq zmm17, zmm0, zmm1\n        vpunpckldq zmm18, zmm2, zmm3\n        vpunpckhdq zmm19, zmm2, zmm3\n        vpunpckldq zmm20, zmm4, zmm5\n        vpunpckhdq zmm21, zmm4, zmm5\n        vpunpckldq zmm22, zmm6, zmm7\n        vpunpckhdq zmm23, zmm6, zmm7\n        vpunpcklqdq zmm0, zmm16, zmm18\n        vpunpckhqdq zmm1, zmm16, zmm18\n        vpunpcklqdq zmm2, zmm17, zmm19\n        vpunpckhqdq zmm3, zmm17, zmm19\n        vpunpcklqdq zmm4, zmm20, zmm22\n        vpunpckhqdq zmm5, zmm20, zmm22\n        vpunpcklqdq zmm6, zmm21, zmm23\n        vpunpckhqdq zmm7, zmm21, zmm23\n        vshufi32x4 zmm16, zmm0, zmm4, 0x88\n        vshufi32x4 zmm17, zmm1, zmm5, 0x88\n        vshufi32x4 zmm18, zmm2, zmm6, 0x88\n        vshufi32x4 zmm19, zmm3, zmm7, 0x88\n        vshufi32x4 zmm20, zmm0, zmm4, 0xDD\n        vshufi32x4 zmm21, zmm1, zmm5, 0xDD\n        vshufi32x4 zmm22, zmm2, zmm6, 0xDD\n        vshufi32x4 zmm23, zmm3, zmm7, 0xDD\n        vshufi32x4 zmm0, zmm16, zmm17, 0x88\n        vshufi32x4 zmm1, zmm18, zmm19, 0x88\n        vshufi32x4 zmm2, zmm20, zmm21, 0x88\n        vshufi32x4 zmm3, zmm22, zmm23, 0x88\n        vshufi32x4 zmm4, zmm16, zmm17, 0xDD\n        vshufi32x4 zmm5, zmm18, zmm19, 0xDD\n        vshufi32x4 zmm6, zmm20, zmm21, 0xDD\n        vshufi32x4 zmm7, zmm22, zmm23, 0xDD\n        vmovdqu32 zmmword ptr [rbx], zmm0\n        vmovdqu32 zmmword ptr [rbx+0x1*0x40], zmm1\n        vmovdqu32 zmmword ptr [rbx+0x2*0x40], zmm2\n        vmovdqu32 zmmword ptr [rbx+0x3*0x40], zmm3\n        vmovdqu32 zmmword ptr [rbx+0x4*0x40], zmm4\n        vmovdqu32 zmmword ptr [rbx+0x5*0x40], zmm5\n        vmovdqu32 zmmword ptr [rbx+0x6*0x40], zmm6\n        vmovdqu32 zmmword ptr [rbx+0x7*0x40], zmm7\n        vmovdqa32 zmm0, zmmword ptr [rsp]\n        vmovdqa32 zmm1, zmmword ptr [rsp+0x1*0x40]\n        vmovdqa32 zmm2, zmm0\n        vpaddd  zmm2{k1}, zmm0, dword ptr [ADD16+rip] {1to16}\n        vpcmpltud k2, zmm2, zmm0\n        vpaddd  zmm1 {k2}, zmm1, dword ptr [ADD1+rip] {1to16}\n        vmovdqa32 zmmword ptr [rsp], zmm2\n        vmovdqa32 zmmword ptr [rsp+0x1*0x40], zmm1\n        add     rdi, 128\n        add     rbx, 512\n        mov     qword ptr [rbp+0x50], rbx\n        sub     rsi, 16\n        cmp     rsi, 16\n        jnc     2b\n        test    rsi, rsi\n        jnz     3f\n4:\n        vzeroupper\n        mov     rsp, rbp\n        pop     rbp\n        pop     rbx\n        pop     r12\n        pop     r13\n        pop     r14\n        pop     r15\n        ret\n.p2align 6\n3:\n        test    esi, 0x8\n        je      3f\n        vpbroadcastd ymm0, dword ptr [rcx]\n        vpbroadcastd ymm1, dword ptr [rcx+0x4]\n        vpbroadcastd ymm2, dword ptr [rcx+0x8]\n        vpbroadcastd ymm3, dword ptr [rcx+0xC]\n        vpbroadcastd ymm4, dword ptr [rcx+0x10]\n        vpbroadcastd ymm5, dword ptr [rcx+0x14]\n        vpbroadcastd ymm6, dword ptr [rcx+0x18]\n        vpbroadcastd ymm7, dword ptr [rcx+0x1C]\n        mov     r8, qword ptr [rdi]\n        mov     r9, qword ptr [rdi+0x8]\n        mov     r10, qword ptr [rdi+0x10]\n        mov     r11, qword ptr [rdi+0x18]\n        mov     r12, qword ptr [rdi+0x20]\n        mov     r13, qword ptr [rdi+0x28]\n        mov     r14, qword ptr [rdi+0x30]\n        mov     r15, qword ptr [rdi+0x38]\n        movzx   eax, byte ptr [rbp+0x38]\n        movzx   ebx, byte ptr [rbp+0x40]\n        or      eax, ebx\n        xor     edx, edx\n2:\n        movzx   ebx, byte ptr [rbp+0x48]\n        or      ebx, eax\n        add     rdx, 64\n        cmp     rdx, qword ptr [rsp+0x80]\n        cmove   eax, ebx\n        mov     dword ptr [rsp+0x88], eax\n        vmovups xmm8, xmmword ptr [r8+rdx-0x40]\n        vinsertf128 ymm8, ymm8, xmmword ptr [r12+rdx-0x40], 0x01\n        vmovups xmm9, xmmword ptr [r9+rdx-0x40]\n        vinsertf128 ymm9, ymm9, xmmword ptr [r13+rdx-0x40], 0x01\n        vunpcklpd ymm12, ymm8, ymm9\n        vunpckhpd ymm13, ymm8, ymm9\n        vmovups xmm10, xmmword ptr [r10+rdx-0x40]\n        vinsertf128 ymm10, ymm10, xmmword ptr [r14+rdx-0x40], 0x01\n        vmovups xmm11, xmmword ptr [r11+rdx-0x40]\n        vinsertf128 ymm11, ymm11, xmmword ptr [r15+rdx-0x40], 0x01\n        vunpcklpd ymm14, ymm10, ymm11\n        vunpckhpd ymm15, ymm10, ymm11\n        vshufps ymm16, ymm12, ymm14, 136\n        vshufps ymm17, ymm12, ymm14, 221\n        vshufps ymm18, ymm13, ymm15, 136\n        vshufps ymm19, ymm13, ymm15, 221\n        vmovups xmm8, xmmword ptr [r8+rdx-0x30]\n        vinsertf128 ymm8, ymm8, xmmword ptr [r12+rdx-0x30], 0x01\n        vmovups xmm9, xmmword ptr [r9+rdx-0x30]\n        vinsertf128 ymm9, ymm9, xmmword ptr [r13+rdx-0x30], 0x01\n        vunpcklpd ymm12, ymm8, ymm9\n        vunpckhpd ymm13, ymm8, ymm9\n        vmovups xmm10, xmmword ptr [r10+rdx-0x30]\n        vinsertf128 ymm10, ymm10, xmmword ptr [r14+rdx-0x30], 0x01\n        vmovups xmm11, xmmword ptr [r11+rdx-0x30]\n        vinsertf128 ymm11, ymm11, xmmword ptr [r15+rdx-0x30], 0x01\n        vunpcklpd ymm14, ymm10, ymm11\n        vunpckhpd ymm15, ymm10, ymm11\n        vshufps ymm20, ymm12, ymm14, 136\n        vshufps ymm21, ymm12, ymm14, 221\n        vshufps ymm22, ymm13, ymm15, 136\n        vshufps ymm23, ymm13, ymm15, 221\n        vmovups xmm8, xmmword ptr [r8+rdx-0x20]\n        vinsertf128 ymm8, ymm8, xmmword ptr [r12+rdx-0x20], 0x01\n        vmovups xmm9, xmmword ptr [r9+rdx-0x20]\n        vinsertf128 ymm9, ymm9, xmmword ptr [r13+rdx-0x20], 0x01\n        vunpcklpd ymm12, ymm8, ymm9\n        vunpckhpd ymm13, ymm8, ymm9\n        vmovups xmm10, xmmword ptr [r10+rdx-0x20]\n        vinsertf128 ymm10, ymm10, xmmword ptr [r14+rdx-0x20], 0x01\n        vmovups xmm11, xmmword ptr [r11+rdx-0x20]\n        vinsertf128 ymm11, ymm11, xmmword ptr [r15+rdx-0x20], 0x01\n        vunpcklpd ymm14, ymm10, ymm11\n        vunpckhpd ymm15, ymm10, ymm11\n        vshufps ymm24, ymm12, ymm14, 136\n        vshufps ymm25, ymm12, ymm14, 221\n        vshufps ymm26, ymm13, ymm15, 136\n        vshufps ymm27, ymm13, ymm15, 221\n        vmovups xmm8, xmmword ptr [r8+rdx-0x10]\n        vinsertf128 ymm8, ymm8, xmmword ptr [r12+rdx-0x10], 0x01\n        vmovups xmm9, xmmword ptr [r9+rdx-0x10]\n        vinsertf128 ymm9, ymm9, xmmword ptr [r13+rdx-0x10], 0x01\n        vunpcklpd ymm12, ymm8, ymm9\n        vunpckhpd ymm13, ymm8, ymm9\n        vmovups xmm10, xmmword ptr [r10+rdx-0x10]\n        vinsertf128 ymm10, ymm10, xmmword ptr [r14+rdx-0x10], 0x01\n        vmovups xmm11, xmmword ptr [r11+rdx-0x10]\n        vinsertf128 ymm11, ymm11, xmmword ptr [r15+rdx-0x10], 0x01\n        vunpcklpd ymm14, ymm10, ymm11\n        vunpckhpd ymm15, ymm10, ymm11\n        vshufps ymm28, ymm12, ymm14, 136\n        vshufps ymm29, ymm12, ymm14, 221\n        vshufps ymm30, ymm13, ymm15, 136\n        vshufps ymm31, ymm13, ymm15, 221\n        vpbroadcastd ymm8, dword ptr [BLAKE3_IV_0+rip]\n        vpbroadcastd ymm9, dword ptr [BLAKE3_IV_1+rip]\n        vpbroadcastd ymm10, dword ptr [BLAKE3_IV_2+rip]\n        vpbroadcastd ymm11, dword ptr [BLAKE3_IV_3+rip]\n        vmovdqa ymm12, ymmword ptr [rsp]\n        vmovdqa ymm13, ymmword ptr [rsp+0x40]\n        vpbroadcastd ymm14, dword ptr [BLAKE3_BLOCK_LEN+rip]\n        vpbroadcastd ymm15, dword ptr [rsp+0x88]\n        vpaddd  ymm0, ymm0, ymm16\n        vpaddd  ymm1, ymm1, ymm18\n        vpaddd  ymm2, ymm2, ymm20\n        vpaddd  ymm3, ymm3, ymm22\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxord  ymm12, ymm12, ymm0\n        vpxord  ymm13, ymm13, ymm1\n        vpxord  ymm14, ymm14, ymm2\n        vpxord  ymm15, ymm15, ymm3\n        vprord  ymm12, ymm12, 16\n        vprord  ymm13, ymm13, 16\n        vprord  ymm14, ymm14, 16\n        vprord  ymm15, ymm15, 16\n        vpaddd  ymm8, ymm8, ymm12\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxord  ymm4, ymm4, ymm8\n        vpxord  ymm5, ymm5, ymm9\n        vpxord  ymm6, ymm6, ymm10\n        vpxord  ymm7, ymm7, ymm11\n        vprord  ymm4, ymm4, 12\n        vprord  ymm5, ymm5, 12\n        vprord  ymm6, ymm6, 12\n        vprord  ymm7, ymm7, 12\n        vpaddd  ymm0, ymm0, ymm17\n        vpaddd  ymm1, ymm1, ymm19\n        vpaddd  ymm2, ymm2, ymm21\n        vpaddd  ymm3, ymm3, ymm23\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxord  ymm12, ymm12, ymm0\n        vpxord  ymm13, ymm13, ymm1\n        vpxord  ymm14, ymm14, ymm2\n        vpxord  ymm15, ymm15, ymm3\n        vprord  ymm12, ymm12, 8\n        vprord  ymm13, ymm13, 8\n        vprord  ymm14, ymm14, 8\n        vprord  ymm15, ymm15, 8\n        vpaddd  ymm8, ymm8, ymm12\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxord  ymm4, ymm4, ymm8\n        vpxord  ymm5, ymm5, ymm9\n        vpxord  ymm6, ymm6, ymm10\n        vpxord  ymm7, ymm7, ymm11\n        vprord  ymm4, ymm4, 7\n        vprord  ymm5, ymm5, 7\n        vprord  ymm6, ymm6, 7\n        vprord  ymm7, ymm7, 7\n        vpaddd  ymm0, ymm0, ymm24\n        vpaddd  ymm1, ymm1, ymm26\n        vpaddd  ymm2, ymm2, ymm28\n        vpaddd  ymm3, ymm3, ymm30\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxord  ymm15, ymm15, ymm0\n        vpxord  ymm12, ymm12, ymm1\n        vpxord  ymm13, ymm13, ymm2\n        vpxord  ymm14, ymm14, ymm3\n        vprord  ymm15, ymm15, 16\n        vprord  ymm12, ymm12, 16\n        vprord  ymm13, ymm13, 16\n        vprord  ymm14, ymm14, 16\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm8, ymm13\n        vpaddd  ymm9, ymm9, ymm14\n        vpxord  ymm5, ymm5, ymm10\n        vpxord  ymm6, ymm6, ymm11\n        vpxord  ymm7, ymm7, ymm8\n        vpxord  ymm4, ymm4, ymm9\n        vprord  ymm5, ymm5, 12\n        vprord  ymm6, ymm6, 12\n        vprord  ymm7, ymm7, 12\n        vprord  ymm4, ymm4, 12\n        vpaddd  ymm0, ymm0, ymm25\n        vpaddd  ymm1, ymm1, ymm27\n        vpaddd  ymm2, ymm2, ymm29\n        vpaddd  ymm3, ymm3, ymm31\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxord  ymm15, ymm15, ymm0\n        vpxord  ymm12, ymm12, ymm1\n        vpxord  ymm13, ymm13, ymm2\n        vpxord  ymm14, ymm14, ymm3\n        vprord  ymm15, ymm15, 8\n        vprord  ymm12, ymm12, 8\n        vprord  ymm13, ymm13, 8\n        vprord  ymm14, ymm14, 8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm8, ymm13\n        vpaddd  ymm9, ymm9, ymm14\n        vpxord  ymm5, ymm5, ymm10\n        vpxord  ymm6, ymm6, ymm11\n        vpxord  ymm7, ymm7, ymm8\n        vpxord  ymm4, ymm4, ymm9\n        vprord  ymm5, ymm5, 7\n        vprord  ymm6, ymm6, 7\n        vprord  ymm7, ymm7, 7\n        vprord  ymm4, ymm4, 7\n        vpaddd  ymm0, ymm0, ymm18\n        vpaddd  ymm1, ymm1, ymm19\n        vpaddd  ymm2, ymm2, ymm23\n        vpaddd  ymm3, ymm3, ymm20\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxord  ymm12, ymm12, ymm0\n        vpxord  ymm13, ymm13, ymm1\n        vpxord  ymm14, ymm14, ymm2\n        vpxord  ymm15, ymm15, ymm3\n        vprord  ymm12, ymm12, 16\n        vprord  ymm13, ymm13, 16\n        vprord  ymm14, ymm14, 16\n        vprord  ymm15, ymm15, 16\n        vpaddd  ymm8, ymm8, ymm12\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxord  ymm4, ymm4, ymm8\n        vpxord  ymm5, ymm5, ymm9\n        vpxord  ymm6, ymm6, ymm10\n        vpxord  ymm7, ymm7, ymm11\n        vprord  ymm4, ymm4, 12\n        vprord  ymm5, ymm5, 12\n        vprord  ymm6, ymm6, 12\n        vprord  ymm7, ymm7, 12\n        vpaddd  ymm0, ymm0, ymm22\n        vpaddd  ymm1, ymm1, ymm26\n        vpaddd  ymm2, ymm2, ymm16\n        vpaddd  ymm3, ymm3, ymm29\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxord  ymm12, ymm12, ymm0\n        vpxord  ymm13, ymm13, ymm1\n        vpxord  ymm14, ymm14, ymm2\n        vpxord  ymm15, ymm15, ymm3\n        vprord  ymm12, ymm12, 8\n        vprord  ymm13, ymm13, 8\n        vprord  ymm14, ymm14, 8\n        vprord  ymm15, ymm15, 8\n        vpaddd  ymm8, ymm8, ymm12\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxord  ymm4, ymm4, ymm8\n        vpxord  ymm5, ymm5, ymm9\n        vpxord  ymm6, ymm6, ymm10\n        vpxord  ymm7, ymm7, ymm11\n        vprord  ymm4, ymm4, 7\n        vprord  ymm5, ymm5, 7\n        vprord  ymm6, ymm6, 7\n        vprord  ymm7, ymm7, 7\n        vpaddd  ymm0, ymm0, ymm17\n        vpaddd  ymm1, ymm1, ymm28\n        vpaddd  ymm2, ymm2, ymm25\n        vpaddd  ymm3, ymm3, ymm31\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxord  ymm15, ymm15, ymm0\n        vpxord  ymm12, ymm12, ymm1\n        vpxord  ymm13, ymm13, ymm2\n        vpxord  ymm14, ymm14, ymm3\n        vprord  ymm15, ymm15, 16\n        vprord  ymm12, ymm12, 16\n        vprord  ymm13, ymm13, 16\n        vprord  ymm14, ymm14, 16\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm8, ymm13\n        vpaddd  ymm9, ymm9, ymm14\n        vpxord  ymm5, ymm5, ymm10\n        vpxord  ymm6, ymm6, ymm11\n        vpxord  ymm7, ymm7, ymm8\n        vpxord  ymm4, ymm4, ymm9\n        vprord  ymm5, ymm5, 12\n        vprord  ymm6, ymm6, 12\n        vprord  ymm7, ymm7, 12\n        vprord  ymm4, ymm4, 12\n        vpaddd  ymm0, ymm0, ymm27\n        vpaddd  ymm1, ymm1, ymm21\n        vpaddd  ymm2, ymm2, ymm30\n        vpaddd  ymm3, ymm3, ymm24\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxord  ymm15, ymm15, ymm0\n        vpxord  ymm12, ymm12, ymm1\n        vpxord  ymm13, ymm13, ymm2\n        vpxord  ymm14, ymm14, ymm3\n        vprord  ymm15, ymm15, 8\n        vprord  ymm12, ymm12, 8\n        vprord  ymm13, ymm13, 8\n        vprord  ymm14, ymm14, 8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm8, ymm13\n        vpaddd  ymm9, ymm9, ymm14\n        vpxord  ymm5, ymm5, ymm10\n        vpxord  ymm6, ymm6, ymm11\n        vpxord  ymm7, ymm7, ymm8\n        vpxord  ymm4, ymm4, ymm9\n        vprord  ymm5, ymm5, 7\n        vprord  ymm6, ymm6, 7\n        vprord  ymm7, ymm7, 7\n        vprord  ymm4, ymm4, 7\n        vpaddd  ymm0, ymm0, ymm19\n        vpaddd  ymm1, ymm1, ymm26\n        vpaddd  ymm2, ymm2, ymm29\n        vpaddd  ymm3, ymm3, ymm23\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxord  ymm12, ymm12, ymm0\n        vpxord  ymm13, ymm13, ymm1\n        vpxord  ymm14, ymm14, ymm2\n        vpxord  ymm15, ymm15, ymm3\n        vprord  ymm12, ymm12, 16\n        vprord  ymm13, ymm13, 16\n        vprord  ymm14, ymm14, 16\n        vprord  ymm15, ymm15, 16\n        vpaddd  ymm8, ymm8, ymm12\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxord  ymm4, ymm4, ymm8\n        vpxord  ymm5, ymm5, ymm9\n        vpxord  ymm6, ymm6, ymm10\n        vpxord  ymm7, ymm7, ymm11\n        vprord  ymm4, ymm4, 12\n        vprord  ymm5, ymm5, 12\n        vprord  ymm6, ymm6, 12\n        vprord  ymm7, ymm7, 12\n        vpaddd  ymm0, ymm0, ymm20\n        vpaddd  ymm1, ymm1, ymm28\n        vpaddd  ymm2, ymm2, ymm18\n        vpaddd  ymm3, ymm3, ymm30\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxord  ymm12, ymm12, ymm0\n        vpxord  ymm13, ymm13, ymm1\n        vpxord  ymm14, ymm14, ymm2\n        vpxord  ymm15, ymm15, ymm3\n        vprord  ymm12, ymm12, 8\n        vprord  ymm13, ymm13, 8\n        vprord  ymm14, ymm14, 8\n        vprord  ymm15, ymm15, 8\n        vpaddd  ymm8, ymm8, ymm12\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxord  ymm4, ymm4, ymm8\n        vpxord  ymm5, ymm5, ymm9\n        vpxord  ymm6, ymm6, ymm10\n        vpxord  ymm7, ymm7, ymm11\n        vprord  ymm4, ymm4, 7\n        vprord  ymm5, ymm5, 7\n        vprord  ymm6, ymm6, 7\n        vprord  ymm7, ymm7, 7\n        vpaddd  ymm0, ymm0, ymm22\n        vpaddd  ymm1, ymm1, ymm25\n        vpaddd  ymm2, ymm2, ymm27\n        vpaddd  ymm3, ymm3, ymm24\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxord  ymm15, ymm15, ymm0\n        vpxord  ymm12, ymm12, ymm1\n        vpxord  ymm13, ymm13, ymm2\n        vpxord  ymm14, ymm14, ymm3\n        vprord  ymm15, ymm15, 16\n        vprord  ymm12, ymm12, 16\n        vprord  ymm13, ymm13, 16\n        vprord  ymm14, ymm14, 16\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm8, ymm13\n        vpaddd  ymm9, ymm9, ymm14\n        vpxord  ymm5, ymm5, ymm10\n        vpxord  ymm6, ymm6, ymm11\n        vpxord  ymm7, ymm7, ymm8\n        vpxord  ymm4, ymm4, ymm9\n        vprord  ymm5, ymm5, 12\n        vprord  ymm6, ymm6, 12\n        vprord  ymm7, ymm7, 12\n        vprord  ymm4, ymm4, 12\n        vpaddd  ymm0, ymm0, ymm21\n        vpaddd  ymm1, ymm1, ymm16\n        vpaddd  ymm2, ymm2, ymm31\n        vpaddd  ymm3, ymm3, ymm17\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxord  ymm15, ymm15, ymm0\n        vpxord  ymm12, ymm12, ymm1\n        vpxord  ymm13, ymm13, ymm2\n        vpxord  ymm14, ymm14, ymm3\n        vprord  ymm15, ymm15, 8\n        vprord  ymm12, ymm12, 8\n        vprord  ymm13, ymm13, 8\n        vprord  ymm14, ymm14, 8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm8, ymm13\n        vpaddd  ymm9, ymm9, ymm14\n        vpxord  ymm5, ymm5, ymm10\n        vpxord  ymm6, ymm6, ymm11\n        vpxord  ymm7, ymm7, ymm8\n        vpxord  ymm4, ymm4, ymm9\n        vprord  ymm5, ymm5, 7\n        vprord  ymm6, ymm6, 7\n        vprord  ymm7, ymm7, 7\n        vprord  ymm4, ymm4, 7\n        vpaddd  ymm0, ymm0, ymm26\n        vpaddd  ymm1, ymm1, ymm28\n        vpaddd  ymm2, ymm2, ymm30\n        vpaddd  ymm3, ymm3, ymm29\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxord  ymm12, ymm12, ymm0\n        vpxord  ymm13, ymm13, ymm1\n        vpxord  ymm14, ymm14, ymm2\n        vpxord  ymm15, ymm15, ymm3\n        vprord  ymm12, ymm12, 16\n        vprord  ymm13, ymm13, 16\n        vprord  ymm14, ymm14, 16\n        vprord  ymm15, ymm15, 16\n        vpaddd  ymm8, ymm8, ymm12\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxord  ymm4, ymm4, ymm8\n        vpxord  ymm5, ymm5, ymm9\n        vpxord  ymm6, ymm6, ymm10\n        vpxord  ymm7, ymm7, ymm11\n        vprord  ymm4, ymm4, 12\n        vprord  ymm5, ymm5, 12\n        vprord  ymm6, ymm6, 12\n        vprord  ymm7, ymm7, 12\n        vpaddd  ymm0, ymm0, ymm23\n        vpaddd  ymm1, ymm1, ymm25\n        vpaddd  ymm2, ymm2, ymm19\n        vpaddd  ymm3, ymm3, ymm31\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxord  ymm12, ymm12, ymm0\n        vpxord  ymm13, ymm13, ymm1\n        vpxord  ymm14, ymm14, ymm2\n        vpxord  ymm15, ymm15, ymm3\n        vprord  ymm12, ymm12, 8\n        vprord  ymm13, ymm13, 8\n        vprord  ymm14, ymm14, 8\n        vprord  ymm15, ymm15, 8\n        vpaddd  ymm8, ymm8, ymm12\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxord  ymm4, ymm4, ymm8\n        vpxord  ymm5, ymm5, ymm9\n        vpxord  ymm6, ymm6, ymm10\n        vpxord  ymm7, ymm7, ymm11\n        vprord  ymm4, ymm4, 7\n        vprord  ymm5, ymm5, 7\n        vprord  ymm6, ymm6, 7\n        vprord  ymm7, ymm7, 7\n        vpaddd  ymm0, ymm0, ymm20\n        vpaddd  ymm1, ymm1, ymm27\n        vpaddd  ymm2, ymm2, ymm21\n        vpaddd  ymm3, ymm3, ymm17\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxord  ymm15, ymm15, ymm0\n        vpxord  ymm12, ymm12, ymm1\n        vpxord  ymm13, ymm13, ymm2\n        vpxord  ymm14, ymm14, ymm3\n        vprord  ymm15, ymm15, 16\n        vprord  ymm12, ymm12, 16\n        vprord  ymm13, ymm13, 16\n        vprord  ymm14, ymm14, 16\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm8, ymm13\n        vpaddd  ymm9, ymm9, ymm14\n        vpxord  ymm5, ymm5, ymm10\n        vpxord  ymm6, ymm6, ymm11\n        vpxord  ymm7, ymm7, ymm8\n        vpxord  ymm4, ymm4, ymm9\n        vprord  ymm5, ymm5, 12\n        vprord  ymm6, ymm6, 12\n        vprord  ymm7, ymm7, 12\n        vprord  ymm4, ymm4, 12\n        vpaddd  ymm0, ymm0, ymm16\n        vpaddd  ymm1, ymm1, ymm18\n        vpaddd  ymm2, ymm2, ymm24\n        vpaddd  ymm3, ymm3, ymm22\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxord  ymm15, ymm15, ymm0\n        vpxord  ymm12, ymm12, ymm1\n        vpxord  ymm13, ymm13, ymm2\n        vpxord  ymm14, ymm14, ymm3\n        vprord  ymm15, ymm15, 8\n        vprord  ymm12, ymm12, 8\n        vprord  ymm13, ymm13, 8\n        vprord  ymm14, ymm14, 8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm8, ymm13\n        vpaddd  ymm9, ymm9, ymm14\n        vpxord  ymm5, ymm5, ymm10\n        vpxord  ymm6, ymm6, ymm11\n        vpxord  ymm7, ymm7, ymm8\n        vpxord  ymm4, ymm4, ymm9\n        vprord  ymm5, ymm5, 7\n        vprord  ymm6, ymm6, 7\n        vprord  ymm7, ymm7, 7\n        vprord  ymm4, ymm4, 7\n        vpaddd  ymm0, ymm0, ymm28\n        vpaddd  ymm1, ymm1, ymm25\n        vpaddd  ymm2, ymm2, ymm31\n        vpaddd  ymm3, ymm3, ymm30\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxord  ymm12, ymm12, ymm0\n        vpxord  ymm13, ymm13, ymm1\n        vpxord  ymm14, ymm14, ymm2\n        vpxord  ymm15, ymm15, ymm3\n        vprord  ymm12, ymm12, 16\n        vprord  ymm13, ymm13, 16\n        vprord  ymm14, ymm14, 16\n        vprord  ymm15, ymm15, 16\n        vpaddd  ymm8, ymm8, ymm12\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxord  ymm4, ymm4, ymm8\n        vpxord  ymm5, ymm5, ymm9\n        vpxord  ymm6, ymm6, ymm10\n        vpxord  ymm7, ymm7, ymm11\n        vprord  ymm4, ymm4, 12\n        vprord  ymm5, ymm5, 12\n        vprord  ymm6, ymm6, 12\n        vprord  ymm7, ymm7, 12\n        vpaddd  ymm0, ymm0, ymm29\n        vpaddd  ymm1, ymm1, ymm27\n        vpaddd  ymm2, ymm2, ymm26\n        vpaddd  ymm3, ymm3, ymm24\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxord  ymm12, ymm12, ymm0\n        vpxord  ymm13, ymm13, ymm1\n        vpxord  ymm14, ymm14, ymm2\n        vpxord  ymm15, ymm15, ymm3\n        vprord  ymm12, ymm12, 8\n        vprord  ymm13, ymm13, 8\n        vprord  ymm14, ymm14, 8\n        vprord  ymm15, ymm15, 8\n        vpaddd  ymm8, ymm8, ymm12\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxord  ymm4, ymm4, ymm8\n        vpxord  ymm5, ymm5, ymm9\n        vpxord  ymm6, ymm6, ymm10\n        vpxord  ymm7, ymm7, ymm11\n        vprord  ymm4, ymm4, 7\n        vprord  ymm5, ymm5, 7\n        vprord  ymm6, ymm6, 7\n        vprord  ymm7, ymm7, 7\n        vpaddd  ymm0, ymm0, ymm23\n        vpaddd  ymm1, ymm1, ymm21\n        vpaddd  ymm2, ymm2, ymm16\n        vpaddd  ymm3, ymm3, ymm22\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxord  ymm15, ymm15, ymm0\n        vpxord  ymm12, ymm12, ymm1\n        vpxord  ymm13, ymm13, ymm2\n        vpxord  ymm14, ymm14, ymm3\n        vprord  ymm15, ymm15, 16\n        vprord  ymm12, ymm12, 16\n        vprord  ymm13, ymm13, 16\n        vprord  ymm14, ymm14, 16\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm8, ymm13\n        vpaddd  ymm9, ymm9, ymm14\n        vpxord  ymm5, ymm5, ymm10\n        vpxord  ymm6, ymm6, ymm11\n        vpxord  ymm7, ymm7, ymm8\n        vpxord  ymm4, ymm4, ymm9\n        vprord  ymm5, ymm5, 12\n        vprord  ymm6, ymm6, 12\n        vprord  ymm7, ymm7, 12\n        vprord  ymm4, ymm4, 12\n        vpaddd  ymm0, ymm0, ymm18\n        vpaddd  ymm1, ymm1, ymm19\n        vpaddd  ymm2, ymm2, ymm17\n        vpaddd  ymm3, ymm3, ymm20\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxord  ymm15, ymm15, ymm0\n        vpxord  ymm12, ymm12, ymm1\n        vpxord  ymm13, ymm13, ymm2\n        vpxord  ymm14, ymm14, ymm3\n        vprord  ymm15, ymm15, 8\n        vprord  ymm12, ymm12, 8\n        vprord  ymm13, ymm13, 8\n        vprord  ymm14, ymm14, 8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm8, ymm13\n        vpaddd  ymm9, ymm9, ymm14\n        vpxord  ymm5, ymm5, ymm10\n        vpxord  ymm6, ymm6, ymm11\n        vpxord  ymm7, ymm7, ymm8\n        vpxord  ymm4, ymm4, ymm9\n        vprord  ymm5, ymm5, 7\n        vprord  ymm6, ymm6, 7\n        vprord  ymm7, ymm7, 7\n        vprord  ymm4, ymm4, 7\n        vpaddd  ymm0, ymm0, ymm25\n        vpaddd  ymm1, ymm1, ymm27\n        vpaddd  ymm2, ymm2, ymm24\n        vpaddd  ymm3, ymm3, ymm31\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxord  ymm12, ymm12, ymm0\n        vpxord  ymm13, ymm13, ymm1\n        vpxord  ymm14, ymm14, ymm2\n        vpxord  ymm15, ymm15, ymm3\n        vprord  ymm12, ymm12, 16\n        vprord  ymm13, ymm13, 16\n        vprord  ymm14, ymm14, 16\n        vprord  ymm15, ymm15, 16\n        vpaddd  ymm8, ymm8, ymm12\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxord  ymm4, ymm4, ymm8\n        vpxord  ymm5, ymm5, ymm9\n        vpxord  ymm6, ymm6, ymm10\n        vpxord  ymm7, ymm7, ymm11\n        vprord  ymm4, ymm4, 12\n        vprord  ymm5, ymm5, 12\n        vprord  ymm6, ymm6, 12\n        vprord  ymm7, ymm7, 12\n        vpaddd  ymm0, ymm0, ymm30\n        vpaddd  ymm1, ymm1, ymm21\n        vpaddd  ymm2, ymm2, ymm28\n        vpaddd  ymm3, ymm3, ymm17\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxord  ymm12, ymm12, ymm0\n        vpxord  ymm13, ymm13, ymm1\n        vpxord  ymm14, ymm14, ymm2\n        vpxord  ymm15, ymm15, ymm3\n        vprord  ymm12, ymm12, 8\n        vprord  ymm13, ymm13, 8\n        vprord  ymm14, ymm14, 8\n        vprord  ymm15, ymm15, 8\n        vpaddd  ymm8, ymm8, ymm12\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxord  ymm4, ymm4, ymm8\n        vpxord  ymm5, ymm5, ymm9\n        vpxord  ymm6, ymm6, ymm10\n        vpxord  ymm7, ymm7, ymm11\n        vprord  ymm4, ymm4, 7\n        vprord  ymm5, ymm5, 7\n        vprord  ymm6, ymm6, 7\n        vprord  ymm7, ymm7, 7\n        vpaddd  ymm0, ymm0, ymm29\n        vpaddd  ymm1, ymm1, ymm16\n        vpaddd  ymm2, ymm2, ymm18\n        vpaddd  ymm3, ymm3, ymm20\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxord  ymm15, ymm15, ymm0\n        vpxord  ymm12, ymm12, ymm1\n        vpxord  ymm13, ymm13, ymm2\n        vpxord  ymm14, ymm14, ymm3\n        vprord  ymm15, ymm15, 16\n        vprord  ymm12, ymm12, 16\n        vprord  ymm13, ymm13, 16\n        vprord  ymm14, ymm14, 16\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm8, ymm13\n        vpaddd  ymm9, ymm9, ymm14\n        vpxord  ymm5, ymm5, ymm10\n        vpxord  ymm6, ymm6, ymm11\n        vpxord  ymm7, ymm7, ymm8\n        vpxord  ymm4, ymm4, ymm9\n        vprord  ymm5, ymm5, 12\n        vprord  ymm6, ymm6, 12\n        vprord  ymm7, ymm7, 12\n        vprord  ymm4, ymm4, 12\n        vpaddd  ymm0, ymm0, ymm19\n        vpaddd  ymm1, ymm1, ymm26\n        vpaddd  ymm2, ymm2, ymm22\n        vpaddd  ymm3, ymm3, ymm23\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxord  ymm15, ymm15, ymm0\n        vpxord  ymm12, ymm12, ymm1\n        vpxord  ymm13, ymm13, ymm2\n        vpxord  ymm14, ymm14, ymm3\n        vprord  ymm15, ymm15, 8\n        vprord  ymm12, ymm12, 8\n        vprord  ymm13, ymm13, 8\n        vprord  ymm14, ymm14, 8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm8, ymm13\n        vpaddd  ymm9, ymm9, ymm14\n        vpxord  ymm5, ymm5, ymm10\n        vpxord  ymm6, ymm6, ymm11\n        vpxord  ymm7, ymm7, ymm8\n        vpxord  ymm4, ymm4, ymm9\n        vprord  ymm5, ymm5, 7\n        vprord  ymm6, ymm6, 7\n        vprord  ymm7, ymm7, 7\n        vprord  ymm4, ymm4, 7\n        vpaddd  ymm0, ymm0, ymm27\n        vpaddd  ymm1, ymm1, ymm21\n        vpaddd  ymm2, ymm2, ymm17\n        vpaddd  ymm3, ymm3, ymm24\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxord  ymm12, ymm12, ymm0\n        vpxord  ymm13, ymm13, ymm1\n        vpxord  ymm14, ymm14, ymm2\n        vpxord  ymm15, ymm15, ymm3\n        vprord  ymm12, ymm12, 16\n        vprord  ymm13, ymm13, 16\n        vprord  ymm14, ymm14, 16\n        vprord  ymm15, ymm15, 16\n        vpaddd  ymm8, ymm8, ymm12\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxord  ymm4, ymm4, ymm8\n        vpxord  ymm5, ymm5, ymm9\n        vpxord  ymm6, ymm6, ymm10\n        vpxord  ymm7, ymm7, ymm11\n        vprord  ymm4, ymm4, 12\n        vprord  ymm5, ymm5, 12\n        vprord  ymm6, ymm6, 12\n        vprord  ymm7, ymm7, 12\n        vpaddd  ymm0, ymm0, ymm31\n        vpaddd  ymm1, ymm1, ymm16\n        vpaddd  ymm2, ymm2, ymm25\n        vpaddd  ymm3, ymm3, ymm22\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm1, ymm1, ymm5\n        vpaddd  ymm2, ymm2, ymm6\n        vpaddd  ymm3, ymm3, ymm7\n        vpxord  ymm12, ymm12, ymm0\n        vpxord  ymm13, ymm13, ymm1\n        vpxord  ymm14, ymm14, ymm2\n        vpxord  ymm15, ymm15, ymm3\n        vprord  ymm12, ymm12, 8\n        vprord  ymm13, ymm13, 8\n        vprord  ymm14, ymm14, 8\n        vprord  ymm15, ymm15, 8\n        vpaddd  ymm8, ymm8, ymm12\n        vpaddd  ymm9, ymm9, ymm13\n        vpaddd  ymm10, ymm10, ymm14\n        vpaddd  ymm11, ymm11, ymm15\n        vpxord  ymm4, ymm4, ymm8\n        vpxord  ymm5, ymm5, ymm9\n        vpxord  ymm6, ymm6, ymm10\n        vpxord  ymm7, ymm7, ymm11\n        vprord  ymm4, ymm4, 7\n        vprord  ymm5, ymm5, 7\n        vprord  ymm6, ymm6, 7\n        vprord  ymm7, ymm7, 7\n        vpaddd  ymm0, ymm0, ymm30\n        vpaddd  ymm1, ymm1, ymm18\n        vpaddd  ymm2, ymm2, ymm19\n        vpaddd  ymm3, ymm3, ymm23\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxord  ymm15, ymm15, ymm0\n        vpxord  ymm12, ymm12, ymm1\n        vpxord  ymm13, ymm13, ymm2\n        vpxord  ymm14, ymm14, ymm3\n        vprord  ymm15, ymm15, 16\n        vprord  ymm12, ymm12, 16\n        vprord  ymm13, ymm13, 16\n        vprord  ymm14, ymm14, 16\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm8, ymm13\n        vpaddd  ymm9, ymm9, ymm14\n        vpxord  ymm5, ymm5, ymm10\n        vpxord  ymm6, ymm6, ymm11\n        vpxord  ymm7, ymm7, ymm8\n        vpxord  ymm4, ymm4, ymm9\n        vprord  ymm5, ymm5, 12\n        vprord  ymm6, ymm6, 12\n        vprord  ymm7, ymm7, 12\n        vprord  ymm4, ymm4, 12\n        vpaddd  ymm0, ymm0, ymm26\n        vpaddd  ymm1, ymm1, ymm28\n        vpaddd  ymm2, ymm2, ymm20\n        vpaddd  ymm3, ymm3, ymm29\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm1, ymm1, ymm6\n        vpaddd  ymm2, ymm2, ymm7\n        vpaddd  ymm3, ymm3, ymm4\n        vpxord  ymm15, ymm15, ymm0\n        vpxord  ymm12, ymm12, ymm1\n        vpxord  ymm13, ymm13, ymm2\n        vpxord  ymm14, ymm14, ymm3\n        vprord  ymm15, ymm15, 8\n        vprord  ymm12, ymm12, 8\n        vprord  ymm13, ymm13, 8\n        vprord  ymm14, ymm14, 8\n        vpaddd  ymm10, ymm10, ymm15\n        vpaddd  ymm11, ymm11, ymm12\n        vpaddd  ymm8, ymm8, ymm13\n        vpaddd  ymm9, ymm9, ymm14\n        vpxord  ymm5, ymm5, ymm10\n        vpxord  ymm6, ymm6, ymm11\n        vpxord  ymm7, ymm7, ymm8\n        vpxord  ymm4, ymm4, ymm9\n        vprord  ymm5, ymm5, 7\n        vprord  ymm6, ymm6, 7\n        vprord  ymm7, ymm7, 7\n        vprord  ymm4, ymm4, 7\n        vpxor   ymm0, ymm0, ymm8\n        vpxor   ymm1, ymm1, ymm9\n        vpxor   ymm2, ymm2, ymm10\n        vpxor   ymm3, ymm3, ymm11\n        vpxor   ymm4, ymm4, ymm12\n        vpxor   ymm5, ymm5, ymm13\n        vpxor   ymm6, ymm6, ymm14\n        vpxor   ymm7, ymm7, ymm15\n        movzx   eax, byte ptr [rbp+0x38]\n        jne     2b\n        mov     rbx, qword ptr [rbp+0x50]\n        vunpcklps ymm8, ymm0, ymm1\n        vunpcklps ymm9, ymm2, ymm3\n        vunpckhps ymm10, ymm0, ymm1\n        vunpcklps ymm11, ymm4, ymm5\n        vunpcklps ymm0, ymm6, ymm7\n        vshufps ymm12, ymm8, ymm9, 78\n        vblendps ymm1, ymm8, ymm12, 0xCC\n        vshufps ymm8, ymm11, ymm0, 78\n        vunpckhps ymm13, ymm2, ymm3\n        vblendps ymm2, ymm11, ymm8, 0xCC\n        vblendps ymm3, ymm12, ymm9, 0xCC\n        vperm2f128 ymm12, ymm1, ymm2, 0x20\n        vmovups ymmword ptr [rbx], ymm12\n        vunpckhps ymm14, ymm4, ymm5\n        vblendps ymm4, ymm8, ymm0, 0xCC\n        vunpckhps ymm15, ymm6, ymm7\n        vperm2f128 ymm7, ymm3, ymm4, 0x20\n        vmovups ymmword ptr [rbx+0x20], ymm7\n        vshufps ymm5, ymm10, ymm13, 78\n        vblendps ymm6, ymm5, ymm13, 0xCC\n        vshufps ymm13, ymm14, ymm15, 78\n        vblendps ymm10, ymm10, ymm5, 0xCC\n        vblendps ymm14, ymm14, ymm13, 0xCC\n        vperm2f128 ymm8, ymm10, ymm14, 0x20\n        vmovups ymmword ptr [rbx+0x40], ymm8\n        vblendps ymm15, ymm13, ymm15, 0xCC\n        vperm2f128 ymm13, ymm6, ymm15, 0x20\n        vmovups ymmword ptr [rbx+0x60], ymm13\n        vperm2f128 ymm9, ymm1, ymm2, 0x31\n        vperm2f128 ymm11, ymm3, ymm4, 0x31\n        vmovups ymmword ptr [rbx+0x80], ymm9\n        vperm2f128 ymm14, ymm10, ymm14, 0x31\n        vperm2f128 ymm15, ymm6, ymm15, 0x31\n        vmovups ymmword ptr [rbx+0xA0], ymm11\n        vmovups ymmword ptr [rbx+0xC0], ymm14\n        vmovups ymmword ptr [rbx+0xE0], ymm15\n        vmovdqa ymm0, ymmword ptr [rsp]\n        vmovdqa ymm2, ymmword ptr [rsp+0x2*0x20]\n        vmovdqa32 ymm0 {k1}, ymmword ptr [rsp+0x1*0x20]\n        vmovdqa32 ymm2 {k1}, ymmword ptr [rsp+0x3*0x20]\n        vmovdqa ymmword ptr [rsp], ymm0\n        vmovdqa ymmword ptr [rsp+0x2*0x20], ymm2\n        add     rbx, 256\n        mov     qword ptr [rbp+0x50], rbx\n        add     rdi, 64\n        sub     rsi, 8\n3:\n        mov     rbx, qword ptr [rbp+0x50]\n        mov     r15, qword ptr [rsp+0x80]\n        movzx   r13, byte ptr [rbp+0x38]\n        movzx   r12, byte ptr [rbp+0x48]\n        test    esi, 0x4\n        je      3f\n        vbroadcasti32x4 zmm0, xmmword ptr [rcx]\n        vbroadcasti32x4 zmm1, xmmword ptr [rcx+0x1*0x10]\n        vmovdqa xmm12, xmmword ptr [rsp]\n        vmovdqa xmm13, xmmword ptr [rsp+0x4*0x10]\n        vpunpckldq xmm14, xmm12, xmm13\n        vpunpckhdq xmm15, xmm12, xmm13\n        vpermq  ymm14, ymm14, 0xDC\n        vpermq  ymm15, ymm15, 0xDC\n        vpbroadcastd zmm12, dword ptr [BLAKE3_BLOCK_LEN+rip]\n        vinserti64x4 zmm13, zmm14, ymm15, 0x01\n        mov     eax, 17476\n        kmovw   k2, eax\n        vpblendmd zmm13 {k2}, zmm13, zmm12\n        vbroadcasti32x4 zmm15, xmmword ptr [BLAKE3_IV+rip]\n        mov     r8, qword ptr [rdi]\n        mov     r9, qword ptr [rdi+0x8]\n        mov     r10, qword ptr [rdi+0x10]\n        mov     r11, qword ptr [rdi+0x18]\n        mov     eax, 43690\n        kmovw   k3, eax\n        mov     eax, 34952\n        kmovw   k4, eax\n        movzx   eax, byte ptr [rbp+0x40]\n        or      eax, r13d\n        xor     edx, edx\n.p2align 5\n2:\n        mov     r14d, eax\n        or      eax, r12d\n        add     rdx, 64\n        cmp     rdx, r15\n        cmovne  eax, r14d\n        mov     dword ptr [rsp+0x88], eax\n        vmovdqa32 zmm2, zmm15\n        vpbroadcastd zmm8, dword ptr [rsp+0x22*0x4]\n        vpblendmd zmm3 {k4}, zmm13, zmm8\n        vmovups zmm8, zmmword ptr [r8+rdx-0x1*0x40]\n        vinserti32x4 zmm8, zmm8, xmmword ptr [r9+rdx-0x4*0x10], 0x01\n        vinserti32x4 zmm8, zmm8, xmmword ptr [r10+rdx-0x4*0x10], 0x02\n        vinserti32x4 zmm8, zmm8, xmmword ptr [r11+rdx-0x4*0x10], 0x03\n        vmovups zmm9, zmmword ptr [r8+rdx-0x30]\n        vinserti32x4 zmm9, zmm9, xmmword ptr [r9+rdx-0x3*0x10], 0x01\n        vinserti32x4 zmm9, zmm9, xmmword ptr [r10+rdx-0x3*0x10], 0x02\n        vinserti32x4 zmm9, zmm9, xmmword ptr [r11+rdx-0x3*0x10], 0x03\n        vshufps zmm4, zmm8, zmm9, 136\n        vshufps zmm5, zmm8, zmm9, 221\n        vmovups zmm8, zmmword ptr [r8+rdx-0x20]\n        vinserti32x4 zmm8, zmm8, xmmword ptr [r9+rdx-0x2*0x10], 0x01\n        vinserti32x4 zmm8, zmm8, xmmword ptr [r10+rdx-0x2*0x10], 0x02\n        vinserti32x4 zmm8, zmm8, xmmword ptr [r11+rdx-0x2*0x10], 0x03\n        vmovups zmm9, zmmword ptr [r8+rdx-0x10]\n        vinserti32x4 zmm9, zmm9, xmmword ptr [r9+rdx-0x1*0x10], 0x01\n        vinserti32x4 zmm9, zmm9, xmmword ptr [r10+rdx-0x1*0x10], 0x02\n        vinserti32x4 zmm9, zmm9, xmmword ptr [r11+rdx-0x1*0x10], 0x03\n        vshufps zmm6, zmm8, zmm9, 136\n        vshufps zmm7, zmm8, zmm9, 221\n        vpshufd zmm6, zmm6, 0x93\n        vpshufd zmm7, zmm7, 0x93\n        mov     al, 7\n9:\n        vpaddd  zmm0, zmm0, zmm4\n        vpaddd  zmm0, zmm0, zmm1\n        vpxord  zmm3, zmm3, zmm0\n        vprord  zmm3, zmm3, 16\n        vpaddd  zmm2, zmm2, zmm3\n        vpxord  zmm1, zmm1, zmm2\n        vprord  zmm1, zmm1, 12\n        vpaddd  zmm0, zmm0, zmm5\n        vpaddd  zmm0, zmm0, zmm1\n        vpxord  zmm3, zmm3, zmm0\n        vprord  zmm3, zmm3, 8\n        vpaddd  zmm2, zmm2, zmm3\n        vpxord  zmm1, zmm1, zmm2\n        vprord  zmm1, zmm1, 7\n        vpshufd zmm0, zmm0, 0x93\n        vpshufd zmm3, zmm3, 0x4E\n        vpshufd zmm2, zmm2, 0x39\n        vpaddd  zmm0, zmm0, zmm6\n        vpaddd  zmm0, zmm0, zmm1\n        vpxord  zmm3, zmm3, zmm0\n        vprord  zmm3, zmm3, 16\n        vpaddd  zmm2, zmm2, zmm3\n        vpxord  zmm1, zmm1, zmm2\n        vprord  zmm1, zmm1, 12\n        vpaddd  zmm0, zmm0, zmm7\n        vpaddd  zmm0, zmm0, zmm1\n        vpxord  zmm3, zmm3, zmm0\n        vprord  zmm3, zmm3, 8\n        vpaddd  zmm2, zmm2, zmm3\n        vpxord  zmm1, zmm1, zmm2\n        vprord  zmm1, zmm1, 7\n        vpshufd zmm0, zmm0, 0x39\n        vpshufd zmm3, zmm3, 0x4E\n        vpshufd zmm2, zmm2, 0x93\n        dec     al\n        jz      9f\n        vshufps zmm8, zmm4, zmm5, 214\n        vpshufd zmm9, zmm4, 0x0F\n        vpshufd zmm4, zmm8, 0x39\n        vshufps zmm8, zmm6, zmm7, 250\n        vpblendmd zmm9 {k3}, zmm9, zmm8\n        vpunpcklqdq zmm8, zmm7, zmm5\n        vpblendmd zmm8 {k4}, zmm8, zmm6\n        vpshufd zmm8, zmm8, 0x78\n        vpunpckhdq zmm5, zmm5, zmm7\n        vpunpckldq zmm6, zmm6, zmm5\n        vpshufd zmm7, zmm6, 0x1E\n        vmovdqa32 zmm5, zmm9\n        vmovdqa32 zmm6, zmm8\n        jmp     9b\n9:\n        vpxord  zmm0, zmm0, zmm2\n        vpxord  zmm1, zmm1, zmm3\n        mov     eax, r13d\n        cmp     rdx, r15\n        jne     2b\n        vmovdqu xmmword ptr [rbx], xmm0\n        vmovdqu xmmword ptr [rbx+0x10], xmm1\n        vextracti128 xmmword ptr [rbx+0x20], ymm0, 0x01\n        vextracti128 xmmword ptr [rbx+0x30], ymm1, 0x01\n        vextracti32x4 xmmword ptr [rbx+0x4*0x10], zmm0, 0x02\n        vextracti32x4 xmmword ptr [rbx+0x5*0x10], zmm1, 0x02\n        vextracti32x4 xmmword ptr [rbx+0x6*0x10], zmm0, 0x03\n        vextracti32x4 xmmword ptr [rbx+0x7*0x10], zmm1, 0x03\n        vmovdqa xmm0, xmmword ptr [rsp]\n        vmovdqa xmm2, xmmword ptr [rsp+0x40]\n        vmovdqa32 xmm0 {k1}, xmmword ptr [rsp+0x1*0x10]\n        vmovdqa32 xmm2 {k1}, xmmword ptr [rsp+0x5*0x10]\n        vmovdqa xmmword ptr [rsp], xmm0\n        vmovdqa xmmword ptr [rsp+0x40], xmm2\n        add     rbx, 128\n        add     rdi, 32\n        sub     rsi, 4\n3:\n        test    esi, 0x2\n        je      3f\n        vbroadcasti128 ymm0, xmmword ptr [rcx]\n        vbroadcasti128 ymm1, xmmword ptr [rcx+0x10]\n        vmovd   xmm13, dword ptr [rsp]\n        vpinsrd xmm13, xmm13, dword ptr [rsp+0x40], 1\n        vpinsrd xmm13, xmm13, dword ptr [BLAKE3_BLOCK_LEN+rip], 2\n        vmovd   xmm14, dword ptr [rsp+0x4]\n        vpinsrd xmm14, xmm14, dword ptr [rsp+0x44], 1\n        vpinsrd xmm14, xmm14, dword ptr [BLAKE3_BLOCK_LEN+rip], 2\n        vinserti128 ymm13, ymm13, xmm14, 0x01\n        mov     r8, qword ptr [rdi]\n        mov     r9, qword ptr [rdi+0x8]\n        movzx   eax, byte ptr [rbp+0x40]\n        or      eax, r13d\n        xor     edx, edx\n.p2align 5\n2:\n        mov     r14d, eax\n        or      eax, r12d\n        add     rdx, 64\n        cmp     rdx, r15\n        cmovne  eax, r14d\n        mov     dword ptr [rsp+0x88], eax\n        vbroadcasti128 ymm2, xmmword ptr [BLAKE3_IV+rip]\n        vpbroadcastd ymm8, dword ptr [rsp+0x88]\n        vpblendd ymm3, ymm13, ymm8, 0x88\n        vmovups ymm8, ymmword ptr [r8+rdx-0x40]\n        vinsertf128 ymm8, ymm8, xmmword ptr [r9+rdx-0x40], 0x01\n        vmovups ymm9, ymmword ptr [r8+rdx-0x30]\n        vinsertf128 ymm9, ymm9, xmmword ptr [r9+rdx-0x30], 0x01\n        vshufps ymm4, ymm8, ymm9, 136\n        vshufps ymm5, ymm8, ymm9, 221\n        vmovups ymm8, ymmword ptr [r8+rdx-0x20]\n        vinsertf128 ymm8, ymm8, xmmword ptr [r9+rdx-0x20], 0x01\n        vmovups ymm9, ymmword ptr [r8+rdx-0x10]\n        vinsertf128 ymm9, ymm9, xmmword ptr [r9+rdx-0x10], 0x01\n        vshufps ymm6, ymm8, ymm9, 136\n        vshufps ymm7, ymm8, ymm9, 221\n        vpshufd ymm6, ymm6, 0x93\n        vpshufd ymm7, ymm7, 0x93\n        mov     al, 7\n9:\n        vpaddd  ymm0, ymm0, ymm4\n        vpaddd  ymm0, ymm0, ymm1\n        vpxord  ymm3, ymm3, ymm0\n        vprord  ymm3, ymm3, 16\n        vpaddd  ymm2, ymm2, ymm3\n        vpxord  ymm1, ymm1, ymm2\n        vprord  ymm1, ymm1, 12\n        vpaddd  ymm0, ymm0, ymm5\n        vpaddd  ymm0, ymm0, ymm1\n        vpxord  ymm3, ymm3, ymm0\n        vprord  ymm3, ymm3, 8\n        vpaddd  ymm2, ymm2, ymm3\n        vpxord  ymm1, ymm1, ymm2\n        vprord  ymm1, ymm1, 7\n        vpshufd ymm0, ymm0, 0x93\n        vpshufd ymm3, ymm3, 0x4E\n        vpshufd ymm2, ymm2, 0x39\n        vpaddd  ymm0, ymm0, ymm6\n        vpaddd  ymm0, ymm0, ymm1\n        vpxord  ymm3, ymm3, ymm0\n        vprord  ymm3, ymm3, 16\n        vpaddd  ymm2, ymm2, ymm3\n        vpxord  ymm1, ymm1, ymm2\n        vprord  ymm1, ymm1, 12\n        vpaddd  ymm0, ymm0, ymm7\n        vpaddd  ymm0, ymm0, ymm1\n        vpxord  ymm3, ymm3, ymm0\n        vprord  ymm3, ymm3, 8\n        vpaddd  ymm2, ymm2, ymm3\n        vpxord  ymm1, ymm1, ymm2\n        vprord  ymm1, ymm1, 7\n        vpshufd ymm0, ymm0, 0x39\n        vpshufd ymm3, ymm3, 0x4E\n        vpshufd ymm2, ymm2, 0x93\n        dec     al\n        jz      9f\n        vshufps ymm8, ymm4, ymm5, 214\n        vpshufd ymm9, ymm4, 0x0F\n        vpshufd ymm4, ymm8, 0x39\n        vshufps ymm8, ymm6, ymm7, 250\n        vpblendd ymm9, ymm9, ymm8, 0xAA\n        vpunpcklqdq ymm8, ymm7, ymm5\n        vpblendd ymm8, ymm8, ymm6, 0x88\n        vpshufd ymm8, ymm8, 0x78\n        vpunpckhdq ymm5, ymm5, ymm7\n        vpunpckldq ymm6, ymm6, ymm5\n        vpshufd ymm7, ymm6, 0x1E\n        vmovdqa ymm5, ymm9\n        vmovdqa ymm6, ymm8\n        jmp     9b\n9:\n        vpxor   ymm0, ymm0, ymm2\n        vpxor   ymm1, ymm1, ymm3\n        mov     eax, r13d\n        cmp     rdx, r15\n        jne     2b\n        vmovdqu xmmword ptr [rbx], xmm0\n        vmovdqu xmmword ptr [rbx+0x10], xmm1\n        vextracti128 xmmword ptr [rbx+0x20], ymm0, 0x01\n        vextracti128 xmmword ptr [rbx+0x30], ymm1, 0x01\n        vmovdqa xmm0, xmmword ptr [rsp]\n        vmovdqa xmm2, xmmword ptr [rsp+0x4*0x10]\n        vmovdqu32 xmm0 {k1}, xmmword ptr [rsp+0x8]\n        vmovdqu32 xmm2 {k1}, xmmword ptr [rsp+0x48]\n        vmovdqa xmmword ptr [rsp], xmm0\n        vmovdqa xmmword ptr [rsp+0x4*0x10], xmm2\n        add     rbx, 64\n        add     rdi, 16\n        sub     rsi, 2\n3:\n        test    esi, 0x1\n        je      4b\n        vmovdqu xmm0, xmmword ptr [rcx]\n        vmovdqu xmm1, xmmword ptr [rcx+0x10]\n        vmovd   xmm14, dword ptr [rsp]\n        vpinsrd xmm14, xmm14, dword ptr [rsp+0x40], 1\n        vpinsrd xmm14, xmm14, dword ptr [BLAKE3_BLOCK_LEN+rip], 2\n        vmovdqa xmm15, xmmword ptr [BLAKE3_IV+rip]\n        mov     r8, qword ptr [rdi]\n        movzx   eax, byte ptr [rbp+0x40]\n        or      eax, r13d\n        xor     edx, edx\n.p2align 5\n2:\n        mov     r14d, eax\n        or      eax, r12d\n        add     rdx, 64\n        cmp     rdx, r15\n        cmovne  eax, r14d\n        vpinsrd xmm3, xmm14, eax, 3\n        vmovdqa xmm2, xmm15\n        vmovups xmm8, xmmword ptr [r8+rdx-0x40]\n        vmovups xmm9, xmmword ptr [r8+rdx-0x30]\n        vshufps xmm4, xmm8, xmm9, 136\n        vshufps xmm5, xmm8, xmm9, 221\n        vmovups xmm8, xmmword ptr [r8+rdx-0x20]\n        vmovups xmm9, xmmword ptr [r8+rdx-0x10]\n        vshufps xmm6, xmm8, xmm9, 136\n        vshufps xmm7, xmm8, xmm9, 221\n        vpshufd xmm6, xmm6, 0x93\n        vpshufd xmm7, xmm7, 0x93\n        mov     al, 7\n9:\n        vpaddd  xmm0, xmm0, xmm4\n        vpaddd  xmm0, xmm0, xmm1\n        vpxord  xmm3, xmm3, xmm0\n        vprord  xmm3, xmm3, 16\n        vpaddd  xmm2, xmm2, xmm3\n        vpxord  xmm1, xmm1, xmm2\n        vprord  xmm1, xmm1, 12\n        vpaddd  xmm0, xmm0, xmm5\n        vpaddd  xmm0, xmm0, xmm1\n        vpxord  xmm3, xmm3, xmm0\n        vprord  xmm3, xmm3, 8\n        vpaddd  xmm2, xmm2, xmm3\n        vpxord  xmm1, xmm1, xmm2\n        vprord  xmm1, xmm1, 7\n        vpshufd xmm0, xmm0, 0x93\n        vpshufd xmm3, xmm3, 0x4E\n        vpshufd xmm2, xmm2, 0x39\n        vpaddd  xmm0, xmm0, xmm6\n        vpaddd  xmm0, xmm0, xmm1\n        vpxord  xmm3, xmm3, xmm0\n        vprord  xmm3, xmm3, 16\n        vpaddd  xmm2, xmm2, xmm3\n        vpxord  xmm1, xmm1, xmm2\n        vprord  xmm1, xmm1, 12\n        vpaddd  xmm0, xmm0, xmm7\n        vpaddd  xmm0, xmm0, xmm1\n        vpxord  xmm3, xmm3, xmm0\n        vprord  xmm3, xmm3, 8\n        vpaddd  xmm2, xmm2, xmm3\n        vpxord  xmm1, xmm1, xmm2\n        vprord  xmm1, xmm1, 7\n        vpshufd xmm0, xmm0, 0x39\n        vpshufd xmm3, xmm3, 0x4E\n        vpshufd xmm2, xmm2, 0x93\n        dec     al\n        jz      9f\n        vshufps xmm8, xmm4, xmm5, 214\n        vpshufd xmm9, xmm4, 0x0F\n        vpshufd xmm4, xmm8, 0x39\n        vshufps xmm8, xmm6, xmm7, 250\n        vpblendd xmm9, xmm9, xmm8, 0xAA\n        vpunpcklqdq xmm8, xmm7, xmm5\n        vpblendd xmm8, xmm8, xmm6, 0x88\n        vpshufd xmm8, xmm8, 0x78\n        vpunpckhdq xmm5, xmm5, xmm7\n        vpunpckldq xmm6, xmm6, xmm5\n        vpshufd xmm7, xmm6, 0x1E\n        vmovdqa xmm5, xmm9\n        vmovdqa xmm6, xmm8\n        jmp     9b\n9:\n        vpxor   xmm0, xmm0, xmm2\n        vpxor   xmm1, xmm1, xmm3\n        mov     eax, r13d\n        cmp     rdx, r15\n        jne     2b\n        vmovdqu xmmword ptr [rbx], xmm0\n        vmovdqu xmmword ptr [rbx+0x10], xmm1\n        jmp     4b\n.p2align 6\n_blake3_compress_in_place_avx512:\nblake3_compress_in_place_avx512:\n        _CET_ENDBR\n        vmovdqu xmm0, xmmword ptr [rdi]\n        vmovdqu xmm1, xmmword ptr [rdi+0x10]\n        movzx   eax, r8b\n        movzx   edx, dl\n        shl     rax, 32\n        add     rdx, rax\n        vmovq   xmm3, rcx\n        vmovq   xmm4, rdx\n        vpunpcklqdq xmm3, xmm3, xmm4\n        vmovaps xmm2, xmmword ptr [BLAKE3_IV+rip]\n        vmovups xmm8, xmmword ptr [rsi]\n        vmovups xmm9, xmmword ptr [rsi+0x10]\n        vshufps xmm4, xmm8, xmm9, 136\n        vshufps xmm5, xmm8, xmm9, 221\n        vmovups xmm8, xmmword ptr [rsi+0x20]\n        vmovups xmm9, xmmword ptr [rsi+0x30]\n        vshufps xmm6, xmm8, xmm9, 136\n        vshufps xmm7, xmm8, xmm9, 221\n        vpshufd xmm6, xmm6, 0x93\n        vpshufd xmm7, xmm7, 0x93\n        mov     al, 7\n9:\n        vpaddd  xmm0, xmm0, xmm4\n        vpaddd  xmm0, xmm0, xmm1\n        vpxord  xmm3, xmm3, xmm0\n        vprord  xmm3, xmm3, 16\n        vpaddd  xmm2, xmm2, xmm3\n        vpxord  xmm1, xmm1, xmm2\n        vprord  xmm1, xmm1, 12\n        vpaddd  xmm0, xmm0, xmm5\n        vpaddd  xmm0, xmm0, xmm1\n        vpxord  xmm3, xmm3, xmm0\n        vprord  xmm3, xmm3, 8\n        vpaddd  xmm2, xmm2, xmm3\n        vpxord  xmm1, xmm1, xmm2\n        vprord  xmm1, xmm1, 7\n        vpshufd xmm0, xmm0, 0x93\n        vpshufd xmm3, xmm3, 0x4E\n        vpshufd xmm2, xmm2, 0x39\n        vpaddd  xmm0, xmm0, xmm6\n        vpaddd  xmm0, xmm0, xmm1\n        vpxord  xmm3, xmm3, xmm0\n        vprord  xmm3, xmm3, 16\n        vpaddd  xmm2, xmm2, xmm3\n        vpxord  xmm1, xmm1, xmm2\n        vprord  xmm1, xmm1, 12\n        vpaddd  xmm0, xmm0, xmm7\n        vpaddd  xmm0, xmm0, xmm1\n        vpxord  xmm3, xmm3, xmm0\n        vprord  xmm3, xmm3, 8\n        vpaddd  xmm2, xmm2, xmm3\n        vpxord  xmm1, xmm1, xmm2\n        vprord  xmm1, xmm1, 7\n        vpshufd xmm0, xmm0, 0x39\n        vpshufd xmm3, xmm3, 0x4E\n        vpshufd xmm2, xmm2, 0x93\n        dec     al\n        jz      9f\n        vshufps xmm8, xmm4, xmm5, 214\n        vpshufd xmm9, xmm4, 0x0F\n        vpshufd xmm4, xmm8, 0x39\n        vshufps xmm8, xmm6, xmm7, 250\n        vpblendd xmm9, xmm9, xmm8, 0xAA\n        vpunpcklqdq xmm8, xmm7, xmm5\n        vpblendd xmm8, xmm8, xmm6, 0x88\n        vpshufd xmm8, xmm8, 0x78\n        vpunpckhdq xmm5, xmm5, xmm7\n        vpunpckldq xmm6, xmm6, xmm5\n        vpshufd xmm7, xmm6, 0x1E\n        vmovdqa xmm5, xmm9\n        vmovdqa xmm6, xmm8\n        jmp     9b\n9:\n        vpxor   xmm0, xmm0, xmm2\n        vpxor   xmm1, xmm1, xmm3\n        vmovdqu xmmword ptr [rdi], xmm0\n        vmovdqu xmmword ptr [rdi+0x10], xmm1\n        ret\n\n.p2align 6\n_blake3_compress_xof_avx512:\nblake3_compress_xof_avx512:\n        _CET_ENDBR\n        vmovdqu xmm0, xmmword ptr [rdi]\n        vmovdqu xmm1, xmmword ptr [rdi+0x10]\n        movzx   eax, r8b\n        movzx   edx, dl\n        shl     rax, 32\n        add     rdx, rax\n        vmovq   xmm3, rcx\n        vmovq   xmm4, rdx\n        vpunpcklqdq xmm3, xmm3, xmm4\n        vmovaps xmm2, xmmword ptr [BLAKE3_IV+rip]\n        vmovups xmm8, xmmword ptr [rsi]\n        vmovups xmm9, xmmword ptr [rsi+0x10]\n        vshufps xmm4, xmm8, xmm9, 136\n        vshufps xmm5, xmm8, xmm9, 221\n        vmovups xmm8, xmmword ptr [rsi+0x20]\n        vmovups xmm9, xmmword ptr [rsi+0x30]\n        vshufps xmm6, xmm8, xmm9, 136\n        vshufps xmm7, xmm8, xmm9, 221\n        vpshufd xmm6, xmm6, 0x93\n        vpshufd xmm7, xmm7, 0x93\n        mov     al, 7\n9:\n        vpaddd  xmm0, xmm0, xmm4\n        vpaddd  xmm0, xmm0, xmm1\n        vpxord  xmm3, xmm3, xmm0\n        vprord  xmm3, xmm3, 16\n        vpaddd  xmm2, xmm2, xmm3\n        vpxord  xmm1, xmm1, xmm2\n        vprord  xmm1, xmm1, 12\n        vpaddd  xmm0, xmm0, xmm5\n        vpaddd  xmm0, xmm0, xmm1\n        vpxord  xmm3, xmm3, xmm0\n        vprord  xmm3, xmm3, 8\n        vpaddd  xmm2, xmm2, xmm3\n        vpxord  xmm1, xmm1, xmm2\n        vprord  xmm1, xmm1, 7\n        vpshufd xmm0, xmm0, 0x93\n        vpshufd xmm3, xmm3, 0x4E\n        vpshufd xmm2, xmm2, 0x39\n        vpaddd  xmm0, xmm0, xmm6\n        vpaddd  xmm0, xmm0, xmm1\n        vpxord  xmm3, xmm3, xmm0\n        vprord  xmm3, xmm3, 16\n        vpaddd  xmm2, xmm2, xmm3\n        vpxord  xmm1, xmm1, xmm2\n        vprord  xmm1, xmm1, 12\n        vpaddd  xmm0, xmm0, xmm7\n        vpaddd  xmm0, xmm0, xmm1\n        vpxord  xmm3, xmm3, xmm0\n        vprord  xmm3, xmm3, 8\n        vpaddd  xmm2, xmm2, xmm3\n        vpxord  xmm1, xmm1, xmm2\n        vprord  xmm1, xmm1, 7\n        vpshufd xmm0, xmm0, 0x39\n        vpshufd xmm3, xmm3, 0x4E\n        vpshufd xmm2, xmm2, 0x93\n        dec     al\n        jz      9f\n        vshufps xmm8, xmm4, xmm5, 214\n        vpshufd xmm9, xmm4, 0x0F\n        vpshufd xmm4, xmm8, 0x39\n        vshufps xmm8, xmm6, xmm7, 250\n        vpblendd xmm9, xmm9, xmm8, 0xAA\n        vpunpcklqdq xmm8, xmm7, xmm5\n        vpblendd xmm8, xmm8, xmm6, 0x88\n        vpshufd xmm8, xmm8, 0x78\n        vpunpckhdq xmm5, xmm5, xmm7\n        vpunpckldq xmm6, xmm6, xmm5\n        vpshufd xmm7, xmm6, 0x1E\n        vmovdqa xmm5, xmm9\n        vmovdqa xmm6, xmm8\n        jmp     9b\n9:\n        vpxor   xmm0, xmm0, xmm2\n        vpxor   xmm1, xmm1, xmm3\n        vpxor   xmm2, xmm2, [rdi]\n        vpxor   xmm3, xmm3, [rdi+0x10]\n        vmovdqu xmmword ptr [r9], xmm0\n        vmovdqu xmmword ptr [r9+0x10], xmm1\n        vmovdqu xmmword ptr [r9+0x20], xmm2\n        vmovdqu xmmword ptr [r9+0x30], xmm3\n        ret\n\n#ifdef __APPLE__\n.static_data\n#else\n.section .rodata\n#endif\n.p2align  6\nINDEX0:\n        .long    0,  1,  2,  3, 16, 17, 18, 19\n        .long    8,  9, 10, 11, 24, 25, 26, 27\nINDEX1:\n        .long    4,  5,  6,  7, 20, 21, 22, 23\n        .long   12, 13, 14, 15, 28, 29, 30, 31\nADD0:\n        .long    0,  1,  2,  3,  4,  5,  6,  7\n        .long    8,  9, 10, 11, 12, 13, 14, 15\nADD1:   .long    1\n\nADD16:  .long   16\nBLAKE3_BLOCK_LEN:\n        .long   64\n.p2align 6\nBLAKE3_IV:\nBLAKE3_IV_0:\n        .long   0x6A09E667\nBLAKE3_IV_1:\n        .long   0xBB67AE85\nBLAKE3_IV_2:\n        .long   0x3C6EF372\nBLAKE3_IV_3:\n        .long   0xA54FF53A\n"
  },
  {
    "path": "common/blake3/blake3_dispatch.c",
    "content": "#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n\n#include \"blake3_impl.h\"\n\n#if defined(IS_X86)\n#if defined(_MSC_VER)\n#include <intrin.h>\n#elif defined(__GNUC__)\n#include <immintrin.h>\n#else\n#error \"Unimplemented!\"\n#endif\n#endif\n\n#define MAYBE_UNUSED(x) (void)((x))\n\n#if defined(IS_X86)\nstatic uint64_t xgetbv() {\n#if defined(_MSC_VER)\n  return _xgetbv(0);\n#else\n  uint32_t eax = 0, edx = 0;\n  __asm__ __volatile__(\"xgetbv\\n\" : \"=a\"(eax), \"=d\"(edx) : \"c\"(0));\n  return ((uint64_t)edx << 32) | eax;\n#endif\n}\n\nstatic void cpuid(uint32_t out[4], uint32_t id) {\n#if defined(_MSC_VER)\n  __cpuid((int *)out, id);\n#elif defined(__i386__) || defined(_M_IX86)\n  __asm__ __volatile__(\"movl %%ebx, %1\\n\"\n                       \"cpuid\\n\"\n                       \"xchgl %1, %%ebx\\n\"\n                       : \"=a\"(out[0]), \"=r\"(out[1]), \"=c\"(out[2]), \"=d\"(out[3])\n                       : \"a\"(id));\n#else\n  __asm__ __volatile__(\"cpuid\\n\"\n                       : \"=a\"(out[0]), \"=b\"(out[1]), \"=c\"(out[2]), \"=d\"(out[3])\n                       : \"a\"(id));\n#endif\n}\n\nstatic void cpuidex(uint32_t out[4], uint32_t id, uint32_t sid) {\n#if defined(_MSC_VER)\n  __cpuidex((int *)out, id, sid);\n#elif defined(__i386__) || defined(_M_IX86)\n  __asm__ __volatile__(\"movl %%ebx, %1\\n\"\n                       \"cpuid\\n\"\n                       \"xchgl %1, %%ebx\\n\"\n                       : \"=a\"(out[0]), \"=r\"(out[1]), \"=c\"(out[2]), \"=d\"(out[3])\n                       : \"a\"(id), \"c\"(sid));\n#else\n  __asm__ __volatile__(\"cpuid\\n\"\n                       : \"=a\"(out[0]), \"=b\"(out[1]), \"=c\"(out[2]), \"=d\"(out[3])\n                       : \"a\"(id), \"c\"(sid));\n#endif\n}\n\n#endif\n\nenum cpu_feature {\n  SSE2 = 1 << 0,\n  SSSE3 = 1 << 1,\n  SSE41 = 1 << 2,\n  AVX = 1 << 3,\n  AVX2 = 1 << 4,\n  AVX512F = 1 << 5,\n  AVX512VL = 1 << 6,\n  /* ... */\n  UNDEFINED = 1 << 30\n};\n\n#if !defined(BLAKE3_TESTING)\nstatic /* Allow the variable to be controlled manually for testing */\n#endif\n    enum cpu_feature g_cpu_features = UNDEFINED;\n\n#if !defined(BLAKE3_TESTING)\nstatic\n#endif\n    enum cpu_feature\n    get_cpu_features() {\n\n  if (g_cpu_features != UNDEFINED) {\n    return g_cpu_features;\n  } else {\n#if defined(IS_X86)\n    uint32_t regs[4] = {0};\n    uint32_t *eax = &regs[0], *ebx = &regs[1], *ecx = &regs[2], *edx = &regs[3];\n    (void)edx;\n    enum cpu_feature features = 0;\n    cpuid(regs, 0);\n    const int max_id = *eax;\n    cpuid(regs, 1);\n#if defined(__amd64__) || defined(_M_X64)\n    features |= SSE2;\n#else\n    if (*edx & (1UL << 26))\n      features |= SSE2;\n#endif\n    if (*ecx & (1UL << 0))\n      features |= SSSE3;\n    if (*ecx & (1UL << 19))\n      features |= SSE41;\n\n    if (*ecx & (1UL << 27)) { // OSXSAVE\n      const uint64_t mask = xgetbv();\n      if ((mask & 6) == 6) { // SSE and AVX states\n        if (*ecx & (1UL << 28))\n          features |= AVX;\n        if (max_id >= 7) {\n          cpuidex(regs, 7, 0);\n          if (*ebx & (1UL << 5))\n            features |= AVX2;\n          if ((mask & 224) == 224) { // Opmask, ZMM_Hi256, Hi16_Zmm\n            if (*ebx & (1UL << 31))\n              features |= AVX512VL;\n            if (*ebx & (1UL << 16))\n              features |= AVX512F;\n          }\n        }\n      }\n    }\n    g_cpu_features = features;\n    return features;\n#else\n    /* How to detect NEON? */\n    return 0;\n#endif\n  }\n}\n\nvoid blake3_compress_in_place(uint32_t cv[8],\n                              const uint8_t block[BLAKE3_BLOCK_LEN],\n                              uint8_t block_len, uint64_t counter,\n                              uint8_t flags) {\n#if defined(IS_X86)\n  const enum cpu_feature features = get_cpu_features();\n  MAYBE_UNUSED(features);\n#if !defined(BLAKE3_NO_AVX512)\n  if (features & AVX512VL) {\n    blake3_compress_in_place_avx512(cv, block, block_len, counter, flags);\n    return;\n  }\n#endif\n#if !defined(BLAKE3_NO_SSE41)\n  if (features & SSE41) {\n    blake3_compress_in_place_sse41(cv, block, block_len, counter, flags);\n    return;\n  }\n#endif\n#if !defined(BLAKE3_NO_SSE2)\n  if (features & SSE2) {\n    blake3_compress_in_place_sse2(cv, block, block_len, counter, flags);\n    return;\n  }\n#endif\n#endif\n  blake3_compress_in_place_portable(cv, block, block_len, counter, flags);\n}\n\nvoid blake3_compress_xof(const uint32_t cv[8],\n                         const uint8_t block[BLAKE3_BLOCK_LEN],\n                         uint8_t block_len, uint64_t counter, uint8_t flags,\n                         uint8_t out[64]) {\n#if defined(IS_X86)\n  const enum cpu_feature features = get_cpu_features();\n  MAYBE_UNUSED(features);\n#if !defined(BLAKE3_NO_AVX512)\n  if (features & AVX512VL) {\n    blake3_compress_xof_avx512(cv, block, block_len, counter, flags, out);\n    return;\n  }\n#endif\n#if !defined(BLAKE3_NO_SSE41)\n  if (features & SSE41) {\n    blake3_compress_xof_sse41(cv, block, block_len, counter, flags, out);\n    return;\n  }\n#endif\n#if !defined(BLAKE3_NO_SSE2)\n  if (features & SSE2) {\n    blake3_compress_xof_sse2(cv, block, block_len, counter, flags, out);\n    return;\n  }\n#endif\n#endif\n  blake3_compress_xof_portable(cv, block, block_len, counter, flags, out);\n}\n\nvoid blake3_hash_many(const uint8_t *const *inputs, size_t num_inputs,\n                      size_t blocks, const uint32_t key[8], uint64_t counter,\n                      bool increment_counter, uint8_t flags,\n                      uint8_t flags_start, uint8_t flags_end, uint8_t *out) {\n#if defined(IS_X86)\n  const enum cpu_feature features = get_cpu_features();\n  MAYBE_UNUSED(features);\n\n#if !defined(BLAKE3_NO_AVX512)\n  if ((features & (AVX512F|AVX512VL)) == (AVX512F|AVX512VL)) {\n    blake3_hash_many_avx512(inputs, num_inputs, blocks, key, counter,\n                            increment_counter, flags, flags_start, flags_end,\n                            out);\n    return;\n  }\n#endif\n#if !defined(BLAKE3_NO_AVX2)\n  if (features & AVX2) {\n    blake3_hash_many_avx2(inputs, num_inputs, blocks, key, counter,\n                          increment_counter, flags, flags_start, flags_end,\n                          out);\n    return;\n  }\n#endif\n#if !defined(BLAKE3_NO_SSE41)\n  if (features & SSE41) {\n    blake3_hash_many_sse41(inputs, num_inputs, blocks, key, counter,\n                           increment_counter, flags, flags_start, flags_end,\n                           out);\n    return;\n  }\n#endif\n#if !defined(BLAKE3_NO_SSE2)\n  if (features & SSE2) {\n    blake3_hash_many_sse2(inputs, num_inputs, blocks, key, counter,\n                          increment_counter, flags, flags_start, flags_end,\n                          out);\n    return;\n  }\n#endif\n#endif\n\n#if defined(BLAKE3_USE_NEON)\n  blake3_hash_many_neon(inputs, num_inputs, blocks, key, counter,\n                        increment_counter, flags, flags_start, flags_end, out);\n  return;\n#endif\n\n  blake3_hash_many_portable(inputs, num_inputs, blocks, key, counter,\n                            increment_counter, flags, flags_start, flags_end,\n                            out);\n}\n\n// The dynamically detected SIMD degree of the current platform.\nsize_t blake3_simd_degree(void) {\n#if defined(IS_X86)\n  const enum cpu_feature features = get_cpu_features();\n  MAYBE_UNUSED(features);\n#if !defined(BLAKE3_NO_AVX512)\n  if ((features & (AVX512F|AVX512VL)) == (AVX512F|AVX512VL)) {\n    return 16;\n  }\n#endif\n#if !defined(BLAKE3_NO_AVX2)\n  if (features & AVX2) {\n    return 8;\n  }\n#endif\n#if !defined(BLAKE3_NO_SSE41)\n  if (features & SSE41) {\n    return 4;\n  }\n#endif\n#if !defined(BLAKE3_NO_SSE2)\n  if (features & SSE2) {\n    return 4;\n  }\n#endif\n#endif\n#if defined(BLAKE3_USE_NEON)\n  return 4;\n#endif\n  return 1;\n}\n"
  },
  {
    "path": "common/blake3/blake3_impl.h",
    "content": "#ifndef BLAKE3_IMPL_H\n#define BLAKE3_IMPL_H\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <string.h>\n\n#include \"blake3.h\"\n\n// internal flags\nenum blake3_flags {\n  CHUNK_START         = 1 << 0,\n  CHUNK_END           = 1 << 1,\n  PARENT              = 1 << 2,\n  ROOT                = 1 << 3,\n  KEYED_HASH          = 1 << 4,\n  DERIVE_KEY_CONTEXT  = 1 << 5,\n  DERIVE_KEY_MATERIAL = 1 << 6,\n};\n\n// This C implementation tries to support recent versions of GCC, Clang, and\n// MSVC.\n#if defined(_MSC_VER)\n#define INLINE static __forceinline\n#else\n#define INLINE static inline __attribute__((always_inline))\n#endif\n\n#if defined(__x86_64__) || defined(_M_X64)\n#define IS_X86\n#define IS_X86_64\n#endif\n\n#if defined(__i386__) || defined(_M_IX86)\n#define IS_X86\n#define IS_X86_32\n#endif\n\n#if defined(IS_X86)\n#if defined(_MSC_VER)\n#include <intrin.h>\n#endif\n#include <immintrin.h>\n#endif\n\n#if defined(IS_X86)\n#define MAX_SIMD_DEGREE 16\n#elif defined(BLAKE3_USE_NEON)\n#define MAX_SIMD_DEGREE 4\n#else\n#define MAX_SIMD_DEGREE 1\n#endif\n\n// There are some places where we want a static size that's equal to the\n// MAX_SIMD_DEGREE, but also at least 2.\n#define MAX_SIMD_DEGREE_OR_2 (MAX_SIMD_DEGREE > 2 ? MAX_SIMD_DEGREE : 2)\n\nstatic const uint32_t IV[8] = {0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL,\n                               0xA54FF53AUL, 0x510E527FUL, 0x9B05688CUL,\n                               0x1F83D9ABUL, 0x5BE0CD19UL};\n\nstatic const uint8_t MSG_SCHEDULE[7][16] = {\n    {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},\n    {2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8},\n    {3, 4, 10, 12, 13, 2, 7, 14, 6, 5, 9, 0, 11, 15, 8, 1},\n    {10, 7, 12, 9, 14, 3, 13, 15, 4, 0, 11, 2, 5, 8, 1, 6},\n    {12, 13, 9, 11, 15, 10, 14, 8, 7, 2, 5, 3, 0, 1, 6, 4},\n    {9, 14, 11, 5, 8, 12, 15, 1, 13, 3, 0, 10, 2, 6, 4, 7},\n    {11, 15, 5, 0, 1, 9, 8, 6, 14, 10, 2, 12, 3, 4, 7, 13},\n};\n\n/* Find index of the highest set bit */\n/* x is assumed to be nonzero.       */\nstatic unsigned int highest_one(uint64_t x) {\n#if defined(__GNUC__) || defined(__clang__)\n  return 63 ^ __builtin_clzll(x);\n#elif defined(_MSC_VER) && defined(IS_X86_64)\n  unsigned long index;\n  _BitScanReverse64(&index, x);\n  return index;\n#elif defined(_MSC_VER) && defined(IS_X86_32)\n  if(x >> 32) {\n    unsigned long index;\n    _BitScanReverse(&index, x >> 32);\n    return 32 + index;\n  } else {\n    unsigned long index;\n    _BitScanReverse(&index, x);\n    return index;\n  }\n#else\n  unsigned int c = 0;\n  if(x & 0xffffffff00000000ULL) { x >>= 32; c += 32; }\n  if(x & 0x00000000ffff0000ULL) { x >>= 16; c += 16; }\n  if(x & 0x000000000000ff00ULL) { x >>=  8; c +=  8; }\n  if(x & 0x00000000000000f0ULL) { x >>=  4; c +=  4; }\n  if(x & 0x000000000000000cULL) { x >>=  2; c +=  2; }\n  if(x & 0x0000000000000002ULL) {           c +=  1; }\n  return c;\n#endif\n}\n\n// Count the number of 1 bits.\nINLINE unsigned int popcnt(uint64_t x) {\n#if defined(__GNUC__) || defined(__clang__)\n  return __builtin_popcountll(x);\n#else\n  unsigned int count = 0;\n  while (x != 0) {\n    count += 1;\n    x &= x - 1;\n  }\n  return count;\n#endif\n}\n\n// Largest power of two less than or equal to x. As a special case, returns 1\n// when x is 0.\nINLINE uint64_t round_down_to_power_of_2(uint64_t x) {\n  return 1ULL << highest_one(x | 1);\n}\n\nINLINE uint32_t counter_low(uint64_t counter) { return (uint32_t)counter; }\n\nINLINE uint32_t counter_high(uint64_t counter) {\n  return (uint32_t)(counter >> 32);\n}\n\nINLINE uint32_t load32(const void *src) {\n  const uint8_t *p = (const uint8_t *)src;\n  return ((uint32_t)(p[0]) << 0) | ((uint32_t)(p[1]) << 8) |\n         ((uint32_t)(p[2]) << 16) | ((uint32_t)(p[3]) << 24);\n}\n\nINLINE void load_key_words(const uint8_t key[BLAKE3_KEY_LEN],\n                           uint32_t key_words[8]) {\n  key_words[0] = load32(&key[0 * 4]);\n  key_words[1] = load32(&key[1 * 4]);\n  key_words[2] = load32(&key[2 * 4]);\n  key_words[3] = load32(&key[3 * 4]);\n  key_words[4] = load32(&key[4 * 4]);\n  key_words[5] = load32(&key[5 * 4]);\n  key_words[6] = load32(&key[6 * 4]);\n  key_words[7] = load32(&key[7 * 4]);\n}\n\nINLINE void store32(void *dst, uint32_t w) {\n  uint8_t *p = (uint8_t *)dst;\n  p[0] = (uint8_t)(w >> 0);\n  p[1] = (uint8_t)(w >> 8);\n  p[2] = (uint8_t)(w >> 16);\n  p[3] = (uint8_t)(w >> 24);\n}\n\nINLINE void store_cv_words(uint8_t bytes_out[32], uint32_t cv_words[8]) {\n  store32(&bytes_out[0 * 4], cv_words[0]);\n  store32(&bytes_out[1 * 4], cv_words[1]);\n  store32(&bytes_out[2 * 4], cv_words[2]);\n  store32(&bytes_out[3 * 4], cv_words[3]);\n  store32(&bytes_out[4 * 4], cv_words[4]);\n  store32(&bytes_out[5 * 4], cv_words[5]);\n  store32(&bytes_out[6 * 4], cv_words[6]);\n  store32(&bytes_out[7 * 4], cv_words[7]);\n}\n\nvoid blake3_compress_in_place(uint32_t cv[8],\n                              const uint8_t block[BLAKE3_BLOCK_LEN],\n                              uint8_t block_len, uint64_t counter,\n                              uint8_t flags);\n\nvoid blake3_compress_xof(const uint32_t cv[8],\n                         const uint8_t block[BLAKE3_BLOCK_LEN],\n                         uint8_t block_len, uint64_t counter, uint8_t flags,\n                         uint8_t out[64]);\n\nvoid blake3_hash_many(const uint8_t *const *inputs, size_t num_inputs,\n                      size_t blocks, const uint32_t key[8], uint64_t counter,\n                      bool increment_counter, uint8_t flags,\n                      uint8_t flags_start, uint8_t flags_end, uint8_t *out);\n\nsize_t blake3_simd_degree(void);\n\n\n// Declarations for implementation-specific functions.\nvoid blake3_compress_in_place_portable(uint32_t cv[8],\n                                       const uint8_t block[BLAKE3_BLOCK_LEN],\n                                       uint8_t block_len, uint64_t counter,\n                                       uint8_t flags);\n\nvoid blake3_compress_xof_portable(const uint32_t cv[8],\n                                  const uint8_t block[BLAKE3_BLOCK_LEN],\n                                  uint8_t block_len, uint64_t counter,\n                                  uint8_t flags, uint8_t out[64]);\n\nvoid blake3_hash_many_portable(const uint8_t *const *inputs, size_t num_inputs,\n                               size_t blocks, const uint32_t key[8],\n                               uint64_t counter, bool increment_counter,\n                               uint8_t flags, uint8_t flags_start,\n                               uint8_t flags_end, uint8_t *out);\n\n#if defined(IS_X86)\n#if !defined(BLAKE3_NO_SSE2)\nvoid blake3_compress_in_place_sse2(uint32_t cv[8],\n                                   const uint8_t block[BLAKE3_BLOCK_LEN],\n                                   uint8_t block_len, uint64_t counter,\n                                   uint8_t flags);\nvoid blake3_compress_xof_sse2(const uint32_t cv[8],\n                              const uint8_t block[BLAKE3_BLOCK_LEN],\n                              uint8_t block_len, uint64_t counter,\n                              uint8_t flags, uint8_t out[64]);\nvoid blake3_hash_many_sse2(const uint8_t *const *inputs, size_t num_inputs,\n                           size_t blocks, const uint32_t key[8],\n                           uint64_t counter, bool increment_counter,\n                           uint8_t flags, uint8_t flags_start,\n                           uint8_t flags_end, uint8_t *out);\n#endif\n#if !defined(BLAKE3_NO_SSE41)\nvoid blake3_compress_in_place_sse41(uint32_t cv[8],\n                                    const uint8_t block[BLAKE3_BLOCK_LEN],\n                                    uint8_t block_len, uint64_t counter,\n                                    uint8_t flags);\nvoid blake3_compress_xof_sse41(const uint32_t cv[8],\n                               const uint8_t block[BLAKE3_BLOCK_LEN],\n                               uint8_t block_len, uint64_t counter,\n                               uint8_t flags, uint8_t out[64]);\nvoid blake3_hash_many_sse41(const uint8_t *const *inputs, size_t num_inputs,\n                            size_t blocks, const uint32_t key[8],\n                            uint64_t counter, bool increment_counter,\n                            uint8_t flags, uint8_t flags_start,\n                            uint8_t flags_end, uint8_t *out);\n#endif\n#if !defined(BLAKE3_NO_AVX2)\nvoid blake3_hash_many_avx2(const uint8_t *const *inputs, size_t num_inputs,\n                           size_t blocks, const uint32_t key[8],\n                           uint64_t counter, bool increment_counter,\n                           uint8_t flags, uint8_t flags_start,\n                           uint8_t flags_end, uint8_t *out);\n#endif\n#if !defined(BLAKE3_NO_AVX512)\nvoid blake3_compress_in_place_avx512(uint32_t cv[8],\n                                     const uint8_t block[BLAKE3_BLOCK_LEN],\n                                     uint8_t block_len, uint64_t counter,\n                                     uint8_t flags);\n\nvoid blake3_compress_xof_avx512(const uint32_t cv[8],\n                                const uint8_t block[BLAKE3_BLOCK_LEN],\n                                uint8_t block_len, uint64_t counter,\n                                uint8_t flags, uint8_t out[64]);\n\nvoid blake3_hash_many_avx512(const uint8_t *const *inputs, size_t num_inputs,\n                             size_t blocks, const uint32_t key[8],\n                             uint64_t counter, bool increment_counter,\n                             uint8_t flags, uint8_t flags_start,\n                             uint8_t flags_end, uint8_t *out);\n#endif\n#endif\n\n#if defined(BLAKE3_USE_NEON)\nvoid blake3_hash_many_neon(const uint8_t *const *inputs, size_t num_inputs,\n                           size_t blocks, const uint32_t key[8],\n                           uint64_t counter, bool increment_counter,\n                           uint8_t flags, uint8_t flags_start,\n                           uint8_t flags_end, uint8_t *out);\n#endif\n\n\n#endif /* BLAKE3_IMPL_H */\n"
  },
  {
    "path": "common/blake3/blake3_neon.c",
    "content": "#include \"blake3_impl.h\"\n\n#include <arm_neon.h>\n\n// TODO: This is probably incorrect for big-endian ARM. How should that work?\nINLINE uint32x4_t loadu_128(const uint8_t src[16]) {\n  // vld1q_u32 has alignment requirements. Don't use it.\n  uint32x4_t x;\n  memcpy(&x, src, 16);\n  return x;\n}\n\nINLINE void storeu_128(uint32x4_t src, uint8_t dest[16]) {\n  // vst1q_u32 has alignment requirements. Don't use it.\n  memcpy(dest, &src, 16);\n}\n\nINLINE uint32x4_t add_128(uint32x4_t a, uint32x4_t b) {\n  return vaddq_u32(a, b);\n}\n\nINLINE uint32x4_t xor_128(uint32x4_t a, uint32x4_t b) {\n  return veorq_u32(a, b);\n}\n\nINLINE uint32x4_t set1_128(uint32_t x) { return vld1q_dup_u32(&x); }\n\nINLINE uint32x4_t set4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {\n  uint32_t array[4] = {a, b, c, d};\n  return vld1q_u32(array);\n}\n\nINLINE uint32x4_t rot16_128(uint32x4_t x) {\n  return vorrq_u32(vshrq_n_u32(x, 16), vshlq_n_u32(x, 32 - 16));\n}\n\nINLINE uint32x4_t rot12_128(uint32x4_t x) {\n  return vorrq_u32(vshrq_n_u32(x, 12), vshlq_n_u32(x, 32 - 12));\n}\n\nINLINE uint32x4_t rot8_128(uint32x4_t x) {\n  return vorrq_u32(vshrq_n_u32(x, 8), vshlq_n_u32(x, 32 - 8));\n}\n\nINLINE uint32x4_t rot7_128(uint32x4_t x) {\n  return vorrq_u32(vshrq_n_u32(x, 7), vshlq_n_u32(x, 32 - 7));\n}\n\n// TODO: compress_neon\n\n// TODO: hash2_neon\n\n/*\n * ----------------------------------------------------------------------------\n * hash4_neon\n * ----------------------------------------------------------------------------\n */\n\nINLINE void round_fn4(uint32x4_t v[16], uint32x4_t m[16], size_t r) {\n  v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][0]]);\n  v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][2]]);\n  v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][4]]);\n  v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][6]]);\n  v[0] = add_128(v[0], v[4]);\n  v[1] = add_128(v[1], v[5]);\n  v[2] = add_128(v[2], v[6]);\n  v[3] = add_128(v[3], v[7]);\n  v[12] = xor_128(v[12], v[0]);\n  v[13] = xor_128(v[13], v[1]);\n  v[14] = xor_128(v[14], v[2]);\n  v[15] = xor_128(v[15], v[3]);\n  v[12] = rot16_128(v[12]);\n  v[13] = rot16_128(v[13]);\n  v[14] = rot16_128(v[14]);\n  v[15] = rot16_128(v[15]);\n  v[8] = add_128(v[8], v[12]);\n  v[9] = add_128(v[9], v[13]);\n  v[10] = add_128(v[10], v[14]);\n  v[11] = add_128(v[11], v[15]);\n  v[4] = xor_128(v[4], v[8]);\n  v[5] = xor_128(v[5], v[9]);\n  v[6] = xor_128(v[6], v[10]);\n  v[7] = xor_128(v[7], v[11]);\n  v[4] = rot12_128(v[4]);\n  v[5] = rot12_128(v[5]);\n  v[6] = rot12_128(v[6]);\n  v[7] = rot12_128(v[7]);\n  v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][1]]);\n  v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][3]]);\n  v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][5]]);\n  v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][7]]);\n  v[0] = add_128(v[0], v[4]);\n  v[1] = add_128(v[1], v[5]);\n  v[2] = add_128(v[2], v[6]);\n  v[3] = add_128(v[3], v[7]);\n  v[12] = xor_128(v[12], v[0]);\n  v[13] = xor_128(v[13], v[1]);\n  v[14] = xor_128(v[14], v[2]);\n  v[15] = xor_128(v[15], v[3]);\n  v[12] = rot8_128(v[12]);\n  v[13] = rot8_128(v[13]);\n  v[14] = rot8_128(v[14]);\n  v[15] = rot8_128(v[15]);\n  v[8] = add_128(v[8], v[12]);\n  v[9] = add_128(v[9], v[13]);\n  v[10] = add_128(v[10], v[14]);\n  v[11] = add_128(v[11], v[15]);\n  v[4] = xor_128(v[4], v[8]);\n  v[5] = xor_128(v[5], v[9]);\n  v[6] = xor_128(v[6], v[10]);\n  v[7] = xor_128(v[7], v[11]);\n  v[4] = rot7_128(v[4]);\n  v[5] = rot7_128(v[5]);\n  v[6] = rot7_128(v[6]);\n  v[7] = rot7_128(v[7]);\n\n  v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][8]]);\n  v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][10]]);\n  v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][12]]);\n  v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][14]]);\n  v[0] = add_128(v[0], v[5]);\n  v[1] = add_128(v[1], v[6]);\n  v[2] = add_128(v[2], v[7]);\n  v[3] = add_128(v[3], v[4]);\n  v[15] = xor_128(v[15], v[0]);\n  v[12] = xor_128(v[12], v[1]);\n  v[13] = xor_128(v[13], v[2]);\n  v[14] = xor_128(v[14], v[3]);\n  v[15] = rot16_128(v[15]);\n  v[12] = rot16_128(v[12]);\n  v[13] = rot16_128(v[13]);\n  v[14] = rot16_128(v[14]);\n  v[10] = add_128(v[10], v[15]);\n  v[11] = add_128(v[11], v[12]);\n  v[8] = add_128(v[8], v[13]);\n  v[9] = add_128(v[9], v[14]);\n  v[5] = xor_128(v[5], v[10]);\n  v[6] = xor_128(v[6], v[11]);\n  v[7] = xor_128(v[7], v[8]);\n  v[4] = xor_128(v[4], v[9]);\n  v[5] = rot12_128(v[5]);\n  v[6] = rot12_128(v[6]);\n  v[7] = rot12_128(v[7]);\n  v[4] = rot12_128(v[4]);\n  v[0] = add_128(v[0], m[(size_t)MSG_SCHEDULE[r][9]]);\n  v[1] = add_128(v[1], m[(size_t)MSG_SCHEDULE[r][11]]);\n  v[2] = add_128(v[2], m[(size_t)MSG_SCHEDULE[r][13]]);\n  v[3] = add_128(v[3], m[(size_t)MSG_SCHEDULE[r][15]]);\n  v[0] = add_128(v[0], v[5]);\n  v[1] = add_128(v[1], v[6]);\n  v[2] = add_128(v[2], v[7]);\n  v[3] = add_128(v[3], v[4]);\n  v[15] = xor_128(v[15], v[0]);\n  v[12] = xor_128(v[12], v[1]);\n  v[13] = xor_128(v[13], v[2]);\n  v[14] = xor_128(v[14], v[3]);\n  v[15] = rot8_128(v[15]);\n  v[12] = rot8_128(v[12]);\n  v[13] = rot8_128(v[13]);\n  v[14] = rot8_128(v[14]);\n  v[10] = add_128(v[10], v[15]);\n  v[11] = add_128(v[11], v[12]);\n  v[8] = add_128(v[8], v[13]);\n  v[9] = add_128(v[9], v[14]);\n  v[5] = xor_128(v[5], v[10]);\n  v[6] = xor_128(v[6], v[11]);\n  v[7] = xor_128(v[7], v[8]);\n  v[4] = xor_128(v[4], v[9]);\n  v[5] = rot7_128(v[5]);\n  v[6] = rot7_128(v[6]);\n  v[7] = rot7_128(v[7]);\n  v[4] = rot7_128(v[4]);\n}\n\nINLINE void transpose_vecs_128(uint32x4_t vecs[4]) {\n  // Individually transpose the four 2x2 sub-matrices in each corner.\n  uint32x4x2_t rows01 = vtrnq_u32(vecs[0], vecs[1]);\n  uint32x4x2_t rows23 = vtrnq_u32(vecs[2], vecs[3]);\n\n  // Swap the top-right and bottom-left 2x2s (which just got transposed).\n  vecs[0] =\n      vcombine_u32(vget_low_u32(rows01.val[0]), vget_low_u32(rows23.val[0]));\n  vecs[1] =\n      vcombine_u32(vget_low_u32(rows01.val[1]), vget_low_u32(rows23.val[1]));\n  vecs[2] =\n      vcombine_u32(vget_high_u32(rows01.val[0]), vget_high_u32(rows23.val[0]));\n  vecs[3] =\n      vcombine_u32(vget_high_u32(rows01.val[1]), vget_high_u32(rows23.val[1]));\n}\n\nINLINE void transpose_msg_vecs4(const uint8_t *const *inputs,\n                                size_t block_offset, uint32x4_t out[16]) {\n  out[0] = loadu_128(&inputs[0][block_offset + 0 * sizeof(uint32x4_t)]);\n  out[1] = loadu_128(&inputs[1][block_offset + 0 * sizeof(uint32x4_t)]);\n  out[2] = loadu_128(&inputs[2][block_offset + 0 * sizeof(uint32x4_t)]);\n  out[3] = loadu_128(&inputs[3][block_offset + 0 * sizeof(uint32x4_t)]);\n  out[4] = loadu_128(&inputs[0][block_offset + 1 * sizeof(uint32x4_t)]);\n  out[5] = loadu_128(&inputs[1][block_offset + 1 * sizeof(uint32x4_t)]);\n  out[6] = loadu_128(&inputs[2][block_offset + 1 * sizeof(uint32x4_t)]);\n  out[7] = loadu_128(&inputs[3][block_offset + 1 * sizeof(uint32x4_t)]);\n  out[8] = loadu_128(&inputs[0][block_offset + 2 * sizeof(uint32x4_t)]);\n  out[9] = loadu_128(&inputs[1][block_offset + 2 * sizeof(uint32x4_t)]);\n  out[10] = loadu_128(&inputs[2][block_offset + 2 * sizeof(uint32x4_t)]);\n  out[11] = loadu_128(&inputs[3][block_offset + 2 * sizeof(uint32x4_t)]);\n  out[12] = loadu_128(&inputs[0][block_offset + 3 * sizeof(uint32x4_t)]);\n  out[13] = loadu_128(&inputs[1][block_offset + 3 * sizeof(uint32x4_t)]);\n  out[14] = loadu_128(&inputs[2][block_offset + 3 * sizeof(uint32x4_t)]);\n  out[15] = loadu_128(&inputs[3][block_offset + 3 * sizeof(uint32x4_t)]);\n  transpose_vecs_128(&out[0]);\n  transpose_vecs_128(&out[4]);\n  transpose_vecs_128(&out[8]);\n  transpose_vecs_128(&out[12]);\n}\n\nINLINE void load_counters4(uint64_t counter, bool increment_counter,\n                           uint32x4_t *out_low, uint32x4_t *out_high) {\n  uint64_t mask = (increment_counter ? ~0 : 0);\n  *out_low = set4(\n      counter_low(counter + (mask & 0)), counter_low(counter + (mask & 1)),\n      counter_low(counter + (mask & 2)), counter_low(counter + (mask & 3)));\n  *out_high = set4(\n      counter_high(counter + (mask & 0)), counter_high(counter + (mask & 1)),\n      counter_high(counter + (mask & 2)), counter_high(counter + (mask & 3)));\n}\n\nvoid blake3_hash4_neon(const uint8_t *const *inputs, size_t blocks,\n                       const uint32_t key[8], uint64_t counter,\n                       bool increment_counter, uint8_t flags,\n                       uint8_t flags_start, uint8_t flags_end, uint8_t *out) {\n  uint32x4_t h_vecs[8] = {\n      set1_128(key[0]), set1_128(key[1]), set1_128(key[2]), set1_128(key[3]),\n      set1_128(key[4]), set1_128(key[5]), set1_128(key[6]), set1_128(key[7]),\n  };\n  uint32x4_t counter_low_vec, counter_high_vec;\n  load_counters4(counter, increment_counter, &counter_low_vec,\n                 &counter_high_vec);\n  uint8_t block_flags = flags | flags_start;\n\n  for (size_t block = 0; block < blocks; block++) {\n    if (block + 1 == blocks) {\n      block_flags |= flags_end;\n    }\n    uint32x4_t block_len_vec = set1_128(BLAKE3_BLOCK_LEN);\n    uint32x4_t block_flags_vec = set1_128(block_flags);\n    uint32x4_t msg_vecs[16];\n    transpose_msg_vecs4(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs);\n\n    uint32x4_t v[16] = {\n        h_vecs[0],       h_vecs[1],        h_vecs[2],       h_vecs[3],\n        h_vecs[4],       h_vecs[5],        h_vecs[6],       h_vecs[7],\n        set1_128(IV[0]), set1_128(IV[1]),  set1_128(IV[2]), set1_128(IV[3]),\n        counter_low_vec, counter_high_vec, block_len_vec,   block_flags_vec,\n    };\n    round_fn4(v, msg_vecs, 0);\n    round_fn4(v, msg_vecs, 1);\n    round_fn4(v, msg_vecs, 2);\n    round_fn4(v, msg_vecs, 3);\n    round_fn4(v, msg_vecs, 4);\n    round_fn4(v, msg_vecs, 5);\n    round_fn4(v, msg_vecs, 6);\n    h_vecs[0] = xor_128(v[0], v[8]);\n    h_vecs[1] = xor_128(v[1], v[9]);\n    h_vecs[2] = xor_128(v[2], v[10]);\n    h_vecs[3] = xor_128(v[3], v[11]);\n    h_vecs[4] = xor_128(v[4], v[12]);\n    h_vecs[5] = xor_128(v[5], v[13]);\n    h_vecs[6] = xor_128(v[6], v[14]);\n    h_vecs[7] = xor_128(v[7], v[15]);\n\n    block_flags = flags;\n  }\n\n  transpose_vecs_128(&h_vecs[0]);\n  transpose_vecs_128(&h_vecs[4]);\n  // The first four vecs now contain the first half of each output, and the\n  // second four vecs contain the second half of each output.\n  storeu_128(h_vecs[0], &out[0 * sizeof(uint32x4_t)]);\n  storeu_128(h_vecs[4], &out[1 * sizeof(uint32x4_t)]);\n  storeu_128(h_vecs[1], &out[2 * sizeof(uint32x4_t)]);\n  storeu_128(h_vecs[5], &out[3 * sizeof(uint32x4_t)]);\n  storeu_128(h_vecs[2], &out[4 * sizeof(uint32x4_t)]);\n  storeu_128(h_vecs[6], &out[5 * sizeof(uint32x4_t)]);\n  storeu_128(h_vecs[3], &out[6 * sizeof(uint32x4_t)]);\n  storeu_128(h_vecs[7], &out[7 * sizeof(uint32x4_t)]);\n}\n\n/*\n * ----------------------------------------------------------------------------\n * hash_many_neon\n * ----------------------------------------------------------------------------\n */\n\nvoid blake3_compress_in_place_portable(uint32_t cv[8],\n                                       const uint8_t block[BLAKE3_BLOCK_LEN],\n                                       uint8_t block_len, uint64_t counter,\n                                       uint8_t flags);\n\nINLINE void hash_one_neon(const uint8_t *input, size_t blocks,\n                          const uint32_t key[8], uint64_t counter,\n                          uint8_t flags, uint8_t flags_start, uint8_t flags_end,\n                          uint8_t out[BLAKE3_OUT_LEN]) {\n  uint32_t cv[8];\n  memcpy(cv, key, BLAKE3_KEY_LEN);\n  uint8_t block_flags = flags | flags_start;\n  while (blocks > 0) {\n    if (blocks == 1) {\n      block_flags |= flags_end;\n    }\n    // TODO: Implement compress_neon. However note that according to\n    // https://github.com/BLAKE2/BLAKE2/commit/7965d3e6e1b4193438b8d3a656787587d2579227,\n    // compress_neon might not be any faster than compress_portable.\n    blake3_compress_in_place_portable(cv, input, BLAKE3_BLOCK_LEN, counter,\n                                      block_flags);\n    input = &input[BLAKE3_BLOCK_LEN];\n    blocks -= 1;\n    block_flags = flags;\n  }\n  memcpy(out, cv, BLAKE3_OUT_LEN);\n}\n\nvoid blake3_hash_many_neon(const uint8_t *const *inputs, size_t num_inputs,\n                           size_t blocks, const uint32_t key[8],\n                           uint64_t counter, bool increment_counter,\n                           uint8_t flags, uint8_t flags_start,\n                           uint8_t flags_end, uint8_t *out) {\n  while (num_inputs >= 4) {\n    blake3_hash4_neon(inputs, blocks, key, counter, increment_counter, flags,\n                      flags_start, flags_end, out);\n    if (increment_counter) {\n      counter += 4;\n    }\n    inputs += 4;\n    num_inputs -= 4;\n    out = &out[4 * BLAKE3_OUT_LEN];\n  }\n  while (num_inputs > 0) {\n    hash_one_neon(inputs[0], blocks, key, counter, flags, flags_start,\n                  flags_end, out);\n    if (increment_counter) {\n      counter += 1;\n    }\n    inputs += 1;\n    num_inputs -= 1;\n    out = &out[BLAKE3_OUT_LEN];\n  }\n}\n"
  },
  {
    "path": "common/blake3/blake3_portable.c",
    "content": "#include \"blake3_impl.h\"\n#include <string.h>\n\nINLINE uint32_t rotr32(uint32_t w, uint32_t c) {\n  return (w >> c) | (w << (32 - c));\n}\n\nINLINE void g(uint32_t *state, size_t a, size_t b, size_t c, size_t d,\n              uint32_t x, uint32_t y) {\n  state[a] = state[a] + state[b] + x;\n  state[d] = rotr32(state[d] ^ state[a], 16);\n  state[c] = state[c] + state[d];\n  state[b] = rotr32(state[b] ^ state[c], 12);\n  state[a] = state[a] + state[b] + y;\n  state[d] = rotr32(state[d] ^ state[a], 8);\n  state[c] = state[c] + state[d];\n  state[b] = rotr32(state[b] ^ state[c], 7);\n}\n\nINLINE void round_fn(uint32_t state[16], const uint32_t *msg, size_t round) {\n  // Select the message schedule based on the round.\n  const uint8_t *schedule = MSG_SCHEDULE[round];\n\n  // Mix the columns.\n  g(state, 0, 4, 8, 12, msg[schedule[0]], msg[schedule[1]]);\n  g(state, 1, 5, 9, 13, msg[schedule[2]], msg[schedule[3]]);\n  g(state, 2, 6, 10, 14, msg[schedule[4]], msg[schedule[5]]);\n  g(state, 3, 7, 11, 15, msg[schedule[6]], msg[schedule[7]]);\n\n  // Mix the rows.\n  g(state, 0, 5, 10, 15, msg[schedule[8]], msg[schedule[9]]);\n  g(state, 1, 6, 11, 12, msg[schedule[10]], msg[schedule[11]]);\n  g(state, 2, 7, 8, 13, msg[schedule[12]], msg[schedule[13]]);\n  g(state, 3, 4, 9, 14, msg[schedule[14]], msg[schedule[15]]);\n}\n\nINLINE void compress_pre(uint32_t state[16], const uint32_t cv[8],\n                         const uint8_t block[BLAKE3_BLOCK_LEN],\n                         uint8_t block_len, uint64_t counter, uint8_t flags) {\n  uint32_t block_words[16];\n  block_words[0] = load32(block + 4 * 0);\n  block_words[1] = load32(block + 4 * 1);\n  block_words[2] = load32(block + 4 * 2);\n  block_words[3] = load32(block + 4 * 3);\n  block_words[4] = load32(block + 4 * 4);\n  block_words[5] = load32(block + 4 * 5);\n  block_words[6] = load32(block + 4 * 6);\n  block_words[7] = load32(block + 4 * 7);\n  block_words[8] = load32(block + 4 * 8);\n  block_words[9] = load32(block + 4 * 9);\n  block_words[10] = load32(block + 4 * 10);\n  block_words[11] = load32(block + 4 * 11);\n  block_words[12] = load32(block + 4 * 12);\n  block_words[13] = load32(block + 4 * 13);\n  block_words[14] = load32(block + 4 * 14);\n  block_words[15] = load32(block + 4 * 15);\n\n  state[0] = cv[0];\n  state[1] = cv[1];\n  state[2] = cv[2];\n  state[3] = cv[3];\n  state[4] = cv[4];\n  state[5] = cv[5];\n  state[6] = cv[6];\n  state[7] = cv[7];\n  state[8] = IV[0];\n  state[9] = IV[1];\n  state[10] = IV[2];\n  state[11] = IV[3];\n  state[12] = counter_low(counter);\n  state[13] = counter_high(counter);\n  state[14] = (uint32_t)block_len;\n  state[15] = (uint32_t)flags;\n\n  round_fn(state, &block_words[0], 0);\n  round_fn(state, &block_words[0], 1);\n  round_fn(state, &block_words[0], 2);\n  round_fn(state, &block_words[0], 3);\n  round_fn(state, &block_words[0], 4);\n  round_fn(state, &block_words[0], 5);\n  round_fn(state, &block_words[0], 6);\n}\n\nvoid blake3_compress_in_place_portable(uint32_t cv[8],\n                                       const uint8_t block[BLAKE3_BLOCK_LEN],\n                                       uint8_t block_len, uint64_t counter,\n                                       uint8_t flags) {\n  uint32_t state[16];\n  compress_pre(state, cv, block, block_len, counter, flags);\n  cv[0] = state[0] ^ state[8];\n  cv[1] = state[1] ^ state[9];\n  cv[2] = state[2] ^ state[10];\n  cv[3] = state[3] ^ state[11];\n  cv[4] = state[4] ^ state[12];\n  cv[5] = state[5] ^ state[13];\n  cv[6] = state[6] ^ state[14];\n  cv[7] = state[7] ^ state[15];\n}\n\nvoid blake3_compress_xof_portable(const uint32_t cv[8],\n                                  const uint8_t block[BLAKE3_BLOCK_LEN],\n                                  uint8_t block_len, uint64_t counter,\n                                  uint8_t flags, uint8_t out[64]) {\n  uint32_t state[16];\n  compress_pre(state, cv, block, block_len, counter, flags);\n\n  store32(&out[0 * 4], state[0] ^ state[8]);\n  store32(&out[1 * 4], state[1] ^ state[9]);\n  store32(&out[2 * 4], state[2] ^ state[10]);\n  store32(&out[3 * 4], state[3] ^ state[11]);\n  store32(&out[4 * 4], state[4] ^ state[12]);\n  store32(&out[5 * 4], state[5] ^ state[13]);\n  store32(&out[6 * 4], state[6] ^ state[14]);\n  store32(&out[7 * 4], state[7] ^ state[15]);\n  store32(&out[8 * 4], state[8] ^ cv[0]);\n  store32(&out[9 * 4], state[9] ^ cv[1]);\n  store32(&out[10 * 4], state[10] ^ cv[2]);\n  store32(&out[11 * 4], state[11] ^ cv[3]);\n  store32(&out[12 * 4], state[12] ^ cv[4]);\n  store32(&out[13 * 4], state[13] ^ cv[5]);\n  store32(&out[14 * 4], state[14] ^ cv[6]);\n  store32(&out[15 * 4], state[15] ^ cv[7]);\n}\n\nINLINE void hash_one_portable(const uint8_t *input, size_t blocks,\n                              const uint32_t key[8], uint64_t counter,\n                              uint8_t flags, uint8_t flags_start,\n                              uint8_t flags_end, uint8_t out[BLAKE3_OUT_LEN]) {\n  uint32_t cv[8];\n  memcpy(cv, key, BLAKE3_KEY_LEN);\n  uint8_t block_flags = flags | flags_start;\n  while (blocks > 0) {\n    if (blocks == 1) {\n      block_flags |= flags_end;\n    }\n    blake3_compress_in_place_portable(cv, input, BLAKE3_BLOCK_LEN, counter,\n                                      block_flags);\n    input = &input[BLAKE3_BLOCK_LEN];\n    blocks -= 1;\n    block_flags = flags;\n  }\n  store_cv_words(out, cv);\n}\n\nvoid blake3_hash_many_portable(const uint8_t *const *inputs, size_t num_inputs,\n                               size_t blocks, const uint32_t key[8],\n                               uint64_t counter, bool increment_counter,\n                               uint8_t flags, uint8_t flags_start,\n                               uint8_t flags_end, uint8_t *out) {\n  while (num_inputs > 0) {\n    hash_one_portable(inputs[0], blocks, key, counter, flags, flags_start,\n                      flags_end, out);\n    if (increment_counter) {\n      counter += 1;\n    }\n    inputs += 1;\n    num_inputs -= 1;\n    out = &out[BLAKE3_OUT_LEN];\n  }\n}\n"
  },
  {
    "path": "common/blake3/blake3_sse2.c",
    "content": "#include \"blake3_impl.h\"\n\n#include <immintrin.h>\n\n#define DEGREE 4\n\n#define _mm_shuffle_ps2(a, b, c)                                               \\\n  (_mm_castps_si128(                                                           \\\n      _mm_shuffle_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b), (c))))\n\nINLINE __m128i loadu(const uint8_t src[16]) {\n  return _mm_loadu_si128((const __m128i *)src);\n}\n\nINLINE void storeu(__m128i src, uint8_t dest[16]) {\n  _mm_storeu_si128((__m128i *)dest, src);\n}\n\nINLINE __m128i addv(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n\n// Note that clang-format doesn't like the name \"xor\" for some reason.\nINLINE __m128i xorv(__m128i a, __m128i b) { return _mm_xor_si128(a, b); }\n\nINLINE __m128i set1(uint32_t x) { return _mm_set1_epi32((int32_t)x); }\n\nINLINE __m128i set4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {\n  return _mm_setr_epi32((int32_t)a, (int32_t)b, (int32_t)c, (int32_t)d);\n}\n\nINLINE __m128i rot16(__m128i x) {\n  return _mm_shufflehi_epi16(_mm_shufflelo_epi16(x, 0xB1), 0xB1);\n}\n\nINLINE __m128i rot12(__m128i x) {\n  return xorv(_mm_srli_epi32(x, 12), _mm_slli_epi32(x, 32 - 12));\n}\n\nINLINE __m128i rot8(__m128i x) {\n  return xorv(_mm_srli_epi32(x, 8), _mm_slli_epi32(x, 32 - 8));\n}\n\nINLINE __m128i rot7(__m128i x) {\n  return xorv(_mm_srli_epi32(x, 7), _mm_slli_epi32(x, 32 - 7));\n}\n\nINLINE void g1(__m128i *row0, __m128i *row1, __m128i *row2, __m128i *row3,\n               __m128i m) {\n  *row0 = addv(addv(*row0, m), *row1);\n  *row3 = xorv(*row3, *row0);\n  *row3 = rot16(*row3);\n  *row2 = addv(*row2, *row3);\n  *row1 = xorv(*row1, *row2);\n  *row1 = rot12(*row1);\n}\n\nINLINE void g2(__m128i *row0, __m128i *row1, __m128i *row2, __m128i *row3,\n               __m128i m) {\n  *row0 = addv(addv(*row0, m), *row1);\n  *row3 = xorv(*row3, *row0);\n  *row3 = rot8(*row3);\n  *row2 = addv(*row2, *row3);\n  *row1 = xorv(*row1, *row2);\n  *row1 = rot7(*row1);\n}\n\n// Note the optimization here of leaving row1 as the unrotated row, rather than\n// row0. All the message loads below are adjusted to compensate for this. See\n// discussion at https://github.com/sneves/blake2-avx2/pull/4\nINLINE void diagonalize(__m128i *row0, __m128i *row2, __m128i *row3) {\n  *row0 = _mm_shuffle_epi32(*row0, _MM_SHUFFLE(2, 1, 0, 3));\n  *row3 = _mm_shuffle_epi32(*row3, _MM_SHUFFLE(1, 0, 3, 2));\n  *row2 = _mm_shuffle_epi32(*row2, _MM_SHUFFLE(0, 3, 2, 1));\n}\n\nINLINE void undiagonalize(__m128i *row0, __m128i *row2, __m128i *row3) {\n  *row0 = _mm_shuffle_epi32(*row0, _MM_SHUFFLE(0, 3, 2, 1));\n  *row3 = _mm_shuffle_epi32(*row3, _MM_SHUFFLE(1, 0, 3, 2));\n  *row2 = _mm_shuffle_epi32(*row2, _MM_SHUFFLE(2, 1, 0, 3));\n}\n\nINLINE __m128i blend_epi16(__m128i a, __m128i b, const int imm8) {\n  const __m128i bits = _mm_set_epi16(0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01);\n  __m128i mask = _mm_set1_epi16(imm8);\n  mask = _mm_and_si128(mask, bits);\n  mask = _mm_cmpeq_epi16(mask, bits);\n  return _mm_or_si128(_mm_and_si128(mask, b), _mm_andnot_si128(mask, a));\n}\n\nINLINE void compress_pre(__m128i rows[4], const uint32_t cv[8],\n                         const uint8_t block[BLAKE3_BLOCK_LEN],\n                         uint8_t block_len, uint64_t counter, uint8_t flags) {\n  rows[0] = loadu((uint8_t *)&cv[0]);\n  rows[1] = loadu((uint8_t *)&cv[4]);\n  rows[2] = set4(IV[0], IV[1], IV[2], IV[3]);\n  rows[3] = set4(counter_low(counter), counter_high(counter),\n                 (uint32_t)block_len, (uint32_t)flags);\n\n  __m128i m0 = loadu(&block[sizeof(__m128i) * 0]);\n  __m128i m1 = loadu(&block[sizeof(__m128i) * 1]);\n  __m128i m2 = loadu(&block[sizeof(__m128i) * 2]);\n  __m128i m3 = loadu(&block[sizeof(__m128i) * 3]);\n\n  __m128i t0, t1, t2, t3, tt;\n\n  // Round 1. The first round permutes the message words from the original\n  // input order, into the groups that get mixed in parallel.\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(2, 0, 2, 0)); //  6  4  2  0\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 3, 1)); //  7  5  3  1\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(2, 0, 2, 0)); // 14 12 10  8\n  t2 = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2, 1, 0, 3));   // 12 10  8 14\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 1, 3, 1)); // 15 13 11  9\n  t3 = _mm_shuffle_epi32(t3, _MM_SHUFFLE(2, 1, 0, 3));   // 13 11  9 15\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 2. This round and all following rounds apply a fixed permutation\n  // to the message words from the round before.\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 3\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 4\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 5\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 6\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 7\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n}\n\nvoid blake3_compress_in_place_sse2(uint32_t cv[8],\n                                   const uint8_t block[BLAKE3_BLOCK_LEN],\n                                   uint8_t block_len, uint64_t counter,\n                                   uint8_t flags) {\n  __m128i rows[4];\n  compress_pre(rows, cv, block, block_len, counter, flags);\n  storeu(xorv(rows[0], rows[2]), (uint8_t *)&cv[0]);\n  storeu(xorv(rows[1], rows[3]), (uint8_t *)&cv[4]);\n}\n\nvoid blake3_compress_xof_sse2(const uint32_t cv[8],\n                              const uint8_t block[BLAKE3_BLOCK_LEN],\n                              uint8_t block_len, uint64_t counter,\n                              uint8_t flags, uint8_t out[64]) {\n  __m128i rows[4];\n  compress_pre(rows, cv, block, block_len, counter, flags);\n  storeu(xorv(rows[0], rows[2]), &out[0]);\n  storeu(xorv(rows[1], rows[3]), &out[16]);\n  storeu(xorv(rows[2], loadu((uint8_t *)&cv[0])), &out[32]);\n  storeu(xorv(rows[3], loadu((uint8_t *)&cv[4])), &out[48]);\n}\n\nINLINE void round_fn(__m128i v[16], __m128i m[16], size_t r) {\n  v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][0]]);\n  v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][2]]);\n  v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][4]]);\n  v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][6]]);\n  v[0] = addv(v[0], v[4]);\n  v[1] = addv(v[1], v[5]);\n  v[2] = addv(v[2], v[6]);\n  v[3] = addv(v[3], v[7]);\n  v[12] = xorv(v[12], v[0]);\n  v[13] = xorv(v[13], v[1]);\n  v[14] = xorv(v[14], v[2]);\n  v[15] = xorv(v[15], v[3]);\n  v[12] = rot16(v[12]);\n  v[13] = rot16(v[13]);\n  v[14] = rot16(v[14]);\n  v[15] = rot16(v[15]);\n  v[8] = addv(v[8], v[12]);\n  v[9] = addv(v[9], v[13]);\n  v[10] = addv(v[10], v[14]);\n  v[11] = addv(v[11], v[15]);\n  v[4] = xorv(v[4], v[8]);\n  v[5] = xorv(v[5], v[9]);\n  v[6] = xorv(v[6], v[10]);\n  v[7] = xorv(v[7], v[11]);\n  v[4] = rot12(v[4]);\n  v[5] = rot12(v[5]);\n  v[6] = rot12(v[6]);\n  v[7] = rot12(v[7]);\n  v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][1]]);\n  v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][3]]);\n  v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][5]]);\n  v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][7]]);\n  v[0] = addv(v[0], v[4]);\n  v[1] = addv(v[1], v[5]);\n  v[2] = addv(v[2], v[6]);\n  v[3] = addv(v[3], v[7]);\n  v[12] = xorv(v[12], v[0]);\n  v[13] = xorv(v[13], v[1]);\n  v[14] = xorv(v[14], v[2]);\n  v[15] = xorv(v[15], v[3]);\n  v[12] = rot8(v[12]);\n  v[13] = rot8(v[13]);\n  v[14] = rot8(v[14]);\n  v[15] = rot8(v[15]);\n  v[8] = addv(v[8], v[12]);\n  v[9] = addv(v[9], v[13]);\n  v[10] = addv(v[10], v[14]);\n  v[11] = addv(v[11], v[15]);\n  v[4] = xorv(v[4], v[8]);\n  v[5] = xorv(v[5], v[9]);\n  v[6] = xorv(v[6], v[10]);\n  v[7] = xorv(v[7], v[11]);\n  v[4] = rot7(v[4]);\n  v[5] = rot7(v[5]);\n  v[6] = rot7(v[6]);\n  v[7] = rot7(v[7]);\n\n  v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][8]]);\n  v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][10]]);\n  v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][12]]);\n  v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][14]]);\n  v[0] = addv(v[0], v[5]);\n  v[1] = addv(v[1], v[6]);\n  v[2] = addv(v[2], v[7]);\n  v[3] = addv(v[3], v[4]);\n  v[15] = xorv(v[15], v[0]);\n  v[12] = xorv(v[12], v[1]);\n  v[13] = xorv(v[13], v[2]);\n  v[14] = xorv(v[14], v[3]);\n  v[15] = rot16(v[15]);\n  v[12] = rot16(v[12]);\n  v[13] = rot16(v[13]);\n  v[14] = rot16(v[14]);\n  v[10] = addv(v[10], v[15]);\n  v[11] = addv(v[11], v[12]);\n  v[8] = addv(v[8], v[13]);\n  v[9] = addv(v[9], v[14]);\n  v[5] = xorv(v[5], v[10]);\n  v[6] = xorv(v[6], v[11]);\n  v[7] = xorv(v[7], v[8]);\n  v[4] = xorv(v[4], v[9]);\n  v[5] = rot12(v[5]);\n  v[6] = rot12(v[6]);\n  v[7] = rot12(v[7]);\n  v[4] = rot12(v[4]);\n  v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][9]]);\n  v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][11]]);\n  v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][13]]);\n  v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][15]]);\n  v[0] = addv(v[0], v[5]);\n  v[1] = addv(v[1], v[6]);\n  v[2] = addv(v[2], v[7]);\n  v[3] = addv(v[3], v[4]);\n  v[15] = xorv(v[15], v[0]);\n  v[12] = xorv(v[12], v[1]);\n  v[13] = xorv(v[13], v[2]);\n  v[14] = xorv(v[14], v[3]);\n  v[15] = rot8(v[15]);\n  v[12] = rot8(v[12]);\n  v[13] = rot8(v[13]);\n  v[14] = rot8(v[14]);\n  v[10] = addv(v[10], v[15]);\n  v[11] = addv(v[11], v[12]);\n  v[8] = addv(v[8], v[13]);\n  v[9] = addv(v[9], v[14]);\n  v[5] = xorv(v[5], v[10]);\n  v[6] = xorv(v[6], v[11]);\n  v[7] = xorv(v[7], v[8]);\n  v[4] = xorv(v[4], v[9]);\n  v[5] = rot7(v[5]);\n  v[6] = rot7(v[6]);\n  v[7] = rot7(v[7]);\n  v[4] = rot7(v[4]);\n}\n\nINLINE void transpose_vecs(__m128i vecs[DEGREE]) {\n  // Interleave 32-bit lates. The low unpack is lanes 00/11 and the high is\n  // 22/33. Note that this doesn't split the vector into two lanes, as the\n  // AVX2 counterparts do.\n  __m128i ab_01 = _mm_unpacklo_epi32(vecs[0], vecs[1]);\n  __m128i ab_23 = _mm_unpackhi_epi32(vecs[0], vecs[1]);\n  __m128i cd_01 = _mm_unpacklo_epi32(vecs[2], vecs[3]);\n  __m128i cd_23 = _mm_unpackhi_epi32(vecs[2], vecs[3]);\n\n  // Interleave 64-bit lanes.\n  __m128i abcd_0 = _mm_unpacklo_epi64(ab_01, cd_01);\n  __m128i abcd_1 = _mm_unpackhi_epi64(ab_01, cd_01);\n  __m128i abcd_2 = _mm_unpacklo_epi64(ab_23, cd_23);\n  __m128i abcd_3 = _mm_unpackhi_epi64(ab_23, cd_23);\n\n  vecs[0] = abcd_0;\n  vecs[1] = abcd_1;\n  vecs[2] = abcd_2;\n  vecs[3] = abcd_3;\n}\n\nINLINE void transpose_msg_vecs(const uint8_t *const *inputs,\n                               size_t block_offset, __m128i out[16]) {\n  out[0] = loadu(&inputs[0][block_offset + 0 * sizeof(__m128i)]);\n  out[1] = loadu(&inputs[1][block_offset + 0 * sizeof(__m128i)]);\n  out[2] = loadu(&inputs[2][block_offset + 0 * sizeof(__m128i)]);\n  out[3] = loadu(&inputs[3][block_offset + 0 * sizeof(__m128i)]);\n  out[4] = loadu(&inputs[0][block_offset + 1 * sizeof(__m128i)]);\n  out[5] = loadu(&inputs[1][block_offset + 1 * sizeof(__m128i)]);\n  out[6] = loadu(&inputs[2][block_offset + 1 * sizeof(__m128i)]);\n  out[7] = loadu(&inputs[3][block_offset + 1 * sizeof(__m128i)]);\n  out[8] = loadu(&inputs[0][block_offset + 2 * sizeof(__m128i)]);\n  out[9] = loadu(&inputs[1][block_offset + 2 * sizeof(__m128i)]);\n  out[10] = loadu(&inputs[2][block_offset + 2 * sizeof(__m128i)]);\n  out[11] = loadu(&inputs[3][block_offset + 2 * sizeof(__m128i)]);\n  out[12] = loadu(&inputs[0][block_offset + 3 * sizeof(__m128i)]);\n  out[13] = loadu(&inputs[1][block_offset + 3 * sizeof(__m128i)]);\n  out[14] = loadu(&inputs[2][block_offset + 3 * sizeof(__m128i)]);\n  out[15] = loadu(&inputs[3][block_offset + 3 * sizeof(__m128i)]);\n  for (size_t i = 0; i < 4; ++i) {\n    _mm_prefetch(&inputs[i][block_offset + 256], _MM_HINT_T0);\n  }\n  transpose_vecs(&out[0]);\n  transpose_vecs(&out[4]);\n  transpose_vecs(&out[8]);\n  transpose_vecs(&out[12]);\n}\n\nINLINE void load_counters(uint64_t counter, bool increment_counter,\n                          __m128i *out_lo, __m128i *out_hi) {\n  const __m128i mask = _mm_set1_epi32(-(int32_t)increment_counter);\n  const __m128i add0 = _mm_set_epi32(3, 2, 1, 0);\n  const __m128i add1 = _mm_and_si128(mask, add0);\n  __m128i l = _mm_add_epi32(_mm_set1_epi32(counter), add1);\n  __m128i carry = _mm_cmpgt_epi32(_mm_xor_si128(add1, _mm_set1_epi32(0x80000000)),\n                                  _mm_xor_si128(   l, _mm_set1_epi32(0x80000000)));\n  __m128i h = _mm_sub_epi32(_mm_set1_epi32(counter >> 32), carry);\n  *out_lo = l;\n  *out_hi = h;\n}\n\nvoid blake3_hash4_sse2(const uint8_t *const *inputs, size_t blocks,\n                       const uint32_t key[8], uint64_t counter,\n                       bool increment_counter, uint8_t flags,\n                       uint8_t flags_start, uint8_t flags_end, uint8_t *out) {\n  __m128i h_vecs[8] = {\n      set1(key[0]), set1(key[1]), set1(key[2]), set1(key[3]),\n      set1(key[4]), set1(key[5]), set1(key[6]), set1(key[7]),\n  };\n  __m128i counter_low_vec, counter_high_vec;\n  load_counters(counter, increment_counter, &counter_low_vec,\n                &counter_high_vec);\n  uint8_t block_flags = flags | flags_start;\n\n  for (size_t block = 0; block < blocks; block++) {\n    if (block + 1 == blocks) {\n      block_flags |= flags_end;\n    }\n    __m128i block_len_vec = set1(BLAKE3_BLOCK_LEN);\n    __m128i block_flags_vec = set1(block_flags);\n    __m128i msg_vecs[16];\n    transpose_msg_vecs(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs);\n\n    __m128i v[16] = {\n        h_vecs[0],       h_vecs[1],        h_vecs[2],     h_vecs[3],\n        h_vecs[4],       h_vecs[5],        h_vecs[6],     h_vecs[7],\n        set1(IV[0]),     set1(IV[1]),      set1(IV[2]),   set1(IV[3]),\n        counter_low_vec, counter_high_vec, block_len_vec, block_flags_vec,\n    };\n    round_fn(v, msg_vecs, 0);\n    round_fn(v, msg_vecs, 1);\n    round_fn(v, msg_vecs, 2);\n    round_fn(v, msg_vecs, 3);\n    round_fn(v, msg_vecs, 4);\n    round_fn(v, msg_vecs, 5);\n    round_fn(v, msg_vecs, 6);\n    h_vecs[0] = xorv(v[0], v[8]);\n    h_vecs[1] = xorv(v[1], v[9]);\n    h_vecs[2] = xorv(v[2], v[10]);\n    h_vecs[3] = xorv(v[3], v[11]);\n    h_vecs[4] = xorv(v[4], v[12]);\n    h_vecs[5] = xorv(v[5], v[13]);\n    h_vecs[6] = xorv(v[6], v[14]);\n    h_vecs[7] = xorv(v[7], v[15]);\n\n    block_flags = flags;\n  }\n\n  transpose_vecs(&h_vecs[0]);\n  transpose_vecs(&h_vecs[4]);\n  // The first four vecs now contain the first half of each output, and the\n  // second four vecs contain the second half of each output.\n  storeu(h_vecs[0], &out[0 * sizeof(__m128i)]);\n  storeu(h_vecs[4], &out[1 * sizeof(__m128i)]);\n  storeu(h_vecs[1], &out[2 * sizeof(__m128i)]);\n  storeu(h_vecs[5], &out[3 * sizeof(__m128i)]);\n  storeu(h_vecs[2], &out[4 * sizeof(__m128i)]);\n  storeu(h_vecs[6], &out[5 * sizeof(__m128i)]);\n  storeu(h_vecs[3], &out[6 * sizeof(__m128i)]);\n  storeu(h_vecs[7], &out[7 * sizeof(__m128i)]);\n}\n\nINLINE void hash_one_sse2(const uint8_t *input, size_t blocks,\n                          const uint32_t key[8], uint64_t counter,\n                          uint8_t flags, uint8_t flags_start,\n                          uint8_t flags_end, uint8_t out[BLAKE3_OUT_LEN]) {\n  uint32_t cv[8];\n  memcpy(cv, key, BLAKE3_KEY_LEN);\n  uint8_t block_flags = flags | flags_start;\n  while (blocks > 0) {\n    if (blocks == 1) {\n      block_flags |= flags_end;\n    }\n    blake3_compress_in_place_sse2(cv, input, BLAKE3_BLOCK_LEN, counter,\n                                  block_flags);\n    input = &input[BLAKE3_BLOCK_LEN];\n    blocks -= 1;\n    block_flags = flags;\n  }\n  memcpy(out, cv, BLAKE3_OUT_LEN);\n}\n\nvoid blake3_hash_many_sse2(const uint8_t *const *inputs, size_t num_inputs,\n                           size_t blocks, const uint32_t key[8],\n                           uint64_t counter, bool increment_counter,\n                           uint8_t flags, uint8_t flags_start,\n                           uint8_t flags_end, uint8_t *out) {\n  while (num_inputs >= DEGREE) {\n    blake3_hash4_sse2(inputs, blocks, key, counter, increment_counter, flags,\n                      flags_start, flags_end, out);\n    if (increment_counter) {\n      counter += DEGREE;\n    }\n    inputs += DEGREE;\n    num_inputs -= DEGREE;\n    out = &out[DEGREE * BLAKE3_OUT_LEN];\n  }\n  while (num_inputs > 0) {\n    hash_one_sse2(inputs[0], blocks, key, counter, flags, flags_start,\n                  flags_end, out);\n    if (increment_counter) {\n      counter += 1;\n    }\n    inputs += 1;\n    num_inputs -= 1;\n    out = &out[BLAKE3_OUT_LEN];\n  }\n}\n"
  },
  {
    "path": "common/blake3/blake3_sse2_x86-64_unix.S",
    "content": "#if defined(__ELF__) && defined(__linux__)\n.section .note.GNU-stack,\"\",%progbits\n#endif\n\n#if defined(__ELF__) && defined(__CET__) && defined(__has_include)\n#if __has_include(<cet.h>)\n#include <cet.h>\n#endif\n#endif\n\n#if !defined(_CET_ENDBR)\n#define _CET_ENDBR\n#endif\n\n.intel_syntax noprefix\n.global blake3_hash_many_sse2\n.global _blake3_hash_many_sse2\n.global blake3_compress_in_place_sse2\n.global _blake3_compress_in_place_sse2\n.global blake3_compress_xof_sse2\n.global _blake3_compress_xof_sse2\n#ifdef __APPLE__\n.text\n#else\n.section .text\n#endif\n        .p2align  6\n_blake3_hash_many_sse2:\nblake3_hash_many_sse2:\n        _CET_ENDBR\n        push    r15\n        push    r14\n        push    r13\n        push    r12\n        push    rbx\n        push    rbp\n        mov     rbp, rsp\n        sub     rsp, 360\n        and     rsp, 0xFFFFFFFFFFFFFFC0\n        neg     r9d\n        movd    xmm0, r9d\n        pshufd  xmm0, xmm0, 0x00\n        movdqa  xmmword ptr [rsp+0x130], xmm0\n        movdqa  xmm1, xmm0\n        pand    xmm1, xmmword ptr [ADD0+rip]\n        pand    xmm0, xmmword ptr [ADD1+rip]\n        movdqa  xmmword ptr [rsp+0x150], xmm0\n        movd    xmm0, r8d\n        pshufd  xmm0, xmm0, 0x00\n        paddd   xmm0, xmm1\n        movdqa  xmmword ptr [rsp+0x110], xmm0\n        pxor    xmm0, xmmword ptr [CMP_MSB_MASK+rip]\n        pxor    xmm1, xmmword ptr [CMP_MSB_MASK+rip]\n        pcmpgtd xmm1, xmm0\n        shr     r8, 32\n        movd    xmm2, r8d\n        pshufd  xmm2, xmm2, 0x00\n        psubd   xmm2, xmm1\n        movdqa  xmmword ptr [rsp+0x120], xmm2\n        mov     rbx, qword ptr [rbp+0x50]\n        mov     r15, rdx\n        shl     r15, 6\n        movzx   r13d, byte ptr [rbp+0x38]\n        movzx   r12d, byte ptr [rbp+0x48]\n        cmp     rsi, 4\n        jc      3f\n2:\n        movdqu  xmm3, xmmword ptr [rcx]\n        pshufd  xmm0, xmm3, 0x00\n        pshufd  xmm1, xmm3, 0x55\n        pshufd  xmm2, xmm3, 0xAA\n        pshufd  xmm3, xmm3, 0xFF\n        movdqu  xmm7, xmmword ptr [rcx+0x10]\n        pshufd  xmm4, xmm7, 0x00\n        pshufd  xmm5, xmm7, 0x55\n        pshufd  xmm6, xmm7, 0xAA\n        pshufd  xmm7, xmm7, 0xFF\n        mov     r8, qword ptr [rdi]\n        mov     r9, qword ptr [rdi+0x8]\n        mov     r10, qword ptr [rdi+0x10]\n        mov     r11, qword ptr [rdi+0x18]\n        movzx   eax, byte ptr [rbp+0x40]\n        or      eax, r13d\n        xor     edx, edx\n9:\n        mov     r14d, eax\n        or      eax, r12d\n        add     rdx, 64\n        cmp     rdx, r15\n        cmovne  eax, r14d\n        movdqu  xmm8, xmmword ptr [r8+rdx-0x40]\n        movdqu  xmm9, xmmword ptr [r9+rdx-0x40]\n        movdqu  xmm10, xmmword ptr [r10+rdx-0x40]\n        movdqu  xmm11, xmmword ptr [r11+rdx-0x40]\n        movdqa  xmm12, xmm8\n        punpckldq xmm8, xmm9\n        punpckhdq xmm12, xmm9\n        movdqa  xmm14, xmm10\n        punpckldq xmm10, xmm11\n        punpckhdq xmm14, xmm11\n        movdqa  xmm9, xmm8\n        punpcklqdq xmm8, xmm10\n        punpckhqdq xmm9, xmm10\n        movdqa  xmm13, xmm12\n        punpcklqdq xmm12, xmm14\n        punpckhqdq xmm13, xmm14\n        movdqa  xmmword ptr [rsp], xmm8\n        movdqa  xmmword ptr [rsp+0x10], xmm9\n        movdqa  xmmword ptr [rsp+0x20], xmm12\n        movdqa  xmmword ptr [rsp+0x30], xmm13\n        movdqu  xmm8, xmmword ptr [r8+rdx-0x30]\n        movdqu  xmm9, xmmword ptr [r9+rdx-0x30]\n        movdqu  xmm10, xmmword ptr [r10+rdx-0x30]\n        movdqu  xmm11, xmmword ptr [r11+rdx-0x30]\n        movdqa  xmm12, xmm8\n        punpckldq xmm8, xmm9\n        punpckhdq xmm12, xmm9\n        movdqa  xmm14, xmm10\n        punpckldq xmm10, xmm11\n        punpckhdq xmm14, xmm11\n        movdqa  xmm9, xmm8\n        punpcklqdq xmm8, xmm10\n        punpckhqdq xmm9, xmm10\n        movdqa  xmm13, xmm12\n        punpcklqdq xmm12, xmm14\n        punpckhqdq xmm13, xmm14\n        movdqa  xmmword ptr [rsp+0x40], xmm8\n        movdqa  xmmword ptr [rsp+0x50], xmm9\n        movdqa  xmmword ptr [rsp+0x60], xmm12\n        movdqa  xmmword ptr [rsp+0x70], xmm13\n        movdqu  xmm8, xmmword ptr [r8+rdx-0x20]\n        movdqu  xmm9, xmmword ptr [r9+rdx-0x20]\n        movdqu  xmm10, xmmword ptr [r10+rdx-0x20]\n        movdqu  xmm11, xmmword ptr [r11+rdx-0x20]\n        movdqa  xmm12, xmm8\n        punpckldq xmm8, xmm9\n        punpckhdq xmm12, xmm9\n        movdqa  xmm14, xmm10\n        punpckldq xmm10, xmm11\n        punpckhdq xmm14, xmm11\n        movdqa  xmm9, xmm8\n        punpcklqdq xmm8, xmm10\n        punpckhqdq xmm9, xmm10\n        movdqa  xmm13, xmm12\n        punpcklqdq xmm12, xmm14\n        punpckhqdq xmm13, xmm14\n        movdqa  xmmword ptr [rsp+0x80], xmm8\n        movdqa  xmmword ptr [rsp+0x90], xmm9\n        movdqa  xmmword ptr [rsp+0xA0], xmm12\n        movdqa  xmmword ptr [rsp+0xB0], xmm13\n        movdqu  xmm8, xmmword ptr [r8+rdx-0x10]\n        movdqu  xmm9, xmmword ptr [r9+rdx-0x10]\n        movdqu  xmm10, xmmword ptr [r10+rdx-0x10]\n        movdqu  xmm11, xmmword ptr [r11+rdx-0x10]\n        movdqa  xmm12, xmm8\n        punpckldq xmm8, xmm9\n        punpckhdq xmm12, xmm9\n        movdqa  xmm14, xmm10\n        punpckldq xmm10, xmm11\n        punpckhdq xmm14, xmm11\n        movdqa  xmm9, xmm8\n        punpcklqdq xmm8, xmm10\n        punpckhqdq xmm9, xmm10\n        movdqa  xmm13, xmm12\n        punpcklqdq xmm12, xmm14\n        punpckhqdq xmm13, xmm14\n        movdqa  xmmword ptr [rsp+0xC0], xmm8\n        movdqa  xmmword ptr [rsp+0xD0], xmm9\n        movdqa  xmmword ptr [rsp+0xE0], xmm12\n        movdqa  xmmword ptr [rsp+0xF0], xmm13\n        movdqa  xmm9, xmmword ptr [BLAKE3_IV_1+rip]\n        movdqa  xmm10, xmmword ptr [BLAKE3_IV_2+rip]\n        movdqa  xmm11, xmmword ptr [BLAKE3_IV_3+rip]\n        movdqa  xmm12, xmmword ptr [rsp+0x110]\n        movdqa  xmm13, xmmword ptr [rsp+0x120]\n        movdqa  xmm14, xmmword ptr [BLAKE3_BLOCK_LEN+rip]\n        movd    xmm15, eax\n        pshufd  xmm15, xmm15, 0x00\n        prefetcht0 [r8+rdx+0x80]\n        prefetcht0 [r9+rdx+0x80]\n        prefetcht0 [r10+rdx+0x80]\n        prefetcht0 [r11+rdx+0x80]\n        paddd   xmm0, xmmword ptr [rsp]\n        paddd   xmm1, xmmword ptr [rsp+0x20]\n        paddd   xmm2, xmmword ptr [rsp+0x40]\n        paddd   xmm3, xmmword ptr [rsp+0x60]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        pshuflw xmm12, xmm12, 0xB1\n        pshufhw xmm12, xmm12, 0xB1\n        pshuflw xmm13, xmm13, 0xB1\n        pshufhw xmm13, xmm13, 0xB1\n        pshuflw xmm14, xmm14, 0xB1\n        pshufhw xmm14, xmm14, 0xB1\n        pshuflw xmm15, xmm15, 0xB1\n        pshufhw xmm15, xmm15, 0xB1\n        movdqa  xmm8, xmmword ptr [BLAKE3_IV_0+rip]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x10]\n        paddd   xmm1, xmmword ptr [rsp+0x30]\n        paddd   xmm2, xmmword ptr [rsp+0x50]\n        paddd   xmm3, xmmword ptr [rsp+0x70]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmm12\n        psrld   xmm12, 8\n        pslld   xmm8, 24\n        pxor    xmm12, xmm8\n        movdqa  xmm8, xmm13\n        psrld   xmm13, 8\n        pslld   xmm8, 24\n        pxor    xmm13, xmm8\n        movdqa  xmm8, xmm14\n        psrld   xmm14, 8\n        pslld   xmm8, 24\n        pxor    xmm14, xmm8\n        movdqa  xmm8, xmm15\n        psrld   xmm15, 8\n        pslld   xmm8, 24\n        pxor    xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x80]\n        paddd   xmm1, xmmword ptr [rsp+0xA0]\n        paddd   xmm2, xmmword ptr [rsp+0xC0]\n        paddd   xmm3, xmmword ptr [rsp+0xE0]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        pshuflw xmm15, xmm15, 0xB1\n        pshufhw xmm15, xmm15, 0xB1\n        pshuflw xmm12, xmm12, 0xB1\n        pshufhw xmm12, xmm12, 0xB1\n        pshuflw xmm13, xmm13, 0xB1\n        pshufhw xmm13, xmm13, 0xB1\n        pshuflw xmm14, xmm14, 0xB1\n        pshufhw xmm14, xmm14, 0xB1\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x90]\n        paddd   xmm1, xmmword ptr [rsp+0xB0]\n        paddd   xmm2, xmmword ptr [rsp+0xD0]\n        paddd   xmm3, xmmword ptr [rsp+0xF0]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmm15\n        psrld   xmm15, 8\n        pslld   xmm8, 24\n        pxor    xmm15, xmm8\n        movdqa  xmm8, xmm12\n        psrld   xmm12, 8\n        pslld   xmm8, 24\n        pxor    xmm12, xmm8\n        movdqa  xmm8, xmm13\n        psrld   xmm13, 8\n        pslld   xmm8, 24\n        pxor    xmm13, xmm8\n        movdqa  xmm8, xmm14\n        psrld   xmm14, 8\n        pslld   xmm8, 24\n        pxor    xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x20]\n        paddd   xmm1, xmmword ptr [rsp+0x30]\n        paddd   xmm2, xmmword ptr [rsp+0x70]\n        paddd   xmm3, xmmword ptr [rsp+0x40]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        pshuflw xmm12, xmm12, 0xB1\n        pshufhw xmm12, xmm12, 0xB1\n        pshuflw xmm13, xmm13, 0xB1\n        pshufhw xmm13, xmm13, 0xB1\n        pshuflw xmm14, xmm14, 0xB1\n        pshufhw xmm14, xmm14, 0xB1\n        pshuflw xmm15, xmm15, 0xB1\n        pshufhw xmm15, xmm15, 0xB1\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x60]\n        paddd   xmm1, xmmword ptr [rsp+0xA0]\n        paddd   xmm2, xmmword ptr [rsp]\n        paddd   xmm3, xmmword ptr [rsp+0xD0]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmm12\n        psrld   xmm12, 8\n        pslld   xmm8, 24\n        pxor    xmm12, xmm8\n        movdqa  xmm8, xmm13\n        psrld   xmm13, 8\n        pslld   xmm8, 24\n        pxor    xmm13, xmm8\n        movdqa  xmm8, xmm14\n        psrld   xmm14, 8\n        pslld   xmm8, 24\n        pxor    xmm14, xmm8\n        movdqa  xmm8, xmm15\n        psrld   xmm15, 8\n        pslld   xmm8, 24\n        pxor    xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x10]\n        paddd   xmm1, xmmword ptr [rsp+0xC0]\n        paddd   xmm2, xmmword ptr [rsp+0x90]\n        paddd   xmm3, xmmword ptr [rsp+0xF0]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        pshuflw xmm15, xmm15, 0xB1\n        pshufhw xmm15, xmm15, 0xB1\n        pshuflw xmm12, xmm12, 0xB1\n        pshufhw xmm12, xmm12, 0xB1\n        pshuflw xmm13, xmm13, 0xB1\n        pshufhw xmm13, xmm13, 0xB1\n        pshuflw xmm14, xmm14, 0xB1\n        pshufhw xmm14, xmm14, 0xB1\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xB0]\n        paddd   xmm1, xmmword ptr [rsp+0x50]\n        paddd   xmm2, xmmword ptr [rsp+0xE0]\n        paddd   xmm3, xmmword ptr [rsp+0x80]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmm15\n        psrld   xmm15, 8\n        pslld   xmm8, 24\n        pxor    xmm15, xmm8\n        movdqa  xmm8, xmm12\n        psrld   xmm12, 8\n        pslld   xmm8, 24\n        pxor    xmm12, xmm8\n        movdqa  xmm8, xmm13\n        psrld   xmm13, 8\n        pslld   xmm8, 24\n        pxor    xmm13, xmm8\n        movdqa  xmm8, xmm14\n        psrld   xmm14, 8\n        pslld   xmm8, 24\n        pxor    xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x30]\n        paddd   xmm1, xmmword ptr [rsp+0xA0]\n        paddd   xmm2, xmmword ptr [rsp+0xD0]\n        paddd   xmm3, xmmword ptr [rsp+0x70]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        pshuflw xmm12, xmm12, 0xB1\n        pshufhw xmm12, xmm12, 0xB1\n        pshuflw xmm13, xmm13, 0xB1\n        pshufhw xmm13, xmm13, 0xB1\n        pshuflw xmm14, xmm14, 0xB1\n        pshufhw xmm14, xmm14, 0xB1\n        pshuflw xmm15, xmm15, 0xB1\n        pshufhw xmm15, xmm15, 0xB1\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x40]\n        paddd   xmm1, xmmword ptr [rsp+0xC0]\n        paddd   xmm2, xmmword ptr [rsp+0x20]\n        paddd   xmm3, xmmword ptr [rsp+0xE0]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmm12\n        psrld   xmm12, 8\n        pslld   xmm8, 24\n        pxor    xmm12, xmm8\n        movdqa  xmm8, xmm13\n        psrld   xmm13, 8\n        pslld   xmm8, 24\n        pxor    xmm13, xmm8\n        movdqa  xmm8, xmm14\n        psrld   xmm14, 8\n        pslld   xmm8, 24\n        pxor    xmm14, xmm8\n        movdqa  xmm8, xmm15\n        psrld   xmm15, 8\n        pslld   xmm8, 24\n        pxor    xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x60]\n        paddd   xmm1, xmmword ptr [rsp+0x90]\n        paddd   xmm2, xmmword ptr [rsp+0xB0]\n        paddd   xmm3, xmmword ptr [rsp+0x80]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        pshuflw xmm15, xmm15, 0xB1\n        pshufhw xmm15, xmm15, 0xB1\n        pshuflw xmm12, xmm12, 0xB1\n        pshufhw xmm12, xmm12, 0xB1\n        pshuflw xmm13, xmm13, 0xB1\n        pshufhw xmm13, xmm13, 0xB1\n        pshuflw xmm14, xmm14, 0xB1\n        pshufhw xmm14, xmm14, 0xB1\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x50]\n        paddd   xmm1, xmmword ptr [rsp]\n        paddd   xmm2, xmmword ptr [rsp+0xF0]\n        paddd   xmm3, xmmword ptr [rsp+0x10]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmm15\n        psrld   xmm15, 8\n        pslld   xmm8, 24\n        pxor    xmm15, xmm8\n        movdqa  xmm8, xmm12\n        psrld   xmm12, 8\n        pslld   xmm8, 24\n        pxor    xmm12, xmm8\n        movdqa  xmm8, xmm13\n        psrld   xmm13, 8\n        pslld   xmm8, 24\n        pxor    xmm13, xmm8\n        movdqa  xmm8, xmm14\n        psrld   xmm14, 8\n        pslld   xmm8, 24\n        pxor    xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xA0]\n        paddd   xmm1, xmmword ptr [rsp+0xC0]\n        paddd   xmm2, xmmword ptr [rsp+0xE0]\n        paddd   xmm3, xmmword ptr [rsp+0xD0]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        pshuflw xmm12, xmm12, 0xB1\n        pshufhw xmm12, xmm12, 0xB1\n        pshuflw xmm13, xmm13, 0xB1\n        pshufhw xmm13, xmm13, 0xB1\n        pshuflw xmm14, xmm14, 0xB1\n        pshufhw xmm14, xmm14, 0xB1\n        pshuflw xmm15, xmm15, 0xB1\n        pshufhw xmm15, xmm15, 0xB1\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x70]\n        paddd   xmm1, xmmword ptr [rsp+0x90]\n        paddd   xmm2, xmmword ptr [rsp+0x30]\n        paddd   xmm3, xmmword ptr [rsp+0xF0]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmm12\n        psrld   xmm12, 8\n        pslld   xmm8, 24\n        pxor    xmm12, xmm8\n        movdqa  xmm8, xmm13\n        psrld   xmm13, 8\n        pslld   xmm8, 24\n        pxor    xmm13, xmm8\n        movdqa  xmm8, xmm14\n        psrld   xmm14, 8\n        pslld   xmm8, 24\n        pxor    xmm14, xmm8\n        movdqa  xmm8, xmm15\n        psrld   xmm15, 8\n        pslld   xmm8, 24\n        pxor    xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x40]\n        paddd   xmm1, xmmword ptr [rsp+0xB0]\n        paddd   xmm2, xmmword ptr [rsp+0x50]\n        paddd   xmm3, xmmword ptr [rsp+0x10]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        pshuflw xmm15, xmm15, 0xB1\n        pshufhw xmm15, xmm15, 0xB1\n        pshuflw xmm12, xmm12, 0xB1\n        pshufhw xmm12, xmm12, 0xB1\n        pshuflw xmm13, xmm13, 0xB1\n        pshufhw xmm13, xmm13, 0xB1\n        pshuflw xmm14, xmm14, 0xB1\n        pshufhw xmm14, xmm14, 0xB1\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp]\n        paddd   xmm1, xmmword ptr [rsp+0x20]\n        paddd   xmm2, xmmword ptr [rsp+0x80]\n        paddd   xmm3, xmmword ptr [rsp+0x60]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmm15\n        psrld   xmm15, 8\n        pslld   xmm8, 24\n        pxor    xmm15, xmm8\n        movdqa  xmm8, xmm12\n        psrld   xmm12, 8\n        pslld   xmm8, 24\n        pxor    xmm12, xmm8\n        movdqa  xmm8, xmm13\n        psrld   xmm13, 8\n        pslld   xmm8, 24\n        pxor    xmm13, xmm8\n        movdqa  xmm8, xmm14\n        psrld   xmm14, 8\n        pslld   xmm8, 24\n        pxor    xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xC0]\n        paddd   xmm1, xmmword ptr [rsp+0x90]\n        paddd   xmm2, xmmword ptr [rsp+0xF0]\n        paddd   xmm3, xmmword ptr [rsp+0xE0]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        pshuflw xmm12, xmm12, 0xB1\n        pshufhw xmm12, xmm12, 0xB1\n        pshuflw xmm13, xmm13, 0xB1\n        pshufhw xmm13, xmm13, 0xB1\n        pshuflw xmm14, xmm14, 0xB1\n        pshufhw xmm14, xmm14, 0xB1\n        pshuflw xmm15, xmm15, 0xB1\n        pshufhw xmm15, xmm15, 0xB1\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xD0]\n        paddd   xmm1, xmmword ptr [rsp+0xB0]\n        paddd   xmm2, xmmword ptr [rsp+0xA0]\n        paddd   xmm3, xmmword ptr [rsp+0x80]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmm12\n        psrld   xmm12, 8\n        pslld   xmm8, 24\n        pxor    xmm12, xmm8\n        movdqa  xmm8, xmm13\n        psrld   xmm13, 8\n        pslld   xmm8, 24\n        pxor    xmm13, xmm8\n        movdqa  xmm8, xmm14\n        psrld   xmm14, 8\n        pslld   xmm8, 24\n        pxor    xmm14, xmm8\n        movdqa  xmm8, xmm15\n        psrld   xmm15, 8\n        pslld   xmm8, 24\n        pxor    xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x70]\n        paddd   xmm1, xmmword ptr [rsp+0x50]\n        paddd   xmm2, xmmword ptr [rsp]\n        paddd   xmm3, xmmword ptr [rsp+0x60]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        pshuflw xmm15, xmm15, 0xB1\n        pshufhw xmm15, xmm15, 0xB1\n        pshuflw xmm12, xmm12, 0xB1\n        pshufhw xmm12, xmm12, 0xB1\n        pshuflw xmm13, xmm13, 0xB1\n        pshufhw xmm13, xmm13, 0xB1\n        pshuflw xmm14, xmm14, 0xB1\n        pshufhw xmm14, xmm14, 0xB1\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x20]\n        paddd   xmm1, xmmword ptr [rsp+0x30]\n        paddd   xmm2, xmmword ptr [rsp+0x10]\n        paddd   xmm3, xmmword ptr [rsp+0x40]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmm15\n        psrld   xmm15, 8\n        pslld   xmm8, 24\n        pxor    xmm15, xmm8\n        movdqa  xmm8, xmm12\n        psrld   xmm12, 8\n        pslld   xmm8, 24\n        pxor    xmm12, xmm8\n        movdqa  xmm8, xmm13\n        psrld   xmm13, 8\n        pslld   xmm8, 24\n        pxor    xmm13, xmm8\n        movdqa  xmm8, xmm14\n        psrld   xmm14, 8\n        pslld   xmm8, 24\n        pxor    xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x90]\n        paddd   xmm1, xmmword ptr [rsp+0xB0]\n        paddd   xmm2, xmmword ptr [rsp+0x80]\n        paddd   xmm3, xmmword ptr [rsp+0xF0]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        pshuflw xmm12, xmm12, 0xB1\n        pshufhw xmm12, xmm12, 0xB1\n        pshuflw xmm13, xmm13, 0xB1\n        pshufhw xmm13, xmm13, 0xB1\n        pshuflw xmm14, xmm14, 0xB1\n        pshufhw xmm14, xmm14, 0xB1\n        pshuflw xmm15, xmm15, 0xB1\n        pshufhw xmm15, xmm15, 0xB1\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xE0]\n        paddd   xmm1, xmmword ptr [rsp+0x50]\n        paddd   xmm2, xmmword ptr [rsp+0xC0]\n        paddd   xmm3, xmmword ptr [rsp+0x10]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmm12\n        psrld   xmm12, 8\n        pslld   xmm8, 24\n        pxor    xmm12, xmm8\n        movdqa  xmm8, xmm13\n        psrld   xmm13, 8\n        pslld   xmm8, 24\n        pxor    xmm13, xmm8\n        movdqa  xmm8, xmm14\n        psrld   xmm14, 8\n        pslld   xmm8, 24\n        pxor    xmm14, xmm8\n        movdqa  xmm8, xmm15\n        psrld   xmm15, 8\n        pslld   xmm8, 24\n        pxor    xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xD0]\n        paddd   xmm1, xmmword ptr [rsp]\n        paddd   xmm2, xmmword ptr [rsp+0x20]\n        paddd   xmm3, xmmword ptr [rsp+0x40]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        pshuflw xmm15, xmm15, 0xB1\n        pshufhw xmm15, xmm15, 0xB1\n        pshuflw xmm12, xmm12, 0xB1\n        pshufhw xmm12, xmm12, 0xB1\n        pshuflw xmm13, xmm13, 0xB1\n        pshufhw xmm13, xmm13, 0xB1\n        pshuflw xmm14, xmm14, 0xB1\n        pshufhw xmm14, xmm14, 0xB1\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x30]\n        paddd   xmm1, xmmword ptr [rsp+0xA0]\n        paddd   xmm2, xmmword ptr [rsp+0x60]\n        paddd   xmm3, xmmword ptr [rsp+0x70]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmm15\n        psrld   xmm15, 8\n        pslld   xmm8, 24\n        pxor    xmm15, xmm8\n        movdqa  xmm8, xmm12\n        psrld   xmm12, 8\n        pslld   xmm8, 24\n        pxor    xmm12, xmm8\n        movdqa  xmm8, xmm13\n        psrld   xmm13, 8\n        pslld   xmm8, 24\n        pxor    xmm13, xmm8\n        movdqa  xmm8, xmm14\n        psrld   xmm14, 8\n        pslld   xmm8, 24\n        pxor    xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xB0]\n        paddd   xmm1, xmmword ptr [rsp+0x50]\n        paddd   xmm2, xmmword ptr [rsp+0x10]\n        paddd   xmm3, xmmword ptr [rsp+0x80]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        pshuflw xmm12, xmm12, 0xB1\n        pshufhw xmm12, xmm12, 0xB1\n        pshuflw xmm13, xmm13, 0xB1\n        pshufhw xmm13, xmm13, 0xB1\n        pshuflw xmm14, xmm14, 0xB1\n        pshufhw xmm14, xmm14, 0xB1\n        pshuflw xmm15, xmm15, 0xB1\n        pshufhw xmm15, xmm15, 0xB1\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xF0]\n        paddd   xmm1, xmmword ptr [rsp]\n        paddd   xmm2, xmmword ptr [rsp+0x90]\n        paddd   xmm3, xmmword ptr [rsp+0x60]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmm12\n        psrld   xmm12, 8\n        pslld   xmm8, 24\n        pxor    xmm12, xmm8\n        movdqa  xmm8, xmm13\n        psrld   xmm13, 8\n        pslld   xmm8, 24\n        pxor    xmm13, xmm8\n        movdqa  xmm8, xmm14\n        psrld   xmm14, 8\n        pslld   xmm8, 24\n        pxor    xmm14, xmm8\n        movdqa  xmm8, xmm15\n        psrld   xmm15, 8\n        pslld   xmm8, 24\n        pxor    xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xE0]\n        paddd   xmm1, xmmword ptr [rsp+0x20]\n        paddd   xmm2, xmmword ptr [rsp+0x30]\n        paddd   xmm3, xmmword ptr [rsp+0x70]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        pshuflw xmm15, xmm15, 0xB1\n        pshufhw xmm15, xmm15, 0xB1\n        pshuflw xmm12, xmm12, 0xB1\n        pshufhw xmm12, xmm12, 0xB1\n        pshuflw xmm13, xmm13, 0xB1\n        pshufhw xmm13, xmm13, 0xB1\n        pshuflw xmm14, xmm14, 0xB1\n        pshufhw xmm14, xmm14, 0xB1\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xA0]\n        paddd   xmm1, xmmword ptr [rsp+0xC0]\n        paddd   xmm2, xmmword ptr [rsp+0x40]\n        paddd   xmm3, xmmword ptr [rsp+0xD0]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmm15\n        psrld   xmm15, 8\n        pslld   xmm8, 24\n        pxor    xmm15, xmm8\n        movdqa  xmm8, xmm12\n        psrld   xmm12, 8\n        pslld   xmm8, 24\n        pxor    xmm12, xmm8\n        movdqa  xmm8, xmm13\n        psrld   xmm13, 8\n        pslld   xmm8, 24\n        pxor    xmm13, xmm8\n        movdqa  xmm8, xmm14\n        psrld   xmm14, 8\n        pslld   xmm8, 24\n        pxor    xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        pxor    xmm0, xmm8\n        pxor    xmm1, xmm9\n        pxor    xmm2, xmm10\n        pxor    xmm3, xmm11\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        pxor    xmm4, xmm12\n        pxor    xmm5, xmm13\n        pxor    xmm6, xmm14\n        pxor    xmm7, xmm15\n        mov     eax, r13d\n        jne     9b\n        movdqa  xmm9, xmm0\n        punpckldq xmm0, xmm1\n        punpckhdq xmm9, xmm1\n        movdqa  xmm11, xmm2\n        punpckldq xmm2, xmm3\n        punpckhdq xmm11, xmm3\n        movdqa  xmm1, xmm0\n        punpcklqdq xmm0, xmm2\n        punpckhqdq xmm1, xmm2\n        movdqa  xmm3, xmm9\n        punpcklqdq xmm9, xmm11\n        punpckhqdq xmm3, xmm11\n        movdqu  xmmword ptr [rbx], xmm0\n        movdqu  xmmword ptr [rbx+0x20], xmm1\n        movdqu  xmmword ptr [rbx+0x40], xmm9\n        movdqu  xmmword ptr [rbx+0x60], xmm3\n        movdqa  xmm9, xmm4\n        punpckldq xmm4, xmm5\n        punpckhdq xmm9, xmm5\n        movdqa  xmm11, xmm6\n        punpckldq xmm6, xmm7\n        punpckhdq xmm11, xmm7\n        movdqa  xmm5, xmm4\n        punpcklqdq xmm4, xmm6\n        punpckhqdq xmm5, xmm6\n        movdqa  xmm7, xmm9\n        punpcklqdq xmm9, xmm11\n        punpckhqdq xmm7, xmm11\n        movdqu  xmmword ptr [rbx+0x10], xmm4\n        movdqu  xmmword ptr [rbx+0x30], xmm5\n        movdqu  xmmword ptr [rbx+0x50], xmm9\n        movdqu  xmmword ptr [rbx+0x70], xmm7\n        movdqa  xmm1, xmmword ptr [rsp+0x110]\n        movdqa  xmm0, xmm1\n        paddd   xmm1, xmmword ptr [rsp+0x150]\n        movdqa  xmmword ptr [rsp+0x110], xmm1\n        pxor    xmm0, xmmword ptr [CMP_MSB_MASK+rip]\n        pxor    xmm1, xmmword ptr [CMP_MSB_MASK+rip]\n        pcmpgtd xmm0, xmm1\n        movdqa  xmm1, xmmword ptr [rsp+0x120]\n        psubd   xmm1, xmm0\n        movdqa  xmmword ptr [rsp+0x120], xmm1\n        add     rbx, 128\n        add     rdi, 32\n        sub     rsi, 4\n        cmp     rsi, 4\n        jnc     2b\n        test    rsi, rsi\n        jnz     3f\n4:\n        mov     rsp, rbp\n        pop     rbp\n        pop     rbx\n        pop     r12\n        pop     r13\n        pop     r14\n        pop     r15\n        ret\n.p2align 5\n3:\n        test    esi, 0x2\n        je      3f\n        movups  xmm0, xmmword ptr [rcx]\n        movups  xmm1, xmmword ptr [rcx+0x10]\n        movaps  xmm8, xmm0\n        movaps  xmm9, xmm1\n        movd    xmm13, dword ptr [rsp+0x110]\n        movd    xmm14, dword ptr [rsp+0x120]\n        punpckldq xmm13, xmm14\n        movaps  xmmword ptr [rsp], xmm13\n        movd    xmm14, dword ptr [rsp+0x114]\n        movd    xmm13, dword ptr [rsp+0x124]\n        punpckldq xmm14, xmm13\n        movaps  xmmword ptr [rsp+0x10], xmm14\n        mov     r8, qword ptr [rdi]\n        mov     r9, qword ptr [rdi+0x8]\n        movzx   eax, byte ptr [rbp+0x40]\n        or      eax, r13d\n        xor     edx, edx\n2:\n        mov     r14d, eax\n        or      eax, r12d\n        add     rdx, 64\n        cmp     rdx, r15\n        cmovne  eax, r14d\n        movaps  xmm2, xmmword ptr [BLAKE3_IV+rip]\n        movaps  xmm10, xmm2\n        movups  xmm4, xmmword ptr [r8+rdx-0x40]\n        movups  xmm5, xmmword ptr [r8+rdx-0x30]\n        movaps  xmm3, xmm4\n        shufps  xmm4, xmm5, 136\n        shufps  xmm3, xmm5, 221\n        movaps  xmm5, xmm3\n        movups  xmm6, xmmword ptr [r8+rdx-0x20]\n        movups  xmm7, xmmword ptr [r8+rdx-0x10]\n        movaps  xmm3, xmm6\n        shufps  xmm6, xmm7, 136\n        pshufd  xmm6, xmm6, 0x93\n        shufps  xmm3, xmm7, 221\n        pshufd  xmm7, xmm3, 0x93\n        movups  xmm12, xmmword ptr [r9+rdx-0x40]\n        movups  xmm13, xmmword ptr [r9+rdx-0x30]\n        movaps  xmm11, xmm12\n        shufps  xmm12, xmm13, 136\n        shufps  xmm11, xmm13, 221\n        movaps  xmm13, xmm11\n        movups  xmm14, xmmword ptr [r9+rdx-0x20]\n        movups  xmm15, xmmword ptr [r9+rdx-0x10]\n        movaps  xmm11, xmm14\n        shufps  xmm14, xmm15, 136\n        pshufd  xmm14, xmm14, 0x93\n        shufps  xmm11, xmm15, 221\n        pshufd  xmm15, xmm11, 0x93\n        shl     rax, 0x20\n        or      rax, 0x40\n        movq    xmm3, rax\n        movdqa  xmmword ptr [rsp+0x20], xmm3\n        movaps  xmm3, xmmword ptr [rsp]\n        movaps  xmm11, xmmword ptr [rsp+0x10]\n        punpcklqdq xmm3, xmmword ptr [rsp+0x20]\n        punpcklqdq xmm11, xmmword ptr [rsp+0x20]\n        mov     al, 7\n9:\n        paddd   xmm0, xmm4\n        paddd   xmm8, xmm12\n        movaps  xmmword ptr [rsp+0x20], xmm4\n        movaps  xmmword ptr [rsp+0x30], xmm12\n        paddd   xmm0, xmm1\n        paddd   xmm8, xmm9\n        pxor    xmm3, xmm0\n        pxor    xmm11, xmm8\n        pshuflw xmm3, xmm3, 0xB1\n        pshufhw xmm3, xmm3, 0xB1\n        pshuflw xmm11, xmm11, 0xB1\n        pshufhw xmm11, xmm11, 0xB1\n        paddd   xmm2, xmm3\n        paddd   xmm10, xmm11\n        pxor    xmm1, xmm2\n        pxor    xmm9, xmm10\n        movdqa  xmm4, xmm1\n        pslld   xmm1, 20\n        psrld   xmm4, 12\n        por     xmm1, xmm4\n        movdqa  xmm4, xmm9\n        pslld   xmm9, 20\n        psrld   xmm4, 12\n        por     xmm9, xmm4\n        paddd   xmm0, xmm5\n        paddd   xmm8, xmm13\n        movaps  xmmword ptr [rsp+0x40], xmm5\n        movaps  xmmword ptr [rsp+0x50], xmm13\n        paddd   xmm0, xmm1\n        paddd   xmm8, xmm9\n        pxor    xmm3, xmm0\n        pxor    xmm11, xmm8\n        movdqa  xmm13, xmm3\n        psrld   xmm3, 8\n        pslld   xmm13, 24\n        pxor    xmm3, xmm13\n        movdqa  xmm13, xmm11\n        psrld   xmm11, 8\n        pslld   xmm13, 24\n        pxor    xmm11, xmm13\n        paddd   xmm2, xmm3\n        paddd   xmm10, xmm11\n        pxor    xmm1, xmm2\n        pxor    xmm9, xmm10\n        movdqa  xmm4, xmm1\n        pslld   xmm1, 25\n        psrld   xmm4, 7\n        por     xmm1, xmm4\n        movdqa  xmm4, xmm9\n        pslld   xmm9, 25\n        psrld   xmm4, 7\n        por     xmm9, xmm4\n        pshufd  xmm0, xmm0, 0x93\n        pshufd  xmm8, xmm8, 0x93\n        pshufd  xmm3, xmm3, 0x4E\n        pshufd  xmm11, xmm11, 0x4E\n        pshufd  xmm2, xmm2, 0x39\n        pshufd  xmm10, xmm10, 0x39\n        paddd   xmm0, xmm6\n        paddd   xmm8, xmm14\n        paddd   xmm0, xmm1\n        paddd   xmm8, xmm9\n        pxor    xmm3, xmm0\n        pxor    xmm11, xmm8\n        pshuflw xmm3, xmm3, 0xB1\n        pshufhw xmm3, xmm3, 0xB1\n        pshuflw xmm11, xmm11, 0xB1\n        pshufhw xmm11, xmm11, 0xB1\n        paddd   xmm2, xmm3\n        paddd   xmm10, xmm11\n        pxor    xmm1, xmm2\n        pxor    xmm9, xmm10\n        movdqa  xmm4, xmm1\n        pslld   xmm1, 20\n        psrld   xmm4, 12\n        por     xmm1, xmm4\n        movdqa  xmm4, xmm9\n        pslld   xmm9, 20\n        psrld   xmm4, 12\n        por     xmm9, xmm4\n        paddd   xmm0, xmm7\n        paddd   xmm8, xmm15\n        paddd   xmm0, xmm1\n        paddd   xmm8, xmm9\n        pxor    xmm3, xmm0\n        pxor    xmm11, xmm8\n        movdqa  xmm13, xmm3\n        psrld   xmm3, 8\n        pslld   xmm13, 24\n        pxor    xmm3, xmm13\n        movdqa  xmm13, xmm11\n        psrld   xmm11, 8\n        pslld   xmm13, 24\n        pxor    xmm11, xmm13\n        paddd   xmm2, xmm3\n        paddd   xmm10, xmm11\n        pxor    xmm1, xmm2\n        pxor    xmm9, xmm10\n        movdqa  xmm4, xmm1\n        pslld   xmm1, 25\n        psrld   xmm4, 7\n        por     xmm1, xmm4\n        movdqa  xmm4, xmm9\n        pslld   xmm9, 25\n        psrld   xmm4, 7\n        por     xmm9, xmm4\n        pshufd  xmm0, xmm0, 0x39\n        pshufd  xmm8, xmm8, 0x39\n        pshufd  xmm3, xmm3, 0x4E\n        pshufd  xmm11, xmm11, 0x4E\n        pshufd  xmm2, xmm2, 0x93\n        pshufd  xmm10, xmm10, 0x93\n        dec     al\n        je      9f\n        movdqa  xmm12, xmmword ptr [rsp+0x20]\n        movdqa  xmm5, xmmword ptr [rsp+0x40]\n        pshufd  xmm13, xmm12, 0x0F\n        shufps  xmm12, xmm5, 214\n        pshufd  xmm4, xmm12, 0x39\n        movdqa  xmm12, xmm6\n        shufps  xmm12, xmm7, 250\n        pand    xmm13, xmmword ptr [PBLENDW_0x33_MASK+rip]\n        pand    xmm12, xmmword ptr [PBLENDW_0xCC_MASK+rip]\n        por     xmm13, xmm12\n        movdqa  xmmword ptr [rsp+0x20], xmm13\n        movdqa  xmm12, xmm7\n        punpcklqdq xmm12, xmm5\n        movdqa  xmm13, xmm6\n        pand    xmm12, xmmword ptr [PBLENDW_0x3F_MASK+rip]\n        pand    xmm13, xmmword ptr [PBLENDW_0xC0_MASK+rip]\n        por     xmm12, xmm13\n        pshufd  xmm12, xmm12, 0x78\n        punpckhdq xmm5, xmm7\n        punpckldq xmm6, xmm5\n        pshufd  xmm7, xmm6, 0x1E\n        movdqa  xmmword ptr [rsp+0x40], xmm12\n        movdqa  xmm5, xmmword ptr [rsp+0x30]\n        movdqa  xmm13, xmmword ptr [rsp+0x50]\n        pshufd  xmm6, xmm5, 0x0F\n        shufps  xmm5, xmm13, 214\n        pshufd  xmm12, xmm5, 0x39\n        movdqa  xmm5, xmm14\n        shufps  xmm5, xmm15, 250\n        pand    xmm6, xmmword ptr [PBLENDW_0x33_MASK+rip]\n        pand    xmm5, xmmword ptr [PBLENDW_0xCC_MASK+rip]\n        por     xmm6, xmm5\n        movdqa  xmm5, xmm15\n        punpcklqdq xmm5, xmm13\n        movdqa  xmmword ptr [rsp+0x30], xmm2\n        movdqa  xmm2, xmm14\n        pand    xmm5, xmmword ptr [PBLENDW_0x3F_MASK+rip]\n        pand    xmm2, xmmword ptr [PBLENDW_0xC0_MASK+rip]\n        por     xmm5, xmm2\n        movdqa  xmm2, xmmword ptr [rsp+0x30]\n        pshufd  xmm5, xmm5, 0x78\n        punpckhdq xmm13, xmm15\n        punpckldq xmm14, xmm13\n        pshufd  xmm15, xmm14, 0x1E\n        movdqa  xmm13, xmm6\n        movdqa  xmm14, xmm5\n        movdqa  xmm5, xmmword ptr [rsp+0x20]\n        movdqa  xmm6, xmmword ptr [rsp+0x40]\n        jmp     9b\n9:\n        pxor    xmm0, xmm2\n        pxor    xmm1, xmm3\n        pxor    xmm8, xmm10\n        pxor    xmm9, xmm11\n        mov     eax, r13d\n        cmp     rdx, r15\n        jne     2b\n        movups  xmmword ptr [rbx], xmm0\n        movups  xmmword ptr [rbx+0x10], xmm1\n        movups  xmmword ptr [rbx+0x20], xmm8\n        movups  xmmword ptr [rbx+0x30], xmm9\n        mov     eax, dword ptr [rsp+0x130]\n        neg     eax\n        mov    r10d, dword ptr [rsp+0x110+8*rax]\n        mov    r11d, dword ptr [rsp+0x120+8*rax]\n        mov dword ptr [rsp+0x110], r10d\n        mov dword ptr [rsp+0x120], r11d\n        add     rdi, 16\n        add     rbx, 64\n        sub     rsi, 2\n3:\n        test    esi, 0x1\n        je      4b\n        movups  xmm0, xmmword ptr [rcx]\n        movups  xmm1, xmmword ptr [rcx+0x10]\n        movd    xmm13, dword ptr [rsp+0x110]\n        movd    xmm14, dword ptr [rsp+0x120]\n        punpckldq xmm13, xmm14\n        mov     r8, qword ptr [rdi]\n        movzx   eax, byte ptr [rbp+0x40]\n        or      eax, r13d\n        xor     edx, edx\n2:\n        mov     r14d, eax\n        or      eax, r12d\n        add     rdx, 64\n        cmp     rdx, r15\n        cmovne  eax, r14d\n        movaps  xmm2, xmmword ptr [BLAKE3_IV+rip]\n        shl     rax, 32\n        or      rax, 64\n        movq    xmm12, rax\n        movdqa  xmm3, xmm13\n        punpcklqdq xmm3, xmm12\n        movups  xmm4, xmmword ptr [r8+rdx-0x40]\n        movups  xmm5, xmmword ptr [r8+rdx-0x30]\n        movaps  xmm8, xmm4\n        shufps  xmm4, xmm5, 136\n        shufps  xmm8, xmm5, 221\n        movaps  xmm5, xmm8\n        movups  xmm6, xmmword ptr [r8+rdx-0x20]\n        movups  xmm7, xmmword ptr [r8+rdx-0x10]\n        movaps  xmm8, xmm6\n        shufps  xmm6, xmm7, 136\n        pshufd  xmm6, xmm6, 0x93\n        shufps  xmm8, xmm7, 221\n        pshufd  xmm7, xmm8, 0x93\n        mov     al, 7\n9:\n        paddd   xmm0, xmm4\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshuflw xmm3, xmm3, 0xB1\n        pshufhw xmm3, xmm3, 0xB1\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 20\n        psrld   xmm11, 12\n        por     xmm1, xmm11\n        paddd   xmm0, xmm5\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        movdqa  xmm14, xmm3\n        psrld   xmm3, 8\n        pslld   xmm14, 24\n        pxor    xmm3, xmm14\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 25\n        psrld   xmm11, 7\n        por     xmm1, xmm11\n        pshufd  xmm0, xmm0, 0x93\n        pshufd  xmm3, xmm3, 0x4E\n        pshufd  xmm2, xmm2, 0x39\n        paddd   xmm0, xmm6\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshuflw xmm3, xmm3, 0xB1\n        pshufhw xmm3, xmm3, 0xB1\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 20\n        psrld   xmm11, 12\n        por     xmm1, xmm11\n        paddd   xmm0, xmm7\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        movdqa  xmm14, xmm3\n        psrld   xmm3, 8\n        pslld   xmm14, 24\n        pxor    xmm3, xmm14\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 25\n        psrld   xmm11, 7\n        por     xmm1, xmm11\n        pshufd  xmm0, xmm0, 0x39\n        pshufd  xmm3, xmm3, 0x4E\n        pshufd  xmm2, xmm2, 0x93\n        dec     al\n        jz      9f\n        movdqa  xmm8, xmm4\n        shufps  xmm8, xmm5, 214\n        pshufd  xmm9, xmm4, 0x0F\n        pshufd  xmm4, xmm8, 0x39\n        movdqa  xmm8, xmm6\n        shufps  xmm8, xmm7, 250\n        pand    xmm9, xmmword ptr [PBLENDW_0x33_MASK+rip]\n        pand    xmm8, xmmword ptr [PBLENDW_0xCC_MASK+rip]\n        por     xmm9, xmm8\n        movdqa  xmm8, xmm7\n        punpcklqdq xmm8, xmm5\n        movdqa  xmm10, xmm6\n        pand    xmm8, xmmword ptr [PBLENDW_0x3F_MASK+rip]\n        pand    xmm10, xmmword ptr [PBLENDW_0xC0_MASK+rip]\n        por     xmm8, xmm10\n        pshufd  xmm8, xmm8, 0x78\n        punpckhdq xmm5, xmm7\n        punpckldq xmm6, xmm5\n        pshufd  xmm7, xmm6, 0x1E\n        movdqa  xmm5, xmm9\n        movdqa  xmm6, xmm8\n        jmp     9b\n9:\n        pxor    xmm0, xmm2\n        pxor    xmm1, xmm3\n        mov     eax, r13d\n        cmp     rdx, r15\n        jne     2b\n        movups  xmmword ptr [rbx], xmm0\n        movups  xmmword ptr [rbx+0x10], xmm1\n        jmp     4b\n\n.p2align 6\nblake3_compress_in_place_sse2:\n_blake3_compress_in_place_sse2:\n        _CET_ENDBR\n        movups  xmm0, xmmword ptr [rdi]\n        movups  xmm1, xmmword ptr [rdi+0x10]\n        movaps  xmm2, xmmword ptr [BLAKE3_IV+rip]\n        shl     r8, 32\n        add     rdx, r8\n        movq    xmm3, rcx\n        movq    xmm4, rdx\n        punpcklqdq xmm3, xmm4\n        movups  xmm4, xmmword ptr [rsi]\n        movups  xmm5, xmmword ptr [rsi+0x10]\n        movaps  xmm8, xmm4\n        shufps  xmm4, xmm5, 136\n        shufps  xmm8, xmm5, 221\n        movaps  xmm5, xmm8\n        movups  xmm6, xmmword ptr [rsi+0x20]\n        movups  xmm7, xmmword ptr [rsi+0x30]\n        movaps  xmm8, xmm6\n        shufps  xmm6, xmm7, 136\n        pshufd  xmm6, xmm6, 0x93\n        shufps  xmm8, xmm7, 221\n        pshufd  xmm7, xmm8, 0x93\n        mov     al, 7\n9:\n        paddd   xmm0, xmm4\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshuflw xmm3, xmm3, 0xB1\n        pshufhw xmm3, xmm3, 0xB1\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 20\n        psrld   xmm11, 12\n        por     xmm1, xmm11\n        paddd   xmm0, xmm5\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        movdqa  xmm14, xmm3\n        psrld   xmm3, 8\n        pslld   xmm14, 24\n        pxor    xmm3, xmm14\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 25\n        psrld   xmm11, 7\n        por     xmm1, xmm11\n        pshufd  xmm0, xmm0, 0x93\n        pshufd  xmm3, xmm3, 0x4E\n        pshufd  xmm2, xmm2, 0x39\n        paddd   xmm0, xmm6\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshuflw xmm3, xmm3, 0xB1\n        pshufhw xmm3, xmm3, 0xB1\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 20\n        psrld   xmm11, 12\n        por     xmm1, xmm11\n        paddd   xmm0, xmm7\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        movdqa  xmm14, xmm3\n        psrld   xmm3, 8\n        pslld   xmm14, 24\n        pxor    xmm3, xmm14\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 25\n        psrld   xmm11, 7\n        por     xmm1, xmm11\n        pshufd  xmm0, xmm0, 0x39\n        pshufd  xmm3, xmm3, 0x4E\n        pshufd  xmm2, xmm2, 0x93\n        dec     al\n        jz      9f\n        movdqa  xmm8, xmm4\n        shufps  xmm8, xmm5, 214\n        pshufd  xmm9, xmm4, 0x0F\n        pshufd  xmm4, xmm8, 0x39\n        movdqa  xmm8, xmm6\n        shufps  xmm8, xmm7, 250\n        pand    xmm9, xmmword ptr [PBLENDW_0x33_MASK+rip]\n        pand    xmm8, xmmword ptr [PBLENDW_0xCC_MASK+rip]\n        por     xmm9, xmm8\n        movdqa  xmm8, xmm7\n        punpcklqdq xmm8, xmm5\n        movdqa  xmm10, xmm6\n        pand    xmm8, xmmword ptr [PBLENDW_0x3F_MASK+rip]\n        pand    xmm10, xmmword ptr [PBLENDW_0xC0_MASK+rip]\n        por     xmm8, xmm10\n        pshufd  xmm8, xmm8, 0x78\n        punpckhdq xmm5, xmm7\n        punpckldq xmm6, xmm5\n        pshufd  xmm7, xmm6, 0x1E\n        movdqa  xmm5, xmm9\n        movdqa  xmm6, xmm8\n        jmp     9b\n9:\n        pxor    xmm0, xmm2\n        pxor    xmm1, xmm3\n        movups  xmmword ptr [rdi], xmm0\n        movups  xmmword ptr [rdi+0x10], xmm1\n        ret\n\n.p2align 6\nblake3_compress_xof_sse2:\n_blake3_compress_xof_sse2:\n        _CET_ENDBR\n        movups  xmm0, xmmword ptr [rdi]\n        movups  xmm1, xmmword ptr [rdi+0x10]\n        movaps  xmm2, xmmword ptr [BLAKE3_IV+rip]\n        movzx   eax, r8b\n        movzx   edx, dl\n        shl     rax, 32\n        add     rdx, rax\n        movq    xmm3, rcx\n        movq    xmm4, rdx\n        punpcklqdq xmm3, xmm4\n        movups  xmm4, xmmword ptr [rsi]\n        movups  xmm5, xmmword ptr [rsi+0x10]\n        movaps  xmm8, xmm4\n        shufps  xmm4, xmm5, 136\n        shufps  xmm8, xmm5, 221\n        movaps  xmm5, xmm8\n        movups  xmm6, xmmword ptr [rsi+0x20]\n        movups  xmm7, xmmword ptr [rsi+0x30]\n        movaps  xmm8, xmm6\n        shufps  xmm6, xmm7, 136\n        pshufd  xmm6, xmm6, 0x93\n        shufps  xmm8, xmm7, 221\n        pshufd  xmm7, xmm8, 0x93\n        mov     al, 7\n9:\n        paddd   xmm0, xmm4\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshuflw xmm3, xmm3, 0xB1\n        pshufhw xmm3, xmm3, 0xB1\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 20\n        psrld   xmm11, 12\n        por     xmm1, xmm11\n        paddd   xmm0, xmm5\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        movdqa  xmm14, xmm3\n        psrld   xmm3, 8\n        pslld   xmm14, 24\n        pxor    xmm3, xmm14\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 25\n        psrld   xmm11, 7\n        por     xmm1, xmm11\n        pshufd  xmm0, xmm0, 0x93\n        pshufd  xmm3, xmm3, 0x4E\n        pshufd  xmm2, xmm2, 0x39\n        paddd   xmm0, xmm6\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshuflw xmm3, xmm3, 0xB1\n        pshufhw xmm3, xmm3, 0xB1\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 20\n        psrld   xmm11, 12\n        por     xmm1, xmm11\n        paddd   xmm0, xmm7\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        movdqa  xmm14, xmm3\n        psrld   xmm3, 8\n        pslld   xmm14, 24\n        pxor    xmm3, xmm14\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 25\n        psrld   xmm11, 7\n        por     xmm1, xmm11\n        pshufd  xmm0, xmm0, 0x39\n        pshufd  xmm3, xmm3, 0x4E\n        pshufd  xmm2, xmm2, 0x93\n        dec     al\n        jz      9f\n        movdqa  xmm8, xmm4\n        shufps  xmm8, xmm5, 214\n        pshufd  xmm9, xmm4, 0x0F\n        pshufd  xmm4, xmm8, 0x39\n        movdqa  xmm8, xmm6\n        shufps  xmm8, xmm7, 250\n        pand    xmm9, xmmword ptr [PBLENDW_0x33_MASK+rip]\n        pand    xmm8, xmmword ptr [PBLENDW_0xCC_MASK+rip]\n        por     xmm9, xmm8\n        movdqa  xmm8, xmm7\n        punpcklqdq xmm8, xmm5\n        movdqa  xmm10, xmm6\n        pand    xmm8, xmmword ptr [PBLENDW_0x3F_MASK+rip]\n        pand    xmm10, xmmword ptr [PBLENDW_0xC0_MASK+rip]\n        por     xmm8, xmm10\n        pshufd  xmm8, xmm8, 0x78\n        punpckhdq xmm5, xmm7\n        punpckldq xmm6, xmm5\n        pshufd  xmm7, xmm6, 0x1E\n        movdqa  xmm5, xmm9\n        movdqa  xmm6, xmm8\n        jmp     9b\n9:\n        movdqu  xmm4, xmmword ptr [rdi]\n        movdqu  xmm5, xmmword ptr [rdi+0x10]\n        pxor    xmm0, xmm2\n        pxor    xmm1, xmm3\n        pxor    xmm2, xmm4\n        pxor    xmm3, xmm5\n        movups  xmmword ptr [r9], xmm0\n        movups  xmmword ptr [r9+0x10], xmm1\n        movups  xmmword ptr [r9+0x20], xmm2\n        movups  xmmword ptr [r9+0x30], xmm3\n        ret\n\n\n#ifdef __APPLE__\n.static_data\n#else\n.section .rodata\n#endif\n.p2align  6\nBLAKE3_IV:\n        .long  0x6A09E667, 0xBB67AE85\n        .long  0x3C6EF372, 0xA54FF53A\nADD0:\n        .long  0, 1, 2, 3\nADD1:\n\t.long  4, 4, 4, 4\nBLAKE3_IV_0:\n\t.long  0x6A09E667, 0x6A09E667, 0x6A09E667, 0x6A09E667\nBLAKE3_IV_1:\n\t.long  0xBB67AE85, 0xBB67AE85, 0xBB67AE85, 0xBB67AE85\nBLAKE3_IV_2:\n\t.long  0x3C6EF372, 0x3C6EF372, 0x3C6EF372, 0x3C6EF372\nBLAKE3_IV_3:\n\t.long  0xA54FF53A, 0xA54FF53A, 0xA54FF53A, 0xA54FF53A\nBLAKE3_BLOCK_LEN:\n\t.long  64, 64, 64, 64\nCMP_MSB_MASK:\n\t.long  0x80000000, 0x80000000, 0x80000000, 0x80000000\nPBLENDW_0x33_MASK:\n\t.long  0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000\nPBLENDW_0xCC_MASK:\n\t.long  0x00000000, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF\nPBLENDW_0x3F_MASK:\n\t.long  0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000\nPBLENDW_0xC0_MASK:\n\t.long  0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF\n"
  },
  {
    "path": "common/blake3/blake3_sse41.c",
    "content": "#include \"blake3_impl.h\"\n\n#include <immintrin.h>\n\n#define DEGREE 4\n\n#define _mm_shuffle_ps2(a, b, c)                                               \\\n  (_mm_castps_si128(                                                           \\\n      _mm_shuffle_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b), (c))))\n\nINLINE __m128i loadu(const uint8_t src[16]) {\n  return _mm_loadu_si128((const __m128i *)src);\n}\n\nINLINE void storeu(__m128i src, uint8_t dest[16]) {\n  _mm_storeu_si128((__m128i *)dest, src);\n}\n\nINLINE __m128i addv(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n\n// Note that clang-format doesn't like the name \"xor\" for some reason.\nINLINE __m128i xorv(__m128i a, __m128i b) { return _mm_xor_si128(a, b); }\n\nINLINE __m128i set1(uint32_t x) { return _mm_set1_epi32((int32_t)x); }\n\nINLINE __m128i set4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {\n  return _mm_setr_epi32((int32_t)a, (int32_t)b, (int32_t)c, (int32_t)d);\n}\n\nINLINE __m128i rot16(__m128i x) {\n  return _mm_shuffle_epi8(\n      x, _mm_set_epi8(13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2));\n}\n\nINLINE __m128i rot12(__m128i x) {\n  return xorv(_mm_srli_epi32(x, 12), _mm_slli_epi32(x, 32 - 12));\n}\n\nINLINE __m128i rot8(__m128i x) {\n  return _mm_shuffle_epi8(\n      x, _mm_set_epi8(12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1));\n}\n\nINLINE __m128i rot7(__m128i x) {\n  return xorv(_mm_srli_epi32(x, 7), _mm_slli_epi32(x, 32 - 7));\n}\n\nINLINE void g1(__m128i *row0, __m128i *row1, __m128i *row2, __m128i *row3,\n               __m128i m) {\n  *row0 = addv(addv(*row0, m), *row1);\n  *row3 = xorv(*row3, *row0);\n  *row3 = rot16(*row3);\n  *row2 = addv(*row2, *row3);\n  *row1 = xorv(*row1, *row2);\n  *row1 = rot12(*row1);\n}\n\nINLINE void g2(__m128i *row0, __m128i *row1, __m128i *row2, __m128i *row3,\n               __m128i m) {\n  *row0 = addv(addv(*row0, m), *row1);\n  *row3 = xorv(*row3, *row0);\n  *row3 = rot8(*row3);\n  *row2 = addv(*row2, *row3);\n  *row1 = xorv(*row1, *row2);\n  *row1 = rot7(*row1);\n}\n\n// Note the optimization here of leaving row1 as the unrotated row, rather than\n// row0. All the message loads below are adjusted to compensate for this. See\n// discussion at https://github.com/sneves/blake2-avx2/pull/4\nINLINE void diagonalize(__m128i *row0, __m128i *row2, __m128i *row3) {\n  *row0 = _mm_shuffle_epi32(*row0, _MM_SHUFFLE(2, 1, 0, 3));\n  *row3 = _mm_shuffle_epi32(*row3, _MM_SHUFFLE(1, 0, 3, 2));\n  *row2 = _mm_shuffle_epi32(*row2, _MM_SHUFFLE(0, 3, 2, 1));\n}\n\nINLINE void undiagonalize(__m128i *row0, __m128i *row2, __m128i *row3) {\n  *row0 = _mm_shuffle_epi32(*row0, _MM_SHUFFLE(0, 3, 2, 1));\n  *row3 = _mm_shuffle_epi32(*row3, _MM_SHUFFLE(1, 0, 3, 2));\n  *row2 = _mm_shuffle_epi32(*row2, _MM_SHUFFLE(2, 1, 0, 3));\n}\n\nINLINE void compress_pre(__m128i rows[4], const uint32_t cv[8],\n                         const uint8_t block[BLAKE3_BLOCK_LEN],\n                         uint8_t block_len, uint64_t counter, uint8_t flags) {\n  rows[0] = loadu((uint8_t *)&cv[0]);\n  rows[1] = loadu((uint8_t *)&cv[4]);\n  rows[2] = set4(IV[0], IV[1], IV[2], IV[3]);\n  rows[3] = set4(counter_low(counter), counter_high(counter),\n                 (uint32_t)block_len, (uint32_t)flags);\n\n  __m128i m0 = loadu(&block[sizeof(__m128i) * 0]);\n  __m128i m1 = loadu(&block[sizeof(__m128i) * 1]);\n  __m128i m2 = loadu(&block[sizeof(__m128i) * 2]);\n  __m128i m3 = loadu(&block[sizeof(__m128i) * 3]);\n\n  __m128i t0, t1, t2, t3, tt;\n\n  // Round 1. The first round permutes the message words from the original\n  // input order, into the groups that get mixed in parallel.\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(2, 0, 2, 0)); //  6  4  2  0\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 3, 1)); //  7  5  3  1\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(2, 0, 2, 0)); // 14 12 10  8\n  t2 = _mm_shuffle_epi32(t2, _MM_SHUFFLE(2, 1, 0, 3));   // 12 10  8 14\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 1, 3, 1)); // 15 13 11  9\n  t3 = _mm_shuffle_epi32(t3, _MM_SHUFFLE(2, 1, 0, 3));   // 13 11  9 15\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 2. This round and all following rounds apply a fixed permutation\n  // to the message words from the round before.\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = _mm_blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = _mm_blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 3\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = _mm_blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = _mm_blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 4\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = _mm_blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = _mm_blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 5\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = _mm_blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = _mm_blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 6\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = _mm_blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = _mm_blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n  m0 = t0;\n  m1 = t1;\n  m2 = t2;\n  m3 = t3;\n\n  // Round 7\n  t0 = _mm_shuffle_ps2(m0, m1, _MM_SHUFFLE(3, 1, 1, 2));\n  t0 = _mm_shuffle_epi32(t0, _MM_SHUFFLE(0, 3, 2, 1));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t0);\n  t1 = _mm_shuffle_ps2(m2, m3, _MM_SHUFFLE(3, 3, 2, 2));\n  tt = _mm_shuffle_epi32(m0, _MM_SHUFFLE(0, 0, 3, 3));\n  t1 = _mm_blend_epi16(tt, t1, 0xCC);\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t1);\n  diagonalize(&rows[0], &rows[2], &rows[3]);\n  t2 = _mm_unpacklo_epi64(m3, m1);\n  tt = _mm_blend_epi16(t2, m2, 0xC0);\n  t2 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(1, 3, 2, 0));\n  g1(&rows[0], &rows[1], &rows[2], &rows[3], t2);\n  t3 = _mm_unpackhi_epi32(m1, m3);\n  tt = _mm_unpacklo_epi32(m2, t3);\n  t3 = _mm_shuffle_epi32(tt, _MM_SHUFFLE(0, 1, 3, 2));\n  g2(&rows[0], &rows[1], &rows[2], &rows[3], t3);\n  undiagonalize(&rows[0], &rows[2], &rows[3]);\n}\n\nvoid blake3_compress_in_place_sse41(uint32_t cv[8],\n                                    const uint8_t block[BLAKE3_BLOCK_LEN],\n                                    uint8_t block_len, uint64_t counter,\n                                    uint8_t flags) {\n  __m128i rows[4];\n  compress_pre(rows, cv, block, block_len, counter, flags);\n  storeu(xorv(rows[0], rows[2]), (uint8_t *)&cv[0]);\n  storeu(xorv(rows[1], rows[3]), (uint8_t *)&cv[4]);\n}\n\nvoid blake3_compress_xof_sse41(const uint32_t cv[8],\n                               const uint8_t block[BLAKE3_BLOCK_LEN],\n                               uint8_t block_len, uint64_t counter,\n                               uint8_t flags, uint8_t out[64]) {\n  __m128i rows[4];\n  compress_pre(rows, cv, block, block_len, counter, flags);\n  storeu(xorv(rows[0], rows[2]), &out[0]);\n  storeu(xorv(rows[1], rows[3]), &out[16]);\n  storeu(xorv(rows[2], loadu((uint8_t *)&cv[0])), &out[32]);\n  storeu(xorv(rows[3], loadu((uint8_t *)&cv[4])), &out[48]);\n}\n\nINLINE void round_fn(__m128i v[16], __m128i m[16], size_t r) {\n  v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][0]]);\n  v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][2]]);\n  v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][4]]);\n  v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][6]]);\n  v[0] = addv(v[0], v[4]);\n  v[1] = addv(v[1], v[5]);\n  v[2] = addv(v[2], v[6]);\n  v[3] = addv(v[3], v[7]);\n  v[12] = xorv(v[12], v[0]);\n  v[13] = xorv(v[13], v[1]);\n  v[14] = xorv(v[14], v[2]);\n  v[15] = xorv(v[15], v[3]);\n  v[12] = rot16(v[12]);\n  v[13] = rot16(v[13]);\n  v[14] = rot16(v[14]);\n  v[15] = rot16(v[15]);\n  v[8] = addv(v[8], v[12]);\n  v[9] = addv(v[9], v[13]);\n  v[10] = addv(v[10], v[14]);\n  v[11] = addv(v[11], v[15]);\n  v[4] = xorv(v[4], v[8]);\n  v[5] = xorv(v[5], v[9]);\n  v[6] = xorv(v[6], v[10]);\n  v[7] = xorv(v[7], v[11]);\n  v[4] = rot12(v[4]);\n  v[5] = rot12(v[5]);\n  v[6] = rot12(v[6]);\n  v[7] = rot12(v[7]);\n  v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][1]]);\n  v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][3]]);\n  v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][5]]);\n  v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][7]]);\n  v[0] = addv(v[0], v[4]);\n  v[1] = addv(v[1], v[5]);\n  v[2] = addv(v[2], v[6]);\n  v[3] = addv(v[3], v[7]);\n  v[12] = xorv(v[12], v[0]);\n  v[13] = xorv(v[13], v[1]);\n  v[14] = xorv(v[14], v[2]);\n  v[15] = xorv(v[15], v[3]);\n  v[12] = rot8(v[12]);\n  v[13] = rot8(v[13]);\n  v[14] = rot8(v[14]);\n  v[15] = rot8(v[15]);\n  v[8] = addv(v[8], v[12]);\n  v[9] = addv(v[9], v[13]);\n  v[10] = addv(v[10], v[14]);\n  v[11] = addv(v[11], v[15]);\n  v[4] = xorv(v[4], v[8]);\n  v[5] = xorv(v[5], v[9]);\n  v[6] = xorv(v[6], v[10]);\n  v[7] = xorv(v[7], v[11]);\n  v[4] = rot7(v[4]);\n  v[5] = rot7(v[5]);\n  v[6] = rot7(v[6]);\n  v[7] = rot7(v[7]);\n\n  v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][8]]);\n  v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][10]]);\n  v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][12]]);\n  v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][14]]);\n  v[0] = addv(v[0], v[5]);\n  v[1] = addv(v[1], v[6]);\n  v[2] = addv(v[2], v[7]);\n  v[3] = addv(v[3], v[4]);\n  v[15] = xorv(v[15], v[0]);\n  v[12] = xorv(v[12], v[1]);\n  v[13] = xorv(v[13], v[2]);\n  v[14] = xorv(v[14], v[3]);\n  v[15] = rot16(v[15]);\n  v[12] = rot16(v[12]);\n  v[13] = rot16(v[13]);\n  v[14] = rot16(v[14]);\n  v[10] = addv(v[10], v[15]);\n  v[11] = addv(v[11], v[12]);\n  v[8] = addv(v[8], v[13]);\n  v[9] = addv(v[9], v[14]);\n  v[5] = xorv(v[5], v[10]);\n  v[6] = xorv(v[6], v[11]);\n  v[7] = xorv(v[7], v[8]);\n  v[4] = xorv(v[4], v[9]);\n  v[5] = rot12(v[5]);\n  v[6] = rot12(v[6]);\n  v[7] = rot12(v[7]);\n  v[4] = rot12(v[4]);\n  v[0] = addv(v[0], m[(size_t)MSG_SCHEDULE[r][9]]);\n  v[1] = addv(v[1], m[(size_t)MSG_SCHEDULE[r][11]]);\n  v[2] = addv(v[2], m[(size_t)MSG_SCHEDULE[r][13]]);\n  v[3] = addv(v[3], m[(size_t)MSG_SCHEDULE[r][15]]);\n  v[0] = addv(v[0], v[5]);\n  v[1] = addv(v[1], v[6]);\n  v[2] = addv(v[2], v[7]);\n  v[3] = addv(v[3], v[4]);\n  v[15] = xorv(v[15], v[0]);\n  v[12] = xorv(v[12], v[1]);\n  v[13] = xorv(v[13], v[2]);\n  v[14] = xorv(v[14], v[3]);\n  v[15] = rot8(v[15]);\n  v[12] = rot8(v[12]);\n  v[13] = rot8(v[13]);\n  v[14] = rot8(v[14]);\n  v[10] = addv(v[10], v[15]);\n  v[11] = addv(v[11], v[12]);\n  v[8] = addv(v[8], v[13]);\n  v[9] = addv(v[9], v[14]);\n  v[5] = xorv(v[5], v[10]);\n  v[6] = xorv(v[6], v[11]);\n  v[7] = xorv(v[7], v[8]);\n  v[4] = xorv(v[4], v[9]);\n  v[5] = rot7(v[5]);\n  v[6] = rot7(v[6]);\n  v[7] = rot7(v[7]);\n  v[4] = rot7(v[4]);\n}\n\nINLINE void transpose_vecs(__m128i vecs[DEGREE]) {\n  // Interleave 32-bit lates. The low unpack is lanes 00/11 and the high is\n  // 22/33. Note that this doesn't split the vector into two lanes, as the\n  // AVX2 counterparts do.\n  __m128i ab_01 = _mm_unpacklo_epi32(vecs[0], vecs[1]);\n  __m128i ab_23 = _mm_unpackhi_epi32(vecs[0], vecs[1]);\n  __m128i cd_01 = _mm_unpacklo_epi32(vecs[2], vecs[3]);\n  __m128i cd_23 = _mm_unpackhi_epi32(vecs[2], vecs[3]);\n\n  // Interleave 64-bit lanes.\n  __m128i abcd_0 = _mm_unpacklo_epi64(ab_01, cd_01);\n  __m128i abcd_1 = _mm_unpackhi_epi64(ab_01, cd_01);\n  __m128i abcd_2 = _mm_unpacklo_epi64(ab_23, cd_23);\n  __m128i abcd_3 = _mm_unpackhi_epi64(ab_23, cd_23);\n\n  vecs[0] = abcd_0;\n  vecs[1] = abcd_1;\n  vecs[2] = abcd_2;\n  vecs[3] = abcd_3;\n}\n\nINLINE void transpose_msg_vecs(const uint8_t *const *inputs,\n                               size_t block_offset, __m128i out[16]) {\n  out[0] = loadu(&inputs[0][block_offset + 0 * sizeof(__m128i)]);\n  out[1] = loadu(&inputs[1][block_offset + 0 * sizeof(__m128i)]);\n  out[2] = loadu(&inputs[2][block_offset + 0 * sizeof(__m128i)]);\n  out[3] = loadu(&inputs[3][block_offset + 0 * sizeof(__m128i)]);\n  out[4] = loadu(&inputs[0][block_offset + 1 * sizeof(__m128i)]);\n  out[5] = loadu(&inputs[1][block_offset + 1 * sizeof(__m128i)]);\n  out[6] = loadu(&inputs[2][block_offset + 1 * sizeof(__m128i)]);\n  out[7] = loadu(&inputs[3][block_offset + 1 * sizeof(__m128i)]);\n  out[8] = loadu(&inputs[0][block_offset + 2 * sizeof(__m128i)]);\n  out[9] = loadu(&inputs[1][block_offset + 2 * sizeof(__m128i)]);\n  out[10] = loadu(&inputs[2][block_offset + 2 * sizeof(__m128i)]);\n  out[11] = loadu(&inputs[3][block_offset + 2 * sizeof(__m128i)]);\n  out[12] = loadu(&inputs[0][block_offset + 3 * sizeof(__m128i)]);\n  out[13] = loadu(&inputs[1][block_offset + 3 * sizeof(__m128i)]);\n  out[14] = loadu(&inputs[2][block_offset + 3 * sizeof(__m128i)]);\n  out[15] = loadu(&inputs[3][block_offset + 3 * sizeof(__m128i)]);\n  for (size_t i = 0; i < 4; ++i) {\n    _mm_prefetch(&inputs[i][block_offset + 256], _MM_HINT_T0);\n  }\n  transpose_vecs(&out[0]);\n  transpose_vecs(&out[4]);\n  transpose_vecs(&out[8]);\n  transpose_vecs(&out[12]);\n}\n\nINLINE void load_counters(uint64_t counter, bool increment_counter,\n                          __m128i *out_lo, __m128i *out_hi) {\n  const __m128i mask = _mm_set1_epi32(-(int32_t)increment_counter);\n  const __m128i add0 = _mm_set_epi32(3, 2, 1, 0);\n  const __m128i add1 = _mm_and_si128(mask, add0);\n  __m128i l = _mm_add_epi32(_mm_set1_epi32(counter), add1);\n  __m128i carry = _mm_cmpgt_epi32(_mm_xor_si128(add1, _mm_set1_epi32(0x80000000)),\n\t\t\t\t  _mm_xor_si128(   l, _mm_set1_epi32(0x80000000)));\n  __m128i h = _mm_sub_epi32(_mm_set1_epi32(counter >> 32), carry);\n  *out_lo = l;\n  *out_hi = h;\n}\n\nvoid blake3_hash4_sse41(const uint8_t *const *inputs, size_t blocks,\n                        const uint32_t key[8], uint64_t counter,\n                        bool increment_counter, uint8_t flags,\n                        uint8_t flags_start, uint8_t flags_end, uint8_t *out) {\n  __m128i h_vecs[8] = {\n      set1(key[0]), set1(key[1]), set1(key[2]), set1(key[3]),\n      set1(key[4]), set1(key[5]), set1(key[6]), set1(key[7]),\n  };\n  __m128i counter_low_vec, counter_high_vec;\n  load_counters(counter, increment_counter, &counter_low_vec,\n                &counter_high_vec);\n  uint8_t block_flags = flags | flags_start;\n\n  for (size_t block = 0; block < blocks; block++) {\n    if (block + 1 == blocks) {\n      block_flags |= flags_end;\n    }\n    __m128i block_len_vec = set1(BLAKE3_BLOCK_LEN);\n    __m128i block_flags_vec = set1(block_flags);\n    __m128i msg_vecs[16];\n    transpose_msg_vecs(inputs, block * BLAKE3_BLOCK_LEN, msg_vecs);\n\n    __m128i v[16] = {\n        h_vecs[0],       h_vecs[1],        h_vecs[2],     h_vecs[3],\n        h_vecs[4],       h_vecs[5],        h_vecs[6],     h_vecs[7],\n        set1(IV[0]),     set1(IV[1]),      set1(IV[2]),   set1(IV[3]),\n        counter_low_vec, counter_high_vec, block_len_vec, block_flags_vec,\n    };\n    round_fn(v, msg_vecs, 0);\n    round_fn(v, msg_vecs, 1);\n    round_fn(v, msg_vecs, 2);\n    round_fn(v, msg_vecs, 3);\n    round_fn(v, msg_vecs, 4);\n    round_fn(v, msg_vecs, 5);\n    round_fn(v, msg_vecs, 6);\n    h_vecs[0] = xorv(v[0], v[8]);\n    h_vecs[1] = xorv(v[1], v[9]);\n    h_vecs[2] = xorv(v[2], v[10]);\n    h_vecs[3] = xorv(v[3], v[11]);\n    h_vecs[4] = xorv(v[4], v[12]);\n    h_vecs[5] = xorv(v[5], v[13]);\n    h_vecs[6] = xorv(v[6], v[14]);\n    h_vecs[7] = xorv(v[7], v[15]);\n\n    block_flags = flags;\n  }\n\n  transpose_vecs(&h_vecs[0]);\n  transpose_vecs(&h_vecs[4]);\n  // The first four vecs now contain the first half of each output, and the\n  // second four vecs contain the second half of each output.\n  storeu(h_vecs[0], &out[0 * sizeof(__m128i)]);\n  storeu(h_vecs[4], &out[1 * sizeof(__m128i)]);\n  storeu(h_vecs[1], &out[2 * sizeof(__m128i)]);\n  storeu(h_vecs[5], &out[3 * sizeof(__m128i)]);\n  storeu(h_vecs[2], &out[4 * sizeof(__m128i)]);\n  storeu(h_vecs[6], &out[5 * sizeof(__m128i)]);\n  storeu(h_vecs[3], &out[6 * sizeof(__m128i)]);\n  storeu(h_vecs[7], &out[7 * sizeof(__m128i)]);\n}\n\nINLINE void hash_one_sse41(const uint8_t *input, size_t blocks,\n                           const uint32_t key[8], uint64_t counter,\n                           uint8_t flags, uint8_t flags_start,\n                           uint8_t flags_end, uint8_t out[BLAKE3_OUT_LEN]) {\n  uint32_t cv[8];\n  memcpy(cv, key, BLAKE3_KEY_LEN);\n  uint8_t block_flags = flags | flags_start;\n  while (blocks > 0) {\n    if (blocks == 1) {\n      block_flags |= flags_end;\n    }\n    blake3_compress_in_place_sse41(cv, input, BLAKE3_BLOCK_LEN, counter,\n                                   block_flags);\n    input = &input[BLAKE3_BLOCK_LEN];\n    blocks -= 1;\n    block_flags = flags;\n  }\n  memcpy(out, cv, BLAKE3_OUT_LEN);\n}\n\nvoid blake3_hash_many_sse41(const uint8_t *const *inputs, size_t num_inputs,\n                            size_t blocks, const uint32_t key[8],\n                            uint64_t counter, bool increment_counter,\n                            uint8_t flags, uint8_t flags_start,\n                            uint8_t flags_end, uint8_t *out) {\n  while (num_inputs >= DEGREE) {\n    blake3_hash4_sse41(inputs, blocks, key, counter, increment_counter, flags,\n                       flags_start, flags_end, out);\n    if (increment_counter) {\n      counter += DEGREE;\n    }\n    inputs += DEGREE;\n    num_inputs -= DEGREE;\n    out = &out[DEGREE * BLAKE3_OUT_LEN];\n  }\n  while (num_inputs > 0) {\n    hash_one_sse41(inputs[0], blocks, key, counter, flags, flags_start,\n                   flags_end, out);\n    if (increment_counter) {\n      counter += 1;\n    }\n    inputs += 1;\n    num_inputs -= 1;\n    out = &out[BLAKE3_OUT_LEN];\n  }\n}\n"
  },
  {
    "path": "common/blake3/blake3_sse41_x86-64_unix.S",
    "content": "#if defined(__ELF__) && defined(__linux__)\n.section .note.GNU-stack,\"\",%progbits\n#endif\n\n#if defined(__ELF__) && defined(__CET__) && defined(__has_include)\n#if __has_include(<cet.h>)\n#include <cet.h>\n#endif\n#endif\n\n#if !defined(_CET_ENDBR)\n#define _CET_ENDBR\n#endif\n\n.intel_syntax noprefix\n.global blake3_hash_many_sse41\n.global _blake3_hash_many_sse41\n.global blake3_compress_in_place_sse41\n.global _blake3_compress_in_place_sse41\n.global blake3_compress_xof_sse41\n.global _blake3_compress_xof_sse41\n#ifdef __APPLE__\n.text\n#else\n.section .text\n#endif\n        .p2align  6\n_blake3_hash_many_sse41:\nblake3_hash_many_sse41:\n        _CET_ENDBR\n        push    r15\n        push    r14\n        push    r13\n        push    r12\n        push    rbx\n        push    rbp\n        mov     rbp, rsp\n        sub     rsp, 360\n        and     rsp, 0xFFFFFFFFFFFFFFC0\n        neg     r9d\n        movd    xmm0, r9d\n        pshufd  xmm0, xmm0, 0x00\n        movdqa  xmmword ptr [rsp+0x130], xmm0\n        movdqa  xmm1, xmm0\n        pand    xmm1, xmmword ptr [ADD0+rip]\n        pand    xmm0, xmmword ptr [ADD1+rip]\n        movdqa  xmmword ptr [rsp+0x150], xmm0\n        movd    xmm0, r8d\n        pshufd  xmm0, xmm0, 0x00\n        paddd   xmm0, xmm1\n        movdqa  xmmword ptr [rsp+0x110], xmm0\n        pxor    xmm0, xmmword ptr [CMP_MSB_MASK+rip]\n        pxor    xmm1, xmmword ptr [CMP_MSB_MASK+rip]\n        pcmpgtd xmm1, xmm0\n        shr     r8, 32\n        movd    xmm2, r8d\n        pshufd  xmm2, xmm2, 0x00\n        psubd   xmm2, xmm1\n        movdqa  xmmword ptr [rsp+0x120], xmm2\n        mov     rbx, qword ptr [rbp+0x50]\n        mov     r15, rdx\n        shl     r15, 6\n        movzx   r13d, byte ptr [rbp+0x38]\n        movzx   r12d, byte ptr [rbp+0x48]\n        cmp     rsi, 4\n        jc      3f\n2:\n        movdqu  xmm3, xmmword ptr [rcx]\n        pshufd  xmm0, xmm3, 0x00\n        pshufd  xmm1, xmm3, 0x55\n        pshufd  xmm2, xmm3, 0xAA\n        pshufd  xmm3, xmm3, 0xFF\n        movdqu  xmm7, xmmword ptr [rcx+0x10]\n        pshufd  xmm4, xmm7, 0x00\n        pshufd  xmm5, xmm7, 0x55\n        pshufd  xmm6, xmm7, 0xAA\n        pshufd  xmm7, xmm7, 0xFF\n        mov     r8, qword ptr [rdi]\n        mov     r9, qword ptr [rdi+0x8]\n        mov     r10, qword ptr [rdi+0x10]\n        mov     r11, qword ptr [rdi+0x18]\n        movzx   eax, byte ptr [rbp+0x40]\n        or      eax, r13d\n        xor     edx, edx\n9:\n        mov     r14d, eax\n        or      eax, r12d\n        add     rdx, 64\n        cmp     rdx, r15\n        cmovne  eax, r14d\n        movdqu  xmm8, xmmword ptr [r8+rdx-0x40]\n        movdqu  xmm9, xmmword ptr [r9+rdx-0x40]\n        movdqu  xmm10, xmmword ptr [r10+rdx-0x40]\n        movdqu  xmm11, xmmword ptr [r11+rdx-0x40]\n        movdqa  xmm12, xmm8\n        punpckldq xmm8, xmm9\n        punpckhdq xmm12, xmm9\n        movdqa  xmm14, xmm10\n        punpckldq xmm10, xmm11\n        punpckhdq xmm14, xmm11\n        movdqa  xmm9, xmm8\n        punpcklqdq xmm8, xmm10\n        punpckhqdq xmm9, xmm10\n        movdqa  xmm13, xmm12\n        punpcklqdq xmm12, xmm14\n        punpckhqdq xmm13, xmm14\n        movdqa  xmmword ptr [rsp], xmm8\n        movdqa  xmmword ptr [rsp+0x10], xmm9\n        movdqa  xmmword ptr [rsp+0x20], xmm12\n        movdqa  xmmword ptr [rsp+0x30], xmm13\n        movdqu  xmm8, xmmword ptr [r8+rdx-0x30]\n        movdqu  xmm9, xmmword ptr [r9+rdx-0x30]\n        movdqu  xmm10, xmmword ptr [r10+rdx-0x30]\n        movdqu  xmm11, xmmword ptr [r11+rdx-0x30]\n        movdqa  xmm12, xmm8\n        punpckldq xmm8, xmm9\n        punpckhdq xmm12, xmm9\n        movdqa  xmm14, xmm10\n        punpckldq xmm10, xmm11\n        punpckhdq xmm14, xmm11\n        movdqa  xmm9, xmm8\n        punpcklqdq xmm8, xmm10\n        punpckhqdq xmm9, xmm10\n        movdqa  xmm13, xmm12\n        punpcklqdq xmm12, xmm14\n        punpckhqdq xmm13, xmm14\n        movdqa  xmmword ptr [rsp+0x40], xmm8\n        movdqa  xmmword ptr [rsp+0x50], xmm9\n        movdqa  xmmword ptr [rsp+0x60], xmm12\n        movdqa  xmmword ptr [rsp+0x70], xmm13\n        movdqu  xmm8, xmmword ptr [r8+rdx-0x20]\n        movdqu  xmm9, xmmword ptr [r9+rdx-0x20]\n        movdqu  xmm10, xmmword ptr [r10+rdx-0x20]\n        movdqu  xmm11, xmmword ptr [r11+rdx-0x20]\n        movdqa  xmm12, xmm8\n        punpckldq xmm8, xmm9\n        punpckhdq xmm12, xmm9\n        movdqa  xmm14, xmm10\n        punpckldq xmm10, xmm11\n        punpckhdq xmm14, xmm11\n        movdqa  xmm9, xmm8\n        punpcklqdq xmm8, xmm10\n        punpckhqdq xmm9, xmm10\n        movdqa  xmm13, xmm12\n        punpcklqdq xmm12, xmm14\n        punpckhqdq xmm13, xmm14\n        movdqa  xmmword ptr [rsp+0x80], xmm8\n        movdqa  xmmword ptr [rsp+0x90], xmm9\n        movdqa  xmmword ptr [rsp+0xA0], xmm12\n        movdqa  xmmword ptr [rsp+0xB0], xmm13\n        movdqu  xmm8, xmmword ptr [r8+rdx-0x10]\n        movdqu  xmm9, xmmword ptr [r9+rdx-0x10]\n        movdqu  xmm10, xmmword ptr [r10+rdx-0x10]\n        movdqu  xmm11, xmmword ptr [r11+rdx-0x10]\n        movdqa  xmm12, xmm8\n        punpckldq xmm8, xmm9\n        punpckhdq xmm12, xmm9\n        movdqa  xmm14, xmm10\n        punpckldq xmm10, xmm11\n        punpckhdq xmm14, xmm11\n        movdqa  xmm9, xmm8\n        punpcklqdq xmm8, xmm10\n        punpckhqdq xmm9, xmm10\n        movdqa  xmm13, xmm12\n        punpcklqdq xmm12, xmm14\n        punpckhqdq xmm13, xmm14\n        movdqa  xmmword ptr [rsp+0xC0], xmm8\n        movdqa  xmmword ptr [rsp+0xD0], xmm9\n        movdqa  xmmword ptr [rsp+0xE0], xmm12\n        movdqa  xmmword ptr [rsp+0xF0], xmm13\n        movdqa  xmm9, xmmword ptr [BLAKE3_IV_1+rip]\n        movdqa  xmm10, xmmword ptr [BLAKE3_IV_2+rip]\n        movdqa  xmm11, xmmword ptr [BLAKE3_IV_3+rip]\n        movdqa  xmm12, xmmword ptr [rsp+0x110]\n        movdqa  xmm13, xmmword ptr [rsp+0x120]\n        movdqa  xmm14, xmmword ptr [BLAKE3_BLOCK_LEN+rip]\n        movd    xmm15, eax\n        pshufd  xmm15, xmm15, 0x00\n        prefetcht0 [r8+rdx+0x80]\n        prefetcht0 [r9+rdx+0x80]\n        prefetcht0 [r10+rdx+0x80]\n        prefetcht0 [r11+rdx+0x80]\n        paddd   xmm0, xmmword ptr [rsp]\n        paddd   xmm1, xmmword ptr [rsp+0x20]\n        paddd   xmm2, xmmword ptr [rsp+0x40]\n        paddd   xmm3, xmmword ptr [rsp+0x60]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmmword ptr [ROT16+rip]\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        pshufb  xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [BLAKE3_IV_0+rip]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x10]\n        paddd   xmm1, xmmword ptr [rsp+0x30]\n        paddd   xmm2, xmmword ptr [rsp+0x50]\n        paddd   xmm3, xmmword ptr [rsp+0x70]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmmword ptr [ROT8+rip]\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        pshufb  xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x80]\n        paddd   xmm1, xmmword ptr [rsp+0xA0]\n        paddd   xmm2, xmmword ptr [rsp+0xC0]\n        paddd   xmm3, xmmword ptr [rsp+0xE0]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmmword ptr [ROT16+rip]\n        pshufb  xmm15, xmm8\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x90]\n        paddd   xmm1, xmmword ptr [rsp+0xB0]\n        paddd   xmm2, xmmword ptr [rsp+0xD0]\n        paddd   xmm3, xmmword ptr [rsp+0xF0]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmmword ptr [ROT8+rip]\n        pshufb  xmm15, xmm8\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x20]\n        paddd   xmm1, xmmword ptr [rsp+0x30]\n        paddd   xmm2, xmmword ptr [rsp+0x70]\n        paddd   xmm3, xmmword ptr [rsp+0x40]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmmword ptr [ROT16+rip]\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        pshufb  xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x60]\n        paddd   xmm1, xmmword ptr [rsp+0xA0]\n        paddd   xmm2, xmmword ptr [rsp]\n        paddd   xmm3, xmmword ptr [rsp+0xD0]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmmword ptr [ROT8+rip]\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        pshufb  xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x10]\n        paddd   xmm1, xmmword ptr [rsp+0xC0]\n        paddd   xmm2, xmmword ptr [rsp+0x90]\n        paddd   xmm3, xmmword ptr [rsp+0xF0]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmmword ptr [ROT16+rip]\n        pshufb  xmm15, xmm8\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xB0]\n        paddd   xmm1, xmmword ptr [rsp+0x50]\n        paddd   xmm2, xmmword ptr [rsp+0xE0]\n        paddd   xmm3, xmmword ptr [rsp+0x80]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmmword ptr [ROT8+rip]\n        pshufb  xmm15, xmm8\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x30]\n        paddd   xmm1, xmmword ptr [rsp+0xA0]\n        paddd   xmm2, xmmword ptr [rsp+0xD0]\n        paddd   xmm3, xmmword ptr [rsp+0x70]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmmword ptr [ROT16+rip]\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        pshufb  xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x40]\n        paddd   xmm1, xmmword ptr [rsp+0xC0]\n        paddd   xmm2, xmmword ptr [rsp+0x20]\n        paddd   xmm3, xmmword ptr [rsp+0xE0]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmmword ptr [ROT8+rip]\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        pshufb  xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x60]\n        paddd   xmm1, xmmword ptr [rsp+0x90]\n        paddd   xmm2, xmmword ptr [rsp+0xB0]\n        paddd   xmm3, xmmword ptr [rsp+0x80]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmmword ptr [ROT16+rip]\n        pshufb  xmm15, xmm8\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x50]\n        paddd   xmm1, xmmword ptr [rsp]\n        paddd   xmm2, xmmword ptr [rsp+0xF0]\n        paddd   xmm3, xmmword ptr [rsp+0x10]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmmword ptr [ROT8+rip]\n        pshufb  xmm15, xmm8\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xA0]\n        paddd   xmm1, xmmword ptr [rsp+0xC0]\n        paddd   xmm2, xmmword ptr [rsp+0xE0]\n        paddd   xmm3, xmmword ptr [rsp+0xD0]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmmword ptr [ROT16+rip]\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        pshufb  xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x70]\n        paddd   xmm1, xmmword ptr [rsp+0x90]\n        paddd   xmm2, xmmword ptr [rsp+0x30]\n        paddd   xmm3, xmmword ptr [rsp+0xF0]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmmword ptr [ROT8+rip]\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        pshufb  xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x40]\n        paddd   xmm1, xmmword ptr [rsp+0xB0]\n        paddd   xmm2, xmmword ptr [rsp+0x50]\n        paddd   xmm3, xmmword ptr [rsp+0x10]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmmword ptr [ROT16+rip]\n        pshufb  xmm15, xmm8\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp]\n        paddd   xmm1, xmmword ptr [rsp+0x20]\n        paddd   xmm2, xmmword ptr [rsp+0x80]\n        paddd   xmm3, xmmword ptr [rsp+0x60]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmmword ptr [ROT8+rip]\n        pshufb  xmm15, xmm8\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xC0]\n        paddd   xmm1, xmmword ptr [rsp+0x90]\n        paddd   xmm2, xmmword ptr [rsp+0xF0]\n        paddd   xmm3, xmmword ptr [rsp+0xE0]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmmword ptr [ROT16+rip]\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        pshufb  xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xD0]\n        paddd   xmm1, xmmword ptr [rsp+0xB0]\n        paddd   xmm2, xmmword ptr [rsp+0xA0]\n        paddd   xmm3, xmmword ptr [rsp+0x80]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmmword ptr [ROT8+rip]\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        pshufb  xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x70]\n        paddd   xmm1, xmmword ptr [rsp+0x50]\n        paddd   xmm2, xmmword ptr [rsp]\n        paddd   xmm3, xmmword ptr [rsp+0x60]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmmword ptr [ROT16+rip]\n        pshufb  xmm15, xmm8\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x20]\n        paddd   xmm1, xmmword ptr [rsp+0x30]\n        paddd   xmm2, xmmword ptr [rsp+0x10]\n        paddd   xmm3, xmmword ptr [rsp+0x40]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmmword ptr [ROT8+rip]\n        pshufb  xmm15, xmm8\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x90]\n        paddd   xmm1, xmmword ptr [rsp+0xB0]\n        paddd   xmm2, xmmword ptr [rsp+0x80]\n        paddd   xmm3, xmmword ptr [rsp+0xF0]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmmword ptr [ROT16+rip]\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        pshufb  xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xE0]\n        paddd   xmm1, xmmword ptr [rsp+0x50]\n        paddd   xmm2, xmmword ptr [rsp+0xC0]\n        paddd   xmm3, xmmword ptr [rsp+0x10]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmmword ptr [ROT8+rip]\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        pshufb  xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xD0]\n        paddd   xmm1, xmmword ptr [rsp]\n        paddd   xmm2, xmmword ptr [rsp+0x20]\n        paddd   xmm3, xmmword ptr [rsp+0x40]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmmword ptr [ROT16+rip]\n        pshufb  xmm15, xmm8\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0x30]\n        paddd   xmm1, xmmword ptr [rsp+0xA0]\n        paddd   xmm2, xmmword ptr [rsp+0x60]\n        paddd   xmm3, xmmword ptr [rsp+0x70]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmmword ptr [ROT8+rip]\n        pshufb  xmm15, xmm8\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xB0]\n        paddd   xmm1, xmmword ptr [rsp+0x50]\n        paddd   xmm2, xmmword ptr [rsp+0x10]\n        paddd   xmm3, xmmword ptr [rsp+0x80]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmmword ptr [ROT16+rip]\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        pshufb  xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xF0]\n        paddd   xmm1, xmmword ptr [rsp]\n        paddd   xmm2, xmmword ptr [rsp+0x90]\n        paddd   xmm3, xmmword ptr [rsp+0x60]\n        paddd   xmm0, xmm4\n        paddd   xmm1, xmm5\n        paddd   xmm2, xmm6\n        paddd   xmm3, xmm7\n        pxor    xmm12, xmm0\n        pxor    xmm13, xmm1\n        pxor    xmm14, xmm2\n        pxor    xmm15, xmm3\n        movdqa  xmm8, xmmword ptr [ROT8+rip]\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        pshufb  xmm15, xmm8\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm12\n        paddd   xmm9, xmm13\n        paddd   xmm10, xmm14\n        paddd   xmm11, xmm15\n        pxor    xmm4, xmm8\n        pxor    xmm5, xmm9\n        pxor    xmm6, xmm10\n        pxor    xmm7, xmm11\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xE0]\n        paddd   xmm1, xmmword ptr [rsp+0x20]\n        paddd   xmm2, xmmword ptr [rsp+0x30]\n        paddd   xmm3, xmmword ptr [rsp+0x70]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmmword ptr [ROT16+rip]\n        pshufb  xmm15, xmm8\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        movdqa  xmmword ptr [rsp+0x100], xmm8\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 12\n        pslld   xmm5, 20\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 12\n        pslld   xmm6, 20\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 12\n        pslld   xmm7, 20\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 12\n        pslld   xmm4, 20\n        por     xmm4, xmm8\n        paddd   xmm0, xmmword ptr [rsp+0xA0]\n        paddd   xmm1, xmmword ptr [rsp+0xC0]\n        paddd   xmm2, xmmword ptr [rsp+0x40]\n        paddd   xmm3, xmmword ptr [rsp+0xD0]\n        paddd   xmm0, xmm5\n        paddd   xmm1, xmm6\n        paddd   xmm2, xmm7\n        paddd   xmm3, xmm4\n        pxor    xmm15, xmm0\n        pxor    xmm12, xmm1\n        pxor    xmm13, xmm2\n        pxor    xmm14, xmm3\n        movdqa  xmm8, xmmword ptr [ROT8+rip]\n        pshufb  xmm15, xmm8\n        pshufb  xmm12, xmm8\n        pshufb  xmm13, xmm8\n        pshufb  xmm14, xmm8\n        paddd   xmm10, xmm15\n        paddd   xmm11, xmm12\n        movdqa  xmm8, xmmword ptr [rsp+0x100]\n        paddd   xmm8, xmm13\n        paddd   xmm9, xmm14\n        pxor    xmm5, xmm10\n        pxor    xmm6, xmm11\n        pxor    xmm7, xmm8\n        pxor    xmm4, xmm9\n        pxor    xmm0, xmm8\n        pxor    xmm1, xmm9\n        pxor    xmm2, xmm10\n        pxor    xmm3, xmm11\n        movdqa  xmm8, xmm5\n        psrld   xmm8, 7\n        pslld   xmm5, 25\n        por     xmm5, xmm8\n        movdqa  xmm8, xmm6\n        psrld   xmm8, 7\n        pslld   xmm6, 25\n        por     xmm6, xmm8\n        movdqa  xmm8, xmm7\n        psrld   xmm8, 7\n        pslld   xmm7, 25\n        por     xmm7, xmm8\n        movdqa  xmm8, xmm4\n        psrld   xmm8, 7\n        pslld   xmm4, 25\n        por     xmm4, xmm8\n        pxor    xmm4, xmm12\n        pxor    xmm5, xmm13\n        pxor    xmm6, xmm14\n        pxor    xmm7, xmm15\n        mov     eax, r13d\n        jne     9b\n        movdqa  xmm9, xmm0\n        punpckldq xmm0, xmm1\n        punpckhdq xmm9, xmm1\n        movdqa  xmm11, xmm2\n        punpckldq xmm2, xmm3\n        punpckhdq xmm11, xmm3\n        movdqa  xmm1, xmm0\n        punpcklqdq xmm0, xmm2\n        punpckhqdq xmm1, xmm2\n        movdqa  xmm3, xmm9\n        punpcklqdq xmm9, xmm11\n        punpckhqdq xmm3, xmm11\n        movdqu  xmmword ptr [rbx], xmm0\n        movdqu  xmmword ptr [rbx+0x20], xmm1\n        movdqu  xmmword ptr [rbx+0x40], xmm9\n        movdqu  xmmword ptr [rbx+0x60], xmm3\n        movdqa  xmm9, xmm4\n        punpckldq xmm4, xmm5\n        punpckhdq xmm9, xmm5\n        movdqa  xmm11, xmm6\n        punpckldq xmm6, xmm7\n        punpckhdq xmm11, xmm7\n        movdqa  xmm5, xmm4\n        punpcklqdq xmm4, xmm6\n        punpckhqdq xmm5, xmm6\n        movdqa  xmm7, xmm9\n        punpcklqdq xmm9, xmm11\n        punpckhqdq xmm7, xmm11\n        movdqu  xmmword ptr [rbx+0x10], xmm4\n        movdqu  xmmword ptr [rbx+0x30], xmm5\n        movdqu  xmmword ptr [rbx+0x50], xmm9\n        movdqu  xmmword ptr [rbx+0x70], xmm7\n        movdqa  xmm1, xmmword ptr [rsp+0x110]\n        movdqa  xmm0, xmm1\n        paddd   xmm1, xmmword ptr [rsp+0x150]\n        movdqa  xmmword ptr [rsp+0x110], xmm1\n        pxor    xmm0, xmmword ptr [CMP_MSB_MASK+rip]\n        pxor    xmm1, xmmword ptr [CMP_MSB_MASK+rip]\n        pcmpgtd xmm0, xmm1\n        movdqa  xmm1, xmmword ptr [rsp+0x120]\n        psubd   xmm1, xmm0\n        movdqa  xmmword ptr [rsp+0x120], xmm1\n        add     rbx, 128\n        add     rdi, 32\n        sub     rsi, 4\n        cmp     rsi, 4\n        jnc     2b\n        test    rsi, rsi\n        jnz     3f\n4:\n        mov     rsp, rbp\n        pop     rbp\n        pop     rbx\n        pop     r12\n        pop     r13\n        pop     r14\n        pop     r15\n        ret\n.p2align 5\n3:\n        test    esi, 0x2\n        je      3f\n        movups  xmm0, xmmword ptr [rcx]\n        movups  xmm1, xmmword ptr [rcx+0x10]\n        movaps  xmm8, xmm0\n        movaps  xmm9, xmm1\n        movd    xmm13, dword ptr [rsp+0x110]\n        pinsrd  xmm13, dword ptr [rsp+0x120], 1\n        pinsrd  xmm13, dword ptr [BLAKE3_BLOCK_LEN+rip], 2\n        movaps  xmmword ptr [rsp], xmm13\n        movd    xmm14, dword ptr [rsp+0x114]\n        pinsrd  xmm14, dword ptr [rsp+0x124], 1\n        pinsrd  xmm14, dword ptr [BLAKE3_BLOCK_LEN+rip], 2\n        movaps  xmmword ptr [rsp+0x10], xmm14\n        mov     r8, qword ptr [rdi]\n        mov     r9, qword ptr [rdi+0x8]\n        movzx   eax, byte ptr [rbp+0x40]\n        or      eax, r13d\n        xor     edx, edx\n2:\n        mov     r14d, eax\n        or      eax, r12d\n        add     rdx, 64\n        cmp     rdx, r15\n        cmovne  eax, r14d\n        movaps  xmm2, xmmword ptr [BLAKE3_IV+rip]\n        movaps  xmm10, xmm2\n        movups  xmm4, xmmword ptr [r8+rdx-0x40]\n        movups  xmm5, xmmword ptr [r8+rdx-0x30]\n        movaps  xmm3, xmm4\n        shufps  xmm4, xmm5, 136\n        shufps  xmm3, xmm5, 221\n        movaps  xmm5, xmm3\n        movups  xmm6, xmmword ptr [r8+rdx-0x20]\n        movups  xmm7, xmmword ptr [r8+rdx-0x10]\n        movaps  xmm3, xmm6\n        shufps  xmm6, xmm7, 136\n        pshufd  xmm6, xmm6, 0x93\n        shufps  xmm3, xmm7, 221\n        pshufd  xmm7, xmm3, 0x93\n        movups  xmm12, xmmword ptr [r9+rdx-0x40]\n        movups  xmm13, xmmword ptr [r9+rdx-0x30]\n        movaps  xmm11, xmm12\n        shufps  xmm12, xmm13, 136\n        shufps  xmm11, xmm13, 221\n        movaps  xmm13, xmm11\n        movups  xmm14, xmmword ptr [r9+rdx-0x20]\n        movups  xmm15, xmmword ptr [r9+rdx-0x10]\n        movaps  xmm11, xmm14\n        shufps  xmm14, xmm15, 136\n        pshufd  xmm14, xmm14, 0x93\n        shufps  xmm11, xmm15, 221\n        pshufd  xmm15, xmm11, 0x93\n        movaps  xmm3, xmmword ptr [rsp]\n        movaps  xmm11, xmmword ptr [rsp+0x10]\n        pinsrd  xmm3, eax, 3\n        pinsrd  xmm11, eax, 3\n        mov     al, 7\n9:\n        paddd   xmm0, xmm4\n        paddd   xmm8, xmm12\n        movaps  xmmword ptr [rsp+0x20], xmm4\n        movaps  xmmword ptr [rsp+0x30], xmm12\n        paddd   xmm0, xmm1\n        paddd   xmm8, xmm9\n        pxor    xmm3, xmm0\n        pxor    xmm11, xmm8\n        movaps  xmm12, xmmword ptr [ROT16+rip]\n        pshufb  xmm3, xmm12\n        pshufb  xmm11, xmm12\n        paddd   xmm2, xmm3\n        paddd   xmm10, xmm11\n        pxor    xmm1, xmm2\n        pxor    xmm9, xmm10\n        movdqa  xmm4, xmm1\n        pslld   xmm1, 20\n        psrld   xmm4, 12\n        por     xmm1, xmm4\n        movdqa  xmm4, xmm9\n        pslld   xmm9, 20\n        psrld   xmm4, 12\n        por     xmm9, xmm4\n        paddd   xmm0, xmm5\n        paddd   xmm8, xmm13\n        movaps  xmmword ptr [rsp+0x40], xmm5\n        movaps  xmmword ptr [rsp+0x50], xmm13\n        paddd   xmm0, xmm1\n        paddd   xmm8, xmm9\n        pxor    xmm3, xmm0\n        pxor    xmm11, xmm8\n        movaps  xmm13, xmmword ptr [ROT8+rip]\n        pshufb  xmm3, xmm13\n        pshufb  xmm11, xmm13\n        paddd   xmm2, xmm3\n        paddd   xmm10, xmm11\n        pxor    xmm1, xmm2\n        pxor    xmm9, xmm10\n        movdqa  xmm4, xmm1\n        pslld   xmm1, 25\n        psrld   xmm4, 7\n        por     xmm1, xmm4\n        movdqa  xmm4, xmm9\n        pslld   xmm9, 25\n        psrld   xmm4, 7\n        por     xmm9, xmm4\n        pshufd  xmm0, xmm0, 0x93\n        pshufd  xmm8, xmm8, 0x93\n        pshufd  xmm3, xmm3, 0x4E\n        pshufd  xmm11, xmm11, 0x4E\n        pshufd  xmm2, xmm2, 0x39\n        pshufd  xmm10, xmm10, 0x39\n        paddd   xmm0, xmm6\n        paddd   xmm8, xmm14\n        paddd   xmm0, xmm1\n        paddd   xmm8, xmm9\n        pxor    xmm3, xmm0\n        pxor    xmm11, xmm8\n        pshufb  xmm3, xmm12\n        pshufb  xmm11, xmm12\n        paddd   xmm2, xmm3\n        paddd   xmm10, xmm11\n        pxor    xmm1, xmm2\n        pxor    xmm9, xmm10\n        movdqa  xmm4, xmm1\n        pslld   xmm1, 20\n        psrld   xmm4, 12\n        por     xmm1, xmm4\n        movdqa  xmm4, xmm9\n        pslld   xmm9, 20\n        psrld   xmm4, 12\n        por     xmm9, xmm4\n        paddd   xmm0, xmm7\n        paddd   xmm8, xmm15\n        paddd   xmm0, xmm1\n        paddd   xmm8, xmm9\n        pxor    xmm3, xmm0\n        pxor    xmm11, xmm8\n        pshufb  xmm3, xmm13\n        pshufb  xmm11, xmm13\n        paddd   xmm2, xmm3\n        paddd   xmm10, xmm11\n        pxor    xmm1, xmm2\n        pxor    xmm9, xmm10\n        movdqa  xmm4, xmm1\n        pslld   xmm1, 25\n        psrld   xmm4, 7\n        por     xmm1, xmm4\n        movdqa  xmm4, xmm9\n        pslld   xmm9, 25\n        psrld   xmm4, 7\n        por     xmm9, xmm4\n        pshufd  xmm0, xmm0, 0x39\n        pshufd  xmm8, xmm8, 0x39\n        pshufd  xmm3, xmm3, 0x4E\n        pshufd  xmm11, xmm11, 0x4E\n        pshufd  xmm2, xmm2, 0x93\n        pshufd  xmm10, xmm10, 0x93\n        dec     al\n        je      9f\n        movdqa  xmm12, xmmword ptr [rsp+0x20]\n        movdqa  xmm5, xmmword ptr [rsp+0x40]\n        pshufd  xmm13, xmm12, 0x0F\n        shufps  xmm12, xmm5, 214\n        pshufd  xmm4, xmm12, 0x39\n        movdqa  xmm12, xmm6\n        shufps  xmm12, xmm7, 250\n        pblendw xmm13, xmm12, 0xCC\n        movdqa  xmm12, xmm7\n        punpcklqdq xmm12, xmm5\n        pblendw xmm12, xmm6, 0xC0\n        pshufd  xmm12, xmm12, 0x78\n        punpckhdq xmm5, xmm7\n        punpckldq xmm6, xmm5\n        pshufd  xmm7, xmm6, 0x1E\n        movdqa  xmmword ptr [rsp+0x20], xmm13\n        movdqa  xmmword ptr [rsp+0x40], xmm12\n        movdqa  xmm5, xmmword ptr [rsp+0x30]\n        movdqa  xmm13, xmmword ptr [rsp+0x50]\n        pshufd  xmm6, xmm5, 0x0F\n        shufps  xmm5, xmm13, 214\n        pshufd  xmm12, xmm5, 0x39\n        movdqa  xmm5, xmm14\n        shufps  xmm5, xmm15, 250\n        pblendw xmm6, xmm5, 0xCC\n        movdqa  xmm5, xmm15\n        punpcklqdq xmm5, xmm13\n        pblendw xmm5, xmm14, 0xC0\n        pshufd  xmm5, xmm5, 0x78\n        punpckhdq xmm13, xmm15\n        punpckldq xmm14, xmm13\n        pshufd  xmm15, xmm14, 0x1E\n        movdqa  xmm13, xmm6\n        movdqa  xmm14, xmm5\n        movdqa  xmm5, xmmword ptr [rsp+0x20]\n        movdqa  xmm6, xmmword ptr [rsp+0x40]\n        jmp     9b\n9:\n        pxor    xmm0, xmm2\n        pxor    xmm1, xmm3\n        pxor    xmm8, xmm10\n        pxor    xmm9, xmm11\n        mov     eax, r13d\n        cmp     rdx, r15\n        jne     2b\n        movups  xmmword ptr [rbx], xmm0\n        movups  xmmword ptr [rbx+0x10], xmm1\n        movups  xmmword ptr [rbx+0x20], xmm8\n        movups  xmmword ptr [rbx+0x30], xmm9\n        movdqa  xmm0, xmmword ptr [rsp+0x130]\n        movdqa  xmm1, xmmword ptr [rsp+0x110]\n        movdqa  xmm2, xmmword ptr [rsp+0x120]\n        movdqu  xmm3, xmmword ptr [rsp+0x118]\n        movdqu  xmm4, xmmword ptr [rsp+0x128]\n        blendvps xmm1, xmm3, xmm0\n        blendvps xmm2, xmm4, xmm0\n        movdqa  xmmword ptr [rsp+0x110], xmm1\n        movdqa  xmmword ptr [rsp+0x120], xmm2\n        add     rdi, 16\n        add     rbx, 64\n        sub     rsi, 2\n3:\n        test    esi, 0x1\n        je      4b\n        movups  xmm0, xmmword ptr [rcx]\n        movups  xmm1, xmmword ptr [rcx+0x10]\n        movd    xmm13, dword ptr [rsp+0x110]\n        pinsrd  xmm13, dword ptr [rsp+0x120], 1\n        pinsrd  xmm13, dword ptr [BLAKE3_BLOCK_LEN+rip], 2\n        movaps  xmm14, xmmword ptr [ROT8+rip]\n        movaps  xmm15, xmmword ptr [ROT16+rip]\n        mov     r8, qword ptr [rdi]\n        movzx   eax, byte ptr [rbp+0x40]\n        or      eax, r13d\n        xor     edx, edx\n2:\n        mov     r14d, eax\n        or      eax, r12d\n        add     rdx, 64\n        cmp     rdx, r15\n        cmovne  eax, r14d\n        movaps  xmm2, xmmword ptr [BLAKE3_IV+rip]\n        movaps  xmm3, xmm13\n        pinsrd  xmm3, eax, 3\n        movups  xmm4, xmmword ptr [r8+rdx-0x40]\n        movups  xmm5, xmmword ptr [r8+rdx-0x30]\n        movaps  xmm8, xmm4\n        shufps  xmm4, xmm5, 136\n        shufps  xmm8, xmm5, 221\n        movaps  xmm5, xmm8\n        movups  xmm6, xmmword ptr [r8+rdx-0x20]\n        movups  xmm7, xmmword ptr [r8+rdx-0x10]\n        movaps  xmm8, xmm6\n        shufps  xmm6, xmm7, 136\n        pshufd  xmm6, xmm6, 0x93\n        shufps  xmm8, xmm7, 221\n        pshufd  xmm7, xmm8, 0x93\n        mov     al, 7\n9:\n        paddd   xmm0, xmm4\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshufb  xmm3, xmm15\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 20\n        psrld   xmm11, 12\n        por     xmm1, xmm11\n        paddd   xmm0, xmm5\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshufb  xmm3, xmm14\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 25\n        psrld   xmm11, 7\n        por     xmm1, xmm11\n        pshufd  xmm0, xmm0, 0x93\n        pshufd  xmm3, xmm3, 0x4E\n        pshufd  xmm2, xmm2, 0x39\n        paddd   xmm0, xmm6\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshufb  xmm3, xmm15\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 20\n        psrld   xmm11, 12\n        por     xmm1, xmm11\n        paddd   xmm0, xmm7\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshufb  xmm3, xmm14\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 25\n        psrld   xmm11, 7\n        por     xmm1, xmm11\n        pshufd  xmm0, xmm0, 0x39\n        pshufd  xmm3, xmm3, 0x4E\n        pshufd  xmm2, xmm2, 0x93\n        dec     al\n        jz      9f\n        movdqa  xmm8, xmm4\n        shufps  xmm8, xmm5, 214\n        pshufd  xmm9, xmm4, 0x0F\n        pshufd  xmm4, xmm8, 0x39\n        movdqa  xmm8, xmm6\n        shufps  xmm8, xmm7, 250\n        pblendw xmm9, xmm8, 0xCC\n        movdqa  xmm8, xmm7\n        punpcklqdq xmm8, xmm5\n        pblendw xmm8, xmm6, 0xC0\n        pshufd  xmm8, xmm8, 0x78\n        punpckhdq xmm5, xmm7\n        punpckldq xmm6, xmm5\n        pshufd  xmm7, xmm6, 0x1E\n        movdqa  xmm5, xmm9\n        movdqa  xmm6, xmm8\n        jmp     9b\n9:\n        pxor    xmm0, xmm2\n        pxor    xmm1, xmm3\n        mov     eax, r13d\n        cmp     rdx, r15\n        jne     2b\n        movups  xmmword ptr [rbx], xmm0\n        movups  xmmword ptr [rbx+0x10], xmm1\n        jmp     4b\n\n.p2align 6\nblake3_compress_in_place_sse41:\n_blake3_compress_in_place_sse41:\n        _CET_ENDBR\n        movups  xmm0, xmmword ptr [rdi]\n        movups  xmm1, xmmword ptr [rdi+0x10]\n        movaps  xmm2, xmmword ptr [BLAKE3_IV+rip]\n        shl     r8, 32\n        add     rdx, r8\n        movq    xmm3, rcx\n        movq    xmm4, rdx\n        punpcklqdq xmm3, xmm4\n        movups  xmm4, xmmword ptr [rsi]\n        movups  xmm5, xmmword ptr [rsi+0x10]\n        movaps  xmm8, xmm4\n        shufps  xmm4, xmm5, 136\n        shufps  xmm8, xmm5, 221\n        movaps  xmm5, xmm8\n        movups  xmm6, xmmword ptr [rsi+0x20]\n        movups  xmm7, xmmword ptr [rsi+0x30]\n        movaps  xmm8, xmm6\n        shufps  xmm6, xmm7, 136\n        pshufd  xmm6, xmm6, 0x93\n        shufps  xmm8, xmm7, 221\n        pshufd  xmm7, xmm8, 0x93\n        movaps  xmm14, xmmword ptr [ROT8+rip]\n        movaps  xmm15, xmmword ptr [ROT16+rip]\n        mov     al, 7\n9:\n        paddd   xmm0, xmm4\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshufb  xmm3, xmm15\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 20\n        psrld   xmm11, 12\n        por     xmm1, xmm11\n        paddd   xmm0, xmm5\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshufb  xmm3, xmm14\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 25\n        psrld   xmm11, 7\n        por     xmm1, xmm11\n        pshufd  xmm0, xmm0, 0x93\n        pshufd  xmm3, xmm3, 0x4E\n        pshufd  xmm2, xmm2, 0x39\n        paddd   xmm0, xmm6\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshufb  xmm3, xmm15\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 20\n        psrld   xmm11, 12\n        por     xmm1, xmm11\n        paddd   xmm0, xmm7\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshufb  xmm3, xmm14\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 25\n        psrld   xmm11, 7\n        por     xmm1, xmm11\n        pshufd  xmm0, xmm0, 0x39\n        pshufd  xmm3, xmm3, 0x4E\n        pshufd  xmm2, xmm2, 0x93\n        dec     al\n        jz      9f\n        movdqa  xmm8, xmm4\n        shufps  xmm8, xmm5, 214\n        pshufd  xmm9, xmm4, 0x0F\n        pshufd  xmm4, xmm8, 0x39\n        movdqa  xmm8, xmm6\n        shufps  xmm8, xmm7, 250\n        pblendw xmm9, xmm8, 0xCC\n        movdqa  xmm8, xmm7\n        punpcklqdq xmm8, xmm5\n        pblendw xmm8, xmm6, 0xC0\n        pshufd  xmm8, xmm8, 0x78\n        punpckhdq xmm5, xmm7\n        punpckldq xmm6, xmm5\n        pshufd  xmm7, xmm6, 0x1E\n        movdqa  xmm5, xmm9\n        movdqa  xmm6, xmm8\n        jmp     9b\n9:\n        pxor    xmm0, xmm2\n        pxor    xmm1, xmm3\n        movups  xmmword ptr [rdi], xmm0\n        movups  xmmword ptr [rdi+0x10], xmm1\n        ret\n\n.p2align 6\nblake3_compress_xof_sse41:\n_blake3_compress_xof_sse41:\n        _CET_ENDBR\n        movups  xmm0, xmmword ptr [rdi]\n        movups  xmm1, xmmword ptr [rdi+0x10]\n        movaps  xmm2, xmmword ptr [BLAKE3_IV+rip]\n        movzx   eax, r8b\n        movzx   edx, dl\n        shl     rax, 32\n        add     rdx, rax\n        movq    xmm3, rcx\n        movq    xmm4, rdx\n        punpcklqdq xmm3, xmm4\n        movups  xmm4, xmmword ptr [rsi]\n        movups  xmm5, xmmword ptr [rsi+0x10]\n        movaps  xmm8, xmm4\n        shufps  xmm4, xmm5, 136\n        shufps  xmm8, xmm5, 221\n        movaps  xmm5, xmm8\n        movups  xmm6, xmmword ptr [rsi+0x20]\n        movups  xmm7, xmmword ptr [rsi+0x30]\n        movaps  xmm8, xmm6\n        shufps  xmm6, xmm7, 136\n        pshufd  xmm6, xmm6, 0x93\n        shufps  xmm8, xmm7, 221\n        pshufd  xmm7, xmm8, 0x93\n        movaps  xmm14, xmmword ptr [ROT8+rip]\n        movaps  xmm15, xmmword ptr [ROT16+rip]\n        mov     al, 7\n9:\n        paddd   xmm0, xmm4\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshufb  xmm3, xmm15\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 20\n        psrld   xmm11, 12\n        por     xmm1, xmm11\n        paddd   xmm0, xmm5\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshufb  xmm3, xmm14\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 25\n        psrld   xmm11, 7\n        por     xmm1, xmm11\n        pshufd  xmm0, xmm0, 0x93\n        pshufd  xmm3, xmm3, 0x4E\n        pshufd  xmm2, xmm2, 0x39\n        paddd   xmm0, xmm6\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshufb  xmm3, xmm15\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 20\n        psrld   xmm11, 12\n        por     xmm1, xmm11\n        paddd   xmm0, xmm7\n        paddd   xmm0, xmm1\n        pxor    xmm3, xmm0\n        pshufb  xmm3, xmm14\n        paddd   xmm2, xmm3\n        pxor    xmm1, xmm2\n        movdqa  xmm11, xmm1\n        pslld   xmm1, 25\n        psrld   xmm11, 7\n        por     xmm1, xmm11\n        pshufd  xmm0, xmm0, 0x39\n        pshufd  xmm3, xmm3, 0x4E\n        pshufd  xmm2, xmm2, 0x93\n        dec     al\n        jz      9f\n        movdqa  xmm8, xmm4\n        shufps  xmm8, xmm5, 214\n        pshufd  xmm9, xmm4, 0x0F\n        pshufd  xmm4, xmm8, 0x39\n        movdqa  xmm8, xmm6\n        shufps  xmm8, xmm7, 250\n        pblendw xmm9, xmm8, 0xCC\n        movdqa  xmm8, xmm7\n        punpcklqdq xmm8, xmm5\n        pblendw xmm8, xmm6, 0xC0\n        pshufd  xmm8, xmm8, 0x78\n        punpckhdq xmm5, xmm7\n        punpckldq xmm6, xmm5\n        pshufd  xmm7, xmm6, 0x1E\n        movdqa  xmm5, xmm9\n        movdqa  xmm6, xmm8\n        jmp     9b\n9:\n        movdqu  xmm4, xmmword ptr [rdi]\n        movdqu  xmm5, xmmword ptr [rdi+0x10]\n        pxor    xmm0, xmm2\n        pxor    xmm1, xmm3\n        pxor    xmm2, xmm4\n        pxor    xmm3, xmm5\n        movups  xmmword ptr [r9], xmm0\n        movups  xmmword ptr [r9+0x10], xmm1\n        movups  xmmword ptr [r9+0x20], xmm2\n        movups  xmmword ptr [r9+0x30], xmm3\n        ret\n\n\n#ifdef __APPLE__\n.static_data\n#else\n.section .rodata\n#endif\n.p2align  6\nBLAKE3_IV:\n        .long  0x6A09E667, 0xBB67AE85\n        .long  0x3C6EF372, 0xA54FF53A\nROT16:\n        .byte  2, 3, 0, 1, 6, 7, 4, 5, 10, 11, 8, 9, 14, 15, 12, 13\nROT8:\n        .byte  1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8, 13, 14, 15, 12\nADD0:\n        .long  0, 1, 2, 3\nADD1:\n\t.long  4, 4, 4, 4\nBLAKE3_IV_0:\n\t.long  0x6A09E667, 0x6A09E667, 0x6A09E667, 0x6A09E667\nBLAKE3_IV_1:\n\t.long  0xBB67AE85, 0xBB67AE85, 0xBB67AE85, 0xBB67AE85\nBLAKE3_IV_2:\n\t.long  0x3C6EF372, 0x3C6EF372, 0x3C6EF372, 0x3C6EF372\nBLAKE3_IV_3:\n\t.long  0xA54FF53A, 0xA54FF53A, 0xA54FF53A, 0xA54FF53A\nBLAKE3_BLOCK_LEN:\n\t.long  64, 64, 64, 64\nCMP_MSB_MASK:\n\t.long  0x80000000, 0x80000000, 0x80000000, 0x80000000\n"
  },
  {
    "path": "common/blake3/main.c",
    "content": "/*\n * This main file is intended for testing via `make test`. It does not build in\n * other settings. See README.md in this directory for examples of how to build\n * C code.\n */\n\n#include <assert.h>\n#include <errno.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n#include \"blake3.h\"\n#include \"blake3_impl.h\"\n\n#define HASH_MODE 0\n#define KEYED_HASH_MODE 1\n#define DERIVE_KEY_MODE 2\n\nstatic void hex_char_value(uint8_t c, uint8_t *value, bool *valid) {\n  if ('0' <= c && c <= '9') {\n    *value = c - '0';\n    *valid = true;\n  } else if ('a' <= c && c <= 'f') {\n    *value = 10 + c - 'a';\n    *valid = true;\n  } else {\n    *valid = false;\n  }\n}\n\nstatic int parse_key(char *hex_key, uint8_t out[BLAKE3_KEY_LEN]) {\n  size_t hex_len = strlen(hex_key);\n  if (hex_len != 64) {\n    fprintf(stderr, \"Expected a 64-char hexadecimal key, got %zu chars.\\n\",\n            hex_len);\n    return 1;\n  }\n  for (size_t i = 0; i < 64; i++) {\n    uint8_t value;\n    bool valid;\n    hex_char_value(hex_key[i], &value, &valid);\n    if (!valid) {\n      fprintf(stderr, \"Invalid hex char.\\n\");\n      return 1;\n    }\n    if (i % 2 == 0) {\n      out[i / 2] = 0;\n      value <<= 4;\n    }\n    out[i / 2] += value;\n  }\n  return 0;\n}\n\n/* A little repetition here */\nenum cpu_feature {\n  SSE2 = 1 << 0,\n  SSSE3 = 1 << 1,\n  SSE41 = 1 << 2,\n  AVX = 1 << 3,\n  AVX2 = 1 << 4,\n  AVX512F = 1 << 5,\n  AVX512VL = 1 << 6,\n  /* ... */\n  UNDEFINED = 1 << 30\n};\n\nextern enum cpu_feature g_cpu_features;\nenum cpu_feature get_cpu_features();\n\nint main(int argc, char **argv) {\n  size_t out_len = BLAKE3_OUT_LEN;\n  uint8_t key[BLAKE3_KEY_LEN];\n  char *context = \"\";\n  uint8_t mode = HASH_MODE;\n  while (argc > 1) {\n    if (argc <= 2) {\n      fprintf(stderr, \"Odd number of arguments.\\n\");\n      return 1;\n    }\n    if (strcmp(\"--length\", argv[1]) == 0) {\n      char *endptr = NULL;\n      errno = 0;\n      unsigned long long out_len_ll = strtoull(argv[2], &endptr, 10);\n      if (errno != 0 || out_len > SIZE_MAX || endptr == argv[2] ||\n          *endptr != 0) {\n        fprintf(stderr, \"Bad length argument.\\n\");\n        return 1;\n      }\n      out_len = (size_t)out_len_ll;\n    } else if (strcmp(\"--keyed\", argv[1]) == 0) {\n      mode = KEYED_HASH_MODE;\n      int ret = parse_key(argv[2], key);\n      if (ret != 0) {\n        return ret;\n      }\n    } else if (strcmp(\"--derive-key\", argv[1]) == 0) {\n      mode = DERIVE_KEY_MODE;\n      context = argv[2];\n    } else {\n      fprintf(stderr, \"Unknown flag.\\n\");\n      return 1;\n    }\n    argc -= 2;\n    argv += 2;\n  }\n\n  /*\n   * We're going to hash the input multiple times, so we need to buffer it all.\n   * This is just for test cases, so go ahead and assume that the input is less\n   * than 1 MiB.\n   */\n  size_t buf_capacity = 1 << 20;\n  uint8_t *buf = malloc(buf_capacity);\n  assert(buf != NULL);\n  size_t buf_len = 0;\n  while (1) {\n    size_t n = fread(&buf[buf_len], 1, buf_capacity - buf_len, stdin);\n    if (n == 0) {\n      break;\n    }\n    buf_len += n;\n    assert(buf_len < buf_capacity);\n  }\n\n  const int mask = get_cpu_features();\n  int feature = 0;\n  do {\n    fprintf(stderr, \"Testing 0x%08X\\n\", feature);\n    g_cpu_features = feature;\n    blake3_hasher hasher;\n    switch (mode) {\n    case HASH_MODE:\n      blake3_hasher_init(&hasher);\n      break;\n    case KEYED_HASH_MODE:\n      blake3_hasher_init_keyed(&hasher, key);\n      break;\n    case DERIVE_KEY_MODE:\n      blake3_hasher_init_derive_key(&hasher, context);\n      break;\n    default:\n      abort();\n    }\n\n    blake3_hasher_update(&hasher, buf, buf_len);\n\n    /* TODO: An incremental output reader API to avoid this allocation. */\n    uint8_t *out = malloc(out_len);\n    if (out_len > 0 && out == NULL) {\n      fprintf(stderr, \"malloc() failed.\\n\");\n      return 1;\n    }\n    blake3_hasher_finalize(&hasher, out, out_len);\n    for (size_t i = 0; i < out_len; i++) {\n      printf(\"%02x\", out[i]);\n    }\n    printf(\"\\n\");\n    free(out);\n    feature = (feature - mask) & mask;\n  } while (feature != 0);\n  free(buf);\n  return 0;\n}\n"
  },
  {
    "path": "common/concurrency/AlignMacros.hh",
    "content": "//------------------------------------------------------------------------------\n// File: AlignMacros.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#pragma once\n#include <new>\n// Macros to help with alignment, ideally __cpp_lib_hardware_interference_size\n// is a cpp17 feature, but looks like none of the compilers actually implement\n// this!\n#ifdef __cpp_lib_hardware_interference_size\nusing std::hardware_constructive_interference_size;\nusing std::hardware_destructive_interference_size;\n#else\n// 64 bytes on x86-64 │ L1_CACHE_BYTES │ L1_CACHE_SHIFT │ __cacheline_aligned │ ...\nconstexpr std::size_t hardware_constructive_interference_size = 64;\nconstexpr std::size_t hardware_destructive_interference_size = 64;\n#endif\n"
  },
  {
    "path": "common/concurrency/AlignedArray.hh",
    "content": "//------------------------------------------------------------------------------\n// File: AlignedArray.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/concurrency/AlignMacros.hh\"\n#include <atomic>\n#include <array>\n\nnamespace eos::common {\n\ntemplate <typename T>\nstruct alignas(hardware_destructive_interference_size) AlignedAtomic\n    : private std::atomic<T> {\n  using std::atomic<T>::atomic;\n  using std::atomic<T>::operator=;\n  using std::atomic<T>::store;\n  using std::atomic<T>::load;\n  using std::atomic<T>::exchange;\n  using std::atomic<T>::fetch_add;\n  using std::atomic<T>::fetch_sub;\n};\n\n// Check some basic properties of AlignedAtomic; since we have this at compile\n// time and asserts do not exist in actual code, compiler does the unit testing for us\nstatic_assert(sizeof(AlignedAtomic<int>) == hardware_destructive_interference_size,\n               \"AlignedAtomic should be hardware_destructive_interference_size\");\nstatic_assert(alignof(AlignedAtomic<int>) == hardware_destructive_interference_size,\n              \"AlignedAtomic should be hardware_destructive_interference_size\");\n\n// An array where each element is aligned to hardware_destructive_interference_size\n// ie. elements do not share cache lines\ntemplate <typename T, std::size_t N>\nusing AlignedAtomicArray = std::array<AlignedAtomic<T>, N>;\n} // namespace eos::common\n"
  },
  {
    "path": "common/concurrency/AtomicUniquePtr.h",
    "content": "//------------------------------------------------------------------------------\n// File: AtomicUniquePtr.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n#include <atomic>\n#include <cassert>\n\nnamespace eos::common {\n\n/*\n * A thread safe unique_ptr - The main use case of this is when you have data\n * that is rarely changing, read often and written rarely. In this case\n * with a classic RWLock, even though the data is rarely changing, readers always\n * have to pay the cost of acquiring the lock. With this class the data load itself\n * is wait-free costing only a single atomic load. While the rest of the API closely\n * matches unique_ptr, reset() is different. We return the old value of data,\n * and the caller is responsible for deleting it. This is because we cannot make\n * any assumptions on how many readers are using the old value. So the writer has\n * to copy the old value and delete it after a sufficient point of synchronization.\n *\n * For really small data, you could get away with not bothering to delete and just\n * storing in some global list.\n */\ntemplate <typename T>\nclass atomic_unique_ptr\n{\npublic:\n  using pointer = T*;\n  using element_type = T;\n  atomic_unique_ptr() = default;\n  atomic_unique_ptr(T* p) : p_(p) {}\n\n  atomic_unique_ptr(const atomic_unique_ptr&) = delete;\n  atomic_unique_ptr& operator=(const atomic_unique_ptr&) = delete;\n\n  atomic_unique_ptr(atomic_unique_ptr&& other) {\n    T* p = other.p_.exchange(nullptr, std::memory_order_acq_rel);\n    publish(p);\n  }\n\n  // No move assignment operator, as to do this safely we have no idea how many\n  // readers could be potentially accessing both the old and new values!\n\n  ~atomic_unique_ptr() {;\n    delete p_.load(std::memory_order_relaxed);\n  }\n\n  T* get() const noexcept {\n    return p_.load(std::memory_order_acquire);\n  }\n\n  T* release() {\n    return p_.exchange(nullptr, std::memory_order_acq_rel);\n  }\n\n  /*!\n   * reset- the old pointer is returned instead of deleted\n   * This is because we cannot make sure that the pointer is not being used\n   * by another thread, so it is upto the caller to ensure a sufficient point\n   * of synchronization where it is safe to delete the old value. When using\n   * reset as a way to initialize the pointer, it is safe to use reset_from_null\n   *\n   * An atomic exchange is used over a get/publish as 2 transactions would no longer\n   * be atomic, though calling reset from multiple threads is generally not a good\n   * idea\n   *  @param p: pointer to be stored in the atomic_unique_ptr\n   *  @return: the old pointer\n   * */\n  [[nodiscard]] T* reset(T* p)\n  {\n    return p_.exchange(p, std::memory_order_acq_rel);\n  }\n\n  // not TS! spinning in an atomic compare exchange can be used to make it so,\n  // but reset_from_null is a construction routine and just like construction of\n  // the AtomicPtr itself isn't threadsafe, this shouldn't be!\n  void reset_from_null(T* p) {\n    assert(p_.load(std::memory_order_acquire) == nullptr);\n    publish(p);\n  }\n\n  T* operator->() const {\n    return p_.load(std::memory_order_acquire);\n  }\n\n  T& operator*() const {\n    return *this->get();\n  }\n\n  explicit operator bool() const {\n    return p_.load(std::memory_order_acquire)  != nullptr;\n  }\nprivate:\n  void publish(T* p) noexcept {\n    p_.store(p, std::memory_order_release);\n  }\n\n  std::atomic<T*> p_ {nullptr};\n};\n\n\n} // eos::common\n\n\n"
  },
  {
    "path": "common/concurrency/RCULite.hh",
    "content": "//------------------------------------------------------------------------------\n// File: RCULite.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n#include \"common/concurrency/AlignMacros.hh\"\n#include \"common/concurrency/ThreadEpochCounter.hh\"\n#include <atomic>\n#include <thread>\n\n\nnamespace eos::common\n{\n\nconstexpr size_t MAX_THREADS = 4096;\n// A simple ticket spin lock implementation\n\nclass TicketLock {\npublic:\n  void lock() noexcept {\n    auto my_ticket = ticket.fetch_add(1, std::memory_order_acquire);\n\n    uint32_t spin_count = 0;\n    while (serving.load(std::memory_order_acquire) != my_ticket) {\n      if (spin_count < 100) {\n        ++spin_count;\n      } else if (spin_count < 1000) {\n        if (++spin_count % 20 == 0) {\n          std::this_thread::yield();\n        }\n      } else {\n        std::this_thread::sleep_for(std::chrono::microseconds(10));\n      }\n    }\n  }\n\n  void unlock() noexcept {\n    serving.fetch_add(1, std::memory_order_release);\n  }\nprivate:\n  alignas(hardware_destructive_interference_size)  std::atomic<uint32_t> ticket {0};\n  alignas(hardware_destructive_interference_size) std::atomic<uint32_t> serving {0};\n};\n\n /*\n  A Read Copy Update Like primitive that guarantees that is wait-free on the\n  readers and guarantees that all memory is protected from deletion. This\n  is similar to folly's RCU implementation, but a bit simpler to accomodate\n  our use cases.\n\n  Let's say you've a data type that is mostly a read workload with very rare\n  updates, with classical RW Locks this is what you'd be doing\n\n  void reader() {\n     std::shared_lock lock(shared_mutex);\n     process(myconfig);\n  }\n\n  A rather simple way to not pay the cost would be using something like\n  atomic_unique_ptr\n\n  void reader() {\n     auto* config_data = myconfig.get()\n     process(config_data);\n  }\n\n  void writer() {\n    auto *old_config_data = myconfig.reset(new myconfig(config_data));\n    // This works and is safe, however we don't know when is a good checkpoint\n    // in the program to delete the old_config_data. Deleting when another reader\n    // is still accessing the data is something we want to avoid\n\n  }\n\n  void reader() {\n    RCUReadLock rlock(my_rcu_domain);\n    process(myconfig.get());\n  }\n\n  void writer() {\n    ConfigData* old_config_data(nullptr);\n    {\n      RCUWriteLock wlock(my_rcu_domain);\n      old_config_data = myconfig.reset(new config(config_data));\n    }\n\n    delete (old_config_data);\n  }\n\n  // Alternatively a scopedRCUWrite will drain the readers and wait for them to\n  complete before deletion\n\n  void writer() { ScopedRCUWrite(my_rcu_domain,\n  myconfig, new config(config_data)); }\n\n\n */\n\ntemplate <typename CounterT = ThreadEpochCounter>\nclass RCUDomain : public detail::stateful_trait_base<CounterT>\n{\npublic:\n\n  RCUDomain() = default;\n\n  inline uint64_t get_current_epoch(std::memory_order order\n                                    = std::memory_order_acquire) noexcept\n  {\n    return mEpoch.load(order);\n  }\n\n\n  inline size_t rcu_read_lock(uint64_t epoch) noexcept\n  {\n    return mReadersCounter.increment(epoch);\n  }\n\n  inline size_t rcu_read_lock() noexcept\n  {\n    return rcu_read_lock(mEpoch.load(std::memory_order_acquire));\n  }\n\n\n  inline void rcu_read_unlock(uint64_t epoch, uint64_t tag) noexcept\n  {\n    mReadersCounter.decrement(epoch, tag);\n  }\n\n  // rcu_read_unlock for a stateless list, which doesn't depend on return from\n  // the lock call\n  inline auto\n  rcu_read_unlock() noexcept\n  {\n    mReadersCounter.decrement();\n  }\n\n  template <typename T = CounterT>\n  inline auto rcu_read_unlock(uint64_t tag) noexcept\n  -> std::enable_if_t<detail::is_stateful_v<T>, void>\n  {\n    mReadersCounter.decrement(mEpoch.load(std::memory_order_acquire), tag);\n  }\n\n  inline void rcu_write_lock() noexcept\n  {\n    mWriterLock.lock();\n  }\n\n\n\n  inline void rcu_write_unlock() noexcept\n  {\n    rcu_synchronize();\n    mWriterLock.unlock();\n  }\n\n\nprivate:\n\n  inline void rcu_synchronize() noexcept\n  {\n    auto old_epoch = mEpoch.fetch_add(1, std::memory_order_acq_rel);\n    uint32_t spin_count = 0;\n    while (mReadersCounter.epochHasReaders(old_epoch)) {\n      if (++spin_count % 1000 == 0) {\n        std::this_thread::sleep_for(std::chrono::microseconds(100));\n      } else if (spin_count % 20 == 0) {\n        std::this_thread::yield();\n      }\n    }\n  }\n\n  CounterT mReadersCounter;\n  alignas(hardware_destructive_interference_size) std::atomic<uint64_t> mEpoch{0};\n  TicketLock mWriterLock;\n};\n\ntemplate <typename RCUDomain>\nstruct RCUWriteLock {\n  RCUWriteLock(RCUDomain& _rcu_domain): rcu_domain(_rcu_domain)\n  {\n    rcu_domain.rcu_write_lock();\n  }\n\n  ~RCUWriteLock()\n  {\n    rcu_domain.rcu_write_unlock();\n  }\n\n  RCUDomain& rcu_domain;\n};\n\nusing VersionedRCUDomain = RCUDomain<experimental::VersionEpochCounter<32>>;\nusing EpochRCUDomain = RCUDomain<ThreadEpochCounter>;\nstatic_assert(!detail::is_stateful_v<EpochRCUDomain>);\nstatic_assert(detail::is_stateful_v<VersionedRCUDomain>);\n// An adapter to use RCUDomain as a std::shared_mutex like object\ntemplate <typename RCUDomainT = EpochRCUDomain>\nclass RCUMutexT {\n  static_assert(!detail::is_stateful_v<RCUDomainT>,\n                \"RCUMutex needs to be stateless to confirm to std::shared_mutex api\");\npublic:\n  // implement here the std::shared_lock and unique_lock api\n  void lock_shared() {\n    rcu_domain.rcu_read_lock();\n  }\n\n  void unlock_shared() {\n    rcu_domain.rcu_read_unlock();\n  }\n\n  void lock() {\n    rcu_domain.rcu_write_lock();\n  }\n\n  void unlock() {\n    rcu_domain.rcu_write_unlock();\n  }\nprivate:\n  RCUDomainT rcu_domain;\n};\n\n// Specialization of ScopedRCUWrite for RCUMutexT which is\n// compatible with std::shared_mutex/unique/shared_lock apis\ntemplate <typename RCUMutexT, typename Ptr>\nclass ScopedRCUWrite {\npublic:\n    ScopedRCUWrite(RCUMutexT& _rcu_mutex,\n                   Ptr& ptr,\n                   typename Ptr::pointer new_val) : rcu_mutex(_rcu_mutex)\n    {\n      rcu_mutex.lock();\n      old_val = ptr.reset(new_val);\n    }\n\n  ~ScopedRCUWrite()\n    {\n      rcu_mutex.unlock();\n      delete old_val;\n    }\n    private:\n    RCUMutexT& rcu_mutex;\n    typename Ptr::pointer old_val;\n};\n\n// Implementation for ReadLock that correctly handles both stateful and stateless counters\n// For stateless domains, we'd just have the single reference to the mutex stored (8 bytes)\n// Stateful domains need to store the epoch/tag and hence utilize the 24 byte\nnamespace detail {\n\nstruct StatefulStorage {\n  uint64_t epoch;\n  uint64_t tag;\n};\n\n// Stateless storage would be EBO'd by most compilers\nstruct StatelessStorage {};\n\ntemplate <typename T>\nusing ReadLockStorage = std::conditional_t<detail::is_stateful_v<T>,\n  StatefulStorage,\n  StatelessStorage>;\n} // detail\n\ntemplate <typename T>\nclass RCUReadLock : private detail::ReadLockStorage<T> {\npublic:\n  explicit RCUReadLock(T& lockable) : m_lockable(lockable) {\n    lock_impl();\n  }\n\n  ~RCUReadLock() {\n    unlock_impl();\n  }\nprivate:\n  template <typename U = T>\n  std::enable_if_t<detail::is_stateful_v<U>> lock_impl() {\n    this->epoch = m_lockable.get_current_epoch();\n    this->tag = m_lockable.rcu_read_lock(this->epoch);\n  }\n\n  template <typename U = T>\n  std::enable_if_t<!detail::is_stateful_v<U>> lock_impl() {\n    // Check if it is a raw domain being passed\n    if constexpr (std::is_same_v<U, EpochRCUDomain>) {\n      m_lockable.rcu_read_lock();\n    } else {\n      // Otherwise we assume it's a mutex wrapper\n      m_lockable.lock_shared();\n    }\n  }\n\n  template <typename U = T>\n  std::enable_if_t<detail::is_stateful_v<U>> unlock_impl() {\n    m_lockable.rcu_read_unlock(this->epoch, this->tag);\n  }\n\n  template <typename U = T>\n  std::enable_if_t <!detail::is_stateful_v<U>> unlock_impl() {\n    if constexpr (std::is_same_v<U, EpochRCUDomain>) {\n      m_lockable.rcu_read_unlock();\n    } else {\n      m_lockable.unlock_shared();\n    }\n  }\n\n  T& m_lockable;\n};\n\nstatic_assert(sizeof(RCUReadLock<RCUMutexT<>>) == 8);\n} // eos::common\n"
  },
  {
    "path": "common/concurrency/ThreadEpochCounter.cc",
    "content": "// /************************************************************************\n//  * EOS - the CERN Disk Storage System                                   *\n//  * Copyright (C) 2024 CERN/Switzerland                           *\n//  *                                                                      *\n//  * This program is free software: you can redistribute it and/or modify *\n//  * it under the terms of the GNU General Public License as published by *\n//  * the Free Software Foundation, either version 3 of the License, or    *\n//  * (at your option) any later version.                                  *\n//  *                                                                      *\n//  * This program is distributed in the hope that it will be useful,      *\n//  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n//  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n//  * GNU General Public License for more details.                         *\n//  *                                                                      *\n//  * You should have received a copy of the GNU General Public License    *\n//  * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n//  ************************************************************************\n//\n#include \"ThreadEpochCounter.hh\"\n#include \"common/Logging.hh\"\n\nnamespace eos::common {\n\nstd::array<std::atomic<bool>, EOS_MAX_THREADS> g_thread_in_use {false};\nthread_local ThreadID tlocalID;\n\nThreadID::ThreadID()\n{\n  for (size_t i = 0; i < EOS_MAX_THREADS; ++i) {\n    bool expected = false;\n    if (!g_thread_in_use[i] &&\n        g_thread_in_use[i].compare_exchange_strong(expected, true)) {\n      tid = i;\n      return;\n    }\n  }\n\n  // COULD NOT FIND A FREE THREAD ID, PANIC!\n  // assert(true); In the rare event we reach here, we can't guarantee EpochCounter\n  // correctness, so we'll just log and move on!\n  // Since the commonest user of this code path is the counter for getting the current scheduler\n  // we can take the risky case when we are at 65k threads\n  eos_static_alert(\"Could not find a free thread ID, panicking! You've more than %d threads: current_thread: %d %lu\",\n                   EOS_MAX_THREADS, pthread_self());\n}\n\nThreadID::~ThreadID()\n{\n  g_thread_in_use[tid] = false;\n}\n} // namespace eos::common"
  },
  {
    "path": "common/concurrency/ThreadEpochCounter.hh",
    "content": "//------------------------------------------------------------------------------\n// File: ThreadEpochCounter.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n#include \"common/concurrency/AlignMacros.hh\"\n#include <array>\n#include <atomic>\n#include <cassert>\n#include <iostream>\n#include <thread>\n\nnamespace eos::common {\n\nnamespace detail {\n\ntemplate <typename, typename = void>\nstruct is_stateful : std::false_type {};\n\ntemplate <typename T>\nstruct is_stateful<T,\n                   std::void_t<typename T::is_stateful>> : std::true_type {};\n\ntemplate <typename T>\nconstexpr bool is_stateful_v = is_stateful<T>::value;\n\ntemplate <typename T, typename = void>\nstruct stateful_trait_base {};\n\ntemplate <typename T>\nstruct stateful_trait_base <T, std::void_t<typename T::is_stateful>> {\n  using is_stateful = void;\n};\n\n} // detail\n\nnamespace experimental {\n  // The Counters in experimental namespace are not\n  // yet production ready! This is for testing only\n  // currently\n  template <size_t kMaxEpochs=32768>\n  class VersionEpochCounter {\n  public:\n    using is_stateful = void;\n    inline uint64_t getEpochIndex(uint64_t epoch) noexcept {\n      if (epoch < kMaxEpochs)\n        return epoch;\n      // TODO: This only works assuming that we wouldn't really have\n      // readers at epoch 0 by the time kMaxEpochs is reached, which\n      // is relatively safe given kMaxEpochs amount of writes don't happen\n      // before the first reader finishes.\n      return epoch % kMaxEpochs;\n\n    }\n\n    inline size_t increment(uint64_t epoch, uint16_t count=1) noexcept {\n      auto index = getEpochIndex(epoch);\n      mCounter[index].fetch_add(count, std::memory_order_release);\n      return index;\n    }\n\n    inline void decrement(uint64_t epoch) noexcept {\n      auto index = getEpochIndex(epoch);\n      mCounter[index].fetch_sub(1, std::memory_order_release);\n    }\n\n    inline void decrement(uint64_t epoch, uint64_t index) noexcept {\n      mCounter[index].fetch_sub(1, std::memory_order_release);\n    }\n\n    inline size_t getReaders(uint64_t epoch) noexcept {\n      return mCounter[getEpochIndex(epoch)].load(std::memory_order_relaxed);\n    }\n\n    bool epochHasReaders(uint64_t epoch) noexcept {\n      auto index = getEpochIndex(epoch);\n      return mCounter[index].load(std::memory_order_acquire) > 0;\n    }\n\n  private:\n    alignas(hardware_destructive_interference_size) std::array<std::atomic<uint16_t>, kMaxEpochs> mCounter{0};\n  };\n} // experimental\n\n// The Idea of Thread local ID is borrowed from\n// https://github.com/cmuparlay/concurrent_deferred_rcu\n// Turning Manual Concurrent Memory Reclamation into Automatic Reference Counting\n// Daniel Anderson, Guy E. Blelloch, Yuanhao Wei (PLDI 2022)\nstatic constexpr size_t EOS_MAX_THREADS=65536;\nextern std::array<std::atomic<bool>, EOS_MAX_THREADS> g_thread_in_use;\n\nstruct ThreadID {\n  ThreadID();\n\n  ~ThreadID();\n\n  size_t get() {\n    return tid;\n  }\n\n  size_t tid;\n};\n\nextern thread_local ThreadID tlocalID;\n\n/**\n* @brief a simple epoch counter per thread that can be used to implement\n* RCU-like algorithms. Basically we store a bitfield of\n * 16 bit counter and a 48 bit epoch. If we have no hash collisions, this is fairly\n * simple to implement, you'd only need a simple increment and a memory_order_release\n * store. However, if we have hash collisions, we need to store the oldest epoch\n * as we're tracking the oldest epoch.\n *\n * This counter is supposed to be used with a threadID that is unique\n * like the one provided by ThreadID above.\n */\n\nstruct alignas(hardware_destructive_interference_size) ThreadEpoch {\n  auto get(std::memory_order order = std::memory_order_acquire) {\n    return epoch_counter.load(order);\n  }\n\n  auto get_counter(std::memory_order order = std::memory_order_acquire) {\n    return get(order) & 0xFFFF;\n  }\n\n  std::atomic<uint64_t> epoch_counter;\n};\n\nclass ThreadEpochCounter {\npublic:\n\n  //using is_state_less = void;\n\n  size_t increment(uint64_t epoch, uint16_t count=1) noexcept {\n    auto tid = tlocalID.get();\n    // This is 2 instructions, instead of a single CAS. Given that threads\n    // will not hash to the same number, we can guarantee that we'd only have one\n    // epoch per thread\n\n    auto old = mCounter[tid].get();\n    auto new_val = (epoch << 16) | ((old & 0xFFFF) + count);\n    mCounter[tid].epoch_counter.store(new_val, std::memory_order_release);\n    return tid;\n  }\n\n  inline void decrement(uint64_t epoch, size_t tid) {\n    // assert (old >> 16) == epoch);\n    mCounter[tid].epoch_counter.fetch_sub(1, std::memory_order_release);\n  }\n\n  inline void decrement() {\n    auto tid = tlocalID.get();\n    mCounter[tid].epoch_counter.fetch_sub(1, std::memory_order_release);\n  }\n\n  size_t getReaders(size_t tid) noexcept {\n    return mCounter[tid].get_counter();\n  }\n\n\n  bool epochHasReaders(uint64_t epoch) noexcept {\n    for (size_t i=0; i < EOS_MAX_THREADS; ++i) {\n      auto val = mCounter[i].get();\n      if ((val >> 16) == epoch && (val & 0xFFFF) > 0) {\n        return true;\n      }\n    }\n    return false;\n  }\n\nprivate:\n  std::array<ThreadEpoch, EOS_MAX_THREADS> mCounter{0};\n};\n\n// Cross check our assumptions about statefulness are true\nstatic_assert(detail::is_stateful_v<experimental::VersionEpochCounter<>>);\nstatic_assert(!detail::is_stateful_v<ThreadEpochCounter>);\n\n} // eos::common\n"
  },
  {
    "path": "common/config/ConfigParsing.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ConfigParsing.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/config/ConfigParsing.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Locators.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Parse filesystem configuration into a map. We should have a dedicated\n//! object that represents filesystem configuration ideally, but this will\n//! do for now..\n//!\n//! Returns if parsing was successful or not.\n//------------------------------------------------------------------------------\nbool ConfigParsing::parseFilesystemConfig(const std::string& config,\n    std::map<std::string, std::string>& out)\n{\n  using eos::common::StringConversion;\n\n  if (config.empty()) {\n    return false;\n  }\n\n  out.clear();\n  // Tokenize\n  std::vector<std::string> tokens;\n  eos::common::StringConversion::Tokenize(config, tokens);\n\n  for (size_t i = 0; i < tokens.size(); i++) {\n    std::vector<std::string> keyval;\n    // Split based on \"=\"\n    eos::common::StringConversion::Tokenize(tokens[i], keyval, \"=\");\n\n    if (keyval.size() != 2) {\n      eos_static_err(\"msg=\\\"failed to parse expected key=val pair\\\" \"\n                     \"input=\\\"%s\\\"\", tokens[i].c_str());\n      continue;\n    }\n\n    std::string sval = keyval[1];\n\n    // Curl decode string literal value\n    if (sval[0] == '\"' && sval[sval.length() - 1] == '\"') {\n      std::string to_decode = sval.substr(1, sval.length() - 2);\n      std::string decoded = StringConversion::curl_default_unescaped(to_decode);\n\n      if (!decoded.empty()) {\n        keyval[1] = '\"';\n        keyval[1] += decoded;\n        keyval[1] += '\"';\n      }\n    }\n\n    out[keyval[0]] = keyval[1];\n  }\n\n  if ((!out.count(\"queuepath\")) ||\n      (!out.count(\"queue\")) ||\n      (!out.count(\"id\"))) {\n    eos_static_err(\"msg=\\\"could not parse configuration entry: %s\\\"\",\n                   config.c_str());\n    return false;\n  }\n\n  // All clear, configuration is valid\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Relocate a filesystem to a different FST\n//------------------------------------------------------------------------------\nStatus ConfigParsing::relocateFilesystem(const std::string& newFstHost,\n    int newFstPort,\n    std::map<std::string, std::string>& configEntry)\n{\n  eos::common::FileSystemLocator locator;\n\n  if (!common::FileSystemLocator::fromQueuePath(configEntry[\"queuepath\"],\n      locator)) {\n    return Status(EINVAL, SSTR(\"could not parse queuepath: \" <<\n                               configEntry[\"queuepath\"]));\n  }\n\n  locator = eos::common::FileSystemLocator(newFstHost, newFstPort,\n            locator.getStoragePath());\n  configEntry[\"host\"] = newFstHost;\n  configEntry[\"port\"] = SSTR(newFstPort);\n  configEntry[\"hostport\"] = SSTR(newFstHost << \":\" << newFstPort);\n  configEntry[\"queue\"] = locator.getFSTQueue();\n  configEntry[\"queuepath\"] = locator.getQueuePath();\n  return Status();\n}\n\n//------------------------------------------------------------------------------\n// Parse configuration file\n//\n// Returns if parsing was successful or not.\n//------------------------------------------------------------------------------\nbool ConfigParsing::parseConfigurationFile(const std::string& contents,\n    std::map<std::string, std::string>& out, std::string& err)\n{\n  int line_num = 0;\n  std::string s;\n  std::istringstream streamconfig(contents);\n  out.clear();\n\n  while ((getline(streamconfig, s, '\\n'))) {\n    line_num++;\n\n    if (s.length()) {\n      XrdOucString key = s.c_str();\n      int seppos = key.find(\" => \");\n\n      if (seppos == STR_NPOS) {\n        err = SSTR(\"parsing error in configuration file line \"\n                   << line_num << \":\" <<  s.c_str());\n        return false;\n      }\n\n      XrdOucString value;\n      value.assign(key, seppos + 4);\n      key.erase(seppos);\n\n      // Add entry only if key and value are not empty\n      if (key.length() && value.length()) {\n        eos_static_notice(\"setting config key=%s value=%s\", key.c_str(), value.c_str());\n        out[key.c_str()] = value.c_str();\n      } else {\n        eos_static_notice(\"skipping empty config key=%s value=%s\", key.c_str(),\n                          value.c_str());\n      }\n    }\n  }\n\n  return true;\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/config/ConfigParsing.hh",
    "content": "//------------------------------------------------------------------------------\n// File: ConfigParsing.hh\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_MGM_CONFIG_PARSING_HH\n#define EOS_MGM_CONFIG_PARSING_HH\n\n#include \"common/Namespace.hh\"\n#include \"common/Status.hh\"\n\n#include <string>\n#include <map>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class ConfigParsing\n//------------------------------------------------------------------------------\nclass ConfigParsing {\npublic:\n  //----------------------------------------------------------------------------\n  //! Parse filesystem configuration into a map. We should have a dedicated\n  //! object that represents filesystem configuration ideally, but this will\n  //! do for now..\n  //!\n  //! Returns if parsing was successful or not.\n  //----------------------------------------------------------------------------\n  static bool parseFilesystemConfig(const std::string &config,\n    std::map<std::string, std::string> &out);\n\n  //----------------------------------------------------------------------------\n  //! Relocate a filesystem to a different FST\n  //----------------------------------------------------------------------------\n  static Status relocateFilesystem(const std::string &newFstHost, int newFstPort,\n   std::map<std::string, std::string> &configEntry);\n\n  //----------------------------------------------------------------------------\n  //! Parse configuration file\n  //!\n  //! Returns if parsing was successful or not.\n  //----------------------------------------------------------------------------\n  static bool parseConfigurationFile(const std::string &contents,\n    std::map<std::string, std::string> &out, std::string &err);\n\n};\n\n\nEOSCOMMONNAMESPACE_END\n\n#endif"
  },
  {
    "path": "common/config/ConfigStore.hh",
    "content": "//------------------------------------------------------------------------------\n// File: ConfigStore.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/StringUtils.hh\"\n#include \"common/Logging.hh\"\n\nnamespace eos::common\n{\n\n/*\n * A simple class to talk to various ways of talking to generic string key-value\n * store, the intent is that consumer classes can talk to for eg. a global config\n * store/ a per space config store or qdb etc. It is upto the subclasses to implement\n * how to save and load the key value from config. A couple of convenience functions\n * are added to retrieve numeric keys and defaults in case no keys exist.\n */\nclass ConfigStore: public eos::common::LogId\n{\npublic:\n  //! Save a key value to the underlying config store\n  //! \\param key the string key\n  //! \\param val the string value\n  //! \\return bool status of the save\n  virtual bool save(const std::string& key, const std::string& val) = 0;\n\n  //! Obtain the value corresponding to the key from the store\n  //! \\param key the string key\n  //! \\return the string value or empty string\n  virtual std::string load(const std::string& key) = 0;\n  virtual ~ConfigStore() = default;\n\n  std::string get(const std::string& key, const std::string& default_val)\n  {\n    if (auto s = load(key);\n        !s.empty()) {\n      return s;\n    }\n\n    return default_val;\n  }\n\n  //! Get a numeric (int/float/etc) value from the key value store\n  //! \\tparam NumT deduced from default_val, needn't be supplied\n  //! \\param key the string key\n  //! \\param default_val a default val of desired numeric return type\n  //! \\return the value or default_val if no hits were found\n  template <typename NumT>\n  auto get(const std::string& key, NumT default_val) noexcept\n  -> typename std::enable_if_t<std::is_arithmetic_v<NumT>, NumT> {\n    NumT val;\n    std::string log_msg;\n\n    if (!StringToNumeric(load(key), val, default_val, &log_msg))\n    {\n      eos_err(\"msg=\\\"failed to load key from Configstore\\\" key=\\\"%s\\\" err=%s\",\n      key.c_str(), log_msg.c_str());\n    }\n\n    return val;\n  }\n};\n\n} // eos::common\n"
  },
  {
    "path": "common/crc32c/crc32c.cc",
    "content": "// Copyright 2008,2009,2010 Massachusetts Institute of Technology.\n// All rights reserved. Use of this source code is governed by a\n// BSD-style license that can be found in the LICENSE file.\n\n#include <cassert>\n#include <cstdio>\n#include <cstring>\n#include <stdlib.h>\n#include \"crc32c.h\"\n#include \"crc32ctables.h\"\n\n#undef __PIC__\n\nnamespace checksum\n{\n\nstatic uint32_t crc32c_CPUDetection(uint32_t crc, const void* data,\n                                    size_t length)\n{\n  // Avoid issues that could potentially be caused by multiple threads: use a local variable\n  CRC32CFunctionPtr best = detectBestCRC32C();\n  crc32c = best;\n  return best(crc, data, length);\n}\n\nCRC32CFunctionPtr crc32c = crc32c_CPUDetection;\n\nstatic uint32_t cpuid(uint32_t functionInput)\n{\n  uint32_t ecx;\n#if __SIZEOF_POINTER__ == 8\n  uint32_t eax;\n  uint32_t ebx;\n  uint32_t edx;\n#endif\n#if __SIZEOF_POINTER__ == 8\n#ifdef __PIC__\n  // PIC: Need to save and restore ebx See:\n  // http://sam.zoy.org/blog/2007-04-13-shlib-with-non-pic-code-have-inline-assembly-and-pic-mix-well\n  asm(\"pushl %%ebx\\n\\t\" /* save %ebx */\n      \"cpuid\\n\\t\"\n      \"movl %%ebx, %[ebx]\\n\\t\" /* save what cpuid just put in %ebx */\n      \"popl %%ebx\" : \"=a\"(eax), [ebx] \"=r\"(ebx), \"=c\"(ecx), \"=d\"(edx) : \"a\"(functionInput)\n      : \"cc\");\n#else\n#if !defined(__aarch64__)\n  asm(\"cpuid\" : \"=a\"(eax), \"=b\"(ebx), \"=c\"(ecx), \"=d\"(edx) : \"a\"(functionInput));\n#endif // APPLE detection end\n#endif\n#else\n  fprintf(stderr, \"error: crc32c is not supported on 32bit platforms\\n\");\n  ecx = 0;\n#endif\n  return ecx;\n}\n\nCRC32CFunctionPtr detectBestCRC32C()\n{\n  static const int SSE42_BIT = 20;\n  uint32_t ecx = cpuid(1);\n  bool hasSSE42 = ecx & (1 << SSE42_BIT);\n  // test if living in a virtual machine\n  int rc = system(\"dmidecode | egrep -i 'manufacturer|product' | grep 'Virtual Machine'\");\n\n  if (!WEXITSTATUS(rc)) {\n    // fprintf(stderr,\n    //\"------ --:--:-- ----- CRC32C configured for virtual machines running without SSE42\\n\");\n    hasSSE42 = 0;\n  } else {\n    if (hasSSE42) {\n      //fprintf(stderr,\n      //        \"------ --:--:-- ----- CRC32C configured for machine with SSE42 extension\\n\");\n    } else {\n      //fprintf(stderr,\n      //        \"------ --:--:-- ----- CRC32C configured for machine without SSE42 extension\\n\");\n    }\n  }\n\n  if (hasSSE42) {\n#ifdef __LP64__\n    return crc32cHardware64;\n#else\n    return crc32cHardware32;\n#endif\n  } else {\n    return crc32cSlicingBy8;\n  }\n}\n\n// Implementations adapted from Intel's Slicing By 8 Sourceforge Project\n// http://sourceforge.net/projects/slicing-by-8/\n/*++\n *\n * Copyright (c) 2004-2006 Intel Corporation - All Rights Reserved\n *\n * This software program is licensed subject to the BSD License,\n * available at http://www.opensource.org/licenses/bsd-license.html\n *\n * Abstract: The main routine\n *\n --*/\nuint32_t crc32cSarwate(uint32_t crc, const void* data, size_t length)\n{\n  const char* p_buf = (const char*) data;\n  const char* p_end = p_buf + length;\n\n  while (p_buf < p_end) {\n    crc = crc_tableil8_o32[(crc ^ *p_buf++) & 0x000000FF] ^ (crc >> 8);\n  }\n\n  return crc;\n}\n\nuint32_t crc32cSlicingBy4(uint32_t crc, const void* data, size_t length)\n{\n  const char* p_buf = (const char*) data;\n  // Handle leading misaligned bytes\n  size_t initial_bytes = (sizeof(int32_t) - (intptr_t)p_buf) & (sizeof(\n                           int32_t) - 1);\n\n  if (length < initial_bytes) {\n    initial_bytes = length;\n  }\n\n  for (size_t li = 0; li < initial_bytes; li++) {\n    crc = crc_tableil8_o32[(crc ^ *p_buf++) & 0x000000FF] ^ (crc >> 8);\n  }\n\n  length -= initial_bytes;\n  size_t running_length = length & ~(sizeof(int32_t) - 1);\n  size_t end_bytes = length - running_length;\n\n  for (size_t li = 0; li < running_length / 4; li++) {\n    crc ^= *(uint32_t*) p_buf;\n    p_buf += 4;\n    uint32_t term1 = crc_tableil8_o56[crc & 0x000000FF] ^\n                     crc_tableil8_o48[(crc >> 8) & 0x000000FF];\n    uint32_t term2 = crc >> 16;\n    crc = term1 ^\n          crc_tableil8_o40[term2 & 0x000000FF] ^\n          crc_tableil8_o32[(term2 >> 8) & 0x000000FF];\n  }\n\n  for (size_t li = 0; li < end_bytes; li++) {\n    crc = crc_tableil8_o32[(crc ^ *p_buf++) & 0x000000FF] ^ (crc >> 8);\n  }\n\n  return crc;\n}\n\nuint32_t crc32cSlicingBy8(uint32_t crc, const void* data, size_t length)\n{\n  const char* p_buf = (const char*) data;\n  // Handle leading misaligned bytes\n  size_t initial_bytes = (sizeof(int32_t) - (intptr_t)p_buf) & (sizeof(\n                           int32_t) - 1);\n\n  if (length < initial_bytes) {\n    initial_bytes = length;\n  }\n\n  for (size_t li = 0; li < initial_bytes; li++) {\n    crc = crc_tableil8_o32[(crc ^ *p_buf++) & 0x000000FF] ^ (crc >> 8);\n  }\n\n  length -= initial_bytes;\n  size_t running_length = length & ~(sizeof(uint64_t) - 1);\n  size_t end_bytes = length - running_length;\n\n  for (size_t li = 0; li < running_length / 8; li++) {\n    crc ^= *(uint32_t*) p_buf;\n    p_buf += 4;\n    uint32_t term1 = crc_tableil8_o88[crc & 0x000000FF] ^\n                     crc_tableil8_o80[(crc >> 8) & 0x000000FF];\n    uint32_t term2 = crc >> 16;\n    crc = term1 ^\n          crc_tableil8_o72[term2 & 0x000000FF] ^\n          crc_tableil8_o64[(term2 >> 8) & 0x000000FF];\n    term1 = crc_tableil8_o56[(*(uint32_t*)p_buf) & 0x000000FF] ^\n            crc_tableil8_o48[((*(uint32_t*)p_buf) >> 8) & 0x000000FF];\n    term2 = (*(uint32_t*)p_buf) >> 16;\n    crc = crc ^ term1 ^\n          crc_tableil8_o40[term2  & 0x000000FF] ^\n          crc_tableil8_o32[(term2 >> 8) & 0x000000FF];\n    p_buf += 4;\n  }\n\n  for (size_t li = 0; li < end_bytes; li++) {\n    crc = crc_tableil8_o32[(crc ^ *p_buf++) & 0x000000FF] ^ (crc >> 8);\n  }\n\n  return crc;\n}\n\n// Hardware-accelerated CRC-32C (using CRC32 instruction)\nuint32_t crc32cHardware32(uint32_t crc, const void* data, size_t length)\n{\n  const char* p_buf = (const char*) data;\n\n  // alignment doesn't seem to help?\n  for (size_t i = 0; i < length / sizeof(uint32_t); i++) {\n    crc = __builtin_ia32_crc32si(crc, *(uint32_t*) p_buf);\n    p_buf += sizeof(uint32_t);\n  }\n\n  // This ugly switch is slightly faster for short strings than the straightforward loop\n  length &= sizeof(uint32_t) - 1;\n\n  /*\n    while (length > 0) {\n    crc32bit = __builtin_ia32_crc32qi(crc32bit, *p_buf++);\n    length--;\n    }\n  */\n  switch (length) {\n  case 3:\n    crc = __builtin_ia32_crc32qi(crc, *p_buf++);\n    /* fallthrough */\n\n  case 2:\n    crc = __builtin_ia32_crc32hi(crc, *(uint16_t*) p_buf);\n    break;\n\n  case 1:\n    crc = __builtin_ia32_crc32qi(crc, *p_buf);\n    break;\n\n  case 0:\n    break;\n\n  default:\n    // This should never happen; enable in debug code\n    assert(false);\n  }\n\n  return crc;\n}\n\n// Hardware-accelerated CRC-32C (using CRC32 instruction)\nuint32_t crc32cHardware64(uint32_t crc, const void* data, size_t length)\n{\n#if !defined(__LP64__) || (defined(__APPLE__) && defined(__aarch64__))\n  return crc32cHardware32(crc, data, length);\n#else\n  const char* p_buf = (const char*) data;\n  // alignment doesn't seem to help?\n  uint64_t crc64bit = crc;\n\n  for (size_t i = 0; i < length / sizeof(uint64_t); i++) {\n    crc64bit = __builtin_ia32_crc32di(crc64bit, *(uint64_t*) p_buf);\n    p_buf += sizeof(uint64_t);\n  }\n\n  // This ugly switch is slightly faster for short strings than the straightforward loop\n  uint32_t crc32bit = (uint32_t) crc64bit;\n  length &= sizeof(uint64_t) - 1;\n\n  /*\n    while (length > 0) {\n    crc32bit = __builtin_ia32_crc32qi(crc32bit, *p_buf++);\n    length--;\n    }\n  */\n  switch (length) {\n  case 7:\n    crc32bit = __builtin_ia32_crc32qi(crc32bit, *p_buf++);\n    /* fallthrough */\n\n  case 6:\n    crc32bit = __builtin_ia32_crc32hi(crc32bit, *(uint16_t*) p_buf);\n    p_buf += 2;\n    /* fallthrough */\n\n  // case 5 is below: 4 + 1\n  case 4:\n    crc32bit = __builtin_ia32_crc32si(crc32bit, *(uint32_t*) p_buf);\n    break;\n\n  case 3:\n    crc32bit = __builtin_ia32_crc32qi(crc32bit, *p_buf++);\n    /* fallthrough */\n\n  case 2:\n    crc32bit = __builtin_ia32_crc32hi(crc32bit, *(uint16_t*) p_buf);\n    break;\n\n  case 5:\n    crc32bit = __builtin_ia32_crc32si(crc32bit, *(uint32_t*) p_buf);\n    p_buf += 4;\n    /* fallthrough */\n\n  case 1:\n    crc32bit = __builtin_ia32_crc32qi(crc32bit, *p_buf);\n    break;\n\n  case 0:\n    break;\n\n  default:\n    // This should never happen; enable in debug code\n    assert(false);\n  }\n\n  return crc32bit;\n#endif\n}\n\n}  // namespace checksum\n"
  },
  {
    "path": "common/crc32c/crc32c.h",
    "content": "// Copyright 2008,2009,2010 Massachusetts Institute of Technology.\n// All rights reserved. Use of this source code is governed by a\n// BSD-style license that can be found in the LICENSE file.\n\n#ifndef LOGGING_CRC32C_H__\n#define LOGGING_CRC32C_H__\n\n#include <cstddef>\n#include <stdint.h>\n\nnamespace checksum\n{\n\n/** Returns the initial value for a CRC32-C computation. */\nstatic inline uint32_t crc32cInit()\n{\n  return 0xFFFFFFFF;\n}\n\n/** Pointer to a function that computes a CRC32C checksum.\n@arg crc Previous CRC32C value, or crc32c_init().\n@arg data Pointer to the data to be checksummed.\n@arg length length of the data in bytes.\n*/\ntypedef uint32_t (*CRC32CFunctionPtr)(uint32_t crc, const void* data,\n                                      size_t length);\n\n/** This will map automatically to the \"best\" CRC implementation. */\nextern CRC32CFunctionPtr crc32c;\n\nCRC32CFunctionPtr detectBestCRC32C();\n\n/** Converts a partial CRC32-C computation to the final value. */\nstatic inline uint32_t crc32cFinish(uint32_t crc)\n{\n  return ~crc;\n}\n\nuint32_t crc32cSarwate(uint32_t crc, const void* data, size_t length);\nuint32_t crc32cSlicingBy4(uint32_t crc, const void* data, size_t length);\nuint32_t crc32cSlicingBy8(uint32_t crc, const void* data, size_t length);\nuint32_t crc32cHardware32(uint32_t crc, const void* data, size_t length);\nuint32_t crc32cHardware64(uint32_t crc, const void* data, size_t length);\n\n#ifdef __aarch64__\n#ifdef __APPLE__\n#define __builtin_ia32_crc32si __builtin_arm_crc32cw\n#define __builtin_ia32_crc32hi __builtin_arm_crc32ch\n#define __builtin_ia32_crc32qi __builtin_arm_crc32cb\n#else\n#define __builtin_ia32_crc32si __builtin_aarch64_crc32cw\n#define __builtin_ia32_crc32hi __builtin_aarch64_crc32ch\n#define __builtin_ia32_crc32qi __builtin_aarch64_crc32cb\n#define __builtin_ia32_crc32di __builtin_aarch64_crc32cx\n#endif // APPLE\n#endif // __aarch64__\n\n}  // namespace checksum\n#endif\n"
  },
  {
    "path": "common/crc32c/crc32ctables.cc",
    "content": "// Copyright 2008,2009,2010 Massachusetts Institute of Technology.\r\n// All rights reserved. Use of this source code is governed by a\r\n// BSD-style license that can be found in the LICENSE file.\r\n\r\n// Implementations adapted from Intel's Slicing By 8 Sourceforge Project\r\n// http://sourceforge.net/projects/slicing-by-8/\r\n/*\r\n * Copyright (c) 2004-2006 Intel Corporation - All Rights Reserved\r\n *\r\n *\r\n * This software program is licensed subject to the BSD License,\r\n * available at http://www.opensource.org/licenses/bsd-license.html.\r\n *\r\n * Abstract:\r\n *\r\n *  Tables for software CRC generation\r\n */\r\n\r\n#include \"crc32ctables.h\"\r\n\r\nnamespace checksum\r\n{\r\n\r\n/* Tables generated with code like the following:\r\n\r\n#define CRCPOLY 0x82f63b78 // reversed 0x1EDC6F41\r\n#define CRCINIT 0xFFFFFFFF\r\n\r\nvoid init() {\r\nfor (uint32_t i = 0; i <= 0xFF; i++) {\r\nuint32_t x = i;\r\nfor (uint32_t j = 0; j < 8; j++)\r\nx = (x>>1) ^ (CRCPOLY & (-(int32_t)(x & 1)));\r\ng_crc_slicing[0][i] = x;\r\n}\r\n\r\nfor (uint32_t i = 0; i <= 0xFF; i++) {\r\nuint32_t c = g_crc_slicing[0][i];\r\nfor (uint32_t j = 1; j < 8; j++) {\r\nc = g_crc_slicing[0][c & 0xFF] ^ (c >> 8);\r\ng_crc_slicing[j][i] = c;\r\n}\r\n}\r\n}\r\n*/\r\n\r\n/*\r\n * The following CRC lookup table was generated automagically\r\n * using the following model parameters:\r\n *\r\n * Generator Polynomial = ................. 0x1EDC6F41\r\n * Generator Polynomial Length = .......... 32 bits\r\n * Reflected Bits = ....................... TRUE\r\n * Table Generation Offset = .............. 32 bits\r\n * Number of Slices = ..................... 8 slices\r\n * Slice Lengths = ........................ 8 8 8 8 8 8 8 8\r\n * Directory Name = ....................... .\\\r\n * File Name = ............................ 8x256_tables.c\r\n */\r\n\r\nconst uint32_t crc_tableil8_o32[256] = {\r\n  0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB,\r\n  0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24,\r\n  0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384,\r\n  0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B,\r\n  0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35,\r\n  0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA,\r\n  0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A,\r\n  0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595,\r\n  0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957,\r\n  0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198,\r\n  0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38,\r\n  0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7,\r\n  0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789,\r\n  0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46,\r\n  0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6,\r\n  0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829,\r\n  0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93,\r\n  0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C,\r\n  0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC,\r\n  0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033,\r\n  0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D,\r\n  0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982,\r\n  0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622,\r\n  0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED,\r\n  0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F,\r\n  0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0,\r\n  0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540,\r\n  0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F,\r\n  0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1,\r\n  0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E,\r\n  0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E,\r\n  0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351\r\n};\r\n\r\n/*\r\n * end of the CRC lookup table crc_tableil8_o32\r\n */\r\n\r\n\r\n\r\n/*\r\n * The following CRC lookup table was generated automagically\r\n * using the following model parameters:\r\n *\r\n * Generator Polynomial = ................. 0x1EDC6F41\r\n * Generator Polynomial Length = .......... 32 bits\r\n * Reflected Bits = ....................... TRUE\r\n * Table Generation Offset = .............. 32 bits\r\n * Number of Slices = ..................... 8 slices\r\n * Slice Lengths = ........................ 8 8 8 8 8 8 8 8\r\n * Directory Name = ....................... .\\\r\n * File Name = ............................ 8x256_tables.c\r\n */\r\n\r\nconst uint32_t crc_tableil8_o40[256] = {\r\n  0x00000000, 0x13A29877, 0x274530EE, 0x34E7A899, 0x4E8A61DC, 0x5D28F9AB, 0x69CF5132, 0x7A6DC945,\r\n  0x9D14C3B8, 0x8EB65BCF, 0xBA51F356, 0xA9F36B21, 0xD39EA264, 0xC03C3A13, 0xF4DB928A, 0xE7790AFD,\r\n  0x3FC5F181, 0x2C6769F6, 0x1880C16F, 0x0B225918, 0x714F905D, 0x62ED082A, 0x560AA0B3, 0x45A838C4,\r\n  0xA2D13239, 0xB173AA4E, 0x859402D7, 0x96369AA0, 0xEC5B53E5, 0xFFF9CB92, 0xCB1E630B, 0xD8BCFB7C,\r\n  0x7F8BE302, 0x6C297B75, 0x58CED3EC, 0x4B6C4B9B, 0x310182DE, 0x22A31AA9, 0x1644B230, 0x05E62A47,\r\n  0xE29F20BA, 0xF13DB8CD, 0xC5DA1054, 0xD6788823, 0xAC154166, 0xBFB7D911, 0x8B507188, 0x98F2E9FF,\r\n  0x404E1283, 0x53EC8AF4, 0x670B226D, 0x74A9BA1A, 0x0EC4735F, 0x1D66EB28, 0x298143B1, 0x3A23DBC6,\r\n  0xDD5AD13B, 0xCEF8494C, 0xFA1FE1D5, 0xE9BD79A2, 0x93D0B0E7, 0x80722890, 0xB4958009, 0xA737187E,\r\n  0xFF17C604, 0xECB55E73, 0xD852F6EA, 0xCBF06E9D, 0xB19DA7D8, 0xA23F3FAF, 0x96D89736, 0x857A0F41,\r\n  0x620305BC, 0x71A19DCB, 0x45463552, 0x56E4AD25, 0x2C896460, 0x3F2BFC17, 0x0BCC548E, 0x186ECCF9,\r\n  0xC0D23785, 0xD370AFF2, 0xE797076B, 0xF4359F1C, 0x8E585659, 0x9DFACE2E, 0xA91D66B7, 0xBABFFEC0,\r\n  0x5DC6F43D, 0x4E646C4A, 0x7A83C4D3, 0x69215CA4, 0x134C95E1, 0x00EE0D96, 0x3409A50F, 0x27AB3D78,\r\n  0x809C2506, 0x933EBD71, 0xA7D915E8, 0xB47B8D9F, 0xCE1644DA, 0xDDB4DCAD, 0xE9537434, 0xFAF1EC43,\r\n  0x1D88E6BE, 0x0E2A7EC9, 0x3ACDD650, 0x296F4E27, 0x53028762, 0x40A01F15, 0x7447B78C, 0x67E52FFB,\r\n  0xBF59D487, 0xACFB4CF0, 0x981CE469, 0x8BBE7C1E, 0xF1D3B55B, 0xE2712D2C, 0xD69685B5, 0xC5341DC2,\r\n  0x224D173F, 0x31EF8F48, 0x050827D1, 0x16AABFA6, 0x6CC776E3, 0x7F65EE94, 0x4B82460D, 0x5820DE7A,\r\n  0xFBC3FAF9, 0xE861628E, 0xDC86CA17, 0xCF245260, 0xB5499B25, 0xA6EB0352, 0x920CABCB, 0x81AE33BC,\r\n  0x66D73941, 0x7575A136, 0x419209AF, 0x523091D8, 0x285D589D, 0x3BFFC0EA, 0x0F186873, 0x1CBAF004,\r\n  0xC4060B78, 0xD7A4930F, 0xE3433B96, 0xF0E1A3E1, 0x8A8C6AA4, 0x992EF2D3, 0xADC95A4A, 0xBE6BC23D,\r\n  0x5912C8C0, 0x4AB050B7, 0x7E57F82E, 0x6DF56059, 0x1798A91C, 0x043A316B, 0x30DD99F2, 0x237F0185,\r\n  0x844819FB, 0x97EA818C, 0xA30D2915, 0xB0AFB162, 0xCAC27827, 0xD960E050, 0xED8748C9, 0xFE25D0BE,\r\n  0x195CDA43, 0x0AFE4234, 0x3E19EAAD, 0x2DBB72DA, 0x57D6BB9F, 0x447423E8, 0x70938B71, 0x63311306,\r\n  0xBB8DE87A, 0xA82F700D, 0x9CC8D894, 0x8F6A40E3, 0xF50789A6, 0xE6A511D1, 0xD242B948, 0xC1E0213F,\r\n  0x26992BC2, 0x353BB3B5, 0x01DC1B2C, 0x127E835B, 0x68134A1E, 0x7BB1D269, 0x4F567AF0, 0x5CF4E287,\r\n  0x04D43CFD, 0x1776A48A, 0x23910C13, 0x30339464, 0x4A5E5D21, 0x59FCC556, 0x6D1B6DCF, 0x7EB9F5B8,\r\n  0x99C0FF45, 0x8A626732, 0xBE85CFAB, 0xAD2757DC, 0xD74A9E99, 0xC4E806EE, 0xF00FAE77, 0xE3AD3600,\r\n  0x3B11CD7C, 0x28B3550B, 0x1C54FD92, 0x0FF665E5, 0x759BACA0, 0x663934D7, 0x52DE9C4E, 0x417C0439,\r\n  0xA6050EC4, 0xB5A796B3, 0x81403E2A, 0x92E2A65D, 0xE88F6F18, 0xFB2DF76F, 0xCFCA5FF6, 0xDC68C781,\r\n  0x7B5FDFFF, 0x68FD4788, 0x5C1AEF11, 0x4FB87766, 0x35D5BE23, 0x26772654, 0x12908ECD, 0x013216BA,\r\n  0xE64B1C47, 0xF5E98430, 0xC10E2CA9, 0xD2ACB4DE, 0xA8C17D9B, 0xBB63E5EC, 0x8F844D75, 0x9C26D502,\r\n  0x449A2E7E, 0x5738B609, 0x63DF1E90, 0x707D86E7, 0x0A104FA2, 0x19B2D7D5, 0x2D557F4C, 0x3EF7E73B,\r\n  0xD98EEDC6, 0xCA2C75B1, 0xFECBDD28, 0xED69455F, 0x97048C1A, 0x84A6146D, 0xB041BCF4, 0xA3E32483\r\n};\r\n\r\n/*\r\n * end of the CRC lookup table crc_tableil8_o40\r\n */\r\n\r\n\r\n\r\n/*\r\n * The following CRC lookup table was generated automagically\r\n * using the following model parameters:\r\n *\r\n * Generator Polynomial = ................. 0x1EDC6F41\r\n * Generator Polynomial Length = .......... 32 bits\r\n * Reflected Bits = ....................... TRUE\r\n * Table Generation Offset = .............. 32 bits\r\n * Number of Slices = ..................... 8 slices\r\n * Slice Lengths = ........................ 8 8 8 8 8 8 8 8\r\n * Directory Name = ....................... .\\\r\n * File Name = ............................ 8x256_tables.c\r\n */\r\n\r\nconst uint32_t crc_tableil8_o48[256] = {\r\n  0x00000000, 0xA541927E, 0x4F6F520D, 0xEA2EC073, 0x9EDEA41A, 0x3B9F3664, 0xD1B1F617, 0x74F06469,\r\n  0x38513EC5, 0x9D10ACBB, 0x773E6CC8, 0xD27FFEB6, 0xA68F9ADF, 0x03CE08A1, 0xE9E0C8D2, 0x4CA15AAC,\r\n  0x70A27D8A, 0xD5E3EFF4, 0x3FCD2F87, 0x9A8CBDF9, 0xEE7CD990, 0x4B3D4BEE, 0xA1138B9D, 0x045219E3,\r\n  0x48F3434F, 0xEDB2D131, 0x079C1142, 0xA2DD833C, 0xD62DE755, 0x736C752B, 0x9942B558, 0x3C032726,\r\n  0xE144FB14, 0x4405696A, 0xAE2BA919, 0x0B6A3B67, 0x7F9A5F0E, 0xDADBCD70, 0x30F50D03, 0x95B49F7D,\r\n  0xD915C5D1, 0x7C5457AF, 0x967A97DC, 0x333B05A2, 0x47CB61CB, 0xE28AF3B5, 0x08A433C6, 0xADE5A1B8,\r\n  0x91E6869E, 0x34A714E0, 0xDE89D493, 0x7BC846ED, 0x0F382284, 0xAA79B0FA, 0x40577089, 0xE516E2F7,\r\n  0xA9B7B85B, 0x0CF62A25, 0xE6D8EA56, 0x43997828, 0x37691C41, 0x92288E3F, 0x78064E4C, 0xDD47DC32,\r\n  0xC76580D9, 0x622412A7, 0x880AD2D4, 0x2D4B40AA, 0x59BB24C3, 0xFCFAB6BD, 0x16D476CE, 0xB395E4B0,\r\n  0xFF34BE1C, 0x5A752C62, 0xB05BEC11, 0x151A7E6F, 0x61EA1A06, 0xC4AB8878, 0x2E85480B, 0x8BC4DA75,\r\n  0xB7C7FD53, 0x12866F2D, 0xF8A8AF5E, 0x5DE93D20, 0x29195949, 0x8C58CB37, 0x66760B44, 0xC337993A,\r\n  0x8F96C396, 0x2AD751E8, 0xC0F9919B, 0x65B803E5, 0x1148678C, 0xB409F5F2, 0x5E273581, 0xFB66A7FF,\r\n  0x26217BCD, 0x8360E9B3, 0x694E29C0, 0xCC0FBBBE, 0xB8FFDFD7, 0x1DBE4DA9, 0xF7908DDA, 0x52D11FA4,\r\n  0x1E704508, 0xBB31D776, 0x511F1705, 0xF45E857B, 0x80AEE112, 0x25EF736C, 0xCFC1B31F, 0x6A802161,\r\n  0x56830647, 0xF3C29439, 0x19EC544A, 0xBCADC634, 0xC85DA25D, 0x6D1C3023, 0x8732F050, 0x2273622E,\r\n  0x6ED23882, 0xCB93AAFC, 0x21BD6A8F, 0x84FCF8F1, 0xF00C9C98, 0x554D0EE6, 0xBF63CE95, 0x1A225CEB,\r\n  0x8B277743, 0x2E66E53D, 0xC448254E, 0x6109B730, 0x15F9D359, 0xB0B84127, 0x5A968154, 0xFFD7132A,\r\n  0xB3764986, 0x1637DBF8, 0xFC191B8B, 0x595889F5, 0x2DA8ED9C, 0x88E97FE2, 0x62C7BF91, 0xC7862DEF,\r\n  0xFB850AC9, 0x5EC498B7, 0xB4EA58C4, 0x11ABCABA, 0x655BAED3, 0xC01A3CAD, 0x2A34FCDE, 0x8F756EA0,\r\n  0xC3D4340C, 0x6695A672, 0x8CBB6601, 0x29FAF47F, 0x5D0A9016, 0xF84B0268, 0x1265C21B, 0xB7245065,\r\n  0x6A638C57, 0xCF221E29, 0x250CDE5A, 0x804D4C24, 0xF4BD284D, 0x51FCBA33, 0xBBD27A40, 0x1E93E83E,\r\n  0x5232B292, 0xF77320EC, 0x1D5DE09F, 0xB81C72E1, 0xCCEC1688, 0x69AD84F6, 0x83834485, 0x26C2D6FB,\r\n  0x1AC1F1DD, 0xBF8063A3, 0x55AEA3D0, 0xF0EF31AE, 0x841F55C7, 0x215EC7B9, 0xCB7007CA, 0x6E3195B4,\r\n  0x2290CF18, 0x87D15D66, 0x6DFF9D15, 0xC8BE0F6B, 0xBC4E6B02, 0x190FF97C, 0xF321390F, 0x5660AB71,\r\n  0x4C42F79A, 0xE90365E4, 0x032DA597, 0xA66C37E9, 0xD29C5380, 0x77DDC1FE, 0x9DF3018D, 0x38B293F3,\r\n  0x7413C95F, 0xD1525B21, 0x3B7C9B52, 0x9E3D092C, 0xEACD6D45, 0x4F8CFF3B, 0xA5A23F48, 0x00E3AD36,\r\n  0x3CE08A10, 0x99A1186E, 0x738FD81D, 0xD6CE4A63, 0xA23E2E0A, 0x077FBC74, 0xED517C07, 0x4810EE79,\r\n  0x04B1B4D5, 0xA1F026AB, 0x4BDEE6D8, 0xEE9F74A6, 0x9A6F10CF, 0x3F2E82B1, 0xD50042C2, 0x7041D0BC,\r\n  0xAD060C8E, 0x08479EF0, 0xE2695E83, 0x4728CCFD, 0x33D8A894, 0x96993AEA, 0x7CB7FA99, 0xD9F668E7,\r\n  0x9557324B, 0x3016A035, 0xDA386046, 0x7F79F238, 0x0B899651, 0xAEC8042F, 0x44E6C45C, 0xE1A75622,\r\n  0xDDA47104, 0x78E5E37A, 0x92CB2309, 0x378AB177, 0x437AD51E, 0xE63B4760, 0x0C158713, 0xA954156D,\r\n  0xE5F54FC1, 0x40B4DDBF, 0xAA9A1DCC, 0x0FDB8FB2, 0x7B2BEBDB, 0xDE6A79A5, 0x3444B9D6, 0x91052BA8\r\n};\r\n\r\n/*\r\n * end of the CRC lookup table crc_tableil8_o48\r\n */\r\n\r\n\r\n\r\n/*\r\n * The following CRC lookup table was generated automagically\r\n * using the following model parameters:\r\n *\r\n * Generator Polynomial = ................. 0x1EDC6F41\r\n * Generator Polynomial Length = .......... 32 bits\r\n * Reflected Bits = ....................... TRUE\r\n * Table Generation Offset = .............. 32 bits\r\n * Number of Slices = ..................... 8 slices\r\n * Slice Lengths = ........................ 8 8 8 8 8 8 8 8\r\n * Directory Name = ....................... .\\\r\n * File Name = ............................ 8x256_tables.c\r\n */\r\n\r\nconst uint32_t crc_tableil8_o56[256] = {\r\n  0x00000000, 0xDD45AAB8, 0xBF672381, 0x62228939, 0x7B2231F3, 0xA6679B4B, 0xC4451272, 0x1900B8CA,\r\n  0xF64463E6, 0x2B01C95E, 0x49234067, 0x9466EADF, 0x8D665215, 0x5023F8AD, 0x32017194, 0xEF44DB2C,\r\n  0xE964B13D, 0x34211B85, 0x560392BC, 0x8B463804, 0x924680CE, 0x4F032A76, 0x2D21A34F, 0xF06409F7,\r\n  0x1F20D2DB, 0xC2657863, 0xA047F15A, 0x7D025BE2, 0x6402E328, 0xB9474990, 0xDB65C0A9, 0x06206A11,\r\n  0xD725148B, 0x0A60BE33, 0x6842370A, 0xB5079DB2, 0xAC072578, 0x71428FC0, 0x136006F9, 0xCE25AC41,\r\n  0x2161776D, 0xFC24DDD5, 0x9E0654EC, 0x4343FE54, 0x5A43469E, 0x8706EC26, 0xE524651F, 0x3861CFA7,\r\n  0x3E41A5B6, 0xE3040F0E, 0x81268637, 0x5C632C8F, 0x45639445, 0x98263EFD, 0xFA04B7C4, 0x27411D7C,\r\n  0xC805C650, 0x15406CE8, 0x7762E5D1, 0xAA274F69, 0xB327F7A3, 0x6E625D1B, 0x0C40D422, 0xD1057E9A,\r\n  0xABA65FE7, 0x76E3F55F, 0x14C17C66, 0xC984D6DE, 0xD0846E14, 0x0DC1C4AC, 0x6FE34D95, 0xB2A6E72D,\r\n  0x5DE23C01, 0x80A796B9, 0xE2851F80, 0x3FC0B538, 0x26C00DF2, 0xFB85A74A, 0x99A72E73, 0x44E284CB,\r\n  0x42C2EEDA, 0x9F874462, 0xFDA5CD5B, 0x20E067E3, 0x39E0DF29, 0xE4A57591, 0x8687FCA8, 0x5BC25610,\r\n  0xB4868D3C, 0x69C32784, 0x0BE1AEBD, 0xD6A40405, 0xCFA4BCCF, 0x12E11677, 0x70C39F4E, 0xAD8635F6,\r\n  0x7C834B6C, 0xA1C6E1D4, 0xC3E468ED, 0x1EA1C255, 0x07A17A9F, 0xDAE4D027, 0xB8C6591E, 0x6583F3A6,\r\n  0x8AC7288A, 0x57828232, 0x35A00B0B, 0xE8E5A1B3, 0xF1E51979, 0x2CA0B3C1, 0x4E823AF8, 0x93C79040,\r\n  0x95E7FA51, 0x48A250E9, 0x2A80D9D0, 0xF7C57368, 0xEEC5CBA2, 0x3380611A, 0x51A2E823, 0x8CE7429B,\r\n  0x63A399B7, 0xBEE6330F, 0xDCC4BA36, 0x0181108E, 0x1881A844, 0xC5C402FC, 0xA7E68BC5, 0x7AA3217D,\r\n  0x52A0C93F, 0x8FE56387, 0xEDC7EABE, 0x30824006, 0x2982F8CC, 0xF4C75274, 0x96E5DB4D, 0x4BA071F5,\r\n  0xA4E4AAD9, 0x79A10061, 0x1B838958, 0xC6C623E0, 0xDFC69B2A, 0x02833192, 0x60A1B8AB, 0xBDE41213,\r\n  0xBBC47802, 0x6681D2BA, 0x04A35B83, 0xD9E6F13B, 0xC0E649F1, 0x1DA3E349, 0x7F816A70, 0xA2C4C0C8,\r\n  0x4D801BE4, 0x90C5B15C, 0xF2E73865, 0x2FA292DD, 0x36A22A17, 0xEBE780AF, 0x89C50996, 0x5480A32E,\r\n  0x8585DDB4, 0x58C0770C, 0x3AE2FE35, 0xE7A7548D, 0xFEA7EC47, 0x23E246FF, 0x41C0CFC6, 0x9C85657E,\r\n  0x73C1BE52, 0xAE8414EA, 0xCCA69DD3, 0x11E3376B, 0x08E38FA1, 0xD5A62519, 0xB784AC20, 0x6AC10698,\r\n  0x6CE16C89, 0xB1A4C631, 0xD3864F08, 0x0EC3E5B0, 0x17C35D7A, 0xCA86F7C2, 0xA8A47EFB, 0x75E1D443,\r\n  0x9AA50F6F, 0x47E0A5D7, 0x25C22CEE, 0xF8878656, 0xE1873E9C, 0x3CC29424, 0x5EE01D1D, 0x83A5B7A5,\r\n  0xF90696D8, 0x24433C60, 0x4661B559, 0x9B241FE1, 0x8224A72B, 0x5F610D93, 0x3D4384AA, 0xE0062E12,\r\n  0x0F42F53E, 0xD2075F86, 0xB025D6BF, 0x6D607C07, 0x7460C4CD, 0xA9256E75, 0xCB07E74C, 0x16424DF4,\r\n  0x106227E5, 0xCD278D5D, 0xAF050464, 0x7240AEDC, 0x6B401616, 0xB605BCAE, 0xD4273597, 0x09629F2F,\r\n  0xE6264403, 0x3B63EEBB, 0x59416782, 0x8404CD3A, 0x9D0475F0, 0x4041DF48, 0x22635671, 0xFF26FCC9,\r\n  0x2E238253, 0xF36628EB, 0x9144A1D2, 0x4C010B6A, 0x5501B3A0, 0x88441918, 0xEA669021, 0x37233A99,\r\n  0xD867E1B5, 0x05224B0D, 0x6700C234, 0xBA45688C, 0xA345D046, 0x7E007AFE, 0x1C22F3C7, 0xC167597F,\r\n  0xC747336E, 0x1A0299D6, 0x782010EF, 0xA565BA57, 0xBC65029D, 0x6120A825, 0x0302211C, 0xDE478BA4,\r\n  0x31035088, 0xEC46FA30, 0x8E647309, 0x5321D9B1, 0x4A21617B, 0x9764CBC3, 0xF54642FA, 0x2803E842\r\n};\r\n\r\n/*\r\n * end of the CRC lookup table crc_tableil8_o56\r\n */\r\n\r\n\r\n\r\n/*\r\n * The following CRC lookup table was generated automagically\r\n * using the following model parameters:\r\n *\r\n * Generator Polynomial = ................. 0x1EDC6F41\r\n * Generator Polynomial Length = .......... 32 bits\r\n * Reflected Bits = ....................... TRUE\r\n * Table Generation Offset = .............. 32 bits\r\n * Number of Slices = ..................... 8 slices\r\n * Slice Lengths = ........................ 8 8 8 8 8 8 8 8\r\n * Directory Name = ....................... .\\\r\n * File Name = ............................ 8x256_tables.c\r\n */\r\n\r\nconst uint32_t crc_tableil8_o64[256] = {\r\n  0x00000000, 0x38116FAC, 0x7022DF58, 0x4833B0F4, 0xE045BEB0, 0xD854D11C, 0x906761E8, 0xA8760E44,\r\n  0xC5670B91, 0xFD76643D, 0xB545D4C9, 0x8D54BB65, 0x2522B521, 0x1D33DA8D, 0x55006A79, 0x6D1105D5,\r\n  0x8F2261D3, 0xB7330E7F, 0xFF00BE8B, 0xC711D127, 0x6F67DF63, 0x5776B0CF, 0x1F45003B, 0x27546F97,\r\n  0x4A456A42, 0x725405EE, 0x3A67B51A, 0x0276DAB6, 0xAA00D4F2, 0x9211BB5E, 0xDA220BAA, 0xE2336406,\r\n  0x1BA8B557, 0x23B9DAFB, 0x6B8A6A0F, 0x539B05A3, 0xFBED0BE7, 0xC3FC644B, 0x8BCFD4BF, 0xB3DEBB13,\r\n  0xDECFBEC6, 0xE6DED16A, 0xAEED619E, 0x96FC0E32, 0x3E8A0076, 0x069B6FDA, 0x4EA8DF2E, 0x76B9B082,\r\n  0x948AD484, 0xAC9BBB28, 0xE4A80BDC, 0xDCB96470, 0x74CF6A34, 0x4CDE0598, 0x04EDB56C, 0x3CFCDAC0,\r\n  0x51EDDF15, 0x69FCB0B9, 0x21CF004D, 0x19DE6FE1, 0xB1A861A5, 0x89B90E09, 0xC18ABEFD, 0xF99BD151,\r\n  0x37516AAE, 0x0F400502, 0x4773B5F6, 0x7F62DA5A, 0xD714D41E, 0xEF05BBB2, 0xA7360B46, 0x9F2764EA,\r\n  0xF236613F, 0xCA270E93, 0x8214BE67, 0xBA05D1CB, 0x1273DF8F, 0x2A62B023, 0x625100D7, 0x5A406F7B,\r\n  0xB8730B7D, 0x806264D1, 0xC851D425, 0xF040BB89, 0x5836B5CD, 0x6027DA61, 0x28146A95, 0x10050539,\r\n  0x7D1400EC, 0x45056F40, 0x0D36DFB4, 0x3527B018, 0x9D51BE5C, 0xA540D1F0, 0xED736104, 0xD5620EA8,\r\n  0x2CF9DFF9, 0x14E8B055, 0x5CDB00A1, 0x64CA6F0D, 0xCCBC6149, 0xF4AD0EE5, 0xBC9EBE11, 0x848FD1BD,\r\n  0xE99ED468, 0xD18FBBC4, 0x99BC0B30, 0xA1AD649C, 0x09DB6AD8, 0x31CA0574, 0x79F9B580, 0x41E8DA2C,\r\n  0xA3DBBE2A, 0x9BCAD186, 0xD3F96172, 0xEBE80EDE, 0x439E009A, 0x7B8F6F36, 0x33BCDFC2, 0x0BADB06E,\r\n  0x66BCB5BB, 0x5EADDA17, 0x169E6AE3, 0x2E8F054F, 0x86F90B0B, 0xBEE864A7, 0xF6DBD453, 0xCECABBFF,\r\n  0x6EA2D55C, 0x56B3BAF0, 0x1E800A04, 0x269165A8, 0x8EE76BEC, 0xB6F60440, 0xFEC5B4B4, 0xC6D4DB18,\r\n  0xABC5DECD, 0x93D4B161, 0xDBE70195, 0xE3F66E39, 0x4B80607D, 0x73910FD1, 0x3BA2BF25, 0x03B3D089,\r\n  0xE180B48F, 0xD991DB23, 0x91A26BD7, 0xA9B3047B, 0x01C50A3F, 0x39D46593, 0x71E7D567, 0x49F6BACB,\r\n  0x24E7BF1E, 0x1CF6D0B2, 0x54C56046, 0x6CD40FEA, 0xC4A201AE, 0xFCB36E02, 0xB480DEF6, 0x8C91B15A,\r\n  0x750A600B, 0x4D1B0FA7, 0x0528BF53, 0x3D39D0FF, 0x954FDEBB, 0xAD5EB117, 0xE56D01E3, 0xDD7C6E4F,\r\n  0xB06D6B9A, 0x887C0436, 0xC04FB4C2, 0xF85EDB6E, 0x5028D52A, 0x6839BA86, 0x200A0A72, 0x181B65DE,\r\n  0xFA2801D8, 0xC2396E74, 0x8A0ADE80, 0xB21BB12C, 0x1A6DBF68, 0x227CD0C4, 0x6A4F6030, 0x525E0F9C,\r\n  0x3F4F0A49, 0x075E65E5, 0x4F6DD511, 0x777CBABD, 0xDF0AB4F9, 0xE71BDB55, 0xAF286BA1, 0x9739040D,\r\n  0x59F3BFF2, 0x61E2D05E, 0x29D160AA, 0x11C00F06, 0xB9B60142, 0x81A76EEE, 0xC994DE1A, 0xF185B1B6,\r\n  0x9C94B463, 0xA485DBCF, 0xECB66B3B, 0xD4A70497, 0x7CD10AD3, 0x44C0657F, 0x0CF3D58B, 0x34E2BA27,\r\n  0xD6D1DE21, 0xEEC0B18D, 0xA6F30179, 0x9EE26ED5, 0x36946091, 0x0E850F3D, 0x46B6BFC9, 0x7EA7D065,\r\n  0x13B6D5B0, 0x2BA7BA1C, 0x63940AE8, 0x5B856544, 0xF3F36B00, 0xCBE204AC, 0x83D1B458, 0xBBC0DBF4,\r\n  0x425B0AA5, 0x7A4A6509, 0x3279D5FD, 0x0A68BA51, 0xA21EB415, 0x9A0FDBB9, 0xD23C6B4D, 0xEA2D04E1,\r\n  0x873C0134, 0xBF2D6E98, 0xF71EDE6C, 0xCF0FB1C0, 0x6779BF84, 0x5F68D028, 0x175B60DC, 0x2F4A0F70,\r\n  0xCD796B76, 0xF56804DA, 0xBD5BB42E, 0x854ADB82, 0x2D3CD5C6, 0x152DBA6A, 0x5D1E0A9E, 0x650F6532,\r\n  0x081E60E7, 0x300F0F4B, 0x783CBFBF, 0x402DD013, 0xE85BDE57, 0xD04AB1FB, 0x9879010F, 0xA0686EA3\r\n};\r\n\r\n/*\r\n * end of the CRC lookup table crc_tableil8_o64\r\n */\r\n\r\n\r\n\r\n/*\r\n * The following CRC lookup table was generated automagically\r\n * using the following model parameters:\r\n *\r\n * Generator Polynomial = ................. 0x1EDC6F41\r\n * Generator Polynomial Length = .......... 32 bits\r\n * Reflected Bits = ....................... TRUE\r\n * Table Generation Offset = .............. 32 bits\r\n * Number of Slices = ..................... 8 slices\r\n * Slice Lengths = ........................ 8 8 8 8 8 8 8 8\r\n * Directory Name = ....................... .\\\r\n * File Name = ............................ 8x256_tables.c\r\n */\r\n\r\nconst uint32_t crc_tableil8_o72[256] = {\r\n  0x00000000, 0xEF306B19, 0xDB8CA0C3, 0x34BCCBDA, 0xB2F53777, 0x5DC55C6E, 0x697997B4, 0x8649FCAD,\r\n  0x6006181F, 0x8F367306, 0xBB8AB8DC, 0x54BAD3C5, 0xD2F32F68, 0x3DC34471, 0x097F8FAB, 0xE64FE4B2,\r\n  0xC00C303E, 0x2F3C5B27, 0x1B8090FD, 0xF4B0FBE4, 0x72F90749, 0x9DC96C50, 0xA975A78A, 0x4645CC93,\r\n  0xA00A2821, 0x4F3A4338, 0x7B8688E2, 0x94B6E3FB, 0x12FF1F56, 0xFDCF744F, 0xC973BF95, 0x2643D48C,\r\n  0x85F4168D, 0x6AC47D94, 0x5E78B64E, 0xB148DD57, 0x370121FA, 0xD8314AE3, 0xEC8D8139, 0x03BDEA20,\r\n  0xE5F20E92, 0x0AC2658B, 0x3E7EAE51, 0xD14EC548, 0x570739E5, 0xB83752FC, 0x8C8B9926, 0x63BBF23F,\r\n  0x45F826B3, 0xAAC84DAA, 0x9E748670, 0x7144ED69, 0xF70D11C4, 0x183D7ADD, 0x2C81B107, 0xC3B1DA1E,\r\n  0x25FE3EAC, 0xCACE55B5, 0xFE729E6F, 0x1142F576, 0x970B09DB, 0x783B62C2, 0x4C87A918, 0xA3B7C201,\r\n  0x0E045BEB, 0xE13430F2, 0xD588FB28, 0x3AB89031, 0xBCF16C9C, 0x53C10785, 0x677DCC5F, 0x884DA746,\r\n  0x6E0243F4, 0x813228ED, 0xB58EE337, 0x5ABE882E, 0xDCF77483, 0x33C71F9A, 0x077BD440, 0xE84BBF59,\r\n  0xCE086BD5, 0x213800CC, 0x1584CB16, 0xFAB4A00F, 0x7CFD5CA2, 0x93CD37BB, 0xA771FC61, 0x48419778,\r\n  0xAE0E73CA, 0x413E18D3, 0x7582D309, 0x9AB2B810, 0x1CFB44BD, 0xF3CB2FA4, 0xC777E47E, 0x28478F67,\r\n  0x8BF04D66, 0x64C0267F, 0x507CEDA5, 0xBF4C86BC, 0x39057A11, 0xD6351108, 0xE289DAD2, 0x0DB9B1CB,\r\n  0xEBF65579, 0x04C63E60, 0x307AF5BA, 0xDF4A9EA3, 0x5903620E, 0xB6330917, 0x828FC2CD, 0x6DBFA9D4,\r\n  0x4BFC7D58, 0xA4CC1641, 0x9070DD9B, 0x7F40B682, 0xF9094A2F, 0x16392136, 0x2285EAEC, 0xCDB581F5,\r\n  0x2BFA6547, 0xC4CA0E5E, 0xF076C584, 0x1F46AE9D, 0x990F5230, 0x763F3929, 0x4283F2F3, 0xADB399EA,\r\n  0x1C08B7D6, 0xF338DCCF, 0xC7841715, 0x28B47C0C, 0xAEFD80A1, 0x41CDEBB8, 0x75712062, 0x9A414B7B,\r\n  0x7C0EAFC9, 0x933EC4D0, 0xA7820F0A, 0x48B26413, 0xCEFB98BE, 0x21CBF3A7, 0x1577387D, 0xFA475364,\r\n  0xDC0487E8, 0x3334ECF1, 0x0788272B, 0xE8B84C32, 0x6EF1B09F, 0x81C1DB86, 0xB57D105C, 0x5A4D7B45,\r\n  0xBC029FF7, 0x5332F4EE, 0x678E3F34, 0x88BE542D, 0x0EF7A880, 0xE1C7C399, 0xD57B0843, 0x3A4B635A,\r\n  0x99FCA15B, 0x76CCCA42, 0x42700198, 0xAD406A81, 0x2B09962C, 0xC439FD35, 0xF08536EF, 0x1FB55DF6,\r\n  0xF9FAB944, 0x16CAD25D, 0x22761987, 0xCD46729E, 0x4B0F8E33, 0xA43FE52A, 0x90832EF0, 0x7FB345E9,\r\n  0x59F09165, 0xB6C0FA7C, 0x827C31A6, 0x6D4C5ABF, 0xEB05A612, 0x0435CD0B, 0x308906D1, 0xDFB96DC8,\r\n  0x39F6897A, 0xD6C6E263, 0xE27A29B9, 0x0D4A42A0, 0x8B03BE0D, 0x6433D514, 0x508F1ECE, 0xBFBF75D7,\r\n  0x120CEC3D, 0xFD3C8724, 0xC9804CFE, 0x26B027E7, 0xA0F9DB4A, 0x4FC9B053, 0x7B757B89, 0x94451090,\r\n  0x720AF422, 0x9D3A9F3B, 0xA98654E1, 0x46B63FF8, 0xC0FFC355, 0x2FCFA84C, 0x1B736396, 0xF443088F,\r\n  0xD200DC03, 0x3D30B71A, 0x098C7CC0, 0xE6BC17D9, 0x60F5EB74, 0x8FC5806D, 0xBB794BB7, 0x544920AE,\r\n  0xB206C41C, 0x5D36AF05, 0x698A64DF, 0x86BA0FC6, 0x00F3F36B, 0xEFC39872, 0xDB7F53A8, 0x344F38B1,\r\n  0x97F8FAB0, 0x78C891A9, 0x4C745A73, 0xA344316A, 0x250DCDC7, 0xCA3DA6DE, 0xFE816D04, 0x11B1061D,\r\n  0xF7FEE2AF, 0x18CE89B6, 0x2C72426C, 0xC3422975, 0x450BD5D8, 0xAA3BBEC1, 0x9E87751B, 0x71B71E02,\r\n  0x57F4CA8E, 0xB8C4A197, 0x8C786A4D, 0x63480154, 0xE501FDF9, 0x0A3196E0, 0x3E8D5D3A, 0xD1BD3623,\r\n  0x37F2D291, 0xD8C2B988, 0xEC7E7252, 0x034E194B, 0x8507E5E6, 0x6A378EFF, 0x5E8B4525, 0xB1BB2E3C\r\n};\r\n\r\n/*\r\n * end of the CRC lookup table crc_tableil8_o72\r\n */\r\n\r\n\r\n\r\n/*\r\n * The following CRC lookup table was generated automagically\r\n * using the following model parameters:\r\n *\r\n * Generator Polynomial = ................. 0x1EDC6F41\r\n * Generator Polynomial Length = .......... 32 bits\r\n * Reflected Bits = ....................... TRUE\r\n * Table Generation Offset = .............. 32 bits\r\n * Number of Slices = ..................... 8 slices\r\n * Slice Lengths = ........................ 8 8 8 8 8 8 8 8\r\n * Directory Name = ....................... .\\\r\n * File Name = ............................ 8x256_tables.c\r\n */\r\n\r\nconst uint32_t crc_tableil8_o80[256] = {\r\n  0x00000000, 0x68032CC8, 0xD0065990, 0xB8057558, 0xA5E0C5D1, 0xCDE3E919, 0x75E69C41, 0x1DE5B089,\r\n  0x4E2DFD53, 0x262ED19B, 0x9E2BA4C3, 0xF628880B, 0xEBCD3882, 0x83CE144A, 0x3BCB6112, 0x53C84DDA,\r\n  0x9C5BFAA6, 0xF458D66E, 0x4C5DA336, 0x245E8FFE, 0x39BB3F77, 0x51B813BF, 0xE9BD66E7, 0x81BE4A2F,\r\n  0xD27607F5, 0xBA752B3D, 0x02705E65, 0x6A7372AD, 0x7796C224, 0x1F95EEEC, 0xA7909BB4, 0xCF93B77C,\r\n  0x3D5B83BD, 0x5558AF75, 0xED5DDA2D, 0x855EF6E5, 0x98BB466C, 0xF0B86AA4, 0x48BD1FFC, 0x20BE3334,\r\n  0x73767EEE, 0x1B755226, 0xA370277E, 0xCB730BB6, 0xD696BB3F, 0xBE9597F7, 0x0690E2AF, 0x6E93CE67,\r\n  0xA100791B, 0xC90355D3, 0x7106208B, 0x19050C43, 0x04E0BCCA, 0x6CE39002, 0xD4E6E55A, 0xBCE5C992,\r\n  0xEF2D8448, 0x872EA880, 0x3F2BDDD8, 0x5728F110, 0x4ACD4199, 0x22CE6D51, 0x9ACB1809, 0xF2C834C1,\r\n  0x7AB7077A, 0x12B42BB2, 0xAAB15EEA, 0xC2B27222, 0xDF57C2AB, 0xB754EE63, 0x0F519B3B, 0x6752B7F3,\r\n  0x349AFA29, 0x5C99D6E1, 0xE49CA3B9, 0x8C9F8F71, 0x917A3FF8, 0xF9791330, 0x417C6668, 0x297F4AA0,\r\n  0xE6ECFDDC, 0x8EEFD114, 0x36EAA44C, 0x5EE98884, 0x430C380D, 0x2B0F14C5, 0x930A619D, 0xFB094D55,\r\n  0xA8C1008F, 0xC0C22C47, 0x78C7591F, 0x10C475D7, 0x0D21C55E, 0x6522E996, 0xDD279CCE, 0xB524B006,\r\n  0x47EC84C7, 0x2FEFA80F, 0x97EADD57, 0xFFE9F19F, 0xE20C4116, 0x8A0F6DDE, 0x320A1886, 0x5A09344E,\r\n  0x09C17994, 0x61C2555C, 0xD9C72004, 0xB1C40CCC, 0xAC21BC45, 0xC422908D, 0x7C27E5D5, 0x1424C91D,\r\n  0xDBB77E61, 0xB3B452A9, 0x0BB127F1, 0x63B20B39, 0x7E57BBB0, 0x16549778, 0xAE51E220, 0xC652CEE8,\r\n  0x959A8332, 0xFD99AFFA, 0x459CDAA2, 0x2D9FF66A, 0x307A46E3, 0x58796A2B, 0xE07C1F73, 0x887F33BB,\r\n  0xF56E0EF4, 0x9D6D223C, 0x25685764, 0x4D6B7BAC, 0x508ECB25, 0x388DE7ED, 0x808892B5, 0xE88BBE7D,\r\n  0xBB43F3A7, 0xD340DF6F, 0x6B45AA37, 0x034686FF, 0x1EA33676, 0x76A01ABE, 0xCEA56FE6, 0xA6A6432E,\r\n  0x6935F452, 0x0136D89A, 0xB933ADC2, 0xD130810A, 0xCCD53183, 0xA4D61D4B, 0x1CD36813, 0x74D044DB,\r\n  0x27180901, 0x4F1B25C9, 0xF71E5091, 0x9F1D7C59, 0x82F8CCD0, 0xEAFBE018, 0x52FE9540, 0x3AFDB988,\r\n  0xC8358D49, 0xA036A181, 0x1833D4D9, 0x7030F811, 0x6DD54898, 0x05D66450, 0xBDD31108, 0xD5D03DC0,\r\n  0x8618701A, 0xEE1B5CD2, 0x561E298A, 0x3E1D0542, 0x23F8B5CB, 0x4BFB9903, 0xF3FEEC5B, 0x9BFDC093,\r\n  0x546E77EF, 0x3C6D5B27, 0x84682E7F, 0xEC6B02B7, 0xF18EB23E, 0x998D9EF6, 0x2188EBAE, 0x498BC766,\r\n  0x1A438ABC, 0x7240A674, 0xCA45D32C, 0xA246FFE4, 0xBFA34F6D, 0xD7A063A5, 0x6FA516FD, 0x07A63A35,\r\n  0x8FD9098E, 0xE7DA2546, 0x5FDF501E, 0x37DC7CD6, 0x2A39CC5F, 0x423AE097, 0xFA3F95CF, 0x923CB907,\r\n  0xC1F4F4DD, 0xA9F7D815, 0x11F2AD4D, 0x79F18185, 0x6414310C, 0x0C171DC4, 0xB412689C, 0xDC114454,\r\n  0x1382F328, 0x7B81DFE0, 0xC384AAB8, 0xAB878670, 0xB66236F9, 0xDE611A31, 0x66646F69, 0x0E6743A1,\r\n  0x5DAF0E7B, 0x35AC22B3, 0x8DA957EB, 0xE5AA7B23, 0xF84FCBAA, 0x904CE762, 0x2849923A, 0x404ABEF2,\r\n  0xB2828A33, 0xDA81A6FB, 0x6284D3A3, 0x0A87FF6B, 0x17624FE2, 0x7F61632A, 0xC7641672, 0xAF673ABA,\r\n  0xFCAF7760, 0x94AC5BA8, 0x2CA92EF0, 0x44AA0238, 0x594FB2B1, 0x314C9E79, 0x8949EB21, 0xE14AC7E9,\r\n  0x2ED97095, 0x46DA5C5D, 0xFEDF2905, 0x96DC05CD, 0x8B39B544, 0xE33A998C, 0x5B3FECD4, 0x333CC01C,\r\n  0x60F48DC6, 0x08F7A10E, 0xB0F2D456, 0xD8F1F89E, 0xC5144817, 0xAD1764DF, 0x15121187, 0x7D113D4F\r\n};\r\n\r\n/*\r\n * end of the CRC lookup table crc_tableil8_o80\r\n */\r\n\r\n\r\n\r\n/*\r\n * The following CRC lookup table was generated automagically\r\n * using the following model parameters:\r\n *\r\n * Generator Polynomial = ................. 0x1EDC6F41\r\n * Generator Polynomial Length = .......... 32 bits\r\n * Reflected Bits = ....................... TRUE\r\n * Table Generation Offset = .............. 32 bits\r\n * Number of Slices = ..................... 8 slices\r\n * Slice Lengths = ........................ 8 8 8 8 8 8 8 8\r\n * Directory Name = ....................... .\\\r\n * File Name = ............................ 8x256_tables.c\r\n */\r\n\r\nconst uint32_t crc_tableil8_o88[256] = {\r\n  0x00000000, 0x493C7D27, 0x9278FA4E, 0xDB448769, 0x211D826D, 0x6821FF4A, 0xB3657823, 0xFA590504,\r\n  0x423B04DA, 0x0B0779FD, 0xD043FE94, 0x997F83B3, 0x632686B7, 0x2A1AFB90, 0xF15E7CF9, 0xB86201DE,\r\n  0x847609B4, 0xCD4A7493, 0x160EF3FA, 0x5F328EDD, 0xA56B8BD9, 0xEC57F6FE, 0x37137197, 0x7E2F0CB0,\r\n  0xC64D0D6E, 0x8F717049, 0x5435F720, 0x1D098A07, 0xE7508F03, 0xAE6CF224, 0x7528754D, 0x3C14086A,\r\n  0x0D006599, 0x443C18BE, 0x9F789FD7, 0xD644E2F0, 0x2C1DE7F4, 0x65219AD3, 0xBE651DBA, 0xF759609D,\r\n  0x4F3B6143, 0x06071C64, 0xDD439B0D, 0x947FE62A, 0x6E26E32E, 0x271A9E09, 0xFC5E1960, 0xB5626447,\r\n  0x89766C2D, 0xC04A110A, 0x1B0E9663, 0x5232EB44, 0xA86BEE40, 0xE1579367, 0x3A13140E, 0x732F6929,\r\n  0xCB4D68F7, 0x827115D0, 0x593592B9, 0x1009EF9E, 0xEA50EA9A, 0xA36C97BD, 0x782810D4, 0x31146DF3,\r\n  0x1A00CB32, 0x533CB615, 0x8878317C, 0xC1444C5B, 0x3B1D495F, 0x72213478, 0xA965B311, 0xE059CE36,\r\n  0x583BCFE8, 0x1107B2CF, 0xCA4335A6, 0x837F4881, 0x79264D85, 0x301A30A2, 0xEB5EB7CB, 0xA262CAEC,\r\n  0x9E76C286, 0xD74ABFA1, 0x0C0E38C8, 0x453245EF, 0xBF6B40EB, 0xF6573DCC, 0x2D13BAA5, 0x642FC782,\r\n  0xDC4DC65C, 0x9571BB7B, 0x4E353C12, 0x07094135, 0xFD504431, 0xB46C3916, 0x6F28BE7F, 0x2614C358,\r\n  0x1700AEAB, 0x5E3CD38C, 0x857854E5, 0xCC4429C2, 0x361D2CC6, 0x7F2151E1, 0xA465D688, 0xED59ABAF,\r\n  0x553BAA71, 0x1C07D756, 0xC743503F, 0x8E7F2D18, 0x7426281C, 0x3D1A553B, 0xE65ED252, 0xAF62AF75,\r\n  0x9376A71F, 0xDA4ADA38, 0x010E5D51, 0x48322076, 0xB26B2572, 0xFB575855, 0x2013DF3C, 0x692FA21B,\r\n  0xD14DA3C5, 0x9871DEE2, 0x4335598B, 0x0A0924AC, 0xF05021A8, 0xB96C5C8F, 0x6228DBE6, 0x2B14A6C1,\r\n  0x34019664, 0x7D3DEB43, 0xA6796C2A, 0xEF45110D, 0x151C1409, 0x5C20692E, 0x8764EE47, 0xCE589360,\r\n  0x763A92BE, 0x3F06EF99, 0xE44268F0, 0xAD7E15D7, 0x572710D3, 0x1E1B6DF4, 0xC55FEA9D, 0x8C6397BA,\r\n  0xB0779FD0, 0xF94BE2F7, 0x220F659E, 0x6B3318B9, 0x916A1DBD, 0xD856609A, 0x0312E7F3, 0x4A2E9AD4,\r\n  0xF24C9B0A, 0xBB70E62D, 0x60346144, 0x29081C63, 0xD3511967, 0x9A6D6440, 0x4129E329, 0x08159E0E,\r\n  0x3901F3FD, 0x703D8EDA, 0xAB7909B3, 0xE2457494, 0x181C7190, 0x51200CB7, 0x8A648BDE, 0xC358F6F9,\r\n  0x7B3AF727, 0x32068A00, 0xE9420D69, 0xA07E704E, 0x5A27754A, 0x131B086D, 0xC85F8F04, 0x8163F223,\r\n  0xBD77FA49, 0xF44B876E, 0x2F0F0007, 0x66337D20, 0x9C6A7824, 0xD5560503, 0x0E12826A, 0x472EFF4D,\r\n  0xFF4CFE93, 0xB67083B4, 0x6D3404DD, 0x240879FA, 0xDE517CFE, 0x976D01D9, 0x4C2986B0, 0x0515FB97,\r\n  0x2E015D56, 0x673D2071, 0xBC79A718, 0xF545DA3F, 0x0F1CDF3B, 0x4620A21C, 0x9D642575, 0xD4585852,\r\n  0x6C3A598C, 0x250624AB, 0xFE42A3C2, 0xB77EDEE5, 0x4D27DBE1, 0x041BA6C6, 0xDF5F21AF, 0x96635C88,\r\n  0xAA7754E2, 0xE34B29C5, 0x380FAEAC, 0x7133D38B, 0x8B6AD68F, 0xC256ABA8, 0x19122CC1, 0x502E51E6,\r\n  0xE84C5038, 0xA1702D1F, 0x7A34AA76, 0x3308D751, 0xC951D255, 0x806DAF72, 0x5B29281B, 0x1215553C,\r\n  0x230138CF, 0x6A3D45E8, 0xB179C281, 0xF845BFA6, 0x021CBAA2, 0x4B20C785, 0x906440EC, 0xD9583DCB,\r\n  0x613A3C15, 0x28064132, 0xF342C65B, 0xBA7EBB7C, 0x4027BE78, 0x091BC35F, 0xD25F4436, 0x9B633911,\r\n  0xA777317B, 0xEE4B4C5C, 0x350FCB35, 0x7C33B612, 0x866AB316, 0xCF56CE31, 0x14124958, 0x5D2E347F,\r\n  0xE54C35A1, 0xAC704886, 0x7734CFEF, 0x3E08B2C8, 0xC451B7CC, 0x8D6DCAEB, 0x56294D82, 0x1F1530A5\r\n};\r\n\r\n/*\r\n * end of the CRC lookup table crc_tableil8_o88\r\n */\r\n\r\n}  // namespace checksum\r\n"
  },
  {
    "path": "common/crc32c/crc32ctables.h",
    "content": "// Copyright 2008,2009,2010 Massachusetts Institute of Technology.\n// All rights reserved. Use of this source code is governed by a\n// BSD-style license that can be found in the LICENSE file.\n\n#ifndef LOGGING_CRC32CTABLES_H__\n#define LOGGING_CRC32CTABLES_H__\n\n#include <stdint.h>\n\nnamespace checksum\n{\n\nextern const uint32_t crc_tableil8_o32[256];\nextern const uint32_t crc_tableil8_o40[256];\nextern const uint32_t crc_tableil8_o48[256];\nextern const uint32_t crc_tableil8_o56[256];\nextern const uint32_t crc_tableil8_o64[256];\nextern const uint32_t crc_tableil8_o72[256];\nextern const uint32_t crc_tableil8_o80[256];\nextern const uint32_t crc_tableil8_o88[256];\n\n}\n#endif\n"
  },
  {
    "path": "common/doxygen.hh",
    "content": "/**\n * @mainpage EOS Source Code Documentation\n * @section home Project Homepage\n * Information about EOS can be found under http://eos.cern.ch.\n * The project homepage links to a GIT repository and release information.\n * @section license License\n * EOS is released under GNU GPL license. More information see the source code or license information on the project homepage.\n */\n\n/**\n * @file   doxygen.hh\n * \n * @brief  Doxygen Main Page Definition.\n * \n * \n */\n"
  },
  {
    "path": "common/eos_cta_pb/CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Jozsef Makai - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2018 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n#-------------------------------------------------------------------------------\n# Disable -Wsign-compare warnings due to\n# grpcpp/support/proto_buffer_reader.h:157:24: warning: comparison of\n# integer expressions of different signedness: ‘uint64_t’ {aka ‘long\n# unsigned int’} and ‘int’ [-Wsign-compare]\n#-------------------------------------------------------------------------------\nadd_compile_options(-Wno-sign-compare)\n\nset(EOS_CTA_PB_DIR ${CMAKE_SOURCE_DIR}/common/xrootd-ssi-protobuf-interface/eos_cta/protobuf/)\nPROTOBUF_GENERATE_CPP(CTA_ADMIN_SRCS CTA_ADMIN_HDRS ${EOS_CTA_PB_DIR}/cta_admin.proto)\nPROTOBUF_GENERATE_CPP(CTA_COMMON_SRCS CTA_COMMON_HDRS ${EOS_CTA_PB_DIR}/cta_common.proto)\nPROTOBUF_GENERATE_CPP(CTA_EOS_SRCS CTA_EOS_HDRS ${EOS_CTA_PB_DIR}/cta_eos.proto)\nPROTOBUF_GENERATE_CPP(CTA_FRONTEND_SRCS CTA_FRONTEND_HDRS ${EOS_CTA_PB_DIR}/cta_frontend.proto)\n\nif (GRPC_FOUND)\n  # compile the gRPC code\n  set(GRPC_PROTOBUF_PATH \"${CMAKE_BINARY_DIR}/proto/\")\n  grpc_generate_cpp(GRPC_CTA_FRONTEND_SRCS GRPC_CTA_FRONTEND_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_CTA_PB_DIR}/cta_frontend.proto)\nendif()\n\nset(EOS_CTA_PB_SRCS\n  ${CTA_ADMIN_SRCS} ${CTA_COMMON_SRCS} ${CTA_EOS_SRCS} ${CTA_FRONTEND_SRCS} ${GRPC_CTA_FRONTEND_SRCS})\n\nset(EOS_CTA_PB_HDRS\n  ${CTA_ADMIN_HDRS} ${CTA_COMMON_HDRS} ${CTA_EOS_HDRS} ${CTA_FRONTEND_HDRS} ${GRPC_CTA_FRONTEND_HDRS})\n\nset_source_files_properties(\n  ${EOS_CTA_PB_SRCS} ${EOS_CTA_PB_HDRS}\n  PROPERTIES GENERATED TRUE)\n\nadd_library(XrdSsiPbEosCta-Objects OBJECT\n  ${EOS_CTA_PB_SRCS} ${EOS_CTA_PB_HDRS})\n\ntarget_link_libraries(XrdSsiPbEosCta-Objects PUBLIC\n  PROTOBUF::PROTOBUF\n  GRPC::grpc\n  GRPC::grpc++)\n\ntarget_include_directories(XrdSsiPbEosCta-Objects PUBLIC\n  $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)\n\nset_target_properties(XrdSsiPbEosCta-Objects PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n"
  },
  {
    "path": "common/eos_cta_pb/EosCtaAlertHandler.hh",
    "content": "//------------------------------------------------------------------------------\n// File: EosCtaAlertHandler.hh\n// Author: Jozsef Makai - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n\n#include \"common/Logging.hh\"\n#include \"common/xrootd-ssi-protobuf-interface/eos_cta/include/CtaFrontendApi.hpp\"\n\n// Define XRootD SSI Alert message callback\n\nnamespace XrdSsiPb {\n\n/*!\n * Alert callback.\n *\n * Handles alert messages from CTA.\n */\n\n  template<>\n  void RequestCallback<cta::xrd::Alert>::operator()(const cta::xrd::Alert &alert)\n  {\n    eos_static_alert(\"Alert from CTA with message: %s\", alert.message_txt().c_str());\n  }\n\n}\n"
  },
  {
    "path": "common/exception/Exception.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file Exception.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"Exception.hh\"\n#include <XrdSfs/XrdSfsInterface.hh>\n\nEOSCOMMONNAMESPACE_BEGIN\n\nException::Exception(const std::string& exceptionMsg): std::exception(),\n  mErrorMsg(exceptionMsg)\n{\n}\n\nconst char* Exception::what() const noexcept\n{\n  return mErrorMsg.c_str();\n}\n\nint Exception::fillXrdErrInfo(XrdOucErrInfo& error, int errorCode) const\n{\n  char buffer[4096];\n\n  // Get the reason for the error\n  if (errorCode < 0) {\n    errorCode = -errorCode;\n  }\n\n  snprintf(buffer, sizeof(buffer), \"%s\" , mErrorMsg.c_str());\n  error.setErrInfo(errorCode, buffer);\n  return SFS_ERROR;\n}\n\nEOSCOMMONNAMESPACE_END"
  },
  {
    "path": "common/exception/Exception.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Exception.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_EXCEPTION_HH\n#define EOS_EXCEPTION_HH\n\n#include \"common/Namespace.hh\"\n#include <exception>\n#include <string>\n#include <XrdOuc/XrdOucErrInfo.hh>\n\nEOSCOMMONNAMESPACE_BEGIN\n\nclass Exception : public std::exception\n{\npublic:\n  /**\n   * Constructor of the Exception\n   * @param exceptionMsg the error message associated to this exception\n   */\n  Exception(const std::string& exceptionMsg);\n  /**\n   * Returns the message of this exception\n   * @return the message of this exception\n   */\n  virtual const char*  what() const noexcept;\n\n  /**\n   * Assign the exception message to the Xrd error information passed in parameter\n   * @param error the Xrd error info object to assign the exception message\n   * @param errorCode the Xrd error code assocaited to the exception message\n   * @return the error code linked to this exception\n   */\n  virtual int fillXrdErrInfo(XrdOucErrInfo& error, int errorCode) const;\n\n  virtual ~Exception() {}\nprivate:\n  std::string mErrorMsg;\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif // EOS_EXCEPTION_HH\n"
  },
  {
    "path": "common/highwayhash/arch_specific.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_ARCH_SPECIFIC_H_\n#define HIGHWAYHASH_ARCH_SPECIFIC_H_\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME.\n//\n// Background: older GCC/Clang require flags such as -mavx2 before AVX2 SIMD\n// intrinsics can be used. These intrinsics are only used within blocks that\n// first verify CPU capabilities. However, the flag also allows the compiler to\n// generate AVX2 code in other places. This can violate the One Definition Rule,\n// which requires multiple instances of a function with external linkage\n// (e.g. extern inline in a header) to be \"equivalent\". To prevent the resulting\n// crashes on non-AVX2 CPUs, any header (transitively) included from a\n// translation unit compiled with different flags is \"restricted\". This means\n// all function definitions must have internal linkage (e.g. static inline), or\n// reside in namespace HH_TARGET_NAME, which expands to a name unique to the\n// current compiler flags.\n//\n// Most C system headers are safe to include, but C++ headers should generally\n// be avoided because they often do not specify static linkage and cannot\n// reliably be wrapped in a namespace.\n\n#include \"highwayhash/compiler_specific.h\"\n\n#include <stdint.h>\n\n#if HH_MSC_VERSION\n#include <intrin.h>  // _byteswap_*\n#endif\n\nnamespace highwayhash\n{\n\n#if defined(__x86_64__) || defined(_M_X64)\n#define HH_ARCH_X64 1\n#else\n#define HH_ARCH_X64 0\n#endif\n\n#if defined(__aarch64__) || defined(__arm64__)\n#define HH_ARCH_AARCH64 1\n#else\n#define HH_ARCH_AARCH64 0\n#endif\n\n#ifdef __arm__\n#define HH_ARCH_ARM 1\n#else\n#define HH_ARCH_ARM 0\n#endif\n\n#if defined(__ARM_NEON__) || defined(__ARM_NEON)\n#define HH_ARCH_NEON 1\n#else\n#define HH_ARCH_NEON 0\n#endif\n\n#if defined(__powerpc64__) || defined(_M_PPC)\n#define HH_ARCH_PPC 1\n#else\n#define HH_ARCH_PPC 0\n#endif\n\n// Target := instruction set extension(s) such as SSE41. A translation unit can\n// only provide a single target-specific implementation because they require\n// different compiler flags.\n\n// Either the build system specifies the target by defining HH_TARGET_NAME\n// (which is necessary for Portable on X64, and SSE41 on MSVC), or we'll choose\n// the most efficient one that can be compiled given the current flags:\n#ifndef HH_TARGET_NAME\n\n// To avoid excessive code size and dispatch overhead, we only support a few\n// groups of extensions, e.g. FMA+BMI2+AVX+AVX2 =: \"AVX2\". These names must\n// match the HH_TARGET_* suffixes below.\n#ifdef __AVX2__\n#define HH_TARGET_NAME AVX2\n// MSVC does not set SSE4_1, but it does set AVX; checking for the latter means\n// we at least get SSE4 on machines supporting AVX but not AVX2.\n// https://stackoverflow.com/questions/18563978/detect-the-availability-of-sse-sse2-instruction-set-in-visual-studio\n#elif defined(__SSE4_1__) || (HH_MSC_VERSION != 0 && defined(__AVX__))\n#define HH_TARGET_NAME SSE41\n#elif defined(__VSX__)\n#define HH_TARGET_NAME VSX\n#elif HH_ARCH_NEON\n#define HH_TARGET_NAME NEON\n#else\n#define HH_TARGET_NAME Portable\n#endif\n\n#endif  // HH_TARGET_NAME\n\n#define HH_CONCAT(first, second) first##second\n// Required due to macro expansion rules.\n#define HH_EXPAND_CONCAT(first, second) HH_CONCAT(first, second)\n// Appends HH_TARGET_NAME to \"identifier_prefix\".\n#define HH_ADD_TARGET_SUFFIX(identifier_prefix) \\\n  HH_EXPAND_CONCAT(identifier_prefix, HH_TARGET_NAME)\n\n// HH_TARGET expands to an integer constant. Typical usage: HHStateT<HH_TARGET>.\n// This ensures your code will work correctly when compiler flags are changed,\n// and benefit from subsequently added targets/specializations.\n#define HH_TARGET HH_ADD_TARGET_SUFFIX(HH_TARGET_)\n\n// Deprecated former name of HH_TARGET; please use HH_TARGET instead.\n#define HH_TARGET_PREFERRED HH_TARGET\n\n// Associate targets with integer literals so the preprocessor can compare them\n// with HH_TARGET. Do not instantiate templates with these values - use\n// HH_TARGET instead. Must be unique powers of two, see TargetBits. Always\n// defined even if unavailable on this HH_ARCH to allow calling TargetName.\n// The suffixes must match the HH_TARGET_NAME identifiers.\n#define HH_TARGET_Portable 1\n#define HH_TARGET_SSE41 2\n#define HH_TARGET_AVX2 4\n#define HH_TARGET_VSX 8\n#define HH_TARGET_NEON 16\n\n// Bit array for one or more HH_TARGET_*. Used to indicate which target(s) are\n// supported or were called by InstructionSets::RunAll.\nusing TargetBits = unsigned;\n\nnamespace HH_TARGET_NAME\n{\n\n// Calls func(bit_value) for every nonzero bit in \"bits\".\ntemplate <class Func>\nvoid ForeachTarget(TargetBits bits, const Func& func)\n{\n  while (bits != 0) {\n    const TargetBits lowest = bits & (~bits + 1);\n    func(lowest);\n    bits &= ~lowest;\n  }\n}\n\n}  // namespace HH_TARGET_NAME\n\n// Returns a brief human-readable string literal identifying one of the above\n// bits, or nullptr if zero, multiple, or unknown bits are set.\nconst char* TargetName(const TargetBits target_bit);\n\n// Returns the nominal (without Turbo Boost) CPU clock rate [Hertz]. Useful for\n// (roughly) characterizing the CPU speed.\ndouble NominalClockRate();\n\n// Returns tsc_timer frequency, useful for converting ticks to seconds. This is\n// unaffected by CPU throttling (\"invariant\"). Thread-safe. Returns timebase\n// frequency on PPC and NominalClockRate on all other platforms.\ndouble InvariantTicksPerSecond();\n\n#if HH_ARCH_X64\n\n// Calls CPUID instruction with eax=level and ecx=count and returns the result\n// in abcd array where abcd = {eax, ebx, ecx, edx} (hence the name abcd).\nvoid Cpuid(const uint32_t level, const uint32_t count,\n           uint32_t* HH_RESTRICT abcd);\n\n// Returns the APIC ID of the CPU on which we're currently running.\nuint32_t ApicId();\n\n#endif  // HH_ARCH_X64\n\n}  // namespace highwayhash\n\n#endif  // HIGHWAYHASH_ARCH_SPECIFIC_H_\n"
  },
  {
    "path": "common/highwayhash/c_bindings.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_HIGHWAYHASH_C_BINDINGS_H_\n#define HIGHWAYHASH_HIGHWAYHASH_C_BINDINGS_H_\n\n// C-callable function prototypes, documented in the other header files.\n\n#include <stdint.h>\n\n#include \"hh_types.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n\n// Bring the symbols out of the namespace.\nusing highwayhash::HHKey;\nusing highwayhash::HHPacket;\nusing highwayhash::HHResult128;\nusing highwayhash::HHResult256;\nusing highwayhash::HHResult64;\n#endif\n\nuint64_t SipHashC(const uint64_t* key, const char* bytes, const uint64_t size);\nuint64_t SipHash13C(const uint64_t* key, const char* bytes,\n                    const uint64_t size);\n\n// Uses the best implementation of HighwayHash for the current CPU and\n// calculates 64-bit hash of given data.\nuint64_t HighwayHash64(const HHKey key, const char* bytes, const uint64_t size);\n\n// Defined by highwayhash_target.cc, which requires a _Target* suffix.\nuint64_t HighwayHash64_TargetPortable(const HHKey key, const char* bytes,\n                                      const uint64_t size);\nuint64_t HighwayHash64_TargetSSE41(const HHKey key, const char* bytes,\n                                   const uint64_t size);\nuint64_t HighwayHash64_TargetAVX2(const HHKey key, const char* bytes,\n                                  const uint64_t size);\nuint64_t HighwayHash64_TargetVSX(const HHKey key, const char* bytes,\n                                 const uint64_t size);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif  // HIGHWAYHASH_HIGHWAYHASH_C_BINDINGS_H_\n"
  },
  {
    "path": "common/highwayhash/compiler_specific.h",
    "content": "// Copyright 2015 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_COMPILER_SPECIFIC_H_\n#define HIGHWAYHASH_COMPILER_SPECIFIC_H_\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n// Compiler\n\n// #if is shorter and safer than #ifdef. *_VERSION are zero if not detected,\n// otherwise 100 * major + minor version. Note that other packages check for\n// #ifdef COMPILER_MSVC, so we cannot use that same name.\n\n#ifdef _MSC_VER\n#define HH_MSC_VERSION _MSC_VER\n#else\n#define HH_MSC_VERSION 0\n#endif\n\n#ifdef __GNUC__\n#define HH_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)\n#else\n#define HH_GCC_VERSION 0\n#endif\n\n#ifdef __clang__\n#define HH_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)\n#else\n#define HH_CLANG_VERSION 0\n#endif\n\n//-----------------------------------------------------------------------------\n\n#if HH_GCC_VERSION && HH_GCC_VERSION < 408\n#define HH_ALIGNAS(multiple) __attribute__((aligned(multiple)))\n#else\n#define HH_ALIGNAS(multiple) alignas(multiple)  // C++11\n#endif\n\n#if HH_MSC_VERSION\n#define HH_RESTRICT __restrict\n#elif HH_GCC_VERSION\n#define HH_RESTRICT __restrict__\n#else\n#define HH_RESTRICT\n#endif\n\n#if HH_MSC_VERSION\n#define HH_INLINE __forceinline\n#define HH_NOINLINE __declspec(noinline)\n#else\n#define HH_INLINE inline\n#define HH_NOINLINE __attribute__((noinline))\n#endif\n\n#if HH_MSC_VERSION\n// Unsupported, __assume is not the same.\n#define HH_LIKELY(expr) expr\n#define HH_UNLIKELY(expr) expr\n#else\n#define HH_LIKELY(expr) __builtin_expect(!!(expr), 1)\n#define HH_UNLIKELY(expr) __builtin_expect(!!(expr), 0)\n#endif\n\n#if HH_MSC_VERSION\n#include <intrin.h>\n#pragma intrinsic(_ReadWriteBarrier)\n#define HH_COMPILER_FENCE _ReadWriteBarrier()\n#elif HH_GCC_VERSION\n#define HH_COMPILER_FENCE asm volatile(\"\" : : : \"memory\")\n#else\n#define HH_COMPILER_FENCE\n#endif\n\n#endif  // HIGHWAYHASH_COMPILER_SPECIFIC_H_\n"
  },
  {
    "path": "common/highwayhash/data_parallel.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_DATA_PARALLEL_H_\n#define HIGHWAYHASH_DATA_PARALLEL_H_\n\n// Portable C++11 alternative to OpenMP for data-parallel computations:\n// provides low-overhead ThreadPool, plus PerThread with support for reduction.\n\n#include <stdio.h>\n#include <algorithm>  // find_if\n#include <atomic>\n#include <condition_variable>  //NOLINT\n#include <cstdint>\n#include <cstdlib>\n#include <functional>\n#include <memory>\n#include <mutex>  //NOLINT\n#include <thread>  //NOLINT\n#include <utility>\n#include <vector>\n\n#define DATA_PARALLEL_CHECK(condition)                           \\\n  while (!(condition)) {                                         \\\n    printf(\"data_parallel check failed at line %d\\n\", __LINE__); \\\n    abort();                                                     \\\n  }\n\nnamespace highwayhash\n{\n\n// Highly scalable thread pool, especially suitable for data-parallel\n// computations in the fork-join model, where clients need to know when all\n// tasks have completed.\n//\n// Thread pools usually store small numbers of heterogeneous tasks in a queue.\n// When tasks are identical or differ only by an integer input parameter, it is\n// much faster to store just one function of an integer parameter and call it\n// for each value.\n//\n// This thread pool can efficiently load-balance millions of tasks using an\n// atomic counter, thus avoiding per-task syscalls. With 48 hyperthreads and\n// 1M tasks that add to an atomic counter, overall runtime is 10-20x higher\n// when using std::async, and up to 200x for a queue-based ThreadPool.\n//\n// Usage:\n// ThreadPool pool;\n// pool.Run(0, 1000000, [](const int i) { Func1(i); });\n// // When Run returns, all of its tasks have finished.\n//\n// pool.RunTasks({Func2, Func3, Func4});\n// // The destructor waits until all worker threads have exited cleanly.\nclass ThreadPool\n{\npublic:\n  // Starts the given number of worker threads and blocks until they are ready.\n  // \"num_threads\" defaults to one per hyperthread.\n  explicit ThreadPool(\n    const int num_threads = std::thread::hardware_concurrency())\n    : num_threads_(num_threads)\n  {\n    DATA_PARALLEL_CHECK(num_threads_ > 0);\n    threads_.reserve(num_threads_);\n\n    for (int i = 0; i < num_threads_; ++i) {\n      threads_.emplace_back(ThreadFunc, this);\n    }\n\n    padding_[0] = 0;  // avoid unused member warning.\n    WorkersReadyBarrier();\n  }\n\n  ThreadPool(const ThreadPool&) = delete;\n  ThreadPool& operator&(const ThreadPool&) = delete;\n\n  // Waits for all threads to exit.\n  ~ThreadPool()\n  {\n    StartWorkers(kWorkerExit);\n\n    for (std::thread& thread : threads_) {\n      thread.join();\n    }\n  }\n\n  // Runs func(i) on worker thread(s) for every i in [begin, end).\n  // Not thread-safe - no two calls to Run and RunTasks may overlap.\n  // Subsequent calls will reuse the same threads.\n  //\n  // Precondition: 0 <= begin <= end.\n  template <class Func>\n  void Run(const int begin, const int end, const Func& func)\n  {\n    DATA_PARALLEL_CHECK(0 <= begin && begin <= end);\n\n    if (begin == end) {\n      return;\n    }\n\n    const WorkerCommand worker_command = (WorkerCommand(end) << 32) + begin;\n    // Ensure the inputs do not result in a reserved command.\n    DATA_PARALLEL_CHECK(worker_command != kWorkerWait);\n    DATA_PARALLEL_CHECK(worker_command != kWorkerExit);\n    // If Func is large (many captures), this will allocate memory, but it is\n    // still slower to use a std::ref wrapper.\n    task_ = func;\n    num_reserved_.store(0);\n    StartWorkers(worker_command);\n    WorkersReadyBarrier();\n  }\n\n  // Runs each task (closure, typically a lambda function) on worker thread(s).\n  // Not thread-safe - no two calls to Run and RunTasks may overlap.\n  // Subsequent calls will reuse the same threads.\n  //\n  // This is a more conventional interface for heterogeneous tasks that may be\n  // independent/unrelated.\n  void RunTasks(const std::vector<std::function<void(void)>>& tasks)\n  {\n    Run(0, static_cast<int>(tasks.size()),\n    [&tasks](const int i) {\n      tasks[i]();\n    });\n  }\n\n  // Statically (and deterministically) splits [begin, end) into ranges and\n  // calls \"func\" for each of them. Useful when \"func\" involves some overhead\n  // (e.g. for PerThread::Get or random seeding) that should be amortized over\n  // a range of values. \"func\" is void(int chunk, uint32_t begin, uint32_t end).\n  template <class Func>\n  void RunRanges(const uint32_t begin, const uint32_t end, const Func& func)\n  {\n    const uint32_t length = end - begin;\n    // Use constant rather than num_threads_ for machine-independent splitting.\n    const uint32_t chunk = std::max(1U, (length + 127) / 128);\n    std::vector<std::pair<uint32_t, uint32_t>> ranges;  // begin/end\n    ranges.reserve(length / chunk + 1);\n\n    for (uint32_t i = 0; i < length; i += chunk) {\n      ranges.emplace_back(begin + i, begin + std::min(i + chunk, length));\n    }\n\n    Run(0, static_cast<int>(ranges.size()), [&ranges, func](const int i) {\n      func(i, ranges[i].first, ranges[i].second);\n    });\n  }\n\nprivate:\n  // After construction and between calls to Run, workers are \"ready\", i.e.\n  // waiting on worker_start_cv_. They are \"started\" by sending a \"command\"\n  // and notifying all worker_start_cv_ waiters. (That is why all workers\n  // must be ready/waiting - otherwise, the notification will not reach all of\n  // them and the main thread waits in vain for them to report readiness.)\n  using WorkerCommand = uint64_t;\n\n  // Special values; all others encode the begin/end parameters.\n  static constexpr WorkerCommand kWorkerWait = 0;\n  static constexpr WorkerCommand kWorkerExit = ~0ULL;\n\n  void WorkersReadyBarrier()\n  {\n    std::unique_lock<std::mutex> lock(mutex_);\n    workers_ready_cv_.wait(lock,\n    [this]() {\n      return workers_ready_ == num_threads_;\n    });\n    workers_ready_ = 0;\n  }\n\n  // Precondition: all workers are ready.\n  void StartWorkers(const WorkerCommand worker_command)\n  {\n    std::unique_lock<std::mutex> lock(mutex_);\n    worker_start_command_ = worker_command;\n    // Workers will need this lock, so release it before they wake up.\n    lock.unlock();\n    worker_start_cv_.notify_all();\n  }\n\n  // Attempts to reserve and perform some work from the global range of tasks,\n  // which is encoded within \"command\". Returns after all tasks are reserved.\n  static void RunRange(ThreadPool* self, const WorkerCommand command)\n  {\n    const int begin = command & 0xFFFFFFFF;\n    const int end = command >> 32;\n    const int num_tasks = end - begin;\n\n    // OpenMP introduced several \"schedule\" strategies:\n    // \"single\" (static assignment of exactly one chunk per thread): slower.\n    // \"dynamic\" (allocates k tasks at a time): competitive for well-chosen k.\n    // \"guided\" (allocates k tasks, decreases k): computing k = remaining/n\n    //   is faster than halving k each iteration. We prefer this strategy\n    //   because it avoids user-specified parameters.\n\n    for (;;) {\n      const int num_reserved = self->num_reserved_.load();\n      const int num_remaining = num_tasks - num_reserved;\n      const int my_size = std::max(num_remaining / (self->num_threads_ * 2), 1);\n      const int my_begin = begin + self->num_reserved_.fetch_add(my_size);\n      const int my_end = std::min(my_begin + my_size, begin + num_tasks);\n\n      // Another thread already reserved the last task.\n      if (my_begin >= my_end) {\n        break;\n      }\n\n      for (int i = my_begin; i < my_end; ++i) {\n        self->task_(i);\n      }\n    }\n  }\n\n  static void ThreadFunc(ThreadPool* self)\n  {\n    // Until kWorkerExit command received:\n    for (;;) {\n      std::unique_lock<std::mutex> lock(self->mutex_);\n\n      // Notify main thread that this thread is ready.\n      if (++self->workers_ready_ == self->num_threads_) {\n        self->workers_ready_cv_.notify_one();\n      }\n\nRESUME_WAIT:\n      // Wait for a command.\n      self->worker_start_cv_.wait(lock);\n      const WorkerCommand command = self->worker_start_command_;\n\n      switch (command) {\n      case kWorkerWait:    // spurious wakeup:\n        goto RESUME_WAIT;  // lock still held, avoid incrementing ready.\n\n      case kWorkerExit:\n        return;  // exits thread\n      }\n\n      lock.unlock();\n      RunRange(self, command);\n    }\n  }\n\n  const int num_threads_;\n\n  // Unmodified after ctor, but cannot be const because we call thread::join().\n  std::vector<std::thread> threads_;\n\n  std::mutex mutex_;  // guards both cv and their variables.\n  std::condition_variable workers_ready_cv_;\n  int workers_ready_ = 0;\n  std::condition_variable worker_start_cv_;\n  WorkerCommand worker_start_command_;\n\n  // Written by main thread, read by workers (after mutex lock/unlock).\n  std::function<void(int)> task_;\n\n  // Updated by workers; alignment/padding avoids false sharing.\n  alignas(64) std::atomic<int> num_reserved_{0};\n  int padding_[15];\n};\n\n// Thread-local storage with support for reduction (combining into one result).\n// The \"T\" type must be unique to the call site because the list of threads'\n// copies is a static member. (With knowledge of the underlying threads, we\n// could eliminate this list and T allocations, but that is difficult to\n// arrange and we prefer this to be usable independently of ThreadPool.)\n//\n// Usage:\n// for (int i = 0; i < N; ++i) {\n//   // in each thread:\n//   T& my_copy = PerThread<T>::Get();\n//   my_copy.Modify();\n//\n//   // single-threaded:\n//   T& combined = PerThread<T>::Reduce();\n//   Use(combined);\n//   PerThread<T>::Destroy();\n// }\n//\n// T is duck-typed and implements the following interface:\n//\n// // Returns true if T is default-initialized or Destroy was called without\n// // any subsequent re-initialization.\n// bool IsNull() const;\n//\n// // Releases any resources. Postcondition: IsNull() == true.\n// void Destroy();\n//\n// // Merges in data from \"victim\". Precondition: !IsNull() && !victim.IsNull().\n// void Assimilate(const T& victim);\ntemplate <class T>\nclass PerThread\n{\npublic:\n  // Returns reference to this thread's T instance (dynamically allocated,\n  // so its address is unique). Callers are responsible for any initialization\n  // beyond the default ctor.\n  static T& Get()\n  {\n    static thread_local T* t;\n\n    if (t == nullptr) {\n      t = new T;\n      static std::mutex mutex;\n      std::lock_guard<std::mutex> lock(mutex);\n      Threads().push_back(t);\n    }\n\n    return *t;\n  }\n\n  // Returns vector of all per-thread T. Used inside Reduce() or by clients\n  // that require direct access to T instead of Assimilating them.\n  // Function wrapper avoids separate static member variable definition.\n  static std::vector<T*>& Threads()\n  {\n    static std::vector<T*> threads;\n    return threads;\n  }\n\n  // Returns the first non-null T after assimilating all other threads' T\n  // into it. Precondition: at least one non-null T exists (caller must have\n  // called Get() and initialized the result).\n  static T& Reduce()\n  {\n    std::vector<T*>& threads = Threads();\n    // Find first non-null T\n    const auto it = std::find_if(threads.begin(), threads.end(),\n    [](const T * t) {\n      return !t->IsNull();\n    });\n\n    if (it == threads.end()) {\n      abort();\n    }\n\n    T* const first = *it;\n\n    for (const T* t : threads) {\n      if (t != first && !t->IsNull()) {\n        first->Assimilate(*t);\n      }\n    }\n\n    return *first;\n  }\n\n  // Calls each thread's T::Destroy to release resources and/or prepare for\n  // reuse by the same threads/ThreadPool. Note that all T remain allocated\n  // (we need thread-independent pointers for iterating over each thread's T,\n  // and deleting them would leave dangling pointers in each thread, which is\n  // unacceptable because the same thread may call Get() again later.)\n  static void Destroy()\n  {\n    for (T* t : Threads()) {\n      t->Destroy();\n    }\n  }\n};\n\n}  // namespace highwayhash\n\n#endif  // HIGHWAYHASH_DATA_PARALLEL_H_\n"
  },
  {
    "path": "common/highwayhash/endianess.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_ENDIANESS_H_\n#define HIGHWAYHASH_ENDIANESS_H_\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include <stdint.h>\n\n#if defined(BYTE_ORDER) && defined(LITTLE_ENDIAN) && defined(BIG_ENDIAN)\n\n/* Someone has already included <endian.h> or equivalent. */\n\n#elif defined(__LITTLE_ENDIAN__)\n\n#  define HH_IS_LITTLE_ENDIAN  1\n#  define HH_IS_BIG_ENDIAN     0\n#  ifdef __BIG_ENDIAN__\n#    error \"Platform is both little and big endian?\"\n#  endif\n\n#elif defined(__BIG_ENDIAN__)\n\n#    define HH_IS_LITTLE_ENDIAN  0\n#    define HH_IS_BIG_ENDIAN     1\n\n#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \\\n      defined(__ORDER_LITTLE_ENDIAN__)\n\n#  define HH_IS_LITTLE_ENDIAN  (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)\n#  define HH_IS_BIG_ENDIAN     (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)\n\n#elif defined(__linux__) || defined(__CYGWIN__) || defined( __GNUC__ ) || \\\n      defined( __GNU_LIBRARY__ )\n\n#  include <endian.h>\n\n#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || \\\n      defined(__DragonFly__)\n\n#  include <sys/endian.h>\n\n#elif defined(_WIN32)\n\n#define HH_IS_LITTLE_ENDIAN 1\n#define HH_IS_BIG_ENDIAN 0\n\n#else\n\n#  error \"Unsupported platform.  Cannot determine byte order.\"\n\n#endif\n\n\n#ifndef HH_IS_LITTLE_ENDIAN\n#  define HH_IS_LITTLE_ENDIAN  (BYTE_ORDER == LITTLE_ENDIAN)\n#  define HH_IS_BIG_ENDIAN     (BYTE_ORDER == BIG_ENDIAN)\n#endif\n\n\nnamespace highwayhash\n{\n\n#if HH_IS_LITTLE_ENDIAN\n\nstatic inline uint32_t le32_from_host(uint32_t x)\n{\n  return x;\n}\nstatic inline uint32_t host_from_le32(uint32_t x)\n{\n  return x;\n}\nstatic inline uint64_t le64_from_host(uint64_t x)\n{\n  return x;\n}\nstatic inline uint64_t host_from_le64(uint64_t x)\n{\n  return x;\n}\n\n#elif !HH_IS_BIG_ENDIAN\n\n#  error \"Unsupported byte order.\"\n\n#elif defined(_WIN16) || defined(_WIN32) || defined(_WIN64)\n\n#include <intrin.h>\nstatic inline uint32_t host_from_le32(uint32_t x)\n{\n  return _byteswap_ulong(x);\n}\nstatic inline uint32_t le32_from_host(uint32_t x)\n{\n  return _byteswap_ulong(x);\n}\nstatic inline uint64_t host_from_le64(uint64_t x)\n{\n  return _byteswap_uint64(x);\n}\nstatic inline uint64_t le64_from_host(uint64_t x)\n{\n  return _byteswap_uint64(x);\n}\n\n#else\n\nstatic inline uint32_t host_from_le32(uint32_t x)\n{\n  return __builtin_bswap32(x);\n}\nstatic inline uint32_t le32_from_host(uint32_t x)\n{\n  return __builtin_bswap32(x);\n}\nstatic inline uint64_t host_from_le64(uint64_t x)\n{\n  return __builtin_bswap64(x);\n}\nstatic inline uint64_t le64_from_host(uint64_t x)\n{\n  return __builtin_bswap64(x);\n}\n\n#endif\n\n}  // namespace highwayhash\n\n#endif  // HIGHWAYHASH_ENDIANESS_H_\n"
  },
  {
    "path": "common/highwayhash/hh_avx2.h",
    "content": "// Copyright 2015-2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_HH_AVX2_H_\n#define HIGHWAYHASH_HH_AVX2_H_\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n#include \"highwayhash/hh_buffer.h\"\n#include \"highwayhash/hh_types.h\"\n#include \"highwayhash/load3.h\"\n#include \"highwayhash/vector128.h\"\n#include \"highwayhash/vector256.h\"\n\n// For auto-dependency generation, we need to include all headers but not their\n// contents (otherwise compilation fails because -mavx2 is not specified).\n#ifndef HH_DISABLE_TARGET_SPECIFIC\n\nnamespace highwayhash\n{\n// See vector128.h for why this namespace is necessary; matching it here makes\n// it easier use the vector128 symbols, but requires textual inclusion.\nnamespace HH_TARGET_NAME\n{\n\nclass HHStateAVX2\n{\npublic:\n  explicit HH_INLINE HHStateAVX2(const HHKey key_lanes)\n  {\n    Reset(key_lanes);\n  }\n\n  HH_INLINE void Reset(const HHKey key_lanes)\n  {\n    // \"Nothing up my sleeve\" numbers, concatenated hex digits of Pi from\n    // http://www.numberworld.org/digits/Pi/, retrieved Feb 22, 2016.\n    //\n    // We use this python code to generate the fourth number to have\n    // more even mixture of bits:\n    /*\n    def x(a,b,c):\n    retval = 0\n    for i in range(64):\n    count = ((a >> i) & 1) + ((b >> i) & 1) + ((c >> i) & 1)\n    if (count <= 1):\n      retval |= 1 << i\n    return retval\n    */\n    const V4x64U init0(0x243f6a8885a308d3ull, 0x13198a2e03707344ull,\n                       0xa4093822299f31d0ull, 0xdbe6d5d5fe4cce2full);\n    const V4x64U init1(0x452821e638d01377ull, 0xbe5466cf34e90c6cull,\n                       0xc0acf169b5f18a8cull, 0x3bd39e10cb0ef593ull);\n    const V4x64U key = LoadUnaligned<V4x64U>(key_lanes);\n    v0 = key ^ init0;\n    v1 = Rotate64By32(key) ^ init1;\n    mul0 = init0;\n    mul1 = init1;\n  }\n\n  HH_INLINE void Update(const HHPacket& packet_bytes)\n  {\n    const uint64_t* HH_RESTRICT packet =\n      reinterpret_cast<const uint64_t* HH_RESTRICT>(packet_bytes);\n    Update(LoadUnaligned<V4x64U>(packet));\n  }\n\n  HH_INLINE void UpdateRemainder(const char* bytes, const size_t size_mod32)\n  {\n    // 'Length padding' differentiates zero-valued inputs that have the same\n    // size/32. mod32 is sufficient because each Update behaves as if a\n    // counter were injected, because the state is large and mixed thoroughly.\n    const V8x32U size256(\n      _mm256_broadcastd_epi32(_mm_cvtsi64_si128(size_mod32)));\n    // Equivalent to storing size_mod32 in packet.\n    v0 += V4x64U(size256);\n    // Boosts the avalanche effect of mod32.\n    v1 = Rotate32By(v1, size256);\n    const char* remainder = bytes + (size_mod32 & ~3);\n    const size_t size_mod4 = size_mod32 & 3;\n    const V4x32U size(_mm256_castsi256_si128(size256));\n\n    // (Branching is faster than a single _mm256_maskload_epi32.)\n    if (HH_UNLIKELY(size_mod32 & 16)) {  // 16..31 bytes left\n      const V4x32U packetL =\n        LoadUnaligned<V4x32U>(reinterpret_cast<const uint32_t*>(bytes));\n      const V4x32U int_mask = IntMask<16>()(size);\n      const V4x32U int_lanes = MaskedLoadInt(bytes + 16, int_mask);\n      const uint32_t last4 =\n        Load3()(Load3::AllowReadBeforeAndReturn(), remainder, size_mod4);\n      // The upper four bytes of packetH are zero, so insert there.\n      const V4x32U packetH(_mm_insert_epi32(int_lanes, last4, 3));\n      Update(packetH, packetL);\n    } else {  // size_mod32 < 16\n      const V4x32U int_mask = IntMask<0>()(size);\n      const V4x32U packetL = MaskedLoadInt(bytes, int_mask);\n      const uint64_t last3 =\n        Load3()(Load3::AllowUnordered(), remainder, size_mod4);\n      // Rather than insert into packetL[3], it is faster to initialize\n      // the otherwise empty packetH.\n      const V4x32U packetH(_mm_cvtsi64_si128(last3));\n      Update(packetH, packetL);\n    }\n  }\n\n  HH_INLINE void Finalize(HHResult64* HH_RESTRICT result)\n  {\n    // Mix together all lanes. It is slightly better to permute v0 than v1;\n    // it will be added to v1.\n    Update(Permute(v0));\n    Update(Permute(v0));\n    Update(Permute(v0));\n    Update(Permute(v0));\n    const V2x64U sum0(_mm256_castsi256_si128(v0 + mul0));\n    const V2x64U sum1(_mm256_castsi256_si128(v1 + mul1));\n    const V2x64U hash = sum0 + sum1;\n    // Each lane is sufficiently mixed, so just truncate to 64 bits.\n    _mm_storel_epi64(reinterpret_cast<__m128i*>(result), hash);\n  }\n\n  HH_INLINE void Finalize(HHResult128* HH_RESTRICT result)\n  {\n    for (int n = 0; n < 6; n++) {\n      Update(Permute(v0));\n    }\n\n    const V2x64U sum0(_mm256_castsi256_si128(v0 + mul0));\n    const V2x64U sum1(_mm256_extracti128_si256(v1 + mul1, 1));\n    const V2x64U hash = sum0 + sum1;\n    _mm_storeu_si128(reinterpret_cast<__m128i*>(result), hash);\n  }\n\n  HH_INLINE void Finalize(HHResult256* HH_RESTRICT result)\n  {\n    for (int n = 0; n < 10; n++) {\n      Update(Permute(v0));\n    }\n\n    const V4x64U sum0 = v0 + mul0;\n    const V4x64U sum1 = v1 + mul1;\n    const V4x64U hash = ModularReduction(sum1, sum0);\n    StoreUnaligned(hash, &(*result)[0]);\n  }\n\n  // \"buffer\" must be 32-byte aligned.\n  static HH_INLINE void ZeroInitialize(char* HH_RESTRICT buffer)\n  {\n    const __m256i zero = _mm256_setzero_si256();\n    _mm256_store_si256(reinterpret_cast<__m256i*>(buffer), zero);\n  }\n\n  // \"buffer\" must be 32-byte aligned.\n  static HH_INLINE void CopyPartial(const char* HH_RESTRICT from,\n                                    const size_t size_mod32,\n                                    char* HH_RESTRICT buffer)\n  {\n    const V4x32U size(size_mod32);\n    const uint32_t* const HH_RESTRICT from_u32 =\n      reinterpret_cast<const uint32_t* HH_RESTRICT>(from);\n    uint32_t* const HH_RESTRICT buffer_u32 =\n      reinterpret_cast<uint32_t* HH_RESTRICT>(buffer);\n\n    if (HH_UNLIKELY(size_mod32 & 16)) {  // Copying 16..31 bytes\n      const V4x32U inL = LoadUnaligned<V4x32U>(from_u32);\n      Store(inL, buffer_u32);\n      const V4x32U inH = Load0To16<16, Load3::AllowReadBefore>(\n                           from + 16, size_mod32 - 16, size);\n      Store(inH, buffer_u32 + V4x32U::N);\n    } else {  // Copying 0..15 bytes\n      const V4x32U inL = Load0To16<>(from, size_mod32, size);\n      Store(inL, buffer_u32);\n      // No need to change upper 16 bytes of buffer.\n    }\n  }\n\n  // \"buffer\" must be 32-byte aligned.\n  static HH_INLINE void AppendPartial(const char* HH_RESTRICT from,\n                                      const size_t size_mod32,\n                                      char* HH_RESTRICT buffer,\n                                      const size_t buffer_valid)\n  {\n    const V4x32U size(size_mod32);\n    uint32_t* const HH_RESTRICT buffer_u32 =\n      reinterpret_cast<uint32_t* HH_RESTRICT>(buffer);\n\n    // buffer_valid + size <= 32 => appending 0..16 bytes inside upper 16 bytes.\n    if (HH_UNLIKELY(buffer_valid & 16)) {\n      const V4x32U suffix = Load0To16<>(from, size_mod32, size);\n      const V4x32U bufferH = Load<V4x32U>(buffer_u32 + V4x32U::N);\n      const V4x32U outH = Concatenate(bufferH, buffer_valid - 16, suffix);\n      Store(outH, buffer_u32 + V4x32U::N);\n    } else {  // Appending 0..32 bytes starting at offset 0..15.\n      const V4x32U bufferL = Load<V4x32U>(buffer_u32);\n      const V4x32U suffixL = Load0To16<>(from, size_mod32, size);\n      const V4x32U outL = Concatenate(bufferL, buffer_valid, suffixL);\n      Store(outL, buffer_u32);\n      const size_t offsetH = sizeof(V4x32U) - buffer_valid;\n\n      // Do we have enough input to start filling the upper 16 buffer bytes?\n      if (size_mod32 > offsetH) {\n        const size_t sizeH = size_mod32 - offsetH;\n        const V4x32U outH = Load0To16<>(from + offsetH, sizeH, V4x32U(sizeH));\n        Store(outH, buffer_u32 + V4x32U::N);\n      }\n    }\n  }\n\n  // \"buffer\" must be 32-byte aligned.\n  HH_INLINE void AppendAndUpdate(const char* HH_RESTRICT from,\n                                 const size_t size_mod32,\n                                 const char* HH_RESTRICT buffer,\n                                 const size_t buffer_valid)\n  {\n    const V4x32U size(size_mod32);\n    const uint32_t* const HH_RESTRICT buffer_u32 =\n      reinterpret_cast<const uint32_t* HH_RESTRICT>(buffer);\n\n    // buffer_valid + size <= 32 => appending 0..16 bytes inside upper 16 bytes.\n    if (HH_UNLIKELY(buffer_valid & 16)) {\n      const V4x32U suffix = Load0To16<>(from, size_mod32, size);\n      const V4x32U packetL = Load<V4x32U>(buffer_u32);\n      const V4x32U bufferH = Load<V4x32U>(buffer_u32 + V4x32U::N);\n      const V4x32U packetH = Concatenate(bufferH, buffer_valid - 16, suffix);\n      Update(packetH, packetL);\n    } else {  // Appending 0..32 bytes starting at offset 0..15.\n      const V4x32U bufferL = Load<V4x32U>(buffer_u32);\n      const V4x32U suffixL = Load0To16<>(from, size_mod32, size);\n      const V4x32U packetL = Concatenate(bufferL, buffer_valid, suffixL);\n      const size_t offsetH = sizeof(V4x32U) - buffer_valid;\n      V4x32U packetH = packetL - packetL;\n\n      // Do we have enough input to start filling the upper 16 packet bytes?\n      if (size_mod32 > offsetH) {\n        const size_t sizeH = size_mod32 - offsetH;\n        packetH = Load0To16<>(from + offsetH, sizeH, V4x32U(sizeH));\n      }\n\n      Update(packetH, packetL);\n    }\n  }\n\nprivate:\n  static HH_INLINE V4x32U MaskedLoadInt(const char* from,\n                                        const V4x32U& int_mask)\n  {\n    // No faults will be raised when reading n=0..3 ints from \"from\" provided\n    // int_mask[n] = 0.\n    const int* HH_RESTRICT int_from = reinterpret_cast<const int*>(from);\n    return V4x32U(_mm_maskload_epi32(int_from, int_mask));\n  }\n\n  // Loads <= 16 bytes without accessing any byte outside [from, from + size).\n  // from[i] is loaded into lane i; from[i >= size] is undefined.\n  template <uint32_t kSizeOffset = 0, class Load3Policy = Load3::AllowNone>\n  static HH_INLINE V4x32U Load0To16(const char* from, const size_t size_mod32,\n                                    const V4x32U& size)\n  {\n    const char* remainder = from + (size_mod32 & ~3);\n    const uint64_t last3 = Load3()(Load3Policy(), remainder, size_mod32 & 3);\n    const V4x32U int_mask = IntMask<kSizeOffset>()(size);\n    const V4x32U int_lanes = MaskedLoadInt(from, int_mask);\n    return Insert4AboveMask(last3, int_mask, int_lanes);\n  }\n\n  static HH_INLINE V4x64U Rotate64By32(const V4x64U& v)\n  {\n    return V4x64U(_mm256_shuffle_epi32(v, _MM_SHUFFLE(2, 3, 0, 1)));\n  }\n\n  // Rotates 32-bit lanes by \"count\" bits.\n  static HH_INLINE V4x64U Rotate32By(const V4x64U& v, const V8x32U& count)\n  {\n    // Use variable shifts because sll_epi32 has 4 cycle latency (presumably\n    // to broadcast the shift count).\n    const V4x64U shifted_left(_mm256_sllv_epi32(v, count));\n    const V4x64U shifted_right(_mm256_srlv_epi32(v, V8x32U(32) - count));\n    return shifted_left | shifted_right;\n  }\n\n  static HH_INLINE V4x64U Permute(const V4x64U& v)\n  {\n    // For complete mixing, we need to swap the upper and lower 128-bit halves;\n    // we also swap all 32-bit halves. This is faster than extracti128 plus\n    // inserti128 followed by Rotate64By32.\n    const V4x64U indices(0x0000000200000003ull, 0x0000000000000001ull,\n                         0x0000000600000007ull, 0x0000000400000005ull);\n    return V4x64U(_mm256_permutevar8x32_epi32(v, indices));\n  }\n\n  static HH_INLINE V4x64U MulLow32(const V4x64U& a, const V4x64U& b)\n  {\n    return V4x64U(_mm256_mul_epu32(a, b));\n  }\n\n  static HH_INLINE V4x64U ZipperMerge(const V4x64U& v)\n  {\n    // Multiplication mixes/scrambles bytes 0-7 of the 64-bit result to\n    // varying degrees. In descending order of goodness, bytes\n    // 3 4 2 5 1 6 0 7 have quality 228 224 164 160 100 96 36 32.\n    // As expected, the upper and lower bytes are much worse.\n    // For each 64-bit lane, our objectives are:\n    // 1) maximizing and equalizing total goodness across the four lanes.\n    // 2) mixing with bytes from the neighboring lane (AVX-2 makes it difficult\n    //    to cross the 128-bit wall, but PermuteAndUpdate takes care of that);\n    // 3) placing the worst bytes in the upper 32 bits because those will not\n    //    be used in the next 32x32 multiplication.\n    const uint64_t hi = 0x070806090D0A040Bull;\n    const uint64_t lo = 0x000F010E05020C03ull;\n    return V4x64U(_mm256_shuffle_epi8(v, V4x64U(hi, lo, hi, lo)));\n  }\n\n  // Updates four hash lanes in parallel by injecting four 64-bit packets.\n  HH_INLINE void Update(const V4x64U& packet)\n  {\n    v1 += packet;\n    v1 += mul0;\n    mul0 ^= MulLow32(v1, v0 >> 32);\n    HH_COMPILER_FENCE;\n    v0 += mul1;\n    mul1 ^= MulLow32(v0, v1 >> 32);\n    HH_COMPILER_FENCE;\n    v0 += ZipperMerge(v1);\n    v1 += ZipperMerge(v0);\n  }\n\n  HH_INLINE void Update(const V4x32U& packetH, const V4x32U& packetL)\n  {\n    const __m256i packetL256 = _mm256_castsi128_si256(packetL);\n    Update(V4x64U(_mm256_inserti128_si256(packetL256, packetH, 1)));\n  }\n\n  // XORs a << 1 and a << 2 into *out after clearing the upper two bits of a.\n  // Also does the same for the upper 128 bit lane \"b\". Bit shifts are only\n  // possible on independent 64-bit lanes. We therefore insert the upper bits\n  // of a[0] that were lost into a[1]. Thanks to D. Lemire for helpful comments!\n  static HH_INLINE void XorByShift128Left12(const V4x64U& ba,\n      V4x64U* HH_RESTRICT out)\n  {\n    const V4x64U zero = ba ^ ba;\n    const V4x64U top_bits2 = ba >> (64 - 2);\n    const V4x64U ones = ba == ba;              // FF .. FF\n    const V4x64U shifted1_unmasked = ba + ba;  // (avoids needing port0)\n    HH_COMPILER_FENCE;\n    // Only the lower halves of top_bits1's 128 bit lanes will be used, so we\n    // can compute it before clearing the upper two bits of ba.\n    const V4x64U top_bits1 = ba >> (64 - 1);\n    const V4x64U upper_8bytes(_mm256_slli_si256(ones, 8));  // F 0 F 0\n    const V4x64U shifted2 = shifted1_unmasked + shifted1_unmasked;\n    HH_COMPILER_FENCE;\n    const V4x64U upper_bit_of_128 = upper_8bytes << 63;  // 80..00 80..00\n    const V4x64U new_low_bits2(_mm256_unpacklo_epi64(zero, top_bits2));\n    *out ^= shifted2;\n    HH_COMPILER_FENCE;\n    // The result must be as if the upper two bits of the input had been clear,\n    // otherwise we're no longer computing a reduction.\n    const V4x64U shifted1 = AndNot(upper_bit_of_128, shifted1_unmasked);\n    *out ^= new_low_bits2;\n    HH_COMPILER_FENCE;\n    const V4x64U new_low_bits1(_mm256_unpacklo_epi64(zero, top_bits1));\n    *out ^= shifted1;\n    *out ^= new_low_bits1;\n  }\n\n  // Modular reduction by the irreducible polynomial (x^128 + x^2 + x).\n  // Input: two 256-bit numbers a3210 and b3210, interleaved in 2 vectors.\n  // The upper and lower 128-bit halves are processed independently.\n  static HH_INLINE V4x64U ModularReduction(const V4x64U& b32a32,\n      const V4x64U& b10a10)\n  {\n    // See Lemire, https://arxiv.org/pdf/1503.03465v8.pdf.\n    V4x64U out = b10a10;\n    XorByShift128Left12(b32a32, &out);\n    return out;\n  }\n\n  V4x64U v0;\n  V4x64U v1;\n  V4x64U mul0;\n  V4x64U mul1;\n};\n\n}  // namespace HH_TARGET_NAME\n}  // namespace highwayhash\n\n#endif  // HH_DISABLE_TARGET_SPECIFIC\n#endif  // HIGHWAYHASH_HH_AVX2_H_\n"
  },
  {
    "path": "common/highwayhash/hh_buffer.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_HH_BUFFER_H_\n#define HIGHWAYHASH_HH_BUFFER_H_\n\n// Helper functions used by hh_avx2 and hh_sse41.\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#if HH_TARGET == HH_TARGET_NEON\n#include \"highwayhash/vector_neon.h\"\n#else\n#include \"highwayhash/vector128.h\"\n#endif\n\n// For auto-dependency generation, we need to include all headers but not their\n// contents (otherwise compilation fails because -msse4.1 is not specified).\n#ifndef HH_DISABLE_TARGET_SPECIFIC\n\nnamespace highwayhash\n{\n// To prevent ODR violations when including this from multiple translation\n// units (TU) that are compiled with different flags, the contents must reside\n// in a namespace whose name is unique to the TU. NOTE: this behavior is\n// incompatible with precompiled modules and requires textual inclusion instead.\nnamespace HH_TARGET_NAME\n{\n\ntemplate <uint32_t kSizeOffset>\nstruct IntMask {};  // primary template\n\ntemplate <>\nstruct IntMask<0> {\n  // Returns 32-bit lanes : ~0U if that lane can be loaded given \"size\" bytes.\n  // Typical case: size = 0..16, nothing deducted.\n  HH_INLINE V4x32U operator()(const V4x32U& size) const\n  {\n    // Lane n is valid if size >= (n + 1) * 4; subtract one because we only have\n    // greater-than comparisons and don't want a negated mask.\n#if HH_TARGET == HH_TARGET_NEON\n    return V4x32U(vcgtq_u32(size, V4x32U(15, 11, 7, 3)));\n#else\n    return V4x32U(_mm_cmpgt_epi32(size, V4x32U(15, 11, 7, 3)));\n#endif\n  }\n};\n\ntemplate <>\nstruct IntMask<16> {\n  // \"size\" is 16..31; this is for loading the upper half of a packet, so\n  // effectively deduct 16 from size by changing the comparands.\n  HH_INLINE V4x32U operator()(const V4x32U& size) const\n  {\n#if HH_TARGET == HH_TARGET_NEON\n    return V4x32U(vcgtq_u32(size, V4x32U(31, 27, 23, 19)));\n#else\n    return V4x32U(_mm_cmpgt_epi32(size, V4x32U(31, 27, 23, 19)));\n#endif\n  }\n};\n\n// Inserts \"bytes4\" into \"prev\" at the lowest i such that mask[i] = 0.\n// Assumes prev[j] == 0 if mask[j] = 0.\nHH_INLINE V4x32U Insert4AboveMask(const uint32_t bytes4, const V4x32U& mask,\n                                  const V4x32U& prev)\n{\n  // There is no 128-bit shift by a variable count. Using shuffle_epi8 with a\n  // control mask requires a table lookup. We know the shift count is a\n  // multiple of 4 bytes, so we can broadcastd_epi32 and clear all lanes except\n  // those where mask != 0. This works because any upper output lanes need not\n  // be zero.\n  return prev | AndNot(mask, V4x32U(bytes4));\n}\n\n#if HH_TARGET == HH_TARGET_AVX2\n// Shifts \"suffix\" left by \"prefix_len\" = 0..15 bytes, clears upper bytes of\n// \"prefix\", and returns the merged/concatenated bytes.\nHH_INLINE V4x32U Concatenate(const V4x32U& prefix, const size_t prefix_len,\n                             const V4x32U& suffix)\n{\n  static const uint64_t table[V16x8U::N][V2x64U::N] = {\n    {0x0706050403020100ull, 0x0F0E0D0C0B0A0908ull},\n    {0x06050403020100FFull, 0x0E0D0C0B0A090807ull},\n    {0x050403020100FFFFull, 0x0D0C0B0A09080706ull},\n    {0x0403020100FFFFFFull, 0x0C0B0A0908070605ull},\n    {0x03020100FFFFFFFFull, 0x0B0A090807060504ull},\n    {0x020100FFFFFFFFFFull, 0x0A09080706050403ull},\n    {0x0100FFFFFFFFFFFFull, 0x0908070605040302ull},\n    {0x00FFFFFFFFFFFFFFull, 0x0807060504030201ull},\n    {0xFFFFFFFFFFFFFFFFull, 0x0706050403020100ull},\n    {0xFFFFFFFFFFFFFFFFull, 0x06050403020100FFull},\n    {0xFFFFFFFFFFFFFFFFull, 0x050403020100FFFFull},\n    {0xFFFFFFFFFFFFFFFFull, 0x0403020100FFFFFFull},\n    {0xFFFFFFFFFFFFFFFFull, 0x03020100FFFFFFFFull},\n    {0xFFFFFFFFFFFFFFFFull, 0x020100FFFFFFFFFFull},\n    {0xFFFFFFFFFFFFFFFFull, 0x0100FFFFFFFFFFFFull},\n    {0xFFFFFFFFFFFFFFFFull, 0x00FFFFFFFFFFFFFFull}\n  };\n  const V2x64U control = Load<V2x64U>(&table[prefix_len][0]);\n  const V2x64U shifted_suffix(_mm_shuffle_epi8(suffix, control));\n  return V4x32U(_mm_blendv_epi8(shifted_suffix, prefix, control));\n}\n#endif\n}  // namespace HH_TARGET_NAME\n}  // namespace highwayhash\n\n#endif  // HH_DISABLE_TARGET_SPECIFIC\n#endif  // HIGHWAYHASH_HH_BUFFER_H_\n"
  },
  {
    "path": "common/highwayhash/hh_neon.h",
    "content": "// Copyright 2015-2019 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_HH_NEON_H_\n#define HIGHWAYHASH_HH_NEON_H_\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n#include \"highwayhash/hh_buffer.h\"\n#include \"highwayhash/hh_types.h\"\n#include \"highwayhash/load3.h\"\n#include \"highwayhash/vector_neon.h\"\n\n// For auto-dependency generation, we need to include all headers but not their\n// contents.\n#ifndef HH_DISABLE_TARGET_SPECIFIC\n\nnamespace highwayhash\n{\n\n// See vector_neon.h for why this namespace is necessary; matching it here makes\n// it easier use the vector_neon symbols, but requires textual inclusion.\nnamespace HH_TARGET_NAME\n{\n\n// J-lanes tree hashing: see https://doi.org/10.4236/jis.2014.53010\n// Uses the same method that SSE4.1 uses, only with NEON used instead.\nclass HHStateNEON\n{\npublic:\n  explicit HH_INLINE HHStateNEON(const HHKey key)\n  {\n    Reset(key);\n  }\n\n  HH_INLINE void Reset(const HHKey key)\n  {\n    // \"Nothing up my sleeve numbers\"; see HHStateTAVX2.\n    const V2x64U init0L(0xa4093822299f31d0ull, 0xdbe6d5d5fe4cce2full);\n    const V2x64U init0H(0x243f6a8885a308d3ull, 0x13198a2e03707344ull);\n    const V2x64U init1L(0xc0acf169b5f18a8cull, 0x3bd39e10cb0ef593ull);\n    const V2x64U init1H(0x452821e638d01377ull, 0xbe5466cf34e90c6cull);\n    const V2x64U keyL = LoadUnaligned<V2x64U>(key + 0);\n    const V2x64U keyH = LoadUnaligned<V2x64U>(key + 2);\n    v0L = keyL ^ init0L;\n    v0H = keyH ^ init0H;\n    v1L = Rotate64By32(keyL) ^ init1L;\n    v1H = Rotate64By32(keyH) ^ init1H;\n    mul0L = init0L;\n    mul0H = init0H;\n    mul1L = init1L;\n    mul1H = init1H;\n  }\n\n  HH_INLINE void Update(const HHPacket& packet_bytes)\n  {\n    const uint64_t* HH_RESTRICT packet =\n      reinterpret_cast<const uint64_t* HH_RESTRICT>(packet_bytes);\n    const V2x64U packetL = LoadUnaligned<V2x64U>(packet + 0);\n    const V2x64U packetH = LoadUnaligned<V2x64U>(packet + 2);\n    Update(packetH, packetL);\n  }\n\n  HH_INLINE void UpdateRemainder(const char* bytes, const size_t size_mod32)\n  {\n    // 'Length padding' differentiates zero-valued inputs that have the same\n    // size/32. mod32 is sufficient because each Update behaves as if a\n    // counter were injected, because the state is large and mixed thoroughly.\n    // We can't use vshl/vsra because it needs a constant expression.\n    // In order to do this right now, we would need a switch statement.\n    const int32x4_t vsize_mod32(vdupq_n_s32(static_cast<int32_t>(size_mod32)));\n    // -32 - size_mod32\n    const int32x4_t shift_right_amt =\n      vdupq_n_s32(static_cast<int32_t>(size_mod32) + (~32 + 1));\n    // Equivalent to storing size_mod32 in packet.\n    v0L += V2x64U(vreinterpretq_u64_s32(vsize_mod32));\n    v0H += V2x64U(vreinterpretq_u64_s32(vsize_mod32));\n    // Boosts the avalanche effect of mod32.\n    v1L = V2x64U(vreinterpretq_u64_u32(\n                   vorrq_u32(vshlq_u32(vreinterpretq_u32_u64(v1L), vsize_mod32),\n                             vshlq_u32(vreinterpretq_u32_u64(v1L), shift_right_amt))));\n    v1H = V2x64U(vreinterpretq_u64_u32(\n                   vorrq_u32(vshlq_u32(vreinterpretq_u32_u64(v1H), vsize_mod32),\n                             vshlq_u32(vreinterpretq_u32_u64(v1H), shift_right_amt))));\n    const size_t size_mod4 = size_mod32 & 3;\n    const char* HH_RESTRICT remainder = bytes + (size_mod32 & ~3);\n\n    if (HH_UNLIKELY(size_mod32 & 16)) {  // 16..31 bytes left\n      const V2x64U packetL =\n        LoadUnaligned<V2x64U>(reinterpret_cast<const uint64_t*>(bytes));\n      V2x64U packetH = LoadMultipleOfFour(bytes + 16, size_mod32);\n      const uint32_t last4 =\n        Load3()(Load3::AllowReadBeforeAndReturn(), remainder, size_mod4);\n      // The upper four bytes of packetH are zero, so insert there.\n      packetH = V2x64U(vreinterpretq_u64_u32(\n                         vsetq_lane_u32(last4, vreinterpretq_u32_u64(packetH), 3)));\n      Update(packetH, packetL);\n    } else {  // size_mod32 < 16\n      const V2x64U packetL = LoadMultipleOfFour(bytes, size_mod32);\n      const uint64_t last4 =\n        Load3()(Load3::AllowUnordered(), remainder, size_mod4);\n      // Rather than insert into packetL[3], it is faster to initialize\n      // the otherwise empty packetH.\n      HH_ALIGNAS(16) uint64_t tmp[2] = {last4, 0};\n      const V2x64U packetH(vld1q_u64(tmp));\n      Update(packetH, packetL);\n    }\n  }\n\n  HH_INLINE void Finalize(HHResult64* HH_RESTRICT result)\n  {\n    // Mix together all lanes.\n    for (int n = 0; n < 4; n++) {\n      PermuteAndUpdate();\n    }\n\n    const V2x64U sum0 = v0L + mul0L;\n    const V2x64U sum1 = v1L + mul1L;\n    const V2x64U hash = sum0 + sum1;\n    vst1q_low_u64(reinterpret_cast<uint64_t*>(result), hash);\n  }\n\n  HH_INLINE void Finalize(HHResult128* HH_RESTRICT result)\n  {\n    for (int n = 0; n < 6; n++) {\n      PermuteAndUpdate();\n    }\n\n    const V2x64U sum0 = v0L + mul0L;\n    const V2x64U sum1 = v1H + mul1H;\n    const V2x64U hash = sum0 + sum1;\n    StoreUnaligned(hash, &(*result)[0]);\n  }\n\n  HH_INLINE void Finalize(HHResult256* HH_RESTRICT result)\n  {\n    for (int n = 0; n < 10; n++) {\n      PermuteAndUpdate();\n    }\n\n    const V2x64U sum0L = v0L + mul0L;\n    const V2x64U sum1L = v1L + mul1L;\n    const V2x64U sum0H = v0H + mul0H;\n    const V2x64U sum1H = v1H + mul1H;\n    const V2x64U hashL = ModularReduction(sum1L, sum0L);\n    const V2x64U hashH = ModularReduction(sum1H, sum0H);\n    StoreUnaligned(hashL, &(*result)[0]);\n    StoreUnaligned(hashH, &(*result)[2]);\n  }\n\n  static HH_INLINE void ZeroInitialize(char* HH_RESTRICT buffer_bytes)\n  {\n    for (size_t i = 0; i < sizeof(HHPacket); ++i) {\n      buffer_bytes[i] = 0;\n    }\n  }\n\n  static HH_INLINE void CopyPartial(const char* HH_RESTRICT from,\n                                    const size_t size_mod32,\n                                    char* HH_RESTRICT buffer)\n  {\n    for (size_t i = 0; i < size_mod32; ++i) {\n      buffer[i] = from[i];\n    }\n  }\n\n  static HH_INLINE void AppendPartial(const char* HH_RESTRICT from,\n                                      const size_t size_mod32,\n                                      char* HH_RESTRICT buffer,\n                                      const size_t buffer_valid)\n  {\n    for (size_t i = 0; i < size_mod32; ++i) {\n      buffer[buffer_valid + i] = from[i];\n    }\n  }\n\n  HH_INLINE void AppendAndUpdate(const char* HH_RESTRICT from,\n                                 const size_t size_mod32,\n                                 const char* HH_RESTRICT buffer,\n                                 const size_t buffer_valid)\n  {\n    HHPacket tmp HH_ALIGNAS(32);\n\n    for (size_t i = 0; i < buffer_valid; ++i) {\n      tmp[i] = buffer[i];\n    }\n\n    for (size_t i = 0; i < size_mod32; ++i) {\n      tmp[buffer_valid + i] = from[i];\n    }\n\n    Update(tmp);\n  }\n\nprivate:\n  // Swap 32-bit halves of each lane (caller swaps 128-bit halves)\n  static HH_INLINE V2x64U Rotate64By32(const V2x64U& v)\n  {\n    return V2x64U(vreinterpretq_u64_u32(vrev64q_u32(vreinterpretq_u32_u64(v))));\n  }\n\n  static HH_INLINE V2x64U ZipperMerge(const V2x64U& v)\n  {\n    // Multiplication mixes/scrambles bytes 0-7 of the 64-bit result to\n    // varying degrees. In descending order of goodness, bytes\n    // 3 4 2 5 1 6 0 7 have quality 228 224 164 160 100 96 36 32.\n    // As expected, the upper and lower bytes are much worse.\n    // For each 64-bit lane, our objectives are:\n    // 1) maximizing and equalizing total goodness across each lane's bytes;\n    // 2) mixing with bytes from the neighboring lane;\n    // 3) placing the worst bytes in the upper 32 bits because those will not\n    //    be used in the next 32x32 multiplication.\n    // The positions of each byte in the new vector.\n    const uint8_t shuffle_positions[] = {3,  12, 2,  5,  14, 1, 15, 0,\n                                         11, 4,  10, 13, 9,  6, 8,  7\n                                        };\n    const uint8x16_t tbl = vld1q_u8(shuffle_positions);\n    // Note: vqtbl1q_u8 is polyfilled for ARMv7a in vector_neon.h.\n    return V2x64U(\n             vreinterpretq_u64_u8(vqtbl1q_u8(vreinterpretq_u8_u64(v), tbl)));\n  }\n\n  HH_INLINE void Update(const V2x64U& packetH, const V2x64U& packetL)\n  {\n    v1L += packetL;\n    v1H += packetH;\n    v1L += mul0L;\n    v1H += mul0H;\n    // mul0L ^= (v1L & 0xFFFFFFFF) * (v0L >> 32);\n    mul0L ^= V2x64U(vmull_u32(vmovn_u64(v1L), vshrn_n_u64(v0L, 32)));\n    // mul0H ^= (v1H & 0xFFFFFFFF) * (v0H >> 32);\n    mul0H ^= V2x64U(vmull_u32(vmovn_u64(v1H), vshrn_n_u64(v0H, 32)));\n    v0L += mul1L;\n    v0H += mul1H;\n    // mul1L ^= (v0L & 0xFFFFFFFF) * (v1L >> 32);\n    mul1L ^= V2x64U(vmull_u32(vmovn_u64(v0L), vshrn_n_u64(v1L, 32)));\n    // mul1H ^= (v0H & 0xFFFFFFFF) * (v1H >> 32);\n    mul1H ^= V2x64U(vmull_u32(vmovn_u64(v0H), vshrn_n_u64(v1H, 32)));\n    v0L += ZipperMerge(v1L);\n    v0H += ZipperMerge(v1H);\n    v1L += ZipperMerge(v0L);\n    v1H += ZipperMerge(v0H);\n  }\n\n  HH_INLINE void PermuteAndUpdate()\n  {\n    // It is slightly better to permute v0 than v1; it will be added to v1.\n    Update(Rotate64By32(v0L), Rotate64By32(v0H));\n  }\n\n  // Returns zero-initialized vector with the lower \"size\" = 0, 4, 8 or 12\n  // bytes loaded from \"bytes\". Serves as a replacement for AVX2 maskload_epi32.\n  static HH_INLINE V2x64U LoadMultipleOfFour(const char* bytes,\n      const size_t size)\n  {\n    const uint32_t* words = reinterpret_cast<const uint32_t*>(bytes);\n    // Mask of 1-bits where the final 4 bytes should be inserted (replacement\n    // for variable shift/insert using broadcast+blend).\n    alignas(16) const uint64_t mask_pattern[2] = {0xFFFFFFFFULL, 0};\n    V2x64U mask4(vld1q_u64(mask_pattern));  // 'insert' into lane 0\n    V2x64U ret(vdupq_n_u64(0));\n\n    if (size & 8) {\n      ret = V2x64U(vld1q_low_u64(reinterpret_cast<const uint64_t*>(words)));\n      // mask4 = 0 ~0 0 0 ('insert' into lane 2)\n      mask4 = V2x64U(vshlq_n_u128(mask4, 8));\n      words += 2;\n    }\n\n    // Final 4 (possibly after the 8 above); 'insert' into lane 0 or 2 of ret.\n    if (size & 4) {\n      // = 0 word2 0 word2; mask4 will select which lane to keep.\n      const V2x64U broadcast(vreinterpretq_u64_u32(vdupq_n_u32(words[0])));\n      // (slightly faster than blendv_epi8)\n      ret |= V2x64U(broadcast & mask4);\n    }\n\n    return ret;\n  }\n\n  // XORs x << 1 and x << 2 into *out after clearing the upper two bits of x.\n  // Bit shifts are only possible on independent 64-bit lanes. We therefore\n  // insert the upper bits of x[0] that were lost into x[1].\n  // Thanks to D. Lemire for helpful comments!\n  static HH_INLINE void XorByShift128Left12(const V2x64U& x,\n      V2x64U* HH_RESTRICT out)\n  {\n    const V4x32U zero(vdupq_n_u32(0));\n    const V2x64U sign_bit128(\n      vreinterpretq_u64_u32(vsetq_lane_u32(0x80000000u, zero, 3)));\n    const V2x64U top_bits2 = x >> (64 - 2);\n    HH_COMPILER_FENCE;\n    const V2x64U shifted1_unmasked = x + x;  // (avoids needing port0)\n    // Only the lower half of top_bits1 will be used, so we\n    // can compute it before clearing the upper two bits of x.\n    const V2x64U top_bits1 = x >> (64 - 1);\n    const V2x64U shifted2 = shifted1_unmasked + shifted1_unmasked;\n    HH_COMPILER_FENCE;\n    const V2x64U new_low_bits2(vshlq_n_u128(top_bits2, 8));\n    *out ^= shifted2;\n    // The result must be as if the upper two bits of the input had been clear,\n    // otherwise we're no longer computing a reduction.\n    const V2x64U shifted1 = AndNot(sign_bit128, shifted1_unmasked);\n    HH_COMPILER_FENCE;\n    const V2x64U new_low_bits1(vshlq_n_u128(top_bits1, 8));\n    *out ^= new_low_bits2;\n    *out ^= shifted1;\n    *out ^= new_low_bits1;\n  }\n\n  // Modular reduction by the irreducible polynomial (x^128 + x^2 + x).\n  // Input: a 256-bit number a3210.\n  static HH_INLINE V2x64U ModularReduction(const V2x64U& a32_unmasked,\n      const V2x64U& a10)\n  {\n    // See Lemire, https://arxiv.org/pdf/1503.03465v8.pdf.\n    V2x64U out = a10;\n    XorByShift128Left12(a32_unmasked, &out);\n    return out;\n  }\n\n  V2x64U v0L;\n  V2x64U v0H;\n  V2x64U v1L;\n  V2x64U v1H;\n  V2x64U mul0L;\n  V2x64U mul0H;\n  V2x64U mul1L;\n  V2x64U mul1H;\n};\n\n}  // namespace HH_TARGET_NAME\n}  // namespace highwayhash\n\n#endif  // HH_DISABLE_TARGET_SPECIFIC\n#endif  // HIGHWAYHASH_HH_NEON_H_\n"
  },
  {
    "path": "common/highwayhash/hh_portable.h",
    "content": "// Copyright 2015-2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_HH_PORTABLE_H_\n#define HIGHWAYHASH_HH_PORTABLE_H_\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n#include \"highwayhash/endianess.h\"\n#include \"highwayhash/hh_types.h\"\n#include \"highwayhash/load3.h\"\n\nnamespace highwayhash\n{\n// See vector128.h for why this namespace is necessary; we match it here for\n// consistency. As a result, this header requires textual inclusion.\nnamespace HH_TARGET_NAME\n{\n\nclass HHStatePortable\n{\npublic:\n  static const int kNumLanes = 4;\n  using Lanes = uint64_t[kNumLanes];\n\n  explicit HH_INLINE HHStatePortable(const HHKey keys)\n  {\n    Reset(keys);\n  }\n\n  HH_INLINE void Reset(const HHKey keys)\n  {\n    static const Lanes init0 = {0xdbe6d5d5fe4cce2full, 0xa4093822299f31d0ull,\n                                0x13198a2e03707344ull, 0x243f6a8885a308d3ull\n                               };\n    static const Lanes init1 = {0x3bd39e10cb0ef593ull, 0xc0acf169b5f18a8cull,\n                                0xbe5466cf34e90c6cull, 0x452821e638d01377ull\n                               };\n    Lanes rotated_keys;\n    Rotate64By32(keys, &rotated_keys);\n    Copy(init0, &mul0);\n    Copy(init1, &mul1);\n    Xor(init0, keys, &v0);\n    Xor(init1, rotated_keys, &v1);\n  }\n\n  HH_INLINE void Update(const HHPacket& packet)\n  {\n    Lanes packet_lanes;\n    CopyPartial(&packet[0], sizeof(HHPacket),\n                reinterpret_cast<char*>(&packet_lanes));\n\n    for (int lane = 0; lane < kNumLanes; ++lane) {\n      packet_lanes[lane] = host_from_le64(packet_lanes[lane]);\n    }\n\n    Update(packet_lanes);\n  }\n\n  HH_INLINE void UpdateRemainder(const char* bytes, const size_t size_mod32)\n  {\n    // 'Length padding' differentiates zero-valued inputs that have the same\n    // size/32. mod32 is sufficient because each Update behaves as if a\n    // counter were injected, because the state is large and mixed thoroughly.\n    const uint64_t mod32_pair =\n      (static_cast<uint64_t>(size_mod32) << 32) + size_mod32;\n\n    for (int lane = 0; lane < kNumLanes; ++lane) {\n      v0[lane] += mod32_pair;\n    }\n\n    Rotate32By(reinterpret_cast<uint32_t*>(&v1), size_mod32);\n    const size_t size_mod4 = size_mod32 & 3;\n    const char* remainder = bytes + (size_mod32 & ~3);\n    HHPacket packet HH_ALIGNAS(32) = {0};\n    CopyPartial(bytes, remainder - bytes, &packet[0]);\n\n    if (size_mod32 & 16) {  // 16..31 bytes left\n      // Read the last 0..3 bytes and previous 1..4 into the upper bits.\n      // Insert into the upper four bytes of packet, which are zero.\n      uint32_t last4 =\n        Load3()(Load3::AllowReadBeforeAndReturn(), remainder, size_mod4);\n      CopyPartial(reinterpret_cast<const char*>(&last4), 4, &packet[28]);\n    } else {  // size_mod32 < 16\n      uint64_t last4 = Load3()(Load3::AllowUnordered(), remainder, size_mod4);\n      // Rather than insert at packet + 28, it is faster to initialize\n      // the otherwise empty packet + 16 with up to 64 bits of padding.\n      CopyPartial(reinterpret_cast<const char*>(&last4), sizeof(last4),\n                  &packet[16]);\n    }\n\n    Update(packet);\n  }\n\n  HH_INLINE void Finalize(HHResult64* HH_RESTRICT result)\n  {\n    for (int n = 0; n < 4; n++) {\n      PermuteAndUpdate();\n    }\n\n    *result = v0[0] + v1[0] + mul0[0] + mul1[0];\n  }\n\n  HH_INLINE void Finalize(HHResult128* HH_RESTRICT result)\n  {\n    for (int n = 0; n < 6; n++) {\n      PermuteAndUpdate();\n    }\n\n    (*result)[0] = v0[0] + mul0[0] + v1[2] + mul1[2];\n    (*result)[1] = v0[1] + mul0[1] + v1[3] + mul1[3];\n  }\n\n  HH_INLINE void Finalize(HHResult256* HH_RESTRICT result)\n  {\n    for (int n = 0; n < 10; n++) {\n      PermuteAndUpdate();\n    }\n\n    ModularReduction(v1[1] + mul1[1], v1[0] + mul1[0], v0[1] + mul0[1],\n                     v0[0] + mul0[0], &(*result)[1], &(*result)[0]);\n    ModularReduction(v1[3] + mul1[3], v1[2] + mul1[2], v0[3] + mul0[3],\n                     v0[2] + mul0[2], &(*result)[3], &(*result)[2]);\n  }\n\n  static HH_INLINE void ZeroInitialize(char* HH_RESTRICT buffer)\n  {\n    for (size_t i = 0; i < sizeof(HHPacket); ++i) {\n      buffer[i] = 0;\n    }\n  }\n\n  static HH_INLINE void CopyPartial(const char* HH_RESTRICT from,\n                                    const size_t size_mod32,\n                                    char* HH_RESTRICT buffer)\n  {\n    for (size_t i = 0; i < size_mod32; ++i) {\n      buffer[i] = from[i];\n    }\n  }\n\n  static HH_INLINE void AppendPartial(const char* HH_RESTRICT from,\n                                      const size_t size_mod32,\n                                      char* HH_RESTRICT buffer,\n                                      const size_t buffer_valid)\n  {\n    for (size_t i = 0; i < size_mod32; ++i) {\n      buffer[buffer_valid + i] = from[i];\n    }\n  }\n\n  HH_INLINE void AppendAndUpdate(const char* HH_RESTRICT from,\n                                 const size_t size_mod32,\n                                 const char* HH_RESTRICT buffer,\n                                 const size_t buffer_valid)\n  {\n    HHPacket tmp HH_ALIGNAS(32);\n\n    for (size_t i = 0; i < buffer_valid; ++i) {\n      tmp[i] = buffer[i];\n    }\n\n    for (size_t i = 0; i < size_mod32; ++i) {\n      tmp[buffer_valid + i] = from[i];\n    }\n\n    Update(tmp);\n  }\n\nprivate:\n  static HH_INLINE void Copy(const Lanes& source, Lanes* HH_RESTRICT dest)\n  {\n    for (int lane = 0; lane < kNumLanes; ++lane) {\n      (*dest)[lane] = source[lane];\n    }\n  }\n\n  static HH_INLINE void Add(const Lanes& source, Lanes* HH_RESTRICT dest)\n  {\n    for (int lane = 0; lane < kNumLanes; ++lane) {\n      (*dest)[lane] += source[lane];\n    }\n  }\n\n  template <typename LanesOrPointer>\n  static HH_INLINE void Xor(const Lanes& op1, const LanesOrPointer& op2,\n                            Lanes* HH_RESTRICT dest)\n  {\n    for (int lane = 0; lane < kNumLanes; ++lane) {\n      (*dest)[lane] = op1[lane] ^ op2[lane];\n    }\n  }\n\n// Clears all bits except one byte at the given offset.\n#define MASK(v, bytes) ((v) & (0xFFull << ((bytes)*8)))\n\n  // 16-byte permutation; shifting is about 10% faster than byte loads.\n  // Adds zipper-merge result to add*.\n  static HH_INLINE void ZipperMergeAndAdd(const uint64_t v1, const uint64_t v0,\n                                          uint64_t* HH_RESTRICT add1,\n                                          uint64_t* HH_RESTRICT add0)\n  {\n    *add0 += ((MASK(v0, 3) + MASK(v1, 4)) >> 24) +\n             ((MASK(v0, 5) + MASK(v1, 6)) >> 16) + MASK(v0, 2) +\n             (MASK(v0, 1) << 32) + (MASK(v1, 7) >> 8) + (v0 << 56);\n    *add1 += ((MASK(v1, 3) + MASK(v0, 4)) >> 24) + MASK(v1, 2) +\n             (MASK(v1, 5) >> 16) + (MASK(v1, 1) << 24) + (MASK(v0, 6) >> 8) +\n             (MASK(v1, 0) << 48) + MASK(v0, 7);\n  }\n\n#undef MASK\n\n  // For inputs that are already in native byte order (e.g. PermuteAndAdd)\n  HH_INLINE void Update(const Lanes& packet_lanes)\n  {\n    Add(packet_lanes, &v1);\n    Add(mul0, &v1);\n\n    // (Loop is faster than unrolling)\n    for (int lane = 0; lane < kNumLanes; ++lane) {\n      const uint32_t v1_32 = static_cast<uint32_t>(v1[lane]);\n      mul0[lane] ^= v1_32 * (v0[lane] >> 32);\n      v0[lane] += mul1[lane];\n      const uint32_t v0_32 = static_cast<uint32_t>(v0[lane]);\n      mul1[lane] ^= v0_32 * (v1[lane] >> 32);\n    }\n\n    ZipperMergeAndAdd(v1[1], v1[0], &v0[1], &v0[0]);\n    ZipperMergeAndAdd(v1[3], v1[2], &v0[3], &v0[2]);\n    ZipperMergeAndAdd(v0[1], v0[0], &v1[1], &v1[0]);\n    ZipperMergeAndAdd(v0[3], v0[2], &v1[3], &v1[2]);\n  }\n\n  static HH_INLINE uint64_t Rotate64By32(const uint64_t x)\n  {\n    return (x >> 32) | (x << 32);\n  }\n\n  template <typename LanesOrPointer>\n  static HH_INLINE void Rotate64By32(const LanesOrPointer& v,\n                                     Lanes* HH_RESTRICT rotated)\n  {\n    for (int i = 0; i < kNumLanes; ++i) {\n      (*rotated)[i] = Rotate64By32(v[i]);\n    }\n  }\n\n  static HH_INLINE void Rotate32By(uint32_t* halves, const uint64_t count)\n  {\n    for (int i = 0; i < 2 * kNumLanes; ++i) {\n      const uint32_t x = halves[i];\n      halves[i] = (x << count) | (x >> (32 - count));\n    }\n  }\n\n  static HH_INLINE void Permute(const Lanes& v, Lanes* HH_RESTRICT permuted)\n  {\n    (*permuted)[0] = Rotate64By32(v[2]);\n    (*permuted)[1] = Rotate64By32(v[3]);\n    (*permuted)[2] = Rotate64By32(v[0]);\n    (*permuted)[3] = Rotate64By32(v[1]);\n  }\n\n  HH_INLINE void PermuteAndUpdate()\n  {\n    Lanes permuted;\n    Permute(v0, &permuted);\n    Update(permuted);\n  }\n\n  // Computes a << kBits for 128-bit a = (a1, a0).\n  // Bit shifts are only possible on independent 64-bit lanes. We therefore\n  // insert the upper bits of a0 that were lost into a1. This is slightly\n  // shorter than Lemire's (a << 1) | (((a >> 8) << 1) << 8) approach.\n  template <int kBits>\n  static HH_INLINE void Shift128Left(uint64_t* HH_RESTRICT a1,\n                                     uint64_t* HH_RESTRICT a0)\n  {\n    const uint64_t shifted1 = (*a1) << kBits;\n    const uint64_t top_bits = (*a0) >> (64 - kBits);\n    *a0 <<= kBits;\n    *a1 = shifted1 | top_bits;\n  }\n\n  // Modular reduction by the irreducible polynomial (x^128 + x^2 + x).\n  // Input: a 256-bit number a3210.\n  static HH_INLINE void ModularReduction(const uint64_t a3_unmasked,\n                                         const uint64_t a2, const uint64_t a1,\n                                         const uint64_t a0,\n                                         uint64_t* HH_RESTRICT m1,\n                                         uint64_t* HH_RESTRICT m0)\n  {\n    // The upper two bits must be clear, otherwise a3 << 2 would lose bits,\n    // in which case we're no longer computing a reduction.\n    const uint64_t a3 = a3_unmasked & 0x3FFFFFFFFFFFFFFFull;\n    // See Lemire, https://arxiv.org/pdf/1503.03465v8.pdf.\n    uint64_t a3_shl1 = a3;\n    uint64_t a2_shl1 = a2;\n    uint64_t a3_shl2 = a3;\n    uint64_t a2_shl2 = a2;\n    Shift128Left<1>(&a3_shl1, &a2_shl1);\n    Shift128Left<2>(&a3_shl2, &a2_shl2);\n    *m1 = a1 ^ a3_shl1 ^ a3_shl2;\n    *m0 = a0 ^ a2_shl1 ^ a2_shl2;\n  }\n\n  Lanes v0;\n  Lanes v1;\n  Lanes mul0;\n  Lanes mul1;\n};\n\n}  // namespace HH_TARGET_NAME\n}  // namespace highwayhash\n\n#endif  // HIGHWAYHASH_HH_PORTABLE_H_\n"
  },
  {
    "path": "common/highwayhash/hh_sse41.h",
    "content": "// Copyright 2015-2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_HH_SSE41_H_\n#define HIGHWAYHASH_HH_SSE41_H_\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n#include \"highwayhash/hh_buffer.h\"\n#include \"highwayhash/hh_types.h\"\n#include \"highwayhash/load3.h\"\n#include \"highwayhash/vector128.h\"\n\n// For auto-dependency generation, we need to include all headers but not their\n// contents (otherwise compilation fails because -msse4.1 is not specified).\n#ifndef HH_DISABLE_TARGET_SPECIFIC\n\nnamespace highwayhash\n{\n// See vector128.h for why this namespace is necessary; matching it here makes\n// it easier use the vector128 symbols, but requires textual inclusion.\nnamespace HH_TARGET_NAME\n{\n\n// J-lanes tree hashing: see https://doi.org/10.4236/jis.2014.53010\n// Uses pairs of SSE4.1 instructions to emulate the AVX-2 algorithm.\nclass HHStateSSE41\n{\npublic:\n  explicit HH_INLINE HHStateSSE41(const HHKey key)\n  {\n    Reset(key);\n  }\n\n  HH_INLINE void Reset(const HHKey key)\n  {\n    // \"Nothing up my sleeve numbers\"; see HHStateTAVX2.\n    const V2x64U init0L(0xa4093822299f31d0ull, 0xdbe6d5d5fe4cce2full);\n    const V2x64U init0H(0x243f6a8885a308d3ull, 0x13198a2e03707344ull);\n    const V2x64U init1L(0xc0acf169b5f18a8cull, 0x3bd39e10cb0ef593ull);\n    const V2x64U init1H(0x452821e638d01377ull, 0xbe5466cf34e90c6cull);\n    const V2x64U keyL = LoadUnaligned<V2x64U>(key + 0);\n    const V2x64U keyH = LoadUnaligned<V2x64U>(key + 2);\n    v0L = keyL ^ init0L;\n    v0H = keyH ^ init0H;\n    v1L = Rotate64By32(keyL) ^ init1L;\n    v1H = Rotate64By32(keyH) ^ init1H;\n    mul0L = init0L;\n    mul0H = init0H;\n    mul1L = init1L;\n    mul1H = init1H;\n  }\n\n  HH_INLINE void Update(const HHPacket& packet_bytes)\n  {\n    const uint64_t* HH_RESTRICT packet =\n      reinterpret_cast<const uint64_t* HH_RESTRICT>(packet_bytes);\n    const V2x64U packetL = LoadUnaligned<V2x64U>(packet + 0);\n    const V2x64U packetH = LoadUnaligned<V2x64U>(packet + 2);\n    Update(packetH, packetL);\n  }\n\n  HH_INLINE void UpdateRemainder(const char* bytes, const size_t size_mod32)\n  {\n    // 'Length padding' differentiates zero-valued inputs that have the same\n    // size/32. mod32 is sufficient because each Update behaves as if a\n    // counter were injected, because the state is large and mixed thoroughly.\n    const V4x32U vsize_mod32(static_cast<uint32_t>(size_mod32));\n    // Equivalent to storing size_mod32 in packet.\n    v0L += V2x64U(vsize_mod32);\n    v0H += V2x64U(vsize_mod32);\n    // Boosts the avalanche effect of mod32.\n    Rotate32By(&v1H, &v1L, size_mod32);\n    const size_t size_mod4 = size_mod32 & 3;\n    const char* HH_RESTRICT remainder = bytes + (size_mod32 & ~3);\n\n    if (HH_UNLIKELY(size_mod32 & 16)) {  // 16..31 bytes left\n      const V2x64U packetL =\n        LoadUnaligned<V2x64U>(reinterpret_cast<const uint64_t*>(bytes));\n      V2x64U packetH = LoadMultipleOfFour(bytes + 16, size_mod32);\n      const uint32_t last4 =\n        Load3()(Load3::AllowReadBeforeAndReturn(), remainder, size_mod4);\n      // The upper four bytes of packetH are zero, so insert there.\n      packetH = V2x64U(_mm_insert_epi32(packetH, last4, 3));\n      Update(packetH, packetL);\n    } else {  // size_mod32 < 16\n      const V2x64U packetL = LoadMultipleOfFour(bytes, size_mod32);\n      const uint64_t last4 =\n        Load3()(Load3::AllowUnordered(), remainder, size_mod4);\n      // Rather than insert into packetL[3], it is faster to initialize\n      // the otherwise empty packetH.\n      const V2x64U packetH(_mm_cvtsi64_si128(last4));\n      Update(packetH, packetL);\n    }\n  }\n\n  HH_INLINE void Finalize(HHResult64* HH_RESTRICT result)\n  {\n    // Mix together all lanes.\n    for (int n = 0; n < 4; n++) {\n      PermuteAndUpdate();\n    }\n\n    const V2x64U sum0 = v0L + mul0L;\n    const V2x64U sum1 = v1L + mul1L;\n    const V2x64U hash = sum0 + sum1;\n    _mm_storel_epi64(reinterpret_cast<__m128i*>(result), hash);\n  }\n\n  HH_INLINE void Finalize(HHResult128* HH_RESTRICT result)\n  {\n    for (int n = 0; n < 6; n++) {\n      PermuteAndUpdate();\n    }\n\n    const V2x64U sum0 = v0L + mul0L;\n    const V2x64U sum1 = v1H + mul1H;\n    const V2x64U hash = sum0 + sum1;\n    StoreUnaligned(hash, &(*result)[0]);\n  }\n\n  HH_INLINE void Finalize(HHResult256* HH_RESTRICT result)\n  {\n    for (int n = 0; n < 10; n++) {\n      PermuteAndUpdate();\n    }\n\n    const V2x64U sum0L = v0L + mul0L;\n    const V2x64U sum1L = v1L + mul1L;\n    const V2x64U sum0H = v0H + mul0H;\n    const V2x64U sum1H = v1H + mul1H;\n    const V2x64U hashL = ModularReduction(sum1L, sum0L);\n    const V2x64U hashH = ModularReduction(sum1H, sum0H);\n    StoreUnaligned(hashL, &(*result)[0]);\n    StoreUnaligned(hashH, &(*result)[2]);\n  }\n\n  static HH_INLINE void ZeroInitialize(char* HH_RESTRICT buffer_bytes)\n  {\n    __m128i* buffer = reinterpret_cast<__m128i*>(buffer_bytes);\n    const __m128i zero = _mm_setzero_si128();\n    _mm_store_si128(buffer + 0, zero);\n    _mm_store_si128(buffer + 1, zero);\n  }\n\n  static HH_INLINE void CopyPartial(const char* HH_RESTRICT from,\n                                    const size_t size_mod32,\n                                    char* HH_RESTRICT buffer)\n  {\n    for (size_t i = 0; i < size_mod32; ++i) {\n      buffer[i] = from[i];\n    }\n  }\n\n  static HH_INLINE void AppendPartial(const char* HH_RESTRICT from,\n                                      const size_t size_mod32,\n                                      char* HH_RESTRICT buffer,\n                                      const size_t buffer_valid)\n  {\n    for (size_t i = 0; i < size_mod32; ++i) {\n      buffer[buffer_valid + i] = from[i];\n    }\n  }\n\n  HH_INLINE void AppendAndUpdate(const char* HH_RESTRICT from,\n                                 const size_t size_mod32,\n                                 const char* HH_RESTRICT buffer,\n                                 const size_t buffer_valid)\n  {\n    HHPacket tmp HH_ALIGNAS(32);\n\n    for (size_t i = 0; i < buffer_valid; ++i) {\n      tmp[i] = buffer[i];\n    }\n\n    for (size_t i = 0; i < size_mod32; ++i) {\n      tmp[buffer_valid + i] = from[i];\n    }\n\n    Update(tmp);\n  }\n\nprivate:\n  // Swap 32-bit halves of each lane (caller swaps 128-bit halves)\n  static HH_INLINE V2x64U Rotate64By32(const V2x64U& v)\n  {\n    return V2x64U(_mm_shuffle_epi32(v, _MM_SHUFFLE(2, 3, 0, 1)));\n  }\n\n  // Rotates 32-bit lanes by \"count\" bits.\n  static HH_INLINE void Rotate32By(V2x64U* HH_RESTRICT vH,\n                                   V2x64U* HH_RESTRICT vL,\n                                   const uint64_t count)\n  {\n    // WARNING: the shift count is 64 bits, so we can't reuse vsize_mod32,\n    // which is broadcast into 32-bit lanes.\n    const __m128i count_left = _mm_cvtsi64_si128(count);\n    const __m128i count_right = _mm_cvtsi64_si128(32 - count);\n    const V2x64U shifted_leftL(_mm_sll_epi32(*vL, count_left));\n    const V2x64U shifted_leftH(_mm_sll_epi32(*vH, count_left));\n    const V2x64U shifted_rightL(_mm_srl_epi32(*vL, count_right));\n    const V2x64U shifted_rightH(_mm_srl_epi32(*vH, count_right));\n    *vL = shifted_leftL | shifted_rightL;\n    *vH = shifted_leftH | shifted_rightH;\n  }\n\n  static HH_INLINE V2x64U ZipperMerge(const V2x64U& v)\n  {\n    // Multiplication mixes/scrambles bytes 0-7 of the 64-bit result to\n    // varying degrees. In descending order of goodness, bytes\n    // 3 4 2 5 1 6 0 7 have quality 228 224 164 160 100 96 36 32.\n    // As expected, the upper and lower bytes are much worse.\n    // For each 64-bit lane, our objectives are:\n    // 1) maximizing and equalizing total goodness across each lane's bytes;\n    // 2) mixing with bytes from the neighboring lane;\n    // 3) placing the worst bytes in the upper 32 bits because those will not\n    //    be used in the next 32x32 multiplication.\n    const uint64_t hi = 0x070806090D0A040Bull;\n    const uint64_t lo = 0x000F010E05020C03ull;\n    return V2x64U(_mm_shuffle_epi8(v, V2x64U(hi, lo)));\n  }\n\n  HH_INLINE void Update(const V2x64U& packetH, const V2x64U& packetL)\n  {\n    v1L += packetL;\n    v1H += packetH;\n    v1L += mul0L;\n    v1H += mul0H;\n    mul0L ^= V2x64U(_mm_mul_epu32(v1L, Rotate64By32(v0L)));\n    mul0H ^= V2x64U(_mm_mul_epu32(v1H, v0H >> 32));\n    v0L += mul1L;\n    v0H += mul1H;\n    mul1L ^= V2x64U(_mm_mul_epu32(v0L, Rotate64By32(v1L)));\n    mul1H ^= V2x64U(_mm_mul_epu32(v0H, v1H >> 32));\n    v0L += ZipperMerge(v1L);\n    v0H += ZipperMerge(v1H);\n    v1L += ZipperMerge(v0L);\n    v1H += ZipperMerge(v0H);\n  }\n\n  HH_INLINE void PermuteAndUpdate()\n  {\n    // It is slightly better to permute v0 than v1; it will be added to v1.\n    // AVX-2 Permute also swaps 128-bit halves, so swap input operands.\n    Update(Rotate64By32(v0L), Rotate64By32(v0H));\n  }\n\n  // Returns zero-initialized vector with the lower \"size\" = 0, 4, 8 or 12\n  // bytes loaded from \"bytes\". Serves as a replacement for AVX2 maskload_epi32.\n  static HH_INLINE V2x64U LoadMultipleOfFour(const char* bytes,\n      const size_t size)\n  {\n    const uint32_t* words = reinterpret_cast<const uint32_t*>(bytes);\n    // Mask of 1-bits where the final 4 bytes should be inserted (replacement\n    // for variable shift/insert using broadcast+blend).\n    V2x64U mask4(_mm_cvtsi64_si128(0xFFFFFFFFULL));  // 'insert' into lane 0\n    V2x64U ret(0);\n\n    if (size & 8) {\n      ret = V2x64U(_mm_loadl_epi64(reinterpret_cast<const __m128i*>(words)));\n      // mask4 = 0 ~0 0 0 ('insert' into lane 2)\n      mask4 = V2x64U(_mm_slli_si128(mask4, 8));\n      words += 2;\n    }\n\n    // Final 4 (possibly after the 8 above); 'insert' into lane 0 or 2 of ret.\n    if (size & 4) {\n      const __m128i word2 = _mm_cvtsi32_si128(words[0]);\n      // = 0 word2 0 word2; mask4 will select which lane to keep.\n      const V2x64U broadcast(_mm_shuffle_epi32(word2, 0x00));\n      // (slightly faster than blendv_epi8)\n      ret |= V2x64U(broadcast & mask4);\n    }\n\n    return ret;\n  }\n\n  // XORs x << 1 and x << 2 into *out after clearing the upper two bits of x.\n  // Bit shifts are only possible on independent 64-bit lanes. We therefore\n  // insert the upper bits of x[0] that were lost into x[1].\n  // Thanks to D. Lemire for helpful comments!\n  static HH_INLINE void XorByShift128Left12(const V2x64U& x,\n      V2x64U* HH_RESTRICT out)\n  {\n    const V2x64U zero(_mm_setzero_si128());\n    const V2x64U sign_bit128(_mm_insert_epi32(zero, 0x80000000u, 3));\n    const V2x64U top_bits2 = x >> (64 - 2);\n    HH_COMPILER_FENCE;\n    const V2x64U shifted1_unmasked = x + x;  // (avoids needing port0)\n    // Only the lower half of top_bits1 will be used, so we\n    // can compute it before clearing the upper two bits of x.\n    const V2x64U top_bits1 = x >> (64 - 1);\n    const V2x64U shifted2 = shifted1_unmasked + shifted1_unmasked;\n    HH_COMPILER_FENCE;\n    const V2x64U new_low_bits2(_mm_slli_si128(top_bits2, 8));\n    *out ^= shifted2;\n    // The result must be as if the upper two bits of the input had been clear,\n    // otherwise we're no longer computing a reduction.\n    const V2x64U shifted1 = AndNot(sign_bit128, shifted1_unmasked);\n    HH_COMPILER_FENCE;\n    const V2x64U new_low_bits1(_mm_slli_si128(top_bits1, 8));\n    *out ^= new_low_bits2;\n    *out ^= shifted1;\n    *out ^= new_low_bits1;\n  }\n\n  // Modular reduction by the irreducible polynomial (x^128 + x^2 + x).\n  // Input: a 256-bit number a3210.\n  static HH_INLINE V2x64U ModularReduction(const V2x64U& a32_unmasked,\n      const V2x64U& a10)\n  {\n    // See Lemire, https://arxiv.org/pdf/1503.03465v8.pdf.\n    V2x64U out = a10;\n    XorByShift128Left12(a32_unmasked, &out);\n    return out;\n  }\n\n  V2x64U v0L;\n  V2x64U v0H;\n  V2x64U v1L;\n  V2x64U v1H;\n  V2x64U mul0L;\n  V2x64U mul0H;\n  V2x64U mul1L;\n  V2x64U mul1H;\n};\n\n}  // namespace HH_TARGET_NAME\n}  // namespace highwayhash\n\n#endif  // HH_DISABLE_TARGET_SPECIFIC\n#endif  // HIGHWAYHASH_HH_SSE41_H_\n"
  },
  {
    "path": "common/highwayhash/hh_types.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_HH_TYPES_H_\n#define HIGHWAYHASH_HH_TYPES_H_\n\n// WARNING: included from c_bindings => must be C-compatible.\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include <stddef.h>  // size_t\n#include <stdint.h>\n\n#ifdef __cplusplus\nnamespace highwayhash\n{\n#endif\n\n// 256-bit secret key that should remain unknown to attackers.\n// We recommend initializing it to a random value.\ntypedef uint64_t HHKey[4];\n\n// How much input is hashed by one call to HHStateT::Update.\ntypedef char HHPacket[32];\n\n// Hash 'return' types.\ntypedef uint64_t HHResult64;  // returned directly\ntypedef uint64_t HHResult128[2];\ntypedef uint64_t HHResult256[4];\n\n// Called if a test fails, indicating which target and size.\ntypedef void (*HHNotify)(const char*, size_t);\n\n#ifdef __cplusplus\n}  // namespace highwayhash\n#endif\n\n#endif  // HIGHWAYHASH_HH_TYPES_H_\n"
  },
  {
    "path": "common/highwayhash/hh_vsx.h",
    "content": "// Copyright 2015-2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_HH_VSX_H_\n#define HIGHWAYHASH_HH_VSX_H_\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n#include \"highwayhash/hh_types.h\"\n#include \"highwayhash/load3.h\"\n\n// For auto-dependency generation, we need to include all headers but not their\n// contents\n#ifndef HH_DISABLE_TARGET_SPECIFIC\n\n#include <altivec.h>\n#undef vector\n#undef pixel\n#undef bool\n\nnamespace highwayhash\n{\n\ntypedef __vector unsigned long long PPC_VEC_U64;  // NOLINT\ntypedef __vector unsigned int PPC_VEC_U32;\ntypedef __vector unsigned char PPC_VEC_U8;\n\n// See vector128.h for why this namespace is necessary;\nnamespace HH_TARGET_NAME\n{\n\n// Helper Functions\n\n// gcc doesn't support vec_mule() and vec_mulo() for vector long.\n// Use the generic version, which is defined here only for gcc.\n\n#ifndef __clang__\nstatic HH_INLINE PPC_VEC_U64 vec_mule(PPC_VEC_U32 a, PPC_VEC_U32 b)    // NOLINT\n{\n  PPC_VEC_U64 result;                                                  // NOLINT\n#ifdef __LITTLE_ENDIAN__\n  asm(\"vmulouw %0, %1, %2\" : \"=v\"(result) : \"v\"(a), \"v\"(b));\n#else\n  asm(\"vmuleuw %0, %1, %2\" : \"=v\"(result) : \"v\"(a), \"v\"(b));\n#endif\n  return result;\n}\n#endif\n\n// LoadUnaligned uses vec_vsx_ld(offset, address) format,\n// Offset here is number of bytes and is 0 for this implementation.\nstatic HH_INLINE PPC_VEC_U64\nLoadUnaligned(const uint64_t* const HH_RESTRICT from)\n{\n  const PPC_VEC_U64* const HH_RESTRICT p =\n    reinterpret_cast<const PPC_VEC_U64*>(from);\n  return vec_vsx_ld(0, p);\n}\n\nstatic HH_INLINE void StoreUnaligned(const PPC_VEC_U64& hash,\n                                     uint64_t* const HH_RESTRICT to)\n{\n  PPC_VEC_U64* HH_RESTRICT p = reinterpret_cast<PPC_VEC_U64* HH_RESTRICT>(to);\n  vec_vsx_st(hash, 0, p);\n}\n\nstatic HH_INLINE PPC_VEC_U64 MultiplyVectors(const PPC_VEC_U64& vec1,\n    const PPC_VEC_U64& vec2)\n{\n  return vec_mule(reinterpret_cast<const PPC_VEC_U32>(vec1),\n                  reinterpret_cast<const PPC_VEC_U32>(vec2));\n}\n\n// J-lanes tree hashing: see https://doi.org/10.4236/jis.2014.53010\nclass HHStateVSX\n{\npublic:\n  explicit HH_INLINE HHStateVSX(const HHKey key)\n  {\n    Reset(key);\n  }\n\n  HH_INLINE void Reset(const HHKey key)\n  {\n    // \"Nothing up my sleeve numbers\";\n    const PPC_VEC_U64 init0L = {0xdbe6d5d5fe4cce2full, 0xa4093822299f31d0ull};\n    const PPC_VEC_U64 init0H = {0x13198a2e03707344ull, 0x243f6a8885a308d3ull};\n    const PPC_VEC_U64 init1L = {0x3bd39e10cb0ef593ull, 0xc0acf169b5f18a8cull};\n    const PPC_VEC_U64 init1H = {0xbe5466cf34e90c6cull, 0x452821e638d01377ull};\n    const PPC_VEC_U64 keyL = LoadUnaligned(key);\n    const PPC_VEC_U64 keyH = LoadUnaligned(key + 2);\n    v0L = keyL ^ init0L;\n    v0H = keyH ^ init0H;\n    v1L = Rotate64By32(keyL) ^ init1L;\n    v1H = Rotate64By32(keyH) ^ init1H;\n    mul0L = init0L;\n    mul0H = init0H;\n    mul1L = init1L;\n    mul1H = init1H;\n  }\n\n  HH_INLINE void Update(const HHPacket& packet_bytes)\n  {\n    const uint64_t* HH_RESTRICT packet =\n      reinterpret_cast<const uint64_t* HH_RESTRICT>(packet_bytes);\n    const PPC_VEC_U64 packetL = LoadUnaligned(packet);\n    const PPC_VEC_U64 packetH = LoadUnaligned(packet + 2);\n    Update(packetH, packetL);\n  }\n\n  HH_INLINE void UpdateRemainder(const char* bytes, const size_t size_mod32)\n  {\n    // 'Length padding' differentiates zero-valued inputs that have the same\n    // size/32. mod32 is sufficient because each Update behaves as if a\n    // counter were injected, because the state is large and mixed thoroughly.\n    uint32_t size_rounded = static_cast<uint32_t>(size_mod32);\n    PPC_VEC_U32 vsize_mod32 = {size_rounded, size_rounded, size_rounded,\n                               size_rounded\n                              };\n    // Equivalent to storing size_mod32 in packet.\n    v0L += reinterpret_cast<PPC_VEC_U64>(vsize_mod32);\n    v0H += reinterpret_cast<PPC_VEC_U64>(vsize_mod32);\n    // Boosts the avalanche effect of mod32.\n    Rotate32By(&v1H, &v1L, size_mod32);\n    const size_t size_mod4 = size_mod32 & 3;\n    const char* HH_RESTRICT remainder = bytes + (size_mod32 & ~3);\n\n    if (HH_UNLIKELY(size_mod32 & 16)) {  // 16..31 bytes left\n      const PPC_VEC_U64 packetL =\n        vec_vsx_ld(0, reinterpret_cast<const PPC_VEC_U64*>(bytes));\n      PPC_VEC_U64 packetH = LoadMultipleOfFour(bytes + 16, size_mod32);\n      const uint32_t last4 =\n        Load3()(Load3::AllowReadBeforeAndReturn(), remainder, size_mod4);\n      // The upper four bytes of packetH are zero, so insert there.\n      PPC_VEC_U32 packetH_32 = reinterpret_cast<PPC_VEC_U32>(packetH);\n      packetH_32[3] = last4;\n      packetH = reinterpret_cast<PPC_VEC_U64>(packetH_32);\n      Update(packetH, packetL);\n    } else {  // size_mod32 < 16\n      const PPC_VEC_U64 packetL = LoadMultipleOfFour(bytes, size_mod32);\n      const uint64_t last4 =\n        Load3()(Load3::AllowUnordered(), remainder, size_mod4);\n      // Rather than insert into packetL[3], it is faster to initialize\n      // the otherwise empty packetH.\n      const PPC_VEC_U64 packetH = {last4, 0};\n      Update(packetH, packetL);\n    }\n  }\n\n  HH_INLINE void Finalize(HHResult64* HH_RESTRICT result)\n  {\n    // Mix together all lanes.\n    for (int n = 0; n < 4; n++) {\n      PermuteAndUpdate();\n    }\n\n    const PPC_VEC_U64 hash = v0L + v1L + mul0L + mul1L;\n    *result = hash[0];\n  }\n\n  HH_INLINE void Finalize(HHResult128* HH_RESTRICT result)\n  {\n    for (int n = 0; n < 6; n++) {\n      PermuteAndUpdate();\n    }\n\n    const PPC_VEC_U64 hash = v0L + mul0L + v1H + mul1H;\n    StoreUnaligned(hash, *result);\n  }\n\n  HH_INLINE void Finalize(HHResult256* HH_RESTRICT result)\n  {\n    for (int n = 0; n < 10; n++) {\n      PermuteAndUpdate();\n    }\n\n    const PPC_VEC_U64 sum0L = v0L + mul0L;\n    const PPC_VEC_U64 sum1L = v1L + mul1L;\n    const PPC_VEC_U64 sum0H = v0H + mul0H;\n    const PPC_VEC_U64 sum1H = v1H + mul1H;\n    const PPC_VEC_U64 hashL = ModularReduction(sum1L, sum0L);\n    const PPC_VEC_U64 hashH = ModularReduction(sum1H, sum0H);\n    StoreUnaligned(hashL, *result);\n    StoreUnaligned(hashH, *result + 2);\n  }\n\n  static HH_INLINE void ZeroInitialize(char* HH_RESTRICT buffer_bytes)\n  {\n    for (size_t i = 0; i < sizeof(HHPacket); ++i) {\n      buffer_bytes[i] = 0;\n    }\n  }\n\n  static HH_INLINE void CopyPartial(const char* HH_RESTRICT from,\n                                    const size_t size_mod32,\n                                    char* HH_RESTRICT buffer)\n  {\n    for (size_t i = 0; i < size_mod32; ++i) {\n      buffer[i] = from[i];\n    }\n  }\n\n  static HH_INLINE void AppendPartial(const char* HH_RESTRICT from,\n                                      const size_t size_mod32,\n                                      char* HH_RESTRICT buffer,\n                                      const size_t buffer_valid)\n  {\n    for (size_t i = 0; i < size_mod32; ++i) {\n      buffer[buffer_valid + i] = from[i];\n    }\n  }\n\n  HH_INLINE void AppendAndUpdate(const char* HH_RESTRICT from,\n                                 const size_t size_mod32,\n                                 const char* HH_RESTRICT buffer,\n                                 const size_t buffer_valid)\n  {\n    HHPacket tmp HH_ALIGNAS(32);\n\n    for (size_t i = 0; i < buffer_valid; ++i) {\n      tmp[i] = buffer[i];\n    }\n\n    for (size_t i = 0; i < size_mod32; ++i) {\n      tmp[buffer_valid + i] = from[i];\n    }\n\n    Update(tmp);\n  }\n\nprivate:\n  // Swap 32-bit halves of each lane (caller swaps 128-bit halves)\n  static HH_INLINE PPC_VEC_U64 Rotate64By32(const PPC_VEC_U64& v)\n  {\n    PPC_VEC_U64 shuffle_vec = {32, 32};\n    return vec_rl(v, shuffle_vec);\n  }\n\n  // Rotates 32-bit lanes by \"count\" bits.\n  static HH_INLINE void Rotate32By(PPC_VEC_U64* HH_RESTRICT vH,\n                                   PPC_VEC_U64* HH_RESTRICT vL,\n                                   const uint64_t count)\n  {\n    // WARNING: the shift count is 64 bits, so we can't reuse vsize_mod32,\n    // which is broadcast into 32-bit lanes.\n    uint32_t count_rl = uint32_t(count);\n    PPC_VEC_U32 rot_left = {count_rl, count_rl, count_rl, count_rl};\n    *vL = reinterpret_cast<PPC_VEC_U64>(vec_rl(PPC_VEC_U32(*vL), rot_left));\n    *vH = reinterpret_cast<PPC_VEC_U64>(vec_rl(PPC_VEC_U32(*vH), rot_left));\n  }\n\n  static HH_INLINE PPC_VEC_U64 ZipperMerge(const PPC_VEC_U64& v)\n  {\n    // Multiplication mixes/scrambles bytes 0-7 of the 64-bit result to\n    // varying degrees. In descending order of goodness, bytes\n    // 3 4 2 5 1 6 0 7 have quality 228 224 164 160 100 96 36 32.\n    // As expected, the upper and lower bytes are much worse.\n    // For each 64-bit lane, our objectives are:\n    // 1) maximizing and equalizing total goodness across each lane's bytes;\n    // 2) mixing with bytes from the neighboring lane;\n    // 3) placing the worst bytes in the upper 32 bits because those will not\n    //    be used in the next 32x32 multiplication.\n    const PPC_VEC_U64 mask = {0x000F010E05020C03ull, 0x070806090D0A040Bull};\n    return vec_vperm(v, v, reinterpret_cast<const PPC_VEC_U8>(mask));\n  }\n\n  HH_INLINE void Update(const PPC_VEC_U64& packetH,\n                        const PPC_VEC_U64& packetL)\n  {\n    // Tried rearranging the instructions below and benchmarks are similar\n    v1L += packetL + mul0L;\n    v1H += packetH + mul0H;\n    mul0L ^= MultiplyVectors(v1L, Rotate64By32(v0L));\n    mul0H ^= MultiplyVectors(v1H, v0H >> 32);\n    v0L += mul1L;\n    v0H += mul1H;\n    mul1L ^= MultiplyVectors(v0L, Rotate64By32(v1L));\n    mul1H ^= MultiplyVectors(v0H, v1H >> 32);\n    v0L += ZipperMerge(v1L);\n    v1L += ZipperMerge(v0L);\n    v0H += ZipperMerge(v1H);\n    v1H += ZipperMerge(v0H);\n  }\n\n  HH_INLINE void PermuteAndUpdate()\n  {\n    // Permutes v0L and V0H by swapping 32 bits halves of each lane\n    Update(Rotate64By32(v0L), Rotate64By32(v0H));\n  }\n\n  // Returns zero-initialized vector with the lower \"size\" = 0, 4, 8 or 12\n  // bytes loaded from \"bytes\". Serves as a replacement for AVX2 maskload_epi32.\n  static HH_INLINE PPC_VEC_U64 LoadMultipleOfFour(const char* bytes,\n      const size_t size)\n  {\n    const uint32_t* words = reinterpret_cast<const uint32_t*>(bytes);\n    // Updating the entries, as if done by vec_insert function call\n    PPC_VEC_U32 ret = {0, 0, 0, 0};\n\n    if (size & 8) {\n      ret[0] = words[0];\n      ret[1] = words[1];\n      words += 2;\n\n      if (size & 4) {\n        ret[2] = words[0];\n      }\n    } else if (size & 4) {\n      ret[0] = words[0];\n    }\n\n    return reinterpret_cast<PPC_VEC_U64>(ret);\n  }\n\n  // Modular reduction by the irreducible polynomial (x^128 + x^2 + x).\n  // Input: a 256-bit number a3210.\n  static HH_INLINE PPC_VEC_U64 ModularReduction(const PPC_VEC_U64& a32_unmasked,\n      const PPC_VEC_U64& a10)\n  {\n    // See Lemire, https://arxiv.org/pdf/1503.03465v8.pdf.\n    PPC_VEC_U64 out = a10;\n    const PPC_VEC_U64 shifted1 = reinterpret_cast<PPC_VEC_U64>(\n                                   vec_sll(reinterpret_cast<PPC_VEC_U32>(a32_unmasked), vec_splat_u8(1)));\n    const PPC_VEC_U64 shifted2 = reinterpret_cast<PPC_VEC_U64>(\n                                   vec_sll(reinterpret_cast<PPC_VEC_U32>(a32_unmasked), vec_splat_u8(2)));\n    // The result must be as if the upper two bits of the input had been clear,\n    // otherwise we're no longer computing a reduction.\n    const PPC_VEC_U64 mask = {0xFFFFFFFFFFFFFFFFull, 0x7FFFFFFFFFFFFFFFull};\n    const PPC_VEC_U64 shifted1_masked = shifted1 & mask;\n    out ^= shifted1_masked ^ shifted2;\n    return out;\n  }\n\n  PPC_VEC_U64 v0L;\n  PPC_VEC_U64 v0H;\n  PPC_VEC_U64 v1L;\n  PPC_VEC_U64 v1H;\n  PPC_VEC_U64 mul0L;\n  PPC_VEC_U64 mul0H;\n  PPC_VEC_U64 mul1L;\n  PPC_VEC_U64 mul1H;\n};\n\n}  // namespace HH_TARGET_NAME\n}  // namespace highwayhash\n\n#endif  // HH_DISABLE_TARGET_SPECIFIC\n#endif  // HIGHWAYHASH_HH_VSX_H_\n"
  },
  {
    "path": "common/highwayhash/highwayhash.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_HIGHWAYHASH_H_\n#define HIGHWAYHASH_HIGHWAYHASH_H_\n\n// This header's templates are useful for inlining into other CPU-specific code:\n// template<TargetBits Target> CodeUsingHash() { HighwayHashT<Target>(...); },\n// and can also be instantiated with HH_TARGET when callers don't care about the\n// exact implementation. Otherwise, they are implementation details of the\n// highwayhash_target wrapper. Use that instead if you need to detect the best\n// available implementation at runtime.\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n#include \"highwayhash/hh_types.h\"\n\n#if HH_ARCH_X64\n#include \"highwayhash/iaca.h\"\n#endif\n\n// Include exactly one (see arch_specific.h) header, which defines a state\n// object in a target-specific namespace, e.g. AVX2::HHStateAVX2.\n// Attempts to use \"computed includes\" (#define MACRO \"path/or_just_filename\",\n// #include MACRO) fail with 'file not found', so we need an #if chain.\n#if HH_TARGET == HH_TARGET_AVX2\n#include \"highwayhash/hh_avx2.h\"\n#elif HH_TARGET == HH_TARGET_SSE41\n#include \"highwayhash/hh_sse41.h\"\n#elif HH_TARGET == HH_TARGET_VSX\n#include \"highwayhash/hh_vsx.h\"\n#elif HH_TARGET == HH_TARGET_NEON\n#include \"highwayhash/hh_neon.h\"\n#elif HH_TARGET == HH_TARGET_Portable\n#include \"highwayhash/hh_portable.h\"\n#else\n#error \"Unknown target, add its hh_*.h include here.\"\n#endif\n\n#ifndef HH_DISABLE_TARGET_SPECIFIC\nnamespace highwayhash\n{\n\n// Alias templates (HHStateT) cannot be specialized, so we need a helper struct.\n// Note that hh_*.h don't just specialize HHStateT directly because vector128.h\n// must reside in a distinct namespace (to allow including it from multiple\n// translation units), and it is easier if its users, i.e. the concrete HHState,\n// also reside in that same namespace, which precludes specialization.\ntemplate <TargetBits Target>\nstruct HHStateForTarget {};\n\ntemplate <>\nstruct HHStateForTarget<HH_TARGET> {\n  // (The namespace is sufficient and the additional HH_TARGET_NAME suffix is\n  // technically redundant, but it makes searching easier.)\n  using type = HH_TARGET_NAME::HH_ADD_TARGET_SUFFIX(HHState);\n};\n\n// Typically used as HHStateT<HH_TARGET>. It would be easier to just have a\n// concrete type HH_STATE, but this alias template is required by the\n// templates in highwayhash_target.cc.\ntemplate <TargetBits Target>\nusing HHStateT = typename HHStateForTarget<Target>::type;\n\n// Computes HighwayHash of \"bytes\" using the implementation chosen by \"State\".\n//\n// \"state\" is a HHStateT<> initialized with a key.\n// \"bytes\" is the data to hash (possibly unaligned).\n// \"size\" is the number of bytes to hash; we do not read any additional bytes.\n// \"hash\" is a HHResult* (either 64, 128 or 256 bits).\n//\n// HighwayHash is a strong pseudorandom function with security claims\n// [https://arxiv.org/abs/1612.06257]. It is intended as a safer general-purpose\n// hash, about 4x faster than SipHash and 10x faster than BLAKE2.\n//\n// This template allows callers (e.g. tests) to invoke a specific\n// implementation. It must be compiled with the flags required by the desired\n// implementation. If the entire program cannot be built with these flags, use\n// the wrapper in highwayhash_target.h instead.\n//\n// Callers wanting to hash multiple pieces of data should duplicate this\n// function, calling HHStateT::Update for each input and only Finalizing once.\ntemplate <class State, typename Result>\nHH_INLINE void HighwayHashT(State* HH_RESTRICT state,\n                            const char* HH_RESTRICT bytes, const size_t size,\n                            Result* HH_RESTRICT hash)\n{\n  // BeginIACA();\n  const size_t remainder = size & (sizeof(HHPacket) - 1);\n  const size_t truncated = size & ~(sizeof(HHPacket) - 1);\n\n  for (size_t offset = 0; offset < truncated; offset += sizeof(HHPacket)) {\n    state->Update(*reinterpret_cast<const HHPacket*>(bytes + offset));\n  }\n\n  if (remainder != 0) {\n    state->UpdateRemainder(bytes + truncated, remainder);\n  }\n\n  state->Finalize(hash);\n  // EndIACA();\n}\n\n// Wrapper class for incrementally hashing a series of data ranges. The final\n// result is the same as HighwayHashT of the concatenation of all the ranges.\n// This is useful for computing the hash of cords, iovecs, and similar\n// data structures.\ntemplate <TargetBits Target>\nclass HighwayHashCatT\n{\npublic:\n  HH_INLINE HighwayHashCatT(const HHKey& key) : state_(key)\n  {\n    // Avoids msan uninitialized-memory warnings.\n    HHStateT<Target>::ZeroInitialize(buffer_);\n  }\n\n  // Resets the state of the hasher so it can be used to hash a new string.\n  HH_INLINE void Reset(const HHKey& key)\n  {\n    state_.Reset(key);\n    buffer_usage_ = 0;\n  }\n\n  // Adds \"bytes\" to the internal buffer, feeding it to HHStateT::Update as\n  // required. Call this as often as desired. Only reads bytes within the\n  // interval [bytes, bytes + num_bytes). \"num_bytes\" == 0 has no effect.\n  //\n  // Beware that this implies hashing two strings {\"A\", \"\"} has the same result\n  // as {\"\", \"A\"}. To prevent this when hashing independent fields, you can\n  // append some extra (non-empty) data when a field is empty, or\n  // unconditionally also Append the field length. Either option would ensure\n  // the two examples above result in a different hash.\n  //\n  // There are no alignment requirements.\n  HH_INLINE void Append(const char* HH_RESTRICT bytes, size_t num_bytes)\n  {\n    // BeginIACA();\n    const size_t capacity = sizeof(HHPacket) - buffer_usage_;\n\n    // New bytes fit within buffer, but still not enough to Update.\n    if (HH_UNLIKELY(num_bytes < capacity)) {\n      HHStateT<Target>::AppendPartial(bytes, num_bytes, buffer_, buffer_usage_);\n      buffer_usage_ += num_bytes;\n      return;\n    }\n\n    // HACK: ensures the state is kept in SIMD registers; otherwise, Update\n    // constantly load/stores its operands, which is much slower.\n    // Restrict-qualified pointers to external state or the state_ member are\n    // not sufficient for keeping this in registers.\n    HHStateT<Target> state_copy = state_;\n    // Have prior bytes to flush.\n    const size_t buffer_usage = buffer_usage_;\n\n    if (HH_LIKELY(buffer_usage != 0)) {\n      // Calls update with prior buffer contents plus new data. Does not modify\n      // the buffer because some implementations can load into SIMD registers\n      // and Append to them directly.\n      state_copy.AppendAndUpdate(bytes, capacity, buffer_, buffer_usage);\n      bytes += capacity;\n      num_bytes -= capacity;\n    }\n\n    // Buffer currently empty => Update directly from the source.\n    while (num_bytes >= sizeof(HHPacket)) {\n      state_copy.Update(*reinterpret_cast<const HHPacket*>(bytes));\n      bytes += sizeof(HHPacket);\n      num_bytes -= sizeof(HHPacket);\n    }\n\n    // Unconditionally assign even if zero because we didn't reset to zero\n    // after the AppendAndUpdate above.\n    buffer_usage_ = num_bytes;\n    state_ = state_copy;\n\n    // Store any remainders in buffer, no-op if multiple of a packet.\n    if (HH_LIKELY(num_bytes != 0)) {\n      HHStateT<Target>::CopyPartial(bytes, num_bytes, buffer_);\n    }\n\n    // EndIACA();\n  }\n\n  // Stores the resulting 64, 128 or 256-bit hash of all data passed to Append.\n  // Must be called exactly once, or after a prior Reset.\n  template <typename Result>  // HHResult*\n  HH_INLINE void Finalize(Result* HH_RESTRICT hash)\n  {\n    // BeginIACA();\n    HHStateT<Target> state_copy = state_;\n    const size_t buffer_usage = buffer_usage_;\n\n    if (HH_LIKELY(buffer_usage != 0)) {\n      state_copy.UpdateRemainder(buffer_, buffer_usage);\n    }\n\n    state_copy.Finalize(hash);\n    // EndIACA();\n  }\n\nprivate:\n  HHPacket buffer_ HH_ALIGNAS(64);\n  HHStateT<Target> state_ HH_ALIGNAS(32);\n  // How many bytes in buffer_ (starting with offset 0) are valid.\n  size_t buffer_usage_ = 0;\n};\n\n}  // namespace highwayhash\n#endif  // HH_DISABLE_TARGET_SPECIFIC\n#endif  // HIGHWAYHASH_HIGHWAYHASH_H_\n"
  },
  {
    "path": "common/highwayhash/highwayhash_target.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_HIGHWAYHASH_TARGET_H_\n#define HIGHWAYHASH_HIGHWAYHASH_TARGET_H_\n\n// Adapter for the InstructionSets::Run dispatcher, which invokes the best\n// implementations available on the current CPU.\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n#include \"highwayhash/hh_types.h\"\n\nnamespace highwayhash\n{\n\n// Usage: InstructionSets::Run<HighwayHash>(key, bytes, size, hash).\n// This incurs some small dispatch overhead. If the entire program is compiled\n// for the target CPU, you can instead call HighwayHashT directly to avoid any\n// overhead. This template is instantiated in the source file, which is\n// compiled once for every target with the required flags (e.g. -mavx2).\ntemplate <TargetBits Target>\nstruct HighwayHash {\n  // Stores a 64/128/256 bit hash of \"bytes\" using the HighwayHashT\n  // implementation for the \"Target\" CPU. The hash result is identical\n  // regardless of which implementation is used.\n  //\n  // \"key\" is a (randomly generated or hard-coded) HHKey.\n  // \"bytes\" is the data to hash (possibly unaligned).\n  // \"size\" is the number of bytes to hash; we do not read any additional bytes.\n  // \"hash\" is a HHResult* (either 64, 128 or 256 bits).\n  //\n  // HighwayHash is a strong pseudorandom function with security claims\n  // [https://arxiv.org/abs/1612.06257]. It is intended as a safer\n  // general-purpose hash, 5x faster than SipHash and 10x faster than BLAKE2.\n  void operator()(const HHKey& key, const char* HH_RESTRICT bytes,\n                  const size_t size, HHResult64* HH_RESTRICT hash) const;\n  void operator()(const HHKey& key, const char* HH_RESTRICT bytes,\n                  const size_t size, HHResult128* HH_RESTRICT hash) const;\n  void operator()(const HHKey& key, const char* HH_RESTRICT bytes,\n                  const size_t size, HHResult256* HH_RESTRICT hash) const;\n};\n\n// Replacement for C++17 std::string_view that avoids dependencies.\n// A struct requires fewer allocations when calling HighwayHashCat with\n// non-const \"num_fragments\".\nstruct StringView {\n  const char* data;  // not necessarily aligned/padded\n  size_t num_bytes;  // possibly zero\n};\n\n// Note: this interface avoids dispatch overhead per fragment.\ntemplate <TargetBits Target>\nstruct HighwayHashCat {\n  // Stores a 64/128/256 bit hash of all \"num_fragments\" \"fragments\" using the\n  // HighwayHashCatT implementation for \"Target\". The hash result is identical\n  // to HighwayHash of the flattened data, regardless of Target.\n  //\n  // \"key\" is a (randomly generated or hard-coded) HHKey.\n  // \"fragments\" contain unaligned pointers and the number of valid bytes.\n  // \"num_fragments\" indicates the number of entries in \"fragments\".\n  // \"hash\" is a HHResult* (either 64, 128 or 256 bits).\n  void operator()(const HHKey& key, const StringView* HH_RESTRICT fragments,\n                  const size_t num_fragments,\n                  HHResult64* HH_RESTRICT hash) const;\n  void operator()(const HHKey& key, const StringView* HH_RESTRICT fragments,\n                  const size_t num_fragments,\n                  HHResult128* HH_RESTRICT hash) const;\n  void operator()(const HHKey& key, const StringView* HH_RESTRICT fragments,\n                  const size_t num_fragments,\n                  HHResult256* HH_RESTRICT hash) const;\n};\n\n}  // namespace highwayhash\n\n#endif  // HIGHWAYHASH_HIGHWAYHASH_TARGET_H_\n"
  },
  {
    "path": "common/highwayhash/highwayhash_test_target.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_HIGHWAYHASH_TEST_TARGET_H_\n#define HIGHWAYHASH_HIGHWAYHASH_TEST_TARGET_H_\n\n// Tests called by InstructionSets::RunAll, so we can verify all\n// implementations supported by the current CPU.\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include <stddef.h>\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n#include \"highwayhash/hh_types.h\"\n#include \"highwayhash/highwayhash.h\"\n#include \"highwayhash/nanobenchmark.h\"\n\nnamespace highwayhash\n{\n\n// Verifies the hash result matches \"expected\" and calls \"notify\" if not.\ntemplate <TargetBits Target>\nstruct HighwayHashTest {\n  void operator()(const HHKey& key, const char* HH_RESTRICT bytes,\n                  const size_t size, const HHResult64* expected,\n                  const HHNotify notify) const;\n  void operator()(const HHKey& key, const char* HH_RESTRICT bytes,\n                  const size_t size, const HHResult128* expected,\n                  const HHNotify notify) const;\n  void operator()(const HHKey& key, const char* HH_RESTRICT bytes,\n                  const size_t size, const HHResult256* expected,\n                  const HHNotify notify) const;\n};\n\n// For every possible partition of \"bytes\" into zero to three fragments,\n// verifies HighwayHashCat returns the same result as HighwayHashT of the\n// concatenated fragments, and calls \"notify\" if not. The value of \"expected\"\n// is ignored; it is only used for overloading.\ntemplate <TargetBits Target>\nstruct HighwayHashCatTest {\n  void operator()(const HHKey& key, const char* HH_RESTRICT bytes,\n                  const uint64_t size, const HHResult64* expected,\n                  const HHNotify notify) const;\n  void operator()(const HHKey& key, const char* HH_RESTRICT bytes,\n                  const uint64_t size, const HHResult128* expected,\n                  const HHNotify notify) const;\n  void operator()(const HHKey& key, const char* HH_RESTRICT bytes,\n                  const uint64_t size, const HHResult256* expected,\n                  const HHNotify notify) const;\n};\n\n// Called by benchmark with prefix, target_name, input_map, context.\n// This function must set input_map->num_items to 0.\nusing NotifyBenchmark = void (*)(const char*, const char*, DurationsForInputs*,\n                                 void*);\n\nconstexpr size_t kMaxBenchmarkInputSize = 1024;\n\n// Calls \"notify\" with benchmark results for the input sizes specified by\n// \"input_map\" (<= kMaxBenchmarkInputSize) plus a \"context\" parameter.\ntemplate <TargetBits Target>\nstruct HighwayHashBenchmark {\n  void operator()(DurationsForInputs* input_map, NotifyBenchmark notify,\n                  void* context) const;\n};\n\ntemplate <TargetBits Target>\nstruct HighwayHashCatBenchmark {\n  void operator()(DurationsForInputs* input_map, NotifyBenchmark notify,\n                  void* context) const;\n};\n\n}  // namespace highwayhash\n\n#endif  // HIGHWAYHASH_HIGHWAYHASH_TEST_TARGET_H_\n"
  },
  {
    "path": "common/highwayhash/iaca.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_IACA_H_\n#define HIGHWAYHASH_IACA_H_\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include \"highwayhash/compiler_specific.h\"\n\n// IACA (Intel's Code Analyzer, go/intel-iaca) analyzes instruction latencies,\n// but only for code between special markers. These functions embed such markers\n// in an executable, but only for reading via IACA - they deliberately trigger\n// a crash if executed to ensure they are removed in normal builds.\n\n// Default off; callers must `#define HH_ENABLE_IACA 1` before including this.\n#ifndef HH_ENABLE_IACA\n#define HH_ENABLE_IACA 0\n#endif\n\nnamespace highwayhash\n{\n\n#if HH_ENABLE_IACA && (HH_GCC_VERSION || HH_CLANG_VERSION)\n\n// Call before the region of interest. Fences hopefully prevent reordering.\nstatic HH_INLINE void BeginIACA()\n{\n  HH_COMPILER_FENCE;\n  asm volatile(\n    \".byte 0x0F, 0x0B\\n\\t\"  // UD2\n    \"movl $111, %ebx\\n\\t\"\n    \".byte 0x64, 0x67, 0x90\\n\\t\");\n  HH_COMPILER_FENCE;\n}\n\n// Call after the region of interest. Fences hopefully prevent reordering.\nstatic HH_INLINE void EndIACA()\n{\n  HH_COMPILER_FENCE;\n  asm volatile(\n    \"movl $222, %ebx\\n\\t\"\n    \".byte 0x64, 0x67, 0x90\\n\\t\"\n    \".byte 0x0F, 0x0B\\n\\t\");  // UD2\n  HH_COMPILER_FENCE;\n}\n\n#endif\n\n}  // namespace highwayhash\n\n#endif  // HIGHWAYHASH_IACA_H_\n"
  },
  {
    "path": "common/highwayhash/instruction_sets.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_INSTRUCTION_SETS_H_\n#define HIGHWAYHASH_INSTRUCTION_SETS_H_\n\n// Calls the best specialization of a template supported by the current CPU.\n//\n// Usage: for each dispatch site, declare a Functor template with a 'Target'\n// argument, add a source file defining its operator() and instantiating\n// Functor<HH_TARGET>, add a cc_library_for_targets rule for that source file,\n// and call InstructionSets::Run<Functor>(/*args*/).\n\n#include <utility>  // std::forward\n\n#include \"highwayhash/arch_specific.h\"  // HH_TARGET_*\n#include \"highwayhash/compiler_specific.h\"\n\nnamespace highwayhash\n{\n\n// Detects TargetBits and calls specializations of a user-defined functor.\nclass InstructionSets\n{\npublic:\n// Returns bit array of HH_TARGET_* supported by the current CPU.\n// The HH_TARGET_Portable bit is guaranteed to be set.\n#if HH_ARCH_X64\n  static TargetBits Supported();\n#elif HH_ARCH_PPC\n  static HH_INLINE TargetBits Supported()\n  {\n    return HH_TARGET_VSX | HH_TARGET_Portable;\n  }\n#elif HH_ARCH_NEON\n  static HH_INLINE TargetBits Supported()\n  {\n    return HH_TARGET_NEON | HH_TARGET_Portable;\n  }\n#else\n  static HH_INLINE TargetBits Supported()\n  {\n    return HH_TARGET_Portable;\n  }\n#endif\n\n  // Chooses the best available \"Target\" for the current CPU, runs the\n  // corresponding Func<Target>::operator()(args) and returns that Target\n  // (a single bit). The overhead of dispatching is low, about 4 cycles, but\n  // this should only be called infrequently (e.g. hoisting it out of loops).\n  template <template <TargetBits> class Func, typename... Args>\n  static HH_INLINE TargetBits Run(Args&& ... args)\n  {\n#if HH_ARCH_X64\n    const TargetBits supported = Supported();\n\n    if (supported & HH_TARGET_AVX2) {\n      Func<HH_TARGET_AVX2>()(std::forward<Args>(args)...);\n      return HH_TARGET_AVX2;\n    }\n\n    if (supported & HH_TARGET_SSE41) {\n      Func<HH_TARGET_SSE41>()(std::forward<Args>(args)...);\n      return HH_TARGET_SSE41;\n    }\n\n#elif HH_ARCH_PPC\n    const TargetBits supported = Supported();\n\n    if (supported & HH_TARGET_VSX) {\n      Func<HH_TARGET_VSX>()(std::forward<Args>(args)...);\n      return HH_TARGET_VSX;\n    }\n\n#elif HH_ARCH_NEON\n    const TargetBits supported = Supported();\n\n    if (supported & HH_TARGET_NEON) {\n      Func<HH_TARGET_NEON>()(std::forward<Args>(args)...);\n      return HH_TARGET_NEON;\n    }\n\n#endif\n    // No matching HH_ARCH or no supported HH_TARGET:\n    Func<HH_TARGET_Portable>()(std::forward<Args>(args)...);\n    return HH_TARGET_Portable;\n  }\n\n  // Calls Func<Target>::operator()(args) for all Target supported by the\n  // current CPU, and returns their HH_TARGET_* bits.\n  template <template <TargetBits> class Func, typename... Args>\n  static HH_INLINE TargetBits RunAll(Args&& ... args)\n  {\n    const TargetBits supported = Supported();\n#if HH_ARCH_X64\n\n    if (supported & HH_TARGET_AVX2) {\n      Func<HH_TARGET_AVX2>()(std::forward<Args>(args)...);\n    }\n\n    if (supported & HH_TARGET_SSE41) {\n      Func<HH_TARGET_SSE41>()(std::forward<Args>(args)...);\n    }\n\n#elif HH_ARCH_PPC\n\n    if (supported & HH_TARGET_VSX) {\n      Func<HH_TARGET_VSX>()(std::forward<Args>(args)...);\n    }\n\n#elif HH_ARCH_NEON\n\n    if (supported & HH_TARGET_NEON) {\n      Func<HH_TARGET_NEON>()(std::forward<Args>(args)...);\n    }\n\n#endif\n    Func<HH_TARGET_Portable>()(std::forward<Args>(args)...);\n    return supported;  // i.e. all that were run\n  }\n};\n\n}  // namespace highwayhash\n\n#endif  // HIGHWAYHASH_INSTRUCTION_SETS_H_\n"
  },
  {
    "path": "common/highwayhash/load3.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_HH_LOAD3_H_\n#define HIGHWAYHASH_HH_LOAD3_H_\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include <stddef.h>\n#include <stdint.h>\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n#include \"highwayhash/endianess.h\"\n\nnamespace highwayhash\n{\n// To prevent ODR violations when including this from multiple translation\n// units (TU) that are compiled with different flags, the contents must reside\n// in a namespace whose name is unique to the TU. NOTE: this behavior is\n// incompatible with precompiled modules and requires textual inclusion instead.\nnamespace HH_TARGET_NAME\n{\n\n// Loads 0 to 3 bytes from a given location using one of several policies.\n// These are potentially faster than 8-bit loads, but require certain additional\n// promises by the caller: that 'out of bounds' memory accesses are allowed,\n// and/or that the bytes may be permuted or duplicated.\nclass Load3\n{\npublic:\n  // In increasing order of complexity:\n  struct AllowReadBeforeAndReturn {};\n  struct AllowReadBefore {};\n  struct AllowUnordered {};\n  struct AllowNone {};\n\n  // Up to 4 preceding bytes may be read and returned along with the 0..3\n  // valid bytes. The valid bytes are in little-endian order, except that the\n  // preceding bytes occupy the least-significant bytes.\n  HH_INLINE uint32_t operator()(AllowReadBeforeAndReturn, const char* from,\n                                const size_t size_mod4)\n  {\n    // It's safe to read before \"from\", so we can load 32 bits, which is faster\n    // than individual byte loads. We assume little-endian byte order, so\n    // big-endian platforms will need to swap. Type punning can generate\n    // incorrect code if compiled with strict aliasing; the only safe\n    // alternatives are memcpy and reading through char*. We must avoid memcpy\n    // because string.h must not be included per the warning above. On GCC and\n    // Clang, we can use a builtin instead.\n    uint32_t last4;\n    Copy(from + size_mod4 - 4, 4, reinterpret_cast<char*>(&last4));\n    return host_from_le32(last4);\n  }\n\n  // As above, but preceding bytes are removed and upper byte(s) are zero.\n  HH_INLINE uint64_t operator()(AllowReadBefore, const char* from,\n                                const size_t size_mod4)\n  {\n    // Shift 0..3 valid bytes into LSB as if loaded in little-endian order.\n    // 64-bit type enables 32-bit shift when size_mod4 == 0.\n    uint64_t last3 = operator()(AllowReadBeforeAndReturn(), from, size_mod4);\n    last3 >>= 32 - (size_mod4 * 8);\n    return last3;\n  }\n\n  // The bytes need not be loaded in little-endian order. This particular order\n  // (and the duplication of some bytes depending on \"size_mod4\") was chosen for\n  // computational convenience and can no longer be changed because it is part\n  // of the HighwayHash length padding definition.\n  HH_INLINE uint64_t operator()(AllowUnordered, const char* from,\n                                const size_t size_mod4)\n  {\n    uint64_t last3 = 0;\n\n    // Not allowed to read any bytes; early-out is faster than reading from a\n    // constant array of zeros.\n    if (size_mod4 == 0) {\n      return last3;\n    }\n\n    // These indices are chosen as an easy-to-compute sequence containing the\n    // same elements as [0, size), but repeated and/or reordered. This enables\n    // unconditional loads, which outperform conditional 8 or 16+8 bit loads.\n    const uint64_t idx0 = 0;\n    const uint64_t idx1 = size_mod4 >> 1;\n    const uint64_t idx2 = size_mod4 - 1;\n    // Store into least significant bytes (avoids one shift).\n    last3 = U64FromChar(from[idx0]);\n    last3 += U64FromChar(from[idx1]) << 8;\n    last3 += U64FromChar(from[idx2]) << 16;\n    return last3;\n  }\n\n  // Must read exactly [0, size) bytes in little-endian order.\n  HH_INLINE uint64_t operator()(AllowNone, const char* from,\n                                const size_t size_mod4)\n  {\n    // We need to load in little-endian order without accessing anything outside\n    // [from, from + size_mod4). Unrolling is faster than looping backwards.\n    uint64_t last3 = 0;\n\n    if (size_mod4 >= 1) {\n      last3 += U64FromChar(from[0]);\n    }\n\n    if (size_mod4 >= 2) {\n      last3 += U64FromChar(from[1]) << 8;\n    }\n\n    if (size_mod4 == 3) {\n      last3 += U64FromChar(from[2]) << 16;\n    }\n\n    return last3;\n  }\n\nprivate:\n  static HH_INLINE uint32_t U32FromChar(const char c)\n  {\n    return static_cast<uint32_t>(static_cast<unsigned char>(c));\n  }\n\n  static HH_INLINE uint64_t U64FromChar(const char c)\n  {\n    return static_cast<uint64_t>(static_cast<unsigned char>(c));\n  }\n\n  static HH_INLINE void Copy(const char* HH_RESTRICT from, const size_t size,\n                             char* HH_RESTRICT to)\n  {\n#if HH_MSC_VERSION\n\n    for (size_t i = 0; i < size; ++i) {\n      to[i] = from[i];\n    }\n\n#else\n    __builtin_memcpy(to, from, size);\n#endif\n  }\n};\n\n}  // namespace HH_TARGET_NAME\n}  // namespace highwayhash\n\n#endif  // HIGHWAYHASH_LOAD3_H_\n"
  },
  {
    "path": "common/highwayhash/nanobenchmark.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_NANOBENCHMARK_H_\n#define HIGHWAYHASH_NANOBENCHMARK_H_\n\n// Benchmarks functions of a single integer argument with realistic branch\n// prediction hit rates. Uses a robust estimator to summarize the measurements.\n// Measurements are precise to about 0.2 cycles.\n//\n// Example:\n//   #include \"highwayhash/nanobenchmark.h\"\n//   using namespace highwayhash;\n//\n//   uint64_t RegionToMeasure(const void*, size_t size) {\n//     char from[8] = {static_cast<char>(size)};\n//     char to[8];\n//     memcpy(to, from, size);\n//     return to[0];\n//   }\n//\n//   PinThreadToRandomCPU();\n//\n//   static const size_t distribution[] = {3, 3, 4, 4, 7, 7, 8, 8};\n//   DurationsForInputs input_map = MakeDurationsForInputs(distribution, 10);\n//   MeasureDurations(&RegionToMeasure, &input_map);\n//   for (size_t i = 0; i < input_map.num_items; ++i) {\n//     input_map.items[i].PrintMedianAndVariability();\n//   }\n//\n// Output:\n//   3: median= 25.2 ticks; median abs. deviation= 0.1 ticks\n//   4: median= 13.5 ticks; median abs. deviation= 0.1 ticks\n//   7: median= 13.5 ticks; median abs. deviation= 0.1 ticks\n//   8: median= 27.5 ticks; median abs. deviation= 0.2 ticks\n// (7 is presumably faster because it can use two unaligned 32-bit load/stores.)\n//\n// Background: Microbenchmarks such as http://github.com/google/benchmark\n// can measure elapsed times on the order of a microsecond. Shorter functions\n// are typically measured by repeating them thousands of times and dividing\n// the total elapsed time by this count. Unfortunately, repetition (especially\n// with the same input parameter!) influences the runtime. In time-critical\n// code, it is reasonable to expect warm instruction/data caches and TLBs,\n// but a perfect record of which branches will be taken is unrealistic.\n// Unless the application also repeatedly invokes the measured function with\n// the same parameter, the benchmark is measuring something very different -\n// a best-case result, almost as if the parameter were made a compile-time\n// constant. This may lead to erroneous conclusions about branch-heavy\n// algorithms outperforming branch-free alternatives.\n//\n// Our approach differs in three ways. Adding fences to the timer functions\n// reduces variability due to instruction reordering, improving the timer\n// resolution to about 10 nanoseconds. However, shorter functions must still\n// be invoked repeatedly. For more realistic branch prediction performance,\n// we vary the input parameter according to a user-specified distribution.\n// Thus, instead of VaryInputs(Measure(Repeat(func))), we change the\n// loop nesting to Measure(Repeat(VaryInputs(func))). We also estimate the\n// central tendency of the measurement samples with the \"half sample mode\",\n// which is more robust to outliers and skewed data than the mean or median.\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include <stddef.h>\n#include <stdint.h>\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n\nnamespace highwayhash\n{\n\n// Argument to the function being measured (e.g. number of bytes to copy).\nusing FuncInput = size_t;\n\n// \"Proof of work\" returned by the function to ensure it is not elided.\nusing FuncOutput = uint64_t;\n\n// Function to measure (cannot use std::function in a restricted header).\n// Users either pass a function pointer or captureless lambda with this\n// signature, or use MeasureClosureDuration to convert a closure (e.g. lambda\n// with captures) to this kind of function pointer.\nusing Func = FuncOutput(*)(const void*, FuncInput);\n\n// Flat map of input -> durations[]. NOTE: durations are 'ticks' (tsc_timer.h);\n// convert to seconds via division by InvariantTicksPerSecond.\nclass DurationsForInputs\n{\npublic:\n  struct Item {\n    // The optional \"mul\" scaling factor is applied to median and variability\n    // (useful for reporting cycles per byte etc.)\n    void PrintMedianAndVariability(const double mul = 1.0);\n\n    FuncInput input;       // read-only (set by AddItem).\n    size_t num_durations;  // written so far: [0, max_durations).\n    float* durations;      // max_durations entries; points into all_durations.\n  };\n\n  // \"inputs\" is an array of \"num_inputs\" (not necessarily unique) arguments to\n  // \"func\". The values are chosen to maximize coverage of \"func\". The pointer\n  // must remain valid until after MeasureDurations. This represents a\n  // distribution, so a value's frequency should reflect its probability in the\n  // real application. Order does not matter; for example, a uniform\n  // distribution over [0, 4) could be represented as {3,0,2,1}. Repeating each\n  // value at least once ensures the leave-one-out distribution is closer to the\n  // original distribution, leading to more realistic results.\n  //\n  // \"max_durations\" is the number of duration samples to measure for each\n  // unique input value. Larger values decrease variability.\n  //\n  // Runtime is proportional to \"num_inputs\" * #unique * \"max_durations\".\n  DurationsForInputs(const FuncInput* inputs, const size_t num_inputs,\n                     const size_t max_durations);\n  ~DurationsForInputs();\n\n  // Adds an item with the given \"input\" and \"sample\". Must only be called once\n  // per unique \"input\" value.\n  void AddItem(const FuncInput input, const float sample);\n\n  // Adds \"sample\" to an already existing Item with the given \"input\".\n  void AddSample(const FuncInput input, const float sample);\n\n  // Allow direct inspection of items[0..num_items-1] because accessor or\n  // ForeachItem functions are unsafe in a restricted header.\n  Item* items;       // owned by this class, do not allocate/free.\n  size_t num_items;  // safe to reset to zero.\n\nprivate:\n  friend void MeasureDurations(Func, DurationsForInputs*, const uint8_t*);\n\n  const FuncInput* const inputs_;\n  const size_t num_inputs_;\n  const size_t max_durations_;\n  float* const all_durations_;\n};\n\n// Helper function to detect num_inputs from arrays.\ntemplate <size_t N>\nstatic HH_INLINE DurationsForInputs MakeDurationsForInputs(\n  const FuncInput(&inputs)[N], const size_t max_durations)\n{\n  return DurationsForInputs(&inputs[0], N, max_durations);\n}\n\n// Returns precise measurements of the number of ticks (see tsc_timer.h)\n// elapsed when calling \"func\" with each unique input value in \"input_map\",\n// taking special care to maintain realistic branch prediction hit rates.\n//\n// \"func\" returns a 'proof of work' to ensure its computations are not elided.\n// \"arg*\" are for use by MeasureClosureDurations.\nvoid MeasureDurations(const Func func, DurationsForInputs* input_map,\n                      const uint8_t* arg = nullptr);\n\nnamespace HH_TARGET_NAME\n{\n// Calls operator() of the given closure (lambda function).\ntemplate <class Closure>\nstatic FuncOutput CallClosure(const Closure* f, const FuncInput input)\n{\n  return (*f)(input);\n}\n}  // namespace HH_TARGET_NAME\n\n// Returns a function pointer that will be called with the address of \"closure\".\ntemplate <class Closure>\nstatic HH_INLINE Func MakeThunk(const Closure& closure)\n{\n  return reinterpret_cast<Func>(&HH_TARGET_NAME::CallClosure<Closure>);\n}\n\n// Same as MeasureDurations, except \"closure\" is typically a lambda function of\n// FuncInput -> FuncOutput with a capture list.\ntemplate <class Closure>\nvoid HH_INLINE MeasureClosureDurations(const Closure& closure,\n                                       DurationsForInputs* input_map)\n{\n  MeasureDurations(MakeThunk(closure), input_map,\n                   reinterpret_cast<const uint8_t*>(&closure));\n}\n\n}  // namespace highwayhash\n\n#endif  // HIGHWAYHASH_NANOBENCHMARK_H_\n"
  },
  {
    "path": "common/highwayhash/os_mac.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n// Created by Alexander Gryanko on 16/09/2017.\n\n#ifndef HIGHWAYHASH_OS_MAC_H_\n#define HIGHWAYHASH_OS_MAC_H_\n\n#ifndef HH_DISABLE_TARGET_SPECIFIC\n\n#include <mach/mach_types.h>\n#include <mach/thread_act.h>\n#include <pthread.h>\n#include <sys/sysctl.h>\n\n#include <cstdint>\n#include <cstdio>\n#include <cstring>\n\ntypedef unsigned long int cpu_mask;\n\n#define SYSCTL_CORE_COUNT \"machdep.cpu.thread_count\"\n#define NR_CPUS 512  // from the linux kernel limit\n#define NR_CPUBITS (8 * sizeof(cpu_mask))\n\nstruct cpu_set_t {\n  cpu_mask bits[NR_CPUS / NR_CPUBITS];\n};\n\nstatic inline void CPU_ZERO(cpu_set_t* set)\n{\n  memset(set, 0, sizeof(cpu_set_t));\n}\n\nstatic inline int CPU_ISSET(int cpu, const cpu_set_t* set)\n{\n  if (cpu < NR_CPUS) {\n    return (set->bits[cpu / NR_CPUBITS] & 1L << (cpu % NR_CPUBITS)) != 0;\n  }\n\n  return 0;\n}\n\nstatic inline void CPU_SET(int cpu, cpu_set_t* set)\n{\n  if (cpu < NR_CPUS) {\n    set->bits[cpu / NR_CPUBITS] |= 1L << (cpu % NR_CPUBITS);\n  }\n}\n\nint mac_getaffinity(cpu_set_t* set);\nint mac_setaffinity(cpu_set_t* set);\n\n#endif  // !HH_DISABLE_TARGET_SPECIFIC\n#endif  // HIGHWAYHASH_OS_MAC_H_\n"
  },
  {
    "path": "common/highwayhash/os_specific.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_OS_SPECIFIC_H_\n#define HIGHWAYHASH_OS_SPECIFIC_H_\n\n#include <vector>\n\nnamespace highwayhash\n{\n\n// Returns current wall-clock time [seconds].\ndouble Now();\n\n// Sets this thread's priority to the maximum. This should not be called on\n// single-core systems. Requires elevated permissions. No effect on Linux\n// because it increases runtime and variability (issue #19).\nvoid RaiseThreadPriority();\n\n// Returns CPU numbers in [0, N), where N is the number of bits in the\n// thread's initial affinity (unaffected by any SetThreadAffinity).\nstd::vector<int> AvailableCPUs();\n\n// Opaque.\nstruct ThreadAffinity;\n\n// Caller must free() the return value.\nThreadAffinity* GetThreadAffinity();\n\n// Restores a previous affinity returned by GetThreadAffinity.\nvoid SetThreadAffinity(ThreadAffinity* affinity);\n\n// Ensures the thread is running on the specified cpu, and no others.\n// Useful for reducing nanobenchmark variability (fewer context switches).\n// Uses SetThreadAffinity.\nvoid PinThreadToCPU(const int cpu);\n\n// Random choice of CPU avoids overloading any one core.\n// Uses SetThreadAffinity.\nvoid PinThreadToRandomCPU();\n\n}  // namespace highwayhash\n\n#endif  // HIGHWAYHASH_OS_SPECIFIC_H_\n"
  },
  {
    "path": "common/highwayhash/profiler.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_PROFILER_H_\n#define HIGHWAYHASH_PROFILER_H_\n\n// High precision, low overhead time measurements. Returns exact call counts and\n// total elapsed time for user-defined 'zones' (code regions, i.e. C++ scopes).\n//\n// Usage: add this header to BUILD srcs; instrument regions of interest:\n// { PROFILER_ZONE(\"name\"); /*code*/ } or\n// void FuncToMeasure() { PROFILER_FUNC; /*code*/ }.\n// After all threads have exited any zones, invoke PROFILER_PRINT_RESULTS() to\n// print call counts and average durations [CPU cycles] to stdout, sorted in\n// descending order of total duration.\n\n// Configuration settings:\n\n// If zero, this file has no effect and no measurements will be recorded.\n#ifndef PROFILER_ENABLED\n#define PROFILER_ENABLED 1\n#endif\n\n// How many mebibytes to allocate (if PROFILER_ENABLED) per thread that\n// enters at least one zone. Once this buffer is full, the thread will analyze\n// and discard packets, thus temporarily adding some observer overhead.\n// Each zone occupies 16 bytes.\n#ifndef PROFILER_THREAD_STORAGE\n#define PROFILER_THREAD_STORAGE 200ULL\n#endif\n\n#if PROFILER_ENABLED\n\n#define PROFILER_PRINT_OVERHEAD 0\n\n#include <algorithm>  // min/max\n#include <atomic>\n#include <cassert>\n#include <cstddef>  // ptrdiff_t\n#include <cstdint>\n#include <cstdio>\n#include <cstdlib>\n#include <cstring>  // memcpy\n#include <new>\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n\n// Non-portable aspects:\n// - SSE2 128-bit load/store (write-combining, UpdateOrAdd)\n// - RDTSCP timestamps (serializing, high-resolution)\n// - assumes string literals are stored within an 8 MiB range\n// - compiler-specific annotations (restrict, alignment, fences)\n#if HH_ARCH_X64\n#include <emmintrin.h>\n#if HH_MSC_VERSION\n#include <intrin.h>\n#else\n#include <x86intrin.h>\n#endif\n#endif\n\n#include \"highwayhash/robust_statistics.h\"\n#include \"highwayhash/tsc_timer.h\"\n\n#define PROFILER_CHECK(condition)                           \\\n  while (!(condition)) {                                    \\\n    printf(\"Profiler check failed at line %d\\n\", __LINE__); \\\n    abort();                                                \\\n  }\n\nnamespace highwayhash\n{\n\n// Upper bounds for various fixed-size data structures (guarded via assert):\n\n// How many threads can actually enter a zone (those that don't do not count).\n// Memory use is about kMaxThreads * PROFILER_THREAD_STORAGE MiB.\n// WARNING: a fiber library can spawn hundreds of threads.\nstatic constexpr size_t kMaxThreads = 128;\n\n// Maximum nesting of zones.\nstatic constexpr size_t kMaxDepth = 64;\n\n// Total number of zones.\nstatic constexpr size_t kMaxZones = 256;\n\n// Functions that depend on the cache line size.\nclass CacheAligned\n{\npublic:\n  static constexpr size_t kPointerSize = sizeof(void*);\n  static constexpr size_t kCacheLineSize = 64;\n\n  static void* Allocate(const size_t bytes)\n  {\n    char* const allocated = static_cast<char*>(malloc(bytes + kCacheLineSize));\n\n    if (allocated == nullptr) {\n      return nullptr;\n    }\n\n    const uintptr_t misalignment =\n      reinterpret_cast<uintptr_t>(allocated) & (kCacheLineSize - 1);\n    // malloc is at least kPointerSize aligned, so we can store the \"allocated\"\n    // pointer immediately before the aligned memory.\n    assert(misalignment % kPointerSize == 0);\n    char* const aligned = allocated + kCacheLineSize - misalignment;\n    memcpy(aligned - kPointerSize, &allocated, kPointerSize);\n    return aligned;\n  }\n\n  // Template allows freeing pointer-to-const.\n  template <typename T>\n  static void Free(T* aligned_pointer)\n  {\n    if (aligned_pointer == nullptr) {\n      return;\n    }\n\n    const char* const aligned = reinterpret_cast<const char*>(aligned_pointer);\n    assert(reinterpret_cast<uintptr_t>(aligned) % kCacheLineSize == 0);\n    char* allocated;\n    memcpy(&allocated, aligned - kPointerSize, kPointerSize);\n    assert(allocated <= aligned - kPointerSize);\n    assert(allocated >= aligned - kCacheLineSize);\n    free(allocated);\n  }\n\n#if HH_ARCH_X64\n  // Overwrites \"to\" without loading it into the cache (read-for-ownership).\n  template <typename T>\n  static void StreamCacheLine(const T* from_items, T* to_items)\n  {\n    const __m128i* const from = reinterpret_cast<const __m128i*>(from_items);\n    __m128i* const to = reinterpret_cast<__m128i*>(to_items);\n    HH_COMPILER_FENCE;\n    const __m128i v0 = _mm_load_si128(from + 0);\n    const __m128i v1 = _mm_load_si128(from + 1);\n    const __m128i v2 = _mm_load_si128(from + 2);\n    const __m128i v3 = _mm_load_si128(from + 3);\n    // Fences prevent the compiler from reordering loads/stores, which may\n    // interfere with write-combining.\n    HH_COMPILER_FENCE;\n    _mm_stream_si128(to + 0, v0);\n    _mm_stream_si128(to + 1, v1);\n    _mm_stream_si128(to + 2, v2);\n    _mm_stream_si128(to + 3, v3);\n    HH_COMPILER_FENCE;\n  }\n#endif\n};\n\n// Represents zone entry/exit events. Stores a full-resolution timestamp plus\n// an offset (representing zone name or identifying exit packets). POD.\nclass Packet\n{\npublic:\n  // If offsets do not fit, UpdateOrAdd will overrun our heap allocation\n  // (governed by kMaxZones). We have seen multi-megabyte offsets.\n  static constexpr size_t kOffsetBits = 25;\n  static constexpr uint64_t kOffsetBias = 1ULL << (kOffsetBits - 1);\n\n  // We need full-resolution timestamps; at an effective rate of 4 GHz,\n  // this permits 1 minute zone durations (for longer durations, split into\n  // multiple zones). Wraparound is handled by masking.\n  static constexpr size_t kTimestampBits = 64 - kOffsetBits;\n  static constexpr uint64_t kTimestampMask = (1ULL << kTimestampBits) - 1;\n\n  static Packet Make(const size_t biased_offset, const uint64_t timestamp)\n  {\n    assert(biased_offset < (1ULL << kOffsetBits));\n    Packet packet;\n    packet.bits_ =\n      (biased_offset << kTimestampBits) + (timestamp & kTimestampMask);\n    return packet;\n  }\n\n  uint64_t Timestamp() const\n  {\n    return bits_ & kTimestampMask;\n  }\n\n  size_t BiasedOffset() const\n  {\n    return (bits_ >> kTimestampBits);\n  }\n\nprivate:\n  uint64_t bits_;\n};\nstatic_assert(sizeof(Packet) == 8, \"Wrong Packet size\");\n\n// Returns the address of a string literal. Assuming zone names are also\n// literals and stored nearby, we can represent them as offsets, which are\n// faster to compute than hashes or even a static index.\n//\n// This function must not be static - each call (even from other translation\n// units) must return the same value.\ninline const char* StringOrigin()\n{\n  // Chosen such that no zone name is a prefix nor suffix of this string\n  // to ensure they aren't merged (offset 0 identifies zone-exit packets).\n  static const char* string_origin = \"__#__\";\n  return string_origin - Packet::kOffsetBias;\n}\n\n// Representation of an active zone, stored in a stack. Used to deduct\n// child duration from the parent's self time. POD.\nstruct Node {\n  Packet packet;\n  uint64_t child_total;\n};\n\n// Holds statistics for all zones with the same name. POD.\nstruct Accumulator {\n  static constexpr size_t kNumCallBits = 64 - Packet::kOffsetBits;\n\n  uint64_t BiasedOffset() const\n  {\n    return num_calls >> kNumCallBits;\n  }\n  uint64_t NumCalls() const\n  {\n    return num_calls & ((1ULL << kNumCallBits) - 1);\n  }\n\n  // UpdateOrAdd relies upon this layout.\n  uint64_t num_calls = 0;  // upper bits = biased_offset.\n  uint64_t total_duration = 0;\n};\n#if HH_ARCH_X64\nstatic_assert(sizeof(Accumulator) == sizeof(__m128i), \"Wrong Accumulator size\");\n#endif\n\ntemplate <typename T>\ninline T ClampedSubtract(const T minuend, const T subtrahend)\n{\n  if (subtrahend > minuend) {\n    return 0;\n  }\n\n  return minuend - subtrahend;\n}\n\n// Per-thread call graph (stack) and Accumulator for each zone.\nclass Results\n{\npublic:\n  Results()\n  {\n    // Zero-initialize first accumulator to avoid a check for num_zones_ == 0.\n    memset(zones_, 0, sizeof(Accumulator));\n  }\n\n  // Used for computing overhead when this thread encounters its first Zone.\n  // This has no observable effect apart from increasing \"analyze_elapsed_\".\n  uint64_t ZoneDuration(const Packet* packets)\n  {\n    PROFILER_CHECK(depth_ == 0);\n    PROFILER_CHECK(num_zones_ == 0);\n    AnalyzePackets(packets, 2);\n    const uint64_t duration = zones_[0].total_duration;\n    zones_[0].num_calls = 0;\n    zones_[0].total_duration = 0;\n    PROFILER_CHECK(depth_ == 0);\n    num_zones_ = 0;\n    return duration;\n  }\n\n  void SetSelfOverhead(const uint64_t self_overhead)\n  {\n    self_overhead_ = self_overhead;\n  }\n\n  void SetChildOverhead(const uint64_t child_overhead)\n  {\n    child_overhead_ = child_overhead;\n  }\n\n  // Draw all required information from the packets, which can be discarded\n  // afterwards. Called whenever this thread's storage is full.\n  void AnalyzePackets(const Packet* packets, const size_t num_packets)\n  {\n    const uint64_t t0 = Start<uint64_t>();\n\n    for (size_t i = 0; i < num_packets; ++i) {\n      const Packet p = packets[i];\n\n      // Entering a zone\n      if (p.BiasedOffset() != Packet::kOffsetBias) {\n        assert(depth_ < kMaxDepth);\n        nodes_[depth_].packet = p;\n        nodes_[depth_].child_total = 0;\n        ++depth_;\n        continue;\n      }\n\n      assert(depth_ != 0);\n      const Node& node = nodes_[depth_ - 1];\n      // Masking correctly handles unsigned wraparound.\n      const uint64_t duration =\n        (p.Timestamp() - node.packet.Timestamp()) & Packet::kTimestampMask;\n      const uint64_t self_duration = ClampedSubtract(\n                                       duration, self_overhead_ + child_overhead_ + node.child_total);\n      UpdateOrAdd(node.packet.BiasedOffset(), 1, self_duration);\n      --depth_;\n\n      // Deduct this nested node's time from its parent's self_duration.\n      if (depth_ != 0) {\n        nodes_[depth_ - 1].child_total += duration + child_overhead_;\n      }\n    }\n\n    const uint64_t t1 = Stop<uint64_t>();\n    analyze_elapsed_ += t1 - t0;\n  }\n\n  // Incorporates results from another thread. Call after all threads have\n  // exited any zones.\n  void Assimilate(const Results& other)\n  {\n    const uint64_t t0 = Start<uint64_t>();\n    assert(depth_ == 0);\n    assert(other.depth_ == 0);\n\n    for (size_t i = 0; i < other.num_zones_; ++i) {\n      const Accumulator& zone = other.zones_[i];\n      UpdateOrAdd(zone.BiasedOffset(), zone.NumCalls(), zone.total_duration);\n    }\n\n    const uint64_t t1 = Stop<uint64_t>();\n    analyze_elapsed_ += t1 - t0 + other.analyze_elapsed_;\n  }\n\n  // Single-threaded.\n  void Print()\n  {\n    const uint64_t t0 = Start<uint64_t>();\n    MergeDuplicates();\n    // Sort by decreasing total (self) cost.\n    std::sort(zones_, zones_ + num_zones_,\n    [](const Accumulator & r1, const Accumulator & r2) {\n      return r1.total_duration > r2.total_duration;\n    });\n    const char* string_origin = StringOrigin();\n\n    for (size_t i = 0; i < num_zones_; ++i) {\n      const Accumulator& r = zones_[i];\n      const uint64_t num_calls = r.NumCalls();\n      printf(\"%40s: %10zu x %15zu = %15zu\\n\", string_origin + r.BiasedOffset(),\n             num_calls, r.total_duration / num_calls, r.total_duration);\n    }\n\n    const uint64_t t1 = Stop<uint64_t>();\n    analyze_elapsed_ += t1 - t0;\n    printf(\"Total clocks during analysis: %zu\\n\", analyze_elapsed_);\n  }\n\nprivate:\n#if HH_ARCH_X64\n  static bool SameOffset(const __m128i& zone, const size_t biased_offset)\n  {\n    const uint64_t num_calls = _mm_cvtsi128_si64(zone);\n    return (num_calls >> Accumulator::kNumCallBits) == biased_offset;\n  }\n#endif\n\n  // Updates an existing Accumulator (uniquely identified by biased_offset) or\n  // adds one if this is the first time this thread analyzed that zone.\n  // Uses a self-organizing list data structure, which avoids dynamic memory\n  // allocations and is far faster than unordered_map. Loads, updates and\n  // stores the entire Accumulator with vector instructions.\n  void UpdateOrAdd(const size_t biased_offset, const uint64_t num_calls,\n                   const uint64_t duration)\n  {\n    assert(biased_offset < (1ULL << Packet::kOffsetBits));\n#if HH_ARCH_X64\n    const __m128i num_calls_64 = _mm_cvtsi64_si128(num_calls);\n    const __m128i duration_64 = _mm_cvtsi64_si128(duration);\n    const __m128i add_duration_call =\n      _mm_unpacklo_epi64(num_calls_64, duration_64);\n    __m128i* const HH_RESTRICT zones = reinterpret_cast<__m128i*>(zones_);\n    // Special case for first zone: (maybe) update, without swapping.\n    __m128i prev = _mm_load_si128(zones);\n\n    if (SameOffset(prev, biased_offset)) {\n      prev = _mm_add_epi64(prev, add_duration_call);\n      assert(SameOffset(prev, biased_offset));\n      _mm_store_si128(zones, prev);\n      return;\n    }\n\n    // Look for a zone with the same offset.\n    for (size_t i = 1; i < num_zones_; ++i) {\n      __m128i zone = _mm_load_si128(zones + i);\n\n      if (SameOffset(zone, biased_offset)) {\n        zone = _mm_add_epi64(zone, add_duration_call);\n        assert(SameOffset(zone, biased_offset));\n        // Swap with predecessor (more conservative than move to front,\n        // but at least as successful).\n        _mm_store_si128(zones + i - 1, zone);\n        _mm_store_si128(zones + i, prev);\n        return;\n      }\n\n      prev = zone;\n    }\n\n    // Not found; create a new Accumulator.\n    const __m128i biased_offset_64 = _mm_slli_epi64(\n                                       _mm_cvtsi64_si128(biased_offset), Accumulator::kNumCallBits);\n    const __m128i zone = _mm_add_epi64(biased_offset_64, add_duration_call);\n    assert(SameOffset(zone, biased_offset));\n    assert(num_zones_ < kMaxZones);\n    _mm_store_si128(zones + num_zones_, zone);\n    ++num_zones_;\n#else\n\n    // Special case for first zone: (maybe) update, without swapping.\n    if (zones_[0].BiasedOffset() == biased_offset) {\n      zones_[0].total_duration += duration;\n      zones_[0].num_calls += num_calls;\n      assert(zones_[0].BiasedOffset() == biased_offset);\n      return;\n    }\n\n    // Look for a zone with the same offset.\n    for (size_t i = 1; i < num_zones_; ++i) {\n      if (zones_[i].BiasedOffset() == biased_offset) {\n        zones_[i].total_duration += duration;\n        zones_[i].num_calls += num_calls;\n        assert(zones_[i].BiasedOffset() == biased_offset);\n        // Swap with predecessor (more conservative than move to front,\n        // but at least as successful).\n        const Accumulator prev = zones_[i - 1];\n        zones_[i - 1] = zones_[i];\n        zones_[i] = prev;\n        return;\n      }\n    }\n\n    // Not found; create a new Accumulator.\n    assert(num_zones_ < kMaxZones);\n    Accumulator* HH_RESTRICT zone = zones_ + num_zones_;\n    zone->num_calls = (biased_offset << Accumulator::kNumCallBits) + num_calls;\n    zone->total_duration = duration;\n    assert(zone->BiasedOffset() == biased_offset);\n    ++num_zones_;\n#endif\n  }\n\n  // Each instantiation of a function template seems to get its own copy of\n  // __func__ and GCC doesn't merge them. An N^2 search for duplicates is\n  // acceptable because we only expect a few dozen zones.\n  void MergeDuplicates()\n  {\n    const char* string_origin = StringOrigin();\n\n    for (size_t i = 0; i < num_zones_; ++i) {\n      const size_t biased_offset = zones_[i].BiasedOffset();\n      const char* name = string_origin + biased_offset;\n      // Separate num_calls from biased_offset so we can add them together.\n      uint64_t num_calls = zones_[i].NumCalls();\n\n      // Add any subsequent duplicates to num_calls and total_duration.\n      for (size_t j = i + 1; j < num_zones_;) {\n        if (!strcmp(name, string_origin + zones_[j].BiasedOffset())) {\n          num_calls += zones_[j].NumCalls();\n          zones_[i].total_duration += zones_[j].total_duration;\n          // Fill hole with last item.\n          zones_[j] = zones_[--num_zones_];\n        } else {  // Name differed, try next Accumulator.\n          ++j;\n        }\n      }\n\n      assert(num_calls < (1ULL << Accumulator::kNumCallBits));\n      // Re-pack regardless of whether any duplicates were found.\n      zones_[i].num_calls =\n        (biased_offset << Accumulator::kNumCallBits) + num_calls;\n    }\n  }\n\n  uint64_t analyze_elapsed_ = 0;\n  uint64_t self_overhead_ = 0;\n  uint64_t child_overhead_ = 0;\n\n  size_t depth_ = 0;      // Number of active zones.\n  size_t num_zones_ = 0;  // Number of retired zones.\n\n  HH_ALIGNAS(64) Node nodes_[kMaxDepth];         // Stack\n  HH_ALIGNAS(64) Accumulator zones_[kMaxZones];  // Self-organizing list\n};\n\n// Per-thread packet storage, allocated via CacheAligned.\nclass ThreadSpecific\n{\n  static constexpr size_t kBufferCapacity =\n    CacheAligned::kCacheLineSize / sizeof(Packet);\n\npublic:\n  // \"name\" is used to sanity-check offsets fit in kOffsetBits.\n  explicit ThreadSpecific(const char* name)\n    : packets_(static_cast<Packet*>(\n                 CacheAligned::Allocate(PROFILER_THREAD_STORAGE << 20))),\n      num_packets_(0),\n      max_packets_(PROFILER_THREAD_STORAGE << 17),\n      string_origin_(StringOrigin())\n  {\n    // Even in optimized builds (with NDEBUG), verify that this zone's name\n    // offset fits within the allotted space. If not, UpdateOrAdd is likely to\n    // overrun zones_[]. We also assert(), but users often do not run debug\n    // builds. Checking here on the cold path (only reached once per thread)\n    // is cheap, but it only covers one zone.\n    const size_t biased_offset = name - string_origin_;\n    PROFILER_CHECK(biased_offset <= (1ULL << Packet::kOffsetBits));\n  }\n\n  ~ThreadSpecific()\n  {\n    CacheAligned::Free(packets_);\n  }\n\n  // Depends on Zone => defined below.\n  void ComputeOverhead();\n\n  void WriteEntry(const char* name, const uint64_t timestamp)\n  {\n    const size_t biased_offset = name - string_origin_;\n    Write(Packet::Make(biased_offset, timestamp));\n  }\n\n  void WriteExit(const uint64_t timestamp)\n  {\n    const size_t biased_offset = Packet::kOffsetBias;\n    Write(Packet::Make(biased_offset, timestamp));\n  }\n\n  void AnalyzeRemainingPackets()\n  {\n#if HH_ARCH_X64\n    // Ensures prior weakly-ordered streaming stores are globally visible.\n    _mm_sfence();\n\n    // Storage full => empty it.\n    if (num_packets_ + buffer_size_ > max_packets_) {\n      results_.AnalyzePackets(packets_, num_packets_);\n      num_packets_ = 0;\n    }\n\n    memcpy(packets_ + num_packets_, buffer_, buffer_size_ * sizeof(Packet));\n    num_packets_ += buffer_size_;\n#endif\n    results_.AnalyzePackets(packets_, num_packets_);\n    num_packets_ = 0;\n  }\n\n  Results& GetResults()\n  {\n    return results_;\n  }\n\nprivate:\n  // Write packet to buffer/storage, emptying them as needed.\n  void Write(const Packet packet)\n  {\n#if HH_ARCH_X64\n\n    // Buffer full => copy to storage.\n    if (buffer_size_ == kBufferCapacity) {\n      // Storage full => empty it.\n      if (num_packets_ + kBufferCapacity > max_packets_) {\n        results_.AnalyzePackets(packets_, num_packets_);\n        num_packets_ = 0;\n      }\n\n      // This buffering halves observer overhead and decreases the overall\n      // runtime by about 3%.\n      CacheAligned::StreamCacheLine(buffer_, packets_ + num_packets_);\n      num_packets_ += kBufferCapacity;\n      buffer_size_ = 0;\n    }\n\n    buffer_[buffer_size_] = packet;\n    ++buffer_size_;\n#else\n\n    // Write directly to storage.\n    if (num_packets_ >= max_packets_) {\n      results_.AnalyzePackets(packets_, num_packets_);\n      num_packets_ = 0;\n    }\n\n    packets_[num_packets_] = packet;\n    ++num_packets_;\n#endif\n  }\n\n  // Write-combining buffer to avoid cache pollution. Must be the first\n  // non-static member to ensure cache-line alignment.\n#if HH_ARCH_X64\n  Packet buffer_[kBufferCapacity];\n  size_t buffer_size_ = 0;\n#endif\n\n  // Contiguous storage for zone enter/exit packets.\n  Packet* const HH_RESTRICT packets_;\n  size_t num_packets_;\n  const size_t max_packets_;\n  // Cached here because we already read this cache line on zone entry/exit.\n  const char* HH_RESTRICT string_origin_;\n  Results results_;\n};\n\nclass ThreadList\n{\npublic:\n  // Thread-safe.\n  void Add(ThreadSpecific* const ts)\n  {\n    const uint32_t index = num_threads_.fetch_add(1);\n    PROFILER_CHECK(index < kMaxThreads);\n    threads_[index] = ts;\n  }\n\n  // Single-threaded.\n  void PrintResults()\n  {\n    const uint32_t num_threads = num_threads_.load();\n\n    for (uint32_t i = 0; i < num_threads; ++i) {\n      threads_[i]->AnalyzeRemainingPackets();\n    }\n\n    // Combine all threads into a single Result.\n    for (uint32_t i = 1; i < num_threads; ++i) {\n      threads_[0]->GetResults().Assimilate(threads_[i]->GetResults());\n    }\n\n    if (num_threads != 0) {\n      threads_[0]->GetResults().Print();\n    }\n  }\n\nprivate:\n  // Owning pointers.\n  HH_ALIGNAS(64) ThreadSpecific* threads_[kMaxThreads];\n  std::atomic<uint32_t> num_threads_{0};\n};\n\n// RAII zone enter/exit recorder constructed by the ZONE macro; also\n// responsible for initializing ThreadSpecific.\nclass Zone\n{\npublic:\n  // \"name\" must be a string literal (see StringOrigin).\n  HH_NOINLINE explicit Zone(const char* name)\n  {\n    HH_COMPILER_FENCE;\n    ThreadSpecific* HH_RESTRICT thread_specific = StaticThreadSpecific();\n\n    if (HH_UNLIKELY(thread_specific == nullptr)) {\n      void* mem = CacheAligned::Allocate(sizeof(ThreadSpecific));\n      thread_specific = new(mem) ThreadSpecific(name);\n      // Must happen before ComputeOverhead, which re-enters this ctor.\n      Threads().Add(thread_specific);\n      StaticThreadSpecific() = thread_specific;\n      thread_specific->ComputeOverhead();\n    }\n\n    // (Capture timestamp ASAP, not inside WriteEntry.)\n    HH_COMPILER_FENCE;\n    const uint64_t timestamp = Start<uint64_t>();\n    thread_specific->WriteEntry(name, timestamp);\n  }\n\n  HH_NOINLINE ~Zone()\n  {\n    HH_COMPILER_FENCE;\n    const uint64_t timestamp = Stop<uint64_t>();\n    StaticThreadSpecific()->WriteExit(timestamp);\n    HH_COMPILER_FENCE;\n  }\n\n  // Call exactly once after all threads have exited all zones.\n  static void PrintResults()\n  {\n    Threads().PrintResults();\n  }\n\nprivate:\n  // Returns reference to the thread's ThreadSpecific pointer (initially null).\n  // Function-local static avoids needing a separate definition.\n  static ThreadSpecific*& StaticThreadSpecific()\n  {\n    static thread_local ThreadSpecific* thread_specific;\n    return thread_specific;\n  }\n\n  // Returns the singleton ThreadList. Non time-critical.\n  static ThreadList& Threads()\n  {\n    static ThreadList threads_;\n    return threads_;\n  }\n};\n\n// Creates a zone starting from here until the end of the current scope.\n// Timestamps will be recorded when entering and exiting the zone.\n// \"name\" must be a string literal, which is ensured by merging with \"\".\n#define PROFILER_ZONE(name)           \\\n  HH_COMPILER_FENCE;                  \\\n  const Zone zone(\"\" name); \\\n  HH_COMPILER_FENCE\n\n// Creates a zone for an entire function (when placed at its beginning).\n// Shorter/more convenient than ZONE.\n#define PROFILER_FUNC                  \\\n  HH_COMPILER_FENCE;                   \\\n  const Zone zone(__func__); \\\n  HH_COMPILER_FENCE\n\n#define PROFILER_PRINT_RESULTS Zone::PrintResults\n\ninline void ThreadSpecific::ComputeOverhead()\n{\n  // Delay after capturing timestamps before/after the actual zone runs. Even\n  // with frequency throttling disabled, this has a multimodal distribution,\n  // including 32, 34, 48, 52, 59, 62.\n  uint64_t self_overhead;\n  {\n    const size_t kNumSamples = 32;\n    uint32_t samples[kNumSamples];\n\n    for (size_t idx_sample = 0; idx_sample < kNumSamples; ++idx_sample) {\n      const size_t kNumDurations = 1024;\n      uint32_t durations[kNumDurations];\n\n      for (size_t idx_duration = 0; idx_duration < kNumDurations;\n           ++idx_duration) {\n        {\n          PROFILER_ZONE(\"Dummy Zone (never shown)\");\n        }\n#if HH_ARCH_X64\n        const uint64_t duration = results_.ZoneDuration(buffer_);\n        buffer_size_ = 0;\n#else\n        const uint64_t duration = results_.ZoneDuration(packets_);\n        num_packets_ = 0;\n#endif\n        durations[idx_duration] = static_cast<uint32_t>(duration);\n        PROFILER_CHECK(num_packets_ == 0);\n      }\n\n      CountingSort(durations, durations + kNumDurations);\n      samples[idx_sample] = Mode(durations, kNumDurations);\n    }\n\n    // Median.\n    CountingSort(samples, samples + kNumSamples);\n    self_overhead = samples[kNumSamples / 2];\n#if PROFILER_PRINT_OVERHEAD\n    printf(\"Overhead: %zu\\n\", self_overhead);\n#endif\n    results_.SetSelfOverhead(self_overhead);\n  }\n  // Delay before capturing start timestamp / after end timestamp.\n  const size_t kNumSamples = 32;\n  uint32_t samples[kNumSamples];\n\n  for (size_t idx_sample = 0; idx_sample < kNumSamples; ++idx_sample) {\n    const size_t kNumDurations = 16;\n    uint32_t durations[kNumDurations];\n\n    for (size_t idx_duration = 0; idx_duration < kNumDurations;\n         ++idx_duration) {\n      const size_t kReps = 10000;\n      // Analysis time should not be included => must fit within buffer.\n      PROFILER_CHECK(kReps * 2 < max_packets_);\n#if HH_ARCH_X64\n      _mm_mfence();\n#endif\n      const uint64_t t0 = Start<uint64_t>();\n\n      for (size_t i = 0; i < kReps; ++i) {\n        PROFILER_ZONE(\"Dummy\");\n      }\n\n#if HH_ARCH_X64\n      _mm_sfence();\n#endif\n      const uint64_t t1 = Stop<uint64_t>();\n#if HH_ARCH_X64\n      PROFILER_CHECK(num_packets_ + buffer_size_ == kReps * 2);\n      buffer_size_ = 0;\n#else\n      PROFILER_CHECK(num_packets_ == kReps * 2);\n#endif\n      num_packets_ = 0;\n      const uint64_t avg_duration = (t1 - t0 + kReps / 2) / kReps;\n      durations[idx_duration] =\n        static_cast<uint32_t>(ClampedSubtract(avg_duration, self_overhead));\n    }\n\n    CountingSort(durations, durations + kNumDurations);\n    samples[idx_sample] = Mode(durations, kNumDurations);\n  }\n\n  CountingSort(samples, samples + kNumSamples);\n  const uint64_t child_overhead = samples[9 * kNumSamples / 10];\n#if PROFILER_PRINT_OVERHEAD\n  printf(\"Child overhead: %zu\\n\", child_overhead);\n#endif\n  results_.SetChildOverhead(child_overhead);\n}\n\n}  // namespace highwayhash\n\n#else  // !PROFILER_ENABLED\n#define PROFILER_ZONE(name)\n#define PROFILER_FUNC\n#define PROFILER_PRINT_RESULTS()\n#endif\n\n#endif  // HIGHWAYHASH_PROFILER_H_\n"
  },
  {
    "path": "common/highwayhash/robust_statistics.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_ROBUST_STATISTICS_H_\n#define HIGHWAYHASH_ROBUST_STATISTICS_H_\n\n// Robust statistics: Mode, Median, MedianAbsoluteDeviation.\n\n#include <stddef.h>\n#include <algorithm>\n#include <cassert>\n#include <cmath>\n#include <limits>\n#include <vector>\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n\nnamespace highwayhash\n{\n\n// @return i in [idx_begin, idx_begin + half_count) that minimizes\n// sorted[i + half_count] - sorted[i].\ntemplate <typename T>\nsize_t MinRange(const T* const HH_RESTRICT sorted, const size_t idx_begin,\n                const size_t half_count)\n{\n  T min_range = std::numeric_limits<T>::max();\n  size_t min_idx = 0;\n\n  for (size_t idx = idx_begin; idx < idx_begin + half_count; ++idx) {\n    assert(sorted[idx] <= sorted[idx + half_count]);\n    const T range = sorted[idx + half_count] - sorted[idx];\n\n    if (range < min_range) {\n      min_range = range;\n      min_idx = idx;\n    }\n  }\n\n  return min_idx;\n}\n\n// Returns an estimate of the mode by calling MinRange on successively\n// halved intervals. \"sorted\" must be in ascending order. This is the\n// Half Sample Mode estimator proposed by Bickel in \"On a fast, robust\n// estimator of the mode\", with complexity O(N log N). The mode is less\n// affected by outliers in highly-skewed distributions than the median.\n// The averaging operation below assumes \"T\" is an unsigned integer type.\ntemplate <typename T>\nT Mode(const T* const HH_RESTRICT sorted, const size_t num_values)\n{\n  size_t idx_begin = 0;\n  size_t half_count = num_values / 2;\n\n  while (half_count > 1) {\n    idx_begin = MinRange(sorted, idx_begin, half_count);\n    half_count >>= 1;\n  }\n\n  const T x = sorted[idx_begin + 0];\n\n  if (half_count == 0) {\n    return x;\n  }\n\n  assert(half_count == 1);\n  const T average = (x + sorted[idx_begin + 1] + 1) / 2;\n  return average;\n}\n\n// Sorts integral values in ascending order. About 3x faster than std::sort for\n// input distributions with very few unique values.\ntemplate <class T>\nvoid CountingSort(T* begin, T* end)\n{\n  // Unique values and their frequency (similar to flat_map).\n  using Unique = std::pair<T, int>;\n  std::vector<Unique> unique;\n\n  for (const T* p = begin; p != end; ++p) {\n    const T value = *p;\n    const auto pos =\n      std::find_if(unique.begin(), unique.end(),\n    [value](const Unique & u) {\n      return u.first == value;\n    });\n\n    if (pos == unique.end()) {\n      unique.push_back(std::make_pair(*p, 1));\n    } else {\n      ++pos->second;\n    }\n  }\n\n  // Sort in ascending order of value (pair.first).\n  std::sort(unique.begin(), unique.end());\n  // Write that many copies of each unique value to the array.\n  T* HH_RESTRICT p = begin;\n\n  for (const auto& value_count : unique) {\n    std::fill(p, p + value_count.second, value_count.first);\n    p += value_count.second;\n  }\n\n  assert(p == end);\n}\n\n// Returns the median value. Side effect: sorts \"samples\".\ntemplate <typename T>\nT Median(std::vector<T>* samples)\n{\n  assert(!samples->empty());\n  std::sort(samples->begin(), samples->end());\n  const size_t half = samples->size() / 2;\n\n  // Odd count: return middle\n  if (samples->size() % 2) {\n    return (*samples)[half];\n  }\n\n  // Even count: return average of middle two.\n  return ((*samples)[half] + (*samples)[half - 1]) / 2;\n}\n\n// Returns a robust measure of variability.\ntemplate <typename T>\nT MedianAbsoluteDeviation(const std::vector<T>& samples, const T median)\n{\n  assert(!samples.empty());\n  std::vector<T> abs_deviations;\n  abs_deviations.reserve(samples.size());\n\n  for (const T sample : samples) {\n    abs_deviations.push_back(std::abs(sample - median));\n  }\n\n  return Median(&abs_deviations);\n}\n\n}  // namespace highwayhash\n\n#endif  // HIGHWAYHASH_ROBUST_STATISTICS_H_\n"
  },
  {
    "path": "common/highwayhash/scalar.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_SCALAR_H_\n#define HIGHWAYHASH_SCALAR_H_\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include <stddef.h>  // size_t\n#include <stdint.h>\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n\nnamespace highwayhash\n{\n// To prevent ODR violations when including this from multiple translation\n// units (TU) that are compiled with different flags, the contents must reside\n// in a namespace whose name is unique to the TU. NOTE: this behavior is\n// incompatible with precompiled modules and requires textual inclusion instead.\nnamespace HH_TARGET_NAME\n{\n\n// Single-lane \"vector\" type with the same interface as V128/Scalar. Allows the\n// same client template to generate both SIMD and portable code.\ntemplate <typename Type>\nclass Scalar\n{\npublic:\n  struct Intrinsic {\n    Type t;\n  };\n\n  using T = Type;\n  static constexpr size_t N = 1;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE Scalar() {}\n\n  HH_INLINE explicit Scalar(const T t) : v_(t) {}\n\n  HH_INLINE Scalar(const Scalar<T>& other) : v_(other.v_) {}\n\n  HH_INLINE Scalar& operator=(const Scalar<T>& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE Scalar(const Intrinsic& v) : v_(v.t) {}\n  HH_INLINE Scalar& operator=(const Intrinsic& v)\n  {\n    v_ = v.t;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return {v_};\n  }\n\n  HH_INLINE Scalar operator==(const Scalar& other) const\n  {\n    Scalar eq;\n    eq.FillWithByte(v_ == other.v_ ? 0xFF : 0x00);\n    return eq;\n  }\n  HH_INLINE Scalar operator<(const Scalar& other) const\n  {\n    Scalar lt;\n    lt.FillWithByte(v_ < other.v_ ? 0xFF : 0x00);\n    return lt;\n  }\n  HH_INLINE Scalar operator>(const Scalar& other) const\n  {\n    Scalar gt;\n    gt.FillWithByte(v_ > other.v_ ? 0xFF : 0x00);\n    return gt;\n  }\n\n  HH_INLINE Scalar& operator*=(const Scalar& other)\n  {\n    v_ *= other.v_;\n    return *this;\n  }\n  HH_INLINE Scalar& operator/=(const Scalar& other)\n  {\n    v_ /= other.v_;\n    return *this;\n  }\n  HH_INLINE Scalar& operator+=(const Scalar& other)\n  {\n    v_ += other.v_;\n    return *this;\n  }\n  HH_INLINE Scalar& operator-=(const Scalar& other)\n  {\n    v_ -= other.v_;\n    return *this;\n  }\n\n  HH_INLINE Scalar& operator&=(const Scalar& other)\n  {\n    v_ &= other.v_;\n    return *this;\n  }\n  HH_INLINE Scalar& operator|=(const Scalar& other)\n  {\n    v_ |= other.v_;\n    return *this;\n  }\n  HH_INLINE Scalar& operator^=(const Scalar& other)\n  {\n    v_ ^= other.v_;\n    return *this;\n  }\n\n  HH_INLINE Scalar& operator<<=(const int count)\n  {\n    // In C, int64_t << 64 is undefined, but we want to match the sensible\n    // behavior of SSE2 (zeroing).\n    if (count >= static_cast<int>(sizeof(T)) * 8) {\n      v_ = 0;\n    } else {\n      v_ <<= count;\n    }\n\n    return *this;\n  }\n\n  HH_INLINE Scalar& operator>>=(const int count)\n  {\n    if (count >= static_cast<int>(sizeof(T)) * 8) {\n      v_ = 0;\n    } else {\n      v_ >>= count;\n    }\n\n    return *this;\n  }\n\n  // For internal use only. We need to avoid memcpy/memset because this is a\n  // restricted header.\n  void FillWithByte(const unsigned char value)\n  {\n    unsigned char* bytes = reinterpret_cast<unsigned char*>(&v_);\n\n    for (size_t i = 0; i < sizeof(T); ++i) {\n      bytes[i] = value;\n    }\n  }\n\n  void CopyTo(unsigned char* HH_RESTRICT to_bytes) const\n  {\n    const unsigned char* from_bytes =\n      reinterpret_cast<const unsigned char*>(&v_);\n\n    for (size_t i = 0; i < sizeof(T); ++i) {\n      to_bytes[i] = from_bytes[i];\n    }\n  }\n\nprivate:\n  T v_;\n};\n\n// Non-member operators.\n\ntemplate <typename T>\nHH_INLINE Scalar<T> operator*(const Scalar<T>& left, const Scalar<T>& right)\n{\n  Scalar<T> t(left);\n  return t *= right;\n}\n\ntemplate <typename T>\nHH_INLINE Scalar<T> operator/(const Scalar<T>& left, const Scalar<T>& right)\n{\n  Scalar<T> t(left);\n  return t /= right;\n}\n\ntemplate <typename T>\nHH_INLINE Scalar<T> operator+(const Scalar<T>& left, const Scalar<T>& right)\n{\n  Scalar<T> t(left);\n  return t += right;\n}\n\ntemplate <typename T>\nHH_INLINE Scalar<T> operator-(const Scalar<T>& left, const Scalar<T>& right)\n{\n  Scalar<T> t(left);\n  return t -= right;\n}\n\ntemplate <typename T>\nHH_INLINE Scalar<T> operator&(const Scalar<T>& left, const Scalar<T>& right)\n{\n  Scalar<T> t(left);\n  return t &= right;\n}\n\ntemplate <typename T>\nHH_INLINE Scalar<T> operator|(const Scalar<T> left, const Scalar<T>& right)\n{\n  Scalar<T> t(left);\n  return t |= right;\n}\n\ntemplate <typename T>\nHH_INLINE Scalar<T> operator^(const Scalar<T>& left, const Scalar<T>& right)\n{\n  Scalar<T> t(left);\n  return t ^= right;\n}\n\ntemplate <typename T>\nHH_INLINE Scalar<T> operator<<(const Scalar<T>& v, const int count)\n{\n  Scalar<T> t(v);\n  return t <<= count;\n}\n\ntemplate <typename T>\nHH_INLINE Scalar<T> operator>>(const Scalar<T>& v, const int count)\n{\n  Scalar<T> t(v);\n  return t >>= count;\n}\n\nusing V1x8U = Scalar<uint8_t>;\nusing V1x16U = Scalar<uint16_t>;\nusing V1x16I = Scalar<int16_t>;\nusing V1x32U = Scalar<uint32_t>;\nusing V1x32I = Scalar<int32_t>;\nusing V1x64U = Scalar<uint64_t>;\nusing V1x32F = Scalar<float>;\nusing V1x64F = Scalar<double>;\n\n// Load/Store.\n\n// We differentiate between targets' vector types via template specialization.\n// Calling Load<V>(floats) is more natural than Load(V8x32F(), floats) and may\n// generate better code in unoptimized builds. Only declare the primary\n// templates to avoid needing mutual exclusion with vector128/256.\ntemplate <class V>\nHH_INLINE V Load(const typename V::T* const HH_RESTRICT from);\ntemplate <class V>\nHH_INLINE V LoadUnaligned(const typename V::T* const HH_RESTRICT from);\n\ntemplate <>\nHH_INLINE V1x8U Load<V1x8U>(const V1x8U::T* const HH_RESTRICT from)\n{\n  return V1x8U(*from);\n}\ntemplate <>\nHH_INLINE V1x16U Load<V1x16U>(const V1x16U::T* const HH_RESTRICT from)\n{\n  return V1x16U(*from);\n}\ntemplate <>\nHH_INLINE V1x16I Load<V1x16I>(const V1x16I::T* const HH_RESTRICT from)\n{\n  return V1x16I(*from);\n}\ntemplate <>\nHH_INLINE V1x32U Load<V1x32U>(const V1x32U::T* const HH_RESTRICT from)\n{\n  return V1x32U(*from);\n}\ntemplate <>\nHH_INLINE V1x32I Load<V1x32I>(const V1x32I::T* const HH_RESTRICT from)\n{\n  return V1x32I(*from);\n}\ntemplate <>\nHH_INLINE V1x64U Load<V1x64U>(const V1x64U::T* const HH_RESTRICT from)\n{\n  return V1x64U(*from);\n}\ntemplate <>\nHH_INLINE V1x32F Load<V1x32F>(const V1x32F::T* const HH_RESTRICT from)\n{\n  return V1x32F(*from);\n}\ntemplate <>\nHH_INLINE V1x64F Load<V1x64F>(const V1x64F::T* const HH_RESTRICT from)\n{\n  return V1x64F(*from);\n}\n\ntemplate <>\nHH_INLINE V1x8U LoadUnaligned<V1x8U>(const V1x8U::T* const HH_RESTRICT from)\n{\n  return V1x8U(*from);\n}\ntemplate <>\nHH_INLINE V1x16U\nLoadUnaligned<V1x16U>(const V1x16U::T* const HH_RESTRICT from)\n{\n  return V1x16U(*from);\n}\ntemplate <>\nHH_INLINE V1x16I\nLoadUnaligned<V1x16I>(const V1x16I::T* const HH_RESTRICT from)\n{\n  return V1x16I(*from);\n}\ntemplate <>\nHH_INLINE V1x32U\nLoadUnaligned<V1x32U>(const V1x32U::T* const HH_RESTRICT from)\n{\n  return V1x32U(*from);\n}\ntemplate <>\nHH_INLINE V1x32I\nLoadUnaligned<V1x32I>(const V1x32I::T* const HH_RESTRICT from)\n{\n  return V1x32I(*from);\n}\ntemplate <>\nHH_INLINE V1x64U\nLoadUnaligned<V1x64U>(const V1x64U::T* const HH_RESTRICT from)\n{\n  return V1x64U(*from);\n}\ntemplate <>\nHH_INLINE V1x32F\nLoadUnaligned<V1x32F>(const V1x32F::T* const HH_RESTRICT from)\n{\n  return V1x32F(*from);\n}\ntemplate <>\nHH_INLINE V1x64F\nLoadUnaligned<V1x64F>(const V1x64F::T* const HH_RESTRICT from)\n{\n  return V1x64F(*from);\n}\n\ntemplate <typename T>\nHH_INLINE void Store(const Scalar<T>& v, T* const HH_RESTRICT to)\n{\n  v.CopyTo(reinterpret_cast<unsigned char*>(to));\n}\n\ntemplate <typename T>\nHH_INLINE void StoreUnaligned(const Scalar<T>& v, T* const HH_RESTRICT to)\n{\n  v.CopyTo(reinterpret_cast<unsigned char*>(to));\n}\n\ntemplate <typename T>\nHH_INLINE void Stream(const Scalar<T>& v, T* const HH_RESTRICT to)\n{\n  v.CopyTo(reinterpret_cast<unsigned char*>(to));\n}\n\n// Miscellaneous functions.\n\ntemplate <typename T>\nHH_INLINE Scalar<T> RotateLeft(const Scalar<T>& v, const int count)\n{\n  constexpr size_t num_bits = sizeof(T) * 8;\n  return (v << count) | (v >> (num_bits - count));\n}\n\ntemplate <typename T>\nHH_INLINE Scalar<T> AndNot(const Scalar<T>& neg_mask, const Scalar<T>& values)\n{\n  return values & ~neg_mask;\n}\n\ntemplate <typename T>\nHH_INLINE Scalar<T> Select(const Scalar<T>& a, const Scalar<T>& b,\n                           const Scalar<T>& mask)\n{\n  const char* mask_bytes = reinterpret_cast<const char*>(&mask);\n  return (mask_bytes[sizeof(T) - 1] & 0x80) ? b : a;\n}\n\ntemplate <typename T>\nHH_INLINE Scalar<T> Min(const Scalar<T>& v0, const Scalar<T>& v1)\n{\n  return (v0 < v1) ? v0 : v1;\n}\n\ntemplate <typename T>\nHH_INLINE Scalar<T> Max(const Scalar<T>& v0, const Scalar<T>& v1)\n{\n  return (v0 < v1) ? v1 : v0;\n}\n\n}  // namespace HH_TARGET_NAME\n}  // namespace highwayhash\n\n#endif  // HIGHWAYHASH_SCALAR_H_\n"
  },
  {
    "path": "common/highwayhash/scalar_sip_tree_hash.h",
    "content": "// Copyright 2015 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_SCALAR_SIP_TREE_HASH_H_\n#define HIGHWAYHASH_SCALAR_SIP_TREE_HASH_H_\n\n// Scalar (non-vector/SIMD) version for comparison purposes.\n\n#include \"highwayhash/state_helpers.h\"\n\n#ifdef __cplusplus\nnamespace highwayhash\n{\nextern \"C\" {\n#endif\n\nHH_U64 ScalarSipTreeHash(const HH_U64(&key)[4], const char* bytes,\nconst HH_U64 size);\nHH_U64 ScalarSipTreeHash13(const HH_U64(&key)[4], const char* bytes,\nconst HH_U64 size);\n\n#ifdef __cplusplus\n}  // extern \"C\"\n}  // namespace highwayhash\n#endif\n\n#endif  // HIGHWAYHASH_SCALAR_SIP_TREE_HASH_H_\n"
  },
  {
    "path": "common/highwayhash/sip_hash.h",
    "content": "// Copyright 2016 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_SIP_HASH_H_\n#define HIGHWAYHASH_SIP_HASH_H_\n\n// Portable but fast SipHash implementation.\n\n#include <cstddef>\n#include <cstring>  // memcpy\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n#include \"highwayhash/endianess.h\"\n#include \"highwayhash/state_helpers.h\"\n\nnamespace highwayhash\n{\n\n// Paper: https://www.131002.net/siphash/siphash.pdf\ntemplate <int kUpdateIters, int kFinalizeIters>\nclass SipHashStateT\n{\npublic:\n  using Key = HH_U64[2];\n  static const size_t kPacketSize = sizeof(HH_U64);\n\n  explicit HH_INLINE SipHashStateT(const Key& key)\n  {\n    v0 = 0x736f6d6570736575ull ^ key[0];\n    v1 = 0x646f72616e646f6dull ^ key[1];\n    v2 = 0x6c7967656e657261ull ^ key[0];\n    v3 = 0x7465646279746573ull ^ key[1];\n  }\n\n  HH_INLINE void Update(const char* bytes)\n  {\n    HH_U64 packet;\n    memcpy(&packet, bytes, sizeof(packet));\n    packet = host_from_le64(packet);\n    v3 ^= packet;\n    Compress<kUpdateIters>();\n    v0 ^= packet;\n  }\n\n  HH_INLINE HH_U64 Finalize()\n  {\n    // Mix in bits to avoid leaking the key if all packets were zero.\n    v2 ^= 0xFF;\n    Compress<kFinalizeIters>();\n    return (v0 ^ v1) ^ (v2 ^ v3);\n  }\nprivate:\n  // Rotate a 64-bit value \"v\" left by N bits.\n  template <HH_U64 bits>\n  static HH_INLINE HH_U64 RotateLeft(const HH_U64 v)\n  {\n    const HH_U64 left = v << bits;\n    const HH_U64 right = v >> (64 - bits);\n    return left | right;\n  }\n\n  template <size_t rounds>\n  HH_INLINE void Compress()\n  {\n    for (size_t i = 0; i < rounds; ++i) {\n      // ARX network: add, rotate, exclusive-or.\n      v0 += v1;\n      v2 += v3;\n      v1 = RotateLeft<13>(v1);\n      v3 = RotateLeft<16>(v3);\n      v1 ^= v0;\n      v3 ^= v2;\n      v0 = RotateLeft<32>(v0);\n      v2 += v1;\n      v0 += v3;\n      v1 = RotateLeft<17>(v1);\n      v3 = RotateLeft<21>(v3);\n      v1 ^= v2;\n      v3 ^= v0;\n      v2 = RotateLeft<32>(v2);\n    }\n  }\n\n  HH_U64 v0;\n  HH_U64 v1;\n  HH_U64 v2;\n  HH_U64 v3;\n};\n\nusing SipHashState = SipHashStateT<2, 4>;\nusing SipHash13State = SipHashStateT<1, 3>;\n\n// Override the HighwayTreeHash padding scheme with that of SipHash so that\n// the hash output matches the known-good values in sip_hash_test.\ntemplate <>\nHH_INLINE void PaddedUpdate<SipHashState>(const HH_U64 size,\n    const char* remaining_bytes,\n    const HH_U64 remaining_size,\n    SipHashState* state)\n{\n  // Copy to avoid overrunning the input buffer.\n  char final_packet[SipHashState::kPacketSize] = {0};\n  memcpy(final_packet, remaining_bytes, remaining_size);\n  final_packet[SipHashState::kPacketSize - 1] = static_cast<char>(size & 0xFF);\n  state->Update(final_packet);\n}\n\ntemplate <>\nHH_INLINE void PaddedUpdate<SipHash13State>(const HH_U64 size,\n    const char* remaining_bytes,\n    const HH_U64 remaining_size,\n    SipHash13State* state)\n{\n  // Copy to avoid overrunning the input buffer.\n  char final_packet[SipHash13State::kPacketSize] = {0};\n  memcpy(final_packet, remaining_bytes, remaining_size);\n  final_packet[SipHash13State::kPacketSize - 1] =\n    static_cast<char>(size & 0xFF);\n  state->Update(final_packet);\n}\n\n// Fast, cryptographically strong pseudo-random function, e.g. for\n// deterministic/idempotent 'random' number generation. See also\n// README.md for information on resisting hash flooding attacks.\n//\n// Robust versus timing attacks because memory accesses are sequential\n// and the algorithm is branch-free. Compute time is proportional to the\n// number of 8-byte packets and about twice as fast as an sse41 implementation.\n//\n// \"key\" is a secret 128-bit key unknown to attackers.\n// \"bytes\" is the data to hash; ceil(size / 8) * 8 bytes are read.\n// Returns a 64-bit hash of the given data bytes, which are swapped on\n// big-endian CPUs so the return value is the same as on little-endian CPUs.\nstatic HH_INLINE HH_U64 SipHash(const SipHashState::Key& key, const char* bytes,\n                                const HH_U64 size)\n{\n  return ComputeHash<SipHashState>(key, bytes, size);\n}\n\n// Round-reduced SipHash version (1 update and 3 finalization rounds).\nstatic HH_INLINE HH_U64 SipHash13(const SipHash13State::Key& key,\n                                  const char* bytes, const HH_U64 size)\n{\n  return ComputeHash<SipHash13State>(key, bytes, size);\n}\n\ntemplate <int kNumLanes, int kUpdateIters, int kFinalizeIters>\nstatic HH_INLINE HH_U64 ReduceSipTreeHash(\n  const typename SipHashStateT<kUpdateIters, kFinalizeIters>::Key& key,\n  const uint64_t (&hashes)[kNumLanes])\n{\n  SipHashStateT<kUpdateIters, kFinalizeIters> state(key);\n\n  for (int i = 0; i < kNumLanes; ++i) {\n    state.Update(reinterpret_cast<const char*>(&hashes[i]));\n  }\n\n  return state.Finalize();\n}\n\n}  // namespace highwayhash\n\n#endif  // HIGHWAYHASH_SIP_HASH_H_\n"
  },
  {
    "path": "common/highwayhash/sip_tree_hash.h",
    "content": "// Copyright 2015 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_SIP_TREE_HASH_H_\n#define HIGHWAYHASH_SIP_TREE_HASH_H_\n\n#include \"highwayhash/state_helpers.h\"\n\n#ifdef __cplusplus\nnamespace highwayhash\n{\nextern \"C\" {\n#endif\n\n// Fast, cryptographically strong pseudo-random function. Useful for:\n// . hash tables holding attacker-controlled data. This function is\n//   immune to hash flooding DOS attacks because multi-collisions are\n//   infeasible to compute, provided the key remains secret.\n// . deterministic/idempotent 'random' number generation, e.g. for\n//   choosing a subset of items based on their contents.\n//\n// Robust versus timing attacks because memory accesses are sequential\n// and the algorithm is branch-free. Compute time is proportional to the\n// number of 8-byte packets and 1.5x faster than an sse41 implementation.\n// Requires an AVX-2 capable CPU.\n//\n// \"key\" is a secret 256-bit key unknown to attackers.\n// \"bytes\" is the data to hash (possibly unaligned).\n// \"size\" is the number of bytes to hash; exactly that many bytes are read.\n// Returns a 64-bit hash of the given data bytes.\nHH_U64 SipTreeHash(const HH_U64(&key)[4], const char* bytes,\nconst HH_U64 size);\n\nHH_U64 SipTreeHash13(const HH_U64(&key)[4], const char* bytes,\nconst HH_U64 size);\n\n#ifdef __cplusplus\n}  // extern \"C\"\n}  // namespace highwayhash\n#endif\n\n#endif  // HIGHWAYHASH_SIP_TREE_HASH_H_\n"
  },
  {
    "path": "common/highwayhash/state_helpers.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_STATE_H_\n#define HIGHWAYHASH_STATE_H_\n\n// Helper functions to split inputs into packets and call State::Update on each.\n\n#include <stdint.h>\n#include <cstddef>\n#include <cstring>\n#include <memory>\n\n#include \"highwayhash/compiler_specific.h\"\n\nnamespace highwayhash\n{\n\n// uint64_t is unsigned long on Linux; we need 'unsigned long long'\n// for interoperability with TensorFlow.\ntypedef unsigned long long HH_U64;  // NOLINT\n\n// Copies the remaining bytes to a zero-padded buffer, sets the upper byte to\n// size % 256 (always possible because this should only be called if the\n// total size is not a multiple of the packet size) and updates hash state.\n//\n// The padding scheme is essentially from SipHash, but permuted for the\n// convenience of AVX-2 masked loads. This function must use the same layout so\n// that the vector and scalar HighwayTreeHash have the same result.\n//\n// \"remaining_size\" is the number of accessible/remaining bytes\n// (size % kPacketSize).\n//\n// Primary template; the specialization for AVX-2 is faster. Intended as an\n// implementation detail, do not call directly.\ntemplate <class State>\nHH_INLINE void PaddedUpdate(const HH_U64 size, const char* remaining_bytes,\n                            const HH_U64 remaining_size, State* state)\n{\n  char final_packet[State::kPacketSize] HH_ALIGNAS(32) = {0};\n  // This layout matches the AVX-2 specialization in highway_tree_hash.h.\n  uint32_t packet4 = static_cast<uint32_t>(size) << 24;\n  const size_t remainder_mod4 = remaining_size & 3;\n\n  if (remainder_mod4 != 0) {\n    const char* final_bytes = remaining_bytes + remaining_size - remainder_mod4;\n    packet4 += static_cast<uint32_t>(final_bytes[0]);\n    const int idx1 = remainder_mod4 >> 1;\n    const int idx2 = remainder_mod4 - 1;\n    packet4 += static_cast<uint32_t>(final_bytes[idx1]) << 8;\n    packet4 += static_cast<uint32_t>(final_bytes[idx2]) << 16;\n  }\n\n  memcpy(final_packet, remaining_bytes, remaining_size - remainder_mod4);\n  memcpy(final_packet + State::kPacketSize - 4, &packet4, sizeof(packet4));\n  state->Update(final_packet);\n}\n\n// Updates hash state for every whole packet, and once more for the final\n// padded packet.\ntemplate <class State>\nHH_INLINE void UpdateState(const char* bytes, const HH_U64 size, State* state)\n{\n  // Feed entire packets.\n  const int kPacketSize = State::kPacketSize;\n  static_assert((kPacketSize & (kPacketSize - 1)) == 0, \"Size must be 2^i.\");\n  const size_t remainder = size & (kPacketSize - 1);\n  const size_t truncated_size = size - remainder;\n\n  for (size_t i = 0; i < truncated_size; i += kPacketSize) {\n    state->Update(bytes + i);\n  }\n\n  PaddedUpdate(size, bytes + truncated_size, remainder, state);\n}\n\n// Convenience function for updating with the bytes of a string.\ntemplate <class String, class State>\nHH_INLINE void UpdateState(const String& s, State* state)\n{\n  const char* bytes = reinterpret_cast<const char*>(s.data());\n  const size_t size = s.length() * sizeof(typename String::value_type);\n  UpdateState(bytes, size, state);\n}\n\n// Computes a hash of a byte array using the given hash State class.\n//\n// Example: const SipHashState::Key key = { 1, 2 }; char data[4];\n// ComputeHash<SipHashState>(key, data, sizeof(data));\n//\n// This function avoids duplicating Update/Finalize in every call site.\n// Callers wanting to combine multiple hashes should repeatedly UpdateState()\n// and only call State::Finalize once.\ntemplate <class State>\nHH_U64 ComputeHash(const typename State::Key& key, const char* bytes,\n                   const HH_U64 size)\n{\n  State state(key);\n  UpdateState(bytes, size, &state);\n  return state.Finalize();\n}\n\n// Computes a hash of a string's bytes using the given hash State class.\n//\n// Example: const SipHashState::Key key = { 1, 2 };\n// StringHasher<SipHashState>()(key, std::u16string(u\"abc\"));\n//\n// A struct with nested function template enables deduction of the String type.\ntemplate <class State>\nstruct StringHasher {\n  template <class String>\n  HH_U64 operator()(const typename State::Key& key, const String& s)\n  {\n    State state(key);\n    UpdateState(s, &state);\n    return state.Finalize();\n  }\n};\n\n}  // namespace highwayhash\n\n#endif  // HIGHWAYHASH_STATE_H_\n"
  },
  {
    "path": "common/highwayhash/tsc_timer.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_TSC_TIMER_H_\n#define HIGHWAYHASH_TSC_TIMER_H_\n\n// High-resolution (~10 ns) timestamps, using fences to prevent reordering and\n// ensure exactly the desired regions are measured.\n\n#include <stdint.h>\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n\n#if HH_ARCH_X64 && HH_MSC_VERSION\n#include <emmintrin.h>  // _mm_lfence\n#include <intrin.h>\n#endif\n\n#include <ctime>\n\nnamespace highwayhash\n{\n\n// Start/Stop return absolute timestamps and must be placed immediately before\n// and after the region to measure. We provide separate Start/Stop functions\n// because they use different fences.\n//\n// Background: RDTSC is not 'serializing'; earlier instructions may complete\n// after it, and/or later instructions may complete before it. 'Fences' ensure\n// regions' elapsed times are independent of such reordering. The only\n// documented unprivileged serializing instruction is CPUID, which acts as a\n// full fence (no reordering across it in either direction). Unfortunately\n// the latency of CPUID varies wildly (perhaps made worse by not initializing\n// its EAX input). Because it cannot reliably be deducted from the region's\n// elapsed time, it must not be included in the region to measure (i.e.\n// between the two RDTSC).\n//\n// The newer RDTSCP is sometimes described as serializing, but it actually\n// only serves as a half-fence with release semantics. Although all\n// instructions in the region will complete before the final timestamp is\n// captured, subsequent instructions may leak into the region and increase the\n// elapsed time. Inserting another fence after the final RDTSCP would prevent\n// such reordering without affecting the measured region.\n//\n// Fortunately, such a fence exists. The LFENCE instruction is only documented\n// to delay later loads until earlier loads are visible. However, Intel's\n// reference manual says it acts as a full fence (waiting until all earlier\n// instructions have completed, and delaying later instructions until it\n// completes). AMD assigns the same behavior to MFENCE.\n//\n// We need a fence before the initial RDTSC to prevent earlier instructions\n// from leaking into the region, and arguably another after RDTSC to avoid\n// region instructions from completing before the timestamp is recorded.\n// When surrounded by fences, the additional RDTSCP half-fence provides no\n// benefit, so the initial timestamp can be recorded via RDTSC, which has\n// lower overhead than RDTSCP because it does not read TSC_AUX. In summary,\n// we define Start = LFENCE/RDTSC/LFENCE; Stop = RDTSCP/LFENCE.\n//\n// Using Start+Start leads to higher variance and overhead than Stop+Stop.\n// However, Stop+Stop includes an LFENCE in the region measurements, which\n// adds a delay dependent on earlier loads. The combination of Start+Stop\n// is faster than Start+Start and more consistent than Stop+Stop because\n// the first LFENCE already delayed subsequent loads before the measured\n// region. This combination seems not to have been considered in prior work:\n// http://akaros.cs.berkeley.edu/lxr/akaros/kern/arch/x86/rdtsc_test.c\n//\n// Note: performance counters can measure 'exact' instructions-retired or\n// (unhalted) cycle counts. The RDPMC instruction is not serializing and also\n// requires fences. Unfortunately, it is not accessible on all OSes and we\n// prefer to avoid kernel-mode drivers. Performance counters are also affected\n// by several under/over-count errata, so we use the TSC instead.\n\n// Primary templates; must use one of the specializations.\ntemplate <typename T>\ninline T Start();\n\ntemplate <typename T>\ninline T Stop();\n\n// Returns a 64-bit timestamp in unit of 'ticks'; to convert to seconds,\n// divide by InvariantTicksPerSecond.\ntemplate <>\ninline uint64_t Start<uint64_t>()\n{\n  uint64_t t;\n#if HH_ARCH_PPC\n  asm volatile(\"mfspr %0, %1\" : \"=r\"(t) : \"i\"(268));\n#elif HH_ARCH_AARCH64\n  asm volatile(\"mrs %0, cntvct_el0\" : \"=r\"(t));\n#elif HH_ARCH_X64 && HH_MSC_VERSION\n  _mm_lfence();\n  HH_COMPILER_FENCE;\n  t = __rdtsc();\n  _mm_lfence();\n  HH_COMPILER_FENCE;\n#elif HH_ARCH_X64 && (HH_CLANG_VERSION || HH_GCC_VERSION)\n  asm volatile(\n    \"lfence\\n\\t\"\n    \"rdtsc\\n\\t\"\n    \"shl $32, %%rdx\\n\\t\"\n    \"or %%rdx, %0\\n\\t\"\n    \"lfence\"\n    : \"=a\"(t)\n    :\n    // \"memory\" avoids reordering. rdx = TSC >> 32.\n    // \"cc\" = flags modified by SHL.\n    : \"rdx\", \"memory\", \"cc\");\n#else\n  t = static_cast<uint64_t>(clock());\n#endif\n  return t;\n}\n\ntemplate <>\ninline uint64_t Stop<uint64_t>()\n{\n  uint64_t t;\n#if HH_ARCH_PPC\n  asm volatile(\"mfspr %0, %1\" : \"=r\"(t) : \"i\"(268));\n#elif HH_ARCH_AARCH64\n  asm volatile(\"mrs %0, cntvct_el0\" : \"=r\"(t));\n#elif HH_ARCH_X64 && HH_MSC_VERSION\n  HH_COMPILER_FENCE;\n  unsigned aux;\n  t = __rdtscp(&aux);\n  _mm_lfence();\n  HH_COMPILER_FENCE;\n#elif HH_ARCH_X64 && (HH_CLANG_VERSION || HH_GCC_VERSION)\n  // Use inline asm because __rdtscp generates code to store TSC_AUX (ecx).\n  asm volatile(\n    \"rdtscp\\n\\t\"\n    \"shl $32, %%rdx\\n\\t\"\n    \"or %%rdx, %0\\n\\t\"\n    \"lfence\"\n    : \"=a\"(t)\n    :\n    // \"memory\" avoids reordering. rcx = TSC_AUX. rdx = TSC >> 32.\n    // \"cc\" = flags modified by SHL.\n    : \"rcx\", \"rdx\", \"memory\", \"cc\");\n#else\n  t = static_cast<uint64_t>(clock());\n#endif\n  return t;\n}\n\n// Returns a 32-bit timestamp with about 4 cycles less overhead than\n// Start<uint64_t>. Only suitable for measuring very short regions because the\n// timestamp overflows about once a second.\ntemplate <>\ninline uint32_t Start<uint32_t>()\n{\n  uint32_t t;\n#if HH_ARCH_X64 && HH_MSC_VERSION\n  _mm_lfence();\n  HH_COMPILER_FENCE;\n  t = static_cast<uint32_t>(__rdtsc());\n  _mm_lfence();\n  HH_COMPILER_FENCE;\n#elif HH_ARCH_X64 && (HH_CLANG_VERSION || HH_GCC_VERSION)\n  asm volatile(\n    \"lfence\\n\\t\"\n    \"rdtsc\\n\\t\"\n    \"lfence\"\n    : \"=a\"(t)\n    :\n    // \"memory\" avoids reordering. rdx = TSC >> 32.\n    : \"rdx\", \"memory\");\n#else\n  t = static_cast<uint32_t>(Start<uint64_t>());\n#endif\n  return t;\n}\n\ntemplate <>\ninline uint32_t Stop<uint32_t>()\n{\n  uint32_t t;\n#if HH_ARCH_X64 && HH_MSC_VERSION\n  HH_COMPILER_FENCE;\n  unsigned aux;\n  t = static_cast<uint32_t>(__rdtscp(&aux));\n  _mm_lfence();\n  HH_COMPILER_FENCE;\n#elif HH_ARCH_X64 && (HH_CLANG_VERSION || HH_GCC_VERSION)\n  // Use inline asm because __rdtscp generates code to store TSC_AUX (ecx).\n  asm volatile(\n    \"rdtscp\\n\\t\"\n    \"lfence\"\n    : \"=a\"(t)\n    :\n    // \"memory\" avoids reordering. rcx = TSC_AUX. rdx = TSC >> 32.\n    : \"rcx\", \"rdx\", \"memory\");\n#else\n  t = static_cast<uint32_t>(Stop<uint64_t>());\n#endif\n  return t;\n}\n\n}  // namespace highwayhash\n\n#endif  // HIGHWAYHASH_TSC_TIMER_H_\n"
  },
  {
    "path": "common/highwayhash/vector128.h",
    "content": "// Copyright 2016 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_VECTOR128_H_\n#define HIGHWAYHASH_VECTOR128_H_\n\n// Defines SIMD vector classes (\"V2x64U\") with overloaded arithmetic operators:\n// const V2x64U masked_sum = (a + b) & m;\n// This is shorter and more readable than compiler intrinsics:\n// const __m128i masked_sum = _mm_and_si128(_mm_add_epi64(a, b), m);\n// There is typically no runtime cost for these abstractions.\n//\n// The naming convention is VNxBBT where N is the number of lanes, BB the\n// number of bits per lane and T is the lane type: unsigned integer (U),\n// signed integer (I), or floating-point (F).\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include <stddef.h>\n#include <stdint.h>\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n\n// For auto-dependency generation, we need to include all headers but not their\n// contents (otherwise compilation fails because -msse4.1 is not specified).\n#ifndef HH_DISABLE_TARGET_SPECIFIC\n\n// WARNING: smmintrin.h will also be included through immintrin.h in the AVX2\n// translation unit, which is compiled with different flags. This risks ODR\n// violations, and can cause crashes when functions are not inlined and the\n// linker selects the AVX2 version. Unfortunately this include cannot reside\n// within a namespace due to conflicts with other system headers. We need to\n// assume all the intrinsic functions (defined as static inline by Clang's\n// library and as extern inline by GCC) are in fact inlined. targets.bzl\n// generates a test that verifies this by detecting duplicate symbols.\n#include <smmintrin.h>  // SSE4.1\n\nnamespace highwayhash\n{\n// To prevent ODR violations when including this from multiple translation\n// units (TU) that are compiled with different flags, the contents must reside\n// in a namespace whose name is unique to the TU. NOTE: this behavior is\n// incompatible with precompiled modules and requires textual inclusion instead.\nnamespace HH_TARGET_NAME\n{\n\n// Primary template for 128-bit SSE4.1 vectors; only specializations are used.\ntemplate <typename T>\nclass V128 {};\n\ntemplate <>\nclass V128<uint8_t>\n{\npublic:\n  using Intrinsic = __m128i;\n  using T = uint8_t;\n  static constexpr size_t N = 16;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V128() {}\n\n  // Broadcasts i to all lanes (usually by loading from memory).\n  HH_INLINE explicit V128(T i) : v_(_mm_set1_epi8(i)) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V128(const V128& other) : v_(other.v_) {}\n  template <typename U>\n  HH_INLINE explicit V128(const V128<U>& other) : v_(other) {}\n  HH_INLINE V128& operator=(const V128& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V128(const Intrinsic& v) : v_(v) {}\n  HH_INLINE V128& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  // There are no greater-than comparison instructions for unsigned T.\n  HH_INLINE V128 operator==(const V128& other) const\n  {\n    return V128(_mm_cmpeq_epi8(v_, other.v_));\n  }\n\n  HH_INLINE V128& operator+=(const V128& other)\n  {\n    v_ = _mm_add_epi8(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator-=(const V128& other)\n  {\n    v_ = _mm_sub_epi8(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator&=(const V128& other)\n  {\n    v_ = _mm_and_si128(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator|=(const V128& other)\n  {\n    v_ = _mm_or_si128(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator^=(const V128& other)\n  {\n    v_ = _mm_xor_si128(v_, other.v_);\n    return *this;\n  }\n\nprivate:\n  Intrinsic v_;\n};\n\ntemplate <>\nclass V128<uint16_t>\n{\npublic:\n  using Intrinsic = __m128i;\n  using T = uint16_t;\n  static constexpr size_t N = 8;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V128() {}\n\n  // Lane 0 (p_0) is the lowest.\n  HH_INLINE V128(T p_7, T p_6, T p_5, T p_4, T p_3, T p_2, T p_1, T p_0)\n    : v_(_mm_set_epi16(p_7, p_6, p_5, p_4, p_3, p_2, p_1, p_0)) {}\n\n  // Broadcasts i to all lanes (usually by loading from memory).\n  HH_INLINE explicit V128(T i) : v_(_mm_set1_epi16(i)) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V128(const V128& other) : v_(other.v_) {}\n  template <typename U>\n  HH_INLINE explicit V128(const V128<U>& other) : v_(other) {}\n  HH_INLINE V128& operator=(const V128& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V128(const Intrinsic& v) : v_(v) {}\n  HH_INLINE V128& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  // There are no greater-than comparison instructions for unsigned T.\n  HH_INLINE V128 operator==(const V128& other) const\n  {\n    return V128(_mm_cmpeq_epi16(v_, other.v_));\n  }\n\n  HH_INLINE V128& operator+=(const V128& other)\n  {\n    v_ = _mm_add_epi16(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator-=(const V128& other)\n  {\n    v_ = _mm_sub_epi16(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator&=(const V128& other)\n  {\n    v_ = _mm_and_si128(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator|=(const V128& other)\n  {\n    v_ = _mm_or_si128(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator^=(const V128& other)\n  {\n    v_ = _mm_xor_si128(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator<<=(const int count)\n  {\n    v_ = _mm_slli_epi16(v_, count);\n    return *this;\n  }\n  HH_INLINE V128& operator<<=(const Intrinsic& count)\n  {\n    v_ = _mm_sll_epi16(v_, count);\n    return *this;\n  }\n\n  HH_INLINE V128& operator>>=(const int count)\n  {\n    v_ = _mm_srli_epi16(v_, count);\n    return *this;\n  }\n  HH_INLINE V128& operator>>=(const Intrinsic& count)\n  {\n    v_ = _mm_srl_epi16(v_, count);\n    return *this;\n  }\n\nprivate:\n  Intrinsic v_;\n};\n\ntemplate <>\nclass V128<uint32_t>\n{\npublic:\n  using Intrinsic = __m128i;\n  using T = uint32_t;\n  static constexpr size_t N = 4;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V128() {}\n\n  // Lane 0 (p_0) is the lowest.\n  HH_INLINE V128(T p_3, T p_2, T p_1, T p_0)\n    : v_(_mm_set_epi32(p_3, p_2, p_1, p_0)) {}\n\n  // Broadcasts i to all lanes (usually by loading from memory).\n  HH_INLINE explicit V128(T i) : v_(_mm_set1_epi32(i)) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V128(const V128& other) : v_(other.v_) {}\n  template <typename U>\n  HH_INLINE explicit V128(const V128<U>& other) : v_(other) {}\n  HH_INLINE V128& operator=(const V128& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V128(const Intrinsic& v) : v_(v) {}\n  HH_INLINE V128& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  // There are no greater-than comparison instructions for unsigned T.\n  HH_INLINE V128 operator==(const V128& other) const\n  {\n    return V128(_mm_cmpeq_epi32(v_, other.v_));\n  }\n\n  HH_INLINE V128& operator+=(const V128& other)\n  {\n    v_ = _mm_add_epi32(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator-=(const V128& other)\n  {\n    v_ = _mm_sub_epi32(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator&=(const V128& other)\n  {\n    v_ = _mm_and_si128(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator|=(const V128& other)\n  {\n    v_ = _mm_or_si128(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator^=(const V128& other)\n  {\n    v_ = _mm_xor_si128(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator<<=(const int count)\n  {\n    v_ = _mm_slli_epi32(v_, count);\n    return *this;\n  }\n  HH_INLINE V128& operator<<=(const Intrinsic& count)\n  {\n    v_ = _mm_sll_epi32(v_, count);\n    return *this;\n  }\n\n  HH_INLINE V128& operator>>=(const int count)\n  {\n    v_ = _mm_srli_epi32(v_, count);\n    return *this;\n  }\n  HH_INLINE V128& operator>>=(const Intrinsic& count)\n  {\n    v_ = _mm_srl_epi32(v_, count);\n    return *this;\n  }\n\nprivate:\n  Intrinsic v_;\n};\n\ntemplate <>\nclass V128<uint64_t>\n{\npublic:\n  using Intrinsic = __m128i;\n  using T = uint64_t;\n  static constexpr size_t N = 2;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V128() {}\n\n  // Lane 0 (p_0) is the lowest.\n  HH_INLINE V128(T p_1, T p_0) : v_(_mm_set_epi64x(p_1, p_0)) {}\n\n  // Broadcasts i to all lanes (usually by loading from memory).\n  HH_INLINE explicit V128(T i) : v_(_mm_set_epi64x(i, i)) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V128(const V128& other) : v_(other.v_) {}\n  template <typename U>\n  HH_INLINE explicit V128(const V128<U>& other) : v_(other) {}\n  HH_INLINE V128& operator=(const V128& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V128(const Intrinsic& v) : v_(v) {}\n  HH_INLINE V128& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  // There are no greater-than comparison instructions for unsigned T.\n  HH_INLINE V128 operator==(const V128& other) const\n  {\n    return V128(_mm_cmpeq_epi64(v_, other.v_));\n  }\n\n  HH_INLINE V128& operator+=(const V128& other)\n  {\n    v_ = _mm_add_epi64(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator-=(const V128& other)\n  {\n    v_ = _mm_sub_epi64(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator&=(const V128& other)\n  {\n    v_ = _mm_and_si128(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator|=(const V128& other)\n  {\n    v_ = _mm_or_si128(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator^=(const V128& other)\n  {\n    v_ = _mm_xor_si128(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator<<=(const int count)\n  {\n    v_ = _mm_slli_epi64(v_, count);\n    return *this;\n  }\n  HH_INLINE V128& operator<<=(const Intrinsic& count)\n  {\n    v_ = _mm_sll_epi64(v_, count);\n    return *this;\n  }\n\n  HH_INLINE V128& operator>>=(const int count)\n  {\n    v_ = _mm_srli_epi64(v_, count);\n    return *this;\n  }\n  HH_INLINE V128& operator>>=(const Intrinsic& count)\n  {\n    v_ = _mm_srl_epi64(v_, count);\n    return *this;\n  }\n\nprivate:\n  Intrinsic v_;\n};\n\ntemplate <>\nclass V128<float>\n{\npublic:\n  using Intrinsic = __m128;\n  using T = float;\n  static constexpr size_t N = 4;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V128() {}\n\n  // Lane 0 (p_0) is the lowest.\n  HH_INLINE V128(T p_3, T p_2, T p_1, T p_0)\n    : v_(_mm_set_ps(p_3, p_2, p_1, p_0)) {}\n\n  // Broadcasts to all lanes.\n  HH_INLINE explicit V128(T f) : v_(_mm_set1_ps(f)) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V128(const V128& other) : v_(other.v_) {}\n  template <typename U>\n  HH_INLINE explicit V128(const V128<U>& other) : v_(other) {}\n  HH_INLINE V128& operator=(const V128& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V128(const Intrinsic& v) : v_(v) {}\n  HH_INLINE V128& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  HH_INLINE V128 operator==(const V128& other) const\n  {\n    return V128(_mm_cmpeq_ps(v_, other.v_));\n  }\n  HH_INLINE V128 operator<(const V128& other) const\n  {\n    return V128(_mm_cmplt_ps(v_, other.v_));\n  }\n  HH_INLINE V128 operator>(const V128& other) const\n  {\n    return V128(_mm_cmplt_ps(other.v_, v_));\n  }\n\n  HH_INLINE V128& operator*=(const V128& other)\n  {\n    v_ = _mm_mul_ps(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator/=(const V128& other)\n  {\n    v_ = _mm_div_ps(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator+=(const V128& other)\n  {\n    v_ = _mm_add_ps(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator-=(const V128& other)\n  {\n    v_ = _mm_sub_ps(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator&=(const V128& other)\n  {\n    v_ = _mm_and_ps(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator|=(const V128& other)\n  {\n    v_ = _mm_or_ps(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator^=(const V128& other)\n  {\n    v_ = _mm_xor_ps(v_, other.v_);\n    return *this;\n  }\n\nprivate:\n  Intrinsic v_;\n};\n\ntemplate <>\nclass V128<double>\n{\npublic:\n  using Intrinsic = __m128d;\n  using T = double;\n  static constexpr size_t N = 2;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V128() {}\n\n  // Lane 0 (p_0) is the lowest.\n  HH_INLINE V128(T p_1, T p_0) : v_(_mm_set_pd(p_1, p_0)) {}\n\n  // Broadcasts to all lanes.\n  HH_INLINE explicit V128(T f) : v_(_mm_set1_pd(f)) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V128(const V128& other) : v_(other.v_) {}\n  template <typename U>\n  HH_INLINE explicit V128(const V128<U>& other) : v_(other) {}\n  HH_INLINE V128& operator=(const V128& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V128(const Intrinsic& v) : v_(v) {}\n  HH_INLINE V128& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  HH_INLINE V128 operator==(const V128& other) const\n  {\n    return V128(_mm_cmpeq_pd(v_, other.v_));\n  }\n  HH_INLINE V128 operator<(const V128& other) const\n  {\n    return V128(_mm_cmplt_pd(v_, other.v_));\n  }\n  HH_INLINE V128 operator>(const V128& other) const\n  {\n    return V128(_mm_cmplt_pd(other.v_, v_));\n  }\n\n  HH_INLINE V128& operator*=(const V128& other)\n  {\n    v_ = _mm_mul_pd(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator/=(const V128& other)\n  {\n    v_ = _mm_div_pd(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator+=(const V128& other)\n  {\n    v_ = _mm_add_pd(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator-=(const V128& other)\n  {\n    v_ = _mm_sub_pd(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator&=(const V128& other)\n  {\n    v_ = _mm_and_pd(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator|=(const V128& other)\n  {\n    v_ = _mm_or_pd(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator^=(const V128& other)\n  {\n    v_ = _mm_xor_pd(v_, other.v_);\n    return *this;\n  }\n\nprivate:\n  Intrinsic v_;\n};\n\n// Nonmember functions for any V128 via member functions.\n\ntemplate <typename T>\nHH_INLINE V128<T> operator*(const V128<T>& left, const V128<T>& right)\n{\n  V128<T> t(left);\n  return t *= right;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator/(const V128<T>& left, const V128<T>& right)\n{\n  V128<T> t(left);\n  return t /= right;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator+(const V128<T>& left, const V128<T>& right)\n{\n  V128<T> t(left);\n  return t += right;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator-(const V128<T>& left, const V128<T>& right)\n{\n  V128<T> t(left);\n  return t -= right;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator&(const V128<T>& left, const V128<T>& right)\n{\n  V128<T> t(left);\n  return t &= right;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator|(const V128<T>& left, const V128<T>& right)\n{\n  V128<T> t(left);\n  return t |= right;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator^(const V128<T>& left, const V128<T>& right)\n{\n  V128<T> t(left);\n  return t ^= right;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator<<(const V128<T>& v, const int count)\n{\n  V128<T> t(v);\n  return t <<= count;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator>>(const V128<T>& v, const int count)\n{\n  V128<T> t(v);\n  return t >>= count;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator<<(const V128<T>& v, const __m128i& count)\n{\n  V128<T> t(v);\n  return t <<= count;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator>>(const V128<T>& v, const __m128i& count)\n{\n  V128<T> t(v);\n  return t >>= count;\n}\n\nusing V16x8U = V128<uint8_t>;\nusing V8x16U = V128<uint16_t>;\nusing V4x32U = V128<uint32_t>;\nusing V2x64U = V128<uint64_t>;\nusing V4x32F = V128<float>;\nusing V2x64F = V128<double>;\n\n// Load/Store for any V128.\n\n// We differentiate between targets' vector types via template specialization.\n// Calling Load<V>(floats) is more natural than Load(V8x32F(), floats) and may\n// generate better code in unoptimized builds. Only declare the primary\n// templates to avoid needing mutual exclusion with vector256.\n\ntemplate <class V>\nHH_INLINE V Load(const typename V::T* const HH_RESTRICT from);\n\ntemplate <class V>\nHH_INLINE V LoadUnaligned(const typename V::T* const HH_RESTRICT from);\n\n// \"from\" must be vector-aligned.\ntemplate <>\nHH_INLINE V16x8U Load<V16x8U>(const V16x8U::T* const HH_RESTRICT from)\n{\n  const __m128i* const HH_RESTRICT p = reinterpret_cast<const __m128i*>(from);\n  return V16x8U(_mm_load_si128(p));\n}\ntemplate <>\nHH_INLINE V8x16U Load<V8x16U>(const V8x16U::T* const HH_RESTRICT from)\n{\n  const __m128i* const HH_RESTRICT p = reinterpret_cast<const __m128i*>(from);\n  return V8x16U(_mm_load_si128(p));\n}\ntemplate <>\nHH_INLINE V4x32U Load<V4x32U>(const V4x32U::T* const HH_RESTRICT from)\n{\n  const __m128i* const HH_RESTRICT p = reinterpret_cast<const __m128i*>(from);\n  return V4x32U(_mm_load_si128(p));\n}\ntemplate <>\nHH_INLINE V2x64U Load<V2x64U>(const V2x64U::T* const HH_RESTRICT from)\n{\n  const __m128i* const HH_RESTRICT p = reinterpret_cast<const __m128i*>(from);\n  return V2x64U(_mm_load_si128(p));\n}\ntemplate <>\nHH_INLINE V4x32F Load<V4x32F>(const V4x32F::T* const HH_RESTRICT from)\n{\n  return V4x32F(_mm_load_ps(from));\n}\ntemplate <>\nHH_INLINE V2x64F Load<V2x64F>(const V2x64F::T* const HH_RESTRICT from)\n{\n  return V2x64F(_mm_load_pd(from));\n}\n\ntemplate <>\nHH_INLINE V16x8U\nLoadUnaligned<V16x8U>(const V16x8U::T* const HH_RESTRICT from)\n{\n  const __m128i* const HH_RESTRICT p = reinterpret_cast<const __m128i*>(from);\n  return V16x8U(_mm_loadu_si128(p));\n}\ntemplate <>\nHH_INLINE V8x16U\nLoadUnaligned<V8x16U>(const V8x16U::T* const HH_RESTRICT from)\n{\n  const __m128i* const HH_RESTRICT p = reinterpret_cast<const __m128i*>(from);\n  return V8x16U(_mm_loadu_si128(p));\n}\ntemplate <>\nHH_INLINE V4x32U\nLoadUnaligned<V4x32U>(const V4x32U::T* const HH_RESTRICT from)\n{\n  const __m128i* const HH_RESTRICT p = reinterpret_cast<const __m128i*>(from);\n  return V4x32U(_mm_loadu_si128(p));\n}\ntemplate <>\nHH_INLINE V2x64U\nLoadUnaligned<V2x64U>(const V2x64U::T* const HH_RESTRICT from)\n{\n  const __m128i* const HH_RESTRICT p = reinterpret_cast<const __m128i*>(from);\n  return V2x64U(_mm_loadu_si128(p));\n}\ntemplate <>\nHH_INLINE V4x32F\nLoadUnaligned<V4x32F>(const V4x32F::T* const HH_RESTRICT from)\n{\n  return V4x32F(_mm_loadu_ps(from));\n}\ntemplate <>\nHH_INLINE V2x64F\nLoadUnaligned<V2x64F>(const V2x64F::T* const HH_RESTRICT from)\n{\n  return V2x64F(_mm_loadu_pd(from));\n}\n\n// \"to\" must be vector-aligned.\ntemplate <typename T>\nHH_INLINE void Store(const V128<T>& v, T* const HH_RESTRICT to)\n{\n  _mm_store_si128(reinterpret_cast<__m128i* HH_RESTRICT>(to), v);\n}\nHH_INLINE void Store(const V128<float>& v, float* const HH_RESTRICT to)\n{\n  _mm_store_ps(to, v);\n}\nHH_INLINE void Store(const V128<double>& v, double* const HH_RESTRICT to)\n{\n  _mm_store_pd(to, v);\n}\n\ntemplate <typename T>\nHH_INLINE void StoreUnaligned(const V128<T>& v, T* const HH_RESTRICT to)\n{\n  _mm_storeu_si128(reinterpret_cast<__m128i* HH_RESTRICT>(to), v);\n}\nHH_INLINE void StoreUnaligned(const V128<float>& v,\n                              float* const HH_RESTRICT to)\n{\n  _mm_storeu_ps(to, v);\n}\nHH_INLINE void StoreUnaligned(const V128<double>& v,\n                              double* const HH_RESTRICT to)\n{\n  _mm_storeu_pd(to, v);\n}\n\n// Writes directly to (aligned) memory, bypassing the cache. This is useful for\n// data that will not be read again in the near future.\ntemplate <typename T>\nHH_INLINE void Stream(const V128<T>& v, T* const HH_RESTRICT to)\n{\n  _mm_stream_si128(reinterpret_cast<__m128i* HH_RESTRICT>(to), v);\n}\nHH_INLINE void Stream(const V128<float>& v, float* const HH_RESTRICT to)\n{\n  _mm_stream_ps(to, v);\n}\nHH_INLINE void Stream(const V128<double>& v, double* const HH_RESTRICT to)\n{\n  _mm_stream_pd(to, v);\n}\n\n// Miscellaneous functions.\n\ntemplate <typename T>\nHH_INLINE V128<T> RotateLeft(const V128<T>& v, const int count)\n{\n  constexpr size_t num_bits = sizeof(T) * 8;\n  return (v << count) | (v >> (num_bits - count));\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> AndNot(const V128<T>& neg_mask, const V128<T>& values)\n{\n  return V128<T>(_mm_andnot_si128(neg_mask, values));\n}\ntemplate <>\nHH_INLINE V128<float> AndNot(const V128<float>& neg_mask,\n                             const V128<float>& values)\n{\n  return V128<float>(_mm_andnot_ps(neg_mask, values));\n}\ntemplate <>\nHH_INLINE V128<double> AndNot(const V128<double>& neg_mask,\n                              const V128<double>& values)\n{\n  return V128<double>(_mm_andnot_pd(neg_mask, values));\n}\n\nHH_INLINE V4x32F Select(const V4x32F& a, const V4x32F& b, const V4x32F& mask)\n{\n  return V4x32F(_mm_blendv_ps(a, b, mask));\n}\n\nHH_INLINE V2x64F Select(const V2x64F& a, const V2x64F& b, const V2x64F& mask)\n{\n  return V2x64F(_mm_blendv_pd(a, b, mask));\n}\n\n// Min/Max\n\nHH_INLINE V16x8U Min(const V16x8U& v0, const V16x8U& v1)\n{\n  return V16x8U(_mm_min_epu8(v0, v1));\n}\n\nHH_INLINE V16x8U Max(const V16x8U& v0, const V16x8U& v1)\n{\n  return V16x8U(_mm_max_epu8(v0, v1));\n}\n\nHH_INLINE V8x16U Min(const V8x16U& v0, const V8x16U& v1)\n{\n  return V8x16U(_mm_min_epu16(v0, v1));\n}\n\nHH_INLINE V8x16U Max(const V8x16U& v0, const V8x16U& v1)\n{\n  return V8x16U(_mm_max_epu16(v0, v1));\n}\n\nHH_INLINE V4x32U Min(const V4x32U& v0, const V4x32U& v1)\n{\n  return V4x32U(_mm_min_epu32(v0, v1));\n}\n\nHH_INLINE V4x32U Max(const V4x32U& v0, const V4x32U& v1)\n{\n  return V4x32U(_mm_max_epu32(v0, v1));\n}\n\nHH_INLINE V4x32F Min(const V4x32F& v0, const V4x32F& v1)\n{\n  return V4x32F(_mm_min_ps(v0, v1));\n}\n\nHH_INLINE V4x32F Max(const V4x32F& v0, const V4x32F& v1)\n{\n  return V4x32F(_mm_max_ps(v0, v1));\n}\n\nHH_INLINE V2x64F Min(const V2x64F& v0, const V2x64F& v1)\n{\n  return V2x64F(_mm_min_pd(v0, v1));\n}\n\nHH_INLINE V2x64F Max(const V2x64F& v0, const V2x64F& v1)\n{\n  return V2x64F(_mm_max_pd(v0, v1));\n}\n\n}  // namespace HH_TARGET_NAME\n}  // namespace highwayhash\n\n#endif  // HH_DISABLE_TARGET_SPECIFIC\n#endif  // HIGHWAYHASH_VECTOR128_H_\n"
  },
  {
    "path": "common/highwayhash/vector256.h",
    "content": "// Copyright 2016 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_VECTOR256_H_\n#define HIGHWAYHASH_VECTOR256_H_\n\n// Defines SIMD vector classes (\"V4x64U\") with overloaded arithmetic operators:\n// const V4x64U masked_sum = (a + b) & m;\n// This is shorter and more readable than compiler intrinsics:\n// const __m256i masked_sum = _mm256_and_si256(_mm256_add_epi64(a, b), m);\n// There is typically no runtime cost for these abstractions.\n//\n// The naming convention is VNxBBT where N is the number of lanes, BB the\n// number of bits per lane and T is the lane type: unsigned integer (U),\n// signed integer (I), or floating-point (F).\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include <stddef.h>\n#include <stdint.h>\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n\n// For auto-dependency generation, we need to include all headers but not their\n// contents (otherwise compilation fails because -mavx2 is not specified).\n#ifndef HH_DISABLE_TARGET_SPECIFIC\n\n// (This include cannot be moved within a namespace due to conflicts with\n// other system headers; see the comment in hh_sse41.h.)\n#include <immintrin.h>\n\nnamespace highwayhash\n{\n// To prevent ODR violations when including this from multiple translation\n// units (TU) that are compiled with different flags, the contents must reside\n// in a namespace whose name is unique to the TU. NOTE: this behavior is\n// incompatible with precompiled modules and requires textual inclusion instead.\nnamespace HH_TARGET_NAME\n{\n\n// Primary template for 256-bit AVX2 vectors; only specializations are used.\ntemplate <typename T>\nclass V256 {};\n\ntemplate <>\nclass V256<uint8_t>\n{\npublic:\n  using Intrinsic = __m256i;\n  using T = uint8_t;\n  static constexpr size_t N = 32;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V256() {}\n\n  // Broadcasts i to all lanes.\n  HH_INLINE explicit V256(T i)\n    : v_(_mm256_broadcastb_epi8(_mm_cvtsi32_si128(i))) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V256(const V256& other) : v_(other.v_) {}\n  template <typename U>\n  HH_INLINE explicit V256(const V256<U>& other) : v_(other) {}\n  HH_INLINE V256& operator=(const V256& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V256(const Intrinsic& v) : v_(v) {}\n  HH_INLINE V256& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  // There are no greater-than comparison instructions for unsigned T.\n  HH_INLINE V256 operator==(const V256& other) const\n  {\n    return V256(_mm256_cmpeq_epi8(v_, other.v_));\n  }\n\n  HH_INLINE V256& operator+=(const V256& other)\n  {\n    v_ = _mm256_add_epi8(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator-=(const V256& other)\n  {\n    v_ = _mm256_sub_epi8(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V256& operator&=(const V256& other)\n  {\n    v_ = _mm256_and_si256(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator|=(const V256& other)\n  {\n    v_ = _mm256_or_si256(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator^=(const V256& other)\n  {\n    v_ = _mm256_xor_si256(v_, other.v_);\n    return *this;\n  }\n\nprivate:\n  Intrinsic v_;\n};\n\ntemplate <>\nclass V256<uint16_t>\n{\npublic:\n  using Intrinsic = __m256i;\n  using T = uint16_t;\n  static constexpr size_t N = 16;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V256() {}\n\n  // Lane 0 (p_0) is the lowest.\n  HH_INLINE V256(T p_F, T p_E, T p_D, T p_C, T p_B, T p_A, T p_9, T p_8, T p_7,\n                 T p_6, T p_5, T p_4, T p_3, T p_2, T p_1, T p_0)\n    : v_(_mm256_set_epi16(p_F, p_E, p_D, p_C, p_B, p_A, p_9, p_8, p_7, p_6,\n                          p_5, p_4, p_3, p_2, p_1, p_0)) {}\n\n  // Broadcasts i to all lanes.\n  HH_INLINE explicit V256(T i)\n    : v_(_mm256_broadcastw_epi16(_mm_cvtsi32_si128(i))) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V256(const V256& other) : v_(other.v_) {}\n  template <typename U>\n  HH_INLINE explicit V256(const V256<U>& other) : v_(other) {}\n  HH_INLINE V256& operator=(const V256& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V256(const Intrinsic& v) : v_(v) {}\n  HH_INLINE V256& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  // There are no greater-than comparison instructions for unsigned T.\n  HH_INLINE V256 operator==(const V256& other) const\n  {\n    return V256(_mm256_cmpeq_epi16(v_, other.v_));\n  }\n\n  HH_INLINE V256& operator+=(const V256& other)\n  {\n    v_ = _mm256_add_epi16(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator-=(const V256& other)\n  {\n    v_ = _mm256_sub_epi16(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V256& operator&=(const V256& other)\n  {\n    v_ = _mm256_and_si256(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator|=(const V256& other)\n  {\n    v_ = _mm256_or_si256(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator^=(const V256& other)\n  {\n    v_ = _mm256_xor_si256(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V256& operator<<=(const int count)\n  {\n    v_ = _mm256_slli_epi16(v_, count);\n    return *this;\n  }\n\n  HH_INLINE V256& operator>>=(const int count)\n  {\n    v_ = _mm256_srli_epi16(v_, count);\n    return *this;\n  }\n\nprivate:\n  Intrinsic v_;\n};\n\ntemplate <>\nclass V256<uint32_t>\n{\npublic:\n  using Intrinsic = __m256i;\n  using T = uint32_t;\n  static constexpr size_t N = 8;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V256() {}\n\n  // Lane 0 (p_0) is the lowest.\n  HH_INLINE V256(T p_7, T p_6, T p_5, T p_4, T p_3, T p_2, T p_1, T p_0)\n    : v_(_mm256_set_epi32(p_7, p_6, p_5, p_4, p_3, p_2, p_1, p_0)) {}\n\n  // Broadcasts i to all lanes.\n  HH_INLINE explicit V256(T i)\n    : v_(_mm256_broadcastd_epi32(_mm_cvtsi32_si128(i))) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V256(const V256& other) : v_(other.v_) {}\n  template <typename U>\n  HH_INLINE explicit V256(const V256<U>& other) : v_(other) {}\n  HH_INLINE V256& operator=(const V256& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V256(const Intrinsic& v) : v_(v) {}\n  HH_INLINE V256& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  // There are no greater-than comparison instructions for unsigned T.\n  HH_INLINE V256 operator==(const V256& other) const\n  {\n    return V256(_mm256_cmpeq_epi32(v_, other.v_));\n  }\n\n  HH_INLINE V256& operator+=(const V256& other)\n  {\n    v_ = _mm256_add_epi32(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator-=(const V256& other)\n  {\n    v_ = _mm256_sub_epi32(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V256& operator&=(const V256& other)\n  {\n    v_ = _mm256_and_si256(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator|=(const V256& other)\n  {\n    v_ = _mm256_or_si256(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator^=(const V256& other)\n  {\n    v_ = _mm256_xor_si256(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V256& operator<<=(const int count)\n  {\n    v_ = _mm256_slli_epi32(v_, count);\n    return *this;\n  }\n\n  HH_INLINE V256& operator>>=(const int count)\n  {\n    v_ = _mm256_srli_epi32(v_, count);\n    return *this;\n  }\n\nprivate:\n  Intrinsic v_;\n};\n\ntemplate <>\nclass V256<uint64_t>\n{\npublic:\n  using Intrinsic = __m256i;\n  using T = uint64_t;\n  static constexpr size_t N = 4;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V256() {}\n\n  // Lane 0 (p_0) is the lowest.\n  HH_INLINE V256(T p_3, T p_2, T p_1, T p_0)\n    : v_(_mm256_set_epi64x(p_3, p_2, p_1, p_0)) {}\n\n  // Broadcasts i to all lanes.\n  HH_INLINE explicit V256(T i)\n    : v_(_mm256_broadcastq_epi64(_mm_cvtsi64_si128(i))) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V256(const V256& other) : v_(other.v_) {}\n  template <typename U>\n  HH_INLINE explicit V256(const V256<U>& other) : v_(other) {}\n  HH_INLINE V256& operator=(const V256& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V256(const Intrinsic& v) : v_(v) {}\n  HH_INLINE V256& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  // There are no greater-than comparison instructions for unsigned T.\n  HH_INLINE V256 operator==(const V256& other) const\n  {\n    return V256(_mm256_cmpeq_epi64(v_, other.v_));\n  }\n\n  HH_INLINE V256& operator+=(const V256& other)\n  {\n    v_ = _mm256_add_epi64(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator-=(const V256& other)\n  {\n    v_ = _mm256_sub_epi64(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V256& operator&=(const V256& other)\n  {\n    v_ = _mm256_and_si256(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator|=(const V256& other)\n  {\n    v_ = _mm256_or_si256(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator^=(const V256& other)\n  {\n    v_ = _mm256_xor_si256(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V256& operator<<=(const int count)\n  {\n    v_ = _mm256_slli_epi64(v_, count);\n    return *this;\n  }\n\n  HH_INLINE V256& operator>>=(const int count)\n  {\n    v_ = _mm256_srli_epi64(v_, count);\n    return *this;\n  }\n\nprivate:\n  Intrinsic v_;\n};\n\ntemplate <>\nclass V256<float>\n{\npublic:\n  using Intrinsic = __m256;\n  using T = float;\n  static constexpr size_t N = 8;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V256() {}\n\n  // Lane 0 (p_0) is the lowest.\n  HH_INLINE V256(T p_7, T p_6, T p_5, T p_4, T p_3, T p_2, T p_1, T p_0)\n    : v_(_mm256_set_ps(p_7, p_6, p_5, p_4, p_3, p_2, p_1, p_0)) {}\n\n  // Broadcasts to all lanes.\n  HH_INLINE explicit V256(T f) : v_(_mm256_set1_ps(f)) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V256(const V256& other) : v_(other.v_) {}\n  template <typename U>\n  HH_INLINE explicit V256(const V256<U>& other) : v_(other) {}\n  HH_INLINE V256& operator=(const V256& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V256(const Intrinsic& v) : v_(v) {}\n  HH_INLINE V256& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  HH_INLINE V256 operator==(const V256& other) const\n  {\n    return V256(_mm256_cmp_ps(v_, other.v_, 0));\n  }\n  HH_INLINE V256 operator<(const V256& other) const\n  {\n    return V256(_mm256_cmp_ps(v_, other.v_, 1));\n  }\n  HH_INLINE V256 operator>(const V256& other) const\n  {\n    return V256(_mm256_cmp_ps(other.v_, v_, 1));\n  }\n\n  HH_INLINE V256& operator*=(const V256& other)\n  {\n    v_ = _mm256_mul_ps(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator/=(const V256& other)\n  {\n    v_ = _mm256_div_ps(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator+=(const V256& other)\n  {\n    v_ = _mm256_add_ps(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator-=(const V256& other)\n  {\n    v_ = _mm256_sub_ps(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V256& operator&=(const V256& other)\n  {\n    v_ = _mm256_and_ps(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator|=(const V256& other)\n  {\n    v_ = _mm256_or_ps(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator^=(const V256& other)\n  {\n    v_ = _mm256_xor_ps(v_, other.v_);\n    return *this;\n  }\n\nprivate:\n  Intrinsic v_;\n};\n\ntemplate <>\nclass V256<double>\n{\npublic:\n  using Intrinsic = __m256d;\n  using T = double;\n  static constexpr size_t N = 4;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V256() {}\n\n  // Lane 0 (p_0) is the lowest.\n  HH_INLINE V256(T p_3, T p_2, T p_1, T p_0)\n    : v_(_mm256_set_pd(p_3, p_2, p_1, p_0)) {}\n\n  // Broadcasts to all lanes.\n  HH_INLINE explicit V256(T f) : v_(_mm256_set1_pd(f)) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V256(const V256& other) : v_(other.v_) {}\n  template <typename U>\n  HH_INLINE explicit V256(const V256<U>& other) : v_(other) {}\n  HH_INLINE V256& operator=(const V256& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V256(const Intrinsic& v) : v_(v) {}\n  HH_INLINE V256& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  HH_INLINE V256 operator==(const V256& other) const\n  {\n    return V256(_mm256_cmp_pd(v_, other.v_, 0));\n  }\n  HH_INLINE V256 operator<(const V256& other) const\n  {\n    return V256(_mm256_cmp_pd(v_, other.v_, 1));\n  }\n  HH_INLINE V256 operator>(const V256& other) const\n  {\n    return V256(_mm256_cmp_pd(other.v_, v_, 1));\n  }\n\n  HH_INLINE V256& operator*=(const V256& other)\n  {\n    v_ = _mm256_mul_pd(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator/=(const V256& other)\n  {\n    v_ = _mm256_div_pd(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator+=(const V256& other)\n  {\n    v_ = _mm256_add_pd(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator-=(const V256& other)\n  {\n    v_ = _mm256_sub_pd(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V256& operator&=(const V256& other)\n  {\n    v_ = _mm256_and_pd(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator|=(const V256& other)\n  {\n    v_ = _mm256_or_pd(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V256& operator^=(const V256& other)\n  {\n    v_ = _mm256_xor_pd(v_, other.v_);\n    return *this;\n  }\n\nprivate:\n  Intrinsic v_;\n};\n\n// Nonmember functions for any V256 via member functions.\n\ntemplate <typename T>\nHH_INLINE V256<T> operator*(const V256<T>& left, const V256<T>& right)\n{\n  V256<T> t(left);\n  return t *= right;\n}\n\ntemplate <typename T>\nHH_INLINE V256<T> operator/(const V256<T>& left, const V256<T>& right)\n{\n  V256<T> t(left);\n  return t /= right;\n}\n\ntemplate <typename T>\nHH_INLINE V256<T> operator+(const V256<T>& left, const V256<T>& right)\n{\n  V256<T> t(left);\n  return t += right;\n}\n\ntemplate <typename T>\nHH_INLINE V256<T> operator-(const V256<T>& left, const V256<T>& right)\n{\n  V256<T> t(left);\n  return t -= right;\n}\n\ntemplate <typename T>\nHH_INLINE V256<T> operator&(const V256<T>& left, const V256<T>& right)\n{\n  V256<T> t(left);\n  return t &= right;\n}\n\ntemplate <typename T>\nHH_INLINE V256<T> operator|(const V256<T> left, const V256<T>& right)\n{\n  V256<T> t(left);\n  return t |= right;\n}\n\ntemplate <typename T>\nHH_INLINE V256<T> operator^(const V256<T>& left, const V256<T>& right)\n{\n  V256<T> t(left);\n  return t ^= right;\n}\n\ntemplate <typename T>\nHH_INLINE V256<T> operator<<(const V256<T>& v, const int count)\n{\n  V256<T> t(v);\n  return t <<= count;\n}\n\ntemplate <typename T>\nHH_INLINE V256<T> operator>>(const V256<T>& v, const int count)\n{\n  V256<T> t(v);\n  return t >>= count;\n}\n\n// We do not provide operator<<(V, __m128i) because it has 4 cycle latency\n// (to broadcast the shift count). It is faster to use sllv_epi64 etc. instead.\n\nusing V32x8U = V256<uint8_t>;\nusing V16x16U = V256<uint16_t>;\nusing V8x32U = V256<uint32_t>;\nusing V4x64U = V256<uint64_t>;\nusing V8x32F = V256<float>;\nusing V4x64F = V256<double>;\n\n// Load/Store for any V256.\n\n// We differentiate between targets' vector types via template specialization.\n// Calling Load<V>(floats) is more natural than Load(V8x32F(), floats) and may\n// generate better code in unoptimized builds. Only declare the primary\n// templates to avoid needing mutual exclusion with vector128.\n\ntemplate <class V>\nHH_INLINE V Load(const typename V::T* const HH_RESTRICT from);\n\ntemplate <class V>\nHH_INLINE V LoadUnaligned(const typename V::T* const HH_RESTRICT from);\n\ntemplate <>\nHH_INLINE V32x8U Load(const V32x8U::T* const HH_RESTRICT from)\n{\n  const __m256i* const HH_RESTRICT p = reinterpret_cast<const __m256i*>(from);\n  return V32x8U(_mm256_load_si256(p));\n}\ntemplate <>\nHH_INLINE V16x16U Load(const V16x16U::T* const HH_RESTRICT from)\n{\n  const __m256i* const HH_RESTRICT p = reinterpret_cast<const __m256i*>(from);\n  return V16x16U(_mm256_load_si256(p));\n}\ntemplate <>\nHH_INLINE V8x32U Load(const V8x32U::T* const HH_RESTRICT from)\n{\n  const __m256i* const HH_RESTRICT p = reinterpret_cast<const __m256i*>(from);\n  return V8x32U(_mm256_load_si256(p));\n}\ntemplate <>\nHH_INLINE V4x64U Load(const V4x64U::T* const HH_RESTRICT from)\n{\n  const __m256i* const HH_RESTRICT p = reinterpret_cast<const __m256i*>(from);\n  return V4x64U(_mm256_load_si256(p));\n}\ntemplate <>\nHH_INLINE V8x32F Load(const V8x32F::T* const HH_RESTRICT from)\n{\n  return V8x32F(_mm256_load_ps(from));\n}\ntemplate <>\nHH_INLINE V4x64F Load(const V4x64F::T* const HH_RESTRICT from)\n{\n  return V4x64F(_mm256_load_pd(from));\n}\n\ntemplate <>\nHH_INLINE V32x8U LoadUnaligned(const V32x8U::T* const HH_RESTRICT from)\n{\n  const __m256i* const HH_RESTRICT p = reinterpret_cast<const __m256i*>(from);\n  return V32x8U(_mm256_loadu_si256(p));\n}\ntemplate <>\nHH_INLINE V16x16U LoadUnaligned(const V16x16U::T* const HH_RESTRICT from)\n{\n  const __m256i* const HH_RESTRICT p = reinterpret_cast<const __m256i*>(from);\n  return V16x16U(_mm256_loadu_si256(p));\n}\ntemplate <>\nHH_INLINE V8x32U LoadUnaligned(const V8x32U::T* const HH_RESTRICT from)\n{\n  const __m256i* const HH_RESTRICT p = reinterpret_cast<const __m256i*>(from);\n  return V8x32U(_mm256_loadu_si256(p));\n}\ntemplate <>\nHH_INLINE V4x64U LoadUnaligned(const V4x64U::T* const HH_RESTRICT from)\n{\n  const __m256i* const HH_RESTRICT p = reinterpret_cast<const __m256i*>(from);\n  return V4x64U(_mm256_loadu_si256(p));\n}\ntemplate <>\nHH_INLINE V8x32F LoadUnaligned(const V8x32F::T* const HH_RESTRICT from)\n{\n  return V8x32F(_mm256_loadu_ps(from));\n}\ntemplate <>\nHH_INLINE V4x64F LoadUnaligned(const V4x64F::T* const HH_RESTRICT from)\n{\n  return V4x64F(_mm256_loadu_pd(from));\n}\n\n// \"to\" must be vector-aligned.\ntemplate <typename T>\nHH_INLINE void Store(const V256<T>& v, T* const HH_RESTRICT to)\n{\n  _mm256_store_si256(reinterpret_cast<__m256i* HH_RESTRICT>(to), v);\n}\nHH_INLINE void Store(const V256<float>& v, float* const HH_RESTRICT to)\n{\n  _mm256_store_ps(to, v);\n}\nHH_INLINE void Store(const V256<double>& v, double* const HH_RESTRICT to)\n{\n  _mm256_store_pd(to, v);\n}\n\ntemplate <typename T>\nHH_INLINE void StoreUnaligned(const V256<T>& v, T* const HH_RESTRICT to)\n{\n  _mm256_storeu_si256(reinterpret_cast<__m256i* HH_RESTRICT>(to), v);\n}\nHH_INLINE void StoreUnaligned(const V256<float>& v,\n                              float* const HH_RESTRICT to)\n{\n  _mm256_storeu_ps(to, v);\n}\nHH_INLINE void StoreUnaligned(const V256<double>& v,\n                              double* const HH_RESTRICT to)\n{\n  _mm256_storeu_pd(to, v);\n}\n\n// Writes directly to (aligned) memory, bypassing the cache. This is useful for\n// data that will not be read again in the near future.\ntemplate <typename T>\nHH_INLINE void Stream(const V256<T>& v, T* const HH_RESTRICT to)\n{\n  _mm256_stream_si256(reinterpret_cast<__m256i* HH_RESTRICT>(to), v);\n}\nHH_INLINE void Stream(const V256<float>& v, float* const HH_RESTRICT to)\n{\n  _mm256_stream_ps(to, v);\n}\nHH_INLINE void Stream(const V256<double>& v, double* const HH_RESTRICT to)\n{\n  _mm256_stream_pd(to, v);\n}\n\n// Miscellaneous functions.\n\ntemplate <typename T>\nHH_INLINE V256<T> RotateLeft(const V256<T>& v, const int count)\n{\n  constexpr size_t num_bits = sizeof(T) * 8;\n  return (v << count) | (v >> (num_bits - count));\n}\n\ntemplate <typename T>\nHH_INLINE V256<T> AndNot(const V256<T>& neg_mask, const V256<T>& values)\n{\n  return V256<T>(_mm256_andnot_si256(neg_mask, values));\n}\ntemplate <>\nHH_INLINE V256<float> AndNot(const V256<float>& neg_mask,\n                             const V256<float>& values)\n{\n  return V256<float>(_mm256_andnot_ps(neg_mask, values));\n}\ntemplate <>\nHH_INLINE V256<double> AndNot(const V256<double>& neg_mask,\n                              const V256<double>& values)\n{\n  return V256<double>(_mm256_andnot_pd(neg_mask, values));\n}\n\nHH_INLINE V8x32F Select(const V8x32F& a, const V8x32F& b, const V8x32F& mask)\n{\n  return V8x32F(_mm256_blendv_ps(a, b, mask));\n}\n\nHH_INLINE V4x64F Select(const V4x64F& a, const V4x64F& b, const V4x64F& mask)\n{\n  return V4x64F(_mm256_blendv_pd(a, b, mask));\n}\n\n// Min/Max\n\nHH_INLINE V32x8U Min(const V32x8U& v0, const V32x8U& v1)\n{\n  return V32x8U(_mm256_min_epu8(v0, v1));\n}\n\nHH_INLINE V32x8U Max(const V32x8U& v0, const V32x8U& v1)\n{\n  return V32x8U(_mm256_max_epu8(v0, v1));\n}\n\nHH_INLINE V16x16U Min(const V16x16U& v0, const V16x16U& v1)\n{\n  return V16x16U(_mm256_min_epu16(v0, v1));\n}\n\nHH_INLINE V16x16U Max(const V16x16U& v0, const V16x16U& v1)\n{\n  return V16x16U(_mm256_max_epu16(v0, v1));\n}\n\nHH_INLINE V8x32U Min(const V8x32U& v0, const V8x32U& v1)\n{\n  return V8x32U(_mm256_min_epu32(v0, v1));\n}\n\nHH_INLINE V8x32U Max(const V8x32U& v0, const V8x32U& v1)\n{\n  return V8x32U(_mm256_max_epu32(v0, v1));\n}\n\nHH_INLINE V8x32F Min(const V8x32F& v0, const V8x32F& v1)\n{\n  return V8x32F(_mm256_min_ps(v0, v1));\n}\n\nHH_INLINE V8x32F Max(const V8x32F& v0, const V8x32F& v1)\n{\n  return V8x32F(_mm256_max_ps(v0, v1));\n}\n\nHH_INLINE V4x64F Min(const V4x64F& v0, const V4x64F& v1)\n{\n  return V4x64F(_mm256_min_pd(v0, v1));\n}\n\nHH_INLINE V4x64F Max(const V4x64F& v0, const V4x64F& v1)\n{\n  return V4x64F(_mm256_max_pd(v0, v1));\n}\n\n}  // namespace HH_TARGET_NAME\n}  // namespace highwayhash\n\n#endif  // HH_DISABLE_TARGET_SPECIFIC\n#endif  // HIGHWAYHASH_VECTOR256_H_\n"
  },
  {
    "path": "common/highwayhash/vector_neon.h",
    "content": "// Copyright 2016-2019 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_VECTOR128_NEON_H_\n#define HIGHWAYHASH_VECTOR128_NEON_H_\n\n// Defines SIMD vector classes (\"V2x64U\") with overloaded arithmetic operators:\n// const V2x64U masked_sum = (a + b) & m;\n// This is shorter and more readable than compiler intrinsics:\n// const uint64x2_t masked_sum = vandq_u64(vaddq_u64(a, b), m);\n// There is typically no runtime cost for these abstractions.\n//\n// The naming convention is VNxBBT where N is the number of lanes, BB the\n// number of bits per lane and T is the lane type: unsigned integer (U),\n// signed integer (I), or floating-point (F). (Note: Floating point vectors are\n// currently disabled).\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/compiler_specific.h\"\n#include \"highwayhash/hh_types.h\"\n\n// For auto-dependency generation, we need to include all headers but not their\n// contents.\n#ifndef HH_DISABLE_TARGET_SPECIFIC\n#include <arm_neon.h>  // NEON\n\nnamespace highwayhash\n{\n// To prevent ODR violations when including this from multiple translation\n// units (TU) that are compiled with different flags, the contents must reside\n// in a namespace whose name is unique to the TU. NOTE: this behavior is\n// incompatible with precompiled modules and requires textual inclusion instead.\nnamespace HH_TARGET_NAME\n{\n\n// Polyfills for ARMv7. ARMv7 lacks a few important instructions which were added\n// in aarch64, so we simulate them with these polyfills.\n#if !defined(__aarch64__) && !defined(__arm64__)\n#ifndef vqtbl1q_u8\n// aarch64 allows a 128-bit lookup table with vtbl. ARMv7 needs to do two\n// lookups for the same effect.\nHH_INLINE uint8x16_t vqtbl1q_u8(uint8x16_t t, uint8x16_t idx)\n{\n  // Prevents scalarizing by GCC.\n  // NOT PORTABLE TO AARCH64! aarch64 uses two separate vectors instead of packing\n  // them, so this reinterpret_cast would fail!\n  uint8x8x2_t split = *reinterpret_cast<const uint8x8x2_t*>(&t);\n  return vcombine_u8(\n           vtbl2_u8(split, vget_low_u8(idx)),\n           vtbl2_u8(split, vget_high_u8(idx))\n         );\n}\n#endif\n#ifndef vnegq_s64\n// ARMv7 lacks this for some weird reason.\nHH_INLINE int64x2_t vnegq_s64(int64x2_t v)\n{\n  const int64x2_t zero = vdupq_n_u64(0);\n  return vsubq_s64(zero, v);\n}\n#endif\n#ifndef vceqq_u64\nHH_INLINE uint64x2_t vceqq_u64(uint64x2_t v1, uint64x2_t v2)\n{\n  uint32x4_t comparison = vceqq_u32(vreinterpretq_u32_u64(v1),\n                                    vreinterpretq_u32_u64(v2));\n  return vreinterpretq_u64_u32(vandq_u32(comparison, vrev64q_u32(comparison)));\n}\n#endif // vnegq_s64\n#endif // !__aarch64__ && !__arm64__\n\n// Pseudo-instructions.\n// _mm_storel_epi64\nHH_INLINE void vst1q_low_u64(uint64_t* a, uint64x2_t b)\n{\n  uint64x1_t lo = vget_low_u64(b);\n  vst1_u8(reinterpret_cast<uint8_t*>(a), vreinterpret_u8_u64(lo));\n}\n// _mm_loadl_epi64\nHH_INLINE uint64x2_t vld1q_low_u64(const uint64_t* p)\n{\n  return vcombine_u64(\n           vld1_u64(p),\n           vdup_n_u64(0)\n         );\n}\n// _mm_slli_si128 (almost)\n#define vshlq_n_u128(a, imm) ( \\\n  vreinterpretq_u64_u8( \\\n    vextq_u8( \\\n      vdupq_n_u8(0), \\\n      vreinterpretq_u8_u64(a), \\\n      16 - (imm) \\\n    ) \\\n  ) \\\n)\n\n// Adapted from xsimd.\n// arm_neon.h requires literals for their parameters in many\n// functions, such as vshrq_n_u64, and it will complain even when\n// the value is known at compile-time.\n#define EXPAND(...) __VA_ARGS__\n\n#define CASE(op, i, ...)                   \\\n    case i: v_ = op(__VA_ARGS__, i); break;\n\n#define INTRINSIC_REPEAT_8_0(op, addx, ...)    \\\n    CASE(EXPAND(op), 1 + addx, __VA_ARGS__);       \\\n    CASE(EXPAND(op), 2 + addx, __VA_ARGS__);       \\\n    CASE(EXPAND(op), 3 + addx, __VA_ARGS__);       \\\n    CASE(EXPAND(op), 4 + addx, __VA_ARGS__);       \\\n    CASE(EXPAND(op), 5 + addx, __VA_ARGS__);       \\\n    CASE(EXPAND(op), 6 + addx, __VA_ARGS__);       \\\n    CASE(EXPAND(op), 7 + addx, __VA_ARGS__);\n\n#define INTRINSIC_REPEAT_8_N(op, addx, ...)    \\\n    CASE(EXPAND(op), 0 + addx, __VA_ARGS__);       \\\n    INTRINSIC_REPEAT_8_0(op, addx, __VA_ARGS__);\n\n#define INTRINSIC_REPEAT_8(op, ...)            \\\n    INTRINSIC_REPEAT_8_0(op, 0, __VA_ARGS__);\n\n#define INTRINSIC_REPEAT_16_0(op, addx,...)   \\\n    INTRINSIC_REPEAT_8_0(op, 0 + addx, __VA_ARGS__);   \\\n    INTRINSIC_REPEAT_8_N(op, 8 + addx, __VA_ARGS__);\n\n#define INTRINSIC_REPEAT_16_N(op, addx, ...)   \\\n    INTRINSIC_REPEAT_8_N(op, 0 + addx, __VA_ARGS__);   \\\n    INTRINSIC_REPEAT_8_N(op, 8 + addx, __VA_ARGS__);\n\n#define INTRINSIC_EACH_16(op, ...)           \\\n    INTRINSIC_REPEAT_16_0(op, 0, __VA_ARGS__);\n\n#define INTRINSIC_REPEAT_32_0(op, addx, ...)   \\\n    INTRINSIC_REPEAT_16_0(op, 0 + addx, __VA_ARGS__);  \\\n    INTRINSIC_REPEAT_16_N(op, 16 + addx, __VA_ARGS__);\n\n#define INTRINSIC_REPEAT_32_N(op, addx, ...)   \\\n    INTRINSIC_REPEAT_16_N(op, 0 + addx, __VA_ARGS__);  \\\n    INTRINSIC_REPEAT_16_N(op, 16 + addx, __VA_ARGS__);\n\n#define INTRINSIC_EACH_32(op, ...)           \\\n    INTRINSIC_REPEAT_32_0(op, 0, __VA_ARGS__);\n\n#define INTRINSIC_EACH_64(op, ...)           \\\n    INTRINSIC_REPEAT_32_0(op, 0, __VA_ARGS__);         \\\n    INTRINSIC_REPEAT_32_N(op, 32, __VA_ARGS__);\n\n// Primary template for 128-bit SSE4.1 vectors; only specializations are used.\ntemplate <typename T>\nclass V128 {};\n\ntemplate <>\nclass V128<uint8_t>\n{\npublic:\n  using Intrinsic = uint8x16_t;\n  using T = uint8_t;\n  static constexpr size_t N = 16;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V128() {}\n\n  // Sets all lanes to the same value.\n  HH_INLINE explicit V128(T i) : v_(vdupq_n_u8(i)) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V128(const V128& other) : v_(other.v_) {}\n\n  // C-style cast because vector casts are stupid on NEON.\n  template <typename U>\n  HH_INLINE explicit V128(const V128<U>& other) : v_((const uint8x16_t)(other)) {}\n  HH_INLINE V128& operator=(const V128& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V128(const Intrinsic& v) : v_(v) {}\n\n  HH_INLINE V128& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  // There are no greater-than comparison instructions for unsigned T.\n  HH_INLINE V128 operator==(const V128& other) const\n  {\n    return V128(vceqq_u8(v_, other.v_));\n  }\n\n  HH_INLINE V128& operator+=(const V128& other)\n  {\n    v_ = vaddq_u8(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator-=(const V128& other)\n  {\n    v_ = vsubq_u8(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator&=(const V128& other)\n  {\n    v_ = vandq_u8(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator|=(const V128& other)\n  {\n    v_ = vorrq_u8(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator^=(const V128& other)\n  {\n    v_ = veorq_u8(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& AndNot(const Intrinsic& neg_mask)\n  {\n    v_ = vbicq_u8(v_, neg_mask);\n    return *this;\n  }\nprivate:\n  Intrinsic v_;\n};\n\ntemplate <>\nclass V128<uint16_t>\n{\npublic:\n  using Intrinsic = uint16x8_t;\n  using T = uint16_t;\n  static constexpr size_t N = 8;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V128() {}\n\n  // Lane 0 (p_0) is the lowest.\n  HH_INLINE V128(T p_7, T p_6, T p_5, T p_4, T p_3, T p_2, T p_1, T p_0)\n  {\n    alignas(16) const uint16_t data[8] = {\n      p_0, p_1, p_2, p_3, p_4, p_5, p_6, p_7\n    };\n    v_ = vld1q_u16(data);\n  }\n\n  // Broadcasts i to all lanes (usually by loading from memory).\n  HH_INLINE explicit V128(T i) : v_(vdupq_n_u16(i)) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V128(const V128& other) : v_(other.v_) {}\n  template <typename U>\n  HH_INLINE explicit V128(const V128<U>& other) : v_(other) {}\n  HH_INLINE V128& operator=(const V128& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V128(const Intrinsic& v) : v_(v) {}\n  HH_INLINE V128& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  // There are no greater-than comparison instructions for unsigned T.\n  HH_INLINE V128 operator==(const V128& other) const\n  {\n    return V128(vceqq_u16(v_, other.v_));\n  }\n\n  HH_INLINE V128& operator+=(const V128& other)\n  {\n    v_ = vaddq_u16(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator-=(const V128& other)\n  {\n    v_ = vsubq_u16(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator&=(const V128& other)\n  {\n    v_ = vandq_u16(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator|=(const V128& other)\n  {\n    v_ = vorrq_u16(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator^=(const V128& other)\n  {\n    v_ = veorq_u16(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator<<=(const int count)\n  {\n    switch (count) {\n      INTRINSIC_EACH_16(vshlq_n_u16, v_)\n    }\n\n    return *this;\n  }\n  HH_INLINE V128& operator<<=(const Intrinsic& count)\n  {\n    v_ = vshlq_u16(v_, vreinterpretq_s16_u16(count));\n    return *this;\n  }\n\n  HH_INLINE V128& operator>>=(const int count)\n  {\n    switch (count) {\n      INTRINSIC_EACH_16(vshrq_n_u16, v_)\n    }\n\n    return *this;\n  }\n  HH_INLINE V128& operator>>=(const Intrinsic& count)\n  {\n    v_ = vshlq_u16(v_, vnegq_s16(vreinterpretq_s16_u16(count)));\n    return *this;\n  }\n\n  HH_INLINE V128& ShiftRightInsert(const Intrinsic& value, const int count)\n  {\n    switch (count) {\n      INTRINSIC_EACH_16(vsriq_n_u16, v_, value)\n    }\n\n    return *this;\n  }\n\n  HH_INLINE V128& AndNot(const Intrinsic& neg_mask)\n  {\n    v_ = vbicq_u16(v_, neg_mask);\n    return *this;\n  }\nprivate:\n  Intrinsic v_;\n};\n\ntemplate <>\nclass V128<uint32_t>\n{\npublic:\n  using Intrinsic = uint32x4_t;\n  using T = uint32_t;\n  static constexpr size_t N = 4;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V128() {}\n\n  // Lane 0 (p_0) is the lowest.\n  HH_INLINE V128(T p_3, T p_2, T p_1, T p_0)\n  {\n    alignas(16) const T data[4] = {\n      p_0, p_1, p_2, p_3\n    };\n    v_ = vld1q_u32(data);\n  }\n\n  // Broadcasts i to all lanes (usually by loading from memory).\n  HH_INLINE explicit V128(T i) : v_(vdupq_n_u32(i)) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V128(const V128& other) : v_(other.v_) {}\n  template <typename U>\n  HH_INLINE explicit V128(const V128<U>& other) : v_((const uint32x4_t)other) {}\n  HH_INLINE V128& operator=(const V128& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V128(const Intrinsic& v) : v_(v) {}\n  HH_INLINE V128& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  // There are no greater-than comparison instructions for unsigned T.\n  HH_INLINE V128 operator==(const V128& other) const\n  {\n    return V128(vceqq_u32(v_, other.v_));\n  }\n\n  HH_INLINE V128& operator+=(const V128& other)\n  {\n    v_ = vaddq_u32(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator-=(const V128& other)\n  {\n    v_ = vsubq_u32(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator&=(const V128& other)\n  {\n    v_ = vandq_u32(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator|=(const V128& other)\n  {\n    v_ = vorrq_u32(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator^=(const V128& other)\n  {\n    v_ = veorq_u32(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator<<=(const int count)\n  {\n    switch (count) {\n      INTRINSIC_EACH_32(vshlq_n_u32, v_)\n    }\n\n    return *this;\n  }\n  HH_INLINE V128& operator<<=(const Intrinsic& count)\n  {\n    v_ = vshlq_u32(v_, vreinterpretq_s32_u32(count));\n    return *this;\n  }\n\n  HH_INLINE V128& operator>>=(const int count)\n  {\n    switch (count) {\n      INTRINSIC_EACH_32(vshrq_n_u32, v_)\n    }\n\n    return *this;\n  }\n  HH_INLINE V128& operator>>=(const Intrinsic& count)\n  {\n    v_ = vshlq_u32(v_, vnegq_s32(vreinterpretq_s32_u32(count)));\n    return *this;\n  }\n\n  HH_INLINE V128& ShiftRightInsert(const Intrinsic& value, const int count)\n  {\n    switch (count) {\n      INTRINSIC_EACH_32(vsriq_n_u32, v_, value)\n    }\n\n    return *this;\n  }\n\n  HH_INLINE V128& AndNot(const Intrinsic& neg_mask)\n  {\n    v_ = vbicq_u32(v_, neg_mask);\n    return *this;\n  }\nprivate:\n  Intrinsic v_;\n};\n\ntemplate <>\nclass V128<uint64_t>\n{\npublic:\n  using Intrinsic = uint64x2_t;\n  using T = uint64_t;\n  static constexpr size_t N = 2;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V128() {}\n\n  // Lane 0 (p_0) is the lowest.\n  HH_INLINE V128(T p_1, T p_0)\n  {\n    alignas(16) const T data[2] = {\n      p_0, p_1\n    };\n    v_ = vld1q_u64(data);\n  }\n\n  // Broadcasts i to all lanes (usually by loading from memory).\n  HH_INLINE explicit V128(T i) : v_(vdupq_n_u64(i)) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V128(const V128& other) : v_(other.v_) {}\n  template <typename U>\n  HH_INLINE explicit V128(const V128<U>& other) : v_(other) {}\n  HH_INLINE V128& operator=(const V128& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V128(const Intrinsic& v) : v_(v) {}\n  HH_INLINE V128& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  // There are no greater-than comparison instructions for unsigned T.\n  HH_INLINE V128 operator==(const V128& other) const\n  {\n    return V128(vceqq_u64(v_, other.v_));\n  }\n\n  HH_INLINE V128& operator+=(const V128& other)\n  {\n    v_ = vaddq_u64(v_, other.v_);\n    // Prevent Clang from converting to vaddhn when nearby vmovn, which\n    // causes four spills in the main loop on ARMv7a.\n#ifdef __GNUC__\n    __asm__(\"\" : \"+w\"(v_));\n#endif\n    return *this;\n  }\n  HH_INLINE V128& operator-=(const V128& other)\n  {\n    v_ = vsubq_u64(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator&=(const V128& other)\n  {\n    v_ = vandq_u64(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator|=(const V128& other)\n  {\n    v_ = vorrq_u64(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator^=(const V128& other)\n  {\n    v_ = veorq_u64(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator<<=(const int count)\n  {\n    switch (count) {\n      INTRINSIC_EACH_64(vshlq_n_u64, v_)\n    }\n\n    return *this;\n  }\n  HH_INLINE V128& operator<<=(const Intrinsic& count)\n  {\n    v_ = vshlq_u64(v_, vreinterpretq_s64_u64(count));\n    return *this;\n  }\n\n  HH_INLINE V128& operator>>=(const int count)\n  {\n    switch (count) {\n      INTRINSIC_EACH_64(vshrq_n_u64, v_)\n    }\n\n    return *this;\n  }\n  HH_INLINE V128& operator>>=(const Intrinsic& count)\n  {\n    v_ = vshlq_u64(v_, vnegq_s64(vreinterpretq_s64_u64(count)));\n    return *this;\n  }\n\n  HH_INLINE V128& AndNot(const Intrinsic& neg_mask)\n  {\n    v_ = vbicq_u64(v_, neg_mask);\n    return *this;\n  }\n\n  HH_INLINE V128& ShiftRightInsert(const Intrinsic& value, const int count)\n  {\n    switch (count) {\n      INTRINSIC_EACH_64(vsriq_n_u64, v_, value)\n    }\n\n    return *this;\n  }\n\nprivate:\n  Intrinsic v_;\n};\n\n// TODO: Enable. For now, this is disabled for the following reasons:\n//   1. ARMv7a lacks float64x2_t.\n//   2. ARMv7a's float32x4_t is not IEE-754 compliant\n//   3. We don't actually use the float vectors right now.\n#if 0\ntemplate <>\nclass V128<float>\n{\npublic:\n  using Intrinsic = float32x4_t;\n  using T = float;\n  static constexpr size_t N = 4;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V128() {}\n\n  // Lane 0 (p_0) is the lowest.\n  HH_INLINE V128(T p_3, T p_2, T p_1, T p_0)\n  {\n    HH_ALIGNAS(16) float tmp[4] = { p_0, p_1, p_2, p_3 };\n    v_ = vld1q_f32(tmp);\n  }\n\n  // Broadcasts to all lanes.\n  HH_INLINE explicit V128(T f) : v_(vdupq_n_f32(f)) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V128(const V128& other) : v_(other.v_) {}\n  template <typename U>\n  HH_INLINE explicit V128(const V128<U>& other) : v_(other) {}\n  HH_INLINE V128& operator=(const V128& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V128(const Intrinsic& v) : v_(v) {}\n  HH_INLINE V128& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  HH_INLINE V128 operator==(const V128& other) const\n  {\n    return V128(vceqq_f32(v_, other.v_));\n  }\n  HH_INLINE V128 operator<(const V128& other) const\n  {\n    return V128(vcltq_f32(v_, other.v_));\n  }\n  HH_INLINE V128 operator>(const V128& other) const\n  {\n    return V128(vcltq_f32(other.v_, v_));\n  }\n\n  HH_INLINE V128& operator*=(const V128& other)\n  {\n    v_ = vmulq_f32(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator/=(const V128& other)\n  {\n    v_ = vdivq_f32(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator+=(const V128& other)\n  {\n    v_ = vaddq_f32(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator-=(const V128& other)\n  {\n    v_ = vsubq_f32(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator&=(const V128& other)\n  {\n    v_ = vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(v_),\n                                         vreinterpretq_u32_f32(other.v_)));\n    return *this;\n  }\n  HH_INLINE V128& operator|=(const V128& other)\n  {\n    v_ = vreinterpretq_f32_u32(vorrq_u32(vreinterpretq_u32_f32(v_),\n                                         vreinterpretq_u32_f32(other.v_)));\n    return *this;\n  }\n  HH_INLINE V128& operator^=(const V128& other)\n  {\n    v_ = vreinterpretq_f32_u32(veorq_u32(vreinterpretq_u32_f32(v_),\n                                         vreinterpretq_u32_f32(other.v_)));\n    return *this;\n  }\n\nprivate:\n  Intrinsic v_;\n};\n\ntemplate <>\nclass V128<double>\n{\npublic:\n  using Intrinsic = float64x2_t;\n  using T = double;\n  static constexpr size_t N = 2;\n\n  // Leaves v_ uninitialized - typically used for output parameters.\n  HH_INLINE V128() {}\n\n  // Lane 0 (p_0) is the lowest.\n  HH_INLINE V128(T p_1, T p_0)\n  {\n    HH_ALIGNAS(16) double tmp[2] = { p_0, p_1 };\n    v_ = vld1q_f64(tmp);\n  }\n\n  // Broadcasts to all lanes.\n  HH_INLINE explicit V128(T f) : v_(vdupq_n_f64(f)) {}\n\n  // Copy from other vector.\n  HH_INLINE explicit V128(const V128& other) : v_(other.v_) {}\n  template <typename U>\n  HH_INLINE explicit V128(const V128<U>& other) : v_(other) {}\n  HH_INLINE V128& operator=(const V128& other)\n  {\n    v_ = other.v_;\n    return *this;\n  }\n\n  // Convert from/to intrinsics.\n  HH_INLINE V128(const Intrinsic& v) : v_(v) {}\n  HH_INLINE V128& operator=(const Intrinsic& v)\n  {\n    v_ = v;\n    return *this;\n  }\n  HH_INLINE operator Intrinsic() const\n  {\n    return v_;\n  }\n\n  HH_INLINE V128 operator==(const V128& other) const\n  {\n    return V128(vceqq_f64(v_, other.v_));\n  }\n  HH_INLINE V128 operator<(const V128& other) const\n  {\n    return V128(vcltq_f64(v_, other.v_));\n  }\n  HH_INLINE V128 operator>(const V128& other) const\n  {\n    return V128(vcltq_f64(other.v_, v_));\n  }\n\n  HH_INLINE V128& operator*=(const V128& other)\n  {\n    v_ = vmulq_f64(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator/=(const V128& other)\n  {\n    v_ = vdivq_f64(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator+=(const V128& other)\n  {\n    v_ = vaddq_f64(v_, other.v_);\n    return *this;\n  }\n  HH_INLINE V128& operator-=(const V128& other)\n  {\n    v_ = vsubq_f64(v_, other.v_);\n    return *this;\n  }\n\n  HH_INLINE V128& operator&=(const V128& other)\n  {\n    v_ = vreinterpretq_f64_u64(vandq_u64(vreinterpretq_u64_f64(v_),\n                                         vreinterpretq_u64_f64(other.v_)));\n    return *this;\n  }\n  HH_INLINE V128& operator|=(const V128& other)\n  {\n    v_ = vreinterpretq_f64_u64(vorrq_u64(vreinterpretq_u64_f64(v_),\n                                         vreinterpretq_u64_f64(other.v_)));\n    return *this;\n  }\n  HH_INLINE V128& operator^=(const V128& other)\n  {\n    v_ = vreinterpretq_f64_u64(veorq_u64(vreinterpretq_u64_f64(v_),\n                                         vreinterpretq_u64_f64(other.v_)));\n    return *this;\n  }\n\nprivate:\n  Intrinsic v_;\n};\n#endif\n\n// Nonmember functions for any V128 via member functions.\n\ntemplate <typename T>\nHH_INLINE V128<T> operator*(const V128<T>& left, const V128<T>& right)\n{\n  V128<T> t(left);\n  return t *= right;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator/(const V128<T>& left, const V128<T>& right)\n{\n  V128<T> t(left);\n  return t /= right;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator+(const V128<T>& left, const V128<T>& right)\n{\n  V128<T> t(left);\n  return t += right;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator-(const V128<T>& left, const V128<T>& right)\n{\n  V128<T> t(left);\n  return t -= right;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator&(const V128<T>& left, const V128<T>& right)\n{\n  V128<T> t(left);\n  return t &= right;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator|(const V128<T>& left, const V128<T>& right)\n{\n  V128<T> t(left);\n  return t |= right;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator^(const V128<T>& left, const V128<T>& right)\n{\n  V128<T> t(left);\n  return t ^= right;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator<<(const V128<T>& v, const int count)\n{\n  V128<T> t(v);\n  return t <<= count;\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> operator>>(const V128<T>& v, const int count)\n{\n  V128<T> t(v);\n  return t >>= count;\n}\n\nusing V16x8U = V128<uint8_t>;\nusing V8x16U = V128<uint16_t>;\nusing V4x32U = V128<uint32_t>;\nusing V2x64U = V128<uint64_t>;\nusing V4x32F = V128<float>;\nusing V2x64F = V128<double>;\n\n// Load/Store for any V128.\n\n// We differentiate between targets' vector types via template specialization.\n// Calling Load<V>(floats) is more natural than Load(V8x32F(), floats) and may\n// generate better code in unoptimized builds. Only declare the primary\n// templates to avoid needing mutual exclusion with vector256.\n\ntemplate <class V>\nHH_INLINE V Load(const typename V::T* const HH_RESTRICT from);\n\ntemplate <class V>\nHH_INLINE V LoadUnaligned(const typename V::T* const HH_RESTRICT from);\n\n// \"from\" must be vector-aligned.\n#ifdef __GNUC__\n#define HH_ALIGN(x) __builtin_assume_aligned((x), 16)\n#else\n#define HH_ALIGN(x) (x)\n#endif\ntemplate <>\nHH_INLINE V16x8U Load<V16x8U>(const V16x8U::T* const HH_RESTRICT from)\n{\n  const uint8_t* const HH_RESTRICT p = reinterpret_cast<const uint8_t*>(HH_ALIGN(\n                                         from));\n  return V16x8U(vld1q_u8(p));\n}\ntemplate <>\nHH_INLINE V8x16U Load<V8x16U>(const V8x16U::T* const HH_RESTRICT from)\n{\n  const uint16_t* const HH_RESTRICT p = reinterpret_cast<const uint16_t*>\n                                        (HH_ALIGN(from));\n  return V8x16U(vld1q_u16(p));\n}\ntemplate <>\nHH_INLINE V4x32U Load<V4x32U>(const V4x32U::T* const HH_RESTRICT from)\n{\n  const uint32_t* const HH_RESTRICT p = reinterpret_cast<const uint32_t*>\n                                        (HH_ALIGN(from));\n  return V4x32U(vld1q_u32(p));\n}\ntemplate <>\nHH_INLINE V2x64U Load<V2x64U>(const V2x64U::T* const HH_RESTRICT from)\n{\n  const uint64_t* const HH_RESTRICT p = reinterpret_cast<const uint64_t*>\n                                        (HH_ALIGN(from));\n  return V2x64U(vld1q_u64(p));\n}\n#if 0\ntemplate <>\nHH_INLINE V4x32F Load<V4x32F>(const V4x32F::T* const HH_RESTRICT from)\n{\n  return V4x32F(vld1q_f32(from));\n}\ntemplate <>\nHH_INLINE V2x64F Load<V2x64F>(const V2x64F::T* const HH_RESTRICT from)\n{\n  return V2x64F(vld1q_f64(from));\n}\n#endif\n// GCC for ARM 32-bit flips out on unaligned reads after a cast.\n// Only vld1q_u8 is safe on unaligned pointers.\ntemplate <>\nHH_INLINE V16x8U\nLoadUnaligned<V16x8U>(const V16x8U::T* const HH_RESTRICT from)\n{\n  const uint8_t* const HH_RESTRICT p = reinterpret_cast<const uint8_t*>(from);\n  return V16x8U(vld1q_u8(p));\n}\ntemplate <>\nHH_INLINE V8x16U\nLoadUnaligned<V8x16U>(const V8x16U::T* const HH_RESTRICT from)\n{\n  const uint8_t* const HH_RESTRICT p = reinterpret_cast<const uint8_t*>(from);\n  return V8x16U(vreinterpretq_u16_u8(vld1q_u8(p)));\n}\ntemplate <>\nHH_INLINE V4x32U\nLoadUnaligned<V4x32U>(const V4x32U::T* const HH_RESTRICT from)\n{\n  const uint8_t* const HH_RESTRICT p = reinterpret_cast<const uint8_t*>(from);\n  return V4x32U(vreinterpretq_u32_u8(vld1q_u8(p)));\n}\ntemplate <>\nHH_INLINE V2x64U\nLoadUnaligned<V2x64U>(const V2x64U::T* const HH_RESTRICT from)\n{\n  const uint8_t* const HH_RESTRICT p = reinterpret_cast<const uint8_t*>(from);\n  return V2x64U(vreinterpretq_u64_u8(vld1q_u8(p)));\n}\n#if 0\ntemplate <>\nHH_INLINE V4x32F\nLoadUnaligned<V4x32F>(const V4x32F::T* const HH_RESTRICT from)\n{\n  return V4x32F(vld1q_f32(from));\n}\ntemplate <>\nHH_INLINE V2x64F\nLoadUnaligned<V2x64F>(const V2x64F::T* const HH_RESTRICT from)\n{\n  return V2x64F(vld1q_f64(from));\n}\n#endif\n\n// \"to\" must be vector-aligned.\ntemplate <typename T>\nHH_INLINE void Store(const V128<T>& v, T* const HH_RESTRICT to);\n\ntemplate<>\nHH_INLINE void Store<uint8_t>(const V128<uint8_t>& v, uint8_t* HH_RESTRICT to)\n{\n  uint8_t* const HH_RESTRICT p = reinterpret_cast<uint8_t*>(HH_ALIGN(to));\n  vst1q_u8(p, v);\n}\n\ntemplate<>\nHH_INLINE void Store<uint16_t>(const V128<uint16_t>& v,\n                               uint16_t* const HH_RESTRICT to)\n{\n  uint16_t* const HH_RESTRICT p = reinterpret_cast<uint16_t*>(HH_ALIGN(to));\n  vst1q_u16(p, v);\n}\n\ntemplate<>\nHH_INLINE void Store<uint32_t>(const V128<uint32_t>& v,\n                               uint32_t* const HH_RESTRICT to)\n{\n  uint32_t* const HH_RESTRICT p = reinterpret_cast<uint32_t*>(HH_ALIGN(to));\n  vst1q_u32(p, v);\n}\n\ntemplate<>\nHH_INLINE void Store<uint64_t>(const V128<uint64_t>& v,\n                               uint64_t* const HH_RESTRICT to)\n{\n  uint64_t* const HH_RESTRICT p = reinterpret_cast<uint64_t*>(HH_ALIGN(to));\n  vst1q_u64(p, v);\n}\n#undef HH_ALIGN\n\n#if 0\nHH_INLINE void Store(const V128<float>& v, float* const HH_RESTRICT to)\n{\n  vst1q_f32(to, v);\n}\nHH_INLINE void Store(const V128<double>& v, double* const HH_RESTRICT to)\n{\n  vst1q_f64(to, v);\n}\n#endif\ntemplate <typename T>\nHH_INLINE void StoreUnaligned(const V128<T>& v, T* const HH_RESTRICT to);\n\ntemplate<>\nHH_INLINE void StoreUnaligned<uint8_t>(const V128<uint8_t>& v,\n                                       uint8_t* const HH_RESTRICT to)\n{\n  vst1q_u8(to, v);\n}\n\ntemplate<>\nHH_INLINE void StoreUnaligned<uint16_t>(const V128<uint16_t>& v,\n                                        uint16_t* const HH_RESTRICT to)\n{\n  vst1q_u8(reinterpret_cast<uint8_t*>(to), vreinterpretq_u8_u16(v));\n}\n\ntemplate<>\nHH_INLINE void StoreUnaligned<uint32_t>(const V128<uint32_t>& v,\n                                        uint32_t* const HH_RESTRICT to)\n{\n  vst1q_u8(reinterpret_cast<uint8_t*>(to), vreinterpretq_u8_u32(v));\n}\n\ntemplate<>\nHH_INLINE void StoreUnaligned<uint64_t>(const V128<uint64_t>& v,\n                                        uint64_t* const HH_RESTRICT to)\n{\n  vst1q_u8(reinterpret_cast<uint8_t*>(to), vreinterpretq_u8_u64(v));\n}\n#if 0\nHH_INLINE void StoreUnaligned(const V128<float>& v,\n                              float* const HH_RESTRICT to)\n{\n  _mm_storeu_ps(to, v);\n}\nHH_INLINE void StoreUnaligned(const V128<double>& v,\n                              double* const HH_RESTRICT to)\n{\n  _mm_storeu_pd(to, v);\n}\n#endif\n\n// TODO: Enable.\n#if 0\n// Writes directly to (aligned) memory, bypassing the cache. This is useful for\n// data that will not be read again in the near future.\ntemplate <typename T>\nHH_INLINE void Stream(const V128<T>& v, T* const HH_RESTRICT to)\n{\n  _mm_stream_si128(reinterpret_cast<__m128i* HH_RESTRICT>(to), v);\n}\nHH_INLINE void Stream(const V128<float>& v, float* const HH_RESTRICT to)\n{\n  _mm_stream_ps(to, v);\n}\nHH_INLINE void Stream(const V128<double>& v, double* const HH_RESTRICT to)\n{\n  _mm_stream_pd(to, v);\n}\n#endif\n\n// Miscellaneous functions.\n\ntemplate <typename T>\nHH_INLINE V128<T> RotateLeft(const V128<T>& v, const int count)\n{\n  const size_t num_bits = sizeof(T) * 8;\n  const V128<T>& tmp = v << count;\n  return tmp.ShiftRightAccumulate(v, num_bits - count);\n}\n\ntemplate <typename T>\nHH_INLINE V128<T> AndNot(const V128<T>& neg_mask, const V128<T>& values)\n{\n  V128<T> tmp = values;\n  return tmp.AndNot(neg_mask);\n}\n#if 0\ntemplate <>\nHH_INLINE V128<float> AndNot(const V128<float>& neg_mask,\n                             const V128<float>& values)\n{\n  return V128<float>(_mm_andnot_ps(neg_mask, values));\n}\ntemplate <>\nHH_INLINE V128<double> AndNot(const V128<double>& neg_mask,\n                              const V128<double>& values)\n{\n  return V128<double>(_mm_andnot_pd(neg_mask, values));\n}\n#endif\nHH_INLINE V4x32U Select(const V4x32U& a, const V4x32U& b, const V4x32U& mask)\n{\n  return V4x32U(vbslq_u32(mask, a, b));\n}\n\nHH_INLINE V2x64U Select(const V2x64U& a, const V2x64U& b, const V2x64U& mask)\n{\n  return V2x64U(vbslq_u64(mask, a, b));\n}\n\n// Min/Max\n\nHH_INLINE V16x8U Min(const V16x8U& v0, const V16x8U& v1)\n{\n  return V16x8U(vminq_u8(v0, v1));\n}\n\nHH_INLINE V16x8U Max(const V16x8U& v0, const V16x8U& v1)\n{\n  return V16x8U(vmaxq_u8(v0, v1));\n}\n\nHH_INLINE V8x16U Min(const V8x16U& v0, const V8x16U& v1)\n{\n  return V8x16U(vminq_u16(v0, v1));\n}\n\nHH_INLINE V8x16U Max(const V8x16U& v0, const V8x16U& v1)\n{\n  return V8x16U(vmaxq_u16(v0, v1));\n}\n\nHH_INLINE V4x32U Min(const V4x32U& v0, const V4x32U& v1)\n{\n  return V4x32U(vminq_u32(v0, v1));\n}\n\nHH_INLINE V4x32U Max(const V4x32U& v0, const V4x32U& v1)\n{\n  return V4x32U(vmaxq_u32(v0, v1));\n}\n\n\n#if 0\nHH_INLINE V4x32F Min(const V4x32F& v0, const V4x32F& v1)\n{\n  return V4x32F(vminq_f32(v0, v1));\n}\n\nHH_INLINE V4x32F Max(const V4x32F& v0, const V4x32F& v1)\n{\n  return V4x32F(vmaxq_f32(v0, v1));\n}\n\nHH_INLINE V2x64F Min(const V2x64F& v0, const V2x64F& v1)\n{\n  return V2x64F(vminq_f64(v0, v1));\n}\n\nHH_INLINE V2x64F Max(const V2x64F& v0, const V2x64F& v1)\n{\n  return V2x64F(vmaxq_f64(v0, v1));\n}\n#endif\n\n}  // namespace HH_TARGET_NAME\n}  // namespace highwayhash\n\n#endif  // HH_DISABLE_TARGET_SPECIFIC\n#endif  // HIGHWAYHASH_VECTOR128_H_\n"
  },
  {
    "path": "common/highwayhash/vector_test_target.h",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n#ifndef HIGHWAYHASH_VECTOR_TEST_TARGET_H_\n#define HIGHWAYHASH_VECTOR_TEST_TARGET_H_\n\n// WARNING: this is a \"restricted\" header because it is included from\n// translation units compiled with different flags. This header and its\n// dependencies must not define any function unless it is static inline and/or\n// within namespace HH_TARGET_NAME. See arch_specific.h for details.\n\n#include \"highwayhash/arch_specific.h\"\n#include \"highwayhash/hh_types.h\"\n\nnamespace highwayhash\n{\n\n// Usage: InstructionSets::RunAll<VectorTest>(). Calls \"notify\" for each test\n// failure.\ntemplate <TargetBits Target>\nstruct VectorTest {\n  void operator()(const HHNotify notify) const;\n};\n\n}  // namespace highwayhash\n\n#endif  // HIGHWAYHASH_VECTOR_TEST_TARGET_H_\n"
  },
  {
    "path": "common/hopscotch_hash.hh",
    "content": "/**\n * MIT License\n *\n * Copyright (c) 2017 Tessil\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n#ifndef TSL_HOPSCOTCH_HASH_H\n#define TSL_HOPSCOTCH_HASH_H\n\n\n#include <algorithm>\n#include <array>\n#include <cassert>\n#include <cmath>\n#include <climits>\n#include <cstddef>\n#include <cstdint>\n#include <exception>\n#include <functional>\n#include <initializer_list>\n#include <iterator>\n#include <limits>\n#include <memory>\n#include <ratio>\n#include <stdexcept>\n#include <tuple>\n#include <type_traits>\n#include <utility>\n#include <vector>\n\n#if (defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ < 9))\n#define TSL_NO_RANGE_ERASE_WITH_CONST_ITERATOR\n#endif\n\n\n\n/*\n * Only activate tsl_assert if TSL_DEBUG is defined.\n * This way we avoid the performance hit when NDEBUG is not defined with assert as tsl_assert is used a lot\n * (people usually compile with \"-O3\" and not \"-O3 -DNDEBUG\").\n */\n#ifndef tsl_assert\n#ifdef TSL_DEBUG\n#define tsl_assert(expr) assert(expr)\n#else\n#define tsl_assert(expr) (static_cast<void>(0))\n#endif\n#endif\n\nnamespace tsl\n{\n\n/**\n * Grow the map by a factor of two keeping bucket_count to a power of two. It allows\n * the map to use a mask operation instead of a modulo operation to map a hash to a bucket.\n */\nclass power_of_two_growth_policy\n{\npublic:\n  /**\n   * Called on map creation and rehash. The number of buckets requested is passed by parameter.\n   * This number is a minimum, the policy may update this value with a higher value if needed.\n   */\n  power_of_two_growth_policy(std::size_t& min_bucket_count_in_out)\n  {\n    if (min_bucket_count_in_out > max_bucket_count()) {\n      throw std::length_error(\"The map exceeds its maxmimum size.\");\n    }\n\n    static_assert(MIN_BUCKETS_SIZE > 0, \"\");\n    const std::size_t min_bucket_count = MIN_BUCKETS_SIZE;\n    min_bucket_count_in_out = std::max(min_bucket_count, min_bucket_count_in_out);\n    min_bucket_count_in_out = round_up_to_power_of_two(min_bucket_count_in_out);\n    m_mask = min_bucket_count_in_out - 1;\n  }\n\n  /**\n   * Return the bucket [0, bucket_count()) to which the hash belongs.\n   */\n  std::size_t bucket_for_hash(std::size_t hash) const\n  {\n    return hash & m_mask;\n  }\n\n  /**\n   * Return the bucket count to uses when the bucket array grows on rehash.\n   */\n  std::size_t next_bucket_count() const\n  {\n    if ((m_mask + 1) > max_bucket_count() / 2) {\n      throw std::length_error(\"The map exceeds its maxmimum size.\");\n    }\n\n    return (m_mask + 1) * 2;\n  }\n\n  /**\n   * Return the maximum number of buckets supported by the policy.\n   */\n  std::size_t max_bucket_count() const\n  {\n    return std::numeric_limits<std::size_t>::max() / 2 + 1;\n  }\n\nprivate:\n  static std::size_t round_up_to_power_of_two(std::size_t value)\n  {\n    if (value == 0) {\n      return 1;\n    }\n\n    if (is_power_of_two(value)) {\n      return value;\n    }\n\n    --value;\n\n    for (std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) {\n      value |= value >> i;\n    }\n\n    return value + 1;\n  }\n\n  static constexpr bool is_power_of_two(std::size_t value)\n  {\n    return value != 0 && (value & (value - 1)) == 0;\n  }\n\nprivate:\n  static const std::size_t MIN_BUCKETS_SIZE = 2;\n\n  std::size_t m_mask;\n};\n\n/**\n * Grow the map by GrowthFactor::num/GrowthFactor::den and use a modulo to map a hash\n * to a bucket. Slower but it can be usefull if you want a slower growth.\n */\ntemplate<class GrowthFactor = std::ratio<3, 2>>\nclass mod_growth_policy\n{\npublic:\n  mod_growth_policy(std::size_t& min_bucket_count_in_out)\n  {\n    if (min_bucket_count_in_out > max_bucket_count()) {\n      throw std::length_error(\"The map exceeds its maxmimum size.\");\n    }\n\n    static_assert(MIN_BUCKETS_SIZE > 0, \"\");\n    const std::size_t min_bucket_count = MIN_BUCKETS_SIZE;\n    min_bucket_count_in_out = std::max(min_bucket_count, min_bucket_count_in_out);\n    m_bucket_count = min_bucket_count_in_out;\n  }\n\n  std::size_t bucket_for_hash(std::size_t hash) const\n  {\n    tsl_assert(m_bucket_count != 0);\n    return hash % m_bucket_count;\n  }\n\n  std::size_t next_bucket_count() const\n  {\n    if (m_bucket_count == max_bucket_count()) {\n      throw std::length_error(\"The map exceeds its maxmimum size.\");\n    }\n\n    const double next_bucket_count = std::ceil(double(m_bucket_count) *\n                                     REHASH_SIZE_MULTIPLICATION_FACTOR);\n\n    if (!std::isnormal(next_bucket_count)) {\n      throw std::length_error(\"The map exceeds its maxmimum size.\");\n    }\n\n    if (next_bucket_count > double(max_bucket_count())) {\n      return max_bucket_count();\n    } else {\n      return std::size_t(next_bucket_count);\n    }\n  }\n\n  std::size_t max_bucket_count() const\n  {\n    return MAX_BUCKET_COUNT;\n  }\n\nprivate:\n  static const std::size_t MIN_BUCKETS_SIZE = 2;\n  static constexpr double REHASH_SIZE_MULTIPLICATION_FACTOR = 1.0 *\n      GrowthFactor::num / GrowthFactor::den;\n  static const std::size_t MAX_BUCKET_COUNT =\n    std::size_t(double(\n                  std::numeric_limits<std::size_t>::max() / REHASH_SIZE_MULTIPLICATION_FACTOR\n                ));\n\n  static_assert(REHASH_SIZE_MULTIPLICATION_FACTOR >= 1.1,\n                \"Growth factor should be >= 1.1.\");\n\n  std::size_t m_bucket_count;\n};\n\n\n\nnamespace detail_hopscotch_hash\n{\n\nstatic constexpr const std::array<std::size_t, 39> PRIMES = {{\n    5ul, 17ul, 29ul, 37ul, 53ul, 67ul, 79ul, 97ul, 131ul, 193ul, 257ul, 389ul, 521ul, 769ul, 1031ul, 1543ul, 2053ul,\n    3079ul, 6151ul, 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, 393241ul, 786433ul, 1572869ul, 3145739ul,\n    6291469ul, 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,\n    1610612741ul, 3221225473ul, 4294967291ul\n  }\n};\n\ntemplate<unsigned int IPrime>\nstatic std::size_t mod(std::size_t hash)\n{\n  return hash % PRIMES[IPrime];\n}\n\n// MOD_PRIME[iprime](hash) returns hash % PRIMES[iprime]. This table allows for faster modulo as the\n// compiler can optimize the modulo code better with a constant known at the compilation.\nstatic constexpr const std::array<std::size_t(*)(std::size_t), 39> MOD_PRIME\n= {{\n    &mod<0>, &mod<1>, &mod<2>, &mod<3>, &mod<4>, &mod<5>, &mod<6>, &mod<7>, &mod<8>, &mod<9>, &mod<10>,\n    &mod<11>, &mod<12>, &mod<13>, &mod<14>, &mod<15>, &mod<16>, &mod<17>, &mod<18>, &mod<19>, &mod<20>,\n    &mod<21>, &mod<22>, &mod<23>, &mod<24>, &mod<25>, &mod<26>, &mod<27>, &mod<28>, &mod<29>, &mod<30>,\n    &mod<31>, &mod<32>, &mod<33>, &mod<34>, &mod<35>, &mod<36>, &mod<37> , &mod<38>\n  }\n};\n\n}\n\n/**\n * Grow the map by using prime numbers as size. Slower than tsl::power_of_two_growth_policy in general\n * but will probably distribute the values around better in the buckets with a poor hash function.\n */\nclass prime_growth_policy\n{\npublic:\n  prime_growth_policy(std::size_t& min_bucket_count_in_out)\n  {\n    auto it_prime = std::lower_bound(tsl::detail_hopscotch_hash::PRIMES.begin(),\n                                     tsl::detail_hopscotch_hash::PRIMES.end(), min_bucket_count_in_out);\n\n    if (it_prime == tsl::detail_hopscotch_hash::PRIMES.end()) {\n      throw std::length_error(\"The map exceeds its maxmimum size.\");\n    }\n\n    m_iprime = static_cast<unsigned int>(std::distance(\n                                           tsl::detail_hopscotch_hash::PRIMES.begin(), it_prime));\n    min_bucket_count_in_out = *it_prime;\n  }\n\n  std::size_t bucket_for_hash(std::size_t hash) const\n  {\n    return bucket_for_hash_iprime(hash, m_iprime);\n  }\n\n  std::size_t next_bucket_count() const\n  {\n    if (m_iprime + 1 >= tsl::detail_hopscotch_hash::PRIMES.size()) {\n      throw std::length_error(\"The map exceeds its maxmimum size.\");\n    }\n\n    return tsl::detail_hopscotch_hash::PRIMES[m_iprime + 1];\n  }\n\n  std::size_t max_bucket_count() const\n  {\n    return tsl::detail_hopscotch_hash::PRIMES.back();\n  }\n\nprivate:\n  std::size_t bucket_for_hash_iprime(std::size_t hash, unsigned int iprime) const\n  {\n    tsl_assert(iprime < tsl::detail_hopscotch_hash::MOD_PRIME.size());\n    return tsl::detail_hopscotch_hash::MOD_PRIME[iprime](hash);\n  }\n\nprivate:\n  unsigned int m_iprime;\n};\n\n\nnamespace detail_hopscotch_hash\n{\n\n\n\ntemplate<typename T>\nstruct make_void {\n  using type = void;\n};\n\n\ntemplate<typename T, typename = void>\nstruct has_is_transparent : std::false_type {\n};\n\ntemplate<typename T>\nstruct has_is_transparent<T, typename make_void<typename T::is_transparent>::type> :\n  std::true_type {\n};\n\n\ntemplate<typename T, typename = void>\nstruct has_key_compare : std::false_type {\n};\n\ntemplate<typename T>\nstruct has_key_compare<T, typename make_void<typename T::key_compare>::type> :\n  std::true_type {\n};\n\n\n\n\n\n/*\n * smallest_type_for_min_bits::type returns the smallest type that can fit MinBits.\n */\nstatic const size_t SMALLEST_TYPE_MAX_BITS_SUPPORTED = 64;\ntemplate<unsigned int MinBits, typename Enable = void>\nclass smallest_type_for_min_bits\n{\n};\n\ntemplate<unsigned int MinBits>\nclass smallest_type_for_min_bits < MinBits,\n        typename std::enable_if < (MinBits > 0)&& (MinBits <= 8) >::type >\n{\npublic:\n  using type = std::uint_least8_t;\n};\n\ntemplate<unsigned int MinBits>\nclass smallest_type_for_min_bits < MinBits,\n        typename std::enable_if < (MinBits > 8)&& (MinBits <= 16) >::type >\n{\npublic:\n  using type = std::uint_least16_t;\n};\n\ntemplate<unsigned int MinBits>\nclass smallest_type_for_min_bits < MinBits,\n        typename std::enable_if < (MinBits > 16)&& (MinBits <= 32) >::type >\n{\npublic:\n  using type = std::uint_least32_t;\n};\n\ntemplate<unsigned int MinBits>\nclass smallest_type_for_min_bits < MinBits,\n        typename std::enable_if < (MinBits > 32)&& (MinBits <= 64) >::type >\n{\npublic:\n  using type = std::uint_least64_t;\n};\n\n\n\n/*\n * Each bucket may store up to three elements:\n * - An aligned storage to store a value_type object with placement-new.\n * - An (optional) hash of the value in the bucket.\n * - An unsigned integer of type neighborhood_bitmap used to tell us which buckets in the neighborhood of the\n *   current bucket contain a value with a hash belonging to the current bucket.\n *\n * For a bucket 'b' a bit 'i' (counting from 0 and from the least significant bit to the most significant)\n * set to 1 means that the bucket 'b+i' contains a value with a hash belonging to bucket 'b'.\n * The bits used for that, start from the third least significant bit.\n *\n * The least significant bit is set to 1 if there is a value in the bucket storage.\n * The second least significant bit is set to 1 if there is an overflow. More than NeighborhoodSize values\n * give the same hash, all overflow values are stored in the m_overflow_elements list of the map.\n */\nstatic const std::size_t NB_RESERVED_BITS_IN_NEIGHBORHOOD = 2;\n\n\ntemplate<bool StoreHash>\nclass hopscotch_bucket_hash\n{\npublic:\n  using hash_type = std::false_type;\n\n  bool bucket_hash_equal(std::size_t /*hash*/) const noexcept\n  {\n    return true;\n  }\n\n  std::size_t truncated_bucket_hash() const noexcept\n  {\n    assert(false);\n    return 0;\n  }\n\nprotected:\n  void copy_hash(const hopscotch_bucket_hash&) noexcept\n  {\n  }\n\n  void set_hash(std::size_t /*hash*/) noexcept\n  {\n  }\n};\n\ntemplate<>\nclass hopscotch_bucket_hash<true>\n{\npublic:\n  using hash_type = std::uint_least32_t;\n  static_assert(sizeof(hash_type) <= sizeof(std::size_t), \"\");\n\n  bool bucket_hash_equal(std::size_t hash) const noexcept\n  {\n    return m_hash == hash_type(hash);\n  }\n\n  std::size_t truncated_bucket_hash() const noexcept\n  {\n    return m_hash;\n  }\n\nprotected:\n  void copy_hash(const hopscotch_bucket_hash& bucket) noexcept\n  {\n    m_hash = bucket.m_hash;\n  }\n\n  void set_hash(std::size_t hash) noexcept\n  {\n    m_hash = hash_type(hash);\n  }\n\nprivate:\n  hash_type m_hash;\n};\n\ntemplate<typename ValueType, unsigned int NeighborhoodSize, bool StoreHash>\nclass hopscotch_bucket: public hopscotch_bucket_hash<StoreHash>\n{\nprivate:\n  static const size_t MIN_NEIGHBORHOOD_SIZE = 4;\n  static const size_t MAX_NEIGHBORHOOD_SIZE = SMALLEST_TYPE_MAX_BITS_SUPPORTED -\n      NB_RESERVED_BITS_IN_NEIGHBORHOOD;\n\n\n  static_assert(NeighborhoodSize >= 4, \"NeighborhoodSize should be >= 4.\");\n  // We can't put a variable in the message, ensure coherence\n  static_assert(MIN_NEIGHBORHOOD_SIZE == 4, \"\");\n\n  static_assert(NeighborhoodSize <= 62, \"NeighborhoodSize should be <= 62.\");\n  // We can't put a variable in the message, ensure coherence\n  static_assert(MAX_NEIGHBORHOOD_SIZE == 62, \"\");\n\n\n  static_assert(!StoreHash || NeighborhoodSize <= 30,\n                \"NeighborhoodSize should be <= 30 if StoreHash is true.\");\n  // We can't put a variable in the message, ensure coherence\n  static_assert(MAX_NEIGHBORHOOD_SIZE - 32 == 30, \"\");\n\n  using bucket_hash = hopscotch_bucket_hash<StoreHash>;\n\npublic:\n  using value_type = ValueType;\n  using neighborhood_bitmap =\n    typename smallest_type_for_min_bits < NeighborhoodSize +\n    NB_RESERVED_BITS_IN_NEIGHBORHOOD >::type;\n\n\n  hopscotch_bucket() noexcept: bucket_hash(), m_neighborhood_infos(0)\n  {\n    tsl_assert(empty());\n  }\n\n\n  hopscotch_bucket(const hopscotch_bucket& bucket)\n  noexcept(std::is_nothrow_copy_constructible<value_type>::value): bucket_hash(\n      bucket),\n    m_neighborhood_infos(0)\n  {\n    if (!bucket.empty()) {\n      ::new(static_cast<void*>(std::addressof(m_value))) value_type(bucket.value());\n    }\n\n    m_neighborhood_infos = bucket.m_neighborhood_infos;\n  }\n\n  hopscotch_bucket(hopscotch_bucket&& bucket)\n  noexcept(std::is_nothrow_move_constructible<value_type>::value) : bucket_hash(\n      std::move(bucket)),\n    m_neighborhood_infos(0)\n  {\n    if (!bucket.empty()) {\n      ::new(static_cast<void*>(std::addressof(m_value))) value_type(std::move(\n            bucket.value()));\n    }\n\n    m_neighborhood_infos = bucket.m_neighborhood_infos;\n  }\n\n  hopscotch_bucket& operator=(const hopscotch_bucket& bucket)\n  noexcept(std::is_nothrow_copy_constructible<value_type>::value)\n  {\n    if (this != &bucket) {\n      remove_value();\n      bucket_hash::operator=(bucket);\n\n      if (!bucket.empty()) {\n        ::new(static_cast<void*>(std::addressof(m_value))) value_type(bucket.value());\n      }\n\n      m_neighborhood_infos = bucket.m_neighborhood_infos;\n    }\n\n    return *this;\n  }\n\n  hopscotch_bucket& operator=(hopscotch_bucket&&) = delete;\n\n  ~hopscotch_bucket() noexcept\n  {\n    if (!empty()) {\n      destroy_value();\n    }\n  }\n\n  neighborhood_bitmap neighborhood_infos() const noexcept\n  {\n    return neighborhood_bitmap(m_neighborhood_infos >>\n                               NB_RESERVED_BITS_IN_NEIGHBORHOOD);\n  }\n\n  void set_overflow(bool has_overflow) noexcept\n  {\n    if (has_overflow) {\n      m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos | 2);\n    } else {\n      m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos & ~2);\n    }\n  }\n\n  bool has_overflow() const noexcept\n  {\n    return (m_neighborhood_infos & 2) != 0;\n  }\n\n  bool empty() const noexcept\n  {\n    return (m_neighborhood_infos & 1) == 0;\n  }\n\n  void toggle_neighbor_presence(std::size_t ineighbor) noexcept\n  {\n    tsl_assert(ineighbor <= NeighborhoodSize);\n    m_neighborhood_infos = neighborhood_bitmap(\n                             m_neighborhood_infos ^ (1ull << (ineighbor +\n                                 NB_RESERVED_BITS_IN_NEIGHBORHOOD)));\n  }\n\n  bool check_neighbor_presence(std::size_t ineighbor) const noexcept\n  {\n    tsl_assert(ineighbor <= NeighborhoodSize);\n\n    if (((m_neighborhood_infos >> (ineighbor + NB_RESERVED_BITS_IN_NEIGHBORHOOD)) &\n         1) == 1) {\n      return true;\n    }\n\n    return false;\n  }\n\n  value_type& value() noexcept\n  {\n    tsl_assert(!empty());\n    return *reinterpret_cast<value_type*>(std::addressof(m_value));\n  }\n\n  const value_type& value() const noexcept\n  {\n    tsl_assert(!empty());\n    return *reinterpret_cast<const value_type*>(std::addressof(m_value));\n  }\n\n  template<typename... Args>\n  void set_value_of_empty_bucket(std::size_t hash, Args&& ... value_type_args)\n  {\n    tsl_assert(empty());\n    ::new(static_cast<void*>(std::addressof(m_value))) value_type(\n      std::forward<Args>(value_type_args)...);\n    set_empty(false);\n    this->set_hash(hash);\n  }\n\n  void swap_value_into_empty_bucket(hopscotch_bucket& empty_bucket)\n  {\n    tsl_assert(empty_bucket.empty());\n\n    if (!empty()) {\n      ::new(static_cast<void*>(std::addressof(empty_bucket.m_value))) value_type(\n        std::move(value()));\n      empty_bucket.copy_hash(*this);\n      empty_bucket.set_empty(false);\n      destroy_value();\n      set_empty(true);\n    }\n  }\n\n  void remove_value() noexcept\n  {\n    if (!empty()) {\n      destroy_value();\n      set_empty(true);\n    }\n  }\n\n  void clear() noexcept\n  {\n    if (!empty()) {\n      destroy_value();\n    }\n\n    m_neighborhood_infos = 0;\n    tsl_assert(empty());\n  }\n\n  static std::size_t max_size() noexcept\n  {\n    if (StoreHash) {\n      return std::numeric_limits<typename bucket_hash::hash_type>::max();\n    } else {\n      return std::numeric_limits<std::size_t>::max();\n    }\n  }\n\nprivate:\n  void set_empty(bool is_empty) noexcept\n  {\n    if (is_empty) {\n      m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos & ~1);\n    } else {\n      m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos | 1);\n    }\n  }\n\n  void destroy_value() noexcept\n  {\n    try {\n      tsl_assert(!empty());\n      value().~value_type();\n    } catch (...) {\n      std::terminate();\n    }\n  }\n\nprivate:\n  using storage = typename\n                  std::aligned_storage<sizeof(value_type), alignof(value_type)>::type;\n\n  neighborhood_bitmap m_neighborhood_infos;\n  storage m_value;\n};\n\n\n/**\n * Internal common class used by hopscotch_(sc)_map and hopscotch_(sc)_set.\n *\n * ValueType is what will be stored by hopscotch_hash (usually std::pair<Key, T> for map and Key for set).\n *\n * KeySelect should be a FunctionObject which takes a ValueType in parameter and returns a reference to the key.\n *\n * ValueSelect should be a FunctionObject which takes a ValueType in parameter and returns a reference to the value.\n * ValueSelect should be void if there is no value (in set for example).\n *\n * OverflowContainer will be used as containers for overflown elements. Usually it should be a list<ValueType>\n * or a set<Key>/map<Key, T>.\n */\ntemplate<class ValueType,\n         class KeySelect,\n         class ValueSelect,\n         class Hash,\n         class KeyEqual,\n         class Allocator,\n         unsigned int NeighborhoodSize,\n         bool StoreHash,\n         class GrowthPolicy,\n         class OverflowContainer>\nclass hopscotch_hash: private Hash, private KeyEqual, private GrowthPolicy\n{\nprivate:\n  template<typename U>\n  using has_mapped_type = typename std::integral_constant < bool,\n        !std::is_same<U, void>::value >;\n\npublic:\n  template<bool is_const>\n  class hopscotch_iterator;\n\n  using key_type = typename KeySelect::key_type;\n  using value_type = ValueType;\n  using size_type = std::size_t;\n  using difference_type = std::ptrdiff_t;\n  using hasher = Hash;\n  using key_equal = KeyEqual;\n  using allocator_type = Allocator;\n  using reference = value_type&;\n  using const_reference = const value_type&;\n  using pointer = value_type*;\n  using const_pointer = const value_type*;\n  using iterator = hopscotch_iterator<false>;\n  using const_iterator = hopscotch_iterator<true>;\n\nprivate:\n  using hopscotch_bucket =\n    tsl::detail_hopscotch_hash::hopscotch_bucket<ValueType, NeighborhoodSize, StoreHash>;\n  using neighborhood_bitmap = typename hopscotch_bucket::neighborhood_bitmap;\n\n  using buckets_allocator = typename\n                            std::allocator_traits<allocator_type>::template rebind_alloc<hopscotch_bucket>;\n  using buckets_container_type = std::vector<hopscotch_bucket, buckets_allocator>;\n\n  using overflow_container_type = OverflowContainer;\n\n  static_assert(\n    std::is_same<typename overflow_container_type::value_type, ValueType>::value,\n    \"OverflowContainer should have ValueType as type.\");\n\n  static_assert(\n    std::is_same<typename overflow_container_type::allocator_type, Allocator>::value,\n    \"Invalid allocator, not the same type as the value_type.\");\n\n\n  using iterator_buckets = typename buckets_container_type::iterator;\n  using const_iterator_buckets = typename buckets_container_type::const_iterator;\n\n  using iterator_overflow = typename overflow_container_type::iterator;\n  using const_iterator_overflow = typename\n                                  overflow_container_type::const_iterator;\n\npublic:\n  /**\n   * The 'operator*()' and 'operator->()' methods return a const reference and const pointer respectively to the\n   * stored value type.\n   *\n   * In case of a map, to get a modifiable reference to the value associated to a key (the '.second' in the\n   * stored pair), you have to call 'value()'.\n   */\n  template<bool is_const>\n  class hopscotch_iterator\n  {\n    friend class hopscotch_hash;\n  private:\n    using iterator_bucket = typename std::conditional<is_const,\n          typename hopscotch_hash::const_iterator_buckets,\n          typename hopscotch_hash::iterator_buckets>::type;\n    using iterator_overflow = typename std::conditional<is_const,\n          typename hopscotch_hash::const_iterator_overflow,\n          typename hopscotch_hash::iterator_overflow>::type;\n\n\n    hopscotch_iterator(iterator_bucket buckets_iterator,\n                       iterator_bucket buckets_end_iterator,\n                       iterator_overflow overflow_iterator) noexcept :\n      m_buckets_iterator(buckets_iterator),\n      m_buckets_end_iterator(buckets_end_iterator),\n      m_overflow_iterator(overflow_iterator)\n    {\n    }\n\n  public:\n    using iterator_category = std::forward_iterator_tag;\n    using value_type = const typename hopscotch_hash::value_type;\n    using difference_type = std::ptrdiff_t;\n    using reference = value_type&;\n    using pointer = value_type*;\n\n\n    hopscotch_iterator() noexcept\n    {\n    }\n\n    hopscotch_iterator(const hopscotch_iterator<false>& other) noexcept :\n      m_buckets_iterator(other.m_buckets_iterator),\n      m_buckets_end_iterator(other.m_buckets_end_iterator),\n      m_overflow_iterator(other.m_overflow_iterator)\n    {\n    }\n\n    const typename hopscotch_hash::key_type& key() const\n    {\n      if (m_buckets_iterator != m_buckets_end_iterator) {\n        return KeySelect()(m_buckets_iterator->value());\n      }\n\n      return KeySelect()(*m_overflow_iterator);\n    }\n\n    template<class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>\n    typename std::conditional <\n    is_const,\n    const typename U::value_type&,\n    typename U::value_type& >::type value() const\n    {\n      if (m_buckets_iterator != m_buckets_end_iterator) {\n        return U()(m_buckets_iterator->value());\n      }\n\n      return U()(*m_overflow_iterator);\n    }\n\n    reference operator*() const\n    {\n      if (m_buckets_iterator != m_buckets_end_iterator) {\n        return m_buckets_iterator->value();\n      }\n\n      return *m_overflow_iterator;\n    }\n\n    pointer operator->() const\n    {\n      if (m_buckets_iterator != m_buckets_end_iterator) {\n        return std::addressof(m_buckets_iterator->value());\n      }\n\n      return std::addressof(*m_overflow_iterator);\n    }\n\n    hopscotch_iterator& operator++()\n    {\n      if (m_buckets_iterator == m_buckets_end_iterator) {\n        ++m_overflow_iterator;\n        return *this;\n      }\n\n      do {\n        ++m_buckets_iterator;\n      } while (m_buckets_iterator != m_buckets_end_iterator &&\n               m_buckets_iterator->empty());\n\n      return *this;\n    }\n\n    hopscotch_iterator operator++(int)\n    {\n      hopscotch_iterator tmp(*this);\n      ++*this;\n      return tmp;\n    }\n\n    friend bool operator==(const hopscotch_iterator& lhs,\n                           const hopscotch_iterator& rhs)\n    {\n      return lhs.m_buckets_iterator == rhs.m_buckets_iterator &&\n             lhs.m_overflow_iterator == rhs.m_overflow_iterator;\n    }\n\n    friend bool operator!=(const hopscotch_iterator& lhs,\n                           const hopscotch_iterator& rhs)\n    {\n      return !(lhs == rhs);\n    }\n\n  private:\n    iterator_bucket m_buckets_iterator;\n    iterator_bucket m_buckets_end_iterator;\n    iterator_overflow m_overflow_iterator;\n  };\n\n\n\npublic:\n  template < class OC = OverflowContainer,\n             typename std::enable_if < !has_key_compare<OC>::value >::type* = nullptr >\n  hopscotch_hash(size_type bucket_count,\n                 const Hash& hash,\n                 const KeyEqual& equal,\n                 const Allocator& alloc,\n                 float max_load_factor) :  Hash(hash),\n    KeyEqual(equal),\n    GrowthPolicy(bucket_count),\n    m_buckets(alloc),\n    m_overflow_elements(alloc),\n    m_nb_elements(0)\n  {\n    if (bucket_count > max_bucket_count()) {\n      throw std::length_error(\"The map exceeds its maxmimum size.\");\n    }\n\n    static_assert(NeighborhoodSize - 1 > 0, \"\");\n    m_buckets.resize(bucket_count + NeighborhoodSize - 1);\n    this->max_load_factor(max_load_factor);\n  }\n\n  template<class OC = OverflowContainer, typename std::enable_if<has_key_compare<OC>::value>::type* = nullptr>\n  hopscotch_hash(size_type bucket_count,\n                 const Hash& hash,\n                 const KeyEqual& equal,\n                 const Allocator& alloc,\n                 float max_load_factor,\n                 const typename OC::key_compare& comp) : Hash(hash),\n    KeyEqual(equal),\n    GrowthPolicy(bucket_count),\n    m_buckets(alloc),\n    m_overflow_elements(comp, alloc),\n    m_nb_elements(0)\n  {\n    if (bucket_count > max_bucket_count()) {\n      throw std::length_error(\"The map exceeds its maxmimum size.\");\n    }\n\n    static_assert(NeighborhoodSize - 1 > 0, \"\");\n    // Can't directly construct with the appropriate size in the initializer\n    // as m_buckets(bucket_count, alloc) is not supported by GCC 4.8\n    m_buckets.resize(bucket_count + NeighborhoodSize - 1);\n    this->max_load_factor(max_load_factor);\n  }\n\n  hopscotch_hash(const hopscotch_hash& other) = default;\n\n  hopscotch_hash(hopscotch_hash&& other)\n  noexcept(\n    std::is_nothrow_move_constructible<Hash>::value&&\n    std::is_nothrow_move_constructible<KeyEqual>::value&&\n    std::is_nothrow_move_constructible<GrowthPolicy>::value&&\n    std::is_nothrow_move_constructible<buckets_container_type>::value&&\n    std::is_nothrow_move_constructible<overflow_container_type>::value\n  )\n    : Hash(std::move(static_cast<Hash&>(other))),\n      KeyEqual(std::move(static_cast<KeyEqual&>(other))),\n      GrowthPolicy(std::move(static_cast<GrowthPolicy&>(other))),\n      m_buckets(std::move(other.m_buckets)),\n      m_overflow_elements(std::move(other.m_overflow_elements)),\n      m_nb_elements(other.m_nb_elements),\n      m_max_load_factor(other.m_max_load_factor),\n      m_load_threshold(other.m_load_threshold),\n      m_min_load_factor_rehash_threshold(other.m_min_load_factor_rehash_threshold)\n  {\n    other.clear();\n  }\n\n  hopscotch_hash& operator=(const hopscotch_hash& other) = default;\n\n  hopscotch_hash& operator=(hopscotch_hash&& other)\n  {\n    other.swap(*this);\n    other.clear();\n    return *this;\n  }\n\n  allocator_type get_allocator() const\n  {\n    return m_buckets.get_allocator();\n  }\n\n\n  /*\n   * Iterators\n   */\n  iterator begin() noexcept\n  {\n    auto begin = m_buckets.begin();\n\n    while (begin != m_buckets.end() && begin->empty()) {\n      ++begin;\n    }\n\n    return iterator(begin, m_buckets.end(), m_overflow_elements.begin());\n  }\n\n  const_iterator begin() const noexcept\n  {\n    return cbegin();\n  }\n\n  const_iterator cbegin() const noexcept\n  {\n    auto begin = m_buckets.cbegin();\n\n    while (begin != m_buckets.cend() && begin->empty()) {\n      ++begin;\n    }\n\n    return const_iterator(begin, m_buckets.cend(), m_overflow_elements.cbegin());\n  }\n\n  iterator end() noexcept\n  {\n    return iterator(m_buckets.end(), m_buckets.end(), m_overflow_elements.end());\n  }\n\n  const_iterator end() const noexcept\n  {\n    return cend();\n  }\n\n  const_iterator cend() const noexcept\n  {\n    return const_iterator(m_buckets.cend(), m_buckets.cend(),\n                          m_overflow_elements.cend());\n  }\n\n\n  /*\n   * Capacity\n   */\n  bool empty() const noexcept\n  {\n    return m_nb_elements == 0;\n  }\n\n  size_type size() const noexcept\n  {\n    return m_nb_elements;\n  }\n\n  size_type max_size() const noexcept\n  {\n    return hopscotch_bucket::max_size();\n  }\n\n  /*\n   * Modifiers\n   */\n  void clear() noexcept\n  {\n    for (auto& bucket : m_buckets) {\n      bucket.clear();\n    }\n\n    m_overflow_elements.clear();\n    m_nb_elements = 0;\n  }\n\n\n  std::pair<iterator, bool> insert(const value_type& value)\n  {\n    return insert_impl(value);\n  }\n\n  template < class P, typename std::enable_if < std::is_constructible <\n               value_type, P&& >::value >::type* = nullptr >\n  std::pair<iterator, bool> insert(P && value)\n  {\n    return emplace(std::forward<P>(value));\n  }\n\n  std::pair<iterator, bool> insert(value_type&& value)\n  {\n    return insert_impl(std::move(value));\n  }\n\n\n  iterator insert(const_iterator hint, const value_type& value)\n  {\n    if (hint != cend() && compare_keys(KeySelect()(*hint), KeySelect()(value))) {\n      return mutable_iterator(hint);\n    }\n\n    return insert(value).first;\n  }\n\n  template < class P, typename std::enable_if < std::is_constructible <\n               value_type, P&& >::value >::type* = nullptr >\n  iterator insert(const_iterator hint, P && value)\n  {\n    return emplace_hint(hint, std::forward<P>(value));\n  }\n\n  iterator insert(const_iterator hint, value_type&& value)\n  {\n    if (hint != cend() && compare_keys(KeySelect()(*hint), KeySelect()(value))) {\n      return mutable_iterator(hint);\n    }\n\n    return insert(std::move(value)).first;\n  }\n\n\n  template<class InputIt>\n  void insert(InputIt first, InputIt last)\n  {\n    if (std::is_base_of<std::forward_iterator_tag,\n        typename std::iterator_traits<InputIt>::iterator_category>::value) {\n      const auto nb_elements_insert = std::distance(first, last);\n      const std::size_t nb_elements_in_buckets = m_nb_elements -\n          m_overflow_elements.size();\n      const std::size_t nb_free_buckets = m_load_threshold - nb_elements_in_buckets;\n      tsl_assert(m_nb_elements >= m_overflow_elements.size());\n      tsl_assert(m_load_threshold >= nb_elements_in_buckets);\n\n      if (nb_elements_insert > 0 &&\n          nb_free_buckets < std::size_t(nb_elements_insert)) {\n        reserve(nb_elements_in_buckets + std::size_t(nb_elements_insert));\n      }\n    }\n\n    for (; first != last; ++first) {\n      insert(*first);\n    }\n  }\n\n\n  template<class M>\n  std::pair<iterator, bool> insert_or_assign(const key_type& k, M&& obj)\n  {\n    return insert_or_assign_impl(k, std::forward<M>(obj));\n  }\n\n  template<class M>\n  std::pair<iterator, bool> insert_or_assign(key_type&& k, M&& obj)\n  {\n    return insert_or_assign_impl(std::move(k), std::forward<M>(obj));\n  }\n\n\n  template<class M>\n  iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj)\n  {\n    if (hint != cend() && compare_keys(KeySelect()(*hint), k)) {\n      auto it = mutable_iterator(hint);\n      it.value() = std::forward<M>(obj);\n      return it;\n    }\n\n    return insert_or_assign(k, std::forward<M>(obj)).first;\n  }\n\n  template<class M>\n  iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj)\n  {\n    if (hint != cend() && compare_keys(KeySelect()(*hint), k)) {\n      auto it = mutable_iterator(hint);\n      it.value() = std::forward<M>(obj);\n      return it;\n    }\n\n    return insert_or_assign(std::move(k), std::forward<M>(obj)).first;\n  }\n\n\n  template<class... Args>\n  std::pair<iterator, bool> emplace(Args&& ... args)\n  {\n    return insert(value_type(std::forward<Args>(args)...));\n  }\n\n  template<class... Args>\n  iterator emplace_hint(const_iterator hint, Args&& ... args)\n  {\n    return insert(hint, value_type(std::forward<Args>(args)...));\n  }\n\n  template<class... Args>\n  std::pair<iterator, bool> try_emplace(const key_type& k, Args&& ... args)\n  {\n    return try_emplace_impl(k, std::forward<Args>(args)...);\n  }\n\n  template<class... Args>\n  std::pair<iterator, bool> try_emplace(key_type&& k, Args&& ... args)\n  {\n    return try_emplace_impl(std::move(k), std::forward<Args>(args)...);\n  }\n\n  template<class... Args>\n  iterator try_emplace(const_iterator hint, const key_type& k, Args&& ... args)\n  {\n    if (hint != cend() && compare_keys(KeySelect()(*hint), k)) {\n      return mutable_iterator(hint);\n    }\n\n    return try_emplace(k, std::forward<Args>(args)...).first;\n  }\n\n  template<class... Args>\n  iterator try_emplace(const_iterator hint, key_type&& k, Args&& ... args)\n  {\n    if (hint != cend() && compare_keys(KeySelect()(*hint), k)) {\n      return mutable_iterator(hint);\n    }\n\n    return try_emplace(std::move(k), std::forward<Args>(args)...).first;\n  }\n\n\n\n  iterator erase(iterator pos)\n  {\n    return erase(const_iterator(pos));\n  }\n\n  iterator erase(const_iterator pos)\n  {\n    const std::size_t ibucket_for_hash = bucket_for_hash(hash_key(pos.key()));\n\n    if (pos.m_buckets_iterator != pos.m_buckets_end_iterator) {\n      auto it_bucket = m_buckets.begin() + std::distance(m_buckets.cbegin(),\n                       pos.m_buckets_iterator);\n      erase_from_bucket(it_bucket, ibucket_for_hash);\n      return ++iterator(it_bucket, m_buckets.end(), m_overflow_elements.begin());\n    } else {\n      auto it_next_overflow = erase_from_overflow(pos.m_overflow_iterator,\n                              ibucket_for_hash);\n      return iterator(m_buckets.end(), m_buckets.end(), it_next_overflow);\n    }\n  }\n\n  iterator erase(const_iterator first, const_iterator last)\n  {\n    if (first == last) {\n      return mutable_iterator(first);\n    }\n\n    auto to_delete = erase(first);\n\n    while (to_delete != last) {\n      to_delete = erase(to_delete);\n    }\n\n    return to_delete;\n  }\n\n  template<class K>\n  size_type erase(const K& key)\n  {\n    return erase(key, hash_key(key));\n  }\n\n  template<class K>\n  size_type erase(const K& key, std::size_t hash)\n  {\n    const std::size_t ibucket_for_hash = bucket_for_hash(hash);\n    auto it_find = find_in_buckets(key, hash, m_buckets.begin() + ibucket_for_hash);\n\n    if (it_find != m_buckets.end()) {\n      erase_from_bucket(it_find, ibucket_for_hash);\n      return 1;\n    }\n\n    if (m_buckets[ibucket_for_hash].has_overflow()) {\n      auto it_overflow = find_in_overflow(key);\n\n      if (it_overflow != m_overflow_elements.end()) {\n        erase_from_overflow(it_overflow, ibucket_for_hash);\n        return 1;\n      }\n    }\n\n    return 0;\n  }\n\n  void swap(hopscotch_hash& other)\n  {\n    using std::swap;\n    swap(static_cast<Hash&>(*this), static_cast<Hash&>(other));\n    swap(static_cast<KeyEqual&>(*this), static_cast<KeyEqual&>(other));\n    swap(static_cast<GrowthPolicy&>(*this), static_cast<GrowthPolicy&>(other));\n    swap(m_buckets, other.m_buckets);\n    swap(m_overflow_elements, other.m_overflow_elements);\n    swap(m_nb_elements, other.m_nb_elements);\n    swap(m_max_load_factor, other.m_max_load_factor);\n    swap(m_load_threshold, other.m_load_threshold);\n    swap(m_min_load_factor_rehash_threshold,\n         other.m_min_load_factor_rehash_threshold);\n  }\n\n\n  /*\n   * Lookup\n   */\n  template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>\n  typename U::value_type & at(const K& key)\n  {\n    return at(key, hash_key(key));\n  }\n\n  template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>\n  typename U::value_type & at(const K& key, std::size_t hash)\n  {\n    return const_cast<typename U::value_type&>(static_cast<const hopscotch_hash*>\n           (this)->at(key, hash));\n  }\n\n\n  template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>\n  const typename U::value_type & at(const K& key) const\n  {\n    return at(key, hash_key(key));\n  }\n\n  template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>\n  const typename U::value_type & at(const K& key, std::size_t hash) const\n  {\n    using T = typename U::value_type;\n    const T* value = find_value_impl(key, hash,\n                                     m_buckets.begin() + bucket_for_hash(hash));\n\n    if (value == nullptr) {\n      throw std::out_of_range(\"Couldn't find key.\");\n    } else {\n      return *value;\n    }\n  }\n\n\n  template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>\n  typename U::value_type & operator[](K && key)\n  {\n    using T = typename U::value_type;\n    const std::size_t hash = hash_key(key);\n    const std::size_t ibucket_for_hash = bucket_for_hash(hash);\n    T* value = find_value_impl(key, hash, m_buckets.begin() + ibucket_for_hash);\n\n    if (value != nullptr) {\n      return *value;\n    } else {\n      return insert_impl(ibucket_for_hash, hash, std::piecewise_construct,\n                         std::forward_as_tuple(std::forward<K>(key)),\n                         std::forward_as_tuple()).first.value();\n    }\n  }\n\n\n  template<class K>\n  size_type count(const K& key) const\n  {\n    return count(key, hash_key(key));\n  }\n\n  template<class K>\n  size_type count(const K& key, std::size_t hash) const\n  {\n    return count_impl(key, hash, m_buckets.cbegin() + bucket_for_hash(hash));\n  }\n\n\n  template<class K>\n  iterator find(const K& key)\n  {\n    return find(key, hash_key(key));\n  }\n\n  template<class K>\n  iterator find(const K& key, std::size_t hash)\n  {\n    return find_impl(key, hash, m_buckets.begin() + bucket_for_hash(hash));\n  }\n\n\n  template<class K>\n  const_iterator find(const K& key) const\n  {\n    return find(key, hash_key(key));\n  }\n\n  template<class K>\n  const_iterator find(const K& key, std::size_t hash) const\n  {\n    return find_impl(key, hash, m_buckets.begin() + bucket_for_hash(hash));\n  }\n\n\n  template<class K>\n  std::pair<iterator, iterator> equal_range(const K& key)\n  {\n    return equal_range(key, hash_key(key));\n  }\n\n  template<class K>\n  std::pair<iterator, iterator> equal_range(const K& key, std::size_t hash)\n  {\n    iterator it = find(key, hash);\n    return std::make_pair(it, (it == end()) ? it : std::next(it));\n  }\n\n\n  template<class K>\n  std::pair<const_iterator, const_iterator> equal_range(const K& key) const\n  {\n    return equal_range(key, hash_key(key));\n  }\n\n  template<class K>\n  std::pair<const_iterator, const_iterator> equal_range(const K& key,\n      std::size_t hash) const\n  {\n    const_iterator it = find(key, hash);\n    return std::make_pair(it, (it == cend()) ? it : std::next(it));\n  }\n\n  /*\n   * Bucket interface\n   */\n  size_type bucket_count() const\n  {\n    /*\n     * So that the last bucket can have NeighborhoodSize neighbors, the size of the bucket array is a little\n     * bigger than the real number of buckets. We could use some of the buckets at the beginning, but\n     * it is easier this way and we avoid weird behaviour with iterators.\n     */\n    return m_buckets.size() - NeighborhoodSize + 1;\n  }\n\n  size_type max_bucket_count() const\n  {\n    const std::size_t max_bucket_count = std::min(GrowthPolicy::max_bucket_count(),\n                                         m_buckets.max_size());\n    return max_bucket_count - NeighborhoodSize + 1;\n  }\n\n\n  /*\n   *  Hash policy\n   */\n  float load_factor() const\n  {\n    return float(m_nb_elements) / float(bucket_count());\n  }\n\n  float max_load_factor() const\n  {\n    return m_max_load_factor;\n  }\n\n  void max_load_factor(float ml)\n  {\n    m_max_load_factor = ml;\n    m_load_threshold = size_type(float(bucket_count()) * m_max_load_factor);\n    m_min_load_factor_rehash_threshold = size_type(bucket_count() *\n                                         MIN_LOAD_FACTOR_FOR_REHASH);\n  }\n\n  void rehash(size_type count)\n  {\n    count = std::max(count, size_type(std::ceil(float(size()) /\n                                      max_load_factor())));\n    rehash_impl(count);\n  }\n\n  void reserve(size_type count)\n  {\n    rehash(size_type(std::ceil(float(count) / max_load_factor())));\n  }\n\n\n  /*\n   * Observers\n   */\n  hasher hash_function() const\n  {\n    return static_cast<Hash>(*this);\n  }\n\n  key_equal key_eq() const\n  {\n    return static_cast<KeyEqual>(*this);\n  }\n\n  /*\n   * Other\n   */\n  iterator mutable_iterator(const_iterator pos)\n  {\n    if (pos.m_buckets_iterator != pos.m_buckets_end_iterator) {\n      // Get a non-const iterator\n      auto it = m_buckets.begin() + std::distance(m_buckets.cbegin(),\n                pos.m_buckets_iterator);\n      return iterator(it, m_buckets.end(), m_overflow_elements.begin());\n    } else {\n      // Get a non-const iterator\n      auto it = mutable_overflow_iterator(pos.m_overflow_iterator);\n      return iterator(m_buckets.end(), m_buckets.end(), it);\n    }\n  }\n\n  size_type overflow_size() const noexcept\n  {\n    return m_overflow_elements.size();\n  }\n\n  template<class U = OverflowContainer, typename std::enable_if<has_key_compare<U>::value>::type* = nullptr>\n  typename U::key_compare key_comp() const\n  {\n    return m_overflow_elements.key_comp();\n  }\n\n\nprivate:\n  template<class K>\n  std::size_t hash_key(const K& key) const\n  {\n    return Hash::operator()(key);\n  }\n\n  template<class K1, class K2>\n  bool compare_keys(const K1& key1, const K2& key2) const\n  {\n    return KeyEqual::operator()(key1, key2);\n  }\n\n  std::size_t bucket_for_hash(std::size_t hash) const\n  {\n    return GrowthPolicy::bucket_for_hash(hash);\n  }\n\n\n  static_assert(std::is_nothrow_move_constructible<value_type>::value ||\n                std::is_copy_constructible<value_type>::value,\n                \"value_type must be either copy constructible or nothrow move constructible.\");\n\n  template<typename U = value_type,\n           typename std::enable_if<std::is_nothrow_move_constructible<U>::value>::type* = nullptr>\n  void rehash_impl(size_type count)\n  {\n    hopscotch_hash new_map = new_hopscotch_hash(count);\n\n    if (!m_overflow_elements.empty()) {\n      new_map.m_overflow_elements.swap(m_overflow_elements);\n      new_map.m_nb_elements += new_map.m_overflow_elements.size();\n\n      for (const value_type& value : new_map.m_overflow_elements) {\n        const std::size_t ibucket_for_hash = new_map.bucket_for_hash(new_map.hash_key(\n                                               KeySelect()(value)));\n        new_map.m_buckets[ibucket_for_hash].set_overflow(true);\n      }\n    }\n\n    try {\n      for (auto it_bucket = m_buckets.begin(); it_bucket != m_buckets.end();\n           ++it_bucket) {\n        if (it_bucket->empty()) {\n          continue;\n        }\n\n        const std::size_t hash = USE_STORED_HASH_ON_REHASH ?\n                                 it_bucket->truncated_bucket_hash() :\n                                 new_map.hash_key(KeySelect()(it_bucket->value()));\n        const std::size_t ibucket_for_hash = new_map.bucket_for_hash(hash);\n        new_map.insert_impl(ibucket_for_hash, hash, std::move(it_bucket->value()));\n        erase_from_bucket(it_bucket, bucket_for_hash(hash));\n      }\n    }\n    /*\n     * The call to insert_impl may throw an exception if an element is added to the overflow\n     * list. Rollback the elements in this case.\n     */\n    catch (...) {\n      m_overflow_elements.swap(new_map.m_overflow_elements);\n\n      for (auto it_bucket = new_map.m_buckets.begin();\n           it_bucket != new_map.m_buckets.end(); ++it_bucket) {\n        if (it_bucket->empty()) {\n          continue;\n        }\n\n        const std::size_t hash = USE_STORED_HASH_ON_REHASH ?\n                                 it_bucket->truncated_bucket_hash() :\n                                 hash_key(KeySelect()(it_bucket->value()));\n        const std::size_t ibucket_for_hash = bucket_for_hash(hash);\n        // The elements we insert were not in the overflow list before the switch.\n        // They will not be go in the overflow list if we rollback the switch.\n        insert_impl(ibucket_for_hash, hash, std::move(it_bucket->value()));\n      }\n\n      throw;\n    }\n\n    new_map.swap(*this);\n  }\n\n  template < typename U = value_type,\n             typename std::enable_if < std::is_copy_constructible<U>::value &&\n                                       !std::is_nothrow_move_constructible<U>::value >::type* = nullptr >\n  void rehash_impl(size_type count)\n  {\n    hopscotch_hash new_map = new_hopscotch_hash(count);\n\n    for (const hopscotch_bucket& bucket : m_buckets) {\n      if (bucket.empty()) {\n        continue;\n      }\n\n      const std::size_t hash = USE_STORED_HASH_ON_REHASH ?\n                               bucket.truncated_bucket_hash() :\n                               new_map.hash_key(KeySelect()(bucket.value()));\n      const std::size_t ibucket_for_hash = new_map.bucket_for_hash(hash);\n      new_map.insert_impl(ibucket_for_hash, hash, bucket.value());\n    }\n\n    for (const value_type& value : m_overflow_elements) {\n      const std::size_t hash = new_map.hash_key(KeySelect()(value));\n      const std::size_t ibucket_for_hash = new_map.bucket_for_hash(hash);\n      new_map.insert_impl(ibucket_for_hash, hash, value);\n    }\n\n    new_map.swap(*this);\n  }\n\n#ifdef TSL_NO_RANGE_ERASE_WITH_CONST_ITERATOR\n  iterator_overflow mutable_overflow_iterator(const_iterator_overflow it)\n  {\n    return std::next(m_overflow_elements.begin(),\n                     std::distance(m_overflow_elements.cbegin(), it));\n  }\n#else\n  iterator_overflow mutable_overflow_iterator(const_iterator_overflow it)\n  {\n    return m_overflow_elements.erase(it, it);\n  }\n#endif\n\n  // iterator is in overflow list\n  iterator_overflow erase_from_overflow(const_iterator_overflow pos,\n                                        std::size_t ibucket_for_hash)\n  {\n#ifdef TSL_NO_RANGE_ERASE_WITH_CONST_ITERATOR\n    auto it_next = m_overflow_elements.erase(mutable_overflow_iterator(pos));\n#else\n    auto it_next = m_overflow_elements.erase(pos);\n#endif\n    m_nb_elements--;\n    // Check if we can remove the overflow flag\n    tsl_assert(m_buckets[ibucket_for_hash].has_overflow());\n\n    for (const value_type& value : m_overflow_elements) {\n      const std::size_t bucket_for_value = bucket_for_hash(hash_key(KeySelect()(\n                                             value)));\n\n      if (bucket_for_value == ibucket_for_hash) {\n        return it_next;\n      }\n    }\n\n    m_buckets[ibucket_for_hash].set_overflow(false);\n    return it_next;\n  }\n\n  // iterator is in bucket\n  void erase_from_bucket(iterator_buckets pos,\n                         std::size_t ibucket_for_hash) noexcept\n  {\n    const std::size_t ibucket_for_pos = std::distance(m_buckets.begin(), pos);\n    tsl_assert(ibucket_for_pos >= ibucket_for_hash);\n    m_buckets[ibucket_for_pos].remove_value();\n    m_buckets[ibucket_for_hash].toggle_neighbor_presence(ibucket_for_pos -\n        ibucket_for_hash);\n    m_nb_elements--;\n  }\n\n\n\n  template<class K, class M>\n  std::pair<iterator, bool> insert_or_assign_impl(K&& key, M&& obj)\n  {\n    auto it = try_emplace_impl(std::forward<K>(key), std::forward<M>(obj));\n\n    if (!it.second) {\n      it.first.value() = std::forward<M>(obj);\n    }\n\n    return it;\n  }\n\n  template<typename P, class... Args>\n  std::pair<iterator, bool> try_emplace_impl(P&& key, Args&& ... args_value)\n  {\n    const std::size_t hash = hash_key(key);\n    const std::size_t ibucket_for_hash = bucket_for_hash(hash);\n    // Check if already presents\n    auto it_find = find_impl(key, hash, m_buckets.begin() + ibucket_for_hash);\n\n    if (it_find != end()) {\n      return std::make_pair(it_find, false);\n    }\n\n    return insert_impl(ibucket_for_hash, hash, std::piecewise_construct,\n                       std::forward_as_tuple(std::forward<P>(key)),\n                       std::forward_as_tuple(std::forward<Args>(args_value)...));\n  }\n\n  template<typename P>\n  std::pair<iterator, bool> insert_impl(P&& value)\n  {\n    const std::size_t hash = hash_key(KeySelect()(value));\n    const std::size_t ibucket_for_hash = bucket_for_hash(hash);\n    // Check if already presents\n    auto it_find = find_impl(KeySelect()(value), hash,\n                             m_buckets.begin() + ibucket_for_hash);\n\n    if (it_find != end()) {\n      return std::make_pair(it_find, false);\n    }\n\n    return insert_impl(ibucket_for_hash, hash, std::forward<P>(value));\n  }\n\n  template<typename... Args>\n  std::pair<iterator, bool> insert_impl(std::size_t ibucket_for_hash,\n                                        std::size_t hash, Args&& ... value_type_args)\n  {\n    if ((m_nb_elements - m_overflow_elements.size()) >= m_load_threshold) {\n      rehash(GrowthPolicy::next_bucket_count());\n      ibucket_for_hash = bucket_for_hash(hash);\n    }\n\n    std::size_t ibucket_empty = find_empty_bucket(ibucket_for_hash);\n\n    if (ibucket_empty < m_buckets.size()) {\n      do {\n        tsl_assert(ibucket_empty >= ibucket_for_hash);\n\n        // Empty bucket is in range of NeighborhoodSize, use it\n        if (ibucket_empty - ibucket_for_hash < NeighborhoodSize) {\n          auto it = insert_in_bucket(ibucket_empty, ibucket_for_hash,\n                                     hash, std::forward<Args>(value_type_args)...);\n          return std::make_pair(iterator(it, m_buckets.end(),\n                                         m_overflow_elements.begin()), true);\n        }\n      }\n\n      // else, try to swap values to get a closer empty bucket\n      while (swap_empty_bucket_closer(ibucket_empty));\n    }\n\n    // Load factor is too low or a rehash will not change the neighborhood, put the value in overflow list\n    if (size() < m_min_load_factor_rehash_threshold ||\n        !will_neighborhood_change_on_rehash(ibucket_for_hash)) {\n      auto it_insert = insert_in_overflow(std::forward<Args>(value_type_args)...);\n      m_buckets[ibucket_for_hash].set_overflow(true);\n      m_nb_elements++;\n      return std::make_pair(iterator(m_buckets.end(), m_buckets.end(), it_insert),\n                            true);\n    }\n\n    rehash(GrowthPolicy::next_bucket_count());\n    ibucket_for_hash = bucket_for_hash(hash);\n    return insert_impl(ibucket_for_hash, hash,\n                       std::forward<Args>(value_type_args)...);\n  }\n\n  /*\n   * Return true if a rehash will change the position of a key-value in the neighborhood of\n   * ibucket_neighborhood_check. In this case a rehash is needed instead of puting the value in overflow list.\n   */\n  bool will_neighborhood_change_on_rehash(size_t ibucket_neighborhood_check)\n  const\n  {\n    std::size_t expand_bucket_count = GrowthPolicy::next_bucket_count();\n    GrowthPolicy expand_growth_policy(expand_bucket_count);\n\n    for (size_t ibucket = ibucket_neighborhood_check;\n         ibucket < m_buckets.size() &&\n         (ibucket - ibucket_neighborhood_check) < NeighborhoodSize;\n         ++ibucket) {\n      tsl_assert(!m_buckets[ibucket].empty());\n      const size_t hash = USE_STORED_HASH_ON_REHASH ?\n                          m_buckets[ibucket].truncated_bucket_hash() :\n                          hash_key(KeySelect()(m_buckets[ibucket].value()));\n\n      if (bucket_for_hash(hash) != expand_growth_policy.bucket_for_hash(hash)) {\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  /*\n   * Return the index of an empty bucket in m_buckets.\n   * If none, the returned index equals m_buckets.size()\n   */\n  std::size_t find_empty_bucket(std::size_t ibucket_start) const\n  {\n    const std::size_t limit = std::min(ibucket_start + MAX_PROBES_FOR_EMPTY_BUCKET,\n                                       m_buckets.size());\n\n    for (; ibucket_start < limit; ibucket_start++) {\n      if (m_buckets[ibucket_start].empty()) {\n        return ibucket_start;\n      }\n    }\n\n    return m_buckets.size();\n  }\n\n  /*\n   * Insert value in ibucket_empty where value originally belongs to ibucket_for_hash\n   *\n   * Return bucket iterator to ibucket_empty\n   */\n  template<typename... Args>\n  iterator_buckets insert_in_bucket(std::size_t ibucket_empty,\n                                    std::size_t ibucket_for_hash,\n                                    std::size_t hash, Args&& ... value_type_args)\n  {\n    tsl_assert(ibucket_empty >= ibucket_for_hash);\n    tsl_assert(m_buckets[ibucket_empty].empty());\n    m_buckets[ibucket_empty].set_value_of_empty_bucket(hash,\n        std::forward<Args>(value_type_args)...);\n    tsl_assert(!m_buckets[ibucket_for_hash].empty());\n    m_buckets[ibucket_for_hash].toggle_neighbor_presence(ibucket_empty -\n        ibucket_for_hash);\n    m_nb_elements++;\n    return m_buckets.begin() + ibucket_empty;\n  }\n\n  /*\n   * Try to swap the bucket ibucket_empty_in_out with a bucket preceding it while keeping the neighborhood\n   * conditions correct.\n   *\n   * If a swap was possible, the position of ibucket_empty_in_out will be closer to 0 and true will re returned.\n   */\n  bool swap_empty_bucket_closer(std::size_t& ibucket_empty_in_out)\n  {\n    tsl_assert(ibucket_empty_in_out >= NeighborhoodSize);\n    const std::size_t neighborhood_start = ibucket_empty_in_out - NeighborhoodSize +\n                                           1;\n\n    for (std::size_t to_check = neighborhood_start; to_check < ibucket_empty_in_out;\n         to_check++) {\n      neighborhood_bitmap neighborhood_infos =\n        m_buckets[to_check].neighborhood_infos();\n      std::size_t to_swap = to_check;\n\n      while (neighborhood_infos != 0 && to_swap < ibucket_empty_in_out) {\n        if ((neighborhood_infos & 1) == 1) {\n          tsl_assert(m_buckets[ibucket_empty_in_out].empty());\n          tsl_assert(!m_buckets[to_swap].empty());\n          m_buckets[to_swap].swap_value_into_empty_bucket(\n            m_buckets[ibucket_empty_in_out]);\n          tsl_assert(!m_buckets[to_check].check_neighbor_presence(\n                       ibucket_empty_in_out - to_check));\n          tsl_assert(m_buckets[to_check].check_neighbor_presence(to_swap - to_check));\n          m_buckets[to_check].toggle_neighbor_presence(ibucket_empty_in_out - to_check);\n          m_buckets[to_check].toggle_neighbor_presence(to_swap - to_check);\n          ibucket_empty_in_out = to_swap;\n          return true;\n        }\n\n        to_swap++;\n        neighborhood_infos = neighborhood_bitmap(neighborhood_infos >> 1);\n      }\n    }\n\n    return false;\n  }\n\n\n\n  template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>\n  typename U::value_type * find_value_impl(const K& key, std::size_t hash,\n      iterator_buckets it_bucket)\n  {\n    return const_cast<typename U::value_type*>(\n             static_cast<const hopscotch_hash*>(this)->find_value_impl(key, hash,\n                 it_bucket));\n  }\n\n  /*\n   * Avoid the creation of an iterator to just get the value for operator[] and at() in maps. Faster this way.\n   *\n   * Return null if no value for key (TODO use std::optional when available).\n   */\n  template<class K, class U = ValueSelect, typename std::enable_if<has_mapped_type<U>::value>::type* = nullptr>\n  const typename U::value_type * find_value_impl(const K& key, std::size_t hash,\n      const_iterator_buckets it_bucket) const\n  {\n    auto it_find = find_in_buckets(key, hash, it_bucket);\n\n    if (it_find != m_buckets.cend()) {\n      return std::addressof(ValueSelect()(it_find->value()));\n    }\n\n    if (it_bucket->has_overflow()) {\n      auto it_overflow = find_in_overflow(key);\n\n      if (it_overflow != m_overflow_elements.end()) {\n        return std::addressof(ValueSelect()(*it_overflow));\n      }\n    }\n\n    return nullptr;\n  }\n\n  template<class K>\n  size_type count_impl(const K& key, std::size_t hash,\n                       const_iterator_buckets it_bucket) const\n  {\n    if (find_in_buckets(key, hash, it_bucket) != m_buckets.cend()) {\n      return 1;\n    } else if (it_bucket->has_overflow() &&\n               find_in_overflow(key) != m_overflow_elements.cend()) {\n      return 1;\n    } else {\n      return 0;\n    }\n  }\n\n  template<class K>\n  iterator find_impl(const K& key, std::size_t hash, iterator_buckets it_bucket)\n  {\n    auto it = find_in_buckets(key, hash, it_bucket);\n\n    if (it != m_buckets.end()) {\n      return iterator(it, m_buckets.end(), m_overflow_elements.begin());\n    }\n\n    if (!it_bucket->has_overflow()) {\n      return end();\n    }\n\n    return iterator(m_buckets.end(), m_buckets.end(), find_in_overflow(key));\n  }\n\n  template<class K>\n  const_iterator find_impl(const K& key, std::size_t hash,\n                           const_iterator_buckets it_bucket) const\n  {\n    auto it = find_in_buckets(key, hash, it_bucket);\n\n    if (it != m_buckets.cend()) {\n      return const_iterator(it, m_buckets.cend(), m_overflow_elements.cbegin());\n    }\n\n    if (!it_bucket->has_overflow()) {\n      return cend();\n    }\n\n    return const_iterator(m_buckets.cend(), m_buckets.cend(),\n                          find_in_overflow(key));\n  }\n\n  template<class K>\n  iterator_buckets find_in_buckets(const K& key, std::size_t hash,\n                                   iterator_buckets it_bucket)\n  {\n    auto it_find = static_cast<const hopscotch_hash*>(this)->find_in_buckets(key,\n                   hash, it_bucket);\n    return m_buckets.begin() + std::distance(m_buckets.cbegin(), it_find);\n  }\n\n\n  template<class K>\n  const_iterator_buckets find_in_buckets(const K& key, std::size_t hash,\n                                         const_iterator_buckets it_bucket) const\n  {\n    (void) hash; // Avoid warning of unused variable when StoreHash is false;\n    // TODO Try to optimize the function.\n    // I tried to use ffs and  __builtin_ffs functions but I could not reduce the time the function\n    // takes with -march=native\n    neighborhood_bitmap neighborhood_infos = it_bucket->neighborhood_infos();\n\n    while (neighborhood_infos != 0) {\n      if ((neighborhood_infos & 1) == 1) {\n        // Check StoreHash before calling bucket_hash_equal. Functionally it doesn't change anythin.\n        // If StoreHash is false, bucket_hash_equal is a no-op. Avoiding the call is there to help\n        // GCC optimizes `hash` parameter away, it seems to not be able to do without this hint.\n        if ((!StoreHash || it_bucket->bucket_hash_equal(hash)) &&\n            compare_keys(KeySelect()(it_bucket->value()), key)) {\n          return it_bucket;\n        }\n      }\n\n      ++it_bucket;\n      neighborhood_infos = neighborhood_bitmap(neighborhood_infos >> 1);\n    }\n\n    return m_buckets.end();\n  }\n\n\n\n  template < class K, class U = OverflowContainer,\n             typename std::enable_if < !has_key_compare<U>::value >::type* = nullptr >\n  iterator_overflow find_in_overflow(const K& key)\n  {\n    return std::find_if(m_overflow_elements.begin(), m_overflow_elements.end(),\n    [&](const value_type & value) {\n      return compare_keys(key, KeySelect()(value));\n    });\n  }\n\n  template < class K, class U = OverflowContainer,\n             typename std::enable_if < !has_key_compare<U>::value >::type* = nullptr >\n  const_iterator_overflow find_in_overflow(const K& key) const\n  {\n    return std::find_if(m_overflow_elements.cbegin(), m_overflow_elements.cend(),\n                        [&](const value_type& value)\n    {\n      return compare_keys(key, KeySelect()(value));\n    });\n  }\n\n  template<class K, class U = OverflowContainer, typename std::enable_if<has_key_compare<U>::value>::type* = nullptr>\n  iterator_overflow find_in_overflow(const K& key)\n  {\n    return m_overflow_elements.find(key);\n  }\n\n  template<class K, class U = OverflowContainer, typename std::enable_if<has_key_compare<U>::value>::type* = nullptr>\n  const_iterator_overflow find_in_overflow(const K& key) const\n  {\n    return m_overflow_elements.find(key);\n  }\n\n\n\n  template < class... Args, class U = OverflowContainer,\n             typename std::enable_if < !has_key_compare<U>::value >::type* = nullptr >\n  iterator_overflow insert_in_overflow(Args && ... value_type_args)\n  {\n    return m_overflow_elements.emplace(m_overflow_elements.end(),\n                                       std::forward<Args>(value_type_args)...);\n  }\n\n  template<class... Args, class U = OverflowContainer, typename std::enable_if<has_key_compare<U>::value>::type* = nullptr>\n  iterator_overflow insert_in_overflow(Args && ... value_type_args)\n  {\n    return m_overflow_elements.emplace(std::forward<Args>\n                                       (value_type_args)...).first;\n  }\n\n\n\n  template < class U = OverflowContainer,\n             typename std::enable_if < !has_key_compare<U>::value >::type* = nullptr >\n  hopscotch_hash new_hopscotch_hash(size_type bucket_count)\n  {\n    return hopscotch_hash(bucket_count, static_cast<Hash&>(*this),\n                          static_cast<KeyEqual&>(*this),\n                          get_allocator(), m_max_load_factor);\n  }\n\n  template<class U = OverflowContainer, typename std::enable_if<has_key_compare<U>::value>::type* = nullptr>\n  hopscotch_hash new_hopscotch_hash(size_type bucket_count)\n  {\n    return hopscotch_hash(bucket_count, static_cast<Hash&>(*this),\n                          static_cast<KeyEqual&>(*this),\n                          get_allocator(), m_max_load_factor, m_overflow_elements.key_comp());\n  }\n\npublic:\n  static const size_type DEFAULT_INIT_BUCKETS_SIZE = 16;\n  static constexpr float DEFAULT_MAX_LOAD_FACTOR = (NeighborhoodSize <= 30) ? 0.8f\n      : 0.9f;\n\nprivate:\n  static const std::size_t MAX_PROBES_FOR_EMPTY_BUCKET = 12 * NeighborhoodSize;\n  static constexpr float MIN_LOAD_FACTOR_FOR_REHASH = 0.1f;\n\n  static const bool USE_STORED_HASH_ON_REHASH =\n    StoreHash && std::is_same<GrowthPolicy, tsl::power_of_two_growth_policy>::value;\n\nprivate:\n  buckets_container_type m_buckets;\n  overflow_container_type m_overflow_elements;\n\n  size_type m_nb_elements;\n\n  float m_max_load_factor;\n  size_type m_load_threshold;\n  size_type m_min_load_factor_rehash_threshold;\n};\n\n} // end namespace detail_hopscotch_hash\n\n\n} // end namespace tsl\n\n#endif\n"
  },
  {
    "path": "common/hopscotch_map.hh",
    "content": "/**\n * MIT License\n *\n * Copyright (c) 2017 Tessil\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n#ifndef TSL_HOPSCOTCH_MAP_H\n#define TSL_HOPSCOTCH_MAP_H\n\n\n#include <algorithm>\n#include <cstddef>\n#include <functional>\n#include <initializer_list>\n#include <list>\n#include <memory>\n#include <type_traits>\n#include <utility>\n#include \"common/hopscotch_hash.hh\"\n\n\nnamespace tsl\n{\n\n/**\n * Implementation of a hash map using the hopscotch hashing algorithm.\n *\n * The Key and the value T must be either nothrow move-constructible, copy-constuctible or both.\n *\n * The size of the neighborhood (NeighborhoodSize) must be > 0 and <= 62 if StoreHash is false.\n * When StoreHash is true, 32-bits of the hash will be stored alongside the neighborhood limiting\n * the NeighborhoodSize to <= 30. There is no memory usage difference between\n * 'NeighborhoodSize 62; StoreHash false' and 'NeighborhoodSize 30; StoreHash true'.\n *\n * Storing the hash may improve performance on insert during the rehash process if the hash takes time\n * to compute. It may also improve read performance if the KeyEqual function takes time (or incurs a cache-miss).\n * If used with simple Hash and KeyEqual it may slow things down.\n *\n * StoreHash can only be set if the GrowthPolicy is set to tsl::power_of_two_growth_policy.\n *\n * GrowthPolicy defines how the map grows and consequently how a hash value is mapped to a bucket.\n * By default the map uses tsl::power_of_two_growth_policy. This policy keeps the number of buckets\n * to a power of two and uses a mask to map the hash to a bucket instead of the slow modulo.\n * You may define your own growth policy, check tsl::power_of_two_growth_policy for the interface.\n *\n * If the destructors of Key or T throw an exception, behaviour of the class is undefined.\n *\n * Iterators invalidation:\n *  - clear, operator=, reserve, rehash: always invalidate the iterators.\n *  - insert, emplace, emplace_hint, operator[]: if there is an effective insert, invalidate the iterators\n *    if a displacement is needed to resolve a collision (which mean that most of the time,\n *    insert will invalidate the iterators). Or if there is a rehash.\n *  - erase: iterator on the erased element is the only one which become invalid.\n */\ntemplate<class Key,\n         class T,\n         class Hash = std::hash<Key>,\n         class KeyEqual = std::equal_to<Key>,\n         class Allocator = std::allocator<std::pair<Key, T>>,\n         unsigned int NeighborhoodSize = 62,\n         bool StoreHash = false,\n         class GrowthPolicy = tsl::power_of_two_growth_policy>\nclass hopscotch_map\n{\nprivate:\n  template<typename U>\n  using has_is_transparent = tsl::detail_hopscotch_hash::has_is_transparent<U>;\n\n  class KeySelect\n  {\n  public:\n    using key_type = Key;\n\n    const key_type& operator()(const std::pair<Key, T>& key_value) const\n    {\n      return key_value.first;\n    }\n\n    key_type& operator()(std::pair<Key, T>& key_value)\n    {\n      return key_value.first;\n    }\n  };\n\n  class ValueSelect\n  {\n  public:\n    using value_type = T;\n\n    const value_type& operator()(const std::pair<Key, T>& key_value) const\n    {\n      return key_value.second;\n    }\n\n    value_type& operator()(std::pair<Key, T>& key_value)\n    {\n      return key_value.second;\n    }\n  };\n\n\n  using overflow_container_type = std::list<std::pair<Key, T>, Allocator>;\n  using ht =\n    detail_hopscotch_hash::hopscotch_hash<std::pair<Key, T>, KeySelect, ValueSelect,\n    Hash, KeyEqual,\n    Allocator, NeighborhoodSize,\n    StoreHash, GrowthPolicy,\n    overflow_container_type>;\n\npublic:\n  using key_type = typename ht::key_type;\n  using mapped_type = T;\n  using value_type = typename ht::value_type;\n  using size_type = typename ht::size_type;\n  using difference_type = typename ht::difference_type;\n  using hasher = typename ht::hasher;\n  using key_equal = typename ht::key_equal;\n  using allocator_type = typename ht::allocator_type;\n  using reference = typename ht::reference;\n  using const_reference = typename ht::const_reference;\n  using pointer = typename ht::pointer;\n  using const_pointer = typename ht::const_pointer;\n  using iterator = typename ht::iterator;\n  using const_iterator = typename ht::const_iterator;\n\n\n\n  /*\n   * Constructors\n   */\n  hopscotch_map() : hopscotch_map(ht::DEFAULT_INIT_BUCKETS_SIZE)\n  {\n  }\n\n  explicit hopscotch_map(size_type bucket_count,\n                         const Hash& hash = Hash(),\n                         const KeyEqual& equal = KeyEqual(),\n                         const Allocator& alloc = Allocator()) :\n    m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR)\n  {\n  }\n\n  hopscotch_map(size_type bucket_count,\n                const Allocator& alloc) : hopscotch_map(bucket_count, Hash(), KeyEqual(), alloc)\n  {\n  }\n\n  hopscotch_map(size_type bucket_count,\n                const Hash& hash,\n                const Allocator& alloc) : hopscotch_map(bucket_count, hash, KeyEqual(), alloc)\n  {\n  }\n\n  explicit hopscotch_map(const Allocator& alloc) : hopscotch_map(\n      ht::DEFAULT_INIT_BUCKETS_SIZE, alloc)\n  {\n  }\n\n  template<class InputIt>\n  hopscotch_map(InputIt first, InputIt last,\n                size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE,\n                const Hash& hash = Hash(),\n                const KeyEqual& equal = KeyEqual(),\n                const Allocator& alloc = Allocator()) : hopscotch_map(bucket_count, hash, equal,\n                      alloc)\n  {\n    insert(first, last);\n  }\n\n  template<class InputIt>\n  hopscotch_map(InputIt first, InputIt last,\n                size_type bucket_count,\n                const Allocator& alloc) : hopscotch_map(first, last, bucket_count, Hash(),\n                      KeyEqual(), alloc)\n  {\n  }\n\n  template<class InputIt>\n  hopscotch_map(InputIt first, InputIt last,\n                size_type bucket_count,\n                const Hash& hash,\n                const Allocator& alloc) : hopscotch_map(first, last, bucket_count, hash,\n                      KeyEqual(), alloc)\n  {\n  }\n\n  hopscotch_map(std::initializer_list<value_type> init,\n                size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE,\n                const Hash& hash = Hash(),\n                const KeyEqual& equal = KeyEqual(),\n                const Allocator& alloc = Allocator()) :\n    hopscotch_map(init.begin(), init.end(), bucket_count, hash, equal, alloc)\n  {\n  }\n\n  hopscotch_map(std::initializer_list<value_type> init,\n                size_type bucket_count,\n                const Allocator& alloc) :\n    hopscotch_map(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc)\n  {\n  }\n\n  hopscotch_map(std::initializer_list<value_type> init,\n                size_type bucket_count,\n                const Hash& hash,\n                const Allocator& alloc) :\n    hopscotch_map(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc)\n  {\n  }\n\n\n  hopscotch_map& operator=(std::initializer_list<value_type> ilist)\n  {\n    m_ht.clear();\n    m_ht.reserve(ilist.size());\n    m_ht.insert(ilist.begin(), ilist.end());\n    return *this;\n  }\n\n  allocator_type get_allocator() const\n  {\n    return m_ht.get_allocator();\n  }\n\n\n  /*\n   * Iterators\n   */\n  iterator begin() noexcept\n  {\n    return m_ht.begin();\n  }\n  const_iterator begin() const noexcept\n  {\n    return m_ht.begin();\n  }\n  const_iterator cbegin() const noexcept\n  {\n    return m_ht.cbegin();\n  }\n\n  iterator end() noexcept\n  {\n    return m_ht.end();\n  }\n  const_iterator end() const noexcept\n  {\n    return m_ht.end();\n  }\n  const_iterator cend() const noexcept\n  {\n    return m_ht.cend();\n  }\n\n\n  /*\n   * Capacity\n   */\n  bool empty() const noexcept\n  {\n    return m_ht.empty();\n  }\n  size_type size() const noexcept\n  {\n    return m_ht.size();\n  }\n  size_type max_size() const noexcept\n  {\n    return m_ht.max_size();\n  }\n\n  /*\n   * Modifiers\n   */\n  void clear() noexcept\n  {\n    m_ht.clear();\n  }\n\n\n\n\n  std::pair<iterator, bool> insert(const value_type& value)\n  {\n    return m_ht.insert(value);\n  }\n\n  template < class P, typename std::enable_if < std::is_constructible <\n               value_type, P&& >::value >::type* = nullptr >\n  std::pair<iterator, bool> insert(P && value)\n  {\n    return m_ht.insert(std::forward<P>(value));\n  }\n\n  std::pair<iterator, bool> insert(value_type&& value)\n  {\n    return m_ht.insert(std::move(value));\n  }\n\n\n  iterator insert(const_iterator hint, const value_type& value)\n  {\n    return m_ht.insert(hint, value);\n  }\n\n  template < class P, typename std::enable_if < std::is_constructible <\n               value_type, P&& >::value >::type* = nullptr >\n  iterator insert(const_iterator hint, P && value)\n  {\n    return m_ht.insert(hint, std::forward<P>(value));\n  }\n\n  iterator insert(const_iterator hint, value_type&& value)\n  {\n    return m_ht.insert(hint, std::move(value));\n  }\n\n\n  template<class InputIt>\n  void insert(InputIt first, InputIt last)\n  {\n    m_ht.insert(first, last);\n  }\n\n  void insert(std::initializer_list<value_type> ilist)\n  {\n    m_ht.insert(ilist.begin(), ilist.end());\n  }\n\n\n\n\n  template<class M>\n  std::pair<iterator, bool> insert_or_assign(const key_type& k, M&& obj)\n  {\n    return m_ht.insert_or_assign(k, std::forward<M>(obj));\n  }\n\n  template<class M>\n  std::pair<iterator, bool> insert_or_assign(key_type&& k, M&& obj)\n  {\n    return m_ht.insert_or_assign(std::move(k), std::forward<M>(obj));\n  }\n\n  template<class M>\n  iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj)\n  {\n    return m_ht.insert_or_assign(hint, k, std::forward<M>(obj));\n  }\n\n  template<class M>\n  iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj)\n  {\n    return m_ht.insert_or_assign(hint, std::move(k), std::forward<M>(obj));\n  }\n\n\n\n\n  /**\n   * Due to the way elements are stored, emplace will need to move or copy the key-value once.\n   * The method is equivalent to insert(value_type(std::forward<Args>(args)...));\n   *\n   * Mainly here for compatibility with the std::unordered_map interface.\n   */\n  template<class... Args>\n  std::pair<iterator, bool> emplace(Args&& ... args)\n  {\n    return m_ht.emplace(std::forward<Args>(args)...);\n  }\n\n\n\n\n  /**\n   * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once.\n   * The method is equivalent to insert(hint, value_type(std::forward<Args>(args)...));\n   *\n   * Mainly here for compatibility with the std::unordered_map interface.\n   */\n  template<class... Args>\n  iterator emplace_hint(const_iterator hint, Args&& ... args)\n  {\n    return m_ht.emplace_hint(hint, std::forward<Args>(args)...);\n  }\n\n\n\n\n  template<class... Args>\n  std::pair<iterator, bool> try_emplace(const key_type& k, Args&& ... args)\n  {\n    return m_ht.try_emplace(k, std::forward<Args>(args)...);\n  }\n\n  template<class... Args>\n  std::pair<iterator, bool> try_emplace(key_type&& k, Args&& ... args)\n  {\n    return m_ht.try_emplace(std::move(k), std::forward<Args>(args)...);\n  }\n\n  template<class... Args>\n  iterator try_emplace(const_iterator hint, const key_type& k, Args&& ... args)\n  {\n    return m_ht.try_emplace(hint, k, std::forward<Args>(args)...);\n  }\n\n  template<class... Args>\n  iterator try_emplace(const_iterator hint, key_type&& k, Args&& ... args)\n  {\n    return m_ht.try_emplace(hint, std::move(k), std::forward<Args>(args)...);\n  }\n\n\n\n\n  iterator erase(iterator pos)\n  {\n    return m_ht.erase(pos);\n  }\n  iterator erase(const_iterator pos)\n  {\n    return m_ht.erase(pos);\n  }\n  iterator erase(const_iterator first, const_iterator last)\n  {\n    return m_ht.erase(first, last);\n  }\n  size_type erase(const key_type& key)\n  {\n    return m_ht.erase(key);\n  }\n\n  /**\n   * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same\n   * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash.\n   */\n  size_type erase(const key_type& key, std::size_t precalculated_hash)\n  {\n    return m_ht.erase(key, precalculated_hash);\n  }\n\n  /**\n   * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists.\n   * If so, K must be hashable and comparable to Key.\n   */\n  template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>\n  size_type erase(const K& key)\n  {\n    return m_ht.erase(key);\n  }\n\n  /**\n   * @copydoc erase(const K& key)\n   *\n   * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same\n   * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash.\n   */\n  template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>\n  size_type erase(const K& key, std::size_t precalculated_hash)\n  {\n    return m_ht.erase(key, precalculated_hash);\n  }\n\n\n\n\n  void swap(hopscotch_map& other)\n  {\n    other.m_ht.swap(m_ht);\n  }\n\n  /*\n   * Lookup\n   */\n  T& at(const Key& key)\n  {\n    return m_ht.at(key);\n  }\n\n  /**\n   * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same\n   * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.\n   */\n  T& at(const Key& key, std::size_t precalculated_hash)\n  {\n    return m_ht.at(key, precalculated_hash);\n  }\n\n\n  const T& at(const Key& key) const\n  {\n    return m_ht.at(key);\n  }\n\n  /**\n   * @copydoc at(const Key& key, std::size_t precalculated_hash)\n   */\n  const T& at(const Key& key, std::size_t precalculated_hash) const\n  {\n    return m_ht.at(key, precalculated_hash);\n  }\n\n\n  /**\n   * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists.\n   * If so, K must be hashable and comparable to Key.\n   */\n  template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>\n  T & at(const K& key)\n  {\n    return m_ht.at(key);\n  }\n\n  /**\n   * @copydoc at(const K& key)\n   *\n   * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same\n   * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.\n   */\n  template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>\n  T & at(const K& key, std::size_t precalculated_hash)\n  {\n    return m_ht.at(key, precalculated_hash);\n  }\n\n\n  /**\n   * @copydoc at(const K& key)\n   */\n  template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>\n  const T & at(const K& key) const\n  {\n    return m_ht.at(key);\n  }\n\n  /**\n   * @copydoc at(const K& key, std::size_t precalculated_hash)\n   */\n  template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>\n  const T & at(const K& key, std::size_t precalculated_hash) const\n  {\n    return m_ht.at(key, precalculated_hash);\n  }\n\n\n\n\n  T& operator[](const Key& key)\n  {\n    return m_ht[key];\n  }\n  T& operator[](Key&& key)\n  {\n    return m_ht[std::move(key)];\n  }\n\n\n\n\n  size_type count(const Key& key) const\n  {\n    return m_ht.count(key);\n  }\n\n  /**\n   * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same\n   * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.\n   */\n  size_type count(const Key& key, std::size_t precalculated_hash) const\n  {\n    return m_ht.count(key, precalculated_hash);\n  }\n\n  /**\n   * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists.\n   * If so, K must be hashable and comparable to Key.\n   */\n  template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>\n  size_type count(const K& key) const\n  {\n    return m_ht.count(key);\n  }\n\n  /**\n   * @copydoc count(const K& key) const\n   *\n   * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same\n   * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.\n   */\n  template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>\n  size_type count(const K& key, std::size_t precalculated_hash) const\n  {\n    return m_ht.count(key, precalculated_hash);\n  }\n\n\n\n\n  iterator find(const Key& key)\n  {\n    return m_ht.find(key);\n  }\n\n  /**\n   * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same\n   * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.\n   */\n  iterator find(const Key& key, std::size_t precalculated_hash)\n  {\n    return m_ht.find(key, precalculated_hash);\n  }\n\n  const_iterator find(const Key& key) const\n  {\n    return m_ht.find(key);\n  }\n\n  /**\n   * @copydoc find(const Key& key, std::size_t precalculated_hash)\n   */\n  const_iterator find(const Key& key, std::size_t precalculated_hash) const\n  {\n    return m_ht.find(key, precalculated_hash);\n  }\n\n  /**\n   * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists.\n   * If so, K must be hashable and comparable to Key.\n   */\n  template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>\n  iterator find(const K& key)\n  {\n    return m_ht.find(key);\n  }\n\n  /**\n   * @copydoc find(const K& key)\n   *\n   * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same\n   * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.\n   */\n  template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>\n  iterator find(const K& key, std::size_t precalculated_hash)\n  {\n    return m_ht.find(key, precalculated_hash);\n  }\n\n  /**\n   * @copydoc find(const K& key)\n   */\n  template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>\n  const_iterator find(const K& key) const\n  {\n    return m_ht.find(key);\n  }\n\n  /**\n   * @copydoc find(const K& key)\n   *\n   * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same\n   * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.\n   */\n  template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>\n  const_iterator find(const K& key, std::size_t precalculated_hash) const\n  {\n    return m_ht.find(key, precalculated_hash);\n  }\n\n\n\n\n  std::pair<iterator, iterator> equal_range(const Key& key)\n  {\n    return m_ht.equal_range(key);\n  }\n\n  /**\n   * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same\n   * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.\n   */\n  std::pair<iterator, iterator> equal_range(const Key& key,\n      std::size_t precalculated_hash)\n  {\n    return m_ht.equal_range(key, precalculated_hash);\n  }\n\n  std::pair<const_iterator, const_iterator> equal_range(const Key& key) const\n  {\n    return m_ht.equal_range(key);\n  }\n\n  /**\n   * @copydoc equal_range(const Key& key, std::size_t precalculated_hash)\n   */\n  std::pair<const_iterator, const_iterator> equal_range(const Key& key,\n      std::size_t precalculated_hash) const\n  {\n    return m_ht.equal_range(key, precalculated_hash);\n  }\n\n  /**\n   * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists.\n   * If so, K must be hashable and comparable to Key.\n   */\n  template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>\n  std::pair<iterator, iterator> equal_range(const K& key)\n  {\n    return m_ht.equal_range(key);\n  }\n\n\n  /**\n   * @copydoc equal_range(const K& key)\n   *\n   * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same\n   * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash.\n   */\n  template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>\n  std::pair<iterator, iterator> equal_range(const K& key,\n      std::size_t precalculated_hash)\n  {\n    return m_ht.equal_range(key, precalculated_hash);\n  }\n\n  /**\n   * @copydoc equal_range(const K& key)\n   */\n  template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>\n  std::pair<const_iterator, const_iterator> equal_range(const K& key) const\n  {\n    return m_ht.equal_range(key);\n  }\n\n  /**\n   * @copydoc equal_range(const K& key, std::size_t precalculated_hash)\n   */\n  template<class K, class KE = KeyEqual, typename std::enable_if<has_is_transparent<KE>::value>::type* = nullptr>\n  std::pair<const_iterator, const_iterator> equal_range(const K& key,\n      std::size_t precalculated_hash) const\n  {\n    return m_ht.equal_range(key, precalculated_hash);\n  }\n\n\n\n\n  /*\n   * Bucket interface\n   */\n  size_type bucket_count() const\n  {\n    return m_ht.bucket_count();\n  }\n  size_type max_bucket_count() const\n  {\n    return m_ht.max_bucket_count();\n  }\n\n\n  /*\n   *  Hash policy\n   */\n  float load_factor() const\n  {\n    return m_ht.load_factor();\n  }\n  float max_load_factor() const\n  {\n    return m_ht.max_load_factor();\n  }\n  void max_load_factor(float ml)\n  {\n    m_ht.max_load_factor(ml);\n  }\n\n  void rehash(size_type count)\n  {\n    m_ht.rehash(count);\n  }\n  void reserve(size_type count)\n  {\n    m_ht.reserve(count);\n  }\n\n\n  /*\n   * Observers\n   */\n  hasher hash_function() const\n  {\n    return m_ht.hash_function();\n  }\n  key_equal key_eq() const\n  {\n    return m_ht.key_eq();\n  }\n\n  /*\n   * Other\n   */\n\n  /**\n   * Convert a const_iterator to an iterator.\n   */\n  iterator mutable_iterator(const_iterator pos)\n  {\n    return m_ht.mutable_iterator(pos);\n  }\n\n  size_type overflow_size() const noexcept\n  {\n    return m_ht.overflow_size();\n  }\n\n  friend bool operator==(const hopscotch_map& lhs, const hopscotch_map& rhs)\n  {\n    if (lhs.size() != rhs.size()) {\n      return false;\n    }\n\n    for (const auto& element_lhs : lhs) {\n      const auto it_element_rhs = rhs.find(element_lhs.first);\n\n      if (it_element_rhs == rhs.cend() ||\n          element_lhs.second != it_element_rhs->second) {\n        return false;\n      }\n    }\n\n    return true;\n  }\n\n  friend bool operator!=(const hopscotch_map& lhs, const hopscotch_map& rhs)\n  {\n    return !operator==(lhs, rhs);\n  }\n\n  friend void swap(hopscotch_map& lhs, hopscotch_map& rhs)\n  {\n    lhs.swap(rhs);\n  }\n\n\n\nprivate:\n  ht m_ht;\n};\n\n} // end namespace tsl\n\n#endif\n"
  },
  {
    "path": "common/http/HttpHandler.hh",
    "content": "// ----------------------------------------------------------------------\n// File: HttpHandler.hh\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   HttpHandler.hh\n *\n * @brief  Class to handle plain HTTP requests and build responses.\n */\n\n#ifndef __EOSCOMMON_HTTP_HANDLER__HH__\n#define __EOSCOMMON_HTTP_HANDLER__HH__\n\n#include \"common/http/ProtocolHandler.hh\"\n#include \"common/Namespace.hh\"\n#include <map>\n#include <string>\n\nEOSCOMMONNAMESPACE_BEGIN\n\nclass HttpHandler : virtual public eos::common::ProtocolHandler\n{\n\npublic:\n  /**\n   * Standard plain HTTP request methods\n   */\n  enum Methods {\n    GET,     //!< Requests a representation of the specified resource. Requests\n    //!< using GET should only retrieve data and have no other effect.\n    HEAD,    //!< Asks for the response identical to the one that would\n    //!< correspond to a GET request, but without the response body.\n    //!< This is useful for retrieving meta-information written in\n    //!< response headers, without having to transport the entire content.\n    POST,    //!< Requests that the server accept the entity enclosed in the\n    //!< request as a new subordinate of the web resource identified by\n    //!< the URI.\n    PUT,     //!< Requests that the enclosed entity be stored under the supplied\n    //!< URI. If the URI refers to an already existing resource, it is\n    //!< modified; if the URI does not point to an existing resource,\n    //!< then the server can create the resource with that URI.\n    DELETE,  //!< Deletes the specified resource.\n    TRACE,   //!< Echoes back the received request so that a client can see what\n    //!< (if any) changes or additions have been made by intermediate\n    //!< servers.\n    OPTIONS, //!< Returns the HTTP methods that the server supports for specified\n    //!< URL. This can be used to check the functionality of a web\n    //!< server by requesting '*' instead of a specific resource.\n    CONNECT, //!< Converts the request connection to a transparent TCP/IP tunnel,\n    //!< usually to facilitate SSL-encrypted communication (HTTPS)\n    //!< through an unencrypted HTTP proxy.\n    PATCH,   //!< Is used to apply partial modifications to a resource.\n    CREATE,  //!< internal method used by Xrdhttp - creates a file without payload\n  };\n\n  /**\n   * Constructor\n   */\n  HttpHandler() {};\n\n  /**\n   * Destructor\n   */\n  virtual ~HttpHandler() {};\n\n  /**\n   * Check whether the given method and headers are a match for this protocol.\n   *\n   * @param method  the request verb used by the client (GET, PUT, etc)\n   * @param headers the map of request headers\n   *\n   * @return true if the protocol matches, false otherwise\n   */\n  static bool\n  Matches(const std::string& method, HeaderMap& headers);\n\n  /**\n   * Build a response to the given plain HTTP request.\n   *\n   * @param request  the map of request headers sent by the client\n   * @param method   the request verb used by the client (GET, PUT, etc)\n   * @param url      the URL requested by the client\n   * @param query    the GET request query string (if any)\n   * @param body     the request body data sent by the client\n   * @param bodysize the size of the request body\n   * @param cookies  the map of cookie headers\n   */\n  virtual void\n  HandleRequest(eos::common::HttpRequest* request) = 0;\n\n  /**\n   * Convert the given request method string into its integer constant\n   * representation.\n   *\n   * @param method  the method string to convert\n   *\n   * @return the converted method string as an integer\n   */\n  inline static int\n  ParseMethodString(const std::string& method)\n  {\n    if (method == \"GET\") {\n      return Methods::GET;\n    } else if (method == \"HEAD\") {\n      return Methods::HEAD;\n    } else if (method == \"POST\") {\n      return Methods::POST;\n    } else if (method == \"PUT\") {\n      return Methods::PUT;\n    } else if (method == \"DELETE\") {\n      return Methods::DELETE;\n    } else if (method == \"TRACE\") {\n      return Methods::TRACE;\n    } else if (method == \"OPTIONS\") {\n      return Methods::OPTIONS;\n    } else if (method == \"CONNECT\") {\n      return Methods::CONNECT;\n    } else if (method == \"PATCH\") {\n      return Methods::PATCH;\n    } else if (method == \"CREATE\") {\n      return Methods::CREATE;\n    } else {\n      return -1;\n    }\n  }\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif /* __EOSCOMMON_HTTP_HANDLER__HH__ */\n"
  },
  {
    "path": "common/http/HttpRequest.cc",
    "content": "// ----------------------------------------------------------------------\n// File: HttpRequest.cc\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"common/http/HttpRequest.hh\"\n#include \"common/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include \"common/http/OwnCloud.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucString.hh>\n/*----------------------------------------------------------------------------*/\n#include <sstream>\n/*----------------------------------------------------------------------------*/\n\nEOSCOMMONNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\nHttpRequest::HttpRequest (HeaderMap          headers,\n                          const std::string &method,\n                          const std::string &url,\n                          const std::string &query,\n                          const std::string &body,\n                          size_t            *bodySize,\n                          HeaderMap          cookies, \n\t\t\t  bool               xrdhttp) :\n  mRequestHeaders(headers), mRequestMethod(method), mRequestUrl(url),\n  mRequestQuery(query), mRequestBody(body), mRequestBodySize(bodySize),\n  mRequestCookies(cookies),mXrdHttp(xrdhttp) {}\n\n/*----------------------------------------------------------------------------*/\nstd::string\nHttpRequest::ToString()\n{\n  std::stringstream ss;\n  ss << GetMethod() << \" \" << GetUrl() << (GetQuery().size() ? \"?\" : \"\")\n     << GetQuery() << std::endl;\n  for (auto it = GetHeaders().begin(); it != GetHeaders().end(); ++it)\n  {\n    ss << it->first << \": \" << it->second.c_str() << std::endl;\n  }\n  return ss.str();\n}\n\n/*----------------------------------------------------------------------------*/\n\n/**\n * @return the client request URL\n */\nconst std::string\nHttpRequest::GetUrl (bool orig) \n{\n  std::string ocurl;\n  if (orig)\n    return mRequestUrl;\n\n  XrdOucString sUrl = mRequestUrl.c_str();\n  eos::common::OwnCloud::OwnCloudRemapping(sUrl, this);\n  eos::common::OwnCloud::ReplaceRemotePhp(sUrl);\n  ocurl = sUrl.c_str();\n  return ocurl;\n}\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/http/HttpRequest.hh",
    "content": "// ----------------------------------------------------------------------\n// File: HttpRequest.hh\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   HttpRequest.hh\n *\n * @brief  Simple utility class to hold client request parameters.\n */\n\n#ifndef __EOSCOMMON_HTTP_REQUEST__HH__\n#define __EOSCOMMON_HTTP_REQUEST__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/Namespace.hh\"\n#include \"common/Utils.hh\"\n/*----------------------------------------------------------------------------*/\n#include <map>\n#include <string>\n/*----------------------------------------------------------------------------*/\n\nEOSCOMMONNAMESPACE_BEGIN\n\nclass HttpRequest\n{\n\npublic:\n  typedef std::map<std::string, std::string> HeaderMap;\n\nprivate:\n  HeaderMap         mRequestHeaders;  //!< the map of client request headers\n  const std::string mRequestMethod;   //!< the client request method\n  const std::string mRequestUrl;      //!< the client request URL\n  std::string       mRequestQuery;    //!< the client request query string\n  const std::string mRequestBody;     //!< the client request body\n  size_t           *mRequestBodySize; //!< the size of the client request body\n  HeaderMap         mRequestCookies;  //!< the client request cookie header map\n  bool              mXrdHttp;         //!< the request came with XrdHttp\n\npublic:\n\n  /**\n   * Constructor\n   *\n   * @param headers  the map of request headers sent by the client\n   * @param method   the request verb used by the client (GET, PUT, etc)\n   * @param url      the URL requested by the client\n   * @param query    the GET request query string (if any)\n   * @param body     the request body data sent by the client\n   * @param bodysize the size of the request body\n   * @param cookies  the map of cookie headers\n   * @param xrdhttp  indicate an xrdhttp request\n   */\n  HttpRequest (HeaderMap          headers,\n               const std::string &method,\n               const std::string &url,\n               const std::string &query,\n               const std::string &uploadData,\n               size_t            *uploadDataSize,\n               HeaderMap          cookies, \n\t       bool               xrdhttp = false);\n\n  /**\n   * Destructor\n   */\n  virtual ~HttpRequest () {};\n\n  /**\n   * @return the map of request headers\n   */\n  inline HeaderMap&\n  GetHeaders () { return mRequestHeaders; }\n\n  /**\n   * @return the client request method\n   */\n  inline const std::string&\n  GetMethod () { return mRequestMethod; }\n\n  /**\n   * @return the client request URL\n   */\n  const std::string\n  GetUrl (bool orig=false);\n\n  /**\n   * @return the client request query string (GET parameters)\n   */\n  inline const std::string&\n  GetQuery () { return mRequestQuery; }\n\n  /**\n   * @return the client request body\n   */\n  inline const std::string&\n  GetBody () { return mRequestBody; }\n\n  /**\n   * @return the size of the client request body\n   */\n  inline size_t*\n  GetBodySize () { return mRequestBodySize; }\n\n  /**\n   * @return the map of client request cookie headers\n   */\n  inline HeaderMap&\n  GetCookies () { return mRequestCookies; }\n  \n  /**\n   * @return true if xrdhttp request\n   */\n  inline bool&\n  IsXrdHttp () { return mXrdHttp; }\n\n  /**\n   * Change the request query string, useful in the case where a capability\n   * cookie should override the request query\n   *\n   * @param query  the new query string to use\n   */\n  inline void\n  SetQuery (std::string query) { mRequestQuery = query; }\n\n  /**\n   * @return nicely formatted request, ready for printing\n   */\n  std::string\n  ToString();\n\n  /**\n   * Add the eos HTTP application CGI query\n   */\n  void AddEosApp () {\n    common::AddEosApp(mRequestQuery,\"http\");\n  }\n\n};\n\n/*----------------------------------------------------------------------------*/\nEOSCOMMONNAMESPACE_END\n\n#endif /* __EOSCOMMON_HTTP_REQUEST__HH__ */\n"
  },
  {
    "path": "common/http/HttpResponse.cc",
    "content": "// ----------------------------------------------------------------------\n// File: HttpResponse.cc\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/http/HttpResponse.hh\"\n#include \"common/Logging.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n#include <sstream>\n/*----------------------------------------------------------------------------*/\n\nEOSCOMMONNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\nvoid\nHttpResponse::AddHeader(const std::string key, const std::string value)\n{\n  mResponseHeaders[key] = value;\n}\n\n/*----------------------------------------------------------------------------*/\nstd::string\nHttpResponse::ContentType(const std::string& path)\n{\n  XrdOucString name = path.c_str();\n\n  if (name.endswith(\".txt\") || name.endswith(\".log\")) {\n    return \"text/plain\";\n  }\n\n  if (name.endswith(\".xml\")) {\n    return \"text/xml\";\n  }\n\n  if (name.endswith(\".gif\")) {\n    return \"image/gif\";\n  }\n\n  if (name.endswith(\".jpg\")) {\n    return \"image/jpg\";\n  }\n\n  if (name.endswith(\".png\")) {\n    return \"image/png\";\n  }\n\n  if (name.endswith(\".tiff\")) {\n    return \"image/tiff\";\n  }\n\n  if (name.endswith(\".mp3\")) {\n    return \"audio/mp3\";\n  }\n\n  if (name.endswith(\".mp4\")) {\n    return \"audio/mp4\";\n  }\n\n  if (name.endswith(\".pdf\")) {\n    return \"application/pdf\";\n  }\n\n  if (name.endswith(\".zip\")) {\n    return \"application/zip\";\n  }\n\n  if (name.endswith(\".gzip\")) {\n    return \"application/gzip\";\n  }\n\n  if (name.endswith(\".tar.gz\")) {\n    return \"application/gzip\";\n  }\n\n  // default is text/plain\n  return \"text/plain\";\n}\n\n//------------------------------------------------------------------------------\n// Serialize response to string\n//------------------------------------------------------------------------------\nstd::string\nHttpResponse::ToString()\n{\n  std::stringstream ss;\n  ss <<   \"Response code: \" << mResponseCode << std::endl;\n\n  for (auto it = GetHeaders().begin(); it != GetHeaders().end(); ++it) {\n    ss << it->first  << \": \" << it->second << std::endl;\n  }\n\n  ss << \"\\n\\n\" << mResponseBody << std::endl;\n  return ss.str();\n}\n\n//------------------------------------------------------------------------------\n// Get response code string description\n//------------------------------------------------------------------------------\nstd::string\nHttpResponse::GetResponseCodeDescription()\n{\n  switch (mResponseCode) {\n  case CONTINUE:\n    return std::string(\"CONTINUE\");\n\n  case OK:\n    return std::string(\"OK\");\n\n  case CREATED:\n    return std::string(\"CREATED\");\n\n  case NO_CONTENT:\n    return std::string(\"NO_CONTENT\");\n\n  case PARTIAL_CONTENT:\n    return std::string(\"PARTIAL_CONTENT\");\n\n  case MULTI_STATUS:\n    return std::string(\"MULTI_STATUS\");\n\n  case NOT_MODIFIED:\n    return std::string(\"NOT_MODIFIED\");\n\n  case TEMPORARY_REDIRECT:\n    return std::string(\"TEMPORARY_REDIRECT\");\n\n  case BAD_REQUEST:\n    return std::string(\"BAD_REQUEST\");\n\n  case UNAUTHORIZED:\n    return std::string(\"UNAUTHORIZED\");\n\n  case FORBIDDEN:\n    return std::string(\"FORBIDDEN\");\n\n  case NOT_FOUND:\n    return std::string(\"NOT_FOUND\");\n\n  case METHOD_NOT_ALLOWED:\n    return std::string(\"METHOD_NOT_ALLOWED\");\n\n  case CONFLICT:\n    return std::string(\"CONFLICT\");\n\n  case LENGTH_REQUIRED:\n    return std::string(\"LENGTH_REQUIRED\");\n\n  case PRECONDITION_FAILED:\n    return std::string(\"PRECONDITION_FAILED\");\n\n  case UNSUPPORTED_MEDIA_TYPE:\n    return std::string(\"UNSUPPORTED_MEDIA_TYPE\");\n\n  case REQUESTED_RANGE_NOT_SATISFIABLE:\n    return std::string(\"REQUESTED_RANGE_NOT_SATISFIABLE\");\n\n  case UNPROCESSABLE_ENTITY:\n    return std::string(\"UNPROCESSABLE_ENTITY\");\n\n  case FAILED_DEPENDENCY:\n    return std::string(\"FAILED_DEPENDENCY\");\n\n  case INTERNAL_SERVER_ERROR:\n    return std::string(\"INTERNAL_SERVER_ERROR\");\n\n  case NOT_IMPLEMENTED:\n    return std::string(\"NOT_IMPLEMENTED\");\n\n  case BAD_GATEWAY:\n    return std::string(\"BAD_GATEWAY\");\n\n  case SERVICE_UNAVAILABLE:\n    return std::string(\"SERVICE_UNAVAILABLE\");\n\n  default:\n    return std::string(\"UNKNOWN_RESPONSE_CODE\");\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get serialized headers to string applying filter to the header keys\n//------------------------------------------------------------------------------\nstd::string\nHttpResponse::GetHdrsWithFilter(const std::set<std::string_view>& filter_out)\nconst\n{\n  std::ostringstream oss;\n\n  for (const auto& hdr : mResponseHeaders) {\n    if (filter_out.find(hdr.first) != filter_out.end()) {\n      continue;\n    }\n\n    oss << hdr.first << \": \" << hdr.second << \"\\r\\n\";\n  }\n\n  // Trim the last two characters, if any\n  std::string out = oss.str();\n\n  if (!out.empty()) {\n    out.erase(out.length() - 2);\n  }\n\n  return out;\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/http/HttpResponse.hh",
    "content": "// ----------------------------------------------------------------------\n// File: HttpResponse.hh\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   HttpResponse.hh\n *\n * @brief  Holds all information related to a pure HTTP server response,\n *         such as status code, response headers and response body.\n */\n\n#ifndef __EOSCOMMON_HTTP_RESPONSE__HH__\n#define __EOSCOMMON_HTTP_RESPONSE__HH__\n\n#include \"common/Namespace.hh\"\n#include <map>\n#include <set>\n#include <string>\n#include <string_view>\n\nEOSCOMMONNAMESPACE_BEGIN\n\nclass HttpRequest;\n\nclass HttpResponse\n{\n\npublic:\n  constexpr static std::string_view kContentLength {\"Content-Length\"};\n\n  /**\n   * Standard HTTP response codes which we use\n   */\n  enum ResponseCodes {\n    // Informational 1xx\n    CONTINUE                        = 100,\n\n    // Successful 2xx\n    OK                              = 200,\n    CREATED                         = 201,\n    NO_CONTENT                      = 204,\n    PARTIAL_CONTENT                 = 206,\n    MULTI_STATUS                    = 207,\n\n    // Redirection 3xx\n    NOT_MODIFIED                    = 304,\n    TEMPORARY_REDIRECT              = 307,\n\n    // Client Error 4xx\n    BAD_REQUEST                     = 400,\n    UNAUTHORIZED                    = 401,\n    FORBIDDEN                       = 403,\n    NOT_FOUND                       = 404,\n    METHOD_NOT_ALLOWED              = 405,\n    CONFLICT                        = 409,\n    LENGTH_REQUIRED                 = 411,\n    PRECONDITION_FAILED             = 412,\n    UNSUPPORTED_MEDIA_TYPE          = 415,\n    REQUESTED_RANGE_NOT_SATISFIABLE = 416,\n    UNPROCESSABLE_ENTITY            = 422,\n    FAILED_DEPENDENCY               = 424,\n\n    // Server Error 5xx\n    INTERNAL_SERVER_ERROR           = 500,\n    NOT_IMPLEMENTED                 = 501,\n    BAD_GATEWAY                     = 502,\n    SERVICE_UNAVAILABLE             = 503,\n    INSUFFICIENT_STORAGE            = 507,\n  };\n\npublic:\n  typedef std::map<std::string, std::string> HeaderMap;\n\nprotected:\n  HeaderMap    mResponseHeaders;       //!< the response headers to be filled\n  std::string  mResponseBody;          //!< the response body to be created\n  int          mResponseCode;          //!< the response code to be determined\n\npublic:\n  off_t        mResponseLength;        //!< length of the response\n  bool         mUseFileReaderCallback; //!< read the file using callbacks\n\npublic:\n\n  /**\n   * Constructor\n   */\n  HttpResponse() :\n    mResponseCode(OK), mResponseLength(0), mUseFileReaderCallback(false) {};\n\n  /**\n   * Destructor\n   */\n  virtual ~HttpResponse() {};\n\n  /**\n   * Build an appropriate response to the given request. This will be\n   * implemented by the various protocol response types (WebDAV, S3)\n   *\n   * @param request  the client request object\n   *\n   * @return the newly built response object\n   */\n  virtual HttpResponse*\n  BuildResponse(eos::common::HttpRequest* request) = 0;\n\n  /**\n   * @return the map of server response headers\n   */\n  inline HeaderMap&\n  GetHeaders()\n  {\n    return mResponseHeaders;\n  }\n\n  /**\n   * @return server response headers as string and filter out the given headers\n   */\n  std::string\n  GetHdrsWithFilter(const std::set<std::string_view>& filter_out) const;\n\n  /**\n   * Set all server response headers at once\n   *\n   * @param headers  the server response headers to set\n   */\n  inline void\n  SetHeaders(HeaderMap headers)\n  {\n    mResponseHeaders = headers;\n  }\n\n  /**\n   * Add a header into the server response header map.\n   *\n   * @param key    the header key, e.g. Content-Type\n   * @param value  the header value, e.g. \"text/plain\"\n   */\n  void\n  AddHeader(const std::string key, const std::string value);\n\n  /**\n   * @return the server response body\n   */\n  inline const std::string&\n  GetBody()\n  {\n    return mResponseBody;\n  }\n\n  /**\n   * Set the server response body.\n   *\n   * @param body  the server response body to be set\n   */\n  inline void\n  SetBody(std::string body)\n  {\n    mResponseBody = body;\n  };\n\n  /**\n   * @return the size of the current response body\n   */\n  inline size_t\n  GetBodySize()\n  {\n    return mResponseBody.length();\n  }\n\n  /**\n   * @return the server response code\n   */\n  inline int\n  GetResponseCode()\n  {\n    return mResponseCode;\n  }\n\n  /**\n   * Set the server response code\n   *\n   * @param responseCode  the new response code to be set\n   */\n  inline void\n  SetResponseCode(int responseCode)\n  {\n    mResponseCode = responseCode;\n  };\n\n  /**\n   * Deduce an appropriate MIME type for the given path, based on the file\n   * extension. Note: the default MIME type is \"text/plain\".\n   *\n   * @param path  the filename to get a content type for\n   *\n   * @return the MIME type string, e.g. \"application/xml\"\n   */\n  static std::string\n  ContentType(const std::string& path);\n\n  /**\n   * @return nicely formatted response, ready for printing\n   */\n  std::string\n  ToString();\n\n  /**\n   * @return response code string\n   */\n  std::string\n  GetResponseCodeDescription();\n};\n\n/*----------------------------------------------------------------------------*/\nEOSCOMMONNAMESPACE_END\n\n#endif /* __EOSCOMMON_HTTP_RESPONSE__HH__ */\n"
  },
  {
    "path": "common/http/HttpServer.cc",
    "content": "// ----------------------------------------------------------------------\n// File: HttpServer.cc\n// Author: ABndreas-Joachim Peters & Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/http/HttpServer.hh\"\n#include \"common/StringConversion.hh\"\n#include <XrdSys/XrdSysPthread.hh>\n#include <XrdSys/XrdSysLogger.hh>\n#include <XrdSys/XrdSysError.hh>\n#include <XrdNet/XrdNet.hh>\n#include <string>\n#include <map>\n#include <sstream>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n#if MHD_VERSION < 0x00093300\n#define MHD_USE_EPOLL_LINUX_ONLY 512\n#endif\n\nHttpServer* HttpServer::gHttp {nullptr}; //!< Global HTTP server\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nHttpServer::HttpServer(int port):\n  mPort(port), mRunning(false)\n{\n  gHttp = this;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nHttpServer::Start()\n{\n  if (!mRunning) {\n    mThreadId.reset(&HttpServer::Run, this);\n    mRunning = true;\n    return true;\n  } else {\n    return false;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nHttpServer::Run(ThreadAssistant& assistant) noexcept\n{\n#ifdef EOS_MICRO_HTTPD\n  std::string thread_model = \"threads\";\n  {\n    // Delay to make sure xrootd is configured before serving\n    std::this_thread::sleep_for(std::chrono::seconds(1));\n    int nthreads = 16;\n\n    if (getenv(\"EOS_HTTP_THREADPOOL\")) {\n      thread_model = getenv(\"EOS_HTTP_THREADPOOL\");\n    }\n\n    if (getenv(\"EOS_HTTP_THREADPOOL_SIZE\")) {\n      nthreads = atoi(getenv(\"EOS_HTTP_THREADPOOL_SIZE\"));\n\n      if (nthreads < 1) {\n        nthreads = 16;\n      }\n\n      if (nthreads > 4096) {\n        nthreads = 4096;\n      }\n    }\n\n    if (thread_model == \"threads\") {\n      eos_static_notice(\"msg=\\\"starting http server\\\" mode=\\\"thread-per-connection\\\"\");\n      mDaemon = MHD_start_daemon(MHD_USE_DEBUG |  MHD_USE_THREAD_PER_CONNECTION |\n                                 MHD_USE_DUAL_STACK |\n                                 MHD_USE_POLL,\n                                 mPort,\n                                 NULL,\n                                 NULL,\n                                 &HttpServer::StaticHandler,\n                                 (void*) 0,\n                                 MHD_OPTION_NOTIFY_COMPLETED, &HttpServer::StaticCompleteHandler, NULL,\n                                 MHD_OPTION_CONNECTION_MEMORY_LIMIT,\n                                 getenv(\"EOS_HTTP_CONNECTION_MEMORY_LIMIT\") ? atoi(\n                                   getenv(\"EOS_HTTP_CONNECTION_MEMORY_LIMIT\")) : (128 * 1024 * 1024),\n                                 MHD_OPTION_CONNECTION_TIMEOUT,\n                                 getenv(\"EOS_HTTP_CONNECTION_TIMEOUT\") ? atoi(\n                                   getenv(\"EOS_HTTP_CONNECTION_TIMEOUT\")) : 128,\n                                 MHD_OPTION_END\n                                );\n    } else if (thread_model == \"epoll\") {\n      eos_static_notice(\"msg=\\\"starting http server\\\" mode=\\\"epoll\\\" threads=%d\",\n                        nthreads);\n      mDaemon = MHD_start_daemon(MHD_USE_DEBUG |  MHD_USE_SELECT_INTERNALLY |\n                                 MHD_USE_DUAL_STACK |\n                                 MHD_USE_EPOLL_LINUX_ONLY,\n                                 mPort,\n                                 NULL,\n                                 NULL,\n                                 &HttpServer::StaticHandler,\n                                 (void*) 0,\n                                 MHD_OPTION_THREAD_POOL_SIZE,\n                                 nthreads,\n                                 MHD_OPTION_NOTIFY_COMPLETED, &HttpServer::StaticCompleteHandler, NULL,\n                                 MHD_OPTION_CONNECTION_MEMORY_LIMIT,\n                                 getenv(\"EOS_HTTP_CONNECTION_MEMORY_LIMIT\") ? atoi(\n                                   getenv(\"EOS_HTTP_CONNECTION_MEMORY_LIMIT\")) : (128 * 1024 * 1024),\n                                 MHD_OPTION_CONNECTION_TIMEOUT,\n                                 getenv(\"EOS_HTTP_CONNECTION_TIMEOUT\") ? atoi(\n                                   getenv(\"EOS_HTTP_CONNECTION_TIMEOUT\")) : 128,\n                                 MHD_OPTION_END\n                                );\n    } else {\n      eos_static_notice(\"msg=\\\"starting http server\\\" mode=\\\"single-threaded\\\"\");\n      mDaemon = MHD_start_daemon(MHD_USE_DEBUG | MHD_USE_DUAL_STACK,\n                                 mPort,\n                                 NULL,\n                                 NULL,\n                                 &HttpServer::StaticHandler,\n                                 (void*) 0,\n                                 MHD_OPTION_NOTIFY_COMPLETED, &HttpServer::StaticCompleteHandler, NULL,\n                                 MHD_OPTION_CONNECTION_MEMORY_LIMIT,\n                                 128 * 1024 * 1024 /* 128MB */,\n                                 MHD_OPTION_END\n                                );\n    }\n  }\n\n  if (!mDaemon) {\n    mRunning = false;\n    eos_static_warning(\"msg=\\\"start of micro httpd failed [port=%d]\\\"\", mPort);\n    return;\n  } else {\n    mRunning = true;\n  }\n\n  eos_static_info(\"msg=\\\"start of micro httpd succeeded [port=%d]\\\"\", mPort);\n  fd_set rs;\n  fd_set ws;\n  fd_set es;\n  int max;\n  unsigned MHD_LONG_LONG mhd_timeout;\n  struct timeval tv;\n\n  if ((thread_model == \"epoll\") || (thread_model == \"threads\")) {\n    while (!assistant.terminationRequested()) {\n      assistant.wait_for(std::chrono::seconds(30));\n    }\n  } else {\n    while (!assistant.terminationRequested()) {\n      tv.tv_sec = 3600;\n      tv.tv_usec = 0;\n      max = 0;\n      FD_ZERO(&rs);\n      FD_ZERO(&ws);\n      FD_ZERO(&es);\n\n      if (MHD_YES != MHD_get_fdset(mDaemon, &rs, &ws, &es, &max)) {\n        break;  /* fatal internal error */\n      }\n\n      if (MHD_get_timeout(mDaemon, &mhd_timeout) == MHD_YES) {\n        if ((tv.tv_sec * 1000) < (long long) mhd_timeout) {\n          tv.tv_sec = mhd_timeout / 1000;\n          tv.tv_usec = (mhd_timeout - (tv.tv_sec * 1000)) * 1000;\n        }\n      }\n\n      (void) select(max + 1, &rs, &ws, &es, &tv);\n      MHD_run(mDaemon);\n    }\n  }\n\n  MHD_stop_daemon(mDaemon);\n#endif\n}\n\n#ifdef EOS_MICRO_HTTPD\n\n\n/*----------------------------------------------------------------------------*/\nvoid\nHttpServer::CleanupConnections()\n{\n  // currently we cannot call the clean-up in libmicrohttpd directly,\n  // we just connect to our self and trigger the cleanup\n  XrdSysLogger logger;\n  XrdSysError error(&logger);\n  XrdNet cNet(&error);\n  // XrdNetPeer cPeer;\n  // cNet.Connect(cPeer, \"localhost\", mPort);\n}\n\n\n/*----------------------------------------------------------------------------*/\nMHD_RESULT\nHttpServer::StaticHandler(void* cls,\n                          struct MHD_Connection* connection,\n                          const char* url,\n                          const char* method,\n                          const char* version,\n                          const char* upload_data,\n                          size_t* upload_data_size,\n                          void** ptr)\n{\n  // The static handler function calls back the original http object\n  if (gHttp) {\n    return convertToMHD_RESULT(gHttp->Handler(cls,\n                               connection,\n                               url,\n                               method,\n                               version,\n                               upload_data,\n                               upload_data_size,\n                               ptr));\n  } else {\n    return MHD_NO;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nHttpServer::StaticCompleteHandler(void* cls,\n                                  struct MHD_Connection* connection,\n                                  void** con_cls,\n                                  enum MHD_RequestTerminationCode toe)\n{\n  // The static handler function calls back the original http object\n  if (gHttp) {\n    gHttp->CompleteHandler(cls, connection, con_cls, toe);\n  }\n\n  return;\n}\n\n/*----------------------------------------------------------------------------*/\nMHD_RESULT\nHttpServer::BuildHeaderMap(void* cls,\n                           enum MHD_ValueKind kind,\n                           const char* key,\n                           const char* value)\n{\n  // Call back function to return the header key-val map of an HTTP request\n  std::map<std::string, std::string>* hMap\n    = static_cast<std::map<std::string, std::string>*>(cls);\n\n  if (key && value && hMap)  {\n    std::string low_key = LC_STRING(key);\n    (*hMap)[low_key] = value;\n  }\n\n  return MHD_YES;\n}\n\n/*----------------------------------------------------------------------------*/\nMHD_RESULT\nHttpServer::BuildQueryString(void* cls,\n                             enum MHD_ValueKind kind,\n                             const char* key,\n                             const char* value)\n{\n  // Call back function to return the query string of an HTTP request\n  std::string* qString = static_cast<std::string*>(cls);\n\n  if (key && qString) {\n    if (value) {\n      if (qString->length()) {\n        *qString += \"&\";\n      }\n\n      *qString += key;\n      *qString += \"=\";\n      *qString += value;\n    } else {\n      if (qString->length()) {\n        *qString += \"&\";\n      }\n\n      *qString += key;\n    }\n  }\n\n  return MHD_YES;\n}\n#endif\n\n/*----------------------------------------------------------------------------*/\nHttpResponse*\nHttpServer::HttpError(const char* errorText, int errorCode)\n{\n  HttpResponse* response = new PlainHttpResponse();\n\n  if (errorCode == ENOENT) {\n    response->SetResponseCode(response->NOT_FOUND);\n  } else if (errorCode == EOPNOTSUPP) {\n    response->SetResponseCode(response->NOT_IMPLEMENTED);\n  } else if ((errorCode == EDQUOT) || (errorCode == ENOSPC)) {\n    response->SetResponseCode(response->INSUFFICIENT_STORAGE);\n  } else if (errorCode == ETXTBSY) {\n    response->SetResponseCode(response->SERVICE_UNAVAILABLE);\n  } else if (errorCode == EILSEQ) {\n    response->SetResponseCode(response->UNPROCESSABLE_ENTITY);\n  } else if (errorCode == EPERM || errorCode == EACCES) {\n    response->SetResponseCode(response->FORBIDDEN);\n  } else if (errorCode == EAGAIN) {\n    response->SetResponseCode(response->CONFLICT);\n  } else {\n    response->SetResponseCode(response->INTERNAL_SERVER_ERROR);\n  }\n\n  if (errorCode >= 400) {\n    response->SetResponseCode(errorCode);\n  }\n\n  std::string error_body = (errorText ? errorText : \"\");\n\n  if (!error_body.empty()) {\n    common::StringConversion::html_escape(error_body);\n    error_body += \"\\n\";\n  }\n\n  response->SetBody(error_body.c_str());\n  response->AddHeader(\"Content-Length\", std::to_string(error_body.length()));\n  response->AddHeader(\"Content-Type\", \"text/html\");\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\nHttpResponse*\nHttpServer::HttpData(const char* data, int length)\n{\n  HttpResponse* response = new PlainHttpResponse();\n  response->SetResponseCode(HttpResponse::ResponseCodes::OK);\n  response->SetBody(std::string(data, length));\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\nHttpResponse*\nHttpServer::HttpHead(off_t length, std::string name)\n{\n  HttpResponse* response = new PlainHttpResponse();\n  response->SetResponseCode(HttpResponse::ResponseCodes::OK);\n  response->SetBody(std::string(\"\"));\n  response->AddHeader(\"Content-Length\", std::to_string((long long) length));\n  response->AddHeader(\"Content-Type\", \"application/octet-stream\");\n  response->AddHeader(\"Accept-Ranges\", \"bytes\");\n  response->AddHeader(\"Content-Disposition\", std::string(\"filename=\\\"\") + name\n                      + std::string(\"\\\"\"));\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\nHttpResponse*\nHttpServer::HttpStall(const char* stallText, int seconds)\n{\n  return HttpError(\"Unable to stall\",\n                   HttpResponse::ResponseCodes::SERVICE_UNAVAILABLE);\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nHttpServer::EncodeURI(std::string& cgi)\n{\n  // replace '+' '/' '=' '&' '#' '\"'\n  XrdOucString scgi = cgi.c_str();\n\n  while (scgi.replace(\"+\", \"%2B\")) {\n  }\n\n  while (scgi.replace(\"/\", \"%2F\")) {\n  }\n\n  while (scgi.replace(\"=\", \"%3D\")) {\n  }\n\n  while (scgi.replace(\"&\", \"%26\")) {\n  }\n\n  while (scgi.replace(\"#\", \"%23\")) {\n  }\n\n  while (scgi.replace(\"\\\"\", \"%22\")) {\n  }\n\n  cgi = \"encURI=\";\n  cgi += scgi.c_str();\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nHttpServer::DecodeURI(std::string& cgi)\n{\n  // replace \"%2B\" \"%2F\" \"%3D\" \"%26\" \"%23\" \"%22\"\n  XrdOucString scgi = cgi.c_str();\n\n  while (scgi.replace(\"%2B\", \"+\")) {\n  }\n\n  while (scgi.replace(\"%2F\", \"/\")) {\n  }\n\n  while (scgi.replace(\"%3D\", \"=\")) {\n  }\n\n  while (scgi.replace(\"%26\", \"&\")) {\n  }\n\n  while (scgi.replace(\"%23\", \"#\")) {\n  }\n\n  while (scgi.replace(\"%22\", \"\\\"\")) {\n  }\n\n  if (scgi.beginswith(\"encURI=\")) {\n    scgi.erase(0, 7);\n  }\n\n  cgi = scgi.c_str();\n}\n\n/*----------------------------------------------------------------------------*/\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/http/HttpServer.hh",
    "content": "// ----------------------------------------------------------------------\n// File: HttpServer.hh\n// Author: Andreas-Joachim Peters & Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   HttpServer.hh\n *\n * @brief  Class running an HTTP daemon. Creates an embedded HTTP server\n *         instance\n */\n\n#pragma once\n#include \"common/http/HttpRequest.hh\"\n#include \"common/http/HttpResponse.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Namespace.hh\"\n#include \"common/http/PlainHttpResponse.hh\"\n\n#ifdef EOS_MICRO_HTTPD\n#include <microhttpd.h>\n#endif\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class HttpServer\n//------------------------------------------------------------------------------\nclass HttpServer\n{\npublic:\n  //! Instance of the HTTP server allowing the Handler function to call\n  //!<class member functions\n  static HttpServer* gHttp;\n\n  /**\n   * Constructor\n   */\n  HttpServer(int port = 8000);\n\n  /**\n   * Destructor\n   */\n  virtual ~HttpServer()\n  {\n    eos_static_info(\"%s\", \"msg=\\\"Common HttpServer destructor\\\"\");\n    mThreadId.join();\n  }\n\n  /**\n   * Return port number of service\n   */\n  int Port()\n  {\n    return mPort;\n  }\n\n  /**\n   * Start the listening HTTP server\n   *\n   * @return true if server running otherwise false\n   */\n  virtual bool Start();\n\n  /**\n   * Create the embedded server and run\n   */\n  void Run(ThreadAssistant& assistant) noexcept;\n\n  /**\n   * Get an HTTP redirect response object.\n   *\n   * @param url      the url to redirect to\n   * @param hostCGI  the string returned by the open() call that told us to\n   *                 redirect. Contains hostname?redirect_query\n   * @param port     the port number to redirect to\n   * @param cookie   true if we should use cookies, false for CGI\n   *\n   * @return an HTTP response object\n   */\n  inline static HttpResponse*\n  HttpRedirect(const std::string& url,\n               const std::string& hostCGI,\n               int                port,\n               bool               cookie)\n  {\n    eos_static_info(\"info=redirecting\");\n    HttpResponse* response = new PlainHttpResponse();\n    response->SetResponseCode(HttpResponse::ResponseCodes::TEMPORARY_REDIRECT);\n    std::string host = hostCGI;\n    std::string cgi = \"\";\n    size_t qpos;\n\n    if ((qpos = host.find(\"?\")) != std::string::npos) {\n      cgi = host;\n      cgi.erase(0, qpos + 1);\n      host.erase(qpos);\n    }\n\n    eos_static_debug(\"host=%s\", host.c_str());\n    eos_static_debug(\"cgi=%s\", cgi.c_str());\n    std::string redirect;\n    redirect = \"http://\";\n    redirect += host;\n    char sport[16];\n    snprintf(sport, sizeof(sport) - 1, \":%d\", port);\n    redirect += sport;\n    redirect += url;\n    EncodeURI(cgi); // encode '+' '/' '='\n\n    if (cookie) {\n      response->AddHeader(\"Set-Cookie\", \"EOSCAPABILITY=\"\n                          + cgi\n                          + \";Max-Age=60;\"\n                          + \"Path=\"\n                          + url\n                          + \";Version=1\"\n                          + \";Domain=\"\n                          + \"cern.ch\");\n    } else {\n      redirect += \"?\";\n      redirect += cgi;\n    }\n\n    response->AddHeader(\"Location\", redirect);\n    redirect = \"/internal_redirect/\" + redirect.substr(7);\n    response->AddHeader(\"X-Accel-Redirect\", redirect);\n    response->AddHeader(\"X-Sendfile\", redirect);\n    return response;\n  }\n\n  /**\n   * Get an HTTP error response object containing an HTML error page inside\n   * the body.\n   *\n   * @param errorText  the error message to be displayed on the error page\n   * @param errorCode  the derived HTTP error code\n   *\n   * @return an HTTP response object\n   */\n  static HttpResponse*\n  HttpError(const char* errorText, int errorCode);\n\n  /**\n   * Get an HTTP HEAD response object with an empty\n   * body.\n   *\n   * @param length  the size of the data page\n   * @param name basename to add to header\n   *\n   * @return an HTTP response object\n   */\n  static HttpResponse*\n  HttpHead(off_t length, std::string name);\n\n\n  /**\n   * Get an HTTP data response object containing an HTML data page inside the\n   * body.\n   *\n   * @param data    the HTML data page string\n   * @param length  the size of the data page\n   *\n   * @return an HTTP response object\n   */\n  static HttpResponse*\n  HttpData(const char* data, int length);\n\n  /**\n   * Get an HTTP stall response object.\n   *\n   * @param stallText  the stall text to display\n   * @param seconds    number of seconds to stall for\n   *\n   * @return an HTTP response object\n   */\n  static HttpResponse*\n  HttpStall(const char* stallText, int seconds);\n\n  /**\n   * Encode the provided CGI string, escaping '/' '+' '=' characters\n   *\n   * @param cgi  the CGI string to encode\n   */\n  static void\n  EncodeURI(std::string& cgi);\n\n  /**\n   * Deocde the provided CGI string, unesacping '/' '+' '=' characters\n   *\n   * @param cgi  the CGI string to decode\n   */\n  static void\n  DecodeURI(std::string& cgi);\n\n#ifdef EOS_MICRO_HTTPD\n\n#if MHD_VERSION >= 0x00097002\n#define MHD_RESULT enum MHD_Result\n#else\n#define MHD_RESULT int\n#endif\n\n  /**\n   * Compatibility hacks for MHD versions\n   */\n  static MHD_RESULT convertToMHD_RESULT(int code)\n  {\n#if MHD_VERSION >= 0x00097002\n\n    if (code == 1) {\n      return MHD_YES;\n    }\n\n    return MHD_NO;\n#else\n    return code;\n#endif\n  }\n\n  /**\n   * Calls the instance handler function of the Http object\n   *\n   * @return see implementation\n   */\n  static MHD_RESULT\n  StaticHandler(void*                  cls,\n                struct MHD_Connection* connection,\n                const char*            url,\n                const char*            method,\n                const char*            version,\n                const char*            upload_data,\n                size_t*                upload_data_size,\n                void**                 ptr);\n\n  /**\n   * Calls the instance handler function of the Http object\n   *\n   * @return nothing\n   */\n  static void\n  StaticCompleteHandler(void*                  cls,\n                        struct MHD_Connection* connection,\n                        void** con_cls,\n                        enum MHD_RequestTerminationCode toe);\n\n  /**\n   * HTTP object handler function\n   *\n   * @return see implementation\n   */\n  virtual int\n  Handler(void*                  cls,\n          struct MHD_Connection* connection,\n          const char*            url,\n          const char*            method,\n          const char*            version,\n          const char*            upload_data,\n          size_t*                upload_data_size,\n          void**                 ptr) = 0;\n\n  /**\n   * HTTP complete handler function\n   *\n   * @return nothing\n   */\n  virtual void\n  CompleteHandler(void*                  cls,\n                  struct MHD_Connection* connection,\n                  void**                 con_cls,\n                  enum MHD_RequestTerminationCode toe) = 0;\n\n  /**\n   * Returns the query string for an HTTP request\n   *\n   * @param cls    in-out address of a std::string containing the query string\n   * @param kind   the request type\n   * @param key    the query parameter key\n   * @param value  the query parameter value\n   *\n   * @return MHD_YES\n   */\n  static MHD_RESULT\n  BuildQueryString(void*              cls,\n                   enum MHD_ValueKind kind,\n                   const char*        key,\n                   const char*        value);\n\n  /**\n   * Returns the header map for an HTTP request\n   *\n   * @param cls    in-out address of a std::map<std::string,std::string>\n   *               containing the query string\n   * @param kind   the request type\n   * @param key    the query parameter key\n   * @param value  the query parameter value\n   *\n   * @return MHD_YES\n   */\n  static MHD_RESULT\n  BuildHeaderMap(void*              cls,\n                 enum MHD_ValueKind kind,\n                 const char*        key,\n                 const char*        value);\n\n  /**\n   * Cleans closed connections earlier than MHD_run\n   */\n\n  void\n  CleanupConnections();\n\n#endif\n\nprotected:\n#ifdef EOS_MICRO_HTTPD\n  struct MHD_Daemon* mDaemon {\n    nullptr\n  };   //!< MicroHttpd daemon instance\n#endif\n  int                mPort;     //!< The port this server listens on\n  std::atomic<bool>  mRunning;  //!< Is this server running?\n  AssistedThread     mThreadId; //!< This thread's ID\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/http/MimeTypes.hh",
    "content": "// ----------------------------------------------------------------------\n// File: MimeTypes.hh\n// Author: Andreas-Joachim Peters CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   MimeTypes.hh\n *\n * @brief  class deriving mime types from suffixes\n */\n\n#ifndef __EOSCOMMON_MIMETYPES__HH__\n#define __EOSCOMMON_MIMETYPES__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/Namespace.hh\"\n#include \"common/StringConversion.hh\"\n/*----------------------------------------------------------------------------*/\n\n/*----------------------------------------------------------------------------*/\n#include <map>\n#include <string>\n\n/*----------------------------------------------------------------------------*/\n\nEOSCOMMONNAMESPACE_BEGIN\n\nclass MimeTypes {\nprivate:\n  std::map<std::string, std::string> mTypes;\n\npublic:\n\n  std::string\n  MimeType (const char* suffix)\n  {\n    if (mTypes.count(suffix))\n      return mTypes[suffix];\n    else\n      return \"application/octet-stream\";\n  }\n\n  std::string\n  Match (const std::string path)\n  {\n    std::string suffix = path;\n    if (path.rfind(\".\") != std::string::npos)\n      suffix.erase(0,path.rfind(\".\"));\n    std::string lower_case_suffix = LC_STRING(suffix);\n    return MimeType(lower_case_suffix.c_str());\n  }\n\n  MimeTypes ()\n  {\n    mTypes[\".3dm\"] = \"x-world/x-3dmf\";\n    mTypes[\".3dmf\"] = \"x-world/x-3dmf\";\n    mTypes[\".a\"] = \"application/octet-stream\";\n    mTypes[\".aab\"] = \"application/x-authorware-bin\";\n    mTypes[\".aam\"] = \"application/x-authorware-map\";\n    mTypes[\".aas\"] = \"application/x-authorware-seg\";\n    mTypes[\".abc\"] = \"text/vnd.abc\";\n    mTypes[\".acgi\"] = \"text/html\";\n    mTypes[\".afl\"] = \"video/animaflex\";\n    mTypes[\".ai\"] = \"application/postscript\";\n    mTypes[\".aif\"] = \"audio/aiff\";\n    mTypes[\".aif\"] = \"audio/x-aiff\";\n    mTypes[\".aifc\"] = \"audio/aiff\";\n    mTypes[\".aiff\"] = \"audio/aiff\";\n    mTypes[\".aim\"] = \"application/x-aim\";\n    mTypes[\".aip\"] = \"text/x-audiosoft-intra\";\n    mTypes[\".ani\"] = \"application/x-navi-animation\";\n    mTypes[\".aos\"] = \"application/x-nokia-9000-communicator-add-on-software\";\n    mTypes[\".aps\"] = \"application/mime\";\n    mTypes[\".arc\"] = \"application/octet-stream\";\n    mTypes[\".arj\"] = \"application/arj\";\n    mTypes[\".art\"] = \"image/x-jg\";\n    mTypes[\".asf\"] = \"video/x-ms-asf\";\n    mTypes[\".asm\"] = \"text/x-asm\";\n    mTypes[\".asp\"] = \"text/asp\";\n    mTypes[\".asx\"] = \"application/x-mplayer2\";\n    mTypes[\".au\"] = \"audio/basic\";\n    mTypes[\".avi\"] = \"video/avi\";\n    mTypes[\".bcpio\"] = \"application/x-bcpio\";\n    mTypes[\".bin\"] = \"application/octet-stream\";\n    mTypes[\".bm\"] = \"image/bmp\";\n    mTypes[\".bmp\"] = \"image/bmp\";\n    mTypes[\".boo\"] = \"application/book\";\n    mTypes[\".book\"] = \"application/book\";\n    mTypes[\".boz\"] = \"application/x-bzip2\";\n    mTypes[\".bsh\"] = \"application/x-bsh\";\n    mTypes[\".bz\"] = \"application/x-bzip\";\n    mTypes[\".bz2\"] = \"application/x-bzip2\";\n    mTypes[\".c\"] = \"text/plain\";\n    mTypes[\".c++\"] = \"text/plain\";\n    mTypes[\".cat\"] = \"application/vnd.ms-pki.seccat\";\n    mTypes[\".cc\"] = \"text/plain\";\n    mTypes[\".ccad\"] = \"application/clariscad\";\n    mTypes[\".cco\"] = \"application/x-cocoa\";\n    mTypes[\".cdf\"] = \"application/cdf\";\n    mTypes[\".cer\"] = \"application/pkix-cert\";\n    mTypes[\".cha\"] = \"application/x-chat\";\n    mTypes[\".chat\"] = \"application/x-chat\";\n    mTypes[\".class\"] = \"application/java\";\n    mTypes[\".com\"] = \"application/octet-stream\";\n    mTypes[\".conf\"] = \"text/plain\";\n    mTypes[\".cpio\"] = \"application/x-cpio\";\n    mTypes[\".cpp\"] = \"text/x-c\";\n    mTypes[\".cpt\"] = \"application/x-cpt\";\n    mTypes[\".crl\"] = \"application/pkcs-crl\";\n    mTypes[\".crt\"] = \"application/pkix-cert\";\n    mTypes[\".csh\"] = \"application/x-csh\";\n    mTypes[\".css\"] = \"text/css\";\n    mTypes[\".cxx\"] = \"text/plain\";\n    mTypes[\".dcr\"] = \"application/x-director\";\n    mTypes[\".deepv\"] = \"application/x-deepv\";\n    mTypes[\".def\"] = \"text/plain\";\n    mTypes[\".dif\"] = \"video/x-dv\";\n    mTypes[\".dir\"] = \"application/x-director\";\n    mTypes[\".dl\"] = \"video/dl\";\n    mTypes[\".doc\"] = \"application/msword\";\n    mTypes[\".docm\"] = \"application/vnd.ms-word.document.macroEnabled.12\";\n    mTypes[\".docx\"] = \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\";\n    mTypes[\".dotm\"] = \"application/vnd.ms-word.templatet.macroEnabled.12\";\n    mTypes[\".dotx\"] = \"application/vnd.openxmlformats-officedocument.wordprocessingml.template\";\n    mTypes[\".dot\"] = \"application/msword\";\n    mTypes[\".dp\"] = \"application/commonground\";\n    mTypes[\".drw\"] = \"application/drafting\";\n    mTypes[\".dump\"] = \"application/octet-stream\";\n    mTypes[\".dv\"] = \"video/x-dv\";\n    mTypes[\".dvi\"] = \"application/x-dvi\";\n    mTypes[\".dwf\"] = \"drawing/x-dwf\";\n    mTypes[\".dwf\"] = \"model/vnd.dwf\";\n    mTypes[\".dwg\"] = \"application/acad\";\n    mTypes[\".dxf\"] = \"application/dxf\";\n    mTypes[\".dxr\"] = \"application/x-director\";\n    mTypes[\".el\"] = \"text/x-script.elisp\";\n    mTypes[\".elc\"] = \"application/x-bytecode.elisp\";\n    mTypes[\".env\"] = \"application/x-envoy\";\n    mTypes[\".eps\"] = \"application/postscript\";\n    mTypes[\".es\"] = \"application/x-esrehber\";\n    mTypes[\".etx\"] = \"text/x-setext\";\n    mTypes[\".evy\"] = \"application/envoy\";\n    mTypes[\".evy\"] = \"application/x-envoy\";\n    mTypes[\".exe\"] = \"application/octet-stream\";\n    mTypes[\".f\"] = \"text/plain\";\n    mTypes[\".f77\"] = \"text/x-fortran\";\n    mTypes[\".f90\"] = \"text/plain\";\n    mTypes[\".fdf\"] = \"application/vnd.fdf\";\n    mTypes[\".fif\"] = \"image/fif\";\n    mTypes[\".fli\"] = \"video/fli\";\n    mTypes[\".fli\"] = \"video/x-fli\";\n    mTypes[\".flo\"] = \"image/florian\";\n    mTypes[\".flx\"] = \"text/vnd.fmi.flexstor\";\n    mTypes[\".fmf\"] = \"video/x-atomic3d-feature\";\n    mTypes[\".for\"] = \"text/plain\";\n    mTypes[\".fpx\"] = \"image/vnd.fpx\";\n    mTypes[\".frl\"] = \"application/freeloader\";\n    mTypes[\".funk\"] = \"audio/make\";\n    mTypes[\".g\"] = \"text/plain\";\n    mTypes[\".g3\"] = \"image/g3fax\";\n    mTypes[\".gif\"] = \"image/gif\";\n    mTypes[\".gl\"] = \"video/gl\";\n    mTypes[\".gsd\"] = \"audio/x-gsm\";\n    mTypes[\".gsm\"] = \"audio/x-gsm\";\n    mTypes[\".gsp\"] = \"application/x-gsp\";\n    mTypes[\".gss\"] = \"application/x-gss\";\n    mTypes[\".gtar\"] = \"application/x-gtar\";\n    mTypes[\".gz\"] = \"application/x-gzip\";\n    mTypes[\".gzip\"] = \"application/x-gzip\";\n    mTypes[\".h\"] = \"text/plain\";\n    mTypes[\".hdf\"] = \"application/x-hdf\";\n    mTypes[\".help\"] = \"application/x-helpfile\";\n    mTypes[\".hgl\"] = \"application/vnd.hp-hpgl\";\n    mTypes[\".hh\"] = \"text/plain\";\n    mTypes[\".hlb\"] = \"text/x-script\";\n    mTypes[\".hlp\"] = \"application/hlp\";\n    mTypes[\".hpg\"] = \"application/vnd.hp-hpgl\";\n    mTypes[\".hpgl\"] = \"application/vnd.hp-hpgl\";\n    mTypes[\".hqx\"] = \"application/binhex\";\n    mTypes[\".hta\"] = \"application/hta\";\n    mTypes[\".htc\"] = \"text/x-component\";\n    mTypes[\".htm\"] = \"text/html\";\n    mTypes[\".html\"] = \"text/html\";\n    mTypes[\".htmls\"] = \"text/html\";\n    mTypes[\".htt\"] = \"text/webviewhtml\";\n    mTypes[\".htx\"] = \"text/html\";\n    mTypes[\".ice\"] = \"x-conference/x-cooltalk\";\n    mTypes[\".ico\"] = \"image/x-icon\";\n    mTypes[\".idc\"] = \"text/plain\";\n    mTypes[\".ief\"] = \"image/ief\";\n    mTypes[\".iefs\"] = \"image/ief\";\n    mTypes[\".iges\"] = \"application/iges\";\n    mTypes[\".igs\"] = \"application/iges\";\n    mTypes[\".ima\"] = \"application/x-ima\";\n    mTypes[\".imap\"] = \"application/x-httpd-imap\";\n    mTypes[\".inf\"] = \"application/inf\";\n    mTypes[\".ins\"] = \"application/x-internett-signup\";\n    mTypes[\".ip\"] = \"application/x-ip2\";\n    mTypes[\".isu\"] = \"video/x-isvideo\";\n    mTypes[\".it\"] = \"audio/it\";\n    mTypes[\".iv\"] = \"application/x-inventor\";\n    mTypes[\".ivr\"] = \"i-world/i-vrml\";\n    mTypes[\".ivy\"] = \"application/x-livescreen\";\n    mTypes[\".jam\"] = \"audio/x-jam\";\n    mTypes[\".jar\"] = \"application/java-archive\";\n    mTypes[\".jav\"] = \"text/plain\";\n    mTypes[\".jav\"] = \"text/x-java-source\";\n    mTypes[\".java\"] = \"text/plain\";\n    mTypes[\".jcm\"] = \"application/x-java-commerce\";\n    mTypes[\".jfif\"] = \"image/jpeg\";\n    mTypes[\".jfif-tbnl\"] = \"image/jpeg\";\n    mTypes[\".jpe\"] = \"image/jpeg\";\n    mTypes[\".jpeg\"] = \"image/jpeg\";\n    mTypes[\".jpg\"] = \"image/jpeg\";\n    mTypes[\".js\"] = \"application/x-javascript\";\n    mTypes[\".js\"] = \"text/javascript\";\n    mTypes[\".jut\"] = \"image/jutvision\";\n    mTypes[\".kar\"] = \"audio/midi\";\n    mTypes[\".ksh\"] = \"application/x-ksh\";\n    mTypes[\".la\"] = \"audio/nspaudio\";\n    mTypes[\".lam\"] = \"audio/x-liveaudio\";\n    mTypes[\".latex\"] = \"application/x-latex\";\n    mTypes[\".lha\"] = \"application/lha\";\n    mTypes[\".lhx\"] = \"application/octet-stream\";\n    mTypes[\".list\"] = \"text/plain\";\n    mTypes[\".lma\"] = \"audio/nspaudio\";\n    mTypes[\".log\"] = \"text/plain\";\n    mTypes[\".lsp\"] = \"application/x-lisp\";\n    mTypes[\".lst\"] = \"text/plain\";\n    mTypes[\".lsx\"] = \"text/x-la-asf\";\n    mTypes[\".ltx\"] = \"application/x-latex\";\n    mTypes[\".lzh\"] = \"application/octet-stream\";\n    mTypes[\".lzx\"] = \"application/lzx\";\n    mTypes[\".m\"] = \"text/plain\";\n    mTypes[\".m1v\"] = \"video/mpeg\";\n    mTypes[\".m2a\"] = \"audio/mpeg\";\n    mTypes[\".m2v\"] = \"video/mpeg\";\n    mTypes[\".m3u\"] = \"audio/x-mpequrl\";\n    mTypes[\".man\"] = \"application/x-troff-man\";\n    mTypes[\".map\"] = \"application/x-navimap\";\n    mTypes[\".mar\"] = \"text/plain\";\n    mTypes[\".mbd\"] = \"application/mbedlet\";\n    mTypes[\".mc$\"] = \"application/x-magic-cap-package-1.0\";\n    mTypes[\".mcd\"] = \"application/mcad\";\n    mTypes[\".mcf\"] = \"text/mcf\";\n    mTypes[\".mcp\"] = \"application/netmc\";\n    mTypes[\".me\"] = \"application/x-troff-me\";\n    mTypes[\".mht\"] = \"message/rfc822\";\n    mTypes[\".mhtml\"] = \"message/rfc822\";\n    mTypes[\".mid\"] = \"audio/midi\";\n    mTypes[\".midi\"] = \"audio/midi\";\n    mTypes[\".mif\"] = \"application/x-mif\";\n    mTypes[\".mime\"] = \"www/mime\";\n    mTypes[\".mjf\"] = \"audio/x-vnd.audioexplosion.mjuicemediafile\";\n    mTypes[\".mjpg\"] = \"video/x-motion-jpeg\";\n    mTypes[\".mm\"] = \"application/base64\";\n    mTypes[\".mme\"] = \"application/base64\";\n    mTypes[\".mod\"] = \"audio/mod\";\n    mTypes[\".moov\"] = \"video/quicktime\";\n    mTypes[\".mov\"] = \"video/quicktime\";\n    mTypes[\".movie\"] = \"video/x-sgi-movie\";\n    mTypes[\".mp2\"] = \"audio/mpeg\";\n    mTypes[\".mp3\"] = \"audio/mpeg\";\n    mTypes[\".mp4\"] = \"video/mp4\";\n    mTypes[\".m4a\"] = \"audio/m4a\";\n    mTypes[\".mpa\"] = \"audio/mpeg\";\n    mTypes[\".mpc\"] = \"application/x-project\";\n    mTypes[\".mpe\"] = \"video/mpeg\";\n    mTypes[\".mpeg\"] = \"video/mpeg\";\n    mTypes[\".mpg\"] = \"audio/mpeg\";\n    mTypes[\".mpga\"] = \"audio/mpeg\";\n    mTypes[\".mpp\"] = \"application/vnd.ms-project\";\n    mTypes[\".mpt\"] = \"application/x-project\";\n    mTypes[\".mpv\"] = \"application/x-project\";\n    mTypes[\".mpx\"] = \"application/x-project\";\n    mTypes[\".mrc\"] = \"application/marc\";\n    mTypes[\".ms\"] = \"application/x-troff-ms\";\n    mTypes[\".mv\"] = \"video/x-sgi-movie\";\n    mTypes[\".my\"] = \"audio/make\";\n    mTypes[\".mzz\"] = \"application/x-vnd.audioexplosion.mzz\";\n    mTypes[\".nap\"] = \"image/naplps\";\n    mTypes[\".naplps\"] = \"image/naplps\";\n    mTypes[\".nc\"] = \"application/x-netcdf\";\n    mTypes[\".ncm\"] = \"application/vnd.nokia.configuration-message\";\n    mTypes[\".nif\"] = \"image/x-niff\";\n    mTypes[\".niff\"] = \"image/x-niff\";\n    mTypes[\".nix\"] = \"application/x-mix-transfer\";\n    mTypes[\".nsc\"] = \"application/x-conference\";\n    mTypes[\".nvd\"] = \"application/x-navidoc\";\n    mTypes[\".o\"] = \"application/octet-stream\";\n    mTypes[\".oda\"] = \"application/oda\";\n    mTypes[\".ogg\"] = \"application/ogg\";\n    mTypes[\".omc\"] = \"application/x-omc\";\n    mTypes[\".omcd\"] = \"application/x-omcdatamaker\";\n    mTypes[\".omcr\"] = \"application/x-omcregerator\";\n    mTypes[\".p\"] = \"text/x-pascal\";\n    mTypes[\".p10\"] = \"application/pkcs10\";\n    mTypes[\".p12\"] = \"application/pkcs-12\";\n    mTypes[\".p7a\"] = \"application/x-pkcs7-signature\";\n    mTypes[\".p7c\"] = \"application/pkcs7-mime\";\n    mTypes[\".p7m\"] = \"application/pkcs7-mime\";\n    mTypes[\".p7r\"] = \"application/x-pkcs7-certreqresp\";\n    mTypes[\".p7s\"] = \"application/pkcs7-signature\";\n    mTypes[\".part\"] = \"application/pro_eng\";\n    mTypes[\".pas\"] = \"text/pascal\";\n    mTypes[\".pbm\"] = \"image/x-portable-bitmap\";\n    mTypes[\".pcl\"] = \"application/vnd.hp-pcl\";\n    mTypes[\".pct\"] = \"image/x-pict\";\n    mTypes[\".pcx\"] = \"image/x-pcx\";\n    mTypes[\".pdb\"] = \"chemical/x-pdb\";\n    mTypes[\".pdf\"] = \"application/pdf\";\n    mTypes[\".pfunk\"] = \"audio/make\";\n    mTypes[\".pgm\"] = \"image/x-portable-graymap\";\n    mTypes[\".pic\"] = \"image/pict\";\n    mTypes[\".pict\"] = \"image/pict\";\n    mTypes[\".pkg\"] = \"application/x-newton-compatible-pkg\";\n    mTypes[\".pko\"] = \"application/vnd.ms-pki.pko\";\n    mTypes[\".pl\"] = \"text/plain\";\n    mTypes[\".plx\"] = \"application/x-pixclscript\";\n    mTypes[\".pm\"] = \"image/x-xpixmap\";\n    mTypes[\".pm4\"] = \"application/x-pagemaker\";\n    mTypes[\".pm5\"] = \"application/x-pagemaker\";\n    mTypes[\".png\"] = \"image/png\";\n    mTypes[\".pnm\"] = \"application/x-portable-anymap\";\n    mTypes[\".pot\"] = \"application/vnd.ms-powerpoint\";\n    mTypes[\".potm\"] = \"application/vnd.ms-powerpoint.template.macroEnabled.12\";\n    mTypes[\".potx\"] = \"application/vnd.openxmlformats-officedocument.presentationml.template\";\n    mTypes[\".pov\"] = \"model/x-pov\";\n    mTypes[\".ppa\"] = \"application/vnd.ms-powerpoint\";\n    mTypes[\".ppam\"] = \"application/vnd.ms-powerpoint.addin.macroEnabled.12\";\n    mTypes[\".ppm\"] = \"image/x-portable-pixmap\";\n    mTypes[\".pps\"] = \"application/vnd.ms-powerpoint\";\n    mTypes[\".ppsm\"] = \"application/vnd.ms-powerpoint.slideshow.macroEnabled.12\";\n    mTypes[\".ppsx\"] = \"application/vnd.openxmlformats-officedocument.presentationml.slideshow\";\n    mTypes[\".ppt\"] = \"application/vnd.ms-powerpoint\";\n    mTypes[\".pptm\"] = \"application/vnd.ms-powerpoint.presentation.macroEnabled.12\";\n    mTypes[\".pptx\"] = \"application/vnd.openxmlformats-officedocument.presentationml.presentation\";\n    mTypes[\".ppz\"] = \"application/mspowerpoint\";\n    mTypes[\".pre\"] = \"application/x-freelance\";\n    mTypes[\".prt\"] = \"application/pro_eng\";\n    mTypes[\".ps\"] = \"application/postscript\";\n    mTypes[\".psd\"] = \"application/octet-stream\";\n    mTypes[\".pvu\"] = \"paleovu/x-pv\";\n    mTypes[\".pwz\"] = \"application/vnd.ms-powerpoint\";\n    mTypes[\".py\"] = \"text/x-script.phyton\";\n    mTypes[\".pyc\"] = \"application/x-bytecode.python\";\n    mTypes[\".qcp\"] = \"audio/vnd.qcelp\";\n    mTypes[\".qd3\"] = \"x-world/x-3dmf\";\n    mTypes[\".qd3d\"] = \"x-world/x-3dmf\";\n    mTypes[\".qif\"] = \"image/x-quicktime\";\n    mTypes[\".qt\"] = \"video/quicktime\";\n    mTypes[\".qtc\"] = \"video/x-qtc\";\n    mTypes[\".qti\"] = \"image/x-quicktime\";\n    mTypes[\".qtif\"] = \"image/x-quicktime\";\n    mTypes[\".ra\"] = \"audio/x-realaudio\";\n    mTypes[\".ram\"] = \"audio/x-pn-realaudio\";\n    mTypes[\".ras\"] = \"application/x-cmu-raster\";\n    mTypes[\".ras\"] = \"image/cmu-raster\";\n    mTypes[\".rast\"] = \"image/cmu-raster\";\n    mTypes[\".rexx\"] = \"text/x-script.rexx\";\n    mTypes[\".rf\"] = \"image/vnd.rn-realflash\";\n    mTypes[\".rgb\"] = \"image/x-rgb\";\n    mTypes[\".rm\"] = \"application/vnd.rn-realmedia\";\n    mTypes[\".rmi\"] = \"audio/mid\";\n    mTypes[\".rmm\"] = \"audio/x-pn-realaudio\";\n    mTypes[\".rmp\"] = \"audio/x-pn-realaudio\";\n    mTypes[\".rng\"] = \"application/ringing-tones\";\n    mTypes[\".rnx\"] = \"application/vnd.rn-realplayer\";\n    mTypes[\".roff\"] = \"application/x-troff\";\n    mTypes[\".rp\"] = \"image/vnd.rn-realpix\";\n    mTypes[\".rpm\"] = \"audio/x-pn-realaudio-plugin\";\n    mTypes[\".rt\"] = \"text/richtext\";\n    mTypes[\".rtf\"] = \"application/rtf\";\n    mTypes[\".rtx\"] = \"application/rtf\";\n    mTypes[\".rv\"] = \"video/vnd.rn-realvideo\";\n    mTypes[\".s\"] = \"text/x-asm\";\n    mTypes[\".s3m\"] = \"audio/s3m\";\n    mTypes[\".saveme\"] = \"application/octet-stream\";\n    mTypes[\".sbk\"] = \"application/x-tbook\";\n    mTypes[\".scm\"] = \"video/x-scm\";\n    mTypes[\".sdml\"] = \"text/plain\";\n    mTypes[\".sdp\"] = \"application/sdp\";\n    mTypes[\".sdr\"] = \"application/sounder\";\n    mTypes[\".sea\"] = \"application/sea\";\n    mTypes[\".set\"] = \"application/set\";\n    mTypes[\".sgm\"] = \"text/sgml\";\n    mTypes[\".sgml\"] = \"text/sgml\";\n    mTypes[\".sh\"] = \"application/x-sh\";\n    mTypes[\".shar\"] = \"application/x-bsh\";\n    mTypes[\".shar\"] = \"application/x-shar\";\n    mTypes[\".shtml\"] = \"text/html\";\n    mTypes[\".sid\"] = \"audio/x-psid\";\n    mTypes[\".sit\"] = \"application/x-sit\";\n    mTypes[\".skd\"] = \"application/x-koan\";\n    mTypes[\".skm\"] = \"application/x-koan\";\n    mTypes[\".skp\"] = \"application/x-koan\";\n    mTypes[\".skt\"] = \"application/x-koan\";\n    mTypes[\".sl\"] = \"application/x-seelogo\";\n    mTypes[\".sldx\"] = \"application/vnd.openxmlformats-officedocument.presentationml.slide\";\n    mTypes[\".smi\"] = \"application/smil\";\n    mTypes[\".smil\"] = \"application/smil\";\n    mTypes[\".snd\"] = \"audio/basic\";\n    mTypes[\".sol\"] = \"application/solids\";\n    mTypes[\".spc\"] = \"text/x-speech\";\n    mTypes[\".spl\"] = \"application/futuresplash\";\n    mTypes[\".spr\"] = \"application/x-sprite\";\n    mTypes[\".sprite\"] = \"application/x-sprite\";\n    mTypes[\".src\"] = \"application/x-wais-source\";\n    mTypes[\".ssi\"] = \"text/x-server-parsed-html\";\n    mTypes[\".ssm\"] = \"application/streamingmedia\";\n    mTypes[\".sst\"] = \"application/vnd.ms-pki.certstore\";\n    mTypes[\".step\"] = \"application/step\";\n    mTypes[\".stl\"] = \"application/sla\";\n    mTypes[\".stp\"] = \"application/step\";\n    mTypes[\".sv4cpio\"] = \"application/x-sv4cpio\";\n    mTypes[\".sv4crc\"] = \"application/x-sv4crc\";\n    mTypes[\".svf\"] = \"image/vnd.dwg\";\n    mTypes[\".svg\"] = \"image/svg+xml\";\n    mTypes[\".svr\"] = \"application/x-world\";\n    mTypes[\".swf\"] = \"application/x-shockwave-flash\";\n    mTypes[\".t\"] = \"application/x-troff\";\n    mTypes[\".talk\"] = \"text/x-speech\";\n    mTypes[\".tar\"] = \"application/x-tar\";\n    mTypes[\".tbk\"] = \"application/toolbook\";\n    mTypes[\".tcl\"] = \"application/x-tcl\";\n    mTypes[\".tcsh\"] = \"text/x-script.tcsh\";\n    mTypes[\".tex\"] = \"application/x-tex\";\n    mTypes[\".texi\"] = \"application/x-texinfo\";\n    mTypes[\".texinfo\"] = \"application/x-texinfo\";\n    mTypes[\".text\"] = \"text/plain\";\n    mTypes[\".tgz\"] = \"application/gnutar\";\n    mTypes[\".tif\"] = \"image/tiff\";\n    mTypes[\".tiff\"] = \"image/tiff\";\n    mTypes[\".tr\"] = \"application/x-troff\";\n    mTypes[\".tsi\"] = \"audio/tsp-audio\";\n    mTypes[\".tsp\"] = \"application/dsptype\";\n    mTypes[\".tsv\"] = \"text/tab-separated-values\";\n    mTypes[\".turbot\"] = \"image/florian\";\n    mTypes[\".txt\"] = \"text/plain\";\n    mTypes[\".uil\"] = \"text/x-uil\";\n    mTypes[\".uni\"] = \"text/uri-list\";\n    mTypes[\".unis\"] = \"text/uri-list\";\n    mTypes[\".unv\"] = \"application/i-deas\";\n    mTypes[\".uri\"] = \"text/uri-list\";\n    mTypes[\".uris\"] = \"text/uri-list\";\n    mTypes[\".ustar\"] = \"application/x-ustar\";\n    mTypes[\".uu\"] = \"application/octet-stream\";\n    mTypes[\".uue\"] = \"text/x-uuencode\";\n    mTypes[\".vcd\"] = \"application/x-cdlink\";\n    mTypes[\".vcs\"] = \"text/x-vcalendar\";\n    mTypes[\".vda\"] = \"application/vda\";\n    mTypes[\".vdo\"] = \"video/vdo\";\n    mTypes[\".vew\"] = \"application/groupwise\";\n    mTypes[\".viv\"] = \"video/vivo\";\n    mTypes[\".vivo\"] = \"video/vivo\";\n    mTypes[\".vmd\"] = \"application/vocaltec-media-desc\";\n    mTypes[\".vmf\"] = \"application/vocaltec-media-file\";\n    mTypes[\".voc\"] = \"audio/voc\";\n    mTypes[\".vos\"] = \"video/vosaic\";\n    mTypes[\".vox\"] = \"audio/voxware\";\n    mTypes[\".vqe\"] = \"audio/x-twinvq-plugin\";\n    mTypes[\".vqf\"] = \"audio/x-twinvq\";\n    mTypes[\".vql\"] = \"audio/x-twinvq-plugin\";\n    mTypes[\".vrml\"] = \"application/x-vrml\";\n    mTypes[\".vrt\"] = \"x-world/x-vrt\";\n    mTypes[\".vsd\"] = \"application/x-visio\";\n    mTypes[\".vst\"] = \"application/x-visio\";\n    mTypes[\".vsw\"] = \"application/x-visio\";\n    mTypes[\".w60\"] = \"application/wordperfect6.0\";\n    mTypes[\".w61\"] = \"application/wordperfect6.1\";\n    mTypes[\".w6w\"] = \"application/msword\";\n    mTypes[\".wav\"] = \"audio/wav\";\n    mTypes[\".wb1\"] = \"application/x-qpro\";\n    mTypes[\".wbmp\"] = \"image/vnd.wap.wbmp\";\n    mTypes[\".web\"] = \"application/vnd.xara\";\n    mTypes[\".wiz\"] = \"application/msword\";\n    mTypes[\".wk1\"] = \"application/x-123\";\n    mTypes[\".wmf\"] = \"windows/metafile\";\n    mTypes[\".wml\"] = \"text/vnd.wap.wml\";\n    mTypes[\".wmlc\"] = \"application/vnd.wap.wmlc\";\n    mTypes[\".wmls\"] = \"text/vnd.wap.wmlscript\";\n    mTypes[\".wmlsc\"] = \"application/vnd.wap.wmlscriptc\";\n    mTypes[\".word\"] = \"application/msword\";\n    mTypes[\".wp\"] = \"application/wordperfect\";\n    mTypes[\".wp5\"] = \"application/wordperfect\";\n    mTypes[\".wp6\"] = \"application/wordperfect\";\n    mTypes[\".wpd\"] = \"application/wordperfect\";\n    mTypes[\".wq1\"] = \"application/x-lotus\";\n    mTypes[\".wri\"] = \"application/mswrite\";\n    mTypes[\".wri\"] = \"application/x-wri\";\n    mTypes[\".wrl\"] = \"model/vrml\";\n    mTypes[\".wrz\"] = \"x-world/x-vrml\";\n    mTypes[\".wsc\"] = \"text/scriplet\";\n    mTypes[\".wsrc\"] = \"application/x-wais-source\";\n    mTypes[\".wtk\"] = \"application/x-wintalk\";\n    mTypes[\".xbm\"] = \"image/xbm\";\n    mTypes[\".xdr\"] = \"video/x-amt-demorun\";\n    mTypes[\".xgz\"] = \"xgl/drawing\";\n    mTypes[\".xif\"] = \"image/vnd.xiff\";\n    mTypes[\".xl\"] = \"application/vnd-ms.excel\";\n    mTypes[\".xla\"] = \"application/vnd-ms.excel\";\n    mTypes[\".xlam\"] = \"application/vnd.ms-excel.addin.macroEnabled.12\";\n    mTypes[\".xlb\"] = \"application/x-excel\";\n    mTypes[\".xlc\"] = \"application/vnd-ms.excel\";\n    mTypes[\".xlc\"] = \"application/x-excel\";\n    mTypes[\".xld\"] = \"application/vnd.ms-excel\";\n    mTypes[\".xlk\"] = \"application/vnd.ms-excel\";\n    mTypes[\".xll\"] = \"application/vnd.ms-excel\";\n    mTypes[\".xll\"] = \"application/x-excel\";\n    mTypes[\".xlm\"] = \"application/vnd-ms.excel\";\n    mTypes[\".xls\"] = \"application/vnd.ms.excel\";\n    mTypes[\".xlsb\"] = \"application/vnd.ms-excel.sheet.binary.macroEnabled.12\";\n    mTypes[\".xlsx\"] = \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\";\n    mTypes[\".xltx\"] = \"application/vnd.openxmlformats-officedocument.spreadsheetml.template\";\n    mTypes[\".xlt\"] = \"application/vnd.ms-excel\";\n    mTypes[\".xlv\"] = \"application/vnd.ms-excel\";\n    mTypes[\".xlw\"] = \"application/vnd.ms-excel\";\n    mTypes[\".xlw\"] = \"application/x-msexcel\";\n    mTypes[\".xm\"] = \"audio/xm\";\n    mTypes[\".xml\"] = \"text/xml\";\n    mTypes[\".xmz\"] = \"xgl/movie\";\n    mTypes[\".xpix\"] = \"application/x-vnd.ls-xpix\";\n    mTypes[\".xpm\"] = \"image/xpm\";\n    mTypes[\".x-png\"] = \"image/png\";\n    mTypes[\".xsr\"] = \"video/x-amt-showrun\";\n    mTypes[\".xwd\"] = \"image/x-xwd\";\n    mTypes[\".xyz\"] = \"chemical/x-pdb\";\n    mTypes[\".z\"] = \"application/x-compress\";\n    mTypes[\".zip\"] = \"application/zip\";\n    mTypes[\".zoo\"] = \"application/octet-stream\";\n    mTypes[\".zsh\"] = \"text/x-script.zsh\";\n  }\n};\n\n/*----------------------------------------------------------------------------*/\n\nEOSCOMMONNAMESPACE_END\n\n/*----------------------------------------------------------------------------*/\n\n#endif\n"
  },
  {
    "path": "common/http/OwnCloud.hh",
    "content": "// ----------------------------------------------------------------------\n// File: OwnCloud.hh\n// Author: Andreas-Joachim Peters CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   OwnCloud.hh\n *\n * @brief  Deals with OwnCloud specific headers and naming conventions\n */\n\n#ifndef __EOSCOMMON_OWNCLOUD__HH__\n#define __EOSCOMMON_OWNCLOUD__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/Namespace.hh\"\n#include \"common/http/HttpRequest.hh\"\n#include \"common/http/HttpResponse.hh\"\n#include \"common/http/HttpServer.hh\"\n#include \"common/Path.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/Logging.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucEnv.hh>\n/*----------------------------------------------------------------------------*/\n#include <map>\n#include <string>\n\n/*----------------------------------------------------------------------------*/\n\nEOSCOMMONNAMESPACE_BEGIN\n\nclass OwnCloudPath : public Path\n{\npublic:\n  int mMaxChunks; //< max OC index for a chunked path\n  int mNChunk; //< OC index for a chunked path\n  XrdOucString mUploadId; //< OC client id for a chunked path\n\n  // ---------------------------------------------------------------------------\n  //! Parse a chunked path into pieces\n  //! OC used <basename>-<id>-<max-chunks>-<n-chunk>\n  // ---------------------------------------------------------------------------\n\n  OwnCloudPath(const char* p) :\n    Path(p), mMaxChunks(0), mNChunk(0)\n  {}\n\n  const char* ParseChunkedPath()\n  {\n    atomicPath = GetFullPath();\n    int lOCnChunk, lOCmaxChunks;\n    lOCnChunk = lOCmaxChunks = 0;\n    XrdOucString lOCuploadId;\n    XrdOucString unchunkedPath = atomicPath;\n    int pos;\n\n    if ((pos = unchunkedPath.rfind(\"-\")) != STR_NPOS) {\n      lOCnChunk = atoi(unchunkedPath.c_str() + pos + 1);\n      atomicPath.erase(pos);\n      unchunkedPath.erase(pos);\n\n      if ((pos = unchunkedPath.rfind(\"-\")) != STR_NPOS) {\n        unchunkedPath.erase(pos);\n        lOCmaxChunks = atoi(unchunkedPath.c_str() + pos + 1);\n\n        if ((pos = unchunkedPath.rfind(\"-\", pos - 1)) != STR_NPOS) {\n          lOCuploadId = unchunkedPath.c_str() + pos + 1;\n          unchunkedPath.erase(pos);\n\n          if ((pos = unchunkedPath.rfind(\"-\", pos - 1)) != STR_NPOS) {\n            if (unchunkedPath.endswith(\"-chunking\")) {\n              // remove -chunking at the end\n              unchunkedPath.erase(pos);\n            }\n          }\n        }\n      }\n    }\n\n    Init(unchunkedPath.c_str());\n    mNChunk = lOCnChunk;\n    mMaxChunks = lOCmaxChunks;\n    mUploadId = lOCuploadId;\n    return GetPath();\n  }\n};\n\n/*----------------------------------------------------------------------------*/\nclass OwnCloud\n{\nprivate:\npublic:\n\n  // ---------------------------------------------------------------------------\n\n  static bool isChunkUpload(HttpRequest* request)\n  {\n    return request->GetHeaders().count(\"oc-chunked\");\n  }\n\n  static bool isChunkUpload(XrdOucEnv& env)\n  {\n    return (env.Get(\"oc-chunk-n\") ? true : false);\n  }\n\n  // ---------------------------------------------------------------------------\n\n  static const char* getContentSize(HttpRequest* request)\n  {\n    if (request->GetHeaders().count(\"oc-total-length\")) {\n      return request->GetHeaders()[\"oc-total-length\"].c_str();\n    } else {\n      return 0;\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n\n  static bool GetChunkInfo(const char* request,\n                           int& chunk_n,\n                           int& chunk_max,\n                           XrdOucString& chunk_uuid)\n  {\n    eos_static_debug(\"opaque=%s\", request);\n    bool ok = true;\n    XrdOucEnv env(request);\n    char* val = 0;\n\n    if ((val = env.Get(\"oc-chunk-n\"))) {\n      chunk_n = (int) strtol(val, 0, 10);\n    } else {\n      ok = false;\n    }\n\n    if ((val = env.Get(\"oc-chunk-max\"))) {\n      chunk_max = (int) strtol(val, 0, 10);\n    } else {\n      ok = false;\n    }\n\n    if ((val = env.Get(\"oc-chunk-uuid\"))) {\n      chunk_uuid = val;\n    } else {\n      ok = false;\n    }\n\n    return ok;\n  }\n\n  // ---------------------------------------------------------------------------\n\n  static int GetNChunk(HttpRequest* request)\n  {\n    if (!request->GetHeaders().count(\"oc-chunk-n\")) {\n      return 0;\n    } else {\n      return (int) strtol(request->GetHeaders()[\"oc-chunk-n\"].c_str(), 0, 10);\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n\n  typedef std::pair<std::string, std::string> checksum_t;\n\n  static checksum_t GetChecksum(HttpRequest* request,\n                                std::string headertag = \"oc-checksum\")\n  {\n    if (!request->GetHeaders().count(headertag)) {\n      return std::make_pair<std::string, std::string>(\"\", \"\");\n    }\n\n    std::string checksum_data = request->GetHeaders()[headertag];\n    std::string checksum_type = checksum_data;\n    std::string checksum_value = checksum_data;\n    size_t epos = checksum_data.find(\":\");\n\n    if (epos != std::string::npos) {\n      checksum_value.erase(0, checksum_data.find(\":\") + 1);\n      checksum_type.erase(checksum_data.find(\":\"));\n    }\n\n    checksum_type = LC_STRING(checksum_type);\n\n    // map checksum types to EOS checksum names\n    if (checksum_type == \"adler32\") {\n      checksum_type = \"adler\";\n    }\n\n    return std::make_pair(checksum_type, checksum_value);\n  }\n\n  // ---------------------------------------------------------------------------\n\n  static std::string GetChecksumString(std::string type, std::string value)\n  {\n    std::string checksum;\n\n    if (type == \"adler\") {\n      checksum += \"Adler32\";\n    } else if (type == \"md5\") {\n      checksum += \"MD5\";\n    } else if (type == \"sha1\") {\n      checksum += \"SHA1\";\n    } else if (type == \"crc32c\") {\n      checksum += \"CRC32C\";\n    } else if (type == \"crc32\") {\n      checksum += \"CRC32\";\n    } else {\n      checksum += \"unknown\";\n    }\n\n    checksum += \":\";\n    checksum += value;\n    return checksum;\n  }\n\n  // ---------------------------------------------------------------------------\n\n  static int GetMaxChunks(HttpRequest* request)\n  {\n    if (!request->GetHeaders().count(\"oc-chunk-max\")) {\n      return 0;\n    } else {\n      return (int) strtol(request->GetHeaders()[\"oc-chunk-max\"].c_str(), 0, 10);\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n\n  static bool HasOcContentLength(HttpRequest* request)\n  {\n    return request->GetHeaders().count(\"oc-total-length\");\n  }\n\n  // ---------------------------------------------------------------------------\n\n  static void ReplaceRemotePhp(XrdOucString& path)\n  {\n    if (path.find(\"/remote.php/webdav/\") != STR_NPOS) {\n      path.replace(\"remote.php/webdav/\", \"\");\n    }\n  }\n  // ---------------------------------------------------------------------------\n\n  static bool WantsStatus(XrdOucString& path)\n  {\n    if (path.find(\"/status.php\") != STR_NPOS) {\n      path.replace(\"/status.php\", \"\");\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n\n  static std::string prepareChunkUpload(HttpRequest* request,\n                                        HttpResponse** response,\n                                        std::map<std::string, std::string>& ocHeader)\n  {\n    eos::common::OwnCloudPath ocPath(request->GetUrl().c_str());\n    ocPath.ParseChunkedPath();\n    eos_static_info(\"type=\\\"oc-chunked\\\" in-path=\\\"%s\\\" final-path=\\\"%s\\\"\"\n                    \" id=\\\"%s\\\" n=%d max=%d\",\n                    request->GetUrl().c_str(),\n                    ocPath.GetFullPath().c_str(),\n                    ocPath.mUploadId.c_str(),\n                    ocPath.mNChunk,\n                    ocPath.mMaxChunks\n                   );\n\n    if (ocPath.mMaxChunks > 0xffff) {\n      // -----------------------------------------------------------------------\n      // we support maximum 65536 chunks\n      // the reason is, that we can only store 16-bit under the flags entry\n      // in the namespace meta data per file\n      // -----------------------------------------------------------------------\n      *response = HttpServer::HttpError(\"Too many chunks to upload (>65536)\",\n                                        EOPNOTSUPP);\n      return \"\";\n    }\n\n    XrdOucString OcMaxChunks = \"\";\n    OcMaxChunks += (int) ocPath.mMaxChunks;\n    XrdOucString OcNChunk = \"\";\n    OcNChunk += (int) ocPath.mNChunk;\n    XrdOucString OcUuid = \"\";\n    OcUuid += ocPath.mUploadId.c_str();\n    int pad = 36 - ocPath.mUploadId.length();\n\n    if (pad > 0) {\n      for (int i = 0; i < pad; i++) {\n        OcUuid += \"0\";\n      }\n    }\n\n    if (pad < 0) {\n      OcUuid.erase(OcUuid.length() + pad);\n    }\n\n    // -------------------------------------------------------------------------\n    // return some\n    // -------------------------------------------------------------------------\n    ocHeader[\"oc-chunk-n\"] = OcNChunk.c_str();\n    ocHeader[\"oc-chunk-max\"] = OcMaxChunks.c_str();\n    ocHeader[\"oc-chunk-uuid\"] = OcUuid.c_str();\n    // -------------------------------------------------------------------------\n    // we return the final path\n    // -------------------------------------------------------------------------\n    return ocPath.GetFullPath().c_str();\n  }\n\n  // ---------------------------------------------------------------------------\n\n  static void addOcHeader(HttpResponse* response,\n                          std::map<std::string, std::string>& ocHeader)\n  {\n    for (auto it = ocHeader.begin(); it != ocHeader.end(); ++it) {\n      response->AddHeader(it->first, it->second);\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n\n  static XrdOucString HeaderToQuery(std::map<std::string, std::string>& ocHeader)\n  {\n    XrdOucString query;\n\n    for (auto it = ocHeader.begin(); it != ocHeader.end(); ++it) {\n      if (it->first.substr(0, 3) == \"oc-\") {\n        query += \"&\";\n        query += it->first.c_str();\n        query += \"=\";\n        query += it->second.c_str();\n      }\n    }\n\n    return query;\n  }\n\n  // ---------------------------------------------------------------------------\n\n  static std::string FilterOcQuery(const char* query)\n  {\n    std::string output;\n    std::map<std::string, std::string> map;\n    eos::common::StringConversion::GetKeyValueMap(query, map, \"=\", \"&\");\n\n    for (auto it = map.begin(); it != map.end(); ++it) {\n      if (it->first.substr(0, 3) == \"oc-\") {\n        output += \"&\";\n        output += it->first.c_str();\n        output += \"=\";\n        output += it->second.c_str();\n      }\n    }\n\n    return output;\n  }\n\n  // ---------------------------------------------------------------------------\n\n  static const char* OwnCloudNs()\n  {\n    return \"xmlns:oc\";\n  }\n\n  // ---------------------------------------------------------------------------\n\n  static const char* OwnCloudNsUrl()\n  {\n    return \"http://owncloud.org/ns\";\n  }\n\n  // ---------------------------------------------------------------------------\n\n  static const char* OwnCloudRemapping(XrdOucString& path, HttpRequest* request)\n  {\n    XrdOucString client_path = \"\";\n    XrdOucString server_path = \"\";\n\n    if (request->GetHeaders().count(\"cbox-client-mapping\")) {\n      client_path = request->GetHeaders()[\"cbox-client-mapping\"].c_str();\n    }\n\n    if (request->GetHeaders().count(\"cbox-server-mapping\")) {\n      server_path = request->GetHeaders()[\"cbox-server-mapping\"].c_str();\n    }\n\n    while (path.replace(\"//\", \"/\")) {\n    }\n\n    if (!path.beginswith(\"/\")) {\n      path.insert(\"/\", 0);\n    }\n\n    // shortcut if there is nothing to replace\n    if (!client_path.length()) {\n      return path.c_str();\n    }\n\n    while (client_path.replace(\"//\", \"/\")) {\n    }\n\n    while (server_path.replace(\"//\", \"/\")) {\n    }\n\n    if (!client_path.beginswith(\"/\")) {\n      client_path.insert(\"/\", 0);\n    }\n\n    if (!server_path.beginswith(\"/\")) {\n      server_path.insert(\"/\", 0);\n    }\n\n    path.replace(client_path, server_path);\n    return path.c_str();\n  }\n\n  // ---------------------------------------------------------------------------\n\n  static const char* GetAllowSyncName()\n  {\n    return \"sys.allow.oc.sync\";\n  }\n\n};\n\n/*----------------------------------------------------------------------------*/\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/http/PlainHttpResponse.hh",
    "content": "// ----------------------------------------------------------------------\n// File: PlainHttpResponse.hh\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   PlainHttpResponse.hh\n *\n * @brief  The simplest possible HTTP response. Does no request processing\n *         whatsoever.\n */\n\n#ifndef __EOSCOMMON_PLAIN_HTTP_RESPONSE__HH__\n#define __EOSCOMMON_PLAIN_HTTP_RESPONSE__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/http/HttpResponse.hh\"\n#include \"common/Namespace.hh\"\n#include \"common/Logging.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n#include <map>\n#include <string>\n/*----------------------------------------------------------------------------*/\n\nEOSCOMMONNAMESPACE_BEGIN\n\nclass PlainHttpResponse : public HttpResponse\n{\n\npublic:\n\n  PlainHttpResponse () {};\n  virtual ~PlainHttpResponse () {};\n\n  /**\n   * Build an appropriate response to the given request.\n   *\n   * @param request  the client request object\n   *\n   * @return the newly built response object (empty in this case)\n   */\n  HttpResponse*\n  BuildResponse (eos::common::HttpRequest *request) { return this; };\n};\n\n/*----------------------------------------------------------------------------*/\nEOSCOMMONNAMESPACE_END\n\n#endif /* __EOSCOMMON_PLAIN_HTTP_RESPONSE__HH__ */\n"
  },
  {
    "path": "common/http/ProtocolHandler.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ProtocolHandler.hh\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   ProtocolHandler.hh\n *\n * @brief  Abstract base class representing an interface which a concrete\n *         protocol must implement, e.g. HTTP, WebDAV, S3.\n */\n\n#ifndef __EOSCOMMON_PROTOCOLHANDLER__HH__\n#define __EOSCOMMON_PROTOCOLHANDLER__HH__\n\n#include \"common/http/HttpRequest.hh\"\n#include \"common/http/HttpResponse.hh\"\n#include \"common/Mapping.hh\"\n#include \"common/Namespace.hh\"\n#include <string>\n#include <map>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class ProtocolHandler\n//------------------------------------------------------------------------------\nclass ProtocolHandler\n{\npublic:\n  typedef std::map<std::string, std::string> HeaderMap;\n\nprotected:\n  HttpResponse*                          mHttpResponse;    //!< the HTTP response\n  eos::common::VirtualIdentity*\n  mVirtualIdentity; //!< the virtual identity\n\n  std::string mRequestBody; //!< store small request bodies like PROPFIND\npublic:\n\n  /**\n   * Constructor\n   */\n  ProtocolHandler() :\n    mHttpResponse(0), mVirtualIdentity(0) {};\n\n  /**\n   * Constructor\n   */\n  ProtocolHandler(eos::common::VirtualIdentity* vid) :\n    mHttpResponse(0), mVirtualIdentity(vid) {};\n\n  /**\n   * Destructor\n   */\n  virtual ~ProtocolHandler()\n  {\n    delete mHttpResponse;\n    delete mVirtualIdentity;\n  };\n\n  /**\n   * Concrete implementations must use this function to check whether the given\n   * method and headers are a match for their protocol.\n   *\n   * @param method  the request verb used by the client (GET, PUT, etc)\n   * @param headers the map of request headers\n   *\n   * @return true if the protocol matches, false otherwise\n   */\n  static bool\n  Matches(const std::string& method, HeaderMap& headers);\n\n  /**\n   * Concrete implementations must use this function to build a response to the\n   * given request.\n   *\n   * @param request the client request object\n   */\n  virtual void\n  HandleRequest(HttpRequest* request) = 0;\n\n  /**\n   * @return the HttpResponse object\n   */\n  inline HttpResponse*\n  GetResponse()\n  {\n    return mHttpResponse;\n  }\n\n  /**\n   * Delete the HttpResponse object\n   */\n  inline void\n  DeleteResponse()\n  {\n    delete mHttpResponse;\n    mHttpResponse = 0;\n  }\n\n  /**\n   * Add a piece to the body\n   */\n  void\n  AddToBody(const char* body, size_t size)\n  {\n    mRequestBody.append(body, size);\n  }\n\n  /**\n   * @return the client request body\n   */\n  inline const std::string&\n  GetBody()\n  {\n    return mRequestBody;\n  }\n};\n\n\nEOSCOMMONNAMESPACE_END\n\n#endif /* __EOSCOMMON_PROTOCOLHANDLER__HH__ */\n"
  },
  {
    "path": "common/http/ProtocolHandlerFactory.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ProtocolHandlerFactory.hh\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   ProtocolHandlerFactory.hh\n *\n * @brief  Abstract factory class to be implemented by the MGM and FST to\n *         create the correct protocol handler.\n */\n\n#ifndef __EOSCOMMON_PROTOCOLHANDLERFACTORY__HH__\n#define __EOSCOMMON_PROTOCOLHANDLERFACTORY__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/Namespace.hh\"\n#include \"common/http/ProtocolHandler.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n#include <map>\n#include <string>\n/*----------------------------------------------------------------------------*/\n\nEOSCOMMONNAMESPACE_BEGIN\n\nclass ProtocolHandlerFactory\n{\npublic:\n\n  ProtocolHandlerFactory () {};\n  virtual ~ProtocolHandlerFactory () {};\n\n  /**\n   * Factory function to create an appropriate object which will handle this\n   * request based on the method and headers.\n   *\n   * @param method  the request verb used by the client (GET, PUT, etc)\n   * @param headers the map of request headers\n   * @param vid     the mapped virtual identity of this client\n   *\n   * @return a concrete ProtocolHandler, or NULL if no matching protocol found\n   */\n  virtual eos::common::ProtocolHandler*\n  CreateProtocolHandler (const std::string                     &method,\n                         std::map<std::string, std::string>    &headers,\n                         eos::common::VirtualIdentity          *vid) = 0;\n\n};\n\n/*----------------------------------------------------------------------------*/\nEOSCOMMONNAMESPACE_END\n\n#endif /* __EOSCOMMON_PROTOCOLHANDLERFACTORY__HH__ */\n"
  },
  {
    "path": "common/http/s3/S3Handler.cc",
    "content": "// ----------------------------------------------------------------------\n// File: S3Handler.cc\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"common/http/s3/S3Handler.hh\"\n#include \"common/http/s3/S3Response.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/Logging.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n\nEOSCOMMONNAMESPACE_BEGIN\n\n\n/*----------------------------------------------------------------------------*/\nstd::string\nS3Handler:: ContentType (const std::string &path)\n{\n  XrdOucString name = path.c_str();\n  if (name.endswith(\".3g2\")) return \"video/3gpp2\";\n  if (name.endswith(\".3gp\")) return \"video/3gpp\";\n  if (name.endswith(\".3gp2\")) return \"video/3gpp2\";\n  if (name.endswith(\".3gpp\")) return \"video/3gpp\";\n  if (name.endswith(\".aa\")) return \"audio/audible\";\n  if (name.endswith(\".aac\")) return \"audio/vnd.dlna.adts\";\n  if (name.endswith(\".aax\")) return \"audio/vnd.audible.aax\";\n  if (name.endswith(\".addin\")) return \"text/xml\";\n  if (name.endswith(\".adt\")) return \"audio/vnd.dlna.adts\";\n  if (name.endswith(\".adts\")) return \"audio/vnd.dlna.adts\";\n  if (name.endswith(\".ai\")) return \"application/postscript\";\n  if (name.endswith(\".aif\")) return \"audio/aiff\";\n  if (name.endswith(\".aifc\")) return \"audio/aiff\";\n  if (name.endswith(\".aiff\")) return \"audio/aiff\";\n  if (name.endswith(\".application\")) return \"application/x-ms-application\";\n  if (name.endswith(\".asax\")) return \"application/xml\";\n  if (name.endswith(\".ascx\")) return \"application/xml\";\n  if (name.endswith(\".asf\")) return \"video/x-ms-asf\";\n  if (name.endswith(\".ashx\")) return \"application/xml\";\n  if (name.endswith(\".asmx\")) return \"application/xml\";\n  if (name.endswith(\".aspx\")) return \"application/xml\";\n  if (name.endswith(\".asx\")) return \"video/x-ms-asf\";\n  if (name.endswith(\".au\")) return \"audio/basic\";\n  if (name.endswith(\".avi\")) return \"video/avi\";\n  if (name.endswith(\".bmp\")) return \"image/bmp\";\n  if (name.endswith(\".btapp\")) return \"application/x-bittorrent-app\";\n  if (name.endswith(\".btinstall\")) return \"application/x-bittorrent-appinst\";\n  if (name.endswith(\".btkey\")) return \"application/x-bittorrent-key\";\n  if (name.endswith(\".btsearch\")) return \"application/x-bittorrentsearchdescription+xml\";\n  if (name.endswith(\".btskin\")) return \"application/x-bittorrent-skin\";\n  if (name.endswith(\".cat\")) return \"application/vnd.ms-pki.seccat\";\n  if (name.endswith(\".cd\")) return \"text/plain\";\n  if (name.endswith(\".cer\")) return \"application/x-x509-ca-cert\";\n  if (name.endswith(\".config\")) return \"application/xml\";\n  if (name.endswith(\".contact\")) return \"text/x-ms-contact\";\n  if (name.endswith(\".crl\")) return \"application/pkix-crl\";\n  if (name.endswith(\".crt\")) return \"application/x-x509-ca-cert\";\n  if (name.endswith(\".cs\")) return \"text/plain\";\n  if (name.endswith(\".csproj\")) return \"text/plain\";\n  if (name.endswith(\".css\")) return \"text/css\";\n  if (name.endswith(\".csv\")) return \"application/vnd.ms-excel\";\n  if (name.endswith(\".datasource\")) return \"application/xml\";\n  if (name.endswith(\".der\")) return \"application/x-x509-ca-cert\";\n  if (name.endswith(\".dib\")) return \"image/bmp\";\n  if (name.endswith(\".dll\")) return \"application/x-msdownload\";\n  if (name.endswith(\".doc\")) return \"application/msword\";\n  if (name.endswith(\".docm\")) return \"application/vnd.ms-word.document.macroEnabled.12\";\n  if (name.endswith(\".docx\")) return \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\";\n  if (name.endswith(\".dot\")) return \"application/msword\";\n  if (name.endswith(\".dotm\")) return \"application/vnd.ms-word.template.macroEnabled.12\";\n  if (name.endswith(\".dotx\")) return \"application/vnd.openxmlformats-officedocument.wordprocessingml.template\";\n  if (name.endswith(\".dtd\")) return \"application/xml-dtd\";\n  if (name.endswith(\".dtsconfig\")) return \"text/xml\";\n  if (name.endswith(\".eps\")) return \"application/postscript\";\n  if (name.endswith(\".exe\")) return \"application/x-msdownload\";\n  if (name.endswith(\".fdf\")) return \"application/vnd.fdf\";\n  if (name.endswith(\".fif\")) return \"application/fractals\";\n  if (name.endswith(\".gif\")) return \"image/gif\";\n  if (name.endswith(\".group\")) return \"text/x-ms-group\";\n  if (name.endswith(\".hdd\")) return \"application/x-virtualbox-hdd\";\n  if (name.endswith(\".hqx\")) return \"application/mac-binhex40\";\n  if (name.endswith(\".hta\")) return \"application/hta\";\n  if (name.endswith(\".htc\")) return \"text/x-component\";\n  if (name.endswith(\".htm\")) return \"text/html\";\n  if (name.endswith(\".html\")) return \"text/html\";\n  if (name.endswith(\".hxa\")) return \"application/xml\";\n  if (name.endswith(\".hxc\")) return \"application/xml\";\n  if (name.endswith(\".hxd\")) return \"application/octet-stream\";\n  if (name.endswith(\".hxe\")) return \"application/xml\";\n  if (name.endswith(\".hxf\")) return \"application/xml\";\n  if (name.endswith(\".hxh\")) return \"application/octet-stream\";\n  if (name.endswith(\".hxi\")) return \"application/octet-stream\";\n  if (name.endswith(\".hxk\")) return \"application/xml\";\n  if (name.endswith(\".hxq\")) return \"application/octet-stream\";\n  if (name.endswith(\".hxr\")) return \"application/octet-stream\";\n  if (name.endswith(\".hxs\")) return \"application/octet-stream\";\n  if (name.endswith(\".hxt\")) return \"application/xml\";\n  if (name.endswith(\".hxv\")) return \"application/xml\";\n  if (name.endswith(\".hxw\")) return \"application/octet-stream\";\n  if (name.endswith(\".ico\")) return \"image/x-icon\";\n  if (name.endswith(\".ics\")) return \"text/calendar\";\n  if (name.endswith(\".ipa\")) return \"application/x-itunes-ipa\";\n  if (name.endswith(\".ipg\")) return \"application/x-itunes-ipg\";\n  if (name.endswith(\".ipsw\")) return \"application/x-itunes-ipsw\";\n  if (name.endswith(\".iqy\")) return \"text/x-ms-iqy\";\n  if (name.endswith(\".iss\")) return \"text/plain\";\n  if (name.endswith(\".ite\")) return \"application/x-itunes-ite\";\n  if (name.endswith(\".itlp\")) return \"application/x-itunes-itlp\";\n  if (name.endswith(\".itls\")) return \"application/x-itunes-itls\";\n  if (name.endswith(\".itms\")) return \"application/x-itunes-itms\";\n  if (name.endswith(\".itpc\")) return \"application/x-itunes-itpc\";\n  if (name.endswith(\".jfif\")) return \"image/jpeg\";\n  if (name.endswith(\".jnlp\")) return \"application/x-java-jnlp-file\";\n  if (name.endswith(\".jpe\")) return \"image/jpeg\";\n  if (name.endswith(\".jpeg\")) return \"image/jpeg\";\n  if (name.endswith(\".jpg\")) return \"image/jpeg\";\n  if (name.endswith(\".js\")) return \"application/javascript\";\n  if (name.endswith(\".latex\")) return \"application/x-latex\";\n  if (name.endswith(\".library-ms\")) return \"application/windows-library+xml\";\n  if (name.endswith(\".m1v\")) return \"video/mpeg\";\n  if (name.endswith(\".m2t\")) return \"video/vnd.dlna.mpeg-tts\";\n  if (name.endswith(\".m2ts\")) return \"video/vnd.dlna.mpeg-tts\";\n  if (name.endswith(\".m2v\")) return \"video/mpeg\";\n  if (name.endswith(\".m3u\")) return \"audio/mpegurl\";\n  if (name.endswith(\".m3u8\")) return \"audio/x-mpegurl\";\n  if (name.endswith(\".m4a\")) return \"audio/m4a\";\n  if (name.endswith(\".m4b\")) return \"audio/m4b\";\n  if (name.endswith(\".m4p\")) return \"audio/m4p\";\n  if (name.endswith(\".m4r\")) return \"audio/x-m4r\";\n  if (name.endswith(\".m4v\")) return \"video/x-m4v\";\n  if (name.endswith(\".magnet\")) return \"application/x-magnet\";\n  if (name.endswith(\".man\")) return \"application/x-troff-man\";\n  if (name.endswith(\".master\")) return \"application/xml\";\n  if (name.endswith(\".mht\")) return \"message/rfc822\";\n  if (name.endswith(\".mhtml\")) return \"message/rfc822\";\n  if (name.endswith(\".mid\")) return \"audio/mid\";\n  if (name.endswith(\".midi\")) return \"audio/mid\";\n  if (name.endswith(\".mod\")) return \"video/mpeg\";\n  if (name.endswith(\".mov\")) return \"video/quicktime\";\n  if (name.endswith(\".mp2\")) return \"audio/mpeg\";\n  if (name.endswith(\".mp2v\")) return \"video/mpeg\";\n  if (name.endswith(\".mp3\")) return \"audio/mpeg\";\n  if (name.endswith(\".mp4\")) return \"video/mp4\";\n  if (name.endswith(\".mp4v\")) return \"video/mp4\";\n  if (name.endswith(\".mpa\")) return \"video/mpeg\";\n  if (name.endswith(\".mpe\")) return \"video/mpeg\";\n  if (name.endswith(\".mpeg\")) return \"video/mpeg\";\n  if (name.endswith(\".mpf\")) return \"application/vnd.ms-mediapackage\";\n  if (name.endswith(\".mpg\")) return \"video/mpeg\";\n  if (name.endswith(\".mpv2\")) return \"video/mpeg\";\n  if (name.endswith(\".mts\")) return \"video/vnd.dlna.mpeg-tts\";\n  if (name.endswith(\".odc\")) return \"text/x-ms-odc\";\n  if (name.endswith(\".odg\")) return \"application/vnd.oasis.opendocument.graphics\";\n  if (name.endswith(\".odm\")) return \"application/vnd.oasis.opendocument.text-master\";\n  if (name.endswith(\".odp\")) return \"application/vnd.oasis.opendocument.presentation\";\n  if (name.endswith(\".ods\")) return \"application/vnd.oasis.opendocument.spreadsheet\";\n  if (name.endswith(\".odt\")) return \"application/vnd.oasis.opendocument.text\";\n  if (name.endswith(\".otg\")) return \"application/vnd.oasis.opendocument.graphics-template\";\n  if (name.endswith(\".oth\")) return \"application/vnd.oasis.opendocument.text-web\";\n  if (name.endswith(\".ots\")) return \"application/vnd.oasis.opendocument.spreadsheet-template\";\n  if (name.endswith(\".ott\")) return \"application/vnd.oasis.opendocument.text-template\";\n  if (name.endswith(\".ova\")) return \"application/x-virtualbox-ova\";\n  if (name.endswith(\".ovf\")) return \"application/x-virtualbox-ovf\";\n  if (name.endswith(\".oxt\")) return \"application/vnd.openofficeorg.extension\";\n  if (name.endswith(\".p10\")) return \"application/pkcs10\";\n  if (name.endswith(\".p12\")) return \"application/x-pkcs12\";\n  if (name.endswith(\".p7b\")) return \"application/x-pkcs7-certificates\";\n  if (name.endswith(\".p7c\")) return \"application/pkcs7-mime\";\n  if (name.endswith(\".p7m\")) return \"application/pkcs7-mime\";\n  if (name.endswith(\".p7r\")) return \"application/x-pkcs7-certreqresp\";\n  if (name.endswith(\".p7s\")) return \"application/pkcs7-signature\";\n  if (name.endswith(\".pcast\")) return \"application/x-podcast\";\n  if (name.endswith(\".pdf\")) return \"application/pdf\";\n  if (name.endswith(\".pdfxml\")) return \"application/vnd.adobe.pdfxml\";\n  if (name.endswith(\".pdx\")) return \"application/vnd.adobe.pdx\";\n  if (name.endswith(\".pfx\")) return \"application/x-pkcs12\";\n  if (name.endswith(\".pko\")) return \"application/vnd.ms-pki.pko\";\n  if (name.endswith(\".pls\")) return \"audio/scpls\";\n  if (name.endswith(\".png\")) return \"image/png\";\n  if (name.endswith(\".pot\")) return \"application/vnd.ms-powerpoint\";\n  if (name.endswith(\".potm\")) return \"application/vnd.ms-powerpoint.template.macroEnabled.12\";\n  if (name.endswith(\".potx\")) return \"application/vnd.openxmlformats-officedocument.presentationml.template\";\n  if (name.endswith(\".ppa\")) return \"application/vnd.ms-powerpoint\";\n  if (name.endswith(\".ppam\")) return \"application/vnd.ms-powerpoint.addin.macroEnabled.12\";\n  if (name.endswith(\".pps\")) return \"application/vnd.ms-powerpoint\";\n  if (name.endswith(\".ppsm\")) return \"application/vnd.ms-powerpoint.slideshow.macroEnabled.12\";\n  if (name.endswith(\".ppsx\")) return \"application/vnd.openxmlformats-officedocument.presentationml.slideshow\";\n  if (name.endswith(\".ppt\")) return \"application/vnd.ms-powerpoint\";\n  if (name.endswith(\".pptm\")) return \"application/vnd.ms-powerpoint.presentation.macroEnabled.12\";\n  if (name.endswith(\".pptx\")) return \"application/vnd.openxmlformats-officedocument.presentationml.presentation\";\n  if (name.endswith(\".prf\")) return \"application/pics-rules\";\n  if (name.endswith(\".ps\")) return \"application/postscript\";\n  if (name.endswith(\".psc1\")) return \"application/PowerShell\";\n  if (name.endswith(\".pwz\")) return \"application/vnd.ms-powerpoint\";\n  if (name.endswith(\".py\")) return \"text/plain\";\n  if (name.endswith(\".pyw\")) return \"text/plain\";\n  if (name.endswith(\".rat\")) return \"application/rat-file\";\n  if (name.endswith(\".rc\")) return \"text/plain\";\n  if (name.endswith(\".rc2\")) return \"text/plain\";\n  if (name.endswith(\".rct\")) return \"text/plain\";\n  if (name.endswith(\".rdlc\")) return \"application/xml\";\n  if (name.endswith(\".resx\")) return \"application/xml\";\n  if (name.endswith(\".rmi\")) return \"audio/mid\";\n  if (name.endswith(\".rmp\")) return \"application/vnd.rn-rn_music_package\";\n  if (name.endswith(\".rqy\")) return \"text/x-ms-rqy\";\n  if (name.endswith(\".rtf\")) return \"application/msword\";\n  if (name.endswith(\".sct\")) return \"text/scriptlet\";\n  if (name.endswith(\".settings\")) return \"application/xml\";\n  if (name.endswith(\".shtml\")) return \"text/html\";\n  if (name.endswith(\".sit\")) return \"application/x-stuffit\";\n  if (name.endswith(\".sitemap\")) return \"application/xml\";\n  if (name.endswith(\".skin\")) return \"application/xml\";\n  if (name.endswith(\".sldm\")) return \"application/vnd.ms-powerpoint.slide.macroEnabled.12\";\n  if (name.endswith(\".sldx\")) return \"application/vnd.openxmlformats-officedocument.presentationml.slide\";\n  if (name.endswith(\".slk\")) return \"application/vnd.ms-excel\";\n  if (name.endswith(\".sln\")) return \"text/plain\";\n  if (name.endswith(\".slupkg-ms\")) return \"application/x-ms-license\";\n  if (name.endswith(\".snd\")) return \"audio/basic\";\n  if (name.endswith(\".snippet\")) return \"application/xml\";\n  if (name.endswith(\".spc\")) return \"application/x-pkcs7-certificates\";\n  if (name.endswith(\".sst\")) return \"application/vnd.ms-pki.certstore\";\n  if (name.endswith(\".stc\")) return \"application/vnd.sun.xml.calc.template\";\n  if (name.endswith(\".std\")) return \"application/vnd.sun.xml.draw.template\";\n  if (name.endswith(\".stl\")) return \"application/vnd.ms-pki.stl\";\n  if (name.endswith(\".stw\")) return \"application/vnd.sun.xml.writer.template\";\n  if (name.endswith(\".svg\")) return \"image/svg+xml\";\n  if (name.endswith(\".sxc\")) return \"application/vnd.sun.xml.calc\";\n  if (name.endswith(\".sxd\")) return \"application/vnd.sun.xml.draw\";\n  if (name.endswith(\".sxg\")) return \"application/vnd.sun.xml.writer.global\";\n  if (name.endswith(\".sxw\")) return \"application/vnd.sun.xml.writer\";\n  if (name.endswith(\".tga\")) return \"image/targa\";\n  if (name.endswith(\".thmx\")) return \"application/vnd.ms-officetheme\";\n  if (name.endswith(\".tif\")) return \"image/tiff\";\n  if (name.endswith(\".tiff\")) return \"image/tiff\";\n  if (name.endswith(\".torrent\")) return \"application/x-bittorrent\";\n  if (name.endswith(\".ts\")) return \"video/vnd.dlna.mpeg-tts\";\n  if (name.endswith(\".tts\")) return \"video/vnd.dlna.mpeg-tts\";\n  if (name.endswith(\".txt\")) return \"text/plain\";\n  if (name.endswith(\".user\")) return \"text/plain\";\n  if (name.endswith(\".vb\")) return \"text/plain\";\n  if (name.endswith(\".vbox\")) return \"application/x-virtualbox-vbox\";\n  if (name.endswith(\".vbox-extpack\")) return \"application/x-virtualbox-vbox-extpack\";\n  if (name.endswith(\".vbproj\")) return \"text/plain\";\n  if (name.endswith(\".vcf\")) return \"text/x-vcard\";\n  if (name.endswith(\".vdi\")) return \"application/x-virtualbox-vdi\";\n  if (name.endswith(\".vdp\")) return \"text/plain\";\n  if (name.endswith(\".vdproj\")) return \"text/plain\";\n  if (name.endswith(\".vhd\")) return \"application/x-virtualbox-vhd\";\n  if (name.endswith(\".vmdk\")) return \"application/x-virtualbox-vmdk\";\n  if (name.endswith(\".vor\")) return \"application/vnd.stardivision.writer\";\n  if (name.endswith(\".vscontent\")) return \"application/xml\";\n  if (name.endswith(\".vsi\")) return \"application/ms-vsi\";\n  if (name.endswith(\".vspolicy\")) return \"application/xml\";\n  if (name.endswith(\".vspolicydef\")) return \"application/xml\";\n  if (name.endswith(\".vspscc\")) return \"text/plain\";\n  if (name.endswith(\".vsscc\")) return \"text/plain\";\n  if (name.endswith(\".vssettings\")) return \"text/xml\";\n  if (name.endswith(\".vssscc\")) return \"text/plain\";\n  if (name.endswith(\".vstemplate\")) return \"text/xml\";\n  if (name.endswith(\".vsto\")) return \"application/x-ms-vsto\";\n  if (name.endswith(\".wal\")) return \"interface/x-winamp3-skin\";\n  if (name.endswith(\".wav\")) return \"audio/wav\";\n  if (name.endswith(\".wave\")) return \"audio/wav\";\n  if (name.endswith(\".wax\")) return \"audio/x-ms-wax\";\n  if (name.endswith(\".wbk\")) return \"application/msword\";\n  if (name.endswith(\".wdp\")) return \"image/vnd.ms-photo\";\n  if (name.endswith(\".website\")) return \"application/x-mswebsite\";\n  if (name.endswith(\".wiz\")) return \"application/msword\";\n  if (name.endswith(\".wlz\")) return \"interface/x-winamp-lang\";\n  if (name.endswith(\".wm\")) return \"video/x-ms-wm\";\n  if (name.endswith(\".wma\")) return \"audio/x-ms-wma\";\n  if (name.endswith(\".wmd\")) return \"application/x-ms-wmd\";\n  if (name.endswith(\".wmv\")) return \"video/x-ms-wmv\";\n  if (name.endswith(\".wmx\")) return \"video/x-ms-wmx\";\n  if (name.endswith(\".wmz\")) return \"application/x-ms-wmz\";\n  if (name.endswith(\".wpl\")) return \"application/vnd.ms-wpl\";\n  if (name.endswith(\".wsc\")) return \"text/scriptlet\";\n  if (name.endswith(\".wsdl\")) return \"application/xml\";\n  if (name.endswith(\".wsz\")) return \"interface/x-winamp-skin\";\n  if (name.endswith(\".wvx\")) return \"video/x-ms-wvx\";\n  if (name.endswith(\".xaml\")) return \"application/xaml+xml\";\n  if (name.endswith(\".xbap\")) return \"application/x-ms-xbap\";\n  if (name.endswith(\".xdp\")) return \"application/vnd.adobe.xdp+xml\";\n  if (name.endswith(\".xdr\")) return \"application/xml\";\n  if (name.endswith(\".xfdf\")) return \"application/vnd.adobe.xfdf\";\n  if (name.endswith(\".xht\")) return \"application/xhtml+xml\";\n  if (name.endswith(\".xhtml\")) return \"application/xhtml+xml\";\n  if (name.endswith(\".xla\")) return \"application/vnd.ms-excel\";\n  if (name.endswith(\".xlam\")) return \"application/vnd.ms-excel.addin.macroEnabled.12\";\n  if (name.endswith(\".xld\")) return \"application/vnd.ms-excel\";\n  if (name.endswith(\".xlk\")) return \"application/vnd.ms-excel\";\n  if (name.endswith(\".xll\")) return \"application/vnd.ms-excel\";\n  if (name.endswith(\".xlm\")) return \"application/vnd.ms-excel\";\n  if (name.endswith(\".xls\")) return \"application/vnd.ms-excel\";\n  if (name.endswith(\".xlsb\")) return \"application/vnd.ms-excel.sheet.binary.macroEnabled.12\";\n  if (name.endswith(\".xlsm\")) return \"application/vnd.ms-excel.sheet.macroEnabled.12\";\n  if (name.endswith(\".xlsx\")) return \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\";\n  if (name.endswith(\".xlt\")) return \"application/vnd.ms-excel\";\n  if (name.endswith(\".xltm\")) return \"application/vnd.ms-excel.template.macroEnabled.12\";\n  if (name.endswith(\".xltx\")) return \"application/vnd.openxmlformats-officedocument.spreadsheetml.template\";\n  if (name.endswith(\".xlw\")) return \"application/vnd.ms-excel\";\n  if (name.endswith(\".xml\")) return \"text/xml\";\n  if (name.endswith(\".xrm-ms\")) return \"text/xml\";\n  if (name.endswith(\".xsc\")) return \"application/xml\";\n  if (name.endswith(\".xsd\")) return \"application/xml\";\n  if (name.endswith(\".xsl\")) return \"text/xml\";\n  if (name.endswith(\".xslt\")) return \"application/xml\";\n  if (name.endswith(\".xss\")) return \"application/xml\";\n\n  // default is binary/octet\n  return \"binary/octet-stream\";\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nS3Handler::ParseHeader (eos::common::HttpRequest *request)\n{\n  HeaderMap header = request->GetHeaders();\n  std::string header_line;\n  for (auto it = header.begin(); it != header.end(); it++)\n  {\n    header_line += it->first;\n    header_line += \"=\";\n    header_line += it->second;\n    header_line += \" \";\n  }\n  eos_static_info(\"%s\", header_line.c_str());\n\n  if (header.count(\"authorization\"))\n  {\n    if (header[\"authorization\"].substr(0, 3) == \"AWS\")\n    {\n      // this is amanzon webservice authorization\n      mId = header[\"authorization\"].substr(4);\n      mSignature = mId;\n      size_t dpos = mId.find(\":\");\n      if (dpos != std::string::npos)\n      {\n        mId.erase(dpos);\n        mSignature.erase(0, dpos + 1);\n\n        mHttpMethod = request->GetMethod();\n\n        mPath = request->GetUrl();\n        std::string subdomain = SubDomain(header[\"host\"]);\n\n        if (subdomain.length())\n        {\n          // implementation for DNS buckets\n          mBucket = subdomain;\n          mVirtualHost = true;\n        }\n        else\n        {\n          mVirtualHost = false;\n          // implementation for non DNS buckets\n          mBucket = mPath;\n\n          if (mBucket[0] == '/')\n          {\n            mBucket.erase(0, 1);\n          }\n\n          size_t slash_pos = mBucket.find(\"/\");\n          if (slash_pos != std::string::npos)\n          {\n            // something like data/...\n\n            mPath = mBucket;\n            mPath.erase(0, slash_pos);\n            mBucket.erase(slash_pos);\n          }\n          else\n          {\n            mPath = \"/\";\n          }\n        }\n\n        mQuery = request->GetQuery();\n\n        if (header.count(\"content-md5\"))\n        {\n          mContentMD5 = header[\"content-md5\"];\n        }\n        if (header.count(\"date\"))\n        {\n          mDate = header[\"date\"];\n        }\n\n        if (header.count(\"content-type\"))\n        {\n          mContentType = header[\"content-type\"];\n        }\n\n        if (header.count(\"host\"))\n        {\n          mHost = header[\"host\"];\n        }\n        if (header.count(\"user-agent\"))\n        {\n          mUserAgent = header[\"user-agent\"];\n        }\n        // canonical amz header\n        for (auto it = header.begin(); it != header.end(); it++)\n        {\n          XrdOucString amzstring = it->first.c_str();\n          XrdOucString amzfield = it->second.c_str();\n          // make lower case\n          amzstring.lower(0);\n\n          if (!amzstring.beginswith(\"x-amz-\"))\n          {\n            // skip everything which is not amazon style\n            continue;\n          }\n          // trim white space in the beginning\n          while (amzfield.beginswith(\" \"))\n          {\n            amzfield.erase(0, 1);\n          }\n          int pos;\n          // remove line folding and spaces after folding\n          while ((pos = amzfield.find(\"\\r\\n \")) != STR_NPOS)\n          {\n            amzfield.erase(pos, 3);\n            while (amzfield[pos] == ' ')\n            {\n              amzfield.erase(pos, 1);\n            }\n          }\n          if (!mAmzMap.count(amzstring.c_str()))\n          {\n            mAmzMap[amzstring.c_str()] = amzfield.c_str();\n          }\n          else\n          {\n            mAmzMap[amzstring.c_str()] += \",\";\n            mAmzMap[amzstring.c_str()] += amzfield.c_str();\n          }\n        }\n        // build a canonicalized resource\n        for (auto it = mAmzMap.begin(); it != mAmzMap.end(); it++)\n        {\n          mCanonicalizedAmzHeaders += it->first;\n          mCanonicalizedAmzHeaders += \":\";\n          mCanonicalizedAmzHeaders += it->second;\n          mCanonicalizedAmzHeaders += \"\\n\";\n        }\n        mIsS3 = true;\n      }\n    }\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nS3Handler::IsS3 ()\n{\n  // Check if S3 object is complete\n  return mIsS3;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nS3Handler::Dump (std::string & out)\n{\n  // Print the S3 object contents to out\n  out = \"id=\";\n  out += mId.c_str();\n  out += \" \";\n  out += \"signature=\";\n  out += mSignature.c_str();\n  return;\n}\n\n/*----------------------------------------------------------------------------*/\nstd::string\nS3Handler::ExtractSubResource ()\n{\n  // Extract everything from the query which is a sub-resource aka used for\n  // signatures\n  std::vector<std::string> srvec;\n  eos::common::StringConversion::Tokenize(GetQuery(), srvec, \"&\");\n  for (auto it = srvec.begin(); it != srvec.end(); it++)\n  {\n    std::string key;\n    std::string value;\n    if (!eos::common::StringConversion::SplitKeyValue(*it, key, value))\n    {\n      // there are subresources without assigned value\n      key = *it;\n      value = \"\";\n    }\n\n    if ((key == \"acl\") ||\n        (key == \"lifecycle\") ||\n        (key == \"location\") ||\n        (key == \"logging\") ||\n        (key == \"delete\") ||\n        (key == \"notification\") ||\n        (key == \"uploads\") ||\n        (key == \"partNumber\") ||\n        (key == \"requestPayment\") ||\n        (key == \"uploadId\") ||\n        (key == \"versionId\") ||\n        (key == \"versioning\") ||\n        (key == \"versions\") ||\n        (key == \"website\") ||\n        (key == \"torrent\"))\n    {\n      mSubResourceMap[key] = value;\n    }\n  }\n  mSubResource = \"\";\n  for (auto it = mSubResourceMap.begin(); it != mSubResourceMap.end(); it++)\n  {\n    if (mSubResource.length())\n    {\n      mSubResource += \"&\";\n    }\n    mSubResource += it->first;\n    if (it->second.length())\n    {\n      mSubResource += \"=\";\n      mSubResource += it->second;\n    }\n  }\n  return mSubResource;\n}\n\n/*----------------------------------------------------------------------------*/\nstd::string\nS3Handler::SubDomain (std::string hostname)\n{\n  std::string subdomain = \"\";\n  size_t pos1 = hostname.rfind(\".\");\n  size_t pos2 = hostname.substr(0, pos1).rfind(\".\");\n  size_t pos3 = hostname.substr(0, pos2).rfind(\".\");\n\n  if ((pos1 != pos2) &&\n      (pos2 != pos3) &&\n      (pos1 != pos3) &&\n      (pos1 != std::string::npos) &&\n      (pos2 != std::string::npos) &&\n      (pos3 != std::string::npos))\n  {\n    subdomain = hostname;\n    subdomain.erase(pos3);\n  }\n\n  return subdomain;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nS3Handler::RestErrorResponse (int responseCode, std::string errorCode,\n  std::string errorMessage, std::string resource, std::string requestId)\n{\n  eos_static_info(\"Sending error code=%d error=%s msg=\\\"%s\\\" resource=%s\",\n                 responseCode, errorCode.c_str(), errorMessage.c_str(),\n                 resource.c_str());\n\n  eos::common::HttpResponse *response = new eos::common::S3Response();\n  response->SetResponseCode(responseCode);\n\n  std::string result = XML_V1_UTF8;\n  result += \"<Error><Code>\";\n  result += errorCode;\n  result += \"</Code>\";\n  result += \"<Message>\";\n  result += errorMessage;\n  result += \"</Message>\";\n  result += \"<Resource>\";\n  result += resource;\n  result += \"</Resource>\";\n  result += \"<RequestId>\";\n  result += requestId;\n  result += \"</RequestId>\";\n  result += \"</Error>\";\n\n  response->SetBody(result);\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/http/s3/S3Handler.hh",
    "content": "// ----------------------------------------------------------------------\n// File: S3Handler.hh\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   S3Handler.hh\n *\n * @brief  Dealing with all S3 goodies\n */\n\n#ifndef __EOSCOMMON_S3_HANDLER__HH__\n#define __EOSCOMMON_S3_HANDLER__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/http/HttpHandler.hh\"\n#include \"common/Namespace.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n#include <string>\n#include <map>\n/*----------------------------------------------------------------------------*/\n\nEOSCOMMONNAMESPACE_BEGIN\n\nclass S3Response;\n#define XML_V1_UTF8 \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\"\n\nclass S3Handler : virtual public eos::common::HttpHandler\n{\n\nprotected:\n  bool            mIsS3;           //!< indicates if this is a valid S3Handler object\n  std::string     mId;             //!< the S3Handler id of the client\n  std::string     mSignature;      //!< the S3Handler signature of the client\n  std::string     mHost;           //!< header host\n  std::string     mContentMD5;     //!< header MD5\n  std::string     mContentType;    //!< header content type\n  std::string     mUserAgent;      //!< header user agent\n  std::string     mHttpMethod;     //!< http method\n  std::string     mPath;           //!< http path\n  std::string     mQuery;          //!< http query\n  std::string     mSubResource;    //!< S3Handler sub resource\n  HeaderMap       mSubResourceMap; //!< map with S3Handler subresource key/vals\n  std::string     mBucket;         //!< http bucket\n  std::string     mDate;           //!< http date\n  HeaderMap       mAmzMap;         //!< canonical amz map\n  std::string     mCanonicalizedAmzHeaders; //!< canonical resource built from\n                                            //!< canonical amz map\n  bool            mVirtualHost;    //!< true if bucket name comes via virtual\n                                   //!< host, otherwise false (relevant for\n                                   //!< signature verification)\n\npublic:\n\n  /**\n   * Constructor\n   */\n  S3Handler () : mIsS3(false), mVirtualHost(false) {};\n\n  /**\n   * Destructor\n   */\n  virtual ~S3Handler () {};\n\n  /**\n   * Check whether the given method and headers are a match for this protocol.\n   *\n   * @param method  the request verb used by the client (GET, PUT, etc)\n   * @param headers the map of request headers\n   *\n   * @return true if the protocol matches, false otherwise\n   */\n  static bool\n  Matches (const std::string &method, HeaderMap &headers);\n\n  /**\n   * Build a response to the given S3 request.\n   *\n   * @param request  the client request object\n   */\n  void\n  HandleRequest (eos::common::HttpRequest *request) = 0;\n\n  /**\n   * Guess the default content type by suffix\n   *\n   * @param file path\n   * @return S3 content type string\n   */\n  static\n  std::string ContentType (const std::string &path);\n\n  /**\n   * Analyze the header map, searching for HTTP and Amazon headers\n   *\n   * @param request  the client request object\n   */\n  void\n  ParseHeader (eos::common::HttpRequest *request);\n\n  /**\n   * @return the client S3Handler ID\n   */\n  inline std::string\n  GetId () const { return mId; }\n\n  /**\n   * @return the client S3Handler signature\n   */\n  inline std::string\n  GetSignature () const { return mSignature; }\n\n  /**\n   * @return the client hostname\n   */\n  inline std::string\n  GetHost () const { return mHost; }\n\n  /**\n   * @return the md5 hash of the request content\n   */\n  inline std::string\n  GetContentMD5 () const { return mContentMD5; }\n\n  /**\n   * @return the request content type\n   */\n  inline std::string\n  GetContentType () const { return mContentType; }\n\n  /**\n   * @return the request user agent\n   */\n  inline std::string\n  GetUserAgent () const { return mUserAgent; }\n\n  /**\n   * @return the request method\n   */\n  inline std::string\n  GetHttpMethod () const { return mHttpMethod; }\n\n  /**\n   * @return the request path\n   */\n  inline std::string\n  GetPath () const { return mPath; }\n\n  /**\n   * @return the request query string\n   */\n  inline std::string\n  GetQuery () const { return mQuery; }\n\n  /**\n   * @return\n   */\n  inline std::string\n  GetSubResource () const { return mSubResource; }\n\n  /**\n   * @return the request sub respurce (used for signatures)\n   */\n  std::string\n  ExtractSubResource();\n\n  /**\n   * @return the requested bucket\n   */\n  inline std::string\n  GetBucket () const { return mBucket; }\n\n  /**\n   * @return the request date\n   */\n  inline std::string\n  GetDate () const { return mDate; }\n\n  /**\n   * @return the canonicalized amazon request headers\n   */\n  inline std::string\n  GetCanonicalizedAmzHeaders () const { return mCanonicalizedAmzHeaders; }\n\n  /**\n   * Check if the current S3 object is containing all the relevant S3 tags\n   *\n   * @return true if S3 headers have been provided otherwise false\n   */\n  bool IsS3 ();\n\n  /**\n   * Print the current S3 object\n   *\n   * TODO: change this to ToString()\n   */\n  void Dump (std::string &out);\n\n  /**\n   * @deprecated\n   *\n   * return rest respcode response string\n   * @param reponse_code set to http_code or respcode_code\n   * @param http_code to put for the response\n   * @param errcode as string\n   * @param errmsg as string\n   * @param resource as string\n   * @param requestid as string\n   * @return rest error response string\n   */\n  static std::string\n  RestErrorResponse (int        &response_code,\n                     int         http_code,\n                     std::string errcode,\n                     std::string errmsg,\n                     std::string resource,\n                     std::string requestid)\n  {\n    response_code = http_code;\n    std::string result = XML_V1_UTF8;\n    result += \"<Error><Code>\";\n    result += errcode;\n    result += \"</Code>\";\n    result += \"<Message>\";\n    result += errmsg;\n    result += \"</Message>\";\n    result += \"<Resource>\";\n    result += resource;\n    result += \"</Resource>\";\n    result += \"<RequestId>\";\n    result += requestid;\n    result += \"</RequestId>\";\n    result += \"</Error>\";\n    return result;\n  }\n\n  /**\n   * Create an S3 REST error response object\n   *\n   * @param responseCode  the error response code\n   * @param errorCode     error response code as string\n   * @param errorMessage  the error message to display\n   * @param resource      the requested resource as string\n   * @param requestId     the request id as string\n   *\n   * @return the S3 error response object\n   */\n  static eos::common::HttpResponse*\n  RestErrorResponse (int         responseCode,\n                     std::string errorCode,\n                     std::string errorMessage,\n                     std::string resource,\n                     std::string requestId);\n\n  /**\n   * Extract a subdomain name from the given hostname.\n   *\n   * @param hostname  from where to extract subdomain\n   *\n   * @return the extracted subdomain\n   */\n  std::string SubDomain (std::string hostname);\n};\n\n/*----------------------------------------------------------------------------*/\nEOSCOMMONNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/http/s3/S3Response.hh",
    "content": "// ----------------------------------------------------------------------\n// File: S3Response.hh\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   S3Response.hh\n *\n * @brief  TODO\n */\n\n#ifndef __EOSCOMMON_S3_RESPONSE__HH__\n#define __EOSCOMMON_S3_RESPONSE__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/http/HttpResponse.hh\"\n#include \"common/Namespace.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n#include <map>\n#include <string>\n/*----------------------------------------------------------------------------*/\n\nEOSCOMMONNAMESPACE_BEGIN\n\n#define XML_V1_UTF8 \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\"\n\nclass S3Response : public HttpResponse\n{\n\npublic:\n\n  HttpResponse*\n  BuildResponse (HttpRequest *request) { return this; };\n\n  /*----------------------------------------------------------------------------*/\n//  static std::string\n//  RestErrorResponse (int        &response_code,\n//                     int         http_code,\n//                     std::string errcode,\n//                     std::string errmsg,\n//                     std::string resource,\n//                     std::string requestid)\n//  {\n//    //.............................................................................\n//    // Creates a AWS RestError Response string\n//    //.............................................................................\n//    response_code = http_code;\n//    std::string result = XML_V1_UTF8;\n//    result += \"<Error><Code>\";\n//    result += errcode;\n//    result += \"</Code>\";\n//    result += \"<Message>\";\n//    result += errmsg;\n//    result += \"</Message>\";\n//    result += \"<Resource>\";\n//    result += resource;\n//    result += \"</Resource>\";\n//    result += \"<RequestId>\";\n//    result += requestid;\n//    result += \"</RequestId>\";\n//    result += \"</Error\";\n//    return result;\n//  }\n};\n\n/*----------------------------------------------------------------------------*/\nEOSCOMMONNAMESPACE_END\n\n#endif /* __EOSCOMMON_S3_RESPONSE__HH__ */\n"
  },
  {
    "path": "common/json/Json.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Json.hh\n// Author: Gianmaria Del Monte - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include <json/json.h>\n#include <type_traits>\n#include <atomic>\n#include <array>\n#include <vector>\n#include <set>\n#include <map>\n#include <unordered_map>\n#include <string>\n#include <sstream>\n\ntemplate <typename T>\nstd::enable_if_t<std::is_integral_v<T>, Json::Value> inline ConvertToJson(\n  const T& input);\n\ntemplate <typename T>\nstd::enable_if_t<std::is_floating_point_v<T>, Json::Value> inline ConvertToJson(\n  const T& input);\n\ntemplate <typename T>\ninline Json::Value ConvertToJson(const std::atomic<T>& input);\n\ntemplate <typename T, std::size_t N>\nJson::Value ConvertToJson(const std::array<T, N>& input);\n\ntemplate <typename T, std::size_t N>\nJson::Value ConvertToJson(const T(&input)[N]);\n\ntemplate <typename T>\nJson::Value ConvertToJson(const std::vector<T>& input);\n\ntemplate <typename T>\nJson::Value ConvertToJson(const std::set<T>& input);\n\ntemplate <typename K, typename V>\nJson::Value ConvertToJson(const std::map<K, V>& input);\n\ntemplate <typename K, typename V>\nJson::Value ConvertToJson(const std::unordered_map<K, V>& input);\n\ninline Json::Value ConvertToJson(const std::string& input);\n\ninline Json::Value ConvertToJson(const char* input);\n\ntemplate <typename T>\ninline void ConvertFromJson(const Json::Value& val, T& out);\n\ntemplate <typename T>\ninline void ConvertFromJson(const Json::Value& val, std::atomic<T>& out);\n\ntemplate <typename T, std::size_t N>\nvoid ConvertFromJson(const Json::Value& val, std::array<T, N>& out);\n\ntemplate <typename T, std::size_t N>\nvoid ConvertFromJson(const Json::Value& val, T(&out)[N]);\n\ntemplate <typename T>\nvoid ConvertFromJson(const Json::Value& val, std::vector<T>& out);\n\ntemplate <typename V>\nvoid ConvertFromJson(const Json::Value& val, std::set<V>& out);\n\ntemplate <typename K, typename V>\nvoid ConvertFromJson(const Json::Value& val, std::map<K, V>& out);\n\ntemplate <typename K, typename V>\nvoid ConvertFromJson(const Json::Value& val, std::unordered_map<K, V>& out);\n\n\ntemplate <typename T>\nstd::enable_if_t <\nstd::is_integral_v<T>,\n    Json::Value >\n    inline ConvertToJson(const T& input)\n{\n  if constexpr(std::is_signed_v<T>) {\n    return Json::Int64(input);\n  } else if constexpr(std::is_unsigned_v<T>) {\n    return Json::UInt64(input);\n  }\n}\n\ntemplate <typename T>\nstd::enable_if_t<std::is_floating_point_v<T>,\n    Json::Value>\n    inline ConvertToJson(const T& input)\n{\n  return Json::Value(input);\n}\n\ntemplate <typename T>\ninline Json::Value ConvertToJson(const std::atomic<T>& input)\n{\n  return ConvertToJson(input.load());\n}\n\ntemplate <typename T, std::size_t N>\nJson::Value ConvertToJson(const std::array<T, N>& input)\n{\n  Json::Value root(Json::arrayValue);\n\n  for (const auto& value : input) {\n    root.append(ConvertToJson(value));\n  }\n\n  return root;\n}\n\ntemplate <typename T, std::size_t N>\nJson::Value ConvertToJson(const T(&input)[N])\n{\n  Json::Value root(Json::arrayValue);\n\n  for (const auto& value : input) {\n    root.append(ConvertToJson(value));\n  }\n\n  return root;\n}\n\ntemplate <typename T>\nJson::Value ConvertToJson(const std::vector<T>& input)\n{\n  Json::Value root(Json::arrayValue);\n\n  for (const auto& value : input) {\n    root.append(ConvertToJson(value));\n  }\n\n  return root;\n}\n\ntemplate <typename T>\nJson::Value ConvertToJson(const std::set<T>& input)\n{\n  Json::Value root(Json::arrayValue);\n\n  for (const auto& val : input) {\n    root.append(ConvertToJson(val));\n  }\n\n  return root;\n}\n\ntemplate <typename K, typename V>\nJson::Value ConvertToJson(const std::map<K, V>& input)\n{\n  Json::Value root;\n\n  for (const auto& pair : input) {\n    if constexpr(std::is_same_v<K, std::string>) {\n      root[pair.first] = ConvertToJson(pair.second);\n    } else {\n      root[std::to_string(pair.first)] = ConvertToJson(pair.second);\n    }\n  }\n\n  return root;\n}\n\ntemplate <typename K, typename V>\nJson::Value ConvertToJson(const std::unordered_map<K, V>& input)\n{\n  Json::Value root;\n\n  for (const auto& pair : input) {\n    root[std::to_string(pair.first)] = ConvertToJson(pair.second);\n  }\n\n  return root;\n}\n\ninline Json::Value ConvertToJson(const std::string& input)\n{\n  return Json::Value(input);\n}\n\ninline Json::Value ConvertToJson(const char* input)\n{\n  return Json::Value(input);\n}\n\ntemplate <typename T>\ninline void ConvertFromJson(const Json::Value& val, T& out)\n{\n  if constexpr(std::is_signed_v<T>) {\n    out = static_cast<T>(val.asInt64());\n  } else if constexpr(std::is_unsigned_v<T>) {\n    out = static_cast<T>(val.asUInt64());\n  } else if constexpr(std::is_floating_point_v<T>) {\n    out = static_cast<T>(val.asDouble());\n  }\n}\n\n\ntemplate <typename T>\ninline void ConvertFromJson(const Json::Value& val, std::atomic<T>& out)\n{\n  T v;\n  ConvertFromJson(val, v);\n  out = std::move(v);\n}\n\ntemplate <typename T, std::size_t N>\nvoid ConvertFromJson(const Json::Value& val, std::array<T, N>& out)\n{\n  for (std::size_t i = 0; i < N; i++) {\n    ConvertFromJson(val[Json::ArrayIndex(i)], out[i]);\n  }\n}\n\ntemplate <typename T, std::size_t N>\nvoid ConvertFromJson(const Json::Value& val, T(&out)[N])\n{\n  for (std::size_t i = 0; i < N; i++) {\n    auto v = val[Json::ArrayIndex(i)];\n    ConvertFromJson(v, out[i]);\n  }\n}\n\ntemplate <typename T>\nvoid ConvertFromJson(const Json::Value& val, std::vector<T>& out)\n{\n  out.clear();\n  out.reserve(val.size());\n\n  for (Json::ArrayIndex i = 0; i < val.size(); i++) {\n    T v;\n    ConvertFromJson(val[i], v);\n    out.emplace_back(std::move(v));\n  }\n}\n\ntemplate <typename V>\nvoid ConvertFromJson(const Json::Value& val, std::set<V>& out)\n{\n  out.clear();\n\n  for (Json::ArrayIndex i = 0; i < val.size(); i++) {\n    V v;\n    ConvertFromJson(val[i], v);\n    out.emplace(std::move(v));\n  }\n}\n\ntemplate <typename K, typename V>\nvoid ConvertFromJson(const Json::Value& val, std::map<K, V>& out)\n{\n  for (const auto& key : val.getMemberNames()) {\n    K map_key;\n    V map_value;\n    std::istringstream(key) >> map_key;\n    auto v = val[key];\n    ConvertFromJson(v, map_value);\n    out.emplace(std::move(map_key), std::move(map_value));\n  }\n}\n\ntemplate <typename K, typename V>\nvoid ConvertFromJson(const Json::Value& val, std::unordered_map<K, V>& out)\n{\n  for (const auto& key : val.getMemberNames()) {\n    K map_key;\n    V map_value;\n    std::istringstream(key) >> map_key;\n    ConvertFromJson(val[key], map_value);\n    out.emplace(std::move(map_key), std::move(map_value));\n  }\n}\n\n/**\n * @brief Serializes an object into a JSON string.\n *\n * @tparam T The type of the object to serialize.\n * @param input The object to be serialized.\n * @param indentation (Optional) Indentation string for pretty-printing. Defaults to no indentation.\n * @return A string containing the JSON representation of the object.\n */\ntemplate <typename T>\nstd::string Marshal(const T& input, const char* indentation = \"\")\n{\n  Json::Value root = ConvertToJson(input);\n  Json::StreamWriterBuilder writer;\n  writer[\"indentation\"] = indentation;\n  return Json::writeString(writer, root);\n}\n\n/**\n * @brief Deserializes a JSON string into an object.\n *\n * @tparam T The type of the object to deserialize into.\n * @param input The JSON string to deserialize.\n * @param out The object where the deserialized data will be stored.\n */\ntemplate <typename T>\nvoid Unmarshal(const std::string input, T& out)\n{\n  Json::Value root;\n  std::istringstream(input) >> root;\n  ConvertFromJson(root, out);\n}\n"
  },
  {
    "path": "common/json/JsonCppJsonifier.hh",
    "content": "// ----------------------------------------------------------------------\n// File: JsonCppJsonifier.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_JSONCPPJSONIFIER_HH\n#define EOS_JSONCPPJSONIFIER_HH\n\n#include \"common/Namespace.hh\"\n#include \"common/json/Jsonifier.hh\"\n#include <sstream>\n#include <memory>\n#include <json/json.h>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n/**\n * Inherit this interface in order to implement\n * the way to generate the json representation of any object using the JsonCPP library\n * @tparam Obj the Object you want to generate the json representation from\n */\ntemplate <typename Obj>\nclass JsonCppJsonifier : public virtual common::Jsonifier<Obj>\n{\npublic:\n  /**\n   * Implement this method to generate the json representation of any object\n   * @param object the object from which you want the json representation\n   * @param ss the stream where this json representation will be pushed to\n   */\n  virtual void jsonify(const Obj* obj, std::stringstream& oss) = 0;\nprotected:\n  inline virtual void initializeArray(Json::Value& value)\n  {\n    value = Json::Value(Json::arrayValue);\n  }\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif // EOS_JSONCPPJSONIFIER_HH\n"
  },
  {
    "path": "common/json/Jsonifiable.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Jsonifiable.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_JSONIFIABLE_HH\n#define EOS_JSONIFIABLE_HH\n\n#include \"mgm/Namespace.hh\"\n#include <memory>\n#include <sstream>\n#include \"common/json/Jsonifier.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n\n/**\n * Common class allowing any object inheriting from this class\n * can give its json representation\n * You can implement the jsonify() method by hand\n * or set a custom jsonifier before calling jsonify()\n * @tparam Object the object to give its json representation\n */\ntemplate<typename Object>\nclass Jsonifiable\n{\npublic:\n  inline void setJsonifier(std::shared_ptr<common::Jsonifier<Object>> jsonifier)\n  {\n    mJsonifier = jsonifier;\n  }\n  virtual void jsonify(std::stringstream& ss) const\n  {\n    if (mJsonifier) {\n      Object* thisptr = const_cast<Object*>(static_cast<const Object*>(this));\n      mJsonifier->jsonify(thisptr, ss);\n    }\n  }\n  virtual ~Jsonifiable() {}\nprotected:\n  std::shared_ptr<common::Jsonifier<Object>> mJsonifier;\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif // EOS_JSONIFIABLE_HH\n"
  },
  {
    "path": "common/json/Jsonifier.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Jsonifier.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_JSONIFIER_HH\n#define EOS_JSONIFIER_HH\n\n#include \"common/Namespace.hh\"\n#include <sstream>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n/**\n * Inherit this interface in order to implement\n * the way to generate the json representation of any object\n * @tparam Obj the Object you want to generate the json representation from\n */\ntemplate<typename Obj>\nclass Jsonifier\n{\npublic:\n  /**\n   * Implement this method to generate the json representation of any object\n   * @param object the object from which you want the json representation\n   * @param ss the stream where this json representation will be pushed to\n   */\n  virtual void jsonify(const Obj* object, std::stringstream& ss) = 0;\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif // EOS_JSONIFIER_HH\n"
  },
  {
    "path": "common/mq/FsChangeListener.cc",
    "content": "// ----------------------------------------------------------------------\n// File: FsChangeListener.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/mq/FsChangeListener.hh\"\n#include \"common/mq/MessagingRealm.hh\"\n\nEOSMQNAMESPACE_BEGIN\n\nstd::string FsChangeListener::sAllMatchTag = \"*\";\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFsChangeListener::FsChangeListener(mq::MessagingRealm* realm,\n                                   const std::string& name)\n  : mMessagingRealm(realm), mListenerName(name)\n{}\n\n//------------------------------------------------------------------------------\n// Subscribe to the given key, such as \"stat.errc\" or \"stat.geotag\"\n//------------------------------------------------------------------------------\nbool\nFsChangeListener::subscribe(const std::string& key)\n{\n  eos::common::RWMutexWriteLock wr_lock(mMutexMap);\n  mMapInterests[sAllMatchTag].insert(key);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Subscribe to the given channel and key combination - MUST NOT be used\n// directly but only from FileSystem::AttachFsListener\n//------------------------------------------------------------------------------\nbool\nFsChangeListener::subscribe(const std::string& channel,\n                            const std::set<std::string>& keys)\n{\n  eos::common::RWMutexWriteLock wr_lock(mMutexMap);\n  auto resp = mMapInterests.emplace(channel, std::set<std::string>());\n  auto& set_keys = resp.first->second;\n  set_keys.insert(keys.begin(), keys.end());\n  return true;\n}\n\n//----------------------------------------------------------------------------\n// Unsubscribe from the given channel and key combination - MUST NOT be used\n// directly but only from FileSystem::DetachFsListener\n//----------------------------------------------------------------------------\nbool\nFsChangeListener::unsubscribe(const std::string& channel,\n                              const std::set<std::string>& keys)\n{\n  eos::common::RWMutexWriteLock wr_lock(mMutexMap);\n  auto it = mMapInterests.find(channel);\n  \n  if (it != mMapInterests.end()) {\n    for (const auto& key : keys) {\n      it->second.erase(key);\n    }\n    \n    if (it->second.empty()) {\n      mMapInterests.erase(it);\n    }\n  }\n  \n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Check if current listener is interested in updates from the given\n// channel. Return set of keys that listener is interested in.\n//------------------------------------------------------------------------------\nstd::set<std::string>\nFsChangeListener::GetInterests(const std::string& channel) const\n{\n  std::set<std::string> keys;\n  eos::common::RWMutexReadLock rd_lock(mMutexMap);\n  // Check if this listener is interested in some updates from all channels\n  auto it = mMapInterests.find(sAllMatchTag);\n\n  if (it != mMapInterests.end()) {\n    keys.insert(it->second.begin(), it->second.end());\n  }\n\n  // Check lister has some special interests in this particular channel\n  it = mMapInterests.find(channel);\n\n  if (it != mMapInterests.end()) {\n    keys.insert(it->second.begin(), it->second.end());\n  }\n\n  return keys;\n}\n\n//------------------------------------------------------------------------------\n// Consume next event, block until there's one\n//------------------------------------------------------------------------------\nbool FsChangeListener::fetch(ThreadAssistant& assistant, Event& out,\n                             std::chrono::seconds timeout)\n{\n  return WaitForEvent(out, timeout);\n}\n\n//------------------------------------------------------------------------------\n// Check if given event is interesting for the current listener given its\n// interests\n//------------------------------------------------------------------------------\nbool\nFsChangeListener::IsEventInteresting(const Event& event) const\n{\n  const std::set<std::string> key_interest = GetInterests(event.fileSystemQueue);\n\n  if (key_interest.find(event.key) != key_interest.cend()) {\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Notify new event\n//------------------------------------------------------------------------------\nvoid\nFsChangeListener::NotifyEvent(const Event& event)\n{\n  if (IsEventInteresting(event)) {\n    {\n      std::lock_guard lock(mMutex);\n      mPendingEvents.emplace_back(event);\n    }\n    mCv.notify_one();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Waiting at most timout seconds for an event\n//------------------------------------------------------------------------------\nbool\nFsChangeListener::WaitForEvent(Event& out, std::chrono::seconds timeout)\n{\n  std::unique_lock lock(mMutex);\n\n  if (mPendingEvents.empty()) {\n    if (!mCv.wait_for(lock, timeout, [&] {return !mPendingEvents.empty();})) {\n      return false;\n    }\n  }\n\n  out = mPendingEvents.front();\n  mPendingEvents.pop_front();\n  return true;\n}\n\nEOSMQNAMESPACE_END\n"
  },
  {
    "path": "common/mq/FsChangeListener.hh",
    "content": "// ----------------------------------------------------------------------\n// File: FsChangeListener.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/mq/Namespace.hh\"\n#include \"common/RWMutex.hh\"\n#include <string>\n#include <map>\n#include <set>\n#include <list>\n#include <mutex>\n#include <condition_variable>\n\n//! Forward declarations\nclass ThreadAssistant;\n\nnamespace eos\n{\nnamespace mq\n{\nclass MessagingRealm;\n}\n}\n\nEOSMQNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Utility class listening for FileSystem changes\n//------------------------------------------------------------------------------\nclass FsChangeListener\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Event struct, containing things like FileSystem name, and key changed\n  //----------------------------------------------------------------------------\n  struct Event {\n    std::string fileSystemQueue;\n    std::string key;\n    bool deletion = false;\n\n    bool isDeletion() const\n    {\n      return deletion;\n    }\n  };\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param realm messaging realm\n  //! @param name listener name\n  //----------------------------------------------------------------------------\n  FsChangeListener(mq::MessagingRealm* realm, const std::string& name);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~FsChangeListener() = default;\n\n  //----------------------------------------------------------------------------\n  //! Subscribe to the given key, such as \"stat.errc\" or \"stat.geotag\" for\n  //! existing and future file systems\n  //!\n  //! @param key interested update key\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool subscribe(const std::string& key);\n\n  //----------------------------------------------------------------------------\n  //! Subscribe to the given channel and key combination - MUST not be used\n  //! directly but only from mgm::FileSystem::AttachFsListener\n  //!\n  //! @param fs file system object\n  //! @param channel file system identifier\n  //! @param key set of interesting keys for the current listener\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool subscribe(const std::string& channel, const std::set<std::string>& key);\n\n  //----------------------------------------------------------------------------\n  //! Unsubscribe from the given channel and key combination - MUST not be used\n  //! directly but only from mgm::FileSystem::AttachFsListener\n  //!\n  //! @param channel file system identifier\n  //! @param key set of keys from which to unsubscribe\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool unsubscribe(const std::string& channel, const std::set<std::string>& key);\n\n  //----------------------------------------------------------------------------\n  //! Check if current listener is interested in updates from the given\n  //! channel. Return set of keys that listener is interested in.\n  //!\n  //! @param channel file system identifier\n  //!\n  //! @return set of keys that the listener is interested in or empty\n  //----------------------------------------------------------------------------\n  std::set<std::string> GetInterests(const std::string& channel) const;\n\n  //----------------------------------------------------------------------------\n  //! Consume next event, block until there's one or timeout expires\n  //!\n  //! @param assistant thread executing this method\n  //! @param out new event\n  //! @param timeout max time we're willing to wait, default 5\n  //!\n  //! @return true if there is an event, otherwise false\n  //----------------------------------------------------------------------------\n  bool fetch(ThreadAssistant& assistant, Event& out,\n             std::chrono::seconds timeout = std::chrono::seconds(5));\n\n  //----------------------------------------------------------------------------\n  //! Notify new event\n  //!\n  //! @param event new event object\n  //----------------------------------------------------------------------------\n  void NotifyEvent(const Event& event);\n\n  //----------------------------------------------------------------------------\n  //! Get name of the current listener\n  //!\n  //! @return listener name\n  //----------------------------------------------------------------------------\n  inline std::string GetName() const\n  {\n    return mListenerName;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get number of pending events\n  //----------------------------------------------------------------------------\n  uint64_t GetNumPendingEvents() const\n  {\n    std::lock_guard lock(mMutex);\n    return mPendingEvents.size();\n  }\n\nprivate:\n  static std::string sAllMatchTag;\n  mq::MessagingRealm* mMessagingRealm;\n  std::string mListenerName;\n  mutable std::mutex mMutex;\n  std::condition_variable mCv;\n  std::list<Event> mPendingEvents;\n  //! Mutex protecting access to mMapInterests\n  mutable eos::common::RWMutex mMutexMap;\n  //! Map of channel to set of interest keys\n  std::map<std::string, std::set<std::string>> mMapInterests;\n\n  //----------------------------------------------------------------------------\n  //! Waiting at most timout seconds for an event\n  //!\n  //! @param out update event\n  //! @param timeout max time we're willing to wait\n  //!\n  //! @return true if there was an event, otherwise false\n  //----------------------------------------------------------------------------\n  bool WaitForEvent(Event& out,\n                    std::chrono::seconds timeout = std::chrono::seconds(5));\n\n  //----------------------------------------------------------------------------\n  //! Check if given event is interesting for the current listener given the\n  //! keys that it has subscribed with (i.e. interests)\n  //!\n  //! @param event new event object\n  //!\n  //! @return true if event is interesting, otherwise false\n  //----------------------------------------------------------------------------\n  bool IsEventInteresting(const Event& event) const;\n};\n\nEOSMQNAMESPACE_END\n"
  },
  {
    "path": "common/mq/GlobalConfigChangeListener.cc",
    "content": "// ----------------------------------------------------------------------\n// File: GlobalConfigChangeListener.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/mq/GlobalConfigChangeListener.hh\"\n#include \"common/mq/MessagingRealm.hh\"\n#include \"common/Locators.hh\"\n\n#include <qclient/shared/SharedHashSubscription.hh>\n#include <qclient/shared/SharedHash.hh>\n\nEOSMQNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nGlobalConfigChangeListener::GlobalConfigChangeListener(mq::MessagingRealm*\n    realm, const std::string& name, const std::string& configQueue)\n  : mMessagingRealm(realm), mSubscription(nullptr)\n{\n  using namespace std::placeholders;\n  mSharedHash = mMessagingRealm->getHashProvider()->Get(\n                  eos::common::SharedHashLocator::makeForGlobalHash());\n  mSubscription = mSharedHash->subscribe(true);\n  mSubscription->attachCallback(std::bind(\n                                  &GlobalConfigChangeListener::ProcessUpdateCb, this, _1));\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nGlobalConfigChangeListener::~GlobalConfigChangeListener()\n{\n  if (mSubscription) {\n    mSubscription->detachCallback();\n  }\n}\n\n//----------------------------------------------------------------------------\n// Callback to process update for the shared hash\n//----------------------------------------------------------------------------\nvoid\nGlobalConfigChangeListener::ProcessUpdateCb(qclient::SharedHashUpdate&& upd)\n{\n  {\n    std::lock_guard lock(mMutex);\n    mPendingUpdates.emplace_back(upd);\n  }\n  mCv.notify_one();\n}\n\n//------------------------------------------------------------------------------\n// Block waiting for an event\n//------------------------------------------------------------------------------\nbool\nGlobalConfigChangeListener::WaitForEvent(Event& out,\n    std::chrono::seconds timeout)\n{\n  std::unique_lock lock(mMutex);\n\n  if (mPendingUpdates.empty()) {\n    if (!mCv.wait_for(lock, timeout, [&] {return !mPendingUpdates.empty();})) {\n      return false;\n    }\n  }\n\n  auto update = mPendingUpdates.front();\n  mPendingUpdates.pop_front();\n  lock.unlock();\n  out.key = update.key;\n  out.deletion = update.value.empty();\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Consume next event, block until there's one\n//------------------------------------------------------------------------------\nbool GlobalConfigChangeListener::fetch(ThreadAssistant& assistant, Event& out)\n{\n  return WaitForEvent(out);\n}\n\nEOSMQNAMESPACE_END\n"
  },
  {
    "path": "common/mq/GlobalConfigChangeListener.hh",
    "content": "// ----------------------------------------------------------------------\n// File: GlobalConfigChangeListener.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_MQ_GLOBAL_CONFIG_CHANGE_LISTENER_HH\n#define EOS_MQ_GLOBAL_CONFIG_CHANGE_LISTENER_HH\n\n#include \"common/mq/Namespace.hh\"\n#include <string>\n#include <memory>\n#include <list>\n#include <mutex>\n#include <condition_variable>\n\nnamespace qclient\n{\nclass SharedHash;\nclass SharedHashSubscription;\nstruct SharedHashUpdate;\n}\n\nclass ThreadAssistant;\n\nEOSMQNAMESPACE_BEGIN\n\nclass MessagingRealm;\n\n//------------------------------------------------------------------------------\n//! Utility class for listening to global MGM configuration changes.\n//------------------------------------------------------------------------------\nclass GlobalConfigChangeListener\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Event struct\n  //----------------------------------------------------------------------------\n  struct Event {\n    std::string key;\n    bool deletion = false;\n\n    bool isDeletion() const\n    {\n      return deletion;\n    }\n  };\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  GlobalConfigChangeListener(mq::MessagingRealm* realm, const std::string& name,\n                             const std::string& configQueue);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~GlobalConfigChangeListener();\n\n  //----------------------------------------------------------------------------\n  //! Consume next event, block until there's one.\n  //----------------------------------------------------------------------------\n  bool fetch(ThreadAssistant& assistant, Event& out);\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Callback to process update for the shared hash\n  //!\n  //! @param upd SharedHashUpdate object\n  //----------------------------------------------------------------------------\n  void ProcessUpdateCb(qclient::SharedHashUpdate&& upd);\n\n  //----------------------------------------------------------------------------\n  //! Waiting at most timout seconds for an event\n  //!\n  //! @param out update event\n  //! @param timeout max time we're willing to wait\n  //!\n  //! @return true if there was an event, otherwise false\n  //----------------------------------------------------------------------------\n  bool WaitForEvent(Event& out,\n                    std::chrono::seconds timeout = std::chrono::seconds(5));\n\n  mq::MessagingRealm* mMessagingRealm;\n  std::shared_ptr<qclient::SharedHash> mSharedHash;\n  std::unique_ptr<qclient::SharedHashSubscription> mSubscription;\n  std::mutex mMutex;\n  std::condition_variable mCv;\n  std::list<qclient::SharedHashUpdate> mPendingUpdates;\n};\n\nEOSMQNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/mq/LocalHash.cc",
    "content": "//------------------------------------------------------------------------------\n// File: LocalHash.cc\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/mq/LocalHash.hh\"\n#include \"qclient/shared/UpdateBatch.hh\"\n\nEOSMQNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nLocalHash::LocalHash(const std::string& key):\n  qclient::SharedHash(nullptr, key),\n  mKey(key)\n{\n  // empty\n}\n\n//------------------------------------------------------------------------------\n// Set value\n//------------------------------------------------------------------------------\nstd::future<qclient::redisReplyPtr>\nLocalHash::set(const qclient::UpdateBatch& batch)\n{\n  std::promise<qclient::redisReplyPtr> promise;\n  auto future = promise.get_future();\n  promise.set_value(qclient::redisReplyPtr());\n  std::unique_lock<std::mutex> lock(mMutex);\n\n  for (const auto& batch_map : {\n         batch.getLocal(),\n         batch.getTransient(),\n         batch.getPersistent()\n       }) {\n    for (auto it = batch_map.cbegin(); it != batch_map.cend(); ++it) {\n      // This is actually a delete key\n      if (it->second.empty()) {\n        auto it_main = mMap.find(it->first);\n\n        if (it_main != mMap.end()) {\n          mMap.erase(it_main);\n        }\n      } else {\n        // This is a real set key=val\n        mMap[it->first] = it->second;\n      }\n    }\n  }\n  return future;\n}\n\n//------------------------------------------------------------------------------\n// Get value\n//------------------------------------------------------------------------------\nbool\nLocalHash::get(const std::string& key, std::string& value) const\n{\n  std::unique_lock<std::mutex> lock(mMutex);\n  auto it = mMap.find(key);\n\n  if (it != mMap.end()) {\n    value = it->second;\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Get a list of values, returns a map of kv pairs of found values, expects\n// empty map as the out param, returns true if all the values have been found\n//------------------------------------------------------------------------------\nbool\nLocalHash::get(const std::vector<std::string>& keys,\n               std::map<std::string, std::string>& out) const\n{\n  if (!out.empty()) {\n    return false;\n  }\n\n  std::unique_lock<std::mutex> lock(mMutex);\n\n  for (const auto& key : keys) {\n    auto it = mMap.find(key);\n\n    if (it != mMap.end()) {\n      out.emplace(it->first, it->second);\n    }\n  }\n\n  return (keys.size() == out.size());\n}\n\n//------------------------------------------------------------------------------\n// Get the set of keys in the current hash\n//------------------------------------------------------------------------------\nstd::vector<std::string>\nLocalHash::getKeys() const\n{\n  std::vector<std::string> keys;\n  std::unique_lock<std::mutex> lock(mMutex);\n\n  for (const auto& elem : mMap) {\n    keys.push_back(elem.first);\n  }\n\n  return keys;\n}\n\n//------------------------------------------------------------------------------\n// Get contents of the hash\n//------------------------------------------------------------------------------\nstd::map<std::string, std::string>\nLocalHash::getContents() const\n{\n  std::unique_lock<std::mutex> lock(mMutex);\n  return mMap;\n}\n\nEOSMQNAMESPACE_END\n"
  },
  {
    "path": "common/mq/LocalHash.hh",
    "content": "//------------------------------------------------------------------------------\n// File: LocalHash.hh\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/mq/Namespace.hh\"\n#include \"qclient/shared/SharedHash.hh\"\n#include \"qclient/shared/PersistentSharedHash.hh\"\n#include \"qclient/shared/TransientSharedHash.hh\"\n#include \"qclient/Reply.hh\"\n#include <map>\n#include <string>\n#include <mutex>\n\n//! Forward declarations\nnamespace qclient\n{\nclass UpdateBatch;\n}\n\nEOSMQNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Hash that stores the key values locally\n//------------------------------------------------------------------------------\nclass LocalHash: public qclient::SharedHash\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  LocalHash(const std::string& key);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~LocalHash() = default;\n\n  //----------------------------------------------------------------------------\n  //! Set value\n  //----------------------------------------------------------------------------\n  std::future<qclient::redisReplyPtr>\n  set(const qclient::UpdateBatch& batch) override;\n\n  //----------------------------------------------------------------------------\n  //! Get value\n  //----------------------------------------------------------------------------\n  bool get(const std::string& key, std::string& value) const override;\n\n  //----------------------------------------------------------------------------\n  //! Get a list of values, returns a map of kv pairs of found values, expects\n  //! empty map as the out param, returns true if all the values have been found\n  //!\n  //! @param keys vector of string keys\n  //! @param out empty map, which will be populated\n  //!\n  //! @return true if all keys were found, false otherwise or in case of\n  //! non empty map\n  //----------------------------------------------------------------------------\n  bool get(const std::vector<std::string>& keys,\n           std::map<std::string, std::string>& out) const override;\n\n  //----------------------------------------------------------------------------\n  //! Get the set of keys in the current hash\n  //!\n  //! @return set of keys in the hash, or empty if none\n  //----------------------------------------------------------------------------\n  std::vector<std::string> getKeys() const override;\n\n  //----------------------------------------------------------------------------\n  //! Get contents of the hash\n  //!\n  //! @return map of the key value pairs\n  //----------------------------------------------------------------------------\n  std::map<std::string, std::string> getContents() const override;\n\nprivate:\n  std::string mKey;\n  mutable std::mutex mMutex;\n  std::map<std::string, std::string> mMap;\n};\n\nEOSMQNAMESPACE_END\n"
  },
  {
    "path": "common/mq/MessagingRealm.cc",
    "content": "// ----------------------------------------------------------------------\n// File: MessagingRealm.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <qclient/QClient.hh>\n#include <qclient/shared/SharedManager.hh>\n#include <qclient/ResponseParsing.hh>\n#include \"common/Logging.hh\"\n#include \"common/mq/MessagingRealm.hh\"\n#include \"common/mq/FsChangeListener.hh\"\n\nEOSMQNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nMessagingRealm::MessagingRealm(qclient::SharedManager* qsom)\n\n  : mQSom(qsom), mHashProvider(qsom), mDequeProvider(qsom)\n{}\n\n//------------------------------------------------------------------------------\n// Get qclient shared manager\n//------------------------------------------------------------------------------\nqclient::SharedManager* MessagingRealm::getQSom() const\n{\n  return mQSom;\n}\n\n//------------------------------------------------------------------------------\n// Get pointer to hash provider\n//------------------------------------------------------------------------------\nSharedHashProvider* MessagingRealm::getHashProvider()\n{\n  return &mHashProvider;\n}\n\n//------------------------------------------------------------------------------\n// Get pointer to deque provider\n//------------------------------------------------------------------------------\nSharedDequeProvider* MessagingRealm::getDequeProvider()\n{\n  return &mDequeProvider;\n}\n\n//------------------------------------------------------------------------------\n//! Send message to the given receiver queue\n//------------------------------------------------------------------------------\nMessagingRealm::Response\nMessagingRealm::sendMessage(const std::string& descr,\n                            const std::string& payload,\n                            const std::string& receiver, bool is_monitor)\n{\n  Response resp;\n  // The reply to publish is the number of subscribers that receive the msg\n  qclient::redisReplyPtr reply = mQSom->getQClient()->exec(\"PUBLISH\", receiver,\n                                 payload).get();\n\n  if (reply->type == REDIS_REPLY_INTEGER) {\n    resp.status = (reply->integer == 0 ? 1 : 0);\n  } else {\n    resp.status = 1;\n  }\n\n  return resp;\n}\n\n//------------------------------------------------------------------------------\n// Set instance name\n//------------------------------------------------------------------------------\nbool MessagingRealm::setInstanceName(const std::string& name)\n{\n  qclient::QClient* qcl = mQSom->getQClient();\n  qclient::redisReplyPtr reply = qcl->exec(\"SET\", \"eos-instance-name\",\n                                 name).get();\n  qclient::StatusParser parser(reply);\n\n  if (!parser.ok()) {\n    eos_static_crit(\"error while setting instance name in QDB: %s\",\n                    parser.err().c_str());\n    return false;\n  }\n\n  if (parser.value() != \"OK\") {\n    eos_static_crit(\"unexpected response while setting instance name in QDB: %s\",\n                    parser.value().c_str());\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Get instance name\n//------------------------------------------------------------------------------\nbool MessagingRealm::getInstanceName(std::string& name)\n{\n  qclient::QClient* qcl = mQSom->getQClient();\n  qclient::redisReplyPtr reply = qcl->exec(\"GET\", \"eos-instance-name\").get();\n  qclient::StringParser parser(reply);\n\n  if (!parser.ok()) {\n    return false;\n  }\n\n  name = parser.value();\n\n  if (name.empty()) {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Get FsChange listener with given name\n//------------------------------------------------------------------------------\nstd::shared_ptr<FsChangeListener>\nMessagingRealm::GetFsChangeListener(const std::string& name)\n{\n  {\n    eos::common::RWMutexReadLock rd_lock(mMutexListeners);\n    auto it = mFsListeners.find(name);\n\n    if (it != mFsListeners.end()) {\n      return it->second;\n    }\n  }\n  eos::common::RWMutexWriteLock wr_lock(mMutexListeners);\n  auto it = mFsListeners.find(name);\n\n  if (it == mFsListeners.end()) {\n    mFsListeners[name] = std::make_shared<FsChangeListener>(this, name);\n  }\n\n  return mFsListeners[name];\n}\n\n//------------------------------------------------------------------------------\n// Get map of listeners and the keys they are interested in for the given\n// channel i.e. file system queue path\n//------------------------------------------------------------------------------\nstd::map<std::shared_ptr<FsChangeListener>, std::set<std::string>>\n    MessagingRealm::GetInterestedListeners(const std::string& channel)\n{\n  std::map<std::shared_ptr<FsChangeListener>,\n      std::set<std::string>> map_interest;\n  eos::common::RWMutexReadLock rd_lock(mMutexListeners);\n\n  for (auto& elem : mFsListeners) {\n    auto& listener = elem.second;\n    std::set<std::string> interested_keys = listener->GetInterests(channel);\n\n    if (!interested_keys.empty()) {\n      map_interest.emplace(listener, std::move(interested_keys));\n    }\n  }\n\n  return map_interest;\n}\n\n//------------------------------------------------------------------------------\n// Enable broadcasts\n//------------------------------------------------------------------------------\nvoid\nMessagingRealm::EnableBroadcast()\n{\n  mBroadcast = true;\n}\n\n//------------------------------------------------------------------------------\n// Disable broadcasts\n//------------------------------------------------------------------------------\nvoid\nMessagingRealm::DisableBroadcast()\n{\n  mBroadcast = false;\n}\n\nEOSMQNAMESPACE_END\n"
  },
  {
    "path": "common/mq/MessagingRealm.hh",
    "content": "// ----------------------------------------------------------------------\n// File: MessagingRealm.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/mq/Namespace.hh\"\n#include \"common/mq/SharedHashProvider.hh\"\n#include \"common/mq/SharedDequeProvider.hh\"\n#include \"common/RWMutex.hh\"\n#include <string>\n#include <set>\n\n//! Forward declarations\nnamespace qclient\n{\nclass SharedManager;\n}\n\nEOSMQNAMESPACE_BEGIN\n\n//! Forward declaration\nclass FsChangeListener;\n\n//------------------------------------------------------------------------------\n//! Class allowing contact with a specified messaging realm.\n//! Can be either legacy MQ, or QDB.\n//!\n//! Work in progress.\n//------------------------------------------------------------------------------\nclass MessagingRealm\n{\npublic:\n  struct Response {\n    int status;\n    std::string response;\n\n    bool ok() const\n    {\n      return status == 0;\n    }\n  };\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  MessagingRealm(qclient::SharedManager* qsom);\n\n  //----------------------------------------------------------------------------\n  //! Get qclient shared manager\n  //----------------------------------------------------------------------------\n  qclient::SharedManager* getQSom() const;\n\n  //----------------------------------------------------------------------------\n  //! Get pointer to hash provider\n  //----------------------------------------------------------------------------\n  SharedHashProvider* getHashProvider();\n\n  //----------------------------------------------------------------------------\n  //! Get pointer to deque provider\n  //----------------------------------------------------------------------------\n  SharedDequeProvider* getDequeProvider();\n\n  //----------------------------------------------------------------------------\n  //! Send message to the given receiver queue\n  //----------------------------------------------------------------------------\n  Response sendMessage(const std::string& descr, const std::string& payload,\n                       const std::string& receiver, bool is_monitor = false);\n\n  //----------------------------------------------------------------------------\n  //! Set instance name\n  //----------------------------------------------------------------------------\n  bool setInstanceName(const std::string& name);\n\n  //----------------------------------------------------------------------------\n  //! Get instance name\n  //----------------------------------------------------------------------------\n  bool getInstanceName(std::string& name);\n\n  //----------------------------------------------------------------------------\n  //! Get FsChange listener with given name\n  //!\n  //! @param name name of the file system change listner\n  //!\n  //! @return FsChangeListener object\n  //----------------------------------------------------------------------------\n  std::shared_ptr<FsChangeListener>\n  GetFsChangeListener(const std::string& name);\n\n  //----------------------------------------------------------------------------\n  //! Get map of listeners and the keys they are interested in for the given\n  //! channel i.e. file system queue path\n  //!\n  //! @param channel channel name i.e. file system queue path\n  //!\n  //! @return map of listeners and their interest in the current channel\n  //----------------------------------------------------------------------------\n  std::map<std::shared_ptr<FsChangeListener>, std::set<std::string>>\n      GetInterestedListeners(const std::string& channel);\n\n  //----------------------------------------------------------------------------\n  //! Enable broadcasts\n  //----------------------------------------------------------------------------\n  void EnableBroadcast();\n\n  //----------------------------------------------------------------------------\n  //! Disable broadcasts\n  //----------------------------------------------------------------------------\n  void DisableBroadcast();\n\n  //----------------------------------------------------------------------------\n  //! Check if broadcasts are enabled\n  //----------------------------------------------------------------------------\n  inline bool ShouldBroadcast()\n  {\n    return mBroadcast.load();\n  }\n\n\nprivate:\n  //! Flag to mark when broadcasting should be done\n  std::atomic<bool> mBroadcast {false};\n  qclient::SharedManager* mQSom;\n  SharedHashProvider mHashProvider;\n  SharedDequeProvider mDequeProvider;\n  eos::common::RWMutex mMutexListeners;\n  std::map<std::string, std::shared_ptr<FsChangeListener>> mFsListeners;\n};\n\nEOSMQNAMESPACE_END\n"
  },
  {
    "path": "common/mq/Namespace.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Namespace.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   Namespace.hh\n *\n * @brief  Macros defining eos:mq namespace.\n *\n *\n */\n\n\n/*----------------------------------------------------------------------------*/\n//! Macros defining the MQ namespace\n/*----------------------------------------------------------------------------*/\n\n#ifndef __EOSMQ_NAMESPACE_HH__\n#define __EOSMQ_NAMESPACE_HH__\n\n#define EOSMQNAMESPACE_BEGIN   namespace eos { /** @namespace eos project namespace*/  namespace mq { /** @namespace @brief mq classes*/\n\n#define EOSMQNAMESPACE_END }}\n\n#endif\n"
  },
  {
    "path": "common/mq/QdbListener.cc",
    "content": "//------------------------------------------------------------------------------\n// File: QdbListener.hh\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/mq/QdbListener.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"qclient/pubsub/Message.hh\"\n\nEOSMQNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nQdbListener::QdbListener(eos::QdbContactDetails& qdb_details,\n                         const std::string& channel):\n  mSubscriber(qdb_details.members, qdb_details.constructSubscriptionOptions())\n{\n  using namespace std::placeholders;\n  mSubscription = mSubscriber.subscribe(channel);\n  mSubscription->attachCallback(std::bind(\n                                  &QdbListener::ProcessUpdateCb,\n                                  this, _1));\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nQdbListener::~QdbListener()\n{\n  if (mSubscription) {\n    mSubscription->detachCallback();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Callback to process message\n//------------------------------------------------------------------------------\nvoid\nQdbListener::ProcessUpdateCb(qclient::Message&& msg)\n{\n  {\n    std::lock_guard lock(mMutex);\n    mPendingUpdates.emplace_back(msg);\n  }\n  mCv.notify_one();\n}\n\n//------------------------------------------------------------------------------\n// Fetch error report\n//------------------------------------------------------------------------------\nbool\nQdbListener::fetch(std::string& out, ThreadAssistant* assistant)\n{\n  ThreadAssistant::setSelfThreadName(\"QdbListener\");\n  std::chrono::seconds timeout {5};\n  std::unique_lock lock(mMutex);\n\n  if (mPendingUpdates.empty()) {\n    if (!mCv.wait_for(lock, timeout, [&] {return !mPendingUpdates.empty();})) {\n      return false;\n    }\n  }\n\n  auto msg = mPendingUpdates.front();\n  mPendingUpdates.pop_front();\n  lock.unlock();\n  out = msg.getPayload();\n\n  if (out.empty()) {\n    return false;\n  }\n\n  return true;\n}\n\nEOSMQNAMESPACE_END\n"
  },
  {
    "path": "common/mq/QdbListener.hh",
    "content": "//------------------------------------------------------------------------------\n// File: QdbListener.hh\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/mq/Namespace.hh\"\n#include \"qclient/pubsub/Subscriber.hh\"\n#include <list>\n#include <string>\n#include <mutex>\n#include <condition_variable>\n\n//! Forward declarations\nclass ThreadAssistant;\n\nnamespace eos\n{\nclass QdbContactDetails;\n}\n\nnamespace qclient\n{\nclass QClient;\nclass Message;\nclass Subscriber;\nclass Subscription;\n}\n\nEOSMQNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Helper class for listening to error report messages sent through QDB\n//------------------------------------------------------------------------------\nclass QdbListener\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param qdb details QDB contact details\n  //! @param channel subscription channel for receiving messages\n  //----------------------------------------------------------------------------\n  QdbListener(eos::QdbContactDetails& qdb_details, const std::string& channel);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~QdbListener();\n\n  //----------------------------------------------------------------------------\n  //! Fetch error report\n  //!\n  //! @param out recived message\n  //! @oaram assistant thread running method\n  //----------------------------------------------------------------------------\n  bool fetch(std::string& out, ThreadAssistant* assistant = nullptr);\n\nprivate:\n  qclient::Subscriber mSubscriber; ///< Subscriber to notifications\n  //! Subscription to channel\n  std::unique_ptr<qclient::Subscription> mSubscription;\n  std::mutex mMutex;\n  std::condition_variable mCv;\n  std::list<qclient::Message> mPendingUpdates;\n\n  //----------------------------------------------------------------------------\n  //! Callback to process message\n  //!\n  //! @param msg subscription message\n  //----------------------------------------------------------------------------\n  void ProcessUpdateCb(qclient::Message&& msg);\n};\n\nEOSMQNAMESPACE_END\n"
  },
  {
    "path": "common/mq/SharedDequeProvider.cc",
    "content": "// ----------------------------------------------------------------------\n// File: SharedDequeProvider.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/mq/SharedDequeProvider.hh\"\n#include <qclient/shared/SharedDeque.hh>\n\nEOSMQNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nSharedDequeProvider::SharedDequeProvider(qclient::SharedManager* manager)\n  : mSharedManager(manager) {}\n\n//------------------------------------------------------------------------------\n// Get shared deque\n//------------------------------------------------------------------------------\nstd::shared_ptr<qclient::SharedDeque> SharedDequeProvider::get(\n  const std::string& key)\n{\n  std::unique_lock lock(mMutex);\n  auto it = mStore.find(key);\n\n  if (it != mStore.end()) {\n    return it->second;\n  }\n\n  std::shared_ptr<qclient::SharedDeque> deque;\n  deque.reset(new qclient::SharedDeque(mSharedManager, key));\n  mStore[key] = deque;\n  return deque;\n}\n\nEOSMQNAMESPACE_END\n"
  },
  {
    "path": "common/mq/SharedDequeProvider.hh",
    "content": "// ----------------------------------------------------------------------\n// File: SharedDequeProvider.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_MQ_SHARED_DEQUE_PROVIDER_HH\n#define EOS_MQ_SHARED_DEQUE_PROVIDER_HH\n\n#include \"common/mq/Namespace.hh\"\n#include <memory>\n#include <map>\n#include <mutex>\n\nnamespace qclient\n{\nclass SharedManager;\nclass SharedDeque;\n}\n\nEOSMQNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class to keep ownership of qclient SharedDeques\n//------------------------------------------------------------------------------\nclass SharedDequeProvider\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  SharedDequeProvider(qclient::SharedManager* manager);\n\n  //----------------------------------------------------------------------------\n  //! Get shared deque\n  //----------------------------------------------------------------------------\n  std::shared_ptr<qclient::SharedDeque> get(const std::string& key);\n\nprivate:\n  qclient::SharedManager* mSharedManager;\n\n  std::mutex mMutex;\n  std::map<std::string, std::shared_ptr<qclient::SharedDeque>> mStore;\n\n};\n\nEOSMQNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/mq/SharedHashProvider.cc",
    "content": "// ----------------------------------------------------------------------\n// File: SharedHashProvider.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/mq/SharedHashProvider.hh\"\n#include \"common/mq/LocalHash.hh\"\n#include \"common/Locators.hh\"\n#include \"qclient/QClient.hh\"\n#include \"qclient/shared/SharedHash.hh\"\n#include \"qclient/shared/SharedManager.hh\"\n\nEOSMQNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nSharedHashProvider::SharedHashProvider(qclient::SharedManager* manager)\n  : mSharedManager(manager) {}\n\n//------------------------------------------------------------------------------\n// Get shared hash\n//------------------------------------------------------------------------------\nstd::shared_ptr<qclient::SharedHash>\nSharedHashProvider::Get(const eos::common::SharedHashLocator& locator)\n{\n  const std::string& key = locator.getQDBKey();\n  std::unique_lock lock(mMutex);\n  auto it = mStore.find(key);\n\n  if (it != mStore.end()) {\n    return it->second;\n  }\n\n  using eos::common::SharedHashLocator;\n  std::shared_ptr<qclient::SharedHash> hash;\n\n  if ((locator.getType() == SharedHashLocator::Type::kSpace) ||\n      ((locator.getType() == SharedHashLocator::Type::kGroup))) {\n    hash.reset(new LocalHash(key));\n  } else {\n    hash.reset(new qclient::SharedHash(mSharedManager, key));\n  }\n\n  mStore[key] = hash;\n  return hash;\n}\n\n//------------------------------------------------------------------------------\n// Delete shared hash\n//------------------------------------------------------------------------------\nvoid\nSharedHashProvider::Delete(const eos::common::SharedHashLocator& locator,\n                           bool delete_from_qdb)\n{\n  const std::string qdb_key = locator.getQDBKey();\n  std::unique_lock lock(mMutex);\n  auto it = mStore.find(qdb_key);\n\n  if (it != mStore.end()) {\n    mStore.erase(it);\n  }\n\n  if (delete_from_qdb) {\n    qclient::QClient* qcl = mSharedManager->getQClient();\n\n    if (qcl) {\n      qcl->del(qdb_key);\n    }\n  }\n}\n\nEOSMQNAMESPACE_END\n"
  },
  {
    "path": "common/mq/SharedHashProvider.hh",
    "content": "// ----------------------------------------------------------------------\n// File: SharedHashProvider.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_MQ_SHARED_HASH_PROVIDER_HH\n#define EOS_MQ_SHARED_HASH_PROVIDER_HH\n\n#include \"common/mq/Namespace.hh\"\n#include <memory>\n#include <map>\n#include <mutex>\n\nnamespace qclient\n{\nclass SharedManager;\nclass SharedHash;\n}\n\nnamespace eos\n{\nnamespace common\n{\nclass SharedHashLocator;\n}\n}\n\nEOSMQNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class to keep ownership of qclient SharedHashes\n//------------------------------------------------------------------------------\nclass SharedHashProvider\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  SharedHashProvider(qclient::SharedManager* manager);\n\n  //----------------------------------------------------------------------------\n  //! Get shared hash\n  //----------------------------------------------------------------------------\n  std::shared_ptr<qclient::SharedHash>\n  Get(const eos::common::SharedHashLocator& locator);\n\n  //----------------------------------------------------------------------------\n  //! Delete shared hash\n  //!\n  //! @param locator locator object for the given hash\n  //! @param delete_from_qdb if true delete the backing SharedHash from QDB\n  //----------------------------------------------------------------------------\n  void Delete(const eos::common::SharedHashLocator& locator,\n              bool delete_from_qdb);\n\nprivate:\n  qclient::SharedManager* mSharedManager;\n\n  std::mutex mMutex;\n  std::map<std::string, std::shared_ptr<qclient::SharedHash>> mStore;\n\n};\n\nEOSMQNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/mq/SharedHashWrapper.cc",
    "content": "// ----------------------------------------------------------------------\n// File: SharedHashWrapper.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/mq/SharedHashWrapper.hh\"\n#include \"common/mq/MessagingRealm.hh\"\n#include \"common/ParseUtils.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/Locators.hh\"\n#include <qclient/shared/SharedHash.hh>\n#include <qclient/shared/UpdateBatch.hh>\n#include <qclient/shared/SharedHashSubscription.hh>\n\nEOSMQNAMESPACE_BEGIN\n\nstatic std::string LOCAL_PREFIX = \"local.\";\n\n//------------------------------------------------------------------------------\n// Set value, detect based on prefix whether it should be durable,\n// transient, or local\n//------------------------------------------------------------------------------\nvoid SharedHashWrapper::Batch::Set(const std::string& key,\n                                   const std::string& value)\n{\n  if (common::startsWith(key, LOCAL_PREFIX)) {\n    SetLocal(key, value);\n  } else if (common::startsWith(key, \"stat.\")) {\n    SetTransient(key, value);\n  } else {\n    SetDurable(key, value);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Set durable value\n//------------------------------------------------------------------------------\nvoid SharedHashWrapper::Batch::SetDurable(const std::string& key,\n    const std::string& value)\n{\n  mDurableUpdates[key] = value;\n}\n\n//------------------------------------------------------------------------------\n// Set transient value\n//------------------------------------------------------------------------------\nvoid SharedHashWrapper::Batch::SetTransient(const std::string& key,\n    const std::string& value)\n{\n  mTransientUpdates[key] = value;\n}\n\n//------------------------------------------------------------------------------\n// Set local value\n//------------------------------------------------------------------------------\nvoid SharedHashWrapper::Batch::SetLocal(const std::string& key,\n                                        const std::string& value)\n{\n  mLocalUpdates[key] = value;\n}\n\n//------------------------------------------------------------------------------\n// Constructor SharedHashWrapper\n//------------------------------------------------------------------------------\nSharedHashWrapper::SharedHashWrapper(mq::MessagingRealm* realm,\n                                     const common::SharedHashLocator& locator,\n                                     bool takeLock, bool create)\n  : mLocator(locator)\n{\n  mSharedHash = realm->getHashProvider()->Get(locator);\n}\n\n//------------------------------------------------------------------------------\n// Subscribe for updates from the underlying hash\n//------------------------------------------------------------------------------\nstd::unique_ptr<qclient::SharedHashSubscription>\nSharedHashWrapper::subscribe()\n{\n  if (mSharedHash) {\n    return mSharedHash->subscribe();\n  }\n\n  return nullptr;\n}\n\n//------------------------------------------------------------------------------\n// Make global MGM hash\n//------------------------------------------------------------------------------\nSharedHashWrapper\nSharedHashWrapper::makeGlobalMgmHash(mq::MessagingRealm* realm)\n{\n  return SharedHashWrapper(realm, common::SharedHashLocator::makeForGlobalHash());\n}\n\n//------------------------------------------------------------------------------\n// Set key-value pair\n//------------------------------------------------------------------------------\nbool SharedHashWrapper::set(const std::string& key, const std::string& value,\n                            bool broadcast)\n{\n  Batch batch;\n  batch.Set(key, value);\n  return set(batch);\n}\n\n//------------------------------------------------------------------------------\n// Set key-value batch\n//------------------------------------------------------------------------------\nbool SharedHashWrapper::set(const Batch& batch)\n{\n  if (!mSharedHash) {\n    return false;\n  }\n\n  qclient::UpdateBatch updateBatch;\n\n  for (auto it = batch.mDurableUpdates.begin();\n       it != batch.mDurableUpdates.end(); it++) {\n    updateBatch.setDurable(it->first, it->second);\n  }\n\n  for (auto it = batch.mTransientUpdates.begin();\n       it != batch.mTransientUpdates.end(); it++) {\n    updateBatch.setTransient(it->first, it->second);\n  }\n\n  for (auto it = batch.mLocalUpdates.begin();\n       it != batch.mLocalUpdates.end(); it++) {\n    updateBatch.setLocal(it->first, it->second);\n  }\n\n  std::future<qclient::redisReplyPtr> reply = mSharedHash->set(updateBatch);\n  reply.wait();\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Query the given key, return if retrieval successful\n//------------------------------------------------------------------------------\nbool SharedHashWrapper::get(const std::string& key, std::string& value)\n{\n  if (mSharedHash) {\n    return mSharedHash->get(key, value);\n  } else {\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Query the given key\n//------------------------------------------------------------------------------\nstd::string SharedHashWrapper::get(const std::string& key)\n{\n  std::string retval;\n  bool outcome = this->get(key, retval);\n\n  if (!outcome) {\n    return \"\";\n  }\n\n  return retval;\n}\n\n//------------------------------------------------------------------------------\n// Query the given key - convert to long long automatically\n//------------------------------------------------------------------------------\nlong long SharedHashWrapper::getLongLong(const std::string& key)\n{\n  return eos::common::ParseLongLong(get(key));\n}\n\n//----------------------------------------------------------------------------\n// Query the given key - convert to double automatically\n//----------------------------------------------------------------------------\ndouble SharedHashWrapper::getDouble(const std::string& key)\n{\n  return eos::common::ParseDouble(get(key));\n}\n\n//------------------------------------------------------------------------------\n// Query the given key, return if retrieval successful\n//------------------------------------------------------------------------------\nbool\nSharedHashWrapper::get(const std::vector<std::string>& keys,\n                       std::map<std::string, std::string>& values)\n{\n  if (mSharedHash) {\n    return mSharedHash->get(keys, values);\n  } else {\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Delete the given key\n//------------------------------------------------------------------------------\nbool SharedHashWrapper::del(const std::string& key, bool broadcast)\n{\n  if (mSharedHash) {\n    qclient::UpdateBatch updateBatch;\n\n    if (common::startsWith(key, \"stat.\")) {\n      updateBatch.setTransient(key, \"\");\n    } else if (common::startsWith(key, \"local.\")) {\n      updateBatch.setLocal(key, \"\");\n    } else {\n      updateBatch.setDurable(key, \"\");\n    }\n\n    std::future<qclient::redisReplyPtr> reply = mSharedHash->set(updateBatch);\n    reply.wait();\n    return true;\n  } else {\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get all keys in hash\n//------------------------------------------------------------------------------\nbool SharedHashWrapper::getKeys(std::vector<std::string>& out)\n{\n  if (mSharedHash) {\n    out = mSharedHash->getKeys();\n    return true;\n  } else {\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get all hash contents as a map\n//------------------------------------------------------------------------------\nbool SharedHashWrapper::getContents(std::map<std::string, std::string>& out)\n{\n  if (mSharedHash) {\n    out = mSharedHash->getContents();\n    return true;\n  } else {\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Delete a shared hash, without creating an object first\n//------------------------------------------------------------------------------\nbool\nSharedHashWrapper::deleteHash(mq::MessagingRealm* realm,\n                              const common::SharedHashLocator& locator,\n                              bool delete_from_qdb)\n{\n  if (realm->getQSom()) { // QDB backend\n    realm->getHashProvider()->Delete(locator, delete_from_qdb);\n    return true;\n  } else {\n    eos_static_crit(\"msg=\\\"no shared object manager\\\" locator=\\\"%s\\\"\",\n                    locator.getConfigQueue().c_str());\n    return false;\n  }\n}\n\nEOSMQNAMESPACE_END\n"
  },
  {
    "path": "common/mq/SharedHashWrapper.hh",
    "content": "// ----------------------------------------------------------------------\n// File: SharedHashWrapper.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_MQ_SHARED_HASH_WRAPPER_HH\n#define EOS_MQ_SHARED_HASH_WRAPPER_HH\n\n#include \"common/mq/Namespace.hh\"\n#include \"common/Locators.hh\"\n#include \"common/RWMutex.hh\"\n#include <string>\n#include <vector>\n#include <map>\n#include <memory>\n\nnamespace qclient\n{\nclass UpdateBatch;\nclass SharedHash;\nclass SharedHashSubscription;\n}\n\nEOSMQNAMESPACE_BEGIN\n\nclass MessagingRealm;\n\n//------------------------------------------------------------------------------\n//! Compatibility class for shared hashes - work in progress.\n//------------------------------------------------------------------------------\nclass SharedHashWrapper\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Update batch object\n  //----------------------------------------------------------------------------\n  class Batch\n  {\n  public:\n    //--------------------------------------------------------------------------\n    //! Constructor\n    //--------------------------------------------------------------------------\n    Batch() {}\n\n    //--------------------------------------------------------------------------\n    //! Set value, detect based on prefix whether it should be durable,\n    //! transient, or local\n    //--------------------------------------------------------------------------\n    void Set(const std::string& key, const std::string& value);\n\n    //--------------------------------------------------------------------------\n    //! Set durable value\n    //--------------------------------------------------------------------------\n    void SetDurable(const std::string& key, const std::string& value);\n\n    //--------------------------------------------------------------------------\n    //! Set transient value\n    //--------------------------------------------------------------------------\n    void SetTransient(const std::string& key, const std::string& value);\n\n    //--------------------------------------------------------------------------\n    //! Set local value\n    //--------------------------------------------------------------------------\n    void SetLocal(const std::string& key, const std::string& value);\n\n  private:\n    friend class SharedHashWrapper;\n    std::map<std::string, std::string> mDurableUpdates;\n    std::map<std::string, std::string> mTransientUpdates;\n    std::map<std::string, std::string> mLocalUpdates;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Delete a shared hash, without creating an object first\n  //!\n  //! @param realm messaging realm instance\n  //! @param locator hash locator\n  //! @param delete_from_qdb by default true and triggers the deletion of the\n  //!        SharedHash object from QDB\n  //----------------------------------------------------------------------------\n  static bool deleteHash(mq::MessagingRealm* realm,\n                         const common::SharedHashLocator& locator,\n                         bool delete_from_qdb = true);\n\n  //----------------------------------------------------------------------------\n  //! \"Constructor\" for global MGM hash\n  //----------------------------------------------------------------------------\n  static SharedHashWrapper makeGlobalMgmHash(mq::MessagingRealm* realm);\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  SharedHashWrapper(mq::MessagingRealm* realm,\n                    const common::SharedHashLocator& locator,\n                    bool takeLock = true, bool create = true);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~SharedHashWrapper() = default;\n\n  //----------------------------------------------------------------------------\n  //! Release any interal locks - DO NOT use this object any further\n  //----------------------------------------------------------------------------\n  void releaseLocks();\n\n  //----------------------------------------------------------------------------\n  //! Set key-value pair\n  //----------------------------------------------------------------------------\n  bool set(const std::string& key, const std::string& value,\n           bool broadcast = true);\n\n  //----------------------------------------------------------------------------\n  //! Set key-value batch\n  //----------------------------------------------------------------------------\n  bool set(const Batch& batch);\n\n  //----------------------------------------------------------------------------\n  //! Query the given key\n  //----------------------------------------------------------------------------\n  std::string get(const std::string& key);\n\n  //----------------------------------------------------------------------------\n  //! Query the given key - convert to long long automatically\n  //----------------------------------------------------------------------------\n  long long getLongLong(const std::string& key);\n\n  //----------------------------------------------------------------------------\n  //! Query the given key - convert to double automatically\n  //----------------------------------------------------------------------------\n  double getDouble(const std::string& key);\n\n  //----------------------------------------------------------------------------\n  //! Query the given key, return if retrieval successful\n  //----------------------------------------------------------------------------\n  bool get(const std::string& key, std::string& value);\n\n  //----------------------------------------------------------------------------\n  //! Query the given key, return if retrieval successful\n  //----------------------------------------------------------------------------\n  bool get(const std::vector<std::string>& keys,\n           std::map<std::string, std::string>& value);\n\n  //----------------------------------------------------------------------------\n  //! Delete the given key\n  //----------------------------------------------------------------------------\n  bool del(const std::string& key, bool broadcast = true);\n\n  //----------------------------------------------------------------------------\n  //! Get all keys in hash\n  //----------------------------------------------------------------------------\n  bool getKeys(std::vector<std::string>& out);\n\n  //----------------------------------------------------------------------------\n  //! Get all hash contents as a map\n  //----------------------------------------------------------------------------\n  bool getContents(std::map<std::string, std::string>& out);\n\n  //----------------------------------------------------------------------------\n  //! Subscribe for updates from the underlying hash\n  //----------------------------------------------------------------------------\n  std::unique_ptr<qclient::SharedHashSubscription> subscribe();\n\nprivate:\n  common::SharedHashLocator mLocator;\n  std::shared_ptr<qclient::SharedHash> mSharedHash;\n};\n\nEOSMQNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "common/mq/XrdMqTiming.hh",
    "content": "// ----------------------------------------------------------------------\n// File: XrdMqTiming.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __MQ__TIMING__HH__\n#define __MQ__TIMING__HH__\n\n#include <XrdOuc/XrdOucString.hh>\n#include <sys/time.h>\n#include <string.h>\n\nclass XrdMqTiming\n{\npublic:\n  struct timeval tv;\n  XrdOucString tag;\n  XrdOucString maintag;\n  XrdMqTiming* next;\n  XrdMqTiming* ptr;\n\n  XrdMqTiming(const char* name, struct timeval& i_tv):\n    tv{0}\n  {\n    memcpy(&tv, &i_tv, sizeof(struct timeval));\n    tag = name;\n    next = 0;\n    ptr  = this;\n  }\n  XrdMqTiming(const char* i_maintag):\n    tv{0}\n  {\n    tag = \"BEGIN\";\n    next = 0;\n    ptr  = this;\n    maintag = i_maintag;\n  }\n\n  void Print()\n  {\n    char msg[512];\n    XrdMqTiming* p = this->next;\n    XrdMqTiming* n;\n    std::cerr << std::endl;\n\n    while (p && (n = p->next)) {\n      sprintf(msg,\n              \"                                        [%12s] %12s<=>%-12s : %.03f\\n\",\n              maintag.c_str(), p->tag.c_str(), n->tag.c_str(),\n              (float)((n->tv.tv_sec - p->tv.tv_sec) * 1000000 +\n                      (n->tv.tv_usec - p->tv.tv_usec)) / 1000.0);\n      std::cerr << msg;\n      p = n;\n    }\n\n    n = p;\n    p = this->next;\n    sprintf(msg,\n            \"                                        =%12s= %12s<=>%-12s : %.03f\\n\",\n            maintag.c_str(), p->tag.c_str(), n->tag.c_str(),\n            (float)((n->tv.tv_sec - p->tv.tv_sec) * 1000000 + (n->tv.tv_usec -\n                    p->tv.tv_usec)) / 1000.0);\n    std::cerr << msg;\n  }\n\n  virtual ~XrdMqTiming()\n  {\n    XrdMqTiming* n = next;\n\n    if (n) {\n      delete n;\n    }\n  };\n};\n\n#define TIMING( __ID__,__LIST__)                                        \\\n  do {                                                                  \\\n    struct timeval tp;                                                  \\\n    struct timezone tz;                                                 \\\n    gettimeofday(&tp, &tz);                                             \\\n    (__LIST__)->ptr->next=new XrdMqTiming(__ID__,tp);                   \\\n    (__LIST__)->ptr = (__LIST__)->ptr->next;                            \\\n  } while(0);                                                           \\\n\n#endif\n"
  },
  {
    "path": "common/mutextest/RWMutexTest.cc",
    "content": "// ----------------------------------------------------------------------\n// File: RWMutexTest.cc\n// Author: Geoffray Adde - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2012 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//----------------------------------------------------------------------------\n// Program demonstrating the timing facilities of the RWMutex class\n//----------------------------------------------------------------------------\n#include \"common/RWMutex.hh\"\n#include \"common/Timing.hh\"\n#include <iostream>\n#include <thread>\n\nusing namespace eos::common;\nconst int loopsize = 10e6;\nconst unsigned long int NNUM_THREADS = 10;\npthread_t threads[NNUM_THREADS];\nunsigned long int NUM_THREADS = NNUM_THREADS;\nRWMutex globmutex;\nRWMutex gm1, gm2, gm3;\n\n\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n//----------------------------------------------------------------------------\n//! Output to stream operator for TimingStats structure\n//!\n//! @param os output strream\n//! @param stats structure to serialize\n//!\n//! @return reference to output stream\n//----------------------------------------------------------------------------\ninline std::ostream& operator << (std::ostream& os,\n                                  const RWMutex::TimingStats& stats)\n{\n  os << \"\\t\" << \"RWMutex Read  Wait (number : min , avg , max)\" << \" = \"\n     << stats.readLockCounterSample << \" : \" << stats.minwaitread << \" , \"\n     << stats.averagewaitread << \" , \" << stats.maxwaitread << std::endl\n     << \"\\t\" << \"RWMutex Write Wait (number : min , avg , max)\" << \" = \"\n     << stats.writeLockCounterSample << \" : \" << stats.minwaitwrite << \" , \"\n     << stats.averagewaitwrite << \" , \" << stats.maxwaitwrite << std::endl;\n  return os;\n}\n\ntypedef void* (*TestFuncT)(void*);\n\n//----------------------------------------------------------------------------\n// Test function ran by a thread\n//----------------------------------------------------------------------------\nvoid*\nTestThread(void* threadid)\n{\n  unsigned long int tid = *(unsigned long int*)threadid;\n\n  if (tid % 2) {\n    for (int k = 0; k < loopsize / (int) NUM_THREADS; k++) {\n      globmutex.LockWrite();\n      globmutex.UnLockWrite();\n    }\n  } else {\n    for (int k = 0; k < loopsize / (int) NUM_THREADS; k++) {\n      globmutex.LockRead();\n      globmutex.UnLockRead();\n    }\n  }\n\n  pthread_exit(NULL);\n  return NULL;\n}\n\n//----------------------------------------------------------------------------\n// Function to run all threads\n//----------------------------------------------------------------------------\nvoid\nRunThreads(TestFuncT func)\n{\n  for (unsigned long int t = 0; t < NUM_THREADS; t++) {\n    int rc = pthread_create(&threads[t], NULL, func, (void*) &t);\n\n    if (rc) {\n      printf(\"ERROR; return code from pthread_create() is %d\\n\", rc);\n      exit(-1);\n    }\n  }\n\n  void* ret;\n\n  for (unsigned long int t = 0; t < NUM_THREADS; t++) {\n    pthread_join(threads[t], &ret);\n  }\n}\n\n//----------------------------------------------------------------------------\n// Test function 2 ran by a thread\n//----------------------------------------------------------------------------\nvoid*\nTestThread2(void* threadid)\n{\n  int tid = (int) XrdSysThread::Num();\n\n  for (int k = 0; k < loopsize / (int) NUM_THREADS; ++k) {\n    if (k == tid) {\n      std::cout << \"!!!!!!!! Thread \" << tid << \" triggers an incorrect \"\n                << \"lock/unlock order ON PURPOSE at iteration \" << k\n                << \" !!!!!!!!\" << std::endl;\n      gm1.LockWrite();\n      gm3.LockWrite();\n      gm2.LockWrite();\n      gm2.UnLockWrite();\n      gm3.UnLockWrite(); // swapped with previous one\n      gm1.UnLockWrite();\n    } else {\n      gm1.LockWrite();\n      gm2.LockWrite();\n      gm3.LockWrite();\n      gm3.UnLockWrite();\n      gm2.UnLockWrite();\n      gm1.UnLockWrite();\n    }\n  }\n\n  return NULL;\n}\n\n#endif\n\n//----------------------------------------------------------------------------\n// Main function\n//----------------------------------------------------------------------------\nint\nmain()\n{\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n  RWMutex::SetOrderCheckingGlobal(false);\n  std::cout << \" Using Instrumented Version of RWMutex class\" << std::endl;\n  RWMutex::EstimateLatenciesAndCompensation();\n  size_t t = Timing::GetNowInNs();\n\n  for (int k = 0; k < loopsize; ++k) {\n    struct timespec ts;\n    eos::common::Timing::GetTimeSpec(ts);\n  }\n\n  t = Timing::GetNowInNs() - t;\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Measuring speed of function clock_gettime() \" << std::endl;\n  std::cout << \" Monothreaded Loop of size \" << double(loopsize) << \" took \" <<\n            t /\n            1.0e9 << \" sec\" << \" (\" << double(loopsize) / (t / 1.0e9) << \"Hz\" << \")\" <<\n            std::endl;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n  RWMutex::SetTimingGlobal(true);\n  RWMutex mutex, mutex2;\n  mutex.SetTiming(true);\n  t = Timing::GetNowInNs();\n\n  for (int k = 0; k < loopsize; ++k) {\n    mutex.LockWrite();\n    mutex.UnLockWrite();\n  }\n\n  t = Timing::GetNowInNs() - t;\n  RWMutex::TimingStats stats;\n  mutex.GetTimingStatistics(stats);\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Monothreaded Loop of size \" << double(loopsize) << \" took \" <<\n            t /\n            1.0e9 << \" sec\" << \" (\" << double(loopsize) / (t / 1.0e9) << \"Hz\" << \")\" <<\n            std::endl;\n  std::cout << stats;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n  float rate = RWMutex::GetSamplingRateFromCPUOverhead(0.033);\n  std::cout << \" suggested sample rate is \" << rate << std::endl << std::endl;\n  mutex2.SetTiming(true);\n  mutex2.SetSampling(true);\n  t = Timing::GetNowInNs();\n\n  for (int k = 0; k < loopsize; ++k) {\n    mutex2.LockWrite();\n    mutex2.UnLockWrite();\n  }\n\n  t = Timing::GetNowInNs() - t;\n  mutex2.GetTimingStatistics(stats);\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Monothreaded Loop of size \" << double(loopsize) <<\n            \" with a sample rate of \" << rate << \" took \" << t / 1.0e9 << \" sec\" << \" (\" <<\n            double(loopsize) / (t / 1.0e9) << \"Hz\" << \")\" << std::endl;\n  std::cout << stats;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n  RWMutex mutex3;\n  // By default no local timing, but global timing with samplerate of 1\n  RWMutex::SetTimingGlobal(false);\n  t = Timing::GetNowInNs();\n\n  for (int k = 0; k < loopsize; ++k) {\n    mutex3.LockWrite();\n    mutex3.UnLockWrite();\n  }\n\n  t = Timing::GetNowInNs() - t;\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Monothreaded Loop of size \" << double(loopsize) <<\n            \" without stats took \" << t / 1.0e9 << \" sec\" << \" (\" << double(loopsize) /\n            (t / 1.0e9) << \"Hz\" << \")\" << std::endl;\n  std::cout << \" no stats available\" << std::endl;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n  globmutex.SetBlocking(true);\n  RWMutex::SetTimingGlobal(false);\n  t = Timing::GetNowInNs();\n  RunThreads(&TestThread);\n  t = Timing::GetNowInNs() - t;\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Multithreaded Loop (\" << NUM_THREADS <<\n            \" threads half reading/half writing, blocking mutex) of size \" << double(\n              loopsize) / (int) NUM_THREADS\n            << \" without stats took \" << t / 1.0e9 << \" sec\" << \" (\" << double(loopsize) /\n            (t / 1.0e9) << \"Hz\" << \")\" << std::endl;\n  std::cout << \" no stats available\" << std::endl;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n  sleep(1);\n  //----------------------------------------------------------------------------\n  globmutex.SetBlocking(false);\n  RWMutex::SetTimingGlobal(false);\n  t = Timing::GetNowInNs();\n  RunThreads(&TestThread);\n  t = Timing::GetNowInNs() - t;\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Multithreaded Loop (\" << NUM_THREADS <<\n            \" threads half reading/half writing, NON-blocking mutex) of size \" << double(\n              loopsize) / (int) NUM_THREADS\n            << \" without stats took \" << t / 1.0e9 << \" sec\" << \" (\" << double(loopsize) /\n            (t / 1.0e9) << \"Hz\" << \")\" << std::endl;\n  std::cout << \" no stats available\" << std::endl;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n  sleep(1);\n  //----------------------------------------------------------------------------\n  globmutex.SetBlocking(true);\n  globmutex.SetDeadlockCheck(true);\n  RWMutex::SetTimingGlobal(false);\n  t = Timing::GetNowInNs();\n  RunThreads(&TestThread);\n  t = Timing::GetNowInNs() - t;\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Multithreaded Loop (\" << NUM_THREADS\n            << \" threads half reading/half writing, blocking mutex, with \"\n            << \"deadlock check) of size \"\n            << double(loopsize) / (int) NUM_THREADS\n            << \" without stats took \" << t / 1.0e9 << \" sec\" << \" (\" << double(loopsize) /\n            (t / 1.0e9) << \"Hz\" << \")\" << std::endl;\n  std::cout << \" no stats available\" << std::endl;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n  globmutex.SetDeadlockCheck(false);\n  //----------------------------------------------------------------------------\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Native statistics for global mutex\" << std::endl;\n  std::cout << \" ReadLockCount = \" << globmutex.GetReadLockCounter() << std::endl;\n  std::cout << \" WriteLockCount = \" << globmutex.GetWriteLockCounter() <<\n            std::endl;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n  globmutex.SetBlocking(true);\n  globmutex.SetTiming(true);\n  globmutex.SetSampling(true);\n  globmutex.ResetTimingStatistics();\n  RWMutex::SetTimingGlobal(true);\n  t = Timing::GetNowInNs();\n  RunThreads(&TestThread);\n  t = Timing::GetNowInNs() - t;\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Multithreaded Loop (\" << NUM_THREADS <<\n            \" threads half reading/half writing, blocking mutex) of size \" << double(\n              loopsize) / (int) NUM_THREADS\n            << \" with a sample rate of \" << rate << \" took \" << t / 1.0e9 << \" sec\" << \" (\"\n            << double(loopsize) / (t / 1.0e9) << \"Hz\" << \")\" << std::endl;\n  globmutex.GetTimingStatistics(stats);\n  std::cout << stats;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n  sleep(1);\n  globmutex.SetBlocking(false);\n  globmutex.SetTiming(true);\n  globmutex.SetSampling(true);\n  globmutex.ResetTimingStatistics();\n  RWMutex::SetTimingGlobal(true);\n  t = Timing::GetNowInNs();\n  RunThreads(&TestThread);\n  t = Timing::GetNowInNs() - t;\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Multithreaded Loop (\" << NUM_THREADS <<\n            \" threads half reading/half writing, NON-blocking mutex) of size \" << double(\n              loopsize) / (int) NUM_THREADS\n            << \" with a sample rate of \" << rate << \" took \" << t / 1.0e9 << \" sec\" << \" (\"\n            << double(loopsize) / (t / 1.0e9) << \"Hz\" << \")\" << std::endl;\n  globmutex.GetTimingStatistics(stats);\n  std::cout << stats;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Global statistics\" << std::endl;\n  RWMutex::GetTimingStatisticsGlobal(stats);\n  std::cout << stats;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n  std::cout << \"#################################################\" << std::endl;\n  std::cout << \"######## MONOTHREADED ORDER CHECKING TESTS ######\" << std::endl;\n  std::cout << \"#################################################\" << std::endl;\n  RWMutex::SetTimingGlobal(false);\n  RWMutex::SetOrderCheckingGlobal(true);\n  std::vector<RWMutex*> order;\n  order.push_back(&gm1);\n  gm1.SetDebugName(\"mutex1\");\n  order.push_back(&gm2);\n  gm2.SetDebugName(\"mutex2\");\n  order.push_back(&gm3);\n  gm3.SetDebugName(\"mutex3\");\n  RWMutex::AddOrderRule(\"rule1\", order);\n  order.clear();\n  order.push_back(&gm2);\n  order.push_back(&gm3);\n  RWMutex::AddOrderRule(\"rule2\", order);\n  std::cout << \"==== Trying lock/unlock mutex in proper order... ====\" <<\n            std::endl;\n  std::cout.flush();\n  gm1.LockWrite();\n  gm2.LockWrite();\n  gm3.LockWrite();\n  gm3.UnLockWrite();\n  gm2.UnLockWrite();\n  gm1.UnLockWrite();\n  std::cout << \"======== ... done ========\" << std::endl << std::endl;\n  std::cout.flush();\n  std::cout << \"=== Trying lock/unlock mutex in an improper order... ===\" <<\n            std::endl;\n  std::cout.flush();\n  gm1.LockWrite();\n  gm3.LockWrite();\n  gm2.LockWrite();\n  gm2.UnLockWrite();\n  gm3.UnLockWrite();\n  gm1.UnLockWrite();\n  std::cout << \"======== ... done ========\" << std::endl << std::endl;\n  std::cout.flush();\n  RWMutex::SetOrderCheckingGlobal(false);\n  t = Timing::GetNowInNs();\n\n  for (int k = 0; k < loopsize; ++k) {\n    gm1.LockWrite();\n    gm2.LockWrite();\n    gm3.LockWrite();\n    gm3.UnLockWrite();\n    gm2.UnLockWrite();\n    gm1.UnLockWrite();\n  }\n\n  t = Timing::GetNowInNs() - t;\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Monothreaded Loop of size \" << double(loopsize)\n            << \" WITHOUT order check took \" << t / 1.0e9 << \" sec\" << \" (\"\n            << double(loopsize) / (t / 1.0e9) << \"Hz\" << \")\" << std::endl;\n  std::cout << \" no stats available\" << std::endl;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n  RWMutex::SetOrderCheckingGlobal(true);\n  t = Timing::GetNowInNs();\n\n  for (int k = 0; k < loopsize; ++k) {\n    gm1.LockWrite();\n    gm2.LockWrite();\n    gm3.LockWrite();\n    gm3.UnLockWrite();\n    gm2.UnLockWrite();\n    gm1.UnLockWrite();\n  }\n\n  t = Timing::GetNowInNs() - t;\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Monothreaded Loop of size \" << double(loopsize)\n            << \" WITH order check took \" << t / 1.0e9 << \" sec\" << \" (\"\n            <<  double(loopsize) / (t / 1.0e9) << \"Hz\" << \")\" << std::endl;\n  std::cout << \" no stats available\" << std::endl;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n  std::cout << \"#################################################\" << std::endl;\n  std::cout << \"####### MULTITHREADED ORDER CHECKING TESTS ######\" << std::endl;\n  std::cout << \"#################################################\" << std::endl;\n  RunThreads(&TestThread2);\n  return 0;\n#endif\n  // Make compiler happy\n  (void) loopsize;\n  return 0;\n}\n\n/*\nint\nmain()\n{\n  std::cout << \" Using NON-Instrumented Version of RWMutex class\" << std::endl;\n  RWMutex mutex3;\n  size_t t = Timing::GetNowInNs();\n\n  for (int k = 0; k < loopsize; ++k) {\n    mutex3.LockWrite();\n    mutex3.UnLockWrite();\n  }\n\n  t = Timing::GetNowInNs() - t;\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Monothreaded Loop of size \" << double(loopsize) <<\n       \" without stats took \" << t / 1.0e9 << \" sec\" << \" (\" << double(loopsize) /\n       (t / 1.0e9) << \"Hz\" << \")\" << std::endl;\n  std::cout << \" no stats available\" << std::endl;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n  globmutex.SetBlocking(true);\n  t = Timing::GetNowInNs();\n  RunThreads(&TestThread);\n  t = Timing::GetNowInNs() - t;\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Multithreaded Loop (\" << NUM_THREADS <<\n       \" threads half reading/half writing, blocking mutex) of size \" << double(\n         loopsize) / (int) NUM_THREADS\n       << \" without stats took \" << t / 1.0e9 << \" sec\" << \" (\" << double(loopsize) /\n       (t / 1.0e9) << \"Hz\" << \")\" << std::endl;\n  std::cout << \" no stats available\" << std::endl;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n  globmutex.SetBlocking(false);\n  t = Timing::GetNowInNs();\n  RunThreads(&TestThread);\n  t = Timing::GetNowInNs() - t;\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Multithreaded Loop (\" << NUM_THREADS <<\n       \" threads half reading/half writing, NON-blocking mutex) of size \" << double(\n         loopsize) / (int) NUM_THREADS\n       << \" without stats took \" << t / 1.0e9 << \" sec\" << \" (\" << double(loopsize) /\n       (t / 1.0e9) << \"Hz\" << \")\" << std::endl;\n  std::cout << \" no stats available\" << std::endl;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Native statistics for global mutex\" << std::endl;\n  std::cout << \" ReadLockCount = \" << globmutex.GetReadLockCounter() << std::endl;\n  std::cout << \" WriteLockCount = \" << globmutex.GetWriteLockCounter() << std::endl;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n  std::cout << \"###################################################################\" <<\n       std::endl;\n  std::cout << \"################ MONOTHREADED ORDER CHECKING TESTS ################\" <<\n       std::endl;\n  std::cout << \"###################################################################\" <<\n       std::endl;\n  t = Timing::GetNowInNs();\n\n  for (int k = 0; k < loopsize; ++k) {\n    gm1.LockWrite();\n    gm2.LockWrite();\n    gm3.LockWrite();\n    gm3.UnLockWrite();\n    gm2.UnLockWrite();\n    gm1.UnLockWrite();\n  }\n\n  t = Timing::GetNowInNs() - t;\n  std::cout << \" ------------------------- \" << std::endl;\n  std::cout << \" Monothreaded Loop of size \" << double(loopsize) <<\n       \" WITHOUT order check took \" << t / 1.0e9 << \" sec\" << \" (\" << double(\n         loopsize) / (t / 1.0e9) << \"Hz\" << \")\" << std::endl;\n  std::cout << \" no stats available\" << std::endl;\n  std::cout << \" ------------------------- \" << std::endl << std::endl;\n}\n*/\n"
  },
  {
    "path": "common/plugin_manager/DynamicLibrary.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file DynamicLibrary.cc\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"DynamicLibrary.hh\"\n#include <dlfcn.h>\n#include <sstream>\n#include <iostream>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor with parameter\n//------------------------------------------------------------------------------\nDynamicLibrary::DynamicLibrary(void* handle):\n  mHandle(handle)\n{\n  // empty\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nDynamicLibrary::~DynamicLibrary()\n{\n  if (mHandle) {\n    ::dlclose(mHandle);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Load dynamic library\n//------------------------------------------------------------------------------\nDynamicLibrary*\nDynamicLibrary::Load(const std::string& name,\n                     std::string& error)\n{\n  if (name.empty()) {\n    error = \"Empty path\";\n    return NULL;\n  }\n\n  void* handle = NULL;\n  handle = ::dlopen(name.c_str(), RTLD_NOW);\n\n  if (!handle) {\n    const char* zErrorString = ::dlerror();\n    error += \"Failed to load \\\"\" + name + '\"';\n\n    if (zErrorString) {\n      std::string dl_error = zErrorString;\n      error += \": \" + dl_error;\n    }\n\n    return NULL;\n  }\n\n  return new DynamicLibrary(handle);\n}\n\n//------------------------------------------------------------------------------\n// Get symbol\n//------------------------------------------------------------------------------\nvoid*\nDynamicLibrary::GetSymbol(const std::string& symbol)\n{\n  if (!mHandle) {\n    std::cerr << \"No handle object\" << std::endl;\n    return NULL;\n  }\n\n  void* dlsym_obj = ::dlsym(mHandle, symbol.c_str());\n  const char* dlsym_error = ::dlerror();\n\n  if (dlsym_error) {\n    std::cerr << \"Cannot load symbol: \" << symbol\n              << \" error: \" << dlsym_error << std::endl;\n    return NULL;\n  }\n\n  return dlsym_obj;\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/plugin_manager/DynamicLibrary.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file DynamicLibrary.hh\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOS_PF_DYNAMIC_LIBRARY_HH__\n#define __EOS_PF_DYNAMIC_LIBRARY_HH__\n\n#include \"common/Namespace.hh\"\n#include <string>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class DyanmicLibrary\n//------------------------------------------------------------------------------\nclass DynamicLibrary\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Load dynamic library\n  //!\n  //! @param path path to dynamic library\n  //! @param error error message\n  //!\n  //! @return handle to DynamicLibrary object\n  //----------------------------------------------------------------------------\n  static DynamicLibrary* Load(const std::string& path,\n                              std::string& error);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~DynamicLibrary();\n\n  //----------------------------------------------------------------------------\n  //! Get hadle to symbol from current dynamic library\n  //!\n  //! @param name symbol name\n  //!\n  //! @return handle to requestes object if successful, otherwise NULL\n  //----------------------------------------------------------------------------\n  void* GetSymbol(const std::string& name);\n\nprivate:\n\n  void* mHandle;  ///< handle to dynamic library\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  DynamicLibrary();\n\n  //----------------------------------------------------------------------------\n  //! Constructor with parameter\n  //----------------------------------------------------------------------------\n  DynamicLibrary(void* handle);\n\n  //----------------------------------------------------------------------------\n  //! Copy constructor\n  //----------------------------------------------------------------------------\n  DynamicLibrary(const DynamicLibrary&) = delete;\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif // __EOS_PF_DYNAMIC_LIBRARY_HH__\n"
  },
  {
    "path": "common/plugin_manager/Plugin.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Plugin.hh\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __PF_PLUGIN_HH__\n#define __PF_PLUGIN_HH__\n\n#include <cstdint>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n//! Foward declaration\nstruct PF_PlatformServices;\n\n//------------------------------------------------------------------------------\n//! Plugin layer value indicating the layer for which the plugin object is\n//! responsible\n//------------------------------------------------------------------------------\ntypedef enum PF_Plugin_Layer {\n  L0, // bottom one\n  L1,\n  L2,\n  L3  // top one\n} PF_Plugin_Layer;\n\n//------------------------------------------------------------------------------\n//! Plugin version information\n//------------------------------------------------------------------------------\ntypedef struct PF_PluginAPI_Version {\n  int32_t major;\n  int32_t minor;\n} PF_PluginAPI_Version;\n\n//------------------------------------------------------------------------------\n//! Create and destroy methods to be implemented by the plugin objects. Through\n//! these the PluginManager manages the lifetime of plugin objects\n//------------------------------------------------------------------------------\ntypedef void* (*PF_CreateFunc)(PF_PlatformServices*);\ntypedef int32_t (*PF_DestroyFunc)(void*);\n\n//------------------------------------------------------------------------------\n//! Parameters registered by the plugin objects with the PluginManager\n//------------------------------------------------------------------------------\ntypedef struct PF_RegisterParams {\n  PF_PluginAPI_Version version;\n  PF_CreateFunc CreateFunc;\n  PF_DestroyFunc DestroyFunc;\n  PF_Plugin_Layer layer;\n} PF_RegisterParams;\n\n//------------------------------------------------------------------------------\n//! Register function pointer used by a plugin object to register itself with\n//! the PluginManager\n//------------------------------------------------------------------------------\ntypedef int32_t (*PF_RegisterFunc)(const char* objType,\n                                   const PF_RegisterParams* params);\n\n//------------------------------------------------------------------------------\n//! Function pointer calling platform services specified by name. This can be\n//! used by the plugin object to pass information to/from the main application\n//! e.g allocate memory, logging etc.\n//------------------------------------------------------------------------------\ntypedef int32_t (*PF_InvokeServiceFunc)(const char* serviceName,\n                                        void* serviceParams);\n\n//------------------------------------------------------------------------------\n//! Platform services structure provided by the PluginManager to any loaded\n//! plugin. It describes the PM's version, register function to be called by\n//! the plugin object and also other possible services which are made available\n//! by the PM to the plugin objects.\n//------------------------------------------------------------------------------\ntypedef struct PF_PlatformServices {\n  PF_PluginAPI_Version version;\n  PF_RegisterFunc registerObject;\n  PF_InvokeServiceFunc invokeService;\n} PF_PlatformServices;\n\n//------------------------------------------------------------------------------\n//! Discovery service parameters\n//------------------------------------------------------------------------------\ntypedef struct PF_Discovery_Service {\n  char* objType;\n  void* ptrService;\n} PF_Discovery_Service;\n\n//------------------------------------------------------------------------------\n//! Exit function pointer returned after registering a new plugin. This is\n//! called by the PluginManager when destroying loaded plugins.\n//! NOTE: this does not destroy the plugin objects created by this plugin\n//------------------------------------------------------------------------------\ntypedef int32_t (*PF_ExitFunc)();\n\n//------------------------------------------------------------------------------\n//! Function used by the PluginManager to initialize registered plugins.\n//! The plugin may use the runtime services - allocate memory, log messages\n//! and of course register plugin objects.\n//!\n//! @param  params  platform services struct\n//!\n//! @return the exit func of the plugin or NULL if initialization failed\n//------------------------------------------------------------------------------\ntypedef PF_ExitFunc(*PF_InitFunc)(const PF_PlatformServices*);\n\nextern\n#ifdef  __cplusplus\n\"C\"\n#endif\n\n//------------------------------------------------------------------------------\n//! Each plugin implementation must contain this function with the same name\n//! and signature. The method is called each time a new plugin library is loaded.\n//!\n//! @param  params the platform services struct\n//!\n//! @return the exit func of the plugin or NULL if initialization failed\n//------------------------------------------------------------------------------\nPF_ExitFunc PF_initPlugin(const PF_PlatformServices*);\n\n#ifdef  __cplusplus\n}\n#endif\n\n#endif // __PF_PLUGIN_HH__\n"
  },
  {
    "path": "common/plugin_manager/PluginManager.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file PluginManager.cc\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <string>\n#include <iostream>\n#include <sys/types.h>\n#include <dirent.h>\n#include <unistd.h>\n#include \"PluginManager.hh\"\n#include \"DynamicLibrary.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\nstatic std::vector<std::string> sDynLibExtensions {\".so\", \".dylib\"};\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nPluginManager::PluginManager()\n{\n  mPlatformServices.version.major = 0;\n  mPlatformServices.version.minor = 1;\n  mPlatformServices.invokeService = NULL; // can be populated during LoadAll()\n  mPlatformServices.registerObject = RegisterObject;\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nPluginManager::~PluginManager()\n{\n  // Just in case it wasn't called earlier\n  Shutdown();\n}\n\n//------------------------------------------------------------------------------\n// Get instance of PluginManager\n//------------------------------------------------------------------------------\nPluginManager&\nPluginManager::GetInstance()\n{\n  static PluginManager instance;\n  return instance;\n}\n\n//------------------------------------------------------------------------------\n// Shutdown method\n//------------------------------------------------------------------------------\nint32_t PluginManager::Shutdown()\n{\n  int32_t result = 0;\n\n  for (auto func = mExitFuncVec.begin(); func != mExitFuncVec.end(); ++func) {\n    try {\n      // @todo(esindril): this could be re-enabled if we don't do the cleaning\n      // ourselves before\n      //result += (*func)();\n    } catch (...) {\n      result = -1;\n    }\n  }\n\n  mDynamicLibMap.clear();\n  mObjectMap.clear();\n  mExitFuncVec.clear();\n  return result;\n}\n\n//------------------------------------------------------------------------------\n// The registration params may be received from an external plugin so it is\n// crucial to validate it, because it was never subject to our tests\n//------------------------------------------------------------------------------\nstatic bool IsValid(const char* objType,\n                    const PF_RegisterParams* params)\n{\n  if (!objType) {\n    return false;\n  }\n\n  // Plugin does not implement the interface\n  if (!params || !params->CreateFunc || !params->DestroyFunc) {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Initialize plugin\n//------------------------------------------------------------------------------\nint32_t\nPluginManager::InitializePlugin(PF_InitFunc initFunc)\n{\n  PluginManager& pm = PluginManager::GetInstance();\n  PF_ExitFunc exitFunc = initFunc(&pm.mPlatformServices);\n\n  if (!exitFunc) {\n    return -1;\n  }\n\n  // Store the exit func so it can be called when unloading this plugin\n  pm.mExitFuncVec.push_back(exitFunc);\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Plugin registers the objects that it provides through this function\n//------------------------------------------------------------------------------\nint32_t\nPluginManager::RegisterObject(const char* objType,\n                              const PF_RegisterParams* params)\n{\n  // Check parameters\n  if (!IsValid(objType, params)) {\n    return -1;\n  }\n\n  std::string key = std::string(objType);\n  PluginManager& pm = PluginManager::GetInstance();\n  // Verify that versions match\n  PF_PluginAPI_Version v = pm.mPlatformServices.version;\n\n  if (v.major != params->version.major) {\n    std::cerr << \"Plugin manager API and plugin object API version mismatch\"\n              << std::endl;\n    return -1;\n  }\n\n  // Fail if item already exists (only one can handle)\n  if (pm.mObjectMap.find(key) != pm.mObjectMap.end()) {\n    std::cerr << \"Error, object type already registered\" << std::endl;\n    return -1;\n  }\n\n  eos_static_info(\"register plugin object name=%s\", key.c_str());\n  pm.mObjectMap[key] = *params;\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Load all dynamic libraries from directory\n//------------------------------------------------------------------------------\nint32_t\nPluginManager::LoadAll(std::string dir_path,\n                       PF_InvokeServiceFunc func)\n{\n  if (dir_path.empty()) {\n    std::cerr << \"Plugin path is empty\" << std::endl;\n    return -1;\n  }\n\n  // If relative path, get current working directory\n  if (dir_path[0] == '.') {\n    char* cwd {0};\n    size_t size {0};\n    cwd = getcwd(cwd, size);\n\n    if (cwd) {\n      std::string tmp_path = cwd;\n      dir_path = dir_path.erase(0, 1);\n      dir_path = tmp_path + dir_path;\n      free(cwd);\n    }\n  }\n\n  // Add backslash at the end\n  if (dir_path[dir_path.length() - 1] != '/') {\n    dir_path += '/';\n  }\n\n  if (func) {\n    mPlatformServices.invokeService = func;\n  }\n\n  auto dir = opendir(dir_path.c_str());\n\n  if (dir == 0) {\n    std::cerr << \"Cannot open dir: \" << dir_path << std::endl;\n    return -1;\n  }\n\n  std::string full_path;\n  struct dirent* entity {\n    0\n  };\n\n  while ((entity = readdir(dir))) {\n    // Skip directories and link files\n    if ((entity->d_type & DT_DIR) || (entity->d_type == DT_LNK)) {\n      continue;\n    }\n\n    full_path = dir_path + entity->d_name;\n\n    // Try all accepted extensions\n    for (auto extension = sDynLibExtensions.begin();\n         extension != sDynLibExtensions.end();\n         ++extension) {\n      if (full_path.length() <= extension->length()) {\n        continue;\n      }\n\n      if (full_path.find(*extension) != std::string::npos) {\n        LoadByPath(full_path);\n        break;\n      }\n    }\n  }\n\n  (void) closedir(dir);\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Get plugin object from dynamic library\n//------------------------------------------------------------------------------\nint32_t\nPluginManager::LoadByPath(const std::string& lib_path)\n{\n  // Don't load the same dynamic library twice\n  if (mDynamicLibMap.find(lib_path) != mDynamicLibMap.end()) {\n    return -1;\n  }\n\n  std::string error;\n  DynamicLibrary* dyn_lib = LoadLibrary(lib_path, error);\n\n  if (!dyn_lib) {\n    std::cerr << error << std::endl;\n    return -1;\n  }\n\n  // Get the *_initPlugin() function\n  PF_InitFunc initFunc = (PF_InitFunc)(dyn_lib->GetSymbol(\"PF_initPlugin\"));\n\n  // Expected entry point missing from dynamic library\n  if (!initFunc) {\n    eos_err(\"expected entry point PF_initPlugin missing from plugin \"\n            \"library\");\n    return -1;\n  }\n\n  int32_t res = InitializePlugin(initFunc);\n\n  if (res < 0) {\n    eos_err(\"failed initialization of plugin library=%s\", lib_path.c_str());\n    return res;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Create plugin object for the specified layer\n//------------------------------------------------------------------------------\nvoid*\nPluginManager::CreateObject(const std::string& obj_type)\n{\n  auto iter = mObjectMap.find(obj_type);\n\n  if (iter != mObjectMap.end()) {\n    PF_RegisterParams& rp = iter->second;\n    void* object = rp.CreateFunc(&mPlatformServices);\n\n    // Register the new plugin object\n    if (object) {\n      eos_info(\"created plugin object type=%s\", obj_type.c_str());\n      return object;\n    }\n  }\n\n  eos_err(\"failed creating plugin object type=%s\", obj_type.c_str());\n  return NULL;\n}\n\n//------------------------------------------------------------------------------\n// Load dynamic library\n//------------------------------------------------------------------------------\nDynamicLibrary*\nPluginManager::LoadLibrary(const std::string& path, std::string& error)\n{\n  DynamicLibrary* dyn_lib = DynamicLibrary::Load(path, error);\n\n  if (!dyn_lib) {\n    return NULL;\n  }\n\n  // Add library to map, so it can be unloaded at the end\n  mDynamicLibMap[path] = std::shared_ptr<DynamicLibrary>(dyn_lib);\n  return dyn_lib;\n}\n\n//------------------------------------------------------------------------------\n// Get registration map\n//------------------------------------------------------------------------------\nconst PluginManager::RegistrationMap&\nPluginManager::GetRegistrationMap() const\n{\n  return mObjectMap;\n}\n\n//------------------------------------------------------------------------------\n// Get dynamic libraries map\n//------------------------------------------------------------------------------\nconst PluginManager::DynamicLibMap&\nPluginManager::GetDynamicLibMap() const\n{\n  return mDynamicLibMap;\n}\n\n//------------------------------------------------------------------------------\n// Get available platform services\n//------------------------------------------------------------------------------\nPF_PlatformServices&\nPluginManager::GetPlatformServices()\n{\n  return mPlatformServices;\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/plugin_manager/PluginManager.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file PluginManager.hh\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOS_PF_PLUGIN_MANAGER_HH__\n#define __EOS_PF_PLUGIN_MANAGER_HH__\n\n#include <vector>\n#include <map>\n#include <memory>\n#include \"Plugin.hh\"\n#include \"common/Namespace.hh\"\n#include \"common/Logging.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//! Forward declaration\nclass DynamicLibrary;\n\n//----------------------------------------------------------------------------\n//! Class Plugin Manager\n//----------------------------------------------------------------------------\nclass PluginManager: public LogId\n{\n  typedef std::vector<PF_ExitFunc> ExitFuncVec;\n\npublic:\n\n  typedef std::map<std::string, std::shared_ptr<DynamicLibrary> > DynamicLibMap;\n  typedef std::map<std::string, PF_RegisterParams> RegistrationMap;\n\n  //----------------------------------------------------------------------------\n  //! Get instance of PluginManager\n  //----------------------------------------------------------------------------\n  static PluginManager& GetInstance();\n\n  //----------------------------------------------------------------------------\n  //! Initialize plugin object\n  //!\n  //! @param initFunc plugin init function\n  //!\n  //! @return 0 if successful, otherwise !0\n  //----------------------------------------------------------------------------\n  static int32_t InitializePlugin(PF_InitFunc initFunc);\n\n  //----------------------------------------------------------------------------\n  //! Load all dynamic libraries in the specified directory\n  //!\n  //! @param pluginDirectory path to directory\n  //! @param fun callable to get various services provided by the PM\n  //!\n  //! @return 0 if successful, otherwise !0\n  //----------------------------------------------------------------------------\n  int32_t LoadAll(std::string pluginDirectory,\n                  PF_InvokeServiceFunc func = NULL);\n\n  //----------------------------------------------------------------------------\n  //! Load dynamic library given its path\n  //!\n  //! @param path path to dynamic library\n  //!\n  //! @return 0 if successful, otherwise !0\n  //----------------------------------------------------------------------------\n  int32_t LoadByPath(const std::string& path);\n\n  //----------------------------------------------------------------------------\n  //! Create a plugin object\n  //!\n  //! @param objType object type name\n  //!\n  //! @return pointer to newly create object if successful, otherwise NULL\n  //----------------------------------------------------------------------------\n  void* CreateObject(const std::string& objType);\n\n  //----------------------------------------------------------------------------\n  //! Cleanup function called before PluginManager is destroyed\n  //!\n  //! @return 0 if successful, otherwise !0\n  //----------------------------------------------------------------------------\n  int32_t Shutdown();\n\n  //----------------------------------------------------------------------------\n  //! Method called by the plugin to register the objects it provides\n  //!\n  //! @param objType object type name\n  //! @param params parameters registered by the plugin\n  //!\n  //! @return 0 if successful, otherwise !0\n  //----------------------------------------------------------------------------\n  static int32_t RegisterObject(const char* objType,\n                                const PF_RegisterParams* params);\n\n  //----------------------------------------------------------------------------\n  //! Initialize the plugin stack. The PluginManager takes care of initializing\n  //! all the available plugins from bottom to top taking care of the dependencies\n  //! between plugin objects. If a plugin layer is missing than the closest two\n  //! plugin object are connected\n  //!\n  //! @return 0 if successful, otherwise !0\n  //----------------------------------------------------------------------------\n  int32_t InitPluginStack();\n\n  //----------------------------------------------------------------------------\n  //! Get registration map i.e. plugin object types available\n  //----------------------------------------------------------------------------\n  const RegistrationMap& GetRegistrationMap() const;\n\n  //----------------------------------------------------------------------------\n  //! Get dynamic libraries map\n  //----------------------------------------------------------------------------\n  const DynamicLibMap& GetDynamicLibMap() const;\n\n  //----------------------------------------------------------------------------\n  //! Get services provided by the platform i.e. logging\n  //----------------------------------------------------------------------------\n  PF_PlatformServices& GetPlatformServices();\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  PluginManager();\n\n  //----------------------------------------------------------------------------\n  //! Copy constructor\n  //----------------------------------------------------------------------------\n  PluginManager(const PluginManager&) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~PluginManager();\n\n  //----------------------------------------------------------------------------\n  //! Load dynamic library\n  //!\n  //! @param path path to library\n  //! @param errorString error in string format\n  //!\n  //! @return dynamic library object\n  //----------------------------------------------------------------------------\n  DynamicLibrary* LoadLibrary(const std::string& path, std::string& errorString);\n\nprivate:\n  PF_PlatformServices mPlatformServices;\n  DynamicLibMap mDynamicLibMap; ///< library path to DynamicLibrary obj. map\n  ExitFuncVec mExitFuncVec;  ///< vector of ExitFunc object for each plugin\n  RegistrationMap mObjectMap; ///< registered object types by plugins\n};\n\nEOSCOMMONNAMESPACE_END\n\n#endif  // __PF_PLUGIN_MANAGER_HH__\n"
  },
  {
    "path": "common/shaping/IoStatsKey.hh",
    "content": "#pragma once\n\n#include <cstddef>\n#include <cstdint>\n#include <functional>\n#include <string>\n\nnamespace eos::common::traffic_shaping {\n\n// Uniquely identifies a traffic stream by (app, uid, gid).\n// Used by both FST (for recording I/O) and MGM (for policy look-ups and rate tracking).\nstruct IoStatsKey {\n  std::string app;\n  uint32_t uid;\n  uint32_t gid;\n\n  bool\n  operator==(const IoStatsKey& other) const\n  {\n    return uid == other.uid && gid == other.gid && app == other.app;\n  }\n};\n\nstruct IoStatsKeyHash {\n  std::size_t\n  operator()(const IoStatsKey& k) const\n  {\n    // Use hash_combine to avoid XOR's cancellation/commutativity pitfalls\n    // (e.g. uid==gid would collapse to just hash(app) with plain XOR).\n    // The magic constant is the golden-ratio fractional bits, same as\n    // boost::hash_combine.\n    auto combine = [](const std::size_t seed, const std::size_t val) -> std::size_t {\n      return seed ^ (val + 0x9e3779b9 + (seed << 6) + (seed >> 2));\n    };\n    std::size_t h = std::hash<std::string>{}(k.app);\n    h = combine(h, std::hash<uint32_t>{}(k.uid));\n    h = combine(h, std::hash<uint32_t>{}(k.gid));\n    return h;\n  }\n};\n\n} // namespace eos::common::traffic_shaping\n"
  },
  {
    "path": "common/shaping/SlidingWindowStats.hh",
    "content": "#pragma once\n\n#include <algorithm>\n#include <cmath>\n#include <cstdint>\n#include <vector>\n\nnamespace eos::common::traffic_shaping {\n\n// Circular-buffer sliding window for computing rates over variable time windows.\n// Not thread-safe on its own; callers must hold any required locks.\nclass SlidingWindowStats {\npublic:\n  SlidingWindowStats() = default;\n\n  SlidingWindowStats(const double max_history_seconds, const double tick_interval_seconds)\n      : mTickIntervalSec(tick_interval_seconds)\n      , mHistorySize(\n            std::max(1, static_cast<int>(max_history_seconds / tick_interval_seconds)))\n      , mBuffer(mHistorySize, 0)\n  {\n  }\n\n  void\n  Add(const uint64_t bytes)\n  {\n    mBuffer[mHead] += bytes;\n  }\n\n  void\n  Tick()\n  {\n    mHead = (mHead + 1) % mHistorySize;\n    mBuffer[mHead] = 0;\n  }\n\n  double GetRate(double seconds) const;\n\n  uint64_t GetMax(bool ignore_zeroes = false) const;\n\n  uint64_t GetMin(bool ignore_zeroes = false) const;\n\n  double GetMean(bool ignore_zeroes = false) const;\n\n  double GetMedian(bool ignore_zeroes = false) const;\n\nprivate:\n  double mTickIntervalSec{};\n  int mHistorySize{};\n  std::vector<uint64_t> mBuffer{};\n  int mHead{};\n};\n\ninline double\nSlidingWindowStats::GetRate(const double seconds) const\n{\n  if (seconds <= 0.0) {\n    return 0.0;\n  }\n\n  int num_buckets = static_cast<int>(std::round(seconds / mTickIntervalSec));\n  if (num_buckets <= 0) {\n    num_buckets = 1;\n  }\n  if (num_buckets > mHistorySize) {\n    num_buckets = mHistorySize;\n  }\n\n  uint64_t sum = 0;\n  int idx = mHead;\n\n  for (int i = 0; i < num_buckets; ++i) {\n    sum += mBuffer[idx];\n\n    if (--idx < 0) {\n      idx = mHistorySize - 1;\n    }\n  }\n\n  const double actual_window_sec = static_cast<double>(num_buckets) * mTickIntervalSec;\n  return static_cast<double>(sum) / actual_window_sec;\n}\n\ninline uint64_t\nSlidingWindowStats::GetMax(const bool ignore_zeroes) const\n{\n  uint64_t max_val = 0;\n  for (int i = 0; i < mHistorySize; ++i) {\n    if (i == mHead) {\n      continue;\n    }\n    if (mBuffer[i] == 0 && ignore_zeroes) {\n      continue;\n    }\n    if (mBuffer[i] > max_val) {\n      max_val = mBuffer[i];\n    }\n  }\n  return max_val;\n}\n\ninline uint64_t\nSlidingWindowStats::GetMin(const bool ignore_zeroes) const\n{\n  uint64_t min_val = UINT64_MAX;\n  for (int i = 0; i < mHistorySize; ++i) {\n    if (i == mHead) {\n      continue;\n    }\n    if (mBuffer[i] == 0 && ignore_zeroes) {\n      continue;\n    }\n    if (mBuffer[i] < min_val) {\n      min_val = mBuffer[i];\n    }\n  }\n  return min_val == UINT64_MAX ? 0 : min_val;\n}\n\ninline double\nSlidingWindowStats::GetMean(const bool ignore_zeroes) const\n{\n  uint64_t sum = 0;\n  int count = 0;\n  for (int i = 0; i < mHistorySize; ++i) {\n    if (i == mHead) {\n      continue;\n    }\n    if (mBuffer[i] == 0 && ignore_zeroes) {\n      continue;\n    }\n    sum += mBuffer[i];\n    count++;\n  }\n  return count == 0 ? 0.0 : static_cast<double>(sum) / count;\n}\n\ninline double\nSlidingWindowStats::GetMedian(const bool ignore_zeroes) const\n{\n  std::vector<uint64_t> valid_values;\n  valid_values.reserve(mHistorySize);\n\n  for (int i = 0; i < mHistorySize; ++i) {\n    if (i == mHead) {\n      continue;\n    }\n    if (mBuffer[i] == 0 && ignore_zeroes) {\n      continue;\n    }\n    valid_values.push_back(mBuffer[i]);\n  }\n\n  if (valid_values.empty()) {\n    return 0.0;\n  }\n\n  std::sort(valid_values.begin(), valid_values.end());\n\n  const size_t size = valid_values.size();\n  const size_t mid = size / 2;\n\n  if (size % 2 == 0) {\n    return (static_cast<double>(valid_values[mid - 1]) +\n            static_cast<double>(valid_values[mid])) /\n           2.0;\n  }\n  return static_cast<double>(valid_values[mid]);\n}\n\n} // namespace eos::common::traffic_shaping\n"
  },
  {
    "path": "common/shellexectest/shell_exec_test.cc",
    "content": "// ----------------------------------------------------------------------\n// File: shell_exec_test.cc\n// Author: Michal Kamin Simon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"common/ShellExecutor.hh\"\n#include \"common/ShellCmd.hh\"\n/*----------------------------------------------------------------------------*/\n#include <string.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <stdlib.h>\n#include <iostream>\n#include <sstream>\n#include <stdio.h>\n#include <uuid/uuid.h>\n#include <iterator>\n\nusing namespace eos::common;\n\n/*----------------------------------------------------------------------------*/\nvoid\ntest_stdin_to_stdout ()\n{\n  ShellCmd cmd(\"tee\");\n  std::string expected = \"123456789\";\n  write(cmd.infd, expected.c_str(), expected.size() + 1);\n  char buff[2048];\n  int end = read(cmd.outfd, buff, sizeof (buff));\n  buff[end - 1] = 0;\n  std::string result = buff;\n\n  cmd.kill();\n\n  if (expected == result)\n    std::cout << \"OK\" << std::endl;\n  else\n    std::cout << \"FAILED\" << std::endl;\n}\n\nvoid\ntest_stderr ()\n{\n  ShellCmd cmd(\"echo something >&2\");\n  std::string expected = \"something\";\n\n  char buff[2048];\n  int end = read(cmd.errfd, buff, sizeof (buff));\n  buff[end - 1] = 0;\n  std::string result = buff;\n\n  if (expected == result)\n    std::cout << \"OK\" << std::endl;\n  else\n    std::cout << \"FAILED\" << std::endl;\n}\n\nvoid\ntest_echo ()\n{\n  // a long string (1100 characters long, so longer than the buffer size)\n  std::string expected =\n          \"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"\n          \"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"\n          \"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"\n          \"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"\n          \"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"\n          \"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"\n          \"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"\n          \"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"\n          \"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"\n          \"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\"\n          \"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\";\n\n  ShellCmd cmd(\"echo \" + expected);\n  cmd.wait();\n\n  char buff[2048];\n  int end = read(cmd.outfd, buff, sizeof (buff));\n  buff[end - 1] = 0;\n  std::string result = buff;\n\n  if (expected == result)\n    std::cout << \"OK\" << std::endl;\n  else\n    std::cout << \"FAILED\" << std::endl;\n}\n\nvoid\ntest_wait ()\n{\n  time_t start = time(0);\n  ShellCmd cmd(\"sleep 3\");\n  cmd.wait();\n  time_t stop = time(0);\n\n  if (stop - start >= 3)\n    std::cout << \"OK\" << std::endl;\n  else\n    std::cout << \"FAILED\" << std::endl;\n}\n\nvoid\ntest_is_active ()\n{\n  ShellCmd cmd(\"grep .\");\n  sleep(1);\n  if (cmd.is_active())\n    std::cout << \"OK\" << std::endl;\n  else\n    std::cout << \"FAILED\" << std::endl;\n  cmd.kill();\n  sleep(1);\n  if (!cmd.is_active())\n    std::cout << \"OK\" << std::endl;\n  else\n    std::cout << \"FAILED\" << std::endl;\n  cmd.wait();\n}\n\nvoid\ntest_status1 ()\n{\n  ShellCmd cmd(\":\");\n  sleep(1);\n  cmd_status status = cmd.wait();\n  if (status.exited)\n    std::cout << \"OK\" << std::endl;\n  else\n    std::cout << \"FAILED\" << std::endl;\n  if (status.exit_code == 0)\n    std::cout << \"OK\" << std::endl;\n  else\n    std::cout << \"FAILED\" << std::endl;\n  if (!status.signaled)\n    std::cout << \"OK\" << std::endl;\n  else\n    std::cout << \"FAILED\" << std::endl;\n}\n\nvoid\ntest_status2 ()\n{\n  ShellCmd cmd(\"sleep 2\");\n  cmd.kill();\n  cmd_status status = cmd.wait();\n  if (!status.exited)\n    std::cout << \"OK\" << std::endl;\n  else\n    std::cout << \"FAILED\" << std::endl;\n  if (status.signaled)\n    std::cout << \"OK\" << std::endl;\n  else\n    std::cout << \"FAILED\" << std::endl;\n  if (status.signo == SIGKILL)\n    std::cout << \"OK\" << std::endl;\n  else\n    std::cout << \"FAILED\" << std::endl;\n}\n\nvoid\ntest_status3 ()\n{\n  ShellCmd cmd(\"non_existent_command\");\n  sleep(1);\n  cmd_status status = cmd.wait();\n  if (status.exited)\n    std::cout << \"OK\" << std::endl;\n  else\n    std::cout << \"FAILED\" << std::endl;\n  if (status.exit_code == 127)\n    std::cout << \"OK\" << std::endl;\n  else\n    std::cout << \"FAILED\" << std::endl;\n  if (!status.signaled)\n    std::cout << \"OK\" << std::endl;\n  else\n    std::cout << \"FAILED\" << std::endl;\n}\n\nint\nmain (int argc, char** argv)\n{\n  ShellExecutor::instance();\n  test_echo();\n  test_stdin_to_stdout();\n  test_stderr();\n  test_wait();\n  test_is_active();\n  test_status1();\n  test_status2();\n  test_status3();\n\n  return 0;\n}\n\n"
  },
  {
    "path": "common/stringencoders/modp_numtoa.c",
    "content": "/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */\n/* vi: set expandtab shiftwidth=4 tabstop=4: */\n\n#include \"modp_numtoa.h\"\n\n#include <stdint.h>\n#include <stdio.h>\n#include <math.h>\n\n// other interesting references on num to string convesion\n// http://www.jb.man.ac.uk/~slowe/cpp/itoa.html\n// and http://www.ddj.com/dept/cpp/184401596?pgno=6\n\n// Version 19-Nov-2007\n// Fixed round-to-even rules to match printf\n//   thanks to Johannes Otepka\n\n/**\n * Powers of 10\n * 10^0 to 10^9\n */\nstatic const double pow10[] = {1, 10, 100, 1000, 10000, 100000, 1000000,\n                               10000000, 100000000, 1000000000};\n\nstatic void strreverse(char* begin, char* end)\n{\n    char aux;\n    while (end > begin)\n        aux = *end, *end-- = *begin, *begin++ = aux;\n}\n\nvoid modp_itoa10(int32_t value, char* str)\n{\n    char* wstr=str;\n    // Take care of sign\n    unsigned int uvalue = (value < 0) ? -value : value;\n    // Conversion. Number is reversed.\n    do *wstr++ = (char)(48 + (uvalue % 10)); while(uvalue /= 10);\n    if (value < 0) *wstr++ = '-';\n    *wstr='\\0';\n\n    // Reverse string\n    strreverse(str,wstr-1);\n}\n\nvoid modp_uitoa10(uint32_t value, char* str)\n{\n    char* wstr=str;\n    // Conversion. Number is reversed.\n    do *wstr++ = (char)(48 + (value % 10)); while (value /= 10);\n    *wstr='\\0';\n    // Reverse string\n    strreverse(str, wstr-1);\n}\n\nvoid modp_litoa10(int64_t value, char* str)\n{\n    char* wstr=str;\n    unsigned long uvalue = (value < 0) ? -value : value;\n\n    // Conversion. Number is reversed.\n    do *wstr++ = (char)(48 + (uvalue % 10)); while(uvalue /= 10);\n    if (value < 0) *wstr++ = '-';\n    *wstr='\\0';\n\n    // Reverse string\n    strreverse(str,wstr-1);\n}\n\nvoid modp_ulitoa10(uint64_t value, char* str)\n{\n    char* wstr=str;\n    // Conversion. Number is reversed.\n    do *wstr++ = (char)(48 + (value % 10)); while (value /= 10);\n    *wstr='\\0';\n    // Reverse string\n    strreverse(str, wstr-1);\n}\n\nvoid modp_dtoa(double value, char* str, int prec)\n{\n    /* Hacky test for NaN\n     * under -fast-math this won't work, but then you also won't\n     * have correct nan values anyways.  The alternative is\n     * to link with libmath (bad) or hack IEEE double bits (bad)\n     */\n    if (! (value == value)) {\n        str[0] = 'n'; str[1] = 'a'; str[2] = 'n'; str[3] = '\\0';\n        return;\n    }\n    /* if input is larger than thres_max, revert to exponential */\n    const double thres_max = (double)(0x7FFFFFFF);\n\n    double diff = 0.0;\n    char* wstr = str;\n\n    if (prec < 0) {\n        prec = 0;\n    } else if (prec > 9) {\n        /* precision of >= 10 can lead to overflow errors */\n        prec = 9;\n    }\n\n\n    /* we'll work in positive values and deal with the\n       negative sign issue later */\n    int neg = 0;\n    if (value < 0) {\n        neg = 1;\n        value = -value;\n    }\n\n\n    int whole = (int) value;\n    double tmp = (value - whole) * pow10[prec];\n    uint32_t frac = (uint32_t)(tmp);\n    diff = tmp - frac;\n\n    if (diff > 0.5) {\n        ++frac;\n        /* handle rollover, e.g.  case 0.99 with prec 1 is 1.0  */\n        if (frac >= pow10[prec]) {\n            frac = 0;\n            ++whole;\n        }\n    } else if (diff == 0.5 && ((frac == 0) || (frac & 1))) {\n        /* if halfway, round up if odd, OR\n           if last digit is 0.  That last part is strange */\n        ++frac;\n    }\n\n    /* for very large numbers switch back to native sprintf for exponentials.\n       anyone want to write code to replace this? */\n    /*\n      normal printf behavior is to print EVERY whole number digit\n      which can be 100s of characters overflowing your buffers == bad\n    */\n    if (value > thres_max) {\n        sprintf(str, \"%e\", neg ? -value : value);\n        return;\n    }\n\n    if (prec == 0) {\n        diff = value - whole;\n        if (diff > 0.5) {\n            /* greater than 0.5, round up, e.g. 1.6 -> 2 */\n            ++whole;\n        } else if (diff == 0.5 && (whole & 1)) {\n            /* exactly 0.5 and ODD, then round up */\n            /* 1.5 -> 2, but 2.5 -> 2 */\n            ++whole;\n        }\n    } else {\n        int count = prec;\n        // now do fractional part, as an unsigned number\n        do {\n            --count;\n            *wstr++ = (char)(48 + (frac % 10));\n        } while (frac /= 10);\n        // add extra 0s\n        while (count-- > 0) *wstr++ = '0';\n        // add decimal\n        *wstr++ = '.';\n    }\n\n    // do whole part\n    // Take care of sign\n    // Conversion. Number is reversed.\n    do *wstr++ = (char)(48 + (whole % 10)); while (whole /= 10);\n    if (neg) {\n        *wstr++ = '-';\n    }\n    *wstr='\\0';\n    strreverse(str, wstr-1);\n}\n\n\n// This is near identical to modp_dtoa above\n//   The differnce is noted below\nvoid modp_dtoa2(double value, char* str, int prec)\n{\n    /* Hacky test for NaN\n     * under -fast-math this won't work, but then you also won't\n     * have correct nan values anyways.  The alternative is\n     * to link with libmath (bad) or hack IEEE double bits (bad)\n     */\n    if (! (value == value)) {\n        str[0] = 'n'; str[1] = 'a'; str[2] = 'n'; str[3] = '\\0';\n        return;\n    }\n\n    /* if input is larger than thres_max, revert to exponential */\n    const double thres_max = (double)(0x7FFFFFFF);\n\n    int count;\n    double diff = 0.0;\n    char* wstr = str;\n\n    if (prec < 0) {\n        prec = 0;\n    } else if (prec > 9) {\n        /* precision of >= 10 can lead to overflow errors */\n        prec = 9;\n    }\n\n\n    /* we'll work in positive values and deal with the\n       negative sign issue later */\n    int neg = 0;\n    if (value < 0) {\n        neg = 1;\n        value = -value;\n    }\n\n\n    int whole = (int) value;\n    double tmp = (value - whole) * pow10[prec];\n    uint32_t frac = (uint32_t)(tmp);\n    diff = tmp - frac;\n\n    if (diff > 0.5) {\n        ++frac;\n        /* handle rollover, e.g.  case 0.99 with prec 1 is 1.0  */\n        if (frac >= pow10[prec]) {\n            frac = 0;\n            ++whole;\n        }\n    } else if (diff == 0.5 && ((frac == 0) || (frac & 1))) {\n        /* if halfway, round up if odd, OR\n           if last digit is 0.  That last part is strange */\n        ++frac;\n    }\n\n    /* for very large numbers switch back to native sprintf for exponentials.\n       anyone want to write code to replace this? */\n    /*\n      normal printf behavior is to print EVERY whole number digit\n      which can be 100s of characters overflowing your buffers == bad\n    */\n    if (value > thres_max) {\n        sprintf(str, \"%e\", neg ? -value : value);\n        return;\n    }\n\n    if (prec == 0) {\n        diff = value - whole;\n        if (diff > 0.5) {\n            /* greater than 0.5, round up, e.g. 1.6 -> 2 */\n            ++whole;\n        } else if (diff == 0.5 && (whole & 1)) {\n            /* exactly 0.5 and ODD, then round up */\n            /* 1.5 -> 2, but 2.5 -> 2 */\n            ++whole;\n        }\n\n        //vvvvvvvvvvvvvvvvvvv  Diff from modp_dto2\n    } else if (frac) {\n        count = prec;\n        // now do fractional part, as an unsigned number\n        // we know it is not 0 but we can have leading zeros, these\n        // should be removed\n        while (!(frac % 10)) {\n            --count;\n            frac /= 10;\n        }\n        //^^^^^^^^^^^^^^^^^^^  Diff from modp_dto2\n\n        // now do fractional part, as an unsigned number\n        do {\n            --count;\n            *wstr++ = (char)(48 + (frac % 10));\n        } while (frac /= 10);\n        // add extra 0s\n        while (count-- > 0) *wstr++ = '0';\n        // add decimal\n        *wstr++ = '.';\n    }\n\n    // do whole part\n    // Take care of sign\n    // Conversion. Number is reversed.\n    do *wstr++ = (char)(48 + (whole % 10)); while (whole /= 10);\n    if (neg) {\n        *wstr++ = '-';\n    }\n    *wstr='\\0';\n    strreverse(str, wstr-1);\n}\n\n\n\n"
  },
  {
    "path": "common/stringencoders/modp_numtoa.h",
    "content": "/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */\n/* vi: set expandtab shiftwidth=4 tabstop=4: */\n\n/**\n * \\file\n *\n * <pre>\n * Copyright &copy; 2007, Nick Galbreath -- nickg [at] modp [dot] com\n * All rights reserved.\n * http://code.google.com/p/stringencoders/\n * Released under the bsd license.\n * </pre>\n *\n * This defines signed/unsigned integer, and 'double' to char buffer\n * converters.  The standard way of doing this is with \"sprintf\", however\n * these functions are\n *   * guarenteed maximum size output\n *   * 5-20x faster!\n *   * core-dump safe\n *\n *\n */\n\n#ifndef COM_MODP_STRINGENCODERS_NUMTOA_H\n#define COM_MODP_STRINGENCODERS_NUMTOA_H\n\n#ifdef __cplusplus\n#define BEGIN_C extern \"C\" {\n#define END_C }\n#else\n#define BEGIN_C\n#define END_C\n#endif\n\nBEGIN_C\n\n#include <stdint.h>\n\n/** \\brief convert an signed integer to char buffer\n *\n * \\param[in] value\n * \\param[out] buf the output buffer.  Should be 16 chars or more.\n */\nvoid modp_itoa10(int32_t value, char* buf);\n\n/** \\brief convert an unsigned integer to char buffer\n *\n * \\param[in] value\n * \\param[out] buf The output buffer, should be 16 chars or more.\n */\nvoid modp_uitoa10(uint32_t value, char* buf);\n\n/** \\brief convert an signed long integer to char buffer\n *\n * \\param[in] value\n * \\param[out] buf the output buffer.  Should be 24 chars or more.\n */\nvoid modp_litoa10(int64_t value, char* buf);\n\n/** \\brief convert an unsigned long integer to char buffer\n *\n * \\param[in] value\n * \\param[out] buf The output buffer, should be 24 chars or more.\n */\nvoid modp_ulitoa10(uint64_t value, char* buf);\n\n/** \\brief convert a floating point number to char buffer with\n *         fixed-precision format\n *\n * This is similar to \"%.[0-9]f\" in the printf style.  It will include\n * trailing zeros\n *\n * If the input value is greater than 1<<31, then the output format\n * will be switched exponential format.\n *\n * \\param[in] value\n * \\param[out] buf  The allocated output buffer.  Should be 32 chars or more.\n * \\param[in] precision  Number of digits to the right of the decimal point.\n *    Can only be 0-9.\n */\nvoid modp_dtoa(double value, char* buf, int precision);\n\n/** \\brief convert a floating point number to char buffer with a\n *         variable-precision format, and no trailing zeros\n *\n * This is similar to \"%.[0-9]f\" in the printf style, except it will\n * NOT include trailing zeros after the decimal point.  This type\n * of format oddly does not exists with printf.\n *\n * If the input value is greater than 1<<31, then the output format\n * will be switched exponential format.\n *\n * \\param[in] value\n * \\param[out] buf  The allocated output buffer.  Should be 32 chars or more.\n * \\param[in] precision  Number of digits to the right of the decimal point.\n *    Can only be 0-9.\n */\nvoid modp_dtoa2(double value, char* buf, int precision);\n\nEND_C\n\n#endif\n"
  },
  {
    "path": "common/table_formatter/TableCell.cc",
    "content": "//------------------------------------------------------------------------------\n// File: TableCell.cc\n// Author: Ivan Arizanovic & Stefan Isidorovic - Comtrade\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"TableCell.hh\"\n\nstatic std::string getColorSequence(TableFormatterColor color) {\n\n  switch(color) {\n    case TableFormatterColor::NONE: {\n      return \"\";\n    }\n    case TableFormatterColor::DEFAULT: {\n      return \"\\33[0m\";\n    }\n    case TableFormatterColor::RED: {\n      return \"\\33[0;31m\";\n    }\n    case TableFormatterColor::GREEN: {\n      return \"\\33[0;32m\";\n    }\n    case TableFormatterColor::YELLOW: {\n      return \"\\33[0;33m\";\n    }\n    case TableFormatterColor::BLUE: {\n      return \"\\33[0;34m\";\n    }\n    case TableFormatterColor::MARGARITA: {\n      return \"\\33[0;35m\";\n    }\n    case TableFormatterColor::CYAN: {\n      return \"\\33[0;36m\";\n    }\n    case TableFormatterColor::WHITE: {\n      return \"\\33[0;39m\";\n    }\n    case TableFormatterColor::BOLD: {\n      return \"\\33[1m\";\n    }\n    case TableFormatterColor::BRED: {\n      return \"\\33[1;31m\";\n    }\n    case TableFormatterColor::BGREEN: {\n      return \"\\33[1;32m\";\n    }\n    case TableFormatterColor::BYELLOW: {\n      return \"\\33[1;33m\";\n    }\n    case TableFormatterColor::BBLUE: {\n      return \"\\33[1;34m\";\n    }\n    case TableFormatterColor::BMARGARITA: {\n      return \"\\33[1;35m\";\n    }\n    case TableFormatterColor::BCYAN: {\n      return \"\\33[1;36m\";\n    }\n    case TableFormatterColor::BWHITE: {\n      return \"\\33[1;39m\";\n    }\n    case TableFormatterColor::DARK: {\n      return \"\\33[2m\";\n    }\n    case TableFormatterColor::DRED: {\n      return \"\\33[2;31m\";\n    }\n    case TableFormatterColor::DGREEN: {\n      return \"\\33[2;32m\";\n    }\n    case TableFormatterColor::DYELLOW: {\n      return \"\\33[2;33m\";\n    }\n    case TableFormatterColor::DBLUE: {\n      return \"\\33[2;34m\";\n    }\n    case TableFormatterColor::DMARGARITA: {\n      return \"\\33[2;35m\";\n    }\n    case TableFormatterColor::DCYAN: {\n      return \"\\33[2;36m\";\n    }\n    case TableFormatterColor::DWHITE: {\n      return \"\\33[2;39m\";\n    }\n    case TableFormatterColor::BRED_BGWHITE: {\n      return \"\\33[1;31;47m\";\n    }\n    case TableFormatterColor::BGREEN_BGWHITE: {\n      return \"\\33[1;32;47m\";\n    }\n    case TableFormatterColor::BYELLOW_BGWHITE: {\n      return \"\\33[1;33;47m\";\n    }\n    case TableFormatterColor::BBLUE_BGWHITE: {\n      return \"\\33[1;34;47m\";\n    }\n    case TableFormatterColor::BMARGARITA_BGWHITE: {\n      return \"\\33[1;35;47m\";\n    }\n    case TableFormatterColor::BCYAN_BGWHITE: {\n      return \"\\33[1;36;47m\";\n    }\n    case TableFormatterColor::BWHITE_BGRED: {\n      return \"\\33[1;39;41m\";\n    }\n    case TableFormatterColor::BWHITE_BGGREEN: {\n      return \"\\33[1;39;42m\";\n    }\n    case TableFormatterColor::BWHITE_BGYELLOW: {\n      return \"\\33[1;39;43m\";\n    }\n    case TableFormatterColor::BWHITE_BGBLUE: {\n      return \"\\33[1;39;44m\";\n    }\n    case TableFormatterColor::BWHITE_BGMARGARITA: {\n      return \"\\33[1;39;45m\";\n    }\n    case TableFormatterColor::BWHITE_BGCYAN: {\n      return \"\\33[1;39;46m\";\n    }\n    case TableFormatterColor::BYELLOW_BGRED: {\n      return \"\\33[1;33;41m\";\n    }\n    case TableFormatterColor::BYELLOW_BGGREEN: {\n      return \"\\33[1;33;42m\";\n    }\n    case TableFormatterColor::BYELLOW_BGBLUE: {\n      return \"\\33[1;33;44m\";\n    }\n    case TableFormatterColor::BYELLOW_BGMARGARITA: {\n      return \"\\33[1;33;45m\";\n    }\n    case TableFormatterColor::BYELLOW_BGCYAN: {\n      return \"\\33[1;33;46m\";\n    }\n    default: {\n      return \"\";\n    }\n  }\n\n}\n\n//------------------------------------------------------------------------------\n// Constructor for unsigned int data\n//------------------------------------------------------------------------------\nTableCell::TableCell(unsigned int value, const std::string& format,\n                     const std::string& unit, bool empty,\n                     TableFormatterColor col)\n  : mFormat(format), mUnit(unit), mEmpty(empty), mColor(col),\n    mSelectedValue(TypeContainingValue::DOUBLE)\n{\n  if (mFormat.find(\"l\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::UINT;\n    SetValue((unsigned long long int)value);\n  }\n\n  if (mFormat.find(\"f\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::DOUBLE;\n    SetValue((double)value);\n  }\n\n  if (mFormat.find(\"s\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::STRING;\n    std::string value_temp = std::to_string(value);\n    SetValue(value_temp);\n  }\n\n  if (mFormat.find(\"t\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::TREE;\n    mTree = (unsigned)value;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Constructor for unsigned long long int data\n//------------------------------------------------------------------------------\nTableCell::TableCell(unsigned long long int value, const std::string& format,\n                     const std::string& unit, bool empty,\n                     TableFormatterColor col)\n  : mFormat(format), mUnit(unit), mEmpty(empty), mColor(col),\n    mSelectedValue(TypeContainingValue::DOUBLE)\n{\n  if (mFormat.find(\"l\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::UINT;\n    SetValue(value);\n  }\n\n  if (mFormat.find(\"f\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::DOUBLE;\n    SetValue((double)value);\n  }\n\n  if (mFormat.find(\"s\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::STRING;\n    std::string value_temp = std::to_string(value);\n    SetValue(value_temp);\n  }\n\n  if (mFormat.find(\"t\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::TREE;\n    mTree = (unsigned)value;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Constructor for int data\n//------------------------------------------------------------------------------\nTableCell::TableCell(int value, const std::string& format,\n                     const std::string& unit, bool empty,\n                     TableFormatterColor col)\n  : mFormat(format), mUnit(unit), mEmpty(empty), mColor(col),\n    mSelectedValue(TypeContainingValue::DOUBLE)\n{\n  if (mFormat.find(\"l\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::INT;\n    SetValue((long long int)value);\n  }\n\n  if (mFormat.find(\"f\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::DOUBLE;\n    SetValue((double)value);\n  }\n\n  if (mFormat.find(\"s\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::STRING;\n    std::string value_temp = std::to_string(value);\n    SetValue(value_temp);\n  }\n\n  if (mFormat.find(\"t\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::TREE;\n    mTree = (unsigned)value;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Constructor for long long int data\n//------------------------------------------------------------------------------\nTableCell::TableCell(long long int value, const std::string& format,\n                     const std::string& unit, bool empty,\n                     TableFormatterColor col)\n  : mFormat(format), mUnit(unit), mEmpty(empty), mColor(col),\n    mSelectedValue(TypeContainingValue::DOUBLE)\n{\n  if (mFormat.find(\"l\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::INT;\n    SetValue(value);\n  }\n\n  if (mFormat.find(\"f\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::DOUBLE;\n    SetValue((double)value);\n  }\n\n  if (mFormat.find(\"s\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::STRING;\n    std::string value_temp = std::to_string(value);\n    SetValue(value_temp);\n  }\n\n  if (mFormat.find(\"t\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::TREE;\n    mTree = (unsigned)value;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Constructor for float data\n//------------------------------------------------------------------------------\nTableCell::TableCell(float value, const std::string& format,\n                     const std::string& unit, bool empty,\n                     TableFormatterColor col)\n  : mFormat(format), mUnit(unit), mEmpty(empty), mColor(col),\n    mSelectedValue(TypeContainingValue::DOUBLE)\n{\n  if (mFormat.find(\"l\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::INT;\n    SetValue((long long int)value);\n  }\n\n  if (mFormat.find(\"f\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::DOUBLE;\n    SetValue((double)value);\n  }\n\n  if (mFormat.find(\"s\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::STRING;\n    std::string value_temp = std::to_string(value);\n    SetValue(value_temp);\n  }\n\n  if (mFormat.find(\"t\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::TREE;\n    mTree = (unsigned)value;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Constructor for double data\n//------------------------------------------------------------------------------\nTableCell::TableCell(double value, const std::string& format,\n                     const std::string& unit, bool empty,\n                     TableFormatterColor col)\n  : mFormat(format), mUnit(unit), mEmpty(empty), mColor(col),\n    mSelectedValue(TypeContainingValue::DOUBLE)\n{\n  if (mFormat.find(\"l\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::INT;\n    SetValue((long long int)value);\n  }\n\n  if (mFormat.find(\"f\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::DOUBLE;\n    SetValue(value);\n  }\n\n  if (mFormat.find(\"s\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::STRING;\n    std::string value_temp = std::to_string(value);\n    SetValue(value_temp);\n  }\n\n  if (mFormat.find(\"t\") != std::string::npos) {\n    mSelectedValue = TypeContainingValue::TREE;\n    mTree = (unsigned)value;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Constructor for char* data\n//------------------------------------------------------------------------------\nTableCell::TableCell(const char* value, const std::string& format,\n                     const std::string& unit, bool empty,\n                     TableFormatterColor col)\n  : mFormat(format), mUnit(unit), mEmpty(empty), mColor(col),\n    mSelectedValue(TypeContainingValue::STRING)\n{\n  std::string value_temp(value);\n  SetValue(value_temp);\n}\n\n//------------------------------------------------------------------------------\n// Constructor for string data\n//------------------------------------------------------------------------------\nTableCell::TableCell(const std::string& value, const std::string& format,\n                     const std::string& unit, bool empty,\n                     TableFormatterColor col)\n  : mFormat(format), mUnit(unit), mEmpty(empty), mColor(col),\n    mSelectedValue(TypeContainingValue::STRING)\n{\n  SetValue(value);\n}\n\n//------------------------------------------------------------------------------\n// Set color of cell\n//------------------------------------------------------------------------------\nvoid TableCell::SetColor(TableFormatterColor color)\n{\n  if (color != DEFAULT) {\n    mColor = color;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Set unsigned long long int value\n//------------------------------------------------------------------------------\nvoid TableCell::SetValue(unsigned long long int value)\n{\n  if (mSelectedValue == TypeContainingValue::UINT) {\n    // If convert unsigned int value into K,M,G,T,P,E scale,\n    // we convert unsigned int value to double\n    if (mFormat.find(\"+\") != std::string::npos && value >= 1000) {\n      mSelectedValue = TypeContainingValue::DOUBLE;\n      SetValue((double)value);\n    } else {\n      m_ullValue = value;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Set long long int value\n//------------------------------------------------------------------------------\nvoid TableCell::SetValue(long long int value)\n{\n  if (mSelectedValue == TypeContainingValue::INT) {\n    // If convert int value into K,M,G,T,P,E scale,\n    // we convert int value to double\n    if (mFormat.find(\"+\") != std::string::npos &&\n        (value >= 1000 || value <= -1000)) {\n      mSelectedValue = TypeContainingValue::DOUBLE;\n      SetValue((double)value);\n    } else {\n      m_llValue = value;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Set double value\n//------------------------------------------------------------------------------\nvoid TableCell::SetValue(double value)\n{\n  if (mSelectedValue == TypeContainingValue::DOUBLE) {\n    // Convert value into f,p,n,u,m,K,M,G,T,P,E scale\n    // double scale = (mUnit == \"B\") ? 1024.0 : 1000.0;\n    double scale = 1000.0;\n\n    // Use IEC standard to display values power of 2\n    // if (mUnit == \"B\") {\n    //   mUnit.insert(0, \"i\");\n    // }\n\n    if (mFormat.find(\"+\") != std::string::npos && value != 0) {\n      bool value_negative = false;\n\n      if (value < 0) {\n        value *= -1;\n        value_negative = true;\n      }\n\n      if (value >= scale * scale * scale * scale * scale * scale) {\n        mUnit.insert(0, \"E\");\n        value /= scale * scale * scale * scale * scale * scale;\n      } else if (value >= scale * scale * scale * scale * scale) {\n        mUnit.insert(0, \"P\");\n        value /= scale * scale * scale * scale * scale;\n      } else if (value >= scale * scale * scale * scale) {\n        mUnit.insert(0, \"T\");\n        value /= scale * scale * scale * scale;\n      } else if (value >= scale * scale * scale) {\n        mUnit.insert(0, \"G\");\n        value /= scale * scale * scale;\n      } else if (value >= scale * scale) {\n        mUnit.insert(0, \"M\");\n        value /= scale * scale;\n      } else if (value >= scale) {\n        mUnit.insert(0, \"K\");\n        value /= scale;\n        //} else if (value >= 1) {\n        //  value = value;\n      } else if (value >= 1 / scale) {\n        mUnit.insert(0, \"m\");\n        value *= scale;\n      } else if (value >= 1 / (scale * scale)) {\n        mUnit.insert(0, \"u\");\n        value *= scale * scale;\n      } else if (value >= 1 / (scale * scale * scale)) {\n        mUnit.insert(0, \"n\");\n        value *= scale * scale * scale;\n      } else if (value >= 1 / (scale * scale * scale * scale)) {\n        mUnit.insert(0, \"p\");\n        value *= scale * scale * scale * scale;\n      } else if (value >= 1 / (scale * scale * scale * scale * scale)) {\n        mUnit.insert(0, \"f\");\n        value *= scale * scale * scale * scale * scale;\n      }\n\n      if (value_negative) {\n        value *= -1;\n      }\n    }\n\n    mDoubleValue =  value;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Set string value\n//------------------------------------------------------------------------------\nvoid TableCell::SetValue(const std::string& value)\n{\n  if (mSelectedValue == TypeContainingValue::STRING) {\n    // \" \" -> \"%20\" is for monitoring output\n    if (mFormat.find(\"o\") != std::string::npos) {\n      std::string cpy_val = value;\n      std::string search = \" \";\n      std::string replace = \"%20\";\n      size_t pos = 0;\n\n      while ((pos = cpy_val.find(search, pos)) != std::string::npos) {\n        cpy_val.replace(pos, search.length(), replace);\n        pos += replace.length();\n      }\n\n      mStrValue = cpy_val;\n    } else {\n      mStrValue = value;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print tablecell\n//------------------------------------------------------------------------------\nvoid TableCell::Print(std::ostream& ostream, size_t width_left,\n                      size_t width_right) const\n{\n  ostream.fill(' ');\n\n  // Left space before cellValue\n  if (width_left) {\n    // Because of prefix\n    if (mFormat.find(\"±\") != std::string::npos) {\n      width_left += 3;\n    }\n\n    // Because of escape characters - see TableFromatterColorContainer, we need\n    // to add 4 for bold and dark display, 7 for colored display,\n    // 10 for colored display with background etc.\n    if (mColor == TableFormatterColor::NONE) {\n      // Normal display\n      ostream.width(width_left);\n    } else if (mColor == TableFormatterColor::DEFAULT ||\n               mColor == TableFormatterColor::BOLD ||\n               mColor == TableFormatterColor::DARK) {\n      // Default, bold and dark display\n      ostream.width(width_left + 4);\n    } else if (TableFormatterColor::RED <= mColor &&\n               mColor <= TableFormatterColor::DWHITE) {\n      // Display with color\n      ostream.width(width_left + 7);\n    } else {\n      // Display with color and background\n      ostream.width(width_left + 10);\n    }\n  }\n\n  // Prefix \"±\"\n  if (mFormat.find(\"±\") != std::string::npos) {\n    if (mFormat.find(\"o\") != std::string::npos) {\n      ostream << \"±%20\" ;\n    } else {\n      ostream << \"± \";\n    }\n  }\n\n  // Color\n  if (mFormat.find(\"o\") == std::string::npos) {\n    ostream << getColorSequence(mColor);\n  }\n\n  // Value\n  if (mSelectedValue == TypeContainingValue::UINT) {\n    ostream << m_ullValue;\n  } else if (mSelectedValue == TypeContainingValue::INT) {\n    ostream << m_llValue;\n  } else if (mSelectedValue == TypeContainingValue::DOUBLE) {\n    auto flags = ostream.flags();\n    ostream << std::setprecision(2) << std::fixed << mDoubleValue;\n    ostream.flags(flags);\n  } else if (mSelectedValue == TypeContainingValue::STRING) {\n    ostream << mStrValue;\n  }\n\n  // Color (return color to default)\n  if ((mFormat.find(\"o\") == std::string::npos) &&\n      (mColor != TableFormatterColor::NONE)) {\n    ostream << getColorSequence(TableFormatterColor::DEFAULT);\n  }\n\n  // Postfix \".\"\n  if (mFormat.find(\".\") != std::string::npos) {\n    ostream << \".\";\n  }\n\n  // Unit\n  if (!mUnit.empty()) {\n    if (mFormat.find(\"o\") != std::string::npos) {\n      ostream << \"%20\" << mUnit;\n    } else {\n      ostream << \" \" << mUnit;\n    }\n  }\n\n  // Right space after cellValue\n  if (width_right) {\n    ostream.width(width_right);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print value of tablecell in string, without unit and without color\n//------------------------------------------------------------------------------\nstd::string TableCell::Str()\n{\n  std::stringstream ostream;\n\n  if (mSelectedValue == TypeContainingValue::UINT) {\n    ostream << m_ullValue;\n  } else if (mSelectedValue == TypeContainingValue::INT) {\n    ostream << m_llValue;\n  } else if (mSelectedValue == TypeContainingValue::DOUBLE) {\n    auto flags = ostream.flags();\n    ostream << std::setprecision(2) << std::fixed << mDoubleValue;\n    ostream.flags(flags);\n  } else if (mSelectedValue == TypeContainingValue::STRING) {\n    ostream << mStrValue;\n  }\n\n  return ostream.str();\n}\n\n//------------------------------------------------------------------------------\n// Operators\n//------------------------------------------------------------------------------\nstd::ostream& operator<<(std::ostream& stream, const TableCell& cell)\n{\n  cell.Print(stream);\n  return stream;\n}\n\n//------------------------------------------------------------------------------\n// if we don't need print for this cell (for monitoring option)\n//------------------------------------------------------------------------------\nbool TableCell::Empty()\n{\n  return mEmpty;\n}\n\n//------------------------------------------------------------------------------\n// Tree\n//------------------------------------------------------------------------------\nunsigned TableCell::Tree()\n{\n  if (mSelectedValue == TypeContainingValue::TREE) {\n    return mTree;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Calculating print width of table cell\n//------------------------------------------------------------------------------\nsize_t TableCell::Length()\n{\n  size_t ret = 0;\n\n  // Value length\n  if (mSelectedValue == TypeContainingValue::UINT) {\n    // Get length of unsigned integer value\n    unsigned long long int temp = m_ullValue;\n\n    if (temp == 0) {\n      ret = 1;\n    }\n\n    while (temp != 0) {\n      ++ret;\n      temp /= 10;\n    }\n  } else   if (mSelectedValue == TypeContainingValue::INT) {\n    // Get length of integer value\n    long long int temp = m_llValue;\n\n    if (temp <= 0) {\n      ret = 1;\n    }\n\n    while (temp != 0) {\n      ++ret;\n      temp /= 10;\n    }\n  } else if (mSelectedValue == TypeContainingValue::DOUBLE) {\n    // Get length of double value\n    std::stringstream temp;\n    auto flags = temp.flags();\n    temp << std::setprecision(2) << std::fixed << mDoubleValue;\n    temp.flags(flags);\n    ret = temp.str().length() ;\n  } else if (mSelectedValue == TypeContainingValue::STRING) {\n    // Get length of string\n    ret = mStrValue.length();\n  }\n\n  // Prefix \"±\"\n  if (mFormat.find(\"±\") != std::string::npos) {\n    ret += 2;\n  }\n\n  // Postfix \".\"\n  if (mFormat.find(\".\") != std::string::npos) {\n    ret += 1;\n  }\n\n  // Unit length\n  if (!mUnit.empty()) {\n    ret += mUnit.length() + 1;\n  }\n\n  return ret;\n}\n"
  },
  {
    "path": "common/table_formatter/TableCell.hh",
    "content": "//------------------------------------------------------------------------------\n// File: TableCell.hh\n// Author: Ivan Arizanovic & Stefan Isidorovic - Comtrade\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __TABLE_CELL__HH__\n#define __TABLE_CELL__HH__\n\n#include \"TableFormatting.hh\"\n\nclass TableCell\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Different types of constructors depending on the type of value added to\n  //! the current cell\n  //!\n  //! @param value of cell\n  //! @param format of cell: \"s\" - print cell as string\n  //!                        \"l\" - print cell as long long\n  //!                        \"f\" - print cell as double\n  //!                        \"t\" - print cell as tree arrows\n  //!                        \"o\" - print cell as monitoring view (TODO (Ivan): Move this in TableFormatterBase::GenerateBody())\n  //!                        \"-\" - left align the printout (TODO (Ivan): Move this only in TableFormatterBase::SetHeader())\n  //!                        \"+\" - convert numbers into f,p,n,u,m,K,M,G,T,P,E scale\n  //!                              (e.g. 2200 with format=\"+\" will be \"2.2 K\")\n  //!                        \"±\" - prefix \"±\" for value (e.g. \"± 22 ms\")\n  //!                        \".\" - postfix \".\" for value (e.g. first one \"1.\")\n  //! @param unit Postfix of cell (e.g. \"2.2 K\" with unit=B will be \"2.2 KB\")\n  //! @param empty If we don't want to see cell in monitoring view\n  //! @param col Color of cell\n  //----------------------------------------------------------------------------\n  TableCell(unsigned int value, const std::string& format,\n            const std::string& unit = \"\", bool empty = false,\n            TableFormatterColor col = TableFormatterColor::NONE);\n\n  TableCell(unsigned long long int value, const std::string& format,\n            const std::string& unit = \"\", bool empty = false,\n            TableFormatterColor col = TableFormatterColor::NONE);\n\n  TableCell(int value, const std::string& format,\n            const std::string& unit = \"\", bool empty = false,\n            TableFormatterColor col = TableFormatterColor::NONE);\n\n  TableCell(long long int value, const std::string& format,\n            const std::string& unit = \"\", bool empty = false,\n            TableFormatterColor col = TableFormatterColor::NONE);\n\n  TableCell(float value, const std::string& format,\n            const std::string& unit = \"\", bool empty = false,\n            TableFormatterColor col = TableFormatterColor::NONE);\n\n  TableCell(double value, const std::string& format,\n            const std::string& unit = \"\", bool empty = false,\n            TableFormatterColor col = TableFormatterColor::NONE);\n\n  TableCell(const char* value, const std::string& format,\n            const std::string& unit = \"\", bool empty = false,\n            TableFormatterColor col = TableFormatterColor::NONE);\n\n  TableCell(const std::string& value, const std::string& format,\n            const std::string& unit = \"\", bool empty = false,\n            TableFormatterColor col = TableFormatterColor::NONE);\n\n  //------------------------------------------------------------------------------\n  //! Set color of cell\n  //------------------------------------------------------------------------------\n  void SetColor(TableFormatterColor color);\n\n  //----------------------------------------------------------------------------\n  //! Print table cell to stream. Needed to dump data into a stringstream or\n  //! anything overloading std::stringstream\n  //!\n  //! @param ostream output string stream\n  //! @param width_left left padding\n  //! @param width_right  right padding\n  //----------------------------------------------------------------------------\n  void Print(std::ostream& ostream, size_t width_left = 0,\n             size_t width_right = 0) const;\n  std::string Str();\n\n  //----------------------------------------------------------------------------\n  //! Calculate print width of table cell\n  //----------------------------------------------------------------------------\n  size_t Length();\n  bool Empty();\n  unsigned Tree();\n\nprotected:\n  //----------------------------------------------------------------------------\n  //! Set value of the table cell data (convert into K,M,G,T,P,E scale).\n  //! Implementled with guards to prevent the cell having any other value then\n  //! the one initially set.\n  //----------------------------------------------------------------------------\n  void SetValue(unsigned long long int value);\n  void SetValue(long long int value);\n  void SetValue(double value);\n  void SetValue(const std::string& value);\n\n  //----------------------------------------------------------------------------\n  //! Store value for cell\n  //----------------------------------------------------------------------------\n  unsigned long long int m_ullValue = 0;\n  long long int m_llValue = 0;\n  double mDoubleValue = 0.f;\n  std::string mStrValue = \"\";\n  std::string mFormat;\n  std::string mUnit;\n  bool mEmpty;\n  unsigned mTree =\n    0; //0=\"\",1=\"│  \",2=\"└─▶\",3=\"├─▶\",4=\"└──\",5=\"├──\",6=\"───\",7=\"──▶\"\n\n  //----------------------------------------------------------------------------\n  //! Color of the cell\n  //----------------------------------------------------------------------------\n  TableFormatterColor mColor;\n\n  //----------------------------------------------------------------------------\n  //! Type of value stored in the current cell\n  //----------------------------------------------------------------------------\n  enum TypeContainingValue {\n    UINT   = 1,\n    INT    = 2,\n    DOUBLE = 3,\n    STRING = 4,\n    TREE   = 5\n  };\n\n  //! Indicate which value if carrying information\n  TypeContainingValue mSelectedValue;\n\n  //----------------------------------------------------------------------------\n  //! Making sure that a cell will not be created with no arguments or proper\n  //! handling.\n  //----------------------------------------------------------------------------\n  TableCell() = delete;\n};\n\n#endif //__TABLE_CELL__HH__\n"
  },
  {
    "path": "common/table_formatter/TableFormatterBase.cc",
    "content": "//------------------------------------------------------------------------------\n// File: TableFormatterBase.cc\n// Author: Ivan Arizanovic & Stefan Isidorovic - Comtrade\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"TableFormatterBase.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nTableFormatterBase::TableFormatterBase()\n  : mSink(\"\"), mDontColor(false)\n{\n}\n\nTableFormatterBase::TableFormatterBase(bool DontColor)\n  : mSink(\"\"), mDontColor(DontColor)\n{\n}\n\n//------------------------------------------------------------------------------\n// Generate table\n//------------------------------------------------------------------------------\nstd::string TableFormatterBase::GenerateTable(TableFormatterStyle style,\n    const TableString& selections)\n{\n  Style(style);\n  bool body_exist = false;\n\n  // Generate monitoring information in line (option \"-m\")\n  if (!mHeader.empty() &&\n      std::get<2>(mHeader[0]).find(\"o\") != std::string::npos) {\n    body_exist = GenerateMonitoring(selections);\n  }\n\n  // Generate classic table (with/without second table)\n  if (!mHeader.empty() &&\n      std::get<2>(mHeader[0]).find(\"o\") == std::string::npos) {\n    WidthCorrection();\n    GenerateHeader();\n    body_exist = GenerateBody(selections);\n  }\n\n  // Generate string (e.g.second table)\n  if (mHeader.empty()) {\n    body_exist = GenerateBody(selections);\n  }\n\n  if (body_exist) {\n    return mSink.str();\n  } else {\n    return \"\";\n  }\n}\n\n//------------------------------------------------------------------------------\n// Generate monitoring output\n//------------------------------------------------------------------------------\nbool TableFormatterBase::GenerateMonitoring(const TableString& selections)\n{\n  bool body_exist = false;\n\n  for (auto& row : mData) {\n    if (!row.empty()) {\n      std::ostringstream tmp_sink;\n\n      for (size_t i = 0, size = row.size(); i < size; ++i) {\n        if (!row[i].Empty()) {\n          tmp_sink << std::get<0>(mHeader[i]) << \"=\" << row[i] << \" \";\n        }\n      }\n\n      std::string str_sink = tmp_sink.str();\n\n      // Apply selection filter\n      if (selections.empty()) {\n        mSink << str_sink << std::endl;\n        body_exist = true;\n      } else {\n        bool filter_out = false;\n\n        for (const auto& filter : selections) {\n          if (str_sink.find(filter) == std::string::npos) {\n            filter_out = true;\n            break;\n          }\n        }\n\n        if (filter_out) {\n          continue;\n        } else {\n          mSink << str_sink << std::endl;\n          body_exist = true;\n        }\n      }\n    }\n  }\n\n  return body_exist;\n}\n\n//------------------------------------------------------------------------------\n// Generate table separator\n//------------------------------------------------------------------------------\nstd::string\nTableFormatterBase::GenerateSeparator(std::string left, std::string center,\n                                      std::string right, std::string line)\n{\n  std::string separator = left;\n\n  for (size_t i = 0, size = mHeader.size(); i < size; ++i) {\n    for (size_t i2 = 0; i2 < std::get<1>(mHeader[i]); i2++) {\n      separator += line;\n    }\n\n    if (i < size - 1) {\n      separator += center;\n    }\n  }\n\n  separator += right;\n  return separator;\n}\n\n//------------------------------------------------------------------------------\n// Generate table header\n//------------------------------------------------------------------------------\nvoid TableFormatterBase::GenerateHeader()\n{\n  // Top edge of header\n  mSink << GenerateSeparator(mBorderHead[0], mBorderHead[1],\n                             mBorderHead[2], mBorderHead[3])\n        << std::endl;\n\n  for (size_t i = 0, size = mHeader.size(); i < size; ++i) {\n    // Left edge of header\n    if (i == 0) {\n      mSink << mBorderHead[4];\n    }\n\n    // Generate cell\n    if (std::get<2>(mHeader[i]).find(\"-\") == std::string::npos) {\n      mSink.width(std::get<1>(mHeader[i]));\n    }\n\n    mSink << std::get<0>(mHeader[i]);\n\n    if (std::get<2>(mHeader[i]).find(\"-\") != std::string::npos) {\n      mSink.width(std::get<1>(mHeader[i]) - std::get<0>(mHeader[i]).length() +\n                  mBorderHead[5].length());\n    }\n\n    // Add right edge of cell\n    if (i < size - 1) {\n      mSink << mBorderHead[5];\n    }\n  }\n\n  // Right edge of the header\n  mSink << mBorderHead[6] << std::endl;\n  // Bottom edge of the header\n  mSink << GenerateSeparator(mBorderHead[7], mBorderHead[8], mBorderHead[9],\n                             mBorderHead[10])\n        << std::endl;\n}\n\n//------------------------------------------------------------------------------\n// Generate table body\n//------------------------------------------------------------------------------\nbool TableFormatterBase::GenerateBody(const TableString& selections)\n{\n  size_t row_size = 0;\n  size_t string_size = 0;\n  bool body_exist = false;\n  bool row_exist = true;  //true because alone string\n  bool string_exist = false;\n\n  for (auto& row : mData) {\n    if (row.empty()) {\n      // Generate string\n      if (!mString.empty() && !mString[string_size].empty() && row_exist) {\n        if (!mHeader.empty()) {\n          if (row_size > 0 && !mData[row_size - 1].empty()) {\n            // Bottom edge of table, before string\n            mSink << GenerateSeparator(mBorderBody[3], mBorderBody[4],\n                                       mBorderBody[5], mBorderBody[6])\n                  << std::endl;\n            mSink << mString[string_size];\n            body_exist = true;\n            string_exist = true;\n          }\n        } else {\n          // If we have only string, without table\n          mSink << mString[string_size];\n          body_exist = true;\n          string_exist = true;\n        }\n      }\n\n      // Generate separator\n      if (body_exist && !string_exist && selections.empty()) {\n        mSink << GenerateSeparator(mBorderSep[0], mBorderSep[1],\n                                   mBorderSep[2], mBorderSep[3])\n              << std::endl;\n      }\n\n      string_size++;\n    }\n\n    // Generate rows\n    if (!row.empty() && !mHeader.empty()) {\n      std::stringstream output;\n\n      for (size_t i = 0, size = row.size(); i < size; ++i) {\n        // Left edge\n        if (i == 0) {\n          output << mBorderBody[0];\n        }\n\n        if (!mDontColor) {\n          // Change color of cell\n          row[i].SetColor(ChangeColor(std::get<0>(mHeader[i]), row[i].Str()));\n        }\n\n        // Generate tree\n        unsigned tree = row[i].Tree();\n\n        if (1 <= tree && tree <= 7) {\n          size_t tree_name_length = 0; //Length of name above the tree cell in same column\n\n          if (1 <= tree && tree <= 5) {\n            for (int j = row_size; j >= 0; j--) {\n              if (mData[j][i].Tree() == 0) {\n                tree_name_length = mData[j][i].Length();\n                break;\n              }\n            }\n          }\n\n          size_t tree_cell_width = std::get<1>(mHeader[i]);\n          size_t tree_cell_spaces = tree_cell_width - tree_name_length / 2;\n          tree_cell_spaces = (tree_cell_spaces < 2) ? 2 : tree_cell_spaces;\n          std::string arrow {};\n\n          if (tree == 1) { // \"│   \"\n            arrow = mBorderTree[tree];\n            tree_cell_width += 2;\n\n            for (size_t j = 0; j < tree_cell_spaces - 1; j++) {\n              arrow += \" \";\n            }\n          } else if (tree == 2 || tree == 3) { // \"└─▶\", \"├─▶\"\n            arrow = mBorderTree[tree];\n            tree_cell_width += 2;\n\n            for (size_t j = 0; j < tree_cell_spaces - 2; j++) {\n              arrow += mBorderTree[4];\n              tree_cell_width += 2;\n            }\n\n            arrow += mBorderTree[5];\n            tree_cell_width += 2;\n          } else if (tree == 4 || tree == 5) { // \"└──\", \"├──\"\n            arrow = mBorderTree[tree - 2];\n            tree_cell_width += 2;\n\n            for (size_t j = 0; j < tree_cell_spaces - 1; j++) {\n              arrow += mBorderTree[4];\n              tree_cell_width += 2;\n            }\n          } else if (tree == 6) { // \"───\"\n            for (size_t j = 0; j < std::get<1>(mHeader[i]) + 1; j++) {\n              arrow += mBorderTree[4];\n              tree_cell_width += 2;\n            }\n          } else if (tree == 7) { // \"──▶\"\n            for (size_t j = 0; j < std::get<1>(mHeader[i]); j++) {\n              arrow += mBorderTree[4];\n              tree_cell_width += 2;\n            }\n\n            arrow += mBorderTree[5];\n            tree_cell_width += 2;\n          }\n\n          output.width(tree_cell_width);\n          output << arrow;\n        } else {\n          // Generate cell\n          size_t cellspace_width = std::get<1>(mHeader[i]) - row[i].Length();\n\n          if (std::get<2>(mHeader[i]).find(\"-\") == std::string::npos) {\n            row[i].Print(output, cellspace_width, 0);\n          } else {\n            row[i].Print(output, 0, cellspace_width + mBorderBody[1].length());\n          }\n        }\n\n        // Right edge of cell\n        if (i < size - 1 && (tree != 4 && tree != 5 && tree != 6)) {\n          output << mBorderBody[1];\n        }\n      }\n\n      // Right edge of row\n      output << mBorderBody[2] << std::endl;\n      // Filter\n      size_t filter_count = 0;\n\n      for (size_t i = 0, size = selections.size(); i < size; ++i)\n        if (output.str().find(selections[i]) != std::string::npos) {\n          filter_count++;\n        }\n\n      if (filter_count == selections.size()) {\n        // Generate header if string exist before\n        if (row_size > 0 && mData[row_size - 1].empty() &&\n            string_size > 0 && !mString[string_size - 1].empty() && row_exist) {\n          GenerateHeader();\n        }\n\n        // Generate row\n        mSink << output.str();\n        body_exist = true;\n        row_exist = true;\n        string_exist = false;\n      } else {\n        row_exist = false;\n      }\n    }\n\n    row_size++;\n  }\n\n  // Bottom edge\n  if (!mHeader.empty() && !string_exist) {\n    mSink << GenerateSeparator(mBorderBody[3], mBorderBody[4],\n                               mBorderBody[5], mBorderBody[6])\n          << std::endl;\n  }\n\n  return body_exist;\n}\n\n//------------------------------------------------------------------------------\n// Recompute the width of the cells taking into account the data\n//------------------------------------------------------------------------------\nvoid TableFormatterBase::WidthCorrection()\n{\n  for (auto& row : mData) {\n    if (!row.empty())\n      for (size_t i = 0, size = row.size(); i < size; i++) {\n        if (std::get<1>(mHeader[i]) < std::get<0>(mHeader[i]).length()) {\n          std::get<1>(mHeader[i]) = std::get<0>(mHeader[i]).length();\n        }\n\n        if (std::get<1>(mHeader[i]) < row[i].Length()) {\n          std::get<1>(mHeader[i]) = row[i].Length();\n        }\n      }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Set table header\n//------------------------------------------------------------------------------\nvoid TableFormatterBase::SetHeader(const TableHeader& header)\n{\n  if (mHeader.empty()) {\n    mHeader = header;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Add separator to table\n//------------------------------------------------------------------------------\nvoid TableFormatterBase::AddSeparator()\n{\n  mData.emplace_back();\n  mString.emplace_back();\n}\n\n//------------------------------------------------------------------------------\n// Add table data\n//------------------------------------------------------------------------------\nvoid TableFormatterBase::AddRows(const TableData& body)\n{\n  std::copy(body.begin(), body.end(), std::back_inserter(mData));\n}\n\n//------------------------------------------------------------------------------\n// Add string\n//------------------------------------------------------------------------------\nvoid TableFormatterBase::AddString(std::string string)\n{\n  mData.emplace_back();\n  mString.push_back(string);\n}\n\n//------------------------------------------------------------------------------\n// Set cell color\n//------------------------------------------------------------------------------\nTableFormatterColor TableFormatterBase::ChangeColor(std::string header,\n    std::string value)\n{\n  if (mDontColor) {\n    return DEFAULT;\n  }\n\n  // Colors for \"fs ls\", \"node ls\", \"fileinfo\" and \"health\" commands\n  if (header == \"status\" || header == \"active\") {\n    if (value == \"online\") {\n      return BWHITE;\n    }\n\n    if (value == \"offline\" || value == \"unknown\") {\n      return BRED_BGWHITE;\n    }\n\n    if (value == \"overload\") {\n      return BWHITE_BGBLUE;\n    }\n\n    if (value == \"ok\" || value == \"fine\") {\n      return BGREEN;\n    }\n\n    if (value.find(\"warning\") != std::string::npos) {\n      return YELLOW;\n    }\n\n    if (value == \"full\") {\n      return BRED;\n    }\n  }\n\n  // Colors for \"quota ls\"\n  if (header == \"vol-status\" || header == \"ino-status\") {\n    if (value == \"ok\") {\n      return BGREEN;\n    }\n\n    if (value == \"warning\") {\n      return BYELLOW;\n    }\n\n    if (value == \"exceeded\") {\n      return BRED;\n    }\n  }\n\n  return DEFAULT;\n}\n\n//------------------------------------------------------------------------------\n// Set table style\n//------------------------------------------------------------------------------\nvoid TableFormatterBase::Style(TableFormatterStyle style)\n{\n  switch (style) {\n  // Full normal border [Default] (\"│\",\"┌\",\"┬\",\"┐\",\"├\",\"┼\",\"┤\",\"└\",\"┴\",\"┘\",\"─\")\n  case FULL: {\n    std::string head [11] = {\"┌\", \"┬\", \"┐\", \"─\",\n                             \"│\", \"│\", \"│\",\n                             \"├\", \"┴\", \"┤\", \"─\"\n                            };\n    std::string sep [4]   = {\"│\", \"-\", \"│\", \"-\"};\n    std::string body [7]  = {\"│\", \" \", \"│\",\n                             \"└\", \"─\", \"┘\", \"─\"\n                            };\n    std::string tree [6]  = {\"\", \"│\", \"└\", \"├\", \"─\", \"▶\"};\n    std::copy(head, head + 11, mBorderHead);\n    std::copy(sep, sep + 4, mBorderSep);\n    std::copy(body, body + 7, mBorderBody);\n    std::copy(tree, tree + 6, mBorderTree);\n    break;\n  }\n\n  // Full bold border (\"┃\",\"┏\",\"┳\",\"┓\",\"┣\",\"╋\",\"┫\",\"┗\",\"┻\",\"┛\",\"━\")\n  case FULLBOLD: {\n    std::string head [11] = {\"┏\", \"┳\", \"┓\", \"━\",\n                             \"┃\", \"┃\", \"┃\",\n                             \"┣\", \"┻\", \"┫\", \"━\"\n                            };\n    std::string sep [4]   = {\"┃\", \"-\", \"┃\", \"-\"};\n    std::string body [7]  = {\"┃\", \" \", \"┃\",\n                             \"┗\", \"━\", \"┛\", \"━\"\n                            };\n    std::string tree [6]  = {\"\", \"│\", \"└\", \"├\", \"─\", \"▶\"};\n    std::copy(head, head + 11, mBorderHead);\n    std::copy(sep, sep + 4, mBorderSep);\n    std::copy(body, body + 7, mBorderBody);\n    std::copy(tree, tree + 6, mBorderTree);\n    break;\n  }\n\n  // Full double border (\"║\",\"╔\",\"╦\",\"╗\",\"╠\",\"╬\",\"╣\",\"╚\",\"╩\",\"╝\",\"═\")\n  case FULLDOUBLE: {\n    std::string head [11] = {\"╔\", \"╦\", \"╗\", \"═\",\n                             \"║\", \"║\", \"║\",\n                             \"╠\", \"╩\", \"╣\", \"═\"\n                            };\n    std::string sep [4]   = {\"║\", \"-\", \"║\", \"-\"};\n    std::string body [7]  = {\"║\", \" \", \"║\",\n                             \"╚\", \"═\", \"╝\", \"═\"\n                            };\n    std::string tree [6]  = {\"\", \"│\", \"└\", \"├\", \"─\", \"▶\"};\n    std::copy(head, head + 11, mBorderHead);\n    std::copy(sep, sep + 4, mBorderSep);\n    std::copy(body, body + 7, mBorderBody);\n    std::copy(tree, tree + 6, mBorderTree);\n    break;\n  }\n\n  // Header normal border (\"│\",\"┌\",\"┬\",\"┐\",\"├\",\"┼\",\"┤\",\"└\",\"┴\",\"┘\",\"─\")\n  case HEADER: {\n    std::string head [11] = {\"┌\", \"┬\", \"┐\", \"─\",\n                             \"│\", \"│\", \"│\",\n                             \"└\", \"┴\", \"┘\", \"─\"\n                            };\n    std::string sep [4]   = {\" \", \"-\", \" \", \"-\"};\n    std::string body [7]  = {\" \", \" \", \" \"};\n    std::string tree [6]  = {\"\", \"│\", \"└\", \"├\", \"─\", \"▶\"};\n    std::copy(head, head + 11, mBorderHead);\n    std::copy(sep, sep + 4, mBorderSep);\n    std::copy(body, body + 7, mBorderBody);\n    std::copy(tree, tree + 6, mBorderTree);\n    break;\n  }\n\n  // Header normal border with Title\n  case HEADER2: {\n    std::string head [11] = {\"┌\", \"┬\", \"┐\", \"─\",\n                             \"│\", \"│\", \"│\",\n                             \"└\", \"┴\", \"┘\", \"─\"\n                            };\n    std::string sep [4]   = {\" \", \"-\", \" \", \"-\"};\n    std::string body [7]  = {\" \", \" \", \" \",\n                             \"┗\", \"━\", \"┛\", \"━\"\n                            };\n    std::string tree [6]  = {\"\", \"│\", \"└\", \"├\", \"─\", \"▶\"};\n    std::copy(head, head + 11, mBorderHead);\n    std::copy(sep, sep + 4, mBorderSep);\n    std::copy(body, body + 7, mBorderBody);\n    std::copy(tree, tree + 6, mBorderTree);\n    break;\n  }\n\n  // Header bold border (\"┃\",\"┏\",\"┳\",\"┓\",\"┣\",\"╋\",\"┫\",\"┗\",\"┻\",\"┛\",\"━\")\n  case HEADERBOLD: {\n    std::string head [11] = {\"┏\", \"┳\", \"┓\", \"━\",\n                             \"┃\", \"┃\", \"┃\",\n                             \"┗\", \"┻\", \"┛\", \"━\"\n                            };\n    std::string sep [4]   = {\" \", \"-\", \" \", \"-\"};\n    std::string body [7]  = {\" \", \" \", \" \"};\n    std::string tree [6]  = {\"\", \"│\", \"└\", \"├\", \"─\", \"▶\"};\n    std::copy(head, head + 11, mBorderHead);\n    std::copy(sep, sep + 4, mBorderSep);\n    std::copy(body, body + 7, mBorderBody);\n    std::copy(tree, tree + 6, mBorderTree);\n    break;\n  }\n\n  // Header double border (\"║\",\"╔\",\"╦\",\"╗\",\"╠\",\"╬\",\"╣\",\"╚\",\"╩\",\"╝\",\"═\")\n  case HEADERDOUBLE: {\n    std::string head [11] = {\"╔\", \"╦\", \"╗\", \"═\",\n                             \"║\", \"║\", \"║\",\n                             \"╚\", \"╩\", \"╝\", \"═\"\n                            };\n    std::string sep [4]   = {\" \", \"-\", \" \", \"-\"};\n    std::string body [7]  = {\" \", \" \", \" \"};\n    std::string tree [6]  = {\"\", \"│\", \"└\", \"├\", \"─\", \"▶\"};\n    std::copy(head, head + 11, mBorderHead);\n    std::copy(sep, sep + 4, mBorderSep);\n    std::copy(body, body + 7, mBorderBody);\n    std::copy(tree, tree + 6, mBorderTree);\n    break;\n  }\n\n  // Minimal style\n  case MINIMAL: {\n    std::string head [11] = {\" \", \"  \", \" \", \"-\",\n                             \" \", \"  \", \" \",\n                             \" \", \"  \", \" \", \"-\"\n                            };\n    std::string sep [4]   = {\" \", \"  \", \" \", \"-\"};\n    std::string body [7]  = {\" \", \"  \", \" \"};\n    std::string tree [6]  = {\"\", \"│\", \"└\", \"├\", \"─\", \"▶\"};\n    std::copy(head, head + 11, mBorderHead);\n    std::copy(sep, sep + 4, mBorderSep);\n    std::copy(body, body + 7, mBorderBody);\n    std::copy(tree, tree + 6, mBorderTree);\n    break;\n  }\n\n  // Old style\n  case OLD: {\n    std::string head [11] = {\"#-\", \"--\", \"-\", \"-\",\n                             \"# \", \"# \", \"#\",\n                             \"#-\", \"--\", \"-\", \"-\"\n                            };\n    std::string sep [4]   = {\" -\", \"--\", \" \", \"-\"};\n    std::string body [7]  = {\"  \", \"  \", \" \"};\n    std::copy(head, head + 11, mBorderHead);\n    std::copy(sep, sep + 4, mBorderSep);\n    std::copy(body, body + 7, mBorderBody);\n    break;\n  }\n\n  // Old style - wide\n  case OLDWIDE: {\n    std::string head [11] = {\"#-\", \"---\", \"--\", \"-\",\n                             \"# \", \" # \", \" #\",\n                             \"#-\", \"---\", \"--\", \"-\"\n                            };\n    std::string sep [4]   = {\" -\", \"---\", \"- \", \"-\"};\n    std::string body [7]  = {\"  \", \"   \", \"  \"};\n    std::copy(head, head + 11, mBorderHead);\n    std::copy(sep, sep + 4, mBorderSep);\n    std::copy(body, body + 7, mBorderBody);\n    break;\n  }\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "common/table_formatter/TableFormatterBase.hh",
    "content": "//------------------------------------------------------------------------------\n// File: TableFormatterBase.hh\n// Author: Ivan Arizanovic & Stefan Isidorovic - Comtrade\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __TABLE__FORMATTER__HH__\n#define __TABLE__FORMATTER__HH__\n\n#include \"mgm/Namespace.hh\"\n#include \"TableCell.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nclass TableFormatterBase\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  TableFormatterBase();\n  TableFormatterBase(bool DontColor);\n\n  //----------------------------------------------------------------------------\n  //! Set table header\n  //----------------------------------------------------------------------------\n  void SetHeader(const TableHeader& header);\n\n  //----------------------------------------------------------------------------\n  //! Add table data\n  //----------------------------------------------------------------------------\n  void AddRows(const TableData& body);\n\n  //----------------------------------------------------------------------------\n  //! Add string to the current table. This can be anything, even another\n  //! table.\n  //!\n  //! @param string blob to be added to the current table\n  //----------------------------------------------------------------------------\n  void AddString(std::string string);\n\n  //----------------------------------------------------------------------------\n  //! Add separator\n  //----------------------------------------------------------------------------\n  void AddSeparator();\n\n  //----------------------------------------------------------------------------\n  //! Generate table\n  //!\n  //! @param style of the table\n  //! @param selections of the table\n  //!\n  //! @return string representation of the table\n  //----------------------------------------------------------------------------\n  std::string GenerateTable(TableFormatterStyle style = FULL,\n                            const TableString& selections = TableString());\n\n\nprotected:\n  std::stringstream mSink;\n  TableHeader mHeader;\n  TableData mData;\n  TableString mString;\n  bool mDontColor;\n\n\n  //----------------------------------------------------------------------------\n  //! Set cell color\n  //!\n  //! @param header name\n  //! @param value of cell\n  //----------------------------------------------------------------------------\n  TableFormatterColor ChangeColor(std::string header, std::string value);\n\n  //----------------------------------------------------------------------------\n  //! Set table style. This will set the border, separator and body border\n  //! string to be used when generating the table.\n  //----------------------------------------------------------------------------\n  void Style(TableFormatterStyle style);\n\n  //----------------------------------------------------------------------------\n  //! Generate monitoring output\n  //!\n  //! @param selections filter the output\n  //!\n  //! @return true if there is smth to display, otherwise false\n  //----------------------------------------------------------------------------\n  bool GenerateMonitoring(const TableString& selections = TableString());\n\n  //----------------------------------------------------------------------------\n  //! Generate table header\n  //----------------------------------------------------------------------------\n  void GenerateHeader();\n\n  //----------------------------------------------------------------------------\n  //! Generate table body\n  //----------------------------------------------------------------------------\n  bool GenerateBody(const TableString& selections = TableString());\n\n  //----------------------------------------------------------------------------\n  //! Generate separator\n  //!\n  //! @param left left separator\n  //! @param center between cells in the center separator\n  //! @param right right separator\n  //! @param line line separator\n  //----------------------------------------------------------------------------\n  std::string GenerateSeparator(std::string left, std::string center,\n                                std::string right, std::string line);\n\n  //----------------------------------------------------------------------------\n  //! Recompute the width of the cells taking into account the data\n  //----------------------------------------------------------------------------\n  void WidthCorrection();\n\nprivate:\n  std::string mBorderHead [11];\n  std::string mBorderSep [4];\n  std::string mBorderBody [7];\n  std::string mBorderTree [6];\n};\n\nEOSMGMNAMESPACE_END\n\n#endif // __TABLE__FORMATTER__HH__\n"
  },
  {
    "path": "common/table_formatter/TableFormatting.hh",
    "content": "//------------------------------------------------------------------------------\n// File: TableFormatting.hh\n// Author: Ivan Arizanovic & Stefan Isidorovic - Comtrade\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __TABLE__FORMATTING__HH__\n#define __TABLE__FORMATTING__HH__\n\n#include <iomanip>\n#include <iostream>\n#include <sstream>\n#include <string>\n#include <vector>\n#include <tuple>\n\n//! Forward declaration\nclass TableCell;\n\n//------------------------------------------------------------------------------\n//! Typedefs for easier reading and understanding\n//------------------------------------------------------------------------------\nusing HeadCell = std::tuple<std::string, unsigned, std::string>;\nusing TableHeader = std::vector<HeadCell>;\nusing TableRow = std::vector<TableCell>;\nusing TableData = std::vector<TableRow>;\nusing TableString = std::vector<std::string>;\n\nenum TableFormatterColor {\n  NONE         = 0,\n  //Normal display\n  DEFAULT, // used to finish off any of the below formats\n  RED,\n  GREEN,\n  YELLOW,\n  BLUE,\n  MARGARITA,\n  CYAN,\n  WHITE,\n  //Bold display (B...)\n  BOLD,\n  BRED,\n  BGREEN,\n  BYELLOW,\n  BBLUE,\n  BMARGARITA,\n  BCYAN,\n  BWHITE,\n  //Dark display (D...)\n  DARK,\n  DRED,\n  DGREEN,\n  DYELLOW,\n  DBLUE,\n  DMARGARITA,\n  DCYAN,\n  DWHITE,\n  //Bold font with white BackGround (B..._BGWHITE)\n  BRED_BGWHITE,\n  BGREEN_BGWHITE,\n  BYELLOW_BGWHITE,\n  BBLUE_BGWHITE,\n  BMARGARITA_BGWHITE,\n  BCYAN_BGWHITE,\n  //Bold white font with BackGround (BWHITE_BG...)\n  BWHITE_BGRED,\n  BWHITE_BGGREEN,\n  BWHITE_BGYELLOW,\n  BWHITE_BGBLUE,\n  BWHITE_BGMARGARITA,\n  BWHITE_BGCYAN,\n  //Bold yellow font with BackGround (BYELLOW_BG...)\n  BYELLOW_BGRED,\n  BYELLOW_BGGREEN,\n  BYELLOW_BGBLUE,\n  BYELLOW_BGMARGARITA,\n  BYELLOW_BGCYAN\n};\n\nenum TableFormatterStyle {\n  FULL,\n  FULLBOLD,\n  FULLDOUBLE,\n  HEADER,\n  HEADER2,\n  HEADERBOLD,\n  HEADERDOUBLE,\n  MINIMAL,\n  OLD,\n  OLDWIDE\n};\n\n//------------------------------------------------------------------------------\n//! Operators\n//------------------------------------------------------------------------------\nstd::ostream& operator<<(std::ostream& stream, const TableCell& cell);\n\n#endif //__TABLE__FORMATTING__HH__\n"
  },
  {
    "path": "common/thread_id.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_THREAD_ID_HH\n#define EOS_THREAD_ID_HH\n\n#include \"common/Namespace.hh\"\n\n#ifdef __APPLE__\n#include <pthread.h>\n#else\n#include <pthread.h>\n#include <sys/syscall.h>\n#include <unistd.h>\n#endif\n\nEOSCOMMONNAMESPACE_BEGIN\n// Replaces former function-like macros like thread_id(_x_).\n// Macros performed textual substitution at every parse site and collided\n// with identically named members of unrelated types (e.g. rocksdb's\n// ThreadStatus::thread_id) whenever this header was included ahead of the\n// third-party header. Inline functions are scoped and immune to that class\n// of collision.\n#ifdef __APPLE__\nstatic inline pthread_t\nthread_id()\n{\n  return pthread_self();\n}\n#else\nstatic inline pthread_t\nthread_id()\n{\n  return (pthread_t)syscall(SYS_gettid);\n}\n#endif\n\nEOSCOMMONNAMESPACE_END\n#endif // EOS_THREAD_ID_HH\n"
  },
  {
    "path": "common/token/EosTok.cc",
    "content": "// ----------------------------------------------------------------------\n// File: EosTok.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   EosTok.cc\n *\n * @brief  Class providing EOS token support\n *\n */\n\n\n#ifdef __APPLE__\n#define EBADE 52\n#define EKEYEXPIRED 127\n#endif\n\n#include \"EosTok.hh\"\n#include \"proto/ConsoleRequest.pb.h\"\n#include <google/protobuf/util/json_util.h>\n#include \"common/SymKeys.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/Path.hh\"\n#include \"common/RegexWrapper.hh\"\n#include \"common/utils/RandUtils.hh\"\n#include <errno.h>\n#include <iostream>\n#include <XrdOuc/XrdOucEnv.hh>\n#include \"common/Logging.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n\nstd::atomic<uint64_t> EosTok::sTokenGeneration;\n\nEosTok::EosTok()\n{\n  share = std::make_shared<eos::console::TokenEnclosure>();\n  valid = false;\n}\n\nEosTok::~EosTok()\n{\n}\n\nEosTok::EosTok(eos::console::TokenEnclosure& token)\n{\n  share = std::make_shared<eos::console::TokenEnclosure>();\n  share->CopyFrom(token);\n  valid = false;\n}\n\nstd::string\nEosTok::Write(const std::string& key)\n{\n  valid = false;\n  share->set_seed(eos::common::getRandom());\n  // create a unique id for this token\n  share->mutable_token()->set_voucher(\n    eos::common::StringConversion::random_uuidstring());\n\n  if (Serialize()) {\n    return \"\";\n  }\n\n  std::string rkey = std::to_string(share->seed()) + key + std::to_string(\n                       share->seed());\n  Sign(rkey);\n  std::string os;\n  share->SerializeToString(&os);\n  std::string zb64os;\n  eos::common::SymKey::ZBase64(os, zb64os);\n  zb64os.replace(0, 5, \"zteos\");\n  eos::common::StringConversion::Replace(zb64os, '/', '_');\n  eos::common::StringConversion::Replace(zb64os, '+', '-');\n  // encode the padding\n  ssize_t pad = 0;\n\n  if (zb64os.back() == '=') {\n    zb64os.pop_back();\n    pad++;\n  }\n\n  if (zb64os.back() == '=') {\n    zb64os.pop_back();\n    pad++;\n  }\n\n  for (auto i = 0; i < pad; i++) {\n    zb64os += \"%3d\";\n  }\n\n  return zb64os;\n}\n\nint\nEosTok::Read(const std::string& zb64is, const std::string& key,\n             uint64_t generation, bool ignoreerror)\n{\n  std::string is;\n  std::string nzb64is(zb64is);\n\n  if (nzb64is.substr(0, 5) != \"zteos\") {\n    return -EINVAL;\n  }\n\n  if ((nzb64is.substr(0, 10) == \"zteos64%3A\") ||\n      (nzb64is.substr(0, 10) == \"zteos64%3a\")) {\n    // support URL encoding\n    nzb64is.replace(0, 10, \"zbase64:\");\n  } else {\n    nzb64is.replace(0, 5, \"zbase\");\n  }\n\n  eos::common::StringConversion::Replace(nzb64is, '_', '/');\n  eos::common::StringConversion::Replace(nzb64is, '-', '+');\n  // deocde the padding\n  size_t l = nzb64is.length();\n  ssize_t l1 = l - 6;\n  ssize_t l2 = l - 3;\n  ssize_t pad = 0;\n\n  if (l1 >= 0)  {\n    if ((nzb64is.substr(l1, 3) == \"%3d\") ||\n        (nzb64is.substr(l1, 3) == \"%3D\")) {\n      pad++;\n    }\n  }\n\n  if (l2 >= 0) {\n    if ((nzb64is.substr(l2, 3) == \"%3d\") ||\n        (nzb64is.substr(l2, 3) == \"%3D\")) {\n      pad++;\n    }\n  }\n\n  nzb64is.erase(l - (pad * 3));\n\n  for (auto i = 0; i < pad; ++i) {\n    nzb64is += \"=\";\n  }\n\n  if (!eos::common::SymKey::ZDeBase64(nzb64is, is)) {\n    return -EINVAL;\n  }\n\n  if (!share->ParseFromString(is)) {\n    return -EINVAL;\n  }\n\n  Deserialize();\n  time_t now = time(NULL);\n\n  if (!ignoreerror) {\n    if ((time_t)share->token().expires() < now) {\n      return -EKEYEXPIRED;\n    }\n\n    if (generation != share->token().generation()) {\n      return -EACCES;\n    }\n  }\n\n  return Verify(key);\n}\n\nint\nEosTok::Sign(const std::string& key)\n{\n  std::string nkey(key);\n  std::string nserialized = share->serialized();\n  share->set_signature(eos::common::SymKey::HmacSha256(nkey, nserialized));\n  return 0;\n}\n\n\nint\nEosTok::Verify(const std::string& key)\n{\n  std::string nkey = std::to_string(share->seed()) + key + std::to_string(\n                       share->seed());\n  std::string nserialized = share->serialized();\n  std::string sign = eos::common::SymKey::HmacSha256(nkey, nserialized);\n\n  if (sign != share->signature()) {\n    return -EPERM;\n  }\n\n  valid = true;\n  return 0;\n}\n\nint\nEosTok::Serialize()\n{\n  std::string os;\n  share->token().SerializeToString(&os);\n  share->set_serialized(os);\n  return 0;\n}\n\nint\nEosTok::Deserialize()\n{\n  return !share->mutable_token()->ParseFromString(share->serialized());\n}\n\nint\nEosTok::Dump(std::string& dump, bool filtersec, bool oneline)\n{\n  dump = \"\";\n  google::protobuf::util::JsonPrintOptions options;\n  options.add_whitespace = true;\n#if GOOGLE_PROTOBUF_VERSION >= 5027000\n  options.always_print_fields_with_no_presence = true;\n#else\n  options.always_print_primitive_fields = true;\n#endif\n  (void) google::protobuf::util::MessageToJsonString(*share,\n      &dump, options);\n\n  if (filtersec) {\n    std::istringstream f(dump);\n    std::string line;\n    std::string filtereddump;\n\n    while (std::getline(f, line)) {\n      if ((line.find(\"\\\"signature\\\"\") != std::string::npos) ||\n          (line.find(\"\\\"serialized\\\"\") != std::string::npos) ||\n          (line.find(\"\\\"voucher\\\"\") != std::string::npos) ||\n          (line.find(\"\\\"requester\\\"\") != std::string::npos) ||\n          (line.find(\"\\\"seed\\\"\") != std::string::npos)) {\n      } else {\n        filtereddump += line;\n\n        if (!oneline) {\n          filtereddump += \"\\n\";\n        }\n      }\n    }\n\n    dump = filtereddump;\n  }\n\n  return 0;\n}\n\n\nint\nEosTok::Reset()\n{\n  share->Clear();\n  valid = false;\n  return 0;\n}\n\nint\nEosTok::SetPath(const std::string& path, bool subtree)\n{\n  share->mutable_token()->set_path(path);\n  share->mutable_token()->set_allowtree(subtree);\n  return 0;\n}\n\nint\nEosTok::SetPermission(const std::string& perm)\n{\n  share->mutable_token()->set_permission(perm);\n  return 0;\n}\n\nint\nEosTok::SetOwner(const std::string& owner)\n{\n  share->mutable_token()->set_owner(owner);\n  return 0;\n}\n\nint\nEosTok::SetGroup(const std::string& group)\n{\n  share->mutable_token()->set_group(group);\n  return 0;\n}\n\nint\nEosTok::SetExpires(time_t expires)\n{\n  share->mutable_token()->set_expires(expires);\n  return 0;\n}\n\n\nint\nEosTok::SetGeneration(uint64_t generation)\n{\n  share->mutable_token()->set_generation(generation);\n  return 0;\n}\n\nint\nEosTok::SetRequester(const std::string& requester)\n{\n  share->mutable_token()->set_requester(requester);\n  return 0;\n}\n\nint\nEosTok::AddOrigin(const std::string& host, const std::string& name,\n                  const std::string& prot)\n{\n  eos::console::TokenAuth* auth = share->mutable_token()->add_origins();\n  auth->set_prot(prot);\n  auth->set_host(host);\n  auth->set_name(name);\n  return 0;\n}\n\nint\nEosTok::VerifyOrigin(const std::string& host, const std::string& name,\n                     const std::string& prot)\n{\n  // if no origin is defined, it always matches\n  if (!share->token().origins_size()) {\n    return 0;\n  }\n\n  for (int i = 0; i < share->token().origins_size(); ++i) {\n    const eos::console::TokenAuth& auth = share->token().origins(i);\n    int m1 = Match(host, auth.host());\n    int m2 = Match(name, auth.name());\n    int m3 = Match(prot, auth.prot());\n    eos_static_debug(\"m1=%i m2=%i m3=%i\", m1, m2, m3);\n\n    if ((m1 < 0) || (m2 < 0) || (m3 < 0)) {\n      return -EBADE;\n    }\n\n    if ((m1 == 1) && (m2 == 1) && (m3 == 1)) {\n      return 0;\n    }\n  }\n\n  return -ENODATA;\n}\n\nint\nEosTok::Match(const std::string& input, const std::string& regexString)\n{\n  eos_static_debug(\"input=\\\"%s\\\" regex=\\\"%s\\\"\", input.c_str(),\n                   regexString.c_str());\n\n  if (input == regexString) {\n    return 1;\n  }\n\n  return (eos::common::eos_regex_match(input, regexString) ? 1 : -1);\n}\n\nbool\nEosTok::TreeToken() const\n{\n  return share->token().allowtree();\n}\n\nint\nEosTok::ValidatePath(const std::string& path) const\n{\n  // this function can now deal to have several paths listed in the token path e.g.\n  // '/eos/dir1/://:/eos/dir2/://:/eos/dir3/' using '://:' as separator which cannot occur in a regular normalized path!\n  std::vector<std::string> paths;\n  eos::common::StringConversion::MulticharTokenize(share->token().path(),\n      paths, \"://:\");\n\n  for (auto p : paths) {\n    if (share->token().allowtree()) {\n      eos_static_debug(\"comparing %s <=> %s\", path.substr(0, p.length()).c_str(),\n                       p.c_str());\n\n      // this is a tree permission\n      if (path.substr(0, p.length()) != p) {\n        continue;\n      }\n\n      // we have a match!\n      return 0;\n    } else {\n      if ((path.back() == '/') && (p.back() != '/')) {\n        eos::common::Path cPath(p);\n\n        if (path == cPath.GetParentPath()) {\n          return 0;\n        }\n      }\n\n      // this is an exact permission\n      if (path != p) {\n        continue;\n      }\n\n      // we have an exact path match\n      return 0;\n    }\n  }\n\n  return -EACCES;\n}\n\nbool\nEosTok::Valid() const\n{\n  return valid;\n}\n\nstd::string\nEosTok::Owner() const\n{\n  return share->token().owner();\n}\n\nstd::string\nEosTok::Group() const\n{\n  return share->token().group();\n}\n\nint\nEosTok::Generation() const\n{\n  return share->token().generation();\n}\n\nstd::string\nEosTok::Permission() const\n{\n  return share->token().permission();\n}\n\nstd::string\nEosTok::Path() const\n{\n  return share->token().path();\n}\n\nstd::string\nEosTok::Voucher() const\n{\n  return share->token().voucher();\n}\n\ntime_t\nEosTok::Expires() const\n{\n  return share->token().expires();\n}\n\nstd::string\nEosTok::Requester() const\n{\n  return share->token().requester();\n}\n\nbool\nEosTok::IsEosToken(XrdOucEnv* env)\n{\n  static const std::string http_enc_tag = \"Bearer%20zteos64\";\n  static const std::string http_tag = \"Bearer zteos64\";\n  static const std::string tag = \"zteos64\";\n\n  if (env) {\n    const char* authz_opaque = env->Get(\"authz\");\n\n    if (authz_opaque) {\n      if (strncmp(authz_opaque, http_enc_tag.c_str(),\n                  http_enc_tag.length()) == 0) {\n        return true;\n      }\n\n      if (strncmp(authz_opaque, http_tag.c_str(), http_tag.length()) == 0) {\n        return true;\n      }\n\n      if (strncmp(authz_opaque, tag.c_str(), tag.length()) == 0) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/token/EosTok.hh",
    "content": "// ----------------------------------------------------------------------\n// File: EosToke.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   EosTok.hh\n *\n * @brief  Class providing EOS token support\n *\n */\n\n#pragma once\n\n#include \"Token.hh\"\n#include \"common/Namespace.hh\"\n#include <memory>\n#include <atomic>\nnamespace  eos\n{\nnamespace console\n{\nclass TokenEnclosure;\n}\n}\n\nclass XrdOucEnv;\n\nEOSCOMMONNAMESPACE_BEGIN\n\n\n\nclass EosTok : public Token\n{\npublic:\n\n  EosTok();\n  EosTok(eos::console::TokenEnclosure& token);\n\n  virtual ~EosTok();\n\n  virtual std::string Write(const std::string& key);\n  virtual int Read(const std::string& input, const std::string& key,\n                   uint64_t generation, bool ignoreerror = false);\n\n  virtual int Reset();\n  virtual int Serialize();\n  virtual int Deserialize();\n  virtual int Sign(const std::string& key);\n  virtual int Verify(const std::string& key);\n  virtual int Dump(std::string& dump, bool filtersec = false,\n                   bool oneline = false);\n  virtual int SetPath(const std::string& path, bool subtree);\n  virtual int SetPermission(const std::string& perm);\n  virtual int SetOwner(const std::string& owner);\n  virtual int SetGroup(const std::string& group);\n  virtual int SetExpires(time_t expires);\n  virtual int SetGeneration(uint64_t generation);\n  virtual int SetRequester(const std::string& requester);\n  virtual int AddOrigin(const std::string& host, const std::string& name,\n                        const std::string& prot);\n  virtual int VerifyOrigin(const std::string& host, const std::string& name,\n                           const std::string& prot);\n  virtual int ValidatePath(const std::string& path) const;\n  virtual bool Valid() const;\n  virtual bool TreeToken() const;\n  virtual std::string Owner() const;\n  virtual std::string Group() const;\n  virtual std::string Permission() const;\n  virtual std::string Path() const;\n  virtual std::string Voucher() const;\n  virtual std::string Requester() const;\n  virtual time_t Expires() const;\n  virtual int Generation() const;\n\n  static std::atomic<uint64_t>\n  sTokenGeneration; ///< generation value for token issuing/verification\n\n  static bool IsEosToken(XrdOucEnv* env);\nprivate:\n\n  int Match(const std::string& input, const std::string& match);\n  std::shared_ptr<eos::console::TokenEnclosure> share;\n  bool valid;\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/token/SciToken.cc",
    "content": "//------------------------------------------------------------------------------\n// File: SciToken.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __APPLE__\n#include \"common/token/SciToken.hh\"\n#include \"common/StringConversion.hh\"\n#include <string>\n#include <scitokens/scitokens.h>\n\neos::common::SciToken* eos::common::SciToken::sSciToken = nullptr;\n\nextern \"C\" {\n\n  //----------------------------------------------------------------------------\n  // C binding for c_scitoken_factory_init\n  //----------------------------------------------------------------------------\n  void*\n  c_scitoken_factory_init(const char* cred, const char* key, const char* keyid,\n                          const char* issuer)\n  {\n    eos::common::SciToken::Init();\n    auto f = eos::common::SciToken::Factory(cred, key, keyid, issuer);\n    return (void*)(f);\n  }\n\n  //----------------------------------------------------------------------------\n  // C binding for c_scitken_create\n  //----------------------------------------------------------------------------\n  int\n  c_scitoken_create(char* token, size_t token_length, time_t expires,\n                    const char* claim1, const char* claim2,\n                    const char* claim3, const char* claim4)\n  {\n    errno = 0;\n\n    if (!eos::common::SciToken::sSciToken) {\n      std::cerr << \"c_sci_token_init was not called\" << std::endl;\n      errno = EFAULT;\n      return -1;\n    }\n\n    std::string stoken;\n    std::set<std::string> claims;\n\n    if (strlen(claim1)) {\n      claims.insert(std::string(claim1));\n    }\n\n    if (strlen(claim2)) {\n      claims.insert(std::string(claim2));\n    }\n\n    if (strlen(claim3)) {\n      claims.insert(std::string(claim3));\n    }\n\n    if (strlen(claim4)) {\n      claims.insert(std::string(claim4));\n    }\n\n    int rc = eos::common::SciToken::sSciToken->CreateToken(stoken, expires,\n             claims);\n\n    if (!rc) {\n      if (token_length > stoken.length()) {\n        memcpy(token, stoken.c_str(), stoken.length());\n        token[stoken.length()] = 0;\n      } else {\n        std::cerr << \"error: token too big for return buffer!\" << std::endl;\n        errno = EFBIG;\n        return -1;\n      }\n\n      return 0;\n    } else {\n      return -1;\n    }\n  }\n}\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Factory method to crate SciToken objects\n//------------------------------------------------------------------------------\nSciToken*\nSciToken::Factory(std::string_view cred, std::string_view key,\n                  std::string_view keyid, std::string_view issuer)\n{\n  static std::mutex g_i_mutex;\n  const std::lock_guard<std::mutex> lock(g_i_mutex);\n\n  if (sSciToken) {\n    return sSciToken;\n  }\n\n  std::string keydata;\n  std::string creddata;\n  eos::common::StringConversion::LoadFileIntoString(key.data(), keydata);\n\n  if (keydata.empty()) {\n    std::cerr << \"error: cannot load private key from '\"\n              << key.data() << \"'\" << std::endl;\n    return nullptr;\n  }\n\n  eos::common::StringConversion::LoadFileIntoString(cred.data(), creddata);\n\n  if (creddata.empty()) {\n    std::cerr << \"error: cannot load public key from '\"\n              << cred.data() << \"'\" << std::endl;\n    return nullptr;\n  }\n\n  sSciToken = new eos::common::SciToken();\n  sSciToken->SetKeys(creddata, keydata, keyid, issuer);\n  return sSciToken;\n}\n\n//------------------------------------------------------------------------------\n// Method to create a new token\n//------------------------------------------------------------------------------\nint\neos::common::SciToken::CreateToken(std::string& scitoken, time_t expires,\n                                   const std::set<std::string>& claims)\n{\n  std::string profile = \"wlcg\";\n  char* err_msg = 0;\n  errno = 0;\n  auto key_raw = scitoken_key_create(mKeyId.c_str(), \"ES256\", mCredData.c_str(),\n                                     mKeyData.c_str(), &err_msg);\n  std::unique_ptr<void, decltype(&scitoken_key_destroy)> key(\n    key_raw, scitoken_key_destroy);\n\n  if (key_raw == nullptr) {\n    std::cerr << \"error: failed to generate a key: \" << err_msg << std::endl;\n    free(err_msg);\n    errno = EFAULT;\n    return -1;\n  }\n\n  std::unique_ptr<void, decltype(&scitoken_destroy)> token(\n    scitoken_create(key_raw), scitoken_destroy);\n\n  if (token.get() == nullptr) {\n    std::cerr << \"error: failed to generate a new token\" << std::endl;\n    errno = EFAULT;\n    return -1;\n  }\n\n  int rv = scitoken_set_claim_string(token.get(), \"iss\",\n                                     mIssuer.c_str(), &err_msg);\n\n  if (rv) {\n    std::cerr << \"error: failed to set issuer: \" << err_msg << std::endl;\n    free(err_msg);\n    errno = EFAULT;\n    return -1;\n  }\n\n  for (const auto& claim : claims) {\n    auto pos = claim.find(\"=\");\n\n    if (pos == std::string::npos) {\n      std::cerr << \"error: claim must contain a '=' character: \"\n                << claim.c_str() << std::endl;\n      errno = EFAULT;\n      return -1;\n    }\n\n    auto key = claim.substr(0, pos);\n    auto val = claim.substr(pos + 1);\n    rv = scitoken_set_claim_string(token.get(), key.c_str(),\n                                   val.c_str(), &err_msg);\n\n    if (rv) {\n      std::cerr << \"error: failed to set claim '\" << key << \"'='\" << val\n                << \"' error:\" << err_msg << std::endl;\n      free(err_msg);\n      errno = EFAULT;\n      return -1;\n    }\n  }\n\n  if (expires) {\n    auto lifetime = expires - time(NULL);\n\n    if (lifetime < 0) {\n      lifetime = 0;\n    }\n\n    scitoken_set_lifetime(token.get(), lifetime);\n  }\n\n  if (profile == \"wlcg\") {\n    profile = SciTokenProfile::WLCG_1_0;\n  } else if (profile == \"scitokens1\") {\n    profile = SciTokenProfile::SCITOKENS_1_0;\n  } else if (profile == \"scitokens2\") {\n    profile = SciTokenProfile::SCITOKENS_2_0;\n  } else if (profile == \"atjwt\") {\n  } else {\n    std::cerr << \"error: unknown token profile: \" << profile << std::endl;\n    errno = EINVAL;\n    return -1;\n  }\n\n  SciTokenProfile sprofile = WLCG_1_0;\n  scitoken_set_serialize_mode(token.get(), sprofile);\n  // finalue dump the token\n  char* value = 0;\n  rv = scitoken_serialize(token.get(), &value, &err_msg);\n\n  if (rv) {\n    std::cerr << \"error: failed to serialize the token: \"\n              << err_msg << std::endl;\n    free(err_msg);\n    errno = EFAULT;\n    return -1;\n  }\n\n  scitoken = value;\n  return 0;\n}\n\nEOSCOMMONNAMESPACE_END\n#endif\n"
  },
  {
    "path": "common/token/SciToken.hh",
    "content": "//------------------------------------------------------------------------------\n// File: SciToken.hh\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include <mutex>\n#include <set>\n#include <string>\n#include <string_view>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! @brief  Class providing SciToken creation functions\n//------------------------------------------------------------------------------\nclass SciToken\n{\npublic:\n  static SciToken* sSciToken;\n\n  //----------------------------------------------------------------------------\n  //! Method to re-initialize the static sSciToken object\n  //----------------------------------------------------------------------------\n  static void Init()\n  {\n    if (sSciToken) {\n      //! @note there is leak here, but this is on purpose and should be fixed\n      //! by extending the scitokens library!\n      sSciToken = nullptr;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Factory method to crate SciToken objects\n  //----------------------------------------------------------------------------\n  static SciToken*\n  Factory(std::string_view cred, std::string_view key,\n          std::string_view keyid, std::string_view issuer);\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  SciToken() = default;\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~SciToken() = default;\n\n  //----------------------------------------------------------------------------\n  //! Method to create a new token\n  //!\n  //! @param SciToken new token object\n  //! @param expires expiration date\n  //! @param claim set of claims to embed in the token\n  //!\n  //! @return 0 if successful, otherwise -1\n  //----------------------------------------------------------------------------\n  int CreateToken(std::string& SciToken, time_t expires,\n                  const std::set<std::string>& claims);\n\n  //----------------------------------------------------------------------------\n  //! Set parameters like keys, credential data and issuer to be used by the\n  //! current SciToken object.\n  //!\n  //! @param creddata credentials data\n  //! @param keydata key data\n  //! @param keyid key id\n  //! @param issuer issuer embedded in the tokens\n  //----------------------------------------------------------------------------\n  void SetKeys(std::string_view creddata, std::string_view keydata,\n               std::string_view keyid, std::string_view issuer)\n  {\n    mKeyData = keydata;\n    mCredData = creddata;\n    mKeyId = keyid;\n    mIssuer = issuer;\n  }\n\nprivate:\n  std::string mCredData;\n  std::string mKeyData;\n  std::string mKeyId;\n  std::string mIssuer;\n};\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/token/Token.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Token.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   Token.hh\n *\n * @brief  Base class for token support\n *\n */\n\n#pragma once\n\n#include <cstdint>\n#include <string>\n\nclass Token {\npublic:\n  Token() {};\n  virtual ~Token() {};\n\n  virtual std::string Write(const std::string& key) = 0;\n  virtual int Read(const std::string& input, const std::string& key, uint64_t generation, bool ignoreexpired) = 0;\n\n\n  virtual int Reset() = 0;\n  virtual int Serialize() = 0;\n  virtual int Deserialize() = 0;\n  virtual int Sign(const std::string& key) = 0;\n  virtual int Verify(const std::string&key) = 0;\n  virtual int Dump(std::string& dump, bool filtersec, bool oneline) = 0;\n  virtual int SetPath(const std::string& path, bool subtree) = 0;\n  virtual int SetPermission(const std::string& perm) = 0;\n  virtual int SetOwner(const std::string& owner) = 0;\n  virtual int SetGroup(const std::string& group) = 0;\n  virtual int SetExpires(time_t expires) = 0;\n  virtual int SetGeneration(uint64_t generation) = 0;\n  virtual int SetRequester(const std::string& requesteor) = 0;\n  virtual int AddOrigin(const std::string& host, const std::string& name, const std::string& prot) = 0;\n  virtual int VerifyOrigin(const std::string& host, const std::string& name, const std::string& prot) = 0;\n  virtual int ValidatePath(const std::string& path) const  = 0;\n  virtual bool Valid() const = 0;\n  virtual bool TreeToken() const = 0;\n  virtual std::string Owner() const = 0;\n  virtual std::string Group() const = 0;\n  virtual std::string Permission() const = 0;\n  virtual std::string Path() const = 0;\n  virtual std::string Voucher() const = 0;\n  virtual std::string Requester() const = 0;\n  virtual int Generation() const = 0;\n};\n"
  },
  {
    "path": "common/token/eosscitokenmodule.c",
    "content": "//------------------------------------------------------------------------------\n// File: eosscitokenmodule.c\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <Python.h>\n#include <stdlib.h>\n\n// Declare your original C functions here\nextern void* c_scitoken_factory_init(const char* cred, const char* key,\n                                     const char* keyid, const char* issuer);\nextern int c_scitoken_create(char* token, size_t token_length, time_t validity,\n                             const char* claim1, const char* claim2,\n                             const char* claim3, const char* claim4);\n\n// Wrapper for c_scitoken_factory_init\nstatic PyObject*\npy_c_scitoken_factory_init(PyObject* self, PyObject* args)\n{\n  const char* cred;\n  const char* key;\n  const char* keyid;\n  const char* issuer;\n\n  // Parse arguments from Python (all strings)\n  if (!PyArg_ParseTuple(args, \"ssss\", &cred, &key, &keyid, &issuer)) {\n    return NULL;\n  }\n\n  // Call the C function\n  void* result = c_scitoken_factory_init(cred, key, keyid, issuer);\n\n  // Return the pointer as a Python integer (or None if NULL)\n  if (result == NULL) {\n    Py_RETURN_NONE;\n  } else {\n    return PyLong_FromVoidPtr(result);\n  }\n}\n\n// Wrapper for c_scitoken_create\nstatic PyObject*\npy_c_scitoken_create(PyObject* self, PyObject* args)\n{\n  time_t validity = 0;\n  const char* claim1 = \"\";\n  const char* claim2 = \"\";\n  const char* claim3 = \"\";\n  const char* claim4 = \"\";\n  size_t token_length;\n\n  // Parse arguments from Python\n  PyObject* token_buffer_obj;\n  if (!PyArg_ParseTuple(args, \"O!kL|ssss\", &PyByteArray_Type, &token_buffer_obj,\n                        &token_length, &validity, &claim1, &claim2, &claim3,\n                        &claim4)) {\n    return NULL;\n  }\n\n  // Get the pointer to the token buffer\n  char* token_buffer = PyByteArray_AsString(token_buffer_obj);\n\n  // Call the C function\n  int result = c_scitoken_create(token_buffer, token_length, validity, claim1,\n                                 claim2, claim3, claim4);\n\n  // Return the result as an integer\n  return PyLong_FromLong(result);\n}\n\n// Define the module's methods\nstatic PyMethodDef SciTokenMethods[] = {\n    {\"c_scitoken_factory_init\", py_c_scitoken_factory_init, METH_VARARGS,\n     \"Initialize SciToken factory\"},\n    {\"c_scitoken_create\", py_c_scitoken_create, METH_VARARGS,\n     \"Create SciToken\"},\n    {NULL, NULL, 0, NULL}};\n\n// Define the module\nstatic struct PyModuleDef scitokenmodule = {\n    PyModuleDef_HEAD_INIT,\n    \"eosscitoken\",  // Module name\n    NULL,           // Module documentation\n    -1,             // Size of per-interpreter state of the module\n    SciTokenMethods // Module's methods\n};\n\n// Module initialization function\nPyMODINIT_FUNC\nPyInit_eosscitoken(void)\n{\n  return PyModule_Create(&scitokenmodule);\n}\n"
  },
  {
    "path": "common/token/example/eossci.py",
    "content": "#/usr/bin/env python3\nimport eosscitoken\n\ncred = \"/etc/xrootd/eos-pkey.pem\"\nkey = \"/etc/xrootd/eos-key.pem\"\nkeyid = \"eos\"\nissuer = \"localhost\"\nfactory_instance = eosscitoken.c_scitoken_factory_init(cred, key, keyid, issuer)\ntoken_length = 4096\ntoken_buffer = bytearray(token_length)\nresult = eosscitoken.c_scitoken_create(token_buffer, token_length, 86400,\"scope=storage.read:\\\"/eos/alice/grid/01/16384/18d1b39d-0124-4517-898d-81547ddf1016\\\"\",\"\",\"\")\nprint(result)\nprint(token_buffer.split(b'\\x00', 1)[0].decode('utf-8'))\n"
  },
  {
    "path": "common/token/scitoken.h",
    "content": "//------------------------------------------------------------------------------\n// File: scitoken.h\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/ASwitzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\nextern \"C\" {\n  void* c_scitoken_factory_init(const char* cred, const char* key,\n                                const char* keyid, const char* issuer);\n\n  int   c_scitoken_create(char* token, size_t token_length, time_t expires,\n                          const char* claim1 = \"\", const char* claim2 = \"\",\n                          const char* claim3 = \"\", const char* claim4 = \"\");\n}\n"
  },
  {
    "path": "common/token/setup.py",
    "content": "from setuptools import setup, Extension\nimport distutils.command.build\nimport os\n\n# Set the name of your library without the \"lib\" prefix or extension\n# For example, \"scitoken\" will refer to \"libscitoken.so\" on Linux/macOS or \"scitoken.dll\" on Windows\nlibrary_name = \"EosCommon\"\n\ndestdir = os.environ.get('DESTDIR', '')\ncustom_build_base = os.environ.get('BUILD_BASE', '')\ncustom_source_base = os.environ.get(\"SOURCE_BASE\", \"\")\nsource = \"\".join([custom_source_base, \"eosscitokenmodule.c\"])\n\nprint(\"info: Using custom_build_base=\\\"{}\\\"\".format(custom_build_base))\nprint(\"info: Using custom_source_base=\\\"{}\\\"\".format(custom_source_base))\n\n# Override build command to set a custom build_base as destination for the\n# build artifacts.\nclass BuildCommand(distutils.command.build.build):\n    def initialize_options(self):\n        distutils.command.build.build.initialize_options(self)\n        self.build_base = custom_build_base\n\n# Set the path to the directory containing the shared library, if it's not in a standard location\nlibrary_dir = f\"{destdir}/usr/lib64/\"\n\nmodule = Extension(\n    'eosscitoken',\n    sources=[source],               # Only the wrapper C extension file\n    libraries=[library_name],       # Link against the shared library\n    library_dirs=[library_dir],     # Directory where the library is located\n)\n\nsetup(\n    name='eosscitoken',\n    version='1.0',\n    description='Python interface for EOS SciToken C library',\n    ext_modules=[module],\n    cmdclass={\"build\": BuildCommand},\n)\n"
  },
  {
    "path": "common/ulib/hash_align.h",
    "content": "/* The MIT License\n\n   Copyright (C) 2011, 2012 Zilong Tan (eric.zltan@gmail.com)\n\n   Permission is hereby granted, free of charge, to any person obtaining\n   a copy of this software and associated documentation files (the\n   \"Software\"), to deal in the Software without restriction, including\n   without limitation the rights to use, copy, modify, merge, publish,\n   distribute, sublicense, and/or sell copies of the Software, and to\n   permit persons to whom the Software is furnished to do so, subject to\n   the following conditions:\n\n   The above copyright notice and this permission notice shall be\n   included in all copies or substantial portions of the Software.\n\n   THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n   SOFTWARE.\n*/\n\n/*\n  This file provides proxy classes for alignhash. The origial\n  implementation is in C, which is located in alignhash_tpl.h. A few\n  points should be noted before using these classes:\n\n  (1) Several implicit operators are used for key, namely '==', '<',\n  and the unsigned long operator. These operators must be implemented\n  on the part of the key class.\n\n  (2) Alignhash iterator does not have 'first' and 'second' members,\n  key() and value() are respectively responsible for accessing the key\n  and value associated with the iterator instead.\n\n  (3) Optional flags can be specified preceding alignhash.h, such as\n  AH_DOUBLE_HASHING.\n*/\n\n#ifndef _ULIB_HASH_ALIGN_H\n#define _ULIB_HASH_ALIGN_H\n\n#include \"util_class.h\"\n#include \"hash_align_prot.h\"\n\nnamespace ulib {\n\ntemplate<class _Key, class _Val, class _Except = ulib_except>\nclass align_hash_map\n{\npublic:\n\tDEFINE_ALIGNHASH(inclass, _Key, _Val, 1, alignhash_hashfn, alignhash_equalfn);\n\n\ttypedef _Key\t    key_type;\n\ttypedef _Val\t    value_type;\n\ttypedef ah_iter_t   size_type;\n\ttypedef _Val *\t    pointer;\n\ttypedef const _Val* const_pointer;\n\ttypedef _Val &\t    reference;\n\ttypedef const _Val& const_reference;\n\ttypedef ah_iter_t   hashing_iterator;\n\ttypedef alignhash_t(inclass) * hashing;\n\n\tstruct iterator\n\t{\n\t\ttypedef ah_iter_t size_type;\n\t\ttypedef _Val& reference;\n\t\ttypedef _Val* pointer;\n\t\ttypedef alignhash_t(inclass) * hashing;\n\t\ttypedef ah_iter_t hashing_iterator;\n\n\t\thashing\t\t _hashing;\n\t\thashing_iterator _cur;\n\n\t\titerator(hashing h, hashing_iterator itr)\n\t\t\t: _hashing(h), _cur(itr) { }\n\n\t\titerator() { }\n\n\t\t_Key &\n\t\tkey() const\n\t\t{ return alignhash_key(_hashing, _cur); }\n\n\t\treference\n\t\tvalue() const\n\t\t{ return alignhash_value(_hashing, _cur); }\n\n\t\treference\n\t\toperator*() const\n\t\t{ return value(); }\n\n\t\tpointer\n\t\toperator->() const\n\t\t{ return &(operator*()); }\n\n\t\titerator&\n\t\toperator++()\n\t\t{\n\t\t\tif (_cur != alignhash_end(_hashing))\n\t\t\t\t++_cur;\n\t\t\twhile (!alignhash_exist(_hashing, _cur) && _cur != alignhash_end(_hashing))\n\t\t\t\t++_cur;\n\t\t\treturn *this;\n\t\t}\n\n\t\titerator\n\t\toperator++(int)\n\t\t{\n\t\t\titerator old = *this;\n\t\t\t++*this;\n\t\t\treturn old;\n\t\t}\n\n\t\tbool\n\t\toperator==(const iterator &other) const\n\t\t{ return _cur == other._cur; }\n\n\t\tbool\n\t\toperator!=(const iterator &other) const\n\t\t{ return _cur != other._cur; }\n\t};\n\n\tstruct const_iterator\n\t{\n\t\ttypedef ah_iter_t size_type;\n\t\ttypedef const _Val& reference;\n\t\ttypedef const _Val* pointer;\n\t\ttypedef const alignhash_t(inclass) * hashing;\n\t\ttypedef ah_iter_t hashing_iterator;\n\n\t\thashing\t\t _hashing;\n\t\thashing_iterator _cur;\n\n\t\tconst_iterator(const hashing h, hashing_iterator itr)\n\t\t\t: _hashing(h), _cur(itr) { }\n\n\t\tconst_iterator() { }\n\n\t\tconst_iterator(const iterator &it)\n\t\t\t: _hashing(it._hashing), _cur(it._cur) { }\n\n\t\tconst _Key &\n\t\tkey() const\n\t\t{ return alignhash_key(_hashing, _cur); }\n\n\t\treference\n\t\tvalue() const\n\t\t{ return alignhash_value(_hashing, _cur); }\n\n\t\treference\n\t\toperator*() const\n\t\t{ return value(); }\n\n\t\tpointer\n\t\toperator->() const\n\t\t{ return &(operator*()); }\n\n\t\tconst_iterator&\n\t\toperator++()\n\t\t{\n\t\t\tif (_cur != alignhash_end(_hashing))\n\t\t\t\t++_cur;\n\t\t\twhile (!alignhash_exist(_hashing, _cur) && _cur != alignhash_end(_hashing))\n\t\t\t\t++_cur;\n\t\t\treturn *this;\n\t\t}\n\n\t\tconst_iterator\n\t\toperator++(int)\n\t\t{\n\t\t\tconst_iterator old = *this;\n\t\t\t++*this;\n\t\t\treturn old;\n\t\t}\n\n\t\tbool\n\t\toperator==(const const_iterator &other) const\n\t\t{ return _cur == other._cur; }\n\n\t\tbool\n\t\toperator!=(const const_iterator &other) const\n\t\t{ return _cur != other._cur; }\n\t};\n\n\talign_hash_map()\n\t{\n\t\t_hashing = alignhash_init(inclass);\n\t\tif (_hashing == 0)\n\t\t\tthrow _Except();\n\t}\n\n\talign_hash_map(const align_hash_map &other)\n\t{\n\t\t_hashing = alignhash_init(inclass);\n\t\tif (_hashing == 0)\n\t\t\tthrow _Except();\n\t\tfor (const_iterator it = other.begin(); it != other.end(); ++it)\n\t\t\tinsert(it.key(), it.value());\n\t}\n\n\talign_hash_map &\n\toperator= (const align_hash_map &other)\n\t{\n\t\tif (&other != this) {\n\t\t\tclear();\n\t\t\tfor (const_iterator it = other.begin(); it != other.end(); ++it)\n\t\t\t\tinsert(it.key(), it.value());\n\t\t}\n\t\treturn *this;\n\t}\n\n\tvirtual\n\t~align_hash_map()\n\t{ alignhash_destroy(inclass, _hashing); }\n\n\tsize_type\n\tsize() const\n\t{ return alignhash_size(_hashing); }\n\n\tbool\n\tempty() const\n\t{ return size() == 0; }\n\n\titerator\n\tbegin()\n\t{\n\t\tfor (size_type itr = alignhash_begin(_hashing);\n\t\t     itr != alignhash_end(_hashing); ++itr)\n\t\t\tif (alignhash_exist(_hashing, itr))\n\t\t\t\treturn iterator(_hashing, itr);\n\t\treturn end();\n\t}\n\n\titerator\n\tend()\n\t{ return iterator(_hashing, alignhash_end(_hashing)); }\n\n\tconst_iterator\n\tbegin() const\n\t{\n\t\tfor (size_type itr = alignhash_begin(_hashing);\n\t\t     itr != alignhash_end(_hashing); ++itr)\n\t\t\tif (alignhash_exist(_hashing, itr))\n\t\t\t\treturn iterator(_hashing, itr);\n\t\treturn end();\n\t}\n\n\tconst_iterator\n\tend() const\n\t{ return iterator(_hashing, alignhash_end(_hashing)); }\n\n\tsize_type\n        count() const\n\t{ return alignhash_nbucket(_hashing); }\n\n\tbool\n\tcontain(const _Key &key) const\n\t{ return alignhash_get(inclass, _hashing, key) != alignhash_end(_hashing); }\n\n\titerator\n\tinsert(const _Key &key, const _Val &val, bool replace = false)\n\t{\n\t\tint ret = AH_INS_ERR;\n\t\thashing_iterator itr = alignhash_set(inclass, _hashing, key, &ret);\n\t\tif (itr == alignhash_end(_hashing))\n\t\t\tthrow _Except();\n\t\tif (ret != AH_INS_ERR || replace)\n\t\t\talignhash_value(_hashing, itr) = val;\n\t\treturn iterator(_hashing, itr);\n\t}\n\n\titerator\n\tfind_or_insert(const _Key &key, const _Val &val)\n\t{\n\t\tint ret = AH_INS_ERR;\n\t\thashing_iterator itr = alignhash_set(inclass, _hashing, key, &ret);\n\t\tif (itr == alignhash_end(_hashing))\n\t\t\tthrow _Except();\n\t\tif (ret != AH_INS_ERR)\n\t\t\talignhash_value(_hashing, itr) = val;\n\t\treturn iterator(_hashing, itr);\n\t}\n\n\treference\n\toperator[](const _Key &key)\n\t{ return *find_or_insert(key, _Val()); }\n\n\titerator\n\tfind(const _Key &key)\n\t{ return iterator(_hashing, alignhash_get(inclass, _hashing, key)); }\n\n\tconst_iterator\n\tfind(const _Key &key) const\n\t{ return const_iterator(_hashing, alignhash_get(inclass, _hashing, key)); }\n\n\tvoid\n\terase(const _Key &key)\n\t{ alignhash_del(inclass, _hashing, alignhash_get(inclass, _hashing, key)); }\n\n\tvoid\n\terase(const iterator &it)\n\t{ alignhash_del(inclass, _hashing, it._cur); }\n\n\tvoid\n\tclear()\n\t{ alignhash_clear(inclass, _hashing); }\n\n\tint\n\tresize(size_t n)\n\t{\n\t\tif (n & (n - 1)) {\n\t\t\t// reject any value that is not the power of two\n\t\t\treturn -1;\n\t\t}\n\t\treturn alignhash_resize(inclass, _hashing, n);\n\t}\n\nprivate:\n\thashing _hashing;\n};\n\ntemplate<class _Key, class _Except = ulib_except>\nclass align_hash_set\n{\npublic:\n\tDEFINE_ALIGNHASH(inclass, _Key, int, 0, alignhash_hashfn, alignhash_equalfn);\n\n\ttypedef _Key\t  key_type;\n\ttypedef ah_iter_t size_type;\n\ttypedef ah_iter_t hashing_iterator;\n\ttypedef alignhash_t(inclass) * hashing;\n\n\tstruct iterator\n\t{\n\t\ttypedef ah_iter_t size_type;\n\t\ttypedef alignhash_t(inclass) * hashing;\n\t\ttypedef ah_iter_t hashing_iterator;\n\n\t\thashing\t\t _hashing;\n\t\thashing_iterator _cur;\n\n\t\titerator(hashing h, hashing_iterator itr)\n\t\t\t: _hashing(h), _cur(itr) { }\n\n\t\titerator() { }\n\n\t\t_Key &\n\t\tkey() const\n\t\t{ return alignhash_key(_hashing, _cur); }\n\n\t\tbool\n\t\tvalue() const\n\t\t{  return _cur != alignhash_end(_hashing); }\n\n\t\tbool\n\t\toperator*() const\n\t\t{ return value(); }\n\n\t\titerator&\n\t\toperator++()\n\t\t{\n\t\t\tif (_cur != alignhash_end(_hashing))\n\t\t\t\t++_cur;\n\t\t\twhile (!alignhash_exist(_hashing, _cur) && _cur != alignhash_end(_hashing))\n\t\t\t\t++_cur;\n\t\t\treturn *this;\n\t\t}\n\n\t\titerator\n\t\toperator++(int)\n\t\t{\n\t\t\titerator old = *this;\n\t\t\t++*this;\n\t\t\treturn old;\n\t\t}\n\n\t\tbool\n\t\toperator==(const iterator &other) const\n\t\t{ return _cur == other._cur; }\n\n\t\tbool\n\t\toperator!=(const iterator &other) const\n\t\t{ return _cur != other._cur; }\n\t};\n\n\tstruct const_iterator\n\t{\n\t\ttypedef ah_iter_t size_type;\n\t\ttypedef const alignhash_t(inclass) * hashing;\n\t\ttypedef ah_iter_t hashing_iterator;\n\n\t\thashing\t\t _hashing;\n\t\thashing_iterator _cur;\n\n\t\tconst_iterator(const hashing h, hashing_iterator itr)\n\t\t\t: _hashing(h), _cur(itr) { }\n\n\t\tconst_iterator() { }\n\n\t\tconst_iterator(const iterator &it)\n\t\t\t: _hashing(it._hashing), _cur(it._cur) { }\n\n\t\tconst _Key &\n\t\tkey() const\n\t\t{ return alignhash_key(_hashing, _cur); }\n\n\t\tbool\n\t\tvalue() const\n\t\t{  return _cur != alignhash_end(_hashing); }\n\n\t\tbool\n\t\toperator*() const\n\t\t{ return value(); }\n\n\t\tconst_iterator&\n\t\toperator++()\n\t\t{\n\t\t\tif (_cur != alignhash_end(_hashing))\n\t\t\t\t++_cur;\n\t\t\twhile (!alignhash_exist(_hashing, _cur) && _cur != alignhash_end(_hashing))\n\t\t\t\t++_cur;\n\t\t\treturn *this;\n\t\t}\n\n\t\tconst_iterator\n\t\toperator++(int)\n\t\t{\n\t\t\tconst_iterator old = *this;\n\t\t\t++*this;\n\t\t\treturn old;\n\t\t}\n\n\t\tbool\n\t\toperator==(const const_iterator &other) const\n\t\t{ return _cur == other._cur; }\n\n\t\tbool\n\t\toperator!=(const const_iterator &other) const\n\t\t{ return _cur != other._cur; }\n\t};\n\n\talign_hash_set()\n\t{\n\t\t_hashing = alignhash_init(inclass);\n\t\tif (_hashing == 0)\n\t\t\tthrow _Except();\n\t}\n\n\talign_hash_set(const align_hash_set &other)\n\t{\n\t\t_hashing = alignhash_init(inclass);\n\t\tif (_hashing == 0)\n\t\t\tthrow _Except();\n\t\tfor (const_iterator it = other.begin(); it != other.end(); ++it)\n\t\t\tinsert(it.key());\n\t}\n\n\talign_hash_set &\n\toperator= (const align_hash_set &other)\n\t{\n\t\tif (&other != this) {\n\t\t\tclear();\n\t\t\tfor (const_iterator it = other.begin(); it != other.end(); ++it)\n\t\t\t\tinsert(it.key());\n\t\t}\n\t\treturn *this;\n\t}\n\n\tvirtual\n\t~align_hash_set()\n\t{ alignhash_destroy(inclass, _hashing); }\n\n\tsize_type\n\tsize() const\n\t{ return alignhash_size(_hashing); }\n\n\tbool\n\tempty() const\n\t{ return size() == 0; }\n\n\titerator\n\tbegin()\n\t{\n\t\tfor (size_type itr = alignhash_begin(_hashing);\n\t\t     itr != alignhash_end(_hashing); ++itr)\n\t\t\tif (alignhash_exist(_hashing, itr))\n\t\t\t\treturn iterator(_hashing, itr);\n\t\treturn end();\n\t}\n\n\titerator\n\tend()\n\t{ return iterator(_hashing, alignhash_end(_hashing)); }\n\n\tconst_iterator\n\tbegin() const\n\t{\n\t\tfor (size_type itr = alignhash_begin(_hashing);\n\t\t     itr != alignhash_end(_hashing); ++itr)\n\t\t\tif (alignhash_exist(_hashing, itr))\n\t\t\t\treturn iterator(_hashing, itr);\n\t\treturn end();\n\t}\n\n\tconst_iterator\n\tend() const\n\t{ return iterator(_hashing, alignhash_end(_hashing)); }\n\n\tsize_type\n\tcount() const\n\t{ return alignhash_nbucket(_hashing); }\n\n\tbool\n\tcontain(const _Key &key) const\n\t{ return alignhash_get(inclass, _hashing, key) != alignhash_end(_hashing); }\n\n\titerator\n\tinsert(const _Key &key)\n\t{\n\t\tint ret;\n\t\thashing_iterator itr = alignhash_set(inclass, _hashing, key, &ret);\n\t\tif (itr == alignhash_end(_hashing))\n\t\t\tthrow _Except();\n\t\treturn iterator(_hashing, itr);\n\t}\n\n\tbool\n\toperator[](const _Key &key) const\n\t{ return contain(key); }\n\n\titerator\n\tfind(const _Key &key)\n\t{ return iterator(_hashing, alignhash_get(inclass, _hashing, key)); }\n\n\tconst_iterator\n\tfind(const _Key &key) const\n\t{ return const_iterator(_hashing, alignhash_get(inclass, _hashing, key)); }\n\n\tvoid\n\terase(const _Key &key)\n\t{ alignhash_del(inclass, _hashing, alignhash_get(inclass, _hashing, key)); }\n\n\tvoid\n\terase(const iterator &it)\n\t{ alignhash_del(inclass, _hashing, it._cur); }\n\n\tvoid\n\tclear()\n\t{ alignhash_clear(inclass, _hashing); }\n\n\tint\n\tresize(size_t n)\n\t{\n\t\tif (n & (n - 1)) {\n\t\t\t// reject any value that is not the power of two\n\t\t\treturn -1;\n\t\t}\n\t\treturn alignhash_resize(inclass, _hashing, n);\n\t}\n\nprivate:\n\thashing _hashing;\n};\n\n}  // namespace ulib\n\n#endif\t/* _ULIB_HASH_ALIGN_H */\n"
  },
  {
    "path": "common/ulib/hash_align_prot.h",
    "content": "/* The MIT License\n\n   Copyright (C) 2011, 2012, 2013 Zilong Tan (eric.zltan@gmail.com)\n   Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>\n\n   Permission is hereby granted, free of charge, to any person obtaining\n   a copy of this software and associated documentation files (the\n   \"Software\"), to deal in the Software without restriction, including\n   without limitation the rights to use, copy, modify, merge, publish,\n   distribute, sublicense, and/or sell copies of the Software, and to\n   permit persons to whom the Software is furnished to do so, subject to\n   the following conditions:\n\n   The above copyright notice and this permission notice shall be\n   included in all copies or substantial portions of the Software.\n\n   THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n   SOFTWARE.\n*/\n\n#ifndef _ULIB_HASH_ALIGN_PROT_H\n#define _ULIB_HASH_ALIGN_PROT_H\n\n#include <stddef.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"util_algo.h\"\n\n#if __WORDSIZE == 64\n\ntypedef uint64_t ah_iter_t;\ntypedef uint64_t ah_size_t;\n\n#define AH_ISDEL(flag, i)\t ( ((flag)[(i) >> 5] >> (((i) & 0x1fU) << 1)) & 1      )\n#define AH_ISEMPTY(flag, i)\t ( ((flag)[(i) >> 5] >> (((i) & 0x1fU) << 1)) & 2      )\n#define AH_ISEITHER(flag, i)\t ( ((flag)[(i) >> 5] >> (((i) & 0x1fU) << 1)) & 3      )\n#define AH_CLEAR_DEL(flag, i)\t (  (flag)[(i) >> 5] &= ~(1ul << (((i) & 0x1fU) << 1)) )\n#define AH_CLEAR_EMPTY(flag, i)\t (  (flag)[(i) >> 5] &= ~(2ul << (((i) & 0x1fU) << 1)) )\n#define AH_CLEAR_BOTH(flag, i)\t (  (flag)[(i) >> 5] &= ~(3ul << (((i) & 0x1fU) << 1)) )\n#define AH_SET_DEL(flag, i)\t (  (flag)[(i) >> 5] |=\t (1ul << (((i) & 0x1fU) << 1)) )\n\n#define AH_FLAGS_BYTE(nb)\t ( (nb) < 32? 8: (nb) >> 2 )\n\n#else\n\ntypedef uint32_t ah_iter_t;\ntypedef uint32_t ah_size_t;\n\n#define AH_ISDEL(flag, i)\t ( ((flag)[(i) >> 4] >> (((i) & 0xfU) << 1)) & 1      )\n#define AH_ISEMPTY(flag, i)\t ( ((flag)[(i) >> 4] >> (((i) & 0xfU) << 1)) & 2      )\n#define AH_ISEITHER(flag, i)\t ( ((flag)[(i) >> 4] >> (((i) & 0xfU) << 1)) & 3      )\n#define AH_CLEAR_DEL(flag, i)\t (  (flag)[(i) >> 4] &= ~(1ul << (((i) & 0xfU) << 1)) )\n#define AH_CLEAR_EMPTY(flag, i)\t (  (flag)[(i) >> 4] &= ~(2ul << (((i) & 0xfU) << 1)) )\n#define AH_CLEAR_BOTH(flag, i)\t (  (flag)[(i) >> 4] &= ~(3ul << (((i) & 0xfU) << 1)) )\n#define AH_SET_DEL(flag, i)\t (  (flag)[(i) >> 4] |=\t (1ul << (((i) & 0xfU) << 1)) )\n\n#define AH_FLAGS_BYTE(nb)\t ( (nb) < 16? 4: (nb) >> 2 )\n\n#endif\n\n/* error codes for alignhash_set() */\nenum {\n\tAH_INS_ERR = 0,\t /**< element exists */\n\tAH_INS_NEW = 1,\t /**< element was placed at a new bucket */\n\tAH_INS_DEL = 2\t /**< element was placed at a deleted bucket */\n};\n\n/* Double hashing can be specified by defining\n * AH_DOUBLE_HASHING. Double hashing is preferable only when either\n * the hash table is relatively small, i.e. the flag bit array fits\n * into the CPU cache, or the key hash value distribution is biased. */\n#ifdef AH_DOUBLE_HASHING\n#define AH_PROBING_STEP(h)\t ( ((h) ^ (h) >> 3) | 1 )\n#define AH_LOAD_FACTOR\t\t 0.85\n#else\n#define AH_PROBING_STEP(h)\t ( 1 )\n#define AH_LOAD_FACTOR\t\t 0.77\n#endif\n\n#define DEFINE_ALIGNHASH_RAW(name, key_t, keyref_t, val_t, ismap, hashfn, equalfn) \\\n\ttypedef struct {\t\t\t\t\t\t\\\n\t\tah_size_t nbucket;\t\t\t\t\t\\\n\t\tah_size_t nelem;\t\t\t\t\t\\\n\t\tah_size_t noccupied;\t\t\t\t\t\\\n\t\tah_size_t bound;\t\t\t\t\t\\\n\t\tah_size_t *flags;\t\t\t\t\t\\\n\t\tkey_t\t  *keys;\t\t\t\t\t\\\n\t\tval_t\t  *vals;\t\t\t\t\t\\\n\t} alignhash_##name##_t;\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\\\n\tstatic inline alignhash_##name##_t *\t\t\t\t\\\n\talignhash_init_##name()\t\t\t\t\t\t\\\n\t{\t\t\t\t\t\t\t\t\\\n\t\treturn (alignhash_##name##_t*)\t\t\t\t\\\n\t\t\tcalloc(1, sizeof(alignhash_##name##_t));\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\\\n\tstatic inline void\t\t\t\t\t\t\\\n\talignhash_destroy_##name(alignhash_##name##_t *h)\t\t\\\n\t{\t\t\t\t\t\t\t\t\\\n\t\tif (h) {\t\t\t\t\t\t\\\n\t\t\tfree(h->flags);\t\t\t\t\t\\\n\t\t\tfree(h->keys);\t\t\t\t\t\\\n\t\t\tfree(h->vals);\t\t\t\t\t\\\n\t\t\tfree(h);\t\t\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\\\n\tstatic inline void\t\t\t\t\t\t\\\n\talignhash_clear_##name(alignhash_##name##_t *h)\t\t\t\\\n\t{\t\t\t\t\t\t\t\t\\\n\t\tif (h && h->flags) {\t\t\t\t\t\\\n\t\t\tmemset(h->flags, 0xaa, AH_FLAGS_BYTE(h->nbucket)); \\\n\t\t\th->nelem  = 0;\t\t\t\t\t\\\n\t\t\th->noccupied = 0;\t\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\\\n\tstatic inline ah_iter_t\t\t\t\t\t\t\\\n\talignhash_get_##name(const alignhash_##name##_t *h, keyref_t key) \\\n\t{\t\t\t\t\t\t\t\t\\\n\t\tif (h->nbucket) {\t\t\t\t\t\\\n\t\t\tah_size_t i, k, step, last;\t\t\t\\\n\t\t\tah_size_t mask = h->nbucket - 1;\t\t\\\n\t\t\tk = hashfn(key);\t\t\t\t\\\n\t\t\ti = k & mask;\t\t\t\t\t\\\n\t\t\tstep = AH_PROBING_STEP(k);\t\t\t\\\n\t\t\tlast = i;\t\t\t\t\t\\\n\t\t\twhile (!AH_ISEMPTY(h->flags, i) &&\t\t\\\n\t\t\t       (AH_ISDEL(h->flags, i) || !equalfn(h->keys[i], key))) { \\\n\t\t\t\ti = (i + step) & mask;\t\t\t\\\n\t\t\t\tif (i == last)\t\t\t\t\\\n\t\t\t\t\treturn h->nbucket;\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t\treturn AH_ISEMPTY(h->flags, i)? h->nbucket : i;\t\\\n\t\t} else\t\t\t\t\t\t\t\\\n\t\t\treturn 0;\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\\\n\tstatic inline int\t\t\t\t\t\t\\\n\talignhash_resize_##name(alignhash_##name##_t *h, ah_size_t nbucket) \\\n\t{\t\t\t\t\t\t\t\t\\\n\t\tah_size_t *flags;\t\t\t\t\t\\\n\t\tkey_t\t  *keys;\t\t\t\t\t\\\n\t\tval_t\t  *vals;\t\t\t\t\t\\\n\t\tah_size_t  mask = nbucket - 1;\t\t\t\t\\\n\t\tah_size_t  j, flen;\t\t\t\t\t\\\n\t\tif (h->nelem >= (ah_size_t)(nbucket * AH_LOAD_FACTOR))\t\\\n\t\t\treturn -1;\t\t\t\t\t\\\n\t\tflen  = AH_FLAGS_BYTE(nbucket);\t\t\t\t\\\n\t\tflags = (ah_size_t *) malloc(flen);\t\t\t\\\n\t\tif (flags == NULL)\t\t\t\t\t\\\n\t\t\treturn -1;\t\t\t\t\t\\\n\t\tmemset(flags, 0xaa, flen);\t\t\t\t\\\n\t\tif (h->nbucket < nbucket) {\t\t\t\t\\\n\t\t\tkeys = (key_t*)\trealloc(h->keys, nbucket * sizeof(key_t)); \\\n\t\t\tif (keys == NULL) {\t\t\t\t\\\n\t\t\t\tfree(flags);\t\t\t\t\\\n\t\t\t\treturn -1;\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t\th->keys = keys;\t\t\t\t\t\\\n\t\t\tif (ismap) {\t\t\t\t\t\\\n\t\t\t\tvals = (val_t*)\trealloc(h->vals, nbucket * sizeof(val_t)); \\\n\t\t\t\tif (vals == NULL) {\t\t\t\\\n\t\t\t\t\tfree(flags);\t\t\t\\\n\t\t\t\t\treturn -1;\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\\\n\t\t\t\th->vals = vals;\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\\\n\t\tfor (j = 0; j != h->nbucket; ++j) {\t\t\t\\\n\t\t\tif (AH_ISEITHER(h->flags, j) == 0) {\t\t\\\n\t\t\t\tkey_t key = h->keys[j];\t\t\t\\\n\t\t\t\tval_t val;\t\t\t\t\\\n\t\t\t\tif (ismap) val = h->vals[j];\t\t\\\n\t\t\t\tAH_SET_DEL(h->flags, j);\t\t\\\n\t\t\t\tfor (;;) {\t\t\t\t\\\n\t\t\t\t\tah_size_t i, k, step;\t\t\\\n\t\t\t\t\tk = hashfn(key);\t\t\\\n\t\t\t\t\ti = k & mask;\t\t\t\\\n\t\t\t\t\tstep = AH_PROBING_STEP(k);\t\\\n\t\t\t\t\twhile (!AH_ISEMPTY(flags, i))\t\\\n\t\t\t\t\t\ti = (i + step) & mask;\t\\\n\t\t\t\t\tAH_CLEAR_EMPTY(flags, i);\t\\\n\t\t\t\t\tif (i < h->nbucket && AH_ISEITHER(h->flags, i) == 0) { \\\n\t\t\t\t\t\t_swap(h->keys[i], key);\t\\\n\t\t\t\t\t\tif (ismap) _swap(h->vals[i], val); \\\n\t\t\t\t\t\tAH_SET_DEL(h->flags, i); \\\n\t\t\t\t\t} else {\t\t\t\\\n\t\t\t\t\t\th->keys[i] = key;\t\\\n\t\t\t\t\t\tif (ismap) h->vals[i] = val; \\\n\t\t\t\t\t\tbreak;\t\t\t\\\n\t\t\t\t\t}\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\\\n\t\tif (h->nbucket > nbucket) {\t\t\t\t\\\n\t\t\tkeys = (key_t*) realloc(h->keys, nbucket * sizeof(key_t)); \\\n\t\t\tif (keys) h->keys = keys;\t\t\t\\\n\t\t\tif (ismap) {\t\t\t\t\t\\\n\t\t\t\tvals = (val_t*) realloc(h->vals, nbucket * sizeof(val_t)); \\\n\t\t\t\tif (vals) h->vals = vals;\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\\\n\t\tfree(h->flags);\t\t\t\t\t\t\\\n\t\th->flags = flags;\t\t\t\t\t\\\n\t\th->nbucket = nbucket;\t\t\t\t\t\\\n\t\th->noccupied = h->nelem;\t\t\t\t\\\n\t\th->bound = (ah_size_t)(h->nbucket * AH_LOAD_FACTOR);\t\\\n\t\treturn 0;\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\\\n\tstatic inline ah_iter_t\t\t\t\t\t\t\\\n\talignhash_set_##name(alignhash_##name##_t *h, keyref_t key, int *ret) \\\n\t{\t\t\t\t\t\t\t\t\\\n\t\tah_size_t i, x, k, step, mask, site, last;\t\t\\\n\t\tif (h->noccupied >= h->bound) {\t\t\t\t\\\n\t\t\tif (h->nbucket) {\t\t\t\t\\\n\t\t\t\tif (alignhash_resize_##name(h, h->nbucket << 1)) \\\n\t\t\t\t\treturn h->nbucket;\t\t\\\n\t\t\t} else {\t\t\t\t\t\\\n\t\t\t\tif (alignhash_resize_##name(h, 2))\t\\\n\t\t\t\t\treturn h->nbucket;\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\\\n\t\tsite = h->nbucket;\t\t\t\t\t\\\n\t\tmask = h->nbucket - 1;\t\t\t\t\t\\\n\t\tx = site;\t\t\t\t\t\t\\\n\t\tk = hashfn(key);\t\t\t\t\t\\\n\t\ti = k & mask;\t\t\t\t\t\t\\\n\t\tif (AH_ISEMPTY(h->flags, i))\t\t\t\t\\\n\t\t\tx = i;\t\t\t\t\t\t\\\n\t\telse {\t\t\t\t\t\t\t\\\n\t\t\tstep = AH_PROBING_STEP(k);\t\t\t\\\n\t\t\tlast = i;\t\t\t\t\t\\\n\t\t\twhile (!AH_ISEMPTY(h->flags, i) &&\t\t\\\n\t\t\t       (AH_ISDEL(h->flags, i) || !equalfn(h->keys[i], key))) { \\\n\t\t\t\tif (AH_ISDEL(h->flags, i))\t\t\\\n\t\t\t\t\tsite = i;\t\t\t\\\n\t\t\t\ti = (i + step) & mask;\t\t\t\\\n\t\t\t\tif (i == last) {\t\t\t\\\n\t\t\t\t\tx = site;\t\t\t\\\n\t\t\t\t\tbreak;\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t\tif (x == h->nbucket) {\t\t\t\t\\\n\t\t\t\tif (AH_ISEMPTY(h->flags, i) && site != h->nbucket) \\\n\t\t\t\t\tx = site;\t\t\t\\\n\t\t\t\telse\t\t\t\t\t\\\n\t\t\t\t\tx = i;\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\\\n\t\tif (AH_ISEMPTY(h->flags, x)) {\t\t\t\t\\\n\t\t\th->keys[x] = key;\t\t\t\t\\\n\t\t\tAH_CLEAR_BOTH(h->flags, x);\t\t\t\\\n\t\t\t++h->nelem;\t\t\t\t\t\\\n\t\t\t++h->noccupied;\t\t\t\t\t\\\n\t\t\t*ret = AH_INS_NEW;\t\t\t\t\\\n\t\t} else if (AH_ISDEL(h->flags, x)) {\t\t\t\\\n\t\t\th->keys[x] = key;\t\t\t\t\\\n\t\t\tAH_CLEAR_BOTH(h->flags, x);\t\t\t\\\n\t\t\t++h->nelem;\t\t\t\t\t\\\n\t\t\t*ret = AH_INS_DEL;\t\t\t\t\\\n\t\t} else\t\t\t\t\t\t\t\\\n\t\t\t*ret = AH_INS_ERR;\t\t\t\t\\\n\t\treturn x;\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\\\n\tstatic inline void\t\t\t\t\t\t\\\n\talignhash_del_##name(alignhash_##name##_t *h, ah_iter_t x)\t\\\n\t{\t\t\t\t\t\t\t\t\\\n\t\tif (x != h->nbucket && !AH_ISEITHER(h->flags, x)) {\t\\\n\t\t\tAH_SET_DEL(h->flags, x);\t\t\t\\\n\t\t\t--h->nelem;\t\t\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\\\n\t}\n\n/* provide two versions, the C++ version uses reference to save the\n * parameter copying costs. */\n#ifdef __cplusplus\n#define DEFINE_ALIGNHASH(name, key_t, val_t, ismap, hashfn, equalfn)\t\\\n\tDEFINE_ALIGNHASH_RAW(name, key_t, const key_t &, val_t,\t\t\\\n\t\t\t     ismap, hashfn, equalfn)\n#else\n#define DEFINE_ALIGNHASH(name, key_t, val_t, ismap, hashfn, equalfn)\t\\\n\tDEFINE_ALIGNHASH_RAW(name, key_t, key_t, val_t, ismap,\t\t\\\n\t\t\t     hashfn, equalfn)\n#endif\n\n/*------------------------- Human Interface -------------------------*/\n\n/* Identity hash function, converting a key to an integer. This coerce\n * the key to be of integer type or integer interpretable. */\n#define alignhash_hashfn(key) (ah_size_t)(key)\n\n/* boolean function that tests whether two keys are equal */\n#define alignhash_equalfn(a, b) ((a) == (b))\n\n/* alignhash type */\n#define alignhash_t(name) alignhash_##name##_t\n\n/* return the key/value associated with the iterator */\n#define alignhash_key(h, x) ((h)->keys[x])\n#define alignhash_value(h, x) ((h)->vals[x])\n\n/* Core alignhash functions. */\n#define alignhash_init(name) alignhash_init_##name()\n#define alignhash_destroy(name, h) alignhash_destroy_##name(h)\n#define alignhash_clear(name, h) alignhash_clear_##name(h)\n/* The resize function is called automatically when certain load limit\n * is reached. Thus don't call it manually unless you have to. */\n#define alignhash_resize(name, h, s) alignhash_resize_##name(h, s)\n\n/* Insert a new element without replacement.\n * r will hold the error code as defined above.\n * Returns an iterator to the new or existing element. */\n#define alignhash_set(name, h, k, r) alignhash_set_##name(h, k, r)\n#define alignhash_get(name, h, k) alignhash_get_##name(h, k)\n/* delete an element by iterator */\n#define alignhash_del(name, h, x) alignhash_del_##name(h, x)\n/* test whether an iterator is valid */\n#define alignhash_exist(h, x) (!AH_ISEITHER((h)->flags, (x)))\n\n/* Iterator functions. */\n#define alignhash_begin(h) (ah_iter_t)(0)\n#define alignhash_end(h) ((h)->nbucket)\n\n/* number of elements in the hash table */\n#define alignhash_size(h) ((h)->nelem)\n/* return the current capacity of the hash table */\n#define alignhash_nbucket(h) ((h)->nbucket)\n\n#endif\t/* _ULIB_HASH_ALIGN_PROT_H */\n"
  },
  {
    "path": "common/ulib/ulib.c",
    "content": "#include \"hash_align.h\"\n#include \"hash_align_prot.h\"\n#include \"util_algo.h\"\n#include \"util_class.h\"\n"
  },
  {
    "path": "common/ulib/util_algo.h",
    "content": "/* The MIT License\n\n   Copyright (C) 2011, 2012 Zilong Tan (eric.zltan@gmail.com)\n\n   Permission is hereby granted, free of charge, to any person obtaining\n   a copy of this software and associated documentation files (the\n   \"Software\"), to deal in the Software without restriction, including\n   without limitation the rights to use, copy, modify, merge, publish,\n   distribute, sublicense, and/or sell copies of the Software, and to\n   permit persons to whom the Software is furnished to do so, subject to\n   the following conditions:\n\n   The above copyright notice and this permission notice shall be\n   included in all copies or substantial portions of the Software.\n\n   THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n   SOFTWARE.\n*/\n\n#ifndef _ULIB_UTIL_ALGO_H\n#define _ULIB_UTIL_ALGO_H\n\n#include <stddef.h>\n#include <stdint.h>\n\n#ifndef typeof\n#define typeof __typeof__\n#endif\n\n#define _min(x, y) ({\t\t\t\t\t\t\\\n\t\t\ttypeof(x) _min1 = (x);\t\t\t\\\n\t\t\ttypeof(y) _min2 = (y);\t\t\t\\\n\t\t\t(void) (&_min1 == &_min2);\t\t\\\n\t\t\t_min1 < _min2 ? _min1 : _min2; })\n\n#define _max(x, y) ({\t\t\t\t\t\t\\\n\t\t\ttypeof(x) _max1 = (x);\t\t\t\\\n\t\t\ttypeof(y) _max2 = (y);\t\t\t\\\n\t\t\t(void) (&_max1 == &_max2);\t\t\\\n\t\t\t_max1 > _max2 ? _max1 : _max2; })\n\n#define min3(x, y, z) ({\t\t\t\t\t\t\\\n\t\t\ttypeof(x) _min1 = (x);\t\t\t\t\\\n\t\t\ttypeof(y) _min2 = (y);\t\t\t\t\\\n\t\t\ttypeof(z) _min3 = (z);\t\t\t\t\\\n\t\t\t(void) (&_min1 == &_min2);\t\t\t\\\n\t\t\t(void) (&_min1 == &_min3);\t\t\t\\\n\t\t\t_min1 < _min2 ? (_min1 < _min3 ? _min1 : _min3) : \\\n\t\t\t\t(_min2 < _min3 ? _min2 : _min3); })\n\n#define max3(x, y, z) ({\t\t\t\t\t\t\\\n\t\t\ttypeof(x) _max1 = (x);\t\t\t\t\\\n\t\t\ttypeof(y) _max2 = (y);\t\t\t\t\\\n\t\t\ttypeof(z) _max3 = (z);\t\t\t\t\\\n\t\t\t(void) (&_max1 == &_max2);\t\t\t\\\n\t\t\t(void) (&_max1 == &_max3);\t\t\t\\\n\t\t\t_max1 > _max2 ? (_max1 > _max3 ? _max1 : _max3) : \\\n\t\t\t\t(_max2 > _max3 ? _max2 : _max3); })\n\n/**\n * min_not_zero - return the minimum that is _not_ zero, unless both are zero\n * @x: value1\n * @y: value2\n */\n#define min_not_zero(x, y) ({\t\t\t\t\t\t\\\n\t\t\ttypeof(x) __x = (x);\t\t\t\t\\\n\t\t\ttypeof(y) __y = (y);\t\t\t\t\\\n\t\t\t__x == 0 ? __y : ((__y == 0) ? __x : _min(__x, __y)); })\n\n/**\n * clamp - return a value clamped to a given range with strict typechecking\n * @val: current value\n * @min: minimum allowable value\n * @max: maximum allowable value\n *\n * This macro does strict typechecking of min/max to make sure they are of the\n * same type as val.  See the unnecessary pointer comparisons.\n */\n#define clamp(val, min, max) ({\t\t\t\t\t\\\n\t\t\ttypeof(val) __val = (val);\t\t\\\n\t\t\ttypeof(min) __min = (min);\t\t\\\n\t\t\ttypeof(max) __max = (max);\t\t\\\n\t\t\t(void) (&__val == &__min);\t\t\\\n\t\t\t(void) (&__val == &__max);\t\t\\\n\t\t\t__val = __val < __min ? __min: __val;\t\\\n\t\t\t__val > __max ? __max: __val; })\n\n#define min_t(type, x, y) ({\t\t\t\t\t\\\n\t\t\ttype __min1 = (x);\t\t\t\\\n\t\t\ttype __min2 = (y);\t\t\t\\\n\t\t\t__min1 < __min2 ? __min1: __min2; })\n\n#define max_t(type, x, y) ({\t\t\t\t\t\\\n\t\t\ttype __max1 = (x);\t\t\t\\\n\t\t\ttype __max2 = (y);\t\t\t\\\n\t\t\t__max1 > __max2 ? __max1: __max2; })\n\n/**\n * clamp_t - return a value clamped to a given range using a given type\n * @type: the type of variable to use\n * @val: current value\n * @min: minimum allowable value\n * @max: maximum allowable value\n *\n * This macro does no typechecking and uses temporary variables of type\n * 'type' to make all the comparisons.\n */\n#define clamp_t(type, val, min, max) ({\t\t\t\t\\\n\t\t\ttype __val = (val);\t\t\t\\\n\t\t\ttype __min = (min);\t\t\t\\\n\t\t\ttype __max = (max);\t\t\t\\\n\t\t\t__val = __val < __min ? __min: __val;\t\\\n\t\t\t__val > __max ? __max: __val; })\n\n/**\n * clamp_val - return a value clamped to a given range using val's type\n * @val: current value\n * @min: minimum allowable value\n * @max: maximum allowable value\n *\n * This macro does no typechecking and uses temporary variables of whatever\n * type the input argument 'val' is.  This is useful when val is an unsigned\n * type and min and max are literals that will otherwise be assigned a signed\n * integer type.\n */\n#define clamp_val(val, min, max) ({\t\t\t\t\\\n\t\t\ttypeof(val) __val = (val);\t\t\\\n\t\t\ttypeof(val) __min = (min);\t\t\\\n\t\t\ttypeof(val) __max = (max);\t\t\\\n\t\t\t__val = __val < __min ? __min: __val;\t\\\n\t\t\t__val > __max ? __max: __val; })\n\n\n#define _swap(a, b)\t\t\t\t\t\t\t\\\n\tdo { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0)\n\n/**\n * container_of - cast a member of a structure out to the containing structure\n * @ptr:\tthe pointer to the member.\n * @type:\tthe type of the container struct this is embedded in.\n * @member:\tthe name of the member within the struct.\n *\n */\n#define container_of(ptr, type, member) ({\t\t\t\t\\\n\t\t\tconst typeof( ((type *)0)->member ) *__mptr = (ptr); \\\n\t\t\t(type *)( (char *)__mptr - __builtin_offsetof(type,member) );})\n\n#define generic_compare(x, y) (((x) > (y)) - ((x) < (y)))\n\nstatic inline void memswp(unsigned long *x, unsigned long *y, size_t size)\n{\n\tunsigned long *p = x + size/sizeof(*x);;\n\tunsigned char *h, *v;\n\n\twhile (x != p) {\n\t\t_swap(*x, *y);\n\t\tx++;\n\t\ty++;\n\t}\n\n\th = (unsigned char *)x;\n\tv = (unsigned char *)y;\n\n#if __WORDSIZE == 64\n\tswitch (size & 7) {\n\tcase 7: _swap(h[6], v[6]);\n\tcase 6: _swap(h[5], v[5]);\n\tcase 5: _swap(h[4], v[4]);\n\tcase 4: _swap(h[3], v[3]);\n#else\n\t\tswitch (size & 3) {\n#endif\n\t\tcase 3: _swap(h[2], v[2]);\n\t\tcase 2: _swap(h[1], v[1]);\n\t\tcase 1: _swap(h[0], v[0]);\n\t\t}\n\t}\n\n#endif\t/* _ULIB_UTIL_ALGO_H */\n"
  },
  {
    "path": "common/ulib/util_class.h",
    "content": "/* The MIT License\n\n   Copyright (C) 2012 Zilong Tan (eric.zltan@gmail.com)\n\n   Permission is hereby granted, free of charge, to any person obtaining\n   a copy of this software and associated documentation files (the\n   \"Software\"), to deal in the Software without restriction, including\n   without limitation the rights to use, copy, modify, merge, publish,\n   distribute, sublicense, and/or sell copies of the Software, and to\n   permit persons to whom the Software is furnished to do so, subject to\n   the following conditions:\n\n   The above copyright notice and this permission notice shall be\n   included in all copies or substantial portions of the Software.\n\n   THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n   SOFTWARE.\n*/\n\n#ifndef _ULIB_UTIL_CLASS_H\n#define _ULIB_UTIL_CLASS_H\n\n#include <exception>\n\nnamespace ulib {\n\nstruct ulib_except : public std::exception {\n\tvirtual\n\t~ulib_except() throw() { }\n};\n\ntemplate<typename T>\nstruct do_nothing_combiner {\n\tvirtual void\n\toperator() (T &, const T &) const { }\n};\n\n}  // namespace ulib\n\n#endif\n"
  },
  {
    "path": "common/utils/BackOffInvoker.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file BackOffInvoker.hh\n//! @author Abhishek Lekshmanan <abhishek.lekshmanan@cern.ch>\n//-----------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include <limits>\n#include <cstdint>\n#include <type_traits>\n\nnamespace eos::common {\n\ntemplate <typename int_type = uint16_t,\n          bool wrap_around=true>\nclass BackOffInvoker {\npublic:\n  static constexpr auto LIMIT_BY_2 =\n      (std::numeric_limits<int_type>::max() >> 1) + 1;\n\n  template <typename Fn>\n  bool\n  invoke(Fn&& fn)\n  {\n    bool status = ++mCounter == mLimit;\n    if (status) {\n      fn();\n\n      mLimit = mLimit == LIMIT_BY_2 ? wrap_around : mLimit << 1;\n    }\n    return status;\n  }\n\n  BackOffInvoker() : mCounter(0), mLimit(1) {}\n\n  static_assert(std::is_unsigned<int_type>::value, \"int_type must be unsigned\");\n\nprivate:\n  int_type mCounter;\n  int_type mLimit;\n};\n\n} // eos::common\n"
  },
  {
    "path": "common/utils/BindArguments.hh",
    "content": "//------------------------------------------------------------------------------\n// File: BindArguments.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include <tuple>\n\nnamespace eos::common\n{\n\n/*!\n * A simple struct that binds a callable with Arguments so that it may be used\n * in contexts where a function of void(void) signature is necessary, for eg\n * ThreadPools and Executors.\n * The main necessity for doing this instead of a lambda/bind expression is to\n * preserve the c-v-ref nature of arguments in template like scenarios\n * for eg. [&args...](){ my_function(std::forward<Args>(args)...); } will not\n * work correctly as we would end up forwarding a int& where int was expected\n * It is expected to use the bindArgs factory function which correctly creates\n * this structure\n * @tparam Fn Callable\n * @tparam TArgs a tuple of args\n */\ntemplate <typename Fn, typename TArgs>\nstruct BoundArgsHandler {\n  Fn f;\n  TArgs arg_tuple;\n\n  BoundArgsHandler(Fn&& _f,\n                   TArgs&& _arg_tuple) : f(std::move(_f)),\n    arg_tuple(std::move(_arg_tuple)) {}\n\n  BoundArgsHandler(const Fn& _f,\n                   TArgs&& _arg_tuple) : f(_f),\n    arg_tuple(std::move(_arg_tuple)) {}\n\n  void operator()() const&\n  {\n    std::apply(f, arg_tuple);\n  }\n\n  void operator()()&& {\n    std::apply(std::move(f), std::move(arg_tuple));\n  }\n};\n\ntemplate <typename Fn, typename... Args>\nauto bindArgs(Fn&& f, Args&& ... args)\n{\n  return BoundArgsHandler(std::forward<Fn>(f),\n                          std::make_tuple(std::forward<Args>(args)...));\n}\n\n} // eos::common\n"
  },
  {
    "path": "common/utils/ContainerUtils.hh",
    "content": "//------------------------------------------------------------------------------\n// File: ContainerUtils.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/utils/TypeTraits.hh\"\n#include <cstdint>\n#include <iterator>\n\nnamespace eos::common\n{\n\n\n//----------------------------------------------------------------------------\n//! erase_if that erases elements in place for elements matching a predicate\n//! This is useful in erase remove idiom useful for assoc. containers where\n//! std::remove_if will not compile, almost borrowed from erase_if C++ ref page\n//!\n//! @param C the associative container, elements will be removed in place\n//! @param pred the predicate to evaluate, please note the container\n//!             value_type aka the pair of values will be the input for the\n//!             predicate\n//! @return the no of elements removed\n//! Usage eg:\n//!   eos::common::erase_if(m, [](const auto& p){ return p.first % 2 == 0;})\n//----------------------------------------------------------------------------\ntemplate <typename C, typename Pred>\ntypename C::size_type\nerase_if(C& c, Pred pred)\n{\n  // TODO: (abhi) sfinae for normal overloads where you can just erase\n  //  (remove_if) for container types like vector/lists\n  static_assert(detail::is_assoc_container_v<C>,\n                \"This method is only implemented for assoc. containers just \"\n                \"use std::erase(std::remove_if(C,pred)) instead\");\n  auto init_sz = c.size();\n\n  for (auto it = c.begin(), last = c.end(); it != last;) {\n    if (pred(*it)) {\n      it = c.erase(it);\n    } else {\n      ++it;\n    }\n  }\n\n  return init_sz - c.size();\n}\n\n\n// Both the functions below assume 0 is not supplied as an argument!\ninline uint8_t get_msb(uint64_t val)\n{\n#if defined(__GNUC__) || defined(__clang__)\n  return 63 - __builtin_clzll(val);\n#else\n  uint8_t msb = 0;\n  while (val >>= 1) {\n    msb++;\n  }\n  return msb;\n#endif\n}\n\ninline uint64_t\nclamp_index(uint64_t index, uint64_t size)\n{\n  if (index < size) {\n    return index;\n  }\n  // For powers of 2 sizes, you just need to find the number within\n  // the power of 2 range which is cheaper than a modulo.\n  if ((size & (size - 1)) == 0) {\n    auto bit = get_msb(size);\n    return index & ((1ULL << bit) - 1);\n  }\n  return index % size;\n}\n\n//----------------------------------------------------------------------------\n//! A simple Round Robin like picker for a container, for indices past the size\n//! we simply wrap around giving a feel of circular iterator\n//! NOTE: In case of an empty container this function raises out_of_range\n//! exception, so please ensure container is not empty!\n//!\n//! @param C Container to pick from\n//! @param index\n//! @return item at index\n//! Usage eg:\n//!   vector<int> v {1,2,3};\n//!   pickIndexRR(v,3) -> 1 (v,4)->2 (v,5) -> 3  ...\n//----------------------------------------------------------------------------\ntemplate <typename C>\ntypename C::value_type\npickIndexRR(const C& c, uint64_t index)\n{\n  if (!c.size()) {\n    throw std::out_of_range(\"Empty Container!\");\n  }\n\n  auto iter = c.begin();\n  std::advance(iter, clamp_index(index, c.size()));\n  return *iter;\n}\n\n//----------------------------------------------------------------------------\n//! Transfer a container onto another, this variants destructively move values\n//! from other container onto source container at a given pos.\n//! \\tparam C container type -  will be inferred\n//! \\param c container where other container will be spliced onto\n//! \\param other container whose elements will be consumed\n//! \\param pos position where we need to splice\n//----------------------------------------------------------------------------\ntemplate <typename C>\nvoid\nsplice(C& c, C&& other,\n       typename C::const_iterator pos)\n{\n  c.insert(pos,\n           std::make_move_iterator(other.begin()),\n           std::make_move_iterator(other.end()));\n}\n\n//----------------------------------------------------------------------------\n//! Transfer a container onto another at the end, this variants destructively move values\n//! from other container onto source container at a given pos.\n//! \\tparam C container type -  will be inferred\n//! \\param c container where other container will be spliced onto\n//! \\param other container whose elements will be consumed\n//----------------------------------------------------------------------------\ntemplate <typename C>\nvoid splice(C& c, C&& other)\n{\n  splice(c, std::move(other), c.end());\n}\n\nuint64_t inline next_power2(uint64_t x) {\n#if defined(__GNUC__) || defined(__clang__)\n    x = x ? x : 1;\n    return x == 1 ? 1 : 1ULL<<(64 - __builtin_clzll(x-1));\n#else\n    // https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2\n    --x;\n    x |= x >> 1;\n    x |= x >> 2;\n    x |= x >> 4;\n    x |= x >> 8;\n    x |= x >> 16;\n    ++x;\n    x += (x == 0);\n    return x;\n#endif\n}\n\n} // eos::common\n\n"
  },
  {
    "path": "common/utils/RandUtils.hh",
    "content": "// ----------------------------------------------------------------------\n// File: RandUtils.hh\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/ASwitzerland                                 *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Namespace.hh\"\n#include <random>\n\nEOSCOMMONNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Method to generate random number in the given interval - thread safe\n//!\n//! @param start start interval\n//! @param end end interval\n//!\n//! @return random number uniformly distributed in the given interval\n//------------------------------------------------------------------------------\ntemplate <typename IntType = uint64_t>\nauto\ngetRandom(IntType start = 0, IntType end = static_cast<IntType>(RAND_MAX)) -> IntType\n{\n  thread_local std::random_device tlrd;\n  thread_local std::mt19937 generator(tlrd());\n\n  std::uniform_int_distribution<IntType> distrib(start, end);\n  return distrib(generator);\n}\n\n//------------------------------------------------------------------------------\n//! Method to generate random number in the given interval - thread safe\n//!\n//! @param start start interval\n//! @param end end interval\n//!\n//! @return random number uniformly distributed in the given interval\n//------------------------------------------------------------------------------\ntemplate <typename IntType = uint64_t>\nauto\ngetRandom64(IntType start = 0, IntType end = static_cast<IntType>(RAND_MAX)) -> IntType\n{\n  thread_local std::random_device tlrd;\n  thread_local std::mt19937_64 generator(tlrd());\n\n  std::uniform_int_distribution<IntType> distrib(start, end);\n  return distrib(generator);\n}\n\n//------------------------------------------------------------------------------\n//! Method to generate a random number following a normal distribution\n//! (Gaussian) - thread safe\n//!\n//! @param mean   mean value of the distribution\n//! @param stddev standard deviation of the distribution\n//!\n//! @return random number normally distributed with the given mean and stddev\n//------------------------------------------------------------------------------\ntemplate <typename FloatType = double>\nauto\ngetRandomNormal(FloatType mean = 0.0, FloatType stddev = 1.0) -> FloatType\n{\n  thread_local std::random_device tlrd;\n  thread_local std::mt19937 generator(tlrd());\n  std::normal_distribution<FloatType> distrib(mean, stddev);\n  return distrib(generator);\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/utils/TypeTraits.hh",
    "content": "//------------------------------------------------------------------------------\n// File: TypeTraits.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#pragma once\n\n/**\n * @file   TypeTraits.hh\n *\n * @brief  Some useful compile time type traits utilities\n *\n *  A common place for some simple template type inspection utilities, so that\n *  classes/functions defining them needn't define them at site, everything is\n *  defined in a detail namespace as this functionality isn't meant to be exposed\n *  to outside world but for internal static_asserts/SFINAE use cases\n */\n\n#include <type_traits>\n#include <utility>  // declval, and charconv\n\n// version can be used for feature testing macros, while we're not C++20 yet\n// helpful in case of identifying compiler support for partially supported\n// C++17 features, for eg. charconv's float conversions which are only\n// implemented by newer GCC/VS compilers\n#if __has_include(<version>)\n#include <version>\n#endif\n\nnamespace eos::common::detail\n{\n\n// A simple test for containers that expose a C::key_type\n// Ideally std::remove_if requires a dereferenced iterator to be MoveAssignable\n// ie *it = std::move(value) to be legal\n// However for all assoc. containers this would be illegal as keys have\n// pointer stability, so this will fail (with long compiler messages)\n// A poor man's choice of tests for detecting associative containers is just\n// testing for key_type which is kind of ok for std:: containers\ntemplate <typename, typename = void>\nstruct is_assoc_container_t : std::false_type {};\n\ntemplate <typename T>\nstruct is_assoc_container_t<T, std::void_t<typename T::key_type>>:\n      std::true_type {};\n\ntemplate <typename T>\ninline constexpr bool is_assoc_container_v = is_assoc_container_t<T>::value;\n\n\n// A simple test for containers that expose a data() type which is usually defined\n// for contiguous sequences like string/_view/arrays/vectors\ntemplate <typename, typename = void>\nstruct has_data_t : std::false_type {};\n\ntemplate <typename T>\nstruct has_data_t<T, std::void_t<decltype(std::declval<T>().data())>> :\n    std::true_type {};\n\n// While technically this will be only defined by compilers implementing, given\n// that preprocessors will replace unknowns with 0s, we'll have valid arithmetic\n// expressions to write forward compatible code\n#if __cpp_lib_to_chars >= 201611\n// We are dealing with a compiler fully implementing charconv including floats\ntemplate <typename T>\ninline constexpr bool is_charconv_numeric_v = std::is_arithmetic<T>::value;\n#else\n// Partial implementation only for integral types for eg. clang/gcc < 11\ntemplate <typename T>\ninline constexpr bool is_charconv_numeric_v = std::is_integral<T>::value;\n#endif\n\n} // eos::common::detail\n"
  },
  {
    "path": "common/utils/XrdUtils.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file XrdUtils.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"XrdUtils.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\nunsigned int XrdUtils::countNbElementsInXrdOucTList(const XrdOucTList* listPtr)\n{\n  int count = 0;\n\n  while (listPtr) {\n    count++;\n    listPtr = listPtr->next;\n  }\n\n  return count;\n}\n\nstd::string\nXrdUtils::GetEnv(XrdOucEnv& env, const char* key,\n                 std::string_view default_str)\n{\n  char* val = 0;\n\n  if ((val = env.Get(key))) {\n    return val;\n  }\n\n  return std::string(default_str);\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "common/utils/XrdUtils.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file XrdUtils.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_XRDUTILS_HH\n#define EOS_XRDUTILS_HH\n\n#include <XrdOuc/XrdOucTList.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n#include <type_traits>\n#include \"common/StringUtils.hh\"\n#include \"common/Namespace.hh\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\n/**\n * Utility class linked to Xrootd objects and containers\n */\nclass XrdUtils\n{\npublic:\n  /**\n   * Counts the number of elements contained in a XrdOucTList\n   * @param listPtr the pointer pointing to the XrdOucTList which we want to count the number of elements\n   * @return The number of elements the list listPtr contains\n   */\n  static unsigned int countNbElementsInXrdOucTList(const XrdOucTList* listPtr);\n\n\n  // While we don't modify XrdOucEnv, since Get isn't const marked, we've to use\n  // a mutable ref\n  static std::string\n  GetEnv(XrdOucEnv& env, const char* key,\n         std::string_view default_str = {});\n\n  template <typename T>\n  static auto\n  GetEnv(XrdOucEnv& env, const char* key,\n         T& out, T default_val={})\n    -> std::enable_if_t<std::is_arithmetic_v<T>, bool> {\n    char* val = 0;\n    if ((val = env.Get(key))) {\n      return eos::common::StringToNumeric(std::string_view(val), out, default_val);\n    }\n\n    return false;\n  }\n\n  // Variant for GetEnv for getting from system environment variables\n  template <typename T>\n  static auto\n  GetEnv(const char* key, T& out, T default_val={})\n    -> std::enable_if_t<std::is_arithmetic_v<T>, bool> {\n    char *val = nullptr;\n    if ((val = getenv(key))) {\n      return common::StringToNumeric(std::string_view(val), out, default_val);\n    }\n    return false;\n  }\n};\n\nEOSCOMMONNAMESPACE_END\n#endif // EOS_XRDUTILS_HH\n"
  },
  {
    "path": "console/CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2023 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ninclude_directories(${CMAKE_CURRENT_SOURCE_DIR}/commands/helpers/)\n\n#-------------------------------------------------------------------------------\n# CLI11 parser submodule (console/parser)\n#-------------------------------------------------------------------------------\nif(EXISTS \"${CMAKE_CURRENT_SOURCE_DIR}/parser/CMakeLists.txt\")\n  set(CLI11_BUILD_TESTS OFF CACHE BOOL \"Disable CLI11 tests\" FORCE)\n  set(CLI11_BUILD_EXAMPLES OFF CACHE BOOL \"Disable CLI11 examples\" FORCE)\n  set(CLI11_BUILD_DOCS OFF CACHE BOOL \"Disable CLI11 docs\" FORCE)\n  set(CLI11_INSTALL ON CACHE BOOL \"Install CLI11 headers\" FORCE)\n  add_subdirectory(parser)\nendif()\n\n#-------------------------------------------------------------------------------\n# eos executable\n#-------------------------------------------------------------------------------\nadd_library(EosConsoleHelpers-Objects OBJECT\n  commands/helpers/ICmdHelper.cc    commands/helpers/ICmdHelper.hh\n  commands/helpers/FsHelper.cc      commands/helpers/FsHelper.hh\n  commands/helpers/RecycleHelper.cc commands/helpers/RecycleHelper.hh\n  commands/helpers/FsckHelper.cc    commands/helpers/FsckHelper.hh\n  commands/helpers/NodeHelper.cc    commands/helpers/NodeHelper.hh\n  commands/helpers/TokenHelper.cc   commands/helpers/TokenHelper.hh)\n\ntarget_link_libraries(EosConsoleHelpers-Objects PUBLIC\n  EosFstProto-Objects\n  EosCliProto-Objects\n  ZMQ::ZMQ\n  OpenSSL::SSL\n  GOOGLE::SPARSEHASH\n  XROOTD::UTILS\n  XROOTD::POSIX\n  XROOTD::PRIVATE\n  SCITOKENS::SCITOKENS)\n\nif (Linux)\n  target_link_libraries(EosConsoleHelpers-Objects PUBLIC stdc++fs)\nendif()\n\nset_target_properties(EosConsoleHelpers-Objects PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\nadd_library(EosConsoleCommands-Objects OBJECT\n  # Core console framework\n  ConsoleMain.cc  ConsoleMain.hh\n  CommandFramework.cc CommandFramework.hh\n  ConsoleCompletion.cc ConsoleCompletion.hh\n  RegexUtil.cc RegexUtil.hh\n\n  # Core/native base\n  commands/native/CoreNativeCommands.cc\n  commands/native/pwd-native.cc\n  commands/native/status-native.cc\n  commands/native/tui-native.cc\n\n  # Native aliases\n  commands/native/info-alias.cc\n  commands/native/fileinfo-alias.cc\n  commands/native/mv-alias.cc\n  commands/native/ls-compat.cc\n\n  # Native com wrappers\n  commands/native/cat-com-native.cc\n  commands/native/cp-cmd-native.cc\n  commands/native/oldfind-cmd-native.cc\n  commands/native/rclone-cmd-native.cc\n  commands/native/squash-cmd-native.cc\n\n  # Native cmd implementations (mgm.cmd=...)\n  commands/native/accounting-cmd-native.cc\n  commands/native/archive-cmd-native.cc\n  commands/native/attr-cmd-native.cc\n  commands/native/backup-cmd-native.cc\n  commands/native/cd-cmd-native.cc\n  commands/native/chmod-cmd-native.cc\n  commands/native/chown-cmd-native.cc\n  commands/native/clear-cmd-native.cc\n  commands/native/debug-cmd-native.cc\n  commands/native/evict-cmd-native.cc\n  commands/native/file-cmd-native.cc\n  commands/native/geosched-cmd-native.cc\n  commands/native/ln-cmd-native.cc\n  commands/native/ls-cmd-native.cc\n  commands/native/map-cmd-native.cc\n  commands/native/member-cmd-native.cc\n  commands/native/mkdir-cmd-native.cc\n  commands/native/motd-cmd-native.cc\n  commands/native/rmdir-cmd-native.cc\n  commands/native/rtlog-cmd-native.cc\n  commands/native/touch-cmd-native.cc\n  commands/native/version-cmd-native.cc\n  commands/native/vid-cmd-native.cc\n  commands/native/who-cmd-native.cc\n  commands/native/whoami-cmd-native.cc\n\n  # Native proto implementations\n  commands/native/access-proto-native.cc\n  commands/native/acl-proto-native.cc\n  commands/native/config-proto-native.cc\n  commands/native/convert-proto-native.cc\n  commands/native/devices-proto-native.cc\n  commands/native/df-proto-native.cc\n  commands/native/du-proto-native.cc\n  commands/native/find-proto-native.cc\n  commands/native/fs-proto-native.cc\n  commands/native/fsck-proto-native.cc\n  commands/native/group-proto-native.cc\n  commands/native/inspector-proto-native.cc\n  commands/native/io-proto-native.cc\n  commands/native/node-proto-native.cc\n  commands/native/ns-proto-native.cc\n  commands/native/quota-proto-native.cc\n  commands/native/recycle-proto-native.cc\n  commands/native/register-proto-native.cc\n  commands/native/rm-proto-native.cc\n  commands/native/route-proto-native.cc\n  commands/native/sched-proto-native.cc\n  commands/native/space-proto-native.cc\n  commands/native/token-proto-native.cc\n  commands/native/tracker-proto-native.cc\n\n  # Other native commands\n  commands/native/daemon-native.cc\n  commands/native/fuse-native.cc\n  commands/native/fusex-cmd-native.cc\n  commands/native/health-native.cc\n  commands/native/license-native.cc\n  commands/native/reconnect-native.cc\n  commands/native/report-native.cc\n  commands/native/role-native.cc\n  commands/native/scitoken-native.cc\n  commands/native/stat-native.cc\n  commands/native/test-native.cc\n\n  # Legacy symbol aggregation\n  commands/native/LegacySymbols.cc\n\n  # Shared helpers used by native/proto commands\n  commands/helpers/NewfindHelper.cc commands/helpers/NewfindHelper.hh\n  commands/helpers/AclHelper.cc     commands/helpers/AclHelper.hh\n  commands/HealthCommand.cc         commands/HealthCommand.hh\n)\n\ntarget_link_libraries(EosConsoleCommands-Objects PUBLIC\n  EosConsoleHelpers-Objects\n  READLINE::READLINE\n  JSONCPP::JSONCPP\n  SCITOKENS::SCITOKENS\n  XROOTD::UTILS\n  CLI11::CLI11)\n\ntarget_include_directories(EosConsoleCommands-Objects PUBLIC\n  ${CMAKE_CURRENT_SOURCE_DIR}/parser/include)\n\nset_target_properties(EosConsoleCommands-Objects PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\nadd_executable(eos ConsoleMainExecutable.cc)\n\n#-------------------------------------------------------------------------------\n# Add dependency which guarantees that the protocol buffer files are generated\n# when we build the \"eos\" executable.\n#-------------------------------------------------------------------------------\ntarget_link_libraries(\n  eos PUBLIC\n  EosConsoleCommands-Objects\n  EosConsoleHelpers-Objects\n  ZMQ::ZMQ\n  XROOTD::POSIX\n  SCITOKENS::SCITOKENS\n  EosFstIo-Static)\n\nif (Linux)\n  target_link_libraries(eos PUBLIC stdc++fs)\nendif()\n\ninstall(PROGRAMS eosadmin eos-iam-mapfile\n  DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR})\n\ninstall(TARGETS eos\n  LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n  ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\n"
  },
  {
    "path": "console/CommandFramework.cc",
    "content": "// ----------------------------------------------------------------------\n// File: CommandFramework.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/Path.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <sstream>\n#include <cstdlib>\n\nCommandRegistry& CommandRegistry::instance()\n{\n  static CommandRegistry inst;\n  return inst;\n}\n\nnamespace {\nbool gRegistryInitialized = false;\n}\n\nvoid CommandRegistry::reg(std::unique_ptr<IConsoleCommand> cmd)\n{\n  mCommandsView.push_back(cmd.get());\n  mCommands.emplace_back(std::move(cmd));\n}\n\nIConsoleCommand* CommandRegistry::find(const std::string& name) const\n{\n  // Prefer most recently registered (native overrides legacy)\n  for (auto it = mCommandsView.rbegin(); it != mCommandsView.rend(); ++it) {\n    if (name == (*it)->name()) return *it;\n  }\n  // Simple aliases\n  if (name == \"fileinfo\") {\n    return find(\"file\");\n  }\n  return nullptr;\n}\n\nint CFuncCommandAdapter::run(const std::vector<std::string>& args, CommandContext&)\n{\n  std::ostringstream oss;\n  for (size_t i = 0; i < args.size(); ++i) {\n    if (i) oss << ' ';\n    oss << args[i];\n  }\n  std::string joined = oss.str();\n  return mFunc((char*)joined.c_str());\n}\n\n// Default empty; concrete commands will be registered here as they are migrated\nvoid RegisterNativeConsoleCommands()\n{\n  if (gRegistryInitialized) {\n    return;\n  }\n\n  gRegistryInitialized = true;\n\n  // Registration split across native modules for maintainability\n  // Core\n  extern void RegisterCoreNativeCommands();\n  RegisterCoreNativeCommands();\n  // Pwd\n  extern void RegisterPwdNativeCommand();\n  RegisterPwdNativeCommand();\n  // Cd\n  extern void RegisterCdNativeCommand();\n  RegisterCdNativeCommand();\n  // Ls\n  extern void RegisterLsNativeCommand();\n  RegisterLsNativeCommand();\n  // Cp\n  extern void RegisterCpNativeCommand();\n  RegisterCpNativeCommand();\n  // Version\n  extern void RegisterVersionNativeCommand();\n  RegisterVersionNativeCommand();\n  // Status\n  extern void RegisterStatusNativeCommand();\n  RegisterStatusNativeCommand();\n  // Tui\n  extern void RegisterTuiNativeCommand();\n  RegisterTuiNativeCommand();\n  // Mkdir/Rm\n  extern void RegisterMkdirNativeCommand();\n  RegisterMkdirNativeCommand();\n  extern void RegisterRmProtoNativeCommand();\n  RegisterRmProtoNativeCommand();\n  // Info\n  extern void RegisterInfoNativeCommand();\n  RegisterInfoNativeCommand();\n  // Stat\n  extern void RegisterStatNativeCommand();\n  RegisterStatNativeCommand();\n  // Mv\n  extern void RegisterMvNativeCommand();\n  RegisterMvNativeCommand();\n  // Ln\n  extern void RegisterLnNativeCommand();\n  RegisterLnNativeCommand();\n  // Rmdir\n  extern void RegisterRmdirNativeCommand();\n  RegisterRmdirNativeCommand();\n  // Touch\n  extern void RegisterTouchNativeCommand();\n  RegisterTouchNativeCommand();\n  // Cat\n  extern void RegisterCatNativeCommand();\n  RegisterCatNativeCommand();\n  // Who\n  extern void RegisterWhoNativeCommand();\n  RegisterWhoNativeCommand();\n  // Whoami\n  extern void RegisterWhoamiNativeCommand();\n  RegisterWhoamiNativeCommand();\n  // Proto commands\n  extern void RegisterAccessProtoNativeCommand();\n  RegisterAccessProtoNativeCommand();\n  extern void RegisterAclProtoNativeCommand();\n  RegisterAclProtoNativeCommand();\n  extern void RegisterConfigProtoNativeCommand();\n  RegisterConfigProtoNativeCommand();\n  extern void RegisterConvertProtoNativeCommand();\n  RegisterConvertProtoNativeCommand();\n  extern void RegisterDevicesProtoNativeCommand();\n  RegisterDevicesProtoNativeCommand();\n  extern void RegisterDfProtoNativeCommand();\n  RegisterDfProtoNativeCommand();\n  extern void RegisterFindProtoNativeCommand();\n  RegisterFindProtoNativeCommand();\n  extern void RegisterFsProtoNativeCommand();\n  RegisterFsProtoNativeCommand();\n  extern void RegisterFsckProtoNativeCommand();\n  RegisterFsckProtoNativeCommand();\n  extern void RegisterGroupProtoNativeCommand();\n  RegisterGroupProtoNativeCommand();\n  extern void RegisterIoProtoNativeCommand();\n  RegisterIoProtoNativeCommand();\n  extern void RegisterNodeProtoNativeCommand();\n  RegisterNodeProtoNativeCommand();\n  extern void RegisterNsProtoNativeCommand();\n  RegisterNsProtoNativeCommand();\n  extern void RegisterQuotaProtoNativeCommand();\n  RegisterQuotaProtoNativeCommand();\n  extern void RegisterRecycleProtoNativeCommand();\n  RegisterRecycleProtoNativeCommand();\n  extern void RegisterRegisterProtoNativeCommand();\n  RegisterRegisterProtoNativeCommand();\n  extern void RegisterRouteProtoNativeCommand();\n  RegisterRouteProtoNativeCommand();\n  extern void RegisterTokenProtoNativeCommand();\n  RegisterTokenProtoNativeCommand();\n  extern void RegisterSpaceProtoNativeCommand();\n  RegisterSpaceProtoNativeCommand();\n  extern void RegisterSchedProtoNativeCommand();\n  RegisterSchedProtoNativeCommand();\n  // file/fuse/fusex\n  extern void RegisterFileNativeCommand();\n  RegisterFileNativeCommand();\n  extern void RegisterFileInfoAliasCommand();\n  RegisterFileInfoAliasCommand();\n  extern void RegisterFuseNativeCommand();\n  RegisterFuseNativeCommand();\n  extern void RegisterFusexNativeCommand();\n  RegisterFusexNativeCommand();\n  // Misc\n  extern void RegisterBackupNativeCommand();\n  RegisterBackupNativeCommand();\n  extern void RegisterClearNativeCommand();\n  RegisterClearNativeCommand();\n  extern void RegisterDebugNativeCommand();\n  RegisterDebugNativeCommand();\n  extern void RegisterDuNativeCommand();\n  RegisterDuNativeCommand();\n  extern void RegisterEvictNativeCommand();\n  RegisterEvictNativeCommand();\n  extern void RegisterMotdNativeCommand();\n  RegisterMotdNativeCommand();\n  extern void RegisterOldfindNativeCommand();\n  RegisterOldfindNativeCommand();\n  extern void RegisterRcloneNativeCommand();\n  RegisterRcloneNativeCommand();\n  extern void RegisterSquashNativeCommand();\n  RegisterSquashNativeCommand();\n  extern void RegisterTestNativeCommand();\n  RegisterTestNativeCommand();\n  // Attr/Mode\n  extern void RegisterArchiveNativeCommand();\n  RegisterArchiveNativeCommand();\n  extern void RegisterAttrNativeCommand();\n  RegisterAttrNativeCommand();\n  extern void RegisterChmodNativeCommand();\n  RegisterChmodNativeCommand();\n  extern void RegisterChownNativeCommand();\n  RegisterChownNativeCommand();\n  // Admin/Device and misc extras\n  extern void RegisterDaemonNativeCommand();\n  RegisterDaemonNativeCommand();\n  extern void RegisterGeoschedNativeCommand();\n  RegisterGeoschedNativeCommand();\n  extern void RegisterInspectorNativeCommand();\n  RegisterInspectorNativeCommand();\n  extern void RegisterLicenseNativeCommand();\n  RegisterLicenseNativeCommand();\n  extern void RegisterMapNativeCommand();\n  RegisterMapNativeCommand();\n  extern void RegisterMemberNativeCommand();\n  RegisterMemberNativeCommand();\n  extern void RegisterAccountingNativeCommand();\n  RegisterAccountingNativeCommand();\n  extern void RegisterHealthNativeCommand();\n  RegisterHealthNativeCommand();\n  extern void RegisterReconnectNativeCommand();\n  RegisterReconnectNativeCommand();\n  extern void RegisterReportNativeCommand();\n  RegisterReportNativeCommand();\n  extern void RegisterRtlogNativeCommand();\n  RegisterRtlogNativeCommand();\n  extern void RegisterRoleNativeCommand();\n  RegisterRoleNativeCommand();\n  extern void RegisterScitokenNativeCommand();\n  RegisterScitokenNativeCommand();\n  extern void RegisterTrackerNativeCommand();\n  RegisterTrackerNativeCommand();\n  extern void RegisterVidNativeCommand();\n  RegisterVidNativeCommand();\n  // RemainingLegacyNativeCommands removed\n}\n\nvoid\nEnsureNativeCommandRegistryInitialized()\n{\n  RegisterNativeConsoleCommands();\n}\n"
  },
  {
    "path": "console/CommandFramework.hh",
    "content": "// ----------------------------------------------------------------------\n// File: CommandFramework.hh\n// Purpose: Lightweight command registry and adapters for console commands\n// ----------------------------------------------------------------------\n\n#pragma once\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"console/ConsoleMain.hh\"\n\n// Context passed to commands to access existing facilities without global coupling\nstruct CommandContext {\n  std::string serverUri;\n  GlobalOptions* globalOpts;\n  bool json;\n  bool silent;\n  bool interactive;\n  bool timing;\n  std::string userRole;\n  std::string groupRole;\n\n  // Thin wrappers to existing functions\n  XrdOucEnv* (*clientCommand)(XrdOucString& in, bool isAdmin, std::string* reply);\n  int (*outputResult)(XrdOucEnv* result, bool highlighting);\n};\n\nclass IConsoleCommand {\npublic:\n  virtual ~IConsoleCommand() = default;\n  virtual const char* name() const = 0;\n  virtual const char* description() const = 0;\n  virtual std::string\n  helpText() const\n  {\n    return {};\n  }\n  virtual bool requiresMgm(const std::string& args) const { return !wants_help(args.c_str()); }\n  virtual std::vector<std::string>\n  complete(const std::vector<std::string>& args) const\n  {\n    return {};\n  }\n  virtual int run(const std::vector<std::string>& args, CommandContext& ctx) = 0;\n  virtual void printHelp() const = 0;\n};\n\nclass CommandRegistry {\npublic:\n  static CommandRegistry& instance();\n  void reg(std::unique_ptr<IConsoleCommand> cmd);\n  IConsoleCommand* find(const std::string& name) const;\n  const std::vector<IConsoleCommand*>& all() const { return mCommandsView; }\n\nprivate:\n  std::vector<std::unique_ptr<IConsoleCommand>> mCommands;\n  std::vector<IConsoleCommand*> mCommandsView;\n};\n\n// Adapter to integrate legacy C-style commands (int func(char*))\nclass CFuncCommandAdapter : public IConsoleCommand {\npublic:\n  using CFunc = int(*)(char*);\n  CFuncCommandAdapter(const char* n, const char* d, CFunc f, bool reqMgm)\n    : mName(n), mDesc(d), mFunc(f), mRequiresMgm(reqMgm) {}\n\n  const char* name() const override { return mName.c_str(); }\n  const char* description() const override { return mDesc.c_str(); }\n  bool requiresMgm(const std::string& args) const override { return mRequiresMgm && !wants_help(args.c_str()); }\n  int run(const std::vector<std::string>& args, CommandContext&) override;\n  void printHelp() const override {}\n\nprivate:\n  std::string mName;\n  std::string mDesc;\n  CFunc mFunc;\n  bool mRequiresMgm;\n};\n\n// Register native (class-based) commands that supersede legacy ones\nvoid RegisterNativeConsoleCommands();\nvoid EnsureNativeCommandRegistryInitialized();\n"
  },
  {
    "path": "console/ConsoleArgParser.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ConsoleArgParser.cc\n// ----------------------------------------------------------------------\n\n#include \"console/ConsoleArgParser.hh\"\n#include <sstream>\n\nnamespace {\nstatic bool starts_with(const std::string& s, const char* pfx) {\n  size_t n = 0; while (pfx[n]) ++n; if (s.size() < n) return false; return s.compare(0, n, pfx) == 0;\n}\n}\n\nConsoleArgParser::ConsoleArgParser() = default;\n\nnamespace {\nstatic inline std::string dequoteToken(const std::string& in) {\n  if (in.size() >= 2) {\n    char a = in.front();\n    char b = in.back();\n    if ((a == '\"' && b == '\"') || (a == '\\'' && b == '\\'')) {\n      // avoid \"\" and '' edge-case returning empty meaningful token\n      return in.substr(1, in.size() - 2);\n    }\n  }\n  return in;\n}\n}\n\nConsoleArgParser& ConsoleArgParser::setProgramName(const std::string& nm) {\n  mProgramName = nm; return *this;\n}\n\nConsoleArgParser& ConsoleArgParser::setDescription(const std::string& desc) {\n  mDescription = desc; return *this;\n}\n\nConsoleArgParser& ConsoleArgParser::allowCombinedShortOptions(bool allow) {\n  mAllowCombinedShorts = allow; return *this;\n}\n\nConsoleArgParser& ConsoleArgParser::allowAttachedValue(bool allow) {\n  mAllowAttachedValue = allow; return *this;\n}\n\nConsoleArgParser& ConsoleArgParser::acceptBareAssignments(bool accept) {\n  mAcceptBareAssignments = accept; return *this;\n}\n\nConsoleArgParser& ConsoleArgParser::collectUnknownTokens(bool collect) {\n  mCollectUnknownTokens = collect; return *this;\n}\n\nConsoleArgParser& ConsoleArgParser::addOption(const OptionSpec& spec) {\n  InternalSpec is; is.spec = spec;\n  size_t idx = mSpecs.size();\n  mSpecs.push_back(is);\n  if (!spec.longName.empty()) mLongToIndex.emplace(spec.longName, idx);\n  if (spec.shortName) mShortToIndex.emplace(spec.shortName, idx);\n  return *this;\n}\n\nconst ConsoleArgParser::InternalSpec* ConsoleArgParser::findByLong(const std::string& nm) const {\n  auto it = mLongToIndex.find(nm);\n  if (it == mLongToIndex.end()) return nullptr;\n  return &mSpecs[it->second];\n}\n\nconst ConsoleArgParser::InternalSpec* ConsoleArgParser::findByShort(char c) const {\n  auto it = mShortToIndex.find(c);\n  if (it == mShortToIndex.end()) return nullptr;\n  return &mSpecs[it->second];\n}\n\nbool ConsoleArgParser::ParseResult::has(const std::string& name) const {\n  return optionToValues.find(name) != optionToValues.end();\n}\n\nbool ConsoleArgParser::ParseResult::flag(const std::string& name) const {\n  auto it = optionToValues.find(name);\n  if (it == optionToValues.end()) return false;\n  return it->second.empty();\n}\n\nstd::string ConsoleArgParser::ParseResult::value(const std::string& name, const std::string& fallback) const {\n  auto it = optionToValues.find(name);\n  if (it == optionToValues.end() || it->second.empty()) return fallback;\n  return it->second.back();\n}\n\nstd::vector<std::string> ConsoleArgParser::ParseResult::values(const std::string& name) const {\n  auto it = optionToValues.find(name);\n  if (it == optionToValues.end()) return {};\n  return it->second;\n}\n\nConsoleArgParser::ParseResult ConsoleArgParser::parse(const std::vector<std::string>& args) const {\n  ParseResult r;\n  bool onlyPositionals = false;\n\n  auto add_value = [&](const InternalSpec* spec, const std::string& v) {\n    if (!spec) return;\n    auto& vec = r.optionToValues[spec->spec.longName.empty() ? std::string(1, spec->spec.shortName) : spec->spec.longName];\n    vec.push_back(v);\n  };\n\n  auto set_flag = [&](const InternalSpec* spec){ add_value(spec, std::string()); };\n\n  for (size_t i = 0; i < args.size(); ++i) {\n    std::string tok = dequoteToken(args[i]);\n    if (onlyPositionals) { r.positionals.push_back(tok); continue; }\n\n    if (tok == \"--\") { onlyPositionals = true; continue; }\n\n    // Long option: --opt or --opt=value\n    if (starts_with(tok, \"--\")) {\n      std::string nameval = dequoteToken(tok.substr(2));\n      std::string name, val;\n      size_t eq = nameval.find('=');\n      if (eq == std::string::npos) name = nameval; else { name = nameval.substr(0, eq); val = dequoteToken(nameval.substr(eq+1)); }\n      auto* s = findByLong(name);\n      if (!s) { if (mCollectUnknownTokens) r.unknownTokens.push_back(tok); else r.errors.push_back(\"Unknown option: \" + tok); continue; }\n      if (s->spec.requiresValue) {\n        if (!val.empty()) { add_value(s, val); }\n        else {\n          if (i+1 < args.size()) { add_value(s, dequoteToken(args[++i])); }\n          else { r.errors.push_back(\"Missing value for option --\" + name); }\n        }\n      } else {\n        set_flag(s);\n      }\n      continue;\n    }\n\n    // Short option(s): -a -abc -oValue -o Value\n    if (tok.size() >= 2 && tok[0] == '-' && tok[1] != '-') {\n      // Combined or single\n      if (mAllowCombinedShorts && tok.size() > 2) {\n        // iterate every char after '-'\n        for (size_t k = 1; k < tok.size(); ++k) {\n          char c = tok[k];\n          auto* s = findByShort(c);\n          if (!s) { if (mCollectUnknownTokens) r.unknownTokens.push_back(std::string(\"-\") + c); else r.errors.push_back(std::string(\"Unknown option: -\") + c); continue; }\n          if (s->spec.requiresValue) {\n            std::string val;\n            if (mAllowAttachedValue && k+1 < tok.size()) {\n              val = tok.substr(k+1);\n              add_value(s, val);\n              break; // rest belongs to value\n            } else if (i+1 < args.size()) {\n              add_value(s, dequoteToken(args[++i]));\n            } else {\n              r.errors.push_back(std::string(\"Missing value for option -\") + c);\n            }\n          } else {\n            set_flag(s);\n          }\n        }\n      } else {\n        // Single short option like -o or -oValue\n        char c = tok.size() > 1 ? tok[1] : '\\0';\n        auto* s = findByShort(c);\n        if (!s) { if (mCollectUnknownTokens) r.unknownTokens.push_back(tok); else r.errors.push_back(\"Unknown option: \" + tok); continue; }\n        if (s->spec.requiresValue) {\n          std::string val;\n          if (mAllowAttachedValue && tok.size() > 2) val = tok.substr(2);\n          else if (i+1 < args.size()) val = dequoteToken(args[++i]);\n          else r.errors.push_back(std::string(\"Missing value for option -\") + c);\n          if (!val.empty()) add_value(s, val);\n        } else {\n          set_flag(s);\n        }\n      }\n      continue;\n    }\n\n    // Bare assignment key=value (legacy style)\n    if (mAcceptBareAssignments) {\n      size_t eq = tok.find('=');\n      if (eq != std::string::npos && eq > 0) {\n        std::string key = dequoteToken(tok.substr(0, eq));\n        std::string val = dequoteToken(tok.substr(eq+1));\n        // if there is a spec matching the long name, map it; otherwise keep as positional assignment\n        if (auto* s = findByLong(key)) {\n          add_value(s, val);\n        } else {\n          r.positionals.push_back(tok);\n        }\n        continue;\n      }\n    }\n\n    // Positional\n    r.positionals.push_back(tok);\n  }\n\n  // Apply defaults\n  for (const auto& ins : mSpecs) {\n    const auto& spec = ins.spec;\n    if (!spec.defaultValue.empty() && r.optionToValues.find(spec.longName) == r.optionToValues.end()) {\n      r.optionToValues[spec.longName].push_back(spec.defaultValue);\n    }\n  }\n\n  return r;\n}\n\nstd::string ConsoleArgParser::help() const {\n  std::ostringstream oss;\n  if (!mProgramName.empty()) oss << \"Usage: \" << mProgramName << \" [options] [--] [args...]\\n\";\n  if (!mDescription.empty()) oss << mDescription << \"\\n\\n\";\n  oss << \"Options:\\n\";\n  for (const auto& ins : mSpecs) {\n    const auto& s = ins.spec;\n    oss << \"  \";\n    if (s.shortName) oss << '-' << s.shortName << (s.longName.empty() ? \"\" : \", \");\n    if (!s.longName.empty()) oss << \"--\" << s.longName;\n    if (s.requiresValue) oss << ' ' << (s.valueName.empty() ? \"<value>\" : s.valueName);\n    if (!s.description.empty()) oss << \"\\t\" << s.description;\n    if (!s.defaultValue.empty()) oss << \" (default: \" << s.defaultValue << \")\";\n    oss << \"\\n\";\n  }\n  return oss.str();\n}\n"
  },
  {
    "path": "console/ConsoleArgParser.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ConsoleArgParser.hh\n// ----------------------------------------------------------------------\n\n#pragma once\n\n#include <string>\n#include <vector>\n#include <unordered_map>\n#include <unordered_set>\n#include <memory>\n\nclass ConsoleArgParser {\npublic:\n  struct OptionSpec {\n    std::string longName;\n    char shortName = '\\0';\n    bool requiresValue = false;\n    bool allowMultiple = false;\n    std::string valueName;\n    std::string description;\n    std::string defaultValue;\n  };\n\n  struct ParseResult {\n    std::unordered_map<std::string, std::vector<std::string>> optionToValues;\n    std::vector<std::string> positionals;\n    std::vector<std::string> unknownTokens;\n    std::vector<std::string> errors;\n\n    bool has(const std::string& name) const;\n    bool flag(const std::string& name) const; // present without a required value\n    std::string value(const std::string& name, const std::string& fallback = \"\") const;\n    std::vector<std::string> values(const std::string& name) const;\n  };\n\n  ConsoleArgParser();\n\n  ConsoleArgParser& setProgramName(const std::string& nm);\n  ConsoleArgParser& setDescription(const std::string& desc);\n  ConsoleArgParser& allowCombinedShortOptions(bool allow);\n  ConsoleArgParser& allowAttachedValue(bool allow);\n  ConsoleArgParser& acceptBareAssignments(bool accept); // e.g. key=value without dashes\n  ConsoleArgParser& collectUnknownTokens(bool collect);\n\n  ConsoleArgParser& addOption(const OptionSpec& spec);\n\n  ParseResult parse(const std::vector<std::string>& args) const;\n\n  std::string help() const;\n\nprivate:\n  struct InternalSpec {\n    OptionSpec spec;\n  };\n\n  const InternalSpec* findByLong(const std::string& nm) const;\n  const InternalSpec* findByShort(char c) const;\n\n  std::string mProgramName;\n  std::string mDescription;\n  bool mAllowCombinedShorts = true;\n  bool mAllowAttachedValue = true;\n  bool mAcceptBareAssignments = true;\n  bool mCollectUnknownTokens = true;\n\n  std::vector<InternalSpec> mSpecs;\n  std::unordered_map<std::string, size_t> mLongToIndex;\n  std::unordered_map<char, size_t> mShortToIndex;\n};\n"
  },
  {
    "path": "console/ConsoleCliCommand.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ConsoleCliCommand.cc\n// Author: Joaquim Rocha - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <string.h>\n#include <assert.h>\n#include <sstream>\n#include <algorithm>\n#include <climits>\n#include \"ConsoleCliCommand.hh\"\n#include \"common/StringTokenizer.hh\"\n\n#define HELP_PADDING 50\n#define DESC_LINE_LENGTH 70\n\nbool isFloatEvalFunc(const CliOptionWithArgs* option,\n                     std::vector<std::string>& args,\n                     std::string** error,\n                     void* userData)\n{\n  for (size_t i = 0; i < args.size(); i++) {\n    for (size_t s = 0; s < args[i].length(); s++) {\n      istringstream ss(args[i]);\n      float number;\n\n      if (!(ss >> number)) {\n        if (error) {\n          *error = new std::string(\"Error: Option \" + option->repr() +\n                                   \" needs a float.\");\n        }\n\n        return false;\n      }\n    }\n  }\n\n  return true;\n}\n\nbool isIntegerEvalFunc(const CliOptionWithArgs* option,\n                       std::vector<std::string>& args,\n                       std::string** error,\n                       void* userData)\n{\n  for (size_t i = 0; i < args.size(); i++) {\n    size_t s = 0;\n\n    if (args[i][0] == '-') {\n      s++;\n    }\n\n    for (; s < args[i].length(); s++) {\n      if (!std::isdigit(args[i][s])) {\n        if (error) {\n          *error = new std::string(\"Error: Option \" + option->repr() +\n                                   \" needs an integer.\");\n        }\n\n        return false;\n      }\n    }\n  }\n\n  return true;\n}\n\nbool isNumberInRangeEvalFunc(const CliOptionWithArgs* option,\n                             std::vector<std::string>& args,\n                             std::string** error,\n                             const std::pair<float, float>* range)\n{\n  for (size_t i = 0; i < args.size(); i++) {\n    for (size_t s = 0; s < args[i].length(); s++) {\n      istringstream ss(args[i]);\n      float number;\n\n      if (!(ss >> number) || (range->first > number || number > range->second)) {\n        ostringstream limit;\n\n        if (error) {\n          *error = new std::string(\"Error: Option \" + option->repr() +\n                                   \" needs to be between \");\n          limit << range->first << \" and \" << range->second;\n          (*error)->append(limit.str());\n        }\n\n        return false;\n      }\n    }\n  }\n\n  return true;\n}\n\nbool isPositiveNumberEvalFunc(const CliOptionWithArgs* option,\n                              std::vector<std::string>& args,\n                              std::string** error,\n                              void* data)\n{\n  std::pair<float, float> groupSizeRange = {0.0, (float) INT_MAX};\n\n  if (!isNumberInRangeEvalFunc(option, args, 0, &groupSizeRange)) {\n    *error = new std::string(\"Error: Option \" + option->repr() + \" needs to be \"\n                             \"a positive number.\");\n    return false;\n  }\n\n  return true;\n}\n\nbool isNegativeNumberEvalFunc(const CliOptionWithArgs* option,\n                              std::vector<std::string>& args,\n                              std::string** error,\n                              void* data)\n{\n  std::pair<float, float> groupSizeRange = {(float) - INT_MAX, 0};\n\n  if (!isNumberInRangeEvalFunc(option, args, 0, &groupSizeRange)) {\n    *error = new std::string(\"Error: Option \" + option->repr() + \" needs to be \"\n                             \"a negative number.\");\n    return false;\n  }\n\n  return true;\n}\n\nbool isChoiceEvalFunc(const CliOptionWithArgs* option,\n                      std::vector<std::string>& args,\n                      std::string** error,\n                      const std::vector<std::string>* choices)\n{\n  assert(choices->size() != 0);\n\n  for (size_t i = 0; i < args.size(); i++) {\n    if (std::find(choices->begin(), choices->end(), args[i]) == choices->end()) {\n      if (error) {\n        *error = new std::string(\"Error: Option \" + option->repr() + \" needs to be\");\n        (*error)->append(\" \" + choices->at(0));\n\n        for (size_t c = 1; c < choices->size(); c++) {\n          (*error)->append((c == choices->size() - 1 ? \" or \" : \", \") + choices->at(c));\n        }\n      }\n\n      return false;\n    }\n  }\n\n  return true;\n}\n\nstd::vector<std::string>*\nsplitKeywords(std::string keywords, char delimiter)\n{\n  std::vector<std::string>* splitKeywords = new std::vector<std::string>;\n  eos::common::StringTokenizer tokenizer(keywords, delimiter);\n  tokenizer.GetLine();\n  const char* token = tokenizer.GetToken();\n\n  while (token) {\n    splitKeywords->push_back(token);\n    token = tokenizer.GetToken();\n  }\n\n  return splitKeywords;\n}\n\nParseError::ParseError(CliBaseOption* option, std::string message)\n  : mOption(option),\n    mMessage(message)\n{}\n\nCliBaseOption::CliBaseOption(std::string name, std::string desc)\n  : mName(name),\n    mDescription(desc),\n    mRequired(false),\n    mHidden(false)\n{};\n\nCliBaseOption::~CliBaseOption() {};\n\nCliOption::CliOption(std::string name, std::string desc, std::string keywords)\n  : CliBaseOption(name, desc),\n    mKeywords(splitKeywords(keywords, ',')),\n    mGroup(0)\n{\n};\n\nCliOption::CliOption():\n  mKeywords(nullptr), mGroup(nullptr)\n{\n  CliOption(\"\", \"\", \"\");\n}\n\nCliOption::CliOption(const CliOption& option)\n  : CliBaseOption(option.mName, option.mDescription),\n    mGroup(nullptr)\n{\n  CliOption(option.mName, option.mDescription, \"\");\n  mRequired = option.required();\n  mHidden = option.hidden();\n\n  if (option.mKeywords) {\n    mKeywords = new std::vector<std::string>;\n    std::vector<std::string>::const_iterator it;\n\n    for (it = option.mKeywords->cbegin(); it != option.mKeywords->cend(); it++) {\n      mKeywords->push_back(*it);\n    }\n  }\n}\n\nCliOption::~CliOption()\n{\n  delete mKeywords;\n  mKeywords = nullptr;\n}\n\nvoid\nCliOption::setGroup(OptionsGroup* group)\n{\n  if (mGroup == group) {\n    return;\n  }\n\n  if (mGroup) {\n    mGroup->removeOption(this);\n  }\n\n  mGroup = group;\n}\n\nstd::string\nCliOption::hasKeyword(std::string keyword)\n{\n  if (!mKeywords) {\n    return \"\";\n  }\n\n  std::vector<std::string>::const_iterator it = mKeywords->cbegin();\n\n  for (; it != mKeywords->cend(); it++) {\n    if (keyword.compare(*it) == 0) {\n      return *it;\n    }\n  }\n\n  return \"\";\n}\n\nAnalysisResult*\nCliOption::analyse(std::vector<std::string>& cliArgs)\n{\n  std::pair<std::string, std::vector<std::string>> ret;\n  std::vector<std::string>::iterator it = cliArgs.begin();\n  AnalysisResult* res = new AnalysisResult;\n  res->start = res->end = it;\n\n  for (; it != cliArgs.end(); it++) {\n    if (hasKeyword(*it) != \"\") {\n      ret.first = mName;\n      res->start = it;\n      res->end = it + 1;\n      break;\n    }\n  }\n\n  res->values = ret;\n  res->errorMsg = \"\";\n  return res;\n}\n\nstd::string\nCliOption::joinKeywords()\n{\n  std::string keyword(\"\");\n\n  if (!mKeywords) {\n    return \"\";\n  }\n\n  for (size_t i = 0; i < mKeywords->size(); i++) {\n    keyword += mKeywords->at(i);\n\n    if (i < mKeywords->size() - 1) {\n      keyword += std::string(\"|\");\n    }\n  }\n\n  return keyword;\n}\n\nchar*\nCliOption::keywordsRepr()\n{\n  char* repr = NULL;\n  std::string keyword = joinKeywords();\n\n  if (keyword != \"\") {\n    if (!mRequired) {\n      keyword = \"[\" + keyword + \"]\";\n    }\n\n    repr = strdup(keyword.c_str());\n  }\n\n  return repr;\n}\n\nstd::string\nCliOption::repr() const\n{\n  return mKeywords ? mKeywords->at(0) : \"\";\n}\n\nstatic std::string\ntruncateDescString(const std::string& description, const std::string& prefix)\n{\n  std::string desc(\"\");\n  size_t lineStart = 0, lineEnd = 0;\n\n  for (size_t i = 0; i < description.length(); i++) {\n    if (i == description.length() - 1) {\n      lineEnd = i + 1;\n    }\n\n    if (lineEnd != lineStart &&\n        (i - lineStart > DESC_LINE_LENGTH || i == description.length() - 1 ||\n         description[lineEnd] == '\\n')) {\n      if (lineEnd > DESC_LINE_LENGTH || desc[desc.length() - 1] == '\\n') {\n        desc += \"\\n\" + std::string(HELP_PADDING, ' ') + prefix;\n      }\n\n      desc += std::string(description, lineStart, lineEnd - lineStart);\n      lineStart = lineEnd + 1;\n      lineEnd = lineStart;\n      continue;\n    }\n\n    if (description[i] == ' ' || description[i] == '\\n') {\n      lineEnd = i;\n    }\n  }\n\n  desc += \"\\n\";\n  return desc;\n}\n\nchar*\nCliOption::helpString()\n{\n  if (mDescription == \"\" || !mKeywords) {\n    return NULL;\n  }\n\n  char* helpStr = 0;\n  std::string keyword(\"\");\n\n  for (size_t i = 0; i < mKeywords->size(); i++) {\n    keyword += mKeywords->at(i);\n\n    if (i < mKeywords->size() - 1) {\n      keyword += std::string(\"|\");\n    }\n  }\n\n  if (keyword != \"\") {\n    int strSize = keyword.length() + HELP_PADDING + 10;\n    helpStr = new char[strSize];\n  }\n\n  sprintf(helpStr, \"%*s\\t- \", HELP_PADDING, keyword.c_str());\n  std::string help(helpStr);\n  help += truncateDescString(mDescription, \"\\t  \");\n  delete[] helpStr;\n  return strdup(help.c_str());\n}\n\nvoid\nCliOptionWithArgs::init(int numArgs,\n                        std::string repr,\n                        bool required)\n{\n  mNumArgs = numArgs;\n  mRepr = repr;\n  mEvalFunctions = 0;\n  mUserData = 0;\n  mRequired = required;\n}\n\nCliOptionWithArgs::CliOptionWithArgs(std::string name,\n                                     std::string desc,\n                                     std::string keywords,\n                                     int numArgs,\n                                     std::string repr,\n                                     bool required)\n  : CliOption::CliOption(name, desc, keywords)\n{\n  init(numArgs, repr, required);\n}\n\nCliOptionWithArgs::CliOptionWithArgs(std::string name,\n                                     std::string desc,\n                                     std::string keywords,\n                                     std::string repr,\n                                     bool required)\n  : CliOption::CliOption(name, desc, keywords)\n{\n  init(1, repr, required);\n}\n\nCliOptionWithArgs::CliOptionWithArgs()\n  : CliOption::CliOption(\"\", \"\", \"\")\n{\n  init(1, \"\", false);\n}\n\nCliOptionWithArgs::CliOptionWithArgs(const CliOptionWithArgs& otherOption)\n  : CliOption::CliOption(otherOption.mName, otherOption.mDescription, \"\")\n{\n  init(otherOption.mNumArgs, otherOption.mRepr, otherOption.mRequired);\n  mHidden = otherOption.hidden();\n\n  if (otherOption.mKeywords) {\n    std::vector<std::string>::const_iterator it;\n\n    for (it = otherOption.mKeywords->cbegin();\n         it != otherOption.mKeywords->cend(); it++) {\n      mKeywords->push_back(*it);\n    }\n  }\n\n  if (otherOption.mEvalFunctions) {\n    mEvalFunctions = new std::vector<evalFuncCb>(*otherOption.mEvalFunctions);\n\n    if (otherOption.mUserData) {\n      mUserData = new std::vector<void*>(*otherOption.mUserData);\n    }\n  }\n}\n\nCliOptionWithArgs::~CliOptionWithArgs()\n{\n  if (mEvalFunctions) {\n    delete mEvalFunctions;\n  }\n\n  mEvalFunctions = 0;\n\n  if (mUserData) {\n    delete mUserData;\n  }\n\n  mUserData = 0;\n}\n\nchar*\nCliOptionWithArgs::helpString()\n{\n  if (mDescription == \"\" || !mKeywords) {\n    return NULL;\n  }\n\n  char* helpStr = 0;\n  std::string keyword(\"\");\n  ostringstream helpRepr(mRepr);\n\n  if (helpRepr.str() == \"\") {\n    if (mNumArgs == -1) {\n      helpRepr << \"<value1> <value2> ...\";\n    } else {\n      for (int i = 1; i <= mNumArgs; i++) {\n        helpRepr << \"<value\" << i << \">\" << (i == mNumArgs ? \"\" : \" \");\n      }\n\n      helpRepr << \"...\";\n    }\n  }\n\n  for (size_t i = 0; i < mKeywords->size(); i++) {\n    keyword += mKeywords->at(i);\n\n    if (keyword[keyword.length() - 1] == '=') {\n      keyword += helpRepr.str();\n    } else {\n      keyword += \" \" + helpRepr.str();\n    }\n\n    if (i < mKeywords->size() - 1) {\n      keyword += std::string(\"|\");\n    }\n  }\n\n  if (keyword != \"\") {\n    int strSize = keyword.length() + HELP_PADDING + 10;\n    helpStr = new char[strSize];\n  }\n\n  sprintf(helpStr, \"%*s\\t- \", HELP_PADDING, keyword.c_str());\n  std::string help(helpStr);\n  help += truncateDescString(mDescription, \"\\t  \");\n  delete[] helpStr;\n  return strdup(help.c_str());\n}\n\nvoid\nCliOptionWithArgs::addEvalFunction(evalFuncCb func, void* userData)\n{\n  if (!mEvalFunctions) {\n    mEvalFunctions = new std::vector<evalFuncCb>;\n    mUserData = new std::vector<void*>;\n  }\n\n  mEvalFunctions->push_back(func);\n  mUserData->push_back(userData);\n}\n\nstd::string\nCliOptionWithArgs::hasKeyword(std::string keyword)\n{\n  if (!mKeywords) {\n    return \"\";\n  }\n\n  std::vector<std::string>::const_iterator it = mKeywords->cbegin();\n\n  for (; it != mKeywords->cend(); it++) {\n    if (keyword.compare(*it) == 0) {\n      return *it;\n    }\n\n    const std::string& kw = *it;\n\n    // If the current keyword ends up in an '=' we check\n    // if its a prefix of the given arguemtn\n    if (kw[kw.length() - 1] == '=' &&\n        keyword.compare(0, kw.length(), kw) == 0) {\n      return *it;\n    }\n  }\n\n  return \"\";\n}\n\nstd::string\nCliOptionWithArgs::repr() const\n{\n  std::string reprStr = mRepr;\n\n  if (reprStr != \"\" && mKeywords) {\n    const std::string& firstKw = mKeywords->front();\n    reprStr = firstKw + (firstKw[firstKw.length() - 1] == '=' ? \"\" : \" \") + reprStr;\n  }\n\n  return reprStr;\n}\n\nAnalysisResult*\nCliOptionWithArgs::commonAnalysis(std::vector<std::string>& cliArgs,\n                                  int initPos, const std::string& firstArg)\n{\n  AnalysisResult* res;\n  std::vector<std::string> optionArgs;\n  int numArgs = mNumArgs == -1 ? max((int) cliArgs.size(), 1) : mNumArgs;\n\n  if (firstArg != \"\") {\n    optionArgs.insert(optionArgs.begin(), firstArg);\n    numArgs--;\n  }\n\n  if (initPos + numArgs <= (int) cliArgs.size())\n    optionArgs.insert(optionArgs.end(), cliArgs.cbegin() + initPos,\n                      cliArgs.cbegin() + initPos + numArgs);\n\n  if (numArgs > (int) optionArgs.size()) {\n    if (!mRequired) {\n      return NULL;\n    }\n\n    res = new AnalysisResult;\n    res->values.first = mName;\n    res->start = cliArgs.begin() + initPos;\n\n    if (optionArgs.size() == 0) {\n      res->end = res->start;\n      res->errorMsg = \"Error: Please specify \" + repr() + \".\";\n    } else {\n      res->end = res->start + min((int) optionArgs.size(), numArgs);\n      res->errorMsg = \"Error: Too few arguments for \" + repr() + \".\";\n    }\n\n    return res;\n  }\n\n  res = new AnalysisResult;\n  res->values.first = mName;\n  res->start = cliArgs.begin() + initPos;\n  res->errorMsg = \"\";\n  std::string* evalErrorMsg = 0;\n\n  if (shouldEvaluate()) {\n    for (size_t f = 0; f < mEvalFunctions->size(); f++) {\n      if (!mEvalFunctions->at(f)(this, optionArgs, &evalErrorMsg, mUserData->at(f))) {\n        goto bailout;\n      }\n    }\n  }\n\n  int i;\n\n  for (i = 0; i < (int) optionArgs.size(); i++) {\n    res->values.second.push_back(optionArgs.at(i));\n  }\n\nbailout:\n  res->end = res->start + numArgs;\n\n  if (evalErrorMsg) {\n    res->errorMsg = std::string(evalErrorMsg->c_str());\n  }\n\n  delete evalErrorMsg;\n  return res;\n}\n\nAnalysisResult*\nCliOptionWithArgs::analyse(std::vector<std::string>& cliArgs)\n{\n  AnalysisResult* res;\n  size_t initPos;\n  std::string firstArg(\"\");\n\n  for (initPos = 0; initPos < cliArgs.size(); initPos++) {\n    const std::string& foundKw = hasKeyword(cliArgs[initPos]);\n\n    if (foundKw != \"\") {\n      if (foundKw[foundKw.length() - 1] == '=') {\n        firstArg = cliArgs[initPos].substr(foundKw.length());\n      }\n\n      break;\n    }\n  }\n\n  initPos++;\n  res = commonAnalysis(cliArgs, initPos, firstArg);\n\n  // we decrease the start of our options because we need to include\n  // the keyword used.\n  if (res && initPos <=  cliArgs.size()) {\n    res->start = res->start - 1;\n  }\n\n  return res;\n}\n\nCliPositionalOption::CliPositionalOption(std::string name, std::string desc,\n    int position, int numArgs, std::string repr)\n  : CliOptionWithArgs(name, desc, \"\", numArgs, repr, false),\n    mPosition(position)\n{\n  assert(mPosition > 0 || mPosition == -1);\n}\n\nCliPositionalOption::CliPositionalOption(std::string name, std::string desc,\n    int position, int numArgs,\n    std::string repr, bool required)\n  : CliOptionWithArgs(name, desc, \"\", numArgs, repr, required),\n    mPosition(position)\n{\n  assert(mPosition > 0 || mPosition == -1);\n}\n\nCliPositionalOption::CliPositionalOption(std::string name, std::string desc,\n    int position, std::string repr)\n  : CliOptionWithArgs(name, desc, \"\", 1, repr, false),\n    mPosition(position)\n{\n  assert(mPosition > 0 || mPosition == -1);\n}\n\nCliPositionalOption::CliPositionalOption(const CliPositionalOption& option)\n  : CliOptionWithArgs(option.name(), option.description(), \"\",\n                      option.mNumArgs, option.mRepr, option.mRequired),\n    mPosition(option.mPosition)\n{\n  if (option.mEvalFunctions) {\n    mEvalFunctions = new std::vector<evalFuncCb>(*option.mEvalFunctions);\n  }\n\n  if (option.mUserData) {\n    mUserData = new std::vector<void*>(*option.mUserData);\n  }\n}\n\nCliPositionalOption::~CliPositionalOption() {};\n\nchar*\nCliPositionalOption::helpString()\n{\n  if (mDescription == \"\") {\n    return NULL;\n  }\n\n  char* helpStr;\n  std::string repr = mRepr;\n  int strLength = repr.length() + HELP_PADDING + 10;\n  helpStr = new char[strLength];\n  sprintf(helpStr, \"%*s\\t- \", HELP_PADDING, repr.c_str());\n  std::string help(helpStr);\n  help += truncateDescString(mDescription, \"\\t  \");\n  delete[] helpStr;\n  return strdup(help.c_str());\n}\n\nAnalysisResult*\nCliPositionalOption::analyse(std::vector<std::string>& cliArgs)\n{\n  return commonAnalysis(cliArgs, mPosition - 1, \"\");\n}\n\nstd::string\nCliPositionalOption::repr() const\n{\n  return mRepr;\n}\n\nvoid\nConsoleCliCommand::init(const std::string& name,\n                        const std::string& description)\n{\n  mName = name;\n  mDescription = description;\n  mSubcommands = 0;\n  mMainGroup = 0;\n  mPositionalOptions = 0;\n  mParentCommand = 0;\n  mErrors = 0;\n  mGroups = 0;\n  mStandalone = true;\n}\n\nConsoleCliCommand::ConsoleCliCommand(const std::string& name,\n                                     const std::string& description)\n{\n  init(name, description);\n}\n\nConsoleCliCommand::ConsoleCliCommand(const ConsoleCliCommand& otherCmd)\n{\n  init(otherCmd.mName, otherCmd.mDescription);\n\n  if (otherCmd.mMainGroup) {\n    mMainGroup = new OptionsGroup(*otherCmd.mMainGroup);\n  }\n\n  if (otherCmd.mPositionalOptions) {\n    std::map<int, CliPositionalOption*>::const_iterator it;\n\n    for (it = otherCmd.mPositionalOptions->cbegin();\n         it != otherCmd.mPositionalOptions->cend(); it++) {\n      addOption(*(*it).second);\n    }\n  }\n\n  if (otherCmd.mGroups) {\n    for (size_t i = 0; i < otherCmd.mGroups->size(); i++) {\n      OptionsGroup* newGroup = new OptionsGroup(*otherCmd.mGroups->at(i));\n      addGroup(newGroup);\n    }\n  }\n\n  if (otherCmd.mSubcommands) {\n    mSubcommands = new std::vector<ConsoleCliCommand*>(*otherCmd.mSubcommands);\n  }\n\n  if (otherCmd.mParentCommand) {\n    const_cast<ConsoleCliCommand*>(otherCmd.mParentCommand)->addSubcommand(this);\n  }\n}\n\nConsoleCliCommand::~ConsoleCliCommand()\n{\n  size_t i;\n  delete mMainGroup;\n  mMainGroup = 0;\n\n  if (mGroups) {\n    for (i = 0; i < mGroups->size(); i++) {\n      delete mGroups->at(i);\n    }\n\n    delete mGroups;\n    mGroups = 0;\n  }\n\n  if (mPositionalOptions) {\n    std::map<int, CliPositionalOption*>::const_iterator it;\n\n    for (it = mPositionalOptions->cbegin(); it != mPositionalOptions->cend();\n         it++) {\n      delete(*it).second;\n    }\n\n    delete mPositionalOptions;\n    mPositionalOptions = 0;\n  }\n\n  if (mSubcommands) {\n    for (i = 0; i < mSubcommands->size(); i++) {\n      delete mSubcommands->at(i);\n    }\n\n    delete mSubcommands;\n    mSubcommands = 0;\n  }\n\n  clean();\n  mErrors = 0;\n}\n\nvoid\nConsoleCliCommand::clean()\n{\n  if (mErrors) {\n    for (size_t i = 0; i < mErrors->size(); i++) {\n      delete mErrors->at(i);\n    }\n\n    mErrors->clear();\n  }\n\n  mOptionsMap.clear();\n}\n\nvoid\nConsoleCliCommand::addOption(CliOption* option)\n{\n  if (mMainGroup == 0) {\n    mMainGroup = new OptionsGroup;\n  }\n\n  mMainGroup->addOption(option);\n}\n\nvoid\nConsoleCliCommand::addOption(CliPositionalOption* option)\n{\n  if (mPositionalOptions == 0) {\n    mPositionalOptions = new std::map<int, CliPositionalOption*>;\n  }\n\n  int pos = option->position();\n\n  if (mPositionalOptions->count(pos) != 0) {\n    delete mPositionalOptions->at(pos);\n    mPositionalOptions->erase(pos);\n  }\n\n  mPositionalOptions->insert({pos, option});\n}\n\nvoid\nConsoleCliCommand::addSubcommand(ConsoleCliCommand* subcommand)\n{\n  assert(subcommand != this);\n\n  if (mSubcommands == 0) {\n    mSubcommands = new std::vector<ConsoleCliCommand*>;\n    mStandalone = false;\n  }\n\n  if (std::find(mSubcommands->begin(), mSubcommands->end(),\n                subcommand) == mSubcommands->end()) {\n    mSubcommands->push_back(subcommand);\n  }\n\n  subcommand->setParent(this);\n}\n\nvoid\nConsoleCliCommand::addOption(const CliOption& option)\n{\n  CliOption* newObj = new CliOption(option);\n  addOption(newObj);\n}\n\nvoid\nConsoleCliCommand::addOption(const CliOptionWithArgs& option)\n{\n  CliOptionWithArgs* newObj = new CliOptionWithArgs(option);\n  addOption(newObj);\n}\n\nvoid\nConsoleCliCommand::addOption(const CliPositionalOption& option)\n{\n  CliPositionalOption* newObj = new CliPositionalOption(option);\n  addOption(newObj);\n}\n\nvoid\nConsoleCliCommand::addOptions(std::vector<CliOption> options)\n{\n  std::vector<CliOption>::const_iterator it = options.cbegin();\n\n  for (; it != options.cend(); it++) {\n    addOption(*it);\n  }\n}\n\nvoid\nConsoleCliCommand::addOptions(std::vector<CliOptionWithArgs> options)\n{\n  std::vector<CliOptionWithArgs>::const_iterator it = options.cbegin();\n\n  for (; it != options.cend(); it++) {\n    addOption(*it);\n  }\n}\n\nvoid\nConsoleCliCommand::addOptions(std::vector<CliPositionalOption> options)\n{\n  std::vector<CliPositionalOption>::const_iterator it = options.cbegin();\n\n  for (; it != options.cend(); it++) {\n    addOption(*it);\n  }\n}\n\nCliOption*\nConsoleCliCommand::getOption(const std::string& name) const\n{\n  CliOption* ret = 0;\n\n  if (mMainGroup) {\n    ret = mMainGroup->getOption(name);\n  }\n\n  if (!ret && mPositionalOptions) {\n    std::map<int, CliPositionalOption*>::iterator pos_it =\n      mPositionalOptions->begin();\n\n    for (; pos_it != mPositionalOptions->end(); pos_it++) {\n      if ((*pos_it).second->name() == name) {\n        return (*pos_it).second;\n      }\n    }\n  }\n\n  std::vector<OptionsGroup*>::const_iterator it;\n\n  for (it = mGroups->cbegin(); !ret && it != mGroups->cend(); it++) {\n    ret = (*it)->getOption(name);\n  }\n\n  return ret;\n}\n\nOptionsGroup*\nConsoleCliCommand::addGroupedOptions(std::vector<CliOption> options)\n{\n  if (options.size() == 0) {\n    return 0;\n  }\n\n  OptionsGroup* group = new OptionsGroup;\n  addGroup(group);\n  group->addOptions(options);\n  return group;\n}\n\nOptionsGroup*\nConsoleCliCommand::addGroupedOptions(std::vector<CliOptionWithArgs> options)\n{\n  if (options.size() == 0) {\n    return 0;\n  }\n\n  OptionsGroup* group = new OptionsGroup;\n  addGroup(group);\n  group->addOptions(options);\n  return group;\n}\n\nvoid\nConsoleCliCommand::addGroup(OptionsGroup* group)\n{\n  if (!mGroups) {\n    mGroups = new std::vector<OptionsGroup*>;\n  }\n\n  if (std::find(mGroups->begin(), mGroups->end(), group) == mGroups->end()) {\n    mGroups->push_back(group);\n  }\n}\n\nvoid\nConsoleCliCommand::addError(const ParseError* error)\n{\n  if (!mErrors) {\n    mErrors = new std::vector<const ParseError*>;\n  }\n\n  mErrors->push_back(error);\n}\n\nbool\nConsoleCliCommand::hasErrors()\n{\n  return mErrors && !mErrors->empty();\n}\n\nConsoleCliCommand*\nConsoleCliCommand::isSubcommand(std::vector<std::string>& cliArgs)\n{\n  if (cliArgs.size() == 0) {\n    return 0;\n  }\n\n  std::vector<ConsoleCliCommand*>::const_iterator it = mSubcommands->cbegin();\n\n  for (; it != mSubcommands->cend(); it++) {\n    if ((*it)->name().compare(cliArgs[0]) == 0) {\n      return *it;\n    }\n  }\n\n  return 0;\n}\n\nvoid\nConsoleCliCommand::analyseGroup(OptionsGroup* group,\n                                std::vector<std::string>& cliArgs)\n{\n  std::vector<CliOption*>* options = group->options();\n\n  if (options) {\n    bool optionFound = false;\n    std::vector<CliOption*>::iterator it = options->begin();\n\n    for (; it != options->end(); it++) {\n      AnalysisResult* res = (*it)->analyse(cliArgs);\n\n      if (!res) {\n        continue;\n      }\n\n      if (res->values.first != \"\") {\n        if (optionFound && group != mMainGroup) {\n          addError(new ParseError(0,\n                                  \"Error: Use only one option: \" + group->optionsRepr()));\n          delete res;\n          return;\n        }\n\n        if (res->errorMsg == \"\") {\n          mOptionsMap.insert(res->values);\n        } else {\n          addError(new ParseError(*it, res->errorMsg));\n        }\n\n        cliArgs.erase(res->start, res->end);\n        optionFound = true;\n      }\n\n      delete res;\n    }\n\n    if (!optionFound && group->required())\n      addError(new ParseError(0, \"Error: You have to use at least one \"\n                              \"of these options: \" + group->optionsRepr()));\n  }\n}\n\nConsoleCliCommand*\nConsoleCliCommand::parse(std::vector<std::string>& cliArgs)\n{\n  clean();\n\n  if (mSubcommands) {\n    ConsoleCliCommand* subcommand = isSubcommand(cliArgs);\n\n    if (subcommand) {\n      std::vector<std::string> subcommandArgs(cliArgs);\n      subcommandArgs.erase(subcommandArgs.begin());\n      return subcommand->parse(subcommandArgs);\n    }\n  }\n\n  if (mMainGroup) {\n    analyseGroup(mMainGroup, cliArgs);\n  }\n\n  if (mGroups) {\n    std::vector<OptionsGroup*>::const_iterator it;\n\n    for (it = mGroups->cbegin(); it != mGroups->cend(); it++) {\n      analyseGroup(*it, cliArgs);\n    }\n  }\n\n  int numArgsProcessed = (int) cliArgs.size();\n\n  if (mPositionalOptions) {\n    std::map<int, CliPositionalOption*>::iterator pos_it =\n      mPositionalOptions->begin();\n\n    for (; pos_it != mPositionalOptions->end(); pos_it++) {\n      AnalysisResult* res = (*pos_it).second->analyse(cliArgs);\n\n      if (!res) { // not required and not found\n        continue;\n      }\n\n      if (res->errorMsg == \"\") {\n        mOptionsMap.insert(res->values);\n      } else {\n        addError(new ParseError((*pos_it).second, res->errorMsg));\n      }\n\n      numArgsProcessed -= (res->end - res->start);\n      delete res;\n    }\n  }\n\n  if (numArgsProcessed > 0) {\n    addError(new ParseError(0, \"Error: Unknown arguments found.\"));\n  }\n\n  return this;\n}\n\nConsoleCliCommand*\nConsoleCliCommand::parse(const std::string& cliArgs)\n{\n  std::vector<std::string>* cliArgsVector = splitKeywords(cliArgs, ' ');\n  ConsoleCliCommand* cmd = parse(*cliArgsVector);\n  delete cliArgsVector;\n  return cmd;\n}\n\nbool\nConsoleCliCommand::hasValue(std::string optionName)\n{\n  return mOptionsMap.count(optionName);\n}\n\nbool\nConsoleCliCommand::hasValues()\n{\n  return !mOptionsMap.empty();\n}\n\nstd::string\nConsoleCliCommand::getValue(std::string optionName)\n{\n  return mOptionsMap[optionName][0];\n}\n\nstd::vector<std::string>\nConsoleCliCommand::getValues(std::string optionName)\n{\n  return mOptionsMap[optionName];\n}\n\nvoid\nConsoleCliCommand::printHelpForOptions(std::vector<CliOption*>* options) const\n{\n  std::vector<CliOption*>::const_iterator it;\n\n  for (it = options->cbegin(); it != options->cend(); it++) {\n    char* str = (*it)->helpString();\n\n    if (str != NULL) {\n      fprintf(stdout, \"%s\", str);\n      free(str);\n    }\n  }\n}\n\nvoid\nConsoleCliCommand::printHelp() const\n{\n  if (mMainGroup) {\n    printHelpForOptions(mMainGroup->options());\n  }\n\n  if (mGroups) {\n    for (size_t i = 0; i < mGroups->size(); i++) {\n      printHelpForOptions(mGroups->at(i)->options());\n    }\n  }\n\n  if (mPositionalOptions) {\n    std::map<int, CliPositionalOption*>::const_iterator it;\n\n    for (it = mPositionalOptions->cbegin(); it != mPositionalOptions->cend();\n         it++) {\n      char* str = (*it).second->helpString();\n\n      if (str != NULL) {\n        fprintf(stdout, \"%s\", str);\n        free(str);\n      }\n    }\n  }\n}\n\nvoid\nConsoleCliCommand::printUsage() const\n{\n  std::string subcommRepr = subcommandsRepr();\n  std::string kwRepr = keywordsRepr();\n  std::string posOptionsRepr = positionalOptionsRepr();\n  std::string commandAndOptions = \"\";\n  std::string fullCommandName = mName;\n  const ConsoleCliCommand* parent;\n\n  if (subcommRepr != \"\") {\n    commandAndOptions += \" \" + subcommRepr;\n  }\n\n  if (kwRepr != \"\") {\n    commandAndOptions += \" \" + kwRepr;\n  }\n\n  if (mGroups) {\n    std::vector<OptionsGroup*>::const_iterator it;\n\n    for (it = mGroups->cbegin(); it != mGroups->cend(); it++) {\n      std::string groupRepr((*it)->name());\n\n      if (groupRepr == \"\") {\n        groupRepr = (*it)->optionsRepr();\n      }\n\n      commandAndOptions += \" \" + ((*it)->required() ? groupRepr : \"[\" + groupRepr +\n                                  \"]\");\n    }\n  }\n\n  if (posOptionsRepr != \"\") {\n    commandAndOptions += \" \" + posOptionsRepr;\n  }\n\n  for (parent = mParentCommand; parent; parent = parent->parent()) {\n    fullCommandName = parent->name() + \" \" + fullCommandName;\n  }\n\n  commandAndOptions = fullCommandName + commandAndOptions;\n  fprintf(stdout, \"Usage: %s\", commandAndOptions.c_str());\n\n  if (mDescription != \"\") {\n    fprintf(stdout, \" : %s\", mDescription.c_str());\n  }\n\n  fprintf(stdout, \"\\n\");\n  printHelp();\n}\n\nvoid\nConsoleCliCommand::printErrors() const\n{\n  if (!mErrors) {\n    return;\n  }\n\n  std::string errorsStr(\"\");\n  std::vector<const ParseError*>::const_iterator it;\n\n  for (it = mErrors->cbegin(); it != mErrors->cend(); it++) {\n    errorsStr += (*it)->message() + \"\\n\";\n  }\n\n  fprintf(stdout, \"%s\", errorsStr.c_str());\n}\n\nvoid\nConsoleCliCommand::setParent(const ConsoleCliCommand* parent)\n{\n  mParentCommand = parent;\n}\n\nstd::string\nConsoleCliCommand::keywordsRepr() const\n{\n  std::vector<CliOption*>* options;\n  std::string repr(\"\");\n\n  if (!mMainGroup) {\n    return repr;\n  }\n\n  options = mMainGroup->options();\n\n  if (!options) {\n    return repr;\n  }\n\n  for (size_t i = 0; i < options->size(); i++) {\n    if (options->at(i)->hidden()) {\n      continue;\n    }\n\n    std::string optionRepr = options->at(i)->repr();\n\n    if (!options->at(i)->required()) {\n      optionRepr = \"[\" + optionRepr + \"]\";\n    }\n\n    if (repr != \"\") {\n      repr += \" \";\n    }\n\n    repr += optionRepr;\n  }\n\n  return repr;\n}\n\nstd::string\nConsoleCliCommand::subcommandsRepr() const\n{\n  std::string repr(\"\");\n\n  if (!mSubcommands) {\n    return repr;\n  }\n\n  for (size_t i = 0; i < mSubcommands->size(); i++) {\n    repr += mSubcommands->at(i)->name();\n\n    if (i < mSubcommands->size() - 1) {\n      repr += \"|\";\n    }\n  }\n\n  if (mStandalone) {\n    repr = \"[\" + repr + \"]\";\n  }\n\n  return repr;\n}\n\nstd::string\nConsoleCliCommand::positionalOptionsRepr() const\n{\n  std::string repr(\"\");\n\n  if (!mPositionalOptions) {\n    return repr;\n  }\n\n  std::map<int, CliPositionalOption*>::const_iterator it;\n\n  for (it = mPositionalOptions->cbegin(); it != mPositionalOptions->cend();\n       it++) {\n    const std::string& currentRepr = (*it).second->repr();\n    repr += (*it).second->required() ? currentRepr : \"[\" + currentRepr + \"]\";\n\n    if (it == --mPositionalOptions->cend()) {\n      break;\n    }\n\n    repr += \" \";\n  }\n\n  return repr;\n}\n\nOptionsGroup::OptionsGroup(std::string name)\n  : mName(name),\n    mOptions(0),\n    mRequired(false)\n{}\n\nOptionsGroup::OptionsGroup()\n  : mName(\"\"),\n    mOptions(0),\n    mRequired(false)\n{\n}\n\nOptionsGroup::OptionsGroup(const OptionsGroup& otherGroup)\n  : mName(otherGroup.mName),\n    mOptions(0),\n    mRequired(otherGroup.mRequired)\n{\n  if (otherGroup.mOptions) {\n    for (size_t i = 0; i < otherGroup.mOptions->size(); i++) {\n      CliPositionalOption* positionalOption =\n        dynamic_cast<CliPositionalOption*>(otherGroup.mOptions->at(i));\n\n      if (positionalOption) {\n        addOption(*positionalOption);\n        continue;\n      }\n\n      CliOptionWithArgs* optionWithArgs =\n        dynamic_cast<CliOptionWithArgs*>(otherGroup.mOptions->at(i));\n\n      if (optionWithArgs) {\n        addOption(*optionWithArgs);\n        continue;\n      }\n\n      addOption(*otherGroup.mOptions->at(i));\n    }\n  }\n}\n\nOptionsGroup::~OptionsGroup()\n{\n  if (mOptions) {\n    size_t i;\n\n    for (i = 0; i < mOptions->size(); i++) {\n      delete mOptions->at(i);\n    }\n\n    delete mOptions;\n    mOptions = 0;\n  }\n}\n\nvoid\nOptionsGroup::addOption(CliOption* option)\n{\n  if (!mOptions) {\n    mOptions = new std::vector<CliOption*>;\n  }\n\n  if (option->group() != this) {\n    option->setGroup(this);\n    mOptions->push_back(option);\n  }\n}\n\nvoid\nOptionsGroup::addOption(const CliOption& option)\n{\n  CliOption* optionPtr = new CliOption(option);\n  optionPtr->setRequired(false);\n  addOption(optionPtr);\n}\n\nvoid\nOptionsGroup::addOption(const CliOptionWithArgs& option)\n{\n  CliOptionWithArgs* optionPtr = new CliOptionWithArgs(option);\n  optionPtr->setRequired(false);\n  addOption(optionPtr);\n}\n\nvoid\nOptionsGroup::addOptions(std::vector<CliOption> options)\n{\n  std::vector<CliOption>::const_iterator it = options.cbegin();\n\n  for (; it != options.cend(); it++) {\n    addOption(*it);\n  }\n}\n\nvoid\nOptionsGroup::addOptions(std::vector<CliOptionWithArgs> options)\n{\n  std::vector<CliOptionWithArgs>::const_iterator it = options.cbegin();\n\n  for (; it != options.cend(); it++) {\n    addOption(*it);\n  }\n}\n\nCliOption*\nOptionsGroup::getOption(const std::string& name) const\n{\n  std::vector<CliOption*>::iterator it;\n\n  for (it = mOptions->begin(); it != mOptions->end(); it++) {\n    if ((*it)->name() == name) {\n      return *it;\n    }\n  }\n\n  return 0;\n}\n\nvoid\nOptionsGroup::removeOption(CliOption* option)\n{\n  option->setGroup(0);\n\n  if (!mOptions) {\n    return;\n  }\n\n  std::vector<CliOption*>::iterator it;\n\n  for (it = mOptions->begin(); it != mOptions->end(); it++) {\n    if (*it == option) {\n      mOptions->erase(it);\n      return;\n    }\n  }\n}\n\nstd::string\nOptionsGroup::optionsRepr()\n{\n  std::string repr = \"\";\n\n  if (!mOptions) {\n    return repr;\n  }\n\n  size_t i;\n  std::vector<const CliOption*>::iterator it;\n\n  for (i = 0; i < mOptions->size(); i++) {\n    if (mOptions->at(i)->hidden()) {\n      continue;\n    }\n\n    repr += mOptions->at(i)->repr();\n\n    if (i != mOptions->size() - 1 &&\n        !mOptions->at(i + 1)->hidden()) {\n      repr += \"|\";\n    }\n  }\n\n  return repr;\n}\n"
  },
  {
    "path": "console/ConsoleCliCommand.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ConsoleCliCommand.hh\n// Author: Joaquim Rocha - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef CONSOLE_CLI_COMMAND_HH\n#define CONSOLE_CLI_COMMAND_HH\n\n#include <string>\n#include <vector>\n#include <utility>\n#include <map>\n\n#define optionIsFloatEvalFunc ((evalFuncCb) isFloatEvalFunc)\n#define optionIsIntegerEvalFunc ((evalFuncCb) isIntegerEvalFunc)\n#define optionIsNumberInRangeEvalFunc ((evalFuncCb) isNumberInRangeEvalFunc)\n#define optionIsChoiceEvalFunc ((evalFuncCb) isChoiceEvalFunc)\n#define optionIsPositiveNumberEvalFunc ((evalFuncCb) isPositiveNumberEvalFunc)\n#define optionIsNegativeNumberEvalFunc ((evalFuncCb) isNegativeNumberEvalFunc)\n\nclass CliBaseOption;\nclass CliOptionWithArgs;\nclass ConsoleCliCommand;\nclass OptionsGroup;\n\ntypedef bool (*evalFuncCb) (const CliOptionWithArgs *option,\n                            std::vector<std::string> &args,\n                            std::string **error,\n                            void *userData);\n\nbool isFloatEvalFunc (const CliOptionWithArgs *option,\n                      std::vector<std::string> &args,\n                      std::string **error,\n                      void *userData);\n\nbool isIntegerEvalFunc (const CliOptionWithArgs *option,\n                        std::vector<std::string> &args,\n                        std::string **error,\n                        void *userData);\n\nbool isNumberInRangeEvalFunc (const CliOptionWithArgs *option,\n                              std::vector<std::string> &args,\n                              std::string **error,\n                              const std::pair<float, float> *range);\n\nbool isPositiveNumberEvalFunc (const CliOptionWithArgs *option,\n                               std::vector<std::string> &args,\n                               std::string **error,\n                               void *data);\n\nbool isNegativeNumberEvalFunc (const CliOptionWithArgs *option,\n                               std::vector<std::string> &args,\n                               std::string **error,\n                               void *data);\n\nbool isChoiceEvalFunc (const CliOptionWithArgs *option,\n                       std::vector<std::string> &args,\n                       std::string **error,\n                       const std::vector<std::string> *choices);\n\nclass ParseError {\npublic:\n  ParseError(CliBaseOption *option, std::string message);\n  CliBaseOption* option() const { return mOption; };\n  std::string message() const { return mMessage; };\n\nprivate:\n  CliBaseOption *mOption;\n  std::string mMessage;\n};\n\ntypedef struct\n{\n  std::pair<std::string, std::vector<std::string>> values;\n  std::vector<std::string>::iterator start;\n  std::vector<std::string>::iterator end;\n  std::string errorMsg;\n} AnalysisResult;\n\nclass CliBaseOption {\npublic:\n  CliBaseOption(std::string name = \"\", std::string desc = \"\");\n  virtual ~CliBaseOption();\n  virtual AnalysisResult* analyse(std::vector<std::string> &cliArgs) = 0;\n  virtual char* helpString() { return strdup(\"\"); };\n  virtual char* keywordsRepr() { return strdup(\"\"); };\n  virtual const char* name() const { return mName.c_str(); };\n  virtual const char* description() const { return mDescription.c_str(); };\n  virtual void setName(const std::string &name) { mName = name; };\n  virtual void setDescription(const std::string &desc) { mDescription = desc; };\n  virtual bool required() const { return mRequired; };\n  virtual void setRequired(bool req) { mRequired = req; };\n  virtual bool hidden() const { return mHidden; };\n  virtual void setHidden(bool hidden) { mHidden = hidden; };\n  virtual std::string repr() const = 0;\n\nprotected:\n  std::string mName;\n  std::string mDescription;\n  bool mRequired;\n  bool mHidden;\n};\n\nclass CliOption : public CliBaseOption {\npublic:\n  CliOption(std::string name, std::string desc, std::string keywords);\n  CliOption();\n  CliOption(const CliOption &option);\n  virtual ~CliOption();\n  virtual AnalysisResult* analyse(std::vector<std::string> &cliArgs);\n  virtual char* helpString();\n  virtual char* keywordsRepr();\n  virtual std::string repr() const;\n  virtual void setGroup(OptionsGroup *group);\n  virtual const OptionsGroup *group() const { return mGroup; };\n\nprotected:\n  std::vector<std::string> *mKeywords;\n  OptionsGroup *mGroup;\n\n  virtual std::string hasKeyword(std::string keyword);\n  virtual std::string joinKeywords();\n};\n\nclass CliOptionWithArgs : public CliOption{\npublic:\n  CliOptionWithArgs(std::string name, std::string desc,\n                    std::string keywords, int numArgs,\n                    std::string repr, bool required);\n  CliOptionWithArgs(std::string name, std::string desc,\n                    std::string keywords, std::string repr,\n                    bool required);\n  CliOptionWithArgs();\n  CliOptionWithArgs(const CliOptionWithArgs &otherOption);\n  virtual ~CliOptionWithArgs();\n  virtual AnalysisResult* analyse(std::vector<std::string> &cliArgs);\n  virtual std::string repr() const;\n  virtual char* helpString();\n  bool shouldEvaluate() const { return mEvalFunctions && mUserData; };\n  void addEvalFunction(evalFuncCb func, void *userData);\n\nprotected:\n  virtual std::string hasKeyword(std::string keyword);\n  std::string mRepr;\n  int mNumArgs;\n  std::vector<evalFuncCb> *mEvalFunctions;\n  std::vector<void *> *mUserData;\n\n  AnalysisResult* commonAnalysis(std::vector<std::string> &cliArgs,\n                                 int initPos,\n                                 const std::string &firstArg);\n\nprivate:\n  void init(int numArgs, std::string repr, bool required);\n};\n\nclass CliPositionalOption : public CliOptionWithArgs {\npublic:\n  CliPositionalOption(std::string name, std::string desc, int position,\n                      int numArgs, std::string repr);\n  CliPositionalOption(std::string name, std::string desc, int position,\n                      int numArgs, std::string repr, bool required);\n  CliPositionalOption(std::string name, std::string desc, int position,\n                      std::string repr);\n  CliPositionalOption(const CliPositionalOption &option);\n  virtual ~CliPositionalOption();\n  virtual AnalysisResult* analyse(std::vector<std::string> &cliArgs);\n  virtual char* helpString();\n  virtual std::string repr() const;\n  int position() { return mPosition; };\n  void setPosition(int position) { mPosition = position; }\n\nprivate:\n  int mPosition;\n};\n\nclass OptionsGroup {\npublic:\n  OptionsGroup();\n  OptionsGroup(std::string name);\n  OptionsGroup(const OptionsGroup &otherGroup);\n  virtual ~OptionsGroup();\n  void addOption(CliOption *option);\n  void addOption(const CliOption &option);\n  void addOption(const CliOptionWithArgs &option);\n  void addOptions(std::vector<CliOption> options);\n  void addOptions(std::vector<CliOptionWithArgs> options);\n  CliOption *getOption(const std::string &name) const;\n  void removeOption(CliOption *option);\n  std::vector<CliOption *>* options() { return mOptions; };\n  bool required() const { return mRequired; };\n  void setRequired(bool req) { mRequired = req; };\n  std::string optionsRepr();\n  std::string name() const { return mName; };\n  void setName(std::string name) { mName = name; };\n\nprivate:\n  std::string mName;\n  std::vector<CliOption *> *mOptions;\n  bool mRequired;\n};\n\nclass ConsoleCliCommand {\npublic:\n  ConsoleCliCommand (const std::string &name, const std::string &description);\n  ConsoleCliCommand (const ConsoleCliCommand &otherCmd);\n  ~ConsoleCliCommand ();\n  void addSubcommand(ConsoleCliCommand *subcommand);\n  void addOption(CliOption *option);\n  void addOption(CliPositionalOption *option);\n  void addOption(const CliPositionalOption &option);\n  void addOption(const CliOption &option);\n  void addOption(const CliOptionWithArgs &option);\n  void addOptions(std::vector<CliOption> options);\n  void addOptions(std::vector<CliPositionalOption> options);\n  void addOptions(std::vector<CliOptionWithArgs> options);\n  void addGroup(OptionsGroup *group);\n  CliOption *getOption(const std::string &name) const;\n  OptionsGroup* addGroupedOptions(std::vector<CliOption> options);\n  OptionsGroup* addGroupedOptions(std::vector<CliOptionWithArgs> options);\n  bool hasErrors();\n  ConsoleCliCommand* parse(std::vector<std::string> &cliArgs);\n  ConsoleCliCommand* parse(const std::string &cliArgs);\n  bool hasValue(std::string optionName);\n  bool hasValues();\n  std::vector<std::string> getValues(std::string optionName);\n  std::string getValue(std::string optionName);\n  void printHelp() const;\n  void printUsage() const;\n  void printErrors() const;\n  std::string name() const { return mName;};\n  std::string description() const { return mDescription; };\n  void setName(const std::string &name) { mName = name;};\n  void setDescription(const std::string &desc) { mDescription = desc; };\n  void setParent(const ConsoleCliCommand *parent);\n  const ConsoleCliCommand *parent() const { return mParentCommand; };\n  std::vector<ConsoleCliCommand *> *subcommands() const { return mSubcommands; };\n  void setStandalone(bool standalone) { mStandalone = standalone; };\n  bool standalone() const { return mStandalone; };\n\nprivate:\n  std::string mName;\n  std::string mDescription;\n  std::vector<ConsoleCliCommand *> *mSubcommands;\n  OptionsGroup *mMainGroup;\n  std::map<int, CliPositionalOption *> *mPositionalOptions;\n  const ConsoleCliCommand *mParentCommand;\n  std::map<std::string, std::vector<std::string>> mOptionsMap;\n  std::vector<const ParseError *> *mErrors;\n  std::vector<OptionsGroup *> *mGroups;\n  /* mStandalone dictates whether this command can be used without any\n   * subcommands; When adding the first subcommand to a command, the latter\n   * will become non-standalone so, if it should be, remember to call\n   * setStandalone(true) on it; adding other subcommand (after the first) will\n   * not change the standalone setting because it means that the user might\n   * have already changed it! */\n  bool mStandalone;\n\n  void analyseGroup(OptionsGroup *group, std::vector<std::string> &cliArgs);\n  ConsoleCliCommand* isSubcommand(std::vector<std::string> &cliArgs);\n  void printHelpForOptions(std::vector<CliOption *>* options) const;\n  std::string keywordsRepr() const;\n  std::string subcommandsRepr() const;\n  std::string positionalOptionsRepr() const;\n  void addError(const ParseError *);\n  void clean();\n  void init(const std::string &name, const std::string &description);\n};\n\n#endif // CONSOLE_CLI_COMMAND_HH\n"
  },
  {
    "path": "console/ConsoleCompletion.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file ConsoleCompletion.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"ConsoleCompletion.hh\"\n#include \"CommandFramework.hh\"\n#include \"ConsoleMain.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/SymKeys.hh\"\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdOuc/XrdOucTokenizer.hh>\n#include <algorithm>\n#include <cctype>\n#include <limits>\n#include <map>\n#include <memory>\n#include <readline/history.h>\n#include <readline/readline.h>\n#include <regex>\n#include <set>\n#include <sstream>\n#include <string.h>\n#include <unordered_set>\n#include <vector>\n\nextern int com_ls(char*);\n\nnamespace {\nstd::vector<std::string> gWordCompletionCandidates;\n\nbool starts_with(const std::string& value, const std::string& prefix);\nstd::vector<std::string> dedupe_and_sort(std::vector<std::string> candidates);\n\nstruct HelpCompletionLeaf {\n  size_t optionActivationPositionalCount = std::numeric_limits<size_t>::max();\n  std::map<size_t, std::set<std::string>> literalsAtPositionalCount;\n  std::set<std::string> options;\n};\n\nusing HelpCompletionSpec = std::map<std::vector<std::string>, HelpCompletionLeaf>;\n\nstd::string\ntrim_copy(const std::string& value)\n{\n  const auto begin = value.find_first_not_of(\" \\t\\r\\n\");\n\n  if (begin == std::string::npos) {\n    return {};\n  }\n\n  const auto end = value.find_last_not_of(\" \\t\\r\\n\");\n  return value.substr(begin, end - begin + 1);\n}\n\nbool\nis_command_word(const std::string& token)\n{\n  if (token.empty()) {\n    return false;\n  }\n\n  for (const char c : token) {\n    const auto uch = static_cast<unsigned char>(c);\n\n    if (!std::isalnum(uch) && c != '-' && c != '_') {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nbool\nallows_path_extension_token(const std::string& token)\n{\n  return !token.empty() && (token[0] == '<' || token[0] == '[' || token[0] == '-' ||\n                            token == \":\" || token.find('|') != std::string::npos);\n}\n\nstd::vector<std::string>\nsplit_words(const std::string& value)\n{\n  std::istringstream iss(value);\n  std::vector<std::string> words;\n  std::string word;\n\n  while (iss >> word) {\n    words.push_back(word);\n  }\n\n  return words;\n}\n\nstd::vector<std::string>\nextract_option_tokens(const std::string& text)\n{\n  static const std::regex kOptionRegex(\n      R\"((--[A-Za-z0-9][A-Za-z0-9-]*|-{1}[A-Za-z0-9][A-Za-z0-9-]*))\");\n  std::vector<std::string> options;\n\n  for (std::sregex_iterator it(text.begin(), text.end(), kOptionRegex), end; it != end;\n       ++it) {\n    if (it->position() > 0) {\n      const char prev = text[it->position() - 1];\n\n      if (std::isalnum(static_cast<unsigned char>(prev)) || prev == '<' || prev == '_' ||\n          prev == '.') {\n        continue;\n      }\n    }\n\n    options.push_back(it->str());\n  }\n\n  return dedupe_and_sort(std::move(options));\n}\n\nstd::string\nstrip_choice_token(std::string token)\n{\n  while (!token.empty() &&\n         (token.front() == '[' || token.front() == '(' || token.front() == '<')) {\n    token.erase(token.begin());\n  }\n\n  while (!token.empty() &&\n         (token.back() == ']' || token.back() == ')' || token.back() == '>' ||\n          token.back() == ',' || token.back() == ':' || token.back() == ';' ||\n          token.back() == '.')) {\n    token.pop_back();\n  }\n\n  return trim_copy(token);\n}\n\nstd::vector<std::string>\nextract_literal_choices(const std::string& token)\n{\n  if (token.find('|') == std::string::npos || token.find(\"--\") != std::string::npos ||\n      token.find('<') != std::string::npos) {\n    return {};\n  }\n\n  std::vector<std::string> choices;\n  std::stringstream ss(token);\n  std::string part;\n\n  while (std::getline(ss, part, '|')) {\n    part = strip_choice_token(part);\n\n    if (!part.empty() && part[0] != '-' && is_command_word(part)) {\n      choices.push_back(part);\n    }\n  }\n\n  return dedupe_and_sort(std::move(choices));\n}\n\nbool\nparse_absolute_help_path(const std::string& commandName, const std::string& line,\n                         std::vector<std::string>& path, std::string& rest)\n{\n  const std::string prefix = commandName + \" \";\n\n  if (!starts_with(line, prefix)) {\n    return false;\n  }\n\n  const auto words = split_words(line.substr(prefix.size()));\n\n  if (words.empty() || !is_command_word(words[0])) {\n    return false;\n  }\n\n  path.clear();\n  path.push_back(words[0]);\n\n  for (size_t i = 1; i < words.size(); ++i) {\n    const auto& token = words[i];\n\n    if (!is_command_word(token)) {\n      break;\n    }\n\n    if (i + 1 >= words.size() || !allows_path_extension_token(words[i + 1])) {\n      break;\n    }\n\n    path.push_back(token);\n  }\n\n  size_t offset = prefix.size();\n\n  for (const auto& segment : path) {\n    offset += segment.size();\n\n    if (offset < line.size() && line[offset] == ' ') {\n      ++offset;\n    }\n  }\n\n  rest = offset < line.size() ? trim_copy(line.substr(offset)) : std::string{};\n  return true;\n}\n\nbool\nparse_rootless_file_help_path(const std::string& commandName, const std::string& rawLine,\n                              const std::string& line, std::vector<std::string>& path,\n                              std::string& rest)\n{\n  if (commandName != \"file\" || rawLine != line || line.empty() ||\n      !std::islower(line[0]) || starts_with(line, \"Usage:\") || starts_with(line, \"'\")) {\n    return false;\n  }\n\n  const auto words = split_words(line);\n\n  if (words.empty() || !is_command_word(words[0])) {\n    return false;\n  }\n\n  path = {words[0]};\n  rest = words.size() > 1 ? trim_copy(line.substr(words[0].size())) : std::string{};\n  return true;\n}\n\nsize_t\nleading_indent(const std::string& line)\n{\n  size_t indent = 0;\n\n  for (const char c : line) {\n    if (c == ' ') {\n      ++indent;\n    } else if (c == '\\t') {\n      indent += 8 - (indent % 8);\n    } else {\n      break;\n    }\n  }\n\n  return indent;\n}\n\nbool\nparse_relative_help_path(const std::vector<std::string>& currentParent,\n                         const std::string& line, std::vector<std::string>& path,\n                         std::string& rest)\n{\n  if (currentParent.empty() || line.empty() || !std::isalpha(line[0]) ||\n      starts_with(line, \"usage:\") || starts_with(line, \"Usage:\") ||\n      starts_with(line, \"SUBCOMMANDS\") || starts_with(line, \"EXAMPLES\") ||\n      starts_with(line, \"TAPE \") || starts_with(line, \"eos \") || starts_with(line, \"#\")) {\n    return false;\n  }\n\n  std::smatch actionMatch;\n  static const std::regex kActionRegex(R\"(^action '([A-Za-z0-9_-]+)')\");\n\n  if (std::regex_search(line, actionMatch, kActionRegex)) {\n    path = currentParent;\n    path.push_back(actionMatch[1].str());\n    rest = trim_copy(line.substr(actionMatch.position() + actionMatch.length()));\n    return true;\n  }\n\n  const auto words = split_words(line);\n\n  if (words.empty() || !is_command_word(words[0]) || words[0] == \"Note\") {\n    return false;\n  }\n\n  path = currentParent;\n  path.push_back(words[0]);\n  rest = words.size() > 1 ? trim_copy(line.substr(words[0].size())) : std::string{};\n  return true;\n}\n\nvoid\nattach_primary_help_line(HelpCompletionLeaf& leaf, const std::string& rest)\n{\n  size_t leadingRequiredPositionals = 0;\n  const auto tokens = split_words(rest);\n\n  for (const auto& token : tokens) {\n    if (token.empty() || token == \":\") {\n      continue;\n    }\n\n    const auto options = extract_option_tokens(token);\n\n    if (!options.empty()) {\n      leaf.options.insert(options.begin(), options.end());\n\n      if (leaf.optionActivationPositionalCount == std::numeric_limits<size_t>::max()) {\n        leaf.optionActivationPositionalCount = leadingRequiredPositionals;\n      }\n\n      continue;\n    }\n\n    if (token[0] == '<' || token.find('<') != std::string::npos) {\n      if (token[0] != '[') {\n        ++leadingRequiredPositionals;\n      }\n      continue;\n    }\n\n    const auto choices = extract_literal_choices(token);\n\n    if (!choices.empty()) {\n      auto& slot = leaf.literalsAtPositionalCount[leadingRequiredPositionals];\n      slot.insert(choices.begin(), choices.end());\n    }\n  }\n}\n\nbool\nattach_help_key_literal(const std::vector<std::string>& path, const std::string& line,\n                        HelpCompletionLeaf& leaf)\n{\n  if (path.empty() || path.back() != \"config\" || line.empty() || line[0] == '-') {\n    return false;\n  }\n\n  const auto eq = line.find('=');\n\n  if (eq == std::string::npos) {\n    return false;\n  }\n\n  std::string key = trim_copy(line.substr(0, eq + 1));\n\n  if (key.empty() || !std::isalpha(static_cast<unsigned char>(key[0]))) {\n    return false;\n  }\n\n  leaf.literalsAtPositionalCount[1].insert(key);\n\n  const auto options = extract_option_tokens(line);\n  leaf.options.insert(options.begin(), options.end());\n  return true;\n}\n\nbool\nis_non_completion_help_line(const std::string& line)\n{\n  return starts_with(line, \"eos \") || starts_with(line, \"#\");\n}\n\nHelpCompletionSpec\nparse_help_completion_spec(const std::string& commandName, const std::string& helpText)\n{\n  HelpCompletionSpec spec;\n  std::istringstream lines(helpText);\n  std::string line;\n  std::vector<std::string> currentLeaf;\n  std::map<size_t, std::vector<std::string>> parentByIndent;\n\n  while (std::getline(lines, line)) {\n    const size_t indent = leading_indent(line);\n    const auto trimmed = trim_copy(line);\n\n    if (trimmed.empty()) {\n      continue;\n    }\n\n    std::vector<std::string> path;\n    std::string rest;\n    const bool matchedAbsolute =\n        parse_absolute_help_path(commandName, trimmed, path, rest);\n    const bool matchedRootless =\n        !matchedAbsolute &&\n        parse_rootless_file_help_path(commandName, line, trimmed, path, rest);\n    std::vector<std::string> relativeParent;\n    auto parentIt = parentByIndent.lower_bound(indent);\n\n    if (parentIt != parentByIndent.begin()) {\n      --parentIt;\n      relativeParent = parentIt->second;\n    }\n\n    const bool matchedRelative =\n        !matchedAbsolute && !matchedRootless &&\n        parse_relative_help_path(relativeParent, trimmed, path, rest);\n\n    if (matchedAbsolute || matchedRootless || matchedRelative) {\n      auto& leaf = spec[path];\n      attach_primary_help_line(leaf, rest);\n      currentLeaf = path;\n      parentByIndent.erase(parentByIndent.lower_bound(indent), parentByIndent.end());\n\n      if (trimmed.find(\"[subcommand]\") != std::string::npos ||\n          trimmed.find(\"[action]\") != std::string::npos) {\n        parentByIndent[indent] = path;\n      }\n\n      continue;\n    }\n\n    if (currentLeaf.empty()) {\n      continue;\n    }\n\n    if (is_non_completion_help_line(trimmed)) {\n      continue;\n    }\n\n    auto& leaf = spec[currentLeaf];\n\n    if (attach_help_key_literal(currentLeaf, trimmed, leaf)) {\n      continue;\n    }\n\n    const auto options = extract_option_tokens(trimmed);\n\n    if (!options.empty()) {\n      leaf.options.insert(options.begin(), options.end());\n\n      if (leaf.optionActivationPositionalCount == std::numeric_limits<size_t>::max()) {\n        leaf.optionActivationPositionalCount = 0;\n      }\n    }\n  }\n\n  return spec;\n}\n\nconst HelpCompletionSpec&\ncached_help_completion_spec(const std::string& commandName, const std::string& helpText)\n{\n  static std::map<std::string, HelpCompletionSpec> cache;\n  const std::string key = commandName + \"\\n\" + helpText;\n  auto it = cache.find(key);\n\n  if (it == cache.end()) {\n    it = cache.emplace(key, parse_help_completion_spec(commandName, helpText)).first;\n  }\n\n  return it->second;\n}\n\nsize_t\ncount_non_option_arguments(const std::vector<std::string>& args)\n{\n  size_t count = 0;\n\n  for (const auto& arg : args) {\n    if (arg.empty() || arg[0] == '-') {\n      continue;\n    }\n\n    ++count;\n  }\n\n  return count;\n}\n\nbool\nstarts_with(const std::string& value, const std::string& prefix)\n{\n  return value.compare(0, prefix.size(), prefix) == 0;\n}\n\nstd::vector<std::string>\ndedupe_and_sort(std::vector<std::string> candidates)\n{\n  std::sort(candidates.begin(), candidates.end());\n  candidates.erase(std::unique(candidates.begin(), candidates.end()), candidates.end());\n  return candidates;\n}\n\nbool\nends_with(const std::string& value, const std::string& suffix)\n{\n  if (suffix.size() > value.size()) {\n    return false;\n  }\n\n  return value.compare(value.size() - suffix.size(), suffix.size(), suffix) == 0;\n}\n\nstd::vector<std::string>\nfilter_candidates(const std::vector<std::string>& candidates, const std::string& prefix)\n{\n  std::vector<std::string> filtered;\n\n  for (const auto& candidate : candidates) {\n    if (!candidate.empty() && starts_with(candidate, prefix)) {\n      filtered.push_back(candidate);\n    }\n  }\n\n  return dedupe_and_sort(std::move(filtered));\n}\n\nstd::vector<std::string>\nglobal_option_candidates()\n{\n  return {\"--app\", \"--batch\", \"--help\", \"--json\", \"--role\", \"--version\", \"-a\",\n          \"-b\",    \"-h\",      \"-j\",     \"-r\",     \"-s\",     \"-v\"};\n}\n\nsize_t\nfind_command_token_index(const std::vector<std::string>& tokens)\n{\n  for (size_t i = 0; i < tokens.size(); ++i) {\n    const auto& token = tokens[i];\n\n    if (token.empty()) {\n      continue;\n    }\n\n    if (token == \"-a\" || token == \"--app\") {\n      ++i;\n      continue;\n    }\n\n    if (token == \"-r\" || token == \"--role\") {\n      i += 2;\n      continue;\n    }\n\n    if (token == \"-b\" || token == \"--batch\" || token == \"-h\" || token == \"--help\" ||\n        token == \"-j\" || token == \"--json\" || token == \"-s\" || token == \"-v\" ||\n        token == \"--version\") {\n      continue;\n    }\n\n    if (token.rfind(\"root://\", 0) == 0 || token.rfind(\"ipc://\", 0) == 0) {\n      continue;\n    }\n\n    if (!token.empty() && token[0] == '-') {\n      continue;\n    }\n\n    return i;\n  }\n\n  return tokens.size();\n}\n\nbool\nis_path_identifier_token(const std::string& token)\n{\n  return token.rfind(\"fid:\", 0) == 0 || token.rfind(\"fxid:\", 0) == 0 ||\n         token.rfind(\"cid:\", 0) == 0 || token.rfind(\"cxid:\", 0) == 0 ||\n         token.rfind(\"pid:\", 0) == 0 || token.rfind(\"pxid:\", 0) == 0;\n}\n\nbool\nlooks_like_non_eos_path(const std::string& token)\n{\n  return token.rfind(\"./\", 0) == 0 || token.rfind(\"../\", 0) == 0 ||\n         token.rfind(\"~/\", 0) == 0 || token.rfind(\"file:\", 0) == 0 ||\n         token.rfind(\"root://\", 0) == 0 || token.rfind(\"as3:\", 0) == 0;\n}\n\nbool\nis_eos_path_prefix(const std::string& token)\n{\n  return token == \"/eos\" || token.rfind(\"/eos/\", 0) == 0;\n}\n\nbool\nshould_complete_file_subcommand_paths(const std::vector<std::string>& args)\n{\n  if (args.empty()) {\n    return false;\n  }\n\n  static const std::unordered_set<std::string> kPathSubcommands = {\"adjustreplica\",\n                                                                   \"check\",\n                                                                   \"convert\",\n                                                                   \"copy\",\n                                                                   \"drop\",\n                                                                   \"info\",\n                                                                   \"layout\",\n                                                                   \"move\",\n                                                                   \"purge\",\n                                                                   \"rename\",\n                                                                   \"rename_with_symlink\",\n                                                                   \"replicate\",\n                                                                   \"share\",\n                                                                   \"symlink\",\n                                                                   \"tag\",\n                                                                   \"touch\",\n                                                                   \"verify\",\n                                                                   \"version\",\n                                                                   \"versions\",\n                                                                   \"workflow\"};\n\n  return kPathSubcommands.find(args[0]) != kPathSubcommands.end();\n}\n\nbool\nis_path_capable_command_name(const std::string& commandName)\n{\n  static const std::unordered_set<std::string> kPathCommands = {\n      \"acl\", \"attr\",     \"cat\",   \"cd\",   \"chmod\", \"chown\", \"cp\",\n      \"du\",  \"fileinfo\", \"find\",  \"info\", \"ln\",    \"ls\",    \"mkdir\",\n      \"mv\",  \"rm\",       \"rmdir\", \"stat\", \"touch\"};\n\n  return kPathCommands.find(commandName) != kPathCommands.end();\n}\n\nstruct CompletionClientState {\n  XrdOucString serverUri;\n  XrdOucString userRole;\n  XrdOucString groupRole;\n  bool jsonOutput;\n};\n\nclass ScopedCompletionClientState {\npublic:\n  ScopedCompletionClientState()\n      : mSaved{serveruri, user_role, group_role, json}\n  {\n  }\n\n  ~ScopedCompletionClientState()\n  {\n    serveruri = mSaved.serverUri;\n    user_role = mSaved.userRole;\n    group_role = mSaved.groupRole;\n    json = mSaved.jsonOutput;\n  }\n\nprivate:\n  CompletionClientState mSaved;\n};\n\nvoid\napply_completion_client_context(const std::vector<std::string>& tokens)\n{\n  for (size_t i = 0; i < tokens.size(); ++i) {\n    const auto& token = tokens[i];\n\n    if ((token == \"-r\" || token == \"--role\") && i + 2 < tokens.size()) {\n      user_role = tokens[i + 1].c_str();\n      group_role = tokens[i + 2].c_str();\n      i += 2;\n      continue;\n    }\n\n    if (token.rfind(\"root://\", 0) == 0) {\n      serveruri = token.c_str();\n      continue;\n    }\n\n    if (token.rfind(\"ipc://\", 0) == 0) {\n      if (token == \"ipc://\") {\n        serveruri = \"ipc:///var/eos/md/.admin_socket:1094\";\n      } else {\n        serveruri = token.c_str();\n      }\n      continue;\n    }\n  }\n\n  json = false;\n}\n\nstd::vector<std::string>\ndecode_env_lines(XrdOucEnv* env, const char* key)\n{\n  std::vector<std::string> lines;\n\n  if (!env) {\n    return lines;\n  }\n\n  XrdOucString value = env->Get(key);\n\n  if (!value.length()) {\n    return lines;\n  }\n\n  if (value.beginswith(\"base64:\")) {\n    XrdOucString decoded;\n    eos::common::SymKey::DeBase64(value, decoded);\n    value = decoded;\n  } else {\n    eos::common::StringConversion::UnSeal(value);\n  }\n\n  XrdOucTokenizer tokenizer((char*)value.c_str());\n  const char* line = nullptr;\n\n  while ((line = tokenizer.GetLine())) {\n    std::string candidate = line;\n\n    if (!candidate.empty() && candidate.back() == '\\n') {\n      candidate.pop_back();\n    }\n\n    if (!candidate.empty()) {\n      lines.push_back(std::move(candidate));\n    }\n  }\n\n  return lines;\n}\n\nstd::vector<std::string>\nlist_remote_path_candidates(const std::vector<std::string>& tokens,\n                            const std::string& lookupDir,\n                            const std::string& displayPrefix, const std::string& basename,\n                            EosShellPathCompletionMode mode)\n{\n  ScopedCompletionClientState stateGuard;\n  apply_completion_client_context(tokens);\n\n  XrdOucString escaped =\n      eos::common::StringConversion::curl_escaped(lookupDir.c_str()).c_str();\n  XrdOucString in = \"mgm.cmd=ls&mgm.path=\";\n  in += escaped;\n  in += \"&eos.encodepath=1&mgm.option=-F\";\n\n  std::unique_ptr<XrdOucEnv> env(client_command(in, false, nullptr));\n  auto entries = decode_env_lines(env.get(), \"mgm.proc.stdout\");\n  CommandEnv = nullptr;\n  std::vector<std::string> candidates;\n\n  for (const auto& entry : entries) {\n    const bool isDir = !entry.empty() && entry.back() == '/';\n\n    if (mode == EosShellPathCompletionMode::Directories && !isDir) {\n      continue;\n    }\n\n    if (!basename.empty() && !starts_with(entry, basename)) {\n      continue;\n    }\n\n    candidates.push_back(displayPrefix + entry);\n  }\n\n  return dedupe_and_sort(std::move(candidates));\n}\n} // namespace\n\n//------------------------------------------------------------------------------\n// Tokenize the command prefix before the cursor while honoring simple quoting.\n//------------------------------------------------------------------------------\nstd::vector<std::string>\neos_completion_tokenize_prefix(const std::string& input)\n{\n  std::vector<std::string> tokens;\n  std::string cur;\n  bool in_double = false;\n  bool in_single = false;\n\n  for (size_t i = 0; i < input.size(); ++i) {\n    const char c = input[i];\n\n    if (c == '\\\\' && !in_single && i + 1 < input.size()) {\n      cur.push_back(input[i + 1]);\n      ++i;\n      continue;\n    }\n\n    if (c == '\"' && !in_single) {\n      in_double = !in_double;\n      continue;\n    }\n\n    if (c == '\\'' && !in_double) {\n      in_single = !in_single;\n      continue;\n    }\n\n    if (isspace(static_cast<unsigned char>(c)) && !in_double && !in_single) {\n      if (!cur.empty()) {\n        tokens.push_back(cur);\n        cur.clear();\n      }\n      continue;\n    }\n\n    cur.push_back(c);\n  }\n\n  if (!cur.empty()) {\n    tokens.push_back(cur);\n  }\n\n  return tokens;\n}\n\nEosShellPathCompletionMode\neos_shell_path_completion_mode(const std::string& commandName,\n                               const std::vector<std::string>& args,\n                               const std::string& currentWord)\n{\n  if ((!currentWord.empty() && currentWord[0] == '-') ||\n      is_path_identifier_token(currentWord) || looks_like_non_eos_path(currentWord)) {\n    return EosShellPathCompletionMode::None;\n  }\n\n  if (!currentWord.empty() && !is_eos_path_prefix(currentWord)) {\n    return EosShellPathCompletionMode::None;\n  }\n\n  if (commandName == \"cd\" || commandName == \"mkdir\" || commandName == \"rmdir\") {\n    return EosShellPathCompletionMode::Directories;\n  }\n\n  if (is_path_capable_command_name(commandName)) {\n    return EosShellPathCompletionMode::Any;\n  }\n\n  if (commandName == \"file\" && should_complete_file_subcommand_paths(args)) {\n    return EosShellPathCompletionMode::Any;\n  }\n\n  return EosShellPathCompletionMode::None;\n}\n\nvoid\neos_shell_resolve_rooted_path_input(const std::string& currentWord,\n                                    std::string& lookupDir, std::string& displayPrefix,\n                                    std::string& basename)\n{\n  if (currentWord.empty()) {\n    lookupDir = \"/eos/\";\n    displayPrefix = \"/eos/\";\n    basename.clear();\n    return;\n  }\n\n  if (currentWord == \"/eos\") {\n    lookupDir = \"/\";\n    displayPrefix = \"/\";\n    basename = \"eos\";\n    return;\n  }\n\n  eos_path_split(currentWord, displayPrefix, basename);\n\n  if (currentWord.back() == '/') {\n    basename.clear();\n    lookupDir = currentWord;\n  } else if (!displayPrefix.empty()) {\n    lookupDir = displayPrefix;\n  } else {\n    lookupDir = \"/eos/\";\n  }\n\n  if (lookupDir.empty()) {\n    lookupDir = \"/eos/\";\n  }\n\n  if (!ends_with(lookupDir, \"/\")) {\n    lookupDir += \"/\";\n  }\n}\n\nstd::vector<std::string>\neos_shell_completion_candidates(const std::vector<std::string>& precedingTokens,\n                                const std::string& currentWord)\n{\n  EnsureNativeCommandRegistryInitialized();\n\n  const auto commandIndex = find_command_token_index(precedingTokens);\n\n  if (commandIndex >= precedingTokens.size()) {\n    if (!currentWord.empty() && currentWord[0] == '-') {\n      return filter_candidates(global_option_candidates(), currentWord);\n    }\n\n    std::vector<std::string> candidates = global_option_candidates();\n\n    for (const auto* command : CommandRegistry::instance().all()) {\n      if (command && command->name()) {\n        candidates.emplace_back(command->name());\n      }\n    }\n\n    return filter_candidates(candidates, currentWord);\n  }\n\n  const auto& commandName = precedingTokens[commandIndex];\n  IConsoleCommand* icmd = CommandRegistry::instance().find(commandName);\n\n  if (!icmd) {\n    return {};\n  }\n\n  std::vector<std::string> args(precedingTokens.begin() + commandIndex + 1,\n                                precedingTokens.end());\n  const auto pathMode = eos_shell_path_completion_mode(commandName, args, currentWord);\n\n  if (pathMode != EosShellPathCompletionMode::None) {\n    std::string lookupDir;\n    std::string displayPrefix;\n    std::string basename;\n    eos_shell_resolve_rooted_path_input(currentWord, lookupDir, displayPrefix, basename);\n\n    auto pathCandidates = list_remote_path_candidates(precedingTokens, lookupDir,\n                                                      displayPrefix, basename, pathMode);\n\n    if (!pathCandidates.empty()) {\n      return pathCandidates;\n    }\n  }\n\n  return filter_candidates(icmd->complete(args), currentWord);\n}\n\nstd::vector<std::string>\neos_help_completion_candidates(const std::string& commandName,\n                               const std::string& helpText,\n                               const std::vector<std::string>& args)\n{\n  if (helpText.empty()) {\n    return {};\n  }\n\n  const auto& spec = cached_help_completion_spec(commandName, helpText);\n  std::set<std::string> pathSuggestions;\n  size_t bestMatchLength = 0;\n  std::vector<const HelpCompletionLeaf*> bestLeaves;\n\n  for (const auto& entry : spec) {\n    const auto& path = entry.first;\n    const auto& leaf = entry.second;\n\n    if (args.size() < path.size() && std::equal(args.begin(), args.end(), path.begin())) {\n      pathSuggestions.insert(path[args.size()]);\n    }\n\n    if (args.size() >= path.size() &&\n        std::equal(path.begin(), path.end(), args.begin())) {\n      if (path.size() > bestMatchLength) {\n        bestMatchLength = path.size();\n        bestLeaves.clear();\n      }\n\n      if (path.size() == bestMatchLength) {\n        bestLeaves.push_back(&leaf);\n      }\n    }\n  }\n\n  if (!pathSuggestions.empty()) {\n    return dedupe_and_sort(\n        std::vector<std::string>(pathSuggestions.begin(), pathSuggestions.end()));\n  }\n\n  if (bestLeaves.empty()) {\n    return {};\n  }\n\n  const std::vector<std::string> remaining(args.begin() + bestMatchLength, args.end());\n  const size_t positionalCount = count_non_option_arguments(remaining);\n  std::set<std::string> literalSuggestions;\n  std::set<std::string> optionSuggestions;\n\n  for (const auto* leaf : bestLeaves) {\n    auto literalIt = leaf->literalsAtPositionalCount.find(positionalCount);\n\n    if (literalIt != leaf->literalsAtPositionalCount.end()) {\n      literalSuggestions.insert(literalIt->second.begin(), literalIt->second.end());\n    }\n  }\n\n  if (!literalSuggestions.empty()) {\n    return dedupe_and_sort(\n        std::vector<std::string>(literalSuggestions.begin(), literalSuggestions.end()));\n  }\n\n  for (const auto* leaf : bestLeaves) {\n    if (leaf->optionActivationPositionalCount != std::numeric_limits<size_t>::max() &&\n        positionalCount >= leaf->optionActivationPositionalCount) {\n      optionSuggestions.insert(leaf->options.begin(), leaf->options.end());\n    }\n  }\n\n  return dedupe_and_sort(\n      std::vector<std::string>(optionSuggestions.begin(), optionSuggestions.end()));\n}\n\n//------------------------------------------------------------------------------\n// Helper function to extract the dirname and base name from a absolute or\n// relative path. For example:\n// \"/a/b/c/d\"  -> dirname: \"/a/b/c/\"   and basename: \"d\"\n// \"/a/b/c/d/\" -> dirname: \"/a/b/c/d/\" and basename: \"\"\n// \"x/y/z\"     -> dirname: \"x/y/\"      and basename: \"z\"\n// \"x/y/z/\"    -> dirname: \"x/y/z/\"    and basename: \"\"\n// \"\"          -> dirname: \"\"          and basename: \"\"\n//------------------------------------------------------------------------------\nvoid eos_path_split(const std::string& input, std::string& dirname,\n                    std::string& basename)\n{\n  if (input.empty()) {\n    dirname.clear();\n    basename.clear();\n    return;\n  }\n\n  size_t pos = input.rfind('/');\n\n  if (pos == std::string::npos) {\n    dirname.clear();\n    basename = input;\n    return;\n  }\n\n  dirname = input.substr(0, pos + 1);\n  basename = input.substr(pos + 1);\n  return;\n}\n\n//------------------------------------------------------------------------------\n// EOS console custom completion function to be used by the readline library\n// to provide autocompletion.\n//------------------------------------------------------------------------------\nchar** eos_console_completion(const char* text, int start, int end)\n{\n  char** matches;\n  matches = (char**) 0;\n  // Disable filename completion if our generator finds no matches\n  rl_attempted_completion_over = 1;\n\n  EnsureNativeCommandRegistryInitialized();\n\n  // If this word is at the start of the line, then it is a command\n  // to complete.  Otherwise it is the name of a file in the current\n  // directory.\n  if (start == 0) {\n    rl_completion_append_character = ' ';\n    matches = rl_completion_matches(text, eos_command_generator);\n    return matches;\n  }\n\n  std::string prefix(rl_line_buffer, rl_line_buffer + start);\n  const auto tokens = eos_completion_tokenize_prefix(prefix);\n\n  if (!tokens.empty()) {\n    IConsoleCommand* icmd = CommandRegistry::instance().find(tokens.front());\n\n    if (icmd) {\n      std::vector<std::string> args(tokens.begin() + 1, tokens.end());\n      auto candidates = icmd->complete(args);\n\n      if (!candidates.empty()) {\n        std::unordered_set<std::string> seen;\n        gWordCompletionCandidates.clear();\n\n        for (const auto& candidate : candidates) {\n          if (candidate.empty()) {\n            continue;\n          }\n\n          if (seen.insert(candidate).second) {\n            gWordCompletionCandidates.push_back(candidate);\n          }\n        }\n\n        std::sort(gWordCompletionCandidates.begin(), gWordCompletionCandidates.end());\n        rl_completion_append_character = ' ';\n        matches = rl_completion_matches(text, eos_word_generator);\n\n        if (matches) {\n          return matches;\n        }\n      }\n    }\n  }\n\n  XrdOucString cmd = rl_line_buffer;\n\n  if (cmd.beginswith(\"mkdir \") ||\n      cmd.beginswith(\"rmdir \") ||\n      cmd.beginswith(\"find \") ||\n      cmd.beginswith(\"cd \") ||\n      cmd.beginswith(\"chown \") ||\n      cmd.beginswith(\"chmod \") ||\n      cmd.beginswith(\"attr \") ||\n      cmd.beginswith(\"acl \")) {\n    // dir completion\n    rl_completion_append_character = '\\0';\n    matches = rl_completion_matches(text, eos_dir_generator);\n  }\n\n  if (cmd.beginswith(\"rm \") ||\n      cmd.beginswith(\"ls \") ||\n      cmd.beginswith(\"fileinfo \") ||\n      cmd.beginswith(\"cp \")) {\n    // dir/file completion\n    rl_completion_append_character = '\\0';\n    matches = rl_completion_matches(text, eos_all_generator);\n  }\n\n  return (matches);\n}\n\n//------------------------------------------------------------------------------\n// EOS entry (files/directories) generator\n//------------------------------------------------------------------------------\nchar* eos_entry_generator(const char* text, int state, bool only_dirs)\n{\n  static size_t index;\n  static std::vector<std::string> entries;\n\n  // If this is a new word to complete, initialize now. This includes\n  // saving the length of TEXT for efficiency, and initializing the index\n  // variable to 0.\n  if (!state) {\n    index = 0;\n    entries.clear();\n    std::string inarg = text;\n    std::string dirname;\n    std::string basename;\n    eos_path_split(inarg, dirname, basename);\n\n    if (dirname.empty()) {\n      inarg = gPwd.c_str();\n    } else {\n      if (dirname.at(0) == '/') {\n        inarg = dirname;\n      } else {\n        inarg = gPwd.c_str() + dirname;\n      }\n    }\n\n    bool oldsilent = silent;\n    silent = true;\n    XrdOucString comarg = \"-F \";\n    comarg += inarg.c_str();\n    char buffer[4096];\n    sprintf(buffer, \"%s\", comarg.c_str());\n    com_ls((char*) buffer);\n    silent = oldsilent;\n\n    if (rstdout.c_str()) {\n      XrdOucTokenizer subtokenizer((char*) rstdout.c_str());\n\n      do {\n        XrdOucString entry = subtokenizer.GetLine();\n\n        if (entry.length()) {\n          if (entry.endswith('\\n')) {\n            entry.erase(entry.length() - 1);\n          }\n\n          if (only_dirs && !entry.endswith('/')) {\n            continue;\n          }\n\n          if (rl_completion_type == 63) { // ? - list possible completions\n            // When listing completions we need to return the basename of the\n            // candidates\n            if (basename.empty() ||\n                ((strncmp(basename.c_str(), entry.c_str(), basename.length()) == 0) &&\n                 // Exclude exact matches\n                 (basename.length() < (size_t)entry.length()))) {\n              entries.push_back(entry.c_str());\n            }\n          } else if (rl_completion_type == 9) { // TAB - do standard completion\n            // When doing the standard completion we need to return the full path\n            // as given by the user initially (i.e. not including the pwd deduction).\n            if (basename.empty() ||\n                (strncmp(basename.c_str(), entry.c_str(), basename.length()) == 0)) {\n              std::string add_path = dirname;\n              add_path += entry.c_str();\n              entries.push_back(add_path.c_str());\n            }\n          }\n        } else {\n          break;\n        }\n      } while (1);\n    }\n  }\n\n  if (index < entries.size()) {\n    return strdup(entries[index++].c_str());\n  }\n\n  return ((char*) 0);\n}\n\n//------------------------------------------------------------------------------\n// EOS directories generator - similar to the above\n//------------------------------------------------------------------------------\nchar* eos_dir_generator(const char* text, int state)\n{\n  return eos_entry_generator(text, state, true);\n}\n\n//------------------------------------------------------------------------------\n// EOS files and directories generator - similar to the above\n//------------------------------------------------------------------------------\nchar* eos_all_generator(const char* text, int state)\n{\n  return eos_entry_generator(text, state);\n}\n\n//------------------------------------------------------------------------------\n// Generator function for command completion - similar to the above.\n//------------------------------------------------------------------------------\nchar* eos_command_generator(const char* text, int state)\n{\n  static size_t index;\n  static std::vector<std::string> completions;\n\n  // If this is a new word to complete, initialize now.  This includes\n  // saving the length of TEXT for efficiency, and initializing the index\n  // variable to 0.\n  if (!state) {\n    int len = strlen(text);\n    completions.clear();\n    index = 0;\n\n    // Prefer registry over static commands array\n    auto& all = CommandRegistry::instance().all();\n    for (auto* c : all) {\n      const char* name = c->name();\n      if (strncmp(name, text, len) == 0) completions.push_back(name);\n    }\n  } else {\n    ++index;\n  }\n\n  if (index < completions.size()) {\n    return strdup(completions[index].c_str());\n  }\n\n  return ((char*) 0);\n}\n\n//------------------------------------------------------------------------------\n// Generator function for static word completion lists returned by commands.\n//------------------------------------------------------------------------------\nchar*\neos_word_generator(const char* text, int state)\n{\n  static size_t index;\n  static std::vector<std::string> completions;\n\n  if (!state) {\n    index = 0;\n    completions.clear();\n    const int len = strlen(text);\n\n    for (const auto& candidate : gWordCompletionCandidates) {\n      if (strncmp(candidate.c_str(), text, len) == 0) {\n        completions.push_back(candidate);\n      }\n    }\n  } else {\n    ++index;\n  }\n\n  if (index < completions.size()) {\n    return strdup(completions[index].c_str());\n  }\n\n  return ((char*)0);\n}\n"
  },
  {
    "path": "console/ConsoleCompletion.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file ConsoleCompletion.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include <string>\n#include <vector>\n\n//------------------------------------------------------------------------------\n//! EOS console custom completion function to be used by the readline library\n//! to provide autocompletion.\n//!\n//! @param text text to be completed\n//! @param start start index of text in the rl_line_buffer\n//! @param end end index of text in the rl_line_buffer\n//!\n//! @return NULL if no completion available, otherwise an array of strings to\n//!         be used\n//------------------------------------------------------------------------------\nchar** eos_console_completion(const char* text, int start, int end);\n\n//------------------------------------------------------------------------------\n//! EOS entry (files/directories) generator\n//!\n//! @param text partial text to be completed\n//! @param state 0 the first time the function is called allowing the generator\n//!        to perform initialization and positive non-zero integer for each\n//!        subsequent call.\n//! @param only_dirs if true then return only directories, otherwise return\n//!        both files and directories\n//!\n//! @return NULL when there are no more completion possibilities left, otherwise\n//!        return a string that must be allocated with malloc() since readline\n//!        will free the strings when it has finished with them.\n//------------------------------------------------------------------------------\nchar* eos_entry_generator(const char* text, int state,\n                          bool only_dirs = false);\n\n//------------------------------------------------------------------------------\n//! EOS directories generator - similar to the above\n//------------------------------------------------------------------------------\nchar* eos_dir_generator(const char* text, int state);\n\n//------------------------------------------------------------------------------\n//! EOS files and directories generator - similar to the above\n//------------------------------------------------------------------------------\nchar* eos_all_generator(const char* text, int state);\n\n//------------------------------------------------------------------------------\n//! Generator function for command completion - similar to the above\n//------------------------------------------------------------------------------\nchar* eos_command_generator(const char* text, int state);\n\n//------------------------------------------------------------------------------\n//! Generic generator for word-based completions returned by a command hook.\n//------------------------------------------------------------------------------\nchar* eos_word_generator(const char* text, int state);\n\n//------------------------------------------------------------------------------\n//! Helper function to extract the dirname and base name from a absolute or\n//! relative path. For example:\n//! \"/a/b/c/d\"  -> dirname: \"/a/b/c/\"   and basename: \"d\"\n//! \"/a/b/c/d/\" -> dirname: \"/a/b/c/d/\" and basename: \"\"\n//! \"x/y/z\"     -> dirname: \"x/y/\"      and basename: \"z\"\n//! \"x/y/z/\"    -> dirname: \"x/y/z/\"    and basename: \"\"\n//! \"\"          -> dirname: \"\"          and basename: \"\"\n//! \"x\"         -> dirname: \"\"          and basename: \"x\"\n//!\n//! @param input input path to process\n//! @param dirname dirname component\n//! @param basename basename component\n//------------------------------------------------------------------------------\nvoid eos_path_split(const std::string& input, std::string& dirname,\n                    std::string& basename);\n\n//------------------------------------------------------------------------------\n//! Tokenize the command prefix before the cursor while honoring simple shell\n//! quoting rules.\n//------------------------------------------------------------------------------\nstd::vector<std::string> eos_completion_tokenize_prefix(const std::string& input);\n\n//------------------------------------------------------------------------------\n//! Return completion candidates for shell integrations based on the tokens\n//! already present before the current word and the current word prefix.\n//------------------------------------------------------------------------------\nstd::vector<std::string>\neos_shell_completion_candidates(const std::vector<std::string>& precedingTokens,\n                                const std::string& currentWord);\n\n//------------------------------------------------------------------------------\n//! Return completion candidates derived from a command help text.\n//------------------------------------------------------------------------------\nstd::vector<std::string>\neos_help_completion_candidates(const std::string& commandName,\n                               const std::string& helpText,\n                               const std::vector<std::string>& args);\n\n//------------------------------------------------------------------------------\n//! Decide whether the current shell-completion context should offer EOS path\n//! candidates and whether they should be limited to directories.\n//------------------------------------------------------------------------------\nenum class EosShellPathCompletionMode {\n  None,\n  Any,\n  Directories,\n};\n\nEosShellPathCompletionMode\neos_shell_path_completion_mode(const std::string& commandName,\n                               const std::vector<std::string>& args,\n                               const std::string& currentWord);\n\n//------------------------------------------------------------------------------\n//! Resolve a shell token into a rooted EOS lookup directory, the prefix that\n//! should be re-applied to display candidates, and the basename currently being\n//! completed.\n//------------------------------------------------------------------------------\nvoid eos_shell_resolve_rooted_path_input(const std::string& currentWord,\n                                         std::string& lookupDir,\n                                         std::string& displayPrefix,\n                                         std::string& basename);\n"
  },
  {
    "path": "console/ConsoleMain.cc",
    "content": "//------------------------------------------------------------------------------\n// File ConsoleMain.cc\n// Author Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"ConsoleMain.hh\"\n#include \"ConsoleCompletion.hh\"\n#include \"CommandFramework.hh\"\n#include \"console/RegexUtil.hh\"\n#include <XrdCl/XrdClDefaultEnv.hh>\n#include <XrdCl/XrdClURL.hh>\n#include \"License\"\n#include \"common/FileId.hh\"\n#include \"common/Path.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/mq/XrdMqTiming.hh\"\n#include <XrdOuc/XrdOucTokenizer.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdCl/XrdClFile.hh>\n#include <zmq.hpp>\n#include <iomanip>\n#include <setjmp.h>\n#include <readline/readline.h>\n#include <readline/history.h>\n#include <list>\n#include <sys/time.h>\n#include <sys/resource.h>\n#include <XrdCl/XrdClPostMaster.hh>\n\n#ifdef __APPLE__\n#define ENONET 64\n#endif\n\n\n//------------------------------------------------------------------------------\n// Global variables\n//------------------------------------------------------------------------------\nXrdOucString serveruri = \"\";\nXrdOucString historyfile = \"\";\nXrdOucString pwdfile = \"\";\nXrdOucString gPwd = \"/\";\nXrdOucString rstdout;\nXrdOucString rstderr;\nXrdOucString rstdjson;\nXrdOucString user_role = \"\";\nXrdOucString group_role = \"\";\nXrdOucString global_comment = \"\";\nXrdOucString app = \"\";\n\nint global_retc = 0;\nbool global_highlighting = true;\nbool global_debug = false;\nbool interactive = true;\nbool hasterminal = true;\nbool silent = false;\nbool timing = false;\nbool json = false;\nGlobalOptions gGlobalOpts;\n//! When non-zero, this global means the user is done using this program. */\nint done;\n\n// Pointer to the result of client_command. It gets invalid when the\n// output_result function is called.\nXrdOucEnv* CommandEnv = 0;\nstatic sigjmp_buf sigjump_buf;\n\n// Convenience runner using the registered native commands\nstatic int RunRegisteredCommand(const std::string& cmdName,\n                                const std::vector<std::string>& argsVec)\n{\n  EnsureNativeCommandRegistryInitialized();\n  IConsoleCommand* icmd = CommandRegistry::instance().find(cmdName);\n  if (!icmd) {\n    fprintf(stderr, \"%s: No such command for EOS Console.\\n\", cmdName.c_str());\n    return -1;\n  }\n\n  std::string rest;\n  for (size_t i = 0; i < argsVec.size(); ++i) {\n    if (i) rest.push_back(' ');\n    rest += argsVec[i];\n  }\n\n  if (icmd->requiresMgm(rest) &&\n      !CheckMgmOnline(serveruri.c_str())) {\n    std::cerr << \"error: MGM \" << serveruri.c_str()\n              << \" not online/reachable\" << std::endl;\n    exit(ENONET);\n  }\n\n  CommandContext ctx;\n  ctx.serverUri = serveruri.c_str();\n  ctx.globalOpts = &gGlobalOpts;\n  ctx.json = json;\n  ctx.silent = silent;\n  ctx.interactive = interactive;\n  ctx.timing = timing;\n  ctx.userRole = user_role.c_str();\n  ctx.groupRole = group_role.c_str();\n  ctx.clientCommand = &client_command;\n  ctx.outputResult = &output_result;\n\n  return icmd->run(argsVec, ctx);\n}\n\n//------------------------------------------------------------------------------\n// Exit handler\n//------------------------------------------------------------------------------\nvoid\nexit_handler(int a)\n{\n  fprintf(stdout, \"\\n\");\n  fprintf(stderr, \"<Control-C>\\n\");\n  write_history(historyfile.c_str());\n  exit(-1);\n}\n\n//------------------------------------------------------------------------------\n// Jump handler\n//------------------------------------------------------------------------------\nvoid\njump_handler(int a)\n{\n  siglongjmp(sigjump_buf, 1);\n}\n\n//------------------------------------------------------------------------------\n// Absolute path conversion function\n//------------------------------------------------------------------------------\nconst char*\nabspath(const char* in)\n{\n  static XrdOucString inpath;\n  inpath = in;\n\n  if (inpath.beginswith(\"fxid:\") || inpath.beginswith(\"fid:\") ||\n      inpath.beginswith(\"cxid:\") || inpath.beginswith(\"cid:\") ||\n      inpath.beginswith(\"pxid:\") || inpath.beginswith(\"pid:\")) {\n    return inpath.c_str();\n  }\n\n  if (inpath.beginswith(\"/\")) {\n    return inpath.c_str();\n  }\n\n  if (gPwd == \"/\") {\n    // check if we are in a /eos/ mountpoint\n    char pwd[4096];\n\n    if (getcwd(pwd, sizeof(pwd))) {\n      XrdOucString lpwd = pwd;\n\n      if (lpwd.beginswith(\"/eos\")) {\n        inpath = pwd;\n        inpath += \"/\";\n      } else {\n        inpath = gPwd;\n      }\n    } else {\n      inpath = gPwd;\n    }\n  } else {\n    inpath = gPwd;\n  }\n\n  inpath += in;\n  eos::common::Path cPath(inpath.c_str());\n  inpath = cPath.GetPath();\n  return inpath.c_str();\n}\n\n//------------------------------------------------------------------------------\n// Help flag filter\n//------------------------------------------------------------------------------\nbool\nwants_help(const char* args_line, bool no_h)\n{\n  std::string allargs = \" \";\n  allargs += args_line;\n  allargs += \" \";\n\n  if ((allargs.find(\"\\\"--help\\\"\") != std::string::npos) ||\n      (allargs.find(\" --help \") != std::string::npos) ||\n      (allargs.find(\" \\\"--help\\\" \") != std::string::npos)) {\n    const char* ptr;\n    std::string token;\n    eos::common::StringTokenizer tokenizer(allargs);\n    tokenizer.GetLine();\n\n    while ((ptr = tokenizer.GetToken(false))) {\n      token = ptr;\n\n      if (token == \"--help\") {\n        return true;\n      }\n    }\n  }\n\n  if (!no_h) {\n    if ((allargs.find(\"\\\"-h\\\"\") != std::string::npos) ||\n        (allargs.find(\" -h \") != std::string::npos) ||\n        (allargs.find(\" \\\"-h\\\" \") != std::string::npos)) {\n      const char* ptr;\n      std::string token;\n      eos::common::StringTokenizer tokenizer(allargs);\n      tokenizer.GetLine();\n\n      while ((ptr = tokenizer.GetToken(false))) {\n        token = ptr;\n\n        if (token == \"-h\") {\n          return true;\n        }\n      }\n    }\n  }\n\n  return false;\n}\n\n/* **************************************************************** */\n/*                                                                  */\n/*                       EOSConsole Commands                        */\n/*                                                                  */\n/* **************************************************************** */\nvoid\ncommand_result_stdout_to_vector(std::vector<std::string>& string_vector)\n{\n  string_vector.clear();\n\n  if (!CommandEnv) {\n    fprintf(stderr, \"error: command env is 0!\\n\");\n    return;\n  }\n\n  rstdout = CommandEnv->Get(\"mgm.proc.stdout\");\n\n  if (!rstdout.length()) {\n    return;\n  }\n\n  if (rstdout.beginswith(\"base64:\")) {\n    XrdOucString ub64out;\n    eos::common::SymKey::DeBase64(rstdout, ub64out);\n    rstdout = ub64out;\n  } else {\n    eos::common::StringConversion::UnSeal(rstdout);\n  }\n\n  XrdOucTokenizer subtokenizer((char*) rstdout.c_str());\n  const char* nextline = 0;\n  int i = 0;\n\n  while ((nextline = subtokenizer.GetLine())) {\n    if ((!strlen(nextline)) || (nextline[0] == '\\n')) {\n      continue;\n    }\n\n    string_vector.resize(i + 1);\n    string_vector.push_back(nextline);\n    i++;\n  }\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nint\noutput_result(XrdOucEnv* result, bool highlighting)\n{\n  using eos::common::StringConversion;\n\n  if (!result) {\n    return EINVAL;\n  }\n\n  rstdout = result->Get(\"mgm.proc.stdout\");\n  rstderr = result->Get(\"mgm.proc.stderr\");\n  rstdjson = result->Get(\"mgm.proc.json\");\n\n  if (rstdout.beginswith(\"base64:\")) {\n    XrdOucString ub64out;\n    eos::common::SymKey::DeBase64(rstdout, ub64out);\n    rstdout = ub64out;\n  } else {\n    StringConversion::UnSeal(rstdout);\n  }\n\n  if (rstderr.beginswith(\"base64:\")) {\n    XrdOucString ub64out;\n    eos::common::SymKey::DeBase64(rstderr, ub64out);\n    rstderr = ub64out;\n  } else {\n    StringConversion::UnSeal(rstderr);\n  }\n\n  if (rstdjson.beginswith(\"base64:\")) {\n    XrdOucString ub64out;\n    eos::common::SymKey::DeBase64(rstdjson, ub64out);\n    rstdjson = ub64out;\n  } else {\n    StringConversion::UnSeal(rstdjson);\n  }\n\n  if (highlighting && global_highlighting) {\n    // color replacements\n    rstdout.replace(\"[booted]\", \"\\033[1m[booted]\\033[0m\");\n    rstdout.replace(\"[down]\", \"\\033[49;31m[down]\\033[0m\");\n    rstdout.replace(\"[failed]\", \"\\033[49;31m[failed]\\033[0m\");\n    rstdout.replace(\"[booting]\", \"\\033[49;32m[booting]\\033[0m\");\n    rstdout.replace(\"[compacting]\", \"\\033[49;34m[compacting]\\033[0m\");\n    // replication highlighting\n    rstdout.replace(\"master-rw\", \"\\033[49;31mmaster-rw\\033[0m\");\n    rstdout.replace(\"master-ro\", \"\\033[49;34mmaster-ro\\033[0m\");\n    rstdout.replace(\"slave-ro\", \"\\033[1mslave-ro\\033[0m\");\n    rstdout.replace(\"=ok\", \"=\\033[49;32mok\\033[0m\");\n    rstdout.replace(\"=compacting\", \"=\\033[49;32mcompacting\\033[0m\");\n    rstdout.replace(\"=off\", \"=\\033[49;34moff\\033[0m\");\n    rstdout.replace(\"=blocked\", \"=\\033[49;34mblocked\\033[0m\");\n    rstdout.replace(\"=wait\", \"=\\033[49;34mwait\\033[0m\");\n    rstdout.replace(\"=starting\", \"=\\033[49;34mstarting\\033[0m\");\n    rstdout.replace(\"=true\", \"=\\033[49;32mtrue\\033[0m\");\n    rstdout.replace(\"=false\", \"=\\033[49;31mfalse\\033[0m\");\n  }\n\n  int retc = 0;\n\n  if (result->Get(\"mgm.proc.retc\")) {\n    retc = atoi(result->Get(\"mgm.proc.retc\"));\n  }\n\n  if (json) {\n    if (rstdjson.length())\n      if (!silent) {\n        fprintf(stdout, \"%s\", rstdjson.c_str());\n\n        if (rstdjson.endswith('\\n')) {\n          fprintf(stdout, \"\\n\");\n        }\n      }\n  } else {\n    if (rstdout.length())\n      if (!silent) {\n        fprintf(stdout, \"%s\", rstdout.c_str());\n\n        if (!rstdout.endswith('\\n')) {\n          fprintf(stdout, \"\\n\");\n        }\n      }\n\n    if (rstderr.length()) {\n      fprintf(stderr, \"%s (errc=%d) (%s)\\n\", rstderr.c_str(), retc, strerror(retc));\n    }\n  }\n\n  fflush(stdout);\n  fflush(stderr);\n  CommandEnv = 0;\n  delete result;\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Execute user command\n//------------------------------------------------------------------------------\nXrdOucEnv*\nclient_command(XrdOucString& in, bool is_admin, std::string* reply)\n{\n  if (app.length()) {\n    in += \"&eos.app=\";\n    in += app;\n  }\n\n  if (user_role.length()) {\n    in += \"&eos.ruid=\";\n    in += user_role;\n  }\n\n  if (group_role.length()) {\n    in += \"&eos.rgid=\";\n    in += group_role;\n  } else {\n    if (getenv(\"EOS_NEWGRP\")) {\n      if (getegid()) {\n        // add the current effective group ID as a wish to the request, but not root!\n        in += \"&eos.rgid=\";\n        in += std::to_string(getegid()).c_str();\n      }\n    }\n  }\n\n  if (json) {\n    in += \"&mgm.format=json\";\n  }\n\n  if (global_comment.length()) {\n    in += \"&mgm.comment=\";\n    in += global_comment;\n    global_comment = \"\";\n  }\n\n  if (getenv(\"EOSAUTHZ\")) {\n    in += \"&authz=\";\n    in += getenv(\"EOSAUTHZ\");\n  }\n\n  XrdMqTiming mytiming(\"eos\");\n  TIMING(\"start\", &mytiming);\n  XrdOucString out = \"\";\n  XrdOucString path = serveruri;\n\n  if (is_admin) {\n    path += \"//proc/admin/\";\n  } else {\n    path += \"//proc/user/\";\n  }\n\n  path += \"?\";\n  path += in;\n\n  if (global_debug) {\n    printf(\"> %s\\n\", path.c_str());\n  }\n\n  if (path.beginswith(\"ipc://\")) {\n    // local ZMQ ipc connection\n    zmq::context_t context(1);\n    zmq::socket_t socket(context, ZMQ_REQ);\n    path.erase(0, serveruri.length() + 1);\n    socket.connect(serveruri.c_str());\n    zmq::message_t request(path.length());\n    memcpy(request.data(), path.c_str(), path.length());\n    socket.send(request, zmq::send_flags::none);\n    std::string sout;\n    zmq::message_t response;\n    zmq::recv_result_t ret_recv = socket.recv(response);\n\n    if (ret_recv.has_value()) {\n      sout.assign((char*)response.data(), response.size());\n    }\n\n    CommandEnv = new XrdOucEnv(sout.c_str());\n\n    if (reply) {\n      reply->assign(out.c_str());\n    }\n  } else {\n    // xrootd based connection\n    XrdCl::OpenFlags::Flags flags_xrdcl = XrdCl::OpenFlags::Read;\n    std::unique_ptr<XrdCl::File> client {new XrdCl::File()};\n    XrdCl::XRootDStatus status = client->Open(path.c_str(), flags_xrdcl);\n\n    if (status.IsOK()) {\n      off_t offset = 0;\n      uint32_t nbytes = 0;\n      char buffer[4096 + 1];\n      status = client->Read(offset, 4096, buffer, nbytes);\n\n      while (status.IsOK() && (nbytes > 0)) {\n        buffer[nbytes] = 0;\n        out += buffer;\n        offset += nbytes;\n        status = client->Read(offset, 4096, buffer, nbytes);\n      }\n\n      status = client->Close();\n      TIMING(\"stop\", &mytiming);\n\n      if (timing) {\n        mytiming.Print();\n      }\n\n      if (global_debug) {\n        printf(\"> %s\\n\", out.c_str());\n      }\n\n      CommandEnv = new XrdOucEnv(out.c_str());\n\n      // Save the reply string from the server\n      if (reply) {\n        reply->assign(out.c_str());\n      }\n    } else {\n      std::string errmsg;\n      std::ostringstream oss;\n      int retc = status.GetShellCode();\n\n      if (status.errNo) {\n        retc = status.errNo;\n      }\n\n      oss << \"mgm.proc.stdout=&\"\n          << \"mgm.proc.stderr=\" << \"error: errc=\" << retc\n          << \" msg=\\\"\" << status.ToString() << \"\\\"&\"\n          << \"mgm.proc.retc=\" << retc;\n      CommandEnv = new XrdOucEnv(oss.str().c_str());\n\n      // Save the reply string from the server\n      if (reply) {\n        reply->assign(oss.str().c_str());\n      }\n    }\n  }\n\n  return CommandEnv;\n}\n\n//------------------------------------------------------------------------------\n// Load and apply the last used directory\n//------------------------------------------------------------------------------\nvoid\nread_pwdfile()\n{\n  std::string lpwd;\n  eos::common::StringConversion::LoadFileIntoString(pwdfile.c_str(), lpwd);\n\n  if (lpwd.length()) {\n    RunRegisteredCommand(\"cd\", {lpwd});\n  }\n}\n\n//------------------------------------------------------------------------------\n// Colour definitions\n//------------------------------------------------------------------------------\nstd::string textnormal(\"\\001\\033[0m\\002\");\nstd::string textblack(\"\\001\\033[49;30m\\002\");\nstd::string textred(\"\\001\\033[49;31m\\002\");\nstd::string textrederror(\"\\001\\033[47;31m\\e[5m\\002\");\nstd::string textblueerror(\"\\001\\033[47;34m\\e[5m\\002\");\nstd::string textgreen(\"\\001\\033[49;32m\\002\");\nstd::string textyellow(\"\\001\\033[49;33m\\002\");\nstd::string textblue(\"\\001\\033[49;34m\\002\");\nstd::string textbold(\"\\001\\033[1m\\002\");\nstd::string textunbold(\"\\001\\033[0m\\002\");\n\n//------------------------------------------------------------------------------\n// Usage Information\n//------------------------------------------------------------------------------\nvoid\nusage()\n{\n  fprintf(stderr,\n          \"`eos' is the command line interface (CLI) of the EOS storage system.\\n\");\n  fprintf(stderr,\n          \"Usage: eos [-r|--role <uid> <gid>] [-s] [-a|--app <app>] [-b|--batch] [-v|--version] [-j|--json] [<mgm-url>] [<cmd> {<argN>}|<filename>.eosh]\\n\");\n  fprintf(stderr,\n          \"            -r, --role <uid> <gid>              : select user role <uid> and group role <gid>\\n\");\n  fprintf(stderr,\n          \"            -a, --app <application>             : set the application name for the CLI\\n\");\n  fprintf(stderr,\n          \"            -b, --batch                         : run in batch mode without colour and syntax highlighting\\n\");\n  fprintf(stderr,\n          \"            -j, --json                          : switch to json output format\\n\");\n  fprintf(stderr,\n          \"            -h, --help                          : print help text\\n\");\n  fprintf(stderr,\n          \"            -v, --version                       : print version information\\n\");\n  fprintf(stderr,\n          \"            -s                                  : run <status> command\\n\");\n  fprintf(stderr,\n          \"            <mgm-url>                           : XRoot URL of the management server e.g. root://<hostname>[:<port>]\\n\");\n  fprintf(stderr,\n          \"            <cmd>                               : eos shell command (use 'eos help' to see available commands)\\n\");\n  fprintf(stderr,\n          \"            {<argN>}                            : single or list of arguments for the eos shell command <cmd>\\n\");\n  fprintf(stderr,\n          \"            <filename>.eosh                     : eos script file name ending with .eosh suffix\\n\\n\");\n  fprintf(stderr, \"Environment Variables: \\n\");\n  fprintf(stderr,\n          \"            EOS_MGM_URL                         : sets the redirector URL - if ipc://[ipc-path] is used, it will talk via ZMQ messaging to a single dedicated thread in the MGM\\n\");\n  fprintf(stderr,\n          \"            EOS_HISTORY_FILE                    : sets the command history file - by default '$HOME/.eos_history' is used\\n\\n\");\n  fprintf(stderr,\n          \"            EOS_NEWGRP                          : requests for each command the group ID of the current shell\\n\");\n  fprintf(stderr,\n          \"            EOS_PWD_FILE                        : sets the file where the last working directory is stored- by default '$HOME/.eos_pwd\\n\\n\");\n  fprintf(stderr, \"Return Value: \\n\");\n  fprintf(stderr,\n          \"            The return code of the last executed command is returned. 0 is returned in case of success otherwise <errno> (!=0).\\n\\n\");\n  fprintf(stderr, \"Examples:\\n\");\n  fprintf(stderr,\n          \"            eos                                 : start the interactive eos shell client connected to localhost or URL defined in environment variable EOS_MGM_URL\\n\");\n  fprintf(stderr,\n          \"            eos -r 0 0                          : as before but take role root/root [only numeric IDs are supported]\\n\");\n  fprintf(stderr,\n          \"            eos root://myeos                    : start the interactive eos shell connecting to MGM host 'myeos'\\n\");\n  fprintf(stderr,\n          \"            eos -b whoami                       : run the eos shell command 'whoami' in batch mode without syntax highlighting\\n\");\n  fprintf(stderr,\n          \"            eos space ls --io                   : run the eos shell command 'space' with arguments 'ls --io'\\n\");\n  fprintf(stderr,\n          \"            eos --version                       : print version information\\n\");\n  fprintf(stderr,\n          \"            eos -b eosscript.eosh               : run the eos shell script 'eosscript.eosh'. This script has to contain linewise commands which are understood by the eos interactive shell\\n\");\n  fprintf(stderr,\n          \"            eos -s                              : run <status> command\\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr,\n          \"You can leave the interactive shell with <Control-D>. <Control-C> cleans the current shell line or terminates the shell when a command is currently executed.\\n\");\n  fprintf(stderr, \"Report bugs to eos-dev@cern.ch\\n\");\n}\n\nnamespace {\nint\nRunShellCompletion(int argc, char* argv[], int argindex)\n{\n  std::vector<std::string> words;\n\n  for (int i = argindex; i < argc; ++i) {\n    words.emplace_back(argv[i] ? argv[i] : \"\");\n  }\n\n  std::string currentWord;\n\n  if (!words.empty()) {\n    currentWord = words.back();\n    words.pop_back();\n  }\n\n  const auto candidates = eos_shell_completion_candidates(words, currentWord);\n\n  for (const auto& candidate : candidates) {\n    fprintf(stdout, \"%s\\n\", candidate.c_str());\n  }\n\n  return 0;\n}\n} // namespace\n\n//------------------------------------------------------------------------------\n// Main executable\n//------------------------------------------------------------------------------\nint\nRun(int argc, char* argv[])\n{\n  // Ugly temporary hack for stopping the XRootD PostMaster environment no matter what happens (https://its.cern.ch/jira/browse/EOS-6282)\n  auto stopPostMaster = [&](void*) {\n    XrdCl::DefaultEnv::GetPostMaster()->Stop();\n  };\n  std::unique_ptr<void, decltype(stopPostMaster)> stopPostMasterDeleter((void *)1, stopPostMaster);\n\n  // std::unique_ptr<void, void(*)(void *)> libShutDown(nullptr, [](void *){google::protobuf::ShutdownProtobufLibrary();});\n  // std::unique_ptr<void, void(*)(void *)> stopPostMaster(nullptr, [](void *){XrdCl::DefaultEnv::GetPostMaster()->Stop();});\n  atexit([]{google::protobuf::ShutdownProtobufLibrary();});\n  // atexit([]{XrdCl::DefaultEnv::GetPostMaster()->Stop();});\n\n  char* line, *s;\n  serveruri = (char*) \"root://localhost\";\n  // Enable fork handlers for XrdCl\n  XrdCl::Env* env = XrdCl::DefaultEnv::GetEnv();\n  env->PutInt(\"RunForkHandler\", 1);\n  env->PutInt(\"RequestTimeout\", 900);\n  env->PutInt(\"StreamTimeout\", 1200);\n\n  if (getenv(\"EOS_MGM_URL\")) {\n    serveruri = getenv(\"EOS_MGM_URL\");\n\n    if (serveruri == \"ipc://\") {\n      // set the default ipc pipe\n      serveruri = \"ipc:///var/eos/md/.admin_socket:1094\";\n    }\n  }\n\n  gGlobalOpts.mMgmUri = serveruri.c_str();\n  XrdOucString urole = \"\";\n  XrdOucString grole = \"\";\n  bool selectedrole = false;\n  int argindex = 1;\n  int retc = system(\"test -t 0 && test -t 1\");\n\n  if (getenv(\"EOS_CONSOLE_DEBUG\")) {\n    global_debug = true;\n    gGlobalOpts.mDebug = true;\n  }\n\n  if (!retc) {\n    hasterminal = true;\n    global_highlighting = true;\n    interactive = true;\n  } else {\n    hasterminal = false;\n    global_highlighting = false;\n    interactive = false;\n  }\n\n  if (argc > 1) {\n    XrdOucString in1 = argv[argindex];\n\n    if (in1 == \"__complete\") {\n      return RunShellCompletion(argc, argv, argindex + 1);\n    }\n\n    if (in1.beginswith(\"-\")) {\n      if ((in1 != \"--help\") &&\n          (in1 != \"--version\") &&\n          (in1 != \"--batch\") &&\n          (in1 != \"--role\") &&\n          (in1 != \"--json\") &&\n          (in1 != \"--app\") &&\n          (in1 != \"-h\") &&\n          (in1 != \"-b\") &&\n          (in1 != \"-v\") &&\n          (in1 != \"-s\") &&\n          (in1 != \"-j\") &&\n          (in1 != \"-a\") &&\n          (in1 != \"-r\")) {\n        usage();\n        exit(-1);\n      }\n    }\n\n    if ((in1 == \"--help\") || (in1 == \"-h\")) {\n      usage();\n      exit(-1);\n    }\n\n    if ((in1 == \"-s\")) {\n      return RunRegisteredCommand(\"status\", {});\n    }\n\n    if ((in1 == \"--version\") || (in1 == \"-v\")) {\n      fprintf(stderr, \"EOS %s (2026)\\n\\n\", VERSION);\n      fprintf(stderr, \"Developed by the CERN IT Storage Group\\n\");\n      exit(0);\n    }\n\n    if ((in1 == \"--batch\") || (in1 == \"-b\")) {\n      interactive = false;\n      global_highlighting = false;\n      argindex++;\n      in1 = argv[argindex];\n    }\n\n    if ((in1 == \"--json\") || (in1 == \"-j\")) {\n      interactive = false;\n      global_highlighting = false;\n      json = true;\n      argindex++;\n      in1 = argv[argindex];\n      gGlobalOpts.mJsonFormat = true;\n    }\n\n    if ((in1 == \"fuse\")) {\n      interactive = false;\n      global_highlighting = false;\n    }\n\n    if ((in1 == \"--role\") || (in1 == \"-r\")) {\n      urole = argv[argindex + 1];\n      grole = argv[argindex + 2];\n      in1 = argv[argindex + 3];\n      argindex += 3;\n      // execute the role function\n      XrdOucString cmdline = \"role \";\n      cmdline += urole;\n      cmdline += \" \";\n      cmdline += grole;\n      in1 = argv[argindex];\n\n      if (in1.length()) {\n        silent = true;\n      }\n\n      execute_line((char*) cmdline.c_str());\n\n      if (in1.length()) {\n        silent = false;\n      }\n\n      selectedrole = true;\n    }\n\n    if ((in1 == \"--app\") || (in1 == \"-a\")) {\n      app = argv[argindex + 1];\n      in1 = argv[argindex + 2];\n      argindex += 2;\n      setenv(\"EOSAPP\", app.c_str(), 1);\n    }\n\n    app = getenv(\"EOSAPP\");\n\n    if ((in1 == \"--batch\") || (in1 == \"-b\")) {\n      interactive = false;\n      argindex++;\n      in1 = argv[argindex];\n    }\n\n    if ((in1 == \"cp\") || (in1 == \"rclone\")) {\n      interactive = false;\n      global_highlighting = false;\n    }\n\n    if ((in1 == \"fuse\")) {\n      interactive = false;\n    }\n\n    if (in1.beginswith(\"root://\")) {\n      serveruri = argv[argindex];\n      gGlobalOpts.mMgmUri = serveruri.c_str();\n      argindex++;\n      in1 = argv[argindex];\n    }\n\n    if (in1.beginswith(\"ipc://\")) {\n      serveruri = argv[argindex];\n\n      if (serveruri == \"ipc://\") {\n        // set the default ipc pipe\n        serveruri = \"ipc:///var/eos/md/.admin_socket:1094\";\n      }\n\n      gGlobalOpts.mMgmUri = serveruri.c_str();\n      argindex++;\n      in1 = argv[argindex];\n    }\n\n    if (in1.length()) {\n      // check if this is a file (workaround for XrdOucString bug\n      if ((in1.length() > 5) && (in1.endswith(\".eosh\")) &&\n          (!access(in1.c_str(), R_OK))) {\n        // this is a script file\n        char str[16384];\n        std::fstream file_op(in1.c_str(), std::ios::in);\n\n        while (!file_op.eof()) {\n          file_op.getline(str, 16384);\n          XrdOucString cmdline = \"\";\n          cmdline = str;\n\n          if (!cmdline.length()) {\n            break;\n          }\n\n          while (cmdline.beginswith(\" \")) {\n            cmdline.erase(0, 1);\n          }\n\n          while (cmdline.endswith(\" \")) {\n            cmdline.erase(cmdline.length() - 1, 1);\n          }\n\n          execute_line((char*) cmdline.c_str());\n        }\n\n        file_op.close();\n        exit(0);\n      } else {\n        XrdOucString cmdline = \"\";\n\n        // enclose all arguments except first in quotes\n        for (int i = argindex; i < argc; i++) {\n          if (i == argindex) {\n            cmdline += argv[i];\n          } else {\n            std::stringstream ss;\n            ss << std::quoted(argv[i]);\n            cmdline += \" \";\n            cmdline += ss.str().c_str();\n          }\n        }\n\n        if ((!selectedrole) && (!getuid()) &&\n            (serveruri.beginswith(\"root://localhost\"))) {\n          // we are root, we always select also the root role by default\n          XrdOucString cmdline = \"role 0 0 \";\n          silent = true;\n          execute_line((char*) cmdline.c_str());\n          silent = false;\n        }\n\n        // strip leading and trailing white spaces\n        while (cmdline.beginswith(\" \")) {\n          cmdline.erase(0, 1);\n        }\n\n        while (cmdline.endswith(\" \")) {\n          cmdline.erase(cmdline.length() - 1, 1);\n        }\n\n        execute_line((char*) cmdline.c_str());\n        exit(global_retc);\n      }\n    }\n  }\n\n  // By default select the root role if we are root@localhost\n  if ((!selectedrole) && (!getuid()) &&\n      (serveruri.beginswith(\"root://localhost\"))) {\n    // we are root, we always select also the root role by default\n    XrdOucString cmdline = \"role 0 0 \";\n    silent = true;\n    execute_line((char*) cmdline.c_str());\n    silent = false;\n  }\n\n  /* install a shutdown handler */\n  //signal(SIGINT, exit_handler);\n\n  if (!interactive) {\n    textnormal = \"\";\n    textblack = \"\";\n    textred = \"\";\n    textrederror = \"\";\n    textblueerror = \"\";\n    textgreen = \"\";\n    textyellow = \"\";\n    textblue = \"\";\n    textbold = \"\";\n    textunbold = \"\";\n  }\n\n  if (interactive) {\n    fprintf(stderr,\n            \"# ---------------------------------------------------------------------------\\n\");\n    fprintf(stderr, \"# EOS  Copyright (C) 2011-2025 CERN/Switzerland\\n\");\n    fprintf(stderr,\n            \"# This program comes with ABSOLUTELY NO WARRANTY; for details type `license'.\\n\");\n    fprintf(stderr,\n            \"# This is free software, and you are welcome to redistribute it \\n\");\n    fprintf(stderr, \"# under certain conditions; type `license' for details.\\n\");\n    fprintf(stderr,\n            \"# ---------------------------------------------------------------------------\\n\");\n    execute_line((char*) \"motd\");\n    execute_line((char*) \"version\");\n  }\n\n// bump up the filedescriptor limits\n  {\n    int fdlimit = 4096;\n    struct rlimit newrlimit;\n    newrlimit.rlim_cur = fdlimit;\n    newrlimit.rlim_max = fdlimit;\n\n    if ((setrlimit(RLIMIT_NOFILE, &newrlimit) != 0) && (!geteuid())) {\n      fprintf(stderr, \"warning: unable to set fd limit to %d - errno %d\\n\",\n              fdlimit, errno);\n    }\n  }\n  char prompt[4096];\n\n  sprintf(prompt, \"%sEOS Console%s [%s%s%s] |> \", textbold.c_str(),\n          textunbold.c_str(), textred.c_str(), serveruri.c_str(), textnormal.c_str());\n\n  // Bind our completer\n  rl_readline_name = (char*) \"EOS Console\";\n  rl_attempted_completion_function = eos_console_completion;\n  rl_completer_quote_characters = (char*) \"\\\"\";\n  rl_completion_append_character = '\\0';\n\n  if (getenv(\"EOS_HISTORY_FILE\")) {\n    historyfile = getenv(\"EOS_HISTORY_FILE\");\n  } else {\n    if (getenv(\"HOME\")) {\n      historyfile = getenv(\"HOME\");\n      historyfile += \"/.eos_history\";\n    }\n  }\n\n  if (getenv(\"EOS_PWD_FILE\")) {\n    pwdfile = getenv(\"EOS_PWD_FILE\");\n  } else {\n    if (getenv(\"HOME\")) {\n      pwdfile = getenv(\"HOME\");\n      pwdfile += \"/.eos_pwd\";\n    }\n  }\n\n  read_history(historyfile.c_str());\n\n  // load the last used current working directory\n  if (interactive) {\n    read_pwdfile();\n  }\n\n  // Loop reading and executing lines until the user quits.\n  for (; done == 0;) {\n    char prompt[4096];\n\n    sprintf(prompt, \"%sEOS Console%s [%s%s%s] |%s> \", textbold.c_str(),\n            textunbold.c_str(), textred.c_str(), serveruri.c_str(), textnormal.c_str(),\n            gPwd.c_str());\n\n    signal(SIGINT, jump_handler);\n\n    if (sigsetjmp(sigjump_buf, 1)) {\n      signal(SIGINT, jump_handler);\n      fprintf(stdout, \"\\n\");\n    }\n\n    line = readline(prompt);\n    signal(SIGINT, exit_handler);\n\n    if (!line) {\n      fprintf(stdout, \"\\n\");\n      break;\n    }\n\n    // Remove leading and trailing whitespace from the line. Then, if there\n    // is anything left, add it to the history list and execute it.\n    s = stripwhite(line);\n\n    if (*s) {\n      add_history(s);\n      // 20 minutes timeout for commands ... that is long !\n      signal(SIGALRM, exit_handler);\n      alarm(3600);\n      execute_line(s);\n      alarm(0);\n      std::cout << std::flush;\n      std::cerr << std::flush;\n      fflush(stdout);\n      fflush(stderr);\n    }\n\n    free(line);\n  }\n\n  write_history(historyfile.c_str());\n  signal(SIGINT, SIG_IGN);\n  exit(0);\n}\n\n//------------------------------------------------------------------------------\n// Command line execution function\n//------------------------------------------------------------------------------\nint\nexecute_line(char* line)\n{\n  std::string comment;\n  std::string line_without_comment = parse_comment(line, comment);\n\n  if (line_without_comment.empty()) {\n    fprintf(stderr, \"error: syntax for comment is '<command> <args> \"\n            \"--comment \\\"<comment>\\\"'\\n\");\n    global_retc = -1;\n    return (-1);\n  }\n\n  global_comment = comment.c_str();\n  gGlobalOpts.mComment = comment.c_str();\n  // Isolate the command word from the rest of the arguments\n  std::list<std::string> tokens = eos::common::StringTokenizer::split\n                                  <std::list<std::string>>(line_without_comment.c_str(), ' ');\n\n  if (!tokens.size()) {\n    global_retc = -1;\n    return (-1);\n  }\n\n  // Initialize registry on first use\n  EnsureNativeCommandRegistryInitialized();\n\n  std::string cmdName = *tokens.begin();\n  IConsoleCommand* icmd = CommandRegistry::instance().find(cmdName);\n  if (!icmd) {\n    fprintf(stderr, \"%s: No such command for EOS Console.\\n\", cmdName.c_str());\n    global_retc = -1;\n    return (-1);\n  }\n\n  // Extract arguments vector from full command line\n  line_without_comment = line_without_comment.substr(tokens.begin()->size());\n  eos::common::trim(line_without_comment);\n  std::string rest = line_without_comment;\n  // Quote-aware tokenization: preserve spaces within quoted strings, drop quotes\n  std::vector<std::string> argsVec;\n  {\n    bool inD = false, inS = false;\n    std::string cur;\n    for (size_t i = 0; i < rest.size(); ++i) {\n      char c = rest[i];\n      if (c == '\\\\' && i + 1 < rest.size()) {\n        char next = rest[i + 1];\n        if (next == '\"' || next == '\\'') {\n          // Preserve escaped quotes as literals and avoid toggling state.\n          cur.push_back(c);\n          cur.push_back(next);\n          ++i;\n          continue;\n        }\n      }\n      if (c == '\"' && !inS) {\n        inD = !inD;\n        continue;\n      }\n      if (c == '\\'' && !inD) {\n        inS = !inS;\n        continue;\n      }\n      if (c == ' ' && !inD && !inS) {\n        if (!cur.empty()) {\n          argsVec.push_back(cur);\n          cur.clear();\n        }\n        continue;\n      }\n      cur.push_back(c);\n    }\n    if (!cur.empty()) {\n      argsVec.push_back(cur);\n    }\n  }\n\n  // Check MGM availability\n  if (icmd->requiresMgm(rest) &&\n      !CheckMgmOnline(serveruri.c_str())) {\n    std::cerr << \"error: MGM \" << serveruri.c_str()\n              << \" not online/reachable\" << std::endl;\n    exit(ENONET);\n  }\n\n  CommandContext ctx;\n  ctx.serverUri = serveruri.c_str();\n  ctx.globalOpts = &gGlobalOpts;\n  ctx.json = json;\n  ctx.silent = silent;\n  ctx.interactive = interactive;\n  ctx.timing = timing;\n  ctx.userRole = user_role.c_str();\n  ctx.groupRole = group_role.c_str();\n  ctx.clientCommand = &client_command;\n  ctx.outputResult = &output_result;\n\n  return icmd->run(argsVec, ctx);\n}\n\n//------------------------------------------------------------------------------\n// Strip whitespace from the start and end of STRING.  Return a pointer to\n// STRING.\n//------------------------------------------------------------------------------\nchar*\nstripwhite(char* string)\n{\n  char* s, *t;\n\n  for (s = string; (*s) == ' '; s++)\n    ;\n\n  if (*s == 0) {\n    return (s);\n  }\n\n  t = s + strlen(s) - 1;\n\n  while (t > s && ((*t) == ' ')) {\n    t--;\n  }\n\n  *++t = '\\0';\n  return s;\n}\n\n//------------------------------------------------------------------------------\n// Parse the command line, extracts the comment\n// and returns the line without the comment in it\n//------------------------------------------------------------------------------\nstd::string\nparse_comment(const char* line, std::string& comment)\n{\n  std::string exec_line = line;\n  // Commands issued from the EOS shell do not encase arguments in quotes\n  // whereas commands issued from the terminal do\n  size_t cbpos = exec_line.find(\"\\\"--comment\\\"\");\n  int size = 11;\n\n  if (cbpos == std::string::npos) {\n    cbpos = exec_line.find(\"--comment\");\n    size = 9;\n  }\n\n  if (cbpos != std::string::npos) {\n    // Check that line doesn't end with comment flag\n    if (cbpos + size == exec_line.length()) {\n      return std::string();\n    }\n\n    // Check we found a complete word\n    if (exec_line[cbpos + size] == ' ') {\n      // Check we have comment text\n      if (cbpos + size + 3 >= exec_line.length()) {\n        return std::string();\n      }\n\n      // Comment text should always start with quotes: --comment \"<comment>\"\n      if (exec_line[cbpos + size + 1] == '\"') {\n        size_t cepos = exec_line.find('\"', cbpos + size + 2);\n\n        // Comment text should always end with quotes: --comment \"<comment>\"\n        if (cepos != std::string::npos) {\n          comment = exec_line.substr(cbpos + size + 1, cepos -\n                                     (cbpos + size)).c_str();\n          exec_line.erase(cbpos, cepos - cbpos + 1);\n        } else {\n          return std::string();\n        }\n      } else {\n        return std::string();\n      }\n    }\n  }\n\n  return exec_line;\n}\n\n//------------------------------------------------------------------------------\n// Given an input string, return the appropriate path identifier.\n//------------------------------------------------------------------------------\nstd::string PathIdentifier(const char* in, bool escapeand)\n{\n  std::string input;\n\n  if (in == nullptr) {\n    return input;\n  }\n\n  input = in;\n\n  if ((input.find(\"fid:\") == 0) || (input.find(\"fxid:\") == 0) ||\n      (input.find(\"cid:\") == 0) || (input.find(\"cxid:\") == 0) ||\n      (input.find(\"pid:\") == 0) || (input.find(\"pxid:\") == 0)) {\n    return in;\n  }\n\n  input = abspath(in);\n\n  if (escapeand) {\n    input = eos::common::StringConversion::SealXrdPath(input);\n  }\n\n  return input;\n}\n\n//------------------------------------------------------------------------------\n// Check if input matches pattern and extract the file id if possible\n//------------------------------------------------------------------------------\nbool RegWrapDenominator(XrdOucString& input, const std::string& key)\n{\n  try {\n    RegexUtil reg;\n    reg.SetRegex(key);\n    reg.SetOrigin(input.c_str());\n    reg.initTokenizerMode();\n    std::string temp = reg.Match();\n    auto pos = temp.find(':');\n    temp = std::string(temp.begin() + pos + 1, temp.end());\n    input = XrdOucString(temp.c_str());\n    return true;\n  } catch (std::string& e) {\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Extract file id specifier if input is in one of the following formats:\n// fxid:<hex_id> | fid:<dec_id>\n//------------------------------------------------------------------------------\nbool Path2FileDenominator(XrdOucString& input)\n{\n  if (RegWrapDenominator(input, \"fxid:[a-fA-F0-9]+$\")) {\n    std::string temp = std::to_string(strtoull(input.c_str(), 0, 16));\n    input = XrdOucString(temp.c_str());\n    return true;\n  }\n\n  return RegWrapDenominator(input, \"fid:[0-9]+$\");\n}\n\n//------------------------------------------------------------------------------\n// Extract file id specifier if input is in one of the following formats:\n// fxid:<hex_id> | fid:<dec_id>\n//------------------------------------------------------------------------------\nbool Path2FileDenominator(XrdOucString& input, unsigned long long& id)\n{\n  if (RegWrapDenominator(input, \"fxid:[a-fA-F0-9]+$\")) {\n    id = strtoull(input.c_str(), nullptr, 16);\n    return true;\n  } else if (RegWrapDenominator(input, \"fid:[0-9]+$\")) {\n    id = strtoull(input.c_str(), nullptr, 10);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Extract container id specifier if input is in one of the following formats:\n// cxid:<hex_id> | cid:<dec_id>\n//------------------------------------------------------------------------------\nbool Path2ContainerDenominator(XrdOucString& input)\n{\n  if (RegWrapDenominator(input, \"cxid:[a-fA-F0-9]+$\")) {\n    std::string temp = std::to_string(strtoull(input.c_str(), 0, 16));\n    input = XrdOucString(temp.c_str());\n    return true;\n  }\n\n  return RegWrapDenominator(input, \"cid:[0-9]+$\");\n}\n\n//------------------------------------------------------------------------------\n// Extract container id specifier if input is in one of the following formats:\n// cxid:<hex_id> | cid:<dec_id>\n//------------------------------------------------------------------------------\nbool Path2ContainerDenominator(XrdOucString& input, unsigned long long& id)\n{\n  if (RegWrapDenominator(input, \"cxid:[a-fA-F0-9]+$\")) {\n    id = strtoull(input.c_str(), nullptr, 16);\n    return true;\n  } else if (RegWrapDenominator(input, \"cid:[0-9]+$\")) {\n    id = strtoull(input.c_str(), nullptr, 10);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Check whether the given command performs an MGM call\n//------------------------------------------------------------------------------\nbool RequiresMgm(const std::string& name, const std::string& args)\n{\n  if ((name == \"clear\") || (name == \"console\") || (name == \"cp\") ||\n      (name == \"exit\") || (name == \"help\") || (name == \"json\") ||\n      (name == \"pwd\") || (name == \"quit\") || (name == \"role\") ||\n      (name == \"silent\") || (name == \"timing\") || (name == \"?\") ||\n      (name == \".q\") || (name == \"daemon\") || (name == \"scitoken\")) {\n    return false;\n  }\n\n  return !wants_help(args.c_str());\n}\n\n//------------------------------------------------------------------------------\n// Check if MGM is online and reachable\n//------------------------------------------------------------------------------\nbool CheckMgmOnline(const std::string& uri)\n{\n  if (uri.substr(0, 6) == \"ipc://\") {\n    return true;\n  }\n\n  uint16_t timeout = 10;\n  XrdCl::URL url(uri);\n\n  if (!url.IsValid()) {\n    std::cerr << \"error: \" << uri << \" not a valid URL\" << std::endl;\n    return false;\n  }\n\n  XrdCl::FileSystem fs(url);\n  XrdCl::XRootDStatus status = fs.Ping(timeout);\n  return status.IsOK();\n}\n\n//------------------------------------------------------------------------------\n// Guess a default 'route' e.g. home directory\n//------------------------------------------------------------------------------\nstd::string DefaultRoute()\n{\n  std::string default_route = \"\";\n\n  // add a default 'route' for the command\n  if (getenv(\"EOSHOME\")) {\n    default_route = getenv(\"EOSHOME\");\n  } else {\n    char default_home[4096];\n    std::string username;\n\n    if (getenv(\"EOSUSER\")) {\n      username = getenv(\"EOSUSER\");\n    }\n\n    if (getenv(\"USER\")) {\n      username = getenv(\"USER\");\n    }\n\n    if (username.length()) {\n      snprintf(default_home, sizeof(default_home), \"/eos/user/%s/%s/\",\n               username.substr(0, 1).c_str(), username.c_str());\n      // @note route warning is no longer displayed\n      // fprintf(stderr,\n      //         \"# pre-configuring default route to %s\\n# -use $EOSHOME variable to override\\n\",\n      //         default_home);\n      default_route = default_home;\n    }\n  }\n\n  return default_route;\n}\n\n//------------------------------------------------------------------------------\n// Load current filesystems into a map\n//------------------------------------------------------------------------------\nint filesystems::Load(bool verbose)\n{\n  struct stat buf;\n  std::string cachefile = \"/tmp/.eos.filesystems.\";\n  XrdOucString serverflat = serveruri;\n\n  while (serverflat.replace(\"/\", \":\")) {}\n\n  cachefile += serverflat.c_str();\n  cachefile += std::to_string(geteuid());\n  bool use_cache = false;\n\n  if (!::stat(cachefile.c_str(), &buf)) {\n    if ((buf.st_mtime + 3600) > time(NULL)) {\n      use_cache = true;\n    }\n  }\n\n  std::string cachefiletmp = cachefile + \".tmp\";\n  int retc = 0;\n\n  if (use_cache) {\n    std::string out;\n    rstdout = eos::common::StringConversion::LoadFileIntoString(cachefile.c_str(),\n              out);\n  } else {\n    retc = RunRegisteredCommand(\"fs\", {\"-m\", \"-s\"});\n    std::string in = rstdout.c_str();\n    eos::common::StringConversion::SaveStringIntoFile(cachefiletmp.c_str(), in);\n    ::rename(cachefiletmp.c_str(), cachefile.c_str());\n  }\n\n  if (!retc) {\n    std::istringstream f(rstdout.c_str());\n    std::string line;\n\n    while (std::getline(f, line)) {\n      std::map<std::string, std::string> fs;\n      eos::common::StringConversion::GetKeyValueMap(line.c_str(),\n          fs, \"=\", \" \");\n      std::string hostport = fs[\"host\"] + \":\" + fs[\"port\"];\n      fs[\"hostport\"] = hostport;\n      fsmap[std::stoi(fs[\"id\"])] = fs;\n\n      if (verbose) {\n        fprintf(stdout, \"[fs] id=%06d %s\\n\", std::stoi(fs[\"id\"]), hostport.c_str());\n      }\n    }\n\n    fprintf(stdout, \"# loaded %lu filesystems\\n\", fsmap.size());\n    fflush(stdout);\n  }\n\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Connect current filesystems via XrdCl objects\n//------------------------------------------------------------------------------\n\nint filesystems::Connect()\n{\n  for (auto it = fsmap.begin(); it != fsmap.end() ; ++it) {\n    if (!clientmap.count(it->first)) {\n      XrdCl::URL url(std::string(\"root://\") + it->second[\"id\"] + \"@\" +\n                     it->second[\"hostport\"] + \"//dummy\");\n      // make a new connection\n      clientmap[it->first] = std::make_shared<XrdCl::FileSystem> (url);\n    }\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Find files\n//------------------------------------------------------------------------------\nint files::Find(const char* path, bool verbose)\n{\n  fprintf(stdout, \"# finding files ...\\n\");\n  fflush(stdout);\n  std::string cmd = \"-f --nrep --fid --fs --checksum --size \";\n  cmd += path;\n  bool old_silent = silent;\n  silent = true;\n  int retc = RunRegisteredCommand(\"find\",\n                                  {\"-f\", \"--nrep\", \"--fid\", \"--fs\", \"--checksum\",\n                                   \"--size\", path});\n  silent = old_silent;\n\n  if (!retc) {\n    std::istringstream f(rstdout.c_str());\n    std::string line;\n\n    while (std::getline(f, line)) {\n      std::map<std::string, std::string> f;\n      eos::common::StringConversion::GetKeyValueMap(line.c_str(),\n          f, \"=\", \" \");\n      filemap[f[\"path\"]].size = std::stol(f[\"size\"]);\n      filemap[f[\"path\"]].nrep = std::stoi(f[\"nrep\"]);\n      filemap[f[\"path\"]].checksum = f[\"checksum\"];\n      filemap[f[\"path\"]].hexid = f[\"fid\"];\n      std::vector<std::string> tokens;\n      eos::common::StringConversion::Tokenize(f[\"fsid\"], tokens, \",\");\n\n      for (size_t i = 0 ; i < tokens.size(); ++i) {\n        filemap[f[\"path\"]].locations.insert(std::stoi(tokens[i]));\n      }\n\n      if (verbose) {\n        fprintf(stdout,\n                \"[file] path=%s hexid=%s checksum=%s nrep=%d size=%lu locations=%lu\\n\",\n                f[\"path\"].c_str(),\n                filemap[f[\"path\"]].hexid.c_str(),\n                filemap[f[\"path\"]].checksum.c_str(),\n                filemap[f[\"path\"]].nrep,\n                filemap[f[\"path\"]].size,\n                filemap[f[\"path\"]].locations.size());\n      }\n    }\n  }\n\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Lookup locations\n//------------------------------------------------------------------------------\n\nint files::Lookup(filesystems& fsmap, bool verbose)\n{\n  size_t max_queue = 49512;\n  size_t stat_timeout = 300;\n  size_t n_timeouts = 0;\n  size_t n_missing = 0;\n  size_t n_other = 0;\n  std::list< SyncResponseHandler*> callback_queue;\n  std::map<uint64_t, SyncResponseHandler*> callbacks;\n  size_t count = 0;\n\n  for (auto it = filemap.begin(); it != filemap.end(); ++it) {\n    count++;\n\n    if (!(count % 100)) {\n      fprintf(stdout,\n              \"# progress %.01f %% [ %lu/%lu ] [ unix:%lu ] [ cb:%lu ] [ to:%lu ] [ miss:%lu ] [ oth:%lu ] \\n\",\n              100.0 * count / filemap.size(), count, filemap.size(), time(NULL),\n              callbacks.size(), n_timeouts, n_missing, n_other);\n      fflush(stdout);\n    }\n\n    for (auto loc = it->second.locations.begin(); loc != it->second.locations.end();\n         ++loc) {\n      if (!fsmap.fs().count(*loc)) {\n        fprintf(stderr, \"[ERROR] [SHADOWFS] fs=%d path=%s\\n\", *loc, it->first.c_str());\n        continue;\n      }\n\n      std::string prefix = fsmap.fs()[*loc][\"path\"];\n      std::string fullpath =\n        eos::common::FileId::FidPrefix2FullPath(it->second.hexid.c_str(),\n            prefix.c_str());\n\n      if (verbose) {\n        fprintf(stdout, \"[file] path=%s loc=%d fstpath=%s\\n\", it->first.c_str(), *loc,\n                fullpath.c_str());\n      }\n\n      bool cleaning = false;\n\n      while (callbacks.size() > max_queue || (cleaning &&\n                                              (callbacks.size() > max_queue / 2))) {\n        //  fprintf(stderr,\"waiting 1 %lu\\n\", callbacks.size());\n        //  cleaning = true;\n        for (auto it = callback_queue.begin(); it != callback_queue.end(); ++it) {\n          if ((*it)->HasStatus()) {\n            (*it)->WaitForResponse();\n\n            if ((*it)->GetStatus()->IsOK()) {\n              StatInfo* statinfo;\n              (*it)->GetResponse()->Get(statinfo);\n\n              //        fprintf(stderr,\"path=%s size=%ld\\n\", (*it)->GetPath(), statinfo->GetSize());\n              if (statinfo->GetSize() != filemap[(*it)->GetPath()].size) {\n                filemap[(*it)->GetPath()].wrongsize_locations.insert((*it)->GetFsid());\n              }\n\n              delete statinfo;\n            } else {\n              if ((*it)->GetStatus()->code == XrdCl::errOperationExpired) {\n                filemap[(*it)->GetPath()].expired = true;\n                n_timeouts++;\n              } else {\n                if (((*it)->GetStatus()->code == XrdCl::errErrorResponse) &&\n                    ((*it)->GetStatus()->errNo == kXR_NotFound)) {\n                  filemap[(*it)->GetPath()].missing_locations.insert((*it)->GetFsid());\n                  n_missing++;\n                } else {\n                  n_other++;\n                }\n              }\n            }\n\n            callbacks.erase((uint64_t)*it);\n            delete *it;\n            callback_queue.erase(it);\n            break;\n          } else {\n            size_t age = (*it) -> GetAge();\n\n            if (age > (stat_timeout + 60))  {\n              fprintf(stderr, \"pending request since %lu seconds - path=%s fsid=%d\\n\", age,\n                      (*it)->GetPath(), (*it)->GetFsid());\n            }\n          }\n        }\n\n        //  fprintf(stderr,\"waiting 2 %lu\\n\", callbacks.size());\n      }\n\n      SyncResponseHandler* cb = new SyncResponseHandler(it->first.c_str(), *loc);\n      callback_queue.push_back(cb);\n      callbacks[(uint64_t)cb] = cb;\n\n      if (verbose) {\n        fprintf(stdout, \"sending to %d %s count=%lu\\n\", *loc,\n                fsmap.fs()[*loc][\"hostport\"].c_str(),\n                fsmap.clients().count(*loc));\n      }\n\n      XRootDStatus status = fsmap.clients() [*loc]->Stat(fullpath.c_str(), cb,\n                            stat_timeout);\n\n      if (!status.IsOK()) {\n        fprintf(stderr, \"error: failed to send path=%s to %d : %s\\n\", cb->GetPath(),\n                cb->GetFsid(), status.ToString().c_str());\n        callback_queue.pop_back();\n        callbacks.erase((uint64_t)cb);\n      }\n    }\n  }\n\n  // wait for call-backs to be returned or time-out\n  while (callbacks.size()) {\n    for (auto it = callback_queue.begin(); it != callback_queue.end(); ++it) {\n      if ((*it)->HasStatus()) {\n        (*it)->WaitForResponse();\n\n        if ((*it)->GetStatus()->IsOK()) {\n          StatInfo* statinfo;\n          (*it)->GetResponse()->Get(statinfo);\n\n          // fprintf(stderr,\"response=%llx path=%s size=%ld\\n\", (*it)->GetResponse(), (*it)->GetPath(), statinfo->GetSize());\n          if (statinfo->GetSize() != filemap[(*it)->GetPath()].size) {\n            filemap[(*it)->GetPath()].wrongsize_locations.insert((*it)->GetFsid());\n          }\n\n          delete statinfo;\n        } else {\n          if ((*it)->GetStatus()->code == XrdCl::errOperationExpired) {\n            fprintf(stderr, \"status=%s\\n\", (*it)->GetStatus()->ToString().c_str());\n            filemap[(*it)->GetPath()].expired = true;\n          } else {\n            if (((*it)->GetStatus()->code == XrdCl::errErrorResponse) &&\n                ((*it)->GetStatus()->errNo == kXR_NotFound)) {\n              filemap[(*it)->GetPath()].missing_locations.insert((*it)->GetFsid());\n              n_missing++;\n            } else {\n              n_other++;\n            }\n          }\n        }\n\n        if (verbose) {\n          fprintf(stderr, \"erasing callback %s %d\\n\", (*it)->GetPath(), (*it)->GetFsid());\n        }\n\n        callbacks.erase((uint64_t)*it);\n        delete *it;\n        callback_queue.erase(it);\n        break;\n      }\n\n      size_t age = (*it) -> GetAge();\n\n      if (age > (stat_timeout + 60))  {\n        fprintf(stderr, \"pending request since %lu seconds - path=%s fsid=%d\\n\", age,\n                (*it)->GetPath(), (*it)->GetFsid());\n      }\n    }\n  }\n\n  fprintf(stdout,\n          \"# progress %.01f %% [ %lu/%lu ] [ unix:%lu ] [ cb:%lu ] [ to:%lu ] [ miss:%lu ] [ oth:%lu ] \\n\",\n          100.0 * count / filemap.size(), count, filemap.size(), time(NULL),\n          callbacks.size(), n_timeouts, n_missing, n_other);\n  fflush(stdout);\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Report files\n//------------------------------------------------------------------------------\nint files::Report(size_t expect_nrep)\n{\n  size_t n_missing = 0;\n  size_t n_size = 0;\n  size_t n_nrep = 0;\n  size_t n_expired = 0;\n  size_t n_lost = 0;\n\n  for (auto it = filemap.begin(); it != filemap.end(); ++it) {\n    if (it->second.expired) {\n      fprintf(stderr, \"[ERROR] [ EXPIRED ] path=%s nrep=%d \\n\", it->first.c_str(),\n              it->second.nrep);\n      n_expired++;\n    } else {\n      for (auto loc = it->second.missing_locations.begin();\n           loc != it->second.missing_locations.end(); ++loc) {\n        fprintf(stderr, \"[ERROR] [ MISSING ] path=%s nrep=%d loc=%d \\n\",\n                it->first.c_str(), it->second.nrep, *loc);\n        n_missing++;\n      }\n\n      for (auto loc = it->second.wrongsize_locations.begin();\n           loc != it->second.wrongsize_locations.end(); ++loc) {\n        fprintf(stderr, \"[ERROR] [ SIZE    ] path=%s loc=%d\\n\", it->first.c_str(),\n                *loc);\n        n_size++;\n      }\n\n      if (expect_nrep) {\n        if (it->second.locations.size() != expect_nrep) {\n          fprintf(stderr, \"[ERROR] [ NREP    ] path=%s nrep=%lu expected=%lu\\n\",\n                  it->first.c_str(), it->second.locations.size(), expect_nrep);\n          n_nrep++;\n        }\n      }\n    }\n\n    if (it->second.missing_locations.size() == it->second.locations.size()) {\n      fprintf(stderr, \"[ERROR] [ LOST    ] path=%s nrep=%lu missing=%lu\\n\",\n              it->first.c_str(), it->second.locations.size(),\n              it->second.missing_locations.size());\n      n_lost++;\n    }\n  }\n\n  fprintf(stderr,\n          \"[SUMMARY] expired:%lu missing:%lu size:%lu nrep:%lu lost:%lu total:%lu\\n\",\n          n_expired, n_missing, n_size, n_nrep, n_lost, filemap.size());\n  return 0;\n}\n"
  },
  {
    "path": "console/ConsoleMain.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file ConsoleMain.hh\n//! @author Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"console/GlobalOptions.hh\"\n#include \"common/StringConversion.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdCl/XrdClXRootDResponses.hh>\n#include <vector>\n#include <math.h>\n#include <map>\n#include <iostream>\n#include <memory>\n\nclass XrdOucEnv;\n\nextern const char* abspath(const char* in);\nextern XrdOucString gPwd;\nextern XrdOucString rstdout;\nextern XrdOucString rstderr;\nextern XrdOucString user_role;\nextern XrdOucString group_role;\nextern XrdOucString serveruri;\nextern XrdOucString global_comment;\nextern XrdOucString pwdfile;\nextern void exit_handler(int a);\nextern int global_retc;\nextern bool global_highlighting;\nextern bool global_debug;\nextern bool interactive;\nextern bool hasterminal;\nextern bool silent;\nextern bool timing;\nextern bool pipemode;\nextern bool runpipe;\nextern bool ispipe;\nextern bool json;\nextern int output_result(XrdOucEnv* result, bool highlighting = true);\nextern void command_result_stdout_to_vector(std::vector<std::string>&\n    string_vector);\nextern XrdOucEnv* CommandEnv;\n// Structure used for the protobuf impelmentation to pass down the global\n// options that are set before parsing the actual command\nextern GlobalOptions gGlobalOpts;\n\nusing namespace XrdCl;\n\n//------------------------------------------------------------------------------\n//! Send client command to the MGM\n//!\n//! @param in command to be appended as opaque info to the XrdCl::File object\n//! @param is_admin if true execute as an admin command, otherwise as an user\n//!        command\n//!\n//! @return object containing the server response\n//------------------------------------------------------------------------------\nextern XrdOucEnv* client_command(XrdOucString& in, bool is_admin = false,\n                                 std::string* reply = nullptr);\n\ntypedef int CFunction(char*);\n//! Structure which contains information on the commands this program\n//! understands.\ntypedef struct {\n  char* name; /* User printable name of the function. */\n  CFunction* func; /* Function to call to do the job. */\n  char* doc; /* Documentation for this function.  */\n} COMMAND;\n\n//------------------------------------------------------------------------------\n//! Help flag filter function.\n//! Given a line containing command arguments, search for help keywords\n//! to determine whether help message should be displayed.\n//!\n//! @param args_line the arguments line which to filter\n//! @param no_h if true don't consider -h as requesting help e.g du -h\n//!\n//! @return true if help messaged should be displayed, false otherwise\n//------------------------------------------------------------------------------\nextern bool wants_help(const char* args_line, bool no_h = false);\n\nextern COMMAND commands[];\nextern int done;\n\nchar* stripwhite(char* string);\nstd::string parse_comment(const char* line, std::string& comment);\nCOMMAND* find_command(const char* command);\nint execute_line(char* line);\n\nint Run(int argc, char* argv[]);\n\n\n//------------------------------------------------------------------------------\n//! Given an input string, return the appropriate path identifier:\n//!   - string begins with fid:|fxid:|cid:|cxid:|pid:|pxid: then leave untouched\n//!   - otherwise -- return abspath of the given string\n//!\n//! @param in the string to process\n//! @param escapeand whether to replace '&' symbols with '#AND#'\n//!\n//! @return processed path identifier\n//------------------------------------------------------------------------------\nstd::string PathIdentifier(const char* in, bool escapeand = false);\n\n//------------------------------------------------------------------------------\n//! Check if input matches pattern and extract the file id if possible\n//!\n//! @param input input string which can also be a path\n//! @param pattern regular expression fxid:<hex_id> | fid: <dec_id>\n//!\n//! @return true if input matches pattern, false otherwise\n//------------------------------------------------------------------------------\nbool RegWrapDenominator(XrdOucString& input, const std::string& key);\n\n//------------------------------------------------------------------------------\n//! Extract file id specifier if input is in one of the following formats:\n//! fxid:<hex_id> | fid:<dec_id>\n//!\n//! @param input input following the above format or an actual path\n//!\n//! @return true if path is given as a file id specifier, otherwise false\n//------------------------------------------------------------------------------\nbool Path2FileDenominator(XrdOucString& path);\n\n//------------------------------------------------------------------------------\n//! Extract file id specifier if input is in one of the following formats:\n//! fxid:<hex_id> | fid:<dec_id>\n//!\n//! @param input input following the above format or an actual path\n//!\n//! @param id result with the extracted id\n//!\n//! @return true if path is given as a file id specifier, otherwise false\n//------------------------------------------------------------------------------\nbool Path2FileDenominator(XrdOucString& path, unsigned long long& id);\n\n//------------------------------------------------------------------------------\n//! Extract container id specifier if input is in one of the following formats:\n//! cxid:<hex_id> | cid:<dec_id>\n//!\n//! @param input input following the above format or an actual path\n//!\n//! @return true if path is given as a file id specifier, otherwise false\n//------------------------------------------------------------------------------\nbool Path2ContainerDenominator(XrdOucString& path);\n\n//------------------------------------------------------------------------------\n//! Extract container id specifier if input is in one of the following formats:\n//! cxid:<hex_id> | cid:<dec_id>\n//!\n//! @param input input following the above format or an actual path\n//!\n//! @param id result with the extracted id\n//!\n//! @return true if path is given as a file id specifier, otherwise false\n//------------------------------------------------------------------------------\nbool Path2ContainerDenominator(XrdOucString& path, unsigned long long& id);\n\n//------------------------------------------------------------------------------\n//! Check whether the given command performs an MGM call\n//!\n//! @param name command name\n//!\n//! @param args arguments string\n//!\n//! @return true if remote command, false otherwise\n//------------------------------------------------------------------------------\nbool RequiresMgm(const std::string& name, const std::string& args);\n\n//------------------------------------------------------------------------------\n//! Check if MGM is online and reachable\n//!\n//! @param uri where to connect to the MGM\n//!\n//! @return true if MGM is online, otherwise false\n//------------------------------------------------------------------------------\nbool CheckMgmOnline(const std::string& uri);\n\n\n//------------------------------------------------------------------------------\n//! Compute a default home directory or take it from the environment.\n//!\n//! @return guessed home path\n//------------------------------------------------------------------------------\nstd::string DefaultRoute();\n\n//------------------------------------------------------------------------------\n//! API to load the filesystem configuration of an instance\n//------------------------------------------------------------------------------\n\nclass filesystems\n{\npublic:\n  filesystems() {}\n  ~filesystems() {}\n\n  typedef std::map<int, std::map<std::string, std::string>> filesystemmap_t;\n  typedef std::map<int, std::shared_ptr<XrdCl::FileSystem>> clientmap_t;\n  int Connect();\n  int Load(bool verbose = false);\n\n  filesystemmap_t& fs()\n  {\n    return fsmap;\n  }\n  clientmap_t& clients()\n  {\n    return clientmap;\n  }\nprivate:\n  filesystemmap_t fsmap;\n  clientmap_t clientmap;\n};\n\nclass files\n{\npublic:\n  files() {}\n  ~files() {}\n\n  struct fileentry {\n    int expired;\n    int nrep;\n    size_t size;\n    std::string hexid;\n    std::set<int> locations;\n    std::string checksum;\n    std::set<int> missing_locations;\n    std::set<int> wrongsize_locations;\n  };\n\n  int Find(const char* path, bool verbose = false);\n  int Lookup(filesystems& fsmap, bool verbose = false);\n  size_t Size()\n  {\n    return filemap.size();\n  }\n  int Report(size_t expect_replica);\n\n  class SyncResponseHandler: public ResponseHandler\n  {\n  public:\n    SyncResponseHandler(const char* path, int fsid):\n      pStatus(0),\n      pResponse(0),\n      pCondVar(0),\n      pPath(path),\n      pFsid(fsid)\n    {\n      pStartTime = time(NULL);\n    }\n\n    virtual ~SyncResponseHandler() {}\n\n    virtual void HandleResponse(XRootDStatus* status,\n                                AnyObject*    response)\n    {\n      XrdSysCondVarHelper scopedLock(pCondVar);\n      pStatus = status;\n      pResponse = response;\n      pCondVar.Broadcast();\n    }\n\n    XRootDStatus* GetStatus()\n    {\n      return pStatus;\n    }\n\n    bool HasStatus()\n    {\n      XrdSysCondVarHelper scopedLock(pCondVar);\n\n      if (pStatus) {\n        return true;\n      } else {\n        return false;\n      }\n    }\n\n    AnyObject* GetResponse()\n    {\n      return pResponse;\n    }\n\n    void WaitForResponse()\n    {\n      XrdSysCondVarHelper scopedLock(pCondVar);\n\n      while (pStatus == 0) {\n        pCondVar.Wait();\n      }\n    }\n\n    const char* GetPath()\n    {\n      return pPath.c_str();\n    }\n\n    int         GetFsid()\n    {\n      return pFsid;\n    }\n\n    size_t      GetAge()\n    {\n      return time(NULL) - pStartTime;\n    }\n\n  private:\n    SyncResponseHandler(const SyncResponseHandler& other);\n    SyncResponseHandler& operator = (const SyncResponseHandler& other);\n\n    XRootDStatus*    pStatus;\n    AnyObject*       pResponse;\n    XrdSysCondVar    pCondVar;\n    std::string      pPath;\n    int              pFsid;\n    time_t           pStartTime;\n  };\n\nprivate:\n  std::map<std::string, struct fileentry> filemap;\n};\n\n"
  },
  {
    "path": "console/ConsoleMainExecutable.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ConsoleMainExecutable.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"ConsoleMain.hh\"\n\nint\nmain(int argc, char* argv[])\n{\n  return Run(argc, argv);\n}\n"
  },
  {
    "path": "console/GlobalOptions.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file GlobalOptions.hh\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include <string>\n\n//------------------------------------------------------------------------------\n//! Struct GlobalOptions\n//------------------------------------------------------------------------------\nstruct GlobalOptions {\n  std::string mMgmUri {\"\"};\n  std::string mUserRole {\"\"};\n  std::string mGroupRole {\"\"};\n  std::string mComment {\"\"};\n  bool mJsonFormat {false};\n  bool mForceSss {false};\n  bool mDebug {false};\n};\n"
  },
  {
    "path": "console/ICommand.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file ICommand.hh\n//! @author Stefan Isidorovic <stefan.isidorovic@comtrade.com>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __ICOMMAND__HH__\n#define __ICOMMAND__HH__\n\n//------------------------------------------------------------------------------\n//! Class ICommand\n//!\n//! @description Interface for console commands.\n//------------------------------------------------------------------------------\nclass ICommand\n{\npublic:\n  virtual void PrintHelp() = 0;\n  virtual void Execute()   = 0;\n  virtual ~ICommand()      = default;\n};\n\n#endif //__ICOMMAND__HH__\n"
  },
  {
    "path": "console/README.md",
    "content": "## EOS Console Command Framework (Native)\n\nAll console commands are dispatched through `CommandRegistry`/`IConsoleCommand`. The legacy `commands[]` table is gone; registration happens only via per-file `Register*` functions.\n\n### Directory layout (console/)\n- `CommandFramework.*`        : Registry, interfaces, and core wiring\n- `ConsoleMain.*`             : REPL and dispatch; calls `RegisterNativeConsoleCommands()`\n- `commands/native/`          : Native command implementations\n  - `*-proto-native.cc`       : Proto-based commands\n  - `*-cmd-native.cc`         : Direct MGM (`mgm.cmd=...`) commands\n  - `*-com-native.cc`         : Wrappers around legacy `com_*`\n  - `*-alias.cc`              : Aliases forwarding to other commands\n- `commands/helpers/`         : Shared helpers (e.g., `ICmdHelper`, FsHelper, TokenHelper)\n- `commands/coms/`            : Legacy `com_*.cc` sources\n  - `commands/coms/unused/`   : Legacy sources no longer linked/needed\n- `commands/native/LegacySymbols.cc` : Minimal legacy symbols still required by some native wrappers\n- `console/CMakeLists.txt`    : Build list; add new native files here\n\n### Core interfaces\n- `IConsoleCommand`: `name()`, `description()`, `requiresMgm(args)`, `run(args, CommandContext&)`, `printHelp()`.\n- `CommandContext`: current CLI state plus callbacks `clientCommand(...)` and `outputResult(...)`. Prefer these over direct legacy globals.\n- `CommandRegistry`: `reg(...)`, `find(name)`, `all()`. Newest registration wins, so native overrides any legacy adapters.\n\n### Naming conventions for native files\n- `*-proto-native.cc`  : builds/uses protobuf helpers (`SpaceProto_*`, etc.).\n- `*-cmd-native.cc`    : builds MGM requests directly (`mgm.cmd=...`).\n- `*-com-native.cc`    : wraps or delegates to legacy `com_*` implementations.\n- `*-alias.cc`         : forwards to another command (e.g., `info-alias` → `file info`).\n\n### Registering commands\n- Each file exposes `RegisterXxx...()` and calls `CommandRegistry::instance().reg(...)`.\n- `CommandFramework.cc` invokes all `Register*` once at startup (`RegisterNativeConsoleCommands()`).\n- Add new files to `console/CMakeLists.txt` under `EosConsoleCommands-Objects`.\n\n### Calling another console command\nUse the registry to compose/forward behavior:\n```c++\nIConsoleCommand* other = CommandRegistry::instance().find(\"file\");\nif (!other) { fprintf(stderr, \"error: 'file' command not available\\n\"); return EINVAL; }\nstd::vector<std::string> forwarded = {\"info\", path};\nint rc = other->run(forwarded, ctx); // reuse the same CommandContext\n```\n\n### Creating an alias\n- Create a small `*-alias.cc`.\n- In `run(...)`, find the target command, prepend the subcommand, and call `run` on the target with the same `CommandContext`.\n- Implement `printHelp()` with a `usage:` line (emit to `stderr`) noting it forwards to the target.\n\n### Help / usage\n- `printHelp()` should write to `stderr` and begin with `usage: <cmd> ...`.\n- On invalid args or `--help`, call `printHelp()`, set `global_retc = EINVAL`, and return.\n\n### MGM requirement\n- Return `false` from `requiresMgm(...)` for local-only commands (e.g., `clear`, `whoami`); otherwise typically return `!wants_help(args.c_str())` so help does not ping MGM.\n\n### Migrating legacy commands\n- If a native command still calls `com_*`, keep the file named `*-com-native.cc`.\n- If it builds `mgm.cmd=...`, use `*-cmd-native.cc`.\n- If it uses protobuf helpers, use `*-proto-native.cc`.\n- Move logic into `run(...)` using `CommandContext` callbacks; drop legacy dependencies once behavior matches.\n"
  },
  {
    "path": "console/RegexUtil.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file RegexUtil.hh\n//! @author Stefan Isidorovic <stefan.isidorovic@comtrade.com>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"RegexUtil.hh\"\n\nRegexUtil::RegexUtil()\n  : m_regex(), m_tokenize(false), m_origin(\"\"), mInitialized(false)\n{\n  for (unsigned i = 0; i < max_num_of_matches; ++i) {\n    m_matches[i].rm_so = 0;\n    m_matches[i].rm_eo = 0;\n  }\n}\n\nvoid RegexUtil::SetRegex(std::string regex_txt, int flags)\n{\n  if (mInitialized) {\n    regfree(&m_regex);\n  } else {\n    mInitialized = true;\n  }\n\n  int status = regcomp(&(m_regex), regex_txt.c_str(),\n                       flags ? flags : REG_EXTENDED | REG_NEWLINE);\n\n  if (status != 0) {\n    char error_message[256];\n    regerror(status, &(m_regex), error_message, 256);\n    throw std::string(error_message);\n  }\n}\n\nvoid RegexUtil::initTokenizerMode()\n{\n  if (m_origin.empty()) {\n    throw std::string(\"No origin set!\");\n  }\n\n  int nomatch = regexec(&(m_regex), m_origin.c_str(),\n                        max_num_of_matches, m_matches, 0);\n\n  if (nomatch) {\n    throw std::string(\"Nothing matches.\");\n  }\n\n  m_tokenize = true;\n}\n\nstd::string RegexUtil::Match()\n{\n  if (!m_tokenize) {\n    throw std::string(\"RegexUtil: Tokenizer mode doesn't initialized!\");\n  }\n\n  return std::string(\n           m_origin.begin() + m_matches[0].rm_so,\n           m_origin.begin() + m_matches[0].rm_eo);\n}\n"
  },
  {
    "path": "console/RegexUtil.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file RegexUtil.hh\n//! @author Stefan Isidorovic <stefan.isidorovic@comtrade.com>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __REGEXUTIL__HH__\n#define __REGEXUTIL__HH__\n\n#include <iostream>\n#include <string>\n#include <regex.h>\n\n//------------------------------------------------------------------------------\n//! Class RegexUtil\n//!\n//! @description Simple wrapper aroud posix regex implementation.\n//!     TODO: This will be obsolete when min supported gcc is 4.9 and std::regex\n//!           become available.\n//------------------------------------------------------------------------------\nclass RegexUtil\n{\n  static const unsigned max_num_of_matches = 128; ///< Length of matched regex arr\n  ///< Enum containing signal values if smth gone wrong\n  enum RegexErr {NOTOKENMODEON = -1, NOMOREMATCHES = -2};\n  regex_t m_regex; ///< Posix regex object\n  regmatch_t m_matches[max_num_of_matches]; ///< Matches from regex_t\n  bool m_tokenize; ///< Tokenizer mode indicator\n  std::string m_origin; ///< pointer to source string\n  std::string m_regex_string; ///< Regex string\n  bool mInitialized; ///< True if regex obj is initialized\n\npublic:\n  //------------------------------------------------------------------------------\n  //! Constructor\n  //------------------------------------------------------------------------------\n  RegexUtil();\n\n  //------------------------------------------------------------------------------\n  //! Destructor\n  //------------------------------------------------------------------------------\n  ~RegexUtil()\n  {\n    if (mInitialized) {\n      regfree(&m_regex);\n    }\n  }\n\n  RegexUtil(const RegexUtil &other) = delete;\n  RegexUtil(RegexUtil &&other) = delete;\n  RegexUtil& operator=(RegexUtil &other) = delete;\n  RegexUtil& operator=(RegexUtil &&other) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Setting regex string and flags\n  //!\n  //! @param in regex string\n  //! @param flags int\n  //----------------------------------------------------------------------------\n  void SetRegex(std::string regex, int flags = 0);\n\n  //----------------------------------------------------------------------------\n  //! Setting origin pointer to string\n  //!\n  //! @param in pointer to origin string\n  //----------------------------------------------------------------------------\n  inline void SetOrigin(const std::string& origin)\n  {\n    m_origin = origin;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Applying regex actually on string and storing matches\n  //----------------------------------------------------------------------------\n  void initTokenizerMode(); /*throw (std::string)*/\n\n  //----------------------------------------------------------------------------\n  //! Getting match (if there is any)\n  //----------------------------------------------------------------------------\n  std::string Match(); /*throw (std::string)*/\n};\n\n#endif //__REGEXUTIL__HH__\n"
  },
  {
    "path": "console/commands/HealthCommand.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file HealthCommand.cc\n//! @author Stefan Isidorovic <stefan.isidorovic@comtrade.com>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"HealthCommand.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/NodeHelper.hh\"\n#include \"console/commands/helpers/FsHelper.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/Statistics.hh\"\n#include <algorithm>\n\nstd::string HealthCommand::GetValueWrapper::GetValue(const std::string& key)\n{\n  try {\n    RegexUtil reg;\n    reg.SetRegex(key + \"=[%a-zA-Z0-9/.:-]*\");\n    reg.SetOrigin(m_token);\n    reg.initTokenizerMode();\n    std::string temp = reg.Match();\n    auto pos = temp.find('=');\n\n    if (pos == std::string::npos) {\n      return std::string(\"\");\n    }\n\n    return std::string(temp.begin() + pos + 1, temp.end());\n  } catch (std::string& e) {\n    m_error_message = e;\n    throw std::string(\" REGEX_ERROR: \" + e);\n  }\n}\n\nvoid FSInfo::ReadFromString(const std::string& input)\n{\n  size_t pos = 0,  last = 0;\n  std::string value;\n\n  if ((pos = input.find(' ',  last)) != std::string::npos) {\n    host = input.substr(last, pos);\n    last = ++pos;\n  }\n\n  if ((pos = input.find(' ',  last)) != std::string::npos) {\n    port = std::stoi(input.substr(last, pos));\n    last = ++pos;\n  }\n\n  if ((pos = input.find(' ',  last)) != std::string::npos) {\n    id = std::stoi(input.substr(last, pos));\n    last = ++pos;\n  }\n\n  if ((pos = input.find(' ',  last)) != std::string::npos) {\n    active = input.substr(last, pos - last);\n    last = ++pos;\n  }\n\n  if ((pos = input.find(' ',  last)) != std::string::npos) {\n    path = input.substr(last, pos - last);\n    last = ++pos;\n  }\n\n  if ((pos = input.find(' ',  last)) != std::string::npos) {\n    headroom = std::strtoull(input.substr(last, pos).c_str(),  nullptr, 10);\n    last = ++pos;\n  }\n\n  if ((pos = input.find(' ',  last)) != std::string::npos) {\n    free_bytes = std::strtoull(input.substr(last, pos).c_str(),  nullptr, 10);\n    last = ++pos;\n  }\n\n  if ((pos = input.find(' ',  last)) != std::string::npos) {\n    used_bytes = std::strtoull(input.substr(last, pos).c_str(),  nullptr, 10);\n    last = ++pos;\n  }\n\n  if ((pos = input.find(' ',  last)) != std::string::npos) {\n    capacity = std::strtoull(input.substr(last, pos).c_str(),  nullptr, 10);\n    last = ++pos;\n  }\n}\n\nbool FSInfo::operator==(const FSInfo& other)\n{\n  return host == other.host && port == other.port &&\n         id == other.id && active == other.active &&\n         path == other.path && free_bytes == other.free_bytes &&\n         capacity == other.capacity && headroom == other.headroom;\n}\n\n\nHealthCommand::HealthCommand(const char* comm)\n  : m_comm(const_cast<char*>(comm)),\n    m_monitoring(false),\n    m_all(false),\n    m_section(\"\"),\n    m_dont_color(!isatty(STDOUT_FILENO) || !isatty(STDERR_FILENO))\n{\n}\n\nvoid HealthCommand::DeadNodesCheck(NodeHelper& node_cmd)\n{\n  std::string ret = node_cmd.GetResult();\n  std::string line;\n  std::istringstream splitter(ret);\n  std::string format_s = !m_monitoring ? \"s\" : \"os\";\n  std::string format_ss = !m_monitoring ? \"-s\" : \"os\";\n  eos::mgm::TableFormatterBase table(m_dont_color);\n\n  if (!m_monitoring) {\n    table.SetHeader({\n      std::make_tuple(\"hostport\", 32, format_ss),\n      std::make_tuple(\"status\", 8, format_s)\n    });\n  } else {\n    table.SetHeader({\n      std::make_tuple(\"type\", 0, format_ss),\n      std::make_tuple(\"hostport\", 0, format_ss),\n      std::make_tuple(\"status\", 0, format_s)\n    });\n  }\n\n  while (std::getline(splitter, line, '\\n')) {\n    GetValueWrapper extractor(line);\n    std::string hostport = extractor.GetValue(\"hostport\");\n    std::string status = extractor.GetValue(\"status\");\n    bool trigger = status != \"online\";\n\n    if (trigger || m_all) {\n      TableData table_data;\n      table_data.emplace_back();\n\n      if (m_monitoring) {\n        table_data.back().emplace_back(\"DeadNodesCheck\", format_ss);\n      }\n\n      table_data.back().emplace_back(hostport, format_s);\n      table_data.back().emplace_back(status, format_s);\n      table.AddRows(table_data);\n    }\n  }\n\n  m_output << table.GenerateTable(HEADER).c_str();\n}\n\n\nvoid HealthCommand::BlackHoleCheck(NodeHelper& node_cmd)\n{\n  std::string ret = node_cmd.GetResult();\n  std::string line;\n  std::istringstream splitter(ret);\n  std::map<std::string, float> files_ropen;\n  std::map<std::string, float> files_wopen;\n  std::multiset<float> ropen_set;\n  std::multiset<float> wopen_set;\n  double ropen_avg;\n  double wopen_avg;\n  double ropen_sig;\n  double wopen_sig;\n\n  while (std::getline(splitter, line, '\\n')) {\n    GetValueWrapper extractor(line);\n    std::string hostport = extractor.GetValue(\"hostport\");\n    std::string status = extractor.GetValue(\"status\");\n    std::string str_temp;\n    str_temp = extractor.GetValue(\"nofs\");\n    float norm = std::stoi(str_temp.empty() ? \"0\" : str_temp);\n    str_temp = extractor.GetValue(\"sum.stat.ropen\");\n    float ropen = std::stoi(str_temp.empty() ? \"0\" : str_temp);\n    str_temp = extractor.GetValue(\"sum.stat.wopen\");\n    float wopen = std::stoi(str_temp.empty() ? \"0\" : str_temp);\n\n    if (norm) {\n      // renormalize these values for a default 24 disk node\n      ropen = ropen / norm * 24;\n      wopen = wopen / norm * 24;\n    }\n\n    bool trigger = status == \"online\";\n    trigger = true;\n\n    if (trigger) {\n      files_ropen[hostport] = ropen;\n      files_wopen[hostport] = wopen;\n      ropen_set.insert((float) ropen);\n      wopen_set.insert((float) wopen);\n    }\n  }\n\n  ropen_avg = eos::common::Statistics::avg(ropen_set);\n  wopen_avg = eos::common::Statistics::avg(wopen_set);\n  ropen_sig   = eos::common::Statistics::sig(ropen_set);\n  wopen_sig   = eos::common::Statistics::sig(wopen_set);\n  double warning_sigmas = 2;\n\n  if (getenv(\"EOS_HEALTH_SIGMAS\")) {\n    warning_sigmas = std::stoi(getenv(\"EOS_HEALTH_SIGMAS\"));\n  }\n\n  std::string format_s = !m_monitoring ? \"s\" : \"os\";\n  std::string format_ss = !m_monitoring ? \"-s\" : \"os\";\n  std::string format_f = !m_monitoring ? \"f\" : \"of\";\n  eos::mgm::TableFormatterBase table(!isatty(STDOUT_FILENO) ||\n                                     !isatty(STDERR_FILENO));\n\n  if (!m_monitoring) {\n    table.SetHeader({\n      std::make_tuple(\"hostport\", 32, format_ss),\n      std::make_tuple(\"warning\", 32, format_ss),\n      std::make_tuple(\"avg\", 8, format_f),\n      std::make_tuple(\"sig\", 8, format_f),\n      std::make_tuple(\"now\", 8 , format_f)\n    });\n  } else {\n    table.SetHeader({\n      std::make_tuple(\"type\", 0, format_ss),\n      std::make_tuple(\"hostport\", 32, format_ss),\n      std::make_tuple(\"warning\", 32, format_ss),\n      std::make_tuple(\"avg\", 8, format_f),\n      std::make_tuple(\"sig\", 8, format_f),\n      std::make_tuple(\"now\", 8 , format_f)\n    });\n  }\n\n  for (auto i : files_ropen) {\n    if ((i.second - ropen_avg) > (warning_sigmas * ropen_sig)) {\n      // warn about it\n      TableData table_data;\n      table_data.emplace_back();\n\n      if (m_monitoring) {\n        table_data.back().emplace_back(\"BlackHoleCheck\", format_ss);\n      }\n\n      table_data.back().emplace_back(i.first, format_s);\n      table_data.back().emplace_back(\"many read streams\", format_s);\n      table_data.back().emplace_back(ropen_avg, format_f);\n      table_data.back().emplace_back(ropen_sig, format_f);\n      table_data.back().emplace_back(i.second, format_f);\n      table.AddRows(table_data);\n    }\n  }\n\n  for (auto i : files_wopen) {\n    if ((i.second - wopen_avg) > (warning_sigmas * wopen_sig)) {\n      // warn about it\n      TableData table_data;\n      table_data.emplace_back();\n\n      if (m_monitoring) {\n        table_data.back().emplace_back(\"BlackHoleCheck\", format_ss);\n      }\n\n      table_data.back().emplace_back(i.first, format_s);\n      table_data.back().emplace_back(\"many write streams\", format_s);\n      table_data.back().emplace_back(wopen_avg, format_f);\n      table_data.back().emplace_back(wopen_sig, format_f);\n      table_data.back().emplace_back(i.second, format_f);\n      table.AddRows(table_data);\n    }\n  }\n\n  m_output << table.GenerateTable(HEADER).c_str();\n}\n\n\nvoid HealthCommand::TooFullForDrainingCheck()\n{\n  std::vector<std::tuple<std::string, std::string,\n      unsigned long long, unsigned long long, std::string>> data;\n  std::string format_s = !m_monitoring ? \"s\" : \"os\";\n  std::string format_ss = !m_monitoring ? \"-s\" : \"os\";\n  std::string format_l = !m_monitoring ? \"+l\" : \"ol\";\n  std::string unit = !m_monitoring ? \"B\" : \"\";\n  eos::mgm::TableFormatterBase table(m_dont_color);\n\n  if (!m_monitoring) {\n    table.SetHeader({\n      std::make_tuple(\"group\", 12, format_ss),\n      std::make_tuple(\"offline used\", 12, format_l),\n      std::make_tuple(\"online free\", 12, format_l),\n      std::make_tuple(\"status\", 8, format_s)\n    });\n  } else {\n    table.SetHeader({\n      std::make_tuple(\"type\", 0, format_ss),\n      std::make_tuple(\"group\", 0, format_ss),\n      std::make_tuple(\"offline_used_space\", 0, format_l),\n      std::make_tuple(\"online_free_space\", 0, format_l),\n      std::make_tuple(\"status\", 0, format_s)\n    });\n  }\n\n  for (auto& group : m_group_data) {\n    unsigned long long summed_free_space = 0;\n    unsigned long long offline_used_space = 0;\n\n    for (auto& fs : group.second) {\n      if (fs.active != \"online\") {\n        offline_used_space += fs.used_bytes;\n      } else {\n        summed_free_space += fs.free_bytes - fs.headroom;\n      }\n    }\n\n    bool trigger = summed_free_space <= offline_used_space;\n    std::string status = trigger ? \"full\" : \"ok\";\n\n    if (trigger || m_all) {\n      data.emplace_back(std::make_tuple(\"FullDrainCheck\", group.first.c_str(),\n                                        offline_used_space, summed_free_space, status));\n    }\n  }\n\n  std::sort(data.begin(), data.end());\n\n  for (auto it : data) {\n    TableData table_data;\n    table_data.emplace_back();\n\n    if (m_monitoring) {\n      table_data.back().emplace_back(std::get<0>(it), format_ss);\n    }\n\n    table_data.back().emplace_back(std::get<1>(it), format_ss);\n    table_data.back().emplace_back(std::get<2>(it), format_l, unit);\n    table_data.back().emplace_back(std::get<3>(it), format_l, unit);\n    table_data.back().emplace_back(std::get<4>(it), format_s);\n    table.AddRows(table_data);\n  }\n\n  m_output << table.GenerateTable(HEADER).c_str();\n}\n\nvoid HealthCommand::PlacementContentionCheck()\n{\n  std::vector<std::tuple<std::string, std::string, unsigned long long,\n      unsigned long long, unsigned long long, std::string>> data;\n  std::string format_s = !m_monitoring ? \"s\" : \"os\";\n  std::string format_ss = !m_monitoring ? \"-s\" : \"os\";\n  std::string format_l = !m_monitoring ? \"l\" : \"ol\";\n  std::string unit = !m_monitoring ? \"%\" : \"\";\n  eos::mgm::TableFormatterBase table(m_dont_color);\n\n  if (!m_monitoring) {\n    table.SetHeader({\n      std::make_tuple(\"group\", 12, format_ss),\n      std::make_tuple(\"free fs\", 8, format_l),\n      std::make_tuple(\"full fs\", 8, format_l),\n      std::make_tuple(\"contention\", 10, format_l),\n      std::make_tuple(\"status\", 8, format_s)\n    });\n  } else {\n    table.SetHeader({\n      std::make_tuple(\"type\", 0, format_ss),\n      std::make_tuple(\"group\", 0, format_ss),\n      std::make_tuple(\"free_fs\", 0, format_l),\n      std::make_tuple(\"full_fs\", 0, format_l),\n      std::make_tuple(\"contention\", 10, format_l),\n      std::make_tuple(\"status\", 0, format_s)\n    });\n  }\n\n  unsigned min = 100;\n  unsigned avg = 0;\n  unsigned max = 0;\n  unsigned min_free_fs = 1024;\n  std::string critical_group;\n\n  for (auto& group : m_group_data) {\n    unsigned int free_space_left = 0;\n\n    for (auto& fs : group.second) {\n      if (fs.free_bytes > uint64_t(2) * fs.headroom) {\n        ++free_space_left;\n      }\n    }\n\n    unsigned int full_fs = group.second.size() - free_space_left;\n    unsigned contention = 100 - (free_space_left * 1. / group.second.size()) * 100;\n    std::string status;\n    bool trigger = true;\n\n    if (group.second.size() < 4) {\n      status = \"warning: Less than 4 fs in group\";\n    } else if (free_space_left <= 2) {\n      status = \"full\";\n    } else {\n      status = \"fine\";\n      trigger = false;\n    }\n\n    if (trigger || m_all) {\n      data.emplace_back(std::make_tuple(\"PlacementContentionCheck\",\n                                        group.first.c_str(), free_space_left, full_fs, contention, status));\n    }\n\n    min = (contention < min) ? contention : min;\n    avg += contention;\n    max = (contention > max) ? contention : max;\n\n    if (free_space_left < min_free_fs) {\n      min_free_fs = free_space_left;\n      critical_group = group.first;\n    }\n  }\n\n  std::sort(data.begin(), data.end());\n\n  for (auto it : data) {\n    TableData table_data;\n    table_data.emplace_back();\n\n    if (m_monitoring) {\n      table_data.back().emplace_back(std::get<0>(it), format_ss);\n    }\n\n    table_data.back().emplace_back(std::get<1>(it), format_ss);\n    table_data.back().emplace_back(std::get<2>(it), format_l);\n    table_data.back().emplace_back(std::get<3>(it), format_l);\n    table_data.back().emplace_back(std::get<4>(it), format_l, unit);\n    table_data.back().emplace_back(std::get<5>(it), format_s);\n    table.AddRows(table_data);\n  }\n\n  m_output << table.GenerateTable(HEADER).c_str();\n  //! Summary\n  avg = (m_group_data.empty() ? 0 : avg / m_group_data.size()) ;\n  eos::mgm::TableFormatterBase table_summ(m_dont_color);\n\n  if (!m_monitoring) {\n    table_summ.SetHeader({\n      std::make_tuple(\"min\", 6, format_l),\n      std::make_tuple(\"avg\", 6, format_l),\n      std::make_tuple(\"max\", 6, format_l),\n      std::make_tuple(\"min placement\", 14, format_l),\n      std::make_tuple(\"critical group\", 15, format_s)\n    });\n  } else {\n    table_summ.SetHeader({\n      std::make_tuple(\"type\", 0, format_ss),\n      std::make_tuple(\"min\", 0, format_l),\n      std::make_tuple(\"avg\", 0, format_l),\n      std::make_tuple(\"max\", 0, format_l),\n      std::make_tuple(\"min_placement\", 0, format_l),\n      std::make_tuple(\"critical_group\", 0, format_s)\n    });\n  }\n\n  TableData table_data;\n  table_data.emplace_back();\n\n  if (m_monitoring) {\n    table_data.back().emplace_back(\"Summary\", format_ss);\n  }\n\n  table_data.back().emplace_back(min, format_l, unit);\n  table_data.back().emplace_back(avg, format_l, unit);\n  table_data.back().emplace_back(max, format_l, unit);\n  table_data.back().emplace_back(min_free_fs, format_l);\n  table_data.back().emplace_back(critical_group, format_s);\n  table_summ.AddRows(table_data);\n  m_output << table_summ.GenerateTable(HEADER).c_str();\n}\n\nvoid HealthCommand::GetGroupsInfo()\n{\n  FsHelper fs(gGlobalOpts);\n  fs.ParseCommand(\"ls -m\");\n\n  if (fs.ExecuteWithoutPrint() != 0) {\n    throw std::string(\"FsHelper: \" + fs.GetError());\n  }\n\n  std::string ret = fs.GetResult();\n\n  if (ret.empty()) {\n    throw std::string(\"There is no FileSystems registered!\");\n  }\n\n  std::string line;\n  std::istringstream splitter(ret);\n\n  while (std::getline(splitter, line, '\\n')) {\n    if (line.empty()) {\n      continue;\n    }\n\n    GetValueWrapper extractor(line);\n    std::string str_temp;\n    std::string group = extractor.GetValue(\"schedgroup\");\n    FSInfo temp;\n    temp.host       = extractor.GetValue(\"host\");\n    str_temp = extractor.GetValue(\"port\");\n    temp.port       = std::stoi(str_temp.empty() ? \"0\" : str_temp);\n    temp.active     = extractor.GetValue(\"stat.active\");\n    str_temp = extractor.GetValue(\"id\");\n    temp.id         = std::stoi(str_temp.empty() ? \"0\" : str_temp);\n    temp.path       = extractor.GetValue(\"path\");\n    str_temp = extractor.GetValue(\"headroom\");\n    temp.headroom   = std::stoul(str_temp.empty() ? \"0\" : str_temp);\n    str_temp = extractor.GetValue(\"stat.statfs.freebytes\");\n    temp.free_bytes = std::stoul(str_temp.empty() ? \"0\" : str_temp);\n    str_temp = extractor.GetValue(\"stat.statfs.usedbytes\");\n    temp.used_bytes = std::stoul(str_temp.empty() ? \"0\" : str_temp);\n    str_temp = extractor.GetValue(\"stat.statfs.capacity\");\n    temp.capacity   = std::stoul(str_temp.empty() ? \"0\" : str_temp);\n\n    if (m_group_data.find(group) == m_group_data.end()) {\n      FSInfoVec temp_vec;\n      temp_vec.push_back(temp);\n      m_group_data[group] = temp_vec;\n    } else {\n      m_group_data[group].push_back(temp);\n    }\n  }\n}\n\nvoid HealthCommand::AllCheck()\n{\n  // run node ls -m once - avoid to run commands several times\n  NodeHelper node_cmd(gGlobalOpts);\n  node_cmd.ParseCommand(\"ls -m\");\n\n  if (node_cmd.ExecuteWithoutPrint()) {\n    throw std::string(\"MGMError: \" + node_cmd.GetError());\n  }\n\n  DeadNodesCheck(node_cmd);\n  TooFullForDrainingCheck();\n  PlacementContentionCheck();\n  BlackHoleCheck(node_cmd);\n}\n\n\nvoid HealthCommand::PrintHelp()\n{\n  std::cerr << \"Usage: eos health [OPTION] [SECTION]\" << std::endl;\n  std::cerr << std::endl;\n  std::cerr << \"Options available: \" << std::endl;\n  std::cerr << \"  --help    Print help\" << std::endl;\n  std::cerr << \"   -m       Turn on monitoring mode\" << std::endl;\n  std::cerr << \"   -a       Display all information, not just critical\" <<\n            std::endl;\n  std::cerr << std::endl;\n  std::cerr << \"Sections available: \" << std::endl;\n  std::cerr << \"  all         Display all sections (default value)\" << std::endl;\n  std::cerr << \"  nodes       Display only information about nodes\" << std::endl;\n  std::cerr << \"  drain       Display drain health information\" << std::endl;\n  std::cerr << \"  placement   Display placement contention health information\" <<\n            std::endl;\n}\n\nvoid HealthCommand::ParseCommand()\n{\n  std::string token;\n  const char* temp;\n  eos::common::StringTokenizer m_subtokenizer(m_comm);\n  m_subtokenizer.GetLine();\n\n  while ((temp = m_subtokenizer.GetToken()) != nullptr) {\n    token = std::string(temp);\n    eos::common::trim(token);\n\n    if (token.empty()) {\n      continue;\n    }\n\n    if (token == \"all\"   ||\n        token == \"nodes\" ||\n        token == \"drain\" ||\n        token == \"placement\"\n       ) {\n      m_section = token;\n      continue;\n    }\n\n    if (token == \"-a\") {\n      m_all = true;\n      continue;\n    }\n\n    if (token == \"-m\") {\n      m_monitoring = true;\n      continue;\n    }\n\n    if (token == \"--help\") {\n      PrintHelp();\n      m_section = \"/\";\n      return;\n    }\n\n    throw std::string(\"Unrecognized token (\" + token + \")!\");\n  }\n}\n\nvoid HealthCommand::Execute()\n{\n  ParseCommand();\n  GetGroupsInfo();\n\n  if (m_section == \"nodes\") {\n    // ---------------------------------------------------\n    // run node ls -m once\n    // avoid to run commands several times\n    NodeHelper node_cmd(gGlobalOpts);\n    node_cmd.ParseCommand(\"ls -m\");\n\n    if (node_cmd.ExecuteWithoutPrint()) {\n      throw std::string(\"MGMError: \" + node_cmd.GetError());\n    }\n\n    // ---------------------------------------------------\n    DeadNodesCheck(node_cmd);\n    BlackHoleCheck(node_cmd);\n  }\n\n  if (m_section == \"drain\") {\n    TooFullForDrainingCheck();\n  }\n\n  if (m_section == \"placement\") {\n    PlacementContentionCheck();\n  }\n\n  if (m_section.empty() || m_section == \"all\") {\n    AllCheck();\n  }\n\n  std::cout << m_output.str();\n}\n\nvoid HealthCommand::Execute(std::string& out)\n{\n  m_dont_color = false; // Color text for EOS-wnc reply\n  ParseCommand();\n  GetGroupsInfo();\n\n  if (m_section.empty() || m_section == \"all\") {\n    AllCheck();\n  } else if (m_section == \"nodes\") {\n    // run node ls -m once - avoid to run commands several times\n    NodeHelper node_cmd(gGlobalOpts);\n    node_cmd.ParseCommand(\"ls -m\");\n\n    if (node_cmd.ExecuteWithoutPrint()) {\n      throw std::string(\"MGMError: \" + node_cmd.GetError());\n    }\n\n    DeadNodesCheck(node_cmd);\n    BlackHoleCheck(node_cmd);\n  } else if (m_section == \"drain\") {\n    TooFullForDrainingCheck();\n  } else if (m_section == \"placement\") {\n    PlacementContentionCheck();\n  }\n\n  out = m_output.str();\n}\n"
  },
  {
    "path": "console/commands/HealthCommand.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file HealthCommand.hh\n//! @author Stefan Isidorovic <stefan.isidorovic@comtrade.com>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __HEALTHCOMMAND__HH__\n#define __HEALTHCOMMAND__HH__\n\n#include \"common/table_formatter/TableFormatterBase.hh\"\n#include \"console/commands/helpers/NodeHelper.hh\"\n#include \"console/ICommand.hh\"\n#include \"console/RegexUtil.hh\"\n#include <cstdlib>\n#include <iostream>\n#include <functional>\n#include <unordered_map>\n#include <string>\n#include <sstream>\n#include <vector>\n\nstruct FSInfo;\n\ntypedef std::vector<FSInfo> FSInfoVec;\ntypedef std::unordered_map<std::string, FSInfoVec> GroupsInfo;\n\n//------------------------------------------------------------------------------\n//! struct FSInfo\n//!\n//! @description Data container for needed informations about filesystems with\n//!   few additional methods, primarly for easier testing and comparing.\n//------------------------------------------------------------------------------\nstruct FSInfo {\n  std::string host;\n  unsigned port;\n  unsigned id;\n  std::string active;\n  std::string path;\n  uint64_t headroom;\n  uint64_t free_bytes;\n  uint64_t used_bytes;\n  uint64_t capacity;\n\n  //----------------------------------------------------------------------------\n  //! Filling container based on values from string.\n  //!\n  //! @param input Input string containing needed information\n  //----------------------------------------------------------------------------\n  void ReadFromString(const std::string& input);\n\n  //----------------------------------------------------------------------------\n  //! Operator == for comparing two instances.\n  //!\n  //! @param other Other instance of this structure\n  //----------------------------------------------------------------------------\n  bool operator==(const FSInfo& other);\n};\n\n//------------------------------------------------------------------------------\n//! Class HealthCommand\n//!\n//! @description Implementing simple CLI tool for showing status of some\n//!     aspects of EOS system.\n//!\n//------------------------------------------------------------------------------\nclass HealthCommand : public ICommand\n{\n\n  //------------------------------------------------------------------------------\n  //! Class GetValueWrapper\n  //!\n  //! @description Private class intended to wrap around regex utility to ensure\n  //!     easier and cleaner use of utility in this case.\n  //!\n  //------------------------------------------------------------------------------\n  class GetValueWrapper\n  {\n    std::string m_token;\n    std::string m_error_message;\n  public:\n    GetValueWrapper(const std::string& s) : m_token(s) {}\n    std::string GetValue(const std::string& key);\n  };\n\n  GroupsInfo m_group_data; ///< Storing necessary data\n  char* m_comm; ///< Input command\n  bool m_monitoring; ///< Indicator for monitoring mode\n  bool m_all; ///< Indicator for all statistic\n  std::string m_section; ///< Chosen section\n  std::ostringstream m_output; ///< Object containing output\n  bool m_dont_color; ///< Disable text coloring\n\n  //----------------------------------------------------------------------------\n  //! Performing dead node check. Results are kept inside class attributes.\n  //----------------------------------------------------------------------------\n  void DeadNodesCheck(NodeHelper& node_cmd);\n\n  //----------------------------------------------------------------------------\n  //! Performing check if we have a back hole attracting many transfers\n  //----------------------------------------------------------------------------\n  void BlackHoleCheck(NodeHelper& node_cmd);\n\n  //----------------------------------------------------------------------------\n  //! Performing if drain is possible check. Results are kept inside\n  //! class attributes.\n  //----------------------------------------------------------------------------\n  void TooFullForDrainingCheck();\n\n  //----------------------------------------------------------------------------\n  //! Performing placemente contention check. Results are kept inside\n  //! class attributes.\n  //----------------------------------------------------------------------------\n  void PlacementContentionCheck();\n\n  //----------------------------------------------------------------------------\n  //! Performing all checks respectively. Results are kept inside\n  //! class attributes.\n  //----------------------------------------------------------------------------\n  void AllCheck();\n\n  //----------------------------------------------------------------------------\n  //! Getting groups info from MGM. Results are kept inside\n  //! class attributes.\n  //----------------------------------------------------------------------------\n  void GetGroupsInfo();\n\n  //----------------------------------------------------------------------------\n  //! Performing if drain is possible check. Results are kept inside\n  //! class attributes.\n  //----------------------------------------------------------------------------\n  void ParseCommand();\n\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  HealthCommand(const char* comm);\n\n  //----------------------------------------------------------------------------\n  //! Printing help.\n  //----------------------------------------------------------------------------\n  void PrintHelp();\n\n  //----------------------------------------------------------------------------\n  //! Executing command.\n  //----------------------------------------------------------------------------\n  void Execute();\n\n  //----------------------------------------------------------------------------\n  //! Execute command and save the output.\n  //----------------------------------------------------------------------------\n  void Execute(std::string& out);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~HealthCommand() {}\n};\n\n#endif //__HEALTHCOMMAND__HH__\n"
  },
  {
    "path": "console/commands/coms/unused/com_access.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_access.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n/*----------------------------------------------------------------------------*/\n\n/* access (deny/bounce/redirect) -  Interface */\nint\ncom_access(char* arg1)\n{\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString option = \"\";\n  XrdOucString options = \"\";\n  bool ok = false;\n  XrdOucString in = \"\";\n  in = \"mgm.cmd=access\";\n  XrdOucString subcmd = subtokenizer.GetToken();\n\n  if (wants_help(arg1)) {\n    goto com_access_usage;\n  }\n\n  if (subcmd == \"ban\") {\n    ok = true;\n    in += \"&mgm.subcmd=ban\";\n  }\n\n  if (subcmd == \"unban\") {\n    in += \"&mgm.subcmd=unban\";\n    ok = true;\n  }\n\n  if (subcmd == \"allow\") {\n    in += \"&mgm.subcmd=allow\";\n    ok = true;\n  }\n\n  if (subcmd == \"unallow\") {\n    in += \"&mgm.subcmd=unallow\";\n    ok = true;\n  }\n\n  if (subcmd == \"ls\") {\n    in += \"&mgm.subcmd=ls\";\n    ok = true;\n  }\n\n  if (subcmd == \"set\") {\n    in += \"&mgm.subcmd=set\";\n    ok = true;\n  }\n\n  if (subcmd == \"rm\") {\n    in += \"&mgm.subcmd=rm\";\n    ok = true;\n  }\n\n  if (ok) {\n    ok = false;\n    XrdOucString type = \"\";\n    XrdOucString maybeoption = subtokenizer.GetToken();\n\n    while (maybeoption.beginswith(\"-\")) {\n      if ((subcmd == \"ls\") && (maybeoption != \"-m\") && (maybeoption != \"-n\")) {\n        goto com_access_usage;\n      }\n\n      if ((subcmd != \"ls\")) {\n        goto com_access_usage;\n      }\n\n      maybeoption.replace(\"-\", \"\");\n      option += maybeoption;\n      maybeoption = subtokenizer.GetToken();\n    }\n\n    if (subcmd == \"ls\") {\n      ok = true;\n    }\n\n    if ((subcmd == \"ban\") || (subcmd == \"unban\") || (subcmd == \"allow\") ||\n        (subcmd == \"unallow\")) {\n      type = maybeoption;\n      XrdOucString id = subtokenizer.GetToken();\n\n      if ((!type.length()) || (!id.length())) {\n        goto com_access_usage;\n      }\n\n      if (type == \"host\") {\n        in += \"&mgm.access.host=\";\n        in += id;\n        ok = true;\n      }\n\n      if (type == \"domain\") {\n        in += \"&mgm.access.domain=\";\n        in += id;\n        ok = true;\n      }\n\n      if (type == \"user\") {\n        in += \"&mgm.access.user=\";\n        in += id;\n        ok = true;\n      }\n\n      if (type == \"group\") {\n        in += \"&mgm.access.group=\";\n        in += id;\n        ok = true;\n      }\n    }\n\n    if ((subcmd == \"set\") || (subcmd == \"rm\")) {\n      type = maybeoption;\n      XrdOucString id = subtokenizer.GetToken();\n\n      if ((subcmd != \"rm\") && ((!type.length()) || (!id.length()))) {\n        goto com_access_usage;\n      }\n\n      XrdOucString rtype = subtokenizer.GetToken();\n\n      if (subcmd == \"rm\") {\n        rtype = id;\n      }\n\n      if (!id.length()) {\n        id = \"dummy\";\n      }\n\n      if (type == \"redirect\") {\n        in += \"&mgm.access.redirect=\";\n        in += id;\n\n        if (rtype.length()) {\n          if (rtype == \"r\") {\n            in += \"&mgm.access.type=r\";\n            ok = true;\n          } else {\n            if (rtype == \"w\") {\n              in += \"&mgm.access.type=w\";\n              ok = true;\n            } else {\n              if (rtype == \"ENONET\") {\n                in += \"&mgm.access.type=ENONET\";\n                ok = true;\n              } else {\n                if (rtype == \"ENOENT\") {\n                  in += \"&mgm.access.type=ENOENT\";\n                  ok = true;\n                }\n              }\n            }\n          }\n        } else {\n          ok = true;\n        }\n      }\n\n      if (type == \"stall\") {\n        in += \"&mgm.access.stall=\";\n        in += id;\n\n        if (rtype.length()) {\n          if (rtype == \"r\") {\n            in += \"&mgm.access.type=r\";\n            ok = true;\n          } else {\n            if (rtype == \"w\") {\n              in += \"&mgm.access.type=w\";\n              ok = true;\n            } else {\n              if (rtype == \"ENONET\") {\n                in += \"&mgm.access.type=ENONET\";\n                ok = true;\n              } else {\n                if (rtype == \"ENOENT\") {\n                  in += \"&mgm.access.type=ENOENT\";\n                  ok = true;\n                }\n              }\n            }\n          }\n        } else {\n          ok = true;\n        }\n      }\n\n      if (type == \"limit\") {\n        in += \"&mgm.access.stall=\";\n        in += id;\n\n        if ((rtype.beginswith(\"rate:user:\")) || (rtype.beginswith(\"rate:group:\"))) {\n          if ((rtype.find(\":\"), 11) != STR_NPOS) {\n            in += \"&mgm.access.type=\";\n            in += rtype;\n            ok = true;\n          }\n        }\n      }\n    }\n\n    if (!ok) {\n      goto com_access_usage;\n    }\n  } else {\n    goto com_access_usage;\n  }\n\n  if (option.length()) {\n    in += \"&mgm.access.option=\";\n    in += option;\n  }\n\n  global_retc = output_result(client_command(in, true));\n  return (0);\ncom_access_usage:\n  fprintf(stdout,\n          \"'[eos] access ..' provides the access interface of EOS to allow/disallow hosts/domains and/or users\\n\");\n  fprintf(stdout, \"Usage: access ban|unban|allow|unallow|set|rm|ls ...\\n\\n\");\n  fprintf(stdout, \"Options:\\n\");\n  fprintf(stdout, \"access ban user|group|host|domain <identifier> : \\n\");\n  fprintf(stdout,\n          \"                                                  ban user,group or host,DOMAIN with identifier <identifier>\\n\");\n  fprintf(stdout,\n          \"                                   <identifier> : can be a user name, user id, group name, group id, hostname or IP or domainname\\n\");\n  fprintf(stdout, \"access unban user|group|host|domain <identifier> :\\n\");\n  fprintf(stdout,\n          \"                                                  unban user,group or host,domain with identifier <identifier>\\n\");\n  fprintf(stdout,\n          \"                                   <identifier> : can be a user name, user id, group name, group id, hostname or IP or domainname\\n\");\n  fprintf(stdout, \"access allow user|group|host|domain <identifier> :\\n\");\n  fprintf(stdout,\n          \"                                                  allows this user,group or host,domain access\\n\");\n  fprintf(stdout,\n          \"                                   <identifier> : can be a user name, user id, group name, group id, hostname or IP or domainname\\n\");\n  fprintf(stdout, \"access unallow user|group|host|domain <identifier> :\\n\");\n  fprintf(stdout,\n          \"                                                  unallows this user,group or host,domain access\\n\");\n  fprintf(stdout,\n          \"                                   <identifier> : can be a user name, user id, group name, group id, hostname or IP or domainname\\n\");\n  fprintf(stdout,\n          \"HINT:  if you add any 'allow' the instance allows only the listed users.\\nA banned identifier will still overrule an allowed identifier!\\n\\n\");\n  fprintf(stdout, \"access set redirect <target-host> [r|w|ENOENT|ENONET] :\\n\");\n  fprintf(stdout,\n          \"                                                  allows to set a global redirection to <target-host>\\n\");\n  fprintf(stdout,\n          \"                                  <target-host> : hostname to which all requests get redirected\\n\");\n  fprintf(stdout,\n          \"                                                  <taget-hosts> can be structured like <host>:<port[:<delay-in-ms>] where <delay> holds each request for a given time before redirecting\\n\");\n  fprintf(stdout,\n          \"                                          [r|w] : optional set a redirect for read/write requests seperatly\\n\");\n  fprintf(stdout,\n          \"                                       [ENONET] : optional set a redirect if a file is offline (ENONET) \\n\");\n  fprintf(stdout,\n          \"                                       [ENOENT] : optional set a redirect if a file is not existing     \\n\");\n  fprintf(stdout, \"access rm redirect [r|w|ENOENT|ENONET]  :\\n\");\n  fprintf(stdout,\n          \"                                                  removes global redirection\\n\");\n  fprintf(stdout, \"access set stall <stall-time> [r|w|ENOENT|ENONET] :\\n\");\n  fprintf(stdout,\n          \"                                                  allows to set a global stall time\\n\");\n  fprintf(stdout,\n          \"                                   <stall-time> : time in seconds after which clients should rebounce\\n\");\n  fprintf(stdout,\n          \"                                          [r|w] : optional set stall time for read/write requests separately\\n\");\n  fprintf(stdout,\n          \"                                       [ENONET] : optional set a stall if a file is offline (ENONET) \\n\");\n  fprintf(stdout,\n          \"                                       [ENOENT] : optional set a stall if a file is not existing     \\n\");\n  fprintf(stdout,\n          \"access set limit <frequency> rate:{user,group}:{name}:<counter>\\n\");\n  fprintf(stdout,\n          \"       rate:{user:group}:{name}:<counter>       : stall the defined user group for 5s if the <counter> exceeds a frequency of <frequency> in a 5s interval\\n\");\n  fprintf(stdout,\n          \"                                                  - the instantanious rate can exceed this value by 33%%\\n\");\n  fprintf(stdout,\n          \"                                                  rate:user:*:<counter> : apply to all users based on user counter\\n\");\n  fprintf(stdout,\n          \"                                                  rate:group:*:<counter>: apply to all groups based on group counter\\n\");\n  fprintf(stdout,\n          \"                                                                          set <frequency> to 0 (zero) to continuously stall the user or group\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout, \"access set limit <nfiles> rate:user:{name}:FindFiles\\n\");\n  fprintf(stdout,\n          \"                                                : set find query limit to <nfiles> for user {name}\\n\");\n  fprintf(stdout, \"access set limit <ndirs> rate:user:{name}:FindDirs\\n\");\n  fprintf(stdout,\n          \"                                                : set find query limit to <ndirs> for user {name}\\n\");\n  fprintf(stdout, \"access set limit <nfiles> rate:group:{name}:FindFiles\\n\");\n  fprintf(stdout,\n          \"                                                : set find query limit to <nfiles> for group {name}\\n\");\n  fprintf(stdout, \"access set limit <ndirs> rate:group:{name}:FindDirs\\n\");\n  fprintf(stdout,\n          \"                                                : set find query limit to <ndirss> for group {name}\\n\");\n  fprintf(stdout, \"access set limit <nfiles> rate:user:*:FindFiles\\n\");\n  fprintf(stdout,\n          \"                                                : set default find query limit to <nfiles> for everybody\\n\");\n  fprintf(stdout, \"access set limit <ndirs> rate:user:*:FindDirs\\n\");\n  fprintf(stdout,\n          \"                                                : set default find query limit to <ndirss> for everybody\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"                                                : rule strength: user-limit >> group-limit >> wildcard-limit\\n\");\n  fprintf(stdout, \"access rm  stall [r|w|ENOENT|ENONET]:\\n\");\n  fprintf(stdout,\n          \"                                                  removes global stall time\\n\");\n  fprintf(stdout,\n          \"                                          [r|w] : removes stall time for read or write requests\\n\");\n  fprintf(stdout, \"       rm limit rate:{user,group}:{name}:<counter\\n\");\n  fprintf(stdout,\n          \"                                                : remove rate limitation\\n\");\n  fprintf(stdout, \"access ls [-m] [-n] :\\n\");\n  fprintf(stdout,\n          \"                                                  print banned,unbanned user,group, hosts\\n\");\n  fprintf(stdout,\n          \"                                                                  -m    : output in monitoring format with <key>=<value>\\n\");\n  fprintf(stdout,\n          \"                                                                  -n    : don't translate uid/gids to names\\n\");\n  fprintf(stdout, \"Examples:\\n\");\n  fprintf(stdout, \"  access ban host foo      Ban host foo\\n\");\n  fprintf(stdout, \"  access ban domain bar    Ban domain bar\\n\");\n  fprintf(stdout,\n          \"  access allow domain nobody@bar Allows user nobody from domain bar\\n\");\n  fprintf(stdout,\n          \"  access allow domain -    use domain allow as whitelist - e.g. nobody@bar will additionally allow the nobody user from domain bar!\");\n  fprintf(stdout, \"  access allow domain bar  Allow only domain bar\\n\");\n  fprintf(stdout,\n          \"  access set redirect foo  Redirect all requests to host foo\\n\");\n  fprintf(stdout,\n          \"  access rm redirect       Remove redirection to previously defined host foo\\n\");\n  fprintf(stdout, \"  access set stall 60      Stall all clients by 60 seconds\\n\");\n  fprintf(stdout, \"  access ls                Print all defined access rules\\n\");\n  fprintf(stdout,\n          \"  access set limit 100  rate:user:*:OpenRead      Limit the rate of open for read to a frequency of 100 Hz for all users\\n\");\n  fprintf(stdout,\n          \"  access set limit 0    rate:user:ab:OpenRead     Limit the open for read rate for the ab user to 0 Hz, to continuously stall it\\n\");\n  fprintf(stdout,\n          \"  access set limit 2000 rate:group:zp:Stat        Limit the stat rate for the zp group to 2kHz\\n\");\n  fprintf(stdout,\n          \"  access rm limit rate:user:*:OpenRead            Removes the defined limit\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_accounting.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_accounting.cc\n// Author: Jozsef Makai- CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n\n\ninline int\ncom_accounting_usage()\n{\n  fprintf(stdout,\n          \"usage: accounting report [-f]                          : prints accounting report in JSON, data is served from cache if possible\\n\");\n  fprintf(stdout,\n          \"                                                    -f : forces a synchronous report instead of using the cache (only use this if the cached data is too old)\\n\");\n  fprintf(stdout,\n          \"       accounting config -e [<expired>] -i [<invalid>] : configure caching behaviour\\n\");\n  fprintf(stdout,\n          \"                                                    -e : expiry time in minutes, after this time frame asynchronous update happens, default is 10 minutes\\n\");\n  fprintf(stdout,\n          \"                                                    -i : invalidity time in minutes, after this time frame synchronous update happens, must be greater than expiry time, default is never\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n\nint\ncom_accounting(char* arg)\n{\n  eos::common::StringTokenizer subtokenizer(arg);\n  subtokenizer.GetLine();\n  XrdOucString in = \"mgm.cmd=accounting\";\n  XrdOucString subcmd = subtokenizer.GetToken();\n  XrdOucString option = \"\";\n  bool ok = false;\n\n  if (subcmd == \"report\") {\n    ok = true;\n    in += \"&mgm.subcmd=report\";\n  } else if (subcmd == \"config\") {\n    ok = true;\n    in += \"&mgm.subcmd=config\";\n  }\n\n  if (!ok) {\n    return com_accounting_usage();\n  }\n\n  XrdOucString maybeoption = subtokenizer.GetToken();\n\n  if (subcmd == \"report\") {\n    while (maybeoption.beginswith(\"-\")) {\n      if ((maybeoption != \"-f\")) {\n        return com_accounting_usage();\n      }\n\n      maybeoption.replace(\"-\", \"\");\n      option += maybeoption;\n      maybeoption = subtokenizer.GetToken();\n    }\n  } else if ((subcmd == \"config\")) {\n    while (maybeoption.beginswith(\"-\")) {\n      if (maybeoption == \"-e\") {\n        in += \"&mgm.accounting.expired=\";\n      } else if (maybeoption == \"-i\") {\n        in += \"&mgm.accounting.invalid=\";\n      } else {\n        return com_accounting_usage();\n      }\n\n      maybeoption = subtokenizer.GetToken();\n\n      if (eos::common::StringTokenizer::IsUnsignedNumber(maybeoption.c_str())) {\n        in += maybeoption;\n      } else {\n        return com_accounting_usage();\n      }\n\n      maybeoption = subtokenizer.GetToken();\n    }\n  }\n\n  if (option.length()) {\n    in += \"&mgm.option=\";\n    in += option;\n  }\n\n  global_retc = output_result(client_command(in));\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_archive.cc",
    "content": "//------------------------------------------------------------------------------\n// File: com_archive.cc\n// Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2014 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <sstream>\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <XrdCl/XrdClURL.hh>\n\nint\ncom_archive(char* arg1)\n{\n  XrdOucString in = \"\";\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString subcmd = subtokenizer.GetToken();\n  std::ostringstream in_cmd;\n  XrdOucString token;\n  in_cmd << \"mgm.cmd=archive&mgm.subcmd=\" << subcmd;\n\n  if (subcmd == \"create\") {\n    XrdOucString path = subtokenizer.GetToken();\n\n    if (!path.length()) {\n      path = gPwd;\n    }\n\n    path = abspath(path.c_str());\n    in_cmd << \"&mgm.archive.path=\" << path;\n  } else if ((subcmd == \"put\") ||\n             (subcmd == \"get\") ||\n             (subcmd == \"purge\") ||\n             (subcmd == \"delete\")) {\n    token = subtokenizer.GetToken();\n\n    if (!token.length()) {\n      goto com_archive_usage;\n    } else if (token.beginswith(\"--\")) {\n      token.erase(0, 2);\n\n      if (token != \"retry\") {\n        fprintf(stdout, \"Unknown option: %s\", token.c_str());\n        goto com_archive_usage;\n      } else {\n        in_cmd << \"&mgm.archive.option=r\";\n      }\n\n      token = subtokenizer.GetToken();\n    }\n\n    // The last token is the path\n    if (!token.length()) {\n      in_cmd << \"&mgm.archive.path=\" << gPwd;\n    } else {\n      token = abspath(token.c_str());\n      in_cmd << \"&mgm.archive.path=\" << token;\n    }\n  } else if (subcmd == \"transfers\") {\n    // type: all, stage, migrate, job_uuid\n    token = subtokenizer.GetToken();\n\n    if (!token.length()) {\n      in_cmd << \"&mgm.archive.option=all\";\n    } else {\n      in_cmd << \"&mgm.archive.option=\" << token;\n    }\n  } else if (subcmd == \"list\") {\n    token = subtokenizer.GetToken();\n\n    if (!token.length()) {\n      in_cmd << \"&mgm.archive.path=/\";\n    } else if (token == \"./\" || token == \".\") {\n      in_cmd << \"&mgm.archive.path=\" << abspath(gPwd.c_str());\n    } else {\n      in_cmd << \"&mgm.archive.path=\" << token;\n    }\n  } else if (subcmd == \"kill\") {\n    // Token is the job_uuid\n    token = subtokenizer.GetToken();\n\n    if (token.length()) {\n      in_cmd  << \"&mgm.archive.option=\" << token;\n    } else {\n      goto com_archive_usage;\n    }\n  } else {\n    goto com_archive_usage;\n  }\n\n  in = in_cmd.str().c_str();\n  global_retc = output_result(client_command(in));\n  return (0);\ncom_archive_usage:\n  std::ostringstream oss;\n  oss << \"usage: archive <subcmd> \" << std::endl\n      << \"               create <path>                          \"\n      << \": create archive file\" << std::endl\n      << \"               put [--retry] <path>                   \"\n      << \": copy files from EOS to archive location\" << std::endl\n      << \"               get [--retry] <path>                   \"\n      << \": recall archive back to EOS\" << std::endl\n      << \"               purge[--retry] <path>                  \"\n      << \": purge files on disk\" << std::endl\n      << \"               transfers [all|put|get|purge|job_uuid] \"\n      << \": show status of running jobs\" << std::endl\n      << \"               list [<path>]                          \"\n      << \": show status of archived directories in subtree\" << std::endl\n      << \"               kill <job_uuid>                        \"\n      << \": kill transfer\" << std::endl\n      << \"               delete <path>                          \"\n      << \": delete files from tape, keeping the ones on disk\" << std::endl\n      << \"               help [--help|-h]                       \"\n      << \": display help message\" << std::endl;\n  fprintf(stdout, \"%s\", oss.str().c_str());\n  global_retc = EINVAL;\n  return 0;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_attr.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_attr.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Utils.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/SymKeys.hh\"\n#include \"console/ConsoleMain.hh\"\n\n/* Attribute ls, get, set rm */\nint\ncom_attr(char* arg1)\n{\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString subcommand = subtokenizer.GetToken();\n  XrdOucString option = \"\";\n  XrdOucString optionstring = \"\";\n  XrdOucString in = \"mgm.cmd=attr\";\n  XrdOucString arg = \"\";\n\n  if (wants_help(arg1)) {\n    goto com_attr_usage;\n  }\n\n  if (subcommand.beginswith(\"-\")) {\n    option = subcommand;\n    option.erase(0, 1);\n    optionstring += subcommand;\n    optionstring += \" \";\n    subcommand = subtokenizer.GetToken(false);\n    arg = subtokenizer.GetToken(false);\n\n    if (subcommand == \"set\") {\n      if (arg.beginswith(\"-\")) {\n        if (arg == \"-c\") {\n          option += \"c\";\n          arg = subtokenizer.GetToken(false);\n        } else {\n          goto com_attr_usage;\n        }\n      }\n    }\n\n    in += \"&mgm.option=\";\n    in += option;\n  } else {\n    if (subcommand == \"set\") {\n      arg = subtokenizer.GetToken(false);\n\n      if (arg.beginswith(\"-\")) {\n        if (arg == \"-c\") {\n          in += \"&mgm.option=c\";\n          arg = subtokenizer.GetToken(false);\n        } else {\n          goto com_attr_usage;\n        }\n      }\n    } else {\n      if (subcommand == \"ls\") {\n        arg = subtokenizer.GetToken(true);\n      } else {\n        arg = subtokenizer.GetToken(false);\n      }\n    }\n  }\n\n  // require base64 encoding of response\n  in += \"&mgm.enc=b64\";\n\n  if ((!subcommand.length()) || (!arg.length()) ||\n      ((subcommand != \"ls\") && (subcommand != \"set\") && (subcommand != \"get\") &&\n       (subcommand != \"rm\") && (subcommand != \"link\") && (subcommand != \"unlink\") &&\n       (subcommand != \"fold\"))) {\n    goto com_attr_usage;\n  }\n\n  if (subcommand == \"ls\") {\n    XrdOucString path = arg;\n\n    if (!path.length()) {\n      goto com_attr_usage;\n    }\n\n    path = PathIdentifier(path.c_str(), true).c_str();\n    in += \"&mgm.subcmd=ls\";\n    in += \"&mgm.path=\";\n    in += path;\n  }\n\n  if ((subcommand == \"set\") || (subcommand == \"link\")) {\n    XrdOucString key = arg;\n    XrdOucString value = \"\";\n    int epos = key.find(\"=\");\n\n    if (epos != STR_NPOS) {\n      XrdOucString value64;\n      value = key;\n      value.erase(0, epos + 1);\n      key.erase(epos);\n\n      if (key != \"default\" && key != \"sys.attr.link\") {\n        eos::common::SymKey::Base64(value, value64);\n        value = value64;\n      }\n    } else {\n      value = \"\";\n    }\n\n    if (subcommand == \"link\") {\n      key = \"sys.attr.link\";\n      value = arg;\n    }\n\n    if (!value.length()) {\n      goto com_attr_usage;\n    }\n\n    if (value.beginswith(\"\\\"\")) {\n      if (!value.endswith(\"\\\"\")) {\n        do {\n          XrdOucString morevalue = subtokenizer.GetToken();\n\n          if (morevalue.endswith(\"\\\"\")) {\n            value += \" \";\n            value += morevalue;\n            break;\n          }\n\n          if (!morevalue.length()) {\n            goto com_attr_usage;\n          }\n\n          value += \" \";\n          value += morevalue;\n        } while (1);\n      }\n    }\n\n    XrdOucString path = subtokenizer.GetToken();\n\n    if (!key.length() || !value.length() || !path.length()) {\n      goto com_attr_usage;\n    }\n\n    path = PathIdentifier(path.c_str(), true).c_str();\n\n    if (key == \"default\") {\n      if (value == \"replica\") {\n        XrdOucString d1 = optionstring;\n        d1 += \"set \";\n        d1 += \"sys.forced.blocksize=4k \";\n        d1 += path;\n        XrdOucString d2 = optionstring;\n        d2 += \"set \";\n        d2 += \"sys.forced.checksum=adler \";\n        d2 += path;\n        XrdOucString d3 = optionstring;\n        d3 += \"set \";\n        d3 += \"sys.forced.layout=replica \";\n        d3 += path;\n        XrdOucString d4 = optionstring;\n        d4 += \"set \";\n        d4 += \"sys.forced.nstripes=2 \";\n        d4 += path;\n        XrdOucString d5 = optionstring;\n        d5 += \"set \";\n        d5 += \"sys.forced.space=default \";\n        d5 += path;\n        global_retc = com_attr((char*) d1.c_str()) || com_attr((char*) d2.c_str()) ||\n                      com_attr((char*) d3.c_str()) || com_attr((char*) d4.c_str()) ||\n                      com_attr((char*) d5.c_str());\n        return (0);\n      }\n\n      if (value == \"raiddp\") {\n        XrdOucString d1 = optionstring;\n        d1 += \"set \";\n        d1 += \"sys.forced.blocksize=1M \";\n        d1 += path;\n        XrdOucString d2 = optionstring;\n        d2 += \"set \";\n        d2 += \"sys.forced.checksum=adler \";\n        d2 += path;\n        XrdOucString d3 = optionstring;\n        d3 += \"set \";\n        d3 += \"sys.forced.layout=raiddp \";\n        d3 += path;\n        XrdOucString d4 = optionstring;\n        d4 += \"set \";\n        d4 += \"sys.forced.nstripes=6 \";\n        d4 += path;\n        XrdOucString d5 = optionstring;\n        d5 += \"set \";\n        d5 += \"sys.forced.space=default \";\n        d5 += path;\n        XrdOucString d6 = optionstring;\n        d6 += \"set \";\n        d6 += \"sys.forced.blockchecksum=crc32c \";\n        d6 += path;\n        global_retc = com_attr((char*) d1.c_str()) || com_attr((char*) d2.c_str()) ||\n                      com_attr((char*) d3.c_str()) || com_attr((char*) d4.c_str()) ||\n                      com_attr((char*) d5.c_str()) || com_attr((char*) d6.c_str());\n        return (0);\n      }\n\n      if (value == \"raid5\") {\n        XrdOucString d1 = optionstring;\n        d1 += \"set \";\n        d1 += \"sys.forced.blocksize=1M \";\n        d1 += path;\n        XrdOucString d2 = optionstring;\n        d2 += \"set \";\n        d2 += \"sys.forced.checksum=adler \";\n        d2 += path;\n        XrdOucString d3 = optionstring;\n        d3 += \"set \";\n        d3 += \"sys.forced.layout=raid5 \";\n        d3 += path;\n        XrdOucString d4 = optionstring;\n        d4 += \"set \";\n        d4 += \"sys.forced.nstripes=5 \";\n        d4 += path;\n        XrdOucString d5 = optionstring;\n        d5 += \"set \";\n        d5 += \"sys.forced.space=default \";\n        d5 += path;\n        XrdOucString d6 = optionstring;\n        d6 += \"set \";\n        d6 += \"sys.forced.blockchecksum=crc32c \";\n        d6 += path;\n        global_retc = com_attr((char*) d1.c_str()) || com_attr((char*) d2.c_str()) ||\n                      com_attr((char*) d3.c_str()) || com_attr((char*) d4.c_str()) ||\n                      com_attr((char*) d5.c_str()) || com_attr((char*) d6.c_str());\n        return (0);\n      }\n\n      if (value == \"raid6\") {\n        XrdOucString d1 = optionstring;\n        d1 += \"set \";\n        d1 += \"sys.forced.blocksize=1M \";\n        d1 += path;\n        XrdOucString d2 = optionstring;\n        d2 += \"set \";\n        d2 += \"sys.forced.checksum=adler \";\n        d2 += path;\n        XrdOucString d3 = optionstring;\n        d3 += \"set \";\n        d3 += \"sys.forced.layout=raid6 \";\n        d3 += path;\n        XrdOucString d4 = optionstring;\n        d4 += \"set \";\n        d4 += \"sys.forced.nstripes=6 \";\n        d4 += path;\n        XrdOucString d5 = optionstring;\n        d5 += \"set \";\n        d5 += \"sys.forced.space=default \";\n        d5 += path;\n        XrdOucString d6 = optionstring;\n        d6 += \"set \";\n        d6 += \"sys.forced.blockchecksum=crc32c \";\n        d6 += path;\n        global_retc = com_attr((char*) d1.c_str()) || com_attr((char*) d2.c_str()) ||\n                      com_attr((char*) d3.c_str()) || com_attr((char*) d4.c_str()) ||\n                      com_attr((char*) d5.c_str()) || com_attr((char*) d6.c_str());\n        return (0);\n      }\n\n      if (value == \"archive\") {\n        XrdOucString d1 = optionstring;\n        d1 += \"set \";\n        d1 += \"sys.forced.blocksize=1M \";\n        d1 += path;\n        XrdOucString d2 = optionstring;\n        d2 += \"set \";\n        d2 += \"sys.forced.checksum=adler \";\n        d2 += path;\n        XrdOucString d3 = optionstring;\n        d3 += \"set \";\n        d3 += \"sys.forced.layout=archive \";\n        d3 += path;\n        XrdOucString d4 = optionstring;\n        d4 += \"set \";\n        d4 += \"sys.forced.nstripes=8 \";\n        d4 += path;\n        XrdOucString d5 = optionstring;\n        d5 += \"set \";\n        d5 += \"sys.forced.space=default \";\n        d5 += path;\n        XrdOucString d6 = optionstring;\n        d6 += \"set \";\n        d6 += \"sys.forced.blockchecksum=crc32c \";\n        d6 += path;\n        global_retc = com_attr((char*) d1.c_str()) || com_attr((char*) d2.c_str()) ||\n                      com_attr((char*) d3.c_str()) || com_attr((char*) d4.c_str()) ||\n                      com_attr((char*) d5.c_str()) || com_attr((char*) d6.c_str());\n        return (0);\n      }\n\n      if (value == \"qrain\") {\n        XrdOucString d1 = optionstring;\n        d1 += \"set \";\n        d1 += \"sys.forced.blocksize=1M \";\n        d1 += path;\n        XrdOucString d2 = optionstring;\n        d2 += \"set \";\n        d2 += \"sys.forced.checksum=adler \";\n        d2 += path;\n        XrdOucString d3 = optionstring;\n        d3 += \"set \";\n        d3 += \"sys.forced.layout=qrain \";\n        d3 += path;\n        XrdOucString d4 = optionstring;\n        d4 += \"set \";\n        d4 += \"sys.forced.nstripes=12 \";\n        d4 += path;\n        XrdOucString d5 = optionstring;\n        d5 += \"set \";\n        d5 += \"sys.forced.space=default \";\n        d5 += path;\n        XrdOucString d6 = optionstring;\n        d6 += \"set \";\n        d6 += \"sys.forced.blockchecksum=crc32c \";\n        d6 += path;\n        global_retc = com_attr((char*) d1.c_str()) || com_attr((char*) d2.c_str()) ||\n                      com_attr((char*) d3.c_str()) || com_attr((char*) d4.c_str()) ||\n                      com_attr((char*) d5.c_str()) || com_attr((char*) d6.c_str());\n        return (0);\n      }\n\n      goto com_attr_usage;\n    }\n\n    if (subcommand == \"set\" && key.endswith(\".forced.placementpolicy\")) {\n      XrdOucString ouc_policy;\n      eos::common::SymKey::DeBase64(value, ouc_policy);\n      std::string policy = ouc_policy.c_str();\n\n      // Check placement policy\n      if (policy != \"scattered\" &&\n          policy.rfind(\"hybrid:\", 0) != 0 &&\n          policy.rfind(\"gathered:\", 0) != 0) {\n        fprintf(stderr, \"Error: placement policy '%s' is invalid\\n\", policy.c_str());\n        global_retc = EINVAL;\n        return (0);\n      }\n\n      // Check geotag in case of hybrid or gathered policy\n      if (policy != \"scattered\") {\n        std::string targetgeotag = policy.substr(policy.find(':') + 1);\n        std::string tmp_geotag = eos::common::SanitizeGeoTag(targetgeotag);\n\n        if (tmp_geotag != targetgeotag) {\n          fprintf(stderr, \"%s\\n\", tmp_geotag.c_str());\n          global_retc = EINVAL;\n          return (0);\n        }\n      }\n    }\n\n    in += \"&mgm.subcmd=set&mgm.attr.key=\";\n    in += key;\n    in += \"&mgm.attr.value=\";\n    in += value;\n    in += \"&mgm.path=\";\n    in += path;\n  }\n\n  if (subcommand == \"get\") {\n    XrdOucString key = arg;\n    XrdOucString path = subtokenizer.GetToken();\n\n    if (!key.length() || !path.length()) {\n      goto com_attr_usage;\n    }\n\n    path = PathIdentifier(path.c_str(), true).c_str();\n    in += \"&mgm.subcmd=get&mgm.attr.key=\";\n    in += key;\n    in += \"&mgm.path=\";\n    in += path;\n  }\n\n  if (subcommand == \"fold\") {\n    XrdOucString path = arg;\n\n    if (!path.length()) {\n      goto com_attr_usage;\n    }\n\n    path = PathIdentifier(path.c_str(), true).c_str();\n    in += \"&mgm.subcmd=fold\";\n    in += \"&mgm.path=\";\n    in += path;\n  }\n\n  if ((subcommand == \"rm\") || (subcommand == \"unlink\")) {\n    XrdOucString key = arg;\n    XrdOucString path = subtokenizer.GetToken();\n\n    if (subcommand == \"unlink\") {\n      key = \"sys.attr.link\";\n      path = arg;\n    }\n\n    if (!key.length() || !path.length()) {\n      goto com_attr_usage;\n    }\n\n    path = PathIdentifier(path.c_str(), true).c_str();\n    in += \"&mgm.subcmd=rm&mgm.attr.key=\";\n    in += key;\n    in += \"&mgm.path=\";\n    in += path;\n  }\n\n  global_retc = output_result(client_command(in));\n  return (0);\ncom_attr_usage:\n  fprintf(stdout,\n          \"'[eos] attr ..' provides the extended attribute interface for directories in EOS.\\n\");\n  fprintf(stdout, \"Usage: attr [OPTIONS] ls|set|get|rm ...\\n\");\n  fprintf(stdout, \"Options:\\n\");\n  fprintf(stdout, \"attr [-r] ls <identifier> :\\n\");\n  fprintf(stdout,\n          \"                                                : list attributes of path\\n\");\n  fprintf(stdout, \" -r : list recursive on all directory children\\n\");\n  fprintf(stdout, \"attr [-r] set [-c] <key>=<value> <identifier> :\\n\");\n  fprintf(stdout,\n          \"                                                : set attributes of path (-r : recursive) (-c : only if attribute does not exist already)\\n\");\n  fprintf(stdout,\n          \"attr [-r] set default=replica|raiddp|raid5|raid6|archive|qrain <identifier> :\\n\");\n  fprintf(stdout,\n          \"                                                : set attributes of path (-r recursive) to the EOS defaults for replicas, dual-parity-raid (4+2), raid-6 (4+2) or archive layouts (5+3).\\n\");\n  //  fprintf(stdout,\"attr [-r] set default=reeds <path> :\\n\");\n  //  fprintf(stdout,\"                                                : set attributes of path (-r recursive) to the EOS defaults for reed solomon (4+2).\\n\");\n  fprintf(stdout, \" -r : set recursive on all directory children\\n\");\n  fprintf(stdout, \"attr [-r] [-V] get <key> <identifier> :\\n\");\n  fprintf(stdout,\n          \"                                                : get attributes of path (-r recursive)\\n\");\n  fprintf(stdout, \" -r : get recursive on all directory children\\n\");\n  fprintf(stdout, \" -V : only print the value\\n\");\n  fprintf(stdout, \"attr [-r] rm  <key> <identifier> :\\n\");\n  fprintf(stdout,\n          \"                                                : delete attributes of path (-r recursive)\\n\\n\");\n  fprintf(stdout, \" -r : delete recursive on all directory children\\n\");\n  fprintf(stdout, \"attr [-r] link <origin> <identifier> :\\n\");\n  fprintf(stdout,\n          \"                                                : link attributes of <origin> under the attributes of <identifier> (-r recursive)\\n\\n\");\n  fprintf(stdout, \" -r : apply recursive on all directory children\\n\");\n  fprintf(stdout, \"attr [-r] unlink <identifier> :\\n\");\n  fprintf(stdout,\n          \"                                                : remove attribute link of <identifier> (-r recursive)\\n\\n\");\n  fprintf(stdout, \" -r : apply recursive on all directory children\\n\");\n  fprintf(stdout, \"attr [-r] fold <identifier> :\\n\");\n  fprintf(stdout,\n          \"                                                : fold attributes of <identifier> if an attribute link is defined (-r recursive)\\n\\n\");\n  fprintf(stdout,\n          \"                                                  all attributes which are identical to the origin-link attributes are removed locally\\n\");\n  fprintf(stdout, \" -r : apply recursive on all directory children\\n\\n\");\n  fprintf(stdout, \"Remarks:\\n\");\n  fprintf(stdout,\n          \"         <identifier> = <path>|fid:<fid-dec>|fxid:<fid-hex>|cid:<cid-dec>|cxid:<cid-hex>\\n\"\n          \"                        deprecated pid:<pid-dec>|pxid:<pid-hex>\\n\");\n  fprintf(stdout,\n          \"         If <key> starts with 'sys.' you have to be member of the sudoers group to see these attributes or modify.\\n\\n\");\n  fprintf(stdout, \"Administrator Variables:\\n\");\n  // ---------------------------------------------------------------------------\n  fprintf(stdout,\n          \"         sys.forced.space=<space>              : enforces to use <space>    [configuration dependent]\\n\");\n  fprintf(stdout,\n          \"         sys.forced.group=<group>              : enforces to use <group>, where <group> is the numerical index of <space>.<n>    [configuration dependent]\\n\");\n  fprintf(stdout,\n          \"         sys.forced.layout=<layout>            : enforces to use <layout>   [<layout>=(plain,replica,raid5,raid6,archive,qrain)]\\n\");\n  fprintf(stdout,\n          \"         sys.forced.checksum=<checksum>        : enforces to use file-level checksum <checksum>\\n\");\n  fprintf(stdout,\n          \"                                              <checksum> = adler,crc32,crc32c,md5,sha\\n\");\n  fprintf(stdout,\n          \"         sys.forced.blockchecksum=<checksum>   : enforces to use block-level checksum <checksum>\\n\");\n  fprintf(stdout,\n          \"                                              <checksum> = adler,crc32,crc32c,md5,sha\\n\");\n  fprintf(stdout,\n          \"         sys.forced.nstripes=<n>               : enforces to use <n> stripes[<n>= 1..16]\\n\");\n  fprintf(stdout,\n          \"         sys.forced.blocksize=<w>              : enforces to use a blocksize of <w> - <w> can be 4k,64k,128k,256k or 1M \\n\");\n  fprintf(stdout,\n          \"         sys.forced.placementpolicy=<policy>[:geotag] : enforces to use replica/stripe placement policy <policy> [<policy>={scattered|hybrid:<geotag>|gathered:<geotag>}]\\n\");\n  fprintf(stdout,\n          \"         sys.forced.nouserplacementpolicy=1    : disables user defined replica/stripe placement policy\\n\");\n  fprintf(stdout,\n          \"         sys.forced.nouserlayout=1             : disables the user settings with user.forced.<xxx>\\n\");\n  fprintf(stdout,\n          \"         sys.forced.nofsselection=1            : disables user defined filesystem selection with environment variables for reads\\n\");\n  fprintf(stdout,\n          \"         sys.forced.bookingsize=<bytes>        : set's the number of bytes which get for each new created replica\\n\");\n  fprintf(stdout,\n          \"         sys.forced.minsize=<bytes>            : set's the minimum number of bytes a file to be stored must have\\n\");\n  fprintf(stdout,\n          \"         sys.forced.maxsize=<bytes>            : set's the maximum number of bytes a file to be stored can have\\n\");\n  fprintf(stdout,\n          \"         sys.forced.atomic=1                   : if present enforce atomic uploads e.g. files appear only when their upload is complete - during the upload they have the name <dirname>/.<basename>.<uuid>\\n\");\n  fprintf(stdout,\n          \"         sys.forced.leasetime=86400            : allows to overwrite the eosxd client provided leasetime with a new value\\n\");\n  fprintf(stdout,\n          \"         sys.forced.iotype=direct|sync|dsync|csync\"\n          \"                                               : force the given iotype for that directory\");\n  fprintf(stdout,\n          \"         sys.mtime.propagation=1               : if present a change under this directory propagates an mtime change up to all parents until the attribute is not present anymore\\n\");\n  fprintf(stdout,\n          \"         sys.allow.oc.sync=1                   : if present, OwnCloud clients can sync pointing to this subtree\\n\");\n  // ---------------------------------------------------------------------------\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"         sys.lru.expire.empty=<age>            : delete empty directories older than <age>\\n\");\n  fprintf(stdout,\n          \"         sys.lru.expire.match=[match1:<age1>,match2:<age2>:<<size2>,match3:<age3>:><size3>..]\\n\");\n  fprintf(stdout,\n          \"                                               : defines the rule that files with a given match will be removed if \\n\");\n  fprintf(stdout,\n          \"                                                 they haven't been accessed longer than <age> ago and they match the optional <size> criteria.\\n\");\n  fprintf(stdout,\n          \"                                                 <age> is defined like 3600,3600s,60min,1h,1mo,1y... <size> is defined like 1G, 100M...\\n\");\n  fprintf(stdout,\n          \"         sys.lru.lowwatermark=<low>\\n\");\n  fprintf(stdout,\n          \"         sys.lru.highwatermark=<high>        : if the watermark reaches more than <high> %%, files will be removed until the usage is reaching <low> %%.\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"         sys.lru.convert.match=[match1:<age1>,match2:<age2>,match3:<age3>:<<size3>,match4:<age4>:><size4>...]\\n\");\n  fprintf(stdout,\n          \"                                                 defines the rule that files with a given match will be converted to the layouts defined by sys.conversion.<match> when their access time reaches <age>. Optionally a size limitation can be given e.g. '*:1w:>1G' as 1 week old and larger than 1G or '*:1d:<1k' as one day old and smaller than 1k \\n\");\n  fprintf(stdout, \"\\n\");\n  // ---------------------------------------------------------------------------\n  fprintf(stdout,\n          \"         sys.stall.unavailable=<sec>           : stall clients for <sec> seconds if a needed file system is unavailable\\n\");\n  // ---------------------------------------------------------------------------\n  fprintf(stdout,\n          \"         sys.redirect.enoent=<host[:port]>     : redirect clients opening non existing files to <host[:port]>\\n\");\n  fprintf(stdout,\n          \"               => hence this variable has to be set on the directory at level 2 in the eos namespace e.g. /eos/public \\n\\n\");\n  fprintf(stdout,\n          \"         sys.redirect.enonet=<host[:port]>     : redirect clients opening inaccessible files to <host[:port]>\\n\");\n  fprintf(stdout,\n          \"               => hence this variable has to be set on the directory at level 2 in the eos namespace e.g. /eos/public \\n\\n\");\n  // ---------------------------------------------------------------------------\n  fprintf(stdout,\n          \"         sys.recycle=....                      : define the recycle bin for that directory - WARNING: never modify this variables via 'attr' ... use the 'recycle' interface\\n\");\n  fprintf(stdout,\n          \"         sys.recycle.keeptime=<seconds>        : define the time how long files stay in a recycle bin before final deletions takes place. This attribute has to defined on the recycle - WARNING: never modify this variables via 'attr' ... use the 'recycle' interface\\n\\n\");\n  fprintf(stdout,\n          \"         sys.recycle.keepratio=< 0 .. 1.0 >    : ratio of used/max quota for space and inodes in the recycle bin under which files are still kept in the recycle bin even if their lifetime has exceeded. If not defined pure lifetime policy will be applied \\n\\n\");\n  fprintf(stdout,\n          \"         sys.versioning=<n>                    : keep <n> versions of a file e.g. if you upload a file <n+10> times it will keep the last <n+1> versions\\n\");\n  // ---------------------------------------------------------------------------\n  fprintf(stdout,\n          \"         sys.acl=<acllist>                     : set's an ACL which is honored for open,rm & rmdir operations\\n\");\n  fprintf(stdout,\n          \"               => <acllist> = <rule1>,<rule2>...<ruleN> is a comma separated list of rules\\n\");\n  fprintf(stdout,\n          \"               => z:{u:<uid|username>|g:<gid|groupname>|egroup:<name>:{Aarw[o]Xximc(!u)\");\n  fprintf(stdout,\n          \"               e.g.: <acllist=\\\"u:300:rw,g:z2:rwo:egroup:eos-dev:rwx,u:500:rwm!d:u:600:rwqc\\\"\\n\\n\");\n  fprintf(stdout, \"               => user id 300 can read + write\\n\");\n  fprintf(stdout,\n          \"               => group z2 can read + write-once (create new files but can't delete)\\n\");\n  fprintf(stdout,\n          \"               => members of egroup 'eos-dev' can read & write & browse\\n\");\n  fprintf(stdout,\n          \"               => user id 500 can read + write into and chmod(m), but cannot delete the directory itself(!d)!\\n\");\n  fprintf(stdout,\n          \"               => user id 600 can read + write and administer the quota node(q) and can change the directory ownership in child directories(c)\\n\");\n  fprintf(stdout,\n          \"              '+d' : this tag can be used to overwrite a group rule excluding deletion via '!d' for certain users\\n\");\n  fprintf(stdout,\n          \"              '+u' : this tag can be used to overwrite a rul excluding updates via '!u'\\n\");\n  fprintf(stdout,\n          \"              'c'  : this tag can be used to grant chown permissions\\n\");\n  fprintf(stdout,\n          \"              'q'  : this tag can be used to grant quota administrator permissions\\n\");\n  fprintf(stdout,\n          \"               e.g.: sys.acl='z:!d' => 'z' is a rule for every user besides root e.g. nobody can delete here'b\\n\");\n  fprintf(stdout,\n          \"                     sys.acl='z:i' => directory becomes immutable\\n\");\n  fprintf(stdout,\n          \"         sys.eval.useracl                      : enables the evaluation of user acls if key is defined\\n\");\n  fprintf(stdout,\n          \"         sys.mask                              : masks all unix access permissions with a given mask .e.g sys.mask=775 disables writing to others\\n\");\n  fprintf(stdout,\n          \"         sys.owner.auth=<owner-auth-list>      : set's additional owner on a directory - open/create + mkdir commands will use the owner id for operations if the client is part of the owner authentication list\\n\");\n  fprintf(stdout,\n          \"         sys.owner.auth=*                      : every person with write permission will be mapped to the owner uid/gid pair of the parent directory and quota will be accounted on the owner uid/gid pair\\n\");\n  fprintf(stdout,\n          \"               => <owner-auth-list> = <auth1>:<name1>,<auth2>:<name2  e.g. krb5:nobody,gsi:DN=...\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"         sys.attr.link=<directory>             : symbolic links for attributes - all attributes of <directory> are visible in this directory and overwritten/extended by the local attributes\\n\");\n  // ---------------------------------------------------------------------------\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"         sys.http.index=<path>                 : show a static page as directory index instead of the dynamic one\\n\");\n  fprintf(stdout,\n          \"               => <path> can be a relative or absolute file path!\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"         sys.accounting.*=<value>              : set accounting attributes with value on the proc directory (common values) or quota nodes which translate to JSON output in the accounting report command\\n\");\n  fprintf(stdout,\n          \"               => You have to create such an attribute for each leaf value in the desired JSON.\\n\");\n  fprintf(stdout,\n          \"               => JSON objects: create a new key with a new name after a '.', e.g. sys.accounting.storagecapacity.online.totalsize=x or sys.accounting.storagecapacity.online.usedsize=y to add a new key-value to this object\\n\");\n  fprintf(stdout,\n          \"               => JSON arrays: place a continuous whole number from 0 to the attribute name, e.g. sys.accounting.accessmode.{0,1,2,...}\\n\");\n  fprintf(stdout,\n          \"               => array of objects: you can combine the above two to achieve arbitrary JSON output, e.g. sys.accounting.storageendpoints.0.name, sys.accounting.storageendpoints.0.id and sys.accounting.storageendpoints.1.name ...\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"         sys.proc=<opaque command>             : run arbitrary command on accessing the file\\n\");\n  fprintf(stdout,\n          \"               => <opaque command> command to execute in opaque format, e.g. mgm.cmd=accounting&mgm.subcmd=report&mgm.format=fuse\\n\");\n  fprintf(stdout, \"\\n\");\n  // ---------------------------------------------------------------------------\n  fprintf(stdout, \"User Variables:\\n\");\n  fprintf(stdout, \"         user.forced.space=<space>              : s.a.\\n\");\n  fprintf(stdout, \"         user.forced.layout=<layout>            : s.a.\\n\");\n  fprintf(stdout, \"         user.forced.checksum=<checksum>        : s.a.\\n\");\n  fprintf(stdout, \"         user.forced.blockchecksum=<checksum>   : s.a.\\n\");\n  fprintf(stdout, \"         user.forced.nstripes=<n>               : s.a.\\n\");\n  fprintf(stdout, \"         user.forced.blocksize=<w>              : s.a.\\n\");\n  fprintf(stdout,\n          \"         user.forced.placementpolicy=<policy>[:geotag] : s.a.\\n\");\n  fprintf(stdout,\n          \"         user.forced.nouserplacementpolicy=1            : s.a.\\n\");\n  fprintf(stdout, \"         user.forced.nouserlayout=1             : s.a.\\n\");\n  fprintf(stdout, \"         user.forced.nofsselection=1            : s.a.\\n\");\n  fprintf(stdout, \"         user.forced.atomic=1                   : s.a.\\n\");\n  fprintf(stdout, \"         user.stall.unavailable=<sec>           : s.a.\\n\");\n  fprintf(stdout, \"         user.acl=<acllist>                     : s.a.\\n\");\n  fprintf(stdout, \"         user.versioning=<n>                    : s.a.\\n\");\n  fprintf(stdout,\n          \"         user.tag=<tag>                         : Tag <tag> to group files for scheduling and flat file distribution. Use this tag to define datasets (if <tag> contains space use tag with quotes)\\n\");\n  fprintf(stdout, \"\\n\\n\");\n  fprintf(stdout,\n          \"--------------------------------------------------------------------------------\\n\");\n  fprintf(stdout, \"Examples:\\n\");\n  fprintf(stdout, \"...................\\n\");\n  fprintf(stdout, \"....... Layouts ...\\n\");\n  fprintf(stdout, \"...................\\n\");\n  fprintf(stdout, \"- set 2 replica as standard layout ...\\n\");\n  fprintf(stdout,\n          \"     |eos> attr set default=replica /eos/instance/2-replica\\n\");\n  fprintf(stdout,\n          \"--------------------------------------------------------------------------------\\n\");\n  fprintf(stdout, \"- set RAID-6 4+2 as standard layout ...\\n\");\n  fprintf(stdout, \"     |eos> attr set default=raid6 /eos/instance/raid-6\\n\");\n  fprintf(stdout,\n          \"--------------------------------------------------------------------------------\\n\");\n  fprintf(stdout, \"- set ARCHIVE 5+3 as standard layout ...\\n\");\n  fprintf(stdout, \"     |eos> attr set default=archive /eos/instance/archive\\n\");\n  fprintf(stdout,\n          \"--------------------------------------------------------------------------------\\n\");\n  fprintf(stdout, \"- set QRAIN 8+4 as standard layout ...\\n\");\n  fprintf(stdout, \"     |eos> attr set default=qrain /eos/instance/qrain\\n\");\n  fprintf(stdout,\n          \"--------------------------------------------------------------------------------\\n\");\n  fprintf(stdout,\n          \"- re-configure a layout for different number of stripes (e.g. 10) ...\\n\");\n  fprintf(stdout,\n          \"     |eos> attr set sys.forced.nstripes=10 /eos/instance/archive\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout, \"................\\n\");\n  fprintf(stdout, \"....... ACLs ...\\n\");\n  fprintf(stdout, \"................\\n\");\n  fprintf(stdout,\n          \"- forbid deletion and updates for group xx in a directory ...\\n\");\n  fprintf(stdout,\n          \"     |eos> attr set sys.acl=g:xx::!d!u /eos/instance/no-update-deletion\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout, \".....................\\n\");\n  fprintf(stdout, \"....... LRU Cache ...\\n\");\n  fprintf(stdout, \".....................\\n\");\n  fprintf(stdout,\n          \"- configure a volume based LRU cache with a low/high watermark \\n\");\n  fprintf(stdout,\n          \"  e.g. when the cache reaches the high watermark it cleans the oldest files until low-watermark is reached ...\\n\");\n  fprintf(stdout,\n          \"     |eos> quota set -g 99 -v 1T /eos/instance/cache/                           # define project quota on the cache\\n\");\n  fprintf(stdout,\n          \"     |eos> attr set sys.lru.lowwatermark=90  /eos/instance/cache/               \\n\");\n  fprintf(stdout,\n          \"     |eos> attr set sys.lru.highwatermark=95  /eos/instance/cache/               # define 90 as low and 95 as high watermark\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"--------------------------------------------------------------------------------\\n\");\n  fprintf(stdout, \"- configure clean-up of empty directories ...\\n\");\n  fprintf(stdout,\n          \"     |eos> attr set sys.lru.expire.empty=\\\"1h\\\" /eos/dev/instance/empty/          # remove automatically empty directories if they are older than 1 hour\\n\");\n  fprintf(stdout,\n          \"--------------------------------------------------------------------------------\\n\");\n  fprintf(stdout,\n          \"- configure a time based LRU cache with an expiration time ...\\n\");\n  fprintf(stdout,\n          \"     |eos> attr set sys.lru.expire.match=\\\"*.root:1mo,*.tgz:1w,*.root:6d:>1G\\\"  /eos/dev/instance/scratch/\\n\");\n  fprintf(stdout,\n          \"                                                                                # files with suffix *.root get removed after a month or after 6 days if they are bigger than 1GB, files with *.tgz after one week\\n\");\n  fprintf(stdout,\n          \"     |eos> attr set sys.lru.expire.match=\\\"*:1d\\\" /eos/dev/instance/scratch/      # all files older than a day are automatically removed\\n\");\n  fprintf(stdout,\n          \"--------------------------------------------------------------------------------\\n\");\n  fprintf(stdout,\n          \"- configure automatic layout conversion if a file has reached a defined age ...\\n\");\n  fprintf(stdout,\n          \"     |eos> attr set sys.lru.convert.match=\\\"*:1mo\\\" /eos/dev/instance/convert/    # convert all files older than a month to the layout defined next\\n\");\n  fprintf(stdout,\n          \"     |eos> attr set sys.lru.convert.match=\\\"*:1mo:>2G\\\" /eos/dev/instance/convert/# convert all files older than a month and larger than 2Gb to the layout defined next\\n\");\n  fprintf(stdout,\n          \"     |eos> attr set sys.conversion.*=20640542 /eos/dev/instance/convert/          # define the conversion layout (hex) for the match rule '*' - this is RAID6 4+2 \\n\");\n  fprintf(stdout,\n          \"     |eos> attr set sys.conversion.*=20640542|gathered:site1::rack2 /eos/dev/instance/convert/ # same thing specifying a placement policy for the replicas/stripes \\n\");\n  fprintf(stdout,\n          \"--------------------------------------------------------------------------------\\n\");\n  fprintf(stdout,\n          \"- configure automatic layout conversion if a file has not been used during the last 6 month ...\\n\");\n  fprintf(stdout,\n          \"     |eos> attr set sys.lru.convert.match=\\\"*:6mo\\\" /eos/dev/instance/convert/    # convert all files older than a month to the layout defined next\\n\");\n  fprintf(stdout,\n          \"     |eos> attr set sys.conversion.*=20640542  /eos/dev/instance/convert/         # define the conversion layout (hex) for the match rule '*' - this is RAID6 4+2 \\n\");\n  fprintf(stdout,\n          \"     |eos> attr set sys.conversion.*=20640542|gathered:site1::rack2 /eos/dev/instance/convert/ # same thing specifying a placement policy for the replicas/stripes \\n\");\n  fprintf(stdout,\n          \"--------------------------------------------------------------------------------\\n\");\n  fprintf(stdout, \".......................\\n\");\n  fprintf(stdout, \"....... Recycle Bin ...\\n\");\n  fprintf(stdout, \".......................\\n\");\n  fprintf(stdout,\n          \"- configure a recycle bin with 1 week garbage collection and 100 TB space ...\\n\");\n  fprintf(stdout,\n          \"     |eos> recycle config --lifetime 604800                                     # set the lifetime to 1 week\\n\");\n  fprintf(stdout,\n          \"     |eos> recycle config --size 100T                                           # set the size of 100T\\n\");\n  fprintf(stdout,\n          \"     |eos> recycle config --add-bin /eos/dev/instance/                          # add's the recycle bin to the subtree /eos/dev/instance\\n\");\n  fprintf(stdout, \".......................\\n\");\n  fprintf(stdout, \".... Atomic Uploads ...\\n\");\n  fprintf(stdout, \".......................\\n\");\n  fprintf(stdout,\n          \"     |eos> attr set sys.forced.atomic=1 /eos/dev/instance/atomic/\\n\");\n  fprintf(stdout, \".......................\\n\");\n  fprintf(stdout, \".... Attribute Link ...\\n\");\n  fprintf(stdout, \".......................\\n\");\n  fprintf(stdout,\n          \"     |eos> attr set sys.attr.link=/eos/dev/origin-attr/ /eos/dev/instance/attr-linked/\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_backup.cc",
    "content": "//------------------------------------------------------------------------------\n// File: com_backup.cc\n// Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2014 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include <sstream>\n#include <memory>\n#include <sys/time.h>\n/*----------------------------------------------------------------------------*/\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <XrdCl/XrdClFile.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n/*----------------------------------------------------------------------------*/\n\nint com_backup(char* arg1)\n{\n  XrdOucString in, token;\n  std::ostringstream in_cmd;\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdCl::URL src_url;\n  XrdCl::URL dst_url;\n  XrdOucString src_surl = subtokenizer.GetToken();\n  XrdOucString dst_surl = subtokenizer.GetToken();\n\n  // Check if minimal input is present\n  if (!src_surl.length() || !dst_surl.length()) {\n    goto com_backup_usage;\n  }\n\n  // Check that these are valid XRootD URLs\n  if (!src_url.FromString(src_surl.c_str()) ||\n      !dst_url.FromString(dst_surl.c_str())) {\n    goto com_backup_usage;\n  }\n\n  in_cmd << \"mgm.cmd=backup&mgm.backup.src=\" << src_surl\n         << \"&mgm.backup.dst=\" << dst_surl;\n  token = subtokenizer.GetToken();\n\n  while (token.length()) {\n    if (!token.beginswith(\"--\")) {\n      goto com_backup_usage;\n    }\n\n    // Get the type of incremental backup either by mtime or by ctime\n    if (token == \"--ctime\" || token == \"--mtime\") {\n      if (token == \"--ctime\") {\n        in_cmd << \"&mgm.backup.ttime=ctime\";\n      } else if (token == \"--mtime\") {\n        in_cmd << \"&mgm.backup.ttime=mtime\";\n      }\n\n      // Get the interval time\n      token = subtokenizer.GetToken();\n\n      if (!token.length()) {\n        goto com_backup_usage;\n      }\n\n      char last = token[token.length() - 1];\n      long int seconds = 0;\n\n      if (last == 's') {\n        seconds = 1;  // seconds\n      } else if (last == 'm') {\n        seconds = 60;  //minutes\n      } else if (last == 'h') {\n        seconds = 3600;  // hours\n      } else if (last == 'd') {\n        seconds = 24 * 3600;  // days\n      } else {\n        goto com_backup_usage;\n      }\n\n      // Try to convert the time window to integer value\n      char* p_end = (char*)(token.c_str() + token.length());\n      long int value = strtol(token.c_str(), &p_end, 10);\n\n      if (value == 0L) {\n        goto com_backup_usage;\n      }\n\n      value *= seconds;\n      struct timeval tv;\n\n      if (gettimeofday(&tv, NULL)) {\n        fprintf(stderr, \"Error getting current timestamp\\n\");\n        goto com_backup_usage;\n      }\n\n      in_cmd << \"&mgm.backup.vtime=\" << (tv.tv_sec - value);\n    } else if (token == \"--excl_xattr\") {\n      // Exclude certain directory extended attributes from being enforced\n      // and checked\n      token = subtokenizer.GetToken();\n\n      if (!token.length()) {\n        goto com_backup_usage;\n      }\n\n      in_cmd << \"&mgm.backup.excl_xattr=\" << token.c_str();\n    } else {\n      goto com_backup_usage;\n    }\n\n    token = subtokenizer.GetToken();\n  }\n\n  in = in_cmd.str().c_str();\n  global_retc = output_result(client_command(in, true));\n  return 0;\ncom_backup_usage:\n  std::ostringstream oss;\n  oss << \"usage: backup <src_url> <dst_url> [options] \" << std::endl\n      << \" \" << std::endl\n      << \" optional arguments: \" << std::endl\n      << \" --ctime|mtime <val>s|m|h|d use the specified timewindow to select entries for backup\"\n      << std::endl\n      << \" --excl_xattr val_1[,val_2]...[,val_n] extended attributes which are not enforced and\"\n      << std::endl\n      << \"              also not checked during the verification step\" << std::endl;\n  fprintf(stdout, \"%s\", oss.str().c_str());\n  global_retc = EINVAL;\n  return 0;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_cd.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_cd.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <fcntl.h>\n#include <unistd.h>\n\n/* Change working directory &*/\nint\ncom_cd(char* arg1)\n{\n  static XrdOucString opwd = \"/\";\n  static XrdOucString oopwd = \"/\";\n  XrdOucString lsminuss;\n  XrdOucString newpath;\n  XrdOucString oldpwd;\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString arg = subtokenizer.GetToken();\n\n  if ((arg.beginswith(\"--help\")) || (arg.beginswith(\"-h\"))) {\n    goto com_cd_usage;\n  }\n\n  // cd -\n  if (arg == \"-\") {\n    oopwd = opwd;\n    arg = (char*) opwd.c_str();\n  }\n\n  opwd = gPwd;\n  newpath = abspath(arg.c_str());\n  oldpwd = gPwd;\n\n  // cd ~ (home)\n  if ((arg == \"\") || (arg == \"~\")) {\n    if (getenv(\"EOS_HOME\")) {\n      newpath = abspath(getenv(\"EOS_HOME\"));\n    } else {\n      fprintf(stderr, \"warning: there is no home directory defined via EOS_HOME\\n\");\n      newpath = opwd;\n    }\n  }\n\n  gPwd = newpath;\n\n  if ((!gPwd.endswith(\"/\")) && (!gPwd.endswith(\"/\\\"\"))) {\n    gPwd += \"/\";\n  }\n\n  // filter \"/./\";\n  while (gPwd.replace(\"/./\", \"/\")) {\n  }\n\n  // filter \"..\";\n  int dppos;\n  dppos = 0;\n\n  while ((dppos = gPwd.find(\"/../\")) != STR_NPOS) {\n    if (dppos == 0) {\n      gPwd = oldpwd;\n      break;\n    }\n\n    int rpos = gPwd.rfind(\"/\", dppos - 1);\n\n    //    fprintf(stdout,\"%s %d %d\\n\", gPwd.c_str(), dppos, rpos);\n    if (rpos != STR_NPOS) {\n      //      fprintf(stdout,\"erasing %d %d\", rpos, dppos-rpos+3);\n      gPwd.erase(rpos, dppos - rpos + 3);\n    } else {\n      gPwd = oldpwd;\n      break;\n    }\n  }\n\n  if ((!gPwd.endswith(\"/\")) && (!gPwd.endswith(\"/\\\"\"))) {\n    gPwd += \"/\";\n  }\n\n  // check if this exists, otherwise go back to oldpwd\n  lsminuss = \"mgm.cmd=cd&mgm.path=\";\n  lsminuss += gPwd;\n  lsminuss += \"&mgm.option=s\";\n  global_retc = output_result(client_command(lsminuss));\n\n  if (global_retc) {\n    gPwd = oldpwd;\n  } else {\n    if (pwdfile.length()) {\n      // store the last used directory\n      int cfd = open(pwdfile.c_str(), O_CREAT | O_TRUNC | O_RDWR, S_IRWXU);\n\n      if (cfd >= 0) {\n        if ((::write(cfd, gPwd.c_str(), gPwd.length())) != gPwd.length()) {\n          fprintf(stderr, \"warning: unable to store CWD to %s [errno=%d]\\n\",\n                  pwdfile.c_str(), errno);\n        }\n\n        close(cfd);\n      } else {\n        fprintf(stderr, \"warning: unable to store CWD to %s\\n\", pwdfile.c_str());\n      }\n    }\n  }\n\n  return (0);\ncom_cd_usage:\n  fprintf(stdout,\n          \"'[eos] cd ...' provides the namespace change directory command in EOS.\\n\");\n  fprintf(stdout, \"Usage: cd <dir>|-|..|~\\n\");\n  fprintf(stdout, \"Options:\\n\");\n  fprintf(stdout, \"cd <dir> :\\n\");\n  fprintf(stdout,\n          \"                                                  change into direcotry <dir>. If it does not exist, the current directory will stay as before!\\n\");\n  fprintf(stdout, \"cd - :\\n\");\n  fprintf(stdout,\n          \"                                                  change into the previous directory\\n\");\n  fprintf(stdout, \"cd .. :\\n\");\n  fprintf(stdout,\n          \"                                                  change into the directory one level up\\n\");\n  fprintf(stdout, \"cd ~ :\\n\");\n  fprintf(stdout,\n          \"                                                  change into the directory defined via the environment variable EOS_HOME\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_chmod.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_chmod.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n/*----------------------------------------------------------------------------*/\n\n/* Mode Interface */\nint\ncom_chmod(char* arg1)\n{\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString mode = subtokenizer.GetToken();\n  XrdOucString option = \"\";\n  XrdOucString in = \"mgm.cmd=chmod\";\n  XrdOucString arg = \"\";\n\n  if (mode.beginswith(\"-\")) {\n    option = mode;\n    option.erase(0, 1);\n    mode = subtokenizer.GetToken();\n    in += \"&mgm.option=\";\n    in += option;\n  }\n\n  XrdOucString path = subtokenizer.GetToken();\n\n  if (wants_help(arg1)) {\n    goto com_chmod_usage;\n  }\n\n  if (!path.length() || !mode.length()) {\n    goto com_chmod_usage;\n  }\n\n  path = abspath(path.c_str());\n  in += \"&mgm.path=\";\n  in += path;\n  in += \"&mgm.chmod.mode=\";\n  in += mode;\n  global_retc = output_result(client_command(in));\n  return (0);\ncom_chmod_usage:\n  fprintf(stdout,\n          \"usage: chmod [-r] <mode> <path>                             : set mode for <path> (-r recursive)\\n\");\n  fprintf(stdout,\n          \"                 <mode> can be only numerical like 755, 644, 700\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_chown.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_chown.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n/*----------------------------------------------------------------------------*/\n\n/* Owner Interface */\nint\ncom_chown(char* arg1)\n{\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString owner;\n  XrdOucString option = \"\";\n  XrdOucString in = \"mgm.cmd=chown\";\n  XrdOucString arg = \"\";\n  bool bypasshelp = false;\n\n  while ((owner = subtokenizer.GetToken()).length() && owner.length()) {\n    if (owner.beginswith(\"-\")) {\n      XrdOucString loption = owner;\n\n      if (loption == \"--nodereference\") {\n        loption = \"h\";\n      }\n\n      loption.erase(0, 1);\n      option += loption;\n      bypasshelp = true;\n    } else {\n      break;\n    }\n  }\n\n  XrdOucString path = subtokenizer.GetToken();\n\n  if (option.length()) {\n    in += \"&mgm.chown.option=\";\n    in += option;\n  }\n\n  if (!bypasshelp && wants_help(arg1)) {\n    goto com_chown_usage;\n  }\n\n  if (!path.length() || !owner.length()) {\n    goto com_chown_usage;\n  }\n\n  path = abspath(path.c_str());\n  in += \"&mgm.path=\";\n  in += path;\n  in += \"&mgm.chown.owner=\";\n  in += owner;\n  global_retc = output_result(client_command(in));\n  return (0);\ncom_chown_usage:\n  fprintf(stdout,\n          \"Usage: chown [-r] [-h --nodereference] <owner>[:<group>] <path>\\n\");\n  fprintf(stdout, \"       chown [-r] :<group> <path>\\n\");\n  fprintf(stdout,\n          \"'[eos] chown ..' provides the change owner interface of EOS.\\n\");\n  fprintf(stdout,\n          \"<path> is the file/directory to modify, <owner> has to be a user id or user name. <group> is optional and has to be a group id or group name.\\n\");\n  fprintf(stdout, \"To modify only the group use :<group> as identifier!\\n\");\n  fprintf(stdout,\n          \"Remark: if you use the -r -h option and path points to a link the owner of the link parent will also be updated!\");\n  fprintf(stdout, \"Options:\\n\");\n  fprintf(stdout, \"                  -r : recursive\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_clear.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_clear.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include <string.h>\n/*----------------------------------------------------------------------------*/\n\n\n/* Clear the terminal screen */\nint\ncom_clear (char *arg) {\n  if (!strcmp(arg, \"-h\") || !strcmp(arg, \"--help\") ||\n      !strcmp(arg, \"\\\"-h\\\"\") || !strcmp(arg, \"\\\"--help\\\"\")) {\n    fprintf(stdout,\"Usage: clear\\n\");\n    fprintf(stdout,\"'[eos] clear' is equivalent to the interactive shell command to clear the screen.\\n\");\n    return (0);\n  }\n\n  int rc = system(\"clear\");\n  return (rc);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_cp.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_cp.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include <iomanip>\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"common/Path.hh\"\n#include \"common/StringConversion.hh\"\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdCl/XrdClURL.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n/*----------------------------------------------------------------------------*/\n\nint\ncom_cp_usage()\n{\n  fprintf(stdout,\n          \"Usage: cp [--async] [--atomic] [--rate=<rate>] [--streams=<n>] [--depth=<d>] [--checksum] [--no-overwrite|-k] [--preserve|-p] [--recursive|-r|-R] [-s|--silent] [-a] [-n] [-S] [-d[=][<lvl>] <src> <dst>\\n\");\n  fprintf(stdout, \"'[eos] cp ..' provides copy functionality to EOS.\\n\");\n  fprintf(stdout,\n          \"          <src>|<dst> can be root://<host>/<path>, a local path /tmp/../ or an eos path /eos/ in the connected instance\\n\");\n  fprintf(stdout, \"Options:\\n\");\n  fprintf(stdout,\n          \"       --atomic        : run an atomic upload where files are only visible with the target name when their are completely uploaded [ adds ?eos.atomic=1 to the target URL ]\\n\");\n  fprintf(stdout, \"       --rate          : limit the cp rate to <rate>\\n\");\n  fprintf(stdout, \"       --streams       : use <#> parallel streams\\n\");\n  fprintf(stdout, \"       --depth         : depth for recursive copy\\n\");\n  fprintf(stdout, \"       --checksum      : output the checksums\\n\");\n  fprintf(stdout,\n          \"       -a              : append to the target, don't truncate\\n\");\n  fprintf(stdout, \"       -p              : create destination directory\\n\");\n  fprintf(stdout, \"       -n              : hide progress bar\\n\");\n  fprintf(stdout, \"       -S              : print summary\\n\");\n  fprintf(stdout,\n          \"   -d | --debug          : enable debug information (optional <lvl>=1|2|3)\\n\");\n  fprintf(stdout,\n          \"   -s | --silent         : no output outside error messages\\n\");\n  fprintf(stdout,\n          \"   -k | --no-overwrite   : disable overwriting of files\\n\");\n  fprintf(stdout,\n          \"   -P | --preserve       : preserves file creation and modification time from the source\\n\");\n  fprintf(stdout,\n          \"   -r | -R | --recursive : copy source location recursively\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout, \"Remark: \\n\");\n  fprintf(stdout,\n          \"       If you deal with directories always add a '/' in the end of source or target paths e.g. if the target should be a directory and not a file put a '/' in the end. To copy a directory hierarchy use '-r' and source and target directories terminated with '/' !\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout, \"Examples: \\n\");\n  fprintf(stdout,\n          \"       eos cp /var/data/myfile /eos/foo/user/data/                   : copy 'myfile' to /eos/foo/user/data/myfile\\n\");\n  fprintf(stdout,\n          \"       eos cp /var/data/ /eos/foo/user/data/                         : copy all plain files in /var/data to /eos/foo/user/data/\\n\");\n  fprintf(stdout,\n          \"       eos cp -r /var/data/ /eos/foo/user/data/                      : copy the full hierarchy from /var/data/ to /eos/foo/user/data/ => empty directories won't show up on the target!\\n\");\n  fprintf(stdout,\n          \"       eos cp -r --checksum --silent /var/data/ /eos/foo/user/data/  : copy the full hierarchy and just printout the checksum information for each file copied!\\n\");\n  fprintf(stdout, \"\\nS3:\\n\");\n  fprintf(stdout, \"      URLs have to be written as:\\n\");\n  fprintf(stdout,\n          \"         as3://<hostname>/<bucketname>/<filename> as implemented in ROOT\\n\");\n  fprintf(stdout,\n          \"      or as3:<bucketname>/<filename> with environment variable S3_HOSTNAME set\\n\");\n  fprintf(stdout, \"     and as3:....?s3.id=<id>&s3.key=<key>\\n\\n\");\n  fprintf(stdout, \"      The access id can be defined in 3 ways:\\n\");\n  fprintf(stdout,\n          \"      env S3_ACCESS_ID=<access-id>          [as used in ROOT  ]\\n\");\n  fprintf(stdout,\n          \"      env S3_ACCESS_KEY_ID=<access-id>      [as used in libs3 ]\\n\");\n  fprintf(stdout,\n          \"      <as3-url>?s3.id=<access-id>           [as used in EOS transfers ]\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout, \"      The access key can be defined in 3 ways:\\n\");\n  fprintf(stdout,\n          \"      env S3_ACCESS_KEY=<access-key>        [as used in ROOT ]\\n\");\n  fprintf(stdout,\n          \"      env S3_SECRET_ACCESS_KEY=<access-key> [as used in libs3 ]\\n\");\n  fprintf(stdout,\n          \"      <as3-url>?s3.key=<access-key>         [as used in EOS transfers ]\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"      If <src> and <dst> are using S3, we are using the same credentials on both ends and the target credentials will overwrite source credentials!\\n\");\n  return (EINVAL);\n}\n\n/* Helper types */\nenum Protocol {\n  HTTP, HTTPS, GSIFTP,\n  S3, AS3, XROOT,\n  EOS, LOCAL, UNKNOWN\n};\n\nstruct File_t {\n  XrdOucString name;\n  XrdOucString opaque;\n  Protocol protocol;\n  timespec atime;\n  timespec mtime;\n  unsigned long long size;\n\n  File_t() : name(\"\"), opaque(\"\"), protocol(Protocol::UNKNOWN), size(0) { }\n};\n\n/* Helper functions */\nint run_eos_command(const char* cmdline, std::vector<XrdOucString>& result);\nint run_command(const char* cmdline, std::vector<XrdOucString>& result);\nconst char* absolute_path(const char* path);\nbool is_dir(const char* path, Protocol protocol, struct stat* buf = NULL);\nXrdOucString process_symlink(XrdOucString path);\nconst char* setup_s3_environment(XrdOucString path, XrdOucString opaque);\nstd::string eos_roles_opaque();\nint do_stat(const char* path, Protocol protocol, struct stat& buf);\nint check_protocol_tool(const char* path);\nProtocol get_protocol(XrdOucString path);\nconst char* protocol_to_string(Protocol protocol);\nint parse_debug_level(XrdOucString option);\n\n/* eos cp command */\nint\ncom_cp(char* argin)\n{\n  XrdOucString rate = \"\";\n  XrdOucString streams = \"0\";\n  XrdOucString atomic = \"\";\n  std::vector<XrdOucString> source_find_list;\n  std::vector<XrdOucString> source_basepath_list;\n  std::vector<File_t> source_list;\n  File_t target;\n  bool target_is_stdout;\n  bool target_is_dir = false;\n  bool recursive = false;\n  bool summary = false;\n  bool noprogress = false;\n  bool append = false;\n  bool makeparent = false;\n  bool debug = false;\n  int debug_level = 0;\n  bool checksums = false;\n  bool silent = false;\n  bool nooverwrite = false;\n  bool preserve = false;\n  unsigned long long copysize = 0;\n  unsigned long long copiedsize = 0;\n  unsigned long depth = 0;\n  struct timeval start_time, end_time;\n  struct timezone tz;\n  int files_copied = 0;\n  int retc = 0;\n  // Check if this is an 'async' command\n  XrdOucString sarg = argin;\n  // ----------------------------------------------------------------------------\n  // Parse arguments\n  // ----------------------------------------------------------------------------\n  eos::common::StringTokenizer subtokenizer(argin);\n  subtokenizer.GetLine();\n\n  do {\n    XrdOucString option = subtokenizer.GetToken();\n\n    if (!option.length()) {\n      break;\n    }\n\n    if (option.beginswith(\"--rate=\")) {\n      rate = option;\n      rate.replace(\"--rate=\", \"\");\n    } else if (option.beginswith(\"--streams=\")) {\n      streams = option;\n      streams.replace(\"--streams=\", \"\");\n    } else if ((option == \"--recursive\") ||\n               (option == \"-R\") || (option == \"-r\")) {\n      recursive = true;\n    } else if (option == \"-n\") {\n      noprogress = true;\n    } else if (option == \"-a\") {\n      append = true;\n    } else if (option == \"-p\") {\n      makeparent = true;\n    } else if (option == \"-S\") {\n      summary = true;\n    } else if ((option == \"-s\") || (option == \"--silent\")) {\n      silent = true;\n    } else if ((option == \"-k\") || (option == \"--no-overwrite\")) {\n      nooverwrite = true;\n    } else if (option == \"--checksum\") {\n      checksums = true;\n    } else if ((option.beginswith(\"-d\")) || (option.beginswith(\"--debug\"))) {\n      if ((debug_level = parse_debug_level(option)) < 0) {\n        return com_cp_usage();\n      }\n\n      debug = true;\n    } else if ((option == \"--preserve\") || (option == \"-P\")) {\n      preserve = true;\n    } else if (option == \"--atomic\") {\n      atomic = \"&eos.atomic=1\";\n    } else if (option.beginswith(\"--depth=\")) {\n      option.replace(\"--depth=\", \"\");\n\n      try {\n        depth = std::stoul(option.c_str());\n      } catch (...) {\n        fprintf(stderr, \"error: invalid value for <depth>=%s\", option.c_str());\n        return com_cp_usage();\n      }\n    } else if (option.beginswith(\"-\")) {\n      return com_cp_usage();\n    } else {\n      if ((!option.beginswith(\"/eos/\")) || (!option.beginswith(\"root:/\"))) {\n        // Do this since tokenizer sealed the path when extracting the token!\n        eos::common::StringConversion::UnsealXrdPath(option);\n      }\n\n      source_find_list.emplace_back(option.c_str());\n      break;\n    }\n  } while (true);\n\n  if (silent || !hasterminal) {\n    noprogress = true;\n  }\n\n  if (recursive) {\n    makeparent = true;\n  }\n\n  // Store list of source locations + target destination\n  XrdOucString nextarg = subtokenizer.GetToken();\n  XrdOucString lastarg = subtokenizer.GetToken();\n\n  while (lastarg.length()) {\n    source_find_list.emplace_back(nextarg.c_str());\n    nextarg = lastarg;\n    lastarg = subtokenizer.GetToken();\n  }\n\n  target.name = nextarg;\n\n  if (!target.name.length()) {\n    fprintf(stderr, \"warning: no target specified. Please view 'eos cp --help'.\\n\");\n    global_retc = 0;\n    return 0;\n  }\n\n  // --------------------------------------------------------------------------\n  // Expand source list into final list to copy.\n  // This means interpreting the '*' character in file names\n  // and traversing directories for the recursive flag.\n  // Every source path also has an associated base path,\n  // which will get appended to the target.\n  // --------------------------------------------------------------------------\n\n  for (size_t i = 0; i < source_find_list.size(); i++) {\n    std::vector<XrdOucString> files;\n    XrdOucString source = source_find_list[i];\n    XrdOucString source_opaque;\n    XrdOucString basepath = \"\";\n    Protocol protocol;\n    std::string sprotocol = \"\";\n    int opos = source.find(\"?\");\n    bool wildcard = false;\n    files.clear();\n\n    // Extract opaque info\n    if (opos != STR_NPOS) {\n      source_opaque = source;\n      source_opaque.erase(0, opos + 1);\n      source.erase(opos);\n    }\n\n    // Identify protocol\n    protocol = get_protocol(source.c_str());\n\n    if (protocol == Protocol::UNKNOWN) {\n      fprintf(stderr, \"warning: %s -- protocol not recognized. Skipping path..\",\n              source.c_str());\n      continue;\n    }\n\n    // Convert local to absolute path\n    const char* abs_path = absolute_path(source.c_str());\n    source = abs_path;\n    free((char*)abs_path);\n\n    // Check if source is a directory\n    if (!source.endswith(\"/\") && is_dir(source.c_str(), protocol, NULL)) {\n      source.append(\"/\");\n    }\n\n    // Extract file name and parent path\n    const char* filepath = source.c_str();\n\n    // URLs need different processing in order to extract the path\n    if ((protocol != Protocol::EOS) && (protocol != Protocol::LOCAL)) {\n      XrdOucString sprot, hostport;\n      filepath = eos::common::StringConversion::ParseUrl(source.c_str(),\n                 sprot, hostport);\n\n      if (!filepath) {\n        fprintf(stderr, \"error: cannot process file=%s [protocol=%s]\\n\",\n                source.c_str(), protocol_to_string(protocol));\n        continue;\n      }\n    }\n\n    eos::common::Path cPath(filepath);\n    basepath = cPath.GetParentPath();\n\n    if ((source.find(\"*\") != STR_NPOS) || (source.endswith(\"/\"))) {\n      std::string cmdtext;\n\n      if ((protocol != Protocol::EOS) && (protocol != Protocol::LOCAL)) {\n        fprintf(stderr, \"error: %s -- path expansion not implemented for %s protocol.\"\n                \" Skipping path..\\n\", source.c_str(), protocol_to_string(protocol));\n        continue;\n      }\n\n      // Get all paths matching wildcard\n      if (source.find(\"*\") != STR_NPOS) {\n        // Will use 'ls -lF' combined with grep to identify matches\n        // ls -l[F|p] <path> | awk 'NF == 9 {print $9}' [ | egrep \"<match>\" ]\n        // Note: eos::common::Path removes trailing '/'!\n        XrdOucString basename = cPath.GetName();\n\n        if (source.endswith(\"/\")) {\n          basename.append(\"/\");\n        }\n\n        // Wildcards are supported only in the basename\n        if (basename.find(\"*\") == STR_NPOS) {\n          fprintf(stderr, \"warning: %s -- wildcards not supported outside basename.\"\n                  \" Skipping path..\\n\", source.c_str());\n          continue;\n        }\n\n        XrdOucString match = basename.c_str();\n        wildcard = true;\n\n        if (!match.beginswith(\"*\")) {\n          match.insert(\"^\", 0);\n        }\n\n        if (!match.endswith(\"*\"))   {\n          match.append(\"$\");\n        }\n\n        match.replace(\"*\", \".*\");\n        // Construct command text\n        cmdtext = \"ls -l\";\n        cmdtext += (protocol == Protocol::EOS) ? \"F \" : \"p \";\n        cmdtext += basepath.c_str();\n        cmdtext +=\n          \" | awk '{out=$9; for (i=10; i<=NF; i++) {out=out\\\" \\\"$i}; print out}' | egrep \\\"\";\n        cmdtext += match.c_str();\n        cmdtext += \"\\\"\";\n      } else if (source.endswith(\"/\")) {\n        // Get all files within directory\n\n        // Will use 'find' to identify files\n        // local file: find <path> [-maxdepth <depth>] -follow -type f\n        // eos file:   find -f [--maxdepth <depth>] <path>\n        if (!recursive) {\n          fprintf(stderr, \"warning: omitting directory %s\\n\", source.c_str());\n          continue;\n        }\n\n        // Enclose source path in quotes, as the path may contain whitespace\n        std::stringstream ss;\n        ss.clear();\n        ss << std::quoted(source.c_str());\n        source = ss.str().c_str();\n        // Capture only last directory\n        // This will end up appended to the target\n        std::string smaxdepth = \" \";\n\n        if (depth != 0) {\n          smaxdepth = \" -maxdepth \";\n          smaxdepth += std::to_string(depth);\n          smaxdepth += \" \";\n\n          if (protocol == Protocol::EOS) {\n            smaxdepth.insert(1, \"-\");\n          }\n        }\n\n        cmdtext = \"find \";\n\n        if (protocol == Protocol::EOS) {\n          cmdtext += \"-f\";\n          cmdtext += smaxdepth.c_str();\n          cmdtext += source.c_str();\n        } else {\n          cmdtext += source.c_str();\n          cmdtext += smaxdepth.c_str();\n          cmdtext += \"-follow -type f\";\n        }\n      }\n\n      cmdtext += \" 2> /dev/null\";\n\n      if (debug) {\n        fprintf(stderr, \"[eos-cp] running: %s\\n\", cmdtext.c_str());\n      }\n\n      int rc = (protocol == Protocol::EOS)  ?\n               run_eos_command(cmdtext.c_str(), files) :\n               run_command(cmdtext.c_str(), files);\n\n      if (rc && !files.size()) {\n        fprintf(stderr, \"warning: could not expand source: %s\\n\", source.c_str());\n        global_retc = rc;\n        return -1;\n      }\n    } else {\n      files.emplace_back(source.c_str());\n    }\n\n    for (auto& file : files) {\n      // Check if path expansion discovered a symlink\n      if (file.find(\" -> \") != STR_NPOS) {\n        file = process_symlink(file.c_str());\n      }\n\n      if (wildcard) {\n        file.insert(basepath.c_str(), 0);\n        source_find_list.emplace_back(file.c_str());\n        continue;\n      }\n\n      if (debug) {\n        fprintf(stderr, \"[eos-cp] Copy list: %s\\n\", file.c_str());\n      }\n\n      File_t source_file;\n      source_file.name = file.c_str();\n      source_file.opaque = source_opaque.c_str();\n      if (getenv(\"EOSAUTHZ\")) {\n\tsource_file.opaque += \"&authz=\";\n\tsource_file.opaque += getenv(\"EOSAUTHZ\");\n      }\n      source_file.protocol = protocol;\n      source_list.emplace_back(source_file);\n      source_basepath_list.emplace_back(basepath.c_str());\n    }\n  }\n\n  // Check if there is any file in the list\n  if (source_list.empty()) {\n    fprintf(stderr, \"warning: found zero files to copy!\\n\");\n    global_retc = 0;\n    return 0;\n  }\n\n  // --------------------------------------------------------------------------\n  // Process target path\n  // --------------------------------------------------------------------------\n  bool target_exists;\n  struct stat target_stat;\n  target.protocol = get_protocol(target.name.c_str());\n\n  // Make sure executable to reach target exists\n  if (check_protocol_tool(target.name.c_str())) {\n    return -1;\n  }\n\n  // Handle opaque information for target\n  if (target.protocol != Protocol::LOCAL) {\n    int qpos = target.name.find(\"?\");\n\n    if (qpos != STR_NPOS) {\n      target.opaque = target.name.c_str();\n      target.opaque.keep(qpos + 1);\n      target.name.erase(qpos);\n    }\n\n    // Seal the target name\n    if (target.protocol == Protocol::EOS) {\n      eos::common::StringConversion::SealXrdPath(target.name);\n    }\n  }\n\n  // Detect whether target is stdout\n  const char* abs_path = absolute_path(target.name.c_str());\n  target.name = abs_path;\n  free((char*)abs_path);\n  target_is_stdout = (target.name == \"-\");\n\n  if (!target_is_stdout) {\n    // Detect whether target is a directory\n    int stat_rc = do_stat(target.name.c_str(), target.protocol, target_stat);\n    target_exists = (stat_rc == 0);\n    target_is_dir = is_dir(target.name.c_str(), target.protocol, &target_stat);\n\n    // If multiple source files target must be a directory\n    if (source_list.size() > 1) {\n      // Target doesn't exist, mark it as directory\n      if (!target_exists) {\n        target_is_dir = true;\n      }\n\n      // Target is not a directory\n      if (!target_is_dir) {\n        fprintf(stderr, \"error: target must be a directory\\n\");\n        global_retc = EINVAL;\n        return -1;\n      }\n    }\n\n    // Target doesn't exist but name suggests should be a directory\n    if (!target_exists && target.name.endswith(\"/\")) {\n      target_is_dir = true;\n    }\n\n    // If target is a directory then the name should also reflect this\n    if (target_is_dir && !target.name.endswith(\"/\")) {\n      target.name.append(\"/\");\n    }\n\n    // Check rights to create target directory\n    if (target_is_dir && !target_exists) {\n      if (!makeparent) {\n        fprintf(stderr, \"error: target must be created. Please try with \"\n                \"create flag '-p' or see 'eos cp --help' for more info.\\n\");\n        global_retc = EINVAL;\n        return -1;\n      }\n    }\n\n    // Create target directory tree for EOS or local path\n    if (makeparent) {\n      if ((target.protocol == Protocol::EOS) ||\n          (target.protocol == Protocol::LOCAL)) {\n        XrdOucString mktarget;\n\n        if (target.name.endswith(\"/\")) {\n          mktarget = target.name.c_str();\n        } else {\n          eos::common::Path cTarget(target.name.c_str());\n          mktarget = cTarget.GetParentPath();\n        }\n\n        std::string cmdtext = \"mkdir -p \";\n\n        if (target.protocol == Protocol::LOCAL) {\n          cmdtext += \"--mode 755 \";\n        }\n\n        cmdtext += mktarget.c_str();\n        std::vector<XrdOucString> tmp;\n        int rc = (target.protocol == Protocol::EOS) ?\n                 run_eos_command(cmdtext.c_str(), tmp) :\n                 run_command(cmdtext.c_str(), tmp);\n\n        if (rc) {\n          fprintf(stderr, \"error: failed to create target directory : %s\\n\",\n                  mktarget.c_str());\n          global_retc = rc;\n          return -1;\n        }\n      }\n    }\n  } else {\n    // Disable all output for stdout target\n    silent = true;\n    noprogress = true;\n  }\n\n  // Set up environment for S3 target\n  if ((target.protocol == Protocol::AS3) ||\n      (target.protocol == Protocol::S3)) {\n    const char* url = setup_s3_environment(target.name, target.opaque);\n\n    if (url == NULL) {\n      return -1;\n    }\n\n    target.name = url;\n  }\n\n  // Expand '/eos/' shortcut for EOS protocol\n  if ((target.protocol == Protocol::EOS) &&\n      (target.name.beginswith(\"/eos/\"))) {\n    if (!serveruri.endswith(\"/\")) {\n      target.name.insert(\"/\", 0);\n    }\n\n    target.name.insert(serveruri.c_str(), 0);\n    if (getenv(\"EOSAUTHZ\")) {\n      target.opaque+= \"&authz=\";\n      target.opaque+= getenv(\"EOSAUTHZ\");\n    }\n  }\n\n  if (debug) {\n    fprintf(stderr, \"[eos-cp] # of source files: %lu\\n\", source_list.size());\n    fprintf(stderr, \"[eos-cp] Setting target %s [protocol=%s]\\n\",\n            target.name.c_str(), protocol_to_string(target.protocol));\n  }\n\n  // --------------------------------------------------------------------------\n  // Compute size for each source path\n  // --------------------------------------------------------------------------\n  // As needed, check whether tools to access these protocols can be found\n  bool s3_tool = false;\n  bool http_tool = false;\n  bool gsiftp_tool = false;\n\n  for (auto& source : source_list) {\n    bool statok = false;\n    struct stat buf;\n    source.atime.tv_nsec = source.mtime.tv_nsec = 0;\n\n    switch (source.protocol) {\n    // ------------------------------------------\n    // EOS, XRoot or local file\n    // ------------------------------------------\n    case Protocol::EOS:\n    case Protocol::XROOT:\n    case Protocol::LOCAL:\n      if (!do_stat(source.name.c_str(), source.protocol, buf)) {\n        // For symbolic links, EOS stat returns the size of the link.\n        // Ignore the size attribute in this case\n        if (source.protocol != Protocol::LOCAL && !S_ISREG(buf.st_mode)) {\n          source.size = 0;\n\n          if (debug || !silent) {\n            fprintf(stderr,\n                    \"warning: disable size check for path=%s [EOS symbolic link]\\n\",\n                    source.name.c_str());\n          }\n        } else {\n          copysize += buf.st_size;\n          source.size = (unsigned long long) buf.st_size;\n        }\n\n        // Store the a/m-time\n        source.atime.tv_sec = buf.st_atime;\n        source.mtime.tv_sec = buf.st_mtime;\n        statok = true;\n      }\n\n      break;\n\n    // ------------------------------------------\n    // S3 file\n    // ------------------------------------------\n    case Protocol::AS3:\n    case Protocol::S3: {\n      if (!s3_tool) {\n        if (check_protocol_tool(source.name.c_str())) {\n          return -1;\n        }\n\n        s3_tool = true;\n      }\n\n      const char* url = setup_s3_environment(source.name, source.opaque);\n\n      if (url == NULL) {\n        return -1;\n      }\n\n      XrdOucString s3env = \"env S3_ACCESS_KEY_ID=\";\n      s3env += getenv(\"S3_ACCESS_KEY_ID\");\n      s3env += \" S3_HOSTNAME=\";\n      s3env += getenv(\"S3_HOSTNAME\");\n      s3env += \" S3_SECRET_ACCESS_KEY=\";\n      s3env += getenv(\"S3_SECRET_ACCESS_KEY\");\n      // Execute 's3' command to retrieve size\n      XrdOucString cmdtext = \"bash -c \\\"\";\n      cmdtext += s3env;\n      cmdtext += \" s3 head \";\n      cmdtext += url;\n      cmdtext += \" | grep Content-Length | awk '{print \\\\$2}' 2> /dev/null\\\"\";\n\n      if (debug) {\n        fprintf(stderr, \"[eos-cp] running %s\\n\", cmdtext.c_str());\n      }\n\n      long long size = eos::common::StringConversion::LongLongFromShellCmd(\n                         cmdtext.c_str());\n\n      if ((!size) || (size == LLONG_MAX)) {\n        fprintf(stderr, \"error: path=%s cannot obtain size of S3 source file \"\n                \"or file size is 0!\\n\", source.name.c_str());\n        global_retc = EIO;\n        return -1;\n      }\n\n      copysize += size;\n      source.size = (unsigned long long) size;\n      source.atime.tv_sec = source.mtime.tv_sec = 0;\n      statok = true;\n      break;\n    }\n\n    // ------------------------------------------\n    // HTTP(S) & GSIFTP file\n    // ------------------------------------------\n    case Protocol::GSIFTP:\n    case Protocol::HTTP:\n    case Protocol::HTTPS:\n      if ((source.protocol == Protocol::HTTP ||\n           source.protocol == Protocol::HTTPS) && (!http_tool)) {\n        if (check_protocol_tool(source.name.c_str())) {\n          return -1;\n        }\n\n        http_tool = true;\n      } else if ((source.protocol == Protocol::GSIFTP) && (!gsiftp_tool)) {\n        if (check_protocol_tool(source.name.c_str())) {\n          return -1;\n        }\n\n        gsiftp_tool = true;\n      }\n\n      source.size = 0;\n      source.atime.tv_sec = source.mtime.tv_sec = 0;\n\n      if (debug || !silent) {\n        fprintf(stderr,\n                \"warning: disabling size check for path=%s [protocol=%s]\\n\",\n                source.name.c_str(), protocol_to_string(source.protocol));\n      }\n\n      statok = true;\n      break;\n\n    default:\n      break;\n    }\n\n    if (!statok) {\n      fprintf(stderr, \"error: cannot get file size of path=%s [protocol=%s]\\n\",\n              source.name.c_str(), protocol_to_string(source.protocol));\n      global_retc = EINVAL;\n      return -1;\n    }\n\n    if (debug) {\n      fprintf(stderr, \"[eos-cp] path=%s size=%llu [protocol=%s]\\n\",\n              source.name.c_str(), source.size,\n              protocol_to_string(source.protocol));\n    }\n  }\n\n  if (debug || (!silent && source_list.size() > 1)) {\n    XrdOucString ssize;\n    fprintf(stderr, \"[eos-cp] going to copy %lu files and %s\\n\", source_list.size(),\n            eos::common::StringConversion::GetReadableSizeString(ssize, copysize, \"B\"));\n  }\n\n  // Mark start timestamp\n  gettimeofday(&start_time, &tz);\n  // --------------------------------------------------------------------------\n  // Create 'eoscp' command for each source path\n  // and effectively perform the copy operation\n  // --------------------------------------------------------------------------\n  int file_idx = -1;\n  retc = 0;\n\n  for (auto& source : source_list) {\n    XrdOucString dest = target.name.c_str();\n    // Processed target path + original target opaque info\n    XrdOucString target_path = \"\";\n    // Temporary file upload flag\n    bool temporary_file = false;\n    file_idx++;\n\n    //------------------------------------\n    // Process destination path\n    //------------------------------------\n\n    // Append source suffix to destination\n    // The source suffix: <source_path> = <source_basepath/><source_suffix>\n    if (target_is_dir) {\n      XrdOucString source_suffix = source.name.c_str();\n      int pos = source_suffix.find(source_basepath_list[file_idx].c_str());\n\n      if (pos == STR_NPOS) {\n        fprintf(stderr, \"error: could not identify source suffix for path=%s\\n\",\n                source.name.c_str());\n        global_retc = EINVAL;\n        return -1;\n      }\n\n      pos += source_basepath_list[file_idx].length();\n      source_suffix.keep(pos);\n      dest += source_suffix.c_str();\n    }\n\n    // Check that source and destination are different\n    if (!strcmp(source.name.c_str(), dest.c_str())) {\n      fprintf(stderr,\n              \"warning: source and target are the same path=%s. Skipping path..\\n\",\n              source.name.c_str());\n      continue;\n    }\n\n    // Add opaque info to destination\n    if (target.opaque.length()) {\n      dest += \"?\";\n      dest += target.opaque.c_str();\n    }\n\n    target_path = dest.c_str();\n\n    // Continue processing for non STDOUT targets\n    if (!target_is_stdout) {\n      // Check if destination exists\n      if (nooverwrite) {\n        if ((target.protocol == Protocol::LOCAL) ||\n            (target.protocol == Protocol::EOS)) {\n          struct stat tmp;\n\n          if (!do_stat(dest.c_str(), target.protocol, tmp)) {\n            fprintf(stderr, \"warning: target=%s exists, but --no-overwrite \"\n                    \"flag specified\\n\", dest.c_str());\n            retc |= EEXIST;\n            continue;\n          }\n        }\n      }\n\n      // Handle EOS specific opaque info\n      if ((target.protocol == Protocol::EOS) ||\n          (target.protocol == Protocol::XROOT)) {\n        char opaque[1024];\n        std::string roles = eos_roles_opaque();\n        snprintf(opaque, sizeof(opaque) - 1,\n                 \"%ceos.targetsize=%llu&eos.bookingsize=%llu&eos.app=%s%s%s%s\",\n                 (target.opaque.length()) ? '&' : '?',\n                 source.size, source.size, getenv(\"EOSAPP\") ? getenv(\"EOSAPP\") : \"eoscp\",\n                 atomic.c_str(),\n                 roles.size() ? \"&\" : \"\",\n                 roles.size() ? roles.c_str() : \"\");\n        dest.append(opaque);\n      }\n\n      // Protocols for EOS, XRoot and local targets are supported directly\n      // S3 targets will be uploaded via STDIN & STDOUT pipes\n      // Remaining protocols will be copied to a temporary file\n      if ((target.protocol == Protocol::HTTP) ||\n          (target.protocol == Protocol::HTTPS) ||\n          (target.protocol == Protocol::GSIFTP)) {\n        char tmp_name[] = \"/tmp/com_cp.XXXXXX\";\n        int tmp_fd = mkstemp(tmp_name);\n\n        if (tmp_fd == -1) {\n          fprintf(stderr, \"error: failed to create temporary file \"\n                  \"while preparing copy for path=%s [protocol=%s]\\n\",\n                  dest.c_str(), protocol_to_string(target.protocol));\n          global_retc = errno;\n          return -1;\n        }\n\n        close(tmp_fd);\n        temporary_file = true;\n        dest = tmp_name;\n      }\n    }\n\n    //------------------------------------\n    // Process source path\n    //------------------------------------\n\n    // Expand '/eos/' shortcut for EOS protocol\n    if ((source.protocol == Protocol::EOS) &&\n        (source.name.beginswith(\"/eos/\"))) {\n      if (!serveruri.endswith(\"/\")) {\n        source.name.insert(\"/\", 0);\n      }\n\n      source.name.insert(serveruri.c_str(), 0);\n    }\n\n    // Add opaque info to source\n    if (source.opaque.length()) {\n      source.name += \"?\";\n      source.name += source.opaque.c_str();\n    }\n\n    if (debug) {\n      fprintf(stderr, \"\\n[eos-cp] copying %s to %s\\n\",\n              source.name.c_str(), target_path.c_str());\n    }\n\n    //------------------------------------\n    // Prepare STDIN and STDOUT pipes\n    //------------------------------------\n    XrdOucString transfersize =\n      \"\"; // used for STDIN pipes to specify the target size to eoscp\n    XrdOucString cmdtext = \"\";\n    bool rstdin = false;\n    bool rstdout = false;\n\n    if ((source.protocol == Protocol::EOS) ||\n        (source.protocol == Protocol::XROOT)) {\n      std::string roles = eos_roles_opaque();\n      source.name += (source.opaque.length())  ?  \"&\"  :  \"?\";\n      source.name += \"eos.app=\";\n      source.name += getenv(\"EOSAPP\") ? getenv(\"EOSAPP\") : \"eoscp\";\n      source.name += roles.size()  ?  \"&\"  :  \"\";\n      source.name += roles.size()  ?  roles.c_str()  : \"\";\n    } else if ((source.protocol != Protocol::LOCAL) &&\n               (source.protocol != Protocol::UNKNOWN)) {\n      bool old_noprogress = noprogress;\n      noprogress = true;\n      XrdOucString safesource = source.name.c_str();\n\n      while (safesource.replace(\"'\", \"\\\\'\")) {}\n\n      safesource.replace(\"as3:\", \"\", 0, 3);\n      XrdOucString tool = \"\";\n\n      if (source.protocol == Protocol::HTTP)    {\n        tool = \"curl \";\n      }\n\n      if (source.protocol == Protocol::HTTPS)   {\n        tool = \"curl -k \";\n      }\n\n      if (source.protocol == Protocol::GSIFTP)  {\n        tool = \"globus-url-copy \";\n      }\n\n      if ((source.protocol == Protocol::AS3) ||\n          (source.protocol == Protocol::S3)) {\n        tool = \"s3 get \";\n        noprogress = old_noprogress;\n      }\n\n      cmdtext += tool;\n      cmdtext += \"$'\";\n      cmdtext += safesource;\n      cmdtext += \"'\";\n\n      if (source.protocol == Protocol::GSIFTP) {\n        cmdtext += \" -\";\n      }\n\n      cmdtext += \" | \";\n      rstdin = true;\n    }\n\n    if ((source.protocol == Protocol::AS3) ||\n        (source.protocol == Protocol::S3)  ||\n        (target.protocol == Protocol::AS3) ||\n        (target.protocol == Protocol::S3)) {\n      char ts[1024];\n      snprintf(ts, sizeof(ts) - 1, \"%llu \", source.size);\n      transfersize = ts;\n    }\n\n    if ((target.protocol == Protocol::AS3) ||\n        (target.protocol == Protocol::S3)) {\n      rstdout = true;\n    }\n\n    //------------------------------------\n    // Prepare eoscp transaction name\n    //------------------------------------\n    XrdOucString safename = source.name.c_str();\n    int qpos = safename.rfind(\"?\");\n\n    if (qpos != STR_NPOS) {\n      safename.erase(qpos);\n    }\n\n    if (source.protocol != Protocol::LOCAL) {\n      XrdOucString sprot, hostport;\n      const char* url = eos::common::StringConversion::ParseUrl(safename.c_str(),\n                        sprot, hostport);\n\n      if (url) {\n        std::string surl = url;\n        safename = surl.c_str();\n      }\n    }\n\n    safename = eos::common::Path(safename.c_str()).GetName();;\n    eos::common::StringConversion::SealXrdPath(safename);\n    safename.replace(\"'\", \"\\\\'\");\n    //------------------------------------\n    // Construct 'eoscp' command\n    //------------------------------------\n    cmdtext += \"eoscp \";\n\n    if (append) {\n      cmdtext += \"-a \";\n    }\n\n    if (debug_level) {\n      cmdtext += (debug_level == 1) ? \"-v \" : \"-d \";\n    }\n\n    if (!summary) {\n      cmdtext += \"-s \";\n    }\n\n    if (makeparent) {\n      cmdtext += \"-p \";\n    }\n\n    if (noprogress) {\n      cmdtext += \"-n \";\n    }\n\n    if (nooverwrite) {\n      cmdtext += \"-x \";\n    }\n\n    if (transfersize.length()) {\n      cmdtext += \"-T \";\n      cmdtext += transfersize;\n      cmdtext += \" \";\n    }\n\n    if (rate.length()) {\n      cmdtext += \"-t \";\n      cmdtext += rate.c_str();\n      cmdtext += \" \";\n    }\n\n    cmdtext += \"-N $'\";\n    cmdtext += safename.c_str();\n    cmdtext += \"' \";\n\n    if (rstdin) {\n      cmdtext += \"- \";\n    } else {\n      XrdOucString safesource = source.name.c_str();\n      safesource.replace(\"'\", \"\\\\'\");\n      cmdtext += \"$'\";\n      cmdtext += safesource;\n      cmdtext += \"' \";\n    }\n\n    if (rstdout) {\n      cmdtext += \"-\";\n    } else {\n      XrdOucString safedest = dest.c_str();\n      safedest.replace(\"'\", \"\\\\'\");\n      cmdtext += \"$'\";\n      cmdtext += safedest;\n      cmdtext += \"'\";\n    }\n\n    if ((target.protocol == Protocol::AS3) ||\n        (target.protocol == Protocol::S3)) {\n      // s3 can upload via STDIN\n      XrdOucString s3dest = dest.c_str();\n      s3dest.replace(\"as3:\", \"\", 0, 3);\n      cmdtext += \" | s3 put \";\n      cmdtext += s3dest.c_str();\n      cmdtext += \" contentLength=\";\n      cmdtext += transfersize.c_str();\n      cmdtext += \" > /dev/null\";\n    }\n\n    if (debug) {\n      fprintf(stderr, \"[eos-cp] running: %s\\n\", cmdtext.c_str());\n    }\n\n    int lrc = system(cmdtext.c_str());\n\n    // Check if we got a CONTROL-C\n    if (lrc == EINTR) {\n      fprintf(stderr, \"<Control-C>\\n\");\n      break;\n    }\n\n    if (WEXITSTATUS(lrc)) {\n      fprintf(stderr, \"error: failed copying path=%s\\n\", target_path.c_str());\n      retc |= lrc;\n      continue;\n    }\n\n    //------------------------------------\n    // Check target size\n    //------------------------------------\n\n    if (((target.protocol == Protocol::EOS)    ||\n         (target.protocol == Protocol::XROOT)  ||\n         (target.protocol == Protocol::LOCAL)) && (!target_is_stdout)) {\n      struct stat buf;\n\n      if (!do_stat(target_path.c_str(), target.protocol, buf)) {\n        if ((!source.size) ||\n            (buf.st_size == (off_t)(append ? target_stat.st_size + source.size :\n                                    (off_t) source.size)\n            )\n           ) {\n          // Preserve creation and modification timestamps\n          if ((preserve) && (source.atime.tv_sec > 0) && (source.mtime.tv_sec > 0)) {\n            bool updateok;\n\n            if (target.protocol == Protocol::LOCAL) {\n              struct timeval times[2];\n              times[0].tv_sec = source.atime.tv_sec;\n              times[0].tv_usec = source.atime.tv_nsec / 1000;\n              times[1].tv_sec = source.mtime.tv_sec;\n              times[1].tv_usec = source.mtime.tv_nsec / 1000;\n              updateok = (utimes(target_path.c_str(), times) == 0);\n            } else {\n              char update[1024];\n              auto roles = eos_roles_opaque();\n              sprintf(update, \"%ceos.app=%s%s%s&mgm.pcmd=utimes\"\n                      \"&tv1_sec=%llu&tv1_nsec=%llu\"\n                      \"&tv2_sec=%llu&tv2_nsec=%llu\",\n                      (target.opaque.length()) ? '&' : '?',\n                      getenv(\"EOSAPP\") ? getenv(\"EOSAPP\") : \"eoscp\",\n                      roles.size() ? \"&\" : \"\",\n                      roles.size() ? roles.c_str() : \"\",\n                      (unsigned long long) source.atime.tv_sec,\n                      (unsigned long long) source.atime.tv_nsec,\n                      (unsigned long long) source.mtime.tv_sec,\n                      (unsigned long long) source.mtime.tv_nsec);\n              XrdOucString request = target_path.c_str();\n              request += update;\n              char value[4096];\n              value[0] = 0;\n              long long update_rc = XrdPosixXrootd::QueryOpaque(request.c_str(),\n                                    value, 4096);\n              updateok = (update_rc >= 0);\n\n              // Parse the stat output\n              if (updateok) {\n                char tag[1024];\n                int tmp_retc;\n                int items = sscanf(value, \"%1023s retc=%d\", tag, &tmp_retc);\n                updateok = ((items == 2) && (strcmp(tag, \"utimes:\") == 0));\n              }\n            }\n\n            if (!updateok) {\n              fprintf(stderr, \"warning: creation/modification time \"\n                      \"could not be preserved for path=%s\\n\",\n                      target_path.c_str());\n            }\n          }\n\n          // Verify checksum\n          if ((checksums) && (target.protocol != Protocol::LOCAL)) {\n            XrdOucString address = serveruri.c_str();\n            address += \"//dummy\";\n            XrdCl::URL url(address.c_str());\n\n            if (!url.IsValid()) {\n              fprintf(stderr, \"error: invalid file system URL=%s \"\n                      \"[attempting checksum]\\n\",\n                      url.GetURL().c_str());\n              global_retc = EINVAL;\n              return -1;\n            }\n\n            auto* fs = new XrdCl::FileSystem(url);\n\n            if (!fs) {\n              fprintf(stderr, \"error: failed to get new FS object \"\n                      \"[attempting checksum]\\n\");\n              global_retc = EINVAL;\n              return -1;\n            }\n\n            XrdCl::Buffer arg;\n            XrdCl::Buffer* response = nullptr;\n            XrdCl::XRootDStatus status;\n            std::string query_path = dest.c_str();\n            std::string::size_type pos = query_path.rfind(\"//\");\n\n            if (pos != std::string::npos) {\n              query_path.erase(0, pos + 1);\n            }\n\n            arg.FromString(query_path);\n            status = fs->Query(XrdCl::QueryCode::Checksum, arg, response);\n\n            if (status.IsOK()) {\n              XrdOucString xsum = response->GetBuffer();\n              xsum.replace(\"eos \", \"\");\n              fprintf(stdout, \"path=%s size=%llu checksum=%s\\n\",\n                      source.name.c_str(), source.size, xsum.c_str());\n            } else {\n              fprintf(stdout, \"warning: failed getting checksum for path=%s size=%llu\\n\",\n                      source.name.c_str(), source.size);\n            }\n\n            delete response;\n            delete fs;\n          }\n        } else {\n          XrdOucString ssize1, ssize2;\n          fprintf(stderr, \"error: file size difference between source and target file \"\n                  \"source=%s [%s] target=%s [%s]\\n\",\n                  source.name.c_str(),\n                  eos::common::StringConversion::GetReadableSizeString(ssize1,\n                      source.size, \"B\"),\n                  target_path.c_str(),\n                  eos::common::StringConversion::GetReadableSizeString(ssize2,\n                      (unsigned long long) buf.st_size, \"B\"));\n          lrc |= 0xffff00;\n        }\n      } else {\n        fprintf(stderr, \"error: target file not created source=%s target=%s\\n\",\n                source.name.c_str(), target_path.c_str());\n        lrc |= 0xffff00;\n      }\n    }\n\n    // Attempt to upload temporary file\n    if (temporary_file) {\n      if (target.protocol == Protocol::GSIFTP) {\n        cmdtext = \"globus-url-copy file://\";\n        cmdtext += dest.c_str();\n        cmdtext += \" \";\n        cmdtext += target_path.c_str();\n\n        if (silent || noprogress) {\n          cmdtext += \" >& /dev/null\";\n        }\n\n        if (debug) {\n          fprintf(stderr, \"[eos-cp] running: %s\\n\", cmdtext.c_str());\n        }\n\n        int rc = system(cmdtext.c_str());\n\n        if (WEXITSTATUS(rc)) {\n          fprintf(stderr, \"error: failed to upload %s [protocol=gsiftp]\\n\",\n                  target_path.c_str());\n          lrc |= 0xffff00;\n        }\n      }\n\n      if ((target.protocol == Protocol::HTTP) ||\n          (target.protocol == Protocol::HTTPS)) {\n        fprintf(stderr, \"error: file uploads not supported for %s protocol [path=%s]\\n\",\n                protocol_to_string(target.protocol), target_path.c_str());\n        lrc |= 0xffff00;\n      }\n\n      // Clean-up the temporary file\n      unlink(dest.c_str());\n    }\n\n    if (!WEXITSTATUS(lrc)) {\n      files_copied++;\n      copiedsize += source.size;\n    }\n\n    retc |= lrc;\n  }\n\n  // Mark end timestamp\n  gettimeofday(&end_time, &tz);\n\n  if (debug || !silent) {\n    float time_elapsed = (float)(((end_time.tv_sec - start_time.tv_sec) * 1000000 +\n                                  (end_time.tv_usec - start_time.tv_usec)) / 1000000.0);\n    unsigned long long copyrate = (copiedsize / time_elapsed);\n    XrdOucString ssize1, ssize2;\n    fprintf(stderr,\n            \"%s[eos-cp] copied %d/%d files and %s in %.02f seconds with %s\\n\",\n            (retc) ? \"#WARNING \" : \"\",\n            files_copied,\n            (int) source_list.size(),\n            eos::common::StringConversion::GetReadableSizeString(ssize1, copiedsize, \"B\"),\n            time_elapsed,\n            eos::common::StringConversion::GetReadableSizeString(ssize2, copyrate, \"B/s\"));\n  }\n\n  global_retc = WEXITSTATUS(retc);\n  return global_retc;\n}\n\n\n// ----------------------------------------------------------------------------\n// Helper functions implementation\n// ----------------------------------------------------------------------------\n\n/**\n * Convenience function to be used by 'eos cp' to query EOS for file names.\n * The output of the command is placed into the result vector.\n * @param cmdline the eos command to be executed\n * @param result reference to the result vector\n * @return error code of the command\n */\nint run_eos_command(const char* cmdline, std::vector<XrdOucString>& result)\n{\n  XrdOucString cmd = \"eos -b \";\n\n  if (user_role.length() && group_role.length()) {\n    cmd += \"--role \";\n    cmd += user_role;\n    cmd += \" \";\n    cmd += group_role;\n    cmd += \" \";\n  }\n\n  cmd += cmdline;\n  return run_command(cmd.c_str(), result);\n}\n\n/**\n * Convenience function to be used by 'eos cp' to execute a command.\n * The output of the command is placed into the result vector.\n * @param cmdline the bash command to be executed\n * @param result reference to the result vector\n * @return error code of the command\n */\nint run_command(const char* cmdline, std::vector<XrdOucString>& result)\n{\n  FILE* fp = popen(cmdline, \"r\");\n  char line[4096];\n  int rc;\n\n  if (!fp) {\n    fprintf(stderr, \"error: failed executing command %s\\n\", cmdline);\n    return errno;\n  }\n\n  while (fgets(line, sizeof(line), fp)) {\n    int size = strlen(line);\n\n    if (line[size - 1] == '\\n') {\n      line[size - 1] = '\\0';\n    }\n\n    result.emplace_back(line);\n  }\n\n  rc = pclose(fp);\n  return WEXITSTATUS(rc);\n}\n\n/**\n * Converts from local to absolute path.\n * This function makes the distinction between local or EOS paths.\n * Any other protocol will be left untouched.\n * Function is aware of interactive eos shell environment.\n * Local files will have the 'file:' prefix removed.\n * @param path the given path\n * @return abspath the absolute path\n */\nconst char* absolute_path(const char* path)\n{\n  Protocol protocol = get_protocol(path);\n\n  if (protocol != Protocol::EOS && protocol != Protocol::LOCAL) {\n    return strdup(path);\n  }\n\n  if (strcmp(path, \"-\") == 0) {\n    return strdup(path);\n  }\n\n  XrdOucString spath = path;\n\n  if (protocol == Protocol::LOCAL && spath.beginswith(\"file:\")) {\n    spath.erase(0, 5);\n  }\n\n  if (!spath.beginswith(\"/\")) {\n    XrdOucString abspath = \"\";\n\n    if (interactive) {\n      // Construct absolute path within eos shell\n      abspath.insert(gPwd.c_str(), 0);\n    } else {\n      // Construct absolute path within regular shell\n      abspath.insert(\"/\", 0);\n      abspath.insert(getenv(\"PWD\"), 0);\n    }\n\n    spath.insert(abspath.c_str(), 0);\n  }\n\n  // Note: eos::common::Path expects an absolute path!\n  // Note: eos::common::Path removes trailing '/'!\n  std::string trailing_slash = \"\";\n\n  if ((spath.endswith(\"/\")) && (!spath.endswith(\"/./\")) &&\n      (!spath.endswith(\"/../\"))) {\n    trailing_slash = \"/\";\n  }\n\n  // Sanitize '.' and '..' entries\n  spath = eos::common::Path(spath.c_str()).GetFullPath().c_str();\n  spath += trailing_slash.c_str();\n  return strdup(spath.c_str());\n}\n\n/**\n * Given a symlink path of the following format 'link -> target',\n * will return the name of the 'link'.\n * @param path the path to check\n * @return path the processed symlink name\n */\nXrdOucString process_symlink(XrdOucString path)\n{\n  int pos = path.find(\" -> \");\n\n  if (pos != STR_NPOS) {\n    path.erase(pos);\n  }\n\n  return path;\n}\n\n/**\n * Will check whether the given path is a directory or not.\n * For local and EOS protocols, stat information is used.\n * The stat structure may be passed, otherwise it is constructed.\n * Function is aware of interactive eos shell environment.\n * @param path the path to check\n * @param protocol the protocol to access the path\n * @param buf stat structure\n * @return true if directory, false otherwise\n */\nbool is_dir(const char* path, Protocol protocol, struct stat* buf)\n{\n  if (protocol != Protocol::EOS && protocol != Protocol::LOCAL) {\n    XrdOucString spath = path;\n    return spath.endswith(\"/\");\n  }\n\n  int rc = 0;\n  struct stat tmpbuf {};\n\n  if (buf == nullptr) {\n    buf = &tmpbuf;\n    const char* abs_path = absolute_path(path);\n    rc = do_stat(abs_path, protocol, *buf);\n    free(const_cast<char*>(abs_path));\n  }\n\n  return (rc == 0)  ?  S_ISDIR(buf->st_mode)  :  false;\n}\n\n/**\n * Returns eos roles opaque info from the global user variables.\n * @return roles opaque info containing eos roles\n */\nstd::string\neos_roles_opaque()\n{\n  std::string roles;\n\n  if (user_role.length() && group_role.length()) {\n    roles = \"eos.ruid=\";\n    roles += user_role.c_str();\n    roles += \"&eos.rgid=\";\n    roles += group_role.c_str();\n    return roles;\n  }\n\n  return roles;\n}\n\n/**\n * Perform stat on a given path.\n * Function makes the distinction between local or EOS paths.\n * @param path the path to stat\n * @param protocol the protocol to access the path\n * @param buf stat structure to fill\n * @return rc stat error code\n */\nint do_stat(const char* path, Protocol protocol, struct stat& buf)\n{\n  const char* abs_path = absolute_path(path);\n  int rc = -1;\n\n  if (protocol == Protocol::EOS || protocol == Protocol::XROOT) {\n    // Stat EOS file\n    XrdOucString url = abs_path;\n    std::string roles = eos_roles_opaque();\n\n    // Expand '/eos/' shortcut for EOS protocol\n    if (url.beginswith(\"/eos/\")) {\n      url = serveruri.c_str();\n      url += (!url.endswith(\"/\"))  ?  \"/\"  :  \"\";\n      url += abs_path;\n    }\n\n    if (!roles.empty()) {\n      url += (url.find(\"?\") == STR_NPOS)  ?  \"?\"  :  \"&\";\n      url += roles.c_str();\n    }\n\n    rc = XrdPosixXrootd::Stat(url.c_str(), &buf);\n  } else if (protocol == Protocol::LOCAL) {\n    // Stat local file\n    rc = stat(abs_path, &buf);\n  }\n\n  free((char*)abs_path);\n  return rc;\n}\n\n/**\n * Given an S3 path, will parse and remove the opaque info.\n * The following environment variables are set:\n * S3_ACCESS_KEY_ID <br/>\n * S3_SECRET_ACCESS_KEY <br/>\n * S3_HOSTNAME <br/>\n * @param path the S3 path\n * @param opaque the opaque info to parse for S3 info\n * @return url the S3 url\n */\nconst char* setup_s3_environment(XrdOucString path, XrdOucString opaque)\n{\n  XrdOucString sprot, hostport;\n  XrdOucString url = eos::common::StringConversion::ParseUrl(path.c_str(),\n                     sprot, hostport);\n\n  if (!url.length()) {\n    fprintf(stderr, \"error: could not parse S3 url=%s\", path.c_str());\n    global_retc = EINVAL;\n    return 0;\n  }\n\n  if (opaque.length()) {\n    XrdOucEnv env(opaque.c_str());\n\n    // Extract opaque S3 tags if present\n    if (env.Get(\"s3.id\"))  {\n      setenv(\"S3_ACCESS_KEY_ID\", env.Get(\"s3.id\"), 1);\n    }\n\n    if (env.Get(\"s3.key\")) {\n      setenv(\"S3_SECRET_ACCESS_KEY\", env.Get(\"s3.key\"), 1);\n    }\n  }\n\n  if (hostport.length()) {\n    setenv(\"S3_HOSTNAME\", hostport.c_str(), 1);\n  }\n\n  // Apply the ROOT compatibility environment variables\n  if (getenv(\"S3_ACCESS_ID\")) {\n    setenv(\"S3_ACCESS_KEY_ID\", getenv(\"S3_ACCESS_ID\"), 1);\n  }\n\n  if (getenv(\"S3_ACCESS_KEY\")) {\n    setenv(\"S3_SECRET_ACCESS_KEY\", getenv(\"S3_ACCESS_KEY\"), 1);\n  }\n\n  // Check S3 environment\n  if ((!getenv(\"S3_HOSTNAME\")) || (!getenv(\"S3_ACCESS_KEY_ID\")) ||\n      (!getenv(\"S3_SECRET_ACCESS_KEY\"))) {\n    fprintf(stderr, \"error: S3 environment not set up for %s\\n\", path.c_str());\n    fprintf(stderr, \"You have to set the following environment variables: \"\n            \"S3_ACCESS_KEY_ID or S3_ACCESS_ID\\n\"\n            \"S3_SECRET_ACCESS_KEY or S3_ACCESS_KEY\\n\"\n            \"S3_HOSTNAME (or use path with URI)\");\n    global_retc = EINVAL;\n    return 0;\n  }\n\n  return url.c_str();\n}\n\n/**\n * Check if required tools are available to access the given path.\n * @param path the path to access\n */\nint check_protocol_tool(const char* path)\n{\n  Protocol protocol = get_protocol(path);\n  std::string tool = \"\";\n  char cmd[128];\n\n  if (protocol == Protocol::HTTP || protocol == Protocol::HTTPS) {\n    tool = \"curl\";\n  } else if (protocol == Protocol::AS3 || protocol == Protocol::S3) {\n    tool = \"s3\";\n  } else if (protocol == Protocol::GSIFTP) {\n    tool = \"globus-url-copy\";\n  } else {\n    return 0;\n  }\n\n  sprintf(cmd, \"which %s >& /dev/null\", tool.c_str());\n  int rc = system(cmd);\n\n  if (WEXITSTATUS(rc)) {\n    fprintf(stderr, \"error: %s executable not found in PATH\\n\", tool.c_str());\n\n    if (tool == \"s3\") {\n      fprintf(stderr, \" error: please install S3 executable from libs3\\n\");\n    }\n\n    global_retc = WEXITSTATUS(rc);\n  }\n\n  return WEXITSTATUS(rc);\n}\n\n/**\n * Returns the protocol for a given path.\n * Function is aware of interactive eos shell environment.\n */\nProtocol get_protocol(XrdOucString path)\n{\n  if (path.beginswith(\"/eos/\")) {\n    return Protocol::EOS;\n  } else if (path.beginswith(\"http://\")) {\n    return Protocol::HTTP;\n  } else if (path.beginswith(\"https://\")) {\n    return Protocol::HTTPS;\n  } else if (path.beginswith(\"gsiftp://\")) {\n    return Protocol::GSIFTP;\n  } else if (path.beginswith(\"root://\")) {\n    return Protocol::XROOT;\n  } else if (path.beginswith(\"as3:\")) {\n    return Protocol::AS3;\n  } else if (path.beginswith(\"s3://\")) {\n    return Protocol::S3;\n  } else if (path.beginswith(\"file:\")) {\n    return Protocol::LOCAL;\n  } else if (path.beginswith(\"/\") || (path.find(\":/\") == STR_NPOS)) {\n    return (interactive)  ?  Protocol::EOS  :  Protocol::LOCAL;\n  }\n\n  return Protocol::UNKNOWN;\n}\n\n/**\n * Returns a string representation of the protocol.\n */\nconst char* protocol_to_string(Protocol protocol)\n{\n  if (protocol == Protocol::EOS)       {\n    return \"eos\";\n  } else if (protocol == Protocol::HTTP)      {\n    return \"http\";\n  } else if (protocol == Protocol::HTTPS)     {\n    return \"https\";\n  } else if (protocol == Protocol::GSIFTP)    {\n    return \"gsiftp\";\n  } else if (protocol == Protocol::XROOT)     {\n    return \"root\";\n  } else if (protocol == Protocol::AS3)       {\n    return \"as3\";\n  } else if (protocol == Protocol::S3)        {\n    return \"s3\";\n  } else if (protocol == Protocol::LOCAL)     {\n    return \"local\";\n  }\n\n  return \"unknown\";\n}\n\n/**\n * Parse and returns debug level from option string or -1 if invalid.\n * Option format: -d[=][1|2|3]\n */\nint parse_debug_level(XrdOucString option)\n{\n  if (option.beginswith(\"-d\")) {\n    option.erase(0, 2);\n  } else if (option.beginswith(\"--debug\")) {\n    option.erase(0, 7);\n  }\n\n  if (option.length() && ((option[0] == ' ') || (option[0] == '='))) {\n    option.erase(0, 1);\n  }\n\n  if (!option.length()) {\n    return 0;\n  }\n\n  int level = 0;\n\n  try {\n    level = std::stoul(option.c_str());\n  } catch (...) { }\n\n  if (level < 1 || level > 3) {\n    fprintf(stderr, \"error: invalid value for <debug level>=%s\\n\", option.c_str());\n    return -1;\n  }\n\n  return level - 1;\n}\n\n/* eos cat command - just eos cp with '-' destination*/\nint com_cat(char* argin)\n{\n  std::string catarg=(const char*)argin;\n  catarg += \" -\";\n  return com_cp((char*)catarg.c_str());\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_daemon.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_daemon.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/ICmdHelper.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/Config.hh\"\n#include \"common/Path.hh\"\n#include \"common/SymKeys.hh\"\n/*----------------------------------------------------------------------------*/\n#include <sys/wait.h>\n#include <sched.h>\n#include <sys/mount.h>\n#include <sys/ptrace.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n/*----------------------------------------------------------------------------*/\n\n#ifdef __APPLE__\n#define EOS_PTRACE_TRACEME PT_TRACEME\n#define EOS_PTRACE_ATTACH   PT_ATTACH\n#define EOS_PTRACE_DETACH   PT_DETACH\n#else\n#define EOS_PTRACE_TRACEME PTRACE_TRACEME\n#define EOS_PTRACE_ATTACH   PTRACE_ATTACH\n#define EOS_PTRACE_DETACH   PTRACE_DETACH\n#endif //__APPLE__\n\n/* Steer a service */\nint\ncom_daemon(char* arg)\n{\n#ifdef __APPLE__\n  fprintf(stderr, \"error: daemon command is not support on OSX\\n\");\n  global_retc = EINVAL;\n  return (0);\n#else\n  eos::common::StringTokenizer subtokenizer(arg);\n  eos::common::Config cfg;\n  XrdOucString option = \"\";\n  XrdOucString name = \"\";\n  XrdOucString service = \"\";\n  XrdOucString subcmd;\n  XrdOucString modules;\n  std::vector<std::string> mods;\n  std::string chapter;\n  std::string executable;\n  std::string pidfile;\n  std::string envfile;\n  std::string cfile;\n  FILE* pipe = NULL;\n  pid_t pid = 0;\n  bool ok = false;\n  subtokenizer.GetLine();\n\n  if (wants_help(arg)) {\n    goto com_daemon_usage;\n  }\n\n  option = subtokenizer.GetToken();\n\n  if (!option.length()) {\n    goto com_daemon_usage;\n  }\n\n  if (option == \"sss\") {\n    subcmd = subtokenizer.GetToken();\n\n    if (!subcmd.length()) {\n      goto com_daemon_usage;\n    }\n\n    if (subcmd == \"recreate\") {\n      if (geteuid()) {\n        std::cerr << \"error: you have to run this command as root!\" << std::endl;\n        global_retc = EPERM;\n        return (0);\n      }\n\n      struct stat buf;\n\n      std::cout <<\n                \"info: you are going to (re-)create the instance sss key. A previous key will be moved to /etc/eos.keytab.<unixtimestamp>\"\n                << std::endl;\n\n      if (!::stat(\"/etc/eos.keytab\", &buf)) {\n        std::string oldkeytab = \"/etc/eos.keytab.\";\n        oldkeytab += std::to_string(time(NULL));\n\n        if (::rename(\"/etc/eos.keytab\", oldkeytab.c_str())) {\n          std::cerr <<\n                    \"error: renaming of existing old keytab file /etc/eos.keytab failed!\" <<\n                    std::endl;\n          global_retc = errno;\n          return (0);\n        }\n      }\n\n      bool interactive = false;\n\n      if (isatty(STDOUT_FILENO)) {\n        interactive = true;\n      }\n\n      if (!interactive || ICmdHelper::ConfirmOperation()) {\n        if (!::stat(\"/opt/eos/xrootd/bin/xrdsssadmin\", &buf)) {\n          system(\"yes | /opt/eos/xrootd/bin/xrdsssadmin -u daemon -g daemon -k eosmaster add /etc/eos.keytab\");\n          system(\"yes | /opt/eos/xrootd/bin/xrdsssadmin -u eosnobody -g eosnobody -k eosnobody add /etc/eos.keytab\");\n        } else {\n          system(\"yes | xrdsssadmin -u daemon -g daemon -k eosmaster add /etc/eos.keytab\");\n          system(\"yes | xrdsssadmin -u eosnobody -g eosnobody -k eosnobody add /etc/eos.keytab\");\n        }\n\n        system(\"mkdir -p /etc/eos/; cat /etc/eos.keytab | grep eosnobody > /etc/eos/fuse.sss.keytab; chmod 400 /etc/eos/fuse.sss.keytab\");\n        std::cout << \"info: recreated /etc/eos.keytab /etc/eos/fuse.sss.keytab\" <<\n                  std::endl;\n      } else {\n        global_retc = EINVAL;\n        return (0);\n      }\n\n      return (0);\n    }\n  }\n\n  if (option == \"seal\") {\n    XrdOucString toseal = subtokenizer.GetToken();\n    XrdOucString sealed;\n    std::string key;\n\n    if (!toseal.length()) {\n      return (0);\n    }\n\n    if (toseal.beginswith(\"/\")) {\n      // treat it as a file\n      std::string contents;\n      eos::common::StringConversion::LoadFileIntoString(toseal.c_str(), contents);\n      toseal = contents.c_str();\n    }\n\n    const char* pkey = subtokenizer.GetToken();\n\n    if (!pkey) {\n      key = eos::common::StringConversion::StringFromShellCmd(\"cat /etc/eos.keytab | grep u:daemon | md5sum\");\n    } else {\n      key = pkey;\n    }\n\n    std::string shakey = eos::common::SymKey::HexSha256(key);\n    eos::common::SymKey::SymmetricStringEncrypt(toseal, sealed,\n        (char*)shakey.c_str());\n    fprintf(stderr, \"enc:%s\\n\", sealed.c_str());\n    return (0);\n  }\n\n  if ((option != \"run\") &&\n      (option != \"config\") &&\n      (option != \"stack\") &&\n      (option != \"stop\") &&\n      (option != \"restart\") &&\n      (option != \"kill\") &&\n      (option != \"jwk\") &&\n      (option != \"module-init\")) {\n    goto com_daemon_usage;\n  }\n\n  service = subtokenizer.GetToken();\n\n  if ((option != \"jwk\") && ((!service.length()) ||\n                            ((service != \"mgm\") &&\n                             (service != \"fst\") &&\n                             (service != \"qdb\")))) {\n    goto com_daemon_usage;\n  }\n\n  name = subtokenizer.GetToken();\n\n  if (!name.length()) {\n    name = service.c_str();\n  }\n\n  pipe = popen(std::string(\"pidof -s eos-\" + std::string(service.c_str())).c_str(), \"r\");\n  if (!pipe || fscanf(pipe, \"%d\", &pid) != 1)\n    pid = -1;\n  pclose(pipe);\n\n  modules = name;\n  modules += \".modules\";\n  executable = \"eos-\";\n  executable += service.c_str();\n  envfile = \"/var/run/eos/\";\n  envfile += executable.c_str() ;\n  envfile += \".\";\n  envfile += name.c_str();\n  envfile += \".env\";\n  pidfile = \"/var/run/eos/xrd.\";\n  pidfile += service.c_str();\n  pidfile += \".\";\n  pidfile += name.c_str();\n  pidfile += \".pid\";\n  cfg.Load(\"generic\", \"all\");\n  ok |= cfg.ok();\n  cfg.Load(service.c_str(), name.c_str(), false);\n  ok |= cfg.ok();\n  // this might fail if there are no modules\n  cfg.Load(service.c_str(), modules.c_str(), false);\n  {\n    // load all the modules:\n    eos::common::StringConversion::Tokenize(cfg.Dump(\"modules\", true), mods, \"\\n\");\n\n    for (size_t i = 0; i < mods.size(); ++i) {\n      if (mods[i].front() == '#') {\n        // ignore comments\n        continue;\n      }\n\n      if (mods[i].find(\" \") != std::string::npos) {\n        fprintf(stderr, \"warning: ignoring module line '%s' (contains space)\\n\",\n                mods[i].c_str());\n        continue;\n      }\n\n      if (mods[i].empty()) {\n        // ignore empty lines\n        continue;\n      }\n\n      if (!cfg.Load(\"modules\", mods[i].c_str(), false)) {\n        fprintf(stderr, \"error: failed to load module '%s'\\n\", mods[i].c_str());\n        global_retc = EINVAL;\n        return 0;\n      }\n    }\n  }\n  chapter = service.c_str();\n  chapter += \":xrootd:\";\n  chapter += name.c_str();\n  cfile = \"/var/run/eos/xrd.cf.\";\n  cfile += name.c_str();\n\n  if (option == \"config\") {\n    char** const envv = cfg.Env(\"sysconfig\");\n\n    for (size_t i = 0; i < 1024; ++i) {\n      if (envv[i]) {\n        putenv(envv[i]);\n        fprintf(stderr, \"[putenv] %s\\n\", envv[i]);\n      } else {\n        break;\n      }\n    }\n\n    if (service == \"qdb\") {\n      XrdOucString subcmd = subtokenizer.GetToken();\n\n      if (subcmd == \"coup\") {\n        std::string kline;\n        kline = \"export REDISCLI_AUTH=`cat /etc/eos.keytab`; redis-cli -p `cat \";\n        kline += cfile;\n        kline += \"|grep xrd.port | cut -d ' ' -f 2` <<< raft-attempt-coup\";\n        int rc = system(kline.c_str());\n        fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(), WEXITSTATUS(rc));\n        global_retc = WEXITSTATUS(rc);\n        return (0);\n      } else if (subcmd == \"info\") {\n        std::string kline;\n        kline = \"export REDISCLI_AUTH=`cat /etc/eos.keytab`; redis-cli -p `cat \";\n        kline += cfile;\n        kline += \"|grep xrd.port | cut -d ' ' -f 2` <<< raft-info\";\n        int rc = system(kline.c_str());\n        fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(), WEXITSTATUS(rc));\n        global_retc = WEXITSTATUS(rc);\n        return (0);\n      } else if (subcmd == \"remove\") {\n        XrdOucString member = subtokenizer.GetToken();\n\n        if (!member.length()) {\n          fprintf(stderr,\n                  \"error: remove misses member argument host:port : 'eos daemon config qdb qdb remove host:port'\\n\");\n          global_retc = EINVAL;\n          return 0;\n        } else {\n          std::string kline;\n          kline = \"export REDISCLI_AUTH=`cat /etc/eos.keytab`; redis-cli -p `cat \";\n          kline += cfile;\n          kline += \"|grep xrd.port | cut -d ' ' -f 2` <<< \\\"raft-remove-member \";\n          kline += member.c_str();\n          kline += \"\\\"\";\n          int rc = system(kline.c_str());\n          fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(), WEXITSTATUS(rc));\n          global_retc = WEXITSTATUS(rc);\n          return (0);\n        }\n      } else if (subcmd == \"add\") {\n        XrdOucString member = subtokenizer.GetToken();\n\n        if (!member.length()) {\n          fprintf(stderr,\n                  \"error: add misses member argument host:port : 'eos daemon config qdb qdb add host:port'\\n\");\n          global_retc = EINVAL;\n          return 0;\n        } else {\n          std::string kline;\n          kline = \"export REDISCLI_AUTH=`cat /etc/eos.keytab`; redis-cli -p `cat \";\n          kline += cfile;\n          kline += \"|grep xrd.port | cut -d ' ' -f 2` \\\"<<< raft-add-observer \";\n          kline += member.c_str();\n          kline += \"\\\"\";\n          int rc = system(kline.c_str());\n          fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(), WEXITSTATUS(rc));\n          global_retc = WEXITSTATUS(rc);\n          return (0);\n        }\n      } else if (subcmd == \"promote\") {\n        XrdOucString member = subtokenizer.GetToken();\n\n        if (!member.length()) {\n          fprintf(stderr,\n                  \"error: promote misses member argument host:port : 'eos daemon config qdb qdb promote host:port'\\n\");\n          global_retc = EINVAL;\n          return 0;\n        } else {\n          std::string kline;\n          kline = \"export REDISCLI_AUTH=`cat /etc/eos.keytab`; redis-cli -p `cat \";\n          kline += cfile;\n          kline += \"|grep xrd.port | cut -d ' ' -f 2` <<< \\\"raft-promote-observer \";\n          kline += member.c_str();\n          kline += \"\\\"\";\n          int rc = system(kline.c_str());\n          fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(), WEXITSTATUS(rc));\n          global_retc = WEXITSTATUS(rc);\n          return (0);\n        }\n      } else if (subcmd == \"new\") {\n        XrdOucString member = subtokenizer.GetToken();\n\n        if (member != \"observer\") {\n          fprintf(stderr,\n                  \"error: new misses 'observer' arguement : 'eos daemon config qdb qdb new observer'\\n\");\n          global_retc = EINVAL;\n          return 0;\n        } else {\n          std::string stopqdb = \"systemctl stop qdb \";\n          stopqdb += name.c_str();\n          system(stopqdb.c_str());\n          std::string qdbpath = getenv(\"QDB_PATH\") ? getenv(\"QDB_PATH\") : \"\";\n          std::string qdbcluster = getenv(\"QDB_CLUSTER_ID\") ? getenv(\"QDB_CLUSTER_ID\") :\n                                   \"\";\n          std::string qdbnode = getenv(\"QDB_NODE\") ? getenv(\"QDB_NODE\") : \"\";\n\n          if (qdbpath.empty())    {\n            fprintf(stderr, \"error: QDB_PATH is undefined in your configuration\\n\");\n            global_retc = EINVAL;\n            return 0;\n          }\n\n          if (qdbcluster.empty()) {\n            fprintf(stderr, \"error: QDB_CLUSTER_ID is undefined in your configuration\\n\");\n            global_retc = EINVAL;\n            return 0;\n          }\n\n          if (qdbnode.empty()) {\n            fprintf(stderr, \"error: QDB_NODE is undefined in your configuration\\n\");\n            global_retc = EINVAL;\n            return 0;\n          }\n\n          struct stat buf;\n\n          if (!::stat(qdbpath.c_str(), &buf)) {\n            fprintf(stderr,\n                    \"error: path '%s' exists - to create a new observer this path has to be changed or removed\\n\",\n                    qdbpath.c_str());\n            global_retc = EINVAL;\n            return 0;\n          } else {\n            fprintf(stderr, \"info: creating QDB under %s ...\\n\", qdbpath.c_str());\n          }\n\n          std::string kline;\n          kline = \"quarkdb-create --path \";\n          kline += qdbpath;\n          kline += \" --clusterID \";\n          kline += qdbcluster;\n          int rc = system(kline.c_str());\n          fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(), WEXITSTATUS(rc));\n          global_retc = WEXITSTATUS(rc);\n\n          if (!global_retc) {\n            fprintf(stderr, \"info: to get this node joining the cluster you do:\\n\");\n            fprintf(stderr, \"1 [ this node ] : systemctl start eos5-@qdb@%s\\n\",\n                    name.c_str());\n            fprintf(stderr, \"2 [ leader    ] : eos daemon config qdb %s add %s\\n\",\n                    name.c_str(), qdbnode.c_str());\n            fprintf(stderr, \"3 [ leader    ] : eos daemon config qdb %s promote %s\\n\",\n                    name.c_str(), qdbnode.c_str());\n          }\n\n          return (0);\n        }\n      } else if (subcmd == \"backup\") {\n        std::string qdbpath = \"/var/lib/qdb1\";\n\n        for (auto it : cfg[chapter.c_str()]) {\n          size_t pos;\n\n          if ((pos = it.find(\"redis.database\")) != std::string::npos) {\n            qdbpath = it;\n            qdbpath.erase(pos, 15);\n          }\n        }\n\n        std::string kline;\n        std::string qdblocation = qdbpath;\n        qdblocation += \"/backup/\";\n        qdblocation += std::to_string(time(NULL));\n        kline += \"export REDISCLI_AUTH=`cat /etc/eos.keytab`; redis-cli -p `cat \";\n        kline += cfile;\n        kline += \"|grep xrd.port | cut -d ' ' -f 2` <<< \\\"quarkdb-checkpoint \";\n        kline += qdblocation;\n        kline += \"\\\"\";\n        int rc = system(kline.c_str());\n        fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(), WEXITSTATUS(rc));\n        global_retc = WEXITSTATUS(rc);\n        return (0);\n      }\n    }\n\n    fprintf(stderr, \"# ---------------------------------------\\n\");\n    fprintf(stderr, \"# ------------- i n i t -----------------\\n\");\n    fprintf(stderr, \"# ---------------------------------------\\n\");\n    fprintf(stderr, \"%s\\n\", cfg.Dump(\"init\", true).c_str());\n    fprintf(stderr, \"# ---------------------------------------\\n\");\n    fprintf(stderr, \"# ------------- s y s c o n f i g -------\\n\");\n    fprintf(stderr, \"# ---------------------------------------\\n\");\n    fprintf(stderr, \"%s\\n\", cfg.Dump(\"sysconfig\", true).c_str());\n    fprintf(stderr, \"# ---------------------------------------\\n\");\n    fprintf(stderr, \"# ------------- m o d u l e s -----------\\n\");\n    fprintf(stderr, \"# ---------------------------------------\\n\");\n    fprintf(stderr, \"%s\\n\", cfg.Dump(\"modules\", true).c_str());\n    fprintf(stderr, \"# ---------------------------------------\\n\");\n    fprintf(stderr, \"# ------------- x r o o t d  ------------\\n\");\n    fprintf(stderr, \"# ---------------------------------------\\n\");\n    fprintf(stderr, \"# running config file: %s\\n\", cfile.c_str());\n    fprintf(stderr, \"%s\\n\", cfg.Dump(chapter.c_str(), true).c_str());\n    fprintf(stderr, \"#########################################\\n\");\n  } else if (option == \"module-init\") {\n    std::string initfile = \"/tmp/.eos.daemon.init\";\n    std::string initsection = name.c_str();\n    initsection += \":init\";\n\n    if (!eos::common::StringConversion::SaveStringIntoFile(initfile.c_str(),\n        cfg.Dump(initsection.c_str(), true))) {\n      fprintf(stderr, \"error: unable to create startup config file '%s'\\n\",\n              initfile.c_str());\n      global_retc = errno;\n      return (0);\n    } else {\n      chmod(initfile.c_str(), S_IRWXU);\n      // run the init file\n      int rc = system(initfile.c_str());\n\n      if (WEXITSTATUS(rc)) {\n        fprintf(stderr, \"error: init script '%s' failed with errc=%d\\n\",\n                initsection.c_str(), WEXITSTATUS(rc));\n        global_retc = WEXITSTATUS(rc);\n        return (0);\n      }\n    }\n  } else if (option == \"stack\") {\n    std::string kline;\n    kline = \"test -e \";\n    kline += envfile.c_str();\n    kline += \" && eu-stack -p `cat \";\n    kline += envfile.c_str();\n    kline += \"| cut -d '&' -f 1 | cut -d '=' -f 2`\";\n    int rc = system(kline.c_str());\n    fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(), WEXITSTATUS(rc));\n    global_retc = WEXITSTATUS(rc);\n    return (0);\n  } else if (option == \"jwk\") {\n    XrdOucString jwkfile = name.c_str();\n    struct stat buf;\n\n    if (::stat(jwkfile.c_str(), &buf)) {\n      fprintf(stderr, \"error: jwk key file '%s' does not exist!\\n\", jwkfile.c_str());\n      global_retc = ENOENT;\n      return (0);\n    }\n\n    std::string kline;\n    kline = \"env EOS_JWK=\\\"$(cat \\\"\";\n    kline += jwkfile.c_str();\n    kline += \"\\\")\\\" /sbin/eos-jwk-https\";\n    int rc = system(kline.c_str());\n    fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(), WEXITSTATUS(rc));\n    global_retc = WEXITSTATUS(rc);\n    return (0);\n  } else if (option == \"kill\" || option == \"restart\") {\n\tif (option == \"kill\" && pid == -1){\n\t  fprintf(stderr, \"error: %s is not launched\\n\", service.c_str());\n\t  return 0;\n\t}\n    std::string kline;\n    kline = \"test -e \";\n    kline += envfile.c_str();\n    kline += \" && kill -9 `cat \";\n    kline += envfile.c_str();\n    kline += \"| cut -d '&' -f 1 | cut -d '=' -f 2`\";\n    int rc = system(kline.c_str());\n    fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(), WEXITSTATUS(rc));\n\tif (option == \"kill\"){\n\t  global_retc = WEXITSTATUS(rc);\n\t  return (0);\n\t}\n  } else if (option == \"stop\") {\n    std::string kline;\n    kline = \"test -e \";\n    kline += envfile.c_str();\n    kline += \" && kill -15 `cat \";\n    kline += envfile.c_str();\n    kline += \"| cut -d '&' -f 1 | cut -d '=' -f 2`\";\n    int rc = system(kline.c_str());\n    fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(), WEXITSTATUS(rc));\n    global_retc = WEXITSTATUS(rc);\n    return (0);\n  }\n  if (option == \"run\" || option == \"restart\") {\n    if (!cfg.Has(chapter.c_str())) {\n      fprintf(stderr,\n              \"error: missing service configuration [%s] in generic config file '/etc/eos/config/generic/all' or '/etc/eos/config/%s/%s'\\n\",\n              chapter.c_str(), service.c_str(), name.c_str());\n      global_retc = EINVAL;\n      return (0);\n    }\n\n\tif (option == \"run\"){\n\t  if (pid != -1){\n\t    fprintf(stderr, \"error: %s currently running\\n\", service.c_str());\n\t  \treturn 0;\n\t  }\n\t}\n\n    char** const envv = cfg.Env(\"sysconfig\");\n\n    for (size_t i = 0; i < 1024; ++i) {\n      if (envv[i]) {\n        fprintf(stderr, \"%s\\n\", envv[i]);\n      } else {\n        break;\n      }\n    }\n\n    if (cfg.Has(\"init\")) {\n      fprintf(stderr, \"# ---------------------------------------\\n\");\n      fprintf(stderr, \"# ------------- i n i t -----------------\\n\");\n      fprintf(stderr, \"# ---------------------------------------\\n\");\n\n      if (cfg.Has(\"unshare\")) {\n        fprintf(stderr, \"# ---------------------------------------\\n\");\n        fprintf(stderr, \"# ------------- u n s h a r e -----------\\n\");\n\n        if (unshare(CLONE_NEWNS)) {\n          fprintf(stderr, \"warning: failed to unshare mount namespace errno=%d\\n\", errno);\n        }\n\n        if (mount(\"none\", \"/\", NULL, MS_REC | MS_PRIVATE, NULL)) {\n          fprintf(stderr, \"warning: failed none mount / - errno=%d\\n\", errno);\n        }\n\n        fprintf(stderr, \"# ---------------------------------------\\n\");\n      }\n\n      for (auto it : cfg[\"init\"]) {\n        bool exit_on_failure = false;\n        fprintf(stderr, \"# run: %s\\n\", it.c_str());\n        pid_t pid;\n        std::string cline = it;\n\n        if (cline.substr(0, 4) == \"enc:\") {\n          std::string key;\n          const char* pkey = subtokenizer.GetToken();\n\n          if (!pkey) {\n            key = eos::common::StringConversion::StringFromShellCmd(\"cat /etc/eos.keytab | grep u:daemon | md5sum\");\n          } else {\n            key = pkey;\n          }\n\n          std::string shakey = eos::common::SymKey::HexSha256(key);\n          XrdOucString in = cline.substr(4).c_str();;\n          XrdOucString out;\n          eos::common::SymKey::SymmetricStringDecrypt(in, out, (char*)shakey.c_str());\n\n          if (!out.c_str()) {\n            fprintf(stderr, \"error: encoded init line '%s' cannot be decoded\\n\",\n                    in.c_str());\n            continue;\n          }\n\n          cline = out.c_str();\n          exit_on_failure = true;\n        }\n\n        if (exit_on_failure) {\n          // test that nobody traces us ..\n          if (!(pid = fork())) {\n            pause();\n            exit(0);\n          } else {\n            if (ptrace(EOS_PTRACE_ATTACH, pid, 0, 0)) {\n              kill(pid, SIGKILL);\n              fprintf(stderr,\n                      \"error: failed to attach to forked process pid=%d errno=%d - we are untraceable\\n\",\n                      pid, errno);\n\n              if (exit_on_failure) {\n                exit(-1);\n              }\n            } else {\n              ptrace(EOS_PTRACE_DETACH, pid, 0, 0);\n              kill(pid, 9);\n              waitpid(pid, 0, 0);\n            }\n          }\n        }\n\n        if (!(pid = fork())) {\n          execle(\"/bin/bash\", \"eos-bash\", \"-c\", cline.c_str(), NULL, envv);\n          exit(0);\n        } else {\n          waitpid(pid, 0, 0);\n        }\n      }\n    }\n\n    if (ok) {\n      fprintf(stderr, \"# ---------------------------------------\\n\");\n      fprintf(stderr, \"# ------------- x r o o t d  ------------\\n\");\n      fprintf(stderr, \"# ---------------------------------------\\n\");\n      fprintf(stderr, \"# running config file: %s\\n\", cfile.c_str());\n      fprintf(stderr, \"# ---------------------------------------\\n\");\n      fprintf(stderr, \"%s\\n\", cfg.Dump(chapter.c_str(), true).c_str());\n      fprintf(stderr, \"#########################################\\n\");\n      eos::common::Path cPath(cfile.c_str());\n\n      if (!cPath.MakeParentPath(0x1ed)) {\n        fprintf(stderr, \"error: unable to create run directory '%s'\\n\",\n                cPath.GetParentPath());\n        global_retc = errno;\n        return (0);\n      }\n\n      if (!eos::common::StringConversion::SaveStringIntoFile(cfile.c_str(),\n          cfg.Dump(chapter.c_str(), true))) {\n        fprintf(stderr, \"error: unable to create startup config file '%s'\\n\",\n                cfile.c_str());\n        global_retc = errno;\n        return (0);\n      }\n\n      ::chdir(cPath.GetParentPath());\n      std::string logfile = \"/var/log/eos/xrdlog.\";\n      logfile += service.c_str();\n\n      if (service == \"qdb\") {\n        execle(\"/opt/eos/xrootd/bin/xrootd\", executable.c_str(), \"-n\", name.c_str(),\n               \"-c\", cfile.c_str(), \"-l\", logfile.c_str(), \"-R\", \"daemon\", \"-k\", \"fifo\", \"-s\",\n               pidfile.c_str(), NULL, envv);\n      } else {\n        execle(\"/opt/eos/xrootd/bin/xrootd\", executable.c_str(), \"-n\", name.c_str(),\n               \"-c\", cfile.c_str(), \"-l\", logfile.c_str(), \"-R\", \"daemon\", \"-s\",\n               pidfile.c_str(), NULL, envv);\n      }\n\n      return (0);\n    } else {\n      fprintf(stderr, \"error: rc=%d msg=%s\\n\", cfg.getErrc(), cfg.getMsg().c_str());\n      global_retc = cfg.getErrc();\n      return (0);\n    }\n  }\n\n  return (0);\ncom_daemon_usage:\n  fprintf(stdout,\n          \"usage: daemon config|sss|kill|run|stack|stop|jwk|module-init <service> [name] [subcmd]                                     :  \\n\");\n  fprintf(stdout,\n          \"                <service> := mgm | fst | qdb\\n\");\n  fprintf(stdout,\n          \"                config                                                -  configure a service / show configuration\\n\");\n  fprintf(stdout,\n          \"                kill                                                  -  kill -9 a given service\\n\");\n  fprintf(stdout,\n          \"                run                                                   -  run the given service daemon optionally identified by name\\n\");\n  fprintf(stdout,\n          \"                restart                                               -  restart the given service daemon optionally identified by name\\n\");\n  fprintf(stdout,\n          \"                sss recreate                                          -  re-create an instance sss key and the eosnobody keys (/etc/eos.keytab,/etc/eos/fuse.sss.keytab)'\\n\");\n  fprintf(stdout,\n          \"                stack                                                 -  print an 'eu-stack'\\n\");\n  fprintf(stdout,\n          \"                stop                                                  -  kill -15 a given service\\n\");\n  fprintf(stdout,\n          \"                jwk                                                   -  run a 'jwk' public key server on port 4443\\n\");\n  fprintf(stdout,\n          \"                module-init                                           -  run the init procedure for a module\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"      examples: eos daemon config qdb qdb coup                        -  try to make instance [qdb] a leader of QDB\\n\");\n  fprintf(stdout,\n          \"                eos daemon config qdb qdb info                        -  show raft-info for the [qdb] QDB instance\\n\");\n  fprintf(stdout,\n          \"                eos daemon config qdb qdb remove host:port            -  remove a member of the qdb cluster\\n\");\n  fprintf(stdout,\n          \"                eos daemon config qdb qdb add host:port               -  add an observer to the qdb cluster\\n\");\n  fprintf(stdout,\n          \"                eos daemon config qdb qdb promote host:port           -  promote an observer to a full member of the qdb cluster\\n\");\n  fprintf(stdout,\n          \"                eos daemon config qdb qdb new observer                -  create a new observer\\n\");\n  fprintf(stdout,\n          \"                eos daemon config fst fst.1                           -  show the init,sysconfig and xrootd config for the [fst.1] FST service\\n\");\n  fprintf(stdout,\n          \"                eos daemon kill mgm                                   -  shoot the MGM service with signal -9\\n\");\n  fprintf(stdout,\n          \"                eos daemon stop MGM                                   -  gracefully shut down the MGM service with signal -15\\n\");\n  fprintf(stdout,\n          \"                eos daemon stack mgm                                  -  take an 'eu-stack' of the MGM service\\n\");\n  fprintf(stdout,\n          \"                eos daemon run fst fst.1                              -  run the fst.1 subservice FST\\n\");\n  global_retc = EINVAL;\n  return (0);\n#endif\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_debug.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_debug.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n/*----------------------------------------------------------------------------*/\n\n/* Debug Level Setting */\nint\ncom_debug(char* arg1)\n{\n  // split subcommands\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString level = subtokenizer.GetToken();\n  XrdOucString nodequeue = subtokenizer.GetToken();\n  XrdOucString filterlist = \"\";\n\n  if ((level != \"-h\") && (level != \"--help\")) {\n    if (level == \"this\") {\n      global_debug = !global_debug;\n      fprintf(stdout, \"info: toggling shell debugmode to debug=%d\\n\", global_debug);\n      return (0);\n    }\n\n    // @todo the addition of a subcommand makes the variable names of the parsed token strings very misleading. Change them\n    if (level == \"getloglevel\") {\n      XrdOucString in = \"mgm.cmd=debug&mgm.subcmd=\";\n      in += level;\n      global_retc = output_result(client_command(in, true));\n      return (0);\n    }\n\n    if (level.length()) {\n      XrdOucString in = \"mgm.cmd=debug&mgm.debuglevel=\";\n      in += level;\n\n      if (nodequeue.length()) {\n        if (nodequeue == \"--filter\") {\n          filterlist = subtokenizer.GetToken();\n          in += \"&mgm.filter=\";\n          in += filterlist;\n        } else {\n          in += \"&mgm.nodename=\";\n          in += nodequeue;\n          nodequeue = subtokenizer.GetToken();\n\n          if (nodequeue == \"--filter\") {\n            filterlist = subtokenizer.GetToken();\n            in += \"&mgm.filter=\";\n            in += filterlist;\n          }\n        }\n      }\n\n      global_retc = output_result(client_command(in, true));\n      return (0);\n    }\n  }\n\n  fprintf(stdout,\n          \"Usage: debug [node-queue] this|<level> [--filter <unitlist>]\\n\");\n  fprintf(stdout,\n          \"'[eos] debug ...' allows to modify the verbosity of the EOS log files in MGM and FST services.\\n\\n\");\n  fprintf(stdout, \"Options:\\n\");\n  fprintf(stdout, \"debug  this :\\n\");\n  fprintf(stdout,\n          \"                                                  toggle EOS shell debug mode\\n\");\n  fprintf(stdout, \"debug  <level> [--filter <unitlist>] :\\n\");\n  fprintf(stdout,\n          \"                                                  set the MGM where the console is connected to into debug level <level>\\n\");\n  fprintf(stdout, \"debug  <level> <node-queue> [--filter <unitlist>] :\\n\");\n  fprintf(stdout,\n          \"                                                  set the <node-queue> into debug level <level>. <node-queue> are internal EOS names e.g. '/eos/<hostname>:<port>/fst'\\n\");\n  fprintf(stdout,\n          \"     <unitlist> : a comma separated list of strings of software units which should be filtered out in the message log!\\n\");\n  fprintf(stdout,\n          \"                  The default filter list is: 'Process,AddQuota,Update,UpdateHint,Deletion,PrintOut,SharedHash,work'.\\n\\n\");\n  fprintf(stdout,\n          \"The allowed debug levels are: debug info warning notice err crit alert emerg\\n\\n\");\n  fprintf(stdout, \"Examples:\\n\");\n  fprintf(stdout,\n          \"  debug info *                         set MGM & all FSTs into debug mode 'info'\\n\\n\");\n  fprintf(stdout,\n          \"  debug err /eos/*/fst                 set all FSTs into debug mode 'err'\\n\\n\");\n  fprintf(stdout,\n          \"  debug crit /eos/*/mgm                set MGM into debug mode 'crit'\\n\\n\");\n  fprintf(stdout,\n          \"  debug debug --filter MgmOfsMessage   set MGM into debug mode 'debug' and filter only messages coming from unit 'MgmOfsMessage'.\\n\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_du.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: com_proto_df.cc\n// @author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n\nextern int com_proto_find(char* arg);\nvoid com_du_help();\n\n//------------------------------------------------------------------------------\n// Du CLI\n//------------------------------------------------------------------------------\nint\ncom_du(char* arg)\n{\n  // front-end wrapper using com_proto_find\n  if (wants_help(arg, true)) {\n    com_du_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  std::string token;\n\n  bool printfiles = false;\n  bool printreadable = false;\n  bool printsummary = false;\n  bool printsi = false;\n\n  std::string path;\n\n  do {\n    if (!tokenizer.NextToken(token)) {\n      com_du_help();\n      global_retc = EINVAL;\n      return EINVAL;\n    }\n\n    if (token == \"-a\") {\n      printfiles = true;\n    } else {\n      if (token == \"-h\") {\n\tprintreadable = true;\n      } else {\n\tif (token == \"-s\") {\n\t  printsummary = true;\n\t} else {\n\t  if ( token == \"--si\" ) {\n\t    printsi = true;\n\t  } else {\n\t    path = abspath(token.c_str());\n\t    break;\n\t  }\n\t}\n      }\n    }\n  } while (1);\n\n  std::string cmd = \"--du\";\n  if (!printfiles) {\n    cmd += \" -d\";\n  }\n  if (printsi) {\n    cmd += \" --du-si\";\n  }\n  if (printreadable) {\n    cmd += \" --du-h\";\n  }\n  if (printsummary) {\n    cmd += \" --maxdepth 0\";\n  }\n  cmd += \" \";\n  cmd += path;\n\n  return com_proto_find((char*)cmd.c_str());\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_du_help()\n{\n  std::ostringstream oss;\n  oss\n      << \" usage:\\n\"\n      << \"du [-a][-h][-s][--si] path\\n\"\n      << \"'[eos] du ...' print unix like 'du' information showing subtreesize for directories\\n\"\n      << std::endl\n      << \"Options:\\n\"\n      << std::endl\n      << \"-a   : print also for files\\n\"\n      << \"-h   : print human readable in units of 1000\\n\"\n      << \"-s   : print only the summary\\n\"\n      << \"--si : print in si units\\n\"\n      << std::endl;\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_evict.cc",
    "content": "//------------------------------------------------------------------------------\n// File: com_evict.cc\n// Author: Jozsef Makai - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringTokenizer.hh\"\n#include \"common/Path.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/ICmdHelper.hh\"\n\nvoid com_evict_help();\n\n//------------------------------------------------------------------------------\n//! Class EvictHelper\n//------------------------------------------------------------------------------\nclass EvictHelper: public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  EvictHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {\n    mIsAdmin = false;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~EvictHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n};\n\nbool\nEvictHelper::ParseCommand(const char* arg)\n{\n  const char* nextToken;\n  std::string sNextToken;\n  eos::console::EvictProto* evict = mReq.mutable_evict();\n  eos::common::StringTokenizer tokenizer(arg);\n  XrdOucString path = tokenizer.GetLine();\n\n  if (!(nextToken = tokenizer.GetToken())) {\n    return false;\n  }\n\n\n  for (\n      sNextToken = nextToken;\n      sNextToken == \"--fsid\" || sNextToken == \"--ignore-evict-counter\" || sNextToken == \"--ignore-removal-on-fst\";\n      nextToken = tokenizer.GetToken(), sNextToken = nextToken ? nextToken : \"\"\n      ) {\n    if (sNextToken == \"--ignore-evict-counter\") {\n      evict->set_ignoreevictcounter(true);\n    } else if (sNextToken == \"--ignore-removal-on-fst\") {\n      evict->set_ignoreremovalonfst(true);\n    } else if (sNextToken == \"--fsid\") {\n      if (!(nextToken = tokenizer.GetToken())) {\n        std::cerr << \"error: --fsid needs to be followed by value\" << std::endl;\n        return false;\n      } else {\n        try {\n          uint64_t fsid = std::stoull(nextToken);\n          evict->mutable_evictsinglereplica()->set_fsid(fsid);\n        } catch (const std::exception& e) {\n          std::cerr << \"error: --fsid value needs to be numeric\" << std::endl;\n          return false;\n        }\n      }\n    }\n  }\n\n  if (evict->has_evictsinglereplica() && !evict->ignoreevictcounter()) {\n    std::cerr << \"error: --fsid can only be used with --ignore-evict-counter\" << std::endl;\n    return false;\n  }\n\n  if (!evict->has_evictsinglereplica() && evict->ignoreremovalonfst()) {\n    std::cerr << \"error: --ignore-removal-on-fst can only be used with --fsid\" << std::endl;\n    return false;\n  }\n\n  path = nextToken;\n  while (path != \"\") {\n    // remove escaped blanks\n    while (path.replace(\"\\\\ \", \" \"));\n\n    if (path != \"\") {\n      auto file = evict->add_file();\n      auto fid = 0ull;\n\n      if (Path2FileDenominator(path, fid)) {\n        file->set_fid(fid);\n      } else {\n        path = abspath(path.c_str());\n        file->set_path(path.c_str());\n      }\n    }\n\n    path = tokenizer.GetToken();\n  }\n\n  // at least 1 path has to be given\n  return evict->file_size() > 0;\n}\n\n//------------------------------------------------------------------------------\n// Evict command entry point\n//------------------------------------------------------------------------------\nint com_evict(char* arg)\n{\n  if (wants_help(arg)) {\n    com_evict_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  EvictHelper evict(gGlobalOpts);\n\n  if (!evict.ParseCommand(arg)) {\n    com_evict_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = evict.Execute();\n  return global_retc;\n}\n\nvoid com_evict_help()\n{\n  std::ostringstream oss;\n  oss << \"Usage: evict [--fsid <fsid>] [--ignore-removal-on-fst] [--ignore-evict-counter] <path>|fid:<fid-dec>]|fxid:<fid-hex> [<path>|fid:<fid-dec>]|fxid:<fid-hex>] ...\\n\"\n      << \"    Removes disk replicas of the given files, separated by space\\n\"\n      << std::endl\n      << \"  Optional arguments:\\n\"\n      << \"    --ignore-evict-counter  : Force eviction by bypassing evict counter\\n\"\n      << \"    --fsid <fsid>           : Evict disk copy only from a single fsid\\n\"\n      << \"    --ignore-removal-on-fst : Ignore file removal on fst, namespace-only operation\\n\"\n      << std::endl\n      << \"    This command requires 'write' and 'p' acl flag permission\\n\"\n      << std::endl;\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_file.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_file.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#define __STDC_FORMAT_MACROS\n#include <inttypes.h>\n\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/FileId.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/Fmd.hh\"\n#include <XrdCl/XrdClFileSystem.hh>\n\n#ifdef __APPLE__\n#define ECOMM 70\n#endif\n\nvoid com_fileinfo_help();\n\n//------------------------------------------------------------------------------\n//! Return Fmd from a remote filesystem\n//!\n//! @param manager host:port of the server to contact\n//! @param shexfid hex string of the file id\n//! @param sfsid string of filesystem id\n//! @param fmd reference to the Fmd struct to store Fmd\n//------------------------------------------------------------------------------\nint\nGetRemoteFmdFromLocalDb(const char* manager, const char* shexfid,\n                        const char* sfsid, eos::common::FmdHelper& fmd)\n{\n  if ((!manager) || (!shexfid) || (!sfsid)) {\n    return EINVAL;\n  }\n\n  int rc = 0;\n  XrdCl::Buffer arg;\n  XrdCl::Buffer* response = 0;\n  XrdCl::XRootDStatus status;\n  XrdOucString fmdquery = \"/?fst.pcmd=getfmd&fst.getfmd.fid=\";\n  fmdquery += shexfid;\n  fmdquery += \"&fst.getfmd.fsid=\";\n  fmdquery += sfsid;\n  XrdOucString address = \"root://\";\n  address += manager;\n  address += \"//dummy\";\n  XrdCl::URL url(address.c_str());\n\n  if (!url.IsValid()) {\n    eos_static_err(\"error=URL is not valid: %s\", address.c_str());\n    return EINVAL;\n  }\n\n  std::unique_ptr<XrdCl::FileSystem> fs(new XrdCl::FileSystem(url));\n\n  if (!fs) {\n    eos_static_err(\"error=failed to get new FS object\");\n    return EINVAL;\n  }\n\n  arg.FromString(fmdquery.c_str());\n  status = fs->Query(XrdCl::QueryCode::OpaqueFile, arg, response);\n\n  if (status.IsOK()) {\n    rc = 0;\n    eos_static_debug(\"got replica file meta data from server %s for fxid=%s fsid=%s\",\n                     manager, shexfid, sfsid);\n  } else {\n    rc = ECOMM;\n    eos_static_err(\"Unable to retrieve meta data from server %s for fxid=%s fsid=%s\",\n                   manager, shexfid, sfsid);\n  }\n\n  if (rc) {\n    delete response;\n    return EIO;\n  }\n\n  if (!strncmp(response->GetBuffer(), \"ERROR\", 5)) {\n    // remote side couldn't get the record\n    eos_static_info(\"Unable to retrieve meta data on remote server %s for fxid=%s fsid=%s\",\n                    manager, shexfid, sfsid);\n    delete response;\n    return ENODATA;\n  }\n\n  // get the remote file meta data into an env hash\n  XrdOucEnv fmdenv(response->GetBuffer());\n\n  if (!eos::common::EnvToFstFmd(fmdenv, fmd)) {\n    int envlen;\n    eos_static_err(\"Failed to unparse file meta data %s\", fmdenv.Env(envlen));\n    delete response;\n    return EIO;\n  }\n\n  // very simple check\n  if (fmd.mProtoFmd.fid() != eos::common::FileId::Hex2Fid(shexfid)) {\n    eos_static_err(\"Uups! Received wrong meta data from remote server - fid \"\n                   \"is %lu instead of %lu !\", fmd.mProtoFmd.fid(),\n                   eos::common::FileId::Hex2Fid(shexfid));\n    delete response;\n    return EIO;\n  }\n\n  delete response;\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Fileinfo command entry point\n//------------------------------------------------------------------------------\nint\ncom_fileinfo(char* arg1)\n{\n  XrdOucString savearg = arg1;\n  // Split subcommands\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString path = subtokenizer.GetToken();\n  XrdOucString option = \"\";\n\n  do {\n    XrdOucString newoption = subtokenizer.GetToken();\n\n    if (!newoption.length()) {\n      break;\n    } else {\n      if (newoption == \"s\") {\n        option += \"silent\";\n      } else {\n        option += newoption;\n      }\n    }\n  } while (1);\n\n  if (wants_help(savearg.c_str())) {\n    com_fileinfo_help();\n    return 0;\n  }\n\n  if (!path.length() || path.beginswith(\"-\")) {\n    com_fileinfo_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  if ((!path.beginswith(\"fid:\")) && (!path.beginswith(\"fxid:\")) &&\n      (!path.beginswith(\"pid:\")) && (!path.beginswith(\"pxid:\")) &&\n      (!path.beginswith(\"inode:\"))) {\n    path = abspath(path.c_str());\n  }\n\n  XrdOucString in = \"mgm.cmd=fileinfo&\";\n  in += \"mgm.path=\";\n  in += path;\n\n  if (option.length()) {\n    in += \"&mgm.file.info.option=\";\n    in += option;\n  }\n\n  if ((option.find(\"silent\") == STR_NPOS)) {\n    global_retc = output_result(client_command(in));\n  }\n\n  return (0);\n}\n\n//------------------------------------------------------------------------------\n// Fileinfo help message\n//------------------------------------------------------------------------------\nvoid com_fileinfo_help()\n{\n  std::ostringstream oss;\n  oss << \"Usage: fileinfo <identifier> [--path] [--fid] [--fxid] [--size] \"\n      << \"[--checksum] [--fullpath] [--proxy] [-m] [--env] [-s|--silent]\\n\"\n      << \"  Prints information for specified <identifier>\\n\"\n      << \"  <identifier> = <path>|fid:<fid-dec>|fxid:<fid-hex>|\"\n      << \"pid:<contid-dec>|pxid:<contid-hex>|inode:<inode-dec>\\n\"\n      << \"\\n\"\n      << \"  fid/fxid - refers to a file identifier\\n\"\n      << \"  pid/pxid - refers to a container identifier\\n\"\n      << \"  inode    - refers to a fuse encoded inode value\\n\"\n      << \"\\n\"\n      << \"Options:\\n\"\n      << \"  --path        : filters output to show path field\\n\"\n      << \"  --fid         : filters output to show fid field\\n\"\n      << \"  --fxid        : filters output to show fxid field\\n\"\n      << \"  --size        : filters output to show size field\\n\"\n      << \"  --checksum    : filters output to show checksum field\\n\"\n      << \"  --fullpath    : adds physical path information to the output\\n\"\n      << \"  --proxy       : adds proxy information to the output\\n\"\n      << \"  --env         : prints information in OucEnv format\\n\"\n      << \"  -m            : prints single-line information in monitoring format\\n\"\n      << \"  -s | --silent : silent - used to run as internal command\\n\"\n      << \"\\n\"\n      << \" Remarks:\\n\"\n      << \"  Filters stack up and apply only to normal display mode.\\n\"\n      << \"  Command also supports JSON output.\\n\";\n  std::cout << oss.str() << std::endl;\n}\n\nint\ncom_file(char* arg1)\n{\n  XrdOucString savearg = arg1;\n  XrdOucString arg = arg1;\n  eos::common::StringTokenizer subtokenizer(arg1);\n  XrdOucString option = \"\";\n  XrdOucString path = \"\";\n  subtokenizer.GetLine();\n  XrdOucString cmd = subtokenizer.GetToken();\n  XrdOucString tmpArg;\n\n  do {\n    tmpArg = subtokenizer.GetToken();\n\n    if (tmpArg.beginswith(\"-\")) {\n      while (tmpArg.replace(\"-\", \"\")) {\n      }\n\n      option += tmpArg;\n    } else {\n      path = tmpArg;\n      break;\n    }\n  } while (1);\n\n  XrdOucString fsid1 = subtokenizer.GetToken();\n  XrdOucString fsid2 = subtokenizer.GetToken();\n  XrdOucString fsid3 = subtokenizer.GetToken();\n  XrdOucString in = \"mgm.cmd=file\";\n\n  if (!path.length()) {\n    goto com_file_usage;\n  }\n\n  if (wants_help(savearg.c_str())) {\n    goto com_file_usage;\n  }\n\n  if ((cmd != \"drop\") && (cmd != \"move\") && (cmd != \"touch\") &&\n      (cmd != \"replicate\") && (cmd != \"check\") && (cmd != \"adjustreplica\") &&\n      (cmd != \"info\") && (cmd != \"layout\") && (cmd != \"verify\") &&\n      (cmd != \"rename\") && (cmd != \"copy\") && (cmd != \"convert\") &&\n      (cmd != \"share\") && (cmd != \"purge\") && (cmd != \"version\") &&\n      (cmd != \"versions\") && (cmd != \"symlink\") && (cmd != \"tag\") &&\n      (cmd != \"workflow\") && (cmd != \"rename_with_symlink\")) {\n    goto com_file_usage;\n  }\n\n  if ((!path.beginswith(\"fid:\")) && (!path.beginswith(\"fxid:\"))) {\n    path = abspath(path.c_str());\n  }\n\n  // convenience function\n  if (cmd == \"info\") {\n    arg.erase(0, arg.find(\" \") + 1);\n    return com_fileinfo((char*) arg.c_str());\n  }\n\n  if (cmd == \"rename\") {\n    if (!path.length() || !fsid1.length()) {\n      goto com_file_usage;\n    }\n\n    fsid1 = abspath(fsid1.c_str());\n    in += \"&mgm.subcmd=rename\";\n    in += Path2FileDenominator(path) ? \"&mgm.file.id=\" : \"&mgm.path=\";\n    in += path;\n    in += \"&mgm.file.source=\";\n    in += path;\n    in += \"&mgm.file.target=\";\n    in += fsid1.c_str();\n  }\n\n  if (cmd == \"rename_with_symlink\") {\n    if (!path.length() || !fsid1.length()) {\n      goto com_file_usage;\n    }\n\n    fsid1 = abspath(fsid1.c_str());\n    in += \"&mgm.subcmd=rename_with_symlink\";\n    in += Path2FileDenominator(path) ? \"&mgm.file.id=\" : \"&mgm.path=\";\n    in += path;\n    in += \"&mgm.file.source=\";\n    in += path;\n    in += \"&mgm.file.target=\";\n    in += fsid1.c_str();\n  }\n\n  if (cmd == \"symlink\") {\n    if (!path.length() || !fsid1.length()) {\n      goto com_file_usage;\n    }\n\n    in += \"&mgm.path=\";\n    in += path;\n    in += \"&mgm.subcmd=symlink\";\n    in += \"&mgm.file.source=\";\n    in += path.c_str();\n    in += \"&mgm.file.target=\";\n    in += fsid1.c_str();\n\n    if (option.find(\"f\") != STR_NPOS) {\n      in +=  \"&mgm.file.force=1\";\n    }\n  }\n\n  if (cmd == \"share\") {\n    if (!path.length()) {\n      goto com_file_usage;\n    }\n\n    path = abspath(path.c_str());\n    in += \"&mgm.path=\";\n    in += path;\n    in += \"&mgm.subcmd=share\";\n    in += \"&mgm.file.expires=\";\n    unsigned long long expires = (time(NULL) + 28 * 86400);\n\n    if (fsid1.length()) {\n      expires = time(NULL) + eos::common::StringConversion::GetSizeFromString(fsid1);\n    }\n\n    char sexpires[1024];\n    snprintf(sexpires, sizeof(sexpires) - 1, \"%llu\", expires);\n    in += sexpires;\n  }\n\n  if (cmd == \"touch\") {\n    if (!path.length()) {\n      goto com_file_usage;\n    }\n\n    in += Path2FileDenominator(path) ? \"&mgm.file.id=\" : \"&mgm.path=\";\n    in += path;\n    in += \"&mgm.subcmd=touch\";\n\n    if (option.find(\"n\") != STR_NPOS) {\n      in += \"&mgm.file.touch.nolayout=true\";\n    }\n\n    if (option.find(\"0\") != STR_NPOS) {\n      in += \"&mgm.file.touch.truncate=true\";\n    }\n\n    if (option.find(\"a\") != STR_NPOS) {\n      in += \"&mgm.file.touch.absorb=true\";\n    }\n\n    if (option.find(\"l\") != STR_NPOS) {\n      in += \"&mgm.file.touch.lockop=lock\";\n\n      if (fsid1.length()) {\n        in += \"&mgm.file.touch.lockop.lifetime=\";\n        in += fsid1.c_str();\n        fsid1 = \"\";\n      }\n\n      if (fsid2.length()) {\n        if ((fsid2 != \"app\") &&\n            (fsid2 != \"user\")) {\n          goto com_file_usage;\n        }\n\n        if (fsid2 == \"app\") {\n          // this is inverted logic because we set the wildcard\n          in += \"&mgm.file.touch.wildcard=user\";\n        } else {\n          // this is inverted logic because we set the wildcard\n          in += \"&mgm.file.touch.wildcard=app\";\n        }\n\n        fsid2 = \"\";\n      }\n    }\n\n    if (option.find(\"u\") != STR_NPOS) {\n      in += \"&mgm.file.touch.lockop=unlock\";\n      fsid1 = \"\";\n      fsid2 = \"\";\n    }\n\n    if (fsid1.length()) {\n      if (fsid1.beginswith(\"/\")) {\n        in += \"&mgm.file.touch.hardlinkpath=\";\n        in += fsid1.c_str();\n      } else {\n        in += \"&mgm.file.touch.size=\";\n        in += fsid1.c_str();\n      }\n    }\n\n    if (fsid2.length()) {\n      in += \"&mgm.file.touch.checksuminfo=\";\n      in += fsid2.c_str();\n    }\n  }\n\n  if (cmd == \"drop\") {\n    if (!path.length() || !fsid1.length()) {\n      goto com_file_usage;\n    }\n\n    in += \"&mgm.subcmd=drop\";\n    in += Path2FileDenominator(path) ? \"&mgm.file.id=\" : \"&mgm.path=\";\n    in += path;\n    in += \"&mgm.file.fsid=\";\n    in += fsid1;\n\n    if (fsid2 == \"-f\") {\n      in += \"&mgm.file.force=1\";\n    } else {\n      if (fsid2.length()) {\n        goto com_file_usage;\n      }\n    }\n  }\n\n  if (cmd == \"move\") {\n    if (!path.length() || !fsid1.length() || !fsid2.length()) {\n      goto com_file_usage;\n    }\n\n    in += \"&mgm.subcmd=move\";\n    in += Path2FileDenominator(path) ? \"&mgm.file.id=\" : \"&mgm.path=\";\n    in += path;\n    in += \"&mgm.file.sourcefsid=\";\n    in += fsid1;\n    in += \"&mgm.file.targetfsid=\";\n    in += fsid2;\n  }\n\n  if (cmd == \"copy\") {\n    XrdOucString dest_path = fsid1;\n\n    if (!path.length() || !dest_path.length()) {\n      goto com_file_usage;\n    }\n\n    in += \"&mgm.subcmd=copy\";\n    in += Path2FileDenominator(path) ? \"&mgm.file.id=\" : \"&mgm.path=\";\n    in += path;\n\n    if (option.length()) {\n      XrdOucString checkoption = option;\n      checkoption.replace(\"f\", \"\");\n      checkoption.replace(\"s\", \"\");\n      checkoption.replace(\"c\", \"\");\n\n      if (checkoption.length()) {\n        goto com_file_usage;\n      }\n\n      in += \"&mgm.file.option=\";\n      in += option;\n    }\n\n    dest_path = abspath(dest_path.c_str());\n    in += \"&mgm.file.target=\";\n    in += dest_path;\n  }\n\n  if (cmd == \"convert\") {\n    XrdOucString layout = fsid1;\n    XrdOucString space = fsid2;\n    XrdOucString plctplcty = fsid3;\n    XrdOucString checksum = subtokenizer.GetToken();\n\n    if (!path.length()) {\n      goto com_file_usage;\n    }\n\n    in += \"&mgm.subcmd=convert\";\n    in += Path2FileDenominator(path) ? \"&mgm.file.id=\" : \"&mgm.path=\";\n    in += path;\n\n    if (layout.length()) {\n      in += \"&mgm.convert.layout=\";\n      in += layout;\n    }\n\n    if (space.length()) {\n      in += \"&mgm.convert.space=\";\n      in += space;\n    }\n\n    if (plctplcty.length()) {\n      in += \"&mgm.convert.placementpolicy=\";\n      in += plctplcty;\n    }\n\n    if (checksum.length()) {\n      in += \"&mgm.convert.checksum=\";\n      in += checksum;\n    }\n\n    if (option == \"sync\") {\n      fprintf(stderr, \"error: --sync is currently not supported\\n\");\n      goto com_file_usage;\n    }\n\n    if (option == \"rewrite\") {\n      in += \"&mgm.option=rewrite\";\n    } else {\n      if (option.length()) {\n        goto com_file_usage;\n      }\n    }\n  }\n\n  if (cmd == \"replicate\") {\n    if (!path.length() || !fsid1.length() || !fsid2.length()) {\n      goto com_file_usage;\n    }\n\n    in += \"&mgm.subcmd=replicate\";\n    in += Path2FileDenominator(path) ? \"&mgm.file.id=\" : \"&mgm.path=\";\n    in += path;\n    in += \"&mgm.file.sourcefsid=\";\n    in += fsid1;\n    in += \"&mgm.file.targetfsid=\";\n    in += fsid2;\n  }\n\n  if ((cmd == \"purge\") || (cmd == \"version\")) {\n    if (!path.length()) {\n      goto com_file_usage;\n    }\n\n    in += \"&mgm.subcmd=\";\n    in += cmd;\n    in += \"&mgm.path=\";\n    in += path;\n    in += \"&mgm.purge.version=\";\n\n    if (fsid1.length()) {\n      in += fsid1;\n    } else {\n      in += \"-1\";\n    }\n  }\n\n  if (cmd == \"versions\") {\n    if (!path.length()) {\n      goto com_file_usage;\n    }\n\n    in += \"&mgm.subcmd=\";\n    in += cmd;\n    in += Path2FileDenominator(path) ? \"&mgm.file.id=\" : \"&mgm.path=\";\n    in += path;\n    in += \"&mgm.grab.version=\";\n\n    if (fsid1.length()) {\n      in += fsid1;\n    } else {\n      in += \"-1\";\n    }\n  }\n\n  if (cmd == \"adjustreplica\") {\n    if (!path.length()) {\n      goto com_file_usage;\n    }\n\n    in += \"&mgm.subcmd=adjustreplica\";\n    in += Path2FileDenominator(path) ? \"&mgm.file.id=\" : \"&mgm.path=\";\n    in += path;\n    std::vector<std::string> args;\n\n    if (fsid1.length()) {\n      args.push_back(fsid1.c_str());\n    }\n\n    if (fsid2.length()) {\n      args.push_back(fsid2.c_str());\n    }\n\n    if (fsid3.length()) {\n      args.push_back(fsid3.c_str());\n    }\n\n    const char* token;\n\n    while ((token = subtokenizer.GetToken())) {\n      args.push_back(token);\n    }\n\n    int positional_index = 0;\n\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (args[i] == \"--exclude-fs\") {\n        if (i + 1 < args.size()) {\n          in += \"&mgm.file.excludefs=\";\n          in += args[i + 1].c_str();\n          i++; // skip value\n        } else {\n          goto com_file_usage;\n        }\n      } else {\n        // Positional\n        if (positional_index == 0) {\n          in += \"&mgm.file.desiredspace=\";\n          in += args[i].c_str();\n          positional_index++;\n        } else if (positional_index == 1) {\n          in += \"&mgm.file.desiredsubgroup=\";\n          in += args[i].c_str();\n          positional_index++;\n        } else {\n          goto com_file_usage;\n        }\n      }\n    }\n  }\n\n  if (cmd == \"layout\") {\n    if (!path.length()) {\n      goto com_file_usage;\n    }\n\n    in += \"&mgm.subcmd=layout\";\n    in += Path2FileDenominator(path) ? \"&mgm.file.id=\" : \"&mgm.path=\";\n    in += path;\n\n    if ((fsid1 != \"-stripes\") && (fsid1 != \"-checksum\") && (fsid1 != \"-type\")) {\n      goto com_file_usage;\n    }\n\n    if (!fsid2.length()) {\n      goto com_file_usage;\n    }\n\n    if (fsid1 == \"-stripes\") {\n      in += \"&mgm.file.layout.stripes=\";\n      in += fsid2;\n    }\n\n    if (fsid1 == \"-checksum\") {\n      in += \"&mgm.file.layout.checksum=\";\n      in += fsid2;\n    }\n\n    if (fsid1 == \"-type\") {\n      in += \"&mgm.file.layout.type=\";\n      in += fsid2;\n    }\n  }\n\n  if (cmd == \"workflow\") {\n    if (!path.length()) {\n      goto com_file_usage;\n    }\n\n    if (!fsid1.length()) {\n      goto com_file_usage;\n    }\n\n    if (!fsid2.length()) {\n      goto com_file_usage;\n    }\n\n    in += \"&mgm.subcmd=workflow\";\n    in += \"&mgm.path=\";\n    in += path;\n    in += \"&mgm.workflow=\";\n    in += fsid1;\n    in += \"&mgm.event=\";\n    in += fsid2;\n  }\n\n  if (cmd == \"tag\") {\n    if (!path.length()) {\n      goto com_file_usage;\n    }\n\n    in += \"&mgm.subcmd=tag\";\n    in += Path2FileDenominator(path) ? \"&mgm.file.id=\" : \"&mgm.path=\";\n    in += path;\n\n    if ((!fsid1.beginswith(\"+\")) &&\n        (!fsid1.beginswith(\"-\")) &&\n        (!fsid1.beginswith(\"~\"))) {\n      goto com_file_usage;\n    }\n\n    in += \"&mgm.file.tag.fsid=\";\n    in += fsid1;\n  }\n\n  if (cmd == \"verify\") {\n    if (!path.length()) {\n      goto com_file_usage;\n    }\n\n    const char* opt;\n    std::vector<std::string> options;\n    in += \"&mgm.subcmd=verify\";\n    in += \"&mgm.path=\";\n    in += path;\n\n    // TODO: all this is silly and should be properly re-written\n    if (fsid1.length()) {\n      if ((fsid1 != \"-checksum\") && (fsid1 != \"-commitchecksum\") &&\n          (fsid1 != \"-commitsize\") && (fsid1 != \"-commitfmd\") && (fsid1 != \"-rate\") &&\n          (fsid1 != \"-resync\")) {\n        if (fsid1.beginswith(\"-\")) {\n          goto com_file_usage;\n        }\n\n        in += \"&mgm.file.verify.filterid=\";\n        in += fsid1;\n\n        if (fsid2.length()) {\n          options.push_back(fsid2.c_str());\n\n          if (fsid3.length()) {\n            options.push_back(fsid3.c_str());\n          }\n\n          while ((opt = subtokenizer.GetToken())) {\n            options.push_back(opt);\n            opt = 0;\n          }\n        }\n      } else {\n        options.push_back(fsid1.c_str());\n\n        if (fsid2.length()) {\n          options.push_back(fsid2.c_str());\n        }\n\n        if (fsid3.length()) {\n          options.push_back(fsid3.c_str());\n        }\n\n        while ((opt = subtokenizer.GetToken())) {\n          options.push_back(opt);\n          opt = 0;\n        }\n      }\n    }\n\n    for (auto& elem : options) {\n      if (elem.length()) {\n        if (elem == \"-checksum\") {\n          in += \"&mgm.file.compute.checksum=1\";\n        } else if (elem == \"-commitchecksum\") {\n          in += \"&mgm.file.commit.checksum=1\";\n        } else if (elem == \"-commitsize\") {\n          in += \"&mgm.file.commit.size=1\";\n        } else if (elem == \"-commitfmd\") {\n          in += \"&mgm.file.commit.fmd=1\";\n        } else if (elem == \"-rate\") {\n          in += \"&mgm.file.verify.rate=\";\n        } else if (elem == \"-resync\") {\n          in += \"&mgm.file.resync=1\";\n        } else {\n          goto com_file_usage;\n        }\n      }\n    }\n  }\n\n  if (cmd == \"check\") {\n    if (!path.length()) {\n      goto com_file_usage;\n    }\n\n    in += \"&mgm.subcmd=getmdlocation\";\n    in += \"&mgm.format=fuse\";\n    in += \"&mgm.path=\";\n    in += path;\n    XrdOucString option = fsid1;\n    // Eventually disable json format to avoid parsin issues\n    bool old_json = json;\n\n    if (old_json) {\n      json = false;\n    }\n\n    XrdOucEnv* result = client_command(in);\n\n    if (old_json) {\n      json = true;\n    }\n\n    if (!result) {\n      fprintf(stderr, \"error: getmdlocation query failed\\n\");\n      global_retc = EINVAL;\n      return (0);\n    }\n\n    int envlen = 0;\n    std::unique_ptr<XrdOucEnv> newresult(new XrdOucEnv(result->Env(envlen)));\n    delete result;\n\n    if (!envlen) {\n      fprintf(stderr, \"error: couldn't get meta data information\\n\");\n      global_retc = EIO;\n      return (0);\n    }\n\n    char* ptr = newresult->Get(\"mgm.proc.retc\");\n\n    if (ptr) {\n      int retc_getmdloc = 0;\n\n      try {\n        retc_getmdloc = std::stoi(ptr);\n      } catch (...) {\n        retc_getmdloc = EINVAL;\n      }\n\n      if (retc_getmdloc) {\n        fprintf(stderr, \"error: failed getmdlocation command, errno=%i\",\n                retc_getmdloc);\n        global_retc = retc_getmdloc;\n        return (0);\n      }\n    }\n\n    XrdOucString ns_path = newresult->Get(\"mgm.nspath\");\n    XrdOucString checksumtype = newresult->Get(\"mgm.checksumtype\");\n    XrdOucString checksum = newresult->Get(\"mgm.checksum\");\n    uint64_t mgm_size = std::stoull(newresult->Get(\"mgm.size\"));\n    bool silent_cmd = ((option.find(\"%silent\") != STR_NPOS) || silent);\n\n    if (!silent_cmd) {\n      fprintf(stdout, \"path=\\\"%s\\\" fxid=\\\"%4s\\\" size=\\\"%llu\\\" nrep=\\\"%s\\\" \"\n              \"checksumtype=\\\"%s\\\" checksum=\\\"%s\\\"\\n\",\n              ns_path.c_str(), newresult->Get(\"mgm.fid0\"),\n              (unsigned long long)mgm_size, newresult->Get(\"mgm.nrep\"),\n              checksumtype.c_str(), newresult->Get(\"mgm.checksum\"));\n    }\n\n    std::string err_label;\n    std::set<std::string> set_errors;\n    int nrep_online = 0;\n    int i = 0;\n\n    for (i = 0; i < 255; ++i) {\n      err_label = \"none\";\n      XrdOucString repurl = \"mgm.replica.url\";\n      repurl += i;\n      XrdOucString repfid = \"mgm.fid\";\n      repfid += i;\n      XrdOucString repfsid = \"mgm.fsid\";\n      repfsid += i;\n      XrdOucString repbootstat = \"mgm.fsbootstat\";\n      repbootstat += i;\n      XrdOucString repfstpath = \"mgm.fstpath\";\n      repfstpath += i;\n\n      if (!newresult->Get(repurl.c_str())) {\n        break;\n      }\n\n      // Query the FSTs for stripe info\n      XrdCl::StatInfo* stat_info = 0;\n      XrdCl::XRootDStatus status;\n      std::ostringstream oss;\n      oss << \"root://\" << newresult->Get(repurl.c_str()) << \"//dummy\";\n      XrdCl::URL url(oss.str());\n\n      if (!url.IsValid()) {\n        fprintf(stderr, \"error: URL is not valid: %s\", oss.str().c_str());\n        global_retc = EINVAL;\n        return (0);\n      }\n\n      // Get XrdCl::FileSystem object\n      std::unique_ptr<XrdCl::FileSystem> fs {new XrdCl::FileSystem(url)};\n\n      if (!fs) {\n        fprintf(stderr, \"error: failed to get new FS object\");\n        global_retc = ECOMM;\n        return (0);\n      }\n\n      XrdOucString bs = newresult->Get(repbootstat.c_str());\n      bool down = (bs != \"booted\");\n\n      if (down && ((option.find(\"%force\")) == STR_NPOS)) {\n        err_label = \"DOWN\";\n        set_errors.insert(err_label);\n\n        if (!silent_cmd) {\n          fprintf(stderr, \"error: unable to retrieve file meta data from %s \"\n                  \"[ status=%s ]\\n\", newresult->Get(repurl.c_str()), bs.c_str());\n        }\n\n        continue;\n      }\n\n      // Do a remote stat using XrdCl::FileSystem\n      uint64_t stat_size = std::numeric_limits<uint64_t>::max();\n      XrdOucString statpath = newresult->Get(repfstpath.c_str());\n\n      if (!statpath.beginswith(\"/\")) {\n        // base 64 encode this path\n        XrdOucString statpath64;\n        eos::common::SymKey::Base64(statpath, statpath64);\n        statpath = \"/#/\";\n        statpath += statpath64;\n      }\n\n      status = fs->Stat(statpath.c_str(), stat_info);\n\n      if (!status.IsOK()) {\n        err_label = \"STATFAILED\";\n        set_errors.insert(err_label);\n      } else {\n        stat_size = stat_info->GetSize();\n      }\n\n      // Free memory\n      delete stat_info;\n      int retc = 0;\n      eos::common::FmdHelper fmd;\n\n      if ((retc = GetRemoteFmdFromLocalDb(newresult->Get(repurl.c_str()),\n                                          newresult->Get(repfid.c_str()),\n                                          newresult->Get(repfsid.c_str()), fmd))) {\n        if (!silent_cmd) {\n          fprintf(stderr, \"error: unable to retrieve file meta data from %s [%d]\\n\",\n                  newresult->Get(repurl.c_str()), retc);\n        }\n\n        err_label = \"NOFMD\";\n        set_errors.insert(err_label);\n      } else {\n        const auto& proto_fmd = fmd.mProtoFmd;\n        XrdOucString cx = proto_fmd.checksum().c_str();\n\n        for (unsigned int k = (cx.length() / 2); k < SHA256_DIGEST_LENGTH; ++k) {\n          cx += \"00\";\n        }\n\n        std::string disk_cx = proto_fmd.diskchecksum().c_str();\n\n        for (unsigned int k = (disk_cx.length() / 2); k < SHA256_DIGEST_LENGTH; ++k) {\n          disk_cx += \"00\";\n        }\n\n        if (eos::common::LayoutId::IsRain(proto_fmd.lid()) == false) {\n          // These checks make sense only for non-rain layouts\n          if (proto_fmd.size() != mgm_size) {\n            err_label = \"SIZE\";\n            set_errors.insert(err_label);\n          } else {\n            if (proto_fmd.size() != (unsigned long long) stat_size) {\n              err_label = \"FSTSIZE\";\n              set_errors.insert(err_label);\n            }\n          }\n\n          if (cx != checksum) {\n            err_label = \"CHECKSUM\";\n            set_errors.insert(err_label);\n          }\n\n          uint64_t disk_cx_val = 0ull;\n\n          try {\n            disk_cx_val = std::stoull(disk_cx.substr(0, 8), nullptr, 16);\n          } catch (...) {\n            // error during conversion\n          }\n\n          if ((disk_cx.length() > 0) && disk_cx_val &&\n              ((disk_cx.length() < 8) ||\n               (!cx.beginswith(disk_cx.c_str())))) {\n            err_label = \"DISK_CHECKSUM\";\n            set_errors.insert(err_label);\n          }\n\n          if (!silent_cmd) {\n            fprintf(stdout, \"nrep=\\\"%02d\\\" fsid=\\\"%s\\\" host=\\\"%s\\\" fstpath=\\\"%s\\\" \"\n                    \"size=\\\"%llu\\\" statsize=\\\"%llu\\\" checksum=\\\"%s\\\" diskchecksum=\\\"%s\\\" \"\n                    \"error_label=\\\"%s\\\"\\n\",\n                    i, newresult->Get(repfsid.c_str()),\n                    newresult->Get(repurl.c_str()),\n                    newresult->Get(repfstpath.c_str()),\n                    (unsigned long long)proto_fmd.size(),\n                    (unsigned long long)(stat_size),\n                    cx.c_str(), disk_cx.c_str(), err_label.c_str());\n          }\n        } else {\n          // For RAIN layouts we only check for block-checksum errors\n          if (proto_fmd.blockcxerror()) {\n            err_label = \"BLOCK_XS\";\n            set_errors.insert(err_label);\n          }\n\n          if (!silent_cmd) {\n            fprintf(stdout, \"nrep=\\\"%02d\\\" fsid=\\\"%s\\\" host=\\\"%s\\\" fstpath=\\\"%s\\\" \"\n                    \"size=\\\"%llu\\\" statsize=\\\"%llu\\\" error_label=\\\"%s\\\"\\n\",\n                    i, newresult->Get(repfsid.c_str()),\n                    newresult->Get(repurl.c_str()),\n                    newresult->Get(repfstpath.c_str()),\n                    (unsigned long long)proto_fmd.size(),\n                    (unsigned long long)(stat_size), err_label.c_str());\n          }\n        }\n\n        ++nrep_online;\n      }\n    }\n\n    int nrep = 0;\n    int stripes = 0;\n\n    if (newresult->Get(\"mgm.stripes\")) {\n      stripes = atoi(newresult->Get(\"mgm.stripes\"));\n    }\n\n    if (newresult->Get(\"mgm.nrep\")) {\n      nrep = atoi(newresult->Get(\"mgm.nrep\"));\n    }\n\n    if (nrep != stripes) {\n      if (set_errors.find(\"NOFMD\") == set_errors.end()) {\n        err_label = \"NUM_REPLICAS\";\n        set_errors.insert(err_label);\n      }\n    }\n\n    if (set_errors.size()) {\n      if ((option.find(\"%output\")) != STR_NPOS) {\n        fprintf(stdout, \"INCONSISTENCY %s path=%-32s fxid=%s size=%llu \"\n                \"stripes=%d nrep=%d nrepstored=%d nreponline=%d \"\n                \"checksumtype=%s checksum=%s\\n\", set_errors.begin()->c_str(),\n                path.c_str(), newresult->Get(\"mgm.fid0\"),\n                (unsigned long long) mgm_size, stripes, nrep, i, nrep_online,\n                checksumtype.c_str(), newresult->Get(\"mgm.checksum\"));\n      }\n\n      if (((option.find(\"%size\") != STR_NPOS) &&\n           ((set_errors.find(\"SIZE\") != set_errors.end() ||\n             set_errors.find(\"FSTSIZE\") != set_errors.end()))) ||\n          ((option.find(\"%checksum\") != STR_NPOS) &&\n           ((set_errors.find(\"CHECKSUM\") != set_errors.end()) ||\n            (set_errors.find(\"BLOCK_XS\") != set_errors.end()))) ||\n          ((option.find(\"%diskchecksum\") != STR_NPOS) &&\n           (set_errors.find(\"DISK_CHECKSUM\") != set_errors.end())) ||\n          ((option.find(\"%nrep\") != STR_NPOS) &&\n           ((set_errors.find(\"NOFMD\") != set_errors.end()) ||\n            (set_errors.find(\"NUM_REPLICAS\") != set_errors.end())))) {\n        global_retc = EFAULT;\n      }\n    }\n\n    return (0);\n  }\n\n  if (option.length()) {\n    in += \"&mgm.file.option=\";\n    in += option;\n  }\n\n  global_retc = output_result(client_command(in));\n  return (0);\ncom_file_usage:\n  fprintf(stdout,\n          \"Usage: file adjustreplica|check|convert|copy|drop|info|layout|move|purge|rename|replicate|verify|version ...\\n\");\n  fprintf(stdout,\n          \"'[eos] file ..' provides the file management interface of EOS.\\n\");\n  fprintf(stdout, \"Options:\\n\");\n  fprintf(stdout,\n          \"file adjustreplica [--nodrop] <path>|fid:<fid-dec>|fxid:<fid-hex> [space [subgroup]] [--exclude-fs <fsid>] :\\n\");\n  fprintf(stdout,\n          \"                                                  tries to bring a files with replica layouts to the nominal replica level [ need to be root ]\\n\");\n  fprintf(stdout,\n          \"       --exclude-fs <fsid>                                            :  exclude the given filesystem from being used for the replica adjustment\\n\");\n  fprintf(stdout,\n          \"file check [<path>|fid:<fid-dec>|fxid:<fid-hex>] [%%size%%checksum%%nrep%%diskchecksum%%force%%output%%silent] :\\n\");\n  fprintf(stdout,\n          \"                                                  retrieves stat information from the physical replicas and verifies the correctness\\n\");\n  fprintf(stdout,\n          \"       - %%size                                                       :  return EFAULT if mismatch between the size meta data information\\n\");\n  fprintf(stdout,\n          \"       - %%checksum                                                   :  return EFAULT if mismatch between the checksum meta data information\\n\");\n  fprintf(stdout,\n          \"       - %%nrep                                                       :  return EFAULT if mismatch between the layout number of replicas and the existing replicas\\n\");\n  fprintf(stdout,\n          \"       - %%diskchecksum                                               :  return EFAULT if mismatch between the disk checksum on the FST and the reference checksum\\n\");\n  fprintf(stdout,\n          \"       - %%silent                                                     :  suppresses all information for each replica to be printed\\n\");\n  fprintf(stdout,\n          \"       - %%force                                                      :  forces to get the MD even if the node is down\\n\");\n  fprintf(stdout,\n          \"       - %%output                                                     :  prints lines with inconsistency information\\n\");\n  fprintf(stdout,\n          \"file convert [--sync|--rewrite] [<path>|fid:<fid-dec>|fxid:<fid-hex>] [<layout>:<stripes> | <layout-id> | <sys.attribute.name>] [target-space] [placement-policy] [checksum]:\\n\");\n  fprintf(stdout,\n          \"                                                                         convert the layout of a file\\n\");\n  fprintf(stdout,\n          \"        <layout>:<stripes>   : specify the target layout and number of stripes\\n\");\n  fprintf(stdout,\n          \"        <layout-id>          : specify the hexadecimal layout id \\n\");\n  fprintf(stdout,\n          \"        <conversion-name>    : specify the name of the attribute sys.conversion.<name> in the parent directory of <path> defining the target layout\\n\");\n  fprintf(stdout,\n          \"        <target-space>       : optional name of the target space or group e.g. default or default.3\\n\");\n  fprintf(stdout,\n          \"        <placement-policy>   : optional placement policy valid values are 'scattered','hybrid:<some_geotag>' and 'gathered:<some_geotag>'\\n\");\n  fprintf(stdout,\n          \"        <checksum>           : optional target checksum name. E.g.: md5, adler, etc.\\n\");\n  fprintf(stdout,\n          \"        --sync               : run conversion in synchronous mode (by default conversions are asynchronous) - not supported yet\\n\");\n  fprintf(stdout,\n          \"        --rewrite            : run conversion rewriting the file as is creating new copies and dropping old\\n\");\n  fprintf(stdout,\n          \"file copy [-f] [-s] [-c] <src> <dst>                                   :  synchronous third party copy from <src> to <dst>\\n\");\n  fprintf(stdout,\n          \"         <src>                                                         :  source can be a file or a directory (<path>|fid:<fid-dec>|fxid:<fid-hex>) \\n\");\n  fprintf(stdout,\n          \"         <dst>                                                         :  destination can be a file (if source is a file) or a directory\\n\");\n  fprintf(stdout,\n          \"         -f                                                            :  force overwrite\\n\");\n  fprintf(stdout,\n          \"         -s                                                            :  don't print output\\n\");\n  fprintf(stdout,\n          \"         -c                                                            :  clone the file (keep ctime, mtime)\\n\");\n  fprintf(stdout,\n          \"file drop [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid> [-f] :\\n\");\n  fprintf(stdout,\n          \"                                                  drop the file <path> from <fsid> - force removes replica without trigger/wait for deletion (used to retire a filesystem) \\n\");\n  fprintf(stdout, \"file info [<path>|fid:<fid-dec>|fxid:<fid-hex>] :\\n\");\n  fprintf(stdout,\n          \"                                                  convenience function aliasing to 'fileinfo' command\\n\");\n  fprintf(stdout,\n          \"file layout <path>|fid:<fid-dec>|fxid:<fid-hex>  -stripes <n> :\\n\");\n  fprintf(stdout,\n          \"                                                  change the number of stripes of a file with replica layout to <n>\\n\");\n  fprintf(stdout,\n          \"file layout <path>|fid:<fid-dec>|fxid:<fid-hex>  -checksum <checksum-type> :\\n\");\n  fprintf(stdout,\n          \"                                                  change the checksum-type of a file to <checksum-type>\\n\");\n  fprintf(stdout,\n          \"file layout <path>|fid:<fid-dec>|fxid:<fid-hex>  -type <hex-layout-type> :\\n\");\n  fprintf(stdout,\n          \"                                                  change the layout-type of a file to <hex-layout-type> (as shown by file info)\\n\");\n  fprintf(stdout,\n          \"file move [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid1> <fsid2> :\\n\");\n  fprintf(stdout,\n          \"                                                  move the file <path> from  <fsid1> to <fsid2>\\n\");\n  fprintf(stdout, \"file purge <path> [purge-version] :\\n\");\n  fprintf(stdout,\n          \"                                                  keep maximum <purge-version> versions of a file. If not specified apply the attribute definition from sys.versioning.\\n\");\n  fprintf(stdout, \"file rename [<path>|fid:<fid-dec>|fxid:<fid-hex>] <new> :\\n\");\n  fprintf(stdout,\n          \"                                                  rename from <old> to <new> name (works for files and directories!).\\n\");\n  fprintf(stdout, \"file rename_with_symlink <source_file> <destination_dir> :\\n\");\n  fprintf(stdout,\n          \"     rename/move source file to destination directory by doing\\n\"\n          \"     two operations in an atomic step:\\n\"\n          \"     - move file to destination directory\\n\"\n          \"     - create symlink in the source directory to the new location\\n\");\n  fprintf(stdout,\n          \"file replicate [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid1> <fsid2> :\\n\");\n  fprintf(stdout,\n          \"                                                  replicate file <path> part on <fsid1> to <fsid2>\\n\");\n  fprintf(stdout,\n          \"file symlink [-f] <name> <link-name> :\\n\");\n  fprintf(stdout,\n          \"                                                  create a symlink with <name> pointing to <link-name>\\n\");\n  fprintf(stdout,\n          \"         -f                                                            :  force overwrite\\n\");\n  fprintf(stdout, \"file tag <path>|fid:<fid-dec>|fxid:<fid-hex> +|-|~<fsid> :\\n\");\n  fprintf(stdout,\n          \"                                                  add/remove/unlink a filesystem location to/from a file in the location index - attention this does not move any data!\\n\");\n  fprintf(stdout,\n          \"                                                  unlink keeps the location in the list of deleted files e.g. the location gets a deletion request\\n\");\n  fprintf(stdout,\n          \"file touch [-a] [-n] [-0] <path>|fid:<fid-dec>|fxid:<fid-hex> [linkpath|size [hexchecksum]] :\\n\");\n  fprintf(stdout,\n          \"                                                  create/touch a 0-size/0-replica file if <path> does not exist or update modification time of an existing file to the present time\\n\");\n  fprintf(stdout,\n          \"                                          - by default it uses placement logic - use [-n] to disable placement\\n\");\n  fprintf(stdout,\n          \"                                          - use 'file touch -0 myfile' to truncate a file\\n\");\n  fprintf(stdout,\n          \"                                          - use 'file touch -a myfile /external/path' if you want to adopt (absorb) a file which is provied by the hardlink argument - this means that the file disappears from the given hardlink path and is taken under control of an EOS FST\\n\");\n  fprintf(stdout,\n          \"                                          - provide the optional size argument to preset the size\\n\");\n  fprintf(stdout,\n          \"                                          - provide the optional linkpath argument to hard- or softlink the touched file to a shared filesystem\\n\");\n  fprintf(stdout,\n          \"                                          - provide the optional checksum information for a new touched file\\n\");\n  fprintf(stdout,\n          \"file touch -l <path>|fid:<fid-dec>|fxid:<fid-hex> [<lifetime> [<audience>=user|app]] :\\n\");\n  fprintf(stdout,\n          \"                                          - touch a file and create an extended attribute lock with <lifetime> (default 24h)\\n\");\n  fprintf(stdout,\n          \"                                          - with <audience> one can relax the lock owner requirements to be either same user or same app - default is both have to match\\n\");\n  fprintf(stdout,\n          \"                                          - if the lock is already held by another caller EBUSY is returned\\n\");\n  fprintf(stdout,\n          \"                                          - if a lock is already held by the caller a second call will extend the liftime as provided\\n\");\n  fprintf(stdout,\n          \"                                          - use in combination with 'eos -a application' to tag a client with a given application for the lock\\n\");\n  fprintf(stdout,\n          \"file touch -u <path|fid:<fid-dec>|fxid:<fid-hex> :\\n\");\n  fprintf(stdout,\n          \"                                          - remove an extended attribute lock\\n\");\n  fprintf(stdout,\n          \"                                          - if no lock was not present no error is returned - only an message\\n\");\n  fprintf(stdout,\n          \"                                          - if the lock is held by someone else EBUSY is returned\\n\");\n  fprintf(stdout,\n          \"                                          - use in combination with 'eos -a application' to tag a client with a given application for the lock\\n\");\n  fprintf(stdout,\n          \"file verify <path>|fid:<fid-dec>|fxid:<fid-hex> [<fsid>] [-checksum] [-commitchecksum] [-commitsize] [-rate <rate>] : \\n\");\n  fprintf(stdout,\n          \"                                                  verify a file against the disk images\\n\");\n  fprintf(stdout,\n          \"file verify <path|fid:<fid-dec>|fxid:<fid-hex> -resync : \\n\");\n  fprintf(stdout,\n          \"                                                  ask all locations to resync their file md records\\n\");\n  fprintf(stdout,\n          \"       <fsid>          : verifies only the replica on <fsid>\\n\");\n  fprintf(stdout,\n          \"       -checksum       : trigger the checksum calculation during the verification process\\n\");\n  fprintf(stdout,\n          \"       -commitchecksum : commit the computed checksum to the MGM\\n\");\n  fprintf(stdout, \"       -commitsize     : commit the file size to the MGM\\n\");\n  fprintf(stdout,\n          \"       -rate <rate>    : restrict the verification speed to <rate> per node\\n\");\n  fprintf(stdout, \"file version <path> [purge-version] :\\n\");\n  fprintf(stdout,\n          \"                                                  create a new version of a file by cloning\\n\");\n  fprintf(stdout,\n          \"       <purge-version> : defines the max. number of versions to keep\\n\");\n  fprintf(stdout, \"file versions [grab-version] :\\n\");\n  fprintf(stdout,\n          \"                                                  list versions of a file\\n\");\n  fprintf(stdout,\n          \"                                                  grab a version [grab-version] of a file\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"                         if not specified it will add a new version without purging any previous version\\n\");\n  fprintf(stdout, \"file share <path> [lifetime] :\\n\");\n  fprintf(stdout, \"       <path>          : path to create a share link\\n\");\n  fprintf(stdout,\n          \"       <lifetime>      : validity time of the share link like 1, 1s, 1d, 1w, 1mo, 1y, ... default is 28d\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"file workflow <path>|fid:<fid-dec>|fxid:<fid-hex> <workflow> <event> :\\n\");\n  fprintf(stdout,\n          \"                                                  trigger workflow <workflow> with event <event> on <path>\\n\");\n  fprintf(stdout, \"\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_fuse.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_fuse.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright(C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n *(at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n/*----------------------------------------------------------------------------*/\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <string.h>\n\nextern XrdOucString serveruri;\n\n/* mount/umount via fuse */\nint\ncom_fuse(char* arg1)\n{\n  if (interactive) {\n    fprintf(stderr,\n            \"error: don't call <fuse> from an interactive shell - call via 'eos fuse ...'!\\n\");\n    global_retc = -1;\n    return 0;\n  }\n\n// split subcommands\n  XrdOucString mountpoint = \"\";\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString cmd = subtokenizer.GetToken();\n  XrdOucString option = \"\";\n  XrdOucString logfile = \"\";\n  XrdCl::URL url(serveruri.c_str());\n  XrdOucString params = \"fsname=\";\n\n  if (url.GetHostName() == \"localhost\") {\n    params += \"localhost.localdomain\";\n  } else {\n    params += url.GetHostName().c_str();\n  }\n\n  params += \":\";\n  params += url.GetPath().c_str();\n\n  if (wants_help(arg1)) {\n    goto com_fuse_usage;\n  }\n\n  if ((cmd != \"mount\") && (cmd != \"umount\")) {\n    goto com_fuse_usage;\n  }\n\n  do {\n    option = subtokenizer.GetToken();\n\n    if (!option.length()) {\n      break;\n    }\n\n    if (option.beginswith(\"-o\")) {\n      params = subtokenizer.GetToken();\n\n      if (!params.length()) {\n        goto com_fuse_usage;\n      }\n    } else {\n      break;\n    }\n  } while (1);\n\n  mountpoint = option;\n\n  if (!mountpoint.length()) {\n    goto com_fuse_usage;\n  }\n\n  if (mountpoint.beginswith(\"-\")) {\n    goto com_fuse_usage;\n  }\n\n  if (!mountpoint.beginswith(\"/\")) {\n    fprintf(stderr,\n            \"warning: assuming you gave a relative path with respect to current working directory => mountpoint=%s\\n\",\n            mountpoint.c_str());\n    XrdOucString pwd = getenv(\"PWD\");\n\n    if (!pwd.endswith(\"/\")) {\n      pwd += \"/\";\n    }\n\n    mountpoint.insert(pwd.c_str(), 0);\n  }\n\n  if (cmd == \"mount\") {\n    struct stat buf;\n    struct stat buf2;\n\n    if (stat(mountpoint.c_str(), &buf)) {\n      XrdOucString createdir = \"mkdir -p \";\n      createdir += mountpoint;\n      createdir += \" >& /dev/null\";\n      fprintf(stderr, \".... trying to create ... %s\\n\", mountpoint.c_str());\n      int rc = system(createdir.c_str());\n\n      if (WEXITSTATUS(rc)) {\n        fprintf(stderr, \"error: creation of mountpoint failed\");\n      }\n    }\n\n    if (stat(mountpoint.c_str(), &buf)) {\n      fprintf(stderr, \"error: cannot create mountpoint %s !\\n\", mountpoint.c_str());\n      exit(-1);\n    } else {\n      if (buf.st_dev == 19) {\n        fprintf(stderr, \"error: already/still mounted on %s !\\n\", mountpoint.c_str());\n        exit(EBUSY);\n      }\n    }\n\n#ifdef __APPLE__\n    params += \" -onoappledouble,allow_root,defer_permissions,volname=EOS,iosize=65536,fsname=eos@cern.ch\";\n#endif\n    fprintf(stderr, \"===> Mountpoint   : %s\\n\", mountpoint.c_str());\n    fprintf(stderr, \"===> Fuse-Options : %s\\n\", params.c_str());\n    XrdOucString mount;\n    mount = \"eosxd \";\n    mount += mountpoint.c_str();\n    mount += \" -o\";\n    mount += params;\n    fprintf(stderr, \"running %s\\n\", mount.c_str());\n#ifdef __APPLE__\n    mount += \" >& /dev/null\";\n#else\n    mount += \" >& /dev/null\";\n#endif\n    int rc = system(mount.c_str());\n\n    if (WEXITSTATUS(rc)) {\n      fprintf(stderr, \"error: failed mount, maybe still mounted? Check with \"\n              \"df and eventually 'killall eosd'\\n\");\n      exit(-1);\n    }\n\n#ifdef __APPLE__\n    int cnt = 5;\n\n    for (cnt = 5; cnt > 0; cnt--) {\n      fprintf(stderr, \"\\r[wait] %i seconds ...\", cnt);\n      fflush(stderr);\n      sleep(1);\n    }\n\n    fprintf(stderr, \"\\n\");\n#endif\n    bool mountok = false;\n\n    // Keep checking for 5 seconds\n    for (size_t i = 0; i < 50; i++) {\n      if (stat(mountpoint.c_str(), &buf2) || (buf2.st_ino == buf.st_ino)) {\n        usleep(100000);\n\n        if (i && (!(i % 10))) {\n          fprintf(stderr, \"[check] %zu. time for mount ...\\n\", i / 10);\n        }\n      } else {\n        mountok = true;\n        break;\n      }\n    }\n\n    if (!mountok) {\n      fprintf(stderr, \"error: failed mount, maybe still mounted? Check with \"\n              \"df and eventually 'killall eosd'\\n\");\n      exit(-1);\n    } else {\n      fprintf(stderr, \"info: successfully mounted EOS [%s] under %s\\n\",\n              serveruri.c_str(), mountpoint.c_str());\n    }\n  }\n\n  if (cmd == \"umount\") {\n    struct stat buf2;\n#ifndef __APPLE__\n    struct stat buf1;\n    XrdOucString pmount = mountpoint;\n\n    if (pmount.endswith(\"/\")) {\n      pmount.erase(pmount.length() - 1);\n    }\n\n    pmount.erase(pmount.rfind('/'));\n    int r1 = stat(mountpoint.c_str(), &buf1);\n    int r2 = stat(pmount.c_str(), &buf2);\n\n    if ((r1 || r2) || (buf1.st_dev == buf2.st_dev)) {\n      fprintf(stderr, \"error: there is no eos mount at %s\\n\", mountpoint.c_str());\n      exit(-1);\n    }\n\n#endif\n    XrdOucString umount;\n#ifdef __APPLE__\n    umount = \"umount -f \";\n    umount += mountpoint.c_str();\n    umount += \" >& /dev/null\";\n#else\n    umount = \"fusermount -z -u \";\n    umount += mountpoint.c_str();\n#endif\n    int rc = system(umount.c_str());\n\n    if (WEXITSTATUS(rc)) {\n      fprintf(stderr, \"error: umount failed - maybe wasn't mounted?\\n\");\n    }\n\n    if ((stat(mountpoint.c_str(), &buf2))) {\n      fprintf(stderr, \"error: mount directory disappeared from %s\\n\",\n              mountpoint.c_str());\n      exit(-1);\n    }\n\n#ifndef __APPLE__\n\n    if (buf1.st_ino == buf2.st_ino) {\n      fprintf(stderr, \"error: umount didn't work\\n\");\n      exit(-1);\n    }\n\n#endif\n  }\n\n  exit(0);\ncom_fuse_usage:\n  fprintf(stdout,\n          \"usage: fuse mount  <mount-point>                                         : mount connected eos instance on <mount-point>\\n\");\n  fprintf(stdout,\n          \"       fuse umount <mount-point>                                         : unmount eos pool from <mount-point>\\n\");\n  exit(-1);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_fusex.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_fusex.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/SymKeys.hh\"\n/*----------------------------------------------------------------------------*/\n\n/* fusex Clients -  Interface */\nint\ncom_fusex(char* arg1)\n{\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString option = \"\";\n  XrdOucString options = \"\";\n  XrdOucString in = \"\";\n  XrdOucString subcmd = subtokenizer.GetToken();\n  XrdOucString filter;\n\n  if (wants_help(arg1)) {\n    goto com_fusex_usage;\n  }\n\n  in = \"mgm.cmd=fusex\";\n\n  if (subcmd == \"ls\") {\n    in += \"&mgm.subcmd=ls\";\n  } else if (subcmd == \"evict\") {\n    XrdOucString uuid = subtokenizer.GetToken();\n    XrdOucString reason = subtokenizer.GetToken();\n\n    if (!uuid.length()) {\n      goto com_fusex_usage;\n    }\n\n    in += \"&mgm.subcmd=evict\";\n    in += \"&mgm.fusex.uuid=\";\n    in += uuid;\n\n    if (reason.length()) {\n      XrdOucString b64;\n      eos::common::SymKey::Base64(reason, b64);\n      in += \"&mgm.fusex.reason=\";\n      in += b64;\n    }\n  } else if (subcmd == \"caps\") {\n    option = subtokenizer.GetToken();\n    filter = subtokenizer.GetToken();\n\n    while (option.replace(\"-\", \"\")) {\n    };\n\n    in += \"&mgm.subcmd=caps\";\n\n    in += \"&mgm.option=\";\n\n    in += option;\n\n    while (auto val = subtokenizer.GetToken()) {\n      filter += \" \";\n      filter += val;\n    }\n\n    if (filter.length()) {\n      in += \"&mgm.filter=\";\n      in += eos::common::StringConversion::curl_escaped(filter.c_str()).c_str();\n    }\n  } else if (subcmd == \"dropcaps\") {\n    XrdOucString uuid = subtokenizer.GetToken();\n\n    if (!uuid.length()) {\n      goto com_fusex_usage;\n    }\n\n    in += \"&mgm.subcmd=dropcaps\";\n    in += \"&mgm.fusex.uuid=\";\n    in += uuid;\n  } else if (subcmd == \"droplocks\") {\n    XrdOucString inode = subtokenizer.GetToken();\n    XrdOucString pid = subtokenizer.GetToken();\n\n    if (!inode.length() || !pid.length()) {\n      goto com_fusex_usage;\n    }\n\n    in += \"&mgm.subcmd=droplocks\";\n    in += \"&mgm.inode=\";\n    in += inode;\n    in += \"&mgm.fusex.pid=\";\n    in += pid;\n  } else if (subcmd == \"conf\") {\n    XrdOucString interval = subtokenizer.GetToken();\n    XrdOucString quota_interval = subtokenizer.GetToken();\n    XrdOucString bc_audience = subtokenizer.GetToken();\n    XrdOucString bc_audience_match = subtokenizer.GetToken();\n\n    int i_interval = interval.length() ? atoi(interval.c_str()) : 0;\n    int q_interval = quota_interval.length() ? atoi(quota_interval.c_str()) : 0;\n\n    if ((i_interval < 0) ||\n        (i_interval > 60)) {\n      goto com_fusex_usage;\n    }\n\n    if ((q_interval < 0) ||\n        (q_interval > 120)) {\n      goto com_fusex_usage;\n    }\n\n    in += \"&mgm.subcmd=conf\";\n    in += \"&mgm.fusex.hb=\";\n    in += interval;\n\n    if (quota_interval.length()) {\n      in += \"&mgm.fusex.qc=\";\n      in += quota_interval;\n    }\n\n    if (bc_audience.length()) {\n      in += \"&mgm.fusex.bc.max=\";\n      in += bc_audience;\n    }\n    if (bc_audience_match.length()) {\n      in += \"&mgm.fusex.bc.match=\";\n      in += bc_audience_match;\n    }\n  } else {\n    goto com_fusex_usage;\n  }\n\n  do {\n    option = subtokenizer.GetToken();\n\n    if (!option.length()) {\n      break;\n    }\n\n    if (option == \"-a\") {\n      options += \"a\";\n    } else {\n      if (option == \"-m\") {\n        options += \"m\";\n      } else {\n        if (option == \"-s\") {\n          options += \"s\";\n        } else {\n          if (option == \"-f\") {\n            options += \"f\";\n          } else {\n            if (option == \"-l\") {\n              options += \"l\";\n            } else {\n\t      if (option == \"-k\") {\n\t\toptions += \"k\";\n\t      } else {\n\t\tgoto com_fusex_usage;\n\t      }\n\t    }\n          }\n        }\n      }\n    }\n  } while (true);\n\n  if (options.length()) {\n    in += \"&mgm.option=\";\n    in += options;\n  }\n\n  global_retc = output_result(client_command(in, true));\n  return (0);\ncom_fusex_usage:\n  fprintf(stdout,\n          \"usage: fusex ls [-l] [-f] [-m]                     :  print statistics about eosxd fuse clients\\n\");\n  fprintf(stdout,\n          \"                [no option]                                          -  break down by client host [default]\\n\");\n  fprintf(stdout,\n          \"                -l                                                   -  break down by client host and show statistics \\n\");\n  fprintf(stdout,\n          \"                -f                                                   -  show ongoing flush locks\\n\");\n  fprintf(stdout,\n          \"                -k                                                   -  show R/W locks\\n\");\n\n  fprintf(stdout,\n          \"                -m                                                   -  show monitoring output format\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"       fuxex evict <uuid> [<reason>]                                 :  evict a fuse client\\n\");\n  fprintf(stdout,\n          \"                                                              <uuid> -  uuid of the client to evict\\n\");\n  fprintf(stdout,\n          \"                                                            <reason> -  optional text shown to the client why he has been evicted or an instruction for an action to the client\\n\");\n  fprintf(stdout,\n          \"                                                                     - if the reason contains the keywoard 'abort' the abort handler will be called on client side (might create a stack trace/core)\\n\");\n  fprintf(stdout,\n          \"                                                                     - if reason contains the keyword 'log2big' the client will effectily not be evicted, but will truncate his logfile to 0\\n\");\n\n  fprintf(stdout,\n\t  \"                                                                     - if reason contains the keyword 'setlog' and 'debug','notice', 'error', 'crit', 'info', 'warning' the log level of the targeted mount is changed accordingly .e.g evict <uuid> \\\"setlog error\\\"\\n\");\n\n  fprintf(stdout,\n\t  \"                                                                     - if reason contains the keyword 'stacktrace' the client will send a self-stacktrace with the next heartbeat message and it will be stored in /var/log/eos/mgm/eosxd-stacktraces.log e.g. evict <uuid> stacktrace\\n\");\n  fprintf(stdout,\n\t  \"                                                                     - if reason contains the keyword 'sendlog' the client will send max. the last 512 lines of each log level and the log will be stored in /var/log/eos/mgm/eosxd-logtraces.log e.g. evict <uuid> sendlog\\n\");\n  fprintf(stdout,\n\t  \"                                                                     - if reason contains the keyword 'resetbuffer' the client will reset the read-ahead and write-buffers in flight and possibly unlock a locked mount point\");\n\n  fprintf(stdout, \"\\n\");\n\n  fprintf(stdout,\n          \"       fusex evict static|autofs mem:<size-in-mb>|idle:<seconds>     :  evict all autofs or static mounts which have a resident memory footprint larger than <size-in-mb> or are idle longer than <seconds>\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"       fusex dropcaps <uuid>                                         :  advice a client to drop all caps\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"       fusex droplocks <inode> <pid>                                 :  advice a client to drop for a given (hexadecimal) inode and process id\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"       fusex caps [-t | -i | -p [<regexp>] ]                         :  print caps\\n\");\n  fprintf(stdout,\n          \"                -t                                                   -  sort by expiration time\\n\");\n  fprintf(stdout,\n          \"                -i                                                   -  sort by inode\\n\");\n  fprintf(stdout,\n          \"                -p                                                   -  display by path\\n\");\n  fprintf(stdout,\n          \"                -t|i|p <regexp>>                                     -  display entries matching <regexp> for the used filter type\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout, \"examples:\\n\");\n  fprintf(stdout,\n          \"           fusex caps -i ^0000abcd$                                  :  show caps for inode 0000abcd\\n\");\n  fprintf(stdout,\n          \"           fusex caps -p ^/eos/$                                     :  show caps for path /eos\\n\");\n  fprintf(stdout,\n          \"           fusex caps -p ^/eos/caps/                                 :  show all caps in subtree /eos/caps\\n\");\n  fprintf(stdout,\n          \"       fusex conf [<heartbeat-in-seconds>] [quota-check-in-seconds] [max broadcast audience] [broadcast audience match]\\n\");\n  fprintf(stdout, \"                                                             :  show heartbeat and quota interval\\n\");\n\n\n  fprintf(stdout,\n          \"                                                                     :  [ optional change heartbeat interval from [1-15] seconds ]\\n\");\n  fprintf(stdout,\n          \"                                                                     :  [ optional set quota check interval from [1-16] seconds ]\\n\");\n  fprintf(stdout, \"examples:\\n\");\n  fprintf(stdout,\n          \"   fusex conf                                                :  show heartbeat and quota interval\\n\");\n  fprintf(stdout,\n          \"   fusex conf 10                                             :  define heartbeat interval as 10 seconds\\n\");\n  fprintf(stdout,\n          \"   fusex conf 10 30                                          :  define heartbeat as 10 seconds and quota interval as 30 seconds\\n\");\n  fprintf(stdout,\n\t  \"   fusex conf 0 0 256 @b[67]                                :  suppress broadcasts when more than 256 clients are conected and the target matches @b[67]\\n\");\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_geosched.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_geosched.cc\n// Author: Geoffray Adde - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include \"common/Utils.hh\"\n#include \"common/RWMutex.hh\"\n#include \"common/StringTokenizer.hh\"\n#include<set>\n#include<string>\n/*----------------------------------------------------------------------------*/\n\n/* Namespace Interface */\nint\ncom_geosched(char* arg1)\n{\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString cmd = subtokenizer.GetToken();\n  std::set<std::string> supportedParam = {\"skipSaturatedAccess\",\n                                          \"skipSaturatedDrnAccess\", \"skipSaturatedBlcAccess\",\n                                          \"plctDlScorePenalty\", \"plctUlScorePenalty\",\n                                          \"accessDlScorePenalty\", \"accessUlScorePenalty\",\n                                          \"fillRatioLimit\", \"fillRatioCompTol\", \"saturationThres\",\n                                          \"timeFrameDurationMs\", \"penaltyUpdateRate\", \"proxyCloseToFs\"\n                                         };\n  XrdOucString in = \"\";\n\n  if (wants_help(arg1)) {\n    goto com_geosched_usage;\n  }\n\n  if ((cmd != \"show\") && (cmd != \"set\") && (cmd != \"updater\") &&\n      (cmd != \"forcerefresh\") && (cmd != \"disabled\") && (cmd != \"access\")) {\n    goto com_geosched_usage;\n  }\n\n  in = \"mgm.cmd=geosched\";\n\n  if (cmd == \"show\") {\n    XrdOucString subcmd = subtokenizer.GetToken();\n\n    if (subcmd == \"-c\") {\n      in += \"&mgm.usecolors=1\";\n      subcmd = subtokenizer.GetToken();\n    } else if (subcmd == \"-m\") {\n      in += \"&mgm.monitoring=1\";\n      subcmd = subtokenizer.GetToken();\n    }\n\n    if ((subcmd != \"tree\") && (subcmd != \"snapshot\") && (subcmd != \"state\") &&\n        (subcmd != \"param\")) {\n      goto com_geosched_usage;\n    }\n\n    if (subcmd == \"state\") {\n      in += \"&mgm.subcmd=showstate\";\n      subcmd = subtokenizer.GetToken();\n\n      if (subcmd == \"-m\") {\n        in += \"&mgm.monitoring=1\";\n      }\n    }\n\n    if (subcmd == \"param\") {\n      in += \"&mgm.subcmd=showparam\";\n    }\n\n    if (subcmd == \"tree\") {\n      in += \"&mgm.subcmd=showtree\";\n      in += \"&mgm.schedgroup=\";\n      XrdOucString group = subtokenizer.GetToken();\n\n      if (group.length()) {\n        in += group;\n      }\n    }\n\n    if (subcmd == \"snapshot\") {\n      in += \"&mgm.subcmd=showsnapshot\";\n      in += \"&mgm.schedgroup=\";\n      XrdOucString group = subtokenizer.GetToken();\n\n      if (group.length()) {\n        in += group;\n      }\n\n      in += \"&mgm.optype=\";\n      XrdOucString optype = subtokenizer.GetToken();\n\n      if (optype.length()) {\n        in += optype;\n      }\n    }\n  }\n\n  if (cmd == \"set\") {\n    XrdOucString parameter = subtokenizer.GetToken();\n\n    if (!parameter.length()) {\n      fprintf(stderr, \"Error: parameter name is not provided\\n\");\n      goto com_geosched_usage;\n    }\n\n    if (supportedParam.find(parameter.c_str()) == supportedParam.end()) {\n      fprintf(stderr, \"Error: parameter %s not supported\\n\", parameter.c_str());\n      return 0;\n    }\n\n    XrdOucString index = subtokenizer.GetToken();\n    XrdOucString value = subtokenizer.GetToken();\n\n    if (!index.length()) {\n      fprintf(stderr, \"Error: value is not provided\\n\");\n      goto com_geosched_usage;\n    }\n\n    if (!value.length()) {\n      value = index;\n      index = \"-1\";\n    }\n\n    double didx = 0.0;\n\n    if (!sscanf(value.c_str(), \"%lf\", &didx)) {\n      fprintf(stderr,\n              \"Error: parameter %s should have a numeric value, %s was provided\\n\",\n              parameter.c_str(), value.c_str());\n      return 0;\n    }\n\n    if (!XrdOucString(index.c_str()).isdigit()) {\n      fprintf(stderr,\n              \"Error: index for parameter %s should have a numeric value, %s was provided\\n\",\n              parameter.c_str(), index.c_str());\n      return 0;\n    }\n\n    in += \"&mgm.subcmd=set\";\n    in += \"&mgm.param=\";\n    in += parameter.c_str();\n    in += \"&mgm.paramidx=\";\n    in += index.c_str();\n    in += \"&mgm.value=\";\n    in += value.c_str();\n  }\n\n  if (cmd == \"updater\") {\n    XrdOucString subcmd = subtokenizer.GetToken();\n\n    if (subcmd == \"pause\") {\n      in += \"&mgm.subcmd=updtpause\";\n    }\n\n    if (subcmd == \"resume\") {\n      in += \"&mgm.subcmd=updtresume\";\n    }\n  }\n\n  if (cmd == \"forcerefresh\") {\n    in += \"&mgm.subcmd=forcerefresh\";\n  }\n\n  if (cmd == \"disabled\") {\n    XrdOucString subcmd = subtokenizer.GetToken();\n    XrdOucString geotag, group, optype;\n\n    if ((subcmd != \"add\") && (subcmd != \"rm\") && (subcmd != \"show\")) {\n      goto com_geosched_usage;\n    }\n\n    geotag = subtokenizer.GetToken();\n    optype = subtokenizer.GetToken();\n    group = subtokenizer.GetToken();\n\n    if (!group.length() || !optype.length() || !geotag.length()) {\n      goto com_geosched_usage;\n    }\n\n    std::string sgroup(group.c_str()), soptype(optype.c_str()),\n        sgeotag(geotag.c_str());\n    const char fbdChars[] = \"&/,;%$#@!*\";\n    auto fbdMatch =  sgroup.find_first_of(fbdChars);\n\n    if (fbdMatch != std::string::npos && !(sgroup == \"*\")) {\n      fprintf(stdout, \"illegal character %c detected in group name %s\\n\",\n              sgroup[fbdMatch], sgroup.c_str());\n      return 0;\n    }\n\n    fbdMatch =  soptype.find_first_of(fbdChars);\n\n    if (fbdMatch != std::string::npos && !(soptype == \"*\")) {\n      fprintf(stdout, \"illegal character %c detected in optype %s\\n\",\n              soptype[fbdMatch], soptype.c_str());\n      return 0;\n    }\n\n    if (!(sgeotag == \"*\" && subcmd != \"add\")) {\n      std::string tmp_geotag = eos::common::SanitizeGeoTag(sgeotag);\n\n      if (tmp_geotag != sgeotag) {\n        fprintf(stderr, \"%s\\n\", tmp_geotag.c_str());\n        return 0;\n      }\n    }\n\n    in += (\"&mgm.subcmd=disabled\" +\n           subcmd); // mgm.subcmd is  disabledadd or disabledrm or disabledshow\n\n    if (geotag.length()) {\n      in += (\"&mgm.geotag=\" + geotag);\n    }\n\n    in += (\"&mgm.schedgroup=\" + group);\n    in += (\"&mgm.optype=\" + optype);\n  }\n\n  if (cmd == \"access\") {\n    XrdOucString subcmd = subtokenizer.GetToken();\n    XrdOucString geotag, geotag_list, optype;\n\n    if ((subcmd != \"setdirect\") && (subcmd != \"showdirect\") &&\n        (subcmd != \"cleardirect\") &&\n        (subcmd != \"setproxygroup\") && (subcmd != \"showproxygroup\") &&\n        (subcmd != \"clearproxygroup\")) {\n      goto com_geosched_usage;\n    }\n\n    const char* token = 0;\n\n    if ((token = subtokenizer.GetToken())) {\n      geotag = token;\n    }\n\n    if ((token = subtokenizer.GetToken())) {\n      geotag_list = token;\n    }\n\n    // mgm.subcmd is accesssetdirect or accesssetproxygroup or\n    // accessshowdirect or accessshowproxygroup or\n    // accesscleardirect or accessclearproxygroup\n    in += (\"&mgm.subcmd=access\" + subcmd);\n\n    if (subcmd == \"showdirect\" || subcmd == \"showproxygroup\") {\n      if (geotag.length()) {\n        if (geotag != \"-m\" || geotag_list.length()) {\n          goto com_geosched_usage;\n        } else {\n          in += \"&mgm.monitoring=1\";\n        }\n      }\n    } else {\n      if (subcmd == \"setdirect\" || subcmd == \"setproxygroup\") {\n        if (!geotag.length() || !geotag_list.length()) {\n          goto com_geosched_usage;\n        }\n\n        // Check each geotag from the list\n        if (subcmd == \"setdirect\") {\n          std::string tmp_list(geotag_list.c_str());\n          auto geotags = eos::common::StringTokenizer::split<std::vector\n                         <std::string>>(tmp_list, ',');\n          tmp_list.clear();\n\n          for (const auto& tag : geotags) {\n            std::string tmp_tag = eos::common::SanitizeGeoTag(tag);\n\n            if (tmp_tag != tag) {\n              fprintf(stderr, \"%s\\n\", tmp_tag.c_str());\n              return 0;\n            }\n          }\n        }\n\n        in += (\"&mgm.geotaglist=\" + geotag_list);\n      } else { // cleardirect or clearproxygroup\n        if (!geotag.length() || geotag_list.length()) {\n          goto com_geosched_usage;\n        }\n      }\n\n      std::string tmp_geotag = eos::common::SanitizeGeoTag(geotag.c_str());\n\n      if (tmp_geotag != geotag.c_str()) {\n        fprintf(stderr, \"%s\\n\", tmp_geotag.c_str());\n        return 0;\n      }\n\n      in += (\"&mgm.geotag=\" + geotag);\n    }\n  }\n\n  if (subtokenizer.GetToken()) {\n    goto com_geosched_usage;\n  }\n\n  global_retc = output_result(client_command(in, true));\n  return (0);\ncom_geosched_usage:\n  fprintf(stdout,\n          \"'[eos] geosched ..' Interact with the file geoscheduling engine in EOS.\\n\");\n  fprintf(stdout,\n          \"Usage: geosched show|set|updater|forcerefresh|disabled|access ...\\n\");\n  fprintf(stdout, \"Options:\\n\");\n  fprintf(stdout,\n          \"       geosched show [-c|-m] tree [<scheduling group>]                    :  show scheduling trees\\n\");\n  fprintf(stdout,\n          \"                                                                          :  if <scheduling group> is specified only the tree for this group is shown. If it's not all, the trees are shown.\\n\");\n  fprintf(stdout,\n          \"                                                                          :  '-c' enables color display\\n\");\n  fprintf(stdout,\n          \"                                                                          :  '-m' list in monitoring format\\n\");\n  fprintf(stdout,\n          \"       geosched show [-c|-m] snapshot [{<scheduling group>,*} [<optype>]] :  show snapshots of scheduling trees\\n\");\n  fprintf(stdout,\n          \"                                                                          :  if <scheduling group> is specified only the snapshot(s) for this group is/are shown. If it's not all, the snapshots for all the groups are shown.\\n\");\n  fprintf(stdout,\n          \"                                                                          :  if <optype> is specified only the snapshot for this operation is shown. If it's not, the snapshots for all the optypes are shown.\\n\");\n  fprintf(stdout,\n          \"                                                                          :  <optype> can be one of the folowing plct,accsro,accsrw,accsdrain,plctdrain\\n\");\n  fprintf(stdout,\n          \"                                                                          :  '-c' enables color display\\n\");\n  fprintf(stdout,\n          \"                                                                          :  '-m' list in monitoring format\\n\");\n  fprintf(stdout,\n          \"       geosched show param                                                :  show internal parameters\\n\");\n  fprintf(stdout,\n          \"       geosched show state [-m]                                           :  show internal state\\n\");\n  fprintf(stdout,\n          \"                                                                          :  '-m' list in monitoring format\\n\");\n  fprintf(stdout,\n          \"       geosched set <param name> [param index] <param value>              :  set the value of an internal state parameter (all names can be listed with geosched show param) \\n\");\n  fprintf(stdout,\n          \"       geosched updater {pause|resume}                                    :  pause / resume the tree updater\\n\");\n  fprintf(stdout,\n          \"       geosched forcerefresh                                              :  force a refresh of the trees/snapshots\\n\");\n  fprintf(stdout,\n          \"       geosched disabled add <geotag> {<optype>,*} {<scheduling subgroup>,*}      :  disable a branch of a subtree for the specified group and operation\\n\");\n  fprintf(stdout,\n          \"                                                                                  :  multiple branches can be disabled (by successive calls) as long as they have no intersection\\n\");\n  fprintf(stdout,\n          \"       geosched disabled rm {<geotag>,*} {<optype>,*} {<scheduling subgroup>,*}   :  re-enable a disabled branch for the specified group and operation\\n\");\n  fprintf(stdout,\n          \"                                                                                  :  when called with <geotag> *, the whole tree(s) are re-enabled, canceling all previous disabling\\n\");\n  fprintf(stdout,\n          \"       geosched disabled show {<geotag>,*} {<optype>,*} {<scheduling subgroup>,*} :  show list of disabled branches for for the specified groups and operation\\n\");\n  fprintf(stdout,\n          \"       geosched access setdirect <geotag> <geotag_list>                   :  set a mapping between an accesser geotag and a set of target geotags \\n\");\n  fprintf(stdout,\n          \"                                                                          :  these mappings specify which geotag can be accessed from which geotag without going through a firewall entrypoint\\n\");\n  fprintf(stdout,\n          \"                                                                          :  geotag_list is of the form token1::token2,token3::token4::token5,... \\n\");\n  fprintf(stdout,\n          \"       geosched access showdirect [-m]                                    :  show mappings between accesser geotags and target geotags\\n\");\n  fprintf(stdout,\n          \"                                                                          :  '-m' list in monitoring format\\n\");\n  fprintf(stdout,\n          \"       geosched access cleardirect {<geotag>|all}                         :  clear a mapping between an accesser geotag and a set of target geotags\\n\");\n  fprintf(stdout,\n          \"       geosched access setproxygroup <geotag> <proxygroup>                :  set the proxygroup acting as a firewall entrypoint for the given subtree \\n\");\n  fprintf(stdout,\n          \"                                                                          :  if a client accesses a file from a geotag which does not have direct access to the subtree the replica is,\\n\");\n  fprintf(stdout,\n          \"                                                                          :  it will be scheduled to access through a node from the given proxygroup \\n\");\n  fprintf(stdout,\n          \"       geosched access showproxygroup [-m]                                :  show mappings between accesser geotags and target geotags\\n\");\n  fprintf(stdout,\n          \"                                                                          :  '-m' list in monitoring format\\n\");\n  fprintf(stdout,\n          \"       geosched access clearproxygroup {<geotag>|all}                     :  clear a mapping between an accesser geotag and a set of target geotags\\n\");\n  fprintf(stdout, \"\\nNote:\\n\");\n  fprintf(stdout,\n          \"       Make sure that geotags contain only alphanumeric segments which are no longer than 8 characters, in <tag1>::<tag2>::...::<tagN> format.\\n\");\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_health.cc",
    "content": "// ----------------------------------------------------------------------\n// File com_health.cc\n// Author Stefan Isidorovic <stefan.isidorovic@comtrade.com>\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"HealthCommand.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <iostream>\n\nint com_health(char* arg1)\n{\n  HealthCommand health(arg1);\n\n  try {\n    if (wants_help(arg1)) {\n      health.PrintHelp();\n    } else {\n      health.Execute();\n    }\n  } catch (std::string& e) {\n    std::cout << \"Error: \" << e << std::endl;\n  }\n\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_info.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_info.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\nextern int com_file (char*);\n/*----------------------------------------------------------------------------*/\n\nint\ncom_info (char *arg1)\n{\n  XrdOucString cmd = \"info \";\n  cmd += arg1;\n  return com_file((char*)cmd.c_str());\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_inspector.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_inspector.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/ConsoleMain.hh\"\nextern int com_proto_space(char*);\n\nint\ncom_inspector(char* arg1)\n{\n  XrdOucString cmd = \"inspector\";\n\n  if (arg1 && strlen(arg1)) {\n    cmd += \" \";\n    cmd += arg1;\n  }\n\n  return com_proto_space((char*)cmd.c_str());\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_json.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_silent.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n\n/*----------------------------------------------------------------------------*/\n\nint\ncom_json (char*)\n{\n  gGlobalOpts.mJsonFormat = json = (!json);\n\n  if (json) {\n    interactive = false;\n    global_highlighting = false;\n    runpipe = false;\n  }\n\n  if (!silent)\n  {\n    fprintf(stderr, \"json=%d\\n\", json);\n  }\n\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_license.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_license.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n/*----------------------------------------------------------------------------*/\n\nextern const char* license;\n\n/* Display License File*/\nint\ncom_license(char* arg)\n{\n  fprintf(stdout, \"%s\", license);\n  global_retc = 0;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_ln.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_ln.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\nextern int com_file (char*);\n/*----------------------------------------------------------------------------*/\n\nint\ncom_ln (char *arg1)\n{\n  XrdOucString cmd = \"symlink \";\n  cmd += arg1;\n  return com_file((char*)cmd.c_str());\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_map.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_map.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n/*----------------------------------------------------------------------------*/\n\n/* Map ls, link, unlink */\nint\ncom_map(char* arg1)\n{\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString subcommand = subtokenizer.GetToken();\n  XrdOucString option = \"\";\n  XrdOucString optionstring = \"\";\n  XrdOucString in = \"mgm.cmd=map\";\n  XrdOucString arg = \"\";\n\n  if (wants_help(arg1)) {\n    goto com_map_usage;\n  }\n\n  if (subcommand.beginswith(\"-\")) {\n    option = subcommand;\n    option.erase(0, 1);\n    optionstring += subcommand;\n    optionstring += \" \";\n    subcommand = subtokenizer.GetToken();\n    arg = subtokenizer.GetToken();\n    in += \"&mgm.option=\";\n    in += option;\n  } else {\n    arg = subtokenizer.GetToken();\n  }\n\n  if ((!subcommand.length()) ||\n      ((subcommand != \"ls\") && (subcommand != \"link\") && (subcommand != \"unlink\"))) {\n    goto com_map_usage;\n  }\n\n  if (subcommand == \"ls\") {\n    in += \"&mgm.subcmd=ls\";\n  }\n\n  if (subcommand == \"link\") {\n    XrdOucString key = arg;\n    XrdOucString value = subtokenizer.GetToken();\n\n    if ((!key.length()) || (!value.length())) {\n      goto com_map_usage;\n    }\n\n    in += \"&mgm.subcmd=link&mgm.map.src=\";\n    in += key;\n    in += \"&mgm.map.dest=\";\n    in += value;\n  }\n\n  if (subcommand == \"unlink\") {\n    XrdOucString key = arg;\n\n    if (!key.length()) {\n      goto com_map_usage;\n    }\n\n    in += \"&mgm.subcmd=unlink&mgm.map.src=\";\n    in += key;\n  }\n\n  global_retc = output_result(client_command(in));\n  return (0);\ncom_map_usage:\n  fprintf(stdout,\n          \"'[eos] map ..' provides a namespace mapping interface for directories in EOS.\\n\");\n  fprintf(stdout, \"Usage: map [OPTIONS] ls|link|unlink ...\\n\");\n  fprintf(stdout, \"Options:\\n\");\n  fprintf(stdout, \"map ls :\\n\");\n  fprintf(stdout,\n          \"                                                : list all defined mappings\\n\");\n  fprintf(stdout, \"map link <source-path> <destination-path> :\\n\");\n  fprintf(stdout,\n          \"                                                : create a symbolic link from source-path to destination-path\\n\");\n  fprintf(stdout, \"map unlink <source-path> :\\n\");\n  fprintf(stdout,\n          \"                                                : remove symbolic link from source-path\\n\");\n  global_retc = 0;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_member.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_member.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_member_help()\n{\n  std::ostringstream oss;\n  oss << \"Usage: member [--update] <egroup>\\n\"\n      << \"   show the (cached) information about egroup membership for the\\n\"\n      << \"   current user running the command. If the check is required for\\n\"\n      << \"   a different user then please use the \\\"eos -r <uid> <gid>\\\"\\n\"\n      << \"   command to switch to a different role.\\n\"\n      << \" Options:\\n\"\n      << \"    --update : Refresh cached egroup information\\n\";\n  std::cerr << oss.str() << std::endl;\n}\n\n//------------------------------------------------------------------------------\n// Egroup member\n//------------------------------------------------------------------------------\nint\ncom_member(char* arg)\n{\n  if (!arg || wants_help(arg)) {\n    com_member_help();\n    return (global_retc = EINVAL);\n  }\n\n  bool update = false;\n  const char* option = nullptr;\n  std::string soption;\n  std::string egroup = \"\";\n  XrdOucString in = \"\";\n  eos::common::StringTokenizer subtokenizer(arg);\n  subtokenizer.GetLine();\n\n  do {\n    option = subtokenizer.GetToken();\n\n    if (!option || !strlen(option)) {\n      break;\n    }\n\n    soption = option;\n\n    if ((soption == \"--help\") || (soption == \"-h\")) {\n      com_member_help();\n      return (global_retc = 0);\n    }\n\n    if (soption == \"--update\") {\n      update = true;\n      continue;\n    }\n\n    if (egroup.empty()) {\n      egroup = option;\n    } else {\n      std::cerr << \"error: command accepts only one egroup argument\" << std::endl;\n      return (global_retc = EINVAL);\n    }\n  } while (option && strlen(option));\n\n  if (egroup.empty()) {\n    std::cerr << \"error: no egroup argument given\" << std::endl;\n    return (global_retc = EINVAL);\n  }\n\n  std::cout << \"egroup: \" << egroup << std::endl;\n  in = \"mgm.cmd=member\";\n  in += \"&mgm.egroup=\";\n  in += egroup.c_str();\n\n  if (update) {\n    in += \"&mgm.egroupupdate=true\";\n  }\n\n  return (global_retc = output_result(client_command(in)));\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_mkdir.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_mkdir.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n/*----------------------------------------------------------------------------*/\n\n/* Create a directory */\nint\ncom_mkdir(char* arg1)\n{\n  // split subcommands\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString path = subtokenizer.GetToken();\n  XrdOucString in = \"mgm.cmd=mkdir\";\n\n  if (wants_help(arg1)) {\n    goto com_mkdir_usage;\n  }\n\n  if (path == \"-p\") {\n    path = subtokenizer.GetToken();\n    in += \"&mgm.option=p\";\n  } else {\n    if (path.beginswith(\"-\")) {\n      goto com_mkdir_usage;\n    }\n  }\n\n  do {\n    // read space separated names as a single directory name\n    XrdOucString param;\n    param = subtokenizer.GetToken();\n\n    if (param.length()) {\n      path += \" \";\n      path += param;\n    } else {\n      break;\n    }\n  } while (1);\n\n  // remove escaped blanks\n  while (path.replace(\"\\\\ \", \" \")) {\n  }\n\n  if (!path.length()) {\n    goto com_mkdir_usage;\n  } else {\n    path = abspath(path.c_str());\n    in += \"&mgm.path=\";\n    in += path;\n    global_retc = output_result(client_command(in));\n    return (0);\n  }\n\ncom_mkdir_usage:\n  fprintf(stdout,\n          \"usage: mkdir -p <path>                                                :  create directory <path>\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_motd.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_motd.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include \"common/SymKeys.hh\"\n#include <unistd.h>\n#include <fcntl.h>\n/*----------------------------------------------------------------------------*/\n\n/* Get the server version*/\nint\ncom_motd(char* arg)\n{\n  XrdOucString in = \"mgm.cmd=motd\";\n  XrdOucString motdfile = arg;\n\n  if (motdfile.length()) {\n    int fd = open(motdfile.c_str(), O_RDONLY);\n\n    if (fd >= 0) {\n      char maxmotd[1024];\n      memset(maxmotd, 0, sizeof(maxmotd));\n      size_t nread = read(fd, maxmotd, sizeof(maxmotd));\n      maxmotd[1023] = 0;\n      XrdOucString b64out;\n\n      if (nread > 0) {\n        eos::common::SymKey::Base64Encode(maxmotd, strlen(maxmotd) + 1, b64out);\n      }\n\n      in += \"&mgm.motd=\";\n      in += b64out.c_str();\n      (void) close(fd);\n    }\n  }\n\n  global_retc = output_result(client_command(in));\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_mv.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_mv.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\nextern int com_file (char*);\n/*----------------------------------------------------------------------------*/\n\nint\ncom_mv (char *arg1)\n{\n  XrdOucString cmd = \"rename \";\n  cmd += arg1;\n  return com_file((char*)cmd.c_str());\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_old_find.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_find.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/ICmdHelper.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringConversion.hh\"\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n/*----------------------------------------------------------------------------*/\n\nextern int com_file(char*);\n\n/* Find files/directories */\nint\ncom_old_find(char* arg1)\n{\n  XrdPosixXrootd Xroot;\n  // split subcommands\n  XrdOucString oarg = arg1;\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString s1;\n  XrdOucString path;\n  XrdOucString option = \"\";\n  XrdOucString attribute = \"\";\n  XrdOucString maxdepth = \"\";\n  XrdOucString olderthan = \"\";\n  XrdOucString youngerthan = \"\";\n  XrdOucString printkey = \"\";\n  XrdOucString filter = \"\";\n  XrdOucString stripes = \"\";\n  XrdOucString versions = \"\";\n  XrdOucString filematch = \"\";\n  XrdOucString in = \"mgm.cmd=find&\";\n  bool valid = false;\n\n  if (wants_help(arg1)) {\n    goto com_find_usage;\n  }\n\n  while ((s1 = subtokenizer.GetToken()).length() && (s1.beginswith(\"-\"))) {\n    valid = false;\n\n    if (s1 == \"-j\") {\n      option += \"j\";\n      continue;\n    }\n\n    if (s1 == \"-s\") {\n      option += \"s\";\n      valid = true;\n    }\n\n    if (s1 == \"-d\") {\n      option += \"d\";\n      valid = true;\n    }\n\n    if (s1 == \"-f\") {\n      option += \"f\";\n      valid = true;\n    }\n\n    if (s1 == \"-0\") {\n      option += \"f0\";\n      valid = true;\n    }\n\n    if (s1 == \"-m\") {\n      option += \"fG\";\n      valid = true;\n    }\n\n    if (s1 == \"--size\") {\n      option += \"S\";\n      valid = true;\n    }\n\n    if (s1 == \"--fs\") {\n      option += \"L\";\n      valid = true;\n    }\n\n    if (s1 == \"--checksum\") {\n      option += \"X\";\n      valid = true;\n    }\n\n    if (s1 == \"--ctime\") {\n      option += \"C\";\n      valid = true;\n    }\n\n    if (s1 == \"--mtime\") {\n      option += \"M\";\n      valid = true;\n    }\n\n    if (s1 == \"--fid\") {\n      option += \"F\";\n      valid = true;\n    }\n\n    if (s1 == \"--nrep\") {\n      option += \"R\";\n      valid = true;\n    }\n\n    if (s1 == \"--online\") {\n      option += \"O\";\n      valid = true;\n    }\n\n    if (s1 == \"--fileinfo\") {\n      option += \"I\";\n      valid = true;\n    }\n\n    if (s1 == \"--nunlink\") {\n      option += \"U\";\n      valid = true;\n    }\n\n    if (s1 == \"--uid\") {\n      option += \"u\";\n      valid = true;\n    }\n\n    if (s1 == \"--gid\") {\n      option += \"g\";\n      valid = true;\n    }\n\n    if (s1 == \"--stripediff\") {\n      option += \"D\";\n      valid = true;\n    }\n\n    if (s1 == \"--faultyacl\") {\n      option += \"A\";\n      valid = true;\n    }\n\n    if (s1 == \"--count\") {\n      option += \"Z\";\n      valid = true;\n    }\n\n    if (s1 == \"--hosts\") {\n      option += \"H\";\n      valid = true;\n    }\n\n    if (s1 == \"--partition\") {\n      option += \"P\";\n      valid = true;\n    }\n\n    if (s1 == \"--childcount\") {\n      option += \"l\";\n      valid = true;\n    }\n\n    if (s1 == \"--xurl\") {\n      option += \"x\";\n      valid = true;\n    }\n\n    if (s1 == \"-1\") {\n      option += \"1\";\n      valid = true;\n    }\n\n    if (s1.beginswith(\"-h\") || (s1.beginswith(\"--help\"))) {\n      goto com_find_usage;\n    }\n\n    if (s1 == \"-x\") {\n      valid = true;\n      attribute = subtokenizer.GetToken();\n\n      if (!attribute.length()) {\n        goto com_find_usage;\n      }\n\n      if ((attribute.find(\"&\")) != STR_NPOS) {\n        goto com_find_usage;\n      }\n    }\n\n    if (s1 == \"--maxdepth\") {\n      valid = true;\n      maxdepth = subtokenizer.GetToken();\n\n      if (!maxdepth.length()) {\n        goto com_find_usage;\n      }\n    }\n\n    if ((s1 == \"-ctime\") || (s1 == \"-mtime\")) {\n      valid = true;\n      XrdOucString period = \"\";\n      period = subtokenizer.GetToken();\n\n      if (!period.length()) {\n        goto com_find_usage;\n      }\n\n      bool do_olderthan;\n      do_olderthan = false;\n      bool do_youngerthan;\n      do_youngerthan = false;\n\n      if (period.beginswith(\"+\")) {\n        do_olderthan = true;\n      }\n\n      if (period.beginswith(\"-\")) {\n        do_youngerthan = true;\n      }\n\n      if ((!do_olderthan) && (!do_youngerthan)) {\n        goto com_find_usage;\n      }\n\n      period.erase(0, 1);\n      time_t now = time(NULL);\n      now -= (86400 * strtoul(period.c_str(), 0, 10));\n      char snow[1024];\n      snprintf(snow, sizeof(snow) - 1, \"%lu\", now);\n\n      if (do_olderthan) {\n        olderthan = snow;\n      }\n\n      if (do_youngerthan) {\n        youngerthan = snow;\n      }\n\n      if (s1 == \"-ctime\") {\n        option += \"C\";\n      }\n\n      if (s1 == \"-mtime\") {\n        option += \"M\";\n      }\n    }\n\n    if (s1 == \"-c\") {\n      valid = true;\n      option += \"c\";\n      filter = subtokenizer.GetToken();\n\n      if (!filter.length()) {\n        goto com_find_usage;\n      }\n\n      if ((filter.find(\"%%\")) != STR_NPOS) {\n        goto com_find_usage;\n      }\n    }\n\n    if (s1 == \"--purge\") {\n      valid = true;\n      versions = subtokenizer.GetToken();\n\n      if (!versions.length()) {\n        goto com_find_usage;\n      }\n    }\n\n    if (s1 == \"-name\") {\n      valid = true;\n      filematch = subtokenizer.GetToken();\n      option += \"f\";\n\n      if (!filematch.length()) {\n        goto com_find_usage;\n      }\n    }\n\n    if (s1 == \"-layoutstripes\") {\n      valid = true;\n      stripes = subtokenizer.GetToken();\n\n      if (!stripes.length()) {\n        goto com_find_usage;\n      }\n    }\n\n    if (s1 == \"-p\") {\n      valid = true;\n      option += \"p\";\n      printkey = subtokenizer.GetToken();\n\n      if (!printkey.length()) {\n        goto com_find_usage;\n      }\n    }\n\n    if (s1 == \"-b\") {\n      valid = true;\n      option += \"b\";\n    }\n\n    if (!valid) {\n      goto com_find_usage;\n    }\n  }\n\n  if (s1.length()) {\n    path = s1;\n  }\n\n  if (path == \"help\") {\n    goto com_find_usage;\n  }\n\n  if (!path.endswith(\"/\")) {\n    if (!path.endswith(\":\")) {\n      // if the user gave file: as a search path we shouldn't add '/'=root\n      path += \"/\";\n    }\n  }\n\n  if (path.beginswith(\"root://\") || path.beginswith(\"file:\")) {\n    // -------------------------------------------------------------\n    // do a find with XRootd or local file system\n    // -------------------------------------------------------------\n    bool XRootD = path.beginswith(\"root:\");\n    std::vector< std::vector<std::string> > found_dirs;\n    std::map<std::string, std::set<std::string> > found;\n    XrdOucString protocol;\n    XrdOucString hostport;\n    XrdOucString sPath;\n\n    if (path == \"/\") {\n      fprintf(stderr, \"error: I won't do a find on '/'\\n\");\n      global_retc = EINVAL;\n      return (0);\n    }\n\n    const char* v = 0;\n\n    if (!(v = eos::common::StringConversion::ParseUrl(path.c_str(), protocol,\n              hostport))) {\n      global_retc = EINVAL;\n      return (0);\n    }\n\n    sPath = v;\n    std::string Path = v;\n\n    if (sPath == \"\" && (protocol == \"file\")) {\n      sPath = getenv(\"PWD\");\n      Path = getenv(\"PWD\");\n\n      if (!sPath.endswith(\"/\")) {\n        sPath += \"/\";\n        Path += \"/\";\n      }\n    }\n\n    found_dirs.resize(1);\n    found_dirs[0].resize(1);\n    found_dirs[0][0] = Path.c_str();\n    int deepness = 0;\n\n    do {\n      struct stat buf;\n      found_dirs.resize(deepness + 2);\n\n      // loop over all directories in that deepness\n      for (unsigned int i = 0; i < found_dirs[deepness].size(); i++) {\n        Path = found_dirs[deepness][i].c_str();\n        XrdOucString url = \"\";\n        eos::common::StringConversion::CreateUrl(protocol.c_str(), hostport.c_str(),\n            Path.c_str(), url);\n        int rstat = 0;\n        rstat = (XRootD) ? XrdPosixXrootd::Stat(url.c_str(), &buf) : stat(url.c_str(),\n                &buf);\n\n        if (!rstat) {\n          //\n          if (S_ISDIR(buf.st_mode)) {\n            // add all children\n            DIR* dir = (XRootD) ? XrdPosixXrootd::Opendir(url.c_str()) : opendir(\n                         url.c_str());\n\n            if (dir) {\n              struct dirent* entry;\n\n              while ((entry = (XRootD) ? XrdPosixXrootd::Readdir(dir) : readdir(dir))) {\n                XrdOucString curl = \"\";\n                XrdOucString cpath = Path.c_str();\n                cpath += entry->d_name;\n\n                if ((!strcmp(entry->d_name, \".\")) || (!strcmp(entry->d_name, \"..\"))) {\n                  continue;  // skip . and .. directories\n                }\n\n                eos::common::StringConversion::CreateUrl(protocol.c_str(), hostport.c_str(),\n                    cpath.c_str(), curl);\n\n                if (!((XRootD) ? XrdPosixXrootd::Stat(curl.c_str(), &buf) : stat(curl.c_str(),\n                      &buf))) {\n                  if (S_ISDIR(buf.st_mode)) {\n                    curl += \"/\";\n                    cpath += \"/\";\n                    found_dirs[deepness + 1].push_back(cpath.c_str());\n                    (void) found[curl.c_str()].size();\n                  } else {\n                    found[url.c_str()].insert(entry->d_name);\n                  }\n                }\n              }\n\n              (XRootD) ? XrdPosixXrootd::Closedir(dir) : closedir(dir);\n            }\n          }\n        }\n      }\n\n      deepness++;\n    } while (found_dirs[deepness].size());\n\n    bool show_files = false;\n    bool show_dirs = false;\n\n    if ((option.find(\"f\") == STR_NPOS) && (option.find(\"d\") == STR_NPOS)) {\n      show_files = show_dirs = true;\n    } else {\n      if (option.find(\"f\") != STR_NPOS) {\n        show_files = true;\n      }\n\n      if (option.find(\"d\") != STR_NPOS) {\n        show_dirs = true;\n      }\n    }\n\n    std::map<std::string, std::set<std::string> >::const_iterator it;\n\n    for (it = found.begin(); it != found.end(); it++) {\n      std::set<std::string>::const_iterator sit;\n\n      if (show_dirs) {\n        fprintf(stdout, \"%s\\n\", it->first.c_str());\n      }\n\n      for (sit = it->second.begin(); sit != it->second.end(); sit++) {\n        if (show_files) {\n          fprintf(stdout, \"%s%s\\n\", it->first.c_str(), sit->c_str());\n        }\n      }\n    }\n\n    return 0;\n  }\n\n  if (path.beginswith(\"as3:\")) {\n    // ----------------------------------------------------------------\n    // this is nightmare code because of a missing proper CLI for S3\n    // ----------------------------------------------------------------\n    XrdOucString hostport;\n    XrdOucString protocol;\n    int rc = system(\"which s3 >&/dev/null\");\n\n    if (WEXITSTATUS(rc)) {\n      fprintf(stderr,\n              \"error: you miss the <s3> executable provided by libs3 in your PATH\\n\");\n      exit(-1);\n    }\n\n    if (path.endswith(\"/\")) {\n      path.erase(path.length() - 1);\n    }\n\n    XrdOucString sPath = path.c_str();\n    XrdOucString sOpaque;\n    int qpos = 0;\n\n    if ((qpos = sPath.find(\"?\")) != STR_NPOS) {\n      sOpaque.assign(sPath, qpos + 1);\n      sPath.erase(qpos);\n    }\n\n    XrdOucString fPath = eos::common::StringConversion::ParseUrl(sPath.c_str(),\n                         protocol, hostport);\n    XrdOucEnv env(sOpaque.c_str());\n\n    if (env.Get(\"s3.key\")) {\n      setenv(\"S3_SECRET_ACCESS_KEY\", env.Get(\"s3.key\"), 1);\n    }\n\n    if (env.Get(\"s3.id\")) {\n      setenv(\"S3_ACCESS_KEY_ID\", env.Get(\"s3.id\"), 1);\n    }\n\n    // Apply the ROOT compatability environment variables\n    const char* cstr = getenv(\"S3_ACCESS_KEY\");\n\n    if (cstr) {\n      setenv(\"S3_SECRET_ACCESS_KEY\", cstr, 1);\n    }\n\n    cstr = getenv(\"S3_ACESSS_ID\");\n\n    if (cstr) {\n      setenv(\"S3_ACCESS_KEY_ID\", cstr, 1);\n    }\n\n    // check that the environment is set\n    if (!getenv(\"S3_ACCESS_KEY_ID\") ||\n        !getenv(\"S3_HOSTNAME\") ||\n        !getenv(\"S3_SECRET_ACCESS_KEY\")) {\n      fprintf(stderr,\n              \"error: you have to set the S3 environment variables S3_ACCESS_KEY_ID | S3_ACCESS_ID, S3_HOSTNAME (or use a URI), S3_SECRET_ACCESS_KEY | S3_ACCESS_KEY\\n\");\n      global_retc = EINVAL;\n      return (0);\n    }\n\n    XrdOucString s3env;\n    s3env = \"env S3_ACCESS_KEY_ID=\";\n    s3env += getenv(\"S3_ACCESS_KEY_ID\");\n    s3env += \" S3_HOSTNAME=\";\n    s3env += getenv(\"S3_HOSTNAME\");\n    s3env += \" S3_SECRET_ACCESS_KEY=\";\n    s3env += getenv(\"S3_SECRET_ACCESS_KEY\");\n    XrdOucString cmd = \"bash -c \\\"\";\n    cmd += s3env;\n    cmd += \" s3 list \";\n    // extract bucket from path\n    int bpos = fPath.find(\"/\");\n    XrdOucString bucket;\n\n    if (bpos != STR_NPOS) {\n      bucket.assign(fPath, 0, bpos - 1);\n    } else {\n      bucket = fPath.c_str();\n    }\n\n    XrdOucString match;\n\n    if (bpos != STR_NPOS) {\n      match.assign(fPath, bpos + 1);\n    } else {\n      match = \"\";\n    }\n\n    if ((!bucket.length()) || (bucket.find(\"*\") != STR_NPOS)) {\n      fprintf(stderr, \"error: no bucket specified or wildcard in bucket name!\\n\");\n      global_retc = EINVAL;\n      return (0);\n    }\n\n    cmd += bucket.c_str();\n    cmd += \" | awk '{print \\\\$1}' \";\n\n    if (match.length()) {\n      if (match.endswith(\"*\")) {\n        match.erase(match.length() - 1);\n        match.insert(\"^\", 0);\n      }\n\n      if (match.beginswith(\"*\")) {\n        match.erase(0, 1);\n        match += \"$\";\n      }\n\n      cmd += \" | egrep '\";\n      cmd += match.c_str();\n      cmd += \"'\";\n    }\n\n    cmd += \" | grep -v 'Bucket' | grep -v '\\\\-\\\\-\\\\-\\\\-\\\\-\\\\-\\\\-\\\\-\\\\-\\\\-' | grep -v 'Key' | awk -v prefix=\";\n    cmd += \"'\";\n    cmd += bucket.c_str();\n    cmd += \"' \";\n    cmd += \"'{print \\\\\\\"as3:\\\\\\\"prefix\\\\\\\"/\\\\\\\"\\\\$1}'\";\n    cmd += \"\\\"\";\n    rc = system(cmd.c_str());\n\n    if (WEXITSTATUS(rc)) {\n      fprintf(stderr, \"error: failed to run %s\\n\", cmd.c_str());\n    }\n  }\n\n  // the find to change a layout\n  if ((stripes.length())) {\n    XrdOucString subfind = oarg;\n    XrdOucString repstripes = \" \";\n    repstripes += stripes;\n    repstripes += \" \";\n    subfind.replace(\"-layoutstripes\", \"\");\n    subfind.replace(repstripes, \" -f -s \");\n    int rc = com_old_find((char*) subfind.c_str());\n    std::vector<std::string> files_found;\n    files_found.clear();\n    command_result_stdout_to_vector(files_found);\n    unsigned long long cnt = 0;\n    unsigned long long goodentries = 0;\n    unsigned long long badentries = 0;\n\n    for (unsigned int i = 0; i < files_found.size(); i++) {\n      if (!files_found[i].length()) {\n        continue;\n      }\n\n      XrdOucString cline = \"layout \";\n      cline += files_found[i].c_str();\n      cline += \" -stripes \";\n      cline += stripes;\n      rc = com_file((char*) cline.c_str());\n\n      if (rc) {\n        badentries++;\n      } else {\n        goodentries++;\n      }\n\n      cnt++;\n    }\n\n    rc = 0;\n\n    if (!silent) {\n      fprintf(stderr, \"nentries=%llu good=%llu bad=%llu\\n\", cnt, goodentries,\n              badentries);\n    }\n\n    return 0;\n  }\n\n  // the find with consistency check\n  if ((option.find(\"c\")) != STR_NPOS) {\n    XrdOucString subfind = oarg;\n    subfind.replace(\"-c\", \"-s -f\");\n    subfind.replace(filter, \"\");\n    int rc = com_old_find((char*) subfind.c_str());\n    std::vector<std::string> files_found;\n    files_found.clear();\n    command_result_stdout_to_vector(files_found);\n    unsigned long long cnt = 0;\n    unsigned long long goodentries = 0;\n    unsigned long long badentries = 0;\n\n    for (unsigned int i = 0; i < files_found.size(); i++) {\n      if (!files_found[i].length()) {\n        continue;\n      }\n\n      XrdOucString cline = \"check \";\n      cline += files_found[i].c_str();\n      cline += \" \";\n      cline += filter;\n      rc = com_file((char*) cline.c_str());\n\n      if (rc) {\n        badentries++;\n      } else {\n        goodentries++;\n      }\n\n      cnt++;\n    }\n\n    rc = 0;\n\n    if (!silent) {\n      fprintf(stderr, \"nentries=%llu good=%llu bad=%llu\\n\", cnt, goodentries,\n              badentries);\n    }\n\n    return 0;\n  }\n\n  path = abspath(path.c_str());\n\n  if (!s1.length() && (path == \"/\")) {\n    fprintf(stderr,\n            \"error: you didnt' provide any path and would query '/' - will not do that!\\n\");\n    return EINVAL;\n  }\n\n  in += \"mgm.path=\";\n  in += path;\n  in += \"&mgm.option=\";\n  in += option;\n\n  if (attribute.length()) {\n    in += \"&mgm.find.attribute=\";\n    in += attribute;\n  }\n\n  if (maxdepth.length()) {\n    in += \"&mgm.find.maxdepth=\";\n    in += maxdepth;\n  }\n\n  if (olderthan.length()) {\n    in += \"&mgm.find.olderthan=\";\n    in += olderthan;\n  }\n\n  if (youngerthan.length()) {\n    in += \"&mgm.find.youngerthan=\";\n    in += youngerthan;\n  }\n\n  if (versions.length()) {\n    in += \"&mgm.find.purge.versions=\";\n    in += versions;\n  }\n\n  if (filematch.length()) {\n    in += \"&mgm.find.match=\";\n    in += filematch;\n  }\n\n  if (printkey.length()) {\n    in += \"&mgm.find.printkey=\";\n    in += printkey;\n  }\n\n  XrdOucEnv* result;\n  result = client_command(in);\n\n  if ((option.find(\"s\")) == STR_NPOS) {\n    global_retc = output_result(result);\n  } else {\n    if (result) {\n      global_retc = 0;\n    } else {\n      global_retc = EINVAL;\n    }\n  }\n\n  return (0);\ncom_find_usage:\n  fprintf(stdout,\n          \"usage: find [-name <pattern>] [--xurl] [--childcount] [--purge <n> ] [--count] [-s] [-d] [-f] [-0] [-1] [-ctime +<n>|-<n>] [-m] [-x <key>=<val>] [-p <key>] [-b] [-c %%tags] [-layoutstripes <n>] <path>\\n\");\n  fprintf(stdout,\n          \"                                                                        -f -d :  find files(-f) or directories (-d) in <path>\\n\");\n  fprintf(stdout,\n          \"                                                              -name <pattern> :  find by name or wildcard match\\n\");\n  fprintf(stdout,\n          \"                                                               -x <key>=<val> :  find entries with <key>=<val>\\n\");\n  fprintf(stdout,\n          \"                                                                           -0 :  find 0-size files \\n\");\n  fprintf(stdout,\n          \"                                                                           -g :  find files with mixed scheduling groups\\n\");\n  fprintf(stdout,\n          \"                                                                     -p <key> :  additionally print the value of <key> for each entry\\n\");\n  fprintf(stdout,\n          \"                                                                           -b :  query the server balance of the files found\\n\");\n  fprintf(stdout,\n          \"                                                                    -c %%tags  :  find all files with inconsistencies defined by %%tags [ see help of 'file check' command]\\n\");\n  fprintf(stdout,\n          \"                                                                           -s :  run as a subcommand (in silent mode)\\n\");\n  fprintf(stdout,\n          \"                                                                  -ctime +<n> :  find files older than <n> days\\n\");\n  fprintf(stdout,\n          \"                                                                  -ctime -<n> :  find files younger than <n> days\\n\");\n  fprintf(stdout,\n          \"                                                           -layoutstripes <n> :  apply new layout with <n> stripes to all files found\\n\");\n  fprintf(stdout,\n          \"                                                               --maxdepth <n> :  descend only <n> levels\\n\");\n  fprintf(stdout,\n          \"                                                                           -1 :  find files which are at least 1 hour old\\n\");\n  fprintf(stdout,\n          \"                                                                 --stripediff :  find files which have not the nominal number of stripes(replicas)\\n\");\n  fprintf(stdout,\n          \"                                                                  --faultyacl :  find directories with illegal ACLs\\n\");\n  fprintf(stdout,\n          \"                                                                      --count :  just print global counters for files/dirs found\\n\");\n  fprintf(stdout,\n          \"                                                                       --xurl :  print the XRootD URL instead of the path name\\n\");\n  fprintf(stdout,\n          \"                                                                 --childcount :  print the number of children in each directory\\n\");\n  fprintf(stdout,\n          \"                                                                  --purge <n> | atomic\\n\");\n  fprintf(stdout,\n          \"                                                                              :  remove versioned files keeping <n> versions - to remove all old versions use --purge 0 ! To \\n\"\n          \"                                                                                 apply the settings of the extended attribute definition use <n>=-1! To remove all atomic upload\\n\"\n          \"                                                                                 left-overs older than a day user --purge atomic\\n\");\n  fprintf(stdout,\n          \"                                                                      default :  find files and directories\\n\");\n  fprintf(stdout,\n          \"       find [--nrep] [--nunlink] [--size] [--fileinfo] [--online] [--hosts] [--partition] [--fid] [--fs] [--checksum] [--ctime] [--mtime] [--uid] [--gid] <path>\\n\"\n          \"                                                                              :  find files and print out the requested meta data as key value pairs\\n\");\n  fprintf(stdout,\n          \"                                                               path=file:...  :  do a find in the local file system (options ignored) - 'file:' is the current working directory \\n\");\n  fprintf(stdout,\n          \"                                                               path=root:...  :  do a find on a plain XRootD server (options ignored) - does not work on native XRootD clusters\\n\");\n  fprintf(stdout,\n          \"                                                               path=as3:...   :  do a find on an S3 bucket\\n\");\n  fprintf(stdout,\n          \"                                                               path=...       :  all other paths are considered to be EOS paths!\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n/*\nvoid com_oldfind_help()\n{\n  std::ostringstream oss;\n  oss << \"Usage: oldfind [-name <pattern>] [--xurl] [--childcount] [--purge <n> ] [--count] [-s] [-d] [-f] [-0] [-1] [-g] [-uid <n>] [-nuid <n>] [-gid <n>] [-ngid <n>] [-flag <n>] [-nflag <n>] [-ctime +<n>|-<n>] [-m] [-x <key>=<val>] [-p <key>] [-b] [--layoutstripes <n>] <path>\"\n      << std::endl;\n  oss << \"                -f -d :  find files(-f) or directories (-d) in <path>\"\n      << std::endl;\n  oss << \"     --name <pattern> :  find by name or wildcard match\" << std::endl;\n  oss << \"       -x <key>=<val> :  find entries with <key>=<val>\" << std::endl;\n  oss << \"                   -0 :  find 0-size files only\" << std::endl;\n  oss << \"                   -g :  find files with mixed scheduling groups\" <<\n      std::endl;\n  oss << \"             -p <key> :  additionally print the value of <key> for each entry\"\n      << std::endl;\n  oss << \"                   -b :  query the server balance of the files found\" <<\n      std::endl;\n  oss << \"                   -s :  run as a subcommand (in silent mode)\" <<\n      std::endl;\n  oss << \"             -uid <n> :  entries owned by given user id number\" <<\n      std::endl;\n  oss << \"            -nuid <n> :  entries not owned by given user id number\" <<\n      std::endl;\n  oss << \"             -gid <n> :  entries owned by given group id number\" <<\n      std::endl;\n  oss << \"            -ngid <n> :  entries not owned by given group id number\" <<\n      std::endl;\n  oss << \"            -flag <n> :  directories with specified UNIX access flag, e.g. 755\"\n      << std::endl;\n  oss << \"           -nflag <n> :  directories not with specified UNIX access flag, e.g. 755\"\n      << std::endl;\n  oss << \"          -ctime +<n> :  find files older than <n> days\" << std::endl;\n  oss << \"          -ctime -<n> :  find files younger than <n> days\" << std::endl;\n  oss << \"  --layoutstripes <n> :  apply new layout with <n> stripes to all files found\"\n      << std::endl;\n  oss << \"       --maxdepth <n> :  descend only <n> levels\" << std::endl;\n  oss << \"                   -1 :  find files which are at least 1 hour old\" <<\n      std::endl;\n  oss << \"         --stripediff :  find files which have not the nominal number of stripes(replicas)\"\n      << std::endl;\n  oss << \"          --faultyacl :  find directories with illegal ACLs\" <<\n      std::endl;\n  oss << \"              --count :  just print global counters for files/dirs found\"\n      << std::endl;\n  oss << \"               --xurl :  print the XRootD URL instead of the path name\"\n      << std::endl;\n  oss << \"         --childcount :  print the number of children in each directory\"\n      << std::endl;\n  oss << \"          --purge <n> | atomic\" << std::endl;\n  oss << \"                      :  remove versioned files keeping <n> versions - to remove all old versions use --purge 0\"\n      << std::endl;\n  oss << \"                         To apply the settings of the extended attribute definition use <n>=-1\"\n      << std::endl;\n  oss << \"                         To remove all atomic upload left-overs older than a day user --purge atomic\"\n      << std::endl;\n  oss << \"              default :  find files and directories\" << std::endl;\n  oss << \"       find [--nrep] [--nunlink] [--size] [--fileinfo] [--online] [--hosts] [--partition] [--fid] [--fs] [--checksum] [--ctime] [--mtime] [--uid] [--gid] <path>\"\n      << std::endl;\n  oss << \"                      :  find files and print out the requested meta data as key value pairs\"\n      << std::endl;\n  oss << \"       path=file:...  :  do a find in the local file system (options ignored) - 'file:' is the current working directory\"\n      << std::endl;\n  oss << \"       path=root:...  :  do a find on a plain XRootD server (options ignored) - does not work on native XRootD clusters\"\n      << std::endl;\n  oss << \"       path=as3:...   :  do a find on an S3 bucket\" << std::endl;\n  oss << \"       path=...       :  all other paths are considered to be EOS paths!\"\n      << std::endl;\n  std::cerr << oss.str() << std::endl;\n}\n*/\n"
  },
  {
    "path": "console/commands/coms/unused/com_print.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_print.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include <string.h>\n#include <sstream>\n#include <iomanip>\n/*----------------------------------------------------------------------------*/\n\n//------------------------------------------------------------------------------\n// Print help text for ARG or print help text\n// for all of the commands if ARG is not present\n//------------------------------------------------------------------------------\nint\ncom_help (char *arg)\n{\n  std::string sarg;\n  int printed = 0;\n\n  // Unquote argument\n  std::stringstream ss;\n  ss << arg;\n  ss >> std::quoted(sarg);\n\n  // Print specific help text or print all commands if null argument\n  for (int i = 0; commands[i].name; i++) {\n    if (!*arg || (strcmp(sarg.c_str(), commands[i].name) == 0)) {\n      printf(\"%-20s %s\\n\", commands[i].name, commands[i].doc);\n      printed++;\n    }\n  }\n\n  if (!printed) {\n    printf(\"No commands match '%s'. Possibilities are:\\n\", sarg.c_str());\n\n    for (int i = 0; commands[i].name; i++) {\n      /* Print in six columns. */\n      if (printed == 6) {\n        printed = 0;\n        printf(\"\\n\");\n      }\n\n      printf(\"%-12s\", commands[i].name);\n      printed++;\n    }\n\n    if (printed) { printf(\"\\n\"); }\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_access.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: com_proto_access.cc\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your token) any later version.                                   *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/commands/ICmdHelper.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n\nextern int com_access(char*);\nvoid com_access_help();\n\n//------------------------------------------------------------------------------\n//! Class AccessHelper\n//------------------------------------------------------------------------------\nclass AccessHelper : public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  AccessHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~AccessHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n};\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool AccessHelper::ParseCommand(const char* arg)\n{\n  eos::console::AccessProto* access = mReq.mutable_access();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  std::string token;\n\n  if (!tokenizer.NextToken(token)) {\n    return false;\n  }\n\n  if (token == \"ls\") {\n    eos::console::AccessProto_LsProto* ls = access->mutable_ls();\n\n    while (tokenizer.NextToken(token)) {\n      if (token == \"-m\") {\n        ls->set_monitoring(true);\n      } else if (token == \"-n\") {\n        ls->set_id2name(true);\n      } else {\n        return false;\n      }\n    }\n  } else if (token == \"rm\") {\n    eos::console::AccessProto_RmProto* rm = access->mutable_rm();\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"redirect\") {\n      rm->set_rule(eos::console::AccessProto_RmProto::REDIRECT);\n\n      if (tokenizer.NextToken(token)) {\n        if (token == \"r\" || token == \"w\" || token == \"ENOENT\" || token == \"ENONET\" ||\n            token == \"ENETUNREACH\") {\n          rm->set_key(token);\n        } else {\n          return false;\n        }\n      }\n    } else if (token == \"stall\") {\n      rm->set_rule(eos::console::AccessProto_RmProto::STALL);\n\n      if (tokenizer.NextToken(token)) {\n        if (token == \"r\" || token == \"w\" || token == \"ENOENT\" || token == \"ENONET\" ||\n            token == \"ENETUNREACH\") {\n          rm->set_key(token);\n        } else {\n          return false;\n        }\n      }\n    } else if (token == \"limit\") {\n      rm->set_rule(eos::console::AccessProto_RmProto::LIMIT);\n\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      if ((!token.find(\"threads:\")) && (\n            !(token.find(\"rate:user:\") || token.find(\"rate:group:\")) &&\n            token.find(':', 11))) {\n        return false;\n      }\n\n      rm->set_key(token);\n    } else {\n      return false;\n    }\n  } else if (token == \"set\") {\n    eos::console::AccessProto_SetProto* set = access->mutable_set();\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"redirect\") {\n      set->set_rule(eos::console::AccessProto_SetProto::REDIRECT);\n\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      set->set_target(token);\n\n      if (tokenizer.NextToken(token)) {\n        if (token == \"r\" || token == \"w\" || token == \"ENOENT\" || token == \"ENONET\" ||\n            token == \"ENETUNREACH\") {\n          set->set_key(token);\n        } else {\n          return false;\n        }\n      }\n    } else if (token == \"stall\") {\n      set->set_rule(eos::console::AccessProto_SetProto::STALL);\n\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      set->set_target(token);\n\n      if (tokenizer.NextToken(token)) {\n        if (token == \"r\" || token == \"w\" || token == \"ENOENT\" || token == \"ENONET\" ||\n            token == \"ENETUNREACH\") {\n          set->set_key(token);\n        } else {\n          return false;\n        }\n      }\n    } else if (token == \"limit\") {\n      set->set_rule(eos::console::AccessProto_SetProto::LIMIT);\n\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      set->set_target(token);\n\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      if ((!token.find(\"threads:\")) && (\n            !(token.find(\"rate:user:\") || token.find(\"rate:group:\")) &&\n            token.find(':', 11))) {\n        return false;\n      }\n\n      set->set_key(token);\n    } else {\n      return false;\n    }\n  } else if (token == \"ban\") {\n    eos::console::AccessProto_BanProto* ban = access->mutable_ban();\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"user\") {\n      ban->set_idtype(eos::console::AccessProto_BanProto::USER);\n    } else if (token == \"group\") {\n      ban->set_idtype(eos::console::AccessProto_BanProto::GROUP);\n    } else if (token == \"host\") {\n      ban->set_idtype(eos::console::AccessProto_BanProto::HOST);\n    } else if (token == \"domain\") {\n      ban->set_idtype(eos::console::AccessProto_BanProto::DOMAINNAME);\n    } else if (token == \"token\") {\n      ban->set_idtype(eos::console::AccessProto_BanProto::TOKEN);\n    } else {\n      return false;\n    }\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    ban->set_id(token);\n  } else if (token == \"unban\") {\n    eos::console::AccessProto_UnbanProto* unban = access->mutable_unban();\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"user\") {\n      unban->set_idtype(eos::console::AccessProto_UnbanProto::USER);\n    } else if (token == \"group\") {\n      unban->set_idtype(eos::console::AccessProto_UnbanProto::GROUP);\n    } else if (token == \"host\") {\n      unban->set_idtype(eos::console::AccessProto_UnbanProto::HOST);\n    } else if (token == \"domain\") {\n      unban->set_idtype(eos::console::AccessProto_UnbanProto::DOMAINNAME);\n    } else if (token == \"token\") {\n      unban->set_idtype(eos::console::AccessProto_UnbanProto::TOKEN);\n    } else {\n      return false;\n    }\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    unban->set_id(token);\n  } else if (token == \"allow\") {\n    eos::console::AccessProto_AllowProto* allow = access->mutable_allow();\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"user\") {\n      allow->set_idtype(eos::console::AccessProto_AllowProto::USER);\n    } else if (token == \"group\") {\n      allow->set_idtype(eos::console::AccessProto_AllowProto::GROUP);\n    } else if (token == \"host\") {\n      allow->set_idtype(eos::console::AccessProto_AllowProto::HOST);\n    } else if (token == \"domain\") {\n      allow->set_idtype(eos::console::AccessProto_AllowProto::DOMAINNAME);\n    } else if (token == \"token\") {\n      allow->set_idtype(eos::console::AccessProto_AllowProto::TOKEN);\n    } else {\n      return false;\n    }\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    allow->set_id(token);\n  } else if (token == \"unallow\") {\n    eos::console::AccessProto_UnallowProto* unallow = access->mutable_unallow();\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"user\") {\n      unallow->set_idtype(eos::console::AccessProto_UnallowProto::USER);\n    } else if (token == \"group\") {\n      unallow->set_idtype(eos::console::AccessProto_UnallowProto::GROUP);\n    } else if (token == \"host\") {\n      unallow->set_idtype(eos::console::AccessProto_UnallowProto::HOST);\n    } else if (token == \"domain\") {\n      unallow->set_idtype(eos::console::AccessProto_UnallowProto::DOMAINNAME);\n    } else if (token == \"token\") {\n      unallow->set_idtype(eos::console::AccessProto_UnallowProto::TOKEN);\n    } else {\n      return false;\n    }\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    unallow->set_id(token);\n  } else if (token == \"stallhosts\") {\n    eos::console::AccessProto_StallHostsProto* stall = access->mutable_stallhosts();\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"add\") {\n      stall->set_op(eos::console::AccessProto_StallHostsProto::ADD);\n    } else if (token == \"remove\") {\n      stall->set_op(eos::console::AccessProto_StallHostsProto::REMOVE);\n    } else {\n      return false;\n    }\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"stall\") {\n      stall->set_type(eos::console::AccessProto_StallHostsProto::STALL);\n    } else if (token == \"nostall\") {\n      stall->set_type(eos::console::AccessProto_StallHostsProto::NOSTALL);\n    } else {\n      return false;\n    }\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    stall->set_hostpattern(token);\n  } else { // no proper subcommand\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Config command entry point\n//------------------------------------------------------------------------------\nint com_protoaccess(char* arg)\n{\n  if (wants_help(arg)) {\n    com_access_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  AccessHelper access(gGlobalOpts);\n\n  if (!access.ParseCommand(arg)) {\n    com_access_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = access.Execute();\n  return global_retc;\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_access_help()\n{\n  std::ostringstream oss;\n  oss\n      << \" usage:\\n\"\n      << \"access ban|unban|allow|unallow|set|rm|stallhosts|ls [OPTIONS]\\n\"\n      << \"'[eos] access ..' provides the access interface of EOS to allow/disallow hosts/domains and/or users\\n\"\n      << std::endl\n      << \"Subcommands:\\n\"\n      << \"access ban user|group|host|domain|token <identifier> : ban user, group, host, domain or token with identifier <identifier>\\n\"\n      << \"\\t <identifier> : can be a user name, user id, group name, group id, hostname or IP or domainname or token voucher id\\n\"\n      << std::endl\n      << \"access unban user|group|host|domain|token <identifier> : unban user, group, host, domain or token with identifier <identifier>\\n\"\n      << \"\\t <identifier> : can be a user name, user id, group name, group id, hostname or IP or domainname or token voucher id\\n\"\n      << std::endl\n      << \"access allow user|group|host|domain|token <identifier> : allows this user, group, host, domain or token access\\n\"\n      << \"\\t <identifier> : can be a user name, user id, group name, group id, hostname or IP or domainname or token voucher id\\n\"\n      << std::endl\n      << \"access unallow user|group|host|domain|token <identifier> : unallows this user,group, host, domain or token access\\n\"\n      << \"\\t <identifier> : can be a user name, user id, group name, group id, hostname or IP or domainname or token voucher id\\n\"\n      << std::endl\n      << \"\\t HINT: if you add any 'allow' the instance allows only the listed identity. A banned identifier will still overrule an allowed identifier!\\n\"\n      << std::endl\n      << \"access set redirect <target-host> [r|w|ENOENT|ENONET|ENETUNREACH] : allows to set a global redirection to <target-host>\\n\"\n      << \"\\t <target-host>      : hostname to which all requests get redirected\\n\"\n      << \"\\t         [r|w]      : optional set a redirect for read/write requests seperatly\\n\"\n      << \"\\t      [ENOENT]      : optional set a redirect if a file is not existing\\n\"\n      << \"\\t      [ENONET]      : optional set a redirect if a file is offline\\n\"\n      << \"\\t      [ENETUNREACH] : optional set a redirect if @todo \\n\"\n      << \"\\t                      <taget-hosts> can be structured like <host>:<port[:<delay-in-ms>] where <delay> holds each request for a given time before redirecting\\n\"\n      << std::endl\n      << \"access set stall <stall-time> [r|w|ENOENT|ENONET|ENETUNREACH] : allows to set a global stall time\\n\"\n      << \"\\t <stall-time> : time in seconds after which clients should rebounce\\n\"\n      << \"\\t         [r|w]      : optional set stall time for read/write requests seperatly\\n\"\n      << \"\\t      [ENOENT]      : optional set stall time if a file is not existing\\n\"\n      << \"\\t      [ENONET]      : optional set stall time if a file is offline\\n\"\n      << \"\\t      [ENETUNREACH] : optional set a stall time if @todo \\n\"\n      << std::endl\n      << \"access set limit <frequency> rate:{user,group}:{name}:<counter>\\n\"\n      << \"\\t rate:{user:group}:{name}:<counter> : stall the defined user group for 5s if the <counter> exceeds a frequency of <frequency> in a 5s interval\\n\"\n      << \"\\t                                      - the instantaneous rate can exceed this value by 33%%\\n\"\n      << \"\\t              rate:user:*:<counter> : apply to all users based on user counter\\n\"\n      << \"\\t              rate:group:*:<counter>: apply to all groups based on group counter\\n\"\n      << \"\\t                                      set <frequency> to 0 (zero) to continuously stall the user or group\\n\"\n      << std::endl\n      << \"access set limit <frequency> threads:{*,max,<uid/username>}\\n\"\n      << \"\\t             threads:max            : set the maximum number of threads running in parallel\\n\"\n      << \"\\t             threads:*              : set the default thread pool limit for each user\\n\"\n      << \"\\t             threads:<uid/username> : set a specific thread pool limit for user <username/uid>\\n\"\n      << std::endl\n      << \"access set limit <nfiles> rate:user:{name}:FindFiles :\\n\\tset find query limit to <nfiles> for user {name}\\n\"\n      << std::endl\n      << \"access set limit <ndirs> rate:user:{name}:FindDirs:\\n\\tset find query limit to <ndirs> for user {name}\\n\"\n      << std::endl\n      << \"access set limit <nfiles> rate:group:{name}:FindFiles :\\n\\tset find query limit to <nfiles> for group {name}\\n\"\n      << std::endl\n      << \"access set limit <ndirs> rate:group:{name}:FindDirs :\\n\\tset find query limit to <ndirss> for group {name}\\n\"\n      << std::endl\n      << \"access set limit <nfiles> rate:user:*:FindFiles :\\n\\tset default find query limit to <nfiles> for everybody\\n\"\n      << std::endl\n      << \"access set limit <ndirs> rate:user:*:FindDirs :\\n\\tset default find query limit to <ndirss> for everybody\\n\"\n      << std::endl\n      << \"\\t HINT : rule strength => user-limit >> group-limit >> wildcard-limit\\n\"\n      << std::endl\n      << \"access rm redirect [r|w|ENOENT|ENONET|ENETUNREACH] : removes global redirection\\n\"\n      << std::endl\n      << \"access rm stall [r|w|ENOENT|ENONET|ENETUNREACH] : removes global stall time\\n\"\n      << std::endl\n      << \"access rm limit rate:{user,group}:{name}:<counter> : remove rate limitation\\n\"\n      << std::endl\n      << \"access rm limit threads:{max,*,<uid/username>} : remove thread pool limit\\n\"\n      << std::endl\n      << \"access stallhosts add|remove stall|nostall <pattern>\\n\"\n      << std::endl\n      << \"access ls [-m] [-n] : print banned,unbanned user,group, hosts\\n\"\n      << \"\\t -m : output in monitoring format with <key>=<value>\\n\"\n      << \"\\t -n : don't translate uid/gids to names\\n\"\n      << std::endl\n      << \"Examples:\\n\"\n      << \" access ban host foo                            : Ban host foo\\n\"\n      << \" access ban domain bar                          : Ban domain bar\\n\"\n      << \" access allow domain nobody@bar                 : Allows user nobody from domain bar\\n\"\n      << \" access allow domain -                          : use domain allow as whitelist - e.g. nobody@bar will additionally allow the nobody user from domain bar!\\n\"\n      << \" access allow domain bar                        : Allow only domain bar\\n\"\n      << \" access set redirect foo                        : Redirect all requests to host foo\\n\"\n      << \" access set redirect foo:1094:1000              : Redirect all requests to host foo:1094 and hold each reqeust for 1000ms\\n\"\n      << \" access rm redirect                             : Remove redirection to previously defined host foo\\n\"\n      << \" access set stall 60                            : Stall all clients by 60 seconds\\n\"\n      << \" access ls                                      : Print all defined access rules\\n\"\n      << \" access set limit 100  rate:user:*:OpenRead     : Limit the open for read rate to a frequency of 100 Hz for all users\\n\"\n      << \" access set limit 0    rate:user:ab:OpenRead    : Limit the open for read rate for the ab user to 0 Hz, to continuously stall it\\n\"\n      << \" access set limit 2000 rate:group:zp:Stat       : Limit the stat rate for the zp group to 2kHz\\n\"\n      << \" access set limit 500 threads:*                 : Limit the thread pool usage to 500 threads per user\\n\"\n      << \" access rm limit rate:user:*:OpenRead           : Removes the defined limit\\n\"\n      << \" access rm limit threads:*                      : Removes the default per user thread pool limit\\n\"\n      << \" access stallhosts add stall foo*.bar           : Add foo*.bar to the list of hosts which are stalled by limit rules (white list)\\n\"\n      << \" access stallhosts remove stall foo*.bar        : Remove foo*.bar from the list of hosts which are stalled by limit rules (white list)\\n\"\n      << \" access stallhosts add nostall foo*.bar         : Add foo*.bar to the list of hosts which are never stalled by limit rules (black list)\\n\"\n      << \" access stallhosts remove nostall foo*.bar      : Remove foo*.bar from the list of hosts which are never stalled by limit rules (black list)\\n\";\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_acl.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file com_acl.cc\n//! @author Stefan Isidorovic <stefan.isidorovic@comtrade.com>\n//!         Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/AclHelper.hh\"\n#include <sstream>\n\n//! Forward declaration\nvoid com_acl_help();\n\n//------------------------------------------------------------------------------\n// Acl command entrypoint\n//------------------------------------------------------------------------------\nint com_acl(char* arg)\n{\n  if (wants_help(arg)) {\n    com_acl_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  AclHelper acl(gGlobalOpts);\n\n  if (!acl.ParseCommand(arg)) {\n    com_acl_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = acl.Execute(true, true);\n  return global_retc;\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_acl_help()\n{\n  std::ostringstream oss;\n  oss\n      << \"Usage: eos acl [-l|--list] [-R|--recursive]\"\n      << \" [-p|--position <pos>] [-f|--front] \"\n      << \"[--sys|--user] [<rule>] <identifier>\" << std::endl\n      << \"  atomically set and modify ACLs for the given directory path/sub-tree\\n\"\n      << std::endl\n      << \"  -h, --help      : print help message\" << std::endl\n      << \"  -R, --recursive : apply to directories recursively\" << std::endl\n      << \"  -l, --list      : list ACL rules\" << std::endl\n      << \"  -p, --position  : add the acl rule at specified position\" << std::endl\n      << \"  -f, --front     : add the acl rule at the front position\" << std::endl\n      << \"      --user      : handle user.acl rules on directory\" << std::endl\n      << \"      --sys       : handle sys.acl rules on directory - admin only\\n\"\n      << std::endl\n      << \"  <identifier> can be one of <path>|cid:<cid-dec>|cxid:<cid-hex>\\n\"\n      << std::endl\n      << \"  <rule> is created similarly to chmod rules. Every rule begins with\"\n      << std::endl\n      << \"    [u|g|egroup] followed by \\\":\\\" or \\\"=\\\" and an identifier.\"\n      << std::endl\n      << \"    \\\":\\\" is used to for modifying permissions while\" << std::endl\n      << \"    \\\"=\\\" is used for setting/overwriting permissions.\" << std::endl\n      << \"    When modifying permissions every ACL flag can be added with\" <<\n      std::endl\n      << \"    \\\"+\\\" or removed with \\\"-\\\".\" << std::endl\n      << \"    By default rules are appended at the end of acls\" << std::endl\n      << \"    This ordering can be changed via --position flag\" << std::endl\n      << \"    which will add the new rule at a given position starting at 1 or\" <<\n      std::endl\n      << \"    the --front flag which adds the rule at the front instead\" << std::endl\n      << std::endl\n      << \"Examples:\" << std::endl\n      << \"  acl --user u:1001=rwx /eos/dev/\" << std::endl\n      << \"    Set ACLs for user id 1001 to rwx\" << std::endl\n      << \"  acl --user u:1001:-w /eos/dev\" << std::endl\n      << \"    Remove \\'w\\' flag for user id 1001\" << std::endl\n      << \"  acl --user u:1001:+m /eos/dev\" << std::endl\n      << \"    Add change mode permission flag for user id 1001\" << std::endl\n      << \"  acl --user u:1010= /eos/dev\" << std::endl\n      << \"    Remove all ACls for user id 1001\" << std::endl\n      << \"  acl --front --user u:1001=rwx /eos/dev\" << std::endl\n      << \"     Add the user id 1001 rule to the front of ACL rules\" << std::endl;\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_config.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: com_proto_config.cc\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your token) any later version.                                   *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/commands/ICmdHelper.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n\nextern int com_config(char*);\nvoid com_config_help();\n\n//------------------------------------------------------------------------------\n//! Class ConfigHelper\n//------------------------------------------------------------------------------\nclass ConfigHelper : public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  ConfigHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~ConfigHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n};\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool ConfigHelper::ParseCommand(const char* arg)\n{\n  eos::console::ConfigProto* config = mReq.mutable_config();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  std::string token;\n\n  if (!tokenizer.NextToken(token)) {\n    return false;\n  }\n\n  if (token == \"ls\") {\n    eos::console::ConfigProto_LsProto* ls = config->mutable_ls();\n\n    if (tokenizer.NextToken(token)) {\n      if (token == \"--backup\" || token == \"-b\") {\n        ls->set_showbackup(true);\n      } else {\n        return false;\n      }\n    }\n  } else if (token == \"dump\") {\n    eos::console::ConfigProto_DumpProto* dump = config->mutable_dump();\n\n    if (tokenizer.NextToken(token)) {\n      dump->set_file(token);\n    }\n  } else if (token == \"reset\") {\n    if (tokenizer.NextToken(token)) {\n      return false;  // no need for more arguments\n    }\n\n    config->set_reset(true);\n  } else if (token == \"export\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::ConfigProto_ExportProto* exp = config->mutable_exp();\n\n    // either \"<file> or <file> -f\n    if (token.find('-') != 0) { // does not begins with '-'\n      exp->set_file(token);\n\n      if (tokenizer.NextToken(token)) {\n        if (token == \"-f\") {\n          exp->set_force(true);\n        } else {\n          return false;\n        }\n      }\n    } else {\n      return false;\n    }\n  } else if (token == \"save\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::ConfigProto_SaveProto* save = config->mutable_save();\n\n    if (token.find('-') != 0) {\n      save->set_file(token);\n    } else {\n      return false;\n    }\n\n    while (tokenizer.NextToken(token)) {\n      if (token == \"-c\" ||\n          token == \"--comment\") { // put the comment in the mReq object\n        std::string sline = arg;\n\n        if (token == \"-c\") {\n          // have to replace \"-c\" with \"--comment\" in sline\n          size_t pos = sline.find(\"-c\");\n          sline.replace(pos, std::string(\"-c\").length(), \"--comment\");\n          parse_comment(sline.c_str(), token);\n        } else if (token == \"--comment\") {\n          parse_comment(sline.c_str(), token);\n        }\n\n        mReq.set_comment(token);\n        tokenizer.NextToken(token); // skip comment text\n      } else if (token == \"-f\") {\n        save->set_force(true);\n      } else {\n        return false;\n      }\n    }\n  } else if (token == \"load\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::ConfigProto_LoadProto* load = config->mutable_load();\n    load->set_file(token);\n  } else if (token == \"changelog\") {\n    eos::console::ConfigProto_ChangelogProto* changelog =\n      config->mutable_changelog();\n\n    if (tokenizer.NextToken(token)) {\n      if (token.find('-') == 0) {\n        token.erase(0);  // remove first char to allow both -100 and 100\n      }\n\n      try {\n        changelog->set_lines(std::stoi(token));\n      } catch (const std::exception& e) {\n        std::cerr << \"error: argument needs to be numeric\" << std::endl;\n        return false;\n      }\n    } else {\n      changelog->set_lines(10);\n    }\n  } else { // no proper subcommand\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Config command entry point\n//------------------------------------------------------------------------------\nint com_protoconfig(char* arg)\n{\n  if (wants_help(arg)) {\n    com_config_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  ConfigHelper config(gGlobalOpts);\n\n  if (!config.ParseCommand(arg)) {\n    com_config_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = config.Execute();\n  return global_retc;\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_config_help()\n{\n  std::ostringstream oss;\n  oss\n      << \" usage:\\n\"\n      << \"config changelog|dump|export|load|ls|reset|save [OPTIONS]\\n\"\n      << \"'[eos] config' provides the configuration interface to EOS.\\n\"\n      << std::endl\n      << \"Subcommands:\\n\"\n      << \"config changelog [#lines] : show the last #lines from the changelog - default is 10\\n\"\n      << std::endl\n      << \"config dump [<name>] : dump configuration with name <name> or current one by default\\n\"\n      << std::endl\n      << \"config export <name> [-f] : export a configuration stored on file to QuarkDB (you need to specify the full path!)\\n\"\n      << \"\\t -f : overwrite existing config name and create a timestamped backup\\n\"\n      << std::endl\n      << \"config load <name> : load <name> config\\n\"\n      << std::endl\n      << \"config ls [-b|--backup] : list existing configurations\\n\"\n      << \"\\t -b : show also backup & autosave files\\n\"\n      << std::endl\n      << \"config reset : reset all configuration to empty state\\n\"\n      << std::endl\n      << \"config save <name> [-f] [-c|--comment \\\"<comment>\\\"] : save config under <name>\\n\"\n      << \"\\t -f : overwrite existing config name and create a timestamped backup\\n\"\n      << \"\\t -c : add a comment entry to the config\\n\";\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_convert.cc",
    "content": "//------------------------------------------------------------------------------\n// File com_proto_convert.cc\n// Author: Mihai Patrascoiu - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/LayoutId.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/ICmdHelper.hh\"\n\nvoid com_convert_help();\n\n//------------------------------------------------------------------------------\n//! Class ConvertHelper\n//------------------------------------------------------------------------------\nclass ConvertHelper: public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  ConvertHelper(const GlobalOptions& opts) : ICmdHelper(opts) {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~ConvertHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Parse identifier string and construct identifier proto object.\n  //----------------------------------------------------------------------------\n  eos::console::ConvertProto_IdentifierProto*\n  ParseIdentifier(std::string path);\n\n  //----------------------------------------------------------------------------\n  //! Parse conversion string and construct conversion proto object.\n  //! Returns null if conversion string is invalid.\n  //----------------------------------------------------------------------------\n  eos::console::ConvertProto_ConversionProto*\n  ParseConversion(eos::common::StringTokenizer& tokenizer);\n};\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool\nConvertHelper::ParseCommand(const char* arg)\n{\n  using eos::console::ConvertProto_ConfigProto;\n  eos::console::ConvertProto* convert = mReq.mutable_convert();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  std::string token;\n\n  if (!tokenizer.NextToken(token)) {\n    return false;\n  }\n\n  if (token == \"config\") {\n    ConvertProto_ConfigProto* config = convert->mutable_config();\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"list\") {\n      config->set_op(ConvertProto_ConfigProto::LIST);\n    } else if (token == \"set\") {\n      if (!tokenizer.NextToken(token)) {\n        std::cerr << \"error: config set takes a parameter\" << std::endl;\n        return false;\n      }\n\n      size_t pos = token.find(\"=\");\n\n      if ((pos == std::string::npos) ||\n          (pos == token.length() - 1)) {\n        std::cerr << \"error: config set takes a key=value parameter\"\n                  << std::endl;\n        return false;\n      }\n\n      config->set_op(ConvertProto_ConfigProto::SET);\n      config->set_key(token.substr(0, pos));\n      config->set_value(token.substr(pos + 1));\n    } else {\n      std::cerr << \"error: unknown config option '\"\n                << token << \"'\" << std::endl;\n      return false;\n    }\n  } else if (token == \"file\") {\n    eos::console::ConvertProto_FileProto* file = convert->mutable_file();\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    file->set_allocated_identifier(ParseIdentifier(token));\n    eos::console::ConvertProto_ConversionProto*\n    conversion = ParseConversion(tokenizer);\n\n    if (conversion == nullptr) {\n      return false;\n    }\n\n    file ->set_allocated_conversion(conversion);\n    // Placeholder for options\n  } else if (token == \"list\") {\n    convert->mutable_list();\n  } else if (token == \"clear\") {\n    convert->mutable_clear();\n  } else {\n    return false;\n  }\n\n  return true;\n}\n\n//----------------------------------------------------------------------------\n// Parse string identifier and construct identifier proto object\n//----------------------------------------------------------------------------\neos::console::ConvertProto_IdentifierProto*\nConvertHelper::ParseIdentifier(std::string spath)\n{\n  XrdOucString path = spath.c_str();\n  auto identifier = new eos::console::ConvertProto_IdentifierProto{};\n  auto id = 0ull;\n\n  if (Path2FileDenominator(path, id)) {\n    identifier->set_fileid(id);\n  } else if (Path2ContainerDenominator(path, id)) {\n    identifier->set_containerid(id);\n  } else {\n    identifier->set_path(abspath(path.c_str()));\n  }\n\n  return identifier;\n}\n\n//----------------------------------------------------------------------------\n// Parse conversion string and construct conversion proto object.\n// Returns null if conversion string is invalid\n//----------------------------------------------------------------------------\neos::console::ConvertProto_ConversionProto*\nConvertHelper::ParseConversion(eos::common::StringTokenizer& tokenizer)\n{\n  std::string token;\n  std::string layout;\n  std::string space;\n  std::string placement;\n  std::string checksum;\n  int replica = 0;\n  size_t pos;\n  bool ok;\n  // Lambda function to validate layout string\n  auto validLayout = [](const std::string & layout) {\n    return eos::common::LayoutId::GetLayoutFromString(layout) != -1;\n  };\n  // Lambda function to validate placement policy string\n  auto validPlacement = [](const std::string & placement) {\n    if (placement == \"scattered\" || placement == \"hybrid\" ||\n        placement == \"gathered\") {\n      return true;\n    }\n\n    return false;\n  };\n  // Lambda function to validate checksum string\n  auto validChecksum = [](const std::string & checksum) {\n    using eos::common::LayoutId;\n    auto xs_id = LayoutId::GetChecksumFromString(checksum);\n    return ((xs_id > -1) && (xs_id != LayoutId::eChecksum::kNone));\n  };\n\n  if (!tokenizer.NextToken(token)) {\n    std::cerr << \"error: missing <layout:replica> argument\" << std::endl;\n    return nullptr;\n  }\n\n  if ((pos = token.find(\":\")) == std::string::npos) {\n    std::cerr << \"error: invalid <layout:replica> format\" << std::endl;\n    return nullptr;\n  }\n\n  layout = token.substr(0, pos);\n\n  try {\n    replica = std::stol(token.substr(pos + 1));\n  } catch (...) {\n    std::cerr << \"error: failed to interpret replica number '\"\n              << token.substr(pos + 1) << \"'\" << std::endl;\n    return nullptr;\n  }\n\n  if (!validLayout(layout)) {\n    std::cerr << \"error: invalid layout '\" << layout << \"'\" << std::endl;\n    return nullptr;\n  }\n\n  if (replica < 1 || replica > 32) {\n    std::cerr << \"error: invalid replica number=\" << replica\n              << \" (must be between 1 and 32)\" << std::endl;\n    return nullptr;\n  }\n\n  while (tokenizer.NextToken(token)) {\n    if ((ok = validChecksum(token))) {\n      checksum = std::move(token);\n    } else if ((ok = validPlacement(token))) {\n      placement = std::move(token);\n    } else if ((ok = space.empty())) {\n      space = std::move(token);\n    }\n\n    if (!ok) {\n      std::cerr << \"error: could not interpret '\" << token << \"' argument\"\n                << std::endl;\n      return nullptr;\n    }\n  }\n\n  auto conversion = new eos::console::ConvertProto_ConversionProto{};\n  conversion->set_layout(layout);\n  conversion->set_replica(replica);\n  conversion->set_space(space);\n  conversion->set_placement(placement);\n  conversion->set_checksum(checksum);\n  return conversion;\n}\n\n//------------------------------------------------------------------------------\n// Convert command entry point\n//------------------------------------------------------------------------------\nint com_convert(char* arg)\n{\n  if (wants_help(arg)) {\n    com_convert_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  ConvertHelper convert(gGlobalOpts);\n\n  if (!convert.ParseCommand(arg)) {\n    com_convert_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = convert.Execute();\n  return global_retc;\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_convert_help()\n{\n  std::ostringstream oss;\n  oss << \"Usage: convert <subcomand>                         \\n\"\n      << \"  convert config list|set [<key>=<value>]          \\n\"\n      << \"    list: list converter configuration parameters and status\\n\"\n      << \"    set : set converter configuration parameters. Options:\\n\"\n      << \"      status               : \\\"on\\\" or \\\"off\\\"     \\n\"\n      << \"      max-thread-pool-size : max number of threads in converter pool [default 100]\\n\"\n      << \"      max-queue-size       : max number of queued conversion jobs [default 1000]\\n\"\n      << std::endl\n      << \"  convert list                                     \\n\"\n      << \"    list conversion jobs                           \\n\"\n      << std::endl\n      << \"  convert clear                                    \\n\"\n      << \"    clear list of pending jobs                     \\n\"\n      << std::endl\n      << \"  convert file <identifier> <conversion>           \\n\"\n      << \"    schedule a file conversion                     \\n\"\n      << \"    <identifier> = fid|fxid|path                   \\n\"\n      << \"    <conversion> = <layout:replica> [space] [placement] [checksum]\\n\";\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_debug.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: com_proto_debug.cc\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/ICmdHelper.hh\"\n\nextern int com_debug(char*);\nvoid com_debug_help();\n\n//------------------------------------------------------------------------------\n//! Class DebugHelper\n//------------------------------------------------------------------------------\nclass DebugHelper : public ICmdHelper\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  DebugHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~DebugHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n};\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool DebugHelper::ParseCommand(const char* arg)\n{\n  eos::console::DebugProto* debugproto = mReq.mutable_debug();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  std::string token;\n\n  if (!tokenizer.NextToken(token)) {\n    return false;\n  }\n\n  if (token == \"get\") {\n    eos::console::DebugProto_GetProto* get = debugproto->mutable_get();\n    get->set_placeholder(true);\n  } else if (token == \"this\") {\n    global_debug = !global_debug;\n    gGlobalOpts.mDebug = global_debug;\n    fprintf(stdout, \"info: toggling shell debugmode to debug=%d\\n\", global_debug);\n    mIsLocal = true;\n  } else {\n    // token should be one of [debug info warning notice err crit alert emerg]\n    eos::console::DebugProto_SetProto* set = debugproto->mutable_set();\n    set->set_debuglevel(token);\n\n    if (tokenizer.NextToken(token)) {\n      if (token == \"--filter\") {\n        if (!tokenizer.NextToken(token)) {\n          return false;\n        }\n\n        set->set_filter(token);\n      } else {\n        set->set_nodename(token);\n\n        if (tokenizer.NextToken(token)) {\n          if (token != \"--filter\") {\n            return false;\n          } else {\n            if (!tokenizer.NextToken(token)) {\n              return false;\n            }\n\n            set->set_filter(token);\n          }\n        }\n      }\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Debug CLI\n//------------------------------------------------------------------------------\nint\ncom_protodebug(char* arg)\n{\n  if (wants_help(arg)) {\n    com_debug_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  DebugHelper debug(gGlobalOpts);\n\n  if (!debug.ParseCommand(arg)) {\n    com_debug_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = debug.Execute();\n  return global_retc;\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_debug_help()\n{\n  std::ostringstream oss;\n  oss\n      << \" usage:\\n\"\n      << \"debug get|this|<level> [node-queue] [--filter <unitlist>]\\n\"\n      << \"'[eos] debug ...' allows to get or set the verbosity of the EOS log files in MGM and FST services.\\n\"\n      << std::endl\n      << \"Options:\\n\"\n      << std::endl\n      << \"debug get : retrieve the current log level for the mgm and fsts node-queue\\n\"\n      << std::endl\n      << \"debug this : toggle EOS shell debug mode\\n\"\n      << std::endl\n      << \"debug  <level> [--filter <unitlist>] : set the MGM where the console is connected to into debug level <level>\\n\"\n      << std::endl\n      << \"debug  <level> <node-queue> [--filter <unitlist>] : set the <node-queue> into debug level <level>.\\n\"\n      << \"\\t - <node-queue> are internal EOS names e.g. '/eos/<hostname>:<port>/fst'\\n\"\n      << \"\\t - <unitlist> is a comma separated list of strings of software units which should be filtered out in the message log!\\n\"\n      << std::endl\n      << \"The default filter list is:\\n\"\n      << \"'Process,AddQuota,Update,UpdateHint,UpdateQuotaStatus,SetConfigValue,Deletion,GetQuota,PrintOut,RegisterNode,SharedHash,listenFsChange,placeNewReplicas,\"\n      << \"placeNewReplicasOneGroup,accessReplicas,accessReplicasOneGroup,accessHeadReplicaMultipleGroup,updateTreeInfo,updateAtomicPenalties,updateFastStructures,work'.\\n\"\n      << std::endl\n      << \"The allowed debug levels are:\\n\"\n      << \"debug,info,warning,notice,err,crit,alert,emerg\\n\"\n      << std::endl\n      << \"Examples:\\n\"\n      << \"\\t debug info \\\"*\\\"                     set MGM & all FSTs 'info' log level\\n\"\n      << std::endl\n      << \"\\t debug err hostname:port              set FST indicated by the hostname and port to 'info' log level\\n\"\n      << std::endl\n      << \"\\t debug crit                           set MGM to 'crit' log level\\n\"\n      << std::endl\n      << \"\\t debug debug --filter MgmOfsMessage   set MGM to 'debug' log level and filter only messages coming from unit 'MgmOfsMessage'.\\n\"\n      << std::endl;\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_devices.cc",
    "content": "//------------------------------------------------------------------------------\n// File: com_proto_devices.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringTokenizer.hh\"\n#include \"common/Path.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/ICmdHelper.hh\"\n\nvoid com_devices_help();\n\n//------------------------------------------------------------------------------\n//! Class DevicesHelper\n//------------------------------------------------------------------------------\nclass DevicesHelper: public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  DevicesHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {\n    mIsAdmin = true;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~DevicesHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n};\n\nbool\nDevicesHelper::ParseCommand(const char* arg)\n{\n  XrdOucString option;\n  XrdOucString token;\n  eos::console::DevicesProto* devices = mReq.mutable_devices();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n\n  if (!tokenizer.NextToken(token)) {\n    return false;\n  }\n\n  eos::console::DevicesProto_LsProto* ls = devices->mutable_ls();\n  ls->set_outformat(eos::console::DevicesProto_LsProto::NONE);\n\n  if (token == \"ls\") {\n    do {\n      tokenizer.NextToken(token);\n      if (!token.length()) {\n\treturn true;\n      }\n      if ( token == \"-l\") {\n\tls->set_outformat(eos::console::DevicesProto_LsProto::LISTING);\n      } else if (token == \"-m\") {\n\tls->set_outformat(eos::console::DevicesProto_LsProto::MONITORING);\n      } else if (token == \"--refresh\") {\n\tls->set_refresh(true);\n      } else {\n\treturn false;\n      }\n    } while (token.length());\n  } else {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Devices command entry point\n//------------------------------------------------------------------------------\nint com_proto_devices(char* arg)\n{\n  if (wants_help(arg)) {\n    com_devices_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  DevicesHelper devices(gGlobalOpts);\n\n  if (!devices.ParseCommand(arg)) {\n    com_devices_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = devices.Execute(true, true);\n\n  return global_retc;\n}\n\nvoid com_devices_help()\n{\n  std::ostringstream oss;\n  oss << \"Usage: devices ls [-l] [-m] [--refresh]\"\n      << std::endl;\n  oss << \"                                       : without option prints statistics per space of all storage devices used based on S.M.A.R.T information\\n\";\n  oss << \"                                    -l : prints S.M.A.R.T information for each configured filesystem\\n\";\n  oss << \"                                    -m : print montiroing output format (key=val)\";\n  oss << \"                             --refresh : forces to reparse the current available S.M.A.R.T information and output this\\n\";\n  oss << \"\\n\";\n  oss << \"                                  JSON : to retrieve JSON output, use 'eos --json devices ls' !\\n\";\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_df.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: com_proto_df.cc\n// @author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/ICmdHelper.hh\"\n\nextern int com_df(char*);\nvoid com_df_help();\n\n//------------------------------------------------------------------------------\n//! Class DfHelper\n//------------------------------------------------------------------------------\nclass DfHelper : public ICmdHelper\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  DfHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~DfHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n};\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool DfHelper::ParseCommand(const char* arg)\n{\n  eos::console::DfProto* dfproto = mReq.mutable_df();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  std::string token;\n\n  dfproto->set_si(true);\n  dfproto->set_readable(true);\n\n  if (!tokenizer.NextToken(token)) {\n    return true;\n  }\n\n  if (token == \"-m\") {\n    dfproto->set_monitoring(true);\n    dfproto->set_readable(false);\n  } else {\n    if (token == \"-H\") {\n      dfproto->set_si(false);\n      dfproto->set_readable(true);\n    } else {\n      if (token == \"-b\") {\n\tdfproto->set_si(false);\n\tdfproto->set_readable(false);\n      } else {\n\tif (token.substr(0,1) != \"/\") {\n\t  return false;\n\t}\n      }\n    }\n  }\n\n  std::string path = token;\n  if (tokenizer.NextToken(token)) {\n    if (token.substr(0,1) == \"-\") {\n      return false;\n    }\n    if (token.substr(0,1) != \"/\") {\n      return false;\n    }\n    path = token;\n  }\n\n  if (tokenizer.NextToken(token)) {\n    return false;\n  }\n  dfproto->set_path(path);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Df CLI\n//------------------------------------------------------------------------------\nint\ncom_protodf(char* arg)\n{\n  if (wants_help(arg)) {\n    com_df_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  DfHelper df(gGlobalOpts);\n\n  if (!df.ParseCommand(arg)) {\n    com_df_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = df.Execute();\n\n  return global_retc;\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_df_help()\n{\n  std::ostringstream oss;\n  oss\n      << \" usage:\\n\"\n      << \"df [-m|-H|-b] [path]\\n\"\n      << \"'[eos] df ...' print unix like 'df' information (1024 base)\\n\"\n      << std::endl\n      << \"Options:\\n\"\n      << std::endl\n      << \"-m : print in monitoring format\\n\"\n      << \"-H : print human readable in units of 1000\\n\"\n      << \"-b : print raw bytes/number values\\n\"\n      << std::endl;\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_find.cc",
    "content": "// ----------------------------------------------------------------------\n// @file: com_proto_find.cc\n// @author: Fabio Luchetti - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/NewfindHelper.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringConversion.hh\"\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n/*----------------------------------------------------------------------------*/\n\nextern void com_find_help();\n\nint\ncom_proto_find(char* arg)\n{\n  if (wants_help(arg)) {\n    com_find_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  NewfindHelper find(gGlobalOpts);\n  // Handle differently if it's an xroot, file or as3 path\n  std::string argStr(arg);\n  auto xrootAt = argStr.rfind(\"root://\");\n  auto fileAt = argStr.rfind(\"file:\");\n  auto as3At = argStr.rfind(\"as3:\");\n\n  if (xrootAt != std::string::npos) {\n    auto path = argStr.substr(xrootAt);\n    // remove \" from the path\n    path.erase(std::remove(path.begin(), path.end(), '\"'), path.end());\n    global_retc = find.FindXroot(path);\n    return global_retc;\n  } else if (fileAt != std::string::npos) {\n    auto path = argStr.substr(fileAt);\n    // remove \" from the path\n    path.erase(std::remove(path.begin(), path.end(), '\"'), path.end());\n    global_retc = find.FindXroot(path);\n    return global_retc;\n  } else if (as3At != std::string::npos) {\n    auto path = argStr.substr(as3At);\n    // remove \" from the path\n    path.erase(std::remove(path.begin(), path.end(), '\"'), path.end());\n    global_retc = find.FindAs3(path);\n    return global_retc;\n  }\n\n  if (!find.ParseCommand(arg)) {\n    com_find_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = find.Execute();\n  return global_retc;\n}\n\nvoid com_find_help()\n{\n  std::ostringstream oss;\n  oss\n      << \" usage\\n\"\n      << \"find/newfind [OPTIONS] <path> : find files and directories\\n\"\n      << \"OPTIONS can be filters, actions, or output modifiers for the found items\\n\"\n      << \"Filters: [--maxdepth <n>] [--name <pattern>] [-f] [-d] [-0] [-g] [-uid <n>] [-nuid <n>]\\n\"\n      << \"         [-gid <n>] [-ngid <n>] [-flag <n>] [-nflag <n>] [--ctime|--mtime +<n>|-<n>]\\n\"\n      << \"         [-x <key>=<val>] [--faultyacl] [--stripediff]\\n\"\n      << \"\\t       --maxdepth <n> : descend only <n> levels\\n\"\n      << \"\\t     --name <pattern> : find by name, filtering by 'egrep' style regex match\\n\"\n      << \"\\t                -f,-d : find only files(-f) or directories (-d) in <path>\\n\"\n      << \"\\t                   -0 : find 0-size files only\\n\"\n      << \"\\t                   -g : find files with mixed scheduling groups\\n\"\n      << \"\\t   -uid <n>,-nuid <n> : find entries owned / not owned by a given user id number\\n\"\n      << \"\\t   -gid <n>,-ngid <n> : find entries owned / not owned by a given group id number\\n\"\n      << \"\\t -flag <n>,-nflag <n> : find entries with / without specified UNIX access flag, e.g. 755\\n\"\n      << \"\\t   --ctime <+n>, <-n> : find files with ctime older (+n) or younger (-n) than <n> days\\n\"\n      << \"\\t   --mtime <+n>, <-n> : find files with mtime older (+n) or younger (-n) than <n> days\\n\"\n      << \"\\t       -x <key>=<val> : find entries with <key>=<val>\\n\"\n      << \"\\t          --faultyacl : find files and directories with illegal ACLs\\n\"\n      << \"\\t         --stripediff : find files that do not have the nominal number of stripes(replicas)\\n\"\n      << \"\\t  --skip-version-dirs : skip version directories in the traversed hierarchy\\n\"\n      << \"\\n\"\n      << \"Actions: [-b] [--layoutstripes <n>] [--purge <n> ] [--fileinfo] [--format formatlist] [--cache] [--du]\\n\"\n      << \"\\t                   -b : query the server balance of the files found\\n\"\n      << \"\\t  --layoutstripes <n> : apply new layout with <n> stripes to the files found\\n\"\n      << \"\\t --purge <n> | atomic : remove versioned files keeping <n> versions (use --purge 0 to remove all old versions)\\n\"\n      << \"\\t                        To apply the settings of the extended attribute definition use --purge -1\\n\"\n      << \"\\t                        To remove all atomic upload left-overs older than a day use --purge atomic\\n\"\n      << \"\\t         [--fileinfo] : invoke `eos fileinfo` on the entry\\n\"\n      << \"\\t              --count : print aggregated number of file and directory including the search path\\n\"\n      << \"\\t         --childcount : print the number of children in each directory\\n\"\n      << \"\\t          --treecount : print the aggregated number of filesand directory children excluding the search path\\n\"\n      << \"\\t             --format : print with the given komma separated format list, redundant switches like\\n\"\n      << \"\\t                        --uid --checksum, which can be specified via the format are automatically disabled.\\n\"\n      << \"\\t                        Possible values for format tags are: uid,gid,size,checksum,checksumtype,etag,fxid,\\n\"\n      << \"\\t                        pxid,cxid,fid,pid,cid,atime,btime,ctime,mtime,type,mode,files,link,directories,\\n\"\n      << \"\\t                        attr.*,attr.<name> e.g. attr.sys.acl !\\n\"\n      << \"\\t              --cache : store all found entries in the in-memory namespace cache\\n\"\n      << \"\\t                 --du : create du-style output\\n\"\n      << \"\\n\"\n      << \"Output mode: [--xurl] [-p <key>] [--nrep] [--nunlink] [--size] [--online] [--hosts]\\n\"\n      << \"             [--partition] [--fid] [--fs] [--checksum] [--ctime] [--mtime] [--uid] [--gid]\\n\"\n      << \"\\t                : print out the requested meta data as key value pairs\\n\"\n      << \"The <path> argument ca be:\\n\"\n      << \"\\t path=file:...  :  do a find in the local file system (options ignored) - 'file:' is the current working directory\\n\"\n      << \"\\t path=root:...  :  do a find on a plain XRootD server (options ignored) - does not work on native XRootD clusters\\n\"\n      << \"\\t path=as3:...   :  do a find on an S3 bucket\\n\"\n      << \"\\t path=...       :  all other paths are considered to be EOS paths!\\n\";\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_fs.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file com_proto_fs.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/FsHelper.hh\"\n#include <algorithm>\n#include <sstream>\n\nvoid com_fs_help();\n\n//------------------------------------------------------------------------------\n// Fs command entry point\n//------------------------------------------------------------------------------\nint com_protofs(char* arg)\n{\n  if (wants_help(arg)) {\n    com_fs_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  FsHelper fs(gGlobalOpts);\n\n  if (!fs.ParseCommand(arg)) {\n    com_fs_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  if (fs.NeedsConfirmation() && !fs.ConfirmOperation()) {\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = fs.Execute();\n  return global_retc;\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_fs_help()\n{\n  std::ostringstream oss;\n  oss << \"Usage: fs add|boot|config|dropdeletion|dropghosts|dropfiles|dumpmd|ls|mv|rm|status [OPTIONS]\"\n      << std::endl\n      << \"  Options:\" << std::endl\n      << \"  fs add [-m|--manual <fsid>] <uuid> <node-queue>|<host>[:<port>] \"\n      << \"<mountpoint> [<space_info> [<status> [<sharedfs>]]]\" << std::endl\n      << \"    add and assign a filesystem based on the unique identifier of the disk <uuid>\"\n      << std::endl\n      << \"    -m|--manual  : add with user specified <fsid> and <space>\"\n      << std::endl\n      << \"    <fsid>       : numeric filesystem id 1...65535\" << std::endl\n      << \"    <uuid>       : unique string identifying current filesystem\" <<\n      std::endl\n      << \"    <node-queue> : internal EOS identifier for a node e.g /eos/<host>:<port>/fst\"\n      << std::endl\n      << \"                   it is preferable to use the host:port syntax\" <<\n      std::endl\n      << \"    <host>       : FQDN of host where filesystem is mounter\" << std::endl\n      << \"    <port>       : FST XRootD port number [usually 1095]\" << std::endl\n      << \"    <mountponit> : local path of the mounted filesystem e.g /data/\" <<\n      std::endl\n      << \"    <space_info> : space or space.group location where to insert the filesystem,\\n\"\n      << \"                   if nothing is specified then space \\\"default\\\" is used. E.g:\\n\"\n      << \"                   default, default.7, ssd.3, spare\"\n      << std::endl\n      << \"    <status>     : set filesystem status after insertion e.g off|rw|ro|empty etc.\"\n      << std::endl\n      << \"    <sharedfs>   : set the name of a shared filesystem\"\n      << std::endl\n      << std::endl\n      << \"  fs boot <fsid>|<uuid>|<node-queue>|* [--syncdisk|--syncmgm]\" << std::endl\n      << \"    boot - filesystem identified by <fsid> or <uuid>\\n\"\n      << \"         - all filesystems on a node identified by <node-queue>\\n\"\n      << \"         - all filesystems registered\\n\"\n      << \"    --syncdisk   : do disk resynchronization during the booting\\n\"\n      << \"    --syncmgm    : do MGM and disk resynchronization during the booting\\n\"\n      << std::endl\n      << \"  fs clone <sourceid> <targetid>\" << std::endl\n      << \"    replicate files from the source to the target filesystem\"\n      << std::endl\n      << \"    <sourceid>   : id of the source filesystem\" << std::endl\n      << \"    <targetid>   : id of the target filesystem\" << std::endl\n      << std::endl\n      << std::endl\n      << \"  fs compare <sourceid> <targetid>\" << std::endl\n      << \"    compares and reports which files are present on one filesystem and not on the other\"\n      << std::endl\n      << \"    <sourceid>   : id of the source filesystem\" << std::endl\n      << \"    <targetid>   : id of the target filesystem\" << std::endl\n      << std::endl\n      << std::endl\n      << \"  fs config <fsid> <key>=<value>\" << std::endl\n      << \"    configure the filesystem parameter, where <key> and <value> can be:\" <<\n      std::endl\n      << \"    configstatus=rw|wo|ro|drain|draindead|off|empty [--comment \\\"<comment>\\\"]\"\n      << std::endl\n      << \"      rw        : set filesystem in read-write mode\" << std::endl\n      << \"      wo        : set filesystem in write-only mode\" << std::endl\n      << \"      ro        : set filesystem in read-only mode\" << std::endl\n      << \"      drain     : set filesystem in drain mode\" << std::endl\n      << \"      draindead : set filesystem in draindead mode, unusable for any read\"\n      << std::endl\n      << \"      off       : disable filesystem\" << std::endl\n      << \"      empty     : empty filesystem, possible only if there are no\"\n      << std::endl\n      << \"                  more files stored on it\" << std::endl\n      << \"      --comment : pass a reason for the status change\" << std::endl\n      << \"    headroom=<size>\" << std::endl\n      << \"      headroom to keep per filesystem. <size> can be (>0)[BMGT]\"\n      << std::endl\n      << \"    scaninterval=<seconds>\\n\"\n      << \"      entry rescan interval (default 7 days), 0 disables scanning\"\n      << std::endl\n      << \"    scan_rain_interval=<seconds>\\n\"\n      << \"      rain entry rescan interval (default 4 weeks), 0 disables scanning\"\n      << std::endl\n      << \"    scanrate=<MB/s>\" << std::endl\n      << \"      maximum IO scan rate per filesystem\"\n      << std::endl\n      << \"    scan_disk_interval=<seconds>\\n\"\n      << \"      disk consistency thread scan interval (default 4h)\"\n      << std::endl\n      << \"    scan_ns_interval=<seconds>\\n\"\n      << \"      namespace consistency thread scan interval (default 3 days)\"\n      << std::endl\n      << \"    scan_ns_rate=<entries/s>\\n\"\n      << \"      maximum scan rate of ns entries for the NS consistency. This\\n\"\n      << \"      is bound by the maxium number of IOPS per disk.\"\n      << std::endl\n      << \"    fsck_refresh_interval=<sec>\\n\"\n      << \"       time interval after which fsck inconsistencies are refreshed\"\n      << std::endl\n      << \"    scan_altxs_rate=<entries/s>\\n\"\n      << \"       maximum scan rate of ns entries for checking if alternative\\n\"\n      << \"       checksums have been computed.\"\n      << std::endl\n      << \"    scan_altxs_interval=<sec>\\n\"\n      << \"       alternative checksum computing interval (default 30 days), 0 disables scanning\"\n      << std::endl\n      << \"    altxs_sync=0|1\\n\"\n      << \"       enable synchronization of alternative checksums settings from namespace\"\n      << std::endl\n      << \"    altxs_sync_interval=<seconds>\\n\"\n      << \"       time interval after which synchronization of alternative checksums\\n\"\n      << \"       settings are refreshed. If 0 it is synchronized only once (default 0)\"\n      << std::endl\n      << \"    graceperiod=<seconds>\\n\"\n      << \"      grace period before a filesystem with an operation error gets\\n\"\n      << \"      automatically drained\"\n      << std::endl\n      << \"    drainperiod=<seconds>\\n\"\n      << \"      period a drain job is allowed to finish the drain procedure\"\n      << std::endl\n      << \"    proxygroup=<proxy_grp_name>\" << std::endl\n      << \"      schedule a proxy for the current filesystem by taking it from\"\n      << std::endl\n      << \"      the given proxy group. The special value \\\"<none>\\\" is the\"\n      << std::endl\n      << \"      same as no value and means no proxy scheduling\" << std::endl\n      << \"    filestickyproxydepth=<depth>\" << std::endl\n      << \"      depth of the subtree to be considered for file-stickyness. A\"\n      << std::endl\n      << \"      negative value means no file-stickyness\" << std::endl\n      << \"    forcegeotag=<geotag>\" << std::endl\n      << \"      set the filesystem's geotag, overriding the host geotag value.\"\n      << std::endl\n      << \"      The special value \\\"<none>\\\" is the same as no value and means\"\n      << std::endl\n      << \"      no override\" << std::endl\n      << \"    s3credentials=<accesskey>:<secretkey>\" << std::endl\n      << \"      the access and secret key pair used to authenticate\" << std::endl\n      << \"      with the S3 storage endpoint\" << std::endl\n      << \"    sharedfs=<name>\" << std::endl\n      << \"      the sharedfs this filesystem is to be assigned to. To unlabel set the sharedfs name to 'none' !\"\n      << std::endl\n      << std::endl\n      << \"  fs dropdeletion <fsid> \" << std::endl\n      << \"    drop all pending deletions on the filesystem\" << std::endl\n      << std::endl\n      << \"  fs dropghosts <fsid> [--fxid fid1 [fid2] ...]\\n\"\n      << \"    drop file ids (hex) without a corresponding metadata object in\\n\"\n      << \"    the namespace that are still accounted in the file system view.\\n\"\n      << \"    If no fxid is provided then all fids on the file system are checked.\\n\"\n      << std::endl\n      << \"  fs dropfiles <fsid> [-f]\" << std::endl\n      << \"    drop all files on the filesystem\" << std::endl\n      << \"    -f : unlink/remove files from the namespace (you have to remove\"\n      << std::endl\n      << \"         the files from disk)\" << std::endl\n      << std::endl\n      << \"  fs dumpmd <fsid> [--count] [--fid|--fxid|--path] [--size] [-m|-s]\\n\"\n      << \"    dump/count file metadata entries on the given filesystem, only if less than 100k\\n\"\n      << \"    --count : print only the number of entries on the file system [default]\\n\"\n      << \"    --fid   : dump only the file ids in decimal\\n\"\n      << \"    --fxid  : dump only the file ids in hexadecimal\\n\"\n      << \"    --path  : dump only the file paths\\n\"\n      << \"    --size  : dump only the file sizes\\n\"\n      << \"    -m      : print full metadata record in env format\\n\"\n      << \"    -s      : silent mode (will keep an internal reference)\\n\"\n      << std::endl\n      << \"  fs ls [-m|-l|-e|--io|--fsck|[-d|--drain]|-D|-F] [-s] [-b|--brief] [[matchlist]]\"\n      << std::endl\n      << \"    list filesystems using the default output format\" << std::endl\n      << \"    -m         : monitoring format\" << std::endl\n      << \"    -b|--brief : display hostnames without domain names\" << std::endl\n      << \"    -l         : display parameters in long format\" << std::endl\n      << \"    -e         : display filesystems in error state\" << std::endl\n      << \"    --io       : IO output format\" << std::endl\n      << \"    --fsck     : display filesystem check statistics\" << std::endl\n      << \"    -d|--drain : display filesystems in drain or draindead status\"\n      << std::endl\n      << \"                 along with drain progress and statistics\" << std::endl\n      << \"    -D|--drain_jobs : \" << std::endl\n      << \"                 display ongoing drain transfers, matchlist needs to be an integer\"\n      << std::endl\n      << \"                 representing the drain file system id\" << std::endl\n      << \"    -F|--failed_drain_jobs : \" << std::endl\n      << \"                 display failed drain transfers, matchlist needs to be an integer\"\n      << std::endl\n      << \"                 representing the drain file system id. This will only display\"\n      << std::endl\n      << \"                 information while the draining is ongoing\" << std::endl\n      << \"    -s         : silent mode\" << std::endl\n      << \"    [matchlist]\" << std::endl\n      << \"       -> can be the name of a space or a comma separated list of\"\n      << std::endl\n      << \"          spaces e.g 'default,spare'\" << std::endl\n      << \"       -> can be a grep style list to filter certain filesystems\"\n      << std::endl\n      << \"          e.g. 'fs ls -d drain,bootfailure'\" << std::endl\n      << \"       -> can be a combination of space filter and grep e.g.\"\n      << std::endl\n      << \"          'fs ls -l default,drain,bootfailure'\" << std::endl\n      << std::endl\n      << \"  fs mv [--force] <src_fsid|src_grp|src_space> <dst_grp|dst_space|node:port>\"\n      <<\n      std::endl\n      << \"    move filesystem(s) in different scheduling group, space or node\"\n      << std::endl\n      << \"    --force   : force mode - allows to move non-empty filesystems bypassing group \"\n      << std::endl\n      << \"                and node constraints\" << std::endl\n      << \"    src_fsid  : source filesystem id\" << std::endl\n      << \"    src_grp   : all filesystems from scheduling group are moved\"\n      << std::endl\n      << \"    src_space : all filesystems from space are moved\" << std::endl\n      << \"    dst_grp   : destination scheduling group\" << std::endl\n      << \"    dst_space : destination space - best match scheduling group\" <<\n      std::endl\n      << \"    node:port : destination node, useful to serve a disk from a different\\n\"\n      << \"                FST on the same node or failover an FST serving data from\\n\"\n      << \"                a shared filesystem backend\\n\"\n      << std::endl\n      << \"  fs rm <fsid>|<mnt>|<node-queue> <mnt>|<hostname> <mnt>\" << std::endl\n      << \"    remove filesystem by various identifiers, where <mnt> is the \"\n      << std::endl\n      << \"    mountpoint\" << std::endl\n      << std::endl\n      << \"  fs status [-r] [-l] <identifier>\" << std::endl\n      << \"    return all status variables of a filesystem and calculates\"\n      << std::endl\n      << \"    the risk of data loss if this filesystem is removed\" << std::endl\n      << \"    <identifier> can be: \" << std::endl\n      << \"       <fsid> : filesystem id\" << std::endl\n      << \"       [<host>] <mountpoint> : if host is not specified then it's\"\n      << std::endl\n      << \"       considered localhost\" << std::endl\n      << \"    -l : list all files which are at risk and offline files\"\n      << std::endl\n      << \"    -r : show risk analysis\" << std::endl\n      << std::endl\n      << \"  Examples: \" << std::endl\n      << \"  fs ls --io -> list all filesystems with IO statistics\" << std::endl\n      << \"  fs boot *  -> send boot request to all filesystems\" << std::endl\n      << \"  fs dumpmd 100 --path -> dump all logical path names on filesystem\"\n      << \" 100\" << std::endl\n      << \"  fs mv 100 default.0 -> move filesystem 100 to scheduling group\"\n      << \" default.0\" << std::endl;\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_fsck.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file com_fsck.cc\n//! @autor Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/FsckHelper.hh\"\n\nvoid com_fsck_help();\n\n//------------------------------------------------------------------------------\n// Fsck command entry point\n//------------------------------------------------------------------------------\nint com_proto_fsck(char* arg)\n{\n  if (wants_help(arg)) {\n    com_fsck_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  FsckHelper fsck(gGlobalOpts);\n\n  if (!fsck.ParseCommand(arg)) {\n    com_fsck_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  return fsck.Execute();\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_fsck_help()\n{\n  std::ostringstream oss;\n  oss << \"Usage: fsck [stat|config|report|repair]\\n\"\n      << \"    control and display file system check information\\n\"\n      << std::endl\n      << \"  fsck stat [-m]\\n\"\n      << \"    print summary of consistency checks\\n\"\n      << \"    -m         : print in monitoring format\\n\"\n      << std::endl\n      << \"  fsck config <key> <value>\\n\"\n      << \"    configure the fsck with the following possible options:\\n\"\n      << \"    collect-interval-min : collection interval in minutes [default 30]\\n\"\n      << \"    collect              : control error collection thread - on/off\\n\"\n      << \"    repair               : control error repair thread - on/off\\n\"\n      << \"    best-effort          : control best-effort repair mode - on/off\\n\"\n      << \"    repair-category      : specify error types that the repair thread will handle\\n\"\n      << \"                           e.g all, m_cx_diff, m_mem_sz_diff, d_cx_diff, d_mem_sz_diff,\\n\"\n      << \"                           unreg_n, rep_diff_n, rep_missing_n, blockxs_err, stripe_err\\n\"\n      << \"    max-queued-jobs      : maximum number of queued jobs\\n\"\n      << \"    max-thread-pool-size : maximum number of threads in the fsck pool\\n\"\n      << \"    show-dark-files      : on/off [default off] - might affect instance performance\\n\"\n      << \"    show-offline         : on/off [default off] - might affect instance performance\\n\"\n      << \"    show-no-replica      : on/off [default off] - might affect instance performance\\n\"\n      << std::endl\n      << \"  fsck report [-a] [-h] [-i] [-l] [-j|--json] [--error <tag1> <tag2> ...]\\n\"\n      << \"    report consistency check results, with the following options\\n\"\n      << \"    -a         : break down statistics per file system\" << std::endl\n      << \"    -i         : display file identifiers\" << std::endl\n      << \"    -l         : display logical file name\" << std::endl\n      << \"    -j|--json  : display in JSON output format\" << std::endl\n      << \"    --error    : display information about the following error tags\\n\"\n      << std::endl\n      << \"  fsck repair --fxid <val> [--fsid <val>] [--error <err_type>] [--async]\\n\"\n      << \"    repair the given file if there are any errors\\n\"\n      << \"    --fxid  : hexadecimal file identifier\\n\"\n      << \"    --fsid  : file system id used for collecting info\\n\"\n      << \"    --error : error type for given file system id e.g. m_cx_diff unreg_n etc\\n\"\n      << \"    --async : job queued and ran by the repair thread if enabled\\n\"\n      << std::endl\n      << \"  fsck clean_orphans [--fsid <val>] [--force-qdb-cleanup]\\n\"\n      << \"     clean orphans by removing the entries from disk and local\\n \"\n      << \"     database for all file systems or only for the given fsid.\\n\"\n      << \"     This operation is synchronous but the fsck output will be\\n\"\n      << \"     updated once the inconsistencies are refreshed.\\n\"\n      << \"     --force-qdb-cleanup : force remove orphan entries from qdb\\n\"\n      << std::endl;\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_group.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: com_proto_group.cc\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your token) any later version.                                   *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringTokenizer.hh\"\n#include \"common/Path.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/ICmdHelper.hh\"\n\nextern int com_group(char*);\nvoid com_group_help();\n\n//------------------------------------------------------------------------------\n//! Class GroupHelper\n//------------------------------------------------------------------------------\nclass GroupHelper : public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  GroupHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~GroupHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n};\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool GroupHelper::ParseCommand(const char* arg)\n{\n  eos::console::GroupProto* group = mReq.mutable_group();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  std::string token;\n\n  if (!tokenizer.NextToken(token)) {\n    return false;\n  }\n\n  // one of { ls, rm, set }\n  if (token == \"ls\") {\n    eos::console::GroupProto_LsProto* ls = group->mutable_ls();\n\n    while (tokenizer.NextToken(token)) {\n      if (token == \"-s\") {\n        mIsSilent = true;\n      } else if (token == \"-g\") {\n        if (!tokenizer.NextToken(token) ||\n            !eos::common::StringTokenizer::IsUnsignedNumber(token)) {\n          std::cerr << \"error: geodepth was not provided or it does not have \"\n                    << \"the correct value: geodepth should be a positive \"\n                    << \"integer\\n\";\n          return false;\n        }\n\n        try {\n          ls->set_outdepth(std::stoi(token));\n        } catch (const std::exception& e) {\n          std::cerr <<\n                    \"error: argument to geodepth (-g flag) needs to be a positive integer\\n\";\n          return false;\n        }\n      } else if (token == \"-b\" || token == \"--brief\") {\n        ls->set_outhost(true);\n      } else if (token == \"-m\") {\n        ls->set_outformat(eos::console::GroupProto_LsProto::MONITORING);\n      } else if (token == \"-l\") {\n        ls->set_outformat(eos::console::GroupProto_LsProto::LISTING);\n      } else if (token == \"--io\") {\n        ls->set_outformat(eos::console::GroupProto_LsProto::IOGROUP);\n      } else if (token == \"--IO\") {\n        ls->set_outformat(eos::console::GroupProto_LsProto::IOFS);\n      } else if (token.find('-') != 0) { // does not begin with \"-\"\n        ls->set_selection(token);\n      } else {\n        return false;\n      }\n    }\n  } else if (token == \"rm\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::GroupProto_RmProto* rm = group->mutable_rm();\n    rm->set_group(token);\n  } else if (token == \"set\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::GroupProto_SetProto* set = group->mutable_set();\n    set->set_group(token);\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"on\" || token == \"off\" || token == \"drain\") {\n      set->set_group_state(token);\n    } else {\n      return false;\n    }\n  } else { // no proper subcommand\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Group command entry point\n//------------------------------------------------------------------------------\nint com_protogroup(char* arg)\n{\n  if (wants_help(arg)) {\n    com_group_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  GroupHelper group(gGlobalOpts);\n\n  if (!group.ParseCommand(arg)) {\n    com_group_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = group.Execute();\n  return global_retc;\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_group_help()\n{\n  std::ostringstream oss;\n  oss\n      << \" usage:\\n\"\n      << std::endl\n      << \"group ls [-s] [-g <depth>] [-b|--brief] [-m|-l|--io] [<groups>] : list groups\\n\"\n      << \"\\t <groups> : list <groups> only, where <groups> is a substring match and can be a comma seperated list\\n\"\n      << \"\\t       -s : silent mode\\n\"\n      << \"\\t       -g : geo output - aggregate group information along the instance geotree down to <depth>\\n\"\n      << \"\\t       -b : brief output\\n\"\n      << \"\\t       -m : monitoring key=value output format\\n\"\n      << \"\\t       -l : long output - list also file systems after each group\\n\"\n      << \"\\t     --io : print IO statistics for the group\\n\"\n      << \"\\t     --IO : print IO statistics for each filesystem\\n\"\n      << std::endl\n      << \"group rm <group-name> : remove group\\n\"\n      << std::endl\n      << \"group set <group-name> on|drain|off : activate/drain/deactivate group\\n\"\n      << \"\\t  => when a group is (re-)enabled, the drain pull flag is recomputed for all filesystems within a group\\n\"\n      << \"\\t  => when a group is (re-)disabled, the drain pull flag is removed from all members in the group\\n\"\n      << \"\\t  => when a group is in drain, all the filesystems in the group will be drained to other groups\\n\"\n      << std::endl;\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_io.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: com_proto_io.cc\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your token) any later version.                                   *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/ICmdHelper.hh\"\n#include \"proto/Io.pb.h\"\n#include <common/CLI11.hpp>\n\nextern int com_io(char*);\n\nvoid com_io_help();\n\n//------------------------------------------------------------------------------\n//! Class IoHelper\n//------------------------------------------------------------------------------\nclass IoHelper : public ICmdHelper {\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  IoHelper(const GlobalOptions& opts)\n      : ICmdHelper(opts)\n  {\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~IoHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n\nprivate:\n  static bool ParseTrafficShapingCommand(eos::common::StringTokenizer& tokenizer,\n                                         eos::console::IoProto* io);\n};\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool\nIoHelper::ParseCommand(const char* arg)\n{\n  eos::console::IoProto* io = mReq.mutable_io();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  std::string token;\n\n  if (!tokenizer.NextToken(token)) {\n    return false;\n  }\n\n  // one of { stat, ns, report, enable, disable }\n  if (token == \"stat\") {\n    eos::console::IoProto_StatProto* stat = io->mutable_stat();\n\n    while (tokenizer.NextToken(token)) {\n      if (token == \"-a\") {\n        stat->set_details(true);\n      } else if (token == \"-m\") {\n        stat->set_monitoring(true);\n      } else if (token == \"-n\") {\n        stat->set_numerical(true);\n      } else if (token == \"-t\") {\n        stat->set_top(true);\n      } else if (token == \"-d\") {\n        stat->set_domain(true);\n      } else if (token == \"-x\") {\n        stat->set_apps(true);\n      } else if (token == \"--ss\") {\n        stat->set_sample_stat(true);\n      } else if (token == \"--sa\") {\n        if (!(tokenizer.NextToken(token))) {\n          continue;\n        }\n        // Parse <time_ago>\n        bool not_numeric = (token.find_first_not_of(\"0123456789\") != std::string::npos);\n\n        if (not_numeric) {\n          std::cerr << \"error: --sa value needs to be numeric (seconds ago)\" << std::endl;\n          return false;\n        }\n        try {\n          uint64_t tago = std::stoull(token);\n          stat->set_time_ago(tago);\n        } catch (const std::exception& e) {\n          std::cerr << \"error: --sa value needs to be numeric (seconds ago)\" << std::endl;\n          return false;\n        }\n      } else if (token == \"--si\") {\n        if (!(tokenizer.NextToken(token))) {\n          continue;\n        }\n        // Parse <time_interval>\n        bool not_numeric = (token.find_first_not_of(\"0123456789\") != std::string::npos);\n\n        if (not_numeric) {\n          std::cerr << \"error: --si value needs to be numeric (interval in seconds)\"\n                    << std::endl;\n          return false;\n        } else {\n          try {\n            uint64_t tint = std::stoull(token);\n            stat->set_time_interval(tint);\n          } catch (const std::exception& e) {\n            std::cerr << \"error: --si value needs to be numeric (interval in seconds)\"\n                      << std::endl;\n            return false;\n          }\n        }\n\n      } else if (token == \"-l\") {\n        stat->set_summary(true);\n      } else {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  if (token == \"ns\") {\n    eos::console::IoProto_NsProto* ns = io->mutable_ns();\n\n    while (tokenizer.NextToken(token)) {\n      if (token == \"-m\") {\n        ns->set_monitoring(true);\n      } else if (token == \"-b\") {\n        ns->set_rank_by_byte(true);\n      } else if (token == \"-n\") {\n        ns->set_rank_by_access(true);\n      } else if (token == \"-w\") {\n        ns->set_last_week(true);\n      } else if (token == \"-f\") {\n        ns->set_hotfiles(true);\n        /* (token == \"-100\" || token == \"-1000\" || token == \"-10000\" || token == \"-a\" ) */\n      } else if (token == \"-100\") {\n        ns->set_count(eos::console::IoProto_NsProto::ONEHUNDRED);\n      } else if (token == \"-1000\") {\n        ns->set_count(eos::console::IoProto_NsProto::ONETHOUSAND);\n      } else if (token == \"-10000\") {\n        ns->set_count(eos::console::IoProto_NsProto::TENTHOUSAND);\n      } else if (token == \"-a\") {\n        ns->set_count(eos::console::IoProto_NsProto::ALL);\n      } else {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  if (token == \"report\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::IoProto_ReportProto* report = io->mutable_report();\n    report->set_path(token);\n    return true;\n  }\n\n  if (token == \"enable\" || token == \"disable\") {\n    eos::console::IoProto_EnableProto* enable = io->mutable_enable();\n    enable->set_switchx(token == \"enable\");\n\n    while (tokenizer.NextToken(token)) {\n      if (token == \"-r\") {\n        enable->set_reports(true);\n      } else if (token == \"-p\") {\n        enable->set_popularity(true);\n      } else if (token == \"-n\") {\n        enable->set_namespacex(true);\n      } else if (token == \"--udp\") {\n        if (!(tokenizer.NextToken(token)) || (token.find('-') == 0)) {\n          return false;\n        }\n        enable->set_upd_address(token);\n\n      } else {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  if (token == \"shaping\") {\n    return ParseTrafficShapingCommand(tokenizer, io);\n  }\n\n  return false; // Unknown command\n}\n\n//------------------------------------------------------------------------------\n// io command entry point\n//------------------------------------------------------------------------------\nint\ncom_protoio(char* arg)\n{\n  if (wants_help(arg)) {\n    com_io_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  IoHelper io(gGlobalOpts);\n\n  if (!io.ParseCommand(arg)) {\n    com_io_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = io.Execute();\n  return global_retc;\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid\ncom_io_help()\n{\n  std::ostringstream oss;\n  oss << \" usage:\\n\"\n      << std::endl\n      << \"io stat [-l] [-a] [-m] [-n] [-t] [-d] [-x] [--ss] [--sa] [--si] : print io \"\n         \"statistics\\n\"\n      << \"\\t  -l : show summary information (this is the default if -a,-t,-d,-x is not \"\n         \"selected)\\n\"\n      << \"\\t  -a : break down by uid/gid\\n\"\n      << \"\\t  -m : print in <key>=<val> monitoring format\\n\"\n      << \"\\t  -n : print numerical uid/gids\\n\"\n      << \"\\t  -t : print top user stats\\n\"\n      << \"\\t  -d : break down by domains\\n\"\n      << \"\\t  -x : break down by application\\n\"\n      << \"\\t  --ss : show table with transfer sample statistics\\n\"\n      << \"\\t  --sa : start collection of statistics given number of seconds ago\\n\"\n      << \"\\t  --si : collect statistics over given interval of seconds\\n\"\n      << \"\\t  Note: this tool shows data for finished transfers only (using storage node \"\n         \"reports)\\n\"\n      << \"\\t  Example: asking for data of finished transfers which were transferred \"\n         \"during interval [now - 180s, now - \"\n         \"120s]:\\n\"\n      << \"\\t           eos io stat -x --sa 120 --si 60\\n\"\n      << std::endl\n      << \"io enable [-r] [-p] [-n] [--udp <address>] : enable collection of io \"\n         \"statistics\\n\"\n      << \"\\t         no arg. : start the colleciton thread\\n\"\n      << \"\\t              -r : enable collection of io reports\\n\"\n      << \"\\t              -p : enable popularity accounting\\n\"\n      << \"\\t              -n : enable report namespace\\n\"\n      << \"\\t --udp <address> : add a UDP message target for io UDP packtes (the \"\n         \"configured targets are shown by 'io \"\n         \"stat -l)\\n\"\n      << std::endl\n      << \"io disable [-r] [-p] [-n] [--udp <address>] : disable collection of io \"\n         \"statistics\\n\"\n      << \"\\t         no arg. : stop the collection thread\\n\"\n      << \"\\t              -r : disable collection of io reports\\n\"\n      << \"\\t              -p : disable popularity accounting\\n\"\n      << \"\\t              -n : disable report namespace\\n\"\n      << \"\\t --udp <address> : remove a UDP message target for io UDP packtes (the \"\n         \"configured targets are shown by 'io \"\n         \"stat -l)\\n\"\n      << std::endl\n      << \"io report <path> : show contents of report namespace for <path>\\n\"\n      << std::endl\n      << \"io ns [-a] [-n] [-b] [-100|-1000|-10000] [-w] [-f] : show namespace IO ranking \"\n         \"(popularity)\\n\"\n      << \"\\t      -a :  don't limit the output list\\n\"\n      << \"\\t      -n :  show ranking by number of accesses\\n\"\n      << \"\\t      -b :  show ranking by number of bytes\\n\"\n      << \"\\t    -100 :  show the first 100 in the ranking\\n\"\n      << \"\\t   -1000 :  show the first 1000 in the ranking\\n\"\n      << \"\\t  -10000 :  show the first 10000 in the ranking\\n\"\n      << \"\\t      -w :  show history for the last 7 days\\n\"\n      << \"\\t      -f :  show the 'hotfiles' which are the files with highest number of \"\n         \"present file opens\\n\"\n      << std::endl\n      << \"io shaping [subcommand] [options...] : interact with the Traffic Shaping \"\n         \"engine\\n\"\n      << std::endl\n      << \"   SUBCOMMANDS\\n\"\n      << \"     ls [options...] : view real-time IO rates and shaping status\\n\"\n      << \"\\t   --apps   : show rates by application\\n\"\n      << \"\\t   --users  : show rates by user (uid)\\n\"\n      << \"\\t   --groups : show rates by group (gid)\\n\"\n      << \"\\t   --nodes  : show rates by storage node (FST)\\n\"\n      << \"\\t   --json   : output in JSON format\\n\"\n      << std::endl\n      << \"     enable  : globally enable traffic shaping\\n\"\n      << \"     disable : globally disable traffic shaping\\n\"\n      << std::endl\n      << \"     policy [action] [options...] : manage shaping limits and reservations\\n\"\n      << \"\\t   action 'ls' : list configured policies\\n\"\n      << \"\\t     usage: policy ls [options...]\\n\"\n      << \"\\t       --apps       : filter by applications\\n\"\n      << \"\\t       --users      : filter by users (uid)\\n\"\n      << \"\\t       --groups     : filter by groups (gid)\\n\"\n      << \"\\t       --controller : show ephemeral controller limits\\n\"\n      << \"\\t       --json       : output in JSON format\\n\"\n      << \"\\t   action 'set' : configure a new policy or modify an existing one\\n\"\n      << \"\\t     usage: policy set <identity> [parameters...] [--enable|--disable]\\n\"\n      << \"\\t       <identity>   : --app <name> | --uid <id> | --gid <id>\\n\"\n      << \"\\t       [parameters] : --limit-read <rate> | --limit-write <rate> | \"\n         \"--reservation-read <rate> | --reservation-write <rate>\\n\"\n      << \"\\t                      (rate can use suffixes, e.g., 10M, 500K, or 0 to \"\n         \"remove)\\n\"\n      << \"\\t   action 'rm' : completely remove a configured policy\\n\"\n      << \"\\t     usage: policy rm <identity>\\n\"\n      << \"\\t       <identity>   : --app <name> | --uid <id> | --gid <id>\\n\"\n      << std::endl\n      << \"     config [action] [options...] : manage traffic shaping thread \"\n         \"configurations\\n\"\n      << \"\\t   action 'ls' : list current thread update periods\\n\"\n      << \"\\t     usage: config ls\\n\"\n      << \"\\t   action 'set' : modify configuration settings such as update periods for \"\n         \"estimators and policy enforcement\\n\"\n      << \"\\t     usage: config set [--estimators-period <ms>] [--policy-period <ms>] \"\n         \"[--report-period <ms>] [--system-window <s>]\\n\"\n      << std::endl\n      << \"   EXAMPLES\\n\"\n      << \"\\t   # Show current application rates\\n\"\n      << \"\\t   eos io shaping ls --apps\\n\"\n      << std::endl\n      << \"\\t   # Globally enable the traffic shaping engine\\n\"\n      << \"\\t   eos io shaping enable\\n\"\n      << std::endl\n      << \"\\t   # Globally disable the traffic shaping engine\\n\"\n      << \"\\t   eos io shaping disable\\n\"\n      << std::endl\n      << \"\\t   # Limit 'eoscp' read rate to 10 MB/s and write rate to 50 MB/s\\n\"\n      << \"\\t   eos io shaping policy set --app eoscp --limit-read 10M --limit-write 50M\\n\"\n      << std::endl\n      << \"\\t   # Temporarily disable the policy for 'eoscp'\\n\"\n      << \"\\t   eos io shaping policy set --app eoscp --disable\\n\"\n      << std::endl\n      << \"\\t   # Remove the read limit for 'eoscp' but keep the write limit\\n\"\n      << \"\\t   eos io shaping policy set --app eoscp --limit-read 0\\n\"\n      << std::endl\n      << \"\\t   # Completely delete the policy for user 1001\\n\"\n      << \"\\t   eos io shaping policy rm --uid 1001\\n\"\n      << std::endl\n      << \"\\t   # List all configured application policies including machine limits\\n\"\n      << \"\\t   eos io shaping policy ls --apps --controller\\n\"\n      << std::endl\n      << \"\\t   # Show current thread configurations\\n\"\n      << \"\\t   eos io shaping config ls\\n\"\n      << std::endl\n      << \"\\t   # Change the estimators update period to 200 ms\\n\"\n      << \"\\t   eos io shaping config set --estimators-period 200\\n\"\n      << std::endl;\n\n  std::cerr << oss.str() << std::endl;\n}\n\nvoid\nSetupTrafficEnableCommand(CLI::App& app, eos::console::IoProto_ShapingProto* proto)\n{\n  auto* cmd = app.add_subcommand(\"enable\", \"Globally enable traffic shaping\");\n\n  cmd->callback([proto]() { proto->mutable_enable(); });\n}\n\nvoid\nSetupTrafficDisableCommand(CLI::App& app, eos::console::IoProto_ShapingProto* proto)\n{\n  auto* cmd = app.add_subcommand(\"disable\", \"Globally disable traffic shaping\");\n\n  cmd->callback([proto]() { proto->mutable_disable(); });\n}\n\nvoid\nSetupTrafficListCommand(CLI::App& app, eos::console::IoProto_ShapingProto* proto)\n{\n  auto* cmd = app.add_subcommand(\"ls\", \"View real-time IO rates\");\n\n  auto* grp = cmd->add_option_group(\"Grouping\")->require_option(0, 1);\n  grp->add_flag(\"--apps\", \"Show rates by application\");\n  grp->add_flag(\"--users\", \"Show rates by user (uid)\");\n  grp->add_flag(\"--groups\", \"Show rates by group (gid)\");\n  grp->add_flag(\"--nodes\", \"Show rates by storage node\");\n  cmd->add_flag(\"--json\", \"Output in JSON format\");\n  cmd->add_flag(\"--sys\", \"Include meta statistics about Traffic Shaping system\");\n  cmd->add_option(\"--window\",\n                  \"Time window in seconds for the simple moving average (SMA)\")\n      ->check(CLI::IsMember({\"1\", \"5\", \"15\", \"60\", \"300\"}))\n      ->default_val(\"60\");\n\n  cmd->callback([cmd, proto]() {\n    auto* action = proto->mutable_list();\n\n    const bool json_output = cmd->count(\"--json\") > 0;\n    const bool include_sys = cmd->count(\"--sys\") > 0;\n\n    const bool show_users = cmd->count(\"--users\") > 0;\n    const bool show_groups = cmd->count(\"--groups\") > 0;\n    const bool show_nodes = cmd->count(\"--nodes\") > 0;\n\n    // If neither is specified, show apps\n    const bool show_apps =\n        (cmd->count(\"--apps\") > 0) || (!show_users && !show_groups && !show_nodes);\n\n    action->set_show_apps(show_apps);\n    action->set_show_users(show_users);\n    action->set_show_groups(show_groups);\n    action->set_show_nodes(show_nodes);\n    action->set_json_output(json_output);\n    action->set_system_stats(include_sys);\n\n    const auto window_sec = cmd->get_option(\"--window\")->as<uint32_t>();\n    action->set_time_window_seconds(window_sec);\n  });\n}\n\nvoid\nSetupPolicyListCommand(CLI::App* policy_cmd, eos::console::IoProto_ShapingProto* proto)\n{\n  auto* cmd = policy_cmd->add_subcommand(\"ls\", \"Show configured policies\");\n\n  cmd->add_flag(\"--apps\", \"Show application policies\");\n  cmd->add_flag(\"--users\", \"Show user policies\");\n  cmd->add_flag(\"--groups\", \"Show group policies\");\n  cmd->add_flag(\"--controller\", \"Include ephemeral Traffic Shaping Controller limits\");\n  cmd->add_flag(\"--json\", \"Output in JSON format\");\n\n  cmd->callback([cmd, proto]() {\n    auto* action = proto->mutable_policy()->mutable_list();\n    action->set_filter_apps(cmd->count(\"--apps\") > 0);\n    action->set_filter_users(cmd->count(\"--users\") > 0);\n    action->set_filter_groups(cmd->count(\"--groups\") > 0);\n    action->set_show_controller_limits(cmd->count(\"--controller\") > 0);\n    action->set_json_output(cmd->count(\"--json\") > 0);\n  });\n}\n\nvoid\nSetupConfigCommand(CLI::App* config_cmd, eos::console::IoProto_ShapingProto* proto)\n{\n  config_cmd->require_subcommand(1);\n\n  auto* ls_cmd = config_cmd->add_subcommand(\"ls\", \"Show current shaping configuration\");\n\n  ls_cmd->callback([proto]() { proto->mutable_config()->mutable_list(); });\n\n  auto* set_cmd =\n      config_cmd->add_subcommand(\"set\", \"Set shaping configuration parameters\");\n\n  // At least one of the parameters must be provided when using 'set'\n  set_cmd->require_option(1, 4);\n\n  set_cmd->add_option(\"--estimators-period\", \"Estimators update thread period (ms)\");\n  set_cmd->add_option(\"--policy-period\", \"FST IO policy update thread period (ms)\");\n  set_cmd->add_option(\"--report-period\", \"FST IO stats reporting thread period (ms)\");\n  set_cmd->add_option(\"--system-window\", \"Time window for calculating system stats (s)\");\n\n  set_cmd->callback([set_cmd, proto]() {\n    auto* action = proto->mutable_config()->mutable_set();\n\n    if (set_cmd->count(\"--estimators-period\")) {\n      action->set_update_estimators_thread_period_ms(\n          set_cmd->get_option(\"--estimators-period\")->as<uint32_t>());\n    }\n\n    if (set_cmd->count(\"--policy-period\")) {\n      action->set_fst_io_policy_update_thread_period_ms(\n          set_cmd->get_option(\"--policy-period\")->as<uint32_t>());\n    }\n\n    if (set_cmd->count(\"--report-period\")) {\n      action->set_fst_io_stats_reporting_thread_period_ms(\n          set_cmd->get_option(\"--report-period\")->as<uint32_t>());\n    }\n\n    if (set_cmd->count(\"--system-window\")) {\n      action->set_system_stats_time_window_seconds(\n          set_cmd->get_option(\"--system-window\")->as<uint32_t>());\n    }\n  });\n}\n\nvoid\nSetupPolicySetCommand(CLI::App* policy_cmd, eos::console::IoProto_ShapingProto* proto)\n{\n  auto* cmd = policy_cmd->add_subcommand(\"set\", \"Create or update a shaping policy\");\n\n  auto* target_grp = cmd->add_option_group(\"Target Identity\")->require_option(1);\n  target_grp->add_option(\"--app\", \"Application name\");\n  target_grp->add_option(\"--uid\", \"User ID\");\n  target_grp->add_option(\"--gid\", \"Group ID\");\n\n  auto* param_grp = cmd->add_option_group(\"Policy Parameters\")->require_option(1, 6);\n  param_grp->add_option(\"--limit-read\", \"Max read rate (e.g. 10M, 0 to remove)\");\n  param_grp->add_option(\"--limit-write\", \"Max write rate\");\n  param_grp->add_option(\"--reservation-read\", \"Guaranteed read rate\");\n  param_grp->add_option(\"--reservation-write\", \"Guaranteed write rate\");\n  param_grp->add_option(\n      \"--controller-limit-read\",\n      \"Ephemeral read limit to be set by the Traffic Shaping Controller\");\n  param_grp->add_option(\n      \"--controller-limit-write\",\n      \"Ephemeral write limit to be set by the Traffic Shaping Controller\");\n\n  auto* opt_enable = param_grp->add_flag(\"--enable\", \"Enable the policy\");\n  auto* opt_disable = param_grp->add_flag(\"--disable\", \"Disable the policy\");\n  opt_enable->excludes(opt_disable);\n\n  cmd->callback([cmd, proto]() {\n    auto* action = proto->mutable_policy()->mutable_set();\n\n    if (cmd->count(\"--app\")) {\n      action->set_app(cmd->get_option(\"--app\")->as<std::string>());\n    }\n    if (cmd->count(\"--uid\")) {\n      action->set_uid(cmd->get_option(\"--uid\")->as<uint32_t>());\n    }\n    if (cmd->count(\"--gid\")) {\n      action->set_gid(cmd->get_option(\"--gid\")->as<uint32_t>());\n    }\n\n    auto parse_rate = [](const std::string& input) -> uint64_t {\n      uint64_t size = 0;\n      eos::common::StringConversion::GetSizeFromString(input, size);\n      return size;\n    };\n\n    if (cmd->count(\"--limit-read\")) {\n      action->set_limit_read_bytes_per_sec(\n          parse_rate(cmd->get_option(\"--limit-read\")->as<std::string>()));\n    }\n    if (cmd->count(\"--limit-write\")) {\n      action->set_limit_write_bytes_per_sec(\n          parse_rate(cmd->get_option(\"--limit-write\")->as<std::string>()));\n    }\n    if (cmd->count(\"--reservation-read\")) {\n      action->set_reservation_read_bytes_per_sec(\n          parse_rate(cmd->get_option(\"--reservation-read\")->as<std::string>()));\n    }\n    if (cmd->count(\"--reservation-write\")) {\n      action->set_reservation_write_bytes_per_sec(\n          parse_rate(cmd->get_option(\"--reservation-write\")->as<std::string>()));\n    }\n    if (cmd->count(\"--controller-limit-read\")) {\n      action->set_controller_limit_read_bytes_per_sec(\n          parse_rate(cmd->get_option(\"--controller-limit-read\")->as<std::string>()));\n    }\n    if (cmd->count(\"--controller-limit-write\")) {\n      action->set_controller_limit_write_bytes_per_sec(\n          parse_rate(cmd->get_option(\"--controller-limit-write\")->as<std::string>()));\n    }\n\n    if (cmd->count(\"--enable\") > 0) {\n      action->set_is_enabled(true);\n    } else if (cmd->count(\"--disable\") > 0) {\n      action->set_is_enabled(false);\n    }\n  });\n}\n\nvoid\nSetupPolicyRemoveCommand(CLI::App* policy_cmd, eos::console::IoProto_ShapingProto* proto)\n{\n  auto* cmd = policy_cmd->add_subcommand(\"rm\", \"Remove a configured policy\");\n\n  auto* target_grp = cmd->add_option_group(\"Target Identity\")->require_option(1);\n  target_grp->add_option(\"--app\", \"Application name\");\n  target_grp->add_option(\"--uid\", \"User ID\");\n  target_grp->add_option(\"--gid\", \"Group ID\");\n\n  cmd->callback([cmd, proto]() {\n    auto* action = proto->mutable_policy()->mutable_remove();\n\n    if (cmd->count(\"--app\")) {\n      action->set_app(cmd->get_option(\"--app\")->as<std::string>());\n    }\n    if (cmd->count(\"--uid\")) {\n      action->set_uid(cmd->get_option(\"--uid\")->as<uint32_t>());\n    }\n    if (cmd->count(\"--gid\")) {\n      action->set_gid(cmd->get_option(\"--gid\")->as<uint32_t>());\n    }\n  });\n}\n\nbool\nIoHelper::ParseTrafficShapingCommand(eos::common::StringTokenizer& tokenizer,\n                                     eos::console::IoProto* io)\n{\n  std::string full_cmd = \"eos-io-shaping\"; // Dummy argv[0] for CLI11\n  std::string arg_token;\n  while (tokenizer.NextToken(arg_token)) {\n    full_cmd += \" \";\n    full_cmd += arg_token;\n  }\n\n  CLI::App io_shaping_app{\"Interact with the Traffic Shaping engine\"};\n  io_shaping_app.require_subcommand(1);\n\n  eos::console::IoProto_ShapingProto* shaping_proto = io->mutable_shaping();\n\n  SetupTrafficListCommand(io_shaping_app, shaping_proto);\n  SetupTrafficEnableCommand(io_shaping_app, shaping_proto);\n  SetupTrafficDisableCommand(io_shaping_app, shaping_proto);\n\n  auto* policy_cmd = io_shaping_app.add_subcommand(\n      \"policy\", \"Manage traffic shaping limits and reservations\");\n  policy_cmd->require_subcommand(1);\n\n  SetupPolicyListCommand(policy_cmd, shaping_proto);\n  SetupPolicySetCommand(policy_cmd, shaping_proto);\n  SetupPolicyRemoveCommand(policy_cmd, shaping_proto);\n\n  // Assuming 'shaping_app' is the parent subcommand for all shaping actions\n  auto* config_cmd = io_shaping_app.add_subcommand(\n      \"config\", \"Manage traffic shaping thread configurations\");\n  SetupConfigCommand(config_cmd, shaping_proto);\n\n  try {\n    io_shaping_app.parse(full_cmd, true);\n  } catch (const CLI::ParseError& e) {\n    io_shaping_app.exit(e);\n    return false;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_node.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: com_proto_node.cc\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your token) any later version.                                   *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/NodeHelper.hh\"\n\nextern int com_node(char*);\nvoid com_node_help();\n\n//------------------------------------------------------------------------------\n// Node command entry point\n//------------------------------------------------------------------------------\nint com_protonode(char* arg)\n{\n  if (wants_help(arg)) {\n    com_node_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  NodeHelper node(gGlobalOpts);\n\n  if (!node.ParseCommand(arg)) {\n    com_node_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = node.Execute();\n  return global_retc;\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_node_help()\n{\n  std::ostringstream oss;\n  oss\n      << \" usage:\\n\"\n      << \"node ls [-s] [-b|--brief] [-m|-l|--sys|--io|--fsck] [<node>] : list all nodes or only <node>. <node> is a substring match and can be a comma seperated list\\n\"\n      << \"\\t      -s : silent mode\\n\"\n      << \"\\t      -b : display host names without domain names\\n\"\n      << \"\\t      -m : monitoring key=value output format\\n\"\n      << \"\\t      -l : long output - list also file systems after each node\\n\"\n      << \"\\t    --io : print IO statistics\\n\"\n      << \"\\t   --sys : print SYS statistics (memory + threads)\\n\"\n      << \"\\t  --fsck : print filesystem check statistcis\\n\"\n      << std::endl\n      << \"node config <host:port> <key>=<value : configure file system parameters for each filesystem of this node\\n\"\n      << \"\\t    <key> : error.simulation=io_read|io_write|xs_read|xs_write|fmd_open|fake_write|close|unresponsive\\n\"\n      << \"\\t            If offset is given then the error will get triggered for requests past the given value.\\n\"\n      << \"\\t            Accepted format for offset: 8B, 10M, 20G etc.\\n\"\n      << \"\\t            fmd_open            : simulate a file metadata mismatch when opening a file\\n\"\n      << \"\\t            open_delay[_<sec>]  : add by default 120 sec delay per open operation\\n\"\n      << \"\\t            read_delay[_<sec>]  : add by default 10 sec delay per read operation\\n\"\n      << \"\\t            io_read[_<offset>]  : simulate read errors\\n\"\n      << \"\\t            io_write[_<offset>] : simulate write errors\\n\"\n      << \"\\t            xs_read             : simulate checksum errors when reading a file\\n\"\n      << \"\\t            xs_write[_<sec>]    : simulate checksum errors on write with an optional delay, default 0\\n\"\n      << \"\\t            fake_write          : do not really write data to disk\\n\"\n      << \"\\t            close               : return an error on close\\n\"\n      << \"\\t            close_commit_mgm    : simulate error during close commit to MGM\\n\"\n      << \"\\t            unresponsive        : emulate a write/close request taking 2 minutes\\n\"\n      << \"\\t            <none>              : disable error simulation (any value other than the previous ones is fine!)\\n\"\n      << \"\\t    <key> : publish.interval=<sec> - set the filesystem state publication interval to <sec> seconds\\n\"\n      << \"\\t    <key> : debug.level=<level>    - set the node into debug level <level> [default=notice] -> see debug --help for available levels\\n\"\n      << \"\\t    <key> : stripexs=on|off        - enable/disable synchronously stripe checksum computation\\n\"\n      << \"\\t    <key> : for other keys see help of 'fs config' for details\\n\"\n      << std::endl\n      << \"node set <queue-name>|<host:port> on|off                 : activate/deactivate node\\n\"\n      << std::endl\n      << \"node rm  <queue-name>|<host:port>                        : remove a node\\n\"\n      << std::endl\n      << \"node txgw <queue-name>|<host:port> <on|off> : enable (on) or disable (off) node as a transfer gateway\\n\"\n      << std::endl\n      << \"node proxygroupadd <group-name> <queue-name>|<host:port> : add a node to a proxy group\\n\"\n      << std::endl\n      << \"node proxygrouprm <group-name> <queue-name>|<host:port> : rm a node from a proxy group\\n\"\n      << std::endl\n      << \"node proxygroupclear <queue-name>|<host:port> : clear the list of groups a node belongs to\\n\"\n      << std::endl\n      << \"node status <queue-name>|<host:port> : print's all defined variables for a node\\n\"\n      << std::endl;\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_ns.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file com_ns.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/ParseUtils.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/ICmdHelper.hh\"\n\nvoid com_ns_help();\n\n//------------------------------------------------------------------------------\n//! Class NsHelper\n//------------------------------------------------------------------------------\nclass NsHelper: public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  NsHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {\n    mIsAdmin = true;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~NsHelper() = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg);\n};\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool\nNsHelper::ParseCommand(const char* arg)\n{\n  const char* option;\n  std::string soption;\n  eos::console::NsProto* ns = mReq.mutable_ns();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  option = tokenizer.GetToken();\n  std::string cmd = (option ? option : \"\");\n\n  if (cmd == \"stat\") {\n    eos::console::NsProto_StatProto* stat = ns->mutable_stat();\n\n    if (!(option = tokenizer.GetToken())) {\n      stat->set_monitor(false);\n    } else {\n      while (true) {\n        soption = option;\n\n        if (soption == \"-a\") {\n          stat->set_groupids(true);\n        } else if (soption == \"-x\") {\n          stat->set_apps(true);\n        } else if (soption == \"-m\") {\n          stat->set_monitor(true);\n        } else if (soption == \"-n\") {\n          stat->set_numericids(true);\n        } else if (soption == \"--reset\") {\n          stat->set_reset(true);\n        } else {\n          return false;\n        }\n\n        if (!(option = tokenizer.GetToken())) {\n          break;\n        }\n      }\n    }\n  } else if (cmd == \"mutex\") {\n    using eos::console::NsProto_MutexProto;\n    NsProto_MutexProto* mutex = ns->mutable_mutex();\n\n    if (!(option = tokenizer.GetToken())) {\n      mutex->set_list(true);\n    } else {\n      while (true) {\n        soption = option;\n\n        if (soption == \"--toggletime\") {\n          mutex->set_toggle_timing(true);\n        } else if (soption == \"--toggleorder\") {\n          mutex->set_toggle_order(true);\n        } else if (soption == \"--toggledeadlock\") {\n          mutex->set_toggle_deadlock(true);\n        } else if (soption == \"--smplrate1\") {\n          mutex->set_sample_rate1(true);\n        } else if (soption == \"--smplrate10\") {\n          mutex->set_sample_rate10(true);\n        } else if (soption == \"--smplrate100\") {\n          mutex->set_sample_rate100(true);\n        } else if (soption == \"--setblockedtime\") {\n          option = tokenizer.GetToken();\n\n          if (option) {\n            mutex->set_blockedtime(std::stoul(option));\n          } else {\n            return false;\n          }\n        } else {\n          return false;\n        }\n\n        if (!(option = tokenizer.GetToken())) {\n          break;\n        }\n      }\n    }\n  } else if (cmd == \"compact\") {\n    using eos::console::NsProto_CompactProto;\n    NsProto_CompactProto* compact = ns->mutable_compact();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      soption = option;\n\n      if (soption == \"off\") {\n        compact->set_on(false);\n      } else if (soption == \"on\") {\n        compact->set_on(true);\n\n        if ((option = tokenizer.GetToken())) {\n          soption = option;\n          int64_t delay  = 0;\n\n          try {\n            delay = std::stol(soption);\n          } catch (std::exception& e) {\n            return false;\n          }\n\n          compact->set_delay(delay);\n\n          if ((option = tokenizer.GetToken())) {\n            soption = option;\n            int64_t interval = 0;\n\n            try {\n              interval = std::stol(soption);\n            } catch (std::exception& e) {\n              return false;\n            }\n\n            compact->set_interval(interval);\n\n            if ((option = tokenizer.GetToken())) {\n              soption = option;\n\n              if (soption == \"files\") {\n                compact->set_type(NsProto_CompactProto::FILES);\n              } else if (soption == \"directories\") {\n                compact->set_type(NsProto_CompactProto::DIRS);\n              } else if (soption == \"all\") {\n                compact->set_type(NsProto_CompactProto::ALL);\n              } else if (soption == \"files-repair\") {\n                compact->set_type(NsProto_CompactProto::FILES_REPAIR);\n              } else if (soption == \"directories-repair\") {\n                compact->set_type(NsProto_CompactProto::DIRS_REPAIR);\n              } else if (soption == \"all-repair\") {\n                compact->set_type(NsProto_CompactProto::ALL_REPAIR);\n              } else {\n                return false;\n              }\n            }\n          }\n        }\n      } else {\n        return false;\n      }\n    }\n  } else if (cmd == \"master\") {\n    using eos::console::NsProto_MasterProto;\n    NsProto_MasterProto* master = ns->mutable_master();\n\n    if (!(option = tokenizer.GetToken())) {\n      master->set_op(NsProto_MasterProto::LOG);\n    } else {\n      soption = option;\n\n      if (soption == \"--log\") {\n        master->set_op(NsProto_MasterProto::LOG);\n      } else if (soption == \"--log-clear\") {\n        master->set_op(NsProto_MasterProto::LOG_CLEAR);\n      } else if (soption == \"--enable\") {\n        master->set_op(NsProto_MasterProto::ENABLE);\n      } else if (soption == \"--disable\") {\n        master->set_op(NsProto_MasterProto::DISABLE);\n      } else {\n        master->set_host(soption);\n      }\n    }\n  } else if (cmd == \"recompute_tree_size\") {\n    using eos::console::NsProto_TreeSizeProto;\n    NsProto_TreeSizeProto* tree = ns->mutable_tree();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      while (true) {\n        int pos = 0;\n        soption = option;\n\n        if (soption == \"--depth\") {\n          if (!(option = tokenizer.GetToken())) {\n            return false;\n          }\n\n          soption = option;\n\n          try {\n            tree->set_depth(std::stoul(soption));\n          } catch (const std::exception& e) {\n            return false;\n          }\n        } else if ((soption.find(\"cid:\") == 0)) {\n          pos = soption.find(':') + 1;\n          tree->mutable_container()->set_cid(soption.substr(pos));\n        } else if (soption.find(\"cxid:\") == 0) {\n          pos = soption.find(':') + 1;\n          tree->mutable_container()->set_cxid(soption.substr(pos));\n        } else { // this should be a plain path\n          tree->mutable_container()->set_path(soption);\n        }\n\n        if (!(option = tokenizer.GetToken())) {\n          break;\n        }\n      }\n    }\n  } else if (cmd == \"recompute_quotanode\") {\n    using eos::console::NsProto_QuotaSizeProto;\n    NsProto_QuotaSizeProto* quota = ns->mutable_quota();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      while (true) {\n        int pos = 0;\n        soption = option;\n\n        if ((soption.find(\"cid:\") == 0)) {\n          pos = soption.find(':') + 1;\n          quota->mutable_container()->set_cid(soption.substr(pos));\n        } else if (soption.find(\"cxid:\") == 0) {\n          pos = soption.find(':') + 1;\n          quota->mutable_container()->set_cxid(soption.substr(pos));\n        } else { // this should be a plain path\n          quota->mutable_container()->set_path(soption);\n        }\n\n        if (!(option = tokenizer.GetToken())) {\n          break;\n        }\n      }\n    }\n  } else if (cmd == \"update_quotanode\") {\n    using eos::console::NsProto_QuotaSizeProto;\n    NsProto_QuotaSizeProto* quota = ns->mutable_quota();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      int npar = 0;\n\n      while (true) {\n        int pos = 0;\n        soption = option;\n\n        if ((soption.find(\"cid:\") == 0)) {\n          pos = soption.find(':') + 1;\n          quota->mutable_container()->set_cid(soption.substr(pos));\n        } else if (soption.find(\"cxid:\") == 0) {\n          pos = soption.find(':') + 1;\n          quota->mutable_container()->set_cxid(soption.substr(pos));\n        } else if (soption.find(\"uid:\") == 0) {\n          pos = soption.find(':') + 1;\n          quota->set_uid(soption.substr(pos));\n        } else if (soption.find(\"gid:\") == 0) {\n          pos = soption.find(':') + 1;\n          quota->set_gid(soption.substr(pos));\n        } else if (soption.find(\"bytes:\") == 0) {\n          pos = soption.find(':') + 1;\n          quota->set_used_bytes(strtoul(soption.substr(pos).c_str(), 0, 10));\n          npar++;\n        } else if (soption.find(\"physicalbytes:\") == 0) {\n          pos = soption.find(':') + 1;\n          quota->set_physical_bytes(strtoul(soption.substr(pos).c_str(), 0, 10));\n          npar++;\n        } else if (soption.find(\"inodes:\") == 0) {\n          pos = soption.find(':') + 1;\n          quota->set_used_inodes(strtoul(soption.substr(pos).c_str(), 0, 10));\n          npar++;\n        } else { // this should be a plain path\n          quota->mutable_container()->set_path(soption);\n        }\n\n        if (!(option = tokenizer.GetToken())) {\n          break;\n        }\n      }\n\n      if (npar && (npar != 3)) {\n        return false;\n      }\n    }\n  } else if (cmd == \"cache\") {\n    eos::console::NsProto_CacheProto* cache = ns->mutable_cache();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n\n    soption = option;\n\n    if (soption == \"set\")  {\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n\n      soption = option;\n\n      if (soption == \"-f\") {\n        cache->set_op(eos::console::NsProto_CacheProto::SET_FILE);\n      } else if (soption == \"-d\") {\n        cache->set_op(eos::console::NsProto_CacheProto::SET_DIR);\n      } else {\n        return false;\n      }\n\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n\n      uint64_t max_num = 0ull, max_size = 0ull;\n\n      try {\n        max_num = std::stoull(option);\n      } catch (const std::exception& e) {\n        return false;\n      }\n\n      if ((option = tokenizer.GetToken())) {\n        try {\n          max_size = eos::common::StringConversion::GetDataSizeFromString(option);\n        } catch (const std::exception& e) {\n          return false;\n        }\n      }\n\n      cache->set_max_num(max_num);\n      cache->set_max_size(max_size);\n    } else if (soption == \"drop\") {\n      if (!(option = tokenizer.GetToken())) {\n        cache->set_op(eos::console::NsProto_CacheProto::DROP_ALL);\n      } else {\n        soption = option;\n\n        if (soption == \"-f\") {\n          cache->set_op(eos::console::NsProto_CacheProto::DROP_FILE);\n        } else if (soption == \"-d\") {\n          cache->set_op(eos::console::NsProto_CacheProto::DROP_DIR);\n        } else {\n          return false;\n        }\n      }\n    } else if (soption == \"drop-single-file\") {\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n\n      uint64_t target;\n\n      try {\n        target = std::stoull(option);\n      } catch (const std::exception& e) {\n        return false;\n      }\n\n      cache->set_op(eos::console::NsProto_CacheProto::DROP_SINGLE_FILE);\n      cache->set_single_to_drop(target);\n    } else if (soption == \"drop-single-container\") {\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n\n      uint64_t target;\n\n      try {\n        target = std::stoull(option);\n      } catch (const std::exception& e) {\n        return false;\n      }\n\n      cache->set_op(eos::console::NsProto_CacheProto::DROP_SINGLE_CONTAINER);\n      cache->set_single_to_drop(target);\n    } else {\n      return false;\n    }\n  } else if (cmd == \"drain\") {\n    using eos::console::NsProto_DrainProto;\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      NsProto_DrainProto* drain = ns->mutable_drain();\n      soption = option;\n\n      if (soption == \"list\") {\n        drain->set_op(eos::console::NsProto_DrainProto::LIST);\n      } else if (soption == \"set\") {\n        if (!(option = tokenizer.GetToken())) {\n          return false;\n        }\n\n        soption = option;\n        size_t pos = soption.find(\"=\");\n\n        if ((pos == std::string::npos) || (pos == soption.length() - 1)) {\n          return false;\n        }\n\n        drain->set_op(eos::console::NsProto_DrainProto::SET);\n        drain->set_key(soption.substr(0, pos));\n        drain->set_value(soption.substr(pos + 1));\n      } else {\n        return false;\n      }\n    }\n  } else if (cmd == \"reserve-ids\") {\n    using eos::console::NsProto_ReserveIdsProto;\n    NsProto_ReserveIdsProto* reserve = ns->mutable_reserve();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n\n    int64_t fileID = 0;\n\n    if (!eos::common::ParseInt64(option, fileID) || fileID < 0) {\n      return false;\n    }\n\n    // ---\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n\n    int64_t containerID = 0;\n\n    if (!eos::common::ParseInt64(option, containerID) || containerID < 0) {\n      return false;\n    }\n\n    reserve->set_fileid(fileID);\n    reserve->set_containerid(containerID);\n  } else if (cmd == \"benchmark\") {\n    using eos::console::NsProto_BenchmarkProto;\n    NsProto_BenchmarkProto* benchmark = ns->mutable_benchmark();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n\n    int64_t n_threads = 0;\n    int64_t n_subdirs = 0;\n    int64_t n_subfiles = 0;\n\n    if (!eos::common::ParseInt64(option, n_threads) || n_threads < 0) {\n      return false;\n    }\n\n    // ---\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n\n    if (!eos::common::ParseInt64(option, n_subdirs) || n_subdirs < 0) {\n      return false;\n    }\n\n    // ---\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n\n    if (!eos::common::ParseInt64(option, n_subfiles) || n_subfiles < 0) {\n      return false;\n    }\n\n    if ((option = tokenizer.GetToken())) {\n      benchmark->set_prefix(option);\n    }\n\n    benchmark->set_threads(n_threads);\n    benchmark->set_subdirs(n_subdirs);\n    benchmark->set_subfiles(n_subfiles);\n  } else if (cmd == \"tracker\") {\n    eos::console::NsProto_TrackerProto* tracker = ns->mutable_tracker();\n    tracker->set_op(eos::console::NsProto_TrackerProto::NONE);\n\n    while ((option = tokenizer.GetToken())) {\n      soption = option;\n\n      if (soption == \"list\")  {\n        if (tracker->op() != eos::console::NsProto_TrackerProto::NONE) {\n          std::cerr << \"error: only one operation per command\" << std::endl;\n          return false;\n        } else {\n          tracker->set_op(eos::console::NsProto_TrackerProto::LIST);\n        }\n      } else if (soption == \"clear\") {\n        if (tracker->op() != eos::console::NsProto_TrackerProto::NONE) {\n          std::cerr << \"error: only one operation per command\" << std::endl;\n          return false;\n        } else {\n          tracker->set_op(eos::console::NsProto_TrackerProto::CLEAR);\n        }\n      } else if (soption == \"--name\") {\n        if (!(option = tokenizer.GetToken())) {\n          return false;\n        }\n\n        tracker->set_name(option);\n      } else {\n        return false;\n      }\n    }\n\n    if (tracker->op() == eos::console::NsProto_TrackerProto::NONE) {\n      std::cerr << \"error: no operation specified\" << std::endl;\n      return false;\n    }\n  } else if (cmd == \"behaviour\") {\n    eos::console::NsProto_BehaviourProto* behaviour = ns->mutable_behaviour();\n    behaviour->set_op(eos::console::NsProto_BehaviourProto::NONE);\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n\n    soption = option;\n\n    if (soption == \"list\") {\n      behaviour->set_op(eos::console::NsProto_BehaviourProto::LIST);\n    } else if (soption == \"set\") {\n      behaviour->set_op(eos::console::NsProto_BehaviourProto::SET);\n\n      while ((option = tokenizer.GetToken())) {\n        soption = option;\n\n        if (behaviour->name().empty()) {\n          if (soption == \"all\") {\n            std::cerr << \"error: \\\"all\\\" is a reserved keyword\" << std::endl;\n            return false;\n          }\n\n          behaviour->set_name(soption);\n        } else {\n          behaviour->set_value(soption);\n          break;\n        }\n      }\n\n      if (behaviour->name().empty() || behaviour->value().empty()) {\n        return false;\n      }\n    } else if (soption == \"get\") {\n      behaviour->set_op(eos::console::NsProto_BehaviourProto::GET);\n\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n\n      soption = option;\n      behaviour->set_name(soption);\n    } else if (soption == \"clear\") {\n      behaviour->set_op(eos::console::NsProto_BehaviourProto::CLEAR);\n\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n\n      soption = option;\n      behaviour->set_name(soption);\n    } else {\n      std::cerr << \"error: unknown behaviour subcommand\" << std::endl;\n      return false;\n    }\n  } else if (cmd == \"\") {\n    eos::console::NsProto_StatProto* stat = ns->mutable_stat();\n    stat->set_summary(true);\n  } else {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Ns command entrypoint\n//------------------------------------------------------------------------------\nint com_ns(char* arg)\n{\n  if (wants_help(arg)) {\n    com_ns_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  NsHelper ns(gGlobalOpts);\n\n  if (!ns.ParseCommand(arg)) {\n    com_ns_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = ns.Execute();\n  return global_retc;\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_ns_help()\n{\n  std::ostringstream oss;\n  oss << \"Usage: ns [stat|mutex|compact|master|cache|benchmark]\" << std::endl\n      << \"    print or configure basic namespace parameters\" << std::endl\n      << \"  ns stat [-a] [-x] [-m] [-n] [--reset]\" << std::endl\n      << \"    print namespace statistics\" << std::endl\n      << \"    -a      : break down by uid/gid\" << std::endl\n      << \"    -x      : break down by application\" << std::endl\n      << \"    -m      : display in monitoring format <key>=<value>\" << std::endl\n      << \"    -n      : display numerical uid/gid(s)\" << std::endl\n      << \"    --reset : reset namespace counters\" << std::endl\n      << std::endl\n      << \"  ns mutex [<option>]\" << std::endl\n      << \"    manage mutex monitoring. Option can be:\" << std::endl\n      << \"    --toggletime     : toggle the timing\" << std::endl\n      << \"    --toggleorder    : toggle the order\" << std::endl\n      << \"    --toggledeadlock : toggle deadlock check\" << std::endl\n      << \"    --smplrate1      : set timing sample rate at 1% (default, no slow-down)\"\n      << std::endl\n      << \"    --smplrate10     : set timing sample rate at 10% (medium slow-down)\"\n      << std::endl\n      << \"    --smplrate100    : set timing sample rate at 100% (severe slow-down)\"\n      << std::endl\n      << \"    --setblockedtime <ms>\" << std::endl\n      << \"                     : set minimum time when a mutex lock lasting longer than <ms> \\n\"\n      << \"                       is reported in the log file [default=10000]\\n\"\n      << std::endl\n      << \"  ns compact off|on <delay> [<interval>] [<type>]\\n\"\n      << \"    enable online compaction after <delay> seconds\\n\"\n      << \"    <interval> : if >0 then compaction is repeated automatically \\n\"\n      << \"                 after so many seconds\\n\"\n      << \"    <type>     : can be 'files', 'directories' or 'all'. By default  only the file\\n\"\n      << \"                 changelog is compacted. The repair flag can be indicated by using:\\n\"\n      << \"                 'files-repair', 'directories-repair' or 'all-repair'\\n\"\n      << std::endl\n      << \"  ns master [<option>]\" << std::endl\n      << \"    master/slave operations. Option can be:\" << std::endl\n      << \"    <master_hostname> : set hostname of MGM master RW daemon\" << std::endl\n      << \"    --log             : show master log\" << std::endl\n      << \"    --log-clear       : clean master log\" << std::endl\n      << \"    --enable          : enable the slave/master supervisor thread modifying stall/\"\n      << std::endl\n      << \"                        redirectorion rules\" << std::endl\n      << \"    --disable         : disable supervisor thread\"\n      << std::endl\n      << std::endl\n      << \"  ns recompute_tree_size <path>|cid:<decimal_id>|cxid:<hex_id> [--depth <val>]\"\n      << std::endl\n      << \"    recompute the tree size of a directory and all its subdirectories\"\n      << std::endl\n      << \"    --depth : maximum depth for recomputation, default 0 i.e no limit\"\n      << std::endl\n      << std::endl\n      << \"  ns recompute_quotanode <path>|cid:<decimal_id>|cxid:<hex_id>\"\n      << std::endl\n      << \"    recompute the specified quotanode\"\n      << std::endl\n      << std::endl\n      << \"  ns update_quotanode <path>|cid:<decimal_id>|cxid:<hex_id> uid:<uid>|gid:<gid> bytes:<bytes> physicalbytes:<bytes> inodes:<inodes>\\n\"\n      << \"    update quota node with the specified (and unchecked) values\\n\"\n      << \"    Note: for project quotas the uid values needs to be specified\\n\"\n      << \"    since the accounting is done by accumulating the individual\\n\"\n      << \"    quotas of the users registered with the quota node.\\n\"\n      << std::endl\n      << \"  ns cache set|drop [-d|-f] [<max_num>] [<max_size>K|M|G...]\" << std::endl\n      << \"    set the max number of entries or the max size of the cache. Use the\" <<\n      std::endl\n      << \"    ns stat command to see the current values.\" << std::endl\n      << \"    set        : update cache size for files or directories\" << std::endl\n      << \"    drop       : drop cached file and/or directory entries\"\n      << std::endl\n      << \"    -d         : control the directory cache\" << std::endl\n      << \"    -f         : control the file cache\" << std::endl\n      << \"    <max_num>  : max number of entries\" << std::endl\n      << \"    <max_size> : max size of the cache - not implemented yet\"\n      << std::endl\n      << std::endl\n      << \"  ns cache drop-single-file <id of file to drop>\\n\"\n      << \"    force refresh of the given FileMD by dropping it from the cache\\n\"\n      << std::endl\n      << \"  ns cache drop-single-container <id of container to drop>\\n\"\n      << \"    force refresh of the given ContainerMD by dropping it from the cache\\n\"\n      << std::endl\n      << \"  ns drain list|set [<key>=<value>]                                 \\n\"\n      << \"    list : list the global drain configuration parameters           \\n\"\n      << \"    set  : set one of the following drain configuration parameters  \\n\"\n      << \"           max-thread-pool-size : max number of threads in drain pool\\n\"\n      << \"                                  [default 100, minimum 5]          \\n\"\n      << \"           max-fs-per-node      : max number of file systems per node that\\n\"\n      << \"                                  can be drained in parallel [default 5]\\n\"\n      << std::endl\n      << \"  ns reserve-ids <file id> <container id>\\n\"\n      << \"    blacklist file and container IDs below the given threshold. The namespace\\n\"\n      << \"    will not allocate any file or container with IDs less than, or equal to the\\n\"\n      << \"    given blacklist thresholds.\\n\"\n      << std::endl\n      << \"  ns benchmark <n-threads> <n-subdirs> <n-subfiles> [prefix=/benchmark]\\n\"\n      << \"     run metadata benchmark inside the MGM - results are printed into the MGM logfile and the shell\\n\"\n      << \"                n-threads  : number of parallel threads running a benchmark in the MGM\\n\"\n      << \"                n-subdirs  : directories created by each threads\\n\"\n      << \"                n-subfiles : number of files created in each sub-directory\\n\"\n      << \"                prefix     : absolute directory where to write the benchmarkf iles - default is /benchmark\\n\"\n      << std::endl\n      << \"     example: eos ns benchmark 100 10 10\\n\"\n      << std::endl\n      << \" ns tracker list|clear --name tracker_type\\n\"\n      << \"     list or clear the different file identifier trackers\\n\"\n      << \"     tracker_type : one of the following: drain, balance, fsck, convert, all\\n\"\n      << std::endl\n      << \" ns behaviour list|set|clear\\n\"\n      << \"     modify the behaviour of internal mechanisms for the manager node\\n\"\n      << \"     list                    : list all the behaviour changes enforced\\n\"\n      << \"     set <behaviour> <value> : enforce given behavior\\n\"\n      << \"     get <behaviour>         : get behaviour configuration\\n\"\n      << \"     clear <behaviour>|all   : remove enforced behavior\\n\"\n      << std::endl\n      << \"     The following behaviours are supported:\\n\"\n      << \"       rain_min_fsid_entry : for RAIN files the entry server will deterministically\\n\"\n      << \"         be the file system with the lowest fsid from the list of stripes\\n\"\n      << \"         Accepted values: \\\"on\\\" or \\\"off\\\" [default off]\\n\"\n      << std::endl;\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_quota.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: com_proto_quota.cc\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your token) any later version.                                   *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <iomanip>\n#include <map>\n#include <random>\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/ICmdHelper.hh\"\n\nextern int com_quota(char*);\nvoid com_quota_help();\n\n//------------------------------------------------------------------------------\n//! Class QuotaHelper\n//------------------------------------------------------------------------------\nclass QuotaHelper : public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  QuotaHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~QuotaHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n};\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool QuotaHelper::ParseCommand(const char* arg)\n{\n  eos::console::QuotaProto* quota = mReq.mutable_quota();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  std::string token;\n  tokenizer.NextToken(token);\n\n// quite ugly, but not to break the legacy syntax...\n  if ( token == \"\" || token == \"-m\" || token == \"--path\" || token == \"-p\" || token == \"-x\" || token == \"-q\" || (token.find('/') == 0) ) { // ... or begins with \"/\"\n    // lsuser\n    eos::console::QuotaProto_LsuserProto* lsuser = quota->mutable_lsuser();\n    std::string aux_string;\n    if (token == \"\") {\n      aux_string = DefaultRoute(false);\n      if (aux_string.find('/') == 0) {\n        lsuser->set_space(aux_string);\n      }\n    } else {\n      do {\n        if (token == \"-m\") {\n          lsuser->set_format(true);\n          aux_string = DefaultRoute(false);\n          if (aux_string.find('/') == 0) {\n            lsuser->set_space(aux_string);\n          }\n        } else if (token == \"--path\" || token == \"-p\" || token == \"-x\" || token == \"-q\" || (token.find('/') == 0)) {\n          if (token == \"--path\" || token == \"-p\" || token == \"-x\" || token == \"-q\") {\n\t    if (token == \"-x\") {\n\t      lsuser->set_exists(true);\n\t    }\n\t    if (token == \"-q\") {\n\t      lsuser->set_quotanode(true);\n\t    }\n            if (tokenizer.NextToken(token)) {\n              lsuser->set_space(token);\n            } else {\n              return false;\n            }\n          } else if (token.find('/') == 0) {\n            lsuser->set_space(token);\n            // for convenience can omit --path and use /some/path/ as *last*\n            // argument - e.g. quota ls /eos/ ...\n            if (tokenizer.NextToken(token)) {\n              return false;\n            }\n          }\n        } else { // no proper argument\n          return false;\n        }\n      } while (tokenizer.NextToken(token));\n    }\n  } else if (token == \"ls\") {\n    eos::console::QuotaProto_LsProto* ls = quota->mutable_ls();\n\n    while (tokenizer.NextToken(token)) {\n      if (token == \"--uid\" || token == \"-u\") {\n        if (tokenizer.NextToken(token)) {\n          ls->set_uid(token);\n        } else {\n          return false;\n        }\n      } else if (token == \"--gid\" || token == \"-g\") {\n        if (tokenizer.NextToken(token)) {\n          ls->set_gid(token);\n        } else {\n          return false;\n        }\n      } else if (token == \"-m\") {\n        ls->set_format(true);\n      } else if (token == \"-n\") {\n        ls->set_printid(true);\n      } else if (token == \"--path\" ||\n\t\t token == \"-p\" ||\n\t\t token == \"-x\" ||\n\t\t token == \"-q\" ||\n\t\t (token.find('/') == 0)) {\n        if (token == \"--path\" || token == \"-p\" || token == \"-q\" || token == \"-x\") {\n\t  if (token == \"-x\") {\n\t    ls->set_exists(true);\n\t  }\n\t  if (token == \"-q\") {\n\t    ls->set_quotanode(true);\n\t  }\n          if (tokenizer.NextToken(token)) {\n            ls->set_space(token);\n          } else {\n            return false;\n          }\n        } else if (token.find('/') == 0) {\n          ls->set_space(token);\n          // for convenience can omit --path and use /some/path/ as *last*\n          // argument - e.g. quota ls /eos/ ...\n          if (tokenizer.NextToken(token)) {\n            return false;\n          }\n        }\n      } else { // no proper argument\n        return false;\n      }\n    }\n  } else if (token == \"set\") {\n    eos::console::QuotaProto_SetProto* set = quota->mutable_set();\n\n    while (tokenizer.NextToken(token)) {\n      if (token == \"--uid\" || token == \"-u\") {\n        if (tokenizer.NextToken(token)) {\n          set->set_uid(token);\n        } else {\n          return false;\n        }\n      } else if (token == \"--gid\" || token == \"-g\") {\n        if (tokenizer.NextToken(token)) {\n          set->set_gid(token);\n        } else {\n          return false;\n        }\n      } else if (token == \"--volume\" || token == \"-v\") {\n        if (tokenizer.NextToken(token)) {\n          set->set_maxbytes(token);\n        } else {\n          return false;\n        }\n      } else if (token == \"--inodes\" || token == \"-i\") {\n        if (tokenizer.NextToken(token)) {\n          set->set_maxinodes(token);\n        } else {\n          return false;\n        }\n      } else if (token == \"--path\" || token == \"-p\" || (token.find('/') == 0)) {\n        if (token == \"--path\" || token == \"-p\") {\n          if (tokenizer.NextToken(token)) {\n            set->set_space(token);\n          } else {\n            return false;\n          }\n        } else if (token.find('/') == 0) {\n          set->set_space(token);\n\n          // for convenience can omit --path and use /some/path/ as *last*\n          // argument - e.g. quota set /eos/ ...\n          if (tokenizer.NextToken(token)) {\n            return false;\n          }\n        }\n      } else { // no proper argument\n        return false;\n      }\n    }\n  } else if (token == \"rm\") {\n    eos::console::QuotaProto_RmProto* rm = quota->mutable_rm();\n\n    while (tokenizer.NextToken(token)) {\n      if (token == \"--uid\" || token == \"-u\") {\n        if (tokenizer.NextToken(token)) {\n          rm->set_uid(token);\n        } else {\n          return false;\n        }\n      } else if (token == \"--gid\" || token == \"-g\") {\n        if (tokenizer.NextToken(token)) {\n          rm->set_gid(token);\n        } else {\n          return false;\n        }\n      } else if (token == \"--volume\" || token == \"-v\") {\n        rm->set_type(eos::console::QuotaProto_RmProto::VOLUME);\n      } else if (token == \"--inode\" || token == \"-i\") {\n        rm->set_type(eos::console::QuotaProto_RmProto::INODE);\n      } else if (token == \"--path\" || token == \"-p\" || (token.find('/') == 0)) {\n        if (token == \"--path\" || token == \"-p\") {\n          if (tokenizer.NextToken(token)) {\n            rm->set_space(token);\n          } else {\n            return false;\n          }\n        } else if (token.find('/') == 0) {\n          rm->set_space(token);\n\n          // for convenience can omit --path and use /some/path/ as *last*\n          // argument - e.g. quota rm /eos/ ...\n          if (tokenizer.NextToken(token)) {\n            return false;\n          }\n        }\n      } else { // no proper argument\n        return false;\n      }\n    }\n  } else if (token == \"rmnode\") {\n    bool dontask = false;\n    eos::console::QuotaProto_RmnodeProto* rmnode = quota->mutable_rmnode();\n    tokenizer.NextToken(token);\n\n    if (token == \"--really-want\") {\n      dontask = true;\n      tokenizer.NextToken(token);\n    }\n\n    if (token == \"--path\" || token == \"-p\" || (token.find('/') == 0)) {\n      if (token == \"--path\" || token == \"-p\") {\n        if (tokenizer.NextToken(token)) {\n          rmnode->set_space(token);\n        } else {\n          return false;\n        }\n      } else if (token.find('/') == 0) {\n        rmnode->set_space(token);\n\n        // for convenience, the --path / -p flags can be omitted\n        if (tokenizer.NextToken(token)) {\n          return false;\n        }\n      }\n    } else { // no proper argument\n      return false;\n    }\n\n    std::string in_string;\n    std::string random_confirmation_string;\n\n    if (!dontask) {\n      std::cout << \"Do you really want to delete the quota node under path: \"\n\t\t<< rmnode->space() << \" ?\" << std::endl;\n      std::cout << \"Confirm the deletion by typing => \";\n      // Seed with a real random value, if available\n      std::random_device rd;\n      // Choose a random 10-digits number\n      std::default_random_engine dre(rd());\n      std::uniform_int_distribution<long> uniform_dist(1000000000, 9999999999);\n      long random_long = uniform_dist(dre);\n      random_confirmation_string = std::to_string(random_long);\n      std::cout << random_confirmation_string << std::endl;\n      std::cout << \"                               => \";\n      std::cin >> in_string;\n    }\n\n    if (dontask || (in_string == random_confirmation_string)) {\n      std::cout << \"\\nSending deletion request to server ...\\n\";\n    } else {\n      std::cout << \"\\nDeletion aborted!\\n\";\n      return false;\n    }\n  } else { // no proper subcommand\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// quota command entry point\n//------------------------------------------------------------------------------\nint com_protoquota(char* arg)\n{\n  if (wants_help(arg)) {\n    com_quota_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  QuotaHelper quota(gGlobalOpts);\n\n  if (!quota.ParseCommand(arg)) {\n    com_quota_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = quota.Execute(true, true);\n\n  return global_retc;\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_quota_help()\n{\n  std::ostringstream oss;\n  std::vector<std::uint32_t> col_size = {0, 0};\n  std::map<std::string, std::string> map_cmds = {\n    {\n      \"quota [<path>]\",\n      \": show personal quota for all or only the quota node responsible for <path>\"\n    },\n    {\n      \"quota ls [-n] [-m] [-u <uid>] [-g <gid>] [[-p|x|q] <path>]\",\n      \": list configured quota and quota node(s)\"\n      \"\\n                                                                       -p : find closest matching quotanode\"\n      \"\\n                                                                       -x : as -p but <path> has to exist\"\n      \"\\n                                                                       -q : as -p but <path> has to be a quotanode\"\n    },\n    {\n      \"quota set -u <uid>|-g <gid> [-v <bytes>] [-i <inodes>] [[-p] <path>]\",\n      \": set volume and/or inode quota by uid or gid\"\n    },\n    {\n      \"quota rm -u <uid>|-g <gid> [-v] [-i] [[-p] <path>]\",\n      \": remove configured quota type(s) for uid/gid in path\"\n    },\n    {\n      \"quota rmnode [-p] <path>\",\n      \": remove quota node and every defined quota on that node\"\n    }\n  };\n\n  // Compute max width for command and description table\n  for (auto& map_cmd : map_cmds) {\n    if (col_size[0] < map_cmd.first.length()) {\n      col_size[0] = map_cmd.first.length() + 1;\n    }\n\n    if (col_size[1] < map_cmd.second.length()) {\n      col_size[1] = map_cmd.second.length() + 1;\n    }\n  }\n\n  std::int8_t tab_size = 2;\n  std::string usage_txt = \"Usage:\";\n  std::string opt_txt = \"General options:\";\n  std::string notes_txt = \"Notes:\";\n  oss << usage_txt << std::endl;\n\n  // Print the command and their description\n  for (auto& map_cmd : map_cmds) {\n    oss << std::setw(usage_txt.length()) << \"\"\n        << std::setw(col_size[0]) << std::setiosflags(std::ios_base::left)\n        << map_cmd.first\n        << std::setw(col_size[1]) << std::setiosflags(std::ios_base::left)\n        << map_cmd.second\n        << std::endl;\n  }\n\n  std::uint32_t indent_len = usage_txt.length() + tab_size;\n  // Print general options\n  oss << std::endl << std::setw(usage_txt.length()) << \"\"\n      << opt_txt << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"-m : print information in monitoring <key>=<value> format\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"-n : don't translate ids, print uid and gid number\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"-u/--uid <uid> : print information only for uid <uid>\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"-g/--gid <gid> : print information only for gid <gid>\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"-p/--path <path> : print information only for path <path> - this \"\n      << \"can also be given without -p or --path\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"-v/--volume <bytes> : refer to volume limit in <bytes>\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"-i/--inodes <inodes> : refer to inode limit in number of <inodes>\"\n      << std::endl;\n  indent_len = usage_txt.length() + tab_size;\n  // Print extra notes\n  oss << std::endl << std::setw(usage_txt.length()) << \"\"\n      << notes_txt << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"=> you have to specify either the user or the group identified by the \"\n      << \"unix id or the user/group name\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"=> the space argument is by default assumed as 'default'\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"=> you have to specify at least a volume or an inode limit to set quota\" <<\n      std::endl\n      << std::setw(indent_len) << \"\"\n      << \"=> for convenience all commands can just use <path> as last argument \"\n      << \"omitting the -p|--path e.g. quota ls /eos/ ...\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"=> if <path> is not terminated with a '/' it is assumed to be a file \"\n      << \"so it won't match the quota node with <path>/ !\" << std::endl;\n  fprintf(stdout, \"%s\", oss.str().c_str());\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_recycle.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file com_proto_recycle.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/RecycleHelper.hh\"\n#include <sstream>\n\nextern int com_recycle(char*);\nvoid com_recycle_help();\n\n//------------------------------------------------------------------------------\n// Recycle command entrypoint\n//------------------------------------------------------------------------------\nint com_protorecycle(char* arg)\n{\n  if (wants_help(arg)) {\n    com_recycle_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  RecycleHelper recycle(gGlobalOpts);\n\n  if (!recycle.ParseCommand(arg)) {\n    com_recycle_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = recycle.Execute(true, true);\n  return global_retc;\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_recycle_help()\n{\n  std::ostringstream oss;\n  oss << \"Usage: recycle [ls|purge|restore|config ...]\\n\"\n      << \"    provides recycle bin functionality\\n\"\n      << \"  recycle [-m]\\n\"\n      << \"    print status of recycle bin and config status if executed by root\\n\"\n      << \"    -m     : display info in monitoring format\\n\"\n      << std::endl\n      << \"  recycle ls [<date> [<limit>]] [-m] [-n] [--all] [--uid] [--rid <val>] \\n\"\n      << \"    list files in the recycle bin\\n\"\n      << \"    <date>      : can be <year>, <year>/<month> or <year>/<month>/<day> or\\n\"\n      << \"                   <year>/<month>/<day>/<index>\\n\"\n      << \"    <limit>     : maximum number of entries to return when listing\\n\"\n      << \"                  e.g.: recycle ls 2018/08/12 1000\\n\"\n      << \"    -m          : display info in monitoring format\\n\"\n      << \"    -n          : display numeric uid/gid(s) instead of names\\n\"\n      << \"    --all       : display entries of all users - only if root or admin\\n\"\n      << \"    --uid       : display entries for the current user id [default]\\n\"\n      << \"    --rid <val> : display entries corresponding to the given recycle id\\n\"\n      << \"                  which represents the container id of the top directory\\n\"\n      << \"                  e.g. recycle ls --rid 1001\\n\"\n      << std::endl\n      << \"  recycle purge [--all] [--uid] [--rid <val>] <date> | -k <key>\\n\"\n      << \"    purge files in the recycle bin either by date or by key\\n\"\n      << \"    --all       : purge entries of all users - only if root or admin\\n\"\n      << \"    --uid       : purge entries for the current user [default] \\n\"\n      << \"    --rid <val> : purge entries corresponding to the given recycle id\\n\"\n      << \"    <date>      : can be <year>, <year>/<month> or <year>/<month>/<day>\\n\"\n      << \"                  and can't be used together with a recycle key\\n\"\n      << \"    -k <key>    : purge only the given key\\n\"\n      << std::endl\n      << \"  recycle restore [-p] [-f|--force-original-name] [-r|--restore-versions] \"\n         \"<key>\\n\"\n      << \"    undo the deletion identified by the recycle <key>\\n\"\n      << \"    -p          : create all missing parent directories\\n\"\n      << \"    -f          : move deleted files/dirs back to their original location\\n\"\n      << \"                  (otherwise the key entry will have a <.inode> suffix)\\n\"\n      << \"    -r          : restore all previous versions of a file\\n\"\n      << std::endl\n      << \"  recycle project --path <path> [--acl <val>]\\n\"\n      << \"    setup a recycle id that will group all the recycled paths from\\n\"\n      << \"    the given top level directory <path>. Optionally, specify a list\\n\"\n      << \"    of ACLs that are appended to the recycle location and control the \\n\"\n      << \"    access to the recycled entries. The recycle id is represented by the\\n\"\n      << \"    container id of <path> and is used to construct the recycle path:\\n\"\n      << \"    /eos/<instance>/proc/recycle/rid:<cid_value>/2025...\\n\"\n      << \"    ACL val is the usual string representation of ACLs e.g u:1234=rx\\n\"\n      << std::endl\n      << \"  recycle config <key> <value>\\n\"\n      << \"    where <key> and <value> need to be one of the following:\\n\"\n      << \"    --dump\\n\"\n      << \"      dump the current recycle policy configuration\\n\"\n      << \"    [--add-bin|--remove-bin] <sub-tree>\\n\"\n      << \"      --add-bin    : enable recycle bin for deletion in <sub-tree>\\n\"\n      << \"      --remove-bin : disable recycle bin for <sub-tree>\\n\"\n      << \"    --enable <on/off>\\n\"\n      << \"      enable or disable the recycle bin functionality\\n\"\n      << \"      Default value: on\\n\"\n      << \"    --enforce <on/off>\\n\"\n      << \"      enforce default recycle bin location globally on the instance\\n\"\n      << \"      Default value: off\\n\"\n      << \"    --lifetime <seconds>\\n\"\n      << \"      configure FIFO lifetime for the recycle bin\\n\"\n      << \"    --ratio <0..1.0>\\n\"\n      << \"      configure the volume/inode keep ratio. E.g.: 0.8 means files\\n\"\n      << \"      will only be recycled if more than 80% of the volume/inodes\\n\"\n      << \"      quota is used. The low-watermark is by default 10% below the\\n\"\n      << \"      the given ratio.\\n\"\n      << \"    --size <value>[K|M|G]\\n\"\n      << \"      configure the quota for the maximum size of the recycle bin\\n\"\n      << \"      If no unit is set explicitly then bytes is assumed.\\n\"\n      << \"    --inodes <value>[K|M|G]\\n\"\n      << \"      configure the quota for the maximum number of inodes in the\\n\"\n      << \"      recycle bin.\\n\"\n      << \"    --dry-run <yes/no>\\n\"\n      << \"      when dry-run mode is enabled, no removal of entries is performed\\n\"\n      << \"    --collect-interval <seconds>\\n\"\n      << \"      how ofen the recycler collects new entries to be removed from\\n\"\n      << \"      the recycle bin. Default once per day i.e 86400 seconds.\\n\"\n      << \"      Change only for testing!\\n\"\n      << \"    --remove-interval <seconds>\\n\"\n      << \"      how often the recycler removes collected entries. The collected\\n\"\n      << \"      container ids to be removed are sharded and the removal is spread\\n\"\n      << \"      evenly across collect-interval/remove-interval slots. Default once\\n\"\n      << \"      every hour i.e. 3600. Change only for testing!\\n\"\n      << \"    Note: The last two parameters should be changed only for testing\\n\"\n      << \"    and while maintaining the following order: \\n\"\n      << \"    remove-interval << collection-interval\\n\"\n      << std::endl;\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_register.cc",
    "content": "//------------------------------------------------------------------------------\n// File: com_proto_register.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringTokenizer.hh\"\n#include \"common/Path.hh\"\n#include \"common/Timing.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/ICmdHelper.hh\"\n\nint com_protoregister(char*);\nvoid com_register_help();\n\n//------------------------------------------------------------------------------\n//! Class RegisterHelper\n//------------------------------------------------------------------------------\nclass RegisterHelper: public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  RegisterHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {\n    mIsAdmin = true;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~RegisterHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n};\n\nbool\nRegisterHelper::ParseCommand(const char* arg)\n{\n  XrdOucString option;\n  eos::console::FileRegisterProto* reg = mReq.mutable_record();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n\n  do {\n    XrdOucString s = tokenizer.GetToken();\n    if (s == \"-u\") {\n      reg->set_update(true);\n      s = tokenizer.GetToken();\n    }\n    if (s.length()) {\n      std::string param = s.c_str();\n      if (param.substr(0,4) == \"uid=\") {\n\tif (eos::common::StringConversion::IsDecimalNumber(param.substr(4))) {\n\t  reg->mutable_owner()->set_uid(std::stoi(param.substr(4).c_str(),0,10));\n\t} else {\n\t  reg->mutable_owner()->set_username(param.substr(4));\n\t}\n      } else if (param.substr(0,4) == \"gid=\") {\n\tif (eos::common::StringConversion::IsDecimalNumber(param.substr(4))) {\n\t  reg->mutable_owner()->set_gid(std::stoi(param.substr(4).c_str(),0,10));\n\t} else {\n\t  reg->mutable_owner()->set_groupname(param.substr(4));\n\t}\n      } else if (param.substr(0,5) == \"size=\") {\n\tif (eos::common::StringConversion::IsDecimalNumber(param.substr(5))) {\n\t  reg->set_size(std::stoull(param.substr(5).c_str()));\n\t} else {\n\t  return false;\n\t}\n      } else if (param.substr(0,5) == \"path=\") {\n\tstd::string path = param.substr(5);\n\tpath = eos::common::StringConversion::UnQuote(path);\n\tif (path.empty()) {\n\t  return false;\n\t}\n\tif (path.front() != '/') {\n\t  return false;\n\t}\n\treg->set_path(path);\n      } else if (param.substr(0,6) == \"xattr=\") {\n\tstd::string key,value;\n\tstd::string kv = eos::common::StringConversion::UnQuote(param.substr(6));\n\teos::common::StringConversion::SplitKeyValue(kv, key, value, \"=\");\n\tvalue = eos::common::StringConversion::UnQuote(value);\n\tif (key.length()) {\n\t  (*reg->mutable_attr())[key] = value;\n\t}\n      } else if (param.substr(0,6) == \"ctime=\") {\n\tstd::string t = param.substr(6);\n\tstruct timespec ts;\n\tif (eos::common::Timing::Timespec_from_TimespecStr(t,ts)) {\n\t  return false;\n\t}\n\treg->mutable_ctime()->set_sec(ts.tv_sec);\n\treg->mutable_ctime()->set_nsec(ts.tv_nsec);\n      } else if (param.substr(0,6) == \"atime=\") {\n\tstd::string t = param.substr(6);\n\tstruct timespec ts;\n\tif (eos::common::Timing::Timespec_from_TimespecStr(t,ts)) {\n\t  return false;\n\t}\n\treg->mutable_atime()->set_sec(ts.tv_sec);\n\treg->mutable_atime()->set_nsec(ts.tv_nsec);\n      } else if (param.substr(0,13) == \"atimeifnewer=\") {\n\tstd::string t = param.substr(13);\n\tstruct timespec ts;\n\tif (eos::common::Timing::Timespec_from_TimespecStr(t,ts)) {\n\t  return false;\n\t}\n\treg->mutable_atime()->set_sec(ts.tv_sec);\n\treg->mutable_atime()->set_nsec(ts.tv_nsec);\n\treg->set_atimeifnewer(true);\n      } else if (param.substr(0,6) == \"btime=\") {\n\tstd::string t = param.substr(6);\n\tstruct timespec ts;\n\tif (eos::common::Timing::Timespec_from_TimespecStr(t,ts)) {\n\t  return false;\n\t}\n\treg->mutable_btime()->set_sec(ts.tv_sec);\n\treg->mutable_btime()->set_nsec(ts.tv_nsec);\n      } else if (param.substr(0,6) == \"mtime=\") {\n\tstd::string t = param.substr(6);\n\tstruct timespec ts;\n\tif (eos::common::Timing::Timespec_from_TimespecStr(t,ts)) {\n\t  return false;\n\t}\n\treg->mutable_mtime()->set_sec(ts.tv_sec);\n\treg->mutable_mtime()->set_nsec(ts.tv_nsec);\n      } else if (param.substr(0,5) == \"mode=\") {\n\tif (eos::common::StringConversion::IsDecimalNumber(param.substr(5))) {\n\t  reg->set_mode(std::stoi(param.substr(5).c_str(),0, 8));\n\t} else {\n\t  return false;\n\t}\n      } else if (param.substr(0,9) == \"location=\") {\n\treg->mutable_locations()->Add(std::stoi(param.substr(9).c_str(),0,10));\n      } else if (param.substr(0,9) == \"layoutid=\") {\n\treg->set_layoutid(std::stoi(param.substr(9).c_str(),0,10));\n\tfprintf(stderr,\"layoutid:%d %s\\n\", std::stoi(param.substr(9).c_str(),0,10), param.substr(9).c_str());\n      } else if (param.substr(0,9) == \"checksum=\") {\n\treg->set_checksum(param.substr(9));\n      } else {\n\tstd::string path = param;\n\tpath = eos::common::StringConversion::UnQuote(path);\n\tif (path.empty()) {\n\t  return false;\n\t}\n\tif (path.front() != '/') {\n\t  return false;\n\t}\n\treg->set_path(path);\n      }\n    } else {\n      break;\n    }\n  } while (true);\n\n  if (reg->path().empty()) {\n    return false;\n  }\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Register command entry point\n//------------------------------------------------------------------------------\nint com_protoregister(char* arg)\n{\n  if (wants_help(arg)) {\n    com_register_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  RegisterHelper reg(gGlobalOpts);\n\n  if (!reg.ParseCommand(arg)) {\n    com_register_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = reg.Execute(true, true);\n\n  return global_retc;\n}\n\nvoid com_register_help()\n{\n  std::ostringstream oss;\n  oss << \"Usage: register [-u] <path> {tag1,tag2,tag3...}\"\n      << std::endl;\n  oss << \"          :  when called without the -u flag the parent has to exist while the basename should not exist\"  << std::endl;\n  oss << \"       -u :  if the file exists this will update all the provided meta-data of a file\" << std::endl;\n  oss << std::endl;\n  oss << \"       tagN is optional, but can be one or many of: \"\n      << std::endl;\n  oss << \"             size=100\" << std::endl;\n  oss << \"             uid=101 | username=foo\" << std::endl;\n  oss << \"             gid=102 | username=bar\" << std::endl;\n  oss << \"             checksum=abcdabcd\" << std::endl;\n  oss << \"             layoutid=00100112\" << std::endl;\n  oss << \"             location=1 location=2 ...\" << std::endl;\n  oss << \"             mode=777\" << std::endl;\n  oss << \"             btime=1670334863.101232\" << std::endl;\n  oss << \"             atime=1670334863.101232\" << std::endl;\n  oss << \"             ctime=1670334863.110123\" << std::endl;\n  oss << \"             mtime=1670334863.11234d\" << std::endl;\n  oss << \"             attr=\\\"sys.acl=u:100:rwx\\\"\" << std::endl;\n  oss << \"             attr=\\\"user.md=private\\\"\" << std::endl;\n  oss << \"             path=\\\"/eos/newfile\\\"   # can be used instead of the regular path argument of the path\" << std::endl;\n  oss << \"             atimeifnewer=1670334863.101233  # only update if this atime is newer than the existing one!\" << std::endl;\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_rm.cc",
    "content": "//------------------------------------------------------------------------------\n// File: com_protorm.cc\n// Author: Jozsef Makai - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringTokenizer.hh\"\n#include \"common/Path.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/ICmdHelper.hh\"\n\nextern int com_rm(char*);\nvoid com_rm_help();\n\n//------------------------------------------------------------------------------\n//! Class RmHelper\n//------------------------------------------------------------------------------\nclass RmHelper: public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  RmHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {\n    mIsAdmin = false;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~RmHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n};\n\nbool\nRmHelper::ParseCommand(const char* arg)\n{\n  XrdOucString option;\n  eos::console::RmProto* rm = mReq.mutable_rm();\n  eos::common::StringTokenizer tokenizer(arg);\n  bool noconfirmation = false;\n\n  tokenizer.GetLine();\n\n  while ((option = tokenizer.GetToken(false)).length() > 0 &&\n         (option.beginswith(\"-\"))) {\n    if ((option == \"-r\") || (option == \"-rf\") || (option == \"-fr\")) {\n      rm->set_recursive(true);\n    } else if ((option == \"-F\") || (option == \"--no-recycle-bin\")) {\n      rm->set_bypassrecycle(true);\n    } else if (option == \"-rF\" || option == \"-Fr\") {\n      rm->set_recursive(true);\n      rm->set_bypassrecycle(true);\n    } else if (option == \"--no-confirmation\") {\n      noconfirmation = true;\n    } else if ( option== \"--no-workflow\" || option == \"-n\" ) {\n      rm->set_noworkflow(true);\n    } else if ( option == \"--no-globbing\") {\n      rm->set_noglobbing(true);\n    } else {\n      return false;\n    }\n  }\n\n  auto path = option;\n\n  do {\n    XrdOucString param = tokenizer.GetToken();\n\n    if (param.length()) {\n      path += \" \";\n      path += param;\n    } else {\n      break;\n    }\n  } while (true);\n\n  // remove escaped blanks\n  while (path.replace(\"\\\\ \", \" \"));\n\n  if (path.length() == 0) {\n    return false;\n  }\n\n  auto id = 0ull;\n\n  if (Path2FileDenominator(path, id)) {\n    rm->set_fileid(id);\n    rm->set_recursive(false); // disable recursive option for files\n    path = \"\";\n  } else {\n    if (Path2ContainerDenominator(path, id)) {\n      rm->set_containerid(id);\n      path = \"\";\n    } else {\n      path = abspath(path.c_str());\n      rm->set_path(path.c_str());\n    }\n  }\n\n  eos::common::Path cPath(path.c_str());\n\n  if (path.length()) {\n    mNeedsConfirmation =\n        rm->recursive() && (cPath.GetSubPathSize() < 4) && !noconfirmation;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Rm command entry point\n//------------------------------------------------------------------------------\nint com_protorm(char* arg)\n{\n  if (wants_help(arg)) {\n    com_rm_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n\n  std::vector<std::string> paths;\n  std::string optStr;\n  std::string currentPath;\n  bool inOptions = true;\n\n  XrdOucString token;\n  while ((token = tokenizer.GetToken(false)).length() > 0) {\n    if (inOptions && token.beginswith(\"-\")) {\n      if (!optStr.empty()) {\n        optStr += \" \";\n      }\n      optStr += token.c_str();\n    } else {\n      inOptions = false;\n\n      // Check if this is a new path\n      if (token.beginswith(\"/\") || token.beginswith(\"fid:\") ||\n          token.beginswith(\"fxid:\") || token.beginswith(\"cid:\") ||\n          token.beginswith(\"cxid:\")) {\n        // Save previous path if exists\n        if (!currentPath.empty()) {\n          paths.push_back(std::move(currentPath));\n        }\n        currentPath = token.c_str();\n      } else {\n        // Continue current path\n        if (!currentPath.empty()) {\n          currentPath += \" \";\n        }\n        currentPath += token.c_str();\n      }\n    }\n  }\n\n  if (!currentPath.empty()) {\n    paths.push_back(std::move(currentPath));\n  }\n\n  if (paths.empty()) {\n    com_rm_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  // Execute rm for each path\n  int retc = 0;\n  for (const auto& path : paths) {\n    std::string cmdArg = optStr;\n    if (!cmdArg.empty()) {\n      cmdArg += \" \";\n    }\n    cmdArg += path;\n\n    RmHelper rm(gGlobalOpts);\n\n    if (!rm.ParseCommand(cmdArg.c_str())) {\n      com_rm_help();\n      global_retc = EINVAL;\n      return EINVAL;\n    }\n\n    if (rm.NeedsConfirmation() && !rm.ConfirmOperation()) {\n      retc = EINTR;\n      continue;\n    }\n\n    if (const int rc = rm.Execute(true, true); rc != 0) {\n      retc = rc;\n    }\n  }\n\n  global_retc = retc;\n  return global_retc;\n}\n\nvoid com_rm_help()\n{\n  std::ostringstream oss;\n  oss << \"Usage: rm [-r|-rf|-rF|-n] [--no-recycle-bin|-F] [--no-confirmation] [--no-workflow] [--no-globbing] [<path>|fid:<fid-dec>|fxid:<fid-hex>|cid:<cid-dec>|cxid:<cid-hex>]\"\n      << std::endl\n      << \"            -r | -rf : remove files/directories recursively\" << std::endl\n      << \"                     - the 'f' option is a convenience option with no additional functionality!\"\n      << std::endl\n      << \"                     - the recursive flag is automatically removed it the target is a file!\"\n      << std::endl << std::endl\n      << \" --no-recycle-bin|-F : remove bypassing recycling policies\" << std::endl\n      << \"                     - you have to take the root role to use this flag!\"\n      << std::endl << std::endl\n      << \"            -rF | Fr : remove files/directories recursively bypassing recycling policies\"\n      << std::endl\n      << \"                     - you have to take the root role to use this flag!\" <<\n      std::endl\n      << \"                     - the recursive flag is automatically removed it the target is a file!\"\n      << std::endl\n      << \" --no-workflow | -n  : don't run a workflow when deleting!\"\n      << std::endl\n      << \" --no-confirmation : don't ask for confirmation if recursive deletions is running in directory level < 4\"\n      << std::endl\n      << \" --no-globbing     : disables path globbing feature (e.g: delete a file containing '[]' characters)\" << std::endl;\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_route.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file com_proto_route.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringConversion.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/ParseUtils.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/ICmdHelper.hh\"\n\nconstexpr static int sDefaultXrdPort = 1094;\nconstexpr static int sDefaultHttpPort = 8000;\nvoid com_route_help();\n\n//------------------------------------------------------------------------------\n//! Class RouteHelper\n//------------------------------------------------------------------------------\nclass RouteHelper: public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  RouteHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~RouteHelper() = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg);\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Check path validity - it shouldn't contain spaces, '/./' or '/../' or\n  //! backslash characters. Append if necessary and end '/'.\n  //!\n  //! @param path given path\n  //!\n  //! @return true if valid, otherwise false\n  //----------------------------------------------------------------------------\n  bool ValidatePath(std::string& path) const;\n};\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool\nRouteHelper::ParseCommand(const char* arg)\n{\n  const char* option;\n  std::string soption;\n  eos::console::RouteProto* route = mReq.mutable_route();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  option = tokenizer.GetToken();\n  std::string cmd = (option ? option : \"\");\n\n  if (cmd == \"ls\") {\n    using eos::console::RouteProto_ListProto;\n    RouteProto_ListProto* list = route->mutable_list();\n\n    if (!(option = tokenizer.GetToken())) {\n      list->set_path(\"\");\n    } else {\n      soption = option;\n\n      if (!ValidatePath(soption)) {\n        return false;\n      }\n\n      list->set_path(soption);\n    }\n  } else if (cmd == \"unlink\") {\n    using eos::console::RouteProto_UnlinkProto;\n    RouteProto_UnlinkProto* unlink = route->mutable_unlink();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n\n    // Do basic checks that this is a path\n    soption = option;\n\n    if (!ValidatePath(soption)) {\n      return false;\n    }\n\n    unlink->set_path(soption);\n  } else if (cmd == \"link\") {\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n\n    // Do basic checks that this is a path\n    soption = option;\n\n    if (!ValidatePath(soption)) {\n      return false;\n    }\n\n    eos::console::RouteProto_LinkProto* link = route->mutable_link();\n    link->set_path(soption);\n\n    // Parse redirection locations which are \",\" separated\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n\n    soption = option;\n    std::vector<std::string> endpoints;\n    eos::common::StringConversion::Tokenize(soption, endpoints, \",\");\n\n    if (endpoints.empty()) {\n      return false;\n    }\n\n    for (const auto& endpoint : endpoints) {\n      eos::console::RouteProto_LinkProto_Endpoint* ep = link->add_endpoints();\n      std::vector<std::string> elems;\n      eos::common::StringConversion::Tokenize(endpoint, elems, \":\");\n      const std::string fqdn = elems[0];\n\n      if (!eos::common::ValidHostnameOrIP(fqdn)) {\n        std::cerr << \"error: invalid hostname specified\" << std::endl;\n        return false;\n      }\n\n      uint32_t xrd_port = sDefaultXrdPort;\n      uint32_t http_port = sDefaultHttpPort;\n\n      if (elems.size() == 3) {\n        try {\n          xrd_port = std::stoul(elems[1]);\n          http_port = std::stoul(elems[2]);\n        } catch (const std::exception& e) {\n          std::cerr << \"error: failed to parse ports for route\" << std::endl;\n          return false;\n        }\n      } else if (elems.size() == 2) {\n        try {\n          xrd_port = std::stoi(elems[1]);\n        } catch (const std::exception& e) {\n          std::cerr << \"error: failed to parse xrd port for route\" << std::endl;\n          return false;\n        }\n      }\n\n      ep->set_fqdn(fqdn);\n      ep->set_xrd_port(xrd_port);\n      ep->set_http_port(http_port);\n    }\n  } else {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Check if path is valid\n//------------------------------------------------------------------------------\nbool\nRouteHelper::ValidatePath(std::string& path) const\n{\n  if (path.empty() || path[0] != '/') {\n    std::cerr << \"error: path should be non-empty and start with '/'\"\n              << std::endl;\n    return false;\n  }\n\n  if (path.back() != '/') {\n    path += '/';\n  }\n\n  std::set<std::string> forbidden {\" \", \"/../\", \"/./\", \"\\\\\"};\n\n  for (const auto& needle : forbidden) {\n    if (path.find(needle) != std::string::npos) {\n      std::cerr << \"error: path should no contain any of the following \"\n                << \"sequences of characters: \\\" \\\", \\\"/../\\\", \\\"/./\\\" or \"\n                << \"\\\"\\\\\\\"\" << std::endl;\n      return false;\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Route command entrypoint\n//------------------------------------------------------------------------------\nint com_route(char* arg)\n{\n  if (wants_help(arg)) {\n    com_route_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  RouteHelper route(gGlobalOpts);\n\n  if (!route.ParseCommand(arg)) {\n    com_route_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = route.Execute();\n\n  return global_retc;\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_route_help()\n{\n  std::ostringstream oss;\n  oss << \"Usage: route [ls|link|unlink]\" << std::endl\n      << \"    namespace routing to redirect clients to external instances\"\n      << std::endl\n      << std::endl\n      << \"  route ls [<path>]\" << std::endl\n      << \"    list all routes or the one matching for the given path\"\n      << std::endl\n      << \"      * as the first character means the node is a master\"\n      << std::endl\n      << \"      _ as the first character means the node is offline\"\n      << std::endl\n      << std::endl\n      << \"  route link <path> <dst_host>[:<xrd_port>[:<http_port>]],...\"\n      << std::endl\n      << \"    create routing from <path> to destination host. If the xrd_port\"\n      << std::endl\n      << \"    is omitted the default 1094 is used, if the http_port is omitted\"\n      << std::endl\n      << \"    the default 8000 is used. Several dst_hosts can be specified by\"\n      << std::endl\n      << \"    separating them with \\\",\\\". The redirection will go to the MGM\"\n      << std::endl\n      << \"    from the specified list\"\n      << std::endl\n      << \"    e.g route /eos/dummy/ foo.bar:1094:8000\" << std::endl\n      << std::endl\n      << \"  route unlink <path>\" << std::endl\n      << \"    remove routing matching path\" << std::endl;\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_sched.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_proto_sched.cc\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/ParseUtils.hh\"\n\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/ICmdHelper.hh\"\n\nvoid com_sched_help();\n\n//------------------------------------------------------------------------------\n//! Class SchedHelper\n//------------------------------------------------------------------------------\nstruct SchedHelper: public ICmdHelper\n{\n  SchedHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {\n    mIsAdmin = true;\n  }\n\n  ~SchedHelper() = default;\n\n  bool ParseCommand(const char* arg) override;\n};\n\nbool SchedHelper::ParseCommand(const char* arg)\n{\n  eos::console::SchedProto* sched = mReq.mutable_sched();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  std::string token;\n\n  if (!tokenizer.NextToken(token)) {\n    return false;\n  }\n\n  if (token == \"configure\" || token == \"config\") {\n    eos::console::SchedProto_ConfigureProto* config = sched->mutable_config();\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"type\") {\n      eos::console::SchedProto_TypeProto* type = config->mutable_type();\n\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      type->set_schedtype(token);\n    } else if (token == \"weight\") {\n      eos::console::SchedProto_WeightProto* weight_prot = config->mutable_weight();\n\n      std::string space, id_str, weight_str;\n      bool status = tokenizer.NextToken(space) && tokenizer.NextToken(id_str) && tokenizer.NextToken(weight_str);\n      if (!status) {\n        return false;\n      }\n\n      int32_t item_id;\n      uint8_t weight;\n      eos::common::StringToNumeric(id_str, item_id);\n      eos::common::StringToNumeric(weight_str, weight);\n\n      weight_prot->set_id(item_id);\n      weight_prot->set_weight(weight);\n      weight_prot->set_spacename(space);\n    } else if (token == \"show\") {\n      eos::console::SchedProto_ShowProto* show_prot = config->mutable_show();\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      if (token == \"type\") {\n        show_prot->set_option(eos::console::SchedProto_ShowProto::TYPE);\n        if (tokenizer.NextToken(token)) {\n          show_prot->set_spacename(token);\n        }\n      }\n    } else if (token == \"forcerefresh\") {\n      // TODO: implement a space level refresh command; however it requires a\n      // deep copy of the internal spacemap ptrs of all other spaces other than\n      // the asked space probably easier to just do a full refresh\n      config->mutable_refresh();\n    }\n\n  } else if (token == \"ls\") {\n    // Implement me!\n    eos::console::SchedProto_LsProto* ls = sched->mutable_ls();\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n    ls->set_spacename(token);\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"bucket\") {\n      ls->set_option(eos::console::SchedProto_LsProto::BUCKET);\n    } else if (token == \"disk\") {\n      ls->set_option(eos::console::SchedProto_LsProto::DISK);\n    } else {\n      ls->set_option(eos::console::SchedProto_LsProto::ALL);\n    }\n  }\n\n  return true;\n}\n\nint com_proto_sched(char* arg)\n{\n  if (wants_help(arg)) {\n    com_sched_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  SchedHelper sched(gGlobalOpts);\n\n  if (!sched.ParseCommand(arg)) {\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = sched.Execute();\n  return global_retc;\n}\n\nvoid com_sched_help()\n{\n  std::ostringstream oss;\n  oss << \" Usage:\\n\"\n      << \" sched configure type <schedtype>\\n\"\n      << \"\\t <schedtype> is one of roundrobin,weightedrr,tlrr,random,weightedrandom,geo\\n\"\n      << \"\\t if configured via space; space takes precedence\\n\"\n      << \" sched configure weight <space> <fsid> <weight>\\n\"\n      << \"\\t configure weight for a given fsid in the given space\\n\"\n      << \" sched configure show type [spacename]\\n\"\n      << \"\\t show existing configured scheduler; optionally for space\\n\"\n      << \" sched configure forcerefresh [spacename]\\n\"\n      << \"\\t Force refresh scheduler internal state\\n\"\n      << \" ls <spacename> <bucket|disk|all>\\n\"\n      << std::endl;\n  std::cerr << oss.str();\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_space.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: com_space_node.cc\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your token) any later version.                                   *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <streambuf>\n#include <string>\n#include <cerrno>\n#include <XrdOuc/XrdOucEnv.hh>\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/SymKeys.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/ICmdHelper.hh\"\n#include \"mgm/tgc/Constants.hh\"\n#include \"mgm/http/rest-api/Constants.hh\"\n\nvoid com_space_help();\n\n//------------------------------------------------------------------------------\n//! Class SpaceHelper\n//------------------------------------------------------------------------------\nclass SpaceHelper : public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  SpaceHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {\n    mIsAdmin = true;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~SpaceHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n};\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool SpaceHelper::ParseCommand(const char* arg)\n{\n  eos::console::SpaceProto* space = mReq.mutable_space();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  std::string token;\n\n  if (!tokenizer.NextToken(token)) {\n    return false;\n  }\n\n  if (token == \"ls\") {\n    eos::console::SpaceProto_LsProto* ls = space->mutable_ls();\n\n    while (tokenizer.NextToken(token)) {\n      if (token == \"-s\") {\n        mIsSilent = true;\n      } else if (token == \"-g\") {\n        if (!tokenizer.NextToken(token) ||\n            !eos::common::StringTokenizer::IsUnsignedNumber(token)) {\n          std::cerr << \"error: geodepth was not provided or it does not have \"\n                    << \"the correct value: geodepth should be a positive \"\n                    << \"integer\" << std::endl;\n          return false;\n        }\n\n        try {\n          ls->set_outdepth(std::stoi(token));\n        } catch (const std::exception& e) {\n          std::cerr << \"error: argument needs to be numeric\" << std::endl;\n          return false;\n        }\n      } else if (token == \"-m\") {\n        ls->set_outformat(eos::console::SpaceProto_LsProto::MONITORING);\n      } else if (token == \"-l\") {\n        ls->set_outformat(eos::console::SpaceProto_LsProto::LISTING);\n      } else if (token == \"--io\") {\n        ls->set_outformat(eos::console::SpaceProto_LsProto::IO);\n      } else if (token == \"--fsck\") {\n        ls->set_outformat(eos::console::SpaceProto_LsProto::FSCK);\n      } else if ((token.find('-') != 0)) { // does not begin with \"-\"\n        ls->set_selection(token);\n      } else {\n        return false;\n      }\n    }\n  } else if (token == \"tracker\") {\n    eos::console::SpaceProto_TrackerProto* tracker = space->mutable_tracker();\n    tracker->set_mgmspace(\"default\");\n  } else if (token == \"inspector\") {\n    eos::console::SpaceProto_InspectorProto* inspector = space->mutable_inspector();\n    inspector->set_mgmspace(\"default\");\n    std::string options;\n\n    while (tokenizer.NextToken(token)) {\n      if ((token == \"-s\") || (token == \"--space\")) {\n        if (tokenizer.NextToken(token)) {\n          inspector->set_mgmspace(token);\n        } else {\n          std::cerr << \"error: no space specified\" << std::endl;\n          return false;\n        }\n      } else if (token == \"-c\" || token == \"--current\") {\n        options += \"c\";\n      } else if (token == \"-l\" || token == \"--last\") {\n        options += \"l\";\n      } else if (token == \"-m\") {\n        options += \"m\";\n      } else if (token == \"-p\") {\n        options += \"p\";\n      } else if (token == \"-e\") {\n        options += \"e\";\n      } else if (token == \"-C\" || token == \"--cost\") {\n        options += \"C\";\n      } else if (token == \"-U\" || token == \"--usage\") {\n        options += \"U\";\n      } else if (token == \"-L\" || token == \"--layouts\") {\n        options += \"L\";\n      } else if (token == \"-B\" || token == \"--birth\") {\n        options += \"B\";\n      } else if (token == \"-A\" || token == \"--access\") {\n        options += \"A\";\n      } else if (token == \"-a\" || token == \"--all\") {\n        options += \"Z\";\n      } else if (token == \"-V\" || token == \"--vs\") {\n        options += \"V\";\n      } else if (token == \"-M\" || token == \"--money\") {\n        options += \"M\";\n      } else {\n        return false;\n      }\n    }\n\n    inspector->set_options(options);\n  } else if (token == \"reset\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::SpaceProto_ResetProto* reset = space->mutable_reset();\n    reset->set_mgmspace(token);\n\n    while (tokenizer.NextToken(token)) {\n      if (token == \"--egroup\") {\n        reset->set_option(eos::console::SpaceProto_ResetProto::EGROUP);\n      } else if (token == \"--mapping\") {\n        reset->set_option(eos::console::SpaceProto_ResetProto::MAPPING);\n      } else if (token == \"--drain\") {\n        reset->set_option(eos::console::SpaceProto_ResetProto::DRAIN);\n      } else if (token == \"--scheduledrain\") {\n        reset->set_option(eos::console::SpaceProto_ResetProto::SCHEDULEDRAIN);\n      } else if (token == \"--schedulebalance\") {\n        reset->set_option(eos::console::SpaceProto_ResetProto::SCHEDULEBALANCE);\n      } else if (token == \"--ns\") {\n        reset->set_option(eos::console::SpaceProto_ResetProto::NS);\n      } else if (token == \"--nsfilesystemview\") {\n        reset->set_option(eos::console::SpaceProto_ResetProto::NSFILESISTEMVIEW);\n      } else if (token == \"--nsfilemap\") {\n        reset->set_option(eos::console::SpaceProto_ResetProto::NSFILEMAP);\n      } else if (token == \"--nsdirectorymap\") {\n        reset->set_option(eos::console::SpaceProto_ResetProto::NSDIRECTORYMAP);\n      } else {\n        return false;\n      }\n    }\n  } else if (token == \"define\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::SpaceProto_DefineProto* define = space->mutable_define();\n    define->set_mgmspace(token);\n\n    if (!tokenizer.NextToken(token)) {\n      define->set_groupsize(0);\n      define->set_groupmod(24);\n    } else {\n      define->set_groupsize(std::stoi(token));\n\n      if (!tokenizer.NextToken(token)) {\n        define->set_groupmod(24);\n      } else {\n        define->set_groupmod(std::stoi(token));\n      }\n    }\n  } else if (token == \"set\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::SpaceProto_SetProto* set = space->mutable_set();\n    set->set_mgmspace(token);\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"on\") {\n      set->set_state_switch(true);\n    } else if (token == \"off\") {\n      set->set_state_switch(false);\n    } else {\n      return false;\n    }\n  } else if (token == \"rm\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::SpaceProto_RmProto* rm = space->mutable_rm();\n    rm->set_mgmspace(token);\n  } else if (token == \"status\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::SpaceProto_StatusProto* status = space->mutable_status();\n    status->set_mgmspace(token);\n\n    if (tokenizer.NextToken(token)) {\n      if (token == \"-m\") {\n        status->set_outformat_m(true);\n      } else {\n        return false;\n      }\n    }\n\n    std::string contents =\n      eos::common::StringConversion::StringFromShellCmd(\"cat /var/eos/md/stacktrace 2> /dev/null\");\n  } else if (token == \"node-set\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::SpaceProto_NodeSetProto* nodeset = space->mutable_nodeset();\n    nodeset->set_mgmspace(token);\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    nodeset->set_nodeset_key(token);\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token.find('/') == 0) { // if begins with \"/\"\n      std::ifstream ifs(token, std::ios::in | std::ios::binary);\n\n      if (!ifs) {\n        std::cerr << \"error: unable to read \" << token << \" - errno=\" << errno << '\\n';\n        return false;\n      }\n\n      std::string val = std::string((std::istreambuf_iterator<char>(ifs)),\n                                    std::istreambuf_iterator<char>());\n\n      if (val.length() > 512) {\n        std::cerr <<\n                  \"error: the file contents exceeds 0.5 kB - configure a file hosted on the MGM using file:<mgm-path>\\n\";\n        return false;\n      }\n\n      // store the value b64 encoded\n      XrdOucString val64;\n      eos::common::SymKey::Base64Encode((char*) val.c_str(), val.length(), val64);\n\n      while (val64.replace(\"=\", \":\")) {}\n\n      nodeset->set_nodeset_value(std::string((\"base64:\" + val64).c_str()));\n    } else {\n      nodeset->set_nodeset_value(token);\n    }\n  } else if (token == \"node-get\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::SpaceProto_NodeGetProto* nodeget = space->mutable_nodeget();\n    nodeget->set_mgmspace(token);\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    nodeget->set_nodeget_key(token);\n  } else if (token == \"quota\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::SpaceProto_QuotaProto* quota = space->mutable_quota();\n    quota->set_mgmspace(token);\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"on\") {\n      quota->set_quota_switch(true);\n    } else if (token == \"off\") {\n      quota->set_quota_switch(false);\n    } else {\n      return false;\n    }\n  } else if (token == \"config\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::SpaceProto_ConfigProto* config = space->mutable_config();\n\n    if (token == \"rm\") {\n      config->set_remove(true);\n\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n    }\n\n    config->set_mgmspace_name(token);\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (config->remove()) {\n      config->set_mgmspace_key(token);\n    } else {\n      std::string::size_type pos = token.find('=');\n\n      // contains 1 and only 1 '='. It expects a token like <key>=<value>\n      if ((pos != std::string::npos) &&\n          (count(token.begin(), token.end(), '=') == 1)) {\n        config->set_mgmspace_key(token.substr(0, pos));\n        config->set_mgmspace_value(token.substr(pos + 1, token.length() - 1));\n      } else {\n        return false;\n      }\n    }\n  } else if (token == \"groupbalancer\") {\n    // Parsing eos space groupbalancer <subcmd> <space-name> <options>\n    auto groupbalancer = space->mutable_groupbalancer();\n\n    // subcmd\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"status\") {\n      // spacename\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      groupbalancer->set_mgmspace(token);\n      auto groupbalancer_status = groupbalancer->mutable_status();\n      // Now parse options\n      std::string options;\n\n      while (tokenizer.NextToken(token)) {\n        if (token == \"--detail\" || token == \"-d\") {\n          options += \"d\";\n        } else if (token == \"-m\") {\n          options += \"m\";\n        }\n      }\n\n      if (!options.empty()) {\n        groupbalancer_status->set_options(options);\n      }\n\n      return true;\n    }\n\n    return false;\n  } else if (token == \"groupdrainer\") {\n    auto groupdrainer = space->mutable_groupdrainer();\n\n    // subcmd\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"status\") {\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      groupdrainer->set_mgmspace(token);\n      auto status_cmd = groupdrainer->mutable_status();\n\n      if (tokenizer.NextToken(token)) {\n        if (token == \"--detail\" || token == \"-d\") {\n          status_cmd->set_outformat(\n            eos::console::SpaceProto::GroupDrainerStatusProto::DETAIL);\n        } else if (token == \"-m\") {\n          status_cmd->set_outformat(\n            eos::console::SpaceProto::GroupDrainerStatusProto::MONITORING);\n        }\n      }\n\n      return true;\n    } else if (token == \"reset\") {\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      groupdrainer->set_mgmspace(token);\n      auto reset_cmd = groupdrainer->mutable_reset();\n\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      if (token == \"--failed\") {\n        reset_cmd->set_option(eos::console::SpaceProto::GroupDrainerResetProto::FAILED);\n      } else if (token == \"--all\") {\n        reset_cmd->set_option(eos::console::SpaceProto::GroupDrainerResetProto::ALL);\n      }\n    }\n  } else { // no proper subcommand\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Space command entry point\n//------------------------------------------------------------------------------\nint com_proto_space(char* arg)\n{\n  if (wants_help(arg)) {\n    com_space_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  SpaceHelper space(gGlobalOpts);\n\n  if (!space.ParseCommand(arg)) {\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = space.Execute();\n  return global_retc;\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_space_help()\n{\n  std::ostringstream oss;\n  oss\n      << \" usage:\\n\"\n      << \"space ls [-s|-g <depth>] [-m|-l|--io|--fsck] [<space>] : list in all spaces or select only <space>. <space> is a substring match and can be a comma separated list\\n\"\n      << \"\\t      -s : silent mode\\n\"\n      << \"\\t      -m : monitoring key=value output format\\n\"\n      << \"\\t      -l : long output - list also file systems after each space\\n\"\n      << \"\\t      -g : geo output - aggregate space information along the instance geotree down to <depth>\\n\"\n      << \"\\t    --io : print IO statistics\\n\"\n      << \"\\t  --fsck : print filesystem check statistics\\n\"\n      << std::endl\n      << \"space config <space-name> space.attr.<key> =[<>|]<value>               : configure a space extended attribute which is added to all directories referencing this space via sys.forced.space\"\n      << \"                                                                        space.attr.sys.acl=<u:1000:rwx (the < sign indicates to add to the acl on the left side\"\n      << \"                                                                        space.attr.sys.acl=>u:1000:rwx (the > sign indicates to add to the acl on the right side\"\n      << \"                                                                        space.attr.sys.acl=|u:1000:rwx (the | sign indicates to set the acl if there is none defined\"\n      << \"                                                                        space.attr.sys.foo=bar ( the sys.foo attribute is overwriting the local sys.foo attribute\"\n      << \"                                                                        space.attr.sys.foo=|bar ( the sys.foo attribute is set only if there is no local sys.foo attribute\"\n      << \"space config <space-name> space.nominalsize=<value>                   : configure the nominal size for this space\\n\"\n      << \"space config <space-name> space.balancer=on|off                       : enable/disable the space balancer [ default=off ]\\n\"\n      << \"space config <space-name> space.balancer.threshold=<percent>          : configure the used bytes deviation which triggers balancing             [ default=20 (%%)     ] \\n\"\n      << \"space config <space-name> space.balancer.node.rate=<MB/s>             : configure the nominal transfer bandwidth per running transfer on a node [ default=25 (MB/s)   ]\\n\"\n      << \"space config <space-name> space.balancer.node.ntx=<#>                 : configure the number of parallel balancing transfers per node           [ default=2 (streams) ]\\n\"\n      << \"space config <space-name> space.balancer.max-queue-jobs=<#>           : configure the maximum number of queued jobs allowed in the balancer thread pool [ default=1000 (jobs) ]\\n\"\n      << \"space config <space-name> space.balancer.max-thread-pool-size=<#>     : configure the maximum number of threads to be used in the balancer thread pool [ default=100 (threads) ]\\n\"\n      << \"space config <space-name> space.balancer.update.interval=<#>          : configure the update interval of the balancing statistics used for spawning transfers [ default=60 (seconds) min=1 max=300]\\n\"\n      << \"space config <space-name> space.drainer.tx.minrate=<MB/s >            : configure the minimum transfer bandwidth per running transfer used for computing transfer timeout [ default=25 (MB/s) ]\\n\"\n      << \"space config <space-name> space.drainer.fs.ntx=<#>                    : configure the number of parallel draining transfers per fs [ default=5 ]\\n\"\n      << \"space config <space-name> space.groupbalancer=on|off                  : enable/disable the group balancer [ default=off ]\\n\"\n      << \"space config <space-name> space.groupbalancer.ntx=<ntx>               : configure the number of parallel group balancer jobs per 10s [ default=10 ]\\n\"\n      << \"space config <space-name> space.groupbalancer.engine=[value]          : configure the groupbalancer engine - std/minmax/freespace [ default=std ]\\n\"\n      << \"space config <space-name> space.groupbalancer.min_threshold=<v>       : configure the groupbalancer min threshold(%), groups below this will be picked as targets [default=60]\\n\"\n      << \"space config <space-name> space.groupbalancer.max_threshold=<v>       : configure the groupbalancer max threshold(%), groups above this will be picked as sources [default=95]\\n\"\n      << \"space config <space-name> space.groupbalancer.min_file_size=<#K/M/G/T>: configure the min file size to move between groups [ default=1G ]\\n\"\n      << \"space config <space-name> space.groupbalancer.max_file_size=<#K/M/G/T>: configure the max file size to move between groups [ default=16G ]\\n\"\n      << \"space config <space-name> space.groupbalancer.file_attempts=<#>       : configure the no of attempts to find a file within sizes [ default=50 ]\\n\"\n      << \"space config <space-name> space.groupbalancer.threshold=<threshold>   : [Deprecated use <..>.min/max_threshold (see above)] configure the threshold when a group is balanced\\n\"\n      << \"space config <space-name> space.groupbalancer.blocklist=<list>        : comma list eg. group1, group2 of groups blocklisted (only available for freespace engine)\\n\"\n      << \"space config <space-name> space.geobalancer=on|off                    : enable/disable the geo balancer [ default=off ]\\n\"\n      << \"space config <space-name> space.geobalancer.ntx=<ntx>                 : configure the number of parallel geobalancer jobs [ default=0 ]\\n\"\n      << \"space config <space-name> space.geobalancer.threshold=<threshold>     : configure the threshold when a geotag is balanced [ default=0 ] \\n\"\n      << \"space config <space-name> space.groupdrainer=on|off                   : enable/disable the group drainer [ default=on ]\\n\"\n      << \"space config <space-name> space.groupdrainer.threshold=<threshold>    : configure the threshold(%) for picking target groups\\n\"\n      << \"space config <space-name> space.groupdrainer.group_refresh_interval   : configure time in seconds for refreshing cached groups info [default=300]\\n\"\n      << \"space config <space-name> space.groupdrainer.retry_interval           : configure time in seconds for retrying failed drains [default=4*3600]\\n\"\n      << \"space config <space-name> space.groupdrainer.retry_count              : configure the amount of retries for failed drains [default=5]\\n\"\n      << \"space config <space-name> space.groupdrainer.ntx                      : configure the max file transfer queue size [default=10000]\\n\"\n      << \"space config <space-name> space.lru=on|off                            : enable/disable the LRU policy engine [ default=off ]\\n\"\n      << \"space config <space-name> space.lru.interval=<sec>                    : configure the default lru scan interval\\n\"\n      << \"space config <space-name> fs.max.ropen=<n>                            : allow more than <n> read streams per disk in the given space\\n\"\n      << \"space config <space-name> fs.max.wopen=<n>                            : allow more than <n> write streams per disk in the given space\\n\"\n      << \"space config <space-name> space.wfe=on|off|paused                     : enable/disable the Workflow Engine [ default=off ]\\n\"\n      << \"space config <space-name> space.wfe.interval=<sec>                    : configure the default WFE scan interval\\n\"\n      << \"space config <space-name> space.headroom=<size>                       : configure the default disk headroom if not defined on a filesystem (see fs for details)\\n\"\n      << \"space config <space-name> space.scaninterval=<sec>                    : configure the default scan interval if not defined on a filesystem (see fs for details)\\n\"\n      << \"space config <space-name> space.scan_rain_interval=<sec>              : configure the default rain scan interval if not defined on a filesystem (see fs for details)\\n\"\n      << \"space config <space-name> space.scanrate=<MB/S>                       : configure the default scan rate if not defined on a filesystem     (see fs for details)\\n\"\n      << \"space config <space-name> space.scan_disk_interva=<sec>               : time interval after which the disk scanner will run, default 4h\\n\"\n      << \"space config <space-name> space.scan_ns_interval=<sec>                : time interval after which the namespace scanner will run, default 3 days\\n\"\n      << \"space config <space-name> space.scan_ns_rate=entry/sec                : namespace scan rate in terms of number of stat requests per second done against the local disk\\n\"\n      << \"space config <space-name> space.scheduler.type=<type>                 : configure the default scheduler for space, eg. geo, roundrobin, weightedrandom etc\\n\"\n      << \"space config <space-name> space.drainperiod=<sec>                     : configure the default drain  period if not defined on a filesystem (see fs for details)\\n\"\n      << \"space config <space-name> space.graceperiod=<sec>                     : configure the default grace  period if not defined on a filesystem (see fs for details)\\n\"\n      << \"space config <space-name> space.filearchivedgc=on|off                 : enable/disable the 'file archived' garbage collector [ default=off ]\\n\"\n      << \"space config <space-name> space.tracker=on|off                        : enable/disable the space layout creation tracker [ default=off ]\\n\"\n      << \"space config <space-name> space.inspector=on|off                      : enable/disable the file inspector [ default=off ]\\n\"\n      << \"space config <space-name> space.inspector.interval=<sec>              : time interval after which the inspector will run, default 4h\\n\"\n      << \"space config <space-name> space.inspector.price.currency=[0-5]        : currency printed by the cost evaluation ( 0=EOS, 1=CHF, 2=EUR, 3=USD, 4=AUD, 5=YEN )\\n\"\n      << \"space config <space-name> space.inspector.price.disk.tbyear=<price>   : set the price of a tb year of data on disk without redundancy (default=20)\\n\"\n      << \"space config <space-name> space.inspector.price.tape.tbyear=<price>   : set the price of a tb year of data on disk without redundancy (default=10)\\n\"\n      << \"space config <space-name> space.geo.access.policy.write.exact=on|off  : if 'on' use exact matching geo replica (if available), 'off' uses weighting [ for write case ]\\n\"\n      << \"space config <space-name> space.geo.access.policy.read.exact=on|off   : if 'on' use exact matching geo replica (if available), 'off' uses weighting [ for read  case ]\\n\"\n      << \"space config <space-name> fs.<key>=<value>                            : configure file system parameters for each filesystem in this space (see help of 'fs config' for details)\\n\"\n      << \"space config <space-name> space.policy.[layout|nstripes|checksum|blockchecksum|blocksize|bw|schedule|iopriority|iotype]=<value>      \\n\"\n      << \"                                                                      : configure default file layout creation settings as a space policy - a value='remove' deletes the space policy\\n\"\n      << std::endl\n      << \"space config <space-name> space.altxs=on|off                          : enable/disable the alternative checksums computation when the file is uploaded\\n\"\n      << std::endl\n      << \"TAPE REST API specific parameters:\\n\"\n      << \"space config default \" << eos::mgm::rest::TAPE_REST_API_SWITCH_ON_OFF <<\n      \"=on|off                               : enable/disable the tape REST API handler [ default=off ]\\n\"\n      << \"space config default \" << eos::mgm::rest::TAPE_REST_API_STAGE_SWITCH_ON_OFF\n      << \"=on|off                         : enable/disable the tape REST API STAGE resource [ default=off ]\\n\"\n      << std::endl\n      << \"Tape specific configuration parameters:\\n\"\n      << \"space config <space-name> space.\" << eos::mgm::tgc::TGC_NAME_QRY_PERIOD_SECS\n      << \"=<#>                 : tape-aware GC query period in seconds [ default=\" <<\n      eos::mgm::tgc::TGC_DEFAULT_QRY_PERIOD_SECS << \" ]\\n\"\n      << \"                                                                        => value must be > 0 and <= \"\n      << eos::mgm::tgc::TGC_MAX_QRY_PERIOD_SECS << \"\\n\"\n      << \"space config <space-name> space.\" <<\n      eos::mgm::tgc::TGC_NAME_FREE_BYTES_SCRIPT <<\n      \"=<path>            : optional path to a script used to determine the number of free bytes in a given EOS space [ default='\"\n      << eos::mgm::tgc::TGC_DEFAULT_FREE_BYTES_SCRIPT << \"' ]\\n\"\n      << \"                                                                        => an empty or invalid path means the compile time default way of determining free space will be used\\n\"\n      << \"space config <space-name> space.\" << eos::mgm::tgc::TGC_NAME_AVAIL_BYTES <<\n      \"=<#>                    : configure the number of available bytes the space should have [ default=\"\n      << eos::mgm::tgc::TGC_DEFAULT_AVAIL_BYTES << \" ] \\n\"\n      << \"space config <space-name> space.\" << eos::mgm::tgc::TGC_NAME_TOTAL_BYTES <<\n      \"=<#>                    : configure the total number of bytes the space should have before the tape-aware GC kicks in [ default=\"\n      << eos::mgm::tgc::TGC_DEFAULT_TOTAL_BYTES << \" ] \\n\"\n      << std::endl\n      << \"space config rm <space-name> <key>                   : remove the given key from the space configuration\\n\"\n      << std::endl\n      << \"space define <space-name> [<groupsize> [<groupmod>]] : define how many filesystems can end up in one scheduling group <groupsize> [ default=0 ]\\n\"\n      << \"                                                       => <groupsize>=0 means that no groups are built within a space, otherwise it should be the maximum number of nodes in a scheduling group\\n\"\n      << \"                                                       => <groupmod> maximum number of groups in the space, which should be at least equal to the maximum number of filesystems per node\\n\"\n      << std::endl\n      << \"space inspector [--current|-c] [--last|-l] [-m] [-p] [-e] [-s|--space <space_name>] [--all|-a] [--cost|-C] [--usage|-U] [--birth|-B] [--access|-A] [--vs|-V] [--layouts|-L] : show namespace inspector output\\n\"\n      << \"\\t  -c  : show current scan\\n\"\n      << \"\\t  -l  : show last complete scan\\n\"\n      << \"\\t  -m  : print last scan in monitoring format ( by default this enables --cost --usage --birth --access --layouts)\\n\"\n      << \"\\t  -A  : combined with -m prints access time distributions\\n\"\n      << \"\\t  -V  : combined with -m prints birth time vs access time distributions\\n\"\n      << \"\\t  -B  : combined with -m prints birth time distributions\\n\"\n      << \"\\t  -C  : combined with -m prints cost information (storage price per user/group)\\n\"\n      << \"\\t  -U  : combined with -m prints usage information (stored bytes per user/group)\\n\"\n      << \"\\t  -L  : combined with -m prints layout statistics\\n\"\n      << \"\\t  -a  : combined with -m or -C or -U removes the restriction to show only the top 10 user ranking\\n\"\n      << \"\\t  -p  : combined with -c or -l lists erroneous files\\n\"\n      << \"\\t  -e  : combined with -c or -l exports erroneous files on the MGM into /var/log/eos/mgm/FileInspector.<date>.list\\n\"\n      << \"\\t  -s  : select target space, by default \\\"default\\\" space is used\\n\"\n      << std::endl\n      << \"space node-set <space-name> <node.key> <file-name> : store the contents of <file-name> into the node configuration variable <node.key> visible to all FSTs\\n\"\n      << \"                                                     => if <file-name> matches file:<path> the file is loaded from the MGM and not from the client\\n\"\n      << \"                                                     => local files cannot exceed 512 bytes - MGM files can be arbitrary length\\n\"\n      << \"                                                     => the contents gets base64 encoded by default\\n\"\n      << std::endl\n      << \"space node-get <space-name> <node.key> : get the value of <node.key> and base64 decode before output\\n\"\n      << \"                                         => if the value for <node.key> is identical for all nodes in the referenced space, it is dumped only once, otherwise the value is dumped for each node separately\\n\"\n      << std::endl\n      << \"space reset <space-name> [--egroup|mapping|drain|scheduledrain|schedulebalance|ns|nsfilesystemview|nsfilemap|nsdirectorymap] : reset different space attributes\\n\"\n      << \"\\t            --egroup : clear cached egroup information\\n\"\n      << \"\\t           --mapping : clear all user/group uid/gid caches\\n\"\n      << \"\\t             --drain : reset draining\\n\"\n      << \"\\t     --scheduledrain : reset drain scheduling map\\n\"\n      << \"\\t   --schedulebalance : reset balance scheduling map\\n\"\n      << \"\\t                --ns : resize all namespace maps\\n\"\n      << \"\\t  --nsfilesystemview : resize namespace filesystem view\\n\"\n      << \"\\t         --nsfilemap : resize namespace file map\\n\"\n      << \"\\t    --nsdirectorymap : resize namespace directory map\\n\"\n      << std::endl\n      << \"space status <space-name> [-m] : print all defined variables for space\\n\"\n      << std::endl\n      << \"space tracker : print all file replication tracking entries\\n\"\n      << std::endl\n      << \"space set <space-name> on|off : enable/disable all groups under that space\\n\"\n      << \"                                => <on> value will enable all nodes, <off> value won't affect nodes\\n\"\n      << std::endl\n      << \"space rm <space-name> : remove space\\n\"\n      << std::endl\n      << \"space quota <space-name> on|off : enable/disable quota\\n\"\n      << std::endl\n      << \"space groupbalancer status <space-name> [--detail(-d)|-m] : print groupbalancer status\\n\"\n      << std::endl\n      << \"space groupdrainer status <space-name> [--detail(-d)|-m]  : print groupdrainer status\\n\"\n      << \"space groupdrainer reset <space-name> <--failed|--all>    : reset failed transfers/all caches\\n\"\n      << std::endl;\n  std::cerr << oss.str();\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_proto_token.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file com_proto_token.cc\n//! @author Andreas-Joachim Peteres - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/TokenHelper.hh\"\n\nvoid com_token_help();\n\n//------------------------------------------------------------------------------\n// Token command entry point\n//------------------------------------------------------------------------------\nint com_proto_token(char* arg)\n{\n  if (wants_help(arg)) {\n    com_token_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  TokenHelper token(gGlobalOpts);\n\n  if (!token.ParseCommand(arg)) {\n    com_token_help();\n    global_retc = EINVAL;\n    return EINVAL;\n  }\n\n  global_retc = token.Execute(true, true);\n  return global_retc;\n}\n\n//------------------------------------------------------------------------------\n// Print help message\n//------------------------------------------------------------------------------\nvoid com_token_help()\n{\n  std::ostringstream oss;\n  oss << \"Usage: token --token  <token> | --path <path> --expires <expires> [--permission <perm>] [--owner <owner>] [--group <group>] [--tree] [--origin <origin1> [--origin <origin2>] ...]] \\n\"\n      << \"    get or show a token\\n\\n\"\n      << \"       token --token <token> \\n\"\n      << \"                                           : provide a JSON dump of a token - independent of validity\\n\"\n      << \"             --path <path>                 : define the namespace restriction - if ending with '/' this is a directory or tree, otherwise it references a file\\n\"\n      << \"             --path <path1>://:<path2>://: ...\"\n      << \"                                           : define multi-path token which share ACLs for all of them\\n\"\n      << \"             --permission <perm>           : define the token bearer permissions e.g 'rx' 'rwx' 'rwx!d' 'rwxq' - see acl command for permissions\\n\"\n      << \"             --owner <owner>               : identify the bearer with as user <owner> \\n\"\n      << \"             --group <group>               : identify the beaere with a group <group> \\n\"\n      << \"             --tree                        : request a subtree token granting permissions for the whole tree under <path>\\n\"\n      << \"              --origin <origin>            : restrict token usage to <origin> - multiple origin parameters can be provided\\n\"\n      << \"                                             <origin> := <regexp:hostname>#<regex:username>#<regex:protocol>\\n\"\n      << \"                                             - described by three regular extended expressions matching the \\n\"\n      << \"                                               bearers hostname, possible authenticated name and protocol\\n\"\n      << \"                                             - default is .*#.*#.* (be careful with proper shell escaping)\"\n      << \"\\n\"\n      << \"Examples:\\n\"\n      << \"          eos token --path /eos/ --permission rx --tree\\n\"\n      << \"                                           : token with browse permission for the whole /eos/ tree\\n\"\n      << \"          eos token --path /eos/file --permission rwx --owner foo --group bar\\n\"\n      << \"                                           : token granting write permission for /eos/file as user foo:bar\\n\"\n      << \"          eos token --token zteos64:...\\n\"\n      << \"                                           : dump the given token\\n\"\n      << std::endl;\n  std::cerr << oss.str() << std::endl;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_pwd.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_pwd.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n/*----------------------------------------------------------------------------*/\n\n/* Print working directory */\nint\ncom_pwd(char* arg)\n{\n  fprintf(stdout, \"%s\\n\", gPwd.c_str());\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_quit.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_quit.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n/*----------------------------------------------------------------------------*/\n\n/* The user wishes to quit using this program.  Just set DONE non-zero. */\nint\ncom_quit (char *arg)\n{\n  done = 1;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_quota.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_quota.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <iomanip>\n#include <map>\n\n//------------------------------------------------------------------------------\n// Quota System listing, configuration and manipulation\n//------------------------------------------------------------------------------\nint\ncom_quota(char* arg1)\n{\n  // Split subcommands\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString subcommand = subtokenizer.GetToken();\n  XrdOucString arg = subtokenizer.GetToken();\n  bool highlighting = true;\n\n  if (subcommand == \"-m\") {\n    subcommand = \"\";\n    arg = \"-m\";\n  }\n\n  if (subcommand == \"\" || subcommand.beginswith(\"/\")) {\n    XrdOucString in = \"mgm.cmd=quota&mgm.subcmd=lsuser\";\n\n    if (subcommand == \"\") {\n      subcommand = DefaultRoute().c_str();\n    }\n\n    if (subcommand.beginswith(\"/\")) {\n      in += \"&mgm.quota.space=\";\n      in += subcommand;\n    }\n\n    if (arg == \"-m\") {\n      in += \"&mgm.quota.format=m\";\n    }\n\n    global_retc = output_result(client_command(in));\n    return (0);\n  }\n\n  bool has_space = false;\n\n  if (wants_help(arg1)) {\n    goto com_quota_usage;\n  }\n\n  if (subcommand == \"ls\") {\n    XrdOucString in = \"mgm.cmd=quota&mgm.subcmd=ls\";\n\n    if (arg.length())\n      do {\n        if ((arg == \"--uid\") || (arg == \"-u\")) {\n          XrdOucString uid = subtokenizer.GetToken();\n\n          if (!uid.length()) {\n            goto com_quota_usage;\n          }\n\n          in += \"&mgm.quota.uid=\";\n          in += uid;\n          arg = subtokenizer.GetToken();\n        } else if ((arg == \"--gid\") || (arg == \"-g\")) {\n          XrdOucString gid = subtokenizer.GetToken();\n\n          if (!gid.length()) {\n            goto com_quota_usage;\n          }\n\n          in += \"&mgm.quota.gid=\";\n          in += gid;\n          arg = subtokenizer.GetToken();\n        } else if ((arg == \"--path\") || (arg == \"-p\")) {\n          if (has_space) {\n            goto com_quota_usage;\n          }\n\n          XrdOucString space = subtokenizer.GetToken();\n\n          if (space.c_str()) {\n            in += \"&mgm.quota.space=\";\n            in += space;\n            arg = subtokenizer.GetToken();\n            has_space = true;\n          }\n        } else if ((arg == \"-m\")) {\n          in += \"&mgm.quota.format=m\";\n          arg = subtokenizer.GetToken();\n          highlighting = false;\n        } else if ((arg == \"-n\")) {\n          in += \"&mgm.quota.printid=n\";\n          arg = subtokenizer.GetToken();\n        } else {\n          if ((arg.beginswith(\"/\")) && (!has_space)) {\n            in += \"&mgm.quota.space=\";\n            in += arg;\n            has_space = true;\n            arg = subtokenizer.GetToken();\n          } else {\n            goto com_quota_usage;\n          }\n        }\n      } while (arg.length());\n\n    global_retc = output_result(client_command(in), highlighting);\n    return (0);\n  }\n\n  if (subcommand == \"set\") {\n    XrdOucString in = \"mgm.cmd=quota&mgm.subcmd=set\";\n    XrdOucString space = \"default\";\n\n    do {\n      if ((arg == \"--uid\") || (arg == \"-u\")) {\n        XrdOucString uid = subtokenizer.GetToken();\n\n        if (!uid.length()) {\n          goto com_quota_usage;\n        }\n\n        in += \"&mgm.quota.uid=\";\n        in += uid;\n        arg = subtokenizer.GetToken();\n      } else if ((arg == \"--gid\") || (arg == \"-g\")) {\n        XrdOucString gid = subtokenizer.GetToken();\n\n        if (!gid.length()) {\n          goto com_quota_usage;\n        }\n\n        in += \"&mgm.quota.gid=\";\n        in += gid;\n        arg = subtokenizer.GetToken();\n      } else if ((arg == \"--path\") || (arg == \"-p\")) {\n        if (has_space) {\n          goto com_quota_usage;\n        }\n\n        space = subtokenizer.GetToken();\n\n        if (!space.length()) {\n          goto com_quota_usage;\n        }\n\n        in += \"&mgm.quota.space=\";\n        in += space;\n        arg = subtokenizer.GetToken();\n        has_space = true;\n      } else if ((arg == \"--volume\") || (arg == \"-v\")) {\n        XrdOucString bytes = subtokenizer.GetToken();\n\n        if (!bytes.length()) {\n          goto com_quota_usage;\n        }\n\n        in += \"&mgm.quota.maxbytes=\";\n        in += bytes;\n        arg = subtokenizer.GetToken();\n      } else if ((arg == \"--inodes\") || (arg == \"-i\")) {\n        XrdOucString inodes = subtokenizer.GetToken();\n\n        if (!inodes.length()) {\n          goto com_quota_usage;\n        }\n\n        in += \"&mgm.quota.maxinodes=\";\n        in += inodes;\n        arg = subtokenizer.GetToken();\n      } else {\n        if ((arg.beginswith(\"/\")) && (!has_space)) {\n          in += \"&mgm.quota.space=\";\n          in += arg;\n          has_space = true;\n          arg = subtokenizer.GetToken();\n        } else {\n          goto com_quota_usage;\n        }\n      }\n    } while (arg.length());\n\n    global_retc = output_result(client_command(in));\n    return (0);\n  }\n\n  if (subcommand == \"rm\") {\n    XrdOucString in = \"mgm.cmd=quota&mgm.subcmd=rm\";\n\n    do {\n      if ((arg == \"--uid\") || (arg == \"-u\")) {\n        XrdOucString uid = subtokenizer.GetToken();\n\n        if (!uid.length()) {\n          goto com_quota_usage;\n        }\n\n        in += \"&mgm.quota.uid=\";\n        in += uid;\n        arg = subtokenizer.GetToken();\n      } else if ((arg == \"--gid\") || (arg == \"-g\")) {\n        XrdOucString gid = subtokenizer.GetToken();\n\n        if (!gid.length()) {\n          goto com_quota_usage;\n        }\n\n        in += \"&mgm.quota.gid=\";\n        in += gid;\n        arg = subtokenizer.GetToken();\n      } else if ((arg == \"--path\") || (arg == \"-p\")) {\n        if (has_space) {\n          goto com_quota_usage;\n        }\n\n        XrdOucString space = subtokenizer.GetToken();\n\n        if (!space.length()) {\n          goto com_quota_usage;\n        }\n\n        in += \"&mgm.quota.space=\";\n        in += space;\n        arg = subtokenizer.GetToken();\n        has_space = true;\n      } else if ((arg == \"--inode\") || (arg == \"-i\")) {\n        in += \"&mgm.quota.type=inode\";\n        arg = subtokenizer.GetToken();\n      } else if ((arg == \"--volume\") || (arg == \"-v\")) {\n        in += \"&mgm.quota.type=volume\";\n        arg = subtokenizer.GetToken();\n      } else {\n        if ((arg.beginswith(\"/\")) && (!has_space)) {\n          in += \"&mgm.quota.space=\";\n          in += arg;\n          has_space = true;\n          arg = subtokenizer.GetToken();\n        } else {\n          goto com_quota_usage;\n        }\n      }\n    } while (arg.length());\n\n    global_retc = output_result(client_command(in));\n    return (0);\n  }\n\n  if (subcommand == \"rmnode\") {\n    XrdOucString in = \"mgm.cmd=quota&mgm.subcmd=rmnode\";\n    XrdOucString space = \"\";\n    bool dontask = false;\n\n    do {\n      if (arg == \"--really-want\") {\n\tfprintf(stderr,\"arg: %s\\n\", arg.c_str());\n\targ = subtokenizer.GetToken();\n\tfprintf(stderr,\"arg: %s\\n\", arg.c_str());\n\tdontask = true;\n\tcontinue;\n      }\n\n      if ((arg == \"--path\") || (arg == \"-p\")) {\n        space = subtokenizer.GetToken();\n\n        if (!space.length()) {\n          goto com_quota_usage;\n        }\n\n        in += \"&mgm.quota.space=\";\n        in += space;\n        arg = subtokenizer.GetToken();\n      } else {\n        goto com_quota_usage;\n      }\n    } while (arg.length());\n\n    if (!space.length()) {\n      goto com_quota_usage;\n    }\n\n    std::string s;\n    std::string sconfirmation;\n\n    if (!dontask) {\n      fprintf(stdout, \"Do you really want to delete the quota node under path %s?\\n\",\n\t      space.c_str());\n      fprintf(stdout, \"Confirm the deletion by typing => \");\n      XrdOucString confirmation = \"\";\n\n      for (int i = 0; i < 10; i++) {\n        // coverity[DC.WEAK_CRYPTO]\n        confirmation += eos::common::getRandom<int>(0, 8);\n      }\n\n      fprintf(stdout, \"%s\\n\", confirmation.c_str());\n      fprintf(stdout, \"                               => \");\n      getline(std::cin, s);\n      sconfirmation = confirmation.c_str();\n    }\n\n    if (s == sconfirmation) {\n      fprintf(stdout, \"\\nSending deletion request to server ...\\n\");\n      global_retc = output_result(client_command(in, true));\n    } else {\n      fprintf(stdout, \"\\nDeletion aborted!\\n\");\n      global_retc = -1;\n    }\n\n    return 0;\n  }\n\ncom_quota_usage:\n  std::ostringstream oss;\n  std::vector<std::uint32_t> col_size = {0, 0};\n  std::map<std::string, std::string> map_cmds = {\n    {\n      \"quota [<path>]\",\n      \": show personal quota for all or only the quota node responsible for <path>\"\n    },\n    {\n      \"quota ls [-n] [-m] [-u <uid>] [-g <gid>] [ [-p] <path> ]\",\n      \": list configured quota and quota node(s)\"\n    },\n    {\n      \"quota set -u <uid>|-g <gid> [-v <bytes>] [-i <inodes>] [-p] <path>\",\n      \": set volume and/or inode quota by uid or gid\"\n    },\n    {\n      \"quota rm -u <uid>|-g <gid> [-v] [-i] [-p] <path>\",\n      \": remove configured quota type(s) for uid/gid in path\"\n    },\n    {\n      \"quota rmnode -p <path>\",\n      \": remove quota node and every defined quota on that node\"\n    }\n  };\n\n  // Compute max width for command and description table\n  for (auto it = map_cmds.begin(); it != map_cmds.end(); ++it) {\n    if (col_size[0] < it->first.length()) {\n      col_size[0] = it->first.length() + 1;\n    }\n\n    if (col_size[1] < it->second.length()) {\n      col_size[1] = it->second.length() + 1;\n    }\n  }\n\n  std::int8_t tab_size = 2;\n  std::string usage_txt = \"Usage:\";\n  std::string opt_txt = \"General options:\";\n  std::string notes_txt = \"Notes:\";\n  oss << usage_txt << std::endl;\n\n  // Print the command and their description\n  for (auto it = map_cmds.begin(); it != map_cmds.end(); ++it) {\n    oss << std::setw(usage_txt.length()) << \"\"\n        << std::setw(col_size[0]) << std::setiosflags(std::ios_base::left)\n        << it->first\n        << std::setw(col_size[1]) << std::setiosflags(std::ios_base::left)\n        << it->second\n        << std::endl;\n  }\n\n  std::uint32_t indent_len = usage_txt.length() + tab_size;\n  // Print general options\n  oss << std::endl << std::setw(usage_txt.length()) << \"\"\n      << opt_txt << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"-m : print information in monitoring <key>=<value> format\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"-n : don't translate ids, print uid and gid number\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"-u/--uid <uid> : print information only for uid <uid>\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"-g/--gid <gid> : print information only for gid <gid>\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"-p/--path <path> : print information only for path <path> - this \"\n      << \"can also be given without -p or --path\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"-v/--volume <bytes> : refer to volume limit in <bytes>\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"-i/--inodes <inodes> : refer to inode limit in number of <inodes>\"\n      << std::endl;\n  indent_len = usage_txt.length() + tab_size;\n  // Print extra notes\n  oss << std::endl << std::setw(usage_txt.length()) << \"\"\n      << notes_txt << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"=> you have to specify either the user or the group identified by the \"\n      << \"unix id or the user/group name\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"=> the space argument is by default assumed as 'default'\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"=> you have to specify at least a volume or an inode limit to set quota\" <<\n      std::endl\n      << std::setw(indent_len) << \"\"\n      << \"=> for convenience all commands can just use <path> as last argument \"\n      << \"ommitting the -p|--path e.g. quota ls /eos/ ...\" << std::endl\n      << std::setw(indent_len) << \"\"\n      << \"=> if <path> is not terminated with a '/' it is assumed to be a file \"\n      << \"so it won't match the quota node with <path>/ !\" << std::endl;\n  fprintf(stdout, \"%s\", oss.str().c_str());\n  global_retc = EINVAL;\n  return 0;\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_rclone.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_rclone.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright(C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n *(at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/NewfindHelper.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/Timing.hh\"\n#include \"common/Path.hh\"\n#include \"common/LayoutId.hh\"\n/*----------------------------------------------------------------------------*/\n\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdCl/XrdClCopyProcess.hh>\n#include <XrdCl/XrdClPropertyList.hh>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <string.h>\n#include <iostream>\n#include <filesystem>\n#include <chrono>\n#include <optional>\n\nextern XrdOucString serveruri;\n\nstruct fs_entry {\n  struct timespec mtime;\n  size_t size;\n  std::string type;\n  std::string target;\n\n  bool newer(struct timespec& cmptime)\n  {\n    if (mtime.tv_sec < cmptime.tv_sec) {\n      return true;\n    } else if (mtime.tv_sec > cmptime.tv_sec) {\n      return false;\n    } else if (mtime.tv_nsec < cmptime.tv_nsec) {\n      return true;\n    } else {\n      return false;\n    }\n  }\n};\n\nstruct fs_result {\n  std::map<std::string, fs_entry> directories;\n  std::map<std::string, fs_entry> files;\n  std::map<std::string, fs_entry> links;\n};\n\nbool dryrun = false;\nbool noreplace = false;\nbool nodelete = false;\nbool verbose = false;\nbool is_silent = false;\nbool filter_versions = true;\nbool filter_atomic = true;\nbool filter_hidden = true;\n\nfs_result fs_find(const char* path)\n{\n  fs_result result;\n  std::stringstream s;\n  eos::common::Path cPath(path);\n  namespace fs = std::filesystem;\n  fs::path path_to_traverse = path;\n  struct stat buf;\n\n  try {\n    for (const auto& entry : fs::recursive_directory_iterator(path_to_traverse,\n         std::filesystem::directory_options::skip_permission_denied)) {\n      std::string p = entry.path().string();\n      // filter functions\n      eos::common::Path iPath(p.c_str());\n\n      if (filter_versions) {\n        if (iPath.isVersionPath()) {\n          continue;\n        }\n      }\n\n      if (filter_atomic) {\n        if (iPath.isAtomicFile()) {\n          continue;\n        }\n      }\n\n      if (filter_hidden && iPath.GetFullPath().find(\"/.\") != STR_NPOS) {\n        if (!iPath.isVersionPath() && !iPath.isAtomicFile()) {\n          continue;\n        }\n      }\n\n      std::string t = p;\n\n      if (!::lstat(p.c_str(), &buf)) {\n        p.erase(0, cPath.GetFullPath().length());\n\n        switch ((buf.st_mode & S_IFMT)) {\n        case S_IFDIR :\n          p += \"/\";\n          result.directories[p].mtime = buf.st_mtim;\n          result.directories[p].size  = buf.st_size;\n          //  s << \"path=\\\"\" << p << \"/\\\" mtime=\" << eos::common::Timing::TimespecToString(buf.st_mtim) << \" size=\" << buf.st_size << std::endl;\n          break;\n\n        case S_IFREG :\n          result.files[p].mtime = buf.st_mtim;\n          result.files[p].size  = buf.st_size;\n          //s << \"path=\\\"\" << p << \"\\\" mtime=\" << eos::common::Timing::TimespecToString(buf.st_mtim) << \" size=\" << buf.st_size << std::endl;\n          break;\n\n        case S_IFLNK :\n          result.links[p].size = 0;\n          result.links[p].mtime = buf.st_mtim;\n          char link[4096];\n          ssize_t target = readlink(t.c_str(), link, sizeof(link));\n\n          if (target >= 0) {\n            result.links[p].target = std::string(link, target);\n          }\n\n          break;\n        }\n      }\n    }\n  } catch (std::filesystem::filesystem_error const& ex) {\n    std::cerr\n        << \"error:  \" << ex.what() << '\\n'\n        << \"#      path  : \" << ex.path1() << '\\n'\n        << \"#      errc  :    \" << ex.code().value() << '\\n'\n        << \"#      msg   :  \" << ex.code().message() << '\\n'\n        << \"#      class : \" << ex.code().category().name() << '\\n';\n    exit(-1);\n  }\n\n  //  std::cout << s.str();\n  return result;\n}\n\nfs_result eos_find(const char* path)\n{\n  fs_result result;\n  eos::common::Path cPath(path);\n  NewfindHelper find(gGlobalOpts);\n  std::string args = \"--format type,mtime,size,link \";\n  args += path;\n\n  if (!find.ParseCommand(args.c_str())) {\n    std::cerr << \"error: illegal subcommand '\" << args << \"'\" << std::endl;\n  }\n\n  find.Silent();\n  int rc = find.Execute();\n\n  if (!rc) {\n    std::string findresult = find.GetResult();\n    std::vector<std::string> lines;\n    eos::common::StringConversion::Tokenize(findresult, lines, \"\\n\");\n\n    for (auto l : lines) {\n      std::vector<std::string> kvs;\n      eos::common::StringConversion::Tokenize(l, kvs, \" \");\n      struct timespec ts {\n        0, 0\n      };\n      size_t size{0};\n      std::string path;\n      std::string type;\n\n      for (auto k : kvs) {\n        std::string tag, value;\n        eos::common::StringConversion::SplitKeyValue(k, tag, value, \"=\");\n\n        if (tag == \"mtime\") {\n          eos::common::Timing::Timespec_from_TimespecStr(value, ts);\n\n          if (type == \"directory\") {\n            result.directories[path].mtime = ts;\n          } else if (type == \"file\") {\n            result.files[path].mtime = ts;\n          } else if (type == \"symlink\") {\n            result.links[path].mtime = ts;\n          }\n        }\n\n        if (tag == \"size\") {\n          size = std::stoull(value.c_str(), 0, 10);\n\n          if (type == \"directory\") {\n            result.directories[path].size = size;\n          } else if (type == \"file\") {\n            result.files[path].size = size;\n          } else if (type == \"symlink\") {\n            result.links[path].size = 0;\n          }\n        }\n\n        if (tag == \"path\") {\n          // remove quotes\n          value.erase(0, 1);\n          value.erase(value.length() - 1);\n          value.erase(0, cPath.GetFullPath().length());\n          path = value;\n        }\n\n        if (tag == \"type\") {\n          type = value;\n        }\n\n        if (tag == \"target\" && type == \"symlink\") {\n          value.erase(0, 1);\n          value.erase(value.length() - 1);\n          result.links[path].target = value;\n        }\n\n        // filter functions\n        eos::common::Path iPath(path.c_str());\n\n        if (filter_versions) {\n          if (iPath.isVersionPath()) {\n            break;\n          }\n        }\n\n        if (filter_atomic) {\n          if (iPath.isAtomicFile()) {\n            break;\n          }\n        }\n\n        if (filter_hidden && iPath.GetFullPath().find(\"/.\") != STR_NPOS) {\n          if (!iPath.isVersionPath() && !iPath.isAtomicFile()) {\n            break;\n          }\n        }\n      }\n    }\n  } else {\n    std::cerr << \"error: \" << find.GetError() << std::endl;\n    exit(rc);\n  }\n\n  return result;\n}\n\nint createDir(const std::string& i, eos::common::Path& prefix)\n{\n  if (!prefix.GetFullPath().beginswith(\"/eos/\")) {\n    int rc = 0;\n    std::string mkpath = std::string(prefix.GetFullPath().c_str()) +\n                         std::string(\"/\") + i;\n\n    if (!dryrun) {\n      rc = ::mkdir(mkpath.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);\n    }\n\n    std::cerr << \"[ mkdir                 ] : path:\" << \"[mkdir] path: \" <<\n              mkpath.c_str() << \" retc: \" << rc << std::endl;\n    return rc;\n  } else {\n    XrdCl::URL url(serveruri.c_str());\n    url.SetPath(std::string(prefix.GetFullPath().c_str()) + std::string(\"/\") + i);\n\n    if (!url.IsValid()) {\n      std::cerr << \"error: invalid url \" << i.c_str() << std::endl;\n      return 0;\n    }\n\n    XrdCl::FileSystem fs(url);\n    mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP;\n    XrdCl::Access::Mode mode_xrdcl = eos::common::LayoutId::MapModeSfs2XrdCl(mode);\n    XrdCl::XRootDStatus status = fs.MkDir(url.GetPath(),\n                                          XrdCl::MkDirFlags::MakePath,\n                                          mode_xrdcl);\n    std::cerr << \"[ mkdir                 ] : url:\" << url.GetURL() << \" : \" <<\n              status.IsOK() << std::endl;\n    return (!status.IsOK());\n  }\n}\n\nint removeDir(const std::string& i, eos::common::Path& prefix)\n{\n  if (!prefix.GetFullPath().beginswith(\"/eos/\")) {\n    int rc = 0;\n    std::string rmpath = std::string(prefix.GetFullPath().c_str()) +\n                         std::string(\"/\") + i;\n\n    if (!dryrun) {\n      rc = ::rmdir(rmpath.c_str());\n    }\n\n    std::cerr << \"[ rmdir                 ] : path:\" << rmpath.c_str() << \" retc: \"\n              << rc << std::endl;\n    return rc;\n  } else {\n    XrdCl::URL url(serveruri.c_str());\n    url.SetPath(std::string(prefix.GetFullPath().c_str()) + std::string(\"/\") + i);\n\n    if (!url.IsValid()) {\n      std::cerr << \"error: invalid url \" << i.c_str() << std::endl;\n      return 0;\n    }\n\n    XrdCl::FileSystem fs(url);\n    XrdCl::XRootDStatus status = fs.RmDir(url.GetPath());\n    std::cerr << \"[ rmdir                 ] : url:\" << url.GetURL() << \" : \" <<\n              status.IsOK() << std::endl;\n    return (!status.IsOK());\n  }\n}\n\nint removeFile(const std::string& i, eos::common::Path& prefix)\n{\n  if (!prefix.GetFullPath().beginswith(\"/eos/\")) {\n    int rc = 0;\n    std::string rmpath = std::string(prefix.GetFullPath().c_str()) +\n                         std::string(\"/\") + i;\n\n    if (!dryrun) {\n      rc = ::unlink(rmpath.c_str());\n    }\n\n    std::cerr << \"[ unlink                ] : path:\" << rmpath.c_str() << \" retc: \"\n              << rc << std::endl;\n    return rc;\n  } else {\n    XrdCl::URL url(serveruri.c_str());\n    url.SetPath(std::string(prefix.GetFullPath().c_str()) + std::string(\"/\") + i);\n\n    if (!url.IsValid()) {\n      std::cerr << \"error: invalid url \" << i.c_str() << std::endl;\n      return 0;\n    }\n\n    XrdCl::FileSystem fs(url);\n    XrdCl::XRootDStatus status = fs.Rm(url.GetPath());\n    std::cerr << \"[ unlink                ] : url:\" << url.GetURL() << \" : \" <<\n              status.IsOK() << std::endl;\n    return (!status.IsOK());\n  }\n}\n\nint createLink(const std::string& i, eos::common::Path& prefix,\n               const std::string& target, eos::common::Path& targetprefix,\n               struct timespec& mtime)\n{\n  std::string targetpath = target;\n\n  if (targetpath.find(prefix.GetFullPath().c_str()) == 0) {\n    // might need to rewrite the link target with a new prefix!\n    targetpath.erase(0, prefix.GetFullPath().length());\n    targetpath.insert(0, targetprefix.GetFullPath().c_str());\n  }\n\n  if (!prefix.GetFullPath().beginswith(\"/eos/\")) {\n    int rc = 0;\n    std::string linkpath = std::string(prefix.GetFullPath().c_str()) +\n                           std::string(\"/\") + i;\n\n    if (!is_silent && verbose) {\n      std::cout << \"[ link  ] linking \" << linkpath.c_str() << \" => \" <<\n                target.c_str() << \" \" << mtime.tv_sec << \".\" << mtime.tv_nsec << std::endl;\n    }\n\n    if (!dryrun) {\n      rc = ::symlink(target.c_str(), linkpath.c_str());\n\n      if (rc) {\n        std::cerr << \"error: symlink rc=\" << rc << \" errno=\" << errno << std::endl;\n      }\n    }\n\n    struct timespec times[2];\n\n    times[0] = mtime;\n\n    times[1] = mtime;\n\n    if (!dryrun) {\n      int rc2 = utimensat(0, linkpath.c_str(), times, AT_SYMLINK_NOFOLLOW);\n      rc |= rc2;\n\n      if (rc2) {\n        std::cerr << \"error: utimesat rc=\" << rc << \" errno=\" << errno << std::endl;\n      }\n    }\n\n    if (!is_silent && verbose) {\n      std::cout << \"[ symlink               ] : path:\" << linkpath.c_str() <<\n                \" retc: \" << rc << std::endl;\n    }\n\n    return rc;\n  } else {\n    XrdCl::URL url(serveruri.c_str());\n    url.SetPath(std::string(prefix.GetFullPath().c_str()) + std::string(\"/\") + i);\n\n    if (!url.IsValid()) {\n      std::cerr << \"error: invalid url \" << i.c_str() << std::endl;\n      return 0;\n    }\n\n    int retc = 0;\n    std::string request;\n    {\n      // create link\n      XrdCl::Buffer arg;\n      XrdCl::Buffer* response = nullptr;\n      request = eos::common::StringConversion::curl_escaped(std::string(\n                  prefix.GetFullPath().c_str()) + std::string(\"/\") + i);\n      request += \"?\";\n      request += \"mgm.pcmd=symlink&target=\";\n      request += eos::common::StringConversion::curl_escaped(targetpath);\n      request += \"&eos.encodepath=1\";\n      arg.FromString(request);\n      XrdCl::FileSystem fs(url);\n      XrdCl::XRootDStatus status = fs.Query(XrdCl::QueryCode::OpaqueFile, arg,\n                                            response);\n\n      if (response) {\n        delete response;\n      }\n\n      retc = !status.IsOK();\n    }\n    {\n      // fix mtime\n      XrdCl::Buffer arg;\n      XrdCl::Buffer* response = nullptr;\n      request = eos::common::StringConversion::curl_escaped(std::string(\n                  prefix.GetFullPath().c_str()) + std::string(\"/\") + i);\n      request += \"?\";\n      request += \"mgm.pcmd=utimes\";\n      request += \"&tv1_sec=0\";  //ignored\n      request += \"&tv1_nsec=0\"; // ignored\n      request += \"&tv2_sec=\";\n      request += std::to_string(mtime.tv_sec);\n      request += \"&tv2_nsec=\";\n      std::stringstream oss;\n      oss << std::setfill('0') << std::setw(9) << mtime.tv_nsec;\n      request += oss.str();\n      request += \"&eos.encodepath=1\";\n      arg.FromString(request);\n      XrdCl::FileSystem fs(url);\n      XrdCl::XRootDStatus status = fs.Query(XrdCl::QueryCode::OpaqueFile, arg,\n                                            response);\n\n      if (response) {\n        delete response;\n      }\n\n      retc |= !status.IsOK();\n    }\n\n    if (!is_silent && verbose) {\n      std::cout << \"[ symlink               ] : url:\" << url.GetURL() << \" : \" << retc\n                << std::endl;\n    }\n\n    return retc;\n  }\n}\n\n\nint setDirMtime(const std::string& i, eos::common::Path& prefix,\n                struct timespec mtime)\n{\n  std::string mtpath = std::string(prefix.GetFullPath().c_str()) +\n                       std::string(\"/\") + i;\n\n  if (!prefix.GetFullPath().beginswith(\"/eos/\")) {\n    // apply local mtime;\n    struct timespec times[2];\n    times[0] = mtime;\n    times[1] = mtime;\n    int rc = 0;\n\n    if (!dryrun) {\n      rc = utimensat(0, mtpath.c_str(), times, AT_SYMLINK_NOFOLLOW);\n    }\n\n    if (!is_silent && verbose) {\n      std::cout << \"[ mtime                 ] : path:\" << \"[utime] path: \" <<\n                mtpath.c_str() << \" retc: \" << rc <<  \" \" << mtime.tv_sec << \":\" <<\n                mtime.tv_nsec << std::endl;\n    }\n\n    return rc;\n  } else {\n    XrdCl::URL url(serveruri.c_str());\n    url.SetPath(std::string(prefix.GetFullPath().c_str()) + std::string(\"/\") + i);\n\n    if (!url.IsValid()) {\n      std::cerr << \"error: invalid url \" << i.c_str() << std::endl;\n      return 0;\n    }\n\n    std::string request;\n    XrdCl::Buffer arg;\n    XrdCl::Buffer* response = nullptr;\n    request = eos::common::StringConversion::curl_escaped(std::string(\n                prefix.GetFullPath().c_str()) + std::string(\"/\") + i);\n    request += \"?\";\n    request += \"mgm.pcmd=utimes\";\n    request += \"&tv1_sec=0\";  //ignored\n    request += \"&tv1_nsec=0\"; // ignored\n    request += \"&tv2_sec=\";\n    request += std::to_string(mtime.tv_sec);\n    request += \"&tv2_nsec=\";\n    std::stringstream oss;\n    oss << std::setfill('0') << std::setw(9) << mtime.tv_nsec;\n    request += oss.str();\n    request += \"&eos.encodepath=1\";\n    arg.FromString(request);\n    XrdCl::FileSystem fs(url);\n    XrdCl::XRootDStatus status = fs.Query(XrdCl::QueryCode::OpaqueFile, arg,\n                                          response);\n\n    if (response) {\n      delete response;\n    }\n\n    int rc = !status.IsOK();\n\n    if (!is_silent && verbose) {\n      std::cerr << \"[ mtime                 ] : path:\" << \"[utime] path: \" <<\n                mtpath.c_str() << \" retc: \" << rc << std::endl;\n    }\n\n    return rc;\n  }\n}\n\nXrdCl::CopyProcess copyProcess;\nstd::vector<XrdCl::PropertyList*> tprops;\n\nXrdCl::PropertyList* copyFile(const std::string& i, eos::common::Path& src,\n                              eos::common::Path& dst, struct timespec mtime)\n{\n  XrdCl::PropertyList props;\n  XrdCl::PropertyList* result = new XrdCl::PropertyList();\n  std::string srcurl = std::string(src.GetFullPath().c_str()) + i;\n  std::string dsturl = std::string(dst.GetFullPath().c_str()) + i;\n\n  if (srcurl.substr(0, 5) == \"/eos/\") {\n    XrdCl::URL surl(serveruri.c_str());\n    surl.SetPath(srcurl);\n    srcurl = surl.GetURL();\n  }\n\n  if (dsturl.substr(0, 5) == \"/eos/\") {\n    XrdCl::URL durl(serveruri.c_str());\n    durl.SetPath(dsturl);\n    XrdCl::URL::ParamsMap params;\n    params[\"eos.mtime\"] = eos::common::Timing::TimespecToString(mtime);\n    durl.SetParams(params);\n    dsturl = durl.GetURL();\n  } else {\n    XrdCl::URL durl(dsturl);\n    XrdCl::URL::ParamsMap params;\n    params[\"local.mtime\"] = eos::common::Timing::TimespecToString(mtime);\n    durl.SetParams(params);\n    dsturl = durl.GetURL();\n  }\n\n  props.Set(\"source\", srcurl);\n  props.Set(\"target\", dsturl);\n  props.Set(\"force\", true); // allows overwrite\n  result->Set(\"source\", srcurl);\n  result->Set(\"target\", dsturl);\n\n  //  props.Set(\"parallel\", 10);\n  if (verbose) {\n    std::cout << \"[ copy file             ] : srcurl: \" << srcurl << \" dsturl: \" <<\n              dsturl << std::endl;\n  }\n\n  copyProcess.AddJob(props, result);\n  return result;\n}\n\nvoid rclone_usage()\n{\n  fprintf(stderr,\n          \"usage: rclone copy src-dir dst-dir [--delete] [--noupdate] [--dryrun] [--atomic] [--versions] [--hidden] [-v|--verbose] [-s|--silent]\\n\");\n  fprintf(stderr,\n          \"                                       : copy from source to destination [one-way sync]\\n\");\n  fprintf(stderr,\n          \"       rclone sync dir1 dir2 [--delete] [--noupdate] [--dryrun] [--atomic] [--versions] [--hidden] [-v|--verbose] [-s|--silent]\\n\");\n  fprintf(stderr,\n          \"                                       : bi-directional sync based on modification times\\n\");\n  fprintf(stderr,\n          \"                              --delete : delete based on mtimes (currently unsupported)!\\n\");\n  fprintf(stderr,\n          \"                            --noupdate : never update files, only create new ones!\\n\");\n  fprintf(stderr,\n          \"                            --dryrun   : simulate the command and show all actions, but don't do it!\\n\");\n  fprintf(stderr,\n          \"                            --atomic   : copy/sync also EOS atomic files\\n\");\n  fprintf(stderr,\n          \"                            --versions : copy/sync also EOS atomic files\\n\");\n  fprintf(stderr,\n          \"                            --hidden   : copy/sync also hidden files/directories\\n\");\n  fprintf(stderr,\n          \"                         -v --verbose  : display all actions, not only a summary\\n\");\n  fprintf(stderr,\n          \"                         -s --silent   : only show errors\\n\");\n  exit(-1);\n}\n\n\nstd::string parent(const std::string& path)\n{\n  std::filesystem::path p(path);\n  return p.parent_path();\n}\n\n\nstd::optional<bool> parent_newer(std::map<std::string, fs_entry>& a,\n                                 std::map<std::string, fs_entry>& b, const std::string& path)\n{\n  // checks if the parent mtime of b is newer than parent mtime of a !\n  std::string p_path = parent(path);\n\n  if (!a.count(path) || !b.count(path) ||\n      !a.count(p_path) || !b.count(p_path)) {\n    return {};\n  }\n\n  if ((b[p_path].mtime.tv_sec == a[p_path].mtime.tv_sec) &&\n      (b[p_path].mtime.tv_nsec == a[p_path].mtime.tv_nsec)) {\n    return {};\n  }\n\n  if (b[p_path].newer(a[p_path].mtime)) {\n    return true;\n  } else {\n    return false;\n  }\n}\n\n\nint\ncom_rclone(char* arg1)\n{\n  if (interactive) {\n    fprintf(stderr,\n            \"error: don't call <rclone> from an interactive shell - run 'eos -b rclone ...'!\\n\");\n    global_retc = -1;\n    return 0;\n  }\n\n// split subcommands\n  XrdOucString mountpoint = \"\";\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString cmd = subtokenizer.GetToken();\n  std::set<std::string> target_create_dirs;\n  std::set<std::string> target_delete_dirs;\n  std::set<std::string> target_mtime_dirs;\n  std::set<std::string> target_create_files;\n  std::set<std::string> target_delete_files;\n  std::set<std::string> target_updated_files;\n  std::set<std::string> target_mismatch_files;\n  std::set<std::string> target_create_links;\n  std::set<std::string> target_delete_links;\n  std::set<std::string> target_updated_links;\n  std::set<std::string> target_mismatch_links;\n  std::set<std::string> source_create_dirs;\n  std::set<std::string> source_delete_dirs;\n  std::set<std::string> source_mtime_dirs;\n  std::set<std::string> source_create_files;\n  std::set<std::string> source_delete_files;\n  std::set<std::string> source_updated_files;\n  std::set<std::string> source_mismatch_files;\n  std::set<std::string> source_create_links;\n  std::set<std::string> source_delete_links;\n  std::set<std::string> source_updated_links;\n  std::set<std::string> source_mismatch_links;\n  std::set<std::string> cp_target_files;\n  std::set<std::string> cp_source_files;\n  uint64_t copySize = 0;\n  uint64_t copyTransactions = 0;\n  enum eActions {\n    kTargetDirCreate, kSourceDirCreate,\n    kTargetDirDelete, kSourceDirDelete,\n    kTargetFileCreate, kSourceFileCreate,\n    kTargetFileUpdate, kSourceFileUpdate,\n    kTargetFileDelete, kSourceFileDelete,\n    kTargetFileMismatch, kSourceFileMismatch,\n    kTargetLinkCreate, kSourceLinkCreate,\n    kTargetLinkUpdate, kSourceLinkUpdate,\n    kTargetLinkDelete, kSourceLinkDelete,\n    kTargetLinkMismatch, kSourceLinkMismatch,\n    kTargetDirMtime, kSourceDirMtime\n  };\n  std::vector<eActions> actions;\n\n  if (cmd == \"copy\") {\n    actions.push_back(kTargetDirCreate);\n    actions.push_back(kTargetFileCreate);\n\n    if (!noreplace) {\n      actions.push_back(kTargetFileUpdate);\n    }\n\n    actions.push_back(kTargetFileMismatch);\n    actions.push_back(kTargetLinkCreate);\n\n    if (!noreplace) {\n      actions.push_back(kTargetLinkUpdate);\n    }\n\n    actions.push_back(kTargetLinkMismatch);\n\n    if (!nodelete) {\n      actions.push_back(kTargetLinkDelete);\n      actions.push_back(kTargetFileDelete);\n      actions.push_back(kTargetDirDelete);\n    }\n\n    actions.push_back(kTargetDirMtime);\n  } else if (cmd == \"sync\") {\n    actions.push_back(kTargetDirCreate);\n    actions.push_back(kTargetFileCreate);\n\n    if (!noreplace) {\n      actions.push_back(kTargetFileUpdate);\n    }\n\n    actions.push_back(kTargetFileMismatch);\n    actions.push_back(kTargetLinkCreate);\n\n    if (!noreplace) {\n      actions.push_back(kTargetLinkUpdate);\n    }\n\n    actions.push_back(kTargetLinkMismatch);\n    // we cannot detect two-way deletion without history\n    // if (!nodelete) {\n    //   actions.push_back(kTargetLinkDelete);\n    //   actions.push_back(kTargetFileDelete);\n    //   actions.push_back(kTargetDirDelete);\n    // }\n    actions.push_back(kTargetDirMtime);\n    actions.push_back(kSourceDirCreate);\n    actions.push_back(kSourceFileCreate);\n    actions.push_back(kSourceFileUpdate);\n    //    actions.push_back(kSourceFileMismatch);\n    actions.push_back(kSourceLinkCreate);\n\n    if (!noreplace) {\n      actions.push_back(kSourceLinkUpdate);\n    }\n\n    //    actions.push_back(kSourceLinkMismatch);\n    // we cannot detec two-way deletion without historya\n    // if (!nodelete) {\n    //   actions.push_back(kSourceLinkDelete);\n    //   actions.push_back(kSourceFileDelete);\n    //   actions.push_back(kSourceDirDelete);\n    // }\n    actions.push_back(kSourceDirMtime);\n  } else {\n    rclone_usage();\n  }\n\n  XrdOucString src = subtokenizer.GetToken();\n  XrdOucString dst = subtokenizer.GetToken();\n  eos::common::Path srcPath(src.c_str());\n  eos::common::Path dstPath(dst.c_str());\n  src = srcPath.GetFullPath();\n  dst = dstPath.GetFullPath();\n\n  if (!src.length() || !dst.length()) {\n    rclone_usage();\n  }\n\n  nodelete = true;\n  noreplace = false;\n  dryrun = false;\n  XrdOucString option;\n\n  do {\n    option = subtokenizer.GetToken();\n\n    if (!option.length()) {\n      break;\n    }\n\n    if (option == \"--delete\") {\n      nodelete = false;\n    } else if (option == \"--noreplace\") {\n      noreplace = true;\n    } else if (option == \"--dryrun\") {\n      dryrun = true;\n    } else if (option == \"--atomic\") {\n      filter_atomic = false;\n    } else if (option == \"--versions\") {\n      filter_versions = false;\n    } else if (option == \"--hidden\") {\n      filter_hidden = false;\n    } else if (option == \"-v\" || option == \"--verbose\") {\n      verbose = true;\n    } else if (option == \"-s\" || option == \"--silent\") {\n      is_silent = true;\n    } else {\n      rclone_usage();\n    }\n  } while (1);\n\n  fs_result srcmap;\n  fs_result dstmap;\n  bool ignore_errors = false;\n\n  if (src.beginswith(\"/eos/\")) {\n    // get the sync informtion using newfind\n    srcmap = eos_find(src.c_str());\n  } else {\n    // travers using UNIX find\n    srcmap = fs_find(src.c_str());\n  }\n\n  if (dst.beginswith(\"/eos/\")) {\n    // get the sync information using newfind\n    dstmap = eos_find(dst.c_str());\n  } else {\n    // travers using UNIX find\n    dstmap = fs_find(dst.c_str());\n  }\n\n  srcmap.directories.erase(\"/\");\n  dstmap.directories.erase(\"/\");\n\n  // forward comparison\n  for (auto d : srcmap.directories) {\n    if (!dstmap.directories.count(d.first)) {\n      if (!is_silent && verbose) {\n        std::cout << \"[ target folder missing ] : \" << d.first << std::endl;\n      }\n\n      target_create_dirs.insert(d.first);\n      target_mtime_dirs.insert(d.first);\n    } else {\n      if (dstmap.directories[d.first].newer(srcmap.directories[d.first].mtime)) {\n        target_mtime_dirs.insert(d.first);\n      }\n    }\n  }\n\n  /// backward\n  for (auto d : dstmap.directories) {\n    if (!srcmap.directories.count(d.first)) {\n      if (!is_silent && verbose) {\n        std::cout << \"[ source folder missing ] : \" << d.first << std::endl;\n      }\n\n      if (!nodelete) {\n        target_delete_dirs.insert(d.first);\n        target_mtime_dirs.insert(parent(d.first));\n      } else {\n        source_create_dirs.insert(d.first);\n        source_mtime_dirs.insert(d.first);\n      }\n    } else {\n      if (srcmap.directories[d.first].newer(dstmap.directories[d.first].mtime)) {\n        source_mtime_dirs.insert(d.first);\n      }\n    }\n  }\n\n  // forward comparison\n  for (auto d : srcmap.files) {\n    if (!dstmap.files.count(d.first)) {\n      if (!is_silent && verbose) {\n        std::cout << \"[ target file   missing ] : \" << d.first << std::endl;\n      }\n\n      target_create_files.insert(d.first);\n      copySize += d.second.size;\n      copyTransactions++;\n    } else {\n      if (dstmap.files[d.first].newer(srcmap.files[d.first].mtime)) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ target file   older   ] : \" << d.first << std::endl;\n        }\n\n        target_updated_files.insert(d.first);\n\n        if (!noreplace) {\n          copySize += d.second.size;\n          copyTransactions++;\n        }\n      } else {\n        if (dstmap.files[d.first].size != srcmap.files[d.first].size) {\n          if (!is_silent && verbose) {\n            std::cout << \"[ target file diff size ] : \" << d.first << std::endl;\n          }\n\n          target_mismatch_files.insert(d.first);\n        }\n      }\n    }\n  }\n\n  // backward comparison\n  for (auto d : dstmap.files) {\n    if (!srcmap.files.count(d.first)) {\n      if (!is_silent && verbose) {\n        std::cout << \"[ source file   missing ] : \" << d.first << std::endl;\n      }\n\n      if (!nodelete) {\n        target_delete_files.insert(d.first);\n      } else {\n        source_create_files.insert(d.first);\n        copySize += d.second.size;\n        copyTransactions++;\n      }\n    } else {\n      if (srcmap.files[d.first].newer(dstmap.files[d.first].mtime)) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ source file   older   ] : \" << d.first << std::endl;\n        }\n\n        source_updated_files.insert(d.first);\n\n        if (!noreplace) {\n          copySize += d.second.size;\n          copyTransactions++;\n        }\n      } else {\n        if (dstmap.files[d.first].size != srcmap.files[d.first].size) {\n          if (!is_silent && verbose) {\n            std::cout << \"[ source file diff size ] : \" << d.first << std::endl;\n          }\n\n          source_mismatch_files.insert(d.first);\n        }\n      }\n    }\n  }\n\n  // forward comparison\n  for (auto d : srcmap.links) {\n    if (!dstmap.links.count(d.first)) {\n      if (!is_silent && verbose) {\n        std::cout << \"[ target link   missing ] : \" << d.first << std::endl;\n      }\n\n      target_create_links.insert(d.first);\n    } else {\n      if (dstmap.links[d.first].newer(srcmap.links[d.first].mtime)) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ target link   older   ] : \" << d.first << std::endl;\n        }\n\n        target_updated_links.insert(d.first);\n      } else {\n        if (dstmap.links[d.first].target != srcmap.links[d.first].target) {\n          if (!is_silent && verbose) {\n            std::cout << \"[ target link diff size ] : \" << d.first << std::endl;\n          }\n\n          target_mismatch_links.insert(d.first);\n        }\n      }\n    }\n  }\n\n  // backward comparison\n  for (auto d : dstmap.links) {\n    if (!srcmap.links.count(d.first)) {\n      if (!is_silent && verbose) {\n        std::cout << \"[ source link   missing ] : \" << d.first << std::endl;\n      }\n\n      if (!nodelete) {\n        target_delete_links.insert(d.first);\n      } else {\n        source_create_links.insert(d.first);\n      }\n    } else {\n      if (srcmap.links[d.first].newer(dstmap.links[d.first].mtime)) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ source link   older   ] : \" << d.first << std::endl;\n        }\n\n        source_updated_links.insert(d.first);\n      } else {\n        if (dstmap.links[d.first].target != srcmap.links[d.first].target) {\n          if (!is_silent && verbose) {\n            std::cout << \"[ source link diff size ] : \" << d.first << std::endl;\n          }\n\n          source_mismatch_links.insert(d.first);\n        }\n      }\n    }\n  }\n\n  if (!is_silent) {\n    std::cout << \"[ --------------------------- ]\" << std::endl;\n    std::cout << \"[ EOS remote sync tool (beta) ]\" << std::endl;\n  }\n\n  if (!dryrun) {\n    if (!is_silent) {\n      std::cout << \"[ --------------------------- ]\" << std::endl;\n      std::cout << \"[ target                      ]\" << std::endl;\n      std::cout << \"[ --------------------------- ]\" << std::endl;\n      std::cout << \"[ # dir,files,links to create ] : \" << target_create_dirs.size()\n                << \",\" << target_create_files.size() << \",\" << target_create_links.size() <<\n                std::endl;\n      std::cout << \"[ # dir,files,links to delete ] : \" << target_delete_dirs.size()\n                << \",\" << target_delete_files.size() << \",\" << target_delete_links.size() <<\n                std::endl;\n      std::cout << \"[ # files,links to update     ] : \" << target_updated_files.size()\n                << \",\" << target_updated_links.size() << std::endl;\n      std::cout << \"[ # files,links mismatch      ] : \" <<\n                target_mismatch_files.size() << \",\" << target_mismatch_links.size() <<\n                std::endl;\n      std::cout << \"[ --------------------------- ]\" << std::endl;\n      std::cout << \"[ source                      ]\" << std::endl;\n      std::cout << \"[ --------------------------- ]\" << std::endl;\n      std::cout << \"[ # dir,files,links to create ] : \" << source_create_dirs.size()\n                << \",\" << source_create_files.size() << \",\" << source_create_links.size() <<\n                std::endl;\n      std::cout << \"[ # dir,files,links to delete ] : \" << source_delete_dirs.size()\n                << \",\" << source_delete_files.size() << \",\" << source_delete_links.size() <<\n                std::endl;\n      std::cout << \"[ # files,links to update     ] : \" << source_updated_files.size()\n                << \",\" << source_updated_links.size() << std::endl;\n      std::cout << \"[ # files,links mismatch      ] : \" <<\n                source_mismatch_files.size() << \",\" << source_mismatch_links.size() <<\n                std::endl;\n      std::cout << \"[ --------------------------- ]\" << std::endl;\n      std::cout << \"[ volume                      ]\" << std::endl;\n      std::cout << \"[ --------------------------- ]\" << std::endl;\n      XrdOucString sizestring;\n      eos::common::StringConversion::GetReadableSizeString(sizestring, copySize, \"B\");\n      std::cout << \"[ # data size                 ] : \" << sizestring.c_str() <<\n                std::endl;\n      eos::common::StringConversion::GetReadableSizeString(sizestring,\n          copyTransactions, \"\");\n      std::cout << \"[ # copy transactions         ] : \" << sizestring.c_str() <<\n                std::endl;\n      std::cout << \"[ --------------------------- ]\" << std::endl;\n    }\n  }\n\n  for (auto a : actions) {\n    if (a == kTargetDirCreate) {\n      for (auto i : target_create_dirs) {\n        int rc = createDir(i, dstPath);\n\n        if (rc && !ignore_errors) {\n          std::cerr << \"error: failed to create directory '\" << dstPath.GetFullPath() << i\n                    << \"'\" << std::endl;\n          exit(-1);\n        }\n      }\n    }\n\n    if (a == kSourceDirCreate) {\n      for (auto i : source_create_dirs) {\n        int rc = createDir(i, srcPath);\n\n        if (rc && !ignore_errors) {\n          std::cerr << \"error: failed to create directory '\" << dstPath.GetFullPath() << i\n                    << \"'\" << std::endl;\n          exit(-1);\n        }\n      }\n    }\n\n    if (a == kTargetDirDelete) {\n      for (auto i : target_delete_dirs) {\n        int rc = removeDir(i, dstPath);\n\n        if (rc && !ignore_errors) {\n          std::cerr << \"error: failed to remove directory '\" << dstPath.GetFullPath() << i\n                    << \"'\" << std::endl;\n          exit(-1);\n        }\n      }\n    }\n\n    if (a == kTargetFileDelete) {\n      for (auto i : target_delete_files) {\n        int rc = removeFile(i, dstPath);\n\n        if (rc && !ignore_errors) {\n          std::cerr << \"error: failed to remove file '\" << dstPath.GetFullPath() << i <<\n                    \"'\" << std::endl;\n          exit(-1);\n        }\n      }\n    }\n\n    if (a == kTargetLinkDelete) {\n      for (auto i : target_delete_links) {\n        int rc = removeFile(i, dstPath);\n\n        if (rc && !ignore_errors) {\n          std::cerr << \"error: failed to remove link '\" << dstPath.GetFullPath() << i <<\n                    \"'\" << std::endl;\n          exit(-1);\n        }\n      }\n    }\n\n    if (a == kSourceDirDelete) {\n      for (auto i : source_delete_dirs) {\n        int rc = removeDir(i, srcPath);\n\n        if (rc && !ignore_errors) {\n          std::cerr << \"error: failed to remove directory '\" << srcPath.GetFullPath() << i\n                    << \"'\" << std::endl;\n          exit(-1);\n        }\n      }\n    }\n\n    if (a == kSourceFileDelete) {\n      for (auto i : source_delete_files) {\n        int rc = removeFile(i, srcPath);\n\n        if (rc && !ignore_errors) {\n          std::cerr << \"error: failed to remove file '\" << srcPath.GetFullPath() << i <<\n                    \"'\" << std::endl;\n          exit(-1);\n        }\n      }\n    }\n\n    if (a == kSourceLinkDelete) {\n      for (auto i : source_delete_links) {\n        int rc = removeFile(i, srcPath);\n\n        if (rc && !ignore_errors) {\n          std::cerr << \"error: failed to remove link '\" << srcPath.GetFullPath() << i <<\n                    \"'\" << std::endl;\n          exit(-1);\n        }\n      }\n    }\n\n    if (a == kTargetFileCreate) {\n      for (auto i : target_create_files) {\n        cp_target_files.insert(i);\n      }\n    }\n\n    if (a == kTargetFileUpdate) {\n      for (auto i : target_updated_files) {\n        cp_target_files.insert(i);\n      }\n    }\n\n    if (a == kTargetFileMismatch) {\n      for (auto i : target_mismatch_files) {\n        cp_target_files.insert(i);\n      }\n    }\n\n    if (a == kTargetLinkCreate) {\n      for (auto i : target_create_links) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ link  ] create link \" << i.c_str() << \" => \" <<\n                    srcmap.links[i].target.c_str() << std::endl;\n        }\n\n        if (!dryrun) {\n          int rc = createLink(i, dstPath, srcmap.links[i].target, srcPath,\n                              srcmap.links[i].mtime);\n\n          if (rc && !ignore_errors) {\n            std::cerr << \"error: failed to create link '\" << dstPath.GetFullPath() << i <<\n                      \"'\" << std::endl;\n            exit(-1);\n          }\n        }\n      }\n    }\n\n    if (a == kTargetLinkUpdate) {\n      for (auto i : target_updated_links) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ link  ] update link \" << i.c_str() << \" => \" <<\n                    srcmap.links[i].target.c_str() << std::endl;\n        }\n\n        if (!dryrun) {\n          int rc = removeFile(i, dstPath);\n          rc |= createLink(i, dstPath, srcmap.links[i].target, srcPath,\n                           srcmap.links[i].mtime);\n\n          if (rc && !ignore_errors) {\n            std::cerr << \"error: failed to update link '\" << dstPath.GetFullPath() << i <<\n                      \"'\" << std::endl;\n            exit(-1);\n          }\n        }\n      }\n    }\n\n    if (a == kTargetLinkMismatch) {\n      for (auto i : target_mismatch_links) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ link  ] remove link \" << i.c_str() << std::endl;\n        }\n\n        if (!dryrun) {\n          int rc = removeFile(i, dstPath);\n          rc |= createLink(i, dstPath, srcmap.links[i].target, srcPath,\n                           srcmap.links[i].mtime);\n\n          if (rc && !ignore_errors) {\n            std::cerr << \"error: failed to update mismatching link '\" <<\n                      dstPath.GetFullPath() << i << \"'\" << std::endl;\n            exit(-1);\n          }\n        }\n      }\n    }\n\n    if (a == kSourceFileCreate) {\n      for (auto i : source_create_files) {\n        cp_source_files.insert(i);\n      }\n    }\n\n    if (a == kSourceFileUpdate) {\n      for (auto i : source_updated_files) {\n        cp_source_files.insert(i);\n      }\n    }\n\n    if (a == kSourceFileMismatch) {\n      for (auto i : source_mismatch_files) {\n        cp_source_files.insert(i);\n      }\n    }\n\n    if (a == kSourceLinkCreate) {\n      for (auto i : source_create_links) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ link  ] create link \" << i.c_str() << \" => \" <<\n                    dstmap.links[i].target.c_str() << std::endl;\n        }\n\n        if (!dryrun) {\n          int rc = createLink(i, srcPath, dstmap.links[i].target, dstPath,\n                              dstmap.links[i].mtime);\n\n          if (rc && !ignore_errors) {\n            std::cerr << \"error: failed to create link '\" << srcPath.GetFullPath() << i <<\n                      \"'\" << std::endl;\n            exit(-1);\n          }\n        }\n      }\n    }\n\n    if (a == kSourceLinkUpdate) {\n      for (auto i : source_updated_links) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ link  ] update link \" <<  i.c_str() <<\n                    dstmap.links[i].target.c_str() << std::endl;\n        }\n\n        if (!dryrun) {\n          int rc = removeFile(i, srcPath);\n          rc |= createLink(i, srcPath, dstmap.links[i].target, dstPath,\n                           dstmap.links[i].mtime);\n\n          if (rc && !ignore_errors) {\n            std::cerr << \"error: failed to update link '\" << srcPath.GetFullPath() << i <<\n                      \"'\" << std::endl;\n            exit(-1);\n          }\n        }\n      }\n    }\n\n    if (a == kSourceLinkMismatch) {\n      for (auto i : source_mismatch_links) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ link  ]remove link \" << i.c_str() << std::endl;\n        }\n\n        if (!dryrun) {\n          int rc = removeFile(i, srcPath);\n          rc |= createLink(i, srcPath, dstmap.links[i].target, dstPath,\n                           dstmap.links[i].mtime);\n\n          if (rc && !ignore_errors) {\n            std::cerr << \"error: failed to update mismatching link '\" <<\n                      srcPath.GetFullPath() << i << \"'\" << std::endl;\n            exit(-1);\n          }\n        }\n      }\n    }\n  }\n\n  for (auto i : cp_target_files) {\n    if (!dryrun) {\n      tprops.push_back(copyFile(i, srcPath, dstPath, srcmap.files[i].mtime));\n    } else {\n      if (!is_silent && verbose) {\n        std::cout << \"[ copy ] : \" << i.c_str() << \" \" << srcPath.GetFullPath().c_str()\n                  << \" => \" << dstPath.GetFullPath().c_str() << std::endl;\n      }\n    }\n  }\n\n  for (auto i : cp_source_files) {\n    if (!dryrun) {\n      tprops.push_back(copyFile(i, dstPath, srcPath, dstmap.files[i].mtime));\n    } else {\n      if (!is_silent && verbose) {\n        std::cout << \"[ copy ] : \" << i.c_str() << \" \" << dstPath.GetFullPath().c_str()\n                  << \" => \" << srcPath.GetFullPath().c_str() << std::endl;\n      }\n    }\n  }\n\n  class RCloneProgressHandler : public XrdCl::CopyProgressHandler\n  {\n  public:\n    virtual void BeginJob(uint16_t   jobNum,\n                          uint16_t   jobTotal,\n                          const URL* source,\n                          const URL* destination)\n    {\n      n = jobNum;\n      tot = jobTotal;\n    }\n\n    virtual void EndJob(uint16_t            jobNum,\n                        const PropertyList* result)\n    {\n      (void)jobNum;\n      (void)result;\n      std::string src;\n      std::string dst;\n      result->Get(\"source\", src);\n      result->Get(\"target\", dst);\n      XrdCl::URL durl(dst.c_str());\n      auto param = durl.GetParams();\n\n      if (param.count(\"local.mtime\")) {\n        // apply mtime changes when done to local files\n        struct timespec ts;\n        std::string tss = param[\"local.mtime\"];\n\n        if (!eos::common::Timing::Timespec_from_TimespecStr(tss, ts)) {\n          // apply local mtime;\n          struct timespec times[2];\n          times[0] = ts;\n          times[1] = ts;\n\n          if (utimensat(0, durl.GetPath().c_str(), times, AT_SYMLINK_NOFOLLOW)) {\n            std::cerr << \"error: failed to update modification time of '\" << durl.GetPath()\n                      << \"'\" << std::endl;\n          }\n        }\n      }\n    };\n\n    virtual void JobProgress(uint16_t jobNum,\n                             uint64_t bytesProcessed,\n                             uint64_t bytesTotal)\n    {\n      bp = bytesProcessed;\n      bt = bytesTotal;\n      n  = jobNum;\n\n      if (verbose) {\n        std::cerr << \"[ \" << jobNum << \"/\" << tot << \" ] files copied\" << std::endl;\n      } else {\n        if (!is_silent) {\n          std::cerr << \"[ \" << jobNum << \"/\" << tot << \" ] files copied\" << \"\\r\";\n        }\n      }\n    }\n\n    virtual bool ShouldCancel(uint16_t jobNum)\n    {\n      (void)jobNum;\n      return false;\n    }\n\n    std::atomic<uint64_t> bp;\n    std::atomic<uint64_t> bt;\n    std::atomic<uint16_t> n;\n    std::atomic<uint16_t> tot;\n  };\n\n  RCloneProgressHandler copyProgress;\n\n  if (!is_silent && verbose) {\n    std::cerr << \"# preparing\" << std::endl;\n  }\n\n  copyProcess.Prepare();\n\n  if (!is_silent && verbose) {\n    std::cerr << \"# running\" << std::endl;\n  }\n\n  copyProcess.Run(&copyProgress);\n\n  if (!is_silent) {\n    std::cout << std::endl;\n  }\n\n  // last step is to adjust directory mtimes\n  for (auto a : actions) {\n    if (a == kTargetDirMtime) {\n      for (auto i : target_mtime_dirs) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ mtime ] updating target mtime \" << i << std::endl;\n        }\n\n        if (!dryrun) {\n          int rc = setDirMtime(i, dstPath, srcmap.directories[i].mtime);\n\n          if (rc && !ignore_errors) {\n            std::cerr << \"error: failed to update directory mtime  '\" <<\n                      dstPath.GetFullPath() << i << \"'\" << std::endl;\n            exit(-1);\n          }\n        }\n      }\n    }\n\n    if (a == kSourceDirMtime) {\n      for (auto i : source_mtime_dirs) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ mtime ] updating source mtime \"  << i << std::endl;\n        }\n\n        if (!dryrun) {\n          int rc = setDirMtime(i, srcPath, dstmap.directories[i].mtime);\n\n          if (rc && !ignore_errors) {\n            std::cerr << \"error: failed to update directory mtime  '\" <<\n                      srcPath.GetFullPath() << i << \"'\" << std::endl;\n            exit(-1);\n          }\n        }\n      }\n    }\n  }\n\n  if (dryrun && !is_silent) {\n    std::cout << \"[ --------------------------- ]\" << std::endl;\n    std::cout << \"[ target                      ]\" << std::endl;\n    std::cout << \"[ --------------------------- ]\" << std::endl;\n    std::cout << \"[ # dir,files,links to create ] : \" << target_create_dirs.size()\n              << \",\" << target_create_files.size() << \",\" << target_create_links.size() <<\n              std::endl;\n    std::cout << \"[ # dir,files,links to delete ] : \" << target_delete_dirs.size()\n              << \",\" << target_delete_files.size() << \",\" << target_delete_links.size() <<\n              std::endl;\n    std::cout << \"[ # files,links to update     ] : \" << target_updated_files.size()\n              << \",\" << target_updated_links.size() << std::endl;\n    std::cout << \"[ # files,links mismatch      ] : \" <<\n              target_mismatch_files.size() << \",\" << target_mismatch_links.size() <<\n              std::endl;\n    std::cout << \"[ --------------------------- ]\" << std::endl;\n    std::cout << \"[ source                      ]\" << std::endl;\n    std::cout << \"[ --------------------------- ]\" << std::endl;\n    std::cout << \"[ # dir,files,links to create ] : \" << source_create_dirs.size()\n              << \",\" << source_create_files.size() << \",\" << source_create_links.size() <<\n              std::endl;\n    std::cout << \"[ # dir,files,links to delete ] : \" << source_delete_dirs.size()\n              << \",\" << source_delete_files.size() << \",\" << source_delete_links.size() <<\n              std::endl;\n    std::cout << \"[ # files,links to update     ] : \" << source_updated_files.size()\n              << \",\" << source_updated_links.size() << std::endl;\n    std::cout << \"[ # files,links mismatch      ] : \" <<\n              source_mismatch_files.size() << \",\" << source_mismatch_links.size() <<\n              std::endl;\n    std::cout << \"[ --------------------------- ]\" << std::endl;\n    std::cout << \"[ volume                      ]\" << std::endl;\n    std::cout << \"[ --------------------------- ]\" << std::endl;\n    XrdOucString sizestring;\n    eos::common::StringConversion::GetReadableSizeString(sizestring, copySize, \"B\");\n    std::cout << \"[ # data size                 ] : \" << sizestring.c_str() <<\n              std::endl;\n    eos::common::StringConversion::GetReadableSizeString(sizestring,\n        copyTransactions, \"\");\n    std::cout << \"[ # copy transactions         ] : \" << sizestring.c_str() <<\n              std::endl;\n    std::cout << \"[ --------------------------- ]\" << std::endl;\n  }\n\n  exit(0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_reconnect.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_reconnect.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n\n/* Force a reconnection/reauthentication */\nint\ncom_reconnect(char* arg1)\n{\n  // split subcommands\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString param = \"\";\n  XrdOucString option = \"\";\n  param = subtokenizer.GetToken();\n\n  if ((!param.length()) ||\n      (param == \"gsi\") ||\n      (param == \"krb5\") ||\n      (param == \"unix\") ||\n      (param == \"sss\")) {\n    if (param.length()) {\n      fprintf(stdout, \"# reconnecting to %s with <%s> authentication\\n\",\n              serveruri.c_str(), param.c_str());\n      setenv(\"XrdSecPROTOCOL\", param.c_str(), 1);\n    } else {\n      fprintf(stdout, \"# reconnecting to %s\\n\", serveruri.c_str());\n    }\n\n    XrdOucString path = serveruri;\n    path += \"//proc/admin/\";\n    return (0);\n  } else {\n    fprintf(stdout,\n            \"usage: reconnect [gsi,krb5,unix,sss]                                    :  reconnect to the management node [using the specified protocol]\\n\");\n    global_retc = EINVAL;\n    return (0);\n  }\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_report.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_report.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"common/Statistics.hh\"\n#include \"common/Path.hh\"\n#include \"common/Logging.hh\"\n#include <fcntl.h>\n#include <unistd.h>\n#include <set>\n#include <regex.h>\n#include <json/json.h>\n#include <sstream>\n\n/* Change working directory &*/\nint\ncom_report(char* arg1)\n{\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString arg;\n  XrdOucString path;\n  std::string sregex;\n  size_t max_reports = 2000000000;\n  bool silent = false;\n  XrdOucString squash;\n  time_t start_time = 0;\n  time_t stop_time = 0;\n  time_t first_ts = 0;\n  time_t last_ts = 0;\n  double max_eff = 100.0;\n  bool reading = false;\n  bool writing = false;\n  bool json = false;\n  Json::Value gjson;\n\n  do {\n    arg = subtokenizer.GetToken();\n\n    if (!arg.length() && path.length()) {\n      break;\n    }\n\n    if (arg == \"--regex\") {\n      arg = subtokenizer.GetToken();\n\n      if (!arg.length()) {\n        goto com_report_usage;\n      } else {\n        sregex = arg.c_str();\n      }\n\n      continue;\n    }\n\n    if (arg == \"-n\") {\n      arg = subtokenizer.GetToken();\n\n      if (!arg.length()) {\n        goto com_report_usage;\n      } else {\n        max_reports = std::strtoul(arg.c_str(), 0, 10);\n      }\n\n      continue;\n    }\n\n    if (arg == \"--read\") {\n      reading = true;\n      continue;\n    }\n\n    if (arg == \"--write\") {\n      writing = true;\n      continue;\n    }\n\n    if (arg == \"--json\") {\n      json = true;\n      continue;\n    }\n\n    if (arg == \"--max-efficiency\") {\n      arg = subtokenizer.GetToken();\n      max_eff = strtod(arg.c_str(), 0);\n\n      if ((max_eff < 0) || (max_eff > 100)) {\n        goto com_report_usage;\n      }\n\n      continue;\n    }\n\n    if (arg == \"--squash\") {\n      arg = subtokenizer.GetToken();\n\n      if (!arg.beginswith(\"/\") && !arg.endswith(\"/\")) {\n        goto com_report_usage;\n      }\n\n      squash = arg;\n      continue;\n    }\n\n    if (arg == \"--start\") {\n      arg = subtokenizer.GetToken();\n\n      if (!arg.length()) {\n        goto com_report_usage;\n      } else {\n        start_time = std::strtoul(arg.c_str(), 0, 10);\n      }\n\n      continue;\n    }\n\n    if (arg == \"--stop\") {\n      arg = subtokenizer.GetToken();\n\n      if (!arg.length()) {\n        goto com_report_usage;\n      } else {\n        stop_time = std::strtoul(arg.c_str(), 0, 10);\n      }\n\n      continue;\n    }\n\n    if (arg == \"-s\") {\n      silent = true;\n      continue;\n    }\n\n    if ((!arg.length()) || (arg.beginswith(\"--help\")) || (arg.beginswith(\"-h\"))) {\n      goto com_report_usage;\n    }\n\n    path = arg;\n  } while (arg.length());\n\n  if (!reading && !writing) {\n    reading = writing = true;\n  }\n\n  {\n    std::string reportfile = path.c_str();\n    std::ifstream file(reportfile);\n\n    if (file.is_open()) {\n      std::map<std::string, std::string> map;\n      std::string line;\n      std::vector<std::string> keys;\n      std::multiset<float> r_t;\n      std::multiset<float> w_t;\n      uint64_t sum_w, sum_r;\n      size_t n_w = 0;\n      size_t n_r = 0;\n      double reff = 0;\n      double srleff = 0;\n      double weff = 0;\n      double swleff = 0;\n      sum_w = sum_r = 0;\n      std::string sizestring;\n      size_t n_reports = 0;\n      regex_t regex;\n\n      if (sregex.length()) {\n        // Compile regex\n        int regexErrorCode = regcomp(&regex, sregex.c_str(), REG_EXTENDED);\n\n        if (regexErrorCode) {\n          fprintf(stderr, \"error: regular expression is invalid regex-rc=%d\\n\",\n                  regexErrorCode);\n          global_retc = EINVAL;\n          return (0);\n        }\n      }\n\n      while (std::getline(file, line)) {\n        if (sregex.length()) {\n          // Execute regex\n          int result = regexec(&regex, line.c_str(), 0, NULL, 0);\n\n          // Check the result\n          if (result == REG_NOMATCH) {\n            // next entry\n            continue;\n          } else if (result != 0) { // REG_BADPAT, REG_ESPACE, etc...\n            fprintf(stderr, \"error: invalid regex\\n\");\n            global_retc = EINVAL;\n            return (0);\n          } else {\n          }\n        }\n\n        std::map<std::string, std::string> map;\n\n        if (eos::common::StringConversion::GetSpecialKeyValueMap(line.c_str(),\n            map,\n            \"=\",\n            \"&\",\n            &keys)) {\n          if (!sregex.length() && map[\"td\"].substr(0, 6) == \"daemon\") {\n            continue;\n          }\n\n          if (!map.count(\"rb\") && !map.count(\"wb\")) {\n            continue;\n          }\n\n          if (map[\"sec.app\"] == \"deletion\") {\n            continue;\n          }\n\n          bool found = false;\n          time_t start_ots = std::stoul(map[\"ots\"]);\n          time_t start_cts = std::stoul(map[\"cts\"]);\n\n          if (start_time) {\n            if (start_ots < start_time) {\n              continue;\n            }\n          }\n\n          if (stop_time) {\n            if (start_ots > stop_time) {\n              continue;\n            }\n          }\n\n\t  if (!first_ts) {\n            first_ts = start_ots;\n          }\n\n          last_ts = start_cts;\n\n          ssize_t wsize = std::stol(map[\"wb\"]);\n          ssize_t rsize = std::stol(map[\"rb\"]);\n          double iot = std::stod(map[\"iot\"]);\n          double idt = std::stod(map[\"idt\"]);\n          double lwt = std::stod(map[\"lwt\"]);\n          double lrt = std::stod(map[\"lrt\"]);\n          double lrvt = std::stod(map[\"lrvt\"]);\n          double deff = 100.0 - (iot ? (100.0 * idt / iot) : 0.0);\n          double lreff = 100.0 * ((iot - lrt - lrvt) / iot);\n          double lweff = 100.0 * ((iot - lwt) / iot);\n          int eff = (int)(deff);\n\n          // filter maximum efficiency\n          if (deff > max_eff)  {\n            continue;\n          }\n\n          if (json && !silent) {\n            Json::Value ljson;\n\n            for (auto it = map.begin(); it != map.end(); ++it) {\n              if (eos::common::StringConversion::IsDecimalNumber(it->second)) {\n                ljson[it->first] = (Json::Value::UInt64)strtoull(it->second.c_str(), 0, 10);\n              } else if (eos::common::StringConversion::IsDouble(it->second)) {\n                ljson[it->first] = strtod(it->second.c_str(), 0);\n              } else {\n                ljson[it->first] = it->second;\n              }\n            }\n\n            ljson[\"io\"][\"efficiency\"][\"total\"] = deff;\n            ljson[\"io\"][\"efficiency\"][\"disk\"][\"rd\"] = lreff;\n            ljson[\"io\"][\"efficiency\"][\"disk\"][\"wr\"] = lweff;\n            Json::StreamWriterBuilder builder;\n            builder[\"indentation\"] = \"\";  // assume default for comments is None\n            std::string str = Json::writeString(builder, ljson);\n            fprintf(stdout, \"%s\", str.c_str());\n            fprintf(stdout, \"\\n\");\n          }\n\n          // classify write or read\n          if (std::stol(map[\"wb\"]) > 0 && writing) {\n            sum_w += wsize;\n            n_w++;\n            weff += deff;\n            swleff += lweff;\n            double tt = std::stoul(map[\"cts\"]) - std::stoul(map[\"ots\"]) +\n                        (0.001 * std::stoul(map[\"ctms\"])) - (0.001 * std::stoul(map[\"otms\"]));\n            float rate = wsize  / tt / 1000000.0;\n\n            if (!silent && !json) {\n              fprintf(stdout,\n                      \"W %-16s t=%06.02f [s] r=%06.02f [MB/s] eff=%02d/%02d [%%] path=%64s\\n\",\n                      eos::common::StringConversion::GetReadableSizeString(sizestring, wsize, \"\"), tt,\n                      rate, eff, (int)lweff, map[\"path\"].c_str());\n            }\n\n            w_t.insert(tt);\n            found = true;\n          }\n\n          if (std::stol(map[\"rb\"]) > 0 && reading) {\n            sum_r += rsize;\n            n_r++;\n            reff += deff;\n            srleff += lreff;\n            double tt = std::stoul(map[\"cts\"]) - std::stoul(map[\"ots\"]) +\n                        (0.001 * std::stoul(map[\"ctms\"])) - (0.001 * std::stoul(map[\"otms\"]));\n            float rate = rsize / tt / 1000000.0;\n\n            if (!silent && !json && !squash.length()) {\n              fprintf(stdout,\n                      \"R %-16s t=%06.02f [s] r=%06.02f [MB/s] eff=%02d/%02d [%%] path=%64s\\n\",\n                      eos::common::StringConversion::GetReadableSizeString(sizestring, rsize, \"\"), tt,\n                      rate, eff, (int)lreff, map[\"path\"].c_str());\n            }\n\n            r_t.insert(tt);\n            found = true;\n          }\n\n          if (found) {\n            n_reports++;\n          }\n        } else {\n          fprintf(stderr, \"error: failed to parse '%s'\\n\", line.c_str());\n        }\n\n        if (n_reports >= max_reports) {\n          break;\n        }\n\n        if (squash.length()) {\n          std::string rpath = squash.c_str();\n          rpath += map[\"path\"].c_str();\n          eos::common::Path cPath(rpath.c_str());\n          fprintf(stderr, \"info: squash %s\\n\", cPath.GetFullPath().c_str());\n          cPath.MakeParentPath(0644);\n          int fd = open(rpath.c_str(), O_CREAT | O_RDWR | O_APPEND, S_IRWXU | S_IRWXG);\n\n          if (fd < 0) {\n            fprintf(stderr, \"error:failed to create\\n\");\n          } else {\n            (void) ::write(fd, line.c_str(), line.length() + 1);\n            (void) ::close(fd);\n          }\n        }\n      }\n\n      if (!json) {\n        std::string sizestring1, sizestring2;\n        fprintf(stdout,\n                \"---------------------------------------------------------------------\\n\");\n        fprintf(stdout, \"- n(r): %lu vol(r): %s n(w): %lu vol(w): %s\\n\",\n                r_t.size(),\n                eos::common::StringConversion::GetReadableSizeString(sizestring1, sum_r, \"B\"),\n                w_t.size(),\n                eos::common::StringConversion::GetReadableSizeString(sizestring2, sum_w, \"B\"));\n        fprintf(stdout,\n                \"---------------------------------------------------------------------\\n\");\n        fprintf(stdout, \"- r:t avg: %s +- %s 95-perc: %s 99-perc: %s max: %s \\n\",\n                eos::common::StringConversion::GetFixedDouble(eos::common::Statistics::avg(r_t),\n                    6, 2).c_str(),\n                eos::common::StringConversion::GetFixedDouble(eos::common::Statistics::sig(r_t),\n                    6, 2).c_str(),\n                eos::common::StringConversion::GetFixedDouble(eos::common::Statistics::nperc(\n                      r_t, 95), 6, 2).c_str(),\n                eos::common::StringConversion::GetFixedDouble(eos::common::Statistics::nperc(\n                      r_t, 99), 6, 2).c_str(),\n                eos::common::StringConversion::GetFixedDouble(eos::common::Statistics::max(r_t),\n                    6, 2).c_str()\n               );\n        fprintf(stdout, \"- w:t avg: %s +- %s 95-perc: %s 99-perc: %s max: %s \\n\",\n                eos::common::StringConversion::GetFixedDouble(eos::common::Statistics::avg(w_t),\n                    6, 2).c_str(),\n                eos::common::StringConversion::GetFixedDouble(eos::common::Statistics::sig(w_t),\n                    6, 2).c_str(),\n                eos::common::StringConversion::GetFixedDouble(eos::common::Statistics::nperc(\n                      w_t, 95), 6, 2).c_str(),\n                eos::common::StringConversion::GetFixedDouble(eos::common::Statistics::nperc(\n                      w_t, 99), 6, 2).c_str(),\n                eos::common::StringConversion::GetFixedDouble(eos::common::Statistics::max(w_t),\n                    6, 2).c_str()\n               );\n        fprintf(stdout,\n                \"---------------------------------------------------------------------\\n\");\n        XrdOucString agestring;\n        fprintf(stdout, \"- first-ts:%ld last-ts:%ld time-span:%ld s [ %s ] \\n\",\n                first_ts,\n                last_ts,\n                last_ts - first_ts,\n                eos::common::StringConversion::GetReadableAgeString(agestring,\n                    last_ts - first_ts));\n        fprintf(stdout, \"- r:rate eff: %02d/%02d%% avg: %.02f MB/s\\n\",\n                n_r ? ((int)(reff / n_r)) : 0, n_r ? ((int)(srleff / n_r)) : 0,\n                (last_ts - first_ts) ? sum_r / 1000000.0 / (last_ts - first_ts) : 0);\n        fprintf(stdout, \"- w:rate eff: %02d/%02d%% avg: %.02f MB/s\\n\",\n                n_w ? ((int)(weff / n_w)) : 0, n_w ? ((int)(swleff / n_w)) : 0,\n                (last_ts - first_ts) ? sum_w / 1000000.0 / (last_ts - first_ts) : 0);\n        fprintf(stdout,\n                \"---------------------------------------------------------------------\\n\");\n      } else {\n        if (silent) {\n          gjson[\"report\"][\"rd\"][\"n\"] = (Json::Value::UInt64)n_r;\n          gjson[\"report\"][\"timestamp\"][\"first\"] = (Json::Value::UInt64)first_ts;\n          gjson[\"report\"][\"timestamp\"][\"last\"]  = (Json::Value::UInt64)last_ts;\n          gjson[\"report\"][\"wr\"][\"n\"] = (Json::Value::UInt64)n_w;\n          gjson[\"report\"][\"rd\"][\"bytes\"][\"sum\"] = (Json::Value::UInt64)sum_r;\n          gjson[\"report\"][\"wr\"][\"bytes\"][\"sum\"] = (Json::Value::UInt64)sum_w;\n          gjson[\"report\"][\"rd\"][\"bytes\"][\"avg\"] = eos::common::Statistics::avg(r_t);\n          gjson[\"report\"][\"wr\"][\"bytes\"][\"avg\"] = eos::common::Statistics::avg(w_t);\n          gjson[\"report\"][\"rd\"][\"bytes\"][\"sig\"] = eos::common::Statistics::sig(r_t);\n          gjson[\"report\"][\"wr\"][\"bytes\"][\"sig\"] = eos::common::Statistics::sig(w_t);\n          gjson[\"report\"][\"rd\"][\"bytes\"][\"max\"] = eos::common::Statistics::max(r_t);\n          gjson[\"report\"][\"wr\"][\"bytes\"][\"max\"] = eos::common::Statistics::max(w_t);\n          gjson[\"report\"][\"rd\"][\"bytes\"][\"95\"] = eos::common::Statistics::nperc(r_t, 95);\n          gjson[\"report\"][\"wr\"][\"bytes\"][\"95\"] = eos::common::Statistics::nperc(w_t, 95);\n          gjson[\"report\"][\"rd\"][\"bytes\"][\"99\"] = eos::common::Statistics::nperc(r_t, 99);\n          gjson[\"report\"][\"wr\"][\"bytes\"][\"99\"] = eos::common::Statistics::nperc(w_t, 99);\n          gjson[\"report\"][\"rd\"][\"rate\"] = (last_ts - first_ts) ? sum_r / 1000000.0 /\n                                          (last_ts - first_ts) : 0;\n          gjson[\"report\"][\"wr\"][\"rate\"] = (last_ts - first_ts) ? sum_w / 1000000.0 /\n                                          (last_ts - first_ts) : 0;\n          gjson[\"report\"][\"rd\"][\"efficiency\"][\"client\"] = n_r ? (reff / n_r) : 0;\n          gjson[\"report\"][\"rd\"][\"efficiency\"][\"server\"] = n_r ? (srleff / n_r) : 0;\n          gjson[\"report\"][\"rd\"][\"efficiency\"][\"client\"] = n_w ? (weff / n_w) : 0;\n          gjson[\"report\"][\"rd\"][\"efficiency\"][\"server\"] = n_w ? (swleff / n_w) : 0;\n          fprintf(stdout, \"%s\", SSTR(gjson).c_str());\n        }\n      }\n\n      file.close();\n\n      if (sregex.length()) {\n        regfree(&regex);\n      }\n    } else {\n      fprintf(stderr, \"error: unable to open file!\\n\");\n      global_retc = EIO;\n      return (0);\n    }\n  }\n\n  return (0);\ncom_report_usage:\n  fprintf(stdout,\n          \"'[eos] report [-n <nrecords>] [--regex <regex>] [-s] [--start <unixtime>] [--stop <unixtime>] [--max-efficiency <percent>] [--read] [--write] [--json] <reportfile>\\n\");\n  fprintf(stdout, \"Usage: report <file>\\n\");\n  fprintf(stdout, \"Options:\\n\");\n  fprintf(stdout,\n          \"          -s         : show only the summary with N(r) [number of files read] N(w) [number of files written] VOL(r) [data volume read] VOL (w) [data volume written],\\n\");\n  fprintf(stdout,\n          \"                       + timings avg [average transfer time], 95-perc [95 percentile], 99-perc [99 percentila] max [maximal transfer time]\\n\");\n  fprintf(stdout,\n          \"          -n <n>     : stop after n records are accepted for the statistics\\n\");\n  fprintf(stdout,\n          \"--max-efficiency <n> : consider records which have an efficienc <=n (in percent)\\n\");\n  fprintf(stdout,\n          \"     --regex <regex> : apply <regex> for filtering the records\\n\");\n  fprintf(stdout,\n          \"  --start <unixtime> : only take records starting after <unixtime>\\n\");\n  fprintf(stdout,\n          \"  --stop <unixtime>  : only take records starting before <unixtime>\\n\");\n  fprintf(stdout,\n          \"              --read : select all read records\\n\");\n  fprintf(stdout,\n          \"             --write : select all write records\\n\");\n  fprintf(stdout,\n          \"              --json : write json output format\\n\");\n  fprintf(stdout,\n          \"Example:               bash> eos report /var/eos/report/2021/05/20210530.eosreport\\n\");\n  fprintf(stdout,\n          \"                       bash> zcat /var/eos/report/2021/05/20210530.eosreport.gz | eos report /dev/stdin -s\\n\");\n  fprintf(stdout,\n          \"                       bash> eos report /var/eos/report/2021/05/20210530.eosreport --regex \\\"sec.app=fuse\\\" -s\\n\");\n  fprintf(stdout,\n          \"                       #select only reads\\n\"\n          \"                       bash> eos report /var/eos/report/2021/05/20210530.eosreport --read\\n\");\n  fprintf(stdout,\n          \"                       #select only writes\\n\"\n          \"                       bash> eos report /var/eos/report/2021/05/20210530.eosreport --write\\n\");\n  fprintf(stdout,\n          \"                       #convert into line-wise json records\\n\"\n          \"                       bash> eos report /var/eos/report/2021/05/20210530.eosreport --json\\n\");\n  fprintf(stdout,\n          \"                       #get summary as json output\\n\"\n          \"                       bash> eos report /var/eos/report/2021/05/20210530.eosreport --json -s\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_rm.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_rm.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"common/Path.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/utils/RandUtils.hh\"\n#include \"console/ConsoleMain.hh\"\n/*----------------------------------------------------------------------------*/\n\n/* Remove a file */\nint\ncom_rm(char* arg1)\n{\n  // split subcommands\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString s1 = subtokenizer.GetToken();\n  XrdOucString s2 = subtokenizer.GetToken();\n  XrdOucString path;\n  XrdOucString option;\n  eos::common::Path* cPath = 0;\n  XrdOucString in = \"mgm.cmd=rm&\";\n  bool noconfirmation = false;\n\n  if (wants_help(arg1)) {\n    goto com_rm_usage;\n  }\n\n  if ((s1 == \"-r\")  || (s1 == \"-rf\") || (s1 == \"-fr\")) {\n    // normal recursive delete\n    option = \"r\";\n    path = s2;\n  } else if ((s1 == \"-rF\") || (s1 == \"-Fr\")) {\n    // recursive delete disabling the recycle bin\n    option = \"rf\";\n    path = s2;\n  } else if ((s1 == \"-F\") || (s1 == \"--no-recycle-bin\")) {\n    // delete disabling the recycle bin\n    option = \"f\";\n    path = s2;\n  } else if (s1.beginswith(\"-\")) {\n    goto com_rm_usage;\n  } else {\n    option = \"\";\n    path = s1;\n  }\n\n  if (path==\"--no-confirmation\") {\n    fprintf(stderr,\"disabling configmration\\n\");\n    noconfirmation=true;\n  }\n\n  do {\n    XrdOucString param = subtokenizer.GetToken();\n    if (param.length()) {\n      path += \" \";\n      path += param;\n    } else {\n      break;\n    }\n  } while (1);\n\n  // remove escaped blanks\n  while (path.replace(\"\\\\ \", \" \")) {\n  }\n\n  if (!path.length()) {\n    goto com_rm_usage;\n  } else {\n    unsigned long long id;\n\n    if (Path2FileDenominator(path, id)) {\n      in += \"&mgm.file.id=\";\n      in += std::to_string(id).c_str();\n\n      if (option.find(\"r\") != STR_NPOS) {\n        fprintf(stderr,\n                \"error: you cannot use a recursive deletion giving a file id!\\n\");\n        goto com_rm_usage;\n      }\n    } else {\n      if (Path2ContainerDenominator(path, id)) {\n        in += \"&mgm.container.id=\";\n        in += std::to_string(id).c_str();\n      } else {\n        path = abspath(path.c_str());\n        in += \"&mgm.path=\";\n        in += path;\n      }\n    }\n\n    in += \"&mgm.option=\";\n    in += option;\n    cPath = new eos::common::Path(path.c_str());\n\n    if ((option == \"r\") && (cPath->GetSubPathSize() < 4) && !noconfirmation) {\n      string s;\n      fprintf(stdout, \"Do you really want to delete ALL files starting at %s ?\\n\",\n              path.c_str());\n      fprintf(stdout, \"Confirm the deletion by typing => \");\n      XrdOucString confirmation = \"\";\n\n      for (int i = 0; i < 10; i++) {\n        confirmation += eos::common::getRandom<int>(0, 8);\n      }\n\n      fprintf(stdout, \"%s\\n\", confirmation.c_str());\n      fprintf(stdout, \"                               => \");\n      getline(std::cin, s);\n      std::string sconfirmation = confirmation.c_str();\n\n      if (s == sconfirmation) {\n        fprintf(stdout, \"\\nDeletion confirmed\\n\");\n        in += \"&mgm.deletion=deep\";\n        delete cPath;\n      } else {\n        fprintf(stdout, \"\\nDeletion aborted\\n\");\n        global_retc = EINTR;\n        delete cPath;\n        return (0);\n      }\n    }\n\n    global_retc = output_result(client_command(in));\n    return (0);\n  }\n\ncom_rm_usage:\n  fprintf(stdout,\n          \"usage: rm [-rf] [-F|--no-recycle-bin] [--no-confirmation] [--no-globbing] [<path>|fid:<fid-dec>|fxid:<fid-hex>]                    :  remove file <path>\\n\");\n  fprintf(stdout,\n          \"                                                                    -r :  remove recursivly\\n\");\n  fprintf(stdout,\n          \"                                                                    -f :  default force flag is ignored because there is no file by file feedback to confirm removing\\n\");\n  fprintf(stdout,\n          \"                                                      --no-recycle-bin :\\n\"\n          \"                                                                    -F :  remove bypassing recycling policies (you have to take the root role to use this flag!)\\n\");\n  fprintf(stdout,\n\t  \"                                                      --no-confirmation:  will not ask an interactive confirmation code if a recursive deletion is running in directory level < 4!\\n\");\n  fprintf(stdout,\n          \"                                                      --no-globbing:  disables path globbing feature (e.g: delete a file containing '[]' characters)\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_rmdir.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_rmdir.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n/*----------------------------------------------------------------------------*/\n\n/* Remove a directory */\nint\ncom_rmdir(char* arg1)\n{\n  // split subcommands\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString path = subtokenizer.GetToken();\n  XrdOucString in = \"mgm.cmd=rmdir&\";\n\n  if (wants_help(arg1)) {\n    goto com_rmdir_usage;\n  }\n\n  if ((path == \"--help\") || (path == \"-h\")) {\n    goto com_rmdir_usage;\n  }\n\n  if (!path.length()) {\n    goto com_rmdir_usage;\n  } else {\n    path = abspath(path.c_str());\n    in += \"mgm.path=\";\n    in += path;\n    global_retc = output_result(client_command(in));\n    return (0);\n  }\n\ncom_rmdir_usage:\n  fprintf(stdout,\n          \"usage: rmdir <path>                                                   :  remote directory <path>\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_role.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_role.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n\n/* Set the client user and group role */\nint\ncom_role(char* arg)\n{\n  eos::common::StringTokenizer subtokenizer(arg);\n  subtokenizer.GetLine();\n  user_role = subtokenizer.GetToken();\n  group_role = subtokenizer.GetToken();\n\n  if (wants_help(arg)) {\n    goto com_role_usage;\n  }\n\n  if (!silent) {\n    fprintf(stdout, \"=> selected user role ruid=<%s> and group role rgid=<%s>\\n\",\n            user_role.c_str(), group_role.c_str());\n  }\n\n  if (user_role.beginswith(\"-\")) {\n    goto com_role_usage;\n  }\n\n  gGlobalOpts.mUserRole = user_role.c_str();\n  gGlobalOpts.mGroupRole = group_role.c_str();\n  return (0);\ncom_role_usage:\n  fprintf(stdout,\n          \"usage: role <user-role> [<group-role>]                       : select user role <user-role> [and group role <group-role>]\\n\");\n  fprintf(stdout,\n          \"            <user-role> can be a virtual user ID (unsigned int) or a user mapping alias\\n\");\n  fprintf(stdout,\n          \"            <group-role> can be a virtual group ID (unsigned int) or a group mapping alias\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_rtlog.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_rtlog.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n#include <string.h>\n/*----------------------------------------------------------------------------*/\n\n/* Retrieve realtime log output */\n\nint\ncom_rtlog(char* arg1)\n{\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString queue = subtokenizer.GetToken();\n  XrdOucString lines = subtokenizer.GetToken();\n  XrdOucString tag = subtokenizer.GetToken();\n  XrdOucString filter = subtokenizer.GetToken();\n  XrdOucString in = \"mgm.cmd=rtlog&mgm.rtlog.queue=\";\n\n  if (wants_help(arg1) || !strlen(arg1)) {\n    goto com_rtlog_usage;\n  }\n\n  if (!queue.length()) {\n    goto com_rtlog_usage;\n  }\n\n  if ((queue != \".\") && (queue != \"*\") && (!queue.beginswith(\"/eos/\"))) {\n    // there is no queue argument and means to talk with the mgm directly\n    filter = tag;\n    tag = lines;\n    lines = queue;\n    queue = \".\";\n  }\n\n  if (queue.length()) {\n    in += queue;\n\n    if (!lines.length()) {\n      in += \"&mgm.rtlog.lines=10\";\n    } else {\n      in += \"&mgm.rtlog.lines=\";\n    }\n\n    in += lines;\n\n    if (!tag.length()) {\n      in += \"&mgm.rtlog.tag=err\";\n    } else {\n      in += \"&mgm.rtlog.tag=\";\n    }\n\n    in += tag;\n\n    if (filter.length()) {\n      in += \"&mgm.rtlog.filter=\";\n    }\n\n    in += filter;\n    global_retc = output_result(client_command(in, true));\n    return (0);\n  }\n\ncom_rtlog_usage:\n  fprintf(stdout,\n          \"usage: rtlog [<queue>|*|.] [<sec in the past>=3600] [<debug>=err] [filter-word]\\n\");\n  fprintf(stdout, \"                     - '*' means to query all nodes\\n\");\n  fprintf(stdout,\n          \"                     - '.' means to query only the connected mgm\\n\");\n  fprintf(stdout,\n          \"                     - if the first argument is ommitted '.' is assumed\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_scitoken.cc",
    "content": "//------------------------------------------------------------------------------\n// File: com_scitoken.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifdef __APPLE__\nint\ncom_scitoken(char* arg1)\n{\n  fprintf(stderr, \"error: scitoken command is not support on OSX\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n\n#else\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/jwk_generator/jwk_generator.hpp\"\n#include \"common/Mapping.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/Utils.hh\"\n#include <common/Logging.hh>\n#include <cstdio>\n#include <fstream>\n#include <getopt.h>\n#include <json/json.h>\n#include <memory>\n#include <scitokens/scitokens.h>\n#include <stdlib.h>\n#include <string>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n#include <vector>\n\n//------------------------------------------------------------------------------\n// SciToken command\n//------------------------------------------------------------------------------\nint\ncom_scitoken(char* arg1)\n{\n  // split subcommands\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString subcommand = subtokenizer.GetTokenUnquoted();\n  std::string option;\n  std::string value;\n  time_t expires = 0;\n  std::string cred;\n  std::string key;\n  std::string creddata;\n  std::string keydata;\n  std::string keyid;\n  std::string issuer;\n  std::string jwk;\n  std::string profile = \"wlcg\";\n  std::set<std::string> claims;\n\n  if (wants_help(arg1)) {\n    goto com_scitoken_usage;\n  }\n\n  if (subcommand == \"create\") {\n    do {\n      const char* o = subtokenizer.GetTokenUnquoted();\n      const char* v = subtokenizer.GetTokenUnquoted();\n\n      if (o && !v) {\n        goto com_scitoken_usage;\n      }\n\n      if (!o && !v) {\n        break;\n      }\n\n      option = o;\n      value = v;\n\n      if (option == \"--pubkey\") {\n        cred = value;\n      }\n\n      if (option == \"--privkey\") {\n        key = value;\n      }\n\n      if (option == \"--keyid\") {\n        keyid = value;\n      }\n\n      if (option == \"--issuer\") {\n        issuer = value;\n      }\n\n      if (option == \"--claim\") {\n        claims.insert(value);\n      }\n\n      if (option == \"--expires\") {\n        expires = atoi(value.c_str());\n      }\n\n      if (option == \"--profile\") {\n        profile = value;\n      }\n    } while (option.length());\n\n    if (issuer.empty() || !claims.size() || keyid.empty()) {\n      goto com_scitoken_usage;\n    }\n\n    if (cred.empty()) {\n      cred = \"/etc/xrootd/\";\n      cred += keyid;\n      cred += \"-pkey.pem\";\n    }\n\n    if (key.empty()) {\n      key = \"/etc/xrootd/\";\n      key += keyid;\n      key += \"-key.pem\";\n    }\n\n    eos::common::StringConversion::LoadFileIntoString(key.c_str(), keydata);\n\n    if (keydata.empty()) {\n      std::cerr << \"error: cannot load private key from '\" << key.c_str() << \"'\"\n                << std::endl;\n      global_retc = EINVAL;\n      return (0);\n    }\n\n    eos::common::StringConversion::LoadFileIntoString(cred.c_str(), creddata);\n\n    if (creddata.empty()) {\n      std::cerr << \"error: cannot load public key from '\" << cred.c_str() << \"'\"\n                << std::endl;\n      global_retc = EINVAL;\n      return (0);\n    }\n\n    // sci-token code\n    char* err_msg = 0;\n    auto key_raw = scitoken_key_create(keyid.c_str(), \"ES256\", creddata.c_str(),\n                                       keydata.c_str(), &err_msg);\n    std::unique_ptr<void, decltype(&scitoken_key_destroy)> key(\n      key_raw, scitoken_key_destroy);\n\n    if (key_raw == nullptr) {\n      std::cerr << \"error: failed to generate a key: \" << err_msg << std::endl;\n      free(err_msg);\n      global_retc = EFAULT;\n      return (0);\n    }\n\n    std::unique_ptr<void, decltype(&scitoken_destroy)> token(\n      scitoken_create(key_raw), scitoken_destroy);\n\n    if (token.get() == nullptr) {\n      std::cerr << \"error: failed to generate a new token\" << std::endl;\n      global_retc = EFAULT;\n      return (0);\n    }\n\n    int rv =\n      scitoken_set_claim_string(token.get(), \"iss\", issuer.c_str(), &err_msg);\n\n    if (rv) {\n      std::cerr << \"error: failed to set issuer: \" << err_msg << std::endl;\n      free(err_msg);\n      global_retc = EFAULT;\n      return (0);\n    }\n\n    for (const auto& claim : claims) {\n      auto pos = claim.find(\"=\");\n\n      if (pos == std::string::npos) {\n        std::cerr << \"error: claim must contain a '=' character: \"\n                  << claim.c_str() << std::endl;\n        global_retc = EFAULT;\n        return (0);\n      }\n\n      auto key = claim.substr(0, pos);\n      auto val = claim.substr(pos + 1);\n      rv = scitoken_set_claim_string(token.get(), key.c_str(), val.c_str(),\n                                     &err_msg);\n\n      if (rv) {\n        std::cerr << \"error: failed to set claim '\" << key << \"'='\" << val\n                  << \"' error:\" << err_msg << std::endl;\n        free(err_msg);\n        global_retc = EFAULT;\n        return (0);\n      }\n    }\n\n    if (expires) {\n      auto lifetime = expires - time(NULL);\n\n      if (lifetime < 0) {\n        lifetime = 0;\n      }\n\n      scitoken_set_lifetime(token.get(), lifetime);\n    }\n\n    if (profile == \"wlcg\") {\n      profile = SciTokenProfile::WLCG_1_0;\n    } else if (profile == \"scitokens1\") {\n      profile = SciTokenProfile::SCITOKENS_1_0;\n    } else if (profile == \"scitokens2\") {\n      profile = SciTokenProfile::SCITOKENS_2_0;\n    } else if (profile == \"atjwt\") {\n      profile = SciTokenProfile::AT_JWT;\n    } else {\n      std::cerr << \"error: unknown token profile: \" << profile << std::endl;\n      global_retc = EINVAL;\n      return (0);\n    }\n\n    SciTokenProfile sprofile = WLCG_1_0;\n    scitoken_set_serialize_mode(token.get(), sprofile);\n    // finalue dump the token\n    char* value = 0;\n    rv = scitoken_serialize(token.get(), &value, &err_msg);\n\n    if (rv) {\n      std::cerr << \"error: failed to serialize the token: \" << err_msg\n                << std::endl;\n      free(err_msg);\n      global_retc = EFAULT;\n      return (0);\n    }\n\n    std::cout << value << std::endl;\n    return (0);\n  }\n\n  if (subcommand == \"dump\") {\n    XrdOucString token = subtokenizer.GetTokenUnquoted();\n\n    if (!token.length()) {\n      goto com_scitoken_usage;\n    }\n\n    try {\n      std::string stoken = token.c_str();\n      std::cerr << \"# \"\n                \"-----------------------------------------------------------\"\n                \"-------------------- #\"\n                << std::endl;\n      std::cerr << eos::common::Mapping::PrintJWT(stoken, false) << std::endl;\n      std::cerr << \"# \"\n                \"-----------------------------------------------------------\"\n                \"-------------------- #\"\n                << std::endl;\n      global_retc = 0;\n    } catch (...) {\n      std::cerr << \"error: failed to print token\" << std::endl;\n      global_retc = EINVAL;\n    }\n\n    return (0);\n  }\n\n  if (subcommand == \"create-keys\") {\n    do {\n      const char* o = subtokenizer.GetTokenUnquoted();\n      const char* v = subtokenizer.GetTokenUnquoted();\n\n      if (o && !v) {\n        goto com_scitoken_usage;\n      }\n\n      if (!o && !v) {\n        break;\n      }\n\n      option = o;\n      value = v;\n\n      if (option == \"--keyid\") {\n        keyid = value;\n      }\n    } while (option.length());\n\n    std::string prefix;\n\n    if (!keyid.empty()) {\n      prefix = \"/etc/xrootd/\";\n    } else {\n      keyid = \"default\";\n      const size_t size = 1024;\n      char buffer[size];\n\n      if (getcwd(buffer, size) == nullptr) {\n        std::cerr << \"error: can not get CWD\" << std::endl;\n        global_retc = errno;\n        return 0;\n      }\n\n      prefix = buffer;\n    }\n\n    if (*prefix.rbegin() != '/') {\n      prefix += '/';\n    }\n\n    // If the public/private key files exist then we use them to generate\n    // the jwk file, otherwise we generate new keys\n    bool store_keys = false;\n    std::string fn_public = SSTR(prefix << keyid << \"-pkey.pem\").c_str();\n    std::string fn_private = SSTR(prefix << keyid << \"-key.pem\").c_str();\n    std::string jwk_file;\n    struct stat buf;\n\n    if (::stat(fn_public.c_str(), &buf) ||\n        ::stat(fn_private.c_str(), &buf)) {\n      // We generate new keys\n      fn_public = \"\";\n      fn_private = \"\";\n      store_keys = true;\n    }\n\n    using namespace jwk_generator;\n    JwkGenerator<ES256> jwk(keyid, fn_public, fn_private);\n    std::cout << \"JWK:\\n\" << jwk.to_pretty_string()\n              << std::endl << std::endl;\n\n    if (store_keys)  {\n      fn_public = SSTR(prefix << keyid << \"-pkey.pem\").c_str();\n      fn_private = SSTR(prefix << keyid << \"-key.pem\").c_str();\n      jwk_file = SSTR(prefix << keyid << \"-sci.jwk\").c_str();\n\n      for (auto pair : std::list<std::pair<std::string, std::string>> {\n      {fn_public, jwk.public_to_pem()},\n        {fn_private, jwk.private_to_pem()},\n        {jwk_file, jwk.to_pretty_string()}\n      }) {\n        std::ofstream file(pair.first);\n\n        if (!file.is_open()) {\n          std::cerr << \"error: failed to open public key file \"\n                    << pair.first << std::endl;\n          global_retc = EINVAL;\n          return 0;\n        }\n\n        file << pair.second << std::endl;\n        file.close();\n      }\n    }\n\n    if (!fn_public.empty() && !fn_private.empty()) {\n      std::cerr << (store_keys ? \"Wrote\" : \"Used\") << \" public key :  \"\n                << fn_public << std::endl\n                << (store_keys ? \"Wrote\" : \"Used\") << \" private key: \"\n                << fn_private << std::endl;\n\n      if (!jwk_file.empty()) {\n        std::cerr << \"Wrote JWK file   : \" << jwk_file << std::endl;\n      }\n    }\n\n    return 0;\n  }\n\ncom_scitoken_usage:\n  std::ostringstream oss;\n  oss << \"Usage: scitoken create|dump|create-keys\\n\"\n      << \"    command for handling scitokens generated by EOS\\n\"\n      << std::endl\n      << \"  scitoken create --issuer <issuer> --keyid <keyid> [--profile <profile>] \"\n      << \"--claim <claim-1> {... --claim <claim-n>} [--privkey <private-key-file>] \"\n      << \"[--pubkey <public-key-file>] [--expires unix-ts]\\n\"\n      << \"    create a scitoken for a given keyid, issuer, profile containing claims\\n\"\n      << \"    <issuer>           : URL of the issuer\\n\"\n      << \"    <keyid>            : key id to request from the issuer\\n\"\n      << \"    <profile>          : token profile, one of \\\"wlcg\\\" [default], \\\"scitokens1\\\", \"\n      << \"\\\"scitokens2\\\", \\\"atjwt\\\"\\n\"\n      << \"    <claims>           : <key>=<value> e.g. scope=storage.read:/eos/, scope=storage.modify:/eos/ ...\\n\"\n      << \"    <private-key-file> : file with the private key in PEM format - default /eos/xrootd/<keyid>-key.pem\\n\"\n      << \"    <public-key-file>  : file with the public key in PEM format - default /eos/xrootd/<keyid>-pkey.pem\\n\"\n      << std::endl\n      << \"  scitoken dump <token>\\n\"\n      << \"    base64 decode a scitokens without verification\\n\"\n      << std::endl\n      << \"  scitoken create-keys [--keyid <keyid>]\\n\"\n      << \"    create a PEM key pair and a JSON public web key. If <keyid> is specified \\n\"\n      << \"    then the pub/priv key pair is in /eos/xrootd/<keyid>-{key,pkey}.pem.\\n\"\n      << \"    Otherwise they are stored in CWD in default-{key,pkey}.pem. The JSON web \\n\"\n      << \"    key is printed on stdout, and the key locations on stderr.\\n\"\n      << std::endl\n      << \"  Examples:\\n\"\n      << \"    eos scitoken create --issuer eos.cern.ch --keyid eos \"\n      << \"profile wlcg --claim sub=foo --claim scope=storage.read:/eos\\n\"\n      << \"    eos scitoken dump eyJhb ...\\n\"\n      << \"    eos scitoken create-keys --keyid eos > /etc/xrootd/eos.jwk\\n\";\n  std::cerr << oss.str().c_str() << std::endl;\n  global_retc = EINVAL;\n  return 0;\n}\n#endif\n"
  },
  {
    "path": "console/commands/coms/unused/com_silent.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_silent.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n\n/*----------------------------------------------------------------------------*/\n\nint\ncom_silent (char*)\n{\n  silent = (!silent);\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_squash.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_squash.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/Path.hh\"\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n#include \"pwd.h\"\n#include <thread>\n#include <chrono>\n\n/* List a directory */\nint\ncom_squash(char* arg1)\n{\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString cmd = \"\";\n  XrdOucString path = \"\";\n  XrdOucString option = \"\";\n  XrdOucString fulloption = \"\";\n  bool ok = false;\n  const int len = 4096;\n  char username[len];\n  struct passwd* pw = getpwuid(geteuid());\n\n  if (pw == nullptr) {\n    fprintf(stderr, \"error: failed to get effective UID username of calling \"\n            \"process\\n\");\n    goto com_squash_usage;\n  }\n\n  (void) strncpy(username, pw->pw_name, len - 1);\n  username[len - 1] = '\\0';\n\n  do {\n    cmd = subtokenizer.GetToken();\n    path = subtokenizer.GetToken();\n\n    if (!cmd.length()) {\n      goto com_squash_usage;\n    }\n\n    if (cmd == \"--help\") {\n      goto com_squash_usage;\n    }\n\n    if (cmd == \"-h\") {\n      goto com_squash_usage;\n    }\n\n    if (path.length() && (path[0] == '-')) {\n      option = path[1];\n      fulloption = path;\n      path = subtokenizer.GetToken();\n    }\n\n    if (!path.length()) {\n      goto com_squash_usage;\n    }\n\n    if ((cmd != \"trim-release\") && (cmd != \"new-release\")) {\n      XrdOucString garbage = subtokenizer.GetToken();\n\n      if (garbage.length()) {\n        goto com_squash_usage;\n      } else {\n        break;\n      }\n    } else {\n      break;\n    }\n  } while (1);\n\n  path = abspath(path.c_str());\n\n  if (cmd == \"new\") {\n    struct stat buf;\n    eos::common::Path packagepath(path.c_str());\n\n    if (!stat(packagepath.GetPath(), &buf)) {\n      fprintf(stderr, \"error: package path='%s' exists already\\n\",\n              packagepath.GetPath());\n      global_retc = EEXIST;\n      return (0);\n    }\n\n    std::string mkpath = \"/var/tmp/\";\n    mkpath += username;\n    mkpath += \"/eosxd/mksquash/\";\n    mkpath += packagepath.GetContractedPath();\n    mkpath += \"/dummy\";\n    eos::common::Path mountpath(mkpath.c_str());\n\n    if (!mountpath.MakeParentPath(S_IRWXU | S_IROTH | S_IXOTH | S_IRGRP |\n                                  S_IXGRP)) {\n      fprintf(stderr, \"error: failed to create local mount point path='%s'\\n\",\n              mountpath.GetParentPath());\n      global_retc = errno;\n      return (0);\n    }\n\n    if (symlink(mountpath.GetParentPath(), packagepath.GetPath())) {\n      fprintf(stderr, \"error: failed to create symbolic link from '%s' => '%s'\\n\",\n              mountpath.GetParentPath(), packagepath.GetPath());\n      global_retc = errno;\n      return (0);\n    }\n\n    ok = true;\n    fprintf(stderr, \"info: ready to install your software under '%s'\\n\",\n            packagepath.GetPath());\n    fprintf(stderr, \"info: when done run 'eos squash pack %s' to create an \"\n            \"image file and a smart link in EOS!\\n\", packagepath.GetPath());\n  }\n\n  if (cmd == \"install\") {\n    if (fulloption.beginswith(\"--curl=\")) {\n      ok = true;\n      std::string url = fulloption.c_str() + 7;\n\n      if (fulloption.endswith(\".tgz\") ||\n          fulloption.endswith(\".tar.gz\")) {\n        int sub_rc = 0;\n        // squash rm\n        std::string subcommand = \"rm \\\"\";\n        subcommand += path.c_str();\n        subcommand += \"\\\"\";\n        com_squash((char*)subcommand.c_str());\n        sub_rc |= global_retc;\n        // squash new\n        subcommand = \"new \\\"\";\n        subcommand += path.c_str();\n        subcommand += \"\\\"\";\n        com_squash((char*)subcommand.c_str());\n        sub_rc |= global_retc;\n        // download\n        std::string shellcmd = \"cd \\\"\";\n        shellcmd += path.c_str();\n        shellcmd += \"\\\";\";\n        shellcmd += \"curl \";\n        shellcmd += url;\n        shellcmd += \" /dev/stdout | \";\n        shellcmd += \"tar xvzf -\";\n        int rc = system(shellcmd.c_str());\n\n        if (WEXITSTATUS(rc)) {\n          fprintf(stderr, \"error: curl download failed with retc='%d'\\n\",\n                  WEXITSTATUS(rc));\n          global_retc = WEXITSTATUS(rc);\n          return (0);\n        }\n\n        // squash pack\n        subcommand = \"pack \\\"\";\n        subcommand += path.c_str();\n        subcommand += \"\\\"\";\n        com_squash((char*)subcommand.c_str());\n        sub_rc |= global_retc;\n\n        if (sub_rc) {\n          global_retc = sub_rc;\n          return 0;\n        }\n      } else {\n        fprintf(stderr, \"error: suffix of '%s' is not supported\\n\", url.c_str());\n        global_retc = EINVAL;\n        return (0);\n      }\n    } else {\n      goto com_squash_usage;\n    }\n  }\n\n  if (cmd == \"pack\") {\n    eos::common::Path packagepath(path.c_str());\n    std::string squashpack = packagepath.GetParentPath();\n    squashpack += \".\";\n    squashpack += packagepath.GetName();\n    squashpack += \".sqsh\";\n    std::string shellcmd = \"mksquashfs \";\n    char linktarget[4096];\n    memset(linktarget, 0, sizeof(linktarget));\n    ssize_t rl;\n\n    // resolve symlink\n    if ((rl = readlink(packagepath.GetPath(), linktarget,\n                       sizeof(linktarget))) == -1) {\n      fprintf(stderr,\n              \"error: failed to resolve symbolic link of squashfs package '%s'\\n - errno '%d'\",\n              packagepath.GetPath(), errno);\n      global_retc = errno;\n      return (0);\n    } else {\n      linktarget[rl] = 0;\n    }\n\n    struct stat buf;\n\n    if (stat(linktarget, &buf)) {\n      fprintf(stderr, \"error: cannot find local package directory '%s'\\n\",\n              linktarget);\n      global_retc = errno;\n      return (0);\n    }\n\n    shellcmd += linktarget;\n    shellcmd += \" \";\n    shellcmd += squashpack;\n    shellcmd += \"~\";\n    shellcmd += \" -noappend\";\n    shellcmd += \" -force-uid \";\n    shellcmd += std::to_string(geteuid());\n    shellcmd += \" -force-gid \";\n    shellcmd += std::to_string(getegid());\n    shellcmd += \" && mv -f -T \";\n    shellcmd += squashpack;\n    shellcmd += \"~\";\n    shellcmd += \" \";\n    shellcmd += squashpack;\n    fprintf(stderr, \"running %s\\n\", shellcmd.c_str());\n    int rc = system(shellcmd.c_str());\n\n    if (WEXITSTATUS(rc)) {\n      fprintf(stderr, \"error: mksquashfs failed with retc='%d'\\n\", WEXITSTATUS(rc));\n      global_retc = WEXITSTATUS(rc);\n      return (0);\n    } else {\n      if (option != \"f\") {\n        if (unlink(packagepath.GetPath())) {\n          fprintf(stderr,\n                  \"error: failed to unlink locally staged squashfs archive '%s' - errno '%d'\\n\",\n                  squashpack.c_str(), errno);\n          global_retc = errno;\n          return (0);\n        } else {\n          std::string targetline = \"eosxd get eos.hostport \";\n          targetline += packagepath.GetParentPath();\n          std::string hostport = eos::common::StringConversion::StringFromShellCmd(\n                                   targetline.c_str());\n\n          if (!hostport.length()) {\n            fprintf(stderr, \"error: failed to get eos.hostport from mountpoint '%s'\\n\",\n                    targetline.c_str());\n            global_retc = EIO;\n            return (0);\n          }\n\n          std::string target = \"/eos/squashfs/\";\n          target += hostport;\n          target += \"@\";\n          XrdOucString spackagepath = squashpack.c_str();\n\n          while (spackagepath.replace(\"/\", \"---\")) {}\n\n          target += spackagepath.c_str();\n\n          if (symlink(target.c_str(), packagepath.GetPath())) {\n            fprintf(stderr, \"error: failed to create squashfs symlink '%s' => '%s'\\n\",\n                    packagepath.GetPath(),\n                    target.c_str());\n          }\n        }\n      }\n    }\n\n    ok = true;\n  }\n\n  if (cmd == \"relabel\") {\n    ok = true;\n    struct stat buf;\n    eos::common::Path packagepath(path.c_str());\n    std::string squashpack = packagepath.GetParentPath();\n    squashpack += \".\";\n    squashpack += packagepath.GetName();\n    squashpack += \".sqsh\";\n\n    if (stat(squashpack.c_str(), &buf)) {\n      fprintf(stderr,\n              \"error: the squashfs package file is missing for this label!\\n\");\n      global_retc = ENOENT;\n      return (0);\n    }\n\n    if (!lstat(packagepath.GetPath(), &buf)) {\n      if (unlink(packagepath.GetPath())) {\n        fprintf(stderr,\n                \"error: failed to remove existing squashfs archive '%s' - errno '%d'\\n\",\n                packagepath.GetPath(), errno);\n        global_retc = errno;\n        return (0);\n      }\n    }\n\n    std::string targetline = \"eosxd get eos.hostport \";\n    targetline += packagepath.GetParentPath();\n    std::string hostport = eos::common::StringConversion::StringFromShellCmd(\n                             targetline.c_str());\n\n    if (!hostport.length()) {\n      fprintf(stderr, \"error: failed to get eos.hostport from mountpoint '%s'\\n\",\n              targetline.c_str());\n      global_retc = EIO;\n      return (0);\n    }\n\n    std::string target = \"/eos/squashfs/\";\n    target += hostport;\n    target += \"@\";\n    XrdOucString spackagepath = squashpack.c_str();\n\n    while (spackagepath.replace(\"/\", \"---\")) {}\n\n    target += spackagepath.c_str();\n\n    if (symlink(target.c_str(), packagepath.GetPath())) {\n      fprintf(stderr, \"error: failed to create squashfs symlink '%s' => '%s'\\n\",\n              packagepath.GetPath(),\n              target.c_str());\n    }\n  }\n\n  if (cmd == \"unpack\") {\n    ok = true;\n    eos::common::Path packagepath(path.c_str());\n    std::string squashpack = packagepath.GetParentPath();\n    squashpack += \".\";\n    squashpack += packagepath.GetName();\n    squashpack += \".sqsh\";\n    char linktarget[4096];\n    ssize_t rl;\n\n    // resolve symlink\n    if ((rl = readlink(packagepath.GetPath(), linktarget,\n                       sizeof(linktarget))) == -1) {\n      fprintf(stderr,\n              \"error: failed to resolve symbolic link of squashfs package '%s'\\n - errno '%d'\",\n              packagepath.GetPath(), errno);\n      global_retc = errno;\n      return (0);\n    } else {\n      linktarget[rl] = 0;\n    }\n\n    XrdOucString mounttarget = linktarget;\n    std::string mkpath = \"/var/tmp/\";\n    mkpath += username;\n    mkpath += \"/eosxd/mksquash/\";\n\n    if (option != \"f\") {\n      if (mounttarget.beginswith(mkpath.c_str())) {\n        fprintf(stderr, \"error: squash image is already unpacked!\\n\");\n        global_retc = EINVAL;\n        return (0);\n      }\n\n      if (!geteuid()) {\n        // remove any mounts - only possible as root\n        std::string umountcmd = \"umount -f -l \";\n        umountcmd += mounttarget.c_str();\n        (void) !system(umountcmd.c_str());\n\n        if (rmdir(mounttarget.c_str())) {\n          if (errno != ENOENT) {\n            fprintf(stderr,\n                    \"error: failed to unlink local mount directory path='%s' errno=%d\\n\",\n                    mounttarget.c_str(), errno);\n          }\n        }\n      }\n    }\n\n    std::string shellcmd = \"unsquashfs -f -d \";\n    mkpath += packagepath.GetContractedPath();\n    mkpath += \"/dummy\";\n    eos::common::Path mountpath(mkpath.c_str());\n\n    if (!mountpath.MakeParentPath(S_IRWXU | S_IROTH | S_IXOTH | S_IRGRP |\n                                  S_IXGRP)) {\n      fprintf(stderr, \"error: failed to create local mount point path='%s'\\n\",\n              mountpath.GetParentPath());\n      global_retc = errno;\n      return (0);\n    }\n\n    if (unlink(packagepath.GetPath())) {\n      fprintf(stderr,\n              \"error: failed to unlink smart link for squashfs archive '%s' - errno '%d'\\n\",\n              squashpack.c_str(), errno);\n      global_retc = errno;\n      return (0);\n    }\n\n    if (symlink(mountpath.GetParentPath(), packagepath.GetPath())) {\n      fprintf(stderr, \"error: failed to create symbolic link from '%s' => '%s'\\n\",\n              mountpath.GetParentPath(), packagepath.GetPath());\n      global_retc = errno;\n      return (0);\n    }\n\n    shellcmd += mountpath.GetParentPath();\n    shellcmd.erase(shellcmd.length() - 1);\n    shellcmd += \"~\";\n    shellcmd += \" \";\n    shellcmd += squashpack.c_str();\n    shellcmd += \" && rsync -aq --delete \";\n    shellcmd += mountpath.GetParentPath();\n    shellcmd.erase(shellcmd.length() - 1);\n    shellcmd += \"~/\";\n    shellcmd += \" \";\n    shellcmd += mountpath.GetParentPath();\n    shellcmd += \" && rm -rf \";\n    shellcmd += mountpath.GetParentPath();\n    shellcmd.erase(shellcmd.length() - 1);\n    shellcmd += \"~\";\n    fprintf(stdout, \"%s\\n\", shellcmd.c_str());\n    int rc = system(shellcmd.c_str());\n\n    if (WEXITSTATUS(rc)) {\n      fprintf(stderr, \"error: unsquashfs failed with retc='%d'\\n\", WEXITSTATUS(rc));\n      global_retc = WEXITSTATUS(rc);\n      return (0);\n    } else {\n      fprintf(stderr, \"info: squashfs image is available unpacked under '%s'\\n\",\n              packagepath.GetPath());\n      fprintf(stderr,\n              \"info: when done with modifications run 'eos squash pack %s' to create an image file and a smart link in EOS!\\n\",\n              packagepath.GetPath());\n    }\n  }\n\n  if (cmd == \"info\") {\n    ok = true;\n    eos::common::Path packagepath(path.c_str());\n    std::string squashpack = packagepath.GetParentPath();\n    squashpack += \".\";\n    squashpack += packagepath.GetName();\n    squashpack += \".sqsh\";\n    struct stat buf;\n\n    if (!stat(squashpack.c_str(), &buf)) {\n      fprintf(stderr, \"info: '%s' has a squashfs image with size=%lu bytes\\n\",\n              squashpack.c_str(), (unsigned long)buf.st_size);\n    } else {\n      fprintf(stderr, \"info: '%s' has no squashfs image\\n\", squashpack.c_str());\n    }\n\n    char linktarget[4096];\n    ssize_t rl;\n\n    // resolve symlink\n    if ((rl = readlink(packagepath.GetPath(), linktarget,\n                       sizeof(linktarget))) == -1) {\n      fprintf(stderr,\n              \"error: failed to resolve symbolic link of squashfs package '%s'\\n - errno '%d'\",\n              packagepath.GetPath(), errno);\n      global_retc = errno;\n      return (0);\n    } else {\n      linktarget[rl] = 0;\n    }\n\n    XrdOucString mounttarget = linktarget;\n    std::string mkpath = \"/var/tmp/\";\n    mkpath += username;\n    mkpath += \"/eosxd/mksquash/\";\n\n    if (mounttarget.beginswith(mkpath.c_str())) {\n      if (stat(linktarget, &buf)) {\n        fprintf(stderr, \"error: cannot find local package directory '%s'\\n\",\n                linktarget);\n        global_retc = EINVAL;\n        return (0);\n      }\n\n      fprintf(stderr,\n              \"info: squashfs image is currently unpacked/open for local RW mode - use 'eos squash pack %s' to close image\\n\",\n              packagepath.GetPath());\n    } else {\n      fprintf(stderr,\n              \"info: squashfs image is currently packed - use 'eos squash unpack %s' to open image locally\\n\",\n              packagepath.GetPath());\n    }\n  }\n\n  if (cmd == \"rm\") {\n    ok = true;\n    eos::common::Path packagepath(path.c_str());\n    std::string squashpack = packagepath.GetParentPath();\n    squashpack += \".\";\n    squashpack += packagepath.GetName();\n    squashpack += \".sqsh\";\n    struct stat buf;\n\n    if (!stat(squashpack.c_str(), &buf)) {\n      if (unlink(squashpack.c_str())) {\n        fprintf(stderr,\n                \"error: failed to remove existing squashfs archive '%s' - errno '%d'\\n\",\n                squashpack.c_str(), errno);\n        global_retc = errno;\n        return (0);\n      } else {\n        fprintf(stderr, \"info: removed squashfs image '%s'\\n\", squashpack.c_str());\n      }\n    }\n\n    if (!lstat(packagepath.GetPath(), &buf)) {\n      if (unlink(packagepath.GetPath())) {\n        fprintf(stderr,\n                \"error: failed to unlink locally staged squashfs archive '%s' - errno '%d'\\n\",\n                squashpack.c_str(), errno);\n        global_retc = errno;\n        return (0);\n      } else {\n        fprintf(stderr, \"info: removed squashfs smart link '%s\\n\",\n                packagepath.GetPath());\n      }\n    }\n  }\n\n  if (cmd == \"rm-release\") {\n    std::string scmd = \"info-release \";\n    scmd += path.c_str();\n    com_squash((char*) scmd.c_str());\n\n    if (!global_retc) {\n      fprintf(stderr, \"info: wiping squashfs releases under '%s'\\n\", path.c_str());\n      eos::common::Path packagepath(path.c_str());\n      std::string nextrelease = std::string(packagepath.GetPath()) +\n                                std::string(\"/next\");\n      std::string currentrelease = std::string(packagepath.GetPath()) +\n                                   std::string(\"/current\");\n      std::string archive = std::string(packagepath.GetPath()) +\n                            std::string(\"/.archive\");\n      fprintf(stdout, \"info: wiping links current,next ... \\n\");\n      ::unlink(currentrelease.c_str());\n      ::unlink(nextrelease.c_str());\n\n      if (archive.substr(0, 5) == \"/eos/\") {\n        fprintf(stdout, \"info: wiping archive ...\\n\");\n        scmd = \"eos rm -rf \";\n        scmd += archive;\n        std::string out = eos::common::StringConversion::StringFromShellCmd(\n                            scmd.c_str());\n        fprintf(stdout, \"%s\", out.c_str());\n      }\n\n      for (size_t i = 0; i < 50; ++i) {\n        struct stat buf;\n\n        if (!::stat(archive.c_str(), &buf)) {\n          // we might have to wait for callback notification on the fuse mount that the archive directory was wiped\n          std::this_thread::sleep_for(std::chrono::milliseconds(100));\n        } else {\n          break;\n        }\n\n        if (i == 49) {\n          {\n            fprintf(stderr, \"=====================================\\n\");\n            fprintf(stderr, \"warning: mount didn't see cleanup ...\\n\");\n            fprintf(stderr, \"remote:\\n\");\n            fprintf(stderr, \"=====================================\\n\");\n            scmd = \"eos ls -la \";\n            scmd += packagepath.GetPath();\n            std::string out = eos::common::StringConversion::StringFromShellCmd(\n                                scmd.c_str());\n            fprintf(stdout, \"%s\", out.c_str());\n          }\n          {\n            fprintf(stderr, \"=====================================\\n\");\n            fprintf(stderr, \"local:\\n\");\n            fprintf(stderr, \"=====================================\\n\");\n            scmd = \"ls -la \";\n            scmd += packagepath.GetPath();\n            std::string out = eos::common::StringConversion::StringFromShellCmd(\n                                scmd.c_str());\n            fprintf(stdout, \"%s\", out.c_str());\n            fprintf(stderr, \"=====================================\\n\");\n          }\n        }\n      }\n\n      if (::rmdir(packagepath.GetPath())) {\n        global_retc = errno;\n        fprintf(stderr, \"error: failed to clean squashfs release under '%s' error=%d\\n\",\n                path.c_str(), global_retc);\n      }\n\n      return (0);\n    } else {\n      fprintf(stderr, \"info: there is no squashfs release under '%s'\\n\",\n              path.c_str());\n      return (0);\n    }\n  }\n\n  if (cmd == \"new-release\") {\n    eos::common::Path packagepath(path.c_str());\n    XrdOucString version = subtokenizer.GetToken();\n    std::string packagename = packagepath.GetName();\n    std::string now =\n      eos::common::StringConversion::StringFromShellCmd(\"date '+%Y%m%d%H%M%S'\");\n\n    if (version.length()) {\n      // overwrite with the given version number\n      now = version.c_str();\n    }\n\n    now.erase(now.find_last_not_of(\" \\n\\r\\t\") + 1);\n    std::string archivepath = std::string(packagepath.GetPath()) + std::string(\"/\")\n                              + std::string(\".archive/\");\n    std::string archivepackage = archivepath + packagename + std::string(\"-\") + now;\n    std::string nextrelease = std::string(packagepath.GetPath()) +\n                              std::string(\"/next\");\n    eos::common::Path archpath(archivepackage.c_str());\n\n    if (!archpath.MakeParentPath(S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) {\n      fprintf(stderr, \"error: couldn't create '%s'\\n\", archpath.GetParentPath());\n      global_retc = errno;\n      return (0);\n    }\n\n    // in the unlikely case this command was executed within 1 second twice ...\n    ::unlink(archivepackage.c_str());\n    ::unlink(nextrelease.c_str());\n    std::string scmd = \"new \\\"\";\n    scmd += archivepackage;\n    scmd += \"\\\"\";\n    int rc = com_squash((char*)scmd.c_str());\n\n    if (!rc) {\n      rc = ::symlink(archivepackage.c_str(), nextrelease.c_str());\n\n      if (rc) {\n        fprintf(stderr, \"error: failed to create symbolic link for next release '%s'\\n\",\n                nextrelease.c_str());\n        global_retc = errno;\n        return (0);\n      }\n    } else {\n      fprintf(stderr, \"error: failed to create squash package for a new release\\n\");\n      return (0);\n    }\n\n    fprintf(stderr, \"info: install the new release under '%s'\\n\",\n            nextrelease.c_str());\n    return (0);\n  }\n\n  if (cmd == \"pack-release\") {\n    char lname[4096];\n    memset(lname, 0, sizeof(lname));\n    eos::common::Path packagepath(path.c_str());\n    std::string nextrelease = std::string(packagepath.GetPath()) +\n                              std::string(\"/next\");\n    std::string currentrelease = std::string(packagepath.GetPath()) +\n                                 std::string(\"/current\");\n    std::string hiddencurrentrelease = std::string(packagepath.GetPath()) +\n                                       std::string(\"/.current\");\n\n    if (::readlink(nextrelease.c_str(), lname, sizeof(lname)) < 0) {\n      fprintf(stderr, \"error: failed to find an open release package under '%s'\\n\",\n              nextrelease.c_str());\n      global_retc = errno;\n      return (0);\n    } else {\n      std::string scmd = \"pack \";\n      scmd += \"\\\"\";\n      scmd += lname;\n      scmd += \"\\\"\";\n      int rc = com_squash((char*)scmd.c_str());\n\n      if (!rc) {\n        rc = ::unlink(nextrelease.c_str());\n\n        if (rc) {\n          fprintf(stderr, \"error: failed to unlink open release package under '%s'\\n\",\n                  nextrelease.c_str());\n          global_retc = errno;\n          return (0);\n        }\n\n        rc = ::symlink(lname, hiddencurrentrelease.c_str());\n\n        if (rc) {\n          fprintf(stderr, \"error: failed to symlink current release package under '%s'\\n\",\n                  hiddencurrentrelease.c_str());\n          global_retc = errno;\n          return (0);\n        } else {\n          rc = ::rename(hiddencurrentrelease.c_str(), currentrelease.c_str());\n\n          if (rc) {\n            fprintf(stderr, \"error: failed to move '%s' to '%s'\\n\",\n                    hiddencurrentrelease.c_str(), currentrelease.c_str());\n            global_retc = errno;\n            return (0);\n          } else {\n            fprintf(stdout, \"info: new release available under '%s'\\n\",\n                    currentrelease.c_str());\n            return (0);\n          }\n        }\n      } else {\n        fprintf(stderr, \"error: failed to pack squash package for a new release\\n\");\n        return (0);\n      }\n    }\n  }\n\n  if (cmd == \"info-release\") {\n    std::string scmd = \"trim-release \\\"\";\n    scmd += path.c_str();\n    scmd += \"\\\" \";\n    scmd += \"999999 999999\";\n    com_squash((char*)scmd.c_str());\n    return (0);\n  }\n\n  if (cmd == \"trim-release\") {\n    eos::common::Path packagepath(path.c_str());\n    XrdOucString keepdays = subtokenizer.GetToken();\n    XrdOucString keepversions  = subtokenizer.GetToken();\n    std::string current = packagepath.GetPath();\n    std::string archive = packagepath.GetPath();\n    current += \"/current\";\n    archive += \"/.archive\";\n    struct stat buf;\n\n    if (::lstat(current.c_str(), &buf)) {\n      fprintf(stderr, \"error: I cannot find any current release under '%s'\\n\",\n              current.c_str());\n      global_retc = EINVAL;\n      return (0);\n    }\n\n    if (::lstat(archive.c_str(), &buf)) {\n      fprintf(stderr, \"error: I cannot find any archive release under '%s'\\n\",\n              archive.c_str());\n      global_retc = EINVAL;\n      return (0);\n    }\n\n    if (!keepdays.length()) {\n      fprintf(stderr,\n              \"error: you have to specify the number of days you want to keep releases : squash trim-release <path> <n-days> [<max-versions]\\n\");\n      global_retc = EINVAL;\n      return (0);\n    }\n\n    size_t n_keepdays = strtol(keepdays.c_str(), 0, 10);\n\n    if (!n_keepdays) {\n      fprintf(stderr,\n              \"error: you have to specify the number of days you want to keep releases : squash trim-release <path> <n-days>\\n\");\n      global_retc = EINVAL;\n      return (0);\n    }\n\n    size_t n_keepversions = keepversions.length() ? strtol(keepversions.c_str(), 0,\n                            10) : 0;\n\n    if (!n_keepversions) {\n      fprintf(stderr, \"info: no !=0 version limit specified ...\\n\");\n      keepversions = \"1000000\";\n    } else {\n      // we have to pass keepversions + 1 to the find commands\n      keepversions = std::to_string(n_keepversions + 1).c_str();\n    }\n\n    std::string find1, find2, find3, find4, find5;\n    find1 = find2 = find3 = find4 = find5 = \"find \";\n    find1 += packagepath.GetPath();\n    find2 += packagepath.GetPath();\n    find3 += packagepath.GetPath();\n    find4 += packagepath.GetPath();\n    find5 += packagepath.GetPath();\n    find1 += \" -type f -mtime +\";\n    find2 += \" -type l -mtime +\";\n    find1 += keepdays.c_str();\n    find2 += keepdays.c_str();\n    find1 += \" -delete\";\n    find2 += \" -delete\";\n    find3 += \"/.archive/ -type f -printf '%Ts\\t%h/%f\\n'     | sort -rn | tail -n +\";\n    find4 += \"/.archive/ -type l -printf '%Ts\\t%h/%f\\n'     | sort -rn | tail -n +\";\n    find3 += keepversions.c_str();\n    find4 += keepversions.c_str();\n    find3 += \" | cut -f2- | xargs -r rm\";\n    find4 += \" | cut -f2- | xargs -r rm\";\n    find5 += \" -type l\";\n    eos::common::StringConversion::StringFromShellCmd(find1.c_str());\n    eos::common::StringConversion::StringFromShellCmd(find2.c_str());\n    eos::common::StringConversion::StringFromShellCmd(find3.c_str());\n    eos::common::StringConversion::StringFromShellCmd(find4.c_str());\n    std::string out = eos::common::StringConversion::StringFromShellCmd(\n                        find5.c_str());\n    fprintf(stdout,\n            \"---------------------------------------------------------------------------\\n\");\n    fprintf(stdout, \"- releases of '%s' \\n\", packagepath.GetPath());\n    fprintf(stdout,\n            \"---------------------------------------------------------------------------\\n\");\n    fprintf(stdout, \"%s\", out.c_str());\n    fprintf(stdout,\n            \"---------------------------------------------------------------------------\\n\");\n    return (0);\n  }\n\n  if (!ok) {\n    goto com_squash_usage;\n  }\n\n  return (0);\ncom_squash_usage:\n  fprintf(stdout,\n          \"usage: squash new <path>                                                  : create a new squashfs under <path>\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"       squash pack [-f] <path>                                            : pack a squashfs image\\n\");\n  fprintf(stdout,\n          \"                                                                            -f will recreate the package but keeps the symbolic link locally\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"       squash unpack [-f] <path>                                          : unpack a squashfs image for modification\\n\");\n  fprintf(stdout,\n          \"                                                                            -f will atomically update the local package\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"       squash info <path>                                                 : squashfs information about <path>\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"       squash rm <path>                                                   : delete a squashfs attached image and its smart link\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"       squash relabel <path>                                              : relable a squashfs image link e.g. after an image move in the namespace\\n\");\n  fprintf(stdout, \"\\n\");\n  /*  fprintf(stdout,\n    \"       squash roll <path>                                                 : will create a squash package from the EOS directory pointed by <path\\n\");\n  fprintf(stdout,\"\\n\");\n  fprintf(stdout,\n    \"       squash unroll <path>                                               : will store the squash package contents unpacked into the EOS package directory\\n\");\n  fprintf(stdout,\"\\n\");\n  */\n  fprintf(stdout,\n          \"       squash install --curl=https://<package>.tgz|.tar.gz <path>         : create a squashfs package from a web archive under <path>\\n\");\n  fprintf(stdout,\n          \"       squash new-release <path> [<version>]                                : create a new squashfs release under <path> - by default versions are made from timestamp, but this can be overwritten using the version field\\n\");\n  fprintf(stdout,\n          \"       squash pack-release <path>                                         : pack a squashfs release under <path>\\n\");\n  fprintf(stdout,\n          \"       squash info-release <path>                                         : show all release revisions under <path> <path>\\n\");\n  fprintf(stdout,\n          \"       squash trim-release <path> <keep-days> [<keep-versions>]           : trim  releases older than <keep-days> and keep maximum <keep-versions> of release\\n\");\n  fprintf(stdout,\n          \"       squash rm-release <path>                                           : delete all squahfs releases udner <path>\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_stat.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_stat.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringConversion.hh\"\n#include <XrdPosix/XrdPosixXrootd.hh>\n/*----------------------------------------------------------------------------*/\n\n/* Stat a directory or a file */\nint\ncom_stat(char* arg1)\n{\n  // split subcommands\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString param = \"\";\n  XrdOucString option = \"\";\n  XrdOucString path = \"\";\n  XrdOucString sizestring;\n  struct stat buf;\n  XrdOucString url = serveruri.c_str();\n\n  if (wants_help(arg1)) {\n    goto com_stat_usage;\n  }\n\n  do {\n    param = subtokenizer.GetToken();\n\n    if (!param.length()) {\n      break;\n    }\n\n    if (param == \"--help\") {\n      goto com_stat_usage;\n    }\n\n    if (param == \"-h\") {\n      goto com_stat_usage;\n    }\n\n    if (param.beginswith(\"-\")) {\n      while (param.replace(\"-\", \"\")) {\n      }\n\n      option += param;\n\n      if ((option.find(\"&\")) != STR_NPOS) {\n        goto com_stat_usage;\n      }\n    } else {\n      path = param;\n      break;\n    }\n  } while (1);\n\n  if (!path.length()) {\n    path = gPwd;\n  }\n\n  if ((option.length()) && ((option != \"f\") && (option != \"d\"))) {\n    fprintf(stderr, \"error: unknown option \\\"%s\\\"\\n\", option.c_str());\n    goto com_stat_usage;\n  }\n\n  path = abspath(path.c_str());\n  url += \"/\";\n  url += path;\n\n  if (!XrdPosixXrootd::Stat(url.c_str(), &buf)) {\n    if ((option.find(\"f\") != STR_NPOS)) {\n      if (S_ISREG(buf.st_mode)) {\n        global_retc = 0;\n        return (0);\n      } else {\n        global_retc = 1;\n        return (0);\n      }\n    }\n\n    if ((option.find(\"d\") != STR_NPOS)) {\n      if (S_ISDIR(buf.st_mode)) {\n        global_retc = 0;\n        return (0);\n      } else {\n        global_retc = 1;\n        return (0);\n      }\n    }\n\n    fprintf(stdout, \"  File: '%s'\", path.c_str());\n\n    if (S_ISDIR(buf.st_mode)) {\n      fprintf(stdout, \" directory\\n\");\n    } else if (S_ISREG(buf.st_mode)) {\n      fprintf(stdout, \"  Size: %llu            %s\", (unsigned long long) buf.st_size,\n              eos::common::StringConversion::GetReadableSizeString(sizestring,\n                  (unsigned long long) buf.st_size, \"B\"));\n      fprintf(stdout, \" regular file\\n\");\n    } else {\n      fprintf(stdout, \" symbolic link\\n\");\n    }\n\n    global_retc = 0;\n  } else {\n    fprintf(stderr, \"error: failed to stat %s\\n\", path.c_str());\n    global_retc = EFAULT;\n    return (0);\n  }\n\n  return (0);\ncom_stat_usage:\n  fprintf(stdout,\n          \"usage: stat [-f|-d]    <path>                                                  :  stat <path>\\n\");\n  fprintf(stdout, \"                    -f : checks if <path> is a file\\n\");\n  fprintf(stdout, \"                    -d : checks if <path> is a directory\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_status.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_status.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n/*----------------------------------------------------------------------------*/\n\nint\ncom_status(char*)\n{\n  (void) !system(\"eos-status\");\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_test.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_test.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/mq/XrdMqTiming.hh\"\n/*----------------------------------------------------------------------------*/\n\nextern int com_mkdir(char*);\nextern int com_rmdir(char*);\nextern int com_ls(char*);\n\n/* Test Interface */\nint\ncom_test(char* arg1)\n{\n  // split subcommands\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n\n  do {\n    XrdOucString tag = subtokenizer.GetToken();\n\n    if (!tag.length()) {\n      break;\n    }\n\n    XrdOucString sn = subtokenizer.GetToken();\n\n    if (!sn.length()) {\n      goto com_test_usage;\n    }\n\n    int n = atoi(sn.c_str());\n    fprintf(stdout, \"info: doing directory test with loop <n>=%d\", n);\n\n    if (tag == \"mkdir\") {\n      XrdMqTiming timing(\"mkdir\");\n      TIMING(\"start\", &timing);\n\n      for (int i = 0; i < 10; i++) {\n        char dname[1024];\n        sprintf(dname, \"/test/%02d\", i);\n        XrdOucString cmd = \"\";\n        cmd += dname;\n        //      fprintf(stdout,\"===> %s\\n\", cmd.c_str());\n        com_mkdir((char*) cmd.c_str());\n\n        for (int j = 0; j < n / 10; j++) {\n          sprintf(dname, \"/test/%02d/%05d\", i, j);\n          XrdOucString cmd = \"\";\n          cmd += dname;\n          //      fprintf(stdout,\"===> %s\\n\", cmd.c_str());\n          com_mkdir((char*) cmd.c_str());\n        }\n      }\n\n      TIMING(\"stop\", &timing);\n      timing.Print();\n    }\n\n    if (tag == \"rmdir\") {\n      XrdMqTiming timing(\"rmdir\");\n      TIMING(\"start\", &timing);\n\n      for (int i = 0; i < 10; i++) {\n        char dname[1024];\n        sprintf(dname, \"/test/%02d\", i);\n        XrdOucString cmd = \"\";\n        cmd += dname;\n        //fprintf(stdout,\"===> %s\\n\", cmd.c_str());\n\n        for (int j = 0; j < n / 10; j++) {\n          sprintf(dname, \"/test/%02d/%05d\", i, j);\n          XrdOucString cmd = \"\";\n          cmd += dname;\n          //fprintf(stdout,\"===> %s\\n\", cmd.c_str());\n          com_rmdir((char*) cmd.c_str());\n        }\n\n        com_rmdir((char*) cmd.c_str());\n      }\n\n      TIMING(\"stop\", &timing);\n      timing.Print();\n    }\n\n    if (tag == \"ls\") {\n      XrdMqTiming timing(\"ls\");\n      TIMING(\"start\", &timing);\n\n      for (int i = 0; i < 10; i++) {\n        char dname[1024];\n        sprintf(dname, \"/test/%02d\", i);\n        XrdOucString cmd = \"\";\n        cmd += dname;\n        com_ls((char*) cmd.c_str());\n      }\n\n      TIMING(\"stop\", &timing);\n      timing.Print();\n    }\n\n    if (tag == \"lsla\") {\n      XrdMqTiming timing(\"lsla\");\n      TIMING(\"start\", &timing);\n\n      for (int i = 0; i < 10; i++) {\n        char dname[1024];\n        sprintf(dname, \"/test/%02d\", i);\n        XrdOucString cmd = \"-la \";\n        cmd += dname;\n        com_ls((char*) cmd.c_str());\n      }\n\n      TIMING(\"stop\", &timing);\n      timing.Print();\n    }\n  } while (1);\n\n  return (0);\ncom_test_usage:\n  fprintf(stdout,\n          \"usage: test [mkdir|rmdir|ls|lsla <N> ]                                             :  run performance test\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_timing.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_timing.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n\n/*----------------------------------------------------------------------------*/\n\nint\ncom_timing (char*)\n{\n  timing = (!timing);\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_touch.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_touch.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\nextern int com_file (char*);\n/*----------------------------------------------------------------------------*/\n\nint\ncom_touch (char *arg1)\n{\n  XrdOucString cmd = \"touch \";\n  cmd += arg1;\n  return com_file((char*)cmd.c_str());\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_tracker.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_tracker.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\nextern int com_proto_space(char*);\n/*----------------------------------------------------------------------------*/\n\nint\ncom_tracker(char* arg1)\n{\n  XrdOucString cmd = \"tracker\";\n\n  if (arg1 && strlen(arg1)) {\n    cmd += \";\";\n    cmd += arg1;\n  }\n\n  return com_proto_space((char*)cmd.c_str());\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_version.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_version.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n/*----------------------------------------------------------------------------*/\n\n/* Get the server version*/\nint\ncom_version(char* arg)\n{\n  XrdOucString in = \"mgm.cmd=version\";\n  eos::common::StringTokenizer subtokenizer(arg);\n  XrdOucString option = \"\";\n  XrdOucString options = \"\";\n  subtokenizer.GetLine();\n\n  if (wants_help(arg)) {\n    goto com_version_usage;\n  }\n\n  do {\n    option = subtokenizer.GetToken();\n\n    if (!option.length()) {\n      break;\n    }\n\n    if (option == \"-f\") {\n      options += \"f\";\n    } else if (option == \"-m\") {\n      options += \"m\";\n    } else {\n      goto com_version_usage;\n    }\n  } while (1);\n\n  if (options.length()) {\n    in += \"&mgm.option=\";\n    in += options;\n  }\n\n  global_retc = output_result(client_command(in));\n  if ( (option.find(\"m\") == STR_NPOS) && !json ) {\n    fprintf(stdout, \"EOS_CLIENT_VERSION=%s EOS_CLIENT_RELEASE=%s\\n\", VERSION,\n          RELEASE);\n  }\n  return (0);\ncom_version_usage:\n  fprintf(stdout,\n          \"usage: version [-f] [-m]                                             :  print EOS version number\\n\");\n  fprintf(stdout,\n          \"                -f                                                   -  print the list of supported features\\n\");\n  fprintf(stdout,\n          \"                -m                                                   -  print in monitoring format\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_vid.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_vid.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/Utils.hh\"\n\n/* VID System listing, configuration, manipulation */\nint\ncom_vid(char* arg1)\n{\n  // split subcommands\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString subcommand = subtokenizer.GetTokenUnquoted();\n\n  if (wants_help(arg1)) {\n    goto com_vid_usage;\n  }\n\n  if (subcommand == \"ls\") {\n    XrdOucString in = \"mgm.cmd=vid&mgm.subcmd=ls\";\n    XrdOucString soption = \"\";\n    XrdOucString option = \"\";\n\n    do {\n      option = subtokenizer.GetTokenUnquoted();\n\n      if (option.beginswith(\"-\")) {\n        option.erase(0, 1);\n        soption += option;\n\n        if (option.beginswith(\"h\") || option.beginswith(\"-h\")) {\n          goto com_vid_usage;\n        }\n      }\n    } while (option.length());\n\n    if (soption.length()) {\n      in += \"&mgm.vid.option=\";\n      in += soption;\n    }\n\n    global_retc = output_result(client_command(in, true));\n    return (0);\n  }\n\n  if (subcommand == \"set\") {\n    XrdOucString in = \"mgm.cmd=vid&mgm.subcmd=set\";\n    XrdOucString key = subtokenizer.GetTokenUnquoted();\n\n    if (!key.length()) {\n      goto com_vid_usage;\n    }\n\n    if (key.beginswith(\"-h\") || key.beginswith(\"=-h\")) {\n      goto com_vid_usage;\n    }\n\n    XrdOucString vidkey = \"\";\n\n    if (key == \"geotag\") {\n      XrdOucString match = subtokenizer.GetTokenUnquoted();\n\n      if (!match.length()) {\n        goto com_vid_usage;\n      }\n\n      if (match.beginswith(\"-h\") || match.beginswith(\"=-h\")) {\n        goto com_vid_usage;\n      }\n\n      XrdOucString target = subtokenizer.GetTokenUnquoted();\n\n      if (!target.length()) {\n        goto com_vid_usage;\n      }\n\n      // Check if geotag is valid\n      std::string geotag = eos::common::SanitizeGeoTag(target.c_str());\n\n      if (geotag != target.c_str()) {\n        fprintf(stderr, \"%s\\n\", geotag.c_str());\n        return 0;\n      }\n\n      vidkey = \"geotag:\";\n      vidkey += match;\n      in += \"&mgm.vid.cmd=geotag\";\n      in += \"&mgm.vid.key=\";\n      in += vidkey.c_str();\n      in += \"&mgm.vid.geotag=\";\n      in += target.c_str();\n      global_retc = output_result(client_command(in, true));\n      return (0);\n    }\n\n    if (key == \"membership\") {\n      XrdOucString uid = subtokenizer.GetTokenUnquoted();\n\n      if (!uid.length()) {\n        goto com_vid_usage;\n      }\n\n      if (uid.beginswith(\"-h\") || uid.beginswith(\"=-h\")) {\n        goto com_vid_usage;\n      }\n\n      vidkey += uid;\n      XrdOucString type = subtokenizer.GetTokenUnquoted();\n\n      if (!type.length()) {\n        goto com_vid_usage;\n      }\n\n      in += \"&mgm.vid.cmd=membership\";\n      in += \"&mgm.vid.source.uid=\";\n      in += uid;\n      XrdOucString list = \"\";\n\n      if ((type == \"-uids\")) {\n        vidkey += \":uids\";\n        list = subtokenizer.GetTokenUnquoted();\n        in += \"&mgm.vid.key=\";\n        in += vidkey;\n        in += \"&mgm.vid.target.uid=\";\n        in += list;\n      }\n\n      if ((type == \"-gids\")) {\n        vidkey += \":gids\";\n        list = subtokenizer.GetTokenUnquoted();\n        in += \"&mgm.vid.key=\";\n        in += vidkey;\n        in += \"&mgm.vid.target.gid=\";\n        in += list;\n      }\n\n      if ((type == \"+sudo\")) {\n        vidkey += \":root\";\n        list = \" \"; // fake\n        in += \"&mgm.vid.key=\";\n        in += vidkey;\n        in += \"&mgm.vid.target.sudo=true\";\n      }\n\n      if ((type == \"-sudo\")) {\n        vidkey += \":root\";\n        list = \" \"; // fake\n        in += \"&mgm.vid.key=\";\n        in += vidkey;\n        in += \"&mgm.vid.target.sudo=false\";\n      }\n\n      if (!list.length()) {\n        goto com_vid_usage;\n      }\n\n      global_retc = output_result(client_command(in, true));\n      return (0);\n    }\n\n    if (key == \"map\") {\n      in += \"&mgm.vid.cmd=map\";\n      XrdOucString type = subtokenizer.GetTokenUnquoted();\n\n      if (!type.length()) {\n        goto com_vid_usage;\n      }\n\n      if ((type.beginswith(\"-h\") || type.beginswith(\"=-h\")) && (type != \"-https\")) {\n        goto com_vid_usage;\n      }\n\n      bool hastype = false;\n\n      if ((type == \"-krb5\")) {\n        in += \"&mgm.vid.auth=krb5\";\n        hastype = true;\n      }\n\n      if ((type == \"-gsi\")) {\n        in += \"&mgm.vid.auth=gsi\";\n        hastype = true;\n      }\n\n      if ((type == \"-https\")) {\n        in += \"&mgm.vid.auth=https\";\n        hastype = true;\n      }\n\n      if ((type == \"-sss\")) {\n        in += \"&mgm.vid.auth=sss\";\n        hastype = true;\n      }\n\n      if ((type == \"-unix\")) {\n        in += \"&mgm.vid.auth=unix\";\n        hastype = true;\n      }\n\n      if ((type == \"-tident\")) {\n        in += \"&mgm.vid.auth=tident\";\n        hastype = true;\n      }\n\n      if ((type == \"-voms\")) {\n        in += \"&mgm.vid.auth=voms\";\n        hastype = true;\n      }\n\n      if ((type == \"-grpc\")) {\n        in += \"&mgm.vid.auth=grpc\";\n        hastype = true;\n      }\n\n      if ((type == \"-oauth2\")) {\n        in += \"&mgm.vid.auth=oauth2\";\n        hastype = true;\n      }\n\n      if (!hastype) {\n        goto com_vid_usage;\n      }\n\n      XrdOucString pattern = subtokenizer.GetTokenUnquoted();\n\n      // deal with patterns containing spaces but inside \"\"\n      if (pattern.beginswith(\"\\\"\")) {\n        if (!pattern.endswith(\"\\\"\"))\n          do {\n            XrdOucString morepattern = subtokenizer.GetTokenUnquoted();\n\n            if (morepattern.endswith(\"\\\"\")) {\n              pattern += \" \";\n              pattern += morepattern;\n              break;\n            }\n\n            if (!morepattern.length()) {\n              goto com_vid_usage;\n            }\n\n            pattern += \" \";\n            pattern += morepattern;\n          } while (1);\n      }\n\n      if (!pattern.length()) {\n        goto com_vid_usage;\n      }\n\n      in += \"&mgm.vid.pattern=\";\n      in += pattern;\n      XrdOucString vid = subtokenizer.GetTokenUnquoted();\n\n      if (!vid.length()) {\n        goto com_vid_usage;\n      }\n\n      if (vid.beginswith(\"vuid:\")) {\n        vid.replace(\"vuid:\", \"\");\n        in += \"&mgm.vid.uid=\";\n        in += vid;\n        XrdOucString vid = subtokenizer.GetTokenUnquoted();\n\n        if (vid.length()) {\n          if (vid.beginswith(\"vgid:\")) {\n            vid.replace(\"vgid:\", \"\");\n            in += \"&mgm.vid.gid=\";\n            in += vid;\n          } else {\n            goto com_vid_usage;\n          }\n        }\n      } else {\n        if (vid.beginswith(\"vgid:\")) {\n          vid.replace(\"vgid:\", \"\");\n          in += \"&mgm.vid.gid=\";\n          in += vid;\n        } else {\n          goto com_vid_usage;\n        }\n      }\n\n      in += \"&mgm.vid.key=\";\n      in += \"<key>\";\n      global_retc = output_result(client_command(in, true));\n      return (0);\n    }\n  }\n\n  if ((subcommand == \"enable\") || (subcommand == \"disable\")) {\n    XrdOucString in = \"mgm.cmd=vid&mgm.subcmd=set&mgm.vid.cmd=map\";\n    XrdOucString disableu =\n      \"mgm.cmd=vid&mgm.subcmd=rm&mgm.vid.cmd=unmap&mgm.vid.key=\";\n    XrdOucString disableg =\n      \"mgm.cmd=vid&mgm.subcmd=rm&mgm.vid.cmd=unmap&mgm.vid.key=\";\n    XrdOucString type = subtokenizer.GetTokenUnquoted();\n\n    if (!type.length()) {\n      goto com_vid_usage;\n    }\n\n    if (type.beginswith(\"-h\") || type.beginswith(\"--h\")) {\n      goto com_vid_usage;\n    }\n\n    bool hastype = false;\n\n    if ((type == \"krb5\")) {\n      in += \"&mgm.vid.auth=krb5\";\n      disableu += \"krb5:\\\"<pwd>\\\":uid\";\n      disableg += \"krb5:\\\"<pwd>\\\":gid\";\n      hastype = true;\n    }\n\n    if ((type == \"sss\")) {\n      in += \"&mgm.vid.auth=sss\";\n      disableu += \"sss:\\\"<pwd>\\\":uid\";\n      disableg += \"sss:\\\"<pwd>\\\":gid\";\n      hastype = true;\n    }\n\n    if ((type == \"gsi\")) {\n      in += \"&mgm.vid.auth=gsi\";\n      disableu += \"gsi:\\\"<pwd>\\\":uid\";\n      disableg += \"gsi:\\\"<pwd>\\\":gid\";\n      hastype = true;\n    }\n\n    if ((type == \"https\")) {\n      in += \"&mgm.vid.auth=https\";\n      disableu += \"https:\\\"<pwd>\\\":uid\";\n      disableg += \"https:\\\"<pwd>\\\":gid\";\n      hastype = true;\n    }\n\n    if ((type == \"unix\")) {\n      in += \"&mgm.vid.auth=unix\";\n      disableu += \"unix:\\\"<pwd>\\\":uid\";\n      disableg += \"unix:\\\"<pwd>\\\":gid\";\n      hastype = true;\n    }\n\n    if ((type == \"grpc\")) {\n      in += \"&mgm.vid.auth=grpc\";\n      disableu += \"grpc:\\\"<pwd>\\\":uid\";\n      disableg += \"grpc:\\\"<pwd>\\\":gid\";\n      hastype = true;\n    }\n\n    if ((type == \"oauth2\")) {\n      in += \"&mgm.vid.auth=oauth2\";\n      disableu += \"oauth2:\\\"<pwd>\\\":uid\";\n      disableg += \"oauth2:\\\"<pwd>\\\":gid\";\n      hastype = true;\n    }\n\n    if ((type == \"tident\")) {\n      in += \"&mgm.vid.auth=tident\";\n      disableu += \"tident:\\\"<pwd>\\\":uid\";\n      disableg += \"tident:\\\"<pwd>\\\":gid\";\n      hastype = true;\n    }\n\n    if ((type == \"ztn\")) {\n      in += \"&mgm.vid.auth=ztn\";\n      disableu += \"ztn:\\\"<pwd>\\\":uid\";\n      disableg += \"ztn:\\\"<pwd>\\\":gid\";\n      hastype = true;\n    }\n\n    if (!hastype) {\n      goto com_vid_usage;\n    }\n\n    in += \"&mgm.vid.pattern=<pwd>\";\n\n    if (type != \"unix\") {\n      in += \"&mgm.vid.uid=0\";\n      in += \"&mgm.vid.gid=0\";\n    } else {\n      in += \"&mgm.vid.uid=99\";\n      in += \"&mgm.vid.gid=99\";\n    }\n\n    in += \"&mgm.vid.key=\";\n    in += \"<key>\";\n\n    if ((subcommand == \"enable\")) {\n      global_retc = output_result(client_command(in, true));\n    }\n\n    if ((subcommand == \"disable\")) {\n      global_retc = output_result(client_command(disableu, true));\n      global_retc |= output_result(client_command(disableg, true));\n    }\n\n    return (0);\n  }\n\n  if (subcommand == \"publicaccesslevel\") {\n    XrdOucString in = \"mgm.cmd=vid&mgm.subcmd=set\";\n    XrdOucString vidkey = \"\";\n    XrdOucString level = subtokenizer.GetTokenUnquoted();\n\n    if (!level.length()) {\n      goto com_vid_usage;\n    }\n\n    if (level.beginswith(\"-h\") || level.beginswith(\"=-h\")) {\n      goto com_vid_usage;\n    }\n\n    vidkey = \"publicaccesslevel\";\n    in += \"&mgm.vid.cmd=publicaccesslevel\";\n    in += \"&mgm.vid.key=\";\n    in += vidkey.c_str();\n    in += \"&mgm.vid.level=\";\n    in += level.c_str();\n    global_retc = output_result(client_command(in, true));\n    return (0);\n  }\n\n  if (subcommand == \"tokensudo\") {\n    XrdOucString in = \"mgm.cmd=vid&mgm.subcmd=set\";\n    XrdOucString vidkey = \"\";\n    XrdOucString level = subtokenizer.GetTokenUnquoted();\n\n    if (!level.length()) {\n      goto com_vid_usage;\n    }\n\n    if (level.beginswith(\"-h\") || level.beginswith(\"=-h\")) {\n      goto com_vid_usage;\n    }\n\n    vidkey = \"tokensudo\";\n    in += \"&mgm.vid.cmd=tokensudo\";\n    in += \"&mgm.vid.key=\";\n    in += vidkey.c_str();\n    in += \"&mgm.vid.tokensudo=\";\n    in += level.c_str();\n    global_retc = output_result(client_command(in, true));\n    return (0);\n  }\n\n  if ((subcommand == \"add\") || (subcommand == \"remove\")) {\n    XrdOucString gw = subtokenizer.GetTokenUnquoted();\n\n    if (gw != \"gateway\") {\n      goto com_vid_usage;\n    }\n\n    XrdOucString host = subtokenizer.GetTokenUnquoted();\n\n    if (!host.length()) {\n      goto com_vid_usage;\n    }\n\n    XrdOucString protocol = subtokenizer.GetTokenUnquoted();\n\n    if (protocol.length() &&\n        ((protocol != \"sss\") && (protocol != \"gsi\") &&\n         (protocol != \"krb5\") && (protocol != \"unix\") && (protocol != \"https\") &&\n         (protocol != \"grpc\") && (protocol != \"outh2\"))) {\n      goto com_vid_usage;\n    }\n\n    if (!protocol.length()) {\n      protocol = \"*\";\n    }\n\n    XrdOucString in = \"mgm.cmd=vid&mgm.subcmd=set&mgm.vid.cmd=map\";\n    XrdOucString disableu =\n      \"mgm.cmd=vid&mgm.subcmd=rm&mgm.vid.cmd=unmap&mgm.vid.key=\";\n    XrdOucString disableg =\n      \"mgm.cmd=vid&mgm.subcmd=rm&mgm.vid.cmd=unmap&mgm.vid.key=\";\n    in += \"&mgm.vid.auth=tident\";\n    in += \"&mgm.vid.pattern=\\\"\";\n    in += protocol;\n    in += \"@\";\n    in += host;\n    in += \"\\\"\";\n    in += \"&mgm.vid.uid=0\";\n    in += \"&mgm.vid.gid=0\";\n    disableu += \"tident:\\\"\";\n    disableu += protocol;\n    disableu += \"@\";\n    disableu += host;\n    disableu += \"\\\":uid\";\n    disableg += \"tident:\\\"\";\n    disableg += protocol;\n    disableg += \"@\";\n    disableg += host;\n    disableg += \"\\\":gid\";\n    in += \"&mgm.vid.key=\";\n    in += \"<key>\";\n\n    if ((subcommand == \"add\")) {\n      global_retc = output_result(client_command(in, true));\n    }\n\n    if ((subcommand == \"remove\")) {\n      global_retc = output_result(client_command(disableu, true));\n      global_retc |= output_result(client_command(disableg, true));\n    }\n\n    return (0);\n  }\n\n  if (subcommand == \"rm\") {\n    XrdOucString in = \"mgm.cmd=vid&mgm.subcmd=rm\";\n    XrdOucString key = subtokenizer.GetTokenUnquoted();\n\n    if (key == \"membership\") {\n      key = subtokenizer.GetTokenUnquoted();\n      key.insert(\"vid:\", 0);\n      XrdOucString key1 = key;\n      XrdOucString key2 = key;\n      XrdOucString in1 = in;\n      XrdOucString in2 = in;\n      key1 += \":uids\";\n      key2 += \":gids\";\n      in1 += \"&mgm.vid.key=\";\n      in1 += key1;\n      in2 += \"&mgm.vid.key=\";\n      in2 += key2;\n      global_retc = output_result(client_command(in1, true));\n      global_retc |= output_result(client_command(in2, true));\n      return (0);\n    }\n\n    if ((!key.length())) {\n      goto com_vid_usage;\n    }\n\n    if (key.beginswith(\"-h\") || key.beginswith(\"--h\")) {\n      goto com_vid_usage;\n    }\n\n    in += \"&mgm.vid.key=\";\n    in += key;\n    global_retc = output_result(client_command(in, true));\n    return (0);\n  }\n\ncom_vid_usage:\n  fprintf(stdout,\n          \"usage: vid ls [-u] [-g] [-s] [-U] [-G] [-g] [-a] [-l] [-n] : list configured policies\\n\");\n  fprintf(stdout,\n          \"                                        -u : show only user role mappings\\n\");\n  fprintf(stdout,\n          \"                                        -g : show only group role mappings\\n\");\n  fprintf(stdout,\n          \"                                        -s : show list of sudoers\\n\");\n  fprintf(stdout,\n          \"                                        -U : show user alias mapping\\n\");\n  fprintf(stdout,\n          \"                                        -G : show group alias mapping\\n\");\n  fprintf(stdout,\n          \"                                        -y : show configured gateways\\n\");\n  fprintf(stdout,\n          \"                                        -a : show authentication\\n\");\n  fprintf(stdout,\n          \"                                        -N : show maximum anonymous (nobody) access level deepness - the tree deepness where unauthenticated access is possible (default is 1024)\\n\");\n  fprintf(stdout,\n          \"                                        -l : show geo location mapping\\n\");\n  fprintf(stdout,\n          \"                                        -n : show numerical ids instead of user/group names\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout, \"       vid set membership <uid> -uids [<uid1>,<uid2>,...]\\n\");\n  fprintf(stdout, \"       vid set membership <uid> -gids [<gid1>,<gid2>,...]\\n\");\n  fprintf(stdout,\n          \"       vid rm membership <uid>             : delete the membership entries for <uid>.\\n\");\n  fprintf(stdout, \"       vid set membership <uid> [+|-]sudo \\n\");\n  fprintf(stdout,\n          \"       vid set map -krb5|-gsi|-https|-sss|-unix|-tident|-voms|-grpc|-oauth2 <pattern> [vuid:<uid>] [vgid:<gid>] \\n\");\n  fprintf(stdout,\n          \"           -voms <pattern>  : <pattern> is <group>:<role> e.g. to map VOMS attribute /dteam/cern/Role=NULL/Capability=NULL one should define <pattern>=/dteam/cern: \\n\");\n  fprintf(stdout,\n          \"           -sss key:<key>   : <key> has to be defined on client side via 'export XrdSecsssENDORSEMENT=<key>'\\n\");\n  fprintf(stdout,\n          \"           -grpc key:<key>  : <key> has to be added to the relevant GRPC request in the field 'authkey'\\n\");\n  fprintf(stdout,\n          \"           -https key:<key> : <key> has to be added to the relevant HTTP(S) request as a header 'x-gateway-authorization'\\n\");\n  fprintf(stdout,\n          \"           -oauth2 key:<oauth-resource> : <oauth-resource> describes the OAUTH resource endpoint to translate OAUTH tokens to user identities\\n\\n\");\n  fprintf(stdout,\n          \"       vid set geotag <IP-prefix> <geotag>  : add to all IP's matching the prefix <prefix> the geo location tag <geotag>\\n\");\n  fprintf(stdout,\n          \"                                              N.B. specify the default assumption via 'vid set geotag default <default-tag>'\\n\");\n  fprintf(stdout,\n          \"       vid rm <key>                         : remove configured vid with name key - hint: use config dump to see the key names of vid rules\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"       vid enable|disable krb5|gsi|sss|unix|https|grpc|oauth2|ztn\\n\");\n  fprintf(stdout,\n          \"                                            : enable/disables the default mapping via password or external database\\n\");\n  fprintf(stdout, \"\\n\");\n  fprintf(stdout,\n          \"       vid add|remove gateway <hostname> [krb5|gsi|sss|unix|https|grpc]\\n\");\n  fprintf(stdout,\n          \"                                            : adds/removes a host as a (fuse) gateway with 'su' priviledges\\n\");\n  fprintf(stdout,\n          \"                                              [<prot>] restricts the gateway role change to the specified authentication method\\n\");\n  fprintf(stdout,\n          \"       vid publicaccesslevel <level>\\n\");\n  fprintf(stdout,\n          \"                                           : sets the deepest directory level where anonymous access (nobody) is possible\\n\");\n  fprintf(stdout,\n          \"       vid tokensudo 0|1|2|3\\n\");\n  fprintf(stdout,\n          \"                                           : configure sudo policy when tokens are used\\n\");\n  fprintf(stdout,\n          \"                                             0 : always allow token sudo (setting uid/gid from token) [default if not set]\\n\");\n  fprintf(stdout,\n          \"                                             1 : allow token sudo if transport is encrypted\\n\");\n  fprintf(stdout,\n          \"                                             2 : allow token sudo for strong authentication (not unix!)\\n\");\n  fprintf(stdout,\n          \"                                             3 : never allow token sudo\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_who.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_who.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n/*----------------------------------------------------------------------------*/\n\n/* Who is connected -  Interface */\nint\ncom_who(char* arg1)\n{\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString option = \"\";\n  XrdOucString options = \"\";\n  XrdOucString in = \"\";\n\n  if (wants_help(arg1)) {\n    goto com_who_usage;\n  }\n\n  in = \"mgm.cmd=who\";\n\n  do {\n    option = subtokenizer.GetToken();\n\n    if (!option.length()) {\n      break;\n    }\n\n    if (option == \"-c\") {\n      options += \"c\";\n    } else {\n      if (option == \"-n\") {\n        options += \"n\";\n      } else {\n        if (option == \"-a\") {\n          options += \"a\";\n        } else {\n          if (option == \"-z\") {\n            options += \"z\";\n          } else {\n            if (option == \"-m\") {\n              options += \"m\";\n            } else {\n              if (option == \"-s\") {\n                options += \"s\";\n              } else {\n                goto com_who_usage;\n              }\n            }\n          }\n        }\n      }\n    }\n  } while (1);\n\n  if (options.length()) {\n    in += \"&mgm.option=\";\n    in += options;\n  }\n\n  global_retc = output_result(client_command(in));\n  return (0);\ncom_who_usage:\n  fprintf(stdout,\n          \"usage: who [-c] [-n] [-z] [-a] [-m] [-s]                             :  print statistics about active users (idle<5min)\\n\");\n  fprintf(stdout,\n          \"                -c                                                   -  break down by client host\\n\");\n  fprintf(stdout,\n          \"                -n                                                   -  print id's instead of names\\n\");\n  fprintf(stdout,\n          \"                -z                                                   -  print auth protocols\\n\");\n  fprintf(stdout,\n          \"                -a                                                   -  print all\\n\");\n  fprintf(stdout,\n          \"                -s                                                   -  print summary for clients\\n\");\n  fprintf(stdout,\n          \"                -m                                                   -  print in monitoring format <key>=<value>\\n\");\n  global_retc = EINVAL;\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/coms/unused/com_whoami.cc",
    "content": "// ----------------------------------------------------------------------\n// File: com_whoami.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n/*----------------------------------------------------------------------------*/\n\n/* Determine the mapping on server side */\nint\ncom_whoami(char* arg)\n{\n  XrdOucString in = \"mgm.cmd=whoami\";\n\n  eos::common::StringTokenizer subtokenizer(arg);\n  subtokenizer.GetLine();\n  XrdOucString token = subtokenizer.GetToken();\n\n\n  if (token.length()) {\n    in += \"&authz=\";\n    in += token;\n  }\n\n  global_retc = output_result(client_command(in));\n  return (0);\n}\n"
  },
  {
    "path": "console/commands/helpers/AclHelper.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file AclHelper.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/commands/helpers/AclHelper.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringUtils.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"proto/Acl.pb.h\"\n#include <algorithm>\n\n//------------------------------------------------------------------------------\n// Set the path doing any necessary modifications to the the absolute path\n//------------------------------------------------------------------------------\nbool\nAclHelper::SetPath(const std::string& in_path)\n{\n  eos::console::AclProto* acl = mReq.mutable_acl();\n\n  if (in_path.empty()) {\n    return false;\n  }\n\n  if (in_path.at(0) == '/') {\n    acl->set_path(in_path);\n  } else {\n    acl->set_path(abspath(in_path.c_str()));\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Check that the id respects the expected format\n//------------------------------------------------------------------------------\nbool\nAclHelper::CheckId(const std::string& id)\n{\n  static const std::string allowed_chars =\n    \"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_-\";\n\n  if ((id.length() > 2) &&\n      ((id.at(0) == 'u' && id.at(1) == ':') ||\n       (id.at(0) == 'k' && id.at(1) == ':') ||\n       (id.at(0) == 'g' && id.at(1) == ':'))) {\n    return (id.find_first_not_of(allowed_chars, 2) == std::string::npos);\n  }\n\n  if ((id.find(\"egroup\") == 0) && (id.length() > 7) && (id.at(6) == ':')) {\n    return (id.find_first_not_of(allowed_chars, 7) == std::string::npos);\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Check that the flags respect the expected format\n//------------------------------------------------------------------------------\nbool\nAclHelper::CheckFlags(const std::string& flags)\n{\n  static const std::string allowed_chars = \"!+-rwoxmduqcatAX\";\n  return flags.find_first_not_of(allowed_chars) == std::string::npos;\n}\n\n//------------------------------------------------------------------------------\n// Check that the rule respects the expected format\n//------------------------------------------------------------------------------\nbool\nAclHelper::CheckRule(const std::string& rule)\n{\n  size_t pos_del_first, pos_del_last, pos_equal;\n  pos_del_first = rule.find(\":\");\n  pos_del_last  = rule.rfind(\":\");\n  pos_equal     = rule.find(\"=\");\n  std::string id, flags;\n\n  if ((pos_del_first == pos_del_last) && (pos_equal != std::string::npos)) {\n    // u:id=rw+x\n    id = std::string(rule.begin(), rule.begin() + pos_equal);\n\n    if (!CheckId(id)) {\n      return false;\n    }\n\n    flags = std::string(rule.begin() + pos_equal + 1, rule.end());\n\n    if (!CheckFlags(flags)) {\n      return false;\n    }\n\n    return true;\n  } else {\n    if ((pos_del_first != pos_del_last) &&\n        (pos_del_first != std::string::npos) &&\n        (pos_del_last  != std::string::npos)) {\n      // u:id:+rwx\n      id = std::string(rule.begin(), rule.begin() + pos_del_last);\n\n      if (!CheckId(id)) {\n        return false;\n      }\n\n      flags = std::string(rule.begin() + pos_del_last + 1, rule.end());\n\n      if (!CheckFlags(flags)) {\n        return false;\n      }\n\n      return true;\n    }\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool\nAclHelper::ParseCommand(const char* arg)\n{\n  using eos::console::AclProto;\n  std::string token;\n  const char* temp;\n  AclProto* acl = mReq.mutable_acl();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n\n  // Get opts\n  while ((temp = tokenizer.GetToken(false)) != 0) {\n    token = std::string(temp);\n\n    // Skip if token is empty or it contains only spaces\n    if ((token == \"\") || (token.find_first_not_of(' ') == std::string::npos)) {\n      continue;\n    }\n\n    if (token == \"-lR\" || token == \"-Rl\") {\n      acl->set_recursive(true);\n      acl->set_op(AclProto::LIST);\n      continue;\n    }\n\n    if ((token == \"-R\") || (token == \"--recursive\")) {\n      acl->set_recursive(true);\n      continue;\n    }\n\n    if ((token == \"-f\") || (token == \"--front\")) {\n      if (acl->position()) {\n        std::cerr << \"error: set only one of position or front argument\" << std::endl;\n        return false;\n      }\n\n      acl->set_position(1);\n      continue;\n    }\n\n    if ((token == \"-p\") || (token == \"--position\")) {\n      if (acl->position()) {\n        std::cerr << \"error: set only one of position or front argument\" << std::endl;\n        return false;\n      }\n\n      std::string spos;\n\n      if (!tokenizer.NextToken(spos)) {\n        std::cerr << \"error: position needs an argument!\" << std::endl;\n        return false;\n      }\n\n      try {\n        int pos = std::stoi(spos);\n\n        if (pos > 0) {\n          acl->set_position(pos);\n        }\n      } catch (const std::exception& e) {\n        std::cerr << \"error: position needs to be integer\" << std::endl;\n        return false;\n      }\n\n      continue;\n    }\n\n    if ((token == \"-l\") || (token == \"--list\")) {\n      acl->set_op(AclProto::LIST);\n      continue;\n    }\n\n    if (token == \"--sys\") {\n      acl->set_sys_acl(true);\n      continue;\n    }\n\n    if (token == \"--user\") {\n      acl->set_user_acl(true);\n      continue;\n    }\n\n    // If there is unsupported flag\n    if (token.at(0) == '-') {\n      std::cerr << \"error: unrecognized flag \" << token << std::endl;\n      return false;\n    } else {\n      if (acl->op() == AclProto::LIST) {\n        // Set the absolute path if necessary\n        if (!SetPath(token)) {\n          std::cerr << \"error: failed to the the absolute path\" << std::endl;\n          return false;\n        }\n      } else {\n        acl->set_op(AclProto::MODIFY);\n\n        if (!CheckRule(token)) {\n          std::cerr << \"error: unrecognized rule format\" << std::endl;\n          return false;\n        }\n\n        acl->set_rule(token);\n\n        if ((temp = tokenizer.GetToken(false)) != 0) {\n          token = std::string(temp);\n\n          if (!SetPath(token)) {\n            std::cerr << \"error: failed to the the absolute path\" << std::endl;\n            return false;\n          }\n        } else {\n          return false;\n        }\n      }\n\n      break;\n    }\n  }\n\n  if ((acl->op() == AclProto::NONE) ||\n      acl->path().empty()) {\n    return false;\n  }\n\n  if (!acl->sys_acl() && !acl->user_acl()) {\n    if (acl->op() == AclProto::LIST) {\n      acl->set_sys_acl(true);\n      acl->set_user_acl(true);\n    } else if (acl->op() == AclProto::MODIFY) {\n      return SetDefaultRole();\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Set the default role - sys or user\n//------------------------------------------------------------------------------\nbool\nAclHelper::SetDefaultRole()\n{\n  eos::console::AclProto* acl = mReq.mutable_acl();\n  XrdOucString cmd(\"mgm.cmd=whoami\");\n  std::unique_ptr<XrdOucEnv> env(client_command(cmd, false, nullptr));\n  std::string result = (env->Get(\"mgm.proc.stdout\") ? env->Get(\"mgm.proc.stdout\")\n                        : \"\");\n\n  if (!result.empty()) {\n    size_t pos = 0;\n\n    if ((pos = result.find(\"uid=\")) != std::string::npos) {\n      if ((result.at(pos + 4) >= '0') && (result.at(pos + 4) <= '4') &&\n          (result.at(pos + 5) == ' ')) {\n        acl->set_sys_acl(true);\n      } else {\n        acl->set_sys_acl(false);\n      }\n\n      return true;\n    }\n\n    std::cerr << \"error: failed to get uid from whoami command\" << std::endl;\n    return false;\n  }\n\n  std::cerr << \"error: failed to execute whoami command\" << std::endl;\n  return false;\n}\n"
  },
  {
    "path": "console/commands/helpers/AclHelper.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file AclHelper.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"console/commands/helpers/ICmdHelper.hh\"\n\n//------------------------------------------------------------------------------\n//! Class AclHelper\n//------------------------------------------------------------------------------\nclass AclHelper: public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  AclHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ~AclHelper() = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg);\n\n  //----------------------------------------------------------------------------\n  //! Set default role - sys or user using the identity of the client\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool SetDefaultRole();\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Check that the rule respects the expected format\n  //!\n  //! @param rule client supplied rule\n  //!\n  //! @return true if correct, otherwise false\n  //----------------------------------------------------------------------------\n  static bool CheckRule(const std::string& rule);\n\n  //----------------------------------------------------------------------------\n  //! Check that the id respects the expected format\n  //!\n  //! @param id client supplied id\n  //!\n  //! @return true if correct, otherwise false\n  //----------------------------------------------------------------------------\n  static bool CheckId(const std::string& id);\n\n  //----------------------------------------------------------------------------\n  //! Check that the flags respect the expected format\n  //!\n  //! @param flags client supplied flags\n  //!\n  //! @return true if correct, otherwise false\n  //----------------------------------------------------------------------------\n  static bool CheckFlags(const std::string& flags);\n\n  //----------------------------------------------------------------------------\n  //! Set the path doing any necessary modifications to the the absolute path\n  //!\n  //! @param in_path input path\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool SetPath(const std::string& in_path);\n};\n"
  },
  {
    "path": "console/commands/helpers/FsHelper.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FsHelper.cc\n// Author: Jozsef Makai - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#include \"FsHelper.hh\"\n#include \"common/StringTokenizer.hh\"\n#include <unistd.h>\n#include <algorithm>\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool\nFsHelper::ParseCommand(const char* arg)\n{\n  const char* option;\n  std::string soption;\n  eos::console::FsProto* fs = mReq.mutable_fs();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  option = tokenizer.GetToken();\n  std::string cmd = (option ? option : \"\");\n\n  if (cmd == \"add\") {\n    eos::console::FsProto_AddProto* add = fs->mutable_add();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      soption = option;\n\n      if ((soption == \"-m\") || (soption == \"--manual\")) {\n        add->set_manual(true);\n\n        // Parse fsid\n        if (!(option = tokenizer.GetToken())) {\n          std::cerr << \"error: manual flag needs to be followed by fsid\"\n                    << std::endl;\n          return false;\n        }\n\n        soption = option;\n\n        try {\n          uint64_t fsid = std::stoull(soption);\n          add->set_fsid(fsid);\n        } catch (const std::exception& e) {\n          std::cerr << \"error: fsid needs to be numeric\" << std::endl;\n          return false;\n        }\n      }\n\n      // Advance token if manual option was set\n      if (add->manual() && !(option = tokenizer.GetToken())) {\n        std::cerr << \"error: missing uuid\" << std::endl;\n        return false;\n      }\n\n      // Parse uuid\n      add->set_uuid(option);\n\n      // Parse node queue or host:port\n      if (!(option = tokenizer.GetToken())) {\n        std::cerr << \"error: missing node-queue or host\" << std::endl;\n        return false;\n      }\n\n      soption = option;\n\n      if (!soption.empty() && (soption[0] == '/')) {\n        add->set_nodequeue(soption);\n      } else {\n        add->set_hostport(soption);\n      }\n\n      // Parse mountpoint\n      if (!(option = tokenizer.GetToken())) {\n        std::cerr << \"error: missing mountpoint\" << std::endl;\n        return false;\n      }\n\n      add->set_mountpoint(option);\n\n      // Parse scheduling group\n      if (!(option = tokenizer.GetToken())) {\n        // Set \"default\" scheduling group\n        add->set_schedgroup(\"default\");\n        add->set_status(\"off\");\n      } else {\n        add->set_schedgroup(option);\n\n        // Parse status\n        if (!(option = tokenizer.GetToken())) {\n          // Default status is \"off\"\n          add->set_status(\"off\");\n        } else {\n          add->set_status(option);\n        }\n      }\n\n      // Parse sharedfs name\n      if ((option = tokenizer.GetToken())) {\n        add->set_sharedfs(option);\n      }\n    }\n  } else if (cmd == \"boot\") {\n    using eos::console::FsProto_BootProto;\n    FsProto_BootProto* boot = fs->mutable_boot();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      soption = option;\n\n      // Parse * or node-queue identifier\n      if (soption == \"*\") {\n        boot->set_nodequeue(\"*\");\n      } else if (soption[0] == '/') {\n        boot->set_nodequeue(soption);\n      } else {\n        // Parse <fsid> or <uuid>\n        bool isUuid =\n          soption.find_first_not_of(\"0123456789\") != std::string::npos;\n\n        if (isUuid) {\n          boot->set_uuid(soption);\n        } else {\n          try {\n            uint64_t fsid = std::stoull(soption);\n            boot->set_fsid(fsid);\n          } catch (const std::exception& e) {\n            std::cerr << \"error: fsid needs to be numeric\" << std::endl;\n            return false;\n          }\n        }\n      }\n\n      if ((option = tokenizer.GetToken())) {\n        soption = option;\n\n        if (soption == \"--syncmgm\") {\n          boot->set_syncmgm(true);\n        } else if (soption == \"--syncdisk\") {\n          boot->set_syncdisk(true);\n        } else {\n          std::cerr << \"error: unknown option: \" << soption << std::endl;\n          return false;\n        }\n      }\n    }\n  } else if (cmd == \"clone\") {\n    auto* clone = fs->mutable_clone();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      soption = option;\n\n      try {\n        auto sourceid = std::stoull(soption);\n        clone->set_sourceid(sourceid);\n      } catch (const std::exception& e) {\n        std::cerr << \"error: fsid needs to be numeric\" << std::endl;\n        return false;\n      }\n    }\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      soption = option;\n\n      try {\n        auto targetid = std::stoull(soption);\n        clone->set_targetid(targetid);\n      } catch (const std::exception& e) {\n        std::cerr << \"error: fsid needs to be numeric\" << std::endl;\n        return false;\n      }\n    }\n  } else if (cmd == \"compare\") {\n    auto* compare = fs->mutable_compare();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      soption = option;\n\n      try {\n        auto sourceid = std::stoull(soption);\n        compare->set_sourceid(sourceid);\n      } catch (const std::exception& e) {\n        std::cerr << \"error: fsid needs to be numeric\" << std::endl;\n        return false;\n      }\n    }\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      soption = option;\n\n      try {\n        auto targetid = std::stoull(soption);\n        compare->set_targetid(targetid);\n      } catch (const std::exception& e) {\n        std::cerr << \"error: fsid needs to be numeric\" << std::endl;\n        return false;\n      }\n    }\n  } else if (cmd == \"config\") {\n    using eos::console::FsProto_ConfigProto;\n    FsProto_ConfigProto* config = fs->mutable_config();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      soption = option;\n      // Parse <fsid>\n      bool not_numeric = (soption.find_first_not_of(\"0123456789\") !=\n                          std::string::npos);\n\n      if (not_numeric) {\n        std::cerr << \"error: fsid needs to be numeric\" << std::endl;\n        return false;\n      } else {\n        try {\n          uint64_t fsid = std::stoull(soption);\n          config->set_fsid(fsid);\n        } catch (const std::exception& e) {\n          std::cerr << \"error: fsid needs to be numeric\" << std::endl;\n          return false;\n        }\n      }\n\n      // Parse key=value\n      if (!(option = tokenizer.GetToken())) {\n        std::cerr << \"error: configuration must be specified in <key>=<value>\"\n                  \" format\" << std::endl;\n        return false;\n      }\n\n      soption = option;\n      auto pos = soption.find('=');\n\n      if (pos == std::string::npos) {\n        std::cerr << \"error: configuration must be specified in <key>=<value>\"\n                  \" format\" << std::endl;\n        return false;\n      }\n\n      config->set_key(soption.substr(0, pos));\n      config->set_value(soption.substr(pos + 1));\n    }\n  } else if (cmd == \"dropdeletion\") {\n    using eos::console::FsProto_DropDeletionProto;\n    FsProto_DropDeletionProto* dropdel = fs->mutable_dropdel();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      soption = option;\n\n      try {\n        uint64_t fsid = std::stoull(soption);\n        dropdel->set_fsid(fsid);\n      } catch (const std::exception& e) {\n        std::cerr << \"error: fsid needs to be numeric\" << std::endl;\n        return false;\n      }\n    }\n  } else if (cmd == \"dropghosts\") {\n    using eos::console::FsProto_DropGhostsProto;\n    FsProto_DropGhostsProto* dropghosts = fs->mutable_dropghosts();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      soption = option;\n\n      try {\n        uint64_t fsid = std::stoull(soption);\n        dropghosts->set_fsid(fsid);\n      } catch (const std::exception& e) {\n        std::cerr << \"error: fsid needs to be numeric\" << std::endl;\n        return false;\n      }\n\n      // Parse optional list of explicit fids\n      if (tokenizer.NextToken(soption)) {\n        if (soption != \"--fxid\") {\n          std::cerr << \"error: unknown option \\\"\" << soption << \"\\\"\" << std::endl;\n          return false;\n        }\n\n        // Now parse the list of fids\n        while (tokenizer.NextToken(soption)) {\n          try {\n            dropghosts->add_fids(std::stoull(soption, 0, 16));\n          } catch (std::exception& e) {\n            std::cerr << \"error: fxid needs to be (hex) numeric\" << std::endl;\n            return false;\n          }\n        }\n      }\n    }\n  } else if (cmd == \"dropfiles\") {\n    using eos::console::FsProto_DropFilesProto;\n    FsProto_DropFilesProto* dropfiles = fs->mutable_dropfiles();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      soption = option;\n\n      try {\n        uint64_t fsid = std::stoull(soption);\n        dropfiles->set_fsid(fsid);\n      } catch (const std::exception& e) {\n        std::cerr << \"error: fsid needs to be numeric\" << std::endl;\n        return false;\n      }\n\n      // Parse -f optional flag\n      if ((option = tokenizer.GetToken())) {\n        soption = option;\n\n        if (soption != \"-f\") {\n          std::cerr << \"error: unknown option: \" << soption << std::endl;\n          return false;\n        }\n\n        dropfiles->set_force(true);\n      }\n\n      mNeedsConfirmation = true;\n    }\n  } else if (cmd == \"dumpmd\") {\n    using eos::console::FsProto_DumpMdProto;\n    FsProto_DumpMdProto* dumpmd = fs->mutable_dumpmd();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      soption = option;\n\n      try {\n        uint64_t fsid = std::stoull(soption);\n        dumpmd->set_fsid(fsid);\n      } catch (const std::exception& e) {\n        std::cerr << \"error: fsid needs to be numeric\" << std::endl;\n        return false;\n      }\n\n      // Parse any optional flags\n      if ((option = tokenizer.GetToken())) {\n        while (true) {\n          soption = option;\n\n          if (soption == \"--fid\") {\n            dumpmd->set_showfid(true);\n          } else if (soption == \"--fxid\") {\n            dumpmd->set_showfxid(true);\n          } else if (soption == \"--path\") {\n            dumpmd->set_showpath(true);\n          } else if (soption == \"--size\") {\n            dumpmd->set_showsize(true);\n          } else if (soption == \"--count\") {\n            dumpmd->set_showcount(true);\n          } else if (soption == \"-s\") {\n            mIsSilent = true;\n          } else if (soption == \"-m\") {\n            dumpmd->set_display(FsProto_DumpMdProto::MONITOR);\n          }\n\n          if (!(option = tokenizer.GetToken())) {\n            break;\n          }\n        }\n      } else {\n        dumpmd->set_showcount(true);\n      }\n    }\n  } else if (cmd == \"mv\") {\n    using eos::console::FsProto_MvProto;\n    FsProto_MvProto* mv = fs->mutable_mv();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      soption = option;\n\n      if (soption == \"--force\") {\n        if (!(option = tokenizer.GetToken())) {\n          return false;\n        }\n\n        mv->set_force(true);\n      }\n\n      soption = option;\n      mv->set_src(soption);\n\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n\n      soption = option;\n      mv->set_dst(soption);\n    }\n  } else if (cmd == \"ls\") {\n    using eos::console::FsProto_LsProto;\n    FsProto_LsProto* ls = fs->mutable_ls();\n\n    if ((option = tokenizer.GetToken())) {\n      int exclusive_opt = 0;\n\n      while (true) {\n        soption = option;\n\n        if (soption == \"-m\") {\n          ls->set_display(FsProto_LsProto::MONITOR);\n          ++exclusive_opt;\n        } else if (soption == \"-l\") {\n          ls->set_display(FsProto_LsProto::LONG);\n          ++exclusive_opt;\n        } else if (soption == \"-e\") {\n          ls->set_display(FsProto_LsProto::ERROR);\n          ++exclusive_opt;\n        } else if (soption == \"--io\") {\n          ls->set_display(FsProto_LsProto::IO);\n          ++exclusive_opt;\n        } else if (soption == \"--fsck\") {\n          ls->set_display(FsProto_LsProto::FSCK);\n          ++exclusive_opt;\n        } else if ((soption == \"-d\") || (soption == \"--drain\")) {\n          ls->set_display(FsProto_LsProto::DRAIN);\n          ++exclusive_opt;\n        } else if ((soption == \"-D\") || (soption == \"--drain_jobs\")) {\n          ls->set_display(FsProto_LsProto::RUNNING_DRAIN_JOBS);\n          ++exclusive_opt;\n        } else if ((soption == \"-F\") || (soption == \"--failed_drain_jobs\")) {\n          ls->set_display(FsProto_LsProto::FAILED_DRAIN_JOBS);\n          ++exclusive_opt;\n        } else if (soption == \"-s\") {\n          mIsSilent = true;\n        } else if ((soption == \"-b\") || (soption == \"--brief\")) {\n          ls->set_brief(true);\n        } else {\n          // This needs to be the matchlist\n          ls->set_matchlist(soption);\n        }\n\n        if (exclusive_opt >= 2) {\n          std::cerr << \"error: two exclusive options in the same command\"\n                    << std::endl;\n          return false;\n        }\n\n        if (!(option = tokenizer.GetToken())) {\n          break;\n        }\n      }\n    }\n  } else if (cmd == \"rm\") {\n    using eos::console::FsProto_RmProto;\n    FsProto_RmProto* rm = fs->mutable_rm();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      soption = option;\n\n      // Parse nodequeue specification\n      if ((soption.find(\"/eos/\") == 0) &&\n          (soption.find(':') != std::string::npos) &&\n          (soption.find('.') != std::string::npos)) {\n        // Check if it ends in /fst, if not append it\n        std::string search = \"/fst\";\n\n        if (soption.rfind(search) != soption.length() - search.length()) {\n          soption += \"/fst\";\n        }\n\n        // Parse the mountpoint and remove any ending /\n        std::string mountpoint;\n\n        if (!(option = tokenizer.GetToken())) {\n          std::cerr << \"error: no mountpoint specified\" << std::endl;\n          return false;\n        }\n\n        mountpoint = option;\n\n        if (*mountpoint.rbegin() == '/') {\n          mountpoint.pop_back();\n        }\n\n        soption += mountpoint;\n        rm->set_nodequeue(soption);\n      } else {\n        // Parse mountpoint and append any required info\n        if (soption[0] == '/') {\n          char hostname[255];\n\n          if (gethostname(hostname, sizeof(hostname)) == -1) {\n            std::cerr << \"error: failed to get local hostname\" << std::endl;\n            return false;\n          }\n\n          std::ostringstream oss;\n          oss << \"/eos/\" << hostname << \":1095/fst\" << soption;\n          rm->set_nodequeue(oss.str());\n        } else if (std::find_if(soption.begin(), soption.end(),\n        [](char c) {\n        return std::isalpha(c);\n        })\n        != soption.end()) {\n          // This contains at least one alphabetic char therefore it must be\n          // a hostname, parse the mountpoint and construct the node-queue\n          std::string mountpoint;\n\n          if (!(option = tokenizer.GetToken())) {\n            std::cerr << \"error: mountpoint missing\" << std::endl;\n            return false;\n          }\n\n          mountpoint = option;\n\n          if (mountpoint.empty() || mountpoint[0] != '/') {\n            std::cerr << \"error: invalid mountpoint\" << std::endl;\n            return false;\n          }\n\n          if (*mountpoint.rbegin() == '/') {\n            mountpoint.pop_back();\n          }\n\n          bool has_port = false;\n          auto pos = soption.find(':');\n\n          if ((pos != std::string::npos) && (pos < soption.length())) {\n            has_port = true;\n          }\n\n          std::ostringstream oss;\n          oss << \"/eos/\" << soption;\n\n          if (!has_port) {\n            oss << \":1095\";\n          }\n\n          oss << \"/fst\" << mountpoint;\n          rm->set_nodequeue(oss.str());\n        }\n        else {\n          // This needs to be an fsid\n          try {\n            uint64_t fsid = std::stoull(soption);\n            rm->set_fsid(fsid);\n          } catch (const std::exception& e) {\n            return false;\n          }\n        }\n      }\n    }\n  } else if (cmd == \"status\") {\n    using eos::console::FsProto_StatusProto;\n    FsProto_StatusProto* status = fs->mutable_status();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    } else {\n      while (true) {\n        soption = option;\n        std::ostringstream oss;\n\n        if (soption == \"-l\") {\n          status->set_longformat(true);\n        } else if (soption == \"-r\") {\n          status->set_riskassessment(true);\n        } else {\n          // This is a hostname specification\n          if ((soption.find('.') != std::string::npos) &&\n              (soption.find('/') == std::string::npos)) {\n            // Check for mountpoint\n            if (!(option = tokenizer.GetToken()) || (option[0] != '/')) {\n              std::cerr << \"error: no mountpoint specified\" << std::endl;\n              return false;\n            }\n\n            oss << \"/eos/\" << soption << \"/fst\" << option;\n            status->set_nodequeue(oss.str());\n          } else if (soption[0] == '/') {\n            // This is a mountpoint append the local hostname\n            char hostname[255];\n\n            if (gethostname(hostname, sizeof(hostname)) == -1) {\n              std::cerr << \"error: failed to get local hostname\" << std::endl;\n              return false;\n            }\n\n            oss << \"/eos/\" << hostname << \":1095/fst\" << soption;\n            status->set_nodequeue(oss.str());\n          } else if (std::isalpha(soption[0])) {\n            // This is a hostname specification, check for mountpoint\n            if (!(option = tokenizer.GetToken())) {\n              std::cerr << \"error: no mountpoint specified\" << std::endl;\n              return false;\n            }\n\n            oss << \"/eos/\" << soption << \"/fst\" << option;\n            status->set_nodequeue(oss.str());\n          } else {\n            // This needs to be a fsid\n            try {\n              uint64_t fsid = std::stoull(soption);\n              status->set_fsid(fsid);\n            } catch (const std::exception& e) {\n              return false;\n            }\n          }\n        }\n\n        if (!(option = tokenizer.GetToken())) {\n          break;\n        }\n      }\n\n      if ((status->fsid() == 0) && (status->nodequeue().empty())) {\n        std::cerr << \"error: fsid or host/mountpoint needs to be specified\"\n                  << std::endl;\n        return false;\n      }\n    }\n  } else {\n    return false;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "console/commands/helpers/FsHelper.hh",
    "content": "//------------------------------------------------------------------------------\n// File: FsHelper.hh\n// Author: Jozsef Makai - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"console/commands/helpers/ICmdHelper.hh\"\n\n//------------------------------------------------------------------------------\n//! Class FsHelper\n//------------------------------------------------------------------------------\nclass FsHelper: public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  FsHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {\n    mIsAdmin = true;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Denstructor\n  //----------------------------------------------------------------------------\n  ~FsHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n};\n"
  },
  {
    "path": "console/commands/helpers/FsckHelper.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file FsckHelper.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/commands/helpers/FsckHelper.hh\"\n#include \"common/FileSystem.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/FileId.hh\"\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool\nFsckHelper::ParseCommand(const char* arg)\n{\n  const char* option;\n  std::string soption;\n  eos::console::FsckProto* fsck = mReq.mutable_fsck();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  option = tokenizer.GetToken();\n  std::string cmd = (option ? option : \"\");\n\n  if (cmd == \"stat\") {\n    fsck->set_stat(true);\n\n    if ((option = tokenizer.GetToken()) != nullptr) {\n      if (strncmp(option, \"-m\", 2) == 0) {\n        mReq.set_format(eos::console::RequestProto_FormatType_FUSE);\n      } else {\n        std::cerr << \"error: unknown option for the stat command\" << std::endl;\n        return false;\n      }\n    }\n  } else if (cmd == \"config\") {\n    if ((option = tokenizer.GetToken()) == nullptr) {\n      return false;\n    }\n\n    std::string key = option;\n    std::string value {};\n\n    if ((option = tokenizer.GetToken()) != nullptr) {\n      value = option;\n    }\n\n    eos::console::FsckProto::ConfigProto* config = fsck->mutable_config();\n    config->set_key(key);\n    config->set_value(value);\n  } else if (cmd == \"report\") {\n    mIsAdmin = false;\n    eos::console::FsckProto::ReportProto* report = fsck->mutable_report();\n\n    while (true) {\n      if ((option = tokenizer.GetToken()) == nullptr) {\n        break;\n      }\n\n      soption = option;\n\n      if (soption == \"-a\") {\n        report->set_display_per_fs(true);\n      } else if (soption == \"-i\") {\n        report->set_display_fxid(true);\n      } else if (soption == \"-l\") {\n        report->set_display_lfn(true);\n      } else if ((soption == \"-j\") || (soption == \"--json\")) {\n        report->set_display_json(true);\n      } else if (soption == \"--error\") {\n        // Now parse the tags until end of line\n        while ((option = tokenizer.GetToken())) {\n          std::string* tag = report->add_tags();\n          tag->assign(option);\n        }\n\n        break;\n      }\n    }\n  } else if (cmd == \"repair\") {\n    eos::console::FsckProto::RepairProto* repair = fsck->mutable_repair();\n\n    while (tokenizer.NextToken(soption)) {\n      if (soption == \"--fxid\") {\n        if ((option = tokenizer.GetToken()) == nullptr) {\n          std::cerr << \"error: fxid option needs a value\\n\\n\";\n          return false;\n        }\n\n        uint64_t fid = eos::common::FileId::Hex2Fid(option);\n\n        if (fid == 0ull) {\n          std::cerr << \"error: fid option needs to be non-zero\\n\\n\";\n          return false;\n        }\n\n        repair->set_fid(fid);\n      } else if (soption == \"--fsid\") {\n        if ((option = tokenizer.GetToken()) == nullptr) {\n          std::cerr << \"error: fsid option needs a value\\n\\n\";\n          return false;\n        }\n\n        soption = option;\n        uint64_t fsid {0ull};\n\n        try {\n          fsid = std::stoull(soption);\n        } catch (...) {\n          std::cerr << \"error: fsid option needs to be numeric\\n\\n\";\n          return false;\n        }\n\n        if (fsid == 0ull) {\n          std::cerr << \"error: fsid option needs to be non-zero\\n\";\n          return false;\n        }\n\n        repair->set_fsid_err(fsid);\n      } else if (soption == \"--error\") {\n        if ((option = tokenizer.GetToken()) == nullptr) {\n          std::cerr << \"error: the error flag needs an option\\n\\n\";\n          return false;\n        }\n\n        repair->set_error(option);\n      } else if (soption == \"--async\") {\n        repair->set_async(true);\n      } else {\n        std::cerr << \"error: unknown option \\\"\" << soption << \"\\\"\\n\\n\";\n        return false;\n      }\n    }\n  } else if (cmd == \"clean_orphans\") {\n    eos::console::FsckProto::CleanOrphansProto* clean =\n      fsck->mutable_clean_orphans();\n    // Clean orphans for all file systems i.e. fsid=0 by default\n    clean->set_fsid(0ull);\n\n    while (tokenizer.NextToken(soption)) {\n      if (soption == \"--fsid\") {\n        if (!tokenizer.NextToken(soption)) {\n          std::cerr << \"error: missing file system id value\\n\\n\";\n          return false;\n        }\n\n        eos::common::FileSystem::fsid_t fsid = 0ul;\n\n        try {\n          size_t pos = 0;\n          fsid = std::stoul(soption.c_str(), &pos);\n\n          if (pos != soption.length()) {\n            throw std::invalid_argument(\"fsid not numeric\");\n          }\n        } catch (...) {\n          std::cerr << \"error: file system id must be numeric\\n\\n\";\n          return false;\n        }\n\n        clean->set_fsid(fsid);\n      } else if (soption == \"--force-qdb-cleanup\") {\n        clean->set_force_qdb_cleanup(true);\n      } else {\n        std::cerr << \"error: unknown option \\\"\" << soption << \"\\\"\\n\\n\";\n        return false;\n      }\n    }\n\n    if (clean->fsid() && clean->force_qdb_cleanup()) {\n      std::cerr << \"error: force qdb cleanup doesn't work on individual fsids\\n\";\n      return false;\n    }\n  } else {\n    return false;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "console/commands/helpers/FsckHelper.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file FsckHelper.hh\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"console/commands/helpers/ICmdHelper.hh\"\n\n//------------------------------------------------------------------------------\n//! Class FsckHelper\n//------------------------------------------------------------------------------\nclass FsckHelper: public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  FsckHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {\n    mIsAdmin = true;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~FsckHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n};\n"
  },
  {
    "path": "console/commands/helpers/ICmdHelper.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file ICmdHelper.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/commands/helpers/ICmdHelper.hh\"\n#include \"common/Logging.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/utils/RandUtils.hh\"\n#include <XrdCl/XrdClFile.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n#include <sstream>\n#include <zmq.hpp>\n\n//------------------------------------------------------------------------------\n// Execute command and display any output information\n//------------------------------------------------------------------------------\nint\nICmdHelper::Execute(bool print_err, bool add_route)\n{\n  if (mIsLocal) {\n    return 0;\n  }\n\n  int retc = ExecuteWithoutPrint(add_route);\n\n  if (!mIsSilent && !mOutcome.result.empty()) {\n    std::cout << GetResult();\n  }\n\n  if (print_err && !mOutcome.error.empty()) {\n    std::cerr << GetError();\n  }\n\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Execute command without displaying the result\n//------------------------------------------------------------------------------\nint\nICmdHelper::ExecuteWithoutPrint(bool add_route)\n{\n  if (!mReq.command_case()) {\n    std::cerr << \"error: generic request object not populated with command\"\n              << std::endl;\n    return EINVAL;\n  }\n\n  std::string b64buff;\n\n  if (!eos::common::SymKey::ProtobufBase64Encode(&mReq, b64buff)) {\n    std::cerr << \"error: failed to base64 encode the request\" << std::endl;\n    return EINVAL;\n  }\n\n  std::string cmd = \"mgm.cmd.proto=\";\n  cmd += b64buff;\n\n  if (add_route) {\n    AddRouteInfo(cmd);\n  }\n\n  std::ostringstream oss;\n  oss << mGlobalOpts.mMgmUri\n      << (mIsAdmin ? \"//proc/admin/\" : \"//proc/user/\") << \"?\"\n      << cmd;\n\n  if (!mGlobalOpts.mUserRole.empty()) {\n    oss << \"&eos.ruid=\" << mGlobalOpts.mUserRole;\n  }\n\n  if (!mGlobalOpts.mGroupRole.empty()) {\n    oss << \"&eos.rgid=\" << mGlobalOpts.mGroupRole;\n  }\n\n  if (mGlobalOpts.mForceSss) {\n    oss << \"&xrd.wantprot=sss\";\n  }\n\n  if (getenv(\"EOSAUTHZ\")) {\n    oss << \"&authz=\" << getenv(\"EOSAUTHZ\");\n  }\n\n  if (getenv(\"EOSAPP\")) {\n    oss << \"&eos.app=\" << getenv(\"EOSAPP\");\n  }\n\n  if (mGlobalOpts.mDebug) {\n    PrintDebugMsg(oss.str());\n  }\n\n  return RawExecute(oss.str());\n}\n\n//------------------------------------------------------------------------------\n// Execute command using the xrootd client\n//------------------------------------------------------------------------------\nint\nICmdHelper::RawExecute(const std::string& full_url)\n{\n  if (mSimulationMode) {\n    if (mSimulatedData.front().expectedCommand != full_url) {\n      mSimulationErrors +=\n        SSTR(\"Expected command '\" << mSimulatedData.front().expectedCommand\n             << \"', received '\" << full_url << \"'\");\n      return EIO;\n    }\n\n    // Command is OK\n    mOutcome = mSimulatedData.front().outcome;\n    mSimulatedData.pop();\n    return mOutcome.errc;\n  }\n\n  std::ostringstream oss;\n\n  if (mGlobalOpts.mMgmUri.substr(0, 6) == \"ipc://\") {\n    // ZMQ connection\n    zmq::context_t context(1);\n    zmq::socket_t socket(context, ZMQ_REQ);\n    std::string path = full_url;\n    path.erase(0, mGlobalOpts.mMgmUri.length() + 1);\n    socket.connect(mGlobalOpts.mMgmUri);\n\n    zmq::message_t request(path.length());\n    memcpy(request.data(), path.c_str(), path.length());\n    socket.send(request, zmq::send_flags::none);\n    std::string sout;\n    zmq::message_t response;\n    zmq::recv_result_t ret_recv = socket.recv(response);\n    if (ret_recv.has_value()) {\n      sout.assign((char*)response.data(), response.size());\n      oss << sout;\n    }\n  } else {\n    // XRootD connection\n    std::unique_ptr<XrdCl::File> client {new XrdCl::File()};\n    XrdCl::XRootDStatus status = client->Open(full_url.c_str(),\n                                 XrdCl::OpenFlags::Read);\n\n    if (status.IsOK()) {\n      off_t offset = 0;\n      uint32_t nbytes = 0;\n      char buffer[4096 + 1];\n      status = client->Read(offset, 4096, buffer, nbytes);\n\n      while (status.IsOK() && (nbytes > 0)) {\n        buffer[nbytes] = 0;\n        oss << buffer;\n        offset += nbytes;\n        status = client->Read(offset, 4096, buffer, nbytes);\n      }\n\n      status = client->Close();\n    } else {\n      int retc = status.GetShellCode();\n\n      if (status.errNo) {\n        retc = status.errNo;\n      }\n\n      oss << \"mgm.proc.stdout=\"\n          << \"&mgm.proc.stderr=\" << \"error: errc=\" << retc\n          << \" msg=\\\"\" << status.ToString() << \"\\\"\"\n          << \"&mgm.proc.retc=\" << retc;\n    }\n  }\n\n  return ProcessResponse(oss.str());\n}\n\n//------------------------------------------------------------------------------\n// Process MGM response\n//------------------------------------------------------------------------------\nint ICmdHelper::ProcessResponse(const std::string& response)\n{\n  if (response.empty()) {\n    mOutcome.error = \"error: failed to read proc response\";\n    mOutcome.errc = EIO;\n    return mOutcome.errc;\n  }\n\n  if (mGlobalOpts.mDebug) {\n    PrintDebugMsg(response);\n  }\n\n  mOutcome.errc = 0;\n  std::vector<std::pair<std::string, size_t>> tags {\n    std::make_pair(\"mgm.proc.stdout=\", -1),\n    std::make_pair(\"&mgm.proc.stderr=\", -1),\n    std::make_pair(\"&mgm.proc.retc=\", -1)\n  };\n\n  for (auto& elem : tags) {\n    elem.second = response.find(elem.first);\n  }\n\n  if ((tags[0].second == std::string::npos) &&\n      (tags[1].second == std::string::npos)) {\n    // This is a \"FUSE\" format response that only contains the stdout without\n    // error message or return code\n    mOutcome.result = response;\n    return mOutcome.errc;\n  }\n\n  // Parse stdout.\n  if (tags[0].second != std::string::npos) {\n    if (tags[1].second != std::string::npos) {\n      mOutcome.result = response.substr(tags[0].first.length(),\n                                        tags[1].second - tags[1].first.length() + 1);\n    } else {\n      mOutcome.result = response.substr(tags[0].first.length(),\n                                        tags[2].second - tags[2].first.length() - 1);\n    }\n  }\n\n  // Parse stderr\n  if (tags[1].second != std::string::npos) {\n    mOutcome.error = response.substr(tags[1].second + tags[1].first.length(),\n                                     tags[2].second - (tags[1].second + tags[1].first.length()));\n  }\n\n  // Parse return code\n  try {\n    mOutcome.errc = std::stoi(response.substr(tags[2].second +\n                              tags[2].first.length()));\n  } catch (...) {\n    mOutcome.error = \"error: failed to parse response from server\";\n    return EINVAL;\n  }\n\n  return mOutcome.errc;\n}\n\n\n//------------------------------------------------------------------------------\n// Method used for user confirmation of the specified command\n//------------------------------------------------------------------------------\nbool\nICmdHelper::ConfirmOperation()\n{\n  std::ostringstream out;\n  std::string confirmation;\n\n  for (int i = 0; i < 10; i++) {\n    confirmation += std::to_string(eos::common::getRandom<int>(0, 8));\n  }\n\n  out << \"Confirm operation by typing => \" << confirmation << std::endl;\n  out << \"                            => \";\n  std::string userInput;\n  std::cout << out.str();\n  getline(std::cin, userInput);\n\n  if (userInput == confirmation) {\n    std::cout << std::endl << \"Operation confirmed\" << std::endl;\n    return true;\n  } else {\n    std::cout << std::endl << \"Operation not confirmed\" << std::endl;\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get command output string\n//------------------------------------------------------------------------------\nstd::string\nICmdHelper::GetResult()\n{\n  // Add new line if necessary\n  std::string out = mOutcome.result;\n\n  if (*out.rbegin() != '\\n') {\n    out += '\\n';\n  }\n\n  return out;\n}\n\n//------------------------------------------------------------------------------\n// Get command error string\n//------------------------------------------------------------------------------\nstd::string\nICmdHelper::GetError()\n{\n  // Add new line if necessary\n  std::string err = mOutcome.error;\n\n  if (*err.rbegin() != '\\n') {\n    err += '\\n';\n  }\n\n  return err;\n}\n\n//------------------------------------------------------------------------------\n// Guess a default 'route' e.g. home directory\n//------------------------------------------------------------------------------\nstd::string\nICmdHelper::DefaultRoute(bool verbose)\n{\n  std::string default_route = \"\";\n\n  // add a default 'route' for the command\n  if (getenv(\"EOSHOME\")) {\n    default_route = getenv(\"EOSHOME\");\n  } else {\n    char default_home[4096];\n    std::string username;\n\n    if (getenv(\"EOSUSER\")) {\n      username = getenv(\"EOSUSER\");\n    }\n\n    if (getenv(\"USER\")) {\n      username = getenv(\"USER\");\n    }\n\n    if (username.length()) {\n      snprintf(default_home, sizeof(default_home), \"/eos/user/%s/%s/\",\n               username.substr(0, 1).c_str(), username.c_str());\n\n      if (verbose) {\n        // @note route warning is no longer displayed\n        // fprintf(stderr,\n        //         \"# pre-configuring default route to %s\\n\"\n        //         \"# -use $EOSHOME variable to override\\n\",\n        //         default_home);\n      }\n\n      default_route = default_home;\n    }\n  }\n\n  return default_route;\n}\n\n//------------------------------------------------------------------------------\n// Add eos.route opaque info depending on the type of request and on the\n// default route configuration\n//------------------------------------------------------------------------------\nvoid\nICmdHelper::AddRouteInfo(std::string& cmd)\n{\n  using eos::console::RequestProto;\n  bool verbose = true;\n\n  // suppress routing output for formatted quota command\n  switch (mReq.command_case()) {\n  case RequestProto::kQuota:\n    if (mReq.quota().lsuser().format()) {\n      verbose = false;\n    }\n\n    if (mReq.quota().ls().format()) {\n      verbose = false;\n    }\n\n    break;\n\n  case RequestProto::kRm:\n    verbose = false;\n    break;\n\n  default:\n    break;\n  }\n\n  const std::string default_route = DefaultRoute(verbose);\n  std::ostringstream oss;\n\n  switch (mReq.command_case()) {\n  case RequestProto::kRecycle:\n    if (!default_route.empty()) {\n      oss << \"&eos.route=\" << default_route;\n    }\n\n    break;\n\n  case RequestProto::kAcl:\n    oss << \"&eos.route=\" << mReq.acl().path();\n    break;\n\n  case RequestProto::kToken:\n    oss << \"&eos.route=\" << mReq.token().path();\n    break;\n\n  case RequestProto::kRm:\n    if (mReq.rm().path().empty()) {\n      if (!default_route.empty()) {\n        oss << \"&eos.route=\" << default_route;\n      }\n    } else {\n      oss << \"&eos.route=\" << mReq.rm().path();\n    }\n\n    break;\n\n  case RequestProto::kQuota:\n    if (mReq.quota().subcmd_case() ==\n        eos::console::QuotaProto::kLsuser) {\n      oss << \"&eos.route=\" << mReq.quota().lsuser().space();\n    }\n\n    break;\n\n  case RequestProto::kFind:\n    oss << \"&eos.route=\" << mReq.find().path();\n    break;\n\n  default:\n    break;\n  }\n\n  cmd += oss.str();\n}\n"
  },
  {
    "path": "console/commands/helpers/ICmdHelper.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file ICmdHelper.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"proto/ConsoleRequest.pb.h\"\n#include \"console/GlobalOptions.hh\"\n#include <queue>\n#include <iostream>\n#include <unistd.h>\n\n//------------------------------------------------------------------------------\n//! Class ICmdHelper\n//! @brief Abstract base class to be inherited in all the command\n//! implementations\n//------------------------------------------------------------------------------\nclass ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! ExecutionOutcome struct: Stores output from a single execution\n  //----------------------------------------------------------------------------\n  struct ExecutionOutcome {\n    ExecutionOutcome() : result(\"\"), error(\"\"), errc(0) {}\n    ExecutionOutcome(const std::string& res, const std::string& err = \"\", int c = 0)\n      : result(res), error(err), errc(c) {}\n\n    std::string result;  ///< String holding the result\n    std::string error;   ///< String holding the error message\n    int errc;            ///< Command return code\n  };\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ICmdHelper(const GlobalOptions& opts):\n    mReq(), mIsAdmin(false), mIsSilent(false),\n    mGlobalOpts(opts)\n  {\n    if (opts.mJsonFormat) {\n      mReq.set_format(eos::console::RequestProto::JSON);\n    }\n\n    if (!opts.mComment.empty()) {\n      mReq.set_comment(opts.mComment);\n    }\n\n    if (!isatty(STDOUT_FILENO) || !isatty(STDERR_FILENO)) {\n      mReq.set_dontcolor(true);\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~ICmdHelper() = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //! @param opts global options parse before current command\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool ParseCommand(const char* arg) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Execute command and display any output information\n  //! @note When this methods is called the generic request object mReq needs\n  //! to already contain the specific command object.\n  //!\n  //! @param print_err flag to enable the display of any potential errors\n  //! @param add_route flag if eos.route opaque info needs to be added\n  //!\n  //! @return command return code\n  //----------------------------------------------------------------------------\n  int Execute(bool print_err = true, bool add_route = false);\n\n  //----------------------------------------------------------------------------\n  //! Execute command without displaying the result\n  //!\n  //! @param add_route flag if eos.route opaque info needs to be added\n  //!\n  //! @return command return code\n  //----------------------------------------------------------------------------\n  int ExecuteWithoutPrint(bool add_route = false);\n\n  //----------------------------------------------------------------------------\n  //! Get command output string\n  //----------------------------------------------------------------------------\n  std::string GetResult();\n\n  //----------------------------------------------------------------------------\n  //! Get command error string\n  //----------------------------------------------------------------------------\n  std::string GetError();\n\n  //----------------------------------------------------------------------------\n  //! Get error code\n  //----------------------------------------------------------------------------\n  inline int GetErrc()\n  {\n    return mOutcome.errc;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if commands needs confirmation from the client\n  //----------------------------------------------------------------------------\n  inline bool NeedsConfirmation() const\n  {\n    return mNeedsConfirmation;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the request object\n  //----------------------------------------------------------------------------\n  inline const eos::console::RequestProto& GetRequest() const\n  {\n    return mReq;\n  }\n\n  //------------------------------------------------------------------------------\n  //! Method used for user confirmation of the specified command\n  //!\n  //! @return true if operation confirmed, otherwise false\n  //------------------------------------------------------------------------------\n  static bool ConfirmOperation();\n\n  //------------------------------------------------------------------------------\n  //! Add eos.route opaque info depending on the type of request and on the\n  //! default route configuration\n  //!\n  //! @param cmd URL opaque info collected so far to which we can append extra\n  //!        route information\n  //------------------------------------------------------------------------------\n  void AddRouteInfo(std::string& cmd);\n\n  //----------------------------------------------------------------------------\n  //! Inject simulated data. After calling this function, ALL responses from\n  //! this class will be simulated, and there's no turning back.\n  //----------------------------------------------------------------------------\n  void InjectSimulated(const std::string& command,\n                       const ExecutionOutcome& outcome)\n  {\n    mSimulationMode = true;\n    mSimulatedData.emplace(FakeEntry{command, outcome});\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check whether simulation was successful, ie we received the exact\n  //! commands in the specified order.\n  //----------------------------------------------------------------------------\n  bool CheckSimulationSuccessful(std::string& message)\n  {\n    message = mSimulationErrors;\n    return mSimulatedData.empty() && mSimulationErrors.empty();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Process command response string\n  //!\n  //! @param response command response string\n  //!\n  //! @return 0 if successful, otherwise error code\n  //----------------------------------------------------------------------------\n  int ProcessResponse(const std::string& response);\n\nprotected:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  //----------------------------------------------------------------------------\n  //! Execute command using the xrootd client\n  //!\n  //! @param full_url full url containing the MGM endpoint, command encoding\n  //!        and any other global options\n  //!\n  //! @return 0 if successful, otherwise error code\n  //----------------------------------------------------------------------------\n  int RawExecute(const std::string& full_url);\n\n  //----------------------------------------------------------------------------\n  //! Guess a default 'route' e.g. home directory - this code is duplicated\n  //! on purpose in ConsoleMain but will be dropped from there in the future.\n  //!\n  //! @param verbose flag indicating whether to print selected route\n  //! @return the computed default route\n  //----------------------------------------------------------------------------\n  std::string DefaultRoute(bool verbose = true);\n\n  //----------------------------------------------------------------------------\n  //! Print debug message to console\n  //----------------------------------------------------------------------------\n  inline void PrintDebugMsg(const std::string& message) const\n  {\n    std::cout << \"> \" << message << std::endl;\n  }\n\n  eos::console::RequestProto mReq; ///< Generic request object send to the MGM\n  bool mIsAdmin; ///< If true execute as admin, otherwise as user\n  bool mIsSilent; ///< If true execute command but don't display anything\n  //! If true it requires a strong user confirmation before executing the command\n  bool mNeedsConfirmation {false};\n  bool mIsLocal {false}; ///< Mark if command is executed only client side\n  GlobalOptions mGlobalOpts; ///< Global options for all commands\n  ExecutionOutcome mOutcome; ///< Stores outcome of last operation\n\n  //----------------------------------------------------------------------------\n  //! FakeEntry struct: Stores information about a fake request / response pair\n  //----------------------------------------------------------------------------\n  struct FakeEntry {\n    std::string expectedCommand;\n    ExecutionOutcome outcome;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Simulation mode: Expect calls in the following order, and provide the\n  //! given fake responses\n  //----------------------------------------------------------------------------\n  bool mSimulationMode = false;\n  std::queue<FakeEntry> mSimulatedData;\n  std::string mSimulationErrors;\n};\n"
  },
  {
    "path": "console/commands/helpers/NewfindHelper.cc",
    "content": "// ----------------------------------------------------------------------\n// @file: NewfindHelper.cc\n// @author: Fabio Luchetti - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/NewfindHelper.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringConversion.hh\"\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool\nNewfindHelper::ParseCommand(const char* arg)\n{\n  auto* find = mReq.mutable_find();\n  XrdOucString s1;\n  std::string token;\n  eos::common::StringTokenizer subtokenizer(arg);\n  subtokenizer.GetLine();\n\n  while ((s1 = subtokenizer.GetToken()).length() > 0 && (s1.beginswith(\"-\"))) {\n    if (s1 == \"-s\") {\n      find->set_silent(true);\n    } else if (s1 == \"-d\") {\n      find->set_directories(true);\n    } else if (s1 == \"-f\") {\n      find->set_files(true);\n    } else if (s1 == \"-0\") {\n      find->set_files(true);\n      find->set_zerosizefiles(true);\n    } else if (s1 == \"--size\") {\n      find->set_size(true);\n    } else if (s1 == \"--fs\") {\n      find->set_fs(true);\n    } else if (s1 == \"--checksum\") {\n      find->set_checksum(true);\n    } else if (s1 == \"--ctime\") {\n      find->set_ctime(true);\n    } else if (s1 == \"--mtime\") {\n      find->set_mtime(true);\n    } else if (s1 == \"--fid\") {\n      find->set_fid(true);\n    } else if (s1 == \"--nrep\") {\n      find->set_nrep(true);\n    } else if (s1 == \"--online\") {\n      find->set_online(true);\n    } else if (s1 == \"--fileinfo\") {\n      find->set_fileinfo(true);\n    } else if (s1 == \"--nunlink\") {\n      find->set_nunlink(true);\n    } else if (s1 == \"--uid\") {\n      find->set_printuid(true);\n    } else if (s1 == \"--gid\") {\n      find->set_printgid(true);\n    } else if (s1 == \"--stripediff\") {\n      find->set_stripediff(true);\n    } else if (s1 == \"--skip-version-dirs\") {\n      find->set_skipversiondirs(true);\n    } else if (s1 == \"--faultyacl\") {\n      find->set_faultyacl(true);\n    } else if (s1 == \"--count\") {\n      find->set_count(true);\n    } else if (s1 == \"--cache\") {\n      find->set_cache(true);\n    } else if (s1 == \"--du\") {\n      find->set_du(true);\n    } else if (s1 == \"--du-si\") {\n      find->set_dusi(true);\n    } else if (s1 == \"--du-h\") {\n      find->set_dureadable(true);\n    } else if (s1 == \"--hosts\") {\n      find->set_hosts(true);\n    } else if (s1 == \"--partition\") {\n      find->set_partition(true);\n    } else if (s1 == \"--childcount\") {\n      find->set_childcount(true);\n    } else if (s1 == \"--treecount\") {\n      find->set_treecount(true);\n    } else if (s1 == \"--format\") {\n      if (!subtokenizer.NextToken(token)) {\n        return false;\n      }\n\n      find->set_format(token);\n    } else if (s1 == \"--xurl\") {\n      find->set_xurl(true);\n    } else if (s1 == \"-b\") {\n      find->set_balance(true);\n    } else if (s1 == \"-g\") {\n      find->set_mixedgroups(true);\n    } else if (s1 == \"-uid\") {\n      find->set_searchuid(true);\n\n      if (!subtokenizer.NextToken(token)) {\n        return false;\n      }\n\n      try {\n        find->set_uid(std::stoul(token));\n      } catch (std::invalid_argument& error) {\n        return false;\n      }\n    } else if (s1 == \"-nuid\") {\n      find->set_searchnotuid(true);\n\n      if (!subtokenizer.NextToken(token)) {\n        return false;\n      }\n\n      try {\n        find->set_notuid(std::stoul(token));\n      } catch (std::invalid_argument& error) {\n        return false;\n      }\n    } else if (s1 == \"-gid\") {\n      find->set_searchgid(true);\n\n      if (!subtokenizer.NextToken(token)) {\n        return false;\n      }\n\n      try {\n        find->set_gid(std::stoul(token));\n      } catch (std::invalid_argument& error) {\n        return false;\n      }\n    } else if (s1 == \"-ngid\") {\n      find->set_searchnotgid(true);\n\n      if (!subtokenizer.NextToken(token)) {\n        return false;\n      }\n\n      try {\n        find->set_notgid(std::stoul(token));\n      } catch (std::invalid_argument& error) {\n        return false;\n      }\n    } else if (s1 == \"-flag\") {\n      find->set_searchpermission(true);\n\n      if (!subtokenizer.NextToken(token)) {\n        return false;\n      }\n\n      if (token.length() != 3 ||\n          token.find_first_not_of(\"01234567\") != std::string::npos) {\n        return false;\n      }\n\n      find->set_permission(token);\n    } else if (s1 == \"-nflag\") {\n      find->set_searchnotpermission(true);\n\n      if (!subtokenizer.NextToken(token)) {\n        return false;\n      }\n\n      if (token.length() != 3 ||\n          token.find_first_not_of(\"01234567\") != std::string::npos) {\n        return false;\n      }\n\n      find->set_notpermission(token);\n    } else if (s1 == \"-x\") {\n      if (!subtokenizer.NextToken(token)) {\n        return false;\n      }\n\n      if (token.length() > 0 && token.find('=') != std::string::npos &&\n          token.find('&') == std::string::npos) {\n        auto key = token;\n        auto value = token;\n        key.erase(token.find('='));\n        value.erase(0, token.find('=') + 1);\n        find->set_attributekey(std::move(key));\n        find->set_attributevalue(std::move(value));\n      } else {\n        return false;\n      }\n    } else if (s1 == \"--maxdepth\") {\n      if (!subtokenizer.NextToken(token)) {\n        return false;\n      }\n\n      if (token.length() > 0) {\n        try {\n          find->set_maxdepth(std::stoul(token));\n        } catch (std::invalid_argument& error) {\n          return false;\n        }\n      } else {\n        return false;\n      }\n    } else if (s1 == \"--purge\") {\n      std::string versions = subtokenizer.GetToken();\n\n      if (versions.length() > 0) {\n        try {\n          std::stoul(versions);\n        } catch (std::logic_error& err) {\n          if (versions != \"atomic\") {\n            return false;\n          }\n        }\n\n        find->set_purge(versions);\n      } else {\n        return false;\n      }\n\n      // @todo drop \"-name\" sometime later\n    } else if (s1 == \"--name\" || s1 == \"-name\") {\n      std::string filematch = subtokenizer.GetToken();\n\n      if (filematch.length() > 0) {\n        find->set_name(std::move(filematch));\n      } else {\n        return false;\n      }\n    } else if (s1 == \"--layoutstripes\") {\n      std::string stripes = subtokenizer.GetToken();\n\n      if (stripes.length() > 0) {\n        find->set_dolayoutstripes(true);\n        find->set_layoutstripes(std::stoul(stripes));\n      } else {\n        return false;\n      }\n    } else if (s1 == \"-p\") {\n      std::string printkey = subtokenizer.GetToken();\n\n      if (printkey.length() > 0) {\n        find->set_printkey(std::move(printkey));\n      } else {\n        return false;\n      }\n      // @todo(esindril) drop the single dash option at some point\n    } else if ((s1 == \"--ctime\") || (s1 == \"-ctime\") ||\n               (s1 == \"--mtime\") || (s1 == \"-mtime\")) {\n      XrdOucString period = \"\";\n      period = subtokenizer.GetToken();\n\n      if (period.length() > 0) {\n        bool do_olderthan = false;\n        bool do_youngerthan = false;\n\n        if (period.beginswith(\"+\")) {\n          do_olderthan = true;\n        } else if (period.beginswith(\"-\")) {\n          do_youngerthan = true;\n        }\n\n        if ((!do_olderthan) && (!do_youngerthan)) {\n          return false;\n        }\n\n        period.erase(0, 1);\n        time_t now = time(NULL);\n        now -= (86400 * strtoul(period.c_str(), 0, 10));\n        char snow[1024];\n        snprintf(snow, sizeof(snow) - 1, \"%lu\", now);\n\n        if ((s1 == \"--ctime\") || (s1 == \"-ctime\")) {\n          find->set_ctime(true);\n        } else if ((s1 == \"--mtime\") || (s1 == \"-mtime\")) {\n          find->set_mtime(true);\n        }\n\n        if (do_olderthan) {\n          try {\n            find->set_olderthan(std::stoul(snow));\n          } catch (std::invalid_argument& error) {\n            return false;\n          }\n        }\n\n        if (do_youngerthan) {\n          try {\n            find->set_youngerthan(std::stoul(snow));\n          } catch (std::invalid_argument& error) {\n            return false;\n          }\n        }\n      } else {\n        return false;\n      }\n    } else {\n      return false;\n    }\n  }\n\n  if (s1.length() > 0) {\n    auto path = s1;\n\n    if (!path.endswith(\"/\") && !path.endswith(\":\")) {\n      // if the user gave file: as a search path we shouldn't add '/'=root\n      path += \"/\";\n    }\n\n    path = abspath(path.c_str());\n    find->set_path(path.c_str());\n  } else {\n    return false;\n  }\n\n  return true;\n}\n\nint\nNewfindHelper::FindXroot(std::string path)\n{\n  XrdPosixXrootd Xroot;\n\n  if (path.rfind('/') != path.length() - 1) {\n    if (path.rfind(':') != path.length() - 1) {\n      // if the user gave file: as a search path we shouldn't add '/'=root\n      path += \"/\";\n    }\n  }\n\n  bool XRootD = path.find(\"root:\") == 0;\n  std::vector< std::vector<std::string> > found_dirs;\n  std::map<std::string, std::set<std::string> > found;\n  XrdOucString protocol;\n  XrdOucString hostport;\n  XrdOucString sPath;\n\n  if (path == \"/\") {\n    std::cerr << \"error: I won't do a find on '/'\" << std::endl;\n    return EINVAL;\n  }\n\n  const char* v = nullptr;\n\n  if (!(v = eos::common::StringConversion::ParseUrl(path.c_str(), protocol,\n            hostport))) {\n    return EINVAL;\n  }\n\n  sPath = v;\n  std::string Path = v;\n\n  if (sPath == \"\" && (protocol == \"file\")) {\n    sPath = getenv(\"PWD\");\n    Path = getenv(\"PWD\");\n\n    if (!sPath.endswith(\"/\")) {\n      sPath += \"/\";\n      Path += \"/\";\n    }\n  }\n\n  found_dirs.resize(1);\n  found_dirs[0].resize(1);\n  found_dirs[0][0] = Path.c_str();\n  int deepness = 0;\n\n  do {\n    struct stat buf;\n    found_dirs.resize(deepness + 2);\n\n    // loop over all directories in that deepness\n    for (unsigned int i = 0; i < found_dirs[deepness].size(); i++) {\n      Path = found_dirs[deepness][i].c_str();\n      XrdOucString url = \"\";\n      eos::common::StringConversion::CreateUrl(protocol.c_str(), hostport.c_str(),\n          Path.c_str(), url);\n      int rstat = 0;\n      rstat = (XRootD) ? XrdPosixXrootd::Stat(url.c_str(), &buf) : stat(url.c_str(),\n              &buf);\n\n      if (rstat == 0) {\n        //\n        if (S_ISDIR(buf.st_mode)) {\n          // add all children\n          DIR* dir = (XRootD) ? XrdPosixXrootd::Opendir(url.c_str()) : opendir(\n                       url.c_str());\n\n          if (dir != nullptr) {\n            struct dirent* entry;\n\n            while ((entry = (XRootD) ? XrdPosixXrootd::Readdir(dir) : readdir(dir))) {\n              XrdOucString curl = \"\";\n              XrdOucString cpath = Path.c_str();\n              cpath += entry->d_name;\n\n              if ((!strcmp(entry->d_name, \".\")) || (!strcmp(entry->d_name, \"..\"))) {\n                continue;  // skip . and .. directories\n              }\n\n              eos::common::StringConversion::CreateUrl(protocol.c_str(), hostport.c_str(),\n                  cpath.c_str(), curl);\n\n              if (!((XRootD) ? XrdPosixXrootd::Stat(curl.c_str(), &buf) : stat(curl.c_str(),\n                    &buf))) {\n                if (S_ISDIR(buf.st_mode)) {\n                  curl += \"/\";\n                  cpath += \"/\";\n                  found_dirs[deepness + 1].push_back(cpath.c_str());\n                  (void) found[curl.c_str()].size();\n                } else {\n                  found[url.c_str()].insert(entry->d_name);\n                }\n              }\n            }\n\n            (XRootD) ? XrdPosixXrootd::Closedir(dir) : closedir(dir);\n          }\n        }\n      }\n    }\n\n    deepness++;\n  } while (found_dirs[deepness].size());\n\n  for (const auto& it : found) {\n    std::cout << it.first << std::endl;\n\n    for (const auto& sit : it.second) {\n      std::cout << it.first << sit << std::endl;\n    }\n  }\n\n  return 0;\n}\n\nint\nNewfindHelper::FindAs3(std::string path)\n{\n  // ----------------------------------------------------------------\n  // this is nightmare code because of a missing proper CLI for S3\n  // ----------------------------------------------------------------\n  XrdOucString hostport;\n  XrdOucString protocol;\n  int rc = system(\"which s3 >&/dev/null\");\n\n  if (WEXITSTATUS(rc)) {\n    std::cerr <<\n              \"error: you miss the <s3> executable provided by libs3 in your PATH\" <<\n              std::endl;\n    exit(-1);\n  }\n\n  if (path.rfind('/') == path.length()) {\n    path.erase(path.length() - 1);\n  }\n\n  XrdOucString sPath = path.c_str();\n  XrdOucString sOpaque;\n  int qpos = 0;\n\n  if ((qpos = sPath.find(\"?\")) != STR_NPOS) {\n    sOpaque.assign(sPath, qpos + 1);\n    sPath.erase(qpos);\n  }\n\n  XrdOucString fPath = eos::common::StringConversion::ParseUrl(sPath.c_str(),\n                       protocol, hostport);\n  XrdOucEnv env(sOpaque.c_str());\n\n  if (env.Get(\"s3.key\")) {\n    setenv(\"S3_SECRET_ACCESS_KEY\", env.Get(\"s3.key\"), 1);\n  }\n\n  if (env.Get(\"s3.id\")) {\n    setenv(\"S3_ACCESS_KEY_ID\", env.Get(\"s3.id\"), 1);\n  }\n\n  // Apply the ROOT compatability environment variables\n  const char* cstr = getenv(\"S3_ACCESS_KEY\");\n\n  if (cstr) {\n    setenv(\"S3_SECRET_ACCESS_KEY\", cstr, 1);\n  }\n\n  cstr = getenv(\"S3_ACESSS_ID\");\n\n  if (cstr) {\n    setenv(\"S3_ACCESS_KEY_ID\", cstr, 1);\n  }\n\n  // check that the environment is set\n  if (!getenv(\"S3_ACCESS_KEY_ID\") ||\n      !getenv(\"S3_HOSTNAME\") ||\n      !getenv(\"S3_SECRET_ACCESS_KEY\")) {\n    std::cerr <<\n              \"error: you have to set the S3 environment variables S3_ACCESS_KEY_ID | S3_ACCESS_ID, S3_HOSTNAME (or use a URI), S3_SECRET_ACCESS_KEY | S3_ACCESS_KEY\"\n              << std::endl;\n    return EINVAL;\n  }\n\n  XrdOucString s3env;\n  s3env = \"env S3_ACCESS_KEY_ID=\";\n  s3env += getenv(\"S3_ACCESS_KEY_ID\");\n  s3env += \" S3_HOSTNAME=\";\n  s3env += getenv(\"S3_HOSTNAME\");\n  s3env += \" S3_SECRET_ACCESS_KEY=\";\n  s3env += getenv(\"S3_SECRET_ACCESS_KEY\");\n  XrdOucString cmd = \"bash -c \\\"\";\n  cmd += s3env;\n  cmd += \" s3 list \";\n  // extract bucket from path\n  int bpos = fPath.find(\"/\");\n  XrdOucString bucket;\n\n  if (bpos != STR_NPOS) {\n    bucket.assign(fPath, 0, bpos - 1);\n  } else {\n    bucket = fPath.c_str();\n  }\n\n  XrdOucString match;\n\n  if (bpos != STR_NPOS) {\n    match.assign(fPath, bpos + 1);\n  } else {\n    match = \"\";\n  }\n\n  if ((!bucket.length()) || (bucket.find(\"*\") != STR_NPOS)) {\n    std::cerr << \"error: no bucket specified or wildcard in bucket name!\" <<\n              std::endl;\n    return EINVAL;\n  }\n\n  cmd += bucket.c_str();\n  cmd += \" | awk '{print \\\\$1}' \";\n\n  if (match.length()) {\n    if (match.endswith(\"*\")) {\n      match.erase(match.length() - 1);\n      match.insert(\"^\", 0);\n    }\n\n    if (match.beginswith(\"*\")) {\n      match.erase(0, 1);\n      match += \"$\";\n    }\n\n    cmd += \" | egrep '\";\n    cmd += match.c_str();\n    cmd += \"'\";\n  }\n\n  cmd += \" | grep -v 'Bucket' | grep -v '\\\\-\\\\-\\\\-\\\\-\\\\-\\\\-\\\\-\\\\-\\\\-\\\\-' | grep -v 'Key' | awk -v prefix=\";\n  cmd += \"'\";\n  cmd += bucket.c_str();\n  cmd += \"' \";\n  cmd += \"'{print \\\\\\\"as3:\\\\\\\"prefix\\\\\\\"/\\\\\\\"\\\\$1}'\";\n  cmd += \"\\\"\";\n  rc = system(cmd.c_str());\n\n  if (WEXITSTATUS(rc)) {\n    std::cerr << \"error: failed to run \" << cmd << std::endl;\n    return rc;\n  }\n\n  return 0;\n}\n\n"
  },
  {
    "path": "console/commands/helpers/NewfindHelper.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file NewfindHelper.hh\n//! @author Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"console/commands/helpers/ICmdHelper.hh\"\n\n//------------------------------------------------------------------------------\n//! Class NewfindHelper\n//------------------------------------------------------------------------------\nclass NewfindHelper: public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  NewfindHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {\n    mIsAdmin = false;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~NewfindHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n\n  int FindXroot(std::string path);\n  int FindAs3(std::string path);\n\n  void Silent() { mIsSilent=true; }\n};\n"
  },
  {
    "path": "console/commands/helpers/NodeHelper.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file NodeHelper.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/commands/helpers/NodeHelper.hh\"\n#include \"common/StringTokenizer.hh\"\n#include <algorithm>\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool NodeHelper::ParseCommand(const char* arg)\n{\n  eos::console::NodeProto* node = mReq.mutable_node();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  std::string token;\n\n  if (!tokenizer.NextToken(token)) {\n    return false;\n  }\n\n  // one of { ls, set, status, txgw, proxygroupadd|proxygrouprm|proxygroupclear, rm, config, register }\n  if (token == \"ls\") {\n    eos::console::NodeProto_LsProto* ls = node->mutable_ls();\n\n    while (tokenizer.NextToken(token)) {\n      if (token == \"-s\") {\n        mIsSilent = true;\n      } else if (token == \"-b\" || token == \"--brief\") {\n        ls->set_outhost(true);\n      } else if (token == \"-m\") {\n        ls->set_outformat(eos::console::NodeProto_LsProto::MONITORING);\n      } else if (token == \"-l\") {\n        ls->set_outformat(eos::console::NodeProto_LsProto::LISTING);\n      } else if (token == \"--io\") {\n        ls->set_outformat(eos::console::NodeProto_LsProto::IO);\n      } else if (token == \"--sys\") {\n        ls->set_outformat(eos::console::NodeProto_LsProto::SYS);\n      } else if (token == \"--fsck\") {\n        ls->set_outformat(eos::console::NodeProto_LsProto::FSCK);\n      } else if ((token.find('-') != 0)) { // does not begin with \"-\"\n        ls->set_selection(token);\n      } else {\n        return false;\n      }\n    }\n  } else if (token == \"rm\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::NodeProto_RmProto* rm = node->mutable_rm();\n    rm->set_node(token);\n  } else if (token == \"status\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::NodeProto_StatusProto* status = node->mutable_status();\n    status->set_node(token);\n  } else if (token == \"set\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::NodeProto_SetProto* set = node->mutable_set();\n    set->set_node(token);\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"on\" || token == \"off\") {\n      set->set_node_state_switch(token);\n    } else {\n      return false;\n    }\n  } else if (token == \"config\") {\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::NodeProto_ConfigProto* config = node->mutable_config();\n    config->set_node_name(token);\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    std::string::size_type pos = token.find('=');\n\n    // contains 1 and only 1 '='. It expects a token like <key>=<value>\n    if ((pos != std::string::npos) &&\n        (std::count(token.begin(), token.end(), '=') == 1)) {\n      config->set_node_key(token.substr(0, pos));\n      config->set_node_value(token.substr(pos + 1, token.length() - 1));\n    } else {\n      return false;\n    }\n  } else if (token == \"proxygroupadd\" || token == \"proxygrouprm\" ||\n             token == \"proxygroupclear\") {\n    eos::console::NodeProto_ProxygroupProto* proxygroup =\n      node->mutable_proxygroup();\n\n    if (token == \"proxygroupadd\") {\n      proxygroup->set_node_action(eos::console::NodeProto_ProxygroupProto::ADD);\n    } else if (token == \"proxygrouprm\") {\n      proxygroup->set_node_action(eos::console::NodeProto_ProxygroupProto::RM);\n    } else if (token == \"proxygroupclear\") {\n      proxygroup->set_node_action(eos::console::NodeProto_ProxygroupProto::CLEAR);\n    }\n\n    if (token == \"proxygroupclear\") {\n      if (tokenizer.NextToken(token)) {\n        proxygroup->set_node(token);\n      } else {\n        return false;\n      }\n    } else {\n      if (tokenizer.NextToken(token)) {\n        proxygroup->set_node_proxygroup(token);\n\n        if (tokenizer.NextToken(token)) {\n          proxygroup->set_node(token);\n        } else {\n          return false;\n        }\n      } else {\n        return false;\n      }\n    }\n  } else { // no proper subcommand\n    return false;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "console/commands/helpers/NodeHelper.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file NodeHelper.hh\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"console/commands/helpers/ICmdHelper.hh\"\n\n//------------------------------------------------------------------------------\n//! Class NodeHelper\n//------------------------------------------------------------------------------\nclass NodeHelper : public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  NodeHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {\n    mIsAdmin = true;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~NodeHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n};\n"
  },
  {
    "path": "console/commands/helpers/RecycleHelper.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file RecycleHelper.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/commands/helpers/RecycleHelper.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringConversion.hh\"\n\n//------------------------------------------------------------------------------\n// Check if given date format respects the specifications\n//------------------------------------------------------------------------------\nbool\nRecycleHelper::CheckDateFormat(const std::string& sdate) const\n{\n  using eos::common::StringConversion;\n\n  if (sdate.find('/') != std::string::npos) {\n    std::vector<std::string> tokens;\n    StringConversion::Tokenize(sdate, tokens, \"/\");\n\n    if (tokens.size() > 4) {\n      return false;\n    }\n\n    // All tokens must be numeric\n    for (const auto& token : tokens) {\n      try {\n        (void) std::stoi(token);\n      } catch (...) {\n        return false;\n      }\n    }\n  } else {\n    try {\n      (void) std::stoi(sdate);\n    } catch (...) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool\nRecycleHelper::ParseCommand(const char* arg)\n{\n  const char* option {nullptr};\n  std::string soption;\n  eos::console::RecycleProto* recycle = mReq.mutable_recycle();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  option = tokenizer.GetToken();\n  std::string cmd = (option ? option : \"\");\n\n  if ((cmd == \"ls\") || cmd.empty() || (cmd == \"-m\")) {\n    eos::console::RecycleProto_LsProto* ls = recycle->mutable_ls();\n    ls->set_type(eos::console::RecycleProto::UID);\n\n    if (cmd.empty()) {\n      ls->set_type(eos::console::RecycleProto::ALL);\n    } else if (cmd == \"-m\") {\n      ls->set_monitorfmt(true);\n    } else {\n      ls->set_fulldetails(true);\n\n      while ((option = tokenizer.GetToken())) {\n        soption = option;\n\n        if (soption == \"--all\") {\n          ls->set_type(eos::console::RecycleProto::ALL);\n        } else if (soption == \"--uid\") {\n          ls->set_type(eos::console::RecycleProto::UID);\n        } else if (soption == \"--rid\") {\n          ls->set_type(eos::console::RecycleProto::RID);\n\n          // Get recycle id value\n          if ((option = tokenizer.GetToken())) {\n            soption = option;\n            ls->set_recycleid(soption);\n          }\n        } else if (soption == \"-m\") {\n          ls->set_monitorfmt(true);\n        } else if (soption == \"-n\") {\n          ls->set_numericids(true);\n        } else {\n          // This must be a date format\n          if (!CheckDateFormat(soption)) {\n            std::cerr << \"error: \\\"\" << soption << \"\\\" does not respect the \"\n                      << \"date format\" << std::endl;\n            return false;\n          }\n\n          ls->set_date(soption);\n\n          // check if a limit is given;\n          if ((option = tokenizer.GetToken())) {\n            soption = option;\n            ls->set_maxentries(atoi(soption.c_str()));\n          }\n        }\n      }\n    }\n  } else if (cmd == \"purge\") {\n    eos::console::RecycleProto_PurgeProto* purge = recycle->mutable_purge();\n    purge->set_type(eos::console::RecycleProto::UID);\n\n    while ((option = tokenizer.GetToken())) {\n      soption = option;\n\n      if (soption == \"--all\") {\n        purge->set_type(eos::console::RecycleProto::ALL);\n      } else if (soption == \"--uid\") {\n        purge->set_type(eos::console::RecycleProto::UID);\n      } else if (soption == \"--rid\") {\n        if (!(option = tokenizer.GetToken())) {\n          // Parse the recycle id value\n          std::cerr << \"error: need to specify a recycle id after --rid option\"\n                    << std::endl;\n          return false;\n        }\n\n        soption = option;\n        purge->set_type(eos::console::RecycleProto::RID);\n\n        // Make sure this is a number\n        try {\n          (void) std::stoull(soption);\n        } catch (...) {\n          std::cerr << \"error: recycle id needs to be numeric\" << std::endl;\n          return false;\n        }\n\n        purge->set_recycleid(soption);\n      } else if (soption == \"-k\") {\n        if (!(option = tokenizer.GetToken())) {\n          std::cerr << \"error: you have to provide a key when using the -k option\" <<\n                    std::endl;\n          return false;\n        }\n\n        purge->set_key(option);\n      } else { // This must be a date\n        if (!CheckDateFormat(soption)) {\n          std::cerr << \"error: \\\"\" << soption << \"\\\" does not respect the \"\n                    << \"date format\" << std::endl;\n          return false;\n        }\n\n        purge->set_date(soption);\n      }\n    }\n\n    if (!purge->date().empty() && !purge->key().empty()) {\n      std::cerr << \"error: recycle key and date can not be used together\"\n                << std::endl;\n      return false;\n    }\n  } else if (cmd == \"restore\") {\n    eos::console::RecycleProto_RestoreProto* restore = recycle->mutable_restore();\n\n    while ((option = tokenizer.GetToken())) {\n      soption = option;\n\n      if ((soption == \"-f\") || (soption == \"--force-original-name\")) {\n        restore->set_forceorigname(true);\n      } else if ((soption == \"-r\") || (soption == \"--restore-versions\")) {\n        restore->set_restoreversions(true);\n      } else if (soption == \"-p\") {\n        restore->set_makepath(true);\n      } else {\n        // This must be the recycle-key\n        restore->set_key(soption);\n        break;\n      }\n    }\n\n    if (restore->key().empty()) {\n      return false;\n    }\n  } else if (cmd == \"project\") {\n    eos::console::RecycleProto_ProjectProto* project = recycle->mutable_project();\n\n    while ((option = tokenizer.GetToken())) {\n      soption = option;\n\n      if (soption == \"--path\") {\n        if (!(option = tokenizer.GetToken())) {\n          return false;\n        }\n\n        soption = option;\n\n        if (soption.empty() && soption[0] != '/') {\n          std::cerr << \"error: path specification is not supported\" << std::endl;\n          return false;\n        }\n\n        project->set_path(soption);\n      } else if (soption == \"--acl\") {\n        if (!(option = tokenizer.GetToken())) {\n          return false;\n        }\n\n        soption = option;\n        project->set_acl(soption);\n      } else {\n        std::cerr << \"error: unknown option \\\"\" << soption << \"\\\"\" << std::endl;\n        return false;\n      }\n\n      if (project->path().empty()) {\n        return false;\n      }\n    }\n  } else if (cmd == \"config\") {\n    eos::console::RecycleProto_ConfigProto* config = recycle->mutable_config();\n\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n\n    soption = option;\n\n    if ((soption == \"--add-bin\") || (soption == \"--remove-bin\")) {\n      if (soption == \"--add-bin\") {\n        config->set_op(eos::console::RecycleProto_ConfigProto::ADD_BIN);\n      } else {\n        config->set_op(eos::console::RecycleProto_ConfigProto::RM_BIN);\n      }\n\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n\n      config->set_subtree(option);\n    } else if (soption == \"--lifetime\") {\n      config->set_op(eos::console::RecycleProto_ConfigProto::LIFETIME);\n\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n\n      soption = option;\n      int lifetime = eos::common::StringConversion::GetSizeFromString(\n                       soption.c_str());\n      config->set_lifetimesec(lifetime);\n    } else if (soption == \"--ratio\") {\n      config->set_op(eos::console::RecycleProto_ConfigProto::RATIO);\n\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n\n      soption = option;\n      float ratio = 0.0;\n\n      try {\n        ratio = std::stof(soption);\n      } catch (...) {\n        return false;\n      }\n\n      config->set_ratio(ratio);\n    } else if (soption == \"--size\") {\n      config->set_op(eos::console::RecycleProto::ConfigProto::SIZE);\n\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n\n      soption = option;\n      std::set<char> units {'K', 'M', 'G'};\n      uint64_t size = eos::common::StringConversion::GetSizeFromString(soption);\n\n      if (errno) {\n        std::cerr << \"error: specified size could not be converted\" << std::endl;\n        return false;\n      }\n\n      config->set_size(size);\n    } else if (soption == \"--inodes\") {\n      config->set_op(eos::console::RecycleProto::ConfigProto::INODES);\n\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n\n      soption = option;\n      std::set<char> units {'K', 'M', 'G'};\n      uint64_t size = eos::common::StringConversion::GetSizeFromString(soption);\n\n      if (errno) {\n        std::cerr << \"error: specified number of inodes could not be converted\"\n                  << std::endl;\n        return false;\n      }\n\n      config->set_size(size);\n    } else if ((soption == \"--collect-interval\") ||\n               (soption == \"--remove-interval\")) {\n      if (soption == \"--collect-interval\") {\n        config->set_op(eos::console::RecycleProto::ConfigProto::COLLECT_INTERVAL);\n      } else {\n        config->set_op(eos::console::RecycleProto::ConfigProto::REMOVE_INTERVAL);\n      }\n\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n\n      soption = option;\n      uint64_t value = 0ull;\n\n      try {\n        value = std::stoull(soption);\n      } catch (...) {\n        std::cerr << \"error: specified intrerval could not be converted\"\n                  << std::endl;\n        return false;\n      }\n\n      config->set_size(value);\n    } else if (soption == \"--dry-run\") {\n      config->set_op(eos::console::RecycleProto::ConfigProto::DRY_RUN);\n\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n\n      config->set_value(option);\n    } else if (soption == \"--enforce\") {\n      config->set_op(eos::console::RecycleProto::ConfigProto::ENFORCE);\n\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n\n      config->set_value(option);\n    } else if (soption == \"--enable\") {\n      config->set_op(eos::console::RecycleProto::ConfigProto::ENABLE);\n\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n\n      config->set_value(option);\n    } else if ((soption == \"--dump\")) {\n      config->set_op(eos::console::RecycleProto::ConfigProto::DUMP);\n    } else {\n      return false;\n    }\n  } else {\n    return false;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "console/commands/helpers/RecycleHelper.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file RecycleHelper.hh\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"console/commands/helpers/ICmdHelper.hh\"\n\n//------------------------------------------------------------------------------\n//! Class RecycleHelper\n//------------------------------------------------------------------------------\nclass RecycleHelper: public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  RecycleHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~RecycleHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Check if given date format respects the specifications\n  //!\n  //! @return true if ok, otherwise false\n  //----------------------------------------------------------------------------\n  bool CheckDateFormat(const std::string& sdate) const;\n};\n"
  },
  {
    "path": "console/commands/helpers/TokenHelper.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file TokenHelper.cc\n//! @author Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/commands/helpers/TokenHelper.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/FileId.hh\"\n#include \"common/Path.hh\"\n\n//------------------------------------------------------------------------------\n// Parse command line input\n//------------------------------------------------------------------------------\nbool\nTokenHelper::ParseCommand(const char* arg)\n{\n  const char* option;\n  std::string soption;\n  eos::console::TokenProto* token = mReq.mutable_token();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  std::map<std::string, std::set<std::string>> args;\n\n  do {\n    std::string element;\n    option = tokenizer.GetTokenUnquoted();\n\n    if (option) {\n      element = option;\n    } else {\n      break;\n    }\n\n    if (element.empty()) {\n      break;\n    }\n\n    if (element.substr(0, 2) == \"--\") {\n      element.erase(0, 2);\n\n      if (element != \"tree\") {\n        option = tokenizer.GetTokenUnquoted();\n\n        if (option) {\n          std::string value = option;\n          args[element].insert(value);\n        }\n      } else {\n        args[element].insert(\"dummy\");\n      }\n    }\n  } while (1);\n\n  if (args.count(\"token\")) {\n    // this is a show token request\n    token->set_vtoken(*(args[\"token\"].begin()));\n  } else {\n    bool isdir = false;\n\n    if (\n      !args.count(\"path\")\n    ) {\n      return false;\n    } else {\n      if (args[\"path\"].begin()->back() == '/') {\n        isdir = true;\n      }\n    }\n\n    eos::common::Path cPath(*args[\"path\"].begin(), true);\n\n    if (!args.count(\"permission\")) {\n      args[\"permission\"].insert(\"rx\");\n    }\n\n    token->set_path(std::string(cPath.GetPath()) + (isdir ? \"/\" : \"\"));\n    token->set_permission(*args[\"permission\"].begin());\n\n    if (args.count(\"expires\")) {\n      token->set_expires(strtoull((args[\"expires\"].begin())->c_str(), 0, 10));\n    } else {\n      // ask by default for 5 min token\n      token->set_expires(time(NULL) + 300);\n    }\n\n    if (args.count(\"owner\")) {\n      token->set_owner(*args[\"owner\"].begin());\n    }\n\n    if (args.count(\"group\")) {\n      token->set_group(*args[\"group\"].begin());\n    }\n\n    if (args.count(\"tree\")) {\n      token->set_allowtree(true);\n    }\n\n    if (args.count(\"origin\")) {\n      for (auto it = args[\"origin\"].begin(); it != args[\"origin\"].end(); ++it) {\n        std::vector<std::string> info;\n        eos::common::StringConversion::Tokenize(*it, info, \"#\");\n        eos::console::TokenAuth* auth = token->add_origins();\n\n        if (info.size() > 0) {\n          auth->set_host(info[0]);\n\n          if (info.size() > 1) {\n            auth->set_name(info[1]);\n\n            if (info.size() > 2) {\n              auth->set_prot(info[2]);\n            } else {\n              auth->set_prot(\"(.*)\");\n            }\n          } else {\n            auth->set_name(\"(.*)\");\n            auth->set_prot(\"(.*)\");\n          }\n        }\n      }\n    }\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "console/commands/helpers/TokenHelper.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file TokenHelper.hh\n//! @author Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"console/commands/helpers/ICmdHelper.hh\"\n\n//------------------------------------------------------------------------------\n//! Class TokenHelper\n//------------------------------------------------------------------------------\nclass TokenHelper: public ICmdHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param opts global options\n  //----------------------------------------------------------------------------\n  TokenHelper(const GlobalOptions& opts):\n    ICmdHelper(opts)\n  {\n    mIsAdmin = false;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~TokenHelper() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Parse command line input\n  //!\n  //! @param arg input\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseCommand(const char* arg) override;\n};\n"
  },
  {
    "path": "console/commands/helpers/jwk_generator/c_resource.hpp",
    "content": "#pragma once\n#include <memory>\n#include <functional>\n#include <type_traits>\n#include <stdexcept>\n\ntemplate <typename T, auto deleter, auto allocator = nullptr>\nclass c_resource\n{\nprivate:\n  std::unique_ptr<T, decltype(deleter)> ptr;\npublic:\n  c_resource(std::function<T*()> creater) : ptr{creater(), deleter} {}\n  c_resource(T* ptr) : ptr{ptr, deleter} {}\n  c_resource() : ptr {nullptr, deleter} {}\n\n  static c_resource allocate()\n  {\n    static_assert(not std::is_null_pointer_v<decltype(allocator)>,\n                  \"allocate should not be used without a defined allocator\");\n    return c_resource(allocator);\n  }\n\n  operator bool() const\n  {\n    return ptr;\n  }\n\n  auto release()\n  {\n    return ptr.release();\n  }\n\n  auto get()\n  {\n    return ptr.get();\n  }\n\n  auto get() const\n  {\n    return ptr.get();\n  }\n\n  operator T* ()\n  {\n    return get();\n  }\n\n  operator const T* () const\n  {\n    return get();\n  }\n};\n"
  },
  {
    "path": "console/commands/helpers/jwk_generator/errors.hpp",
    "content": "#pragma once\n#include <openssl/err.h>\n#include <string>\n#include <stdexcept>\n\nnamespace jwk_generator\n{\nstruct openssl_error: public std::runtime_error {\nprivate:\n  static inline std::string open_ssl_last_error()\n  {\n    int err = ERR_get_error();\n    char errStr[256];\n    ERR_error_string(err, errStr);\n    return std::string(errStr);\n  }\npublic:\n  openssl_error(std::string what) : std::runtime_error(what +\n        open_ssl_last_error()) {}\n};\n};\n\n"
  },
  {
    "path": "console/commands/helpers/jwk_generator/jwk_generator.hpp",
    "content": "#pragma once\n\n#include <sstream>\n#include <stdexcept>\n#include <string>\n#include <memory>\n#include <tuple>\n#include <stdint.h>\n\n#include <openssl/bio.h>\n#include <openssl/pem.h>\n\n#include \"jwk_generator//libs/base64_url.hpp\"\n#include \"jwk_generator/libs/uuid.hpp\"\n#include \"jwk_generator/libs/json.hpp\"\n#include \"jwk_generator/errors.hpp\"\n#include \"jwk_generator/keyspecs/ec_key.hpp\"\n//#include \"jwk_generator/keyspecs/rsa_key.hpp\"\n\nnamespace jwk_generator\n{\ntemplate <typename KeySpec>\nclass JwkGenerator\n{\npublic:\n  KeySpec key;\n  std::string kid;\n\nprivate:\n  std::string to_pem(std::function<int(BIO*, EVP_PKEY*)> writeKeyToBIO) const\n  {\n    using namespace detail;\n#ifdef JWKGEN_OPENSSL_1_0\n    auto pemKeyBIO = std::shared_ptr<BIO>(BIO_new(BIO_s_mem()), BIO_free);\n#else\n    auto pemKeyBIO = std::shared_ptr<BIO>(BIO_new(BIO_s_secmem()), BIO_free);\n#endif\n\n    if (!pemKeyBIO) {\n      throw openssl_error(\"Unable to retrieve public key: \");\n    }\n\n    EVP_PKEY* tmpEVP = key.keyPair.get();\n    int result = writeKeyToBIO(pemKeyBIO.get(), tmpEVP);\n\n    if (!result) {\n      throw openssl_error(\"Unable to convert key to pem: \");\n    }\n\n    char* buffer;\n    auto len = BIO_get_mem_data(pemKeyBIO.get(), &buffer);\n\n    if (!len) {\n      throw openssl_error(\"Unable to retrieve key from bio: \");\n    }\n\n    std::string pem;\n    pem.resize(len);\n    std::memcpy(pem.data(), buffer, len);\n    return pem;\n  }\n\npublic:\n  JwkGenerator(const JwkGenerator&) = delete;\n  JwkGenerator& operator = (const JwkGenerator&) = delete;\n  JwkGenerator(JwkGenerator&&) = default;\n  JwkGenerator& operator = (JwkGenerator&&) = default;\n\n  JwkGenerator()\n  {\n    kid = detail::generate_uuid_v4();\n  }\n\n  JwkGenerator(const std::string& kid_uuid,\n               const std::string& fn_public = \"\",\n               const std::string& fn_private = \"\"):\n    kid(kid_uuid)\n  {\n    if (!fn_public.empty() && !fn_private.empty()) {\n      key = KeySpec(fn_public, fn_private);\n    }\n  }\n\n  std::string private_to_pem() const\n  {\n    return to_pem([](auto bio, auto key) {\n      return PEM_write_bio_PrivateKey(bio, key, NULL, NULL, 0, 0, NULL);\n    });\n  }\n\n  std::string public_to_pem() const\n  {\n    return to_pem([](auto bio, auto key) {\n      return PEM_write_bio_PUBKEY(bio, key);\n    });\n  }\n\n  nlohmann::json to_json() const\n  {\n    nlohmann::json json;\n    key.insert_json(json);\n    json[\"kid\"] = kid;\n    return json;\n  }\n\n  std::string to_pretty_string() const\n  {\n    return to_json().dump(true);\n  }\n\n  operator std::string() const\n  {\n    return to_json().dump();\n  }\n\n  friend std::ostream& operator<< (std::ostream& out, const JwkGenerator& e)\n  {\n    out << std::string(e);\n    return out;\n  }\n};\n\ntemplate <typename... KeySpec>\nclass JwkSetGenerator\n{\nprivate:\n  JwkSetGenerator(JwkSetGenerator&) = delete;\n\npublic:\n  JwkSetGenerator() = default;\n  JwkSetGenerator(std::tuple<JwkGenerator<KeySpec>...>&& keys) : keys{keys} { }\n\n  const std::tuple<JwkGenerator<KeySpec>...> keys;\n\n  nlohmann::json to_json() const\n  {\n    using namespace nlohmann;\n    nlohmann::json jwks;\n    std::apply([&jwks](const auto & ... jwk) {\n      jwks[\"keys\"] = std::array < nlohmann::json, std::tuple_size<decltype(keys)> {} > {jwk.to_json()...};\n    }, keys);\n    return jwks;\n  }\n\n  template <size_t idx>\n  const auto& get() const\n  {\n    return std::get<idx>(keys);\n  }\n\n  operator std::string() const\n  {\n    return to_json().dump();\n  }\n\n  friend std::ostream& operator<< (std::ostream& out, const JwkSetGenerator& e)\n  {\n    out << std::string(e);\n    return out;\n  }\n};\n\ntemplate <typename KeySpec, template < class ... > class Container, class ... Args >\nclass JwkSetSingleSpecGenerator\n{\nprivate:\n  JwkSetSingleSpecGenerator(JwkSetSingleSpecGenerator&) = delete;\npublic:\n  JwkSetSingleSpecGenerator() = default;\n  JwkSetSingleSpecGenerator(Container<JwkGenerator<KeySpec>, Args...>&& keys) :\n    keys{std::move(keys)} { }\n  Container<JwkGenerator<KeySpec>, Args...> keys;\n\n  nlohmann::json to_json() const\n  {\n    using namespace nlohmann;\n    nlohmann::json jwks;\n    jwks[\"keys\"] = json::array();\n\n    for (const auto& jwk : keys) {\n      jwks[\"keys\"].push_back(jwk.to_json());\n    }\n\n    return jwks;\n  }\n\n  const JwkGenerator<KeySpec>& operator[](size_t idx) const\n  {\n    return keys[idx];\n  }\n\n  template <size_t idx>\n  const JwkGenerator<KeySpec>& get() const\n  {\n    return keys[idx];\n  }\n\n  operator std::string() const\n  {\n    return to_json().dump();\n  }\n\n  friend std::ostream& operator<< (std::ostream& out,\n                                   const JwkSetSingleSpecGenerator& e)\n  {\n    out << std::string(e);\n    return out;\n  }\n};\n\ntemplate<typename KeySpec>\nstatic auto make_jwks(size_t nKeys)\n{\n  std::vector<JwkGenerator<KeySpec>> keys;\n  keys.resize(nKeys);\n  return JwkSetSingleSpecGenerator(std::move(keys));\n}\n\n};\n"
  },
  {
    "path": "console/commands/helpers/jwk_generator/keyspecs/ec_key.hpp",
    "content": "#pragma once\n\n#include \"jwk_generator/libs/json.hpp\"\n#include \"jwk_generator/errors.hpp\"\n#include \"jwk_generator/openssl_wrapper.hpp\"\n\nnamespace\n{\n//----------------------------------------------------------------------------\n//! Load public/private key in the corresponding data structure\n//!\n//! @param key EVP_PKEY data structure to be populated\n//! @param fn_public path to the file containing the public key\n//! @param fn_private path to the file containing the private key\n//!\n//! @return true if successful, otherwise false\n//----------------------------------------------------------------------------\nbool load_key_from_file(EVP_PKEY*& priv_key,\n                        const std::string& fn_public,\n                        const std::string& fn_private)\n{\n  // Load the private key\n  FILE* priv_file = fopen(fn_private.c_str(), \"r\");\n\n  if (!priv_file) {\n    std::cerr << \"error: failed to open file \" << fn_private << std::endl;\n    return false;\n  }\n\n  // Read the private keydata\n  priv_key = PEM_read_PrivateKey(priv_file, nullptr, nullptr, nullptr);\n  fclose(priv_file);\n\n  if (!priv_key) {\n    std::cerr << \"error: failed to read private key\" << std::endl;\n    return false;\n  }\n\n  // Ensure the key is of type EC\n  if (EVP_PKEY_base_id(priv_key) != EVP_PKEY_EC) {\n    std::cerr << \"error: private key is not an EC key.\" << std::endl;\n    EVP_PKEY_free(priv_key);\n    return false;\n  }\n\n  // Load the public key\n  FILE* pub_file = fopen(fn_public.c_str(), \"r\");\n\n  if (!pub_file) {\n    std::cerr << \"error: failed to open file \" << fn_public << std::endl;\n    return false;\n  }\n\n  // Read the public key\n  EVP_PKEY* pub_key = PEM_read_PUBKEY(pub_file, nullptr, nullptr, nullptr);\n  fclose(pub_file);\n\n  if (!pub_key) {\n    std::cerr << \"error: failed to read public key\" << std::endl;\n    EVP_PKEY_free(pub_key);\n    return false;\n  }\n\n  // Ensure the public key if of type EC\n  if (EVP_PKEY_base_id(pub_key) != EVP_PKEY_EC) {\n    std::cerr << \"erro: public key is not EC type\" << std::endl;\n    EVP_PKEY_free(pub_key);\n    EVP_PKEY_free(priv_key);\n    return false;\n  }\n\n  // Extract the EC_KEY from EVP_PKEY structures\n  EC_KEY* ec_priv_key = EVP_PKEY_get1_EC_KEY(priv_key);\n  EC_KEY* ec_pub_key = EVP_PKEY_get1_EC_KEY(pub_key);\n\n  if (!ec_priv_key || !ec_pub_key) {\n    std::cerr << \"error: failed to extract EC_KEY from pub/priv key\" << std::endl;\n    EVP_PKEY_free(pub_key);\n    EVP_PKEY_free(priv_key);\n    return false;\n  }\n\n  const EC_POINT* pub_point = EC_KEY_get0_public_key(ec_pub_key);\n\n  if (!pub_point) {\n    std::cerr << \"error: failed to retrieve public key or group\" << std::endl;\n    EC_KEY_free(ec_priv_key);\n    EC_KEY_free(ec_pub_key);\n    EVP_PKEY_free(pub_key);\n    EVP_PKEY_free(priv_key);\n    return false;\n  }\n\n  // Associate the public key with the private key\n  if (EC_KEY_set_public_key(ec_priv_key, pub_point) != 1) {\n    std::cerr << \"error: failed to set the public key\" << std::endl;\n    EVP_PKEY_free(priv_key);\n    EC_KEY_free(ec_priv_key);\n    EVP_PKEY_free(pub_key);\n    return false;\n  }\n\n  // Clean up\n  EC_KEY_free(ec_pub_key);\n  EC_KEY_free(ec_priv_key);\n  EVP_PKEY_free(pub_key);\n  EVP_cleanup();\n  return true;\n}\n}\n\nnamespace jwk_generator\n{\ntemplate<size_t shaBits>\nclass ECKey\n{\nprivate:\n#ifdef JWKGEN_OPENSSL_3_0\n  static constexpr const char* ecdsa_bit_to_curve()\n  {\n    switch (shaBits) {\n    case 256: {\n      return SN_X9_62_prime256v1;\n    }\n\n    case 512: {\n      return SN_secp521r1;\n    }\n\n    case 384: {\n      return SN_secp384r1;\n    }\n    }\n\n    throw std::runtime_error(\"Unsupported EC algorithm\");\n  }\n#else\n  static constexpr int ecdsa_bit_to_curve()\n  {\n    switch (shaBits) {\n    case 256: {\n      return NID_X9_62_prime256v1;\n    }\n\n    case 512: {\n      return NID_secp521r1;\n    }\n\n    case 384: {\n      return NID_secp384r1;\n    }\n    }\n\n    throw std::runtime_error(\"Unsupported EC algorithm\");\n  }\n#endif\n\n  static constexpr size_t bits_to_point_size()\n  {\n    if (shaBits == 256) {\n      return 32;\n    } else if (shaBits == 512) {\n      return 66;\n    } else if (shaBits == 384) {\n      return 48;\n    } else {\n      throw std::runtime_error(\"Unsupported EC algorithm\");\n    }\n  }\npublic:\n  static constexpr size_t pointSize = bits_to_point_size();\n  openssl::EVPPKey keyPair;\n  std::string pointX;\n  std::string pointY;\n\n  ECKey(const ECKey&) = delete;\n  ECKey& operator = (const ECKey&) = delete;\n  ECKey(ECKey&&) = default;\n  ECKey& operator = (ECKey&&) = default;\n\n  //------------------------------------------------------------------------\n  //! Constructor reading public and private key part from files\n  //!\n  //! @param fn_public file holding the public key\n  //! @param fn_private file holding the private key\n  //!\n  //------------------------------------------------------------------------\n  ECKey(const std::string& fn_public, const std::string& fn_private)\n  {\n    using namespace detail;\n    EVP_PKEY* key = nullptr;\n\n    if (!load_key_from_file(key, fn_public, fn_private)) {\n      throw std::runtime_error(\"error: failed to load keys from file\");\n    }\n\n    keyPair = openssl::EVPPKey(key);\n#ifdef JWKGEN_OPENSSL_3_0\n    BIGNUM* xBN = nullptr;\n\n    if (!EVP_PKEY_get_bn_param(key, OSSL_PKEY_PARAM_EC_PUB_X, &xBN)) {\n      throw openssl_error(\"Unable to extract coordinates key: \");\n    }\n\n    BIGNUM* yBN = nullptr;\n\n    if (!EVP_PKEY_get_bn_param(key, OSSL_PKEY_PARAM_EC_PUB_Y, &yBN)) {\n      throw openssl_error(\"Unable extract coordinates from key: \");\n    }\n\n    std::vector<uint8_t> xBin;\n    xBin.resize(pointSize);\n    BN_bn2binpad(xBN, xBin.data(), pointSize);\n    pointX = base64_url_encode(xBin);\n    std::vector<uint8_t> yBin;\n    yBin.resize(pointSize);\n    BN_bn2binpad(yBN, yBin.data(), pointSize);\n    pointY = base64_url_encode(yBin);\n#else\n    std::cout << \"warning: do extraction with openssl < 3.0.0\" << std::endl;\n#endif\n  }\n\n\n  ECKey()\n  {\n    using namespace detail;\n#ifdef JWKGEN_OPENSSL_3_0\n    keyPair = {[]()\n    {\n      return EVP_EC_gen(ecdsa_bit_to_curve());\n    }\n              };\n\n    if (!keyPair) {\n      throw openssl_error(\"Unable to generate ec key: \");\n    }\n\n    BIGNUM* xBN = nullptr;\n\n    if (!EVP_PKEY_get_bn_param(keyPair, OSSL_PKEY_PARAM_EC_PUB_X, &xBN)) {\n      throw openssl_error(\"Unable to extract coordinates key: \");\n    }\n\n    BIGNUM* yBN = nullptr;\n\n    if (!EVP_PKEY_get_bn_param(keyPair, OSSL_PKEY_PARAM_EC_PUB_Y, &yBN)) {\n      throw openssl_error(\"Unable extract coordinates from key: \");\n    }\n\n#else\n    auto group = openssl::ECGroup([]() {\n      return EC_GROUP_new_by_curve_name(ecdsa_bit_to_curve());\n    });\n    auto ec = openssl::ECKey::allocate();\n\n    if (!EC_KEY_set_group(ec, group)) {\n      throw openssl_error(\"Unable to generate ec key: \");\n    }\n\n    if (!EC_KEY_generate_key(ec)) {\n      throw openssl_error(\"Unable to generate ec key: \");\n    }\n\n    keyPair = openssl::EVPPKey::allocate();\n\n    if (!keyPair) {\n      throw openssl_error(\"Unable to generate ec key: \");\n    }\n\n    // release it now as ownership transfers to the key pair\n    EC_KEY* ecPtr = ec.release();\n\n    if (!EVP_PKEY_assign_EC_KEY(keyPair, ecPtr)) {\n      throw openssl_error(\"Unable to generate ec key: \");\n    }\n\n    openssl::BigNum xBN = openssl::BigNum::allocate();\n\n    if (!xBN) {\n      throw openssl_error(\"Unable to allocate BN: \");\n    }\n\n    openssl::BigNum yBN = openssl::BigNum::allocate();\n\n    if (!yBN) {\n      throw openssl_error(\"Unable to allocate BN: \");\n    }\n\n    auto point = EC_KEY_get0_public_key(ecPtr);\n#ifdef JWKGEN_OPENSSL_1_0\n\n    if (!EC_POINT_get_affine_coordinates_GFp(group, point, xBN, yBN, NULL)) {\n#else\n\n    if (!EC_POINT_get_affine_coordinates(group, point, xBN, yBN, NULL)) {\n#endif\n      throw openssl_error(\"Unable to extract coordinates from key: \");\n    }\n\n#endif\n    std::vector<uint8_t> xBin;\n    xBin.resize(pointSize);\n    //BN_bn2binpad(xBN, xBin.data(), pointSize);\n    BN_bn2bin(xBN, xBin.data());\n    pointX = base64_url_encode(xBin);\n    std::vector<uint8_t> yBin;\n    yBin.resize(pointSize);\n    //BN_bn2binpad(yBN, yBin.data(), pointSize);\n    BN_bn2bin(yBN, yBin.data());\n    pointY = base64_url_encode(yBin);\n  }\n\n  void insert_json(nlohmann::json& json) const\n  {\n    std::string crv = std::string(\"P-\") + std::to_string(shaBits);\n\n    if (shaBits == 512) {\n      crv = \"P-521\";\n    }\n\n    json[\"alg\"] = \"ES\" + std::to_string(shaBits);\n    json[\"kty\"] = \"EC\";\n    json[\"x\"] = pointX;\n    json[\"y\"] = pointY;\n    json[\"crv\"] = crv;\n  }\n};\n\nusing ES256 = ECKey<256>;\nusing ES384 = ECKey<384>;\nusing ES512 = ECKey<512>;\n};\n"
  },
  {
    "path": "console/commands/helpers/jwk_generator/keyspecs/rsa_key.hpp",
    "content": "#pragma once\n\n#include \"jwk_generator/libs/json.hpp\"\n#include \"jwk_generator/errors.hpp\"\n#include \"jwk_generator/openssl_wrapper.hpp\"\n\nnamespace jwk_generator\n{\ntemplate<size_t shaBits>\nstruct RSAKey {\npublic:\n  static constexpr const size_t nBits = 2048;\n  openssl::EVPPKey keyPair;\n  std::string modulous;\n  std::string exponent;\n\n  RSAKey(const RSAKey&) = delete;\n  RSAKey& operator = (const RSAKey&) = delete;\n  RSAKey(RSAKey&&) = default;\n  RSAKey& operator = (RSAKey&&) = default;\n  RSAKey()\n  {\n    using namespace detail;\n#ifdef JWKGEN_OPENSSL_3_0\n    keyPair = {[]()\n    {\n      return EVP_RSA_gen(nBits);\n    }\n              };\n\n    if (!keyPair) {\n      throw openssl_error(\"Unable to generate rsa key: \");\n    }\n\n    BIGNUM* modBN = nullptr;\n\n    if (!EVP_PKEY_get_bn_param(keyPair, OSSL_PKEY_PARAM_RSA_N, &modBN)) {\n      throw openssl_error(\"Unable to retrieve public key: \");\n    }\n\n    BIGNUM* exBN = nullptr;\n\n    if (!EVP_PKEY_get_bn_param(keyPair, OSSL_PKEY_PARAM_RSA_E, &exBN)) {\n      throw openssl_error(\"Unable to retrieve public key: \");\n    }\n\n#else\n    auto exBN = openssl::BigNum::allocate();\n\n    if (!exBN) {\n      throw openssl_error(\"Unable to allocate BN: \");\n    }\n\n    BN_set_word(exBN, 65537);\n    auto rsa = openssl::RSA::allocate();\n\n    if (!RSA_generate_key_ex(rsa, nBits, exBN, NULL)) {\n      throw openssl_error(\"Unable to generate rsa key: \");\n    }\n\n    keyPair = openssl::EVPPKey::allocate();\n\n    if (!keyPair) {\n      throw openssl_error(\"Unable to generate rsa key: \");\n    }\n\n    RSA* rsaPtr = rsa.release();\n\n    if (!EVP_PKEY_assign_RSA(keyPair, rsaPtr)) {\n      throw openssl_error(\"Unable to generate rsa key: \");\n    }\n\n    const BIGNUM* modBN = RSA_get0_n(rsaPtr);\n#endif\n    size_t len = BN_num_bytes(modBN);\n    std::vector<uint8_t> modBin;\n    modBin.resize(len);\n    BN_bn2bin(modBN, modBin.data());\n    modulous = base64_url_encode(modBin);\n    len = BN_num_bytes(exBN);\n    std::vector<uint8_t> exBin;\n    exBin.resize(len);\n    BN_bn2bin(exBN, exBin.data());\n    exponent = base64_url_encode(exBin);\n  }\n\n  void insert_json(nlohmann::json& json) const\n  {\n    json[\"alg\"] = \"RS\" + std::to_string(shaBits);\n    json[\"kty\"] = \"RSA\";\n    json[\"e\"] = exponent;\n    json[\"n\"] = modulous;\n  }\n};\n\nusing RS256 = RSAKey<256>;\nusing RS384 = RSAKey<384>;\nusing RS512 = RSAKey<512>;\n};\n"
  },
  {
    "path": "console/commands/helpers/jwk_generator/libs/base64_url.hpp",
    "content": "// altered slightly from the original\n// https://stackoverflow.com/a/180949\n\n#pragma once\n\n#include <vector>\n#include <stdint.h>\n#include <string>\n\nnamespace jwk_generator\n{\nnamespace detail\n{\nstatic const std::string base64_chars =\n  \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n  \"abcdefghijklmnopqrstuvwxyz\"\n  \"0123456789-_\";\n\n\nstatic inline bool is_base64(uint8_t c)\n{\n  return (isalnum(c) || (c == '-') || (c == '_'));\n}\n\nstatic inline std::string base64_url_encode(std::vector<uint8_t> data,\n    bool pad = false)\n{\n  uint8_t const* buf = data.data();\n  unsigned int bufLen = data.size();\n  std::string ret;\n  int i = 0;\n  int j = 0;\n  uint8_t char_array_3[3];\n  uint8_t char_array_4[4];\n\n  while (bufLen--) {\n    char_array_3[i++] = *(buf++);\n\n    if (i == 3) {\n      char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;\n      char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >>\n                        4);\n      char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >>\n                        6);\n      char_array_4[3] = char_array_3[2] & 0x3f;\n\n      for (i = 0; (i < 4) ; i++) {\n        ret += base64_chars[char_array_4[i]];\n      }\n\n      i = 0;\n    }\n  }\n\n  if (i) {\n    for (j = i; j < 3; j++) {\n      char_array_3[j] = '\\0';\n    }\n\n    char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;\n    char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >>\n                      4);\n    char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >>\n                      6);\n    char_array_4[3] = char_array_3[2] & 0x3f;\n\n    for (j = 0; (j < i + 1); j++) {\n      ret += base64_chars[char_array_4[j]];\n    }\n\n    while (pad && (i++ < 3)) {\n      ret += '=';\n    }\n  }\n\n  return ret;\n}\n\nstatic inline std::vector<uint8_t> base64_url_decode(std::string const&\n    encoded_string)\n{\n  int in_len = encoded_string.size();\n  int i = 0;\n  int j = 0;\n  int in_ = 0;\n  uint8_t char_array_4[4], char_array_3[3];\n  std::vector<uint8_t> ret;\n\n  while (in_len-- && (encoded_string[in_] != '=') &&\n         is_base64(encoded_string[in_])) {\n    char_array_4[i++] = encoded_string[in_];\n    in_++;\n\n    if (i == 4) {\n      for (i = 0; i < 4; i++) {\n        char_array_4[i] = base64_chars.find(char_array_4[i]);\n      }\n\n      char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);\n      char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >>\n                        2);\n      char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];\n\n      for (i = 0; (i < 3); i++) {\n        ret.push_back(char_array_3[i]);\n      }\n\n      i = 0;\n    }\n  }\n\n  if (i) {\n    for (j = i; j < 4; j++) {\n      char_array_4[j] = 0;\n    }\n\n    for (j = 0; j < 4; j++) {\n      char_array_4[j] = base64_chars.find(char_array_4[j]);\n    }\n\n    char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);\n    char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >>\n                      2);\n    char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];\n\n    for (j = 0; (j < i - 1); j++) {\n      ret.push_back(char_array_3[j]);\n    }\n  }\n\n  return ret;\n}\n};\n};\n"
  },
  {
    "path": "console/commands/helpers/jwk_generator/libs/json.hpp",
    "content": "//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n/****************************************************************************\\\n * Note on documentation: The source files contain links to the online      *\n * documentation of the public API at https://json.nlohmann.me. This URL    *\n * contains the most recent documentation and should also be applicable to  *\n * previous versions; documentation for deprecated functions is not         *\n * removed, but marked deprecated. See \"Generate documentation\" section in  *\n * file docs/README.md.                                                     *\n\\****************************************************************************/\n\n#ifndef INCLUDE_NLOHMANN_JSON_HPP_\n#define INCLUDE_NLOHMANN_JSON_HPP_\n\n#include <algorithm> // all_of, find, for_each\n#include <cstddef> // nullptr_t, ptrdiff_t, size_t\n#include <functional> // hash, less\n#include <initializer_list> // initializer_list\n#ifndef JSON_NO_IO\n#include <iosfwd> // istream, ostream\n#endif  // JSON_NO_IO\n#include <iterator> // random_access_iterator_tag\n#include <memory> // unique_ptr\n#include <numeric> // accumulate\n#include <string> // string, stoi, to_string\n#include <utility> // declval, forward, move, pair, swap\n#include <vector> // vector\n\n// #include <nlohmann/adl_serializer.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <utility>\n\n// #include <nlohmann/detail/abi_macros.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// This file contains all macro definitions affecting or depending on the ABI\n\n#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK\n#if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)\n#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 2\n#warning \"Already included a different version of the library!\"\n#endif\n#endif\n#endif\n\n#define NLOHMANN_JSON_VERSION_MAJOR 3   // NOLINT(modernize-macro-to-enum)\n#define NLOHMANN_JSON_VERSION_MINOR 11  // NOLINT(modernize-macro-to-enum)\n#define NLOHMANN_JSON_VERSION_PATCH 2   // NOLINT(modernize-macro-to-enum)\n\n#ifndef JSON_DIAGNOSTICS\n#define JSON_DIAGNOSTICS 0\n#endif\n\n#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON\n#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0\n#endif\n\n#if JSON_DIAGNOSTICS\n#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag\n#else\n#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS\n#endif\n\n#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON\n#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp\n#else\n#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON\n#endif\n\n#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION\n#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0\n#endif\n\n// Construct the namespace ABI tags component\n#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b\n#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \\\n    NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b)\n\n#define NLOHMANN_JSON_ABI_TAGS                                       \\\n    NLOHMANN_JSON_ABI_TAGS_CONCAT(                                   \\\n            NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS,                       \\\n            NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON)\n\n// Construct the namespace version component\n#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \\\n    _v ## major ## _ ## minor ## _ ## patch\n#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \\\n    NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)\n\n#if NLOHMANN_JSON_NAMESPACE_NO_VERSION\n#define NLOHMANN_JSON_NAMESPACE_VERSION\n#else\n#define NLOHMANN_JSON_NAMESPACE_VERSION                                 \\\n    NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \\\n                                           NLOHMANN_JSON_VERSION_MINOR, \\\n                                           NLOHMANN_JSON_VERSION_PATCH)\n#endif\n\n// Combine namespace components\n#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b\n#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \\\n    NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)\n\n#ifndef NLOHMANN_JSON_NAMESPACE\n#define NLOHMANN_JSON_NAMESPACE               \\\n    nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \\\n            NLOHMANN_JSON_ABI_TAGS,           \\\n            NLOHMANN_JSON_NAMESPACE_VERSION)\n#endif\n\n#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN\n#define NLOHMANN_JSON_NAMESPACE_BEGIN                \\\n    namespace nlohmann                               \\\n    {                                                \\\n    inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \\\n                NLOHMANN_JSON_ABI_TAGS,              \\\n                NLOHMANN_JSON_NAMESPACE_VERSION)     \\\n    {\n#endif\n\n#ifndef NLOHMANN_JSON_NAMESPACE_END\n#define NLOHMANN_JSON_NAMESPACE_END                                     \\\n    }  /* namespace (inline namespace) NOLINT(readability/namespace) */ \\\n    }  // namespace nlohmann\n#endif\n\n// #include <nlohmann/detail/conversions/from_json.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // transform\n#include <array> // array\n#include <forward_list> // forward_list\n#include <iterator> // inserter, front_inserter, end\n#include <map> // map\n#include <string> // string\n#include <tuple> // tuple, make_tuple\n#include <type_traits> // is_arithmetic, is_same, is_enum, underlying_type, is_convertible\n#include <unordered_map> // unordered_map\n#include <utility> // pair, declval\n#include <valarray> // valarray\n\n// #include <nlohmann/detail/exceptions.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef> // nullptr_t\n#include <exception> // exception\n#include <stdexcept> // runtime_error\n#include <string> // to_string\n#include <vector> // vector\n\n// #include <nlohmann/detail/value_t.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <array> // array\n#include <cstddef> // size_t\n#include <cstdint> // uint8_t\n#include <string> // string\n\n// #include <nlohmann/detail/macro_scope.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <utility> // declval, pair\n// #include <nlohmann/detail/meta/detected.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <type_traits>\n\n// #include <nlohmann/detail/meta/void_t.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename ...Ts> struct make_void {\n  using type = void;\n};\ntemplate<typename ...Ts> using void_t = typename make_void<Ts...>::type;\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n// https://en.cppreference.com/w/cpp/experimental/is_detected\nstruct nonesuch {\n  nonesuch() = delete;\n  ~nonesuch() = delete;\n  nonesuch(nonesuch const&) = delete;\n  nonesuch(nonesuch const&&) = delete;\n  void operator=(nonesuch const&) = delete;\n  void operator=(nonesuch&&) = delete;\n};\n\ntemplate<class Default,\n         class AlwaysVoid,\n         template<class...> class Op,\n         class... Args>\nstruct detector {\n  using value_t = std::false_type;\n  using type = Default;\n};\n\ntemplate<class Default, template<class...> class Op, class... Args>\nstruct detector<Default, void_t<Op<Args...>>, Op, Args...> {\n  using value_t = std::true_type;\n  using type = Op<Args...>;\n};\n\ntemplate<template<class...> class Op, class... Args>\nusing is_detected = typename detector<nonesuch, void, Op, Args...>::value_t;\n\ntemplate<template<class...> class Op, class... Args>\nstruct is_detected_lazy : is_detected<Op, Args...> { };\n\ntemplate<template<class...> class Op, class... Args>\nusing detected_t = typename detector<nonesuch, void, Op, Args...>::type;\n\ntemplate<class Default, template<class...> class Op, class... Args>\nusing detected_or = detector<Default, void, Op, Args...>;\n\ntemplate<class Default, template<class...> class Op, class... Args>\nusing detected_or_t = typename detected_or<Default, Op, Args...>::type;\n\ntemplate<class Expected, template<class...> class Op, class... Args>\nusing is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;\n\ntemplate<class To, template<class...> class Op, class... Args>\nusing is_detected_convertible =\n  std::is_convertible<detected_t<Op, Args...>, To>;\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/thirdparty/hedley/hedley.hpp>\n\n\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson <evan@nemerson.com>\n// SPDX-License-Identifier: MIT\n\n/* Hedley - https://nemequ.github.io/hedley\n * Created by Evan Nemerson <evan@nemerson.com>\n */\n\n#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15)\n#if defined(JSON_HEDLEY_VERSION)\n#undef JSON_HEDLEY_VERSION\n#endif\n#define JSON_HEDLEY_VERSION 15\n\n#if defined(JSON_HEDLEY_STRINGIFY_EX)\n#undef JSON_HEDLEY_STRINGIFY_EX\n#endif\n#define JSON_HEDLEY_STRINGIFY_EX(x) #x\n\n#if defined(JSON_HEDLEY_STRINGIFY)\n#undef JSON_HEDLEY_STRINGIFY\n#endif\n#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x)\n\n#if defined(JSON_HEDLEY_CONCAT_EX)\n#undef JSON_HEDLEY_CONCAT_EX\n#endif\n#define JSON_HEDLEY_CONCAT_EX(a,b) a##b\n\n#if defined(JSON_HEDLEY_CONCAT)\n#undef JSON_HEDLEY_CONCAT\n#endif\n#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b)\n\n#if defined(JSON_HEDLEY_CONCAT3_EX)\n#undef JSON_HEDLEY_CONCAT3_EX\n#endif\n#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c\n\n#if defined(JSON_HEDLEY_CONCAT3)\n#undef JSON_HEDLEY_CONCAT3\n#endif\n#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c)\n\n#if defined(JSON_HEDLEY_VERSION_ENCODE)\n#undef JSON_HEDLEY_VERSION_ENCODE\n#endif\n#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision))\n\n#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR)\n#undef JSON_HEDLEY_VERSION_DECODE_MAJOR\n#endif\n#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000)\n\n#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR)\n#undef JSON_HEDLEY_VERSION_DECODE_MINOR\n#endif\n#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000)\n\n#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION)\n#undef JSON_HEDLEY_VERSION_DECODE_REVISION\n#endif\n#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000)\n\n#if defined(JSON_HEDLEY_GNUC_VERSION)\n#undef JSON_HEDLEY_GNUC_VERSION\n#endif\n#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__)\n#define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)\n#elif defined(__GNUC__)\n#define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK)\n#undef JSON_HEDLEY_GNUC_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_GNUC_VERSION)\n#define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_MSVC_VERSION)\n#undef JSON_HEDLEY_MSVC_VERSION\n#endif\n#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL)\n#define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100)\n#elif defined(_MSC_FULL_VER) && !defined(__ICL)\n#define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10)\n#elif defined(_MSC_VER) && !defined(__ICL)\n#define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0)\n#endif\n\n#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK)\n#undef JSON_HEDLEY_MSVC_VERSION_CHECK\n#endif\n#if !defined(JSON_HEDLEY_MSVC_VERSION)\n#define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0)\n#elif defined(_MSC_VER) && (_MSC_VER >= 1400)\n#define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch)))\n#elif defined(_MSC_VER) && (_MSC_VER >= 1200)\n#define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch)))\n#else\n#define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor)))\n#endif\n\n#if defined(JSON_HEDLEY_INTEL_VERSION)\n#undef JSON_HEDLEY_INTEL_VERSION\n#endif\n#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL)\n#define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE)\n#elif defined(__INTEL_COMPILER) && !defined(__ICL)\n#define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0)\n#endif\n\n#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK)\n#undef JSON_HEDLEY_INTEL_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_INTEL_VERSION)\n#define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_INTEL_CL_VERSION)\n#undef JSON_HEDLEY_INTEL_CL_VERSION\n#endif\n#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL)\n#define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0)\n#endif\n\n#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK)\n#undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_INTEL_CL_VERSION)\n#define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_PGI_VERSION)\n#undef JSON_HEDLEY_PGI_VERSION\n#endif\n#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__)\n#define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__)\n#endif\n\n#if defined(JSON_HEDLEY_PGI_VERSION_CHECK)\n#undef JSON_HEDLEY_PGI_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_PGI_VERSION)\n#define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_SUNPRO_VERSION)\n#undef JSON_HEDLEY_SUNPRO_VERSION\n#endif\n#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000)\n#define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10)\n#elif defined(__SUNPRO_C)\n#define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf)\n#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000)\n#define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10)\n#elif defined(__SUNPRO_CC)\n#define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf)\n#endif\n\n#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK)\n#undef JSON_HEDLEY_SUNPRO_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_SUNPRO_VERSION)\n#define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION)\n#undef JSON_HEDLEY_EMSCRIPTEN_VERSION\n#endif\n#if defined(__EMSCRIPTEN__)\n#define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__)\n#endif\n\n#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK)\n#undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION)\n#define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_ARM_VERSION)\n#undef JSON_HEDLEY_ARM_VERSION\n#endif\n#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION)\n#define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100)\n#elif defined(__CC_ARM) && defined(__ARMCC_VERSION)\n#define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100)\n#endif\n\n#if defined(JSON_HEDLEY_ARM_VERSION_CHECK)\n#undef JSON_HEDLEY_ARM_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_ARM_VERSION)\n#define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_IBM_VERSION)\n#undef JSON_HEDLEY_IBM_VERSION\n#endif\n#if defined(__ibmxl__)\n#define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__)\n#elif defined(__xlC__) && defined(__xlC_ver__)\n#define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff)\n#elif defined(__xlC__)\n#define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0)\n#endif\n\n#if defined(JSON_HEDLEY_IBM_VERSION_CHECK)\n#undef JSON_HEDLEY_IBM_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_IBM_VERSION)\n#define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_VERSION)\n#undef JSON_HEDLEY_TI_VERSION\n#endif\n#if \\\n    defined(__TI_COMPILER_VERSION__) && \\\n    ( \\\n      defined(__TMS470__) || defined(__TI_ARM__) || \\\n      defined(__MSP430__) || \\\n      defined(__TMS320C2000__) \\\n    )\n#if (__TI_COMPILER_VERSION__ >= 16000000)\n#define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n#endif\n\n#if defined(JSON_HEDLEY_TI_VERSION_CHECK)\n#undef JSON_HEDLEY_TI_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_VERSION)\n#define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL2000_VERSION)\n#undef JSON_HEDLEY_TI_CL2000_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__)\n#define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK)\n#undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_CL2000_VERSION)\n#define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL430_VERSION)\n#undef JSON_HEDLEY_TI_CL430_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__)\n#define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK)\n#undef JSON_HEDLEY_TI_CL430_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_CL430_VERSION)\n#define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_ARMCL_VERSION)\n#undef JSON_HEDLEY_TI_ARMCL_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__))\n#define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK)\n#undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_ARMCL_VERSION)\n#define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL6X_VERSION)\n#undef JSON_HEDLEY_TI_CL6X_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__)\n#define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK)\n#undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_CL6X_VERSION)\n#define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL7X_VERSION)\n#undef JSON_HEDLEY_TI_CL7X_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__)\n#define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK)\n#undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_CL7X_VERSION)\n#define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_CLPRU_VERSION)\n#undef JSON_HEDLEY_TI_CLPRU_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__)\n#define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK)\n#undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_CLPRU_VERSION)\n#define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_CRAY_VERSION)\n#undef JSON_HEDLEY_CRAY_VERSION\n#endif\n#if defined(_CRAYC)\n#if defined(_RELEASE_PATCHLEVEL)\n#define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL)\n#else\n#define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0)\n#endif\n#endif\n\n#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK)\n#undef JSON_HEDLEY_CRAY_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_CRAY_VERSION)\n#define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_IAR_VERSION)\n#undef JSON_HEDLEY_IAR_VERSION\n#endif\n#if defined(__IAR_SYSTEMS_ICC__)\n#if __VER__ > 1000\n#define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000))\n#else\n#define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0)\n#endif\n#endif\n\n#if defined(JSON_HEDLEY_IAR_VERSION_CHECK)\n#undef JSON_HEDLEY_IAR_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_IAR_VERSION)\n#define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TINYC_VERSION)\n#undef JSON_HEDLEY_TINYC_VERSION\n#endif\n#if defined(__TINYC__)\n#define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100)\n#endif\n\n#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK)\n#undef JSON_HEDLEY_TINYC_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TINYC_VERSION)\n#define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_DMC_VERSION)\n#undef JSON_HEDLEY_DMC_VERSION\n#endif\n#if defined(__DMC__)\n#define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf)\n#endif\n\n#if defined(JSON_HEDLEY_DMC_VERSION_CHECK)\n#undef JSON_HEDLEY_DMC_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_DMC_VERSION)\n#define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_COMPCERT_VERSION)\n#undef JSON_HEDLEY_COMPCERT_VERSION\n#endif\n#if defined(__COMPCERT_VERSION__)\n#define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100)\n#endif\n\n#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK)\n#undef JSON_HEDLEY_COMPCERT_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_COMPCERT_VERSION)\n#define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_PELLES_VERSION)\n#undef JSON_HEDLEY_PELLES_VERSION\n#endif\n#if defined(__POCC__)\n#define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0)\n#endif\n\n#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK)\n#undef JSON_HEDLEY_PELLES_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_PELLES_VERSION)\n#define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_MCST_LCC_VERSION)\n#undef JSON_HEDLEY_MCST_LCC_VERSION\n#endif\n#if defined(__LCC__) && defined(__LCC_MINOR__)\n#define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__)\n#endif\n\n#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK)\n#undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_MCST_LCC_VERSION)\n#define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_VERSION)\n#undef JSON_HEDLEY_GCC_VERSION\n#endif\n#if \\\n    defined(JSON_HEDLEY_GNUC_VERSION) && \\\n    !defined(__clang__) && \\\n    !defined(JSON_HEDLEY_INTEL_VERSION) && \\\n    !defined(JSON_HEDLEY_PGI_VERSION) && \\\n    !defined(JSON_HEDLEY_ARM_VERSION) && \\\n    !defined(JSON_HEDLEY_CRAY_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_CL430_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \\\n    !defined(__COMPCERT__) && \\\n    !defined(JSON_HEDLEY_MCST_LCC_VERSION)\n#define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION\n#endif\n\n#if defined(JSON_HEDLEY_GCC_VERSION_CHECK)\n#undef JSON_HEDLEY_GCC_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_GCC_VERSION)\n#define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n#define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_ATTRIBUTE)\n#undef JSON_HEDLEY_HAS_ATTRIBUTE\n#endif\n#if \\\n  defined(__has_attribute) && \\\n  ( \\\n    (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \\\n  )\n#  define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute)\n#else\n#  define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE)\n#undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE\n#endif\n#if defined(__has_attribute)\n#define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute)\n#else\n#define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE)\n#undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE\n#endif\n#if defined(__has_attribute)\n#define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute)\n#else\n#define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE)\n#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE\n#endif\n#if \\\n    defined(__has_cpp_attribute) && \\\n    defined(__cplusplus) && \\\n    (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0))\n#define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute)\n#else\n#define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS)\n#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS\n#endif\n#if !defined(__cplusplus) || !defined(__has_cpp_attribute)\n#define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0)\n#elif \\\n    !defined(JSON_HEDLEY_PGI_VERSION) && \\\n    !defined(JSON_HEDLEY_IAR_VERSION) && \\\n    (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \\\n    (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0))\n#define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute)\n#else\n#define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE)\n#undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE\n#endif\n#if defined(__has_cpp_attribute) && defined(__cplusplus)\n#define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute)\n#else\n#define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE)\n#undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE\n#endif\n#if defined(__has_cpp_attribute) && defined(__cplusplus)\n#define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute)\n#else\n#define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_BUILTIN)\n#undef JSON_HEDLEY_HAS_BUILTIN\n#endif\n#if defined(__has_builtin)\n#define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin)\n#else\n#define JSON_HEDLEY_HAS_BUILTIN(builtin) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN)\n#undef JSON_HEDLEY_GNUC_HAS_BUILTIN\n#endif\n#if defined(__has_builtin)\n#define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin)\n#else\n#define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN)\n#undef JSON_HEDLEY_GCC_HAS_BUILTIN\n#endif\n#if defined(__has_builtin)\n#define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin)\n#else\n#define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_FEATURE)\n#undef JSON_HEDLEY_HAS_FEATURE\n#endif\n#if defined(__has_feature)\n#define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature)\n#else\n#define JSON_HEDLEY_HAS_FEATURE(feature) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE)\n#undef JSON_HEDLEY_GNUC_HAS_FEATURE\n#endif\n#if defined(__has_feature)\n#define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature)\n#else\n#define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_FEATURE)\n#undef JSON_HEDLEY_GCC_HAS_FEATURE\n#endif\n#if defined(__has_feature)\n#define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature)\n#else\n#define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_EXTENSION)\n#undef JSON_HEDLEY_HAS_EXTENSION\n#endif\n#if defined(__has_extension)\n#define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension)\n#else\n#define JSON_HEDLEY_HAS_EXTENSION(extension) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION)\n#undef JSON_HEDLEY_GNUC_HAS_EXTENSION\n#endif\n#if defined(__has_extension)\n#define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension)\n#else\n#define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION)\n#undef JSON_HEDLEY_GCC_HAS_EXTENSION\n#endif\n#if defined(__has_extension)\n#define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension)\n#else\n#define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE)\n#undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE\n#endif\n#if defined(__has_declspec_attribute)\n#define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute)\n#else\n#define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE)\n#undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE\n#endif\n#if defined(__has_declspec_attribute)\n#define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute)\n#else\n#define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE)\n#undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE\n#endif\n#if defined(__has_declspec_attribute)\n#define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute)\n#else\n#define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_WARNING)\n#undef JSON_HEDLEY_HAS_WARNING\n#endif\n#if defined(__has_warning)\n#define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning)\n#else\n#define JSON_HEDLEY_HAS_WARNING(warning) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_WARNING)\n#undef JSON_HEDLEY_GNUC_HAS_WARNING\n#endif\n#if defined(__has_warning)\n#define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning)\n#else\n#define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_WARNING)\n#undef JSON_HEDLEY_GCC_HAS_WARNING\n#endif\n#if defined(__has_warning)\n#define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning)\n#else\n#define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if \\\n    (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \\\n    defined(__clang__) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \\\n    JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \\\n    (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR))\n#define JSON_HEDLEY_PRAGMA(value) _Pragma(#value)\n#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0)\n#define JSON_HEDLEY_PRAGMA(value) __pragma(value)\n#else\n#define JSON_HEDLEY_PRAGMA(value)\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH)\n#undef JSON_HEDLEY_DIAGNOSTIC_PUSH\n#endif\n#if defined(JSON_HEDLEY_DIAGNOSTIC_POP)\n#undef JSON_HEDLEY_DIAGNOSTIC_POP\n#endif\n#if defined(__clang__)\n#define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"clang diagnostic push\")\n#define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"clang diagnostic pop\")\n#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n#define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"warning(push)\")\n#define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"warning(pop)\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0)\n#define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"GCC diagnostic push\")\n#define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"GCC diagnostic pop\")\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push))\n#define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop))\n#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0)\n#define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"push\")\n#define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"pop\")\n#elif \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0)\n#define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"diag_push\")\n#define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"diag_pop\")\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0)\n#define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"warning(push)\")\n#define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"warning(pop)\")\n#else\n#define JSON_HEDLEY_DIAGNOSTIC_PUSH\n#define JSON_HEDLEY_DIAGNOSTIC_POP\n#endif\n\n/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for\n   HEDLEY INTERNAL USE ONLY.  API subject to change without notice. */\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_)\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_\n#endif\n#if defined(__cplusplus)\n#  if JSON_HEDLEY_HAS_WARNING(\"-Wc++98-compat\")\n#    if JSON_HEDLEY_HAS_WARNING(\"-Wc++17-extensions\")\n#      if JSON_HEDLEY_HAS_WARNING(\"-Wc++1z-extensions\")\n#        define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++98-compat\\\"\") \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++17-extensions\\\"\") \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++1z-extensions\\\"\") \\\n    xpr \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#      else\n#        define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++98-compat\\\"\") \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++17-extensions\\\"\") \\\n    xpr \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#      endif\n#    else\n#      define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++98-compat\\\"\") \\\n    xpr \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#    endif\n#  endif\n#endif\n#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x\n#endif\n\n#if defined(JSON_HEDLEY_CONST_CAST)\n#undef JSON_HEDLEY_CONST_CAST\n#endif\n#if defined(__cplusplus)\n#  define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast<T>(expr))\n#elif \\\n  JSON_HEDLEY_HAS_WARNING(\"-Wcast-qual\") || \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n#  define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \\\n        JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n        JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \\\n        ((T) (expr)); \\\n        JSON_HEDLEY_DIAGNOSTIC_POP \\\n    }))\n#else\n#  define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr))\n#endif\n\n#if defined(JSON_HEDLEY_REINTERPRET_CAST)\n#undef JSON_HEDLEY_REINTERPRET_CAST\n#endif\n#if defined(__cplusplus)\n#define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast<T>(expr))\n#else\n#define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr))\n#endif\n\n#if defined(JSON_HEDLEY_STATIC_CAST)\n#undef JSON_HEDLEY_STATIC_CAST\n#endif\n#if defined(__cplusplus)\n#define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast<T>(expr))\n#else\n#define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr))\n#endif\n\n#if defined(JSON_HEDLEY_CPP_CAST)\n#undef JSON_HEDLEY_CPP_CAST\n#endif\n#if defined(__cplusplus)\n#  if JSON_HEDLEY_HAS_WARNING(\"-Wold-style-cast\")\n#    define JSON_HEDLEY_CPP_CAST(T, expr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wold-style-cast\\\"\") \\\n    ((T) (expr)) \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#  elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0)\n#    define JSON_HEDLEY_CPP_CAST(T, expr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"diag_suppress=Pe137\") \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#  else\n#    define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr))\n#  endif\n#else\n#  define JSON_HEDLEY_CPP_CAST(T, expr) (expr)\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED)\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wdeprecated-declarations\")\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"clang diagnostic ignored \\\"-Wdeprecated-declarations\\\"\")\n#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"warning(disable:1478 1786)\")\n#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786))\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"diag_suppress 1215,1216,1444,1445\")\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"diag_suppress 1215,1444\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"GCC diagnostic ignored \\\"-Wdeprecated-declarations\\\"\")\n#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996))\n#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"diag_suppress 1215,1444\")\n#elif \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"diag_suppress 1291,1718\")\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)\")\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"error_messages(off,symdeprecated,symdeprecated2)\")\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"diag_suppress=Pe1444,Pe1215\")\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"warn(disable:2241)\")\n#else\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS)\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wunknown-pragmas\")\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"clang diagnostic ignored \\\"-Wunknown-pragmas\\\"\")\n#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"warning(disable:161)\")\n#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161))\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"diag_suppress 1675\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"GCC diagnostic ignored \\\"-Wunknown-pragmas\\\"\")\n#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068))\n#elif \\\n    JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"diag_suppress 163\")\n#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"diag_suppress 163\")\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"diag_suppress=Pe161\")\n#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"diag_suppress 161\")\n#else\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES)\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wunknown-attributes\")\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"clang diagnostic ignored \\\"-Wunknown-attributes\\\"\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"GCC diagnostic ignored \\\"-Wdeprecated-declarations\\\"\")\n#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"warning(disable:1292)\")\n#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292))\n#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030))\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"diag_suppress 1097,1098\")\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"diag_suppress 1097\")\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"error_messages(off,attrskipunsup)\")\n#elif \\\n    JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"diag_suppress 1173\")\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"diag_suppress=Pe1097\")\n#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"diag_suppress 1097\")\n#else\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL)\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wcast-qual\")\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma(\"clang diagnostic ignored \\\"-Wcast-qual\\\"\")\n#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma(\"warning(disable:2203 2331)\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma(\"GCC diagnostic ignored \\\"-Wcast-qual\\\"\")\n#else\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION)\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wunused-function\")\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma(\"clang diagnostic ignored \\\"-Wunused-function\\\"\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma(\"GCC diagnostic ignored \\\"-Wunused-function\\\"\")\n#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505))\n#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma(\"diag_suppress 3142\")\n#else\n#define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION\n#endif\n\n#if defined(JSON_HEDLEY_DEPRECATED)\n#undef JSON_HEDLEY_DEPRECATED\n#endif\n#if defined(JSON_HEDLEY_DEPRECATED_FOR)\n#undef JSON_HEDLEY_DEPRECATED_FOR\n#endif\n#if \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated(\"Since \" # since))\n#define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated(\"Since \" #since \"; use \" #replacement))\n#elif \\\n    (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__(\"Since \" #since)))\n#define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__(\"Since \" #since \"; use \" #replacement)))\n#elif defined(__cplusplus) && (__cplusplus >= 201402L)\n#define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated(\"Since \" #since)]])\n#define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated(\"Since \" #since \"; use \" #replacement)]])\n#elif \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \\\n    JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0)\n#define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__))\n#define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__))\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \\\n    JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated)\n#define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated)\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n#define JSON_HEDLEY_DEPRECATED(since) _Pragma(\"deprecated\")\n#define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma(\"deprecated\")\n#else\n#define JSON_HEDLEY_DEPRECATED(since)\n#define JSON_HEDLEY_DEPRECATED_FOR(since, replacement)\n#endif\n\n#if defined(JSON_HEDLEY_UNAVAILABLE)\n#undef JSON_HEDLEY_UNAVAILABLE\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__(\"Not available until \" #available_since)))\n#else\n#define JSON_HEDLEY_UNAVAILABLE(available_since)\n#endif\n\n#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT)\n#undef JSON_HEDLEY_WARN_UNUSED_RESULT\n#endif\n#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG)\n#undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))\n#define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__))\n#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L)\n#define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]])\n#define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]])\n#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard)\n#define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]])\n#define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]])\n#elif defined(_Check_return_) /* SAL */\n#define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_\n#define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_\n#else\n#define JSON_HEDLEY_WARN_UNUSED_RESULT\n#define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg)\n#endif\n\n#if defined(JSON_HEDLEY_SENTINEL)\n#undef JSON_HEDLEY_SENTINEL\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position)))\n#else\n#define JSON_HEDLEY_SENTINEL(position)\n#endif\n\n#if defined(JSON_HEDLEY_NO_RETURN)\n#undef JSON_HEDLEY_NO_RETURN\n#endif\n#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n#define JSON_HEDLEY_NO_RETURN __noreturn\n#elif \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__))\n#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L\n#define JSON_HEDLEY_NO_RETURN _Noreturn\n#elif defined(__cplusplus) && (__cplusplus >= 201103L)\n#define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]])\n#elif \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0)\n#define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__))\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)\n#define JSON_HEDLEY_NO_RETURN _Pragma(\"does_not_return\")\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#define JSON_HEDLEY_NO_RETURN __declspec(noreturn)\n#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus)\n#define JSON_HEDLEY_NO_RETURN _Pragma(\"FUNC_NEVER_RETURNS;\")\n#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0)\n#define JSON_HEDLEY_NO_RETURN __attribute((noreturn))\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0)\n#define JSON_HEDLEY_NO_RETURN __declspec(noreturn)\n#else\n#define JSON_HEDLEY_NO_RETURN\n#endif\n\n#if defined(JSON_HEDLEY_NO_ESCAPE)\n#undef JSON_HEDLEY_NO_ESCAPE\n#endif\n#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape)\n#define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__))\n#else\n#define JSON_HEDLEY_NO_ESCAPE\n#endif\n\n#if defined(JSON_HEDLEY_UNREACHABLE)\n#undef JSON_HEDLEY_UNREACHABLE\n#endif\n#if defined(JSON_HEDLEY_UNREACHABLE_RETURN)\n#undef JSON_HEDLEY_UNREACHABLE_RETURN\n#endif\n#if defined(JSON_HEDLEY_ASSUME)\n#undef JSON_HEDLEY_ASSUME\n#endif\n#if \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#define JSON_HEDLEY_ASSUME(expr) __assume(expr)\n#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume)\n#define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr)\n#elif \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0)\n#if defined(__cplusplus)\n#define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr)\n#else\n#define JSON_HEDLEY_ASSUME(expr) _nassert(expr)\n#endif\n#endif\n#if \\\n    (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \\\n    JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable()\n#elif defined(JSON_HEDLEY_ASSUME)\n#define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0)\n#endif\n#if !defined(JSON_HEDLEY_ASSUME)\n#if defined(JSON_HEDLEY_UNREACHABLE)\n#define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1)))\n#else\n#define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr)\n#endif\n#endif\n#if defined(JSON_HEDLEY_UNREACHABLE)\n#if  \\\n        JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \\\n        JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0)\n#define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value))\n#else\n#define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE()\n#endif\n#else\n#define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value)\n#endif\n#if !defined(JSON_HEDLEY_UNREACHABLE)\n#define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0)\n#endif\n\nJSON_HEDLEY_DIAGNOSTIC_PUSH\n#if JSON_HEDLEY_HAS_WARNING(\"-Wpedantic\")\n#pragma clang diagnostic ignored \"-Wpedantic\"\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wc++98-compat-pedantic\") && defined(__cplusplus)\n#pragma clang diagnostic ignored \"-Wc++98-compat-pedantic\"\n#endif\n#if JSON_HEDLEY_GCC_HAS_WARNING(\"-Wvariadic-macros\",4,0,0)\n#if defined(__clang__)\n#pragma clang diagnostic ignored \"-Wvariadic-macros\"\n#elif defined(JSON_HEDLEY_GCC_VERSION)\n#pragma GCC diagnostic ignored \"-Wvariadic-macros\"\n#endif\n#endif\n#if defined(JSON_HEDLEY_NON_NULL)\n#undef JSON_HEDLEY_NON_NULL\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0)\n#define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__)))\n#else\n#define JSON_HEDLEY_NON_NULL(...)\n#endif\nJSON_HEDLEY_DIAGNOSTIC_POP\n\n#if defined(JSON_HEDLEY_PRINTF_FORMAT)\n#undef JSON_HEDLEY_PRINTF_FORMAT\n#endif\n#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO)\n#define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check)))\n#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO)\n#define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check)))\n#elif \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(format) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check)))\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0)\n#define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check))\n#else\n#define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check)\n#endif\n\n#if defined(JSON_HEDLEY_CONSTEXPR)\n#undef JSON_HEDLEY_CONSTEXPR\n#endif\n#if defined(__cplusplus)\n#if __cplusplus >= 201103L\n#define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr)\n#endif\n#endif\n#if !defined(JSON_HEDLEY_CONSTEXPR)\n#define JSON_HEDLEY_CONSTEXPR\n#endif\n\n#if defined(JSON_HEDLEY_PREDICT)\n#undef JSON_HEDLEY_PREDICT\n#endif\n#if defined(JSON_HEDLEY_LIKELY)\n#undef JSON_HEDLEY_LIKELY\n#endif\n#if defined(JSON_HEDLEY_UNLIKELY)\n#undef JSON_HEDLEY_UNLIKELY\n#endif\n#if defined(JSON_HEDLEY_UNPREDICTABLE)\n#undef JSON_HEDLEY_UNPREDICTABLE\n#endif\n#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable)\n#define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr))\n#endif\n#if \\\n  (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \\\n  JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#  define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability(  (expr), (value), (probability))\n#  define JSON_HEDLEY_PREDICT_TRUE(expr, probability)   __builtin_expect_with_probability(!!(expr),    1   , (probability))\n#  define JSON_HEDLEY_PREDICT_FALSE(expr, probability)  __builtin_expect_with_probability(!!(expr),    0   , (probability))\n#  define JSON_HEDLEY_LIKELY(expr)                      __builtin_expect                 (!!(expr),    1                  )\n#  define JSON_HEDLEY_UNLIKELY(expr)                    __builtin_expect                 (!!(expr),    0                  )\n#elif \\\n  (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n  (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \\\n  JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n  JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n  JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n  JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \\\n  JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \\\n  JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \\\n  JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \\\n  JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n  JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n  JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \\\n  JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \\\n  JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#  define JSON_HEDLEY_PREDICT(expr, expected, probability) \\\n    (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)))\n#  define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \\\n    (__extension__ ({ \\\n        double hedley_probability_ = (probability); \\\n        ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \\\n    }))\n#  define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \\\n    (__extension__ ({ \\\n        double hedley_probability_ = (probability); \\\n        ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \\\n    }))\n#  define JSON_HEDLEY_LIKELY(expr)   __builtin_expect(!!(expr), 1)\n#  define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0)\n#else\n#  define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))\n#  define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr))\n#  define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr))\n#  define JSON_HEDLEY_LIKELY(expr) (!!(expr))\n#  define JSON_HEDLEY_UNLIKELY(expr) (!!(expr))\n#endif\n#if !defined(JSON_HEDLEY_UNPREDICTABLE)\n#define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5)\n#endif\n\n#if defined(JSON_HEDLEY_MALLOC)\n#undef JSON_HEDLEY_MALLOC\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_MALLOC __attribute__((__malloc__))\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)\n#define JSON_HEDLEY_MALLOC _Pragma(\"returns_new_memory\")\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#define JSON_HEDLEY_MALLOC __declspec(restrict)\n#else\n#define JSON_HEDLEY_MALLOC\n#endif\n\n#if defined(JSON_HEDLEY_PURE)\n#undef JSON_HEDLEY_PURE\n#endif\n#if \\\n  JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n  JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n  JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n  JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n  JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n  (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n  (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n  (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n  (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n  JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n  JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n  JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \\\n  JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#  define JSON_HEDLEY_PURE __attribute__((__pure__))\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)\n#  define JSON_HEDLEY_PURE _Pragma(\"does_not_write_global_data\")\n#elif defined(__cplusplus) && \\\n    ( \\\n      JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \\\n      JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \\\n      JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \\\n    )\n#  define JSON_HEDLEY_PURE _Pragma(\"FUNC_IS_PURE;\")\n#else\n#  define JSON_HEDLEY_PURE\n#endif\n\n#if defined(JSON_HEDLEY_CONST)\n#undef JSON_HEDLEY_CONST\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(const) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_CONST __attribute__((__const__))\n#elif \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)\n#define JSON_HEDLEY_CONST _Pragma(\"no_side_effect\")\n#else\n#define JSON_HEDLEY_CONST JSON_HEDLEY_PURE\n#endif\n\n#if defined(JSON_HEDLEY_RESTRICT)\n#undef JSON_HEDLEY_RESTRICT\n#endif\n#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus)\n#define JSON_HEDLEY_RESTRICT restrict\n#elif \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \\\n    JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \\\n    defined(__clang__) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_RESTRICT __restrict\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus)\n#define JSON_HEDLEY_RESTRICT _Restrict\n#else\n#define JSON_HEDLEY_RESTRICT\n#endif\n\n#if defined(JSON_HEDLEY_INLINE)\n#undef JSON_HEDLEY_INLINE\n#endif\n#if \\\n    (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \\\n    (defined(__cplusplus) && (__cplusplus >= 199711L))\n#define JSON_HEDLEY_INLINE inline\n#elif \\\n    defined(JSON_HEDLEY_GCC_VERSION) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0)\n#define JSON_HEDLEY_INLINE __inline__\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_INLINE __inline\n#else\n#define JSON_HEDLEY_INLINE\n#endif\n\n#if defined(JSON_HEDLEY_ALWAYS_INLINE)\n#undef JSON_HEDLEY_ALWAYS_INLINE\n#endif\n#if \\\n  JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n  JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n  JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n  JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n  JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n  (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n  (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n  (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n  (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n  JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n  JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n  JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \\\n  JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0)\n#  define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE\n#elif \\\n  JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \\\n  JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#  define JSON_HEDLEY_ALWAYS_INLINE __forceinline\n#elif defined(__cplusplus) && \\\n    ( \\\n      JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n      JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n      JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n      JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \\\n      JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n      JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \\\n    )\n#  define JSON_HEDLEY_ALWAYS_INLINE _Pragma(\"FUNC_ALWAYS_INLINE;\")\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n#  define JSON_HEDLEY_ALWAYS_INLINE _Pragma(\"inline=forced\")\n#else\n#  define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE\n#endif\n\n#if defined(JSON_HEDLEY_NEVER_INLINE)\n#undef JSON_HEDLEY_NEVER_INLINE\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \\\n    JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0)\n#define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__))\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#define JSON_HEDLEY_NEVER_INLINE __declspec(noinline)\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0)\n#define JSON_HEDLEY_NEVER_INLINE _Pragma(\"noinline\")\n#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus)\n#define JSON_HEDLEY_NEVER_INLINE _Pragma(\"FUNC_CANNOT_INLINE;\")\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n#define JSON_HEDLEY_NEVER_INLINE _Pragma(\"inline=never\")\n#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0)\n#define JSON_HEDLEY_NEVER_INLINE __attribute((noinline))\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0)\n#define JSON_HEDLEY_NEVER_INLINE __declspec(noinline)\n#else\n#define JSON_HEDLEY_NEVER_INLINE\n#endif\n\n#if defined(JSON_HEDLEY_PRIVATE)\n#undef JSON_HEDLEY_PRIVATE\n#endif\n#if defined(JSON_HEDLEY_PUBLIC)\n#undef JSON_HEDLEY_PUBLIC\n#endif\n#if defined(JSON_HEDLEY_IMPORT)\n#undef JSON_HEDLEY_IMPORT\n#endif\n#if defined(_WIN32) || defined(__CYGWIN__)\n#  define JSON_HEDLEY_PRIVATE\n#  define JSON_HEDLEY_PUBLIC   __declspec(dllexport)\n#  define JSON_HEDLEY_IMPORT   __declspec(dllimport)\n#else\n#  if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \\\n    ( \\\n      defined(__TI_EABI__) && \\\n      ( \\\n        (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n        JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \\\n      ) \\\n    ) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#    define JSON_HEDLEY_PRIVATE __attribute__((__visibility__(\"hidden\")))\n#    define JSON_HEDLEY_PUBLIC  __attribute__((__visibility__(\"default\")))\n#  else\n#    define JSON_HEDLEY_PRIVATE\n#    define JSON_HEDLEY_PUBLIC\n#  endif\n#  define JSON_HEDLEY_IMPORT    extern\n#endif\n\n#if defined(JSON_HEDLEY_NO_THROW)\n#undef JSON_HEDLEY_NO_THROW\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__))\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0)\n#define JSON_HEDLEY_NO_THROW __declspec(nothrow)\n#else\n#define JSON_HEDLEY_NO_THROW\n#endif\n\n#if defined(JSON_HEDLEY_FALL_THROUGH)\n#undef JSON_HEDLEY_FALL_THROUGH\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__))\n#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough)\n#define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]])\n#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough)\n#define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]])\n#elif defined(__fallthrough) /* SAL */\n#define JSON_HEDLEY_FALL_THROUGH __fallthrough\n#else\n#define JSON_HEDLEY_FALL_THROUGH\n#endif\n\n#if defined(JSON_HEDLEY_RETURNS_NON_NULL)\n#undef JSON_HEDLEY_RETURNS_NON_NULL\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__))\n#elif defined(_Ret_notnull_) /* SAL */\n#define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_\n#else\n#define JSON_HEDLEY_RETURNS_NON_NULL\n#endif\n\n#if defined(JSON_HEDLEY_ARRAY_PARAM)\n#undef JSON_HEDLEY_ARRAY_PARAM\n#endif\n#if \\\n    defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \\\n    !defined(__STDC_NO_VLA__) && \\\n    !defined(__cplusplus) && \\\n    !defined(JSON_HEDLEY_PGI_VERSION) && \\\n    !defined(JSON_HEDLEY_TINYC_VERSION)\n#define JSON_HEDLEY_ARRAY_PARAM(name) (name)\n#else\n#define JSON_HEDLEY_ARRAY_PARAM(name)\n#endif\n\n#if defined(JSON_HEDLEY_IS_CONSTANT)\n#undef JSON_HEDLEY_IS_CONSTANT\n#endif\n#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR)\n#undef JSON_HEDLEY_REQUIRE_CONSTEXPR\n#endif\n/* JSON_HEDLEY_IS_CONSTEXPR_ is for\n   HEDLEY INTERNAL USE ONLY.  API subject to change without notice. */\n#if defined(JSON_HEDLEY_IS_CONSTEXPR_)\n#undef JSON_HEDLEY_IS_CONSTEXPR_\n#endif\n#if \\\n    JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \\\n    (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \\\n    JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr)\n#endif\n#if !defined(__cplusplus)\n#  if \\\n       JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \\\n       JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \\\n       JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n       JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \\\n       JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \\\n       JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \\\n       JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24)\n#if defined(__INTPTR_TYPE__)\n#define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*)\n#else\n#include <stdint.h>\n#define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*)\n#endif\n#  elif \\\n       ( \\\n          defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \\\n          !defined(JSON_HEDLEY_SUNPRO_VERSION) && \\\n          !defined(JSON_HEDLEY_PGI_VERSION) && \\\n          !defined(JSON_HEDLEY_IAR_VERSION)) || \\\n       (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \\\n       JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \\\n       JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \\\n       JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \\\n       JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0)\n#if defined(__INTPTR_TYPE__)\n#define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0)\n#else\n#include <stdint.h>\n#define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0)\n#endif\n#  elif \\\n       defined(JSON_HEDLEY_GCC_VERSION) || \\\n       defined(JSON_HEDLEY_INTEL_VERSION) || \\\n       defined(JSON_HEDLEY_TINYC_VERSION) || \\\n       defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \\\n       JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \\\n       defined(JSON_HEDLEY_TI_CL2000_VERSION) || \\\n       defined(JSON_HEDLEY_TI_CL6X_VERSION) || \\\n       defined(JSON_HEDLEY_TI_CL7X_VERSION) || \\\n       defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \\\n       defined(__clang__)\n#    define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \\\n        sizeof(void) != \\\n        sizeof(*( \\\n                  1 ? \\\n                  ((void*) ((expr) * 0L) ) : \\\n((struct { char v[sizeof(void) * 2]; } *) 1) \\\n                ) \\\n              ) \\\n                                            )\n#  endif\n#endif\n#if defined(JSON_HEDLEY_IS_CONSTEXPR_)\n#if !defined(JSON_HEDLEY_IS_CONSTANT)\n#define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr)\n#endif\n#define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1))\n#else\n#if !defined(JSON_HEDLEY_IS_CONSTANT)\n#define JSON_HEDLEY_IS_CONSTANT(expr) (0)\n#endif\n#define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr)\n#endif\n\n#if defined(JSON_HEDLEY_BEGIN_C_DECLS)\n#undef JSON_HEDLEY_BEGIN_C_DECLS\n#endif\n#if defined(JSON_HEDLEY_END_C_DECLS)\n#undef JSON_HEDLEY_END_C_DECLS\n#endif\n#if defined(JSON_HEDLEY_C_DECL)\n#undef JSON_HEDLEY_C_DECL\n#endif\n#if defined(__cplusplus)\n#define JSON_HEDLEY_BEGIN_C_DECLS extern \"C\" {\n#define JSON_HEDLEY_END_C_DECLS }\n#define JSON_HEDLEY_C_DECL extern \"C\"\n#else\n#define JSON_HEDLEY_BEGIN_C_DECLS\n#define JSON_HEDLEY_END_C_DECLS\n#define JSON_HEDLEY_C_DECL\n#endif\n\n#if defined(JSON_HEDLEY_STATIC_ASSERT)\n#undef JSON_HEDLEY_STATIC_ASSERT\n#endif\n#if \\\n  !defined(__cplusplus) && ( \\\n      (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \\\n      (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \\\n      JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \\\n      JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n      defined(_Static_assert) \\\n    )\n#  define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message)\n#elif \\\n  (defined(__cplusplus) && (__cplusplus >= 201103L)) || \\\n  JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \\\n  JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#  define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message))\n#else\n#  define JSON_HEDLEY_STATIC_ASSERT(expr, message)\n#endif\n\n#if defined(JSON_HEDLEY_NULL)\n#undef JSON_HEDLEY_NULL\n#endif\n#if defined(__cplusplus)\n#if __cplusplus >= 201103L\n#define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr)\n#elif defined(NULL)\n#define JSON_HEDLEY_NULL NULL\n#else\n#define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0)\n#endif\n#elif defined(NULL)\n#define JSON_HEDLEY_NULL NULL\n#else\n#define JSON_HEDLEY_NULL ((void*) 0)\n#endif\n\n#if defined(JSON_HEDLEY_MESSAGE)\n#undef JSON_HEDLEY_MESSAGE\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wunknown-pragmas\")\n#  define JSON_HEDLEY_MESSAGE(msg) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \\\n    JSON_HEDLEY_PRAGMA(message msg) \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#elif \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n#  define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg)\n#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0)\n#  define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg)\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n#  define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg))\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0)\n#  define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg))\n#else\n#  define JSON_HEDLEY_MESSAGE(msg)\n#endif\n\n#if defined(JSON_HEDLEY_WARNING)\n#undef JSON_HEDLEY_WARNING\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wunknown-pragmas\")\n#  define JSON_HEDLEY_WARNING(msg) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \\\n    JSON_HEDLEY_PRAGMA(clang warning msg) \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#elif \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \\\n  JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n#  define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg)\n#elif \\\n  JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \\\n  JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#  define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg))\n#else\n#  define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg)\n#endif\n\n#if defined(JSON_HEDLEY_REQUIRE)\n#undef JSON_HEDLEY_REQUIRE\n#endif\n#if defined(JSON_HEDLEY_REQUIRE_MSG)\n#undef JSON_HEDLEY_REQUIRE_MSG\n#endif\n#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if)\n#  if JSON_HEDLEY_HAS_WARNING(\"-Wgcc-compat\")\n#    define JSON_HEDLEY_REQUIRE(expr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wgcc-compat\\\"\") \\\n    __attribute__((diagnose_if(!(expr), #expr, \"error\"))) \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#    define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wgcc-compat\\\"\") \\\n    __attribute__((diagnose_if(!(expr), msg, \"error\"))) \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#  else\n#    define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, \"error\")))\n#    define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, \"error\")))\n#  endif\n#else\n#  define JSON_HEDLEY_REQUIRE(expr)\n#  define JSON_HEDLEY_REQUIRE_MSG(expr,msg)\n#endif\n\n#if defined(JSON_HEDLEY_FLAGS)\n#undef JSON_HEDLEY_FLAGS\n#endif\n#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING(\"-Wbitfield-enum-conversion\"))\n#define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__))\n#else\n#define JSON_HEDLEY_FLAGS\n#endif\n\n#if defined(JSON_HEDLEY_FLAGS_CAST)\n#undef JSON_HEDLEY_FLAGS_CAST\n#endif\n#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0)\n#  define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \\\n        JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n        _Pragma(\"warning(disable:188)\") \\\n        ((T) (expr)); \\\n        JSON_HEDLEY_DIAGNOSTIC_POP \\\n    }))\n#else\n#  define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr)\n#endif\n\n#if defined(JSON_HEDLEY_EMPTY_BASES)\n#undef JSON_HEDLEY_EMPTY_BASES\n#endif\n#if \\\n    (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases)\n#else\n#define JSON_HEDLEY_EMPTY_BASES\n#endif\n\n/* Remaining macros are deprecated. */\n\n#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK)\n#undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK\n#endif\n#if defined(__clang__)\n#define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0)\n#else\n#define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE)\n#undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE\n#endif\n#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE)\n#undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE\n#endif\n#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN)\n#undef JSON_HEDLEY_CLANG_HAS_BUILTIN\n#endif\n#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE)\n#undef JSON_HEDLEY_CLANG_HAS_FEATURE\n#endif\n#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION)\n#undef JSON_HEDLEY_CLANG_HAS_EXTENSION\n#endif\n#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE)\n#undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE\n#endif\n#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_WARNING)\n#undef JSON_HEDLEY_CLANG_HAS_WARNING\n#endif\n#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning)\n\n#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */\n\n\n// This file contains all internal macro definitions (except those affecting ABI)\n// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\n// exclude unsupported compilers\n#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK)\n#if defined(__clang__)\n#if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400\n#error \"unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers\"\n#endif\n#elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER))\n#if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800\n#error \"unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers\"\n#endif\n#endif\n#endif\n\n// C++ language standard detection\n// if the user manually specified the used c++ version this is skipped\n#if !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11)\n#if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)\n#define JSON_HAS_CPP_20\n#define JSON_HAS_CPP_17\n#define JSON_HAS_CPP_14\n#elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464\n#define JSON_HAS_CPP_17\n#define JSON_HAS_CPP_14\n#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)\n#define JSON_HAS_CPP_14\n#endif\n// the cpp 11 flag is always specified because it is the minimal required version\n#define JSON_HAS_CPP_11\n#endif\n\n#ifdef __has_include\n#if __has_include(<version>)\n#include <version>\n#endif\n#endif\n\n#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM)\n#ifdef JSON_HAS_CPP_17\n#if defined(__cpp_lib_filesystem)\n#define JSON_HAS_FILESYSTEM 1\n#elif defined(__cpp_lib_experimental_filesystem)\n#define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1\n#elif !defined(__has_include)\n#define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1\n#elif __has_include(<filesystem>)\n#define JSON_HAS_FILESYSTEM 1\n#elif __has_include(<experimental/filesystem>)\n#define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1\n#endif\n\n// std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/\n#if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8\n#undef JSON_HAS_FILESYSTEM\n#undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n#endif\n\n// no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support\n#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8\n#undef JSON_HAS_FILESYSTEM\n#undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n#endif\n\n// no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support\n#if defined(__clang_major__) && __clang_major__ < 7\n#undef JSON_HAS_FILESYSTEM\n#undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n#endif\n\n// no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support\n#if defined(_MSC_VER) && _MSC_VER < 1914\n#undef JSON_HAS_FILESYSTEM\n#undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n#endif\n\n// no filesystem support before iOS 13\n#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000\n#undef JSON_HAS_FILESYSTEM\n#undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n#endif\n\n// no filesystem support before macOS Catalina\n#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500\n#undef JSON_HAS_FILESYSTEM\n#undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n#endif\n#endif\n#endif\n\n#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n#define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0\n#endif\n\n#ifndef JSON_HAS_FILESYSTEM\n#define JSON_HAS_FILESYSTEM 0\n#endif\n\n#ifndef JSON_HAS_THREE_WAY_COMPARISON\n#if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \\\n        && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L\n#define JSON_HAS_THREE_WAY_COMPARISON 1\n#else\n#define JSON_HAS_THREE_WAY_COMPARISON 0\n#endif\n#endif\n\n#ifndef JSON_HAS_RANGES\n// ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error\n#if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427\n#define JSON_HAS_RANGES 0\n#elif defined(__cpp_lib_ranges)\n#define JSON_HAS_RANGES 1\n#else\n#define JSON_HAS_RANGES 0\n#endif\n#endif\n\n#ifdef JSON_HAS_CPP_17\n#define JSON_INLINE_VARIABLE inline\n#else\n#define JSON_INLINE_VARIABLE\n#endif\n\n#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address)\n#define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]]\n#else\n#define JSON_NO_UNIQUE_ADDRESS\n#endif\n\n// disable documentation warnings on clang\n#if defined(__clang__)\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wdocumentation\"\n#pragma clang diagnostic ignored \"-Wdocumentation-unknown-command\"\n#endif\n\n// allow disabling exceptions\n#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)\n#define JSON_THROW(exception) throw exception\n#define JSON_TRY try\n#define JSON_CATCH(exception) catch(exception)\n#define JSON_INTERNAL_CATCH(exception) catch(exception)\n#else\n#include <cstdlib>\n#define JSON_THROW(exception) std::abort()\n#define JSON_TRY if(true)\n#define JSON_CATCH(exception) if(false)\n#define JSON_INTERNAL_CATCH(exception) if(false)\n#endif\n\n// override exception macros\n#if defined(JSON_THROW_USER)\n#undef JSON_THROW\n#define JSON_THROW JSON_THROW_USER\n#endif\n#if defined(JSON_TRY_USER)\n#undef JSON_TRY\n#define JSON_TRY JSON_TRY_USER\n#endif\n#if defined(JSON_CATCH_USER)\n#undef JSON_CATCH\n#define JSON_CATCH JSON_CATCH_USER\n#undef JSON_INTERNAL_CATCH\n#define JSON_INTERNAL_CATCH JSON_CATCH_USER\n#endif\n#if defined(JSON_INTERNAL_CATCH_USER)\n#undef JSON_INTERNAL_CATCH\n#define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER\n#endif\n\n// allow overriding assert\n#if !defined(JSON_ASSERT)\n#include <cassert> // assert\n#define JSON_ASSERT(x) assert(x)\n#endif\n\n// allow to access some private functions (needed by the test suite)\n#if defined(JSON_TESTS_PRIVATE)\n#define JSON_PRIVATE_UNLESS_TESTED public\n#else\n#define JSON_PRIVATE_UNLESS_TESTED private\n#endif\n\n/*!\n@brief macro to briefly define a mapping between an enum and JSON\n@def NLOHMANN_JSON_SERIALIZE_ENUM\n@since version 3.4.0\n*/\n#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...)                                            \\\n    template<typename BasicJsonType>                                                            \\\n    inline void to_json(BasicJsonType& j, const ENUM_TYPE& e)                                   \\\n    {                                                                                           \\\n        static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE \" must be an enum!\");          \\\n        static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__;                     \\\n        auto it = std::find_if(std::begin(m), std::end(m),                                      \\\n                               [e](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool  \\\n        {                                                                                       \\\n            return ej_pair.first == e;                                                          \\\n        });                                                                                     \\\n        j = ((it != std::end(m)) ? it : std::begin(m))->second;                                 \\\n    }                                                                                           \\\n    template<typename BasicJsonType>                                                            \\\n    inline void from_json(const BasicJsonType& j, ENUM_TYPE& e)                                 \\\n    {                                                                                           \\\n        static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE \" must be an enum!\");          \\\n        static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__;                     \\\n        auto it = std::find_if(std::begin(m), std::end(m),                                      \\\n                               [&j](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \\\n        {                                                                                       \\\n            return ej_pair.second == j;                                                         \\\n        });                                                                                     \\\n        e = ((it != std::end(m)) ? it : std::begin(m))->first;                                  \\\n    }\n\n// Ugly macros to avoid uglier copy-paste when specializing basic_json. They\n// may be removed in the future once the class is split.\n\n#define NLOHMANN_BASIC_JSON_TPL_DECLARATION                                \\\n    template<template<typename, typename, typename...> class ObjectType,   \\\n             template<typename, typename...> class ArrayType,              \\\n             class StringType, class BooleanType, class NumberIntegerType, \\\n             class NumberUnsignedType, class NumberFloatType,              \\\n             template<typename> class AllocatorType,                       \\\n             template<typename, typename = void> class JSONSerializer,     \\\n             class BinaryType>\n\n#define NLOHMANN_BASIC_JSON_TPL                                            \\\n    basic_json<ObjectType, ArrayType, StringType, BooleanType,             \\\n    NumberIntegerType, NumberUnsignedType, NumberFloatType,                \\\n    AllocatorType, JSONSerializer, BinaryType>\n\n// Macros to simplify conversion from/to types\n\n#define NLOHMANN_JSON_EXPAND( x ) x\n#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME\n#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \\\n        NLOHMANN_JSON_PASTE64, \\\n        NLOHMANN_JSON_PASTE63, \\\n        NLOHMANN_JSON_PASTE62, \\\n        NLOHMANN_JSON_PASTE61, \\\n        NLOHMANN_JSON_PASTE60, \\\n        NLOHMANN_JSON_PASTE59, \\\n        NLOHMANN_JSON_PASTE58, \\\n        NLOHMANN_JSON_PASTE57, \\\n        NLOHMANN_JSON_PASTE56, \\\n        NLOHMANN_JSON_PASTE55, \\\n        NLOHMANN_JSON_PASTE54, \\\n        NLOHMANN_JSON_PASTE53, \\\n        NLOHMANN_JSON_PASTE52, \\\n        NLOHMANN_JSON_PASTE51, \\\n        NLOHMANN_JSON_PASTE50, \\\n        NLOHMANN_JSON_PASTE49, \\\n        NLOHMANN_JSON_PASTE48, \\\n        NLOHMANN_JSON_PASTE47, \\\n        NLOHMANN_JSON_PASTE46, \\\n        NLOHMANN_JSON_PASTE45, \\\n        NLOHMANN_JSON_PASTE44, \\\n        NLOHMANN_JSON_PASTE43, \\\n        NLOHMANN_JSON_PASTE42, \\\n        NLOHMANN_JSON_PASTE41, \\\n        NLOHMANN_JSON_PASTE40, \\\n        NLOHMANN_JSON_PASTE39, \\\n        NLOHMANN_JSON_PASTE38, \\\n        NLOHMANN_JSON_PASTE37, \\\n        NLOHMANN_JSON_PASTE36, \\\n        NLOHMANN_JSON_PASTE35, \\\n        NLOHMANN_JSON_PASTE34, \\\n        NLOHMANN_JSON_PASTE33, \\\n        NLOHMANN_JSON_PASTE32, \\\n        NLOHMANN_JSON_PASTE31, \\\n        NLOHMANN_JSON_PASTE30, \\\n        NLOHMANN_JSON_PASTE29, \\\n        NLOHMANN_JSON_PASTE28, \\\n        NLOHMANN_JSON_PASTE27, \\\n        NLOHMANN_JSON_PASTE26, \\\n        NLOHMANN_JSON_PASTE25, \\\n        NLOHMANN_JSON_PASTE24, \\\n        NLOHMANN_JSON_PASTE23, \\\n        NLOHMANN_JSON_PASTE22, \\\n        NLOHMANN_JSON_PASTE21, \\\n        NLOHMANN_JSON_PASTE20, \\\n        NLOHMANN_JSON_PASTE19, \\\n        NLOHMANN_JSON_PASTE18, \\\n        NLOHMANN_JSON_PASTE17, \\\n        NLOHMANN_JSON_PASTE16, \\\n        NLOHMANN_JSON_PASTE15, \\\n        NLOHMANN_JSON_PASTE14, \\\n        NLOHMANN_JSON_PASTE13, \\\n        NLOHMANN_JSON_PASTE12, \\\n        NLOHMANN_JSON_PASTE11, \\\n        NLOHMANN_JSON_PASTE10, \\\n        NLOHMANN_JSON_PASTE9, \\\n        NLOHMANN_JSON_PASTE8, \\\n        NLOHMANN_JSON_PASTE7, \\\n        NLOHMANN_JSON_PASTE6, \\\n        NLOHMANN_JSON_PASTE5, \\\n        NLOHMANN_JSON_PASTE4, \\\n        NLOHMANN_JSON_PASTE3, \\\n        NLOHMANN_JSON_PASTE2, \\\n        NLOHMANN_JSON_PASTE1)(__VA_ARGS__))\n#define NLOHMANN_JSON_PASTE2(func, v1) func(v1)\n#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2)\n#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3)\n#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4)\n#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5)\n#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6)\n#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7)\n#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8)\n#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9)\n#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10)\n#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11)\n#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12)\n#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13)\n#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14)\n#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15)\n#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16)\n#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17)\n#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18)\n#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19)\n#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20)\n#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21)\n#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22)\n#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23)\n#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24)\n#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25)\n#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26)\n#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27)\n#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28)\n#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29)\n#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30)\n#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31)\n#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32)\n#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33)\n#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34)\n#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35)\n#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36)\n#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37)\n#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38)\n#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39)\n#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40)\n#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41)\n#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42)\n#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43)\n#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44)\n#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45)\n#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46)\n#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47)\n#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48)\n#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49)\n#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50)\n#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51)\n#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52)\n#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53)\n#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54)\n#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55)\n#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56)\n#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57)\n#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58)\n#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59)\n#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60)\n#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61)\n#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62)\n#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63)\n\n#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1;\n#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1);\n#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1);\n\n/*!\n@brief macro\n@def NLOHMANN_DEFINE_TYPE_INTRUSIVE\n@since version 3.9.0\n*/\n#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...)  \\\n    friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \\\n    friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }\n\n#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...)  \\\n    friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \\\n    friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { Type nlohmann_json_default_obj; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }\n\n/*!\n@brief macro\n@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE\n@since version 3.9.0\n*/\n#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...)  \\\n    inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \\\n    inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }\n\n#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...)  \\\n    inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \\\n    inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { Type nlohmann_json_default_obj; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }\n\n\n// inspired from https://stackoverflow.com/a/26745591\n// allows to call any std function as if (e.g. with begin):\n// using std::begin; begin(x);\n//\n// it allows using the detected idiom to retrieve the return type\n// of such an expression\n#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name)                                 \\\n    namespace detail {                                                            \\\n    using std::std_name;                                                          \\\n    \\\n    template<typename... T>                                                       \\\n    using result_of_##std_name = decltype(std_name(std::declval<T>()...));        \\\n    }                                                                             \\\n    \\\n    namespace detail2 {                                                           \\\n    struct std_name##_tag                                                         \\\n    {                                                                             \\\n    };                                                                            \\\n    \\\n    template<typename... T>                                                       \\\n    std_name##_tag std_name(T&&...);                                              \\\n    \\\n    template<typename... T>                                                       \\\n    using result_of_##std_name = decltype(std_name(std::declval<T>()...));        \\\n    \\\n    template<typename... T>                                                       \\\n    struct would_call_std_##std_name                                              \\\n    {                                                                             \\\n        static constexpr auto const value = ::nlohmann::detail::                  \\\n                                            is_detected_exact<std_name##_tag, result_of_##std_name, T...>::value; \\\n    };                                                                            \\\n    } /* namespace detail2 */ \\\n    \\\n    template<typename... T>                                                       \\\n    struct would_call_std_##std_name : detail2::would_call_std_##std_name<T...>   \\\n    {                                                                             \\\n    }\n\n#ifndef JSON_USE_IMPLICIT_CONVERSIONS\n#define JSON_USE_IMPLICIT_CONVERSIONS 1\n#endif\n\n#if JSON_USE_IMPLICIT_CONVERSIONS\n#define JSON_EXPLICIT\n#else\n#define JSON_EXPLICIT explicit\n#endif\n\n#ifndef JSON_DISABLE_ENUM_SERIALIZATION\n#define JSON_DISABLE_ENUM_SERIALIZATION 0\n#endif\n\n#ifndef JSON_USE_GLOBAL_UDLS\n#define JSON_USE_GLOBAL_UDLS 1\n#endif\n\n#if JSON_HAS_THREE_WAY_COMPARISON\n#include <compare> // partial_ordering\n#endif\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n///////////////////////////\n// JSON type enumeration //\n///////////////////////////\n\n/*!\n@brief the JSON type enumeration\n\nThis enumeration collects the different JSON types. It is internally used to\ndistinguish the stored values, and the functions @ref basic_json::is_null(),\n@ref basic_json::is_object(), @ref basic_json::is_array(),\n@ref basic_json::is_string(), @ref basic_json::is_boolean(),\n@ref basic_json::is_number() (with @ref basic_json::is_number_integer(),\n@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()),\n@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and\n@ref basic_json::is_structured() rely on it.\n\n@note There are three enumeration entries (number_integer, number_unsigned, and\nnumber_float), because the library distinguishes these three types for numbers:\n@ref basic_json::number_unsigned_t is used for unsigned integers,\n@ref basic_json::number_integer_t is used for signed integers, and\n@ref basic_json::number_float_t is used for floating-point numbers or to\napproximate integers which do not fit in the limits of their respective type.\n\n@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON\nvalue with the default value for a given type\n\n@since version 1.0.0\n*/\nenum class value_t : std::uint8_t {\n  null,             ///< null value\n  object,           ///< object (unordered set of name/value pairs)\n  array,            ///< array (ordered collection of values)\n  string,           ///< string value\n  boolean,          ///< boolean value\n  number_integer,   ///< number value (signed integer)\n  number_unsigned,  ///< number value (unsigned integer)\n  number_float,     ///< number value (floating-point)\n  binary,           ///< binary array (ordered collection of bytes)\n  discarded         ///< discarded by the parser callback function\n};\n\n/*!\n@brief comparison operator for JSON types\n\nReturns an ordering that is similar to Python:\n- order: null < boolean < number < object < array < string < binary\n- furthermore, each type is not smaller than itself\n- discarded values are not comparable\n- binary is represented as a b\"\" string in python and directly comparable to a\n  string; however, making a binary array directly comparable with a string would\n  be surprising behavior in a JSON file.\n\n@since version 1.0.0\n*/\n#if JSON_HAS_THREE_WAY_COMPARISON\ninline std::partial_ordering operator<=>(const value_t lhs,\n    const value_t rhs) noexcept // *NOPAD*\n#else\ninline bool operator<(const value_t lhs, const value_t rhs) noexcept\n#endif\n{\n  static constexpr std::array<std::uint8_t, 9> order = {{\n      0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */,\n      1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */,\n      6 /* binary */\n    }\n  };\n  const auto l_index = static_cast<std::size_t>(lhs);\n  const auto r_index = static_cast<std::size_t>(rhs);\n#if JSON_HAS_THREE_WAY_COMPARISON\n\n  if (l_index < order.size() && r_index < order.size()) {\n    return order[l_index] <=> order[r_index]; // *NOPAD*\n  }\n\n  return std::partial_ordering::unordered;\n#else\n  return l_index < order.size() && r_index < order.size() &&\n         order[l_index] < order[r_index];\n#endif\n}\n\n// GCC selects the built-in operator< over an operator rewritten from\n// a user-defined spaceship operator\n// Clang, MSVC, and ICC select the rewritten candidate\n// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200)\n#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__)\ninline bool operator<(const value_t lhs, const value_t rhs) noexcept\n{\n  return std::is_lt(lhs <=> rhs); // *NOPAD*\n}\n#endif\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/string_escape.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/*!\n@brief replace all occurrences of a substring by another string\n\n@param[in,out] s  the string to manipulate; changed so that all\n               occurrences of @a f are replaced with @a t\n@param[in]     f  the substring to replace with @a t\n@param[in]     t  the string to replace @a f\n\n@pre The search string @a f must not be empty. **This precondition is\nenforced with an assertion.**\n\n@since version 2.0.0\n*/\ntemplate<typename StringType>\ninline void replace_substring(StringType& s, const StringType& f,\n                              const StringType& t)\n{\n  JSON_ASSERT(!f.empty());\n\n  for (auto pos = s.find(f);                // find first occurrence of f\n       pos != StringType::npos;          // make sure f was found\n       s.replace(pos, f.size(), t),      // replace with t, and\n       pos = s.find(f, pos + t.size()))  // find next occurrence of f\n  {}\n}\n\n/*!\n * @brief string escaping as described in RFC 6901 (Sect. 4)\n * @param[in] s string to escape\n * @return    escaped string\n *\n * Note the order of escaping \"~\" to \"~0\" and \"/\" to \"~1\" is important.\n */\ntemplate<typename StringType>\ninline StringType escape(StringType s)\n{\n  replace_substring(s, StringType{\"~\"}, StringType{\"~0\"});\n  replace_substring(s, StringType{\"/\"}, StringType{\"~1\"});\n  return s;\n}\n\n/*!\n * @brief string unescaping as described in RFC 6901 (Sect. 4)\n * @param[in] s string to unescape\n * @return    unescaped string\n *\n * Note the order of escaping \"~1\" to \"/\" and \"~0\" to \"~\" is important.\n */\ntemplate<typename StringType>\nstatic void unescape(StringType& s)\n{\n  replace_substring(s, StringType{\"~1\"}, StringType{\"/\"});\n  replace_substring(s, StringType{\"~0\"}, StringType{\"~\"});\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/input/position_t.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef> // size_t\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/// struct to capture the start position of the current token\nstruct position_t {\n  /// the total number of characters read\n  std::size_t chars_read_total = 0;\n  /// the number of characters read in the current line\n  std::size_t chars_read_current_line = 0;\n  /// the number of lines read\n  std::size_t lines_read = 0;\n\n  /// conversion to size_t to preserve SAX interface\n  constexpr operator size_t() const\n  {\n    return chars_read_total;\n  }\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-FileCopyrightText: 2018 The Abseil Authors\n// SPDX-License-Identifier: MIT\n\n\n\n#include <array> // array\n#include <cstddef> // size_t\n#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type\n#include <utility> // index_sequence, make_index_sequence, index_sequence_for\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename T>\nusing uncvref_t = typename\n                  std::remove_cv<typename std::remove_reference<T>::type>::type;\n\n#ifdef JSON_HAS_CPP_14\n\n// the following utilities are natively available in C++14\nusing std::enable_if_t;\nusing std::index_sequence;\nusing std::make_index_sequence;\nusing std::index_sequence_for;\n\n#else\n\n// alias templates to reduce boilerplate\ntemplate<bool B, typename T = void>\nusing enable_if_t = typename std::enable_if<B, T>::type;\n\n// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h\n// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0.\n\n//// START OF CODE FROM GOOGLE ABSEIL\n\n// integer_sequence\n//\n// Class template representing a compile-time integer sequence. An instantiation\n// of `integer_sequence<T, Ints...>` has a sequence of integers encoded in its\n// type through its template arguments (which is a common need when\n// working with C++11 variadic templates). `absl::integer_sequence` is designed\n// to be a drop-in replacement for C++14's `std::integer_sequence`.\n//\n// Example:\n//\n//   template< class T, T... Ints >\n//   void user_function(integer_sequence<T, Ints...>);\n//\n//   int main()\n//   {\n//     // user_function's `T` will be deduced to `int` and `Ints...`\n//     // will be deduced to `0, 1, 2, 3, 4`.\n//     user_function(make_integer_sequence<int, 5>());\n//   }\ntemplate <typename T, T... Ints>\nstruct integer_sequence {\n  using value_type = T;\n  static constexpr std::size_t size() noexcept\n  {\n    return sizeof...(Ints);\n  }\n};\n\n// index_sequence\n//\n// A helper template for an `integer_sequence` of `size_t`,\n// `absl::index_sequence` is designed to be a drop-in replacement for C++14's\n// `std::index_sequence`.\ntemplate <size_t... Ints>\nusing index_sequence = integer_sequence<size_t, Ints...>;\n\nnamespace utility_internal\n{\n\ntemplate <typename Seq, size_t SeqSize, size_t Rem>\nstruct Extend;\n\n// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency.\ntemplate <typename T, T... Ints, size_t SeqSize>\nstruct Extend<integer_sequence<T, Ints...>, SeqSize, 0> {\n  using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >;\n};\n\ntemplate <typename T, T... Ints, size_t SeqSize>\nstruct Extend<integer_sequence<T, Ints...>, SeqSize, 1> {\n  using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >;\n};\n\n// Recursion helper for 'make_integer_sequence<T, N>'.\n// 'Gen<T, N>::type' is an alias for 'integer_sequence<T, 0, 1, ... N-1>'.\ntemplate <typename T, size_t N>\nstruct Gen {\n  using type =\n    typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type;\n};\n\ntemplate <typename T>\nstruct Gen<T, 0> {\n  using type = integer_sequence<T>;\n};\n\n}  // namespace utility_internal\n\n// Compile-time sequences of integers\n\n// make_integer_sequence\n//\n// This template alias is equivalent to\n// `integer_sequence<int, 0, 1, ..., N-1>`, and is designed to be a drop-in\n// replacement for C++14's `std::make_integer_sequence`.\ntemplate <typename T, T N>\nusing make_integer_sequence = typename utility_internal::Gen<T, N>::type;\n\n// make_index_sequence\n//\n// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`,\n// and is designed to be a drop-in replacement for C++14's\n// `std::make_index_sequence`.\ntemplate <size_t N>\nusing make_index_sequence = make_integer_sequence<size_t, N>;\n\n// index_sequence_for\n//\n// Converts a typename pack into an index sequence of the same length, and\n// is designed to be a drop-in replacement for C++14's\n// `std::index_sequence_for()`\ntemplate <typename... Ts>\nusing index_sequence_for = make_index_sequence<sizeof...(Ts)>;\n\n//// END OF CODE FROM GOOGLE ABSEIL\n\n#endif\n\n// dispatch utility (taken from ranges-v3)\ntemplate<unsigned N> struct priority_tag : priority_tag < N - 1 > {};\ntemplate<> struct priority_tag<0> {};\n\n// taken from ranges-v3\ntemplate<typename T>\nstruct static_const {\n  static JSON_INLINE_VARIABLE constexpr T value{};\n};\n\n#ifndef JSON_HAS_CPP_17\ntemplate<typename T>\nconstexpr T static_const<T>::value;\n#endif\n\ntemplate<typename T, typename... Args>\ninline constexpr std::array<T, sizeof...(Args)> make_array(Args&& ... args)\n{\n  return std::array<T, sizeof...(Args)> {{static_cast<T>(std::forward<Args>(args))...}};\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <limits> // numeric_limits\n#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type\n#include <utility> // declval\n#include <tuple> // tuple\n\n// #include <nlohmann/detail/iterators/iterator_traits.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <iterator> // random_access_iterator_tag\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/meta/void_t.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename It, typename = void>\nstruct iterator_types {};\n\ntemplate<typename It>\nstruct iterator_types <\n  It,\n  void_t<typename It::difference_type, typename It::value_type, typename It::pointer,\n  typename It::reference, typename It::iterator_category >> {\n  using difference_type = typename It::difference_type;\n  using value_type = typename It::value_type;\n  using pointer = typename It::pointer;\n  using reference = typename It::reference;\n  using iterator_category = typename It::iterator_category;\n};\n\n// This is required as some compilers implement std::iterator_traits in a way that\n// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341.\ntemplate<typename T, typename = void>\nstruct iterator_traits {\n};\n\ntemplate<typename T>\nstruct iterator_traits < T, enable_if_t < !std::is_pointer<T>::value >>\n      : iterator_types<T> {\n};\n\ntemplate<typename T>\nstruct iterator_traits<T*, enable_if_t<std::is_object<T>::value>> {\n  using iterator_category = std::random_access_iterator_tag;\n  using value_type = T;\n  using difference_type = ptrdiff_t;\n  using pointer = T*;\n  using reference = T&;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/call_std/begin.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\nNLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin);\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/call_std/end.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\nNLOHMANN_CAN_CALL_STD_FUNC_IMPL(end);\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/detected.hpp>\n\n// #include <nlohmann/json_fwd.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_\n#define INCLUDE_NLOHMANN_JSON_FWD_HPP_\n\n#include <cstdint> // int64_t, uint64_t\n#include <map> // map\n#include <memory> // allocator\n#include <string> // string\n#include <vector> // vector\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\n/*!\n@brief namespace for Niels Lohmann\n@see https://github.com/nlohmann\n@since version 1.0.0\n*/\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/*!\n@brief default JSONSerializer template argument\n\nThis serializer ignores the template arguments and uses ADL\n([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))\nfor serialization.\n*/\ntemplate<typename T = void, typename SFINAE = void>\nstruct adl_serializer;\n\n/// a class to store JSON values\n/// @sa https://json.nlohmann.me/api/basic_json/\ntemplate<template<typename U, typename V, typename... Args> class ObjectType =\n         std::map,\n         template<typename U, typename... Args> class ArrayType = std::vector,\n         class StringType = std::string, class BooleanType = bool,\n         class NumberIntegerType = std::int64_t,\n         class NumberUnsignedType = std::uint64_t,\n         class NumberFloatType = double,\n         template<typename U> class AllocatorType = std::allocator,\n         template<typename T, typename SFINAE = void> class JSONSerializer =\n         adl_serializer,\n         class BinaryType = std::vector<std::uint8_t>>\nclass basic_json;\n\n/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document\n/// @sa https://json.nlohmann.me/api/json_pointer/\ntemplate<typename RefStringType>\nclass json_pointer;\n\n/*!\n@brief default specialization\n@sa https://json.nlohmann.me/api/json/\n*/\nusing json = basic_json<>;\n\n/// @brief a minimal map-like container that preserves insertion order\n/// @sa https://json.nlohmann.me/api/ordered_map/\ntemplate<class Key, class T, class IgnoredLess, class Allocator>\nstruct ordered_map;\n\n/// @brief specialization that maintains the insertion order of object keys\n/// @sa https://json.nlohmann.me/api/ordered_json/\nusing ordered_json = basic_json<nlohmann::ordered_map>;\n\nNLOHMANN_JSON_NAMESPACE_END\n\n#endif  // INCLUDE_NLOHMANN_JSON_FWD_HPP_\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n/*!\n@brief detail namespace with internal helper functions\n\nThis namespace collects functions that should not be exposed,\nimplementations of some @ref basic_json methods, and meta-programming helpers.\n\n@since version 2.1.0\n*/\nnamespace detail\n{\n\n/////////////\n// helpers //\n/////////////\n\n// Note to maintainers:\n//\n// Every trait in this file expects a non CV-qualified type.\n// The only exceptions are in the 'aliases for detected' section\n// (i.e. those of the form: decltype(T::member_function(std::declval<T>())))\n//\n// In this case, T has to be properly CV-qualified to constraint the function arguments\n// (e.g. to_json(BasicJsonType&, const T&))\n\ntemplate<typename> struct is_basic_json : std::false_type {};\n\nNLOHMANN_BASIC_JSON_TPL_DECLARATION\nstruct is_basic_json<NLOHMANN_BASIC_JSON_TPL> : std::true_type {};\n\n// used by exceptions create() member functions\n// true_type for pointer to possibly cv-qualified basic_json or std::nullptr_t\n// false_type otherwise\ntemplate<typename BasicJsonContext>\nstruct is_basic_json_context :\n  std::integral_constant < bool,\n  is_basic_json<typename std::remove_cv<typename std::remove_pointer<BasicJsonContext>::type>::type>::value\n  || std::is_same<BasicJsonContext, std::nullptr_t>::value > {\n};\n\n//////////////////////\n// json_ref helpers //\n//////////////////////\n\ntemplate<typename>\nclass json_ref;\n\ntemplate<typename>\nstruct is_json_ref : std::false_type {};\n\ntemplate<typename T>\nstruct is_json_ref<json_ref<T>> : std::true_type {};\n\n//////////////////////////\n// aliases for detected //\n//////////////////////////\n\ntemplate<typename T>\nusing mapped_type_t = typename T::mapped_type;\n\ntemplate<typename T>\nusing key_type_t = typename T::key_type;\n\ntemplate<typename T>\nusing value_type_t = typename T::value_type;\n\ntemplate<typename T>\nusing difference_type_t = typename T::difference_type;\n\ntemplate<typename T>\nusing pointer_t = typename T::pointer;\n\ntemplate<typename T>\nusing reference_t = typename T::reference;\n\ntemplate<typename T>\nusing iterator_category_t = typename T::iterator_category;\n\ntemplate<typename T, typename... Args>\nusing to_json_function = decltype(T::to_json(std::declval<Args>()...));\n\ntemplate<typename T, typename... Args>\nusing from_json_function = decltype(T::from_json(std::declval<Args>()...));\n\ntemplate<typename T, typename U>\nusing get_template_function = decltype(std::declval<T>().template get<U>());\n\n// trait checking if JSONSerializer<T>::from_json(json const&, udt&) exists\ntemplate<typename BasicJsonType, typename T, typename = void>\nstruct has_from_json : std::false_type {};\n\n// trait checking if j.get<T> is valid\n// use this trait instead of std::is_constructible or std::is_convertible,\n// both rely on, or make use of implicit conversions, and thus fail when T\n// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958)\ntemplate <typename BasicJsonType, typename T>\nstruct is_getable {\n  static constexpr bool value =\n    is_detected<get_template_function, const BasicJsonType&, T>::value;\n};\n\ntemplate<typename BasicJsonType, typename T>\nstruct has_from_json < BasicJsonType, T,\n         enable_if_t < !is_basic_json<T>::value >> {\n  using serializer = typename BasicJsonType::template json_serializer<T, void>;\n\n  static constexpr bool value =\n    is_detected_exact<void, from_json_function, serializer,\n    const BasicJsonType&, T&>::value;\n};\n\n// This trait checks if JSONSerializer<T>::from_json(json const&) exists\n// this overload is used for non-default-constructible user-defined-types\ntemplate<typename BasicJsonType, typename T, typename = void>\nstruct has_non_default_from_json : std::false_type {};\n\ntemplate<typename BasicJsonType, typename T>\nstruct has_non_default_from_json < BasicJsonType, T,\n         enable_if_t < !is_basic_json<T>::value >> {\n  using serializer = typename BasicJsonType::template json_serializer<T, void>;\n\n  static constexpr bool value =\n    is_detected_exact<T, from_json_function, serializer,\n    const BasicJsonType&>::value;\n};\n\n// This trait checks if BasicJsonType::json_serializer<T>::to_json exists\n// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion.\ntemplate<typename BasicJsonType, typename T, typename = void>\nstruct has_to_json : std::false_type {};\n\ntemplate<typename BasicJsonType, typename T>\nstruct has_to_json < BasicJsonType, T,\n         enable_if_t < !is_basic_json<T>::value >> {\n  using serializer = typename BasicJsonType::template json_serializer<T, void>;\n\n  static constexpr bool value =\n    is_detected_exact<void, to_json_function, serializer, BasicJsonType&,\n    T>::value;\n};\n\ntemplate<typename T>\nusing detect_key_compare = typename T::key_compare;\n\ntemplate<typename T>\nstruct has_key_compare :\n  std::integral_constant<bool, is_detected<detect_key_compare, T>::value> {};\n\n// obtains the actual object key comparator\ntemplate<typename BasicJsonType>\nstruct actual_object_comparator {\n  using object_t = typename BasicJsonType::object_t;\n  using object_comparator_t = typename BasicJsonType::default_object_comparator_t;\n  using type = typename std::conditional < has_key_compare<object_t>::value,\n        typename object_t::key_compare, object_comparator_t>::type;\n};\n\ntemplate<typename BasicJsonType>\nusing actual_object_comparator_t = typename\n                                   actual_object_comparator<BasicJsonType>::type;\n\n///////////////////\n// is_ functions //\n///////////////////\n\n// https://en.cppreference.com/w/cpp/types/conjunction\ntemplate<class...> struct conjunction : std::true_type { };\ntemplate<class B> struct conjunction<B> : B { };\ntemplate<class B, class... Bn>\nstruct conjunction<B, Bn...>\n: std::conditional<static_cast<bool>(B::value), conjunction<Bn...>, B>::type {};\n\n// https://en.cppreference.com/w/cpp/types/negation\ntemplate<class B> struct negation : std::integral_constant < bool,\n  !B::value > { };\n\n// Reimplementation of is_constructible and is_default_constructible, due to them being broken for\n// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367).\n// This causes compile errors in e.g. clang 3.5 or gcc 4.9.\ntemplate <typename T>\nstruct is_default_constructible : std::is_default_constructible<T> {};\n\ntemplate <typename T1, typename T2>\nstruct is_default_constructible<std::pair<T1, T2>>\n      : conjunction<is_default_constructible<T1>, is_default_constructible<T2>> {};\n\ntemplate <typename T1, typename T2>\nstruct is_default_constructible<const std::pair<T1, T2>>\n      : conjunction<is_default_constructible<T1>, is_default_constructible<T2>> {};\n\ntemplate <typename... Ts>\nstruct is_default_constructible<std::tuple<Ts...>>\n      : conjunction<is_default_constructible<Ts>...> {};\n\ntemplate <typename... Ts>\nstruct is_default_constructible<const std::tuple<Ts...>>\n      : conjunction<is_default_constructible<Ts>...> {};\n\n\ntemplate <typename T, typename... Args>\nstruct is_constructible : std::is_constructible<T, Args...> {};\n\ntemplate <typename T1, typename T2>\nstruct is_constructible<std::pair<T1, T2>> :\n                                          is_default_constructible<std::pair<T1, T2>> {};\n\ntemplate <typename T1, typename T2>\nstruct is_constructible<const std::pair<T1, T2>> :\n      is_default_constructible<const std::pair<T1, T2>> {};\n\ntemplate <typename... Ts>\nstruct is_constructible<std::tuple<Ts...>> :\n                                          is_default_constructible<std::tuple<Ts...>> {};\n\ntemplate <typename... Ts>\nstruct is_constructible<const std::tuple<Ts...>> :\n      is_default_constructible<const std::tuple<Ts...>> {};\n\n\ntemplate<typename T, typename = void>\nstruct is_iterator_traits : std::false_type {};\n\ntemplate<typename T>\nstruct is_iterator_traits<iterator_traits<T>> {\nprivate:\n  using traits = iterator_traits<T>;\n\npublic:\n  static constexpr auto value =\n    is_detected<value_type_t, traits>::value &&\n    is_detected<difference_type_t, traits>::value &&\n    is_detected<pointer_t, traits>::value &&\n    is_detected<iterator_category_t, traits>::value &&\n    is_detected<reference_t, traits>::value;\n};\n\ntemplate<typename T>\nstruct is_range {\nprivate:\n  using t_ref = typename std::add_lvalue_reference<T>::type;\n\n  using iterator = detected_t<result_of_begin, t_ref>;\n  using sentinel = detected_t<result_of_end, t_ref>;\n\n  // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator\n  // and https://en.cppreference.com/w/cpp/iterator/sentinel_for\n  // but reimplementing these would be too much work, as a lot of other concepts are used underneath\n  static constexpr auto is_iterator_begin =\n    is_iterator_traits<iterator_traits<iterator>>::value;\n\npublic:\n  static constexpr bool value = !std::is_same<iterator, nonesuch>::value &&\n                                !std::is_same<sentinel, nonesuch>::value && is_iterator_begin;\n};\n\ntemplate<typename R>\nusing iterator_t =\n  enable_if_t<is_range<R>::value, result_of_begin<decltype(std::declval<R&>())>>;\n\ntemplate<typename T>\nusing range_value_t = value_type_t<iterator_traits<iterator_t<T>>>;\n\n// The following implementation of is_complete_type is taken from\n// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/\n// and is written by Xiang Fan who agreed to using it in this library.\n\ntemplate<typename T, typename = void>\nstruct is_complete_type : std::false_type {};\n\ntemplate<typename T>\nstruct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type {};\n\ntemplate<typename BasicJsonType, typename CompatibleObjectType,\n         typename = void>\nstruct is_compatible_object_type_impl : std::false_type {};\n\ntemplate<typename BasicJsonType, typename CompatibleObjectType>\nstruct is_compatible_object_type_impl <\n  BasicJsonType, CompatibleObjectType,\n  enable_if_t < is_detected<mapped_type_t, CompatibleObjectType>::value&&\n  is_detected<key_type_t, CompatibleObjectType>::value >> {\n  using object_t = typename BasicJsonType::object_t;\n\n  // macOS's is_constructible does not play well with nonesuch...\n  static constexpr bool value =\n    is_constructible<typename object_t::key_type,\n    typename CompatibleObjectType::key_type>::value &&\n    is_constructible<typename object_t::mapped_type,\n    typename CompatibleObjectType::mapped_type>::value;\n};\n\ntemplate<typename BasicJsonType, typename CompatibleObjectType>\nstruct is_compatible_object_type\n  : is_compatible_object_type_impl<BasicJsonType, CompatibleObjectType> {};\n\ntemplate<typename BasicJsonType, typename ConstructibleObjectType,\n         typename = void>\nstruct is_constructible_object_type_impl : std::false_type {};\n\ntemplate<typename BasicJsonType, typename ConstructibleObjectType>\nstruct is_constructible_object_type_impl <\n  BasicJsonType, ConstructibleObjectType,\n  enable_if_t < is_detected<mapped_type_t, ConstructibleObjectType>::value&&\n  is_detected<key_type_t, ConstructibleObjectType>::value >> {\n  using object_t = typename BasicJsonType::object_t;\n\n  static constexpr bool value =\n    (is_default_constructible<ConstructibleObjectType>::value &&\n     (std::is_move_assignable<ConstructibleObjectType>::value ||\n      std::is_copy_assignable<ConstructibleObjectType>::value) &&\n     (is_constructible<typename ConstructibleObjectType::key_type,\n      typename object_t::key_type>::value &&\n      std::is_same <\n      typename object_t::mapped_type,\n      typename ConstructibleObjectType::mapped_type >::value)) ||\n    (has_from_json<BasicJsonType,\n     typename ConstructibleObjectType::mapped_type>::value ||\n     has_non_default_from_json <\n     BasicJsonType,\n     typename ConstructibleObjectType::mapped_type >::value);\n};\n\ntemplate<typename BasicJsonType, typename ConstructibleObjectType>\nstruct is_constructible_object_type\n  : is_constructible_object_type_impl<BasicJsonType,\n    ConstructibleObjectType> {};\n\ntemplate<typename BasicJsonType, typename CompatibleStringType>\nstruct is_compatible_string_type {\n  static constexpr auto value =\n    is_constructible<typename BasicJsonType::string_t, CompatibleStringType>::value;\n};\n\ntemplate<typename BasicJsonType, typename ConstructibleStringType>\nstruct is_constructible_string_type {\n  // launder type through decltype() to fix compilation failure on ICPC\n#ifdef __INTEL_COMPILER\n  using laundered_type = decltype(std::declval<ConstructibleStringType>());\n#else\n  using laundered_type = ConstructibleStringType;\n#endif\n\n  static constexpr auto value =\n    conjunction <\n    is_constructible<laundered_type, typename BasicJsonType::string_t>,\n    is_detected_exact<typename BasicJsonType::string_t::value_type,\n    value_type_t, laundered_type >>::value;\n};\n\ntemplate<typename BasicJsonType, typename CompatibleArrayType, typename = void>\nstruct is_compatible_array_type_impl : std::false_type {};\n\ntemplate<typename BasicJsonType, typename CompatibleArrayType>\nstruct is_compatible_array_type_impl <\n  BasicJsonType, CompatibleArrayType,\n  enable_if_t <\n  is_detected<iterator_t, CompatibleArrayType>::value&&\n  is_iterator_traits<iterator_traits<detected_t<iterator_t, CompatibleArrayType>>>::value&&\n// special case for types like std::filesystem::path whose iterator's value_type are themselves\n// c.f. https://github.com/nlohmann/json/pull/3073\n  !std::is_same<CompatibleArrayType, detected_t<range_value_t, CompatibleArrayType>>::value\n      >> {\n  static constexpr bool value =\n    is_constructible<BasicJsonType,\n    range_value_t<CompatibleArrayType>>::value;\n};\n\ntemplate<typename BasicJsonType, typename CompatibleArrayType>\nstruct is_compatible_array_type\n  : is_compatible_array_type_impl<BasicJsonType, CompatibleArrayType> {};\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType, typename = void>\nstruct is_constructible_array_type_impl : std::false_type {};\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType>\nstruct is_constructible_array_type_impl <\n  BasicJsonType, ConstructibleArrayType,\n  enable_if_t<std::is_same<ConstructibleArrayType,\n  typename BasicJsonType::value_type>::value >>\n      : std::true_type {};\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType>\nstruct is_constructible_array_type_impl <\n  BasicJsonType, ConstructibleArrayType,\n  enable_if_t < !std::is_same<ConstructibleArrayType,\n  typename BasicJsonType::value_type>::value&&\n  !is_compatible_string_type<BasicJsonType, ConstructibleArrayType>::value&&\n  is_default_constructible<ConstructibleArrayType>::value&&\n(std::is_move_assignable<ConstructibleArrayType>::value ||\n std::is_copy_assignable<ConstructibleArrayType>::value)&&\nis_detected<iterator_t, ConstructibleArrayType>::value&&\nis_iterator_traits<iterator_traits<detected_t<iterator_t, ConstructibleArrayType>>>::value&&\nis_detected<range_value_t, ConstructibleArrayType>::value&&\n// special case for types like std::filesystem::path whose iterator's value_type are themselves\n// c.f. https://github.com/nlohmann/json/pull/3073\n!std::is_same<ConstructibleArrayType, detected_t<range_value_t, ConstructibleArrayType>>::value&&\n    is_complete_type <\ndetected_t<range_value_t, ConstructibleArrayType >>::value >> {\n  using value_type = range_value_t<ConstructibleArrayType>;\n\n  static constexpr bool value =\n    std::is_same<value_type,\n    typename BasicJsonType::array_t::value_type>::value ||\n    has_from_json<BasicJsonType,\n    value_type>::value ||\n    has_non_default_from_json <\n    BasicJsonType,\n    value_type >::value;\n};\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType>\nstruct is_constructible_array_type\n  : is_constructible_array_type_impl<BasicJsonType, ConstructibleArrayType> {};\n\ntemplate<typename RealIntegerType, typename CompatibleNumberIntegerType,\n         typename = void>\nstruct is_compatible_integer_type_impl : std::false_type {};\n\ntemplate<typename RealIntegerType, typename CompatibleNumberIntegerType>\nstruct is_compatible_integer_type_impl <\n  RealIntegerType, CompatibleNumberIntegerType,\n  enable_if_t < std::is_integral<RealIntegerType>::value&&\n  std::is_integral<CompatibleNumberIntegerType>::value&&\n  !std::is_same<bool, CompatibleNumberIntegerType>::value >> {\n  // is there an assert somewhere on overflows?\n  using RealLimits = std::numeric_limits<RealIntegerType>;\n  using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>;\n\n  static constexpr auto value =\n    is_constructible<RealIntegerType,\n    CompatibleNumberIntegerType>::value &&\n    CompatibleLimits::is_integer &&\n    RealLimits::is_signed == CompatibleLimits::is_signed;\n};\n\ntemplate<typename RealIntegerType, typename CompatibleNumberIntegerType>\nstruct is_compatible_integer_type\n  : is_compatible_integer_type_impl<RealIntegerType,\n    CompatibleNumberIntegerType> {};\n\ntemplate<typename BasicJsonType, typename CompatibleType, typename = void>\nstruct is_compatible_type_impl: std::false_type {};\n\ntemplate<typename BasicJsonType, typename CompatibleType>\nstruct is_compatible_type_impl <\n  BasicJsonType, CompatibleType,\n  enable_if_t<is_complete_type<CompatibleType>::value >> {\n  static constexpr bool value =\n    has_to_json<BasicJsonType, CompatibleType>::value;\n};\n\ntemplate<typename BasicJsonType, typename CompatibleType>\nstruct is_compatible_type\n  : is_compatible_type_impl<BasicJsonType, CompatibleType> {};\n\ntemplate<typename T1, typename T2>\nstruct is_constructible_tuple : std::false_type {};\n\ntemplate<typename T1, typename... Args>\nstruct is_constructible_tuple<T1, std::tuple<Args...>> :\n      conjunction<is_constructible<T1, Args>...> {};\n\ntemplate<typename BasicJsonType, typename T>\nstruct is_json_iterator_of : std::false_type {};\n\ntemplate<typename BasicJsonType>\nstruct is_json_iterator_of<BasicJsonType, typename BasicJsonType::iterator> :\n  std::true_type {};\n\ntemplate<typename BasicJsonType>\nstruct is_json_iterator_of<BasicJsonType, typename BasicJsonType::const_iterator> :\n  std::true_type {\n};\n\n// checks if a given type T is a template specialization of Primary\ntemplate<template <typename...> class Primary, typename T>\nstruct is_specialization_of : std::false_type {};\n\ntemplate<template <typename...> class Primary, typename... Args>\nstruct is_specialization_of<Primary, Primary<Args...>> : std::true_type {};\n\ntemplate<typename T>\nusing is_json_pointer =\n  is_specialization_of<::nlohmann::json_pointer, uncvref_t<T>>;\n\n// checks if A and B are comparable using Compare functor\ntemplate<typename Compare, typename A, typename B, typename = void>\nstruct is_comparable : std::false_type {};\n\ntemplate<typename Compare, typename A, typename B>\nstruct is_comparable<Compare, A, B, void_t<\ndecltype(std::declval<Compare>()(std::declval<A>(), std::declval<B>())),\ndecltype(std::declval<Compare>()(std::declval<B>(), std::declval<A>()))\n>> : std::true_type {};\n\ntemplate<typename T>\nusing detect_is_transparent = typename T::is_transparent;\n\n// type trait to check if KeyType can be used as object key (without a BasicJsonType)\n// see is_usable_as_basic_json_key_type below\ntemplate<typename Comparator, typename ObjectKeyType, typename KeyTypeCVRef, bool RequireTransparentComparator = true,\n         bool ExcludeObjectKeyType = RequireTransparentComparator, typename KeyType = uncvref_t<KeyTypeCVRef>>\nusing is_usable_as_key_type = typename std::conditional <\n                              is_comparable<Comparator, ObjectKeyType, KeyTypeCVRef>::value\n                              && !(ExcludeObjectKeyType && std::is_same<KeyType,\n                                   ObjectKeyType>::value)\n                              && (!RequireTransparentComparator\n                                  || is_detected <detect_is_transparent, Comparator>::value)\n                              && !is_json_pointer<KeyType>::value,\n                              std::true_type,\n                              std::false_type >::type;\n\n// type trait to check if KeyType can be used as object key\n// true if:\n//   - KeyType is comparable with BasicJsonType::object_t::key_type\n//   - if ExcludeObjectKeyType is true, KeyType is not BasicJsonType::object_t::key_type\n//   - the comparator is transparent or RequireTransparentComparator is false\n//   - KeyType is not a JSON iterator or json_pointer\ntemplate<typename BasicJsonType, typename KeyTypeCVRef, bool RequireTransparentComparator = true,\n         bool ExcludeObjectKeyType = RequireTransparentComparator, typename KeyType = uncvref_t<KeyTypeCVRef>>\nusing is_usable_as_basic_json_key_type = typename std::conditional <\n    is_usable_as_key_type<typename BasicJsonType::object_comparator_t,\n    typename BasicJsonType::object_t::key_type, KeyTypeCVRef,\n    RequireTransparentComparator, ExcludeObjectKeyType>::value\n    && !is_json_iterator_of<BasicJsonType, KeyType>::value,\n    std::true_type,\n    std::false_type >::type;\n\ntemplate<typename ObjectType, typename KeyType>\nusing detect_erase_with_key_type = decltype(std::declval<ObjectType&>().erase(\n                                     std::declval<KeyType>()));\n\n// type trait to check if object_t has an erase() member functions accepting KeyType\ntemplate<typename BasicJsonType, typename KeyType>\nusing has_erase_with_key_type = typename std::conditional <\n                                is_detected <\n                                detect_erase_with_key_type,\n                                typename BasicJsonType::object_t, KeyType >::value,\n                                std::true_type,\n                                std::false_type >::type;\n\n// a naive helper to check if a type is an ordered_map (exploits the fact that\n// ordered_map inherits capacity() from std::vector)\ntemplate <typename T>\nstruct is_ordered_map {\n  using one = char;\n\n  struct two {\n    char x[2]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n  };\n\n  template <typename C> static one test(decltype(&C::capacity)) ;\n  template <typename C> static two test(...);\n\n  enum { value = sizeof(test<T>(nullptr)) == sizeof(char) }; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n};\n\n// to avoid useless casts (see https://github.com/nlohmann/json/issues/2893#issuecomment-889152324)\ntemplate < typename T, typename U, enable_if_t < !std::is_same<T, U>::value,\n           int > = 0 >\nT conditional_static_cast(U value)\n{\n  return static_cast<T>(value);\n}\n\ntemplate<typename T, typename U, enable_if_t<std::is_same<T, U>::value, int> = 0>\nT conditional_static_cast(U value)\n{\n  return value;\n}\n\ntemplate<typename... Types>\nusing all_integral = conjunction<std::is_integral<Types>...>;\n\ntemplate<typename... Types>\nusing all_signed = conjunction<std::is_signed<Types>...>;\n\ntemplate<typename... Types>\nusing all_unsigned = conjunction<std::is_unsigned<Types>...>;\n\n// there's a disjunction trait in another PR; replace when merged\ntemplate<typename... Types>\nusing same_sign = std::integral_constant < bool,\n      all_signed<Types...>::value || all_unsigned<Types...>::value >;\n\ntemplate<typename OfType, typename T>\nusing never_out_of_range = std::integral_constant < bool,\n      (std::is_signed<OfType>::value && (sizeof(T) < sizeof(OfType)))\n      || (same_sign<OfType, T>::value && sizeof(OfType) == sizeof(T)) >;\n\ntemplate<typename OfType, typename T,\n         bool OfTypeSigned = std::is_signed<OfType>::value,\n         bool TSigned = std::is_signed<T>::value>\nstruct value_in_range_of_impl2;\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl2<OfType, T, false, false> {\n  static constexpr bool test(T val)\n  {\n    using CommonType = typename std::common_type<OfType, T>::type;\n    return static_cast<CommonType>(val) <= static_cast<CommonType>((\n             std::numeric_limits<OfType>::max)());\n  }\n};\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl2<OfType, T, true, false> {\n  static constexpr bool test(T val)\n  {\n    using CommonType = typename std::common_type<OfType, T>::type;\n    return static_cast<CommonType>(val) <= static_cast<CommonType>((\n             std::numeric_limits<OfType>::max)());\n  }\n};\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl2<OfType, T, false, true> {\n  static constexpr bool test(T val)\n  {\n    using CommonType = typename std::common_type<OfType, T>::type;\n    return val >= 0 &&\n           static_cast<CommonType>(val) <= static_cast<CommonType>((\n                 std::numeric_limits<OfType>::max)());\n  }\n};\n\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl2<OfType, T, true, true> {\n  static constexpr bool test(T val)\n  {\n    using CommonType = typename std::common_type<OfType, T>::type;\n    return static_cast<CommonType>(val) >= static_cast<CommonType>((\n             std::numeric_limits<OfType>::min)())\n           && static_cast<CommonType>(val) <= static_cast<CommonType>((\n                 std::numeric_limits<OfType>::max)());\n  }\n};\n\ntemplate<typename OfType, typename T,\n         bool NeverOutOfRange = never_out_of_range<OfType, T>::value,\n         typename = detail::enable_if_t<all_integral<OfType, T>::value>>\nstruct value_in_range_of_impl1;\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl1<OfType, T, false> {\n  static constexpr bool test(T val)\n  {\n    return value_in_range_of_impl2<OfType, T>::test(val);\n  }\n};\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl1<OfType, T, true> {\n  static constexpr bool test(T /*val*/)\n  {\n    return true;\n  }\n};\n\ntemplate<typename OfType, typename T>\ninline constexpr bool value_in_range_of(T val)\n{\n  return value_in_range_of_impl1<OfType, T>::test(val);\n}\n\ntemplate<bool Value>\nusing bool_constant = std::integral_constant<bool, Value>;\n\n///////////////////////////////////////////////////////////////////////////////\n// is_c_string\n///////////////////////////////////////////////////////////////////////////////\n\nnamespace impl\n{\n\ntemplate<typename T>\ninline constexpr bool is_c_string()\n{\n  using TUnExt = typename std::remove_extent<T>::type;\n  using TUnCVExt = typename std::remove_cv<TUnExt>::type;\n  using TUnPtr = typename std::remove_pointer<T>::type;\n  using TUnCVPtr = typename std::remove_cv<TUnPtr>::type;\n  return\n    (std::is_array<T>::value && std::is_same<TUnCVExt, char>::value)\n    || (std::is_pointer<T>::value && std::is_same<TUnCVPtr, char>::value);\n}\n\n}  // namespace impl\n\n// checks whether T is a [cv] char */[cv] char[] C string\ntemplate<typename T>\nstruct is_c_string : bool_constant<impl::is_c_string<T>()> {};\n\ntemplate<typename T>\nusing is_c_string_uncvref = is_c_string<uncvref_t<T>>;\n\n///////////////////////////////////////////////////////////////////////////////\n// is_transparent\n///////////////////////////////////////////////////////////////////////////////\n\nnamespace impl\n{\n\ntemplate<typename T>\ninline constexpr bool is_transparent()\n{\n  return is_detected<detect_is_transparent, T>::value;\n}\n\n}  // namespace impl\n\n// checks whether T has a member named is_transparent\ntemplate<typename T>\nstruct is_transparent : bool_constant<impl::is_transparent<T>()> {};\n\n///////////////////////////////////////////////////////////////////////////////\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/string_concat.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstring> // strlen\n#include <string> // string\n#include <utility> // forward\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/detected.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ninline std::size_t concat_length()\n{\n  return 0;\n}\n\ntemplate<typename... Args>\ninline std::size_t concat_length(const char* cstr, Args&& ... rest);\n\ntemplate<typename StringType, typename... Args>\ninline std::size_t concat_length(const StringType& str, Args&& ... rest);\n\ntemplate<typename... Args>\ninline std::size_t concat_length(const char /*c*/, Args&& ... rest)\n{\n  return 1 + concat_length(std::forward<Args>(rest)...);\n}\n\ntemplate<typename... Args>\ninline std::size_t concat_length(const char* cstr, Args&& ... rest)\n{\n  // cppcheck-suppress ignoredReturnValue\n  return ::strlen(cstr) + concat_length(std::forward<Args>(rest)...);\n}\n\ntemplate<typename StringType, typename... Args>\ninline std::size_t concat_length(const StringType& str, Args&& ... rest)\n{\n  return str.size() + concat_length(std::forward<Args>(rest)...);\n}\n\ntemplate<typename OutStringType>\ninline void concat_into(OutStringType& /*out*/)\n{}\n\ntemplate<typename StringType, typename Arg>\nusing string_can_append = decltype(std::declval<StringType&>().append(\n                                     std::declval < Arg && > ()));\n\ntemplate<typename StringType, typename Arg>\nusing detect_string_can_append =\n  is_detected<string_can_append, StringType, Arg>;\n\ntemplate<typename StringType, typename Arg>\nusing string_can_append_op = decltype(std::declval<StringType&>() +=\n                                        std::declval < Arg && > ());\n\ntemplate<typename StringType, typename Arg>\nusing detect_string_can_append_op =\n  is_detected<string_can_append_op, StringType, Arg>;\n\ntemplate<typename StringType, typename Arg>\nusing string_can_append_iter = decltype(std::declval<StringType&>().append(\n    std::declval<const Arg&>().begin(), std::declval<const Arg&>().end()));\n\ntemplate<typename StringType, typename Arg>\nusing detect_string_can_append_iter =\n  is_detected<string_can_append_iter, StringType, Arg>;\n\ntemplate<typename StringType, typename Arg>\nusing string_can_append_data = decltype(std::declval<StringType&>().append(\n    std::declval<const Arg&>().data(), std::declval<const Arg&>().size()));\n\ntemplate<typename StringType, typename Arg>\nusing detect_string_can_append_data =\n  is_detected<string_can_append_data, StringType, Arg>;\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && detect_string_can_append_op<OutStringType, Arg>::value, int > = 0 >\ninline void concat_into(OutStringType& out, Arg && arg, Args && ... rest);\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && !detect_string_can_append_op<OutStringType, Arg>::value\n                         && detect_string_can_append_iter<OutStringType, Arg>::value, int > = 0 >\ninline void concat_into(OutStringType& out, const Arg& arg, Args && ... rest);\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && !detect_string_can_append_op<OutStringType, Arg>::value\n                         && !detect_string_can_append_iter<OutStringType, Arg>::value\n                         && detect_string_can_append_data<OutStringType, Arg>::value, int > = 0 >\ninline void concat_into(OutStringType& out, const Arg& arg, Args && ... rest);\n\ntemplate<typename OutStringType, typename Arg, typename... Args,\n         enable_if_t<detect_string_can_append<OutStringType, Arg>::value, int> = 0>\ninline void concat_into(OutStringType& out, Arg && arg, Args && ... rest)\n{\n  out.append(std::forward<Arg>(arg));\n  concat_into(out, std::forward<Args>(rest)...);\n}\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && detect_string_can_append_op<OutStringType, Arg>::value, int > >\ninline void concat_into(OutStringType& out, Arg&& arg, Args&& ... rest)\n{\n  out += std::forward<Arg>(arg);\n  concat_into(out, std::forward<Args>(rest)...);\n}\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && !detect_string_can_append_op<OutStringType, Arg>::value\n                         && detect_string_can_append_iter<OutStringType, Arg>::value, int > >\ninline void concat_into(OutStringType& out, const Arg& arg, Args&& ... rest)\n{\n  out.append(arg.begin(), arg.end());\n  concat_into(out, std::forward<Args>(rest)...);\n}\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && !detect_string_can_append_op<OutStringType, Arg>::value\n                         && !detect_string_can_append_iter<OutStringType, Arg>::value\n                         && detect_string_can_append_data<OutStringType, Arg>::value, int > >\ninline void concat_into(OutStringType& out, const Arg& arg, Args&& ... rest)\n{\n  out.append(arg.data(), arg.size());\n  concat_into(out, std::forward<Args>(rest)...);\n}\n\ntemplate<typename OutStringType = std::string, typename... Args>\ninline OutStringType concat(Args && ... args)\n{\n  OutStringType str;\n  str.reserve(concat_length(std::forward<Args>(args)...));\n  concat_into(str, std::forward<Args>(args)...);\n  return str;\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n////////////////\n// exceptions //\n////////////////\n\n/// @brief general exception of the @ref basic_json class\n/// @sa https://json.nlohmann.me/api/basic_json/exception/\nclass exception : public std::exception\n{\npublic:\n  /// returns the explanatory string\n  const char* what() const noexcept override\n  {\n    return m.what();\n  }\n\n  /// the id of the exception\n  const int id; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes)\n\nprotected:\n  JSON_HEDLEY_NON_NULL(3)\n  exception(int id_, const char* what_arg) : id(id_),\n    m(what_arg) {} // NOLINT(bugprone-throw-keyword-missing)\n\n  static std::string name(const std::string& ename, int id_)\n  {\n    return concat(\"[json.exception.\", ename, '.', std::to_string(id_), \"] \");\n  }\n\n  static std::string diagnostics(std::nullptr_t /*leaf_element*/)\n  {\n    return \"\";\n  }\n\n  template<typename BasicJsonType>\n  static std::string diagnostics(const BasicJsonType* leaf_element)\n  {\n#if JSON_DIAGNOSTICS\n    std::vector<std::string> tokens;\n\n    for (const auto* current = leaf_element; current != nullptr &&\n         current->m_parent != nullptr; current = current->m_parent) {\n      switch (current->m_parent->type()) {\n      case value_t::array: {\n        for (std::size_t i = 0; i < current->m_parent->m_value.array->size(); ++i) {\n          if (&current->m_parent->m_value.array->operator[](i) == current) {\n            tokens.emplace_back(std::to_string(i));\n            break;\n          }\n        }\n\n        break;\n      }\n\n      case value_t::object: {\n        for (const auto& element : *current->m_parent->m_value.object) {\n          if (&element.second == current) {\n            tokens.emplace_back(element.first.c_str());\n            break;\n          }\n        }\n\n        break;\n      }\n\n      case value_t::null: // LCOV_EXCL_LINE\n      case value_t::string: // LCOV_EXCL_LINE\n      case value_t::boolean: // LCOV_EXCL_LINE\n      case value_t::number_integer: // LCOV_EXCL_LINE\n      case value_t::number_unsigned: // LCOV_EXCL_LINE\n      case value_t::number_float: // LCOV_EXCL_LINE\n      case value_t::binary: // LCOV_EXCL_LINE\n      case value_t::discarded: // LCOV_EXCL_LINE\n      default:   // LCOV_EXCL_LINE\n        break; // LCOV_EXCL_LINE\n      }\n    }\n\n    if (tokens.empty()) {\n      return \"\";\n    }\n\n    auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},\n    [](const std::string & a, const std::string & b) {\n      return concat(a, '/', detail::escape(b));\n    });\n    return concat('(', str, \") \");\n#else\n    static_cast<void>(leaf_element);\n    return \"\";\n#endif\n  }\n\nprivate:\n  /// an exception object as storage for error messages\n  std::runtime_error m;\n};\n\n/// @brief exception indicating a parse error\n/// @sa https://json.nlohmann.me/api/basic_json/parse_error/\nclass parse_error : public exception\n{\npublic:\n  /*!\n  @brief create a parse error exception\n  @param[in] id_       the id of the exception\n  @param[in] pos       the position where the error occurred (or with\n                       chars_read_total=0 if the position cannot be\n                       determined)\n  @param[in] what_arg  the explanatory string\n  @return parse_error object\n  */\n  template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n  static parse_error create(int id_, const position_t& pos,\n                            const std::string& what_arg, BasicJsonContext context)\n  {\n    std::string w = concat(exception::name(\"parse_error\", id_), \"parse error\",\n                           position_string(pos), \": \", exception::diagnostics(context), what_arg);\n    return {id_, pos.chars_read_total, w.c_str()};\n  }\n\n  template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n  static parse_error create(int id_, std::size_t byte_,\n                            const std::string& what_arg, BasicJsonContext context)\n  {\n    std::string w = concat(exception::name(\"parse_error\", id_), \"parse error\",\n                           (byte_ != 0 ? (concat(\" at byte \", std::to_string(byte_))) : \"\"),\n                           \": \", exception::diagnostics(context), what_arg);\n    return {id_, byte_, w.c_str()};\n  }\n\n  /*!\n  @brief byte index of the parse error\n\n  The byte index of the last read character in the input file.\n\n  @note For an input with n bytes, 1 is the index of the first character and\n        n+1 is the index of the terminating null byte or the end of file.\n        This also holds true when reading a byte vector (CBOR or MessagePack).\n  */\n  const std::size_t byte;\n\nprivate:\n  parse_error(int id_, std::size_t byte_, const char* what_arg)\n    : exception(id_, what_arg), byte(byte_) {}\n\n  static std::string position_string(const position_t& pos)\n  {\n    return concat(\" at line \", std::to_string(pos.lines_read + 1),\n                  \", column \", std::to_string(pos.chars_read_current_line));\n  }\n};\n\n/// @brief exception indicating errors with iterators\n/// @sa https://json.nlohmann.me/api/basic_json/invalid_iterator/\nclass invalid_iterator : public exception\n{\npublic:\n  template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n  static invalid_iterator create(int id_, const std::string& what_arg,\n                                 BasicJsonContext context)\n  {\n    std::string w = concat(exception::name(\"invalid_iterator\", id_),\n                           exception::diagnostics(context), what_arg);\n    return {id_, w.c_str()};\n  }\n\nprivate:\n  JSON_HEDLEY_NON_NULL(3)\n  invalid_iterator(int id_, const char* what_arg)\n    : exception(id_, what_arg) {}\n};\n\n/// @brief exception indicating executing a member function with a wrong type\n/// @sa https://json.nlohmann.me/api/basic_json/type_error/\nclass type_error : public exception\n{\npublic:\n  template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n  static type_error create(int id_, const std::string& what_arg,\n                           BasicJsonContext context)\n  {\n    std::string w = concat(exception::name(\"type_error\", id_),\n                           exception::diagnostics(context), what_arg);\n    return {id_, w.c_str()};\n  }\n\nprivate:\n  JSON_HEDLEY_NON_NULL(3)\n  type_error(int id_, const char* what_arg) : exception(id_, what_arg) {}\n};\n\n/// @brief exception indicating access out of the defined range\n/// @sa https://json.nlohmann.me/api/basic_json/out_of_range/\nclass out_of_range : public exception\n{\npublic:\n  template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n  static out_of_range create(int id_, const std::string& what_arg,\n                             BasicJsonContext context)\n  {\n    std::string w = concat(exception::name(\"out_of_range\", id_),\n                           exception::diagnostics(context), what_arg);\n    return {id_, w.c_str()};\n  }\n\nprivate:\n  JSON_HEDLEY_NON_NULL(3)\n  out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {}\n};\n\n/// @brief exception indicating other library errors\n/// @sa https://json.nlohmann.me/api/basic_json/other_error/\nclass other_error : public exception\n{\npublic:\n  template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n  static other_error create(int id_, const std::string& what_arg,\n                            BasicJsonContext context)\n  {\n    std::string w = concat(exception::name(\"other_error\", id_),\n                           exception::diagnostics(context), what_arg);\n    return {id_, w.c_str()};\n  }\n\nprivate:\n  JSON_HEDLEY_NON_NULL(3)\n  other_error(int id_, const char* what_arg) : exception(id_, what_arg) {}\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/identity_tag.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n// dispatching helper struct\ntemplate <class T> struct identity_tag {};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/std_fs.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\n#if JSON_HAS_EXPERIMENTAL_FILESYSTEM\n#include <experimental/filesystem>\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\nnamespace std_fs = std::experimental::filesystem;\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n#elif JSON_HAS_FILESYSTEM\n#include <filesystem>\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\nnamespace std_fs = std::filesystem;\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n#endif\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, typename std::nullptr_t& n)\n{\n  if (JSON_HEDLEY_UNLIKELY(!j.is_null())) {\n    JSON_THROW(type_error::create(302, concat(\"type must be null, but is \",\n                                  j.type_name()), &j));\n  }\n\n  n = nullptr;\n}\n\n// overloads for basic_json template parameters\ntemplate < typename BasicJsonType, typename ArithmeticType,\n           enable_if_t < std::is_arithmetic<ArithmeticType>::value&&\n                         !std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,\n                         int > = 0 >\nvoid get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)\n{\n  switch (static_cast<value_t>(j)) {\n  case value_t::number_unsigned: {\n    val = static_cast<ArithmeticType>(*j.template\n                                      get_ptr<const typename BasicJsonType::number_unsigned_t*>());\n    break;\n  }\n\n  case value_t::number_integer: {\n    val = static_cast<ArithmeticType>(*j.template\n                                      get_ptr<const typename BasicJsonType::number_integer_t*>());\n    break;\n  }\n\n  case value_t::number_float: {\n    val = static_cast<ArithmeticType>(*j.template\n                                      get_ptr<const typename BasicJsonType::number_float_t*>());\n    break;\n  }\n\n  case value_t::null:\n  case value_t::object:\n  case value_t::array:\n  case value_t::string:\n  case value_t::boolean:\n  case value_t::binary:\n  case value_t::discarded:\n  default:\n    JSON_THROW(type_error::create(302, concat(\"type must be number, but is \",\n                                  j.type_name()), &j));\n  }\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j,\n                      typename BasicJsonType::boolean_t& b)\n{\n  if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) {\n    JSON_THROW(type_error::create(302, concat(\"type must be boolean, but is \",\n                                  j.type_name()), &j));\n  }\n\n  b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>();\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j,\n                      typename BasicJsonType::string_t& s)\n{\n  if (JSON_HEDLEY_UNLIKELY(!j.is_string())) {\n    JSON_THROW(type_error::create(302, concat(\"type must be string, but is \",\n                                  j.type_name()), &j));\n  }\n\n  s = *j.template get_ptr<const typename BasicJsonType::string_t*>();\n}\n\ntemplate <\n  typename BasicJsonType, typename StringType,\n  enable_if_t <\n    std::is_assignable<StringType&, const typename BasicJsonType::string_t>::value\n    && is_detected_exact<typename BasicJsonType::string_t::value_type, value_type_t, StringType>::value\n    && !std::is_same<typename BasicJsonType::string_t, StringType>::value\n    && !is_json_ref<StringType>::value, int > = 0 >\ninline void from_json(const BasicJsonType& j, StringType& s)\n{\n  if (JSON_HEDLEY_UNLIKELY(!j.is_string())) {\n    JSON_THROW(type_error::create(302, concat(\"type must be string, but is \",\n                                  j.type_name()), &j));\n  }\n\n  s = *j.template get_ptr<const typename BasicJsonType::string_t*>();\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j,\n                      typename BasicJsonType::number_float_t& val)\n{\n  get_arithmetic_value(j, val);\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j,\n                      typename BasicJsonType::number_unsigned_t& val)\n{\n  get_arithmetic_value(j, val);\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j,\n                      typename BasicJsonType::number_integer_t& val)\n{\n  get_arithmetic_value(j, val);\n}\n\n#if !JSON_DISABLE_ENUM_SERIALIZATION\ntemplate<typename BasicJsonType, typename EnumType,\n         enable_if_t<std::is_enum<EnumType>::value, int> = 0>\ninline void from_json(const BasicJsonType& j, EnumType& e)\n{\n  typename std::underlying_type<EnumType>::type val;\n  get_arithmetic_value(j, val);\n  e = static_cast<EnumType>(val);\n}\n#endif  // JSON_DISABLE_ENUM_SERIALIZATION\n\n// forward_list doesn't have an insert method\ntemplate<typename BasicJsonType, typename T, typename Allocator,\n         enable_if_t<is_getable<BasicJsonType, T>::value, int> = 0>\ninline void from_json(const BasicJsonType& j,\n                      std::forward_list<T, Allocator>& l)\n{\n  if (JSON_HEDLEY_UNLIKELY(!j.is_array())) {\n    JSON_THROW(type_error::create(302, concat(\"type must be array, but is \",\n                                  j.type_name()), &j));\n  }\n\n  l.clear();\n  std::transform(j.rbegin(), j.rend(),\n  std::front_inserter(l), [](const BasicJsonType & i) {\n    return i.template get<T>();\n  });\n}\n\n// valarray doesn't have an insert method\ntemplate<typename BasicJsonType, typename T,\n         enable_if_t<is_getable<BasicJsonType, T>::value, int> = 0>\ninline void from_json(const BasicJsonType& j, std::valarray<T>& l)\n{\n  if (JSON_HEDLEY_UNLIKELY(!j.is_array())) {\n    JSON_THROW(type_error::create(302, concat(\"type must be array, but is \",\n                                  j.type_name()), &j));\n  }\n\n  l.resize(j.size());\n  std::transform(j.begin(), j.end(), std::begin(l),\n  [](const BasicJsonType & elem) {\n    return elem.template get<T>();\n  });\n}\n\ntemplate<typename BasicJsonType, typename T, std::size_t N>\nauto from_json(const BasicJsonType& j,\n               T(&arr)[N])   // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n-> decltype(j.template get<T>(), void())\n{\n  for (std::size_t i = 0; i < N; ++i) {\n    arr[i] = j.at(i).template get<T>();\n  }\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json_array_impl(const BasicJsonType& j,\n                                 typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/)\n{\n  arr = *j.template get_ptr<const typename BasicJsonType::array_t*>();\n}\n\ntemplate<typename BasicJsonType, typename T, std::size_t N>\nauto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,\n                          priority_tag<2> /*unused*/)\n-> decltype(j.template get<T>(), void())\n{\n  for (std::size_t i = 0; i < N; ++i) {\n    arr[i] = j.at(i).template get<T>();\n  }\n}\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType,\n         enable_if_t<\n           std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value,\n           int> = 0>\nauto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr,\n                          priority_tag<1> /*unused*/)\n-> decltype(\n  arr.reserve(std::declval<typename ConstructibleArrayType::size_type>()),\n  j.template get<typename ConstructibleArrayType::value_type>(),\n  void())\n{\n  using std::end;\n  ConstructibleArrayType ret;\n  ret.reserve(j.size());\n  std::transform(j.begin(), j.end(),\n  std::inserter(ret, end(ret)), [](const BasicJsonType & i) {\n    // get<BasicJsonType>() returns *this, this won't call a from_json\n    // method when value_type is BasicJsonType\n    return i.template get<typename ConstructibleArrayType::value_type>();\n  });\n  arr = std::move(ret);\n}\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType,\n         enable_if_t<\n           std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value,\n           int> = 0>\ninline void from_json_array_impl(const BasicJsonType& j,\n                                 ConstructibleArrayType& arr,\n                                 priority_tag<0> /*unused*/)\n{\n  using std::end;\n  ConstructibleArrayType ret;\n  std::transform(\n    j.begin(), j.end(), std::inserter(ret, end(ret)),\n  [](const BasicJsonType & i) {\n    // get<BasicJsonType>() returns *this, this won't call a from_json\n    // method when value_type is BasicJsonType\n    return i.template get<typename ConstructibleArrayType::value_type>();\n  });\n  arr = std::move(ret);\n}\n\ntemplate < typename BasicJsonType, typename ConstructibleArrayType,\n           enable_if_t <\n             is_constructible_array_type<BasicJsonType, ConstructibleArrayType>::value&&\n             !is_constructible_object_type<BasicJsonType, ConstructibleArrayType>::value&&\n             !is_constructible_string_type<BasicJsonType, ConstructibleArrayType>::value&&\n             !std::is_same<ConstructibleArrayType, typename BasicJsonType::binary_t>::value&&\n             !is_basic_json<ConstructibleArrayType>::value,\n             int > = 0 >\nauto from_json(const BasicJsonType& j, ConstructibleArrayType& arr)\n-> decltype(from_json_array_impl(j, arr, priority_tag<3> {}),\nj.template get<typename ConstructibleArrayType::value_type>(),\nvoid())\n{\n  if (JSON_HEDLEY_UNLIKELY(!j.is_array())) {\n    JSON_THROW(type_error::create(302, concat(\"type must be array, but is \",\n                                  j.type_name()), &j));\n  }\n\n  from_json_array_impl(j, arr, priority_tag<3> {});\n}\n\ntemplate < typename BasicJsonType, typename T, std::size_t... Idx >\nstd::array<T, sizeof...(Idx)> from_json_inplace_array_impl(BasicJsonType&& j,\n    identity_tag<std::array<T, sizeof...(Idx)>> /*unused*/,\n    index_sequence<Idx...> /*unused*/)\n{\n  return { { std::forward<BasicJsonType>(j).at(Idx).template get<T>()... } };\n}\n\ntemplate < typename BasicJsonType, typename T, std::size_t N >\nauto from_json(BasicJsonType&& j, identity_tag<std::array<T, N>> tag)\n-> decltype(from_json_inplace_array_impl(std::forward<BasicJsonType>(j), tag,\nmake_index_sequence<N> {}))\n{\n  if (JSON_HEDLEY_UNLIKELY(!j.is_array())) {\n    JSON_THROW(type_error::create(302, concat(\"type must be array, but is \",\n                                  j.type_name()), &j));\n  }\n\n  return from_json_inplace_array_impl(std::forward<BasicJsonType>(j), tag,\n                                      make_index_sequence<N> {});\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j,\n                      typename BasicJsonType::binary_t& bin)\n{\n  if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) {\n    JSON_THROW(type_error::create(302, concat(\"type must be binary, but is \",\n                                  j.type_name()), &j));\n  }\n\n  bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>();\n}\n\ntemplate<typename BasicJsonType, typename ConstructibleObjectType,\n         enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>\ninline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)\n{\n  if (JSON_HEDLEY_UNLIKELY(!j.is_object())) {\n    JSON_THROW(type_error::create(302, concat(\"type must be object, but is \",\n                                  j.type_name()), &j));\n  }\n\n  ConstructibleObjectType ret;\n  const auto* inner_object = j.template\n                             get_ptr<const typename BasicJsonType::object_t*>();\n  using value_type = typename ConstructibleObjectType::value_type;\n  std::transform(\n    inner_object->begin(), inner_object->end(),\n    std::inserter(ret, ret.begin()),\n  [](typename BasicJsonType::object_t::value_type const & p) {\n    return value_type(p.first,\n                      p.second.template get<typename ConstructibleObjectType::mapped_type>());\n  });\n  obj = std::move(ret);\n}\n\n// overload for arithmetic types, not chosen for basic_json template arguments\n// (BooleanType, etc..); note: Is it really necessary to provide explicit\n// overloads for boolean_t etc. in case of a custom BooleanType which is not\n// an arithmetic type?\ntemplate < typename BasicJsonType, typename ArithmeticType,\n           enable_if_t <\n             std::is_arithmetic<ArithmeticType>::value&&\n             !std::is_same<ArithmeticType, typename BasicJsonType::number_unsigned_t>::value&&\n             !std::is_same<ArithmeticType, typename BasicJsonType::number_integer_t>::value&&\n             !std::is_same<ArithmeticType, typename BasicJsonType::number_float_t>::value&&\n             !std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,\n             int > = 0 >\ninline void from_json(const BasicJsonType& j, ArithmeticType& val)\n{\n  switch (static_cast<value_t>(j)) {\n  case value_t::number_unsigned: {\n    val = static_cast<ArithmeticType>(*j.template\n                                      get_ptr<const typename BasicJsonType::number_unsigned_t*>());\n    break;\n  }\n\n  case value_t::number_integer: {\n    val = static_cast<ArithmeticType>(*j.template\n                                      get_ptr<const typename BasicJsonType::number_integer_t*>());\n    break;\n  }\n\n  case value_t::number_float: {\n    val = static_cast<ArithmeticType>(*j.template\n                                      get_ptr<const typename BasicJsonType::number_float_t*>());\n    break;\n  }\n\n  case value_t::boolean: {\n    val = static_cast<ArithmeticType>(*j.template\n                                      get_ptr<const typename BasicJsonType::boolean_t*>());\n    break;\n  }\n\n  case value_t::null:\n  case value_t::object:\n  case value_t::array:\n  case value_t::string:\n  case value_t::binary:\n  case value_t::discarded:\n  default:\n    JSON_THROW(type_error::create(302, concat(\"type must be number, but is \",\n                                  j.type_name()), &j));\n  }\n}\n\ntemplate<typename BasicJsonType, typename... Args, std::size_t... Idx>\nstd::tuple<Args...> from_json_tuple_impl_base(BasicJsonType&& j,\n    index_sequence<Idx...> /*unused*/)\n{\n  return std::make_tuple(std::forward<BasicJsonType>(j).at(\n                           Idx).template get<Args>()...);\n}\n\ntemplate < typename BasicJsonType, class A1, class A2 >\nstd::pair<A1, A2> from_json_tuple_impl(BasicJsonType&& j,\n                                       identity_tag<std::pair<A1, A2>> /*unused*/, priority_tag<0> /*unused*/)\n{\n  return {std::forward<BasicJsonType>(j).at(0).template get<A1>(),\n          std::forward<BasicJsonType>(j).at(1).template get<A2>()};\n}\n\ntemplate<typename BasicJsonType, typename A1, typename A2>\ninline void from_json_tuple_impl(BasicJsonType&& j, std::pair<A1, A2>& p,\n                                 priority_tag<1> /*unused*/)\n{\n  p = from_json_tuple_impl(std::forward<BasicJsonType>(j),\n                           identity_tag<std::pair<A1, A2>> {}, priority_tag<0> {});\n}\n\ntemplate<typename BasicJsonType, typename... Args>\nstd::tuple<Args...> from_json_tuple_impl(BasicJsonType&& j,\n    identity_tag<std::tuple<Args...>> /*unused*/, priority_tag<2> /*unused*/)\n{\n  return from_json_tuple_impl_base<BasicJsonType, Args...>\n         (std::forward<BasicJsonType>(j), index_sequence_for<Args...> {});\n}\n\ntemplate<typename BasicJsonType, typename... Args>\ninline void from_json_tuple_impl(BasicJsonType&& j, std::tuple<Args...>& t,\n                                 priority_tag<3> /*unused*/)\n{\n  t = from_json_tuple_impl_base<BasicJsonType, Args...>\n      (std::forward<BasicJsonType>(j), index_sequence_for<Args...> {});\n}\n\ntemplate<typename BasicJsonType, typename TupleRelated>\nauto from_json(BasicJsonType&& j, TupleRelated&& t)\n-> decltype(from_json_tuple_impl(std::forward<BasicJsonType>(j),\nstd::forward<TupleRelated>(t), priority_tag<3> {}))\n{\n  if (JSON_HEDLEY_UNLIKELY(!j.is_array())) {\n    JSON_THROW(type_error::create(302, concat(\"type must be array, but is \",\n                                  j.type_name()), &j));\n  }\n\n  return from_json_tuple_impl(std::forward<BasicJsonType>(j),\n                              std::forward<TupleRelated>(t), priority_tag<3> {});\n}\n\ntemplate < typename BasicJsonType, typename Key, typename Value,\n           typename Compare, typename Allocator,\n           typename = enable_if_t < !std::is_constructible <\n                                      typename BasicJsonType::string_t, Key >::value >>\ninline void from_json(const BasicJsonType& j,\n                      std::map<Key, Value, Compare, Allocator>& m)\n{\n  if (JSON_HEDLEY_UNLIKELY(!j.is_array())) {\n    JSON_THROW(type_error::create(302, concat(\"type must be array, but is \",\n                                  j.type_name()), &j));\n  }\n\n  m.clear();\n\n  for (const auto& p : j) {\n    if (JSON_HEDLEY_UNLIKELY(!p.is_array())) {\n      JSON_THROW(type_error::create(302, concat(\"type must be array, but is \",\n                                    p.type_name()), &j));\n    }\n\n    m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());\n  }\n}\n\ntemplate < typename BasicJsonType, typename Key, typename Value, typename Hash,\n           typename KeyEqual, typename Allocator,\n           typename = enable_if_t < !std::is_constructible <\n                                      typename BasicJsonType::string_t, Key >::value >>\ninline void from_json(const BasicJsonType& j,\n                      std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m)\n{\n  if (JSON_HEDLEY_UNLIKELY(!j.is_array())) {\n    JSON_THROW(type_error::create(302, concat(\"type must be array, but is \",\n                                  j.type_name()), &j));\n  }\n\n  m.clear();\n\n  for (const auto& p : j) {\n    if (JSON_HEDLEY_UNLIKELY(!p.is_array())) {\n      JSON_THROW(type_error::create(302, concat(\"type must be array, but is \",\n                                    p.type_name()), &j));\n    }\n\n    m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());\n  }\n}\n\n#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, std_fs::path& p)\n{\n  if (JSON_HEDLEY_UNLIKELY(!j.is_string())) {\n    JSON_THROW(type_error::create(302, concat(\"type must be string, but is \",\n                                  j.type_name()), &j));\n  }\n\n  p = *j.template get_ptr<const typename BasicJsonType::string_t*>();\n}\n#endif\n\nstruct from_json_fn {\n  template<typename BasicJsonType, typename T>\n  auto operator()(const BasicJsonType& j, T&& val) const\n  noexcept(noexcept(from_json(j, std::forward<T>(val))))\n  -> decltype(from_json(j, std::forward<T>(val)))\n  {\n    return from_json(j, std::forward<T>(val));\n  }\n};\n\n}  // namespace detail\n\n#ifndef JSON_HAS_CPP_17\n/// namespace to hold default `from_json` function\n/// to see why this is required:\n/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html\nnamespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces)\n{\n#endif\nJSON_INLINE_VARIABLE constexpr const auto& from_json\n  = // NOLINT(misc-definitions-in-headers)\n    detail::static_const<detail::from_json_fn>::value;\n#ifndef JSON_HAS_CPP_17\n}  // namespace\n#endif\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/conversions/to_json.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // copy\n#include <iterator> // begin, end\n#include <string> // string\n#include <tuple> // tuple, get\n#include <type_traits> // is_same, is_constructible, is_floating_point, is_enum, underlying_type\n#include <utility> // move, forward, declval, pair\n#include <valarray> // valarray\n#include <vector> // vector\n\n// #include <nlohmann/detail/iterators/iteration_proxy.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef> // size_t\n#include <iterator> // input_iterator_tag\n#include <string> // string, to_string\n#include <tuple> // tuple_size, get, tuple_element\n#include <utility> // move\n\n#if JSON_HAS_RANGES\n#include <ranges> // enable_borrowed_range\n#endif\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename string_type>\nvoid int_to_string(string_type& target, std::size_t value)\n{\n  // For ADL\n  using std::to_string;\n  target = to_string(value);\n}\ntemplate<typename IteratorType> class iteration_proxy_value\n{\npublic:\n  using difference_type = std::ptrdiff_t;\n  using value_type = iteration_proxy_value;\n  using pointer = value_type *;\n  using reference = value_type &;\n  using iterator_category = std::input_iterator_tag;\n  using string_type = typename\n                      std::remove_cv< typename std::remove_reference<decltype(std::declval<IteratorType>().key()) >::type >::type;\n\nprivate:\n  /// the iterator\n  IteratorType anchor{};\n  /// an index for arrays (used to create key names)\n  std::size_t array_index = 0;\n  /// last stringified array index\n  mutable std::size_t array_index_last = 0;\n  /// a string representation of the array index\n  mutable string_type array_index_str = \"0\";\n  /// an empty string (to return a reference for primitive values)\n  string_type empty_str{};\n\npublic:\n  explicit iteration_proxy_value() = default;\n  explicit iteration_proxy_value(IteratorType it, std::size_t array_index_ = 0)\n  noexcept(std::is_nothrow_move_constructible<IteratorType>::value\n           && std::is_nothrow_default_constructible<string_type>::value)\n    : anchor(std::move(it))\n    , array_index(array_index_)\n  {}\n\n  iteration_proxy_value(iteration_proxy_value const&) = default;\n  iteration_proxy_value& operator=(iteration_proxy_value const&) = default;\n  // older GCCs are a bit fussy and require explicit noexcept specifiers on defaulted functions\n  iteration_proxy_value(iteration_proxy_value&&)\n  noexcept(std::is_nothrow_move_constructible<IteratorType>::value\n           && std::is_nothrow_move_constructible<string_type>::value) = default;\n  iteration_proxy_value& operator=(iteration_proxy_value&&)\n  noexcept(std::is_nothrow_move_assignable<IteratorType>::value\n           && std::is_nothrow_move_assignable<string_type>::value) = default;\n  ~iteration_proxy_value() = default;\n\n  /// dereference operator (needed for range-based for)\n  const iteration_proxy_value& operator*() const\n  {\n    return *this;\n  }\n\n  /// increment operator (needed for range-based for)\n  iteration_proxy_value& operator++()\n  {\n    ++anchor;\n    ++array_index;\n    return *this;\n  }\n\n  iteration_proxy_value operator++(int)& { // NOLINT(cert-dcl21-cpp)\n    auto tmp = iteration_proxy_value(anchor, array_index);\n    ++anchor;\n    ++array_index;\n    return tmp;\n  }\n\n  /// equality operator (needed for InputIterator)\n  bool operator==(const iteration_proxy_value& o) const\n  {\n    return anchor == o.anchor;\n  }\n\n  /// inequality operator (needed for range-based for)\n  bool operator!=(const iteration_proxy_value& o) const\n  {\n    return anchor != o.anchor;\n  }\n\n  /// return key of the iterator\n  const string_type& key() const\n  {\n    JSON_ASSERT(anchor.m_object != nullptr);\n\n    switch (anchor.m_object->type()) {\n    // use integer array index as key\n    case value_t::array: {\n      if (array_index != array_index_last) {\n        int_to_string(array_index_str, array_index);\n        array_index_last = array_index;\n      }\n\n      return array_index_str;\n    }\n\n    // use key from the object\n    case value_t::object:\n      return anchor.key();\n\n    // use an empty key for all primitive types\n    case value_t::null:\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default:\n      return empty_str;\n    }\n  }\n\n  /// return value of the iterator\n  typename IteratorType::reference value() const\n  {\n    return anchor.value();\n  }\n};\n\n/// proxy class for the items() function\ntemplate<typename IteratorType> class iteration_proxy\n{\nprivate:\n  /// the container to iterate\n  typename IteratorType::pointer container = nullptr;\n\npublic:\n  explicit iteration_proxy() = default;\n\n  /// construct iteration proxy from a container\n  explicit iteration_proxy(typename IteratorType::reference cont) noexcept\n    : container(&cont) {}\n\n  iteration_proxy(iteration_proxy const&) = default;\n  iteration_proxy& operator=(iteration_proxy const&) = default;\n  iteration_proxy(iteration_proxy&&) noexcept = default;\n  iteration_proxy& operator=(iteration_proxy&&) noexcept = default;\n  ~iteration_proxy() = default;\n\n  /// return iterator begin (needed for range-based for)\n  iteration_proxy_value<IteratorType> begin() const noexcept\n  {\n    return iteration_proxy_value<IteratorType>(container->begin());\n  }\n\n  /// return iterator end (needed for range-based for)\n  iteration_proxy_value<IteratorType> end() const noexcept\n  {\n    return iteration_proxy_value<IteratorType>(container->end());\n  }\n};\n\n// Structured Bindings Support\n// For further reference see https://blog.tartanllama.xyz/structured-bindings/\n// And see https://github.com/nlohmann/json/pull/1391\ntemplate<std::size_t N, typename IteratorType, enable_if_t<N == 0, int> = 0>\nauto get(const nlohmann::detail::iteration_proxy_value<IteratorType>& i) ->\ndecltype(i.key())\n{\n  return i.key();\n}\n// Structured Bindings Support\n// For further reference see https://blog.tartanllama.xyz/structured-bindings/\n// And see https://github.com/nlohmann/json/pull/1391\ntemplate<std::size_t N, typename IteratorType, enable_if_t<N == 1, int> = 0>\nauto get(const nlohmann::detail::iteration_proxy_value<IteratorType>& i) ->\ndecltype(i.value())\n{\n  return i.value();\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// The Addition to the STD Namespace is required to add\n// Structured Bindings Support to the iteration_proxy_value class\n// For further reference see https://blog.tartanllama.xyz/structured-bindings/\n// And see https://github.com/nlohmann/json/pull/1391\nnamespace std\n{\n\n#if defined(__clang__)\n// Fix: https://github.com/nlohmann/json/issues/1401\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wmismatched-tags\"\n#endif\ntemplate<typename IteratorType>\nclass tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>>\n      : public std::integral_constant<std::size_t, 2> {};\n\ntemplate<std::size_t N, typename IteratorType>\nclass tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >>\n{\npublic:\n  using type = decltype(\n                 get<N>(std::declval <\n                        ::nlohmann::detail::iteration_proxy_value<IteratorType >> ()));\n};\n#if defined(__clang__)\n#pragma clang diagnostic pop\n#endif\n\n}  // namespace std\n\n#if JSON_HAS_RANGES\ntemplate <typename IteratorType>\ninline constexpr\nbool ::std::ranges::enable_borrowed_range<::nlohmann::detail::iteration_proxy<IteratorType>>\n    = true;\n#endif\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/std_fs.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n//////////////////\n// constructors //\n//////////////////\n\n/*\n * Note all external_constructor<>::construct functions need to call\n * j.m_value.destroy(j.m_type) to avoid a memory leak in case j contains an\n * allocated value (e.g., a string). See bug issue\n * https://github.com/nlohmann/json/issues/2865 for more information.\n */\n\ntemplate<value_t> struct external_constructor;\n\ntemplate<>\nstruct external_constructor<value_t::boolean> {\n  template<typename BasicJsonType>\n  static void construct(BasicJsonType& j,\n                        typename BasicJsonType::boolean_t b) noexcept\n  {\n    j.m_value.destroy(j.m_type);\n    j.m_type = value_t::boolean;\n    j.m_value = b;\n    j.assert_invariant();\n  }\n};\n\ntemplate<>\nstruct external_constructor<value_t::string> {\n  template<typename BasicJsonType>\n  static void construct(BasicJsonType& j,\n                        const typename BasicJsonType::string_t& s)\n  {\n    j.m_value.destroy(j.m_type);\n    j.m_type = value_t::string;\n    j.m_value = s;\n    j.assert_invariant();\n  }\n\n  template<typename BasicJsonType>\n  static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s)\n  {\n    j.m_value.destroy(j.m_type);\n    j.m_type = value_t::string;\n    j.m_value = std::move(s);\n    j.assert_invariant();\n  }\n\n  template < typename BasicJsonType, typename CompatibleStringType,\n             enable_if_t <\n               !std::is_same<CompatibleStringType, typename BasicJsonType::string_t>::value,\n               int > = 0 >\n  static void construct(BasicJsonType& j, const CompatibleStringType& str)\n  {\n    j.m_value.destroy(j.m_type);\n    j.m_type = value_t::string;\n    j.m_value.string = j.template create<typename BasicJsonType::string_t>(str);\n    j.assert_invariant();\n  }\n};\n\ntemplate<>\nstruct external_constructor<value_t::binary> {\n  template<typename BasicJsonType>\n  static void construct(BasicJsonType& j,\n                        const typename BasicJsonType::binary_t& b)\n  {\n    j.m_value.destroy(j.m_type);\n    j.m_type = value_t::binary;\n    j.m_value = typename BasicJsonType::binary_t(b);\n    j.assert_invariant();\n  }\n\n  template<typename BasicJsonType>\n  static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b)\n  {\n    j.m_value.destroy(j.m_type);\n    j.m_type = value_t::binary;\n    j.m_value = typename BasicJsonType::binary_t(std::move(b));\n    j.assert_invariant();\n  }\n};\n\ntemplate<>\nstruct external_constructor<value_t::number_float> {\n  template<typename BasicJsonType>\n  static void construct(BasicJsonType& j,\n                        typename BasicJsonType::number_float_t val) noexcept\n  {\n    j.m_value.destroy(j.m_type);\n    j.m_type = value_t::number_float;\n    j.m_value = val;\n    j.assert_invariant();\n  }\n};\n\ntemplate<>\nstruct external_constructor<value_t::number_unsigned> {\n  template<typename BasicJsonType>\n  static void construct(BasicJsonType& j,\n                        typename BasicJsonType::number_unsigned_t val) noexcept\n  {\n    j.m_value.destroy(j.m_type);\n    j.m_type = value_t::number_unsigned;\n    j.m_value = val;\n    j.assert_invariant();\n  }\n};\n\ntemplate<>\nstruct external_constructor<value_t::number_integer> {\n  template<typename BasicJsonType>\n  static void construct(BasicJsonType& j,\n                        typename BasicJsonType::number_integer_t val) noexcept\n  {\n    j.m_value.destroy(j.m_type);\n    j.m_type = value_t::number_integer;\n    j.m_value = val;\n    j.assert_invariant();\n  }\n};\n\ntemplate<>\nstruct external_constructor<value_t::array> {\n  template<typename BasicJsonType>\n  static void construct(BasicJsonType& j,\n                        const typename BasicJsonType::array_t& arr)\n  {\n    j.m_value.destroy(j.m_type);\n    j.m_type = value_t::array;\n    j.m_value = arr;\n    j.set_parents();\n    j.assert_invariant();\n  }\n\n  template<typename BasicJsonType>\n  static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr)\n  {\n    j.m_value.destroy(j.m_type);\n    j.m_type = value_t::array;\n    j.m_value = std::move(arr);\n    j.set_parents();\n    j.assert_invariant();\n  }\n\n  template < typename BasicJsonType, typename CompatibleArrayType,\n             enable_if_t <\n               !std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value,\n               int > = 0 >\n  static void construct(BasicJsonType& j, const CompatibleArrayType& arr)\n  {\n    using std::begin;\n    using std::end;\n    j.m_value.destroy(j.m_type);\n    j.m_type = value_t::array;\n    j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr),\n                      end(arr));\n    j.set_parents();\n    j.assert_invariant();\n  }\n\n  template<typename BasicJsonType>\n  static void construct(BasicJsonType& j, const std::vector<bool>& arr)\n  {\n    j.m_value.destroy(j.m_type);\n    j.m_type = value_t::array;\n    j.m_value = value_t::array;\n    j.m_value.array->reserve(arr.size());\n\n    for (const bool x : arr) {\n      j.m_value.array->push_back(x);\n      j.set_parent(j.m_value.array->back());\n    }\n\n    j.assert_invariant();\n  }\n\n  template<typename BasicJsonType, typename T,\n           enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>\n  static void construct(BasicJsonType& j, const std::valarray<T>& arr)\n  {\n    j.m_value.destroy(j.m_type);\n    j.m_type = value_t::array;\n    j.m_value = value_t::array;\n    j.m_value.array->resize(arr.size());\n\n    if (arr.size() > 0) {\n      std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin());\n    }\n\n    j.set_parents();\n    j.assert_invariant();\n  }\n};\n\ntemplate<>\nstruct external_constructor<value_t::object> {\n  template<typename BasicJsonType>\n  static void construct(BasicJsonType& j,\n                        const typename BasicJsonType::object_t& obj)\n  {\n    j.m_value.destroy(j.m_type);\n    j.m_type = value_t::object;\n    j.m_value = obj;\n    j.set_parents();\n    j.assert_invariant();\n  }\n\n  template<typename BasicJsonType>\n  static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj)\n  {\n    j.m_value.destroy(j.m_type);\n    j.m_type = value_t::object;\n    j.m_value = std::move(obj);\n    j.set_parents();\n    j.assert_invariant();\n  }\n\n  template < typename BasicJsonType, typename CompatibleObjectType,\n             enable_if_t <\n               !std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value,\n               int > = 0 >\n  static void construct(BasicJsonType& j, const CompatibleObjectType& obj)\n  {\n    using std::begin;\n    using std::end;\n    j.m_value.destroy(j.m_type);\n    j.m_type = value_t::object;\n    j.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(\n                         obj), end(obj));\n    j.set_parents();\n    j.assert_invariant();\n  }\n};\n\n/////////////\n// to_json //\n/////////////\n\ntemplate<typename BasicJsonType, typename T,\n         enable_if_t<std::is_same<T, typename BasicJsonType::boolean_t>::value, int> = 0>\ninline void to_json(BasicJsonType& j, T b) noexcept\n{\n  external_constructor<value_t::boolean>::construct(j, b);\n}\n\ntemplate < typename BasicJsonType, typename BoolRef,\n           enable_if_t <\n             ((std::is_same<std::vector<bool>::reference, BoolRef>::value\n               && !std::is_same\n               <std::vector<bool>::reference, typename BasicJsonType::boolean_t&>::value)\n              || (std::is_same<std::vector<bool>::const_reference, BoolRef>::value\n                  && !std::is_same <detail::uncvref_t<std::vector<bool>::const_reference>,\n                                    typename BasicJsonType::boolean_t >::value))\n             && std::is_convertible<const BoolRef&, typename BasicJsonType::boolean_t>::value,\n             int > = 0 >\ninline void to_json(BasicJsonType& j, const BoolRef& b) noexcept\n{\n  external_constructor<value_t::boolean>::construct(j,\n      static_cast<typename BasicJsonType::boolean_t>(b));\n}\n\ntemplate<typename BasicJsonType, typename CompatibleString,\n         enable_if_t<std::is_constructible<typename BasicJsonType::string_t, CompatibleString>::value, int> = 0>\ninline void to_json(BasicJsonType& j, const CompatibleString& s)\n{\n  external_constructor<value_t::string>::construct(j, s);\n}\n\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s)\n{\n  external_constructor<value_t::string>::construct(j, std::move(s));\n}\n\ntemplate<typename BasicJsonType, typename FloatType,\n         enable_if_t<std::is_floating_point<FloatType>::value, int> = 0>\ninline void to_json(BasicJsonType& j, FloatType val) noexcept\n{\n  external_constructor<value_t::number_float>::construct(j,\n      static_cast<typename BasicJsonType::number_float_t>(val));\n}\n\ntemplate<typename BasicJsonType, typename CompatibleNumberUnsignedType,\n         enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, CompatibleNumberUnsignedType>::value, int> = 0>\ninline void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept\n{\n  external_constructor<value_t::number_unsigned>::construct(j,\n      static_cast<typename BasicJsonType::number_unsigned_t>(val));\n}\n\ntemplate<typename BasicJsonType, typename CompatibleNumberIntegerType,\n         enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType>::value, int> = 0>\ninline void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept\n{\n  external_constructor<value_t::number_integer>::construct(j,\n      static_cast<typename BasicJsonType::number_integer_t>(val));\n}\n\n#if !JSON_DISABLE_ENUM_SERIALIZATION\ntemplate<typename BasicJsonType, typename EnumType,\n         enable_if_t<std::is_enum<EnumType>::value, int> = 0>\ninline void to_json(BasicJsonType& j, EnumType e) noexcept\n{\n  using underlying_type = typename std::underlying_type<EnumType>::type;\n  external_constructor<value_t::number_integer>::construct(j,\n      static_cast<underlying_type>(e));\n}\n#endif  // JSON_DISABLE_ENUM_SERIALIZATION\n\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j, const std::vector<bool>& e)\n{\n  external_constructor<value_t::array>::construct(j, e);\n}\n\ntemplate < typename BasicJsonType, typename CompatibleArrayType,\n           enable_if_t < is_compatible_array_type<BasicJsonType,\n                         CompatibleArrayType>::value&&\n                         !is_compatible_object_type<BasicJsonType, CompatibleArrayType>::value&&\n                         !is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value&&\n                         !std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value&&\n                         !is_basic_json<CompatibleArrayType>::value,\n                         int > = 0 >\ninline void to_json(BasicJsonType& j, const CompatibleArrayType& arr)\n{\n  external_constructor<value_t::array>::construct(j, arr);\n}\n\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j,\n                    const typename BasicJsonType::binary_t& bin)\n{\n  external_constructor<value_t::binary>::construct(j, bin);\n}\n\ntemplate<typename BasicJsonType, typename T,\n         enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>\ninline void to_json(BasicJsonType& j, const std::valarray<T>& arr)\n{\n  external_constructor<value_t::array>::construct(j, std::move(arr));\n}\n\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr)\n{\n  external_constructor<value_t::array>::construct(j, std::move(arr));\n}\n\ntemplate < typename BasicJsonType, typename CompatibleObjectType,\n           enable_if_t <\n             is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value&&\n             !is_basic_json<CompatibleObjectType>::value, int > = 0 >\ninline void to_json(BasicJsonType& j, const CompatibleObjectType& obj)\n{\n  external_constructor<value_t::object>::construct(j, obj);\n}\n\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)\n{\n  external_constructor<value_t::object>::construct(j, std::move(obj));\n}\n\ntemplate <\n  typename BasicJsonType, typename T, std::size_t N,\n  enable_if_t < !std::is_constructible<typename BasicJsonType::string_t,\n                                       const T(&)[N]>::value, // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n                int > = 0 >\ninline void to_json(BasicJsonType& j,\n                    const T(&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n{\n  external_constructor<value_t::array>::construct(j, arr);\n}\n\ntemplate < typename BasicJsonType, typename T1, typename T2,\n           enable_if_t < std::is_constructible<BasicJsonType, T1>::value&&\n                         std::is_constructible<BasicJsonType, T2>::value, int > = 0 >\ninline void to_json(BasicJsonType& j, const std::pair<T1, T2>& p)\n{\n  j = { p.first, p.second };\n}\n\n// for https://github.com/nlohmann/json/pull/1134\ntemplate<typename BasicJsonType, typename T,\n         enable_if_t<std::is_same<T, iteration_proxy_value<typename BasicJsonType::iterator>>::value, int> = 0>\ninline void to_json(BasicJsonType& j, const T& b)\n{\n  j = { {b.key(), b.value()} };\n}\n\ntemplate<typename BasicJsonType, typename Tuple, std::size_t... Idx>\ninline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t,\n                               index_sequence<Idx...> /*unused*/)\n{\n  j = { std::get<Idx>(t)... };\n}\n\ntemplate<typename BasicJsonType, typename T, enable_if_t<is_constructible_tuple<BasicJsonType, T>::value, int > = 0>\ninline void to_json(BasicJsonType& j, const T& t)\n{\n  to_json_tuple_impl(j, t, make_index_sequence<std::tuple_size<T>::value> {});\n}\n\n#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j, const std_fs::path& p)\n{\n  j = p.string();\n}\n#endif\n\nstruct to_json_fn {\n  template<typename BasicJsonType, typename T>\n  auto operator()(BasicJsonType& j, T&& val) const noexcept(noexcept(to_json(j,\n      std::forward<T>(val))))\n  -> decltype(to_json(j, std::forward<T>(val)), void())\n  {\n    return to_json(j, std::forward<T>(val));\n  }\n};\n}  // namespace detail\n\n#ifndef JSON_HAS_CPP_17\n/// namespace to hold default `to_json` function\n/// to see why this is required:\n/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html\nnamespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces)\n{\n#endif\nJSON_INLINE_VARIABLE constexpr const auto& to_json\n  = // NOLINT(misc-definitions-in-headers)\n    detail::static_const<detail::to_json_fn>::value;\n#ifndef JSON_HAS_CPP_17\n}  // namespace\n#endif\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/identity_tag.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/// @sa https://json.nlohmann.me/api/adl_serializer/\ntemplate<typename ValueType, typename>\nstruct adl_serializer {\n  /// @brief convert a JSON value to any value type\n  /// @sa https://json.nlohmann.me/api/adl_serializer/from_json/\n  template<typename BasicJsonType, typename TargetType = ValueType>\n  static auto from_json(BasicJsonType && j, TargetType& val) noexcept(\n    noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val)))\n  -> decltype(::nlohmann::from_json(std::forward<BasicJsonType>(j), val), void())\n  {\n    ::nlohmann::from_json(std::forward<BasicJsonType>(j), val);\n  }\n\n  /// @brief convert a JSON value to any value type\n  /// @sa https://json.nlohmann.me/api/adl_serializer/from_json/\n  template<typename BasicJsonType, typename TargetType = ValueType>\n  static auto from_json(BasicJsonType && j) noexcept(\n    noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j),\n  detail::identity_tag<TargetType> {})))\n  -> decltype(::nlohmann::from_json(std::forward<BasicJsonType>(j),\n                                    detail::identity_tag<TargetType> {}))\n  {\n    return ::nlohmann::from_json(std::forward<BasicJsonType>(j),\n                                 detail::identity_tag<TargetType> {});\n  }\n\n  /// @brief convert any value type to a JSON value\n  /// @sa https://json.nlohmann.me/api/adl_serializer/to_json/\n  template<typename BasicJsonType, typename TargetType = ValueType>\n  static auto to_json(BasicJsonType& j, TargetType && val) noexcept(\n    noexcept(::nlohmann::to_json(j, std::forward<TargetType>(val))))\n  -> decltype(::nlohmann::to_json(j, std::forward<TargetType>(val)), void())\n  {\n    ::nlohmann::to_json(j, std::forward<TargetType>(val));\n  }\n};\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/byte_container_with_subtype.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstdint> // uint8_t, uint64_t\n#include <tuple> // tie\n#include <utility> // move\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/// @brief an internal type for a backed binary type\n/// @sa https://json.nlohmann.me/api/byte_container_with_subtype/\ntemplate<typename BinaryType>\nclass byte_container_with_subtype : public BinaryType\n{\npublic:\n  using container_type = BinaryType;\n  using subtype_type = std::uint64_t;\n\n  /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/\n  byte_container_with_subtype() noexcept(noexcept(container_type()))\n    : container_type()\n  {}\n\n  /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/\n  byte_container_with_subtype(const container_type& b) noexcept(noexcept(\n        container_type(b)))\n    : container_type(b)\n  {}\n\n  /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/\n  byte_container_with_subtype(container_type&& b) noexcept(noexcept(\n        container_type(std::move(b))))\n    : container_type(std::move(b))\n  {}\n\n  /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/\n  byte_container_with_subtype(const container_type& b,\n                              subtype_type subtype_) noexcept(noexcept(container_type(b)))\n    : container_type(b)\n    , m_subtype(subtype_)\n    , m_has_subtype(true)\n  {}\n\n  /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/\n  byte_container_with_subtype(container_type&& b,\n                              subtype_type subtype_) noexcept(noexcept(container_type(std::move(b))))\n    : container_type(std::move(b))\n    , m_subtype(subtype_)\n    , m_has_subtype(true)\n  {}\n\n  bool operator==(const byte_container_with_subtype& rhs) const\n  {\n    return std::tie(static_cast<const BinaryType&>(*this), m_subtype,\n                    m_has_subtype) ==\n           std::tie(static_cast<const BinaryType&>(rhs), rhs.m_subtype, rhs.m_has_subtype);\n  }\n\n  bool operator!=(const byte_container_with_subtype& rhs) const\n  {\n    return !(rhs == *this);\n  }\n\n  /// @brief sets the binary subtype\n  /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/set_subtype/\n  void set_subtype(subtype_type subtype_) noexcept\n  {\n    m_subtype = subtype_;\n    m_has_subtype = true;\n  }\n\n  /// @brief return the binary subtype\n  /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/subtype/\n  constexpr subtype_type subtype() const noexcept\n  {\n    return m_has_subtype ? m_subtype : static_cast<subtype_type>(-1);\n  }\n\n  /// @brief return whether the value has a subtype\n  /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/has_subtype/\n  constexpr bool has_subtype() const noexcept\n  {\n    return m_has_subtype;\n  }\n\n  /// @brief clears the binary subtype\n  /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/clear_subtype/\n  void clear_subtype() noexcept\n  {\n    m_subtype = 0;\n    m_has_subtype = false;\n  }\n\nprivate:\n  subtype_type m_subtype = 0;\n  bool m_has_subtype = false;\n};\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/conversions/from_json.hpp>\n\n// #include <nlohmann/detail/conversions/to_json.hpp>\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/hash.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstdint> // uint8_t\n#include <cstddef> // size_t\n#include <functional> // hash\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n// boost::hash_combine\ninline std::size_t combine(std::size_t seed, std::size_t h) noexcept\n{\n  seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U);\n  return seed;\n}\n\n/*!\n@brief hash a JSON value\n\nThe hash function tries to rely on std::hash where possible. Furthermore, the\ntype of the JSON value is taken into account to have different hash values for\nnull, 0, 0U, and false, etc.\n\n@tparam BasicJsonType basic_json specialization\n@param j JSON value to hash\n@return hash value of j\n*/\ntemplate<typename BasicJsonType>\nstd::size_t hash(const BasicJsonType& j)\n{\n  using string_t = typename BasicJsonType::string_t;\n  using number_integer_t = typename BasicJsonType::number_integer_t;\n  using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n  using number_float_t = typename BasicJsonType::number_float_t;\n  const auto type = static_cast<std::size_t>(j.type());\n\n  switch (j.type()) {\n  case BasicJsonType::value_t::null:\n  case BasicJsonType::value_t::discarded: {\n    return combine(type, 0);\n  }\n\n  case BasicJsonType::value_t::object: {\n    auto seed = combine(type, j.size());\n\n    for (const auto& element : j.items()) {\n      const auto h = std::hash<string_t> {}(element.key());\n      seed = combine(seed, h);\n      seed = combine(seed, hash(element.value()));\n    }\n\n    return seed;\n  }\n\n  case BasicJsonType::value_t::array: {\n    auto seed = combine(type, j.size());\n\n    for (const auto& element : j) {\n      seed = combine(seed, hash(element));\n    }\n\n    return seed;\n  }\n\n  case BasicJsonType::value_t::string: {\n    const auto h = std::hash<string_t> {}(j.template get_ref<const string_t&>());\n    return combine(type, h);\n  }\n\n  case BasicJsonType::value_t::boolean: {\n    const auto h = std::hash<bool> {}(j.template get<bool>());\n    return combine(type, h);\n  }\n\n  case BasicJsonType::value_t::number_integer: {\n    const auto h = std::hash<number_integer_t> {}(j.template\n                   get<number_integer_t>());\n    return combine(type, h);\n  }\n\n  case BasicJsonType::value_t::number_unsigned: {\n    const auto h = std::hash<number_unsigned_t> {}(j.template\n                   get<number_unsigned_t>());\n    return combine(type, h);\n  }\n\n  case BasicJsonType::value_t::number_float: {\n    const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>());\n    return combine(type, h);\n  }\n\n  case BasicJsonType::value_t::binary: {\n    auto seed = combine(type, j.get_binary().size());\n    const auto h = std::hash<bool> {}(j.get_binary().has_subtype());\n    seed = combine(seed, h);\n    seed = combine(seed, static_cast<std::size_t>(j.get_binary().subtype()));\n\n    for (const auto byte : j.get_binary()) {\n      seed = combine(seed, std::hash<std::uint8_t> {}(byte));\n    }\n\n    return seed;\n  }\n\n  default:                   // LCOV_EXCL_LINE\n    JSON_ASSERT(\n      false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n    return 0;              // LCOV_EXCL_LINE\n  }\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/input/binary_reader.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // generate_n\n#include <array> // array\n#include <cmath> // ldexp\n#include <cstddef> // size_t\n#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t\n#include <cstdio> // snprintf\n#include <cstring> // memcpy\n#include <iterator> // back_inserter\n#include <limits> // numeric_limits\n#include <string> // char_traits, string\n#include <utility> // make_pair, move\n#include <vector> // vector\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/input/input_adapters.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <array> // array\n#include <cstddef> // size_t\n#include <cstring> // strlen\n#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next\n#include <memory> // shared_ptr, make_shared, addressof\n#include <numeric> // accumulate\n#include <string> // string, char_traits\n#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer\n#include <utility> // pair, declval\n\n#ifndef JSON_NO_IO\n#include <cstdio>   // FILE *\n#include <istream>  // istream\n#endif                  // JSON_NO_IO\n\n// #include <nlohmann/detail/iterators/iterator_traits.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/// the supported input formats\nenum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata };\n\n////////////////////\n// input adapters //\n////////////////////\n\n#ifndef JSON_NO_IO\n/*!\nInput adapter for stdio file access. This adapter read only 1 byte and do not use any\n buffer. This adapter is a very low level adapter.\n*/\nclass file_input_adapter\n{\npublic:\n  using char_type = char;\n\n  JSON_HEDLEY_NON_NULL(2)\n  explicit file_input_adapter(std::FILE* f) noexcept\n    : m_file(f)\n  {\n    JSON_ASSERT(m_file != nullptr);\n  }\n\n  // make class move-only\n  file_input_adapter(const file_input_adapter&) = delete;\n  file_input_adapter(file_input_adapter&&) noexcept = default;\n  file_input_adapter& operator=(const file_input_adapter&) = delete;\n  file_input_adapter& operator=(file_input_adapter&&) = delete;\n  ~file_input_adapter() = default;\n\n  std::char_traits<char>::int_type get_character() noexcept\n  {\n    return std::fgetc(m_file);\n  }\n\nprivate:\n  /// the file pointer to read from\n  std::FILE* m_file;\n};\n\n\n/*!\nInput adapter for a (caching) istream. Ignores a UFT Byte Order Mark at\nbeginning of input. Does not support changing the underlying std::streambuf\nin mid-input. Maintains underlying std::istream and std::streambuf to support\nsubsequent use of standard std::istream operations to process any input\ncharacters following those used in parsing the JSON input.  Clears the\nstd::istream flags; any input errors (e.g., EOF) will be detected by the first\nsubsequent call for input from the std::istream.\n*/\nclass input_stream_adapter\n{\npublic:\n  using char_type = char;\n\n  ~input_stream_adapter()\n  {\n    // clear stream flags; we use underlying streambuf I/O, do not\n    // maintain ifstream flags, except eof\n    if (is != nullptr) {\n      is->clear(is->rdstate() & std::ios::eofbit);\n    }\n  }\n\n  explicit input_stream_adapter(std::istream& i)\n    : is(&i), sb(i.rdbuf())\n  {}\n\n  // delete because of pointer members\n  input_stream_adapter(const input_stream_adapter&) = delete;\n  input_stream_adapter& operator=(input_stream_adapter&) = delete;\n  input_stream_adapter& operator=(input_stream_adapter&&) = delete;\n\n  input_stream_adapter(input_stream_adapter&& rhs) noexcept\n    : is(rhs.is), sb(rhs.sb)\n  {\n    rhs.is = nullptr;\n    rhs.sb = nullptr;\n  }\n\n  // std::istream/std::streambuf use std::char_traits<char>::to_int_type, to\n  // ensure that std::char_traits<char>::eof() and the character 0xFF do not\n  // end up as the same value, e.g. 0xFFFFFFFF.\n  std::char_traits<char>::int_type get_character()\n  {\n    auto res = sb->sbumpc();\n\n    // set eof manually, as we don't use the istream interface.\n    if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof())) {\n      is->clear(is->rdstate() | std::ios::eofbit);\n    }\n\n    return res;\n  }\n\nprivate:\n  /// the associated input stream\n  std::istream* is = nullptr;\n  std::streambuf* sb = nullptr;\n};\n#endif  // JSON_NO_IO\n\n// General-purpose iterator-based adapter. It might not be as fast as\n// theoretically possible for some containers, but it is extremely versatile.\ntemplate<typename IteratorType>\nclass iterator_input_adapter\n{\npublic:\n  using char_type = typename std::iterator_traits<IteratorType>::value_type;\n\n  iterator_input_adapter(IteratorType first, IteratorType last)\n    : current(std::move(first)), end(std::move(last))\n  {}\n\n  typename std::char_traits<char_type>::int_type get_character()\n  {\n    if (JSON_HEDLEY_LIKELY(current != end)) {\n      auto result = std::char_traits<char_type>::to_int_type(*current);\n      std::advance(current, 1);\n      return result;\n    }\n\n    return std::char_traits<char_type>::eof();\n  }\n\nprivate:\n  IteratorType current;\n  IteratorType end;\n\n  template<typename BaseInputAdapter, size_t T>\n  friend struct wide_string_input_helper;\n\n  bool empty() const\n  {\n    return current == end;\n  }\n};\n\n\ntemplate<typename BaseInputAdapter, size_t T>\nstruct wide_string_input_helper;\n\ntemplate<typename BaseInputAdapter>\nstruct wide_string_input_helper<BaseInputAdapter, 4> {\n  // UTF-32\n  static void fill_buffer(BaseInputAdapter& input,\n                          std::array<std::char_traits<char>::int_type, 4>& utf8_bytes,\n                          size_t& utf8_bytes_index,\n                          size_t& utf8_bytes_filled)\n  {\n    utf8_bytes_index = 0;\n\n    if (JSON_HEDLEY_UNLIKELY(input.empty())) {\n      utf8_bytes[0] = std::char_traits<char>::eof();\n      utf8_bytes_filled = 1;\n    } else {\n      // get the current character\n      const auto wc = input.get_character();\n\n      // UTF-32 to UTF-8 encoding\n      if (wc < 0x80) {\n        utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);\n        utf8_bytes_filled = 1;\n      } else if (wc <= 0x7FF) {\n        utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xC0u | ((\n                          static_cast<unsigned int>(wc) >> 6u) & 0x1Fu));\n        utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u |\n                        (static_cast<unsigned int>(wc) & 0x3Fu));\n        utf8_bytes_filled = 2;\n      } else if (wc <= 0xFFFF) {\n        utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xE0u | ((\n                          static_cast<unsigned int>(wc) >> 12u) & 0x0Fu));\n        utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((\n                          static_cast<unsigned int>(wc) >> 6u) & 0x3Fu));\n        utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u |\n                        (static_cast<unsigned int>(wc) & 0x3Fu));\n        utf8_bytes_filled = 3;\n      } else if (wc <= 0x10FFFF) {\n        utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | ((\n                          static_cast<unsigned int>(wc) >> 18u) & 0x07u));\n        utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((\n                          static_cast<unsigned int>(wc) >> 12u) & 0x3Fu));\n        utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((\n                          static_cast<unsigned int>(wc) >> 6u) & 0x3Fu));\n        utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u |\n                        (static_cast<unsigned int>(wc) & 0x3Fu));\n        utf8_bytes_filled = 4;\n      } else {\n        // unknown character\n        utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);\n        utf8_bytes_filled = 1;\n      }\n    }\n  }\n};\n\ntemplate<typename BaseInputAdapter>\nstruct wide_string_input_helper<BaseInputAdapter, 2> {\n  // UTF-16\n  static void fill_buffer(BaseInputAdapter& input,\n                          std::array<std::char_traits<char>::int_type, 4>& utf8_bytes,\n                          size_t& utf8_bytes_index,\n                          size_t& utf8_bytes_filled)\n  {\n    utf8_bytes_index = 0;\n\n    if (JSON_HEDLEY_UNLIKELY(input.empty())) {\n      utf8_bytes[0] = std::char_traits<char>::eof();\n      utf8_bytes_filled = 1;\n    } else {\n      // get the current character\n      const auto wc = input.get_character();\n\n      // UTF-16 to UTF-8 encoding\n      if (wc < 0x80) {\n        utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);\n        utf8_bytes_filled = 1;\n      } else if (wc <= 0x7FF) {\n        utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xC0u | ((\n                          static_cast<unsigned int>(wc) >> 6u)));\n        utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u |\n                        (static_cast<unsigned int>(wc) & 0x3Fu));\n        utf8_bytes_filled = 2;\n      } else if (0xD800 > wc || wc >= 0xE000) {\n        utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xE0u | ((\n                          static_cast<unsigned int>(wc) >> 12u)));\n        utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((\n                          static_cast<unsigned int>(wc) >> 6u) & 0x3Fu));\n        utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u |\n                        (static_cast<unsigned int>(wc) & 0x3Fu));\n        utf8_bytes_filled = 3;\n      } else {\n        if (JSON_HEDLEY_UNLIKELY(!input.empty())) {\n          const auto wc2 = static_cast<unsigned int>(input.get_character());\n          const auto charcode = 0x10000u + (((static_cast<unsigned int>\n                                              (wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu));\n          utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u |\n                          (charcode >> 18u));\n          utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((\n                            charcode >> 12u) & 0x3Fu));\n          utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((\n                            charcode >> 6u) & 0x3Fu));\n          utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u |\n                          (charcode & 0x3Fu));\n          utf8_bytes_filled = 4;\n        } else {\n          utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);\n          utf8_bytes_filled = 1;\n        }\n      }\n    }\n  }\n};\n\n// Wraps another input apdater to convert wide character types into individual bytes.\ntemplate<typename BaseInputAdapter, typename WideCharType>\nclass wide_string_input_adapter\n{\npublic:\n  using char_type = char;\n\n  wide_string_input_adapter(BaseInputAdapter base)\n    : base_adapter(base) {}\n\n  typename std::char_traits<char>::int_type get_character() noexcept\n  {\n    // check if buffer needs to be filled\n    if (utf8_bytes_index == utf8_bytes_filled) {\n      fill_buffer<sizeof(WideCharType)>();\n      JSON_ASSERT(utf8_bytes_filled > 0);\n      JSON_ASSERT(utf8_bytes_index == 0);\n    }\n\n    // use buffer\n    JSON_ASSERT(utf8_bytes_filled > 0);\n    JSON_ASSERT(utf8_bytes_index < utf8_bytes_filled);\n    return utf8_bytes[utf8_bytes_index++];\n  }\n\nprivate:\n  BaseInputAdapter base_adapter;\n\n  template<size_t T>\n  void fill_buffer()\n  {\n    wide_string_input_helper<BaseInputAdapter, T>::fill_buffer(base_adapter,\n        utf8_bytes, utf8_bytes_index, utf8_bytes_filled);\n  }\n\n  /// a buffer for UTF-8 bytes\n  std::array<std::char_traits<char>::int_type, 4> utf8_bytes = {{0, 0, 0, 0}};\n\n  /// index to the utf8_codes array for the next valid byte\n  std::size_t utf8_bytes_index = 0;\n  /// number of valid bytes in the utf8_codes array\n  std::size_t utf8_bytes_filled = 0;\n};\n\n\ntemplate<typename IteratorType, typename Enable = void>\nstruct iterator_input_adapter_factory {\n  using iterator_type = IteratorType;\n  using char_type = typename std::iterator_traits<iterator_type>::value_type;\n  using adapter_type = iterator_input_adapter<iterator_type>;\n\n  static adapter_type create(IteratorType first, IteratorType last)\n  {\n    return adapter_type(std::move(first), std::move(last));\n  }\n};\n\ntemplate<typename T>\nstruct is_iterator_of_multibyte {\n  using value_type = typename std::iterator_traits<T>::value_type;\n  enum {\n    value = sizeof(value_type) > 1\n  };\n};\n\ntemplate<typename IteratorType>\nstruct iterator_input_adapter_factory<IteratorType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>> {\n  using iterator_type = IteratorType;\n  using char_type = typename std::iterator_traits<iterator_type>::value_type;\n  using base_adapter_type = iterator_input_adapter<iterator_type>;\n  using adapter_type = wide_string_input_adapter<base_adapter_type, char_type>;\n\n  static adapter_type create(IteratorType first, IteratorType last)\n  {\n    return adapter_type(base_adapter_type(std::move(first), std::move(last)));\n  }\n};\n\n// General purpose iterator-based input\ntemplate<typename IteratorType>\ntypename iterator_input_adapter_factory<IteratorType>::adapter_type\ninput_adapter(IteratorType first, IteratorType last)\n{\n  using factory_type = iterator_input_adapter_factory<IteratorType>;\n  return factory_type::create(first, last);\n}\n\n// Convenience shorthand from container to iterator\n// Enables ADL on begin(container) and end(container)\n// Encloses the using declarations in namespace for not to leak them to outside scope\n\nnamespace container_input_adapter_factory_impl\n{\n\nusing std::begin;\nusing std::end;\n\ntemplate<typename ContainerType, typename Enable = void>\nstruct container_input_adapter_factory {};\n\ntemplate<typename ContainerType>\nstruct container_input_adapter_factory< ContainerType,\n       void_t<decltype(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>()))>> {\n         using adapter_type = decltype(input_adapter(begin(\n             std::declval<ContainerType>()), end(std::declval<ContainerType>())));\n\n         static adapter_type create(const ContainerType& container)\n{\n  return input_adapter(begin(container), end(container));\n}\n       };\n\n}  // namespace container_input_adapter_factory_impl\n\ntemplate<typename ContainerType>\ntypename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type\ninput_adapter(const ContainerType& container)\n{\n  return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(\n           container);\n}\n\n#ifndef JSON_NO_IO\n// Special cases with fast paths\ninline file_input_adapter input_adapter(std::FILE* file)\n{\n  return file_input_adapter(file);\n}\n\ninline input_stream_adapter input_adapter(std::istream& stream)\n{\n  return input_stream_adapter(stream);\n}\n\ninline input_stream_adapter input_adapter(std::istream&& stream)\n{\n  return input_stream_adapter(stream);\n}\n#endif  // JSON_NO_IO\n\nusing contiguous_bytes_input_adapter = decltype(input_adapter(\n    std::declval<const char*>(), std::declval<const char*>()));\n\n// Null-delimited strings, and the like.\ntemplate < typename CharT,\n           typename std::enable_if <\n             std::is_pointer<CharT>::value&&\n             !std::is_array<CharT>::value&&\n             std::is_integral<typename std::remove_pointer<CharT>::type>::value&&\n             sizeof(typename std::remove_pointer<CharT>::type) == 1,\n             int >::type = 0 >\ncontiguous_bytes_input_adapter input_adapter(CharT b)\n{\n  auto length = std::strlen(reinterpret_cast<const char*>(b));\n  const auto* ptr = reinterpret_cast<const char*>(b);\n  return input_adapter(ptr, ptr + length);\n}\n\ntemplate<typename T, std::size_t N>\nauto input_adapter(T(&array)[N]) -> decltype(input_adapter(array,\n    array + N))  // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n{\n  return input_adapter(array, array + N);\n}\n\n// This class only handles inputs of input_buffer_adapter type.\n// It's required so that expressions like {ptr, len} can be implicitly cast\n// to the correct adapter.\nclass span_input_adapter\n{\npublic:\n  template < typename CharT,\n             typename std::enable_if <\n               std::is_pointer<CharT>::value&&\n               std::is_integral<typename std::remove_pointer<CharT>::type>::value&&\n               sizeof(typename std::remove_pointer<CharT>::type) == 1,\n               int >::type = 0 >\n  span_input_adapter(CharT b, std::size_t l)\n    : ia(reinterpret_cast<const char*>(b), reinterpret_cast<const char*>(b) + l) {}\n\n  template<class IteratorType,\n           typename std::enable_if<\n             std::is_same<typename iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value,\n             int>::type = 0>\n  span_input_adapter(IteratorType first, IteratorType last)\n    : ia(input_adapter(first, last)) {}\n\n  contiguous_bytes_input_adapter&& get()\n  {\n    return std::move(ia); // NOLINT(hicpp-move-const-arg,performance-move-const-arg)\n  }\n\nprivate:\n  contiguous_bytes_input_adapter ia;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/input/json_sax.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef>\n#include <string> // string\n#include <utility> // move\n#include <vector> // vector\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/*!\n@brief SAX interface\n\nThis class describes the SAX interface used by @ref nlohmann::json::sax_parse.\nEach function is called in different situations while the input is parsed. The\nboolean return value informs the parser whether to continue processing the\ninput.\n*/\ntemplate<typename BasicJsonType>\nstruct json_sax {\n  using number_integer_t = typename BasicJsonType::number_integer_t;\n  using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n  using number_float_t = typename BasicJsonType::number_float_t;\n  using string_t = typename BasicJsonType::string_t;\n  using binary_t = typename BasicJsonType::binary_t;\n\n  /*!\n  @brief a null value was read\n  @return whether parsing should proceed\n  */\n  virtual bool null() = 0;\n\n  /*!\n  @brief a boolean value was read\n  @param[in] val  boolean value\n  @return whether parsing should proceed\n  */\n  virtual bool boolean(bool val) = 0;\n\n  /*!\n  @brief an integer number was read\n  @param[in] val  integer value\n  @return whether parsing should proceed\n  */\n  virtual bool number_integer(number_integer_t val) = 0;\n\n  /*!\n  @brief an unsigned integer number was read\n  @param[in] val  unsigned integer value\n  @return whether parsing should proceed\n  */\n  virtual bool number_unsigned(number_unsigned_t val) = 0;\n\n  /*!\n  @brief a floating-point number was read\n  @param[in] val  floating-point value\n  @param[in] s    raw token value\n  @return whether parsing should proceed\n  */\n  virtual bool number_float(number_float_t val, const string_t& s) = 0;\n\n  /*!\n  @brief a string value was read\n  @param[in] val  string value\n  @return whether parsing should proceed\n  @note It is safe to move the passed string value.\n  */\n  virtual bool string(string_t& val) = 0;\n\n  /*!\n  @brief a binary value was read\n  @param[in] val  binary value\n  @return whether parsing should proceed\n  @note It is safe to move the passed binary value.\n  */\n  virtual bool binary(binary_t& val) = 0;\n\n  /*!\n  @brief the beginning of an object was read\n  @param[in] elements  number of object elements or -1 if unknown\n  @return whether parsing should proceed\n  @note binary formats may report the number of elements\n  */\n  virtual bool start_object(std::size_t elements) = 0;\n\n  /*!\n  @brief an object key was read\n  @param[in] val  object key\n  @return whether parsing should proceed\n  @note It is safe to move the passed string.\n  */\n  virtual bool key(string_t& val) = 0;\n\n  /*!\n  @brief the end of an object was read\n  @return whether parsing should proceed\n  */\n  virtual bool end_object() = 0;\n\n  /*!\n  @brief the beginning of an array was read\n  @param[in] elements  number of array elements or -1 if unknown\n  @return whether parsing should proceed\n  @note binary formats may report the number of elements\n  */\n  virtual bool start_array(std::size_t elements) = 0;\n\n  /*!\n  @brief the end of an array was read\n  @return whether parsing should proceed\n  */\n  virtual bool end_array() = 0;\n\n  /*!\n  @brief a parse error occurred\n  @param[in] position    the position in the input where the error occurs\n  @param[in] last_token  the last read token\n  @param[in] ex          an exception object describing the error\n  @return whether parsing should proceed (must return false)\n  */\n  virtual bool parse_error(std::size_t position,\n                           const std::string& last_token,\n                           const detail::exception& ex) = 0;\n\n  json_sax() = default;\n  json_sax(const json_sax&) = default;\n  json_sax(json_sax&&) noexcept = default;\n  json_sax& operator=(const json_sax&) = default;\n  json_sax& operator=(json_sax&&) noexcept = default;\n  virtual ~json_sax() = default;\n};\n\n\nnamespace detail\n{\n/*!\n@brief SAX implementation to create a JSON value from SAX events\n\nThis class implements the @ref json_sax interface and processes the SAX events\nto create a JSON value which makes it basically a DOM parser. The structure or\nhierarchy of the JSON value is managed by the stack `ref_stack` which contains\na pointer to the respective array or object for each recursion depth.\n\nAfter successful parsing, the value that is passed by reference to the\nconstructor contains the parsed value.\n\n@tparam BasicJsonType  the JSON type\n*/\ntemplate<typename BasicJsonType>\nclass json_sax_dom_parser\n{\npublic:\n  using number_integer_t = typename BasicJsonType::number_integer_t;\n  using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n  using number_float_t = typename BasicJsonType::number_float_t;\n  using string_t = typename BasicJsonType::string_t;\n  using binary_t = typename BasicJsonType::binary_t;\n\n  /*!\n  @param[in,out] r  reference to a JSON value that is manipulated while\n                     parsing\n  @param[in] allow_exceptions_  whether parse errors yield exceptions\n  */\n  explicit json_sax_dom_parser(BasicJsonType& r,\n                               const bool allow_exceptions_ = true)\n    : root(r), allow_exceptions(allow_exceptions_)\n  {}\n\n  // make class move-only\n  json_sax_dom_parser(const json_sax_dom_parser&) = delete;\n  json_sax_dom_parser(json_sax_dom_parser&&) =\n    default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n  json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete;\n  json_sax_dom_parser& operator=(json_sax_dom_parser&&) =\n    default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n  ~json_sax_dom_parser() = default;\n\n  bool null()\n  {\n    handle_value(nullptr);\n    return true;\n  }\n\n  bool boolean(bool val)\n  {\n    handle_value(val);\n    return true;\n  }\n\n  bool number_integer(number_integer_t val)\n  {\n    handle_value(val);\n    return true;\n  }\n\n  bool number_unsigned(number_unsigned_t val)\n  {\n    handle_value(val);\n    return true;\n  }\n\n  bool number_float(number_float_t val, const string_t& /*unused*/)\n  {\n    handle_value(val);\n    return true;\n  }\n\n  bool string(string_t& val)\n  {\n    handle_value(val);\n    return true;\n  }\n\n  bool binary(binary_t& val)\n  {\n    handle_value(std::move(val));\n    return true;\n  }\n\n  bool start_object(std::size_t len)\n  {\n    ref_stack.push_back(handle_value(BasicJsonType::value_t::object));\n\n    if (JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) &&\n                             len > ref_stack.back()->max_size())) {\n      JSON_THROW(out_of_range::create(408, concat(\"excessive object size: \",\n                                      std::to_string(len)), ref_stack.back()));\n    }\n\n    return true;\n  }\n\n  bool key(string_t& val)\n  {\n    JSON_ASSERT(!ref_stack.empty());\n    JSON_ASSERT(ref_stack.back()->is_object());\n    // add null at given key and store the reference for later\n    object_element = &(ref_stack.back()->m_value.object->operator[](val));\n    return true;\n  }\n\n  bool end_object()\n  {\n    JSON_ASSERT(!ref_stack.empty());\n    JSON_ASSERT(ref_stack.back()->is_object());\n    ref_stack.back()->set_parents();\n    ref_stack.pop_back();\n    return true;\n  }\n\n  bool start_array(std::size_t len)\n  {\n    ref_stack.push_back(handle_value(BasicJsonType::value_t::array));\n\n    if (JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) &&\n                             len > ref_stack.back()->max_size())) {\n      JSON_THROW(out_of_range::create(408, concat(\"excessive array size: \",\n                                      std::to_string(len)), ref_stack.back()));\n    }\n\n    return true;\n  }\n\n  bool end_array()\n  {\n    JSON_ASSERT(!ref_stack.empty());\n    JSON_ASSERT(ref_stack.back()->is_array());\n    ref_stack.back()->set_parents();\n    ref_stack.pop_back();\n    return true;\n  }\n\n  template<class Exception>\n  bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,\n                   const Exception& ex)\n  {\n    errored = true;\n    static_cast<void>(ex);\n\n    if (allow_exceptions) {\n      JSON_THROW(ex);\n    }\n\n    return false;\n  }\n\n  constexpr bool is_errored() const\n  {\n    return errored;\n  }\n\nprivate:\n  /*!\n  @invariant If the ref stack is empty, then the passed value will be the new\n             root.\n  @invariant If the ref stack contains a value, then it is an array or an\n             object to which we can add elements\n  */\n  template<typename Value>\n  JSON_HEDLEY_RETURNS_NON_NULL\n  BasicJsonType* handle_value(Value&& v)\n  {\n    if (ref_stack.empty()) {\n      root = BasicJsonType(std::forward<Value>(v));\n      return &root;\n    }\n\n    JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object());\n\n    if (ref_stack.back()->is_array()) {\n      ref_stack.back()->m_value.array->emplace_back(std::forward<Value>(v));\n      return &(ref_stack.back()->m_value.array->back());\n    }\n\n    JSON_ASSERT(ref_stack.back()->is_object());\n    JSON_ASSERT(object_element);\n    *object_element = BasicJsonType(std::forward<Value>(v));\n    return object_element;\n  }\n\n  /// the parsed JSON value\n  BasicJsonType& root;\n  /// stack to model hierarchy of values\n  std::vector<BasicJsonType*> ref_stack {};\n  /// helper to hold the reference for the next object element\n  BasicJsonType* object_element = nullptr;\n  /// whether a syntax error occurred\n  bool errored = false;\n  /// whether to throw exceptions in case of errors\n  const bool allow_exceptions = true;\n};\n\ntemplate<typename BasicJsonType>\nclass json_sax_dom_callback_parser\n{\npublic:\n  using number_integer_t = typename BasicJsonType::number_integer_t;\n  using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n  using number_float_t = typename BasicJsonType::number_float_t;\n  using string_t = typename BasicJsonType::string_t;\n  using binary_t = typename BasicJsonType::binary_t;\n  using parser_callback_t = typename BasicJsonType::parser_callback_t;\n  using parse_event_t = typename BasicJsonType::parse_event_t;\n\n  json_sax_dom_callback_parser(BasicJsonType& r,\n                               const parser_callback_t cb,\n                               const bool allow_exceptions_ = true)\n    : root(r), callback(cb), allow_exceptions(allow_exceptions_)\n  {\n    keep_stack.push_back(true);\n  }\n\n  // make class move-only\n  json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete;\n  json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) =\n    default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n  json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) =\n    delete;\n  json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) =\n    default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n  ~json_sax_dom_callback_parser() = default;\n\n  bool null()\n  {\n    handle_value(nullptr);\n    return true;\n  }\n\n  bool boolean(bool val)\n  {\n    handle_value(val);\n    return true;\n  }\n\n  bool number_integer(number_integer_t val)\n  {\n    handle_value(val);\n    return true;\n  }\n\n  bool number_unsigned(number_unsigned_t val)\n  {\n    handle_value(val);\n    return true;\n  }\n\n  bool number_float(number_float_t val, const string_t& /*unused*/)\n  {\n    handle_value(val);\n    return true;\n  }\n\n  bool string(string_t& val)\n  {\n    handle_value(val);\n    return true;\n  }\n\n  bool binary(binary_t& val)\n  {\n    handle_value(std::move(val));\n    return true;\n  }\n\n  bool start_object(std::size_t len)\n  {\n    // check callback for object start\n    const bool keep = callback(static_cast<int>(ref_stack.size()),\n                               parse_event_t::object_start, discarded);\n    keep_stack.push_back(keep);\n    auto val = handle_value(BasicJsonType::value_t::object, true);\n    ref_stack.push_back(val.second);\n\n    // check object limit\n    if (ref_stack.back() &&\n        JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) &&\n                             len > ref_stack.back()->max_size())) {\n      JSON_THROW(out_of_range::create(408, concat(\"excessive object size: \",\n                                      std::to_string(len)), ref_stack.back()));\n    }\n\n    return true;\n  }\n\n  bool key(string_t& val)\n  {\n    BasicJsonType k = BasicJsonType(val);\n    // check callback for key\n    const bool keep = callback(static_cast<int>(ref_stack.size()),\n                               parse_event_t::key, k);\n    key_keep_stack.push_back(keep);\n\n    // add discarded value at given key and store the reference for later\n    if (keep && ref_stack.back()) {\n      object_element = &(ref_stack.back()->m_value.object->operator[](\n                           val) = discarded);\n    }\n\n    return true;\n  }\n\n  bool end_object()\n  {\n    if (ref_stack.back()) {\n      if (!callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::object_end,\n                    *ref_stack.back())) {\n        // discard object\n        *ref_stack.back() = discarded;\n      } else {\n        ref_stack.back()->set_parents();\n      }\n    }\n\n    JSON_ASSERT(!ref_stack.empty());\n    JSON_ASSERT(!keep_stack.empty());\n    ref_stack.pop_back();\n    keep_stack.pop_back();\n\n    if (!ref_stack.empty() && ref_stack.back() &&\n        ref_stack.back()->is_structured()) {\n      // remove discarded value\n      for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) {\n        if (it->is_discarded()) {\n          ref_stack.back()->erase(it);\n          break;\n        }\n      }\n    }\n\n    return true;\n  }\n\n  bool start_array(std::size_t len)\n  {\n    const bool keep = callback(static_cast<int>(ref_stack.size()),\n                               parse_event_t::array_start, discarded);\n    keep_stack.push_back(keep);\n    auto val = handle_value(BasicJsonType::value_t::array, true);\n    ref_stack.push_back(val.second);\n\n    // check array limit\n    if (ref_stack.back() &&\n        JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) &&\n                             len > ref_stack.back()->max_size())) {\n      JSON_THROW(out_of_range::create(408, concat(\"excessive array size: \",\n                                      std::to_string(len)), ref_stack.back()));\n    }\n\n    return true;\n  }\n\n  bool end_array()\n  {\n    bool keep = true;\n\n    if (ref_stack.back()) {\n      keep = callback(static_cast<int>(ref_stack.size()) - 1,\n                      parse_event_t::array_end, *ref_stack.back());\n\n      if (keep) {\n        ref_stack.back()->set_parents();\n      } else {\n        // discard array\n        *ref_stack.back() = discarded;\n      }\n    }\n\n    JSON_ASSERT(!ref_stack.empty());\n    JSON_ASSERT(!keep_stack.empty());\n    ref_stack.pop_back();\n    keep_stack.pop_back();\n\n    // remove discarded value\n    if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) {\n      ref_stack.back()->m_value.array->pop_back();\n    }\n\n    return true;\n  }\n\n  template<class Exception>\n  bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,\n                   const Exception& ex)\n  {\n    errored = true;\n    static_cast<void>(ex);\n\n    if (allow_exceptions) {\n      JSON_THROW(ex);\n    }\n\n    return false;\n  }\n\n  constexpr bool is_errored() const\n  {\n    return errored;\n  }\n\nprivate:\n  /*!\n  @param[in] v  value to add to the JSON value we build during parsing\n  @param[in] skip_callback  whether we should skip calling the callback\n             function; this is required after start_array() and\n             start_object() SAX events, because otherwise we would call the\n             callback function with an empty array or object, respectively.\n\n  @invariant If the ref stack is empty, then the passed value will be the new\n             root.\n  @invariant If the ref stack contains a value, then it is an array or an\n             object to which we can add elements\n\n  @return pair of boolean (whether value should be kept) and pointer (to the\n          passed value in the ref_stack hierarchy; nullptr if not kept)\n  */\n  template<typename Value>\n  std::pair<bool, BasicJsonType*> handle_value(Value&& v,\n      const bool skip_callback = false)\n  {\n    JSON_ASSERT(!keep_stack.empty());\n\n    // do not handle this value if we know it would be added to a discarded\n    // container\n    if (!keep_stack.back()) {\n      return {false, nullptr};\n    }\n\n    // create value\n    auto value = BasicJsonType(std::forward<Value>(v));\n    // check callback\n    const bool keep = skip_callback ||\n                      callback(static_cast<int>(ref_stack.size()), parse_event_t::value, value);\n\n    // do not handle this value if we just learnt it shall be discarded\n    if (!keep) {\n      return {false, nullptr};\n    }\n\n    if (ref_stack.empty()) {\n      root = std::move(value);\n      return {true, &root};\n    }\n\n    // skip this value if we already decided to skip the parent\n    // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360)\n    if (!ref_stack.back()) {\n      return {false, nullptr};\n    }\n\n    // we now only expect arrays and objects\n    JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object());\n\n    // array\n    if (ref_stack.back()->is_array()) {\n      ref_stack.back()->m_value.array->emplace_back(std::move(value));\n      return {true, &(ref_stack.back()->m_value.array->back())};\n    }\n\n    // object\n    JSON_ASSERT(ref_stack.back()->is_object());\n    // check if we should store an element for the current key\n    JSON_ASSERT(!key_keep_stack.empty());\n    const bool store_element = key_keep_stack.back();\n    key_keep_stack.pop_back();\n\n    if (!store_element) {\n      return {false, nullptr};\n    }\n\n    JSON_ASSERT(object_element);\n    *object_element = std::move(value);\n    return {true, object_element};\n  }\n\n  /// the parsed JSON value\n  BasicJsonType& root;\n  /// stack to model hierarchy of values\n  std::vector<BasicJsonType*> ref_stack {};\n  /// stack to manage which values to keep\n  std::vector<bool> keep_stack {};\n  /// stack to manage which object keys to keep\n  std::vector<bool> key_keep_stack {};\n  /// helper to hold the reference for the next object element\n  BasicJsonType* object_element = nullptr;\n  /// whether a syntax error occurred\n  bool errored = false;\n  /// callback function\n  const parser_callback_t callback = nullptr;\n  /// whether to throw exceptions in case of errors\n  const bool allow_exceptions = true;\n  /// a discarded value for the callback\n  BasicJsonType discarded = BasicJsonType::value_t::discarded;\n};\n\ntemplate<typename BasicJsonType>\nclass json_sax_acceptor\n{\npublic:\n  using number_integer_t = typename BasicJsonType::number_integer_t;\n  using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n  using number_float_t = typename BasicJsonType::number_float_t;\n  using string_t = typename BasicJsonType::string_t;\n  using binary_t = typename BasicJsonType::binary_t;\n\n  bool null()\n  {\n    return true;\n  }\n\n  bool boolean(bool /*unused*/)\n  {\n    return true;\n  }\n\n  bool number_integer(number_integer_t /*unused*/)\n  {\n    return true;\n  }\n\n  bool number_unsigned(number_unsigned_t /*unused*/)\n  {\n    return true;\n  }\n\n  bool number_float(number_float_t /*unused*/, const string_t& /*unused*/)\n  {\n    return true;\n  }\n\n  bool string(string_t& /*unused*/)\n  {\n    return true;\n  }\n\n  bool binary(binary_t& /*unused*/)\n  {\n    return true;\n  }\n\n  bool start_object(std::size_t /*unused*/ = static_cast<std::size_t>(-1))\n  {\n    return true;\n  }\n\n  bool key(string_t& /*unused*/)\n  {\n    return true;\n  }\n\n  bool end_object()\n  {\n    return true;\n  }\n\n  bool start_array(std::size_t /*unused*/ = static_cast<std::size_t>(-1))\n  {\n    return true;\n  }\n\n  bool end_array()\n  {\n    return true;\n  }\n\n  bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,\n                   const detail::exception& /*unused*/)\n  {\n    return false;\n  }\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/input/lexer.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <array> // array\n#include <clocale> // localeconv\n#include <cstddef> // size_t\n#include <cstdio> // snprintf\n#include <cstdlib> // strtof, strtod, strtold, strtoll, strtoull\n#include <initializer_list> // initializer_list\n#include <string> // char_traits, string\n#include <utility> // move\n#include <vector> // vector\n\n// #include <nlohmann/detail/input/input_adapters.hpp>\n\n// #include <nlohmann/detail/input/position_t.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n///////////\n// lexer //\n///////////\n\ntemplate<typename BasicJsonType>\nclass lexer_base\n{\npublic:\n  /// token types for the parser\n  enum class token_type {\n    uninitialized,    ///< indicating the scanner is uninitialized\n    literal_true,     ///< the `true` literal\n    literal_false,    ///< the `false` literal\n    literal_null,     ///< the `null` literal\n    value_string,     ///< a string -- use get_string() for actual value\n    value_unsigned,   ///< an unsigned integer -- use get_number_unsigned() for actual value\n    value_integer,    ///< a signed integer -- use get_number_integer() for actual value\n    value_float,      ///< an floating point number -- use get_number_float() for actual value\n    begin_array,      ///< the character for array begin `[`\n    begin_object,     ///< the character for object begin `{`\n    end_array,        ///< the character for array end `]`\n    end_object,       ///< the character for object end `}`\n    name_separator,   ///< the name separator `:`\n    value_separator,  ///< the value separator `,`\n    parse_error,      ///< indicating a parse error\n    end_of_input,     ///< indicating the end of the input buffer\n    literal_or_value  ///< a literal or the begin of a value (only for diagnostics)\n  };\n\n  /// return name of values of type token_type (only used for errors)\n  JSON_HEDLEY_RETURNS_NON_NULL\n  JSON_HEDLEY_CONST\n  static const char* token_type_name(const token_type t) noexcept\n  {\n    switch (t) {\n    case token_type::uninitialized:\n      return \"<uninitialized>\";\n\n    case token_type::literal_true:\n      return \"true literal\";\n\n    case token_type::literal_false:\n      return \"false literal\";\n\n    case token_type::literal_null:\n      return \"null literal\";\n\n    case token_type::value_string:\n      return \"string literal\";\n\n    case token_type::value_unsigned:\n    case token_type::value_integer:\n    case token_type::value_float:\n      return \"number literal\";\n\n    case token_type::begin_array:\n      return \"'['\";\n\n    case token_type::begin_object:\n      return \"'{'\";\n\n    case token_type::end_array:\n      return \"']'\";\n\n    case token_type::end_object:\n      return \"'}'\";\n\n    case token_type::name_separator:\n      return \"':'\";\n\n    case token_type::value_separator:\n      return \"','\";\n\n    case token_type::parse_error:\n      return \"<parse error>\";\n\n    case token_type::end_of_input:\n      return \"end of input\";\n\n    case token_type::literal_or_value:\n      return \"'[', '{', or a literal\";\n\n    // LCOV_EXCL_START\n    default: // catch non-enum values\n      return \"unknown token\";\n      // LCOV_EXCL_STOP\n    }\n  }\n};\n/*!\n@brief lexical analysis\n\nThis class organizes the lexical analysis during JSON deserialization.\n*/\ntemplate<typename BasicJsonType, typename InputAdapterType>\nclass lexer : public lexer_base<BasicJsonType>\n{\n  using number_integer_t = typename BasicJsonType::number_integer_t;\n  using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n  using number_float_t = typename BasicJsonType::number_float_t;\n  using string_t = typename BasicJsonType::string_t;\n  using char_type = typename InputAdapterType::char_type;\n  using char_int_type = typename std::char_traits<char_type>::int_type;\n\npublic:\n  using token_type = typename lexer_base<BasicJsonType>::token_type;\n\n  explicit lexer(InputAdapterType&& adapter,\n                 bool ignore_comments_ = false) noexcept\n    : ia(std::move(adapter))\n    , ignore_comments(ignore_comments_)\n    , decimal_point_char(static_cast<char_int_type>(get_decimal_point()))\n  {}\n\n  // delete because of pointer members\n  lexer(const lexer&) = delete;\n  lexer(lexer&&) =\n    default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n  lexer& operator=(lexer&) = delete;\n  lexer& operator=(lexer&&) =\n    default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n  ~lexer() = default;\n\nprivate:\n  /////////////////////\n  // locales\n  /////////////////////\n\n  /// return the locale-dependent decimal point\n  JSON_HEDLEY_PURE\n  static char get_decimal_point() noexcept\n  {\n    const auto* loc = localeconv();\n    JSON_ASSERT(loc != nullptr);\n    return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point);\n  }\n\n  /////////////////////\n  // scan functions\n  /////////////////////\n\n  /*!\n  @brief get codepoint from 4 hex characters following `\\u`\n\n  For input \"\\u c1 c2 c3 c4\" the codepoint is:\n    (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4\n  = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0)\n\n  Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f'\n  must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The\n  conversion is done by subtracting the offset (0x30, 0x37, and 0x57)\n  between the ASCII value of the character and the desired integer value.\n\n  @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or\n          non-hex character)\n  */\n  int get_codepoint()\n  {\n    // this function only makes sense after reading `\\u`\n    JSON_ASSERT(current == 'u');\n    int codepoint = 0;\n    const auto factors = { 12u, 8u, 4u, 0u };\n\n    for (const auto factor : factors) {\n      get();\n\n      if (current >= '0' && current <= '9') {\n        codepoint += static_cast<int>((static_cast<unsigned int>\n                                       (current) - 0x30u) << factor);\n      } else if (current >= 'A' && current <= 'F') {\n        codepoint += static_cast<int>((static_cast<unsigned int>\n                                       (current) - 0x37u) << factor);\n      } else if (current >= 'a' && current <= 'f') {\n        codepoint += static_cast<int>((static_cast<unsigned int>\n                                       (current) - 0x57u) << factor);\n      } else {\n        return -1;\n      }\n    }\n\n    JSON_ASSERT(0x0000 <= codepoint && codepoint <= 0xFFFF);\n    return codepoint;\n  }\n\n  /*!\n  @brief check if the next byte(s) are inside a given range\n\n  Adds the current byte and, for each passed range, reads a new byte and\n  checks if it is inside the range. If a violation was detected, set up an\n  error message and return false. Otherwise, return true.\n\n  @param[in] ranges  list of integers; interpreted as list of pairs of\n                     inclusive lower and upper bound, respectively\n\n  @pre The passed list @a ranges must have 2, 4, or 6 elements; that is,\n       1, 2, or 3 pairs. This precondition is enforced by an assertion.\n\n  @return true if and only if no range violation was detected\n  */\n  bool next_byte_in_range(std::initializer_list<char_int_type> ranges)\n  {\n    JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6);\n    add(current);\n\n    for (auto range = ranges.begin(); range != ranges.end(); ++range) {\n      get();\n\n      if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) {\n        add(current);\n      } else {\n        error_message = \"invalid string: ill-formed UTF-8 byte\";\n        return false;\n      }\n    }\n\n    return true;\n  }\n\n  /*!\n  @brief scan a string literal\n\n  This function scans a string according to Sect. 7 of RFC 8259. While\n  scanning, bytes are escaped and copied into buffer token_buffer. Then the\n  function returns successfully, token_buffer is *not* null-terminated (as it\n  may contain \\0 bytes), and token_buffer.size() is the number of bytes in the\n  string.\n\n  @return token_type::value_string if string could be successfully scanned,\n          token_type::parse_error otherwise\n\n  @note In case of errors, variable error_message contains a textual\n        description.\n  */\n  token_type scan_string()\n  {\n    // reset token_buffer (ignore opening quote)\n    reset();\n    // we entered the function by reading an open quote\n    JSON_ASSERT(current == '\\\"');\n\n    while (true) {\n      // get next character\n      switch (get()) {\n      // end of file while parsing string\n      case std::char_traits<char_type>::eof(): {\n        error_message = \"invalid string: missing closing quote\";\n        return token_type::parse_error;\n      }\n\n      // closing quote\n      case '\\\"': {\n        return token_type::value_string;\n      }\n\n      // escapes\n      case '\\\\': {\n        switch (get()) {\n        // quotation mark\n        case '\\\"':\n          add('\\\"');\n          break;\n\n        // reverse solidus\n        case '\\\\':\n          add('\\\\');\n          break;\n\n        // solidus\n        case '/':\n          add('/');\n          break;\n\n        // backspace\n        case 'b':\n          add('\\b');\n          break;\n\n        // form feed\n        case 'f':\n          add('\\f');\n          break;\n\n        // line feed\n        case 'n':\n          add('\\n');\n          break;\n\n        // carriage return\n        case 'r':\n          add('\\r');\n          break;\n\n        // tab\n        case 't':\n          add('\\t');\n          break;\n\n        // unicode escapes\n        case 'u': {\n          const int codepoint1 = get_codepoint();\n          int codepoint = codepoint1; // start with codepoint1\n\n          if (JSON_HEDLEY_UNLIKELY(codepoint1 == -1)) {\n            error_message = \"invalid string: '\\\\u' must be followed by 4 hex digits\";\n            return token_type::parse_error;\n          }\n\n          // check if code point is a high surrogate\n          if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF) {\n            // expect next \\uxxxx entry\n            if (JSON_HEDLEY_LIKELY(get() == '\\\\' && get() == 'u')) {\n              const int codepoint2 = get_codepoint();\n\n              if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1)) {\n                error_message = \"invalid string: '\\\\u' must be followed by 4 hex digits\";\n                return token_type::parse_error;\n              }\n\n              // check if codepoint2 is a low surrogate\n              if (JSON_HEDLEY_LIKELY(0xDC00 <= codepoint2 && codepoint2 <= 0xDFFF)) {\n                // overwrite codepoint\n                codepoint = static_cast<int>(\n                              // high surrogate occupies the most significant 22 bits\n                              (static_cast<unsigned int>(codepoint1) << 10u)\n                              // low surrogate occupies the least significant 15 bits\n                              + static_cast<unsigned int>(codepoint2)\n                              // there is still the 0xD800, 0xDC00 and 0x10000 noise\n                              // in the result, so we have to subtract with:\n                              // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00\n                              - 0x35FDC00u);\n              } else {\n                error_message =\n                  \"invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF\";\n                return token_type::parse_error;\n              }\n            } else {\n              error_message =\n                \"invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF\";\n              return token_type::parse_error;\n            }\n          } else {\n            if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF)) {\n              error_message =\n                \"invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF\";\n              return token_type::parse_error;\n            }\n          }\n\n          // result of the above calculation yields a proper codepoint\n          JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF);\n\n          // translate codepoint into bytes\n          if (codepoint < 0x80) {\n            // 1-byte characters: 0xxxxxxx (ASCII)\n            add(static_cast<char_int_type>(codepoint));\n          } else if (codepoint <= 0x7FF) {\n            // 2-byte characters: 110xxxxx 10xxxxxx\n            add(static_cast<char_int_type>(0xC0u | (static_cast<unsigned int>\n                                                    (codepoint) >> 6u)));\n            add(static_cast<char_int_type>(0x80u | (static_cast<unsigned int>\n                                                    (codepoint) & 0x3Fu)));\n          } else if (codepoint <= 0xFFFF) {\n            // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx\n            add(static_cast<char_int_type>(0xE0u | (static_cast<unsigned int>\n                                                    (codepoint) >> 12u)));\n            add(static_cast<char_int_type>(0x80u | ((static_cast<unsigned int>\n                                                    (codepoint) >> 6u) & 0x3Fu)));\n            add(static_cast<char_int_type>(0x80u | (static_cast<unsigned int>\n                                                    (codepoint) & 0x3Fu)));\n          } else {\n            // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx\n            add(static_cast<char_int_type>(0xF0u | (static_cast<unsigned int>\n                                                    (codepoint) >> 18u)));\n            add(static_cast<char_int_type>(0x80u | ((static_cast<unsigned int>\n                                                    (codepoint) >> 12u) & 0x3Fu)));\n            add(static_cast<char_int_type>(0x80u | ((static_cast<unsigned int>\n                                                    (codepoint) >> 6u) & 0x3Fu)));\n            add(static_cast<char_int_type>(0x80u | (static_cast<unsigned int>\n                                                    (codepoint) & 0x3Fu)));\n          }\n\n          break;\n        }\n\n        // other characters after escape\n        default:\n          error_message = \"invalid string: forbidden character after backslash\";\n          return token_type::parse_error;\n        }\n\n        break;\n      }\n\n      // invalid control characters\n      case 0x00: {\n        error_message =\n          \"invalid string: control character U+0000 (NUL) must be escaped to \\\\u0000\";\n        return token_type::parse_error;\n      }\n\n      case 0x01: {\n        error_message =\n          \"invalid string: control character U+0001 (SOH) must be escaped to \\\\u0001\";\n        return token_type::parse_error;\n      }\n\n      case 0x02: {\n        error_message =\n          \"invalid string: control character U+0002 (STX) must be escaped to \\\\u0002\";\n        return token_type::parse_error;\n      }\n\n      case 0x03: {\n        error_message =\n          \"invalid string: control character U+0003 (ETX) must be escaped to \\\\u0003\";\n        return token_type::parse_error;\n      }\n\n      case 0x04: {\n        error_message =\n          \"invalid string: control character U+0004 (EOT) must be escaped to \\\\u0004\";\n        return token_type::parse_error;\n      }\n\n      case 0x05: {\n        error_message =\n          \"invalid string: control character U+0005 (ENQ) must be escaped to \\\\u0005\";\n        return token_type::parse_error;\n      }\n\n      case 0x06: {\n        error_message =\n          \"invalid string: control character U+0006 (ACK) must be escaped to \\\\u0006\";\n        return token_type::parse_error;\n      }\n\n      case 0x07: {\n        error_message =\n          \"invalid string: control character U+0007 (BEL) must be escaped to \\\\u0007\";\n        return token_type::parse_error;\n      }\n\n      case 0x08: {\n        error_message =\n          \"invalid string: control character U+0008 (BS) must be escaped to \\\\u0008 or \\\\b\";\n        return token_type::parse_error;\n      }\n\n      case 0x09: {\n        error_message =\n          \"invalid string: control character U+0009 (HT) must be escaped to \\\\u0009 or \\\\t\";\n        return token_type::parse_error;\n      }\n\n      case 0x0A: {\n        error_message =\n          \"invalid string: control character U+000A (LF) must be escaped to \\\\u000A or \\\\n\";\n        return token_type::parse_error;\n      }\n\n      case 0x0B: {\n        error_message =\n          \"invalid string: control character U+000B (VT) must be escaped to \\\\u000B\";\n        return token_type::parse_error;\n      }\n\n      case 0x0C: {\n        error_message =\n          \"invalid string: control character U+000C (FF) must be escaped to \\\\u000C or \\\\f\";\n        return token_type::parse_error;\n      }\n\n      case 0x0D: {\n        error_message =\n          \"invalid string: control character U+000D (CR) must be escaped to \\\\u000D or \\\\r\";\n        return token_type::parse_error;\n      }\n\n      case 0x0E: {\n        error_message =\n          \"invalid string: control character U+000E (SO) must be escaped to \\\\u000E\";\n        return token_type::parse_error;\n      }\n\n      case 0x0F: {\n        error_message =\n          \"invalid string: control character U+000F (SI) must be escaped to \\\\u000F\";\n        return token_type::parse_error;\n      }\n\n      case 0x10: {\n        error_message =\n          \"invalid string: control character U+0010 (DLE) must be escaped to \\\\u0010\";\n        return token_type::parse_error;\n      }\n\n      case 0x11: {\n        error_message =\n          \"invalid string: control character U+0011 (DC1) must be escaped to \\\\u0011\";\n        return token_type::parse_error;\n      }\n\n      case 0x12: {\n        error_message =\n          \"invalid string: control character U+0012 (DC2) must be escaped to \\\\u0012\";\n        return token_type::parse_error;\n      }\n\n      case 0x13: {\n        error_message =\n          \"invalid string: control character U+0013 (DC3) must be escaped to \\\\u0013\";\n        return token_type::parse_error;\n      }\n\n      case 0x14: {\n        error_message =\n          \"invalid string: control character U+0014 (DC4) must be escaped to \\\\u0014\";\n        return token_type::parse_error;\n      }\n\n      case 0x15: {\n        error_message =\n          \"invalid string: control character U+0015 (NAK) must be escaped to \\\\u0015\";\n        return token_type::parse_error;\n      }\n\n      case 0x16: {\n        error_message =\n          \"invalid string: control character U+0016 (SYN) must be escaped to \\\\u0016\";\n        return token_type::parse_error;\n      }\n\n      case 0x17: {\n        error_message =\n          \"invalid string: control character U+0017 (ETB) must be escaped to \\\\u0017\";\n        return token_type::parse_error;\n      }\n\n      case 0x18: {\n        error_message =\n          \"invalid string: control character U+0018 (CAN) must be escaped to \\\\u0018\";\n        return token_type::parse_error;\n      }\n\n      case 0x19: {\n        error_message =\n          \"invalid string: control character U+0019 (EM) must be escaped to \\\\u0019\";\n        return token_type::parse_error;\n      }\n\n      case 0x1A: {\n        error_message =\n          \"invalid string: control character U+001A (SUB) must be escaped to \\\\u001A\";\n        return token_type::parse_error;\n      }\n\n      case 0x1B: {\n        error_message =\n          \"invalid string: control character U+001B (ESC) must be escaped to \\\\u001B\";\n        return token_type::parse_error;\n      }\n\n      case 0x1C: {\n        error_message =\n          \"invalid string: control character U+001C (FS) must be escaped to \\\\u001C\";\n        return token_type::parse_error;\n      }\n\n      case 0x1D: {\n        error_message =\n          \"invalid string: control character U+001D (GS) must be escaped to \\\\u001D\";\n        return token_type::parse_error;\n      }\n\n      case 0x1E: {\n        error_message =\n          \"invalid string: control character U+001E (RS) must be escaped to \\\\u001E\";\n        return token_type::parse_error;\n      }\n\n      case 0x1F: {\n        error_message =\n          \"invalid string: control character U+001F (US) must be escaped to \\\\u001F\";\n        return token_type::parse_error;\n      }\n\n      // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace))\n      case 0x20:\n      case 0x21:\n      case 0x23:\n      case 0x24:\n      case 0x25:\n      case 0x26:\n      case 0x27:\n      case 0x28:\n      case 0x29:\n      case 0x2A:\n      case 0x2B:\n      case 0x2C:\n      case 0x2D:\n      case 0x2E:\n      case 0x2F:\n      case 0x30:\n      case 0x31:\n      case 0x32:\n      case 0x33:\n      case 0x34:\n      case 0x35:\n      case 0x36:\n      case 0x37:\n      case 0x38:\n      case 0x39:\n      case 0x3A:\n      case 0x3B:\n      case 0x3C:\n      case 0x3D:\n      case 0x3E:\n      case 0x3F:\n      case 0x40:\n      case 0x41:\n      case 0x42:\n      case 0x43:\n      case 0x44:\n      case 0x45:\n      case 0x46:\n      case 0x47:\n      case 0x48:\n      case 0x49:\n      case 0x4A:\n      case 0x4B:\n      case 0x4C:\n      case 0x4D:\n      case 0x4E:\n      case 0x4F:\n      case 0x50:\n      case 0x51:\n      case 0x52:\n      case 0x53:\n      case 0x54:\n      case 0x55:\n      case 0x56:\n      case 0x57:\n      case 0x58:\n      case 0x59:\n      case 0x5A:\n      case 0x5B:\n      case 0x5D:\n      case 0x5E:\n      case 0x5F:\n      case 0x60:\n      case 0x61:\n      case 0x62:\n      case 0x63:\n      case 0x64:\n      case 0x65:\n      case 0x66:\n      case 0x67:\n      case 0x68:\n      case 0x69:\n      case 0x6A:\n      case 0x6B:\n      case 0x6C:\n      case 0x6D:\n      case 0x6E:\n      case 0x6F:\n      case 0x70:\n      case 0x71:\n      case 0x72:\n      case 0x73:\n      case 0x74:\n      case 0x75:\n      case 0x76:\n      case 0x77:\n      case 0x78:\n      case 0x79:\n      case 0x7A:\n      case 0x7B:\n      case 0x7C:\n      case 0x7D:\n      case 0x7E:\n      case 0x7F: {\n        add(current);\n        break;\n      }\n\n      // U+0080..U+07FF: bytes C2..DF 80..BF\n      case 0xC2:\n      case 0xC3:\n      case 0xC4:\n      case 0xC5:\n      case 0xC6:\n      case 0xC7:\n      case 0xC8:\n      case 0xC9:\n      case 0xCA:\n      case 0xCB:\n      case 0xCC:\n      case 0xCD:\n      case 0xCE:\n      case 0xCF:\n      case 0xD0:\n      case 0xD1:\n      case 0xD2:\n      case 0xD3:\n      case 0xD4:\n      case 0xD5:\n      case 0xD6:\n      case 0xD7:\n      case 0xD8:\n      case 0xD9:\n      case 0xDA:\n      case 0xDB:\n      case 0xDC:\n      case 0xDD:\n      case 0xDE:\n      case 0xDF: {\n        if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF}))) {\n          return token_type::parse_error;\n        }\n        break;\n      }\n\n      // U+0800..U+0FFF: bytes E0 A0..BF 80..BF\n      case 0xE0: {\n        if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) {\n          return token_type::parse_error;\n        }\n        break;\n      }\n\n      // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF\n      // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF\n      case 0xE1:\n      case 0xE2:\n      case 0xE3:\n      case 0xE4:\n      case 0xE5:\n      case 0xE6:\n      case 0xE7:\n      case 0xE8:\n      case 0xE9:\n      case 0xEA:\n      case 0xEB:\n      case 0xEC:\n      case 0xEE:\n      case 0xEF: {\n        if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) {\n          return token_type::parse_error;\n        }\n        break;\n      }\n\n      // U+D000..U+D7FF: bytes ED 80..9F 80..BF\n      case 0xED: {\n        if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) {\n          return token_type::parse_error;\n        }\n        break;\n      }\n\n      // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF\n      case 0xF0: {\n        if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) {\n          return token_type::parse_error;\n        }\n        break;\n      }\n\n      // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF\n      case 0xF1:\n      case 0xF2:\n      case 0xF3: {\n        if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) {\n          return token_type::parse_error;\n        }\n        break;\n      }\n\n      // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF\n      case 0xF4: {\n        if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) {\n          return token_type::parse_error;\n        }\n        break;\n      }\n\n      // remaining bytes (80..C1 and F5..FF) are ill-formed\n      default: {\n        error_message = \"invalid string: ill-formed UTF-8 byte\";\n        return token_type::parse_error;\n      }\n      }\n    }\n  }\n\n  /*!\n   * @brief scan a comment\n   * @return whether comment could be scanned successfully\n   */\n  bool scan_comment()\n  {\n    switch (get()) {\n    // single-line comments skip input until a newline or EOF is read\n    case '/': {\n      while (true) {\n        switch (get()) {\n        case '\\n':\n        case '\\r':\n        case std::char_traits<char_type>::eof():\n        case '\\0':\n          return true;\n\n        default:\n          break;\n        }\n      }\n    }\n\n    // multi-line comments skip input until */ is read\n    case '*': {\n      while (true) {\n        switch (get()) {\n        case std::char_traits<char_type>::eof():\n        case '\\0': {\n          error_message = \"invalid comment; missing closing '*/'\";\n          return false;\n        }\n\n        case '*': {\n          switch (get()) {\n          case '/':\n            return true;\n\n          default: {\n            unget();\n            continue;\n          }\n          }\n        }\n\n        default:\n          continue;\n        }\n      }\n    }\n\n    // unexpected character after reading '/'\n    default: {\n      error_message = \"invalid comment; expecting '/' or '*' after '/'\";\n      return false;\n    }\n    }\n  }\n\n  JSON_HEDLEY_NON_NULL(2)\n  static void strtof(float& f, const char* str, char** endptr) noexcept\n  {\n    f = std::strtof(str, endptr);\n  }\n\n  JSON_HEDLEY_NON_NULL(2)\n  static void strtof(double& f, const char* str, char** endptr) noexcept\n  {\n    f = std::strtod(str, endptr);\n  }\n\n  JSON_HEDLEY_NON_NULL(2)\n  static void strtof(long double& f, const char* str, char** endptr) noexcept\n  {\n    f = std::strtold(str, endptr);\n  }\n\n  /*!\n  @brief scan a number literal\n\n  This function scans a string according to Sect. 6 of RFC 8259.\n\n  The function is realized with a deterministic finite state machine derived\n  from the grammar described in RFC 8259. Starting in state \"init\", the\n  input is read and used to determined the next state. Only state \"done\"\n  accepts the number. State \"error\" is a trap state to model errors. In the\n  table below, \"anything\" means any character but the ones listed before.\n\n  state    | 0        | 1-9      | e E      | +       | -       | .        | anything\n  ---------|----------|----------|----------|---------|---------|----------|-----------\n  init     | zero     | any1     | [error]  | [error] | minus   | [error]  | [error]\n  minus    | zero     | any1     | [error]  | [error] | [error] | [error]  | [error]\n  zero     | done     | done     | exponent | done    | done    | decimal1 | done\n  any1     | any1     | any1     | exponent | done    | done    | decimal1 | done\n  decimal1 | decimal2 | decimal2 | [error]  | [error] | [error] | [error]  | [error]\n  decimal2 | decimal2 | decimal2 | exponent | done    | done    | done     | done\n  exponent | any2     | any2     | [error]  | sign    | sign    | [error]  | [error]\n  sign     | any2     | any2     | [error]  | [error] | [error] | [error]  | [error]\n  any2     | any2     | any2     | done     | done    | done    | done     | done\n\n  The state machine is realized with one label per state (prefixed with\n  \"scan_number_\") and `goto` statements between them. The state machine\n  contains cycles, but any cycle can be left when EOF is read. Therefore,\n  the function is guaranteed to terminate.\n\n  During scanning, the read bytes are stored in token_buffer. This string is\n  then converted to a signed integer, an unsigned integer, or a\n  floating-point number.\n\n  @return token_type::value_unsigned, token_type::value_integer, or\n          token_type::value_float if number could be successfully scanned,\n          token_type::parse_error otherwise\n\n  @note The scanner is independent of the current locale. Internally, the\n        locale's decimal point is used instead of `.` to work with the\n        locale-dependent converters.\n  */\n  token_type scan_number()  // lgtm [cpp/use-of-goto]\n  {\n    // reset token_buffer to store the number's bytes\n    reset();\n    // the type of the parsed number; initially set to unsigned; will be\n    // changed if minus sign, decimal point or exponent is read\n    token_type number_type = token_type::value_unsigned;\n\n    // state (init): we just found out we need to scan a number\n    switch (current) {\n    case '-': {\n      add(current);\n      goto scan_number_minus;\n    }\n\n    case '0': {\n      add(current);\n      goto scan_number_zero;\n    }\n\n    case '1':\n    case '2':\n    case '3':\n    case '4':\n    case '5':\n    case '6':\n    case '7':\n    case '8':\n    case '9': {\n      add(current);\n      goto scan_number_any1;\n    }\n\n    // all other characters are rejected outside scan_number()\n    default:            // LCOV_EXCL_LINE\n      JSON_ASSERT(\n        false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n    }\n\nscan_number_minus:\n    // state: we just parsed a leading minus sign\n    number_type = token_type::value_integer;\n\n    switch (get()) {\n    case '0': {\n      add(current);\n      goto scan_number_zero;\n    }\n\n    case '1':\n    case '2':\n    case '3':\n    case '4':\n    case '5':\n    case '6':\n    case '7':\n    case '8':\n    case '9': {\n      add(current);\n      goto scan_number_any1;\n    }\n\n    default: {\n      error_message = \"invalid number; expected digit after '-'\";\n      return token_type::parse_error;\n    }\n    }\n\nscan_number_zero:\n\n    // state: we just parse a zero (maybe with a leading minus sign)\n    switch (get()) {\n    case '.': {\n      add(decimal_point_char);\n      goto scan_number_decimal1;\n    }\n\n    case 'e':\n    case 'E': {\n      add(current);\n      goto scan_number_exponent;\n    }\n\n    default:\n      goto scan_number_done;\n    }\n\nscan_number_any1:\n\n    // state: we just parsed a number 0-9 (maybe with a leading minus sign)\n    switch (get()) {\n    case '0':\n    case '1':\n    case '2':\n    case '3':\n    case '4':\n    case '5':\n    case '6':\n    case '7':\n    case '8':\n    case '9': {\n      add(current);\n      goto scan_number_any1;\n    }\n\n    case '.': {\n      add(decimal_point_char);\n      goto scan_number_decimal1;\n    }\n\n    case 'e':\n    case 'E': {\n      add(current);\n      goto scan_number_exponent;\n    }\n\n    default:\n      goto scan_number_done;\n    }\n\nscan_number_decimal1:\n    // state: we just parsed a decimal point\n    number_type = token_type::value_float;\n\n    switch (get()) {\n    case '0':\n    case '1':\n    case '2':\n    case '3':\n    case '4':\n    case '5':\n    case '6':\n    case '7':\n    case '8':\n    case '9': {\n      add(current);\n      goto scan_number_decimal2;\n    }\n\n    default: {\n      error_message = \"invalid number; expected digit after '.'\";\n      return token_type::parse_error;\n    }\n    }\n\nscan_number_decimal2:\n\n    // we just parsed at least one number after a decimal point\n    switch (get()) {\n    case '0':\n    case '1':\n    case '2':\n    case '3':\n    case '4':\n    case '5':\n    case '6':\n    case '7':\n    case '8':\n    case '9': {\n      add(current);\n      goto scan_number_decimal2;\n    }\n\n    case 'e':\n    case 'E': {\n      add(current);\n      goto scan_number_exponent;\n    }\n\n    default:\n      goto scan_number_done;\n    }\n\nscan_number_exponent:\n    // we just parsed an exponent\n    number_type = token_type::value_float;\n\n    switch (get()) {\n    case '+':\n    case '-': {\n      add(current);\n      goto scan_number_sign;\n    }\n\n    case '0':\n    case '1':\n    case '2':\n    case '3':\n    case '4':\n    case '5':\n    case '6':\n    case '7':\n    case '8':\n    case '9': {\n      add(current);\n      goto scan_number_any2;\n    }\n\n    default: {\n      error_message =\n        \"invalid number; expected '+', '-', or digit after exponent\";\n      return token_type::parse_error;\n    }\n    }\n\nscan_number_sign:\n\n    // we just parsed an exponent sign\n    switch (get()) {\n    case '0':\n    case '1':\n    case '2':\n    case '3':\n    case '4':\n    case '5':\n    case '6':\n    case '7':\n    case '8':\n    case '9': {\n      add(current);\n      goto scan_number_any2;\n    }\n\n    default: {\n      error_message = \"invalid number; expected digit after exponent sign\";\n      return token_type::parse_error;\n    }\n    }\n\nscan_number_any2:\n\n    // we just parsed a number after the exponent or exponent sign\n    switch (get()) {\n    case '0':\n    case '1':\n    case '2':\n    case '3':\n    case '4':\n    case '5':\n    case '6':\n    case '7':\n    case '8':\n    case '9': {\n      add(current);\n      goto scan_number_any2;\n    }\n\n    default:\n      goto scan_number_done;\n    }\n\nscan_number_done:\n    // unget the character after the number (we only read it to know that\n    // we are done scanning a number)\n    unget();\n    char* endptr =\n      nullptr; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n    errno = 0;\n\n    // try to parse integers first and fall back to floats\n    if (number_type == token_type::value_unsigned) {\n      const auto x = std::strtoull(token_buffer.data(), &endptr, 10);\n      // we checked the number format before\n      JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());\n\n      if (errno == 0) {\n        value_unsigned = static_cast<number_unsigned_t>(x);\n\n        if (value_unsigned == x) {\n          return token_type::value_unsigned;\n        }\n      }\n    } else if (number_type == token_type::value_integer) {\n      const auto x = std::strtoll(token_buffer.data(), &endptr, 10);\n      // we checked the number format before\n      JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());\n\n      if (errno == 0) {\n        value_integer = static_cast<number_integer_t>(x);\n\n        if (value_integer == x) {\n          return token_type::value_integer;\n        }\n      }\n    }\n\n    // this code is reached if we parse a floating-point number or if an\n    // integer conversion above failed\n    strtof(value_float, token_buffer.data(), &endptr);\n    // we checked the number format before\n    JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());\n    return token_type::value_float;\n  }\n\n  /*!\n  @param[in] literal_text  the literal text to expect\n  @param[in] length        the length of the passed literal text\n  @param[in] return_type   the token type to return on success\n  */\n  JSON_HEDLEY_NON_NULL(2)\n  token_type scan_literal(const char_type* literal_text, const std::size_t length,\n                          token_type return_type)\n  {\n    JSON_ASSERT(std::char_traits<char_type>::to_char_type(current) ==\n                literal_text[0]);\n\n    for (std::size_t i = 1; i < length; ++i) {\n      if (JSON_HEDLEY_UNLIKELY(std::char_traits<char_type>::to_char_type(\n                                 get()) != literal_text[i])) {\n        error_message = \"invalid literal\";\n        return token_type::parse_error;\n      }\n    }\n\n    return return_type;\n  }\n\n  /////////////////////\n  // input management\n  /////////////////////\n\n  /// reset token_buffer; current character is beginning of token\n  void reset() noexcept\n  {\n    token_buffer.clear();\n    token_string.clear();\n    token_string.push_back(std::char_traits<char_type>::to_char_type(current));\n  }\n\n  /*\n  @brief get next character from the input\n\n  This function provides the interface to the used input adapter. It does\n  not throw in case the input reached EOF, but returns a\n  `std::char_traits<char>::eof()` in that case.  Stores the scanned characters\n  for use in error messages.\n\n  @return character read from the input\n  */\n  char_int_type get()\n  {\n    ++position.chars_read_total;\n    ++position.chars_read_current_line;\n\n    if (next_unget) {\n      // just reset the next_unget variable and work with current\n      next_unget = false;\n    } else {\n      current = ia.get_character();\n    }\n\n    if (JSON_HEDLEY_LIKELY(current != std::char_traits<char_type>::eof())) {\n      token_string.push_back(std::char_traits<char_type>::to_char_type(current));\n    }\n\n    if (current == '\\n') {\n      ++position.lines_read;\n      position.chars_read_current_line = 0;\n    }\n\n    return current;\n  }\n\n  /*!\n  @brief unget current character (read it again on next get)\n\n  We implement unget by setting variable next_unget to true. The input is not\n  changed - we just simulate ungetting by modifying chars_read_total,\n  chars_read_current_line, and token_string. The next call to get() will\n  behave as if the unget character is read again.\n  */\n  void unget()\n  {\n    next_unget = true;\n    --position.chars_read_total;\n\n    // in case we \"unget\" a newline, we have to also decrement the lines_read\n    if (position.chars_read_current_line == 0) {\n      if (position.lines_read > 0) {\n        --position.lines_read;\n      }\n    } else {\n      --position.chars_read_current_line;\n    }\n\n    if (JSON_HEDLEY_LIKELY(current != std::char_traits<char_type>::eof())) {\n      JSON_ASSERT(!token_string.empty());\n      token_string.pop_back();\n    }\n  }\n\n  /// add a character to token_buffer\n  void add(char_int_type c)\n  {\n    token_buffer.push_back(static_cast<typename string_t::value_type>(c));\n  }\n\npublic:\n  /////////////////////\n  // value getters\n  /////////////////////\n\n  /// return integer value\n  constexpr number_integer_t get_number_integer() const noexcept\n  {\n    return value_integer;\n  }\n\n  /// return unsigned integer value\n  constexpr number_unsigned_t get_number_unsigned() const noexcept\n  {\n    return value_unsigned;\n  }\n\n  /// return floating-point value\n  constexpr number_float_t get_number_float() const noexcept\n  {\n    return value_float;\n  }\n\n  /// return current string value (implicitly resets the token; useful only once)\n  string_t& get_string()\n  {\n    return token_buffer;\n  }\n\n  /////////////////////\n  // diagnostics\n  /////////////////////\n\n  /// return position of last read token\n  constexpr position_t get_position() const noexcept\n  {\n    return position;\n  }\n\n  /// return the last read token (for errors only).  Will never contain EOF\n  /// (an arbitrary value that is not a valid char value, often -1), because\n  /// 255 may legitimately occur.  May contain NUL, which should be escaped.\n  std::string get_token_string() const\n  {\n    // escape control characters\n    std::string result;\n\n    for (const auto c : token_string) {\n      if (static_cast<unsigned char>(c) <= '\\x1F') {\n        // escape control characters\n        std::array<char, 9> cs{{}};\n        static_cast<void>((std::snprintf)(cs.data(), cs.size(), \"<U+%.4X>\",\n                                          static_cast<unsigned char>\n                                          (c))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n        result += cs.data();\n      } else {\n        // add character as is\n        result.push_back(static_cast<std::string::value_type>(c));\n      }\n    }\n\n    return result;\n  }\n\n  /// return syntax error message\n  JSON_HEDLEY_RETURNS_NON_NULL\n  constexpr const char* get_error_message() const noexcept\n  {\n    return error_message;\n  }\n\n  /////////////////////\n  // actual scanner\n  /////////////////////\n\n  /*!\n  @brief skip the UTF-8 byte order mark\n  @return true iff there is no BOM or the correct BOM has been skipped\n  */\n  bool skip_bom()\n  {\n    if (get() == 0xEF) {\n      // check if we completely parse the BOM\n      return get() == 0xBB && get() == 0xBF;\n    }\n\n    // the first character is not the beginning of the BOM; unget it to\n    // process is later\n    unget();\n    return true;\n  }\n\n  void skip_whitespace()\n  {\n    do {\n      get();\n    } while (current == ' ' || current == '\\t' || current == '\\n' ||\n             current == '\\r');\n  }\n\n  token_type scan()\n  {\n    // initially, skip the BOM\n    if (position.chars_read_total == 0 && !skip_bom()) {\n      error_message = \"invalid BOM; must be 0xEF 0xBB 0xBF if given\";\n      return token_type::parse_error;\n    }\n\n    // read next character and ignore whitespace\n    skip_whitespace();\n\n    // ignore comments\n    while (ignore_comments && current == '/') {\n      if (!scan_comment()) {\n        return token_type::parse_error;\n      }\n\n      // skip following whitespace\n      skip_whitespace();\n    }\n\n    switch (current) {\n    // structural characters\n    case '[':\n      return token_type::begin_array;\n\n    case ']':\n      return token_type::end_array;\n\n    case '{':\n      return token_type::begin_object;\n\n    case '}':\n      return token_type::end_object;\n\n    case ':':\n      return token_type::name_separator;\n\n    case ',':\n      return token_type::value_separator;\n\n    // literals\n    case 't': {\n      std::array<char_type, 4> true_literal = {{static_cast<char_type>('t'), static_cast<char_type>('r'), static_cast<char_type>('u'), static_cast<char_type>('e')}};\n      return scan_literal(true_literal.data(), true_literal.size(),\n                          token_type::literal_true);\n    }\n\n    case 'f': {\n      std::array<char_type, 5> false_literal = {{static_cast<char_type>('f'), static_cast<char_type>('a'), static_cast<char_type>('l'), static_cast<char_type>('s'), static_cast<char_type>('e')}};\n      return scan_literal(false_literal.data(), false_literal.size(),\n                          token_type::literal_false);\n    }\n\n    case 'n': {\n      std::array<char_type, 4> null_literal = {{static_cast<char_type>('n'), static_cast<char_type>('u'), static_cast<char_type>('l'), static_cast<char_type>('l')}};\n      return scan_literal(null_literal.data(), null_literal.size(),\n                          token_type::literal_null);\n    }\n\n    // string\n    case '\\\"':\n      return scan_string();\n\n    // number\n    case '-':\n    case '0':\n    case '1':\n    case '2':\n    case '3':\n    case '4':\n    case '5':\n    case '6':\n    case '7':\n    case '8':\n    case '9':\n      return scan_number();\n\n    // end of input (the null byte is needed when parsing from\n    // string literals)\n    case '\\0':\n    case std::char_traits<char_type>::eof():\n      return token_type::end_of_input;\n\n    // error\n    default:\n      error_message = \"invalid literal\";\n      return token_type::parse_error;\n    }\n  }\n\nprivate:\n  /// input adapter\n  InputAdapterType ia;\n\n  /// whether comments should be ignored (true) or signaled as errors (false)\n  const bool ignore_comments = false;\n\n  /// the current character\n  char_int_type current = std::char_traits<char_type>::eof();\n\n  /// whether the next get() call should just return current\n  bool next_unget = false;\n\n  /// the start position of the current token\n  position_t position {};\n\n  /// raw input token string (for error messages)\n  std::vector<char_type> token_string {};\n\n  /// buffer for variable-length tokens (numbers, strings)\n  string_t token_buffer {};\n\n  /// a description of occurred lexer errors\n  const char* error_message = \"\";\n\n  // number values\n  number_integer_t value_integer = 0;\n  number_unsigned_t value_unsigned = 0;\n  number_float_t value_float = 0;\n\n  /// the decimal point\n  const char_int_type decimal_point_char = '.';\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/is_sax.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstdint> // size_t\n#include <utility> // declval\n#include <string> // string\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/meta/detected.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename T>\nusing null_function_t = decltype(std::declval<T&>().null());\n\ntemplate<typename T>\nusing boolean_function_t =\n  decltype(std::declval<T&>().boolean(std::declval<bool>()));\n\ntemplate<typename T, typename Integer>\nusing number_integer_function_t =\n  decltype(std::declval<T&>().number_integer(std::declval<Integer>()));\n\ntemplate<typename T, typename Unsigned>\nusing number_unsigned_function_t =\n  decltype(std::declval<T&>().number_unsigned(std::declval<Unsigned>()));\n\ntemplate<typename T, typename Float, typename String>\nusing number_float_function_t = decltype(std::declval<T&>().number_float(\n                                  std::declval<Float>(), std::declval<const String&>()));\n\ntemplate<typename T, typename String>\nusing string_function_t =\n  decltype(std::declval<T&>().string(std::declval<String&>()));\n\ntemplate<typename T, typename Binary>\nusing binary_function_t =\n  decltype(std::declval<T&>().binary(std::declval<Binary&>()));\n\ntemplate<typename T>\nusing start_object_function_t =\n  decltype(std::declval<T&>().start_object(std::declval<std::size_t>()));\n\ntemplate<typename T, typename String>\nusing key_function_t =\n  decltype(std::declval<T&>().key(std::declval<String&>()));\n\ntemplate<typename T>\nusing end_object_function_t = decltype(std::declval<T&>().end_object());\n\ntemplate<typename T>\nusing start_array_function_t =\n  decltype(std::declval<T&>().start_array(std::declval<std::size_t>()));\n\ntemplate<typename T>\nusing end_array_function_t = decltype(std::declval<T&>().end_array());\n\ntemplate<typename T, typename Exception>\nusing parse_error_function_t = decltype(std::declval<T&>().parse_error(\n    std::declval<std::size_t>(), std::declval<const std::string&>(),\n    std::declval<const Exception&>()));\n\ntemplate<typename SAX, typename BasicJsonType>\nstruct is_sax {\nprivate:\n  static_assert(is_basic_json<BasicJsonType>::value,\n                \"BasicJsonType must be of type basic_json<...>\");\n\n  using number_integer_t = typename BasicJsonType::number_integer_t;\n  using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n  using number_float_t = typename BasicJsonType::number_float_t;\n  using string_t = typename BasicJsonType::string_t;\n  using binary_t = typename BasicJsonType::binary_t;\n  using exception_t = typename BasicJsonType::exception;\n\npublic:\n  static constexpr bool value =\n    is_detected_exact<bool, null_function_t, SAX>::value &&\n    is_detected_exact<bool, boolean_function_t, SAX>::value &&\n    is_detected_exact<bool, number_integer_function_t, SAX, number_integer_t>::value &&\n    is_detected_exact<bool, number_unsigned_function_t, SAX, number_unsigned_t>::value &&\n    is_detected_exact<bool, number_float_function_t, SAX, number_float_t, string_t>::value &&\n    is_detected_exact<bool, string_function_t, SAX, string_t>::value &&\n    is_detected_exact<bool, binary_function_t, SAX, binary_t>::value &&\n    is_detected_exact<bool, start_object_function_t, SAX>::value &&\n    is_detected_exact<bool, key_function_t, SAX, string_t>::value &&\n    is_detected_exact<bool, end_object_function_t, SAX>::value &&\n    is_detected_exact<bool, start_array_function_t, SAX>::value &&\n    is_detected_exact<bool, end_array_function_t, SAX>::value &&\n    is_detected_exact<bool, parse_error_function_t, SAX, exception_t>::value;\n};\n\ntemplate<typename SAX, typename BasicJsonType>\nstruct is_sax_static_asserts {\nprivate:\n  static_assert(is_basic_json<BasicJsonType>::value,\n                \"BasicJsonType must be of type basic_json<...>\");\n\n  using number_integer_t = typename BasicJsonType::number_integer_t;\n  using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n  using number_float_t = typename BasicJsonType::number_float_t;\n  using string_t = typename BasicJsonType::string_t;\n  using binary_t = typename BasicJsonType::binary_t;\n  using exception_t = typename BasicJsonType::exception;\n\npublic:\n  static_assert(is_detected_exact<bool, null_function_t, SAX>::value,\n                \"Missing/invalid function: bool null()\");\n  static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value,\n                \"Missing/invalid function: bool boolean(bool)\");\n  static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value,\n                \"Missing/invalid function: bool boolean(bool)\");\n  static_assert(\n    is_detected_exact<bool, number_integer_function_t, SAX,\n    number_integer_t>::value,\n    \"Missing/invalid function: bool number_integer(number_integer_t)\");\n  static_assert(\n    is_detected_exact<bool, number_unsigned_function_t, SAX,\n    number_unsigned_t>::value,\n    \"Missing/invalid function: bool number_unsigned(number_unsigned_t)\");\n  static_assert(is_detected_exact<bool, number_float_function_t, SAX,\n                number_float_t, string_t>::value,\n                \"Missing/invalid function: bool number_float(number_float_t, const string_t&)\");\n  static_assert(\n    is_detected_exact<bool, string_function_t, SAX, string_t>::value,\n    \"Missing/invalid function: bool string(string_t&)\");\n  static_assert(\n    is_detected_exact<bool, binary_function_t, SAX, binary_t>::value,\n    \"Missing/invalid function: bool binary(binary_t&)\");\n  static_assert(is_detected_exact<bool, start_object_function_t, SAX>::value,\n                \"Missing/invalid function: bool start_object(std::size_t)\");\n  static_assert(is_detected_exact<bool, key_function_t, SAX, string_t>::value,\n                \"Missing/invalid function: bool key(string_t&)\");\n  static_assert(is_detected_exact<bool, end_object_function_t, SAX>::value,\n                \"Missing/invalid function: bool end_object()\");\n  static_assert(is_detected_exact<bool, start_array_function_t, SAX>::value,\n                \"Missing/invalid function: bool start_array(std::size_t)\");\n  static_assert(is_detected_exact<bool, end_array_function_t, SAX>::value,\n                \"Missing/invalid function: bool end_array()\");\n  static_assert(\n    is_detected_exact<bool, parse_error_function_t, SAX, exception_t>::value,\n    \"Missing/invalid function: bool parse_error(std::size_t, const \"\n    \"std::string&, const exception&)\");\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/// how to treat CBOR tags\nenum class cbor_tag_handler_t {\n  error,   ///< throw a parse_error exception in case of a tag\n  ignore,  ///< ignore tags\n  store    ///< store tags as binary type\n};\n\n/*!\n@brief determine system byte order\n\n@return true if and only if system's byte order is little endian\n\n@note from https://stackoverflow.com/a/1001328/266378\n*/\nstatic inline bool little_endianness(int num = 1) noexcept\n{\n  return *reinterpret_cast<char*>(&num) == 1;\n}\n\n\n///////////////////\n// binary reader //\n///////////////////\n\n/*!\n@brief deserialization of CBOR, MessagePack, and UBJSON values\n*/\ntemplate<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_parser<BasicJsonType>>\nclass binary_reader\n{\n  using number_integer_t = typename BasicJsonType::number_integer_t;\n  using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n  using number_float_t = typename BasicJsonType::number_float_t;\n  using string_t = typename BasicJsonType::string_t;\n  using binary_t = typename BasicJsonType::binary_t;\n  using json_sax_t = SAX;\n  using char_type = typename InputAdapterType::char_type;\n  using char_int_type = typename std::char_traits<char_type>::int_type;\n\npublic:\n  /*!\n  @brief create a binary reader\n\n  @param[in] adapter  input adapter to read from\n  */\n  explicit binary_reader(InputAdapterType&& adapter,\n                         const input_format_t format = input_format_t::json) noexcept : ia(std::move(\n                                 adapter)), input_format(format)\n  {\n    (void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};\n  }\n\n  // make class move-only\n  binary_reader(const binary_reader&) = delete;\n  binary_reader(binary_reader&&) =\n    default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n  binary_reader& operator=(const binary_reader&) = delete;\n  binary_reader& operator=(binary_reader&&) =\n    default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n  ~binary_reader() = default;\n\n  /*!\n  @param[in] format  the binary format to parse\n  @param[in] sax_    a SAX event processor\n  @param[in] strict  whether to expect the input to be consumed completed\n  @param[in] tag_handler  how to treat CBOR tags\n\n  @return whether parsing was successful\n  */\n  JSON_HEDLEY_NON_NULL(3)\n  bool sax_parse(const input_format_t format,\n                 json_sax_t* sax_,\n                 const bool strict = true,\n                 const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)\n  {\n    sax = sax_;\n    bool result = false;\n\n    switch (format) {\n    case input_format_t::bson:\n      result = parse_bson_internal();\n      break;\n\n    case input_format_t::cbor:\n      result = parse_cbor_internal(true, tag_handler);\n      break;\n\n    case input_format_t::msgpack:\n      result = parse_msgpack_internal();\n      break;\n\n    case input_format_t::ubjson:\n    case input_format_t::bjdata:\n      result = parse_ubjson_internal();\n      break;\n\n    case input_format_t::json: // LCOV_EXCL_LINE\n    default:            // LCOV_EXCL_LINE\n      JSON_ASSERT(\n        false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n    }\n\n    // strict mode: next byte must be EOF\n    if (result && strict) {\n      if (input_format == input_format_t::ubjson ||\n          input_format == input_format_t::bjdata) {\n        get_ignore_noop();\n      } else {\n        get();\n      }\n\n      if (JSON_HEDLEY_UNLIKELY(current != std::char_traits<char_type>::eof())) {\n        return sax->parse_error(chars_read, get_token_string(), parse_error::create(110,\n                                chars_read,\n                                exception_message(input_format, concat(\"expected end of input; last byte: 0x\",\n                                    get_token_string()), \"value\"), nullptr));\n      }\n    }\n\n    return result;\n  }\n\nprivate:\n  //////////\n  // BSON //\n  //////////\n\n  /*!\n  @brief Reads in a BSON-object and passes it to the SAX-parser.\n  @return whether a valid BSON-value was passed to the SAX parser\n  */\n  bool parse_bson_internal()\n  {\n    std::int32_t document_size{};\n    get_number<std::int32_t, true>(input_format_t::bson, document_size);\n\n    if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast<std::size_t>(-1)))) {\n      return false;\n    }\n\n    if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/false))) {\n      return false;\n    }\n\n    return sax->end_object();\n  }\n\n  /*!\n  @brief Parses a C-style string from the BSON input.\n  @param[in,out] result  A reference to the string variable where the read\n                          string is to be stored.\n  @return `true` if the \\x00-byte indicating the end of the string was\n           encountered before the EOF; false` indicates an unexpected EOF.\n  */\n  bool get_bson_cstr(string_t& result)\n  {\n    auto out = std::back_inserter(result);\n\n    while (true) {\n      get();\n\n      if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, \"cstring\"))) {\n        return false;\n      }\n\n      if (current == 0x00) {\n        return true;\n      }\n\n      *out++ = static_cast<typename string_t::value_type>(current);\n    }\n  }\n\n  /*!\n  @brief Parses a zero-terminated string of length @a len from the BSON\n         input.\n  @param[in] len  The length (including the zero-byte at the end) of the\n                  string to be read.\n  @param[in,out] result  A reference to the string variable where the read\n                          string is to be stored.\n  @tparam NumberType The type of the length @a len\n  @pre len >= 1\n  @return `true` if the string was successfully parsed\n  */\n  template<typename NumberType>\n  bool get_bson_string(const NumberType len, string_t& result)\n  {\n    if (JSON_HEDLEY_UNLIKELY(len < 1)) {\n      auto last_token = get_token_string();\n      return sax->parse_error(chars_read, last_token, parse_error::create(112,\n                              chars_read,\n                              exception_message(input_format_t::bson,\n                                  concat(\"string length must be at least 1, is \", std::to_string(len)), \"string\"),\n                              nullptr));\n    }\n\n    return get_string(input_format_t::bson, len - static_cast<NumberType>(1),\n                      result) && get() != std::char_traits<char_type>::eof();\n  }\n\n  /*!\n  @brief Parses a byte array input of length @a len from the BSON input.\n  @param[in] len  The length of the byte array to be read.\n  @param[in,out] result  A reference to the binary variable where the read\n                          array is to be stored.\n  @tparam NumberType The type of the length @a len\n  @pre len >= 0\n  @return `true` if the byte array was successfully parsed\n  */\n  template<typename NumberType>\n  bool get_bson_binary(const NumberType len, binary_t& result)\n  {\n    if (JSON_HEDLEY_UNLIKELY(len < 0)) {\n      auto last_token = get_token_string();\n      return sax->parse_error(chars_read, last_token, parse_error::create(112,\n                              chars_read,\n                              exception_message(input_format_t::bson,\n                                  concat(\"byte array length cannot be negative, is \", std::to_string(len)),\n                                  \"binary\"), nullptr));\n    }\n\n    // All BSON binary values have a subtype\n    std::uint8_t subtype{};\n    get_number<std::uint8_t>(input_format_t::bson, subtype);\n    result.set_subtype(subtype);\n    return get_binary(input_format_t::bson, len, result);\n  }\n\n  /*!\n  @brief Read a BSON document element of the given @a element_type.\n  @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html\n  @param[in] element_type_parse_position The position in the input stream,\n             where the `element_type` was read.\n  @warning Not all BSON element types are supported yet. An unsupported\n           @a element_type will give rise to a parse_error.114:\n           Unsupported BSON record type 0x...\n  @return whether a valid BSON-object/array was passed to the SAX parser\n  */\n  bool parse_bson_element_internal(const char_int_type element_type,\n                                   const std::size_t element_type_parse_position)\n  {\n    switch (element_type) {\n    case 0x01: { // double\n      double number{};\n      return get_number<double, true>(input_format_t::bson, number) &&\n             sax->number_float(static_cast<number_float_t>(number), \"\");\n    }\n\n    case 0x02: { // string\n      std::int32_t len{};\n      string_t value;\n      return get_number<std::int32_t, true>(input_format_t::bson, len) &&\n             get_bson_string(len, value) && sax->string(value);\n    }\n\n    case 0x03: { // object\n      return parse_bson_internal();\n    }\n\n    case 0x04: { // array\n      return parse_bson_array();\n    }\n\n    case 0x05: { // binary\n      std::int32_t len{};\n      binary_t value;\n      return get_number<std::int32_t, true>(input_format_t::bson, len) &&\n             get_bson_binary(len, value) && sax->binary(value);\n    }\n\n    case 0x08: { // boolean\n      return sax->boolean(get() != 0);\n    }\n\n    case 0x0A: { // null\n      return sax->null();\n    }\n\n    case 0x10: { // int32\n      std::int32_t value{};\n      return get_number<std::int32_t, true>(input_format_t::bson, value) &&\n             sax->number_integer(value);\n    }\n\n    case 0x12: { // int64\n      std::int64_t value{};\n      return get_number<std::int64_t, true>(input_format_t::bson, value) &&\n             sax->number_integer(value);\n    }\n\n    default: { // anything else not supported (yet)\n      std::array<char, 3> cr{{}};\n      static_cast<void>((std::snprintf)(cr.data(), cr.size(), \"%.2hhX\",\n                                        static_cast<unsigned char>\n                                        (element_type))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n      std::string cr_str{cr.data()};\n      return sax->parse_error(element_type_parse_position, cr_str,\n                              parse_error::create(114, element_type_parse_position,\n                                  concat(\"Unsupported BSON record type 0x\", cr_str), nullptr));\n    }\n    }\n  }\n\n  /*!\n  @brief Read a BSON element list (as specified in the BSON-spec)\n\n  The same binary layout is used for objects and arrays, hence it must be\n  indicated with the argument @a is_array which one is expected\n  (true --> array, false --> object).\n\n  @param[in] is_array Determines if the element list being read is to be\n                      treated as an object (@a is_array == false), or as an\n                      array (@a is_array == true).\n  @return whether a valid BSON-object/array was passed to the SAX parser\n  */\n  bool parse_bson_element_list(const bool is_array)\n  {\n    string_t key;\n\n    while (auto element_type = get()) {\n      if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, \"element list\"))) {\n        return false;\n      }\n\n      const std::size_t element_type_parse_position = chars_read;\n\n      if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key))) {\n        return false;\n      }\n\n      if (!is_array && !sax->key(key)) {\n        return false;\n      }\n\n      if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_internal(element_type,\n                               element_type_parse_position))) {\n        return false;\n      }\n\n      // get_bson_cstr only appends\n      key.clear();\n    }\n\n    return true;\n  }\n\n  /*!\n  @brief Reads an array from the BSON input and passes it to the SAX-parser.\n  @return whether a valid BSON-array was passed to the SAX parser\n  */\n  bool parse_bson_array()\n  {\n    std::int32_t document_size{};\n    get_number<std::int32_t, true>(input_format_t::bson, document_size);\n\n    if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast<std::size_t>(-1)))) {\n      return false;\n    }\n\n    if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/true))) {\n      return false;\n    }\n\n    return sax->end_array();\n  }\n\n  //////////\n  // CBOR //\n  //////////\n\n  /*!\n  @param[in] get_char  whether a new character should be retrieved from the\n                       input (true) or whether the last read character should\n                       be considered instead (false)\n  @param[in] tag_handler how CBOR tags should be treated\n\n  @return whether a valid CBOR value was passed to the SAX parser\n  */\n  bool parse_cbor_internal(const bool get_char,\n                           const cbor_tag_handler_t tag_handler)\n  {\n    switch (get_char ? get() : current) {\n    // EOF\n    case std::char_traits<char_type>::eof():\n      return unexpect_eof(input_format_t::cbor, \"value\");\n\n    // Integer 0x00..0x17 (0..23)\n    case 0x00:\n    case 0x01:\n    case 0x02:\n    case 0x03:\n    case 0x04:\n    case 0x05:\n    case 0x06:\n    case 0x07:\n    case 0x08:\n    case 0x09:\n    case 0x0A:\n    case 0x0B:\n    case 0x0C:\n    case 0x0D:\n    case 0x0E:\n    case 0x0F:\n    case 0x10:\n    case 0x11:\n    case 0x12:\n    case 0x13:\n    case 0x14:\n    case 0x15:\n    case 0x16:\n    case 0x17:\n      return sax->number_unsigned(static_cast<number_unsigned_t>(current));\n\n    case 0x18: { // Unsigned integer (one-byte uint8_t follows)\n      std::uint8_t number{};\n      return get_number(input_format_t::cbor, number) && sax->number_unsigned(number);\n    }\n\n    case 0x19: { // Unsigned integer (two-byte uint16_t follows)\n      std::uint16_t number{};\n      return get_number(input_format_t::cbor, number) && sax->number_unsigned(number);\n    }\n\n    case 0x1A: { // Unsigned integer (four-byte uint32_t follows)\n      std::uint32_t number{};\n      return get_number(input_format_t::cbor, number) && sax->number_unsigned(number);\n    }\n\n    case 0x1B: { // Unsigned integer (eight-byte uint64_t follows)\n      std::uint64_t number{};\n      return get_number(input_format_t::cbor, number) && sax->number_unsigned(number);\n    }\n\n    // Negative integer -1-0x00..-1-0x17 (-1..-24)\n    case 0x20:\n    case 0x21:\n    case 0x22:\n    case 0x23:\n    case 0x24:\n    case 0x25:\n    case 0x26:\n    case 0x27:\n    case 0x28:\n    case 0x29:\n    case 0x2A:\n    case 0x2B:\n    case 0x2C:\n    case 0x2D:\n    case 0x2E:\n    case 0x2F:\n    case 0x30:\n    case 0x31:\n    case 0x32:\n    case 0x33:\n    case 0x34:\n    case 0x35:\n    case 0x36:\n    case 0x37:\n      return sax->number_integer(static_cast<std::int8_t>(0x20 - 1 - current));\n\n    case 0x38: { // Negative integer (one-byte uint8_t follows)\n      std::uint8_t number{};\n      return get_number(input_format_t::cbor, number) &&\n             sax->number_integer(static_cast<number_integer_t>(-1) - number);\n    }\n\n    case 0x39: { // Negative integer -1-n (two-byte uint16_t follows)\n      std::uint16_t number{};\n      return get_number(input_format_t::cbor, number) &&\n             sax->number_integer(static_cast<number_integer_t>(-1) - number);\n    }\n\n    case 0x3A: { // Negative integer -1-n (four-byte uint32_t follows)\n      std::uint32_t number{};\n      return get_number(input_format_t::cbor, number) &&\n             sax->number_integer(static_cast<number_integer_t>(-1) - number);\n    }\n\n    case 0x3B: { // Negative integer -1-n (eight-byte uint64_t follows)\n      std::uint64_t number{};\n      return get_number(input_format_t::cbor, number) &&\n             sax->number_integer(static_cast<number_integer_t>(-1)\n                                 - static_cast<number_integer_t>(number));\n    }\n\n    // Binary data (0x00..0x17 bytes follow)\n    case 0x40:\n    case 0x41:\n    case 0x42:\n    case 0x43:\n    case 0x44:\n    case 0x45:\n    case 0x46:\n    case 0x47:\n    case 0x48:\n    case 0x49:\n    case 0x4A:\n    case 0x4B:\n    case 0x4C:\n    case 0x4D:\n    case 0x4E:\n    case 0x4F:\n    case 0x50:\n    case 0x51:\n    case 0x52:\n    case 0x53:\n    case 0x54:\n    case 0x55:\n    case 0x56:\n    case 0x57:\n    case 0x58: // Binary data (one-byte uint8_t for n follows)\n    case 0x59: // Binary data (two-byte uint16_t for n follow)\n    case 0x5A: // Binary data (four-byte uint32_t for n follow)\n    case 0x5B: // Binary data (eight-byte uint64_t for n follow)\n    case 0x5F: { // Binary data (indefinite length)\n      binary_t b;\n      return get_cbor_binary(b) && sax->binary(b);\n    }\n\n    // UTF-8 string (0x00..0x17 bytes follow)\n    case 0x60:\n    case 0x61:\n    case 0x62:\n    case 0x63:\n    case 0x64:\n    case 0x65:\n    case 0x66:\n    case 0x67:\n    case 0x68:\n    case 0x69:\n    case 0x6A:\n    case 0x6B:\n    case 0x6C:\n    case 0x6D:\n    case 0x6E:\n    case 0x6F:\n    case 0x70:\n    case 0x71:\n    case 0x72:\n    case 0x73:\n    case 0x74:\n    case 0x75:\n    case 0x76:\n    case 0x77:\n    case 0x78: // UTF-8 string (one-byte uint8_t for n follows)\n    case 0x79: // UTF-8 string (two-byte uint16_t for n follow)\n    case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)\n    case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)\n    case 0x7F: { // UTF-8 string (indefinite length)\n      string_t s;\n      return get_cbor_string(s) && sax->string(s);\n    }\n\n    // array (0x00..0x17 data items follow)\n    case 0x80:\n    case 0x81:\n    case 0x82:\n    case 0x83:\n    case 0x84:\n    case 0x85:\n    case 0x86:\n    case 0x87:\n    case 0x88:\n    case 0x89:\n    case 0x8A:\n    case 0x8B:\n    case 0x8C:\n    case 0x8D:\n    case 0x8E:\n    case 0x8F:\n    case 0x90:\n    case 0x91:\n    case 0x92:\n    case 0x93:\n    case 0x94:\n    case 0x95:\n    case 0x96:\n    case 0x97:\n      return get_cbor_array(\n               conditional_static_cast<std::size_t>(static_cast<unsigned int>\n                   (current) & 0x1Fu), tag_handler);\n\n    case 0x98: { // array (one-byte uint8_t for n follows)\n      std::uint8_t len{};\n      return get_number(input_format_t::cbor, len) &&\n             get_cbor_array(static_cast<std::size_t>(len), tag_handler);\n    }\n\n    case 0x99: { // array (two-byte uint16_t for n follow)\n      std::uint16_t len{};\n      return get_number(input_format_t::cbor, len) &&\n             get_cbor_array(static_cast<std::size_t>(len), tag_handler);\n    }\n\n    case 0x9A: { // array (four-byte uint32_t for n follow)\n      std::uint32_t len{};\n      return get_number(input_format_t::cbor, len) &&\n             get_cbor_array(conditional_static_cast<std::size_t>(len), tag_handler);\n    }\n\n    case 0x9B: { // array (eight-byte uint64_t for n follow)\n      std::uint64_t len{};\n      return get_number(input_format_t::cbor, len) &&\n             get_cbor_array(conditional_static_cast<std::size_t>(len), tag_handler);\n    }\n\n    case 0x9F: // array (indefinite length)\n      return get_cbor_array(static_cast<std::size_t>(-1), tag_handler);\n\n    // map (0x00..0x17 pairs of data items follow)\n    case 0xA0:\n    case 0xA1:\n    case 0xA2:\n    case 0xA3:\n    case 0xA4:\n    case 0xA5:\n    case 0xA6:\n    case 0xA7:\n    case 0xA8:\n    case 0xA9:\n    case 0xAA:\n    case 0xAB:\n    case 0xAC:\n    case 0xAD:\n    case 0xAE:\n    case 0xAF:\n    case 0xB0:\n    case 0xB1:\n    case 0xB2:\n    case 0xB3:\n    case 0xB4:\n    case 0xB5:\n    case 0xB6:\n    case 0xB7:\n      return get_cbor_object(conditional_static_cast<std::size_t>\n                             (static_cast<unsigned int>(current) & 0x1Fu), tag_handler);\n\n    case 0xB8: { // map (one-byte uint8_t for n follows)\n      std::uint8_t len{};\n      return get_number(input_format_t::cbor, len) &&\n             get_cbor_object(static_cast<std::size_t>(len), tag_handler);\n    }\n\n    case 0xB9: { // map (two-byte uint16_t for n follow)\n      std::uint16_t len{};\n      return get_number(input_format_t::cbor, len) &&\n             get_cbor_object(static_cast<std::size_t>(len), tag_handler);\n    }\n\n    case 0xBA: { // map (four-byte uint32_t for n follow)\n      std::uint32_t len{};\n      return get_number(input_format_t::cbor, len) &&\n             get_cbor_object(conditional_static_cast<std::size_t>(len), tag_handler);\n    }\n\n    case 0xBB: { // map (eight-byte uint64_t for n follow)\n      std::uint64_t len{};\n      return get_number(input_format_t::cbor, len) &&\n             get_cbor_object(conditional_static_cast<std::size_t>(len), tag_handler);\n    }\n\n    case 0xBF: // map (indefinite length)\n      return get_cbor_object(static_cast<std::size_t>(-1), tag_handler);\n\n    case 0xC6: // tagged item\n    case 0xC7:\n    case 0xC8:\n    case 0xC9:\n    case 0xCA:\n    case 0xCB:\n    case 0xCC:\n    case 0xCD:\n    case 0xCE:\n    case 0xCF:\n    case 0xD0:\n    case 0xD1:\n    case 0xD2:\n    case 0xD3:\n    case 0xD4:\n    case 0xD8: // tagged item (1 bytes follow)\n    case 0xD9: // tagged item (2 bytes follow)\n    case 0xDA: // tagged item (4 bytes follow)\n    case 0xDB: { // tagged item (8 bytes follow)\n      switch (tag_handler) {\n      case cbor_tag_handler_t::error: {\n        auto last_token = get_token_string();\n        return sax->parse_error(chars_read, last_token, parse_error::create(112,\n                                chars_read,\n                                exception_message(input_format_t::cbor, concat(\"invalid byte: 0x\", last_token),\n                                    \"value\"), nullptr));\n      }\n\n      case cbor_tag_handler_t::ignore: {\n        // ignore binary subtype\n        switch (current) {\n        case 0xD8: {\n          std::uint8_t subtype_to_ignore{};\n          get_number(input_format_t::cbor, subtype_to_ignore);\n          break;\n        }\n\n        case 0xD9: {\n          std::uint16_t subtype_to_ignore{};\n          get_number(input_format_t::cbor, subtype_to_ignore);\n          break;\n        }\n\n        case 0xDA: {\n          std::uint32_t subtype_to_ignore{};\n          get_number(input_format_t::cbor, subtype_to_ignore);\n          break;\n        }\n\n        case 0xDB: {\n          std::uint64_t subtype_to_ignore{};\n          get_number(input_format_t::cbor, subtype_to_ignore);\n          break;\n        }\n\n        default:\n          break;\n        }\n\n        return parse_cbor_internal(true, tag_handler);\n      }\n\n      case cbor_tag_handler_t::store: {\n        binary_t b;\n\n        // use binary subtype and store in binary container\n        switch (current) {\n        case 0xD8: {\n          std::uint8_t subtype{};\n          get_number(input_format_t::cbor, subtype);\n          b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>\n                        (subtype));\n          break;\n        }\n\n        case 0xD9: {\n          std::uint16_t subtype{};\n          get_number(input_format_t::cbor, subtype);\n          b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>\n                        (subtype));\n          break;\n        }\n\n        case 0xDA: {\n          std::uint32_t subtype{};\n          get_number(input_format_t::cbor, subtype);\n          b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>\n                        (subtype));\n          break;\n        }\n\n        case 0xDB: {\n          std::uint64_t subtype{};\n          get_number(input_format_t::cbor, subtype);\n          b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>\n                        (subtype));\n          break;\n        }\n\n        default:\n          return parse_cbor_internal(true, tag_handler);\n        }\n\n        get();\n        return get_cbor_binary(b) && sax->binary(b);\n      }\n\n      default:                 // LCOV_EXCL_LINE\n        JSON_ASSERT(\n          false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n        return false;        // LCOV_EXCL_LINE\n      }\n    }\n\n    case 0xF4: // false\n      return sax->boolean(false);\n\n    case 0xF5: // true\n      return sax->boolean(true);\n\n    case 0xF6: // null\n      return sax->null();\n\n    case 0xF9: { // Half-Precision Float (two-byte IEEE 754)\n      const auto byte1_raw = get();\n\n      if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, \"number\"))) {\n        return false;\n      }\n\n      const auto byte2_raw = get();\n\n      if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, \"number\"))) {\n        return false;\n      }\n\n      const auto byte1 = static_cast<unsigned char>(byte1_raw);\n      const auto byte2 = static_cast<unsigned char>(byte2_raw);\n      // code from RFC 7049, Appendix D, Figure 3:\n      // As half-precision floating-point numbers were only added\n      // to IEEE 754 in 2008, today's programming platforms often\n      // still only have limited support for them. It is very\n      // easy to include at least decoding support for them even\n      // without such support. An example of a small decoder for\n      // half-precision floating-point numbers in the C language\n      // is shown in Fig. 3.\n      const auto half = static_cast<unsigned int>((byte1 << 8u) + byte2);\n      const double val = [&half] {\n        const int exp = (half >> 10u) & 0x1Fu;\n        const unsigned int mant = half & 0x3FFu;\n        JSON_ASSERT(0 <= exp&& exp <= 32);\n        JSON_ASSERT(mant <= 1024);\n\n        switch (exp)\n        {\n        case 0:\n          return std::ldexp(mant, -24);\n\n        case 31:\n          return (mant == 0)\n          ? std::numeric_limits<double>::infinity()\n          : std::numeric_limits<double>::quiet_NaN();\n\n        default:\n          return std::ldexp(mant + 1024, exp - 25);\n        }\n      }();\n      return sax->number_float((half & 0x8000u) != 0\n                               ? static_cast<number_float_t>(-val)\n                               : static_cast<number_float_t>(val), \"\");\n    }\n\n    case 0xFA: { // Single-Precision Float (four-byte IEEE 754)\n      float number{};\n      return get_number(input_format_t::cbor, number) &&\n             sax->number_float(static_cast<number_float_t>(number), \"\");\n    }\n\n    case 0xFB: { // Double-Precision Float (eight-byte IEEE 754)\n      double number{};\n      return get_number(input_format_t::cbor, number) &&\n             sax->number_float(static_cast<number_float_t>(number), \"\");\n    }\n\n    default: { // anything else (0xFF is handled inside the other types)\n      auto last_token = get_token_string();\n      return sax->parse_error(chars_read, last_token, parse_error::create(112,\n                              chars_read,\n                              exception_message(input_format_t::cbor, concat(\"invalid byte: 0x\", last_token),\n                                  \"value\"), nullptr));\n    }\n    }\n  }\n\n  /*!\n  @brief reads a CBOR string\n\n  This function first reads starting bytes to determine the expected\n  string length and then copies this number of bytes into a string.\n  Additionally, CBOR's strings with indefinite lengths are supported.\n\n  @param[out] result  created string\n\n  @return whether string creation completed\n  */\n  bool get_cbor_string(string_t& result)\n  {\n    if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, \"string\"))) {\n      return false;\n    }\n\n    switch (current) {\n    // UTF-8 string (0x00..0x17 bytes follow)\n    case 0x60:\n    case 0x61:\n    case 0x62:\n    case 0x63:\n    case 0x64:\n    case 0x65:\n    case 0x66:\n    case 0x67:\n    case 0x68:\n    case 0x69:\n    case 0x6A:\n    case 0x6B:\n    case 0x6C:\n    case 0x6D:\n    case 0x6E:\n    case 0x6F:\n    case 0x70:\n    case 0x71:\n    case 0x72:\n    case 0x73:\n    case 0x74:\n    case 0x75:\n    case 0x76:\n    case 0x77: {\n      return get_string(input_format_t::cbor,\n                        static_cast<unsigned int>(current) & 0x1Fu, result);\n    }\n\n    case 0x78: { // UTF-8 string (one-byte uint8_t for n follows)\n      std::uint8_t len{};\n      return get_number(input_format_t::cbor, len) &&\n             get_string(input_format_t::cbor, len, result);\n    }\n\n    case 0x79: { // UTF-8 string (two-byte uint16_t for n follow)\n      std::uint16_t len{};\n      return get_number(input_format_t::cbor, len) &&\n             get_string(input_format_t::cbor, len, result);\n    }\n\n    case 0x7A: { // UTF-8 string (four-byte uint32_t for n follow)\n      std::uint32_t len{};\n      return get_number(input_format_t::cbor, len) &&\n             get_string(input_format_t::cbor, len, result);\n    }\n\n    case 0x7B: { // UTF-8 string (eight-byte uint64_t for n follow)\n      std::uint64_t len{};\n      return get_number(input_format_t::cbor, len) &&\n             get_string(input_format_t::cbor, len, result);\n    }\n\n    case 0x7F: { // UTF-8 string (indefinite length)\n      while (get() != 0xFF) {\n        string_t chunk;\n\n        if (!get_cbor_string(chunk)) {\n          return false;\n        }\n\n        result.append(chunk);\n      }\n\n      return true;\n    }\n\n    default: {\n      auto last_token = get_token_string();\n      return sax->parse_error(chars_read, last_token, parse_error::create(113,\n                              chars_read,\n                              exception_message(input_format_t::cbor,\n                                  concat(\"expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x\",\n                                         last_token), \"string\"), nullptr));\n    }\n    }\n  }\n\n  /*!\n  @brief reads a CBOR byte array\n\n  This function first reads starting bytes to determine the expected\n  byte array length and then copies this number of bytes into the byte array.\n  Additionally, CBOR's byte arrays with indefinite lengths are supported.\n\n  @param[out] result  created byte array\n\n  @return whether byte array creation completed\n  */\n  bool get_cbor_binary(binary_t& result)\n  {\n    if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, \"binary\"))) {\n      return false;\n    }\n\n    switch (current) {\n    // Binary data (0x00..0x17 bytes follow)\n    case 0x40:\n    case 0x41:\n    case 0x42:\n    case 0x43:\n    case 0x44:\n    case 0x45:\n    case 0x46:\n    case 0x47:\n    case 0x48:\n    case 0x49:\n    case 0x4A:\n    case 0x4B:\n    case 0x4C:\n    case 0x4D:\n    case 0x4E:\n    case 0x4F:\n    case 0x50:\n    case 0x51:\n    case 0x52:\n    case 0x53:\n    case 0x54:\n    case 0x55:\n    case 0x56:\n    case 0x57: {\n      return get_binary(input_format_t::cbor,\n                        static_cast<unsigned int>(current) & 0x1Fu, result);\n    }\n\n    case 0x58: { // Binary data (one-byte uint8_t for n follows)\n      std::uint8_t len{};\n      return get_number(input_format_t::cbor, len) &&\n             get_binary(input_format_t::cbor, len, result);\n    }\n\n    case 0x59: { // Binary data (two-byte uint16_t for n follow)\n      std::uint16_t len{};\n      return get_number(input_format_t::cbor, len) &&\n             get_binary(input_format_t::cbor, len, result);\n    }\n\n    case 0x5A: { // Binary data (four-byte uint32_t for n follow)\n      std::uint32_t len{};\n      return get_number(input_format_t::cbor, len) &&\n             get_binary(input_format_t::cbor, len, result);\n    }\n\n    case 0x5B: { // Binary data (eight-byte uint64_t for n follow)\n      std::uint64_t len{};\n      return get_number(input_format_t::cbor, len) &&\n             get_binary(input_format_t::cbor, len, result);\n    }\n\n    case 0x5F: { // Binary data (indefinite length)\n      while (get() != 0xFF) {\n        binary_t chunk;\n\n        if (!get_cbor_binary(chunk)) {\n          return false;\n        }\n\n        result.insert(result.end(), chunk.begin(), chunk.end());\n      }\n\n      return true;\n    }\n\n    default: {\n      auto last_token = get_token_string();\n      return sax->parse_error(chars_read, last_token, parse_error::create(113,\n                              chars_read,\n                              exception_message(input_format_t::cbor,\n                                  concat(\"expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x\",\n                                         last_token), \"binary\"), nullptr));\n    }\n    }\n  }\n\n  /*!\n  @param[in] len  the length of the array or static_cast<std::size_t>(-1) for an\n                  array of indefinite size\n  @param[in] tag_handler how CBOR tags should be treated\n  @return whether array creation completed\n  */\n  bool get_cbor_array(const std::size_t len,\n                      const cbor_tag_handler_t tag_handler)\n  {\n    if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) {\n      return false;\n    }\n\n    if (len != static_cast<std::size_t>(-1)) {\n      for (std::size_t i = 0; i < len; ++i) {\n        if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) {\n          return false;\n        }\n      }\n    } else {\n      while (get() != 0xFF) {\n        if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(false, tag_handler))) {\n          return false;\n        }\n      }\n    }\n\n    return sax->end_array();\n  }\n\n  /*!\n  @param[in] len  the length of the object or static_cast<std::size_t>(-1) for an\n                  object of indefinite size\n  @param[in] tag_handler how CBOR tags should be treated\n  @return whether object creation completed\n  */\n  bool get_cbor_object(const std::size_t len,\n                       const cbor_tag_handler_t tag_handler)\n  {\n    if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) {\n      return false;\n    }\n\n    if (len != 0) {\n      string_t key;\n\n      if (len != static_cast<std::size_t>(-1)) {\n        for (std::size_t i = 0; i < len; ++i) {\n          get();\n\n          if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) {\n            return false;\n          }\n\n          if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) {\n            return false;\n          }\n\n          key.clear();\n        }\n      } else {\n        while (get() != 0xFF) {\n          if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) {\n            return false;\n          }\n\n          if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) {\n            return false;\n          }\n\n          key.clear();\n        }\n      }\n    }\n\n    return sax->end_object();\n  }\n\n  /////////////\n  // MsgPack //\n  /////////////\n\n  /*!\n  @return whether a valid MessagePack value was passed to the SAX parser\n  */\n  bool parse_msgpack_internal()\n  {\n    switch (get()) {\n    // EOF\n    case std::char_traits<char_type>::eof():\n      return unexpect_eof(input_format_t::msgpack, \"value\");\n\n    // positive fixint\n    case 0x00:\n    case 0x01:\n    case 0x02:\n    case 0x03:\n    case 0x04:\n    case 0x05:\n    case 0x06:\n    case 0x07:\n    case 0x08:\n    case 0x09:\n    case 0x0A:\n    case 0x0B:\n    case 0x0C:\n    case 0x0D:\n    case 0x0E:\n    case 0x0F:\n    case 0x10:\n    case 0x11:\n    case 0x12:\n    case 0x13:\n    case 0x14:\n    case 0x15:\n    case 0x16:\n    case 0x17:\n    case 0x18:\n    case 0x19:\n    case 0x1A:\n    case 0x1B:\n    case 0x1C:\n    case 0x1D:\n    case 0x1E:\n    case 0x1F:\n    case 0x20:\n    case 0x21:\n    case 0x22:\n    case 0x23:\n    case 0x24:\n    case 0x25:\n    case 0x26:\n    case 0x27:\n    case 0x28:\n    case 0x29:\n    case 0x2A:\n    case 0x2B:\n    case 0x2C:\n    case 0x2D:\n    case 0x2E:\n    case 0x2F:\n    case 0x30:\n    case 0x31:\n    case 0x32:\n    case 0x33:\n    case 0x34:\n    case 0x35:\n    case 0x36:\n    case 0x37:\n    case 0x38:\n    case 0x39:\n    case 0x3A:\n    case 0x3B:\n    case 0x3C:\n    case 0x3D:\n    case 0x3E:\n    case 0x3F:\n    case 0x40:\n    case 0x41:\n    case 0x42:\n    case 0x43:\n    case 0x44:\n    case 0x45:\n    case 0x46:\n    case 0x47:\n    case 0x48:\n    case 0x49:\n    case 0x4A:\n    case 0x4B:\n    case 0x4C:\n    case 0x4D:\n    case 0x4E:\n    case 0x4F:\n    case 0x50:\n    case 0x51:\n    case 0x52:\n    case 0x53:\n    case 0x54:\n    case 0x55:\n    case 0x56:\n    case 0x57:\n    case 0x58:\n    case 0x59:\n    case 0x5A:\n    case 0x5B:\n    case 0x5C:\n    case 0x5D:\n    case 0x5E:\n    case 0x5F:\n    case 0x60:\n    case 0x61:\n    case 0x62:\n    case 0x63:\n    case 0x64:\n    case 0x65:\n    case 0x66:\n    case 0x67:\n    case 0x68:\n    case 0x69:\n    case 0x6A:\n    case 0x6B:\n    case 0x6C:\n    case 0x6D:\n    case 0x6E:\n    case 0x6F:\n    case 0x70:\n    case 0x71:\n    case 0x72:\n    case 0x73:\n    case 0x74:\n    case 0x75:\n    case 0x76:\n    case 0x77:\n    case 0x78:\n    case 0x79:\n    case 0x7A:\n    case 0x7B:\n    case 0x7C:\n    case 0x7D:\n    case 0x7E:\n    case 0x7F:\n      return sax->number_unsigned(static_cast<number_unsigned_t>(current));\n\n    // fixmap\n    case 0x80:\n    case 0x81:\n    case 0x82:\n    case 0x83:\n    case 0x84:\n    case 0x85:\n    case 0x86:\n    case 0x87:\n    case 0x88:\n    case 0x89:\n    case 0x8A:\n    case 0x8B:\n    case 0x8C:\n    case 0x8D:\n    case 0x8E:\n    case 0x8F:\n      return get_msgpack_object(conditional_static_cast<std::size_t>\n                                (static_cast<unsigned int>(current) & 0x0Fu));\n\n    // fixarray\n    case 0x90:\n    case 0x91:\n    case 0x92:\n    case 0x93:\n    case 0x94:\n    case 0x95:\n    case 0x96:\n    case 0x97:\n    case 0x98:\n    case 0x99:\n    case 0x9A:\n    case 0x9B:\n    case 0x9C:\n    case 0x9D:\n    case 0x9E:\n    case 0x9F:\n      return get_msgpack_array(conditional_static_cast<std::size_t>\n                               (static_cast<unsigned int>(current) & 0x0Fu));\n\n    // fixstr\n    case 0xA0:\n    case 0xA1:\n    case 0xA2:\n    case 0xA3:\n    case 0xA4:\n    case 0xA5:\n    case 0xA6:\n    case 0xA7:\n    case 0xA8:\n    case 0xA9:\n    case 0xAA:\n    case 0xAB:\n    case 0xAC:\n    case 0xAD:\n    case 0xAE:\n    case 0xAF:\n    case 0xB0:\n    case 0xB1:\n    case 0xB2:\n    case 0xB3:\n    case 0xB4:\n    case 0xB5:\n    case 0xB6:\n    case 0xB7:\n    case 0xB8:\n    case 0xB9:\n    case 0xBA:\n    case 0xBB:\n    case 0xBC:\n    case 0xBD:\n    case 0xBE:\n    case 0xBF:\n    case 0xD9: // str 8\n    case 0xDA: // str 16\n    case 0xDB: { // str 32\n      string_t s;\n      return get_msgpack_string(s) && sax->string(s);\n    }\n\n    case 0xC0: // nil\n      return sax->null();\n\n    case 0xC2: // false\n      return sax->boolean(false);\n\n    case 0xC3: // true\n      return sax->boolean(true);\n\n    case 0xC4: // bin 8\n    case 0xC5: // bin 16\n    case 0xC6: // bin 32\n    case 0xC7: // ext 8\n    case 0xC8: // ext 16\n    case 0xC9: // ext 32\n    case 0xD4: // fixext 1\n    case 0xD5: // fixext 2\n    case 0xD6: // fixext 4\n    case 0xD7: // fixext 8\n    case 0xD8: { // fixext 16\n      binary_t b;\n      return get_msgpack_binary(b) && sax->binary(b);\n    }\n\n    case 0xCA: { // float 32\n      float number{};\n      return get_number(input_format_t::msgpack, number) &&\n             sax->number_float(static_cast<number_float_t>(number), \"\");\n    }\n\n    case 0xCB: { // float 64\n      double number{};\n      return get_number(input_format_t::msgpack, number) &&\n             sax->number_float(static_cast<number_float_t>(number), \"\");\n    }\n\n    case 0xCC: { // uint 8\n      std::uint8_t number{};\n      return get_number(input_format_t::msgpack, number) &&\n             sax->number_unsigned(number);\n    }\n\n    case 0xCD: { // uint 16\n      std::uint16_t number{};\n      return get_number(input_format_t::msgpack, number) &&\n             sax->number_unsigned(number);\n    }\n\n    case 0xCE: { // uint 32\n      std::uint32_t number{};\n      return get_number(input_format_t::msgpack, number) &&\n             sax->number_unsigned(number);\n    }\n\n    case 0xCF: { // uint 64\n      std::uint64_t number{};\n      return get_number(input_format_t::msgpack, number) &&\n             sax->number_unsigned(number);\n    }\n\n    case 0xD0: { // int 8\n      std::int8_t number{};\n      return get_number(input_format_t::msgpack, number) &&\n             sax->number_integer(number);\n    }\n\n    case 0xD1: { // int 16\n      std::int16_t number{};\n      return get_number(input_format_t::msgpack, number) &&\n             sax->number_integer(number);\n    }\n\n    case 0xD2: { // int 32\n      std::int32_t number{};\n      return get_number(input_format_t::msgpack, number) &&\n             sax->number_integer(number);\n    }\n\n    case 0xD3: { // int 64\n      std::int64_t number{};\n      return get_number(input_format_t::msgpack, number) &&\n             sax->number_integer(number);\n    }\n\n    case 0xDC: { // array 16\n      std::uint16_t len{};\n      return get_number(input_format_t::msgpack, len) &&\n             get_msgpack_array(static_cast<std::size_t>(len));\n    }\n\n    case 0xDD: { // array 32\n      std::uint32_t len{};\n      return get_number(input_format_t::msgpack, len) &&\n             get_msgpack_array(conditional_static_cast<std::size_t>(len));\n    }\n\n    case 0xDE: { // map 16\n      std::uint16_t len{};\n      return get_number(input_format_t::msgpack, len) &&\n             get_msgpack_object(static_cast<std::size_t>(len));\n    }\n\n    case 0xDF: { // map 32\n      std::uint32_t len{};\n      return get_number(input_format_t::msgpack, len) &&\n             get_msgpack_object(conditional_static_cast<std::size_t>(len));\n    }\n\n    // negative fixint\n    case 0xE0:\n    case 0xE1:\n    case 0xE2:\n    case 0xE3:\n    case 0xE4:\n    case 0xE5:\n    case 0xE6:\n    case 0xE7:\n    case 0xE8:\n    case 0xE9:\n    case 0xEA:\n    case 0xEB:\n    case 0xEC:\n    case 0xED:\n    case 0xEE:\n    case 0xEF:\n    case 0xF0:\n    case 0xF1:\n    case 0xF2:\n    case 0xF3:\n    case 0xF4:\n    case 0xF5:\n    case 0xF6:\n    case 0xF7:\n    case 0xF8:\n    case 0xF9:\n    case 0xFA:\n    case 0xFB:\n    case 0xFC:\n    case 0xFD:\n    case 0xFE:\n    case 0xFF:\n      return sax->number_integer(static_cast<std::int8_t>(current));\n\n    default: { // anything else\n      auto last_token = get_token_string();\n      return sax->parse_error(chars_read, last_token, parse_error::create(112,\n                              chars_read,\n                              exception_message(input_format_t::msgpack, concat(\"invalid byte: 0x\",\n                                  last_token), \"value\"), nullptr));\n    }\n    }\n  }\n\n  /*!\n  @brief reads a MessagePack string\n\n  This function first reads starting bytes to determine the expected\n  string length and then copies this number of bytes into a string.\n\n  @param[out] result  created string\n\n  @return whether string creation completed\n  */\n  bool get_msgpack_string(string_t& result)\n  {\n    if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::msgpack, \"string\"))) {\n      return false;\n    }\n\n    switch (current) {\n    // fixstr\n    case 0xA0:\n    case 0xA1:\n    case 0xA2:\n    case 0xA3:\n    case 0xA4:\n    case 0xA5:\n    case 0xA6:\n    case 0xA7:\n    case 0xA8:\n    case 0xA9:\n    case 0xAA:\n    case 0xAB:\n    case 0xAC:\n    case 0xAD:\n    case 0xAE:\n    case 0xAF:\n    case 0xB0:\n    case 0xB1:\n    case 0xB2:\n    case 0xB3:\n    case 0xB4:\n    case 0xB5:\n    case 0xB6:\n    case 0xB7:\n    case 0xB8:\n    case 0xB9:\n    case 0xBA:\n    case 0xBB:\n    case 0xBC:\n    case 0xBD:\n    case 0xBE:\n    case 0xBF: {\n      return get_string(input_format_t::msgpack,\n                        static_cast<unsigned int>(current) & 0x1Fu, result);\n    }\n\n    case 0xD9: { // str 8\n      std::uint8_t len{};\n      return get_number(input_format_t::msgpack, len) &&\n             get_string(input_format_t::msgpack, len, result);\n    }\n\n    case 0xDA: { // str 16\n      std::uint16_t len{};\n      return get_number(input_format_t::msgpack, len) &&\n             get_string(input_format_t::msgpack, len, result);\n    }\n\n    case 0xDB: { // str 32\n      std::uint32_t len{};\n      return get_number(input_format_t::msgpack, len) &&\n             get_string(input_format_t::msgpack, len, result);\n    }\n\n    default: {\n      auto last_token = get_token_string();\n      return sax->parse_error(chars_read, last_token, parse_error::create(113,\n                              chars_read,\n                              exception_message(input_format_t::msgpack,\n                                  concat(\"expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x\",\n                                         last_token), \"string\"), nullptr));\n    }\n    }\n  }\n\n  /*!\n  @brief reads a MessagePack byte array\n\n  This function first reads starting bytes to determine the expected\n  byte array length and then copies this number of bytes into a byte array.\n\n  @param[out] result  created byte array\n\n  @return whether byte array creation completed\n  */\n  bool get_msgpack_binary(binary_t& result)\n  {\n    // helper function to set the subtype\n    auto assign_and_return_true = [&result](std::int8_t subtype) {\n      result.set_subtype(static_cast<std::uint8_t>(subtype));\n      return true;\n    };\n\n    switch (current) {\n    case 0xC4: { // bin 8\n      std::uint8_t len{};\n      return get_number(input_format_t::msgpack, len) &&\n             get_binary(input_format_t::msgpack, len, result);\n    }\n\n    case 0xC5: { // bin 16\n      std::uint16_t len{};\n      return get_number(input_format_t::msgpack, len) &&\n             get_binary(input_format_t::msgpack, len, result);\n    }\n\n    case 0xC6: { // bin 32\n      std::uint32_t len{};\n      return get_number(input_format_t::msgpack, len) &&\n             get_binary(input_format_t::msgpack, len, result);\n    }\n\n    case 0xC7: { // ext 8\n      std::uint8_t len{};\n      std::int8_t subtype{};\n      return get_number(input_format_t::msgpack, len) &&\n             get_number(input_format_t::msgpack, subtype) &&\n             get_binary(input_format_t::msgpack, len, result) &&\n             assign_and_return_true(subtype);\n    }\n\n    case 0xC8: { // ext 16\n      std::uint16_t len{};\n      std::int8_t subtype{};\n      return get_number(input_format_t::msgpack, len) &&\n             get_number(input_format_t::msgpack, subtype) &&\n             get_binary(input_format_t::msgpack, len, result) &&\n             assign_and_return_true(subtype);\n    }\n\n    case 0xC9: { // ext 32\n      std::uint32_t len{};\n      std::int8_t subtype{};\n      return get_number(input_format_t::msgpack, len) &&\n             get_number(input_format_t::msgpack, subtype) &&\n             get_binary(input_format_t::msgpack, len, result) &&\n             assign_and_return_true(subtype);\n    }\n\n    case 0xD4: { // fixext 1\n      std::int8_t subtype{};\n      return get_number(input_format_t::msgpack, subtype) &&\n             get_binary(input_format_t::msgpack, 1, result) &&\n             assign_and_return_true(subtype);\n    }\n\n    case 0xD5: { // fixext 2\n      std::int8_t subtype{};\n      return get_number(input_format_t::msgpack, subtype) &&\n             get_binary(input_format_t::msgpack, 2, result) &&\n             assign_and_return_true(subtype);\n    }\n\n    case 0xD6: { // fixext 4\n      std::int8_t subtype{};\n      return get_number(input_format_t::msgpack, subtype) &&\n             get_binary(input_format_t::msgpack, 4, result) &&\n             assign_and_return_true(subtype);\n    }\n\n    case 0xD7: { // fixext 8\n      std::int8_t subtype{};\n      return get_number(input_format_t::msgpack, subtype) &&\n             get_binary(input_format_t::msgpack, 8, result) &&\n             assign_and_return_true(subtype);\n    }\n\n    case 0xD8: { // fixext 16\n      std::int8_t subtype{};\n      return get_number(input_format_t::msgpack, subtype) &&\n             get_binary(input_format_t::msgpack, 16, result) &&\n             assign_and_return_true(subtype);\n    }\n\n    default:           // LCOV_EXCL_LINE\n      return false;  // LCOV_EXCL_LINE\n    }\n  }\n\n  /*!\n  @param[in] len  the length of the array\n  @return whether array creation completed\n  */\n  bool get_msgpack_array(const std::size_t len)\n  {\n    if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) {\n      return false;\n    }\n\n    for (std::size_t i = 0; i < len; ++i) {\n      if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) {\n        return false;\n      }\n    }\n\n    return sax->end_array();\n  }\n\n  /*!\n  @param[in] len  the length of the object\n  @return whether object creation completed\n  */\n  bool get_msgpack_object(const std::size_t len)\n  {\n    if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) {\n      return false;\n    }\n\n    string_t key;\n\n    for (std::size_t i = 0; i < len; ++i) {\n      get();\n\n      if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key))) {\n        return false;\n      }\n\n      if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) {\n        return false;\n      }\n\n      key.clear();\n    }\n\n    return sax->end_object();\n  }\n\n  ////////////\n  // UBJSON //\n  ////////////\n\n  /*!\n  @param[in] get_char  whether a new character should be retrieved from the\n                       input (true, default) or whether the last read\n                       character should be considered instead\n\n  @return whether a valid UBJSON value was passed to the SAX parser\n  */\n  bool parse_ubjson_internal(const bool get_char = true)\n  {\n    return get_ubjson_value(get_char ? get_ignore_noop() : current);\n  }\n\n  /*!\n  @brief reads a UBJSON string\n\n  This function is either called after reading the 'S' byte explicitly\n  indicating a string, or in case of an object key where the 'S' byte can be\n  left out.\n\n  @param[out] result   created string\n  @param[in] get_char  whether a new character should be retrieved from the\n                       input (true, default) or whether the last read\n                       character should be considered instead\n\n  @return whether string creation completed\n  */\n  bool get_ubjson_string(string_t& result, const bool get_char = true)\n  {\n    if (get_char) {\n      get();  // TODO(niels): may we ignore N here?\n    }\n\n    if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"value\"))) {\n      return false;\n    }\n\n    switch (current) {\n    case 'U': {\n      std::uint8_t len{};\n      return get_number(input_format, len) && get_string(input_format, len, result);\n    }\n\n    case 'i': {\n      std::int8_t len{};\n      return get_number(input_format, len) && get_string(input_format, len, result);\n    }\n\n    case 'I': {\n      std::int16_t len{};\n      return get_number(input_format, len) && get_string(input_format, len, result);\n    }\n\n    case 'l': {\n      std::int32_t len{};\n      return get_number(input_format, len) && get_string(input_format, len, result);\n    }\n\n    case 'L': {\n      std::int64_t len{};\n      return get_number(input_format, len) && get_string(input_format, len, result);\n    }\n\n    case 'u': {\n      if (input_format != input_format_t::bjdata) {\n        break;\n      }\n\n      std::uint16_t len{};\n      return get_number(input_format, len) && get_string(input_format, len, result);\n    }\n\n    case 'm': {\n      if (input_format != input_format_t::bjdata) {\n        break;\n      }\n\n      std::uint32_t len{};\n      return get_number(input_format, len) && get_string(input_format, len, result);\n    }\n\n    case 'M': {\n      if (input_format != input_format_t::bjdata) {\n        break;\n      }\n\n      std::uint64_t len{};\n      return get_number(input_format, len) && get_string(input_format, len, result);\n    }\n\n    default:\n      break;\n    }\n\n    auto last_token = get_token_string();\n    std::string message;\n\n    if (input_format != input_format_t::bjdata) {\n      message = \"expected length type specification (U, i, I, l, L); last byte: 0x\" +\n                last_token;\n    } else {\n      message = \"expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x\"\n                + last_token;\n    }\n\n    return sax->parse_error(chars_read, last_token, parse_error::create(113,\n                            chars_read, exception_message(input_format, message, \"string\"), nullptr));\n  }\n\n  /*!\n  @param[out] dim  an integer vector storing the ND array dimensions\n  @return whether reading ND array size vector is successful\n  */\n  bool get_ubjson_ndarray_size(std::vector<size_t>& dim)\n  {\n    std::pair<std::size_t, char_int_type> size_and_type;\n    size_t dimlen = 0;\n    bool no_ndarray = true;\n\n    if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type, no_ndarray))) {\n      return false;\n    }\n\n    if (size_and_type.first != npos) {\n      if (size_and_type.second != 0) {\n        if (size_and_type.second != 'N') {\n          for (std::size_t i = 0; i < size_and_type.first; ++i) {\n            if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray,\n                                     size_and_type.second))) {\n              return false;\n            }\n\n            dim.push_back(dimlen);\n          }\n        }\n      } else {\n        for (std::size_t i = 0; i < size_and_type.first; ++i) {\n          if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray))) {\n            return false;\n          }\n\n          dim.push_back(dimlen);\n        }\n      }\n    } else {\n      while (current != ']') {\n        if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, current))) {\n          return false;\n        }\n\n        dim.push_back(dimlen);\n        get_ignore_noop();\n      }\n    }\n\n    return true;\n  }\n\n  /*!\n  @param[out] result  determined size\n  @param[in,out] is_ndarray  for input, `true` means already inside an ndarray vector\n                             or ndarray dimension is not allowed; `false` means ndarray\n                             is allowed; for output, `true` means an ndarray is found;\n                             is_ndarray can only return `true` when its initial value\n                             is `false`\n  @param[in] prefix  type marker if already read, otherwise set to 0\n\n  @return whether size determination completed\n  */\n  bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray,\n                             char_int_type prefix = 0)\n  {\n    if (prefix == 0) {\n      prefix = get_ignore_noop();\n    }\n\n    switch (prefix) {\n    case 'U': {\n      std::uint8_t number{};\n\n      if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) {\n        return false;\n      }\n\n      result = static_cast<std::size_t>(number);\n      return true;\n    }\n\n    case 'i': {\n      std::int8_t number{};\n\n      if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) {\n        return false;\n      }\n\n      if (number < 0) {\n        return sax->parse_error(chars_read, get_token_string(), parse_error::create(113,\n                                chars_read,\n                                exception_message(input_format,\n                                    \"count in an optimized container must be positive\", \"size\"), nullptr));\n      }\n\n      result = static_cast<std::size_t>\n               (number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char\n      return true;\n    }\n\n    case 'I': {\n      std::int16_t number{};\n\n      if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) {\n        return false;\n      }\n\n      if (number < 0) {\n        return sax->parse_error(chars_read, get_token_string(), parse_error::create(113,\n                                chars_read,\n                                exception_message(input_format,\n                                    \"count in an optimized container must be positive\", \"size\"), nullptr));\n      }\n\n      result = static_cast<std::size_t>(number);\n      return true;\n    }\n\n    case 'l': {\n      std::int32_t number{};\n\n      if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) {\n        return false;\n      }\n\n      if (number < 0) {\n        return sax->parse_error(chars_read, get_token_string(), parse_error::create(113,\n                                chars_read,\n                                exception_message(input_format,\n                                    \"count in an optimized container must be positive\", \"size\"), nullptr));\n      }\n\n      result = static_cast<std::size_t>(number);\n      return true;\n    }\n\n    case 'L': {\n      std::int64_t number{};\n\n      if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) {\n        return false;\n      }\n\n      if (number < 0) {\n        return sax->parse_error(chars_read, get_token_string(), parse_error::create(113,\n                                chars_read,\n                                exception_message(input_format,\n                                    \"count in an optimized container must be positive\", \"size\"), nullptr));\n      }\n\n      if (!value_in_range_of<std::size_t>(number)) {\n        return sax->parse_error(chars_read, get_token_string(),\n                                out_of_range::create(408,\n                                    exception_message(input_format, \"integer value overflow\", \"size\"), nullptr));\n      }\n\n      result = static_cast<std::size_t>(number);\n      return true;\n    }\n\n    case 'u': {\n      if (input_format != input_format_t::bjdata) {\n        break;\n      }\n\n      std::uint16_t number{};\n\n      if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) {\n        return false;\n      }\n\n      result = static_cast<std::size_t>(number);\n      return true;\n    }\n\n    case 'm': {\n      if (input_format != input_format_t::bjdata) {\n        break;\n      }\n\n      std::uint32_t number{};\n\n      if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) {\n        return false;\n      }\n\n      result = conditional_static_cast<std::size_t>(number);\n      return true;\n    }\n\n    case 'M': {\n      if (input_format != input_format_t::bjdata) {\n        break;\n      }\n\n      std::uint64_t number{};\n\n      if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) {\n        return false;\n      }\n\n      if (!value_in_range_of<std::size_t>(number)) {\n        return sax->parse_error(chars_read, get_token_string(),\n                                out_of_range::create(408,\n                                    exception_message(input_format, \"integer value overflow\", \"size\"), nullptr));\n      }\n\n      result = detail::conditional_static_cast<std::size_t>(number);\n      return true;\n    }\n\n    case '[': {\n      if (input_format != input_format_t::bjdata) {\n        break;\n      }\n\n      if (is_ndarray) { // ndarray dimensional vector can only contain integers, and can not embed another array\n        return sax->parse_error(chars_read, get_token_string(), parse_error::create(113,\n                                chars_read, exception_message(input_format,\n                                    \"ndarray dimentional vector is not allowed\", \"size\"), nullptr));\n      }\n\n      std::vector<size_t> dim;\n\n      if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim))) {\n        return false;\n      }\n\n      if (dim.size() == 1 || (dim.size() == 2 &&\n                              dim.at(0) == 1)) { // return normal array size if 1D row vector\n        result = dim.at(dim.size() - 1);\n        return true;\n      }\n\n      if (!dim.empty()) { // if ndarray, convert to an object in JData annotated array format\n        for (auto i :\n             dim) { // test if any dimension in an ndarray is 0, if so, return a 1D empty container\n          if (i == 0) {\n            result = 0;\n            return true;\n          }\n        }\n\n        string_t key = \"_ArraySize_\";\n\n        if (JSON_HEDLEY_UNLIKELY(!sax->start_object(3) || !sax->key(key) ||\n                                 !sax->start_array(dim.size()))) {\n          return false;\n        }\n\n        result = 1;\n\n        for (auto i : dim) {\n          result *= i;\n\n          if (result == 0 ||\n              result == npos) { // because dim elements shall not have zeros, result = 0 means overflow happened; it also can't be npos as it is used to initialize size in get_ubjson_size_type()\n            return sax->parse_error(chars_read, get_token_string(),\n                                    out_of_range::create(408, exception_message(input_format,\n                                        \"excessive ndarray size caused overflow\", \"size\"), nullptr));\n          }\n\n          if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(static_cast<number_unsigned_t>\n                                   (i)))) {\n            return false;\n          }\n        }\n\n        is_ndarray = true;\n        return sax->end_array();\n      }\n\n      result = 0;\n      return true;\n    }\n\n    default:\n      break;\n    }\n\n    auto last_token = get_token_string();\n    std::string message;\n\n    if (input_format != input_format_t::bjdata) {\n      message = \"expected length type specification (U, i, I, l, L) after '#'; last byte: 0x\"\n                + last_token;\n    } else {\n      message = \"expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x\"\n                + last_token;\n    }\n\n    return sax->parse_error(chars_read, last_token, parse_error::create(113,\n                            chars_read, exception_message(input_format, message, \"size\"), nullptr));\n  }\n\n  /*!\n  @brief determine the type and size for a container\n\n  In the optimized UBJSON format, a type and a size can be provided to allow\n  for a more compact representation.\n\n  @param[out] result  pair of the size and the type\n  @param[in] inside_ndarray  whether the parser is parsing an ND array dimensional vector\n\n  @return whether pair creation completed\n  */\n  bool get_ubjson_size_type(std::pair<std::size_t, char_int_type>& result,\n                            bool inside_ndarray = false)\n  {\n    result.first = npos; // size\n    result.second = 0; // type\n    bool is_ndarray = false;\n    get_ignore_noop();\n\n    if (current == '$') {\n      result.second = get();  // must not ignore 'N', because 'N' maybe the type\n\n      if (input_format == input_format_t::bjdata\n          && JSON_HEDLEY_UNLIKELY(std::binary_search(bjd_optimized_type_markers.begin(),\n                                  bjd_optimized_type_markers.end(), result.second))) {\n        auto last_token = get_token_string();\n        return sax->parse_error(chars_read, last_token, parse_error::create(112,\n                                chars_read,\n                                exception_message(input_format, concat(\"marker 0x\", last_token,\n                                    \" is not a permitted optimized array type\"), \"type\"), nullptr));\n      }\n\n      if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"type\"))) {\n        return false;\n      }\n\n      get_ignore_noop();\n\n      if (JSON_HEDLEY_UNLIKELY(current != '#')) {\n        if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"value\"))) {\n          return false;\n        }\n\n        auto last_token = get_token_string();\n        return sax->parse_error(chars_read, last_token, parse_error::create(112,\n                                chars_read,\n                                exception_message(input_format,\n                                    concat(\"expected '#' after type information; last byte: 0x\", last_token),\n                                    \"size\"), nullptr));\n      }\n\n      bool is_error = get_ubjson_size_value(result.first, is_ndarray);\n\n      if (input_format == input_format_t::bjdata && is_ndarray) {\n        if (inside_ndarray) {\n          return sax->parse_error(chars_read, get_token_string(), parse_error::create(112,\n                                  chars_read,\n                                  exception_message(input_format, \"ndarray can not be recursive\", \"size\"),\n                                  nullptr));\n        }\n\n        result.second |= (1 <<\n                          8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters\n      }\n\n      return is_error;\n    }\n\n    if (current == '#') {\n      bool is_error = get_ubjson_size_value(result.first, is_ndarray);\n\n      if (input_format == input_format_t::bjdata && is_ndarray) {\n        return sax->parse_error(chars_read, get_token_string(), parse_error::create(112,\n                                chars_read,\n                                exception_message(input_format, \"ndarray requires both type and size\", \"size\"),\n                                nullptr));\n      }\n\n      return is_error;\n    }\n\n    return true;\n  }\n\n  /*!\n  @param prefix  the previously read or set type prefix\n  @return whether value creation completed\n  */\n  bool get_ubjson_value(const char_int_type prefix)\n  {\n    switch (prefix) {\n    case std::char_traits<char_type>::eof():  // EOF\n      return unexpect_eof(input_format, \"value\");\n\n    case 'T':  // true\n      return sax->boolean(true);\n\n    case 'F':  // false\n      return sax->boolean(false);\n\n    case 'Z':  // null\n      return sax->null();\n\n    case 'U': {\n      std::uint8_t number{};\n      return get_number(input_format, number) && sax->number_unsigned(number);\n    }\n\n    case 'i': {\n      std::int8_t number{};\n      return get_number(input_format, number) && sax->number_integer(number);\n    }\n\n    case 'I': {\n      std::int16_t number{};\n      return get_number(input_format, number) && sax->number_integer(number);\n    }\n\n    case 'l': {\n      std::int32_t number{};\n      return get_number(input_format, number) && sax->number_integer(number);\n    }\n\n    case 'L': {\n      std::int64_t number{};\n      return get_number(input_format, number) && sax->number_integer(number);\n    }\n\n    case 'u': {\n      if (input_format != input_format_t::bjdata) {\n        break;\n      }\n\n      std::uint16_t number{};\n      return get_number(input_format, number) && sax->number_unsigned(number);\n    }\n\n    case 'm': {\n      if (input_format != input_format_t::bjdata) {\n        break;\n      }\n\n      std::uint32_t number{};\n      return get_number(input_format, number) && sax->number_unsigned(number);\n    }\n\n    case 'M': {\n      if (input_format != input_format_t::bjdata) {\n        break;\n      }\n\n      std::uint64_t number{};\n      return get_number(input_format, number) && sax->number_unsigned(number);\n    }\n\n    case 'h': {\n      if (input_format != input_format_t::bjdata) {\n        break;\n      }\n\n      const auto byte1_raw = get();\n\n      if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"number\"))) {\n        return false;\n      }\n\n      const auto byte2_raw = get();\n\n      if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"number\"))) {\n        return false;\n      }\n\n      const auto byte1 = static_cast<unsigned char>(byte1_raw);\n      const auto byte2 = static_cast<unsigned char>(byte2_raw);\n      // code from RFC 7049, Appendix D, Figure 3:\n      // As half-precision floating-point numbers were only added\n      // to IEEE 754 in 2008, today's programming platforms often\n      // still only have limited support for them. It is very\n      // easy to include at least decoding support for them even\n      // without such support. An example of a small decoder for\n      // half-precision floating-point numbers in the C language\n      // is shown in Fig. 3.\n      const auto half = static_cast<unsigned int>((byte2 << 8u) + byte1);\n      const double val = [&half] {\n        const int exp = (half >> 10u) & 0x1Fu;\n        const unsigned int mant = half & 0x3FFu;\n        JSON_ASSERT(0 <= exp&& exp <= 32);\n        JSON_ASSERT(mant <= 1024);\n\n        switch (exp)\n        {\n        case 0:\n          return std::ldexp(mant, -24);\n\n        case 31:\n          return (mant == 0)\n          ? std::numeric_limits<double>::infinity()\n          : std::numeric_limits<double>::quiet_NaN();\n\n        default:\n          return std::ldexp(mant + 1024, exp - 25);\n        }\n      }();\n      return sax->number_float((half & 0x8000u) != 0\n                               ? static_cast<number_float_t>(-val)\n                               : static_cast<number_float_t>(val), \"\");\n    }\n\n    case 'd': {\n      float number{};\n      return get_number(input_format, number) &&\n             sax->number_float(static_cast<number_float_t>(number), \"\");\n    }\n\n    case 'D': {\n      double number{};\n      return get_number(input_format, number) &&\n             sax->number_float(static_cast<number_float_t>(number), \"\");\n    }\n\n    case 'H': {\n      return get_ubjson_high_precision_number();\n    }\n\n    case 'C': { // char\n      get();\n\n      if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"char\"))) {\n        return false;\n      }\n\n      if (JSON_HEDLEY_UNLIKELY(current > 127)) {\n        auto last_token = get_token_string();\n        return sax->parse_error(chars_read, last_token, parse_error::create(113,\n                                chars_read,\n                                exception_message(input_format,\n                                    concat(\"byte after 'C' must be in range 0x00..0x7F; last byte: 0x\", last_token),\n                                    \"char\"), nullptr));\n      }\n\n      string_t s(1, static_cast<typename string_t::value_type>(current));\n      return sax->string(s);\n    }\n\n    case 'S': { // string\n      string_t s;\n      return get_ubjson_string(s) && sax->string(s);\n    }\n\n    case '[':  // array\n      return get_ubjson_array();\n\n    case '{':  // object\n      return get_ubjson_object();\n\n    default: // anything else\n      break;\n    }\n\n    auto last_token = get_token_string();\n    return sax->parse_error(chars_read, last_token, parse_error::create(112,\n                            chars_read, exception_message(input_format, \"invalid byte: 0x\" + last_token,\n                                \"value\"), nullptr));\n  }\n\n  /*!\n  @return whether array creation completed\n  */\n  bool get_ubjson_array()\n  {\n    std::pair<std::size_t, char_int_type> size_and_type;\n\n    if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) {\n      return false;\n    }\n\n    // if bit-8 of size_and_type.second is set to 1, encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata):\n    // {\"_ArrayType_\" : \"typeid\", \"_ArraySize_\" : [n1, n2, ...], \"_ArrayData_\" : [v1, v2, ...]}\n\n    if (input_format == input_format_t::bjdata && size_and_type.first != npos &&\n        (size_and_type.second & (1 << 8)) != 0) {\n      size_and_type.second &= ~(static_cast<char_int_type>(1) <<\n                                8);  // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker\n      auto it = std::lower_bound(bjd_types_map.begin(), bjd_types_map.end(),\n      size_and_type.second, [](const bjd_type & p, char_int_type t) {\n        return p.first < t;\n      });\n      string_t key = \"_ArrayType_\";\n\n      if (JSON_HEDLEY_UNLIKELY(it == bjd_types_map.end() ||\n                               it->first != size_and_type.second)) {\n        auto last_token = get_token_string();\n        return sax->parse_error(chars_read, last_token, parse_error::create(112,\n                                chars_read,\n                                exception_message(input_format, \"invalid byte: 0x\" + last_token, \"type\"),\n                                nullptr));\n      }\n\n      string_t type = it->second; // sax->string() takes a reference\n\n      if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->string(type))) {\n        return false;\n      }\n\n      if (size_and_type.second == 'C') {\n        size_and_type.second = 'U';\n      }\n\n      key = \"_ArrayData_\";\n\n      if (JSON_HEDLEY_UNLIKELY(!sax->key(key) ||\n                               !sax->start_array(size_and_type.first))) {\n        return false;\n      }\n\n      for (std::size_t i = 0; i < size_and_type.first; ++i) {\n        if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) {\n          return false;\n        }\n      }\n\n      return (sax->end_array() && sax->end_object());\n    }\n\n    if (size_and_type.first != npos) {\n      if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) {\n        return false;\n      }\n\n      if (size_and_type.second != 0) {\n        if (size_and_type.second != 'N') {\n          for (std::size_t i = 0; i < size_and_type.first; ++i) {\n            if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) {\n              return false;\n            }\n          }\n        }\n      } else {\n        for (std::size_t i = 0; i < size_and_type.first; ++i) {\n          if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) {\n            return false;\n          }\n        }\n      }\n    } else {\n      if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast<std::size_t>(-1)))) {\n        return false;\n      }\n\n      while (current != ']') {\n        if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal(false))) {\n          return false;\n        }\n\n        get_ignore_noop();\n      }\n    }\n\n    return sax->end_array();\n  }\n\n  /*!\n  @return whether object creation completed\n  */\n  bool get_ubjson_object()\n  {\n    std::pair<std::size_t, char_int_type> size_and_type;\n\n    if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) {\n      return false;\n    }\n\n    // do not accept ND-array size in objects in BJData\n    if (input_format == input_format_t::bjdata && size_and_type.first != npos &&\n        (size_and_type.second & (1 << 8)) != 0) {\n      auto last_token = get_token_string();\n      return sax->parse_error(chars_read, last_token, parse_error::create(112,\n                              chars_read,\n                              exception_message(input_format,\n                                  \"BJData object does not support ND-array size in optimized format\", \"object\"),\n                              nullptr));\n    }\n\n    string_t key;\n\n    if (size_and_type.first != npos) {\n      if (JSON_HEDLEY_UNLIKELY(!sax->start_object(size_and_type.first))) {\n        return false;\n      }\n\n      if (size_and_type.second != 0) {\n        for (std::size_t i = 0; i < size_and_type.first; ++i) {\n          if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) {\n            return false;\n          }\n\n          if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) {\n            return false;\n          }\n\n          key.clear();\n        }\n      } else {\n        for (std::size_t i = 0; i < size_and_type.first; ++i) {\n          if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) {\n            return false;\n          }\n\n          if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) {\n            return false;\n          }\n\n          key.clear();\n        }\n      }\n    } else {\n      if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast<std::size_t>(-1)))) {\n        return false;\n      }\n\n      while (current != '}') {\n        if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false) || !sax->key(key))) {\n          return false;\n        }\n\n        if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) {\n          return false;\n        }\n\n        get_ignore_noop();\n        key.clear();\n      }\n    }\n\n    return sax->end_object();\n  }\n\n  // Note, no reader for UBJSON binary types is implemented because they do\n  // not exist\n\n  bool get_ubjson_high_precision_number()\n  {\n    // get size of following number string\n    std::size_t size{};\n    bool no_ndarray = true;\n    auto res = get_ubjson_size_value(size, no_ndarray);\n\n    if (JSON_HEDLEY_UNLIKELY(!res)) {\n      return res;\n    }\n\n    // get number string\n    std::vector<char> number_vector;\n\n    for (std::size_t i = 0; i < size; ++i) {\n      get();\n\n      if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"number\"))) {\n        return false;\n      }\n\n      number_vector.push_back(static_cast<char>(current));\n    }\n\n    // parse number string\n    using ia_type = decltype(detail::input_adapter(number_vector));\n    auto number_lexer = detail::lexer<BasicJsonType, ia_type>(detail::input_adapter(\n                          number_vector), false);\n    const auto result_number = number_lexer.scan();\n    const auto number_string = number_lexer.get_token_string();\n    const auto result_remainder = number_lexer.scan();\n    using token_type = typename detail::lexer_base<BasicJsonType>::token_type;\n\n    if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input)) {\n      return sax->parse_error(chars_read, number_string, parse_error::create(115,\n                              chars_read,\n                              exception_message(input_format, concat(\"invalid number text: \",\n                                  number_lexer.get_token_string()), \"high-precision number\"), nullptr));\n    }\n\n    switch (result_number) {\n    case token_type::value_integer:\n      return sax->number_integer(number_lexer.get_number_integer());\n\n    case token_type::value_unsigned:\n      return sax->number_unsigned(number_lexer.get_number_unsigned());\n\n    case token_type::value_float:\n      return sax->number_float(number_lexer.get_number_float(),\n                               std::move(number_string));\n\n    case token_type::uninitialized:\n    case token_type::literal_true:\n    case token_type::literal_false:\n    case token_type::literal_null:\n    case token_type::value_string:\n    case token_type::begin_array:\n    case token_type::begin_object:\n    case token_type::end_array:\n    case token_type::end_object:\n    case token_type::name_separator:\n    case token_type::value_separator:\n    case token_type::parse_error:\n    case token_type::end_of_input:\n    case token_type::literal_or_value:\n    default:\n      return sax->parse_error(chars_read, number_string, parse_error::create(115,\n                              chars_read,\n                              exception_message(input_format, concat(\"invalid number text: \",\n                                  number_lexer.get_token_string()), \"high-precision number\"), nullptr));\n    }\n  }\n\n  ///////////////////////\n  // Utility functions //\n  ///////////////////////\n\n  /*!\n  @brief get next character from the input\n\n  This function provides the interface to the used input adapter. It does\n  not throw in case the input reached EOF, but returns a -'ve valued\n  `std::char_traits<char_type>::eof()` in that case.\n\n  @return character read from the input\n  */\n  char_int_type get()\n  {\n    ++chars_read;\n    return current = ia.get_character();\n  }\n\n  /*!\n  @return character read from the input after ignoring all 'N' entries\n  */\n  char_int_type get_ignore_noop()\n  {\n    do {\n      get();\n    } while (current == 'N');\n\n    return current;\n  }\n\n  /*\n  @brief read a number from the input\n\n  @tparam NumberType the type of the number\n  @param[in] format   the current format (for diagnostics)\n  @param[out] result  number of type @a NumberType\n\n  @return whether conversion completed\n\n  @note This function needs to respect the system's endianness, because\n        bytes in CBOR, MessagePack, and UBJSON are stored in network order\n        (big endian) and therefore need reordering on little endian systems.\n        On the other hand, BSON and BJData use little endian and should reorder\n        on big endian systems.\n  */\n  template<typename NumberType, bool InputIsLittleEndian = false>\n  bool get_number(const input_format_t format, NumberType& result)\n  {\n    // step 1: read input into array with system's byte order\n    std::array<std::uint8_t, sizeof(NumberType)> vec{};\n\n    for (std::size_t i = 0; i < sizeof(NumberType); ++i) {\n      get();\n\n      if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, \"number\"))) {\n        return false;\n      }\n\n      // reverse byte order prior to conversion if necessary\n      if (is_little_endian != (InputIsLittleEndian ||\n                               format == input_format_t::bjdata)) {\n        vec[sizeof(NumberType) - i - 1] = static_cast<std::uint8_t>(current);\n      } else {\n        vec[i] = static_cast<std::uint8_t>(current); // LCOV_EXCL_LINE\n      }\n    }\n\n    // step 2: convert array into number of type T and return\n    std::memcpy(&result, vec.data(), sizeof(NumberType));\n    return true;\n  }\n\n  /*!\n  @brief create a string by reading characters from the input\n\n  @tparam NumberType the type of the number\n  @param[in] format the current format (for diagnostics)\n  @param[in] len number of characters to read\n  @param[out] result string created by reading @a len bytes\n\n  @return whether string creation completed\n\n  @note We can not reserve @a len bytes for the result, because @a len\n        may be too large. Usually, @ref unexpect_eof() detects the end of\n        the input before we run out of string memory.\n  */\n  template<typename NumberType>\n  bool get_string(const input_format_t format,\n                  const NumberType len,\n                  string_t& result)\n  {\n    bool success = true;\n\n    for (NumberType i = 0; i < len; i++) {\n      get();\n\n      if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, \"string\"))) {\n        success = false;\n        break;\n      }\n\n      result.push_back(static_cast<typename string_t::value_type>(current));\n    }\n\n    return success;\n  }\n\n  /*!\n  @brief create a byte array by reading bytes from the input\n\n  @tparam NumberType the type of the number\n  @param[in] format the current format (for diagnostics)\n  @param[in] len number of bytes to read\n  @param[out] result byte array created by reading @a len bytes\n\n  @return whether byte array creation completed\n\n  @note We can not reserve @a len bytes for the result, because @a len\n        may be too large. Usually, @ref unexpect_eof() detects the end of\n        the input before we run out of memory.\n  */\n  template<typename NumberType>\n  bool get_binary(const input_format_t format,\n                  const NumberType len,\n                  binary_t& result)\n  {\n    bool success = true;\n\n    for (NumberType i = 0; i < len; i++) {\n      get();\n\n      if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, \"binary\"))) {\n        success = false;\n        break;\n      }\n\n      result.push_back(static_cast<std::uint8_t>(current));\n    }\n\n    return success;\n  }\n\n  /*!\n  @param[in] format   the current format (for diagnostics)\n  @param[in] context  further context information (for diagnostics)\n  @return whether the last read character is not EOF\n  */\n  JSON_HEDLEY_NON_NULL(3)\n  bool unexpect_eof(const input_format_t format, const char* context) const\n  {\n    if (JSON_HEDLEY_UNLIKELY(current == std::char_traits<char_type>::eof())) {\n      return sax->parse_error(chars_read, \"<end of file>\",\n                              parse_error::create(110, chars_read, exception_message(format,\n                                  \"unexpected end of input\", context), nullptr));\n    }\n\n    return true;\n  }\n\n  /*!\n  @return a string representation of the last read byte\n  */\n  std::string get_token_string() const\n  {\n    std::array<char, 3> cr{{}};\n    static_cast<void>((std::snprintf)(cr.data(), cr.size(), \"%.2hhX\",\n                                      static_cast<unsigned char>\n                                      (current))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n    return std::string{cr.data()};\n  }\n\n  /*!\n  @param[in] format   the current format\n  @param[in] detail   a detailed error message\n  @param[in] context  further context information\n  @return a message string to use in the parse_error exceptions\n  */\n  std::string exception_message(const input_format_t format,\n                                const std::string& detail,\n                                const std::string& context) const\n  {\n    std::string error_msg = \"syntax error while parsing \";\n\n    switch (format) {\n    case input_format_t::cbor:\n      error_msg += \"CBOR\";\n      break;\n\n    case input_format_t::msgpack:\n      error_msg += \"MessagePack\";\n      break;\n\n    case input_format_t::ubjson:\n      error_msg += \"UBJSON\";\n      break;\n\n    case input_format_t::bson:\n      error_msg += \"BSON\";\n      break;\n\n    case input_format_t::bjdata:\n      error_msg += \"BJData\";\n      break;\n\n    case input_format_t::json: // LCOV_EXCL_LINE\n    default:            // LCOV_EXCL_LINE\n      JSON_ASSERT(\n        false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n    }\n\n    return concat(error_msg, ' ', context, \": \", detail);\n  }\n\nprivate:\n  static JSON_INLINE_VARIABLE constexpr std::size_t npos =\n    static_cast<std::size_t>(-1);\n\n  /// input adapter\n  InputAdapterType ia;\n\n  /// the current character\n  char_int_type current = std::char_traits<char_type>::eof();\n\n  /// the number of characters read\n  std::size_t chars_read = 0;\n\n  /// whether we can assume little endianness\n  const bool is_little_endian = little_endianness();\n\n  /// input format\n  const input_format_t input_format = input_format_t::json;\n\n  /// the SAX parser\n  json_sax_t* sax = nullptr;\n\n  // excluded markers in bjdata optimized type\n#define JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_ \\\n    make_array<char_int_type>('F', 'H', 'N', 'S', 'T', 'Z', '[', '{')\n\n#define JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_ \\\n    make_array<bjd_type>(                      \\\n    bjd_type{'C', \"char\"},                     \\\n    bjd_type{'D', \"double\"},                   \\\n    bjd_type{'I', \"int16\"},                    \\\n    bjd_type{'L', \"int64\"},                    \\\n    bjd_type{'M', \"uint64\"},                   \\\n    bjd_type{'U', \"uint8\"},                    \\\n    bjd_type{'d', \"single\"},                   \\\n    bjd_type{'i', \"int8\"},                     \\\n    bjd_type{'l', \"int32\"},                    \\\n    bjd_type{'m', \"uint32\"},                   \\\n    bjd_type{'u', \"uint16\"})\n\nJSON_PRIVATE_UNLESS_TESTED:\n  // lookup tables\n  // NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes)\n  const decltype(JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_)\n  bjd_optimized_type_markers =\n    JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_;\n\n  using bjd_type = std::pair<char_int_type, string_t>;\n  // NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes)\n  const decltype(JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_) bjd_types_map =\n    JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_;\n\n#undef JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_\n#undef JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_\n};\n\n#ifndef JSON_HAS_CPP_17\ntemplate<typename BasicJsonType, typename InputAdapterType, typename SAX>\nconstexpr std::size_t binary_reader<BasicJsonType, InputAdapterType, SAX>::npos;\n#endif\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/input/input_adapters.hpp>\n\n// #include <nlohmann/detail/input/lexer.hpp>\n\n// #include <nlohmann/detail/input/parser.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cmath> // isfinite\n#include <cstdint> // uint8_t\n#include <functional> // function\n#include <string> // string\n#include <utility> // move\n#include <vector> // vector\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/input/input_adapters.hpp>\n\n// #include <nlohmann/detail/input/json_sax.hpp>\n\n// #include <nlohmann/detail/input/lexer.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/is_sax.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n////////////\n// parser //\n////////////\n\nenum class parse_event_t : std::uint8_t {\n  /// the parser read `{` and started to process a JSON object\n  object_start,\n  /// the parser read `}` and finished processing a JSON object\n  object_end,\n  /// the parser read `[` and started to process a JSON array\n  array_start,\n  /// the parser read `]` and finished processing a JSON array\n  array_end,\n  /// the parser read a key of a value in an object\n  key,\n  /// the parser finished reading a JSON value\n  value\n};\n\ntemplate<typename BasicJsonType>\nusing parser_callback_t =\n  std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;\n\n/*!\n@brief syntax analysis\n\nThis class implements a recursive descent parser.\n*/\ntemplate<typename BasicJsonType, typename InputAdapterType>\nclass parser\n{\n  using number_integer_t = typename BasicJsonType::number_integer_t;\n  using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n  using number_float_t = typename BasicJsonType::number_float_t;\n  using string_t = typename BasicJsonType::string_t;\n  using lexer_t = lexer<BasicJsonType, InputAdapterType>;\n  using token_type = typename lexer_t::token_type;\n\npublic:\n  /// a parser reading from an input adapter\n  explicit parser(InputAdapterType&& adapter,\n                  const parser_callback_t<BasicJsonType> cb = nullptr,\n                  const bool allow_exceptions_ = true,\n                  const bool skip_comments = false)\n    : callback(cb)\n    , m_lexer(std::move(adapter), skip_comments)\n    , allow_exceptions(allow_exceptions_)\n  {\n    // read first token\n    get_token();\n  }\n\n  /*!\n  @brief public parser interface\n\n  @param[in] strict      whether to expect the last token to be EOF\n  @param[in,out] result  parsed JSON value\n\n  @throw parse_error.101 in case of an unexpected token\n  @throw parse_error.102 if to_unicode fails or surrogate error\n  @throw parse_error.103 if to_unicode fails\n  */\n  void parse(const bool strict, BasicJsonType& result)\n  {\n    if (callback) {\n      json_sax_dom_callback_parser<BasicJsonType> sdp(result, callback,\n          allow_exceptions);\n      sax_parse_internal(&sdp);\n\n      // in strict mode, input must be completely read\n      if (strict && (get_token() != token_type::end_of_input)) {\n        sdp.parse_error(m_lexer.get_position(),\n                        m_lexer.get_token_string(),\n                        parse_error::create(101, m_lexer.get_position(),\n                                            exception_message(token_type::end_of_input, \"value\"), nullptr));\n      }\n\n      // in case of an error, return discarded value\n      if (sdp.is_errored()) {\n        result = value_t::discarded;\n        return;\n      }\n\n      // set top-level value to null if it was discarded by the callback\n      // function\n      if (result.is_discarded()) {\n        result = nullptr;\n      }\n    } else {\n      json_sax_dom_parser<BasicJsonType> sdp(result, allow_exceptions);\n      sax_parse_internal(&sdp);\n\n      // in strict mode, input must be completely read\n      if (strict && (get_token() != token_type::end_of_input)) {\n        sdp.parse_error(m_lexer.get_position(),\n                        m_lexer.get_token_string(),\n                        parse_error::create(101, m_lexer.get_position(),\n                                            exception_message(token_type::end_of_input, \"value\"), nullptr));\n      }\n\n      // in case of an error, return discarded value\n      if (sdp.is_errored()) {\n        result = value_t::discarded;\n        return;\n      }\n    }\n\n    result.assert_invariant();\n  }\n\n  /*!\n  @brief public accept interface\n\n  @param[in] strict  whether to expect the last token to be EOF\n  @return whether the input is a proper JSON text\n  */\n  bool accept(const bool strict = true)\n  {\n    json_sax_acceptor<BasicJsonType> sax_acceptor;\n    return sax_parse(&sax_acceptor, strict);\n  }\n\n  template<typename SAX>\n  JSON_HEDLEY_NON_NULL(2)\n  bool sax_parse(SAX* sax, const bool strict = true)\n  {\n    (void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};\n    const bool result = sax_parse_internal(sax);\n\n    // strict mode: next byte must be EOF\n    if (result && strict && (get_token() != token_type::end_of_input)) {\n      return sax->parse_error(m_lexer.get_position(),\n                              m_lexer.get_token_string(),\n                              parse_error::create(101, m_lexer.get_position(),\n                                  exception_message(token_type::end_of_input, \"value\"), nullptr));\n    }\n\n    return result;\n  }\n\nprivate:\n  template<typename SAX>\n  JSON_HEDLEY_NON_NULL(2)\n  bool sax_parse_internal(SAX* sax)\n  {\n    // stack to remember the hierarchy of structured values we are parsing\n    // true = array; false = object\n    std::vector<bool> states;\n    // value to avoid a goto (see comment where set to true)\n    bool skip_to_state_evaluation = false;\n\n    while (true) {\n      if (!skip_to_state_evaluation) {\n        // invariant: get_token() was called before each iteration\n        switch (last_token) {\n        case token_type::begin_object: {\n          if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast<std::size_t>(-1)))) {\n            return false;\n          }\n\n          // closing } -> we are done\n          if (get_token() == token_type::end_object) {\n            if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) {\n              return false;\n            }\n\n            break;\n          }\n\n          // parse key\n          if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) {\n            return sax->parse_error(m_lexer.get_position(),\n                                    m_lexer.get_token_string(),\n                                    parse_error::create(101, m_lexer.get_position(),\n                                        exception_message(token_type::value_string, \"object key\"), nullptr));\n          }\n\n          if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) {\n            return false;\n          }\n\n          // parse separator (:)\n          if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) {\n            return sax->parse_error(m_lexer.get_position(),\n                                    m_lexer.get_token_string(),\n                                    parse_error::create(101, m_lexer.get_position(),\n                                        exception_message(token_type::name_separator, \"object separator\"), nullptr));\n          }\n\n          // remember we are now inside an object\n          states.push_back(false);\n          // parse values\n          get_token();\n          continue;\n        }\n\n        case token_type::begin_array: {\n          if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast<std::size_t>(-1)))) {\n            return false;\n          }\n\n          // closing ] -> we are done\n          if (get_token() == token_type::end_array) {\n            if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) {\n              return false;\n            }\n\n            break;\n          }\n\n          // remember we are now inside an array\n          states.push_back(true);\n          // parse values (no need to call get_token)\n          continue;\n        }\n\n        case token_type::value_float: {\n          const auto res = m_lexer.get_number_float();\n\n          if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res))) {\n            return sax->parse_error(m_lexer.get_position(),\n                                    m_lexer.get_token_string(),\n                                    out_of_range::create(406, concat(\"number overflow parsing '\",\n                                        m_lexer.get_token_string(), '\\''), nullptr));\n          }\n\n          if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) {\n            return false;\n          }\n\n          break;\n        }\n\n        case token_type::literal_false: {\n          if (JSON_HEDLEY_UNLIKELY(!sax->boolean(false))) {\n            return false;\n          }\n\n          break;\n        }\n\n        case token_type::literal_null: {\n          if (JSON_HEDLEY_UNLIKELY(!sax->null())) {\n            return false;\n          }\n\n          break;\n        }\n\n        case token_type::literal_true: {\n          if (JSON_HEDLEY_UNLIKELY(!sax->boolean(true))) {\n            return false;\n          }\n\n          break;\n        }\n\n        case token_type::value_integer: {\n          if (JSON_HEDLEY_UNLIKELY(!sax->number_integer(m_lexer.get_number_integer()))) {\n            return false;\n          }\n\n          break;\n        }\n\n        case token_type::value_string: {\n          if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string()))) {\n            return false;\n          }\n\n          break;\n        }\n\n        case token_type::value_unsigned: {\n          if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(\n                                     m_lexer.get_number_unsigned()))) {\n            return false;\n          }\n\n          break;\n        }\n\n        case token_type::parse_error: {\n          // using \"uninitialized\" to avoid \"expected\" message\n          return sax->parse_error(m_lexer.get_position(),\n                                  m_lexer.get_token_string(),\n                                  parse_error::create(101, m_lexer.get_position(),\n                                      exception_message(token_type::uninitialized, \"value\"), nullptr));\n        }\n\n        case token_type::uninitialized:\n        case token_type::end_array:\n        case token_type::end_object:\n        case token_type::name_separator:\n        case token_type::value_separator:\n        case token_type::end_of_input:\n        case token_type::literal_or_value:\n        default: { // the last token was unexpected\n          return sax->parse_error(m_lexer.get_position(),\n                                  m_lexer.get_token_string(),\n                                  parse_error::create(101, m_lexer.get_position(),\n                                      exception_message(token_type::literal_or_value, \"value\"), nullptr));\n        }\n        }\n      } else {\n        skip_to_state_evaluation = false;\n      }\n\n      // we reached this line after we successfully parsed a value\n      if (states.empty()) {\n        // empty stack: we reached the end of the hierarchy: done\n        return true;\n      }\n\n      if (states.back()) { // array\n        // comma -> next value\n        if (get_token() == token_type::value_separator) {\n          // parse a new value\n          get_token();\n          continue;\n        }\n\n        // closing ]\n        if (JSON_HEDLEY_LIKELY(last_token == token_type::end_array)) {\n          if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) {\n            return false;\n          }\n\n          // We are done with this array. Before we can parse a\n          // new value, we need to evaluate the new state first.\n          // By setting skip_to_state_evaluation to false, we\n          // are effectively jumping to the beginning of this if.\n          JSON_ASSERT(!states.empty());\n          states.pop_back();\n          skip_to_state_evaluation = true;\n          continue;\n        }\n\n        return sax->parse_error(m_lexer.get_position(),\n                                m_lexer.get_token_string(),\n                                parse_error::create(101, m_lexer.get_position(),\n                                    exception_message(token_type::end_array, \"array\"), nullptr));\n      }\n\n      // states.back() is false -> object\n\n      // comma -> next value\n      if (get_token() == token_type::value_separator) {\n        // parse key\n        if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::value_string)) {\n          return sax->parse_error(m_lexer.get_position(),\n                                  m_lexer.get_token_string(),\n                                  parse_error::create(101, m_lexer.get_position(),\n                                      exception_message(token_type::value_string, \"object key\"), nullptr));\n        }\n\n        if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) {\n          return false;\n        }\n\n        // parse separator (:)\n        if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) {\n          return sax->parse_error(m_lexer.get_position(),\n                                  m_lexer.get_token_string(),\n                                  parse_error::create(101, m_lexer.get_position(),\n                                      exception_message(token_type::name_separator, \"object separator\"), nullptr));\n        }\n\n        // parse values\n        get_token();\n        continue;\n      }\n\n      // closing }\n      if (JSON_HEDLEY_LIKELY(last_token == token_type::end_object)) {\n        if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) {\n          return false;\n        }\n\n        // We are done with this object. Before we can parse a\n        // new value, we need to evaluate the new state first.\n        // By setting skip_to_state_evaluation to false, we\n        // are effectively jumping to the beginning of this if.\n        JSON_ASSERT(!states.empty());\n        states.pop_back();\n        skip_to_state_evaluation = true;\n        continue;\n      }\n\n      return sax->parse_error(m_lexer.get_position(),\n                              m_lexer.get_token_string(),\n                              parse_error::create(101, m_lexer.get_position(),\n                                  exception_message(token_type::end_object, \"object\"), nullptr));\n    }\n  }\n\n  /// get next token from lexer\n  token_type get_token()\n  {\n    return last_token = m_lexer.scan();\n  }\n\n  std::string exception_message(const token_type expected,\n                                const std::string& context)\n  {\n    std::string error_msg = \"syntax error \";\n\n    if (!context.empty()) {\n      error_msg += concat(\"while parsing \", context, ' ');\n    }\n\n    error_msg += \"- \";\n\n    if (last_token == token_type::parse_error) {\n      error_msg += concat(m_lexer.get_error_message(), \"; last read: '\",\n                          m_lexer.get_token_string(), '\\'');\n    } else {\n      error_msg += concat(\"unexpected \", lexer_t::token_type_name(last_token));\n    }\n\n    if (expected != token_type::uninitialized) {\n      error_msg += concat(\"; expected \", lexer_t::token_type_name(expected));\n    }\n\n    return error_msg;\n  }\n\nprivate:\n  /// callback function\n  const parser_callback_t<BasicJsonType> callback = nullptr;\n  /// the type of the last read token\n  token_type last_token = token_type::uninitialized;\n  /// the lexer\n  lexer_t m_lexer;\n  /// whether to throw exceptions in case of errors\n  const bool allow_exceptions = true;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/iterators/internal_iterator.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/iterators/primitive_iterator.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef> // ptrdiff_t\n#include <limits>  // numeric_limits\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/*\n@brief an iterator for primitive JSON types\n\nThis class models an iterator for primitive JSON types (boolean, number,\nstring). It's only purpose is to allow the iterator/const_iterator classes\nto \"iterate\" over primitive values. Internally, the iterator is modeled by\na `difference_type` variable. Value begin_value (`0`) models the begin,\nend_value (`1`) models past the end.\n*/\nclass primitive_iterator_t\n{\nprivate:\n  using difference_type = std::ptrdiff_t;\n  static constexpr difference_type begin_value = 0;\n  static constexpr difference_type end_value = begin_value + 1;\n\nJSON_PRIVATE_UNLESS_TESTED:\n  /// iterator as signed integer type\n  difference_type m_it = (std::numeric_limits<std::ptrdiff_t>::min)();\n\npublic:\n  constexpr difference_type get_value() const noexcept\n  {\n    return m_it;\n  }\n\n  /// set iterator to a defined beginning\n  void set_begin() noexcept\n  {\n    m_it = begin_value;\n  }\n\n  /// set iterator to a defined past the end\n  void set_end() noexcept\n  {\n    m_it = end_value;\n  }\n\n  /// return whether the iterator can be dereferenced\n  constexpr bool is_begin() const noexcept\n  {\n    return m_it == begin_value;\n  }\n\n  /// return whether the iterator is at end\n  constexpr bool is_end() const noexcept\n  {\n    return m_it == end_value;\n  }\n\n  friend constexpr bool operator==(primitive_iterator_t lhs,\n                                   primitive_iterator_t rhs) noexcept\n  {\n    return lhs.m_it == rhs.m_it;\n  }\n\n  friend constexpr bool operator<(primitive_iterator_t lhs,\n                                  primitive_iterator_t rhs) noexcept\n  {\n    return lhs.m_it < rhs.m_it;\n  }\n\n  primitive_iterator_t operator+(difference_type n) noexcept\n  {\n    auto result = *this;\n    result += n;\n    return result;\n  }\n\n  friend constexpr difference_type operator-(primitive_iterator_t lhs,\n      primitive_iterator_t rhs) noexcept\n  {\n    return lhs.m_it - rhs.m_it;\n  }\n\n  primitive_iterator_t& operator++() noexcept\n  {\n    ++m_it;\n    return *this;\n  }\n\n  primitive_iterator_t operator++(int)& noexcept { // NOLINT(cert-dcl21-cpp)\n    auto result = *this;\n    ++m_it;\n    return result;\n  }\n\n  primitive_iterator_t& operator--() noexcept\n  {\n    --m_it;\n    return *this;\n  }\n\n  primitive_iterator_t operator--(int)& noexcept { // NOLINT(cert-dcl21-cpp)\n    auto result = *this;\n    --m_it;\n    return result;\n  }\n\n  primitive_iterator_t& operator+=(difference_type n) noexcept\n  {\n    m_it += n;\n    return *this;\n  }\n\n  primitive_iterator_t& operator-=(difference_type n) noexcept\n  {\n    m_it -= n;\n    return *this;\n  }\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/*!\n@brief an iterator value\n\n@note This structure could easily be a union, but MSVC currently does not allow\nunions members with complex constructors, see https://github.com/nlohmann/json/pull/105.\n*/\ntemplate<typename BasicJsonType> struct internal_iterator {\n  /// iterator for JSON objects\n  typename BasicJsonType::object_t::iterator object_iterator {};\n  /// iterator for JSON arrays\n  typename BasicJsonType::array_t::iterator array_iterator {};\n  /// generic iterator for all other types\n  primitive_iterator_t primitive_iterator {};\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/iterators/iter_impl.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <iterator> // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next\n#include <type_traits> // conditional, is_const, remove_const\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/iterators/internal_iterator.hpp>\n\n// #include <nlohmann/detail/iterators/primitive_iterator.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n// forward declare, to be able to friend it later on\ntemplate<typename IteratorType> class iteration_proxy;\ntemplate<typename IteratorType> class iteration_proxy_value;\n\n/*!\n@brief a template for a bidirectional iterator for the @ref basic_json class\nThis class implements a both iterators (iterator and const_iterator) for the\n@ref basic_json class.\n@note An iterator is called *initialized* when a pointer to a JSON value has\n      been set (e.g., by a constructor or a copy assignment). If the iterator is\n      default-constructed, it is *uninitialized* and most methods are undefined.\n      **The library uses assertions to detect calls on uninitialized iterators.**\n@requirement The class satisfies the following concept requirements:\n-\n[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator):\n  The iterator that can be moved can be moved in both directions (i.e.\n  incremented and decremented).\n@since version 1.0.0, simplified in version 2.0.9, change to bidirectional\n       iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593)\n*/\ntemplate<typename BasicJsonType>\nclass iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions)\n{\n  /// the iterator with BasicJsonType of different const-ness\n  using other_iter_impl =\n    iter_impl<typename std::conditional<std::is_const<BasicJsonType>::value, typename std::remove_const<BasicJsonType>::type, const BasicJsonType>::type>;\n  /// allow basic_json to access private members\n  friend other_iter_impl;\n  friend BasicJsonType;\n  friend iteration_proxy<iter_impl>;\n  friend iteration_proxy_value<iter_impl>;\n\n  using object_t = typename BasicJsonType::object_t;\n  using array_t = typename BasicJsonType::array_t;\n  // make sure BasicJsonType is basic_json or const basic_json\n  static_assert(\n    is_basic_json<typename std::remove_const<BasicJsonType>::type>::value,\n    \"iter_impl only accepts (const) basic_json\");\n  // superficial check for the LegacyBidirectionalIterator named requirement\n  static_assert(\n    std::is_base_of<std::bidirectional_iterator_tag, std::bidirectional_iterator_tag>::value\n    &&  std::is_base_of<std::bidirectional_iterator_tag, typename std::iterator_traits<typename array_t::iterator>::iterator_category>::value,\n    \"basic_json iterator assumes array and object type iterators satisfy the LegacyBidirectionalIterator named requirement.\");\n\npublic:\n  /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17.\n  /// The C++ Standard has never required user-defined iterators to derive from std::iterator.\n  /// A user-defined iterator should provide publicly accessible typedefs named\n  /// iterator_category, value_type, difference_type, pointer, and reference.\n  /// Note that value_type is required to be non-const, even for constant iterators.\n  using iterator_category = std::bidirectional_iterator_tag;\n\n  /// the type of the values when the iterator is dereferenced\n  using value_type = typename BasicJsonType::value_type;\n  /// a type to represent differences between iterators\n  using difference_type = typename BasicJsonType::difference_type;\n  /// defines a pointer to the type iterated over (value_type)\n  using pointer = typename std::conditional<std::is_const<BasicJsonType>::value,\n        typename BasicJsonType::const_pointer,\n        typename BasicJsonType::pointer>::type;\n  /// defines a reference to the type iterated over (value_type)\n  using reference =\n    typename std::conditional<std::is_const<BasicJsonType>::value,\n    typename BasicJsonType::const_reference,\n    typename BasicJsonType::reference>::type;\n\n  iter_impl() = default;\n  ~iter_impl() = default;\n  iter_impl(iter_impl&&) noexcept = default;\n  iter_impl& operator=(iter_impl&&) noexcept = default;\n\n  /*!\n  @brief constructor for a given JSON instance\n  @param[in] object  pointer to a JSON object for this iterator\n  @pre object != nullptr\n  @post The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  explicit iter_impl(pointer object) noexcept : m_object(object)\n  {\n    JSON_ASSERT(m_object != nullptr);\n\n    switch (m_object->m_type) {\n    case value_t::object: {\n      m_it.object_iterator = typename object_t::iterator();\n      break;\n    }\n\n    case value_t::array: {\n      m_it.array_iterator = typename array_t::iterator();\n      break;\n    }\n\n    case value_t::null:\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default: {\n      m_it.primitive_iterator = primitive_iterator_t();\n      break;\n    }\n    }\n  }\n\n  /*!\n  @note The conventional copy constructor and copy assignment are implicitly\n        defined. Combined with the following converting constructor and\n        assignment, they support: (1) copy from iterator to iterator, (2)\n        copy from const iterator to const iterator, and (3) conversion from\n        iterator to const iterator. However conversion from const iterator\n        to iterator is not defined.\n  */\n\n  /*!\n  @brief const copy constructor\n  @param[in] other const iterator to copy from\n  @note This copy constructor had to be defined explicitly to circumvent a bug\n        occurring on msvc v19.0 compiler (VS 2015) debug build. For more\n        information refer to: https://github.com/nlohmann/json/issues/1608\n  */\n  iter_impl(const iter_impl<const BasicJsonType>& other) noexcept\n    : m_object(other.m_object), m_it(other.m_it)\n  {}\n\n  /*!\n  @brief converting assignment\n  @param[in] other const iterator to copy from\n  @return const/non-const iterator\n  @note It is not checked whether @a other is initialized.\n  */\n  iter_impl& operator=(const iter_impl<const BasicJsonType>& other) noexcept\n  {\n    if (&other != this) {\n      m_object = other.m_object;\n      m_it = other.m_it;\n    }\n\n    return *this;\n  }\n\n  /*!\n  @brief converting constructor\n  @param[in] other  non-const iterator to copy from\n  @note It is not checked whether @a other is initialized.\n  */\n  iter_impl(const iter_impl<typename std::remove_const<BasicJsonType>::type>&\n            other) noexcept\n    : m_object(other.m_object), m_it(other.m_it)\n  {}\n\n  /*!\n  @brief converting assignment\n  @param[in] other  non-const iterator to copy from\n  @return const/non-const iterator\n  @note It is not checked whether @a other is initialized.\n  */\n  iter_impl& operator=(const\n                       iter_impl<typename std::remove_const<BasicJsonType>::type>& other)\n  noexcept // NOLINT(cert-oop54-cpp)\n  {\n    m_object = other.m_object;\n    m_it = other.m_it;\n    return *this;\n  }\n\nJSON_PRIVATE_UNLESS_TESTED:\n  /*!\n  @brief set the iterator to the first value\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  void set_begin() noexcept\n  {\n    JSON_ASSERT(m_object != nullptr);\n\n    switch (m_object->m_type) {\n    case value_t::object: {\n      m_it.object_iterator = m_object->m_value.object->begin();\n      break;\n    }\n\n    case value_t::array: {\n      m_it.array_iterator = m_object->m_value.array->begin();\n      break;\n    }\n\n    case value_t::null: {\n      // set to end so begin()==end() is true: null is empty\n      m_it.primitive_iterator.set_end();\n      break;\n    }\n\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default: {\n      m_it.primitive_iterator.set_begin();\n      break;\n    }\n    }\n  }\n\n  /*!\n  @brief set the iterator past the last value\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  void set_end() noexcept\n  {\n    JSON_ASSERT(m_object != nullptr);\n\n    switch (m_object->m_type) {\n    case value_t::object: {\n      m_it.object_iterator = m_object->m_value.object->end();\n      break;\n    }\n\n    case value_t::array: {\n      m_it.array_iterator = m_object->m_value.array->end();\n      break;\n    }\n\n    case value_t::null:\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default: {\n      m_it.primitive_iterator.set_end();\n      break;\n    }\n    }\n  }\n\npublic:\n  /*!\n  @brief return a reference to the value pointed to by the iterator\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  reference operator*() const\n  {\n    JSON_ASSERT(m_object != nullptr);\n\n    switch (m_object->m_type) {\n    case value_t::object: {\n      JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end());\n      return m_it.object_iterator->second;\n    }\n\n    case value_t::array: {\n      JSON_ASSERT(m_it.array_iterator != m_object->m_value.array->end());\n      return *m_it.array_iterator;\n    }\n\n    case value_t::null:\n      JSON_THROW(invalid_iterator::create(214, \"cannot get value\", m_object));\n\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default: {\n      if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) {\n        return *m_object;\n      }\n\n      JSON_THROW(invalid_iterator::create(214, \"cannot get value\", m_object));\n    }\n    }\n  }\n\n  /*!\n  @brief dereference the iterator\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  pointer operator->() const\n  {\n    JSON_ASSERT(m_object != nullptr);\n\n    switch (m_object->m_type) {\n    case value_t::object: {\n      JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end());\n      return &(m_it.object_iterator->second);\n    }\n\n    case value_t::array: {\n      JSON_ASSERT(m_it.array_iterator != m_object->m_value.array->end());\n      return &*m_it.array_iterator;\n    }\n\n    case value_t::null:\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default: {\n      if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) {\n        return m_object;\n      }\n\n      JSON_THROW(invalid_iterator::create(214, \"cannot get value\", m_object));\n    }\n    }\n  }\n\n  /*!\n  @brief post-increment (it++)\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  iter_impl operator++(int)& { // NOLINT(cert-dcl21-cpp)\n    auto result = *this;\n    ++(*this);\n    return result;\n  }\n\n  /*!\n  @brief pre-increment (++it)\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  iter_impl& operator++()\n  {\n    JSON_ASSERT(m_object != nullptr);\n\n    switch (m_object->m_type) {\n    case value_t::object: {\n      std::advance(m_it.object_iterator, 1);\n      break;\n    }\n\n    case value_t::array: {\n      std::advance(m_it.array_iterator, 1);\n      break;\n    }\n\n    case value_t::null:\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default: {\n      ++m_it.primitive_iterator;\n      break;\n    }\n    }\n\n    return *this;\n  }\n\n  /*!\n  @brief post-decrement (it--)\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  iter_impl operator--(int)& { // NOLINT(cert-dcl21-cpp)\n    auto result = *this;\n    --(*this);\n    return result;\n  }\n\n  /*!\n  @brief pre-decrement (--it)\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  iter_impl& operator--()\n  {\n    JSON_ASSERT(m_object != nullptr);\n\n    switch (m_object->m_type) {\n    case value_t::object: {\n      std::advance(m_it.object_iterator, -1);\n      break;\n    }\n\n    case value_t::array: {\n      std::advance(m_it.array_iterator, -1);\n      break;\n    }\n\n    case value_t::null:\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default: {\n      --m_it.primitive_iterator;\n      break;\n    }\n    }\n\n    return *this;\n  }\n\n  /*!\n  @brief comparison: equal\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  template < typename IterImpl,\n             detail::enable_if_t < (std::is_same<IterImpl, iter_impl>::value ||\n                                    std::is_same<IterImpl, other_iter_impl>::value), std::nullptr_t > = nullptr >\n  bool operator==(const IterImpl& other) const\n  {\n    // if objects are not the same, the comparison is undefined\n    if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) {\n      JSON_THROW(invalid_iterator::create(212,\n                                          \"cannot compare iterators of different containers\", m_object));\n    }\n\n    JSON_ASSERT(m_object != nullptr);\n\n    switch (m_object->m_type) {\n    case value_t::object:\n      return (m_it.object_iterator == other.m_it.object_iterator);\n\n    case value_t::array:\n      return (m_it.array_iterator == other.m_it.array_iterator);\n\n    case value_t::null:\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default:\n      return (m_it.primitive_iterator == other.m_it.primitive_iterator);\n    }\n  }\n\n  /*!\n  @brief comparison: not equal\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  template < typename IterImpl,\n             detail::enable_if_t < (std::is_same<IterImpl, iter_impl>::value ||\n                                    std::is_same<IterImpl, other_iter_impl>::value), std::nullptr_t > = nullptr >\n  bool operator!=(const IterImpl& other) const\n  {\n    return !operator==(other);\n  }\n\n  /*!\n  @brief comparison: smaller\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  bool operator<(const iter_impl& other) const\n  {\n    // if objects are not the same, the comparison is undefined\n    if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) {\n      JSON_THROW(invalid_iterator::create(212,\n                                          \"cannot compare iterators of different containers\", m_object));\n    }\n\n    JSON_ASSERT(m_object != nullptr);\n\n    switch (m_object->m_type) {\n    case value_t::object:\n      JSON_THROW(invalid_iterator::create(213,\n                                          \"cannot compare order of object iterators\", m_object));\n\n    case value_t::array:\n      return (m_it.array_iterator < other.m_it.array_iterator);\n\n    case value_t::null:\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default:\n      return (m_it.primitive_iterator < other.m_it.primitive_iterator);\n    }\n  }\n\n  /*!\n  @brief comparison: less than or equal\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  bool operator<=(const iter_impl& other) const\n  {\n    return !other.operator < (*this);\n  }\n\n  /*!\n  @brief comparison: greater than\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  bool operator>(const iter_impl& other) const\n  {\n    return !operator<=(other);\n  }\n\n  /*!\n  @brief comparison: greater than or equal\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  bool operator>=(const iter_impl& other) const\n  {\n    return !operator<(other);\n  }\n\n  /*!\n  @brief add to iterator\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  iter_impl& operator+=(difference_type i)\n  {\n    JSON_ASSERT(m_object != nullptr);\n\n    switch (m_object->m_type) {\n    case value_t::object:\n      JSON_THROW(invalid_iterator::create(209,\n                                          \"cannot use offsets with object iterators\", m_object));\n\n    case value_t::array: {\n      std::advance(m_it.array_iterator, i);\n      break;\n    }\n\n    case value_t::null:\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default: {\n      m_it.primitive_iterator += i;\n      break;\n    }\n    }\n\n    return *this;\n  }\n\n  /*!\n  @brief subtract from iterator\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  iter_impl& operator-=(difference_type i)\n  {\n    return operator+=(-i);\n  }\n\n  /*!\n  @brief add to iterator\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  iter_impl operator+(difference_type i) const\n  {\n    auto result = *this;\n    result += i;\n    return result;\n  }\n\n  /*!\n  @brief addition of distance and iterator\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  friend iter_impl operator+(difference_type i, const iter_impl& it)\n  {\n    auto result = it;\n    result += i;\n    return result;\n  }\n\n  /*!\n  @brief subtract from iterator\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  iter_impl operator-(difference_type i) const\n  {\n    auto result = *this;\n    result -= i;\n    return result;\n  }\n\n  /*!\n  @brief return difference\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  difference_type operator-(const iter_impl& other) const\n  {\n    JSON_ASSERT(m_object != nullptr);\n\n    switch (m_object->m_type) {\n    case value_t::object:\n      JSON_THROW(invalid_iterator::create(209,\n                                          \"cannot use offsets with object iterators\", m_object));\n\n    case value_t::array:\n      return m_it.array_iterator - other.m_it.array_iterator;\n\n    case value_t::null:\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default:\n      return m_it.primitive_iterator - other.m_it.primitive_iterator;\n    }\n  }\n\n  /*!\n  @brief access to successor\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  reference operator[](difference_type n) const\n  {\n    JSON_ASSERT(m_object != nullptr);\n\n    switch (m_object->m_type) {\n    case value_t::object:\n      JSON_THROW(invalid_iterator::create(208,\n                                          \"cannot use operator[] for object iterators\", m_object));\n\n    case value_t::array:\n      return *std::next(m_it.array_iterator, n);\n\n    case value_t::null:\n      JSON_THROW(invalid_iterator::create(214, \"cannot get value\", m_object));\n\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default: {\n      if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.get_value() == -n)) {\n        return *m_object;\n      }\n\n      JSON_THROW(invalid_iterator::create(214, \"cannot get value\", m_object));\n    }\n    }\n  }\n\n  /*!\n  @brief return the key of an object iterator\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  const typename object_t::key_type& key() const\n  {\n    JSON_ASSERT(m_object != nullptr);\n\n    if (JSON_HEDLEY_LIKELY(m_object->is_object())) {\n      return m_it.object_iterator->first;\n    }\n\n    JSON_THROW(invalid_iterator::create(207,\n                                        \"cannot use key() for non-object iterators\", m_object));\n  }\n\n  /*!\n  @brief return the value of an iterator\n  @pre The iterator is initialized; i.e. `m_object != nullptr`.\n  */\n  reference value() const\n  {\n    return operator*();\n  }\n\nJSON_PRIVATE_UNLESS_TESTED:\n  /// associated JSON instance\n  pointer m_object = nullptr;\n  /// the actual iterator of the associated instance\n  internal_iterator<typename std::remove_const<BasicJsonType>::type> m_it {};\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/iterators/iteration_proxy.hpp>\n\n// #include <nlohmann/detail/iterators/json_reverse_iterator.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef> // ptrdiff_t\n#include <iterator> // reverse_iterator\n#include <utility> // declval\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n//////////////////////\n// reverse_iterator //\n//////////////////////\n\n/*!\n@brief a template for a reverse iterator class\n\n@tparam Base the base iterator type to reverse. Valid types are @ref\niterator (to create @ref reverse_iterator) and @ref const_iterator (to\ncreate @ref const_reverse_iterator).\n\n@requirement The class satisfies the following concept requirements:\n-\n[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator):\n  The iterator that can be moved can be moved in both directions (i.e.\n  incremented and decremented).\n- [OutputIterator](https://en.cppreference.com/w/cpp/named_req/OutputIterator):\n  It is possible to write to the pointed-to element (only if @a Base is\n  @ref iterator).\n\n@since version 1.0.0\n*/\ntemplate<typename Base>\nclass json_reverse_iterator : public std::reverse_iterator<Base>\n{\npublic:\n  using difference_type = std::ptrdiff_t;\n  /// shortcut to the reverse iterator adapter\n  using base_iterator = std::reverse_iterator<Base>;\n  /// the reference type for the pointed-to element\n  using reference = typename Base::reference;\n\n  /// create reverse iterator from iterator\n  explicit json_reverse_iterator(const typename base_iterator::iterator_type& it)\n  noexcept\n    : base_iterator(it) {}\n\n  /// create reverse iterator from base class\n  explicit json_reverse_iterator(const base_iterator& it) noexcept :\n    base_iterator(it) {}\n\n  /// post-increment (it++)\n  json_reverse_iterator operator++(int)& { // NOLINT(cert-dcl21-cpp)\n    return static_cast<json_reverse_iterator>(base_iterator::operator++(1));\n  }\n\n  /// pre-increment (++it)\n  json_reverse_iterator& operator++()\n  {\n    return static_cast<json_reverse_iterator&>(base_iterator::operator++());\n  }\n\n  /// post-decrement (it--)\n  json_reverse_iterator operator--(int)& { // NOLINT(cert-dcl21-cpp)\n    return static_cast<json_reverse_iterator>(base_iterator::operator--(1));\n  }\n\n  /// pre-decrement (--it)\n  json_reverse_iterator& operator--()\n  {\n    return static_cast<json_reverse_iterator&>(base_iterator::operator--());\n  }\n\n  /// add to iterator\n  json_reverse_iterator& operator+=(difference_type i)\n  {\n    return static_cast<json_reverse_iterator&>(base_iterator::operator+=(i));\n  }\n\n  /// add to iterator\n  json_reverse_iterator operator+(difference_type i) const\n  {\n    return static_cast<json_reverse_iterator>(base_iterator::operator+(i));\n  }\n\n  /// subtract from iterator\n  json_reverse_iterator operator-(difference_type i) const\n  {\n    return static_cast<json_reverse_iterator>(base_iterator::operator-(i));\n  }\n\n  /// return difference\n  difference_type operator-(const json_reverse_iterator& other) const\n  {\n    return base_iterator(*this) - base_iterator(other);\n  }\n\n  /// access to successor\n  reference operator[](difference_type n) const\n  {\n    return *(this->operator+(n));\n  }\n\n  /// return the key of an object iterator\n  auto key() const -> decltype(std::declval<Base>().key())\n  {\n    auto it = --this->base();\n    return it.key();\n  }\n\n  /// return the value of an iterator\n  reference value() const\n  {\n    auto it = --this->base();\n    return it.operator * ();\n  }\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/iterators/primitive_iterator.hpp>\n\n// #include <nlohmann/detail/json_pointer.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // all_of\n#include <cctype> // isdigit\n#include <cerrno> // errno, ERANGE\n#include <cstdlib> // strtoull\n#ifndef JSON_NO_IO\n#include <iosfwd> // ostream\n#endif  // JSON_NO_IO\n#include <limits> // max\n#include <numeric> // accumulate\n#include <string> // string\n#include <utility> // move\n#include <vector> // vector\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/string_escape.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document\n/// @sa https://json.nlohmann.me/api/json_pointer/\ntemplate<typename RefStringType>\nclass json_pointer\n{\n  // allow basic_json to access private members\n  NLOHMANN_BASIC_JSON_TPL_DECLARATION\n  friend class basic_json;\n\n  template<typename>\n  friend class json_pointer;\n\n  template<typename T>\n  struct string_t_helper {\n    using type = T;\n  };\n\n  NLOHMANN_BASIC_JSON_TPL_DECLARATION\n  struct string_t_helper<NLOHMANN_BASIC_JSON_TPL> {\n    using type = StringType;\n  };\n\npublic:\n  // for backwards compatibility accept BasicJsonType\n  using string_t = typename string_t_helper<RefStringType>::type;\n\n  /// @brief create JSON pointer\n  /// @sa https://json.nlohmann.me/api/json_pointer/json_pointer/\n  explicit json_pointer(const string_t& s = \"\")\n    : reference_tokens(split(s))\n  {}\n\n  /// @brief return a string representation of the JSON pointer\n  /// @sa https://json.nlohmann.me/api/json_pointer/to_string/\n  string_t to_string() const\n  {\n    return std::accumulate(reference_tokens.begin(), reference_tokens.end(),\n                           string_t{},\n    [](const string_t& a, const string_t& b) {\n      return detail::concat(a, '/', detail::escape(b));\n    });\n  }\n\n  /// @brief return a string representation of the JSON pointer\n  /// @sa https://json.nlohmann.me/api/json_pointer/operator_string/\n  JSON_HEDLEY_DEPRECATED_FOR(3.11.0, to_string())\n  operator string_t() const\n  {\n    return to_string();\n  }\n\n#ifndef JSON_NO_IO\n  /// @brief write string representation of the JSON pointer to stream\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/\n  friend std::ostream& operator<<(std::ostream& o, const json_pointer& ptr)\n  {\n    o << ptr.to_string();\n    return o;\n  }\n#endif\n\n  /// @brief append another JSON pointer at the end of this JSON pointer\n  /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/\n  json_pointer& operator/=(const json_pointer& ptr)\n  {\n    reference_tokens.insert(reference_tokens.end(),\n                            ptr.reference_tokens.begin(),\n                            ptr.reference_tokens.end());\n    return *this;\n  }\n\n  /// @brief append an unescaped reference token at the end of this JSON pointer\n  /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/\n  json_pointer& operator/=(string_t token)\n  {\n    push_back(std::move(token));\n    return *this;\n  }\n\n  /// @brief append an array index at the end of this JSON pointer\n  /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/\n  json_pointer& operator/=(std::size_t array_idx)\n  {\n    return *this /= std::to_string(array_idx);\n  }\n\n  /// @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer\n  /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/\n  friend json_pointer operator/(const json_pointer& lhs,\n                                const json_pointer& rhs)\n  {\n    return json_pointer(lhs) /= rhs;\n  }\n\n  /// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer\n  /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/\n  friend json_pointer operator/(const json_pointer& lhs,\n                                string_t token) // NOLINT(performance-unnecessary-value-param)\n  {\n    return json_pointer(lhs) /= std::move(token);\n  }\n\n  /// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer\n  /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/\n  friend json_pointer operator/(const json_pointer& lhs, std::size_t array_idx)\n  {\n    return json_pointer(lhs) /= array_idx;\n  }\n\n  /// @brief returns the parent of this JSON pointer\n  /// @sa https://json.nlohmann.me/api/json_pointer/parent_pointer/\n  json_pointer parent_pointer() const\n  {\n    if (empty()) {\n      return *this;\n    }\n\n    json_pointer res = *this;\n    res.pop_back();\n    return res;\n  }\n\n  /// @brief remove last reference token\n  /// @sa https://json.nlohmann.me/api/json_pointer/pop_back/\n  void pop_back()\n  {\n    if (JSON_HEDLEY_UNLIKELY(empty())) {\n      JSON_THROW(detail::out_of_range::create(405, \"JSON pointer has no parent\",\n                                              nullptr));\n    }\n\n    reference_tokens.pop_back();\n  }\n\n  /// @brief return last reference token\n  /// @sa https://json.nlohmann.me/api/json_pointer/back/\n  const string_t& back() const\n  {\n    if (JSON_HEDLEY_UNLIKELY(empty())) {\n      JSON_THROW(detail::out_of_range::create(405, \"JSON pointer has no parent\",\n                                              nullptr));\n    }\n\n    return reference_tokens.back();\n  }\n\n  /// @brief append an unescaped token at the end of the reference pointer\n  /// @sa https://json.nlohmann.me/api/json_pointer/push_back/\n  void push_back(const string_t& token)\n  {\n    reference_tokens.push_back(token);\n  }\n\n  /// @brief append an unescaped token at the end of the reference pointer\n  /// @sa https://json.nlohmann.me/api/json_pointer/push_back/\n  void push_back(string_t&& token)\n  {\n    reference_tokens.push_back(std::move(token));\n  }\n\n  /// @brief return whether pointer points to the root document\n  /// @sa https://json.nlohmann.me/api/json_pointer/empty/\n  bool empty() const noexcept\n  {\n    return reference_tokens.empty();\n  }\n\nprivate:\n  /*!\n  @param[in] s  reference token to be converted into an array index\n\n  @return integer representation of @a s\n\n  @throw parse_error.106  if an array index begins with '0'\n  @throw parse_error.109  if an array index begins not with a digit\n  @throw out_of_range.404 if string @a s could not be converted to an integer\n  @throw out_of_range.410 if an array index exceeds size_type\n  */\n  template<typename BasicJsonType>\n  static typename BasicJsonType::size_type array_index(const string_t& s)\n  {\n    using size_type = typename BasicJsonType::size_type;\n\n    // error condition (cf. RFC 6901, Sect. 4)\n    if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) {\n      JSON_THROW(detail::parse_error::create(106, 0, detail::concat(\"array index '\",\n                                             s, \"' must not begin with '0'\"), nullptr));\n    }\n\n    // error condition (cf. RFC 6901, Sect. 4)\n    if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) {\n      JSON_THROW(detail::parse_error::create(109, 0, detail::concat(\"array index '\",\n                                             s, \"' is not a number\"), nullptr));\n    }\n\n    const char* p = s.c_str();\n    char* p_end = nullptr;\n    errno = 0; // strtoull doesn't reset errno\n    unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)\n\n    if (p == p_end // invalid input or empty string\n        || errno == ERANGE // out of range\n        || JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) !=\n                                s.size())) { // incomplete read\n      JSON_THROW(detail::out_of_range::create(404,\n                                              detail::concat(\"unresolved reference token '\", s, \"'\"), nullptr));\n    }\n\n    // only triggered on special platforms (like 32bit), see also\n    // https://github.com/nlohmann/json/pull/2203\n    if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)\n        ())) { // NOLINT(runtime/int)\n      JSON_THROW(detail::out_of_range::create(410, detail::concat(\"array index \", s,\n                                              \" exceeds size_type\"), nullptr));   // LCOV_EXCL_LINE\n    }\n\n    return static_cast<size_type>(res);\n  }\n\nJSON_PRIVATE_UNLESS_TESTED:\n  json_pointer top() const\n  {\n    if (JSON_HEDLEY_UNLIKELY(empty())) {\n      JSON_THROW(detail::out_of_range::create(405, \"JSON pointer has no parent\",\n                                              nullptr));\n    }\n\n    json_pointer result = *this;\n    result.reference_tokens = {reference_tokens[0]};\n    return result;\n  }\n\nprivate:\n  /*!\n  @brief create and return a reference to the pointed to value\n\n  @complexity Linear in the number of reference tokens.\n\n  @throw parse_error.109 if array index is not a number\n  @throw type_error.313 if value cannot be unflattened\n  */\n  template<typename BasicJsonType>\n  BasicJsonType& get_and_create(BasicJsonType& j) const\n  {\n    auto* result = &j;\n\n    // in case no reference tokens exist, return a reference to the JSON value\n    // j which will be overwritten by a primitive value\n    for (const auto& reference_token : reference_tokens) {\n      switch (result->type()) {\n      case detail::value_t::null: {\n        if (reference_token == \"0\") {\n          // start a new array if reference token is 0\n          result = &result->operator[](0);\n        } else {\n          // start a new object otherwise\n          result = &result->operator[](reference_token);\n        }\n\n        break;\n      }\n\n      case detail::value_t::object: {\n        // create an entry in the object\n        result = &result->operator[](reference_token);\n        break;\n      }\n\n      case detail::value_t::array: {\n        // create an entry in the array\n        result = &result->operator[](array_index<BasicJsonType>(reference_token));\n        break;\n      }\n\n      /*\n      The following code is only reached if there exists a reference\n      token _and_ the current value is primitive. In this case, we have\n      an error situation, because primitive values may only occur as\n      single value; that is, with an empty list of reference tokens.\n      */\n      case detail::value_t::string:\n      case detail::value_t::boolean:\n      case detail::value_t::number_integer:\n      case detail::value_t::number_unsigned:\n      case detail::value_t::number_float:\n      case detail::value_t::binary:\n      case detail::value_t::discarded:\n      default:\n        JSON_THROW(detail::type_error::create(313, \"invalid value to unflatten\", &j));\n      }\n    }\n\n    return *result;\n  }\n\n  /*!\n  @brief return a reference to the pointed to value\n\n  @note This version does not throw if a value is not present, but tries to\n        create nested values instead. For instance, calling this function\n        with pointer `\"/this/that\"` on a null value is equivalent to calling\n        `operator[](\"this\").operator[](\"that\")` on that value, effectively\n        changing the null value to an object.\n\n  @param[in] ptr  a JSON value\n\n  @return reference to the JSON value pointed to by the JSON pointer\n\n  @complexity Linear in the length of the JSON pointer.\n\n  @throw parse_error.106   if an array index begins with '0'\n  @throw parse_error.109   if an array index was not a number\n  @throw out_of_range.404  if the JSON pointer can not be resolved\n  */\n  template<typename BasicJsonType>\n  BasicJsonType& get_unchecked(BasicJsonType* ptr) const\n  {\n    for (const auto& reference_token : reference_tokens) {\n      // convert null values to arrays or objects before continuing\n      if (ptr->is_null()) {\n        // check if reference token is a number\n        const bool nums =\n          std::all_of(reference_token.begin(), reference_token.end(),\n        [](const unsigned char x) {\n          return std::isdigit(x);\n        });\n        // change value to array for numbers or \"-\" or to object otherwise\n        *ptr = (nums || reference_token == \"-\")\n               ? detail::value_t::array\n               : detail::value_t::object;\n      }\n\n      switch (ptr->type()) {\n      case detail::value_t::object: {\n        // use unchecked object access\n        ptr = &ptr->operator[](reference_token);\n        break;\n      }\n\n      case detail::value_t::array: {\n        if (reference_token == \"-\") {\n          // explicitly treat \"-\" as index beyond the end\n          ptr = &ptr->operator[](ptr->m_value.array->size());\n        } else {\n          // convert array index to number; unchecked access\n          ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));\n        }\n\n        break;\n      }\n\n      case detail::value_t::null:\n      case detail::value_t::string:\n      case detail::value_t::boolean:\n      case detail::value_t::number_integer:\n      case detail::value_t::number_unsigned:\n      case detail::value_t::number_float:\n      case detail::value_t::binary:\n      case detail::value_t::discarded:\n      default:\n        JSON_THROW(detail::out_of_range::create(404,\n                                                detail::concat(\"unresolved reference token '\", reference_token, \"'\"), ptr));\n      }\n    }\n\n    return *ptr;\n  }\n\n  /*!\n  @throw parse_error.106   if an array index begins with '0'\n  @throw parse_error.109   if an array index was not a number\n  @throw out_of_range.402  if the array index '-' is used\n  @throw out_of_range.404  if the JSON pointer can not be resolved\n  */\n  template<typename BasicJsonType>\n  BasicJsonType& get_checked(BasicJsonType* ptr) const\n  {\n    for (const auto& reference_token : reference_tokens) {\n      switch (ptr->type()) {\n      case detail::value_t::object: {\n        // note: at performs range check\n        ptr = &ptr->at(reference_token);\n        break;\n      }\n\n      case detail::value_t::array: {\n        if (JSON_HEDLEY_UNLIKELY(reference_token == \"-\")) {\n          // \"-\" always fails the range check\n          JSON_THROW(detail::out_of_range::create(402, detail::concat(\n              \"array index '-' (\", std::to_string(ptr->m_value.array->size()),\n              \") is out of range\"), ptr));\n        }\n\n        // note: at performs range check\n        ptr = &ptr->at(array_index<BasicJsonType>(reference_token));\n        break;\n      }\n\n      case detail::value_t::null:\n      case detail::value_t::string:\n      case detail::value_t::boolean:\n      case detail::value_t::number_integer:\n      case detail::value_t::number_unsigned:\n      case detail::value_t::number_float:\n      case detail::value_t::binary:\n      case detail::value_t::discarded:\n      default:\n        JSON_THROW(detail::out_of_range::create(404,\n                                                detail::concat(\"unresolved reference token '\", reference_token, \"'\"), ptr));\n      }\n    }\n\n    return *ptr;\n  }\n\n  /*!\n  @brief return a const reference to the pointed to value\n\n  @param[in] ptr  a JSON value\n\n  @return const reference to the JSON value pointed to by the JSON\n  pointer\n\n  @throw parse_error.106   if an array index begins with '0'\n  @throw parse_error.109   if an array index was not a number\n  @throw out_of_range.402  if the array index '-' is used\n  @throw out_of_range.404  if the JSON pointer can not be resolved\n  */\n  template<typename BasicJsonType>\n  const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const\n  {\n    for (const auto& reference_token : reference_tokens) {\n      switch (ptr->type()) {\n      case detail::value_t::object: {\n        // use unchecked object access\n        ptr = &ptr->operator[](reference_token);\n        break;\n      }\n\n      case detail::value_t::array: {\n        if (JSON_HEDLEY_UNLIKELY(reference_token == \"-\")) {\n          // \"-\" cannot be used for const access\n          JSON_THROW(detail::out_of_range::create(402, detail::concat(\"array index '-' (\",\n                                                  std::to_string(ptr->m_value.array->size()), \") is out of range\"), ptr));\n        }\n\n        // use unchecked array access\n        ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));\n        break;\n      }\n\n      case detail::value_t::null:\n      case detail::value_t::string:\n      case detail::value_t::boolean:\n      case detail::value_t::number_integer:\n      case detail::value_t::number_unsigned:\n      case detail::value_t::number_float:\n      case detail::value_t::binary:\n      case detail::value_t::discarded:\n      default:\n        JSON_THROW(detail::out_of_range::create(404,\n                                                detail::concat(\"unresolved reference token '\", reference_token, \"'\"), ptr));\n      }\n    }\n\n    return *ptr;\n  }\n\n  /*!\n  @throw parse_error.106   if an array index begins with '0'\n  @throw parse_error.109   if an array index was not a number\n  @throw out_of_range.402  if the array index '-' is used\n  @throw out_of_range.404  if the JSON pointer can not be resolved\n  */\n  template<typename BasicJsonType>\n  const BasicJsonType& get_checked(const BasicJsonType* ptr) const\n  {\n    for (const auto& reference_token : reference_tokens) {\n      switch (ptr->type()) {\n      case detail::value_t::object: {\n        // note: at performs range check\n        ptr = &ptr->at(reference_token);\n        break;\n      }\n\n      case detail::value_t::array: {\n        if (JSON_HEDLEY_UNLIKELY(reference_token == \"-\")) {\n          // \"-\" always fails the range check\n          JSON_THROW(detail::out_of_range::create(402, detail::concat(\n              \"array index '-' (\", std::to_string(ptr->m_value.array->size()),\n              \") is out of range\"), ptr));\n        }\n\n        // note: at performs range check\n        ptr = &ptr->at(array_index<BasicJsonType>(reference_token));\n        break;\n      }\n\n      case detail::value_t::null:\n      case detail::value_t::string:\n      case detail::value_t::boolean:\n      case detail::value_t::number_integer:\n      case detail::value_t::number_unsigned:\n      case detail::value_t::number_float:\n      case detail::value_t::binary:\n      case detail::value_t::discarded:\n      default:\n        JSON_THROW(detail::out_of_range::create(404,\n                                                detail::concat(\"unresolved reference token '\", reference_token, \"'\"), ptr));\n      }\n    }\n\n    return *ptr;\n  }\n\n  /*!\n  @throw parse_error.106   if an array index begins with '0'\n  @throw parse_error.109   if an array index was not a number\n  */\n  template<typename BasicJsonType>\n  bool contains(const BasicJsonType* ptr) const\n  {\n    for (const auto& reference_token : reference_tokens) {\n      switch (ptr->type()) {\n      case detail::value_t::object: {\n        if (!ptr->contains(reference_token)) {\n          // we did not find the key in the object\n          return false;\n        }\n\n        ptr = &ptr->operator[](reference_token);\n        break;\n      }\n\n      case detail::value_t::array: {\n        if (JSON_HEDLEY_UNLIKELY(reference_token == \"-\")) {\n          // \"-\" always fails the range check\n          return false;\n        }\n\n        if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 &&\n                                 !(\"0\" <= reference_token && reference_token <= \"9\"))) {\n          // invalid char\n          return false;\n        }\n\n        if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1)) {\n          if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] &&\n                                     reference_token[0] <= '9'))) {\n            // first char should be between '1' and '9'\n            return false;\n          }\n\n          for (std::size_t i = 1; i < reference_token.size(); i++) {\n            if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] &&\n                                       reference_token[i] <= '9'))) {\n              // other char should be between '0' and '9'\n              return false;\n            }\n          }\n        }\n\n        const auto idx = array_index<BasicJsonType>(reference_token);\n\n        if (idx >= ptr->size()) {\n          // index out of range\n          return false;\n        }\n\n        ptr = &ptr->operator[](idx);\n        break;\n      }\n\n      case detail::value_t::null:\n      case detail::value_t::string:\n      case detail::value_t::boolean:\n      case detail::value_t::number_integer:\n      case detail::value_t::number_unsigned:\n      case detail::value_t::number_float:\n      case detail::value_t::binary:\n      case detail::value_t::discarded:\n      default: {\n        // we do not expect primitive values if there is still a\n        // reference token to process\n        return false;\n      }\n      }\n    }\n\n    // no reference token left means we found a primitive value\n    return true;\n  }\n\n  /*!\n  @brief split the string input to reference tokens\n\n  @note This function is only called by the json_pointer constructor.\n        All exceptions below are documented there.\n\n  @throw parse_error.107  if the pointer is not empty or begins with '/'\n  @throw parse_error.108  if character '~' is not followed by '0' or '1'\n  */\n  static std::vector<string_t> split(const string_t& reference_string)\n  {\n    std::vector<string_t> result;\n\n    // special case: empty reference string -> no reference tokens\n    if (reference_string.empty()) {\n      return result;\n    }\n\n    // check if nonempty reference string begins with slash\n    if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) {\n      JSON_THROW(detail::parse_error::create(107, 1,\n                                             detail::concat(\"JSON pointer must be empty or begin with '/' - was: '\",\n                                                 reference_string, \"'\"), nullptr));\n    }\n\n    // extract the reference tokens:\n    // - slash: position of the last read slash (or end of string)\n    // - start: position after the previous slash\n    for (\n      // search for the first slash after the first character\n      std::size_t slash = reference_string.find_first_of('/', 1),\n      // set the beginning of the first reference token\n      start = 1;\n      // we can stop if start == 0 (if slash == string_t::npos)\n      start != 0;\n      // set the beginning of the next reference token\n      // (will eventually be 0 if slash == string_t::npos)\n      start = (slash == string_t::npos) ? 0 : slash + 1,\n      // find next slash\n      slash = reference_string.find_first_of('/', start)) {\n      // use the text between the beginning of the reference token\n      // (start) and the last slash (slash).\n      auto reference_token = reference_string.substr(start, slash - start);\n\n      // check reference tokens are properly escaped\n      for (std::size_t pos = reference_token.find_first_of('~');\n           pos != string_t::npos;\n           pos = reference_token.find_first_of('~', pos + 1)) {\n        JSON_ASSERT(reference_token[pos] == '~');\n\n        // ~ must be followed by 0 or 1\n        if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||\n                                 (reference_token[pos + 1] != '0' &&\n                                  reference_token[pos + 1] != '1'))) {\n          JSON_THROW(detail::parse_error::create(108, 0,\n                                                 \"escape character '~' must be followed with '0' or '1'\", nullptr));\n        }\n      }\n\n      // finally, store the reference token\n      detail::unescape(reference_token);\n      result.push_back(reference_token);\n    }\n\n    return result;\n  }\n\nprivate:\n  /*!\n  @param[in] reference_string  the reference string to the current value\n  @param[in] value             the value to consider\n  @param[in,out] result        the result object to insert values to\n\n  @note Empty objects or arrays are flattened to `null`.\n  */\n  template<typename BasicJsonType>\n  static void flatten(const string_t& reference_string,\n                      const BasicJsonType& value,\n                      BasicJsonType& result)\n  {\n    switch (value.type()) {\n    case detail::value_t::array: {\n      if (value.m_value.array->empty()) {\n        // flatten empty array as null\n        result[reference_string] = nullptr;\n      } else {\n        // iterate array and use index as reference string\n        for (std::size_t i = 0; i < value.m_value.array->size(); ++i) {\n          flatten(detail::concat(reference_string, '/', std::to_string(i)),\n                  value.m_value.array->operator[](i), result);\n        }\n      }\n\n      break;\n    }\n\n    case detail::value_t::object: {\n      if (value.m_value.object->empty()) {\n        // flatten empty object as null\n        result[reference_string] = nullptr;\n      } else {\n        // iterate object and use keys as reference string\n        for (const auto& element : *value.m_value.object) {\n          flatten(detail::concat(reference_string, '/', detail::escape(element.first)),\n                  element.second, result);\n        }\n      }\n\n      break;\n    }\n\n    case detail::value_t::null:\n    case detail::value_t::string:\n    case detail::value_t::boolean:\n    case detail::value_t::number_integer:\n    case detail::value_t::number_unsigned:\n    case detail::value_t::number_float:\n    case detail::value_t::binary:\n    case detail::value_t::discarded:\n    default: {\n      // add primitive value with its reference string\n      result[reference_string] = value;\n      break;\n    }\n    }\n  }\n\n  /*!\n  @param[in] value  flattened JSON\n\n  @return unflattened JSON\n\n  @throw parse_error.109 if array index is not a number\n  @throw type_error.314  if value is not an object\n  @throw type_error.315  if object values are not primitive\n  @throw type_error.313  if value cannot be unflattened\n  */\n  template<typename BasicJsonType>\n  static BasicJsonType\n  unflatten(const BasicJsonType& value)\n  {\n    if (JSON_HEDLEY_UNLIKELY(!value.is_object())) {\n      JSON_THROW(detail::type_error::create(314, \"only objects can be unflattened\",\n                                            &value));\n    }\n\n    BasicJsonType result;\n\n    // iterate the JSON object values\n    for (const auto& element : *value.m_value.object) {\n      if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) {\n        JSON_THROW(detail::type_error::create(315, \"values in object must be primitive\",\n                                              &element.second));\n      }\n\n      // assign value to reference pointed to by JSON pointer; Note that if\n      // the JSON pointer is \"\" (i.e., points to the whole value), function\n      // get_and_create returns a reference to result itself. An assignment\n      // will then create a primitive value.\n      json_pointer(element.first).get_and_create(result) = element.second;\n    }\n\n    return result;\n  }\n\n  // can't use conversion operator because of ambiguity\n  json_pointer<string_t> convert() const&\n  {\n    json_pointer<string_t> result;\n    result.reference_tokens = reference_tokens;\n    return result;\n  }\n\n  json_pointer<string_t> convert()&& {\n    json_pointer<string_t> result;\n    result.reference_tokens = std::move(reference_tokens);\n    return result;\n  }\n\npublic:\n#if JSON_HAS_THREE_WAY_COMPARISON\n  /// @brief compares two JSON pointers for equality\n  /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/\n  template<typename RefStringTypeRhs>\n  bool operator==(const json_pointer<RefStringTypeRhs>& rhs) const noexcept\n  {\n    return reference_tokens == rhs.reference_tokens;\n  }\n\n  /// @brief compares JSON pointer and string for equality\n  /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/\n  JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer))\n  bool operator==(const string_t& rhs) const\n  {\n    return *this == json_pointer(rhs);\n  }\n\n  /// @brief 3-way compares two JSON pointers\n  template<typename RefStringTypeRhs>\n  std::strong_ordering operator<=>(const json_pointer<RefStringTypeRhs>& rhs)\n  const noexcept // *NOPAD*\n  {\n    return  reference_tokens <=> rhs.reference_tokens; // *NOPAD*\n  }\n#else\n  /// @brief compares two JSON pointers for equality\n  /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/\n  template<typename RefStringTypeLhs, typename RefStringTypeRhs>\n  // NOLINTNEXTLINE(readability-redundant-declaration)\n  friend bool operator==(const json_pointer<RefStringTypeLhs>& lhs,\n                         const json_pointer<RefStringTypeRhs>& rhs) noexcept;\n\n  /// @brief compares JSON pointer and string for equality\n  /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/\n  template<typename RefStringTypeLhs, typename StringType>\n  // NOLINTNEXTLINE(readability-redundant-declaration)\n  friend bool operator==(const json_pointer<RefStringTypeLhs>& lhs,\n                         const StringType& rhs);\n\n  /// @brief compares string and JSON pointer for equality\n  /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/\n  template<typename RefStringTypeRhs, typename StringType>\n  // NOLINTNEXTLINE(readability-redundant-declaration)\n  friend bool operator==(const StringType& lhs,\n                         const json_pointer<RefStringTypeRhs>& rhs);\n\n  /// @brief compares two JSON pointers for inequality\n  /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/\n  template<typename RefStringTypeLhs, typename RefStringTypeRhs>\n  // NOLINTNEXTLINE(readability-redundant-declaration)\n  friend bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,\n                         const json_pointer<RefStringTypeRhs>& rhs) noexcept;\n\n  /// @brief compares JSON pointer and string for inequality\n  /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/\n  template<typename RefStringTypeLhs, typename StringType>\n  // NOLINTNEXTLINE(readability-redundant-declaration)\n  friend bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,\n                         const StringType& rhs);\n\n  /// @brief compares string and JSON pointer for inequality\n  /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/\n  template<typename RefStringTypeRhs, typename StringType>\n  // NOLINTNEXTLINE(readability-redundant-declaration)\n  friend bool operator!=(const StringType& lhs,\n                         const json_pointer<RefStringTypeRhs>& rhs);\n\n  /// @brief compares two JSON pointer for less-than\n  template<typename RefStringTypeLhs, typename RefStringTypeRhs>\n  // NOLINTNEXTLINE(readability-redundant-declaration)\n  friend bool operator<(const json_pointer<RefStringTypeLhs>& lhs,\n                        const json_pointer<RefStringTypeRhs>& rhs) noexcept;\n#endif\n\nprivate:\n  /// the reference tokens\n  std::vector<string_t> reference_tokens;\n};\n\n#if !JSON_HAS_THREE_WAY_COMPARISON\n// functions cannot be defined inside class due to ODR violations\ntemplate<typename RefStringTypeLhs, typename RefStringTypeRhs>\ninline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,\n                       const json_pointer<RefStringTypeRhs>& rhs) noexcept\n{\n  return lhs.reference_tokens == rhs.reference_tokens;\n}\n\ntemplate<typename RefStringTypeLhs,\n         typename StringType = typename json_pointer<RefStringTypeLhs>::string_t>\nJSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))\ninline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,\n                       const StringType& rhs)\n{\n  return lhs == json_pointer<RefStringTypeLhs>(rhs);\n}\n\ntemplate<typename RefStringTypeRhs,\n         typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>\nJSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))\ninline bool operator==(const StringType& lhs,\n                       const json_pointer<RefStringTypeRhs>& rhs)\n{\n  return json_pointer<RefStringTypeRhs>(lhs) == rhs;\n}\n\ntemplate<typename RefStringTypeLhs, typename RefStringTypeRhs>\ninline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,\n                       const json_pointer<RefStringTypeRhs>& rhs) noexcept\n{\n  return !(lhs == rhs);\n}\n\ntemplate<typename RefStringTypeLhs,\n         typename StringType = typename json_pointer<RefStringTypeLhs>::string_t>\nJSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))\ninline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,\n                       const StringType& rhs)\n{\n  return !(lhs == rhs);\n}\n\ntemplate<typename RefStringTypeRhs,\n         typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>\nJSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))\ninline bool operator!=(const StringType& lhs,\n                       const json_pointer<RefStringTypeRhs>& rhs)\n{\n  return !(lhs == rhs);\n}\n\ntemplate<typename RefStringTypeLhs, typename RefStringTypeRhs>\ninline bool operator<(const json_pointer<RefStringTypeLhs>& lhs,\n                      const json_pointer<RefStringTypeRhs>& rhs) noexcept\n{\n  return lhs.reference_tokens < rhs.reference_tokens;\n}\n#endif\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/json_ref.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <initializer_list>\n#include <utility>\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename BasicJsonType>\nclass json_ref\n{\npublic:\n  using value_type = BasicJsonType;\n\n  json_ref(value_type&& value)\n    : owned_value(std::move(value))\n  {}\n\n  json_ref(const value_type& value)\n    : value_ref(&value)\n  {}\n\n  json_ref(std::initializer_list<json_ref> init)\n    : owned_value(init)\n  {}\n\n  template <\n    class... Args,\n    enable_if_t<std::is_constructible<value_type, Args...>::value, int> = 0 >\n  json_ref(Args && ... args)\n    : owned_value(std::forward<Args>(args)...)\n  {}\n\n  // class should be movable only\n  json_ref(json_ref&&) noexcept = default;\n  json_ref(const json_ref&) = delete;\n  json_ref& operator=(const json_ref&) = delete;\n  json_ref& operator=(json_ref&&) = delete;\n  ~json_ref() = default;\n\n  value_type moved_or_copied() const\n  {\n    if (value_ref == nullptr) {\n      return std::move(owned_value);\n    }\n\n    return *value_ref;\n  }\n\n  value_type const& operator*() const\n  {\n    return value_ref ? *value_ref : owned_value;\n  }\n\n  value_type const* operator->() const\n  {\n    return &** this;\n  }\n\nprivate:\n  mutable value_type owned_value = nullptr;\n  value_type const* value_ref = nullptr;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/string_escape.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/output/binary_writer.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // reverse\n#include <array> // array\n#include <map> // map\n#include <cmath> // isnan, isinf\n#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t\n#include <cstring> // memcpy\n#include <limits> // numeric_limits\n#include <string> // string\n#include <utility> // move\n#include <vector> // vector\n\n// #include <nlohmann/detail/input/binary_reader.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/output/output_adapters.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // copy\n#include <cstddef> // size_t\n#include <iterator> // back_inserter\n#include <memory> // shared_ptr, make_shared\n#include <string> // basic_string\n#include <vector> // vector\n\n#ifndef JSON_NO_IO\n#include <ios>      // streamsize\n#include <ostream>  // basic_ostream\n#endif  // JSON_NO_IO\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/// abstract output adapter interface\ntemplate<typename CharType> struct output_adapter_protocol {\n  virtual void write_character(CharType c) = 0;\n  virtual void write_characters(const CharType* s, std::size_t length) = 0;\n  virtual ~output_adapter_protocol() = default;\n\n  output_adapter_protocol() = default;\n  output_adapter_protocol(const output_adapter_protocol&) = default;\n  output_adapter_protocol(output_adapter_protocol&&) noexcept = default;\n  output_adapter_protocol& operator=(const output_adapter_protocol&) = default;\n  output_adapter_protocol& operator=(output_adapter_protocol&&) noexcept =\n    default;\n};\n\n/// a type to simplify interfaces\ntemplate<typename CharType>\nusing output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;\n\n/// output adapter for byte vectors\ntemplate<typename CharType, typename AllocatorType = std::allocator<CharType>>\nclass output_vector_adapter : public output_adapter_protocol<CharType>\n{\npublic:\n  explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec)\n  noexcept\n    : v(vec)\n  {}\n\n  void write_character(CharType c) override\n  {\n    v.push_back(c);\n  }\n\n  JSON_HEDLEY_NON_NULL(2)\n  void write_characters(const CharType* s, std::size_t length) override\n  {\n    v.insert(v.end(), s, s + length);\n  }\n\nprivate:\n  std::vector<CharType, AllocatorType>& v;\n};\n\n#ifndef JSON_NO_IO\n/// output adapter for output streams\ntemplate<typename CharType>\nclass output_stream_adapter : public output_adapter_protocol<CharType>\n{\npublic:\n  explicit output_stream_adapter(std::basic_ostream<CharType>& s) noexcept\n    : stream(s)\n  {}\n\n  void write_character(CharType c) override\n  {\n    stream.put(c);\n  }\n\n  JSON_HEDLEY_NON_NULL(2)\n  void write_characters(const CharType* s, std::size_t length) override\n  {\n    stream.write(s, static_cast<std::streamsize>(length));\n  }\n\nprivate:\n  std::basic_ostream<CharType>& stream;\n};\n#endif  // JSON_NO_IO\n\n/// output adapter for basic_string\ntemplate<typename CharType, typename StringType = std::basic_string<CharType>>\nclass output_string_adapter : public output_adapter_protocol<CharType>\n{\npublic:\n  explicit output_string_adapter(StringType& s) noexcept\n    : str(s)\n  {}\n\n  void write_character(CharType c) override\n  {\n    str.push_back(c);\n  }\n\n  JSON_HEDLEY_NON_NULL(2)\n  void write_characters(const CharType* s, std::size_t length) override\n  {\n    str.append(s, length);\n  }\n\nprivate:\n  StringType& str;\n};\n\ntemplate<typename CharType, typename StringType = std::basic_string<CharType>>\nclass output_adapter\n{\npublic:\n  template<typename AllocatorType = std::allocator<CharType>>\n  output_adapter(std::vector<CharType, AllocatorType>& vec)\n    : oa(std::make_shared<output_vector_adapter<CharType, AllocatorType>>(vec)) {}\n\n#ifndef JSON_NO_IO\n  output_adapter(std::basic_ostream<CharType>& s)\n    : oa(std::make_shared<output_stream_adapter<CharType>>(s)) {}\n#endif  // JSON_NO_IO\n\n  output_adapter(StringType& s)\n    : oa(std::make_shared<output_string_adapter<CharType, StringType>>(s)) {}\n\n  operator output_adapter_t<CharType>()\n  {\n    return oa;\n  }\n\nprivate:\n  output_adapter_t<CharType> oa = nullptr;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n///////////////////\n// binary writer //\n///////////////////\n\n/*!\n@brief serialization to CBOR and MessagePack values\n*/\ntemplate<typename BasicJsonType, typename CharType>\nclass binary_writer\n{\n  using string_t = typename BasicJsonType::string_t;\n  using binary_t = typename BasicJsonType::binary_t;\n  using number_float_t = typename BasicJsonType::number_float_t;\n\npublic:\n  /*!\n  @brief create a binary writer\n\n  @param[in] adapter  output adapter to write to\n  */\n  explicit binary_writer(output_adapter_t<CharType> adapter) : oa(std::move(\n          adapter))\n  {\n    JSON_ASSERT(oa);\n  }\n\n  /*!\n  @param[in] j  JSON value to serialize\n  @pre       j.type() == value_t::object\n  */\n  void write_bson(const BasicJsonType& j)\n  {\n    switch (j.type()) {\n    case value_t::object: {\n      write_bson_object(*j.m_value.object);\n      break;\n    }\n\n    case value_t::null:\n    case value_t::array:\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default: {\n      JSON_THROW(type_error::create(317,\n                                    concat(\"to serialize to BSON, top-level type must be object, but is \",\n                                           j.type_name()), &j));\n    }\n    }\n  }\n\n  /*!\n  @param[in] j  JSON value to serialize\n  */\n  void write_cbor(const BasicJsonType& j)\n  {\n    switch (j.type()) {\n    case value_t::null: {\n      oa->write_character(to_char_type(0xF6));\n      break;\n    }\n\n    case value_t::boolean: {\n      oa->write_character(j.m_value.boolean\n                          ? to_char_type(0xF5)\n                          : to_char_type(0xF4));\n      break;\n    }\n\n    case value_t::number_integer: {\n      if (j.m_value.number_integer >= 0) {\n        // CBOR does not differentiate between positive signed\n        // integers and unsigned integers. Therefore, we used the\n        // code from the value_t::number_unsigned case here.\n        if (j.m_value.number_integer <= 0x17) {\n          write_number(static_cast<std::uint8_t>(j.m_value.number_integer));\n        } else if (j.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)\n                   ()) {\n          oa->write_character(to_char_type(0x18));\n          write_number(static_cast<std::uint8_t>(j.m_value.number_integer));\n        } else if (j.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)\n                   ()) {\n          oa->write_character(to_char_type(0x19));\n          write_number(static_cast<std::uint16_t>(j.m_value.number_integer));\n        } else if (j.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)\n                   ()) {\n          oa->write_character(to_char_type(0x1A));\n          write_number(static_cast<std::uint32_t>(j.m_value.number_integer));\n        } else {\n          oa->write_character(to_char_type(0x1B));\n          write_number(static_cast<std::uint64_t>(j.m_value.number_integer));\n        }\n      } else {\n        // The conversions below encode the sign in the first\n        // byte, and the value is converted to a positive number.\n        const auto positive_number = -1 - j.m_value.number_integer;\n\n        if (j.m_value.number_integer >= -24) {\n          write_number(static_cast<std::uint8_t>(0x20 + positive_number));\n        } else if (positive_number <= (std::numeric_limits<std::uint8_t>::max)()) {\n          oa->write_character(to_char_type(0x38));\n          write_number(static_cast<std::uint8_t>(positive_number));\n        } else if (positive_number <= (std::numeric_limits<std::uint16_t>::max)()) {\n          oa->write_character(to_char_type(0x39));\n          write_number(static_cast<std::uint16_t>(positive_number));\n        } else if (positive_number <= (std::numeric_limits<std::uint32_t>::max)()) {\n          oa->write_character(to_char_type(0x3A));\n          write_number(static_cast<std::uint32_t>(positive_number));\n        } else {\n          oa->write_character(to_char_type(0x3B));\n          write_number(static_cast<std::uint64_t>(positive_number));\n        }\n      }\n\n      break;\n    }\n\n    case value_t::number_unsigned: {\n      if (j.m_value.number_unsigned <= 0x17) {\n        write_number(static_cast<std::uint8_t>(j.m_value.number_unsigned));\n      } else if (j.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)\n                 ()) {\n        oa->write_character(to_char_type(0x18));\n        write_number(static_cast<std::uint8_t>(j.m_value.number_unsigned));\n      } else if (j.m_value.number_unsigned <=\n                 (std::numeric_limits<std::uint16_t>::max)()) {\n        oa->write_character(to_char_type(0x19));\n        write_number(static_cast<std::uint16_t>(j.m_value.number_unsigned));\n      } else if (j.m_value.number_unsigned <=\n                 (std::numeric_limits<std::uint32_t>::max)()) {\n        oa->write_character(to_char_type(0x1A));\n        write_number(static_cast<std::uint32_t>(j.m_value.number_unsigned));\n      } else {\n        oa->write_character(to_char_type(0x1B));\n        write_number(static_cast<std::uint64_t>(j.m_value.number_unsigned));\n      }\n\n      break;\n    }\n\n    case value_t::number_float: {\n      if (std::isnan(j.m_value.number_float)) {\n        // NaN is 0xf97e00 in CBOR\n        oa->write_character(to_char_type(0xF9));\n        oa->write_character(to_char_type(0x7E));\n        oa->write_character(to_char_type(0x00));\n      } else if (std::isinf(j.m_value.number_float)) {\n        // Infinity is 0xf97c00, -Infinity is 0xf9fc00\n        oa->write_character(to_char_type(0xf9));\n        oa->write_character(j.m_value.number_float > 0 ? to_char_type(\n                              0x7C) : to_char_type(0xFC));\n        oa->write_character(to_char_type(0x00));\n      } else {\n        write_compact_float(j.m_value.number_float, detail::input_format_t::cbor);\n      }\n\n      break;\n    }\n\n    case value_t::string: {\n      // step 1: write control byte and the string length\n      const auto N = j.m_value.string->size();\n\n      if (N <= 0x17) {\n        write_number(static_cast<std::uint8_t>(0x60 + N));\n      } else if (N <= (std::numeric_limits<std::uint8_t>::max)()) {\n        oa->write_character(to_char_type(0x78));\n        write_number(static_cast<std::uint8_t>(N));\n      } else if (N <= (std::numeric_limits<std::uint16_t>::max)()) {\n        oa->write_character(to_char_type(0x79));\n        write_number(static_cast<std::uint16_t>(N));\n      } else if (N <= (std::numeric_limits<std::uint32_t>::max)()) {\n        oa->write_character(to_char_type(0x7A));\n        write_number(static_cast<std::uint32_t>(N));\n      }\n      // LCOV_EXCL_START\n      else if (N <= (std::numeric_limits<std::uint64_t>::max)()) {\n        oa->write_character(to_char_type(0x7B));\n        write_number(static_cast<std::uint64_t>(N));\n      }\n\n      // LCOV_EXCL_STOP\n      // step 2: write the string\n      oa->write_characters(\n        reinterpret_cast<const CharType*>(j.m_value.string->c_str()),\n        j.m_value.string->size());\n      break;\n    }\n\n    case value_t::array: {\n      // step 1: write control byte and the array size\n      const auto N = j.m_value.array->size();\n\n      if (N <= 0x17) {\n        write_number(static_cast<std::uint8_t>(0x80 + N));\n      } else if (N <= (std::numeric_limits<std::uint8_t>::max)()) {\n        oa->write_character(to_char_type(0x98));\n        write_number(static_cast<std::uint8_t>(N));\n      } else if (N <= (std::numeric_limits<std::uint16_t>::max)()) {\n        oa->write_character(to_char_type(0x99));\n        write_number(static_cast<std::uint16_t>(N));\n      } else if (N <= (std::numeric_limits<std::uint32_t>::max)()) {\n        oa->write_character(to_char_type(0x9A));\n        write_number(static_cast<std::uint32_t>(N));\n      }\n      // LCOV_EXCL_START\n      else if (N <= (std::numeric_limits<std::uint64_t>::max)()) {\n        oa->write_character(to_char_type(0x9B));\n        write_number(static_cast<std::uint64_t>(N));\n      }\n\n      // LCOV_EXCL_STOP\n\n      // step 2: write each element\n      for (const auto& el : *j.m_value.array) {\n        write_cbor(el);\n      }\n\n      break;\n    }\n\n    case value_t::binary: {\n      if (j.m_value.binary->has_subtype()) {\n        if (j.m_value.binary->subtype() <= (std::numeric_limits<std::uint8_t>::max)()) {\n          write_number(static_cast<std::uint8_t>(0xd8));\n          write_number(static_cast<std::uint8_t>(j.m_value.binary->subtype()));\n        } else if (j.m_value.binary->subtype() <=\n                   (std::numeric_limits<std::uint16_t>::max)()) {\n          write_number(static_cast<std::uint8_t>(0xd9));\n          write_number(static_cast<std::uint16_t>(j.m_value.binary->subtype()));\n        } else if (j.m_value.binary->subtype() <=\n                   (std::numeric_limits<std::uint32_t>::max)()) {\n          write_number(static_cast<std::uint8_t>(0xda));\n          write_number(static_cast<std::uint32_t>(j.m_value.binary->subtype()));\n        } else if (j.m_value.binary->subtype() <=\n                   (std::numeric_limits<std::uint64_t>::max)()) {\n          write_number(static_cast<std::uint8_t>(0xdb));\n          write_number(static_cast<std::uint64_t>(j.m_value.binary->subtype()));\n        }\n      }\n\n      // step 1: write control byte and the binary array size\n      const auto N = j.m_value.binary->size();\n\n      if (N <= 0x17) {\n        write_number(static_cast<std::uint8_t>(0x40 + N));\n      } else if (N <= (std::numeric_limits<std::uint8_t>::max)()) {\n        oa->write_character(to_char_type(0x58));\n        write_number(static_cast<std::uint8_t>(N));\n      } else if (N <= (std::numeric_limits<std::uint16_t>::max)()) {\n        oa->write_character(to_char_type(0x59));\n        write_number(static_cast<std::uint16_t>(N));\n      } else if (N <= (std::numeric_limits<std::uint32_t>::max)()) {\n        oa->write_character(to_char_type(0x5A));\n        write_number(static_cast<std::uint32_t>(N));\n      }\n      // LCOV_EXCL_START\n      else if (N <= (std::numeric_limits<std::uint64_t>::max)()) {\n        oa->write_character(to_char_type(0x5B));\n        write_number(static_cast<std::uint64_t>(N));\n      }\n\n      // LCOV_EXCL_STOP\n      // step 2: write each element\n      oa->write_characters(\n        reinterpret_cast<const CharType*>(j.m_value.binary->data()),\n        N);\n      break;\n    }\n\n    case value_t::object: {\n      // step 1: write control byte and the object size\n      const auto N = j.m_value.object->size();\n\n      if (N <= 0x17) {\n        write_number(static_cast<std::uint8_t>(0xA0 + N));\n      } else if (N <= (std::numeric_limits<std::uint8_t>::max)()) {\n        oa->write_character(to_char_type(0xB8));\n        write_number(static_cast<std::uint8_t>(N));\n      } else if (N <= (std::numeric_limits<std::uint16_t>::max)()) {\n        oa->write_character(to_char_type(0xB9));\n        write_number(static_cast<std::uint16_t>(N));\n      } else if (N <= (std::numeric_limits<std::uint32_t>::max)()) {\n        oa->write_character(to_char_type(0xBA));\n        write_number(static_cast<std::uint32_t>(N));\n      }\n      // LCOV_EXCL_START\n      else if (N <= (std::numeric_limits<std::uint64_t>::max)()) {\n        oa->write_character(to_char_type(0xBB));\n        write_number(static_cast<std::uint64_t>(N));\n      }\n\n      // LCOV_EXCL_STOP\n\n      // step 2: write each element\n      for (const auto& el : *j.m_value.object) {\n        write_cbor(el.first);\n        write_cbor(el.second);\n      }\n\n      break;\n    }\n\n    case value_t::discarded:\n    default:\n      break;\n    }\n  }\n\n  /*!\n  @param[in] j  JSON value to serialize\n  */\n  void write_msgpack(const BasicJsonType& j)\n  {\n    switch (j.type()) {\n    case value_t::null: { // nil\n      oa->write_character(to_char_type(0xC0));\n      break;\n    }\n\n    case value_t::boolean: { // true and false\n      oa->write_character(j.m_value.boolean\n                          ? to_char_type(0xC3)\n                          : to_char_type(0xC2));\n      break;\n    }\n\n    case value_t::number_integer: {\n      if (j.m_value.number_integer >= 0) {\n        // MessagePack does not differentiate between positive\n        // signed integers and unsigned integers. Therefore, we used\n        // the code from the value_t::number_unsigned case here.\n        if (j.m_value.number_unsigned < 128) {\n          // positive fixnum\n          write_number(static_cast<std::uint8_t>(j.m_value.number_integer));\n        } else if (j.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)\n                   ()) {\n          // uint 8\n          oa->write_character(to_char_type(0xCC));\n          write_number(static_cast<std::uint8_t>(j.m_value.number_integer));\n        } else if (j.m_value.number_unsigned <=\n                   (std::numeric_limits<std::uint16_t>::max)()) {\n          // uint 16\n          oa->write_character(to_char_type(0xCD));\n          write_number(static_cast<std::uint16_t>(j.m_value.number_integer));\n        } else if (j.m_value.number_unsigned <=\n                   (std::numeric_limits<std::uint32_t>::max)()) {\n          // uint 32\n          oa->write_character(to_char_type(0xCE));\n          write_number(static_cast<std::uint32_t>(j.m_value.number_integer));\n        } else if (j.m_value.number_unsigned <=\n                   (std::numeric_limits<std::uint64_t>::max)()) {\n          // uint 64\n          oa->write_character(to_char_type(0xCF));\n          write_number(static_cast<std::uint64_t>(j.m_value.number_integer));\n        }\n      } else {\n        if (j.m_value.number_integer >= -32) {\n          // negative fixnum\n          write_number(static_cast<std::int8_t>(j.m_value.number_integer));\n        } else if (j.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)()\n                   &&\n                   j.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)()) {\n          // int 8\n          oa->write_character(to_char_type(0xD0));\n          write_number(static_cast<std::int8_t>(j.m_value.number_integer));\n        } else if (j.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)\n                   () &&\n                   j.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)()) {\n          // int 16\n          oa->write_character(to_char_type(0xD1));\n          write_number(static_cast<std::int16_t>(j.m_value.number_integer));\n        } else if (j.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)\n                   () &&\n                   j.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)()) {\n          // int 32\n          oa->write_character(to_char_type(0xD2));\n          write_number(static_cast<std::int32_t>(j.m_value.number_integer));\n        } else if (j.m_value.number_integer >= (std::numeric_limits<std::int64_t>::min)\n                   () &&\n                   j.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)()) {\n          // int 64\n          oa->write_character(to_char_type(0xD3));\n          write_number(static_cast<std::int64_t>(j.m_value.number_integer));\n        }\n      }\n\n      break;\n    }\n\n    case value_t::number_unsigned: {\n      if (j.m_value.number_unsigned < 128) {\n        // positive fixnum\n        write_number(static_cast<std::uint8_t>(j.m_value.number_integer));\n      } else if (j.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)\n                 ()) {\n        // uint 8\n        oa->write_character(to_char_type(0xCC));\n        write_number(static_cast<std::uint8_t>(j.m_value.number_integer));\n      } else if (j.m_value.number_unsigned <=\n                 (std::numeric_limits<std::uint16_t>::max)()) {\n        // uint 16\n        oa->write_character(to_char_type(0xCD));\n        write_number(static_cast<std::uint16_t>(j.m_value.number_integer));\n      } else if (j.m_value.number_unsigned <=\n                 (std::numeric_limits<std::uint32_t>::max)()) {\n        // uint 32\n        oa->write_character(to_char_type(0xCE));\n        write_number(static_cast<std::uint32_t>(j.m_value.number_integer));\n      } else if (j.m_value.number_unsigned <=\n                 (std::numeric_limits<std::uint64_t>::max)()) {\n        // uint 64\n        oa->write_character(to_char_type(0xCF));\n        write_number(static_cast<std::uint64_t>(j.m_value.number_integer));\n      }\n\n      break;\n    }\n\n    case value_t::number_float: {\n      write_compact_float(j.m_value.number_float, detail::input_format_t::msgpack);\n      break;\n    }\n\n    case value_t::string: {\n      // step 1: write control byte and the string length\n      const auto N = j.m_value.string->size();\n\n      if (N <= 31) {\n        // fixstr\n        write_number(static_cast<std::uint8_t>(0xA0 | N));\n      } else if (N <= (std::numeric_limits<std::uint8_t>::max)()) {\n        // str 8\n        oa->write_character(to_char_type(0xD9));\n        write_number(static_cast<std::uint8_t>(N));\n      } else if (N <= (std::numeric_limits<std::uint16_t>::max)()) {\n        // str 16\n        oa->write_character(to_char_type(0xDA));\n        write_number(static_cast<std::uint16_t>(N));\n      } else if (N <= (std::numeric_limits<std::uint32_t>::max)()) {\n        // str 32\n        oa->write_character(to_char_type(0xDB));\n        write_number(static_cast<std::uint32_t>(N));\n      }\n\n      // step 2: write the string\n      oa->write_characters(\n        reinterpret_cast<const CharType*>(j.m_value.string->c_str()),\n        j.m_value.string->size());\n      break;\n    }\n\n    case value_t::array: {\n      // step 1: write control byte and the array size\n      const auto N = j.m_value.array->size();\n\n      if (N <= 15) {\n        // fixarray\n        write_number(static_cast<std::uint8_t>(0x90 | N));\n      } else if (N <= (std::numeric_limits<std::uint16_t>::max)()) {\n        // array 16\n        oa->write_character(to_char_type(0xDC));\n        write_number(static_cast<std::uint16_t>(N));\n      } else if (N <= (std::numeric_limits<std::uint32_t>::max)()) {\n        // array 32\n        oa->write_character(to_char_type(0xDD));\n        write_number(static_cast<std::uint32_t>(N));\n      }\n\n      // step 2: write each element\n      for (const auto& el : *j.m_value.array) {\n        write_msgpack(el);\n      }\n\n      break;\n    }\n\n    case value_t::binary: {\n      // step 0: determine if the binary type has a set subtype to\n      // determine whether or not to use the ext or fixext types\n      const bool use_ext = j.m_value.binary->has_subtype();\n      // step 1: write control byte and the byte string length\n      const auto N = j.m_value.binary->size();\n\n      if (N <= (std::numeric_limits<std::uint8_t>::max)()) {\n        std::uint8_t output_type{};\n        bool fixed = true;\n\n        if (use_ext) {\n          switch (N) {\n          case 1:\n            output_type = 0xD4; // fixext 1\n            break;\n\n          case 2:\n            output_type = 0xD5; // fixext 2\n            break;\n\n          case 4:\n            output_type = 0xD6; // fixext 4\n            break;\n\n          case 8:\n            output_type = 0xD7; // fixext 8\n            break;\n\n          case 16:\n            output_type = 0xD8; // fixext 16\n            break;\n\n          default:\n            output_type = 0xC7; // ext 8\n            fixed = false;\n            break;\n          }\n        } else {\n          output_type = 0xC4; // bin 8\n          fixed = false;\n        }\n\n        oa->write_character(to_char_type(output_type));\n\n        if (!fixed) {\n          write_number(static_cast<std::uint8_t>(N));\n        }\n      } else if (N <= (std::numeric_limits<std::uint16_t>::max)()) {\n        std::uint8_t output_type = use_ext\n                                   ? 0xC8 // ext 16\n                                   : 0xC5; // bin 16\n        oa->write_character(to_char_type(output_type));\n        write_number(static_cast<std::uint16_t>(N));\n      } else if (N <= (std::numeric_limits<std::uint32_t>::max)()) {\n        std::uint8_t output_type = use_ext\n                                   ? 0xC9 // ext 32\n                                   : 0xC6; // bin 32\n        oa->write_character(to_char_type(output_type));\n        write_number(static_cast<std::uint32_t>(N));\n      }\n\n      // step 1.5: if this is an ext type, write the subtype\n      if (use_ext) {\n        write_number(static_cast<std::int8_t>(j.m_value.binary->subtype()));\n      }\n\n      // step 2: write the byte string\n      oa->write_characters(\n        reinterpret_cast<const CharType*>(j.m_value.binary->data()),\n        N);\n      break;\n    }\n\n    case value_t::object: {\n      // step 1: write control byte and the object size\n      const auto N = j.m_value.object->size();\n\n      if (N <= 15) {\n        // fixmap\n        write_number(static_cast<std::uint8_t>(0x80 | (N & 0xF)));\n      } else if (N <= (std::numeric_limits<std::uint16_t>::max)()) {\n        // map 16\n        oa->write_character(to_char_type(0xDE));\n        write_number(static_cast<std::uint16_t>(N));\n      } else if (N <= (std::numeric_limits<std::uint32_t>::max)()) {\n        // map 32\n        oa->write_character(to_char_type(0xDF));\n        write_number(static_cast<std::uint32_t>(N));\n      }\n\n      // step 2: write each element\n      for (const auto& el : *j.m_value.object) {\n        write_msgpack(el.first);\n        write_msgpack(el.second);\n      }\n\n      break;\n    }\n\n    case value_t::discarded:\n    default:\n      break;\n    }\n  }\n\n  /*!\n  @param[in] j  JSON value to serialize\n  @param[in] use_count   whether to use '#' prefixes (optimized format)\n  @param[in] use_type    whether to use '$' prefixes (optimized format)\n  @param[in] add_prefix  whether prefixes need to be used for this value\n  @param[in] use_bjdata  whether write in BJData format, default is false\n  */\n  void write_ubjson(const BasicJsonType& j, const bool use_count,\n                    const bool use_type, const bool add_prefix = true,\n                    const bool use_bjdata = false)\n  {\n    switch (j.type()) {\n    case value_t::null: {\n      if (add_prefix) {\n        oa->write_character(to_char_type('Z'));\n      }\n\n      break;\n    }\n\n    case value_t::boolean: {\n      if (add_prefix) {\n        oa->write_character(j.m_value.boolean\n                            ? to_char_type('T')\n                            : to_char_type('F'));\n      }\n\n      break;\n    }\n\n    case value_t::number_integer: {\n      write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix,\n                                      use_bjdata);\n      break;\n    }\n\n    case value_t::number_unsigned: {\n      write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix,\n                                      use_bjdata);\n      break;\n    }\n\n    case value_t::number_float: {\n      write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix, use_bjdata);\n      break;\n    }\n\n    case value_t::string: {\n      if (add_prefix) {\n        oa->write_character(to_char_type('S'));\n      }\n\n      write_number_with_ubjson_prefix(j.m_value.string->size(), true, use_bjdata);\n      oa->write_characters(\n        reinterpret_cast<const CharType*>(j.m_value.string->c_str()),\n        j.m_value.string->size());\n      break;\n    }\n\n    case value_t::array: {\n      if (add_prefix) {\n        oa->write_character(to_char_type('['));\n      }\n\n      bool prefix_required = true;\n\n      if (use_type && !j.m_value.array->empty()) {\n        JSON_ASSERT(use_count);\n        const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);\n        const bool same_prefix = std::all_of(j.begin() + 1, j.end(),\n        [this, first_prefix, use_bjdata](const BasicJsonType & v) {\n          return ubjson_prefix(v, use_bjdata) == first_prefix;\n        });\n        std::vector<CharType> bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type\n\n        if (same_prefix && !(use_bjdata &&\n                             std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end())) {\n          prefix_required = false;\n          oa->write_character(to_char_type('$'));\n          oa->write_character(first_prefix);\n        }\n      }\n\n      if (use_count) {\n        oa->write_character(to_char_type('#'));\n        write_number_with_ubjson_prefix(j.m_value.array->size(), true, use_bjdata);\n      }\n\n      for (const auto& el : *j.m_value.array) {\n        write_ubjson(el, use_count, use_type, prefix_required, use_bjdata);\n      }\n\n      if (!use_count) {\n        oa->write_character(to_char_type(']'));\n      }\n\n      break;\n    }\n\n    case value_t::binary: {\n      if (add_prefix) {\n        oa->write_character(to_char_type('['));\n      }\n\n      if (use_type && !j.m_value.binary->empty()) {\n        JSON_ASSERT(use_count);\n        oa->write_character(to_char_type('$'));\n        oa->write_character('U');\n      }\n\n      if (use_count) {\n        oa->write_character(to_char_type('#'));\n        write_number_with_ubjson_prefix(j.m_value.binary->size(), true, use_bjdata);\n      }\n\n      if (use_type) {\n        oa->write_characters(\n          reinterpret_cast<const CharType*>(j.m_value.binary->data()),\n          j.m_value.binary->size());\n      } else {\n        for (size_t i = 0; i < j.m_value.binary->size(); ++i) {\n          oa->write_character(to_char_type('U'));\n          oa->write_character(j.m_value.binary->data()[i]);\n        }\n      }\n\n      if (!use_count) {\n        oa->write_character(to_char_type(']'));\n      }\n\n      break;\n    }\n\n    case value_t::object: {\n      if (use_bjdata && j.m_value.object->size() == 3 &&\n          j.m_value.object->find(\"_ArrayType_\") != j.m_value.object->end() &&\n          j.m_value.object->find(\"_ArraySize_\") != j.m_value.object->end() &&\n          j.m_value.object->find(\"_ArrayData_\") != j.m_value.object->end()) {\n        if (!write_bjdata_ndarray(*j.m_value.object, use_count,\n                                  use_type)) { // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata)\n          break;\n        }\n      }\n\n      if (add_prefix) {\n        oa->write_character(to_char_type('{'));\n      }\n\n      bool prefix_required = true;\n\n      if (use_type && !j.m_value.object->empty()) {\n        JSON_ASSERT(use_count);\n        const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);\n        const bool same_prefix = std::all_of(j.begin(), j.end(),\n        [this, first_prefix, use_bjdata](const BasicJsonType & v) {\n          return ubjson_prefix(v, use_bjdata) == first_prefix;\n        });\n        std::vector<CharType> bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type\n\n        if (same_prefix && !(use_bjdata &&\n                             std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end())) {\n          prefix_required = false;\n          oa->write_character(to_char_type('$'));\n          oa->write_character(first_prefix);\n        }\n      }\n\n      if (use_count) {\n        oa->write_character(to_char_type('#'));\n        write_number_with_ubjson_prefix(j.m_value.object->size(), true, use_bjdata);\n      }\n\n      for (const auto& el : *j.m_value.object) {\n        write_number_with_ubjson_prefix(el.first.size(), true, use_bjdata);\n        oa->write_characters(\n          reinterpret_cast<const CharType*>(el.first.c_str()),\n          el.first.size());\n        write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata);\n      }\n\n      if (!use_count) {\n        oa->write_character(to_char_type('}'));\n      }\n\n      break;\n    }\n\n    case value_t::discarded:\n    default:\n      break;\n    }\n  }\n\nprivate:\n  //////////\n  // BSON //\n  //////////\n\n  /*!\n  @return The size of a BSON document entry header, including the id marker\n          and the entry name size (and its null-terminator).\n  */\n  static std::size_t calc_bson_entry_header_size(const string_t& name,\n      const BasicJsonType& j)\n  {\n    const auto it = name.find(static_cast<typename string_t::value_type>(0));\n\n    if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) {\n      JSON_THROW(out_of_range::create(409,\n                                      concat(\"BSON key cannot contain code point U+0000 (at byte \",\n                                             std::to_string(it), \")\"), &j));\n      static_cast<void>(j);\n    }\n\n    return /*id*/ 1ul + name.size() + /*zero-terminator*/1u;\n  }\n\n  /*!\n  @brief Writes the given @a element_type and @a name to the output adapter\n  */\n  void write_bson_entry_header(const string_t& name,\n                               const std::uint8_t element_type)\n  {\n    oa->write_character(to_char_type(element_type)); // boolean\n    oa->write_characters(\n      reinterpret_cast<const CharType*>(name.c_str()),\n      name.size() + 1u);\n  }\n\n  /*!\n  @brief Writes a BSON element with key @a name and boolean value @a value\n  */\n  void write_bson_boolean(const string_t& name,\n                          const bool value)\n  {\n    write_bson_entry_header(name, 0x08);\n    oa->write_character(value ? to_char_type(0x01) : to_char_type(0x00));\n  }\n\n  /*!\n  @brief Writes a BSON element with key @a name and double value @a value\n  */\n  void write_bson_double(const string_t& name,\n                         const double value)\n  {\n    write_bson_entry_header(name, 0x01);\n    write_number<double>(value, true);\n  }\n\n  /*!\n  @return The size of the BSON-encoded string in @a value\n  */\n  static std::size_t calc_bson_string_size(const string_t& value)\n  {\n    return sizeof(std::int32_t) + value.size() + 1ul;\n  }\n\n  /*!\n  @brief Writes a BSON element with key @a name and string value @a value\n  */\n  void write_bson_string(const string_t& name,\n                         const string_t& value)\n  {\n    write_bson_entry_header(name, 0x02);\n    write_number<std::int32_t>(static_cast<std::int32_t>(value.size() + 1ul), true);\n    oa->write_characters(\n      reinterpret_cast<const CharType*>(value.c_str()),\n      value.size() + 1);\n  }\n\n  /*!\n  @brief Writes a BSON element with key @a name and null value\n  */\n  void write_bson_null(const string_t& name)\n  {\n    write_bson_entry_header(name, 0x0A);\n  }\n\n  /*!\n  @return The size of the BSON-encoded integer @a value\n  */\n  static std::size_t calc_bson_integer_size(const std::int64_t value)\n  {\n    return (std::numeric_limits<std::int32_t>::min)() <= value &&\n           value <= (std::numeric_limits<std::int32_t>::max)()\n           ? sizeof(std::int32_t)\n           : sizeof(std::int64_t);\n  }\n\n  /*!\n  @brief Writes a BSON element with key @a name and integer @a value\n  */\n  void write_bson_integer(const string_t& name,\n                          const std::int64_t value)\n  {\n    if ((std::numeric_limits<std::int32_t>::min)() <= value &&\n        value <= (std::numeric_limits<std::int32_t>::max)()) {\n      write_bson_entry_header(name, 0x10); // int32\n      write_number<std::int32_t>(static_cast<std::int32_t>(value), true);\n    } else {\n      write_bson_entry_header(name, 0x12); // int64\n      write_number<std::int64_t>(static_cast<std::int64_t>(value), true);\n    }\n  }\n\n  /*!\n  @return The size of the BSON-encoded unsigned integer in @a j\n  */\n  static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value)\n  noexcept\n  {\n    return (value <= static_cast<std::uint64_t>((\n              std::numeric_limits<std::int32_t>::max)()))\n           ? sizeof(std::int32_t)\n           : sizeof(std::int64_t);\n  }\n\n  /*!\n  @brief Writes a BSON element with key @a name and unsigned @a value\n  */\n  void write_bson_unsigned(const string_t& name,\n                           const BasicJsonType& j)\n  {\n    if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((\n          std::numeric_limits<std::int32_t>::max)())) {\n      write_bson_entry_header(name, 0x10 /* int32 */);\n      write_number<std::int32_t>(static_cast<std::int32_t>(j.m_value.number_unsigned),\n                                 true);\n    } else if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((\n                 std::numeric_limits<std::int64_t>::max)())) {\n      write_bson_entry_header(name, 0x12 /* int64 */);\n      write_number<std::int64_t>(static_cast<std::int64_t>(j.m_value.number_unsigned),\n                                 true);\n    } else {\n      JSON_THROW(out_of_range::create(407, concat(\"integer number \",\n                                      std::to_string(j.m_value.number_unsigned),\n                                      \" cannot be represented by BSON as it does not fit int64\"), &j));\n    }\n  }\n\n  /*!\n  @brief Writes a BSON element with key @a name and object @a value\n  */\n  void write_bson_object_entry(const string_t& name,\n                               const typename BasicJsonType::object_t& value)\n  {\n    write_bson_entry_header(name, 0x03); // object\n    write_bson_object(value);\n  }\n\n  /*!\n  @return The size of the BSON-encoded array @a value\n  */\n  static std::size_t calc_bson_array_size(const typename BasicJsonType::array_t&\n                                          value)\n  {\n    std::size_t array_index = 0ul;\n    const std::size_t embedded_document_size = std::accumulate(std::begin(value),\n        std::end(value), static_cast<std::size_t>(0), [&array_index](std::size_t result,\n    const typename BasicJsonType::array_t::value_type & el) {\n      return result + calc_bson_element_size(std::to_string(array_index++), el);\n    });\n    return sizeof(std::int32_t) + embedded_document_size + 1ul;\n  }\n\n  /*!\n  @return The size of the BSON-encoded binary array @a value\n  */\n  static std::size_t calc_bson_binary_size(const typename BasicJsonType::binary_t&\n      value)\n  {\n    return sizeof(std::int32_t) + value.size() + 1ul;\n  }\n\n  /*!\n  @brief Writes a BSON element with key @a name and array @a value\n  */\n  void write_bson_array(const string_t& name,\n                        const typename BasicJsonType::array_t& value)\n  {\n    write_bson_entry_header(name, 0x04); // array\n    write_number<std::int32_t>(static_cast<std::int32_t>(calc_bson_array_size(\n                                 value)), true);\n    std::size_t array_index = 0ul;\n\n    for (const auto& el : value) {\n      write_bson_element(std::to_string(array_index++), el);\n    }\n\n    oa->write_character(to_char_type(0x00));\n  }\n\n  /*!\n  @brief Writes a BSON element with key @a name and binary value @a value\n  */\n  void write_bson_binary(const string_t& name,\n                         const binary_t& value)\n  {\n    write_bson_entry_header(name, 0x05);\n    write_number<std::int32_t>(static_cast<std::int32_t>(value.size()), true);\n    write_number(value.has_subtype() ? static_cast<std::uint8_t>\n                 (value.subtype()) : static_cast<std::uint8_t>(0x00));\n    oa->write_characters(reinterpret_cast<const CharType*>(value.data()),\n                         value.size());\n  }\n\n  /*!\n  @brief Calculates the size necessary to serialize the JSON value @a j with its @a name\n  @return The calculated size for the BSON document entry for @a j with the given @a name.\n  */\n  static std::size_t calc_bson_element_size(const string_t& name,\n      const BasicJsonType& j)\n  {\n    const auto header_size = calc_bson_entry_header_size(name, j);\n\n    switch (j.type()) {\n    case value_t::object:\n      return header_size + calc_bson_object_size(*j.m_value.object);\n\n    case value_t::array:\n      return header_size + calc_bson_array_size(*j.m_value.array);\n\n    case value_t::binary:\n      return header_size + calc_bson_binary_size(*j.m_value.binary);\n\n    case value_t::boolean:\n      return header_size + 1ul;\n\n    case value_t::number_float:\n      return header_size + 8ul;\n\n    case value_t::number_integer:\n      return header_size + calc_bson_integer_size(j.m_value.number_integer);\n\n    case value_t::number_unsigned:\n      return header_size + calc_bson_unsigned_size(j.m_value.number_unsigned);\n\n    case value_t::string:\n      return header_size + calc_bson_string_size(*j.m_value.string);\n\n    case value_t::null:\n      return header_size + 0ul;\n\n    // LCOV_EXCL_START\n    case value_t::discarded:\n    default:\n      JSON_ASSERT(\n        false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)\n      return 0ul;\n      // LCOV_EXCL_STOP\n    }\n  }\n\n  /*!\n  @brief Serializes the JSON value @a j to BSON and associates it with the\n         key @a name.\n  @param name The name to associate with the JSON entity @a j within the\n              current BSON document\n  */\n  void write_bson_element(const string_t& name,\n                          const BasicJsonType& j)\n  {\n    switch (j.type()) {\n    case value_t::object:\n      return write_bson_object_entry(name, *j.m_value.object);\n\n    case value_t::array:\n      return write_bson_array(name, *j.m_value.array);\n\n    case value_t::binary:\n      return write_bson_binary(name, *j.m_value.binary);\n\n    case value_t::boolean:\n      return write_bson_boolean(name, j.m_value.boolean);\n\n    case value_t::number_float:\n      return write_bson_double(name, j.m_value.number_float);\n\n    case value_t::number_integer:\n      return write_bson_integer(name, j.m_value.number_integer);\n\n    case value_t::number_unsigned:\n      return write_bson_unsigned(name, j);\n\n    case value_t::string:\n      return write_bson_string(name, *j.m_value.string);\n\n    case value_t::null:\n      return write_bson_null(name);\n\n    // LCOV_EXCL_START\n    case value_t::discarded:\n    default:\n      JSON_ASSERT(\n        false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)\n      return;\n      // LCOV_EXCL_STOP\n    }\n  }\n\n  /*!\n  @brief Calculates the size of the BSON serialization of the given\n         JSON-object @a j.\n  @param[in] value  JSON value to serialize\n  @pre       value.type() == value_t::object\n  */\n  static std::size_t calc_bson_object_size(const typename BasicJsonType::object_t&\n      value)\n  {\n    std::size_t document_size = std::accumulate(value.begin(), value.end(),\n                                static_cast<std::size_t>(0),\n    [](size_t result, const typename BasicJsonType::object_t::value_type & el) {\n      return result += calc_bson_element_size(el.first, el.second);\n    });\n    return sizeof(std::int32_t) + document_size + 1ul;\n  }\n\n  /*!\n  @param[in] value  JSON value to serialize\n  @pre       value.type() == value_t::object\n  */\n  void write_bson_object(const typename BasicJsonType::object_t& value)\n  {\n    write_number<std::int32_t>(static_cast<std::int32_t>(calc_bson_object_size(\n                                 value)), true);\n\n    for (const auto& el : value) {\n      write_bson_element(el.first, el.second);\n    }\n\n    oa->write_character(to_char_type(0x00));\n  }\n\n  //////////\n  // CBOR //\n  //////////\n\n  static constexpr CharType get_cbor_float_prefix(float /*unused*/)\n  {\n    return to_char_type(0xFA);  // Single-Precision Float\n  }\n\n  static constexpr CharType get_cbor_float_prefix(double /*unused*/)\n  {\n    return to_char_type(0xFB);  // Double-Precision Float\n  }\n\n  /////////////\n  // MsgPack //\n  /////////////\n\n  static constexpr CharType get_msgpack_float_prefix(float /*unused*/)\n  {\n    return to_char_type(0xCA);  // float 32\n  }\n\n  static constexpr CharType get_msgpack_float_prefix(double /*unused*/)\n  {\n    return to_char_type(0xCB);  // float 64\n  }\n\n  ////////////\n  // UBJSON //\n  ////////////\n\n  // UBJSON: write number (floating point)\n  template<typename NumberType, typename std::enable_if<\n             std::is_floating_point<NumberType>::value, int>::type = 0>\n  void write_number_with_ubjson_prefix(const NumberType n,\n                                       const bool add_prefix,\n                                       const bool use_bjdata)\n  {\n    if (add_prefix) {\n      oa->write_character(get_ubjson_float_prefix(n));\n    }\n\n    write_number(n, use_bjdata);\n  }\n\n  // UBJSON: write number (unsigned integer)\n  template<typename NumberType, typename std::enable_if<\n             std::is_unsigned<NumberType>::value, int>::type = 0>\n  void write_number_with_ubjson_prefix(const NumberType n,\n                                       const bool add_prefix,\n                                       const bool use_bjdata)\n  {\n    if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int8_t>::max)\n                                        ())) {\n      if (add_prefix) {\n        oa->write_character(to_char_type('i'));  // int8\n      }\n\n      write_number(static_cast<std::uint8_t>(n), use_bjdata);\n    } else if (n <= (std::numeric_limits<std::uint8_t>::max)()) {\n      if (add_prefix) {\n        oa->write_character(to_char_type('U'));  // uint8\n      }\n\n      write_number(static_cast<std::uint8_t>(n), use_bjdata);\n    } else if (n <= static_cast<std::uint64_t>((\n                 std::numeric_limits<std::int16_t>::max)())) {\n      if (add_prefix) {\n        oa->write_character(to_char_type('I'));  // int16\n      }\n\n      write_number(static_cast<std::int16_t>(n), use_bjdata);\n    } else if (use_bjdata &&\n               n <= static_cast<uint64_t>((std::numeric_limits<uint16_t>::max)())) {\n      if (add_prefix) {\n        oa->write_character(to_char_type('u'));  // uint16 - bjdata only\n      }\n\n      write_number(static_cast<std::uint16_t>(n), use_bjdata);\n    } else if (n <= static_cast<std::uint64_t>((\n                 std::numeric_limits<std::int32_t>::max)())) {\n      if (add_prefix) {\n        oa->write_character(to_char_type('l'));  // int32\n      }\n\n      write_number(static_cast<std::int32_t>(n), use_bjdata);\n    } else if (use_bjdata &&\n               n <= static_cast<uint64_t>((std::numeric_limits<uint32_t>::max)())) {\n      if (add_prefix) {\n        oa->write_character(to_char_type('m'));  // uint32 - bjdata only\n      }\n\n      write_number(static_cast<std::uint32_t>(n), use_bjdata);\n    } else if (n <= static_cast<std::uint64_t>((\n                 std::numeric_limits<std::int64_t>::max)())) {\n      if (add_prefix) {\n        oa->write_character(to_char_type('L'));  // int64\n      }\n\n      write_number(static_cast<std::int64_t>(n), use_bjdata);\n    } else if (use_bjdata && n <= (std::numeric_limits<uint64_t>::max)()) {\n      if (add_prefix) {\n        oa->write_character(to_char_type('M'));  // uint64 - bjdata only\n      }\n\n      write_number(static_cast<std::uint64_t>(n), use_bjdata);\n    } else {\n      if (add_prefix) {\n        oa->write_character(to_char_type('H'));  // high-precision number\n      }\n\n      const auto number = BasicJsonType(n).dump();\n      write_number_with_ubjson_prefix(number.size(), true, use_bjdata);\n\n      for (std::size_t i = 0; i < number.size(); ++i) {\n        oa->write_character(to_char_type(static_cast<std::uint8_t>(number[i])));\n      }\n    }\n  }\n\n  // UBJSON: write number (signed integer)\n  template < typename NumberType, typename std::enable_if <\n               std::is_signed<NumberType>::value&&\n               !std::is_floating_point<NumberType>::value, int >::type = 0 >\n  void write_number_with_ubjson_prefix(const NumberType n,\n                                       const bool add_prefix,\n                                       const bool use_bjdata)\n  {\n    if ((std::numeric_limits<std::int8_t>::min)() <= n &&\n        n <= (std::numeric_limits<std::int8_t>::max)()) {\n      if (add_prefix) {\n        oa->write_character(to_char_type('i'));  // int8\n      }\n\n      write_number(static_cast<std::int8_t>(n), use_bjdata);\n    } else if (static_cast<std::int64_t>((std::numeric_limits<std::uint8_t>::min)())\n               <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint8_t>::max)\n                   ())) {\n      if (add_prefix) {\n        oa->write_character(to_char_type('U'));  // uint8\n      }\n\n      write_number(static_cast<std::uint8_t>(n), use_bjdata);\n    } else if ((std::numeric_limits<std::int16_t>::min)() <= n &&\n               n <= (std::numeric_limits<std::int16_t>::max)()) {\n      if (add_prefix) {\n        oa->write_character(to_char_type('I'));  // int16\n      }\n\n      write_number(static_cast<std::int16_t>(n), use_bjdata);\n    } else if (use_bjdata &&\n               (static_cast<std::int64_t>((std::numeric_limits<std::uint16_t>::min)()) <= n &&\n                n <= static_cast<std::int64_t>((std::numeric_limits<std::uint16_t>::max)()))) {\n      if (add_prefix) {\n        oa->write_character(to_char_type('u'));  // uint16 - bjdata only\n      }\n\n      write_number(static_cast<uint16_t>(n), use_bjdata);\n    } else if ((std::numeric_limits<std::int32_t>::min)() <= n &&\n               n <= (std::numeric_limits<std::int32_t>::max)()) {\n      if (add_prefix) {\n        oa->write_character(to_char_type('l'));  // int32\n      }\n\n      write_number(static_cast<std::int32_t>(n), use_bjdata);\n    } else if (use_bjdata &&\n               (static_cast<std::int64_t>((std::numeric_limits<std::uint32_t>::min)()) <= n &&\n                n <= static_cast<std::int64_t>((std::numeric_limits<std::uint32_t>::max)()))) {\n      if (add_prefix) {\n        oa->write_character(to_char_type('m'));  // uint32 - bjdata only\n      }\n\n      write_number(static_cast<uint32_t>(n), use_bjdata);\n    } else if ((std::numeric_limits<std::int64_t>::min)() <= n &&\n               n <= (std::numeric_limits<std::int64_t>::max)()) {\n      if (add_prefix) {\n        oa->write_character(to_char_type('L'));  // int64\n      }\n\n      write_number(static_cast<std::int64_t>(n), use_bjdata);\n    }\n    // LCOV_EXCL_START\n    else {\n      if (add_prefix) {\n        oa->write_character(to_char_type('H'));  // high-precision number\n      }\n\n      const auto number = BasicJsonType(n).dump();\n      write_number_with_ubjson_prefix(number.size(), true, use_bjdata);\n\n      for (std::size_t i = 0; i < number.size(); ++i) {\n        oa->write_character(to_char_type(static_cast<std::uint8_t>(number[i])));\n      }\n    }\n\n    // LCOV_EXCL_STOP\n  }\n\n  /*!\n  @brief determine the type prefix of container values\n  */\n  CharType ubjson_prefix(const BasicJsonType& j,\n                         const bool use_bjdata) const noexcept\n  {\n    switch (j.type()) {\n    case value_t::null:\n      return 'Z';\n\n    case value_t::boolean:\n      return j.m_value.boolean ? 'T' : 'F';\n\n    case value_t::number_integer: {\n      if ((std::numeric_limits<std::int8_t>::min)() <= j.m_value.number_integer &&\n          j.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)()) {\n        return 'i';\n      }\n\n      if ((std::numeric_limits<std::uint8_t>::min)() <= j.m_value.number_integer &&\n          j.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)()) {\n        return 'U';\n      }\n\n      if ((std::numeric_limits<std::int16_t>::min)() <= j.m_value.number_integer &&\n          j.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)()) {\n        return 'I';\n      }\n\n      if (use_bjdata &&\n          ((std::numeric_limits<std::uint16_t>::min)() <= j.m_value.number_integer &&\n           j.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)())) {\n        return 'u';\n      }\n\n      if ((std::numeric_limits<std::int32_t>::min)() <= j.m_value.number_integer &&\n          j.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)()) {\n        return 'l';\n      }\n\n      if (use_bjdata &&\n          ((std::numeric_limits<std::uint32_t>::min)() <= j.m_value.number_integer &&\n           j.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)())) {\n        return 'm';\n      }\n\n      if ((std::numeric_limits<std::int64_t>::min)() <= j.m_value.number_integer &&\n          j.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)()) {\n        return 'L';\n      }\n\n      // anything else is treated as high-precision number\n      return 'H'; // LCOV_EXCL_LINE\n    }\n\n    case value_t::number_unsigned: {\n      if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((\n            std::numeric_limits<std::int8_t>::max)())) {\n        return 'i';\n      }\n\n      if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((\n            std::numeric_limits<std::uint8_t>::max)())) {\n        return 'U';\n      }\n\n      if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((\n            std::numeric_limits<std::int16_t>::max)())) {\n        return 'I';\n      }\n\n      if (use_bjdata &&\n          j.m_value.number_unsigned <= static_cast<std::uint64_t>((\n                std::numeric_limits<std::uint16_t>::max)())) {\n        return 'u';\n      }\n\n      if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((\n            std::numeric_limits<std::int32_t>::max)())) {\n        return 'l';\n      }\n\n      if (use_bjdata &&\n          j.m_value.number_unsigned <= static_cast<std::uint64_t>((\n                std::numeric_limits<std::uint32_t>::max)())) {\n        return 'm';\n      }\n\n      if (j.m_value.number_unsigned <= static_cast<std::uint64_t>((\n            std::numeric_limits<std::int64_t>::max)())) {\n        return 'L';\n      }\n\n      if (use_bjdata &&\n          j.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)()) {\n        return 'M';\n      }\n\n      // anything else is treated as high-precision number\n      return 'H'; // LCOV_EXCL_LINE\n    }\n\n    case value_t::number_float:\n      return get_ubjson_float_prefix(j.m_value.number_float);\n\n    case value_t::string:\n      return 'S';\n\n    case value_t::array: // fallthrough\n    case value_t::binary:\n      return '[';\n\n    case value_t::object:\n      return '{';\n\n    case value_t::discarded:\n    default:  // discarded values\n      return 'N';\n    }\n  }\n\n  static constexpr CharType get_ubjson_float_prefix(float /*unused*/)\n  {\n    return 'd';  // float 32\n  }\n\n  static constexpr CharType get_ubjson_float_prefix(double /*unused*/)\n  {\n    return 'D';  // float 64\n  }\n\n  /*!\n  @return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid\n  */\n  bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value,\n                            const bool use_count, const bool use_type)\n  {\n    std::map<string_t, CharType> bjdtype = {{\"uint8\", 'U'},  {\"int8\", 'i'},  {\"uint16\", 'u'}, {\"int16\", 'I'},\n      {\"uint32\", 'm'}, {\"int32\", 'l'}, {\"uint64\", 'M'}, {\"int64\", 'L'}, {\"single\", 'd'}, {\"double\", 'D'}, {\"char\", 'C'}\n    };\n    string_t key = \"_ArrayType_\";\n    auto it = bjdtype.find(static_cast<string_t>(value.at(key)));\n\n    if (it == bjdtype.end()) {\n      return true;\n    }\n\n    CharType dtype = it->second;\n    key = \"_ArraySize_\";\n    std::size_t len = (value.at(key).empty() ? 0 : 1);\n\n    for (const auto& el : value.at(key)) {\n      len *= static_cast<std::size_t>(el.m_value.number_unsigned);\n    }\n\n    key = \"_ArrayData_\";\n\n    if (value.at(key).size() != len) {\n      return true;\n    }\n\n    oa->write_character('[');\n    oa->write_character('$');\n    oa->write_character(dtype);\n    oa->write_character('#');\n    key = \"_ArraySize_\";\n    write_ubjson(value.at(key), use_count, use_type, true,  true);\n    key = \"_ArrayData_\";\n\n    if (dtype == 'U' || dtype == 'C') {\n      for (const auto& el : value.at(key)) {\n        write_number(static_cast<std::uint8_t>(el.m_value.number_unsigned), true);\n      }\n    } else if (dtype == 'i') {\n      for (const auto& el : value.at(key)) {\n        write_number(static_cast<std::int8_t>(el.m_value.number_integer), true);\n      }\n    } else if (dtype == 'u') {\n      for (const auto& el : value.at(key)) {\n        write_number(static_cast<std::uint16_t>(el.m_value.number_unsigned), true);\n      }\n    } else if (dtype == 'I') {\n      for (const auto& el : value.at(key)) {\n        write_number(static_cast<std::int16_t>(el.m_value.number_integer), true);\n      }\n    } else if (dtype == 'm') {\n      for (const auto& el : value.at(key)) {\n        write_number(static_cast<std::uint32_t>(el.m_value.number_unsigned), true);\n      }\n    } else if (dtype == 'l') {\n      for (const auto& el : value.at(key)) {\n        write_number(static_cast<std::int32_t>(el.m_value.number_integer), true);\n      }\n    } else if (dtype == 'M') {\n      for (const auto& el : value.at(key)) {\n        write_number(static_cast<std::uint64_t>(el.m_value.number_unsigned), true);\n      }\n    } else if (dtype == 'L') {\n      for (const auto& el : value.at(key)) {\n        write_number(static_cast<std::int64_t>(el.m_value.number_integer), true);\n      }\n    } else if (dtype == 'd') {\n      for (const auto& el : value.at(key)) {\n        write_number(static_cast<float>(el.m_value.number_float), true);\n      }\n    } else if (dtype == 'D') {\n      for (const auto& el : value.at(key)) {\n        write_number(static_cast<double>(el.m_value.number_float), true);\n      }\n    }\n\n    return false;\n  }\n\n  ///////////////////////\n  // Utility functions //\n  ///////////////////////\n\n  /*\n  @brief write a number to output input\n  @param[in] n number of type @a NumberType\n  @param[in] OutputIsLittleEndian Set to true if output data is\n                               required to be little endian\n  @tparam NumberType the type of the number\n\n  @note This function needs to respect the system's endianness, because bytes\n        in CBOR, MessagePack, and UBJSON are stored in network order (big\n        endian) and therefore need reordering on little endian systems.\n        On the other hand, BSON and BJData use little endian and should reorder\n        on big endian systems.\n  */\n  template<typename NumberType>\n  void write_number(const NumberType n, const bool OutputIsLittleEndian = false)\n  {\n    // step 1: write number to array of length NumberType\n    std::array<CharType, sizeof(NumberType)> vec{};\n    std::memcpy(vec.data(), &n, sizeof(NumberType));\n\n    // step 2: write array to output (with possible reordering)\n    if (is_little_endian != OutputIsLittleEndian) {\n      // reverse byte order prior to conversion if necessary\n      std::reverse(vec.begin(), vec.end());\n    }\n\n    oa->write_characters(vec.data(), sizeof(NumberType));\n  }\n\n  void write_compact_float(const number_float_t n, detail::input_format_t format)\n  {\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#endif\n\n    if (static_cast<double>(n) >= static_cast<double>\n        (std::numeric_limits<float>::lowest()) &&\n        static_cast<double>(n) <= static_cast<double>((std::numeric_limits<float>::max)\n            ()) &&\n        static_cast<double>(static_cast<float>(n)) == static_cast<double>(n)) {\n      oa->write_character(format == detail::input_format_t::cbor\n                          ? get_cbor_float_prefix(static_cast<float>(n))\n                          : get_msgpack_float_prefix(static_cast<float>(n)));\n      write_number(static_cast<float>(n));\n    } else {\n      oa->write_character(format == detail::input_format_t::cbor\n                          ? get_cbor_float_prefix(n)\n                          : get_msgpack_float_prefix(n));\n      write_number(n);\n    }\n\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif\n  }\n\npublic:\n  // The following to_char_type functions are implement the conversion\n  // between uint8_t and CharType. In case CharType is not unsigned,\n  // such a conversion is required to allow values greater than 128.\n  // See <https://github.com/nlohmann/json/issues/1286> for a discussion.\n  template < typename C = CharType,\n             enable_if_t < std::is_signed<C>::value &&\n                           std::is_signed<char>::value > * = nullptr >\n  static constexpr CharType to_char_type(std::uint8_t x) noexcept\n  {\n    return *reinterpret_cast<char*>(&x);\n  }\n\n  template < typename C = CharType,\n             enable_if_t < std::is_signed<C>::value &&\n                           std::is_unsigned<char>::value > * = nullptr >\n  static CharType to_char_type(std::uint8_t x) noexcept\n  {\n    static_assert(sizeof(std::uint8_t) == sizeof(CharType),\n                  \"size of CharType must be equal to std::uint8_t\");\n    static_assert(std::is_trivial<CharType>::value, \"CharType must be trivial\");\n    CharType result;\n    std::memcpy(&result, &x, sizeof(x));\n    return result;\n  }\n\n  template<typename C = CharType,\n           enable_if_t<std::is_unsigned<C>::value>* = nullptr>\n  static constexpr CharType to_char_type(std::uint8_t x) noexcept\n  {\n    return x;\n  }\n\n  template < typename InputCharType, typename C = CharType,\n             enable_if_t <\n               std::is_signed<C>::value &&\n               std::is_signed<char>::value &&\n               std::is_same<char, typename std::remove_cv<InputCharType>::type>::value\n               > * = nullptr >\n  static constexpr CharType to_char_type(InputCharType x) noexcept\n  {\n    return x;\n  }\n\nprivate:\n  /// whether we can assume little endianness\n  const bool is_little_endian = little_endianness();\n\n  /// the output\n  output_adapter_t<CharType> oa = nullptr;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/output/output_adapters.hpp>\n\n// #include <nlohmann/detail/output/serializer.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2008-2009 Björn Hoehrmann <bjoern@hoehrmann.de>\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // reverse, remove, fill, find, none_of\n#include <array> // array\n#include <clocale> // localeconv, lconv\n#include <cmath> // labs, isfinite, isnan, signbit\n#include <cstddef> // size_t, ptrdiff_t\n#include <cstdint> // uint8_t\n#include <cstdio> // snprintf\n#include <limits> // numeric_limits\n#include <string> // string, char_traits\n#include <iomanip> // setfill, setw\n#include <type_traits> // is_same\n#include <utility> // move\n\n// #include <nlohmann/detail/conversions/to_chars.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2009 Florian Loitsch <https://florian.loitsch.com/>\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <array> // array\n#include <cmath>   // signbit, isfinite\n#include <cstdint> // intN_t, uintN_t\n#include <cstring> // memcpy, memmove\n#include <limits> // numeric_limits\n#include <type_traits> // conditional\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/*!\n@brief implements the Grisu2 algorithm for binary to decimal floating-point\nconversion.\n\nThis implementation is a slightly modified version of the reference\nimplementation which may be obtained from\nhttp://florian.loitsch.com/publications (bench.tar.gz).\n\nThe code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch.\n\nFor a detailed description of the algorithm see:\n\n[1] Loitsch, \"Printing Floating-Point Numbers Quickly and Accurately with\n    Integers\", Proceedings of the ACM SIGPLAN 2010 Conference on Programming\n    Language Design and Implementation, PLDI 2010\n[2] Burger, Dybvig, \"Printing Floating-Point Numbers Quickly and Accurately\",\n    Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language\n    Design and Implementation, PLDI 1996\n*/\nnamespace dtoa_impl\n{\n\ntemplate<typename Target, typename Source>\nTarget reinterpret_bits(const Source source)\n{\n  static_assert(sizeof(Target) == sizeof(Source), \"size mismatch\");\n  Target target;\n  std::memcpy(&target, &source, sizeof(Source));\n  return target;\n}\n\nstruct diyfp { // f * 2^e\n  static constexpr int kPrecision = 64; // = q\n\n  std::uint64_t f = 0;\n  int e = 0;\n\n  constexpr diyfp(std::uint64_t f_, int e_) noexcept : f(f_), e(e_) {}\n\n  /*!\n  @brief returns x - y\n  @pre x.e == y.e and x.f >= y.f\n  */\n  static diyfp sub(const diyfp& x, const diyfp& y) noexcept\n  {\n    JSON_ASSERT(x.e == y.e);\n    JSON_ASSERT(x.f >= y.f);\n    return {x.f - y.f, x.e};\n  }\n\n  /*!\n  @brief returns x * y\n  @note The result is rounded. (Only the upper q bits are returned.)\n  */\n  static diyfp mul(const diyfp& x, const diyfp& y) noexcept\n  {\n    static_assert(kPrecision == 64, \"internal error\");\n    // Computes:\n    //  f = round((x.f * y.f) / 2^q)\n    //  e = x.e + y.e + q\n    // Emulate the 64-bit * 64-bit multiplication:\n    //\n    // p = u * v\n    //   = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi)\n    //   = (u_lo v_lo         ) + 2^32 ((u_lo v_hi         ) + (u_hi v_lo         )) + 2^64 (u_hi v_hi         )\n    //   = (p0                ) + 2^32 ((p1                ) + (p2                )) + 2^64 (p3                )\n    //   = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3                )\n    //   = (p0_lo             ) + 2^32 (p0_hi + p1_lo + p2_lo                      ) + 2^64 (p1_hi + p2_hi + p3)\n    //   = (p0_lo             ) + 2^32 (Q                                          ) + 2^64 (H                 )\n    //   = (p0_lo             ) + 2^32 (Q_lo + 2^32 Q_hi                           ) + 2^64 (H                 )\n    //\n    // (Since Q might be larger than 2^32 - 1)\n    //\n    //   = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H)\n    //\n    // (Q_hi + H does not overflow a 64-bit int)\n    //\n    //   = p_lo + 2^64 p_hi\n    const std::uint64_t u_lo = x.f & 0xFFFFFFFFu;\n    const std::uint64_t u_hi = x.f >> 32u;\n    const std::uint64_t v_lo = y.f & 0xFFFFFFFFu;\n    const std::uint64_t v_hi = y.f >> 32u;\n    const std::uint64_t p0 = u_lo * v_lo;\n    const std::uint64_t p1 = u_lo * v_hi;\n    const std::uint64_t p2 = u_hi * v_lo;\n    const std::uint64_t p3 = u_hi * v_hi;\n    const std::uint64_t p0_hi = p0 >> 32u;\n    const std::uint64_t p1_lo = p1 & 0xFFFFFFFFu;\n    const std::uint64_t p1_hi = p1 >> 32u;\n    const std::uint64_t p2_lo = p2 & 0xFFFFFFFFu;\n    const std::uint64_t p2_hi = p2 >> 32u;\n    std::uint64_t Q = p0_hi + p1_lo + p2_lo;\n    // The full product might now be computed as\n    //\n    // p_hi = p3 + p2_hi + p1_hi + (Q >> 32)\n    // p_lo = p0_lo + (Q << 32)\n    //\n    // But in this particular case here, the full p_lo is not required.\n    // Effectively we only need to add the highest bit in p_lo to p_hi (and\n    // Q_hi + 1 does not overflow).\n    Q += std::uint64_t{1} << (64u - 32u - 1u); // round, ties up\n    const std::uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32u);\n    return {h, x.e + y.e + 64};\n  }\n\n  /*!\n  @brief normalize x such that the significand is >= 2^(q-1)\n  @pre x.f != 0\n  */\n  static diyfp normalize(diyfp x) noexcept\n  {\n    JSON_ASSERT(x.f != 0);\n\n    while ((x.f >> 63u) == 0) {\n      x.f <<= 1u;\n      x.e--;\n    }\n\n    return x;\n  }\n\n  /*!\n  @brief normalize x such that the result has the exponent E\n  @pre e >= x.e and the upper e - x.e bits of x.f must be zero.\n  */\n  static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept\n  {\n    const int delta = x.e - target_exponent;\n    JSON_ASSERT(delta >= 0);\n    JSON_ASSERT(((x.f << delta) >> delta) == x.f);\n    return {x.f << delta, target_exponent};\n  }\n};\n\nstruct boundaries {\n  diyfp w;\n  diyfp minus;\n  diyfp plus;\n};\n\n/*!\nCompute the (normalized) diyfp representing the input number 'value' and its\nboundaries.\n\n@pre value must be finite and positive\n*/\ntemplate<typename FloatType>\nboundaries compute_boundaries(FloatType value)\n{\n  JSON_ASSERT(std::isfinite(value));\n  JSON_ASSERT(value > 0);\n  // Convert the IEEE representation into a diyfp.\n  //\n  // If v is denormal:\n  //      value = 0.F * 2^(1 - bias) = (          F) * 2^(1 - bias - (p-1))\n  // If v is normalized:\n  //      value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1))\n  static_assert(std::numeric_limits<FloatType>::is_iec559,\n                \"internal error: dtoa_short requires an IEEE-754 floating-point implementation\");\n  constexpr int      kPrecision =\n    std::numeric_limits<FloatType>::digits; // = p (includes the hidden bit)\n  constexpr int      kBias      = std::numeric_limits<FloatType>::max_exponent - 1\n                                  + (kPrecision - 1);\n  constexpr int      kMinExp    = 1 - kBias;\n  constexpr std::uint64_t kHiddenBit = std::uint64_t{1} <<\n                                       (kPrecision - 1); // = 2^(p-1)\n  using bits_type = typename\n                    std::conditional<kPrecision == 24, std::uint32_t, std::uint64_t >::type;\n  const auto bits = static_cast<std::uint64_t>(reinterpret_bits<bits_type>\n                    (value));\n  const std::uint64_t E = bits >> (kPrecision - 1);\n  const std::uint64_t F = bits & (kHiddenBit - 1);\n  const bool is_denormal = E == 0;\n  const diyfp v = is_denormal\n                  ? diyfp(F, kMinExp)\n                  : diyfp(F + kHiddenBit, static_cast<int>(E) - kBias);\n  // Compute the boundaries m- and m+ of the floating-point value\n  // v = f * 2^e.\n  //\n  // Determine v- and v+, the floating-point predecessor and successor if v,\n  // respectively.\n  //\n  //      v- = v - 2^e        if f != 2^(p-1) or e == e_min                (A)\n  //         = v - 2^(e-1)    if f == 2^(p-1) and e > e_min                (B)\n  //\n  //      v+ = v + 2^e\n  //\n  // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_\n  // between m- and m+ round to v, regardless of how the input rounding\n  // algorithm breaks ties.\n  //\n  //      ---+-------------+-------------+-------------+-------------+---  (A)\n  //         v-            m-            v             m+            v+\n  //\n  //      -----------------+------+------+-------------+-------------+---  (B)\n  //                       v-     m-     v             m+            v+\n  const bool lower_boundary_is_closer = F == 0 && E > 1;\n  const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1);\n  const diyfp m_minus = lower_boundary_is_closer\n                        ? diyfp(4 * v.f - 1, v.e - 2)  // (B)\n                        : diyfp(2 * v.f - 1, v.e - 1); // (A)\n  // Determine the normalized w+ = m+.\n  const diyfp w_plus = diyfp::normalize(m_plus);\n  // Determine w- = m- such that e_(w-) = e_(w+).\n  const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e);\n  return {diyfp::normalize(v), w_minus, w_plus};\n}\n\n// Given normalized diyfp w, Grisu needs to find a (normalized) cached\n// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies\n// within a certain range [alpha, gamma] (Definition 3.2 from [1])\n//\n//      alpha <= e = e_c + e_w + q <= gamma\n//\n// or\n//\n//      f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q\n//                          <= f_c * f_w * 2^gamma\n//\n// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies\n//\n//      2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma\n//\n// or\n//\n//      2^(q - 2 + alpha) <= c * w < 2^(q + gamma)\n//\n// The choice of (alpha,gamma) determines the size of the table and the form of\n// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well\n// in practice:\n//\n// The idea is to cut the number c * w = f * 2^e into two parts, which can be\n// processed independently: An integral part p1, and a fractional part p2:\n//\n//      f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e\n//              = (f div 2^-e) + (f mod 2^-e) * 2^e\n//              = p1 + p2 * 2^e\n//\n// The conversion of p1 into decimal form requires a series of divisions and\n// modulos by (a power of) 10. These operations are faster for 32-bit than for\n// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be\n// achieved by choosing\n//\n//      -e >= 32   or   e <= -32 := gamma\n//\n// In order to convert the fractional part\n//\n//      p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ...\n//\n// into decimal form, the fraction is repeatedly multiplied by 10 and the digits\n// d[-i] are extracted in order:\n//\n//      (10 * p2) div 2^-e = d[-1]\n//      (10 * p2) mod 2^-e = d[-2] / 10^1 + ...\n//\n// The multiplication by 10 must not overflow. It is sufficient to choose\n//\n//      10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64.\n//\n// Since p2 = f mod 2^-e < 2^-e,\n//\n//      -e <= 60   or   e >= -60 := alpha\n\nconstexpr int kAlpha = -60;\nconstexpr int kGamma = -32;\n\nstruct cached_power { // c = f * 2^e ~= 10^k\n  std::uint64_t f;\n  int e;\n  int k;\n};\n\n/*!\nFor a normalized diyfp w = f * 2^e, this function returns a (normalized) cached\npower-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c\nsatisfies (Definition 3.2 from [1])\n\n     alpha <= e_c + e + q <= gamma.\n*/\ninline cached_power get_cached_power_for_binary_exponent(int e)\n{\n  // Now\n  //\n  //      alpha <= e_c + e + q <= gamma                                    (1)\n  //      ==> f_c * 2^alpha <= c * 2^e * 2^q\n  //\n  // and since the c's are normalized, 2^(q-1) <= f_c,\n  //\n  //      ==> 2^(q - 1 + alpha) <= c * 2^(e + q)\n  //      ==> 2^(alpha - e - 1) <= c\n  //\n  // If c were an exact power of ten, i.e. c = 10^k, one may determine k as\n  //\n  //      k = ceil( log_10( 2^(alpha - e - 1) ) )\n  //        = ceil( (alpha - e - 1) * log_10(2) )\n  //\n  // From the paper:\n  // \"In theory the result of the procedure could be wrong since c is rounded,\n  //  and the computation itself is approximated [...]. In practice, however,\n  //  this simple function is sufficient.\"\n  //\n  // For IEEE double precision floating-point numbers converted into\n  // normalized diyfp's w = f * 2^e, with q = 64,\n  //\n  //      e >= -1022      (min IEEE exponent)\n  //           -52        (p - 1)\n  //           -52        (p - 1, possibly normalize denormal IEEE numbers)\n  //           -11        (normalize the diyfp)\n  //         = -1137\n  //\n  // and\n  //\n  //      e <= +1023      (max IEEE exponent)\n  //           -52        (p - 1)\n  //           -11        (normalize the diyfp)\n  //         = 960\n  //\n  // This binary exponent range [-1137,960] results in a decimal exponent\n  // range [-307,324]. One does not need to store a cached power for each\n  // k in this range. For each such k it suffices to find a cached power\n  // such that the exponent of the product lies in [alpha,gamma].\n  // This implies that the difference of the decimal exponents of adjacent\n  // table entries must be less than or equal to\n  //\n  //      floor( (gamma - alpha) * log_10(2) ) = 8.\n  //\n  // (A smaller distance gamma-alpha would require a larger table.)\n  // NB:\n  // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34.\n  constexpr int kCachedPowersMinDecExp = -300;\n  constexpr int kCachedPowersDecStep = 8;\n  static constexpr std::array<cached_power, 79> kCachedPowers = {\n    {\n      { 0xAB70FE17C79AC6CA, -1060, -300 },\n      { 0xFF77B1FCBEBCDC4F, -1034, -292 },\n      { 0xBE5691EF416BD60C, -1007, -284 },\n      { 0x8DD01FAD907FFC3C,  -980, -276 },\n      { 0xD3515C2831559A83,  -954, -268 },\n      { 0x9D71AC8FADA6C9B5,  -927, -260 },\n      { 0xEA9C227723EE8BCB,  -901, -252 },\n      { 0xAECC49914078536D,  -874, -244 },\n      { 0x823C12795DB6CE57,  -847, -236 },\n      { 0xC21094364DFB5637,  -821, -228 },\n      { 0x9096EA6F3848984F,  -794, -220 },\n      { 0xD77485CB25823AC7,  -768, -212 },\n      { 0xA086CFCD97BF97F4,  -741, -204 },\n      { 0xEF340A98172AACE5,  -715, -196 },\n      { 0xB23867FB2A35B28E,  -688, -188 },\n      { 0x84C8D4DFD2C63F3B,  -661, -180 },\n      { 0xC5DD44271AD3CDBA,  -635, -172 },\n      { 0x936B9FCEBB25C996,  -608, -164 },\n      { 0xDBAC6C247D62A584,  -582, -156 },\n      { 0xA3AB66580D5FDAF6,  -555, -148 },\n      { 0xF3E2F893DEC3F126,  -529, -140 },\n      { 0xB5B5ADA8AAFF80B8,  -502, -132 },\n      { 0x87625F056C7C4A8B,  -475, -124 },\n      { 0xC9BCFF6034C13053,  -449, -116 },\n      { 0x964E858C91BA2655,  -422, -108 },\n      { 0xDFF9772470297EBD,  -396, -100 },\n      { 0xA6DFBD9FB8E5B88F,  -369,  -92 },\n      { 0xF8A95FCF88747D94,  -343,  -84 },\n      { 0xB94470938FA89BCF,  -316,  -76 },\n      { 0x8A08F0F8BF0F156B,  -289,  -68 },\n      { 0xCDB02555653131B6,  -263,  -60 },\n      { 0x993FE2C6D07B7FAC,  -236,  -52 },\n      { 0xE45C10C42A2B3B06,  -210,  -44 },\n      { 0xAA242499697392D3,  -183,  -36 },\n      { 0xFD87B5F28300CA0E,  -157,  -28 },\n      { 0xBCE5086492111AEB,  -130,  -20 },\n      { 0x8CBCCC096F5088CC,  -103,  -12 },\n      { 0xD1B71758E219652C,   -77,   -4 },\n      { 0x9C40000000000000,   -50,    4 },\n      { 0xE8D4A51000000000,   -24,   12 },\n      { 0xAD78EBC5AC620000,     3,   20 },\n      { 0x813F3978F8940984,    30,   28 },\n      { 0xC097CE7BC90715B3,    56,   36 },\n      { 0x8F7E32CE7BEA5C70,    83,   44 },\n      { 0xD5D238A4ABE98068,   109,   52 },\n      { 0x9F4F2726179A2245,   136,   60 },\n      { 0xED63A231D4C4FB27,   162,   68 },\n      { 0xB0DE65388CC8ADA8,   189,   76 },\n      { 0x83C7088E1AAB65DB,   216,   84 },\n      { 0xC45D1DF942711D9A,   242,   92 },\n      { 0x924D692CA61BE758,   269,  100 },\n      { 0xDA01EE641A708DEA,   295,  108 },\n      { 0xA26DA3999AEF774A,   322,  116 },\n      { 0xF209787BB47D6B85,   348,  124 },\n      { 0xB454E4A179DD1877,   375,  132 },\n      { 0x865B86925B9BC5C2,   402,  140 },\n      { 0xC83553C5C8965D3D,   428,  148 },\n      { 0x952AB45CFA97A0B3,   455,  156 },\n      { 0xDE469FBD99A05FE3,   481,  164 },\n      { 0xA59BC234DB398C25,   508,  172 },\n      { 0xF6C69A72A3989F5C,   534,  180 },\n      { 0xB7DCBF5354E9BECE,   561,  188 },\n      { 0x88FCF317F22241E2,   588,  196 },\n      { 0xCC20CE9BD35C78A5,   614,  204 },\n      { 0x98165AF37B2153DF,   641,  212 },\n      { 0xE2A0B5DC971F303A,   667,  220 },\n      { 0xA8D9D1535CE3B396,   694,  228 },\n      { 0xFB9B7CD9A4A7443C,   720,  236 },\n      { 0xBB764C4CA7A44410,   747,  244 },\n      { 0x8BAB8EEFB6409C1A,   774,  252 },\n      { 0xD01FEF10A657842C,   800,  260 },\n      { 0x9B10A4E5E9913129,   827,  268 },\n      { 0xE7109BFBA19C0C9D,   853,  276 },\n      { 0xAC2820D9623BF429,   880,  284 },\n      { 0x80444B5E7AA7CF85,   907,  292 },\n      { 0xBF21E44003ACDD2D,   933,  300 },\n      { 0x8E679C2F5E44FF8F,   960,  308 },\n      { 0xD433179D9C8CB841,   986,  316 },\n      { 0x9E19DB92B4E31BA9,  1013,  324 },\n    }\n  };\n  // This computation gives exactly the same results for k as\n  //      k = ceil((kAlpha - e - 1) * 0.30102999566398114)\n  // for |e| <= 1500, but doesn't require floating-point operations.\n  // NB: log_10(2) ~= 78913 / 2^18\n  JSON_ASSERT(e >= -1500);\n  JSON_ASSERT(e <=  1500);\n  const int f = kAlpha - e - 1;\n  const int k = (f * 78913) / (1 << 18) + static_cast<int>(f > 0);\n  const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) /\n                    kCachedPowersDecStep;\n  JSON_ASSERT(index >= 0);\n  JSON_ASSERT(static_cast<std::size_t>(index) < kCachedPowers.size());\n  const cached_power cached = kCachedPowers[static_cast<std::size_t>(index)];\n  JSON_ASSERT(kAlpha <= cached.e + e + 64);\n  JSON_ASSERT(kGamma >= cached.e + e + 64);\n  return cached;\n}\n\n/*!\nFor n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k.\nFor n == 0, returns 1 and sets pow10 := 1.\n*/\ninline int find_largest_pow10(const std::uint32_t n, std::uint32_t& pow10)\n{\n  // LCOV_EXCL_START\n  if (n >= 1000000000) {\n    pow10 = 1000000000;\n    return 10;\n  }\n\n  // LCOV_EXCL_STOP\n  if (n >= 100000000) {\n    pow10 = 100000000;\n    return  9;\n  }\n\n  if (n >= 10000000) {\n    pow10 = 10000000;\n    return  8;\n  }\n\n  if (n >= 1000000) {\n    pow10 = 1000000;\n    return  7;\n  }\n\n  if (n >= 100000) {\n    pow10 = 100000;\n    return  6;\n  }\n\n  if (n >= 10000) {\n    pow10 = 10000;\n    return  5;\n  }\n\n  if (n >= 1000) {\n    pow10 = 1000;\n    return  4;\n  }\n\n  if (n >= 100) {\n    pow10 = 100;\n    return  3;\n  }\n\n  if (n >= 10) {\n    pow10 = 10;\n    return  2;\n  }\n\n  pow10 = 1;\n  return 1;\n}\n\ninline void grisu2_round(char* buf, int len, std::uint64_t dist,\n                         std::uint64_t delta,\n                         std::uint64_t rest, std::uint64_t ten_k)\n{\n  JSON_ASSERT(len >= 1);\n  JSON_ASSERT(dist <= delta);\n  JSON_ASSERT(rest <= delta);\n  JSON_ASSERT(ten_k > 0);\n\n  //               <--------------------------- delta ---->\n  //                                  <---- dist --------->\n  // --------------[------------------+-------------------]--------------\n  //               M-                 w                   M+\n  //\n  //                                  ten_k\n  //                                <------>\n  //                                       <---- rest ---->\n  // --------------[------------------+----+--------------]--------------\n  //                                  w    V\n  //                                       = buf * 10^k\n  //\n  // ten_k represents a unit-in-the-last-place in the decimal representation\n  // stored in buf.\n  // Decrement buf by ten_k while this takes buf closer to w.\n\n  // The tests are written in this order to avoid overflow in unsigned\n  // integer arithmetic.\n\n  while (rest < dist\n         && delta - rest >= ten_k\n         && (rest + ten_k < dist || dist - rest > rest + ten_k - dist)) {\n    JSON_ASSERT(buf[len - 1] != '0');\n    buf[len - 1]--;\n    rest += ten_k;\n  }\n}\n\n/*!\nGenerates V = buffer * 10^decimal_exponent, such that M- <= V <= M+.\nM- and M+ must be normalized and share the same exponent -60 <= e <= -32.\n*/\ninline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent,\n                             diyfp M_minus, diyfp w, diyfp M_plus)\n{\n  static_assert(kAlpha >= -60, \"internal error\");\n  static_assert(kGamma <= -32, \"internal error\");\n  // Generates the digits (and the exponent) of a decimal floating-point\n  // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's\n  // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma.\n  //\n  //               <--------------------------- delta ---->\n  //                                  <---- dist --------->\n  // --------------[------------------+-------------------]--------------\n  //               M-                 w                   M+\n  //\n  // Grisu2 generates the digits of M+ from left to right and stops as soon as\n  // V is in [M-,M+].\n  JSON_ASSERT(M_plus.e >= kAlpha);\n  JSON_ASSERT(M_plus.e <= kGamma);\n  std::uint64_t delta = diyfp::sub(M_plus,\n                                   M_minus).f; // (significand of (M+ - M-), implicit exponent is e)\n  std::uint64_t dist  = diyfp::sub(M_plus,\n                                   w).f;       // (significand of (M+ - w ), implicit exponent is e)\n  // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0):\n  //\n  //      M+ = f * 2^e\n  //         = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e\n  //         = ((p1        ) * 2^-e + (p2        )) * 2^e\n  //         = p1 + p2 * 2^e\n  const diyfp one(std::uint64_t{1} << -M_plus.e, M_plus.e);\n  auto p1 = static_cast<std::uint32_t>(M_plus.f >>\n                                       -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.)\n  std::uint64_t p2 = M_plus.f & (one.f - 1);                    // p2 = f mod 2^-e\n  // 1)\n  //\n  // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0]\n  JSON_ASSERT(p1 > 0);\n  std::uint32_t pow10{};\n  const int k = find_largest_pow10(p1, pow10);\n  //      10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1)\n  //\n  //      p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1))\n  //         = (d[k-1]         ) * 10^(k-1) + (p1 mod 10^(k-1))\n  //\n  //      M+ = p1                                             + p2 * 2^e\n  //         = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1))          + p2 * 2^e\n  //         = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e\n  //         = d[k-1] * 10^(k-1) + (                         rest) * 2^e\n  //\n  // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0)\n  //\n  //      p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0]\n  //\n  // but stop as soon as\n  //\n  //      rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e\n  int n = k;\n\n  while (n > 0) {\n    // Invariants:\n    //      M+ = buffer * 10^n + (p1 + p2 * 2^e)    (buffer = 0 for n = k)\n    //      pow10 = 10^(n-1) <= p1 < 10^n\n    //\n    const std::uint32_t d = p1 / pow10;  // d = p1 div 10^(n-1)\n    const std::uint32_t r = p1 % pow10;  // r = p1 mod 10^(n-1)\n    //\n    //      M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e\n    //         = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e)\n    //\n    JSON_ASSERT(d <= 9);\n    buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d\n    //\n    //      M+ = buffer * 10^(n-1) + (r + p2 * 2^e)\n    //\n    p1 = r;\n    n--;\n    //\n    //      M+ = buffer * 10^n + (p1 + p2 * 2^e)\n    //      pow10 = 10^n\n    //\n    // Now check if enough digits have been generated.\n    // Compute\n    //\n    //      p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e\n    //\n    // Note:\n    // Since rest and delta share the same exponent e, it suffices to\n    // compare the significands.\n    const std::uint64_t rest = (std::uint64_t{p1} << -one.e) + p2;\n\n    if (rest <= delta) {\n      // V = buffer * 10^n, with M- <= V <= M+.\n      decimal_exponent += n;\n      // We may now just stop. But instead look if the buffer could be\n      // decremented to bring V closer to w.\n      //\n      // pow10 = 10^n is now 1 ulp in the decimal representation V.\n      // The rounding procedure works with diyfp's with an implicit\n      // exponent of e.\n      //\n      //      10^n = (10^n * 2^-e) * 2^e = ulp * 2^e\n      //\n      const std::uint64_t ten_n = std::uint64_t{pow10} << -one.e;\n      grisu2_round(buffer, length, dist, delta, rest, ten_n);\n      return;\n    }\n\n    pow10 /= 10;\n    //\n    //      pow10 = 10^(n-1) <= p1 < 10^n\n    // Invariants restored.\n  }\n\n  // 2)\n  //\n  // The digits of the integral part have been generated:\n  //\n  //      M+ = d[k-1]...d[1]d[0] + p2 * 2^e\n  //         = buffer            + p2 * 2^e\n  //\n  // Now generate the digits of the fractional part p2 * 2^e.\n  //\n  // Note:\n  // No decimal point is generated: the exponent is adjusted instead.\n  //\n  // p2 actually represents the fraction\n  //\n  //      p2 * 2^e\n  //          = p2 / 2^-e\n  //          = d[-1] / 10^1 + d[-2] / 10^2 + ...\n  //\n  // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...)\n  //\n  //      p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m\n  //                      + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...)\n  //\n  // using\n  //\n  //      10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e)\n  //                = (                   d) * 2^-e + (                   r)\n  //\n  // or\n  //      10^m * p2 * 2^e = d + r * 2^e\n  //\n  // i.e.\n  //\n  //      M+ = buffer + p2 * 2^e\n  //         = buffer + 10^-m * (d + r * 2^e)\n  //         = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e\n  //\n  // and stop as soon as 10^-m * r * 2^e <= delta * 2^e\n  JSON_ASSERT(p2 > delta);\n  int m = 0;\n\n  for (;;) {\n    // Invariant:\n    //      M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e\n    //         = buffer * 10^-m + 10^-m * (p2                                 ) * 2^e\n    //         = buffer * 10^-m + 10^-m * (1/10 * (10 * p2)                   ) * 2^e\n    //         = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e\n    //\n    JSON_ASSERT(p2 <= (std::numeric_limits<std::uint64_t>::max)() / 10);\n    p2 *= 10;\n    const std::uint64_t d = p2 >> -one.e;     // d = (10 * p2) div 2^-e\n    const std::uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e\n    //\n    //      M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e\n    //         = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e))\n    //         = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e\n    //\n    JSON_ASSERT(d <= 9);\n    buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d\n    //\n    //      M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e\n    //\n    p2 = r;\n    m++;\n    //\n    //      M+ = buffer * 10^-m + 10^-m * p2 * 2^e\n    // Invariant restored.\n    // Check if enough digits have been generated.\n    //\n    //      10^-m * p2 * 2^e <= delta * 2^e\n    //              p2 * 2^e <= 10^m * delta * 2^e\n    //                    p2 <= 10^m * delta\n    delta *= 10;\n    dist  *= 10;\n\n    if (p2 <= delta) {\n      break;\n    }\n  }\n\n  // V = buffer * 10^-m, with M- <= V <= M+.\n  decimal_exponent -= m;\n  // 1 ulp in the decimal representation is now 10^-m.\n  // Since delta and dist are now scaled by 10^m, we need to do the\n  // same with ulp in order to keep the units in sync.\n  //\n  //      10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e\n  //\n  const std::uint64_t ten_m = one.f;\n  grisu2_round(buffer, length, dist, delta, p2, ten_m);\n  // By construction this algorithm generates the shortest possible decimal\n  // number (Loitsch, Theorem 6.2) which rounds back to w.\n  // For an input number of precision p, at least\n  //\n  //      N = 1 + ceil(p * log_10(2))\n  //\n  // decimal digits are sufficient to identify all binary floating-point\n  // numbers (Matula, \"In-and-Out conversions\").\n  // This implies that the algorithm does not produce more than N decimal\n  // digits.\n  //\n  //      N = 17 for p = 53 (IEEE double precision)\n  //      N = 9  for p = 24 (IEEE single precision)\n}\n\n/*!\nv = buf * 10^decimal_exponent\nlen is the length of the buffer (number of decimal digits)\nThe buffer must be large enough, i.e. >= max_digits10.\n*/\nJSON_HEDLEY_NON_NULL(1)\ninline void grisu2(char* buf, int& len, int& decimal_exponent,\n                   diyfp m_minus, diyfp v, diyfp m_plus)\n{\n  JSON_ASSERT(m_plus.e == m_minus.e);\n  JSON_ASSERT(m_plus.e == v.e);\n  //  --------(-----------------------+-----------------------)--------    (A)\n  //          m-                      v                       m+\n  //\n  //  --------------------(-----------+-----------------------)--------    (B)\n  //                      m-          v                       m+\n  //\n  // First scale v (and m- and m+) such that the exponent is in the range\n  // [alpha, gamma].\n  const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e);\n  const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k\n  // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma]\n  const diyfp w       = diyfp::mul(v,       c_minus_k);\n  const diyfp w_minus = diyfp::mul(m_minus, c_minus_k);\n  const diyfp w_plus  = diyfp::mul(m_plus,  c_minus_k);\n  //  ----(---+---)---------------(---+---)---------------(---+---)----\n  //          w-                      w                       w+\n  //          = c*m-                  = c*v                   = c*m+\n  //\n  // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and\n  // w+ are now off by a small amount.\n  // In fact:\n  //\n  //      w - v * 10^k < 1 ulp\n  //\n  // To account for this inaccuracy, add resp. subtract 1 ulp.\n  //\n  //  --------+---[---------------(---+---)---------------]---+--------\n  //          w-  M-                  w                   M+  w+\n  //\n  // Now any number in [M-, M+] (bounds included) will round to w when input,\n  // regardless of how the input rounding algorithm breaks ties.\n  //\n  // And digit_gen generates the shortest possible such number in [M-, M+].\n  // Note that this does not mean that Grisu2 always generates the shortest\n  // possible number in the interval (m-, m+).\n  const diyfp M_minus(w_minus.f + 1, w_minus.e);\n  const diyfp M_plus(w_plus.f  - 1, w_plus.e);\n  decimal_exponent = -cached.k; // = -(-k) = k\n  grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus);\n}\n\n/*!\nv = buf * 10^decimal_exponent\nlen is the length of the buffer (number of decimal digits)\nThe buffer must be large enough, i.e. >= max_digits10.\n*/\ntemplate<typename FloatType>\nJSON_HEDLEY_NON_NULL(1)\nvoid grisu2(char* buf, int& len, int& decimal_exponent, FloatType value)\n{\n  static_assert(diyfp::kPrecision >= std::numeric_limits<FloatType>::digits + 3,\n                \"internal error: not enough precision\");\n  JSON_ASSERT(std::isfinite(value));\n  JSON_ASSERT(value > 0);\n  // If the neighbors (and boundaries) of 'value' are always computed for double-precision\n  // numbers, all float's can be recovered using strtod (and strtof). However, the resulting\n  // decimal representations are not exactly \"short\".\n  //\n  // The documentation for 'std::to_chars' (https://en.cppreference.com/w/cpp/utility/to_chars)\n  // says \"value is converted to a string as if by std::sprintf in the default (\"C\") locale\"\n  // and since sprintf promotes floats to doubles, I think this is exactly what 'std::to_chars'\n  // does.\n  // On the other hand, the documentation for 'std::to_chars' requires that \"parsing the\n  // representation using the corresponding std::from_chars function recovers value exactly\". That\n  // indicates that single precision floating-point numbers should be recovered using\n  // 'std::strtof'.\n  //\n  // NB: If the neighbors are computed for single-precision numbers, there is a single float\n  //     (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision\n  //     value is off by 1 ulp.\n#if 0\n  const boundaries w = compute_boundaries(static_cast<double>(value));\n#else\n  const boundaries w = compute_boundaries(value);\n#endif\n  grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus);\n}\n\n/*!\n@brief appends a decimal representation of e to buf\n@return a pointer to the element following the exponent.\n@pre -1000 < e < 1000\n*/\nJSON_HEDLEY_NON_NULL(1)\nJSON_HEDLEY_RETURNS_NON_NULL\ninline char* append_exponent(char* buf, int e)\n{\n  JSON_ASSERT(e > -1000);\n  JSON_ASSERT(e <  1000);\n\n  if (e < 0) {\n    e = -e;\n    *buf++ = '-';\n  } else {\n    *buf++ = '+';\n  }\n\n  auto k = static_cast<std::uint32_t>(e);\n\n  if (k < 10) {\n    // Always print at least two digits in the exponent.\n    // This is for compatibility with printf(\"%g\").\n    *buf++ = '0';\n    *buf++ = static_cast<char>('0' + k);\n  } else if (k < 100) {\n    *buf++ = static_cast<char>('0' + k / 10);\n    k %= 10;\n    *buf++ = static_cast<char>('0' + k);\n  } else {\n    *buf++ = static_cast<char>('0' + k / 100);\n    k %= 100;\n    *buf++ = static_cast<char>('0' + k / 10);\n    k %= 10;\n    *buf++ = static_cast<char>('0' + k);\n  }\n\n  return buf;\n}\n\n/*!\n@brief prettify v = buf * 10^decimal_exponent\n\nIf v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point\nnotation. Otherwise it will be printed in exponential notation.\n\n@pre min_exp < 0\n@pre max_exp > 0\n*/\nJSON_HEDLEY_NON_NULL(1)\nJSON_HEDLEY_RETURNS_NON_NULL\ninline char* format_buffer(char* buf, int len, int decimal_exponent,\n                           int min_exp, int max_exp)\n{\n  JSON_ASSERT(min_exp < 0);\n  JSON_ASSERT(max_exp > 0);\n  const int k = len;\n  const int n = len + decimal_exponent;\n\n  // v = buf * 10^(n-k)\n  // k is the length of the buffer (number of decimal digits)\n  // n is the position of the decimal point relative to the start of the buffer.\n\n  if (k <= n && n <= max_exp) {\n    // digits[000]\n    // len <= max_exp + 2\n    std::memset(buf + k, '0', static_cast<size_t>(n) - static_cast<size_t>(k));\n    // Make it look like a floating-point number (#362, #378)\n    buf[n + 0] = '.';\n    buf[n + 1] = '0';\n    return buf + (static_cast<size_t>(n) + 2);\n  }\n\n  if (0 < n && n <= max_exp) {\n    // dig.its\n    // len <= max_digits10 + 1\n    JSON_ASSERT(k > n);\n    std::memmove(buf + (static_cast<size_t>(n) + 1), buf + n,\n                 static_cast<size_t>(k) - static_cast<size_t>(n));\n    buf[n] = '.';\n    return buf + (static_cast<size_t>(k) + 1U);\n  }\n\n  if (min_exp < n && n <= 0) {\n    // 0.[000]digits\n    // len <= 2 + (-min_exp - 1) + max_digits10\n    std::memmove(buf + (2 + static_cast<size_t>(-n)), buf, static_cast<size_t>(k));\n    buf[0] = '0';\n    buf[1] = '.';\n    std::memset(buf + 2, '0', static_cast<size_t>(-n));\n    return buf + (2U + static_cast<size_t>(-n) + static_cast<size_t>(k));\n  }\n\n  if (k == 1) {\n    // dE+123\n    // len <= 1 + 5\n    buf += 1;\n  } else {\n    // d.igitsE+123\n    // len <= max_digits10 + 1 + 5\n    std::memmove(buf + 2, buf + 1, static_cast<size_t>(k) - 1);\n    buf[1] = '.';\n    buf += 1 + static_cast<size_t>(k);\n  }\n\n  *buf++ = 'e';\n  return append_exponent(buf, n - 1);\n}\n\n}  // namespace dtoa_impl\n\n/*!\n@brief generates a decimal representation of the floating-point number value in [first, last).\n\nThe format of the resulting decimal representation is similar to printf's %g\nformat. Returns an iterator pointing past-the-end of the decimal representation.\n\n@note The input number must be finite, i.e. NaN's and Inf's are not supported.\n@note The buffer must be large enough.\n@note The result is NOT null-terminated.\n*/\ntemplate<typename FloatType>\nJSON_HEDLEY_NON_NULL(1, 2)\nJSON_HEDLEY_RETURNS_NON_NULL\nchar* to_chars(char* first, const char* last, FloatType value)\n{\n  static_cast<void>(last); // maybe unused - fix warning\n  JSON_ASSERT(std::isfinite(value));\n\n  // Use signbit(value) instead of (value < 0) since signbit works for -0.\n  if (std::signbit(value)) {\n    value = -value;\n    *first++ = '-';\n  }\n\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#endif\n\n  if (value == 0) { // +-0\n    *first++ = '0';\n    // Make it look like a floating-point number (#362, #378)\n    *first++ = '.';\n    *first++ = '0';\n    return first;\n  }\n\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif\n  JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10);\n  // Compute v = buffer * 10^decimal_exponent.\n  // The decimal digits are stored in the buffer, which needs to be interpreted\n  // as an unsigned decimal integer.\n  // len is the length of the buffer, i.e. the number of decimal digits.\n  int len = 0;\n  int decimal_exponent = 0;\n  dtoa_impl::grisu2(first, len, decimal_exponent, value);\n  JSON_ASSERT(len <= std::numeric_limits<FloatType>::max_digits10);\n  // Format the buffer like printf(\"%.*g\", prec, value)\n  constexpr int kMinExp = -4;\n  // Use digits10 here to increase compatibility with version 2.\n  constexpr int kMaxExp = std::numeric_limits<FloatType>::digits10;\n  JSON_ASSERT(last - first >= kMaxExp + 2);\n  JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) +\n              std::numeric_limits<FloatType>::max_digits10);\n  JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10 + 6);\n  return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp);\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/output/binary_writer.hpp>\n\n// #include <nlohmann/detail/output/output_adapters.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n///////////////////\n// serialization //\n///////////////////\n\n/// how to treat decoding errors\nenum class error_handler_t {\n  strict,  ///< throw a type_error exception in case of invalid UTF-8\n  replace, ///< replace invalid UTF-8 sequences with U+FFFD\n  ignore   ///< ignore invalid UTF-8 sequences\n};\n\ntemplate<typename BasicJsonType>\nclass serializer\n{\n  using string_t = typename BasicJsonType::string_t;\n  using number_float_t = typename BasicJsonType::number_float_t;\n  using number_integer_t = typename BasicJsonType::number_integer_t;\n  using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n  using binary_char_t = typename BasicJsonType::binary_t::value_type;\n  static constexpr std::uint8_t UTF8_ACCEPT = 0;\n  static constexpr std::uint8_t UTF8_REJECT = 1;\n\npublic:\n  /*!\n  @param[in] s  output stream to serialize to\n  @param[in] ichar  indentation character to use\n  @param[in] error_handler_  how to react on decoding errors\n  */\n  serializer(output_adapter_t<char> s, const char ichar,\n             error_handler_t error_handler_ = error_handler_t::strict)\n    : o(std::move(s))\n    , loc(std::localeconv())\n    , thousands_sep(loc->thousands_sep == nullptr ? '\\0' :\n                    std::char_traits<char>::to_char_type(* (loc->thousands_sep)))\n    , decimal_point(loc->decimal_point == nullptr ? '\\0' :\n                    std::char_traits<char>::to_char_type(* (loc->decimal_point)))\n    , indent_char(ichar)\n    , indent_string(512, indent_char)\n    , error_handler(error_handler_)\n  {}\n\n  // delete because of pointer members\n  serializer(const serializer&) = delete;\n  serializer& operator=(const serializer&) = delete;\n  serializer(serializer&&) = delete;\n  serializer& operator=(serializer&&) = delete;\n  ~serializer() = default;\n\n  /*!\n  @brief internal implementation of the serialization function\n\n  This function is called by the public member function dump and organizes\n  the serialization internally. The indentation level is propagated as\n  additional parameter. In case of arrays and objects, the function is\n  called recursively.\n\n  - strings and object keys are escaped using `escape_string()`\n  - integer numbers are converted implicitly via `operator<<`\n  - floating-point numbers are converted to a string using `\"%g\"` format\n  - binary values are serialized as objects containing the subtype and the\n    byte array\n\n  @param[in] val               value to serialize\n  @param[in] pretty_print      whether the output shall be pretty-printed\n  @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters\n  in the output are escaped with `\\uXXXX` sequences, and the result consists\n  of ASCII characters only.\n  @param[in] indent_step       the indent level\n  @param[in] current_indent    the current indent level (only used internally)\n  */\n  void dump(const BasicJsonType& val,\n            const bool pretty_print,\n            const bool ensure_ascii,\n            const unsigned int indent_step,\n            const unsigned int current_indent = 0)\n  {\n    switch (val.m_type) {\n    case value_t::object: {\n      if (val.m_value.object->empty()) {\n        o->write_characters(\"{}\", 2);\n        return;\n      }\n\n      if (pretty_print) {\n        o->write_characters(\"{\\n\", 2);\n        // variable to hold indentation for recursive calls\n        const auto new_indent = current_indent + indent_step;\n\n        if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) {\n          indent_string.resize(indent_string.size() * 2, ' ');\n        }\n\n        // first n-1 elements\n        auto i = val.m_value.object->cbegin();\n\n        for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) {\n          o->write_characters(indent_string.c_str(), new_indent);\n          o->write_character('\\\"');\n          dump_escaped(i->first, ensure_ascii);\n          o->write_characters(\"\\\": \", 3);\n          dump(i->second, true, ensure_ascii, indent_step, new_indent);\n          o->write_characters(\",\\n\", 2);\n        }\n\n        // last element\n        JSON_ASSERT(i != val.m_value.object->cend());\n        JSON_ASSERT(std::next(i) == val.m_value.object->cend());\n        o->write_characters(indent_string.c_str(), new_indent);\n        o->write_character('\\\"');\n        dump_escaped(i->first, ensure_ascii);\n        o->write_characters(\"\\\": \", 3);\n        dump(i->second, true, ensure_ascii, indent_step, new_indent);\n        o->write_character('\\n');\n        o->write_characters(indent_string.c_str(), current_indent);\n        o->write_character('}');\n      } else {\n        o->write_character('{');\n        // first n-1 elements\n        auto i = val.m_value.object->cbegin();\n\n        for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) {\n          o->write_character('\\\"');\n          dump_escaped(i->first, ensure_ascii);\n          o->write_characters(\"\\\":\", 2);\n          dump(i->second, false, ensure_ascii, indent_step, current_indent);\n          o->write_character(',');\n        }\n\n        // last element\n        JSON_ASSERT(i != val.m_value.object->cend());\n        JSON_ASSERT(std::next(i) == val.m_value.object->cend());\n        o->write_character('\\\"');\n        dump_escaped(i->first, ensure_ascii);\n        o->write_characters(\"\\\":\", 2);\n        dump(i->second, false, ensure_ascii, indent_step, current_indent);\n        o->write_character('}');\n      }\n\n      return;\n    }\n\n    case value_t::array: {\n      if (val.m_value.array->empty()) {\n        o->write_characters(\"[]\", 2);\n        return;\n      }\n\n      if (pretty_print) {\n        o->write_characters(\"[\\n\", 2);\n        // variable to hold indentation for recursive calls\n        const auto new_indent = current_indent + indent_step;\n\n        if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) {\n          indent_string.resize(indent_string.size() * 2, ' ');\n        }\n\n        // first n-1 elements\n        for (auto i = val.m_value.array->cbegin();\n             i != val.m_value.array->cend() - 1; ++i) {\n          o->write_characters(indent_string.c_str(), new_indent);\n          dump(*i, true, ensure_ascii, indent_step, new_indent);\n          o->write_characters(\",\\n\", 2);\n        }\n\n        // last element\n        JSON_ASSERT(!val.m_value.array->empty());\n        o->write_characters(indent_string.c_str(), new_indent);\n        dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent);\n        o->write_character('\\n');\n        o->write_characters(indent_string.c_str(), current_indent);\n        o->write_character(']');\n      } else {\n        o->write_character('[');\n\n        // first n-1 elements\n        for (auto i = val.m_value.array->cbegin();\n             i != val.m_value.array->cend() - 1; ++i) {\n          dump(*i, false, ensure_ascii, indent_step, current_indent);\n          o->write_character(',');\n        }\n\n        // last element\n        JSON_ASSERT(!val.m_value.array->empty());\n        dump(val.m_value.array->back(), false, ensure_ascii, indent_step,\n             current_indent);\n        o->write_character(']');\n      }\n\n      return;\n    }\n\n    case value_t::string: {\n      o->write_character('\\\"');\n      dump_escaped(*val.m_value.string, ensure_ascii);\n      o->write_character('\\\"');\n      return;\n    }\n\n    case value_t::binary: {\n      if (pretty_print) {\n        o->write_characters(\"{\\n\", 2);\n        // variable to hold indentation for recursive calls\n        const auto new_indent = current_indent + indent_step;\n\n        if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) {\n          indent_string.resize(indent_string.size() * 2, ' ');\n        }\n\n        o->write_characters(indent_string.c_str(), new_indent);\n        o->write_characters(\"\\\"bytes\\\": [\", 10);\n\n        if (!val.m_value.binary->empty()) {\n          for (auto i = val.m_value.binary->cbegin();\n               i != val.m_value.binary->cend() - 1; ++i) {\n            dump_integer(*i);\n            o->write_characters(\", \", 2);\n          }\n\n          dump_integer(val.m_value.binary->back());\n        }\n\n        o->write_characters(\"],\\n\", 3);\n        o->write_characters(indent_string.c_str(), new_indent);\n        o->write_characters(\"\\\"subtype\\\": \", 11);\n\n        if (val.m_value.binary->has_subtype()) {\n          dump_integer(val.m_value.binary->subtype());\n        } else {\n          o->write_characters(\"null\", 4);\n        }\n\n        o->write_character('\\n');\n        o->write_characters(indent_string.c_str(), current_indent);\n        o->write_character('}');\n      } else {\n        o->write_characters(\"{\\\"bytes\\\":[\", 10);\n\n        if (!val.m_value.binary->empty()) {\n          for (auto i = val.m_value.binary->cbegin();\n               i != val.m_value.binary->cend() - 1; ++i) {\n            dump_integer(*i);\n            o->write_character(',');\n          }\n\n          dump_integer(val.m_value.binary->back());\n        }\n\n        o->write_characters(\"],\\\"subtype\\\":\", 12);\n\n        if (val.m_value.binary->has_subtype()) {\n          dump_integer(val.m_value.binary->subtype());\n          o->write_character('}');\n        } else {\n          o->write_characters(\"null}\", 5);\n        }\n      }\n\n      return;\n    }\n\n    case value_t::boolean: {\n      if (val.m_value.boolean) {\n        o->write_characters(\"true\", 4);\n      } else {\n        o->write_characters(\"false\", 5);\n      }\n\n      return;\n    }\n\n    case value_t::number_integer: {\n      dump_integer(val.m_value.number_integer);\n      return;\n    }\n\n    case value_t::number_unsigned: {\n      dump_integer(val.m_value.number_unsigned);\n      return;\n    }\n\n    case value_t::number_float: {\n      dump_float(val.m_value.number_float);\n      return;\n    }\n\n    case value_t::discarded: {\n      o->write_characters(\"<discarded>\", 11);\n      return;\n    }\n\n    case value_t::null: {\n      o->write_characters(\"null\", 4);\n      return;\n    }\n\n    default:            // LCOV_EXCL_LINE\n      JSON_ASSERT(\n        false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n    }\n  }\n\nJSON_PRIVATE_UNLESS_TESTED:\n  /*!\n  @brief dump escaped string\n\n  Escape a string by replacing certain special characters by a sequence of an\n  escape character (backslash) and another character and other control\n  characters by a sequence of \"\\u\" followed by a four-digit hex\n  representation. The escaped string is written to output stream @a o.\n\n  @param[in] s  the string to escape\n  @param[in] ensure_ascii  whether to escape non-ASCII characters with\n                           \\uXXXX sequences\n\n  @complexity Linear in the length of string @a s.\n  */\n  void dump_escaped(const string_t& s, const bool ensure_ascii)\n  {\n    std::uint32_t codepoint{};\n    std::uint8_t state = UTF8_ACCEPT;\n    std::size_t bytes = 0;  // number of bytes written to string_buffer\n    // number of bytes written at the point of the last valid byte\n    std::size_t bytes_after_last_accept = 0;\n    std::size_t undumped_chars = 0;\n\n    for (std::size_t i = 0; i < s.size(); ++i) {\n      const auto byte = static_cast<std::uint8_t>(s[i]);\n\n      switch (decode(state, codepoint, byte)) {\n      case UTF8_ACCEPT: { // decode found a new code point\n        switch (codepoint) {\n        case 0x08: { // backspace\n          string_buffer[bytes++] = '\\\\';\n          string_buffer[bytes++] = 'b';\n          break;\n        }\n\n        case 0x09: { // horizontal tab\n          string_buffer[bytes++] = '\\\\';\n          string_buffer[bytes++] = 't';\n          break;\n        }\n\n        case 0x0A: { // newline\n          string_buffer[bytes++] = '\\\\';\n          string_buffer[bytes++] = 'n';\n          break;\n        }\n\n        case 0x0C: { // formfeed\n          string_buffer[bytes++] = '\\\\';\n          string_buffer[bytes++] = 'f';\n          break;\n        }\n\n        case 0x0D: { // carriage return\n          string_buffer[bytes++] = '\\\\';\n          string_buffer[bytes++] = 'r';\n          break;\n        }\n\n        case 0x22: { // quotation mark\n          string_buffer[bytes++] = '\\\\';\n          string_buffer[bytes++] = '\\\"';\n          break;\n        }\n\n        case 0x5C: { // reverse solidus\n          string_buffer[bytes++] = '\\\\';\n          string_buffer[bytes++] = '\\\\';\n          break;\n        }\n\n        default: {\n          // escape control characters (0x00..0x1F) or, if\n          // ensure_ascii parameter is used, non-ASCII characters\n          if ((codepoint <= 0x1F) || (ensure_ascii && (codepoint >= 0x7F))) {\n            if (codepoint <= 0xFFFF) {\n              // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n              static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 7, \"\\\\u%04x\",\n                                                static_cast<std::uint16_t>(codepoint)));\n              bytes += 6;\n            } else {\n              // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n              static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 13,\n                                                \"\\\\u%04x\\\\u%04x\",\n                                                static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)),\n                                                static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu))));\n              bytes += 12;\n            }\n          } else {\n            // copy byte to buffer (all previous bytes\n            // been copied have in default case above)\n            string_buffer[bytes++] = s[i];\n          }\n\n          break;\n        }\n        }\n\n        // write buffer and reset index; there must be 13 bytes\n        // left, as this is the maximal number of bytes to be\n        // written (\"\\uxxxx\\uxxxx\\0\") for one code point\n        if (string_buffer.size() - bytes < 13) {\n          o->write_characters(string_buffer.data(), bytes);\n          bytes = 0;\n        }\n\n        // remember the byte position of this accept\n        bytes_after_last_accept = bytes;\n        undumped_chars = 0;\n        break;\n      }\n\n      case UTF8_REJECT: { // decode found invalid UTF-8 byte\n        switch (error_handler) {\n        case error_handler_t::strict: {\n          JSON_THROW(type_error::create(316, concat(\"invalid UTF-8 byte at index \",\n                                        std::to_string(i), \": 0x\", hex_bytes(byte | 0)), nullptr));\n        }\n\n        case error_handler_t::ignore:\n        case error_handler_t::replace: {\n          // in case we saw this character the first time, we\n          // would like to read it again, because the byte\n          // may be OK for itself, but just not OK for the\n          // previous sequence\n          if (undumped_chars > 0) {\n            --i;\n          }\n\n          // reset length buffer to the last accepted index;\n          // thus removing/ignoring the invalid characters\n          bytes = bytes_after_last_accept;\n\n          if (error_handler == error_handler_t::replace) {\n            // add a replacement character\n            if (ensure_ascii) {\n              string_buffer[bytes++] = '\\\\';\n              string_buffer[bytes++] = 'u';\n              string_buffer[bytes++] = 'f';\n              string_buffer[bytes++] = 'f';\n              string_buffer[bytes++] = 'f';\n              string_buffer[bytes++] = 'd';\n            } else {\n              string_buffer[bytes++] =\n                detail::binary_writer<BasicJsonType, char>::to_char_type('\\xEF');\n              string_buffer[bytes++] =\n                detail::binary_writer<BasicJsonType, char>::to_char_type('\\xBF');\n              string_buffer[bytes++] =\n                detail::binary_writer<BasicJsonType, char>::to_char_type('\\xBD');\n            }\n\n            // write buffer and reset index; there must be 13 bytes\n            // left, as this is the maximal number of bytes to be\n            // written (\"\\uxxxx\\uxxxx\\0\") for one code point\n            if (string_buffer.size() - bytes < 13) {\n              o->write_characters(string_buffer.data(), bytes);\n              bytes = 0;\n            }\n\n            bytes_after_last_accept = bytes;\n          }\n\n          undumped_chars = 0;\n          // continue processing the string\n          state = UTF8_ACCEPT;\n          break;\n        }\n\n        default:            // LCOV_EXCL_LINE\n          JSON_ASSERT(\n            false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n        }\n\n        break;\n      }\n\n      default: { // decode found yet incomplete multi-byte code point\n        if (!ensure_ascii) {\n          // code point will not be escaped - copy byte to buffer\n          string_buffer[bytes++] = s[i];\n        }\n\n        ++undumped_chars;\n        break;\n      }\n      }\n    }\n\n    // we finished processing the string\n    if (JSON_HEDLEY_LIKELY(state == UTF8_ACCEPT)) {\n      // write buffer\n      if (bytes > 0) {\n        o->write_characters(string_buffer.data(), bytes);\n      }\n    } else {\n      // we finish reading, but do not accept: string was incomplete\n      switch (error_handler) {\n      case error_handler_t::strict: {\n        JSON_THROW(type_error::create(316,\n                                      concat(\"incomplete UTF-8 string; last byte: 0x\",\n                                             hex_bytes(static_cast<std::uint8_t>(s.back() | 0))), nullptr));\n      }\n\n      case error_handler_t::ignore: {\n        // write all accepted bytes\n        o->write_characters(string_buffer.data(), bytes_after_last_accept);\n        break;\n      }\n\n      case error_handler_t::replace: {\n        // write all accepted bytes\n        o->write_characters(string_buffer.data(), bytes_after_last_accept);\n\n        // add a replacement character\n        if (ensure_ascii) {\n          o->write_characters(\"\\\\ufffd\", 6);\n        } else {\n          o->write_characters(\"\\xEF\\xBF\\xBD\", 3);\n        }\n\n        break;\n      }\n\n      default:            // LCOV_EXCL_LINE\n        JSON_ASSERT(\n          false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n      }\n    }\n  }\n\nprivate:\n  /*!\n  @brief count digits\n\n  Count the number of decimal (base 10) digits for an input unsigned integer.\n\n  @param[in] x  unsigned integer number to count its digits\n  @return    number of decimal digits\n  */\n  inline unsigned int count_digits(number_unsigned_t x) noexcept\n  {\n    unsigned int n_digits = 1;\n\n    for (;;) {\n      if (x < 10) {\n        return n_digits;\n      }\n\n      if (x < 100) {\n        return n_digits + 1;\n      }\n\n      if (x < 1000) {\n        return n_digits + 2;\n      }\n\n      if (x < 10000) {\n        return n_digits + 3;\n      }\n\n      x = x / 10000u;\n      n_digits += 4;\n    }\n  }\n\n  /*!\n   * @brief convert a byte to a uppercase hex representation\n   * @param[in] byte byte to represent\n   * @return representation (\"00\"..\"FF\")\n   */\n  static std::string hex_bytes(std::uint8_t byte)\n  {\n    std::string result = \"FF\";\n    constexpr const char* nibble_to_hex = \"0123456789ABCDEF\";\n    result[0] = nibble_to_hex[byte / 16];\n    result[1] = nibble_to_hex[byte % 16];\n    return result;\n  }\n\n  // templates to avoid warnings about useless casts\n  template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0>\n  bool is_negative_number(NumberType x)\n  {\n    return x < 0;\n  }\n\n  template < typename NumberType, enable_if_t <std::is_unsigned<NumberType>::value, int > = 0 >\n  bool is_negative_number(NumberType /*unused*/)\n  {\n    return false;\n  }\n\n  /*!\n  @brief dump an integer\n\n  Dump a given integer to output stream @a o. Works internally with\n  @a number_buffer.\n\n  @param[in] x  integer number (signed or unsigned) to dump\n  @tparam NumberType either @a number_integer_t or @a number_unsigned_t\n  */\n  template < typename NumberType, detail::enable_if_t <\n               std::is_integral<NumberType>::value ||\n               std::is_same<NumberType, number_unsigned_t>::value ||\n               std::is_same<NumberType, number_integer_t>::value ||\n               std::is_same<NumberType, binary_char_t>::value,\n               int > = 0 >\n  void dump_integer(NumberType x)\n  {\n    static constexpr std::array<std::array<char, 2>, 100> digits_to_99 {\n      {\n        {{'0', '0'}}, {{'0', '1'}}, {{'0', '2'}}, {{'0', '3'}}, {{'0', '4'}}, {{'0', '5'}}, {{'0', '6'}}, {{'0', '7'}}, {{'0', '8'}}, {{'0', '9'}},\n        {{'1', '0'}}, {{'1', '1'}}, {{'1', '2'}}, {{'1', '3'}}, {{'1', '4'}}, {{'1', '5'}}, {{'1', '6'}}, {{'1', '7'}}, {{'1', '8'}}, {{'1', '9'}},\n        {{'2', '0'}}, {{'2', '1'}}, {{'2', '2'}}, {{'2', '3'}}, {{'2', '4'}}, {{'2', '5'}}, {{'2', '6'}}, {{'2', '7'}}, {{'2', '8'}}, {{'2', '9'}},\n        {{'3', '0'}}, {{'3', '1'}}, {{'3', '2'}}, {{'3', '3'}}, {{'3', '4'}}, {{'3', '5'}}, {{'3', '6'}}, {{'3', '7'}}, {{'3', '8'}}, {{'3', '9'}},\n        {{'4', '0'}}, {{'4', '1'}}, {{'4', '2'}}, {{'4', '3'}}, {{'4', '4'}}, {{'4', '5'}}, {{'4', '6'}}, {{'4', '7'}}, {{'4', '8'}}, {{'4', '9'}},\n        {{'5', '0'}}, {{'5', '1'}}, {{'5', '2'}}, {{'5', '3'}}, {{'5', '4'}}, {{'5', '5'}}, {{'5', '6'}}, {{'5', '7'}}, {{'5', '8'}}, {{'5', '9'}},\n        {{'6', '0'}}, {{'6', '1'}}, {{'6', '2'}}, {{'6', '3'}}, {{'6', '4'}}, {{'6', '5'}}, {{'6', '6'}}, {{'6', '7'}}, {{'6', '8'}}, {{'6', '9'}},\n        {{'7', '0'}}, {{'7', '1'}}, {{'7', '2'}}, {{'7', '3'}}, {{'7', '4'}}, {{'7', '5'}}, {{'7', '6'}}, {{'7', '7'}}, {{'7', '8'}}, {{'7', '9'}},\n        {{'8', '0'}}, {{'8', '1'}}, {{'8', '2'}}, {{'8', '3'}}, {{'8', '4'}}, {{'8', '5'}}, {{'8', '6'}}, {{'8', '7'}}, {{'8', '8'}}, {{'8', '9'}},\n        {{'9', '0'}}, {{'9', '1'}}, {{'9', '2'}}, {{'9', '3'}}, {{'9', '4'}}, {{'9', '5'}}, {{'9', '6'}}, {{'9', '7'}}, {{'9', '8'}}, {{'9', '9'}},\n      }\n    };\n\n    // special case for \"0\"\n    if (x == 0) {\n      o->write_character('0');\n      return;\n    }\n\n    // use a pointer to fill the buffer\n    auto buffer_ptr =\n      number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto,cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n    number_unsigned_t abs_value;\n    unsigned int n_chars{};\n\n    if (is_negative_number(x)) {\n      *buffer_ptr = '-';\n      abs_value = remove_sign(static_cast<number_integer_t>(x));\n      // account one more byte for the minus sign\n      n_chars = 1 + count_digits(abs_value);\n    } else {\n      abs_value = static_cast<number_unsigned_t>(x);\n      n_chars = count_digits(abs_value);\n    }\n\n    // spare 1 byte for '\\0'\n    JSON_ASSERT(n_chars < number_buffer.size() - 1);\n    // jump to the end to generate the string from backward,\n    // so we later avoid reversing the result\n    buffer_ptr += n_chars;\n\n    // Fast int2ascii implementation inspired by \"Fastware\" talk by Andrei Alexandrescu\n    // See: https://www.youtube.com/watch?v=o4-CwDo2zpg\n    while (abs_value >= 100) {\n      const auto digits_index = static_cast<unsigned>((abs_value % 100));\n      abs_value /= 100;\n      *(--buffer_ptr) = digits_to_99[digits_index][1];\n      *(--buffer_ptr) = digits_to_99[digits_index][0];\n    }\n\n    if (abs_value >= 10) {\n      const auto digits_index = static_cast<unsigned>(abs_value);\n      *(--buffer_ptr) = digits_to_99[digits_index][1];\n      *(--buffer_ptr) = digits_to_99[digits_index][0];\n    } else {\n      *(--buffer_ptr) = static_cast<char>('0' + abs_value);\n    }\n\n    o->write_characters(number_buffer.data(), n_chars);\n  }\n\n  /*!\n  @brief dump a floating-point number\n\n  Dump a given floating-point number to output stream @a o. Works internally\n  with @a number_buffer.\n\n  @param[in] x  floating-point number to dump\n  */\n  void dump_float(number_float_t x)\n  {\n    // NaN / inf\n    if (!std::isfinite(x)) {\n      o->write_characters(\"null\", 4);\n      return;\n    }\n\n    // If number_float_t is an IEEE-754 single or double precision number,\n    // use the Grisu2 algorithm to produce short numbers which are\n    // guaranteed to round-trip, using strtof and strtod, resp.\n    //\n    // NB: The test below works if <long double> == <double>.\n    static constexpr bool is_ieee_single_or_double\n      = (std::numeric_limits<number_float_t>::is_iec559 &&\n         std::numeric_limits<number_float_t>::digits == 24 &&\n         std::numeric_limits<number_float_t>::max_exponent == 128) ||\n        (std::numeric_limits<number_float_t>::is_iec559 &&\n         std::numeric_limits<number_float_t>::digits == 53 &&\n         std::numeric_limits<number_float_t>::max_exponent == 1024);\n    dump_float(x, std::integral_constant<bool, is_ieee_single_or_double>());\n  }\n\n  void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)\n  {\n    auto* begin = number_buffer.data();\n    auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(),\n                x);\n    o->write_characters(begin, static_cast<size_t>(end - begin));\n  }\n\n  void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)\n  {\n    // get number of digits for a float -> text -> float round-trip\n    static constexpr auto d = std::numeric_limits<number_float_t>::max_digits10;\n    // the actual conversion\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n    std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(),\n                                         \"%.*g\", d, x);\n    // negative value indicates an error\n    JSON_ASSERT(len > 0);\n    // check if buffer was large enough\n    JSON_ASSERT(static_cast<std::size_t>(len) < number_buffer.size());\n\n    // erase thousands separator\n    if (thousands_sep != '\\0') {\n      // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::remove returns an iterator, see https://github.com/nlohmann/json/issues/3081\n      const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len,\n                                   thousands_sep);\n      std::fill(end, number_buffer.end(), '\\0');\n      JSON_ASSERT((end - number_buffer.begin()) <= len);\n      len = (end - number_buffer.begin());\n    }\n\n    // convert decimal point to '.'\n    if (decimal_point != '\\0' && decimal_point != '.') {\n      // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::find returns an iterator, see https://github.com/nlohmann/json/issues/3081\n      const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(),\n                                     decimal_point);\n\n      if (dec_pos != number_buffer.end()) {\n        *dec_pos = '.';\n      }\n    }\n\n    o->write_characters(number_buffer.data(), static_cast<std::size_t>(len));\n    // determine if we need to append \".0\"\n    const bool value_is_int_like =\n      std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1,\n    [](char c) {\n      return c == '.' || c == 'e';\n    });\n\n    if (value_is_int_like) {\n      o->write_characters(\".0\", 2);\n    }\n  }\n\n  /*!\n  @brief check whether a string is UTF-8 encoded\n\n  The function checks each byte of a string whether it is UTF-8 encoded. The\n  result of the check is stored in the @a state parameter. The function must\n  be called initially with state 0 (accept). State 1 means the string must\n  be rejected, because the current byte is not allowed. If the string is\n  completely processed, but the state is non-zero, the string ended\n  prematurely; that is, the last byte indicated more bytes should have\n  followed.\n\n  @param[in,out] state  the state of the decoding\n  @param[in,out] codep  codepoint (valid only if resulting state is UTF8_ACCEPT)\n  @param[in] byte       next byte to decode\n  @return               new state\n\n  @note The function has been edited: a std::array is used.\n\n  @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>\n  @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/\n  */\n  static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep,\n                             const std::uint8_t byte) noexcept\n  {\n    static const std::array<std::uint8_t, 400> utf8d = {\n      {\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F\n        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F\n        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF\n        8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF\n        0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF\n        0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF\n        0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0\n        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2\n        1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4\n        1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6\n        1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8\n      }\n    };\n    JSON_ASSERT(byte < utf8d.size());\n    const std::uint8_t type = utf8d[byte];\n    codep = (state != UTF8_ACCEPT)\n            ? (byte & 0x3fu) | (codep << 6u)\n            : (0xFFu >> type) & (byte);\n    std::size_t index = 256u + static_cast<size_t>(state) * 16u +\n                        static_cast<size_t>(type);\n    JSON_ASSERT(index < 400);\n    state = utf8d[index];\n    return state;\n  }\n\n  /*\n   * Overload to make the compiler happy while it is instantiating\n   * dump_integer for number_unsigned_t.\n   * Must never be called.\n   */\n  number_unsigned_t remove_sign(number_unsigned_t x)\n  {\n    JSON_ASSERT(\n      false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n    return x; // LCOV_EXCL_LINE\n  }\n\n  /*\n   * Helper function for dump_integer\n   *\n   * This function takes a negative signed integer and returns its absolute\n   * value as unsigned integer. The plus/minus shuffling is necessary as we can\n   * not directly remove the sign of an arbitrary signed integer as the\n   * absolute values of INT_MIN and INT_MAX are usually not the same. See\n   * #1708 for details.\n   */\n  inline number_unsigned_t remove_sign(number_integer_t x) noexcept\n  {\n    JSON_ASSERT(x < 0 &&\n                x < (std::numeric_limits<number_integer_t>::max)()); // NOLINT(misc-redundant-expression)\n    return static_cast<number_unsigned_t>(-(x + 1)) + 1;\n  }\n\nprivate:\n  /// the output of the serializer\n  output_adapter_t<char> o = nullptr;\n\n  /// a (hopefully) large enough character buffer\n  std::array<char, 64> number_buffer{{}};\n\n  /// the locale\n  const std::lconv* loc = nullptr;\n  /// the locale's thousand separator character\n  const char thousands_sep = '\\0';\n  /// the locale's decimal point character\n  const char decimal_point = '\\0';\n\n  /// string buffer\n  std::array<char, 512> string_buffer{{}};\n\n  /// the indentation character\n  const char indent_char;\n  /// the indentation string\n  string_t indent_string;\n\n  /// error_handler how to react on decoding errors\n  const error_handler_t error_handler;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/value_t.hpp>\n\n// #include <nlohmann/json_fwd.hpp>\n\n// #include <nlohmann/ordered_map.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <functional> // equal_to, less\n#include <initializer_list> // initializer_list\n#include <iterator> // input_iterator_tag, iterator_traits\n#include <memory> // allocator\n#include <stdexcept> // for out_of_range\n#include <type_traits> // enable_if, is_convertible\n#include <utility> // pair\n#include <vector> // vector\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/// ordered_map: a minimal map-like container that preserves insertion order\n/// for use within nlohmann::basic_json<ordered_map>\ntemplate <class Key, class T, class IgnoredLess = std::less<Key>,\n          class Allocator = std::allocator<std::pair<const Key, T>>>\nstruct ordered_map : std::vector<std::pair<const Key, T>, Allocator> {\n  using key_type = Key;\n  using mapped_type = T;\n  using Container = std::vector<std::pair<const Key, T>, Allocator>;\n  using iterator = typename Container::iterator;\n  using const_iterator = typename Container::const_iterator;\n  using size_type = typename Container::size_type;\n  using value_type = typename Container::value_type;\n#ifdef JSON_HAS_CPP_14\n  using key_compare = std::equal_to<>;\n#else\n  using key_compare = std::equal_to<Key>;\n#endif\n\n  // Explicit constructors instead of `using Container::Container`\n  // otherwise older compilers choke on it (GCC <= 5.5, xcode <= 9.4)\n  ordered_map() noexcept(noexcept(Container())) : Container{} {}\n  explicit ordered_map(const Allocator& alloc) noexcept(noexcept(Container(\n        alloc))) : Container{alloc} {}\n  template <class It>\n  ordered_map(It first, It last, const Allocator& alloc = Allocator())\n    : Container{first, last, alloc} {}\n  ordered_map(std::initializer_list<value_type> init,\n              const Allocator& alloc = Allocator())\n    : Container{init, alloc} {}\n\n  std::pair<iterator, bool> emplace(const key_type& key, T&& t)\n  {\n    for (auto it = this->begin(); it != this->end(); ++it) {\n      if (m_compare(it->first, key)) {\n        return {it, false};\n      }\n    }\n\n    Container::emplace_back(key, std::forward<T>(t));\n    return {std::prev(this->end()), true};\n  }\n\n  template<class KeyType, detail::enable_if_t<\n             detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n  std::pair<iterator, bool> emplace(KeyType && key, T && t)\n  {\n    for (auto it = this->begin(); it != this->end(); ++it) {\n      if (m_compare(it->first, key)) {\n        return {it, false};\n      }\n    }\n\n    Container::emplace_back(std::forward<KeyType>(key), std::forward<T>(t));\n    return {std::prev(this->end()), true};\n  }\n\n  T& operator[](const key_type& key)\n  {\n    return emplace(key, T{}).first->second;\n  }\n\n  template<class KeyType, detail::enable_if_t<\n             detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n  T & operator[](KeyType && key)\n  {\n    return emplace(std::forward<KeyType>(key), T{}).first->second;\n  }\n\n  const T& operator[](const key_type& key) const\n  {\n    return at(key);\n  }\n\n  template<class KeyType, detail::enable_if_t<\n             detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n  const T & operator[](KeyType && key) const\n  {\n    return at(std::forward<KeyType>(key));\n  }\n\n  T& at(const key_type& key)\n  {\n    for (auto it = this->begin(); it != this->end(); ++it) {\n      if (m_compare(it->first, key)) {\n        return it->second;\n      }\n    }\n\n    JSON_THROW(std::out_of_range(\"key not found\"));\n  }\n\n  template<class KeyType, detail::enable_if_t<\n             detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n  T & at(KeyType && key)\n  {\n    for (auto it = this->begin(); it != this->end(); ++it) {\n      if (m_compare(it->first, key)) {\n        return it->second;\n      }\n    }\n\n    JSON_THROW(std::out_of_range(\"key not found\"));\n  }\n\n  const T& at(const key_type& key) const\n  {\n    for (auto it = this->begin(); it != this->end(); ++it) {\n      if (m_compare(it->first, key)) {\n        return it->second;\n      }\n    }\n\n    JSON_THROW(std::out_of_range(\"key not found\"));\n  }\n\n  template<class KeyType, detail::enable_if_t<\n             detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n  const T & at(KeyType && key) const\n  {\n    for (auto it = this->begin(); it != this->end(); ++it) {\n      if (m_compare(it->first, key)) {\n        return it->second;\n      }\n    }\n\n    JSON_THROW(std::out_of_range(\"key not found\"));\n  }\n\n  size_type erase(const key_type& key)\n  {\n    for (auto it = this->begin(); it != this->end(); ++it) {\n      if (m_compare(it->first, key)) {\n        // Since we cannot move const Keys, re-construct them in place\n        for (auto next = it; ++next != this->end(); ++it) {\n          it->~value_type(); // Destroy but keep allocation\n          new (&*it) value_type{std::move(*next)};\n        }\n\n        Container::pop_back();\n        return 1;\n      }\n    }\n\n    return 0;\n  }\n\n  template<class KeyType, detail::enable_if_t<\n             detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n  size_type erase(KeyType && key)\n  {\n    for (auto it = this->begin(); it != this->end(); ++it) {\n      if (m_compare(it->first, key)) {\n        // Since we cannot move const Keys, re-construct them in place\n        for (auto next = it; ++next != this->end(); ++it) {\n          it->~value_type(); // Destroy but keep allocation\n          new (&*it) value_type{std::move(*next)};\n        }\n\n        Container::pop_back();\n        return 1;\n      }\n    }\n\n    return 0;\n  }\n\n  iterator erase(iterator pos)\n  {\n    return erase(pos, std::next(pos));\n  }\n\n  iterator erase(iterator first, iterator last)\n  {\n    if (first == last) {\n      return first;\n    }\n\n    const auto elements_affected = std::distance(first, last);\n    const auto offset = std::distance(Container::begin(), first);\n\n    // This is the start situation. We need to delete elements_affected\n    // elements (3 in this example: e, f, g), and need to return an\n    // iterator past the last deleted element (h in this example).\n    // Note that offset is the distance from the start of the vector\n    // to first. We will need this later.\n\n    // [ a, b, c, d, e, f, g, h, i, j ]\n    //               ^        ^\n    //             first    last\n\n    // Since we cannot move const Keys, we re-construct them in place.\n    // We start at first and re-construct (viz. copy) the elements from\n    // the back of the vector. Example for first iteration:\n\n    //               ,--------.\n    //               v        |   destroy e and re-construct with h\n    // [ a, b, c, d, e, f, g, h, i, j ]\n    //               ^        ^\n    //               it       it + elements_affected\n\n    for (auto it = first; std::next(it, elements_affected) != Container::end();\n         ++it) {\n      it->~value_type(); // destroy but keep allocation\n      new (&*it) value_type{std::move(*std::next(it, elements_affected))}; // \"move\" next element to it\n    }\n\n    // [ a, b, c, d, h, i, j, h, i, j ]\n    //               ^        ^\n    //             first    last\n    // remove the unneeded elements at the end of the vector\n    Container::resize(this->size() - static_cast<size_type>(elements_affected));\n    // [ a, b, c, d, h, i, j ]\n    //               ^        ^\n    //             first    last\n    // first is now pointing past the last deleted element, but we cannot\n    // use this iterator, because it may have been invalidated by the\n    // resize call. Instead, we can return begin() + offset.\n    return Container::begin() + offset;\n  }\n\n  size_type count(const key_type& key) const\n  {\n    for (auto it = this->begin(); it != this->end(); ++it) {\n      if (m_compare(it->first, key)) {\n        return 1;\n      }\n    }\n\n    return 0;\n  }\n\n  template<class KeyType, detail::enable_if_t<\n             detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n  size_type count(KeyType && key) const\n  {\n    for (auto it = this->begin(); it != this->end(); ++it) {\n      if (m_compare(it->first, key)) {\n        return 1;\n      }\n    }\n\n    return 0;\n  }\n\n  iterator find(const key_type& key)\n  {\n    for (auto it = this->begin(); it != this->end(); ++it) {\n      if (m_compare(it->first, key)) {\n        return it;\n      }\n    }\n\n    return Container::end();\n  }\n\n  template<class KeyType, detail::enable_if_t<\n             detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n  iterator find(KeyType && key)\n  {\n    for (auto it = this->begin(); it != this->end(); ++it) {\n      if (m_compare(it->first, key)) {\n        return it;\n      }\n    }\n\n    return Container::end();\n  }\n\n  const_iterator find(const key_type& key) const\n  {\n    for (auto it = this->begin(); it != this->end(); ++it) {\n      if (m_compare(it->first, key)) {\n        return it;\n      }\n    }\n\n    return Container::end();\n  }\n\n  std::pair<iterator, bool> insert(value_type&& value)\n  {\n    return emplace(value.first, std::move(value.second));\n  }\n\n  std::pair<iterator, bool> insert(const value_type& value)\n  {\n    for (auto it = this->begin(); it != this->end(); ++it) {\n      if (m_compare(it->first, value.first)) {\n        return {it, false};\n      }\n    }\n\n    Container::push_back(value);\n    return {--this->end(), true};\n  }\n\n  template<typename InputIt>\n  using require_input_iter = typename\n  std::enable_if<std::is_convertible<typename std::iterator_traits<InputIt>::iterator_category,\n                                     std::input_iterator_tag>::value>::type;\n\n  template<typename InputIt, typename = require_input_iter<InputIt>>\n  void insert(InputIt first, InputIt last)\n  {\n    for (auto it = first; it != last; ++it) {\n      insert(*it);\n    }\n  }\n\nprivate:\n  JSON_NO_UNIQUE_ADDRESS key_compare m_compare = key_compare();\n};\n\nNLOHMANN_JSON_NAMESPACE_END\n\n\n#if defined(JSON_HAS_CPP_17)\n#include <any>\n#include <string_view>\n#endif\n\n/*!\n@brief namespace for Niels Lohmann\n@see https://github.com/nlohmann\n@since version 1.0.0\n*/\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/*!\n@brief a class to store JSON values\n\n@internal\n@invariant The member variables @a m_value and @a m_type have the following\nrelationship:\n- If `m_type == value_t::object`, then `m_value.object != nullptr`.\n- If `m_type == value_t::array`, then `m_value.array != nullptr`.\n- If `m_type == value_t::string`, then `m_value.string != nullptr`.\nThe invariants are checked by member function assert_invariant().\n\n@note ObjectType trick from https://stackoverflow.com/a/9860911\n@endinternal\n\n@since version 1.0.0\n\n@nosubgrouping\n*/\nNLOHMANN_BASIC_JSON_TPL_DECLARATION\nclass basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions)\n{\nprivate:\n  template<detail::value_t> friend struct detail::external_constructor;\n\n  template<typename>\n  friend class ::nlohmann::json_pointer;\n  // can be restored when json_pointer backwards compatibility is removed\n  // friend ::nlohmann::json_pointer<StringType>;\n\n  template<typename BasicJsonType, typename InputType>\n  friend class ::nlohmann::detail::parser;\n  friend ::nlohmann::detail::serializer<basic_json>;\n  template<typename BasicJsonType>\n  friend class ::nlohmann::detail::iter_impl;\n  template<typename BasicJsonType, typename CharType>\n  friend class ::nlohmann::detail::binary_writer;\n  template<typename BasicJsonType, typename InputType, typename SAX>\n  friend class ::nlohmann::detail::binary_reader;\n  template<typename BasicJsonType>\n  friend class ::nlohmann::detail::json_sax_dom_parser;\n  template<typename BasicJsonType>\n  friend class ::nlohmann::detail::json_sax_dom_callback_parser;\n  friend class ::nlohmann::detail::exception;\n\n  /// workaround type for MSVC\n  using basic_json_t = NLOHMANN_BASIC_JSON_TPL;\n\nJSON_PRIVATE_UNLESS_TESTED:\n  // convenience aliases for types residing in namespace detail;\n  using lexer = ::nlohmann::detail::lexer_base<basic_json>;\n\n  template<typename InputAdapterType>\n  static ::nlohmann::detail::parser<basic_json, InputAdapterType> parser(\n    InputAdapterType adapter,\n    detail::parser_callback_t<basic_json>cb = nullptr,\n    const bool allow_exceptions = true,\n    const bool ignore_comments = false\n                             )\n  {\n    return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(\n          adapter),\n        std::move(cb), allow_exceptions, ignore_comments);\n  }\n\nprivate:\n  using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t;\n  template<typename BasicJsonType>\n  using internal_iterator = ::nlohmann::detail::internal_iterator<BasicJsonType>;\n  template<typename BasicJsonType>\n  using iter_impl = ::nlohmann::detail::iter_impl<BasicJsonType>;\n  template<typename Iterator>\n  using iteration_proxy = ::nlohmann::detail::iteration_proxy<Iterator>;\n  template<typename Base> using json_reverse_iterator\n  = ::nlohmann::detail::json_reverse_iterator<Base>;\n\n  template<typename CharType>\n  using output_adapter_t = ::nlohmann::detail::output_adapter_t<CharType>;\n\n  template<typename InputType>\n  using binary_reader = ::nlohmann::detail::binary_reader<basic_json, InputType>;\n  template<typename CharType> using binary_writer\n  = ::nlohmann::detail::binary_writer<basic_json, CharType>;\n\nJSON_PRIVATE_UNLESS_TESTED:\n  using serializer = ::nlohmann::detail::serializer<basic_json>;\n\npublic:\n  using value_t = detail::value_t;\n  /// JSON Pointer, see @ref nlohmann::json_pointer\n  using json_pointer = ::nlohmann::json_pointer<StringType>;\n  template<typename T, typename SFINAE>\n  using json_serializer = JSONSerializer<T, SFINAE>;\n  /// how to treat decoding errors\n  using error_handler_t = detail::error_handler_t;\n  /// how to treat CBOR tags\n  using cbor_tag_handler_t = detail::cbor_tag_handler_t;\n  /// helper type for initializer lists of basic_json values\n  using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>;\n\n  using input_format_t = detail::input_format_t;\n  /// SAX interface type, see @ref nlohmann::json_sax\n  using json_sax_t = json_sax<basic_json>;\n\n  ////////////////\n  // exceptions //\n  ////////////////\n\n  /// @name exceptions\n  /// Classes to implement user-defined exceptions.\n  /// @{\n\n  using exception = detail::exception;\n  using parse_error = detail::parse_error;\n  using invalid_iterator = detail::invalid_iterator;\n  using type_error = detail::type_error;\n  using out_of_range = detail::out_of_range;\n  using other_error = detail::other_error;\n\n  /// @}\n\n\n  /////////////////////\n  // container types //\n  /////////////////////\n\n  /// @name container types\n  /// The canonic container types to use @ref basic_json like any other STL\n  /// container.\n  /// @{\n\n  /// the type of elements in a basic_json container\n  using value_type = basic_json;\n\n  /// the type of an element reference\n  using reference = value_type&;\n  /// the type of an element const reference\n  using const_reference = const value_type&;\n\n  /// a type to represent differences between iterators\n  using difference_type = std::ptrdiff_t;\n  /// a type to represent container sizes\n  using size_type = std::size_t;\n\n  /// the allocator type\n  using allocator_type = AllocatorType<basic_json>;\n\n  /// the type of an element pointer\n  using pointer = typename std::allocator_traits<allocator_type>::pointer;\n  /// the type of an element const pointer\n  using const_pointer = typename\n                        std::allocator_traits<allocator_type>::const_pointer;\n\n  /// an iterator for a basic_json container\n  using iterator = iter_impl<basic_json>;\n  /// a const iterator for a basic_json container\n  using const_iterator = iter_impl<const basic_json>;\n  /// a reverse iterator for a basic_json container\n  using reverse_iterator = json_reverse_iterator<typename basic_json::iterator>;\n  /// a const reverse iterator for a basic_json container\n  using const_reverse_iterator =\n    json_reverse_iterator<typename basic_json::const_iterator>;\n\n  /// @}\n\n\n  /// @brief returns the allocator associated with the container\n  /// @sa https://json.nlohmann.me/api/basic_json/get_allocator/\n  static allocator_type get_allocator()\n  {\n    return allocator_type();\n  }\n\n  /// @brief returns version information on the library\n  /// @sa https://json.nlohmann.me/api/basic_json/meta/\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json meta()\n  {\n    basic_json result;\n    result[\"copyright\"] = \"(C) 2013-2022 Niels Lohmann\";\n    result[\"name\"] = \"JSON for Modern C++\";\n    result[\"url\"] = \"https://github.com/nlohmann/json\";\n    result[\"version\"][\"string\"] =\n      detail::concat(std::to_string(NLOHMANN_JSON_VERSION_MAJOR), '.',\n                     std::to_string(NLOHMANN_JSON_VERSION_MINOR), '.',\n                     std::to_string(NLOHMANN_JSON_VERSION_PATCH));\n    result[\"version\"][\"major\"] = NLOHMANN_JSON_VERSION_MAJOR;\n    result[\"version\"][\"minor\"] = NLOHMANN_JSON_VERSION_MINOR;\n    result[\"version\"][\"patch\"] = NLOHMANN_JSON_VERSION_PATCH;\n#ifdef _WIN32\n    result[\"platform\"] = \"win32\";\n#elif defined __linux__\n    result[\"platform\"] = \"linux\";\n#elif defined __APPLE__\n    result[\"platform\"] = \"apple\";\n#elif defined __unix__\n    result[\"platform\"] = \"unix\";\n#else\n    result[\"platform\"] = \"unknown\";\n#endif\n#if defined(__ICC) || defined(__INTEL_COMPILER)\n    result[\"compiler\"] = {{\"family\", \"icc\"}, {\"version\", __INTEL_COMPILER}};\n#elif defined(__clang__)\n    result[\"compiler\"] = {{\"family\", \"clang\"}, {\"version\", __clang_version__}};\n#elif defined(__GNUC__) || defined(__GNUG__)\n    result[\"compiler\"] = {{\"family\", \"gcc\"}, {\"version\", detail::concat(\n          std::to_string(__GNUC__), '.',\n          std::to_string(__GNUC_MINOR__), '.',\n          std::to_string(__GNUC_PATCHLEVEL__))\n      }\n    };\n#elif defined(__HP_cc) || defined(__HP_aCC)\n    result[\"compiler\"] = \"hp\"\n#elif defined(__IBMCPP__)\n    result[\"compiler\"] = {{\"family\", \"ilecpp\"}, {\"version\", __IBMCPP__}};\n#elif defined(_MSC_VER)\n    result[\"compiler\"] = {{\"family\", \"msvc\"}, {\"version\", _MSC_VER}};\n#elif defined(__PGI)\n    result[\"compiler\"] = {{\"family\", \"pgcpp\"}, {\"version\", __PGI}};\n#elif defined(__SUNPRO_CC)\n    result[\"compiler\"] = {{\"family\", \"sunpro\"}, {\"version\", __SUNPRO_CC}};\n#else\n    result[\"compiler\"] = {{\"family\", \"unknown\"}, {\"version\", \"unknown\"}};\n#endif\n#if defined(_MSVC_LANG)\n    result[\"compiler\"][\"c++\"] = std::to_string(_MSVC_LANG);\n#elif defined(__cplusplus)\n    result[\"compiler\"][\"c++\"] = std::to_string(__cplusplus);\n#else\n    result[\"compiler\"][\"c++\"] = \"unknown\";\n#endif\n    return result;\n  }\n\n\n  ///////////////////////////\n  // JSON value data types //\n  ///////////////////////////\n\n  /// @name JSON value data types\n  /// The data types to store a JSON value. These types are derived from\n  /// the template arguments passed to class @ref basic_json.\n  /// @{\n\n  /// @brief default object key comparator type\n  /// The actual object key comparator type (@ref object_comparator_t) may be\n  /// different.\n  /// @sa https://json.nlohmann.me/api/basic_json/default_object_comparator_t/\n#if defined(JSON_HAS_CPP_14)\n  // use of transparent comparator avoids unnecessary repeated construction of temporaries\n  // in functions involving lookup by key with types other than object_t::key_type (aka. StringType)\n  using default_object_comparator_t = std::less<>;\n#else\n  using default_object_comparator_t = std::less<StringType>;\n#endif\n\n  /// @brief a type for an object\n  /// @sa https://json.nlohmann.me/api/basic_json/object_t/\n  using object_t = ObjectType<StringType,\n        basic_json,\n        default_object_comparator_t,\n        AllocatorType<std::pair<const StringType,\n        basic_json>>>;\n\n  /// @brief a type for an array\n  /// @sa https://json.nlohmann.me/api/basic_json/array_t/\n  using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;\n\n  /// @brief a type for a string\n  /// @sa https://json.nlohmann.me/api/basic_json/string_t/\n  using string_t = StringType;\n\n  /// @brief a type for a boolean\n  /// @sa https://json.nlohmann.me/api/basic_json/boolean_t/\n  using boolean_t = BooleanType;\n\n  /// @brief a type for a number (integer)\n  /// @sa https://json.nlohmann.me/api/basic_json/number_integer_t/\n  using number_integer_t = NumberIntegerType;\n\n  /// @brief a type for a number (unsigned)\n  /// @sa https://json.nlohmann.me/api/basic_json/number_unsigned_t/\n  using number_unsigned_t = NumberUnsignedType;\n\n  /// @brief a type for a number (floating-point)\n  /// @sa https://json.nlohmann.me/api/basic_json/number_float_t/\n  using number_float_t = NumberFloatType;\n\n  /// @brief a type for a packed binary type\n  /// @sa https://json.nlohmann.me/api/basic_json/binary_t/\n  using binary_t = nlohmann::byte_container_with_subtype<BinaryType>;\n\n  /// @brief object key comparator type\n  /// @sa https://json.nlohmann.me/api/basic_json/object_comparator_t/\n  using object_comparator_t = detail::actual_object_comparator_t<basic_json>;\n\n  /// @}\n\nprivate:\n\n  /// helper for exception-safe object creation\n  template<typename T, typename... Args>\n  JSON_HEDLEY_RETURNS_NON_NULL\n  static T* create(Args&& ... args)\n  {\n    AllocatorType<T> alloc;\n    using AllocatorTraits = std::allocator_traits<AllocatorType<T>>;\n    auto deleter = [&](T * obj) {\n      AllocatorTraits::deallocate(alloc, obj, 1);\n    };\n    std::unique_ptr<T, decltype(deleter)> obj(AllocatorTraits::allocate(alloc, 1),\n        deleter);\n    AllocatorTraits::construct(alloc, obj.get(), std::forward<Args>(args)...);\n    JSON_ASSERT(obj != nullptr);\n    return obj.release();\n  }\n\n  ////////////////////////\n  // JSON value storage //\n  ////////////////////////\n\nJSON_PRIVATE_UNLESS_TESTED:\n  /*!\n  @brief a JSON value\n\n  The actual storage for a JSON value of the @ref basic_json class. This\n  union combines the different storage types for the JSON value types\n  defined in @ref value_t.\n\n  JSON type | value_t type    | used type\n  --------- | --------------- | ------------------------\n  object    | object          | pointer to @ref object_t\n  array     | array           | pointer to @ref array_t\n  string    | string          | pointer to @ref string_t\n  boolean   | boolean         | @ref boolean_t\n  number    | number_integer  | @ref number_integer_t\n  number    | number_unsigned | @ref number_unsigned_t\n  number    | number_float    | @ref number_float_t\n  binary    | binary          | pointer to @ref binary_t\n  null      | null            | *no value is stored*\n\n  @note Variable-length types (objects, arrays, and strings) are stored as\n  pointers. The size of the union should not exceed 64 bits if the default\n  value types are used.\n\n  @since version 1.0.0\n  */\n  union json_value {\n    /// object (stored with pointer to save storage)\n    object_t* object;\n    /// array (stored with pointer to save storage)\n    array_t* array;\n    /// string (stored with pointer to save storage)\n    string_t* string;\n    /// binary (stored with pointer to save storage)\n    binary_t* binary;\n    /// boolean\n    boolean_t boolean;\n    /// number (integer)\n    number_integer_t number_integer;\n    /// number (unsigned integer)\n    number_unsigned_t number_unsigned;\n    /// number (floating-point)\n    number_float_t number_float;\n\n    /// default constructor (for null values)\n    json_value() = default;\n    /// constructor for booleans\n    json_value(boolean_t v) noexcept : boolean(v) {}\n    /// constructor for numbers (integer)\n    json_value(number_integer_t v) noexcept : number_integer(v) {}\n    /// constructor for numbers (unsigned)\n    json_value(number_unsigned_t v) noexcept : number_unsigned(v) {}\n    /// constructor for numbers (floating-point)\n    json_value(number_float_t v) noexcept : number_float(v) {}\n    /// constructor for empty values of a given type\n    json_value(value_t t) {\n      switch (t) {\n      case value_t::object: {\n        object = create<object_t>();\n        break;\n      }\n\n      case value_t::array: {\n        array = create<array_t>();\n        break;\n      }\n\n      case value_t::string: {\n        string = create<string_t>(\"\");\n        break;\n      }\n\n      case value_t::binary: {\n        binary = create<binary_t>();\n        break;\n      }\n\n      case value_t::boolean: {\n        boolean = static_cast<boolean_t>(false);\n        break;\n      }\n\n      case value_t::number_integer: {\n        number_integer = static_cast<number_integer_t>(0);\n        break;\n      }\n\n      case value_t::number_unsigned: {\n        number_unsigned = static_cast<number_unsigned_t>(0);\n        break;\n      }\n\n      case value_t::number_float: {\n        number_float = static_cast<number_float_t>(0.0);\n        break;\n      }\n\n      case value_t::null: {\n        object = nullptr;  // silence warning, see #821\n        break;\n      }\n\n      case value_t::discarded:\n      default: {\n        object = nullptr;  // silence warning, see #821\n\n        if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) {\n          JSON_THROW(other_error::create(500,\n                                         \"961c151d2e87f2686a955a9be24d316f1362bf21 3.11.2\", nullptr)); // LCOV_EXCL_LINE\n        }\n\n        break;\n      }\n      }\n    }\n\n    /// constructor for strings\n    json_value(const string_t& value) : string(create<string_t>(value)) {}\n\n    /// constructor for rvalue strings\n    json_value(string_t&& value) : string(create<string_t>(std::move(value))) {}\n\n    /// constructor for objects\n    json_value(const object_t& value) : object(create<object_t>(value)) {}\n\n    /// constructor for rvalue objects\n    json_value(object_t&& value) : object(create<object_t>(std::move(value))) {}\n\n    /// constructor for arrays\n    json_value(const array_t& value) : array(create<array_t>(value)) {}\n\n    /// constructor for rvalue arrays\n    json_value(array_t&& value) : array(create<array_t>(std::move(value))) {}\n\n    /// constructor for binary arrays\n    json_value(const typename binary_t::container_type& value) : binary(\n        create<binary_t>(value)) {}\n\n    /// constructor for rvalue binary arrays\n    json_value(typename binary_t::container_type&& value) : binary(create<binary_t>\n          (std::move(value))) {}\n\n    /// constructor for binary arrays (internal type)\n    json_value(const binary_t& value) : binary(create<binary_t>(value)) {}\n\n    /// constructor for rvalue binary arrays (internal type)\n    json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}\n\n    void destroy(value_t t) {\n      if (t == value_t::array || t == value_t::object) {\n        // flatten the current json_value to a heap-allocated stack\n        std::vector<basic_json> stack;\n\n        // move the top-level items to stack\n        if (t == value_t::array) {\n          stack.reserve(array->size());\n          std::move(array->begin(), array->end(), std::back_inserter(stack));\n        } else {\n          stack.reserve(object->size());\n\n          for (auto&& it : *object) {\n            stack.push_back(std::move(it.second));\n          }\n        }\n\n        while (!stack.empty()) {\n          // move the last item to local variable to be processed\n          basic_json current_item(std::move(stack.back()));\n          stack.pop_back();\n\n          // if current_item is array/object, move\n          // its children to the stack to be processed later\n          if (current_item.is_array()) {\n            std::move(current_item.m_value.array->begin(),\n                      current_item.m_value.array->end(), std::back_inserter(stack));\n            current_item.m_value.array->clear();\n          } else if (current_item.is_object()) {\n            for (auto&& it : *current_item.m_value.object) {\n              stack.push_back(std::move(it.second));\n            }\n\n            current_item.m_value.object->clear();\n          }\n\n          // it's now safe that current_item get destructed\n          // since it doesn't have any children\n        }\n      }\n\n      switch (t) {\n      case value_t::object: {\n        AllocatorType<object_t> alloc;\n        std::allocator_traits<decltype(alloc)>::destroy(alloc, object);\n        std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);\n        break;\n      }\n\n      case value_t::array: {\n        AllocatorType<array_t> alloc;\n        std::allocator_traits<decltype(alloc)>::destroy(alloc, array);\n        std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);\n        break;\n      }\n\n      case value_t::string: {\n        AllocatorType<string_t> alloc;\n        std::allocator_traits<decltype(alloc)>::destroy(alloc, string);\n        std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);\n        break;\n      }\n\n      case value_t::binary: {\n        AllocatorType<binary_t> alloc;\n        std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);\n        std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);\n        break;\n      }\n\n      case value_t::null:\n      case value_t::boolean:\n      case value_t::number_integer:\n      case value_t::number_unsigned:\n      case value_t::number_float:\n      case value_t::discarded:\n      default: {\n        break;\n      }\n      }\n    }\n  };\n\nprivate:\n  /*!\n  @brief checks the class invariants\n\n  This function asserts the class invariants. It needs to be called at the\n  end of every constructor to make sure that created objects respect the\n  invariant. Furthermore, it has to be called each time the type of a JSON\n  value is changed, because the invariant expresses a relationship between\n  @a m_type and @a m_value.\n\n  Furthermore, the parent relation is checked for arrays and objects: If\n  @a check_parents true and the value is an array or object, then the\n  container's elements must have the current value as parent.\n\n  @param[in] check_parents  whether the parent relation should be checked.\n             The value is true by default and should only be set to false\n             during destruction of objects when the invariant does not\n             need to hold.\n  */\n  void assert_invariant(bool check_parents = true) const noexcept\n  {\n    JSON_ASSERT(m_type != value_t::object || m_value.object != nullptr);\n    JSON_ASSERT(m_type != value_t::array || m_value.array != nullptr);\n    JSON_ASSERT(m_type != value_t::string || m_value.string != nullptr);\n    JSON_ASSERT(m_type != value_t::binary || m_value.binary != nullptr);\n#if JSON_DIAGNOSTICS\n    JSON_TRY {\n      // cppcheck-suppress assertWithSideEffect\n      JSON_ASSERT(!check_parents || !is_structured() || std::all_of(begin(), end(), [this](const basic_json & j)\n      {\n        return j.m_parent == this;\n      }));\n    }\n    JSON_CATCH(...) {} // LCOV_EXCL_LINE\n#endif\n    static_cast<void>(check_parents);\n  }\n\n  void set_parents()\n  {\n#if JSON_DIAGNOSTICS\n\n    switch (m_type) {\n    case value_t::array: {\n      for (auto& element : *m_value.array) {\n        element.m_parent = this;\n      }\n\n      break;\n    }\n\n    case value_t::object: {\n      for (auto& element : *m_value.object) {\n        element.second.m_parent = this;\n      }\n\n      break;\n    }\n\n    case value_t::null:\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default:\n      break;\n    }\n\n#endif\n  }\n\n  iterator set_parents(iterator it,\n                       typename iterator::difference_type count_set_parents)\n  {\n#if JSON_DIAGNOSTICS\n\n    for (typename iterator::difference_type i = 0; i < count_set_parents; ++i) {\n      (it + i)->m_parent = this;\n    }\n\n#else\n    static_cast<void>(count_set_parents);\n#endif\n    return it;\n  }\n\n  reference set_parent(reference j,\n                       std::size_t old_capacity = static_cast<std::size_t>(-1))\n  {\n#if JSON_DIAGNOSTICS\n\n    if (old_capacity != static_cast<std::size_t>(-1)) {\n      // see https://github.com/nlohmann/json/issues/2838\n      JSON_ASSERT(type() == value_t::array);\n\n      if (JSON_HEDLEY_UNLIKELY(m_value.array->capacity() != old_capacity)) {\n        // capacity has changed: update all parents\n        set_parents();\n        return j;\n      }\n    }\n\n    // ordered_json uses a vector internally, so pointers could have\n    // been invalidated; see https://github.com/nlohmann/json/issues/2962\n#ifdef JSON_HEDLEY_MSVC_VERSION\n#pragma warning(push )\n#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr\n#endif\n\n    if (detail::is_ordered_map<object_t>::value) {\n      set_parents();\n      return j;\n    }\n\n#ifdef JSON_HEDLEY_MSVC_VERSION\n#pragma warning( pop )\n#endif\n    j.m_parent = this;\n#else\n    static_cast<void>(j);\n    static_cast<void>(old_capacity);\n#endif\n    return j;\n  }\n\npublic:\n  //////////////////////////\n  // JSON parser callback //\n  //////////////////////////\n\n  /// @brief parser event types\n  /// @sa https://json.nlohmann.me/api/basic_json/parse_event_t/\n  using parse_event_t = detail::parse_event_t;\n\n  /// @brief per-element parser callback type\n  /// @sa https://json.nlohmann.me/api/basic_json/parser_callback_t/\n  using parser_callback_t = detail::parser_callback_t<basic_json>;\n\n  //////////////////\n  // constructors //\n  //////////////////\n\n  /// @name constructors and destructors\n  /// Constructors of class @ref basic_json, copy/move constructor, copy\n  /// assignment, static functions creating objects, and the destructor.\n  /// @{\n\n  /// @brief create an empty value with a given type\n  /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n  basic_json(const value_t v)\n    : m_type(v), m_value(v)\n  {\n    assert_invariant();\n  }\n\n  /// @brief create a null object\n  /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n  basic_json(std::nullptr_t = nullptr)\n  noexcept // NOLINT(bugprone-exception-escape)\n    : basic_json(value_t::null)\n  {\n    assert_invariant();\n  }\n\n  /// @brief create a JSON value from compatible types\n  /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n  template < typename CompatibleType,\n             typename U = detail::uncvref_t<CompatibleType>,\n             detail::enable_if_t <\n               !detail::is_basic_json<U>::value &&\n               detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >\n  basic_json(CompatibleType &&\n             val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)\n                             JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),\n                                 std::forward<CompatibleType>(val))))\n  {\n    JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val));\n    set_parents();\n    assert_invariant();\n  }\n\n  /// @brief create a JSON value from an existing one\n  /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n  template < typename BasicJsonType,\n             detail::enable_if_t <\n               detail::is_basic_json<BasicJsonType>::value&&\n               !std::is_same<basic_json, BasicJsonType>::value, int > = 0 >\n  basic_json(const BasicJsonType& val)\n  {\n    using other_boolean_t = typename BasicJsonType::boolean_t;\n    using other_number_float_t = typename BasicJsonType::number_float_t;\n    using other_number_integer_t = typename BasicJsonType::number_integer_t;\n    using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using other_string_t = typename BasicJsonType::string_t;\n    using other_object_t = typename BasicJsonType::object_t;\n    using other_array_t = typename BasicJsonType::array_t;\n    using other_binary_t = typename BasicJsonType::binary_t;\n\n    switch (val.type()) {\n    case value_t::boolean:\n      JSONSerializer<other_boolean_t>::to_json(*this,\n          val.template get<other_boolean_t>());\n      break;\n\n    case value_t::number_float:\n      JSONSerializer<other_number_float_t>::to_json(*this,\n          val.template get<other_number_float_t>());\n      break;\n\n    case value_t::number_integer:\n      JSONSerializer<other_number_integer_t>::to_json(*this,\n          val.template get<other_number_integer_t>());\n      break;\n\n    case value_t::number_unsigned:\n      JSONSerializer<other_number_unsigned_t>::to_json(*this,\n          val.template get<other_number_unsigned_t>());\n      break;\n\n    case value_t::string:\n      JSONSerializer<other_string_t>::to_json(*this,\n                                              val.template get_ref<const other_string_t&>());\n      break;\n\n    case value_t::object:\n      JSONSerializer<other_object_t>::to_json(*this,\n                                              val.template get_ref<const other_object_t&>());\n      break;\n\n    case value_t::array:\n      JSONSerializer<other_array_t>::to_json(*this,\n                                             val.template get_ref<const other_array_t&>());\n      break;\n\n    case value_t::binary:\n      JSONSerializer<other_binary_t>::to_json(*this,\n                                              val.template get_ref<const other_binary_t&>());\n      break;\n\n    case value_t::null:\n      *this = nullptr;\n      break;\n\n    case value_t::discarded:\n      m_type = value_t::discarded;\n      break;\n\n    default:            // LCOV_EXCL_LINE\n      JSON_ASSERT(\n        false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n    }\n\n    JSON_ASSERT(m_type == val.type());\n    set_parents();\n    assert_invariant();\n  }\n\n  /// @brief create a container (array or object) from an initializer list\n  /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n  basic_json(initializer_list_t init,\n             bool type_deduction = true,\n             value_t manual_type = value_t::array)\n  {\n    // check if each element is an array with two elements whose first\n    // element is a string\n    bool is_an_object = std::all_of(init.begin(), init.end(),\n    [](const detail::json_ref<basic_json>& element_ref) {\n      return element_ref->is_array() && element_ref->size() == 2 &&\n             (*element_ref)[0].is_string();\n    });\n\n    // adjust type if type deduction is not wanted\n    if (!type_deduction) {\n      // if array is wanted, do not create an object though possible\n      if (manual_type == value_t::array) {\n        is_an_object = false;\n      }\n\n      // if object is wanted but impossible, throw an exception\n      if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object)) {\n        JSON_THROW(type_error::create(301, \"cannot create object from initializer list\",\n                                      nullptr));\n      }\n    }\n\n    if (is_an_object) {\n      // the initializer list is a list of pairs -> create object\n      m_type = value_t::object;\n      m_value = value_t::object;\n\n      for (auto& element_ref : init) {\n        auto element = element_ref.moved_or_copied();\n        m_value.object->emplace(\n          std::move(*((*element.m_value.array)[0].m_value.string)),\n          std::move((*element.m_value.array)[1]));\n      }\n    } else {\n      // the initializer list describes an array -> create array\n      m_type = value_t::array;\n      m_value.array = create<array_t>(init.begin(), init.end());\n    }\n\n    set_parents();\n    assert_invariant();\n  }\n\n  /// @brief explicitly create a binary array (without subtype)\n  /// @sa https://json.nlohmann.me/api/basic_json/binary/\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json binary(const typename binary_t::container_type& init)\n  {\n    auto res = basic_json();\n    res.m_type = value_t::binary;\n    res.m_value = init;\n    return res;\n  }\n\n  /// @brief explicitly create a binary array (with subtype)\n  /// @sa https://json.nlohmann.me/api/basic_json/binary/\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json binary(const typename binary_t::container_type& init,\n                           typename binary_t::subtype_type subtype)\n  {\n    auto res = basic_json();\n    res.m_type = value_t::binary;\n    res.m_value = binary_t(init, subtype);\n    return res;\n  }\n\n  /// @brief explicitly create a binary array\n  /// @sa https://json.nlohmann.me/api/basic_json/binary/\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json binary(typename binary_t::container_type&& init)\n  {\n    auto res = basic_json();\n    res.m_type = value_t::binary;\n    res.m_value = std::move(init);\n    return res;\n  }\n\n  /// @brief explicitly create a binary array (with subtype)\n  /// @sa https://json.nlohmann.me/api/basic_json/binary/\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json binary(typename binary_t::container_type&& init,\n                           typename binary_t::subtype_type subtype)\n  {\n    auto res = basic_json();\n    res.m_type = value_t::binary;\n    res.m_value = binary_t(std::move(init), subtype);\n    return res;\n  }\n\n  /// @brief explicitly create an array from an initializer list\n  /// @sa https://json.nlohmann.me/api/basic_json/array/\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json array(initializer_list_t init = {})\n  {\n    return basic_json(init, false, value_t::array);\n  }\n\n  /// @brief explicitly create an object from an initializer list\n  /// @sa https://json.nlohmann.me/api/basic_json/object/\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json object(initializer_list_t init = {})\n  {\n    return basic_json(init, false, value_t::object);\n  }\n\n  /// @brief construct an array with count copies of given value\n  /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n  basic_json(size_type cnt, const basic_json& val)\n    : m_type(value_t::array)\n  {\n    m_value.array = create<array_t>(cnt, val);\n    set_parents();\n    assert_invariant();\n  }\n\n  /// @brief construct a JSON container given an iterator range\n  /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n  template < class InputIT, typename std::enable_if <\n               std::is_same<InputIT, typename basic_json_t::iterator>::value ||\n               std::is_same<InputIT, typename basic_json_t::const_iterator>::value,\n               int >::type = 0 >\n  basic_json(InputIT first, InputIT last)\n  {\n    JSON_ASSERT(first.m_object != nullptr);\n    JSON_ASSERT(last.m_object != nullptr);\n\n    // make sure iterator fits the current value\n    if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) {\n      JSON_THROW(invalid_iterator::create(201, \"iterators are not compatible\",\n                                          nullptr));\n    }\n\n    // copy type from first iterator\n    m_type = first.m_object->m_type;\n\n    // check if iterator range is complete for primitive values\n    switch (m_type) {\n    case value_t::boolean:\n    case value_t::number_float:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::string: {\n      if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin()\n                               || !last.m_it.primitive_iterator.is_end())) {\n        JSON_THROW(invalid_iterator::create(204, \"iterators out of range\",\n                                            first.m_object));\n      }\n\n      break;\n    }\n\n    case value_t::null:\n    case value_t::object:\n    case value_t::array:\n    case value_t::binary:\n    case value_t::discarded:\n    default:\n      break;\n    }\n\n    switch (m_type) {\n    case value_t::number_integer: {\n      m_value.number_integer = first.m_object->m_value.number_integer;\n      break;\n    }\n\n    case value_t::number_unsigned: {\n      m_value.number_unsigned = first.m_object->m_value.number_unsigned;\n      break;\n    }\n\n    case value_t::number_float: {\n      m_value.number_float = first.m_object->m_value.number_float;\n      break;\n    }\n\n    case value_t::boolean: {\n      m_value.boolean = first.m_object->m_value.boolean;\n      break;\n    }\n\n    case value_t::string: {\n      m_value = *first.m_object->m_value.string;\n      break;\n    }\n\n    case value_t::object: {\n      m_value.object = create<object_t>(first.m_it.object_iterator,\n                                        last.m_it.object_iterator);\n      break;\n    }\n\n    case value_t::array: {\n      m_value.array = create<array_t>(first.m_it.array_iterator,\n                                      last.m_it.array_iterator);\n      break;\n    }\n\n    case value_t::binary: {\n      m_value = *first.m_object->m_value.binary;\n      break;\n    }\n\n    case value_t::null:\n    case value_t::discarded:\n    default:\n      JSON_THROW(invalid_iterator::create(206,\n                                          detail::concat(\"cannot construct with iterators from \",\n                                              first.m_object->type_name()), first.m_object));\n    }\n\n    set_parents();\n    assert_invariant();\n  }\n\n\n  ///////////////////////////////////////\n  // other constructors and destructor //\n  ///////////////////////////////////////\n\n  template<typename JsonRef,\n           detail::enable_if_t<detail::conjunction<detail::is_json_ref<JsonRef>,\n                               std::is_same<typename JsonRef::value_type, basic_json>>::value, int> = 0 >\n  basic_json(const JsonRef& ref) : basic_json(ref.moved_or_copied()) {}\n\n  /// @brief copy constructor\n  /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n  basic_json(const basic_json& other)\n    : m_type(other.m_type)\n  {\n    // check of passed value is valid\n    other.assert_invariant();\n\n    switch (m_type) {\n    case value_t::object: {\n      m_value = *other.m_value.object;\n      break;\n    }\n\n    case value_t::array: {\n      m_value = *other.m_value.array;\n      break;\n    }\n\n    case value_t::string: {\n      m_value = *other.m_value.string;\n      break;\n    }\n\n    case value_t::boolean: {\n      m_value = other.m_value.boolean;\n      break;\n    }\n\n    case value_t::number_integer: {\n      m_value = other.m_value.number_integer;\n      break;\n    }\n\n    case value_t::number_unsigned: {\n      m_value = other.m_value.number_unsigned;\n      break;\n    }\n\n    case value_t::number_float: {\n      m_value = other.m_value.number_float;\n      break;\n    }\n\n    case value_t::binary: {\n      m_value = *other.m_value.binary;\n      break;\n    }\n\n    case value_t::null:\n    case value_t::discarded:\n    default:\n      break;\n    }\n\n    set_parents();\n    assert_invariant();\n  }\n\n  /// @brief move constructor\n  /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n  basic_json(basic_json&& other) noexcept\n    : m_type(std::move(other.m_type)),\n      m_value(std::move(other.m_value))\n  {\n    // check that passed value is valid\n    other.assert_invariant(false);\n    // invalidate payload\n    other.m_type = value_t::null;\n    other.m_value = {};\n    set_parents();\n    assert_invariant();\n  }\n\n  /// @brief copy assignment\n  /// @sa https://json.nlohmann.me/api/basic_json/operator=/\n  basic_json& operator=(basic_json other) noexcept(\n    std::is_nothrow_move_constructible<value_t>::value&&\n    std::is_nothrow_move_assignable<value_t>::value&&\n    std::is_nothrow_move_constructible<json_value>::value&&\n    std::is_nothrow_move_assignable<json_value>::value\n  )\n  {\n    // check that passed value is valid\n    other.assert_invariant();\n    using std::swap;\n    swap(m_type, other.m_type);\n    swap(m_value, other.m_value);\n    set_parents();\n    assert_invariant();\n    return *this;\n  }\n\n  /// @brief destructor\n  /// @sa https://json.nlohmann.me/api/basic_json/~basic_json/\n  ~basic_json() noexcept\n  {\n    assert_invariant(false);\n    m_value.destroy(m_type);\n  }\n\n  /// @}\n\npublic:\n  ///////////////////////\n  // object inspection //\n  ///////////////////////\n\n  /// @name object inspection\n  /// Functions to inspect the type of a JSON value.\n  /// @{\n\n  /// @brief serialization\n  /// @sa https://json.nlohmann.me/api/basic_json/dump/\n  string_t dump(const int indent = -1,\n                const char indent_char = ' ',\n                const bool ensure_ascii = false,\n                const error_handler_t error_handler = error_handler_t::strict) const\n  {\n    string_t result;\n    serializer s(detail::output_adapter<char, string_t>(result), indent_char,\n                 error_handler);\n\n    if (indent >= 0) {\n      s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent));\n    } else {\n      s.dump(*this, false, ensure_ascii, 0);\n    }\n\n    return result;\n  }\n\n  /// @brief return the type of the JSON value (explicit)\n  /// @sa https://json.nlohmann.me/api/basic_json/type/\n  constexpr value_t type() const noexcept\n  {\n    return m_type;\n  }\n\n  /// @brief return whether type is primitive\n  /// @sa https://json.nlohmann.me/api/basic_json/is_primitive/\n  constexpr bool is_primitive() const noexcept\n  {\n    return is_null() || is_string() || is_boolean() || is_number() || is_binary();\n  }\n\n  /// @brief return whether type is structured\n  /// @sa https://json.nlohmann.me/api/basic_json/is_structured/\n  constexpr bool is_structured() const noexcept\n  {\n    return is_array() || is_object();\n  }\n\n  /// @brief return whether value is null\n  /// @sa https://json.nlohmann.me/api/basic_json/is_null/\n  constexpr bool is_null() const noexcept\n  {\n    return m_type == value_t::null;\n  }\n\n  /// @brief return whether value is a boolean\n  /// @sa https://json.nlohmann.me/api/basic_json/is_boolean/\n  constexpr bool is_boolean() const noexcept\n  {\n    return m_type == value_t::boolean;\n  }\n\n  /// @brief return whether value is a number\n  /// @sa https://json.nlohmann.me/api/basic_json/is_number/\n  constexpr bool is_number() const noexcept\n  {\n    return is_number_integer() || is_number_float();\n  }\n\n  /// @brief return whether value is an integer number\n  /// @sa https://json.nlohmann.me/api/basic_json/is_number_integer/\n  constexpr bool is_number_integer() const noexcept\n  {\n    return m_type == value_t::number_integer || m_type == value_t::number_unsigned;\n  }\n\n  /// @brief return whether value is an unsigned integer number\n  /// @sa https://json.nlohmann.me/api/basic_json/is_number_unsigned/\n  constexpr bool is_number_unsigned() const noexcept\n  {\n    return m_type == value_t::number_unsigned;\n  }\n\n  /// @brief return whether value is a floating-point number\n  /// @sa https://json.nlohmann.me/api/basic_json/is_number_float/\n  constexpr bool is_number_float() const noexcept\n  {\n    return m_type == value_t::number_float;\n  }\n\n  /// @brief return whether value is an object\n  /// @sa https://json.nlohmann.me/api/basic_json/is_object/\n  constexpr bool is_object() const noexcept\n  {\n    return m_type == value_t::object;\n  }\n\n  /// @brief return whether value is an array\n  /// @sa https://json.nlohmann.me/api/basic_json/is_array/\n  constexpr bool is_array() const noexcept\n  {\n    return m_type == value_t::array;\n  }\n\n  /// @brief return whether value is a string\n  /// @sa https://json.nlohmann.me/api/basic_json/is_string/\n  constexpr bool is_string() const noexcept\n  {\n    return m_type == value_t::string;\n  }\n\n  /// @brief return whether value is a binary array\n  /// @sa https://json.nlohmann.me/api/basic_json/is_binary/\n  constexpr bool is_binary() const noexcept\n  {\n    return m_type == value_t::binary;\n  }\n\n  /// @brief return whether value is discarded\n  /// @sa https://json.nlohmann.me/api/basic_json/is_discarded/\n  constexpr bool is_discarded() const noexcept\n  {\n    return m_type == value_t::discarded;\n  }\n\n  /// @brief return the type of the JSON value (implicit)\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_value_t/\n  constexpr operator value_t() const noexcept\n  {\n    return m_type;\n  }\n\n  /// @}\n\nprivate:\n  //////////////////\n  // value access //\n  //////////////////\n\n  /// get a boolean (explicit)\n  boolean_t get_impl(boolean_t* /*unused*/) const\n  {\n    if (JSON_HEDLEY_LIKELY(is_boolean())) {\n      return m_value.boolean;\n    }\n\n    JSON_THROW(type_error::create(302,\n                                  detail::concat(\"type must be boolean, but is \", type_name()), this));\n  }\n\n  /// get a pointer to the value (object)\n  object_t* get_impl_ptr(object_t* /*unused*/) noexcept\n  {\n    return is_object() ? m_value.object : nullptr;\n  }\n\n  /// get a pointer to the value (object)\n  constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const\n  noexcept\n  {\n    return is_object() ? m_value.object : nullptr;\n  }\n\n  /// get a pointer to the value (array)\n  array_t* get_impl_ptr(array_t* /*unused*/) noexcept\n  {\n    return is_array() ? m_value.array : nullptr;\n  }\n\n  /// get a pointer to the value (array)\n  constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept\n  {\n    return is_array() ? m_value.array : nullptr;\n  }\n\n  /// get a pointer to the value (string)\n  string_t* get_impl_ptr(string_t* /*unused*/) noexcept\n  {\n    return is_string() ? m_value.string : nullptr;\n  }\n\n  /// get a pointer to the value (string)\n  constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const\n  noexcept\n  {\n    return is_string() ? m_value.string : nullptr;\n  }\n\n  /// get a pointer to the value (boolean)\n  boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept\n  {\n    return is_boolean() ? &m_value.boolean : nullptr;\n  }\n\n  /// get a pointer to the value (boolean)\n  constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const\n  noexcept\n  {\n    return is_boolean() ? &m_value.boolean : nullptr;\n  }\n\n  /// get a pointer to the value (integer number)\n  number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept\n  {\n    return is_number_integer() ? &m_value.number_integer : nullptr;\n  }\n\n  /// get a pointer to the value (integer number)\n  constexpr const number_integer_t* get_impl_ptr(const\n      number_integer_t* /*unused*/) const noexcept\n  {\n    return is_number_integer() ? &m_value.number_integer : nullptr;\n  }\n\n  /// get a pointer to the value (unsigned number)\n  number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept\n  {\n    return is_number_unsigned() ? &m_value.number_unsigned : nullptr;\n  }\n\n  /// get a pointer to the value (unsigned number)\n  constexpr const number_unsigned_t* get_impl_ptr(const\n      number_unsigned_t* /*unused*/) const noexcept\n  {\n    return is_number_unsigned() ? &m_value.number_unsigned : nullptr;\n  }\n\n  /// get a pointer to the value (floating-point number)\n  number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept\n  {\n    return is_number_float() ? &m_value.number_float : nullptr;\n  }\n\n  /// get a pointer to the value (floating-point number)\n  constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/)\n  const noexcept\n  {\n    return is_number_float() ? &m_value.number_float : nullptr;\n  }\n\n  /// get a pointer to the value (binary)\n  binary_t* get_impl_ptr(binary_t* /*unused*/) noexcept\n  {\n    return is_binary() ? m_value.binary : nullptr;\n  }\n\n  /// get a pointer to the value (binary)\n  constexpr const binary_t* get_impl_ptr(const binary_t* /*unused*/) const\n  noexcept\n  {\n    return is_binary() ? m_value.binary : nullptr;\n  }\n\n  /*!\n  @brief helper function to implement get_ref()\n\n  This function helps to implement get_ref() without code duplication for\n  const and non-const overloads\n\n  @tparam ThisType will be deduced as `basic_json` or `const basic_json`\n\n  @throw type_error.303 if ReferenceType does not match underlying value\n  type of the current JSON\n  */\n  template<typename ReferenceType, typename ThisType>\n  static ReferenceType get_ref_impl(ThisType& obj)\n  {\n    // delegate the call to get_ptr<>()\n    auto* ptr = obj.template\n    get_ptr<typename std::add_pointer<ReferenceType>::type>();\n\n    if (JSON_HEDLEY_LIKELY(ptr != nullptr)) {\n      return *ptr;\n    }\n\n    JSON_THROW(type_error::create(303,\n                                  detail::concat(\"incompatible ReferenceType for get_ref, actual type is \",\n                                      obj.type_name()), &obj));\n  }\n\npublic:\n  /// @name value access\n  /// Direct access to the stored value of a JSON value.\n  /// @{\n\n  /// @brief get a pointer value (implicit)\n  /// @sa https://json.nlohmann.me/api/basic_json/get_ptr/\n  template<typename PointerType, typename std::enable_if<\n             std::is_pointer<PointerType>::value, int>::type = 0>\n  auto get_ptr() noexcept -> decltype(std::declval<basic_json_t&>().get_impl_ptr(\n                                        std::declval<PointerType>()))\n  {\n    // delegate the call to get_impl_ptr<>()\n    return get_impl_ptr(static_cast<PointerType>(nullptr));\n  }\n\n  /// @brief get a pointer value (implicit)\n  /// @sa https://json.nlohmann.me/api/basic_json/get_ptr/\n  template < typename PointerType, typename std::enable_if <\n               std::is_pointer<PointerType>::value&&\n               std::is_const<typename std::remove_pointer<PointerType>::type>::value,\n               int >::type = 0 >\n  constexpr auto get_ptr() const noexcept -> decltype(\n    std::declval<const basic_json_t&>().get_impl_ptr(std::declval<PointerType>()))\n  {\n    // delegate the call to get_impl_ptr<>() const\n    return get_impl_ptr(static_cast<PointerType>(nullptr));\n  }\n\nprivate:\n  /*!\n  @brief get a value (explicit)\n\n  Explicit type conversion between the JSON value and a compatible value\n  which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible)\n  and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible).\n  The value is converted by calling the @ref json_serializer<ValueType>\n  `from_json()` method.\n\n  The function is equivalent to executing\n  @code {.cpp}\n  ValueType ret;\n  JSONSerializer<ValueType>::from_json(*this, ret);\n  return ret;\n  @endcode\n\n  This overloads is chosen if:\n  - @a ValueType is not @ref basic_json,\n  - @ref json_serializer<ValueType> has a `from_json()` method of the form\n    `void from_json(const basic_json&, ValueType&)`, and\n  - @ref json_serializer<ValueType> does not have a `from_json()` method of\n    the form `ValueType from_json(const basic_json&)`\n\n  @tparam ValueType the returned value type\n\n  @return copy of the JSON value, converted to @a ValueType\n\n  @throw what @ref json_serializer<ValueType> `from_json()` method throws\n\n  @liveexample{The example below shows several conversions from JSON values\n  to other types. There a few things to note: (1) Floating-point numbers can\n  be converted to integers\\, (2) A JSON array can be converted to a standard\n  `std::vector<short>`\\, (3) A JSON object can be converted to C++\n  associative containers such as `std::unordered_map<std::string\\,\n  json>`.,get__ValueType_const}\n\n  @since version 2.1.0\n  */\n  template < typename ValueType,\n             detail::enable_if_t <\n               detail::is_default_constructible<ValueType>::value&&\n               detail::has_from_json<basic_json_t, ValueType>::value,\n               int > = 0 >\n  ValueType get_impl(detail::priority_tag<0> /*unused*/) const noexcept(noexcept(\n        JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(),\n            std::declval<ValueType&>())))\n  {\n    auto ret = ValueType();\n    JSONSerializer<ValueType>::from_json(*this, ret);\n    return ret;\n  }\n\n  /*!\n  @brief get a value (explicit); special case\n\n  Explicit type conversion between the JSON value and a compatible value\n  which is **not** [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible)\n  and **not** [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible).\n  The value is converted by calling the @ref json_serializer<ValueType>\n  `from_json()` method.\n\n  The function is equivalent to executing\n  @code {.cpp}\n  return JSONSerializer<ValueType>::from_json(*this);\n  @endcode\n\n  This overloads is chosen if:\n  - @a ValueType is not @ref basic_json and\n  - @ref json_serializer<ValueType> has a `from_json()` method of the form\n    `ValueType from_json(const basic_json&)`\n\n  @note If @ref json_serializer<ValueType> has both overloads of\n  `from_json()`, this one is chosen.\n\n  @tparam ValueType the returned value type\n\n  @return copy of the JSON value, converted to @a ValueType\n\n  @throw what @ref json_serializer<ValueType> `from_json()` method throws\n\n  @since version 2.1.0\n  */\n  template < typename ValueType,\n             detail::enable_if_t <\n               detail::has_non_default_from_json<basic_json_t, ValueType>::value,\n               int > = 0 >\n  ValueType get_impl(detail::priority_tag<1> /*unused*/) const noexcept(noexcept(\n        JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>())))\n  {\n    return JSONSerializer<ValueType>::from_json(*this);\n  }\n\n  /*!\n  @brief get special-case overload\n\n  This overloads converts the current @ref basic_json in a different\n  @ref basic_json type\n\n  @tparam BasicJsonType == @ref basic_json\n\n  @return a copy of *this, converted into @a BasicJsonType\n\n  @complexity Depending on the implementation of the called `from_json()`\n              method.\n\n  @since version 3.2.0\n  */\n  template < typename BasicJsonType,\n             detail::enable_if_t <\n               detail::is_basic_json<BasicJsonType>::value,\n               int > = 0 >\n  BasicJsonType get_impl(detail::priority_tag<2> /*unused*/) const\n  {\n    return *this;\n  }\n\n  /*!\n  @brief get special-case overload\n\n  This overloads avoids a lot of template boilerplate, it can be seen as the\n  identity method\n\n  @tparam BasicJsonType == @ref basic_json\n\n  @return a copy of *this\n\n  @complexity Constant.\n\n  @since version 2.1.0\n  */\n  template<typename BasicJsonType,\n           detail::enable_if_t<\n             std::is_same<BasicJsonType, basic_json_t>::value,\n             int> = 0>\n  basic_json get_impl(detail::priority_tag<3> /*unused*/) const\n  {\n    return *this;\n  }\n\n  /*!\n  @brief get a pointer value (explicit)\n  @copydoc get()\n  */\n  template<typename PointerType,\n           detail::enable_if_t<\n             std::is_pointer<PointerType>::value,\n             int> = 0>\n  constexpr auto get_impl(detail::priority_tag<4> /*unused*/) const noexcept\n  -> decltype(std::declval<const basic_json_t&>().template get_ptr<PointerType>())\n  {\n    // delegate the call to get_ptr\n    return get_ptr<PointerType>();\n  }\n\npublic:\n  /*!\n  @brief get a (pointer) value (explicit)\n\n  Performs explicit type conversion between the JSON value and a compatible value if required.\n\n  - If the requested type is a pointer to the internally stored JSON value that pointer is returned.\n  No copies are made.\n\n  - If the requested type is the current @ref basic_json, or a different @ref basic_json convertible\n  from the current @ref basic_json.\n\n  - Otherwise the value is converted by calling the @ref json_serializer<ValueType> `from_json()`\n  method.\n\n  @tparam ValueTypeCV the provided value type\n  @tparam ValueType the returned value type\n\n  @return copy of the JSON value, converted to @tparam ValueType if necessary\n\n  @throw what @ref json_serializer<ValueType> `from_json()` method throws if conversion is required\n\n  @since version 2.1.0\n  */\n  template < typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>>\n#if defined(JSON_HAS_CPP_14)\n  constexpr\n#endif\n  auto get() const noexcept(\n    noexcept(std::declval<const basic_json_t&>().template get_impl<ValueType>\n  (detail::priority_tag<4> {})))\n  -> decltype(std::declval<const basic_json_t&>().template get_impl<ValueType>\n              (detail::priority_tag<4> {}))\n  {\n    // we cannot static_assert on ValueTypeCV being non-const, because\n    // there is support for get<const basic_json_t>(), which is why we\n    // still need the uncvref\n    static_assert(!std::is_reference<ValueTypeCV>::value,\n                  \"get() cannot be used with reference types, you might want to use get_ref()\");\n    return get_impl<ValueType>(detail::priority_tag<4> {});\n  }\n\n  /*!\n  @brief get a pointer value (explicit)\n\n  Explicit pointer access to the internally stored JSON value. No copies are\n  made.\n\n  @warning The pointer becomes invalid if the underlying JSON object\n  changes.\n\n  @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref\n  object_t, @ref string_t, @ref boolean_t, @ref number_integer_t,\n  @ref number_unsigned_t, or @ref number_float_t.\n\n  @return pointer to the internally stored JSON value if the requested\n  pointer type @a PointerType fits to the JSON value; `nullptr` otherwise\n\n  @complexity Constant.\n\n  @liveexample{The example below shows how pointers to internal values of a\n  JSON value can be requested. Note that no type conversions are made and a\n  `nullptr` is returned if the value and the requested pointer type does not\n  match.,get__PointerType}\n\n  @sa see @ref get_ptr() for explicit pointer-member access\n\n  @since version 1.0.0\n  */\n  template<typename PointerType, typename std::enable_if<\n             std::is_pointer<PointerType>::value, int>::type = 0>\n  auto get() noexcept -> decltype(std::declval<basic_json_t&>().template\n                                  get_ptr<PointerType>())\n  {\n    // delegate the call to get_ptr\n    return get_ptr<PointerType>();\n  }\n\n  /// @brief get a value (explicit)\n  /// @sa https://json.nlohmann.me/api/basic_json/get_to/\n  template < typename ValueType,\n             detail::enable_if_t <\n               !detail::is_basic_json<ValueType>::value&&\n               detail::has_from_json<basic_json_t, ValueType>::value,\n               int > = 0 >\n  ValueType & get_to(ValueType& v) const noexcept(noexcept(\n        JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), v)))\n  {\n    JSONSerializer<ValueType>::from_json(*this, v);\n    return v;\n  }\n\n  // specialization to allow calling get_to with a basic_json value\n  // see https://github.com/nlohmann/json/issues/2175\n  template<typename ValueType,\n           detail::enable_if_t <\n             detail::is_basic_json<ValueType>::value,\n             int> = 0>\n  ValueType & get_to(ValueType& v) const\n  {\n    v = *this;\n    return v;\n  }\n\n  template <\n    typename T, std::size_t N,\n    typename Array = T(\n      &)[N],  // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n    detail::enable_if_t <\n      detail::has_from_json<basic_json_t, Array>::value, int > = 0 >\n  Array get_to(T(&v)[N])\n  const  // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n  noexcept(noexcept(JSONSerializer<Array>::from_json(\n                      std::declval<const basic_json_t&>(), v)))\n  {\n    JSONSerializer<Array>::from_json(*this, v);\n    return v;\n  }\n\n  /// @brief get a reference value (implicit)\n  /// @sa https://json.nlohmann.me/api/basic_json/get_ref/\n  template<typename ReferenceType, typename std::enable_if<\n             std::is_reference<ReferenceType>::value, int>::type = 0>\n  ReferenceType get_ref()\n  {\n    // delegate call to get_ref_impl\n    return get_ref_impl<ReferenceType>(*this);\n  }\n\n  /// @brief get a reference value (implicit)\n  /// @sa https://json.nlohmann.me/api/basic_json/get_ref/\n  template < typename ReferenceType, typename std::enable_if <\n               std::is_reference<ReferenceType>::value&&\n               std::is_const<typename std::remove_reference<ReferenceType>::type>::value,\n               int >::type = 0 >\n  ReferenceType get_ref() const\n  {\n    // delegate call to get_ref_impl\n    return get_ref_impl<ReferenceType>(*this);\n  }\n\n  /*!\n  @brief get a value (implicit)\n\n  Implicit type conversion between the JSON value and a compatible value.\n  The call is realized by calling @ref get() const.\n\n  @tparam ValueType non-pointer type compatible to the JSON value, for\n  instance `int` for JSON integer numbers, `bool` for JSON booleans, or\n  `std::vector` types for JSON arrays. The character type of @ref string_t\n  as well as an initializer list of this type is excluded to avoid\n  ambiguities as these types implicitly convert to `std::string`.\n\n  @return copy of the JSON value, converted to type @a ValueType\n\n  @throw type_error.302 in case passed type @a ValueType is incompatible\n  to the JSON value type (e.g., the JSON value is of type boolean, but a\n  string is requested); see example below\n\n  @complexity Linear in the size of the JSON value.\n\n  @liveexample{The example below shows several conversions from JSON values\n  to other types. There a few things to note: (1) Floating-point numbers can\n  be converted to integers\\, (2) A JSON array can be converted to a standard\n  `std::vector<short>`\\, (3) A JSON object can be converted to C++\n  associative containers such as `std::unordered_map<std::string\\,\n  json>`.,operator__ValueType}\n\n  @since version 1.0.0\n  */\n  template < typename ValueType, typename std::enable_if <\n               detail::conjunction <\n                 detail::negation<std::is_pointer<ValueType>>,\n                 detail::negation<std::is_same<ValueType, std::nullptr_t>>,\n                 detail::negation<std::is_same<ValueType, detail::json_ref<basic_json>>>,\n                                  detail::negation<std::is_same<ValueType, typename string_t::value_type>>,\n                                  detail::negation<detail::is_basic_json<ValueType>>,\n                                  detail::negation<std::is_same<ValueType, std::initializer_list<typename string_t::value_type>>>,\n#if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914))\n                                      detail::negation<std::is_same<ValueType, std::string_view>>,\n#endif\n#if defined(JSON_HAS_CPP_17)\n                                      detail::negation<std::is_same<ValueType, std::any>>,\n#endif\n                                      detail::is_detected_lazy<detail::get_template_function, const basic_json_t&, ValueType>\n                                      >::value, int >::type = 0 >\n                                  JSON_EXPLICIT operator ValueType() const\n  {\n    // delegate the call to get<>() const\n    return get<ValueType>();\n  }\n\n  /// @brief get a binary value\n  /// @sa https://json.nlohmann.me/api/basic_json/get_binary/\n  binary_t& get_binary()\n  {\n    if (!is_binary()) {\n      JSON_THROW(type_error::create(302,\n                                    detail::concat(\"type must be binary, but is \", type_name()), this));\n    }\n\n    return *get_ptr<binary_t*>();\n  }\n\n  /// @brief get a binary value\n  /// @sa https://json.nlohmann.me/api/basic_json/get_binary/\n  const binary_t& get_binary() const\n  {\n    if (!is_binary()) {\n      JSON_THROW(type_error::create(302,\n                                    detail::concat(\"type must be binary, but is \", type_name()), this));\n    }\n\n    return *get_ptr<const binary_t*>();\n  }\n\n  /// @}\n\n\n  ////////////////////\n  // element access //\n  ////////////////////\n\n  /// @name element access\n  /// Access to the JSON value.\n  /// @{\n\n  /// @brief access specified array element with bounds checking\n  /// @sa https://json.nlohmann.me/api/basic_json/at/\n  reference at(size_type idx)\n  {\n    // at only works for arrays\n    if (JSON_HEDLEY_LIKELY(is_array())) {\n      JSON_TRY {\n        return set_parent(m_value.array->at(idx));\n      }\n      JSON_CATCH(std::out_of_range&) {\n        // create better exception explanation\n        JSON_THROW(out_of_range::create(401, detail::concat(\"array index \",\n                                        std::to_string(idx), \" is out of range\"), this));\n      }\n    } else {\n      JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \",\n                                    type_name()), this));\n    }\n  }\n\n  /// @brief access specified array element with bounds checking\n  /// @sa https://json.nlohmann.me/api/basic_json/at/\n  const_reference at(size_type idx) const\n  {\n    // at only works for arrays\n    if (JSON_HEDLEY_LIKELY(is_array())) {\n      JSON_TRY {\n        return m_value.array->at(idx);\n      }\n      JSON_CATCH(std::out_of_range&) {\n        // create better exception explanation\n        JSON_THROW(out_of_range::create(401, detail::concat(\"array index \",\n                                        std::to_string(idx), \" is out of range\"), this));\n      }\n    } else {\n      JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \",\n                                    type_name()), this));\n    }\n  }\n\n  /// @brief access specified object element with bounds checking\n  /// @sa https://json.nlohmann.me/api/basic_json/at/\n  reference at(const typename object_t::key_type& key)\n  {\n    // at only works for objects\n    if (JSON_HEDLEY_UNLIKELY(!is_object())) {\n      JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \",\n                                    type_name()), this));\n    }\n\n    auto it = m_value.object->find(key);\n\n    if (it == m_value.object->end()) {\n      JSON_THROW(out_of_range::create(403, detail::concat(\"key '\", key,\n                                      \"' not found\"), this));\n    }\n\n    return set_parent(it->second);\n  }\n\n  /// @brief access specified object element with bounds checking\n  /// @sa https://json.nlohmann.me/api/basic_json/at/\n  template<class KeyType, detail::enable_if_t<\n             detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n  reference at(KeyType && key)\n  {\n    // at only works for objects\n    if (JSON_HEDLEY_UNLIKELY(!is_object())) {\n      JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \",\n                                    type_name()), this));\n    }\n\n    auto it = m_value.object->find(std::forward<KeyType>(key));\n\n    if (it == m_value.object->end()) {\n      JSON_THROW(out_of_range::create(403, detail::concat(\"key '\",\n                                      string_t(std::forward<KeyType>(key)), \"' not found\"), this));\n    }\n\n    return set_parent(it->second);\n  }\n\n  /// @brief access specified object element with bounds checking\n  /// @sa https://json.nlohmann.me/api/basic_json/at/\n  const_reference at(const typename object_t::key_type& key) const\n  {\n    // at only works for objects\n    if (JSON_HEDLEY_UNLIKELY(!is_object())) {\n      JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \",\n                                    type_name()), this));\n    }\n\n    auto it = m_value.object->find(key);\n\n    if (it == m_value.object->end()) {\n      JSON_THROW(out_of_range::create(403, detail::concat(\"key '\", key,\n                                      \"' not found\"), this));\n    }\n\n    return it->second;\n  }\n\n  /// @brief access specified object element with bounds checking\n  /// @sa https://json.nlohmann.me/api/basic_json/at/\n  template<class KeyType, detail::enable_if_t<\n             detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n  const_reference at(KeyType && key) const\n  {\n    // at only works for objects\n    if (JSON_HEDLEY_UNLIKELY(!is_object())) {\n      JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \",\n                                    type_name()), this));\n    }\n\n    auto it = m_value.object->find(std::forward<KeyType>(key));\n\n    if (it == m_value.object->end()) {\n      JSON_THROW(out_of_range::create(403, detail::concat(\"key '\",\n                                      string_t(std::forward<KeyType>(key)), \"' not found\"), this));\n    }\n\n    return it->second;\n  }\n\n  /// @brief access specified array element\n  /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n  reference operator[](size_type idx)\n  {\n    // implicitly convert null value to an empty array\n    if (is_null()) {\n      m_type = value_t::array;\n      m_value.array = create<array_t>();\n      assert_invariant();\n    }\n\n    // operator[] only works for arrays\n    if (JSON_HEDLEY_LIKELY(is_array())) {\n      // fill up array with null values if given idx is outside range\n      if (idx >= m_value.array->size()) {\n#if JSON_DIAGNOSTICS\n        // remember array size & capacity before resizing\n        const auto old_size = m_value.array->size();\n        const auto old_capacity = m_value.array->capacity();\n#endif\n        m_value.array->resize(idx + 1);\n#if JSON_DIAGNOSTICS\n\n        if (JSON_HEDLEY_UNLIKELY(m_value.array->capacity() != old_capacity)) {\n          // capacity has changed: update all parents\n          set_parents();\n        } else {\n          // set parent for values added above\n          set_parents(begin() + static_cast<typename iterator::difference_type>(old_size),\n                      static_cast<typename iterator::difference_type>(idx + 1 - old_size));\n        }\n\n#endif\n        assert_invariant();\n      }\n\n      return m_value.array->operator[](idx);\n    }\n\n    JSON_THROW(type_error::create(305,\n                                  detail::concat(\"cannot use operator[] with a numeric argument with \",\n                                      type_name()), this));\n  }\n\n  /// @brief access specified array element\n  /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n  const_reference operator[](size_type idx) const\n  {\n    // const operator[] only works for arrays\n    if (JSON_HEDLEY_LIKELY(is_array())) {\n      return m_value.array->operator[](idx);\n    }\n\n    JSON_THROW(type_error::create(305,\n                                  detail::concat(\"cannot use operator[] with a numeric argument with \",\n                                      type_name()), this));\n  }\n\n  /// @brief access specified object element\n  /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n  reference operator[](typename object_t::key_type key)\n  {\n    // implicitly convert null value to an empty object\n    if (is_null()) {\n      m_type = value_t::object;\n      m_value.object = create<object_t>();\n      assert_invariant();\n    }\n\n    // operator[] only works for objects\n    if (JSON_HEDLEY_LIKELY(is_object())) {\n      auto result = m_value.object->emplace(std::move(key), nullptr);\n      return set_parent(result.first->second);\n    }\n\n    JSON_THROW(type_error::create(305,\n                                  detail::concat(\"cannot use operator[] with a string argument with \",\n                                      type_name()), this));\n  }\n\n  /// @brief access specified object element\n  /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n  const_reference operator[](const typename object_t::key_type& key) const\n  {\n    // const operator[] only works for objects\n    if (JSON_HEDLEY_LIKELY(is_object())) {\n      auto it = m_value.object->find(key);\n      JSON_ASSERT(it != m_value.object->end());\n      return it->second;\n    }\n\n    JSON_THROW(type_error::create(305,\n                                  detail::concat(\"cannot use operator[] with a string argument with \",\n                                      type_name()), this));\n  }\n\n  // these two functions resolve a (const) char * ambiguity affecting Clang and MSVC\n  // (they seemingly cannot be constrained to resolve the ambiguity)\n  template<typename T>\n  reference operator[](T* key)\n  {\n    return operator[](typename object_t::key_type(key));\n  }\n\n  template<typename T>\n  const_reference operator[](T* key) const\n  {\n    return operator[](typename object_t::key_type(key));\n  }\n\n  /// @brief access specified object element\n  /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n  template<class KeyType, detail::enable_if_t<\n             detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int > = 0 >\n  reference operator[](KeyType && key)\n  {\n    // implicitly convert null value to an empty object\n    if (is_null()) {\n      m_type = value_t::object;\n      m_value.object = create<object_t>();\n      assert_invariant();\n    }\n\n    // operator[] only works for objects\n    if (JSON_HEDLEY_LIKELY(is_object())) {\n      auto result = m_value.object->emplace(std::forward<KeyType>(key), nullptr);\n      return set_parent(result.first->second);\n    }\n\n    JSON_THROW(type_error::create(305,\n                                  detail::concat(\"cannot use operator[] with a string argument with \",\n                                      type_name()), this));\n  }\n\n  /// @brief access specified object element\n  /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n  template<class KeyType, detail::enable_if_t<\n             detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int > = 0 >\n  const_reference operator[](KeyType && key) const\n  {\n    // const operator[] only works for objects\n    if (JSON_HEDLEY_LIKELY(is_object())) {\n      auto it = m_value.object->find(std::forward<KeyType>(key));\n      JSON_ASSERT(it != m_value.object->end());\n      return it->second;\n    }\n\n    JSON_THROW(type_error::create(305,\n                                  detail::concat(\"cannot use operator[] with a string argument with \",\n                                      type_name()), this));\n  }\n\nprivate:\n  template<typename KeyType>\n  using is_comparable_with_object_key = detail::is_comparable <\n    object_comparator_t, const typename object_t::key_type&, KeyType >;\n\n  template<typename ValueType>\n  using value_return_type = std::conditional <\n    detail::is_c_string_uncvref<ValueType>::value,\n    string_t, typename std::decay<ValueType>::type >;\n\npublic:\n  /// @brief access specified object element with default value\n  /// @sa https://json.nlohmann.me/api/basic_json/value/\n  template < class ValueType, detail::enable_if_t <\n               !detail::is_transparent<object_comparator_t>::value\n               && detail::is_getable<basic_json_t, ValueType>::value\n               && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n  ValueType value(const typename object_t::key_type& key,\n                  const ValueType& default_value) const\n  {\n    // value only works for objects\n    if (JSON_HEDLEY_LIKELY(is_object()))\n    {\n      // if key is found, return value and given default value otherwise\n      const auto it = find(key);\n\n      if (it != end()) {\n        return it->template get<ValueType>();\n      }\n\n      return default_value;\n    }\n\n    JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \",\n                                  type_name()), this));\n  }\n\n  /// @brief access specified object element with default value\n  /// @sa https://json.nlohmann.me/api/basic_json/value/\n  template < class ValueType,\n             class ReturnType = typename value_return_type<ValueType>::type,\n             detail::enable_if_t <\n               !detail::is_transparent<object_comparator_t>::value\n               && detail::is_getable<basic_json_t, ReturnType>::value\n               && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n  ReturnType value(const typename object_t::key_type& key, ValueType &&\n                   default_value) const\n  {\n    // value only works for objects\n    if (JSON_HEDLEY_LIKELY(is_object()))\n    {\n      // if key is found, return value and given default value otherwise\n      const auto it = find(key);\n\n      if (it != end()) {\n        return it->template get<ReturnType>();\n      }\n\n      return std::forward<ValueType>(default_value);\n    }\n\n    JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \",\n                                  type_name()), this));\n  }\n\n  /// @brief access specified object element with default value\n  /// @sa https://json.nlohmann.me/api/basic_json/value/\n  template < class ValueType, class KeyType, detail::enable_if_t <\n               detail::is_transparent<object_comparator_t>::value\n               && !detail::is_json_pointer<KeyType>::value\n               && is_comparable_with_object_key<KeyType>::value\n               && detail::is_getable<basic_json_t, ValueType>::value\n               && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n  ValueType value(KeyType && key, const ValueType& default_value) const\n  {\n    // value only works for objects\n    if (JSON_HEDLEY_LIKELY(is_object()))\n    {\n      // if key is found, return value and given default value otherwise\n      const auto it = find(std::forward<KeyType>(key));\n\n      if (it != end()) {\n        return it->template get<ValueType>();\n      }\n\n      return default_value;\n    }\n\n    JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \",\n                                  type_name()), this));\n  }\n\n  /// @brief access specified object element via JSON Pointer with default value\n  /// @sa https://json.nlohmann.me/api/basic_json/value/\n  template < class ValueType, class KeyType,\n             class ReturnType = typename value_return_type<ValueType>::type,\n             detail::enable_if_t <\n               detail::is_transparent<object_comparator_t>::value\n               && !detail::is_json_pointer<KeyType>::value\n               && is_comparable_with_object_key<KeyType>::value\n               && detail::is_getable<basic_json_t, ReturnType>::value\n               && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n  ReturnType value(KeyType && key, ValueType && default_value) const\n  {\n    // value only works for objects\n    if (JSON_HEDLEY_LIKELY(is_object()))\n    {\n      // if key is found, return value and given default value otherwise\n      const auto it = find(std::forward<KeyType>(key));\n\n      if (it != end()) {\n        return it->template get<ReturnType>();\n      }\n\n      return std::forward<ValueType>(default_value);\n    }\n\n    JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \",\n                                  type_name()), this));\n  }\n\n  /// @brief access specified object element via JSON Pointer with default value\n  /// @sa https://json.nlohmann.me/api/basic_json/value/\n  template < class ValueType, detail::enable_if_t <\n               detail::is_getable<basic_json_t, ValueType>::value\n               && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n  ValueType value(const json_pointer& ptr, const ValueType& default_value) const\n  {\n    // value only works for objects\n    if (JSON_HEDLEY_LIKELY(is_object()))\n    {\n      // if pointer resolves a value, return it or use default value\n      JSON_TRY {\n        return ptr.get_checked(this).template get<ValueType>();\n      }\n      JSON_INTERNAL_CATCH(out_of_range&) {\n        return default_value;\n      }\n    }\n\n    JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \",\n                                  type_name()), this));\n  }\n\n  /// @brief access specified object element via JSON Pointer with default value\n  /// @sa https://json.nlohmann.me/api/basic_json/value/\n  template < class ValueType,\n             class ReturnType = typename value_return_type<ValueType>::type,\n             detail::enable_if_t <\n               detail::is_getable<basic_json_t, ReturnType>::value\n               && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n  ReturnType value(const json_pointer& ptr, ValueType && default_value) const\n  {\n    // value only works for objects\n    if (JSON_HEDLEY_LIKELY(is_object()))\n    {\n      // if pointer resolves a value, return it or use default value\n      JSON_TRY {\n        return ptr.get_checked(this).template get<ReturnType>();\n      }\n      JSON_INTERNAL_CATCH(out_of_range&) {\n        return std::forward<ValueType>(default_value);\n      }\n    }\n\n    JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \",\n                                  type_name()), this));\n  }\n\n  template < class ValueType, class BasicJsonType, detail::enable_if_t <\n               detail::is_basic_json<BasicJsonType>::value\n               && detail::is_getable<basic_json_t, ValueType>::value\n               && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n  JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or\n                             nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n  ValueType value(const ::nlohmann::json_pointer<BasicJsonType>& ptr,\n                  const ValueType& default_value) const\n  {\n    return value(ptr.convert(), default_value);\n  }\n\n  template < class ValueType, class BasicJsonType,\n             class ReturnType = typename value_return_type<ValueType>::type,\n             detail::enable_if_t <\n               detail::is_basic_json<BasicJsonType>::value\n               && detail::is_getable<basic_json_t, ReturnType>::value\n               && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n  JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or\n                             nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n  ReturnType value(const ::nlohmann::json_pointer<BasicJsonType>& ptr,\n                   ValueType && default_value) const\n  {\n    return value(ptr.convert(), std::forward<ValueType>(default_value));\n  }\n\n  /// @brief access the first element\n  /// @sa https://json.nlohmann.me/api/basic_json/front/\n  reference front()\n  {\n    return *begin();\n  }\n\n  /// @brief access the first element\n  /// @sa https://json.nlohmann.me/api/basic_json/front/\n  const_reference front() const\n  {\n    return *cbegin();\n  }\n\n  /// @brief access the last element\n  /// @sa https://json.nlohmann.me/api/basic_json/back/\n  reference back()\n  {\n    auto tmp = end();\n    --tmp;\n    return *tmp;\n  }\n\n  /// @brief access the last element\n  /// @sa https://json.nlohmann.me/api/basic_json/back/\n  const_reference back() const\n  {\n    auto tmp = cend();\n    --tmp;\n    return *tmp;\n  }\n\n  /// @brief remove element given an iterator\n  /// @sa https://json.nlohmann.me/api/basic_json/erase/\n  template < class IteratorType, detail::enable_if_t <\n               std::is_same<IteratorType, typename basic_json_t::iterator>::value ||\n               std::is_same<IteratorType, typename basic_json_t::const_iterator>::value,\n               int > = 0 >\n  IteratorType erase(IteratorType pos)\n  {\n    // make sure iterator fits the current value\n    if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) {\n      JSON_THROW(invalid_iterator::create(202, \"iterator does not fit current value\",\n                                          this));\n    }\n\n    IteratorType result = end();\n\n    switch (m_type) {\n    case value_t::boolean:\n    case value_t::number_float:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::string:\n    case value_t::binary: {\n      if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin())) {\n        JSON_THROW(invalid_iterator::create(205, \"iterator out of range\", this));\n      }\n\n      if (is_string()) {\n        AllocatorType<string_t> alloc;\n        std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.string);\n        std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1);\n        m_value.string = nullptr;\n      } else if (is_binary()) {\n        AllocatorType<binary_t> alloc;\n        std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.binary);\n        std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.binary, 1);\n        m_value.binary = nullptr;\n      }\n\n      m_type = value_t::null;\n      assert_invariant();\n      break;\n    }\n\n    case value_t::object: {\n      result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator);\n      break;\n    }\n\n    case value_t::array: {\n      result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator);\n      break;\n    }\n\n    case value_t::null:\n    case value_t::discarded:\n    default:\n      JSON_THROW(type_error::create(307, detail::concat(\"cannot use erase() with \",\n                                    type_name()), this));\n    }\n\n    return result;\n  }\n\n  /// @brief remove elements given an iterator range\n  /// @sa https://json.nlohmann.me/api/basic_json/erase/\n  template < class IteratorType, detail::enable_if_t <\n               std::is_same<IteratorType, typename basic_json_t::iterator>::value ||\n               std::is_same<IteratorType, typename basic_json_t::const_iterator>::value,\n               int > = 0 >\n  IteratorType erase(IteratorType first, IteratorType last)\n  {\n    // make sure iterator fits the current value\n    if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) {\n      JSON_THROW(invalid_iterator::create(203, \"iterators do not fit current value\",\n                                          this));\n    }\n\n    IteratorType result = end();\n\n    switch (m_type) {\n    case value_t::boolean:\n    case value_t::number_float:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::string:\n    case value_t::binary: {\n      if (JSON_HEDLEY_LIKELY(!first.m_it.primitive_iterator.is_begin()\n                             || !last.m_it.primitive_iterator.is_end())) {\n        JSON_THROW(invalid_iterator::create(204, \"iterators out of range\", this));\n      }\n\n      if (is_string()) {\n        AllocatorType<string_t> alloc;\n        std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.string);\n        std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1);\n        m_value.string = nullptr;\n      } else if (is_binary()) {\n        AllocatorType<binary_t> alloc;\n        std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.binary);\n        std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.binary, 1);\n        m_value.binary = nullptr;\n      }\n\n      m_type = value_t::null;\n      assert_invariant();\n      break;\n    }\n\n    case value_t::object: {\n      result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator,\n                                    last.m_it.object_iterator);\n      break;\n    }\n\n    case value_t::array: {\n      result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator,\n                                   last.m_it.array_iterator);\n      break;\n    }\n\n    case value_t::null:\n    case value_t::discarded:\n    default:\n      JSON_THROW(type_error::create(307, detail::concat(\"cannot use erase() with \",\n                                    type_name()), this));\n    }\n\n    return result;\n  }\n\nprivate:\n  template < typename KeyType, detail::enable_if_t <\n               detail::has_erase_with_key_type<basic_json_t, KeyType>::value, int > = 0 >\n  size_type erase_internal(KeyType && key)\n  {\n    // this erase only works for objects\n    if (JSON_HEDLEY_UNLIKELY(!is_object())) {\n      JSON_THROW(type_error::create(307, detail::concat(\"cannot use erase() with \",\n                                    type_name()), this));\n    }\n\n    return m_value.object->erase(std::forward<KeyType>(key));\n  }\n\n  template < typename KeyType, detail::enable_if_t <\n               !detail::has_erase_with_key_type<basic_json_t, KeyType>::value, int > = 0 >\n  size_type erase_internal(KeyType && key)\n  {\n    // this erase only works for objects\n    if (JSON_HEDLEY_UNLIKELY(!is_object())) {\n      JSON_THROW(type_error::create(307, detail::concat(\"cannot use erase() with \",\n                                    type_name()), this));\n    }\n\n    const auto it = m_value.object->find(std::forward<KeyType>(key));\n\n    if (it != m_value.object->end()) {\n      m_value.object->erase(it);\n      return 1;\n    }\n\n    return 0;\n  }\n\npublic:\n\n  /// @brief remove element from a JSON object given a key\n  /// @sa https://json.nlohmann.me/api/basic_json/erase/\n  size_type erase(const typename object_t::key_type& key)\n  {\n    // the indirection via erase_internal() is added to avoid making this\n    // function a template and thus de-rank it during overload resolution\n    return erase_internal(key);\n  }\n\n  /// @brief remove element from a JSON object given a key\n  /// @sa https://json.nlohmann.me/api/basic_json/erase/\n  template<class KeyType, detail::enable_if_t<\n             detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n  size_type erase(KeyType && key)\n  {\n    return erase_internal(std::forward<KeyType>(key));\n  }\n\n  /// @brief remove element from a JSON array given an index\n  /// @sa https://json.nlohmann.me/api/basic_json/erase/\n  void erase(const size_type idx)\n  {\n    // this erase only works for arrays\n    if (JSON_HEDLEY_LIKELY(is_array())) {\n      if (JSON_HEDLEY_UNLIKELY(idx >= size())) {\n        JSON_THROW(out_of_range::create(401, detail::concat(\"array index \",\n                                        std::to_string(idx), \" is out of range\"), this));\n      }\n\n      m_value.array->erase(m_value.array->begin() + static_cast<difference_type>\n                           (idx));\n    } else {\n      JSON_THROW(type_error::create(307, detail::concat(\"cannot use erase() with \",\n                                    type_name()), this));\n    }\n  }\n\n  /// @}\n\n\n  ////////////\n  // lookup //\n  ////////////\n\n  /// @name lookup\n  /// @{\n\n  /// @brief find an element in a JSON object\n  /// @sa https://json.nlohmann.me/api/basic_json/find/\n  iterator find(const typename object_t::key_type& key)\n  {\n    auto result = end();\n\n    if (is_object()) {\n      result.m_it.object_iterator = m_value.object->find(key);\n    }\n\n    return result;\n  }\n\n  /// @brief find an element in a JSON object\n  /// @sa https://json.nlohmann.me/api/basic_json/find/\n  const_iterator find(const typename object_t::key_type& key) const\n  {\n    auto result = cend();\n\n    if (is_object()) {\n      result.m_it.object_iterator = m_value.object->find(key);\n    }\n\n    return result;\n  }\n\n  /// @brief find an element in a JSON object\n  /// @sa https://json.nlohmann.me/api/basic_json/find/\n  template<class KeyType, detail::enable_if_t<\n             detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n  iterator find(KeyType && key)\n  {\n    auto result = end();\n\n    if (is_object()) {\n      result.m_it.object_iterator = m_value.object->find(std::forward<KeyType>(key));\n    }\n\n    return result;\n  }\n\n  /// @brief find an element in a JSON object\n  /// @sa https://json.nlohmann.me/api/basic_json/find/\n  template<class KeyType, detail::enable_if_t<\n             detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n  const_iterator find(KeyType && key) const\n  {\n    auto result = cend();\n\n    if (is_object()) {\n      result.m_it.object_iterator = m_value.object->find(std::forward<KeyType>(key));\n    }\n\n    return result;\n  }\n\n  /// @brief returns the number of occurrences of a key in a JSON object\n  /// @sa https://json.nlohmann.me/api/basic_json/count/\n  size_type count(const typename object_t::key_type& key) const\n  {\n    // return 0 for all nonobject types\n    return is_object() ? m_value.object->count(key) : 0;\n  }\n\n  /// @brief returns the number of occurrences of a key in a JSON object\n  /// @sa https://json.nlohmann.me/api/basic_json/count/\n  template<class KeyType, detail::enable_if_t<\n             detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n  size_type count(KeyType && key) const\n  {\n    // return 0 for all nonobject types\n    return is_object() ? m_value.object->count(std::forward<KeyType>(key)) : 0;\n  }\n\n  /// @brief check the existence of an element in a JSON object\n  /// @sa https://json.nlohmann.me/api/basic_json/contains/\n  bool contains(const typename object_t::key_type& key) const\n  {\n    return is_object() && m_value.object->find(key) != m_value.object->end();\n  }\n\n  /// @brief check the existence of an element in a JSON object\n  /// @sa https://json.nlohmann.me/api/basic_json/contains/\n  template<class KeyType, detail::enable_if_t<\n             detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n  bool contains(KeyType && key) const\n  {\n    return is_object() &&\n    m_value.object->find(std::forward<KeyType>(key)) != m_value.object->end();\n  }\n\n  /// @brief check the existence of an element in a JSON object given a JSON pointer\n  /// @sa https://json.nlohmann.me/api/basic_json/contains/\n  bool contains(const json_pointer& ptr) const\n  {\n    return ptr.contains(this);\n  }\n\n  template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>\n  JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or\n                             nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n  bool contains(const typename ::nlohmann::json_pointer<BasicJsonType>& ptr) const\n  {\n    return ptr.contains(this);\n  }\n\n  /// @}\n\n\n  ///////////////\n  // iterators //\n  ///////////////\n\n  /// @name iterators\n  /// @{\n\n  /// @brief returns an iterator to the first element\n  /// @sa https://json.nlohmann.me/api/basic_json/begin/\n  iterator begin() noexcept\n  {\n    iterator result(this);\n    result.set_begin();\n    return result;\n  }\n\n  /// @brief returns an iterator to the first element\n  /// @sa https://json.nlohmann.me/api/basic_json/begin/\n  const_iterator begin() const noexcept\n  {\n    return cbegin();\n  }\n\n  /// @brief returns a const iterator to the first element\n  /// @sa https://json.nlohmann.me/api/basic_json/cbegin/\n  const_iterator cbegin() const noexcept\n  {\n    const_iterator result(this);\n    result.set_begin();\n    return result;\n  }\n\n  /// @brief returns an iterator to one past the last element\n  /// @sa https://json.nlohmann.me/api/basic_json/end/\n  iterator end() noexcept\n  {\n    iterator result(this);\n    result.set_end();\n    return result;\n  }\n\n  /// @brief returns an iterator to one past the last element\n  /// @sa https://json.nlohmann.me/api/basic_json/end/\n  const_iterator end() const noexcept\n  {\n    return cend();\n  }\n\n  /// @brief returns an iterator to one past the last element\n  /// @sa https://json.nlohmann.me/api/basic_json/cend/\n  const_iterator cend() const noexcept\n  {\n    const_iterator result(this);\n    result.set_end();\n    return result;\n  }\n\n  /// @brief returns an iterator to the reverse-beginning\n  /// @sa https://json.nlohmann.me/api/basic_json/rbegin/\n  reverse_iterator rbegin() noexcept\n  {\n    return reverse_iterator(end());\n  }\n\n  /// @brief returns an iterator to the reverse-beginning\n  /// @sa https://json.nlohmann.me/api/basic_json/rbegin/\n  const_reverse_iterator rbegin() const noexcept\n  {\n    return crbegin();\n  }\n\n  /// @brief returns an iterator to the reverse-end\n  /// @sa https://json.nlohmann.me/api/basic_json/rend/\n  reverse_iterator rend() noexcept\n  {\n    return reverse_iterator(begin());\n  }\n\n  /// @brief returns an iterator to the reverse-end\n  /// @sa https://json.nlohmann.me/api/basic_json/rend/\n  const_reverse_iterator rend() const noexcept\n  {\n    return crend();\n  }\n\n  /// @brief returns a const reverse iterator to the last element\n  /// @sa https://json.nlohmann.me/api/basic_json/crbegin/\n  const_reverse_iterator crbegin() const noexcept\n  {\n    return const_reverse_iterator(cend());\n  }\n\n  /// @brief returns a const reverse iterator to one before the first\n  /// @sa https://json.nlohmann.me/api/basic_json/crend/\n  const_reverse_iterator crend() const noexcept\n  {\n    return const_reverse_iterator(cbegin());\n  }\n\npublic:\n  /// @brief wrapper to access iterator member functions in range-based for\n  /// @sa https://json.nlohmann.me/api/basic_json/items/\n  /// @deprecated This function is deprecated since 3.1.0 and will be removed in\n  ///             version 4.0.0 of the library. Please use @ref items() instead;\n  ///             that is, replace `json::iterator_wrapper(j)` with `j.items()`.\n  JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items())\n  static iteration_proxy<iterator> iterator_wrapper(reference ref) noexcept\n  {\n    return ref.items();\n  }\n\n  /// @brief wrapper to access iterator member functions in range-based for\n  /// @sa https://json.nlohmann.me/api/basic_json/items/\n  /// @deprecated This function is deprecated since 3.1.0 and will be removed in\n  ///         version 4.0.0 of the library. Please use @ref items() instead;\n  ///         that is, replace `json::iterator_wrapper(j)` with `j.items()`.\n  JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items())\n  static iteration_proxy<const_iterator> iterator_wrapper(\n    const_reference ref) noexcept\n  {\n    return ref.items();\n  }\n\n  /// @brief helper to access iterator member functions in range-based for\n  /// @sa https://json.nlohmann.me/api/basic_json/items/\n  iteration_proxy<iterator> items() noexcept\n  {\n    return iteration_proxy<iterator>(*this);\n  }\n\n  /// @brief helper to access iterator member functions in range-based for\n  /// @sa https://json.nlohmann.me/api/basic_json/items/\n  iteration_proxy<const_iterator> items() const noexcept\n  {\n    return iteration_proxy<const_iterator>(*this);\n  }\n\n  /// @}\n\n\n  //////////////\n  // capacity //\n  //////////////\n\n  /// @name capacity\n  /// @{\n\n  /// @brief checks whether the container is empty.\n  /// @sa https://json.nlohmann.me/api/basic_json/empty/\n  bool empty() const noexcept\n  {\n    switch (m_type) {\n    case value_t::null: {\n      // null values are empty\n      return true;\n    }\n\n    case value_t::array: {\n      // delegate call to array_t::empty()\n      return m_value.array->empty();\n    }\n\n    case value_t::object: {\n      // delegate call to object_t::empty()\n      return m_value.object->empty();\n    }\n\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default: {\n      // all other types are nonempty\n      return false;\n    }\n    }\n  }\n\n  /// @brief returns the number of elements\n  /// @sa https://json.nlohmann.me/api/basic_json/size/\n  size_type size() const noexcept\n  {\n    switch (m_type) {\n    case value_t::null: {\n      // null values are empty\n      return 0;\n    }\n\n    case value_t::array: {\n      // delegate call to array_t::size()\n      return m_value.array->size();\n    }\n\n    case value_t::object: {\n      // delegate call to object_t::size()\n      return m_value.object->size();\n    }\n\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default: {\n      // all other types have size 1\n      return 1;\n    }\n    }\n  }\n\n  /// @brief returns the maximum possible number of elements\n  /// @sa https://json.nlohmann.me/api/basic_json/max_size/\n  size_type max_size() const noexcept\n  {\n    switch (m_type) {\n    case value_t::array: {\n      // delegate call to array_t::max_size()\n      return m_value.array->max_size();\n    }\n\n    case value_t::object: {\n      // delegate call to object_t::max_size()\n      return m_value.object->max_size();\n    }\n\n    case value_t::null:\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default: {\n      // all other types have max_size() == size()\n      return size();\n    }\n    }\n  }\n\n  /// @}\n\n\n  ///////////////\n  // modifiers //\n  ///////////////\n\n  /// @name modifiers\n  /// @{\n\n  /// @brief clears the contents\n  /// @sa https://json.nlohmann.me/api/basic_json/clear/\n  void clear() noexcept\n  {\n    switch (m_type) {\n    case value_t::number_integer: {\n      m_value.number_integer = 0;\n      break;\n    }\n\n    case value_t::number_unsigned: {\n      m_value.number_unsigned = 0;\n      break;\n    }\n\n    case value_t::number_float: {\n      m_value.number_float = 0.0;\n      break;\n    }\n\n    case value_t::boolean: {\n      m_value.boolean = false;\n      break;\n    }\n\n    case value_t::string: {\n      m_value.string->clear();\n      break;\n    }\n\n    case value_t::binary: {\n      m_value.binary->clear();\n      break;\n    }\n\n    case value_t::array: {\n      m_value.array->clear();\n      break;\n    }\n\n    case value_t::object: {\n      m_value.object->clear();\n      break;\n    }\n\n    case value_t::null:\n    case value_t::discarded:\n    default:\n      break;\n    }\n  }\n\n  /// @brief add an object to an array\n  /// @sa https://json.nlohmann.me/api/basic_json/push_back/\n  void push_back(basic_json&& val)\n  {\n    // push_back only works for null objects or arrays\n    if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) {\n      JSON_THROW(type_error::create(308,\n                                    detail::concat(\"cannot use push_back() with \", type_name()), this));\n    }\n\n    // transform null object into an array\n    if (is_null()) {\n      m_type = value_t::array;\n      m_value = value_t::array;\n      assert_invariant();\n    }\n\n    // add element to array (move semantics)\n    const auto old_capacity = m_value.array->capacity();\n    m_value.array->push_back(std::move(val));\n    set_parent(m_value.array->back(), old_capacity);\n    // if val is moved from, basic_json move constructor marks it null, so we do not call the destructor\n  }\n\n  /// @brief add an object to an array\n  /// @sa https://json.nlohmann.me/api/basic_json/operator+=/\n  reference operator+=(basic_json&& val)\n  {\n    push_back(std::move(val));\n    return *this;\n  }\n\n  /// @brief add an object to an array\n  /// @sa https://json.nlohmann.me/api/basic_json/push_back/\n  void push_back(const basic_json& val)\n  {\n    // push_back only works for null objects or arrays\n    if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) {\n      JSON_THROW(type_error::create(308,\n                                    detail::concat(\"cannot use push_back() with \", type_name()), this));\n    }\n\n    // transform null object into an array\n    if (is_null()) {\n      m_type = value_t::array;\n      m_value = value_t::array;\n      assert_invariant();\n    }\n\n    // add element to array\n    const auto old_capacity = m_value.array->capacity();\n    m_value.array->push_back(val);\n    set_parent(m_value.array->back(), old_capacity);\n  }\n\n  /// @brief add an object to an array\n  /// @sa https://json.nlohmann.me/api/basic_json/operator+=/\n  reference operator+=(const basic_json& val)\n  {\n    push_back(val);\n    return *this;\n  }\n\n  /// @brief add an object to an object\n  /// @sa https://json.nlohmann.me/api/basic_json/push_back/\n  void push_back(const typename object_t::value_type& val)\n  {\n    // push_back only works for null objects or objects\n    if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) {\n      JSON_THROW(type_error::create(308,\n                                    detail::concat(\"cannot use push_back() with \", type_name()), this));\n    }\n\n    // transform null object into an object\n    if (is_null()) {\n      m_type = value_t::object;\n      m_value = value_t::object;\n      assert_invariant();\n    }\n\n    // add element to object\n    auto res = m_value.object->insert(val);\n    set_parent(res.first->second);\n  }\n\n  /// @brief add an object to an object\n  /// @sa https://json.nlohmann.me/api/basic_json/operator+=/\n  reference operator+=(const typename object_t::value_type& val)\n  {\n    push_back(val);\n    return *this;\n  }\n\n  /// @brief add an object to an object\n  /// @sa https://json.nlohmann.me/api/basic_json/push_back/\n  void push_back(initializer_list_t init)\n  {\n    if (is_object() && init.size() == 2 && (*init.begin())->is_string()) {\n      basic_json&& key = init.begin()->moved_or_copied();\n      push_back(typename object_t::value_type(\n                  std::move(key.get_ref<string_t&>()), (init.begin() + 1)->moved_or_copied()));\n    } else {\n      push_back(basic_json(init));\n    }\n  }\n\n  /// @brief add an object to an object\n  /// @sa https://json.nlohmann.me/api/basic_json/operator+=/\n  reference operator+=(initializer_list_t init)\n  {\n    push_back(init);\n    return *this;\n  }\n\n  /// @brief add an object to an array\n  /// @sa https://json.nlohmann.me/api/basic_json/emplace_back/\n  template<class... Args>\n  reference emplace_back(Args&& ... args)\n  {\n    // emplace_back only works for null objects or arrays\n    if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) {\n      JSON_THROW(type_error::create(311,\n                                    detail::concat(\"cannot use emplace_back() with \", type_name()), this));\n    }\n\n    // transform null object into an array\n    if (is_null()) {\n      m_type = value_t::array;\n      m_value = value_t::array;\n      assert_invariant();\n    }\n\n    // add element to array (perfect forwarding)\n    const auto old_capacity = m_value.array->capacity();\n    m_value.array->emplace_back(std::forward<Args>(args)...);\n    return set_parent(m_value.array->back(), old_capacity);\n  }\n\n  /// @brief add an object to an object if key does not exist\n  /// @sa https://json.nlohmann.me/api/basic_json/emplace/\n  template<class... Args>\n  std::pair<iterator, bool> emplace(Args&& ... args)\n  {\n    // emplace only works for null objects or arrays\n    if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) {\n      JSON_THROW(type_error::create(311, detail::concat(\"cannot use emplace() with \",\n                                    type_name()), this));\n    }\n\n    // transform null object into an object\n    if (is_null()) {\n      m_type = value_t::object;\n      m_value = value_t::object;\n      assert_invariant();\n    }\n\n    // add element to array (perfect forwarding)\n    auto res = m_value.object->emplace(std::forward<Args>(args)...);\n    set_parent(res.first->second);\n    // create result iterator and set iterator to the result of emplace\n    auto it = begin();\n    it.m_it.object_iterator = res.first;\n    // return pair of iterator and boolean\n    return {it, res.second};\n  }\n\n  /// Helper for insertion of an iterator\n  /// @note: This uses std::distance to support GCC 4.8,\n  ///        see https://github.com/nlohmann/json/pull/1257\n  template<typename... Args>\n  iterator insert_iterator(const_iterator pos, Args&& ... args)\n  {\n    iterator result(this);\n    JSON_ASSERT(m_value.array != nullptr);\n    auto insert_pos = std::distance(m_value.array->begin(),\n                                    pos.m_it.array_iterator);\n    m_value.array->insert(pos.m_it.array_iterator, std::forward<Args>(args)...);\n    result.m_it.array_iterator = m_value.array->begin() + insert_pos;\n    // This could have been written as:\n    // result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val);\n    // but the return value of insert is missing in GCC 4.8, so it is written this way instead.\n    set_parents();\n    return result;\n  }\n\n  /// @brief inserts element into array\n  /// @sa https://json.nlohmann.me/api/basic_json/insert/\n  iterator insert(const_iterator pos, const basic_json& val)\n  {\n    // insert only works for arrays\n    if (JSON_HEDLEY_LIKELY(is_array())) {\n      // check if iterator pos fits to this JSON value\n      if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) {\n        JSON_THROW(invalid_iterator::create(202, \"iterator does not fit current value\",\n                                            this));\n      }\n\n      // insert to array and return iterator\n      return insert_iterator(pos, val);\n    }\n\n    JSON_THROW(type_error::create(309, detail::concat(\"cannot use insert() with \",\n                                  type_name()), this));\n  }\n\n  /// @brief inserts element into array\n  /// @sa https://json.nlohmann.me/api/basic_json/insert/\n  iterator insert(const_iterator pos, basic_json&& val)\n  {\n    return insert(pos, val);\n  }\n\n  /// @brief inserts copies of element into array\n  /// @sa https://json.nlohmann.me/api/basic_json/insert/\n  iterator insert(const_iterator pos, size_type cnt, const basic_json& val)\n  {\n    // insert only works for arrays\n    if (JSON_HEDLEY_LIKELY(is_array())) {\n      // check if iterator pos fits to this JSON value\n      if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) {\n        JSON_THROW(invalid_iterator::create(202, \"iterator does not fit current value\",\n                                            this));\n      }\n\n      // insert to array and return iterator\n      return insert_iterator(pos, cnt, val);\n    }\n\n    JSON_THROW(type_error::create(309, detail::concat(\"cannot use insert() with \",\n                                  type_name()), this));\n  }\n\n  /// @brief inserts range of elements into array\n  /// @sa https://json.nlohmann.me/api/basic_json/insert/\n  iterator insert(const_iterator pos, const_iterator first, const_iterator last)\n  {\n    // insert only works for arrays\n    if (JSON_HEDLEY_UNLIKELY(!is_array())) {\n      JSON_THROW(type_error::create(309, detail::concat(\"cannot use insert() with \",\n                                    type_name()), this));\n    }\n\n    // check if iterator pos fits to this JSON value\n    if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) {\n      JSON_THROW(invalid_iterator::create(202, \"iterator does not fit current value\",\n                                          this));\n    }\n\n    // check if range iterators belong to the same JSON object\n    if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) {\n      JSON_THROW(invalid_iterator::create(210, \"iterators do not fit\", this));\n    }\n\n    if (JSON_HEDLEY_UNLIKELY(first.m_object == this)) {\n      JSON_THROW(invalid_iterator::create(211,\n                                          \"passed iterators may not belong to container\", this));\n    }\n\n    // insert to array and return iterator\n    return insert_iterator(pos, first.m_it.array_iterator,\n                           last.m_it.array_iterator);\n  }\n\n  /// @brief inserts elements from initializer list into array\n  /// @sa https://json.nlohmann.me/api/basic_json/insert/\n  iterator insert(const_iterator pos, initializer_list_t ilist)\n  {\n    // insert only works for arrays\n    if (JSON_HEDLEY_UNLIKELY(!is_array())) {\n      JSON_THROW(type_error::create(309, detail::concat(\"cannot use insert() with \",\n                                    type_name()), this));\n    }\n\n    // check if iterator pos fits to this JSON value\n    if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) {\n      JSON_THROW(invalid_iterator::create(202, \"iterator does not fit current value\",\n                                          this));\n    }\n\n    // insert to array and return iterator\n    return insert_iterator(pos, ilist.begin(), ilist.end());\n  }\n\n  /// @brief inserts range of elements into object\n  /// @sa https://json.nlohmann.me/api/basic_json/insert/\n  void insert(const_iterator first, const_iterator last)\n  {\n    // insert only works for objects\n    if (JSON_HEDLEY_UNLIKELY(!is_object())) {\n      JSON_THROW(type_error::create(309, detail::concat(\"cannot use insert() with \",\n                                    type_name()), this));\n    }\n\n    // check if range iterators belong to the same JSON object\n    if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) {\n      JSON_THROW(invalid_iterator::create(210, \"iterators do not fit\", this));\n    }\n\n    // passed iterators must belong to objects\n    if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) {\n      JSON_THROW(invalid_iterator::create(202,\n                                          \"iterators first and last must point to objects\", this));\n    }\n\n    m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator);\n  }\n\n  /// @brief updates a JSON object from another object, overwriting existing keys\n  /// @sa https://json.nlohmann.me/api/basic_json/update/\n  void update(const_reference j, bool merge_objects = false)\n  {\n    update(j.begin(), j.end(), merge_objects);\n  }\n\n  /// @brief updates a JSON object from another object, overwriting existing keys\n  /// @sa https://json.nlohmann.me/api/basic_json/update/\n  void update(const_iterator first, const_iterator last,\n              bool merge_objects = false)\n  {\n    // implicitly convert null value to an empty object\n    if (is_null()) {\n      m_type = value_t::object;\n      m_value.object = create<object_t>();\n      assert_invariant();\n    }\n\n    if (JSON_HEDLEY_UNLIKELY(!is_object())) {\n      JSON_THROW(type_error::create(312, detail::concat(\"cannot use update() with \",\n                                    type_name()), this));\n    }\n\n    // check if range iterators belong to the same JSON object\n    if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) {\n      JSON_THROW(invalid_iterator::create(210, \"iterators do not fit\", this));\n    }\n\n    // passed iterators must belong to objects\n    if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) {\n      JSON_THROW(type_error::create(312, detail::concat(\"cannot use update() with \",\n                                    first.m_object->type_name()), first.m_object));\n    }\n\n    for (auto it = first; it != last; ++it) {\n      if (merge_objects && it.value().is_object()) {\n        auto it2 = m_value.object->find(it.key());\n\n        if (it2 != m_value.object->end()) {\n          it2->second.update(it.value(), true);\n          continue;\n        }\n      }\n\n      m_value.object->operator[](it.key()) = it.value();\n#if JSON_DIAGNOSTICS\n      m_value.object->operator[](it.key()).m_parent = this;\n#endif\n    }\n  }\n\n  /// @brief exchanges the values\n  /// @sa https://json.nlohmann.me/api/basic_json/swap/\n  void swap(reference other) noexcept(\n    std::is_nothrow_move_constructible<value_t>::value&&\n    std::is_nothrow_move_assignable<value_t>::value&&\n    std::is_nothrow_move_constructible<json_value>::value&&\n    std::is_nothrow_move_assignable<json_value>::value\n  )\n  {\n    std::swap(m_type, other.m_type);\n    std::swap(m_value, other.m_value);\n    set_parents();\n    other.set_parents();\n    assert_invariant();\n  }\n\n  /// @brief exchanges the values\n  /// @sa https://json.nlohmann.me/api/basic_json/swap/\n  friend void swap(reference left, reference right) noexcept(\n    std::is_nothrow_move_constructible<value_t>::value&&\n    std::is_nothrow_move_assignable<value_t>::value&&\n    std::is_nothrow_move_constructible<json_value>::value&&\n    std::is_nothrow_move_assignable<json_value>::value\n  )\n  {\n    left.swap(right);\n  }\n\n  /// @brief exchanges the values\n  /// @sa https://json.nlohmann.me/api/basic_json/swap/\n  void swap(array_t& other) // NOLINT(bugprone-exception-escape)\n  {\n    // swap only works for arrays\n    if (JSON_HEDLEY_LIKELY(is_array())) {\n      using std::swap;\n      swap(*(m_value.array), other);\n    } else {\n      JSON_THROW(type_error::create(310,\n                                    detail::concat(\"cannot use swap(array_t&) with \", type_name()), this));\n    }\n  }\n\n  /// @brief exchanges the values\n  /// @sa https://json.nlohmann.me/api/basic_json/swap/\n  void swap(object_t& other) // NOLINT(bugprone-exception-escape)\n  {\n    // swap only works for objects\n    if (JSON_HEDLEY_LIKELY(is_object())) {\n      using std::swap;\n      swap(*(m_value.object), other);\n    } else {\n      JSON_THROW(type_error::create(310,\n                                    detail::concat(\"cannot use swap(object_t&) with \", type_name()), this));\n    }\n  }\n\n  /// @brief exchanges the values\n  /// @sa https://json.nlohmann.me/api/basic_json/swap/\n  void swap(string_t& other) // NOLINT(bugprone-exception-escape)\n  {\n    // swap only works for strings\n    if (JSON_HEDLEY_LIKELY(is_string())) {\n      using std::swap;\n      swap(*(m_value.string), other);\n    } else {\n      JSON_THROW(type_error::create(310,\n                                    detail::concat(\"cannot use swap(string_t&) with \", type_name()), this));\n    }\n  }\n\n  /// @brief exchanges the values\n  /// @sa https://json.nlohmann.me/api/basic_json/swap/\n  void swap(binary_t& other) // NOLINT(bugprone-exception-escape)\n  {\n    // swap only works for strings\n    if (JSON_HEDLEY_LIKELY(is_binary())) {\n      using std::swap;\n      swap(*(m_value.binary), other);\n    } else {\n      JSON_THROW(type_error::create(310,\n                                    detail::concat(\"cannot use swap(binary_t&) with \", type_name()), this));\n    }\n  }\n\n  /// @brief exchanges the values\n  /// @sa https://json.nlohmann.me/api/basic_json/swap/\n  void swap(typename binary_t::container_type&\n            other) // NOLINT(bugprone-exception-escape)\n  {\n    // swap only works for strings\n    if (JSON_HEDLEY_LIKELY(is_binary())) {\n      using std::swap;\n      swap(*(m_value.binary), other);\n    } else {\n      JSON_THROW(type_error::create(310,\n                                    detail::concat(\"cannot use swap(binary_t::container_type&) with \", type_name()),\n                                    this));\n    }\n  }\n\n  /// @}\n\n  //////////////////////////////////////////\n  // lexicographical comparison operators //\n  //////////////////////////////////////////\n\n  /// @name lexicographical comparison operators\n  /// @{\n\n  // note parentheses around operands are necessary; see\n  // https://github.com/nlohmann/json/issues/1530\n#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result)                       \\\n    const auto lhs_type = lhs.type();                                                                    \\\n    const auto rhs_type = rhs.type();                                                                    \\\n    \\\n    if (lhs_type == rhs_type) /* NOLINT(readability/braces) */                                           \\\n    {                                                                                                    \\\n        switch (lhs_type)                                                                                \\\n        {                                                                                                \\\n            case value_t::array:                                                                         \\\n                return (*lhs.m_value.array) op (*rhs.m_value.array);                                     \\\n                \\\n            case value_t::object:                                                                        \\\n                return (*lhs.m_value.object) op (*rhs.m_value.object);                                   \\\n                \\\n            case value_t::null:                                                                          \\\n                return (null_result);                                                                    \\\n                \\\n            case value_t::string:                                                                        \\\n                return (*lhs.m_value.string) op (*rhs.m_value.string);                                   \\\n                \\\n            case value_t::boolean:                                                                       \\\n                return (lhs.m_value.boolean) op (rhs.m_value.boolean);                                   \\\n                \\\n            case value_t::number_integer:                                                                \\\n                return (lhs.m_value.number_integer) op (rhs.m_value.number_integer);                     \\\n                \\\n            case value_t::number_unsigned:                                                               \\\n                return (lhs.m_value.number_unsigned) op (rhs.m_value.number_unsigned);                   \\\n                \\\n            case value_t::number_float:                                                                  \\\n                return (lhs.m_value.number_float) op (rhs.m_value.number_float);                         \\\n                \\\n            case value_t::binary:                                                                        \\\n                return (*lhs.m_value.binary) op (*rhs.m_value.binary);                                   \\\n                \\\n            case value_t::discarded:                                                                     \\\n            default:                                                                                     \\\n                return (unordered_result);                                                               \\\n        }                                                                                                \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float)                   \\\n    {                                                                                                    \\\n        return static_cast<number_float_t>(lhs.m_value.number_integer) op rhs.m_value.number_float;      \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer)                   \\\n    {                                                                                                    \\\n        return lhs.m_value.number_float op static_cast<number_float_t>(rhs.m_value.number_integer);      \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float)                  \\\n    {                                                                                                    \\\n        return static_cast<number_float_t>(lhs.m_value.number_unsigned) op rhs.m_value.number_float;     \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned)                  \\\n    {                                                                                                    \\\n        return lhs.m_value.number_float op static_cast<number_float_t>(rhs.m_value.number_unsigned);     \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer)                \\\n    {                                                                                                    \\\n        return static_cast<number_integer_t>(lhs.m_value.number_unsigned) op rhs.m_value.number_integer; \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned)                \\\n    {                                                                                                    \\\n        return lhs.m_value.number_integer op static_cast<number_integer_t>(rhs.m_value.number_unsigned); \\\n    }                                                                                                    \\\n    else if(compares_unordered(lhs, rhs))\\\n    {\\\n        return (unordered_result);\\\n    }\\\n    \\\n    return (default_result);\n\nJSON_PRIVATE_UNLESS_TESTED:\n  // returns true if:\n  // - any operand is NaN and the other operand is of number type\n  // - any operand is discarded\n  // in legacy mode, discarded values are considered ordered if\n  // an operation is computed as an odd number of inverses of others\n  static bool compares_unordered(const_reference lhs, const_reference rhs,\n                                 bool inverse = false) noexcept\n  {\n    if ((lhs.is_number_float() && std::isnan(lhs.m_value.number_float) &&\n         rhs.is_number())\n        || (rhs.is_number_float() && std::isnan(rhs.m_value.number_float) &&\n            lhs.is_number())) {\n      return true;\n    }\n\n#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON\n    return (lhs.is_discarded() || rhs.is_discarded()) && !inverse;\n#else\n    static_cast<void>(inverse);\n    return lhs.is_discarded() || rhs.is_discarded();\n#endif\n  }\n\nprivate:\n  bool compares_unordered(const_reference rhs,\n                          bool inverse = false) const noexcept\n  {\n    return compares_unordered(*this, rhs, inverse);\n  }\n\npublic:\n#if JSON_HAS_THREE_WAY_COMPARISON\n  /// @brief comparison: equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/\n  bool operator==(const_reference rhs) const noexcept\n  {\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#endif\n    const_reference lhs = *this;\n    JSON_IMPLEMENT_OPERATOR( ==, true, false, false)\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif\n  }\n\n  /// @brief comparison: equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/\n  template<typename ScalarType>\n  requires std::is_scalar_v<ScalarType>\n  bool operator==(ScalarType rhs) const noexcept\n  {\n    return *this == basic_json(rhs);\n  }\n\n  /// @brief comparison: not equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/\n  bool operator!=(const_reference rhs) const noexcept\n  {\n    if (compares_unordered(rhs, true)) {\n      return false;\n    }\n\n    return !operator==(rhs);\n  }\n\n  /// @brief comparison: 3-way\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/\n  std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD*\n  {\n    const_reference lhs = *this;\n    // default_result is used if we cannot compare values. In that case,\n    // we compare types.\n    JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*\n                            std::partial_ordering::equivalent,\n                            std::partial_ordering::unordered,\n                            lhs_type <=> rhs_type) // *NOPAD*\n  }\n\n  /// @brief comparison: 3-way\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/\n  template<typename ScalarType>\n  requires std::is_scalar_v<ScalarType>\n  std::partial_ordering operator<=>(ScalarType rhs) const noexcept // *NOPAD*\n  {\n    return *this <=> basic_json(rhs); // *NOPAD*\n  }\n\n#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON\n  // all operators that are computed as an odd number of inverses of others\n  // need to be overloaded to emulate the legacy comparison behavior\n\n  /// @brief comparison: less than or equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_le/\n  JSON_HEDLEY_DEPRECATED_FOR(3.11.0,\n                             undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON)\n  bool operator<=(const_reference rhs) const noexcept\n  {\n    if (compares_unordered(rhs, true)) {\n      return false;\n    }\n\n    return !(rhs < *this);\n  }\n\n  /// @brief comparison: less than or equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_le/\n  template<typename ScalarType>\n  requires std::is_scalar_v<ScalarType>\n  bool operator<=(ScalarType rhs) const noexcept\n  {\n    return *this <= basic_json(rhs);\n  }\n\n  /// @brief comparison: greater than or equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/\n  JSON_HEDLEY_DEPRECATED_FOR(3.11.0,\n                             undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON)\n  bool operator>=(const_reference rhs) const noexcept\n  {\n    if (compares_unordered(rhs, true)) {\n      return false;\n    }\n\n    return !(*this < rhs);\n  }\n\n  /// @brief comparison: greater than or equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/\n  template<typename ScalarType>\n  requires std::is_scalar_v<ScalarType>\n  bool operator>=(ScalarType rhs) const noexcept\n  {\n    return *this >= basic_json(rhs);\n  }\n#endif\n#else\n  /// @brief comparison: equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/\n  friend bool operator==(const_reference lhs, const_reference rhs) noexcept\n  {\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#endif\n    JSON_IMPLEMENT_OPERATOR( ==, true, false, false)\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif\n  }\n\n  /// @brief comparison: equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/\n  template<typename ScalarType, typename std::enable_if<\n             std::is_scalar<ScalarType>::value, int>::type = 0>\n  friend bool operator==(const_reference lhs, ScalarType rhs) noexcept\n  {\n    return lhs == basic_json(rhs);\n  }\n\n  /// @brief comparison: equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/\n  template<typename ScalarType, typename std::enable_if<\n             std::is_scalar<ScalarType>::value, int>::type = 0>\n  friend bool operator==(ScalarType lhs, const_reference rhs) noexcept\n  {\n    return basic_json(lhs) == rhs;\n  }\n\n  /// @brief comparison: not equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/\n  friend bool operator!=(const_reference lhs, const_reference rhs) noexcept\n  {\n    if (compares_unordered(lhs, rhs, true)) {\n      return false;\n    }\n\n    return !(lhs == rhs);\n  }\n\n  /// @brief comparison: not equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/\n  template<typename ScalarType, typename std::enable_if<\n             std::is_scalar<ScalarType>::value, int>::type = 0>\n  friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept\n  {\n    return lhs != basic_json(rhs);\n  }\n\n  /// @brief comparison: not equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/\n  template<typename ScalarType, typename std::enable_if<\n             std::is_scalar<ScalarType>::value, int>::type = 0>\n  friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept\n  {\n    return basic_json(lhs) != rhs;\n  }\n\n  /// @brief comparison: less than\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/\n  friend bool operator<(const_reference lhs, const_reference rhs) noexcept\n  {\n    // default_result is used if we cannot compare values. In that case,\n    // we compare types. Note we have to call the operator explicitly,\n    // because MSVC has problems otherwise.\n    JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type))\n  }\n\n  /// @brief comparison: less than\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/\n  template<typename ScalarType, typename std::enable_if<\n             std::is_scalar<ScalarType>::value, int>::type = 0>\n  friend bool operator<(const_reference lhs, ScalarType rhs) noexcept\n  {\n    return lhs < basic_json(rhs);\n  }\n\n  /// @brief comparison: less than\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/\n  template<typename ScalarType, typename std::enable_if<\n             std::is_scalar<ScalarType>::value, int>::type = 0>\n  friend bool operator<(ScalarType lhs, const_reference rhs) noexcept\n  {\n    return basic_json(lhs) < rhs;\n  }\n\n  /// @brief comparison: less than or equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_le/\n  friend bool operator<=(const_reference lhs, const_reference rhs) noexcept\n  {\n    if (compares_unordered(lhs, rhs, true)) {\n      return false;\n    }\n\n    return !(rhs < lhs);\n  }\n\n  /// @brief comparison: less than or equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_le/\n  template<typename ScalarType, typename std::enable_if<\n             std::is_scalar<ScalarType>::value, int>::type = 0>\n  friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept\n  {\n    return lhs <= basic_json(rhs);\n  }\n\n  /// @brief comparison: less than or equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_le/\n  template<typename ScalarType, typename std::enable_if<\n             std::is_scalar<ScalarType>::value, int>::type = 0>\n  friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept\n  {\n    return basic_json(lhs) <= rhs;\n  }\n\n  /// @brief comparison: greater than\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/\n  friend bool operator>(const_reference lhs, const_reference rhs) noexcept\n  {\n    // double inverse\n    if (compares_unordered(lhs, rhs)) {\n      return false;\n    }\n\n    return !(lhs <= rhs);\n  }\n\n  /// @brief comparison: greater than\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/\n  template<typename ScalarType, typename std::enable_if<\n             std::is_scalar<ScalarType>::value, int>::type = 0>\n  friend bool operator>(const_reference lhs, ScalarType rhs) noexcept\n  {\n    return lhs > basic_json(rhs);\n  }\n\n  /// @brief comparison: greater than\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/\n  template<typename ScalarType, typename std::enable_if<\n             std::is_scalar<ScalarType>::value, int>::type = 0>\n  friend bool operator>(ScalarType lhs, const_reference rhs) noexcept\n  {\n    return basic_json(lhs) > rhs;\n  }\n\n  /// @brief comparison: greater than or equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/\n  friend bool operator>=(const_reference lhs, const_reference rhs) noexcept\n  {\n    if (compares_unordered(lhs, rhs, true)) {\n      return false;\n    }\n\n    return !(lhs < rhs);\n  }\n\n  /// @brief comparison: greater than or equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/\n  template<typename ScalarType, typename std::enable_if<\n             std::is_scalar<ScalarType>::value, int>::type = 0>\n  friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept\n  {\n    return lhs >= basic_json(rhs);\n  }\n\n  /// @brief comparison: greater than or equal\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/\n  template<typename ScalarType, typename std::enable_if<\n             std::is_scalar<ScalarType>::value, int>::type = 0>\n  friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept\n  {\n    return basic_json(lhs) >= rhs;\n  }\n#endif\n\n#undef JSON_IMPLEMENT_OPERATOR\n\n  /// @}\n\n  ///////////////////\n  // serialization //\n  ///////////////////\n\n  /// @name serialization\n  /// @{\n#ifndef JSON_NO_IO\n  /// @brief serialize to stream\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/\n  friend std::ostream& operator<<(std::ostream& o, const basic_json& j)\n  {\n    // read width member and use it as indentation parameter if nonzero\n    const bool pretty_print = o.width() > 0;\n    const auto indentation = pretty_print ? o.width() : 0;\n    // reset width to 0 for subsequent calls to this stream\n    o.width(0);\n    // do the actual serialization\n    serializer s(detail::output_adapter<char>(o), o.fill());\n    s.dump(j, pretty_print, false, static_cast<unsigned int>(indentation));\n    return o;\n  }\n\n  /// @brief serialize to stream\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/\n  /// @deprecated This function is deprecated since 3.0.0 and will be removed in\n  ///             version 4.0.0 of the library. Please use\n  ///             operator<<(std::ostream&, const basic_json&) instead; that is,\n  ///             replace calls like `j >> o;` with `o << j;`.\n  JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator<<(std::ostream&, const basic_json&))\n  friend std::ostream& operator>>(const basic_json& j, std::ostream& o)\n  {\n    return o << j;\n  }\n#endif  // JSON_NO_IO\n  /// @}\n\n\n  /////////////////////\n  // deserialization //\n  /////////////////////\n\n  /// @name deserialization\n  /// @{\n\n  /// @brief deserialize from a compatible input\n  /// @sa https://json.nlohmann.me/api/basic_json/parse/\n  template<typename InputType>\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json parse(InputType&& i,\n                          const parser_callback_t cb = nullptr,\n                          const bool allow_exceptions = true,\n                          const bool ignore_comments = false)\n  {\n    basic_json result;\n    parser(detail::input_adapter(std::forward<InputType>(i)), cb, allow_exceptions,\n           ignore_comments).parse(true, result);\n    return result;\n  }\n\n  /// @brief deserialize from a pair of character iterators\n  /// @sa https://json.nlohmann.me/api/basic_json/parse/\n  template<typename IteratorType>\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json parse(IteratorType first,\n                          IteratorType last,\n                          const parser_callback_t cb = nullptr,\n                          const bool allow_exceptions = true,\n                          const bool ignore_comments = false)\n  {\n    basic_json result;\n    parser(detail::input_adapter(std::move(first), std::move(last)), cb,\n           allow_exceptions, ignore_comments).parse(true, result);\n    return result;\n  }\n\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len))\n  static basic_json parse(detail::span_input_adapter&& i,\n                          const parser_callback_t cb = nullptr,\n                          const bool allow_exceptions = true,\n                          const bool ignore_comments = false)\n  {\n    basic_json result;\n    parser(i.get(), cb, allow_exceptions, ignore_comments).parse(true, result);\n    return result;\n  }\n\n  /// @brief check if the input is valid JSON\n  /// @sa https://json.nlohmann.me/api/basic_json/accept/\n  template<typename InputType>\n  static bool accept(InputType&& i,\n                     const bool ignore_comments = false)\n  {\n    return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false,\n                  ignore_comments).accept(true);\n  }\n\n  /// @brief check if the input is valid JSON\n  /// @sa https://json.nlohmann.me/api/basic_json/accept/\n  template<typename IteratorType>\n  static bool accept(IteratorType first, IteratorType last,\n                     const bool ignore_comments = false)\n  {\n    return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr,\n                  false, ignore_comments).accept(true);\n  }\n\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len))\n  static bool accept(detail::span_input_adapter&& i,\n                     const bool ignore_comments = false)\n  {\n    return parser(i.get(), nullptr, false, ignore_comments).accept(true);\n  }\n\n  /// @brief generate SAX events\n  /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/\n  template <typename InputType, typename SAX>\n  JSON_HEDLEY_NON_NULL(2)\n  static bool sax_parse(InputType&& i, SAX* sax,\n                        input_format_t format = input_format_t::json,\n                        const bool strict = true,\n                        const bool ignore_comments = false)\n  {\n    auto ia = detail::input_adapter(std::forward<InputType>(i));\n    return format == input_format_t::json\n           ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict)\n           : detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia),\n               format).sax_parse(format, sax, strict);\n  }\n\n  /// @brief generate SAX events\n  /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/\n  template<class IteratorType, class SAX>\n  JSON_HEDLEY_NON_NULL(3)\n  static bool sax_parse(IteratorType first, IteratorType last, SAX* sax,\n                        input_format_t format = input_format_t::json,\n                        const bool strict = true,\n                        const bool ignore_comments = false)\n  {\n    auto ia = detail::input_adapter(std::move(first), std::move(last));\n    return format == input_format_t::json\n           ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict)\n           : detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia),\n               format).sax_parse(format, sax, strict);\n  }\n\n  /// @brief generate SAX events\n  /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/\n  /// @deprecated This function is deprecated since 3.8.0 and will be removed in\n  ///             version 4.0.0 of the library. Please use\n  ///             sax_parse(ptr, ptr + len) instead.\n  template <typename SAX>\n  JSON_HEDLEY_DEPRECATED_FOR(3.8.0, sax_parse(ptr, ptr + len, ...))\n  JSON_HEDLEY_NON_NULL(2)\n  static bool sax_parse(detail::span_input_adapter&& i, SAX* sax,\n                        input_format_t format = input_format_t::json,\n                        const bool strict = true,\n                        const bool ignore_comments = false)\n  {\n    auto ia = i.get();\n    return format == input_format_t::json\n           // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n           ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict)\n           // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n           : detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia),\n               format).sax_parse(format, sax, strict);\n  }\n#ifndef JSON_NO_IO\n  /// @brief deserialize from stream\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/\n  /// @deprecated This stream operator is deprecated since 3.0.0 and will be removed in\n  ///             version 4.0.0 of the library. Please use\n  ///             operator>>(std::istream&, basic_json&) instead; that is,\n  ///             replace calls like `j << i;` with `i >> j;`.\n  JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator>>(std::istream&, basic_json&))\n  friend std::istream& operator<<(basic_json& j, std::istream& i)\n  {\n    return operator>>(i, j);\n  }\n\n  /// @brief deserialize from stream\n  /// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/\n  friend std::istream& operator>>(std::istream& i, basic_json& j)\n  {\n    parser(detail::input_adapter(i)).parse(false, j);\n    return i;\n  }\n#endif  // JSON_NO_IO\n  /// @}\n\n  ///////////////////////////\n  // convenience functions //\n  ///////////////////////////\n\n  /// @brief return the type as string\n  /// @sa https://json.nlohmann.me/api/basic_json/type_name/\n  JSON_HEDLEY_RETURNS_NON_NULL\n  const char* type_name() const noexcept\n  {\n    switch (m_type) {\n    case value_t::null:\n      return \"null\";\n\n    case value_t::object:\n      return \"object\";\n\n    case value_t::array:\n      return \"array\";\n\n    case value_t::string:\n      return \"string\";\n\n    case value_t::boolean:\n      return \"boolean\";\n\n    case value_t::binary:\n      return \"binary\";\n\n    case value_t::discarded:\n      return \"discarded\";\n\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    default:\n      return \"number\";\n    }\n  }\n\n\nJSON_PRIVATE_UNLESS_TESTED:\n  //////////////////////\n  // member variables //\n  //////////////////////\n\n  /// the type of the current element\n  value_t m_type = value_t::null;\n\n  /// the value of the current element\n  json_value m_value = {};\n\n#if JSON_DIAGNOSTICS\n  /// a pointer to a parent value (for debugging purposes)\n  basic_json* m_parent = nullptr;\n#endif\n\n  //////////////////////////////////////////\n  // binary serialization/deserialization //\n  //////////////////////////////////////////\n\n  /// @name binary serialization/deserialization support\n  /// @{\n\npublic:\n  /// @brief create a CBOR serialization of a given JSON value\n  /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/\n  static std::vector<std::uint8_t> to_cbor(const basic_json& j)\n  {\n    std::vector<std::uint8_t> result;\n    to_cbor(j, result);\n    return result;\n  }\n\n  /// @brief create a CBOR serialization of a given JSON value\n  /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/\n  static void to_cbor(const basic_json& j, detail::output_adapter<std::uint8_t> o)\n  {\n    binary_writer<std::uint8_t>(o).write_cbor(j);\n  }\n\n  /// @brief create a CBOR serialization of a given JSON value\n  /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/\n  static void to_cbor(const basic_json& j, detail::output_adapter<char> o)\n  {\n    binary_writer<char>(o).write_cbor(j);\n  }\n\n  /// @brief create a MessagePack serialization of a given JSON value\n  /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/\n  static std::vector<std::uint8_t> to_msgpack(const basic_json& j)\n  {\n    std::vector<std::uint8_t> result;\n    to_msgpack(j, result);\n    return result;\n  }\n\n  /// @brief create a MessagePack serialization of a given JSON value\n  /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/\n  static void to_msgpack(const basic_json& j,\n                         detail::output_adapter<std::uint8_t> o)\n  {\n    binary_writer<std::uint8_t>(o).write_msgpack(j);\n  }\n\n  /// @brief create a MessagePack serialization of a given JSON value\n  /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/\n  static void to_msgpack(const basic_json& j, detail::output_adapter<char> o)\n  {\n    binary_writer<char>(o).write_msgpack(j);\n  }\n\n  /// @brief create a UBJSON serialization of a given JSON value\n  /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/\n  static std::vector<std::uint8_t> to_ubjson(const basic_json& j,\n      const bool use_size = false,\n      const bool use_type = false)\n  {\n    std::vector<std::uint8_t> result;\n    to_ubjson(j, result, use_size, use_type);\n    return result;\n  }\n\n  /// @brief create a UBJSON serialization of a given JSON value\n  /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/\n  static void to_ubjson(const basic_json& j,\n                        detail::output_adapter<std::uint8_t> o,\n                        const bool use_size = false, const bool use_type = false)\n  {\n    binary_writer<std::uint8_t>(o).write_ubjson(j, use_size, use_type);\n  }\n\n  /// @brief create a UBJSON serialization of a given JSON value\n  /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/\n  static void to_ubjson(const basic_json& j, detail::output_adapter<char> o,\n                        const bool use_size = false, const bool use_type = false)\n  {\n    binary_writer<char>(o).write_ubjson(j, use_size, use_type);\n  }\n\n  /// @brief create a BJData serialization of a given JSON value\n  /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/\n  static std::vector<std::uint8_t> to_bjdata(const basic_json& j,\n      const bool use_size = false,\n      const bool use_type = false)\n  {\n    std::vector<std::uint8_t> result;\n    to_bjdata(j, result, use_size, use_type);\n    return result;\n  }\n\n  /// @brief create a BJData serialization of a given JSON value\n  /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/\n  static void to_bjdata(const basic_json& j,\n                        detail::output_adapter<std::uint8_t> o,\n                        const bool use_size = false, const bool use_type = false)\n  {\n    binary_writer<std::uint8_t>(o).write_ubjson(j, use_size, use_type, true, true);\n  }\n\n  /// @brief create a BJData serialization of a given JSON value\n  /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/\n  static void to_bjdata(const basic_json& j, detail::output_adapter<char> o,\n                        const bool use_size = false, const bool use_type = false)\n  {\n    binary_writer<char>(o).write_ubjson(j, use_size, use_type, true, true);\n  }\n\n  /// @brief create a BSON serialization of a given JSON value\n  /// @sa https://json.nlohmann.me/api/basic_json/to_bson/\n  static std::vector<std::uint8_t> to_bson(const basic_json& j)\n  {\n    std::vector<std::uint8_t> result;\n    to_bson(j, result);\n    return result;\n  }\n\n  /// @brief create a BSON serialization of a given JSON value\n  /// @sa https://json.nlohmann.me/api/basic_json/to_bson/\n  static void to_bson(const basic_json& j, detail::output_adapter<std::uint8_t> o)\n  {\n    binary_writer<std::uint8_t>(o).write_bson(j);\n  }\n\n  /// @brief create a BSON serialization of a given JSON value\n  /// @sa https://json.nlohmann.me/api/basic_json/to_bson/\n  static void to_bson(const basic_json& j, detail::output_adapter<char> o)\n  {\n    binary_writer<char>(o).write_bson(j);\n  }\n\n  /// @brief create a JSON value from an input in CBOR format\n  /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/\n  template<typename InputType>\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json from_cbor(InputType&& i,\n                              const bool strict = true,\n                              const bool allow_exceptions = true,\n                              const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)\n  {\n    basic_json result;\n    detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n    auto ia = detail::input_adapter(std::forward<InputType>(i));\n    const bool res = binary_reader<decltype(ia)>(std::move(ia),\n                     input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict,\n                         tag_handler);\n    return res ? result : basic_json(value_t::discarded);\n  }\n\n  /// @brief create a JSON value from an input in CBOR format\n  /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/\n  template<typename IteratorType>\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json from_cbor(IteratorType first, IteratorType last,\n                              const bool strict = true,\n                              const bool allow_exceptions = true,\n                              const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)\n  {\n    basic_json result;\n    detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n    auto ia = detail::input_adapter(std::move(first), std::move(last));\n    const bool res = binary_reader<decltype(ia)>(std::move(ia),\n                     input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict,\n                         tag_handler);\n    return res ? result : basic_json(value_t::discarded);\n  }\n\n  template<typename T>\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len))\n  static basic_json from_cbor(const T* ptr, std::size_t len,\n                              const bool strict = true,\n                              const bool allow_exceptions = true,\n                              const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)\n  {\n    return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler);\n  }\n\n\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len))\n  static basic_json from_cbor(detail::span_input_adapter&& i,\n                              const bool strict = true,\n                              const bool allow_exceptions = true,\n                              const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)\n  {\n    basic_json result;\n    detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n    auto ia = i.get();\n    // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n    const bool res = binary_reader<decltype(ia)>(std::move(ia),\n                     input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict,\n                         tag_handler);\n    return res ? result : basic_json(value_t::discarded);\n  }\n\n  /// @brief create a JSON value from an input in MessagePack format\n  /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/\n  template<typename InputType>\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json from_msgpack(InputType&& i,\n                                 const bool strict = true,\n                                 const bool allow_exceptions = true)\n  {\n    basic_json result;\n    detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n    auto ia = detail::input_adapter(std::forward<InputType>(i));\n    const bool res = binary_reader<decltype(ia)>(std::move(ia),\n                     input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict);\n    return res ? result : basic_json(value_t::discarded);\n  }\n\n  /// @brief create a JSON value from an input in MessagePack format\n  /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/\n  template<typename IteratorType>\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json from_msgpack(IteratorType first, IteratorType last,\n                                 const bool strict = true,\n                                 const bool allow_exceptions = true)\n  {\n    basic_json result;\n    detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n    auto ia = detail::input_adapter(std::move(first), std::move(last));\n    const bool res = binary_reader<decltype(ia)>(std::move(ia),\n                     input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict);\n    return res ? result : basic_json(value_t::discarded);\n  }\n\n  template<typename T>\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len))\n  static basic_json from_msgpack(const T* ptr, std::size_t len,\n                                 const bool strict = true,\n                                 const bool allow_exceptions = true)\n  {\n    return from_msgpack(ptr, ptr + len, strict, allow_exceptions);\n  }\n\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len))\n  static basic_json from_msgpack(detail::span_input_adapter&& i,\n                                 const bool strict = true,\n                                 const bool allow_exceptions = true)\n  {\n    basic_json result;\n    detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n    auto ia = i.get();\n    // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n    const bool res = binary_reader<decltype(ia)>(std::move(ia),\n                     input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict);\n    return res ? result : basic_json(value_t::discarded);\n  }\n\n  /// @brief create a JSON value from an input in UBJSON format\n  /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/\n  template<typename InputType>\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json from_ubjson(InputType&& i,\n                                const bool strict = true,\n                                const bool allow_exceptions = true)\n  {\n    basic_json result;\n    detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n    auto ia = detail::input_adapter(std::forward<InputType>(i));\n    const bool res = binary_reader<decltype(ia)>(std::move(ia),\n                     input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict);\n    return res ? result : basic_json(value_t::discarded);\n  }\n\n  /// @brief create a JSON value from an input in UBJSON format\n  /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/\n  template<typename IteratorType>\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json from_ubjson(IteratorType first, IteratorType last,\n                                const bool strict = true,\n                                const bool allow_exceptions = true)\n  {\n    basic_json result;\n    detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n    auto ia = detail::input_adapter(std::move(first), std::move(last));\n    const bool res = binary_reader<decltype(ia)>(std::move(ia),\n                     input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict);\n    return res ? result : basic_json(value_t::discarded);\n  }\n\n  template<typename T>\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len))\n  static basic_json from_ubjson(const T* ptr, std::size_t len,\n                                const bool strict = true,\n                                const bool allow_exceptions = true)\n  {\n    return from_ubjson(ptr, ptr + len, strict, allow_exceptions);\n  }\n\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len))\n  static basic_json from_ubjson(detail::span_input_adapter&& i,\n                                const bool strict = true,\n                                const bool allow_exceptions = true)\n  {\n    basic_json result;\n    detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n    auto ia = i.get();\n    // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n    const bool res = binary_reader<decltype(ia)>(std::move(ia),\n                     input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict);\n    return res ? result : basic_json(value_t::discarded);\n  }\n\n\n  /// @brief create a JSON value from an input in BJData format\n  /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/\n  template<typename InputType>\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json from_bjdata(InputType&& i,\n                                const bool strict = true,\n                                const bool allow_exceptions = true)\n  {\n    basic_json result;\n    detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n    auto ia = detail::input_adapter(std::forward<InputType>(i));\n    const bool res = binary_reader<decltype(ia)>(std::move(ia),\n                     input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict);\n    return res ? result : basic_json(value_t::discarded);\n  }\n\n  /// @brief create a JSON value from an input in BJData format\n  /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/\n  template<typename IteratorType>\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json from_bjdata(IteratorType first, IteratorType last,\n                                const bool strict = true,\n                                const bool allow_exceptions = true)\n  {\n    basic_json result;\n    detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n    auto ia = detail::input_adapter(std::move(first), std::move(last));\n    const bool res = binary_reader<decltype(ia)>(std::move(ia),\n                     input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict);\n    return res ? result : basic_json(value_t::discarded);\n  }\n\n  /// @brief create a JSON value from an input in BSON format\n  /// @sa https://json.nlohmann.me/api/basic_json/from_bson/\n  template<typename InputType>\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json from_bson(InputType&& i,\n                              const bool strict = true,\n                              const bool allow_exceptions = true)\n  {\n    basic_json result;\n    detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n    auto ia = detail::input_adapter(std::forward<InputType>(i));\n    const bool res = binary_reader<decltype(ia)>(std::move(ia),\n                     input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict);\n    return res ? result : basic_json(value_t::discarded);\n  }\n\n  /// @brief create a JSON value from an input in BSON format\n  /// @sa https://json.nlohmann.me/api/basic_json/from_bson/\n  template<typename IteratorType>\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json from_bson(IteratorType first, IteratorType last,\n                              const bool strict = true,\n                              const bool allow_exceptions = true)\n  {\n    basic_json result;\n    detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n    auto ia = detail::input_adapter(std::move(first), std::move(last));\n    const bool res = binary_reader<decltype(ia)>(std::move(ia),\n                     input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict);\n    return res ? result : basic_json(value_t::discarded);\n  }\n\n  template<typename T>\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len))\n  static basic_json from_bson(const T* ptr, std::size_t len,\n                              const bool strict = true,\n                              const bool allow_exceptions = true)\n  {\n    return from_bson(ptr, ptr + len, strict, allow_exceptions);\n  }\n\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len))\n  static basic_json from_bson(detail::span_input_adapter&& i,\n                              const bool strict = true,\n                              const bool allow_exceptions = true)\n  {\n    basic_json result;\n    detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);\n    auto ia = i.get();\n    // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n    const bool res = binary_reader<decltype(ia)>(std::move(ia),\n                     input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict);\n    return res ? result : basic_json(value_t::discarded);\n  }\n  /// @}\n\n  //////////////////////////\n  // JSON Pointer support //\n  //////////////////////////\n\n  /// @name JSON Pointer functions\n  /// @{\n\n  /// @brief access specified element via JSON Pointer\n  /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n  reference operator[](const json_pointer& ptr)\n  {\n    return ptr.get_unchecked(this);\n  }\n\n  template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>\n  JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or\n                             nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n  reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr)\n  {\n    return ptr.get_unchecked(this);\n  }\n\n  /// @brief access specified element via JSON Pointer\n  /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n  const_reference operator[](const json_pointer& ptr) const\n  {\n    return ptr.get_unchecked(this);\n  }\n\n  template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>\n  JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or\n                             nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n  const_reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr)\n  const\n  {\n    return ptr.get_unchecked(this);\n  }\n\n  /// @brief access specified element via JSON Pointer\n  /// @sa https://json.nlohmann.me/api/basic_json/at/\n  reference at(const json_pointer& ptr)\n  {\n    return ptr.get_checked(this);\n  }\n\n  template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>\n  JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or\n                             nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n  reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr)\n  {\n    return ptr.get_checked(this);\n  }\n\n  /// @brief access specified element via JSON Pointer\n  /// @sa https://json.nlohmann.me/api/basic_json/at/\n  const_reference at(const json_pointer& ptr) const\n  {\n    return ptr.get_checked(this);\n  }\n\n  template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>\n  JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or\n                             nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n  const_reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr) const\n  {\n    return ptr.get_checked(this);\n  }\n\n  /// @brief return flattened JSON value\n  /// @sa https://json.nlohmann.me/api/basic_json/flatten/\n  basic_json flatten() const\n  {\n    basic_json result(value_t::object);\n    json_pointer::flatten(\"\", *this, result);\n    return result;\n  }\n\n  /// @brief unflatten a previously flattened JSON value\n  /// @sa https://json.nlohmann.me/api/basic_json/unflatten/\n  basic_json unflatten() const\n  {\n    return json_pointer::unflatten(*this);\n  }\n\n  /// @}\n\n  //////////////////////////\n  // JSON Patch functions //\n  //////////////////////////\n\n  /// @name JSON Patch functions\n  /// @{\n\n  /// @brief applies a JSON patch in-place without copying the object\n  /// @sa https://json.nlohmann.me/api/basic_json/patch/\n  void patch_inplace(const basic_json& json_patch)\n  {\n    basic_json& result = *this;\n    // the valid JSON Patch operations\n    enum class patch_operations {add, remove, replace, move, copy, test, invalid};\n    const auto get_op = [](const std::string & op) {\n      if (op == \"add\") {\n        return patch_operations::add;\n      }\n\n      if (op == \"remove\") {\n        return patch_operations::remove;\n      }\n\n      if (op == \"replace\") {\n        return patch_operations::replace;\n      }\n\n      if (op == \"move\") {\n        return patch_operations::move;\n      }\n\n      if (op == \"copy\") {\n        return patch_operations::copy;\n      }\n\n      if (op == \"test\") {\n        return patch_operations::test;\n      }\n\n      return patch_operations::invalid;\n    };\n    // wrapper for \"add\" operation; add value at ptr\n    const auto operation_add = [&result](json_pointer & ptr, basic_json val) {\n      // adding to the root of the target document means replacing it\n      if (ptr.empty()) {\n        result = val;\n        return;\n      }\n\n      // make sure the top element of the pointer exists\n      json_pointer top_pointer = ptr.top();\n\n      if (top_pointer != ptr) {\n        result.at(top_pointer);\n      }\n\n      // get reference to parent of JSON pointer ptr\n      const auto last_path = ptr.back();\n      ptr.pop_back();\n      // parent must exist when performing patch add per RFC6902 specs\n      basic_json& parent = result.at(ptr);\n\n      switch (parent.m_type) {\n      case value_t::null:\n      case value_t::object: {\n        // use operator[] to add value\n        parent[last_path] = val;\n        break;\n      }\n\n      case value_t::array: {\n        if (last_path == \"-\") {\n          // special case: append to back\n          parent.push_back(val);\n        } else {\n          const auto idx = json_pointer::template array_index<basic_json_t>(last_path);\n\n          if (JSON_HEDLEY_UNLIKELY(idx > parent.size())) {\n            // avoid undefined behavior\n            JSON_THROW(out_of_range::create(401, detail::concat(\"array index \",\n                                            std::to_string(idx), \" is out of range\"), &parent));\n          }\n\n          // default case: insert add offset\n          parent.insert(parent.begin() + static_cast<difference_type>(idx), val);\n        }\n\n        break;\n      }\n\n      // if there exists a parent it cannot be primitive\n      case value_t::string: // LCOV_EXCL_LINE\n      case value_t::boolean: // LCOV_EXCL_LINE\n      case value_t::number_integer: // LCOV_EXCL_LINE\n      case value_t::number_unsigned: // LCOV_EXCL_LINE\n      case value_t::number_float: // LCOV_EXCL_LINE\n      case value_t::binary: // LCOV_EXCL_LINE\n      case value_t::discarded: // LCOV_EXCL_LINE\n      default:            // LCOV_EXCL_LINE\n        JSON_ASSERT(\n          false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n      }\n    };\n    // wrapper for \"remove\" operation; remove value at ptr\n    const auto operation_remove = [this, &result](json_pointer & ptr) {\n      // get reference to parent of JSON pointer ptr\n      const auto last_path = ptr.back();\n      ptr.pop_back();\n      basic_json& parent = result.at(ptr);\n\n      // remove child\n      if (parent.is_object()) {\n        // perform range check\n        auto it = parent.find(last_path);\n\n        if (JSON_HEDLEY_LIKELY(it != parent.end())) {\n          parent.erase(it);\n        } else {\n          JSON_THROW(out_of_range::create(403, detail::concat(\"key '\", last_path,\n                                          \"' not found\"), this));\n        }\n      } else if (parent.is_array()) {\n        // note erase performs range check\n        parent.erase(json_pointer::template array_index<basic_json_t>(last_path));\n      }\n    };\n\n    // type check: top level value must be an array\n    if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array())) {\n      JSON_THROW(parse_error::create(104, 0, \"JSON patch must be an array of objects\",\n                                     &json_patch));\n    }\n\n    // iterate and apply the operations\n    for (const auto& val : json_patch) {\n      // wrapper to get a value for an operation\n      const auto get_value = [&val](const std::string & op,\n                                    const std::string & member,\n      bool string_type) -> basic_json & {\n        // find value\n        auto it = val.m_value.object->find(member);\n\n        // context-sensitive error message\n        const auto error_msg = (op == \"op\") ? \"operation\" : detail::concat(\"operation '\", op, '\\'');\n\n        // check if desired value is present\n        if (JSON_HEDLEY_UNLIKELY(it == val.m_value.object->end()))\n        {\n          // NOLINTNEXTLINE(performance-inefficient-string-concatenation)\n          JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg,\n                                         \" must have member '\", member, \"'\"), &val));\n        }\n\n        // check if result is of type string\n        if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string()))\n        {\n          // NOLINTNEXTLINE(performance-inefficient-string-concatenation)\n          JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg,\n                                         \" must have string member '\", member, \"'\"), &val));\n        }\n\n        // no error: return value\n        return it->second;\n      };\n\n      // type check: every element of the array must be an object\n      if (JSON_HEDLEY_UNLIKELY(!val.is_object())) {\n        JSON_THROW(parse_error::create(104, 0, \"JSON patch must be an array of objects\",\n                                       &val));\n      }\n\n      // collect mandatory members\n      const auto op = get_value(\"op\", \"op\", true).template get<std::string>();\n      const auto path = get_value(op, \"path\", true).template get<std::string>();\n      json_pointer ptr(path);\n\n      switch (get_op(op)) {\n      case patch_operations::add: {\n        operation_add(ptr, get_value(\"add\", \"value\", false));\n        break;\n      }\n\n      case patch_operations::remove: {\n        operation_remove(ptr);\n        break;\n      }\n\n      case patch_operations::replace: {\n        // the \"path\" location must exist - use at()\n        result.at(ptr) = get_value(\"replace\", \"value\", false);\n        break;\n      }\n\n      case patch_operations::move: {\n        const auto from_path = get_value(\"move\", \"from\",\n                                         true).template get<std::string>();\n        json_pointer from_ptr(from_path);\n        // the \"from\" location must exist - use at()\n        basic_json v = result.at(from_ptr);\n        // The move operation is functionally identical to a\n        // \"remove\" operation on the \"from\" location, followed\n        // immediately by an \"add\" operation at the target\n        // location with the value that was just removed.\n        operation_remove(from_ptr);\n        operation_add(ptr, v);\n        break;\n      }\n\n      case patch_operations::copy: {\n        const auto from_path = get_value(\"copy\", \"from\",\n                                         true).template get<std::string>();\n        const json_pointer from_ptr(from_path);\n        // the \"from\" location must exist - use at()\n        basic_json v = result.at(from_ptr);\n        // The copy is functionally identical to an \"add\"\n        // operation at the target location using the value\n        // specified in the \"from\" member.\n        operation_add(ptr, v);\n        break;\n      }\n\n      case patch_operations::test: {\n        bool success = false;\n        JSON_TRY {\n          // check if \"value\" matches the one at \"path\"\n          // the \"path\" location must exist - use at()\n          success = (result.at(ptr) == get_value(\"test\", \"value\", false));\n        }\n        JSON_INTERNAL_CATCH(out_of_range&) {\n          // ignore out of range errors: success remains false\n        }\n\n        // throw an exception if test fails\n        if (JSON_HEDLEY_UNLIKELY(!success)) {\n          JSON_THROW(other_error::create(501, detail::concat(\"unsuccessful: \",\n                                         val.dump()), &val));\n        }\n\n        break;\n      }\n\n      case patch_operations::invalid:\n      default: {\n        // op must be \"add\", \"remove\", \"replace\", \"move\", \"copy\", or\n        // \"test\"\n        JSON_THROW(parse_error::create(105, 0, detail::concat(\"operation value '\", op,\n                                       \"' is invalid\"), &val));\n      }\n      }\n    }\n  }\n\n  /// @brief applies a JSON patch to a copy of the current object\n  /// @sa https://json.nlohmann.me/api/basic_json/patch/\n  basic_json patch(const basic_json& json_patch) const\n  {\n    basic_json result = *this;\n    result.patch_inplace(json_patch);\n    return result;\n  }\n\n  /// @brief creates a diff as a JSON patch\n  /// @sa https://json.nlohmann.me/api/basic_json/diff/\n  JSON_HEDLEY_WARN_UNUSED_RESULT\n  static basic_json diff(const basic_json& source, const basic_json& target,\n                         const std::string& path = \"\")\n  {\n    // the patch\n    basic_json result(value_t::array);\n\n    // if the values are the same, return empty patch\n    if (source == target) {\n      return result;\n    }\n\n    if (source.type() != target.type()) {\n      // different types: replace value\n      result.push_back( {\n        {\"op\", \"replace\"}, {\"path\", path}, {\"value\", target}\n      });\n      return result;\n    }\n\n    switch (source.type()) {\n    case value_t::array: {\n      // first pass: traverse common elements\n      std::size_t i = 0;\n\n      while (i < source.size() && i < target.size()) {\n        // recursive call to compare array values at index i\n        auto temp_diff = diff(source[i], target[i], detail::concat(path, '/',\n                              std::to_string(i)));\n        result.insert(result.end(), temp_diff.begin(), temp_diff.end());\n        ++i;\n      }\n\n      // We now reached the end of at least one array\n      // in a second pass, traverse the remaining elements\n      // remove my remaining elements\n      const auto end_index = static_cast<difference_type>(result.size());\n\n      while (i < source.size()) {\n        // add operations in reverse order to avoid invalid\n        // indices\n        result.insert(result.begin() + end_index, object( {\n          {\"op\", \"remove\"},\n          {\"path\", detail::concat(path, '/', std::to_string(i))}\n        }));\n        ++i;\n      }\n\n      // add other remaining elements\n      while (i < target.size()) {\n        result.push_back( {\n          {\"op\", \"add\"},\n          {\"path\", detail::concat(path, \"/-\")},\n          {\"value\", target[i]}\n        });\n        ++i;\n      }\n\n      break;\n    }\n\n    case value_t::object: {\n      // first pass: traverse this object's elements\n      for (auto it = source.cbegin(); it != source.cend(); ++it) {\n        // escape the key name to be used in a JSON patch\n        const auto path_key = detail::concat(path, '/', detail::escape(it.key()));\n\n        if (target.find(it.key()) != target.end()) {\n          // recursive call to compare object values at key it\n          auto temp_diff = diff(it.value(), target[it.key()], path_key);\n          result.insert(result.end(), temp_diff.begin(), temp_diff.end());\n        } else {\n          // found a key that is not in o -> remove it\n          result.push_back(object( {\n            {\"op\", \"remove\"}, {\"path\", path_key}\n          }));\n        }\n      }\n\n      // second pass: traverse other object's elements\n      for (auto it = target.cbegin(); it != target.cend(); ++it) {\n        if (source.find(it.key()) == source.end()) {\n          // found a key that is not in this -> add it\n          const auto path_key = detail::concat(path, '/', detail::escape(it.key()));\n          result.push_back( {\n            {\"op\", \"add\"}, {\"path\", path_key},\n            {\"value\", it.value()}\n          });\n        }\n      }\n\n      break;\n    }\n\n    case value_t::null:\n    case value_t::string:\n    case value_t::boolean:\n    case value_t::number_integer:\n    case value_t::number_unsigned:\n    case value_t::number_float:\n    case value_t::binary:\n    case value_t::discarded:\n    default: {\n      // both primitive type: replace value\n      result.push_back( {\n        {\"op\", \"replace\"}, {\"path\", path}, {\"value\", target}\n      });\n      break;\n    }\n    }\n\n    return result;\n  }\n  /// @}\n\n  ////////////////////////////////\n  // JSON Merge Patch functions //\n  ////////////////////////////////\n\n  /// @name JSON Merge Patch functions\n  /// @{\n\n  /// @brief applies a JSON Merge Patch\n  /// @sa https://json.nlohmann.me/api/basic_json/merge_patch/\n  void merge_patch(const basic_json& apply_patch)\n  {\n    if (apply_patch.is_object()) {\n      if (!is_object()) {\n        *this = object();\n      }\n\n      for (auto it = apply_patch.begin(); it != apply_patch.end(); ++it) {\n        if (it.value().is_null()) {\n          erase(it.key());\n        } else {\n          operator[](it.key()).merge_patch(it.value());\n        }\n      }\n    } else {\n      *this = apply_patch;\n    }\n  }\n\n  /// @}\n};\n\n/// @brief user-defined to_string function for JSON values\n/// @sa https://json.nlohmann.me/api/basic_json/to_string/\nNLOHMANN_BASIC_JSON_TPL_DECLARATION\nstd::string to_string(const NLOHMANN_BASIC_JSON_TPL& j)\n{\n  return j.dump();\n}\n\ninline namespace literals\n{\ninline namespace json_literals\n{\n\n/// @brief user-defined string literal for JSON values\n/// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json/\nJSON_HEDLEY_NON_NULL(1)\ninline nlohmann::json operator \"\" _json(const char* s, std::size_t n)\n{\n  return nlohmann::json::parse(s, s + n);\n}\n\n/// @brief user-defined string literal for JSON pointer\n/// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json_pointer/\nJSON_HEDLEY_NON_NULL(1)\ninline nlohmann::json::json_pointer operator \"\" _json_pointer(const char* s,\n    std::size_t n)\n{\n  return nlohmann::json::json_pointer(std::string(s, n));\n}\n\n}  // namespace json_literals\n}  // namespace literals\nNLOHMANN_JSON_NAMESPACE_END\n\n///////////////////////\n// nonmember support //\n///////////////////////\n\nnamespace std // NOLINT(cert-dcl58-cpp)\n{\n\n/// @brief hash value for JSON objects\n/// @sa https://json.nlohmann.me/api/basic_json/std_hash/\nNLOHMANN_BASIC_JSON_TPL_DECLARATION\nstruct hash<nlohmann::NLOHMANN_BASIC_JSON_TPL> {\n  std::size_t operator()(const nlohmann::NLOHMANN_BASIC_JSON_TPL& j) const\n  {\n    return nlohmann::detail::hash(j);\n  }\n};\n\n// specialization for std::less<value_t>\ntemplate<>\nstruct less< ::nlohmann::detail::value_t> { // do not remove the space after '<', see https://github.com/nlohmann/json/pull/679\n  /*!\n  @brief compare two value_t enum values\n  @since version 3.0.0\n  */\n  bool operator()(::nlohmann::detail::value_t lhs,\n                  ::nlohmann::detail::value_t rhs) const noexcept\n  {\n#if JSON_HAS_THREE_WAY_COMPARISON\n    return std::is_lt(lhs <=> rhs); // *NOPAD*\n#else\n    return ::nlohmann::detail::operator<(lhs, rhs);\n#endif\n  }\n};\n\n// C++20 prohibit function specialization in the std namespace.\n#ifndef JSON_HAS_CPP_20\n\n/// @brief exchanges the values of two JSON objects\n/// @sa https://json.nlohmann.me/api/basic_json/std_swap/\nNLOHMANN_BASIC_JSON_TPL_DECLARATION\ninline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1,\n                 nlohmann::NLOHMANN_BASIC_JSON_TPL& j2)\nnoexcept(  // NOLINT(readability-inconsistent-declaration-parameter-name)\n  is_nothrow_move_constructible<nlohmann::NLOHMANN_BASIC_JSON_TPL>::value&&                          // NOLINT(misc-redundant-expression)\n  is_nothrow_move_assignable<nlohmann::NLOHMANN_BASIC_JSON_TPL>::value)\n{\n  j1.swap(j2);\n}\n\n#endif\n\n}  // namespace std\n\n#if JSON_USE_GLOBAL_UDLS\nusing nlohmann::literals::json_literals::operator \"\"\n_json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers)\nusing nlohmann::literals::json_literals::operator \"\"\n_json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers)\n#endif\n\n// #include <nlohmann/detail/macro_unscope.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// restore clang diagnostic settings\n#if defined(__clang__)\n#pragma clang diagnostic pop\n#endif\n\n// clean up\n#undef JSON_ASSERT\n#undef JSON_INTERNAL_CATCH\n#undef JSON_THROW\n#undef JSON_PRIVATE_UNLESS_TESTED\n#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION\n#undef NLOHMANN_BASIC_JSON_TPL\n#undef JSON_EXPLICIT\n#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL\n#undef JSON_INLINE_VARIABLE\n#undef JSON_NO_UNIQUE_ADDRESS\n#undef JSON_DISABLE_ENUM_SERIALIZATION\n#undef JSON_USE_GLOBAL_UDLS\n\n#ifndef JSON_TEST_KEEP_MACROS\n#undef JSON_CATCH\n#undef JSON_TRY\n#undef JSON_HAS_CPP_11\n#undef JSON_HAS_CPP_14\n#undef JSON_HAS_CPP_17\n#undef JSON_HAS_CPP_20\n#undef JSON_HAS_FILESYSTEM\n#undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n#undef JSON_HAS_THREE_WAY_COMPARISON\n#undef JSON_HAS_RANGES\n#undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON\n#endif\n\n// #include <nlohmann/thirdparty/hedley/hedley_undef.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.11.2\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#undef JSON_HEDLEY_ALWAYS_INLINE\n#undef JSON_HEDLEY_ARM_VERSION\n#undef JSON_HEDLEY_ARM_VERSION_CHECK\n#undef JSON_HEDLEY_ARRAY_PARAM\n#undef JSON_HEDLEY_ASSUME\n#undef JSON_HEDLEY_BEGIN_C_DECLS\n#undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE\n#undef JSON_HEDLEY_CLANG_HAS_BUILTIN\n#undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE\n#undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE\n#undef JSON_HEDLEY_CLANG_HAS_EXTENSION\n#undef JSON_HEDLEY_CLANG_HAS_FEATURE\n#undef JSON_HEDLEY_CLANG_HAS_WARNING\n#undef JSON_HEDLEY_COMPCERT_VERSION\n#undef JSON_HEDLEY_COMPCERT_VERSION_CHECK\n#undef JSON_HEDLEY_CONCAT\n#undef JSON_HEDLEY_CONCAT3\n#undef JSON_HEDLEY_CONCAT3_EX\n#undef JSON_HEDLEY_CONCAT_EX\n#undef JSON_HEDLEY_CONST\n#undef JSON_HEDLEY_CONSTEXPR\n#undef JSON_HEDLEY_CONST_CAST\n#undef JSON_HEDLEY_CPP_CAST\n#undef JSON_HEDLEY_CRAY_VERSION\n#undef JSON_HEDLEY_CRAY_VERSION_CHECK\n#undef JSON_HEDLEY_C_DECL\n#undef JSON_HEDLEY_DEPRECATED\n#undef JSON_HEDLEY_DEPRECATED_FOR\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION\n#undef JSON_HEDLEY_DIAGNOSTIC_POP\n#undef JSON_HEDLEY_DIAGNOSTIC_PUSH\n#undef JSON_HEDLEY_DMC_VERSION\n#undef JSON_HEDLEY_DMC_VERSION_CHECK\n#undef JSON_HEDLEY_EMPTY_BASES\n#undef JSON_HEDLEY_EMSCRIPTEN_VERSION\n#undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK\n#undef JSON_HEDLEY_END_C_DECLS\n#undef JSON_HEDLEY_FLAGS\n#undef JSON_HEDLEY_FLAGS_CAST\n#undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE\n#undef JSON_HEDLEY_GCC_HAS_BUILTIN\n#undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE\n#undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE\n#undef JSON_HEDLEY_GCC_HAS_EXTENSION\n#undef JSON_HEDLEY_GCC_HAS_FEATURE\n#undef JSON_HEDLEY_GCC_HAS_WARNING\n#undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK\n#undef JSON_HEDLEY_GCC_VERSION\n#undef JSON_HEDLEY_GCC_VERSION_CHECK\n#undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE\n#undef JSON_HEDLEY_GNUC_HAS_BUILTIN\n#undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE\n#undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE\n#undef JSON_HEDLEY_GNUC_HAS_EXTENSION\n#undef JSON_HEDLEY_GNUC_HAS_FEATURE\n#undef JSON_HEDLEY_GNUC_HAS_WARNING\n#undef JSON_HEDLEY_GNUC_VERSION\n#undef JSON_HEDLEY_GNUC_VERSION_CHECK\n#undef JSON_HEDLEY_HAS_ATTRIBUTE\n#undef JSON_HEDLEY_HAS_BUILTIN\n#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE\n#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS\n#undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE\n#undef JSON_HEDLEY_HAS_EXTENSION\n#undef JSON_HEDLEY_HAS_FEATURE\n#undef JSON_HEDLEY_HAS_WARNING\n#undef JSON_HEDLEY_IAR_VERSION\n#undef JSON_HEDLEY_IAR_VERSION_CHECK\n#undef JSON_HEDLEY_IBM_VERSION\n#undef JSON_HEDLEY_IBM_VERSION_CHECK\n#undef JSON_HEDLEY_IMPORT\n#undef JSON_HEDLEY_INLINE\n#undef JSON_HEDLEY_INTEL_CL_VERSION\n#undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK\n#undef JSON_HEDLEY_INTEL_VERSION\n#undef JSON_HEDLEY_INTEL_VERSION_CHECK\n#undef JSON_HEDLEY_IS_CONSTANT\n#undef JSON_HEDLEY_IS_CONSTEXPR_\n#undef JSON_HEDLEY_LIKELY\n#undef JSON_HEDLEY_MALLOC\n#undef JSON_HEDLEY_MCST_LCC_VERSION\n#undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK\n#undef JSON_HEDLEY_MESSAGE\n#undef JSON_HEDLEY_MSVC_VERSION\n#undef JSON_HEDLEY_MSVC_VERSION_CHECK\n#undef JSON_HEDLEY_NEVER_INLINE\n#undef JSON_HEDLEY_NON_NULL\n#undef JSON_HEDLEY_NO_ESCAPE\n#undef JSON_HEDLEY_NO_RETURN\n#undef JSON_HEDLEY_NO_THROW\n#undef JSON_HEDLEY_NULL\n#undef JSON_HEDLEY_PELLES_VERSION\n#undef JSON_HEDLEY_PELLES_VERSION_CHECK\n#undef JSON_HEDLEY_PGI_VERSION\n#undef JSON_HEDLEY_PGI_VERSION_CHECK\n#undef JSON_HEDLEY_PREDICT\n#undef JSON_HEDLEY_PRINTF_FORMAT\n#undef JSON_HEDLEY_PRIVATE\n#undef JSON_HEDLEY_PUBLIC\n#undef JSON_HEDLEY_PURE\n#undef JSON_HEDLEY_REINTERPRET_CAST\n#undef JSON_HEDLEY_REQUIRE\n#undef JSON_HEDLEY_REQUIRE_CONSTEXPR\n#undef JSON_HEDLEY_REQUIRE_MSG\n#undef JSON_HEDLEY_RESTRICT\n#undef JSON_HEDLEY_RETURNS_NON_NULL\n#undef JSON_HEDLEY_SENTINEL\n#undef JSON_HEDLEY_STATIC_ASSERT\n#undef JSON_HEDLEY_STATIC_CAST\n#undef JSON_HEDLEY_STRINGIFY\n#undef JSON_HEDLEY_STRINGIFY_EX\n#undef JSON_HEDLEY_SUNPRO_VERSION\n#undef JSON_HEDLEY_SUNPRO_VERSION_CHECK\n#undef JSON_HEDLEY_TINYC_VERSION\n#undef JSON_HEDLEY_TINYC_VERSION_CHECK\n#undef JSON_HEDLEY_TI_ARMCL_VERSION\n#undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK\n#undef JSON_HEDLEY_TI_CL2000_VERSION\n#undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK\n#undef JSON_HEDLEY_TI_CL430_VERSION\n#undef JSON_HEDLEY_TI_CL430_VERSION_CHECK\n#undef JSON_HEDLEY_TI_CL6X_VERSION\n#undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK\n#undef JSON_HEDLEY_TI_CL7X_VERSION\n#undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK\n#undef JSON_HEDLEY_TI_CLPRU_VERSION\n#undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK\n#undef JSON_HEDLEY_TI_VERSION\n#undef JSON_HEDLEY_TI_VERSION_CHECK\n#undef JSON_HEDLEY_UNAVAILABLE\n#undef JSON_HEDLEY_UNLIKELY\n#undef JSON_HEDLEY_UNPREDICTABLE\n#undef JSON_HEDLEY_UNREACHABLE\n#undef JSON_HEDLEY_UNREACHABLE_RETURN\n#undef JSON_HEDLEY_VERSION\n#undef JSON_HEDLEY_VERSION_DECODE_MAJOR\n#undef JSON_HEDLEY_VERSION_DECODE_MINOR\n#undef JSON_HEDLEY_VERSION_DECODE_REVISION\n#undef JSON_HEDLEY_VERSION_ENCODE\n#undef JSON_HEDLEY_WARNING\n#undef JSON_HEDLEY_WARN_UNUSED_RESULT\n#undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG\n#undef JSON_HEDLEY_FALL_THROUGH\n\n\n\n#endif  // INCLUDE_NLOHMANN_JSON_HPP_\n"
  },
  {
    "path": "console/commands/helpers/jwk_generator/libs/uuid.hpp",
    "content": "// https://stackoverflow.com/a/60198074\n#pragma once\n\n#include \"common/utils/RandUtils.hh\"\n#include <sstream>\n\nnamespace jwk_generator\n{\nnamespace detail\n{\nstatic inline std::string generate_uuid_v4()\n{\n  std::stringstream ss;\n  int i;\n  ss << std::hex;\n\n  for (i = 0; i < 8; i++) {\n    ss << eos::common::getRandom(0, 15);\n  }\n\n  ss << \"-\";\n\n  for (i = 0; i < 4; i++) {\n    ss << eos::common::getRandom(0, 15);\n  }\n\n  ss << \"-4\";\n\n  for (i = 0; i < 3; i++) {\n    ss << eos::common::getRandom(0, 15);\n  }\n\n  ss << \"-\";\n  ss << eos::common::getRandom(8, 11);\n\n  for (i = 0; i < 3; i++) {\n    ss << eos::common::getRandom(0, 15);\n  }\n\n  ss << \"-\";\n\n  for (i = 0; i < 12; i++) {\n    ss << eos::common::getRandom(0, 15);\n  };\n\n  return ss.str();\n}\n};\n};\n"
  },
  {
    "path": "console/commands/helpers/jwk_generator/openssl_wrapper.hpp",
    "content": "#pragma once\n#include \"jwk_generator/c_resource.hpp\"\n\n#include \"openssl/evp.h\"\n#include <openssl/ec.h>\n#include <openssl/ecdsa.h>\n#include <openssl/rsa.h>\n#include <openssl/pem.h>\n\n#if OPENSSL_VERSION_NUMBER >= 0x30000000L // 3.0.0\n#define JWKGEN_OPENSSL_3_0\n#include <openssl/types.h>\n#include <openssl/core_names.h>\n#elif OPENSSL_VERSION_NUMBER < 0x10100000L // 1.1.0\n#define JWKGEN_OPENSSL_1_0\n#endif\n\nnamespace jwk_generator\n{\nnamespace openssl\n{\nusing EVPPKey = c_resource<EVP_PKEY, EVP_PKEY_free, EVP_PKEY_new>;\nusing ECGroup = c_resource<EC_GROUP, EC_GROUP_free>;\nusing BigNum = c_resource<BIGNUM, BN_free, BN_new>;\n\n#ifndef JWKGEN_OPENSSL_3_0\nusing ECKey = c_resource<EC_KEY, EC_KEY_free, EC_KEY_new>;\nusing RSA = c_resource<::RSA, RSA_free, RSA_new>;\n#endif\n};\n};\n"
  },
  {
    "path": "console/commands/native/CoreNativeCommands.cc",
    "content": "// ----------------------------------------------------------------------\n// File: CoreNativeCommands.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nclass HelpCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"help\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Display this text\";\n  }\n  bool\n  requiresMgm(const std::string&) const override\n  {\n    return false;\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    if (args.empty()) {\n      auto& all = CommandRegistry::instance().all();\n      fprintf(stderr, \"Available commands:\\n\");\n      // Make a copy and sort by command name\n      std::vector<IConsoleCommand*> sorted(all.begin(), all.end());\n      std::sort(sorted.begin(), sorted.end(),\n                [](const IConsoleCommand* a, const IConsoleCommand* b) {\n                  return std::string(a->name()) < std::string(b->name());\n                });\n      for (auto* c : sorted) {\n        fprintf(stderr, \"  %-16s %s\\n\", c->name(), c->description());\n      }\n      return 0;\n    }\n    // Print detailed help for a specific command\n    IConsoleCommand* cmd = CommandRegistry::instance().find(args[0].c_str());\n    if (!cmd) {\n      fprintf(stderr, \"error: unknown command '%s'\\n\", args[0].c_str());\n      return (global_retc = EINVAL);\n    }\n    cmd->printHelp();\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"usage: help [command]\\n\");\n  }\n};\n\nclass ToggleFlagCommand : public IConsoleCommand {\npublic:\n  enum Which { JSON, SILENT, TIMING };\n  ToggleFlagCommand(const char* n, const char* d, Which w)\n      : mName(n), mDesc(d), mWhich(w)\n  {\n  }\n  const char*\n  name() const override\n  {\n    return mName.c_str();\n  }\n  const char*\n  description() const override\n  {\n    return mDesc.c_str();\n  }\n  bool\n  requiresMgm(const std::string&) const override\n  {\n    return false;\n  }\n  int\n  run(const std::vector<std::string>&, CommandContext&) override\n  {\n    switch (mWhich) {\n    case JSON:\n      ::json = (!::json);\n      gGlobalOpts.mJsonFormat = ::json;\n      if (::json) {\n        ::interactive = false;\n        ::global_highlighting = false;\n      }\n      if (!::silent) {\n        fprintf(stderr, \"json=%d\\n\", ::json);\n      }\n      break;\n    case SILENT:\n      ::silent = (!::silent);\n      break;\n    case TIMING:\n      ::timing = (!::timing);\n      break;\n    }\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n  }\n\nprivate:\n  std::string mName;\n  std::string mDesc;\n  Which mWhich;\n};\n\nclass QuitCommand : public IConsoleCommand {\npublic:\n  QuitCommand(const char* n) : mName(n) {}\n  const char*\n  name() const override\n  {\n    return mName.c_str();\n  }\n  const char*\n  description() const override\n  {\n    return \"Exit from EOS console\";\n  }\n  bool\n  requiresMgm(const std::string&) const override\n  {\n    return false;\n  }\n  int\n  run(const std::vector<std::string>&, CommandContext&) override\n  {\n    ::done = 1;\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n  }\n\nprivate:\n  std::string mName;\n};\n} // namespace\n\nvoid\nRegisterCoreNativeCommands()\n{\n  CommandRegistry::instance().reg(std::make_unique<HelpCommand>());\n  class HelpAlias : public HelpCommand {\n  public:\n    const char*\n    name() const override\n    {\n      return \"?\";\n    }\n  };\n  CommandRegistry::instance().reg(std::make_unique<HelpAlias>());\n  CommandRegistry::instance().reg(std::make_unique<ToggleFlagCommand>(\n      \"json\", \"Toggle JSON output flag for stdout\", ToggleFlagCommand::JSON));\n  CommandRegistry::instance().reg(std::make_unique<ToggleFlagCommand>(\n      \"silent\", \"Toggle silent flag for stdout\", ToggleFlagCommand::SILENT));\n  CommandRegistry::instance().reg(std::make_unique<ToggleFlagCommand>(\n      \"timing\", \"Toggle timing flag for execution time measurement\",\n      ToggleFlagCommand::TIMING));\n  CommandRegistry::instance().reg(std::make_unique<QuitCommand>(\"quit\"));\n  CommandRegistry::instance().reg(std::make_unique<QuitCommand>(\"exit\"));\n  CommandRegistry::instance().reg(std::make_unique<QuitCommand>(\".q\"));\n}\n"
  },
  {
    "path": "console/commands/native/LegacySymbols.cc",
    "content": "// ----------------------------------------------------------------------\n// File: LegacySymbols.cc\n// Purpose: Provide legacy com_* symbol definitions (no registrations)\n// ----------------------------------------------------------------------\n\n// Include only legacy implementations still referenced by native wrappers\n// Direct com_* dependencies from native commands\n// com_cp/com_cat: provided by cp-cmd-native.cc\n// com_rclone: provided by rclone-cmd-native.cc\n// com_squash: provided by squash-cmd-native.cc (no external callers)\n"
  },
  {
    "path": "console/commands/native/access-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: access-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeAccessHelp()\n{\n  std::ostringstream oss;\n  oss << \"Usage: access ban|unban|allow|unallow|set|rm|ls [OPTIONS]\\n\"\n        \"'[eos] access ..' provides the access interface of EOS to \"\n        \"allow/disallow hosts/domains and/or users\\n\\n\"\n        \"Subcommands:\\n\"\n        \"access ban user|group|host|domain <identifier> : ban user, \"\n        \"group, host or domain with identifier <identifier>\\n\"\n        \"\\t <identifier> : can be a user name, user id, group name, group id, \"\n        \"hostname or IP or domainname\\n\\n\"\n        \"access unban user|group|host|domain <identifier> : unban user, \"\n        \"group, host or domain with identifier <identifier>\\n\"\n        \"\\t <identifier> : can be a user name, user id, group name, group id, \"\n        \"hostname or IP or domainname\\n\\n\"\n        \"access allow user|group|host|domain <identifier> : allows this \"\n        \"user, group, host or domain access\\n\"\n        \"\\t <identifier> : can be a user name, user id, group name, group id, \"\n        \"hostname or IP or domainname\\n\\n\"\n        \"access unallow user|group|host|domain <identifier> : unallows \"\n        \"this user,group, host or domain access\\n\"\n        \"\\t <identifier> : can be a user name, user id, group name, group id, \"\n        \"hostname or IP or domainname\\n\\n\"\n        \"\\t HINT: if you add any 'allow' the instance allows only the listed \"\n        \"identity. A banned identifier will still overrule an allowed \"\n        \"identifier!\\n\\n\"\n        \"access set redirect <target-host> [r|w|ENOENT|ENONET] : \"\n        \"allows to set a global redirection to <target-host>\\n\"\n        \"\\t <target-host>      : hostname to which all requests get \"\n        \"redirected\\n\"\n        \"\\t         [r|w]      : optional set a redirect for read/write \"\n        \"requests seperatly\\n\"\n        \"\\t      [ENOENT]      : optional set a redirect if a file is not \"\n        \"existing\\n\"\n        \"\\t      [ENONET]      : optional set a redirect if a file is offline\\n\"\n        \"\\t                      <taget-hosts> can be structured like \"\n        \"<host>:<port[:<delay-in-ms>] where <delay> holds each request for a \"\n        \"given time before redirecting\\n\\n\"\n        \"access set stall <stall-time> [r|w|ENOENT|ENONET] : \"\n        \"allows to set a global stall time\\n\"\n        \"\\t <stall-time> : time in seconds after which clients should \"\n        \"rebounce\\n\"\n        \"\\t         [r|w]      : optional set stall time for read/write \"\n        \"requests seperatly\\n\"\n        \"\\t      [ENOENT]      : optional set stall time if a file is not \"\n        \"existing\\n\"\n        \"\\t      [ENONET]      : optional set stall time if a file is offline\\n\"\n        \"\\n\"\n        \"access set limit <frequency> rate:{user,group}:{name}:<counter>\\n\"\n        \"\\t rate:{user:group}:{name}:<counter> : stall the defined user group \"\n        \"for 5s if the <counter> exceeds a frequency of <frequency> in a 5s \"\n        \"interval\\n\"\n        \"\\t                                      - the instantaneous rate can \"\n        \"exceed this value by 33%%\\n\"\n        \"\\t              rate:user:*:<counter> : apply to all users based on \"\n        \"user counter\\n\"\n        \"\\t              rate:group:*:<counter>: apply to all groups based on \"\n        \"group counter\\n\"\n        \"\\t                                      set <frequency> to 0 (zero) \"\n        \"to continuously stall the user or group\\n\\n\"\n        \"access set limit <frequency> threads:{*,max,<uid/username>}\\n\"\n        \"\\t             threads:max            : set the maximum number of \"\n        \"threads running in parallel\\n\"\n        \"\\t             threads:*              : set the default thread pool \"\n        \"limit for each user\\n\"\n        \"\\t             threads:<uid/username> : set a specific thread pool \"\n        \"limit for user <username/uid>\\n\\n\"\n        \"access set limit <nfiles> rate:user:{name}:FindFiles :\\n\\tset find \"\n        \"query limit to <nfiles> for user {name}\\n\\n\"\n        \"access set limit <ndirs> rate:user:{name}:FindDirs:\\n\\tset find query \"\n        \"limit to <ndirs> for user {name}\\n\\n\"\n        \"access set limit <nfiles> rate:group:{name}:FindFiles :\\n\\tset find \"\n        \"query limit to <nfiles> for group {name}\\n\\n\"\n        \"access set limit <ndirs> rate:group:{name}:FindDirs :\\n\\tset find \"\n        \"query limit to <ndirss> for group {name}\\n\\n\"\n        \"access set limit <nfiles> rate:user:*:FindFiles :\\n\\tset default find \"\n        \"query limit to <nfiles> for everybody\\n\\n\"\n        \"access set limit <ndirs> rate:user:*:FindDirs :\\n\\tset default find \"\n        \"query limit to <ndirss> for everybody\\n\\n\"\n        \"\\t HINT : rule strength => user-limit >> group-limit >> \"\n        \"wildcard-limit\\n\\n\"\n        \"access rm redirect [r|w|ENOENT|ENONET] : removes global \"\n        \"redirection\\n\\n\"\n        \"access rm stall [r|w|ENOENT|ENONET] : removes global \"\n        \"stall time\\n\\n\"\n        \"access rm limit rate:{user,group}:{name}:<counter> : remove rate \"\n        \"limitation\\n\\n\"\n        \"access rm limit threads:{max,*,<uid/username>} : remove thread pool \"\n        \"limit\\n\\n\"\n        \"access ls [-m] [-n] : print banned,unbanned user,group, hosts\\n\"\n        \"\\t -m : output in monitoring format with <key>=<value>\\n\"\n        \"\\t -n : don't translate uid/gids to names\\n\\n\"\n        \"Examples:\\n\"\n        \" access ban host foo                            : Ban host foo\\n\"\n        \" access ban domain bar                          : Ban domain bar\\n\"\n        \" access allow domain nobody@bar                 : Allows user nobody \"\n        \"from domain bar\\n\"\n        \" access allow domain -                          : use domain allow as \"\n        \"whitelist - e.g. nobody@bar will additionally allow the nobody user \"\n        \"from domain bar!\\n\"\n        \" access allow domain bar                        : Allow only domain \"\n        \"bar\\n\"\n        \" access set redirect foo                        : Redirect all \"\n        \"requests to host foo\\n\"\n        \" access set redirect foo:1094:1000              : Redirect all \"\n        \"requests to host foo:1094 and hold each reqeust for 1000ms\\n\"\n        \" access rm redirect                             : Remove redirection \"\n        \"to previously defined host foo\\n\"\n        \" access set stall 60                            : Stall all clients \"\n        \"by 60 seconds\\n\"\n        \" access ls                                      : Print all defined \"\n        \"access rules\\n\"\n        \" access set limit 100  rate:user:*:OpenRead     : Limit the open for \"\n        \"read rate to a frequency of 100 Hz for all users\\n\"\n        \" access set limit 0    rate:user:ab:OpenRead    : Limit the open for \"\n        \"read rate for the ab user to 0 Hz, to continuously stall it\\n\"\n        \" access set limit 2000 rate:group:zp:Stat       : Limit the stat rate \"\n        \"for the zp group to 2kHz\\n\"\n        \" access set limit 500 threads:*                 : Limit the thread \"\n        \"pool usage to 500 threads per user\\n\"\n        \" access rm limit rate:user:*:OpenRead           : Removes the defined \"\n        \"limit\\n\"\n        \" access rm limit threads:*                      : Removes the default \"\n        \"per user thread pool limit\\n\"\n        \" access stallhosts add stall foo*.bar           : Add foo*.bar to the \"\n        \"list of hosts which are stalled by limit rules (white list)\\n\"\n        \" access stallhosts remove stall foo*.bar        : Remove foo*.bar \"\n        \"from the list of hosts which are stalled by limit rules (white list)\\n\"\n        \" access stallhosts add nostall foo*.bar         : Add foo*.bar to the \"\n        \"list of hosts which are never stalled by limit rules (black list)\\n\"\n        \" access stallhosts remove nostall foo*.bar      : Remove foo*.bar \"\n        \"from the list of hosts which are never stalled by limit rules (black \"\n        \"list)\\n\";\n  return oss.str();\n}\n\nvoid ConfigureAccessApp(CLI::App& app)\n{\n  app.name(\"access\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeAccessHelp();\n      }));\n}\n\nclass AccessProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"access\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Access Interface\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    // access ban|unban|allow|unallow|set|rm|ls ...\n    if (args.empty() || wants_help(args[0].c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    const std::string& sub = args[0];\n    XrdOucString in = \"mgm.cmd=access\";\n    size_t i = 1;\n    auto next = [&](std::string& out) -> bool {\n      if (i < args.size()) {\n        out = args[i++];\n        return true;\n      }\n      return false;\n    };\n\n    auto finish = [&]() {\n      global_retc =\n          ctx.outputResult(ctx.clientCommand(in, true, nullptr), true);\n      return 0;\n    };\n\n    if (sub == \"ban\" || sub == \"unban\" || sub == \"allow\" || sub == \"unallow\") {\n      in += \"&mgm.subcmd=\";\n      in += sub.c_str();\n      std::string type, id;\n      if (!next(type) || !next(id)) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      if (type == \"host\") {\n        in += \"&mgm.access.host=\";\n        in += id.c_str();\n      } else if (type == \"domain\") {\n        in += \"&mgm.access.domain=\";\n        in += id.c_str();\n      } else if (type == \"user\") {\n        in += \"&mgm.access.user=\";\n        in += id.c_str();\n      } else if (type == \"group\") {\n        in += \"&mgm.access.group=\";\n        in += id.c_str();\n      } else {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      return finish();\n    }\n\n    if (sub == \"ls\") {\n      in += \"&mgm.subcmd=ls\";\n      // options: -m, -n\n      CLI::App app;\n      app.set_help_flag(\"\");\n      bool opt_m = false;\n      bool opt_n = false;\n      app.add_flag(\"-m\", opt_m, \"monitor format\");\n      app.add_flag(\"-n\", opt_n, \"numeric ids\");\n      std::vector<std::string> rest(args.begin() + 1, args.end());\n      std::vector<std::string> cli_args = rest;\n      std::reverse(cli_args.begin(), cli_args.end());\n      try {\n        app.parse(cli_args);\n      } catch (const CLI::ParseError&) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      std::string option;\n      if (opt_m)\n        option += \"m\";\n      if (opt_n)\n        option += \"n\";\n      if (!option.empty()) {\n        in += \"&mgm.access.option=\";\n        in += option.c_str();\n      }\n      return finish();\n    }\n\n    if (sub == \"set\" || sub == \"rm\") {\n      in += \"&mgm.subcmd=\";\n      in += sub.c_str();\n      std::string type;\n      if (!next(type)) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      std::string id;\n      bool has_id = next(id);\n      if (!has_id) {\n        if (sub == \"rm\") {\n          id = \"dummy\";\n        } else {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n      }\n      std::string rtype;\n      if (sub == \"rm\") {\n        if (has_id) {\n          rtype = id;\n        }\n      } else {\n        next(rtype);\n      }\n\n      if (type == \"redirect\") {\n        in += \"&mgm.access.redirect=\";\n        in += id.c_str();\n      } else if (type == \"stall\") {\n        in += \"&mgm.access.stall=\";\n        in += id.c_str();\n      } else if (type == \"limit\") {\n        in += \"&mgm.access.stall=\";\n        in += id.c_str();\n        if (rtype.empty()) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        if ((rtype.rfind(\"rate:user:\", 0) == 0 ||\n             rtype.rfind(\"rate:group:\", 0) == 0) &&\n            (rtype.find(':', 11) != std::string::npos)) {\n          in += \"&mgm.access.type=\";\n          in += rtype.c_str();\n        } else if (!rtype.empty()) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        return finish();\n      } else {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n\n      if (!rtype.empty()) {\n        if (rtype == \"r\" || rtype == \"w\" || rtype == \"ENONET\" ||\n            rtype == \"ENOENT\") {\n          in += \"&mgm.access.type=\";\n          in += rtype.c_str();\n        } else {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n      }\n      return finish();\n    }\n\n    printHelp();\n    global_retc = EINVAL;\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureAccessApp(app);\n    const std::string help = app.help();\n    fprintf(stderr, \"%s\", help.c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterAccessProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<AccessProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/accounting-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: accounting-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeAccountingHelp()\n{\n  std::ostringstream oss;\n  oss << \"Usage: accounting report [-f]\\n\"\n      << \"       accounting config -e [<expired>] -i [<invalid>]\\n\\n\"\n      << \"  report  prints accounting report in JSON, data is served from \"\n         \"cache if possible\\n\"\n      << \"          -f  force synchronous report instead of using cache\\n\\n\"\n      << \"  config  configure caching behaviour\\n\"\n      << \"          -e  expiry time in minutes (default 10)\\n\"\n      << \"          -i  invalidity time in minutes, must be > expiry\\n\";\n  return oss.str();\n}\n\nvoid ConfigureAccountingApp(CLI::App& app)\n{\n  app.name(\"accounting\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeAccountingHelp();\n      }));\n}\n\nclass AccountingCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"accounting\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Accounting tools\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    if (args.empty()) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    const std::string& sub = args[0];\n    XrdOucString in = \"mgm.cmd=accounting\";\n    if (sub == \"report\") {\n      in += \"&mgm.subcmd=report\";\n      CLI::App app;\n      app.set_help_flag(\"\");\n      app.allow_extras();\n      bool opt_f = false;\n      app.add_flag(\"-f\", opt_f, \"force synchronous report\");\n      std::vector<std::string> rest(args.begin() + 1, args.end());\n      std::vector<std::string> cli_args = rest;\n      std::reverse(cli_args.begin(), cli_args.end());\n      try {\n        app.parse(cli_args);\n      } catch (const CLI::ParseError&) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      if (opt_f) {\n        in += \"&mgm.option=f\";\n      }\n    } else if (sub == \"config\") {\n      in += \"&mgm.subcmd=config\";\n      // -e <min> -i <min>\n      std::vector<std::string> rest(args.begin() + 1, args.end());\n      for (size_t i = 0; i < rest.size();) {\n        const std::string& tok = rest[i];\n        if (tok == \"-e\" || tok == \"-i\") {\n          if (i + 1 >= rest.size()) {\n            printHelp();\n            global_retc = EINVAL;\n            return 0;\n          }\n          const std::string& val = rest[i + 1];\n          if (tok == \"-e\")\n            in += \"&mgm.accounting.expired=\";\n          else\n            in += \"&mgm.accounting.invalid=\";\n          in += val.c_str();\n          i += 2;\n        } else {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n      }\n    } else {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    global_retc = ctx.outputResult(ctx.clientCommand(in, false, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureAccountingApp(app);\n    const std::string help = app.help();\n    fprintf(stderr, \"%s\", help.c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterAccountingNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<AccountingCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/acl-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: acl-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/commands/helpers/AclHelper.hh\"\n#include <CLI/CLI.hpp>\n#include <iomanip>\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeAclHelp()\n{\n  std::ostringstream oss;\n  oss << \"Usage: eos acl [-l|--list] [-R|--recursive]\"\n      << \" [-p|--position <pos>] [-f|--front] \"\n      << \"[--sys|--user] [<rule>] <identifier>\" << std::endl\n      << \"  atomically set and modify ACLs for the given directory path/sub-tree\"\n      << std::endl\n      << std::endl\n      << \"  -h, --help      : print help message\" << std::endl\n      << \"  -R, --recursive : apply to directories recursively\" << std::endl\n      << \"  -l, --list      : list ACL rules\" << std::endl\n      << \"  -p, --position  : add the acl rule at specified position\" << std::endl\n      << \"  -f, --front     : add the acl rule at the front position\" << std::endl\n      << \"      --user      : handle user.acl rules on directory\" << std::endl\n      << \"      --sys       : handle sys.acl rules on directory - admin only\\n\"\n      << std::endl\n      << \"  <identifier> can be one of <path>|cid:<cid-dec>|cxid:<cid-hex>\\n\"\n      << std::endl\n      << \"  <rule> is created similarly to chmod rules. Every rule begins with\"\n      << std::endl\n      << \"    [u|g|egroup] followed by \\\":\\\" or \\\"=\\\" and an identifier.\"\n      << std::endl\n      << \"    \\\":\\\" is used to for modifying permissions while\" << std::endl\n      << \"    \\\"=\\\" is used for setting/overwriting permissions.\" << std::endl\n      << \"    When modifying permissions every ACL flag can be added with\"\n      << std::endl\n      << \"    \\\"+\\\" or removed with \\\"-\\\".\" << std::endl\n      << \"    By default rules are appended at the end of acls\" << std::endl\n      << \"    This ordering can be changed via --position flag\" << std::endl\n      << \"    which will add the new rule at a given position starting at 1 or\"\n      << std::endl\n      << \"    the --front flag which adds the rule at the front instead\"\n      << std::endl\n      << std::endl\n      << \"Examples:\" << std::endl\n      << \"  acl --user u:1001=rwx /eos/dev/\" << std::endl\n      << \"    Set ACLs for user id 1001 to rwx\" << std::endl\n      << \"  acl --user u:1001:-w /eos/dev\" << std::endl\n      << \"    Remove \\'w\\' flag for user id 1001\" << std::endl\n      << \"  acl --user u:1001:+m /eos/dev\" << std::endl\n      << \"    Add change mode permission flag for user id 1001\" << std::endl\n      << \"  acl --user u:1010= /eos/dev\" << std::endl\n      << \"    Remove all ACls for user id 1001\" << std::endl\n      << \"  acl --front --user u:1001=rwx /eos/dev\" << std::endl\n      << \"     Add the user id 1001 rule to the front of ACL rules\" << std::endl;\n  return oss.str();\n}\n\nvoid ConfigureAclApp(CLI::App& app)\n{\n  app.name(\"acl\");\n  app.description(\"Acl Interface\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeAclHelp();\n      }));\n}\n\nclass AclProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"acl\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Acl Interface\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      if (args[i].find(' ') != std::string::npos)\n        oss << std::quoted(args[i]);\n      else\n        oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    AclHelper acl(*ctx.globalOpts);\n    if (!acl.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = acl.Execute(true, true);\n    return global_retc;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureAclApp(app);\n    fprintf(stderr, \"%s\", app.help().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterAclProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<AclProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/archive-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: archive-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <XrdOuc/XrdOucString.hh>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeArchiveHelp()\n{\n  std::ostringstream oss;\n  oss << \"Usage: archive <subcmd> [args...]\\n\\n\"\n      << \"Subcommands:\\n\"\n      << \"  create <path>                          create \"\n         \"archive file\\n\"\n      << \"  put [--retry] <path>                   copy files from EOS to \"\n         \"archive location\\n\"\n      << \"  get [--retry] <path>                   recall archive back to \"\n         \"EOS\\n\"\n      << \"  purge [--retry] <path>                 purge files on disk\\n\"\n      << \"  transfers [all|put|get|purge|job_uuid] show status of running \"\n         \"jobs\\n\"\n      << \"  list [<path>]                          show status of archived \"\n         \"directories in subtree\\n\"\n      << \"  kill <job_uuid>                         kill transfer\\n\"\n      << \"  delete <path>                           delete files from tape, \"\n         \"keeping the ones on disk\\n\"\n      << \"  help [--help|-h]                       display help message\\n\";\n  return oss.str();\n}\n\nvoid ConfigureArchiveApp(CLI::App& app,\n                         std::string& subcmd,\n                         bool& retry,\n                         std::string& arg)\n{\n  app.name(\"archive\");\n  app.description(\"Archive Interface\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeArchiveHelp();\n      }));\n  app.add_option(\"subcmd\", subcmd,\n                 \"create|put|get|purge|delete|transfers|list|kill|help\")\n      ->required();\n  app.add_flag(\"--retry\", retry, \"retry on failure\");\n  app.add_option(\"arg\", arg, \"path, job_uuid, or option\");\n}\n\nclass ArchiveCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"archive\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Archive Interface\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (args.empty() || wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    std::string subcmd;\n    bool retry = false;\n    std::string arg;\n    ConfigureArchiveApp(app, subcmd, retry, arg);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    std::ostringstream in_cmd;\n    in_cmd << \"mgm.cmd=archive&mgm.subcmd=\" << subcmd;\n\n    if (subcmd == \"create\") {\n      std::string p = arg.empty() ? gPwd.c_str() : arg;\n      XrdOucString ap = abspath(p.c_str());\n      in_cmd << \"&mgm.archive.path=\" << ap.c_str();\n    } else if (subcmd == \"put\" || subcmd == \"get\" || subcmd == \"purge\" ||\n               subcmd == \"delete\") {\n      std::string p = arg.empty() ? gPwd.c_str() : arg;\n      if (retry)\n        in_cmd << \"&mgm.archive.option=r\";\n      XrdOucString ap = abspath(p.c_str());\n      in_cmd << \"&mgm.archive.path=\" << ap.c_str();\n    } else if (subcmd == \"transfers\") {\n      if (arg.empty())\n        in_cmd << \"&mgm.archive.option=all\";\n      else\n        in_cmd << \"&mgm.archive.option=\" << arg;\n    } else if (subcmd == \"list\") {\n      if (arg.empty())\n        in_cmd << \"&mgm.archive.path=/\";\n      else if (arg == \"./\" || arg == \".\") {\n        XrdOucString ap = abspath(gPwd.c_str());\n        in_cmd << \"&mgm.archive.path=\" << ap.c_str();\n      } else\n        in_cmd << \"&mgm.archive.path=\" << arg;\n    } else if (subcmd == \"kill\") {\n      if (arg.empty()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      in_cmd << \"&mgm.archive.option=\" << arg;\n    } else if (subcmd == \"help\") {\n      printHelp();\n      return 0;\n    } else {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    XrdOucString in = in_cmd.str().c_str();\n    global_retc = ctx.outputResult(ctx.clientCommand(in, false, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    std::string subcmd;\n    bool retry = false;\n    std::string arg;\n    ConfigureArchiveApp(app, subcmd, retry, arg);\n    const std::string help = app.help();\n    fprintf(stderr, \"%s\", help.c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterArchiveNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<ArchiveCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/attr-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: attr-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/SymKeys.hh\"\n#include \"common/Utils.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <XrdOuc/XrdOucString.hh>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <string>\n#include <utility>\n#include <vector>\n\nnamespace {\nstd::string MakeAttrHelp()\n{\n  std::ostringstream oss;\n  oss << \"Usage: attr [OPTIONS] ls|set|get|rm ...\\n\\n\";\n  oss << \"'[eos] attr ..' provides the extended attribute interface for \"\n         \"directories in EOS.\\n\\n\";\n  oss << \"Options:\\n\"\n      << \"  attr [-r] ls <identifier>\\n\"\n      << \"      List attributes of path\\n\"\n      << \"      -r : list recursive on all directory children\\n\"\n      << \"  attr [-r] set [-c] <key>=<value> <identifier>\\n\"\n      << \"      Use <key>= to set an empty value (e.g. sys.acl=)\\n\"\n      << \"      Set attributes of path (-r recursive, -c only if absent)\\n\"\n      << \"  attr [-r] set default=replica|raiddp|raid5|raid6|archive|qrain \"\n         \"<identifier>\\n\"\n      << \"      Set EOS default layout attributes for the path\\n\"\n      << \"  attr [-r] [-V] get <key> <identifier>\\n\"\n      << \"      Get attributes of path (-r recursive, -V only print value)\\n\"\n      << \"  attr [-r] rm <key> <identifier>\\n\"\n      << \"      Delete attributes of path (-r recursive)\\n\"\n      << \"  attr [-r] link <origin> <identifier>\\n\"\n      << \"      Link attributes of <origin> under <identifier> (-r recursive)\\n\"\n      << \"  attr [-r] unlink <identifier>\\n\"\n      << \"      Remove attribute link of <identifier> (-r recursive)\\n\"\n      << \"  attr [-r] fold <identifier>\\n\"\n      << \"      Fold attributes of <identifier> when attr link is defined\\n\"\n      << \"      (identical attributes are removed locally)\\n\"\n      << \"\\n\"\n      << \"Remarks:\\n\";\n  oss << \"         <identifier> = \"\n         \"<path>|fid:<fid-dec>|fxid:<fid-hex>|cid:<cid-dec>|cxid:<cid-hex>\\n\"\n      << \"                        deprecated pid:<pid-dec>|pxid:<pid-hex>\\n\";\n  oss << \"         If <key> starts with 'sys.' you have to be member of the \"\n         \"sudoers group to see these attributes or modify.\\n\";\n  oss << \"\\nAdministrator Variables:\\n\";\n  oss << \"         sys.forced.space=<space>              : enforces to use \"\n         \"<space>    [configuration dependent]\\n\";\n  oss << \"         sys.forced.group=<group>              : enforces to use \"\n         \"<group>, where <group> is the numerical index of <space>.<n>    \"\n         \"[configuration dependent]\\n\";\n  oss << \"         sys.forced.layout=<layout>            : enforces to use \"\n         \"<layout>   [<layout>=(plain,replica,raid5,raid6,archive,qrain)]\\n\";\n  oss << \"         sys.forced.checksum=<checksum>        : enforces to use \"\n         \"file-level checksum <checksum>\\n\";\n  oss << \"                                              <checksum> = \"\n         \"adler,crc32,crc32c,md5,sha\\n\";\n  oss << \"         sys.forced.blockchecksum=<checksum>   : enforces to use \"\n         \"block-level checksum <checksum>\\n\";\n  oss << \"                                              <checksum> = \"\n         \"adler,crc32,crc32c,md5,sha\\n\";\n  oss << \"         sys.forced.nstripes=<n>               : enforces to use <n> \"\n         \"stripes[<n>= 1..16]\\n\";\n  oss << \"         sys.forced.blocksize=<w>              : enforces to use a \"\n         \"blocksize of <w> - <w> can be 4k,64k,128k,256k or 1M \\n\";\n  oss << \"         sys.forced.placementpolicy=<policy>[:geotag] : enforces to \"\n         \"use replica/stripe placement policy <policy> [<policy>=\"\n         \"{scattered|hybrid:<geotag>|gathered:<geotag>}]\\n\";\n  oss << \"         sys.forced.nouserplacementpolicy=1    : disables user \"\n         \"defined replica/stripe placement policy\\n\";\n  oss << \"         sys.forced.nouserlayout=1             : disables the user \"\n         \"settings with user.forced.<xxx>\\n\";\n  oss << \"         sys.forced.nofsselection=1            : disables user \"\n         \"defined filesystem selection with environment variables for reads\\n\";\n  oss << \"         sys.forced.bookingsize=<bytes>        : set's the number of \"\n         \"bytes which get for each new created replica\\n\";\n  oss << \"         sys.forced.minsize=<bytes>            : set's the minimum \"\n         \"number of bytes a file to be stored must have\\n\";\n  oss << \"         sys.forced.maxsize=<bytes>            : set's the maximum \"\n         \"number of bytes a file to be stored can have\\n\";\n  oss << \"         sys.forced.atomic=1                   : if present enforce \"\n         \"atomic uploads e.g. files appear only when their upload is complete - \"\n         \"during the upload they have the name <dirname>/.<basename>.<uuid>\\n\";\n  oss << \"         sys.forced.leasetime=86400            : allows to overwrite \"\n         \"the eosxd client provided leasetime with a new value\\n\";\n  oss << \"         sys.forced.iotype=direct|sync|dsync|csync\"\n      << \"                                               : force the given \"\n         \"iotype for that directory\\n\";\n  oss << \"         sys.mtime.propagation=1               : if present a change \"\n         \"under this directory propagates an mtime change up to all parents \"\n         \"until the attribute is not present anymore\\n\";\n  oss << \"         sys.allow.oc.sync=1                   : if present, \"\n         \"OwnCloud clients can sync pointing to this subtree\\n\";\n  oss << \"\\n\";\n  oss << \"         sys.lru.expire.empty=<age>            : delete empty \"\n         \"directories older than <age>\\n\";\n  oss << \"         sys.lru.expire.match=[match1:<age1>,match2:<age2>..]\\n\";\n  oss << \"                                               : defines the rule \"\n         \"that files with a given match will be removed if \\n\";\n  oss << \"                                                 they haven't been \"\n         \"accessed longer than <age> ago. <age> is defined like \"\n         \"3600,3600s,60min,1h,1mo,1y...\\n\";\n  oss << \"         sys.lru.lowwatermark=<low>\\n\";\n  oss << \"         sys.lru.highwatermark=<high>        : if the watermark \"\n         \"reaches more than <high> %%, files will be removed until the usage is \"\n         \"reaching <low> %%.\\n\";\n  oss << \"\\n\";\n  oss << \"         sys.lru.convert.match=[match1:<age1>,match2:<age2>,...]\\n\";\n  oss << \"                                                 defines the rule \"\n         \"that files with a given match will be converted to the layouts \"\n         \"defined by sys.conversion.<match> when their access time reaches \"\n         \"<age>.\\n\";\n  oss << \"\\n\";\n  oss << \"         sys.stall.unavailable=<sec>           : stall clients for \"\n         \"<sec> seconds if a needed file system is unavailable\\n\";\n  oss << \"         sys.redirect.enoent=<host[:port]>     : redirect clients \"\n         \"opening non existing files to <host[:port]>\\n\";\n  oss << \"         sys.redirect.enonet=<host[:port]>     : redirect clients \"\n         \"opening inaccessible files to <host[:port]>\\n\";\n  oss << \"         sys.recycle=....                      : define the recycle \"\n         \"bin - WARNING: use the 'recycle' interface\\n\";\n  oss << \"         sys.recycle.keeptime=<seconds>        : define the time how \"\n         \"long files stay in a recycle bin\\n\";\n  oss << \"         sys.recycle.keepratio=< 0 .. 1.0 >    : ratio of used/max \"\n         \"quota for space and inodes in the recycle bin\\n\";\n  oss << \"         sys.versioning=<n>                    : keep <n> versions \"\n         \"of a file\\n\";\n  oss << \"         sys.acl=<acllist>                     : set's an ACL\\n\";\n  oss << \"         sys.eval.useracl                      : enables the \"\n         \"evaluation of user acls\\n\";\n  oss << \"         sys.mask                              : masks all unix \"\n         \"access permissions\\n\";\n  oss << \"         sys.owner.auth=<owner-auth-list>      : set's additional \"\n         \"owner on a directory\\n\";\n  oss << \"         sys.attr.link=<directory>             : symbolic links for \"\n         \"attributes\\n\";\n  oss << \"         sys.http.index=<path>                 : show a static page \"\n         \"as directory index\\n\";\n  oss << \"         sys.accounting.*=<value>              : set accounting \"\n         \"attributes\\n\";\n  oss << \"         sys.proc=<opaque command>             : run arbitrary \"\n         \"command on accessing the file\\n\";\n  oss << \"\\nUser Variables:\\n\";\n  oss << \"         user.forced.space, user.forced.layout, user.forced.checksum, \"\n         \"etc. (s.a. Administrator Variables)\\n\";\n  oss << \"\\nExamples:\\n\";\n  oss << \"  attr set default=replica /eos/instance/2-replica\\n\";\n  oss << \"  attr set sys.forced.nstripes=10 /eos/instance/archive\\n\";\n  oss << \"  attr set sys.acl=g:xx::!d!u /eos/instance/no-update-deletion\\n\";\n  oss << \"  attr set sys.forced.atomic=1 /eos/dev/instance/atomic/\\n\";\n  oss << \"  attr set sys.attr.link=/eos/dev/origin-attr/ \"\n         \"/eos/dev/instance/attr-linked/\\n\";\n  return oss.str();\n}\n\nvoid ConfigureAttrApp(CLI::App& app,\n                     bool& opt_r,\n                     bool& opt_V,\n                     std::string& subcmd)\n{\n  app.name(\"attr\");\n  app.description(\"Attribute Interface\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeAttrHelp();\n      }));\n  app.add_flag(\"-r\", opt_r, \"recursive\");\n  app.add_flag(\"-V\", opt_V, \"only print value (for get)\");\n  app.add_option(\"subcmd\", subcmd, \"ls|set|get|rm|link|unlink|fold\")\n      ->required();\n}\n\nclass AttrCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"attr\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Attribute Interface\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (args.empty() || wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    bool opt_r = false;\n    bool opt_V = false;\n    std::string sub;\n    ConfigureAttrApp(app, opt_r, opt_V, sub);\n\n    // Insert \"--\" before first non-flag so CLI11 treats key=value (e.g. default=replica)\n    // as positional, not as --default=replica option\n    std::vector<std::string> cli_args;\n    cli_args.reserve(args.size() + 1);\n    size_t i = 0;\n    while (i < args.size() && (args[i] == \"-r\" || args[i] == \"-V\")) {\n      cli_args.push_back(args[i]);\n      ++i;\n    }\n    cli_args.push_back(\"--\");\n    for (; i < args.size(); ++i) {\n      cli_args.push_back(args[i]);\n    }\n    // CLI11 parses from the back; reverse so \"--\" is seen first, then set, default=replica, path\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    std::string optionStr;\n    if (opt_r)\n      optionStr += 'r';\n    if (opt_V)\n      optionStr += 'V';\n\n    std::vector<std::string> remaining = app.remaining();\n\n    size_t idx = 0;\n    std::string arg;\n\n    auto appendOption = [&](XrdOucString& target, const std::string& opt) {\n      if (!opt.empty()) {\n        target += \"&mgm.option=\";\n        target += opt.c_str();\n      }\n    };\n\n    auto send_set = [&](const std::string& key,\n                        const std::string& value,\n                        const std::string& path,\n                        bool conditional) -> int {\n      std::string opt = optionStr;\n      if (conditional && opt.find('c') == std::string::npos) {\n        opt.push_back('c');\n      }\n      XrdOucString cmd = \"mgm.cmd=attr&mgm.enc=b64\";\n      appendOption(cmd, opt);\n      XrdOucString k = key.c_str();\n      XrdOucString v = value.c_str();\n      if (key != \"default\" && key != \"sys.attr.link\") {\n        XrdOucString v64;\n        eos::common::SymKey::Base64(v, v64);\n        v = v64;\n      }\n      XrdOucString p = PathIdentifier(path.c_str(), true).c_str();\n      cmd += \"&mgm.subcmd=set&mgm.attr.key=\";\n      cmd += k;\n      cmd += \"&mgm.attr.value=\";\n      cmd += v;\n      cmd += \"&mgm.path=\";\n      cmd += p;\n      return ctx.outputResult(ctx.clientCommand(cmd, false, nullptr), true);\n    };\n\n    // Skip \"--\" which CLI11 adds to remaining when used as positional separator\n    while (idx < remaining.size() && remaining[idx] == \"--\") {\n      ++idx;\n    }\n    if (idx >= remaining.size()) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    arg = remaining[idx++];\n    if (sub == \"set\" && arg == \"-c\") {\n      if (optionStr.find('c') == std::string::npos) {\n        optionStr.push_back('c');\n      }\n      if (idx >= remaining.size()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      arg = remaining[idx++];\n    }\n\n    XrdOucString in = \"mgm.cmd=attr&mgm.enc=b64\";\n    appendOption(in, optionStr);\n\n    if (sub.empty() || arg.empty() ||\n        (sub != \"ls\" && sub != \"set\" && sub != \"get\" && sub != \"rm\" &&\n         sub != \"link\" && sub != \"unlink\" && sub != \"fold\")) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    if (sub == \"ls\") {\n      XrdOucString path = PathIdentifier(arg.c_str(), true).c_str();\n      in += \"&mgm.subcmd=ls&mgm.path=\";\n      in += path;\n    } else if (sub == \"set\" || sub == \"link\") {\n      std::string key = arg;\n      std::string value;\n      int epos = XrdOucString(key.c_str()).find(\"=\");\n      const bool has_equals = (sub == \"set\" && epos != STR_NPOS);\n      if (sub == \"link\") {\n        key = \"sys.attr.link\";\n        value = arg;\n      } else if (epos != STR_NPOS) {\n        value = key.substr(epos + 1);\n        key.erase(epos);\n      } else {\n        value.clear();\n      }\n\n      if (!value.empty() && value.rfind(\"\\\"\", 0) == 0 &&\n          value.size() >= 1 && value.back() != '\"') {\n        while (idx < remaining.size()) {\n          value += \" \";\n          value += remaining[idx];\n          if (!remaining[idx].empty() && remaining[idx].back() == '\"') {\n            ++idx;\n            break;\n          }\n          ++idx;\n        }\n      }\n\n      if (value.empty() && !has_equals) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n\n      std::string path;\n      if (sub == \"link\") {\n        if (idx >= remaining.size()) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        path = remaining[idx++];\n      } else {\n        if (idx >= remaining.size()) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        path = remaining[idx++];\n      }\n\n      if (path.empty()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n\n      if (key == \"default\") {\n        bool conditional = optionStr.find('c') != std::string::npos;\n        std::vector<std::pair<std::string, std::string>> defaults;\n        if (value == \"replica\") {\n          defaults = {{\"sys.forced.blocksize\", \"4k\"},\n                      {\"sys.forced.checksum\", \"adler\"},\n                      {\"sys.forced.layout\", \"replica\"},\n                      {\"sys.forced.nstripes\", \"2\"},\n                      {\"sys.forced.space\", \"default\"}};\n        } else if (value == \"raiddp\") {\n          defaults = {{\"sys.forced.blocksize\", \"1M\"},\n                      {\"sys.forced.checksum\", \"adler\"},\n                      {\"sys.forced.layout\", \"raiddp\"},\n                      {\"sys.forced.nstripes\", \"6\"},\n                      {\"sys.forced.space\", \"default\"},\n                      {\"sys.forced.blockchecksum\", \"crc32c\"}};\n        } else if (value == \"raid5\") {\n          defaults = {{\"sys.forced.blocksize\", \"1M\"},\n                      {\"sys.forced.checksum\", \"adler\"},\n                      {\"sys.forced.layout\", \"raid5\"},\n                      {\"sys.forced.nstripes\", \"5\"},\n                      {\"sys.forced.space\", \"default\"},\n                      {\"sys.forced.blockchecksum\", \"crc32c\"}};\n        } else if (value == \"raid6\") {\n          defaults = {{\"sys.forced.blocksize\", \"1M\"},\n                      {\"sys.forced.checksum\", \"adler\"},\n                      {\"sys.forced.layout\", \"raid6\"},\n                      {\"sys.forced.nstripes\", \"6\"},\n                      {\"sys.forced.space\", \"default\"},\n                      {\"sys.forced.blockchecksum\", \"crc32c\"}};\n        } else if (value == \"archive\") {\n          defaults = {{\"sys.forced.blocksize\", \"1M\"},\n                      {\"sys.forced.checksum\", \"adler\"},\n                      {\"sys.forced.layout\", \"archive\"},\n                      {\"sys.forced.nstripes\", \"8\"},\n                      {\"sys.forced.space\", \"default\"},\n                      {\"sys.forced.blockchecksum\", \"crc32c\"}};\n        } else if (value == \"qrain\") {\n          defaults = {{\"sys.forced.blocksize\", \"1M\"},\n                      {\"sys.forced.checksum\", \"adler\"},\n                      {\"sys.forced.layout\", \"qrain\"},\n                      {\"sys.forced.nstripes\", \"12\"},\n                      {\"sys.forced.space\", \"default\"},\n                      {\"sys.forced.blockchecksum\", \"crc32c\"}};\n        } else {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n\n        int retc = 0;\n        for (const auto& entry : defaults) {\n          retc = retc || send_set(entry.first, entry.second, path, conditional);\n        }\n        global_retc = retc;\n        return 0;\n      }\n\n      std::string encodedValue = value;\n      if (key != \"default\" && key != \"sys.attr.link\") {\n        XrdOucString in = value.c_str();\n        XrdOucString v64;\n        eos::common::SymKey::Base64(in, v64);\n        encodedValue = v64.c_str();\n      }\n\n      if (sub == \"set\" &&\n          XrdOucString(key.c_str()).endswith(\".forced.placementpolicy\")) {\n        XrdOucString in = encodedValue.c_str();\n        XrdOucString ouc_policy;\n        eos::common::SymKey::DeBase64(in, ouc_policy);\n        std::string policy = ouc_policy.c_str();\n        if (policy != \"scattered\" &&\n            policy.rfind(\"hybrid:\", 0) != 0 &&\n            policy.rfind(\"gathered:\", 0) != 0) {\n          fprintf(stderr, \"Error: placement policy '%s' is invalid\\n\",\n                  policy.c_str());\n          global_retc = EINVAL;\n          return 0;\n        }\n        if (policy != \"scattered\") {\n          std::string targetgeotag = policy.substr(policy.find(':') + 1);\n          std::string tmp_geotag = eos::common::SanitizeGeoTag(targetgeotag);\n          if (tmp_geotag != targetgeotag) {\n            fprintf(stderr, \"%s\\n\", tmp_geotag.c_str());\n            global_retc = EINVAL;\n            return 0;\n          }\n        }\n      }\n\n      XrdOucString k = key.c_str();\n      XrdOucString v = encodedValue.c_str();\n      XrdOucString p = PathIdentifier(path.c_str(), true).c_str();\n      in += \"&mgm.subcmd=set&mgm.attr.key=\";\n      in += k;\n      in += \"&mgm.attr.value=\";\n      in += v;\n      in += \"&mgm.path=\";\n      in += p;\n    } else if (sub == \"get\") {\n      std::string key = arg;\n      if (idx >= remaining.size()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      std::string path = remaining[idx++];\n      XrdOucString p = PathIdentifier(path.c_str(), true).c_str();\n      in += \"&mgm.subcmd=get&mgm.attr.key=\";\n      in += key.c_str();\n      in += \"&mgm.path=\";\n      in += p;\n    } else if (sub == \"fold\") {\n      std::string path = arg;\n      XrdOucString p = PathIdentifier(path.c_str(), true).c_str();\n      in += \"&mgm.subcmd=fold&mgm.path=\";\n      in += p;\n    } else if (sub == \"rm\" || sub == \"unlink\") {\n      std::string key = arg;\n      std::string path;\n      if (sub == \"unlink\") {\n        key = \"sys.attr.link\";\n        path = arg;\n      } else {\n        if (idx >= remaining.size()) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        path = remaining[idx++];\n      }\n      if (key.empty() || path.empty()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString p = PathIdentifier(path.c_str(), true).c_str();\n      in += \"&mgm.subcmd=rm&mgm.attr.key=\";\n      in += key.c_str();\n      in += \"&mgm.path=\";\n      in += p;\n    }\n\n    global_retc = ctx.outputResult(ctx.clientCommand(in, false, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    bool opt_r = false;\n    bool opt_V = false;\n    std::string subcmd;\n    ConfigureAttrApp(app, opt_r, opt_V, subcmd);\n    const std::string help = app.help();\n    fprintf(stderr, \"%s\", help.c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterAttrNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<AttrCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/backup-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: backup-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include <algorithm>\n#include \"console/ConsoleMain.hh\"\n#include <memory>\n#include <sstream>\n#include <sys/time.h>\n\nnamespace {\nstd::string MakeBackupHelp()\n{\n  std::ostringstream oss;\n  oss << \"Usage: backup <src_url> <dst_url> [options]\\n\\n\"\n      << \"Options:\\n\"\n      << \"  --ctime, --mtime <val>s|m|h|d  use the specified timewindow to \"\n         \"select entries for backup\\n\"\n      << \"  --excl_xattr val_1[,val_2]...   extended attributes which are not \"\n         \"enforced and not checked during verification\\n\";\n  return oss.str();\n}\n\nvoid ConfigureBackupApp(CLI::App& app,\n                        std::string& ctime,\n                        std::string& mtime,\n                        std::string& excl_xattr)\n{\n  app.name(\"backup\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeBackupHelp();\n      }));\n  app.add_option(\"--ctime\", ctime, \"ctime window\");\n  app.add_option(\"--mtime\", mtime, \"mtime window\");\n  app.add_option(\"--excl_xattr\", excl_xattr, \"exclude xattrs\");\n}\n\nclass BackupCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"backup\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Backup Interface\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    if (args.size() < 2) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    const std::string& src = args[0];\n    const std::string& dst = args[1];\n    std::ostringstream in_cmd;\n    in_cmd << \"mgm.cmd=backup&mgm.backup.src=\" << src\n           << \"&mgm.backup.dst=\" << dst;\n    // parse optional flags\n    CLI::App app;\n    app.allow_extras();\n    std::string ctime;\n    std::string mtime;\n    std::string excl_xattr;\n    ConfigureBackupApp(app, ctime, mtime, excl_xattr);\n    std::vector<std::string> rest;\n    if (args.size() > 2)\n      rest.assign(args.begin() + 2, args.end());\n    std::vector<std::string> cli_args = rest;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    auto append_window = [&](const char* key, const std::string& val) {\n      if (val.empty())\n        return;\n      char last = val.back();\n      long seconds = 0;\n      if (last == 's')\n        seconds = 1;\n      else if (last == 'm')\n        seconds = 60;\n      else if (last == 'h')\n        seconds = 3600;\n      else if (last == 'd')\n        seconds = 24 * 3600;\n      else {\n        printHelp();\n        global_retc = EINVAL;\n        return;\n      }\n      long v = strtol(val.c_str(), nullptr, 10);\n      if (v == 0L) {\n        printHelp();\n        global_retc = EINVAL;\n        return;\n      }\n      struct timeval tv;\n      if (gettimeofday(&tv, NULL)) {\n        fprintf(stderr, \"Error getting current timestamp\\n\");\n        global_retc = EINVAL;\n        return;\n      }\n      in_cmd << \"&mgm.backup.ttime=\" << key\n             << \"&mgm.backup.vtime=\" << (tv.tv_sec - v * seconds);\n    };\n    if (!ctime.empty())\n      append_window(\"ctime\", ctime);\n    if (!mtime.empty())\n      append_window(\"mtime\", mtime);\n    if (!excl_xattr.empty()) {\n      in_cmd << \"&mgm.backup.excl_xattr=\" << excl_xattr;\n    }\n    XrdOucString in = in_cmd.str().c_str();\n    global_retc = ctx.outputResult(ctx.clientCommand(in, true, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    std::string ctime;\n    std::string mtime;\n    std::string excl_xattr;\n    ConfigureBackupApp(app, ctime, mtime, excl_xattr);\n    const std::string help = app.help();\n    fprintf(stderr, \"%s\", help.c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterBackupNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<BackupCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/cat-com-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: cat-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <memory>\n#include <sstream>\n\nextern int com_cat(char*);\n\nnamespace {\nclass CatCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"cat\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Cat a file\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    return com_cat((char*)joined.c_str());\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"Usage: cat <path>\\n\");\n  }\n};\n} // namespace\n\nvoid\nRegisterCatNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<CatCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/cd-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: cd-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeCdHelp()\n{\n  return \"Usage: cd <path> | cd - | cd ~\\n\";\n}\n\nvoid ConfigureCdApp(CLI::App& app, std::string& path)\n{\n  app.name(\"cd\");\n  app.description(\"Change directory\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeCdHelp();\n      }));\n  app.add_option(\"path\", path, \"path (- for previous, ~ for home)\")\n      ->default_val(\"\");\n}\n\nclass CdCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"cd\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Change directory\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      fprintf(stderr, \"%s\", MakeCdHelp().c_str());\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    std::string path;\n    if (!args.empty()) {\n      CLI::App app;\n      ConfigureCdApp(app, path);\n      std::vector<std::string> cli_args = args;\n      std::reverse(cli_args.begin(), cli_args.end());\n      try {\n        app.parse(cli_args);\n      } catch (const CLI::ParseError&) {\n        fprintf(stderr, \"%s\", MakeCdHelp().c_str());\n        global_retc = EINVAL;\n        return 0;\n      }\n    }\n\n    static XrdOucString opwd = \"/\";\n    static XrdOucString oopwd = \"/\";\n\n    XrdOucString lsminuss;\n    XrdOucString newpath;\n    XrdOucString oldpwd;\n\n    XrdOucString arg = path.c_str();\n\n    if (arg == \"-\") {\n      oopwd = opwd;\n      arg = (char*)opwd.c_str();\n    }\n\n    opwd = gPwd;\n    newpath = abspath(arg.c_str());\n    oldpwd = gPwd;\n\n    if ((arg == \"\") || (arg == \"~\")) {\n      if (getenv(\"EOS_HOME\")) {\n        newpath = abspath(getenv(\"EOS_HOME\"));\n      } else {\n        fprintf(stderr,\n                \"warning: there is no home directory defined via EOS_HOME\\n\");\n        newpath = opwd;\n      }\n    }\n\n    gPwd = newpath;\n    if ((!gPwd.endswith(\"/\")) && (!gPwd.endswith(\"/\\\"\"))) {\n      gPwd += \"/\";\n    }\n\n    while (gPwd.replace(\"/./\", \"/\")) {\n    }\n\n    int dppos = 0;\n    while ((dppos = gPwd.find(\"/../\")) != STR_NPOS) {\n      if (dppos == 0) {\n        gPwd = oldpwd;\n        break;\n      }\n      int rpos = gPwd.rfind(\"/\", dppos - 1);\n      if (rpos != STR_NPOS) {\n        gPwd.erase(rpos, dppos - rpos + 3);\n      } else {\n        gPwd = oldpwd;\n        break;\n      }\n    }\n\n    if ((!gPwd.endswith(\"/\")) && (!gPwd.endswith(\"/\\\"\"))) {\n      gPwd += \"/\";\n    }\n\n    lsminuss = \"mgm.cmd=cd&mgm.path=\";\n    lsminuss += gPwd;\n    lsminuss += \"&mgm.option=s\";\n    global_retc =\n        ctx.outputResult(ctx.clientCommand(lsminuss, false, nullptr), true);\n    if (global_retc) {\n      gPwd = oldpwd;\n    }\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeCdHelp().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterCdNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<CdCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/chmod-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: chmod-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeChmodHelp()\n{\n  std::ostringstream oss;\n  oss << \"Usage: chmod [-r] <mode> <path>                             : set \"\n         \"mode for <path> (-r recursive)\\n\";\n  oss << \"                 <mode> can be only numerical like 755, 644, 700\\n\";\n  return oss.str();\n}\n\nvoid ConfigureChmodApp(CLI::App& app,\n                      bool& opt_r,\n                      std::string& mode,\n                      std::string& path)\n{\n  app.name(\"chmod\");\n  app.description(\"Mode Interface\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeChmodHelp();\n      }));\n  app.add_flag(\"-r\", opt_r, \"recursive\");\n  app.add_option(\"mode\", mode, \"mode (e.g. 755, 644, 700)\")->required();\n  app.add_option(\"path\", path, \"path\")->required();\n}\n\nclass ChmodCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"chmod\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Mode Interface\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (args.empty() || wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    bool opt_r = false;\n    std::string mode;\n    std::string path;\n    ConfigureChmodApp(app, opt_r, mode, path);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    XrdOucString in = \"mgm.cmd=chmod\";\n    if (opt_r)\n      in += \"&mgm.option=r\";\n    XrdOucString ap = abspath(path.c_str());\n    in += \"&mgm.path=\";\n    in += ap;\n    in += \"&mgm.chmod.mode=\";\n    in += mode.c_str();\n    global_retc = ctx.outputResult(ctx.clientCommand(in, false, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    bool opt_r = false;\n    std::string mode;\n    std::string path;\n    ConfigureChmodApp(app, opt_r, mode, path);\n    const std::string help = app.help();\n    fprintf(stderr, \"%s\", help.c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterChmodNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<ChmodCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/chown-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: chown-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeChownHelp()\n{\n  std::ostringstream oss;\n  oss << \"Usage: chown [-r] [-h|--nodereference] <owner>[:<group>] <path>\\n\";\n  oss << \"       chown [-r] :<group> <path>\\n\";\n  oss << \"'[eos] chown ..' provides the change owner interface of EOS.\\n\";\n  oss << \"<path> is the file/directory to modify, <owner> has to be a user id \"\n         \"or user name. <group> is optional and has to be a group id or group \"\n         \"name.\\n\";\n  oss << \"To modify only the group use :<group> as identifier!\\n\";\n  oss << \"Remark: if you use the -r -h option and path points to a link the \"\n         \"owner of the link parent will also be updated!\\n\";\n  oss << \"Options:\\n\";\n  oss << \"  -r                    recursive\\n\";\n  oss << \"  -h, --nodereference   don't follow symlinks\\n\";\n  return oss.str();\n}\n\nvoid ConfigureChownApp(CLI::App& app,\n                      bool& opt_r,\n                      bool& opt_h,\n                      std::string& owner,\n                      std::string& path)\n{\n  app.name(\"chown\");\n  app.description(\"change owner interface of EOS\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeChownHelp();\n      }));\n  app.add_flag(\"-r\", opt_r, \"recursive\");\n  app.add_flag(\"-h,--nodereference\", opt_h, \"don't follow symlinks\");\n  app.add_option(\"owner\", owner, \"owner[:group] or :group\")->required();\n  app.add_option(\"path\", path, \"path\")->required();\n}\n\nclass ChownCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"chown\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Chown Interface\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str(), true);\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (args.empty() || wants_help(joined.c_str(), true)) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    bool opt_r = false;\n    bool opt_h = false;\n    std::string owner;\n    std::string path;\n    ConfigureChownApp(app, opt_r, opt_h, owner, path);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    std::string opt;\n    if (opt_r)\n      opt += 'r';\n    if (opt_h)\n      opt += 'h';\n\n    XrdOucString in = \"mgm.cmd=chown\";\n    if (!opt.empty()) {\n      in += \"&mgm.chown.option=\";\n      in += opt.c_str();\n    }\n    XrdOucString ap = abspath(path.c_str());\n    in += \"&mgm.path=\";\n    in += ap;\n    in += \"&mgm.chown.owner=\";\n    in += owner.c_str();\n    global_retc = ctx.outputResult(ctx.clientCommand(in, false, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    bool opt_r = false;\n    bool opt_h = false;\n    std::string owner;\n    std::string path;\n    ConfigureChownApp(app, opt_r, opt_h, owner, path);\n    const std::string help = app.help();\n    fprintf(stderr, \"%s\", help.c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterChownNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<ChownCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/clear-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: clear-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeClearHelp()\n{\n  return \"Usage: clear\\n\"\n         \"'[eos] clear' is equivalent to the interactive shell \"\n         \"command to clear the screen.\\n\";\n}\n\nvoid ConfigureClearApp(CLI::App& app)\n{\n  app.name(\"clear\");\n  app.description(\"Clear the terminal\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeClearHelp();\n      }));\n}\n\nclass ClearCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"clear\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Clear the terminal\";\n  }\n  bool\n  requiresMgm(const std::string&) const override\n  {\n    return false;\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    if (!args.empty()) {\n      std::ostringstream oss;\n      for (size_t i = 0; i < args.size(); ++i) {\n        if (i)\n          oss << ' ';\n        oss << args[i];\n      }\n      std::string joined = oss.str();\n      if (wants_help(joined.c_str())) {\n        printHelp();\n        return 0;\n      }\n    }\n\n    CLI::App app;\n    ConfigureClearApp(app);\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      return 0;\n    }\n\n    int rc = system(\"clear\");\n    return rc;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeClearHelp().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterClearNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<ClearCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/config-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: config-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/ICmdHelper.hh\"\n#include <memory>\n#include <sstream>\n\nextern void com_config_help();\n\nnamespace {\nstd::string MakeConfigHelp()\n{\n  return \"Usage: config changelog|dump|export|load|ls|reset|save [OPTIONS]\\n\\n\"\n         \"'[eos] config' provides the configuration interface to EOS.\\n\\n\"\n         \"Subcommands:\\n\"\n         \"  changelog [#lines]     show last #lines from changelog (default 10)\\n\"\n         \"  dump [<name>]          dump configuration\\n\"\n         \"  export <name> [-f]     export config file to QuarkDB\\n\"\n         \"  load <name>            load config\\n\"\n         \"  ls [-b|--backup]       list configurations\\n\"\n         \"  reset                  reset all configuration\\n\"\n         \"  save <name> [-f] [-c|--comment \\\"<comment>\\\"]  save config\\n\";\n}\n\nvoid ConfigureConfigApp(CLI::App& app)\n{\n  app.name(\"config\");\n  app.description(\"Configuration System\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeConfigHelp();\n      }));\n}\n\n// Ported from legacy com_proto_config.cc\nclass ConfigHelper : public ICmdHelper {\npublic:\n  ConfigHelper(const GlobalOptions& opts) : ICmdHelper(opts) {}\n  ~ConfigHelper() override = default;\n  bool\n  ParseCommand(const char* arg) override\n  {\n    eos::console::ConfigProto* config = mReq.mutable_config();\n    eos::common::StringTokenizer tokenizer(arg);\n    tokenizer.GetLine();\n    std::string token;\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n    if (token == \"ls\") {\n      eos::console::ConfigProto_LsProto* ls = config->mutable_ls();\n      if (tokenizer.NextToken(token)) {\n        if (token == \"--backup\" || token == \"-b\") {\n          ls->set_showbackup(true);\n        } else {\n          return false;\n        }\n      }\n    } else if (token == \"dump\") {\n      eos::console::ConfigProto_DumpProto* dump = config->mutable_dump();\n      if (tokenizer.NextToken(token)) {\n        dump->set_file(token);\n      }\n    } else if (token == \"reset\") {\n      if (tokenizer.NextToken(token)) {\n        return false;\n      }\n      config->set_reset(true);\n    } else if (token == \"export\") {\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n      eos::console::ConfigProto_ExportProto* exp = config->mutable_exp();\n      if (token.find('-') != 0) {\n        exp->set_file(token);\n        if (tokenizer.NextToken(token)) {\n          if (token == \"-f\") {\n            exp->set_force(true);\n          } else {\n            return false;\n          }\n        }\n      } else {\n        return false;\n      }\n    } else if (token == \"save\") {\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n      eos::console::ConfigProto_SaveProto* save = config->mutable_save();\n      if (token.find('-') != 0) {\n        save->set_file(token);\n      } else {\n        return false;\n      }\n      while (tokenizer.NextToken(token)) {\n        if (token == \"-c\" || token == \"--comment\") {\n          std::string sline = arg;\n          if (token == \"-c\") {\n            size_t pos = sline.find(\"-c\");\n            sline.replace(pos, std::string(\"-c\").length(), \"--comment\");\n            parse_comment(sline.c_str(), token);\n          } else {\n            parse_comment(sline.c_str(), token);\n          }\n          mReq.set_comment(token);\n          tokenizer.NextToken(token);\n        } else if (token == \"-f\") {\n          save->set_force(true);\n        } else {\n          return false;\n        }\n      }\n    } else if (token == \"load\") {\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n      eos::console::ConfigProto_LoadProto* load = config->mutable_load();\n      load->set_file(token);\n    } else if (token == \"changelog\") {\n      eos::console::ConfigProto_ChangelogProto* changelog =\n          config->mutable_changelog();\n      if (tokenizer.NextToken(token)) {\n        if (token.find('-') == 0) {\n          token.erase(0);\n        }\n        try {\n          changelog->set_lines(std::stoi(token));\n        } catch (...) {\n          std::cerr << \"error: argument needs to be numeric\" << std::endl;\n          return false;\n        }\n      } else {\n        changelog->set_lines(10);\n      }\n    } else {\n      return false;\n    }\n    return true;\n  }\n};\n\nclass ConfigProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"config\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Configuration System\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    ConfigHelper helper(gGlobalOpts);\n    if (!helper.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = helper.Execute();\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureConfigApp(app);\n    fprintf(stderr, \"%s\", app.help().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterConfigProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<ConfigProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/convert-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: convert-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/LayoutId.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/ICmdHelper.hh\"\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeConvertHelp()\n{\n  return \"Usage: convert config|file|list|rule|clear [OPTIONS]\\n\\n\"\n         \"  config list|set [<key>=<value>]\\n\"\n         \"    list: list converter configuration parameters and status\\n\"\n         \"    set : set converter configuration parameters\\n\\n\"\n         \"  list\\n\"\n         \"    list conversion jobs\\n\\n\"\n         \"  clear\\n\"\n         \"    clear list of jobs stored in the backend\\n\\n\"\n         \"  file <identifier> <conversion>\\n\"\n         \"    schedule a file conversion\\n\"\n         \"    <identifier> = fid|fxid|path\\n\"\n         \"    <conversion> = <layout:replica> [space] [placement] [checksum]\\n\\n\"\n         \"  rule <identifier> <conversion>\\n\"\n         \"    apply a conversion rule on the given directory\\n\"\n         \"    <identifier> = cid|cxid|path\\n\"\n         \"    <conversion> = <layout:replica> [space] [placement] [checksum]\\n\";\n}\n\nvoid ConfigureConvertApp(CLI::App& app)\n{\n  app.name(\"convert\");\n  app.description(\"Convert Interface\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeConvertHelp();\n      }));\n}\n\nclass ConvertHelper : public ICmdHelper {\npublic:\n  ConvertHelper(const GlobalOptions& opts) : ICmdHelper(opts) {}\n  ~ConvertHelper() override = default;\n  bool\n  ParseCommand(const char* arg) override\n  {\n    using eos::console::ConvertProto_ConfigProto;\n    eos::console::ConvertProto* convert = mReq.mutable_convert();\n    eos::common::StringTokenizer tokenizer(arg);\n    tokenizer.GetLine();\n    std::string token;\n    if (!tokenizer.NextToken(token))\n      return false;\n    if (token == \"config\") {\n      ConvertProto_ConfigProto* config = convert->mutable_config();\n      if (!tokenizer.NextToken(token))\n        return false;\n      if (token == \"list\") {\n        config->set_op(ConvertProto_ConfigProto::LIST);\n      } else if (token == \"set\") {\n        if (!tokenizer.NextToken(token)) {\n          return false;\n        }\n        size_t pos = token.find('=');\n        if (pos == std::string::npos || pos == token.length() - 1)\n          return false;\n        config->set_op(ConvertProto_ConfigProto::SET);\n        config->set_key(token.substr(0, pos));\n        config->set_value(token.substr(pos + 1));\n      } else {\n        return false;\n      }\n    } else if (token == \"file\") {\n      eos::console::ConvertProto_FileProto* file = convert->mutable_file();\n      if (!tokenizer.NextToken(token))\n        return false;\n      file->set_allocated_identifier(ParseIdentifier(token));\n      eos::console::ConvertProto_ConversionProto* conversion =\n          ParseConversion(tokenizer);\n      if (conversion == nullptr)\n        return false;\n      file->set_allocated_conversion(conversion);\n    } else if (token == \"rule\") {\n      if (!tokenizer.NextToken(token))\n        return false;\n      eos::console::ConvertProto_RuleProto* rule = convert->mutable_rule();\n      rule->set_allocated_identifier(ParseIdentifier(token));\n      eos::console::ConvertProto_ConversionProto* conversion =\n          ParseConversion(tokenizer);\n      if (conversion == nullptr)\n        return false;\n      rule->set_allocated_conversion(conversion);\n    } else if (token == \"list\") {\n      convert->mutable_list();\n    } else if (token == \"clear\") {\n      convert->mutable_clear();\n    } else {\n      return false;\n    }\n    return true;\n  }\n\nprivate:\n  eos::console::ConvertProto_IdentifierProto*\n  ParseIdentifier(std::string spath)\n  {\n    XrdOucString path = spath.c_str();\n    auto* identifier = new eos::console::ConvertProto_IdentifierProto{};\n    unsigned long long id = 0ull;\n    if (Path2FileDenominator(path, id))\n      identifier->set_fileid(id);\n    else if (Path2ContainerDenominator(path, id))\n      identifier->set_containerid(id);\n    else\n      identifier->set_path(abspath(path.c_str()));\n    return identifier;\n  }\n\n  eos::console::ConvertProto_ConversionProto*\n  ParseConversion(eos::common::StringTokenizer& tokenizer)\n  {\n    std::string token, layout, space, placement, checksum;\n    int replica = 0;\n    size_t pos;\n    bool ok;\n    auto validLayout = [](const std::string& l) {\n      return eos::common::LayoutId::GetLayoutFromString(l) != -1;\n    };\n    auto validPlacement = [](const std::string& p) {\n      return (p == \"scattered\" || p == \"hybrid\" || p == \"gathered\");\n    };\n    auto validChecksum = [](const std::string& c) {\n      using eos::common::LayoutId;\n      auto xs_id = LayoutId::GetChecksumFromString(c);\n      return ((xs_id > -1) && (xs_id != LayoutId::eChecksum::kNone));\n    };\n    if (!tokenizer.NextToken(token))\n      return nullptr;\n    if ((pos = token.find(\":\")) == std::string::npos)\n      return nullptr;\n    layout = token.substr(0, pos);\n    try {\n      replica = std::stol(token.substr(pos + 1));\n    } catch (...) {\n      return nullptr;\n    }\n    if (!validLayout(layout))\n      return nullptr;\n    if (replica < 1 || replica > 32)\n      return nullptr;\n    while (tokenizer.NextToken(token)) {\n      if ((ok = validChecksum(token)))\n        checksum = std::move(token);\n      else if ((ok = validPlacement(token)))\n        placement = std::move(token);\n      else if ((ok = space.empty()))\n        space = std::move(token);\n      if (!ok)\n        return nullptr;\n    }\n    auto* conversion = new eos::console::ConvertProto_ConversionProto{};\n    conversion->set_layout(layout);\n    conversion->set_replica(replica);\n    conversion->set_space(space);\n    conversion->set_placement(placement);\n    conversion->set_checksum(checksum);\n    return conversion;\n  }\n};\n\nclass ConvertProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"convert\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Convert Interface\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    ConvertHelper helper(gGlobalOpts);\n    if (!helper.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = helper.Execute();\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureConvertApp(app);\n    fprintf(stderr, \"%s\", app.help().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterConvertProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<ConvertProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/cp-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: cp-cmd-native.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include <algorithm>\n#include <iomanip>\n#include <iostream>\n#include <memory>\n#include <sstream>\n#include <string>\n#include <vector>\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include \"common/Path.hh\"\n#include \"common/StringConversion.hh\"\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdCl/XrdClURL.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n/*----------------------------------------------------------------------------*/\n\n/** Parsed options for cp command */\nstruct CpOptions {\n  std::string rate;\n  std::string streams = \"0\";\n  std::string atomic;\n  std::vector<std::string> sources;\n  std::string target;\n  bool recursive = false;\n  bool summary = false;\n  bool noprogress = false;\n  bool append = false;\n  bool makeparent = false;\n  bool debug = false;\n  int debug_level = 0;\n  bool checksums = false;\n  bool silent = false;\n  bool nooverwrite = false;\n  bool preserve = false;\n  unsigned long depth = 0;\n};\n\nnamespace {\nstd::string MakeCpHelp()\n{\n  return R\"(Usage: cp [OPTIONS] <src>... <dst>\n\n'[eos] cp ..' provides copy functionality to EOS.\n<src>|<dst> can be root://<host>/<path>, a local path /tmp/../ or an eos path\n/eos/ in the connected instance.\n\nOptions:\n  --atomic          run an atomic upload (files visible only when complete)\n  --rate=<rate>     limit the cp rate\n  --streams=<n>     use <#> parallel streams\n  --depth=<d>       depth for recursive copy\n  --checksum        output the checksums\n  -a                append to the target, don't truncate\n  -p                create destination directory\n  -n                hide progress bar\n  -S                print summary\n  -d, --debug[=1|2|3]  enable debug information\n  -s, --silent      no output outside error messages\n  -k, --no-overwrite  disable overwriting of files\n  -P, --preserve    preserve file creation and modification time\n  -r, -R, --recursive  copy source location recursively\n\nRemark:\n  Add '/' at the end of source or target paths for directories. Use '-r' with\n  source and target directories terminated with '/' for hierarchy copy.\n\nExamples:\n  eos cp /var/data/myfile /eos/foo/user/data/\n  eos cp /var/data/ /eos/foo/user/data/\n  eos cp -r /var/data/ /eos/foo/user/data/\n  eos cp -r --checksum --silent /var/data/ /eos/foo/user/data/\n\nS3:\n  URLs: as3://<host>/<bucket>/<file> or as3:<bucket>/<file> with S3_HOSTNAME set.\n  Credentials: S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_HOSTNAME\n  or s3.id=, s3.key= in URL.\n)\";\n}\n\nvoid ConfigureCpApp(CLI::App& app, CpOptions& opts)\n{\n  app.name(\"cp\");\n  app.description(\"Copy files to/from EOS\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeCpHelp();\n      }));\n  app.add_option(\"--rate\", opts.rate, \"limit cp rate\");\n  app.add_option(\"--streams\", opts.streams, \"parallel streams\")->default_val(\"0\");\n  app.add_flag(\"--atomic\", [&](size_t) { opts.atomic = \"&eos.atomic=1\"; },\n               \"atomic upload\");\n  app.add_option(\"--depth\", opts.depth, \"recursive copy depth\");\n  app.add_flag(\"--checksum\", opts.checksums, \"output checksums\");\n  app.add_flag(\"-a,--append\", opts.append, \"append, don't truncate\");\n  app.add_flag(\"-p\", opts.makeparent, \"create destination directory\");\n  app.add_flag(\"-n\", opts.noprogress, \"hide progress bar\");\n  app.add_flag(\"-S\", opts.summary, \"print summary\");\n  app.add_flag(\"-d,--debug\", [&](size_t c) {\n    if (c)\n      opts.debug_level = 1;\n  }, \"enable debug\");\n  app.add_flag(\"-s,--silent\", opts.silent, \"silent mode\");\n  app.add_flag(\"-k,--no-overwrite\", opts.nooverwrite, \"no overwrite\");\n  app.add_flag(\"-P,--preserve\", opts.preserve, \"preserve mtime\");\n  app.add_flag(\"-r,-R,--recursive\", opts.recursive, \"recursive copy\");\n  app.add_option(\"path\", opts.sources, \"source paths and destination\")\n      ->required()\n      ->expected(2, -1);\n}\n\n/** Parse cp args with CLI11. Returns true on success, false on help/error. */\nbool ParseCpArgs(const std::vector<std::string>& args, CpOptions& opts)\n{\n  if (args.empty())\n    return false;\n\n  for (const auto& a : args) {\n    if (a == \"--help\" || a == \"-h\") {\n      std::cerr << MakeCpHelp();\n      return false;\n    }\n  }\n\n  CLI::App app;\n  ConfigureCpApp(app, opts);\n\n  std::vector<std::string> cli_args = args;\n  std::reverse(cli_args.begin(), cli_args.end());\n  try {\n    app.parse(cli_args);\n  } catch (const CLI::ParseError& e) {\n    std::cerr << MakeCpHelp();\n    return false;\n  }\n\n  if (opts.sources.size() < 2) {\n    std::cerr << \"error: at least one source and one destination required\\n\";\n    return false;\n  }\n\n  opts.target = opts.sources.back();\n  opts.sources.pop_back();\n  opts.debug = (opts.debug_level > 0);\n  if (opts.recursive)\n    opts.makeparent = true;\n  if (opts.silent || !hasterminal)\n    opts.noprogress = true;\n\n  return true;\n}\n\n/** Tokenize char* into vector of strings for CLI11 parsing */\nstd::vector<std::string> TokenizeCpArgs(const char* argin)\n{\n  std::vector<std::string> args;\n  eos::common::StringTokenizer tokenizer(argin);\n  tokenizer.GetLine();\n  XrdOucString tok;\n  while ((tok = tokenizer.GetToken()).length()) {\n    if ((!tok.beginswith(\"/eos/\")) && (!tok.beginswith(\"root:/\")))\n      eos::common::StringConversion::UnsealXrdPath(tok);\n    args.push_back(tok.c_str());\n  }\n  return args;\n}\n} // namespace\n\nstatic int com_cp_usage()\n{\n  std::cerr << MakeCpHelp();\n  return EINVAL;\n}\n\n/* Helper types */\nenum Protocol {\n  HTTP, HTTPS, GSIFTP,\n  S3, AS3, XROOT,\n  EOS, LOCAL, UNKNOWN\n};\n\nstruct File_t {\n  XrdOucString name;\n  XrdOucString opaque;\n  Protocol protocol;\n  timespec atime;\n  timespec mtime;\n  unsigned long long size;\n\n  File_t() : name(\"\"), opaque(\"\"), protocol(Protocol::UNKNOWN), size(0) { }\n};\n\n/* Helper functions */\nint run_eos_command(const char* cmdline, std::vector<XrdOucString>& result);\nint run_command(const char* cmdline, std::vector<XrdOucString>& result);\nconst char* absolute_path(const char* path);\nbool is_dir(const char* path, Protocol protocol, struct stat* buf = NULL);\nXrdOucString process_symlink(XrdOucString path);\nconst char* setup_s3_environment(XrdOucString path, XrdOucString opaque);\nstd::string eos_roles_opaque();\nint do_stat(const char* path, Protocol protocol, struct stat& buf);\nint check_protocol_tool(const char* path);\nProtocol get_protocol(XrdOucString path);\nconst char* protocol_to_string(Protocol protocol);\n\n/** Core copy implementation */\nstatic int cp_impl(const CpOptions& opts)\n{\n  XrdOucString rate = opts.rate.c_str();\n  XrdOucString streams = opts.streams.c_str();\n  XrdOucString atomic = opts.atomic.c_str();\n  std::vector<XrdOucString> source_find_list;\n  for (const auto& s : opts.sources)\n    source_find_list.emplace_back(s.c_str());\n  std::vector<XrdOucString> source_basepath_list;\n  std::vector<File_t> source_list;\n  File_t target;\n  target.name = opts.target.c_str();\n  bool target_is_stdout;\n  bool target_is_dir = false;\n  bool recursive = opts.recursive;\n  bool summary = opts.summary;\n  bool noprogress = opts.noprogress;\n  bool append = opts.append;\n  bool makeparent = opts.makeparent;\n  bool debug = opts.debug;\n  int debug_level = opts.debug_level;\n  bool checksums = opts.checksums;\n  bool silent = opts.silent;\n  bool nooverwrite = opts.nooverwrite;\n  bool preserve = opts.preserve;\n  unsigned long long copysize = 0;\n  unsigned long long copiedsize = 0;\n  unsigned long depth = opts.depth;\n  struct timeval start_time, end_time;\n  struct timezone tz;\n  int files_copied = 0;\n  int retc = 0;\n\n  if (!target.name.length()) {\n    std::cerr << \"warning: no target specified. Please view 'eos cp --help'.\"\n              << std::endl;\n    global_retc = 0;\n    return 0;\n  }\n\n  // --------------------------------------------------------------------------\n  // Expand source list into final list to copy.\n  // This means interpreting the '*' character in file names\n  // and traversing directories for the recursive flag.\n  // Every source path also has an associated base path,\n  // which will get appended to the target.\n  // --------------------------------------------------------------------------\n\n  for (size_t i = 0; i < source_find_list.size(); i++) {\n    std::vector<XrdOucString> files;\n    XrdOucString source = source_find_list[i];\n    XrdOucString source_opaque;\n    XrdOucString basepath = \"\";\n    Protocol protocol;\n    std::string sprotocol = \"\";\n    int opos = source.find(\"?\");\n    bool wildcard = false;\n    files.clear();\n\n    // Extract opaque info\n    if (opos != STR_NPOS) {\n      source_opaque = source;\n      source_opaque.erase(0, opos + 1);\n      source.erase(opos);\n    }\n\n    // Identify protocol\n    protocol = get_protocol(source.c_str());\n\n    if (protocol == Protocol::UNKNOWN) {\n      std::cerr << \"warning: \" << source.c_str()\n                << \" -- protocol not recognized. Skipping path..\" << std::endl;\n      continue;\n    }\n\n    // Convert local to absolute path\n    const char* abs_path = absolute_path(source.c_str());\n    source = abs_path;\n    free((char*)abs_path);\n\n    // Check if source is a directory\n    if (!source.endswith(\"/\") && is_dir(source.c_str(), protocol, NULL)) {\n      source.append(\"/\");\n    }\n\n    // Extract file name and parent path\n    const char* filepath = source.c_str();\n\n    // URLs need different processing in order to extract the path\n    if ((protocol != Protocol::EOS) && (protocol != Protocol::LOCAL)) {\n      XrdOucString sprot, hostport;\n      filepath = eos::common::StringConversion::ParseUrl(source.c_str(),\n                 sprot, hostport);\n\n      if (!filepath) {\n        std::cerr << \"error: cannot process file=\" << source.c_str()\n                  << \" [protocol=\" << protocol_to_string(protocol) << \"]\"\n                  << std::endl;\n        continue;\n      }\n    }\n\n    eos::common::Path cPath(filepath);\n    basepath = cPath.GetParentPath();\n\n    if ((source.find(\"*\") != STR_NPOS) || (source.endswith(\"/\"))) {\n      std::string cmdtext;\n\n      if ((protocol != Protocol::EOS) && (protocol != Protocol::LOCAL)) {\n        std::cerr << \"error: \" << source.c_str()\n                  << \" -- path expansion not implemented for \"\n                  << protocol_to_string(protocol) << \" protocol. Skipping path..\"\n                  << std::endl;\n        continue;\n      }\n\n      // Get all paths matching wildcard\n      if (source.find(\"*\") != STR_NPOS) {\n        // Will use 'ls -lF' combined with grep to identify matches\n        // ls -l[F|p] <path> | awk 'NF == 9 {print $9}' [ | egrep \"<match>\" ]\n        // Note: eos::common::Path removes trailing '/'!\n        XrdOucString basename = cPath.GetName();\n\n        if (source.endswith(\"/\")) {\n          basename.append(\"/\");\n        }\n\n        // Wildcards are supported only in the basename\n        if (basename.find(\"*\") == STR_NPOS) {\n          std::cerr << \"warning: \" << source.c_str()\n                    << \" -- wildcards not supported outside basename. \"\n                    \"Skipping path..\" << std::endl;\n          continue;\n        }\n\n        XrdOucString match = basename.c_str();\n        wildcard = true;\n\n        if (!match.beginswith(\"*\")) {\n          match.insert(\"^\", 0);\n        }\n\n        if (!match.endswith(\"*\"))   {\n          match.append(\"$\");\n        }\n\n        match.replace(\"*\", \".*\");\n        // Construct command text\n        cmdtext = \"ls -l\";\n        cmdtext += (protocol == Protocol::EOS) ? \"F \" : \"p \";\n        cmdtext += basepath.c_str();\n        cmdtext +=\n          \" | awk '{out=$9; for (i=10; i<=NF; i++) {out=out\\\" \\\"$i}; print out}' | egrep \\\"\";\n        cmdtext += match.c_str();\n        cmdtext += \"\\\"\";\n      } else if (source.endswith(\"/\")) {\n        // Get all files within directory\n\n        // Will use 'find' to identify files\n        // local file: find <path> [-maxdepth <depth>] -follow -type f\n        // eos file:   find -f [--maxdepth <depth>] <path>\n        if (!recursive) {\n          std::cerr << \"warning: omitting directory \" << source.c_str()\n                    << std::endl;\n          continue;\n        }\n\n        // Enclose source path in quotes, as the path may contain whitespace\n        std::stringstream ss;\n        ss.clear();\n        ss << std::quoted(source.c_str());\n        source = ss.str().c_str();\n        // Capture only last directory\n        // This will end up appended to the target\n        std::string smaxdepth = \" \";\n\n        if (depth != 0) {\n          smaxdepth = \" -maxdepth \";\n          smaxdepth += std::to_string(depth);\n          smaxdepth += \" \";\n\n          if (protocol == Protocol::EOS) {\n            smaxdepth.insert(1, \"-\");\n          }\n        }\n\n        cmdtext = \"find \";\n\n        if (protocol == Protocol::EOS) {\n          cmdtext += \"-f\";\n          cmdtext += smaxdepth.c_str();\n          cmdtext += source.c_str();\n        } else {\n          cmdtext += source.c_str();\n          cmdtext += smaxdepth.c_str();\n          cmdtext += \"-follow -type f\";\n        }\n      }\n\n      cmdtext += \" 2> /dev/null\";\n\n      if (debug) {\n        std::cerr << \"[eos-cp] running: \" << cmdtext.c_str() << std::endl;\n      }\n\n      int rc = (protocol == Protocol::EOS)  ?\n               run_eos_command(cmdtext.c_str(), files) :\n               run_command(cmdtext.c_str(), files);\n\n      if (rc && !files.size()) {\n        std::cerr << \"warning: could not expand source: \" << source.c_str()\n                  << std::endl;\n        global_retc = rc;\n        return -1;\n      }\n    } else {\n      files.emplace_back(source.c_str());\n    }\n\n    for (auto& file : files) {\n      // Check if path expansion discovered a symlink\n      if (file.find(\" -> \") != STR_NPOS) {\n        file = process_symlink(file.c_str());\n      }\n\n      if (wildcard) {\n        file.insert(basepath.c_str(), 0);\n        source_find_list.emplace_back(file.c_str());\n        continue;\n      }\n\n      if (debug) {\n        std::cerr << \"[eos-cp] Copy list: \" << file.c_str() << std::endl;\n      }\n\n      File_t source_file;\n      source_file.name = file.c_str();\n      source_file.opaque = source_opaque.c_str();\n      if (getenv(\"EOSAUTHZ\")) {\n\tsource_file.opaque += \"&authz=\";\n\tsource_file.opaque += getenv(\"EOSAUTHZ\");\n      }\n      source_file.protocol = protocol;\n      source_list.emplace_back(source_file);\n      source_basepath_list.emplace_back(basepath.c_str());\n    }\n  }\n\n  // Check if there is any file in the list\n  if (source_list.empty()) {\n    std::cerr << \"warning: found zero files to copy!\" << std::endl;\n    global_retc = 0;\n    return 0;\n  }\n\n  // --------------------------------------------------------------------------\n  // Process target path\n  // --------------------------------------------------------------------------\n  bool target_exists;\n  struct stat target_stat;\n  target.protocol = get_protocol(target.name.c_str());\n\n  // Make sure executable to reach target exists\n  if (check_protocol_tool(target.name.c_str())) {\n    return -1;\n  }\n\n  // Handle opaque information for target\n  if (target.protocol != Protocol::LOCAL) {\n    int qpos = target.name.find(\"?\");\n\n    if (qpos != STR_NPOS) {\n      target.opaque = target.name.c_str();\n      target.opaque.keep(qpos + 1);\n      target.name.erase(qpos);\n    }\n\n    // Seal the target name\n    if (target.protocol == Protocol::EOS) {\n      eos::common::StringConversion::SealXrdPath(target.name);\n    }\n  }\n\n  // Detect whether target is stdout\n  const char* abs_path = absolute_path(target.name.c_str());\n  target.name = abs_path;\n  free((char*)abs_path);\n  target_is_stdout = (target.name == \"-\");\n\n  if (!target_is_stdout) {\n    // Detect whether target is a directory\n    int stat_rc = do_stat(target.name.c_str(), target.protocol, target_stat);\n    target_exists = (stat_rc == 0);\n    // Only pass stat buffer when stat succeeded - otherwise is_dir would use\n    // undefined buffer content and could spuriously return true\n    target_is_dir = is_dir(target.name.c_str(), target.protocol,\n                          target_exists ? &target_stat : nullptr);\n\n    // If multiple source files target must be a directory\n    if (source_list.size() > 1) {\n      // Target doesn't exist, mark it as directory\n      if (!target_exists) {\n        target_is_dir = true;\n      }\n\n      // Target is not a directory\n      if (!target_is_dir) {\n        std::cerr << \"error: target must be a directory\" << std::endl;\n        global_retc = EINVAL;\n        return -1;\n      }\n    }\n\n    // Target doesn't exist but name suggests should be a directory\n    if (!target_exists && target.name.endswith(\"/\")) {\n      target_is_dir = true;\n    }\n\n    // If target is a directory then the name should also reflect this\n    if (target_is_dir && !target.name.endswith(\"/\")) {\n      target.name.append(\"/\");\n    }\n\n    // Check rights to create target directory\n    if (target_is_dir && !target_exists) {\n      if (!makeparent) {\n        std::cerr << \"error: target must be created. Please try with \"\n                     \"create flag '-p' or see 'eos cp --help' for more info.\"\n                  << std::endl;\n        global_retc = EINVAL;\n        return -1;\n      }\n    }\n\n    // Create target directory tree for EOS or local path\n    if (makeparent) {\n      if ((target.protocol == Protocol::EOS) ||\n          (target.protocol == Protocol::LOCAL)) {\n        XrdOucString mktarget;\n\n        if (target.name.endswith(\"/\")) {\n          mktarget = target.name.c_str();\n        } else {\n          eos::common::Path cTarget(target.name.c_str());\n          mktarget = cTarget.GetParentPath();\n        }\n\n        std::string cmdtext = \"mkdir -p \";\n\n        if (target.protocol == Protocol::LOCAL) {\n          cmdtext += \"--mode 755 \";\n        }\n\n        cmdtext += mktarget.c_str();\n        std::vector<XrdOucString> tmp;\n        int rc = (target.protocol == Protocol::EOS) ?\n                 run_eos_command(cmdtext.c_str(), tmp) :\n                 run_command(cmdtext.c_str(), tmp);\n\n        if (rc) {\n          std::cerr << \"error: failed to create target directory : \"\n                    << mktarget.c_str() << std::endl;\n          global_retc = rc;\n          return -1;\n        }\n      }\n    }\n  } else {\n    // Disable all output for stdout target\n    silent = true;\n    noprogress = true;\n  }\n\n  // Set up environment for S3 target\n  if ((target.protocol == Protocol::AS3) ||\n      (target.protocol == Protocol::S3)) {\n    const char* url = setup_s3_environment(target.name, target.opaque);\n\n    if (url == NULL) {\n      return -1;\n    }\n\n    target.name = url;\n  }\n\n  // Expand '/eos/' shortcut for EOS protocol\n  if ((target.protocol == Protocol::EOS) &&\n      (target.name.beginswith(\"/eos/\"))) {\n    if (!serveruri.endswith(\"/\")) {\n      target.name.insert(\"/\", 0);\n    }\n\n    target.name.insert(serveruri.c_str(), 0);\n    if (getenv(\"EOSAUTHZ\")) {\n      target.opaque+= \"&authz=\";\n      target.opaque+= getenv(\"EOSAUTHZ\");\n    }\n  }\n\n  if (debug) {\n    std::cerr << \"[eos-cp] # of source files: \" << source_list.size()\n              << std::endl;\n    std::cerr << \"[eos-cp] Setting target \" << target.name.c_str()\n              << \" [protocol=\" << protocol_to_string(target.protocol) << \"]\"\n              << std::endl;\n  }\n\n  // --------------------------------------------------------------------------\n  // Compute size for each source path\n  // --------------------------------------------------------------------------\n  // As needed, check whether tools to access these protocols can be found\n  bool s3_tool = false;\n  bool http_tool = false;\n  bool gsiftp_tool = false;\n\n  for (auto& source : source_list) {\n    bool statok = false;\n    struct stat buf;\n    source.atime.tv_nsec = source.mtime.tv_nsec = 0;\n\n    switch (source.protocol) {\n    // ------------------------------------------\n    // EOS, XRoot or local file\n    // ------------------------------------------\n    case Protocol::EOS:\n    case Protocol::XROOT:\n    case Protocol::LOCAL:\n      if (!do_stat(source.name.c_str(), source.protocol, buf)) {\n        // For symbolic links, EOS stat returns the size of the link.\n        // Ignore the size attribute in this case\n        if (source.protocol != Protocol::LOCAL && !S_ISREG(buf.st_mode)) {\n          source.size = 0;\n\n          if (debug || !silent) {\n            std::cerr << \"warning: disable size check for path=\"\n                      << source.name.c_str() << \" [EOS symbolic link]\"\n                      << std::endl;\n          }\n        } else {\n          copysize += buf.st_size;\n          source.size = (unsigned long long) buf.st_size;\n        }\n\n        // Store the a/m-time\n        source.atime.tv_sec = buf.st_atime;\n        source.mtime.tv_sec = buf.st_mtime;\n        statok = true;\n      }\n\n      break;\n\n    // ------------------------------------------\n    // S3 file\n    // ------------------------------------------\n    case Protocol::AS3:\n    case Protocol::S3: {\n      if (!s3_tool) {\n        if (check_protocol_tool(source.name.c_str())) {\n          return -1;\n        }\n\n        s3_tool = true;\n      }\n\n      const char* url = setup_s3_environment(source.name, source.opaque);\n\n      if (url == NULL) {\n        return -1;\n      }\n\n      XrdOucString s3env = \"env S3_ACCESS_KEY_ID=\";\n      s3env += getenv(\"S3_ACCESS_KEY_ID\");\n      s3env += \" S3_HOSTNAME=\";\n      s3env += getenv(\"S3_HOSTNAME\");\n      s3env += \" S3_SECRET_ACCESS_KEY=\";\n      s3env += getenv(\"S3_SECRET_ACCESS_KEY\");\n      // Execute 's3' command to retrieve size\n      XrdOucString cmdtext = \"bash -c \\\"\";\n      cmdtext += s3env;\n      cmdtext += \" s3 head \";\n      cmdtext += url;\n      cmdtext += \" | grep Content-Length | awk '{print \\\\$2}' 2> /dev/null\\\"\";\n\n      if (debug) {\n        std::cerr << \"[eos-cp] running \" << cmdtext.c_str() << std::endl;\n      }\n\n      long long size = eos::common::StringConversion::LongLongFromShellCmd(\n                         cmdtext.c_str());\n\n      if ((!size) || (size == LLONG_MAX)) {\n        std::cerr << \"error: path=\" << source.name.c_str()\n                  << \" cannot obtain size of S3 source file or file size is 0!\"\n                  << std::endl;\n        global_retc = EIO;\n        return -1;\n      }\n\n      copysize += size;\n      source.size = (unsigned long long) size;\n      source.atime.tv_sec = source.mtime.tv_sec = 0;\n      statok = true;\n      break;\n    }\n\n    // ------------------------------------------\n    // HTTP(S) & GSIFTP file\n    // ------------------------------------------\n    case Protocol::GSIFTP:\n    case Protocol::HTTP:\n    case Protocol::HTTPS:\n      if ((source.protocol == Protocol::HTTP ||\n           source.protocol == Protocol::HTTPS) && (!http_tool)) {\n        if (check_protocol_tool(source.name.c_str())) {\n          return -1;\n        }\n\n        http_tool = true;\n      } else if ((source.protocol == Protocol::GSIFTP) && (!gsiftp_tool)) {\n        if (check_protocol_tool(source.name.c_str())) {\n          return -1;\n        }\n\n        gsiftp_tool = true;\n      }\n\n      source.size = 0;\n      source.atime.tv_sec = source.mtime.tv_sec = 0;\n\n      if (debug || !silent) {\n        std::cerr << \"warning: disabling size check for path=\"\n                  << source.name.c_str() << \" [protocol=\"\n                  << protocol_to_string(source.protocol) << \"]\" << std::endl;\n      }\n\n      statok = true;\n      break;\n\n    default:\n      break;\n    }\n\n    if (!statok) {\n      std::cerr << \"error: cannot get file size of path=\" << source.name.c_str()\n                << \" [protocol=\" << protocol_to_string(source.protocol) << \"]\"\n                << std::endl;\n      global_retc = EINVAL;\n      return -1;\n    }\n\n    if (debug) {\n      std::cerr << \"[eos-cp] path=\" << source.name.c_str() << \" size=\"\n                << source.size << \" [protocol=\"\n                << protocol_to_string(source.protocol) << \"]\" << std::endl;\n    }\n  }\n\n  if (debug || (!silent && source_list.size() > 1)) {\n    XrdOucString ssize;\n    std::cerr << \"[eos-cp] going to copy \" << source_list.size() << \" files and \"\n              << eos::common::StringConversion::GetReadableSizeString(ssize,\n                  copysize, \"B\") << std::endl;\n  }\n\n  // Mark start timestamp\n  gettimeofday(&start_time, &tz);\n  // --------------------------------------------------------------------------\n  // Create 'eoscp' command for each source path\n  // and effectively perform the copy operation\n  // --------------------------------------------------------------------------\n  int file_idx = -1;\n  retc = 0;\n\n  for (auto& source : source_list) {\n    XrdOucString dest = target.name.c_str();\n    // Processed target path + original target opaque info\n    XrdOucString target_path = \"\";\n    // Temporary file upload flag\n    bool temporary_file = false;\n    file_idx++;\n\n    //------------------------------------\n    // Process destination path\n    //------------------------------------\n\n    // Append source suffix to destination\n    // The source suffix: <source_path> = <source_basepath/><source_suffix>\n    if (target_is_dir) {\n      XrdOucString source_suffix = source.name.c_str();\n      int pos = source_suffix.find(source_basepath_list[file_idx].c_str());\n\n      if (pos == STR_NPOS) {\n        std::cerr << \"error: could not identify source suffix for path=\"\n                  << source.name.c_str() << std::endl;\n        global_retc = EINVAL;\n        return -1;\n      }\n\n      pos += source_basepath_list[file_idx].length();\n      source_suffix.keep(pos);\n      dest += source_suffix.c_str();\n    }\n\n    // Check that source and destination are different\n    if (!strcmp(source.name.c_str(), dest.c_str())) {\n      std::cerr << \"warning: source and target are the same path=\"\n                << source.name.c_str() << \". Skipping path..\" << std::endl;\n      continue;\n    }\n\n    // Add opaque info to destination\n    if (target.opaque.length()) {\n      dest += \"?\";\n      dest += target.opaque.c_str();\n    }\n\n    target_path = dest.c_str();\n\n    // Continue processing for non STDOUT targets\n    if (!target_is_stdout) {\n      // Check if destination exists\n      if (nooverwrite) {\n        if ((target.protocol == Protocol::LOCAL) ||\n            (target.protocol == Protocol::EOS)) {\n          struct stat tmp;\n\n          if (!do_stat(dest.c_str(), target.protocol, tmp)) {\n            std::cerr << \"warning: target=\" << dest.c_str()\n                      << \" exists, but --no-overwrite flag specified\"\n                      << std::endl;\n            retc |= EEXIST;\n            continue;\n          }\n        }\n      }\n\n      // Handle EOS specific opaque info\n      if ((target.protocol == Protocol::EOS) ||\n          (target.protocol == Protocol::XROOT)) {\n        char opaque[1024];\n        std::string roles = eos_roles_opaque();\n        snprintf(opaque, sizeof(opaque) - 1,\n                 \"%ceos.targetsize=%llu&eos.bookingsize=%llu&eos.app=%s%s%s%s\",\n                 (target.opaque.length()) ? '&' : '?',\n                 source.size, source.size, getenv(\"EOSAPP\") ? getenv(\"EOSAPP\") : \"eoscp\",\n                 atomic.c_str(),\n                 roles.size() ? \"&\" : \"\",\n                 roles.size() ? roles.c_str() : \"\");\n        dest.append(opaque);\n      }\n\n      // Protocols for EOS, XRoot and local targets are supported directly\n      // S3 targets will be uploaded via STDIN & STDOUT pipes\n      // Remaining protocols will be copied to a temporary file\n      if ((target.protocol == Protocol::HTTP) ||\n          (target.protocol == Protocol::HTTPS) ||\n          (target.protocol == Protocol::GSIFTP)) {\n        char tmp_name[] = \"/tmp/com_cp.XXXXXX\";\n        int tmp_fd = mkstemp(tmp_name);\n\n        if (tmp_fd == -1) {\n          std::cerr << \"error: failed to create temporary file while preparing \"\n                    << \"copy for path=\" << dest.c_str() << \" [protocol=\"\n                    << protocol_to_string(target.protocol) << \"]\" << std::endl;\n          global_retc = errno;\n          return -1;\n        }\n\n        close(tmp_fd);\n        temporary_file = true;\n        dest = tmp_name;\n      }\n    }\n\n    //------------------------------------\n    // Process source path\n    //------------------------------------\n\n    // Expand '/eos/' shortcut for EOS protocol\n    if ((source.protocol == Protocol::EOS) &&\n        (source.name.beginswith(\"/eos/\"))) {\n      if (!serveruri.endswith(\"/\")) {\n        source.name.insert(\"/\", 0);\n      }\n\n      source.name.insert(serveruri.c_str(), 0);\n    }\n\n    // Add opaque info to source\n    if (source.opaque.length()) {\n      source.name += \"?\";\n      source.name += source.opaque.c_str();\n    }\n\n    if (debug) {\n      std::cerr << \"\\n[eos-cp] copying \" << source.name.c_str() << \" to \"\n                << target_path.c_str() << std::endl;\n    }\n\n    //------------------------------------\n    // Prepare STDIN and STDOUT pipes\n    //------------------------------------\n    XrdOucString transfersize =\n      \"\"; // used for STDIN pipes to specify the target size to eoscp\n    XrdOucString cmdtext = \"\";\n    bool rstdin = false;\n    bool rstdout = false;\n\n    if ((source.protocol == Protocol::EOS) ||\n        (source.protocol == Protocol::XROOT)) {\n      std::string roles = eos_roles_opaque();\n      source.name += (source.opaque.length())  ?  \"&\"  :  \"?\";\n      source.name += \"eos.app=\";\n      source.name += getenv(\"EOSAPP\") ? getenv(\"EOSAPP\") : \"eoscp\";\n      source.name += roles.size()  ?  \"&\"  :  \"\";\n      source.name += roles.size()  ?  roles.c_str()  : \"\";\n    } else if ((source.protocol != Protocol::LOCAL) &&\n               (source.protocol != Protocol::UNKNOWN)) {\n      bool old_noprogress = noprogress;\n      noprogress = true;\n      XrdOucString safesource = source.name.c_str();\n\n      while (safesource.replace(\"'\", \"\\\\'\")) {}\n\n      safesource.replace(\"as3:\", \"\", 0, 3);\n      XrdOucString tool = \"\";\n\n      if (source.protocol == Protocol::HTTP)    {\n        tool = \"curl \";\n      }\n\n      if (source.protocol == Protocol::HTTPS)   {\n        tool = \"curl -k \";\n      }\n\n      if (source.protocol == Protocol::GSIFTP)  {\n        tool = \"globus-url-copy \";\n      }\n\n      if ((source.protocol == Protocol::AS3) ||\n          (source.protocol == Protocol::S3)) {\n        tool = \"s3 get \";\n        noprogress = old_noprogress;\n      }\n\n      cmdtext += tool;\n      cmdtext += \"$'\";\n      cmdtext += safesource;\n      cmdtext += \"'\";\n\n      if (source.protocol == Protocol::GSIFTP) {\n        cmdtext += \" -\";\n      }\n\n      cmdtext += \" | \";\n      rstdin = true;\n    }\n\n    if ((source.protocol == Protocol::AS3) ||\n        (source.protocol == Protocol::S3)  ||\n        (target.protocol == Protocol::AS3) ||\n        (target.protocol == Protocol::S3)) {\n      char ts[1024];\n      snprintf(ts, sizeof(ts) - 1, \"%llu \", source.size);\n      transfersize = ts;\n    }\n\n    if ((target.protocol == Protocol::AS3) ||\n        (target.protocol == Protocol::S3)) {\n      rstdout = true;\n    }\n\n    //------------------------------------\n    // Prepare eoscp transaction name\n    //------------------------------------\n    XrdOucString safename = source.name.c_str();\n    int qpos = safename.rfind(\"?\");\n\n    if (qpos != STR_NPOS) {\n      safename.erase(qpos);\n    }\n\n    if (source.protocol != Protocol::LOCAL) {\n      XrdOucString sprot, hostport;\n      const char* url = eos::common::StringConversion::ParseUrl(safename.c_str(),\n                        sprot, hostport);\n\n      if (url) {\n        std::string surl = url;\n        safename = surl.c_str();\n      }\n    }\n\n    safename = eos::common::Path(safename.c_str()).GetName();;\n    eos::common::StringConversion::SealXrdPath(safename);\n    safename.replace(\"'\", \"\\\\'\");\n    //------------------------------------\n    // Construct 'eoscp' command\n    //------------------------------------\n    cmdtext += \"eoscp \";\n\n    if (append) {\n      cmdtext += \"-a \";\n    }\n\n    if (debug_level) {\n      cmdtext += (debug_level == 1) ? \"-v \" : \"-d \";\n    }\n\n    if (!summary) {\n      cmdtext += \"-s \";\n    }\n\n    if (makeparent) {\n      cmdtext += \"-p \";\n    }\n\n    if (noprogress) {\n      cmdtext += \"-n \";\n    }\n\n    if (nooverwrite) {\n      cmdtext += \"-x \";\n    }\n\n    if (transfersize.length()) {\n      cmdtext += \"-T \";\n      cmdtext += transfersize;\n      cmdtext += \" \";\n    }\n\n    if (rate.length()) {\n      cmdtext += \"-t \";\n      cmdtext += rate.c_str();\n      cmdtext += \" \";\n    }\n\n    cmdtext += \"-N $'\";\n    cmdtext += safename.c_str();\n    cmdtext += \"' \";\n\n    if (rstdin) {\n      cmdtext += \"- \";\n    } else {\n      XrdOucString safesource = source.name.c_str();\n      safesource.replace(\"'\", \"\\\\'\");\n      cmdtext += \"$'\";\n      cmdtext += safesource;\n      cmdtext += \"' \";\n    }\n\n    if (rstdout) {\n      cmdtext += \"-\";\n    } else {\n      XrdOucString safedest = dest.c_str();\n      safedest.replace(\"'\", \"\\\\'\");\n      cmdtext += \"$'\";\n      cmdtext += safedest;\n      cmdtext += \"'\";\n    }\n\n    if ((target.protocol == Protocol::AS3) ||\n        (target.protocol == Protocol::S3)) {\n      // s3 can upload via STDIN\n      XrdOucString s3dest = dest.c_str();\n      s3dest.replace(\"as3:\", \"\", 0, 3);\n      cmdtext += \" | s3 put \";\n      cmdtext += s3dest.c_str();\n      cmdtext += \" contentLength=\";\n      cmdtext += transfersize.c_str();\n      cmdtext += \" > /dev/null\";\n    }\n\n    if (debug) {\n      std::cerr << \"[eos-cp] running: \" << cmdtext.c_str() << std::endl;\n    }\n\n    int lrc = system(cmdtext.c_str());\n\n    // Check if we got a CONTROL-C\n    if (lrc == EINTR) {\n      std::cerr << \"<Control-C>\" << std::endl;\n      break;\n    }\n\n    if (WEXITSTATUS(lrc)) {\n      std::cerr << \"error: failed copying path=\" << target_path.c_str()\n                << std::endl;\n      retc |= lrc;\n      continue;\n    }\n\n    //------------------------------------\n    // Check target size\n    //------------------------------------\n\n    if (((target.protocol == Protocol::EOS)    ||\n         (target.protocol == Protocol::XROOT)  ||\n         (target.protocol == Protocol::LOCAL)) && (!target_is_stdout)) {\n      struct stat buf;\n\n      if (!do_stat(target_path.c_str(), target.protocol, buf)) {\n        if ((!source.size) ||\n            (buf.st_size == (off_t)(append ? target_stat.st_size + source.size :\n                                    (off_t) source.size)\n            )\n           ) {\n          // Preserve creation and modification timestamps\n          if ((preserve) && (source.atime.tv_sec > 0) && (source.mtime.tv_sec > 0)) {\n            bool updateok;\n\n            if (target.protocol == Protocol::LOCAL) {\n              struct timeval times[2];\n              times[0].tv_sec = source.atime.tv_sec;\n              times[0].tv_usec = source.atime.tv_nsec / 1000;\n              times[1].tv_sec = source.mtime.tv_sec;\n              times[1].tv_usec = source.mtime.tv_nsec / 1000;\n              updateok = (utimes(target_path.c_str(), times) == 0);\n            } else {\n              char update[1024];\n              auto roles = eos_roles_opaque();\n              sprintf(update, \"%ceos.app=%s%s%s&mgm.pcmd=utimes\"\n                      \"&tv1_sec=%llu&tv1_nsec=%llu\"\n                      \"&tv2_sec=%llu&tv2_nsec=%llu\",\n                      (target.opaque.length()) ? '&' : '?',\n                      getenv(\"EOSAPP\") ? getenv(\"EOSAPP\") : \"eoscp\",\n                      roles.size() ? \"&\" : \"\",\n                      roles.size() ? roles.c_str() : \"\",\n                      (unsigned long long) source.atime.tv_sec,\n                      (unsigned long long) source.atime.tv_nsec,\n                      (unsigned long long) source.mtime.tv_sec,\n                      (unsigned long long) source.mtime.tv_nsec);\n              XrdOucString request = target_path.c_str();\n              request += update;\n              char value[4096];\n              value[0] = 0;\n              long long update_rc = XrdPosixXrootd::QueryOpaque(request.c_str(),\n                                    value, 4096);\n              updateok = (update_rc >= 0);\n\n              // Parse the stat output\n              if (updateok) {\n                char tag[1024];\n                int tmp_retc;\n                int items = sscanf(value, \"%1023s retc=%d\", tag, &tmp_retc);\n                updateok = ((items == 2) && (strcmp(tag, \"utimes:\") == 0));\n              }\n            }\n\n            if (!updateok) {\n              std::cerr << \"warning: creation/modification time could not be \"\n                        << \"preserved for path=\" << target_path.c_str()\n                        << std::endl;\n            }\n          }\n\n          // Verify checksum\n          if ((checksums) && (target.protocol != Protocol::LOCAL)) {\n            XrdOucString address = serveruri.c_str();\n            address += \"//dummy\";\n            XrdCl::URL url(address.c_str());\n\n            if (!url.IsValid()) {\n              std::cerr << \"error: invalid file system URL=\" << url.GetURL()\n                        << \" [attempting checksum]\" << std::endl;\n              global_retc = EINVAL;\n              return -1;\n            }\n\n            auto* fs = new XrdCl::FileSystem(url);\n\n            if (!fs) {\n              std::cerr << \"error: failed to get new FS object \"\n                        << \"[attempting checksum]\" << std::endl;\n              global_retc = EINVAL;\n              return -1;\n            }\n\n            XrdCl::Buffer arg;\n            XrdCl::Buffer* response = nullptr;\n            XrdCl::XRootDStatus status;\n            std::string query_path = dest.c_str();\n            std::string::size_type pos = query_path.rfind(\"//\");\n\n            if (pos != std::string::npos) {\n              query_path.erase(0, pos + 1);\n            }\n\n            arg.FromString(query_path);\n            status = fs->Query(XrdCl::QueryCode::Checksum, arg, response);\n\n            if (status.IsOK()) {\n              XrdOucString xsum = response->GetBuffer();\n              xsum.replace(\"eos \", \"\");\n              std::cout << \"path=\" << source.name.c_str() << \" size=\"\n                        << source.size << \" checksum=\" << xsum.c_str()\n                        << std::endl;\n            } else {\n              std::cout << \"warning: failed getting checksum for path=\"\n                        << source.name.c_str() << \" size=\" << source.size\n                        << std::endl;\n            }\n\n            delete response;\n            delete fs;\n          }\n        } else {\n          XrdOucString ssize1, ssize2;\n          std::cerr << \"error: file size difference between source and target \"\n                    << \"file source=\" << source.name.c_str() << \" [\"\n                    << eos::common::StringConversion::GetReadableSizeString(\n                        ssize1, source.size, \"B\")\n                    << \"] target=\" << target_path.c_str() << \" [\"\n                    << eos::common::StringConversion::GetReadableSizeString(\n                        ssize2, (unsigned long long) buf.st_size, \"B\")\n                    << \"]\" << std::endl;\n          lrc |= 0xffff00;\n        }\n      } else {\n        std::cerr << \"error: target file not created source=\"\n                  << source.name.c_str() << \" target=\" << target_path.c_str()\n                  << std::endl;\n        lrc |= 0xffff00;\n      }\n    }\n\n    // Attempt to upload temporary file\n    if (temporary_file) {\n      if (target.protocol == Protocol::GSIFTP) {\n        cmdtext = \"globus-url-copy file://\";\n        cmdtext += dest.c_str();\n        cmdtext += \" \";\n        cmdtext += target_path.c_str();\n\n        if (silent || noprogress) {\n          cmdtext += \" >& /dev/null\";\n        }\n\n        if (debug) {\n          std::cerr << \"[eos-cp] running: \" << cmdtext.c_str() << std::endl;\n        }\n\n        int rc = system(cmdtext.c_str());\n\n        if (WEXITSTATUS(rc)) {\n          std::cerr << \"error: failed to upload \" << target_path.c_str()\n                    << \" [protocol=gsiftp]\" << std::endl;\n          lrc |= 0xffff00;\n        }\n      }\n\n      if ((target.protocol == Protocol::HTTP) ||\n          (target.protocol == Protocol::HTTPS)) {\n        std::cerr << \"error: file uploads not supported for \"\n                  << protocol_to_string(target.protocol) << \" protocol [path=\"\n                  << target_path.c_str() << \"]\" << std::endl;\n        lrc |= 0xffff00;\n      }\n\n      // Clean-up the temporary file\n      unlink(dest.c_str());\n    }\n\n    if (!WEXITSTATUS(lrc)) {\n      files_copied++;\n      copiedsize += source.size;\n    }\n\n    retc |= lrc;\n  }\n\n  // Mark end timestamp\n  gettimeofday(&end_time, &tz);\n\n  if (debug || !silent) {\n    float time_elapsed = (float)(((end_time.tv_sec - start_time.tv_sec) * 1000000 +\n                                  (end_time.tv_usec - start_time.tv_usec)) / 1000000.0);\n    unsigned long long copyrate = (copiedsize / time_elapsed);\n    XrdOucString ssize1, ssize2;\n    std::cerr << ((retc) ? \"#WARNING \" : \"\")\n              << \"[eos-cp] copied \" << files_copied << \"/\"\n              << (int) source_list.size() << \" files and \"\n              << eos::common::StringConversion::GetReadableSizeString(ssize1,\n                  copiedsize, \"B\")\n              << \" in \" << std::fixed << std::setprecision(2) << time_elapsed\n              << \" seconds with \"\n              << eos::common::StringConversion::GetReadableSizeString(ssize2,\n                  copyrate, \"B/s\")\n              << std::endl;\n  }\n\n  global_retc = WEXITSTATUS(retc);\n  return global_retc;\n}\n\n/* eos cp command - entry point for com_cat and legacy callers */\nint com_cp(char* argin)\n{\n  std::vector<std::string> args = TokenizeCpArgs(argin);\n  CpOptions opts;\n  if (!ParseCpArgs(args, opts))\n    return com_cp_usage();\n  return cp_impl(opts);\n}\n\n// ----------------------------------------------------------------------------\n// Helper functions implementation\n// ----------------------------------------------------------------------------\n\n/**\n * Convenience function to be used by 'eos cp' to query EOS for file names.\n * The output of the command is placed into the result vector.\n * @param cmdline the eos command to be executed\n * @param result reference to the result vector\n * @return error code of the command\n */\nint run_eos_command(const char* cmdline, std::vector<XrdOucString>& result)\n{\n  XrdOucString cmd = \"eos -b \";\n\n  if (user_role.length() && group_role.length()) {\n    cmd += \"--role \";\n    cmd += user_role;\n    cmd += \" \";\n    cmd += group_role;\n    cmd += \" \";\n  }\n\n  cmd += cmdline;\n  return run_command(cmd.c_str(), result);\n}\n\n/**\n * Convenience function to be used by 'eos cp' to execute a command.\n * The output of the command is placed into the result vector.\n * @param cmdline the bash command to be executed\n * @param result reference to the result vector\n * @return error code of the command\n */\nint run_command(const char* cmdline, std::vector<XrdOucString>& result)\n{\n  FILE* fp = popen(cmdline, \"r\");\n  char line[4096];\n  int rc;\n\n  if (!fp) {\n    std::cerr << \"error: failed executing command \" << cmdline << std::endl;\n    return errno;\n  }\n\n  while (fgets(line, sizeof(line), fp)) {\n    int size = strlen(line);\n\n    if (line[size - 1] == '\\n') {\n      line[size - 1] = '\\0';\n    }\n\n    result.emplace_back(line);\n  }\n\n  rc = pclose(fp);\n  return WEXITSTATUS(rc);\n}\n\n/**\n * Converts from local to absolute path.\n * This function makes the distinction between local or EOS paths.\n * Any other protocol will be left untouched.\n * Function is aware of interactive eos shell environment.\n * Local files will have the 'file:' prefix removed.\n * @param path the given path\n * @return abspath the absolute path\n */\nconst char* absolute_path(const char* path)\n{\n  Protocol protocol = get_protocol(path);\n\n  if (protocol != Protocol::EOS && protocol != Protocol::LOCAL) {\n    return strdup(path);\n  }\n\n  if (strcmp(path, \"-\") == 0) {\n    return strdup(path);\n  }\n\n  XrdOucString spath = path;\n\n  if (protocol == Protocol::LOCAL && spath.beginswith(\"file:\")) {\n    spath.erase(0, 5);\n  }\n\n  if (!spath.beginswith(\"/\")) {\n    XrdOucString abspath = \"\";\n\n    if (interactive) {\n      // Construct absolute path within eos shell\n      abspath.insert(gPwd.c_str(), 0);\n    } else {\n      // Construct absolute path within regular shell\n      abspath.insert(\"/\", 0);\n      abspath.insert(getenv(\"PWD\"), 0);\n    }\n\n    spath.insert(abspath.c_str(), 0);\n  }\n\n  // Note: eos::common::Path expects an absolute path!\n  // Note: eos::common::Path removes trailing '/'!\n  std::string trailing_slash = \"\";\n\n  if ((spath.endswith(\"/\")) && (!spath.endswith(\"/./\")) &&\n      (!spath.endswith(\"/../\"))) {\n    trailing_slash = \"/\";\n  }\n\n  // Sanitize '.' and '..' entries\n  spath = eos::common::Path(spath.c_str()).GetFullPath().c_str();\n  spath += trailing_slash.c_str();\n  return strdup(spath.c_str());\n}\n\n/**\n * Given a symlink path of the following format 'link -> target',\n * will return the name of the 'link'.\n * @param path the path to check\n * @return path the processed symlink name\n */\nXrdOucString process_symlink(XrdOucString path)\n{\n  int pos = path.find(\" -> \");\n\n  if (pos != STR_NPOS) {\n    path.erase(pos);\n  }\n\n  return path;\n}\n\n/**\n * Will check whether the given path is a directory or not.\n * For local and EOS protocols, stat information is used.\n * The stat structure may be passed, otherwise it is constructed.\n * Function is aware of interactive eos shell environment.\n * @param path the path to check\n * @param protocol the protocol to access the path\n * @param buf stat structure\n * @return true if directory, false otherwise\n */\nbool is_dir(const char* path, Protocol protocol, struct stat* buf)\n{\n  if (protocol != Protocol::EOS && protocol != Protocol::LOCAL) {\n    XrdOucString spath = path;\n    return spath.endswith(\"/\");\n  }\n\n  int rc = 0;\n  struct stat tmpbuf {};\n\n  if (buf == nullptr) {\n    buf = &tmpbuf;\n    const char* abs_path = absolute_path(path);\n    rc = do_stat(abs_path, protocol, *buf);\n    free(const_cast<char*>(abs_path));\n  }\n\n  return (rc == 0)  ?  S_ISDIR(buf->st_mode)  :  false;\n}\n\n/**\n * Returns eos roles opaque info from the global user variables.\n * @return roles opaque info containing eos roles\n */\nstd::string\neos_roles_opaque()\n{\n  std::string roles;\n\n  if (user_role.length() && group_role.length()) {\n    roles = \"eos.ruid=\";\n    roles += user_role.c_str();\n    roles += \"&eos.rgid=\";\n    roles += group_role.c_str();\n    return roles;\n  }\n\n  return roles;\n}\n\n/**\n * Perform stat on a given path.\n * Function makes the distinction between local or EOS paths.\n * @param path the path to stat\n * @param protocol the protocol to access the path\n * @param buf stat structure to fill\n * @return rc stat error code\n */\nint do_stat(const char* path, Protocol protocol, struct stat& buf)\n{\n  const char* abs_path = absolute_path(path);\n  int rc = -1;\n\n  if (protocol == Protocol::EOS || protocol == Protocol::XROOT) {\n    // Stat EOS file\n    XrdOucString url = abs_path;\n    std::string roles = eos_roles_opaque();\n\n    // Expand '/eos/' shortcut for EOS protocol\n    if (url.beginswith(\"/eos/\")) {\n      url = serveruri.c_str();\n      url += (!url.endswith(\"/\"))  ?  \"/\"  :  \"\";\n      url += abs_path;\n    }\n\n    if (!roles.empty()) {\n      url += (url.find(\"?\") == STR_NPOS)  ?  \"?\"  :  \"&\";\n      url += roles.c_str();\n    }\n\n    rc = XrdPosixXrootd::Stat(url.c_str(), &buf);\n  } else if (protocol == Protocol::LOCAL) {\n    // Stat local file\n    rc = stat(abs_path, &buf);\n  }\n\n  free((char*)abs_path);\n  return rc;\n}\n\n/**\n * Given an S3 path, will parse and remove the opaque info.\n * The following environment variables are set:\n * S3_ACCESS_KEY_ID <br/>\n * S3_SECRET_ACCESS_KEY <br/>\n * S3_HOSTNAME <br/>\n * @param path the S3 path\n * @param opaque the opaque info to parse for S3 info\n * @return url the S3 url\n */\nconst char* setup_s3_environment(XrdOucString path, XrdOucString opaque)\n{\n  XrdOucString sprot, hostport;\n  XrdOucString url = eos::common::StringConversion::ParseUrl(path.c_str(),\n                     sprot, hostport);\n\n  if (!url.length()) {\n    std::cerr << \"error: could not parse S3 url=\" << path.c_str()\n              << std::endl;\n    global_retc = EINVAL;\n    return 0;\n  }\n\n  if (opaque.length()) {\n    XrdOucEnv env(opaque.c_str());\n\n    // Extract opaque S3 tags if present\n    if (env.Get(\"s3.id\"))  {\n      setenv(\"S3_ACCESS_KEY_ID\", env.Get(\"s3.id\"), 1);\n    }\n\n    if (env.Get(\"s3.key\")) {\n      setenv(\"S3_SECRET_ACCESS_KEY\", env.Get(\"s3.key\"), 1);\n    }\n  }\n\n  if (hostport.length()) {\n    setenv(\"S3_HOSTNAME\", hostport.c_str(), 1);\n  }\n\n  // Apply the ROOT compatibility environment variables\n  if (getenv(\"S3_ACCESS_ID\")) {\n    setenv(\"S3_ACCESS_KEY_ID\", getenv(\"S3_ACCESS_ID\"), 1);\n  }\n\n  if (getenv(\"S3_ACCESS_KEY\")) {\n    setenv(\"S3_SECRET_ACCESS_KEY\", getenv(\"S3_ACCESS_KEY\"), 1);\n  }\n\n  // Check S3 environment\n  if ((!getenv(\"S3_HOSTNAME\")) || (!getenv(\"S3_ACCESS_KEY_ID\")) ||\n      (!getenv(\"S3_SECRET_ACCESS_KEY\"))) {\n    std::cerr << \"error: S3 environment not set up for \" << path.c_str()\n              << std::endl;\n    std::cerr << \"You have to set the following environment variables: \"\n              << \"S3_ACCESS_KEY_ID or S3_ACCESS_ID\\n\"\n              << \"S3_SECRET_ACCESS_KEY or S3_ACCESS_KEY\\n\"\n              << \"S3_HOSTNAME (or use path with URI)\" << std::endl;\n    global_retc = EINVAL;\n    return 0;\n  }\n\n  return url.c_str();\n}\n\n/**\n * Check if required tools are available to access the given path.\n * @param path the path to access\n */\nint check_protocol_tool(const char* path)\n{\n  Protocol protocol = get_protocol(path);\n  std::string tool = \"\";\n  char cmd[128];\n\n  if (protocol == Protocol::HTTP || protocol == Protocol::HTTPS) {\n    tool = \"curl\";\n  } else if (protocol == Protocol::AS3 || protocol == Protocol::S3) {\n    tool = \"s3\";\n  } else if (protocol == Protocol::GSIFTP) {\n    tool = \"globus-url-copy\";\n  } else {\n    return 0;\n  }\n\n  sprintf(cmd, \"which %s >& /dev/null\", tool.c_str());\n  int rc = system(cmd);\n\n  if (WEXITSTATUS(rc)) {\n    std::cerr << \"error: \" << tool << \" executable not found in PATH\"\n              << std::endl;\n\n    if (tool == \"s3\") {\n      std::cerr << \" error: please install S3 executable from libs3\"\n                << std::endl;\n    }\n\n    global_retc = WEXITSTATUS(rc);\n  }\n\n  return WEXITSTATUS(rc);\n}\n\n/**\n * Returns the protocol for a given path.\n * Function is aware of interactive eos shell environment.\n */\nProtocol get_protocol(XrdOucString path)\n{\n  if (path.beginswith(\"/eos/\")) {\n    return Protocol::EOS;\n  } else if (path.beginswith(\"http://\")) {\n    return Protocol::HTTP;\n  } else if (path.beginswith(\"https://\")) {\n    return Protocol::HTTPS;\n  } else if (path.beginswith(\"gsiftp://\")) {\n    return Protocol::GSIFTP;\n  } else if (path.beginswith(\"root://\")) {\n    return Protocol::XROOT;\n  } else if (path.beginswith(\"as3:\")) {\n    return Protocol::AS3;\n  } else if (path.beginswith(\"s3://\")) {\n    return Protocol::S3;\n  } else if (path.beginswith(\"file:\")) {\n    return Protocol::LOCAL;\n  } else if (path.beginswith(\"/\") || (path.find(\":/\") == STR_NPOS)) {\n    return (interactive)  ?  Protocol::EOS  :  Protocol::LOCAL;\n  }\n\n  return Protocol::UNKNOWN;\n}\n\n/**\n * Returns a string representation of the protocol.\n */\nconst char* protocol_to_string(Protocol protocol)\n{\n  if (protocol == Protocol::EOS)       {\n    return \"eos\";\n  } else if (protocol == Protocol::HTTP)      {\n    return \"http\";\n  } else if (protocol == Protocol::HTTPS)     {\n    return \"https\";\n  } else if (protocol == Protocol::GSIFTP)    {\n    return \"gsiftp\";\n  } else if (protocol == Protocol::XROOT)     {\n    return \"root\";\n  } else if (protocol == Protocol::AS3)       {\n    return \"as3\";\n  } else if (protocol == Protocol::S3)        {\n    return \"s3\";\n  } else if (protocol == Protocol::LOCAL)     {\n    return \"local\";\n  }\n\n  return \"unknown\";\n}\n\n// ----------------------------------------------------------------------------\n// Native command registration (keeps com_cp interface for cat and other callers)\n// ----------------------------------------------------------------------------\nnamespace {\nclass CpCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"cp\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Copy files\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    (void)ctx;\n    if (args.empty() || wants_help(args[0].c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    CpOptions opts;\n    if (!ParseCpArgs(args, opts)) {\n      global_retc = EINVAL;\n      return 0;\n    }\n    return cp_impl(opts);\n  }\n  void\n  printHelp() const override\n  {\n    std::cerr << MakeCpHelp();\n  }\n};\n} // namespace\n\nvoid\nRegisterCpNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<CpCommand>());\n}\n\n/* eos cat command - just eos cp with '-' destination */\nint com_cat(char* argin)\n{\n  std::string catarg=(const char*)argin;\n  catarg += \" -\";\n  return com_cp((char*)catarg.c_str());\n}\n"
  },
  {
    "path": "console/commands/native/daemon-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: daemon-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include <memory>\n#include <sstream>\n// Native implementation adapted from legacy com_daemon\n#include \"common/Config.hh\"\n#include \"common/Path.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/SymKeys.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/ICmdHelper.hh\"\n#include <fcntl.h>\n#include <sched.h>\n#include <sys/mount.h>\n#include <sys/ptrace.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n\n// Ptrace macros compatibility\n#ifdef __APPLE__\n#define EOS_PTRACE_TRACEME PT_TRACEME\n#define EOS_PTRACE_ATTACH PT_ATTACH\n#define EOS_PTRACE_DETACH PT_DETACH\n#else\n#define EOS_PTRACE_TRACEME PTRACE_TRACEME\n#define EOS_PTRACE_ATTACH PTRACE_ATTACH\n#define EOS_PTRACE_DETACH PTRACE_DETACH\n#endif //__APPLE__\n\n// Native port of legacy com_daemon\nstatic int\nnative_com_daemon(char* arg)\n{\n#ifdef __APPLE__\n  fprintf(stderr, \"error: daemon command is not support on OSX\\n\");\n  global_retc = EINVAL;\n  return (0);\n#else\n  eos::common::StringTokenizer subtokenizer(arg);\n  eos::common::Config cfg;\n  XrdOucString option = \"\";\n  XrdOucString name = \"\";\n  XrdOucString service = \"\";\n  XrdOucString subcmd;\n  XrdOucString modules;\n  std::vector<std::string> mods;\n  std::string chapter;\n  std::string executable;\n  std::string pidfile;\n  std::string envfile;\n  std::string cfile;\n  bool ok = false;\n  subtokenizer.GetLine();\n\n  if (wants_help(arg)) {\n    goto com_daemon_usage;\n  }\n\n  option = subtokenizer.GetToken();\n\n  if (!option.length()) {\n    goto com_daemon_usage;\n  }\n\n  if (option == \"sss\") {\n    subcmd = subtokenizer.GetToken();\n\n    if (!subcmd.length()) {\n      goto com_daemon_usage;\n    }\n\n    if (subcmd == \"recreate\") {\n      if (geteuid()) {\n        std::cerr << \"error: you have to run this command as root!\"\n                  << std::endl;\n        global_retc = EPERM;\n        return (0);\n      }\n\n      struct stat buf;\n\n      std::cerr\n          << \"info: you are going to (re-)create the instance sss key. A \"\n             \"previous key will be moved to /etc/eos.keytab.<unixtimestamp>\"\n          << std::endl;\n\n      if (!::stat(\"/etc/eos.keytab\", &buf)) {\n        std::string oldkeytab = \"/etc/eos.keytab.\";\n        oldkeytab += std::to_string(time(NULL));\n\n        if (::rename(\"/etc/eos.keytab\", oldkeytab.c_str())) {\n          std::cerr << \"error: renaming of existing old keytab file \"\n                       \"/etc/eos.keytab failed!\"\n                    << std::endl;\n          global_retc = errno;\n          return (0);\n        }\n      }\n\n      bool interactive = false;\n\n      if (isatty(STDOUT_FILENO)) {\n        interactive = true;\n      }\n\n      if (!interactive || ICmdHelper::ConfirmOperation()) {\n        if (!::stat(\"/opt/eos/xrootd/bin/xrdsssadmin\", &buf)) {\n          system(\"yes | /opt/eos/xrootd/bin/xrdsssadmin -u daemon -g daemon -k \"\n                 \"eosmaster add /etc/eos.keytab\");\n          system(\"yes | /opt/eos/xrootd/bin/xrdsssadmin -u eosnobody -g \"\n                 \"eosnobody -k eosnobody add /etc/eos.keytab\");\n        } else {\n          system(\"yes | xrdsssadmin -u daemon -g daemon -k eosmaster add \"\n                 \"/etc/eos.keytab\");\n          system(\"yes | xrdsssadmin -u eosnobody -g eosnobody -k eosnobody add \"\n                 \"/etc/eos.keytab\");\n        }\n\n        system(\"mkdir -p /etc/eos/; cat /etc/eos.keytab | grep eosnobody > \"\n               \"/etc/eos/fuse.sss.keytab; chmod 400 /etc/eos/fuse.sss.keytab\");\n        std::cerr << \"info: recreated /etc/eos.keytab /etc/eos/fuse.sss.keytab\"\n                  << std::endl;\n      } else {\n        global_retc = EINVAL;\n        return (0);\n      }\n\n      return (0);\n    }\n  }\n\n  if (option == \"seal\") {\n    XrdOucString toseal = subtokenizer.GetToken();\n    XrdOucString sealed;\n    std::string key;\n\n    if (!toseal.length()) {\n      return (0);\n    }\n\n    if (toseal.beginswith(\"/\")) {\n      // treat it as a file\n      std::string contents;\n      eos::common::StringConversion::LoadFileIntoString(toseal.c_str(),\n                                                        contents);\n      toseal = contents.c_str();\n    }\n\n    const char* pkey = subtokenizer.GetToken();\n\n    if (!pkey) {\n      key = eos::common::StringConversion::StringFromShellCmd(\n          \"cat /etc/eos.keytab | grep u:daemon | md5sum\");\n    } else {\n      key = pkey;\n    }\n\n    std::string shakey = eos::common::SymKey::HexSha256(key);\n    eos::common::SymKey::SymmetricStringEncrypt(toseal, sealed,\n                                                (char*)shakey.c_str());\n    fprintf(stderr, \"enc:%s\\n\", sealed.c_str());\n    return (0);\n  }\n\n  if ((option != \"run\") && (option != \"config\") && (option != \"stack\") &&\n      (option != \"stop\") && (option != \"kill\") && (option != \"jwk\") &&\n      (option != \"module-init\")) {\n    goto com_daemon_usage;\n  }\n\n  service = subtokenizer.GetToken();\n\n  if ((option != \"jwk\") &&\n      ((!service.length()) || ((service != \"mgm\") && (service != \"mq\") &&\n                               (service != \"fst\") && (service != \"qdb\")))) {\n    goto com_daemon_usage;\n  }\n\n  name = subtokenizer.GetToken();\n\n  if (!name.length()) {\n    name = service.c_str();\n  }\n\n  modules = name;\n  modules += \".modules\";\n  executable = \"eos-\";\n  executable += service.c_str();\n  envfile = \"/var/run/eos/\";\n  envfile += executable.c_str();\n  envfile += \".\";\n  envfile += name.c_str();\n  envfile += \".env\";\n  pidfile = \"/var/run/eos/xrd.\";\n  pidfile += service.c_str();\n  pidfile += \".\";\n  pidfile += name.c_str();\n  pidfile += \".pid\";\n  cfg.Load(\"generic\", \"all\");\n  ok |= cfg.ok();\n  cfg.Load(service.c_str(), name.c_str(), false);\n  ok |= cfg.ok();\n  // this might fail if there are no modules\n  cfg.Load(service.c_str(), modules.c_str(), false);\n  {\n    // load all the modules:\n    eos::common::StringConversion::Tokenize(cfg.Dump(\"modules\", true), mods,\n                                            \"\\n\");\n\n    for (size_t i = 0; i < mods.size(); ++i) {\n      if (mods[i].front() == '#') {\n        // ignore comments\n        continue;\n      }\n\n      if (mods[i].find(\" \") != std::string::npos) {\n        fprintf(stderr, \"warning: ignoring module line '%s' (contains space)\\n\",\n                mods[i].c_str());\n        continue;\n      }\n\n      if (mods[i].empty()) {\n        // ignore empty lines\n        continue;\n      }\n\n      if (!cfg.Load(\"modules\", mods[i].c_str(), false)) {\n        fprintf(stderr, \"error: failed to load module '%s'\\n\", mods[i].c_str());\n        global_retc = EINVAL;\n        return 0;\n      }\n    }\n  }\n  chapter = service.c_str();\n  chapter += \":xrootd:\";\n  chapter += name.c_str();\n  cfile = \"/var/run/eos/xrd.cf.\";\n  cfile += name.c_str();\n\n  if (option == \"config\") {\n    char** const envv = cfg.Env(\"sysconfig\");\n\n    for (size_t i = 0; i < 1024; ++i) {\n      if (envv[i]) {\n        putenv(envv[i]);\n        fprintf(stderr, \"[putenv] %s\\n\", envv[i]);\n      } else {\n        break;\n      }\n    }\n\n    if (service == \"qdb\") {\n      XrdOucString subcmd = subtokenizer.GetToken();\n\n      if (subcmd == \"coup\") {\n        std::string kline;\n        kline =\n            \"export REDISCLI_AUTH=`cat /etc/eos.keytab`; redis-cli -p `cat \";\n        kline += cfile;\n        kline += \"|grep xrd.port | cut -d ' ' -f 2` <<< raft-attempt-coup\";\n        int rc = system(kline.c_str());\n        fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(),\n                WEXITSTATUS(rc));\n        global_retc = WEXITSTATUS(rc);\n        return (0);\n      } else if (subcmd == \"info\") {\n        std::string kline;\n        kline =\n            \"export REDISCLI_AUTH=`cat /etc/eos.keytab`; redis-cli -p `cat \";\n        kline += cfile;\n        kline += \"|grep xrd.port | cut -d ' ' -f 2` <<< raft-info\";\n        int rc = system(kline.c_str());\n        fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(),\n                WEXITSTATUS(rc));\n        global_retc = WEXITSTATUS(rc);\n        return (0);\n      } else if (subcmd == \"remove\") {\n        XrdOucString member = subtokenizer.GetToken();\n\n        if (!member.length()) {\n          fprintf(stderr, \"error: remove misses member argument host:port : \"\n                          \"'eos daemon config qdb qdb remove host:port'\\n\");\n          global_retc = EINVAL;\n          return 0;\n        } else {\n          std::string kline;\n          kline =\n              \"export REDISCLI_AUTH=`cat /etc/eos.keytab`; redis-cli -p `cat \";\n          kline += cfile;\n          kline +=\n              \"|grep xrd.port | cut -d ' ' -f 2` <<< \\\"raft-remove-member \";\n          kline += member.c_str();\n          kline += \"\\\"\";\n          int rc = system(kline.c_str());\n          fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(),\n                  WEXITSTATUS(rc));\n          global_retc = WEXITSTATUS(rc);\n          return (0);\n        }\n      } else if (subcmd == \"add\") {\n        XrdOucString member = subtokenizer.GetToken();\n        if (!member.length()) {\n          fprintf(stderr, \"error: add misses member argument host:port : 'eos \"\n                          \"daemon config qdb qdb add host:port'\\n\");\n          global_retc = EINVAL;\n          return 0;\n        } else {\n          std::string kline;\n          kline =\n              \"export REDISCLI_AUTH=`cat /etc/eos.keytab`; redis-cli -p `cat \";\n          kline += cfile;\n          kline += \"|grep xrd.port | cut -d ' ' -f 2` \\\"<<< raft-add-observer \";\n          kline += member.c_str();\n          kline += \"\\\"\";\n          int rc = system(kline.c_str());\n          fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(),\n                  WEXITSTATUS(rc));\n          global_retc = WEXITSTATUS(rc);\n          return (0);\n        }\n      } else if (subcmd == \"promote\") {\n        XrdOucString member = subtokenizer.GetToken();\n\n        if (!member.length()) {\n          fprintf(stderr, \"error: promote misses member argument host:port : \"\n                          \"'eos daemon config qdb qdb promote host:port'\\n\");\n          global_retc = EINVAL;\n          return 0;\n        } else {\n          std::string kline;\n          kline =\n              \"export REDISCLI_AUTH=`cat /etc/eos.keytab`; redis-cli -p `cat \";\n          kline += cfile;\n          kline +=\n              \"|grep xrd.port | cut -d ' ' -f 2` <<< \\\"raft-promote-observer \";\n          kline += member.c_str();\n          kline += \"\\\"\";\n          int rc = system(kline.c_str());\n          fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(),\n                  WEXITSTATUS(rc));\n          global_retc = WEXITSTATUS(rc);\n          return (0);\n        }\n      } else if (subcmd == \"new\") {\n        XrdOucString member = subtokenizer.GetToken();\n        if (member != \"observer\") {\n          fprintf(stderr, \"error: new misses 'observer' arguement : 'eos \"\n                          \"daemon config qdb qdb new observer'\\n\");\n          global_retc = EINVAL;\n          return 0;\n        } else {\n          std::string stopqdb = \"systemctl stop qdb \";\n          stopqdb += name.c_str();\n          system(stopqdb.c_str());\n\n          std::string qdbpath = getenv(\"QDB_PATH\") ? getenv(\"QDB_PATH\") : \"\";\n          std::string qdbcluster =\n              getenv(\"QDB_CLUSTER_ID\") ? getenv(\"QDB_CLUSTER_ID\") : \"\";\n          std::string qdbnode = getenv(\"QDB_NODE\") ? getenv(\"QDB_NODE\") : \"\";\n          if (qdbpath.empty()) {\n            fprintf(stderr,\n                    \"error: QDB_PATH is undefined in your configuration\\n\");\n            global_retc = EINVAL;\n            return 0;\n          }\n          if (qdbcluster.empty()) {\n            fprintf(\n                stderr,\n                \"error: QDB_CLUSTER_ID is undefined in your configuration\\n\");\n            global_retc = EINVAL;\n            return 0;\n          }\n          if (qdbnode.empty()) {\n            fprintf(stderr,\n                    \"error: QDB_NODE is undefined in your configuration\\n\");\n            global_retc = EINVAL;\n            return 0;\n          }\n          struct stat buf;\n          if (!::stat(qdbpath.c_str(), &buf)) {\n            fprintf(stderr,\n                    \"error: path '%s' exists - to create a new observer this \"\n                    \"path has to be changed or removed\\n\",\n                    qdbpath.c_str());\n            global_retc = EINVAL;\n            return 0;\n          } else {\n            fprintf(stderr, \"info: creating QDB under %s ...\\n\",\n                    qdbpath.c_str());\n          }\n\n          std::string kline;\n          kline = \"quarkdb-create --path \";\n          kline += qdbpath;\n          kline += \" --clusterID \";\n          kline += qdbcluster;\n          int rc = system(kline.c_str());\n          fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(),\n                  WEXITSTATUS(rc));\n          global_retc = WEXITSTATUS(rc);\n          if (!global_retc) {\n            fprintf(stderr,\n                    \"info: to get this node joining the cluster you do:\\n\");\n            fprintf(stderr, \"1 [ this node ] : systemctl start eos5-@qdb@%s\\n\",\n                    name.c_str());\n            fprintf(stderr,\n                    \"2 [ leader    ] : eos daemon config qdb %s add %s\\n\",\n                    name.c_str(), qdbnode.c_str());\n            fprintf(stderr,\n                    \"3 [ leader    ] : eos daemon config qdb %s promote %s\\n\",\n                    name.c_str(), qdbnode.c_str());\n          }\n          return (0);\n        }\n      } else if (subcmd == \"backup\") {\n        std::string qdbpath = \"/var/lib/qdb1\";\n\n        for (auto it : cfg[chapter.c_str()]) {\n          size_t pos;\n\n          if ((pos = it.find(\"redis.database\")) != std::string::npos) {\n            qdbpath = it;\n            qdbpath.erase(pos, 15);\n          }\n        }\n\n        std::string kline;\n        std::string qdblocation = qdbpath;\n        qdblocation += \"/backup/\";\n        qdblocation += std::to_string(time(NULL));\n        kline +=\n            \"export REDISCLI_AUTH=`cat /etc/eos.keytab`; redis-cli -p `cat \";\n        kline += cfile;\n        kline += \"|grep xrd.port | cut -d ' ' -f 2` <<< \\\"quarkdb-checkpoint \";\n        kline += qdblocation;\n        kline += \"\\\"\";\n        int rc = system(kline.c_str());\n        fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(),\n                WEXITSTATUS(rc));\n        global_retc = WEXITSTATUS(rc);\n        return (0);\n      }\n    }\n\n    fprintf(stderr, \"# ---------------------------------------\\n\");\n    fprintf(stderr, \"# ------------- i n i t -----------------\\n\");\n    fprintf(stderr, \"# ---------------------------------------\\n\");\n    fprintf(stderr, \"%s\\n\", cfg.Dump(\"init\", true).c_str());\n    fprintf(stderr, \"# ---------------------------------------\\n\");\n    fprintf(stderr, \"# ------------- s y s c o n f i g -------\\n\");\n    fprintf(stderr, \"# ---------------------------------------\\n\");\n    fprintf(stderr, \"%s\\n\", cfg.Dump(\"sysconfig\", true).c_str());\n    fprintf(stderr, \"# ---------------------------------------\\n\");\n    fprintf(stderr, \"# ------------- m o d u l e s -----------\\n\");\n    fprintf(stderr, \"# ---------------------------------------\\n\");\n    fprintf(stderr, \"%s\\n\", cfg.Dump(\"modules\", true).c_str());\n    fprintf(stderr, \"# ---------------------------------------\\n\");\n    fprintf(stderr, \"# ------------- x r o o t d  ------------\\n\");\n    fprintf(stderr, \"# ---------------------------------------\\n\");\n    fprintf(stderr, \"# running config file: %s\\n\", cfile.c_str());\n    fprintf(stderr, \"%s\\n\", cfg.Dump(chapter.c_str(), true).c_str());\n    fprintf(stderr, \"#########################################\\n\");\n  } else if (option == \"module-init\") {\n    std::string initfile = \"/tmp/.eos.daemon.init\";\n    std::string initsection = name.c_str();\n    initsection += \":init\";\n\n    if (!eos::common::StringConversion::SaveStringIntoFile(\n            initfile.c_str(), cfg.Dump(initsection.c_str(), true))) {\n      fprintf(stderr, \"error: unable to create startup config file '%s'\\n\",\n              initfile.c_str());\n      global_retc = errno;\n      return (0);\n    } else {\n      chmod(initfile.c_str(), S_IRWXU);\n      // run the init file\n      int rc = system(initfile.c_str());\n\n      if (WEXITSTATUS(rc)) {\n        fprintf(stderr, \"error: init script '%s' failed with errc=%d\\n\",\n                initsection.c_str(), WEXITSTATUS(rc));\n        global_retc = WEXITSTATUS(rc);\n        return (0);\n      }\n    }\n  } else if (option == \"stack\") {\n    std::string kline;\n    kline = \"test -e \";\n    kline += envfile.c_str();\n    kline += \" && eu-stack -p `cat \";\n    kline += envfile.c_str();\n    kline += \"| cut -d '&' -f 1 | cut -d '=' -f 2`\";\n    int rc = system(kline.c_str());\n    fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(), WEXITSTATUS(rc));\n    global_retc = WEXITSTATUS(rc);\n    return (0);\n  } else if (option == \"jwk\") {\n    XrdOucString jwkfile = name.c_str();\n    struct stat buf;\n    if (::stat(jwkfile.c_str(), &buf)) {\n      fprintf(stderr, \"error: jwk key file '%s' does not exist!\\n\",\n              jwkfile.c_str());\n      global_retc = ENOENT;\n      return (0);\n    }\n    std::string kline;\n    kline = \"env EOS_JWK=\\\"$(cat \\\"\";\n    kline += jwkfile.c_str();\n    kline += \"\\\")\\\" /sbin/eos-jwk-https\";\n    int rc = system(kline.c_str());\n    fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(), WEXITSTATUS(rc));\n    global_retc = WEXITSTATUS(rc);\n    return (0);\n  } else if (option == \"kill\") {\n    std::string kline;\n    kline = \"test -e \";\n    kline += envfile.c_str();\n    kline += \" && kill -9 `cat \";\n    kline += envfile.c_str();\n    kline += \"| cut -d '&' -f 1 | cut -d '=' -f 2`\";\n    int rc = system(kline.c_str());\n    fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(), WEXITSTATUS(rc));\n    global_retc = WEXITSTATUS(rc);\n    return (0);\n  } else if (option == \"stop\") {\n    std::string kline;\n    kline = \"test -e \";\n    kline += envfile.c_str();\n    kline += \" && kill -15 `cat \";\n    kline += envfile.c_str();\n    kline += \"| cut -d '&' -f 1 | cut -d '=' -f 2`\";\n    int rc = system(kline.c_str());\n    fprintf(stderr, \"info: run '%s' retc=%d\\n\", kline.c_str(), WEXITSTATUS(rc));\n    global_retc = WEXITSTATUS(rc);\n    return (0);\n  } else if (option == \"run\") {\n    if (!cfg.Has(chapter.c_str())) {\n      fprintf(stderr,\n              \"error: missing service configuration [%s] in generic config \"\n              \"file '/etc/eos/config/generic/all' or '/etc/eos/config/%s/%s'\\n\",\n              chapter.c_str(), service.c_str(), name.c_str());\n      global_retc = EINVAL;\n      return (0);\n    }\n\n    char** const envv = cfg.Env(\"sysconfig\");\n\n    for (size_t i = 0; i < 1024; ++i) {\n      if (envv[i]) {\n        fprintf(stderr, \"%s\\n\", envv[i]);\n      } else {\n        break;\n      }\n    }\n\n    if (cfg.Has(\"init\")) {\n      fprintf(stderr, \"# ---------------------------------------\\n\");\n      fprintf(stderr, \"# ------------- i n i t -----------------\\n\");\n      fprintf(stderr, \"# ---------------------------------------\\n\");\n\n      if (cfg.Has(\"unshare\")) {\n        fprintf(stderr, \"# ---------------------------------------\\n\");\n        fprintf(stderr, \"# ------------- u n s h a r e -----------\\n\");\n\n        if (unshare(CLONE_NEWNS)) {\n          fprintf(stderr,\n                  \"warning: failed to unshare mount namespace errno=%d\\n\",\n                  errno);\n        }\n\n        if (mount(\"none\", \"/\", NULL, MS_REC | MS_PRIVATE, NULL)) {\n          fprintf(stderr, \"warning: failed none mount / - errno=%d\\n\", errno);\n        }\n\n        fprintf(stderr, \"# ---------------------------------------\\n\");\n      }\n\n      for (auto it : cfg[\"init\"]) {\n        bool exit_on_failure = false;\n        fprintf(stderr, \"# run: %s\\n\", it.c_str());\n        pid_t pid;\n        std::string cline = it;\n\n        if (cline.substr(0, 4) == \"enc:\") {\n          std::string key;\n          const char* pkey = subtokenizer.GetToken();\n\n          if (!pkey) {\n            key = eos::common::StringConversion::StringFromShellCmd(\n                \"cat /etc/eos.keytab | grep u:daemon | md5sum\");\n          } else {\n            key = pkey;\n          }\n\n          std::string shakey = eos::common::SymKey::HexSha256(key);\n          XrdOucString in = cline.substr(4).c_str();\n          ;\n          XrdOucString out;\n          eos::common::SymKey::SymmetricStringDecrypt(in, out,\n                                                      (char*)shakey.c_str());\n\n          if (!out.c_str()) {\n            fprintf(stderr, \"error: encoded init line '%s' cannot be decoded\\n\",\n                    in.c_str());\n            continue;\n          }\n\n          cline = out.c_str();\n          exit_on_failure = true;\n        }\n\n        if (exit_on_failure) {\n          // test that nobody traces us ..\n          if (!(pid = fork())) {\n            pause();\n            exit(0);\n          } else {\n            if (ptrace(EOS_PTRACE_ATTACH, pid, 0, 0)) {\n              kill(pid, SIGKILL);\n              fprintf(stderr,\n                      \"error: failed to attach to forked process pid=%d \"\n                      \"errno=%d - we are untraceable\\n\",\n                      pid, errno);\n\n              if (exit_on_failure) {\n                exit(-1);\n              }\n            } else {\n              ptrace(EOS_PTRACE_DETACH, pid, 0, 0);\n              kill(pid, 9);\n              waitpid(pid, 0, 0);\n            }\n          }\n        }\n\n        if (!(pid = fork())) {\n          execle(\"/bin/bash\", \"eos-bash\", \"-c\", cline.c_str(), NULL, envv);\n          exit(0);\n        } else {\n          waitpid(pid, 0, 0);\n        }\n      }\n    }\n\n    if (ok) {\n      fprintf(stderr, \"# ---------------------------------------\\n\");\n      fprintf(stderr, \"# ------------- x r o o t d  ------------\\n\");\n      fprintf(stderr, \"# ---------------------------------------\\n\");\n      fprintf(stderr, \"# running config file: %s\\n\", cfile.c_str());\n      fprintf(stderr, \"# ---------------------------------------\\n\");\n      fprintf(stderr, \"%s\\n\", cfg.Dump(chapter.c_str(), true).c_str());\n      fprintf(stderr, \"#########################################\\n\");\n      eos::common::Path cPath(cfile.c_str());\n\n      if (!cPath.MakeParentPath(0x1ed)) {\n        fprintf(stderr, \"error: unable to create run directory '%s'\\n\",\n                cPath.GetParentPath());\n        global_retc = errno;\n        return (0);\n      }\n\n      if (!eos::common::StringConversion::SaveStringIntoFile(\n              cfile.c_str(), cfg.Dump(chapter.c_str(), true))) {\n        fprintf(stderr, \"error: unable to create startup config file '%s'\\n\",\n                cfile.c_str());\n        global_retc = errno;\n        return (0);\n      }\n\n      ::chdir(cPath.GetParentPath());\n      std::string logfile = \"/var/log/eos/xrdlog.\";\n      logfile += name.c_str();\n      // When EOS_ZSTD_LOGGING is enabled (in the [sysconfig] chapter of the\n      // service config), the EOS Logging class ingests xrootd diagnostics from\n      // STDERR and writes them as ZSTD-compressed rotating segments. In that\n      // case we must NOT hand xrootd '-l <logfile>' or it would redirect\n      // stdout/stderr to that file and the Logging pipeline would see nothing.\n      std::string zstd_log = cfg.GetValueByKey(\"sysconfig\", \"EOS_ZSTD_LOGGING\");\n      auto tolower_str = [](std::string v) {\n        for (auto& c : v) {\n          c = static_cast<char>(::tolower(static_cast<unsigned char>(c)));\n        }\n        return v;\n      };\n      const std::string v = tolower_str(zstd_log);\n      const bool use_stderr = (v == \"1\" || v == \"true\" || v == \"yes\" ||\n                               v == \"on\");\n\n      if (use_stderr) {\n        fprintf(stderr,\n                \"# EOS_ZSTD_LOGGING=%s : xrootd logs to STDERR (no -l)\\n\",\n                zstd_log.c_str());\n\n        if (service == \"qdb\") {\n          execle(\"/opt/eos/xrootd/bin/xrootd\", executable.c_str(), \"-n\",\n                 name.c_str(), \"-c\", cfile.c_str(), \"-R\", \"daemon\", \"-k\",\n                 \"fifo\", \"-s\", pidfile.c_str(), NULL, envv);\n        } else {\n          execle(\"/opt/eos/xrootd/bin/xrootd\", executable.c_str(), \"-n\",\n                 name.c_str(), \"-c\", cfile.c_str(), \"-R\", \"daemon\", \"-s\",\n                 pidfile.c_str(), NULL, envv);\n        }\n      } else {\n        if (service == \"qdb\") {\n          execle(\"/opt/eos/xrootd/bin/xrootd\", executable.c_str(), \"-n\",\n                 name.c_str(), \"-c\", cfile.c_str(), \"-l\", logfile.c_str(), \"-R\",\n                 \"daemon\", \"-k\", \"fifo\", \"-s\", pidfile.c_str(), NULL, envv);\n        } else {\n          execle(\"/opt/eos/xrootd/bin/xrootd\", executable.c_str(), \"-n\",\n                 name.c_str(), \"-c\", cfile.c_str(), \"-l\", logfile.c_str(), \"-R\",\n                 \"daemon\", \"-s\", pidfile.c_str(), NULL, envv);\n        }\n      }\n\n      return (0);\n    } else {\n      fprintf(stderr, \"error: rc=%d msg=%s\\n\", cfg.getErrc(),\n              cfg.getMsg().c_str());\n      global_retc = cfg.getErrc();\n      return (0);\n    }\n  }\n\n  return (0);\ncom_daemon_usage:\n  fprintf(\n      stderr,\n      \"Usage: daemon config|sss|kill|run|stack|stop|jwk|module-init <service> \"\n      \"[name] [subcmd]                                     :  \\n\");\n  fprintf(stderr, \"                <service> := mq | mgm | fst | qdb\\n\");\n  fprintf(stderr, \"                config                                      \"\n                  \"          -  configure a service / show configuration\\n\");\n  fprintf(stderr, \"                kill                                        \"\n                  \"          -  kill -9 a given service\\n\");\n  fprintf(stderr,\n          \"                run                                                 \"\n          \"  -  run the given service daemon optionally identified by name\\n\");\n  fprintf(stderr,\n          \"                sss recreate                                        \"\n          \"  -  re-create an instance sss key and the eosnobody keys \"\n          \"(/etc/eos.keytab,/etc/eos/fuse.sss.keytab)'\\n\");\n  fprintf(stderr, \"                stack                                       \"\n                  \"          -  print an 'eu-stack'\\n\");\n  fprintf(stderr, \"                stop                                        \"\n                  \"          -  kill -15 a given service\\n\");\n  fprintf(stderr, \"                jwk                                         \"\n                  \"          -  run a 'jwk' public key server on port 4443\\n\");\n  fprintf(stderr, \"                module-init                                 \"\n                  \"          -  run the init procedure for a module\\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"      examples: eos daemon config qdb qdb coup              \"\n                  \"          -  try to make instance [qdb] a leader of QDB\\n\");\n  fprintf(stderr, \"                eos daemon config qdb qdb info              \"\n                  \"          -  show raft-info for the [qdb] QDB instance\\n\");\n  fprintf(stderr, \"                eos daemon config qdb qdb remove host:port  \"\n                  \"          -  remove a member of the qdb cluster\\n\");\n  fprintf(stderr, \"                eos daemon config qdb qdb add host:port     \"\n                  \"          -  add an observer to the qdb cluster\\n\");\n  fprintf(stderr,\n          \"                eos daemon config qdb qdb promote host:port         \"\n          \"  -  promote an observer to a full member of the qdb cluster\\n\");\n  fprintf(stderr, \"                eos daemon config qdb qdb new observer      \"\n                  \"          -  create a new observer\\n\");\n  fprintf(stderr, \"                eos daemon config fst fst.1                 \"\n                  \"          -  show the init,sysconfig and xrootd config for \"\n                  \"the [fst.1] FST service\\n\");\n  fprintf(stderr, \"                eos daemon kill mq                          \"\n                  \"          -  shoot the MQ service with signal -9\\n\");\n  fprintf(stderr,\n          \"                eos daemon stop mq                                  \"\n          \"  -  gracefully shut down the MQ service with signal -15\\n\");\n  fprintf(stderr, \"                eos daemon stack mgm                        \"\n                  \"          -  take an 'eu-stack' of the MGM service\\n\");\n  fprintf(stderr, \"                eos daemon run fst fst.1                    \"\n                  \"          -  run the fst.1 subservice FST\\n\");\n  global_retc = EINVAL;\n  return (0);\n#endif\n}\n\n// Legacy compatibility symbol required by ConsoleMain and other modules\nint\ncom_daemon(char* arg)\n{\n  return native_com_daemon(arg);\n}\n\nnamespace {\nclass DaemonCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"daemon\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Run EOS daemon control\";\n  }\n  bool\n  requiresMgm(const std::string&) const override\n  {\n    return false;\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n#ifdef __APPLE__\n    fprintf(stderr, \"error: daemon command is not support on OSX\\n\");\n    global_retc = EINVAL;\n    return 0;\n#else\n    return native_com_daemon((char*)joined.c_str());\n#endif\n  }\n  void\n  printHelp() const override\n  {\n  }\n};\n} // namespace\n\nvoid\nRegisterDaemonNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<DaemonCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/debug-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: debug-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/ICmdHelper.hh\"\n#include <CLI/CLI.hpp>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeDebugHelp()\n{\n  std::ostringstream oss;\n  oss << \"Usage: debug get|this|<level> [node-queue] [--filter <unitlist>]\\n\"\n      << \"'[eos] debug ...' allows to get or set the verbosity of the EOS \"\n         \"log files in MGM and FST services.\\n\\n\"\n      << \"debug get : retrieve the current log level for the mgm and fsts \"\n         \"node-queue\\n\\n\"\n      << \"debug this : toggle EOS shell debug mode\\n\\n\"\n      << \"debug  <level> [--filter <unitlist>] : set the MGM where the \"\n         \"console is connected to into debug level <level>\\n\\n\"\n      << \"debug  <level> <node-queue> [--filter <unitlist>] : set the \"\n         \"<node-queue> into debug level <level>.\\n\"\n      << \"  - <node-queue> are internal EOS names e.g. \"\n         \"'/eos/<hostname>:<port>/fst'\\n\"\n      << \"  - <unitlist> is a comma separated list of strings of software \"\n         \"units which should be filtered out in the message log!\\n\\n\"\n      << \"The allowed debug levels are: \"\n         \"debug,info,warning,notice,err,crit,alert,emerg\\n\";\n  return oss.str();\n}\n\nvoid ConfigureDebugApp(CLI::App& app,\n                      std::string& mode,\n                      std::string& node,\n                      std::string& filter)\n{\n  app.name(\"debug\");\n  app.description(\"Set debug level\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeDebugHelp();\n      }));\n  app.add_option(\"mode\", mode, \"get|this|<level>\")->required();\n  app.add_option(\"node\", node, \"node-queue (e.g. /eos/host:port/fst)\");\n  app.add_option(\"--filter\", filter, \"comma-separated unit list to filter\");\n}\n\nclass DebugCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"debug\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Set debug level\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (args.empty() || wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    std::string mode;\n    std::string node;\n    std::string filter;\n    ConfigureDebugApp(app, mode, node, filter);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    std::string cmd;\n    if (mode == \"get\" || mode == \"this\") {\n      cmd = mode;\n    } else {\n      cmd = mode;\n      if (!node.empty())\n        cmd += \" \" + node;\n      if (!filter.empty())\n        cmd += \" --filter \" + filter;\n    }\n\n    class LocalHelper : public ICmdHelper {\n    public:\n      using ICmdHelper::ICmdHelper;\n      bool\n      ParseCommand(const char* arg) override\n      {\n        eos::console::DebugProto* debugproto = mReq.mutable_debug();\n        eos::common::StringTokenizer tokenizer(arg);\n        tokenizer.GetLine();\n        std::string token;\n        if (!tokenizer.NextToken(token)) {\n          return false;\n        }\n        if (token == \"get\") {\n          auto* get = debugproto->mutable_get();\n          get->set_placeholder(true);\n        } else if (token == \"this\") {\n          global_debug = !global_debug;\n          gGlobalOpts.mDebug = global_debug;\n          fprintf(stderr, \"info: toggling shell debugmode to debug=%d\\n\",\n                  global_debug);\n          mIsLocal = true;\n        } else {\n          auto* set = debugproto->mutable_set();\n          set->set_debuglevel(token);\n          if (tokenizer.NextToken(token)) {\n            if (token == \"--filter\") {\n              if (!tokenizer.NextToken(token))\n                return false;\n              set->set_filter(token);\n            } else {\n              set->set_nodename(token);\n              if (tokenizer.NextToken(token)) {\n                if (token != \"--filter\")\n                  return false;\n                else {\n                  if (!tokenizer.NextToken(token))\n                    return false;\n                  set->set_filter(token);\n                }\n              }\n            }\n          }\n        }\n        return true;\n      }\n    };\n\n    LocalHelper helper(gGlobalOpts);\n    if (!helper.ParseCommand(cmd.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = helper.Execute();\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeDebugHelp().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterDebugNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<DebugCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/devices-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: devices-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/ICmdHelper.hh\"\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeDevicesHelp()\n{\n  return \"Usage: devices ls [-l] [-m] [--refresh]\\n\\n\"\n         \"Print statistics per space of all storage devices based on S.M.A.R.T.\\n\\n\"\n         \"Options:\\n\"\n         \"  -l         print S.M.A.R.T information for each configured filesystem\\n\"\n         \"  -m         print monitoring output format (key=val)\\n\"\n         \"  --refresh  force reparse of current S.M.A.R.T information\\n\\n\"\n         \"Use 'eos --json devices ls' for JSON output.\\n\";\n}\n\nvoid ConfigureDevicesApp(CLI::App& app)\n{\n  app.name(\"devices\");\n  app.description(\"Get Device Information\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeDevicesHelp();\n      }));\n}\n\n// Ported DevicesHelper from com_proto_devices.cc\nclass DevicesHelper : public ICmdHelper {\npublic:\n  explicit DevicesHelper(const GlobalOptions& opts) : ICmdHelper(opts)\n  {\n    mIsAdmin = true;\n  }\n  bool\n  ParseCommand(const char* arg) override\n  {\n    XrdOucString token;\n    eos::console::DevicesProto* devices = mReq.mutable_devices();\n    eos::common::StringTokenizer tokenizer(arg);\n    tokenizer.GetLine();\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    eos::console::DevicesProto_LsProto* ls = devices->mutable_ls();\n    ls->set_outformat(eos::console::DevicesProto_LsProto::NONE);\n\n    if (token == \"ls\") {\n      do {\n        tokenizer.NextToken(token);\n        if (!token.length()) {\n          return true;\n        }\n        if (token == \"-l\") {\n          ls->set_outformat(eos::console::DevicesProto_LsProto::LISTING);\n        } else if (token == \"-m\") {\n          ls->set_outformat(eos::console::DevicesProto_LsProto::MONITORING);\n        } else if (token == \"--refresh\") {\n          ls->set_refresh(true);\n        } else {\n          return false;\n        }\n      } while (token.length());\n    } else {\n      return false;\n    }\n\n    return true;\n  }\n};\n\nclass DevicesProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"devices\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Get Device Information\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    DevicesHelper helper(gGlobalOpts);\n    if (!helper.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = helper.Execute(true, true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureDevicesApp(app);\n    fprintf(stderr, \"%s\", app.help().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterDevicesProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<DevicesProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/df-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: df-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/ICmdHelper.hh\"\n#include <iomanip>\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeDfHelp()\n{\n  std::ostringstream oss;\n  oss << \" usage:\\n\"\n      << \"df [-m|-H|-b] [path]\\n\"\n      << \"'[eos] df ...' print unix like 'df' information (1024 base)\\n\"\n      << std::endl\n      << \"Options:\\n\"\n      << std::endl\n      << \"-m : print in monitoring format\\n\"\n      << \"-H : print human readable in units of 1000\\n\"\n      << \"-b : print raw bytes/number values\\n\"\n      << std::endl;\n  return oss.str();\n}\n\nvoid ConfigureDfApp(CLI::App& app)\n{\n  app.name(\"df\");\n  app.description(\"Get df output\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeDfHelp();\n      }));\n}\n\n// Ported DfHelper from com_proto_df.cc\nclass DfHelper : public ICmdHelper {\npublic:\n  explicit DfHelper(const GlobalOptions& opts) : ICmdHelper(opts) {}\n  bool\n  ParseCommand(const char* arg) override\n  {\n    eos::console::DfProto* dfproto = mReq.mutable_df();\n    eos::common::StringTokenizer tokenizer(arg);\n    tokenizer.GetLine();\n    std::string token;\n\n    dfproto->set_si(true);\n    dfproto->set_readable(true);\n\n    if (!tokenizer.NextToken(token)) {\n      return true;\n    }\n\n    if (token == \"-m\") {\n      dfproto->set_monitoring(true);\n      dfproto->set_readable(false);\n    } else if (token == \"-H\") {\n      dfproto->set_si(false);\n      dfproto->set_readable(true);\n    } else if (token == \"-b\") {\n      dfproto->set_si(false);\n      dfproto->set_readable(false);\n    } else {\n      if (token.substr(0, 1) != \"/\") {\n        return false;\n      }\n    }\n\n    std::string path = token;\n    if (tokenizer.NextToken(token)) {\n      if (token.substr(0, 1) == \"-\") {\n        return false;\n      }\n      if (token.substr(0, 1) != \"/\") {\n        return false;\n      }\n      path = token;\n    }\n\n    if (tokenizer.NextToken(token)) {\n      return false;\n    }\n    dfproto->set_path(path);\n    return true;\n  }\n};\n\nclass DfProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"df\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Get df output\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i) {\n        oss << ' ';\n      }\n      if (args[i].find(' ') != std::string::npos)\n        oss << std::quoted(args[i]);\n      else\n        oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    DfHelper helper(*ctx.globalOpts);\n    if (!helper.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = helper.Execute();\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureDfApp(app);\n    fprintf(stderr, \"%s\", app.help().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterDfProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<DfProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/du-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: du-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <memory>\n#include <sstream>\nnamespace {\nclass DuCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"du\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Get du output\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    IConsoleCommand* findCmd = CommandRegistry::instance().find(\"find\");\n    if (!findCmd) {\n      fprintf(stderr, \"error: 'find' command not available\\n\");\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i) {\n        oss << ' ';\n      }\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n\n    if (wants_help(joined.c_str(), true)) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    eos::common::StringTokenizer tokenizer(joined.c_str());\n    tokenizer.GetLine();\n    std::string token;\n    bool printfiles = false;\n    bool printreadable = false;\n    bool printsummary = false;\n    bool printsi = false;\n    std::string path;\n\n    do {\n      if (!tokenizer.NextToken(token)) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n\n      if (token == \"-a\") {\n        printfiles = true;\n      } else if (token == \"-h\") {\n        printreadable = true;\n      } else if (token == \"-s\") {\n        printsummary = true;\n      } else if (token == \"--si\") {\n        printsi = true;\n      } else {\n        path = abspath(token.c_str());\n        break;\n      }\n    } while (1);\n\n    std::vector<std::string> findArgs;\n    findArgs.emplace_back(\"--du\");\n    if (!printfiles) {\n      findArgs.emplace_back(\"-d\");\n    }\n    if (printsi) {\n      findArgs.emplace_back(\"--du-si\");\n    }\n    if (printreadable) {\n      findArgs.emplace_back(\"--du-h\");\n    }\n    if (printsummary) {\n      findArgs.emplace_back(\"--maxdepth\");\n      findArgs.emplace_back(\"0\");\n    }\n    findArgs.emplace_back(path);\n\n    int rc = findCmd->run(findArgs, ctx);\n    global_retc = rc;\n    return rc;\n  }\n\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"usage:\\n\"\n                    \"du [-a][-h][-s][--si] path\\n\"\n                    \"'[eos] du ...' print unix like 'du' information showing \"\n                    \"subtreesize for directories\\n\"\n                    \"\\n\"\n                    \"Options:\\n\"\n                    \"\\n\"\n                    \"-a   : print also for files\\n\"\n                    \"-h   : print human readable in units of 1000\\n\"\n                    \"-s   : print only the summary\\n\"\n                    \"--si : print in si units\\n\");\n  }\n};\n} // namespace\n\nvoid\nRegisterDuNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<DuCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/du-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: du-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <memory>\n#include <sstream>\nnamespace {\nstd::string MakeDuHelp()\n{\n  return \"Usage: du [-a] [-h] [-s] [--si] path\\n\\n\"\n         \"Print unix-like 'du' information showing subtree size for directories.\\n\\n\"\n         \"Options:\\n\"\n         \"  -a     print also for files\\n\"\n         \"  -h     print human readable in units of 1000\\n\"\n         \"  -s     print only the summary\\n\"\n         \"  --si   print in SI units\\n\";\n}\n\nvoid ConfigureDuApp(CLI::App& app)\n{\n  app.name(\"du\");\n  app.description(\"Get du output\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeDuHelp();\n      }));\n}\n\nclass DuCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"du\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Get du output\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    IConsoleCommand* findCmd = CommandRegistry::instance().find(\"find\");\n    if (!findCmd) {\n      fprintf(stderr, \"error: 'find' command not available\\n\");\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i) {\n        oss << ' ';\n      }\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n\n    if (wants_help(joined.c_str(), true)) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    eos::common::StringTokenizer tokenizer(joined.c_str());\n    tokenizer.GetLine();\n    std::string token;\n    bool printfiles = false;\n    bool printreadable = false;\n    bool printsummary = false;\n    bool printsi = false;\n    std::string path;\n\n    do {\n      if (!tokenizer.NextToken(token)) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n\n      if (token == \"-a\") {\n        printfiles = true;\n      } else if (token == \"-h\") {\n        printreadable = true;\n      } else if (token == \"-s\") {\n        printsummary = true;\n      } else if (token == \"--si\") {\n        printsi = true;\n      } else {\n        path = abspath(token.c_str());\n        break;\n      }\n    } while (1);\n\n    std::vector<std::string> findArgs;\n    findArgs.emplace_back(\"--du\");\n    if (!printfiles) {\n      findArgs.emplace_back(\"-d\");\n    }\n    if (printsi) {\n      findArgs.emplace_back(\"--du-si\");\n    }\n    if (printreadable) {\n      findArgs.emplace_back(\"--du-h\");\n    }\n    if (printsummary) {\n      findArgs.emplace_back(\"--maxdepth\");\n      findArgs.emplace_back(\"0\");\n    }\n    findArgs.emplace_back(path);\n\n    int rc = findCmd->run(findArgs, ctx);\n    global_retc = rc;\n    return rc;\n  }\n\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureDuApp(app);\n    fprintf(stderr, \"%s\", app.help().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterDuNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<DuCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/evict-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: evict-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include <algorithm>\n#include \"console/ConsoleMain.hh\"\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeEvictHelp()\n{\n  std::ostringstream oss;\n  oss << \"Usage: evict [--fsid <fsid>] [--ignore-removal-on-fst] \"\n         \"[--ignore-evict-counter] <path>|fid:<fid-dec>|fxid:<fid-hex> \"\n         \"[<path>|fid:<fid-dec>|fxid:<fid-hex>] ...\\n\"\n      << \"    Removes disk replicas of the given files, separated by space\\n\\n\"\n      << \"Options:\\n\"\n      << \"    --ignore-evict-counter  : Force eviction by bypassing evict \"\n         \"counter\\n\"\n      << \"    --fsid <fsid>           : Evict disk copy only from a single \"\n         \"fsid\\n\"\n      << \"    --ignore-removal-on-fst : Ignore file removal on fst, \"\n         \"namespace-only operation\\n\\n\"\n      << \"    This command requires 'write' and 'p' acl flag permission\\n\";\n  return oss.str();\n}\n\nvoid ConfigureEvictApp(CLI::App& app,\n                       bool& opt_ignore_evict_counter,\n                       bool& opt_ignore_removal_on_fst,\n                       std::string& fsid,\n                       std::vector<std::string>& paths)\n{\n  app.name(\"evict\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeEvictHelp();\n      }));\n  app.add_flag(\"--ignore-evict-counter\", opt_ignore_evict_counter,\n               \"ignore evict counter\");\n  app.add_flag(\"--ignore-removal-on-fst\", opt_ignore_removal_on_fst, \"ns-only\");\n  app.add_option(\"--fsid\", fsid, \"single fsid\");\n  app.add_option(\"paths\", paths);\n}\n\nclass EvictCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"evict\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Evict disk replicas of a file if it has tape replicas\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (args.empty() || wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    app.allow_extras();\n    bool opt_ignore_evict_counter = false;\n    bool opt_ignore_removal_on_fst = false;\n    std::string fsid;\n    std::vector<std::string> pos;\n    ConfigureEvictApp(app, opt_ignore_evict_counter, opt_ignore_removal_on_fst,\n                      fsid, pos);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    if (pos.empty()) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    XrdOucString in = \"mgm.cmd=evict\"; // using proto interface\n    // Build protobuf-like request via client_command wrapper\n    // Fallback to legacy: use com_evict style mgm.cmd if available\n    // Here, we map to mgm.cmd=evict helper expected by backend\n    if (opt_ignore_evict_counter)\n      in += \"&mgm.evict.ignoreevictcounter=1\";\n    if (opt_ignore_removal_on_fst)\n      in += \"&mgm.evict.ignoreremovalonfst=1\";\n    if (!fsid.empty()) {\n      in += \"&mgm.evict.fsid=\";\n      in += fsid.c_str();\n    }\n    for (const auto& a : pos) {\n      XrdOucString path = a.c_str();\n      unsigned long long fid = 0ull;\n      if (Path2FileDenominator(path, fid)) {\n        in += \"&mgm.evict.fid=\";\n        in += std::to_string(fid).c_str();\n      } else {\n        XrdOucString ap = abspath(path.c_str());\n        in += \"&mgm.evict.path=\";\n        in += ap;\n      }\n    }\n    global_retc = ctx.outputResult(ctx.clientCommand(in, false, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    bool opt_ignore_evict_counter = false;\n    bool opt_ignore_removal_on_fst = false;\n    std::string fsid;\n    std::vector<std::string> pos;\n    ConfigureEvictApp(app, opt_ignore_evict_counter, opt_ignore_removal_on_fst,\n                      fsid, pos);\n    const std::string help = app.help();\n    std::cerr << help << std::endl;\n  }\n};\n} // namespace\n\nvoid\nRegisterEvictNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<EvictCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/file-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: file-native.cc\n// ----------------------------------------------------------------------\n\n#define __STDC_FORMAT_MACROS\n#include <inttypes.h>\n\n#include \"common/FileId.hh\"\n#include \"common/Fmd.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/Logging.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/SymKeys.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleCompletion.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdCl/XrdClURL.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <algorithm>\n#include <cctype>\n#include <cstdlib>\n#include <cstring>\n#include <errno.h>\n#include <limits>\n#include <memory>\n#include <openssl/sha.h>\n#include <set>\n#include <sstream>\n#include <time.h>\n#include <vector>\n\n#ifdef __APPLE__\n#define ECOMM 70\n#endif\n\nnamespace {\nstd::string MakeFileHelp()\n{\n  std::ostringstream oss;\n  oss << \"Usage: file <subcmd> [args...]\\n\\n\"\n      << \"'[eos] file ..' provides the file management interface of EOS.\\n\\n\";\n  oss << \"adjustreplica [--nodrop] [<path>|fid:<fid-dec>|fxid:<fid-hex>] [space [subgroup]] [--exclude-fs <fsid>]\\n\"\n      << \"  Tries to bring files with replica layouts to the nominal replica level [need root].\\n\"\n      << \"  --exclude-fs <fsid>  exclude the given filesystem from being used for the replica adjustment\\n\\n\";\n  oss << \"check [<path>|fid:<fid-dec>|fxid:<fid-hex>] [%size%checksum%nrep%diskchecksum%force%output%silent]\\n\"\n      << \"  Retrieves stat information from the physical replicas and verifies correctness.\\n\"\n      << \"  %size        return EFAULT if mismatch between the size meta data information\\n\"\n      << \"  %checksum    return EFAULT if mismatch between the checksum meta data information\\n\"\n      << \"  %nrep        return EFAULT if mismatch between layout number of replicas and existing replicas\\n\"\n      << \"  %diskchecksum  return EFAULT if mismatch between disk checksum on FST and reference checksum\\n\"\n      << \"  %silent      suppress all information for each replica to be printed\\n\"\n      << \"  %force       force to get the MD even if the node is down\\n\"\n      << \"  %output      print lines with inconsistency information\\n\\n\";\n  oss << \"convert [<path>|fid:<fid-dec>|fxid:<fid-hex>] [<layout>:<stripes>|<layout-id>|<sys.attribute.name>] \"\n      << \"[target-space] [placement-policy] [checksum] [--rewrite]\\n\"\n      << \"  Convert the layout of a file.\\n\"\n      << \"  <layout>:<stripes>   target layout and number of stripes\\n\"\n      << \"  <layout-id>          hexadecimal layout id\\n\"\n      << \"  <conversion-name>    name of sys.conversion.<name> in parent directory defining target layout\\n\"\n      << \"  <target-space>       optional name of target space or group e.g. default or default.3\\n\"\n      << \"  <placement-policy>   scattered, hybrid:<geotag>, gathered:<geotag>\\n\"\n      << \"  <checksum>           optional target checksum name (md5, adler, etc.)\\n\"\n      << \"  --rewrite            run conversion rewriting the file, creating new copies and dropping old\\n\\n\";\n  oss << \"copy [-f] [-s] [-c] <src> <dst>\\n\"\n      << \"  Synchronous third party copy from <src> to <dst>.\\n\"\n      << \"  <src>  source file or directory (<path>|fid:<fid-dec>|fxid:<fid-hex>)\\n\"\n      << \"  <dst>  destination file (if source is file) or directory\\n\"\n      << \"  -f     force overwrite\\n\"\n      << \"  -s     don't print output\\n\"\n      << \"  -c     clone the file (keep ctime, mtime)\\n\\n\";\n  oss << \"drop [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid> [-f]\\n\"\n      << \"  Drop the file from <fsid>. -f force removes replica without trigger/wait for deletion \"\n      << \"(used to retire a filesystem).\\n\\n\";\n  oss << \"info [<path>|fid:<fid-dec>|fxid:<fid-hex>|pid:<cid-dec>|pxid:<cid-hex>|inode:<inode-dec>] [options]\\n\"\n      << \"  Show file info. Options: --path, --fid, --fxid, --size, --checksum, --fullpath, \"\n      << \"--proxy, -m, -s|--silent.\\n\\n\";\n  oss << \"layout <path>|fid:<fid-dec>|fxid:<fid-hex> -stripes <n>\\n\"\n      << \"  Change the number of stripes of a file with replica layout to <n>.\\n\"\n      << \"layout <path>|fid:<fid-dec>|fxid:<fid-hex> -checksum <checksum-type>\\n\"\n      << \"  Change the checksum-type of a file to <checksum-type>.\\n\"\n      << \"layout <path>|fid:<fid-dec>|fxid:<fid-hex> -type <hex-layout-type>\\n\"\n      << \"  Change the layout-type of a file to <hex-layout-type> (as shown by file info).\\n\\n\";\n  oss << \"move [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid1> <fsid2>\\n\"\n      << \"  Move the file from <fsid1> to <fsid2>.\\n\\n\";\n  oss << \"purge <path> [purge-version]\\n\"\n      << \"  Keep maximum <purge-version> versions of a file. If not specified apply sys.versioning attribute.\\n\\n\";\n  oss << \"rename [<path>|fid:<fid-dec>|fxid:<fid-hex>] <new>\\n\"\n      << \"  Rename from <old> to <new> name (works for files and directories).\\n\\n\";\n  oss << \"rename_with_symlink <source_file> <destination_dir>\\n\"\n      << \"  Rename/move source file to destination directory atomically:\\n\"\n      << \"  - move file to destination directory\\n\"\n      << \"  - create symlink in the source directory to the new location\\n\\n\";\n  oss << \"replicate [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid1> <fsid2>\\n\"\n      << \"  Replicate file part on <fsid1> to <fsid2>.\\n\\n\";\n  oss << \"share <path> [lifetime]\\n\"\n      << \"  Create a share link. <lifetime> defaults to 28d (1, 1s, 1d, 1w, 1mo, 1y, ...).\\n\\n\";\n  oss << \"symlink [-f] <name> <target>\\n\"\n      << \"  Create a symlink with <name> pointing to <target>. -f force overwrite.\\n\\n\";\n  oss << \"tag <path>|fid:<fid-dec>|fxid:<fid-hex> +|-|~<fsid>\\n\"\n      << \"  Add/remove/unlink a filesystem location to/from a file in the location index \"\n      << \"(does not move any data).\\n\"\n      << \"  Unlink keeps the location in the list of deleted files (gets a deletion request).\\n\\n\";\n  oss << \"touch [-a] [-n] [-0] <path>|fid:<fid-dec>|fxid:<fid-hex> [linkpath|size [hexchecksum]]\\n\"\n      << \"  Create/touch a 0-size/0-replica file if <path> does not exist or update mtime of existing file.\\n\"\n      << \"  -n        disable placement logic (default uses placement)\\n\"\n      << \"  -0        truncate a file\\n\"\n      << \"  -a        absorb (adopt) a file from hardlink path - file disappears from given path and is taken under EOS FST control\\n\"\n      << \"  linkpath  hard- or softlink the touched file to a shared filesystem\\n\"\n      << \"  size      preset the size for a new touched file\\n\"\n      << \"  hexchecksum  checksum information for a new touched file\\n\"\n      << \"touch -l <path>|fid:<fid-dec>|fxid:<fid-hex> [<lifetime> [<audience>=user|app]]\\n\"\n      << \"  Touch and create an extended attribute lock with <lifetime> (default 24h).\\n\"\n      << \"  <audience> relaxes lock owner: same user or same app (default: both must match).\\n\"\n      << \"  EBUSY if lock held by another; second call by same caller extends lifetime.\\n\"\n      << \"  Use with 'eos -a application' to tag a client with an application for the lock.\\n\"\n      << \"touch -u <path>|fid:<fid-dec>|fxid:<fid-hex>\\n\"\n      << \"  Remove an extended attribute lock. No error if no lock; EBUSY if held by someone else.\\n\\n\";\n  oss << \"verify <path>|fid:<fid-dec>|fxid:<fid-hex> [<fsid>] [-checksum] [-commitchecksum] [-commitsize] [-commitfmd] [-rate <rate>] [-resync]\\n\"\n      << \"  Verify a file against the disk images.\\n\"\n      << \"  <fsid>        verify only the replica on <fsid>\\n\"\n      << \"  -checksum     trigger checksum calculation during verification\\n\"\n      << \"  -commitchecksum  commit the computed checksum to the MGM\\n\"\n      << \"  -commitsize   commit the file size to the MGM\\n\"\n      << \"  -commitfmd    commit the FMD to the MGM\\n\"\n      << \"  -rate <rate>  restrict verification speed to <rate> per node\\n\"\n      << \"  -resync       ask all locations to resync their file md records\\n\\n\";\n  oss << \"version <path> [purge-version]\\n\"\n      << \"  Create a new version of a file by cloning. <purge-version> defines max versions to keep.\\n\\n\";\n  oss << \"versions <path>|fid:<fid-dec>|fxid:<fid-hex> [grab-version]\\n\"\n      << \"  List versions of a file, or grab a version [grab-version].\\n\\n\";\n  oss << \"workflow <path>|fid:<fid-dec>|fxid:<fid-hex> <workflow> <event>\\n\"\n      << \"  Trigger workflow <workflow> with event <event> on <path>.\\n\";\n  return oss.str();\n}\n\nvoid ConfigureFileApp(CLI::App& app, std::string& subcmd)\n{\n  app.name(\"file\");\n  app.description(\"File Handling\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeFileHelp();\n      }));\n  app.add_option(\"subcmd\", subcmd,\n                 \"rename|rename_with_symlink|symlink|drop|touch|move|copy|\"\n                 \"replicate|purge|version|versions|layout|tag|convert|verify|\"\n                 \"adjustreplica|check|share|workflow|info\")\n      ->required();\n}\n\nvoid\nAppendEncodedPath(XrdOucString& in, const XrdOucString& raw, bool absolutize)\n{\n  XrdOucString raw_copy = raw;\n  if (raw_copy.beginswith(\"fid:\") || raw_copy.beginswith(\"fxid:\") ||\n      raw_copy.beginswith(\"pid:\") || raw_copy.beginswith(\"pxid:\") ||\n      raw_copy.beginswith(\"inode:\") || raw_copy.beginswith(\"cid:\") ||\n      raw_copy.beginswith(\"cxid:\")) {\n    in += \"&mgm.path=\";\n    in += raw;\n    return;\n  }\n  XrdOucString path = raw;\n  if (absolutize) {\n    path = abspath(path.c_str());\n  }\n  XrdOucString esc =\n      eos::common::StringConversion::curl_escaped(path.c_str()).c_str();\n  in += \"&mgm.path=\";\n  in += esc;\n  in += \"&eos.encodepath=1\";\n}\n\nint\nGetRemoteFmdFromLocalDb(const char* manager, const char* shexfid,\n                        const char* sfsid, eos::common::FmdHelper& fmd)\n{\n  if ((!manager) || (!shexfid) || (!sfsid)) {\n    return EINVAL;\n  }\n\n  int rc = 0;\n  XrdCl::Buffer arg;\n  XrdCl::Buffer* response = 0;\n  XrdCl::XRootDStatus status;\n  XrdOucString fmdquery = \"/?fst.pcmd=getfmd&fst.getfmd.fid=\";\n  fmdquery += shexfid;\n  fmdquery += \"&fst.getfmd.fsid=\";\n  fmdquery += sfsid;\n  XrdOucString address = \"root://\";\n  address += manager;\n  address += \"//dummy\";\n  XrdCl::URL url(address.c_str());\n\n  if (!url.IsValid()) {\n    eos_static_err(\"error=URL is not valid: %s\", address.c_str());\n    return EINVAL;\n  }\n\n  std::unique_ptr<XrdCl::FileSystem> fs(new XrdCl::FileSystem(url));\n\n  if (!fs) {\n    eos_static_err(\"error=failed to get new FS object\");\n    return EINVAL;\n  }\n\n  arg.FromString(fmdquery.c_str());\n  status = fs->Query(XrdCl::QueryCode::OpaqueFile, arg, response);\n\n  if (status.IsOK()) {\n    rc = 0;\n    eos_static_debug(\n        \"got replica file meta data from server %s for fxid=%s fsid=%s\",\n        manager, shexfid, sfsid);\n  } else {\n    rc = ECOMM;\n    eos_static_err(\n        \"Unable to retrieve meta data from server %s for fxid=%s fsid=%s\",\n        manager, shexfid, sfsid);\n  }\n\n  if (rc) {\n    delete response;\n    return EIO;\n  }\n\n  if (!strncmp(response->GetBuffer(), \"ERROR\", 5)) {\n    // remote side couldn't get the record\n    eos_static_info(\n        \"Unable to retrieve meta data on remote server %s for fxid=%s fsid=%s\",\n        manager, shexfid, sfsid);\n    delete response;\n    return ENODATA;\n  }\n\n  // get the remote file meta data into an env hash\n  XrdOucEnv fmdenv(response->GetBuffer());\n\n  if (!eos::common::EnvToFstFmd(fmdenv, fmd)) {\n    int envlen;\n    eos_static_err(\"Failed to unparse file meta data %s\", fmdenv.Env(envlen));\n    delete response;\n    return EIO;\n  }\n\n  // very simple check\n  if (fmd.mProtoFmd.fid() != eos::common::FileId::Hex2Fid(shexfid)) {\n    eos_static_err(\"Uups! Received wrong meta data from remote server - fid \"\n                   \"is %lu instead of %lu !\",\n                   fmd.mProtoFmd.fid(),\n                   eos::common::FileId::Hex2Fid(shexfid));\n    delete response;\n    return EIO;\n  }\n\n  delete response;\n  return 0;\n}\n\nint\nRunFileCheck(XrdOucString path, const std::string& option, CommandContext& ctx)\n{\n  XrdOucString in = \"mgm.cmd=file\";\n\n  bool absolutize = (!path.beginswith(\"fid:\")) && (!path.beginswith(\"fxid:\"));\n\n  in += \"&mgm.subcmd=getmdlocation\";\n  in += \"&mgm.format=fuse\";\n  AppendEncodedPath(in, path, absolutize);\n\n  // Eventually disable json format to avoid parsing issues\n  bool old_json = json;\n  if (old_json) {\n    json = false;\n  }\n\n  XrdOucEnv* result = ctx.clientCommand(in, false, nullptr);\n\n  if (old_json) {\n    json = true;\n  }\n\n  if (!result) {\n    fprintf(stderr, \"error: getmdlocation query failed\\n\");\n    global_retc = EINVAL;\n    return 0;\n  }\n\n  int envlen = 0;\n  std::unique_ptr<XrdOucEnv> newresult(new XrdOucEnv(result->Env(envlen)));\n  delete result;\n\n  if (!envlen) {\n    fprintf(stderr, \"error: couldn't get meta data information\\n\");\n    global_retc = EIO;\n    return 0;\n  }\n\n  char* ptr = newresult->Get(\"mgm.proc.retc\");\n\n  if (ptr) {\n    int retc_getmdloc = 0;\n\n    try {\n      retc_getmdloc = std::stoi(ptr);\n    } catch (...) {\n      retc_getmdloc = EINVAL;\n    }\n\n    if (retc_getmdloc) {\n      fprintf(stderr, \"error: failed getmdlocation command, errno=%i\",\n              retc_getmdloc);\n      global_retc = retc_getmdloc;\n      return 0;\n    }\n  }\n\n  XrdOucString ns_path = newresult->Get(\"mgm.nspath\");\n  XrdOucString checksumtype = newresult->Get(\"mgm.checksumtype\");\n  XrdOucString checksum = newresult->Get(\"mgm.checksum\");\n  uint64_t mgm_size = std::stoull(newresult->Get(\"mgm.size\"));\n  bool silent_cmd = ((option.find(\"%silent\") != std::string::npos) ||\n                     ctx.silent);\n\n  if (!silent_cmd) {\n    fprintf(stdout, \"path=\\\"%s\\\" fxid=\\\"%4s\\\" size=\\\"%llu\\\" nrep=\\\"%s\\\" \"\n            \"checksumtype=\\\"%s\\\" checksum=\\\"%s\\\"\\n\",\n            ns_path.c_str(), newresult->Get(\"mgm.fid0\"),\n            (unsigned long long)mgm_size, newresult->Get(\"mgm.nrep\"),\n            checksumtype.c_str(), newresult->Get(\"mgm.checksum\"));\n  }\n\n  std::string err_label;\n  std::set<std::string> set_errors;\n  int nrep_online = 0;\n  int i = 0;\n\n  for (i = 0; i < 255; ++i) {\n    err_label = \"none\";\n    XrdOucString repurl = \"mgm.replica.url\";\n    repurl += i;\n    XrdOucString repfid = \"mgm.fid\";\n    repfid += i;\n    XrdOucString repfsid = \"mgm.fsid\";\n    repfsid += i;\n    XrdOucString repbootstat = \"mgm.fsbootstat\";\n    repbootstat += i;\n    XrdOucString repfstpath = \"mgm.fstpath\";\n    repfstpath += i;\n\n    if (!newresult->Get(repurl.c_str())) {\n      break;\n    }\n\n    // Query the FSTs for stripe info\n    XrdCl::StatInfo* stat_info = 0;\n    XrdCl::XRootDStatus status;\n    std::ostringstream oss;\n    oss << \"root://\" << newresult->Get(repurl.c_str()) << \"//dummy\";\n    XrdCl::URL url(oss.str());\n\n    if (!url.IsValid()) {\n      fprintf(stderr, \"error: URL is not valid: %s\", oss.str().c_str());\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    // Get XrdCl::FileSystem object\n    std::unique_ptr<XrdCl::FileSystem> fs {new XrdCl::FileSystem(url)};\n\n    if (!fs) {\n      fprintf(stderr, \"error: failed to get new FS object\");\n      global_retc = ECOMM;\n      return 0;\n    }\n\n    XrdOucString bs = newresult->Get(repbootstat.c_str());\n    bool down = (bs != \"booted\");\n\n    if (down && (option.find(\"%force\") == std::string::npos)) {\n      err_label = \"DOWN\";\n      set_errors.insert(err_label);\n\n      if (!silent_cmd) {\n        fprintf(stderr,\n                \"error: unable to retrieve file meta data from %s \"\n                \"[ status=%s ]\\n\",\n                newresult->Get(repurl.c_str()), bs.c_str());\n      }\n\n      continue;\n    }\n\n    // Do a remote stat using XrdCl::FileSystem\n    uint64_t stat_size = std::numeric_limits<uint64_t>::max();\n    XrdOucString statpath = newresult->Get(repfstpath.c_str());\n\n    if (!statpath.beginswith(\"/\")) {\n      // base 64 encode this path\n      XrdOucString statpath64;\n      eos::common::SymKey::Base64(statpath, statpath64);\n      statpath = \"/#/\";\n      statpath += statpath64;\n    }\n\n    status = fs->Stat(statpath.c_str(), stat_info);\n\n    if (!status.IsOK()) {\n      err_label = \"STATFAILED\";\n      set_errors.insert(err_label);\n    } else {\n      stat_size = stat_info->GetSize();\n    }\n\n    // Free memory\n    delete stat_info;\n    int retc = 0;\n    eos::common::FmdHelper fmd;\n\n    if ((retc = GetRemoteFmdFromLocalDb(newresult->Get(repurl.c_str()),\n                                        newresult->Get(repfid.c_str()),\n                                        newresult->Get(repfsid.c_str()),\n                                        fmd))) {\n      if (!silent_cmd) {\n        fprintf(stderr, \"error: unable to retrieve file meta data from %s [%d]\\n\",\n                newresult->Get(repurl.c_str()), retc);\n      }\n\n      err_label = \"NOFMD\";\n      set_errors.insert(err_label);\n    } else {\n      const auto& proto_fmd = fmd.mProtoFmd;\n      XrdOucString cx = proto_fmd.checksum().c_str();\n\n      for (unsigned int k = (cx.length() / 2); k < SHA256_DIGEST_LENGTH; ++k) {\n        cx += \"00\";\n      }\n\n      std::string disk_cx = proto_fmd.diskchecksum().c_str();\n\n      for (unsigned int k = (disk_cx.length() / 2); k < SHA256_DIGEST_LENGTH;\n           ++k) {\n        disk_cx += \"00\";\n      }\n\n      if (eos::common::LayoutId::IsRain(proto_fmd.lid()) == false) {\n        // These checks make sense only for non-rain layouts\n        if (proto_fmd.size() != mgm_size) {\n          err_label = \"SIZE\";\n          set_errors.insert(err_label);\n        } else {\n          if (proto_fmd.size() != (unsigned long long) stat_size) {\n            err_label = \"FSTSIZE\";\n            set_errors.insert(err_label);\n          }\n        }\n\n        if (cx != checksum) {\n          err_label = \"CHECKSUM\";\n          set_errors.insert(err_label);\n        }\n\n        uint64_t disk_cx_val = 0ull;\n\n        try {\n          disk_cx_val = std::stoull(disk_cx.substr(0, 8), nullptr, 16);\n        } catch (...) {\n          // error during conversion\n        }\n\n        if ((disk_cx.length() > 0) && disk_cx_val &&\n            ((disk_cx.length() < 8) || (!cx.beginswith(disk_cx.c_str())))) {\n          err_label = \"DISK_CHECKSUM\";\n          set_errors.insert(err_label);\n        }\n\n        if (!silent_cmd) {\n          fprintf(stdout,\n                  \"nrep=\\\"%02d\\\" fsid=\\\"%s\\\" host=\\\"%s\\\" fstpath=\\\"%s\\\" \"\n                  \"size=\\\"%llu\\\" statsize=\\\"%llu\\\" checksum=\\\"%s\\\" \"\n                  \"diskchecksum=\\\"%s\\\" error_label=\\\"%s\\\"\\n\",\n                  i, newresult->Get(repfsid.c_str()),\n                  newresult->Get(repurl.c_str()),\n                  newresult->Get(repfstpath.c_str()),\n                  (unsigned long long)proto_fmd.size(),\n                  (unsigned long long)(stat_size),\n                  cx.c_str(), disk_cx.c_str(), err_label.c_str());\n        }\n      } else {\n        // For RAIN layouts we only check for block-checksum errors\n        if (proto_fmd.blockcxerror()) {\n          err_label = \"BLOCK_XS\";\n          set_errors.insert(err_label);\n        }\n\n        if (!silent_cmd) {\n          fprintf(stdout,\n                  \"nrep=\\\"%02d\\\" fsid=\\\"%s\\\" host=\\\"%s\\\" fstpath=\\\"%s\\\" \"\n                  \"size=\\\"%llu\\\" statsize=\\\"%llu\\\" error_label=\\\"%s\\\"\\n\",\n                  i, newresult->Get(repfsid.c_str()),\n                  newresult->Get(repurl.c_str()),\n                  newresult->Get(repfstpath.c_str()),\n                  (unsigned long long)proto_fmd.size(),\n                  (unsigned long long)(stat_size), err_label.c_str());\n        }\n      }\n\n      ++nrep_online;\n    }\n  }\n\n  int nrep = 0;\n  int stripes = 0;\n\n  if (newresult->Get(\"mgm.stripes\")) {\n    stripes = atoi(newresult->Get(\"mgm.stripes\"));\n  }\n\n  if (newresult->Get(\"mgm.nrep\")) {\n    nrep = atoi(newresult->Get(\"mgm.nrep\"));\n  }\n\n  if (nrep != stripes) {\n    if (set_errors.find(\"NOFMD\") == set_errors.end()) {\n      err_label = \"NUM_REPLICAS\";\n      set_errors.insert(err_label);\n    }\n  }\n\n  if (set_errors.size()) {\n    if ((option.find(\"%output\")) != std::string::npos) {\n      fprintf(stdout, \"INCONSISTENCY %s path=%-32s fxid=%s size=%llu \"\n              \"stripes=%d nrep=%d nrepstored=%d nreponline=%d \"\n              \"checksumtype=%s checksum=%s\\n\", set_errors.begin()->c_str(),\n              path.c_str(), newresult->Get(\"mgm.fid0\"),\n              (unsigned long long) mgm_size, stripes, nrep, i, nrep_online,\n              checksumtype.c_str(), newresult->Get(\"mgm.checksum\"));\n    }\n\n    if (((option.find(\"%size\") != std::string::npos) &&\n         ((set_errors.find(\"SIZE\") != set_errors.end() ||\n           set_errors.find(\"FSTSIZE\") != set_errors.end()))) ||\n        ((option.find(\"%checksum\") != std::string::npos) &&\n         ((set_errors.find(\"CHECKSUM\") != set_errors.end()) ||\n          (set_errors.find(\"BLOCK_XS\") != set_errors.end()))) ||\n        ((option.find(\"%diskchecksum\") != std::string::npos) &&\n         (set_errors.find(\"DISK_CHECKSUM\") != set_errors.end())) ||\n        ((option.find(\"%nrep\") != std::string::npos) &&\n         ((set_errors.find(\"NOFMD\") != set_errors.end()) ||\n          (set_errors.find(\"NUM_REPLICAS\") != set_errors.end())))) {\n      global_retc = EFAULT;\n    }\n  }\n\n  return 0;\n}\n\nclass FileCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"file\";\n  }\n  const char*\n  description() const override\n  {\n    return \"File Handling\";\n  }\n  std::string\n  helpText() const override\n  {\n    return MakeFileHelp();\n  }\n  std::vector<std::string>\n  complete(const std::vector<std::string>& args) const override\n  {\n    return eos_help_completion_candidates(name(), helpText(), args);\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (args.empty() || wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    std::string subcmd;\n    ConfigureFileApp(app, subcmd);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    std::vector<std::string> remaining = app.remaining();\n\n    XrdOucString cmd = subcmd.c_str();\n    std::vector<std::string> rest = remaining;\n    XrdOucString in = \"mgm.cmd=file\";\n\n    auto set_path_or_id = [&](XrdOucString path) {\n      if (Path2FileDenominator(path)) {\n        in += \"&mgm.file.id=\";\n        in += path;\n      } else {\n        AppendEncodedPath(in, path, true);\n      }\n    };\n\n    auto is_path_or_id = [](const std::string& s) -> bool {\n      return !s.empty() && (s[0] == '/' || s.find(\"fid:\") == 0 ||\n                            s.find(\"fxid:\") == 0 || s.find(\"pid:\") == 0 ||\n                            s.find(\"pxid:\") == 0 || s.find(\"inode:\") == 0 ||\n                            s.find(\"cid:\") == 0 || s.find(\"cxid:\") == 0 ||\n                            s[0] != '-');\n    };\n\n    if (cmd == \"rename\") {\n      if (rest.size() < 2) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      in += \"&mgm.subcmd=rename\";\n      XrdOucString p = abspath(rest[0].c_str());\n      set_path_or_id(p);\n      in += \"&mgm.file.source=\";\n      in += p;\n      in += \"&mgm.file.target=\";\n      in += abspath(rest[1].c_str());\n    } else if (cmd == \"rename_with_symlink\") {\n      if (rest.size() < 2) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      in += \"&mgm.subcmd=rename_with_symlink\";\n      XrdOucString p = abspath(rest[0].c_str());\n      set_path_or_id(p);\n      in += \"&mgm.file.source=\";\n      in += p;\n      in += \"&mgm.file.target=\";\n      in += abspath(rest[1].c_str());\n    } else if (cmd == \"symlink\") {\n      std::vector<std::string> positionals;\n      bool force = false;\n\n      for (const auto& arg : rest) {\n        if (arg == \"-f\")\n          force = true;\n        else\n          positionals.push_back(arg);\n      }\n      if (positionals.size() < 2) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      in += \"&mgm.subcmd=symlink\";\n      // positionals[0]=link (symlink name), positionals[1]=target (after CLI11 reverse)\n      XrdOucString p = abspath(positionals[0].c_str());\n      set_path_or_id(p);\n      in += \"&mgm.file.source=\";\n      in += p;\n      in += \"&mgm.file.target=\";\n      in += positionals[1].c_str();\n      if (force)\n        in += \"&mgm.file.force=1\";\n    } else if (cmd == \"drop\") {\n      if (rest.size() < 2) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString p = abspath(rest[0].c_str());\n      in += \"&mgm.subcmd=drop\";\n      set_path_or_id(p);\n      in += \"&mgm.file.fsid=\";\n      in += rest[1].c_str();\n      if (rest.size() > 2 && rest[2] == \"-f\")\n        in += \"&mgm.file.force=1\";\n    } else if (cmd == \"touch\") {\n      std::string option;\n      size_t idx = 0;\n      for (; idx < rest.size(); ++idx) {\n        if (!rest[idx].empty() && rest[idx][0] == '-') {\n          std::string tmp = rest[idx];\n          tmp.erase(std::remove(tmp.begin(), tmp.end(), '-'), tmp.end());\n          option += tmp;\n        } else {\n          break;\n        }\n      }\n      if (idx >= rest.size()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString p = abspath(rest[idx].c_str());\n      in += \"&mgm.subcmd=touch\";\n      set_path_or_id(p);\n      std::string fsid1 = (idx + 1 < rest.size()) ? rest[idx + 1] : \"\";\n      std::string fsid2 = (idx + 2 < rest.size()) ? rest[idx + 2] : \"\";\n\n      if (option.find('n') != std::string::npos) {\n        in += \"&mgm.file.touch.nolayout=true\";\n      }\n      if (option.find('0') != std::string::npos) {\n        in += \"&mgm.file.touch.truncate=true\";\n      }\n      if (option.find('a') != std::string::npos) {\n        in += \"&mgm.file.touch.absorb=true\";\n      }\n      if (option.find('l') != std::string::npos) {\n        in += \"&mgm.file.touch.lockop=lock\";\n        if (!fsid1.empty()) {\n          in += \"&mgm.file.touch.lockop.lifetime=\";\n          in += fsid1.c_str();\n          fsid1.clear();\n        }\n        if (!fsid2.empty()) {\n          if ((fsid2 != \"app\") && (fsid2 != \"user\")) {\n            printHelp();\n            global_retc = EINVAL;\n            return 0;\n          }\n          if (fsid2 == \"app\") {\n            // this is inverted logic because we set the wildcard\n            in += \"&mgm.file.touch.wildcard=user\";\n          } else {\n            // this is inverted logic because we set the wildcard\n            in += \"&mgm.file.touch.wildcard=app\";\n          }\n          fsid2.clear();\n        }\n      }\n      if (option.find('u') != std::string::npos) {\n        in += \"&mgm.file.touch.lockop=unlock\";\n        fsid1.clear();\n        fsid2.clear();\n      }\n      if (!fsid1.empty()) {\n        if (!fsid1.empty() && fsid1[0] == '/') {\n          in += \"&mgm.file.touch.hardlinkpath=\";\n          in += fsid1.c_str();\n        } else {\n          in += \"&mgm.file.touch.size=\";\n          in += fsid1.c_str();\n        }\n      }\n      if (!fsid2.empty()) {\n        in += \"&mgm.file.touch.checksuminfo=\";\n        in += fsid2.c_str();\n      }\n    } else if (cmd == \"move\") {\n      if (rest.size() < 3) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString p = abspath(rest[0].c_str());\n      in += \"&mgm.subcmd=move\";\n      set_path_or_id(p);\n      in += \"&mgm.file.sourcefsid=\";\n      in += rest[1].c_str();\n      in += \"&mgm.file.targetfsid=\";\n      in += rest[2].c_str();\n    } else if (cmd == \"copy\") {\n      std::string option;\n      size_t idx = 0;\n      for (; idx < rest.size(); ++idx) {\n        if (!rest[idx].empty() && rest[idx][0] == '-') {\n          std::string tmp = rest[idx];\n          tmp.erase(std::remove(tmp.begin(), tmp.end(), '-'), tmp.end());\n          option += tmp;\n        } else {\n          break;\n        }\n      }\n      if (idx + 1 >= rest.size()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString p = abspath(rest[idx].c_str());\n      XrdOucString dest = rest[idx + 1].c_str();\n      in += \"&mgm.subcmd=copy\";\n      set_path_or_id(p);\n      if (!option.empty()) {\n        std::string checkoption = option;\n        checkoption.erase(\n            std::remove(checkoption.begin(), checkoption.end(), 'f'),\n            checkoption.end());\n        checkoption.erase(\n            std::remove(checkoption.begin(), checkoption.end(), 's'),\n            checkoption.end());\n        checkoption.erase(\n            std::remove(checkoption.begin(), checkoption.end(), 'c'),\n            checkoption.end());\n        if (!checkoption.empty()) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        in += \"&mgm.file.option=\";\n        in += option.c_str();\n      }\n      dest = abspath(dest.c_str());\n      in += \"&mgm.file.target=\";\n      in += dest;\n    } else if (cmd == \"replicate\") {\n      if (rest.size() < 3) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString p = abspath(rest[0].c_str());\n      in += \"&mgm.subcmd=replicate\";\n      set_path_or_id(p);\n      in += \"&mgm.file.sourcefsid=\";\n      in += rest[1].c_str();\n      in += \"&mgm.file.targetfsid=\";\n      in += rest[2].c_str();\n    } else if (cmd == \"purge\" || cmd == \"version\") {\n      if (rest.empty()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      in += \"&mgm.subcmd=\";\n      in += cmd;\n      XrdOucString p = abspath(rest[0].c_str());\n      AppendEncodedPath(in, p, true);\n      in += \"&mgm.purge.version=\";\n      if (rest.size() > 1)\n        in += rest[1].c_str();\n      else\n        in += \"-1\";\n    } else if (cmd == \"versions\") {\n      if (rest.empty()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString p = abspath(rest[0].c_str());\n      in += \"&mgm.subcmd=versions\";\n      set_path_or_id(p);\n      in += \"&mgm.grab.version=\";\n      in += (rest.size() > 1 ? rest[1].c_str() : \"-1\");\n    } else if (cmd == \"layout\") {\n      if (rest.size() < 2) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString p = abspath(rest[0].c_str());\n      in += \"&mgm.subcmd=layout\";\n      set_path_or_id(p);\n      if (rest[1] == \"-stripes\" && rest.size() > 2) {\n        in += \"&mgm.file.layout.stripes=\";\n        in += rest[2].c_str();\n      } else if (rest[1] == \"-checksum\" && rest.size() > 2) {\n        in += \"&mgm.file.layout.checksum=\";\n        in += rest[2].c_str();\n      } else if (rest[1] == \"-type\" && rest.size() > 2) {\n        in += \"&mgm.file.layout.type=\";\n        in += rest[2].c_str();\n      } else {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n    } else if (cmd == \"tag\") {\n      if (rest.size() < 2) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString p = abspath(rest[0].c_str());\n      in += \"&mgm.subcmd=tag\";\n      set_path_or_id(p);\n      in += \"&mgm.file.tag.fsid=\";\n      in += rest[1].c_str();\n    } else if (cmd == \"convert\") {\n      bool rewrite = false;\n      std::vector<std::string> positionals;\n\n      for (const auto& arg : rest) {\n        if (arg == \"--rewrite\")\n          rewrite = true;\n        else if (arg == \"--sync\") {\n          fprintf(stderr, \"error: --sync is currently not supported\\n\");\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        } else\n          positionals.push_back(arg);\n      }\n      if (positionals.empty()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString p = abspath(positionals[0].c_str());\n      in += \"&mgm.subcmd=convert\";\n      set_path_or_id(p);\n      if (positionals.size() > 1) {\n        in += \"&mgm.convert.layout=\";\n        in += positionals[1].c_str();\n      }\n      if (positionals.size() > 2) {\n        in += \"&mgm.convert.space=\";\n        in += positionals[2].c_str();\n      }\n      if (positionals.size() > 3) {\n        in += \"&mgm.convert.placementpolicy=\";\n        in += positionals[3].c_str();\n      }\n      if (positionals.size() > 4) {\n        in += \"&mgm.convert.checksum=\";\n        in += positionals[4].c_str();\n      }\n      if (rewrite)\n        in += \"&mgm.option=rewrite\";\n    } else if (cmd == \"verify\") {\n      std::string path;\n      std::string filter_fsid;\n      std::string rate_val;\n\n      for (size_t i = 0; i < rest.size(); ++i) {\n        const std::string& opt = rest[i];\n        if (opt == \"-checksum\")\n          in += \"&mgm.file.compute.checksum=1\";\n        else if (opt == \"-commitchecksum\")\n          in += \"&mgm.file.commit.checksum=1\";\n        else if (opt == \"-commitsize\")\n          in += \"&mgm.file.commit.size=1\";\n        else if (opt == \"-commitfmd\")\n          in += \"&mgm.file.commit.fmd=1\";\n        else if (opt == \"-rate\") {\n          if (i + 1 < rest.size()) {\n            rate_val = rest[++i];\n            in += \"&mgm.file.verify.rate=\";\n            in += rate_val.c_str();\n          } else {\n            printHelp();\n            global_retc = EINVAL;\n            return 0;\n          }\n        } else if (opt == \"-resync\")\n          in += \"&mgm.file.resync=1\";\n        else if (!path.empty() && !opt.empty() &&\n                   std::isdigit(static_cast<unsigned char>(opt[0]))) {\n          filter_fsid = opt;\n          in += \"&mgm.file.verify.filterid=\";\n          in += filter_fsid.c_str();\n        } else if (is_path_or_id(opt)) {\n          if (path.empty())\n            path = opt;\n          else {\n            printHelp();\n            global_retc = EINVAL;\n            return 0;\n          }\n        } else {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n      }\n      if (path.empty()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString p = abspath(path.c_str());\n      in += \"&mgm.subcmd=verify\";\n      AppendEncodedPath(in, p, true);\n    } else if (cmd == \"adjustreplica\") {\n      if (rest.empty()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString p = abspath(rest[0].c_str());\n      in += \"&mgm.subcmd=adjustreplica\";\n      set_path_or_id(p);\n      std::vector<std::string> args(rest.begin() + 1, rest.end());\n      int positional_index = 0;\n      for (size_t i = 0; i < args.size(); ++i) {\n        if (args[i] == \"--exclude-fs\") {\n          if (i + 1 < args.size()) {\n            in += \"&mgm.file.excludefs=\";\n            in += args[i + 1].c_str();\n            i++;\n          } else {\n            printHelp();\n            global_retc = EINVAL;\n            return 0;\n          }\n        } else if (args[i] == \"--nodrop\") {\n          in += \"&mgm.file.nodrop=1\";\n        } else {\n          if (positional_index == 0) {\n            in += \"&mgm.file.desiredspace=\";\n            in += args[i].c_str();\n            positional_index++;\n          } else if (positional_index == 1) {\n            in += \"&mgm.file.desiredsubgroup=\";\n            in += args[i].c_str();\n            positional_index++;\n          } else {\n            printHelp();\n            global_retc = EINVAL;\n            return 0;\n          }\n        }\n      }\n    } else if (cmd == \"check\") {\n      if (rest.empty()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      std::string option = (rest.size() > 1) ? rest[1] : \"\";\n      return RunFileCheck(rest[0].c_str(), option, ctx);\n    } else if (cmd == \"share\") {\n      if (rest.empty()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString p = abspath(rest[0].c_str());\n      in += \"&mgm.subcmd=share\";\n      AppendEncodedPath(in, p, true);\n      unsigned long long expires = (28ull * 86400ull);\n      if (rest.size() > 1) {\n        in += \"&mgm.file.expires=\";\n        in += rest[1].c_str();\n      } else {\n        char buf[64];\n        snprintf(buf, sizeof(buf), \"%llu\", expires);\n        in += \"&mgm.file.expires=\";\n        in += buf;\n      }\n    } else if (cmd == \"workflow\") {\n      if (rest.size() < 3) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString p = abspath(rest[0].c_str());\n      in += \"&mgm.subcmd=workflow\";\n      AppendEncodedPath(in, p, true);\n      in += \"&mgm.workflow=\";\n      in += rest[1].c_str();\n      in += \"&mgm.event=\";\n      in += rest[2].c_str();\n    } else if (cmd == \"info\") {\n      std::string path;\n      XrdOucString option = \"\";\n\n      for (const auto& arg : rest) {\n        if (is_path_or_id(arg)) {\n          if (path.empty())\n            path = arg;\n          else {\n            printHelp();\n            global_retc = EINVAL;\n            return 0;\n          }\n        } else {\n          XrdOucString tok = arg.c_str();\n          if (tok == \"s\" || tok == \"-s\" || tok == \"--silent\")\n            option += \"silent\";\n          else\n            option += tok;\n        }\n      }\n      if (path.empty()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString path_str = path.c_str();\n      bool absolutize = (!path_str.beginswith(\"fid:\")) && (!path_str.beginswith(\"fxid:\")) &&\n                        (!path_str.beginswith(\"pid:\")) && (!path_str.beginswith(\"pxid:\")) &&\n                        (!path_str.beginswith(\"inode:\"));\n      XrdOucString fin = \"mgm.cmd=fileinfo\";\n      AppendEncodedPath(fin, path_str, absolutize);\n      if (option.length()) {\n        fin += \"&mgm.file.info.option=\";\n        fin += option;\n      }\n      if (option.find(\"silent\") == STR_NPOS) {\n        global_retc =\n            ctx.outputResult(ctx.clientCommand(fin, false, nullptr), true);\n      }\n      return 0;\n    } else {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    global_retc = ctx.outputResult(ctx.clientCommand(in, false, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", helpText().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterFileNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<FileCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/fileinfo-alias.cc",
    "content": "// ----------------------------------------------------------------------\n// File: fileinfo-alias.cc\n// Purpose: Provide 'fileinfo' alias for 'file info ...'\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include <memory>\n#include <vector>\n\nnamespace {\nclass FileInfoAliasCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"fileinfo\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Alias for 'file info'\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    IConsoleCommand* fileCmd = CommandRegistry::instance().find(\"file\");\n    if (!fileCmd) {\n      fprintf(stderr, \"error: 'file' command not available\\n\");\n      return -1;\n    }\n    std::vector<std::string> forwarded;\n    forwarded.reserve(args.size() + 1);\n    forwarded.emplace_back(\"info\");\n    forwarded.insert(forwarded.end(), args.begin(), args.end());\n    return fileCmd->run(forwarded, ctx);\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr,\n            \"Usage: fileinfo <path> [options] (alias for 'file info')\\n\");\n  }\n};\n} // namespace\n\nvoid\nRegisterFileInfoAliasCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<FileInfoAliasCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/find-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: find-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include \"console/commands/helpers/NewfindHelper.hh\"\n#include <algorithm>\n#include <iomanip>\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeFindHelp()\n{\n  std::ostringstream oss;\n  oss << \" usage\\n\"\n      << \"find/newfind [OPTIONS] <path> : find files and directories\\n\"\n      << \"OPTIONS can be filters, actions, or output modifiers for the found items\\n\"\n      << \"Filters: [--maxdepth <n>] [--name <pattern>] [-f] [-d] [-0] [-g] [-uid <n>] \"\n         \"[-nuid <n>]\\n\"\n      << \"         [-gid <n>] [-ngid <n>] [-flag <n>] [-nflag <n>] [--ctime|--mtime \"\n         \"+<n>|-<n>]\\n\"\n      << \"         [-x <key>=<val>] [--faultyacl] [--stripediff]\\n\"\n      << \"\\t       --maxdepth <n> : descend only <n> levels\\n\"\n      << \"\\t     --name <pattern> : find by name, filtering by 'egrep' style regex match\\n\"\n      << \"\\t                -f,-d : find only files(-f) or directories (-d) in <path>\\n\"\n      << \"\\t                   -0 : find 0-size files only\\n\"\n      << \"\\t                   -g : find files with mixed scheduling groups\\n\"\n      << \"\\t   -uid <n>,-nuid <n> : find entries owned / not owned by a given user id \"\n         \"number\\n\"\n      << \"\\t   -gid <n>,-ngid <n> : find entries owned / not owned by a given group id \"\n         \"number\\n\"\n      << \"\\t -flag <n>,-nflag <n> : find entries with / without specified UNIX access \"\n         \"flag, e.g. 755\\n\"\n      << \"\\t   --ctime <+n>, <-n> : find files with ctime older (+n) or younger (-n) than \"\n         \"<n> days\\n\"\n      << \"\\t   --mtime <+n>, <-n> : find files with mtime older (+n) or younger (-n) than \"\n         \"<n> days\\n\"\n      << \"\\t       -x <key>=<val> : find entries with <key>=<val>\\n\"\n      << \"\\t          --faultyacl : find files and directories with illegal ACLs\\n\"\n      << \"\\t         --stripediff : find files that do not have the nominal number of \"\n         \"stripes(replicas)\\n\"\n      << \"\\t  --skip-version-dirs : skip version directories in the traversed hierarchy\\n\"\n      << \"\\n\"\n      << \"Actions: [-b] [--layoutstripes <n>] [--purge <n> ] [--fileinfo] [--format \"\n         \"formatlist] [--cache] [--du]\\n\"\n      << \"\\t                   -b : query the server balance of the files found\\n\"\n      << \"\\t  --layoutstripes <n> : apply new layout with <n> stripes to the files found\\n\"\n      << \"\\t --purge <n> | atomic : remove versioned files keeping <n> versions (use \"\n         \"--purge 0 to remove all old versions)\\n\"\n      << \"\\t                        To apply the settings of the extended attribute \"\n         \"definition use --purge -1\\n\"\n      << \"\\t                        To remove all atomic upload left-overs older than a \"\n         \"day use --purge atomic\\n\"\n      << \"\\t         [--fileinfo] : invoke `eos fileinfo` on the entry\\n\"\n      << \"\\t              --count : print aggregated number of file and directory \"\n         \"including the search path\\n\"\n      << \"\\t         --childcount : print the number of children in each directory\\n\"\n      << \"\\t          --treecount : print the aggregated number of filesand directory \"\n         \"children excluding the search path\\n\"\n      << \"\\t             --format : print with the given komma separated format list, \"\n         \"redundant switches like\\n\"\n      << \"\\t                        --uid --checksum, which can be specified via the \"\n         \"format are automatically disabled.\\n\"\n      << \"\\t                        Possible values for format tags are: uid,gid,size,\"\n         \"checksum,checksumtype,etag,fxid,\\n\"\n      << \"\\t                        pxid,cxid,fid,pid,cid,atime,btime,ctime,mtime,type,\"\n         \"mode,files,link,directories,\\n\"\n      << \"\\t                        attr.*,attr.<name> e.g. attr.sys.acl !\\n\"\n      << \"\\t              --cache : store all found entries in the in-memory namespace \"\n         \"cache\\n\"\n      << \"\\t                 --du : create du-style output\\n\"\n      << \"\\n\"\n      << \"Output mode: [--xurl] [-p <key>] [--nrep] [--nunlink] [--size] [--online] \"\n         \"[--hosts]\\n\"\n      << \"             [--partition] [--fid] [--fs] [--checksum] [--ctime] [--mtime] \"\n         \"[--uid] [--gid]\\n\"\n      << \"\\t                : print out the requested meta data as key value pairs\\n\"\n      << \"The <path> argument ca be:\\n\"\n      << \"\\t path=file:...  :  do a find in the local file system (options ignored) - \"\n         \"'file:' is the current working directory\\n\"\n      << \"\\t path=root:...  :  do a find on a plain XRootD server (options ignored) - \"\n         \"does not work on native XRootD clusters\\n\"\n      << \"\\t path=as3:...   :  do a find on an S3 bucket\\n\"\n      << \"\\t path=...       :  all other paths are considered to be EOS paths!\\n\";\n  return oss.str();\n}\n\nvoid ConfigureFindApp(CLI::App& app)\n{\n  app.name(\"find\");\n  app.description(\"Find files/directories\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeFindHelp();\n      }));\n}\n\nclass FindProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"find\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Find files/directories\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      if (args[i].find(' ') != std::string::npos)\n        oss << std::quoted(args[i]);\n      else\n        oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    // Reuse the same helper as the original newfind implementation\n    NewfindHelper finder(*ctx.globalOpts);\n    // Special schemes handled locally\n    if (joined.find(\"root://\") != std::string::npos) {\n      std::string path = joined.substr(joined.rfind(\"root://\"));\n      path.erase(std::remove(path.begin(), path.end(), '\"'), path.end());\n      global_retc = finder.FindXroot(path);\n      return 0;\n    } else if (joined.find(\"file:\") != std::string::npos) {\n      std::string path = joined.substr(joined.rfind(\"file:\"));\n      path.erase(std::remove(path.begin(), path.end(), '\"'), path.end());\n      global_retc = finder.FindXroot(path);\n      return 0;\n    } else if (joined.find(\"as3:\") != std::string::npos) {\n      std::string path = joined.substr(joined.rfind(\"as3:\"));\n      path.erase(std::remove(path.begin(), path.end(), '\"'), path.end());\n      global_retc = finder.FindAs3(path);\n      return 0;\n    }\n    if (!finder.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = finder.Execute();\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureFindApp(app);\n    fprintf(stderr, \"%s\", app.help().c_str());\n  }\n};\n\n// Provide 'newfind' alias to the same implementation as 'find'\nclass NewfindAliasCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"newfind\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Find files/directories (new)\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    IConsoleCommand* findCmd = CommandRegistry::instance().find(\"find\");\n    if (!findCmd) {\n      fprintf(stderr, \"error: 'find' command not available\\n\");\n      global_retc = EINVAL;\n      return 0;\n    }\n    return findCmd->run(args, ctx);\n  }\n  void\n  printHelp() const override\n  {\n    // Delegate to 'find' help\n    IConsoleCommand* findCmd = CommandRegistry::instance().find(\"find\");\n    if (findCmd)\n      findCmd->printHelp();\n  }\n};\n} // namespace\n\nvoid\nRegisterFindProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<FindProtoCommand>());\n  CommandRegistry::instance().reg(std::make_unique<NewfindAliasCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/fs-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: fs-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleCompletion.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/FsHelper.hh\"\n#include <iomanip>\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeFsHelp()\n{\n  std::ostringstream oss;\n  oss << \"Usage: fs add|boot|clone|compare|config|dropdeletion|dropghosts|\"\n         \"dropfiles|dumpmd|ls|mv|rm|status [OPTIONS]\"\n      << std::endl\n      << \"  Options:\" << std::endl\n      << \"  fs add [-m|--manual <fsid>] <uuid> <node-queue>|<host>[:<port>] \"\n         \"<mountpoint> [<space_info> [<status> [<sharedfs>]]]\" << std::endl\n      << \"    add and assign a filesystem based on the unique identifier of \"\n         \"the disk <uuid>\" << std::endl\n      << \"    -m|--manual  : add with user specified <fsid> and <space>\"\n      << std::endl\n      << \"    <fsid>       : numeric filesystem id 1...65535\" << std::endl\n      << \"    <uuid>       : unique string identifying current filesystem\"\n      << std::endl\n      << \"    <node-queue> : internal EOS identifier for a node e.g \"\n         \"/eos/<host>:<port>/fst\" << std::endl\n      << \"                   it is preferable to use the host:port syntax\"\n      << std::endl\n      << \"    <host>       : FQDN of host where filesystem is mounter\"\n      << std::endl\n      << \"    <port>       : FST XRootD port number [usually 1095]\"\n      << std::endl\n      << \"    <mountpoint> : local path of the mounted filesystem e.g /data/\"\n      << std::endl\n      << \"    <space_info> : space or space.group location where to insert \"\n         \"the filesystem,\\n\"\n      << \"                   if nothing is specified then space \\\"default\\\" \"\n         \"is used. E.g:\\n\"\n      << \"                   default, default.7, ssd.3, spare\" << std::endl\n      << \"    <status>     : set filesystem status after insertion e.g \"\n         \"off|rw|ro|empty etc.\" << std::endl\n      << \"    <sharedfs>   : set the name of a shared filesystem\" << std::endl\n      << std::endl\n      << \"  fs boot <fsid>|<uuid>|<node-queue>|* [--syncdisk|--syncmgm]\"\n      << std::endl\n      << \"    boot - filesystem identified by <fsid> or <uuid>\\n\"\n      << \"         - all filesystems on a node identified by <node-queue>\\n\"\n      << \"         - all filesystems registered\\n\"\n      << \"    --syncdisk   : do disk resynchronization during the booting\\n\"\n      << \"    --syncmgm    : do MGM and disk resynchronization during the \"\n         \"booting\\n\"\n      << std::endl\n      << \"  fs clone <sourceid> <targetid>\" << std::endl\n      << \"    replicate files from the source to the target filesystem\"\n      << std::endl\n      << \"    <sourceid>   : id of the source filesystem\" << std::endl\n      << \"    <targetid>   : id of the target filesystem\" << std::endl\n      << std::endl\n      << std::endl\n      << \"  fs compare <sourceid> <targetid>\" << std::endl\n      << \"    compares and reports which files are present on one filesystem \"\n         \"and not on the other\" << std::endl\n      << \"    <sourceid>   : id of the source filesystem\" << std::endl\n      << \"    <targetid>   : id of the target filesystem\" << std::endl\n      << std::endl\n      << std::endl\n      << \"  fs config <fsid> <key>=<value>\" << std::endl\n      << \"    configure the filesystem parameter, where <key> and <value> \"\n         \"can be:\" << std::endl\n      << \"    configstatus=rw|wo|ro|drain|draindead|off|empty [--comment \"\n         \"\\\"<comment>\\\"]\" << std::endl\n      << \"      rw        : set filesystem in read-write mode\" << std::endl\n      << \"      wo        : set filesystem in write-only mode\" << std::endl\n      << \"      ro        : set filesystem in read-only mode\" << std::endl\n      << \"      drain     : set filesystem in drain mode\" << std::endl\n      << \"      draindead : set filesystem in draindead mode, unusable for \"\n         \"any read\" << std::endl\n      << \"      off       : disable filesystem\" << std::endl\n      << \"      empty     : empty filesystem, possible only if there are no\"\n      << std::endl\n      << \"                  more files stored on it\" << std::endl\n      << \"      --comment : pass a reason for the status change\" << std::endl\n      << \"    headroom=<size>\" << std::endl\n      << \"      headroom to keep per filesystem. <size> can be (>0)[BMGT]\"\n      << std::endl\n      << \"    scaninterval=<seconds>\\n\"\n      << \"      entry rescan interval (default 7 days), 0 disables scanning\"\n      << std::endl\n      << \"    scan_rain_interval=<seconds>\\n\"\n      << \"      rain entry rescan interval (default 4 weeks), 0 disables \"\n         \"scanning\" << std::endl\n      << \"    scanrate=<MB/s>\" << std::endl\n      << \"      maximum IO scan rate per filesystem\" << std::endl\n      << \"    scan_disk_interval=<seconds>\\n\"\n      << \"      disk consistency thread scan interval (default 4h)\"\n      << std::endl\n      << \"    scan_ns_interval=<seconds>\\n\"\n      << \"      namespace consistency thread scan interval (default 3 days)\"\n      << std::endl\n      << \"    scan_ns_rate=<entries/s>\\n\"\n      << \"      maximum scan rate of ns entries for the NS consistency. This\\n\"\n      << \"      is bound by the maxium number of IOPS per disk.\" << std::endl\n      << \"    fsck_refresh_interval=<sec>\\n\"\n      << \"       time interval after which fsck inconsistencies are refreshed\"\n      << std::endl\n      << \"    scan_altxs_rate=<entries/s>\\n\"\n      << \"       maximum scan rate of ns entries for checking if alternative\\n\"\n      << \"       checksums have been computed.\" << std::endl\n      << \"    scan_altxs_interval=<sec>\\n\"\n      << \"       alternative checksum computing interval (default 30 days), \"\n         \"0 disables scanning\" << std::endl\n      << \"    altxs_sync=0|1\\n\"\n      << \"       enable synchronization of alternative checksums settings \"\n         \"from namespace\" << std::endl\n      << \"    altxs_sync_interval=<seconds>\\n\"\n      << \"       time interval after which synchronization of alternative \"\n         \"checksums\\n\"\n      << \"       settings are refreshed. If 0 it is synchronized only once \"\n         \"(default 0)\" << std::endl\n      << \"    graceperiod=<seconds>\\n\"\n      << \"      grace period before a filesystem with an operation error gets\\n\"\n      << \"      automatically drained\" << std::endl\n      << \"    drainperiod=<seconds>\\n\"\n      << \"      period a drain job is allowed to finish the drain procedure\"\n      << std::endl\n      << \"    proxygroup=<proxy_grp_name>\" << std::endl\n      << \"      schedule a proxy for the current filesystem by taking it from\"\n      << std::endl\n      << \"      the given proxy group. The special value \\\"<none>\\\" is the\"\n      << std::endl\n      << \"      same as no value and means no proxy scheduling\" << std::endl\n      << \"    filestickyproxydepth=<depth>\" << std::endl\n      << \"      depth of the subtree to be considered for file-stickyness. A\"\n      << std::endl\n      << \"      negative value means no file-stickyness\" << std::endl\n      << \"    forcegeotag=<geotag>\" << std::endl\n      << \"      set the filesystem's geotag, overriding the host geotag value.\"\n      << std::endl\n      << \"      The special value \\\"<none>\\\" is the same as no value and means\"\n      << std::endl\n      << \"      no override\" << std::endl\n      << \"    s3credentials=<accesskey>:<secretkey>\" << std::endl\n      << \"      the access and secret key pair used to authenticate\"\n      << std::endl\n      << \"      with the S3 storage endpoint\" << std::endl\n      << \"    sharedfs=<name>\" << std::endl\n      << \"      the sharedfs this filesystem is to be assigned to. To \"\n         \"unlabel set the sharedfs name to 'none' !\" << std::endl\n      << std::endl\n      << \"  fs dropdeletion <fsid> \" << std::endl\n      << \"    drop all pending deletions on the filesystem\" << std::endl\n      << std::endl\n      << \"  fs dropghosts <fsid> [--fxid fid1 [fid2] ...]\\n\"\n      << \"    drop file ids (hex) without a corresponding metadata object in\\n\"\n      << \"    the namespace that are still accounted in the file system view.\\n\"\n      << \"    If no fxid is provided then all fids on the file system are \"\n         \"checked.\\n\"\n      << std::endl\n      << \"  fs dropfiles <fsid> [-f]\" << std::endl\n      << \"    drop all files on the filesystem\" << std::endl\n      << \"    -f : unlink/remove files from the namespace (you have to remove\"\n      << std::endl\n      << \"         the files from disk)\" << std::endl\n      << std::endl\n      << \"  fs dumpmd <fsid> [--count] [--fid|--fxid|--path] [--size] \"\n         \"[-m|-s]\\n\"\n      << \"    dump/count file metadata entries on the given filesystem, only \"\n         \"if less than 100k\\n\"\n      << \"    --count : print only the number of entries on the file system \"\n         \"[default]\\n\"\n      << \"    --fid   : dump only the file ids in decimal\\n\"\n      << \"    --fxid  : dump only the file ids in hexadecimal\\n\"\n      << \"    --path  : dump only the file paths\\n\"\n      << \"    --size  : dump only the file sizes\\n\"\n      << \"    -m      : print full metadata record in env format\\n\"\n      << \"    -s      : silent mode (will keep an internal reference)\\n\"\n      << std::endl\n      << \"  fs ls [-m|-l|-e|--io|--fsck|[-d|--drain]|-D|-F] [-s] [-b|--brief] \"\n         \"[[matchlist]]\" << std::endl\n      << \"    list filesystems using the default output format\" << std::endl\n      << \"    -m         : monitoring format\" << std::endl\n      << \"    -b|--brief : display hostnames without domain names\" << std::endl\n      << \"    -l         : display parameters in long format\" << std::endl\n      << \"    -e         : display filesystems in error state\" << std::endl\n      << \"    --io       : IO output format\" << std::endl\n      << \"    --fsck     : display filesystem check statistics\" << std::endl\n      << \"    -d|--drain : display filesystems in drain or draindead status\"\n      << std::endl\n      << \"                 along with drain progress and statistics\" << std::endl\n      << \"    -D|--drain_jobs : \" << std::endl\n      << \"                 display ongoing drain transfers, matchlist needs to \"\n         \"be an integer\" << std::endl\n      << \"                 representing the drain file system id\" << std::endl\n      << \"    -F|--failed_drain_jobs : \" << std::endl\n      << \"                 display failed drain transfers, matchlist needs to \"\n         \"be an integer\" << std::endl\n      << \"                 representing the drain file system id. This will \"\n         \"only display\" << std::endl\n      << \"                 information while the draining is ongoing\"\n      << std::endl\n      << \"    -s         : silent mode\" << std::endl\n      << \"    [matchlist]\" << std::endl\n      << \"       -> can be the name of a space or a comma separated list of\"\n      << std::endl\n      << \"          spaces e.g 'default,spare'\" << std::endl\n      << \"       -> can be a grep style list to filter certain filesystems\"\n      << std::endl\n      << \"          e.g. 'fs ls -d drain,bootfailure'\" << std::endl\n      << \"       -> can be a combination of space filter and grep e.g.\"\n      << std::endl\n      << \"          'fs ls -l default,drain,bootfailure'\" << std::endl\n      << std::endl\n      << \"  fs mv [--force] \"\n         \"<src_fsid|src_grp|src_space> <dst_grp|dst_space|node:port>\"\n      << std::endl\n      << \"    move filesystem(s) in different scheduling group, space or node\"\n      << std::endl\n      << \"    --force   : force mode - allows to move non-empty filesystems \"\n         \"bypassing group \" << std::endl\n      << \"                and node constraints\" << std::endl\n      << \"    src_fsid  : source filesystem id\" << std::endl\n      << \"    src_grp   : all filesystems from scheduling group are moved\"\n      << std::endl\n      << \"    src_space : all filesystems from space are moved\" << std::endl\n      << \"    dst_grp   : destination scheduling group\" << std::endl\n      << \"    dst_space : destination space - best match scheduling group\"\n      << std::endl\n      << \"    node:port : destination node, useful to serve a disk from a \"\n         \"different\\n\"\n      << \"                FST on the same node or failover an FST serving data \"\n         \"from\\n\"\n      << \"                a shared filesystem backend\\n\"\n      << std::endl\n      << \"  fs rm <fsid>|<mnt>|<node-queue> <mnt>|<hostname> <mnt>\" << std::endl\n      << \"    remove filesystem by various identifiers, where <mnt> is the \"\n      << std::endl\n      << \"    mountpoint\" << std::endl\n      << std::endl\n      << \"  fs status [-r] [-l] <identifier>\" << std::endl\n      << \"    return all status variables of a filesystem and calculates\"\n      << std::endl\n      << \"    the risk of data loss if this filesystem is removed\" << std::endl\n      << \"    <identifier> can be: \" << std::endl\n      << \"       <fsid> : filesystem id\" << std::endl\n      << \"       [<host>] <mountpoint> : if host is not specified then it's\"\n      << std::endl\n      << \"       considered localhost\" << std::endl\n      << \"    -l : list all files which are at risk and offline files\"\n      << std::endl\n      << \"    -r : show risk analysis\" << std::endl\n      << std::endl\n      << \"  Examples: \" << std::endl\n      << \"  fs ls --io -> list all filesystems with IO statistics\" << std::endl\n      << \"  fs boot *  -> send boot request to all filesystems\" << std::endl\n      << \"  fs dumpmd 100 --path -> dump all logical path names on filesystem\"\n      << \" 100\" << std::endl\n      << \"  fs mv 100 default.0 -> move filesystem 100 to scheduling group\"\n      << \" default.0\" << std::endl;\n  return oss.str();\n}\n\nclass FsProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"fs\";\n  }\n  const char*\n  description() const override\n  {\n    return \"File System configuration\";\n  }\n  std::string\n  helpText() const override\n  {\n    return MakeFsHelp();\n  }\n  std::vector<std::string>\n  complete(const std::vector<std::string>& args) const override\n  {\n    return eos_help_completion_candidates(name(), helpText(), args);\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      // Quote args with spaces so FsHelper's tokenizer preserves them\n      if (args[i].find(' ') != std::string::npos)\n        oss << std::quoted(args[i]);\n      else\n        oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    FsHelper helper(*ctx.globalOpts);\n    if (!helper.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    // Confirmation is handled inside FsHelper where applicable\n    global_retc = helper.Execute();\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", helpText().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterFsProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<FsProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/fsck-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: fsck-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/FsckHelper.hh\"\n#include <iomanip>\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeFsckHelp()\n{\n  std::ostringstream oss;\n  oss << \"Usage: fsck [stat|config|report|repair|clean_orphans]\\n\"\n      << \"    control and display file system check information\\n\"\n      << std::endl\n      << \"  fsck stat [-m]\\n\"\n      << \"    print summary of consistency checks\\n\"\n      << \"    -m         : print in monitoring format\\n\"\n      << std::endl\n      << \"  fsck config <key> <value>\\n\"\n      << \"    configure the fsck with the following possible options:\\n\"\n      << \"    collect-interval-min : collection interval in minutes [default 30]\\n\"\n      << \"    collect              : control error collection thread - on/off\\n\"\n      << \"    repair               : control error repair thread - on/off\\n\"\n      << \"    best-effort          : control best-effort repair mode - on/off\\n\"\n      << \"    repair-category      : specify error types that the repair thread will handle\\n\"\n      << \"                           e.g all, m_cx_diff, m_mem_sz_diff, d_cx_diff, d_mem_sz_diff,\\n\"\n      << \"                           unreg_n, rep_diff_n, rep_missing_n, blockxs_err, stripe_err\\n\"\n      << \"    max-queued-jobs      : maximum number of queued jobs\\n\"\n      << \"    max-thread-pool-size : maximum number of threads in the fsck pool\\n\"\n      << \"    show-dark-files      : on/off [default off] - might affect instance performance\\n\"\n      << \"    show-offline         : on/off [default off] - might affect instance performance\\n\"\n      << \"    show-no-replica      : on/off [default off] - might affect instance performance\\n\"\n      << std::endl\n      << \"  fsck report [-a] [-i] [-l] [-j|--json] [--error <tag1> <tag2> ...]\\n\"\n      << \"    report consistency check results, with the following options\\n\"\n      << \"    -a         : break down statistics per file system\\n\"\n      << \"    -i         : display file identifiers\\n\"\n      << \"    -l         : display logical file name\\n\"\n      << \"    -j|--json  : display in JSON output format\\n\"\n      << \"    --error    : display information about the following error tags\\n\"\n      << std::endl\n      << \"  fsck repair --fxid <val> [--fsid <val>] [--error <err_type>] [--async]\\n\"\n      << \"    repair the given file if there are any errors\\n\"\n      << \"    --fxid  : hexadecimal file identifier\\n\"\n      << \"    --fsid  : file system id used for collecting info\\n\"\n      << \"    --error : error type for given file system id e.g. m_cx_diff unreg_n etc\\n\"\n      << \"    --async : job queued and ran by the repair thread if enabled\\n\"\n      << std::endl\n      << \"  fsck clean_orphans [--fsid <val>] [--force-qdb-cleanup]\\n\"\n      << \"    clean orphans by removing the entries from disk and local\\n\"\n      << \"    database for all file systems or only for the given fsid.\\n\"\n      << \"    This operation is synchronous but the fsck output will be\\n\"\n      << \"    updated once the inconsistencies are refreshed.\\n\"\n      << \"    --force-qdb-cleanup : force remove orphan entries from qdb\\n\"\n      << std::endl;\n  return oss.str();\n}\n\nclass FsckProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"fsck\";\n  }\n  const char*\n  description() const override\n  {\n    return \"File System Consistency Checking\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      // Quote args with spaces so FsckHelper's tokenizer preserves them\n      if (args[i].find(' ') != std::string::npos)\n        oss << std::quoted(args[i]);\n      else\n        oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    if (args.empty()) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    FsckHelper helper(*ctx.globalOpts);\n    if (!helper.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = helper.Execute();\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeFsckHelp().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterFsckProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<FsckProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/fuse-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: fuse-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <memory>\n#include <sstream>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n\nnamespace {\nclass FuseCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"fuse\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Fuse Mounting\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    if (interactive) {\n      fprintf(stderr, \"error: don't call <fuse> from an interactive shell - \"\n                      \"call via 'eos fuse ...'!\\n\");\n      global_retc = -1;\n      return 0;\n    }\n    if (!args.empty()) {\n      std::ostringstream oss;\n      for (size_t i = 0; i < args.size(); ++i) {\n        if (i)\n          oss << ' ';\n        oss << args[i];\n      }\n      if (wants_help(oss.str().c_str())) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n    }\n    if (args.empty() || (args[0] != \"mount\" && args[0] != \"umount\")) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    XrdOucString cmd = args[0].c_str();\n    XrdOucString mountpoint = (args.size() > 1 ? args[1].c_str() : \"\");\n    XrdCl::URL url(serveruri.c_str());\n    XrdOucString params = \"fsname=\";\n    params += (url.GetHostName() == \"localhost\") ? \"localhost.localdomain\"\n                                                 : url.GetHostName().c_str();\n    params += \":\";\n    params += url.GetPath().c_str();\n    if (cmd == \"mount\") {\n      if (!mountpoint.length()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      struct stat buf {};\n      if (stat(mountpoint.c_str(), &buf)) {\n        XrdOucString createdir = \"mkdir -p \";\n        createdir += mountpoint;\n        createdir += \" >& /dev/null\";\n        fprintf(stderr, \".... trying to create ... %s\\n\", mountpoint.c_str());\n        int rc = system(createdir.c_str());\n        if (WEXITSTATUS(rc))\n          fprintf(stderr, \"error: creation of mountpoint failed\");\n      }\n      if (stat(mountpoint.c_str(), &buf)) {\n        fprintf(stderr, \"error: cannot create mountpoint %s !\\n\",\n                mountpoint.c_str());\n        return -1;\n      }\n#ifdef __APPLE__\n      params += \" -onoappledouble,allow_root,defer_permissions,volname=EOS,\"\n                \"iosize=65536,fsname=eos@cern.ch\";\n#endif\n      fprintf(stderr, \"===> Mountpoint   : %s\\n\", mountpoint.c_str());\n      fprintf(stderr, \"===> Fuse-Options : %s\\n\", params.c_str());\n      XrdOucString mount;\n      mount = \"eosxd \";\n      mount += mountpoint.c_str();\n      mount += \" -o\";\n      mount += params;\n      mount += \" >& /dev/null\";\n      int rc = system(mount.c_str());\n      if (WEXITSTATUS(rc)) {\n        fprintf(stderr, \"error: failed mount\\n\");\n        return -1;\n      }\n      fprintf(stderr, \"info: successfully mounted EOS [%s] under %s\\n\",\n              serveruri.c_str(), mountpoint.c_str());\n    } else {\n      if (!mountpoint.length()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n#ifdef __APPLE__\n      XrdOucString umount = \"umount -f \";\n      umount += mountpoint.c_str();\n      umount += \" >& /dev/null\";\n#else\n      XrdOucString umount = \"fusermount -z -u \";\n      umount += mountpoint.c_str();\n#endif\n      int rc = system(umount.c_str());\n      if (WEXITSTATUS(rc))\n        fprintf(stderr, \"error: umount failed\\n\");\n    }\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"Usage:\\n\"\n                    \"  fuse mount <mount-point>\\n\"\n                    \"  fuse umount <mount-point>\\n\"\n                    \"Mount uses server URI to derive fsname and prepares the \"\n                    \"mountpoint.\\n\");\n  }\n};\n} // namespace\n\nvoid\nRegisterFuseNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<FuseCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/fusex-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: fusex-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringConversion.hh\"\n#include \"common/SymKeys.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <XrdOuc/XrdOucString.hh>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeFusexHelp()\n{\n  return \"Usage: fusex <subcmd> [args...]\\n\\n\"\n         \"Subcommands:\\n\"\n         \"  ls                                    list active FUSEX clients\\n\"\n         \"  evict <uuid> [reason]                  evict a client by UUID \"\n         \"(reason base64-encoded)\\n\"\n         \"  caps [all|token|lock] [filter]         show capabilities, optional \"\n         \"filter string\\n\"\n         \"  dropcaps <uuid>                        drop capabilities for client \"\n         \"UUID\\n\"\n         \"  droplocks <inode> <pid>                drop locks for inode and \"\n         \"process\\n\"\n         \"  conf [hb] [qc] [bc.max] [bc.match]     configure heartbeat (hb), \"\n         \"queue cap (qc),\\n\"\n         \"                                         block cache max and match\\n\";\n}\n\nvoid ConfigureFusexApp(CLI::App& app, std::string& subcmd)\n{\n  app.name(\"fusex\");\n  app.description(\"Fuse(x) Administration\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeFusexHelp();\n      }));\n  app.add_option(\"subcmd\", subcmd,\n                 \"ls|evict|caps|dropcaps|droplocks|conf\")\n      ->required();\n}\n\nclass FusexCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"fusex\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Fuse(x) Administration\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (args.empty() || wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    std::string subcmd;\n    ConfigureFusexApp(app, subcmd);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    std::vector<std::string> remaining = app.remaining();\n    std::reverse(remaining.begin(), remaining.end());\n\n    XrdOucString in = \"mgm.cmd=fusex\";\n    if (subcmd == \"ls\") {\n      in += \"&mgm.subcmd=ls\";\n    } else if (subcmd == \"evict\") {\n      if (remaining.size() < 1) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString uuid = remaining[0].c_str();\n      in += \"&mgm.subcmd=evict&mgm.fusex.uuid=\";\n      in += uuid;\n      if (remaining.size() > 1) {\n        XrdOucString reason = remaining[1].c_str();\n        XrdOucString b64;\n        eos::common::SymKey::Base64(reason, b64);\n        in += \"&mgm.fusex.reason=\";\n        in += b64;\n      }\n    } else if (subcmd == \"caps\") {\n      XrdOucString option = (remaining.size() > 0 ? remaining[0].c_str() : \"\");\n      option.replace(\"-\", \"\");\n      in += \"&mgm.subcmd=caps&mgm.option=\";\n      in += option;\n      if (remaining.size() > 1) {\n        std::ostringstream f;\n        for (size_t i = 1; i < remaining.size(); ++i) {\n          if (i > 1)\n            f << ' ';\n          f << remaining[i];\n        }\n        XrdOucString filter = f.str().c_str();\n        if (filter.length()) {\n          in += \"&mgm.filter=\";\n          in += eos::common::StringConversion::curl_escaped(filter.c_str())\n                    .c_str();\n        }\n      }\n    } else if (subcmd == \"dropcaps\") {\n      if (remaining.size() < 1) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      in += \"&mgm.subcmd=dropcaps&mgm.fusex.uuid=\";\n      in += remaining[0].c_str();\n    } else if (subcmd == \"droplocks\") {\n      if (remaining.size() < 2) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      in += \"&mgm.subcmd=droplocks&mgm.inode=\";\n      in += remaining[0].c_str();\n      in += \"&mgm.fusex.pid=\";\n      in += remaining[1].c_str();\n    } else if (subcmd == \"conf\") {\n      in += \"&mgm.subcmd=conf\";\n      if (remaining.size() > 0) {\n        in += \"&mgm.fusex.hb=\";\n        in += remaining[0].c_str();\n      }\n      if (remaining.size() > 1) {\n        in += \"&mgm.fusex.qc=\";\n        in += remaining[1].c_str();\n      }\n      if (remaining.size() > 2) {\n        in += \"&mgm.fusex.bc.max=\";\n        in += remaining[2].c_str();\n      }\n      if (remaining.size() > 3) {\n        in += \"&mgm.fusex.bc.match=\";\n        in += remaining[3].c_str();\n      }\n    } else {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = ctx.outputResult(ctx.clientCommand(in, true, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeFusexHelp().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterFusexNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<FusexCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/geosched-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: geosched-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringTokenizer.hh\"\n#include \"common/Utils.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <XrdOuc/XrdOucString.hh>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeGeoschedHelp()\n{\n  return \"Usage: geosched show|set|updater|forcerefresh|disabled|access ...\\n\\n\"\n         \"'[eos] geosched ..' Interact with the file geoscheduling engine in \"\n         \"EOS.\\n\\n\"\n         \"Subcommands:\\n\"\n         \"  show [-c|-m] tree [<scheduling group>]     show scheduling trees\\n\"\n         \"  show [-c|-m] snapshot [<group>] [<optype>] show snapshots\\n\"\n         \"  show param                                show internal parameters\\n\"\n         \"  show state [-m]                           show internal state\\n\"\n         \"  set <param> [index] <value>                set parameter value\\n\"\n         \"  updater pause|resume                       pause/resume tree updater\\n\"\n         \"  forcerefresh                               force refresh\\n\"\n         \"  disabled add|rm|show <geotag> <optype> <group>\\n\"\n         \"  access setdirect|showdirect|cleardirect|setproxygroup|showproxygroup|clearproxygroup ...\\n\\n\"\n         \"Options:\\n\"\n         \"  -c  enable color display\\n\"\n         \"  -m  list in monitoring format\\n\\n\"\n         \"Note: Geotags must be alphanumeric segments, max 8 chars, format \"\n         \"<tag1>::<tag2>::...::<tagN>\\n\";\n}\n\nvoid ConfigureGeoschedApp(CLI::App& app, std::string& subcmd)\n{\n  app.name(\"geosched\");\n  app.description(\"Geographical scheduler control\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeGeoschedHelp();\n      }));\n  app.add_option(\"subcmd\", subcmd,\n                 \"show|set|updater|forcerefresh|disabled|access\")\n      ->required();\n}\n\nclass GeoschedCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"geosched\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Geographical scheduler control\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (args.empty() || wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    std::string subcmd;\n    ConfigureGeoschedApp(app, subcmd);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    eos::common::StringTokenizer subtokenizer(joined.c_str());\n    subtokenizer.GetLine();\n    XrdOucString cmd = subtokenizer.GetToken();\n    std::set<std::string> supportedParam = {\n        \"skipSaturatedAccess\",    \"skipSaturatedDrnAccess\",\n        \"skipSaturatedBlcAccess\", \"plctDlScorePenalty\",\n        \"plctUlScorePenalty\",     \"accessDlScorePenalty\",\n        \"accessUlScorePenalty\",   \"fillRatioLimit\",\n        \"fillRatioCompTol\",       \"saturationThres\",\n        \"timeFrameDurationMs\",    \"penaltyUpdateRate\",\n        \"proxyCloseToFs\"};\n    XrdOucString in = \"\";\n    if ((cmd != \"show\") && (cmd != \"set\") && (cmd != \"updater\") &&\n        (cmd != \"forcerefresh\") && (cmd != \"disabled\") && (cmd != \"access\")) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    in = \"mgm.cmd=geosched\";\n    if (cmd == \"show\") {\n      XrdOucString subcmd = subtokenizer.GetToken();\n      if (subcmd == \"-c\") {\n        in += \"&mgm.usecolors=1\";\n        subcmd = subtokenizer.GetToken();\n      } else if (subcmd == \"-m\") {\n        in += \"&mgm.monitoring=1\";\n        subcmd = subtokenizer.GetToken();\n      }\n      if ((subcmd != \"tree\") && (subcmd != \"snapshot\") && (subcmd != \"state\") &&\n          (subcmd != \"param\")) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      if (subcmd == \"state\") {\n        in += \"&mgm.subcmd=showstate\";\n        subcmd = subtokenizer.GetToken();\n        if (subcmd == \"-m\") {\n          in += \"&mgm.monitoring=1\";\n        }\n      }\n      if (subcmd == \"param\") {\n        in += \"&mgm.subcmd=showparam\";\n      }\n      if (subcmd == \"tree\") {\n        in += \"&mgm.subcmd=showtree\";\n        in += \"&mgm.schedgroup=\";\n        XrdOucString group = subtokenizer.GetToken();\n        if (group.length()) {\n          in += group;\n        }\n      }\n      if (subcmd == \"snapshot\") {\n        in += \"&mgm.subcmd=showsnapshot\";\n        in += \"&mgm.schedgroup=\";\n        XrdOucString group = subtokenizer.GetToken();\n        if (group.length()) {\n          in += group;\n        }\n        in += \"&mgm.optype=\";\n        XrdOucString optype = subtokenizer.GetToken();\n        if (optype.length()) {\n          in += optype;\n        }\n      }\n    }\n    if (cmd == \"set\") {\n      XrdOucString parameter = subtokenizer.GetToken();\n      if (!parameter.length()) {\n        fprintf(stderr, \"Error: parameter name is not provided\\n\");\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      if (supportedParam.find(parameter.c_str()) == supportedParam.end()) {\n        fprintf(stderr, \"Error: parameter %s not supported\\n\",\n                parameter.c_str());\n        return 0;\n      }\n      XrdOucString index = subtokenizer.GetToken();\n      XrdOucString value = subtokenizer.GetToken();\n      if (!index.length()) {\n        fprintf(stderr, \"Error: value is not provided\\n\");\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      if (!value.length()) {\n        value = index;\n        index = \"-1\";\n      }\n      double didx = 0.0;\n      if (!sscanf(value.c_str(), \"%lf\", &didx)) {\n        fprintf(stderr,\n                \"Error: parameter %s should have a numeric value, %s was \"\n                \"provided\\n\",\n                parameter.c_str(), value.c_str());\n        return 0;\n      }\n      if (!XrdOucString(index.c_str()).isdigit()) {\n        fprintf(stderr,\n                \"Error: index for parameter %s should have a numeric value, %s \"\n                \"was provided\\n\",\n                parameter.c_str(), index.c_str());\n        return 0;\n      }\n      in += \"&mgm.subcmd=set\";\n      in += \"&mgm.param=\";\n      in += parameter.c_str();\n      in += \"&mgm.paramidx=\";\n      in += index.c_str();\n      in += \"&mgm.value=\";\n      in += value.c_str();\n    }\n    if (cmd == \"updater\") {\n      XrdOucString subcmd = subtokenizer.GetToken();\n      if (subcmd == \"pause\") {\n        in += \"&mgm.subcmd=updtpause\";\n      }\n      if (subcmd == \"resume\") {\n        in += \"&mgm.subcmd=updtresume\";\n      }\n    }\n    if (cmd == \"forcerefresh\") {\n      in += \"&mgm.subcmd=forcerefresh\";\n    }\n    if (cmd == \"disabled\") {\n      XrdOucString subcmd = subtokenizer.GetToken();\n      XrdOucString geotag, group, optype;\n      if ((subcmd != \"add\") && (subcmd != \"rm\") && (subcmd != \"show\")) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      geotag = subtokenizer.GetToken();\n      optype = subtokenizer.GetToken();\n      group = subtokenizer.GetToken();\n      if (!group.length() || !optype.length() || !geotag.length()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      std::string sgroup(group.c_str()), soptype(optype.c_str()),\n          sgeotag(geotag.c_str());\n      const char fbdChars[] = \"&/,;%$#@!*\";\n      auto fbdMatch = sgroup.find_first_of(fbdChars);\n      if (fbdMatch != std::string::npos && !(sgroup == \"*\")) {\n        fprintf(stderr, \"illegal character %c detected in group name %s\\n\",\n                sgroup[fbdMatch], sgroup.c_str());\n        return 0;\n      }\n      fbdMatch = soptype.find_first_of(fbdChars);\n      if (fbdMatch != std::string::npos && !(soptype == \"*\")) {\n        fprintf(stderr, \"illegal character %c detected in optype %s\\n\",\n                soptype[fbdMatch], soptype.c_str());\n        return 0;\n      }\n      if (!(sgeotag == \"*\" && subcmd != \"add\")) {\n        std::string tmp_geotag = eos::common::SanitizeGeoTag(sgeotag);\n        if (tmp_geotag != sgeotag) {\n          fprintf(stderr, \"%s\\n\", tmp_geotag.c_str());\n          return 0;\n        }\n      }\n      in += (\"&mgm.subcmd=disabled\" + subcmd);\n      if (geotag.length()) {\n        in += (\"&mgm.geotag=\" + geotag);\n      }\n      in += (\"&mgm.schedgroup=\" + group);\n      in += (\"&mgm.optype=\" + optype);\n    }\n    if (cmd == \"access\") {\n      XrdOucString subcmd = subtokenizer.GetToken();\n      XrdOucString geotag, geotag_list, optype;\n      if ((subcmd != \"setdirect\") && (subcmd != \"showdirect\") &&\n          (subcmd != \"cleardirect\") && (subcmd != \"setproxygroup\") &&\n          (subcmd != \"showproxygroup\") && (subcmd != \"clearproxygroup\")) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      const char* token = 0;\n      if ((token = subtokenizer.GetToken())) {\n        geotag = token;\n      }\n      if ((token = subtokenizer.GetToken())) {\n        geotag_list = token;\n      }\n      in += (\"&mgm.subcmd=access\" + subcmd);\n      if (subcmd == \"showdirect\" || subcmd == \"showproxygroup\") {\n        if (geotag.length()) {\n          if (geotag != \"-m\" || geotag_list.length()) {\n            printHelp();\n            global_retc = EINVAL;\n            return 0;\n          } else {\n            in += \"&mgm.monitoring=1\";\n          }\n        }\n      } else {\n        if (subcmd == \"setdirect\" || subcmd == \"setproxygroup\") {\n          if (!geotag.length() || !geotag_list.length()) {\n            printHelp();\n            global_retc = EINVAL;\n            return 0;\n          }\n          if (subcmd == \"setdirect\") {\n            std::string tmp_list(geotag_list.c_str());\n            auto geotags =\n                eos::common::StringTokenizer::split<std::vector<std::string>>(\n                    tmp_list, ',');\n            tmp_list.clear();\n            for (const auto& tag : geotags) {\n              std::string tmp_tag = eos::common::SanitizeGeoTag(tag);\n              if (tmp_tag != tag) {\n                fprintf(stderr, \"%s\\n\", tmp_tag.c_str());\n                return 0;\n              }\n            }\n          }\n          in += (\"&mgm.geotaglist=\" + geotag_list);\n        } else {\n          if (!geotag.length() || geotag_list.length()) {\n            printHelp();\n            global_retc = EINVAL;\n            return 0;\n          }\n        }\n        std::string tmp_geotag = eos::common::SanitizeGeoTag(geotag.c_str());\n        if (tmp_geotag != geotag.c_str()) {\n          fprintf(stderr, \"%s\\n\", tmp_geotag.c_str());\n          return 0;\n        }\n        in += (\"&mgm.geotag=\" + geotag);\n      }\n    }\n    if (subtokenizer.GetToken()) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = output_result(client_command(in, true));\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeGeoschedHelp().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterGeoschedNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<GeoschedCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/group-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: group-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleCompletion.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/ICmdHelper.hh\"\n#include <iomanip>\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeGroupHelp()\n{\n  std::ostringstream oss;\n  oss << \" usage:\\n\"\n      << std::endl\n      << \"group ls [-s] [-g <depth>] [-b|--brief] [-m|-l|--io] [<groups>] : list groups\\n\"\n      << \"\\t <groups> : list <groups> only, where <groups> is a substring match and can \"\n         \"be a comma seperated list\\n\"\n      << \"\\t       -s : silent mode\\n\"\n      << \"\\t       -g : geo output - aggregate group information along the instance \"\n         \"geotree down to <depth>\\n\"\n      << \"\\t       -b : brief output\\n\"\n      << \"\\t       -m : monitoring key=value output format\\n\"\n      << \"\\t       -l : long output - list also file systems after each group\\n\"\n      << \"\\t     --io : print IO statistics for the group\\n\"\n      << \"\\t     --IO : print IO statistics for each filesystem\\n\"\n      << std::endl\n      << \"group rm <group-name> : remove group\\n\"\n      << std::endl\n      << \"group set <group-name> on|drain|off : activate/drain/deactivate group\\n\"\n      << \"\\t  => when a group is (re-)enabled, the drain pull flag is recomputed for all \"\n         \"filesystems within a group\\n\"\n      << \"\\t  => when a group is (re-)disabled, the drain pull flag is removed from all \"\n         \"members in the group\\n\"\n      << \"\\t  => when a group is in drain, all the filesystems in the group will be \"\n         \"drained to other groups\\n\"\n      << std::endl;\n  return oss.str();\n}\n\nclass GroupHelper : public ICmdHelper {\npublic:\n  GroupHelper(const GlobalOptions& opts) : ICmdHelper(opts) {}\n  ~GroupHelper() override = default;\n  bool\n  ParseCommand(const char* arg) override\n  {\n    eos::console::GroupProto* group = mReq.mutable_group();\n    eos::common::StringTokenizer tokenizer(arg);\n    tokenizer.GetLine();\n    std::string token;\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n    if (token == \"ls\") {\n      eos::console::GroupProto_LsProto* ls = group->mutable_ls();\n      while (tokenizer.NextToken(token)) {\n        if (token == \"-s\") {\n          mIsSilent = true;\n        } else if (token == \"-g\") {\n          if (!tokenizer.NextToken(token) ||\n              !eos::common::StringTokenizer::IsUnsignedNumber(token)) {\n            std::cerr << \"error: geodepth was not provided or it does not have \"\n                      << \"the correct value: geodepth should be a positive \"\n                      << \"integer\\n\";\n            return false;\n          }\n          try {\n            ls->set_outdepth(std::stoi(token));\n          } catch (...) {\n            std::cerr << \"error: argument to geodepth (-g flag) needs to be a \"\n                         \"positive integer\\n\";\n            return false;\n          }\n        } else if (token == \"-b\" || token == \"--brief\") {\n          ls->set_outhost(true);\n        } else if (token == \"-m\") {\n          ls->set_outformat(eos::console::GroupProto_LsProto::MONITORING);\n        } else if (token == \"-l\") {\n          ls->set_outformat(eos::console::GroupProto_LsProto::LISTING);\n        } else if (token == \"--io\") {\n          ls->set_outformat(eos::console::GroupProto_LsProto::IOGROUP);\n        } else if (token == \"--IO\") {\n          ls->set_outformat(eos::console::GroupProto_LsProto::IOFS);\n        } else if (token.find('-') != 0) {\n          ls->set_selection(token);\n        } else {\n          return false;\n        }\n      }\n    } else if (token == \"rm\") {\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n      eos::console::GroupProto_RmProto* rm = group->mutable_rm();\n      rm->set_group(token);\n    } else if (token == \"set\") {\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n      eos::console::GroupProto_SetProto* set = group->mutable_set();\n      set->set_group(token);\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n      if (token == \"on\" || token == \"off\" || token == \"drain\") {\n        set->set_group_state(token);\n      } else {\n        return false;\n      }\n    } else {\n      return false;\n    }\n    return true;\n  }\n};\n\nclass GroupProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"group\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Group configuration\";\n  }\n  std::string\n  helpText() const override\n  {\n    return MakeGroupHelp();\n  }\n  std::vector<std::string>\n  complete(const std::vector<std::string>& args) const override\n  {\n    return eos_help_completion_candidates(name(), helpText(), args);\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      if (args[i].find(' ') != std::string::npos)\n        oss << std::quoted(args[i]);\n      else\n        oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    GroupHelper helper(*ctx.globalOpts);\n    if (!helper.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = helper.Execute();\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", helpText().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterGroupProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<GroupProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/health-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: health-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/commands/HealthCommand.hh\"\n#include <memory>\n#include <sstream>\n\nnamespace {\nclass HealthConsoleCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"health\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Cluster health check\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    ::HealthCommand cmd(joined.c_str());\n    cmd.Execute();\n    global_retc = 0;\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n  }\n};\n} // namespace\n\nvoid\nRegisterHealthNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<HealthConsoleCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/info-alias.cc",
    "content": "// ----------------------------------------------------------------------\n// File: info-alias.cc\n// Purpose: Provide 'info' alias for 'file info ...'\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include <memory>\n#include <vector>\n\nnamespace {\nclass InfoAliasCommand : public IConsoleCommand {\npublic:\n  const char* name() const override { return \"info\"; }\n  const char* description() const override { return \"Alias for 'file info'\"; }\n  bool requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    IConsoleCommand* fileCmd = CommandRegistry::instance().find(\"file\");\n    if (!fileCmd) {\n      fprintf(stderr, \"error: 'file' command not available\\n\");\n      return -1;\n    }\n    std::vector<std::string> forwarded;\n    forwarded.reserve(args.size() + 1);\n    forwarded.emplace_back(\"info\");\n    forwarded.insert(forwarded.end(), args.begin(), args.end());\n    return fileCmd->run(forwarded, ctx);\n  }\n  void printHelp() const override {\n    fprintf(stderr, \"Usage: info <path> [options] (alias for 'file info')\\n\");\n  }\n};\n} // namespace\n\nvoid RegisterInfoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<InfoAliasCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/info-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: info-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <memory>\n#include <sstream>\n\nnamespace {\nclass InfoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"info\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Retrieve file or directory information\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    if (args.empty() || wants_help(args[0].c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    // Build mgm.cmd=fileinfo with filters\n    XrdOucString path = args[0].c_str();\n    if ((!path.beginswith(\"fid:\")) && (!path.beginswith(\"fxid:\")) &&\n        (!path.beginswith(\"pid:\")) && (!path.beginswith(\"pxid:\")) &&\n        (!path.beginswith(\"inode:\"))) {\n      path = abspath(path.c_str());\n    }\n    XrdOucString in = \"mgm.cmd=fileinfo&mgm.path=\";\n    in += path;\n    // Collect remaining flags/options into mgm.file.info.option\n    XrdOucString option = \"\";\n    for (size_t i = 1; i < args.size(); ++i) {\n      XrdOucString tok = args[i].c_str();\n      if (tok.length()) {\n        if (tok == \"s\") {\n          option += \"silent\";\n        } else {\n          option += tok;\n        }\n      }\n    }\n    if (option.length()) {\n      in += \"&mgm.file.info.option=\";\n      in += option;\n    }\n    // If not silent, print output\n    if (option.find(\"silent\") == STR_NPOS) {\n      global_retc =\n          ctx.outputResult(ctx.clientCommand(in, false, nullptr), true);\n    }\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n  }\n};\n} // namespace\n\nvoid\nRegisterInfoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<InfoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/inspector-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: inspector-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/ICmdHelper.hh\"\n#include <iomanip>\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeInspectorHelp()\n{\n  std::ostringstream oss;\n  oss << \" usage:\\n\"\n      << std::endl\n      << \"inspector [--current|-c] [--last|-l] [-m] [-p] [-e] [-s|--space \"\n         \"<space_name>] [--all|-a] [--cost|-C] [--usage|-U] [--birth|-B] \"\n         \"[--access|-A] [--vs|-V] [--layouts|-L] : show namespace inspector output\\n\"\n      << std::endl\n      << \"Same request as 'space inspector'; default MGM space is \\\"default\\\" \"\n         \"unless -s|--space is given.\\n\"\n      << std::endl\n      << \"\\t  -c  : show current scan\\n\"\n      << \"\\t  -l  : show last complete scan\\n\"\n      << \"\\t  -m  : print last scan in monitoring format ( by default this enables \"\n         \"--cost --usage --birth --access --layouts)\\n\"\n      << \"\\t  -A  : combined with -m prints access time distributions\\n\"\n      << \"\\t  -V  : combined with -m prints birth time vs access time distributions\\n\"\n      << \"\\t  -B  : combined with -m prints birth time distributions\\n\"\n      << \"\\t  -C  : combined with -m prints cost information (storage price per \"\n         \"user/group)\\n\"\n      << \"\\t  -U  : combined with -m prints usage information (stored bytes per \"\n         \"user/group)\\n\"\n      << \"\\t  -L  : combined with -m prints layout statistics\\n\"\n      << \"\\t  -a  : combined with -m or -C or -U removes the restriction to show only \"\n         \"the top 10 user ranking\\n\"\n      << \"\\t  -p  : combined with -c or -l lists erroneous files\\n\"\n      << \"\\t  -e  : combined with -c or -l exports erroneous files on the MGM into \"\n         \"/var/log/eos/mgm/FileInspector.<date>.list\\n\"\n      << \"\\t  -s  : select target space, by default \\\"default\\\" space is used\\n\"\n      << std::endl\n      << \"\\t  -M|--money : money output\\n\"\n      << std::endl\n      << \"space config <space-name> space.inspector=on|off                      : \"\n         \"enable/disable the file inspector [ default=off ]\\n\"\n      << \"space config <space-name> space.inspector.interval=<sec>              : \"\n         \"time interval after which the inspector will run, default 4h\\n\";\n  return oss.str();\n}\n\nvoid ConfigureInspectorApp(CLI::App& app)\n{\n  app.name(\"inspector\");\n  app.description(\"Run inspector tools\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeInspectorHelp();\n      }));\n}\n\nclass InspectorHelper : public ICmdHelper {\npublic:\n  explicit InspectorHelper(const GlobalOptions& opts) : ICmdHelper(opts)\n  {\n    mIsAdmin = true;\n  }\n  bool\n  ParseCommand(const char* arg) override\n  {\n    eos::common::StringTokenizer tok(arg);\n    tok.GetLine();\n    std::string token;\n    eos::console::SpaceProto* space = mReq.mutable_space();\n    eos::console::SpaceProto_InspectorProto* insp = space->mutable_inspector();\n    insp->set_mgmspace(\"default\");\n    std::string options;\n\n    while (tok.NextToken(token)) {\n      if ((token == \"-s\") || (token == \"--space\")) {\n        if (tok.NextToken(token)) {\n          insp->set_mgmspace(token);\n        } else {\n          std::cerr << \"error: no space specified\" << std::endl;\n          return false;\n        }\n      } else if (token == \"-c\" || token == \"--current\") {\n        options += \"c\";\n      } else if (token == \"-l\" || token == \"--last\") {\n        options += \"l\";\n      } else if (token == \"-m\") {\n        options += \"m\";\n      } else if (token == \"-p\") {\n        options += \"p\";\n      } else if (token == \"-e\") {\n        options += \"e\";\n      } else if (token == \"-C\" || token == \"--cost\") {\n        options += \"C\";\n      } else if (token == \"-U\" || token == \"--usage\") {\n        options += \"U\";\n      } else if (token == \"-L\" || token == \"--layouts\") {\n        options += \"L\";\n      } else if (token == \"-B\" || token == \"--birth\") {\n        options += \"B\";\n      } else if (token == \"-A\" || token == \"--access\") {\n        options += \"A\";\n      } else if (token == \"-a\" || token == \"--all\") {\n        options += \"Z\";\n      } else if (token == \"-V\" || token == \"--vs\") {\n        options += \"V\";\n      } else if (token == \"-M\" || token == \"--money\") {\n        options += \"M\";\n      } else {\n        return false;\n      }\n    }\n\n    insp->set_options(options);\n    return true;\n  }\n};\n\nclass InspectorCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"inspector\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Run inspector tools\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      if (args[i].find(' ') != std::string::npos)\n        oss << std::quoted(args[i]);\n      else\n        oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    InspectorHelper helper(*ctx.globalOpts);\n    if (!helper.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = helper.Execute(true, true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureInspectorApp(app);\n    fprintf(stderr, \"%s\", app.help().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterInspectorNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<InspectorCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/io-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: io-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringConversion.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleCompletion.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/ICmdHelper.hh\"\n#include <CLI/CLI.hpp>\n#include <iomanip>\n#include <memory>\n#include <sstream>\n\nnamespace {\n\nstd::string MakeIoHelp()\n{\n  std::ostringstream oss;\n  oss << \" usage:\\n\"\n      << std::endl\n      << \"io stat [-l] [-a] [-m] [-n] [-t] [-d] [-x] [--ss] [--sa] [--si] : print io \"\n         \"statistics\\n\"\n      << \"\\t  -l : show summary information (this is the default if -a,-t,-d,-x is not \"\n         \"selected)\\n\"\n      << \"\\t  -a : break down by uid/gid\\n\"\n      << \"\\t  -m : print in <key>=<val> monitoring format\\n\"\n      << \"\\t  -n : print numerical uid/gids\\n\"\n      << \"\\t  -t : print top user stats\\n\"\n      << \"\\t  -d : break down by domains\\n\"\n      << \"\\t  -x : break down by application\\n\"\n      << \"\\t  --ss : show table with transfer sample statistics\\n\"\n      << \"\\t  --sa : start collection of statistics given number of seconds ago\\n\"\n      << \"\\t  --si : collect statistics over given interval of seconds\\n\"\n      << \"\\t  Note: this tool shows data for finished transfers only (using storage node \"\n         \"reports)\\n\"\n      << \"\\t  Example: asking for data of finished transfers which were transferred \"\n         \"during interval [now - 180s, now - 120s]:\\n\"\n      << \"\\t           eos io stat -x --sa 120 --si 60\\n\"\n      << std::endl\n      << \"io enable [-r] [-p] [-n] [--udp <address>] : enable collection of io \"\n         \"statistics\\n\"\n      << \"\\t         no arg. : start the collection thread\\n\"\n      << \"\\t              -r : enable collection of io reports\\n\"\n      << \"\\t              -p : enable popularity accounting\\n\"\n      << \"\\t              -n : enable report namespace\\n\"\n      << \"\\t --udp <address> : add a UDP message target for io UDP packets (the \"\n         \"configured targets are shown by 'io stat -l')\\n\"\n      << std::endl\n      << \"io disable [-r] [-p] [-n] [--udp <address>] : disable collection of io \"\n         \"statistics\\n\"\n      << \"\\t         no arg. : stop the collection thread\\n\"\n      << \"\\t              -r : disable collection of io reports\\n\"\n      << \"\\t              -p : disable popularity accounting\\n\"\n      << \"\\t              -n : disable report namespace\\n\"\n      << \"\\t --udp <address> : remove a UDP message target for io UDP packets (the \"\n         \"configured targets are shown by 'io stat -l')\\n\"\n      << std::endl\n      << \"io report <path> : show contents of report namespace for <path>\\n\"\n      << std::endl\n      << \"io ns [-a] [-m] [-n] [-b] [-100|-1000|-10000] [-w] [-f] : show namespace IO \"\n         \"ranking (popularity)\\n\"\n      << \"\\t      -a :  don't limit the output list\\n\"\n      << \"\\t      -m :  print in <key>=<val> monitoring format\\n\"\n      << \"\\t      -n :  show ranking by number of accesses\\n\"\n      << \"\\t      -b :  show ranking by number of bytes\\n\"\n      << \"\\t    -100 :  show the first 100 in the ranking\\n\"\n      << \"\\t   -1000 :  show the first 1000 in the ranking\\n\"\n      << \"\\t  -10000 :  show the first 10000 in the ranking\\n\"\n      << \"\\t      -w :  show history for the last 7 days\\n\"\n      << \"\\t      -f :  show the 'hotfiles' which are the files with highest number of \"\n         \"present file opens\\n\"\n      << std::endl\n      << \"io shaping [subcommand] [options...] : interact with the Traffic Shaping \"\n         \"engine\\n\"\n      << std::endl\n      << \"   SUBCOMMANDS\\n\"\n      << \"     ls [options...] : view real-time IO rates and shaping status\\n\"\n      << \"\\t   --apps   : show rates by application\\n\"\n      << \"\\t   --users  : show rates by user (uid)\\n\"\n      << \"\\t   --groups : show rates by group (gid)\\n\"\n      << \"\\t   --nodes  : show rates by storage node (FST)\\n\"\n      << \"\\t   --json   : output in JSON format\\n\"\n      << \"\\t   --sys    : include meta statistics about Traffic Shaping system\\n\"\n      << \"\\t   --window <1|5|15|60|300> : time window in seconds for SMA (default 60)\\n\"\n      << std::endl\n      << \"     enable  : globally enable traffic shaping\\n\"\n      << \"     disable : globally disable traffic shaping\\n\"\n      << std::endl\n      << \"     policy [action] [options...] : manage shaping limits and reservations\\n\"\n      << \"\\t   action 'ls' : list configured policies\\n\"\n      << \"\\t     usage: policy ls [options...]\\n\"\n      << \"\\t       --apps       : filter by applications\\n\"\n      << \"\\t       --users      : filter by users (uid)\\n\"\n      << \"\\t       --groups     : filter by groups (gid)\\n\"\n      << \"\\t       --controller : show ephemeral controller limits\\n\"\n      << \"\\t       --json       : output in JSON format\\n\"\n      << \"\\t   action 'set' : configure a new policy or modify an existing one\\n\"\n      << \"\\t     usage: policy set <identity> [parameters...] [--enable|--disable]\\n\"\n      << \"\\t       <identity>   : --app <name> | --uid <id> | --gid <id>\\n\"\n      << \"\\t       [parameters] : --limit-read <rate> | --limit-write <rate> | \"\n         \"--reservation-read <rate> | --reservation-write <rate>\\n\"\n      << \"\\t                      | --controller-limit-read <rate> | \"\n         \"--controller-limit-write <rate>\\n\"\n      << \"\\t                      (rate can use suffixes, e.g., 10M, 500K, or 0 to \"\n         \"remove)\\n\"\n      << \"\\t   action 'rm' : completely remove a configured policy\\n\"\n      << \"\\t     usage: policy rm <identity>\\n\"\n      << \"\\t       <identity>   : --app <name> | --uid <id> | --gid <id>\\n\"\n      << std::endl\n      << \"     config [action] [options...] : manage traffic shaping thread \"\n         \"configurations\\n\"\n      << \"\\t   action 'ls' : list current thread update periods\\n\"\n      << \"\\t     usage: config ls\\n\"\n      << \"\\t   action 'set' : modify configuration settings such as update periods for \"\n         \"estimators and policy enforcement\\n\"\n      << \"\\t     usage: config set [--estimators-period <ms>] [--policy-period <ms>] \"\n         \"[--report-period <ms>] [--system-window <s>]\\n\"\n      << std::endl\n      << \"   EXAMPLES\\n\"\n      << \"\\t   # Show current application rates\\n\"\n      << \"\\t   eos io shaping ls --apps\\n\"\n      << std::endl\n      << \"\\t   # Globally enable the traffic shaping engine\\n\"\n      << \"\\t   eos io shaping enable\\n\"\n      << std::endl\n      << \"\\t   # Globally disable the traffic shaping engine\\n\"\n      << \"\\t   eos io shaping disable\\n\"\n      << std::endl\n      << \"\\t   # Limit 'eoscp' read rate to 10 MB/s and write rate to 50 MB/s\\n\"\n      << \"\\t   eos io shaping policy set --app eoscp --limit-read 10M --limit-write 50M\\n\"\n      << std::endl\n      << \"\\t   # Temporarily disable the policy for 'eoscp'\\n\"\n      << \"\\t   eos io shaping policy set --app eoscp --disable\\n\"\n      << std::endl\n      << \"\\t   # Remove the read limit for 'eoscp' but keep the write limit\\n\"\n      << \"\\t   eos io shaping policy set --app eoscp --limit-read 0\\n\"\n      << std::endl\n      << \"\\t   # Completely delete the policy for user 1001\\n\"\n      << \"\\t   eos io shaping policy rm --uid 1001\\n\"\n      << std::endl\n      << \"\\t   # List all configured application policies including machine limits\\n\"\n      << \"\\t   eos io shaping policy ls --apps --controller\\n\"\n      << std::endl\n      << \"\\t   # Show current thread configurations\\n\"\n      << \"\\t   eos io shaping config ls\\n\"\n      << std::endl\n      << \"\\t   # Change the estimators update period to 200 ms\\n\"\n      << \"\\t   eos io shaping config set --estimators-period 200\\n\"\n      << std::endl;\n  return oss.str();\n}\n\n//------------------------------------------------------------------------------\n// Build full CLI11 app with all io subcommands and populate proto\n//------------------------------------------------------------------------------\nbool\nBuildAndParseIoApp(const std::string& input, eos::console::IoProto* io)\n{\n  CLI::App app{\"io\"};\n  app.require_subcommand(1);\n\n  // stat\n  auto* stat_cmd = app.add_subcommand(\"stat\", \"Print IO statistics\");\n  stat_cmd->add_flag(\"-l\", \"Show summary\");\n  stat_cmd->add_flag(\"-a\", \"Break down by uid/gid\");\n  stat_cmd->add_flag(\"-m\", \"Monitoring format\");\n  stat_cmd->add_flag(\"-n\", \"Numerical uid/gids\");\n  stat_cmd->add_flag(\"-t\", \"Top user stats\");\n  stat_cmd->add_flag(\"-d\", \"Break down by domains\");\n  stat_cmd->add_flag(\"-x\", \"Break down by application\");\n  stat_cmd->add_flag(\"--ss\", \"Transfer sample statistics\");\n  stat_cmd->add_option(\"--sa\", \"Seconds ago\")->type_name(\"SEC\");\n  stat_cmd->add_option(\"--si\", \"Time interval in seconds\")->type_name(\"SEC\");\n  stat_cmd->callback([stat_cmd, io]() {\n    auto* stat = io->mutable_stat();\n    stat->set_summary(stat_cmd->count(\"-l\") > 0);\n    stat->set_details(stat_cmd->count(\"-a\") > 0);\n    stat->set_monitoring(stat_cmd->count(\"-m\") > 0);\n    stat->set_numerical(stat_cmd->count(\"-n\") > 0);\n    stat->set_top(stat_cmd->count(\"-t\") > 0);\n    stat->set_domain(stat_cmd->count(\"-d\") > 0);\n    stat->set_apps(stat_cmd->count(\"-x\") > 0);\n    stat->set_sample_stat(stat_cmd->count(\"--ss\") > 0);\n    if (stat_cmd->count(\"--sa\")) {\n      stat->set_time_ago(stat_cmd->get_option(\"--sa\")->as<uint64_t>());\n    }\n    if (stat_cmd->count(\"--si\")) {\n      stat->set_time_interval(stat_cmd->get_option(\"--si\")->as<uint64_t>());\n    }\n  });\n\n  // ns\n  auto* ns_cmd = app.add_subcommand(\"ns\", \"Show namespace IO ranking\");\n  ns_cmd->add_flag(\"-m\", \"Monitoring format\");\n  ns_cmd->add_flag(\"-b\", \"Rank by bytes\");\n  ns_cmd->add_flag(\"-n\", \"Rank by access count\");\n  ns_cmd->add_flag(\"-w\", \"Last 7 days\");\n  ns_cmd->add_flag(\"-f\", \"Hotfiles\");\n  ns_cmd->allow_non_standard_option_names();\n  ns_cmd->add_flag(\"-a\", \"Don't limit output\");\n  ns_cmd->add_flag(\"-100\", \"Top 100\");\n  ns_cmd->add_flag(\"-1000\", \"Top 1000\");\n  ns_cmd->add_flag(\"-10000\", \"Top 10000\");\n  ns_cmd->callback([ns_cmd, io]() {\n    auto* ns = io->mutable_ns();\n    ns->set_monitoring(ns_cmd->count(\"-m\") > 0);\n    ns->set_rank_by_byte(ns_cmd->count(\"-b\") > 0);\n    ns->set_rank_by_access(ns_cmd->count(\"-n\") > 0);\n    ns->set_last_week(ns_cmd->count(\"-w\") > 0);\n    ns->set_hotfiles(ns_cmd->count(\"-f\") > 0);\n    if (ns_cmd->count(\"-a\") > 0)\n      ns->set_count(eos::console::IoProto_NsProto::ALL);\n    else if (ns_cmd->count(\"-100\") > 0)\n      ns->set_count(eos::console::IoProto_NsProto::ONEHUNDRED);\n    else if (ns_cmd->count(\"-1000\") > 0)\n      ns->set_count(eos::console::IoProto_NsProto::ONETHOUSAND);\n    else if (ns_cmd->count(\"-10000\") > 0)\n      ns->set_count(eos::console::IoProto_NsProto::TENTHOUSAND);\n  });\n\n  // report\n  auto* report_cmd = app.add_subcommand(\"report\", \"Show report namespace for path\");\n  report_cmd->add_option(\"path\", \"Path to report\")->required();\n  report_cmd->callback([report_cmd, io]() {\n    io->mutable_report()->set_path(report_cmd->get_option(\"path\")->as<std::string>());\n  });\n\n  // enable\n  auto* enable_cmd = app.add_subcommand(\"enable\", \"Enable IO statistics collection\");\n  enable_cmd->add_flag(\"-r\", \"Enable reports\");\n  enable_cmd->add_flag(\"-p\", \"Enable popularity\");\n  enable_cmd->add_flag(\"-n\", \"Enable report namespace\");\n  enable_cmd->add_option(\"--udp\", \"UDP target address\")->type_name(\"ADDR\");\n  enable_cmd->callback([enable_cmd, io]() {\n    auto* en = io->mutable_enable();\n    en->set_switchx(true);\n    en->set_reports(enable_cmd->count(\"-r\") > 0);\n    en->set_popularity(enable_cmd->count(\"-p\") > 0);\n    en->set_namespacex(enable_cmd->count(\"-n\") > 0);\n    if (enable_cmd->count(\"--udp\"))\n      en->set_upd_address(enable_cmd->get_option(\"--udp\")->as<std::string>());\n  });\n\n  // disable\n  auto* disable_cmd = app.add_subcommand(\"disable\", \"Disable IO statistics collection\");\n  disable_cmd->add_flag(\"-r\", \"Disable reports\");\n  disable_cmd->add_flag(\"-p\", \"Disable popularity\");\n  disable_cmd->add_flag(\"-n\", \"Disable report namespace\");\n  disable_cmd->add_option(\"--udp\", \"UDP target address to remove\")->type_name(\"ADDR\");\n  disable_cmd->callback([disable_cmd, io]() {\n    auto* en = io->mutable_enable();\n    en->set_switchx(false);\n    en->set_reports(disable_cmd->count(\"-r\") > 0);\n    en->set_popularity(disable_cmd->count(\"-p\") > 0);\n    en->set_namespacex(disable_cmd->count(\"-n\") > 0);\n    if (disable_cmd->count(\"--udp\"))\n      en->set_upd_address(disable_cmd->get_option(\"--udp\")->as<std::string>());\n  });\n\n  // shaping (nested subcommands)\n  auto* shaping_cmd = app.add_subcommand(\"shaping\", \"Traffic Shaping engine\");\n  shaping_cmd->require_subcommand(1);\n  eos::console::IoProto_ShapingProto* shaping_proto = io->mutable_shaping();\n\n  auto* shaping_ls = shaping_cmd->add_subcommand(\"ls\", \"View real-time IO rates\");\n  auto* grp = shaping_ls->add_option_group(\"Grouping\")->require_option(0, 1);\n  grp->add_flag(\"--apps\", \"Show rates by application\");\n  grp->add_flag(\"--users\", \"Show rates by user\");\n  grp->add_flag(\"--groups\", \"Show rates by group\");\n  grp->add_flag(\"--nodes\", \"Show rates by storage node\");\n  shaping_ls->add_flag(\"--json\", \"JSON output\");\n  shaping_ls->add_flag(\"--sys\", \"Include system stats\");\n  shaping_ls->add_option(\"--window\", \"Time window (1|5|15|60|300)\")\n      ->check(CLI::IsMember({\"1\", \"5\", \"15\", \"60\", \"300\"}))\n      ->default_val(\"60\");\n  shaping_ls->callback([shaping_ls, shaping_proto]() {\n    auto* action = shaping_proto->mutable_list();\n    bool su = shaping_ls->count(\"--users\") > 0;\n    bool sg = shaping_ls->count(\"--groups\") > 0;\n    bool sn = shaping_ls->count(\"--nodes\") > 0;\n    action->set_show_apps(shaping_ls->count(\"--apps\") > 0 || (!su && !sg && !sn));\n    action->set_show_users(su);\n    action->set_show_groups(sg);\n    action->set_show_nodes(sn);\n    action->set_json_output(shaping_ls->count(\"--json\") > 0);\n    action->set_system_stats(shaping_ls->count(\"--sys\") > 0);\n    action->set_time_window_seconds(\n        static_cast<uint32_t>(std::stoul(shaping_ls->get_option(\"--window\")->as<std::string>())));\n  });\n\n  shaping_cmd->add_subcommand(\"enable\", \"Enable traffic shaping\")\n      ->callback([shaping_proto]() { shaping_proto->mutable_enable(); });\n  shaping_cmd->add_subcommand(\"disable\", \"Disable traffic shaping\")\n      ->callback([shaping_proto]() { shaping_proto->mutable_disable(); });\n\n  auto* policy_cmd = shaping_cmd->add_subcommand(\"policy\", \"Manage shaping policies\");\n  policy_cmd->require_subcommand(1);\n\n  auto* policy_ls = policy_cmd->add_subcommand(\"ls\", \"List policies\");\n  policy_ls->add_flag(\"--apps\");\n  policy_ls->add_flag(\"--users\");\n  policy_ls->add_flag(\"--groups\");\n  policy_ls->add_flag(\"--controller\");\n  policy_ls->add_flag(\"--json\");\n  policy_ls->callback([policy_ls, shaping_proto]() {\n    auto* a = shaping_proto->mutable_policy()->mutable_list();\n    a->set_filter_apps(policy_ls->count(\"--apps\") > 0);\n    a->set_filter_users(policy_ls->count(\"--users\") > 0);\n    a->set_filter_groups(policy_ls->count(\"--groups\") > 0);\n    a->set_show_controller_limits(policy_ls->count(\"--controller\") > 0);\n    a->set_json_output(policy_ls->count(\"--json\") > 0);\n  });\n\n  auto* policy_set = policy_cmd->add_subcommand(\"set\", \"Set policy\");\n  auto* tgrp = policy_set->add_option_group(\"Target\")->require_option(1);\n  tgrp->add_option(\"--app\", \"Application\");\n  tgrp->add_option(\"--uid\", \"User ID\");\n  tgrp->add_option(\"--gid\", \"Group ID\");\n  auto* pgrp = policy_set->add_option_group(\"Params\")->require_option(1, 6);\n  pgrp->add_option(\"--limit-read\");\n  pgrp->add_option(\"--limit-write\");\n  pgrp->add_option(\"--reservation-read\");\n  pgrp->add_option(\"--reservation-write\");\n  pgrp->add_option(\"--controller-limit-read\");\n  pgrp->add_option(\"--controller-limit-write\");\n  auto* pe = pgrp->add_flag(\"--enable\");\n  auto* pd = pgrp->add_flag(\"--disable\");\n  pe->excludes(pd);\n  policy_set->callback([policy_set, shaping_proto]() {\n    auto* a = shaping_proto->mutable_policy()->mutable_set();\n    if (policy_set->count(\"--app\"))\n      a->set_app(policy_set->get_option(\"--app\")->as<std::string>());\n    if (policy_set->count(\"--uid\"))\n      a->set_uid(policy_set->get_option(\"--uid\")->as<uint32_t>());\n    if (policy_set->count(\"--gid\"))\n      a->set_gid(policy_set->get_option(\"--gid\")->as<uint32_t>());\n    auto parse_rate = [](const std::string& s) -> uint64_t {\n      uint64_t n = 0;\n      eos::common::StringConversion::GetSizeFromString(s, n);\n      return n;\n    };\n    if (policy_set->count(\"--limit-read\"))\n      a->set_limit_read_bytes_per_sec(\n          parse_rate(policy_set->get_option(\"--limit-read\")->as<std::string>()));\n    if (policy_set->count(\"--limit-write\"))\n      a->set_limit_write_bytes_per_sec(\n          parse_rate(policy_set->get_option(\"--limit-write\")->as<std::string>()));\n    if (policy_set->count(\"--reservation-read\"))\n      a->set_reservation_read_bytes_per_sec(\n          parse_rate(policy_set->get_option(\"--reservation-read\")->as<std::string>()));\n    if (policy_set->count(\"--reservation-write\"))\n      a->set_reservation_write_bytes_per_sec(\n          parse_rate(policy_set->get_option(\"--reservation-write\")->as<std::string>()));\n    if (policy_set->count(\"--controller-limit-read\"))\n      a->set_controller_limit_read_bytes_per_sec(\n          parse_rate(policy_set->get_option(\"--controller-limit-read\")->as<std::string>()));\n    if (policy_set->count(\"--controller-limit-write\"))\n      a->set_controller_limit_write_bytes_per_sec(\n          parse_rate(policy_set->get_option(\"--controller-limit-write\")->as<std::string>()));\n    if (policy_set->count(\"--enable\"))\n      a->set_is_enabled(true);\n    else if (policy_set->count(\"--disable\"))\n      a->set_is_enabled(false);\n  });\n\n  auto* policy_rm = policy_cmd->add_subcommand(\"rm\", \"Remove policy\");\n  auto* rgrp = policy_rm->add_option_group(\"Target\")->require_option(1);\n  rgrp->add_option(\"--app\");\n  rgrp->add_option(\"--uid\");\n  rgrp->add_option(\"--gid\");\n  policy_rm->callback([policy_rm, shaping_proto]() {\n    auto* a = shaping_proto->mutable_policy()->mutable_remove();\n    if (policy_rm->count(\"--app\"))\n      a->set_app(policy_rm->get_option(\"--app\")->as<std::string>());\n    if (policy_rm->count(\"--uid\"))\n      a->set_uid(policy_rm->get_option(\"--uid\")->as<uint32_t>());\n    if (policy_rm->count(\"--gid\"))\n      a->set_gid(policy_rm->get_option(\"--gid\")->as<uint32_t>());\n  });\n\n  auto* config_cmd = shaping_cmd->add_subcommand(\"config\", \"Shaping config\");\n  config_cmd->require_subcommand(1);\n  config_cmd->add_subcommand(\"ls\", \"List config\")\n      ->callback([shaping_proto]() { shaping_proto->mutable_config()->mutable_list(); });\n  auto* config_set = config_cmd->add_subcommand(\"set\", \"Set config\");\n  config_set->require_option(1, 4);\n  config_set->add_option(\"--estimators-period\");\n  config_set->add_option(\"--policy-period\");\n  config_set->add_option(\"--report-period\");\n  config_set->add_option(\"--system-window\");\n  config_set->callback([config_set, shaping_proto]() {\n    auto* a = shaping_proto->mutable_config()->mutable_set();\n    if (config_set->count(\"--estimators-period\"))\n      a->set_update_estimators_thread_period_ms(\n          config_set->get_option(\"--estimators-period\")->as<uint32_t>());\n    if (config_set->count(\"--policy-period\"))\n      a->set_fst_io_policy_update_thread_period_ms(\n          config_set->get_option(\"--policy-period\")->as<uint32_t>());\n    if (config_set->count(\"--report-period\"))\n      a->set_fst_io_stats_reporting_thread_period_ms(\n          config_set->get_option(\"--report-period\")->as<uint32_t>());\n    if (config_set->count(\"--system-window\"))\n      a->set_system_stats_time_window_seconds(\n          config_set->get_option(\"--system-window\")->as<uint32_t>());\n  });\n\n  std::string to_parse = \"io \" + input;\n  try {\n    app.parse(to_parse, true);\n  } catch (const CLI::ParseError& e) {\n    app.exit(e);\n    return false;\n  }\n  return true;\n}\n\nclass IoHelper : public ICmdHelper {\npublic:\n  IoHelper(const GlobalOptions& opts) : ICmdHelper(opts) {}\n  ~IoHelper() override = default;\n  bool\n  ParseCommand(const char* arg) override\n  {\n    return BuildAndParseIoApp(arg, mReq.mutable_io());\n  }\n};\n\nclass IoProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"io\";\n  }\n  const char*\n  description() const override\n  {\n    return \"IO Interface\";\n  }\n  std::string\n  helpText() const override\n  {\n    return MakeIoHelp();\n  }\n  std::vector<std::string>\n  complete(const std::vector<std::string>& args) const override\n  {\n    return eos_help_completion_candidates(name(), helpText(), args);\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      if (args[i].find(' ') != std::string::npos)\n        oss << std::quoted(args[i]);\n      else\n        oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    IoHelper helper(*ctx.globalOpts);\n    if (!helper.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = helper.Execute();\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", helpText().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterIoProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<IoProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/license-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: license-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include <memory>\n#include <sstream>\n\nextern const char* license;\n\nnamespace {\nclass LicenseCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"license\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Show EOS license information\";\n  }\n  bool\n  requiresMgm(const std::string&) const override\n  {\n    return false;\n  }\n  int\n  run(const std::vector<std::string>&, CommandContext&) override\n  {\n    fprintf(stdout, \"%s\", license);\n    global_retc = 0;\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n  }\n};\n} // namespace\n\nvoid\nRegisterLicenseNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<LicenseCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/ln-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ln-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeLnHelp()\n{\n  return \"Usage: ln [-f] <link> <target>\\n\"\n         \"Create a symbolic link from <link> to <target>.\\n\"\n         \"  -f    force overwrite if <link> already exists\\n\";\n}\n\nvoid ConfigureLnApp(CLI::App& app, std::string& link, std::string& target,\n                    bool& force)\n{\n  app.name(\"ln\");\n  app.description(\"Create a symbolic link\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeLnHelp();\n      }));\n  app.add_flag(\"-f,--force\", force, \"force overwrite if link already exists\");\n  app.add_option(\"link\", link, \"link path\")->required();\n  app.add_option(\"target\", target, \"target path\")->required();\n}\n\nclass LnCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"ln\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Create a symbolic link\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (args.size() < 2 || wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    IConsoleCommand* fileCmd = CommandRegistry::instance().find(\"file\");\n    if (!fileCmd) {\n      fprintf(stderr, \"error: 'file' command not available\\n\");\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    std::string link;\n    std::string target;\n    bool force = false;\n    ConfigureLnApp(app, link, target, force);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    std::vector<std::string> fargs = {\"symlink\", link, target};\n    if (force)\n      fargs.insert(fargs.begin() + 1, \"-f\");\n    return fileCmd->run(fargs, ctx);\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeLnHelp().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterLnNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<LnCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/ls-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ls-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringConversion.hh\"\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include \"namespace/utils/Mode.hh\"\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <algorithm>\n#include <dirent.h>\n#include <memory>\n#include <sstream>\n#include <sys/stat.h>\n#include <vector>\n\nnamespace {\nstd::string MakeLsHelp(const CLI::App* app)\n{\n  std::ostringstream oss;\n  const std::string& name = app->get_name();\n  oss << \"Usage: \" << (name.empty() ? \"ls\" : name)\n      << \" [OPTION]... [--no-globbing] [PATH]...\\n\";\n  const std::string desc = app->get_description();\n  if (!desc.empty()) {\n    oss << desc << \"\\n\";\n  }\n  oss << \"\\nOptions:\\n\";\n\n  std::vector<std::pair<std::string, std::string>> lines;\n  size_t max_name = 0;\n  for (const auto* opt : app->get_options()) {\n    if (!opt || !opt->nonpositional()) {\n      continue;\n    }\n    std::string opt_name = opt->get_name(false, true);\n    if (opt_name.empty()) {\n      continue;\n    }\n    for (size_t i = 0; i + 1 < opt_name.size(); ++i) {\n      if (opt_name[i] == ',' && opt_name[i + 1] != ' ') {\n        opt_name.insert(i + 1, \" \");\n        ++i;\n      }\n    }\n    std::string opt_desc = opt->get_description();\n    max_name = std::max(max_name, opt_name.size());\n    lines.emplace_back(std::move(opt_name), std::move(opt_desc));\n  }\n\n  for (const auto& line : lines) {\n    oss << \"  \" << line.first;\n    if (!line.second.empty()) {\n      size_t pad = (max_name > line.first.size()) ? (max_name - line.first.size()) : 0;\n      oss << std::string(pad + 2, ' ') << line.second;\n    }\n    oss << \"\\n\";\n  }\n\n  oss << \"\\nNotes:\\n\"\n      << \"  -lh: show long listing with readable sizes\\n\"\n      << \"  path=file:... : list on a local file system\\n\"\n      << \"  path=root:... : list on a plain XRootD server (does not work on native XRootD clusters)\\n\"\n      << \"  path=...      : all other paths are considered to be EOS paths!\\n\";\n  return oss.str();\n}\n\nvoid ConfigureLsApp(CLI::App& app,\n                    bool& opt_l,\n                    bool& opt_y,\n                    bool& opt_a,\n                    bool& opt_i,\n                    bool& opt_c,\n                    bool& opt_n,\n                    bool& opt_F,\n                    bool& opt_s,\n                    bool& opt_no_globbing)\n{\n  app.name(\"ls\");\n  app.description(\"list directory <path>\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App* app, std::string, CLI::AppFormatMode) {\n        return MakeLsHelp(app);\n      }));\n\n  app.add_flag(\"-l\", opt_l, \"show long listing\");\n  app.add_flag(\"-y\", opt_y, \"show long listing with backend(tape) status\");\n  // Note: '-lh' was accepted historically; '-h' alone shows help in legacy\n  app.add_flag(\"-a\", opt_a, \"show hidden files\");\n  app.add_flag(\"-i\", opt_i, \"add inode information\");\n  app.add_flag(\"-c\", opt_c, \"add checksum value (implies -l)\");\n  app.add_flag(\"-n\", opt_n, \"show numerical user/group ids\");\n  app.add_flag(\"-F\", opt_F, \"append indicator '/' to directories\");\n  app.add_flag(\"-s\", opt_s, \"checks only if the directory exists without listing\");\n  app.add_flag(\"-N,--no-globbing\", opt_no_globbing, \"disables globbing\");\n}\n\nclass LsCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"ls\";\n  }\n  const char*\n  description() const override\n  {\n    return \"List a directory\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    // Setup CLI11 parser (ignore unknown options)\n    CLI::App app;\n    app.allow_extras();\n\n    bool opt_l = false;\n    bool opt_y = false;\n    bool opt_a = false;\n    bool opt_i = false;\n    bool opt_c = false;\n    bool opt_n = false;\n    bool opt_F = false;\n    bool opt_s = false;\n    bool opt_no_globbing = false;\n\n    ConfigureLsApp(app, opt_l, opt_y, opt_a, opt_i, opt_c, opt_n, opt_F, opt_s,\n                   opt_no_globbing);\n\n    std::vector<std::string> positionals;\n    app.add_option(\"path\", positionals);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    // Help handling consistent with legacy (-h or --help)\n    if (!args.empty() && (args[0] == \"-h\" || args[0] == \"--help\")) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    // Build mgm.option string\n    std::string option;\n    auto add_flag = [&](char c) {\n      option += '-';\n      option.push_back(c);\n    };\n    if (opt_l)\n      add_flag('l');\n    if (opt_y)\n      add_flag('y');\n    if (opt_a)\n      add_flag('a');\n    if (opt_i)\n      add_flag('i');\n    if (opt_c) {\n      add_flag('c');\n      if (!opt_l)\n        add_flag('l');\n    }\n    if (opt_n)\n      add_flag('n');\n    if (opt_F)\n      add_flag('F');\n    if (opt_s)\n      add_flag('s');\n    if (opt_no_globbing) {\n      option += \"-N\";\n    }\n\n    // Determine path from positionals (join to allow spaces)\n    std::ostringstream pathoss;\n    for (size_t i = 0; i < positionals.size(); ++i) {\n      if (i)\n        pathoss << ' ';\n      pathoss << positionals[i];\n    }\n    std::string path = pathoss.str();\n    if (path.empty())\n      path = gPwd.c_str();\n\n    // Unescape blanks (legacy behaviour: replace \"\\\\ \" -> \" \")\n    {\n      std::string replaced;\n      replaced.reserve(path.size());\n      for (size_t i = 0; i < path.size(); ++i) {\n        if (path[i] == '\\\\' && i + 1 < path.size() && path[i + 1] == ' ') {\n          replaced.push_back(' ');\n          ++i;\n        } else\n          replaced.push_back(path[i]);\n      }\n      path.swap(replaced);\n    }\n\n    XrdOucString xpath = path.c_str();\n\n    // S3 scheme handling (legacy behaviour): as3:\n    if (xpath.beginswith(\"as3:\")) {\n      XrdOucString hostport;\n      XrdOucString protocol;\n      XrdOucString sPath;\n      const char* v = 0;\n      if (!(v = eos::common::StringConversion::ParseUrl(xpath.c_str(), protocol,\n                                                        hostport))) {\n        fprintf(stderr, \"error: illegal url <%s>\\n\", xpath.c_str());\n        global_retc = EINVAL;\n        return 0;\n      }\n      sPath = v;\n      if (hostport.length())\n        setenv(\"S3_HOSTNAME\", hostport.c_str(), 1);\n\n      XrdOucString envString = xpath;\n      int qpos = 0;\n      if ((qpos = envString.find(\"?\")) != STR_NPOS) {\n        envString.erase(0, qpos + 1);\n        XrdOucEnv env(envString.c_str());\n        if (env.Get(\"s3.key\"))\n          setenv(\"S3_SECRET_ACCESS_KEY\", env.Get(\"s3.key\"), 1);\n        if (env.Get(\"s3.id\"))\n          setenv(\"S3_ACCESS_KEY_ID\", env.Get(\"s3.id\"), 1);\n        xpath.erase(xpath.find(\"?\"));\n        sPath.erase(sPath.find(\"?\"));\n      }\n      const char* cstr = getenv(\"S3_ACCESS_KEY\");\n      if (cstr)\n        setenv(\"S3_SECRET_ACCESS_KEY\", cstr, 1);\n      cstr = getenv(\"S3_ACESSS_ID\");\n      if (cstr)\n        setenv(\"S3_ACCESS_KEY_ID\", cstr, 1);\n      if (!getenv(\"S3_ACCESS_KEY_ID\") || !getenv(\"S3_HOSTNAME\") ||\n          !getenv(\"S3_SECRET_ACCESS_KEY\")) {\n        fprintf(stderr, \"error: you have to set the S3 environment variables \"\n                        \"S3_ACCESS_KEY_ID | S3_ACCESS_ID, S3_HOSTNAME (or use \"\n                        \"a URI), S3_SECRET_ACCESS_KEY | S3_ACCESS_KEY\\n\");\n        exit(-1);\n      }\n      XrdOucString s3env;\n      s3env = \"env S3_ACCESS_KEY_ID=\";\n      s3env += getenv(\"S3_ACCESS_KEY_ID\");\n      s3env += \" S3_HOSTNAME=\";\n      s3env += getenv(\"S3_HOSTNAME\");\n      s3env += \" S3_SECRET_ACCESS_KEY=\";\n      s3env += getenv(\"S3_SECRET_ACCESS_KEY\");\n      XrdOucString s3arg = sPath.c_str();\n      XrdOucString listcmd = \"bash -c \\\"\";\n      listcmd += s3env;\n      listcmd += \" s3 list \";\n      listcmd += s3arg;\n      listcmd += \" \";\n      listcmd += \"\\\"\";\n      global_retc = system(listcmd.c_str());\n      return 0;\n    }\n\n    // Local file or plain XRootD path handling\n    if (xpath.beginswith(\"file:\") || xpath.beginswith(\"root:\")) {\n      bool isXrd = xpath.beginswith(\"root:\");\n      XrdOucString protocol;\n      XrdOucString hostport;\n      XrdOucString sPath;\n      const char* v = 0;\n      if (!(v = eos::common::StringConversion::ParseUrl(xpath.c_str(), protocol,\n                                                        hostport))) {\n        global_retc = EINVAL;\n        return 0;\n      }\n      sPath = v;\n      std::string Path = v;\n      if (sPath == \"\" && (protocol == \"file\")) {\n        sPath = getenv(\"PWD\");\n        Path = getenv(\"PWD\");\n        if (!sPath.endswith(\"/\")) {\n          sPath += \"/\";\n          Path += \"/\";\n        }\n      }\n      XrdOucString url = \"\";\n      eos::common::StringConversion::CreateUrl(\n          protocol.c_str(), hostport.c_str(), Path.c_str(), url);\n      DIR* dir =\n          (isXrd) ? XrdPosixXrootd::Opendir(url.c_str()) : opendir(url.c_str());\n      if (dir) {\n        struct dirent* entry;\n        while (\n            (entry = (isXrd) ? XrdPosixXrootd::Readdir(dir) : readdir(dir))) {\n          struct stat buf;\n          XrdOucString curl = \"\";\n          XrdOucString cpath = Path.c_str();\n          cpath += entry->d_name;\n          eos::common::StringConversion::CreateUrl(\n              protocol.c_str(), hostport.c_str(), cpath.c_str(), curl);\n          if (option.find(\"a\") == std::string::npos) {\n            if (entry->d_name[0] == '.')\n              continue;\n          }\n          if (!((isXrd) ? XrdPosixXrootd::Stat(curl.c_str(), &buf)\n                        : stat(curl.c_str(), &buf))) {\n            if (option.find(\"l\") == std::string::npos) {\n              fprintf(stdout, \"%s\\n\", entry->d_name);\n            } else {\n              char t_creat[14];\n              char modestr[11];\n              eos::modeToBuffer(buf.st_mode, modestr);\n              XrdOucString suid = \"\";\n              suid += (int)buf.st_uid;\n              XrdOucString sgid = \"\";\n              sgid += (int)buf.st_gid;\n              XrdOucString sizestring = \"\";\n              struct tm* t_tm;\n              struct tm t_tm_local;\n              t_tm = localtime_r(&buf.st_ctime, &t_tm_local);\n              strftime(t_creat, 13, \"%b %d %H:%M\", t_tm);\n              XrdOucString dirmarker = \"\";\n              if (option.find(\"F\") != std::string::npos)\n                dirmarker = \"/\";\n              if (modestr[0] != 'd')\n                dirmarker = \"\";\n              fprintf(stdout, \"%s %3d %-8.8s %-8.8s %12s %s %s%s\\n\", modestr,\n                      (int)buf.st_nlink, suid.c_str(), sgid.c_str(),\n                      eos::common::StringConversion::GetSizeString(\n                          sizestring, (unsigned long long)buf.st_size),\n                      t_creat, entry->d_name, dirmarker.c_str());\n            }\n          }\n        }\n        (isXrd) ? XrdPosixXrootd::Closedir(dir) : closedir(dir);\n      }\n      global_retc = 0;\n      return 0;\n    }\n\n    // EOS path via MGM\n    XrdOucString ap = abspath(xpath.c_str());\n    if (strlen(ap.c_str()) >= FILENAME_MAX) {\n      fprintf(stderr, \"error: path length longer than %i bytes\", FILENAME_MAX);\n      global_retc = EINVAL;\n      return 0;\n    }\n    XrdOucString esc =\n        eos::common::StringConversion::curl_escaped(ap.c_str()).c_str();\n    XrdOucString in = \"mgm.cmd=ls\";\n    in += \"&mgm.path=\";\n    in += esc;\n    in += \"&eos.encodepath=1\";\n    in += \"&mgm.option=\";\n    in += option.c_str();\n    global_retc = ctx.outputResult(ctx.clientCommand(in, false, nullptr), true);\n    return 0;\n  }\n\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    bool opt_l = false;\n    bool opt_y = false;\n    bool opt_a = false;\n    bool opt_i = false;\n    bool opt_c = false;\n    bool opt_n = false;\n    bool opt_F = false;\n    bool opt_s = false;\n    bool opt_no_globbing = false;\n    ConfigureLsApp(app, opt_l, opt_y, opt_a, opt_i, opt_c, opt_n, opt_F, opt_s,\n                   opt_no_globbing);\n    const std::string help = app.help();\n    fprintf(stderr, \"%s\", help.c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterLsNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<LsCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/ls-compat.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ls-compat.cc\n// Purpose: Provide legacy com_ls symbol delegating to native LsCommand\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <string>\n#include <vector>\n\nint\ncom_ls(char* arg)\n{\n  std::vector<std::string> argv;\n  if (arg && *arg) {\n    std::string s(arg);\n    std::string cur;\n    for (size_t i = 0; i < s.size(); ++i) {\n      if (s[i] == ' ') {\n        if (!cur.empty()) {\n          argv.push_back(cur);\n          cur.clear();\n        }\n      } else {\n        cur.push_back(s[i]);\n      }\n    }\n    if (!cur.empty())\n      argv.push_back(cur);\n  }\n\n  CommandContext ctx;\n  ctx.serverUri = serveruri.c_str();\n  ctx.globalOpts = &gGlobalOpts;\n  ctx.json = json;\n  ctx.silent = silent;\n  ctx.interactive = interactive;\n  ctx.timing = timing;\n  ctx.userRole = user_role.c_str();\n  ctx.groupRole = group_role.c_str();\n  ctx.clientCommand = &client_command;\n  ctx.outputResult = &output_result;\n\n  IConsoleCommand* cmd = CommandRegistry::instance().find(\"ls\");\n  if (!cmd)\n    return -1;\n  return cmd->run(argv, ctx);\n}\n"
  },
  {
    "path": "console/commands/native/map-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: map-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <XrdOuc/XrdOucString.hh>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeMapHelp()\n{\n  return \"Usage: map [OPTIONS] ls|link|unlink ...\\n\\n\"\n         \"'[eos] map ..' provides a namespace mapping interface for \"\n         \"directories in EOS.\\n\\n\"\n         \"Subcommands:\\n\"\n         \"  ls                                    list all defined mappings\\n\"\n         \"  link <source-path> <destination-path> create a symbolic link from \"\n         \"source to destination\\n\"\n         \"  unlink <source-path>                   remove symbolic link from \"\n         \"source\\n\\n\"\n         \"Options:\\n\"\n         \"  -<option>  optional leading option passed to mgm.option\\n\";\n}\n\nvoid ConfigureMapApp(CLI::App& app,\n                     std::string& subcmd,\n                     std::string& arg1,\n                     std::string& arg2)\n{\n  app.name(\"map\");\n  app.description(\"Mapping utilities\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeMapHelp();\n      }));\n  app.add_option(\"subcmd\", subcmd, \"ls|link|unlink\")->required();\n  app.add_option(\"arg1\", arg1, \"source path or first arg\");\n  app.add_option(\"arg2\", arg2, \"destination path (for link)\");\n}\n\nclass MapCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"map\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Mapping utilities\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (args.empty() || wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    std::vector<std::string> parse_args = args;\n    std::string leading_opt;\n    if (!parse_args.empty() && parse_args[0].size() > 1 &&\n        parse_args[0][0] == '-') {\n      leading_opt = parse_args[0].substr(1);\n      parse_args.erase(parse_args.begin());\n    }\n\n    if (parse_args.empty()) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    std::string subcmd;\n    std::string arg1;\n    std::string arg2;\n    ConfigureMapApp(app, subcmd, arg1, arg2);\n\n    std::vector<std::string> cli_args = parse_args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    XrdOucString in = \"mgm.cmd=map\";\n\n    if (!leading_opt.empty()) {\n      in += \"&mgm.option=\";\n      in += leading_opt.c_str();\n    }\n\n    if (subcmd == \"ls\") {\n      in += \"&mgm.subcmd=ls\";\n    } else if (subcmd == \"link\") {\n      if (arg1.empty() || arg2.empty()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      in += \"&mgm.subcmd=link&mgm.map.src=\";\n      in += arg1.c_str();\n      in += \"&mgm.map.dest=\";\n      in += arg2.c_str();\n    } else if (subcmd == \"unlink\") {\n      if (arg1.empty()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      in += \"&mgm.subcmd=unlink&mgm.map.src=\";\n      in += arg1.c_str();\n    } else {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    global_retc = ctx.outputResult(ctx.clientCommand(in, false, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeMapHelp().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterMapNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<MapCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/member-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: member-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <XrdOuc/XrdOucString.hh>\n#include <algorithm>\n#include <iomanip>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeMemberHelp()\n{\n  std::ostringstream oss;\n  oss << \"Usage: member [--update] <egroup>\\n\"\n      << \"   show the (cached) information about egroup membership for the\\n\"\n      << \"   current user running the command. If the check is required for\\n\"\n      << \"   a different user then please use the \\\"eos -r <uid> <gid>\\\"\\n\"\n      << \"   command to switch to a different role.\\n\"\n      << \" Options:\\n\"\n      << \"    --update : Refresh cached egroup information\\n\";\n  return oss.str();\n}\n\nvoid ConfigureMemberApp(CLI::App& app, bool& opt_update, std::string& egroup)\n{\n  app.name(\"member\");\n  app.description(\"Member management\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeMemberHelp();\n      }));\n  app.add_flag(\"--update\", opt_update, \"refresh cached egroup information\");\n  app.add_option(\"egroup\", egroup, \"egroup name\")->required();\n}\n\nclass MemberCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"member\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Member management\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      if (args[i].find(' ') != std::string::npos)\n        oss << std::quoted(args[i]);\n      else\n        oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (args.empty() || wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    bool opt_update = false;\n    std::string egroup;\n    ConfigureMemberApp(app, opt_update, egroup);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    XrdOucString in = \"mgm.cmd=member\";\n    in += \"&mgm.egroup=\";\n    in += egroup.c_str();\n    if (opt_update) {\n      in += \"&mgm.egroupupdate=true\";\n    }\n    global_retc = ctx.outputResult(ctx.clientCommand(in, true, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeMemberHelp().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterMemberNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<MemberCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/mkdir-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: mkdir-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/Path.hh\"\n#include \"common/StringConversion.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeMkdirHelp()\n{\n  return \"Usage: mkdir [-p] <path>\\n\\n\"\n         \"Create directory <path>. With -p, create parent directories as needed.\\n\\n\"\n         \"Options:\\n\"\n         \"  -p  create parent directories as needed\\n\";\n}\n\nvoid ConfigureMkdirApp(CLI::App& app, bool& opt_p, std::string& path)\n{\n  app.name(\"mkdir\");\n  app.description(\"Create a directory\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeMkdirHelp();\n      }));\n  app.add_flag(\"-p\", opt_p, \"create parent directories as needed\");\n  app.add_option(\"path\", path, \"directory path\")->required();\n}\n\nclass MkdirCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"mkdir\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Create a directory\";\n  }\n  bool\n  requiresMgm(const std::string&) const override\n  {\n    return true;\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (args.empty() || wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    bool opt_p = false;\n    std::string path;\n    ConfigureMkdirApp(app, opt_p, path);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    XrdOucString in = \"mgm.cmd=mkdir\";\n    if (opt_p)\n      in += \"&mgm.option=p\";\n    XrdOucString ap = abspath(path.c_str());\n    XrdOucString esc =\n        eos::common::StringConversion::curl_escaped(ap.c_str()).c_str();\n    in += \"&mgm.path=\";\n    in += esc;\n    in += \"&eos.encodepath=1\";\n    global_retc = ctx.outputResult(ctx.clientCommand(in, false, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeMkdirHelp().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterMkdirNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<MkdirCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/motd-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: motd-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <sstream>\n\nnamespace {\nstd::string MakeMotdHelp()\n{\n  return \"Usage: motd\\n\\n\"\n         \"Display the message of the day.\\n\";\n}\n\nclass MotdCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"motd\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Message of the day\";\n  }\n  bool\n  requiresMgm(const std::string&) const override\n  {\n    return false;\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    if (!args.empty()) {\n      std::ostringstream oss;\n      for (size_t i = 0; i < args.size(); ++i) {\n        if (i)\n          oss << ' ';\n        oss << args[i];\n      }\n      if (wants_help(oss.str().c_str())) {\n        printHelp();\n        return 0;\n      }\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    XrdOucString in = \"mgm.cmd=motd\";\n    global_retc = ctx.outputResult(ctx.clientCommand(in, false, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeMotdHelp().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterMotdNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<MotdCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/mv-alias.cc",
    "content": "// ----------------------------------------------------------------------\n// File: mv-alias.cc\n// Purpose: Provide 'mv' alias to 'file rename'\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nclass MvAliasCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"mv\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Alias for 'file rename'\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    if (wants_help(args.empty() ? \"\" : args[0].c_str()) || args.size() < 2) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    IConsoleCommand* fileCmd = CommandRegistry::instance().find(\"file\");\n    if (!fileCmd) {\n      fprintf(stderr, \"error: 'file' command not available\\n\");\n      global_retc = EINVAL;\n      return 0;\n    }\n    std::vector<std::string> forwarded;\n    forwarded.reserve(args.size() + 1);\n    forwarded.emplace_back(\"rename\");\n    forwarded.insert(forwarded.end(), args.begin(), args.end());\n    return fileCmd->run(forwarded, ctx);\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"Usage: mv <src> <dst>\\n\");\n  }\n};\n} // namespace\n\n// Keep legacy registration symbol name expected by CommandFramework\nvoid\nRegisterMvNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<MvAliasCommand>());\n}\n\n// Backward-compatible alias (if ever referenced elsewhere)\nvoid\nRegisterMvAliasCommand()\n{\n  RegisterMvNativeCommand();\n}\n"
  },
  {
    "path": "console/commands/native/node-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: node-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include \"console/commands/helpers/NodeHelper.hh\"\n#include <iomanip>\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeNodeHelp()\n{\n  std::ostringstream oss;\n  oss << \" usage:\\n\"\n      << \"node ls [-s] [-b|--brief] [-m|-l|--sys|--io|--fsck] [<node>] : \"\n         \"list all nodes or only <node>. <node> is a substring match and can \"\n         \"be a comma seperated list\\n\"\n      << \"\\t      -s : silent mode\\n\"\n      << \"\\t      -b : display host names without domain names\\n\"\n      << \"\\t      -m : monitoring key=value output format\\n\"\n      << \"\\t      -l : long output - list also file systems after each node\\n\"\n      << \"\\t    --io : print IO statistics\\n\"\n      << \"\\t   --sys : print SYS statistics (memory + threads)\\n\"\n      << \"\\t  --fsck : print filesystem check statistcis\\n\"\n      << std::endl\n      << \"node config <host:port> <key>=<value> : configure file system \"\n         \"parameters for each filesystem of this node\\n\"\n      << \"\\t    <key> : \"\n         \"error.simulation=io_read|io_write|xs_read|xs_write|fmd_open|\"\n         \"fake_write|close|unresponsive\\n\"\n      << \"\\t            If offset is given then the error will get triggered \"\n         \"for requests past the given value.\\n\"\n      << \"\\t            Accepted format for offset: 8B, 10M, 20G etc.\\n\"\n      << \"\\t            fmd_open            : simulate a file metadata \"\n         \"mismatch when opening a file\\n\"\n      << \"\\t            open_delay[_<sec>]  : add by default 120 sec delay \"\n         \"per open operation\\n\"\n      << \"\\t            read_delay[_<sec>]  : add by default 10 sec delay per \"\n         \"read operation\\n\"\n      << \"\\t            io_read[_<offset>]  : simulate read errors\\n\"\n      << \"\\t            io_write[_<offset>] : simulate write errors\\n\"\n      << \"\\t            xs_read             : simulate checksum errors when \"\n         \"reading a file\\n\"\n      << \"\\t            xs_write[_<sec>]    : simulate checksum errors on \"\n         \"write with an optional delay, default 0\\n\"\n      << \"\\t            fake_write          : do not really write data to \"\n         \"disk\\n\"\n      << \"\\t            close               : return an error on close\\n\"\n      << \"\\t            close_commit_mgm    : simulate error during close \"\n         \"commit to MGM\\n\"\n      << \"\\t            unresponsive        : emulate a write/close request \"\n         \"taking 2 minutes\\n\"\n      << \"\\t            <none>              : disable error simulation (any \"\n         \"value other than the previous ones is fine!)\\n\"\n      << \"\\t    <key> : publish.interval=<sec> - set the filesystem state \"\n         \"publication interval to <sec> seconds\\n\"\n      << \"\\t    <key> : debug.level=<level>    - set the node into debug \"\n         \"level <level> [default=notice] -> see debug --help for available \"\n         \"levels\\n\"\n      << \"\\t    <key> : stripexs=on|off        - enable/disable synchronously \"\n         \"stripe checksum computation\\n\"\n      << \"\\t    <key> : for other keys see help of 'fs config' for details\\n\"\n      << std::endl\n      << \"node set <queue-name>|<host:port> on|off                 : \"\n         \"activate/deactivate node\\n\"\n      << std::endl\n      << \"node rm  <queue-name>|<host:port>                        : remove a \"\n         \"node\\n\"\n      << std::endl\n      << \"node txgw <queue-name>|<host:port> <on|off> : enable (on) or \"\n         \"disable (off) node as a transfer gateway\\n\"\n      << std::endl\n      << \"node proxygroupadd <group-name> <queue-name>|<host:port> : add a \"\n         \"node to a proxy group\\n\"\n      << std::endl\n      << \"node proxygrouprm <group-name> <queue-name>|<host:port> : rm a node \"\n         \"from a proxy group\\n\"\n      << std::endl\n      << \"node proxygroupclear <queue-name>|<host:port> : clear the list of \"\n         \"groups a node belongs to\\n\"\n      << std::endl\n      << \"node status <queue-name>|<host:port> : print's all defined \"\n         \"variables for a node\\n\"\n      << std::endl;\n  return oss.str();\n}\n\nvoid ConfigureNodeApp(CLI::App& app)\n{\n  app.name(\"node\");\n  app.description(\"Node configuration\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeNodeHelp();\n      }));\n}\n\nclass NodeProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"node\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Node configuration\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      if (args[i].find(' ') != std::string::npos)\n        oss << std::quoted(args[i]);\n      else\n        oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    NodeHelper helper(*ctx.globalOpts);\n    if (!helper.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = helper.Execute();\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureNodeApp(app);\n    fprintf(stderr, \"%s\", app.help().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterNodeProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<NodeProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/ns-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ns-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/ParseUtils.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/ICmdHelper.hh\"\n#include <memory>\n#include <sstream>\n\n// Native helper implementing the protobuf request (ported from legacy)\nclass NsNativeHelper : public ICmdHelper {\npublic:\n  NsNativeHelper(const GlobalOptions& opts) : ICmdHelper(opts)\n  {\n    mIsAdmin = true;\n  }\n  ~NsNativeHelper() override = default;\n  bool ParseCommand(const char* arg);\n};\n\n// Parse command line input (ported logic)\nbool\nNsNativeHelper::ParseCommand(const char* arg)\n{\n  const char* option;\n  std::string soption;\n  eos::console::NsProto* ns = mReq.mutable_ns();\n  eos::common::StringTokenizer tokenizer(arg);\n  tokenizer.GetLine();\n  option = tokenizer.GetToken();\n  std::string cmd = (option ? option : \"\");\n\n  if (cmd == \"stat\") {\n    eos::console::NsProto_StatProto* stat = ns->mutable_stat();\n    if (!(option = tokenizer.GetToken())) {\n      stat->set_monitor(false);\n    } else {\n      while (true) {\n        soption = option;\n        if (soption == \"-a\") {\n          stat->set_groupids(true);\n        } else if (soption == \"-x\") {\n          stat->set_apps(true);\n        } else if (soption == \"-m\") {\n          stat->set_monitor(true);\n        } else if (soption == \"-n\") {\n          stat->set_numericids(true);\n        } else if (soption == \"--reset\") {\n          stat->set_reset(true);\n        } else {\n          return false;\n        }\n        if (!(option = tokenizer.GetToken())) {\n          break;\n        }\n      }\n    }\n  } else if (cmd == \"mutex\") {\n    using eos::console::NsProto_MutexProto;\n    NsProto_MutexProto* mutex = ns->mutable_mutex();\n    if (!(option = tokenizer.GetToken())) {\n      mutex->set_list(true);\n    } else {\n      while (true) {\n        soption = option;\n        if (soption == \"--toggletime\") {\n          mutex->set_toggle_timing(true);\n        } else if (soption == \"--toggleorder\") {\n          mutex->set_toggle_order(true);\n        } else if (soption == \"--toggledeadlock\") {\n          mutex->set_toggle_deadlock(true);\n        } else if (soption == \"--smplrate1\") {\n          mutex->set_sample_rate1(true);\n        } else if (soption == \"--smplrate10\") {\n          mutex->set_sample_rate10(true);\n        } else if (soption == \"--smplrate100\") {\n          mutex->set_sample_rate100(true);\n        } else if (soption == \"--setblockedtime\") {\n          option = tokenizer.GetToken();\n          if (option) {\n            mutex->set_blockedtime(std::stoul(option));\n          } else {\n            return false;\n          }\n        } else {\n          return false;\n        }\n        if (!(option = tokenizer.GetToken())) {\n          break;\n        }\n      }\n    }\n  } else if (cmd == \"compact\") {\n    using eos::console::NsProto_CompactProto;\n    NsProto_CompactProto* compact = ns->mutable_compact();\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n    soption = option;\n    if (soption == \"off\") {\n      compact->set_on(false);\n    } else if (soption == \"on\") {\n      compact->set_on(true);\n      if ((option = tokenizer.GetToken())) {\n        soption = option;\n        int64_t delay = 0;\n        try {\n          delay = std::stol(soption);\n        } catch (...) {\n          return false;\n        }\n        compact->set_delay(delay);\n        if ((option = tokenizer.GetToken())) {\n          soption = option;\n          int64_t interval = 0;\n          try {\n            interval = std::stol(soption);\n          } catch (...) {\n            return false;\n          }\n          compact->set_interval(interval);\n          if ((option = tokenizer.GetToken())) {\n            soption = option;\n            if (soption == \"files\")\n              compact->set_type(NsProto_CompactProto::FILES);\n            else if (soption == \"directories\")\n              compact->set_type(NsProto_CompactProto::DIRS);\n            else if (soption == \"all\")\n              compact->set_type(NsProto_CompactProto::ALL);\n            else if (soption == \"files-repair\")\n              compact->set_type(NsProto_CompactProto::FILES_REPAIR);\n            else if (soption == \"directories-repair\")\n              compact->set_type(NsProto_CompactProto::DIRS_REPAIR);\n            else if (soption == \"all-repair\")\n              compact->set_type(NsProto_CompactProto::ALL_REPAIR);\n            else {\n              return false;\n            }\n          }\n        }\n      }\n    } else {\n      return false;\n    }\n  } else if (cmd == \"master\") {\n    using eos::console::NsProto_MasterProto;\n    NsProto_MasterProto* master = ns->mutable_master();\n    if (!(option = tokenizer.GetToken())) {\n      master->set_op(NsProto_MasterProto::LOG);\n    } else {\n      soption = option;\n      if (soption == \"--log\") {\n        master->set_op(NsProto_MasterProto::LOG);\n      } else if (soption == \"--log-clear\") {\n        master->set_op(NsProto_MasterProto::LOG_CLEAR);\n      } else if (soption == \"--enable\") {\n        master->set_op(NsProto_MasterProto::ENABLE);\n      } else if (soption == \"--disable\") {\n        master->set_op(NsProto_MasterProto::DISABLE);\n      } else {\n        master->set_host(soption);\n      }\n    }\n  } else if (cmd == \"recompute_tree_size\") {\n    using eos::console::NsProto_TreeSizeProto;\n    NsProto_TreeSizeProto* tree = ns->mutable_tree();\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n    while (true) {\n      int pos = 0;\n      soption = option;\n      if (soption == \"--depth\") {\n        if (!(option = tokenizer.GetToken())) {\n          return false;\n        }\n        soption = option;\n        try {\n          tree->set_depth(std::stoul(soption));\n        } catch (...) {\n          return false;\n        }\n      } else if ((soption.find(\"cid:\") == 0)) {\n        pos = soption.find(':') + 1;\n        tree->mutable_container()->set_cid(soption.substr(pos));\n      } else if (soption.find(\"cxid:\") == 0) {\n        pos = soption.find(':') + 1;\n        tree->mutable_container()->set_cxid(soption.substr(pos));\n      } else {\n        tree->mutable_container()->set_path(soption);\n      }\n      if (!(option = tokenizer.GetToken())) {\n        break;\n      }\n    }\n  } else if (cmd == \"recompute_quotanode\") {\n    using eos::console::NsProto_QuotaSizeProto;\n    NsProto_QuotaSizeProto* quota = ns->mutable_quota();\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n    while (true) {\n      int pos = 0;\n      soption = option;\n      if ((soption.find(\"cid:\") == 0)) {\n        pos = soption.find(':') + 1;\n        quota->mutable_container()->set_cid(soption.substr(pos));\n      } else if (soption.find(\"cxid:\") == 0) {\n        pos = soption.find(':') + 1;\n        quota->mutable_container()->set_cxid(soption.substr(pos));\n      } else {\n        quota->mutable_container()->set_path(soption);\n      }\n      if (!(option = tokenizer.GetToken())) {\n        break;\n      }\n    }\n  } else if (cmd == \"update_quotanode\") {\n    using eos::console::NsProto_QuotaSizeProto;\n    NsProto_QuotaSizeProto* quota = ns->mutable_quota();\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n    int npar = 0;\n    while (true) {\n      int pos = 0;\n      soption = option;\n      if ((soption.find(\"cid:\") == 0)) {\n        pos = soption.find(':') + 1;\n        quota->mutable_container()->set_cid(soption.substr(pos));\n      } else if (soption.find(\"cxid:\") == 0) {\n        pos = soption.find(':') + 1;\n        quota->mutable_container()->set_cxid(soption.substr(pos));\n      } else if (soption.find(\"uid:\") == 0) {\n        pos = soption.find(':') + 1;\n        quota->set_uid(soption.substr(pos));\n      } else if (soption.find(\"gid:\") == 0) {\n        pos = soption.find(':') + 1;\n        quota->set_gid(soption.substr(pos));\n      } else if (soption.find(\"bytes:\") == 0) {\n        pos = soption.find(':') + 1;\n        quota->set_used_bytes(strtoul(soption.substr(pos).c_str(), 0, 10));\n        npar++;\n      } else if (soption.find(\"physicalbytes:\") == 0) {\n        pos = soption.find(':') + 1;\n        quota->set_physical_bytes(strtoul(soption.substr(pos).c_str(), 0, 10));\n        npar++;\n      } else if (soption.find(\"inodes:\") == 0) {\n        pos = soption.find(':') + 1;\n        quota->set_used_inodes(strtoul(soption.substr(pos).c_str(), 0, 10));\n        npar++;\n      } else {\n        quota->mutable_container()->set_path(soption);\n      }\n      if (!(option = tokenizer.GetToken())) {\n        break;\n      }\n    }\n    if (npar && (npar != 3)) {\n      return false;\n    }\n  } else if (cmd == \"cache\") {\n    eos::console::NsProto_CacheProto* cache = ns->mutable_cache();\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n    soption = option;\n    if (soption == \"set\") {\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n      soption = option;\n      if (soption == \"-f\") {\n        cache->set_op(eos::console::NsProto_CacheProto::SET_FILE);\n      } else if (soption == \"-d\") {\n        cache->set_op(eos::console::NsProto_CacheProto::SET_DIR);\n      } else {\n        return false;\n      }\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n      uint64_t max_num = 0ull, max_size = 0ull;\n      try {\n        max_num = std::stoull(option);\n      } catch (...) {\n        return false;\n      }\n      if ((option = tokenizer.GetToken())) {\n        try {\n          max_size =\n              eos::common::StringConversion::GetDataSizeFromString(option);\n        } catch (...) {\n          return false;\n        }\n      }\n      cache->set_max_num(max_num);\n      cache->set_max_size(max_size);\n    } else if (soption == \"drop\") {\n      if (!(option = tokenizer.GetToken())) {\n        cache->set_op(eos::console::NsProto_CacheProto::DROP_ALL);\n      } else {\n        soption = option;\n        if (soption == \"-f\") {\n          cache->set_op(eos::console::NsProto_CacheProto::DROP_FILE);\n        } else if (soption == \"-d\") {\n          cache->set_op(eos::console::NsProto_CacheProto::DROP_DIR);\n        } else {\n          return false;\n        }\n      }\n    } else if (soption == \"drop-single-file\") {\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n      uint64_t target;\n      try {\n        target = std::stoull(option);\n      } catch (...) {\n        return false;\n      }\n      cache->set_op(eos::console::NsProto_CacheProto::DROP_SINGLE_FILE);\n      cache->set_single_to_drop(target);\n    } else if (soption == \"drop-single-container\") {\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n      uint64_t target;\n      try {\n        target = std::stoull(option);\n      } catch (...) {\n        return false;\n      }\n      cache->set_op(eos::console::NsProto_CacheProto::DROP_SINGLE_CONTAINER);\n      cache->set_single_to_drop(target);\n    } else {\n      return false;\n    }\n  } else if (cmd == \"drain\") {\n    using eos::console::NsProto_DrainProto;\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n    NsProto_DrainProto* drain = ns->mutable_drain();\n    soption = option;\n    if (soption == \"list\") {\n      drain->set_op(eos::console::NsProto_DrainProto::LIST);\n    } else if (soption == \"set\") {\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n      soption = option;\n      size_t pos = soption.find(\"=\");\n      if ((pos == std::string::npos) || (pos == soption.length() - 1)) {\n        return false;\n      }\n      drain->set_op(eos::console::NsProto_DrainProto::SET);\n      drain->set_key(soption.substr(0, pos));\n      drain->set_value(soption.substr(pos + 1));\n    } else {\n      return false;\n    }\n  } else if (cmd == \"reserve-ids\") {\n    using eos::console::NsProto_ReserveIdsProto;\n    NsProto_ReserveIdsProto* reserve = ns->mutable_reserve();\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n    int64_t fileID = 0;\n    if (!eos::common::ParseInt64(option, fileID) || fileID < 0) {\n      return false;\n    }\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n    int64_t containerID = 0;\n    if (!eos::common::ParseInt64(option, containerID) || containerID < 0) {\n      return false;\n    }\n    reserve->set_fileid(fileID);\n    reserve->set_containerid(containerID);\n  } else if (cmd == \"benchmark\") {\n    using eos::console::NsProto_BenchmarkProto;\n    NsProto_BenchmarkProto* benchmark = ns->mutable_benchmark();\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n    int64_t n_threads = 0;\n    int64_t n_subdirs = 0;\n    int64_t n_subfiles = 0;\n    if (!eos::common::ParseInt64(option, n_threads) || n_threads < 0) {\n      return false;\n    }\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n    if (!eos::common::ParseInt64(option, n_subdirs) || n_subdirs < 0) {\n      return false;\n    }\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n    if (!eos::common::ParseInt64(option, n_subfiles) || n_subfiles < 0) {\n      return false;\n    }\n    if ((option = tokenizer.GetToken())) {\n      benchmark->set_prefix(option);\n    }\n    benchmark->set_threads(n_threads);\n    benchmark->set_subdirs(n_subdirs);\n    benchmark->set_subfiles(n_subfiles);\n  } else if (cmd == \"tracker\") {\n    eos::console::NsProto_TrackerProto* tracker = ns->mutable_tracker();\n    tracker->set_op(eos::console::NsProto_TrackerProto::NONE);\n    while ((option = tokenizer.GetToken())) {\n      soption = option;\n      if (soption == \"list\") {\n        if (tracker->op() != eos::console::NsProto_TrackerProto::NONE) {\n          std::cerr << \"error: only one operation per command\" << std::endl;\n          return false;\n        } else {\n          tracker->set_op(eos::console::NsProto_TrackerProto::LIST);\n        }\n      } else if (soption == \"clear\") {\n        if (tracker->op() != eos::console::NsProto_TrackerProto::NONE) {\n          std::cerr << \"error: only one operation per command\" << std::endl;\n          return false;\n        } else {\n          tracker->set_op(eos::console::NsProto_TrackerProto::CLEAR);\n        }\n      } else if (soption == \"--name\") {\n        if (!(option = tokenizer.GetToken())) {\n          return false;\n        }\n        tracker->set_name(option);\n      } else {\n        return false;\n      }\n    }\n    if (tracker->op() == eos::console::NsProto_TrackerProto::NONE) {\n      std::cerr << \"error: no operation specified\" << std::endl;\n      return false;\n    }\n  } else if (cmd == \"behaviour\") {\n    eos::console::NsProto_BehaviourProto* behaviour = ns->mutable_behaviour();\n    behaviour->set_op(eos::console::NsProto_BehaviourProto::NONE);\n    if (!(option = tokenizer.GetToken())) {\n      return false;\n    }\n    soption = option;\n    if (soption == \"list\") {\n      behaviour->set_op(eos::console::NsProto_BehaviourProto::LIST);\n    } else if (soption == \"set\") {\n      behaviour->set_op(eos::console::NsProto_BehaviourProto::SET);\n      while ((option = tokenizer.GetToken())) {\n        soption = option;\n        if (behaviour->name().empty()) {\n          if (soption == \"all\") {\n            std::cerr << \"error: \\\"all\\\" is a reserved keyword\" << std::endl;\n            return false;\n          }\n          behaviour->set_name(soption);\n        } else {\n          behaviour->set_value(soption);\n          break;\n        }\n      }\n      if (behaviour->name().empty() || behaviour->value().empty()) {\n        return false;\n      }\n    } else if (soption == \"get\") {\n      behaviour->set_op(eos::console::NsProto_BehaviourProto::GET);\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n      soption = option;\n      behaviour->set_name(soption);\n    } else if (soption == \"clear\") {\n      behaviour->set_op(eos::console::NsProto_BehaviourProto::CLEAR);\n      if (!(option = tokenizer.GetToken())) {\n        return false;\n      }\n      soption = option;\n      behaviour->set_name(soption);\n    } else {\n      std::cerr << \"error: unknown behaviour subcommand\" << std::endl;\n      return false;\n    }\n  } else if (cmd == \"\") {\n    eos::console::NsProto_StatProto* stat = ns->mutable_stat();\n    stat->set_summary(true);\n  } else {\n    return false;\n  }\n  return true;\n}\n\nnamespace {\nstd::string MakeNsHelp()\n{\n  std::ostringstream oss;\n  oss << \"Usage: ns [stat|mutex|compact|master|recompute_tree_size|\"\n         \"recompute_quotanode|update_quotanode|cache|drain|reserve-ids|\"\n         \"benchmark|tracker|behaviour]\" << std::endl\n      << \"    print or configure basic namespace parameters\" << std::endl\n      << \"  ns stat [-a] [-x] [-m] [-n] [--reset]\" << std::endl\n      << \"    print namespace statistics\" << std::endl\n      << \"    -a      : break down by uid/gid\" << std::endl\n      << \"    -x      : break down by application\" << std::endl\n      << \"    -m      : display in monitoring format <key>=<value>\"\n      << std::endl\n      << \"    -n      : display numerical uid/gid(s)\" << std::endl\n      << \"    --reset : reset namespace counters\" << std::endl\n      << std::endl\n      << \"  ns mutex [<option>]\" << std::endl\n      << \"    manage mutex monitoring. Option can be:\" << std::endl\n      << \"    --toggletime     : toggle the timing\" << std::endl\n      << \"    --toggleorder    : toggle the order\" << std::endl\n      << \"    --toggledeadlock : toggle deadlock check\" << std::endl\n      << \"    --smplrate1      : set timing sample rate at 1% (default, no \"\n         \"slow-down)\" << std::endl\n      << \"    --smplrate10     : set timing sample rate at 10% (medium \"\n         \"slow-down)\" << std::endl\n      << \"    --smplrate100    : set timing sample rate at 100% (severe \"\n         \"slow-down)\" << std::endl\n      << \"    --setblockedtime <ms>\" << std::endl\n      << \"                     : set minimum time when a mutex lock lasting \"\n         \"longer than <ms> \\n\"\n      << \"                       is reported in the log file [default=10000]\\n\"\n      << std::endl\n      << \"  ns compact off|on <delay> [<interval>] [<type>]\\n\"\n      << \"    enable online compaction after <delay> seconds\\n\"\n      << \"    <interval> : if >0 then compaction is repeated automatically \\n\"\n      << \"                 after so many seconds\\n\"\n      << \"    <type>     : can be 'files', 'directories' or 'all'. By default  \"\n         \"only the file\\n\"\n      << \"                 changelog is compacted. The repair flag can be \"\n         \"indicated by using:\\n\"\n      << \"                 'files-repair', 'directories-repair' or \"\n         \"'all-repair'\\n\"\n      << std::endl\n      << \"  ns master [<option>]\" << std::endl\n      << \"    master/slave operations. Option can be:\" << std::endl\n      << \"    <master_hostname> : set hostname of MGM master RW daemon\"\n      << std::endl\n      << \"    --log             : show master log\" << std::endl\n      << \"    --log-clear       : clean master log\" << std::endl\n      << \"    --enable          : enable the slave/master supervisor thread \"\n         \"modifying stall/\" << std::endl\n      << \"                        redirectorion rules\" << std::endl\n      << \"    --disable         : disable supervisor thread\" << std::endl\n      << std::endl\n      << \"  ns recompute_tree_size \"\n         \"<path>|cid:<decimal_id>|cxid:<hex_id> [--depth <val>]\" << std::endl\n      << \"    recompute the tree size of a directory and all its subdirectories\"\n      << std::endl\n      << \"    --depth : maximum depth for recomputation, default 0 i.e no \"\n         \"limit\" << std::endl\n      << std::endl\n      << \"  ns recompute_quotanode <path>|cid:<decimal_id>|cxid:<hex_id>\"\n      << std::endl\n      << \"    recompute the specified quotanode\" << std::endl\n      << std::endl\n      << \"  ns update_quotanode \"\n         \"<path>|cid:<decimal_id>|cxid:<hex_id> \"\n         \"uid:<uid>|gid:<gid> bytes:<bytes> physicalbytes:<bytes> \"\n         \"inodes:<inodes>\\n\"\n      << \"    update quota node with the specified (and unchecked) values\\n\"\n      << \"    Note: for project quotas the uid values needs to be specified\\n\"\n      << \"    since the accounting is done by accumulating the individual\\n\"\n      << \"    quotas of the users registered with the quota node.\\n\"\n      << std::endl\n      << \"  ns cache set|drop [-d|-f] [<max_num>] [<max_size>K|M|G...]\"\n      << std::endl\n      << \"    set the max number of entries or the max size of the cache. Use \"\n         \"the\" << std::endl\n      << \"    ns stat command to see the current values.\" << std::endl\n      << \"    set        : update cache size for files or directories\"\n      << std::endl\n      << \"    drop       : drop cached file and/or directory entries\"\n      << std::endl\n      << \"    -d         : control the directory cache\" << std::endl\n      << \"    -f         : control the file cache\" << std::endl\n      << \"    <max_num>  : max number of entries\" << std::endl\n      << \"    <max_size> : max size of the cache - not implemented yet\"\n      << std::endl\n      << std::endl\n      << \"  ns cache drop-single-file <id of file to drop>\\n\"\n      << \"    force refresh of the given FileMD by dropping it from the \"\n         \"cache\\n\"\n      << std::endl\n      << \"  ns cache drop-single-container <id of container to drop>\\n\"\n      << \"    force refresh of the given ContainerMD by dropping it from the \"\n         \"cache\\n\"\n      << std::endl\n      << \"  ns drain list|set [<key>=<value>]                                 \\n\"\n      << \"    list : list the global drain configuration parameters           \\n\"\n      << \"    set  : set one of the following drain configuration parameters  \\n\"\n      << \"           max-thread-pool-size : max number of threads in drain pool\\n\"\n      << \"                                  [default 100, minimum 5]          \\n\"\n      << \"           max-fs-per-node      : max number of file systems per node that\\n\"\n      << \"                                  can be drained in parallel [default 5]\\n\"\n      << std::endl\n      << \"  ns reserve-ids <file id> <container id>\\n\"\n      << \"    blacklist file and container IDs below the given threshold. The namespace\\n\"\n      << \"    will not allocate any file or container with IDs less than, or equal to the\\n\"\n      << \"    given blacklist thresholds.\\n\"\n      << std::endl\n      << \"  ns benchmark <n-threads> <n-subdirs> <n-subfiles> [prefix=/benchmark]\\n\"\n      << \"     run metadata benchmark inside the MGM - results are printed into the MGM logfile and the shell\\n\"\n      << \"                n-threads  : number of parallel threads running a benchmark in the MGM\\n\"\n      << \"                n-subdirs  : directories created by each threads\\n\"\n      << \"                n-subfiles : number of files created in each sub-directory\\n\"\n      << \"                prefix     : absolute directory where to write the benchmarkf iles - default is /benchmark\\n\"\n      << std::endl\n      << \"     example: eos ns benchmark 100 10 10\\n\"\n      << std::endl\n      << \" ns tracker list|clear --name tracker_type\\n\"\n      << \"     list or clear the different file identifier trackers\\n\"\n      << \"     tracker_type : one of the following: drain, balance, fsck, convert, all\\n\"\n      << std::endl\n      << \" ns behaviour list|set|clear\\n\"\n      << \"     modify the behaviour of internal mechanisms for the manager node\\n\"\n      << \"     list                    : list all the behaviour changes enforced\\n\"\n      << \"     set <behaviour> <value> : enforce given behavior\\n\"\n      << \"     get <behaviour>         : get behaviour configuration\\n\"\n      << \"     clear <behaviour>|all   : remove enforced behavior\\n\"\n      << std::endl\n      << \"     The following behaviours are supported:\\n\"\n      << \"       rain_min_fsid_entry : for RAIN files the entry server will deterministically\\n\"\n      << \"         be the file system with the lowest fsid from the list of stripes\\n\"\n      << \"         Accepted values: \\\"on\\\" or \\\"off\\\" [default off]\\n\"\n      << std::endl;\n  return oss.str();\n}\n\nvoid ConfigureNsApp(CLI::App& app)\n{\n  app.name(\"ns\");\n  app.description(\"Namespace Interface\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeNsHelp();\n      }));\n}\n\nclass NsProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"ns\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Namespace Interface\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    NsNativeHelper helper(*ctx.globalOpts);\n    if (!helper.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = helper.Execute();\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureNsApp(app);\n    fprintf(stderr, \"%s\", app.help().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterNsProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<NsProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/oldfind-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: oldfind-cmd-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringConversion.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <dirent.h>\n#include <errno.h>\n#include <iostream>\n#include <memory>\n#include <sstream>\n#include <string>\n#include <vector>\n#include <sys/stat.h>\n\nstatic void\nEnsureRegistryInitialized()\n{\n  static bool registryInitialized = false;\n  if (!registryInitialized) {\n    RegisterNativeConsoleCommands();\n    registryInitialized = true;\n  }\n}\n\nstatic std::vector<std::string>\nTokenizeArgs(const std::string& line)\n{\n  eos::common::StringTokenizer tokenizer(line.c_str());\n  tokenizer.GetLine();\n  std::vector<std::string> args;\n  std::string token;\n  while (tokenizer.NextToken(token)) {\n    args.push_back(token);\n  }\n  return args;\n}\n\nstatic int\nRunRegisteredCommand(const std::string& cmdName,\n                     const std::vector<std::string>& argsVec)\n{\n  EnsureRegistryInitialized();\n  IConsoleCommand* icmd = CommandRegistry::instance().find(cmdName);\n  if (!icmd) {\n    fprintf(stderr, \"error: '%s' command not available\\n\", cmdName.c_str());\n    return EINVAL;\n  }\n\n  std::string rest;\n  for (size_t i = 0; i < argsVec.size(); ++i) {\n    if (i) {\n      rest.push_back(' ');\n    }\n    rest += argsVec[i];\n  }\n\n  if (icmd->requiresMgm(rest) && !CheckMgmOnline(serveruri.c_str())) {\n    std::cerr << \"error: MGM \" << serveruri.c_str()\n              << \" not online/reachable\" << std::endl;\n#ifdef __LINUX__\n    return ENONET;\n#else\n    return ENOTCONN;\n#endif\n  }\n\n  CommandContext ctx;\n  ctx.serverUri = serveruri.c_str();\n  ctx.globalOpts = &gGlobalOpts;\n  ctx.json = json;\n  ctx.silent = silent;\n  ctx.interactive = interactive;\n  ctx.timing = timing;\n  ctx.userRole = user_role.c_str();\n  ctx.groupRole = group_role.c_str();\n  ctx.clientCommand = &client_command;\n  ctx.outputResult = &output_result;\n\n  return icmd->run(argsVec, ctx);\n}\n\nstatic std::string MakeOldfindHelp()\n{\n  return \"Usage: find [-name <pattern>] [--xurl] [--childcount] [--purge <n>] \"\n         \"[--count] [-s] [-d] [-f] [-0] [-1] [-ctime +<n>|-<n>] [-m] \"\n         \"[-x <key>=<val>] [-p <key>] [-b] [-c %tags] [-layoutstripes <n>] <path>\\n\\n\"\n         \"Find files/directories (old implementation).\\n\";\n}\n\nstatic int\nnative_com_old_find(char* arg1)\n{\n  XrdPosixXrootd Xroot;\n  XrdOucString oarg = arg1;\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString s1;\n  XrdOucString path;\n  XrdOucString option = \"\";\n  XrdOucString attribute = \"\";\n  XrdOucString maxdepth = \"\";\n  XrdOucString olderthan = \"\";\n  XrdOucString youngerthan = \"\";\n  XrdOucString printkey = \"\";\n  XrdOucString filter = \"\";\n  XrdOucString stripes = \"\";\n  XrdOucString versions = \"\";\n  XrdOucString filematch = \"\";\n  XrdOucString in = \"mgm.cmd=find&\";\n  bool valid = false;\n\n  if (wants_help(arg1)) {\n    goto com_find_usage;\n  }\n\n  while ((s1 = subtokenizer.GetToken()).length() && (s1.beginswith(\"-\"))) {\n    valid = false;\n    if (s1 == \"-j\") {\n      option += \"j\";\n      continue;\n    }\n    if (s1 == \"-s\") {\n      option += \"s\";\n      valid = true;\n    }\n    if (s1 == \"-d\") {\n      option += \"d\";\n      valid = true;\n    }\n    if (s1 == \"-f\") {\n      option += \"f\";\n      valid = true;\n    }\n    if (s1 == \"-0\") {\n      option += \"f0\";\n      valid = true;\n    }\n    if (s1 == \"-m\") {\n      option += \"fG\";\n      valid = true;\n    }\n    if (s1 == \"--size\") {\n      option += \"S\";\n      valid = true;\n    }\n    if (s1 == \"--fs\") {\n      option += \"L\";\n      valid = true;\n    }\n    if (s1 == \"--checksum\") {\n      option += \"X\";\n      valid = true;\n    }\n    if (s1 == \"--ctime\") {\n      option += \"C\";\n      valid = true;\n    }\n    if (s1 == \"--mtime\") {\n      option += \"M\";\n      valid = true;\n    }\n    if (s1 == \"--fid\") {\n      option += \"F\";\n      valid = true;\n    }\n    if (s1 == \"--nrep\") {\n      option += \"R\";\n      valid = true;\n    }\n    if (s1 == \"--online\") {\n      option += \"O\";\n      valid = true;\n    }\n    if (s1 == \"--fileinfo\") {\n      option += \"I\";\n      valid = true;\n    }\n    if (s1 == \"--nunlink\") {\n      option += \"U\";\n      valid = true;\n    }\n    if (s1 == \"--uid\") {\n      option += \"u\";\n      valid = true;\n    }\n    if (s1 == \"--gid\") {\n      option += \"g\";\n      valid = true;\n    }\n    if (s1 == \"--stripediff\") {\n      option += \"D\";\n      valid = true;\n    }\n    if (s1 == \"--faultyacl\") {\n      option += \"A\";\n      valid = true;\n    }\n    if (s1 == \"--count\") {\n      option += \"Z\";\n      valid = true;\n    }\n    if (s1 == \"--hosts\") {\n      option += \"H\";\n      valid = true;\n    }\n    if (s1 == \"--partition\") {\n      option += \"P\";\n      valid = true;\n    }\n    if (s1 == \"--childcount\") {\n      option += \"l\";\n      valid = true;\n    }\n    if (s1 == \"--xurl\") {\n      option += \"x\";\n      valid = true;\n    }\n    if (s1 == \"-1\") {\n      option += \"1\";\n      valid = true;\n    }\n    if (s1.beginswith(\"-h\") || (s1.beginswith(\"--help\"))) {\n      goto com_find_usage;\n    }\n    if (s1 == \"-x\") {\n      valid = true;\n      attribute = subtokenizer.GetToken();\n      if (!attribute.length())\n        goto com_find_usage;\n      if ((attribute.find(\"&\")) != STR_NPOS)\n        goto com_find_usage;\n    }\n    if (s1 == \"--maxdepth\") {\n      valid = true;\n      maxdepth = subtokenizer.GetToken();\n      if (!maxdepth.length())\n        goto com_find_usage;\n    }\n    if ((s1 == \"-ctime\") || (s1 == \"-mtime\")) {\n      valid = true;\n      XrdOucString period = \"\";\n      period = subtokenizer.GetToken();\n      if (!period.length())\n        goto com_find_usage;\n      bool do_olderthan = false, do_youngerthan = false;\n      if (period.beginswith(\"+\"))\n        do_olderthan = true;\n      if (period.beginswith(\"-\"))\n        do_youngerthan = true;\n      if ((!do_olderthan) && (!do_youngerthan))\n        goto com_find_usage;\n      period.erase(0, 1);\n      time_t now = time(NULL);\n      now -= (86400 * strtoul(period.c_str(), 0, 10));\n      char snow[1024];\n      snprintf(snow, sizeof(snow) - 1, \"%lu\", now);\n      if (do_olderthan) {\n        olderthan = snow;\n      }\n      if (do_youngerthan) {\n        youngerthan = snow;\n      }\n      if (s1 == \"-ctime\")\n        option += \"C\";\n      if (s1 == \"-mtime\")\n        option += \"M\";\n    }\n    if (s1 == \"-c\") {\n      valid = true;\n      option += \"c\";\n      filter = subtokenizer.GetToken();\n      if (!filter.length())\n        goto com_find_usage;\n      if ((filter.find(\"%%\")) != STR_NPOS)\n        goto com_find_usage;\n    }\n    if (s1 == \"--purge\") {\n      valid = true;\n      versions = subtokenizer.GetToken();\n      if (!versions.length())\n        goto com_find_usage;\n    }\n    if (s1 == \"-name\") {\n      valid = true;\n      filematch = subtokenizer.GetToken();\n      option += \"f\";\n      if (!filematch.length())\n        goto com_find_usage;\n    }\n    if (s1 == \"-layoutstripes\") {\n      valid = true;\n      stripes = subtokenizer.GetToken();\n      if (!stripes.length())\n        goto com_find_usage;\n    }\n    if (s1 == \"-p\") {\n      valid = true;\n      option += \"p\";\n      printkey = subtokenizer.GetToken();\n      if (!printkey.length())\n        goto com_find_usage;\n    }\n    if (s1 == \"-b\") {\n      valid = true;\n      option += \"b\";\n    }\n    if (!valid) {\n      goto com_find_usage;\n    }\n  }\n\n  if (s1.length()) {\n    path = s1;\n  }\n  if (path == \"help\") {\n    goto com_find_usage;\n  }\n  if (!path.endswith(\"/\")) {\n    if (!path.endswith(\":\")) {\n      path += \"/\";\n    }\n  }\n\n  if (path.beginswith(\"root://\") || path.beginswith(\"file:\")) {\n    bool XRootD = path.beginswith(\"root:\");\n    std::vector<std::vector<std::string>> found_dirs;\n    std::map<std::string, std::set<std::string>> found;\n    XrdOucString protocol;\n    XrdOucString hostport;\n    XrdOucString sPath;\n    if (path == \"/\") {\n      fprintf(stderr, \"error: I won't do a find on '/'\\n\");\n      global_retc = EINVAL;\n      return 0;\n    }\n    const char* v = 0;\n    if (!(v = eos::common::StringConversion::ParseUrl(path.c_str(), protocol,\n                                                      hostport))) {\n      global_retc = EINVAL;\n      return 0;\n    }\n    sPath = v;\n    std::string Path = v;\n    if (sPath == \"\" && (protocol == \"file\")) {\n      sPath = getenv(\"PWD\");\n      Path = getenv(\"PWD\");\n      if (!sPath.endswith(\"/\")) {\n        sPath += \"/\";\n        Path += \"/\";\n      }\n    }\n    found_dirs.resize(1);\n    found_dirs[0].resize(1);\n    found_dirs[0][0] = Path.c_str();\n    int deepness = 0;\n    do {\n      struct stat buf;\n      found_dirs.resize(deepness + 2);\n      for (unsigned int i = 0; i < found_dirs[deepness].size(); i++) {\n        Path = found_dirs[deepness][i].c_str();\n        XrdOucString url = \"\";\n        eos::common::StringConversion::CreateUrl(\n            protocol.c_str(), hostport.c_str(), Path.c_str(), url);\n        int rstat = (XRootD) ? XrdPosixXrootd::Stat(url.c_str(), &buf)\n                             : stat(url.c_str(), &buf);\n        if (!rstat) {\n          if (S_ISDIR(buf.st_mode)) {\n            DIR* dir = (XRootD) ? XrdPosixXrootd::Opendir(url.c_str())\n                                : opendir(url.c_str());\n            if (dir) {\n              struct dirent* entry;\n              while ((entry = (XRootD) ? XrdPosixXrootd::Readdir(dir)\n                                       : readdir(dir))) {\n                XrdOucString curl = \"\";\n                XrdOucString cpath = Path.c_str();\n                cpath += entry->d_name;\n                if ((!strcmp(entry->d_name, \".\")) ||\n                    (!strcmp(entry->d_name, \"..\"))) {\n                  continue;\n                }\n                eos::common::StringConversion::CreateUrl(\n                    protocol.c_str(), hostport.c_str(), cpath.c_str(), curl);\n                if (!((XRootD) ? XrdPosixXrootd::Stat(curl.c_str(), &buf)\n                               : stat(curl.c_str(), &buf))) {\n                  if (S_ISDIR(buf.st_mode)) {\n                    curl += \"/\";\n                    cpath += \"/\";\n                    found_dirs[deepness + 1].push_back(cpath.c_str());\n                    (void)found[curl.c_str()].size();\n                  } else {\n                    found[url.c_str()].insert(entry->d_name);\n                  }\n                }\n              }\n              (XRootD) ? XrdPosixXrootd::Closedir(dir) : closedir(dir);\n            }\n          }\n        }\n      }\n      deepness++;\n    } while (found_dirs[deepness].size());\n\n    bool show_files = false, show_dirs = false;\n    if ((option.find(\"f\") == STR_NPOS) && (option.find(\"d\") == STR_NPOS)) {\n      show_files = show_dirs = true;\n    } else {\n      if (option.find(\"f\") != STR_NPOS)\n        show_files = true;\n      if (option.find(\"d\") != STR_NPOS)\n        show_dirs = true;\n    }\n    for (auto it = found.begin(); it != found.end(); ++it) {\n      if (show_dirs)\n        fprintf(stdout, \"%s\\n\", it->first.c_str());\n      for (auto sit = it->second.begin(); sit != it->second.end(); ++sit) {\n        if (show_files)\n          fprintf(stdout, \"%s%s\\n\", it->first.c_str(), sit->c_str());\n      }\n    }\n    return 0;\n  }\n\n  if (path.beginswith(\"as3:\")) {\n    XrdOucString hostport;\n    XrdOucString protocol;\n    int rc = system(\"which s3 >&/dev/null\");\n    if (WEXITSTATUS(rc)) {\n      fprintf(stderr, \"error: you miss the <s3> executable provided by libs3 \"\n                      \"in your PATH\\n\");\n      exit(-1);\n    }\n    if (path.endswith(\"/\")) {\n      path.erase(path.length() - 1);\n    }\n    XrdOucString sPath = path.c_str();\n    XrdOucString sOpaque;\n    int qpos = 0;\n    if ((qpos = sPath.find(\"?\")) != STR_NPOS) {\n      sOpaque.assign(sPath, qpos + 1);\n      sPath.erase(qpos);\n    }\n    XrdOucString fPath = eos::common::StringConversion::ParseUrl(\n        sPath.c_str(), protocol, hostport);\n    XrdOucEnv env(sOpaque.c_str());\n    if (env.Get(\"s3.key\")) {\n      setenv(\"S3_SECRET_ACCESS_KEY\", env.Get(\"s3.key\"), 1);\n    }\n    if (env.Get(\"s3.id\")) {\n      setenv(\"S3_ACCESS_KEY_ID\", env.Get(\"s3.id\"), 1);\n    }\n    const char* cstr = getenv(\"S3_ACCESS_KEY\");\n    if (cstr) {\n      setenv(\"S3_SECRET_ACCESS_KEY\", cstr, 1);\n    }\n    cstr = getenv(\"S3_ACESSS_ID\");\n    if (cstr) {\n      setenv(\"S3_ACCESS_KEY_ID\", cstr, 1);\n    }\n    if (!getenv(\"S3_ACCESS_KEY_ID\") || !getenv(\"S3_HOSTNAME\") ||\n        !getenv(\"S3_SECRET_ACCESS_KEY\")) {\n      fprintf(stderr, \"error: you have to set the S3 environment variables \"\n                      \"S3_ACCESS_KEY_ID | S3_ACCESS_ID, S3_HOSTNAME (or use a \"\n                      \"URI), S3_SECRET_ACCESS_KEY | S3_ACCESS_KEY\\n\");\n      global_retc = EINVAL;\n      return 0;\n    }\n    XrdOucString s3env;\n    s3env = \"env S3_ACCESS_KEY_ID=\";\n    s3env += getenv(\"S3_ACCESS_KEY_ID\");\n    s3env += \" S3_HOSTNAME=\";\n    s3env += getenv(\"S3_HOSTNAME\");\n    s3env += \" S3_SECRET_ACCESS KEY=\";\n    s3env += getenv(\"S3_SECRET_ACCESS_KEY\");\n    XrdOucString cmd = \"bash -c \\\"\";\n    cmd += s3env;\n    cmd += \" s3 list \";\n    int bpos = fPath.find(\"/\");\n    XrdOucString bucket;\n    if (bpos != STR_NPOS) {\n      bucket.assign(fPath, 0, bpos - 1);\n    } else {\n      bucket = fPath.c_str();\n    }\n    XrdOucString match;\n    if (bpos != STR_NPOS) {\n      match.assign(fPath, bpos + 1);\n    } else {\n      match = \"\";\n    }\n    if ((!bucket.length()) || (bucket.find(\"*\") != STR_NPOS)) {\n      fprintf(stderr,\n              \"error: no bucket specified or wildcard in bucket name!\\n\");\n      global_retc = EINVAL;\n      return 0;\n    }\n    cmd += bucket.c_str();\n    cmd += \" | awk '{print $1}' \";\n    if (match.length()) {\n      if (match.endswith(\"*\")) {\n        match.erase(match.length() - 1);\n        match.insert(\"^\", 0);\n      }\n      if (match.beginswith(\"*\")) {\n        match.erase(0, 1);\n        match += \"$\";\n      }\n      cmd += \" | egrep '\";\n      cmd += match.c_str();\n      cmd += \"'\";\n    }\n    cmd += \" | grep -v 'Bucket' | grep -v '----------' | grep -v 'Key' | awk \"\n           \"-v prefix='\";\n    cmd += bucket.c_str();\n    cmd += \"' '{print \\\"as3:\\\"prefix\\\"/\\\"$1}'\";\n    cmd += \"\\\"\";\n    rc = system(cmd.c_str());\n    if (WEXITSTATUS(rc)) {\n      fprintf(stderr, \"error: failed to run %s\\n\", cmd.c_str());\n    }\n  }\n\n  if ((stripes.length())) {\n    XrdOucString subfind = oarg;\n    XrdOucString repstripes = \" \";\n    repstripes += stripes;\n    repstripes += \" \";\n    subfind.replace(\"-layoutstripes\", \"\");\n    subfind.replace(repstripes, \" -f -s \");\n    int rc = native_com_old_find((char*)subfind.c_str());\n    std::vector<std::string> files_found;\n    files_found.clear();\n    command_result_stdout_to_vector(files_found);\n    unsigned long long cnt = 0, goodentries = 0, badentries = 0;\n    for (unsigned int i = 0; i < files_found.size(); i++) {\n      if (!files_found[i].length())\n        continue;\n      XrdOucString cline = \"layout \";\n      cline += files_found[i].c_str();\n      cline += \" -stripes \";\n      cline += stripes;\n      rc = RunRegisteredCommand(\"file\", TokenizeArgs(cline.c_str()));\n      if (rc)\n        badentries++;\n      else\n        goodentries++;\n      cnt++;\n    }\n    rc = 0;\n    if (!silent) {\n      fprintf(stderr, \"nentries=%llu good=%llu bad=%llu\\n\", cnt, goodentries,\n              badentries);\n    }\n    return 0;\n  }\n\n  if ((option.find(\"c\")) != STR_NPOS) {\n    XrdOucString subfind = oarg;\n    subfind.replace(\"-c\", \"-s -f\");\n    subfind.replace(filter, \"\");\n    int rc = native_com_old_find((char*)subfind.c_str());\n    std::vector<std::string> files_found;\n    files_found.clear();\n    command_result_stdout_to_vector(files_found);\n    unsigned long long cnt = 0, goodentries = 0, badentries = 0;\n    for (unsigned int i = 0; i < files_found.size(); i++) {\n      if (!files_found[i].length())\n        continue;\n      XrdOucString cline = \"check \";\n      cline += files_found[i].c_str();\n      cline += \" \";\n      cline += filter;\n      rc = RunRegisteredCommand(\"file\", TokenizeArgs(cline.c_str()));\n      if (rc)\n        badentries++;\n      else\n        goodentries++;\n      cnt++;\n    }\n    rc = 0;\n    if (!silent) {\n      fprintf(stderr, \"nentries=%llu good=%llu bad=%llu\\n\", cnt, goodentries,\n              badentries);\n    }\n    return 0;\n  }\n\n  path = abspath(path.c_str());\n  if (!s1.length() && (path == \"/\")) {\n    fprintf(stderr, \"error: you didnt' provide any path and would query '/' - \"\n                    \"will not do that!\\n\");\n    return EINVAL;\n  }\n  in += \"mgm.path=\";\n  in += path;\n  in += \"&mgm.option=\";\n  in += option;\n  if (attribute.length()) {\n    in += \"&mgm.find.attribute=\";\n    in += attribute;\n  }\n  if (maxdepth.length()) {\n    in += \"&mgm.find.maxdepth=\";\n    in += maxdepth;\n  }\n  if (olderthan.length()) {\n    in += \"&mgm.find.olderthan=\";\n    in += olderthan;\n  }\n  if (youngerthan.length()) {\n    in += \"&mgm.find.youngerthan=\";\n    in += youngerthan;\n  }\n  if (versions.length()) {\n    in += \"&mgm.find.purge.versions=\";\n    in += versions;\n  }\n  if (filematch.length()) {\n    in += \"&mgm.find.match=\";\n    in += filematch;\n  }\n  if (printkey.length()) {\n    in += \"&mgm.find.printkey=\";\n    in += printkey;\n  }\n  {\n    XrdOucEnv* result;\n    result = client_command(in);\n    if ((option.find(\"s\")) == STR_NPOS) {\n      global_retc = output_result(result);\n    } else {\n      if (result) {\n        global_retc = 0;\n      } else {\n        global_retc = EINVAL;\n      }\n    }\n  }\n  return 0;\ncom_find_usage:\n  fprintf(stderr, \"%s\", MakeOldfindHelp().c_str());\n  global_retc = EINVAL;\n  return 0;\n}\n\nnamespace {\nclass OldfindCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"oldfind\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Find files/directories (old implementation)\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    return native_com_old_find((char*)joined.c_str());\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeOldfindHelp().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterOldfindNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<OldfindCommand>());\n}\n\n// Legacy compatibility symbol required by ConsoleMain and other modules\nint\ncom_old_find(char* arg)\n{\n  return native_com_old_find(arg);\n}\n"
  },
  {
    "path": "console/commands/native/pwd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: pwd-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include <memory>\n\nnamespace {\nclass PwdCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"pwd\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Print working directory\";\n  }\n  bool\n  requiresMgm(const std::string&) const override\n  {\n    return false;\n  }\n  int\n  run(const std::vector<std::string>&, CommandContext&) override\n  {\n    fprintf(stdout, \"%s\\n\", ::gPwd.c_str());\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n  }\n};\n} // namespace\n\nvoid\nRegisterPwdNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<PwdCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/quota-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: quota-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/ICmdHelper.hh\"\n#include <iomanip>\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeQuotaHelp()\n{\n  std::ostringstream oss;\n  oss << \"Usage:\\n\"\n      << \"    quota [<path>]                    : show personal quota for all or \"\n         \"only the quota node responsible for <path>\\n\"\n      << \"    quota ls [-n] [-m] [-u <uid>] [-g <gid>] [[-p|x|q] <path>] : list \"\n         \"configured quota and quota node(s)\\n\"\n      << \"                                                                       -p : \"\n         \"find closest matching quotanode\\n\"\n      << \"                                                                       -x : \"\n         \"as -p but <path> has to exist\\n\"\n      << \"                                                                       -q : \"\n         \"as -p but <path> has to be a quotanode\\n\"\n      << \"    quota set -u <uid>|-g <gid> [-v <bytes>] [-i <inodes>] [[-p] <path>] : set \"\n         \"volume and/or inode quota by uid or gid\\n\"\n      << \"    quota rm -u <uid>|-g <gid> [-v] [-i] [[-p] <path>] : remove configured \"\n         \"quota type(s) for uid/gid in path\\n\"\n      << \"    quota rmnode [-p] <path> [--really-want] : remove quota node and every \"\n         \"defined quota on that node\\n\"\n      << std::endl\n      << \"  General options:\\n\"\n      << \"    -m : print information in monitoring <key>=<value> format\\n\"\n      << \"    -n : don't translate ids, print uid and gid number\\n\"\n      << \"    -u/--uid <uid> : print information only for uid <uid>\\n\"\n      << \"    -g/--gid <gid> : print information only for gid <gid>\\n\"\n      << \"    -p/--path <path> : print information only for path <path> - this can also \"\n         \"be given without -p or --path\\n\"\n      << \"    -v/--volume <bytes> : refer to volume limit in <bytes>\\n\"\n      << \"    -i/--inodes <inodes> : refer to inode limit in number of <inodes>\\n\"\n      << std::endl\n      << \"  Notes:\\n\"\n      << \"    => you have to specify either the user or the group identified by the \"\n         \"unix id or the user/group name\\n\"\n      << \"    => the space argument is by default assumed as 'default'\\n\"\n      << \"    => you have to specify at least a volume or an inode limit to set quota\\n\"\n      << \"    => for convenience all commands can just use <path> as last argument \"\n         \"omitting the -p|--path e.g. quota ls /eos/ ...\\n\"\n      << \"    => if <path> is not terminated with a '/' it is assumed to be a file so \"\n         \"it won't match the quota node with <path>/ !\\n\"\n      << \"    => rmnode requires confirmation unless --really-want is passed\\n\";\n  return oss.str();\n}\n\nvoid ConfigureQuotaApp(CLI::App& app)\n{\n  app.name(\"quota\");\n  app.description(\"Quota System configuration\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeQuotaHelp();\n      }));\n}\n\nclass QuotaHelper : public ICmdHelper {\npublic:\n  QuotaHelper(const GlobalOptions& opts) : ICmdHelper(opts) {}\n  ~QuotaHelper() override = default;\n  bool\n  ParseCommand(const char* arg) override\n  {\n    eos::console::QuotaProto* quota = mReq.mutable_quota();\n    eos::common::StringTokenizer tokenizer(arg);\n    tokenizer.GetLine();\n    std::string token;\n    tokenizer.NextToken(token);\n    if (token == \"\" || token == \"-m\" || token == \"--path\" || token == \"-p\" ||\n        token == \"-x\" || token == \"-q\" || (token.find('/') == 0)) {\n      auto* lsuser = quota->mutable_lsuser();\n      std::string aux;\n      if (token == \"\") {\n        aux = DefaultRoute(false);\n        if (!aux.empty() && aux[0] == '/')\n          lsuser->set_space(aux);\n      } else {\n        do {\n          if (token == \"-m\") {\n            lsuser->set_format(true);\n            aux = DefaultRoute(false);\n            if (!aux.empty() && aux[0] == '/')\n              lsuser->set_space(aux);\n          } else if (token == \"--path\" || token == \"-p\" || token == \"-x\" ||\n                     token == \"-q\" || (token.find('/') == 0)) {\n            if (token == \"-x\")\n              lsuser->set_exists(true);\n            if (token == \"-q\")\n              lsuser->set_quotanode(true);\n            if (token == \"--path\" || token == \"-p\" || token == \"-x\" ||\n                token == \"-q\") {\n              if (tokenizer.NextToken(token))\n                lsuser->set_space(token);\n              else\n                return false;\n            } else if (token.find('/') == 0) {\n              lsuser->set_space(token);\n              if (tokenizer.NextToken(token))\n                return false;\n            }\n          } else {\n            return false;\n          }\n        } while (tokenizer.NextToken(token));\n      }\n    } else if (token == \"ls\") {\n      auto* ls = quota->mutable_ls();\n      while (tokenizer.NextToken(token)) {\n        if (token == \"--uid\" || token == \"-u\") {\n          if (tokenizer.NextToken(token))\n            ls->set_uid(token);\n          else\n            return false;\n        } else if (token == \"--gid\" || token == \"-g\") {\n          if (tokenizer.NextToken(token))\n            ls->set_gid(token);\n          else\n            return false;\n        } else if (token == \"-m\") {\n          ls->set_format(true);\n        } else if (token == \"-n\") {\n          ls->set_printid(true);\n        } else if (token == \"--path\" || token == \"-p\" || token == \"-x\" ||\n                   token == \"-q\" || (token.find('/') == 0)) {\n          if (token == \"-x\")\n            ls->set_exists(true);\n          if (token == \"-q\")\n            ls->set_quotanode(true);\n          if (token == \"--path\" || token == \"-p\" || token == \"-q\" ||\n              token == \"-x\") {\n            if (tokenizer.NextToken(token))\n              ls->set_space(token);\n            else\n              return false;\n          } else if (token.find('/') == 0) {\n            ls->set_space(token);\n            if (tokenizer.NextToken(token))\n              return false;\n          }\n        } else {\n          return false;\n        }\n      }\n    } else if (token == \"set\") {\n      auto* set = quota->mutable_set();\n      while (tokenizer.NextToken(token)) {\n        if (token == \"--uid\" || token == \"-u\") {\n          if (tokenizer.NextToken(token))\n            set->set_uid(token);\n          else\n            return false;\n        } else if (token == \"--gid\" || token == \"-g\") {\n          if (tokenizer.NextToken(token))\n            set->set_gid(token);\n          else\n            return false;\n        } else if (token == \"--volume\" || token == \"-v\") {\n          if (tokenizer.NextToken(token))\n            set->set_maxbytes(token);\n          else\n            return false;\n        } else if (token == \"--inodes\" || token == \"-i\") {\n          if (tokenizer.NextToken(token))\n            set->set_maxinodes(token);\n          else\n            return false;\n        } else if (token == \"--path\" || token == \"-p\" ||\n                   (token.find('/') == 0)) {\n          if (token == \"--path\" || token == \"-p\") {\n            if (tokenizer.NextToken(token))\n              set->set_space(token);\n            else\n              return false;\n          } else if (token.find('/') == 0) {\n            set->set_space(token);\n            if (tokenizer.NextToken(token))\n              return false;\n          }\n        } else {\n          return false;\n        }\n      }\n    } else if (token == \"rm\") {\n      auto* rm = quota->mutable_rm();\n      while (tokenizer.NextToken(token)) {\n        if (token == \"--uid\" || token == \"-u\") {\n          if (tokenizer.NextToken(token))\n            rm->set_uid(token);\n          else\n            return false;\n        } else if (token == \"--gid\" || token == \"-g\") {\n          if (tokenizer.NextToken(token))\n            rm->set_gid(token);\n          else\n            return false;\n        } else if (token == \"--volume\" || token == \"-v\") {\n          rm->set_type(eos::console::QuotaProto_RmProto::VOLUME);\n        } else if (token == \"--inode\" || token == \"-i\") {\n          rm->set_type(eos::console::QuotaProto_RmProto::INODE);\n        } else if (token == \"--path\" || token == \"-p\" ||\n                   (token.find('/') == 0)) {\n          if (token == \"--path\" || token == \"-p\") {\n            if (tokenizer.NextToken(token))\n              rm->set_space(token);\n            else\n              return false;\n          } else if (token.find('/') == 0) {\n            rm->set_space(token);\n            if (tokenizer.NextToken(token))\n              return false;\n          }\n        } else {\n          return false;\n        }\n      }\n    } else if (token == \"rmnode\") {\n      bool dontask = false;\n      auto* rmnode = quota->mutable_rmnode();\n      tokenizer.NextToken(token);\n      if (token == \"--really-want\") {\n        dontask = true;\n        tokenizer.NextToken(token);\n      }\n      if (token == \"--path\" || token == \"-p\" || (token.find('/') == 0)) {\n        if (token == \"--path\" || token == \"-p\") {\n          if (tokenizer.NextToken(token))\n            rmnode->set_space(token);\n          else\n            return false;\n        } else if (token.find('/') == 0) {\n          rmnode->set_space(token);\n          if (tokenizer.NextToken(token))\n            return false;\n        }\n      } else {\n        return false;\n      }\n      if (!dontask) {\n        fprintf(\n            stderr,\n            \"Do you really want to delete the quota node under path: %s ?\\n\",\n            rmnode->space().c_str());\n        fprintf(stderr,\n                \"Use --really-want to skip interactive confirmation.\\n\");\n        mNeedsConfirmation = true;\n      }\n    } else {\n      return false;\n    }\n    return true;\n  }\n};\n\nclass QuotaProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"quota\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Quota System configuration\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      if (args[i].find(' ') != std::string::npos)\n        oss << std::quoted(args[i]);\n      else\n        oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    QuotaHelper helper(*ctx.globalOpts);\n    if (!helper.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = helper.Execute(true, true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureQuotaApp(app);\n    fprintf(stderr, \"%s\", app.help().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterQuotaProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<QuotaProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/rclone-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: rclone-cmd-native.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright(C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n *(at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/NewfindHelper.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/Timing.hh\"\n#include \"common/Path.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/CopyProcess.hh\"\n#include <CLI/CLI.hpp>\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdCl/XrdClCopyProcess.hh>\n#include <XrdCl/XrdClPropertyList.hh>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <string.h>\n#include <iostream>\n#include <memory>\n#include <filesystem>\n#include <algorithm>\n#include <chrono>\n#include <optional>\n#include <string>\n#include <vector>\n\nextern XrdOucString serveruri;\n\nstruct fs_entry {\n  struct timespec mtime;\n  size_t size;\n  std::string type;\n  std::string target;\n\n  bool newer(struct timespec& cmptime)\n  {\n    if (mtime.tv_sec < cmptime.tv_sec) {\n      return true;\n    } else if (mtime.tv_sec > cmptime.tv_sec) {\n      return false;\n    } else if (mtime.tv_nsec < cmptime.tv_nsec) {\n      return true;\n    } else {\n      return false;\n    }\n  }\n};\n\nstruct fs_result {\n  std::map<std::string, fs_entry> directories;\n  std::map<std::string, fs_entry> files;\n  std::map<std::string, fs_entry> links;\n};\n\nbool dryrun = false;\nbool noreplace = false;\nbool nodelete = false;\nbool verbose = false;\nbool is_silent = false;\nbool filter_versions = true;\nbool filter_atomic = true;\nbool filter_hidden = true;\n\n/** Parsed options for rclone command */\nstruct RcloneOptions {\n  std::string subcommand;  // \"copy\" or \"sync\"\n  std::string src;\n  std::string dst;\n  bool delete_opt = false;\n  bool noreplace = false;\n  bool dryrun = false;\n  bool atomic = false;\n  bool versions = false;\n  bool hidden = false;\n  bool verbose = false;\n  bool silent = false;\n};\n\nnamespace {\nstd::string MakeRcloneHelp()\n{\n  return R\"(Usage: rclone copy <src-dir> <dst-dir> [OPTIONS]\n       rclone sync <dir1> <dir2> [OPTIONS]\n\n  copy : one-way sync from source to destination\n  sync : bi-directional sync based on modification times\n\nOptions:\n  --delete    delete based on mtimes (currently unsupported)\n  --noreplace never update files, only create new ones\n  --dryrun    simulate and show actions, don't execute\n  --atomic    copy/sync also EOS atomic files\n  --versions  copy/sync also EOS version files\n  --hidden    copy/sync also hidden files/directories\n  -v,--verbose  display all actions, not only summary\n  -s,--silent   only show errors\n\nRun with: eos rclone copy|sync <src> <dst> [OPTIONS]\n)\";\n}\n\nvoid ConfigureRcloneApp(CLI::App& app, RcloneOptions& opts)\n{\n  app.name(\"rclone\");\n  app.description(\"RClone-like copy/sync for EOS\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeRcloneHelp();\n      }));\n  app.require_subcommand(1, 1);\n  auto* copy_cmd = app.add_subcommand(\"copy\", \"one-way copy src to dst\");\n  auto* sync_cmd = app.add_subcommand(\"sync\", \"bi-directional sync\");\n  for (auto* cmd : {copy_cmd, sync_cmd}) {\n    cmd->add_option(\"src\", opts.src, \"source directory\")->required();\n    cmd->add_option(\"dst\", opts.dst, \"destination directory\")->required();\n    cmd->add_flag(\"--delete\", opts.delete_opt, \"delete based on mtimes\");\n    cmd->add_flag(\"--noreplace\", opts.noreplace, \"never update, only create\");\n    cmd->add_flag(\"--dryrun\", opts.dryrun, \"simulate only\");\n    cmd->add_flag(\"--atomic\", opts.atomic, \"include atomic files\");\n    cmd->add_flag(\"--versions\", opts.versions, \"include version files\");\n    cmd->add_flag(\"--hidden\", opts.hidden, \"include hidden files\");\n    cmd->add_flag(\"-v,--verbose\", opts.verbose, \"verbose output\");\n    cmd->add_flag(\"-s,--silent\", opts.silent, \"silent mode\");\n  }\n}\n\nbool ParseRcloneArgs(const std::vector<std::string>& args, RcloneOptions& opts)\n{\n  if (args.empty())\n    return false;\n\n  for (const auto& a : args) {\n    if (a == \"--help\" || a == \"-h\") {\n      std::cerr << MakeRcloneHelp();\n      return false;\n    }\n  }\n\n  CLI::App app;\n  ConfigureRcloneApp(app, opts);\n\n  std::vector<std::string> cli_args = args;\n  std::reverse(cli_args.begin(), cli_args.end());\n  try {\n    app.parse(cli_args);\n  } catch (const CLI::ParseError&) {\n    std::cerr << MakeRcloneHelp();\n    return false;\n  }\n\n  if (app.got_subcommand(\"copy\"))\n    opts.subcommand = \"copy\";\n  else if (app.got_subcommand(\"sync\"))\n    opts.subcommand = \"sync\";\n  else\n    return false;\n\n  return true;\n}\n\nstd::vector<std::string> TokenizeRcloneArgs(const char* argin)\n{\n  std::vector<std::string> result;\n  eos::common::StringTokenizer tokenizer(argin);\n  tokenizer.GetLine();\n  XrdOucString tok;\n  while ((tok = tokenizer.GetToken()).length())\n    result.push_back(tok.c_str());\n  return result;\n}\n} // namespace\n\nstatic void rclone_usage()\n{\n  std::cerr << MakeRcloneHelp();\n  exit(-1);\n}\n\nfs_result fs_find(const char* path)\n{\n  fs_result result;\n  std::stringstream s;\n  eos::common::Path cPath(path);\n  namespace fs = std::filesystem;\n  fs::path path_to_traverse = path;\n  struct stat buf;\n\n  try {\n    for (const auto& entry : fs::recursive_directory_iterator(path_to_traverse,\n         std::filesystem::directory_options::skip_permission_denied)) {\n      std::string p = entry.path().string();\n      // filter functions\n      eos::common::Path iPath(p.c_str());\n\n      if (filter_versions) {\n        if (iPath.isVersionPath()) {\n          continue;\n        }\n      }\n\n      if (filter_atomic) {\n        if (iPath.isAtomicFile()) {\n          continue;\n        }\n      }\n\n      if (filter_hidden && iPath.GetFullPath().find(\"/.\") != STR_NPOS) {\n        if (!iPath.isVersionPath() && !iPath.isAtomicFile()) {\n          continue;\n        }\n      }\n\n      std::string t = p;\n\n      if (!::lstat(p.c_str(), &buf)) {\n        p.erase(0, cPath.GetFullPath().length());\n\n        switch ((buf.st_mode & S_IFMT)) {\n        case S_IFDIR :\n          p += \"/\";\n          result.directories[p].mtime.tv_sec = buf.st_mtime;\n          result.directories[p].size  = buf.st_size;\n          //  s << \"path=\\\"\" << p << \"/\\\" mtime=\" << eos::common::Timing::TimespecToString(buf.st_mtim) << \" size=\" << buf.st_size << std::endl;\n          break;\n\n        case S_IFREG :\n          result.files[p].mtime.tv_sec = buf.st_mtime;\n          result.files[p].size  = buf.st_size;\n          //s << \"path=\\\"\" << p << \"\\\" mtime=\" << eos::common::Timing::TimespecToString(buf.st_mtim) << \" size=\" << buf.st_size << std::endl;\n          break;\n\n        case S_IFLNK :\n          result.links[p].size = 0;\n          result.links[p].mtime.tv_sec = buf.st_mtime;\n          char link[4096];\n          ssize_t target = readlink(t.c_str(), link, sizeof(link));\n\n          if (target >= 0) {\n            result.links[p].target = std::string(link, target);\n          }\n\n          break;\n        }\n      }\n    }\n  } catch (std::filesystem::filesystem_error const& ex) {\n    std::cerr\n        << \"error:  \" << ex.what() << '\\n'\n        << \"#      path  : \" << ex.path1() << '\\n'\n        << \"#      errc  :    \" << ex.code().value() << '\\n'\n        << \"#      msg   :  \" << ex.code().message() << '\\n'\n        << \"#      class : \" << ex.code().category().name() << '\\n';\n    exit(-1);\n  }\n\n  //  std::cout << s.str();\n  return result;\n}\n\nfs_result eos_find(const char* path)\n{\n  fs_result result;\n  eos::common::Path cPath(path);\n  NewfindHelper find(gGlobalOpts);\n  std::string args = \"--format type,mtime,size,link \";\n  args += path;\n\n  if (!find.ParseCommand(args.c_str())) {\n    std::cerr << \"error: illegal subcommand '\" << args << \"'\" << std::endl;\n  }\n\n  find.Silent();\n  int rc = find.Execute();\n\n  if (!rc) {\n    std::string findresult = find.GetResult();\n    std::vector<std::string> lines;\n    eos::common::StringConversion::Tokenize(findresult, lines, \"\\n\");\n\n    for (auto l : lines) {\n      std::vector<std::string> kvs;\n      eos::common::StringConversion::Tokenize(l, kvs, \" \");\n      struct timespec ts {\n        0, 0\n      };\n      size_t size{0};\n      std::string path;\n      std::string type;\n\n      for (auto k : kvs) {\n        std::string tag, value;\n        eos::common::StringConversion::SplitKeyValue(k, tag, value, \"=\");\n\n        if (tag == \"mtime\") {\n          eos::common::Timing::Timespec_from_TimespecStr(value, ts);\n\n          if (type == \"directory\") {\n            result.directories[path].mtime = ts;\n          } else if (type == \"file\") {\n            result.files[path].mtime = ts;\n          } else if (type == \"symlink\") {\n            result.links[path].mtime = ts;\n          }\n        }\n\n        if (tag == \"size\") {\n          size = std::stoull(value.c_str(), 0, 10);\n\n          if (type == \"directory\") {\n            result.directories[path].size = size;\n          } else if (type == \"file\") {\n            result.files[path].size = size;\n          } else if (type == \"symlink\") {\n            result.links[path].size = 0;\n          }\n        }\n\n        if (tag == \"path\") {\n          // remove quotes\n          value.erase(0, 1);\n          value.erase(value.length() - 1);\n          value.erase(0, cPath.GetFullPath().length());\n          path = value;\n        }\n\n        if (tag == \"type\") {\n          type = value;\n        }\n\n        if (tag == \"target\" && type == \"symlink\") {\n          value.erase(0, 1);\n          value.erase(value.length() - 1);\n          result.links[path].target = value;\n        }\n\n        // filter functions\n        eos::common::Path iPath(path.c_str());\n\n        if (filter_versions) {\n          if (iPath.isVersionPath()) {\n            break;\n          }\n        }\n\n        if (filter_atomic) {\n          if (iPath.isAtomicFile()) {\n            break;\n          }\n        }\n\n        if (filter_hidden && iPath.GetFullPath().find(\"/.\") != STR_NPOS) {\n          if (!iPath.isVersionPath() && !iPath.isAtomicFile()) {\n            break;\n          }\n        }\n      }\n    }\n  } else {\n    std::cerr << \"error: \" << find.GetError() << std::endl;\n    exit(rc);\n  }\n\n  return result;\n}\n\nint createDir(const std::string& i, eos::common::Path& prefix)\n{\n  if (!prefix.GetFullPath().beginswith(\"/eos/\")) {\n    int rc = 0;\n    std::string mkpath = std::string(prefix.GetFullPath().c_str()) +\n                         std::string(\"/\") + i;\n\n    if (!dryrun) {\n      rc = ::mkdir(mkpath.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);\n    }\n\n    std::cerr << \"[ mkdir                 ] : path:\" << \"[mkdir] path: \" <<\n              mkpath.c_str() << \" retc: \" << rc << std::endl;\n    return rc;\n  } else {\n    XrdCl::URL url(serveruri.c_str());\n    url.SetPath(std::string(prefix.GetFullPath().c_str()) + std::string(\"/\") + i);\n\n    if (!url.IsValid()) {\n      std::cerr << \"error: invalid url \" << i.c_str() << std::endl;\n      return 0;\n    }\n\n    XrdCl::FileSystem fs(url);\n    mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP;\n    XrdCl::Access::Mode mode_xrdcl = eos::common::LayoutId::MapModeSfs2XrdCl(mode);\n    XrdCl::XRootDStatus status = fs.MkDir(url.GetPath(),\n                                          XrdCl::MkDirFlags::MakePath,\n                                          mode_xrdcl);\n    std::cerr << \"[ mkdir                 ] : url:\" << url.GetURL() << \" : \" <<\n              status.IsOK() << std::endl;\n    return (!status.IsOK());\n  }\n}\n\nint removeDir(const std::string& i, eos::common::Path& prefix)\n{\n  if (!prefix.GetFullPath().beginswith(\"/eos/\")) {\n    int rc = 0;\n    std::string rmpath = std::string(prefix.GetFullPath().c_str()) +\n                         std::string(\"/\") + i;\n\n    if (!dryrun) {\n      rc = ::rmdir(rmpath.c_str());\n    }\n\n    std::cerr << \"[ rmdir                 ] : path:\" << rmpath.c_str() << \" retc: \"\n              << rc << std::endl;\n    return rc;\n  } else {\n    XrdCl::URL url(serveruri.c_str());\n    url.SetPath(std::string(prefix.GetFullPath().c_str()) + std::string(\"/\") + i);\n\n    if (!url.IsValid()) {\n      std::cerr << \"error: invalid url \" << i.c_str() << std::endl;\n      return 0;\n    }\n\n    XrdCl::FileSystem fs(url);\n    XrdCl::XRootDStatus status = fs.RmDir(url.GetPath());\n    std::cerr << \"[ rmdir                 ] : url:\" << url.GetURL() << \" : \" <<\n              status.IsOK() << std::endl;\n    return (!status.IsOK());\n  }\n}\n\nint removeFile(const std::string& i, eos::common::Path& prefix)\n{\n  if (!prefix.GetFullPath().beginswith(\"/eos/\")) {\n    int rc = 0;\n    std::string rmpath = std::string(prefix.GetFullPath().c_str()) +\n                         std::string(\"/\") + i;\n\n    if (!dryrun) {\n      rc = ::unlink(rmpath.c_str());\n    }\n\n    std::cerr << \"[ unlink                ] : path:\" << rmpath.c_str() << \" retc: \"\n              << rc << std::endl;\n    return rc;\n  } else {\n    XrdCl::URL url(serveruri.c_str());\n    url.SetPath(std::string(prefix.GetFullPath().c_str()) + std::string(\"/\") + i);\n\n    if (!url.IsValid()) {\n      std::cerr << \"error: invalid url \" << i.c_str() << std::endl;\n      return 0;\n    }\n\n    XrdCl::FileSystem fs(url);\n    XrdCl::XRootDStatus status = fs.Rm(url.GetPath());\n    std::cerr << \"[ unlink                ] : url:\" << url.GetURL() << \" : \" <<\n              status.IsOK() << std::endl;\n    return (!status.IsOK());\n  }\n}\n\nint createLink(const std::string& i, eos::common::Path& prefix,\n               const std::string& target, eos::common::Path& targetprefix,\n               struct timespec& mtime)\n{\n  std::string targetpath = target;\n\n  if (targetpath.find(prefix.GetFullPath().c_str()) == 0) {\n    // might need to rewrite the link target with a new prefix!\n    targetpath.erase(0, prefix.GetFullPath().length());\n    targetpath.insert(0, targetprefix.GetFullPath().c_str());\n  }\n\n  if (!prefix.GetFullPath().beginswith(\"/eos/\")) {\n    int rc = 0;\n    std::string linkpath = std::string(prefix.GetFullPath().c_str()) +\n                           std::string(\"/\") + i;\n\n    if (!is_silent && verbose) {\n      std::cout << \"[ link  ] linking \" << linkpath.c_str() << \" => \" <<\n                target.c_str() << \" \" << mtime.tv_sec << \".\" << mtime.tv_nsec << std::endl;\n    }\n\n    if (!dryrun) {\n      rc = ::symlink(target.c_str(), linkpath.c_str());\n\n      if (rc) {\n        std::cerr << \"error: symlink rc=\" << rc << \" errno=\" << errno << std::endl;\n      }\n    }\n\n    struct timespec times[2];\n\n    times[0] = mtime;\n\n    times[1] = mtime;\n\n    if (!dryrun) {\n      int rc2 = utimensat(0, linkpath.c_str(), times, AT_SYMLINK_NOFOLLOW);\n      rc |= rc2;\n\n      if (rc2) {\n        std::cerr << \"error: utimesat rc=\" << rc << \" errno=\" << errno << std::endl;\n      }\n    }\n\n    if (!is_silent && verbose) {\n      std::cout << \"[ symlink               ] : path:\" << linkpath.c_str() <<\n                \" retc: \" << rc << std::endl;\n    }\n\n    return rc;\n  } else {\n    // Use file symlink (mgm.cmd=file) like ln-cmd-native / file symlink instead of deprecated pcmd\n    std::string linkpath =\n        std::string(prefix.GetFullPath().c_str()) + std::string(\"/\") + i;\n    XrdOucString in = \"mgm.cmd=file&mgm.subcmd=symlink\";\n    in += \"&mgm.path=\";\n    in += eos::common::StringConversion::curl_escaped(linkpath).c_str();\n    in += \"&eos.encodepath=1\";\n    in += \"&mgm.file.source=\";\n    in += linkpath.c_str();\n    in += \"&mgm.file.target=\";\n    in += targetpath.c_str();\n\n    XrdOucEnv* result = client_command(in, false, nullptr);\n    int retc = 0;\n    if (result) {\n      int envlen = 0;\n      const char* envstr = result->Env(envlen);\n      if (envlen) {\n        XrdOucEnv env(envstr);\n        const char* ptr = env.Get(\"mgm.proc.retc\");\n        if (ptr) {\n          retc = std::stoi(ptr);\n        }\n      }\n      delete result;\n    } else {\n      retc = EINVAL;\n    }\n    {\n      // fix mtime (still uses pcmd utimes - no mgm.cmd equivalent for symlink mtime)\n      XrdCl::URL url(serveruri.c_str());\n      url.SetPath(std::string(prefix.GetFullPath().c_str()) + std::string(\"/\") + i);\n      if (url.IsValid()) {\n        std::string request =\n            eos::common::StringConversion::curl_escaped(std::string(\n                prefix.GetFullPath().c_str()) + std::string(\"/\") + i);\n        request += \"?\";\n        request += \"mgm.pcmd=utimes\";\n        request += \"&tv1_sec=0\";\n        request += \"&tv1_nsec=0\";\n        request += \"&tv2_sec=\";\n        request += std::to_string(mtime.tv_sec);\n        request += \"&tv2_nsec=\";\n        std::stringstream oss;\n        oss << std::setfill('0') << std::setw(9) << mtime.tv_nsec;\n        request += oss.str();\n        request += \"&eos.encodepath=1\";\n        XrdCl::Buffer arg;\n        XrdCl::Buffer* response = nullptr;\n        arg.FromString(request);\n        XrdCl::FileSystem fs(url);\n        XrdCl::XRootDStatus status = fs.Query(XrdCl::QueryCode::OpaqueFile, arg,\n                                              response);\n        if (response) {\n          delete response;\n        }\n        retc |= !status.IsOK();\n      }\n    }\n\n    if (!is_silent && verbose) {\n      std::cout << \"[ symlink               ] : path:\" << linkpath << \" retc: \" << retc\n                << std::endl;\n    }\n\n    return retc;\n  }\n}\n\n\nint setDirMtime(const std::string& i, eos::common::Path& prefix,\n                struct timespec mtime)\n{\n  std::string mtpath = std::string(prefix.GetFullPath().c_str()) +\n                       std::string(\"/\") + i;\n\n  if (!prefix.GetFullPath().beginswith(\"/eos/\")) {\n    // apply local mtime;\n    struct timespec times[2];\n    times[0] = mtime;\n    times[1] = mtime;\n    int rc = 0;\n\n    if (!dryrun) {\n      rc = utimensat(0, mtpath.c_str(), times, AT_SYMLINK_NOFOLLOW);\n    }\n\n    if (!is_silent && verbose) {\n      std::cout << \"[ mtime                 ] : path:\" << \"[utime] path: \" <<\n                mtpath.c_str() << \" retc: \" << rc <<  \" \" << mtime.tv_sec << \":\" <<\n                mtime.tv_nsec << std::endl;\n    }\n\n    return rc;\n  } else {\n    XrdCl::URL url(serveruri.c_str());\n    url.SetPath(std::string(prefix.GetFullPath().c_str()) + std::string(\"/\") + i);\n\n    if (!url.IsValid()) {\n      std::cerr << \"error: invalid url \" << i.c_str() << std::endl;\n      return 0;\n    }\n\n    std::string request;\n    XrdCl::Buffer arg;\n    XrdCl::Buffer* response = nullptr;\n    request = eos::common::StringConversion::curl_escaped(std::string(\n                prefix.GetFullPath().c_str()) + std::string(\"/\") + i);\n    request += \"?\";\n    request += \"mgm.pcmd=utimes\";\n    request += \"&tv1_sec=0\";  //ignored\n    request += \"&tv1_nsec=0\"; // ignored\n    request += \"&tv2_sec=\";\n    request += std::to_string(mtime.tv_sec);\n    request += \"&tv2_nsec=\";\n    std::stringstream oss;\n    oss << std::setfill('0') << std::setw(9) << mtime.tv_nsec;\n    request += oss.str();\n    request += \"&eos.encodepath=1\";\n    arg.FromString(request);\n    XrdCl::FileSystem fs(url);\n    XrdCl::XRootDStatus status = fs.Query(XrdCl::QueryCode::OpaqueFile, arg,\n                                          response);\n\n    if (response) {\n      delete response;\n    }\n\n    int rc = !status.IsOK();\n\n    if (!is_silent && verbose) {\n      std::cerr << \"[ mtime                 ] : path:\" << \"[utime] path: \" <<\n                mtpath.c_str() << \" retc: \" << rc << std::endl;\n    }\n\n    return rc;\n  }\n}\n\neos::common::CopyProcess copyProcess;\nstd::vector<XrdCl::PropertyList*> tprops;\n\nXrdCl::PropertyList* copyFile(const std::string& i, eos::common::Path& src,\n                              eos::common::Path& dst, struct timespec mtime)\n{\n  XrdCl::PropertyList props;\n  XrdCl::PropertyList* result = new XrdCl::PropertyList();\n  std::string srcurl = std::string(src.GetFullPath().c_str()) + i;\n  std::string dsturl = std::string(dst.GetFullPath().c_str()) + i;\n\n  if (srcurl.substr(0, 5) == \"/eos/\") {\n    XrdCl::URL surl(serveruri.c_str());\n    surl.SetPath(srcurl);\n    srcurl = surl.GetURL();\n  }\n\n  if (dsturl.substr(0, 5) == \"/eos/\") {\n    XrdCl::URL durl(serveruri.c_str());\n    durl.SetPath(dsturl);\n    XrdCl::URL::ParamsMap params;\n    params[\"eos.mtime\"] = eos::common::Timing::TimespecToString(mtime);\n    durl.SetParams(params);\n    dsturl = durl.GetURL();\n  } else {\n    XrdCl::URL durl(dsturl);\n    XrdCl::URL::ParamsMap params;\n    params[\"local.mtime\"] = eos::common::Timing::TimespecToString(mtime);\n    durl.SetParams(params);\n    dsturl = durl.GetURL();\n  }\n\n  props.Set(\"source\", srcurl);\n  props.Set(\"target\", dsturl);\n  props.Set(\"force\", true); // allows overwrite\n\n  result->Set(\"source\", srcurl);\n  result->Set(\"target\", dsturl);\n\n  if (verbose) {\n    std::cout << \"[ copy file             ] : srcurl: \" << srcurl << \" dsturl: \" <<\n              dsturl << std::endl;\n  }\n\n  copyProcess.AddJob(props, result);\n  return result;\n}\n\nstd::string parent(const std::string& path)\n{\n  std::filesystem::path p(path);\n  return p.parent_path();\n}\n\n\nstd::optional<bool> parent_newer(std::map<std::string, fs_entry>& a,\n                                 std::map<std::string, fs_entry>& b, const std::string& path)\n{\n  // checks if the parent mtime of b is newer than parent mtime of a !\n  std::string p_path = parent(path);\n\n  if (!a.count(path) || !b.count(path) ||\n      !a.count(p_path) || !b.count(p_path)) {\n    return {};\n  }\n\n  if ((b[p_path].mtime.tv_sec == a[p_path].mtime.tv_sec) &&\n      (b[p_path].mtime.tv_nsec == a[p_path].mtime.tv_nsec)) {\n    return {};\n  }\n\n  if (b[p_path].newer(a[p_path].mtime)) {\n    return true;\n  } else {\n    return false;\n  }\n}\n\n\n/** Core rclone implementation */\nstatic int rclone_impl(const RcloneOptions& opts)\n{\n  const std::string& cmd = opts.subcommand;\n  eos::common::Path srcPath(opts.src.c_str());\n  eos::common::Path dstPath(opts.dst.c_str());\n  XrdOucString src = srcPath.GetFullPath();\n  XrdOucString dst = dstPath.GetFullPath();\n\n  // Set globals from opts\n  dryrun = opts.dryrun;\n  noreplace = opts.noreplace;\n  nodelete = !opts.delete_opt;\n  verbose = opts.verbose;\n  is_silent = opts.silent;\n  filter_atomic = !opts.atomic;\n  filter_versions = !opts.versions;\n  filter_hidden = !opts.hidden;\n\n  std::set<std::string> target_create_dirs;\n  std::set<std::string> target_delete_dirs;\n  std::set<std::string> target_mtime_dirs;\n  std::set<std::string> target_create_files;\n  std::set<std::string> target_delete_files;\n  std::set<std::string> target_updated_files;\n  std::set<std::string> target_mismatch_files;\n  std::set<std::string> target_create_links;\n  std::set<std::string> target_delete_links;\n  std::set<std::string> target_updated_links;\n  std::set<std::string> target_mismatch_links;\n  std::set<std::string> source_create_dirs;\n  std::set<std::string> source_delete_dirs;\n  std::set<std::string> source_mtime_dirs;\n  std::set<std::string> source_create_files;\n  std::set<std::string> source_delete_files;\n  std::set<std::string> source_updated_files;\n  std::set<std::string> source_mismatch_files;\n  std::set<std::string> source_create_links;\n  std::set<std::string> source_delete_links;\n  std::set<std::string> source_updated_links;\n  std::set<std::string> source_mismatch_links;\n  std::set<std::string> cp_target_files;\n  std::set<std::string> cp_source_files;\n  uint64_t copySize = 0;\n  uint64_t copyTransactions = 0;\n  enum eActions {\n    kTargetDirCreate, kSourceDirCreate,\n    kTargetDirDelete, kSourceDirDelete,\n    kTargetFileCreate, kSourceFileCreate,\n    kTargetFileUpdate, kSourceFileUpdate,\n    kTargetFileDelete, kSourceFileDelete,\n    kTargetFileMismatch, kSourceFileMismatch,\n    kTargetLinkCreate, kSourceLinkCreate,\n    kTargetLinkUpdate, kSourceLinkUpdate,\n    kTargetLinkDelete, kSourceLinkDelete,\n    kTargetLinkMismatch, kSourceLinkMismatch,\n    kTargetDirMtime, kSourceDirMtime\n  };\n  std::vector<eActions> actions;\n\n  if (cmd == \"copy\") {\n    actions.push_back(kTargetDirCreate);\n    actions.push_back(kTargetFileCreate);\n\n    if (!noreplace) {\n      actions.push_back(kTargetFileUpdate);\n    }\n\n    actions.push_back(kTargetFileMismatch);\n    actions.push_back(kTargetLinkCreate);\n\n    if (!noreplace) {\n      actions.push_back(kTargetLinkUpdate);\n    }\n\n    actions.push_back(kTargetLinkMismatch);\n\n    if (!nodelete) {\n      actions.push_back(kTargetLinkDelete);\n      actions.push_back(kTargetFileDelete);\n      actions.push_back(kTargetDirDelete);\n    }\n\n    actions.push_back(kTargetDirMtime);\n  } else if (cmd == \"sync\") {\n    actions.push_back(kTargetDirCreate);\n    actions.push_back(kTargetFileCreate);\n\n    if (!noreplace) {\n      actions.push_back(kTargetFileUpdate);\n    }\n\n    actions.push_back(kTargetFileMismatch);\n    actions.push_back(kTargetLinkCreate);\n\n    if (!noreplace) {\n      actions.push_back(kTargetLinkUpdate);\n    }\n\n    actions.push_back(kTargetLinkMismatch);\n    // we cannot detect two-way deletion without history\n    // if (!nodelete) {\n    //   actions.push_back(kTargetLinkDelete);\n    //   actions.push_back(kTargetFileDelete);\n    //   actions.push_back(kTargetDirDelete);\n    // }\n    actions.push_back(kTargetDirMtime);\n    actions.push_back(kSourceDirCreate);\n    actions.push_back(kSourceFileCreate);\n    actions.push_back(kSourceFileUpdate);\n    //    actions.push_back(kSourceFileMismatch);\n    actions.push_back(kSourceLinkCreate);\n\n    if (!noreplace) {\n      actions.push_back(kSourceLinkUpdate);\n    }\n\n    //    actions.push_back(kSourceLinkMismatch);\n    // we cannot detec two-way deletion without historya\n    // if (!nodelete) {\n    //   actions.push_back(kSourceLinkDelete);\n    //   actions.push_back(kSourceFileDelete);\n    //   actions.push_back(kSourceDirDelete);\n    // }\n    actions.push_back(kSourceDirMtime);\n  } else {\n    rclone_usage();\n  }\n\n  if (!src.length() || !dst.length()) {\n    rclone_usage();\n  }\n\n  fs_result srcmap;\n  fs_result dstmap;\n  bool ignore_errors = false;\n\n  if (src.beginswith(\"/eos/\")) {\n    // get the sync informtion using newfind\n    srcmap = eos_find(src.c_str());\n  } else {\n    // travers using UNIX find\n    srcmap = fs_find(src.c_str());\n  }\n\n  if (dst.beginswith(\"/eos/\")) {\n    // get the sync information using newfind\n    dstmap = eos_find(dst.c_str());\n  } else {\n    // travers using UNIX find\n    dstmap = fs_find(dst.c_str());\n  }\n\n  srcmap.directories.erase(\"/\");\n  dstmap.directories.erase(\"/\");\n\n  // forward comparison\n  for (auto d : srcmap.directories) {\n    if (!dstmap.directories.count(d.first)) {\n      if (!is_silent && verbose) {\n        std::cout << \"[ target folder missing ] : \" << d.first << std::endl;\n      }\n\n      target_create_dirs.insert(d.first);\n      target_mtime_dirs.insert(d.first);\n    } else {\n      if (dstmap.directories[d.first].newer(srcmap.directories[d.first].mtime)) {\n        target_mtime_dirs.insert(d.first);\n      }\n    }\n  }\n\n  /// backward\n  for (auto d : dstmap.directories) {\n    if (!srcmap.directories.count(d.first)) {\n      if (!is_silent && verbose) {\n        std::cout << \"[ source folder missing ] : \" << d.first << std::endl;\n      }\n\n      if (!nodelete) {\n        target_delete_dirs.insert(d.first);\n        target_mtime_dirs.insert(parent(d.first));\n      } else {\n        source_create_dirs.insert(d.first);\n        source_mtime_dirs.insert(d.first);\n      }\n    } else {\n      if (srcmap.directories[d.first].newer(dstmap.directories[d.first].mtime)) {\n        source_mtime_dirs.insert(d.first);\n      }\n    }\n  }\n\n  // forward comparison\n  for (auto d : srcmap.files) {\n    if (!dstmap.files.count(d.first)) {\n      if (!is_silent && verbose) {\n        std::cout << \"[ target file   missing ] : \" << d.first << std::endl;\n      }\n\n      target_create_files.insert(d.first);\n      copySize += d.second.size;\n      copyTransactions++;\n    } else {\n      if (dstmap.files[d.first].newer(srcmap.files[d.first].mtime)) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ target file   older   ] : \" << d.first << std::endl;\n        }\n\n        target_updated_files.insert(d.first);\n\n        if (!noreplace) {\n          copySize += d.second.size;\n          copyTransactions++;\n        }\n      } else {\n        if (dstmap.files[d.first].size != srcmap.files[d.first].size) {\n          if (!is_silent && verbose) {\n            std::cout << \"[ target file diff size ] : \" << d.first << std::endl;\n          }\n\n          target_mismatch_files.insert(d.first);\n        }\n      }\n    }\n  }\n\n  // backward comparison\n  for (auto d : dstmap.files) {\n    if (!srcmap.files.count(d.first)) {\n      if (!is_silent && verbose) {\n        std::cout << \"[ source file   missing ] : \" << d.first << std::endl;\n      }\n\n      if (!nodelete) {\n        target_delete_files.insert(d.first);\n      } else {\n        source_create_files.insert(d.first);\n        copySize += d.second.size;\n        copyTransactions++;\n      }\n    } else {\n      if (srcmap.files[d.first].newer(dstmap.files[d.first].mtime)) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ source file   older   ] : \" << d.first << std::endl;\n        }\n\n        source_updated_files.insert(d.first);\n\n        if (!noreplace) {\n          copySize += d.second.size;\n          copyTransactions++;\n        }\n      } else {\n        if (dstmap.files[d.first].size != srcmap.files[d.first].size) {\n          if (!is_silent && verbose) {\n            std::cout << \"[ source file diff size ] : \" << d.first << std::endl;\n          }\n\n          source_mismatch_files.insert(d.first);\n        }\n      }\n    }\n  }\n\n  // forward comparison\n  for (auto d : srcmap.links) {\n    if (!dstmap.links.count(d.first)) {\n      if (!is_silent && verbose) {\n        std::cout << \"[ target link   missing ] : \" << d.first << std::endl;\n      }\n\n      target_create_links.insert(d.first);\n    } else {\n      if (dstmap.links[d.first].newer(srcmap.links[d.first].mtime)) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ target link   older   ] : \" << d.first << std::endl;\n        }\n\n        target_updated_links.insert(d.first);\n      } else {\n        if (dstmap.links[d.first].target != srcmap.links[d.first].target) {\n          if (!is_silent && verbose) {\n            std::cout << \"[ target link diff size ] : \" << d.first << std::endl;\n          }\n\n          target_mismatch_links.insert(d.first);\n        }\n      }\n    }\n  }\n\n  // backward comparison\n  for (auto d : dstmap.links) {\n    if (!srcmap.links.count(d.first)) {\n      if (!is_silent && verbose) {\n        std::cout << \"[ source link   missing ] : \" << d.first << std::endl;\n      }\n\n      if (!nodelete) {\n        target_delete_links.insert(d.first);\n      } else {\n        source_create_links.insert(d.first);\n      }\n    } else {\n      if (srcmap.links[d.first].newer(dstmap.links[d.first].mtime)) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ source link   older   ] : \" << d.first << std::endl;\n        }\n\n        source_updated_links.insert(d.first);\n      } else {\n        if (dstmap.links[d.first].target != srcmap.links[d.first].target) {\n          if (!is_silent && verbose) {\n            std::cout << \"[ source link diff size ] : \" << d.first << std::endl;\n          }\n\n          source_mismatch_links.insert(d.first);\n        }\n      }\n    }\n  }\n\n  if (!is_silent) {\n    std::cout << \"[ --------------------------- ]\" << std::endl;\n    std::cout << \"[ EOS remote sync tool (beta) ]\" << std::endl;\n  }\n\n  if (!dryrun) {\n    if (!is_silent) {\n      std::cout << \"[ --------------------------- ]\" << std::endl;\n      std::cout << \"[ target                      ]\" << std::endl;\n      std::cout << \"[ --------------------------- ]\" << std::endl;\n      std::cout << \"[ # dir,files,links to create ] : \" << target_create_dirs.size()\n                << \",\" << target_create_files.size() << \",\" << target_create_links.size() <<\n                std::endl;\n      std::cout << \"[ # dir,files,links to delete ] : \" << target_delete_dirs.size()\n                << \",\" << target_delete_files.size() << \",\" << target_delete_links.size() <<\n                std::endl;\n      std::cout << \"[ # files,links to update     ] : \" << target_updated_files.size()\n                << \",\" << target_updated_links.size() << std::endl;\n      std::cout << \"[ # files,links mismatch      ] : \" <<\n                target_mismatch_files.size() << \",\" << target_mismatch_links.size() <<\n                std::endl;\n      std::cout << \"[ --------------------------- ]\" << std::endl;\n      std::cout << \"[ source                      ]\" << std::endl;\n      std::cout << \"[ --------------------------- ]\" << std::endl;\n      std::cout << \"[ # dir,files,links to create ] : \" << source_create_dirs.size()\n                << \",\" << source_create_files.size() << \",\" << source_create_links.size() <<\n                std::endl;\n      std::cout << \"[ # dir,files,links to delete ] : \" << source_delete_dirs.size()\n                << \",\" << source_delete_files.size() << \",\" << source_delete_links.size() <<\n                std::endl;\n      std::cout << \"[ # files,links to update     ] : \" << source_updated_files.size()\n                << \",\" << source_updated_links.size() << std::endl;\n      std::cout << \"[ # files,links mismatch      ] : \" <<\n                source_mismatch_files.size() << \",\" << source_mismatch_links.size() <<\n                std::endl;\n      std::cout << \"[ --------------------------- ]\" << std::endl;\n      std::cout << \"[ volume                      ]\" << std::endl;\n      std::cout << \"[ --------------------------- ]\" << std::endl;\n      XrdOucString sizestring;\n      eos::common::StringConversion::GetReadableSizeString(sizestring, copySize, \"B\");\n      std::cout << \"[ # data size                 ] : \" << sizestring.c_str() <<\n                std::endl;\n      eos::common::StringConversion::GetReadableSizeString(sizestring,\n          copyTransactions, \"\");\n      std::cout << \"[ # copy transactions         ] : \" << sizestring.c_str() <<\n                std::endl;\n      std::cout << \"[ --------------------------- ]\" << std::endl;\n    }\n  }\n\n  for (auto a : actions) {\n    if (a == kTargetDirCreate) {\n      for (auto i : target_create_dirs) {\n        int rc = createDir(i, dstPath);\n\n        if (rc && !ignore_errors) {\n          std::cerr << \"error: failed to create directory '\" << dstPath.GetFullPath() << i\n                    << \"'\" << std::endl;\n          exit(-1);\n        }\n      }\n    }\n\n    if (a == kSourceDirCreate) {\n      for (auto i : source_create_dirs) {\n        int rc = createDir(i, srcPath);\n\n        if (rc && !ignore_errors) {\n          std::cerr << \"error: failed to create directory '\" << dstPath.GetFullPath() << i\n                    << \"'\" << std::endl;\n          exit(-1);\n        }\n      }\n    }\n\n    if (a == kTargetDirDelete) {\n      for (auto i : target_delete_dirs) {\n        int rc = removeDir(i, dstPath);\n\n        if (rc && !ignore_errors) {\n          std::cerr << \"error: failed to remove directory '\" << dstPath.GetFullPath() << i\n                    << \"'\" << std::endl;\n          exit(-1);\n        }\n      }\n    }\n\n    if (a == kTargetFileDelete) {\n      for (auto i : target_delete_files) {\n        int rc = removeFile(i, dstPath);\n\n        if (rc && !ignore_errors) {\n          std::cerr << \"error: failed to remove file '\" << dstPath.GetFullPath() << i <<\n                    \"'\" << std::endl;\n          exit(-1);\n        }\n      }\n    }\n\n    if (a == kTargetLinkDelete) {\n      for (auto i : target_delete_links) {\n        int rc = removeFile(i, dstPath);\n\n        if (rc && !ignore_errors) {\n          std::cerr << \"error: failed to remove link '\" << dstPath.GetFullPath() << i <<\n                    \"'\" << std::endl;\n          exit(-1);\n        }\n      }\n    }\n\n    if (a == kSourceDirDelete) {\n      for (auto i : source_delete_dirs) {\n        int rc = removeDir(i, srcPath);\n\n        if (rc && !ignore_errors) {\n          std::cerr << \"error: failed to remove directory '\" << srcPath.GetFullPath() << i\n                    << \"'\" << std::endl;\n          exit(-1);\n        }\n      }\n    }\n\n    if (a == kSourceFileDelete) {\n      for (auto i : source_delete_files) {\n        int rc = removeFile(i, srcPath);\n\n        if (rc && !ignore_errors) {\n          std::cerr << \"error: failed to remove file '\" << srcPath.GetFullPath() << i <<\n                    \"'\" << std::endl;\n          exit(-1);\n        }\n      }\n    }\n\n    if (a == kSourceLinkDelete) {\n      for (auto i : source_delete_links) {\n        int rc = removeFile(i, srcPath);\n\n        if (rc && !ignore_errors) {\n          std::cerr << \"error: failed to remove link '\" << srcPath.GetFullPath() << i <<\n                    \"'\" << std::endl;\n          exit(-1);\n        }\n      }\n    }\n\n    if (a == kTargetFileCreate) {\n      for (auto i : target_create_files) {\n        cp_target_files.insert(i);\n      }\n    }\n\n    if (a == kTargetFileUpdate) {\n      for (auto i : target_updated_files) {\n        cp_target_files.insert(i);\n      }\n    }\n\n    if (a == kTargetFileMismatch) {\n      for (auto i : target_mismatch_files) {\n        cp_target_files.insert(i);\n      }\n    }\n\n    if (a == kTargetLinkCreate) {\n      for (auto i : target_create_links) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ link  ] create link \" << i.c_str() << \" => \" <<\n                    srcmap.links[i].target.c_str() << std::endl;\n        }\n\n        if (!dryrun) {\n          int rc = createLink(i, dstPath, srcmap.links[i].target, srcPath,\n                              srcmap.links[i].mtime);\n\n          if (rc && !ignore_errors) {\n            std::cerr << \"error: failed to create link '\" << dstPath.GetFullPath() << i <<\n                      \"'\" << std::endl;\n            exit(-1);\n          }\n        }\n      }\n    }\n\n    if (a == kTargetLinkUpdate) {\n      for (auto i : target_updated_links) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ link  ] update link \" << i.c_str() << \" => \" <<\n                    srcmap.links[i].target.c_str() << std::endl;\n        }\n\n        if (!dryrun) {\n          int rc = removeFile(i, dstPath);\n          rc |= createLink(i, dstPath, srcmap.links[i].target, srcPath,\n                           srcmap.links[i].mtime);\n\n          if (rc && !ignore_errors) {\n            std::cerr << \"error: failed to update link '\" << dstPath.GetFullPath() << i <<\n                      \"'\" << std::endl;\n            exit(-1);\n          }\n        }\n      }\n    }\n\n    if (a == kTargetLinkMismatch) {\n      for (auto i : target_mismatch_links) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ link  ] remove link \" << i.c_str() << std::endl;\n        }\n\n        if (!dryrun) {\n          int rc = removeFile(i, dstPath);\n          rc |= createLink(i, dstPath, srcmap.links[i].target, srcPath,\n                           srcmap.links[i].mtime);\n\n          if (rc && !ignore_errors) {\n            std::cerr << \"error: failed to update mismatching link '\" <<\n                      dstPath.GetFullPath() << i << \"'\" << std::endl;\n            exit(-1);\n          }\n        }\n      }\n    }\n\n    if (a == kSourceFileCreate) {\n      for (auto i : source_create_files) {\n        cp_source_files.insert(i);\n      }\n    }\n\n    if (a == kSourceFileUpdate) {\n      for (auto i : source_updated_files) {\n        cp_source_files.insert(i);\n      }\n    }\n\n    if (a == kSourceFileMismatch) {\n      for (auto i : source_mismatch_files) {\n        cp_source_files.insert(i);\n      }\n    }\n\n    if (a == kSourceLinkCreate) {\n      for (auto i : source_create_links) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ link  ] create link \" << i.c_str() << \" => \" <<\n                    dstmap.links[i].target.c_str() << std::endl;\n        }\n\n        if (!dryrun) {\n          int rc = createLink(i, srcPath, dstmap.links[i].target, dstPath,\n                              dstmap.links[i].mtime);\n\n          if (rc && !ignore_errors) {\n            std::cerr << \"error: failed to create link '\" << srcPath.GetFullPath() << i <<\n                      \"'\" << std::endl;\n            exit(-1);\n          }\n        }\n      }\n    }\n\n    if (a == kSourceLinkUpdate) {\n      for (auto i : source_updated_links) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ link  ] update link \" <<  i.c_str() <<\n                    dstmap.links[i].target.c_str() << std::endl;\n        }\n\n        if (!dryrun) {\n          int rc = removeFile(i, srcPath);\n          rc |= createLink(i, srcPath, dstmap.links[i].target, dstPath,\n                           dstmap.links[i].mtime);\n\n          if (rc && !ignore_errors) {\n            std::cerr << \"error: failed to update link '\" << srcPath.GetFullPath() << i <<\n                      \"'\" << std::endl;\n            exit(-1);\n          }\n        }\n      }\n    }\n\n    if (a == kSourceLinkMismatch) {\n      for (auto i : source_mismatch_links) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ link  ]remove link \" << i.c_str() << std::endl;\n        }\n\n        if (!dryrun) {\n          int rc = removeFile(i, srcPath);\n          rc |= createLink(i, srcPath, dstmap.links[i].target, dstPath,\n                           dstmap.links[i].mtime);\n\n          if (rc && !ignore_errors) {\n            std::cerr << \"error: failed to update mismatching link '\" <<\n                      srcPath.GetFullPath() << i << \"'\" << std::endl;\n            exit(-1);\n          }\n        }\n      }\n    }\n  }\n\n  for (auto i : cp_target_files) {\n    if (!dryrun) {\n      tprops.push_back(copyFile(i, srcPath, dstPath, srcmap.files[i].mtime));\n    } else {\n      if (!is_silent && verbose) {\n        std::cout << \"[ copy ] : \" << i.c_str() << \" \" << srcPath.GetFullPath().c_str()\n                  << \" => \" << dstPath.GetFullPath().c_str() << std::endl;\n      }\n    }\n  }\n\n  for (auto i : cp_source_files) {\n    if (!dryrun) {\n      tprops.push_back(copyFile(i, dstPath, srcPath, dstmap.files[i].mtime));\n    } else {\n      if (!is_silent && verbose) {\n        std::cout << \"[ copy ] : \" << i.c_str() << \" \" << dstPath.GetFullPath().c_str()\n                  << \" => \" << srcPath.GetFullPath().c_str() << std::endl;\n      }\n    }\n  }\n\n  class RCloneProgressHandler : public XrdCl::CopyProgressHandler\n  {\n  public:\n    RCloneProgressHandler() {\n      ext_tot = 0;\n      bp = bt = 0;\n      n = 0;\n    }\n\n    virtual void BeginJob(uint16_t   jobNum,\n                          uint16_t   jobTotal,\n                          const URL* source,\n                          const URL* destination)\n    {\n      if (!n) {\n\t// do this only once\n\tn = jobNum;\n      }\n    }\n\n    void Output() {\n      if (verbose) {\n        std::cerr << \"[ \" << n << \"/\" << ext_tot << \" ] files copied\" << std::endl;\n      } else {\n        if (!is_silent) {\n          std::cerr << \"[ \" << n << \"/\" << ext_tot << \" ] files copied\" << \"\\r\";\n        }\n      }\n    }\n\n\n    virtual void EndJob(uint16_t            jobNum,\n                        const PropertyList* result)\n    {\n      (void)jobNum;\n      (void)result;\n      std::string src;\n      std::string dst;\n      result->Get(\"source\", src);\n      result->Get(\"target\", dst);\n      XrdCl::URL durl(dst.c_str());\n      auto param = durl.GetParams();\n\n      if (param.count(\"local.mtime\")) {\n        // apply mtime changes when done to local files\n        struct timespec ts;\n        std::string tss = param[\"local.mtime\"];\n\n        if (!eos::common::Timing::Timespec_from_TimespecStr(tss, ts)) {\n          // apply local mtime;\n          struct timespec times[2];\n          times[0] = ts;\n          times[1] = ts;\n\n          if (utimensat(0, durl.GetPath().c_str(), times, AT_SYMLINK_NOFOLLOW)) {\n            std::cerr << \"error: failed to update modification time of '\" << durl.GetPath()\n                      << \"'\" << std::endl;\n          }\n        }\n      }\n      n++;\n      Output();\n    };\n\n    virtual void JobProgress(uint16_t jobNum,\n                             uint64_t bytesProcessed,\n                             uint64_t bytesTotal)\n    {\n      // we don't want output here\n    }\n\n    virtual bool ShouldCancel(uint16_t jobNum)\n    {\n      (void)jobNum;\n      return false;\n    }\n\n    std::atomic<uint64_t> bp;\n    std::atomic<uint64_t> bt;\n    std::atomic<uint16_t> n;\n    std::atomic<uint64_t> ext_tot;\n  };\n\n  RCloneProgressHandler copyProgress;\n  copyProgress.ext_tot = copyProcess.Jobs();\n\n  if (!is_silent && verbose) {\n    std::cerr << \"# preparing\" << std::endl;\n  }\n  PropertyList processConfig;\n  processConfig.Set( \"jobType\", \"configuration\" );\n  processConfig.Set( \"parallel\", 64 );\n  copyProcess.AddJob( processConfig, 0 );\n\n  copyProcess.Prepare();\n\n  if (!is_silent && verbose) {\n    std::cerr << \"# running\" << std::endl;\n  }\n\n  copyProcess.Run(&copyProgress);\n\n  if (!is_silent) {\n    std::cout << std::endl;\n  }\n\n  // last step is to adjust directory mtimes\n  for (auto a : actions) {\n    if (a == kTargetDirMtime) {\n      for (auto i : target_mtime_dirs) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ mtime ] updating target mtime \" << i << std::endl;\n        }\n\n        if (!dryrun) {\n          int rc = setDirMtime(i, dstPath, srcmap.directories[i].mtime);\n\n          if (rc && !ignore_errors) {\n            std::cerr << \"error: failed to update directory mtime  '\" <<\n                      dstPath.GetFullPath() << i << \"'\" << std::endl;\n            exit(-1);\n          }\n        }\n      }\n    }\n\n    if (a == kSourceDirMtime) {\n      for (auto i : source_mtime_dirs) {\n        if (!is_silent && verbose) {\n          std::cout << \"[ mtime ] updating source mtime \"  << i << std::endl;\n        }\n\n        if (!dryrun) {\n          int rc = setDirMtime(i, srcPath, dstmap.directories[i].mtime);\n\n          if (rc && !ignore_errors) {\n            std::cerr << \"error: failed to update directory mtime  '\" <<\n                      srcPath.GetFullPath() << i << \"'\" << std::endl;\n            exit(-1);\n          }\n        }\n      }\n    }\n  }\n\n  if (dryrun && !is_silent) {\n    std::cout << \"[ --------------------------- ]\" << std::endl;\n    std::cout << \"[ target                      ]\" << std::endl;\n    std::cout << \"[ --------------------------- ]\" << std::endl;\n    std::cout << \"[ # dir,files,links to create ] : \" << target_create_dirs.size()\n              << \",\" << target_create_files.size() << \",\" << target_create_links.size() <<\n              std::endl;\n    std::cout << \"[ # dir,files,links to delete ] : \" << target_delete_dirs.size()\n              << \",\" << target_delete_files.size() << \",\" << target_delete_links.size() <<\n              std::endl;\n    std::cout << \"[ # files,links to update     ] : \" << target_updated_files.size()\n              << \",\" << target_updated_links.size() << std::endl;\n    std::cout << \"[ # files,links mismatch      ] : \" <<\n              target_mismatch_files.size() << \",\" << target_mismatch_links.size() <<\n              std::endl;\n    std::cout << \"[ --------------------------- ]\" << std::endl;\n    std::cout << \"[ source                      ]\" << std::endl;\n    std::cout << \"[ --------------------------- ]\" << std::endl;\n    std::cout << \"[ # dir,files,links to create ] : \" << source_create_dirs.size()\n              << \",\" << source_create_files.size() << \",\" << source_create_links.size() <<\n              std::endl;\n    std::cout << \"[ # dir,files,links to delete ] : \" << source_delete_dirs.size()\n              << \",\" << source_delete_files.size() << \",\" << source_delete_links.size() <<\n              std::endl;\n    std::cout << \"[ # files,links to update     ] : \" << source_updated_files.size()\n              << \",\" << source_updated_links.size() << std::endl;\n    std::cout << \"[ # files,links mismatch      ] : \" <<\n              source_mismatch_files.size() << \",\" << source_mismatch_links.size() <<\n              std::endl;\n    std::cout << \"[ --------------------------- ]\" << std::endl;\n    std::cout << \"[ volume                      ]\" << std::endl;\n    std::cout << \"[ --------------------------- ]\" << std::endl;\n    XrdOucString sizestring;\n    eos::common::StringConversion::GetReadableSizeString(sizestring, copySize, \"B\");\n    std::cout << \"[ # data size                 ] : \" << sizestring.c_str() <<\n              std::endl;\n    eos::common::StringConversion::GetReadableSizeString(sizestring,\n        copyTransactions, \"\");\n    std::cout << \"[ # copy transactions         ] : \" << sizestring.c_str() <<\n              std::endl;\n    std::cout << \"[ --------------------------- ]\" << std::endl;\n  }\n\n  return 0;\n}\n\n/* Entry point for legacy callers */\nint com_rclone(char* arg1)\n{\n  std::vector<std::string> args = TokenizeRcloneArgs(arg1);\n  RcloneOptions opts;\n  if (!ParseRcloneArgs(args, opts))\n    return -1;\n  return rclone_impl(opts);\n}\n\n// ----------------------------------------------------------------------------\n// Native command registration\n// ----------------------------------------------------------------------------\nnamespace {\nclass RcloneCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"rclone\";\n  }\n  const char*\n  description() const override\n  {\n    return \"RClone-like copy/sync for EOS\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    if (args.empty() || wants_help(args[0].c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    RcloneOptions opts;\n    if (!ParseRcloneArgs(args, opts)) {\n      global_retc = EINVAL;\n      return 0;\n    }\n    return rclone_impl(opts);\n  }\n  void\n  printHelp() const override\n  {\n    std::cerr << MakeRcloneHelp();\n  }\n};\n} // namespace\n\nvoid\nRegisterRcloneNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<RcloneCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/reconnect-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: reconnect-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <memory>\n#include <sstream>\n\nnamespace {\nclass ReconnectCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"reconnect\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Reconnect to MGM\";\n  }\n  bool\n  requiresMgm(const std::string&) const override\n  {\n    return false;\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    std::string proto = args.empty() ? std::string() : args[0];\n    if (!proto.empty() && proto != \"gsi\" && proto != \"krb5\" &&\n        proto != \"unix\" && proto != \"sss\") {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    if (!proto.empty()) {\n      fprintf(stderr, \"# reconnecting to %s with <%s> authentication\\n\",\n              serveruri.c_str(), proto.c_str());\n      setenv(\"XrdSecPROTOCOL\", proto.c_str(), 1);\n    } else {\n      fprintf(stderr, \"# reconnecting to %s\\n\", serveruri.c_str());\n    }\n    XrdOucString path = serveruri;\n    path += \"//proc/admin/\"; // nudge connection\n    global_retc = 0;\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"Usage: reconnect [gsi,krb5,unix,sss] : reconnect to the \"\n                    \"management node [using the specified protocol]\\n\");\n  }\n};\n} // namespace\n\nvoid\nRegisterReconnectNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<ReconnectCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/recycle-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: recycle-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include \"console/commands/helpers/RecycleHelper.hh\"\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeRecycleHelp()\n{\n  return \"Usage: recycle [ls|purge|restore|config|project] [OPTIONS]\\n\\n\"\n         \"  [-m]              print status of recycle bin\\n\"\n         \"  ls [<date> [<limit>]] [-m] [-n] [--all] [--uid] [--rid <val>]\\n\"\n         \"    list files in the recycle bin\\n\"\n         \"  purge [--all] [--uid] [--rid <val>] <date> | -k <key>\\n\"\n         \"    purge files by date or by key\\n\"\n         \"  restore [-p] [-f|--force-original-name] [-r|--restore-versions] <key>\\n\"\n         \"    undo deletion identified by recycle key\\n\"\n         \"  project --path <path> [--acl <val>]\\n\"\n         \"    setup recycle id for given top level directory\\n\"\n         \"  config <key> <value>\\n\"\n         \"    configure recycle policy (--dump, --add-bin, --remove-bin, \"\n         \"--enable, etc.)\\n\";\n}\n\nvoid ConfigureRecycleApp(CLI::App& app)\n{\n  app.name(\"recycle\");\n  app.description(\"Recycle Bin Functionality\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeRecycleHelp();\n      }));\n}\n\nclass RecycleProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"recycle\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Recycle Bin Functionality\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    RecycleHelper recycle(gGlobalOpts);\n    if (!recycle.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    global_retc = recycle.Execute(true, true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureRecycleApp(app);\n    fprintf(stderr, \"%s\", app.help().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterRecycleProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<RecycleProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/register-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: register-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeRegisterHelp()\n{\n  return \"Usage: register [-u] <path> [tag1=val1 tag2=val2 ...]\\n\\n\"\n         \"  -u  update existing file metadata (if file exists)\\n\\n\"\n         \"Tags: size=100, uid=101|username=foo, gid=102|groupname=bar,\\n\"\n         \"  checksum=..., layoutid=..., location=1,2,..., mode=777,\\n\"\n         \"  btime=..., atime=..., ctime=..., mtime=..., path=...,\\n\"\n         \"  xattr=..., attr=\\\"sys.acl=u:100:rwx\\\", atimeifnewer=...\\n\";\n}\n\nvoid ConfigureRegisterApp(CLI::App& app)\n{\n  app.name(\"register\");\n  app.description(\"Register a file\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeRegisterHelp();\n      }));\n}\n\nclass RegisterProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"register\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Register a file\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    // Build mgm request from tags\n    // (uid/gid/size/path/xattr/ctime/mtime/atime/atimeifnewer/mode/location/layoutid/checksum)\n    XrdOucString in = \"mgm.cmd=register\";\n    bool update = false;\n    std::vector<std::string> tags;\n    for (const auto& a : args) {\n      if (a == \"-u\") {\n        update = true;\n        continue;\n      }\n      tags.push_back(a);\n    }\n    if (update)\n      in += \"&mgm.update=1\";\n    for (const auto& t : tags) {\n      size_t eq = t.find('=');\n      if (eq == std::string::npos) {\n        in += \"&mgm.path=\";\n        in += abspath(t.c_str());\n        continue;\n      }\n      std::string k = t.substr(0, eq), v = t.substr(eq + 1);\n      if (k == \"uid\" || k == \"username\") {\n        in += \"&mgm.owner.\";\n        in += k.c_str();\n        in += \"=\";\n        in += v.c_str();\n      } else if (k == \"gid\" || k == \"groupname\") {\n        in += \"&mgm.owner.\";\n        in += k.c_str();\n        in += \"=\";\n        in += v.c_str();\n      } else if (k == \"size\" || k == \"mode\" || k == \"layoutid\" ||\n                 k == \"checksum\") {\n        in += \"&mgm.\";\n        in += k.c_str();\n        in += \"=\";\n        in += v.c_str();\n      } else if (k == \"location\") {\n        in += \"&mgm.location=\";\n        in += v.c_str();\n      } else if (k == \"path\") {\n        in += \"&mgm.path=\";\n        in += abspath(v.c_str());\n      } else if (k == \"xattr\") {\n        in += \"&mgm.xattr=\";\n        in += v.c_str();\n      } else if (k == \"ctime\" || k == \"mtime\" || k == \"btime\" || k == \"atime\") {\n        in += \"&mgm.\";\n        in += k.c_str();\n        in += \"=\";\n        in += v.c_str();\n      } else if (k == \"atimeifnewer\") {\n        in += \"&mgm.atime=\";\n        in += v.c_str();\n        in += \"&mgm.atimeifnewer=1\";\n      } else { /* ignore unknown */\n      }\n    }\n    global_retc = ctx.outputResult(ctx.clientCommand(in, true, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureRegisterApp(app);\n    fprintf(stderr, \"%s\", app.help().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterRegisterProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<RegisterProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/report-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: report-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/Path.hh\"\n#include \"common/Statistics.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <fcntl.h>\n#include <json/json.h>\n#include <regex.h>\n#include <set>\n#include <sstream>\n#include <unistd.h>\n\nnamespace {\nclass ReportCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"report\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Generate cluster report\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    eos::common::StringTokenizer subtokenizer(joined.c_str());\n    subtokenizer.GetLine();\n    XrdOucString arg;\n    XrdOucString path;\n    std::string sregex;\n    size_t max_reports = 2000000000;\n    bool silent = false;\n    XrdOucString squash;\n    time_t start_time = 0;\n    time_t stop_time = 0;\n    time_t first_ts = 0;\n    time_t last_ts = 0;\n    double max_eff = 100.0;\n    bool reading = false;\n    bool writing = false;\n    bool json = false;\n    Json::Value gjson;\n    do {\n      arg = subtokenizer.GetToken();\n      if (!arg.length() && path.length())\n        break;\n      if (arg == \"--regex\") {\n        arg = subtokenizer.GetToken();\n        if (!arg.length()) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        } else {\n          sregex = arg.c_str();\n        }\n        continue;\n      }\n      if (arg == \"-n\") {\n        arg = subtokenizer.GetToken();\n        if (!arg.length()) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        } else {\n          max_reports = std::strtoul(arg.c_str(), 0, 10);\n        }\n        continue;\n      }\n      if (arg == \"--read\") {\n        reading = true;\n        continue;\n      }\n      if (arg == \"--write\") {\n        writing = true;\n        continue;\n      }\n      if (arg == \"--json\") {\n        json = true;\n        continue;\n      }\n      if (arg == \"--max-efficiency\") {\n        arg = subtokenizer.GetToken();\n        max_eff = strtod(arg.c_str(), 0);\n        if ((max_eff < 0) || (max_eff > 100)) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        continue;\n      }\n      if (arg == \"--squash\") {\n        arg = subtokenizer.GetToken();\n        if (!arg.beginswith(\"/\") && !arg.endswith(\"/\")) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        squash = arg;\n        continue;\n      }\n      if (arg == \"--start\") {\n        arg = subtokenizer.GetToken();\n        if (!arg.length()) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        } else {\n          start_time = std::strtoul(arg.c_str(), 0, 10);\n        }\n        continue;\n      }\n      if (arg == \"--stop\") {\n        arg = subtokenizer.GetToken();\n        if (!arg.length()) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        } else {\n          stop_time = std::strtoul(arg.c_str(), 0, 10);\n        }\n        continue;\n      }\n      if (arg == \"-s\") {\n        silent = true;\n        continue;\n      }\n      if ((!arg.length()) || (arg.beginswith(\"--help\")) ||\n          (arg.beginswith(\"-h\"))) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      path = arg;\n    } while (arg.length());\n    if (!reading && !writing) {\n      reading = writing = true;\n    }\n    {\n      std::string reportfile = path.c_str();\n      std::ifstream file(reportfile);\n      if (file.is_open()) {\n        std::map<std::string, std::string> map;\n        std::string line;\n        std::vector<std::string> keys;\n        std::multiset<float> r_t;\n        std::multiset<float> w_t;\n        uint64_t sum_w, sum_r;\n        size_t n_w = 0;\n        size_t n_r = 0;\n        double reff = 0;\n        double srleff = 0;\n        double weff = 0;\n        double swleff = 0;\n        sum_w = sum_r = 0;\n        std::string sizestring;\n        size_t n_reports = 0;\n        regex_t regex;\n        if (sregex.length()) {\n          int regexErrorCode = regcomp(&regex, sregex.c_str(), REG_EXTENDED);\n          if (regexErrorCode) {\n            fprintf(stderr,\n                    \"error: regular expression is invalid regex-rc=%d\\n\",\n                    regexErrorCode);\n            global_retc = EINVAL;\n            return 0;\n          }\n        }\n        while (std::getline(file, line)) {\n          if (sregex.length()) {\n            int result = regexec(&regex, line.c_str(), 0, NULL, 0);\n            if (result == REG_NOMATCH) {\n              continue;\n            } else if (result != 0) {\n              fprintf(stderr, \"error: invalid regex\\n\");\n              global_retc = EINVAL;\n              return 0;\n            }\n          }\n          std::map<std::string, std::string> map;\n          if (eos::common::StringConversion::GetSpecialKeyValueMap(\n                  line.c_str(), map, \"=\", \"&\", &keys)) {\n            if (!sregex.length() && map[\"td\"].substr(0, 6) == \"daemon\") {\n              continue;\n            }\n            if (!map.count(\"rb\") && !map.count(\"wb\")) {\n              continue;\n            }\n            if (map[\"sec.app\"] == \"deletion\") {\n              continue;\n            }\n            bool found = false;\n            time_t start_ots = std::stoul(map[\"ots\"]);\n            time_t start_cts = std::stoul(map[\"cts\"]);\n            if (start_time) {\n              if (start_ots < start_time) {\n                continue;\n              }\n            }\n            if (stop_time) {\n              if (start_ots > stop_time) {\n                continue;\n              }\n            }\n            if (!first_ts) {\n              first_ts = start_ots;\n            }\n            last_ts = start_cts;\n            ssize_t wsize = std::stol(map[\"wb\"]);\n            ssize_t rsize = std::stol(map[\"rb\"]);\n            double iot = std::stod(map[\"iot\"]);\n            double idt = std::stod(map[\"idt\"]);\n            double lwt = std::stod(map[\"lwt\"]);\n            double lrt = std::stod(map[\"lrt\"]);\n            double lrvt = std::stod(map[\"lrvt\"]);\n            double deff = 100.0 - (iot ? (100.0 * idt / iot) : 0.0);\n            double lreff = 100.0 * ((iot - lrt - lrvt) / iot);\n            double lweff = 100.0 * ((iot - lwt) / iot);\n            int eff = (int)(deff);\n            if (deff > max_eff) {\n              continue;\n            }\n            if (json && !silent) {\n              Json::Value ljson;\n              for (auto it = map.begin(); it != map.end(); ++it) {\n                if (eos::common::StringConversion::IsDecimalNumber(\n                        it->second)) {\n                  ljson[it->first] =\n                      (Json::Value::UInt64)strtoull(it->second.c_str(), 0, 10);\n                } else if (eos::common::StringConversion::IsDouble(\n                               it->second)) {\n                  ljson[it->first] = strtod(it->second.c_str(), 0);\n                } else {\n                  ljson[it->first] = it->second;\n                }\n              }\n              ljson[\"io\"][\"efficiency\"][\"total\"] = deff;\n              ljson[\"io\"][\"efficiency\"][\"disk\"][\"rd\"] = lreff;\n              ljson[\"io\"][\"efficiency\"][\"disk\"][\"wr\"] = lweff;\n              Json::StreamWriterBuilder builder;\n              builder[\"indentation\"] = \"\";\n              std::string str = Json::writeString(builder, ljson);\n              fprintf(stdout, \"%s\\n\", str.c_str());\n            }\n            if (std::stol(map[\"wb\"]) > 0 && writing) {\n              sum_w += wsize;\n              n_w++;\n              weff += deff;\n              swleff += lweff;\n              double tt = std::stoul(map[\"cts\"]) - std::stoul(map[\"ots\"]) +\n                          (0.001 * std::stoul(map[\"ctms\"])) -\n                          (0.001 * std::stoul(map[\"otms\"]));\n              float rate = wsize / tt / 1000000.0;\n              if (!silent && !json) {\n                fprintf(stdout,\n                        \"W %-16s t=%06.02f [s] r=%06.02f [MB/s] eff=%02d/%02d \"\n                        \"[%%] path=%64s\\n\",\n                        eos::common::StringConversion::GetReadableSizeString(\n                            sizestring, wsize, \"\"),\n                        tt, rate, eff, (int)lweff, map[\"path\"].c_str());\n              }\n              w_t.insert(tt);\n              found = true;\n            }\n            if (std::stol(map[\"rb\"]) > 0 && reading) {\n              sum_r += rsize;\n              n_r++;\n              reff += deff;\n              srleff += lreff;\n              double tt = std::stoul(map[\"cts\"]) - std::stoul(map[\"ots\"]) +\n                          (0.001 * std::stoul(map[\"ctms\"])) -\n                          (0.001 * std::stoul(map[\"otms\"]));\n              float rate = rsize / tt / 1000000.0;\n              if (!silent && !json && !squash.length()) {\n                fprintf(stdout,\n                        \"R %-16s t=%06.02f [s] r=%06.02f [MB/s] eff=%02d/%02d \"\n                        \"[%%] path=%64s\\n\",\n                        eos::common::StringConversion::GetReadableSizeString(\n                            sizestring, rsize, \"\"),\n                        tt, rate, eff, (int)lreff, map[\"path\"].c_str());\n              }\n              r_t.insert(tt);\n              found = true;\n            }\n            if (found) {\n              n_reports++;\n            }\n          } else {\n            fprintf(stderr, \"error: failed to parse '%s'\\n\", line.c_str());\n          }\n          if (n_reports >= max_reports) {\n            break;\n          }\n          if (squash.length()) {\n            std::string rpath = squash.c_str();\n            rpath += map[\"path\"].c_str();\n            eos::common::Path cPath(rpath.c_str());\n            fprintf(stderr, \"info: squash %s\\n\", cPath.GetFullPath().c_str());\n            cPath.MakeParentPath(0644);\n            int fd = open(rpath.c_str(), O_CREAT | O_RDWR | O_APPEND,\n                          S_IRWXU | S_IRWXG);\n            if (fd >= 0) {\n              (void)::write(fd, line.c_str(), line.length() + 1);\n              (void)::close(fd);\n            }\n          }\n        }\n        if (!json) {\n          std::string sizestring1, sizestring2;\n          fprintf(stdout, \"----------------------------------------------------\"\n                          \"-----------------\\n\");\n          fprintf(stdout, \"- n(r): %lu vol(r): %s n(w): %lu vol(w): %s\\n\",\n                  r_t.size(),\n                  eos::common::StringConversion::GetReadableSizeString(\n                      sizestring1, sum_r, \"B\"),\n                  w_t.size(),\n                  eos::common::StringConversion::GetReadableSizeString(\n                      sizestring2, sum_w, \"B\"));\n          fprintf(stdout, \"----------------------------------------------------\"\n                          \"-----------------\\n\");\n          fprintf(stdout,\n                  \"- r:t avg: %s +- %s 95-perc: %s 99-perc: %s max: %s \\n\",\n                  eos::common::StringConversion::GetFixedDouble(\n                      eos::common::Statistics::avg(r_t), 6, 2)\n                      .c_str(),\n                  eos::common::StringConversion::GetFixedDouble(\n                      eos::common::Statistics::sig(r_t), 6, 2)\n                      .c_str(),\n                  eos::common::StringConversion::GetFixedDouble(\n                      eos::common::Statistics::nperc(r_t, 95), 6, 2)\n                      .c_str(),\n                  eos::common::StringConversion::GetFixedDouble(\n                      eos::common::Statistics::nperc(r_t, 99), 6, 2)\n                      .c_str(),\n                  eos::common::StringConversion::GetFixedDouble(\n                      eos::common::Statistics::max(r_t), 6, 2)\n                      .c_str());\n          fprintf(stdout,\n                  \"- w:t avg: %s +- %s 95-perc: %s 99-perc: %s max: %s \\n\",\n                  eos::common::StringConversion::GetFixedDouble(\n                      eos::common::Statistics::avg(w_t), 6, 2)\n                      .c_str(),\n                  eos::common::StringConversion::GetFixedDouble(\n                      eos::common::Statistics::sig(w_t), 6, 2)\n                      .c_str(),\n                  eos::common::StringConversion::GetFixedDouble(\n                      eos::common::Statistics::nperc(w_t, 95), 6, 2)\n                      .c_str(),\n                  eos::common::StringConversion::GetFixedDouble(\n                      eos::common::Statistics::nperc(w_t, 99), 6, 2)\n                      .c_str(),\n                  eos::common::StringConversion::GetFixedDouble(\n                      eos::common::Statistics::max(w_t), 6, 2)\n                      .c_str());\n          fprintf(stdout, \"----------------------------------------------------\"\n                          \"-----------------\\n\");\n          XrdOucString agestring;\n          fprintf(stdout,\n                  \"- first-ts:%ld last-ts:%ld time-span:%ld s [ %s ] \\n\",\n                  first_ts, last_ts, last_ts - first_ts,\n                  eos::common::StringConversion::GetReadableAgeString(\n                      agestring, last_ts - first_ts));\n          fprintf(\n              stdout, \"- r:rate eff: %02d/%02d%% avg: %.02f MB/s\\n\",\n              n_r ? ((int)(reff / n_r)) : 0, n_r ? ((int)(srleff / n_r)) : 0,\n              (last_ts - first_ts) ? sum_r / 1000000.0 / (last_ts - first_ts)\n                                   : 0);\n          fprintf(\n              stdout, \"- w:rate eff: %02d/%02d%% avg: %.02f MB/s\\n\",\n              n_w ? ((int)(weff / n_w)) : 0, n_w ? ((int)(swleff / n_w)) : 0,\n              (last_ts - first_ts) ? sum_w / 1000000.0 / (last_ts - first_ts)\n                                   : 0);\n          fprintf(stdout, \"----------------------------------------------------\"\n                          \"-----------------\\n\");\n        } else if (silent) {\n          gjson[\"report\"][\"rd\"][\"n\"] = (Json::Value::UInt64)n_r;\n          gjson[\"report\"][\"timestamp\"][\"first\"] = (Json::Value::UInt64)first_ts;\n          gjson[\"report\"][\"timestamp\"][\"last\"] = (Json::Value::UInt64)last_ts;\n          gjson[\"report\"][\"wr\"][\"n\"] = (Json::Value::UInt64)n_w;\n          gjson[\"report\"][\"rd\"][\"bytes\"][\"sum\"] = (Json::Value::UInt64)sum_r;\n          gjson[\"report\"][\"wr\"][\"bytes\"][\"sum\"] = (Json::Value::UInt64)sum_w;\n          gjson[\"report\"][\"rd\"][\"bytes\"][\"avg\"] =\n              eos::common::Statistics::avg(r_t);\n          gjson[\"report\"][\"wr\"][\"bytes\"][\"avg\"] =\n              eos::common::Statistics::avg(w_t);\n          gjson[\"report\"][\"rd\"][\"bytes\"][\"sig\"] =\n              eos::common::Statistics::sig(r_t);\n          gjson[\"report\"][\"wr\"][\"bytes\"][\"sig\"] =\n              eos::common::Statistics::sig(w_t);\n          gjson[\"report\"][\"rd\"][\"bytes\"][\"max\"] =\n              eos::common::Statistics::max(r_t);\n          gjson[\"report\"][\"wr\"][\"bytes\"][\"max\"] =\n              eos::common::Statistics::max(w_t);\n          gjson[\"report\"][\"rd\"][\"bytes\"][\"95\"] =\n              eos::common::Statistics::nperc(r_t, 95);\n          gjson[\"report\"][\"wr\"][\"bytes\"][\"95\"] =\n              eos::common::Statistics::nperc(w_t, 95);\n          gjson[\"report\"][\"rd\"][\"bytes\"][\"99\"] =\n              eos::common::Statistics::nperc(r_t, 99);\n          gjson[\"report\"][\"wr\"][\"bytes\"][\"99\"] =\n              eos::common::Statistics::nperc(w_t, 99);\n          gjson[\"report\"][\"rd\"][\"rate\"] =\n              (last_ts - first_ts) ? sum_r / 1000000.0 / (last_ts - first_ts)\n                                   : 0;\n          gjson[\"report\"][\"wr\"][\"rate\"] =\n              (last_ts - first_ts) ? sum_w / 1000000.0 / (last_ts - first_ts)\n                                   : 0;\n          gjson[\"report\"][\"rd\"][\"efficiency\"][\"client\"] =\n              n_r ? (reff / n_r) : 0;\n          gjson[\"report\"][\"rd\"][\"efficiency\"][\"server\"] =\n              n_r ? (srleff / n_r) : 0;\n          gjson[\"report\"][\"rd\"][\"efficiency\"][\"client\"] =\n              n_w ? (weff / n_w) : 0;\n          gjson[\"report\"][\"rd\"][\"efficiency\"][\"server\"] =\n              n_w ? (swleff / n_w) : 0;\n          Json::StreamWriterBuilder builder2;\n          builder2[\"indentation\"] = \"\";\n          std::string out = Json::writeString(builder2, gjson);\n          fprintf(stdout, \"%s\", out.c_str());\n        }\n        file.close();\n        if (sregex.length())\n          regfree(&regex);\n      } else {\n        fprintf(stderr, \"error: unable to open file!\\n\");\n        global_retc = EIO;\n        return 0;\n      }\n    }\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr,\n            \"Usage: report [--read|--write] [--regex <pattern>] [-n <max>]\\n\");\n    fprintf(stderr,\n            \"               [--max-efficiency <0-100>] [--squash <path>]\\n\");\n    fprintf(stderr,\n            \"               [--start <epoch>] [--stop <epoch>] [--json] [-s] <file>\\n\");\n    fprintf(stderr,\n            \"options:\\n\");\n    fprintf(stderr, \"  --read                 filter read reports only\\n\");\n    fprintf(stderr, \"  --write                filter write reports only\\n\");\n    fprintf(stderr, \"  --regex <pattern>      POSIX regex to filter lines\\n\");\n    fprintf(stderr, \"  -n <max>               limit number of reports (default huge)\\n\");\n    fprintf(stderr,\n            \"  --max-efficiency <n>   reject entries with efficiency above n (0-100)\\n\");\n    fprintf(stderr,\n            \"  --squash <path>        squash paths to the given prefix (must start/end with '/')\\n\");\n    fprintf(stderr, \"  --start <epoch>        only include reports >= start time\\n\");\n    fprintf(stderr, \"  --stop <epoch>         only include reports <= stop time\\n\");\n    fprintf(stderr, \"  --json                 output JSON summary\\n\");\n    fprintf(stderr, \"  -s                     silent (no output)\\n\");\n  }\n};\n} // namespace\n\nvoid\nRegisterReportNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<ReportCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/rm-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: rm-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/Path.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/ICmdHelper.hh\"\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeRmHelp()\n{\n  return \"Usage: rm [-r|-rf|-rF|-n] [--no-recycle-bin|-F] [--no-confirmation] \"\n         \"[--no-workflow] [--no-globbing] <path>|fid:<fid>|fxid:<fxid>|cid:<cid>|cxid:<cxid>\\n\\n\"\n         \"  -r|-rf     remove recursively\\n\"\n         \"  -rF|-Fr    remove recursively, bypass recycling\\n\"\n         \"  -F|--no-recycle-bin  bypass recycling policies (root only)\\n\"\n         \"  -n|--no-workflow     don't run workflow when deleting\\n\"\n         \"  --no-confirmation   skip confirmation for recursive deletions\\n\"\n         \"  --no-globbing       disable path globbing\\n\";\n}\n\nvoid ConfigureRmApp(CLI::App& app)\n{\n  app.name(\"rm\");\n  app.description(\"Remove a file\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeRmHelp();\n      }));\n}\n\nclass RmHelper : public ICmdHelper {\npublic:\n  RmHelper(const GlobalOptions& opts) : ICmdHelper(opts) { mIsAdmin = false; }\n  ~RmHelper() override = default;\n\n  bool ParseCommand(const char* arg) override\n  {\n    XrdOucString option;\n    eos::console::RmProto* rm = mReq.mutable_rm();\n    eos::common::StringTokenizer tokenizer(arg);\n    bool noconfirmation = false;\n\n    tokenizer.GetLine();\n\n    while ((option = tokenizer.GetToken(false)).length() > 0 &&\n           (option.beginswith(\"-\"))) {\n      if ((option == \"-r\") || (option == \"-rf\") || (option == \"-fr\")) {\n        rm->set_recursive(true);\n      } else if ((option == \"-F\") || (option == \"--no-recycle-bin\")) {\n        rm->set_bypassrecycle(true);\n      } else if (option == \"-rF\" || option == \"-Fr\") {\n        rm->set_recursive(true);\n        rm->set_bypassrecycle(true);\n      } else if (option == \"--no-confirmation\") {\n        noconfirmation = true;\n      } else if (option == \"--no-workflow\" || option == \"-n\") {\n        rm->set_noworkflow(true);\n      } else if (option == \"--no-globbing\") {\n        rm->set_noglobbing(true);\n      } else {\n        return false;\n      }\n    }\n\n    auto path = option;\n\n    do {\n      XrdOucString param = tokenizer.GetToken();\n\n      if (param.length()) {\n        path += \" \";\n        path += param;\n      } else {\n        break;\n      }\n    } while (true);\n\n    // remove escaped blanks\n    while (path.replace(\"\\\\ \", \" \")) {\n    }\n\n    if (path.length() == 0) {\n      return false;\n    }\n\n    auto id = 0ull;\n\n    if (Path2FileDenominator(path, id)) {\n      rm->set_fileid(id);\n      rm->set_recursive(false); // disable recursive option for files\n      path = \"\";\n    } else {\n      if (Path2ContainerDenominator(path, id)) {\n        rm->set_containerid(id);\n        path = \"\";\n      } else {\n        path = abspath(path.c_str());\n        rm->set_path(path.c_str());\n      }\n    }\n\n    eos::common::Path cPath(path.c_str());\n\n    if (path.length()) {\n      mNeedsConfirmation = rm->recursive() &&\n                           (cPath.GetSubPathSize() < 4) &&\n                           !noconfirmation;\n    }\n\n    return true;\n  }\n};\n\nclass RmProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"rm\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Remove a file\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    (void)ctx;\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    eos::common::StringTokenizer tokenizer(joined.c_str());\n    tokenizer.GetLine();\n\n    std::vector<std::string> paths;\n    std::string optStr;\n    std::string currentPath;\n    bool inOptions = true;\n\n    XrdOucString token;\n    while ((token = tokenizer.GetToken(false)).length() > 0) {\n      if (inOptions && token.beginswith(\"-\")) {\n        if (!optStr.empty()) {\n          optStr += \" \";\n        }\n        optStr += token.c_str();\n      } else {\n        inOptions = false;\n\n        if (token.beginswith(\"/\") || token.beginswith(\"fid:\") ||\n            token.beginswith(\"fxid:\") || token.beginswith(\"cid:\") ||\n            token.beginswith(\"cxid:\")) {\n          if (!currentPath.empty()) {\n            paths.push_back(std::move(currentPath));\n          }\n          currentPath = token.c_str();\n        } else {\n          if (!currentPath.empty()) {\n            currentPath += \" \";\n          }\n          currentPath += token.c_str();\n        }\n      }\n    }\n\n    if (!currentPath.empty()) {\n      paths.push_back(std::move(currentPath));\n    }\n\n    if (paths.empty()) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    int retc = 0;\n    for (const auto& path : paths) {\n      std::string cmdArg = optStr;\n      if (!cmdArg.empty()) {\n        cmdArg += \" \";\n      }\n      cmdArg += path;\n\n      RmHelper rm(gGlobalOpts);\n      if (!rm.ParseCommand(cmdArg.c_str())) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      if (rm.NeedsConfirmation() && !rm.ConfirmOperation()) {\n        retc = EINTR;\n        continue;\n      }\n      if (const int rc = rm.Execute(true, true); rc != 0) {\n        retc = rc;\n      }\n    }\n\n    global_retc = retc;\n    return global_retc;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureRmApp(app);\n    fprintf(stderr, \"%s\", app.help().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterRmProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<RmProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/rmdir-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: rmdir-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringConversion.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <XrdOuc/XrdOucString.hh>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeRmdirHelp()\n{\n  return \"Usage: rmdir <path>\\n\\n\"\n         \"Remove the empty directory <path>.\\n\";\n}\n\nvoid ConfigureRmdirApp(CLI::App& app, std::string& path)\n{\n  app.name(\"rmdir\");\n  app.description(\"Remove a directory\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeRmdirHelp();\n      }));\n  app.add_option(\"path\", path, \"directory path\")->required();\n}\n\nclass RmdirCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"rmdir\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Remove a directory\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (args.empty() || wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    std::string path;\n    ConfigureRmdirApp(app, path);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    XrdOucString in = \"mgm.cmd=rmdir\";\n    XrdOucString p = abspath(path.c_str());\n    XrdOucString esc =\n        eos::common::StringConversion::curl_escaped(p.c_str()).c_str();\n    in += \"&mgm.path=\";\n    in += esc;\n    in += \"&eos.encodepath=1\";\n    global_retc = ctx.outputResult(ctx.clientCommand(in, false, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeRmdirHelp().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterRmdirNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<RmdirCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/role-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: role-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <memory>\n#include <sstream>\n\nnamespace {\nclass RoleCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"role\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Switch role or show roles\";\n  }\n  bool\n  requiresMgm(const std::string&) const override\n  {\n    return false;\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    if (args.empty() || wants_help(args[0].c_str())) {\n      fprintf(stderr, \"Usage: role <user-role> [<group-role>]\\n\");\n      global_retc = EINVAL;\n      return 0;\n    }\n    user_role = args[0].c_str();\n    if (args.size() > 1)\n      group_role = args[1].c_str();\n    else\n      group_role = \"\";\n    if (!silent)\n      fprintf(stdout,\n              \"=> selected user role ruid=<%s> and group role rgid=<%s>\\n\",\n              user_role.c_str(), group_role.c_str());\n    gGlobalOpts.mUserRole = user_role.c_str();\n    gGlobalOpts.mGroupRole = group_role.c_str();\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n  }\n};\n} // namespace\n\nvoid\nRegisterRoleNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<RoleCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/route-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: route-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/ParseUtils.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include \"console/commands/helpers/ICmdHelper.hh\"\n#include <iomanip>\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeRouteHelp()\n{\n  std::ostringstream oss;\n  oss << \"Usage: route [ls|link|unlink]\" << std::endl\n      << \"    namespace routing to redirect clients to external instances\"\n      << std::endl\n      << std::endl\n      << \"  route ls [<path>]\" << std::endl\n      << \"    list all routes or the one matching for the given path\"\n      << std::endl\n      << \"      * as the first character means the node is a master\"\n      << std::endl\n      << \"      _ as the first character means the node is offline\"\n      << std::endl\n      << std::endl\n      << \"  route link <path> <dst_host>[:<xrd_port>[:<http_port>]],...\"\n      << std::endl\n      << \"    create routing from <path> to destination host. If the xrd_port\"\n      << std::endl\n      << \"    is omitted the default 1094 is used, if the http_port is omitted\"\n      << std::endl\n      << \"    the default 8000 is used. Several dst_hosts can be specified by\"\n      << std::endl\n      << \"    separating them with \\\",\\\". The redirection will go to the MGM\"\n      << std::endl\n      << \"    from the specified list\"\n      << std::endl\n      << \"    e.g route /eos/dummy/ foo.bar:1094:8000\" << std::endl\n      << std::endl\n      << \"  route unlink <path>\" << std::endl\n      << \"    remove routing matching path\" << std::endl;\n  return oss.str();\n}\n\nvoid ConfigureRouteApp(CLI::App& app)\n{\n  app.name(\"route\");\n  app.description(\"Routing interface\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeRouteHelp();\n      }));\n}\n\n// Ported from legacy com_proto_route.cc\nclass RouteHelper : public ICmdHelper {\npublic:\n  RouteHelper(const GlobalOptions& opts) : ICmdHelper(opts) {}\n  ~RouteHelper() override = default;\n  bool\n  ParseCommand(const char* arg) override\n  {\n    const char* option;\n    std::string soption;\n    eos::console::RouteProto* route = mReq.mutable_route();\n    eos::common::StringTokenizer tokenizer(arg);\n    tokenizer.GetLine();\n    option = tokenizer.GetToken();\n    std::string cmd = (option ? option : \"\");\n    if (cmd == \"ls\") {\n      using eos::console::RouteProto_ListProto;\n      RouteProto_ListProto* list = route->mutable_list();\n      if (!(option = tokenizer.GetToken())) {\n        list->set_path(\"\");\n      } else {\n        soption = option;\n        if (!ValidatePath(soption))\n          return false;\n        list->set_path(soption);\n      }\n    } else if (cmd == \"unlink\") {\n      using eos::console::RouteProto_UnlinkProto;\n      RouteProto_UnlinkProto* unlink = route->mutable_unlink();\n      if (!(option = tokenizer.GetToken()))\n        return false;\n      soption = option;\n      if (!ValidatePath(soption))\n        return false;\n      unlink->set_path(soption);\n    } else if (cmd == \"link\") {\n      if (!(option = tokenizer.GetToken()))\n        return false;\n      soption = option;\n      if (!ValidatePath(soption))\n        return false;\n      eos::console::RouteProto_LinkProto* link = route->mutable_link();\n      link->set_path(soption);\n      if (!(option = tokenizer.GetToken()))\n        return false;\n      soption = option;\n      std::vector<std::string> endpoints;\n      eos::common::StringConversion::Tokenize(soption, endpoints, \",\");\n      if (endpoints.empty())\n        return false;\n      for (const auto& endpoint : endpoints) {\n        eos::console::RouteProto_LinkProto_Endpoint* ep = link->add_endpoints();\n        std::vector<std::string> elems;\n        eos::common::StringConversion::Tokenize(endpoint, elems, \":\");\n        const std::string fqdn = elems[0];\n        if (!eos::common::ValidHostnameOrIP(fqdn)) {\n          std::cerr << \"error: invalid hostname specified\" << std::endl;\n          return false;\n        }\n        uint32_t xrd_port = 1094;\n        uint32_t http_port = 8000;\n        if (elems.size() == 3) {\n          try {\n            xrd_port = std::stoul(elems[1]);\n            http_port = std::stoul(elems[2]);\n          } catch (...) {\n            std::cerr << \"error: failed to parse ports for route\" << std::endl;\n            return false;\n          }\n        } else if (elems.size() == 2) {\n          try {\n            xrd_port = std::stoul(elems[1]);\n          } catch (...) {\n            std::cerr << \"error: failed to parse xrd port for route\"\n                      << std::endl;\n            return false;\n          }\n        }\n        ep->set_fqdn(fqdn);\n        ep->set_xrd_port(xrd_port);\n        ep->set_http_port(http_port);\n      }\n    } else {\n      return false;\n    }\n    return true;\n  }\n\nprivate:\n  bool\n  ValidatePath(std::string& path) const\n  {\n    if (path.empty() || path[0] != '/') {\n      std::cerr << \"error: path should be non-empty and start with '/'\"\n                << std::endl;\n      return false;\n    }\n    if (path.back() != '/') {\n      path += '/';\n    }\n    std::set<std::string> forbidden{\" \", \"/../\", \"/./\", \"\\\\\"};\n    for (const auto& needle : forbidden) {\n      if (path.find(needle) != std::string::npos) {\n        std::cerr\n            << \"error: path should no contain any of the following sequences \"\n               \"of characters: \\\" \\\", \\\"/../\\\", \\\"/./\\\" or \\\"\\\\\\\"\"\n            << std::endl;\n        return false;\n      }\n    }\n    return true;\n  }\n};\n\nclass RouteProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"route\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Routing interface\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      if (args[i].find(' ') != std::string::npos)\n        oss << std::quoted(args[i]);\n      else\n        oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    RouteHelper helper(*ctx.globalOpts);\n    if (!helper.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = helper.Execute();\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureRouteApp(app);\n    fprintf(stderr, \"%s\", app.help().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterRouteProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<RouteProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/rtlog-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: rtlog-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <XrdOuc/XrdOucString.hh>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeRtlogHelp()\n{\n  return \"Usage: rtlog [<queue>|*|.] [<sec>] [<debug>] [filter-word]\\n\\n\"\n         \"Real-time logging. Query queue for log lines.\\n\\n\"\n         \"  *  query all nodes\\n\"\n         \"  .  query only the connected MGM (default if omitted)\\n\"\n         \"  <sec>  seconds in the past (default 3600)\\n\"\n         \"  <debug>  debug level (default err)\\n\";\n}\n\nvoid ConfigureRtlogApp(CLI::App& app,\n                       std::string& queue,\n                       std::string& lines,\n                       std::string& tag,\n                       std::string& filter)\n{\n  app.name(\"rtlog\");\n  app.description(\"Real-time logging\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeRtlogHelp();\n      }));\n  app.add_option(\"queue\", queue, \"queue (*|.|path)\")->default_val(\".\");\n  app.add_option(\"lines\", lines, \"seconds in past\")->default_val(\"10\");\n  app.add_option(\"tag\", tag, \"debug level\")->default_val(\"err\");\n  app.add_option(\"filter\", filter, \"filter word\");\n}\n\nclass RtlogCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"rtlog\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Real-time logging\";\n  }\n  bool\n  requiresMgm(const std::string&) const override\n  {\n    return false;\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (args.empty() || wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    std::string queue;\n    std::string lines;\n    std::string tag;\n    std::string filter;\n    ConfigureRtlogApp(app, queue, lines, tag, filter);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    XrdOucString q = queue.c_str();\n    XrdOucString l = lines.c_str();\n    XrdOucString t = tag.c_str();\n    XrdOucString f = filter.c_str();\n\n    if (!q.length() || (q != \".\" && q != \"*\" && !q.beginswith(\"/eos/\"))) {\n      f = t;\n      t = l;\n      l = q;\n      q = \".\";\n    }\n\n    XrdOucString in = \"mgm.cmd=rtlog&mgm.rtlog.queue=\";\n    in += q;\n    in += \"&mgm.rtlog.lines=\";\n    in += (l.length() ? l.c_str() : \"10\");\n    in += \"&mgm.rtlog.tag=\";\n    in += (t.length() ? t.c_str() : \"err\");\n    if (f.length()) {\n      in += \"&mgm.rtlog.filter=\";\n      in += f;\n    }\n    global_retc = ctx.outputResult(ctx.clientCommand(in, true, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeRtlogHelp().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterRtlogNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<RtlogCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/sched-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: sched-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/ParseUtils.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/ICmdHelper.hh\"\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeSchedHelp()\n{\n  return \"Usage: sched configure|ls [OPTIONS]\\n\\n\"\n         \"  configure type <schedtype>\\n\"\n         \"    <schedtype>: roundrobin, weightedrr, tlrr, random, weightedrandom, geo\\n\"\n         \"  configure weight <space> <fsid> <weight>\\n\"\n         \"    configure weight for fsid in space\\n\"\n         \"  configure show type [spacename]\\n\"\n         \"    show configured scheduler\\n\"\n         \"  configure forcerefresh\\n\"\n         \"    force refresh scheduler internal state\\n\"\n         \"  ls <spacename> <bucket|disk|all>\\n\";\n}\n\nvoid ConfigureSchedApp(CLI::App& app)\n{\n  app.name(\"sched\");\n  app.description(\"Configure scheduler options\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeSchedHelp();\n      }));\n}\n\nstruct SchedHelper : public ICmdHelper {\n  SchedHelper(const GlobalOptions& opts) : ICmdHelper(opts) { mIsAdmin = true; }\n  ~SchedHelper() override = default;\n  bool\n  ParseCommand(const char* arg) override\n  {\n    eos::console::SchedProto* sched = mReq.mutable_sched();\n    eos::common::StringTokenizer tokenizer(arg);\n    tokenizer.GetLine();\n    std::string token;\n    if (!tokenizer.NextToken(token))\n      return false;\n    if (token == \"configure\" || token == \"config\") {\n      eos::console::SchedProto_ConfigureProto* config = sched->mutable_config();\n      if (!tokenizer.NextToken(token))\n        return false;\n      if (token == \"type\") {\n        auto* type = config->mutable_type();\n        if (!tokenizer.NextToken(token))\n          return false;\n        type->set_schedtype(token);\n      } else if (token == \"weight\") {\n        auto* w = config->mutable_weight();\n        std::string space, id_str, weight_str;\n        if (!(tokenizer.NextToken(space) && tokenizer.NextToken(id_str) &&\n              tokenizer.NextToken(weight_str)))\n          return false;\n        int32_t item_id = 0;\n        uint8_t weight = 0;\n        try {\n          item_id = std::stoi(id_str);\n          unsigned long wtmp = std::stoul(weight_str);\n          if (wtmp > 255)\n            return false;\n          weight = static_cast<uint8_t>(wtmp);\n        } catch (...) {\n          return false;\n        }\n        w->set_id(item_id);\n        w->set_weight(weight);\n        w->set_spacename(space);\n      } else if (token == \"show\") {\n        auto* showp = config->mutable_show();\n        if (!tokenizer.NextToken(token))\n          return false;\n        if (token == \"type\") {\n          showp->set_option(eos::console::SchedProto_ShowProto::TYPE);\n          if (tokenizer.NextToken(token))\n            showp->set_spacename(token);\n        }\n      } else if (token == \"forcerefresh\") {\n        config->mutable_refresh();\n      } else {\n        return false;\n      }\n    } else if (token == \"ls\") {\n      auto* ls = sched->mutable_ls();\n      if (!tokenizer.NextToken(token))\n        return false;\n      ls->set_spacename(token);\n      if (!tokenizer.NextToken(token))\n        return false;\n      if (token == \"bucket\")\n        ls->set_option(eos::console::SchedProto_LsProto::BUCKET);\n      else if (token == \"disk\")\n        ls->set_option(eos::console::SchedProto_LsProto::DISK);\n      else\n        ls->set_option(eos::console::SchedProto_LsProto::ALL);\n    } else {\n      return false;\n    }\n    return true;\n  }\n};\n\nclass SchedProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"sched\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Configure scheduler options\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    SchedHelper helper(gGlobalOpts);\n    if (!helper.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = helper.Execute();\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureSchedApp(app);\n    fprintf(stderr, \"%s\", app.help().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterSchedProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<SchedProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/scitoken-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: scitoken-native.cc\n// ----------------------------------------------------------------------\n\n// ----------------------------------------------------------------------\n// File: scitoken-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/Mapping.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <json/json.h>\n#include <memory>\n#include <sstream>\n#ifndef __APPLE__\n#include \"console/commands/helpers/jwk_generator/jwk_generator.hpp\"\n#include <fstream>\n#include <scitokens/scitokens.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#endif\n\nnamespace {\nclass ScitokenCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"scitoken\";\n  }\n  const char*\n  description() const override\n  {\n    return \"SciToken utilities\";\n  }\n  bool\n  requiresMgm(const std::string&) const override\n  {\n    return false;\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n#ifdef __APPLE__\n    fprintf(stderr, \"error: scitoken command is not support on OSX\\n\");\n    global_retc = EINVAL;\n    return 0;\n#else\n    eos::common::StringTokenizer subtokenizer(joined.c_str());\n    subtokenizer.GetLine();\n    XrdOucString subcommand = subtokenizer.GetTokenUnquoted();\n    std::string option;\n    std::string value;\n    time_t expires = 0;\n    std::string cred, key, creddata, keydata, keyid, issuer, profile = \"wlcg\";\n    std::set<std::string> claims;\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    if (subcommand == \"create\") {\n      do {\n        const char* o = subtokenizer.GetTokenUnquoted();\n        const char* v = subtokenizer.GetTokenUnquoted();\n        if (o && !v) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        if (!o && !v)\n          break;\n        option = o;\n        value = v;\n        if (option == \"--pubkey\")\n          cred = value;\n        if (option == \"--privkey\")\n          key = value;\n        if (option == \"--keyid\")\n          keyid = value;\n        if (option == \"--issuer\")\n          issuer = value;\n        if (option == \"--claim\")\n          claims.insert(value);\n        if (option == \"--expires\")\n          expires = atoi(value.c_str());\n        if (option == \"--profile\")\n          profile = value;\n      } while (option.length());\n      if (issuer.empty() || !claims.size() || keyid.empty()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      if (cred.empty()) {\n        cred = \"/etc/xrootd/\" + keyid + std::string(\"-pkey.pem\");\n      }\n      if (key.empty()) {\n        key = \"/etc/xrootd/\" + keyid + std::string(\"-key.pem\");\n      }\n      eos::common::StringConversion::LoadFileIntoString(key.c_str(), keydata);\n      if (keydata.empty()) {\n        std::cerr << \"error: cannot load private key from '\" << key.c_str()\n                  << \"'\" << std::endl;\n        global_retc = EINVAL;\n        return 0;\n      }\n      eos::common::StringConversion::LoadFileIntoString(cred.c_str(), creddata);\n      if (creddata.empty()) {\n        std::cerr << \"error: cannot load public key from '\" << cred.c_str()\n                  << \"'\" << std::endl;\n        global_retc = EINVAL;\n        return 0;\n      }\n      char* err_msg = 0;\n      auto key_raw = scitoken_key_create(\n          keyid.c_str(), \"ES256\", creddata.c_str(), keydata.c_str(), &err_msg);\n      std::unique_ptr<void, decltype(&scitoken_key_destroy)> pkey(\n          key_raw, scitoken_key_destroy);\n      if (key_raw == nullptr) {\n        std::cerr << \"error: failed to generate a key: \" << err_msg\n                  << std::endl;\n        free(err_msg);\n        global_retc = EFAULT;\n        return 0;\n      }\n      std::unique_ptr<void, decltype(&scitoken_destroy)> token(\n          scitoken_create(key_raw), scitoken_destroy);\n      if (token.get() == nullptr) {\n        std::cerr << \"error: failed to generate a new token\" << std::endl;\n        global_retc = EFAULT;\n        return 0;\n      }\n      int rv = scitoken_set_claim_string(token.get(), \"iss\", issuer.c_str(),\n                                         &err_msg);\n      if (rv) {\n        std::cerr << \"error: failed to set issuer: \" << err_msg << std::endl;\n        free(err_msg);\n        global_retc = EFAULT;\n        return 0;\n      }\n      for (const auto& c : claims) {\n        auto pos = c.find(\"=\");\n        if (pos == std::string::npos) {\n          std::cerr << \"error: claim must contain a '=' character: \"\n                    << c.c_str() << std::endl;\n          global_retc = EFAULT;\n          return 0;\n        }\n        auto k = c.substr(0, pos);\n        auto val = c.substr(pos + 1);\n        rv = scitoken_set_claim_string(token.get(), k.c_str(), val.c_str(),\n                                       &err_msg);\n        if (rv) {\n          std::cerr << \"error: failed to set claim '\" << k << \"'='\" << val\n                    << \"' error:\" << err_msg << std::endl;\n          free(err_msg);\n          global_retc = EFAULT;\n          return 0;\n        }\n      }\n      if (expires) {\n        auto lifetime = expires - time(NULL);\n        if (lifetime < 0)\n          lifetime = 0;\n        scitoken_set_lifetime(token.get(), lifetime);\n      }\n      SciTokenProfile sprofile = WLCG_1_0;\n      if (profile == \"wlcg\")\n        sprofile = WLCG_1_0;\n      else if (profile == \"scitokens1\")\n        sprofile = SCITOKENS_1_0;\n      else if (profile == \"scitokens2\")\n        sprofile = SCITOKENS_2_0;\n      else if (profile == \"atjwt\")\n        sprofile = AT_JWT;\n      else {\n        std::cerr << \"error: unknown token profile: \" << profile << std::endl;\n        global_retc = EINVAL;\n        return 0;\n      }\n      scitoken_set_serialize_mode(token.get(), sprofile);\n      char* out = 0;\n      rv = scitoken_serialize(token.get(), &out, &err_msg);\n      if (rv) {\n        std::cerr << \"error: failed to serialize the token: \" << err_msg\n                  << std::endl;\n        free(err_msg);\n        global_retc = EFAULT;\n        return 0;\n      }\n      std::cout << out << std::endl;\n      return 0;\n    }\n    if (subcommand == \"dump\") {\n      XrdOucString token = subtokenizer.GetTokenUnquoted();\n      if (!token.length()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      try {\n        std::string stoken = token.c_str();\n        std::cerr\n            << \"# -----------------------------------------------------------\"\n               \"-------------------- #\"\n            << std::endl;\n        std::cerr << eos::common::Mapping::PrintJWT(stoken, false) << std::endl;\n        std::cerr\n            << \"# -----------------------------------------------------------\"\n               \"-------------------- #\"\n            << std::endl;\n        global_retc = 0;\n      } catch (...) {\n        std::cerr << \"error: failed to print token\" << std::endl;\n        global_retc = EINVAL;\n      }\n      return 0;\n    }\n    if (subcommand == \"create-keys\") {\n      std::string option, value, keyid;\n      do {\n        const char* o = subtokenizer.GetTokenUnquoted();\n        const char* v = subtokenizer.GetTokenUnquoted();\n        if (o && !v) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        if (!o && !v)\n          break;\n        option = o;\n        value = v;\n        if (option == \"--keyid\")\n          keyid = value;\n      } while (option.length());\n      std::string prefix;\n      if (!keyid.empty()) {\n        prefix = \"/etc/xrootd/\";\n      } else {\n        keyid = \"default\";\n        const size_t size = 1024;\n        char buffer[size];\n        if (getcwd(buffer, size) == nullptr) {\n          std::cerr << \"error: can not get CWD\" << std::endl;\n          global_retc = errno;\n          return 0;\n        }\n        prefix = buffer;\n      }\n      if (!prefix.empty() && prefix.back() != '/')\n        prefix += '/';\n      bool store_keys = false;\n      std::string fn_public = prefix + keyid + \"-pkey.pem\";\n      std::string fn_private = prefix + keyid + \"-key.pem\";\n      std::string jwk_file;\n      struct stat buf;\n      if (::stat(fn_public.c_str(), &buf) || ::stat(fn_private.c_str(), &buf)) {\n        fn_public.clear();\n        fn_private.clear();\n        store_keys = true;\n      }\n      using namespace jwk_generator;\n      JwkGenerator<ES256> jwk(keyid, fn_public, fn_private);\n      std::cout << \"JWK:\\n\" << jwk.to_pretty_string() << std::endl << std::endl;\n      if (store_keys) {\n        fn_public = prefix + keyid + \"-pkey.pem\";\n        fn_private = prefix + keyid + \"-key.pem\";\n        jwk_file = prefix + keyid + \"-sci.jwk\";\n        for (auto& pair : std::list<std::pair<std::string, std::string>>{\n                 {fn_public, jwk.public_to_pem()},\n                 {fn_private, jwk.private_to_pem()},\n                 {jwk_file, jwk.to_pretty_string()}}) {\n          std::ofstream file(pair.first);\n          if (!file.is_open()) {\n            std::cerr << \"error: failed to open public key file \" << pair.first\n                      << std::endl;\n            global_retc = EINVAL;\n            return 0;\n          }\n          file << pair.second << std::endl;\n          file.close();\n        }\n      }\n      if (!fn_public.empty() && !fn_private.empty()) {\n        std::cerr << (store_keys ? \"Wrote\" : \"Used\")\n                  << \" public key :  \" << fn_public << std::endl\n                  << (store_keys ? \"Wrote\" : \"Used\")\n                  << \" private key: \" << fn_private << std::endl;\n        if (!jwk_file.empty()) {\n          std::cerr << \"Wrote JWK file   : \" << jwk_file << std::endl;\n        }\n      }\n      return 0;\n    }\n    printHelp();\n    global_retc = EINVAL;\n    return 0;\n#endif\n  }\n  void\n  printHelp() const override\n  {\n    std::ostringstream oss;\n    oss << \"Usage: scitoken create|dump|create-keys\\n\"\n        << \"    command for handling scitokens generated by EOS\\n\"\n        << std::endl\n        << \"  scitoken create --issuer <issuer> --keyid <keyid> [--profile <profile>] \"\n        << \"--claim <claim-1> {... --claim <claim-n>} [--privkey <private-key-file>] \"\n        << \"[--pubkey <public-key-file>] [--expires unix-ts]\\n\"\n        << \"    create a scitoken for a given keyid, issuer, profile containing claims\\n\"\n        << \"    <issuer>           : URL of the issuer\\n\"\n        << \"    <keyid>            : key id to request from the issuer\\n\"\n        << \"    <profile>          : token profile, one of \\\"wlcg\\\" [default], \\\"scitokens1\\\", \"\n        << \"\\\"scitokens2\\\", \\\"atjwt\\\"\\n\"\n        << \"    <claims>           : <key>=<value> e.g. scope=storage.read:/eos/, scope=storage.modify:/eos/ ...\\n\"\n        << \"    <private-key-file> : file with the private key in PEM format - default /eos/xrootd/<keyid>-key.pem\\n\"\n        << \"    <public-key-file>  : file with the public key in PEM format - default /eos/xrootd/<keyid>-pkey.pem\\n\"\n        << std::endl\n        << \"  scitoken dump <token>\\n\"\n        << \"    base64 decode a scitokens without verification\\n\"\n        << std::endl\n        << \"  scitoken create-keys [--keyid <keyid>]\\n\"\n        << \"    create a PEM key pair and a JSON public web key. If <keyid> is specified \\n\"\n        << \"    then the pub/priv key pair is in /eos/xrootd/<keyid>-{key,pkey}.pem.\\n\"\n        << \"    Otherwise they are stored in CWD in default-{key,pkey}.pem. The JSON web \\n\"\n        << \"    key is printed on stdout, and the key locations on stderr.\\n\"\n        << std::endl\n        << \"  Examples:\\n\"\n        << \"    eos scitoken create --issuer eos.cern.ch --keyid eos profile wlcg --claim sub=foo --claim scope=storage.read:/eos\\n\"\n        << \"    eos scitoken dump eyJhb ...\\n\"\n        << \"    eos scitoken create-keys --keyid eos > /etc/xrootd/eos.jwk\\n\";\n    fprintf(stderr, \"%s\", oss.str().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterScitokenNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<ScitokenCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/space-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: space-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringTokenizer.hh\"\n#include \"common/SymKeys.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleCompletion.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/ICmdHelper.hh\"\n#include \"mgm/http/rest-api/Constants.hh\"\n#include \"mgm/tgc/Constants.hh\"\n#include <XrdOuc/XrdOucEnv.hh>\n#include <algorithm>\n#include <cerrno>\n#include <fstream>\n#include <iomanip>\n#include <iostream>\n#include <memory>\n#include <sstream>\n#include <streambuf>\n\nnamespace {\nstd::string MakeSpaceHelp()\n{\n  std::ostringstream oss;\n  oss\n      << \" usage:\\n\"\n      << \"space ls [-s|-g <depth>] [-m|-l|--io|--fsck] [<space>] : list in all spaces or select only <space>. <space> is a substring match and can be a comma separated list\\n\"\n      << \"\\t      -s : silent mode\\n\"\n      << \"\\t      -m : monitoring key=value output format\\n\"\n      << \"\\t      -l : long output - list also file systems after each space\\n\"\n      << \"\\t      -g : geo output - aggregate space information along the instance geotree down to <depth>\\n\"\n      << \"\\t    --io : print IO statistics\\n\"\n      << \"\\t  --fsck : print filesystem check statistics\\n\"\n      << std::endl\n      << \"space config <space-name> space.attr.<key> =[<>|]<value>               : configure a space extended attribute which is added to all directories referencing this space via sys.forced.space\\n\"\n      << \"                                                                        space.attr.sys.acl=<u:1000:rwx (the < sign indicates to add to the acl on the left side\\n\"\n      << \"                                                                        space.attr.sys.acl=>u:1000:rwx (the > sign indicates to add to the acl on the right side\\n\"\n      << \"                                                                        space.attr.sys.acl=|u:1000:rwx (the | sign indicates to set the acl if there is none defined\\n\"\n      << \"                                                                        space.attr.sys.foo=bar ( the sys.foo attribute is overwriting the local sys.foo attribute\\n\"\n      << \"                                                                        space.attr.sys.foo=|bar ( the sys.foo attribute is set only if there is no local sys.foo attribute\\n\"\n      << \"space config <space-name> space.nominalsize=<value>                   : configure the nominal size for this space\\n\"\n      << \"space config <space-name> space.balancer=on|off                       : enable/disable the space balancer [ default=off ]\\n\"\n      << \"space config <space-name> space.balancer.threshold=<percent>          : configure the used bytes deviation which triggers balancing             [ default=20 (%%)     ]\\n\"\n      << \"space config <space-name> space.balancer.node.rate=<MB/s>             : configure the nominal transfer bandwidth per running transfer on a node [ default=25 (MB/s)   ]\\n\"\n      << \"space config <space-name> space.balancer.node.ntx=<#>                 : configure the number of parallel balancing transfers per node           [ default=2 (streams) ]\\n\"\n      << \"space config <space-name> space.balancer.max-queue-jobs=<#>           : configure the maximum number of queued jobs allowed in the balancer thread pool [ default=1000 (jobs) ]\\n\"\n      << \"space config <space-name> space.balancer.max-thread-pool-size=<#>     : configure the maximum number of threads to be used in the balancer thread pool [ default=100 (threads) ]\\n\"\n      << \"space config <space-name> space.balancer.update.interval=<#>          : configure the update interval of the balancing statistics used for spawning transfers [ default=60 (seconds) min=1 max=300]\\n\"\n      << \"space config <space-name> space.drainer.tx.minrate=<MB/s >            : configure the minimum transfer bandwidth per running transfer used for computing transfer timeout [ default=25 (MB/s) ]\\n\"\n      << \"space config <space-name> space.drainer.fs.ntx=<#>                    : configure the number of parallel draining transfers per fs [ default=5 ]\\n\"\n      << \"space config <space-name> space.groupbalancer=on|off                  : enable/disable the group balancer [ default=off ]\\n\"\n      << \"space config <space-name> space.groupbalancer.ntx=<ntx>               : configure the number of parallel group balancer jobs per 10s [ default=10 ]\\n\"\n      << \"space config <space-name> space.groupbalancer.engine=[value]          : configure the groupbalancer engine - std/minmax/freespace [ default=std ]\\n\"\n      << \"space config <space-name> space.groupbalancer.min_threshold=<v>       : configure the groupbalancer min threshold(%), groups below this will be picked as targets [default=60]\\n\"\n      << \"space config <space-name> space.groupbalancer.max_threshold=<v>       : configure the groupbalancer max threshold(%), groups above this will be picked as sources [default=95]\\n\"\n      << \"space config <space-name> space.groupbalancer.min_file_size=<#K/M/G/T>: configure the min file size to move between groups [ default=1G ]\\n\"\n      << \"space config <space-name> space.groupbalancer.max_file_size=<#K/M/G/T>: configure the max file size to move between groups [ default=16G ]\\n\"\n      << \"space config <space-name> space.groupbalancer.file_attempts=<#>       : configure the no of attempts to find a file within sizes [ default=50 ]\\n\"\n      << \"space config <space-name> space.groupbalancer.threshold=<threshold>   : [Deprecated use <..>.min/max_threshold (see above)] configure the threshold when a group is balanced\\n\"\n      << \"space config <space-name> space.groupbalancer.blocklist=<list>        : comma list eg. group1, group2 of groups blocklisted (only available for freespace engine)\\n\"\n      << \"space config <space-name> space.geobalancer=on|off                    : enable/disable the geo balancer [ default=off ]\\n\"\n      << \"space config <space-name> space.geobalancer.ntx=<ntx>                 : configure the number of parallel geobalancer jobs [ default=0 ]\\n\"\n      << \"space config <space-name> space.geobalancer.threshold=<threshold>     : configure the threshold when a geotag is balanced [ default=0 ] \\n\"\n      << \"space config <space-name> space.groupdrainer=on|off                   : enable/disable the group drainer [ default=on ]\\n\"\n      << \"space config <space-name> space.groupdrainer.threshold=<threshold>    : configure the threshold(%) for picking target groups\\n\"\n      << \"space config <space-name> space.groupdrainer.group_refresh_interval   : configure time in seconds for refreshing cached groups info [default=300]\\n\"\n      << \"space config <space-name> space.groupdrainer.retry_interval           : configure time in seconds for retrying failed drains [default=4*3600]\\n\"\n      << \"space config <space-name> space.groupdrainer.retry_count              : configure the amount of retries for failed drains [default=5]\\n\"\n      << \"space config <space-name> space.groupdrainer.ntx                      : configure the max file transfer queue size [default=10000]\\n\"\n      << \"space config <space-name> space.lru=on|off                            : enable/disable the LRU policy engine [ default=off ]\\n\"\n      << \"space config <space-name> space.lru.interval=<sec>                    : configure the default lru scan interval\\n\"\n      << \"space config <space-name> fs.max.ropen=<n>                            : allow more than <n> read streams per disk in the given space\\n\"\n      << \"space config <space-name> fs.max.wopen=<n>                            : allow more than <n> write streams per disk in the given space\\n\"\n      << \"space config <space-name> space.wfe=on|off|paused                     : enable/disable the Workflow Engine [ default=off ]\\n\"\n      << \"space config <space-name> space.wfe.interval=<sec>                    : configure the default WFE scan interval\\n\"\n      << \"space config <space-name> space.headroom=<size>                       : configure the default disk headroom if not defined on a filesystem (see fs for details)\\n\"\n      << \"space config <space-name> space.scaninterval=<sec>                    : configure the default scan interval if not defined on a filesystem (see fs for details)\\n\"\n      << \"space config <space-name> space.scan_rain_interval=<sec>              : configure the default rain scan interval if not defined on a filesystem (see fs for details)\\n\"\n      << \"space config <space-name> space.scanrate=<MB/S>                       : configure the default scan rate if not defined on a filesystem     (see fs for details)\\n\"\n      << \"space config <space-name> space.scan_disk_interva=<sec>               : time interval after which the disk scanner will run, default 4h\\n\"\n      << \"space config <space-name> space.scan_ns_interval=<sec>                : time interval after which the namespace scanner will run, default 3 days\\n\"\n      << \"space config <space-name> space.scan_ns_rate=entry/sec                : namespace scan rate in terms of number of stat requests per second done against the local disk\\n\"\n      << \"space config <space-name> space.scheduler.type=<type>                 : configure the default scheduler for space, eg. geo, roundrobin, weightedrandom etc\\n\"\n      << \"space config <space-name> space.drainperiod=<sec>                     : configure the default drain  period if not defined on a filesystem (see fs for details)\\n\"\n      << \"space config <space-name> space.graceperiod=<sec>                     : configure the default grace  period if not defined on a filesystem (see fs for details)\\n\"\n      << \"space config <space-name> space.filearchivedgc=on|off                 : enable/disable the 'file archived' garbage collector [ default=off ]\\n\"\n      << \"space config <space-name> space.tracker=on|off                        : enable/disable the space layout creation tracker [ default=off ]\\n\"\n      << \"space config <space-name> space.inspector=on|off                      : enable/disable the file inspector [ default=off ]\\n\"\n      << \"space config <space-name> space.inspector.interval=<sec>              : time interval after which the inspector will run, default 4h\\n\"\n      << \"space config <space-name> space.inspector.price.currency=[0-5]        : currency printed by the cost evaluation ( 0=EOS, 1=CHF, 2=EUR, 3=USD, 4=AUD, 5=YEN )\\n\"\n      << \"space config <space-name> space.inspector.price.disk.tbyear=<price>   : set the price of a tb year of data on disk without redundancy (default=20)\\n\"\n      << \"space config <space-name> space.inspector.price.tape.tbyear=<price>   : set the price of a tb year of data on disk without redundancy (default=10)\\n\"\n      << \"space config <space-name> space.geo.access.policy.write.exact=on|off  : if 'on' use exact matching geo replica (if available), 'off' uses weighting [ for write case ]\\n\"\n      << \"space config <space-name> space.geo.access.policy.read.exact=on|off   : if 'on' use exact matching geo replica (if available), 'off' uses weighting [ for read  case ]\\n\"\n      << \"space config <space-name> fs.<key>=<value>                            : configure file system parameters for each filesystem in this space (see help of 'fs config' for details)\\n\"\n      << \"space config <space-name> space.policy.[layout|nstripes|checksum|blockchecksum|blocksize|bw|schedule|iopriority|iotype]=<value>      \\n\"\n      << \"                                                                      : configure default file layout creation settings as a space policy - a value='remove' deletes the space policy\\n\"\n      << std::endl\n      << \"space config <space-name> space.altxs=on|off                          : enable/disable the alternative checksums computation when the file is uploaded\\n\"\n      << std::endl\n      << \"TAPE REST API specific parameters:\\n\"\n      << \"space config default \" << eos::mgm::rest::TAPE_REST_API_SWITCH_ON_OFF\n      << \"=on|off                               : enable/disable the tape REST API handler [ default=off ]\\n\"\n      << \"space config default \" << eos::mgm::rest::TAPE_REST_API_STAGE_SWITCH_ON_OFF\n      << \"=on|off                         : enable/disable the tape REST API STAGE resource [ default=off ]\\n\"\n      << std::endl\n      << \"Tape specific configuration parameters:\\n\"\n      << \"space config <space-name> space.\" << eos::mgm::tgc::TGC_NAME_QRY_PERIOD_SECS\n      << \"=<#>                 : tape-aware GC query period in seconds [ default=\" << eos::mgm::tgc::TGC_DEFAULT_QRY_PERIOD_SECS << \" ]\\n\"\n      << \"                                                                        => value must be > 0 and <= \" << eos::mgm::tgc::TGC_MAX_QRY_PERIOD_SECS << \"\\n\"\n      << \"space config <space-name> space.\" << eos::mgm::tgc::TGC_NAME_FREE_BYTES_SCRIPT\n      << \"=<path>            : optional path to a script used to determine the number of free bytes in a given EOS space [ default='\" << eos::mgm::tgc::TGC_DEFAULT_FREE_BYTES_SCRIPT << \"' ]\\n\"\n      << \"                                                                        => an empty or invalid path means the compile time default way of determining free space will be used\\n\"\n      << \"space config <space-name> space.\" << eos::mgm::tgc::TGC_NAME_AVAIL_BYTES\n      << \"=<#>                    : configure the number of available bytes the space should have [ default=\" << eos::mgm::tgc::TGC_DEFAULT_AVAIL_BYTES << \" ] \\n\"\n      << \"space config <space-name> space.\" << eos::mgm::tgc::TGC_NAME_TOTAL_BYTES\n      << \"=<#>                    : configure the total number of bytes the space should have before the tape-aware GC kicks in [ default=\" << eos::mgm::tgc::TGC_DEFAULT_TOTAL_BYTES << \" ] \\n\"\n      << std::endl\n      << \"space config rm <space-name> <key>                   : remove the given key from the space configuration\\n\"\n      << std::endl\n      << \"space define <space-name> [<groupsize> [<groupmod>]] : define how many filesystems can end up in one scheduling group <groupsize> [ default=0 ]\\n\"\n      << \"                                                       => <groupsize>=0 means that no groups are built within a space, otherwise it should be the maximum number of nodes in a scheduling group\\n\"\n      << \"                                                       => <groupmod> maximum number of groups in the space, which should be at least equal to the maximum number of filesystems per node\\n\"\n      << std::endl\n      << \"space inspector [--current|-c] [--last|-l] [-m] [-p] [-e] [-s|--space <space_name>] [--all|-a] [--cost|-C] [--usage|-U] [--birth|-B] [--access|-A] [--vs|-V] [--layouts|-L] : show namespace inspector output\\n\"\n      << \"\\t  -c  : show current scan\\n\"\n      << \"\\t  -l  : show last complete scan\\n\"\n      << \"\\t  -m  : print last scan in monitoring format ( by default this enables --cost --usage --birth --access --layouts)\\n\"\n      << \"\\t  -A  : combined with -m prints access time distributions\\n\"\n      << \"\\t  -V  : combined with -m prints birth time vs access time distributions\\n\"\n      << \"\\t  -B  : combined with -m prints birth time distributions\\n\"\n      << \"\\t  -C  : combined with -m prints cost information (storage price per user/group)\\n\"\n      << \"\\t  -U  : combined with -m prints usage information (stored bytes per user/group)\\n\"\n      << \"\\t  -L  : combined with -m prints layout statistics\\n\"\n      << \"\\t  -a  : combined with -m or -C or -U removes the restriction to show only the top 10 user ranking\\n\"\n      << \"\\t  -p  : combined with -c or -l lists erroneous files\\n\"\n      << \"\\t  -e  : combined with -c or -l exports erroneous files on the MGM into /var/log/eos/mgm/FileInspector.<date>.list\\n\"\n      << \"\\t  -s  : select target space, by default \\\"default\\\" space is used\\n\"\n      << std::endl\n      << \"space node-set <space-name> <node.key> <file-name> : store the contents of <file-name> into the node configuration variable <node.key> visible to all FSTs\\n\"\n      << \"                                                     => if <file-name> matches file:<path> the file is loaded from the MGM and not from the client\\n\"\n      << \"                                                     => local files cannot exceed 512 bytes - MGM files can be arbitrary length\\n\"\n      << \"                                                     => the contents gets base64 encoded by default\\n\"\n      << std::endl\n      << \"space node-get <space-name> <node.key> : get the value of <node.key> and base64 decode before output\\n\"\n      << \"                                         => if the value for <node.key> is identical for all nodes in the referenced space, it is dumped only once, otherwise the value is dumped for each node separately\\n\"\n      << std::endl\n      << \"space reset <space-name> [--egroup|mapping|drain|scheduledrain|schedulebalance|ns|nsfilesystemview|nsfilemap|nsdirectorymap] : reset different space attributes\\n\"\n      << \"\\t            --egroup : clear cached egroup information\\n\"\n      << \"\\t           --mapping : clear all user/group uid/gid caches\\n\"\n      << \"\\t             --drain : reset draining\\n\"\n      << \"\\t     --scheduledrain : reset drain scheduling map\\n\"\n      << \"\\t   --schedulebalance : reset balance scheduling map\\n\"\n      << \"\\t                --ns : resize all namespace maps\\n\"\n      << \"\\t  --nsfilesystemview : resize namespace filesystem view\\n\"\n      << \"\\t         --nsfilemap : resize namespace file map\\n\"\n      << \"\\t    --nsdirectorymap : resize namespace directory map\\n\"\n      << std::endl\n      << \"space status <space-name> [-m] : print all defined variables for space\\n\"\n      << std::endl\n      << \"space tracker : print all file replication tracking entries\\n\"\n      << std::endl\n      << \"space set <space-name> on|off : enable/disable all groups under that space\\n\"\n      << \"                                => <on> value will enable all nodes, <off> value won't affect nodes\\n\"\n      << std::endl\n      << \"space rm <space-name> : remove space\\n\"\n      << std::endl\n      << \"space quota <space-name> on|off : enable/disable quota\\n\"\n      << std::endl\n      << \"space groupbalancer status <space-name> [--detail(-d)|-m] : print groupbalancer status\\n\"\n      << std::endl\n      << \"space groupdrainer status <space-name> [--detail(-d)|-m]  : print groupdrainer status\\n\"\n      << \"space groupdrainer reset <space-name> <--failed|--all>    : reset failed transfers/all caches\\n\"\n      << std::endl;\n  return oss.str();\n}\n\nclass SpaceHelper : public ICmdHelper {\npublic:\n  SpaceHelper(const GlobalOptions& opts) : ICmdHelper(opts) { mIsAdmin = true; }\n  ~SpaceHelper() override = default;\n  bool\n  ParseCommand(const char* arg) override\n  {\n    eos::console::SpaceProto* space = mReq.mutable_space();\n    eos::common::StringTokenizer tokenizer(arg);\n    tokenizer.GetLine();\n    std::string token;\n\n    if (!tokenizer.NextToken(token)) {\n      return false;\n    }\n\n    if (token == \"ls\") {\n      eos::console::SpaceProto_LsProto* ls = space->mutable_ls();\n\n      while (tokenizer.NextToken(token)) {\n        if (token == \"-s\") {\n          mIsSilent = true;\n        } else if (token == \"-g\") {\n          if (!tokenizer.NextToken(token) ||\n              !eos::common::StringTokenizer::IsUnsignedNumber(token)) {\n            std::cerr << \"error: geodepth was not provided or it does not have \"\n                      << \"the correct value: geodepth should be a positive \"\n                      << \"integer\" << std::endl;\n            return false;\n          }\n\n          try {\n            ls->set_outdepth(std::stoi(token));\n          } catch (const std::exception& e) {\n            std::cerr << \"error: argument needs to be numeric\" << std::endl;\n            return false;\n          }\n        } else if (token == \"-m\") {\n          ls->set_outformat(eos::console::SpaceProto_LsProto::MONITORING);\n        } else if (token == \"-l\") {\n          ls->set_outformat(eos::console::SpaceProto_LsProto::LISTING);\n        } else if (token == \"--io\") {\n          ls->set_outformat(eos::console::SpaceProto_LsProto::IO);\n        } else if (token == \"--fsck\") {\n          ls->set_outformat(eos::console::SpaceProto_LsProto::FSCK);\n        } else if ((token.find('-') != 0)) {\n          ls->set_selection(token);\n        } else {\n          return false;\n        }\n      }\n    } else if (token == \"tracker\") {\n      eos::console::SpaceProto_TrackerProto* tracker = space->mutable_tracker();\n      tracker->set_mgmspace(\"default\");\n    } else if (token == \"inspector\") {\n      eos::console::SpaceProto_InspectorProto* inspector =\n          space->mutable_inspector();\n      inspector->set_mgmspace(\"default\");\n      std::string options;\n\n      while (tokenizer.NextToken(token)) {\n        if ((token == \"-s\") || (token == \"--space\")) {\n          if (tokenizer.NextToken(token)) {\n            inspector->set_mgmspace(token);\n          } else {\n            std::cerr << \"error: no space specified\" << std::endl;\n            return false;\n          }\n        } else if (token == \"-c\" || token == \"--current\") {\n          options += \"c\";\n        } else if (token == \"-l\" || token == \"--last\") {\n          options += \"l\";\n        } else if (token == \"-m\") {\n          options += \"m\";\n        } else if (token == \"-p\") {\n          options += \"p\";\n        } else if (token == \"-e\") {\n          options += \"e\";\n        } else if (token == \"-C\" || token == \"--cost\") {\n          options += \"C\";\n        } else if (token == \"-U\" || token == \"--usage\") {\n          options += \"U\";\n        } else if (token == \"-L\" || token == \"--layouts\") {\n          options += \"L\";\n        } else if (token == \"-B\" || token == \"--birth\") {\n          options += \"B\";\n        } else if (token == \"-A\" || token == \"--access\") {\n          options += \"A\";\n        } else if (token == \"-a\" || token == \"--all\") {\n          options += \"Z\";\n        } else if (token == \"-V\" || token == \"--vs\") {\n          options += \"V\";\n        } else if (token == \"-M\" || token == \"--money\") {\n          options += \"M\";\n        } else {\n          return false;\n        }\n      }\n\n      inspector->set_options(options);\n    } else if (token == \"reset\") {\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      eos::console::SpaceProto_ResetProto* reset = space->mutable_reset();\n      reset->set_mgmspace(token);\n\n      while (tokenizer.NextToken(token)) {\n        if (token == \"--egroup\") {\n          reset->set_option(eos::console::SpaceProto_ResetProto::EGROUP);\n        } else if (token == \"--mapping\") {\n          reset->set_option(eos::console::SpaceProto_ResetProto::MAPPING);\n        } else if (token == \"--drain\") {\n          reset->set_option(eos::console::SpaceProto_ResetProto::DRAIN);\n        } else if (token == \"--scheduledrain\") {\n          reset->set_option(eos::console::SpaceProto_ResetProto::SCHEDULEDRAIN);\n        } else if (token == \"--schedulebalance\") {\n          reset->set_option(\n              eos::console::SpaceProto_ResetProto::SCHEDULEBALANCE);\n        } else if (token == \"--ns\") {\n          reset->set_option(eos::console::SpaceProto_ResetProto::NS);\n        } else if (token == \"--nsfilesystemview\") {\n          reset->set_option(\n              eos::console::SpaceProto_ResetProto::NSFILESISTEMVIEW);\n        } else if (token == \"--nsfilemap\") {\n          reset->set_option(eos::console::SpaceProto_ResetProto::NSFILEMAP);\n        } else if (token == \"--nsdirectorymap\") {\n          reset->set_option(\n              eos::console::SpaceProto_ResetProto::NSDIRECTORYMAP);\n        } else {\n          return false;\n        }\n      }\n    } else if (token == \"define\") {\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      eos::console::SpaceProto_DefineProto* define = space->mutable_define();\n      define->set_mgmspace(token);\n\n      if (!tokenizer.NextToken(token)) {\n        define->set_groupsize(0);\n        define->set_groupmod(24);\n      } else {\n        try {\n          define->set_groupsize(std::stoi(token));\n        } catch (...) {\n          return false;\n        }\n\n        if (!tokenizer.NextToken(token)) {\n          define->set_groupmod(24);\n        } else {\n          try {\n            define->set_groupmod(std::stoi(token));\n          } catch (...) {\n            return false;\n          }\n        }\n      }\n    } else if (token == \"set\") {\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      eos::console::SpaceProto_SetProto* set = space->mutable_set();\n      set->set_mgmspace(token);\n\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      if (token == \"on\") {\n        set->set_state_switch(true);\n      } else if (token == \"off\") {\n        set->set_state_switch(false);\n      } else {\n        return false;\n      }\n    } else if (token == \"rm\") {\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      eos::console::SpaceProto_RmProto* rm = space->mutable_rm();\n      rm->set_mgmspace(token);\n    } else if (token == \"status\") {\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      eos::console::SpaceProto_StatusProto* status = space->mutable_status();\n      status->set_mgmspace(token);\n\n      if (tokenizer.NextToken(token)) {\n        if (token == \"-m\") {\n          status->set_outformat_m(true);\n        } else {\n          return false;\n        }\n      }\n    } else if (token == \"node-set\") {\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      eos::console::SpaceProto_NodeSetProto* nodeset = space->mutable_nodeset();\n      nodeset->set_mgmspace(token);\n\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      nodeset->set_nodeset_key(token);\n\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      if (token.find('/') == 0) {\n        std::ifstream ifs(token, std::ios::in | std::ios::binary);\n\n        if (!ifs) {\n          std::cerr << \"error: unable to read \" << token\n                    << \" - errno=\" << errno << '\\n';\n          return false;\n        }\n\n        std::string val = std::string((std::istreambuf_iterator<char>(ifs)),\n                                        std::istreambuf_iterator<char>());\n\n        if (val.length() > 512) {\n          std::cerr << \"error: the file contents exceeds 0.5 kB - configure a \"\n                       \"file hosted on the MGM using file:<mgm-path>\\n\";\n          return false;\n        }\n\n        XrdOucString val64;\n        eos::common::SymKey::Base64Encode((char*)val.c_str(), val.length(),\n                                            val64);\n\n        while (val64.replace(\"=\", \":\")) {}\n\n        nodeset->set_nodeset_value(std::string((\"base64:\" + val64).c_str()));\n      } else {\n        nodeset->set_nodeset_value(token);\n      }\n    } else if (token == \"node-get\") {\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      eos::console::SpaceProto_NodeGetProto* nodeget = space->mutable_nodeget();\n      nodeget->set_mgmspace(token);\n\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      nodeget->set_nodeget_key(token);\n    } else if (token == \"quota\") {\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      eos::console::SpaceProto_QuotaProto* quota = space->mutable_quota();\n      quota->set_mgmspace(token);\n\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      if (token == \"on\") {\n        quota->set_quota_switch(true);\n      } else if (token == \"off\") {\n        quota->set_quota_switch(false);\n      } else {\n        return false;\n      }\n    } else if (token == \"config\") {\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      eos::console::SpaceProto_ConfigProto* config = space->mutable_config();\n\n      if (token == \"rm\") {\n        config->set_remove(true);\n\n        if (!tokenizer.NextToken(token)) {\n          return false;\n        }\n      }\n\n      config->set_mgmspace_name(token);\n\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      if (config->remove()) {\n        config->set_mgmspace_key(token);\n      } else {\n        std::string::size_type pos = token.find('=');\n\n        if ((pos != std::string::npos) &&\n            (std::count(token.begin(), token.end(), '=') == 1)) {\n          config->set_mgmspace_key(token.substr(0, pos));\n          config->set_mgmspace_value(token.substr(pos + 1, token.length() - 1));\n        } else {\n          return false;\n        }\n      }\n    } else if (token == \"groupbalancer\") {\n      auto groupbalancer = space->mutable_groupbalancer();\n\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      if (token == \"status\") {\n        if (!tokenizer.NextToken(token)) {\n          return false;\n        }\n\n        groupbalancer->set_mgmspace(token);\n        auto groupbalancer_status = groupbalancer->mutable_status();\n        std::string options;\n\n        while (tokenizer.NextToken(token)) {\n          if (token == \"--detail\" || token == \"-d\") {\n            options += \"d\";\n          } else if (token == \"-m\") {\n            options += \"m\";\n          }\n        }\n\n        if (!options.empty()) {\n          groupbalancer_status->set_options(options);\n        }\n\n        return true;\n      }\n\n      return false;\n    } else if (token == \"groupdrainer\") {\n      auto groupdrainer = space->mutable_groupdrainer();\n\n      if (!tokenizer.NextToken(token)) {\n        return false;\n      }\n\n      if (token == \"status\") {\n        if (!tokenizer.NextToken(token)) {\n          return false;\n        }\n\n        groupdrainer->set_mgmspace(token);\n        auto status_cmd = groupdrainer->mutable_status();\n\n        if (tokenizer.NextToken(token)) {\n          if (token == \"--detail\" || token == \"-d\") {\n            status_cmd->set_outformat(\n                eos::console::SpaceProto::GroupDrainerStatusProto::DETAIL);\n          } else if (token == \"-m\") {\n            status_cmd->set_outformat(\n                eos::console::SpaceProto::GroupDrainerStatusProto::MONITORING);\n          }\n        }\n\n        return true;\n      } else if (token == \"reset\") {\n        if (!tokenizer.NextToken(token)) {\n          return false;\n        }\n\n        groupdrainer->set_mgmspace(token);\n        auto reset_cmd = groupdrainer->mutable_reset();\n\n        if (!tokenizer.NextToken(token)) {\n          return false;\n        }\n\n        if (token == \"--failed\") {\n          reset_cmd->set_option(\n              eos::console::SpaceProto::GroupDrainerResetProto::FAILED);\n        } else if (token == \"--all\") {\n          reset_cmd->set_option(\n              eos::console::SpaceProto::GroupDrainerResetProto::ALL);\n        }\n      }\n    } else {\n      return false;\n    }\n\n    return true;\n  }\n};\n\nclass SpaceProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"space\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Space configuration\";\n  }\n  std::string\n  helpText() const override\n  {\n    return MakeSpaceHelp();\n  }\n  std::vector<std::string>\n  complete(const std::vector<std::string>& args) const override\n  {\n    return eos_help_completion_candidates(name(), helpText(), args);\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      if (args[i].find(' ') != std::string::npos)\n        oss << std::quoted(args[i]);\n      else\n        oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    SpaceHelper helper(*ctx.globalOpts);\n    if (!helper.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = helper.Execute();\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", helpText().c_str());\n  }\n};\n\n} // namespace\n\nvoid\nRegisterSpaceProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<SpaceProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/squash-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: squash-cmd-native.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/Path.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <pwd.h>\n#include <thread>\n#include <chrono>\n#include <memory>\n#include <sstream>\n#include <string>\n#include <vector>\n#include <cstring>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <sys/wait.h>\n\n/*----------------------------------------------------------------------------*/\n\nnamespace {\n\nstruct SquashParsed {\n  std::string cmd;\n  std::string path;\n  std::string option;   // e.g. \"f\" for -f, or full \"--curl=...\" for install\n  std::string version;  // for new-release\n  std::string keepdays;\n  std::string keepversions;\n};\n\nstd::string MakeSquashHelp()\n{\n  return R\"(usage: squash new <path>                                                  : create a new squashfs under <path>\n\n       squash pack [-f] <path>                                            : pack a squashfs image\n                                                                          -f will recreate the package but keeps the symbolic link locally\n\n       squash unpack [-f] <path>                                          : unpack a squashfs image for modification\n                                                                          -f will atomically update the local package\n\n       squash info <path>                                                 : squashfs information about <path>\n\n       squash rm <path>                                                   : delete a squashfs attached image and its smart link\n\n       squash relabel <path>                                              : relabel a squashfs image link e.g. after an image move in the namespace\n\n       squash install --curl=https://<package>.tgz|.tar.gz <path>         : create a squashfs package from a web archive under <path>\n       squash new-release <path> [<version>]                              : create a new squashfs release under <path>\n       squash pack-release <path>                                         : pack a squashfs release under <path>\n       squash info-release <path>                                         : show all release revisions under <path>\n       squash trim-release <path> <keep-days> [<keep-versions>]           : trim releases older than <keep-days> and keep maximum <keep-versions>\n       squash rm-release <path>                                            : delete all squashfs releases under <path>\n)\";\n}\n\nstatic void squash_usage()\n{\n  std::cerr << MakeSquashHelp();\n  global_retc = EINVAL;\n}\n\n/** Safely convert XrdOucString to std::string (c_str() may return nullptr). */\nstatic std::string to_std_string(const XrdOucString& s)\n{\n  const char* p = s.c_str();\n  return p ? p : \"\";\n}\n\n/** Parse arg string into SquashParsed. Returns true on success. */\nbool ParseSquashArgString(const char* arg1, SquashParsed& out)\n{\n  if (!arg1)\n    return false;\n\n  eos::common::StringTokenizer subtokenizer(arg1);\n  subtokenizer.GetLine();\n  XrdOucString cmd = subtokenizer.GetToken();\n  XrdOucString path = subtokenizer.GetToken();\n  XrdOucString option;\n  XrdOucString fulloption;\n\n  if (!cmd.length())\n    return false;\n\n  if (cmd == \"--help\" || cmd == \"-h\")\n    return false;\n\n  if (path.length() && (path[0] == '-')) {\n    option = path[1];\n    fulloption = path;\n    path = subtokenizer.GetToken();\n  }\n\n  if (!path.length())\n    return false;\n\n  if ((cmd != \"trim-release\") && (cmd != \"new-release\")) {\n    XrdOucString garbage = subtokenizer.GetToken();\n    if (garbage.length())\n      return false;\n  }\n\n  out.cmd = to_std_string(cmd);\n  out.path = to_std_string(path);\n  if (fulloption.beginswith(\"--\"))\n    out.option = to_std_string(fulloption);\n  else if (option.length())\n    out.option = std::string(1, option[0]);\n\n  if (out.cmd == \"new-release\") {\n    XrdOucString version = subtokenizer.GetToken();\n    out.version = to_std_string(version);\n  }\n\n  if (out.cmd == \"trim-release\") {\n    XrdOucString keepdays = subtokenizer.GetToken();\n    XrdOucString keepversions = subtokenizer.GetToken();\n    out.keepdays = to_std_string(keepdays);\n    out.keepversions = to_std_string(keepversions);\n  }\n\n  return true;\n}\n\n/** Core squash implementation - called directly and recursively */\nstatic int squash_impl(const SquashParsed& p);\n\n/** Entry point for recursive/legacy callers - parses string and calls squash_impl */\nint com_squash(char* arg1)\n{\n  if (!arg1) {\n    squash_usage();\n    return 0;\n  }\n  SquashParsed p;\n  if (!ParseSquashArgString(arg1, p)) {\n    squash_usage();\n    return 0;\n  }\n  return squash_impl(p);\n}\n\nstatic int squash_impl(const SquashParsed& p)\n{\n  const std::string& cmd = p.cmd;\n  std::string path = p.path;\n  const std::string& option = p.option;\n  const std::string& version = p.version;\n  const std::string& keepdays = p.keepdays;\n  const std::string& keepversions = p.keepversions;\n\n  bool ok = false;\n  const int len = 4096;\n  char username[len];\n  struct passwd* pw = getpwuid(geteuid());\n\n  if (pw == nullptr) {\n    std::cerr << \"error: failed to get effective UID username of calling process\\n\";\n    squash_usage();\n    return 0;\n  }\n\n  (void)strncpy(username, pw->pw_name, len - 1);\n  username[len - 1] = '\\0';\n\n  path = abspath(path.c_str());\n\n  if (cmd == \"new\") {\n    struct stat buf;\n    eos::common::Path packagepath(path.c_str());\n\n    if (!stat(packagepath.GetPath(), &buf)) {\n      std::cerr << \"error: package path='\" << packagepath.GetPath() << \"' exists already\\n\";\n      global_retc = EEXIST;\n      return 0;\n    }\n\n    std::string mkpath = \"/var/tmp/\";\n    mkpath += username;\n    mkpath += \"/eosxd/mksquash/\";\n    mkpath += packagepath.GetContractedPath();\n    mkpath += \"/dummy\";\n    eos::common::Path mountpath(mkpath.c_str());\n\n    if (!mountpath.MakeParentPath(S_IRWXU | S_IROTH | S_IXOTH | S_IRGRP | S_IXGRP)) {\n      std::cerr << \"error: failed to create local mount point path='\" << mountpath.GetParentPath() << \"'\\n\";\n      global_retc = errno;\n      return 0;\n    }\n\n    if (symlink(mountpath.GetParentPath(), packagepath.GetPath())) {\n      std::cerr << \"error: failed to create symbolic link from '\" << mountpath.GetParentPath()\n                << \"' => '\" << packagepath.GetPath() << \"'\\n\";\n      global_retc = errno;\n      return 0;\n    }\n\n    ok = true;\n    std::cerr << \"info: ready to install your software under '\" << packagepath.GetPath() << \"'\\n\";\n    std::cerr << \"info: when done run 'eos squash pack \" << packagepath.GetPath()\n              << \"' to create an image file and a smart link in EOS!\\n\";\n  }\n\n  if (cmd == \"install\") {\n    if (option.substr(0, 7) == \"--curl=\") {\n      ok = true;\n      std::string url = option.substr(7);\n\n      if (url.size() >= 4 && (url.substr(url.size() - 4) == \".tgz\" ||\n                              (url.size() >= 7 && url.substr(url.size() - 7) == \".tar.gz\"))) {\n        int sub_rc = 0;\n        std::string subcommand = \"rm \\\"\" + path + \"\\\"\";\n        com_squash((char*)subcommand.c_str());\n        sub_rc |= global_retc;\n        subcommand = \"new \\\"\" + path + \"\\\"\";\n        com_squash((char*)subcommand.c_str());\n        sub_rc |= global_retc;\n        std::string shellcmd = \"cd \\\"\" + path + \"\\\"; curl \" + url + \" /dev/stdout | tar xvzf -\";\n        int rc = system(shellcmd.c_str());\n\n        if (WEXITSTATUS(rc)) {\n          std::cerr << \"error: curl download failed with retc='\" << WEXITSTATUS(rc) << \"'\\n\";\n          global_retc = WEXITSTATUS(rc);\n          return 0;\n        }\n\n        subcommand = \"pack \\\"\" + path + \"\\\"\";\n        com_squash((char*)subcommand.c_str());\n        sub_rc |= global_retc;\n\n        if (sub_rc) {\n          global_retc = sub_rc;\n          return 0;\n        }\n      } else {\n        std::cerr << \"error: suffix of '\" << url << \"' is not supported\\n\";\n        global_retc = EINVAL;\n        return 0;\n      }\n    } else {\n      squash_usage();\n      return 0;\n    }\n  }\n\n  if (cmd == \"pack\") {\n    eos::common::Path packagepath(path.c_str());\n    std::string squashpack = packagepath.GetParentPath();\n    squashpack += \".\";\n    squashpack += packagepath.GetName();\n    squashpack += \".sqsh\";\n    std::string shellcmd = \"mksquashfs \";\n    char linktarget[4096];\n    memset(linktarget, 0, sizeof(linktarget));\n    ssize_t rl;\n\n    if ((rl = readlink(packagepath.GetPath(), linktarget, sizeof(linktarget))) == -1) {\n      std::cerr << \"error: failed to resolve symbolic link of squashfs package '\" << packagepath.GetPath()\n                << \"' - errno '\" << errno << \"'\\n\";\n      global_retc = errno;\n      return 0;\n    }\n    linktarget[rl] = 0;\n\n    struct stat buf;\n\n    if (stat(linktarget, &buf)) {\n      std::cerr << \"error: cannot find local package directory '\" << linktarget << \"'\\n\";\n      global_retc = errno;\n      return 0;\n    }\n\n    shellcmd += linktarget;\n    shellcmd += \" \";\n    shellcmd += squashpack;\n    shellcmd += \"~\";\n    shellcmd += \" -noappend -force-uid \";\n    shellcmd += std::to_string(geteuid());\n    shellcmd += \" -force-gid \";\n    shellcmd += std::to_string(getegid());\n    shellcmd += \" && mv -f -T \";\n    shellcmd += squashpack;\n    shellcmd += \"~ \";\n    shellcmd += squashpack;\n    std::cerr << \"running \" << shellcmd << \"\\n\";\n    int rc = system(shellcmd.c_str());\n\n    if (WEXITSTATUS(rc)) {\n      std::cerr << \"error: mksquashfs failed with retc='\" << WEXITSTATUS(rc) << \"'\\n\";\n      global_retc = WEXITSTATUS(rc);\n      return 0;\n    }\n\n    if (option != \"f\") {\n      if (unlink(packagepath.GetPath())) {\n        std::cerr << \"error: failed to unlink locally staged squashfs archive '\" << squashpack\n                  << \"' - errno '\" << errno << \"'\\n\";\n        global_retc = errno;\n        return 0;\n      }\n\n      std::string targetline = \"eosxd get eos.hostport \";\n      targetline += packagepath.GetParentPath();\n      std::string hostport = eos::common::StringConversion::StringFromShellCmd(targetline.c_str());\n\n      if (!hostport.length()) {\n        std::cerr << \"error: failed to get eos.hostport from mountpoint '\" << targetline << \"'\\n\";\n        global_retc = EIO;\n        return 0;\n      }\n\n      std::string target = \"/eos/squashfs/\";\n      target += hostport;\n      target += \"@\";\n      XrdOucString spackagepath = squashpack.c_str();\n      while (spackagepath.replace(\"/\", \"---\")) {}\n\n      target += spackagepath.c_str();\n\n      if (symlink(target.c_str(), packagepath.GetPath())) {\n        std::cerr << \"error: failed to create squashfs symlink '\" << packagepath.GetPath()\n                  << \"' => '\" << target << \"'\\n\";\n      }\n    }\n    ok = true;\n  }\n\n  if (cmd == \"relabel\") {\n    ok = true;\n    struct stat buf;\n    eos::common::Path packagepath(path.c_str());\n    std::string squashpack = packagepath.GetParentPath();\n    squashpack += \".\";\n    squashpack += packagepath.GetName();\n    squashpack += \".sqsh\";\n\n    if (stat(squashpack.c_str(), &buf)) {\n      std::cerr << \"error: the squashfs package file is missing for this label!\\n\";\n      global_retc = ENOENT;\n      return 0;\n    }\n\n    if (!lstat(packagepath.GetPath(), &buf)) {\n      if (unlink(packagepath.GetPath())) {\n        std::cerr << \"error: failed to remove existing squashfs archive '\" << packagepath.GetPath()\n                  << \"' - errno '\" << errno << \"'\\n\";\n        global_retc = errno;\n        return 0;\n      }\n    }\n\n    std::string targetline = \"eosxd get eos.hostport \";\n    targetline += packagepath.GetParentPath();\n    std::string hostport = eos::common::StringConversion::StringFromShellCmd(targetline.c_str());\n\n    if (!hostport.length()) {\n      std::cerr << \"error: failed to get eos.hostport from mountpoint '\" << targetline << \"'\\n\";\n      global_retc = EIO;\n      return 0;\n    }\n\n    std::string target = \"/eos/squashfs/\";\n    target += hostport;\n    target += \"@\";\n    XrdOucString spackagepath = squashpack.c_str();\n    while (spackagepath.replace(\"/\", \"---\")) {}\n\n    target += spackagepath.c_str();\n\n    if (symlink(target.c_str(), packagepath.GetPath())) {\n      std::cerr << \"error: failed to create squashfs symlink '\" << packagepath.GetPath()\n                << \"' => '\" << target << \"'\\n\";\n    }\n  }\n\n  if (cmd == \"unpack\") {\n    ok = true;\n    eos::common::Path packagepath(path.c_str());\n    std::string squashpack = packagepath.GetParentPath();\n    squashpack += \".\";\n    squashpack += packagepath.GetName();\n    squashpack += \".sqsh\";\n    char linktarget[4096];\n    ssize_t rl;\n\n    if ((rl = readlink(packagepath.GetPath(), linktarget, sizeof(linktarget))) == -1) {\n      std::cerr << \"error: failed to resolve symbolic link of squashfs package '\" << packagepath.GetPath()\n                << \"' - errno '\" << errno << \"'\\n\";\n      global_retc = errno;\n      return 0;\n    }\n    linktarget[rl] = 0;\n\n    XrdOucString mounttarget = linktarget;\n    std::string mkpath = \"/var/tmp/\";\n    mkpath += username;\n    mkpath += \"/eosxd/mksquash/\";\n\n    if (option != \"f\") {\n      if (mounttarget.beginswith(mkpath.c_str())) {\n        std::cerr << \"error: squash image is already unpacked!\\n\";\n        global_retc = EINVAL;\n        return 0;\n      }\n\n      if (!geteuid()) {\n        std::string umountcmd = \"umount -f -l \";\n        umountcmd += mounttarget.c_str();\n        (void)!system(umountcmd.c_str());\n\n        if (rmdir(mounttarget.c_str())) {\n          if (errno != ENOENT) {\n            std::cerr << \"error: failed to unlink local mount directory path='\" << mounttarget.c_str()\n                      << \"' errno=\" << errno << \"\\n\";\n          }\n        }\n      }\n    }\n\n    std::string shellcmd = \"unsquashfs -f -d \";\n    mkpath += packagepath.GetContractedPath();\n    mkpath += \"/dummy\";\n    eos::common::Path mountpath(mkpath.c_str());\n\n    if (!mountpath.MakeParentPath(S_IRWXU | S_IROTH | S_IXOTH | S_IRGRP | S_IXGRP)) {\n      std::cerr << \"error: failed to create local mount point path='\" << mountpath.GetParentPath() << \"'\\n\";\n      global_retc = errno;\n      return 0;\n    }\n\n    if (unlink(packagepath.GetPath())) {\n      std::cerr << \"error: failed to unlink smart link for squashfs archive '\" << squashpack\n                << \"' - errno '\" << errno << \"'\\n\";\n      global_retc = errno;\n      return 0;\n    }\n\n    if (symlink(mountpath.GetParentPath(), packagepath.GetPath())) {\n      std::cerr << \"error: failed to create symbolic link from '\" << mountpath.GetParentPath()\n                << \"' => '\" << packagepath.GetPath() << \"'\\n\";\n      global_retc = errno;\n      return 0;\n    }\n\n    shellcmd += mountpath.GetParentPath();\n    shellcmd.erase(shellcmd.length() - 1);\n    shellcmd += \"~ \";\n    shellcmd += squashpack.c_str();\n    shellcmd += \" && rsync -aq --delete \";\n    shellcmd += mountpath.GetParentPath();\n    shellcmd.erase(shellcmd.length() - 1);\n    shellcmd += \"~/ \";\n    shellcmd += mountpath.GetParentPath();\n    shellcmd += \" && rm -rf \";\n    shellcmd += mountpath.GetParentPath();\n    shellcmd.erase(shellcmd.length() - 1);\n    shellcmd += \"~\";\n    std::cout << shellcmd << \"\\n\";\n    int rc = system(shellcmd.c_str());\n\n    if (WEXITSTATUS(rc)) {\n      std::cerr << \"error: unsquashfs failed with retc='\" << WEXITSTATUS(rc) << \"'\\n\";\n      global_retc = WEXITSTATUS(rc);\n      return 0;\n    }\n    std::cerr << \"info: squashfs image is available unpacked under '\" << packagepath.GetPath() << \"'\\n\";\n    std::cerr << \"info: when done with modifications run 'eos squash pack \" << packagepath.GetPath()\n              << \"' to create an image file and a smart link in EOS!\\n\";\n  }\n\n  if (cmd == \"info\") {\n    ok = true;\n    eos::common::Path packagepath(path.c_str());\n    std::string squashpack = packagepath.GetParentPath();\n    squashpack += \".\";\n    squashpack += packagepath.GetName();\n    squashpack += \".sqsh\";\n    struct stat buf;\n\n    if (!stat(squashpack.c_str(), &buf)) {\n      std::cerr << \"info: '\" << squashpack << \"' has a squashfs image with size=\" << (unsigned long)buf.st_size\n                << \" bytes\\n\";\n    } else {\n      std::cerr << \"info: '\" << squashpack << \"' has no squashfs image\\n\";\n    }\n\n    char linktarget[4096];\n    ssize_t rl;\n\n    if ((rl = readlink(packagepath.GetPath(), linktarget, sizeof(linktarget))) == -1) {\n      std::cerr << \"error: failed to resolve symbolic link of squashfs package '\" << packagepath.GetPath()\n                << \"' - errno '\" << errno << \"'\\n\";\n      global_retc = errno;\n      return 0;\n    }\n    linktarget[rl] = 0;\n\n    XrdOucString mounttarget = linktarget;\n    std::string mkpath = \"/var/tmp/\";\n    mkpath += username;\n    mkpath += \"/eosxd/mksquash/\";\n\n    if (mounttarget.beginswith(mkpath.c_str())) {\n      if (stat(linktarget, &buf)) {\n        std::cerr << \"error: cannot find local package directory '\" << linktarget << \"'\\n\";\n        global_retc = EINVAL;\n        return 0;\n      }\n      std::cerr << \"info: squashfs image is currently unpacked/open for local RW mode - use 'eos squash pack \"\n                << packagepath.GetPath() << \"' to close image\\n\";\n    } else {\n      std::cerr << \"info: squashfs image is currently packed - use 'eos squash unpack \"\n                << packagepath.GetPath() << \"' to open image locally\\n\";\n    }\n  }\n\n  if (cmd == \"rm\") {\n    ok = true;\n    eos::common::Path packagepath(path.c_str());\n    std::string squashpack = packagepath.GetParentPath();\n    squashpack += \".\";\n    squashpack += packagepath.GetName();\n    squashpack += \".sqsh\";\n    struct stat buf;\n\n    if (!stat(squashpack.c_str(), &buf)) {\n      if (unlink(squashpack.c_str())) {\n        std::cerr << \"error: failed to remove existing squashfs archive '\" << squashpack\n                  << \"' - errno '\" << errno << \"'\\n\";\n        global_retc = errno;\n        return 0;\n      }\n      std::cerr << \"info: removed squashfs image '\" << squashpack << \"'\\n\";\n    }\n\n    if (!lstat(packagepath.GetPath(), &buf)) {\n      if (unlink(packagepath.GetPath())) {\n        std::cerr << \"error: failed to unlink locally staged squashfs archive '\" << squashpack\n                  << \"' - errno '\" << errno << \"'\\n\";\n        global_retc = errno;\n        return 0;\n      }\n      std::cerr << \"info: removed squashfs smart link '\" << packagepath.GetPath() << \"'\\n\";\n    }\n  }\n\n  if (cmd == \"rm-release\") {\n    std::string scmd = \"info-release \" + path;\n    com_squash((char*)scmd.c_str());\n\n    if (!global_retc) {\n      std::cerr << \"info: wiping squashfs releases under '\" << path << \"'\\n\";\n      eos::common::Path packagepath(path.c_str());\n      std::string nextrelease = std::string(packagepath.GetPath()) + \"/next\";\n      std::string currentrelease = std::string(packagepath.GetPath()) + \"/current\";\n      std::string archive = std::string(packagepath.GetPath()) + \"/.archive\";\n      std::cout << \"info: wiping links current,next ... \\n\";\n      ::unlink(currentrelease.c_str());\n      ::unlink(nextrelease.c_str());\n\n      if (archive.substr(0, 5) == \"/eos/\") {\n        std::cout << \"info: wiping archive ...\\n\";\n        scmd = \"eos rm -rf \" + archive;\n        std::string out = eos::common::StringConversion::StringFromShellCmd(scmd.c_str());\n        std::cout << out;\n      }\n\n      for (size_t i = 0; i < 50; ++i) {\n        struct stat buf;\n\n        if (!::stat(archive.c_str(), &buf)) {\n          std::this_thread::sleep_for(std::chrono::milliseconds(100));\n        } else {\n          break;\n        }\n\n        if (i == 49) {\n          std::cerr << \"=====================================\\n\";\n          std::cerr << \"warning: mount didn't see cleanup ...\\n\";\n          std::cerr << \"remote:\\n\";\n          std::cerr << \"=====================================\\n\";\n          scmd = \"eos ls -la \" + std::string(packagepath.GetPath());\n          std::string out = eos::common::StringConversion::StringFromShellCmd(scmd.c_str());\n          std::cout << out;\n          std::cerr << \"=====================================\\n\";\n          std::cerr << \"local:\\n\";\n          std::cerr << \"=====================================\\n\";\n          scmd = \"ls -la \" + std::string(packagepath.GetPath());\n          out = eos::common::StringConversion::StringFromShellCmd(scmd.c_str());\n          std::cout << out;\n          std::cerr << \"=====================================\\n\";\n        }\n      }\n\n      if (::rmdir(packagepath.GetPath())) {\n        global_retc = errno;\n        std::cerr << \"error: failed to clean squashfs release under '\" << path << \"' error=\" << global_retc << \"\\n\";\n      }\n      return 0;\n    }\n    std::cerr << \"info: there is no squashfs release under '\" << path << \"'\\n\";\n    return 0;\n  }\n\n  if (cmd == \"new-release\") {\n    eos::common::Path packagepath(path.c_str());\n    std::string now = eos::common::StringConversion::StringFromShellCmd(\"date '+%Y%m%d%H%M%S'\");\n\n    if (!version.empty()) {\n      now = version;\n    }\n\n    now.erase(now.find_last_not_of(\" \\n\\r\\t\") + 1);\n    std::string archivepath = std::string(packagepath.GetPath()) + \"/.archive/\";\n    std::string packagename = packagepath.GetName();\n    std::string archivepackage = archivepath + packagename + \"-\" + now;\n    std::string nextrelease = std::string(packagepath.GetPath()) + \"/next\";\n    eos::common::Path archpath(archivepackage.c_str());\n\n    if (!archpath.MakeParentPath(S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) {\n      std::cerr << \"error: couldn't create '\" << archpath.GetParentPath() << \"'\\n\";\n      global_retc = errno;\n      return 0;\n    }\n\n    ::unlink(archivepackage.c_str());\n    ::unlink(nextrelease.c_str());\n    std::string scmd = \"new \\\"\" + archivepackage + \"\\\"\";\n    int rc = com_squash((char*)scmd.c_str());\n\n    if (!rc) {\n      rc = ::symlink(archivepackage.c_str(), nextrelease.c_str());\n\n      if (rc) {\n        std::cerr << \"error: failed to create symbolic link for next release '\" << nextrelease << \"'\\n\";\n        global_retc = errno;\n        return 0;\n      }\n    } else {\n      std::cerr << \"error: failed to create squash package for a new release\\n\";\n      return 0;\n    }\n\n    std::cerr << \"info: install the new release under '\" << nextrelease << \"'\\n\";\n    return 0;\n  }\n\n  if (cmd == \"pack-release\") {\n    char lname[4096];\n    memset(lname, 0, sizeof(lname));\n    eos::common::Path packagepath(path.c_str());\n    std::string nextrelease = std::string(packagepath.GetPath()) + \"/next\";\n    std::string currentrelease = std::string(packagepath.GetPath()) + \"/current\";\n    std::string hiddencurrentrelease = std::string(packagepath.GetPath()) + \"/.current\";\n\n    if (::readlink(nextrelease.c_str(), lname, sizeof(lname)) < 0) {\n      std::cerr << \"error: failed to find an open release package under '\" << nextrelease << \"'\\n\";\n      global_retc = errno;\n      return 0;\n    }\n\n    std::string scmd = \"pack \\\"\" + std::string(lname) + \"\\\"\";\n    int rc = com_squash((char*)scmd.c_str());\n\n    if (!rc) {\n      rc = ::unlink(nextrelease.c_str());\n\n      if (rc) {\n        std::cerr << \"error: failed to unlink open release package under '\" << nextrelease << \"'\\n\";\n        global_retc = errno;\n        return 0;\n      }\n\n      rc = ::symlink(lname, hiddencurrentrelease.c_str());\n\n      if (rc) {\n        std::cerr << \"error: failed to symlink current release package under '\" << hiddencurrentrelease << \"'\\n\";\n        global_retc = errno;\n        return 0;\n      }\n\n      rc = ::rename(hiddencurrentrelease.c_str(), currentrelease.c_str());\n\n      if (rc) {\n        std::cerr << \"error: failed to move '\" << hiddencurrentrelease << \"' to '\" << currentrelease << \"'\\n\";\n        global_retc = errno;\n        return 0;\n      }\n      std::cout << \"info: new release available under '\" << currentrelease << \"'\\n\";\n      return 0;\n    }\n    std::cerr << \"error: failed to pack squash package for a new release\\n\";\n    return 0;\n  }\n\n  if (cmd == \"info-release\") {\n    std::string scmd = \"trim-release \\\"\" + path + \"\\\" 999999 999999\";\n    com_squash((char*)scmd.c_str());\n    return 0;\n  }\n\n  if (cmd == \"trim-release\") {\n    eos::common::Path packagepath(path.c_str());\n    std::string current = packagepath.GetPath();\n    std::string archive = packagepath.GetPath();\n    current += \"/current\";\n    archive += \"/.archive\";\n    struct stat buf;\n\n    if (::lstat(current.c_str(), &buf)) {\n      std::cerr << \"error: I cannot find any current release under '\" << current << \"'\\n\";\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    if (::lstat(archive.c_str(), &buf)) {\n      std::cerr << \"error: I cannot find any archive release under '\" << archive << \"'\\n\";\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    if (keepdays.empty()) {\n      std::cerr << \"error: you have to specify the number of days you want to keep releases : squash trim-release \"\n                   \"<path> <n-days> [<max-versions>]\\n\";\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    size_t n_keepdays = strtol(keepdays.c_str(), 0, 10);\n\n    if (!n_keepdays) {\n      std::cerr << \"error: you have to specify the number of days you want to keep releases : squash trim-release \"\n                   \"<path> <n-days>\\n\";\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    std::string keepversions_val = keepversions;\n    size_t n_keepversions = keepversions_val.length() ? strtol(keepversions_val.c_str(), 0, 10) : 0;\n\n    if (!n_keepversions) {\n      keepversions_val = \"1000000\";\n    } else {\n      keepversions_val = std::to_string(n_keepversions + 1);\n    }\n\n    std::string find1 = \"find \" + std::string(packagepath.GetPath()) + \" -type f -mtime +\" + keepdays + \" -delete\";\n    std::string find2 = \"find \" + std::string(packagepath.GetPath()) + \" -type l -mtime +\" + keepdays + \" -delete\";\n    std::string find3 = \"find \" + std::string(packagepath.GetPath()) + \"/.archive/ -type f -printf '%Ts\\t%h/%f\\n' | sort -rn | tail -n +\" +\n                        keepversions_val + \" | cut -f2- | xargs -r rm\";\n    std::string find4 = \"find \" + std::string(packagepath.GetPath()) + \"/.archive/ -type l -printf '%Ts\\t%h/%f\\n' | sort -rn | tail -n +\" +\n                        keepversions_val + \" | cut -f2- | xargs -r rm\";\n    std::string find5 = \"find \" + std::string(packagepath.GetPath()) + \" -type l\";\n\n    eos::common::StringConversion::StringFromShellCmd(find1.c_str());\n    eos::common::StringConversion::StringFromShellCmd(find2.c_str());\n    eos::common::StringConversion::StringFromShellCmd(find3.c_str());\n    eos::common::StringConversion::StringFromShellCmd(find4.c_str());\n    std::string out = eos::common::StringConversion::StringFromShellCmd(find5.c_str());\n\n    std::cout << \"---------------------------------------------------------------------------\\n\";\n    std::cout << \"- releases of '\" << packagepath.GetPath() << \"' \\n\";\n    std::cout << \"---------------------------------------------------------------------------\\n\";\n    std::cout << out;\n    std::cout << \"---------------------------------------------------------------------------\\n\";\n    return 0;\n  }\n\n  if (!ok) {\n    squash_usage();\n    return 0;\n  }\n\n  return 0;\n}\n\nclass SquashCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"squash\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Squashfs utility for EOS\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (args.empty() || wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    return com_squash((char*)joined.c_str());\n  }\n  void\n  printHelp() const override\n  {\n    std::cerr << MakeSquashHelp();\n  }\n};\n\n} // namespace\n\nvoid\nRegisterSquashNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<SquashCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/stat-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: stat-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringConversion.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <memory>\n#include <sstream>\n\nnamespace {\nclass StatCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"stat\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Run 'stat' on a file or directory\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    XrdOucString option = \"\";\n    XrdOucString path = \"\";\n    for (const auto& a : args) {\n      if (a == \"--help\" || a == \"-h\") {\n        fprintf(stderr, \"Usage: stat [-f|-d]    <path>                         \"\n                        \"                         :  stat <path>\\n\");\n        fprintf(stderr,\n                \"                    -f : checks if <path> is a file\\n\");\n        fprintf(stderr,\n                \"                    -d : checks if <path> is a directory\\n\");\n        global_retc = EINVAL;\n        return 0;\n      }\n      if (!a.empty() && a[0] == '-') {\n        XrdOucString p = a.c_str();\n        while (p.replace(\"-\", \"\")) {\n        }\n        option += p;\n      } else {\n        if (!path.length())\n          path = a.c_str();\n      }\n    }\n    if (!path.length())\n      path = gPwd.c_str();\n    if ((option.length()) && ((option != \"f\") && (option != \"d\"))) {\n      fprintf(stderr, \"error: unknown option \\\"%s\\\"\\n\", option.c_str());\n      global_retc = EINVAL;\n      return 0;\n    }\n    path = abspath(path.c_str());\n    XrdOucString url = serveruri.c_str();\n    url += \"/\";\n    url += path;\n    struct stat buf;\n    XrdOucString sizestring;\n    if (!XrdPosixXrootd::Stat(url.c_str(), &buf)) {\n      if ((option.find(\"f\") != STR_NPOS)) {\n        global_retc = S_ISREG(buf.st_mode) ? 0 : 1;\n        return 0;\n      }\n      if ((option.find(\"d\") != STR_NPOS)) {\n        global_retc = S_ISDIR(buf.st_mode) ? 0 : 1;\n        return 0;\n      }\n      fprintf(stdout, \"  File: '%s'\", path.c_str());\n      if (S_ISDIR(buf.st_mode)) {\n        fprintf(stdout, \" directory\\n\");\n      } else if (S_ISREG(buf.st_mode)) {\n        fprintf(stdout, \"  Size: %llu            %s\",\n                (unsigned long long)buf.st_size,\n                eos::common::StringConversion::GetReadableSizeString(\n                    sizestring, (unsigned long long)buf.st_size, \"B\"));\n        fprintf(stdout, \" regular file\\n\");\n      } else {\n        fprintf(stdout, \" symbolic link\\n\");\n      }\n      global_retc = 0;\n    } else {\n      fprintf(stderr, \"error: failed to stat %s\\n\", path.c_str());\n      global_retc = EFAULT;\n      return 0;\n    }\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n  }\n};\n} // namespace\n\nvoid\nRegisterStatNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<StatCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/status-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: status-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include <memory>\n\nnamespace {\nclass StatusCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"status\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Display status information on an MGM\";\n  }\n  bool\n  requiresMgm(const std::string&) const override\n  {\n    return false;\n  }\n  int\n  run(const std::vector<std::string>&, CommandContext&) override\n  {\n    (void)!system(\"eos-status\");\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n  }\n};\n} // namespace\n\nvoid\nRegisterStatusNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<StatusCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/test-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: test-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <memory>\n#include <sstream>\n\nnamespace {\n\nint RunCmd(const std::string& name, const std::vector<std::string>& args) {\n  IConsoleCommand* cmd = CommandRegistry::instance().find(name);\n  if (!cmd) {\n    fprintf(stderr, \"error: command '%s' not available\\n\", name.c_str());\n    return EINVAL;\n  }\n  CommandContext ctx;\n  ctx.serverUri = serveruri.c_str();\n  ctx.globalOpts = &gGlobalOpts;\n  ctx.json = json;\n  ctx.silent = silent;\n  ctx.interactive = interactive;\n  ctx.timing = timing;\n  ctx.userRole = user_role.c_str();\n  ctx.groupRole = group_role.c_str();\n  ctx.clientCommand = &client_command;\n  ctx.outputResult = &output_result;\n  return cmd->run(args, ctx);\n}\n\nclass TestCommand : public IConsoleCommand {\npublic:\n  const char* name() const override { return \"test\"; }\n  const char* description() const override { return \"Run performance test\"; }\n  bool requiresMgm(const std::string& args) const override { return !wants_help(args.c_str()); }\n  int run(const std::vector<std::string>& args, CommandContext&) override {\n    if (args.size() < 2) { printHelp(); global_retc = EINVAL; return 0; }\n    const std::string& tag = args[0];\n    unsigned int n = 0;\n    try { n = std::stoul(args[1]); } catch (...) { printHelp(); global_retc = EINVAL; return 0; }\n\n    auto make_base = [](unsigned int i) {\n      char buf[32]; snprintf(buf, sizeof(buf), \"/test/%02u\", i); return std::string(buf);\n    };\n\n    int rc = 0;\n    if (tag == \"mkdir\") {\n      for (unsigned int i = 0; i < 10; ++i) {\n        std::string base = make_base(i);\n        rc |= RunCmd(\"mkdir\", {base});\n        for (unsigned int j = 0; j < n / 10; ++j) {\n          char sub[64]; snprintf(sub, sizeof(sub), \"%s/%05u\", base.c_str(), j);\n          rc |= RunCmd(\"mkdir\", {sub});\n        }\n      }\n    } else if (tag == \"rmdir\") {\n      for (unsigned int i = 0; i < 10; ++i) {\n        std::string base = make_base(i);\n        for (unsigned int j = 0; j < n / 10; ++j) {\n          char sub[64]; snprintf(sub, sizeof(sub), \"%s/%05u\", base.c_str(), j);\n          rc |= RunCmd(\"rmdir\", {sub});\n        }\n        rc |= RunCmd(\"rmdir\", {base});\n      }\n    } else if (tag == \"ls\") {\n      for (unsigned int i = 0; i < 10; ++i) {\n        rc |= RunCmd(\"ls\", {make_base(i)});\n      }\n    } else if (tag == \"lsla\") {\n      for (unsigned int i = 0; i < 10; ++i) {\n        rc |= RunCmd(\"ls\", {\"-la\", make_base(i)});\n      }\n    } else {\n      printHelp(); global_retc = EINVAL; return 0;\n    }\n\n    global_retc = rc;\n    return 0;\n  }\n  void printHelp() const override {\n    fprintf(stderr,\n            \"Usage: test [mkdir|rmdir|ls|lsla <N> ]                                             :  run performance test\\n\");\n  }\n};\n} // namespace\n\nvoid RegisterTestNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<TestCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/token-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: token-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/commands/helpers/TokenHelper.hh\"\n#include <CLI/CLI.hpp>\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeTokenHelp()\n{\n  return \"Usage: token --token <token> | --path <path> --expires <expires> \"\n         \"[--permission <perm>] [--owner <owner>] [--group <group>] [--tree] \"\n         \"[--origin <origin> ...]\\n\\n\"\n         \"  --token <token>   dump token JSON (independent of validity)\\n\"\n         \"  --path <path>     namespace restriction (directory or file)\\n\"\n         \"  --permission <perm>  e.g. 'rx' 'rwx' 'rwx!d' 'rwxq'\\n\"\n         \"  --owner <owner>   identify bearer as user\\n\"\n         \"  --group <group>   identify bearer with group\\n\"\n         \"  --tree            subtree token for whole tree under path\\n\"\n         \"  --origin <origin> restrict usage (regexp:hostname:username:protocol)\\n\";\n}\n\nvoid ConfigureTokenApp(CLI::App& app)\n{\n  app.name(\"token\");\n  app.description(\"Token interface\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeTokenHelp();\n      }));\n}\n\nclass TokenProtoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"token\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Token interface\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    (void)ctx;\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    TokenHelper token(gGlobalOpts);\n    if (!token.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = token.Execute(true, true);\n    return global_retc;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureTokenApp(app);\n    std::cerr << app.help();\n  }\n};\n} // namespace\n\nvoid\nRegisterTokenProtoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<TokenProtoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/touch-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: touch-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeTouchHelp()\n{\n  return \"Usage: touch [-a] [-n] [-0] <path> [linkpath|size [hexchecksum]]\\n\"\n         \"       touch -l <path> [lifetime [audience=user|app]]\\n\"\n         \"       touch -u <path>\\n\\n\"\n         \"Touch a file. Delegates to 'file touch'.\\n\\n\"\n         \"Options:\\n\"\n         \"  -a  absorb\\n\"\n         \"  -n  no layout\\n\"\n         \"  -0  truncate\\n\"\n         \"  -l  lock\\n\"\n         \"  -u  unlock\\n\";\n}\n\nvoid ConfigureTouchApp(CLI::App& app)\n{\n  app.name(\"touch\");\n  app.description(\"Touch a file\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeTouchHelp();\n      }));\n}\n\nclass TouchCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"touch\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Touch a file\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    IConsoleCommand* fileCmd = CommandRegistry::instance().find(\"file\");\n    if (!fileCmd) {\n      fprintf(stderr, \"error: 'file' command not available\\n\");\n      global_retc = EINVAL;\n      return 0;\n    }\n    if (args.empty() || wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    ConfigureTouchApp(app);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    std::vector<std::string> remaining = app.remaining();\n    std::reverse(remaining.begin(), remaining.end());\n    if (remaining.empty()) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    std::vector<std::string> fargs;\n    fargs.reserve(remaining.size() + 1);\n    fargs.push_back(\"touch\");\n    fargs.insert(fargs.end(), remaining.begin(), remaining.end());\n    return fileCmd->run(fargs, ctx);\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeTouchHelp().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterTouchNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<TouchCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/tracker-proto-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: tracker-proto-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringTokenizer.hh\"\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include \"console/ConsoleMain.hh\"\n#include \"console/commands/helpers/ICmdHelper.hh\"\n#include <iomanip>\n#include <memory>\n#include <sstream>\n\nnamespace {\nstd::string MakeTrackerHelp()\n{\n  std::ostringstream oss;\n  oss << \" usage:\\n\"\n      << std::endl\n      << \"tracker : print all file replication tracking entries\\n\"\n      << std::endl\n      << \"This is the standalone alias for 'space tracker' (same request as the\\n\"\n      << \"space subcommand; the MGM space defaults to \\\"default\\\").\\n\"\n      << std::endl\n      << \"Layout creation tracking for a space can be enabled or disabled with:\\n\"\n      << \"  space config <space-name> space.tracker=on|off [ default=off ]\\n\";\n  return oss.str();\n}\n\nvoid ConfigureTrackerApp(CLI::App& app)\n{\n  app.name(\"tracker\");\n  app.description(\"Print file replication tracking entries\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeTrackerHelp();\n      }));\n}\n\n// Minimal helper mirroring the legacy \"space tracker\" handling\nclass TrackerHelper : public ICmdHelper {\npublic:\n  explicit TrackerHelper(const GlobalOptions& opts) : ICmdHelper(opts) {}\n  bool\n  ParseCommand(const char* /*arg*/) override\n  {\n    eos::console::SpaceProto* space = mReq.mutable_space();\n    eos::console::SpaceProto_TrackerProto* tracker = space->mutable_tracker();\n    tracker->set_mgmspace(\"default\");\n    return true;\n  }\n};\n\nclass TrackerCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"tracker\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Print file replication tracking entries\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      if (args[i].find(' ') != std::string::npos)\n        oss << std::quoted(args[i]);\n      else\n        oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    TrackerHelper helper(*ctx.globalOpts);\n    if (!helper.ParseCommand(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    global_retc = helper.Execute(true, true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    ConfigureTrackerApp(app);\n    fprintf(stderr, \"%s\", app.help().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterTrackerNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<TrackerCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/tui-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: tui-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n\n#include <cerrno>\n#include <cstring>\n#include <memory>\n#include <string>\n#include <sys/wait.h>\n#include <unistd.h>\n#include <vector>\n\nnamespace {\nclass TuiCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"tui\";\n  }\n\n  const char*\n  description() const override\n  {\n    return \"Launch the eos-tui terminal UI\";\n  }\n\n  bool\n  requiresMgm(const std::string&) const override\n  {\n    return false;\n  }\n\n  int\n  run(const std::vector<std::string>& args, CommandContext&) override\n  {\n    std::vector<std::string> ownedArgs;\n    ownedArgs.reserve(args.size() + 1);\n    ownedArgs.emplace_back(\"eos-tui\");\n    ownedArgs.insert(ownedArgs.end(), args.begin(), args.end());\n\n    std::vector<char*> argv;\n    argv.reserve(ownedArgs.size() + 1);\n\n    for (auto& arg : ownedArgs) {\n      argv.push_back(arg.data());\n    }\n\n    argv.push_back(nullptr);\n\n    pid_t pid = fork();\n\n    if (pid < 0) {\n      fprintf(stderr, \"error: failed to fork eos-tui launcher: %s\\n\", strerror(errno));\n      return (global_retc = errno ? errno : EIO);\n    }\n\n    if (pid == 0) {\n      execvp(argv.front(), argv.data());\n      const int execErrno = errno;\n\n      if (execErrno == ENOENT) {\n        fprintf(stderr, \"error: 'eos-tui' is not installed. Install the EOS client \"\n                        \"package with TUI support.\\n\");\n      } else {\n        fprintf(stderr, \"error: failed to launch eos-tui: %s\\n\", strerror(execErrno));\n      }\n\n      _exit(execErrno == ENOENT ? 127 : 126);\n    }\n\n    int status = 0;\n\n    while ((waitpid(pid, &status, 0) < 0) && (errno == EINTR)) {\n    }\n\n    if (WIFEXITED(status)) {\n      return (global_retc = WEXITSTATUS(status));\n    }\n\n    if (WIFSIGNALED(status)) {\n      return (global_retc = 128 + WTERMSIG(status));\n    }\n\n    return (global_retc = EIO);\n  }\n\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"usage: tui [eos-tui-args]\\n\"\n                    \"\\n\"\n                    \"Launch the eos-tui terminal UI.\\n\"\n                    \"Any additional arguments are passed through to eos-tui.\\n\");\n  }\n};\n} // namespace\n\nvoid\nRegisterTuiNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<TuiCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/version-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: version-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeVersionHelp()\n{\n  return \"Usage: version [-f] [-m]\\n\\n\"\n         \"Print EOS version number.\\n\\n\"\n         \"Options:\\n\"\n         \"  -f, --features   print the list of supported features\\n\"\n         \"  -m, --monitoring print in monitoring format\\n\";\n}\n\nvoid ConfigureVersionApp(CLI::App& app, bool& opt_f, bool& opt_m)\n{\n  app.name(\"version\");\n  app.description(\"Verbose client/server version\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeVersionHelp();\n      }));\n  app.add_flag(\"-f,--features\", opt_f, \"print supported features\");\n  app.add_flag(\"-m,--monitoring\", opt_m, \"print in monitoring format\");\n}\n\nclass VersionCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"version\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Verbose client/server version\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    bool opt_f = false;\n    bool opt_m = false;\n    ConfigureVersionApp(app, opt_f, opt_m);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    XrdOucString in = \"mgm.cmd=version\";\n    std::string opts;\n    if (opt_f)\n      opts += \"f\";\n    if (opt_m)\n      opts += \"m\";\n    if (!opts.empty()) {\n      in += \"&mgm.option=\";\n      in += opts.c_str();\n    }\n    global_retc = ctx.outputResult(ctx.clientCommand(in, false, nullptr), true);\n    if (opts.find('m') == std::string::npos && !ctx.json) {\n      fprintf(stdout, \"EOS_CLIENT_VERSION=%s EOS_CLIENT_RELEASE=%s\\n\", VERSION,\n              RELEASE);\n    }\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeVersionHelp().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterVersionNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<VersionCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/vid-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: vid-native.cc\n// ----------------------------------------------------------------------\n\n#include \"common/StringTokenizer.hh\"\n#include \"common/Utils.hh\"\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleMain.hh\"\n#include <CLI/CLI.hpp>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeVidHelp()\n{\n  return \"Usage: vid ls [-u] [-g] [-s] [-U] [-G] [-a] [-l] [-n]\\n\"\n         \"       vid set membership <uid> -uids|-gids [<list>]\\n\"\n         \"       vid set membership <uid> [+|-]sudo\\n\"\n         \"       vid set map -krb5|-gsi|-https|-sss|-unix|-voms|-grpc|-oauth2 <pattern> [vuid:<uid>] [vgid:<gid>]\\n\"\n         \"       vid set geotag <IP-prefix> <geotag>\\n\"\n         \"       vid rm <key> | vid rm membership <uid>\\n\"\n         \"       vid enable|disable krb5|gsi|sss|unix|https|grpc|oauth2|ztn\\n\"\n         \"       vid add|remove gateway <hostname> [prot]\\n\"\n         \"       vid publicaccesslevel <level>\\n\"\n         \"       vid tokensudo 0|1|2|3\\n\\n\"\n         \"VID tools for user/group mapping and authentication.\\n\";\n}\n\nvoid ConfigureVidApp(CLI::App& app, std::string& subcmd)\n{\n  app.name(\"vid\");\n  app.description(\"VID tools\");\n  app.set_help_flag(\"\");\n  app.allow_extras();\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App*, std::string, CLI::AppFormatMode) {\n        return MakeVidHelp();\n      }));\n  app.add_option(\"subcmd\", subcmd, \"ls|set|rm|enable|disable|add|remove|publicaccesslevel|tokensudo\")\n      ->required();\n}\n\nclass VidCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"vid\";\n  }\n  const char*\n  description() const override\n  {\n    return \"VID tools\";\n  }\n  bool\n  requiresMgm(const std::string&) const override\n  {\n    return false;\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    std::ostringstream oss;\n    for (size_t i = 0; i < args.size(); ++i) {\n      if (i)\n        oss << ' ';\n      oss << args[i];\n    }\n    std::string joined = oss.str();\n    if (args.empty() || wants_help(joined.c_str())) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    CLI::App app;\n    std::string subcmd;\n    ConfigureVidApp(app, subcmd);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    eos::common::StringTokenizer tok(joined.c_str());\n    tok.GetLine();\n    XrdOucString sub = tok.GetTokenUnquoted();\n    if (!sub.length()) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    if (sub == \"ls\") {\n      XrdOucString in = \"mgm.cmd=vid&mgm.subcmd=ls\";\n      XrdOucString t;\n      for (;;) {\n        t = tok.GetTokenUnquoted();\n        if (!t.length())\n          break;\n        if (t.beginswith(\"-\")) {\n          t.erase(0, 1);\n          in += \"&mgm.vid.option=\";\n          in += t;\n        } else {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n      }\n      global_retc =\n          ctx.outputResult(ctx.clientCommand(in, true, nullptr), true);\n      return 0;\n    }\n\n    if (sub == \"set\") {\n      XrdOucString in = \"mgm.cmd=vid&mgm.subcmd=set\";\n      XrdOucString key = tok.GetTokenUnquoted();\n      if (!key.length()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      if (key == \"geotag\") {\n        XrdOucString match = tok.GetTokenUnquoted();\n        if (!match.length()) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        XrdOucString target = tok.GetTokenUnquoted();\n        if (!target.length()) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        std::string geotag = eos::common::SanitizeGeoTag(target.c_str());\n        if (geotag != target.c_str()) {\n          fprintf(stderr, \"%s\\n\", geotag.c_str());\n          return 0;\n        }\n        in += \"&mgm.vid.cmd=geotag&mgm.vid.key=geotag:\";\n        in += match;\n        in += \"&mgm.vid.geotag=\";\n        in += target;\n        global_retc =\n            ctx.outputResult(ctx.clientCommand(in, true, nullptr), true);\n        return 0;\n      }\n      if (key == \"membership\") {\n        XrdOucString uid = tok.GetTokenUnquoted();\n        if (!uid.length()) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        XrdOucString type = tok.GetTokenUnquoted();\n        if (!type.length()) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        in += \"&mgm.vid.source.uid=\";\n        in += uid;\n\n        if (type == \"-uids\") {\n          XrdOucString list = tok.GetTokenUnquoted();\n          in += \"&mgm.vid.cmd=membership\";\n          in += \"&mgm.vid.key=\";\n          in += (XrdOucString)(uid + \":uids\");\n          in += \"&mgm.vid.target.uid=\";\n          in += list;\n        } else if (type == \"-gids\") {\n          XrdOucString list = tok.GetTokenUnquoted();\n          in += \"&mgm.vid.cmd=membership\";\n          in += \"&mgm.vid.key=\";\n          in += (XrdOucString)(uid + \":gids\");\n          in += \"&mgm.vid.target.gid=\";\n          in += list;\n        } else if (type == \"+sudo\") {\n          in += \"&mgm.vid.cmd=membership&mgm.vid.key=\";\n          in += (XrdOucString)(uid + \":root\");\n          in += \"&mgm.vid.target.sudo=true\";\n        } else if (type == \"-sudo\") {\n          in += \"&mgm.vid.cmd=membership&mgm.vid.key=\";\n          in += (XrdOucString)(uid + \":root\");\n          in += \"&mgm.vid.target.sudo=false\";\n        } else {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        global_retc =\n            ctx.outputResult(ctx.clientCommand(in, true, nullptr), true);\n        return 0;\n      }\n      if (key == \"map\") {\n        XrdOucString type = tok.GetTokenUnquoted();\n        if (!type.length()) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        XrdOucString in = \"mgm.cmd=vid&mgm.subcmd=set&mgm.vid.cmd=map\";\n        bool hastype = false;\n        auto add_auth = [&](const char* a) {\n          in += \"&mgm.vid.auth=\";\n          in += a;\n          hastype = true;\n        };\n        if (type == \"-krb5\")\n          add_auth(\"krb5\");\n        if (type == \"-gsi\")\n          add_auth(\"gsi\");\n        if (type == \"-https\")\n          add_auth(\"https\");\n        if (type == \"-sss\")\n          add_auth(\"sss\");\n        if (type == \"-unix\")\n          add_auth(\"unix\");\n        if (type == \"-tident\")\n          add_auth(\"tident\");\n        if (type == \"-voms\")\n          add_auth(\"voms\");\n        if (type == \"-grpc\")\n          add_auth(\"grpc\");\n        if (type == \"-oauth2\")\n          add_auth(\"oauth2\");\n        if (!hastype) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        XrdOucString pattern = tok.GetTokenUnquoted();\n        if (!pattern.length()) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        in += \"&mgm.vid.pattern=\";\n        in += pattern;\n        XrdOucString vid = tok.GetTokenUnquoted();\n        if (!vid.length()) {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        if (vid.beginswith(\"vuid:\")) {\n          vid.replace(\"vuid:\", \"\");\n          in += \"&mgm.vid.uid=\";\n          in += vid;\n          XrdOucString vg = tok.GetTokenUnquoted();\n          if (vg.length() && vg.beginswith(\"vgid:\")) {\n            vg.replace(\"vgid:\", \"\");\n            in += \"&mgm.vid.gid=\";\n            in += vg;\n          }\n        } else if (vid.beginswith(\"vgid:\")) {\n          vid.replace(\"vgid:\", \"\");\n          in += \"&mgm.vid.gid=\";\n          in += vid;\n        } else {\n          printHelp();\n          global_retc = EINVAL;\n          return 0;\n        }\n        in += \"&mgm.vid.key=<key>\";\n        global_retc =\n            ctx.outputResult(ctx.clientCommand(in, true, nullptr), true);\n        return 0;\n      }\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    if (sub == \"enable\" || sub == \"disable\") {\n      XrdOucString in = \"mgm.cmd=vid&mgm.subcmd=set&mgm.vid.cmd=map\";\n      XrdOucString disableu =\n          \"mgm.cmd=vid&mgm.subcmd=rm&mgm.vid.cmd=unmap&mgm.vid.key=\";\n      XrdOucString disableg =\n          \"mgm.cmd=vid&mgm.subcmd=rm&mgm.vid.cmd=unmap&mgm.vid.key=\";\n      XrdOucString type = tok.GetTokenUnquoted();\n      if (!type.length()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      auto set_default = [&](const char* a) {\n        in += \"&mgm.vid.auth=\";\n        in += a;\n        disableu += a;\n        disableu += \":\\\"<pwd>\\\":uid\";\n        disableg += a;\n        disableg += \":\\\"<pwd>\\\":gid\";\n      };\n      if (type == \"krb5\")\n        set_default(\"krb5\");\n      else if (type == \"sss\")\n        set_default(\"sss\");\n      else if (type == \"gsi\")\n        set_default(\"gsi\");\n      else if (type == \"https\")\n        set_default(\"https\");\n      else if (type == \"unix\")\n        set_default(\"unix\");\n      else if (type == \"grpc\")\n        set_default(\"grpc\");\n      else if (type == \"oauth2\")\n        set_default(\"oauth2\");\n      else if (type == \"tident\")\n        set_default(\"tident\");\n      else if (type == \"ztn\")\n        set_default(\"ztn\");\n      else {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      in += \"&mgm.vid.pattern=<pwd>\";\n      if (type != \"unix\") {\n        in += \"&mgm.vid.uid=0&mgm.vid.gid=0\";\n      } else {\n        in += \"&mgm.vid.uid=99&mgm.vid.gid=99\";\n      }\n      in += \"&mgm.vid.key=<key>\";\n      if (sub == \"enable\") {\n        global_retc =\n            ctx.outputResult(ctx.clientCommand(in, true, nullptr), true);\n      } else {\n        global_retc =\n            ctx.outputResult(ctx.clientCommand(disableu, true, nullptr), true);\n        global_retc |=\n            ctx.outputResult(ctx.clientCommand(disableg, true, nullptr), true);\n      }\n      return 0;\n    }\n\n    if (sub == \"publicaccesslevel\") {\n      XrdOucString in =\n          \"mgm.cmd=vid&mgm.subcmd=set&mgm.vid.cmd=publicaccesslevel\";\n      XrdOucString level = tok.GetTokenUnquoted();\n      if (!level.length()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      in += \"&mgm.vid.key=publicaccesslevel&mgm.vid.level=\";\n      in += level;\n      global_retc =\n          ctx.outputResult(ctx.clientCommand(in, true, nullptr), true);\n      return 0;\n    }\n\n    if (sub == \"tokensudo\") {\n      XrdOucString in = \"mgm.cmd=vid&mgm.subcmd=set&mgm.vid.cmd=tokensudo\";\n      XrdOucString lvl = tok.GetTokenUnquoted();\n      if (!lvl.length()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      in += \"&mgm.vid.key=tokensudo&mgm.vid.tokensudo=\";\n      in += lvl;\n      global_retc =\n          ctx.outputResult(ctx.clientCommand(in, true, nullptr), true);\n      return 0;\n    }\n\n    if (sub == \"add\" || sub == \"remove\") {\n      XrdOucString gw = tok.GetTokenUnquoted();\n      if (gw != \"gateway\") {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString host = tok.GetTokenUnquoted();\n      if (!host.length()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      XrdOucString protocol = tok.GetTokenUnquoted();\n      if (!protocol.length())\n        protocol = \"*\";\n      XrdOucString in = \"mgm.cmd=vid&mgm.subcmd=set&mgm.vid.cmd=map\";\n      XrdOucString disableu =\n          \"mgm.cmd=vid&mgm.subcmd=rm&mgm.vid.cmd=unmap&mgm.vid.key=\";\n      XrdOucString disableg =\n          \"mgm.cmd=vid&mgm.subcmd=rm&mgm.vid.cmd=unmap&mgm.vid.key=\";\n      in += \"&mgm.vid.auth=tident&mgm.vid.pattern=\\\"\";\n      in += protocol;\n      in += \"@\";\n      in += host;\n      in += \"\\\"&mgm.vid.uid=0&mgm.vid.gid=0&mgm.vid.key=<key>\";\n      disableu += \"tident:\\\"\";\n      disableu += protocol;\n      disableu += \"@\";\n      disableu += host;\n      disableu += \"\\\":uid\";\n      disableg += \"tident:\\\"\";\n      disableg += protocol;\n      disableg += \"@\";\n      disableg += host;\n      disableg += \"\\\":gid\";\n      if (sub == \"add\")\n        global_retc =\n            ctx.outputResult(ctx.clientCommand(in, true, nullptr), true);\n      else {\n        global_retc =\n            ctx.outputResult(ctx.clientCommand(disableu, true, nullptr), true);\n        global_retc |=\n            ctx.outputResult(ctx.clientCommand(disableg, true, nullptr), true);\n      }\n      return 0;\n    }\n\n    if (sub == \"rm\") {\n      XrdOucString key = tok.GetToken();\n      key.replace(\"\\\\\\\"\", \"\\\"\");\n      if (!key.length()) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      if (key == \"membership\") {\n        key = tok.GetToken();\n        key.replace(\"\\\\\\\"\", \"\\\"\");\n        key.insert(\"vid:\", 0);\n        XrdOucString key1 = key;\n        XrdOucString key2 = key;\n        key1 += \":uids\";\n        key2 += \":gids\";\n        XrdOucString in1 = \"mgm.cmd=vid&mgm.subcmd=rm&mgm.vid.key=\";\n        in1 += key1;\n        XrdOucString in2 = \"mgm.cmd=vid&mgm.subcmd=rm&mgm.vid.key=\";\n        in2 += key2;\n        global_retc =\n            ctx.outputResult(ctx.clientCommand(in1, true, nullptr), true);\n        global_retc |=\n            ctx.outputResult(ctx.clientCommand(in2, true, nullptr), true);\n        return 0;\n      }\n      XrdOucString in = \"mgm.cmd=vid&mgm.subcmd=rm&mgm.vid.key=\";\n      in += key;\n      global_retc =\n          ctx.outputResult(ctx.clientCommand(in, true, nullptr), true);\n      return 0;\n    }\n\n    printHelp();\n    global_retc = EINVAL;\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeVidHelp().c_str());\n    fprintf(stderr,\n        \"Options for vid ls:\\n\"\n        \"  -u  show only user role mappings\\n\"\n        \"  -g  show only group role mappings\\n\"\n        \"  -s  show list of sudoers\\n\"\n        \"  -U  show user alias mapping\\n\"\n        \"  -G  show group alias mapping\\n\"\n        \"  -a  show authentication\\n\"\n        \"  -l  show geo location mapping\\n\"\n        \"  -n  show numerical ids instead of names\\n\");\n  }\n};\n} // namespace\n\nvoid\nRegisterVidNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<VidCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/who-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: who-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include <CLI/CLI.hpp>\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstd::string MakeWhoHelp(const CLI::App* app)\n{\n  std::ostringstream oss;\n  const std::string& name = app->get_name();\n  oss << \"Usage: \" << (name.empty() ? \"who\" : name) << \" [OPTION]...\\n\";\n  const std::string desc = app->get_description();\n  if (!desc.empty()) {\n    oss << desc << \"\\n\";\n  }\n  oss << \"\\nOptions:\\n\";\n\n  std::vector<std::pair<std::string, std::string>> lines;\n  size_t max_name = 0;\n  for (const auto* opt : app->get_options()) {\n    if (!opt || !opt->nonpositional()) {\n      continue;\n    }\n    std::string opt_name = opt->get_name(false, true);\n    if (opt_name.empty()) {\n      continue;\n    }\n    for (size_t i = 0; i + 1 < opt_name.size(); ++i) {\n      if (opt_name[i] == ',' && opt_name[i + 1] != ' ') {\n        opt_name.insert(i + 1, \" \");\n        ++i;\n      }\n    }\n    std::string opt_desc = opt->get_description();\n    max_name = std::max(max_name, opt_name.size());\n    lines.emplace_back(std::move(opt_name), std::move(opt_desc));\n  }\n\n  for (const auto& line : lines) {\n    oss << \"  \" << line.first;\n    if (!line.second.empty()) {\n      size_t pad = (max_name > line.first.size()) ? (max_name - line.first.size()) : 0;\n      oss << std::string(pad + 2, ' ') << line.second;\n    }\n    oss << \"\\n\";\n  }\n  return oss.str();\n}\n\nvoid ConfigureWhoApp(CLI::App& app,\n                     bool& opt_c,\n                     bool& opt_n,\n                     bool& opt_z,\n                     bool& opt_a,\n                     bool& opt_m,\n                     bool& opt_s,\n                     bool& opt_h)\n{\n  app.name(\"who\");\n  app.description(\"print statistics about active users (idle<5min)\");\n  app.set_help_flag(\"\");\n  app.formatter(std::make_shared<CLI::FormatterLambda>(\n      [](const CLI::App* app, std::string, CLI::AppFormatMode) {\n        return MakeWhoHelp(app);\n      }));\n  app.add_flag(\"-c\", opt_c, \"break down by client host\");\n  app.add_flag(\"-n\", opt_n, \"print id's instead of names\");\n  app.add_flag(\"-z\", opt_z, \"print auth protocols\");\n  app.add_flag(\"-a\", opt_a, \"print all\");\n  app.add_flag(\"-s\", opt_s, \"print summary for clients\");\n  app.add_flag(\"-m\", opt_m, \"print in monitoring format <key>=<value>\");\n  app.add_flag(\"-h,--help\", opt_h, \"help\");\n}\n\nclass WhoCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"who\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Statistics about connected users\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    CLI::App app;\n    bool opt_c = false;\n    bool opt_n = false;\n    bool opt_z = false;\n    bool opt_a = false;\n    bool opt_m = false;\n    bool opt_s = false;\n    bool opt_h = false;\n    ConfigureWhoApp(app, opt_c, opt_n, opt_z, opt_a, opt_m, opt_s, opt_h);\n\n    std::vector<std::string> cli_args = args;\n    std::reverse(cli_args.begin(), cli_args.end());\n    try {\n      app.parse(cli_args);\n    } catch (const CLI::ParseError&) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    if (opt_h) {\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n    XrdOucString in = \"mgm.cmd=who\";\n    std::string opts;\n    if (opt_c)\n      opts += 'c';\n    if (opt_n)\n      opts += 'n';\n    if (opt_z)\n      opts += 'z';\n    if (opt_a)\n      opts += 'a';\n    if (opt_s)\n      opts += 's';\n    if (opt_m)\n      opts += 'm';\n    if (!opts.empty()) {\n      in += \"&mgm.option=\";\n      in += opts.c_str();\n    }\n    global_retc = ctx.outputResult(ctx.clientCommand(in, false, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    CLI::App app;\n    bool opt_c = false;\n    bool opt_n = false;\n    bool opt_z = false;\n    bool opt_a = false;\n    bool opt_m = false;\n    bool opt_s = false;\n    bool opt_h = false;\n    ConfigureWhoApp(app, opt_c, opt_n, opt_z, opt_a, opt_m, opt_s, opt_h);\n    const std::string help = app.help();\n    fprintf(stderr, \"%s\", help.c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterWhoNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<WhoCommand>());\n}\n"
  },
  {
    "path": "console/commands/native/whoami-cmd-native.cc",
    "content": "// ----------------------------------------------------------------------\n// File: whoami-native.cc\n// ----------------------------------------------------------------------\n\n#include \"console/CommandFramework.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <sstream>\n\nnamespace {\nstd::string MakeWhoamiHelp()\n{\n  return \"Usage: whoami\\n\\n\"\n         \"Determine how the current user is mapped on the server side.\\n\";\n}\n\nclass WhoamiCommand : public IConsoleCommand {\npublic:\n  const char*\n  name() const override\n  {\n    return \"whoami\";\n  }\n  const char*\n  description() const override\n  {\n    return \"Determine how we are mapped on server side\";\n  }\n  bool\n  requiresMgm(const std::string& args) const override\n  {\n    return !wants_help(args.c_str());\n  }\n  int\n  run(const std::vector<std::string>& args, CommandContext& ctx) override\n  {\n    if (!args.empty()) {\n      std::ostringstream oss;\n      for (size_t i = 0; i < args.size(); ++i) {\n        if (i)\n          oss << ' ';\n        oss << args[i];\n      }\n      if (wants_help(oss.str().c_str())) {\n        printHelp();\n        global_retc = EINVAL;\n        return 0;\n      }\n      printHelp();\n      global_retc = EINVAL;\n      return 0;\n    }\n\n    XrdOucString in = \"mgm.cmd=whoami\";\n    global_retc = ctx.outputResult(ctx.clientCommand(in, false, nullptr), true);\n    return 0;\n  }\n  void\n  printHelp() const override\n  {\n    fprintf(stderr, \"%s\", MakeWhoamiHelp().c_str());\n  }\n};\n} // namespace\n\nvoid\nRegisterWhoamiNativeCommand()\n{\n  CommandRegistry::instance().reg(std::make_unique<WhoamiCommand>());\n}\n"
  },
  {
    "path": "console/eos-iam-mapfile",
    "content": "#!/usr/bin/python3\n# ----------------------------------------------------------------------\n# File: eos-iam-mapfile.py\n# Author: Manuel Reis - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2021 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nimport os\nimport re\nimport sys\nimport json\nimport pickle\nimport logging\nimport argparse\nfrom sys import exit\nfrom os import getenv\nfrom urllib import request, parse\nfrom configparser import ConfigParser, DEFAULTSECT\nfrom datetime import datetime, timedelta\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\n\nLOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'\nDATE_FORMAT = '%Y-%m-%d %H:%M:%S'\n\nclass IAM_Server:\n    TOKEN_ENDPOINT = '/token'\n    USER_ENDPOINT ='/scim/Users'\n\n    def __init__(self, server, client_id, client_secret, token_server = None, account = None, group_account_map = dict()):\n        self.server = server\n        self.client_id = client_id\n        self.client_secret = client_secret\n        # Assuming token server is the same as IAM's\n        self.token_server = token_server or server\n        self._token = None\n        self.account = account\n        self.group_account_map = group_account_map\n\n    def __hash__(self):\n        return hash(self.server)\n\n    def __eq__(self, other):\n        return self.server == other.server\n\n    def __get_token(self):\n        \"\"\"\n        Authenticates with the iam server and returns the access token.\n        \"\"\"\n        request_data = {\n            \"client_id\": self.client_id,\n            \"client_secret\": self.client_secret,\n            \"grant_type\": \"client_credentials\",\n            \"scope\": \"scim:read\"\n        }\n        now = datetime.now()\n\n        response = request.urlopen(f'https://{self.token_server}{self.TOKEN_ENDPOINT}',\n                                   data=parse.urlencode(request_data).encode('utf-8'))\n        response = json.loads(response.read())\n\n        if 'access_token' not in response:\n            raise BaseException(\"Authentication Failed\")\n        response['request_time'] = now\n        self._token = response\n\n    @property\n    def token(self):\n        \"\"\"\n        Property that return and renews the bearer token if expired\n        \"\"\"\n        if self._token is None or self._token['request_time'] + timedelta(seconds=self._token['expires_in']-10) < datetime.now():\n            self.__get_token()\n        return self._token['access_token']\n\n    def get_users(self, start_index = 0,count = 1, filter_function=None, **kwargs):\n        \"\"\"\n        Queries the server to get all users belonging to the VO.\n        Each batch can be up to 100 records so the requests are parallelized\n        \"\"\"\n        # Get's a new token if expired\n        header = {\"Authorization\": f\"Bearer {self.token}\"}\n\n        users_so_far = 0\n        startIndex = 0\n        params = {\"startIndex\": startIndex, \"count\": count}\n        params[\"startIndex\"] = startIndex\n        # Get's a new token if expired\n        header[\"Authorization\"] = f\"Bearer {self.token}\"\n        req = request.Request(f\"https://{self.server}{self.USER_ENDPOINT}?{parse.urlencode(params)}\", headers=header)\n        response = request.urlopen(req)\n        response = json.loads(response.read())\n\n        users = set()\n        user_lst = []\n        # We can use a with statement to ensure threads are cleaned up promptly\n        with ThreadPoolExecutor(max_workers=8) as executor:\n            # Start the load operations and mark each future with its URL\n            reqs = []\n\n            for start_index in range(0,response['totalResults'],count):\n                params[\"startIndex\"] = start_index\n\n                # Get's a new token if expired\n                header[\"Authorization\"] = f\"Bearer {self.token}\"\n                req = request.Request(f\"https://{self.server}{self.USER_ENDPOINT}?{parse.urlencode(params)}\", headers=header)\n                reqs.append(executor.submit(request.urlopen, req))\n                logging.debug(f\"https://{self.server}{self.USER_ENDPOINT}?{parse.urlencode(params)}  with headers: {header}\")\n\n            for req in as_completed(reqs):\n                try:\n                    response=req.result()\n                    response = json.loads(response.read())\n                    if filter_function is not None:\n                        users.update(filter_function(*response['Resources'], **kwargs))\n                    else:\n                        user_lst.extend(response['Resources'])\n                except Exception as e:\n                    logging.error(f'{req} generated an exception: {e}')\n\n        if not filter_function:\n            return user_lst\n\n        return users\n\ndef name_map_filter(*users, kwargs=None):\n    \"\"\"\n    Collect user's id to build 'Mapfile format' rules:\n    https://github.com/xrootd/xrootd/tree/master/src/XrdSciTokens\n    \"\"\"\n    logging.debug(f\"This request has {len(users)}\")\n    ids=set()\n    return set((user.get('id') for user in users if user.get('id') is not None))\n\n\ndef extract_grid_dn(user, pattern=None, prefer_cern=False):\n    grid_dns = []\n    try:\n        certs = user['urn:indigo-dc:scim:schemas:IndigoUser']['certificates']\n        # Is there a CERN certificate if prefered?\n        if prefer_cern:\n            certs = [*filter(lambda x: x.get('subjectDn',x.get('issuerDn')).endswith('DC=cern,DC=ch'), certs)]\n\n        for cert in certs:\n            # Revert subjectDn and replace , with / (making sure commas on the values aren't replaced)\n            grid_dn = '/'.join(re.split(r',(?=\\w+=)', cert[\"subjectDn\"])[::-1]) # re: courtesy of Maarten Litmaath\n            if pattern is None or pattern.search(grid_dn):\n                grid_dns.append(grid_dn)\n    except KeyError:\n        logging.warning(f\"User {user['id']} doesn't have certificate to extract info (skipping it)\")\n\n    return grid_dns\n\ndef dn_filter(*users, pattern=None, prefer_cern=False, **kwargs):\n    \"\"\"\n    Collect users with DN certificates matching regex\n    \"\"\"\n    logging.debug(f\"This request has {len(users)}\")\n    matching_dn = set()\n    for user in users:\n        if not user.get('active'):\n            logging.info(f\"User {user['userName']}:{user['id']} is not active, skipping\")\n            continue\n        grid_dns = extract_grid_dn(user, pattern, prefer_cern)\n\n        for grid_dn in grid_dns:\n            matching_dn.add(f'/{grid_dn}')\n\n    logging.info(f\"{len(matching_dn)} matching certificates\")\n    return matching_dn\n\ndef role_group_map_filter(*users, pattern=None, prefer_cern=None, group_name=None, **kwargs):\n    matching_dn = set()\n    if not group_name:\n        return matching_dn\n\n    for user in users:\n        if not user.get('active'):\n            logging.info(f\"User {user['userName']}:{user['id']} is not active, skipping\")\n            continue\n\n        try:\n            groups = user['groups']\n            for group in groups:\n                if group['display'] == group_name:\n                    grid_dns = extract_grid_dn(user, pattern, prefer_cern)\n\n                    for grid_dn in grid_dns:\n                        matching_dn.add(f'/{grid_dn}')\n        except KeyError:\n            logging.warning(f\"User {user['id']} doesn't have group mapping, skipping\")\n\n    return matching_dn\n\ndef build_namemap_file(users_id, account, ifile, ofile):\n    name_map=set() # serialized dictionary!\n    if ifile:\n        try:\n            with open(ifile) as f:\n                for entry in json.load(f):\n                    name_map.add(pickle.dumps(entry))\n        except FileNotFoundError as e:\n            logging.error(f\"Unable to read {ifile}, ignoring its content...\")\n            exit(4)\n\n    for id in users_id:\n        name_map.add(pickle.dumps({'sub':id,'result':account}))\n\n    if ofile:\n        try:\n            with open(ofile,'w') as f:\n                json.dump([pickle.loads(rule) for rule in name_map],f)\n        except Exception as e:\n            logging.error(f'Unable to write to {ofile},raised exception {e}')\n            exit(4)\n    else:\n        print(json.dumps([pickle.loads(rule) for rule in name_map]))\n\ndef build_gridmap_file(users_dn, account, ifile, ofile, lgridmap, grid_map = {}):\n\n    if ifile:\n        try:\n            # As some entries may be encoded in latin let's escape it as unicode\n            with open(ifile, \"r\", encoding='unicode_escape') as igridmap_file:\n                for dn,acc in (l.rsplit(' ',1) for l in igridmap_file.readlines()):\n                    grid_map[dn] = acc.strip()\n        except FileNotFoundError as e:\n            logging.error(f\"Unable to read {ifile}, ignoring its content...\")\n            exit(4)\n\n    # Overwrite / append results\n    for dn in users_dn:\n        if dn in grid_map:\n            logging.debug(f'Overwritting {dn}')\n        grid_map[f'\"{dn}\"'] = account\n\n    # Override with local gridmap entries if any\n    if lgridmap:\n        for lfile in lgridmap.split(\",\"):\n            try:\n                with open(lfile.strip(), \"r\", encoding='unicode_escape') as lgridmap_file:\n                    logging.debug(f\"Processing {lfile}\")\n                    for l in lgridmap_file.readlines():\n                        if l.strip() and not l.startswith('#'):\n                            dn, acc = l.rsplit(' ',1)\n                            grid_map[dn] = acc.strip()\n            except FileNotFoundError as e:\n                logging.error(f\"Unable to read {lfile}, ignoring it's content...\")\n\n\n    content = '\\n'.join(f'{dn} {acc}' for dn, acc in grid_map.items())\n\n    if ofile:\n        try:\n            with open(ofile, \"w\", encoding='utf-8') as ogridmap_file:\n                ogridmap_file.write(content)\n        except Exception as e:\n            logging.error(f'Unable to write to {ofile}, raised exception {e}')\n            exit(4)\n    else:\n        print(content)\n    return grid_map\n\ndef parse_groupmap(group_map_str):\n    group_map = dict()\n    kvs = group_map_str.split(\",\")\n    for kv in kvs:\n        k, v = kv.split(\":\")\n        group_map[k.strip()] = v.strip()\n\n    return group_map\n\ndef configure_servers(credentials, servers, targets, account):\n\n    # First level of configuration - file\n    iam_servers = list()\n\n    # First configuration stage is to use command args\n    if servers:\n        if not account:\n            logging.error('servers configured via cli, but no account configured, exiting!')\n            exit(3)\n        for server, client_id, client_secret in servers:\n            logging.debug(f'Adding iam_server {server} from cli')\n            iam_servers.append(IAM_Server(server, client_id, client_secret, server, account))\n\n    # Third option is to rely on configuration file\n    #[<iam server hostname>]\n    #client-id = <id>\n    #client-secret = <key>\n    if credentials and len(iam_servers) == 0 :\n        config = ConfigParser()\n        files_read = config.read(credentials)\n        if len(files_read) > 0:\n            # Credentials file should have IAM server on the section\n            if targets is not None:\n                it = filter(lambda x: True if targets in x else False, config.sections())\n            else:\n                it = config.sections()\n\n            for section in it:\n                if section == DEFAULTSECT:\n                    continue\n\n                server = section\n                client_id = config.get(section,'client-id')\n                client_secret = config.get(section,'client-secret')\n                # Assuming IAM server is token server if not defined\n                token_server = config.get(section,'token-server', fallback=server)\n                account = config.get(section,'account',fallback=None)\n\n                group_map = dict()\n                group_map_str = config.get(section, 'group_account_map', fallback=None)\n                if group_map_str:\n                    group_map = parse_groupmap(group_map_str)\n\n                logging.debug(f'Adding iam_server {server} mapping to {account} from config')\n                iam_servers.append(IAM_Server(server, client_id, client_secret, token_server, account, group_map))\n        else:\n            logging.warning(\"Credentials couldn't be loaded from configuration file\")\n\n    if len(iam_servers):\n        return iam_servers\n    else:\n        logging.error('Configuration problem! Configuration file not loaded (correctly?) or arguments not passed.')\n        exit(3)\n\n\ndef cleanup_files(fname, count):\n    for i in range(count):\n        f = \"{}.{}\".format(fname,i)\n        try:\n            os.remove(f)\n            logging.debug(f\"Cleaned up {f}\")\n        except FileNotFoundError:\n            logging.warn(f\"Skipping non existent {f}\")\n        except Exception as e:\n            logging.error(f\"Failed deletion {f}: {e}\")\n\ndef setup_filelogging(log_level, logfile):\n    if not logfile:\n        return False\n\n    log_dir = os.path.dirname(logfile)\n\n    if log_dir and not os.path.exists(log_dir):\n        return False\n\n    logging.basicConfig(\n        filename = logfile,\n        filemode = 'a',\n        format = LOG_FORMAT,\n        datefmt = DATE_FORMAT,\n        level = log_level\n    )\n\n    return True\n\n\ndef setup_logging(log_level=logging.WARNING, logfile=None):\n    if not setup_filelogging(log_level, logfile):\n        logging.basicConfig(\n            stream = sys.stdout,\n            format = LOG_FORMAT,\n            datefmt = DATE_FORMAT,\n            level = log_level\n        )\n\nclass CLIConfig:\n\n    LOG_LEVELS = {\n        'CRITICAL': logging.CRITICAL,\n        'ERROR': logging.ERROR,\n        'WARNING': logging.WARNING,\n        'INFO': logging.INFO,\n        'DEBUG': logging.DEBUG,\n        'NOTSET': logging.NOTSET,\n    }\n\n    def __init__(self, conf_file):\n        self.config = ConfigParser()\n        self.config.read(conf_file)\n\n        self.lgridmap = None\n        self.inputfile = None\n        self.outputfile = None\n        self.type_of_format = \"GRIDMAP\"\n        self.cleanup = False\n        self.prefer_cern = False\n        self.pattern = None\n\n    def get_option(self, option, value, fallback = None):\n        if value is not None:\n            return value\n\n        return self.config.get(DEFAULTSECT, option, fallback = fallback)\n\n    # Underlying assumption that store-true is used, ie. args.<value> will evaluate to True if set\n    def get_bool(self, option, value, fallback=False):\n        if value:\n            return True\n\n        return self.config.getboolean(DEFAULTSECT, option, fallback=fallback)\n\n    def setup(self, args):\n        \"\"\"\n        Setup the CLI configuration, overriding any config option from CLI flags\n        \"\"\"\n        log_file = self.get_option(\"log_file\", args.log_file)\n        log_level = self.get_option(\"log_level\",args.debug, \"WARNING\")\n        debug_level = self.LOG_LEVELS.get(log_level.upper(), logging.WARNING)\n        setup_logging(debug_level, log_file)\n\n        self.lgridmap = self.get_option(\"localgridmap\", args.lgridmap)\n        self.inputfile = self.get_option(\"inputfile\", args.ifile)\n        self.outputfile = self.get_option(\"outputfile\", args.ofile)\n        self.type_of_format = self.get_option(\"format\", args.type_of_format, \"GRIDMAP\")\n        self.cleanup = self.get_bool(\"cleanup\", args.cleanup)\n        self.prefer_cern = self.get_bool(\"prefer_cern\", args.prefer_cern)\n\n        pattern = self.get_option(\"pattern\", args.pattern)\n        if pattern:\n            flags = 0\n            if self.get_bool(\"case_sensitive\", args.sensitive):\n                flags = re.IGNORECASE\n\n                try:\n                    self.pattern = re.compile(pattern, flags)\n                except Exception as e:\n                    logging.critical(f'Pattern provided cannot be compiled: {pattern}')\n                    exit(1)\n\n\ndef gen_temp_file(fname, count):\n    if not fname:\n        return None\n    return \"{}.{}\".format(fname, count)\n\ndef main(conf_file = None, args = None):\n    \"\"\"\n    Configure IAM servers to be queried, update/write gridmap file format\n    \"\"\"\n\n    conf = CLIConfig(conf_file)\n    conf.setup(args)\n\n    iam_servers = configure_servers(conf_file, args.server, args.targets, args.account)\n\n    # Query IAM server\n    count = 0\n    temp_outfile = None\n    grid_map = dict()\n    ifile = conf.inputfile\n    ofile = conf.outputfile\n    lgridmap = conf.lgridmap\n    type_of_format = conf.type_of_format\n    for iam in iam_servers:\n        # start with an empty user set for each IAM endpoint, we retain the\n        # grid map which will maintain the correct account mapping\n        if not iam.account:\n            logging.warn(f\"Skipping {iam.server} as no explicit mapping via args or config\")\n            continue\n\n        users = set()\n\n        if type_of_format == \"GRIDMAP\":\n            temp_outfile = gen_temp_file(ofile, count)\n            users.update(iam.get_users(count=100, filter_function=dn_filter,\n                                           pattern=conf.pattern,\n                                           prefer_cern=conf.prefer_cern))\n            grid_map = build_gridmap_file(users, iam.account, ifile, temp_outfile, lgridmap, grid_map)\n            count += 1\n            for group_name, account in iam.group_account_map.items():\n                temp_outfile = gen_temp_file(ofile, count)\n                iam_users = iam.get_users(count=100, filter_function=role_group_map_filter,\n                                              pattern=conf.pattern,\n                                              prefer_cern=conf.prefer_cern,\n                                              group_name = group_name)\n                grid_map = build_gridmap_file(iam_users, account, ifile, temp_outfile, lgridmap, grid_map)\n                count += 1\n\n        elif type_of_format == \"MAPFILE\":\n            users.update(iam.get_users(count=100, filter_function=name_map_filter))\n            build_namemap_file(users, iam.account, ifile, outfile)\n\n\n\n\n\n    if temp_outfile and ofile:\n        logging.debug(f\"Renaming {temp_outfile} to {ofile}\")\n        os.rename(temp_outfile, ofile)\n\n        if conf.cleanup:\n            cleanup_files(ofile, count-1)\n\nif __name__ == '__main__':\n    parser = argparse.ArgumentParser(description='GRID Map file generation from IAM Server', epilog='''\n    Supports a config file via -c option, cli arguments override any configuration\n\n    Takes a config file having the following syntax, cli switches override config settings, DEFAULT section is optional\n    Example config:\n    [DEFAULT]\n    cleanup = True\n    localgridmap = /etc/localgridmap.conf,/etc/localgridmap2.conf\n    # Can be repeated as many times with different servers, we strictly override in the order of config files\n    log_file = /var/log/eos/grid/gridmap.log\n    log_level = WARNING  # Same options as verbose CLI option\n\n    [myiamserver1]\n    client-id = 1223\n    client-secret = 1234\n    account = acc4usermap\n    [..]\n    examples:\n$ echo -e '[myiamserver.cern.ch]\\\\nclient-id = 1234567890\\\\nclient-secret = *******\\\\naccount=acc4usermap' > iam.conf\n$ eos-iam-mapfile -c iam.conf''',formatter_class=argparse.RawDescriptionHelpFormatter)\n    parser.add_argument('-v', '--verbose', type = str.upper, nargs='?', const=\"DEBUG\", default=None, choices=(\"DEBUG\", \"INFO\", \"WARNING\", \"ERROR\", \"CRITICAL\"), dest = 'debug', help = 'Control log verbosity')\n    parser.add_argument('-s', '--server', dest = 'server', nargs=3,action='append', help = 'IAM server to query with respective client key and secret (space separated)', metavar=('SERVER', 'CLIENT_ID','CLIENT_KEY'))\n    parser.add_argument('-c', '--config', dest = 'config', help = r'Client credentials file (for API access) in the following format: `[<iam server hostname>]\\nclient-id = <id>\\nclient-secret = <key>`')\n    parser.add_argument('-t', '--targets', dest = 'targets', help = 'Target specific IAM servers defined in the configuration file (must be used together with -c)')\n    parser.add_argument('-i', '--inputfile', dest = 'ifile', default=None, help = \"Path to existing gridmapfile to be updated (matching DN's will be overwritten)\")\n    parser.add_argument('-o', '--outfile', dest = 'ofile', default=None, help = 'Path to dump gridmapfile, will dump to stdout otherwise')\n    parser.add_argument('-l', '--localgridmap', dest = 'lgridmap', default = None, help = 'Path to local gridmap file, supports comma separated (no space) list of files (Will override any previous mappings encountered in order)')\n    parser.add_argument('-a', '--account', dest = 'account', help = 'Account to which the result from the match should be mapped to')\n    parser.add_argument('-p', '--pattern', type=str, dest = 'pattern', default=None, help = 'Pattern to search on user certificates `subject DN` field')\n    parser.add_argument('-C','--case-sensitive', dest='sensitive',action='store_const', const=0, default=re.IGNORECASE, help = 'Makes the regex pattern (-p) to be case sensitive')\n    parser.add_argument('-u', '--prefer-cern-certs', dest = 'prefer_cern',action='store_true', help = 'Prefers CERN.CH certificates (if any) to map user (uniquely)')\n    parser.add_argument('-f', '--format',  type = str.upper, nargs='?', const=\"MAPFILE\", default=\"GRIDMAP\", choices=(\"MAPFILE\",\"GRIDMAP\"),dest='type_of_format', help = 'Choose file format, using DN or ID (defaults to ID if used, else DN)')\n    parser.add_argument('--cleanup', action='store_true')\n    parser.add_argument('--log-file', type=str, help='The log file for logging')\n\n    args = parser.parse_args()\n\n    main(args.config, args)\n"
  },
  {
    "path": "console/eosadmin",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: eosadmin\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nif [[ -r /etc/sysconfig/eos ]]; then\n # Handle old SysV scripts\n . /etc/sysconfig/eos\nelif [[ -r /etc/sysconfig/eos_env ]]; then\n # Handle systemd environment script\n set -a\n . /etc/sysconfig/eos_env\n set +a\nfi\n\nif [[ -n \"$TERM\" ]]; then\n  NORMAL=$(tput sgr0)\n  RED=$(tput setaf 1)\n\n  function red() {\n      echo -e \"${RED}$*${NORMAL}\"\n  }\nelse\n  function red() {\n    echo -e \"$*\"\n  }\nfi\n\n# Try to guess the value of EOS_MGM_URL if not exit.\nif [[ -z \"${EOS_MGM_URL}\" ]]; then\n  if [[ -n \"${EOS_MGM_ALIAS}\" ]]; then\n    EOS_MGM_URL=root://${EOS_MGM_ALIAS}\n  fi\nfi\n\nif [[ -z \"${EOS_MGM_URL}\" ]]; then\n  red \"EOS_MGM_URL is not defined. Please review the sysconfig script.\"\n  exit 1;\nfi\n\nexport EOS_MGM_URL\nexport XrdSecPROTOCOL=sss\neos \"$@\"\n"
  },
  {
    "path": "console/eosreport",
    "content": "#!/usr/bin/perl\n# ----------------------------------------------------------------------\n# File: eosreport\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nmy $year = $ARGV[0];\nmy $month = $ARGV[1];\nmy $day = $ARGV[2];\nmy $file = \"/var/eos/report/$year/$month/$year$month$day.eosreport\";\nprint \"# Opening $file ...\";\n\nif (open IN ,\" $file\") {\n    while (<IN>) {\n\t$_ =~ s/\\&/\\ /g;\n\tmy @val = split (\" \", $_);\n\tmy $hash;\n\tforeach $kv (@val) {\n\tmy ($key, $val) = split( \"=\",$kv);\n\t$hash->{$key} = $val;\n    }\n\tmy $time = ($hash->{cts}*1000) + $hash->{ctms} - ($hash->{ots}*1000) - $hash->{otms};\n\tif ($time) {\n\t    printf(\"%s uid=%s gid=%s r-bytes=%-10s\\tr-rate=%03.02f\\tr-size=%03.02f MB\\tw-bytes=%-10s\\tw-rate=%03.02f\\tw-size=%03.02f MB path=%s\\n\", scalar localtime($hash->{ots}), $hash->{ruid}, $hash->{rgid}, $hash->{rb}, $hash->{rb} / $time /1000.0, $hash->{rb}/1000000.0, $hash->{wb}, $hash->{wb} / $time/1000.0, $hash->{wb}/1000000.0, $hash->{path});\n\t}\n    }\n} else {\n    print \"\\n\";\n    print \"# Usage: eosreport <year> <month> <day>\\n\";\n}\n\n"
  },
  {
    "path": "coverage/eoslcov.rc",
    "content": "#\n# eoslcov.rc - EOS config options for LCOV\n#\n\n# Specify coverage rate limits (in %) for classifying file entries\n# HI:   hi_limit <= rate <= 100         graph color: green\n# MED: med_limit <= rate <  hi_limit    graph color: orange\n# LO:         0  <= rate <  med_limit   graph color: red\ngenhtml_hi_limit = 70\ngenhtml_med_limit = 50\n\n# Specify if branch coverage should be enabled\nlcov_branch_coverage = 1\ngenhtml_branch_coverage = 1\n"
  },
  {
    "path": "debian/compat",
    "content": "10\n"
  },
  {
    "path": "debian/control.template",
    "content": "Source: eos\nMaintainer: EOS Support <eos-support@cern.ch>\nSection: misc\nPriority: optional\nStandards-Version: 3.9.3\nBuild-Depends: debhelper (>= 10), cmake (>= 3.3.0), git, libfuse-dev, libfuse3-dev, libattr1-dev, libmicrohttpd-dev, xfslibs-dev, libcurl4-openssl-dev, libleveldb-dev, libzmq3-dev, libsparsehash-dev, libprotobuf-dev (>=3.0.0), protobuf-compiler (>=3.0.0), libsystemd-dev, libreadline-dev, ncurses-dev, libssl-dev, zlib1g-dev, libkrb5-dev, libevent-dev, libhiredis-dev, libjsoncpp-dev, libjemalloc-dev, libdw-dev, libbz2-dev, binutils-dev, libiberty-dev, libfmt-dev, librocksdb-dev, libzstd-dev, libsnappy-dev, eos-grpc, scitokens-cpp, libscitokens-dev, libcap-dev,\n eos-xrootd-plugins (>= _XRD_DEB_VER_),\n eos-xrootd-client (>= _XRD_DEB_VER_),\n eos-xrootd-client-plugins (>= _XRD_DEB_VER_),\n eos-xrootd-server (>= _XRD_DEB_VER_),\n eos-xrootd-server-plugins (>= _XRD_DEB_VER_),\n libeosxrootd-dev (>= _XRD_DEB_VER_),\n libeosxrootd-private-dev (>= _XRD_DEB_VER_),\n libeosxrootd-client-dev (>= _XRD_DEB_VER_),\n libeosxrootd-server-dev (>= _XRD_DEB_VER_),\n libeosxrdapputils2t64 (>= _XRD_DEB_VER_),\n libeosxrdcrypto2t64 (>= _XRD_DEB_VER_),\n libeosxrdcryptolite2t64 (>= _XRD_DEB_VER_),\n libeosxrdxml3t64 (>= _XRD_DEB_VER_),\n libeosxrdutils3t64 (>= _XRD_DEB_VER_),\n libeosxrdhttputils2t64 (>= _XRD_DEB_VER_),\n libeosxrdserver3t64 (>= _XRD_DEB_VER_),\n libeosxrdssilib2t64 (>= _XRD_DEB_VER_),\n libeosxrdssishmap2t64 (>= _XRD_DEB_VER_),\n libeosxrdcl3t64 (>= _XRD_DEB_VER_),\n libeosxrdffs3t64 (>= _XRD_DEB_VER_),\n libeosxrdposix3t64 (>= _XRD_DEB_VER_),\n _PROCPS_TAG_\nHomepage: http://eos.web.cern.ch/\nVcs-Git: https://gitlab.cern.ch/dss/eos.git\nVcs-Browser: https://gitlab.cern.ch/dss/eos\n\nPackage: eos-client\nArchitecture: any\nMulti-Arch: same\nDepends: ${shlibs:Depends}, systemd,\n eos-grpc,\n eos-xrootd-plugins (>= _XRD_DEB_VER_),\n libeosxrootd-client-dev (>= _XRD_DEB_VER_),\n libeosxrdposix3t64 (>= _XRD_DEB_VER_),\n libeosxrdutils3t64 (>= _XRD_DEB_VER_),\n libeosxrdcl3t64 (>= _XRD_DEB_VER_)\nDescription: EOS client package\n\nPackage: eos-fusex\nArchitecture: any\nMulti-Arch: same\nDepends: ${shlibs:Depends}, eos-grpc, fuse, fuse3,\n  eos-client (=${binary:Version}),\n  libeosxrootd-client-dev (>= _XRD_DEB_VER_),\n  libeosxrdcl3t64 (>= _XRD_DEB_VER_),\n  libeosxrdutils3t64 (>= _XRD_DEB_VER_)\nReplaces: eos-fuse\nDescription: EOS fuse client bundle\n\nPackage: eos-test\nArchitecture: any\nMulti-Arch: same\nDepends: ${shlibs:Depends}, eos-grpc, bc, davix, jq\nDescription: EOS test package\n\nPackage: eos-testkeytab\nArchitecture: any\nMulti-Arch: same\nDescription: EOS testkeytab package\n"
  },
  {
    "path": "debian/copyright",
    "content": "//                     GNU GENERAL PUBLIC LICENSE\n//                        Version 3, 29 June 2007\n// \n//  Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n//  Everyone is permitted to copy and distribute verbatim copies\n//  of this license document, but changing it is not allowed.\n// \n//                             Preamble\n// \n//   The GNU General Public License is a free, copyleft license for\n// software and other kinds of works.\n// \n//   The licenses for most software and other practical works are designed\n// to take away your freedom to share and change the works.  By contrast,\n// the GNU General Public License is intended to guarantee your freedom to\n// share and change all versions of a program--to make sure it remains free\n// software for all its users.  We, the Free Software Foundation, use the\n// GNU General Public License for most of our software; it applies also to\n// any other work released this way by its authors.  You can apply it to\n// your programs, too.\n// \n//   When we speak of free software, we are referring to freedom, not\n// price.  Our General Public Licenses are designed to make sure that you\n// have the freedom to distribute copies of free software (and charge for\n// them if you wish), that you receive source code or can get it if you\n// want it, that you can change the software or use pieces of it in new\n// free programs, and that you know you can do these things.\n// \n//   To protect your rights, we need to prevent others from denying you\n// these rights or asking you to surrender the rights.  Therefore, you have\n// certain responsibilities if you distribute copies of the software, or if\n// you modify it: responsibilities to respect the freedom of others.\n// \n//   For example, if you distribute copies of such a program, whether\n// gratis or for a fee, you must pass on to the recipients the same\n// freedoms that you received.  You must make sure that they, too, receive\n// or can get the source code.  And you must show them these terms so they\n// know their rights.\n// \n//   Developers that use the GNU GPL protect your rights with two steps:\n// (1) assert copyright on the software, and (2) offer you this License\n// giving you legal permission to copy, distribute and/or modify it.\n// \n//   For the developers' and authors' protection, the GPL clearly explains\n// that there is no warranty for this free software.  For both users' and\n// authors' sake, the GPL requires that modified versions be marked as\n// changed, so that their problems will not be attributed erroneously to\n// authors of previous versions.\n// \n//   Some devices are designed to deny users access to install or run\n// modified versions of the software inside them, although the manufacturer\n// can do so.  This is fundamentally incompatible with the aim of\n// protecting users' freedom to change the software.  The systematic\n// pattern of such abuse occurs in the area of products for individuals to\n// use, which is precisely where it is most unacceptable.  Therefore, we\n// have designed this version of the GPL to prohibit the practice for those\n// products.  If such problems arise substantially in other domains, we\n// stand ready to extend this provision to those domains in future versions\n// of the GPL, as needed to protect the freedom of users.\n// \n//   Finally, every program is threatened constantly by software patents.\n// States should not allow patents to restrict development and use of\n// software on general-purpose computers, but in those that do, we wish to\n// avoid the special danger that patents applied to a free program could\n// make it effectively proprietary.  To prevent this, the GPL assures that\n// patents cannot be used to render the program non-free.\n// \n//   The precise terms and conditions for copying, distribution and\n// modification follow.\n// \n//                        TERMS AND CONDITIONS\n// \n//   0. Definitions.\n// \n//   \"This License\" refers to version 3 of the GNU General Public License.\n// \n//   \"Copyright\" also means copyright-like laws that apply to other kinds of\n// works, such as semiconductor masks.\n// \n//   \"The Program\" refers to any copyrightable work licensed under this\n// License.  Each licensee is addressed as \"you\".  \"Licensees\" and\n// \"recipients\" may be individuals or organizations.\n// \n//   To \"modify\" a work means to copy from or adapt all or part of the work\n// in a fashion requiring copyright permission, other than the making of an\n// exact copy.  The resulting work is called a \"modified version\" of the\n// earlier work or a work \"based on\" the earlier work.\n// \n//   A \"covered work\" means either the unmodified Program or a work based\n// on the Program.\n// \n//   To \"propagate\" a work means to do anything with it that, without\n// permission, would make you directly or secondarily liable for\n// infringement under applicable copyright law, except executing it on a\n// computer or modifying a private copy.  Propagation includes copying,\n// distribution (with or without modification), making available to the\n// public, and in some countries other activities as well.\n// \n//   To \"convey\" a work means any kind of propagation that enables other\n// parties to make or receive copies.  Mere interaction with a user through\n// a computer network, with no transfer of a copy, is not conveying.\n// \n//   An interactive user interface displays \"Appropriate Legal Notices\"\n// to the extent that it includes a convenient and prominently visible\n// feature that (1) displays an appropriate copyright notice, and (2)\n// tells the user that there is no warranty for the work (except to the\n// extent that warranties are provided), that licensees may convey the\n// work under this License, and how to view a copy of this License.  If\n// the interface presents a list of user commands or options, such as a\n// menu, a prominent item in the list meets this criterion.\n// \n//   1. Source Code.\n// \n//   The \"source code\" for a work means the preferred form of the work\n// for making modifications to it.  \"Object code\" means any non-source\n// form of a work.\n// \n//   A \"Standard Interface\" means an interface that either is an official\n// standard defined by a recognized standards body, or, in the case of\n// interfaces specified for a particular programming language, one that\n// is widely used among developers working in that language.\n// \n//   The \"System Libraries\" of an executable work include anything, other\n// than the work as a whole, that (a) is included in the normal form of\n// packaging a Major Component, but which is not part of that Major\n// Component, and (b) serves only to enable use of the work with that\n// Major Component, or to implement a Standard Interface for which an\n// implementation is available to the public in source code form.  A\n// \"Major Component\", in this context, means a major essential component\n// (kernel, window system, and so on) of the specific operating system\n// (if any) on which the executable work runs, or a compiler used to\n// produce the work, or an object code interpreter used to run it.\n// \n//   The \"Corresponding Source\" for a work in object code form means all\n// the source code needed to generate, install, and (for an executable\n// work) run the object code and to modify the work, including scripts to\n// control those activities.  However, it does not include the work's\n// System Libraries, or general-purpose tools or generally available free\n// programs which are used unmodified in performing those activities but\n// which are not part of the work.  For example, Corresponding Source\n// includes interface definition files associated with source files for\n// the work, and the source code for shared libraries and dynamically\n// linked subprograms that the work is specifically designed to require,\n// such as by intimate data communication or control flow between those\n// subprograms and other parts of the work.\n// \n//   The Corresponding Source need not include anything that users\n// can regenerate automatically from other parts of the Corresponding\n// Source.\n// \n//   The Corresponding Source for a work in source code form is that\n// same work.\n// \n//   2. Basic Permissions.\n// \n//   All rights granted under this License are granted for the term of\n// copyright on the Program, and are irrevocable provided the stated\n// conditions are met.  This License explicitly affirms your unlimited\n// permission to run the unmodified Program.  The output from running a\n// covered work is covered by this License only if the output, given its\n// content, constitutes a covered work.  This License acknowledges your\n// rights of fair use or other equivalent, as provided by copyright law.\n// \n//   You may make, run and propagate covered works that you do not\n// convey, without conditions so long as your license otherwise remains\n// in force.  You may convey covered works to others for the sole purpose\n// of having them make modifications exclusively for you, or provide you\n// with facilities for running those works, provided that you comply with\n// the terms of this License in conveying all material for which you do\n// not control copyright.  Those thus making or running the covered works\n// for you must do so exclusively on your behalf, under your direction\n// and control, on terms that prohibit them from making any copies of\n// your copyrighted material outside their relationship with you.\n// \n//   Conveying under any other circumstances is permitted solely under\n// the conditions stated below.  Sublicensing is not allowed; section 10\n// makes it unnecessary.\n// \n//   3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n// \n//   No covered work shall be deemed part of an effective technological\n// measure under any applicable law fulfilling obligations under article\n// 11 of the WIPO copyright treaty adopted on 20 December 1996, or\n// similar laws prohibiting or restricting circumvention of such\n// measures.\n// \n//   When you convey a covered work, you waive any legal power to forbid\n// circumvention of technological measures to the extent such circumvention\n// is effected by exercising rights under this License with respect to\n// the covered work, and you disclaim any intention to limit operation or\n// modification of the work as a means of enforcing, against the work's\n// users, your or third parties' legal rights to forbid circumvention of\n// technological measures.\n// \n//   4. Conveying Verbatim Copies.\n// \n//   You may convey verbatim copies of the Program's source code as you\n// receive it, in any medium, provided that you conspicuously and\n// appropriately publish on each copy an appropriate copyright notice;\n// keep intact all notices stating that this License and any\n// non-permissive terms added in accord with section 7 apply to the code;\n// keep intact all notices of the absence of any warranty; and give all\n// recipients a copy of this License along with the Program.\n// \n//   You may charge any price or no price for each copy that you convey,\n// and you may offer support or warranty protection for a fee.\n// \n//   5. Conveying Modified Source Versions.\n// \n//   You may convey a work based on the Program, or the modifications to\n// produce it from the Program, in the form of source code under the\n// terms of section 4, provided that you also meet all of these conditions:\n// \n//     a) The work must carry prominent notices stating that you modified\n//     it, and giving a relevant date.\n// \n//     b) The work must carry prominent notices stating that it is\n//     released under this License and any conditions added under section\n//     7.  This requirement modifies the requirement in section 4 to\n//     \"keep intact all notices\".\n// \n//     c) You must license the entire work, as a whole, under this\n//     License to anyone who comes into possession of a copy.  This\n//     License will therefore apply, along with any applicable section 7\n//     additional terms, to the whole of the work, and all its parts,\n//     regardless of how they are packaged.  This License gives no\n//     permission to license the work in any other way, but it does not\n//     invalidate such permission if you have separately received it.\n// \n//     d) If the work has interactive user interfaces, each must display\n//     Appropriate Legal Notices; however, if the Program has interactive\n//     interfaces that do not display Appropriate Legal Notices, your\n//     work need not make them do so.\n// \n//   A compilation of a covered work with other separate and independent\n// works, which are not by their nature extensions of the covered work,\n// and which are not combined with it such as to form a larger program,\n// in or on a volume of a storage or distribution medium, is called an\n// \"aggregate\" if the compilation and its resulting copyright are not\n// used to limit the access or legal rights of the compilation's users\n// beyond what the individual works permit.  Inclusion of a covered work\n// in an aggregate does not cause this License to apply to the other\n// parts of the aggregate.\n// \n//   6. Conveying Non-Source Forms.\n// \n//   You may convey a covered work in object code form under the terms\n// of sections 4 and 5, provided that you also convey the\n// machine-readable Corresponding Source under the terms of this License,\n// in one of these ways:\n// \n//     a) Convey the object code in, or embodied in, a physical product\n//     (including a physical distribution medium), accompanied by the\n//     Corresponding Source fixed on a durable physical medium\n//     customarily used for software interchange.\n// \n//     b) Convey the object code in, or embodied in, a physical product\n//     (including a physical distribution medium), accompanied by a\n//     written offer, valid for at least three years and valid for as\n//     long as you offer spare parts or customer support for that product\n//     model, to give anyone who possesses the object code either (1) a\n//     copy of the Corresponding Source for all the software in the\n//     product that is covered by this License, on a durable physical\n//     medium customarily used for software interchange, for a price no\n//     more than your reasonable cost of physically performing this\n//     conveying of source, or (2) access to copy the\n//     Corresponding Source from a network server at no charge.\n// \n//     c) Convey individual copies of the object code with a copy of the\n//     written offer to provide the Corresponding Source.  This\n//     alternative is allowed only occasionally and noncommercially, and\n//     only if you received the object code with such an offer, in accord\n//     with subsection 6b.\n// \n//     d) Convey the object code by offering access from a designated\n//     place (gratis or for a charge), and offer equivalent access to the\n//     Corresponding Source in the same way through the same place at no\n//     further charge.  You need not require recipients to copy the\n//     Corresponding Source along with the object code.  If the place to\n//     copy the object code is a network server, the Corresponding Source\n//     may be on a different server (operated by you or a third party)\n//     that supports equivalent copying facilities, provided you maintain\n//     clear directions next to the object code saying where to find the\n//     Corresponding Source.  Regardless of what server hosts the\n//     Corresponding Source, you remain obligated to ensure that it is\n//     available for as long as needed to satisfy these requirements.\n// \n//     e) Convey the object code using peer-to-peer transmission, provided\n//     you inform other peers where the object code and Corresponding\n//     Source of the work are being offered to the general public at no\n//     charge under subsection 6d.\n// \n//   A separable portion of the object code, whose source code is excluded\n// from the Corresponding Source as a System Library, need not be\n// included in conveying the object code work.\n// \n//   A \"User Product\" is either (1) a \"consumer product\", which means any\n// tangible personal property which is normally used for personal, family,\n// or household purposes, or (2) anything designed or sold for incorporation\n// into a dwelling.  In determining whether a product is a consumer product,\n// doubtful cases shall be resolved in favor of coverage.  For a particular\n// product received by a particular user, \"normally used\" refers to a\n// typical or common use of that class of product, regardless of the status\n// of the particular user or of the way in which the particular user\n// actually uses, or expects or is expected to use, the product.  A product\n// is a consumer product regardless of whether the product has substantial\n// commercial, industrial or non-consumer uses, unless such uses represent\n// the only significant mode of use of the product.\n// \n//   \"Installation Information\" for a User Product means any methods,\n// procedures, authorization keys, or other information required to install\n// and execute modified versions of a covered work in that User Product from\n// a modified version of its Corresponding Source.  The information must\n// suffice to ensure that the continued functioning of the modified object\n// code is in no case prevented or interfered with solely because\n// modification has been made.\n// \n//   If you convey an object code work under this section in, or with, or\n// specifically for use in, a User Product, and the conveying occurs as\n// part of a transaction in which the right of possession and use of the\n// User Product is transferred to the recipient in perpetuity or for a\n// fixed term (regardless of how the transaction is characterized), the\n// Corresponding Source conveyed under this section must be accompanied\n// by the Installation Information.  But this requirement does not apply\n// if neither you nor any third party retains the ability to install\n// modified object code on the User Product (for example, the work has\n// been installed in ROM).\n// \n//   The requirement to provide Installation Information does not include a\n// requirement to continue to provide support service, warranty, or updates\n// for a work that has been modified or installed by the recipient, or for\n// the User Product in which it has been modified or installed.  Access to a\n// network may be denied when the modification itself materially and\n// adversely affects the operation of the network or violates the rules and\n// protocols for communication across the network.\n// \n//   Corresponding Source conveyed, and Installation Information provided,\n// in accord with this section must be in a format that is publicly\n// documented (and with an implementation available to the public in\n// source code form), and must require no special password or key for\n// unpacking, reading or copying.\n// \n//   7. Additional Terms.\n// \n//   \"Additional permissions\" are terms that supplement the terms of this\n// License by making exceptions from one or more of its conditions.\n// Additional permissions that are applicable to the entire Program shall\n// be treated as though they were included in this License, to the extent\n// that they are valid under applicable law.  If additional permissions\n// apply only to part of the Program, that part may be used separately\n// under those permissions, but the entire Program remains governed by\n// this License without regard to the additional permissions.\n// \n//   When you convey a copy of a covered work, you may at your option\n// remove any additional permissions from that copy, or from any part of\n// it.  (Additional permissions may be written to require their own\n// removal in certain cases when you modify the work.)  You may place\n// additional permissions on material, added by you to a covered work,\n// for which you have or can give appropriate copyright permission.\n// \n//   Notwithstanding any other provision of this License, for material you\n// add to a covered work, you may (if authorized by the copyright holders of\n// that material) supplement the terms of this License with terms:\n// \n//     a) Disclaiming warranty or limiting liability differently from the\n//     terms of sections 15 and 16 of this License; or\n// \n//     b) Requiring preservation of specified reasonable legal notices or\n//     author attributions in that material or in the Appropriate Legal\n//     Notices displayed by works containing it; or\n// \n//     c) Prohibiting misrepresentation of the origin of that material, or\n//     requiring that modified versions of such material be marked in\n//     reasonable ways as different from the original version; or\n// \n//     d) Limiting the use for publicity purposes of names of licensors or\n//     authors of the material; or\n// \n//     e) Declining to grant rights under trademark law for use of some\n//     trade names, trademarks, or service marks; or\n// \n//     f) Requiring indemnification of licensors and authors of that\n//     material by anyone who conveys the material (or modified versions of\n//     it) with contractual assumptions of liability to the recipient, for\n//     any liability that these contractual assumptions directly impose on\n//     those licensors and authors.\n// \n//   All other non-permissive additional terms are considered \"further\n// restrictions\" within the meaning of section 10.  If the Program as you\n// received it, or any part of it, contains a notice stating that it is\n// governed by this License along with a term that is a further\n// restriction, you may remove that term.  If a license document contains\n// a further restriction but permits relicensing or conveying under this\n// License, you may add to a covered work material governed by the terms\n// of that license document, provided that the further restriction does\n// not survive such relicensing or conveying.\n// \n//   If you add terms to a covered work in accord with this section, you\n// must place, in the relevant source files, a statement of the\n// additional terms that apply to those files, or a notice indicating\n// where to find the applicable terms.\n// \n//   Additional terms, permissive or non-permissive, may be stated in the\n// form of a separately written license, or stated as exceptions;\n// the above requirements apply either way.\n// \n//   8. Termination.\n// \n//   You may not propagate or modify a covered work except as expressly\n// provided under this License.  Any attempt otherwise to propagate or\n// modify it is void, and will automatically terminate your rights under\n// this License (including any patent licenses granted under the third\n// paragraph of section 11).\n// \n//   However, if you cease all violation of this License, then your\n// license from a particular copyright holder is reinstated (a)\n// provisionally, unless and until the copyright holder explicitly and\n// finally terminates your license, and (b) permanently, if the copyright\n// holder fails to notify you of the violation by some reasonable means\n// prior to 60 days after the cessation.\n// \n//   Moreover, your license from a particular copyright holder is\n// reinstated permanently if the copyright holder notifies you of the\n// violation by some reasonable means, this is the first time you have\n// received notice of violation of this License (for any work) from that\n// copyright holder, and you cure the violation prior to 30 days after\n// your receipt of the notice.\n// \n//   Termination of your rights under this section does not terminate the\n// licenses of parties who have received copies or rights from you under\n// this License.  If your rights have been terminated and not permanently\n// reinstated, you do not qualify to receive new licenses for the same\n// material under section 10.\n// \n//   9. Acceptance Not Required for Having Copies.\n// \n//   You are not required to accept this License in order to receive or\n// run a copy of the Program.  Ancillary propagation of a covered work\n// occurring solely as a consequence of using peer-to-peer transmission\n// to receive a copy likewise does not require acceptance.  However,\n// nothing other than this License grants you permission to propagate or\n// modify any covered work.  These actions infringe copyright if you do\n// not accept this License.  Therefore, by modifying or propagating a\n// covered work, you indicate your acceptance of this License to do so.\n// \n//   10. Automatic Licensing of Downstream Recipients.\n// \n//   Each time you convey a covered work, the recipient automatically\n// receives a license from the original licensors, to run, modify and\n// propagate that work, subject to this License.  You are not responsible\n// for enforcing compliance by third parties with this License.\n// \n//   An \"entity transaction\" is a transaction transferring control of an\n// organization, or substantially all assets of one, or subdividing an\n// organization, or merging organizations.  If propagation of a covered\n// work results from an entity transaction, each party to that\n// transaction who receives a copy of the work also receives whatever\n// licenses to the work the party's predecessor in interest had or could\n// give under the previous paragraph, plus a right to possession of the\n// Corresponding Source of the work from the predecessor in interest, if\n// the predecessor has it or can get it with reasonable efforts.\n// \n//   You may not impose any further restrictions on the exercise of the\n// rights granted or affirmed under this License.  For example, you may\n// not impose a license fee, royalty, or other charge for exercise of\n// rights granted under this License, and you may not initiate litigation\n// (including a cross-claim or counterclaim in a lawsuit) alleging that\n// any patent claim is infringed by making, using, selling, offering for\n// sale, or importing the Program or any portion of it.\n// \n//   11. Patents.\n// \n//   A \"contributor\" is a copyright holder who authorizes use under this\n// License of the Program or a work on which the Program is based.  The\n// work thus licensed is called the contributor's \"contributor version\".\n// \n//   A contributor's \"essential patent claims\" are all patent claims\n// owned or controlled by the contributor, whether already acquired or\n// hereafter acquired, that would be infringed by some manner, permitted\n// by this License, of making, using, or selling its contributor version,\n// but do not include claims that would be infringed only as a\n// consequence of further modification of the contributor version.  For\n// purposes of this definition, \"control\" includes the right to grant\n// patent sublicenses in a manner consistent with the requirements of\n// this License.\n// \n//   Each contributor grants you a non-exclusive, worldwide, royalty-free\n// patent license under the contributor's essential patent claims, to\n// make, use, sell, offer for sale, import and otherwise run, modify and\n// propagate the contents of its contributor version.\n// \n//   In the following three paragraphs, a \"patent license\" is any express\n// agreement or commitment, however denominated, not to enforce a patent\n// (such as an express permission to practice a patent or covenant not to\n// sue for patent infringement).  To \"grant\" such a patent license to a\n// party means to make such an agreement or commitment not to enforce a\n// patent against the party.\n// \n//   If you convey a covered work, knowingly relying on a patent license,\n// and the Corresponding Source of the work is not available for anyone\n// to copy, free of charge and under the terms of this License, through a\n// publicly available network server or other readily accessible means,\n// then you must either (1) cause the Corresponding Source to be so\n// available, or (2) arrange to deprive yourself of the benefit of the\n// patent license for this particular work, or (3) arrange, in a manner\n// consistent with the requirements of this License, to extend the patent\n// license to downstream recipients.  \"Knowingly relying\" means you have\n// actual knowledge that, but for the patent license, your conveying the\n// covered work in a country, or your recipient's use of the covered work\n// in a country, would infringe one or more identifiable patents in that\n// country that you have reason to believe are valid.\n// \n//   If, pursuant to or in connection with a single transaction or\n// arrangement, you convey, or propagate by procuring conveyance of, a\n// covered work, and grant a patent license to some of the parties\n// receiving the covered work authorizing them to use, propagate, modify\n// or convey a specific copy of the covered work, then the patent license\n// you grant is automatically extended to all recipients of the covered\n// work and works based on it.\n// \n//   A patent license is \"discriminatory\" if it does not include within\n// the scope of its coverage, prohibits the exercise of, or is\n// conditioned on the non-exercise of one or more of the rights that are\n// specifically granted under this License.  You may not convey a covered\n// work if you are a party to an arrangement with a third party that is\n// in the business of distributing software, under which you make payment\n// to the third party based on the extent of your activity of conveying\n// the work, and under which the third party grants, to any of the\n// parties who would receive the covered work from you, a discriminatory\n// patent license (a) in connection with copies of the covered work\n// conveyed by you (or copies made from those copies), or (b) primarily\n// for and in connection with specific products or compilations that\n// contain the covered work, unless you entered into that arrangement,\n// or that patent license was granted, prior to 28 March 2007.\n// \n//   Nothing in this License shall be construed as excluding or limiting\n// any implied license or other defenses to infringement that may\n// otherwise be available to you under applicable patent law.\n// \n//   12. No Surrender of Others' Freedom.\n// \n//   If conditions are imposed on you (whether by court order, agreement or\n// otherwise) that contradict the conditions of this License, they do not\n// excuse you from the conditions of this License.  If you cannot convey a\n// covered work so as to satisfy simultaneously your obligations under this\n// License and any other pertinent obligations, then as a consequence you may\n// not convey it at all.  For example, if you agree to terms that obligate you\n// to collect a royalty for further conveying from those to whom you convey\n// the Program, the only way you could satisfy both those terms and this\n// License would be to refrain entirely from conveying the Program.\n// \n//   13. Use with the GNU Affero General Public License.\n// \n//   Notwithstanding any other provision of this License, you have\n// permission to link or combine any covered work with a work licensed\n// under version 3 of the GNU Affero General Public License into a single\n// combined work, and to convey the resulting work.  The terms of this\n// License will continue to apply to the part which is the covered work,\n// but the special requirements of the GNU Affero General Public License,\n// section 13, concerning interaction through a network will apply to the\n// combination as such.\n// \n//   14. Revised Versions of this License.\n// \n//   The Free Software Foundation may publish revised and/or new versions of\n// the GNU General Public License from time to time.  Such new versions will\n// be similar in spirit to the present version, but may differ in detail to\n// address new problems or concerns.\n// \n//   Each version is given a distinguishing version number.  If the\n// Program specifies that a certain numbered version of the GNU General\n// Public License \"or any later version\" applies to it, you have the\n// option of following the terms and conditions either of that numbered\n// version or of any later version published by the Free Software\n// Foundation.  If the Program does not specify a version number of the\n// GNU General Public License, you may choose any version ever published\n// by the Free Software Foundation.\n// \n//   If the Program specifies that a proxy can decide which future\n// versions of the GNU General Public License can be used, that proxy's\n// public statement of acceptance of a version permanently authorizes you\n// to choose that version for the Program.\n// \n//   Later license versions may give you additional or different\n// permissions.  However, no additional obligations are imposed on any\n// author or copyright holder as a result of your choosing to follow a\n// later version.\n// \n//   15. Disclaimer of Warranty.\n// \n//   THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\n// APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\n// HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\n// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\n// IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\n// ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n// \n//   16. Limitation of Liability.\n// \n//   IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\n// WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\n// THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\n// GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\n// USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\n// DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\n// PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\n// EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\n// SUCH DAMAGES.\n// \n//   17. Interpretation of Sections 15 and 16.\n// \n//   If the disclaimer of warranty and limitation of liability provided\n// above cannot be given local legal effect according to their terms,\n// reviewing courts shall apply local law that most closely approximates\n// an absolute waiver of all civil liability in connection with the\n// Program, unless a warranty or assumption of liability accompanies a\n// copy of the Program in return for a fee.\n// \n//                      END OF TERMS AND CONDITIONS\n\nconst char* license=\" /************************************************************************************\\n \\\n* EOS - the CERN Disk Storage System                                                *\\n \\\n* Copyright (C) 2011 CERN/Switzerland                                               *\\n \\\n*                                                                                   *\\n \\\n* This program is free software: you can redistribute it and/or modify              *\\n \\\n* it under the terms of the GNU General Public License as published by              *\\n \\\n* the Free Software Foundation, either version 3 of the License, or                 *\\n \\\n* (at your option) any later version.                                               *\\n \\\n*                                                                                   *\\n \\\n* This program is distributed in the hope that it will be useful,                   *\\n \\\n* but WITHOUT ANY WARRANTY; without even the implied warranty of                    *\\n \\\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                     *\\n \\\n* GNU General Public License for more details.                                      *\\n \\\n*                                                                                   *\\n \\\n* EOS is based on the XRootD software:                                              *\\n \\\n* ----------------------------------------------------------------------------------*\\n \\\n* Copyright (C) 2005-2010, Board of Trustees of the Leland Stanford, Jr. University.*\\n \\\n* Produced under contract DE-AC02-76-SF00515 with the US Department of Energy.      *\\n \\\n* All rights reserved.                                                              *\\n \\\n* See <http://xrootd.org> for more details.                                         *\\n \\\n*                                                                                   *\\n \\\n* EOS uses crc32c checksum alogrithms from MIT/Intel:                               *\\n \\\n* ----------------------------------------------------------------------------------*\\n \\\n* Copyright 2008,2009,2010 Massachusetts Institute of Technology.                   *\\n \\\n* Implementations adapted from Intel's Slicing By 8 Sourceforge Project             *\\n \\\n* http://sourceforge.net/projects/slicing-by-8/                                     *\\n \\\n* Copyright (c) 2004-2006 Intel Corporation                                         *\\n \\\n************************************************************************************/\\n \\\n\\n \\\n                    GNU GENERAL PUBLIC LICENSE\\n \\\n                       Version 3, 29 June 2007\\n \\\n\\n \\\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\\n \\\n Everyone is permitted to copy and distribute verbatim copies\\n \\\n of this license document, but changing it is not allowed.\\n \\\n\\n \\\n                            Preamble\\n \\\n\\n \\\n  The GNU General Public License is a free, copyleft license for\\n \\\nsoftware and other kinds of works.\\n \\\n\\n \\\n  The licenses for most software and other practical works are designed\\n \\\nto take away your freedom to share and change the works.  By contrast,\\n \\\nthe GNU General Public License is intended to guarantee your freedom to\\n \\\nshare and change all versions of a program--to make sure it remains free\\n \\\nsoftware for all its users.  We, the Free Software Foundation, use the\\n \\\nGNU General Public License for most of our software; it applies also to\\n \\\nany other work released this way by its authors.  You can apply it to\\n \\\nyour programs, too.\\n \\\n\\n \\\n  When we speak of free software, we are referring to freedom, not\\n \\\nprice.  Our General Public Licenses are designed to make sure that you\\n \\\nhave the freedom to distribute copies of free software (and charge for\\n \\\nthem if you wish), that you receive source code or can get it if you\\n \\\nwant it, that you can change the software or use pieces of it in new\\n \\\nfree programs, and that you know you can do these things.\\n \\\n\\n \\\n  To protect your rights, we need to prevent others from denying you\\n \\\nthese rights or asking you to surrender the rights.  Therefore, you have\\n \\\ncertain responsibilities if you distribute copies of the software, or if\\n \\\nyou modify it: responsibilities to respect the freedom of others.\\n \\\n\\n \\\n  For example, if you distribute copies of such a program, whether\\n \\\ngratis or for a fee, you must pass on to the recipients the same\\n \\\nfreedoms that you received.  You must make sure that they, too, receive\\n \\\nor can get the source code.  And you must show them these terms so they\\n \\\nknow their rights.\\n \\\n\\n \\\n  Developers that use the GNU GPL protect your rights with two steps:\\n \\\n(1) assert copyright on the software, and (2) offer you this License\\n \\\ngiving you legal permission to copy, distribute and/or modify it.\\n \\\n\\n \\\n  For the developers' and authors' protection, the GPL clearly explains\\n \\\nthat there is no warranty for this free software.  For both users' and\\n \\\nauthors' sake, the GPL requires that modified versions be marked as\\n \\\nchanged, so that their problems will not be attributed erroneously to\\n \\\nauthors of previous versions.\\n \\\n\\n \\\n  Some devices are designed to deny users access to install or run\\n \\\nmodified versions of the software inside them, although the manufacturer\\n \\\ncan do so.  This is fundamentally incompatible with the aim of\\n \\\nprotecting users' freedom to change the software.  The systematic\\n \\\npattern of such abuse occurs in the area of products for individuals to\\n \\\nuse, which is precisely where it is most unacceptable.  Therefore, we\\n \\\nhave designed this version of the GPL to prohibit the practice for those\\n \\\nproducts.  If such problems arise substantially in other domains, we\\n \\\nstand ready to extend this provision to those domains in future versions\\n \\\nof the GPL, as needed to protect the freedom of users.\\n \\\n\\n \\\n  Finally, every program is threatened constantly by software patents.\\n \\\nStates should not allow patents to restrict development and use of\\n \\\nsoftware on general-purpose computers, but in those that do, we wish to\\n \\\navoid the special danger that patents applied to a free program could\\n \\\nmake it effectively proprietary.  To prevent this, the GPL assures that\\n \\\npatents cannot be used to render the program non-free.\\n \\\n\\n \\\n  The precise terms and conditions for copying, distribution and\\n \\\nmodification follow.\\n \\\n\\n \\\n                       TERMS AND CONDITIONS\\n \\\n\\n \\\n  0. Definitions.\\n \\\n\\n \\\n  \\\"This License\\\" refers to version 3 of the GNU General Public License.\\n \\\n\\n \\\n  \\\"Copyright\\\" also means copyright-like laws that apply to other kinds of\\n \\\nworks, such as semiconductor masks.\\n \\\n\\n \\\n  \\\"The Program\\\" refers to any copyrightable work licensed under this\\n \\\nLicense.  Each licensee is addressed as \\\"you\\\".  \\\"Licensees\\\" and\\n \\\n\\\"recipients\\\" may be individuals or organizations.\\n \\\n\\n \\\n  To \\\"modify\\\" a work means to copy from or adapt all or part of the work\\n \\\nin a fashion requiring copyright permission, other than the making of an\\n \\\nexact copy.  The resulting work is called a \\\"modified version\\\" of the\\n \\\nearlier work or a work \\\"based on\\\" the earlier work.\\n \\\n\\n \\\n  A \\\"covered work\\\" means either the unmodified Program or a work based\\n \\\non the Program.\\n \\\n\\n \\\n  To \\\"propagate\\\" a work means to do anything with it that, without\\n \\\npermission, would make you directly or secondarily liable for\\n \\\ninfringement under applicable copyright law, except executing it on a\\n \\\ncomputer or modifying a private copy.  Propagation includes copying,\\n \\\ndistribution (with or without modification), making available to the\\n \\\npublic, and in some countries other activities as well.\\n \\\n\\n \\\n  To \\\"convey\\\" a work means any kind of propagation that enables other\\n \\\nparties to make or receive copies.  Mere interaction with a user through\\n \\\na computer network, with no transfer of a copy, is not conveying.\\n \\\n\\n \\\n  An interactive user interface displays \\\"Appropriate Legal Notices\\\"\\n \\\nto the extent that it includes a convenient and prominently visible\\n \\\nfeature that (1) displays an appropriate copyright notice, and (2)\\n \\\ntells the user that there is no warranty for the work (except to the\\n \\\nextent that warranties are provided), that licensees may convey the\\n \\\nwork under this License, and how to view a copy of this License.  If\\n \\\nthe interface presents a list of user commands or options, such as a\\n \\\nmenu, a prominent item in the list meets this criterion.\\n \\\n\\n \\\n  1. Source Code.\\n \\\n\\n \\\n  The \\\"source code\\\" for a work means the preferred form of the work\\n \\\nfor making modifications to it.  \\\"Object code\\\" means any non-source\\n \\\nform of a work.\\n \\\n\\n \\\n  A \\\"Standard Interface\\\" means an interface that either is an official\\n \\\nstandard defined by a recognized standards body, or, in the case of\\n \\\ninterfaces specified for a particular programming language, one that\\n \\\nis widely used among developers working in that language.\\n \\\n\\n \\\n  The \\\"System Libraries\\\" of an executable work include anything, other\\n \\\nthan the work as a whole, that (a) is included in the normal form of\\n \\\npackaging a Major Component, but which is not part of that Major\\n \\\nComponent, and (b) serves only to enable use of the work with that\\n \\\nMajor Component, or to implement a Standard Interface for which an\\n \\\nimplementation is available to the public in source code form.  A\\n \\\n\\\"Major Component\\\", in this context, means a major essential component\\n \\\n(kernel, window system, and so on) of the specific operating system\\n \\\n(if any) on which the executable work runs, or a compiler used to\\n \\\nproduce the work, or an object code interpreter used to run it.\\n \\\n\\n \\\n  The \\\"Corresponding Source\\\" for a work in object code form means all\\n \\\nthe source code needed to generate, install, and (for an executable\\n \\\nwork) run the object code and to modify the work, including scripts to\\n \\\ncontrol those activities.  However, it does not include the work's\\n \\\nSystem Libraries, or general-purpose tools or generally available free\\n \\\nprograms which are used unmodified in performing those activities but\\n \\\nwhich are not part of the work.  For example, Corresponding Source\\n \\\nincludes interface definition files associated with source files for\\n \\\nthe work, and the source code for shared libraries and dynamically\\n \\\nlinked subprograms that the work is specifically designed to require,\\n \\\nsuch as by intimate data communication or control flow between those\\n \\\nsubprograms and other parts of the work.\\n \\\n\\n \\\n  The Corresponding Source need not include anything that users\\n \\\ncan regenerate automatically from other parts of the Corresponding\\n \\\nSource.\\n \\\n\\n \\\n  The Corresponding Source for a work in source code form is that\\n \\\nsame work.\\n \\\n\\n \\\n  2. Basic Permissions.\\n \\\n\\n \\\n  All rights granted under this License are granted for the term of\\n \\\ncopyright on the Program, and are irrevocable provided the stated\\n \\\nconditions are met.  This License explicitly affirms your unlimited\\n \\\npermission to run the unmodified Program.  The output from running a\\n \\\ncovered work is covered by this License only if the output, given its\\n \\\ncontent, constitutes a covered work.  This License acknowledges your\\n \\\nrights of fair use or other equivalent, as provided by copyright law.\\n \\\n\\n \\\n  You may make, run and propagate covered works that you do not\\n \\\nconvey, without conditions so long as your license otherwise remains\\n \\\nin force.  You may convey covered works to others for the sole purpose\\n \\\nof having them make modifications exclusively for you, or provide you\\n \\\nwith facilities for running those works, provided that you comply with\\n \\\nthe terms of this License in conveying all material for which you do\\n \\\nnot control copyright.  Those thus making or running the covered works\\n \\\nfor you must do so exclusively on your behalf, under your direction\\n \\\nand control, on terms that prohibit them from making any copies of\\n \\\nyour copyrighted material outside their relationship with you.\\n \\\n\\n \\\n  Conveying under any other circumstances is permitted solely under\\n \\\nthe conditions stated below.  Sublicensing is not allowed; section 10\\n \\\nmakes it unnecessary.\\n \\\n\\n \\\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\\n \\\n\\n \\\n  No covered work shall be deemed part of an effective technological\\n \\\nmeasure under any applicable law fulfilling obligations under article\\n \\\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\\n \\\nsimilar laws prohibiting or restricting circumvention of such\\n \\\nmeasures.\\n \\\n\\n \\\n  When you convey a covered work, you waive any legal power to forbid\\n \\\ncircumvention of technological measures to the extent such circumvention\\n \\\nis effected by exercising rights under this License with respect to\\n \\\nthe covered work, and you disclaim any intention to limit operation or\\n \\\nmodification of the work as a means of enforcing, against the work's\\n \\\nusers, your or third parties' legal rights to forbid circumvention of\\n \\\ntechnological measures.\\n \\\n\\n \\\n  4. Conveying Verbatim Copies.\\n \\\n\\n \\\n  You may convey verbatim copies of the Program's source code as you\\n \\\nreceive it, in any medium, provided that you conspicuously and\\n \\\nappropriately publish on each copy an appropriate copyright notice;\\n \\\nkeep intact all notices stating that this License and any\\n \\\nnon-permissive terms added in accord with section 7 apply to the code;\\n \\\nkeep intact all notices of the absence of any warranty; and give all\\n \\\nrecipients a copy of this License along with the Program.\\n \\\n\\n \\\n  You may charge any price or no price for each copy that you convey,\\n \\\nand you may offer support or warranty protection for a fee.\\n \\\n\\n \\\n  5. Conveying Modified Source Versions.\\n \\\n\\n \\\n  You may convey a work based on the Program, or the modifications to\\n \\\nproduce it from the Program, in the form of source code under the\\n \\\nterms of section 4, provided that you also meet all of these conditions:\\n \\\n\\n \\\n    a) The work must carry prominent notices stating that you modified\\n \\\n    it, and giving a relevant date.\\n \\\n\\n \\\n    b) The work must carry prominent notices stating that it is\\n \\\n    released under this License and any conditions added under section\\n \\\n    7.  This requirement modifies the requirement in section 4 to\\n \\\n    \\\"keep intact all notices\\\".\\n \\\n\\n \\\n    c) You must license the entire work, as a whole, under this\\n \\\n    License to anyone who comes into possession of a copy.  This\\n \\\n    License will therefore apply, along with any applicable section 7\\n \\\n    additional terms, to the whole of the work, and all its parts,\\n \\\n    regardless of how they are packaged.  This License gives no\\n \\\n    permission to license the work in any other way, but it does not\\n \\\n    invalidate such permission if you have separately received it.\\n \\\n\\n \\\n    d) If the work has interactive user interfaces, each must display\\n \\\n    Appropriate Legal Notices; however, if the Program has interactive\\n \\\n    interfaces that do not display Appropriate Legal Notices, your\\n \\\n    work need not make them do so.\\n \\\n\\n \\\n  A compilation of a covered work with other separate and independent\\n \\\nworks, which are not by their nature extensions of the covered work,\\n \\\nand which are not combined with it such as to form a larger program,\\n \\\nin or on a volume of a storage or distribution medium, is called an\\n \\\n\\\"aggregate\\\" if the compilation and its resulting copyright are not\\n \\\nused to limit the access or legal rights of the compilation's users\\n \\\nbeyond what the individual works permit.  Inclusion of a covered work\\n \\\nin an aggregate does not cause this License to apply to the other\\n \\\nparts of the aggregate.\\n \\\n\\n \\\n  6. Conveying Non-Source Forms.\\n \\\n\\n \\\n  You may convey a covered work in object code form under the terms\\n \\\nof sections 4 and 5, provided that you also convey the\\n \\\nmachine-readable Corresponding Source under the terms of this License,\\n \\\nin one of these ways:\\n \\\n\\n \\\n    a) Convey the object code in, or embodied in, a physical product\\n \\\n    (including a physical distribution medium), accompanied by the\\n \\\n    Corresponding Source fixed on a durable physical medium\\n \\\n    customarily used for software interchange.\\n \\\n\\n \\\n    b) Convey the object code in, or embodied in, a physical product\\n \\\n    (including a physical distribution medium), accompanied by a\\n \\\n    written offer, valid for at least three years and valid for as\\n \\\n    long as you offer spare parts or customer support for that product\\n \\\n    model, to give anyone who possesses the object code either (1) a\\n \\\n    copy of the Corresponding Source for all the software in the\\n \\\n    product that is covered by this License, on a durable physical\\n \\\n    medium customarily used for software interchange, for a price no\\n \\\n    more than your reasonable cost of physically performing this\\n \\\n    conveying of source, or (2) access to copy the\\n \\\n    Corresponding Source from a network server at no charge.\\n \\\n\\n \\\n    c) Convey individual copies of the object code with a copy of the\\n \\\n    written offer to provide the Corresponding Source.  This\\n \\\n    alternative is allowed only occasionally and noncommercially, and\\n \\\n    only if you received the object code with such an offer, in accord\\n \\\n    with subsection 6b.\\n \\\n\\n \\\n    d) Convey the object code by offering access from a designated\\n \\\n    place (gratis or for a charge), and offer equivalent access to the\\n \\\n    Corresponding Source in the same way through the same place at no\\n \\\n    further charge.  You need not require recipients to copy the\\n \\\n    Corresponding Source along with the object code.  If the place to\\n \\\n    copy the object code is a network server, the Corresponding Source\\n \\\n    may be on a different server (operated by you or a third party)\\n \\\n    that supports equivalent copying facilities, provided you maintain\\n \\\n    clear directions next to the object code saying where to find the\\n \\\n    Corresponding Source.  Regardless of what server hosts the\\n \\\n    Corresponding Source, you remain obligated to ensure that it is\\n \\\n    available for as long as needed to satisfy these requirements.\\n \\\n\\n \\\n    e) Convey the object code using peer-to-peer transmission, provided\\n \\\n    you inform other peers where the object code and Corresponding\\n \\\n    Source of the work are being offered to the general public at no\\n \\\n    charge under subsection 6d.\\n \\\n\\n \\\n  A separable portion of the object code, whose source code is excluded\\n \\\nfrom the Corresponding Source as a System Library, need not be\\n \\\nincluded in conveying the object code work.\\n \\\n\\n \\\n  A \\\"User Product\\\" is either (1) a \\\"consumer product\\\", which means any\\n \\\ntangible personal property which is normally used for personal, family,\\n \\\nor household purposes, or (2) anything designed or sold for incorporation\\n \\\ninto a dwelling.  In determining whether a product is a consumer product,\\n \\\ndoubtful cases shall be resolved in favor of coverage.  For a particular\\n \\\nproduct received by a particular user, \\\"normally used\\\" refers to a\\n \\\ntypical or common use of that class of product, regardless of the status\\n \\\nof the particular user or of the way in which the particular user\\n \\\nactually uses, or expects or is expected to use, the product.  A product\\n \\\nis a consumer product regardless of whether the product has substantial\\n \\\ncommercial, industrial or non-consumer uses, unless such uses represent\\n \\\nthe only significant mode of use of the product.\\n \\\n\\n \\\n  \\\"Installation Information\\\" for a User Product means any methods,\\n \\\nprocedures, authorization keys, or other information required to install\\n \\\nand execute modified versions of a covered work in that User Product from\\n \\\na modified version of its Corresponding Source.  The information must\\n \\\nsuffice to ensure that the continued functioning of the modified object\\n \\\ncode is in no case prevented or interfered with solely because\\n \\\nmodification has been made.\\n \\\n\\n \\\n  If you convey an object code work under this section in, or with, or\\n \\\nspecifically for use in, a User Product, and the conveying occurs as\\n \\\npart of a transaction in which the right of possession and use of the\\n \\\nUser Product is transferred to the recipient in perpetuity or for a\\n \\\nfixed term (regardless of how the transaction is characterized), the\\n \\\nCorresponding Source conveyed under this section must be accompanied\\n \\\nby the Installation Information.  But this requirement does not apply\\n \\\nif neither you nor any third party retains the ability to install\\n \\\nmodified object code on the User Product (for example, the work has\\n \\\nbeen installed in ROM).\\n \\\n\\n \\\n  The requirement to provide Installation Information does not include a\\n \\\nrequirement to continue to provide support service, warranty, or updates\\n \\\nfor a work that has been modified or installed by the recipient, or for\\n \\\nthe User Product in which it has been modified or installed.  Access to a\\n \\\nnetwork may be denied when the modification itself materially and\\n \\\nadversely affects the operation of the network or violates the rules and\\n \\\nprotocols for communication across the network.\\n \\\n\\n \\\n  Corresponding Source conveyed, and Installation Information provided,\\n \\\nin accord with this section must be in a format that is publicly\\n \\\ndocumented (and with an implementation available to the public in\\n \\\nsource code form), and must require no special password or key for\\n \\\nunpacking, reading or copying.\\n \\\n\\n \\\n  7. Additional Terms.\\n \\\n\\n \\\n  \\\"Additional permissions\\\" are terms that supplement the terms of this\\n \\\nLicense by making exceptions from one or more of its conditions.\\n \\\nAdditional permissions that are applicable to the entire Program shall\\n \\\nbe treated as though they were included in this License, to the extent\\n \\\nthat they are valid under applicable law.  If additional permissions\\n \\\napply only to part of the Program, that part may be used separately\\n \\\nunder those permissions, but the entire Program remains governed by\\n \\\nthis License without regard to the additional permissions.\\n \\\n\\n \\\n  When you convey a copy of a covered work, you may at your option\\n \\\nremove any additional permissions from that copy, or from any part of\\n \\\nit.  (Additional permissions may be written to require their own\\n \\\nremoval in certain cases when you modify the work.)  You may place\\n \\\nadditional permissions on material, added by you to a covered work,\\n \\\nfor which you have or can give appropriate copyright permission.\\n \\\n\\n \\\n  Notwithstanding any other provision of this License, for material you\\n \\\nadd to a covered work, you may (if authorized by the copyright holders of\\n \\\nthat material) supplement the terms of this License with terms:\\n \\\n\\n \\\n    a) Disclaiming warranty or limiting liability differently from the\\n \\\n    terms of sections 15 and 16 of this License; or\\n \\\n\\n \\\n    b) Requiring preservation of specified reasonable legal notices or\\n \\\n    author attributions in that material or in the Appropriate Legal\\n \\\n    Notices displayed by works containing it; or\\n \\\n\\n \\\n    c) Prohibiting misrepresentation of the origin of that material, or\\n \\\n    requiring that modified versions of such material be marked in\\n \\\n    reasonable ways as different from the original version; or\\n \\\n\\n \\\n    d) Limiting the use for publicity purposes of names of licensors or\\n \\\n    authors of the material; or\\n \\\n\\n \\\n    e) Declining to grant rights under trademark law for use of some\\n \\\n    trade names, trademarks, or service marks; or\\n \\\n\\n \\\n    f) Requiring indemnification of licensors and authors of that\\n \\\n    material by anyone who conveys the material (or modified versions of\\n \\\n    it) with contractual assumptions of liability to the recipient, for\\n \\\n    any liability that these contractual assumptions directly impose on\\n \\\n    those licensors and authors.\\n \\\n\\n \\\n  All other non-permissive additional terms are considered \\\"further\\n \\\nrestrictions\\\" within the meaning of section 10.  If the Program as you\\n \\\nreceived it, or any part of it, contains a notice stating that it is\\n \\\ngoverned by this License along with a term that is a further\\n \\\nrestriction, you may remove that term.  If a license document contains\\n \\\na further restriction but permits relicensing or conveying under this\\n \\\nLicense, you may add to a covered work material governed by the terms\\n \\\nof that license document, provided that the further restriction does\\n \\\nnot survive such relicensing or conveying.\\n \\\n\\n \\\n  If you add terms to a covered work in accord with this section, you\\n \\\nmust place, in the relevant source files, a statement of the\\n \\\nadditional terms that apply to those files, or a notice indicating\\n \\\nwhere to find the applicable terms.\\n \\\n\\n \\\n  Additional terms, permissive or non-permissive, may be stated in the\\n \\\nform of a separately written license, or stated as exceptions;\\n \\\nthe above requirements apply either way.\\n \\\n\\n \\\n  8. Termination.\\n \\\n\\n \\\n  You may not propagate or modify a covered work except as expressly\\n \\\nprovided under this License.  Any attempt otherwise to propagate or\\n \\\nmodify it is void, and will automatically terminate your rights under\\n \\\nthis License (including any patent licenses granted under the third\\n \\\nparagraph of section 11).\\n \\\n\\n \\\n  However, if you cease all violation of this License, then your\\n \\\nlicense from a particular copyright holder is reinstated (a)\\n \\\nprovisionally, unless and until the copyright holder explicitly and\\n \\\nfinally terminates your license, and (b) permanently, if the copyright\\n \\\nholder fails to notify you of the violation by some reasonable means\\n \\\nprior to 60 days after the cessation.\\n \\\n\\n \\\n  Moreover, your license from a particular copyright holder is\\n \\\nreinstated permanently if the copyright holder notifies you of the\\n \\\nviolation by some reasonable means, this is the first time you have\\n \\\nreceived notice of violation of this License (for any work) from that\\n \\\ncopyright holder, and you cure the violation prior to 30 days after\\n \\\nyour receipt of the notice.\\n \\\n\\n \\\n  Termination of your rights under this section does not terminate the\\n \\\nlicenses of parties who have received copies or rights from you under\\n \\\nthis License.  If your rights have been terminated and not permanently\\n \\\nreinstated, you do not qualify to receive new licenses for the same\\n \\\nmaterial under section 10.\\n \\\n\\n \\\n  9. Acceptance Not Required for Having Copies.\\n \\\n\\n \\\n  You are not required to accept this License in order to receive or\\n \\\nrun a copy of the Program.  Ancillary propagation of a covered work\\n \\\noccurring solely as a consequence of using peer-to-peer transmission\\n \\\nto receive a copy likewise does not require acceptance.  However,\\n \\\nnothing other than this License grants you permission to propagate or\\n \\\nmodify any covered work.  These actions infringe copyright if you do\\n \\\nnot accept this License.  Therefore, by modifying or propagating a\\n \\\ncovered work, you indicate your acceptance of this License to do so.\\n \\\n\\n \\\n  10. Automatic Licensing of Downstream Recipients.\\n \\\n\\n \\\n  Each time you convey a covered work, the recipient automatically\\n \\\nreceives a license from the original licensors, to run, modify and\\n \\\npropagate that work, subject to this License.  You are not responsible\\n \\\nfor enforcing compliance by third parties with this License.\\n \\\n\\n \\\n  An \\\"entity transaction\\\" is a transaction transferring control of an\\n \\\norganization, or substantially all assets of one, or subdividing an\\n \\\norganization, or merging organizations.  If propagation of a covered\\n \\\nwork results from an entity transaction, each party to that\\n \\\ntransaction who receives a copy of the work also receives whatever\\n \\\nlicenses to the work the party's predecessor in interest had or could\\n \\\ngive under the previous paragraph, plus a right to possession of the\\n \\\nCorresponding Source of the work from the predecessor in interest, if\\n \\\nthe predecessor has it or can get it with reasonable efforts.\\n \\\n\\n \\\n  You may not impose any further restrictions on the exercise of the\\n \\\nrights granted or affirmed under this License.  For example, you may\\n \\\nnot impose a license fee, royalty, or other charge for exercise of\\n \\\nrights granted under this License, and you may not initiate litigation\\n \\\n(including a cross-claim or counterclaim in a lawsuit) alleging that\\n \\\nany patent claim is infringed by making, using, selling, offering for\\n \\\nsale, or importing the Program or any portion of it.\\n \\\n\\n \\\n  11. Patents.\\n \\\n\\n \\\n  A \\\"contributor\\\" is a copyright holder who authorizes use under this\\n \\\nLicense of the Program or a work on which the Program is based.  The\\n \\\nwork thus licensed is called the contributor's \\\"contributor version\\\".\\n \\\n\\n \\\n  A contributor's \\\"essential patent claims\\\" are all patent claims\\n \\\nowned or controlled by the contributor, whether already acquired or\\n \\\nhereafter acquired, that would be infringed by some manner, permitted\\n \\\nby this License, of making, using, or selling its contributor version,\\n \\\nbut do not include claims that would be infringed only as a\\n \\\nconsequence of further modification of the contributor version.  For\\n \\\npurposes of this definition, \\\"control\\\" includes the right to grant\\n \\\npatent sublicenses in a manner consistent with the requirements of\\n \\\nthis License.\\n \\\n\\n \\\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\\n \\\npatent license under the contributor's essential patent claims, to\\n \\\nmake, use, sell, offer for sale, import and otherwise run, modify and\\n \\\npropagate the contents of its contributor version.\\n \\\n\\n \\\n  In the following three paragraphs, a \\\"patent license\\\" is any express\\n \\\nagreement or commitment, however denominated, not to enforce a patent\\n \\\n(such as an express permission to practice a patent or covenant not to\\n \\\nsue for patent infringement).  To \\\"grant\\\" such a patent license to a\\n \\\nparty means to make such an agreement or commitment not to enforce a\\n \\\npatent against the party.\\n \\\n\\n \\\n  If you convey a covered work, knowingly relying on a patent license,\\n \\\nand the Corresponding Source of the work is not available for anyone\\n \\\nto copy, free of charge and under the terms of this License, through a\\n \\\npublicly available network server or other readily accessible means,\\n \\\nthen you must either (1) cause the Corresponding Source to be so\\n \\\navailable, or (2) arrange to deprive yourself of the benefit of the\\n \\\npatent license for this particular work, or (3) arrange, in a manner\\n \\\nconsistent with the requirements of this License, to extend the patent\\n \\\nlicense to downstream recipients.  \\\"Knowingly relying\\\" means you have\\n \\\nactual knowledge that, but for the patent license, your conveying the\\n \\\ncovered work in a country, or your recipient's use of the covered work\\n \\\nin a country, would infringe one or more identifiable patents in that\\n \\\ncountry that you have reason to believe are valid.\\n \\\n\\n \\\n  If, pursuant to or in connection with a single transaction or\\n \\\narrangement, you convey, or propagate by procuring conveyance of, a\\n \\\ncovered work, and grant a patent license to some of the parties\\n \\\nreceiving the covered work authorizing them to use, propagate, modify\\n \\\nor convey a specific copy of the covered work, then the patent license\\n \\\nyou grant is automatically extended to all recipients of the covered\\n \\\nwork and works based on it.\\n \\\n\\n \\\n  A patent license is \\\"discriminatory\\\" if it does not include within\\n \\\nthe scope of its coverage, prohibits the exercise of, or is\\n \\\nconditioned on the non-exercise of one or more of the rights that are\\n \\\nspecifically granted under this License.  You may not convey a covered\\n \\\nwork if you are a party to an arrangement with a third party that is\\n \\\nin the business of distributing software, under which you make payment\\n \\\nto the third party based on the extent of your activity of conveying\\n \\\nthe work, and under which the third party grants, to any of the\\n \\\nparties who would receive the covered work from you, a discriminatory\\n \\\npatent license (a) in connection with copies of the covered work\\n \\\nconveyed by you (or copies made from those copies), or (b) primarily\\n \\\nfor and in connection with specific products or compilations that\\n \\\ncontain the covered work, unless you entered into that arrangement,\\n \\\nor that patent license was granted, prior to 28 March 2007.\\n \\\n\\n \\\n  Nothing in this License shall be construed as excluding or limiting\\n \\\nany implied license or other defenses to infringement that may\\n \\\notherwise be available to you under applicable patent law.\\n \\\n\\n \\\n  12. No Surrender of Others' Freedom.\\n \\\n\\n \\\n  If conditions are imposed on you (whether by court order, agreement or\\n \\\notherwise) that contradict the conditions of this License, they do not\\n \\\nexcuse you from the conditions of this License.  If you cannot convey a\\n \\\ncovered work so as to satisfy simultaneously your obligations under this\\n \\\nLicense and any other pertinent obligations, then as a consequence you may\\n \\\nnot convey it at all.  For example, if you agree to terms that obligate you\\n \\\nto collect a royalty for further conveying from those to whom you convey\\n \\\nthe Program, the only way you could satisfy both those terms and this\\n \\\nLicense would be to refrain entirely from conveying the Program.\\n \\\n\\n \\\n  13. Use with the GNU Affero General Public License.\\n \\\n\\n \\\n  Notwithstanding any other provision of this License, you have\\n \\\npermission to link or combine any covered work with a work licensed\\n \\\nunder version 3 of the GNU Affero General Public License into a single\\n \\\ncombined work, and to convey the resulting work.  The terms of this\\n \\\nLicense will continue to apply to the part which is the covered work,\\n \\\nbut the special requirements of the GNU Affero General Public License,\\n \\\nsection 13, concerning interaction through a network will apply to the\\n \\\ncombination as such.\\n \\\n\\n \\\n  14. Revised Versions of this License.\\n \\\n\\n \\\n  The Free Software Foundation may publish revised and/or new versions of\\n \\\nthe GNU General Public License from time to time.  Such new versions will\\n \\\nbe similar in spirit to the present version, but may differ in detail to\\n \\\naddress new problems or concerns.\\n \\\n\\n \\\n  Each version is given a distinguishing version number.  If the\\n \\\nProgram specifies that a certain numbered version of the GNU General\\n \\\nPublic License \\\"or any later version\\\" applies to it, you have the\\n \\\noption of following the terms and conditions either of that numbered\\n \\\nversion or of any later version published by the Free Software\\n \\\nFoundation.  If the Program does not specify a version number of the\\n \\\nGNU General Public License, you may choose any version ever published\\n \\\nby the Free Software Foundation.\\n \\\n\\n \\\n  If the Program specifies that a proxy can decide which future\\n \\\nversions of the GNU General Public License can be used, that proxy's\\n \\\npublic statement of acceptance of a version permanently authorizes you\\n \\\nto choose that version for the Program.\\n \\\n\\n \\\n  Later license versions may give you additional or different\\n \\\npermissions.  However, no additional obligations are imposed on any\\n \\\nauthor or copyright holder as a result of your choosing to follow a\\n \\\nlater version.\\n \\\n\\n \\\n  15. Disclaimer of Warranty.\\n \\\n\\n \\\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\\n \\\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\\n \\\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \\\"AS IS\\\" WITHOUT WARRANTY\\n \\\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\\n \\\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\\n \\\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\\n \\\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\\n \\\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\\n \\\n\\n \\\n  16. Limitation of Liability.\\n \\\n\\n \\\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\\n \\\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\\n \\\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\\n \\\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\\n \\\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\\n \\\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\\n \\\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\\n \\\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\\n \\\nSUCH DAMAGES.\\n \\\n\\n \\\n  17. Interpretation of Sections 15 and 16.\\n \\\n\\n \\\n  If the disclaimer of warranty and limitation of liability provided\\n \\\nabove cannot be given local legal effect according to their terms,\\n \\\nreviewing courts shall apply local law that most closely approximates\\n \\\nan absolute waiver of all civil liability in connection with the\\n \\\nProgram, unless a warranty or assumption of liability accompanies a\\n \\\ncopy of the Program in return for a fee.\\n \\\n\\n \\\n                     END OF TERMS AND CONDITIONS\\n \\\n\\n\";\n\n \n"
  },
  {
    "path": "debian/eos-client.install",
    "content": "usr/bin/eos\nusr/bin/eoscp\nusr/lib/*-linux-gnu/libEosCommon.so.*\nusr/lib/*-linux-gnu/libEosCommon.so\nusr/lib/*-linux-gnu/libEosFstIo.so.*\nusr/lib/*-linux-gnu/libEosFstIo.so\n\n"
  },
  {
    "path": "debian/eos-fusex.install",
    "content": "usr/bin/eosxd\nusr/bin/eosxd3\nusr/bin/eoscfsd\nusr/bin/eosfusebind\nusr/sbin/mount.eosx\nusr/sbin/mount.eosx3\nusr/sbin/mount.eoscfs\nvar/log/eos/\nvar/log/eos/fusex/\netc/fuse.conf.eos\netc/logrotate.d/eos-fusex-logs\netc/eos/\netc/eos/cfsd\netc/eos/cfsd/eoscfsd.conf\n\n"
  },
  {
    "path": "debian/eos-fusex.postinst",
    "content": "#!/bin/bash\n\nchown daemon:daemon /var/log/eos/\nchown daemon:daemon /var/log/eos/fusex/\n"
  },
  {
    "path": "debian/eos-test.install",
    "content": "usr/sbin/eos-fuse-test\nusr/sbin/eos-fusex-certify\nusr/sbin/eos-instance-test\nusr/sbin/eos-instance-test-ci\nusr/sbin/eos-accounting-test\nusr/sbin/eos-file-cont-detached-test\nusr/sbin/eos-lru-test\nusr/sbin/eos-io-test\nusr/sbin/eos-io-tool\nusr/sbin/eos-oc-test\nusr/sbin/eos-rain-test\nusr/sbin/eoscp-rain-test\nusr/sbin/fusex-benchmark\nusr/sbin/xrdcpabort\nusr/sbin/xrdcpappend\nusr/sbin/xrdcpbackward\nusr/sbin/xrdcpdownloadrandom\nusr/sbin/xrdcpextend\nusr/sbin/xrdcpholes\nusr/sbin/xrdcppartial\nusr/sbin/xrdcpposixcache\nusr/sbin/xrdcprandom\nusr/sbin/xrdcpshrink\nusr/sbin/xrdcptruncate\nusr/sbin/xrdstress\nusr/sbin/xrdstress.exe\nusr/sbin/eos-test-credential-bindings\nusr/sbin/eos-fusex-functional-test\nvar/eos/test/fuse/untar/untar.tgz\nvar/eos/test/fuse/untar/xrootd.tgz\n"
  },
  {
    "path": "debian/eos-testkeytab.install",
    "content": "etc/eos.keytab\netc/eos.client.keytab\n"
  },
  {
    "path": "debian/eos-testkeytab.postinst",
    "content": "#!/bin/bash\n\nchown daemon:daemon /etc/eos.keytab\nchown daemon:daemon /etc/eos.client.keytab\nchmod 400 /etc/eos.keytab\nchmod 400 /etc/eos.client.keytab\n"
  },
  {
    "path": "debian/rules",
    "content": "#!/usr/bin/make -f\n\n%:\n\tdh $@ --builddirectory=build --destdir=deb_packages\n\noverride_dh_auto_configure:\n\tdh_auto_configure --                      \\\n\t\t-DCLIENT=1                        \\\n\t\t-DCMAKE_BUILD_TYPE=RelWithDebInfo \\\n\t\t-DCMAKE_VERBOSE_MAKEFILE=OFF\n\noverride_dh_install:\n\tdh_install --sourcedir=deb_packages --autodest\n"
  },
  {
    "path": "debian/source/format",
    "content": "3.0 (quilt)\n"
  },
  {
    "path": "doc/_themes/solar_theme/__init__.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n__author__ = 'Vimalkumar Velayudhan'\n__email__ = 'vimalkumarvelayudhan@gmail.com'\n__version__ = '1.3.3'\n\nimport os.path\n\npkg_dir = os.path.abspath(os.path.dirname(__file__))\ntheme_path = os.path.split(pkg_dir)[0]\n"
  },
  {
    "path": "doc/_themes/solar_theme/layout.html",
    "content": "{% extends \"basic/layout.html\" %}\n\n{%- block doctype -%}\n<!DOCTYPE html>\n{%- endblock -%}\n\n{%- block extrahead -%}\n<link href='http://fonts.googleapis.com/css?family=Source+Code+Pro|Open+Sans:300italic,400italic,700italic,400,300,700' rel='stylesheet' type='text/css'>\n{%- endblock -%}\n\n{# put the sidebar before the body #}\n{% block sidebar1 %}{{ sidebar() }}{% endblock %}\n{% block sidebar2 %}{% endblock %}\n\n{%- block footer %}\n    <div class=\"footer\">\n    {%- if show_copyright %}\n      {%- if hasdoc('copyright') %}\n        {% trans path=pathto('copyright'), copyright=copyright|e %}&copy; <a href=\"{{ path }}\">Copyright</a> {{ copyright }}.{% endtrans %}\n      {%- else %}\n        {% trans copyright=copyright|e %}&copy; Copyright {{ copyright }}.{% endtrans %}\n      {%- endif %}\n    {%- endif %}\n    {%- if last_updated %}\n      {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}\n    {%- endif %}\n    {%- if show_sphinx %}\n    {% trans sphinx_version=sphinx_version|e %}Created using <a href=\"http://sphinx.pocoo.org/\">Sphinx</a> {{ sphinx_version }}.Theme is <a href=\"http://github.com/vimalkvn/solar-theme\">Solar</a>{% endtrans %}\n    {%- endif %}\n    </div>\n{%- endblock %}\n"
  },
  {
    "path": "doc/_themes/solar_theme/static/solar.css",
    "content": "/* solar.css\n * Modified from sphinxdoc.css of the sphinxdoc theme.\n*/\n\n@import url(\"basic.css\");\n\n/* -- page layout ----------------------------------------------------------- */\n\nbody {\n    font-family: 'Open Sans', sans-serif;\n    font-size: 14px;\n    line-height: 150%;\n    text-align: center;\n    color: #002b36;\n    padding: 0;\n    margin: 0px 80px 0px 80px;\n    min-width: 740px;\t\n    -moz-box-shadow: 0px 0px 10px #93a1a1;\n    -webkit-box-shadow: 0px 0px 10px #93a1a1;\n    box-shadow: 0px 0px 10px #93a1a1;\n    background: url(\"subtle_dots.png\") repeat;\n\n}\n\ndiv.document {\n    background-color: #fcfcfc;\n    text-align: left;\n    background-repeat: repeat-x;\n}\n\ndiv.bodywrapper {\n    margin: 0 240px 0 0;\n    border-right: 1px dotted #eee8d5;\n}\n\ndiv.body {\n    background-color: white;\n    margin: 0;\n    padding: 0.5em 20px 20px 20px;\n}\n\ndiv.related {\n    font-size: 1em;\n    background: #f2f2f2;\n    color: #839496;\n    padding: 5px 0px;\n}\n\ndiv.related ul {\n    height: 2em;\n    margin: 2px;\n}\n\ndiv.related ul li {\n    margin: 0;\n    padding: 0;\n    height: 2em;\n    float: left;\n}\n\ndiv.related ul li.right {\n    float: right;\n    margin-right: 5px;\n}\n\ndiv.related ul li a {\n    margin: 0;\n    padding: 2px 5px;\n    line-height: 2em;\n    text-decoration: none;\n    color: #839496;\n}\n\ndiv.related ul li a:hover {\n    background-color: #073642;\n    -webkit-border-radius: 2px;\n    -moz-border-radius: 2px;\n    border-radius: 2px;\n}\n\ndiv.sphinxsidebarwrapper {\n    padding: 0;\n}\n\ndiv.sphinxsidebar {\n    margin: 0;\n    padding: 0.5em 15px 15px 0;\n    width: 210px;\n    float: right;\n    font-size: 0.9em;\n    text-align: left;\n}\n\ndiv.sphinxsidebar h3, div.sphinxsidebar h4 {\n    margin: 1em 0 0.5em 0;\n    font-size: 1em;\n    padding: 0.7em;\n    background-color: #eeeff1;\n}\n\ndiv.sphinxsidebar h3 a {\n    color: #2E3436;\n}\n\ndiv.sphinxsidebar ul {\n    padding-left: 1.5em;\n    margin-top: 7px;\n    padding: 0;\n    line-height: 150%;\n    color: #586e75;\n}\n\ndiv.sphinxsidebar ul ul {\n    margin-left: 20px;\n}\n\ndiv.sphinxsidebar input {\n    border: 1px solid #eee8d5;\n}\n\ndiv.footer {\n    background-color: #93a1a1;\n    color: #eee;\n    padding: 3px 8px 3px 0;\n    clear: both;\n    font-size: 0.8em;\n    text-align: right;\n}\n\ndiv.footer a {\n    color: #eee;\n    text-decoration: none;\n}\n\n/* -- body styles ----------------------------------------------------------- */\n\np {    \n    margin: 0.8em 0 0.5em 0;\n}\n\ndiv.body a, div.sphinxsidebarwrapper a {\n    color: #e87a19;\n    text-decoration: none;\n}\n\ndiv.body a:hover, div.sphinxsidebarwrapper a:hover {\n    border-bottom: 1px solid #e87a19;\n}\n\nh1, h2, h3, h4, h5, h6 {\n    font-family: \"Open Sans\", sans-serif;\n    font-weight: 300;\n}\n\nh1 {\n    margin: 0;\n    padding: 0.7em 0 0.3em 0;\n    line-height: 1.2em;\n    color: #002b36;\n    text-shadow: #eee 0.1em 0.1em 0.1em;\n}\n\nh2 {\n    margin: 1.3em 0 0.2em 0;\n    padding: 0 0 10px 0;\n    color: #073642;\n    border-bottom: 1px solid #eee;\n}\n\nh3 {\n    margin: 1em 0 -0.3em 0;\n    padding-bottom: 5px;\n}\n\nh3, h4, h5, h6 {\n    color: #073642;\n\tborder-bottom: 1px dotted #eee;\n}\n\ndiv.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a {\n    color: #657B83!important;\n}\n\nh1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor {\n    display: none;\n    margin: 0 0 0 0.3em;\n    padding: 0 0.2em 0 0.2em;\n    color: #aaa!important;\n}\n\nh1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor,\nh5:hover a.anchor, h6:hover a.anchor {\n    display: inline;\n}\n\nh1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover,\nh5 a.anchor:hover, h6 a.anchor:hover {\n    color: #777;\n    background-color: #eee;\n}\n\na.headerlink {\n    color: #c60f0f!important;\n    font-size: 1em;\n    margin-left: 6px;\n    padding: 0 4px 0 4px;\n    text-decoration: none!important;\n}\n\na.headerlink:hover {\n    background-color: #ccc;\n    color: white!important;\n}\n\n\ncite, code, tt {\n    font-family: 'Source Code Pro', monospace;\n\tfont-size: 0.9em;\n    letter-spacing: 0.01em;\n    background-color: #eeeff2;\n    font-style: normal;\n}\n\nhr {\n    border: 1px solid #eee;\n    margin: 2em;\n}\n\n.highlight {\n    -webkit-border-radius: 2px;\n    -moz-border-radius: 2px;\n    border-radius: 2px;\n}\n\npre {\n    font-family: 'Source Code Pro', monospace;\n    font-style: normal;\n\tfont-size: 0.9em;\n    letter-spacing: 0.015em;\n    line-height: 120%;\n    padding: 0.7em;\n\twhite-space: pre-wrap;       /* css-3 */\n\twhite-space: -moz-pre-wrap;  /* Mozilla, since 1999 */\n\twhite-space: -pre-wrap;      /* Opera 4-6 */\n\twhite-space: -o-pre-wrap;    /* Opera 7 */\n\tword-wrap: break-word;       /* Internet Explorer 5.5+ */\n}\n\npre a {\n    color: inherit;\n    text-decoration: underline;\n}\n\ntd.linenos pre {\n    padding: 0.5em 0;\n}\n\ndiv.quotebar {\n    background-color: #f8f8f8;\n    max-width: 250px;\n    float: right;\n    padding: 2px 7px;\n    border: 1px solid #ccc;\n}\n\ndiv.topic {\n    background-color: #f8f8f8;\n}\n\ntable {\n    border-collapse: separate;\n    margin: 0 -0.5em 0 -0.5em;\n}\n\ntable td, table th {\n    padding: 0.2em 0.5em 0.2em 0.5em;\n    border: 1px solid;\n    border-color:darkblue;\n    background-color: #f7f7f7;\n}\n\ndiv.admonition {\n    font-size: 0.9em;\n    margin: 1em 0 1em 0;\n    border: 1px solid #eee;\n    background-color: #f7f7f7;\n    padding: 0;\n    -moz-box-shadow: 0px 8px 6px -8px #93a1a1;\n    -webkit-box-shadow: 0px 8px 6px -8px #93a1a1;\n    box-shadow: 0px 8px 6px -8px #93a1a1;\n}\n\ndiv.admonition p, div.admonition div.highlight {\n    margin: 0.5em 1em 0.5em 1em;\n}\n\ndiv.admonition p.admonition-title\n{\n    margin: 0;\n    padding: 0.2em 0 0.2em 0.6em;\n    color: white;\n    border-bottom: 1px solid #eee8d5;\n    font-weight: bold;\n    background-color: #FFBF00;\n}\n\ndiv.warning p.admonition-title,\ndiv.important p.admonition-title {\n    background-color: #cb4b16;\n}\n\ndiv.hint p.admonition-title,\ndiv.tip p.admonition-title {\n    background-color: #859900;\n}\n\ndiv.caution p.admonition-title,\ndiv.attention p.admonition-title,\ndiv.danger p.admonition-title,\ndiv.error p.admonition-title {\n    background-color: #dc322f;\n}\n\ndiv.admonition ul, div.admonition ol {\n    margin: 0.1em 0.5em 0.5em 3em;\n    padding: 0;\n}\n\ndiv.versioninfo {\n    margin: 1em 0 0 0;\n    border: 1px solid #eee;\n    background-color: #DDEAF0;\n    padding: 8px;\n    line-height: 1.3em;\n    font-size: 0.9em;\n}\n\ndiv.viewcode-block:target {\n    background-color: #f4debf;\n    border-top: 1px solid #eee;\n    border-bottom: 1px solid #eee;\n}\n\n\n.highlight  { \n    background: #dffc7f62; \n    color: #151515;\n    font-family: 'Consolas', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;\n    font-size: 13px;\n    letter-spacing: 0.015em;\n    line-height: 120%;\n    border: 1px solid #ccc;\n    border-radius: 2px;\n\n}\n\ntable.docutils \n{\n    border: 0.5px solid; \n    border-collapse: collapse;\n    border-top: 0.5px solid #fb7e02;\n    border-left: 0.5px solid #fb7e02;\n    border-right: 0.5px solid #fb7e02;\n    border-bottom: 0.5px solid #fb7e02;\n}\n\ntr.row-even \n{\n    color: #000000;\n    background-color: #093075;  \n    border-color:#859900\n    border: 0.5px solid; \n}\n\ntr.row-odd \n{\n    color: #000000;\n    background-color: #093075;  \n    border-color:#859900\n    border: 0.5px solid; \n}\n\ndiv.section table, td\n{\n  white-space: normal !important;  \n}\n\n\ndiv.section thead, th\n{\n  color: #ff0000;\n  background-color: #093075;    \n}\n\n.rst-content table.docutils thead th, .rst-content table.field-list thead th {\n    border-color: #e1e4e5;\n}\n\ndiv.sphinxsidebar {\n    top: 50;\n    right: 100px;\n    margin: 0;\n\n    padding: 0.5em 15px 15px 0;\n    width: 210px;\n    height: 80%;\n    float: right;\n    font-size: 0.9em;\n    text-align: left;\n    position: fixed;\n    overflow-y: scroll\n}\n\n\ndiv.sphinxsidebar::-webkit-scrollbar {\n    width: 10px;\n}\n\n/* Customize the scrollbar */\ndiv.sphinxsidebar::-webkit-scrollbar-thumb {\n    background-color: #888;\n}"
  },
  {
    "path": "doc/_themes/solar_theme/static/solarized-dark.css",
    "content": "/* solarized dark style for solar theme */\n\n/*style pre scrollbar*/\npre::-webkit-scrollbar, .highlight::-webkit-scrollbar {\n\theight: 0.5em;\n\tbackground: #073642;\n}\n\npre::-webkit-scrollbar-thumb {\n\tborder-radius: 1em;\n\tbackground: #93a1a1;\n}\n\n/* pygments style */\n.highlight .hll { background-color: #ffffcc }\n.highlight  { background: #002B36!important; color: #93A1A1 }\n.highlight .c { color: #586E75 } /* Comment */\n.highlight .err { color: #93A1A1 } /* Error */\n.highlight .g { color: #93A1A1 } /* Generic */\n.highlight .k { color: #859900 } /* Keyword */\n.highlight .l { color: #93A1A1 } /* Literal */\n.highlight .n { color: #93A1A1 } /* Name */\n.highlight .o { color: #859900 } /* Operator */\n.highlight .x { color: #CB4B16 } /* Other */\n.highlight .p { color: #93A1A1 } /* Punctuation */\n.highlight .cm { color: #586E75 } /* Comment.Multiline */\n.highlight .cp { color: #859900 } /* Comment.Preproc */\n.highlight .c1 { color: #586E75 } /* Comment.Single */\n.highlight .cs { color: #859900 } /* Comment.Special */\n.highlight .gd { color: #2AA198 } /* Generic.Deleted */\n.highlight .ge { color: #93A1A1; font-style: italic } /* Generic.Emph */\n.highlight .gr { color: #DC322F } /* Generic.Error */\n.highlight .gh { color: #CB4B16 } /* Generic.Heading */\n.highlight .gi { color: #859900 } /* Generic.Inserted */\n.highlight .go { color: #93A1A1 } /* Generic.Output */\n.highlight .gp { color: #93A1A1 } /* Generic.Prompt */\n.highlight .gs { color: #93A1A1; font-weight: bold } /* Generic.Strong */\n.highlight .gu { color: #CB4B16 } /* Generic.Subheading */\n.highlight .gt { color: #93A1A1 } /* Generic.Traceback */\n.highlight .kc { color: #CB4B16 } /* Keyword.Constant */\n.highlight .kd { color: #268BD2 } /* Keyword.Declaration */\n.highlight .kn { color: #859900 } /* Keyword.Namespace */\n.highlight .kp { color: #859900 } /* Keyword.Pseudo */\n.highlight .kr { color: #268BD2 } /* Keyword.Reserved */\n.highlight .kt { color: #DC322F } /* Keyword.Type */\n.highlight .ld { color: #93A1A1 } /* Literal.Date */\n.highlight .m { color: #2AA198 } /* Literal.Number */\n.highlight .s { color: #2AA198 } /* Literal.String */\n.highlight .na { color: #93A1A1 } /* Name.Attribute */\n.highlight .nb { color: #B58900 } /* Name.Builtin */\n.highlight .nc { color: #268BD2 } /* Name.Class */\n.highlight .no { color: #CB4B16 } /* Name.Constant */\n.highlight .nd { color: #268BD2 } /* Name.Decorator */\n.highlight .ni { color: #CB4B16 } /* Name.Entity */\n.highlight .ne { color: #CB4B16 } /* Name.Exception */\n.highlight .nf { color: #268BD2 } /* Name.Function */\n.highlight .nl { color: #93A1A1 } /* Name.Label */\n.highlight .nn { color: #93A1A1 } /* Name.Namespace */\n.highlight .nx { color: #93A1A1 } /* Name.Other */\n.highlight .py { color: #93A1A1 } /* Name.Property */\n.highlight .nt { color: #268BD2 } /* Name.Tag */\n.highlight .nv { color: #268BD2 } /* Name.Variable */\n.highlight .ow { color: #859900 } /* Operator.Word */\n.highlight .w { color: #93A1A1 } /* Text.Whitespace */\n.highlight .mf { color: #2AA198 } /* Literal.Number.Float */\n.highlight .mh { color: #2AA198 } /* Literal.Number.Hex */\n.highlight .mi { color: #2AA198 } /* Literal.Number.Integer */\n.highlight .mo { color: #2AA198 } /* Literal.Number.Oct */\n.highlight .sb { color: #586E75 } /* Literal.String.Backtick */\n.highlight .sc { color: #2AA198 } /* Literal.String.Char */\n.highlight .sd { color: #93A1A1 } /* Literal.String.Doc */\n.highlight .s2 { color: #2AA198 } /* Literal.String.Double */\n.highlight .se { color: #CB4B16 } /* Literal.String.Escape */\n.highlight .sh { color: #93A1A1 } /* Literal.String.Heredoc */\n.highlight .si { color: #2AA198 } /* Literal.String.Interpol */\n.highlight .sx { color: #2AA198 } /* Literal.String.Other */\n.highlight .sr { color: #DC322F } /* Literal.String.Regex */\n.highlight .s1 { color: #2AA198 } /* Literal.String.Single */\n.highlight .ss { color: #2AA198 } /* Literal.String.Symbol */\n.highlight .bp { color: #268BD2 } /* Name.Builtin.Pseudo */\n.highlight .vc { color: #268BD2 } /* Name.Variable.Class */\n.highlight .vg { color: #268BD2 } /* Name.Variable.Global */\n.highlight .vi { color: #268BD2 } /* Name.Variable.Instance */\n.highlight .il { color: #2AA198 } /* Literal.Number.Integer.Long */\n"
  },
  {
    "path": "doc/_themes/solar_theme/theme.conf",
    "content": "[theme]\ninherit = basic\nstylesheet = solar.css\npygments_style = none\n"
  },
  {
    "path": "doc/_themes/sphinx13/layout.html",
    "content": "{#\n    sphinxdoc/layout.html\n    ~~~~~~~~~~~~~~~~~~~~~\n\n    Sphinx layout template for the sphinxdoc theme.\n\n    :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.\n    :license: BSD, see LICENSE for details.\n#}\n{%- extends \"basic/layout.html\" %}\n\n{# put the sidebar before the body #}\n{% block sidebar1 %}{{ sidebar() }}{% endblock %}\n{% block sidebar2 %}{% endblock %}\n\n{% block extrahead %}\n    <link href='http://fonts.googleapis.com/css?family=Open+Sans:300,400,700'\n          rel='stylesheet' type='text/css'>\n{{ super() }}\n{%- if not embedded %}\n    <style type=\"text/css\">\n      table.right { float: right; margin-left: 20px; }\n      table.right td { border: 1px solid #ccc; }\n      {% if pagename == 'index' %}\n      .related { display: none; }\n      {% endif %}\n    </style>\n    <script type=\"text/javascript\">\n      // intelligent scrolling of the sidebar content\n      $(window).scroll(function() {\n        var sb = $('.sphinxsidebarwrapper');\n        var win = $(window);\n        var sbh = sb.height();\n        var offset = $('.sphinxsidebar').position()['top'];\n        var wintop = win.scrollTop();\n        var winbot = wintop + win.innerHeight();\n        var curtop = sb.position()['top'];\n        var curbot = curtop + sbh;\n        // does sidebar fit in window?\n        if (sbh < win.innerHeight()) {\n          // yes: easy case -- always keep at the top\n          sb.css('top', $u.min([$u.max([0, wintop - offset - 10]),\n                                $(document).height() - sbh - 200]));\n        } else {\n          // no: only scroll if top/bottom edge of sidebar is at\n          // top/bottom edge of window\n          if (curtop > wintop && curbot > winbot) {\n            sb.css('top', $u.max([wintop - offset - 10, 0]));\n          } else if (curtop < wintop && curbot < winbot) {\n            sb.css('top', $u.min([winbot - sbh - offset - 20,\n                                  $(document).height() - sbh - 200]));\n          }\n        }\n      });\n    </script>\n{%- endif %}\n{% endblock %}\n\n{% block rootrellink %}\n        <li><a href=\"{{ pathto('index') }}\">EOS Home</a>&nbsp;|</li>\n        <li><a href=\"{{ pathto('contents') }}\">Documentation</a> &raquo;</li>\n{% endblock %}\n\n{% block header %}\n<div class=\"pageheader\">\n  <ul>\n    <li><a href=\"{{ pathto('index') }}\">Home</a></li>\n    <li><a href=\"{{ pathto('install') }}\">Get it</a></li>\n    <li><a href=\"{{ pathto('contents') }}\">Docs</a></li>\n    <li><a href=\"{{ pathto('develop') }}\">Extend/Develop</a></li>\n  </ul>\n  <div>\n    <a href=\"{{ pathto('index') }}\">\n      <img src=\"{{ pathto('_static/EOS-Logo2.jpg', 1) }}\" alt=\"EOS\" />\n    </a>\n  </div>\n</div>\n{% endblock %}\n"
  },
  {
    "path": "doc/_themes/sphinx13/theme.conf",
    "content": "[theme]\ninherit = basic\nstylesheet = sphinx13.css\npygments_style = trac\n"
  },
  {
    "path": "doc/citrine/Doxyfile",
    "content": "# Doxyfile 1.7.6.1\n\n# This file describes the settings to be used by the documentation system\n# doxygen (www.doxygen.org) for a project\n#\n# All text after a hash (#) is considered a comment and will be ignored\n# The format is:\n#       TAG = value [value, ...]\n# For lists items can also be appended using:\n#       TAG += value [value, ...]\n# Values that contain spaces should be placed between quotes (\" \")\n\n#---------------------------------------------------------------------------\n# Project related configuration options\n#---------------------------------------------------------------------------\n\n# This tag specifies the encoding used for all characters in the config file \n# that follow. The default is UTF-8 which is also the encoding used for all \n# text before the first occurrence of this tag. Doxygen uses libiconv (or the \n# iconv built into libc) for the transcoding. See \n# http://www.gnu.org/software/libiconv for the list of possible encodings.\n\nDOXYFILE_ENCODING      = UTF-8\n\n# The PROJECT_NAME tag is a single word (or sequence of words) that should \n# identify the project. Note that if you do not use Doxywizard you need \n# to put quotes around the project name if it contains spaces.\n\nPROJECT_NAME           = EOS\n\n# The PROJECT_NUMBER tag can be used to enter a project or revision number. \n# This could be handy for archiving the generated documentation or \n# if some version control system is used.\n\nPROJECT_NUMBER         = \n\n# Using the PROJECT_BRIEF tag one can provide an optional one line description \n# for a project that appears at the top of each page and should give viewer \n# a quick idea about the purpose of the project. Keep the description short.\n\nPROJECT_BRIEF          = \n\n# With the PROJECT_LOGO tag one can specify an logo or icon that is \n# included in the documentation. The maximum height of the logo should not \n# exceed 55 pixels and the maximum width should not exceed 200 pixels. \n# Doxygen will copy the logo to the output directory.\n\nPROJECT_LOGO           = \n\n# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) \n# base path where the generated documentation will be put. \n# If a relative path is entered, it will be relative to the location \n# where doxygen was started. If left blank the current directory will be used.\n\nOUTPUT_DIRECTORY       = ./\n\n# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create \n# 4096 sub-directories (in 2 levels) under the output directory of each output \n# format and will distribute the generated files over these directories. \n# Enabling this option can be useful when feeding doxygen a huge amount of \n# source files, where putting all generated files in the same directory would \n# otherwise cause performance problems for the file system.\n\nCREATE_SUBDIRS         = NO\n\n# The OUTPUT_LANGUAGE tag is used to specify the language in which all \n# documentation generated by doxygen is written. Doxygen will use this \n# information to generate all constant output in the proper language. \n# The default language is English, other supported languages are: \n# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, \n# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, \n# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English \n# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, \n# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, \n# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.\n\nOUTPUT_LANGUAGE        = English\n\n# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will \n# include brief member descriptions after the members that are listed in \n# the file and class documentation (similar to JavaDoc). \n# Set to NO to disable this.\n\nBRIEF_MEMBER_DESC      = YES\n\n# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend \n# the brief description of a member or function before the detailed description. \n# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the \n# brief descriptions will be completely suppressed.\n\nREPEAT_BRIEF           = YES\n\n# This tag implements a quasi-intelligent brief description abbreviator \n# that is used to form the text in various listings. Each string \n# in this list, if found as the leading text of the brief description, will be \n# stripped from the text and the result after processing the whole list, is \n# used as the annotated text. Otherwise, the brief description is used as-is. \n# If left blank, the following values are used (\"$name\" is automatically \n# replaced with the name of the entity): \"The $name class\" \"The $name widget\" \n# \"The $name file\" \"is\" \"provides\" \"specifies\" \"contains\" \n# \"represents\" \"a\" \"an\" \"the\"\n\nABBREVIATE_BRIEF       = \"The $name class\" \\\n                         \"The $name widget\" \\\n                         \"The $name file\" \\\n                         is \\\n                         provides \\\n                         specifies \\\n                         contains \\\n                         represents \\\n                         a \\\n                         an \\\n                         the\n\n# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then \n# Doxygen will generate a detailed section even if there is only a brief \n# description.\n\nALWAYS_DETAILED_SEC    = NO\n\n# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all \n# inherited members of a class in the documentation of that class as if those \n# members were ordinary class members. Constructors, destructors and assignment \n# operators of the base classes will not be shown.\n\nINLINE_INHERITED_MEMB  = NO\n\n# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full \n# path before files name in the file list and in the header files. If set \n# to NO the shortest path that makes the file name unique will be used.\n\nFULL_PATH_NAMES        = NO\n\n# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag \n# can be used to strip a user-defined part of the path. Stripping is \n# only done if one of the specified strings matches the left-hand part of \n# the path. The tag can be used to show relative paths in the file list. \n# If left blank the directory from which doxygen is run is used as the \n# path to strip.\n\nSTRIP_FROM_PATH        = \n\n# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of \n# the path mentioned in the documentation of a class, which tells \n# the reader which header file to include in order to use a class. \n# If left blank only the name of the header file containing the class \n# definition is used. Otherwise one should specify the include paths that \n# are normally passed to the compiler using the -I flag.\n\nSTRIP_FROM_INC_PATH    = \n\n# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter \n# (but less readable) file names. This can be useful if your file system \n# doesn't support long names like on DOS, Mac, or CD-ROM.\n\nSHORT_NAMES            = NO\n\n# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen \n# will interpret the first line (until the first dot) of a JavaDoc-style \n# comment as the brief description. If set to NO, the JavaDoc \n# comments will behave just like regular Qt-style comments \n# (thus requiring an explicit @brief command for a brief description.)\n\nJAVADOC_AUTOBRIEF      = YES\n\n# If the QT_AUTOBRIEF tag is set to YES then Doxygen will \n# interpret the first line (until the first dot) of a Qt-style \n# comment as the brief description. If set to NO, the comments \n# will behave just like regular Qt-style comments (thus requiring \n# an explicit \\brief command for a brief description.)\n\nQT_AUTOBRIEF           = NO\n\n# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen \n# treat a multi-line C++ special comment block (i.e. a block of //! or /// \n# comments) as a brief description. This used to be the default behaviour. \n# The new default is to treat a multi-line C++ comment block as a detailed \n# description. Set this tag to YES if you prefer the old behaviour instead.\n\nMULTILINE_CPP_IS_BRIEF = NO\n\n# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented \n# member inherits the documentation from any documented member that it \n# re-implements.\n\nINHERIT_DOCS           = YES\n\n# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce \n# a new page for each member. If set to NO, the documentation of a member will \n# be part of the file/class/namespace that contains it.\n\nSEPARATE_MEMBER_PAGES  = NO\n\n# The TAB_SIZE tag can be used to set the number of spaces in a tab. \n# Doxygen uses this value to replace tabs by spaces in code fragments.\n\nTAB_SIZE               = 7\n\n# This tag can be used to specify a number of aliases that acts \n# as commands in the documentation. An alias has the form \"name=value\". \n# For example adding \"sideeffect=\\par Side Effects:\\n\" will allow you to \n# put the command \\sideeffect (or @sideeffect) in the documentation, which \n# will result in a user-defined paragraph with heading \"Side Effects:\". \n# You can put \\n's in the value part of an alias to insert newlines.\n\nALIASES                = \n\n# This tag can be used to specify a number of word-keyword mappings (TCL only). \n# A mapping has the form \"name=value\". For example adding \n# \"class=itcl::class\" will allow you to use the command class in the \n# itcl::class meaning.\n\nTCL_SUBST              = \n\n# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C \n# sources only. Doxygen will then generate output that is more tailored for C. \n# For instance, some of the names that are used will be different. The list \n# of all members will be omitted, etc.\n\nOPTIMIZE_OUTPUT_FOR_C  = YES\n\n# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java \n# sources only. Doxygen will then generate output that is more tailored for \n# Java. For instance, namespaces will be presented as packages, qualified \n# scopes will look different, etc.\n\nOPTIMIZE_OUTPUT_JAVA   = NO\n\n# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran \n# sources only. Doxygen will then generate output that is more tailored for \n# Fortran.\n\nOPTIMIZE_FOR_FORTRAN   = NO\n\n# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL \n# sources. Doxygen will then generate output that is tailored for \n# VHDL.\n\nOPTIMIZE_OUTPUT_VHDL   = NO\n\n# Doxygen selects the parser to use depending on the extension of the files it \n# parses. With this tag you can assign which parser to use for a given extension. \n# Doxygen has a built-in mapping, but you can override or extend it using this \n# tag. The format is ext=language, where ext is a file extension, and language \n# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, \n# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make \n# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C \n# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions \n# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.\n\nEXTENSION_MAPPING      = \n\n# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want \n# to include (a tag file for) the STL sources as input, then you should \n# set this tag to YES in order to let doxygen match functions declarations and \n# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. \n# func(std::string) {}). This also makes the inheritance and collaboration \n# diagrams that involve STL classes more complete and accurate.\n\nBUILTIN_STL_SUPPORT    = NO\n\n# If you use Microsoft's C++/CLI language, you should set this option to YES to \n# enable parsing support.\n\nCPP_CLI_SUPPORT        = NO\n\n# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. \n# Doxygen will parse them like normal C++ but will assume all classes use public \n# instead of private inheritance when no explicit protection keyword is present.\n\nSIP_SUPPORT            = NO\n\n# For Microsoft's IDL there are propget and propput attributes to indicate getter \n# and setter methods for a property. Setting this option to YES (the default) \n# will make doxygen replace the get and set methods by a property in the \n# documentation. This will only work if the methods are indeed getting or \n# setting a simple type. If this is not the case, or you want to show the \n# methods anyway, you should set this option to NO.\n\nIDL_PROPERTY_SUPPORT   = YES\n\n# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC \n# tag is set to YES, then doxygen will reuse the documentation of the first \n# member in the group (if any) for the other members of the group. By default \n# all members of a group must be documented explicitly.\n\nDISTRIBUTE_GROUP_DOC   = NO\n\n# Set the SUBGROUPING tag to YES (the default) to allow class member groups of \n# the same type (for instance a group of public functions) to be put as a \n# subgroup of that type (e.g. under the Public Functions section). Set it to \n# NO to prevent subgrouping. Alternatively, this can be done per class using \n# the \\nosubgrouping command.\n\nSUBGROUPING            = YES\n\n# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and \n# unions are shown inside the group in which they are included (e.g. using \n# @ingroup) instead of on a separate page (for HTML and Man pages) or \n# section (for LaTeX and RTF).\n\nINLINE_GROUPED_CLASSES = NO\n\n# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and \n# unions with only public data fields will be shown inline in the documentation \n# of the scope in which they are defined (i.e. file, namespace, or group \n# documentation), provided this scope is documented. If set to NO (the default), \n# structs, classes, and unions are shown on a separate page (for HTML and Man \n# pages) or section (for LaTeX and RTF).\n\nINLINE_SIMPLE_STRUCTS  = NO\n\n# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum \n# is documented as struct, union, or enum with the name of the typedef. So \n# typedef struct TypeS {} TypeT, will appear in the documentation as a struct \n# with name TypeT. When disabled the typedef will appear as a member of a file, \n# namespace, or class. And the struct will be named TypeS. This can typically \n# be useful for C code in case the coding convention dictates that all compound \n# types are typedef'ed and only the typedef is referenced, never the tag name.\n\nTYPEDEF_HIDES_STRUCT   = NO\n\n# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to \n# determine which symbols to keep in memory and which to flush to disk. \n# When the cache is full, less often used symbols will be written to disk. \n# For small to medium size projects (<1000 input files) the default value is \n# probably good enough. For larger projects a too small cache size can cause \n# doxygen to be busy swapping symbols to and from disk most of the time \n# causing a significant performance penalty. \n# If the system has enough physical memory increasing the cache will improve the \n# performance by keeping more symbols in memory. Note that the value works on \n# a logarithmic scale so increasing the size by one will roughly double the \n# memory usage. The cache size is given by this formula: \n# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, \n# corresponding to a cache size of 2^16 = 65536 symbols.\n\nSYMBOL_CACHE_SIZE      = 0\n\n# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be \n# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given \n# their name and scope. Since this can be an expensive process and often the \n# same symbol appear multiple times in the code, doxygen keeps a cache of \n# pre-resolved symbols. If the cache is too small doxygen will become slower. \n# If the cache is too large, memory is wasted. The cache size is given by this \n# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, \n# corresponding to a cache size of 2^16 = 65536 symbols.\n\nLOOKUP_CACHE_SIZE      = 0\n\n#---------------------------------------------------------------------------\n# Build related configuration options\n#---------------------------------------------------------------------------\n\n# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in \n# documentation are documented, even if no documentation was available. \n# Private class members and static file members will be hidden unless \n# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES\n\nEXTRACT_ALL            = YES\n\n# If the EXTRACT_PRIVATE tag is set to YES all private members of a class \n# will be included in the documentation.\n\nEXTRACT_PRIVATE        = YES\n\n# If the EXTRACT_STATIC tag is set to YES all static members of a file \n# will be included in the documentation.\n\nEXTRACT_STATIC         = YES\n\n# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) \n# defined locally in source files will be included in the documentation. \n# If set to NO only classes defined in header files are included.\n\nEXTRACT_LOCAL_CLASSES  = YES\n\n# This flag is only useful for Objective-C code. When set to YES local \n# methods, which are defined in the implementation section but not in \n# the interface are included in the documentation. \n# If set to NO (the default) only methods in the interface are included.\n\nEXTRACT_LOCAL_METHODS  = NO\n\n# If this flag is set to YES, the members of anonymous namespaces will be \n# extracted and appear in the documentation as a namespace called \n# 'anonymous_namespace{file}', where file will be replaced with the base \n# name of the file that contains the anonymous namespace. By default \n# anonymous namespaces are hidden.\n\nEXTRACT_ANON_NSPACES   = NO\n\n# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all \n# undocumented members of documented classes, files or namespaces. \n# If set to NO (the default) these members will be included in the \n# various overviews, but no documentation section is generated. \n# This option has no effect if EXTRACT_ALL is enabled.\n\nHIDE_UNDOC_MEMBERS     = NO\n\n# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all \n# undocumented classes that are normally visible in the class hierarchy. \n# If set to NO (the default) these classes will be included in the various \n# overviews. This option has no effect if EXTRACT_ALL is enabled.\n\nHIDE_UNDOC_CLASSES     = NO\n\n# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all \n# friend (class|struct|union) declarations. \n# If set to NO (the default) these declarations will be included in the \n# documentation.\n\nHIDE_FRIEND_COMPOUNDS  = NO\n\n# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any \n# documentation blocks found inside the body of a function. \n# If set to NO (the default) these blocks will be appended to the \n# function's detailed documentation block.\n\nHIDE_IN_BODY_DOCS      = NO\n\n# The INTERNAL_DOCS tag determines if documentation \n# that is typed after a \\internal command is included. If the tag is set \n# to NO (the default) then the documentation will be excluded. \n# Set it to YES to include the internal documentation.\n\nINTERNAL_DOCS          = NO\n\n# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate \n# file names in lower-case letters. If set to YES upper-case letters are also \n# allowed. This is useful if you have classes or files whose names only differ \n# in case and if your file system supports case sensitive file names. Windows \n# and Mac users are advised to set this option to NO.\n\nCASE_SENSE_NAMES       = NO\n\n# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen \n# will show members with their full class and namespace scopes in the \n# documentation. If set to YES the scope will be hidden.\n\nHIDE_SCOPE_NAMES       = NO\n\n# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen \n# will put a list of the files that are included by a file in the documentation \n# of that file.\n\nSHOW_INCLUDE_FILES     = YES\n\n# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen \n# will list include files with double quotes in the documentation \n# rather than with sharp brackets.\n\nFORCE_LOCAL_INCLUDES   = NO\n\n# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] \n# is inserted in the documentation for inline members.\n\nINLINE_INFO            = YES\n\n# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen \n# will sort the (detailed) documentation of file and class members \n# alphabetically by member name. If set to NO the members will appear in \n# declaration order.\n\nSORT_MEMBER_DOCS       = YES\n\n# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the \n# brief documentation of file, namespace and class members alphabetically \n# by member name. If set to NO (the default) the members will appear in \n# declaration order.\n\nSORT_BRIEF_DOCS        = NO\n\n# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen \n# will sort the (brief and detailed) documentation of class members so that \n# constructors and destructors are listed first. If set to NO (the default) \n# the constructors will appear in the respective orders defined by \n# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. \n# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO \n# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.\n\nSORT_MEMBERS_CTORS_1ST = NO\n\n# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the \n# hierarchy of group names into alphabetical order. If set to NO (the default) \n# the group names will appear in their defined order.\n\nSORT_GROUP_NAMES       = NO\n\n# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be \n# sorted by fully-qualified names, including namespaces. If set to \n# NO (the default), the class list will be sorted only by class name, \n# not including the namespace part. \n# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. \n# Note: This option applies only to the class list, not to the \n# alphabetical list.\n\nSORT_BY_SCOPE_NAME     = NO\n\n# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to \n# do proper type resolution of all parameters of a function it will reject a \n# match between the prototype and the implementation of a member function even \n# if there is only one candidate or it is obvious which candidate to choose \n# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen \n# will still accept a match between prototype and implementation in such cases.\n\nSTRICT_PROTO_MATCHING  = NO\n\n# The GENERATE_TODOLIST tag can be used to enable (YES) or \n# disable (NO) the todo list. This list is created by putting \\todo \n# commands in the documentation.\n\nGENERATE_TODOLIST      = YES\n\n# The GENERATE_TESTLIST tag can be used to enable (YES) or \n# disable (NO) the test list. This list is created by putting \\test \n# commands in the documentation.\n\nGENERATE_TESTLIST      = YES\n\n# The GENERATE_BUGLIST tag can be used to enable (YES) or \n# disable (NO) the bug list. This list is created by putting \\bug \n# commands in the documentation.\n\nGENERATE_BUGLIST       = YES\n\n# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or \n# disable (NO) the deprecated list. This list is created by putting \n# \\deprecated commands in the documentation.\n\nGENERATE_DEPRECATEDLIST= YES\n\n# The ENABLED_SECTIONS tag can be used to enable conditional \n# documentation sections, marked by \\if sectionname ... \\endif.\n\nENABLED_SECTIONS       = \n\n# The MAX_INITIALIZER_LINES tag determines the maximum number of lines \n# the initial value of a variable or macro consists of for it to appear in \n# the documentation. If the initializer consists of more lines than specified \n# here it will be hidden. Use a value of 0 to hide initializers completely. \n# The appearance of the initializer of individual variables and macros in the \n# documentation can be controlled using \\showinitializer or \\hideinitializer \n# command in the documentation regardless of this setting.\n\nMAX_INITIALIZER_LINES  = 27\n\n# Set the SHOW_USED_FILES tag to NO to disable the list of files generated \n# at the bottom of the documentation of classes and structs. If set to YES the \n# list will mention the files that were used to generate the documentation.\n\nSHOW_USED_FILES        = YES\n\n# If the sources in your project are distributed over multiple directories \n# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy \n# in the documentation. The default is NO.\n\nSHOW_DIRECTORIES       = NO\n\n# Set the SHOW_FILES tag to NO to disable the generation of the Files page. \n# This will remove the Files entry from the Quick Index and from the \n# Folder Tree View (if specified). The default is YES.\n\nSHOW_FILES             = YES\n\n# Set the SHOW_NAMESPACES tag to NO to disable the generation of the \n# Namespaces page.  This will remove the Namespaces entry from the Quick Index \n# and from the Folder Tree View (if specified). The default is YES.\n\nSHOW_NAMESPACES        = YES\n\n# The FILE_VERSION_FILTER tag can be used to specify a program or script that \n# doxygen should invoke to get the current version for each file (typically from \n# the version control system). Doxygen will invoke the program by executing (via \n# popen()) the command <command> <input-file>, where <command> is the value of \n# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file \n# provided by doxygen. Whatever the program writes to standard output \n# is used as the file version. See the manual for examples.\n\nFILE_VERSION_FILTER    = \n\n# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed \n# by doxygen. The layout file controls the global structure of the generated \n# output files in an output format independent way. The create the layout file \n# that represents doxygen's defaults, run doxygen with the -l option. \n# You can optionally specify a file name after the option, if omitted \n# DoxygenLayout.xml will be used as the name of the layout file.\n\nLAYOUT_FILE            = \n\n# The CITE_BIB_FILES tag can be used to specify one or more bib files \n# containing the references data. This must be a list of .bib files. The \n# .bib extension is automatically appended if omitted. Using this command \n# requires the bibtex tool to be installed. See also \n# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style \n# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this \n# feature you need bibtex and perl available in the search path.\n\nCITE_BIB_FILES         = \n\n#---------------------------------------------------------------------------\n# configuration options related to warning and progress messages\n#---------------------------------------------------------------------------\n\n# The QUIET tag can be used to turn on/off the messages that are generated \n# by doxygen. Possible values are YES and NO. If left blank NO is used.\n\nQUIET                  = NO\n\n# The WARNINGS tag can be used to turn on/off the warning messages that are \n# generated by doxygen. Possible values are YES and NO. If left blank \n# NO is used.\n\nWARNINGS               = YES\n\n# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings \n# for undocumented members. If EXTRACT_ALL is set to YES then this flag will \n# automatically be disabled.\n\nWARN_IF_UNDOCUMENTED   = YES\n\n# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for \n# potential errors in the documentation, such as not documenting some \n# parameters in a documented function, or documenting parameters that \n# don't exist or using markup commands wrongly.\n\nWARN_IF_DOC_ERROR      = YES\n\n# The WARN_NO_PARAMDOC option can be enabled to get warnings for \n# functions that are documented, but have no documentation for their parameters \n# or return value. If set to NO (the default) doxygen will only warn about \n# wrong or incomplete parameter documentation, but not about the absence of \n# documentation.\n\nWARN_NO_PARAMDOC       = NO\n\n# The WARN_FORMAT tag determines the format of the warning messages that \n# doxygen can produce. The string should contain the $file, $line, and $text \n# tags, which will be replaced by the file and line number from which the \n# warning originated and the warning text. Optionally the format may contain \n# $version, which will be replaced by the version of the file (if it could \n# be obtained via FILE_VERSION_FILTER)\n\nWARN_FORMAT            = \"$file:$line: $text\"\n\n# The WARN_LOGFILE tag can be used to specify a file to which warning \n# and error messages should be written. If left blank the output is written \n# to stderr.\n\nWARN_LOGFILE           = \n\n#---------------------------------------------------------------------------\n# configuration options related to the input files\n#---------------------------------------------------------------------------\n\n# The INPUT tag can be used to specify the files and/or directories that contain \n# documented source files. You may enter file names like \"myfile.cpp\" or \n# directories like \"/usr/src/myproject\". Separate the files or directories \n# with spaces.\n\nINPUT                  = ../mgm/\n\n# This tag can be used to specify the character encoding of the source files \n# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is \n# also the default input encoding. Doxygen uses libiconv (or the iconv built \n# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for \n# the list of possible encodings.\n\nINPUT_ENCODING         = UTF-8\n\n# If the value of the INPUT tag contains directories, you can use the \n# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp \n# and *.h) to filter out the source-files in the directories. If left \n# blank the following patterns are tested: \n# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh \n# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py \n# *.f90 *.f *.for *.vhd *.vhdl\n\nFILE_PATTERNS          = *.hh \\\n                         *.cc\n\n# The RECURSIVE tag can be used to turn specify whether or not subdirectories \n# should be searched for input files as well. Possible values are YES and NO. \n# If left blank NO is used.\n\nRECURSIVE              = YES\n\n# The EXCLUDE tag can be used to specify files and/or directories that should be \n# excluded from the INPUT source files. This way you can easily exclude a \n# subdirectory from a directory tree whose root is specified with the INPUT tag. \n# Note that relative paths are relative to the directory from which doxygen is \n# run.\n\nEXCLUDE                = \n\n# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or \n# directories that are symbolic links (a Unix file system feature) are excluded \n# from the input.\n\nEXCLUDE_SYMLINKS       = NO\n\n# If the value of the INPUT tag contains directories, you can use the \n# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude \n# certain files from those directories. Note that the wildcards are matched \n# against the file with absolute path, so to exclude all test directories \n# for example use the pattern */test/*\n\nEXCLUDE_PATTERNS       = \n\n# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names \n# (namespaces, classes, functions, etc.) that should be excluded from the \n# output. The symbol name can be a fully qualified name, a word, or if the \n# wildcard * is used, a substring. Examples: ANamespace, AClass, \n# AClass::ANamespace, ANamespace::*Test\n\nEXCLUDE_SYMBOLS        = \n\n# The EXAMPLE_PATH tag can be used to specify one or more files or \n# directories that contain example code fragments that are included (see \n# the \\include command).\n\nEXAMPLE_PATH           = \n\n# If the value of the EXAMPLE_PATH tag contains directories, you can use the \n# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp \n# and *.h) to filter out the source-files in the directories. If left \n# blank all files are included.\n\nEXAMPLE_PATTERNS       = *\n\n# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be \n# searched for input files to be used with the \\include or \\dontinclude \n# commands irrespective of the value of the RECURSIVE tag. \n# Possible values are YES and NO. If left blank NO is used.\n\nEXAMPLE_RECURSIVE      = NO\n\n# The IMAGE_PATH tag can be used to specify one or more files or \n# directories that contain image that are included in the documentation (see \n# the \\image command).\n\nIMAGE_PATH             = \n\n# The INPUT_FILTER tag can be used to specify a program that doxygen should \n# invoke to filter for each input file. Doxygen will invoke the filter program \n# by executing (via popen()) the command <filter> <input-file>, where <filter> \n# is the value of the INPUT_FILTER tag, and <input-file> is the name of an \n# input file. Doxygen will then use the output that the filter program writes \n# to standard output.  If FILTER_PATTERNS is specified, this tag will be \n# ignored.\n\nINPUT_FILTER           = \n\n# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern \n# basis.  Doxygen will compare the file name with each pattern and apply the \n# filter if there is a match.  The filters are a list of the form: \n# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further \n# info on how filters are used. If FILTER_PATTERNS is empty or if \n# non of the patterns match the file name, INPUT_FILTER is applied.\n\nFILTER_PATTERNS        = \n\n# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using \n# INPUT_FILTER) will be used to filter the input files when producing source \n# files to browse (i.e. when SOURCE_BROWSER is set to YES).\n\nFILTER_SOURCE_FILES    = NO\n\n# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file \n# pattern. A pattern will override the setting for FILTER_PATTERN (if any) \n# and it is also possible to disable source filtering for a specific pattern \n# using *.ext= (so without naming a filter). This option only has effect when \n# FILTER_SOURCE_FILES is enabled.\n\nFILTER_SOURCE_PATTERNS = \n\n#---------------------------------------------------------------------------\n# configuration options related to source browsing\n#---------------------------------------------------------------------------\n\n# If the SOURCE_BROWSER tag is set to YES then a list of source files will \n# be generated. Documented entities will be cross-referenced with these sources. \n# Note: To get rid of all source code in the generated output, make sure also \n# VERBATIM_HEADERS is set to NO.\n\nSOURCE_BROWSER         = NO\n\n# Setting the INLINE_SOURCES tag to YES will include the body \n# of functions and classes directly in the documentation.\n\nINLINE_SOURCES         = YES\n\n# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct \n# doxygen to hide any special comment blocks from generated source code \n# fragments. Normal C and C++ comments will always remain visible.\n\nSTRIP_CODE_COMMENTS    = YES\n\n# If the REFERENCED_BY_RELATION tag is set to YES \n# then for each documented function all documented \n# functions referencing it will be listed.\n\nREFERENCED_BY_RELATION = NO\n\n# If the REFERENCES_RELATION tag is set to YES \n# then for each documented function all documented entities \n# called/used by that function will be listed.\n\nREFERENCES_RELATION    = NO\n\n# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) \n# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from \n# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will \n# link to the source code.  Otherwise they will link to the documentation.\n\nREFERENCES_LINK_SOURCE = YES\n\n# If the USE_HTAGS tag is set to YES then the references to source code \n# will point to the HTML generated by the htags(1) tool instead of doxygen \n# built-in source browser. The htags tool is part of GNU's global source \n# tagging system (see http://www.gnu.org/software/global/global.html). You \n# will need version 4.8.6 or higher.\n\nUSE_HTAGS              = NO\n\n# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen \n# will generate a verbatim copy of the header file for each class for \n# which an include is specified. Set to NO to disable this.\n\nVERBATIM_HEADERS       = YES\n\n#---------------------------------------------------------------------------\n# configuration options related to the alphabetical class index\n#---------------------------------------------------------------------------\n\n# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index \n# of all compounds will be generated. Enable this if the project \n# contains a lot of classes, structs, unions or interfaces.\n\nALPHABETICAL_INDEX     = YES\n\n# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then \n# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns \n# in which this list will be split (can be a number in the range [1..20])\n\nCOLS_IN_ALPHA_INDEX    = 5\n\n# In case all classes in a project start with a common prefix, all \n# classes will be put under the same header in the alphabetical index. \n# The IGNORE_PREFIX tag can be used to specify one or more prefixes that \n# should be ignored while generating the index headers.\n\nIGNORE_PREFIX          = \n\n#---------------------------------------------------------------------------\n# configuration options related to the HTML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_HTML tag is set to YES (the default) Doxygen will \n# generate HTML output.\n\nGENERATE_HTML          = YES\n\n# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. \n# If a relative path is entered the value of OUTPUT_DIRECTORY will be \n# put in front of it. If left blank `html' will be used as the default path.\n\nHTML_OUTPUT            = html\n\n# The HTML_FILE_EXTENSION tag can be used to specify the file extension for \n# each generated HTML page (for example: .htm,.php,.asp). If it is left blank \n# doxygen will generate files with .html extension.\n\nHTML_FILE_EXTENSION    = .html\n\n# The HTML_HEADER tag can be used to specify a personal HTML header for \n# each generated HTML page. If it is left blank doxygen will generate a \n# standard header. Note that when using a custom header you are responsible  \n# for the proper inclusion of any scripts and style sheets that doxygen \n# needs, which is dependent on the configuration options used. \n# It is advised to generate a default header using \"doxygen -w html \n# header.html footer.html stylesheet.css YourConfigFile\" and then modify \n# that header. Note that the header is subject to change so you typically \n# have to redo this when upgrading to a newer version of doxygen or when \n# changing the value of configuration settings such as GENERATE_TREEVIEW!\n\nHTML_HEADER            = \n\n# The HTML_FOOTER tag can be used to specify a personal HTML footer for \n# each generated HTML page. If it is left blank doxygen will generate a \n# standard footer.\n\nHTML_FOOTER            = \n\n# The HTML_STYLESHEET tag can be used to specify a user-defined cascading \n# style sheet that is used by each HTML page. It can be used to \n# fine-tune the look of the HTML output. If the tag is left blank doxygen \n# will generate a default style sheet. Note that doxygen will try to copy \n# the style sheet file to the HTML output directory, so don't put your own \n# style sheet in the HTML output directory as well, or it will be erased!\n\nHTML_STYLESHEET        = \n\n# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or \n# other source files which should be copied to the HTML output directory. Note \n# that these files will be copied to the base HTML output directory. Use the \n# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these \n# files. In the HTML_STYLESHEET file, use the file name only. Also note that \n# the files will be copied as-is; there are no commands or markers available.\n\nHTML_EXTRA_FILES       = \n\n# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. \n# Doxygen will adjust the colors in the style sheet and background images \n# according to this color. Hue is specified as an angle on a colorwheel, \n# see http://en.wikipedia.org/wiki/Hue for more information. \n# For instance the value 0 represents red, 60 is yellow, 120 is green, \n# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. \n# The allowed range is 0 to 359.\n\nHTML_COLORSTYLE_HUE    = 220\n\n# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of \n# the colors in the HTML output. For a value of 0 the output will use \n# grayscales only. A value of 255 will produce the most vivid colors.\n\nHTML_COLORSTYLE_SAT    = 100\n\n# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to \n# the luminance component of the colors in the HTML output. Values below \n# 100 gradually make the output lighter, whereas values above 100 make \n# the output darker. The value divided by 100 is the actual gamma applied, \n# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, \n# and 100 does not change the gamma.\n\nHTML_COLORSTYLE_GAMMA  = 80\n\n# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML \n# page will contain the date and time when the page was generated. Setting \n# this to NO can help when comparing the output of multiple runs.\n\nHTML_TIMESTAMP         = YES\n\n# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, \n# files or namespaces will be aligned in HTML using tables. If set to \n# NO a bullet list will be used.\n\nHTML_ALIGN_MEMBERS     = YES\n\n# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML \n# documentation will contain sections that can be hidden and shown after the \n# page has loaded. For this to work a browser that supports \n# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox \n# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).\n\nHTML_DYNAMIC_SECTIONS  = NO\n\n# If the GENERATE_DOCSET tag is set to YES, additional index files \n# will be generated that can be used as input for Apple's Xcode 3 \n# integrated development environment, introduced with OSX 10.5 (Leopard). \n# To create a documentation set, doxygen will generate a Makefile in the \n# HTML output directory. Running make will produce the docset in that \n# directory and running \"make install\" will install the docset in \n# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find \n# it at startup. \n# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html \n# for more information.\n\nGENERATE_DOCSET        = NO\n\n# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the \n# feed. A documentation feed provides an umbrella under which multiple \n# documentation sets from a single provider (such as a company or product suite) \n# can be grouped.\n\nDOCSET_FEEDNAME        = \"Doxygen generated docs\"\n\n# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that \n# should uniquely identify the documentation set bundle. This should be a \n# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen \n# will append .docset to the name.\n\nDOCSET_BUNDLE_ID       = org.doxygen.Project\n\n# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify \n# the documentation publisher. This should be a reverse domain-name style \n# string, e.g. com.mycompany.MyDocSet.documentation.\n\nDOCSET_PUBLISHER_ID    = org.doxygen.Publisher\n\n# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.\n\nDOCSET_PUBLISHER_NAME  = Publisher\n\n# If the GENERATE_HTMLHELP tag is set to YES, additional index files \n# will be generated that can be used as input for tools like the \n# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) \n# of the generated HTML documentation.\n\nGENERATE_HTMLHELP      = NO\n\n# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can \n# be used to specify the file name of the resulting .chm file. You \n# can add a path in front of the file if the result should not be \n# written to the html output directory.\n\nCHM_FILE               = \n\n# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can \n# be used to specify the location (absolute path including file name) of \n# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run \n# the HTML help compiler on the generated index.hhp.\n\nHHC_LOCATION           = \n\n# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag \n# controls if a separate .chi index file is generated (YES) or that \n# it should be included in the master .chm file (NO).\n\nGENERATE_CHI           = NO\n\n# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING \n# is used to encode HtmlHelp index (hhk), content (hhc) and project file \n# content.\n\nCHM_INDEX_ENCODING     = \n\n# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag \n# controls whether a binary table of contents is generated (YES) or a \n# normal table of contents (NO) in the .chm file.\n\nBINARY_TOC             = NO\n\n# The TOC_EXPAND flag can be set to YES to add extra items for group members \n# to the contents of the HTML help documentation and to the tree view.\n\nTOC_EXPAND             = NO\n\n# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and \n# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated \n# that can be used as input for Qt's qhelpgenerator to generate a \n# Qt Compressed Help (.qch) of the generated HTML documentation.\n\nGENERATE_QHP           = NO\n\n# If the QHG_LOCATION tag is specified, the QCH_FILE tag can \n# be used to specify the file name of the resulting .qch file. \n# The path specified is relative to the HTML output folder.\n\nQCH_FILE               = \n\n# The QHP_NAMESPACE tag specifies the namespace to use when generating \n# Qt Help Project output. For more information please see \n# http://doc.trolltech.com/qthelpproject.html#namespace\n\nQHP_NAMESPACE          = org.doxygen.Project\n\n# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating \n# Qt Help Project output. For more information please see \n# http://doc.trolltech.com/qthelpproject.html#virtual-folders\n\nQHP_VIRTUAL_FOLDER     = doc\n\n# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to \n# add. For more information please see \n# http://doc.trolltech.com/qthelpproject.html#custom-filters\n\nQHP_CUST_FILTER_NAME   = \n\n# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the \n# custom filter to add. For more information please see \n# <a href=\"http://doc.trolltech.com/qthelpproject.html#custom-filters\"> \n# Qt Help Project / Custom Filters</a>.\n\nQHP_CUST_FILTER_ATTRS  = \n\n# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this \n# project's \n# filter section matches. \n# <a href=\"http://doc.trolltech.com/qthelpproject.html#filter-attributes\"> \n# Qt Help Project / Filter Attributes</a>.\n\nQHP_SECT_FILTER_ATTRS  = \n\n# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can \n# be used to specify the location of Qt's qhelpgenerator. \n# If non-empty doxygen will try to run qhelpgenerator on the generated \n# .qhp file.\n\nQHG_LOCATION           = \n\n# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files  \n# will be generated, which together with the HTML files, form an Eclipse help \n# plugin. To install this plugin and make it available under the help contents \n# menu in Eclipse, the contents of the directory containing the HTML and XML \n# files needs to be copied into the plugins directory of eclipse. The name of \n# the directory within the plugins directory should be the same as \n# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before \n# the help appears.\n\nGENERATE_ECLIPSEHELP   = NO\n\n# A unique identifier for the eclipse help plugin. When installing the plugin \n# the directory name containing the HTML and XML files should also have \n# this name.\n\nECLIPSE_DOC_ID         = org.doxygen.Project\n\n# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) \n# at top of each HTML page. The value NO (the default) enables the index and \n# the value YES disables it. Since the tabs have the same information as the \n# navigation tree you can set this option to NO if you already set \n# GENERATE_TREEVIEW to YES.\n\nDISABLE_INDEX          = NO\n\n# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index \n# structure should be generated to display hierarchical information. \n# If the tag value is set to YES, a side panel will be generated \n# containing a tree-like index structure (just like the one that \n# is generated for HTML Help). For this to work a browser that supports \n# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). \n# Windows users are probably better off using the HTML help feature. \n# Since the tree basically has the same information as the tab index you \n# could consider to set DISABLE_INDEX to NO when enabling this option.\n\nGENERATE_TREEVIEW      = NO\n\n# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values \n# (range [0,1..20]) that doxygen will group on one line in the generated HTML \n# documentation. Note that a value of 0 will completely suppress the enum \n# values from appearing in the overview section.\n\nENUM_VALUES_PER_LINE   = 4\n\n# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, \n# and Class Hierarchy pages using a tree view instead of an ordered list.\n\nUSE_INLINE_TREES       = NO\n\n# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be \n# used to set the initial width (in pixels) of the frame in which the tree \n# is shown.\n\nTREEVIEW_WIDTH         = 250\n\n# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open \n# links to external symbols imported via tag files in a separate window.\n\nEXT_LINKS_IN_WINDOW    = NO\n\n# Use this tag to change the font size of Latex formulas included \n# as images in the HTML documentation. The default is 10. Note that \n# when you change the font size after a successful doxygen run you need \n# to manually remove any form_*.png images from the HTML output directory \n# to force them to be regenerated.\n\nFORMULA_FONTSIZE       = 10\n\n# Use the FORMULA_TRANPARENT tag to determine whether or not the images \n# generated for formulas are transparent PNGs. Transparent PNGs are \n# not supported properly for IE 6.0, but are supported on all modern browsers. \n# Note that when changing this option you need to delete any form_*.png files \n# in the HTML output before the changes have effect.\n\nFORMULA_TRANSPARENT    = YES\n\n# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax \n# (see http://www.mathjax.org) which uses client side Javascript for the \n# rendering instead of using prerendered bitmaps. Use this if you do not \n# have LaTeX installed or if you want to formulas look prettier in the HTML \n# output. When enabled you also need to install MathJax separately and \n# configure the path to it using the MATHJAX_RELPATH option.\n\nUSE_MATHJAX            = NO\n\n# When MathJax is enabled you need to specify the location relative to the \n# HTML output directory using the MATHJAX_RELPATH option. The destination \n# directory should contain the MathJax.js script. For instance, if the mathjax \n# directory is located at the same level as the HTML output directory, then \n# MATHJAX_RELPATH should be ../mathjax. The default value points to the \n# mathjax.org site, so you can quickly see the result without installing \n# MathJax, but it is strongly recommended to install a local copy of MathJax \n# before deployment.\n\nMATHJAX_RELPATH        = http://www.mathjax.org/mathjax\n\n# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension \n# names that should be enabled during MathJax rendering.\n\nMATHJAX_EXTENSIONS     = \n\n# When the SEARCHENGINE tag is enabled doxygen will generate a search box \n# for the HTML output. The underlying search engine uses javascript \n# and DHTML and should work on any modern browser. Note that when using \n# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets \n# (GENERATE_DOCSET) there is already a search function so this one should \n# typically be disabled. For large projects the javascript based search engine \n# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.\n\nSEARCHENGINE           = YES\n\n# When the SERVER_BASED_SEARCH tag is enabled the search engine will be \n# implemented using a PHP enabled web server instead of at the web client \n# using Javascript. Doxygen will generate the search PHP script and index \n# file to put on the web server. The advantage of the server \n# based approach is that it scales better to large projects and allows \n# full text search. The disadvantages are that it is more difficult to setup \n# and does not have live searching capabilities.\n\nSERVER_BASED_SEARCH    = NO\n\n#---------------------------------------------------------------------------\n# configuration options related to the LaTeX output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will \n# generate Latex output.\n\nGENERATE_LATEX         = NO\n\n# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. \n# If a relative path is entered the value of OUTPUT_DIRECTORY will be \n# put in front of it. If left blank `latex' will be used as the default path.\n\nLATEX_OUTPUT           = latex\n\n# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be \n# invoked. If left blank `latex' will be used as the default command name. \n# Note that when enabling USE_PDFLATEX this option is only used for \n# generating bitmaps for formulas in the HTML output, but not in the \n# Makefile that is written to the output directory.\n\nLATEX_CMD_NAME         = latex\n\n# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to \n# generate index for LaTeX. If left blank `makeindex' will be used as the \n# default command name.\n\nMAKEINDEX_CMD_NAME     = makeindex\n\n# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact \n# LaTeX documents. This may be useful for small projects and may help to \n# save some trees in general.\n\nCOMPACT_LATEX          = NO\n\n# The PAPER_TYPE tag can be used to set the paper type that is used \n# by the printer. Possible values are: a4, letter, legal and \n# executive. If left blank a4wide will be used.\n\nPAPER_TYPE             = a4\n\n# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX \n# packages that should be included in the LaTeX output.\n\nEXTRA_PACKAGES         = \n\n# The LATEX_HEADER tag can be used to specify a personal LaTeX header for \n# the generated latex document. The header should contain everything until \n# the first chapter. If it is left blank doxygen will generate a \n# standard header. Notice: only use this tag if you know what you are doing!\n\nLATEX_HEADER           = \n\n# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for \n# the generated latex document. The footer should contain everything after \n# the last chapter. If it is left blank doxygen will generate a \n# standard footer. Notice: only use this tag if you know what you are doing!\n\nLATEX_FOOTER           = \n\n# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated \n# is prepared for conversion to pdf (using ps2pdf). The pdf file will \n# contain links (just like the HTML output) instead of page references \n# This makes the output suitable for online browsing using a pdf viewer.\n\nPDF_HYPERLINKS         = YES\n\n# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of \n# plain latex in the generated Makefile. Set this option to YES to get a \n# higher quality PDF documentation.\n\nUSE_PDFLATEX           = YES\n\n# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\\\batchmode. \n# command to the generated LaTeX files. This will instruct LaTeX to keep \n# running if errors occur, instead of asking the user for help. \n# This option is also used when generating formulas in HTML.\n\nLATEX_BATCHMODE        = NO\n\n# If LATEX_HIDE_INDICES is set to YES then doxygen will not \n# include the index chapters (such as File Index, Compound Index, etc.) \n# in the output.\n\nLATEX_HIDE_INDICES     = NO\n\n# If LATEX_SOURCE_CODE is set to YES then doxygen will include \n# source code with syntax highlighting in the LaTeX output. \n# Note that which sources are shown also depends on other settings \n# such as SOURCE_BROWSER.\n\nLATEX_SOURCE_CODE      = NO\n\n# The LATEX_BIB_STYLE tag can be used to specify the style to use for the \n# bibliography, e.g. plainnat, or ieeetr. The default style is \"plain\". See \n# http://en.wikipedia.org/wiki/BibTeX for more info.\n\nLATEX_BIB_STYLE        = plain\n\n#---------------------------------------------------------------------------\n# configuration options related to the RTF output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output \n# The RTF output is optimized for Word 97 and may not look very pretty with \n# other RTF readers or editors.\n\nGENERATE_RTF           = NO\n\n# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. \n# If a relative path is entered the value of OUTPUT_DIRECTORY will be \n# put in front of it. If left blank `rtf' will be used as the default path.\n\nRTF_OUTPUT             = rtf\n\n# If the COMPACT_RTF tag is set to YES Doxygen generates more compact \n# RTF documents. This may be useful for small projects and may help to \n# save some trees in general.\n\nCOMPACT_RTF            = NO\n\n# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated \n# will contain hyperlink fields. The RTF file will \n# contain links (just like the HTML output) instead of page references. \n# This makes the output suitable for online browsing using WORD or other \n# programs which support those fields. \n# Note: wordpad (write) and others do not support links.\n\nRTF_HYPERLINKS         = NO\n\n# Load style sheet definitions from file. Syntax is similar to doxygen's \n# config file, i.e. a series of assignments. You only have to provide \n# replacements, missing definitions are set to their default value.\n\nRTF_STYLESHEET_FILE    = \n\n# Set optional variables used in the generation of an rtf document. \n# Syntax is similar to doxygen's config file.\n\nRTF_EXTENSIONS_FILE    = \n\n#---------------------------------------------------------------------------\n# configuration options related to the man page output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_MAN tag is set to YES (the default) Doxygen will \n# generate man pages\n\nGENERATE_MAN           = NO\n\n# The MAN_OUTPUT tag is used to specify where the man pages will be put. \n# If a relative path is entered the value of OUTPUT_DIRECTORY will be \n# put in front of it. If left blank `man' will be used as the default path.\n\nMAN_OUTPUT             = man\n\n# The MAN_EXTENSION tag determines the extension that is added to \n# the generated man pages (default is the subroutine's section .3)\n\nMAN_EXTENSION          = .3\n\n# If the MAN_LINKS tag is set to YES and Doxygen generates man output, \n# then it will generate one additional man file for each entity \n# documented in the real man page(s). These additional files \n# only source the real man page, but without them the man command \n# would be unable to find the correct page. The default is NO.\n\nMAN_LINKS              = NO\n\n#---------------------------------------------------------------------------\n# configuration options related to the XML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_XML tag is set to YES Doxygen will \n# generate an XML file that captures the structure of \n# the code including all documentation.\n\nGENERATE_XML           = NO\n\n# The XML_OUTPUT tag is used to specify where the XML pages will be put. \n# If a relative path is entered the value of OUTPUT_DIRECTORY will be \n# put in front of it. If left blank `xml' will be used as the default path.\n\nXML_OUTPUT             = xml\n\n# The XML_SCHEMA tag can be used to specify an XML schema, \n# which can be used by a validating XML parser to check the \n# syntax of the XML files.\n\nXML_SCHEMA             = \n\n# The XML_DTD tag can be used to specify an XML DTD, \n# which can be used by a validating XML parser to check the \n# syntax of the XML files.\n\nXML_DTD                = \n\n# If the XML_PROGRAMLISTING tag is set to YES Doxygen will \n# dump the program listings (including syntax highlighting \n# and cross-referencing information) to the XML output. Note that \n# enabling this will significantly increase the size of the XML output.\n\nXML_PROGRAMLISTING     = YES\n\n#---------------------------------------------------------------------------\n# configuration options for the AutoGen Definitions output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will \n# generate an AutoGen Definitions (see autogen.sf.net) file \n# that captures the structure of the code including all \n# documentation. Note that this feature is still experimental \n# and incomplete at the moment.\n\nGENERATE_AUTOGEN_DEF   = NO\n\n#---------------------------------------------------------------------------\n# configuration options related to the Perl module output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_PERLMOD tag is set to YES Doxygen will \n# generate a Perl module file that captures the structure of \n# the code including all documentation. Note that this \n# feature is still experimental and incomplete at the \n# moment.\n\nGENERATE_PERLMOD       = NO\n\n# If the PERLMOD_LATEX tag is set to YES Doxygen will generate \n# the necessary Makefile rules, Perl scripts and LaTeX code to be able \n# to generate PDF and DVI output from the Perl module output.\n\nPERLMOD_LATEX          = NO\n\n# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be \n# nicely formatted so it can be parsed by a human reader.  This is useful \n# if you want to understand what is going on.  On the other hand, if this \n# tag is set to NO the size of the Perl module output will be much smaller \n# and Perl will parse it just the same.\n\nPERLMOD_PRETTY         = YES\n\n# The names of the make variables in the generated doxyrules.make file \n# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. \n# This is useful so different doxyrules.make files included by the same \n# Makefile don't overwrite each other's variables.\n\nPERLMOD_MAKEVAR_PREFIX = \n\n#---------------------------------------------------------------------------\n# Configuration options related to the preprocessor\n#---------------------------------------------------------------------------\n\n# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will \n# evaluate all C-preprocessor directives found in the sources and include \n# files.\n\nENABLE_PREPROCESSING   = YES\n\n# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro \n# names in the source code. If set to NO (the default) only conditional \n# compilation will be performed. Macro expansion can be done in a controlled \n# way by setting EXPAND_ONLY_PREDEF to YES.\n\nMACRO_EXPANSION        = NO\n\n# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES \n# then the macro expansion is limited to the macros specified with the \n# PREDEFINED and EXPAND_AS_DEFINED tags.\n\nEXPAND_ONLY_PREDEF     = NO\n\n# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files \n# pointed to by INCLUDE_PATH will be searched when a #include is found.\n\nSEARCH_INCLUDES        = YES\n\n# The INCLUDE_PATH tag can be used to specify one or more directories that \n# contain include files that are not input files but should be processed by \n# the preprocessor.\n\nINCLUDE_PATH           = \n\n# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard \n# patterns (like *.h and *.hpp) to filter out the header-files in the \n# directories. If left blank, the patterns specified with FILE_PATTERNS will \n# be used.\n\nINCLUDE_FILE_PATTERNS  = \n\n# The PREDEFINED tag can be used to specify one or more macro names that \n# are defined before the preprocessor is started (similar to the -D option of \n# gcc). The argument of the tag is a list of macros of the form: name \n# or name=definition (no spaces). If the definition and the = are \n# omitted =1 is assumed. To prevent a macro definition from being \n# undefined via #undef or recursively expanded use the := operator \n# instead of the = operator.\n\nPREDEFINED             = \n\n# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then \n# this tag can be used to specify a list of macro names that should be expanded. \n# The macro definition that is found in the sources will be used. \n# Use the PREDEFINED tag if you want to use a different macro definition that \n# overrules the definition found in the source code.\n\nEXPAND_AS_DEFINED      = \n\n# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then \n# doxygen's preprocessor will remove all references to function-like macros \n# that are alone on a line, have an all uppercase name, and do not end with a \n# semicolon, because these will confuse the parser if not removed.\n\nSKIP_FUNCTION_MACROS   = YES\n\n#---------------------------------------------------------------------------\n# Configuration::additions related to external references\n#---------------------------------------------------------------------------\n\n# The TAGFILES option can be used to specify one or more tagfiles. \n# Optionally an initial location of the external documentation \n# can be added for each tagfile. The format of a tag file without \n# this location is as follows: \n#   TAGFILES = file1 file2 ... \n# Adding location for the tag files is done as follows: \n#   TAGFILES = file1=loc1 \"file2 = loc2\" ... \n# where \"loc1\" and \"loc2\" can be relative or absolute paths or \n# URLs. If a location is present for each tag, the installdox tool \n# does not have to be run to correct the links. \n# Note that each tag file must have a unique name \n# (where the name does NOT include the path) \n# If a tag file is not located in the directory in which doxygen \n# is run, you must also specify the path to the tagfile here.\n\nTAGFILES               = \n\n# When a file name is specified after GENERATE_TAGFILE, doxygen will create \n# a tag file that is based on the input files it reads.\n\nGENERATE_TAGFILE       = \n\n# If the ALLEXTERNALS tag is set to YES all external classes will be listed \n# in the class index. If set to NO only the inherited external classes \n# will be listed.\n\nALLEXTERNALS           = NO\n\n# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed \n# in the modules index. If set to NO, only the current project's groups will \n# be listed.\n\nEXTERNAL_GROUPS        = YES\n\n# The PERL_PATH should be the absolute path and name of the perl script \n# interpreter (i.e. the result of `which perl').\n\nPERL_PATH              = /usr/bin/perl\n\n#---------------------------------------------------------------------------\n# Configuration options related to the dot tool\n#---------------------------------------------------------------------------\n\n# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will \n# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base \n# or super classes. Setting the tag to NO turns the diagrams off. Note that \n# this option also works with HAVE_DOT disabled, but it is recommended to \n# install and use dot, since it yields more powerful graphs.\n\nCLASS_DIAGRAMS         = YES\n\n# You can define message sequence charts within doxygen comments using the \\msc \n# command. Doxygen will then run the mscgen tool (see \n# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the \n# documentation. The MSCGEN_PATH tag allows you to specify the directory where \n# the mscgen tool resides. If left empty the tool is assumed to be found in the \n# default search path.\n\nMSCGEN_PATH            = \n\n# If set to YES, the inheritance and collaboration graphs will hide \n# inheritance and usage relations if the target is undocumented \n# or is not a class.\n\nHIDE_UNDOC_RELATIONS   = YES\n\n# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is \n# available from the path. This tool is part of Graphviz, a graph visualization \n# toolkit from AT&T and Lucent Bell Labs. The other options in this section \n# have no effect if this option is set to NO (the default)\n\nHAVE_DOT               = YES\n\n# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is \n# allowed to run in parallel. When set to 0 (the default) doxygen will \n# base this on the number of processors available in the system. You can set it \n# explicitly to a value larger than 0 to get control over the balance \n# between CPU load and processing speed.\n\nDOT_NUM_THREADS        = 0\n\n# By default doxygen will use the Helvetica font for all dot files that \n# doxygen generates. When you want a differently looking font you can specify \n# the font name using DOT_FONTNAME. You need to make sure dot is able to find \n# the font, which can be done by putting it in a standard location or by setting \n# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the \n# directory containing the font.\n\nDOT_FONTNAME           = Helvetica\n\n# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. \n# The default size is 10pt.\n\nDOT_FONTSIZE           = 10\n\n# By default doxygen will tell dot to use the Helvetica font. \n# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to \n# set the path where dot can find it.\n\nDOT_FONTPATH           = \n\n# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen \n# will generate a graph for each documented class showing the direct and \n# indirect inheritance relations. Setting this tag to YES will force the \n# CLASS_DIAGRAMS tag to NO.\n\nCLASS_GRAPH            = YES\n\n# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen \n# will generate a graph for each documented class showing the direct and \n# indirect implementation dependencies (inheritance, containment, and \n# class references variables) of the class with other documented classes.\n\nCOLLABORATION_GRAPH    = YES\n\n# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen \n# will generate a graph for groups, showing the direct groups dependencies\n\nGROUP_GRAPHS           = YES\n\n# If the UML_LOOK tag is set to YES doxygen will generate inheritance and \n# collaboration diagrams in a style similar to the OMG's Unified Modeling \n# Language.\n\nUML_LOOK               = NO\n\n# If set to YES, the inheritance and collaboration graphs will show the \n# relations between templates and their instances.\n\nTEMPLATE_RELATIONS     = NO\n\n# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT \n# tags are set to YES then doxygen will generate a graph for each documented \n# file showing the direct and indirect include dependencies of the file with \n# other documented files.\n\nINCLUDE_GRAPH          = YES\n\n# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and \n# HAVE_DOT tags are set to YES then doxygen will generate a graph for each \n# documented header file showing the documented files that directly or \n# indirectly include this file.\n\nINCLUDED_BY_GRAPH      = YES\n\n# If the CALL_GRAPH and HAVE_DOT options are set to YES then \n# doxygen will generate a call dependency graph for every global function \n# or class method. Note that enabling this option will significantly increase \n# the time of a run. So in most cases it will be better to enable call graphs \n# for selected functions only using the \\callgraph command.\n\nCALL_GRAPH             = YES\n\n# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then \n# doxygen will generate a caller dependency graph for every global function \n# or class method. Note that enabling this option will significantly increase \n# the time of a run. So in most cases it will be better to enable caller \n# graphs for selected functions only using the \\callergraph command.\n\nCALLER_GRAPH           = YES\n\n# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen \n# will generate a graphical hierarchy of all classes instead of a textual one.\n\nGRAPHICAL_HIERARCHY    = YES\n\n# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES \n# then doxygen will show the dependencies a directory has on other directories \n# in a graphical way. The dependency relations are determined by the #include \n# relations between the files in the directories.\n\nDIRECTORY_GRAPH        = YES\n\n# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images \n# generated by dot. Possible values are svg, png, jpg, or gif. \n# If left blank png will be used. If you choose svg you need to set \n# HTML_FILE_EXTENSION to xhtml in order to make the SVG files \n# visible in IE 9+ (other browsers do not have this requirement).\n\nDOT_IMAGE_FORMAT       = png\n\n# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to \n# enable generation of interactive SVG images that allow zooming and panning. \n# Note that this requires a modern browser other than Internet Explorer. \n# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you \n# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files \n# visible. Older versions of IE do not have SVG support.\n\nINTERACTIVE_SVG        = NO\n\n# The tag DOT_PATH can be used to specify the path where the dot tool can be \n# found. If left blank, it is assumed the dot tool can be found in the path.\n\nDOT_PATH               = \n\n# The DOTFILE_DIRS tag can be used to specify one or more directories that \n# contain dot files that are included in the documentation (see the \n# \\dotfile command).\n\nDOTFILE_DIRS           = \n\n# The MSCFILE_DIRS tag can be used to specify one or more directories that \n# contain msc files that are included in the documentation (see the \n# \\mscfile command).\n\nMSCFILE_DIRS           = \n\n# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of \n# nodes that will be shown in the graph. If the number of nodes in a graph \n# becomes larger than this value, doxygen will truncate the graph, which is \n# visualized by representing a node as a red box. Note that doxygen if the \n# number of direct children of the root node in a graph is already larger than \n# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note \n# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.\n\nDOT_GRAPH_MAX_NODES    = 50\n\n# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the \n# graphs generated by dot. A depth value of 3 means that only nodes reachable \n# from the root by following a path via at most 3 edges will be shown. Nodes \n# that lay further from the root node will be omitted. Note that setting this \n# option to 1 or 2 may greatly reduce the computation time needed for large \n# code bases. Also note that the size of a graph can be further restricted by \n# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.\n\nMAX_DOT_GRAPH_DEPTH    = 0\n\n# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent \n# background. This is disabled by default, because dot on Windows does not \n# seem to support this out of the box. Warning: Depending on the platform used, \n# enabling this option may lead to badly anti-aliased labels on the edges of \n# a graph (i.e. they become hard to read).\n\nDOT_TRANSPARENT        = NO\n\n# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output \n# files in one run (i.e. multiple -o and -T options on the command line). This \n# makes dot run faster, but since only newer versions of dot (>1.8.10) \n# support this, this feature is disabled by default.\n\nDOT_MULTI_TARGETS      = NO\n\n# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will \n# generate a legend page explaining the meaning of the various boxes and \n# arrows in the dot generated graphs.\n\nGENERATE_LEGEND        = YES\n\n# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will \n# remove the intermediate dot files that are used to generate \n# the various graphs.\n\nDOT_CLEANUP            = YES\n"
  },
  {
    "path": "doc/citrine/backup_clone.rst",
    "content": "Clones for Backups\n==================\n\nSummary\n-------\n\nThe EOS backup system backs up EOS data from short-lived, copy-on-write backup clones\ninto \"blobs\" onto a local file system or another EOS instance such as CTA.\n\nA backup clone is created at the very beginning of the process, typically within only seconds. From there\ncopy-on-write techniques ensure that data remain unchanged as of the clone creation time while they are\ncopied to backup media, typically much slower. The system operates on\na directory hierarchy and supports full and differential/incremental backups. Backups can be nested, i.e. a higher level\nbackup will skip directories handled by lower-level backups.\n\nA file is included in an incremental backup based on\nfile modification time tracked independently of mtime: the set of files\nmodified after a certain timestamp are identified in a JSON-formatted backup catalogue and marked for copy-on-write\nin case of modifications. At the end of the backup, the backup clone is automatically deleted.\n\nTwo Python\nprograms, eos-backup and eos-backup-browser, are the high-level entry into the system.\n\n- eos-backup scans the catalogue and stores data in blobs. It also performs the inverse operation, reconstructing files from data in backup blobs into an EOS hierarchy, based on a series of full/incremental backup catalogues.\n\n- eos-backup-browser \"mounts\" a volatile file system constructed from a sequence of full/incremental backup catalogues, which can then be used to quickly view a file (reconstruct it from backup blobs). This can be useful e.g. when searching for clues as to from when a directory should be restored. It is also used to determine which blob files should be present (e.g. recalled from tape) in order to restore a particular sub-hierarchy.\n\nEach backup results in a backup catalogue to be created describing the directory hierarchy. This catalogue is vital as the backup blobs are useless without it, it should be stored on backup media alongside the blobs. In addition, a consistent history of at least one full and related incremental backups should be kept: in order to reconstruct a directory hierarchy in a consistent manner, potentially all of them are needed upon restore.\n\nClone internal logic\n--------------------\n\n1. syncTime\n\na \"server-store-timestamp\" is maintained for all directories and files. This is\nconceptually different from mtime (which can be set by the user), however: for\ncontainers the TMTime field is used; for files 'syncTime' has been added - but\nit is only different from the default (hence mostly not stored) if mtime is set\nexplicitly, which should be rare. Thus, in most practical cases syncTime will be\nmtime, minimising the increase in quarkdb footprint. While a clone exists,\ntransient \"cloneId\" and possibly \"cloneFST\" attributes are maintained in the\nmgm.\n\n2. the clone\n\nthe clone is a \"sparse\" or \"lazy\" clone, starting empty. Upon cloning, all\nfiles are tagged with the cloneId field. When files are modified, their original\ncontents are \"reflinked\" on the FSTs (see \"cp --reflink\", which implements a\ncopy-on-write on the original FST) into a new file, which is placed in a\ncontainer modelled after their parent containers under\n/eos/<instance>/proc/clone/cloneId. For each file the clone process returns the\nforeseen clone path, for use in backup scripts.\n\n3. cloning in hierarchies\n\nCloning starts at directory level. All files at directory level are tagged with\nthe cloneId. The cloning process then recursively descends into subdirectories.\nTo prevent this, a directory can be flagged with a different cloneId (i.e. cloning\na parent directory will not re-clone a child directory previously cloned), or a\nspecial \"dummy\" cloneId used as a marker on a directory preventing descent.\n\n4. clone process details\n\nA cloneId is a [unique] second-resolution timestamp generated automatically at\nthe beginning of the cloning process. The clone process is triggered by a \"find\"\ncommand with the '-x sys.clone=+...' option (requires\nprivileges):\n\n \"-x sys.clone=? <dir>\"\n     displays all files and clone attributes under <dir>,\n \"-x sys.clone=-1556195651 <dir>\"\n     deletes all traces of the clone 1556195651,\n \"-x sys.clone=+10 <dir>\"\n     makes a full backup clone (numbers < 10 are markers, from 10 onwards second-precision timestamps),\n \"-x sys.clone=+1 <dir>\"\n     sets a marker not to descend into <dir>  while cloning,\n \"-x sys.clone=+1556195651 <dir>\"\n     makes a clone of all files altered after 1556195651.0\n\nCloning tags every file and directory in the hierarchy with the (generated)\ncloneId.\n\n5. copy-on-write\n\nWhenever a file is deleted, or entirely rewritten using OTRUNCATE, its\ndata are first transferred (using a low-cost \"rename\") to a clone file under a\npredefined name in the clone hierarchy.\nWhen a file is\nupdated (modified) without OTRUNCATE, a copy-on-write clone is created (using\nreflink, which is low-cost in recent file systems).\n\n6. the backup process\n\n\"find -j -x sys.clone=+nnnnnnnn /path\" clones a directory tree and creates a\nJSON output for reference, which must be kept safe indicating which files should\nbe backed up and where their copy-on-write clones. As soon as the clone has been\ncreated, write operations to files may result in copy-on-write clones being created,\nhence during backup a decision has to be made whether to back up a possible clone\nfile or, for a full backup, the live file. For a live file, the decision may even\nhave to be revised after the copy if a copy-on-write clone has been created meanwhile.\n\n7. cleanup\n\nAfter data have been backed up, 'find -x sys.clone=-nnnnnnnn' deletes all the cloneId and\nclone-tag attributes and cleans the clone directory under /proc/clone/cloneId.\nThe temporary clones disappear from the FSTs by-and-by. The backup now\nresides on backup media only.\n\nA typical backup-restore cycle\n------------------------------\n\nHere's a complete backup and restore recipe, including incremental (as\nimplemented by test/eos-backup-test):\n\na. backup triggers a full backup issuing 'find -j -x sys.clone=+10 <path>' and\nstores the returned JSON output for reference in a safe place. For each output line,\nthe JSON dictionary's key 'n' contains the original path name, 't' the server-store-time,\n'c' the cloneId (if this is different from the backup's cloneId the file belongs to another\nbackup!), 'p' the clonePath suffix. The eos-backup-test program illustrates how\nthose can be used for managing backups and restores.\nThe backupId is the cloneId returned for the top-level\ndirectory (the first line). For each file, backup copies the version under the\nclone path to backup media if it exists, or the live file otherwise. If the live\nfile has been copied, a check is performed again after the copy to assess no\nclone file has been created meanwhile, or the clone file replaces the live file\non backup media. <path> is backed up recursively, descent into\nsubdirectories can be stopped with a directory marker (see above). A filename\nending in a '/' signals a directory. The clonePath is relative to\n/eos/<instance>/proc/clone and structured backupId/cloneDir/clonePath for files,\nor simply backupId for directories in the report;\n\nb. backup then cleans the backup clone: find -f -x sys.clone=-nnnnnnnn <path>.\nThe directory returns to its normal EOS state (without copy-on-write overhead);\n\nc. incremental backups may be triggered using\n'find -j -x sys.clone=+<previousBackupId> <path>'. The previousBackupId would either\nbe the backupId of a previous backup or the backupId of the full backup, depending on\nthe type of increment desired. Again, the backupId of the incremental backup is\nthe cloneId of the top-level (first) directory. An incremental backup reports\n*all* current files below <path>. Those not modified since the previous backup\ncan be distinguished in the output by a backupId (often '0') that differs from\nthe top-level directory - no need to back them up again;\n\nd. restore collects all stored backup reports (full, incremental 1,\nincremental2, ... up-to-desired restore date) for <path> into one list sorted by\nfilename and backupId. The last (!) incremental report contains the *final* list\nof files to be restored.  For every file in the sorted list, only the most\nrecent (highest backupId) is restored from backup media (and only if it appears\nin the final list. At this point a filter could be applied to the list to only\nrestore selected files.\n\n\n\n\nTiming\n------\n\n- metadata operations under (ideally) the biggest lock possible for consistency\n- the standard write may be delayed for copy-on-write - slow with ancient kernels\n- copy-on-write clones are short lived, and only needed for partially modified files\n- the backup will be \"largely\" consistent, even without a BIG lock\n\nTiming in current implementation (in seconds), 100 files in 100 directories\n(=10000 files), 30 bytes each (`date`), dockertest-instance, bash script, using\nfusex:\n\ncreation:           105         /eos/rtb/tobbicke/backuptest/t??/tt??\n\nappend `date`:      115\n\nclone creation:     1\n\nappend `date`:      443\n\nappend `date`:      147\n\n\"wc -c\" on clones:  45          /eos/dockertest/proc/clone/1558357820/Dxxx/Fxxx\n\n\"wc -c\" on live files:  49\n\nremove all clones:  2\n\nThe cloning time alone is not visible here, since it includes formatting and\ndisplaying the result. Tests would be needed on bigger instances to evaluate the\nimpact of mgm locking.\n\nThe \"append date\" processes naturally increase in duration - there are more data\nto ship to the FSTs. The first run after the clone actually includes creating\nthe clones - hence it is more \"expensive\".\n\n\n\nBackup / Restore Utilities\n==========================\n\nThese utilities wrap the low-end find commands into higher level operational tools:\n\neos-backup can be used to clone, back up and restore EOS directories. Data are stored in\n\"blobs\" in a filesystem, or in EOS or CTA.\n\neos-backup-browser can \"mount\" an existing backup hierarchy (as defined by a series of full and incremental/differential backup catalogues) using fuse for easy consultation or recovery.\n\neos-backup\n----------\n\n eos-backup clone -B *BackupPrefix* [-P <parentId>] /eos/pathName\n   clones a directory tree, fully or based on parentId, and creates a cloneFile; prints \"cloneId <cloneId> catalog <cloneFile>\" on stdout\n\n eos-backup backup -B *BackupPrefix* [-F catalogFile] [-P <parentId>] /eos/pathName\n   clones a directory tree unless a cloneFile is passed with -F, and then backs up files into *BackupPrefix*.cloneId and deletes the clone; prints \"cloneId <cloneId> backup media <backupDir>\" followed by whatever the deletion of the clone says.\n\n eos-backup restore -F /inputCatalog1[,/inputCatalog2[,...]] /outputDirectory\n   performs a restore into outputDirectory based on 1 or more catalogFiles\n\n*BackupPrefix* would typically be a root://instance//path into a CTA or EOS store, or be a path on a mounted file system\n\n/eos pathnames are as seen by the MGM, not fuse-mounted in the local file system!\n\n environment (can also be specified through -U root://eos-instance):\n   EOS_MGM_URL=root://eos-instance *must* point to the MGM serving /eos/pathName\n\n*Example*: a simple full_backup/make_changes/incremental_backup/restore example in bash::\n\n # full clone and backup in one go, for simplicity into the local file system\n read xx cloneId1 xxx media1 <<<$(eos-backup backup -B /tmp/Backup /eos/dockertest/backuptest)\n\n # make some changes, delete/add/modify files\n date > /eos/dockertest/backuptest/now1\n\n # an incremental backup based on full backup\n read xx cloneId2 xxx media2 <<<$(eos-backup backup -B /tmp/Backup -P $cloneId1 /eos/dockertest/backuptest)\n\n # make more changes, delete/add/modify files\n date >> /eos/dockertest/backuptest/now1\n date > /eos/dockertest/backuptest/now2\n\n # another incremental backup based on previous backup, illustrating clone/backup in two steps\n read xx cloneId3 xxx cloneFile3 <<<$(eos-backup clone -B /tmp/Backup -P $cloneId2 /eos/dockertest/backuptest)\n\n # back those files up\n read xx cloneId3 xxx media3 <<<$(eos-backup backup -B /tmp/Backup -F $cloneFile3 /eos/dockertest/backuptest)\n\n # restore the lot\n eos-backup restore -F $media1/catalog,$media2/catalog,$media3/catalog -B /tmp/Backup /tmp/Restore\n\n\neos-browser\n------------------\n\n eos-backup-browser -F $media1/catalog[,$media2/catalog[,...]] [-L regexp1[,regexp2[,...]] /mountPoint\n\n   mounts the eos-backup tree designated by the catalog files under /mountPoint, read-only. Files can\n   be copied somewhere else from there, provided the backup media files are accessible. Can be used to easily recover\n   a small set of files, without the need to restore a whole tree!\n\n   if '-L' is specified, it designates a list of regular expressions. All media files containing data\n   for files matching any of the regular expressions are listed. This can be used to establish which files\n   would have to be restored from e.g. tape.\n   files can be copied\n"
  },
  {
    "path": "doc/citrine/backup_clone.txt",
    "content": "Clones for Backups\n==================\n\nShort summary:\n\nfile modification times are tracked independent of mtime, a hierarchy of files\nmodified after a certain date can be isolated in a snap-shot with copy-on-write\nsemantics, an easily parsable or json report can feed a back-up script. Two python\nscripts (eos-backup, eos-backup-browser) illustrate how to use the functionality\nand can serve as backup/restore utilities.\n\n\n1. syncTime\n\na \"server-store-timestamp\" is maintained for all directories and files. This is\nconceptually different from mtime (which can be set by the user), however: for\ncontainers the TMTime field is used; for files 'syncTime' has been added - but\nit is only different from the default (hence mostly not stored) if mtime is set\nexplicitly, which should be rare. Thus, in most practical cases syncTime will be\nmtime, minimising the increase in quarkdb footprint. While a clone exists,\ntransient \"cloneId\" and possibly \"cloneFST\" attributes are maintained in the\nmgm.\n\n2. the clone\n\nthe clone is a \"sparse\" or \"lazy\" clone, starting empty. Upon cloning, all\nfiles are tagged with the cloneId field. When files are modified, their original\ncontents are \"reflinked\" on the FSTs (see \"cp --reflink\", which implements a\ncopy-on-write on the original FST) into a new file, which is placed in a\ncontainer modelled after their parent containers under\n/eos/<instance>/proc/clone/cloneId. For each file the clone process returns the\nforeseen clone path, for use in backup scripts. \n\n3. cloning in hierarchies\n\nCloning starts at directory level. All files at directory level are tagged with\nthe cloneId. The cloning process then recursively descends into subdirectories.\nTo prevent this, a directory can be flagged with a different cloneId (i.e. cloning\na parent directory will not re-clone a child directory previously cloned), or a\nspecial \"dummy\" cloneId used as a marker on a directory preventing descent.  \n\n4. clone process details \n\nA cloneId is a [unique] second-resolution timestamp generated automatically at\nthe beginning of the cloning process. The clone process is triggered by a find\ncommand with the '-x sys.clone=+...' option (specifying sys.clone requires\nprivileges):\n\n  \"-x sys.clone=? ...\"\n        - displays all files and clone attributes under ...,\n  \"-x sys.clone=-1556195651 ...\"\n        - deletes all traces of the clone 1556195651,\n  \"-x sys.clone=+10 ...\"\n        - makes a full backup clone (numbers < 10 are markers,\n            from 10 onwards second-precision timestamps),\n  \"-x sys.clone=+1 ...\"\n        - sets a marker not to descend into ...  while cloning,\n  \"-x sys.clone=+1556195651 ...\"\n        - makes a clone of all files altered after 1556195651.0\n\nCloning tags every file and directory in the hierarchy with the (generated)\ncloneId.\n\n5. copy-on-write\n\nWhenever a file is being deleted, or being rewritten using OTRUNCATE, its\nunderlying data are transferred (using a low-cost \"mv\") to a clone file under a\npredefined name in the clone hierarchy. When rewritten with OTRUNCATE, a new\nfile will then automatically be created, tagged as \"cloned\". When a file is\nupdated (modified) without OTRUNCATE, a copy-on-write clone is created, using\nreflink (which is low-cost in recent file systems, but elsewhere a real copy). \n\n6. the backup process\n\n\"find -j -x sys.clone=+nnnnnnnn /path\" clones a directory tree and creates a\njson output for reference, which must be kept safe indicating which files should\nbe backed up and where their copy-on-write clones. As soon as the clone has been\ncreated, write operations to files may result in copy-on-write clones be created,\nhence during back-up a decision has to be made whether to back up a possible clone\nfile or, for a full backup, the live file. For a live file, the decision may even\nhave to be revised after the copy if a copy-on-write clone has been created meanwhile.\n\n7. cleanup\n\nAfter data have been backed up, 'find -x sys.clone=-nnnnnnnn' deletes all the cloneId and\nclone-tag attributes and cleans the clone directory under /proc/clone/cloneId.\nThe temporary clones disappear from the FSTs by-and-by. The back-up now \nresides on backup media only. \n\nA typical backup-restore cycle\n------------------------------\n\nHere's a complete backup and restore recipe, including incremental (as\nimplemented by test/eos-backup-test):\n\na. backup triggers a full backup issuing 'find -j -x sys.clone=+10 <path>' and\nstores the returned json output for reference in a safe place. For each output line, \nthe json dictionary's key 'n' contains the original path name, 't' the server-store-time,\n'c' the cloneId (if this is different from the backup's cloneId the file belongs to another\nbackup!), 'p' the clonePath suffix. The eos-backup-test program illustrates how\nthose can be used for managing backups and restores.\nThe backupId is the cloneId returned for the top-level\ndirectory (the first line). For each file, backup copies the version under the\nclone path to backup media if it exist, or the live file otherwise. If the live\nfile has been copied, a check is performed again after the copy to assess no\nclone file has been created meanwhile, or the clone file replaces the live file\non backup media. <path> is backed up recursively, descent into\nsubdirectories can be stopped with a directory marker (see above). A filename\nending in a '/' signals a directory. The clonePath is relative to\n/eos/<instance>/proc/clone and structured backupId/cloneDir/clonePath for files,\nor simply backupId for directories in the report;\n\nb. backup then cleans the backup clone: find -f -x sys.clone=-nnnnnnnn <path>.\nThe directory returns to its normal EOS state (without copy-on-write overhead);\n\nc. incremental backups may be triggered using\n'find -j -x sys.clone=+<previousBackupId> <path>'. The previousBackupId would either\nbe the backupId of a previous backup or the backupId of the full backup, depending on\nthe type of increment desired. Again, the backupId of the incremental backup is\nthe cloneId of the top-level (first) directory. An incremental backup reports\n*all* current files below <path>. Those not modified since the previous backup\ncan be distinguished in the output by a backupId (often '0') that differs from\nthe top-level directory - no need to back them up again;\n\nd. restore collects all stored backup reports (full, incremental 1,\nincremental2, ... up-to-desired-restore date) for <path> into one list sorted by\nfilename and backupId. The last (!) incremental report contains the *final* list\nof files to be restored.  For every file in the sorted list, only the most\nrecent (highest backupId) is restored from backup media (and only if it appears\nin the final list. At this point a filter could be applied to the list to only\nrestore selected files.\n\n\n\n\nTiming\n------\n\n- metadata operations under (ideally) the biggest lock possible for consistency\n- the standard write may be delayed for copy-on-write - slow with ancient kernels\n- copy-on-write clones are short lived, and only needed for partially modified files\n- the backup will be \"largely\" consistent, even without a BIG lock\n\nTiming in current implementation (in seconds), 100 files in 100 directories\n(=10000 files), 30 bytes each (`date`), dockertest-instance, bash script, using\nfusex:\n\ncreation:           105         /eos/rtb/tobbicke/backuptest/t??/tt??\nappend `date`:      115\nclone creation:     1\nappend `date`:      443\nappend `date`:      147\n\"wc -c\" on clones:  45          /eos/dockertest/proc/clone/1558357820/Dxxx/Fxxx         \n\"wc -c\" on live files:  49\nremove all clones:  2\n\nThe cloning time alone is not visible here, since it includes formatting and\ndisplaying the result. Tests would be needed on bigger instances to evaluate the\nimpact of mgm locking.\n\nThe \"append date\" processes naturally increase in duration - there are more data\nto ship to the FSTs. The first run after the clone actually includes creating\nthe clones - hence it is more \"expensive\".\n\n\n\nBackup / Restore Utilities\n==========================\n\nShort summary:\n\neos-backup can be used to clone, back up and restore EOS directories. Data are stored in\n\"blobs\" in a filesystem, or in EOS or CTA.\n\neos-backup-browser can \"mount\" an existing backup hierarchy (i.e., including incremental\nor differential backups) using fuse for easy consultation or recovery.\n\neos-backup\n----------\n\n eos-backup clone -B /BackupPrefix [-P <parentId>] /eos/pathName\n   - clones a directory tree, fully or based on parentId, and creates a cloneFile\n     Outputs \"cloneId <cloneId> catalog <cloneFile>\"\n\n eos-backup backup -B /BackupPrefix [-F catalogFile] [-P <parentId>] /eos/pathName\n   - clones a directory tree unless a cloneFile is passed with -F, and then backs up files\n     into /BackupPrefix.cloneId and deletes the clone.\n     Outputs \"cloneId <cloneId> backup media <backupDir>\" followed by whatever\n     the deletion of the clone says.\n\n eos-backup restore -F /inputCatalog1[,/inputCatalog2[,...]] /outputDirectory\n   - performs a restore into outputDirectory based on 1 or more catalogFiles\n\n /BackupPrefix can be a path on a mounted file system, or root://instance//path to select\n a EOS/CTA store.\n\n /eos pathnames are as seen by the MGM, not in the local file system!\n\n environment (can also be specified through -U root://eos-instance): \n   EOS_MGM_URL=root://eos-instance must point to the MGM serving /eos/pathName\n\nExample\n-------\n A primitive full_backup/make_changes/incremental_backup/restore example in bash:\n\n   # full clone and backup in one go\n   read xx cloneId1 xxx media1 <<<$(eos-backup backup -B /tmp/Backup/ /eos/dockertest/backuptest)\n\n   : make some changes, delete/add/modify files\n\n   # an incremental clone based on first backup\n   read xx cloneId2 xxx cloneFile2 <<<$(eos-backup clone -B /tmp/Backup/ -P $cloneId1 /eos/dockertest/backuptest)\n\n   # back those files up\n   read xx cloneId2 xxx media2 <<<$(eos-backup backup -B /tmp/Backup/ -F $cloneFile2 /eos/dockertest/backuptest)\n\n   # restore the lot\n   eos-backup restore -F ${media1}catalog,/tmp/Backup.$cloneId2/catalog -B /tmp/Backup /tmp/Restore2\n\n   \neos-backup-browser\n------------------\n\n  eos-backup-browser -F $media1/catalog[,$media2/catalog[,...]] [-L regexp1[,regexp2[,...]] /mountPoint\n\n    - mounts the eos-backup tree designated by the catalog Files under /mountPoint, read-only. Files can\n      be copied somewhere else from there, provided the backup media-files are accessible.\n\n    - if '-L' is specified, it designates a list of regular expressions. All media-files containing data\n      for files matching any of the regular expressions are listed. This can be used to establish which files\n      would have to be restored from e.g. tape.\n      files can be copied \n\n"
  },
  {
    "path": "doc/citrine/clicommands/accounting.rst",
    "content": "accounting\n----------\n\n.. code-block:: text\n\n  usage: accounting report [-f]                          : prints accounting report in JSON, data is served from cache if possible\n    -f : forces a synchronous report instead of using the cache (only use this if the cached data is too old)\n    accounting config -e [<expired>] -i [<invalid>] : configure caching behaviour\n    -e : expiry time in minutes, after this time frame asynchronous update happens, default is 10 minutes\n    -i : invalidity time in minutes, after this time frame synchronous update happens, must be greater than expiry time, default is never\n"
  },
  {
    "path": "doc/citrine/clicommands/acl.rst",
    "content": "acl\n---\n\n.. code-block:: text\n\n  eos acl [-l|--list] [-R|--recursive] [-p | --position <pos>] [-f | --front] [--sys|--user] [<rule>] <path>\n    atomically set and modify ACLs for the given directory path\n.. code-block:: text\n\n    -h, --help      : print help message\n    -R, --recursive : apply to directories recursively\n    -l, --list      : list ACL rules\n    -p, --position  : add the acl rule at specified position\n    -f, --front     : add the acl rule at the front position\n        --user      : handle/list user.acl rules on directory\n        --sys       : handle/list sys.acl rules on directory\n    <rule> is created similarly to chmod rules. Every rule begins with\n    [u|g|egroup] followed by \":\" or \"=\" and an identifier.\n    \":\" is used to for modifying permissions while\n    \"=\" is used for setting/overwriting permissions.\n    When modifying permissions every ACL flag can be added with\n    \"+\" or removed with \"-\n    By default rules are appended at the end of acls\n    This ordering can be changed via --position flag\n    which will add the new rule at a given position starting at 1 or\n    the --front flag which adds the rule at the front instead\n\n  Examples:\n    acl --user u:1001=rwx /eos/dev/\n    Set ACLs for user id 1001 to rwx\n    acl --user u:1001:-w /eos/dev\n    Remove 'w' flag for user id 1001\n    acl --user u:1001:+m /eos/dev\n    Add change mode permission flag for user id 1001\n    acl --user u:1010= /eos/dev\n    Remove all ACls for user id 1001\n"
  },
  {
    "path": "doc/citrine/clicommands/archive.rst",
    "content": "archive\n-------\n\n.. code-block:: text\n\n  usage: archive <subcmd> \n    create <path>                          : create archive file\n    put [--retry] <path>                   : copy files from EOS to archive location\n    get [--retry] <path>                   : recall archive back to EOS\n    purge[--retry] <path>                  : purge files on disk\n    transfers [all|put|get|purge|job_uuid] : show status of running jobs\n    list [<path>]                          : show status of archived directories in subtree\n    kill <job_uuid>                        : kill transfer\n    delete <path>                          : delete files from tape, keeping the ones on disk\n    help [--help|-h]                       : display help message\n"
  },
  {
    "path": "doc/citrine/clicommands/attr.rst",
    "content": "attr\n----\n\n.. code-block:: text\n\n  '[eos] attr ..' provides the extended attribute interface for directories in EOS.\n  attr [OPTIONS] ls|set|get|rm ...\n  Options:\n  attr [-r] ls <identifier> :\n    : list attributes of path\n   -r : list recursive on all directory children\n  attr [-r] set <key>=<value> <identifier> :\n    : set attributes of path (-r recursive)\n  attr [-r] set default=replica|raiddp|raid5|raid6|archive|qrain <identifier> :\n    : set attributes of path (-r recursive) to the EOS defaults for replicas, dual-parity-raid (4+2), raid-6 (4+2) or archive layouts (5+3).\n   -r : set recursive on all directory children\n  attr [-r] get <key> <identifier> :\n    : get attributes of path (-r recursive)\n   -r : get recursive on all directory children\n  attr [-r] rm  <key> <identifier> :\n    : delete attributes of path (-r recursive)\n.. code-block:: text\n\n   -r : delete recursive on all directory children\n  attr [-r] link <origin> <identifier> :\n    : link attributes of <origin> under the attributes of <identifier> (-r recursive)\n   -r : apply recursive on all directory children\n  attr [-r] unlink <identifier> :\n    : remove attribute link of <identifier> (-r recursive)\n   -r : apply recursive on all directory children\n  attr [-r] fold <identifier> :\n    : fold attributes of <identifier> if an attribute link is defined (-r recursive)\n    all attributes which are identical to the origin-link attributes are removed locally\n   -r : apply recursive on all directory children\n  Remarks:\n    <identifier> = <path>|fid:<fid-dec>|fxid:<fid-hex>|pid:<pid-dec>|pxid:<pid-hex>\n    If <key> starts with 'sys.' you have to be member of the sudoers group to see this attributes or modify.\n  Administrator Variables:\n    sys.forced.space=<space>              : enforces to use <space>    [configuration dependent]\n    sys.forced.group=<group>              : enforces to use <group>, where <group> is the numerical index of <space>.<n>    [configuration dependent]\n    sys.forced.layout=<layout>            : enforces to use <layout>   [<layout>=(plain,replica,raid5,raid6,archive,qrain)]\n    sys.forced.checksum=<checksum>        : enforces to use file-level checksum <checksum>\n    <checksum> = adler,crc32,crc32c,md5,sha\n    sys.forced.blockchecksum=<checksum>   : enforces to use block-level checksum <checksum>\n    <checksum> = adler,crc32,crc32c,md5,sha\n    sys.forced.nstripes=<n>               : enforces to use <n> stripes[<n>= 1..16]\n    sys.forced.blocksize=<w>              : enforces to use a blocksize of <w> - <w> can be 4k,64k,128k,256k or 1M\n    sys.forced.placementpolicy=<policy>[:geotag] : enforces to use replica/stripe placement policy <policy> [<policy>={scattered|hybrid:<geotag>|gathered:<geotag>}]\n    sys.forced.nouserplacementpolicy=1    : disables user defined replica/stripe placement policy\n    sys.forced.nouserlayout=1             : disables the user settings with user.forced.<xxx>\n    sys.forced.nofsselection=1            : disables user defined filesystem selection with environment variables for reads\n    sys.forced.bookingsize=<bytes>        : set's the number of bytes which get for each new created replica\n    sys.forced.minsize=<bytes>            : set's the minimum number of bytes a file to be stored must have\n    sys.forced.maxsize=<bytes>            : set's the maximum number of bytes a file to be stored can have\n    sys.forced.atomic=1                   : if present enforce atomic uploads e.g. files appear only when their upload is complete - during the upload they have the name <dirname>/.<basename>.<uuid>\n    sys.mtime.propagation=1               : if present a change under this directory propagates an mtime change up to all parents until the attribute is not present anymore\n    sys.allow.oc.sync=1                   : if present, OwnCloud clients can sync pointing to this subtree\n    sys.force.atime=<age>                 : enables atime tagging under that directory. <age> is the minimum age before the access time is stored as change time.\n    sys.lru.expire.empty=<age>            : delete empty directories older than <age>\n    sys.lru.expire.match=[match1:<age1>,match2:<age2>..]\n    : defines the rule that files with a given match will be removed if\n    they haven't been accessed longer than <age> ago. <age> is defined like 3600,3600s,60min,1h,1mo,1y...\n    sys.lru.lowwatermark=<low>\n    sys.lru.highwatermark=<high>          : if the watermark reaches more than <high> %, files will be removed until the usage is reaching <low> %.\n    sys.lru.convert.match=[match1:<age1>,match2:<age2>,match3:<age3>:<<size3>,match4:<age4>:><size4>...]\n    defines the rule that files with a given match will be converted to the layouts defined by sys.conversion.<match> when their access time reaches <age>. Optionally a size limitation can be given e.g. '*:1w:>1G' as 1 week old and larger than 1G or '*:1d:<1k' as one day old and smaller than 1k\n    sys.stall.unavailable=<sec>           : stall clients for <sec> seconds if a needed file system is unavailable\n    sys.redirect.enoent=<host[:port]>     : redirect clients opening non existing files to <host[:port]>\n    => hence this variable has to be set on the directory at level 2 in the eos namespace e.g. /eos/public\n    sys.redirect.enonet=<host[:port]>     : redirect clients opening inaccessible files to <host[:port]>\n    => hence this variable has to be set on the directory at level 2 in the eos namespace e.g. /eos/public\n    sys.recycle=....                      : define the recycle bin for that directory - WARNING: never modify this variables via 'attr' ... use the 'recycle' interface\n    sys.recycle.keeptime=<seconds>        : define the time how long files stay in a recycle bin before final deletions takes place. This attribute has to defined on the recycle - WARNING: never modify this variables via 'attr' ... use the 'recycle' interface\n    sys.recycle.keepratio=< 0 .. 1.0 >    : ratio of used/max quota for space and inodes in the recycle bin under which files are still kept in the recycle bin even if their lifetime has exceeded. If not defined pure lifetime policy will be applied\n    sys.versioning=<n>                    : keep <n> versions of a file e.g. if you upload a file <n+10> times it will keep the last <n+1> versions\n    sys.acl=<acllist>                     : set's an ACL which is honored for open,rm & rmdir operations\n    => <acllist> = <rule1>,<rule2>...<ruleN> is a comma separated list of rules\n    => <rule> = u:<uid|username>|g:<gid|groupname>|egroup:<name>|z:{irwxomqc(!d)(+d)(!u)(+u)}\n    e.g.: <acllist=\"u:300:rw,g:z2:rwo:egroup:eos-dev:rwx,u:500:rwm!d:u:600:rwqc\"\n    => user id 300 can read + write\n    => group z2 can read + write-once (create new files but can't delete)\n    => members of egroup 'eos-dev' can read & write & browse\n    => user id 500 can read + write into and chmod(m), but cannot delete the directory itself(!d)!\n    => user id 600 can read + write and administer the quota node(q) and can change the directory ownership in child directories(c)\n    '+d' : this tag can be used to overwrite a group rule excluding deletion via '!d' for certain users\n    '+u' : this tag can be used to overwrite a rul excluding updates via '!u'\n    'c'  : this tag can be used to grant chown permissions\n    'q'  : this tag can be used to grant quota administrator permissions\n    e.g.: sys.acl='z:!d' => 'z' is a rule for every user besides root e.g. nobody can delete here'b\n    sys.acl='z:i' => directory becomes immutable\n    sys.eval.useracl                      : enables the evaluation of user acls if key is defined\n    sys.mask                              : masks all unix access permissions with a given mask .e.g sys.mask=775 disables writing to others\n    sys.owner.auth=<owner-auth-list>      : set's additional owner on a directory - open/create + mkdir commands will use the owner id for operations if the client is part of the owner authentication list\n    sys.owner.auth=*                      : every person with write permission will be mapped to the owner uid/gid pair of the parent directory and quota will be accounted on the owner uid/gid pair\n    => <owner-auth-list> = <auth1>:<name1>,<auth2>:<name2  e.g. krb5:nobody,gsi:DN=...\n    sys.attr.link=<directory>             : symbolic links for attributes - all attributes of <directory> are visible in this directory and overwritten/extended by the local attributes\n    sys.http.index=<path>                 : show a static page as directory index instead of the dynamic one\n    => <path> can be a relative or absolute file path!\n    sys.accounting.*=<value>              : set accounting attributes with value on the proc directory (common values) or quota nodes which translate to JSON output in the accounting report command\n    => You have to create such an attribute for each leaf value in the desired JSON.\n    => JSON objects: create a new key with a new name after a '.', e.g. sys.accounting.storagecapacity.online.totalsize=x or sys.accounting.storagecapacity.online.usedsize=y to add a new key-value to this object\n    => JSON arrays: place a continuous whole number from 0 to the attribute name, e.g. sys.accounting.accessmode.{0,1,2,...}\n    => array of objects: you can combine the above two to achieve arbitrary JSON output, e.g. sys.accounting.storageendpoints.0.name, sys.accounting.storageendpoints.0.id and sys.accounting.storageendpoints.1.name ...\n    sys.proc=<opaque command>             : run arbitrary command on accessing the file\n    => <opaque command> command to execute in opaque format, e.g. mgm.cmd=accounting&mgm.subcmd=report&mgm.format=fuse\n  User Variables:\n    user.forced.space=<space>              : s.a.\n    user.forced.layout=<layout>            : s.a.\n    user.forced.checksum=<checksum>        : s.a.\n    user.forced.blockchecksum=<checksum>   : s.a.\n    user.forced.nstripes=<n>               : s.a.\n    user.forced.blocksize=<w>              : s.a.\n    user.forced.placementpolicy=<policy>[:geotag] : s.a.\n    user.forced.nouserplacementpolicy=1            : s.a.\n    user.forced.nouserlayout=1             : s.a.\n    user.forced.nofsselection=1            : s.a.\n    user.forced.atomic=1                   : s.a.\n    user.stall.unavailable=<sec>           : s.a.\n    user.acl=<acllist>                     : s.a.\n    user.versioning=<n>                    : s.a.\n    user.tag=<tag>                         : Tag <tag> to group files for scheduling and flat file distribution. Use this tag to define datasets (if <tag> contains space use tag with quotes)\n  \n  --------------------------------------------------------------------------------\n  Examples:\n  ...................\n  ....... Layouts ...\n  ...................\n  - set 2 replica as standard layout ...\n    |eos> attr set default=replica /eos/instance/2-replica\n  --------------------------------------------------------------------------------\n  - set RAID-6 4+2 as standard layout ...\n    |eos> attr set default=raid6 /eos/instance/raid-6\n  --------------------------------------------------------------------------------\n  - set ARCHIVE 5+3 as standard layout ...\n    |eos> attr set default=archive /eos/instance/archive\n  --------------------------------------------------------------------------------\n  - set QRAIN 8+4 as standard layout ...\n    |eos> attr set default=qrain /eos/instance/qrain\n  --------------------------------------------------------------------------------\n  - re-configure a layout for different number of stripes (e.g. 10) ...\n    |eos> attr set sys.forced.stripes=10 /eos/instance/archive\n  ................\n  ....... ACLs ...\n  ................\n  - forbid deletion and updates for group xx in a directory ...\n    |eos> attr set sys.acl=g:xx::!d!u /eos/instance/no-update-deletion\n  .....................\n  ....... LRU Cache ...\n  .....................\n  - configure a volume based LRU cache with a low/high watermark \n    e.g. when the cache reaches the high watermark it cleans the oldest files until low-watermark is reached ...\n    |eos> quota set -g 99 -v 1T /eos/instance/cache/                           # define project quota on the cache\n    |eos> attr set sys.lru.lowwatermark=90  /eos/instance/cache/\n    |eos> attr set sys.lru.highwatermark=95  /eos/instance/cache/              # define 90 as low and 95 as high watermark\n    |eos> attr set sys.force.atime=300 /eos/dev/instance/cache/                # track atime with a time resolution of 5 minutes\n  --------------------------------------------------------------------------------\n  - configure clean-up of empty directories ...\n    |eos> attr set sys.lru.expire.empty=\"1h\" /eos/dev/instance/empty/          # remove automatically empty directories if they are older than 1 hour\n  --------------------------------------------------------------------------------\n  - configure a time based LRU cache with an expiration time ...\n    |eos> attr set sys.lru.expire.match=\"*.root:1mo,*.tgz:1w\"  /eos/dev/instance/scratch/\n    # files with suffix *.root get removed after a month, files with *.tgz after one week\n    |eos> attr set sys.lru.expire.match=\"*:1d\" /eos/dev/instance/scratch/      # all files older than a day are automatically removed\n  --------------------------------------------------------------------------------\n  - configure automatic layout conversion if a file has reached a defined age ...\n    |eos> attr set sys.lru.convert.match=\"*:1mo\" /eos/dev/instance/convert/    # convert all files older than a month to the layout defined next\n    |eos> attr set sys.lru.convert.match=\"*:1mo:>2G\" /eos/dev/instance/convert/# convert all files older than a month and larger than 2Gb to the layout defined next\n    |eos> attr set sys.conversion.*=20640542 /eos/dev/instance/convert/          # define the conversion layout (hex) for the match rule '*' - this is RAID6 4+2\n    |eos> attr set sys.conversion.*=20640542|gathered:site1::rack2 /eos/dev/instance/convert/ # same thing specifying a placement policy for the replicas/stripes\n  --------------------------------------------------------------------------------\n  - configure automatic layout conversion if a file has not been used during the last 6 month ...\n    |eos> attr set sys.force.atime=1w /eos/dev/instance/cache/                   # track atime with a time resolution of one week\n    |eos> attr set sys.lru.convert.match=\"*:6mo\" /eos/dev/instance/convert/    # convert all files older than a month to the layout defined next\n    |eos> attr set sys.conversion.*=20640542  /eos/dev/instance/convert/         # define the conversion layout (hex) for the match rule '*' - this is RAID6 4+2\n    |eos> attr set sys.conversion.*=20640542|gathered:site1::rack2 /eos/dev/instance/convert/ # same thing specifying a placement policy for the replicas/stripes\n  --------------------------------------------------------------------------------\n  .......................\n  ....... Recycle Bin ...\n  .......................\n  - configure a recycle bin with 1 week garbage collection and 100 TB space ...\n    |eos> recycle config --lifetime 604800                                     # set the lifetime to 1 week\n    |eos> recycle config --size 100T                                           # set the size of 100T\n    |eos> recycle config --add-bin /eos/dev/instance/                          # add's the recycle bin to the subtree /eos/dev/instance\n  .......................\n  .... Atomic Uploads ...\n  .......................\n    |eos> attr set sys.forced.atomic=1 /eos/dev/instance/atomic/\n  .......................\n  .... Attribute Link ...\n  .......................\n    |eos> attr set sys.attr.link=/eos/dev/origin-attr/ /eos/dev/instance/attr-linked/\n"
  },
  {
    "path": "doc/citrine/clicommands/backup.rst",
    "content": "backup\n------\n\n.. code-block:: text\n\n  usage: backup <src_url> <dst_url> [options] \n   \n   optional arguments: \n   --ctime|mtime <val>s|m|h|d use the specified timewindow to select entries for backup\n   --excl_xattr val_1[,val_2]...[,val_n] extended attributes which are not enforced and\n    also not checked during the verification step\n"
  },
  {
    "path": "doc/citrine/clicommands/cd.rst",
    "content": "cd\n--\n\n.. code-block:: text\n\n  '[eos] cd ...' provides the namespace change directory command in EOS.\n  cd <dir>|-|..|~\n  Options:\n  cd <dir> :\n    change into directory <dir>. If it does not exist, the current directory will stay as before!\n  cd - :\n    change into the previous directory\n  cd .. :\n    change into the directory one level up\n  cd ~ :\n    change into the directory defined via the environment variable EOS_HOME\n"
  },
  {
    "path": "doc/citrine/clicommands/chmod.rst",
    "content": "chmod\n-----\n\n.. code-block:: text\n\n  usage: chmod [-r] <mode> <path>                             : set mode for <path> (-r recursive)\n    <mode> can be only numerical like 755, 644, 700\n    <mode> are automatically changed to 2755, 2644, 2700 respectively\n    <mode> to disable attribute inheritance use 4755, 4644, 4700 ...\n"
  },
  {
    "path": "doc/citrine/clicommands/chown.rst",
    "content": "chown\n-----\n\n.. code-block:: text\n\n  chown [-r] [-h --nodereference] <owner>[:<group>] <path>\n    chown [-r] :<group> <path>\n  '[eos] chown ..' provides the change owner interface of EOS.\n  <path> is the file/directory to modify, <owner> has to be a user id or user name. <group> is optional and has to be a group id or group name.\n  To modify only the group use :<group> as identifier!\n  Remark: if you use the -r -h option and path points to a link the owner of the link parent will also be updated!Options:\n    -r : recursive\n"
  },
  {
    "path": "doc/citrine/clicommands/clear.rst",
    "content": "clear\n-----\n\n.. code-block:: text\n\n  clear\n  '[eos] clear' is equivalent to the interactive shell command to clear the screen.\n"
  },
  {
    "path": "doc/citrine/clicommands/config.rst",
    "content": "config\n------\n\n.. code-block:: text\n\n   usage:\n  config changelog|dump|export|load|ls|reset|save [OPTIONS]\n  '[eos] config' provides the configuration interface to EOS.\n  Subcommands:\n  config changelog [-#lines] : show the last #lines from the changelog - default is 10\n  config dump [<name>] : dump configuration with name <name> or current one by default\n  config export <name> [-f] : export a configuration stored on file to QuarkDB (you need to specify the full path!)\n  \t -f : overwrite existing config name and create a timestamped backup\n  config load <name> : load <name> config\n  config ls [-b|--backup] : list existing configurations\n  \t -b : show also backup & autosave files\n  config reset : reset all configuration to empty state\n  config save <name> [-f] [-c|--comment \"<comment>\"] : save config under <name>\n  \t -f : overwrite existing config name and create a timestamped backup\n  \t -c : add a comment entry to the config\n"
  },
  {
    "path": "doc/citrine/clicommands/console.rst",
    "content": "console\n-------\n\n.. code-block:: text\n\n  console :              Run Error Console"
  },
  {
    "path": "doc/citrine/clicommands/cp.rst",
    "content": "cp\n--\n\n.. code-block:: text\n\n  cp [--async] [--atomic] [--rate=<rate>] [--streams=<n>] [--depth=<d>] [--checksum] [--no-overwrite|-k] [--preserve|-p] [--recursive|-r|-R] [-s|--silent] [-a] [-n] [-S] [-d[=][<lvl>] <src> <dst>\n  '[eos] cp ..' provides copy functionality to EOS.\n    <src>|<dst> can be root://<host>/<path>, a local path /tmp/../ or an eos path /eos/ in the connected instance\n  Options:\n    --async         : run an asynchronous transfer via a gateway server (see 'transfer submit --sync' for the full options)\n    --atomic        : run an atomic upload where files are only visible with the target name when their are completely uploaded [ adds ?eos.atomic=1 to the target URL ]\n    --rate          : limit the cp rate to <rate>\n    --streams       : use <#> parallel streams\n    --depth         : depth for recursive copy\n    --checksum      : output the checksums\n    -a              : append to the target, don't truncate\n    -p              : create destination directory\n    -n              : hide progress bar\n    -S              : print summary\n    -d | --debug          : enable debug information (optional <lvl>=1|2|3)\n    -s | --silent         : no output outside error messages\n    -k | --no-overwrite   : disable overwriting of files\n    -P | --preserve       : preserves file creation and modification time from the source\n    -r | -R | --recursive : copy source location recursively\n.. code-block:: text\n\n  Remark: \n    If you deal with directories always add a '/' in the end of source or target paths e.g. if the target should be a directory and not a file put a '/' in the end. To copy a directory hierarchy use '-r' and source and target directories terminated with '/' !\n  Examples: \n    eos cp /var/data/myfile /eos/foo/user/data/                   : copy 'myfile' to /eos/foo/user/data/myfile\n    eos cp /var/data/ /eos/foo/user/data/                         : copy all plain files in /var/data to /eos/foo/user/data/\n    eos cp -r /var/data/ /eos/foo/user/data/                      : copy the full hierarchy from /var/data/ to /eos/foo/user/data/ => empty directories won't show up on the target!\n    eos cp -r --checksum --silent /var/data/ /eos/foo/user/data/  : copy the full hierarchy and just printout the checksum information for each file copied!\n  S3:\n    URLs have to be written as:\n    as3://<hostname>/<bucketname>/<filename> as implemented in ROOT\n    or as3:<bucketname>/<filename> with environment variable S3_HOSTNAME set\n    and as3:....?s3.id=<id>&s3.key=<key>\n    The access id can be defined in 3 ways:\n    env S3_ACCESS_ID=<access-id>          [as used in ROOT  ]\n    env S3_ACCESS_KEY_ID=<access-id>      [as used in libs3 ]\n    <as3-url>?s3.id=<access-id>           [as used in EOS transfers ]\n    The access key can be defined in 3 ways:\n    env S3_ACCESS_KEY=<access-key>        [as used in ROOT ]\n    env S3_SECRET_ACCESS_KEY=<access-key> [as used in libs3 ]\n    <as3-url>?s3.key=<access-key>         [as used in EOS transfers ]\n    If <src> and <dst> are using S3, we are using the same credentials on both ends and the target credentials will overwrite source credentials!\n"
  },
  {
    "path": "doc/citrine/clicommands/debug.rst",
    "content": "debug\n-----\n\n.. code-block:: text\n\n   usage:\n  debug get|this|<level> [node-queue] [--filter <unitlist>]\n  '[eos] debug ...' allows to get or set the verbosity of the EOS log files in MGM and FST services.\n  Options:\n  debug get : retrieve the current log level for the mgm and fsts node-queue\n  debug this : toggle EOS shell debug mode\n  debug  <level> [--filter <unitlist>] : set the MGM where the console is connected to into debug level <level>\n  debug  <level> <node-queue> [--filter <unitlist>] : set the <node-queue> into debug level <level>.\n  \t - <node-queue> are internal EOS names e.g. '/eos/<hostname>:<port>/fst'\n  \t - <unitlist> is a comma separated list of strings of software units which should be filtered out in the message log!\n  The default filter list is:\n  'Process,AddQuota,Update,UpdateHint,UpdateQuotaStatus,SetConfigValue,Deletion,GetQuota,PrintOut,RegisterNode,SharedHash,listenFsChange,placeNewReplicas,placeNewReplicasOneGroup,accessReplicas,accessReplicasOneGroup,accessHeadReplicaMultipleGroup,updateTreeInfo,updateAtomicPenalties,updateFastStructures,work'.\n  The allowed debug levels are:\n  debug,info,warning,notice,err,crit,alert,emerg\n  Examples:\n  \t debug info *                         set MGM & all FSTs into debug mode 'info'\n  \t debug err /eos/*/fst                 set all FSTs into debug mode 'info'\n  \t debug crit /eos/*/mgm                set MGM into debug mode 'crit'\n  \t debug debug --filter MgmOfsMessage   set MGM into debug mode 'debug' and filter only messages coming from unit 'MgmOfsMessage'.\n  \n"
  },
  {
    "path": "doc/citrine/clicommands/evict.rst",
    "content": "evict\n--------\n\n.. code-block:: text\n\n  evict [--fsid <fsid>] [-f|--force] <path>|fid:<fid-dec>]|fxid:<fid-hex> [<path>|fid:<fid-dec>]|fxid:<fid-hex>] ...\n    Evicts all disk replicas of the given files separated by space\n    This command requires write and p acl flag permission\n.. code-block:: text\n\n"
  },
  {
    "path": "doc/citrine/clicommands/exit.rst",
    "content": "exit\n----\n\n.. code-block:: text\n\n  exit :                 Exit from EOS console"
  },
  {
    "path": "doc/citrine/clicommands/file.rst",
    "content": "file\n----\n\n.. code-block:: text\n\n  file adjustreplica|check|convert|copy|drop|info|layout|move|purge|rename|replicate|verify|version ...\n  '[eos] file ..' provides the file management interface of EOS.\n  Options:\n  file adjustreplica [--nodrop] <path>|fid:<fid-dec>|fxid:<fid-hex> [space [subgroup]] :\n    tries to bring a files with replica layouts to the nominal replica level [ need to be root ]\n  file check [<path>|fid:<fid-dec>|fxid:<fid-hex>] [%size%checksum%nrep%checksumattr%force%output%silent] :\n    retrieves stat information from the physical replicas and verifies the correctness\n    - %size                                                       :  return with error code EFAULT if there is a mismatch between the size meta data information\n    - %checksum                                                   :  return with error code EFAULT if there is a mismatch between the checksum meta data information\n    - %nrep                                                       :  return with error code EFAULT if there is a mismatch between the layout number of replicas and the existing replicas\n    - %checksumattr                                               :  return with error code EFAULT if there is a mismatch between the checksum in the extended attributes on the FST and the FMD checksum\n    - %silent                                                     :  suppresses all information for each replica to be printed\n    - %force                                                      :  forces to get the MD even if the node is down\n    - %output                                                     :  prints lines with inconsistency information\n  file convert [--sync|--rewrite] [<path>|fid:<fid-dec>|fxid:<fid-hex>] [<layout>:<stripes> | <layout-id> | <sys.attribute.name>] [target-space] [placement-policy]:\n    convert the layout of a file\n    <layout>:<stripes>   : specify the target layout and number of stripes\n    <layout-id>          : specify the hexadecimal layout id\n    <conversion-name>    : specify the name of the attribute sys.conversion.<name> in the parent directory of <path> defining the target layout\n    <target-space>       : optional name of the target space or group e.g. default or default.3\n    <placement-policy>   : optional placement policy valid values are 'scattered','hybrid:<some_geotag>' and 'gathered:<some_geotag>'\n    --sync               : run conversion in synchronous mode (by default conversions are asynchronous) - not supported yet\n    --rewrite            : run conversion rewriting the file as is creating new copies and dropping old\n  file copy [-f] [-s] [-c] <src> <dst>                                   :  synchronous third party copy from <src> to <dst>\n    <src>                                                         :  source can be a file or a directory (<path>|fid:<fid-dec>|fxid:<fid-hex>)\n    <dst>                                                         :  destination can be a file (if source is a file) or a directory\n    -f                                                            :  force overwrite\n    -s                                                            :  don't print output\n    -c                                                            :  clone the file (keep ctime, mtime)\n  file drop [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid> [-f] :\n    drop the file <path> from <fsid> - force removes replica without trigger/wait for deletion (used to retire a filesystem)\n  file info [<path>|fid:<fid-dec>|fxid:<fid-hex>] :\n    convenience function aliasing to 'fileinfo' command\n  file layout <path>|fid:<fid-dec>|fxid:<fid-hex>  -stripes <n> :\n    change the number of stripes of a file with replica layout to <n>\n  file layout <path>|fid:<fid-dec>|fxid:<fid-hex>  -checksum <checksum-type> :\n    change the checksum-type of a file to <checksum-type>\n  file move [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid1> <fsid2> :\n    move the file <path> from  <fsid1> to <fsid2>\n  file purge <path> [purge-version] :\n    keep maximum <purge-version> versions of a file. If not specified apply the attribute definition from sys.versioning.\n  file rename [<path>|fid:<fid-dec>|fxid:<fid-hex>] <new> :\n    rename from <old> to <new> name (works for files and directories!).\n  file replicate [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid1> <fsid2> :\n    replicate file <path> part on <fsid1> to <fsid2>\n  file symlink <name> <link-name> :\n    create a symlink with <name> pointing to <link-name>\n  file tag <name> +|-|~<fsid> :\n    add/remove/unlink a filesystem location to/from a file in the location index - attention this does not move any data!\n    unlink keeps the location in the list of deleted files e.g. the location gets a deletion request\n  file touch [<path>|fid:<fid-dec>|fxid:<fid-hex>] :\n    create a 0-size/0-replica file if <path> does not exist or update modification time of an existing file to the present time\n  file verify <path>|fid:<fid-dec>|fxid:<fid-hex> [<fsid>] [-checksum] [-commitchecksum] [-commitsize] [-rate <rate>] :\n    verify a file against the disk images\n  file verify <path|fid:<fid-dec>|fxid:<fid-hex> -resync :\n    ask all locations to resync their file md records\n    <fsid>          : verifies only the replica on <fsid>\n    -checksum       : trigger the checksum calculation during the verification process\n    -commitchecksum : commit the computed checksum to the MGM\n    -commitsize     : commit the file size to the MGM\n    -rate <rate>    : restrict the verification speed to <rate> per node\n  file version <path> [purge-version] :\n    create a new version of a file by cloning\n    <purge-version> : defines the max. number of versions to keep\n  file versions [grab-version] :\n    list versions of a file\n    grab a version [grab-version] of a file\n.. code-block:: text\n\n    if not specified it will add a new version without purging any previous version\n  file share <path> [lifetime] :\n    <path>          : path to create a share link\n    <lifetime>      : validity time of the share link like 1, 1s, 1d, 1w, 1mo, 1y, ... default is 28d\n  file workflow <path>|fid:<fid-dec>|fxid:<fid-hex> <workflow> <event> :\n    trigger workflow <workflow> with event <event> on <path>\n"
  },
  {
    "path": "doc/citrine/clicommands/fileinfo.rst",
    "content": "fileinfo\n--------\n\n.. code-block:: text\n\n  fileinfo <identifier> [--path] [--fid] [--fxid] [--size] [--checksum] [--fullpath] [--proxy] [-m] [--env] [-s|--silent]\n    Prints file information for specified <identifier>\n    <identifier> = <path>|fid:<fid-dec>|fxid:<fid-hex>|inode:<inode-dec>\n  Options:\n    --path                        :  filters output to show path field\n    --fid                         :  filters output to show fid field\n    --fxid                        :  filters output to show fxid field\n    --size                        :  filters output to show size field\n    --checksum                    :  filters output to show checksum field\n    --fullpath                    :  adds physical path information to the output\n    --proxy                       :  adds proxy information to the output\n    -m                            :  prints single-line information in monitoring format\n    --env                         :  prints information in OucEnv format\n    -s | --silent                      :  silent - used to run as internal command\n.. code-block:: text\n\n  Remarks:\n    Filters stack up and apply only to normal display mode.\n    Command also supports JSON output.\n"
  },
  {
    "path": "doc/citrine/clicommands/find.rst",
    "content": "find\n----\n\n.. code-block:: text\n\n  usage: find [-name <pattern>] [--xurl] [--childcount] [--purge <n> ] [--count] [-s] [-d] [-f] [-0] [-1] [-ctime +<n>|-<n>] [-m] [-x <key>=<val>] [-p <key>] [-b] [-c %tags] [-layoutstripes <n>] <path>\n    -f -d :  find files(-f) or directories (-d) in <path>\n    -name <pattern> :  find by name or wildcard match\n    -x <key>=<val> :  find entries with <key>=<val>\n    -0 :  find 0-size files\n    -g :  find files with mixed scheduling groups\n    -p <key> :  additionally print the value of <key> for each entry\n    -b :  query the server balance of the files found\n    -c %tags  :  find all files with inconsistencies defined by %tags [ see help of 'file check' command]\n    -s :  run as a subcommand (in silent mode)\n    -ctime +<n> :  find files older than <n> days\n    -ctime -<n> :  find files younger than <n> days\n    -layoutstripes <n> :  apply new layout with <n> stripes to all files found\n    --maxdepth <n> :  descend only <n> levels\n    -1 :  find files which are at least 1 hour old\n    --stripediff :  find files which have not the nominal number of stripes(replicas)\n    --faultyacl :  find directories with illegal ACLs\n    --count :  just print global counters for files/dirs found\n    --xurl :  print the XRootD URL instead of the path name\n    --childcount :  print the number of children in each directory\n    --purge <n> | atomic\n    :  remove versioned files keeping <n> versions - to remove all old versions use --purge 0 ! To\n    apply the settings of the extended attribute definition use <n>=-1! To remove all atomic upload\n    left-overs older than a day user --purge atomic\n    default :  find files and directories\n    find [--nrep] [--nunlink] [--size] [--fileinfo] [--online] [--hosts] [--partition] [--fid] [--fs] [--checksum] [--ctime] [--mtime] [--uid] [--gid] <path>\n    :  find files and print out the requested meta data as key value pairs\n    path=file:...  :  do a find in the local file system (options ignored) - 'file:' is the current working directory\n    path=root:...  :  do a find on a plain XRootD server (options ignored) - does not work on native XRootD clusters\n    path=as3:...   :  do a find on an S3 bucket\n    path=...       :  all other paths are considered to be EOS paths!\n"
  },
  {
    "path": "doc/citrine/clicommands/fs.rst",
    "content": "fs\n--\n\n.. code-block:: text\n\n  fs add|boot|config|dropdeletion|dropghosts|dropfiles|dumpmd|ls|mv|rm|status [OPTIONS]\n    Options:\n    fs add [-m|--manual <fsid>] <uuid> <node-queue>|<host>[:<port>] <mountpoint> [<space>] [<status>]\n    add and assign a filesystem based on the unique identifier of the disk <uuid>\n    -m|--manual  : add with user specified <fsid> and <space>\n    <fsid>       : numeric filesystem id 1...65535\n    <uuid>       : unique string identifying current filesystem\n    <node-queue> : internal EOS identifier for a node e.g /eos/<host>:<port>/fst\n    it is preferable to use the host:port syntax\n    <host>       : FQDN of host where filesystem is mounter\n    <port>       : FST XRootD port number [usually 1095]\n    <mountponit> : local path of the mounted filesystem e.g /data/\n    <space>      : space in which to insert the filesystem, if nothing is\n    specified then space \"default\" is used\n    <status>     : set filesystem status after insertion e.g off|rw|ro etc.\n.. code-block:: text\n\n    fs boot <fsid>|<uuid>|<node-queue>|* [--syncmgm]\n    boot - filesystem identified by <fsid> or <uuid>\n    - all filesystems on a node identified by <node-queue>\n    - all filesystems registered\n    --syncmgm    : for MGM resynchronization during the booting\n    fs clone <sourceid> <targetid>\n    replicate files from the source to the target filesystem\n    <sourceid>   : id of the source filesystem\n    <targetid>   : id of the target filesystem\n\n    fs compare <sourceid> <targetid>\n    compares and reports which files are present on one filesystem and not on the other\n    <sourceid>   : id of the source filesystem\n    <targetid>   : id of the target filesystem\n\n    fs config <fsid> <key>=<value>\n    configure the filesystem parameter, where <key> and <value> can be:\n    configstatus=rw|wo|ro|drain|draindead|off|empty [--comment \"<comment>\"]\n    rw        : set filesystem in read-write mode\n    wo        : set filesystem in write-only mode\n    ro        : set filesystem in read-only mode\n    drain     : set filesystem in drain mode\n    draindead : set filesystem in draindead mode, unusable for any read\n    off       : disable filesystem\n    empty     : empty filesystem, possible only if there are no\n    more files stored on it\n    --comment : pass a reason for the status change\n    headroom=<size>\n    headroom to keep per filesystem. <size> can be (>0)[BMGT]\n    scaninterval=<seconds>\n    entry rescan interval (default 7 days), 0 disables scanning\n    scanrate=<MB/s>\n    maximum IO scan rate per filesystem\n    scan_disk_interval=<seconds>\n    disk consistency thread scan interval (default 4h)\n    scan_ns_interval=<seconds>\n    namespace consistency thread scan interval (default 3 days)\n    scan_ns_rate=<entries/s>\n    maximum scan rate of ns entries for the NS consistency. This\n    is bound by the maximum number of IOPS per disk.\n    graceperiod=<seconds>\n    grace period before a filesystem with an operation error gets\n    automatically drained\n    drainperiod=<seconds>\n    period a drain job is allowed to finish the drain procedure\n    proxygroup=<proxy_grp_name>\n    schedule a proxy for the current filesystem by taking it from\n    the given proxy group. The special value \"<none>\" is the\n    same as no value and means no proxy scheduling\n    filestickyproxydepth=<depth>\n    depth of the subtree to be considered for file-stickyness. A\n    negative value means no file-stickyness\n    forcegeotag=<geotag>\n    set the filesystem's geotag, overriding the host geotag value.\n    The special value \"<none>\" is the same as no value and means\n    no override\n    s3credentials=<accesskey>:<secretkey>\n    the access and secret key pair used to authenticate\n    with the S3 storage endpoint\n    fs dropdeletion <fsid>\n    drop all pending deletions on the filesystem\n    fs dropghosts <fsid> [--fxid fid1 [fid2] ...]\n    drop file ids (hex) without a corresponding metadata object in\n    the namespace that are still accounted in the file system view.\n    If no fxid is provided then all fids on the file system are checked.\n    fs dropfiles <fsid> [-f]\n    drop all files on the filesystem\n    -f : unlink/remove files from the namespace (you have to remove\n    the files from disk)\n    fs dumpmd <fsid> [--fid] [--path] [--size] [-m|-s]\n    dump all file metadata on this filesystem in query format\n    --fid  : dump only the file ids\n    --path : dump only the file paths\n    --size : dump only the file sizes\n    -m     : print full metadata record in env format\n    -s     : silent mode (will keep an internal reference)\n    fs ls [-m|-l|-e|--io|--fsck|[-d|--drain]|-D|-F] [-s] [-b|--brief] [[matchlist]]\n    list filesystems using the default output format\n    -m         : monitoring format\n    -b|--brief : display hostnames without domain names\n    -l         : display parameters in long format\n    -e         : display filesystems in error state\n    --io       : IO output format\n    --fsck     : display filesystem check statistics\n    -d|--drain : display filesystems in drain or draindead status\n    along with drain progress and statistics\n    -D|--drain_jobs :\n    display ongoing drain transfers, matchlist needs to be an integer\n    representing the drain file system id\n    -F|--failed_drain_jobs :\n    display failed drain transfers, matchlist needs to be an integer\n    representing the drain file system id. This will only display\n    information while the draining is ongoing\n    -s         : silent mode\n    [matchlist]\n    -> can be the name of a space or a comma separated list of\n    spaces e.g 'default,spare'\n    -> can be a grep style list to filter certain filesystems\n    e.g. 'fs ls -d drain,bootfailure'\n    -> can be a combination of space filter and grep e.g.\n    'fs ls -l default,drain,bootfailure'\n    fs mv [--force] <src_fsid|src_grp|src_space> <dst_grp|dst_space>\n    move filesystem(s) in different scheduling group or space\n    --force   : force mode - allows to move non-empty filesystems bypassing group\n    and node constraints\n    src_fsid  : source filesystem id\n    src_grp   : all filesystems from scheduling group are moved\n    src_space : all filesystems from space are moved\n    dst_grp   : destination scheduling group\n    dst_space : destination space - best match scheduling group\n    is auto-selected\n    fs rm <fsid>|<mnt>|<node-queue> <mnt>|<hostname> <mnt>\n    remove filesystem by various identifiers, where <mnt> is the\n    mountpoint\n    fs status [-r] [-l] <identifier>\n    return all status variables of a filesystem and calculates\n    the risk of data loss if this filesystem is removed\n    <identifier> can be:\n    <fsid> : filesystem id\n    [<host>] <mountpoint> : if host is not specified then it's\n    considered localhost\n    -l : list all files which are at risk and offline files\n    -r : show risk analysis\n    Examples:\n    fs ls --io -> list all filesystems with IO statistics\n    fs boot *  -> send boot request to all filesystems\n    fs dumpmd 100 -path -> dump all logical path names on filesystem 100\n    fs mv 100 default.0 -> move filesystem 100 to scheduling group default.0\n"
  },
  {
    "path": "doc/citrine/clicommands/fsck.rst",
    "content": "fsck\n----\n\n.. code-block:: text\n\n  fsck [stat|config|report|repair]\n    control and display file system check information\n.. code-block:: text\n\n    fsck stat\n    print summary of consistency checks\n    fsck config <key> [<value>]\n    configure the fsck with the following possible options:\n    toggle-collect       : enable/disable error collection thread, <value> represents\n    the collection interval in minutes [default 30]\n    toggle-repair        : enable/disable repair thread, no <value> required    show-dark-files      : yes/no [default no]\n    show-offline         : yes/no [default no]\n    show-no-replica      : yes/no [default no]\n    max-queued-jobs      : maximum number of queued jobs\n    max-thread-pool-size : maximum number of threads in the fsck pool\n    fsck report [-a] [-h] [-i] [-l] [-j|--json] [--error <tag1> <tag2> ...]\n    report consistency check results, with the following options\n    -a         : break down statistics per file system\n    -i         : display file identifiers\n    -l         : display logical file name\n    -j|--json  : display in JSON output format\n    --error    : display information about the following error tags\n    fsck repair --fxid <val> [--async]\n    repair the given file if there are any errors\n    --fxid  : hexadecimal file identifier\n    --async : job queued and ran by the repair thread if enabled\n  \n"
  },
  {
    "path": "doc/citrine/clicommands/fuse.rst",
    "content": "fuse\n----\n\n.. code-block:: text\n\n  usage: fuse mount  [-o <fuseparameterlist>] [-l <logfile>] <mount-point> : mount connected eos pool on <mount-point>\n    fuse umount <mount-point>                                         : unmount eos pool from <mount-point>\n"
  },
  {
    "path": "doc/citrine/clicommands/fusex.rst",
    "content": "fusex\n-----\n\n.. code-block:: text\n\n  usage: fusex ls [-l] [-f] [-m]                     :  print statistics about eosxd fuse clients\n    [no option]                                          -  break down by client host [default]\n    -l                                                   -  break down by client host and show statistics\n    -f                                                   -  show ongoing flush locks\n    -k                                                   -  show R/W locks\n    -m                                                   -  show monitoring output format\n    fuxex evict <uuid> [<reason>]                                 :  evict a fuse client\n    <uuid> -  uuid of the client to evict\n    <reason> -  optional text shown to the client why he has been evicted or an instruction for an action to the client\n    - if the reason contains the keyword 'abort' the abort handler will be called on client side (might create a stack trace/core)\n    - if reason contains the keyword 'log2big' the client will effectively not be evicted, but will truncate his logfile to 0\n    - if reason contains the keyword 'setlog' and 'debug','notice', 'error', 'crit', 'info', 'warning' the log level of the targeted mount is changed accordingly .e.g evict <uuid> \"setlog error\"\n    - if reason contains the keyword 'stacktrace' the client will send a self-stacktrace with the next heartbeat message and it will be stored in /var/log/eos/mgm/eosxd-stacktraces.log e.g. evict <uuid> stacktrace\n    - if reason contains the keyword 'sendlog' the client will send max. the last 512 lines of each log level and the log will be stored in /var/log/eos/mgm/eosxd-logtraces.log e.g. evict <uuid> sendlog\n    - if reason contains the keyword 'resetbuffer' the client will reset the read-ahead and write-buffers in flight and possibly unlock a locked mount point\n    fusex evict static|autofs mem:<size-in-mb>|idle:<seconds>     :  evict all autofs or static mounts which have a resident memory footprint larger than <size-in-mb> or are idle longer than <seconds>\n    fusex dropcaps <uuid>                                         :  advice a client to drop all caps\n    fusex droplocks <inode> <pid>                                 :  advice a client to drop for a given (hexadecimal) inode and process id\n    fusex caps [-t | -i | -p [<regexp>] ]                         :  print caps\n    -t                                                   -  sort by expiration time\n    -i                                                   -  sort by inode\n    -p                                                   -  display by path\n    -t|i|p <regexp>>                                     -  display entries matching <regexp> for the used filter type\n  examples:\n    fusex caps -i ^0000abcd$                                  :  show caps for inode 0000abcd\n    fusex caps -p ^/eos/$                                     :  show caps for path /eos\n    fusex caps -p ^/eos/caps/                                 :  show all caps in subtree /eos/caps\n    fusex conf [<heartbeat-in-seconds>] [quota-check-in-seconds]  :  show heartbeat and quota interval\n    :  [ optional change heartbeat interval from [1-15] seconds ]\n    :  [ optional set quota check interval from [1-16] seconds ]\n  examples:\n    fusex conf                                                :  show heartbeat and quota interval\n    fusex conf 10                                             :  define heartbeat interval as 10 seconds\n    fusex conf 10 10                                          :  define heartbeat and quota interval as 10 seconds\n"
  },
  {
    "path": "doc/citrine/clicommands/geosched.rst",
    "content": "geosched\n--------\n\n.. code-block:: text\n\n  '[eos] geosched ..' Interact with the file geoscheduling engine in EOS.\n  geosched show|set|updater|forcerefresh|disabled|access ...\n  Options:\n    geosched show [-c|-m] tree [<scheduling group>]                    :  show scheduling trees\n    :  if <scheduling group> is specified only the tree for this group is shown. If it's not all, the trees are shown.\n    :  '-c' enables color display\n    :  '-m' list in monitoring format\n    geosched show [-c|-m] snapshot [{<scheduling group>,*} [<optype>]] :  show snapshots of scheduling trees\n    :  if <scheduling group> is specified only the snapshot(s) for this group is/are shown. If it's not all, the snapshots for all the groups are shown.\n    :  if <optype> is specified only the snapshot for this operation is shown. If it's not, the snapshots for all the optypes are shown.\n    :  <optype> can be one of the following plct,accsro,accsrw,accsdrain,plctdrain\n    :  '-c' enables color display\n    :  '-m' list in monitoring format\n    geosched show param                                                :  show internal parameters\n    geosched show state [-m]                                           :  show internal state\n    :  '-m' list in monitoring format\n    geosched set <param name> [param index] <param value>              :  set the value of an internal state parameter (all names can be listed with geosched show state)\n    geosched updater {pause|resume}                                    :  pause / resume the tree updater\n    geosched forcerefresh                                              :  force a refresh of the trees/snapshots\n    geosched disabled add <geotag> {<optype>,*} {<scheduling subgroup>,*}      :  disable a branch of a subtree for the specified group and operation\n    :  multiple branches can be disabled (by successive calls) as long as they have no intersection\n    geosched disabled rm {<geotag>,*} {<optype>,*} {<scheduling subgroup>,*}   :  re-enable a disabled branch for the specified group and operation\n    :  when called with <geotag> *, the whole tree(s) are re-enabled, canceling all previous disabling\n    geosched disabled show {<geotag>,*} {<optype>,*} {<scheduling subgroup>,*} :  show list of disabled branches for for the specified groups and operation\n    geosched access setdirect <geotag> <geotag_list>                   :  set a mapping between an accesser geotag and a set of target geotags\n    :  these mappings specify which geotag can be accessed from which geotag without going through a firewall entrypoint\n    :  geotag_list is of the form token1::token2,token3::token4::token5,...\n    geosched access showdirect [-m]                                    :  show mappings between accesser geotags and target geotags\n    :  '-m' list in monitoring format\n    geosched access cleardirect {<geotag>|all}                         :  clear a mapping between an accesser geotag and a set of target geotags\n    geosched access setproxygroup <geotag> <proxygroup>                :  set the proxygroup acting as a firewall entrypoint for the given subtree\n    :  if a client accesses a file from a geotag which does not have direct access to the subtree the replica is,\n    :  it will be scheduled to access through a node from the given proxygroup\n    geosched access showproxygroup [-m]                                :  show mappings between accesser geotags and target geotags\n    :  '-m' list in monitoring format\n    geosched access clearproxygroup {<geotag>|all}                     :  clear a mapping between an accesser geotag and a set of target geotags\n"
  },
  {
    "path": "doc/citrine/clicommands/group.rst",
    "content": "group\n-----\n\n.. code-block:: text\n\n   usage:\n  group ls [-s] [-g <depth>] [-b|--brief] [-m|-l|--io] [<groups>] : list groups\n  \t <groups> : list <groups> only, where <groups> is a substring match and can be a comma separated list\n  \t       -s : silent mode\n  \t       -g : geo output - aggregate group information along the instance geotree down to <depth>\n  \t       -b : brief output\n  \t       -m : monitoring key=value output format\n  \t       -l : long output - list also file systems after each group\n  \t     --io : print IO statistics for the group\n  \t     --IO : print IO statistics for each filesystem\n  group rm <group-name> : remove group\n  group set <group-name> on|off : activate/deactivate group\n  \t  => when a group is (re-)enabled, the drain pull flag is recomputed for all filesystems within a group\n  \t  => when a group is (re-)disabled, the drain pull flag is removed from all members in the group\n"
  },
  {
    "path": "doc/citrine/clicommands/health.rst",
    "content": "health\n------\n\n.. code-block:: text\n\n  eos health [OPTION] [SECTION]\n.. code-block:: text\n\n  Options available: \n    --help    Print help\n    -m       Turn on monitoring mode\n    -a       Display all information, not just critical\n  Sections available: \n    all         Display all sections (default value)\n    nodes       Display only information about nodes\n    drain       Display drain health information\n    placement   Display placement contention health information\n"
  },
  {
    "path": "doc/citrine/clicommands/help.rst",
    "content": "help\n----\n\n.. code-block:: text\n\n  help :                 Display this text"
  },
  {
    "path": "doc/citrine/clicommands/info.rst",
    "content": "info\n----\n\n.. code-block:: text\n\n  file adjustreplica|check|convert|copy|drop|info|layout|move|purge|rename|replicate|verify|version ...\n  '[eos] file ..' provides the file management interface of EOS.\n  Options:\n  file adjustreplica [--nodrop] <path>|fid:<fid-dec>|fxid:<fid-hex> [space [subgroup]] :\n    tries to bring a files with replica layouts to the nominal replica level [ need to be root ]\n  file check [<path>|fid:<fid-dec>|fxid:<fid-hex>] [%size%checksum%nrep%checksumattr%force%output%silent] :\n    retrieves stat information from the physical replicas and verifies the correctness\n    - %size                                                       :  return with error code EFAULT if there is a mismatch between the size meta data information\n    - %checksum                                                   :  return with error code EFAULT if there is a mismatch between the checksum meta data information\n    - %nrep                                                       :  return with error code EFAULT if there is a mismatch between the layout number of replicas and the existing replicas\n    - %checksumattr                                               :  return with error code EFAULT if there is a mismatch between the checksum in the extended attributes on the FST and the FMD checksum\n    - %silent                                                     :  suppresses all information for each replica to be printed\n    - %force                                                      :  forces to get the MD even if the node is down\n    - %output                                                     :  prints lines with inconsistency information\n  file convert [--sync|--rewrite] [<path>|fid:<fid-dec>|fxid:<fid-hex>] [<layout>:<stripes> | <layout-id> | <sys.attribute.name>] [target-space] [placement-policy]:\n    convert the layout of a file\n    <layout>:<stripes>   : specify the target layout and number of stripes\n    <layout-id>          : specify the hexadecimal layout id\n    <conversion-name>    : specify the name of the attribute sys.conversion.<name> in the parent directory of <path> defining the target layout\n    <target-space>       : optional name of the target space or group e.g. default or default.3\n    <placement-policy>   : optional placement policy valid values are 'scattered','hybrid:<some_geotag>' and 'gathered:<some_geotag>'\n    --sync               : run conversion in synchronous mode (by default conversions are asynchronous) - not supported yet\n    --rewrite            : run conversion rewriting the file as is creating new copies and dropping old\n  file copy [-f] [-s] [-c] <src> <dst>                                   :  synchronous third party copy from <src> to <dst>\n    <src>                                                         :  source can be a file or a directory (<path>|fid:<fid-dec>|fxid:<fid-hex>)\n    <dst>                                                         :  destination can be a file (if source is a file) or a directory\n    -f                                                            :  force overwrite\n    -s                                                            :  don't print output\n    -c                                                            :  clone the file (keep ctime, mtime)\n  file drop [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid> [-f] :\n    drop the file <path> from <fsid> - force removes replica without trigger/wait for deletion (used to retire a filesystem)\n  file info [<path>|fid:<fid-dec>|fxid:<fid-hex>] :\n    convenience function aliasing to 'fileinfo' command\n  file layout <path>|fid:<fid-dec>|fxid:<fid-hex>  -stripes <n> :\n    change the number of stripes of a file with replica layout to <n>\n  file layout <path>|fid:<fid-dec>|fxid:<fid-hex>  -checksum <checksum-type> :\n    change the checksum-type of a file to <checksum-type>\n  file move [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid1> <fsid2> :\n    move the file <path> from  <fsid1> to <fsid2>\n  file purge <path> [purge-version] :\n    keep maximum <purge-version> versions of a file. If not specified apply the attribute definition from sys.versioning.\n  file rename [<path>|fid:<fid-dec>|fxid:<fid-hex>] <new> :\n    rename from <old> to <new> name (works for files and directories!).\n  file replicate [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid1> <fsid2> :\n    replicate file <path> part on <fsid1> to <fsid2>\n  file symlink <name> <link-name> :\n    create a symlink with <name> pointing to <link-name>\n  file tag <name> +|-|~<fsid> :\n    add/remove/unlink a filesystem location to/from a file in the location index - attention this does not move any data!\n    unlink keeps the location in the list of deleted files e.g. the location gets a deletion request\n  file touch [<path>|fid:<fid-dec>|fxid:<fid-hex>] :\n    create a 0-size/0-replica file if <path> does not exist or update modification time of an existing file to the present time\n  file verify <path>|fid:<fid-dec>|fxid:<fid-hex> [<fsid>] [-checksum] [-commitchecksum] [-commitsize] [-rate <rate>] :\n    verify a file against the disk images\n  file verify <path|fid:<fid-dec>|fxid:<fid-hex> -resync :\n    ask all locations to resync their file md records\n    <fsid>          : verifies only the replica on <fsid>\n    -checksum       : trigger the checksum calculation during the verification process\n    -commitchecksum : commit the computed checksum to the MGM\n    -commitsize     : commit the file size to the MGM\n    -rate <rate>    : restrict the verification speed to <rate> per node\n  file version <path> [purge-version] :\n    create a new version of a file by cloning\n    <purge-version> : defines the max. number of versions to keep\n  file versions [grab-version] :\n    list versions of a file\n    grab a version [grab-version] of a file\n.. code-block:: text\n\n    if not specified it will add a new version without purging any previous version\n  file share <path> [lifetime] :\n    <path>          : path to create a share link\n    <lifetime>      : validity time of the share link like 1, 1s, 1d, 1w, 1mo, 1y, ... default is 28d\n  file workflow <path>|fid:<fid-dec>|fxid:<fid-hex> <workflow> <event> :\n    trigger workflow <workflow> with event <event> on <path>\n"
  },
  {
    "path": "doc/citrine/clicommands/inspector.rst",
    "content": "inspector\n---------\n\n.. code-block:: text\n\n   usage:\n  space ls [-s|-g <depth>] [-m|-l|--io|--fsck] [<space>] : list in all spaces or select only <space>. <space> is a substring match and can be a comma separated list\n  \t      -s : silent mode\n  \t      -m : monitoring key=value output format\n  \t      -l : long output - list also file systems after each space\n  \t      -g : geo output - aggregate space information along the instance geotree down to <depth>\n  \t    --io : print IO statistics\n  \t  --fsck : print filesystem check statistics\n  space config <space-name> space.nominalsize=<value>                   : configure the nominal size for this space\n  space config <space-name> space.balancer=on|off                       : enable/disable the space balancer [ default=off ]\n  space config <space-name> space.balancer.threshold=<percent>          : configure the used bytes deviation which triggers balancing             [ default=20 (%%)     ] \n  space config <space-name> space.balancer.node.rate=<MB/s>             : configure the nominal transfer bandwidth per running transfer on a node [ default=25 (MB/s)   ]\n  space config <space-name> space.balancer.node.ntx=<#>                 : configure the number of parallel balancing transfers per node           [ default=2 (streams) ]\n  space config <space-name> space.converter=on|off                      : enable/disable the space converter [ default=off ]\n  space config <space-name> space.converter.ntx=<#>                     : configure the number of parallel conversions per space                  [ default=2 (streams) ]\n  space config <space-name> space.drainer.node.rate=<MB/s >             : configure the nominal transfer bandwidth per running transfer on a node [ default=25 (MB/s)   ]\n  space config <space-name> space.drainer.node.ntx=<#>                  : configure the number of parallel draining transfers per node            [ default=2 (streams) ]\n  space config <space-name> space.drainer.node.nfs=<#>                  : configure the number of max draining filesystems per node (Valid only for central drain)  [ default=5 ]\n  space config <space-name> space.drainer.retries=<#>                   : configure the number of retry for the draining process (Valid only for central drain)     [ default=1 ]\n  space config <space-name> space.drainer.fs.ntx=<#>                    : configure the number of parallel draining transfers per fs (Valid only for central drain) [ default=5 ]\n  space config <space-name> space.groupbalancer=on|off                  : enable/disable the group balancer [ default=off ]\n  space config <space-name> space.groupbalancer.ntx=<ntx>               : configure the number of parallel group balancer jobs [ default=0 ]\n  space config <space-name> space.groupbalancer.threshold=<threshold>   : configure the threshold when a group is balanced [ default=0 ] ( taken from dev(filled) parameter in 'group ls'\n  space config <space-name> space.geobalancer=on|off                    : enable/disable the geo balancer [ default=off ]\n  space config <space-name> space.geobalancer.ntx=<ntx>                 : configure the number of parallel geobalancer jobs [ default=0 ]\n  space config <space-name> space.geobalancer.threshold=<threshold>     : configure the threshold when a geotag is balanced [ default=0 ] \n  space config <space-name> space.lru=on|off                            : enable/disable the LRU policy engine [ default=off ]\n  space config <space-name> space.lru.interval=<sec>                    : configure the default lru scan interval\n  space config <space-name> space.wfe=on|off|paused                     : enable/disable the Workflow Engine [ default=off ]\n  space config <space-name> space.wfe.interval=<sec>                    : configure the default WFE scan interval\n  space config <space-name> space.headroom=<size>                       : configure the default disk headroom if not defined on a filesystem (see fs for details)\n  space config <space-name> space.scaninterval=<sec>                    : configure the default scan interval if not defined on a filesystem (see fs for details)\n  space config <space-name> space.scanrate=<MB/S>                       : configure the default scan rate if not defined on a filesystem     (see fs for details)\n  space config <space-name> space.drainperiod=<sec>                     : configure the default drain  period if not defined on a filesystem (see fs for details)\n  space config <space-name> space.graceperiod=<sec>                     : configure the default grace  period if not defined on a filesystem (see fs for details)\n  space config <space-name> space.filearchivedgc=on|off                 : enable/disable the 'file archived' garbage collector [ default=off ]\n  space config <space-name> space.tracker=on|off                        : enable/disable the space layout creation tracker [ default=off ]\n  space config <space-name> space.inspector=on|off                      : enable/disable the file inspector [ default=off ]\n  space config <space-name> space.autorepair=on|off                     : enable auto-repair of faulty replica's/files (the converter has to be enabled too)\n    => size can be given also like 10T, 20G, 2P ... (without space before the unit)\n  space config <space-name> space.geo.access.policy.write.exact=on|off  : if 'on' use exact matching geo replica (if available), 'off' uses weighting [ for write case ]\n  space config <space-name> space.geo.access.policy.read.exact=on|off   : if 'on' use exact matching geo replica (if available), 'off' uses weighting [ for read  case ]\n  space config <space-name> fs.<key>=<value>                            : configure file system parameters for each filesystem in this space (see help of 'fs config' for details)\n  space config <space-name> space.policy.[layout|nstripes|checksum|blockchecksum|blocksize]=<value>      \n    : configure default file layout creation settings as a space policy - a value='remove' deletes the space policy\n  space config <space-name> space.policy.recycle=on\n    : globally enforce using always a recycle bin\n  space config default taperestapi.status=on|off                               : enable/disable the tape REST API handler [ default=off ]\n  space config default taperestapi.stage=on|off                                : enable/disable the tape REST API STAGE resource [ default=off ]\n  space define <space-name> [<groupsize> [<groupmod>]] : define how many filesystems can end up in one scheduling group <groupsize> [ default=0 ]\n    => <groupsize>=0 means that no groups are built within a space, otherwise it should be the maximum number of nodes in a scheduling group\n    => <groupmod> maximum number of groups in the space, which should be at least equal to the maximum number of filesystems per node\n  space inspector [--current|-c] [--last|-l] [-m] [-p] [-e] : show namespace inspector output\n  \t  -c  : show current scan\n  \t  -l  : show last complete scan\n  \t  -m  : print last scan in monitoring format\n  \t  -p  : combined with -c or -l lists erroneous files\n  \t  -e  : combined with -c or -l exports erroneous files on the MGM into /var/log/eos/mgm/FileInspector.<date>.list\n  space node-set <space-name> <node.key> <file-name> : store the contents of <file-name> into the node configuration variable <node.key> visible to all FSTs\n    => if <file-name> matches file:<path> the file is loaded from the MGM and not from the client\n    => local files cannot exceed 512 bytes - MGM files can be arbitrary length\n    => the contents gets base64 encoded by default\n  space node-get <space-name> <node.key> : get the value of <node.key> and base64 decode before output\n    => if the value for <node.key> is identical for all nodes in the referenced space, it is dumped only once, otherwise the value is dumped for each node separately\n  space reset <space-name> [--egroup|mapping|drain|scheduledrain|schedulebalance|ns|nsfilesystemview|nsfilemap|nsdirectorymap] : reset different space attributes\n  \t            --egroup : clear cached egroup information\n  \t           --mapping : clear all user/group uid/gid caches\n  \t             --drain : reset draining\n  \t     --scheduledrain : reset drain scheduling map\n  \t   --schedulebalance : reset balance scheduling map\n  \t                --ns : resize all namespace maps\n  \t  --nsfilesystemview : resize namespace filesystem view\n  \t         --nsfilemap : resize namespace file map\n  \t    --nsdirectorymap : resize namespace directory map\n  space status <space-name> [-m] : print all defined variables for space\n  space tracker : print all file replication tracking entries\n  space set <space-name> on|off : enable/disable all groups under that space\n    => <on> value will enable all nodes, <off> value won't affect nodes\n  space rm <space-name> : remove space\n  space quota <space-name> on|off : enable/disable quota\n"
  },
  {
    "path": "doc/citrine/clicommands/io.rst",
    "content": "io\n--\n\n.. code-block:: text\n\n   usage:\n  io stat [-l] [-a] [-m] [-n] [-t] [-d] [-x] [--sa] [--si]: print io statistics\n  \t  -l : show summary information (this is the default if -a,-t,-d,-x is not selected)\n  \t  -a : break down by uid/gid\n  \t  -m : print in <key>=<val> monitoring format\n  \t  -n : print numerical uid/gids\n  \t  -t : print top user stats\n  \t  -d : break down by domains\n  \t  -x : break down by application\n     --sa : start collection of statistics given number of seconds ago\n     --si : collect statistics over given interval of seconds\n     Note: this tool shows data for finished transfers only (using storage node reports)\n     Example: asking for data of finished transfers which were transferred during interval [now - 180s, now - 120s]:\n              eos io stat -x --sa 120 --si 60\\n\"\n\n  io enable [-r] [-p] [-n] [--udp <address>] : enable collection of io statistics\n  \t              -r : enable collection of io reports\n  \t              -p : enable popularity accounting\n  \t              -n : enable report namespace\n  \t --udp <address> : add a UDP message target for io UDP packets (the configured targets are shown by 'io stat -l)\n  io disable [-r] [-p] [-n] [--udp <address>] : disable collection of io statistics\n  \t              -r : disable collection of io reports\n  \t              -p : disable popularity accounting\n  \t              -n : disable report namespace\n  \t --udp <address> : remove a UDP message target for io UDP packets (the configured targets are shown by 'io stat -l)\n  io report <path> : show contents of report namespace for <path>\n  io ns [-a] [-n] [-b] [-100|-1000|-10000] [-w] [-f] : show namespace IO ranking (popularity)\n  \t      -a :  don't limit the output list\n  \t      -n :  show ranking by number of accesses\n  \t      -b :  show ranking by number of bytes\n  \t    -100 :  show the first 100 in the ranking\n  \t   -1000 :  show the first 1000 in the ranking\n  \t  -10000 :  show the first 10000 in the ranking\n  \t      -w :  show history for the last 7 days\n  \t      -f :  show the 'hotfiles' which are the files with highest number of present file opens"
  },
  {
    "path": "doc/citrine/clicommands/json.rst",
    "content": "json\n----\n\n.. code-block:: text\n\n  json :                 Toggle JSON output flag for stdout"
  },
  {
    "path": "doc/citrine/clicommands/license.rst",
    "content": "license\n-------\n\n.. code-block:: text\n\n  license :              Display Software License"
  },
  {
    "path": "doc/citrine/clicommands/ln.rst",
    "content": "ln\n--\n\n.. code-block:: text\n\n  file adjustreplica|check|convert|copy|drop|info|layout|move|purge|rename|replicate|verify|version ...\n  '[eos] file ..' provides the file management interface of EOS.\n  Options:\n  file adjustreplica [--nodrop] <path>|fid:<fid-dec>|fxid:<fid-hex> [space [subgroup]] :\n    tries to bring a files with replica layouts to the nominal replica level [ need to be root ]\n  file check [<path>|fid:<fid-dec>|fxid:<fid-hex>] [%size%checksum%nrep%checksumattr%force%output%silent] :\n    retrieves stat information from the physical replicas and verifies the correctness\n    - %size                                                       :  return with error code EFAULT if there is a mismatch between the size meta data information\n    - %checksum                                                   :  return with error code EFAULT if there is a mismatch between the checksum meta data information\n    - %nrep                                                       :  return with error code EFAULT if there is a mismatch between the layout number of replicas and the existing replicas\n    - %checksumattr                                               :  return with error code EFAULT if there is a mismatch between the checksum in the extended attributes on the FST and the FMD checksum\n    - %silent                                                     :  suppresses all information for each replica to be printed\n    - %force                                                      :  forces to get the MD even if the node is down\n    - %output                                                     :  prints lines with inconsistency information\n  file convert [--sync|--rewrite] [<path>|fid:<fid-dec>|fxid:<fid-hex>] [<layout>:<stripes> | <layout-id> | <sys.attribute.name>] [target-space] [placement-policy]:\n    convert the layout of a file\n    <layout>:<stripes>   : specify the target layout and number of stripes\n    <layout-id>          : specify the hexadecimal layout id\n    <conversion-name>    : specify the name of the attribute sys.conversion.<name> in the parent directory of <path> defining the target layout\n    <target-space>       : optional name of the target space or group e.g. default or default.3\n    <placement-policy>   : optional placement policy valid values are 'scattered','hybrid:<some_geotag>' and 'gathered:<some_geotag>'\n    --sync               : run conversion in synchronous mode (by default conversions are asynchronous) - not supported yet\n    --rewrite            : run conversion rewriting the file as is creating new copies and dropping old\n  file copy [-f] [-s] [-c] <src> <dst>                                   :  synchronous third party copy from <src> to <dst>\n    <src>                                                         :  source can be a file or a directory (<path>|fid:<fid-dec>|fxid:<fid-hex>)\n    <dst>                                                         :  destination can be a file (if source is a file) or a directory\n    -f                                                            :  force overwrite\n    -s                                                            :  don't print output\n    -c                                                            :  clone the file (keep ctime, mtime)\n  file drop [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid> [-f] :\n    drop the file <path> from <fsid> - force removes replica without trigger/wait for deletion (used to retire a filesystem)\n  file info [<path>|fid:<fid-dec>|fxid:<fid-hex>] :\n    convenience function aliasing to 'fileinfo' command\n  file layout <path>|fid:<fid-dec>|fxid:<fid-hex>  -stripes <n> :\n    change the number of stripes of a file with replica layout to <n>\n  file layout <path>|fid:<fid-dec>|fxid:<fid-hex>  -checksum <checksum-type> :\n    change the checksum-type of a file to <checksum-type>\n  file move [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid1> <fsid2> :\n    move the file <path> from  <fsid1> to <fsid2>\n  file purge <path> [purge-version] :\n    keep maximum <purge-version> versions of a file. If not specified apply the attribute definition from sys.versioning.\n  file rename [<path>|fid:<fid-dec>|fxid:<fid-hex>] <new> :\n    rename from <old> to <new> name (works for files and directories!).\n  file replicate [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid1> <fsid2> :\n    replicate file <path> part on <fsid1> to <fsid2>\n  file symlink <name> <link-name> :\n    create a symlink with <name> pointing to <link-name>\n  file tag <name> +|-|~<fsid> :\n    add/remove/unlink a filesystem location to/from a file in the location index - attention this does not move any data!\n    unlink keeps the location in the list of deleted files e.g. the location gets a deletion request\n  file touch [<path>|fid:<fid-dec>|fxid:<fid-hex>] :\n    create a 0-size/0-replica file if <path> does not exist or update modification time of an existing file to the present time\n  file verify <path>|fid:<fid-dec>|fxid:<fid-hex> [<fsid>] [-checksum] [-commitchecksum] [-commitsize] [-rate <rate>] :\n    verify a file against the disk images\n  file verify <path|fid:<fid-dec>|fxid:<fid-hex> -resync :\n    ask all locations to resync their file md records\n    <fsid>          : verifies only the replica on <fsid>\n    -checksum       : trigger the checksum calculation during the verification process\n    -commitchecksum : commit the computed checksum to the MGM\n    -commitsize     : commit the file size to the MGM\n    -rate <rate>    : restrict the verification speed to <rate> per node\n  file version <path> [purge-version] :\n    create a new version of a file by cloning\n    <purge-version> : defines the max. number of versions to keep\n  file versions [grab-version] :\n    list versions of a file\n    grab a version [grab-version] of a file\n.. code-block:: text\n\n    if not specified it will add a new version without purging any previous version\n  file share <path> [lifetime] :\n    <path>          : path to create a share link\n    <lifetime>      : validity time of the share link like 1, 1s, 1d, 1w, 1mo, 1y, ... default is 28d\n  file workflow <path>|fid:<fid-dec>|fxid:<fid-hex> <workflow> <event> :\n    trigger workflow <workflow> with event <event> on <path>\n"
  },
  {
    "path": "doc/citrine/clicommands/ls.rst",
    "content": "ls\n--\n\n.. code-block:: text\n\n  usage: ls [-laniyFN] [--no-globbing] <path>                                     :  list directory <path>\n    -l : show long listing\n    -y : show long listing with backend(tape) status\n    -lh: show long listing with readable sizes\n    -a : show hidden files\n    -i : add inode information\n    -n : show numerical user/group ids\n    -F : append indicator '/' to directories\n    -s : checks only if the directory exists without listing\n    --no-globbing|-N : disables path globbing feature (e.g: list a file containing '[]' characters)\n    path=file:... : list on a local file system\n    path=root:... : list on a plain XRootD server (does not work on native XRootD clusters\n    path=...      : all other paths are considered to be EOS paths!\n"
  },
  {
    "path": "doc/citrine/clicommands/map.rst",
    "content": "map\n---\n\n.. code-block:: text\n\n  '[eos] map ..' provides a namespace mapping interface for directories in EOS.\n  map [OPTIONS] ls|link|unlink ...\n  Options:\n  map ls :\n    : list all defined mappings\n  map link <source-path> <destination-path> :\n    : create a symbolic link from source-path to destination-path\n  map unlink <source-path> :\n    : remove symbolic link from source-path\n"
  },
  {
    "path": "doc/citrine/clicommands/member.rst",
    "content": "member\n------\n\n.. code-block:: text\n\n  member :               Check Egroup membership"
  },
  {
    "path": "doc/citrine/clicommands/mkdir.rst",
    "content": "mkdir\n-----\n\n.. code-block:: text\n\n  usage: mkdir -p <path>                                                :  create directory <path>\n"
  },
  {
    "path": "doc/citrine/clicommands/motd.rst",
    "content": "motd\n----\n\n.. code-block:: text\n\n  motd :                 Message of the day"
  },
  {
    "path": "doc/citrine/clicommands/mv.rst",
    "content": "mv\n--\n\n.. code-block:: text\n\n  file adjustreplica|check|convert|copy|drop|info|layout|move|purge|rename|replicate|verify|version ...\n  '[eos] file ..' provides the file management interface of EOS.\n  Options:\n  file adjustreplica [--nodrop] <path>|fid:<fid-dec>|fxid:<fid-hex> [space [subgroup]] :\n    tries to bring a files with replica layouts to the nominal replica level [ need to be root ]\n  file check [<path>|fid:<fid-dec>|fxid:<fid-hex>] [%size%checksum%nrep%checksumattr%force%output%silent] :\n    retrieves stat information from the physical replicas and verifies the correctness\n    - %size                                                       :  return with error code EFAULT if there is a mismatch between the size meta data information\n    - %checksum                                                   :  return with error code EFAULT if there is a mismatch between the checksum meta data information\n    - %nrep                                                       :  return with error code EFAULT if there is a mismatch between the layout number of replicas and the existing replicas\n    - %checksumattr                                               :  return with error code EFAULT if there is a mismatch between the checksum in the extended attributes on the FST and the FMD checksum\n    - %silent                                                     :  suppresses all information for each replica to be printed\n    - %force                                                      :  forces to get the MD even if the node is down\n    - %output                                                     :  prints lines with inconsistency information\n  file convert [--sync|--rewrite] [<path>|fid:<fid-dec>|fxid:<fid-hex>] [<layout>:<stripes> | <layout-id> | <sys.attribute.name>] [target-space] [placement-policy]:\n    convert the layout of a file\n    <layout>:<stripes>   : specify the target layout and number of stripes\n    <layout-id>          : specify the hexadecimal layout id\n    <conversion-name>    : specify the name of the attribute sys.conversion.<name> in the parent directory of <path> defining the target layout\n    <target-space>       : optional name of the target space or group e.g. default or default.3\n    <placement-policy>   : optional placement policy valid values are 'scattered','hybrid:<some_geotag>' and 'gathered:<some_geotag>'\n    --sync               : run conversion in synchronous mode (by default conversions are asynchronous) - not supported yet\n    --rewrite            : run conversion rewriting the file as is creating new copies and dropping old\n  file copy [-f] [-s] [-c] <src> <dst>                                   :  synchronous third party copy from <src> to <dst>\n    <src>                                                         :  source can be a file or a directory (<path>|fid:<fid-dec>|fxid:<fid-hex>)\n    <dst>                                                         :  destination can be a file (if source is a file) or a directory\n    -f                                                            :  force overwrite\n    -s                                                            :  don't print output\n    -c                                                            :  clone the file (keep ctime, mtime)\n  file drop [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid> [-f] :\n    drop the file <path> from <fsid> - force removes replica without trigger/wait for deletion (used to retire a filesystem)\n  file info [<path>|fid:<fid-dec>|fxid:<fid-hex>] :\n    convenience function aliasing to 'fileinfo' command\n  file layout <path>|fid:<fid-dec>|fxid:<fid-hex>  -stripes <n> :\n    change the number of stripes of a file with replica layout to <n>\n  file layout <path>|fid:<fid-dec>|fxid:<fid-hex>  -checksum <checksum-type> :\n    change the checksum-type of a file to <checksum-type>\n  file move [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid1> <fsid2> :\n    move the file <path> from  <fsid1> to <fsid2>\n  file purge <path> [purge-version] :\n    keep maximum <purge-version> versions of a file. If not specified apply the attribute definition from sys.versioning.\n  file rename [<path>|fid:<fid-dec>|fxid:<fid-hex>] <new> :\n    rename from <old> to <new> name (works for files and directories!).\n  file replicate [<path>|fid:<fid-dec>|fxid:<fid-hex>] <fsid1> <fsid2> :\n    replicate file <path> part on <fsid1> to <fsid2>\n  file symlink <name> <link-name> :\n    create a symlink with <name> pointing to <link-name>\n  file tag <name> +|-|~<fsid> :\n    add/remove/unlink a filesystem location to/from a file in the location index - attention this does not move any data!\n    unlink keeps the location in the list of deleted files e.g. the location gets a deletion request\n  file touch [<path>|fid:<fid-dec>|fxid:<fid-hex>] :\n    create a 0-size/0-replica file if <path> does not exist or update modification time of an existing file to the present time\n  file verify <path>|fid:<fid-dec>|fxid:<fid-hex> [<fsid>] [-checksum] [-commitchecksum] [-commitsize] [-rate <rate>] :\n    verify a file against the disk images\n  file verify <path|fid:<fid-dec>|fxid:<fid-hex> -resync :\n    ask all locations to resync their file md records\n    <fsid>          : verifies only the replica on <fsid>\n    -checksum       : trigger the checksum calculation during the verification process\n    -commitchecksum : commit the computed checksum to the MGM\n    -commitsize     : commit the file size to the MGM\n    -rate <rate>    : restrict the verification speed to <rate> per node\n  file version <path> [purge-version] :\n    create a new version of a file by cloning\n    <purge-version> : defines the max. number of versions to keep\n  file versions [grab-version] :\n    list versions of a file\n    grab a version [grab-version] of a file\n.. code-block:: text\n\n    if not specified it will add a new version without purging any previous version\n  file share <path> [lifetime] :\n    <path>          : path to create a share link\n    <lifetime>      : validity time of the share link like 1, 1s, 1d, 1w, 1mo, 1y, ... default is 28d\n  file workflow <path>|fid:<fid-dec>|fxid:<fid-hex> <workflow> <event> :\n    trigger workflow <workflow> with event <event> on <path>\n"
  },
  {
    "path": "doc/citrine/clicommands/newfind.rst",
    "content": "newfind\n-------\n\n.. code-block:: text\n\n  find [--name <pattern>] [--xurl] [--childcount] [--purge <n> ] [--count] [-s] [-d] [-f] [-0] [-1] [-g] [-uid <n>] [-nuid <n>] [-gid <n>] [-ngid <n>] [-flag <n>] [-nflag <n>] [-ctime +<n>|-<n>] [-m] [-x <key>=<val>] [-p <key>] [-b] [--layoutstripes <n>] <path>\n    -f -d :  find files(-f) or directories (-d) in <path>\n    --name <pattern> :  find by name or wildcard match\n    -x <key>=<val> :  find entries with <key>=<val>\n    -0 :  find 0-size files only\n    -g :  find files with mixed scheduling groups\n    -p <key> :  additionally print the value of <key> for each entry\n    -b :  query the server balance of the files found\n    -s :  run as a subcommand (in silent mode)\n    -uid <n> :  entries owned by given user id number\n    -nuid <n> :  entries not owned by given user id number\n    -gid <n> :  entries owned by given group id number\n    -ngid <n> :  entries not owned by given group id number\n    -flag <n> :  directories with specified UNIX access flag, e.g. 755\n    -nflag <n> :  directories not with specified UNIX access flag, e.g. 755\n    -ctime +<n> :  find files older than <n> days\n    -ctime -<n> :  find files younger than <n> days\n    --layoutstripes <n> :  apply new layout with <n> stripes to all files found\n    --maxdepth <n> :  descend only <n> levels\n    -1 :  find files which are at least 1 hour old\n    --stripediff :  find files which have not the nominal number of stripes(replicas)\n    --faultyacl :  find directories with illegal ACLs\n    --count :  just print global counters for files/dirs found\n    --xurl :  print the XRootD URL instead of the path name\n    --childcount :  print the number of children in each directory\n    --purge <n> | atomic\n    :  remove versioned files keeping <n> versions - to remove all old versions use --purge 0\n    To apply the settings of the extended attribute definition use <n>=-1\n    To remove all atomic upload left-overs older than a day user --purge atomic\n    default :  find files and directories\n    find [--nrep] [--nunlink] [--size] [--fileinfo] [--online] [--hosts] [--partition] [--fid] [--fs] [--checksum] [--ctime] [--mtime] [--uid] [--gid] <path>\n    :  find files and print out the requested meta data as key value pairs\n    path=file:...  :  do a find in the local file system (options ignored) - 'file:' is the current working directory\n    path=root:...  :  do a find on a plain XRootD server (options ignored) - does not work on native XRootD clusters\n    path=as3:...   :  do a find on an S3 bucket\n    path=...       :  all other paths are considered to be EOS paths!\n.. code-block:: text\n\n"
  },
  {
    "path": "doc/citrine/clicommands/node.rst",
    "content": "node\n----\n\n.. code-block:: text\n\n   usage:\n  node ls [-s] [-b|--brief] [-m|-l|--sys|--io|--fsck] [<node>] : list all nodes or only <node>. <node> is a substring match and can be a comma separated list\n  \t      -s : silent mode\n  \t      -b : display host names without domain names\n  \t      -m : monitoring key=value output format\n  \t      -l : long output - list also file systems after each node\n  \t    --io : print IO statistics\n  \t   --sys : print SYS statistics (memory + threads)\n  \t  --fsck : print filesystem check statistcis\n  node config <host:port> <key>=<value : configure file system parameters for each filesystem of this node\n  \t    <key> : gw.rate=<mb/s> - set the transfer speed per gateway transfer\n  \t    <key> : gw.ntx=<#>     - set the number of concurrent transfers for a gateway node\n  \t    <key> : error.simulation=io_read|io_write|xs_read|xs_write|fmd_open\n  \t            If offset is given the the error will get triggered for request past the given value.\n  \t            Accepted format for offset: 8B, 10M, 20G etc.\n  \t            io_read[_<offset>]  : simulate read  errors\n  \t            io_write[_<offset>] : simulate write errors\n  \t            xs_read             : simulate checksum errors when reading a file\n  \t            xs_write            : simulate checksum errors when writing a file\n  \t            fmd_open            : simulate a file metadata mismatch when opening a file\n  \t            <none>              : disable error simulation (every value than the previous ones are fine!)\n  \t    <key> : publish.interval=<sec> - set the filesystem state publication interval to <sec> seconds\n  \t    <key> : debug.level=<level> - set the node into debug level <level> [default=notice] -> see debug --help for available levels\n  \t    <key> : for other keys see help of 'fs config' for details\n  node set <queue-name>|<host:port> on|off                 : activate/deactivate node\n  node rm  <queue-name>|<host:port>                        : remove a node\n  node register <host:port|*> <path2register> <space2register> [--force] [--root] : register filesystems on node <host:port>\n  \t      <path2register> is used as match for the filesystems to register e.g. /data matches filesystems /data01 /data02 etc. ... /data/ registers all subdirectories in /data/\n  \t      <space2register> is formed as <space>:<n> where <space> is the space name and <n> must be equal to the number of filesystems which are matched by <path2register> e.g. data:4 or spare:22 ...\n  \t      --force : removes any existing filesystem label and re-registers\n  \t      --root  : allows to register paths on the root partition\n  node txgw <queue-name>|<host:port> <on|off> : enable (on) or disable (off) node as a transfer gateway\n  node proxygroupadd <group-name> <queue-name>|<host:port> : add a node to a proxy group\n  node proxygrouprm <group-name> <queue-name>|<host:port> : rm a node from a proxy group\n  node proxygroupclear <queue-name>|<host:port> : clear the list of groups a node belongs to\n  node status <queue-name>|<host:port> : print's all defined variables for a node\n"
  },
  {
    "path": "doc/citrine/clicommands/ns.rst",
    "content": "ns\n--\n\n.. code-block:: text\n\n  ns [stat|mutex|compact|master|cache]\n    print or configure basic namespace parameters\n    ns stat [-a] [-m] [-n] [--reset]\n    print namespace statistics\n    -a      : break down by uid/gid\n    -m      : display in monitoring format <key>=<value>\n    -n      : display numerical uid/gid(s)\n    --reset : reset namespace counters\n.. code-block:: text\n\n    ns mutex [<option>]\n    manage mutex monitoring. Option can be:\n    --toggletime     : toggle the timing\n    --toggleorder    : toggle the order\n    --toggledeadlock : toggle deadlock check\n    --smplrate1      : set timing sample rate at 1% (default, no slow-down)\n    --smplrate10     : set timing sample rate at 10% (medium slow-down)\n    --smplrate100    : set timing sample rate at 100% (severe slow-down)\n    ns compact off|on <delay> [<interval>] [<type>]\n    enable online compaction after <delay> seconds\n    <interval> : if >0 then compaction is repeated automatically\n    after so many seconds\n    <type>     : can be 'files', 'directories' or 'all'. By default  only the file\n    changelog is compacted. The repair flag can be indicated by using\n    'files-repair', 'directories-repair' or 'all-repair'.\n    ns master [<option>]\n    master/slave operations. Option can be:\n    <master_hostname> : set hostname of MGM master RW daemon\n    --log             : show master log\n    --log-clear       : clean master log\n    --enable          : enable the slave/master supervisor thread modifying stall/\n    redirectorion rules\n    --disable         : disable supervisor thread\n    ns recompute_tree_size <path>|cid:<decimal_id>|cxid:<hex_id> [--depth <val>]\n    recompute the tree size of a directory and all its subdirectories\n    --depth : maximum depth for recomputation, default 0 i.e no limit\n    ns recompute_quotanode <path>|cid:<decimal_id>|cxid:<hex_id>\n    recompute the specified quotanode\n    ns cache set|drop [-d|-f] [<max_num>] [<max_size>K|M|G...]\n    set the max number of entries or the max size of the cache. Use the\n    ns stat command to see the current values.\n    set        : update cache size for files or directories\n    drop       : drop cached file and/or directory entries\n    -d         : control the directory cache\n    -f         : control the file cache\n    <max_num>  : max number of entries\n    <max_size> : max size of the cache - not implemented yet\n    ns cache drop-single-file <id of file to drop>\n    force refresh of the given FileMD by dropping it from the cache\n    ns cache drop-single-container <id of container to drop>\n    force refresh of the given ContainerMD by dropping it from the cache\n    ns max_drain_threads <num>\n    set the max number of threads in the drain pool, default 400, minimum 4\n    ns reserve-ids <file id> <container id>\n    blacklist file and container IDs below the given threshold. The namespace\n    will not allocate any file or container with IDs less than, or equal to the\n    given blacklist thresholds.\n  \n"
  },
  {
    "path": "doc/citrine/clicommands/pointq.rst",
    "content": ".q\n--\n\n.. code-block:: text\n\n  .q :                   Exit from EOS console"
  },
  {
    "path": "doc/citrine/clicommands/pwd.rst",
    "content": "pwd\n---\n\n.. code-block:: text\n\n  pwd :                  Print working directory"
  },
  {
    "path": "doc/citrine/clicommands/question.rst",
    "content": "?\n-\n\n.. code-block:: text\n\n  ? :                    Synonym for 'help'"
  },
  {
    "path": "doc/citrine/clicommands/quit.rst",
    "content": "quit\n----\n\n.. code-block:: text\n\n  quit :                 Exit from EOS console"
  },
  {
    "path": "doc/citrine/clicommands/quota.rst",
    "content": "quota\n-----\n\n.. code-block:: text\n\n  Usage:\n    quota [<path>]                                                       : show personal quota for all or only the quota node responsible for <path>\n    quota ls [-n] [-m] [-u <uid>] [-g <gid>] [[-p] <path>]               : list configured quota and quota node(s)\n    quota rm -u <uid>|-g <gid> [-v] [-i] [[-p] <path>]                   : remove configured quota type(s) for uid/gid in path\n    quota rmnode [-p] <path>                                             : remove quota node and every defined quota on that node\n    quota set -u <uid>|-g <gid> [-v <bytes>] [-i <inodes>] [[-p] <path>] : set volume and/or inode quota by uid or gid\n    General options:\n    -m : print information in monitoring <key>=<value> format\n    -n : don't translate ids, print uid and gid number\n    -u/--uid <uid> : print information only for uid <uid>\n    -g/--gid <gid> : print information only for gid <gid>\n    -p/--path <path> : print information only for path <path> - this can also be given without -p or --path\n    -v/--volume <bytes> : refer to volume limit in <bytes>\n    -i/--inodes <inodes> : refer to inode limit in number of <inodes>\n    Notes:\n    => you have to specify either the user or the group identified by the unix id or the user/group name; for quotas listed as 'project' that apply to all users, use --gid 99\n    => the space argument is by default assumed as 'default'\n    => you have to specify at least a volume or an inode limit to set quota\n    => for convenience all commands can just use <path> as last argument omitting the -p|--path e.g. quota ls /eos/ ...\n    => if <path> is not terminated with a '/' it is assumed to be a file so it won't match the quota node with <path>/ !\n"
  },
  {
    "path": "doc/citrine/clicommands/reconnect.rst",
    "content": "reconnect\n---------\n\n.. code-block:: text\n\n  usage: reconnect [gsi,krb5,unix,sss]                                    :  reconnect to the management node [using the specified protocol]\n"
  },
  {
    "path": "doc/citrine/clicommands/recycle.rst",
    "content": "recycle\n-------\n\n.. code-block:: text\n\n  recycle [ls|purge|restore|config ...]\n    provides recycle bin functionality\n    recycle [-m]\n    print status of recycle bin and config status if executed by root\n    -m     : display info in monitoring format\n.. code-block:: text\n\n    recycle ls [-g|<date>] [-m] [-n]\n    list files in the recycle bin\n    -g     : list files of all users (if done by root or admin)\n    <date> : can be <year>, <year>/<month> or <year>/<month>/<day>\n    e.g.: recycle ls 2018/08/12\n    -m     : display info in monitoring format\n    -n     : display numeric uid/gid(s) instead of names\n    recycle purge [-g|<date>]\n    purge files in the recycle bin\n    -g     : empties the recycle bin of all users (if done by root or admin)\n    <date> : can be <year>, <year>/<month> or <year>/<month>/<day>\n    recycle restore [-f|--force-original-name] [-r|--restore-versions] <recycle-key>\n    undo the deletion identified by the <recycle-key>\n    -f : move deleted files/dirs back to their original location (otherwise\n    the key entry will have a <.inode> suffix)\n    -r : restore all previous versions of a file\n    recycle config [--add-bin|--remove-bin] <sub-tree>\n    --add-bin    : enable recycle bin for deletions in <sub-tree>\n    --remove-bin : disable recycle bin for deletions in <sub-tree>\n    recycle config --lifetime <seconds>\n    configure FIFO lifetime for the recycle bin\n    recycle config --ratio <0..1.0>\n    configure the volume/inode keep ratio. E.g: 0.8 means files will only\n    be recycled if more than 80% of the volume/inodes quota is used. The\n    low watermark is by default 10% below the given ratio.\n    recycle config --size <value>[K|M|G]\n    configure the quota for the maximum size of the recycle bin.\n    If no unit is set explicitly then we assume bytes.\n    recycle config --inodes <value>[K|M|G]\n    configure the quota for the maximum number of inodes in the recycle\n    bin.\n"
  },
  {
    "path": "doc/citrine/clicommands/rm.rst",
    "content": "rm\n--\n\n.. code-block:: text\n\n  rm [-r|-rf|-rF] [--no-recycle-bin|-F] [<path>|fid:<fid-dec>|fxid:<fid-hex>|cid:<cid-dec>|cxid:<cid-hex>]\n    -r | -rf : remove files/directories recursively\n    - the 'f' option is a convenience option with no additional functionality!\n    - the recursive flag is automatically removed it the target is a file!\n.. code-block:: text\n\n   --no-recycle-bin|-F : remove bypassing recycling policies\n    - you have to take the root role to use this flag!\n    -rF | Fr : remove files/directories recursively bypassing recycling policies\n    - you have to take the root role to use this flag!\n    - the recursive flag is automatically removed it the target is a file!\n"
  },
  {
    "path": "doc/citrine/clicommands/rmdir.rst",
    "content": "rmdir\n-----\n\n.. code-block:: text\n\n  usage: rmdir <path>                                                   :  remote directory <path>\n"
  },
  {
    "path": "doc/citrine/clicommands/role.rst",
    "content": "role\n----\n\n.. code-block:: text\n\n  usage: role <user-role> [<group-role>]                       : select user role <user-role> [and group role <group-role>]\n    <user-role> can be a virtual user ID (unsigned int) or a user mapping alias\n    <group-role> can be a virtual group ID (unsigned int) or a group mapping alias\n"
  },
  {
    "path": "doc/citrine/clicommands/route.rst",
    "content": "route\n-----\n\n.. code-block:: text\n\n  route [ls|link|unlink]\n    namespace routing to redirect clients to external instances\n.. code-block:: text\n\n    route ls [<path>]\n    list all routes or the one matching for the given path\n    * as the first character means the node is a master\n    _ as the first character means the node is offline\n    route link <path> <dst_host>[:<xrd_port>[:<http_port>]],...\n    create routing from <path> to destination host. If the xrd_port\n    is omitted the default 1094 is used, if the http_port is omitted\n    the default 8000 is used. Several dst_hosts can be specified by\n    separating them with \",\". The redirection will go to the MGM\n    from the specified list\n    e.g route /eos/dummy/ foo.bar:1094:8000\n    route unlink <path>\n    remove routing matching path\n"
  },
  {
    "path": "doc/citrine/clicommands/rtlog.rst",
    "content": "rtlog\n-----\n\n.. code-block:: text\n\n  rtlog :                Get realtime log output from mgm & fst servers"
  },
  {
    "path": "doc/citrine/clicommands/silent.rst",
    "content": "silent\n------\n\n.. code-block:: text\n\n  silent :               Toggle silent flag for stdout"
  },
  {
    "path": "doc/citrine/clicommands/space.rst",
    "content": "space\n-----\n\n.. code-block:: text\n\n   usage:\n  space ls [-s|-g <depth>] [-m|-l|--io|--fsck] [<space>] : list in all spaces or select only <space>. <space> is a substring match and can be a comma separated list\n  \t      -s : silent mode\n  \t      -m : monitoring key=value output format\n  \t      -l : long output - list also file systems after each space\n  \t      -g : geo output - aggregate space information along the instance geotree down to <depth>\n  \t    --io : print IO statistics\n  \t  --fsck : print filesystem check statistics\n  space config <space-name> space.nominalsize=<value>                   : configure the nominal size for this space\n  space config <space-name> space.balancer=on|off                       : enable/disable the space balancer [ default=off ]\n  space config <space-name> space.balancer.threshold=<percent>          : configure the used bytes deviation which triggers balancing             [ default=20 (%%)     ] \n  space config <space-name> space.balancer.node.rate=<MB/s>             : configure the nominal transfer bandwidth per running transfer on a node [ default=25 (MB/s)   ]\n  space config <space-name> space.balancer.node.ntx=<#>                 : configure the number of parallel balancing transfers per node           [ default=2 (streams) ]\n  space config <space-name> space.converter=on|off                      : enable/disable the space converter [ default=off ]\n  space config <space-name> space.converter.ntx=<#>                     : configure the number of parallel conversions per space                  [ default=2 (streams) ]\n  space config <space-name> space.drainer.node.rate=<MB/s >             : configure the nominal transfer bandwidth per running transfer on a node [ default=25 (MB/s)   ]\n  space config <space-name> space.drainer.node.ntx=<#>                  : configure the number of parallel draining transfers per node            [ default=2 (streams) ]\n  space config <space-name> space.drainer.node.nfs=<#>                  : configure the number of max draining filesystems per node (Valid only for central drain)  [ default=5 ]\n  space config <space-name> space.drainer.retries=<#>                   : configure the number of retry for the draining process (Valid only for central drain)     [ default=1 ]\n  space config <space-name> space.drainer.fs.ntx=<#>                    : configure the number of parallel draining transfers per fs (Valid only for central drain) [ default=5 ]\n  space config <space-name> space.groupbalancer=on|off                  : enable/disable the group balancer [ default=off ]\n  space config <space-name> space.groupbalancer.ntx=<ntx>               : configure the number of parallel group balancer jobs [ default=0 ]\n  space config <space-name> space.groupbalancer.threshold=<threshold>   : configure the threshold when a group is balanced [ default=0 ] ( taken from dev(filled) parameter in 'group ls'\n  space config <space-name> space.geobalancer=on|off                    : enable/disable the geo balancer [ default=off ]\n  space config <space-name> space.geobalancer.ntx=<ntx>                 : configure the number of parallel geobalancer jobs [ default=0 ]\n  space config <space-name> space.geobalancer.threshold=<threshold>     : configure the threshold when a geotag is balanced [ default=0 ] \n  space config <space-name> space.lru=on|off                            : enable/disable the LRU policy engine [ default=off ]\n  space config <space-name> space.lru.interval=<sec>                    : configure the default lru scan interval\n  space config <space-name> space.wfe=on|off|paused                     : enable/disable the Workflow Engine [ default=off ]\n  space config <space-name> space.wfe.interval=<sec>                    : configure the default WFE scan interval\n  space config <space-name> space.headroom=<size>                       : configure the default disk headroom if not defined on a filesystem (see fs for details)\n  space config <space-name> space.scaninterval=<sec>                    : configure the default scan interval if not defined on a filesystem (see fs for details)\n  space config <space-name> space.scanrate=<MB/S>                       : configure the default scan rate if not defined on a filesystem     (see fs for details)\n  space config <space-name> space.drainperiod=<sec>                     : configure the default drain  period if not defined on a filesystem (see fs for details)\n  space config <space-name> space.graceperiod=<sec>                     : configure the default grace  period if not defined on a filesystem (see fs for details)\n  space config <space-name> space.filearchivedgc=on|off                 : enable/disable the 'file archived' garbage collector [ default=off ]\n  space config <space-name> space.tracker=on|off                        : enable/disable the space layout creation tracker [ default=off ]\n  space config <space-name> space.inspector=on|off                      : enable/disable the file inspector [ default=off ]\n  space config <space-name> space.autorepair=on|off                     : enable auto-repair of faulty replica's/files (the converter has to be enabled too)\n    => size can be given also like 10T, 20G, 2P ... (without space before the unit)\n  space config <space-name> space.geo.access.policy.write.exact=on|off  : if 'on' use exact matching geo replica (if available), 'off' uses weighting [ for write case ]\n  space config <space-name> space.geo.access.policy.read.exact=on|off   : if 'on' use exact matching geo replica (if available), 'off' uses weighting [ for read  case ]\n  space config <space-name> fs.<key>=<value>                            : configure file system parameters for each filesystem in this space (see help of 'fs config' for details)\n  space config <space-name> space.policy.[layout|nstripes|checksum|blockchecksum|blocksize]=<value>      \n    : configure default file layout creation settings as a space policy - a value='remove' deletes the space policy\n  space config <space-name> space.policy.recycle=on\n    : globally enforce using always a recycle bin\n  space define <space-name> [<groupsize> [<groupmod>]] : define how many filesystems can end up in one scheduling group <groupsize> [ default=0 ]\n    => <groupsize>=0 means that no groups are built within a space, otherwise it should be the maximum number of nodes in a scheduling group\n    => <groupmod> maximum number of groups in the space, which should be at least equal to the maximum number of filesystems per node\n  space inspector [--current|-c] [--last|-l] [-m] [-p] [-e] : show namespace inspector output\n  \t  -c  : show current scan\n  \t  -l  : show last complete scan\n  \t  -m  : print last scan in monitoring format\n  \t  -p  : combined with -c or -l lists erroneous files\n  \t  -e  : combined with -c or -l exports erroneous files on the MGM into /var/log/eos/mgm/FileInspector.<date>.list\n  space node-set <space-name> <node.key> <file-name> : store the contents of <file-name> into the node configuration variable <node.key> visible to all FSTs\n    => if <file-name> matches file:<path> the file is loaded from the MGM and not from the client\n    => local files cannot exceed 512 bytes - MGM files can be arbitrary length\n    => the contents gets base64 encoded by default\n  space node-get <space-name> <node.key> : get the value of <node.key> and base64 decode before output\n    => if the value for <node.key> is identical for all nodes in the referenced space, it is dumped only once, otherwise the value is dumped for each node separately\n  space reset <space-name> [--egroup|mapping|drain|scheduledrain|schedulebalance|ns|nsfilesystemview|nsfilemap|nsdirectorymap] : reset different space attributes\n  \t            --egroup : clear cached egroup information\n  \t           --mapping : clear all user/group uid/gid caches\n  \t             --drain : reset draining\n  \t     --scheduledrain : reset drain scheduling map\n  \t   --schedulebalance : reset balance scheduling map\n  \t                --ns : resize all namespace maps\n  \t  --nsfilesystemview : resize namespace filesystem view\n  \t         --nsfilemap : resize namespace file map\n  \t    --nsdirectorymap : resize namespace directory map\n  space status <space-name> [-m] : print all defined variables for space\n  space tracker : print all file replication tracking entries\n  space set <space-name> on|off : enable/disable all groups under that space\n    => <on> value will enable all nodes, <off> value won't affect nodes\n  space rm <space-name> : remove space\n  space quota <space-name> on|off : enable/disable quota\n"
  },
  {
    "path": "doc/citrine/clicommands/squash.rst",
    "content": "squash\n------\n\n.. code-block:: text\n\n  usage: squash new <path>                                                  : create a new squashfs under <path>\n    squash pack [-f] <path>                                            : pack a squashfs image\n    -f will recreate the package but keeps the symbolic link locally\n    squash unpack [-f] <path>                                          : unpack a squashfs image for modification\n    -f will atomically update the local package\n    squash info <path>                                                 : squashfs information about <path>\n    squash rm <path>                                                   : delete a squashfs attached image and its smart link\n    squash relabel <path>                                              : relabel a squashfs image link e.g. after an image move in the namespace\n    squash roll <path>                                                 : will create a squash package from the EOS directory pointed by <path\n    squash unroll <path>                                               : will store the squash package contents unpacked into the EOS package directory\n    squash install --curl=https://<package>.tgz|.tar.gz <path>         : create a squashfs package from a web archive under <path>\n"
  },
  {
    "path": "doc/citrine/clicommands/stat.rst",
    "content": "stat\n----\n\n.. code-block:: text\n\n  usage: stat [-f|-d]    <path>                                                  :  stat <path>\n    -f : checks if <path> is a file\n    -d : checks if <path> is a directory\n"
  },
  {
    "path": "doc/citrine/clicommands/test.rst",
    "content": "test\n----\n\n.. code-block:: text\n\n  usage: test [mkdir|rmdir|ls|lsla <N> ]                                             :  run performance test\n"
  },
  {
    "path": "doc/citrine/clicommands/timing.rst",
    "content": "timing\n------\n\n.. code-block:: text\n\n  timing :               Toggle timing flag for execution time measurement"
  },
  {
    "path": "doc/citrine/clicommands/token.rst",
    "content": "token\n-----\n\n.. code-block:: text\n\n  token --token  <token> | --path <path> --expires <expires> [--permission <perm>] [--owner <owner>] [--group <group>] [--tree] [--origin <origin1> [--origin <origin2>] ...]] \n    get or show a token\n.. code-block:: text\n\n    token --token <token>\n    : provide a JSON dump of a token - independent of validity\n    --path <path>                 : define the namespace restriction - if ending with '/' this is a directory or tree, otherwise it references a file\n    --permission <perm>           : define the token bearer permissions e.g 'rx' 'rwx' 'rwx!d' 'rwxq' - see acl command for permissions\n    --owner <owner>               : identify the bearer with as user <owner>\n    --group <group>               : identify the bearer with a group <group>\n    --tree                        : request a subtree token granting permissions for the whole tree under <path>\n    --origin <origin>            : restrict token usage to <origin> - multiple origin parameters can be provided\n    <origin> := <regexp:hostname>:<regex:username>:<regex:protocol>\n    - described by three regular extended expressions matching the\n    bearers hostname, possible authenticated name and protocol\n    - default is *.*.*\n  Examples:\n    eos token --path /eos/ --permission rx --tree\n    : token with browse permission for the whole /eos/ tree\n    eos token --path /eos/file --permission rwx --owner foo --group bar\n    : token granting write permission for /eos/file as user foo:bar\n    eos token --token zteos64:...\n    : dump the given token\n  \n"
  },
  {
    "path": "doc/citrine/clicommands/touch.rst",
    "content": "touch\n-----\n\n.. code-block:: text\n\n  touch :                Touch a file"
  },
  {
    "path": "doc/citrine/clicommands/tracker.rst",
    "content": "tracker\n-------\n\n.. code-block:: text\n\n   usage:\n  space ls [-s|-g <depth>] [-m|-l|--io|--fsck] [<space>] : list in all spaces or select only <space>. <space> is a substring match and can be a comma separated list\n  \t      -s : silent mode\n  \t      -m : monitoring key=value output format\n  \t      -l : long output - list also file systems after each space\n  \t      -g : geo output - aggregate space information along the instance geotree down to <depth>\n  \t    --io : print IO statistics\n  \t  --fsck : print filesystem check statistics\n  space config <space-name> space.nominalsize=<value>                   : configure the nominal size for this space\n  space config <space-name> space.balancer=on|off                       : enable/disable the space balancer [ default=off ]\n  space config <space-name> space.balancer.threshold=<percent>          : configure the used bytes deviation which triggers balancing             [ default=20 (%%)     ] \n  space config <space-name> space.balancer.node.rate=<MB/s>             : configure the nominal transfer bandwidth per running transfer on a node [ default=25 (MB/s)   ]\n  space config <space-name> space.balancer.node.ntx=<#>                 : configure the number of parallel balancing transfers per node           [ default=2 (streams) ]\n  space config <space-name> space.converter=on|off                      : enable/disable the space converter [ default=off ]\n  space config <space-name> space.converter.ntx=<#>                     : configure the number of parallel conversions per space                  [ default=2 (streams) ]\n  space config <space-name> space.drainer.node.rate=<MB/s >             : configure the nominal transfer bandwidth per running transfer on a node [ default=25 (MB/s)   ]\n  space config <space-name> space.drainer.node.ntx=<#>                  : configure the number of parallel draining transfers per node            [ default=2 (streams) ]\n  space config <space-name> space.drainer.node.nfs=<#>                  : configure the number of max draining filesystems per node (Valid only for central drain)  [ default=5 ]\n  space config <space-name> space.drainer.retries=<#>                   : configure the number of retry for the draining process (Valid only for central drain)     [ default=1 ]\n  space config <space-name> space.drainer.fs.ntx=<#>                    : configure the number of parallel draining transfers per fs (Valid only for central drain) [ default=5 ]\n  space config <space-name> space.groupbalancer=on|off                  : enable/disable the group balancer [ default=off ]\n  space config <space-name> space.groupbalancer.ntx=<ntx>               : configure the number of parallel group balancer jobs [ default=0 ]\n  space config <space-name> space.groupbalancer.threshold=<threshold>   : configure the threshold when a group is balanced [ default=0 ] ( taken from dev(filled) parameter in 'group ls'\n  space config <space-name> space.geobalancer=on|off                    : enable/disable the geo balancer [ default=off ]\n  space config <space-name> space.geobalancer.ntx=<ntx>                 : configure the number of parallel geobalancer jobs [ default=0 ]\n  space config <space-name> space.geobalancer.threshold=<threshold>     : configure the threshold when a geotag is balanced [ default=0 ] \n  space config <space-name> space.lru=on|off                            : enable/disable the LRU policy engine [ default=off ]\n  space config <space-name> space.lru.interval=<sec>                    : configure the default lru scan interval\n  space config <space-name> space.wfe=on|off|paused                     : enable/disable the Workflow Engine [ default=off ]\n  space config <space-name> space.wfe.interval=<sec>                    : configure the default WFE scan interval\n  space config <space-name> space.headroom=<size>                       : configure the default disk headroom if not defined on a filesystem (see fs for details)\n  space config <space-name> space.scaninterval=<sec>                    : configure the default scan interval if not defined on a filesystem (see fs for details)\n  space config <space-name> space.scanrate=<MB/S>                       : configure the default scan rate if not defined on a filesystem     (see fs for details)\n  space config <space-name> space.drainperiod=<sec>                     : configure the default drain  period if not defined on a filesystem (see fs for details)\n  space config <space-name> space.graceperiod=<sec>                     : configure the default grace  period if not defined on a filesystem (see fs for details)\n  space config <space-name> space.filearchivedgc=on|off                 : enable/disable the 'file archived' garbage collector [ default=off ]\n  space config <space-name> space.tracker=on|off                        : enable/disable the space layout creation tracker [ default=off ]\n  space config <space-name> space.inspector=on|off                      : enable/disable the file inspector [ default=off ]\n  space config <space-name> space.autorepair=on|off                     : enable auto-repair of faulty replica's/files (the converter has to be enabled too)\n    => size can be given also like 10T, 20G, 2P ... (without space before the unit)\n  space config <space-name> space.geo.access.policy.write.exact=on|off  : if 'on' use exact matching geo replica (if available), 'off' uses weighting [ for write case ]\n  space config <space-name> space.geo.access.policy.read.exact=on|off   : if 'on' use exact matching geo replica (if available), 'off' uses weighting [ for read  case ]\n  space config <space-name> fs.<key>=<value>                            : configure file system parameters for each filesystem in this space (see help of 'fs config' for details)\n  space config <space-name> space.policy.[layout|nstripes|checksum|blockchecksum|blocksize]=<value>      \n    : configure default file layout creation settings as a space policy - a value='remove' deletes the space policy\n  space config <space-name> space.policy.recycle=on\n    : globally enforce using always a recycle bin\n  space config default taperestapi.status=on|off                        : enable/disable the tape REST API handler [ default=off ]\n  space config default taperestapi.stage=on|off                         : enable/disable the tape REST API STAGE resource [ default=off ]\n  space define <space-name> [<groupsize> [<groupmod>]] : define how many filesystems can end up in one scheduling group <groupsize> [ default=0 ]\n    => <groupsize>=0 means that no groups are built within a space, otherwise it should be the maximum number of nodes in a scheduling group\n    => <groupmod> maximum number of groups in the space, which should be at least equal to the maximum number of filesystems per node\n  space inspector [--current|-c] [--last|-l] [-m] [-p] [-e] : show namespace inspector output\n  \t  -c  : show current scan\n  \t  -l  : show last complete scan\n  \t  -m  : print last scan in monitoring format\n  \t  -p  : combined with -c or -l lists erroneous files\n  \t  -e  : combined with -c or -l exports erroneous files on the MGM into /var/log/eos/mgm/FileInspector.<date>.list\n  space node-set <space-name> <node.key> <file-name> : store the contents of <file-name> into the node configuration variable <node.key> visible to all FSTs\n    => if <file-name> matches file:<path> the file is loaded from the MGM and not from the client\n    => local files cannot exceed 512 bytes - MGM files can be arbitrary length\n    => the contents gets base64 encoded by default\n  space node-get <space-name> <node.key> : get the value of <node.key> and base64 decode before output\n    => if the value for <node.key> is identical for all nodes in the referenced space, it is dumped only once, otherwise the value is dumped for each node separately\n  space reset <space-name> [--egroup|mapping|drain|scheduledrain|schedulebalance|ns|nsfilesystemview|nsfilemap|nsdirectorymap] : reset different space attributes\n  \t            --egroup : clear cached egroup information\n  \t           --mapping : clear all user/group uid/gid caches\n  \t             --drain : reset draining\n  \t     --scheduledrain : reset drain scheduling map\n  \t   --schedulebalance : reset balance scheduling map\n  \t                --ns : resize all namespace maps\n  \t  --nsfilesystemview : resize namespace filesystem view\n  \t         --nsfilemap : resize namespace file map\n  \t    --nsdirectorymap : resize namespace directory map\n  space status <space-name> [-m] : print all defined variables for space\n  space tracker : print all file replication tracking entries\n  space set <space-name> on|off : enable/disable all groups under that space\n    => <on> value will enable all nodes, <off> value won't affect nodes\n  space rm <space-name> : remove space\n  space quota <space-name> on|off : enable/disable quota\n"
  },
  {
    "path": "doc/citrine/clicommands/transfer.rst",
    "content": "transfer\n--------\n\n.. code-block:: text\n\n  transfer submit|cancel|ls|enable|disable|reset|clear|resubmit|log ..'[eos] transfer ..' provides the transfer interface of EOS.\n  Options:\n  transfer submit [--rate=<rate>] [--streams=<#>] [--group=<groupname>] [--sync] [--noauth] <URL1> <URL2> :\n    transfer a file from URL1 to URL2\n    <URL> can be root://<host>/<path> or a local path /eos/...\n    --rate          : limit the transfer rate to <rate>\n    --streams       : use <#> parallel streams\n.. code-block:: text\n\n    --group         : set the group name for this transfer\n    --noauth        : disable authentication protocol enforcement for the copy job\n  transfer cancel <id>|--group=<groupname>\n    cancel transfer with ID <id> or by group <groupname>\n    <id>=*          : cancel all transfers (only root can do that)\n  transfer ls [-a] [-m] [s] [--group=<groupname>] [id] \n    -a              : list all transfers not only of the current role\n    -m              : list all transfers in monitoring format (key-val pairs)\n    -s              : print transfer summary\n    --group         : list all transfers in this group\n    --sync          : follow the transfer in interactive mode (like interactive third party 'cp')\n    <id>            : id of the transfer to list\n  transfer enable\n    : start the transfer engine (you have to be root to do that)\n  transfer disable\n    : stop the transfer engine (you have to be root to do that)\n  transfer reset [<id>|--group=<groupname>]\n    : reset all transfers to 'inserted' state (you have to be root to do that)\n  transfer clear \n    : clear's the transfer database (you have to be root to do that)\n  transfer resubmit <id> [--group=<groupname>]\n    : resubmit's a transfer\n  transfer kill <id>|--group=<groupname>\n    : kill a running transfer\n  transfer purge [<id>|--group=<groupname>]\n    : remove 'failed' transfers from the transfer queue by id, group or all if not specified\n"
  },
  {
    "path": "doc/citrine/clicommands/version.rst",
    "content": "version\n-------\n\n.. code-block:: text\n\n  version :              Verbose client/server version"
  },
  {
    "path": "doc/citrine/clicommands/vid.rst",
    "content": "vid\n---\n\n.. code-block:: text\n\n  usage: vid ls [-u] [-g] [-s] [-U] [-G] [-g] [-a] [-l] [-n] : list configured policies\n    -u : show only user role mappings\n    -g : show only group role mappings\n    -s : show list of sudoers\n    -U : show user  alias mapping\n    -G : show group alias mapping\n    -y : show configured gateways\n    -a : show authentication\n    -N : show maximum anonymous (nobody) access level deepness - the tree deepness where unauthenticated access is possible (default is 1024)\n    -l : show geo location mapping\n    -n : show numerical ids instead of user/group names\n    vid set membership <uid> -uids [<uid1>,<uid2>,...]\n    vid set membership <uid> -gids [<gid1>,<gid2>,...]\n    vid rm membership <uid>             : delete the membership entries for <uid>.\n    vid set membership <uid> [+|-]sudo\n    vid set map -krb5|-gsi|-https|-sss|-unix|-tident|-voms|-grpc|-oauth2 <pattern> [vuid:<uid>] [vgid:<gid>]\n    -voms <pattern>  : <pattern> is <group>:<role> e.g. to map VOMS attribute /dteam/cern/Role=NULL/Capability=NULL one should define <pattern>=/dteam/cern:\n    -sss key:<key>  : <key> has to be defined on client side via 'export XrdSecsssENDORSEMENT=<key>'\n    -grpc key:<key> : <key> has to be added to the relevant GRPC request in the field 'authkey'\n    -oauth2 key:<oauth-resource> : <oauth-resource> describes the OAUTH resource endpoint to translate OAUTH tokens to user identities\n    vid set geotag <IP-prefix> <geotag>  : add to all IP's matching the prefix <prefix> the geo location tag <geotag>\n    N.B. specify the default assumption via 'vid set geotag default <default-tag>'\n    vid rm <key>                         : remove configured vid with name key - hint: use config dump to see the key names of vid rules\n    vid enable|disable krb5|gsi|sss|unix|https|grpc|oauth2\n    : enable/disables the default mapping via password or external database\n    vid add|remove gateway <hostname> [krb5|gsi|sss|unix|https|grpc]\n    : adds/removes a host as a (fuse) gateway with 'su' privileges\n    [<prot>] restricts the gateway role change to the specified authentication method\n    vid publicaccesslevel <level>\n    : sets the deepest directory level where anonymous access (nobody) is possible\n"
  },
  {
    "path": "doc/citrine/clicommands/who.rst",
    "content": "who\n---\n\n.. code-block:: text\n\n  usage: who [-c] [-n] [-z] [-a] [-m] [-s]                             :  print statistics about active users (idle<5min)\n    -c                                                   -  break down by client host\n    -n                                                   -  print id's instead of names\n    -z                                                   -  print auth protocols\n    -a                                                   -  print all\n    -s                                                   -  print summary for clients\n    -m                                                   -  print in monitoring format <key>=<value>\n"
  },
  {
    "path": "doc/citrine/clicommands/whoami.rst",
    "content": "whoami\n------\n\n.. code-block:: text\n\n  whoami :               Determine how we are mapped on server side"
  },
  {
    "path": "doc/citrine/clicommands.rst",
    "content": ".. _clientcommands:\n\nClient Commands\n================\n\n.. toctree::\n  :maxdepth: 2\n\n  clicommands/pointq\n  clicommands/question\n  clicommands/accounting\n  clicommands/acl\n  clicommands/archive\n  clicommands/attr\n  clicommands/backup\n  clicommands/cd\n  clicommands/chmod\n  clicommands/chown\n  clicommands/clear\n  clicommands/config\n  clicommands/console\n  clicommands/cp\n  clicommands/debug\n  clicommands/evict\n  clicommands/exit\n  clicommands/file\n  clicommands/fileinfo\n  clicommands/find\n  clicommands/fs\n  clicommands/fsck\n  clicommands/fuse\n  clicommands/fusex\n  clicommands/geosched\n  clicommands/group\n  clicommands/health\n  clicommands/help\n  clicommands/info\n  clicommands/inspector\n  clicommands/io\n  clicommands/json\n  clicommands/license\n  clicommands/ln\n  clicommands/ls\n  clicommands/map\n  clicommands/member\n  clicommands/mkdir\n  clicommands/motd\n  clicommands/mv\n  clicommands/newfind\n  clicommands/node\n  clicommands/ns\n  clicommands/pwd\n  clicommands/quit\n  clicommands/quota\n  clicommands/reconnect\n  clicommands/recycle\n  clicommands/rm\n  clicommands/rmdir\n  clicommands/role\n  clicommands/route\n  clicommands/rtlog\n  clicommands/silent\n  clicommands/space\n  clicommands/squash\n  clicommands/stat\n  clicommands/test\n  clicommands/timing\n  clicommands/token\n  clicommands/touch\n  clicommands/tracker\n  clicommands/transfer\n  clicommands/version\n  clicommands/vid\n  clicommands/who\n  clicommands/whoami\n"
  },
  {
    "path": "doc/citrine/conf.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# complexity documentation build configuration file, created by\n# sphinx-quickstart on Tue Jul  9 22:26:36 2013.\n#\n# This file is execfile()d with the current directory set to its containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport sys, os\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#sys.path.insert(0, os.path.abspath('.'))\n\n# Get the project root dir, which is the parent dir of this\ncwd = os.getcwd()\nproject_root = os.path.dirname(cwd)\n\n# Insert the project root dir as the first element in the PYTHONPATH.\n# This lets us ensure that the source package is imported, and that its\n# version is used.\nsys.path.insert(0, project_root)\n\nimport solar_theme\n\n# -- General configuration -----------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be extensions\n# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_themes']\n\n# The suffix of source filenames.\nsource_suffix = '.rst'\n\n# The encoding of source files.\n#source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = u'EOS'\ncopyright = u'2020, CERN'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = \"5.1.15\"\nrelease = \"DIOPSIDE\"\n\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\n#today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = ['_build']\n\n# The reST default role (used for this markup: `text`) to use for all documents.\n#default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n#modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built documents.\n#keep_warnings = False\n\n\n# -- Options for HTML output ---------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = 'solar_theme'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#html_theme_options = {}\n\n# Add any paths that contain custom themes here, relative to this directory.\nhtml_theme_path = [solar_theme.theme_path]\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n#html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\nhtml_logo = \"_static/EOS_logo.jpg\"\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\nhtml_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n#html_domain_indices = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n#html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\nhtml_show_sphinx = False\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\nhtml_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'solardoc'\n\n\n# -- Options for LaTeX output --------------------------------------------------\n\nlatex_elements = {\n# The paper size ('letterpaper' or 'a4paper').\n#'papersize': 'letterpaper',\n\n# The font size ('10pt', '11pt' or '12pt').\n#'pointsize': '10pt',\n\n# Additional stuff for the LaTeX preamble.\n#'preamble': '',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title, author, documentclass [howto/manual]).\nlatex_documents = [\n  ('index', 'eos.tex', u'EOS Documentation',\n   u'CERN', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#latex_use_parts = False\n\n# If true, show page references after internal links.\n#latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_domain_indices = True\n\n\n# -- Options for manual page output --------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    ('index', 'eos', u'EOS Documentation',\n     [u'CERN'], 1)\n]\n\n# If true, show URL addresses after external links.\n#man_show_urls = False\n\n\n# -- Options for Texinfo output ------------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n  ('index', 'eos', u'EOS Documentation',\n   u'CERN', 'eos', 'One line description of project.',\n   'Miscellaneous'),\n]\n\n# Documents to append as an appendix to all manuals.\n#texinfo_appendices = []\n\n# If false, no module index is generated.\n#texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n#texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n#texinfo_no_detailmenu = False\n\n\n"
  },
  {
    "path": "doc/citrine/configuration/archive.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Archive; Configuration\n\n\nArchive\n=======\n\nThe archive daemon is managing the transfer of files to/from EOS from/to a\nremote location offering an XRootD interface. Before starting the new service\nthere are a few configuration parameters that need to be set.\n\nDaemon configuration\n+++++++++++++++++++++\nFirst of all, one needs to set the user account under which the daemon will be\nrunning by modifying the script located at ``/etc/sysconfig/eosarchived``. The\ndaemon needs to create a series of temporary files during the transfers which\nwill be saved at the location pointed by the environment variable **EOS_ARCHIVE_DIR**.\nAlso, the location where the daemon log file is saved can be changed by modifying\nthe variable **LOG_DIR**.\n\nIf none of the above variables is modified then the default configuration is as\nfollows:\n\n.. code-block:: bash\n\n  export USER=eosarchi\n  export GROUP=c3\n  export EOS_ARCHIVE_DIR=/var/eos/archive/\n  export LOG_DIR=/var/log/eos/archive/\n\nThe variables set in ``/etc/sysconfig/eosarchived`` are general ones that apply\nto both the daemon process and the individual transfer processes spawned later on.\n\nAnother file which holds further configurable values is\n``/etc/eosarchived.conf``. The advantage of this file is that it can be modified\nwhile the daemon is running and newly submitted transfers will pick-up the new\nconfiguration without the need of a full daemon restart.\n\nThe **LOG_LEVEL** variable is set in ``/etc/eosarchived.conf`` and  must be a\nstring corresponding to the syslog loglevel. The **eosarchived** daemon logs\nare saved by default in ``/var/log/eos/archive/eosarchived.log``.\n\nMGM Configuration\n+++++++++++++++++\n\nThe configuration file for the **MGM** node contains a new directive called\n**mgmofs.archivedir** which needs to point to the same location as the\n**EOS_ARCHIVE_DIR** defined earlier for the **eosarchived** daemon. The two\nlocations must match because the **MGM** and the **eosarchived** daemons\ncommunicate between each other using ZMQ and this is the path where any common\nZMQ files are stored.\n\n.. code-block:: bash\n\n  mgmofs.archivedir /var/eos/archive/  # has to be the same as EOS_ARCHIVE_DIR from eosarchived\n\nAnother variable that needs to be set for the **MGM** node is the location where\nall the archived directories are saved. Care should be taken so that the user\nname under which the **eosarchived** daemon runs, has the proper rights to read\nand write files to this remote location. This environment variables can be set in\nthe ``/etc/sysconfig/eos`` file as follows:\n\n.. code-block:: bash\n\n  export EOS_ARCHIVE_URL=root://castorpps.cern.ch//castor/cern.ch/archives/\n\nKeytab file generation\n++++++++++++++++++++++\n\nAssuming that the **eosarchived** daemon is running under the account *eosarchi*,\nthen one has to make sure the following files are present at the **MGM**. First of\nall, the eos-server.keytab file must include a new entry for the **eosarchi**:\n\n.. code-block:: console\n\n     [root@dev doc]$ ls -lrt /etc/eos-server.keytab\n     -r--------. 1 daemon daemon 137 Mar 22  2012 /etc/eos-server.keytab\n\n     [root@dev ~]# xrdsssadmin list /etc/eos-server.keytab\n     Number Len Date/Time Created Expires  Keyname User & Group\n     ------ --- --------- ------- -------- -------\n\t  2  32 09/17/14 19:25:01 -------- archive eosarchi c3\n\t  1  32 09/17/14 19:24:47 -------- eosinst daemon daemon\n\nThe next file that needs to be present is the eos-archive.keytab file which is\ngoing to be used by the **eosarchived** daemon.\n\n.. code-block:: console\n\n     [root@dev ~]# ls -lrt /etc/eos-archive.keytab\n     -r--------. 1 eosarchi c3 133 Sep 18 09:48 /etc/eos-archive.keytab\n\n     [root@dev ~]# xrdsssadmin list /etc/eos-archive.keytab\n     Number Len Date/Time Created Expires  Keyname User & Group\n     ------ --- --------- ------- -------- -------\n\t  2  32 09/17/14 19:25:01 -------- archive eosarchi c3\n\nSome important notes about the **eos-archive.keytab** file:\n\n- if renamed or saved in a different location then the variable **XrdSecSSSKT**\n  from ``/etc/sysconfig/eosarchived`` needs to be updated to point to the new\n  location/name\n- the permissions on this keytab file must match the identity under which the\n  eoarchived daemon is running\n\nFurthermore, the **eosarchi** user needs to be added to the **sudoers** list in\nEOS so that it can perform any type of operation while creating or transferring\narchives.\n\n.. code-block:: console\n\n    EOS Console [root://localhost] |/eos/> vid set membership eosarchi +sudo\n\nAs far as the **xrd.cf.mgm** configuration file is concerned, one must ensure\nthat **sss** authentication has precedence over **unix** when it comes to local\nconnections:\n\n.. code-block:: bash\n\n   sec.protbind localhost.localdomain sss unix\n   sec.protbind localhost             sss unix\n"
  },
  {
    "path": "doc/citrine/configuration/balancing.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Balancing System\n\nBalancing System\n================\n\nOverview\n--------\n\nThe EOS balancing system provides a fully automated mechanism to balance the\nvolume usage across a scheduling group. Hence currently the balancing system\ndoes not balance between scheduling groups! See :doc:`groupbalancer`!\n\nThe balancing system is made up by the cooperation of several components:\n\n* Central File System View with file system usage information and space configuration\n* Centrally running balancer thread steering the filesystem balancer process by computing averages and deviations\n* Balancer Thread on each FST pulling workload to pull files locally to balance filesystems\n\n.. ::note\n\n   Balancing is en-/disabled in each space separately!\n\nBalancing View and Configuration\n--------------------------------\n\nEach filesystem advertises the used volume and the central view allows to see\nthe deviation from the average filesystem usage in each group.\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> group ls\n   #---------------------------------------------------------------------------------------------------------------------\n   #     type #           name #     status #nofs #dev(filled) #avg(filled) #sig(filled) #balancing #  bal-run #drain-run\n   #---------------------------------------------------------------------------------------------------------------------\n   groupview  default.0                  on     8         0.27         0.10         0.12 idle                0          0\n   groupview  default.1                  on     8         0.28         0.10         0.12 idle                0          0\n   groupview  default.10                 on     8         0.29         0.10         0.13 idle                0          0\n   groupview  default.11                 on     8         0.29         0.10         0.13 idle                0          0\n   groupview  default.12                 on     7         0.28         0.11         0.14 idle                0          0\n   groupview  default.13                 on     8         0.28         0.12         0.14 idle                0          0\n   groupview  default.14                 on     8         0.29         0.10         0.13 idle                0          0\n   groupview  default.15                 on     8         0.30         0.10         0.13 idle                0          0\n   groupview  default.16                 on     7         0.26         0.12         0.13 idle                0          0\n   groupview  default.17                 on     8         0.28         0.12         0.14 idle                0          0\n   groupview  default.18                 on     8         0.30         0.10         0.14 idle                0          0\n   groupview  default.19                 on     8        12.42         4.76         6.80 idle                0          0\n   groupview  default.2                  on     8         0.48         0.16         0.23 idle                0          0\n   groupview  default.20                 on     8        14.03         5.43         7.62 idle                0          0\n   groupview  default.21                 on     8         0.48         0.16         0.23 idle                0          0\n   groupview  default.3                  on     8         0.28         0.10         0.12 idle                0          0\n   groupview  default.4                  on     8         0.26         0.11         0.13 idle                0          0\n   groupview  default.5                  on     8         0.27         0.10         0.12 idle                0          0\n   groupview  default.6                  on     8         0.27         0.10         0.12 idle                0          0\n   groupview  default.7                  on     8         0.27         0.09         0.12 idle                0          0\n   groupview  default.8                  on     8         0.27         0.10         0.12 idle                0          0\n   groupview  default.9                  on     8         0.30         0.11         0.14 idle                0          0\n\n\nThe decision parameters to enable balancing in a group is the maximum deviation\nof the filling state (given in %).\nIn this example two groups are unbalanced (12 + 14 %).\n\nThe balancing is configured on the space level and the current configuration\nis displayed using the 'space status' command:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   balancer                         := off\n   balancer.node.ntx                := 10\n   balancer.node.rate               := 10\n   balancer.threshold               := 1\n   ...\n\nThe configuration variables are:\n\n.. epigraph::\n\n   ========================= ======================================================================\n   variable                  definition\n   ========================= ======================================================================\n   balancer                  can be off or on to disable or enable the balancing\n   balancer.node.ntx         number of parallel balancer transfers running on each FST\n   balancer.node.rate        rate limitation for each running balancer transfer in MB/s\n   balancer.threshold        percentage at which balancing gets enabled within a scheduling group\n   ========================= ======================================================================\n\nIf balancing is enabled ....\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space config default space.balancer=on\n   success: balancer is enabled!\n\nGroups which are balancing are shown via the **eos group ls** command:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> group ls\n   #---------------------------------------------------------------------------------------------------------------------\n   #     type #           name #     status #nofs #dev(filled) #avg(filled) #sig(filled) #balancing #  bal-run #drain-run\n   #---------------------------------------------------------------------------------------------------------------------\n   groupview  default.0                  on     8         0.27         0.10         0.12 idle                0          0\n   groupview  default.1                  on     8         0.28         0.10         0.12 idle                0          0\n   groupview  default.10                 on     8         0.29         0.10         0.13 idle                0          0\n   groupview  default.11                 on     8         0.29         0.10         0.13 idle                0          0\n   groupview  default.12                 on     7         0.28         0.11         0.14 idle                0          0\n   groupview  default.13                 on     8         0.28         0.12         0.14 idle                0          0\n   groupview  default.14                 on     8         0.29         0.10         0.13 idle                0          0\n   groupview  default.15                 on     8         0.30         0.10         0.13 idle                0          0\n   groupview  default.16                 on     7         0.26         0.12         0.13 idle                0          0\n   groupview  default.17                 on     8         0.28         0.12         0.14 idle                0          0\n   groupview  default.18                 on     8         0.30         0.10         0.14 idle                0          0\n   groupview  default.19                 on     8        12.42         4.76         6.80 balancing          10          0\n   groupview  default.2                  on     8         0.48         0.16         0.23 idle                0          0\n   groupview  default.20                 on     8        14.03         5.43         7.62 balancing          12          0\n   groupview  default.21                 on     8         0.48         0.16         0.23 idle                0          0\n   groupview  default.3                  on     8         0.28         0.10         0.12 idle                0          0\n   groupview  default.4                  on     8         0.26         0.11         0.13 idle                0          0\n   groupview  default.5                  on     8         0.27         0.10         0.12 idle                0          0\n   groupview  default.6                  on     8         0.27         0.10         0.12 idle                0          0\n   groupview  default.7                  on     8         0.27         0.09         0.12 idle                0          0\n   groupview  default.8                  on     8         0.27         0.10         0.12 idle                0          0\n   groupview  default.9                  on     8         0.30         0.11         0.14 idle                0          0\n\nThe current balancing can also be viewed by space or node:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space ls --io\n   #----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   #     name # diskload # diskr-MB/s # diskw-MB/s #eth-MiB/s # ethi-MiB # etho-MiB #ropen #wopen # used-bytes #  max-bytes # used-files # max-files #  bal-run #drain-run\n   #----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   default       0.02        66.00        66.00        862         57         60     31     22      1.99 TB    347.33 TB     805.26 k     16.97 G         51          0\n\n   EOS Console [root://localhost] |/> node ls --io\n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   #               hostport # diskload # diskr-MB/s # diskw-MB/s #eth-MiB/s # ethi-MiB # etho-MiB #ropen #wopen # used-bytes #  max-bytes # used-files # max-files #  bal-run #drain-run\n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   lxfsra02a02.cern.ch:1095       0.08        41.00         0.00        119          0         41     23      0    825.47 GB     41.92 TB     298.80 k      2.05 G          0          0\n   lxfsra02a05.cern.ch:1095       0.03        19.00         0.00        119          0         19      2      0    832.01 GB     43.92 TB     152.14 k      2.15 G          0          0\n   lxfsra02a06.cern.ch:1095       0.01         0.00        11.00        119         12          0      0      6     70.05 GB     43.92 TB      54.77 k      2.15 G         10          0\n   lxfsra02a07.cern.ch:1095       0.01         0.00        11.00        119          9          0      0      3     79.95 GB     43.92 TB      75.91 k      2.15 G         10          0\n   lxfsra02a08.cern.ch:1095       0.01         0.00        11.00        119          9          0      0      2     52.01 GB     43.92 TB      61.25 k      2.15 G          8          0\n   lxfsra04a01.cern.ch:1095       0.01         0.00        10.00        119          9          0      0      1     72.12 GB     41.92 TB      60.92 k      2.05 G          8          0\n   lxfsra04a02.cern.ch:1095       0.01         0.00        10.00        119          9          0      0      7     52.32 GB     43.92 TB      86.72 k      2.15 G         10          0\n   lxfsra04a03.cern.ch:1095       0.01         0.00        10.00        119          9          0      0      5     10.53 GB     43.92 TB      14.80 k      2.15 G          5          0\n\nTo see the usage difference within the group, one can inspect all the group filesystems via **eos group ls --IO** e.g.\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> group ls --IO default.20\n   #---------------------------------------------------------------------------------------------------------------------\n   #     type #           name #     status #nofs #dev(filled) #avg(filled) #sig(filled) #balancing #  bal-run #drain-run\n   #---------------------------------------------------------------------------------------------------------------------\n   groupview  default.20                 on     8        13.71         5.48         7.47 balancing          37          0\n   #.................................................................................................................................................................................................................\n   #                     hostport #  id #     schedgroup # diskload # diskr-MB/s # diskw-MB/s #eth-MiB/s # ethi-MiB # etho-MiB #ropen #wopen # used-bytes #  max-bytes # used-files # max-files #  bal-run #drain-run\n   #.................................................................................................................................................................................................................\n   lxfsra02a05.cern.ch:1095    17       default.20       0.47        12.00         0.00        119          0         21      1      0    383.17 GB      2.00 TB      59.33 k     97.52 M          0          0\n   lxfsra02a06.cern.ch:1095    35       default.20       0.08         0.00         6.00        119         10          0      0      6     26.56 GB      2.00 TB       6.23 k     97.52 M          7          0\n   lxfsra04a01.cern.ch:1095    57       default.20       0.13         0.00         6.00        119          9          0      0      4     25.01 GB      2.00 TB       6.11 k     97.52 M          4          0\n   lxfsra02a08.cern.ch:1095    77       default.20       0.08         0.00         6.00        119         11          0      0      5     27.36 GB      2.00 TB       6.64 k     97.52 M          8          0\n   lxfsra04a02.cern.ch:1095    99       default.20       0.07         0.00         4.00        119         10          0      0      3     26.57 GB      2.00 TB       7.75 k     97.52 M          6          0\n   lxfsra02a02.cern.ch:1095   121       default.20       1.00        22.00         0.00        119          0         41     21      0    351.07 GB      2.00 TB      59.80 k     97.52 M          0          0\n   lxfsra02a07.cern.ch:1095   143       default.20       0.10         0.00         7.00        119          9          0      0      2     28.57 GB      2.00 TB       7.46 k     97.52 M          7          0\n   lxfsra04a03.cern.ch:1095   165       default.20       0.12         0.00         6.00        119         10          0      0      5      7.56 GB      2.00 TB       2.96 k     97.52 M          5          0\n\n\nThe scheduling activity for balancing can be monitored with the **eos ns ls** command:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> ns stat\n   # ------------------------------------------------------------------------------------\n   # Namespace Statistic\n   # ------------------------------------------------------------------------------------\n   ALL      Files                            682781 [booted] (12s)\n   ALL      Directories                      1316\n   # ....................................................................................\n   ALL      File Changelog Size              804.27 MB\n   ALL      Dir  Changelog Size              515.98 kB\n   # ....................................................................................\n   ALL      avg. File Entry Size             1.18 kB\n   ALL      avg. Dir  Entry Size             392.00 B\n   # ------------------------------------------------------------------------------------\n   ALL      Execution Time                   0.40 +- 1.12\n   # -----------------------------------------------------------------------------------------------------------\n   who      command                          sum             5s     1min     5min       1h exec(ms) +- sigma(ms)\n   # -----------------------------------------------------------------------------------------------------------\n   ALL        Access                                      0     0.00     0.00     0.00     0.00     -NA- +- -NA-\n    ....\n   ALL        Schedule2Balance                         6423    11.75    10.81    10.71     1.78     -NA- +- -NA-\n   ALL        Schedule2Drain                              0     0.00     0.00     0.00     0.00     -NA- +- -NA-\n   ALL        Scheduled2Balance                        6423    11.75    10.81    10.71     1.78     4.20 +- 0.57\n   ALL        SchedulingFailedBalance                     0     0.00     0.00     0.00     0.00     -NA- +- -NA-\n\n\nThe relevant counters are:\n\n.. epigraph::\n\n   ============================== =====================================================================\n   state                          definition\n   ============================== =====================================================================\n   Schedule2Balance               counter/rate at which all FSTs ask for a file to balance\n   ScheduledBalance               counter/rate of balancing transfers which have been scheduled to FSTs\n   SchedulingFailedBalance        counter/rate of scheduling requests which could not get any workload\n                                  (e.g. no file matches the target machine)\n   ============================== =====================================================================\n"
  },
  {
    "path": "doc/citrine/configuration/converter.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Converter\n\nConverter\n=========\n\nThe converter serves two purposes: For :doc:`groupbalancer` and :doc:`geobalancer`\nit is used to rewrite files with to a new location. For the LRU policy converter\nis used to rewrite a file with a new layout e.g. rewrite a file with 2 replica \ninto a RAID-6 like RAIN layout with the benefit of space savings.\nInternally the converter uses the XRootD third party copy mechanism and consumes\none thread in the **MGM** for each running conversion transfer.\n\nConfiguration\n-------------\nThe Converter is enabled/disabled by space:\n\n.. code-block:: bash\n\n   # enable\n   eos space config default space.converter=on  \n   # disable\n   eos space config default space.converter=off\n\nThe current status of the Converter can be seen via:\n\n.. code-block:: bash\n\n   eos -b space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   ...\n   converter                       := off\n   converter.ntx                   := 0\n   ...\n\nThe number of concurrent transfers to run is defined via the **converter.ntx**\nspace variable:\n\n.. code-block:: bash\n\n   # schedule 10 transfers in parallel\n   eos space config default space.converter.ntx=10\n\nOne can see the same settings and the number of active conversion transfers\n(scroll to the right):\n\n.. code-block:: bash\n   \n   eos space ls \n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   #     type #           name #  groupsize #   groupmod #N(fs) #N(fs-rw) #sum(usedbytes) #sum(capacity) #capacity(rw) #nom.capacity #quota #balancing # threshold # converter #  ntx # active #intergroup\n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   spaceview           default           22           22    202       123          2.91 T       339.38 T      245.53 T          0.00     on        off        0.00          on 100.00     0.00         off\n\nLog Files\n---------\n\nThe Converter has a dedicated log file under ``/var/log/eos/mgm/Converter.log``\nwhich shows scheduled conversions and errors of conversion jobs. To get more\nverbose information you can change the log level:\n\n.. code-block:: bash\n\n   # switch to debug log level on the MGM\n   eos debug debug\n\n   # switch back to info log level on the MGM\n   eos debug info"
  },
  {
    "path": "doc/citrine/configuration/converter_engine.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Converter Engine\n\nConverter Engine\n================\n\nThe Converter Engine is the EOS component responsible for scheduling\nand performing file conversion jobs. A conversion job means rewriting a file\nwith a different storage parameter: layout, replica number, space\nor placement policy.\n\nThe Converter Engine is split into two main components:\n*Converter Driver* and *Converter Scheduler*.\n\n.. note::\n\n  The Converter Engine is compatible only with the QuarkDB\n  namespace implementation. In case the in-memory namespace is used,\n  the service will not start at MGM boot-up.\n\nConverter Driver\n----------------\n\nThe Converter Driver is the component responsible for performing the actual\nconversion job. This is done using XRootD third party copy between the FSTs.\n\nThe Converter Driver keeps a threadpool available for conversion jobs.\nPeriodically, it queries QuarkDB for conversion jobs, in batches of 1000. \nThe retrieved jobs are scheduled, one per thread, up to a configurable \nruntime threads limit. After each scheduling, a check is performed \nto identify completed or failed jobs.\n  \nSuccessful conversion jobs:\n  - get removed from the QuarkDB pending jobs set\n  - get removed from the MGM in-flight jobs tracker\n\nFailed conversion jobs:\n  - get removed from the QuarkDB pending jobs set\n  - get removed from the MGM in-flight jobs tracker\n  - get updated to the QuarkDB failed jobs set\n  - get updated to the MGM failed jobs set\n\nWithin QuarkDB, the following hash sets are used:\n\n.. code-block:: bash\n\n  eos-conversion-jobs-pending\n  eos-conversion-jobs-failed\n\nEach hash entry has the following structure: *<fid>:<conversion_info>*.\n\nConversion Info\n+++++++++++++++\n\nA conversion info is defined as following:\n\n.. code-block:: bash\n\n  <fid(016hex)>:<space[.group]>#<layout(08hex)>[~<placement>]\n\n    <fid>       - 16-digit with leading zeroes hexadecimal file id\n    <space>     - space or space.group notation\n    <layout>    - 8-digit with leading zeroes hexadecimal layout id\n    <placement> - the placement policy to apply\n\nThe job info is parsed by the Converter Driver before creating \nthe associated job. Entries with invalid info are simply discarded \nfrom the QuarkDB pending jobs set.\n\nConversion Job\n++++++++++++++\n\nA conversion job goes through the following steps:\n  - The current file metadata is retrieved\n  - The TPC job is prepared with appropriate opaque info\n  - The TPC job is executed\n  - Once TPC is completed, verify the new file has all fragments according to layout\n  - Verify initial file hasn't changed (checksum is the same)\n  - Merge the conversion entry with the initial file\n  - Mark conversion job as completed\n\nIf at any step a failure is encountered, the conversion job\nwill be flagged as failed.\n\nConverter Scheduler\n-------------------\n\nThe Converter Scheduler is the component responsible for creating conversion jobs,\naccording to a given set of conversion rules. A conversion rule is placed\non a namespace entry (file or directory), contains optional filters\nand the target storage parameter.\n\n- When a conversion rule is placed on a file, an immediate conversion job is created\n  and pushed to QuarkDB.\n- When a conversion rule is placed on a directory, a tree traversal is initiated\n  and all files which pass the filtering criteria will be scheduled for conversion.\n  "
  },
  {
    "path": "doc/citrine/configuration/draining.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Drain System\n\n.. _draining:\n\nDrain System\n============\n\nOverview\n--------\n\nThe EOS drain system provides a fully automatic mechanism to drain (empty)\nfilesystems under certain error conditions. A file system drain is triggered\nby an IO error on a file system or manually by an operator setting a\nfilesystem in drain mode.\n\nThe drain engine makes use of the GeoTreeEngine component to decide where to move the drained replicas.\nThe drain processes are spawned on the MGM and represent simple XRootD third-party-copy transfers.\n\nFST Scrubber\n------------\n\nEach FST run's a dedicated thread doing scrubbing. Scrubbing is running if the\nfile system configuration is at least **wo** ( e.g. in write-only or read-write mode),\nthe file system is in **booted** state and the label of the\nfilesystem ``<mountpoint>/.eosfsid + <mountpoint>/.eosfsuuid`` is readable.\nIf the label is not readable the Scrubber broadcasts an IO error for filesystems\nin **ro**, **wo** or **rw** mode and **booted** state with the error text\n\"filesystem seems to be not mounted anymore\".\n\nThe FST scrubber follows the filling size of a disk and writes test pattern\nfiles at 0%, 10%, 20% ... 90% filling with the goal to do tests equally\ndistributed over the physical size of the disk. At each 10% filling position\nthe scrubber creates a write-once file to be re-read in each scrubbing pass\nand a re-write file which is re-written and re-read in each scrubbing pass.\nThe following pattern is written into the test files:\n\n.. code-block:: bash\n\n   scrubPattern[0][i]=0xaaaa5555aaaa5555ULL;\n   scrubPattern[0][i+1]=0x5555aaaa5555aaaaULL;\n   scrubPattern[1][i]=0x5555aaaa5555aaaaULL;\n   scrubPattern[1][i+1]=0xaaaa5555aaaa5555ULL;\n\nPattern 0 or pattern 1 is selected randomly. Each test file has 1MB size and\nthe scrub file names are ``<mountpoint>/scrub.write-once.[0-9]`` and\n``<mountpoint>/scrub.re-write.[0-9]``.\n\nIn case an error is detected, the FST broadcasts an EIO to the MGM with the\nerror text \"filesystem probe error detected\".\n\nYou can see filesystems in error state and the error text on the MGM node doing:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> fs ls -e\n   #...............................................................................................\n   #                   host #   id #     path #       boot # configstatus #      drain #... #errmsg\n   #...............................................................................................\n        lxfsrk51a02.cern.ch   3235    /data05  opserror            empty      drained   5 filesystem seems to be\n                                                                                          not mounted anymore\n        lxfsrk51a04.cern.ch   3372    /data19  opserror            empty      drained   5 filesystem probe error detected\n\n\nCentral File System View and State Machine\n------------------------------------------\n\nEach filesystem in EOS has a configuration, boot state and drain state.\n\nThe possible configuration states are self explaining:\n\n.. epigraph::\n\n   ============= ======================================================================================\n   state          definition\n   ============= ======================================================================================\n   rw            filesystem set in read write mode\n   wo            filesystem set in write-once mode\n   ro            filesystem set in read-only mode\n   drain         filesystem set in drain mode\n   off           filesystem set disabled\n   empty         filesystem is empty e.g. contains no files any more\n   ============= ======================================================================================\n\nFile systems involved in any kind of IO need to be in boot state booted.\n\nThe configured file systems are shown via:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> fs ls\n\n   #.........................................................................................................................\n   #                   host (#...) #   id #           path #     schedgroup #       boot # configstatus #      drain # active\n   #.........................................................................................................................\n        lxfsra02a05.cern.ch (1095)      1          /data01        default.0       booted             rw      nodrain   online\n        lxfsra02a05.cern.ch (1095)      2          /data02       default.10       booted             rw      nodrain   online\n        lxfsra02a05.cern.ch (1095)      3          /data03        default.1       booted             rw      nodrain   online\n        lxfsra02a05.cern.ch (1095)      4          /data04        default.2       booted             rw      nodrain   online\n        lxfsra02a05.cern.ch (1095)      5          /data05        default.3       booted             rw      nodrain   online\n\nAs shown each file system has also a drain state. Drain states can be:\n\n.. epigraph::\n\n   ================ ==============================================================================================================================================================================\n   state            definition\n   ================ ==============================================================================================================================================================================\n   nodrain          file system is currently not draining\n   prepare          the drain process is prepared - this phase lasts 60 seconds\n   wait             the drain process either waits for the namespace to be booted or it is waiting that the graceperiod has passed (see below)\n   draining         the drain process is enabled - nodes inside the scheduling group start to pull transfers to drop replicas from the filesystem to drain\n   stalling         in the last 5 minutes there was noprogress of the drain procedure. This happens if the files to transfer are very huge or there are only files left which cannot be replicated.\n   expired          the time defined by the drainperiod variable has passed and the drain process is stopped. There are files left on the disk which couldn't be drained.\n   drained          all files have been drained from the filesystem.\n   failed           the drain activity is finished but there are still files on file system that could not be drained and require a manual inspection.\n   ================ ==============================================================================================================================================================================\n\nThe final state can be one of the following: expired, failed or drained.\n\nThe drain and grace periods are defined as a space variables (e.g. automatically\napplied to all filesystems in that space when they are moved into or registered).\n\nOne can see the settings via the space command:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   balancer                         := on\n   balancer.node.ntx                := 10\n   balancer.node.rate               := 10\n   balancer.threshold               := 1\n   drainer.node.ntx                 := 10\n   drainer.node.rate                := 25\n   drainperiod                      := 3600\n   graceperiod                      := 86400\n   groupmod                         := 24\n   groupsize                        := 20\n   headroom                         := 0.00 B\n   quota                            := off\n   scaninterval                     := 1\n\nThey can be modified by setting the *drainperiod* or *graceperiod* variable in\nnumber of seconds:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space config default space.drainperiod=86400\n   success: setting drainperiod=86400\n\n   EOS Console [root://localhost] |/> space config default space.graceperiod=86400\n   success: setting graceperiod=86400\n\n.. warning::\n   This defines the variables only if filesystems are registered or moved into that space.\n\nIf you want to apply this setting to all filesystems in that space,\nyou have additionally to call:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space config default fs.drainperiod=86400\n   EOS Console [root://localhost] |/> space config default fs.graceperiod=86400\n\nIf you want a global overview about running drain processes, you can get the\nnumber of running drain transfers by space, by group, by node and by filesystem:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space ls --io\n   #----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   #     name # diskload # diskr-MB/s # diskw-MB/s #eth-MiB/s # ethi-MiB # etho-MiB #ropen #wopen # used-bytes #  max-bytes # used-files # max-files #  bal-run #drain-run\n   #----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   default       0.01        32.00        17.00        862         15         14      9      9      6.97 TB    347.33 TB      20.42 M     16.97 G          0         10\n\n   EOS Console [root://localhost] |/> group  ls --io\n   #----------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   #           name # diskload # diskr-MB/s # diskw-MB/s #eth-MiB/s # ethi-MiB # etho-MiB #ropen #wopen # used-bytes #  max-bytes # used-files # max-files #  bal-run #drain-run\n   #----------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   default.0              0.00         0.00         0.00        952        217        199      0      0    338.31 GB     15.97 TB     952.65 k    780.14 M          0          0\n   default.1              0.00         0.00         0.00        952        217        199      0      0    336.07 GB     15.97 TB     927.18 k    780.14 M          0          0\n   default.10             0.00         0.00         0.00        952        217        199      0      0    332.23 GB     15.97 TB     926.45 k    780.14 M          0          0\n   default.11             0.00         0.00         0.00        952        217        199      0      0    325.14 GB     15.97 TB     948.02 k    780.14 M          0          0\n   default.12             0.00         0.00         0.00        833        180        179      0      0     22.39 GB     13.97 TB     898.40 k    682.62 M          0          0\n   default.13             0.00         0.00         1.00        952        217        199      0      0    360.30 GB     15.97 TB     951.05 k    780.14 M          0          0\n   default.14             0.99        96.00       206.00        952        217        199     31     30    330.45 GB     15.97 TB     956.50 k    780.14 M          0         36\n   default.15             0.00         0.00         0.00        952        217        199      0      0    308.26 GB     15.97 TB     939.26 k    780.14 M          0          0\n   default.16             0.00         0.00         0.00        833        188        184      0      0    327.76 GB     13.97 TB     899.97 k    682.62 M          0          0\n   default.17             0.87       100.00       202.00        952        217        199     16     28    368.09 GB     15.97 TB     933.95 k    780.14 M          0         31\n   default.18             0.00         0.00         0.00        952        217        199      0      0    364.27 GB     15.97 TB     953.94 k    780.14 M          0          0\n   default.19             0.00         0.00         0.00        952        217        199      0      0    304.66 GB     15.97 TB     939.24 k    780.14 M          0          0\n   default.2              0.00         0.00         0.00        952        217        199      0      0    333.64 GB     15.97 TB     920.26 k    780.14 M          0          0\n   default.20             0.00         0.00         0.00        952        217        199      0      0    335.00 GB     15.97 TB     957.02 k    780.14 M          0          0\n   default.21             0.00         0.00         0.00        952        217        199      0      0    335.18 GB     15.97 TB     921.75 k    780.14 M          0          0\n   default.3              0.00         0.00         0.00        952        217        199      0      0    319.06 GB     15.97 TB     919.02 k    780.14 M          0          0\n   default.4              0.00         0.00         0.00        952        217        199      0      0    320.18 GB     15.97 TB     826.62 k    780.14 M          0          0\n   default.5              0.00         0.00         0.00        952        217        199      0      0    320.12 GB     15.97 TB     924.60 k    780.14 M          0          0\n   default.6              0.00         0.00         0.00        952        217        199      0      0    333.56 GB     15.97 TB     920.32 k    780.14 M          0          0\n   default.7              0.00         0.00         0.00        952        217        199      0      0    333.42 GB     15.97 TB     922.51 k    780.14 M          0          0\n   default.8              0.00         0.00         0.00        952        217        199      0      0    335.67 GB     15.97 TB     925.39 k    780.14 M          0          0\n   default.9              0.00         0.00         0.00        952        217        199      0      0    325.37 GB     15.97 TB     957.84 k    780.14 M          0          0\n   test                   0.00         0.00         0.00          0          0          0      0      0       0.00 B       0.00 B         0.00        0.00          0          0\n\n   EOS Console [root://localhost] |/> node  ls --io\n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   #               hostport # diskload # diskr-MB/s # diskw-MB/s #eth-MiB/s # ethi-MiB # etho-MiB #ropen #wopen # used-bytes #  max-bytes # used-files # max-files #  bal-run #drain-run\n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   eosdevsrv1.cern.ch:1095       0.00         0.00         0.00          0          0          0      0      0       0.00 B       0.00 B         0.00        0.00          0          0\n   lxfsra02a02.cern.ch:1095       0.10        19.00        55.00        119         37         20      7      8    935.18 GB     41.92 TB       2.54 M      2.05 G          0         10\n   lxfsra02a05.cern.ch:1095       0.06         5.00        53.00        119         30          5      1     10    968.03 GB     43.92 TB       2.71 M      2.15 G          0         10\n   lxfsra02a06.cern.ch:1095       0.05         0.00        50.00        119         16          0      0      6    872.91 GB     43.92 TB       2.84 M      2.15 G          0          6\n   lxfsra02a07.cern.ch:1095       0.05        33.00        10.00        119         23         33      6      7    882.25 GB     43.92 TB       3.03 M      2.15 G          0          8\n   lxfsra02a08.cern.ch:1095       0.09        41.00        56.00        119         45         42      9      9    947.68 GB     43.92 TB       2.78 M      2.15 G          0         10\n   lxfsra04a01.cern.ch:1095       0.09        15.00       101.00        119         29         15      2      8    818.77 GB     41.92 TB       2.02 M      2.05 G          0         10\n   lxfsra04a02.cern.ch:1095       0.09        27.00        83.00        119         37         27      2     10    837.91 GB     43.92 TB       2.30 M      2.15 G          0         10\n   lxfsra04a03.cern.ch:1095       0.05        56.00         1.00        119          0         57     20      0    746.40 GB     43.92 TB       2.21 M      2.15 G          0          0\n\n   EOS Console [root://localhost] |/> fs ls --io\n\n   #.................................................................................................................................................................................................................\n   #                     hostport #  id #     schedgroup # diskload # diskr-MB/s # diskw-MB/s #eth-MiB/s # ethi-MiB # etho-MiB #ropen #wopen # used-bytes #  max-bytes # used-files # max-files #  bal-run #drain-run\n   #.................................................................................................................................................................................................................\n\n   ...\n\n   lxfsra04a02.cern.ch:1095   109       default.14       0.21         0.00        15.00        119         21          0      0      8     59.35 GB      2.00 TB     102.85 k     97.52 M          0          8\n\n   ...\n\n\n\nDrain Threads MGM\n-----------------\n\nEach filesystem shown in the drain view in a non-final state has a thread on the\nMGM associated to it.\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> fs ls -d\n\n   #......................................................................................................................\n   #                   host (#...) #   id #           path #      drain #   progress #      files # bytes-left #  timeleft\n   #......................................................................................................................\n   lxfsra02a05.cern.ch (1095)     20          /data20      prepare            0         0.00       0.00 B          24\n\nA drain thread is steering the drain of each filesystem in non-final state and\nis responsible of spawning drain processes directly on the MGM node. These logical\ndrain jobs use the GeoTreeEngine to select the destination file system are queued\nin case the limits per node are reached. The drain parameters can be configured at\nthe space level:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space status default\n\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   ..\n\n   drainer.node.nfs                 := 10\n   drainer.fs.ntx                   := 10\n   drainperiod                      := 3600\n   graceperiod                      := 86400\n   ..\n\nBy default max 5 file systems per node can be drained in parallel with max 5\nparallel transfers per file system.\n\nThe values can be modified via:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space config default space.drainer.node.nfs=20\n   EOS Console [root://localhost] |/> space config default space.drainer.fs.ntx=50\n\n\nExample Drain Process\n---------------------\n\nWe need to drain filesystem 20. However the file system is still fully operational\nhence we use status drain.\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> fs config 20 configstatus=drain\n   EOS Console [root://localhost] |/> fs ls -d\n\n   #......................................................................................................................\n   #                   host (#...) #   id #           path #      drain #   progress #      files # bytes-left #  timeleft\n   #......................................................................................................................\n   lxfsra02a05.cern.ch (1095)     20          /data20      prepare            0         0.00       0.00 B          24\n\nAfter 60 seconds a drain filesystem changes into state draining if the drain\nmode was manually set. If a graceperiod is defined, it will stay in status\nwaiting for the length of the grace period.\n\nIn this example the defined drain period is 1 day:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> fs ls -d\n\n   #......................................................................................................................\n   #                   host (#...) #   id #           path #      drain #   progress #      files # bytes-left #  timeleft\n   #......................................................................................................................\n   lxfsra04a03.cern.ch (1095)    20           /data20     draining            5        75.00     37.29 GB       86269\n\n   When the drain has successfully completed, the output looks like this:\n\n   EOS Console [root://localhost] |/> fs ls -d\n\n   #......................................................................................................................\n   #                   host (#...) #   id #           path #      drain #   progress #      files # bytes-left #  timeleft\n   #......................................................................................................................\n   lxfsra02a05.cern.ch (1095)     20          /data20      drained            0         0.00       0.00 B           0\n\n\nIf the drain can not complete you will see this after the drain period has passed:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> fs ls -d\n\n   #......................................................................................................................\n   #                   host (#...) #   id #           path #      drain #   progress #      files # bytes-left #  timeleft\n   #......................................................................................................................\n   l\n   lxfsra04a03.cern.ch (1095)     20          /data20      expired           56        34.00     27.22 GB       86050\n\nYou can now investigate the origin by doing:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> fs status 20\n\n   ...\n\n   # ....................................................................................\n   # Risk Analysis\n   # ....................................................................................\n   number of files                  :=         34 (100.00%)\n   files healthy                    :=          0 (0.00%)\n   files at risk                    :=          0 (0.00%)\n   files inaccessbile               :=         34 (100.00%)\n   # ------------------------------------------------------------------------------------\n\nHere all remaining files are inaccessible because all replicas are down.\n\nIn case files are claimed to be accessible you have to look directory at the remaining files:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> fs dumpmd 20 -path\n   path=/eos/dev/2rep/sub12/lxplus403.cern.ch_10/0/0/7.root\n   path=/eos/dev/2rep/sub12/lxplus403.cern.ch_10/0/2/8.root\n   path=/eos/dev/2rep/sub12/lxplus406.cern.ch_4/0/1/0.root\n   path=/eos/dev/2rep/sub12/lxplus403.cern.ch_43/0/2/8.root\n   ...\n\nCheck these files using 'file check':\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> file check /eos/dev/2rep/sub12/lxplus403.cern.ch_10/0/0/7.root\n   path=\"/eos/dev/2rep/sub12/lxplus403.cern.ch_10/0/0/7.root\" fid=\"0002d989\" size=\"291241984\" nrep=\"2\" checksumtype=\"adler\" checksum=\"0473000100000000000000000000000000000000\"\n   nrep=\"00\" fsid=\"20\" host=\"lxfsra02a05.cern.ch:1095\" fstpath=\"/data08/00000012/0002d989\" size=\"291241984\" checksum=\"0473000100000000000000000000000000000000\"\n   nrep=\"01\" fsid=\"53\" host=\"lxfsra04a01.cern.ch:1095\" fstpath=\"/data09/00000012/0002d989\" size=\"291241984\" checksum=\"0000000000000000000000000000000000000000\"\n\nIn this case the second replica didn't commit a checksum and cannot be read.\n\nThis you might fix like this:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> file verify /eos/dev/2rep/sub12/lxplus403.cern.ch_10/0/0/7.root -checksum -commitchecksum\n\n\nIf you just want to force the remove of files remaining on a non-drained filesystem,\nyou can drop all files on a particular filesystem using **eos fs dropfiles**.\nIf you use the '-f' flag all references to these files will be removed immediately\nand EOS won't try to delete any file anymore.\n\n.. code-block:: console\n\n   EOS Console [root://localhost] |/> fs dropfiles 170 -f\n   Do you really want to delete ALL 24 replica's from filesystem 170 ?\n   Confirm the deletion by typing => 1434841745\n   => 1434841745\n\n   Deletion confirmed\n"
  },
  {
    "path": "doc/citrine/configuration/egi.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: EGI scripts\n\nEGI scripts\n============\n\nEOS provides a couple of scripts that generate the required space accounting information [https://wiki.egi.eu/wiki/APEL/Storage] and info provider data. These scripts are available in the `eos-server` package starting with release 5.0.15.\n\nStorage accounting\n------------------\n\nThis information is provided by the `eos-star-accounting.py` script that looks at the EOS space configuration present in your instance. An example of how to invoke this script is provided below with some demo information:\n\n.. code-block:: bash\n\n   eos-star-accounting.py\n   <sr:StorageUsageRecords xmlns:sr=\"http://eu-emi.eu/namespaces/2011/02/storagerecord\">\n     <sr:StorageUsageRecord>\n       <sr:RecordIdentity sr:createTime=\"2022-03-21T14:22:21Z\" sr:recordId=\"esdss000.cern.ch-52307ef4-a922-11ec-bc51-dc4a3e6b9f27\"/>\n       <sr:StorageSystem>esdss000.cern.ch</sr:StorageSystem>\n       <sr:SubjectIdentity>\n         <sr:Site>eosdev</sr:Site>\n       </sr:SubjectIdentity>\n       <sr:StorageMedia>disk</sr:StorageMedia>\n       <sr:StartTime>2022-03-20T14:22:21Z</sr:StartTime>\n       <sr:EndTime>2022-03-21T14:22:21Z</sr:EndTime>\n       <sr:FileCount>1289</sr:FileCount>\n       <sr:ResourceCapacityUsed>1287017889792</sr:ResourceCapacityUsed>\n       <sr:ResourceCapacityAllocated>1287017889792</sr:ResourceCapacityAllocated>\n       <sr:LogicalCapacityUsed>1287017889792</sr:LogicalCapacityUsed>\n     </sr:StorageUsageRecord>\n   </sr:StorageUsageRecords>\n\n\nInfo provider\n--------------\n\nThis information is provided by the `eos-info-provider.py` script.\n\n.. code-block:: bash\n\n eos-info-provider.py --sitename eosdev\n version: 1\n dn: GLUE2ServiceID=esdss000.cern.ch/Service,GLUE2GroupID=resource,o=glue\n changetype: add\n objectClass: GLUE2Service\n objectClass: GLUE2StorageService\n GLUE2ServiceID: esdss000.cern.ch/Service\n GLUE2EntityCreationTime: 2022-03-21T14:24:55Z\n GLUE2ServiceQualityLevel: production\n GLUE2ServiceCapability: data.access.flatfiles\n GLUE2ServiceCapability: data.transfer\n GLUE2ServiceCapability: data.management.replica\n GLUE2ServiceCapability: data.management.storage\n GLUE2ServiceCapability: data.management.transfer\n GLUE2ServiceCapability: security.authentication\n GLUE2ServiceCapability: security.authorization\n GLUE2ServiceType: eos\n GLUE2ServiceAdminDomainForeignKey: eosdev\n version: 1\n dn: GLUE2StorageServiceCapacityID=esdss000.cern.ch/StorageServiceCapacity,GLUE\n   2ServiceID=esdss000.cern.ch/Service,GLUE2GroupID=resource,o=glue\n changetype: add\n objectClass: GLUE2StorageServiceCapacity\n GLUE2StorageServiceCapacityUsedSize: 1198\n GLUE2EntityCreationTime: 2022-03-21T14:24:55Z\n GLUE2StorageServiceCapacityType: online\n GLUE2StorageServiceCapacityID: esdss000.cern.ch/StorageServiceCapacity\n GLUE2StorageServiceCapacityFreeSize: 867\n GLUE2StorageServiceCapacityStorageServiceForeignKey: esdss000.cern.ch/Service\n GLUE2StorageServiceCapacityTotalSize: 2065\n version: 1\n dn: GLUE2ManagerID=esdss000.cern.ch/Manager,GLUE2ServiceID=esdss000.cern.ch/Se\n   rvice,GLUE2GroupID=resource,o=glue\n changetype: add\n objectClass: GLUE2StorageManager\n objectClass: GLUE2Manager\n GLUE2ManagerProductName: EOS\n GLUE2EntityCreationTime: 2022-03-21T14:24:58Z\n GLUE2ManagerProductVersion:\n GLUE2StorageManagerStorageServiceForeignKey: esdss000.cern.ch/Service\n GLUE2ManagerServiceForeignKey: esdss000.cern.ch/Service\n GLUE2ManagerID: esdss000.cern.ch/Manager\n version: 1\n dn: GLUE2ResourceID=esdss000.cern.ch/DataStore,GLUE2ManagerID=esdss000.cern.ch\n   /Manager,GLUE2ServiceID=esdss000.cern.ch/Service,GLUE2GroupID=resource,o=glue\n changetype: add\n objectClass: GLUE2DataStore\n GLUE2DataStoreLatency: online\n GLUE2DataStoreFreeSize: 867\n GLUE2ResourceManagerForeignKey: esdss000.cern.ch/Manager\n GLUE2EntityCreationTime: 2022-03-21T14:24:58Z\n GLUE2DataStoreType: disk\n GLUE2DataStoreUsedSize: 1198\n GLUE2DataStoreStorageManagerForeignKey: esdss000.cern.ch/Manager\n GLUE2ResourceID: esdss000.cern.ch/DataStore\n GLUE2DataStoreTotalSize: 2065\n"
  },
  {
    "path": "doc/citrine/configuration/fsck.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Fsck\n\n\nFsck\n=====\n\nThis section describes how the internal file system consistency checks (FSCK) are configured and work.\n\nEnable FST Scan\n---------------\n\nTo enable the FST scan you have to set the variable **scaninterval** on the space and\non all file systems:\n\n.. code-block:: bash\n\n   # set it on the space to inherit a value for all new filesystems in this space every 14 days (time has to be in seconds)\n   space config default space.scaninterval=1209600\n\n   # set it on an existing filesystem (fsid 23) to 14 days (time has to be in seconds)\n   fs config 23 space.scaninterval=1209600\n\n   # set the scaninterval for all the existing file systems already registered in the given space\n   space config default fs.scaninterval=1209601\n\n.. note::\n\n   The *scaninterval* time has to be given in seconds!\n\n\nCaveats\n-------\n\nFor FSCK engine to function correctly, FSTs must be able to connect to QuarkDB directly (and to the MGM).\n\n\nOverview\n--------\n\nHigh level summary\n------------------\n\n#) error collection happens in the FST in defined intervals, no action/trigger by MGM is required for this\n\n#) the locally saved results will be collected by the fsck collection thread of fsck engine\n\n#) if the fsck repair thread is  enabled, the mgm will trigger repair actions (i.e. create / delete replica)\nas required (based on collected error data)\n\nIntervals and config parameters for file systems(FS)\n-----------------------------------------------------\n\nThese values are set as global defaults on the space. A file system should get the values from the space when it is newly created.\nBelow you can find a brief description of the parameters influencing the scanning procedure.\n\n===================  ===============   ===========================================================\nName                 Default           Description\n===================  ===============   ===========================================================\nscan_disk_interval   14400 [s] (4h)    interval at which files in the FS should be scanned, by the FST itself\nscan_ns_interval     259200 [s] (3d)   interval at which files in the FS are compares against the\n                                       namespace information from QuarkDB\nscaninterval         604800 [s] (7d)   target interval at which all file should be scanned\nscan_ns_rate         50 [Hz]           rate limit the requests to QuarkDB for the namespace scans\nscanrate             100 [MB/s]        rate limit bandwidth used by the scanner when reading files\n                                       from disk\n===================  ===============   ===========================================================\n\n**scan_disk_interval** and **scan_ns_interval** are skewed by a random factor per FS so that not all disks become busy at the same time.\n\nThe scan jobs are started with a lower IO priority class (using Linux ioprio_set) within EOS to decrease the impact on normal filesystem access, i.e. check logs for set io priority to 7 (lowest best-effort).\n\n.. code-block:: bash\n\n   210211 12:41:40 time=1613043700.017295 func=RunDiskScan              level=NOTE\n   logid=1af8cd9e-6c5e-11eb-ae37-3868dd2a6fb0 unit=fst@fst-9.eos.grid.vbc.ac.at:1095 tid=00007f98bebff700 source=ScanDir:446\n   tident=<service> sec=   uid=0 gid=0 name= geo=\"\" msg=\"set io priority to 7(lowest best-effort)\" pid=221712\n\n\nScan duration\n-------------\n\nThe first scan of a larger (fuller) FS can take several hours. Following scans will be much faster, within minutes (10-30min).\nSubsequent scans will only look at file that have not been scanned since scaninterval . i.e. each scan iteration will only look at a fraction of the files on disk, compare the logs for such a scan. (see the last line “scannedfiles” vs “skippedfiles” and the scanduration of 293s.)\n\n.. code-block:: bash\n\n   210211 12:49:44 time=1613044184.957472 func=RunDiskScan              level=NOTE  logid=1827f5ea-6c5e-11eb-ae37-3868dd2a6fb0    unit=fst@fst-9.eos.grid.vbc.ac.at:1095 tid=00007f993afff700 source=ScanDir:504                    tident=<service> sec=      uid=0 gid=0 name= geo=\"\" [ScanDir] Directory: /srv/data/data.01 files=147957 scanduration=293 [s] scansize=23732973568 [Bytes] [ 23733 MB ] scannedfiles=391 corruptedfiles=0 hwcorrupted=0 skippedfiles=147557\n\nError types detected by fsck\n-----------------------------\n\n(in decreasing priority)\n\n=============  ====================================================  ==============\nError          Description                                           Fixed by\n=============  ====================================================  =============\nd_mem_sz_diff  disk and reference size mismatch                      FsckRepairJob\nm_mem_sz_diff  MGM and reference size mismatch                       inspecting all the replicas or saved for manual inspection\nd_cx_diff      disk and reference checksum mismatch                  FsckRepairJob\nm_cx_diff      MGM and reference checksum mismatch                   inspecting all the replicas or saved for manual inspection\nunreg_n        unregistered file / replica                           (i.e. file on FS that has no entry in MGM) register replica if metadata match or drop if not needed\nrep_missing_n  missing replica for a file                            replica is registered on mgm but not on disk - FsckRepairJob\nrep_diff_n     replica count is not nominal (too high or too low)    fixed by dropping replicas or creating new ones through FsckRepairJob\norphans_n      orphan files (no record for replica/file in mgm)      no action at the MGM, files not referenced by MGM at all, moved to to .eosorphans directory on FS mountpoint\n=============  ====================================================  ========\n\n\nConfiguration\n--------------\n\nspace\n-----\n\nSome config items on the space are global, some are defaults (i.e. for newly created filesystems), see https://eos-docs.web.cern.ch/configuration/autorepair.html\n\nTo enable the FST scan you have to set the variable **scaninterval** on the space and on all file systems.\n\nThe intervals other than `scaninterval` are defaults for newly created filesystems. For an explanation. of the intervals see above.\n\n\n.. code-block:: bash\n\n   [root@mgm-1 ~]# eos space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   autorepair                       := on\n   [...]\n   scan_disk_interval               := 14400\n   scan_ns_interval                 := 259200\n   scan_ns_rate                     := 50\n   scaninterval                     := 604800\n   scanrate                         := 100\n   [...]\n\n\n\nfilesystem / FS\n---------------\n\nTo enable the FST scan you have to set the variable `scaninterval` on the space and on all file systems\n\n.. code-block:: bash\n\n   [root@mgm-1 ~]# eos fs status 1\n   # ------------------------------------------------------------------------------------\n   # FileSystem Variables\n   # ------------------------------------------------------------------------------------\n   bootcheck                        := 0\n   bootsenttime                     := 1612456466\n   configstatus                     := rw\n   host                             := fst-1.eos.grid.vbc.ac.at\n   hostport                         := fst-1.eos.grid.vbc.ac.at:1095\n   id                               := 1\n   local.drain                      := nodrain\n   path                             := /srv/data/data.00\n   port                             := 1095\n   queue                            := /eos/fst-1.eos.grid.vbc.ac.at:1095/fst\n   queuepath                        := /eos/fst-1.eos.grid.vbc.ac.at:1095/fst/srv/data/data.00\n\n   [...] defaults for these are taken from MGM, scanterval must be set!\n   scan_disk_interval               := 14400\n   scan_ns_interval                 := 259200\n   scan_ns_rate                     := 50\n   scaninterval                     := 604800\n   scanrate                         := 100\n\n   [...] various stat values reported back by the FST\n   stat.fsck.blockxs_err            := 1\n   stat.fsck.d_cx_diff              := 0\n   stat.fsck.d_mem_sz_diff          := 0\n   stat.fsck.d_sync_n               := 148520\n   stat.fsck.m_cx_diff              := 0\n   stat.fsck.m_mem_sz_diff          := 0\n   stat.fsck.m_sync_n               := 148025\n   stat.fsck.mem_n                  := 148526\n   stat.fsck.orphans_n              := 497\n   stat.fsck.rep_diff_n             := 5006\n   stat.fsck.rep_missing_n          := 0\n   stat.fsck.unreg_n                := 5003\n   [...]\n\n\nFfsck settings\n-------------\n\nWith the settings above, stats are collected on the FST (and reported in fs status) but no further action is taken. To setup of the fsck mechanism, see the eos fsck subcommands:\n\n`fsck stat`\n-----------\n\nGives a quick status of error stats collection and if the repair thread is active. The `eos fsck toggle-repair` and `toggle-collect` are really toggles. Use **eos fsck stat** to verify the correctness of your settings!\n\n.. code-block:: bash\n\n   [root@mgm-1 ~]# eos fsck stat\n   Info: collection thread status -> enabled\n   Info: repair thread status     -> enabled\n   210211 15:54:09 1613055249.712603 Start error collection\n   210211 15:54:09 1613055249.712635 Filesystems to check: 252\n   210211 15:54:10 1613055250.769177 blockxs_err                    : 118\n   210211 15:54:10 1613055250.769208 orphans_n                      : 92906\n   210211 15:54:10 1613055250.769221 rep_diff_n                     : 1226274\n   210211 15:54:10 1613055250.769224 rep_missing_n                  : 6\n   210211 15:54:10 1613055250.769231 unreg_n                        : 1221521\n   210211 15:54:10 1613055250.769235 Finished error collection\n   210211 15:54:10 1613055250.769237 Next run in 30 minutes\n\nThe collection thread will interrogate the FSTs for locally collected error stats at configured intervals (default: 30 minutes).\n\n`fsck report`\n-------------\n\nFor a more comprehensive error report, use **eos fsck report** this will only contain data once the error collection has started (also note the switch -a to show errors per filesystem FS)\n\n.. code-block:: bash\n\n   [root@mgm-1 ~]# eos fsck report\n   timestamp=1613055250 tag=\"blockxs_err\" count=43\n   timestamp=1613055250 tag=\"orphans_n\" count=29399\n   timestamp=1613055250 tag=\"rep_diff_n\" count=181913\n   timestamp=1613055250 tag=\"rep_missing_n\" count=4\n   timestamp=1613055250 tag=\"unreg_n\" count=180971\n\n\nRepair\n-------\n\nMost of the repair operations are implemented using the DrainTransferJob functionality.\n\nOperations\n-----------\n\nInspect FST local error stats\n-----------------------------\n\nUse **eos-leveldb-inspect** command to inspect the contents of the local database on the FSTs.\nThe local database contains all information (fxid, error type, etc) that will be collected\nby the mgm (compare the eos fs status <fsid> output).\n\n.. code-block:: bash\n\n   [root@fst-9 ~]# eos-leveldb-inspect  --dbpath /var/eos/md/fmd.0225.LevelDB --fsck\n   Num. entries in DB[mem_n]:                     148152\n   Num. files synced from disk[d_sync_n]:         148150\n   Num, files synced from MGM[m_sync_n]:          147723\n   Disk/reference size mismatch[d_mem_sz_diff]:   0\n   MGM/reference size mismatch[m_mem_sz_diff]:   140065\n   Disk/reference checksum mismatch[d_cx_diff]:  0\n   MGM/reference checksum mismatch[m_cx_diff]:   0\n   Num. of orphans[orphans_n]:                    427\n   Num. of unregistered replicas[unreg_n]:        5078\n   Files with num. replica mismatch[rep_diff_n]: 5081\n   Files missing on disk[rep_missing_n]:          0\n\nCheck fsck repair activity\n--------------------------\n\nSee if the fsck repair thread is active and how log its work queue is (cross check with log activity on mgm):\n\n.. code-block:: bash\n\n   [root@mgm-1 ~]# eos ns | grep fsck\n   ALL      fsck info                        thread_pool=fsck min=2 max=20 size=20 queue_size=562\n   ALL      tracker info                     tracker=fsck size=582\n   compare namespace stats for total count of fsck operations:\n\n\n   [root@mgm-1 ~]# eos ns stat | grep -i fsck\n   ALL      fsck info                        thread_pool=fsck min=2 max=20 size=20 queue_size=168\n   ALL      tracker info                     tracker=fsck size=188\n   all FsckRepairFailed              71.58 K     0.00     0.03     1.35     0.87     -NA-      -NA-\n   all FsckRepairStarted             63.19 M   857.75  1107.25  1112.05   918.32     -NA-      -NA-\n   all FsckRepairSuccessful          63.12 M   857.75  1106.88  1110.64   917.44     -NA-      -NA-\n\nLog examples\n------------\n\nStartup of FST service and initializing fsck threads:\n\n\n.. code-block:: bash\n\n    210211 12:41:39 time=1613043699.997897 func=ConfigScanner level=INFO  logid=1af5b7a8-6c5e-11eb-ae37-3868dd2a6fb0\n    unit=fst@fst-9.eos.grid.vbc.ac.at:1095 tid=00007f99497ff700 source=FileSystem:159 tident=<service> sec= uid=0 gid=0\n    name= geo=\"\" msg=\"started ScanDir thread with default parameters\" fsid=238\n\n   # NS scanner thread with random skew\n   210211 12:41:50 time=1613043710.000322 func=RunNsScan  level=INFO  logid=1af62382-6c5e-11eb-ae37-3868dd2a6fb0\n   unit=fst@fst-9.eos.grid.vbc.ac.at:1095 tid=00007f98e6bfe700 source=ScanDir:224 tident=<service> sec= uid=0 gid=0\n   name= geo=\"\" msg=\"delay ns scan thread by 38889 seconds\" fsid=239 dirpath=\"/srv/data/data.14\"\n\n\nsystemd ScanDir results\n-----------------------\n\nThese logs are also written to /var/log/eos/fst/xrdlog.fst\n\n.. code-block:: bash\n\n   Feb 11 12:41:33 fst-9.eos.grid.vbc.ac.at eos_start.sh[220738]: Using xrootd binary: /opt/eos/xrootd/bin/xrootd\n   Feb 11 12:49:44 fst-9.eos.grid.vbc.ac.at scandir[220738]: skipping scan w-open file: localpath=/srv/data/data.01/000006e3/010d045d fsid=226 fxid=010d045d\n   Feb 11 12:49:44 fst-9.eos.grid.vbc.ac.at scandir[220738]: [ScanDir] Directory: /srv/data/data.01 files=147957 scanduration=293 [s] scansize=23732973568 [Bytes] [ 23733 MB ] scanned...iles=147557\n   Feb 11 13:07:55 fst-9.eos.grid.vbc.ac.at scandir[220738]: [ScanDir] Directory: /srv/data/data.18 files=148074 scanduration=263 [s] scansize=17977114624 [Bytes] [ 17977.1 MB ] scann...iles=147730\n   Feb 11 13:08:36 fst-9.eos.grid.vbc.ac.at scandir[220738]: [ScanDir] Directory: /srv/data/data.22 files=147905 scanduration=258 [s] scansize=19978055680 [Bytes] [ 19978.1 MB ] scann...iles=147498\n   Feb 11 13:14:56 fst-9.eos.grid.vbc.ac.at scandir[220738]: [ScanDir] Directory: /srv/data/data.27 files=147445 scanduration=249 [s] scansize=15998377984 [Bytes] [ 15998.4 MB ] scann...iles=147119\n   fsck repairs. success/failure on MGM\n\n   210211 13:58:17 time=1613048297.294157 func=RepairReplicaInconsistencies level=INFO  logid=cf14c90e-6c68-11eb-becb-3868dd28d0c0 unit=mgm@mgm-1.eos.grid.vbc.ac.at:1094 tid=00007efd53bff700 source=FsckEntry:689                  tident=<service> sec=      uid=0 gid=0 name= geo=\"\" msg=\"file replicas consistent\" fxid=0028819b\n   210211 13:58:17 time=1613048297.294294 func=RepairReplicaInconsistencies level=INFO  logid=cf14c54e-6c68-11eb-becb-3868dd28d0c0 unit=mgm@mgm-1.eos.grid.vbc.ac.at:1094 tid=00007efd51bfb700 source=FsckEntry:689                  tident=<service> sec=      uid=0 gid=0 name= geo=\"\" msg=\"file replicas consistent\" fxid=00ef5955\n   210211 13:59:18 time=1613048358.345753 func=RepairReplicaInconsistencies level=ERROR logid=cf14c7ce-6c68-11eb-becb-3868dd28d0c0 unit=mgm@mgm-1.eos.grid.vbc.ac.at:1094 tid=00007efd523fc700 source=FsckEntry:663                  tident=<service> sec=      uid=0 gid=0 name= geo=\"\" msg=\"replica inconsistency repair failed\" fxid=0079b4d0 src_fsid=244\n\n\nNo repair action, file is being deleted\n---------------------------------------\n\nThe file has an FsckEntry i.e. is marked from repair, and was previously listed on the collected errors, but\n\n.. code-block:: bash\n\n   210211 16:27:45 time=1613057265.418302 func=Repair                   level=INFO  logid=b077de7c-6c7d-11eb-becb-3868dd28d0c0 unit=mgm@mgm-1.eos.grid.vbc.ac.at:1094 tid=00007efd95bff700 source=FsckEntry:773                  tident=<service> sec=      uid=0 gid=0 name= geo=\"\"\n   msg=\"no repair action, file is being deleted\" fxid=00033673\n   The file is noted as “being deleted” as its container (directory) does not exist anymore, i.e.\n\n\n   [root@mgm-1 ~]# eos fileinfo fxid:00033673\n   File: 'fxid:00033673'  Flags: 0600  Clock: 1662bb7c74f01d9f\n   Size: 0\n   Modify: Fri Jul 24 11:32:15 2020 Timestamp: 1595583135.037235673\n   Change: Fri Jul 24 11:32:15 2020 Timestamp: 1595583135.037235673\n   Birth: Fri Jul 24 11:32:15 2020 Timestamp: 1595583135.037235673\n   CUid: 12111 CGid: 11788 Fxid: 00033673 Fid: 210547 Pid: 0 Pxid: 00000000\n   XStype: adler    XS: 00 00 00 00    ETAGs: \"56518279954432:00000000\"\n   Layout: raid6 Stripes: 7 Blocksize: 1M LayoutId: 20640642 Redundancy: d0::t0\n   #Rep: 0\n   *******\n   error: cannot retrieve file meta data - Container #0 not found (errc=0) (Success)\n\n\nDiscrepancy reported errors\n----------------------------\n\n... between fsck report summary / per filesystem and fsck stat.\nEOS fsck report is giving different numbers for total report and per filesystem summary. This is expected.\n\nPer filesystem reports may contain error counts for individual replicas of a single file stored in EOS.\n **eos fsck stat** will reflect the per replica count, **eos fsck report** will show lower numbers,\nnot counting per each replica of a file.\n\n**example script**\n\n.. code-block:: bash\n\n   echo \"summed up by filesystem\"\n   ERR_TYPES=\"blockxs_err orphans_n rep_diff_n rep_missing_n unreg_n\"\n   for ETYPE in $ERR_TYPES; do\n   echo -n \"$ETYPE: \"\n   eos fsck report -a | grep $ETYPE  | awk '{print $4;}' | awk 'BEGIN{ FS=\"=\"; total=0}; { total=total+$2; } END{print total;}'\n   done\n\n   echo \"\"\n\n   echo \"eos fsck summary report\"\n   eos fsck report\n\n**output example**\n\n.. code-block:: bash\n\n   [root@mgm-1 ~]# ./eos_fsck_miscount.sh\n   summed up by filesystem\n   blockxs_err: 115\n   orphans_n: 95056\n   rep_diff_n: 1251566\n   rep_missing_n: 30\n   unreg_n: 1246475\n\n   eos fsck summary report\n   timestamp=1613069473 tag=\"blockxs_err\" count=43\n   timestamp=1613069473 tag=\"orphans_n\" count=29602\n   timestamp=1613069473 tag=\"rep_diff_n\" count=181913\n   timestamp=1613069473 tag=\"rep_missing_n\" count=28\n   timestamp=1613069473 tag=\"unreg_n\" count=180998\n"
  },
  {
    "path": "doc/citrine/configuration/fuse.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   pair: Mounting EOS; FUSE\n\n\nFUSE Client\n===========\n\nThe FUSE client allows to access EOS as a mounted file system.\n\nThere a two FUSE client modes available:\n\n.. epigraph::\n\n   ========= ===== ===================================================================\n   daemon    user  description\n   ========= ===== ===================================================================\n   eosd      !root An end-user private mount which is not shared between users\n   eosd      root  A system-wide mount shared between users\n   ========= ===== ===================================================================\n\n\n**eosd** End-User mount\n-----------------------\nThe end user mount supports the strong authentication methods in EOS:\n\n* **KRB5**\n* **X509**\n\nThe shared mount supports these authentication methods only in the CITRINE branch.\n\nMounting\n+++++++\n\nYou can mount an EOS instance using the EOS client:\n\n.. code-block:: bash\n\n   # mount my EOS instance to /home/user/eos\n\n   eos fuse mount /home/user/eos\n\nUn-Mounting\n+++++++++++\n\nYou can unmount an EOS instance using:\n\n.. code-block:: bash\n\n   # unmount my EOS instance from /home/user/eos\n\n   eos fuse umount /home/user/eos\n\n.. note::\n\n   The mount point can be given as an relative or absolute path!\n\n.. warning::\n\n   The mount point has to be a non-existing or an **empty** directory!\n\nAuthentication\n++++++++++++++\n\nThe authentication method is proposed by the EOS server and the client evaluates\nthe server list until it finds a matching one. You can test the used authentication\nmethod using (see the **authz:** field):\n\n.. code-block:: bash\n\n   [eosdevsrv1]# eos -b whoami\n   Virtual Identity: uid=755 (99,3) gid=1338 (99,4) [authz:krb5] sudo* host=localhost.localdomain geo-location=513\n\nIf the filesystem is mounted you can validate the same information using:\n\n.. code-block:: bash\n\n   [eosdevsrv1]# cat /home/user/eos/<instance>/proc/whoami\n\nLog File\n++++++++\n\nIn case of troubles you can find a log file for private mounts under ``/tmp/eos-fuse-<uid>.log``. If you run the single user\nmount as root, you find the log file in ``/var/log/eos/fuse/fuse.log``\n\n**eosd** Shared mount\n---------------------\nIf you have machines shared by many users like batch nodes it makes sense to use\nthe shared FUSE mount. The shared FUSE mount includes several high-performance add-ons.\n\nConfiguration\n+++++++++++++\n\nYou configure the FUSE mount via ``/etc/syconfig/eos`` (the first two variables **have to be defined**):\n\n.. code-block:: bash\n\n   # Directory where to mount FUSE\n   export EOS_FUSE_MOUNTDIR=/eos/\n\n   # MGM URL from where to mount FUSE\n   export EOS_FUSE_MGM_ALIAS=eosnode.foo.bar\n\n   # If the remote directory path does not match the local, you can define the remote path to be different -\n   # if not defined EOS_FUSE_REMOTEDIR=EOS_FUSE_MOUNTDIR is assumed e.g. local and remote tree have the same prefix\n   # export EOS_FUSE_REMOTEDIR=/eos/testinstance/subtree/\n\n   # Enable FUSE debugging mode (default off)\n   # export EOS_FUSE_DEBUG=1\n\n   # Disable PIO mode (used for high-preformance RAIN file access)\n   # export EOS_FUSE_NOPIO=1\n\n   # Disable multithreading in FUSE (default on)\n   # export EOS_FUSE_NO_MT=1\n\n   # Disable using access for access permission check (default on)\n   # export EOS_FUSE_NOACCESS=0\n\n   # Disable to use the kernel cache (default on)\n   # export EOS_FUSE_KERNELCACHE=0\n\n   # Bypass the buffercache for write - avoids 4k chopping of IO (default off)\n   # (this is not what people understand under O_DIRECT !!!!\n   # export EOS_FUSE_DIRECTIO=1\n\n   # Disable the write-back cache (default on)\n   # export EOS_FUSE_CACHE=0\n\n   # Set the write-back cache size (default 300M)\n   # export EOS_FUSE_CACHE_SIZE=0\n\n   # Set the write-back cache pagesize (default 256k)\n   # export EOS_FUSE_CACHE_SIZE=262144\n\n   # Use the FUSE big write feature ( FUSE >=2.8 ) (default on)\n   # export EOS_FUSE_BIGWRITES=1\n\n   # Mount all files with 'x' bit to be able to run as an executable (default off)\n   # export EOS_FUSE_EXEC=1\n\n   # Enable protection against recursive deletion (rm -r command)\n   #    starting from the root of the mount (if 1)\n   #    or from any of its sub directories at a maximum depth (if >1) (default 1)\n   # EOS_FUSE_RMLVL_PROTECT=1\n\n   # Enable Kerberos authentication. This avoid need to set gateways on the mgm.\n   #    file cache credential should be used. (default 0)\n   # EOS_FUSE_USER_KRB5CC=0\n\n   # Enable X509 GSI authentication. This avoid need to set gateways on the mgm.\n   #    file user proxy should be used. (default 0)\n   # EOS_FUSE_USER_GSIPROXY=0\n\n   # When strong authentication is used (EOS_FUSE_USER_KRB5CC=1 or/and EOS_FUSE_USER_GSIPROXY=1),\n   #    if no strong credentials is found, try to access using unix authentication as nobody\n   #    note: this does not require to configure the box as a gateway on the mgm but it requires that \"nobody\" is allowed there\n   # EOS_FUSE_FALLBACKTONOBODY=0\n\n   # If a connection fails using strong authentication, this is the timeout before actually retrying\n   #    in the meantime, all access by the concerned user will be rejected (indicating authentication failure)\n   #    !! WARNING: If a low value is used on a batch machine, it could have an impact on the authentication burden on the server side\n   #    On interactive servers, it will be the longest time taken between refreshing the credentials and this taking effect on the fuse mount\n   #    (default is XRD_STREAMERRORWINDOW default value)\n   # EOS_FUSE_STREAMERRORWINDOW=1\n\n   # If KRB5 or X509 are enabled, specify the mapping from pid to strong authentication\n   #    should be kept as symlinks under /var/run/eosd/credentials/pidXXXX\n   #    (default 0)\n   # EOS_FUSE_PIDMAP=0\n\n   # Enable FUSE read-ahead (default off)\n   # export EOS_FUSE_RDAHEAD=0\n\n   # Configure FUSE read-ahead window (default 128k)\n   # export EOS_FUSE_RDAHEAD_WINDOW=131072\n\n   # Show hidden files from atomic/versioning and backup entries (default off)\n   # export EOS_FUSE_SHOW_SPECIAL_FILES=0\n\n   # Show extended attributes related to EOS itself - this are sys.* and emulated user.eos.* attributes for files (default off)\n   # export EOS_FUSE_SHOW_EOS_ATTRIBUTES=0\n\n   # Add(OR) an additional mode mask to the mode shown (default off)\n   # export EOS_FUSE_MODE_OVERLAY=000     (use 007 to show things are rwx for w)\n\n   # Enable lazy open on read-only files (default off)\n   # export EOS_FUSE_LAZYOPENRO=1\n\n   # Enable lazy open on read-write files (default on\n   #    this option hides a lot of latency and is recommend to be used\n   #    it requires how-ever that it is supported by EOS MGM version\n   # export EOS_FUSE_LAZYOPENRW=1\n\n   # Enable asynchronous open of files\n   #    it is an optimization over the lazy_open hiding even more latency\n   #    it is used only if lazy-open is activated\n   # export EOS_FUSE_ASYNC_OPEN=1\n\n   # Set the kernel attribute cache time - this is the timewindow before you can see changes done on other clients\n   # export EOS_FUSE_ATTR_CACHE_TIME=10\n\n   # Set the kernel entry timeout - this is the time a directory listing is cached\n   # export EOS_FUSE_ENTRY_CACHE_TIME=10\n\n   # Set the timeout for the kernel negative stat cache\n   # export EOS_FUSE_NEG_ENTRY_CACHE_TIME=30\n\n   # Set the lifetime for a file creation ownership - withint this time each file re-open for update will be considered as cached locally and will not see remote changes\n   # export EOS_FUSE_CREATOR_CAP_LIFETIME=30\n\n   # Set the individual max. cache size per write-opened file where we have a creator capability\n   # export EOS_FUSE_FILE_WB_CACHE_SIZE=67108864\n\n   # Set the global maximum in-memory size for writeback files\n   # export EOS_FUSE_MAX_WB_INMEMORY_SIZE=536870912\n\n   # Configure a log-file prefix - useful for several FUSE instances\n   # export EOS_FUSE_LOG_PREFIX=dev\n   # => will create /var/log/eos/fuse.dev.log\n\n   # Configure multiple FUSE mounts a,b configured in /etc/sysconfig/eos.a /etc/sysconfig/eos.b\n   #export EOS_FUSE_MOUNTS=\"a b\"\n\n\nIn most cases one should enable the read-ahead feature with a read-ahead window of 1M on LAN and larger for WAN RTTs and if available use the big writes feature!\nIf you want to mount several EOS instances, you can specify a list of mounts using **EOS_FUSE_MOUNTS** and then configure these mounts in individual sysconfig files\nwith their name as suffix e.g. mount **dev** will be defined in ``/etc/sysconfig/eos.dev``. In case of a list of mounts the log file names have the name automatically inserted like ``fuse.dev.log``.\n\nStarting the Service\n++++++++++++++++++++\nOnce you configured the FUSE mountpoint(s) you can use standard service mechanism to start, stop and check your shared mounts:\n\n.. code-block:: bash\n\n   # start all eosd instances\n   service eosd start\n\n   # start a particular eosd instance\n   service eosd start myinstance\n\n   # stop all eosd instances\n   service eosd stop\n\n   # stop a particular eosd instance\n   service eosd stop myinstance\n\n   # check the status of all instances\n   service eosd status\n\n   # check the status of a particular instance\n   service eosd status myinstance\n\n   # if instances are up restart them conditional\n   service eosd condrestart [myinstance]\n\n   # shutdown/cleanup all eosd instances running as root\n   service eosd killall\n\nExample Configuration\n+++++++++++++++++++++\n\nHe is an example to configure two FUSE mounts from instance **use** and **public**\n\nDefine two FUSE mounts in /etc/sysconfig/eos\n\n.. code-block:: bash\n\n   # define which instance mounts we have configured\n   export EOS_FUSE_MOUNTS=\"user public\"\n\n   # #################################################################\n   # shared EOS FUSE options\n   # #################################################################\n   # in-memory write-back shared cache\n   export EOS_FUSE_CACHE_SIZE=268435456\n   # just normal logging\n   export EOS_FUSE_DEBUG=0\n   # not to verbose - just prints timing and errors\n   export EOS_FUSE_LOGLEVEL=5\n   # don't waste time to do parallel IO - only useful for RAIN layouts\n   export EOS_FUSE_NOPIO=1\n   # configure 256k readahead (additional to 128k kernel readahead)\n   export EOS_FUSE_RDAHEAD=1\n   export EOS_FUSE_RDAHEAD_WINDOW=262144\n   # stop rm -r for directories with deepness <=2\n   export EOS_FUSE_RMLVL_PROTECT=2\n   # configure JEMALLOC\n   test -e /usr/lib64/libjemalloc.so.1 && export LD_PRELOAD=/usr/lib64/libjemalloc.so.1\n\n   # #################################################################\n   # shared XrdCl options\n   # #################################################################\n   # tag xroot traffic\n   export XRD_APPNAME=eos-fuse\n   export XRD_CONNECTIONRETRY=4096\n   export XRD_CONNECTIONWINDOW=10\n   # keep connections to FSTs for 5 minutes\n   export XRD_DATASERVERTTL=300\n   # keep connections to MGM for 30 minutes\n   export XRD_LOADBALANCERTTL=1800\n   # standard verbosity for logging\n   export XRD_LOGLEVEL=Info\n   # don't follow more than 5 redirects\n   export XRD_REDIRECTLIMIT=5\n   # short request timeout of 60s - might be low for high throughput storage\n   export XRD_REQUESTTIMEOUT=60\n   export XRD_STREAMERRORWINDOW=15\n   export XRD_STREAMTIMEOUT=15\n   # interval how often timeouts are checked .. to get ~60s we have to set it to a second\n   export XRD_TIMEOUTRESOLUTION=1\n   # client worker thread pool\n   export XRD_WORKERTHREADS=16\n\n\nThen the individual part of each FUSE mount is described in two sysconfig files:\n\n**user**: ``/etc/sysconfig/eos.user``\n\n.. code-block:: bash\n\n   # from where do we mount ...\n   export EOS_FUSE_MGM_ALIAS=eosuser.cern.ch\n   # where to we mount\n   export EOS_FUSE_MOUNTDIR=/eos/user/\n\n**public**: ``/etc/sysconfig/eos.public``\n\n.. code-block:: bash\n\n   # from where do we mount ...\n   export EOS_FUSE_MGM_ALIAS=eospublic.cern.ch\n   # where to we mount\n   export EOS_FUSE_MOUNTDIR=/eos/public/\n\nAuthentication\n--------------\nThe shared FUSE mount currently support two authentication modes\n\n- gateway mode authentication\n- strong authentication mode featuring both **KRB5** and **X509**\n\nOnly one authentication mechanism can be used with a single shared mount\nand it is specified using the configuration entry EOS_FUSE_USER_KRB5CC mentioned above.\n\n\nAuthentication in gateway mode\n++++++++++++++++++++++++++++++\nEach machine running a shared FUSE mount has to be\nconfigured as a gateway machine in the MGM:\n\nAdd a FUSE host\n~~~~~~~~~~~~~~~\n\n.. code-block:: bash\n\n   vid add gateway fusehost.foo.bar unix\n\nIt is also possible now to add a set of hosts matching a hostname pattern:\n\n.. code-block:: bash\n\n   vid add gateway lxplus* sss\n\nRemove a FUSE host\n~~~~~~~~~~~~~~~~~~\n\n.. code-block:: bash\n\n   vid remove gateway fusehost.foo.bar unix\n\nTo improve security you can require **sss** (shared secret authentication) instead\nof **unix** (authentication) in the above commands\nand distribute the **sss** keytab file to all FUSE hosts ``/etc/eos.keytab``.\n\nStrong authentication mode\n++++++++++++++++++++++++++\nEnabling and configuring strong authentication is done using config keys\nEOS_FUSE_USER_KRB5CC, EOS_FUSE_USER_USERPROXY and EOS_FUSE_USER_KRB5FIRST (see above).\n\nEach linux session can be bound to one credential file.\nA same user can access the fuse mount using multiple identities using multiple instance.\nTo bind the current linux session to a credential file, the user has to use the script **eosfusebind**\n\nThe following command line\n\n.. code-block:: bash\n\n   eosfusebind krb5 [credfile]\n\ntries to find a krb5 credential cache file in the following order, stopping at the first match\n- optional credfile argument if specified\n- environment variable KRB5CCNAME\n- default location /tmp/krb5cc_<uid>\n\nThe following command line\n\n.. code-block:: bash\n\n   eosfusebind x509 [credfile]\n\ntries to find a x509 user proxy file in the following order, stopping at the first match\n- optional credfile argument if specified\n- environment variable X509_USER_PROXY\n- default location /tmp/x509up_u<uid>\n\nWarning, **eosfusebind** does not check that the credential file is valid.\nIt only checks it exists and has 600 permissions.\nThe actual authentication is carried out by the fuse mount.\nEvery time a new binding is made, all bindings from any terminated sessions (for the current user) are cleaned-up.\nBinding an already bound session replaces the previous binding.\n\nIt is possible to show the bindings for the current session or the current user with the following commands\n\n.. code-block:: bash\n\n   eosfusebind --show-session\n   eosfusebind --show-user\n\nIt is possible to unbind a given session or all the session of the current user using the following command\n\n.. code-block:: bash\n\n   eosfusebind --unbind-session\n   eosfusebind --unbind-user\n\nIf the process tries to access the fuse mount and if its session is not bound to a valid credential file, access will be refused.\n\nProtection against recursive top level deletion\n-----------------------------------------------\n\nThe configuration entry EOS_FUSE_RMLVL_PROTECT defined above allow to enable this protection.\nThis will deny any deletion to an 'rm -r' command starting from the top level directory of the fuse mount down to the specified depth.\n\nFor instance, if eos is mounted in ``/eos`` and if ``EOS_FUSE_RMLVL_PROTECT=3``, then:\n\n- ``rm /eos/*`` WILL run\n- ``rm -i -rf /eos`` will NOT run\n- ``rm -rf /eos/level2`` will NOT run\n- ``rm -r /eos/level2/level3`` will NOT run\n- ``rm -r /eos/level2/level3/level4`` WILL run.\n\nThe rule currently implemented is the following one:\n\nThe fuse mount will deny any removal coming from a command named ``rm`` with one of the short option(s) being ``r`` or one of the long option(s) being ``recursive``\nif one of the non optional arguments is a path located under the mountpoint at a depth lower than the value specified by ``EOS_FUSE_RMLVL_PROTECT``.\n\n**mount** and autofs support\n++++++++++++++++++++++++++++\nIf you have a defined FUSE instances and can manage them with the eosd service scripts, you use a mount wrapper to define mounts in /etc/fstab or mount manually.\n\n.. note::\n\n   You should make sure that you don't have **eosd** as a persistent service:\n   /sbin/chkconfig --del eosd\n\nTo mount **myinstance** to the local directory ``/eos/myinstance`` you can write:\n\n.. code-block:: bash\n\n   # mount\n   mount -t eos myinstance /eos/myinstance\n\n   # umount\n   umount /eos/myinstance\n\nTo define a FUSE mount in ``/etc/fstab`` you add for example:\n\n.. code-block:: bash\n\n   myinstance  /eos/myinstance defaults 0 0\n\nIf you want to use **autofs**, you have to create a file ``/etc/auto.eos`` :\n\n.. code-block:: bash\n\n   myinstance -fstype=eos :myinstance\n\nAdd to the file ``/etc/auto.master`` at the bottom:\n\n.. code-block:: bash\n\n   /eos /etc/auto.eos\n\nFor convenience make sure that you enable browsing in ``/etc/autofst.conf``:\n\n   browse_mode = yes  # this lets you see the mountdir myinstance in ``/eos/`` as ``/eos/myinstance/``. Once you access this directory it will be automatically mounted.\n\n\n\n.. note::\n\n   Enable **autofs** with ``service autofs start``\n\nExporting FUSE filesystems\n--------------------------\n\n\nFUSE export with NFS4\n+++++++++++++++++++++\n\nTo export FUSE via NFS4 you have to disable(shorten) the attribute caching in the FUSE configuration file:\n\n.. code-block:: bash\n\n   export EOS_FUSE_ATTR_CACHE_TIME=0.0000000000000001\n\nIf you mount an instance as /eos you have to configure an NFS export like this in /etc/exports:\n\n   /eos \\*.cern.ch(fsid=131,rw,insecure,subtree_check,async,root_squash)\n\nYou have to start/reload your nfs4 server and then you should be able to access the NFS volume using\n\n.. code-block:: bash\n\n   mount -t nfs4 <server> <localhost>\n\nFUSE export with CIFS/Samba\n+++++++++++++++++++++++++++\n\nTo export FUSE via Samba you have only to enable a mode overlay to avoid messages about permission problems during browsing in the FUSE configuration file:\n\n.. code-block:: bash\n\n   export EOS_FUSE_MODE_OVERLAY=077\n\n\nThe rest of the CIFS server configuration is identical to a local filesystem Samba export.\n"
  },
  {
    "path": "doc/citrine/configuration/fusex.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   pair: Mounting EOS; FUSEx\n\n\nFUSEx Client/Server\n===================\n\nThe FUSEx client is a more posix confirm and performant re-implementation of the FUSE client. It allows to access EOS as a mounted file system.\n\nThere a two FUSEx client modes available:\n\n.. epigraph::\n\n   ========= ===== ===================================================================\n   daemon    user  description\n   ========= ===== ===================================================================\n   eosxd     !root An end-user private mount which is not shared between users \n   eosxd     root  A system-wide mount shared between users\n   ========= ===== ===================================================================\n\n\nThe MGM requires an additional open port (default 1100) to distribute callbacks to clients using ZMQ as distribution network.\n\nLimiting Server Side FUSEx access\n-----------------------------------\n\n**eosxd** client rates  can be limited using the rate limiter interface available via the **access** command in the CLI.\n\n.. code-block:: bash\n\n   # limit the access for listing to 100 Hz per user\n   eos access set limit 100 rate:user:\\*:Eosxd::prot::LS\n\n   # limit the access for stats to 1000 Hz per user\n   eos access set limit 1000 rate:user:\\*:Eosxd::prot::STAT\n\n   # limit the access for returning list entries to 10 kHz per user\n   eos access set limit 10000 rate:user:\\*:Eosxd::ext::LS-Entry\n\n   # limit the access for meta-data updates to 1 kHz per user\n   eos access set limit 1000 rate:user:\\*:Eosxd::prot::SET\n   \n\nLS, STAT and SET limits are applied by the corresponding server side protocol methods. LS-Entry is applied when another LS call is requested. Please note the difference in the naming of the **prot** and **ext** counter types.\n"
  },
  {
    "path": "doc/citrine/configuration/geobalancer.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: GEO Balancer\n\nGEO Balancer\n==============================\n\nThe geo balancer uses the :doc:`converter` mechanism to redistribute files according\nto their geographical location. Currently it is only moving files with replica\nlayouts. To avoid oscillations a threshold parameter defines when geo balancing stops e.g.\nthe deviation from the average in a group is less then the threshold parameter.\n\nConfiguration\n-------------\nGEO balancing uses the relative filling state of a geo tag and not absolute byte\nvalues.\n\nGEO balancing is enabled/disabled by space:\n\n.. code-block:: bash\n\n   # enable\n   eos space config default space.geobalancer=on\n   # disable\n   eos space config default space.geobalancer=off\n\nThe current status of GEO Balancing can be seen via\n\n.. code-block:: bash\n\n   eos -b space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   ...\n   geobalancer                    := off\n   geobalancer.ntx                := 0\n   geobalancer.threshold          := 0.1\n   ...\n\nThe number of concurrent transfers to schedule is defined via the **geobalancer.ntx**\nspace variable:\n\n.. code-block:: bash\n\n   # schedule 10 transfers in parallel\n   eos space config default space.geobalancer.ntx=10\n\nThe threshold in percent is defined via the **geobalancer.threshold** variable:\n\n.. code-block:: bash\n\n   # set a 5 percent threshold\n   eos space config default space.geobalancer.threshold=5\n\nMake sure that you have enabled the converter and the **converter.ntx** space\nvariable is bigger than **geobalancer.ntx** :\n\n.. code-block:: bash\n\n   # enable the converter\n   eos space config default space.converter=on\n   # run 20 conversion transfers in parallel\n   eos space config default space.converter.ntx=20\n\nOne can see the same settings and the number of active conversion transfers\n(scroll to the right):\n\n.. code-block:: bash\n\n   eos space ls\n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   #     type #           name #  groupsize #   groupmod #N(fs) #N(fs-rw) #sum(usedbytes) #sum(capacity) #capacity(rw) #nom.capacity #quota #balancing # threshold # converter #  ntx # active #intergroup\n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   spaceview           default           22           22    202       123          2.91 T       339.38 T      245.53 T          0.00     on        off        0.00          on 100.00     0.00         off\n\n.. warning::\n   You have to configure geo mapping for clients, at least for the MGM machine,\n   otherwise EOS does not apply the geoplacement/scheduling algorithm and GEO\n   Balancing does not give the expected results!\n\n\nLog Files\n---------\nThe GEO Balancer has a dedicated log file under ``/var/log/eos/mgm/GeoBalancer.log``\nwhich shows basic variables used for balancing decisions and scheduled transfers. To get more\nverbose information you can change the log level:\n\n.. code-block:: bash\n\n   # switch to debug log level on the MGM\n   eos debug debug\n\n   # switch back to info log level on the MGM\n   eos debug info\n"
  },
  {
    "path": "doc/citrine/configuration/geoscheduling.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   pair: File Geoscheduling; GEOTAG\n\nFile Geoscheduling\n==================\n\nOverview\n--------\n\nThe EOS file scheduler is a core component of EOS which decides on which filesystems to place or access files.\nThis decision is based on:\n\n* the geotag of each filesystem\n* the state of each filesystem and of the machine hosting it\n* the geotag of the requesting client\n* the layout of the requested file\n* several admin-defined internal parameters\n* several admin-defined or user-defined directory attributes\n\nThis information is structured under the form of so-called *scheduling trees*\n-the shape of the trees being given by the geotags of the filesystems-.\nThere is one *scheduling tree* by scheduling group.\n\nThe file scheduler is a stateful component of which state is continuously updated to reflect the state of all\nthe filesystems involved in the instance.\n\nThe file scheduler is involved in ALL the file access/placement operations including file access/placement from clients,\nspace balancing and filesystem draining.\n\nThe interaction with the file geoscheduling is three-folded:\n\n* the geosched command that allows to view/set internal state/parameters of the GeoTreeEngine\n* geoscheduling related directory attributes that allow to alter the file scheduling in a directory-specific way.\n* geotag aware eos commands that can display useful information summarized along the scheduling trees\n\nInteracting with the GeoTreeEngine using the geosched command\n-------------------------------------------------------------\nThe GeoTreeEngine is a software component inside EOS in charge of keeping a consistent\nup-to-date view of each scheduling group. For each scheduling group, this view is summarized into\na *scheduling tree* and multiple *snapshots* of this scheduling tree, one for each type of access/placement operation.\nThese snapshots are then copied and used to serve all the file access/placement requests.\nTo achieve its tasks, the GeoTreeEngine has several features including:\n\n* a background *updater* which keeps snapshots and trees up-to-date. It updates snapshots and, only when needed, trees. Only when it is ultimately necessary, the updates on the snapshots are backported to the trees. It happens when a filesystem is added or removed from a scheduling group. So in general, snapshots have fresher information than trees. This is perfectly normal.\n* a *penalty system* which makes sure that some filesystems cannot be over-scheduled in bursts of requests. Atomic penalties can be self-estimated or fixed. These penalties are subtracted from the *dlscore* and the *ulscore* of the scheduled fs.\n* a *latency estimation system* which estimates how fresh is the information the state of the GeoTreeEngine is based on.\n\nInternal parameters\n~~~~~~~~~~~~~~~~~~~\nThe commands\n\n::\n\n   geosched show param\n\nand\n\n::\n\n   geosched set\n\nallow to view and set internal parameters of the GeoTreeEngine.\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/demo/> geosched show param\n   ### GeoTreeEngine parameters :\n   skipSaturatedAccess = 1\n   skipSaturatedDrnAccess = 1\n   skipSaturatedBlcAccess = 1\n   penaltyUpdateRate = 1\n   plctDlScorePenalty = 10(default) | 10(1Gbps) | 10(10Gbps) | 10(100Gbps) | 10(1000Gbps)\n   plctUlScorePenalty = 10(defaUlt) | 10(1Gbps) | 10(10Gbps) | 10(100Gbps) | 10(1000Gbps)\n   accessDlScorePenalty = 10(default) | 10(1Gbps) | 10(10Gbps) | 10(100Gbps) | 10(1000Gbps)\n   accessUlScorePenalty = 10(defaUlt) | 10(1Gbps) | 10(10Gbps) | 10(100Gbps) | 10(1000Gbps)\n   fillRatioLimit = 80\n   fillRatioCompTol = 100\n   saturationThres = 10\n   timeFrameDurationMs = 1000\n   ### GeoTreeEngine list of groups :\n   default.0 , default.1 , default.10 , default.11 , default.12 , default.13\n   default.14 , default.15 , default.16 , default.17 , default.18 , default.19\n   default.2 , default.20 , default.21 , default.22 , default.3 , default.4\n   default.5 , default.6 , default.7 , default.8 , default.9 ,\n\nHere follows the list of these parameters.\n\n.. epigraph::\n\n   ========================= ======================================================================\n   parameter                 definition\n   ========================= ======================================================================\n   *skipSaturatedAccess*     as *skipSaturatedPlct* but for access\n   *skipSaturatedDrnAccess*  as *skipSaturatedPlct* but for draining access\n   *skipSaturatedBlcAccess*  as *skipSaturatedPlct* but for balancing access\n   *penaltyUpdateRate*       weight of the penalty update at each time Frame. **0 means penalties are fixed**, 100 means that new values are estimated for each time frame regardless of the past. This parameter is used to ensure some stability for the penalties when they are self-estimated.\n   *plctDlScorePenalty*      atomic penalty applied to a fs download score on any type of placement operation. It is a vector indexed by the networking speed class of the file system.\n   *plctUlScorePenalty*      as *plctDlScorePenalty* but for the upload score\n   *accessDlScorePenalty*    as *plctDlScorePenalty* but for access operations.\n   *accessUlScorePenalty*    as *accessDlScorePenalty* but for the upload score\n   *fillRatioLimit*          fill ratio above which a filesystem should not be used for a placement or a RW access operation.\n   *fillRatioCompTol*        quantity by which fill ratio of two fs should differ to be considered as different. 100 means that whatever the fill ratios of two compared fs are, they will not be considered as different. The file scheduler, among other criterions, tries to balance fs fill ratios using this tolerance. As a consequence, if it is set to 10 it will try to get all the fill ratios equal in a 10% tol. **If this value is set to 100, there is no such inline space balancing**.\n   *saturationThres*         threshold under which a fs upload or download score makes a fs considered as saturated.\n   *timeFrameDurationMs*     periodicity of the internal state update (especially *snapshots* and possibly *trees*).\n   ========================= ======================================================================\n\nInternal state\n~~~~~~~~~~~~~~\nThe internal state of the GeoTreeEngine is essentially composed of *scheduling trees* and *snapshots*.\nThey can be displayed with commands\n\n::\n\n   geosched show tree\n   geosched show snapshot\n\n.. warning::\n\n      By design, information attached to the trees might not be up-to-date. Contrary to the snapshots that should be keep up-to-date.\n\nThe internal state also includes the penalty accounting table and the fs age/latency report. They can be displayed with the command\n\n::\n\n   geosched show state\n\nSome examples follow.\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/demo/> geosched show tree default.0\n   ### scheduling tree for scheduling group default.0 :\n   --------default.0 [3,9]\n          |----------site1 [1,3]\n          |         `----------rack1 [1,2]\n          |                   `----------1@lxfsrd47a04.cern.ch [1,1,UnvRW]\n          |\n          |\n          `----------site2 [2,5]\n                    |----------rack1 [1,2]\n                    |         `----------24@lxfsre13a01.cern.ch [1,1,UnvRW]\n                    |\n                    `----------rack2 [1,2]\n                              `----------46@lxfsrg15a01.cern.ch [1,1,UnvRW]\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/demo/> geosched show snapshot default.0\n   ### scheduling snapshot for scheduling group default.0 and operation 'Placement' :\n   --------default.0/( free:2|repl:0|pidx:1|status:OK|ulSc:99|dlSc:99|filR:0|totS:3.85797e+12)\n          |----------site1/( free:1|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)\n          |         `----------rack1/( free:1|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)\n          |                   `----------1/( free:1|repl:0|pidx:0|status:RW|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)@lxfsrd47a04.cern.ch\n          |\n          |\n          `----------site2/( free:1|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)\n                    |----------rack1/( free:1|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)\n                    |         `----------24/( free:1|repl:0|pidx:0|status:RW|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)@lxfsre13a01.cern.ch\n                    |\n                    `----------rack2/( free:0|repl:0|pidx:0|status:Dis|ulSc:0|dlSc:0|filR:0|totS:0)\n                              `----------46/( free:1|repl:0|pidx:0|status:DISRW|ulSc:99|dlSc:99|filR:0|totS:1.99091e+12)@lxfsrg15a01.cern.ch\n\n   ### scheduling snapshot for scheduling group default.0 and operation 'Access RO' :\n   --------default.0/( free:0|repl:0|pidx:1|status:OK|ulSc:99|dlSc:99|filR:0|totS:3.85797e+12)\n          |----------site1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)\n          |         `----------rack1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)\n          |                   `----------1/( free:0|repl:0|pidx:0|status:RW|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)@lxfsrd47a04.cern.ch\n          |\n          |\n          `----------site2/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)\n                    |----------rack1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)\n                    |         `----------24/( free:0|repl:0|pidx:0|status:RW|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)@lxfsre13a01.cern.ch\n                    |\n                    `----------rack2/( free:0|repl:0|pidx:0|status:Dis|ulSc:0|dlSc:0|filR:0|totS:0)\n                              `----------46/( free:0|repl:0|pidx:0|status:DISRW|ulSc:99|dlSc:99|filR:0|totS:1.99091e+12)@lxfsrg15a01.cern.ch\n\n   ### scheduling snapshot for scheduling group default.0 and operation 'Access RW' :\n   --------default.0/( free:0|repl:0|pidx:1|status:OK|ulSc:99|dlSc:99|filR:0|totS:3.85797e+12)\n          |----------site1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)\n          |         `----------rack1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)\n          |                   `----------1/( free:0|repl:0|pidx:0|status:RW|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)@lxfsrd47a04.cern.ch\n          |\n          |\n          `----------site2/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)\n                    |----------rack1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)\n                    |         `----------24/( free:0|repl:0|pidx:0|status:RW|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)@lxfsre13a01.cern.ch\n                    |\n                    `----------rack2/( free:0|repl:0|pidx:0|status:Dis|ulSc:0|dlSc:0|filR:0|totS:0)\n                              `----------46/( free:0|repl:0|pidx:0|status:DISRW|ulSc:99|dlSc:99|filR:0|totS:1.99091e+12)@lxfsrg15a01.cern.ch\n\n   ### scheduling snapshot for scheduling group default.0 and operation 'Draining Access' :\n   --------default.0/( free:0|repl:0|pidx:1|status:OK|ulSc:99|dlSc:99|filR:0|totS:3.85797e+12)\n          |----------site1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)\n          |         `----------rack1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)\n          |                   `----------1/( free:0|repl:0|pidx:0|status:RW|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)@lxfsrd47a04.cern.ch\n          |\n          |\n          `----------site2/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)\n                    |----------rack1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)\n                    |         `----------24/( free:0|repl:0|pidx:0|status:RW|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)@lxfsre13a01.cern.ch\n                    |\n                    `----------rack2/( free:0|repl:0|pidx:0|status:Dis|ulSc:0|dlSc:0|filR:0|totS:0)\n                              `----------46/( free:0|repl:0|pidx:0|status:DISRW|ulSc:99|dlSc:99|filR:0|totS:1.99091e+12)@lxfsrg15a01.cern.ch\n\n   ### scheduling snapshot for scheduling group default.0 and operation 'Draining Placement' :\n   --------default.0/( free:0|repl:0|pidx:1|status:OK|ulSc:99|dlSc:99|filR:0|totS:3.85797e+12)\n          |----------site1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)\n          |         `----------rack1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)\n          |                   `----------1/( free:1|repl:0|pidx:0|status:RW|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)@lxfsrd47a04.cern.ch\n          |\n          |\n          `----------site2/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)\n                    |----------rack1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)\n                    |         `----------24/( free:1|repl:0|pidx:0|status:RW|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)@lxfsre13a01.cern.ch\n                    |\n                    `----------rack2/( free:0|repl:0|pidx:0|status:Dis|ulSc:0|dlSc:0|filR:0|totS:0)\n                              `----------46/( free:1|repl:0|pidx:0|status:DISRW|ulSc:99|dlSc:99|filR:0|totS:1.99091e+12)@lxfsrg15a01.cern.ch\n\n   ### scheduling snapshot for scheduling group default.0 and operation 'Balancing Access' :\n   --------default.0/( free:0|repl:0|pidx:1|status:OK|ulSc:99|dlSc:99|filR:0|totS:3.85797e+12)\n          |----------site1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)\n          |         `----------rack1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)\n          |                   `----------1/( free:0|repl:0|pidx:0|status:RW|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)@lxfsrd47a04.cern.ch\n          |\n          |\n          `----------site2/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)\n                    |----------rack1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)\n                    |         `----------24/( free:0|repl:0|pidx:0|status:RW|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)@lxfsre13a01.cern.ch\n                    |\n                    `----------rack2/( free:0|repl:0|pidx:0|status:Dis|ulSc:0|dlSc:0|filR:0|totS:0)\n                              `----------46/( free:0|repl:0|pidx:0|status:DISRW|ulSc:99|dlSc:99|filR:0|totS:1.99091e+12)@lxfsrg15a01.cern.ch\n\n   ### scheduling snapshot for scheduling group default.0 and operation 'Draining Placement' :\n   --------default.0/( free:0|repl:0|pidx:1|status:OK|ulSc:99|dlSc:99|filR:0|totS:3.85797e+12)\n          |----------site1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)\n          |         `----------rack1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)\n          |                   `----------1/( free:1|repl:0|pidx:0|status:RW|ulSc:99|dlSc:99|filR:0|totS:1.86507e+12)@lxfsrd47a04.cern.ch\n          |\n          |\n          `----------site2/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)\n                    |----------rack1/( free:0|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)\n                    |         `----------24/( free:1|repl:0|pidx:0|status:RW|ulSc:99|dlSc:99|filR:0|totS:1.99291e+12)@lxfsre13a01.cern.ch\n                    |\n                    `----------rack2/( free:0|repl:0|pidx:0|status:Dis|ulSc:0|dlSc:0|filR:0|totS:0)\n                              `----------46/( free:1|repl:0|pidx:0|status:DISRW|ulSc:99|dlSc:99|filR:0|totS:1.99091e+12)@lxfsrg15a01.cern.ch\n\nThe internal state of the GeoTreeEngine is kept up-to-date by the background updater. It can be paused and resumed with the commands.\n\n::\n\n   geosched updater pause\n   geosched updater resume\n\n**A refresh of all the** *scheduling trees* **and** *snapshots* **can be obtained with the command**\n\n::\n\n   geosched forcerefresh\n\nBranch disabling\n~~~~~~~~~~~~~~~~\nThe GeoTreeEngine implements a mechanism to inhibit branches of the snapshots for selected types of operation.\nIt can be done for all the scheduling groups or only for specific ones.\nThe list of inhibited branches for each operation can be managed with the commands\n\n::\n\n   geosched disabled add\n   geosched disabled rm\n   geosched disabled show\n\n.. warning::\n   By default, placing data to ungeotagged fs is disabled. That means that for very basic instances (like dev ones), this disabling should be removed by the command\n\n   ::\n\n      geosched disabled rm nogeotag * *\n\nOne can foresee multiple applications for this. An example can be found in **the default value that forbids any placement operation to a non-geotagged filesystem**.\n\nGeoscheduling-related directory extended attributes\n---------------------------------------------------\nIn EOS, directories have several extended attributes to control the *placement policy* in multiple situations.\nThere are three types **placement policy**. Here follows a table with their definition depending on the file layout.\n\n.. epigraph::\n\n   ======= ====================================== ==================================================================== ============================\n   Layout     gathered:tag1::tag2                 hybrid:tag1::tag2                                                    scattered\n   ======= ====================================== ==================================================================== ============================\n   Replica all as close as possible to tag1::tag2 all-1 around tag1::tag2 and 1 as scattered as possible               all as scattered as possible\n   RAID    all as close as possible to tag1::tag2 all-n_parity around tag1::tag2 and n_parity as scattered as possible all as scattered as possible\n   ======= ====================================== ==================================================================== ============================\n\nThe following variables deal with the default *placement policy* in a directory.\n\n.. epigraph::\n\n   ================================= ======================================================================\n   parameter                                     definition\n   ================================= ======================================================================\n   sys.forced.placementpolicy        enforces to use a given placement policy for all file placements in the directory\n   sys.forced.nouserplacementpolicy  disables user defined placement policy for the directory\n   user.forced.placementpolicy       s.a.\n   user.forced.nouserplacementpolicy s.a.\n   ================================= ======================================================================\n\nFor more detailed information about these attributes, please refer to the help of the command\n\n::\n\n   attr\n\nThe file conversion command\n\n::\n\n   file convert\n\nsupports mentioning *placement policy*.\n\nThe file conversion feature of the :doc:`lru` is also *placement policy*-aware.\nThe extended directory attribute\n\n.. epigraph::\n\n   ================================== =\n   parameter\n   ================================== =\n   sys.conversion.\\<match_rule_name\\>\n   ================================== =\n\nsupports mentioning *placement policy*. For more detailed information about the syntax, please refer to the help of the command\n\n::\n\n   attr\n\n\nGeotag aware commands\n---------------------\nThe commands\n\n::\n\n   group ls\n   space ls\n\nboth feature a switch *-g <depth>* that allows to summarize the displayed information along the scheduling trees down to depth *<depth>*.\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/demo/> space ls -g 2\n   #-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   #     type #           name  #  groupsize #   groupmod #N(fs) #N(fs-rw) #sum(usedbytes) #sum(capacity) #capacity(rw) #nom.capacity #quota #balancing # threshold # converter #  ntx # active #intergroup\n   #-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   spaceview           default             0            0     67        66        272.69 G       133.62 T      131.62 T             0    off        off          20          on      2        0         off\n   #-------------------------------------------------------------------------------------------------------\n   #                         geotag   #N(fs) #N(fs-rw) #sum(usedbytes) #sum(capacity) #capacity(rw)\n   #-------------------------------------------------------------------------------------------------------\n                             <ROOT>       67        66        272.69 G       133.62 T      131.62 T\n                      <ROOT>::site1       23        23        105.72 G        45.79 T       45.79 T\n                      <ROOT>::site2       44        43        166.97 G        87.83 T       85.84 T\n               <ROOT>::site1::rack1       23        23        105.72 G        45.79 T       45.79 T\n               <ROOT>::site2::rack1       22        22         74.36 G        43.92 T       43.92 T\n               <ROOT>::site2::rack2       22        21         92.61 G        43.92 T       41.92 T\n"
  },
  {
    "path": "doc/citrine/configuration/geotags.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   pair: Storage and Client geo tagging; GEOTAG\n\n\nGeoTags\n=======\n\nIn a geographically distributed storage system it is important to tag each storage server with a geographical location.\nYou should group servers into LAN groups e.g. all servers which are close in term of network latency shared a GeoTag.\nGeoTags are just arbitrary strings (for the time being). Each FST defines in ``/etc/sysconfig/eos`` a GeoTag:\n\nStorage Node Tagging\n--------------------\n\n.. code-block:: bash\n\n   # The EOS host geo location tag used to sort hosts into geographical (rack) locations\n   export EOS_GEOTAG=\"CERN::513::1\"\n\n.. warning::\n   Each portion of the GeoTag string must be delimited by \"::\" and have a maximum length of 8 characters\n\nClient Tagging\n--------------\n\nTo optimise the network path it is not enough to tag only the storage server. EOS allows you to define IP prefixes to match a client\nto a certain location. This is done via the **vid** command:\n\n.. code-block:: console\n\n   eos vid -h\n   ...\n   vid set geotag <IP-prefix> <geotag>  : add to all IP's matching the prefix <prefix> the geo location tag <geotag>\n                                          N.B. specify the default assumption via 'vid set geotag default <default-tag>'\n\nAs an example we could define the default location to be CERN:\n\n\n.. code-block:: bash\n\n   eos vid set geotag default CERN\n\n\nWhile we want to assign all clients with 111.222.x.x. to CANADA:\n\n.. code-block:: bash\n\n   eos vid set 111.122. CANADA\n"
  },
  {
    "path": "doc/citrine/configuration/groupbalancer.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Group Balancer\n\nGroup Balancer\n==============================\n\nThe group balancer uses the :doc:`converter` mechanism to move files from groups\nabove a given threshold filling state to groups under the threshold filling\nstate. Once the groups fall within the threshold they no longer participate in\nbalancing and thus prevents further oscillations, once the groups are in a\nsettled state.\n\nGroup Balancer Engine\n---------------------\n\nFrom EOS 4.8.74 2 different balancer engines are supported which can be switched\nat runtime. A brief description of the various engines and their features are\ndescribed below. Please note that only one engine can be configured to run at a\ntime.\n\nStd\n~~~\n\nThis is the default engine, which uses deviation from the average groups filled\nto decide which groups are the outliers to be balanced. Both the deviation from\nthe left and right can be configured individually to further fine tune how the\ngroups are picked for balancing. The parameter is to be entered as percent value\nas deviation from average. Groups within the threshold values will not\nparticipate in balancing. Files from groups above the threshold will be picked\nat random within constraints (see `min/max_file_size` config below) and moved to\ngroups below threshold. The parameters expected for the engine are\n`max_threshold` and `min_threshold`, groups above max_threshold deviation from\naverage and below min_threshold deviation from average will be the participating\ngroups. For compatibility the currently ``groupbalancer.threshold`` will be as a\ndefault value in case both ``groupbalancer.min_threshold`` and\n``groupbalancer.max_threshold`` aren't provided. It is recommended to explicitly\nconfigure as this option may be removed in a future release.\n\nMinMax\n~~~~~~\n\nThis engine can be used as a stop gap engine to balance outliers, unlike the\nstd. engine no averages are computed, this engine takes static min & max\nthreshold values which are absolute `%` of groups fill ratio. Groups with usage\nabove the `max_threshold` (for eg 90%) will be chosen for filling to groups with\nusage below `min_threshold`. While for almost all common use cases std. engine\nshould fit the bill, when needing to do targeted balancing only on certain\noutliers this engine can be used as a temporary measure. This engine is only\nrecommended as a quick fix to balance outliers and then it is recommended to run\nthe std. engine to balance for longer periods of time.\n\n\nConfiguration\n-------------\nGroupbalancing is enabled/disabled by space:\n\n.. code-block:: bash\n\n   # enable\n   eos space config default space.groupbalancer=on\n   # disable\n   eos space config default space.groupbalancer=off\n\nThe current configuration of Group Balancing can be seen via\n\n.. code-block:: bash\n\n   eos -b space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   ...\n   groupbalancer                    := on\n   groupbalancer.engine             := std\n   groupbalancer.file_attempts      := 50\n   groupbalancer.max_file_size      := 20000000000\n   groupbalancer.min_file_size      := 1000000000\n   groupbalancer.max_threshold      := 5\n   groupbalancer.min_threshold      := 5\n   groupbalancer.ntx                := 1500\n   groupbalancer.threshold          := 1  # Deprecated, this value will not be used if min/max thresholds are set\n   ...\n\nThe ``max_file_size`` and ``min_file_size`` parameter decides the size of files\nto be picked for transfer. The ``file_attempts`` is the number of attempts the\nrandom picker will use to try to find a file within those sizes. For really\nsparse file systems, where the probability of finding a file within the size\nmight be lower, it is possible to tweak this number. The number of concurrent\ntransfers to schedule is defined via the **groupbalancer.ntx** space variable,\nthis is the number of transfers in every cycle of groupbalancer scheduling,\nwhich is every 10s. Hence it is recommended to set a min value in the hundreds\nor around 1000 (and watch the progress occasionally with eos io stat) if the\ngroups are really unbalanced:\n\n.. code-block:: bash\n\n   # schedule 10 transfers in parallel\n   eos space config default space.groupbalancer.ntx=1000\n\nConfigure the groupbalancer engine:\n\n.. code-block:: bash\n\n   # configure the goupbalancer engine\n   eos space config default space.groupbalancer.engine=std\n\nThe threshold in percent is defined via the **groupbalancer.min_threshold** &\n**groupbalancer.max_threshold** variable. For std. balancer engine this is a\npercent deviation from average:\n\n.. code-block:: bash\n\n   # set a 3 percent min threshold & 5 percent max threshold\n   eos space config default space.groupbalancer.min_threshold=3\n   eos space config default space.groupbalancer.max_threshold=5\n\nIn case you want to run the minmax balancer engine, here the values are\nabsolute values\n\n   # set a 3 percent min threshold & 5 percent max threshold\n   eos space config default space.groupbalancer.engine=minmax\n   eos space config default space.groupbalancer.min_threshold=60\n   eos space config default space.groupbalancer.max_threshold=80\n\n\nMake sure that you have enabled the converter and the **converter.ntx** space\nvariable is bigger than **groupbalancer.ntx** :\n\n.. code-block:: bash\n\n   # enable the converter\n   eos space config default space.converter=on\n   # run 20 conversion transfers in parallel\n   eos space config default space.converter.ntx=20\n\nOne can see the same settings and the number of active conversion transfers\n(scroll to the right):\n\n.. code-block:: bash\n\n   eos space ls\n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   #     type #           name #  groupsize #   groupmod #N(fs) #N(fs-rw) #sum(usedbytes) #sum(capacity) #capacity(rw) #nom.capacity #quota #balancing # threshold # converter #  ntx # active #intergroup\n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   spaceview           default           22           22    202       123          2.91 T       339.38 T      245.53 T          0.00     on        off        0.00          on 100.00     0.00         off\n\n\nStatus\n------\n\nStatus of the groupbalancer engine can be viewed with\n\n.. code-block:: bash\n\n   $ eos space groupbalancer status default\n   Engine configured          : Std\n   Current Computed Average   : 0.397366\n   Min Deviation Threshold    : 0.03\n   Max Deviation Threshold    : 0.05\n   Total Group Size: 25\n   Total Groups Over Threshold: 8\n   Total Groups Under Threshold: 12\n   # Detailed view of groups available with `--detail` switch\n   $ eos space groupbalancer status default --detail\n   engine configured          : Std\n   Current Computed Average   : 0.397258\n   Min Deviation Threshold    : 0.03\n   Max Deviation Threshold    : 0.05\n   Total Group Size: 25\n   Total Groups Over Threshold: 8\n   Total Groups Under Threshold: 12\n   Groups Over Threshold\n   ┌──────────┬──────────┬──────────┬──────────┐\n   │Group     │ UsedBytes│  Capacity│    Filled│\n   ├──────────┴──────────┴──────────┴──────────┤\n   │default.8      2.75 T     6.00 T       0.46│\n   │default.6      5.34 T     6.00 T       0.89│\n   │default.5      2.78 T     6.00 T       0.46│\n   │default.12     2.74 T     6.00 T       0.46│\n   │default.11     2.77 T     6.00 T       0.46│\n   │default.10     2.74 T     6.00 T       0.46│\n   │default.3      2.83 T     6.00 T       0.47│\n   │default.0      5.36 T     6.00 T       0.89│\n   └───────────────────────────────────────────┘\n\n   Groups Under Threshold\n   ┌──────────┬──────────┬──────────┬──────────┐\n   │Group     │ UsedBytes│  Capacity│    Filled│\n   ├──────────┴──────────┴──────────┴──────────┤\n   │default.9      2.19 T     6.00 T       0.36│\n   │default.7      2.18 T     6.00 T       0.36│\n   │default.24     1.78 T     6.00 T       0.30│\n   │default.21     2.20 T     6.00 T       0.37│\n   │default.2      1.47 G     6.00 T       0.00│\n   │default.18     1.86 T     6.00 T       0.31│\n   │default.17     2.17 T     6.00 T       0.36│\n   │default.20     1.81 T     6.00 T       0.30│\n   │default.15     1.80 T     6.00 T       0.30│\n   │default.14     6.10 G     6.00 T       0.00│\n   │default.13     2.15 T     6.00 T       0.36│\n   │default.1      1.75 T     6.00 T       0.29│\n   └───────────────────────────────────────────┘\n\nFor MinMax engines these numbers are absolute percent (for eg this was configured with 45 & 85)\n\n.. code-block:: bash\n\n   $ eos space groupbalancer status default\n   Engine configured: MinMax\n   Min Threshold    : 0.45\n   Max Threshold    : 0.85\n   Total Group Size: 25\n   Total Groups Over Threshold: 9\n   Total Groups Under Threshold: 4\n\nThere is a 60s cache for values, so if values are reconfigured\n\nTraffic from the groupbalancer is tagged as ``eos/groupbalancer`` and visible in iostat\n\n.. code-block:: bash\n\n   eos io stat -x\n    io │             application│    1min│    5min│      1h│     24h\n   └───┴────────────────────────┴────────┴────────┴────────┴────────┘\n   out        eos/groupbalancer  86.41 G 190.89 G   2.95 T  19.15 T\n   out          eos/replication        0   1.49 G  52.96 G  52.96 G\n   out                    other      605   1.33 K  10.77 K  64.73 K\n   in         eos/groupbalancer  18.91 G  85.30 G   2.83 T  19.04 T\n   in           eos/replication        0   1.43 G  52.90 G  52.90 G\n   in                     other      605   1.33 K  10.77 K  64.73 K\n\n\nLog Files\n---------\nThe Group Balancer has a dedicated log file under ``/var/log/eos/mgm/GroupBalancer.log``\nwhich shows basic variables used for balancing decisions and scheduled transfers. To get more\nverbose information you can change the log level:\n\n.. code-block:: bash\n\n   # switch to debug log level on the MGM\n   eos debug debug\n\n   # switch back to info log level on the MGM\n   eos debug info\n"
  },
  {
    "path": "doc/citrine/configuration/groupdrainer.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Group Drainer\n\nGroup Drainer\n=============\n\nThe group drainer uses the doc:`converter` mechanism to drain files from groups to target groups.\nFailed transfers are retried a configurable number of times before finally reaching either a\ndrained or drainfail status for a group. It uses an architecture similar to GroupBalancer with a\nspecial Drainer Engine which only looks for groups marked as *drain* as source groups. The target\ngroups are by default chosen as a threshold below the total group fillness average. Similar to\nconverter and groupbalancer this is enabled/disabled at a space level.\n\n\nConfiguration\n-------------\n\n.. code-block:: bash\n\n   # enable/disable\n   eos space config space.groupbalancer = <on/off>\n\n   # force a group to drain\n   eos group set <groupname> drain\n\n\n\n   # The list of various configuration flags supported in the eos cli\n   space config <space-name> space.groupdrainer=on|off                   : enable/disable the group drainer [ default=on ]\n   space config <space-name> space.groupdrainer.threshold=<threshold>    : configure the threshold(%) for picking target groups\n   space config <space-name> space.groupdrainer.group_refresh_interval   : configure time in seconds for refreshing cached groups info [default=300]\n   space config <space-name> space.groupdrainer.retry_interval           : configure time in seconds for retrying failed drains [default=4*3600]\n   space config <space-name> space.groupdrainer.retry_count              : configure the amount of retries for failed drains [default=5]\n   space config <space-name> space.groupdrainer.ntx                      : configure the max file transfer queue size [default=10000]\n\n\nThe `threshold` param by default is a percent threshold below the total computed average of all group fillness. If you want to ignore this and target\nevery available group, then threshold=0 will do that.\nThe `group refresh interval` determines how often we refresh the list of groups in the system, since this is not expected to change that often by\ndefault we only do it every 5 minutes (or when any groupdrainer config sees a change)\nThe `ntx` is the maximum amount of transfers we keep as active, it is okay to set this value higher than converter's ntx so that a healthy queue is maintained\nand the converter is kept busy. However if you want to reduce throughput, reducing the ntx will essentially throttle the files we schedule for transfers\nThe `retry_interval` and `retry_count` determine the amount of retries we do for a failed transfer. By default we try upto 5 times before giving up and\neventually marking the FS as drainfailed. This will need manual intervention similar to handling regular FS drains.\n\nStatus\n------\n\nCurrently a very minimal status command is implemented, which only informs about\nthe total transfers in queue and failed being tracked currently, in addition to\nthe count of groups in drain state and target groups. This is expected to change\nin the future with more information about the progress of the drain.\n\nThis command can be accessed via\n\n.. code-block:: bash\n\n   eos space groupdrainer status <spacename>\n\n\nRecommendations\n---------------\n\nIt is recommended not to drain FS individually within the groups that are marked as in drain state\nas the groupdrainer may target the same files targeted by the regular drainer and similarly they\nmay compete on drain complete statuses.\n\nGroupBalancer only targets groups that are not in drain state, so in groups in drain state will not\nbe picked as either source or target groups by the GroupBalancer. However if no threshold is configured\nthen we might end up in scenarios where a file is being targeted by GroupDrainer to a group that is\nrelatively full eventually forcing the GroupBalancer to also balance. To avoid this it is recommended to\nset the threshold so that only groups below average are targeted by GroupDrainer.\n\n\nCompletion\n----------\n\nIn a groupdrain scenario:\nAn individual FS is marked as either drained/drainfailed\n- When all the files in the FS are converted ie. transferred to other groups (`drained`)\n- There are some files which even after `retry_count` attempts were failing transfer (`drainfailed`)\n\n\nA groupdrain is marked as complete when all the FSes in a group are in drained or drainfailed mode.\nIn this scenario the group status is set as `drained` or `drainfailed`, which should be visible in the\n`eos group ls` command.\n"
  },
  {
    "path": "doc/citrine/configuration/http.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   pair: HTTP; WebDAV\n\nHTTP access\n=======================\n\nThe plain **HTTP** access is always up and running on the **MGM** on\nport **8000** and on **FSTs** on port **8001**.\nClients are mapped to 'nobody' if the authentication headers are missing\n(e.g. the access did not go via an HTTPS proxy in front).\n\nYou should make sure that access to the **MGM** on port **8000** is only possible from **HTTPS**\nproxies by setting up firewall rules.\n\nConfiguration\n-------------\n\nPreconditions\n+++++++++++++\n.. note::\n   To run the HTTPS proxy you need to have the **eos-nginx** RPM installed.\n\n.. code-block:: bash\n\n   yum install eos-nginx\n\nThe configuration for the NGINX HTTPS proxy server is ``/etc/sysconfig/nginx``.\nEach field in the configuration file is well documented.\n\nThe most important settings you might want to change are described in the following.\n\nCertificates\n++++++++++++\nLocation of host key and host certificate:\n\n.. code-block:: bash\n\n   export EOS_NGINX_SSL_CERTIFICATE=/etc/grid-security/hostcert.pem\n   export EOS_NGINX_SSL_KEY=/etc/grid-security/hostkey.pem\n\nPort of the HTTPS server with X509 certificate authentication:\n\n.. code-block:: bash\n\n   export EOS_NGINX_CLIENT_SSL_PORT=443\n\nKerberos Authentication\n+++++++++++++++++++++++\nPort of the HTTPS server with Kerberos5 authentication:\n\n.. code-block:: bash\n\n   export EOS_NGINX_CLIENT_SSL_PORT=443\n\nKerberos REALM and keytab file:\n\n.. code-block:: bash\n\n   export EOS_NGINX_GSS_KEYTAB=/etc/krb5.keytab\n   export EOS_NGINX_GSS_REALM=CERN.CH\n\nThe kerberos keytab file must be readable by the daemon account!\n\nFrontend- or Backend- Redirection\n+++++++++++++++++++++++++++++++++\nNGINX is configured by default to forward redirects to the client.\nHowever many WebDAV clients don't follow redirects. You can enable\ninternal (backend-) redirection proxying the full traffic like this:\n\n.. code-block:: bash\n\n   export EOS_NGINX_REDIRECT_EXTERNALLY=0\n\nDeployment on MGM or Gateway machines\n+++++++++++++++++++++++++++++++++++++\nIf you want to run a proxy on a different host than the MGM, you have to modify\n``/etc/nginx/nginx.eos.conf.template`` and replace **localhost** with the MGM host\nname.\n\n.. warning::\n   Make sure to configure appropriate firewall rules for *non-MGM* HTTPS proxy\n   deployments!\n\n.. code-block:: bash\n\n                  proxy_pass         http://localhost:8000/;\n\nUser Mapping\n------------\nThe **MGM** HTTP module does the user mapping based on the NGINX added authentication header.\nKerberos names are trivially mapped from their principal name, X509 users are mapped using\nthe default gridmapfile ``/etc/grid-security/grid-mapfile``.\nBy default all HTTP(S) traffic is mapped to nobody. To map users according to\ntheir authentication token enable HTTPS mapping in the virtual identity interface:\n\n.. code-block:: bash\n\n   eosdevsrv1 # eos -b vid enable https\n\nLog Files\n---------\nIf you didn't modify the NGINX configuration file, NGINX will produce two log information\nfiles with the access and error log ``/var/log/nginx/access.log`` and ``/var/log/nginx/error.log``.\n\nThe **MGM** writes a HTTP related log file under ``/var/log/eos/mgm/Http.log``.\n\nTo get more\nverbose information you can change the log level:\n\n.. code-block:: bash\n\n   # switch to debug log level on the MGM\n   eos debug debug\n\n   # switch back to info log level on the MGM\n   eos debug info\n\nSE Linux\n--------\n\nMake sure that your proxy host is not blocking outgoing connections for NGINX to the backend MGM/FST servers (port 8000/8001). The same is true for the incoming\nconnections on the HTTPS ports.\n\nProxy Certificates\n------------------\n\n.. warning::\n   NGINX supports proxy certificates only if they are RFC compliant!\n\nYou should create them e.g. with **grid-proxy-init** using the **-rfc** flag:\n\n.. code-block:: bash\n\n   grid-proxy-init -rfc\n\nFile Sharing Links\n------------------\n\nThe web interface shows for each file a small link icon. By clicking on this icon a window appears showing two HTML links, one for ROOT and one for HTTP. If you copy the link address you get\na ROOT or HTTP URL which one can use to share a file for reading bypassing all ACL and permission settings in EOS.\nThe same URLs can be obtained with the EOS shell using\n\n.. code-block:: bash\n\n   eos file share myfile\n"
  },
  {
    "path": "doc/citrine/configuration/http_tpc.rst",
    "content": ".. _http_tpc:\n\n.. highlight:: rst\n\n.. index::\n   pair: XrdHttp; TPC\n\nHTTP(XrdHttp) and XRootD TPC with delegated credentials\n########################################################\n\nThere are several ways in which a third-party transfer can be triggered in an\nXRootD based system like EOS. Currently EOS supports third-party-copy transfers\nfor both the XRootD and HTTP protocol.\n\nDepending on the authentication/authorization model there are several ways in which\na third-party-copy transfer can proceed but they fall in the following broad\ncategories:\n\n  - `XRootD TPC without delegated credentials`_\n  - `XRootD TPC with delegated credentials`_\n  - `HTTP(S) support and HTTP TPC with token authentication`_\n\nXRootD TPC without delegated credentials\n*****************************************\n\nEOS enforces authentication and authorization of client on the MGM node and\nsupports the following authentication mechanisms:\n  - `KRB5 (Kerberos 5) <https://xrootd.slac.stanford.edu/doc/dev49/sec_config.htm#_Toc517294110>`_\n  - `GSI certificates <https://xrootd.slac.stanford.edu/doc/dev49/sec_config.htm#_Toc517294098>`_\n  - `SSS (Simple Shared Secret) <https://xrootd.slac.stanford.edu/doc/dev49/sec_config.htm#_Toc517294117>`_\n\nThe storage nodes (FSTs) on the other hand, accept **unix** and **SSS**\nauthentication, relying on the encrypted opaque information that the MGM\nprovides to the client when redirecting to decide if the transfer is allowed\nor not.\n\nBy default, all outbound connections from the FST daemon to any other endpoint\nhave the **SSS** authentication mechanism enforced. Due to this, a TPC transfer\nbetween EOS instances that don't share the same SSS key is impossible. On the\nother hand, TPC transfers within the same instance will always work and this\nfunctionality is heavily used internally for draining, balancing and other\nmaintenance operations. To relax this constraint and allow non-secure connection\nfrom the FSTs nodes to other endpoints, the service manager can set the following\nenvironment variable to disable **SSS** enforcement.\n\n.. code-block:: bash\n\n  # File /etc/sysconfig/eos_env\n  EOS_FST_NO_SSS_ENFORCEMENT=1\n\nWhile this option can easily be enabled in different EOS services managed by\nthe same organization, this becomes impossible when one of the TPC endpoints\nis not an EOS instance or is managed by a different entity.\n\nThe TPC model in XRootD is pull based. Therefore, TPC transfers that have the\nEOS endpoint as source of the transfer work no matter the configuration setup,\nwhile TPC transfers with EOS as the destination will fail without disabling the\nSSS enforcement. A simple way to trigger a TPC transfer is by using the **xrdcp**\ncommand with the following options:\n\n.. code-block:: bash\n\n   xrdcp --tpc only root://eos1.cern.ch//path/to/source root://eos1.cern.ch//path/to/destination\n\n\nXRootD TPC with delegated credentials\n**************************************\n\nIn order to enable more complex scenarios and to provide a viable alternative\nto the GridFTP service, the XRootD client starting with version 4.10.0 supports\nclient credential delegation. Direct transfers with delegated credentials against\nEOS instances work out of the box without any configuration changes.\n\nOn the other hand, for TPC transfers with delegated credentials to be supported\nby an EOS instance there are several modifications needed. All these changes are\nneed to accommodate the fact that there is no actual authentication of the client\non the FST side, therefore there is no credential information to be delegated.\n\nFirst of all, the EOS service manager will need to deploy a new XRootD Proxy\nservice that will act as a gateway for incoming TPC traffic. As mentioned in the\nprevious section, TPC transfers where EOS is the source work perfectly fine\nwithout any configuration changes. The gateway is a vanilla **XRootD PSS**\nservice with the following reference configuration:\n\n.. code-block:: bash\n   :linenos:\n\n   ofs.osslib  libXrdPss.so\n   ofs.ckslib  * libXrdPss.so\n   xrootd.chksum  adler32\n   xrootd.seclib  libXrdSec.so\n   pss.origin  eos-target-instance.cern.ch:1094\n   all.export  /eos/\n   all.adminpath  /var/spool/xrootd\n   all.pidpath  /var/run/xrootd\n   sec.protocol  gsi -dlgpxy:1 -exppxy:=creds -crl:1 -moninfo:1 -cert:/etc/grid-security/daemon/gridftp-cert.pem -key:/etc/grid-security/daemon/gridftp-key.pem -gridmap:/etc/grid-security/grid-mapfile -d:1 -gmapopt:2\n   sec.protbind  * gsi\n   ofs.tpc  autorm fcreds gsi =X509_USER_PROXY ttl 60 60 xfr 9 pgm /usr/local/bin/xrootd-third-party-copy.sh\n\n\nThe only configuration option to be modified for new setups is the **pss.origin**\nthat needs to point to the EOS MGM node. Particular care should be taken when\ntyping the **ofs.tpc** directive to follow the exact format of the options present\nin the example above. Support for delegated credentials also requires subtle\nchanges to the **sec.protocol** directive that are clearly explained in the\nXRootD documentation and already present in the provided example.\n\n.. The :ref:`helper script <xrootd-third-party-copy>` referenced in the configuration\nThe ``xrootd-third-party-copy.sh`` referenced in the configuration\nmakes use of specific environment variables exported by the XRootD PSS service\nin the context of the TPC process doing the transfer.\n\n.. :caption: Contents of the xrootd-third-party-copy.sh file\n.. :name: xrootd-third-party-copy\n\n.. code-block:: bash\n\n   #!/bin/bash\n   dst='root://'$XRDXROOTD_ORIGIN'/'$2\n   /usr/bin/xrdcp --server -d 3 $1 $dst\n\n\nOnce the XRootD gateway is setup, the EOS MGM configuration needs to be updated\nso that any incoming TPC transfers with delegated credentials where EOS is the\ndestination endpoint are redirected to the gateway node. This is done by adding\nthe following directive to the default EOS MGM configuration file located in\n``/etc/xrd.cf.mgm``:\n\n.. code-block:: bash\n\n   ofs.tpc  redirect delegated eos-gateway-node.cern.ch:1094\n\nIn order to trigger a TPC transfer with delegated credentials the user needs to\nhave a valid X509 certificate that the xrdcp command can use during the transfer.\nThe xrdcp command will automatically pick up the user certificate by using the\nfollowing environment variables:\n\n.. code-block:: bash\n\n   # Set the path for X509 user \"foo\"\n   export X509_USER_CERT=/home/foo/.globus/usercert.pem\n   export X509_USER_KEY=/home/foo/.globus/userkey.pem\n\nThe xrdcp command can also use a user proxy certificate to trigger a TPC transfer\nwith delegated credentials. The easiest way for a user to obtain a proxy\ncertificate is to use the ``voms-proxy-init`` tool form the ``voms-client-cpp``\npackage.\n\n.. code-block:: bash\n\n   voms-proxy-init\n   voms-proxy-info\n   subject   : /DC=ch/DC=cern/OU=Organic Units/OU=Users/CN=foo/CN=007/CN=Foo Bar/CN=220482279\n   issuer    : /DC=ch/DC=cern/OU=Organic Units/OU=Users/CN=foo/CN=007/CN=Foo Bar\n   identity  : /DC=ch/DC=cern/OU=Organic Units/OU=Users/CN=foo/CN=007/CN=Foo Bar\n   type      : RFC compliant proxy\n   strength  : 512 bits\n   path      : /tmp/x509up_u1001\n   timeleft  : 11:53:16\n\nTo make sure we enforce GSI authentication and trigger the delegation of\ncredentials we can also set the **XrdSecPROTOCOL** environment variable together\nwith the following options for the xrdcp command:\n\n.. code-block:: bash\n\n   XrdSecPROTOCOL=gsi,unix xrdcp --tpc delegate only root://eos1.cern.ch//path/to/source root://other.world.com//path/to/destination\n\nThe minimum requirements for this setup to work correctly are the following:\n\n  - XRootD PSS gateway >= 4.11.1\n  - EOS instance >= 4.6.8\n  - User XRootD client triggering the TPC transfer >= 4.11.1\n\n\nHTTP(S) support and HTTP TPC with token authentication\n*******************************************************\n\nEOS supports HTTP access by making use of the XrdHttp plugin which comes by\ndefault with XRootD. There are several configuration changes that need to be\nmade both on the MGM side and on the FST side to have this setup working.\n\nApart from basic HTTP(S) access with client certificates, EOS also supports\nHTTP(S) with token authentication starting with version 4.6.8. There\nare several extra packages that need to be installed on the MGM node to\nenable this feature:\n\n  - **xrdhttpvoms** package which allows the HTTP module to handle proxy\n    certificates from the clients. This can be found in the EPEL repository.\n  - **eos-scitokens** and **eos-scitokens-debuginfo** packages to enable\n    support for SciTokens in EOS. These packages can be found in the\n    `eos-depend repository <http://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-7/x86_64/>`_.\n    Starting with EOS 5.0.16 there is no need to install the *eos-scitokens*\n    package as EOS can use the default library provided by the XRootD framework,\n    namely *libXrdAccSciTokens.so* coming from the *xrootd-server* package.\n\nThe following packages are not mandatory but they provide convenient tools\nfor testing the token support against the EOS instance:\n\n  - **x509-scitokens-issuer** and **x509-scitokens-issuer-client** that provide\n    tools like **macaroon-init** useful when trying to acquire a macaroons for\n    testing purposes. They can be found here: http://koji.chtc.wisc.edu/kojifiles/packages/\n\nSupport for HTTP(S) access in EOS is provided through an HTTP external handler\nplug-in library which is distributed by default with any EOS version called\n**libEosMgmHttp.so**.\n\nBelow you can find a reference configuration file that will enable HTTP(S) support\nand HTTP TPC with both macaroons and scitokens on the MGM. Each line\ncontains a description of the functionality provided.\n\n.. :caption: Contents of /etc/xrd.cf.mgm file\n.. :linenos:\n\n.. code-block:: bash\n\n   # Load and enable HTTP(S) access on port 9000 on the current instance\n   xrd.protocol XrdHttp:9000 /usr/lib64/libXrdHttp.so\n   # Directory containing CA certificates to be used by the server\n   http.cadir /etc/grid-security/certificates/\n   # File containing the x509 server certificate\n   http.cert /etc/grid-security/daemon/hostcert.pem\n   # File containing the x509 server private key\n   http.key /etc/grid-security/daemon/hostkey.pem\n   # Path to the \"grid map file\" to be used for mapping users to specific identities\n   http.gridmap /etc/grid-security/grid-mapfile\n   # Load security extractor plugin able to deal with proxy certificates and VOMS credentials\n   http.secxtractor libXrdVoms.so\n   # Optionally enable tracing on the HTTP plugin\n   http.trace all\n   # Load the XrdTpc external handler which deals only with COPY and OPTIONS http\n   # verbs and provides the default HTTP TPC functionality\n   http.exthandler xrdtpc /usr/lib64/libXrdHttpTPC.so\n   # Load the EOS specific HTTP external handler libEosMgmHttp.so and also specify\n   # the option is HTTP traffic is to be redirected to HTTP(S)\n   http.exthandler EosMgmHttp /usr/lib64/libEosMgmHttp.so eos::mgm::http::redirect-to-https=0\n   # The following two external library plugins are used to provide support for\n   # token based authentication with Macaroons and SciTokens. Presence of the\n   # second library is optional. When the SciTokens library is present and the\n   # XrdMacaroons can not deal with the request then this is delegated to the\n   # SciTokens library.\n   # Note: Until eos-5.0.15 one needs to use the EOS specific SciTokens library\n   # mgmofs.macaroonslib /usr/lib64/libXrdMacaroons.so /usr/lib64/libEosAccSciTokens.so\n   # Starting with eos-5.0.16 one can use the XRootD provided SciTokens library\n   mgmofs.macaroonslib /usr/lib64/libXrdMacaroons.so /usr/lib64/libXrdAccSciTokens.so\n   # Base64-encoded secret key used for generating macroons. A simple way to\n   # generate such a secret key is to use the following command:\n   # openssl rand -base64 -out /etc/eos.macaroon.secret 64\n   macaroons.secretkey /etc/eos.macaroon.secret\n   # Optionally enable tracing for the XrdMacaroons plugin\n   macaroons.trace all\n   # Mandatory sitename configuration for the XrdMacaroons library which is also\n   # embedded in the macaroons attributes\n   all.sitename eosdev\n\nA simple method of generating a valid ``/etc/eos.macaroon.secret`` file is:\n\n.. :caption: Generating an /etc/eos.macaroon.secret file\n\n.. code-block:: bash\n\n   openssl rand -base64 -out /etc/eos.macaroon.secret 64\n\n\nThe **XrdAccSciTokens** library relies on the default **XRootD Authorization**\nplugin to be loaded, which in turn checks that the file ``/opt/xrd/etc/Authfile``\nfile exists. Therefore, one needs to ensure the path exists and that the file is\nowned by daemon:daemon user under which the MGM service runs. The service\nmanager also needs to put in place the basic configuration for SciTokens support\nthat relies on the ``/etc/xrootd/scitokens.cfg`` file. This file contains\ninformation about the IAM (Identity and Access Management) provider that the\nclient/MGM service will contact for SciTokens support. A reference ``scitokens.cfg``\nfile is provided below:\n\n.. :caption: Contents of the /etc/xrootd/scitokens.cfg file\n\n.. code-block:: bash\n\n   [Global]\n   audience = https://wlcg.cern.ch/jwt/v1/any\n\n   [Issuer OSG-Connect]\n   issuer = https://wlcg.cloud.cnaf.infn.it/\n   base_path = /\n   map_subject = False\n   default_user = dteam001\n\nAn important configuration option is the **default_user** field which specifies\nthe local username (i.e. known to the MGM) that any token issued by the given IAM\nis mapped to.\n\nApart from the **MGM**, all the **FST** configurations also need to be updated in\norder to support HTTP(XrdHttp) and HTTP TPC access.\n\n.. :caption: Contents of the /etc/xrd.cf.fst file relevant for HTTP config\n\n.. code-block:: bash\n\n   # Enable the XrdHttp plugin and listen on port 9001 for connections\n   xrd.protocol XrdHttp:9001 /usr/lib64/libXrdHttp.so\n   # Load the libEosFstHttp external handler\n   http.exthandler EosFstHttp /usr/lib64/libEosFstHttp.so none\n   # Load the XrdTpc external handler which deals with COPY and OPTIONS http\n   # verbs and provides the default HTTP TPC functionality\n   http.exthandler xrdtpc /usr/lib64/libXrdHttpTPC-4.so\n\nThe port specified int the **xrd.protocol** directive is specific to the XrdHttp\nplugin implementation and must be properly configured depending on the\nenvironment variable **EOS_FST_HTTP_PORT**. The XrdHttp target port redirection\nis advertised from the FST to the MGM and represents the port location\nwhere MGM will redirect incoming clients requesting HTTP(S) access to the data.\n\nThis can easily be done by adding a systemd custom configuration file for the\nFST service in ``/usr/lib/systemd/system/eos@fst.service.d/custom.conf``.\n\n.. :caption: Contents of the custom.conf file\n\n.. code-block:: bash\n\n   [Service]\n   Environment=EOS_FST_HTTP_PORT=9001\n\nAfter starting the EOS service, one can check for the actual value of the HTTP\nport advertised by the individual FSTs by executing the following command:\n\n.. code-block:: bash\n\n   eos fs status 1 | grep http\n   stat.http.port 9001\n\nIn order to have the identity embedded in the tokens (macaroon/scitoken) properly\nmapped to the local identity used in EOS, one also needs to enable the **https vid**\nmapping:\n\n.. :caption: Enable vid https mapping\n\n.. code-bloc:: bash\n\n   eos vid enable https\n\n\nPractical examples for HTTP(S) transfers\n*****************************************\n\nThis section contains several examples of HTTP transfers done against an EOS\ninstance configured with support for certificates, token authorization and\nwith HTTP TPC. To trigger such transfers we'll make use of the **curl** command\nwhich one of the most feature rich and reliable tools for testing HTTP access\nand is also used in it's turn by other client tools that enable HTTP transfers\nlike for example **davix**.\n\nHTTP transfers with X509 credentials\n------------------------------------\n\nThe assumption here is that the client has a valid certificate and decoded private\nkey available. To trigger a simple upload to EOS one can use the following command:\n\n.. code-block:: bash\n\n   curl -L -v --capath /etc/grid-security/certificates --cert ~/.globus/usercert.pem --cacert ~/.globus/usercert.pem --key ~/.globus/userkey.pem https://e0.cern.ch:9000//eos/dev/replica/file1.dat --upload-file /etc/passwd\n\n\n   [esindril@esdss000 build_clang_ninja]$ sudo eos fileinfo /eos/dev/replica/file1.dat\n   File: '/eos/dev/replica/file1.dat'  Flags: 0644\n   Size: 3314\n   Modify: Wed Jan 29 14:54:20 2020 Timestamp: 1580306060.468009000\n   Change: Wed Jan 29 14:54:20 2020 Timestamp: 1580306060.459330223\n   Birth : Wed Jan 29 14:54:20 2020 Timestamp: 1580306060.459330223\n   CUid: 58602 CGid: 1028  Fxid: 00015ac5 Fid: 88773    Pid: 11   Pxid: 0000000b\n   XStype: adler    XS: 74 d7 7c 3a    ETAGs: \"23829820735488:74d77c3a\"\n   Layout: replica Stripes: 2 Blocksize: 4k LayoutId: 00100112\n   #Rep: 2\n   ┌───┬─────┬───────────┬──────────┬──────────────┬───────┬────────────┬────────┬──────┬──────┐\n   │no.│fs-id│       host│schedgroup│          path│   boot│configstatus│   drain│active│geotag│\n   └───┴─────┴───────────┴──────────┴──────────────┴───────┴────────────┴────────┴──────┴──────┘\n    0       5  e0.cern.ch  default.0 /home/../fst5  booted            rw nodrain  online  elvin\n    1       1  e0.cern.ch  default.0 /home/../fst1  booted            rw nodrain  online  elvin\n\nWhen doing such a transfer the \"grid map file\" specified in the configuration of\nthe MGM node is used to map the client DN to a known local identity.\n\nHTTP transfers with Macaroon authentication\n--------------------------------------------\n\nTo trigger a HTTP transfer using a Macaroon token, we first need to acquire a\nMacaroon from the EOS MGM endpoint using our X509 certificate and then use this\nmacaroon to authenticate/authorize the transfer. The macaroon token will embed\nthe username from the X509 certificate (or the mapped identity from the\n\"grid map file)\" so that when the token request is issued the client identity\non the server side will be mapped to this username.\n\n.. :caption: Requesting a macaroon using a X509 certificate.\n\n.. code-block:: bash\n\n   # Make sure the following environment variables point to the client\n   # certificate and private key\n   X509_USER_CERT=/home/esindril/.globus/usercert.pem\n   X509_USER_KEY=/home/esindril/.globus/userkey.pem\n   # Use the macaroon-init tool to request a macaroon\n   macaroon-init https://esdss000.cern.ch:9000//eos/ 60 DOWNLOAD,UPLOAD\n   MDAxNGxvY2F0aW9uIGVvc2RldgowMDM0aWRlbnRpZmllciBiYzhiZWRmZC0wNzJjLTRmZWEtYjNiYy0wNDJjZjczZDhiYjMKMDAxNmNpZCBuYW1lOmVzaW5kcmlsCjAwMWZjaWQgYWN0aXZpdHk6UkVBRF9NRVRBREFUQQowMDI4Y2lkIGFjdGl2aXR5OkRPV05MT0FELFVQTE9BRCxNQU5BR0UKMDAxM2NpZCBwYXRoOi9lb3MvCjAwMjRjaWQgYmVmb3JlOjIwMjAtMDEtMjlUMTU6MTM6MzVaCjAwMmZzaWduYXR1cmUguNm15NCbrb62KCIvxxDlSgrwgMZKjGPrO7NwxFQwIycK\n   # Export the token as an environment variable for easier use later on\n   export MACAROON=MDAxNGxvY2F0aW9uIGVvc2RldgowMDM0aWRlbnRpZmllciBiYzhiZWRmZC0wNzJjLTRmZWEtYjNiYy0wNDJjZjczZDhiYjMKMDAxNmNpZCBuYW1lOmVzaW5kcmlsCjAwMWZjaWQgYWN0aXZpdHk6UkVBRF9NRVRBREFUQQowMDI4Y2lkIGFjdGl2aXR5OkRPV05MT0FELFVQTE9BRCxNQU5BR0UKMDAxM2NpZCBwYXRoOi9lb3MvCjAwMjRjaWQgYmVmb3JlOjIwMjAtMDEtMjlUMTU6MTM6MzVaCjAwMmZzaWduYXR1cmUguNm15NCbrb62KCIvxxDlSgrwgMZKjGPrO7NwxFQwIycK\n   # Use the curl command to trigger the transfer (download) and properly\n   # populate the header information with the authentication information\n   curl -v -L -H \"Authorization: Bearer $MACAROON\" https://esdss000.cern.ch:9000/eos/dev/replica/file1.dat\n\nFor debugging purposes or just simple curiosity the client can inspect the\ncontents of the macaroon if they have access to the ``/etc/eos.macaroon.secret``\nfile. This can easily be done by installing the **python2-macaroons** package\nfrom EPEL and launching a python shell as follows:\n\n.. :caption: Python script to decode a Macaroon token\n\n.. code-block:: python\n\n   >>> import macaroons\n   >>> secret = open(\"/etc/eos.macaroon.secret\", 'r').read()\n   >>> mtoken = \"MDAxNGxvY2F0aW9uIGVvc2RldgowMDM0aWRlbnRpZmllciBiYzhiZWRmZC0wNzJjLTRmZWEtYjNiYy0wNDJjZjczZDhiYjMKMDAxNmNpZCBuYW1lOmVzaW5kcmlsCjAwMWZjaWQgYWN0aXZpdHk6UkVBRF9NRVRBREFUQQowMDI4Y2lkIGFjdGl2aXR5OkRPV05MT0FELFVQTE9BRCxNQU5BR0UKMDAxM2NpZCBwYXRoOi9lb3MvCjAwMjRjaWQgYmVmb3JlOjIwMjAtMDEtMjlUMTU6MTM6MzVaCjAwMmZzaWduYXR1cmUguNm15NCbrb62KCIvxxDlSgrwgMZKjGPrO7NwxFQwIycK\"\n   >>> M = macaroons.deserialize(mtoken)\n   >>> print M.inspect()\n   location eosdev\n   identifier bc8bedfd-072c-4fea-b3bc-042cf73d8bb3\n   cid name:esindril\n   cid activity:READ_METADATA\n   cid activity:DOWNLOAD,UPLOAD,MANAGE\n   cid path:/eos/\n   cid before:2020-01-29T15:13:35Z\n   signature b8d9b5e4d09badbeb628222fc710e54a0af080c64a8c63eb3bb370c454302327\n\n\nHTTP transfers with SciToken authentication\n-------------------------------------------\n\nHTTP transfers with SciTokens work in a similar way to Macaroon tokens. In order\nto get a SciToken, one needs to be registered with an IAM provider and install\nthe **oidc-agent** package which provides the client tools to register and request\ntokens. An RPM package for CentOS7 is already available from the\n`GitHub releases page of the project <https://github.com/indigo-dc/oidc-agent/releases>`_.\n\nTo configure the **oidc-agent**, you can follow these steps:\n\n.. code-block:: bash\n\n   # Start the oidc-agent in the background\n   eval $(oidc-agent)\n   oidc-gen WLCG-<your_username> -w device\n   # Put as issuer https://wlcg.cloud.cnaf.infn.it/ and configure the set of\n   # scopes as \"max\". Then connect the agent to the IAM provide which will\n   # prompt you for the password you set up earlier.\n   oidc-add WLCG-<your_username>\n   # Request a token from the IAM and save it as an environment variable for\n   # later use\n   export SCI_TOKEN=`oidc-token WLCG-<your_username>`\n   # Trigger a HTTP download using the SciToken information\n   curl -v -L -H \"Authorization: Bearer $SCI_TOKEN\" https://esdss000.cern.ch:9000/eos/dev/replica/file1.dat\n\n\nTo inspect the contents of a SciToken, one can use the following commands:\n\n.. code-block:: bash\n\n    echo $SCI_TOKEN | cut -d. -f2 | base64 --decode | jq .\n    {\n      \"wlcg.ver\": \"1.0\",\n      \"sub\": \"faded49c-e1bc-4208-9634-682b2b8d16e5\",\n      \"aud\": \"https://wlcg.cern.ch/jwt/v1/any\",\n      \"nbf\": 1613993622,\n      \"scope\": \"address storage.create:/ phone openid offline_access profile storage.read:/ storage.modify:/ email wlcg wlcg.groups\",\n      \"iss\": \"https://wlcg.cloud.cnaf.infn.it/\",\n      \"exp\": 1613997222,\n      \"iat\": 1613993622,\n      \"jti\": \"ea07cad1-f504-4c16-9e22-da5de2876ca7\",\n      \"client_id\": \"710b4313-5ff7-4992-a59d-d404ea9d4ac5\",\n      \"wlcg.groups\": [\n                \"/wlcg\",\n                \"/wlcg/xfers\"\n       ]\n    }\n\nHTTP TPC PULL transfers with CURL\n----------------------------------\n\nThe following snippet provides the steps necessary for obtaining the necessary tokens for doing a HTTP TPC PULL transfer.\n\n.. code-block:: bash\n\n   export SRC=https://esdss000.cern.ch//eos/opstest/esindril/file.dat\n   export DST=https://esdss000.cern.ch//eos/opstest/esindril/file1.dat\n   # Get macaroon for source\n   export TSRC=$(curl --silent --cert /tmp/x509up_u$(id -u) --key /tmp/x509up_u$(id -u) --cacert /tmp/x509up_u$(id -u) --capath /etc/grid-security/certificates -X POST -H 'Content-Type: application/macaroon-request' -d '{\"caveats\": [\"activity:DOWNLOAD\"], \"validity\": \"PT3000M\"}' \"$SRC\" | jq -r '.macaroon')\n   # Get macaroon for destination\n   export TDST=$(curl --silent --cert /tmp/x509up_u$(id -u) --key /tmp/x509up_u$(id -u) --cacert /tmp/x509up_u$(id -u) --capath /etc/grid-security/certificates -X POST -H 'Content-Type: application/macaroon-request' -d '{\"caveats\": [\"activity:UPLOAD,DELETE,LIST\"], \"validity\": \"PT3000M\"}' \"$DST\" | jq -r '.macaroon')\n   # Trigger HTTP TPC PULL\n   curl -v --capath /etc/grid-security/certificates -L -X COPY -H 'Secure-Redirection: 1' -H 'X-No-Delegate: 1' -H 'Credentials: none' -H \"Authorization: Bearer $TDST\" -H \"TransferHeaderAuthorization: Bearer $TSRC\" -H \"TransferHeaderTest: Test\" -H \"Source: $SRC\" \"$DST\"\n\nThe same thing now but for a HTTP TPC PUSH transfer.\n\n.. code-block:: bash\n\n   export SRC=https://esdss000.cern.ch//eos/opstest/esindril/xfile.dat\n   export DST=https://esdss000.cern.ch//eos/opstest/esindril/xfile1.dat\n   # Get macaroon for source\n   export TSRC=$(curl --silent --cert /tmp/x509up_u$(id -u) --key /tmp/x509up_u$(id -u) --cacert /tmp/x509up_u$(id -u) --capath /etc/grid-security/certificates -X POST -H 'Content-Type: application/macaroon-request' -d '{\"caveats\": [\"activity:DOWNLOAD\"], \"validity\": \"PT3000M\"}' \"$SRC\" | jq -r '.macaroon')\n   # Get macaroon for destination\n   export TDST=$(curl --silent --cert /tmp/x509up_u$(id -u) --key /tmp/x509up_u$(id -u) --cacert /tmp/x509up_u$(id -u) --capath /etc/grid-security/certificates -X POST -H 'Content-Type: application/macaroon-request' -d '{\"caveats\": [\"activity:UPLOAD,DELETE,LIST\"], \"validity\": \"PT3000M\"}' \"$DST\" | jq -r '.macaroon')\n   # Trigger HTTP TPC PUSH\n   curl -v --capath /etc/grid-security/certificates -L -X COPY -H 'Secure-Redirection: 1' -H 'X-No-Delegate: 1' -H 'Credentials: none' -H \"Authorization: Bearer $TSRC\" -H \"TransferHeaderAuthorization: Bearer $TDST\" -H \"Destination: $DST\" \"$SRC\"\n\n\n.. only:: adminmode\n\n   HTTP TPC transfer triggered by FTS\n   ----------------------------------\n"
  },
  {
    "path": "doc/citrine/configuration/import.rst",
    "content": ".. highlight:: rst\n\n.. index::\n      triple: Import; HTTP; S3\n\nImporting files\n===============\n\nEOS can use multiple protocols when communicating with a storage device.\nMost commonly, the data is stored on local disks. However, data can also be\nstored on an external storage system, such as an S3, WebDAV or XRootD endpoint.\nMore information can be found `here <logicalpath.html>`__.\n\nImporting files works with any underlying storage device\nas file importation is a filesystem operation.\nThis means the endpoint must be mapped and accessible\nthrough an EOS filesystem.\n\nThe importation procedure does a scan of the remote path,\nrecursively traversing all directories from this point on.\nAll discovered files will be imported into the namespace,\ntogether with their size and time of creation/modification.\nUpon registering the files into the namespace,\nthey can be managed like any other EOS file.\n\n.. note::\n   File importation is only a metadata operation.\n   The files themselves will not be moved.\n\nConfiguration\n-------------\n\nPreconditions\n+++++++++++++\n\nTo set-up the importation procedure, a filesystem must be\nassociated with that particular endpoint.\n\nRegistering a filesystem which uses an external endpoint for storage\nis done the same way. For example, to import from ``s3://s3.cern.ch/bucket``,\nwe would do the following:\n\n.. code-block:: bash\n\n   eos fs add [-m 1] <uuid> s3-fst.cern.ch:1095 s3://s3.cern.ch/bucket/ s3 rw\n\nOnce registered, make sure the filesystem is online:\n\n.. code-block:: bash\n\n   ┌────────────────────────┬────┬──────┬──────────────────────────────────────────┬────────────────┬────────────────┬────────────┬──────────────┬────────────┬────────┬────────────────┐\n   │host                    │port│    id│                                      path│      schedgroup│          geotag│        boot│  configstatus│ drainstatus│  active│          health│\n   └────────────────────────┴────┴──────┴──────────────────────────────────────────┴────────────────┴────────────────┴────────────┴──────────────┴────────────┴────────┴────────────────┘\n    s3-fst.cern.ch           1095      1                    s3://s3.cern.ch/bucket/             s3.0              xdc       booted             rw      nodrain   online              N/A\n\nFilesystem set-up\n+++++++++++++++++\n\nAlthough not mandatory, configuring the filesystem to use `logicalpath <logicalpath.html>`__\nwill be needed in most cases.\n\n.. code-block:: bash\n\n   eos fs config <fsid> logicalpath=1\n\nDepending on the remote endpoint, additional configuration might be needed.\n\nS3\n##\n\nFSTs with S3 filesystems registered will need an access/secret key pair.\nThese can be done on a filesystem basis or FST wide.\n\nTo set keys only for a particular, use the ``fs config`` option:\n\n.. code-block:: bash\n\n   eos fs config <fsid> s3credentials=<accesskey>:<secretkey>\n\nTo set keys FST wide, export the following environment variables:\n\n.. code-block:: bash\n\n   export EOS_FST_S3_ACCESS_KEY=<accesskey>\n   export EOS_FST_S3_SECRET_KEY=<secretkey>\n\nWebDAV with x509\n################\n\nTo access WebDAV storage endpoints, you will need a way to authenticate.\nSupport is provided for x509 certificates.\n\nThe setting applies FST wide and is retrieved\nfrom the following environment variable:\n\n.. code-block:: bash\n\n   export EOS_FST_HTTPS_X509_CERTIFICATE_PAT=/path/to/x509/certificate\n\nImport procedure\n----------------\n\nThe import procedure is triggered via the `fs import start` admin command,\nwhich is documented `here <../clicommands/fs.html>`__.\n\nIt offers the option to start an import procedure\nor to check the status of an ongoing import.\n\nWhen starting a new import operation, the command is sent to the MGM\nwhich does the following checks:\n\n- external path begins with the filesystem local prefix\n- destination path is a directory\n- destination path is in same scheduling group as the filesystem\n\nA new id, together with an import status object\nare generated for this import operation.\n\nA signal is sent to the responsible FST,\nwhich will place the import request in a queue.\n\nImport requests are processed on a dedicated thread.\nThis will do a traversal of the endpoint path.\nFor each file discovered, a stat is performed to retrieve\nneeded information. This info is encoded into a message\nwhich is sent back to the MGM.\n\nThe MGM receives this message, updates the namespace\nand the import status object.\n"
  },
  {
    "path": "doc/citrine/configuration/inspector.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single:: File Inspector\n\nFile Inspector\n===================\n\nThe File Inspector is a slow agent scanning all files in a namespace and collects statistics per layout type. Additionally it adds statistic about replication inconsistencies per layout. The target interval to scan all files is user defined. The default cycle is 4 hours, which can create a too high load in large namespaces and should be adjusted accordingly.\n\nConfiguration\n-------------\n\nFile Inspector\n++++++++++++++\nThe File Inspector has to be enabled/disabled in the default space only:\n\n.. code-block:: bash\n\n   # enable\n   eos space config default space.inspector=on\n   # disable\n   eos space config default space.inspector=off\n\nBy default Replication Tracking is disabled.\n\nThe current status of the Tracker can be seen via:\n\n.. code-block:: bash\n\n   eos space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   ...\n   inspector                        := off\n   ...\n\n\nInspector Interval\n------------------\n\nThe default inspector interval to scan all files is 4 hours. The interval can be set using:\n\n.. code-block:: bash\n\n   # set interval to 1d\n   eos space config default space.inspector.interval=86400\n\n\nInspector Status\n----------------\n\nYou can get the inspector status and an estimate for the run time using\n\n.. code-block:: bash\n\n   eos space inspector\n\n   # or\n\n   eos inspector\n\n   # ------------------------------------------------------------------------------------\n   # 2019-07-12T08:38:24Z\n   # 28 % done - estimate to finish: 2575 seconds\n   # ------------------------------------------------------------------------------------\n\nInspector Output\n----------------\n\nYou can see the current statistics of the inspector run using\n\n.. code-block:: bash\n\n   eos inspector -c\n   eos inspector --current\n\n   # ------------------------------------------------------------------------------------\n   # 2019-07-12T08:39:55Z\n   # 28 % done - estimate to finish: 2574 seconds\n   # current scan: 2019-07-12T08:25:42Z\n    not-found-during-scan            : 0\n   ======================================================================================\n   layout=00000000 type=plain         checksum=none     blockchecksum=none     blocksize=4k\n\n   locations                        : 0\n   nolocation                       : 223004\n   repdelta:-1                      : 223004\n   unlinkedlocations                : 0\n   zerosize                         : 223004\n\n   ======================================================================================\n   layout=00100001 type=plain         checksum=none     blockchecksum=none     blocksize=4k\n\n   locations                        : 2\n   repdelta:0                       : 2\n   unlinkedlocations                : 0\n   volume                           : 3484\n\n   ...\n\n\nThe reports tags are:\n\n.. code-block:: bash\n\n   locations         : number of replicas (or stripes) in this layout category\n   nolocation        : number of files without any location attached\n   repdelta:-N       : number of files with -N replicas missing\n   repdelta:0        : number of files with correct replicat count\n   repdelate:+N      : number of files with +N replicas in excess\n   zerosize          : number of files with 0 size\n   volume            : logical bytes stored in this layout type\n   unlinkedlocations : number replicas still to be deleted\n   shadowdeletions   : number of files with a replica pointing to a not configured filesystem for deletion\n   shodowlocation    : number of files with a replica pointing to a not configured filesystem\n\n\nYou can get the statistics of the last completed run using\n\n.. code-block:: bash\n\n   eos inspector -l\n   eos inspector --last\n\nYou can print the current and last run statistics in monitoring format:\n\n.. code-block:: bash\n\n   eos inspector -c -m\n   ...\n\n   eos inspector -l -m\n\n   key=last layout=00100002 type=plain checksum=adler32 blockchecksum=none blocksize=4k locations=638871 repdelta:+1=1 repdelta:0=638869 unlinkedlocations=0 volume=10802198338 zerosize=550002\n   key=last layout=00100012 type=replica checksum=adler32 blockchecksum=none blocksize=4k locations=42 repdelta:0=42 unlinkedlocations=0 volume=21008942\n   key=last layout=00100014 type=replica checksum=md5 blockchecksum=none blocksize=4k locations=1 repdelta:0=1 unlinkedlocations=0 volume=1701\n   key=last layout=00100015 type=replica checksum=sha1 blockchecksum=none blocksize=4k locations=1 repdelta:0=1 unlinkedlocations=0 volume=1701\n   key=last layout=00100112 type=replica checksum=adler32 blockchecksum=none blocksize=4k locations=44 repdelta:0=22 unlinkedlocations=0 volume=10506283\n   key=last layout=00640112 type=replica checksum=adler32 blockchecksum=none blocksize=1M locations=2 repdelta:0=1 unlinkedlocations=0 volume=1783\n   key=last layout=20640342 type=raid6 checksum=adler32 blockchecksum=crc32c blocksize=1M locations=0 nolocation=6 repdelta:-4=6 unlinkedlocations=0 zerosize=6\n   key=last layout=3b9ac9ff type=none checksum=none blockchecksum=none blocksize=illegal unfound=0\n\nThe list of file ids with an inconsistency can be extracted using:\n\n.. code-block:: bash\n\n   # print the list of file ids\n   eos inspector -c -p #current run\n\n   fxid:00140237 repdelta:-1\n   fxid:001410ff repdelta:-1\n   fxid:00141807 repdelta:-1\n   fxid:0013da42 repdelta:-4\n   fxid:0013da43 repdelta:-4\n   fxid:0013da44 repdelta:-4\n   fxid:0013da45 repdelta:-4\n   fxid:0013da57 repdelta:-4\n   fxid:0013da68 repdelta:-4\n   ...\n\n\n   eos inspector -l -p #last run\n   ...\n\n   # export the list of file ids on the mgm\n   eos inspector -c -e #current run\n   # ------------------------------------------------------------------------------------\n   # 2019-07-12T08:53:14Z\n   # 100 % done - estimate to finish: 0 seconds\n   # file list exported on MGM to '/var/log/eos/mgm/FileInspector.1562921594.list'\n   # ------------------------------------------------------------------------------------\n\n   eos inspector -l -e #last run\n   # ------------------------------------------------------------------------------------\n   # 2019-07-12T08:53:33Z\n   # 100 % done - estimate to finish: 0 seconds\n   # file list exported on MGM to '/var/log/eos/mgm/FileInspector.1562921613.list'\n   # -----------------------------------------------------------------------\n\n\nLog Files\n---------\nThe File Inspector has a dedicated log file under ``/var/log/eos/mgm/FileInspector.log``\nwhich shows the scan activity and potential errors. To get more\nverbose information you can change the log level:\n\n.. code-block:: bash\n\n   # switch to debug log level on the MGM\n   eos debug debug\n\n   # switch back to info log level on the MGM\n   eos debug info\n"
  },
  {
    "path": "doc/citrine/configuration/kinetic.rst",
    "content": "Kinetic\n=======================\n\nPreconditions\n+++++++++++++\nThe following standard packages are required\n\n.. code-block:: bash\n\n   yum install json-c libuuid\n\nAdditionally, you will need to install the packages available at https://dss-ci-repo.web.cern.ch/dss-ci-repo/kinetic/. If you have afs access, this can be done by\n\n.. code-block:: bash\n\n   yum install /afs/cern.ch/project/dss_ci/repo/kinetic/*\n\n\nPlease refer to the installation docs on how to install eos. Make sure not to install a version without kinetic support.\n\nKinetic Drive Setup\n+++++++++++++++++++++\nIf you need to administrate individual kinetic drives (e.g. reformat drives, set up security roles and passwords), one solution is to use https://github.com/Seagate/kinetic-java-tools. The tool accepts lists of drives to perform admin operations on. For the syntax see the github documentation.\n\n\nEOS Kinetic Cluster Configuration\n++++++++++++++++++++++++++++++++++++++\n\nFor each eos space you wish to host kinetic clusters in, you need to generate the following configuration files in /var/eos/kinetic/\n\n + kinetic-location-spacename.json  -  Location information (ip / dns name)\n + kinetic-security-spacename.json  -  Security information (user id, password)\n + kinetic-cluster-spacename.json   -  Definitions of clusters as well as overall library configuration.\n\nThe directory will already contain example json files that demonstrate the syntax and can be used to get started. In the following the configuration options are explained in detail.\n\nLOCATION:\n - wwn: The world wide name of the drive. While each drive exports a world wide name that can be used for this field, an arbitrary value can be used as long as it is unique.\n - inet4: The ip addresses for both interfaces of the kinetic drive. If you only want to use a single interface you may list it twice.\n - port: The port to connect to. The standard port for Kinetic services is 8123.\n\nSECURITY:\n - wwn: The world wide name of the drive.\n - userId: The user id to use when establishing a connection.\n - key: The secret key / the password to use when establishing a connection.\nCLUSTER:\n - clusterID: The cluster identifier. As the drive wwn it can be freely chosen but has to be unique. It may not contain the ':' or '/' symbols.\n - numData: The number of data chunks that will be stored in a data stripe (required to be >=1).\n - numParity: Defines the redundancy level of this cluster (required to be >=0). Metadata and attribute keys will be stored with numParity replication. Data will be stored in (numData,numParity) erasure coded stripes (unless numData is defined as 1 in which case replication will be used).\n - chunkSizeKB: The maximum size of data chunks in KB (required to be min. 1 and max. 1024). A value of 1024 is optimal for Kinetic drive performance.\n - timeout: Network timeout for cluster operations in seconds.\n - minReconnectInterval: The minimum time / rate limit in seconds between reconnection attempts.\n - drives: A list of wwn identifiers for all drives associated with the cluster. The order of the drives is important and may not be changed after data has been written to the cluster. If a drive is replaced, the new drive wwn has to replace the old drive wwn at the same position.\n\nLIBRARY:\n - cacheCapacityMB: The maximum cache size in MB. The cache is used to hold data for currently executing operations. It has to be at least the stripe size times the maximum number of concurrent data streams. E.g. for a setup with 16-4 erasure coding configuration, 1 MB chunkSize and an expected 20 concurrent data streams the cache capacity should be 20MB*20Streams=400MB. Larger cache capacities are beneficial as they allow higher concurrency for writing (asynchronous flushes of multiple data blocks per stream) as well as more prefetching and data retention for read scenarios.\n - maxBackgroundIoThreads: The maximum number of background IO threads. If > 0 it sets the limit for concurrent I/O operations (put, get, del). For 10G EOS nodes, 12 to 16 seems to achieve good performance.\n - maxBackgroundIoQueue: The maximum number of IO operations queued for execution. If set to 0, background threads will not be held in a pool but use one-shot threads spawned on-demand. For normal operation a value of ~3 times the number of background threads works well.\n - maxReadaheadWindow: limit the maximum readahead to set number of data stripes. Note that the maximum readahead will only be reached if the access pattern is very predictable and there is no cache pressure.\n\n\nEOS Kinetic Cluster Administration\n++++++++++++++++++++++++++++++++++\nThe eos kinetic command line tools provide a variety of admin functions. In the following, some of the most important functions are described. You can view all available functionality by typing *eos kinetic -h*\n\n.. code-block:: bash\n\n  eos kinetic --space <spacename> config\n\nDisplay the currently loaded configuration for a space\n\n\n.. code-block:: bash\n\n  eos kinetic --space <spacename> config --publish\n\nLoad the configuration specified in the json files for the space into eos. This can be done during normal operation. While library wide configuration changes are immediate, changes to cluster configuration as well as drive location or security will only apply to files opened after the configuration has been published. Files that already had been opened at that time will continue using the configuration that was current when they were opened.\n\n.. code-block:: bash\n\n  eos kinetic --space <spacename> --id <cluster-id> status\n\nDisplay the cluster status / health. This will list healthy & failed connections to kinetic drives of the cluster as well as if any indicator keys on any drive. If none exist, you can assume the data stored to be complete. If indicator keys do exist, a repair operation should be attempted as soon as failed connections can be reestablished.\n\n\n.. code-block:: bash\n\n  eos kinetic --space <spacename> --id <cluster-id> count indicator\n\nCount the number of existing indicators. This can be helpful to estimate runtime of scan and / or repair operations.\n\n.. code-block:: bash\n\n  eos kinetic --space <spacename> --id <cluster-id> scan indicator\n\nCheck if repair operations are possible for the  metadata / attribute / data keys with indicators.\n\n.. code-block:: bash\n\n  eos kinetic --space <spacename> --id <cluster-id> repair indicator\n\nAttempt to repair all metadata / attribute / data keys on the cluster that have an indicator.\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "doc/citrine/configuration/logicalpath.rst",
    "content": ".. highlight:: rst\n\n.. index::\n      pair: Logicalpath; Import\n\nLogicalpath\n===========\n\nThe way files are stored on EOS filesystems is transparent to the users.\nThey identify a file in the namespace either via a path\nor a file id (fid / fxid). How this translates into the placement\nof the file is the responsibility of the system.\nSo far, the physical location of files was computed from the file id.\n\nWith the introduction of the logical path setting,\nfilesystems may store files under a 'path-like' location.\n\nExample:\n\n.. code-block:: bash\n\n  /fst/00000000/0000a12d vs /fst/eos/instance/path/filename\n\nTo activate the logical path setting,\nthe following command must be executed:\n\n.. code-block:: bash\n\n   eos fs config <fsid> logicalpath=1\n\n\nThe setting can be switched on or off at any time, with immediate effects.\n\nTranslation mechanism\n---------------------\n\nFile created on a filesystem with the logicalpath setting on will have\nan additional extended attribute ``sys.eos.lpath``\nwhich keeps a mapping of the file's location.\nThe extended attribute stores string information of a file's\nphysical location on a given file system.\n\nOperations regarding the file's physical path make use of a namespace\nutility class which takes into account both the possibility of a logicalpath,\nas well as constructing the path from the file's id.\n\nFor example, the file ``/eos/s3/testfile`` with 3 replicas,\nwith 2 of those using logicalpath, will look like this:\n\n.. code-block:: bash\n\n   > eos fileinfo /eos/s3/testfile --fullpath\n   ...\n   ┌───┬──────┬────────────────────────┬────────────────┬────────────────────────┬──────────┬──────────────┬────────────┬────────┬────────────────────────┬───────────────────────────────────────┐\n   │no.│ fs-id│                    host│      schedgroup│                    path│      boot│  configstatus│ drainstatus│  active│                  geotag│                      physical location│\n   └───┴──────┴────────────────────────┴────────────────┴────────────────────────┴──────────┴──────────────┴────────────┴────────┴────────────────────────┴───────────────────────────────────────┘\n   0        1    xdc-fst1.cern.ch              default.0                    /fst1     booted             rw      nodrain   online                      xdc                 /fst1/00000013/0002f914\n   1        2    xdc-fst1.cern.ch                lpath.0              /fst1_lpath     booted             rw      nodrain   online                      xdc             /fst1_lpath/eos/s3/testfile\n   2        3    xdc-fst2.cern.ch                   s3.0  s3://s3.cern.ch/bucket/     booted             rw      nodrain   online                      xdc  s3://s3.cern.ch/bucket/eos/s3/testfile\n\n   > eos attr ls /eos/s3/testfile\n   sys.eos.lpath=\"2|/eos/s3/testfile&3|/eos/s3/testfile\"\n\nThe two replicas, stored on filesystems using logicalpath,\ncan be seen in the file's extended attributes.\n"
  },
  {
    "path": "doc/citrine/configuration/lru.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single:: LRU Policies\n   see: Policies; LRU Policies\n\nLRU Engine\n==========\nThe LRU engine scans in a defined interval the full directory hierarchy and applies\nthe so called LRU policies.\n\n.. epigraph::\n\n   ===================================================================================== =====================\n   Policy                                                                                Basis\n   ===================================================================================== =====================\n   Volume based LRU cache with low and high watermark                                    volume/threshold/time\n   Automatic time based cleanup of empty directories                                     ctime\n   Time based LRU cache with expiration time settings                                    ctime\n   Automatic time based layout conversion if a file reaches a defined age                ctime\n   Automatic size based layout conversion if a file fulfills a given size rule          size\n   Automatic time based layout conversion if a file has not been used for specified time mtime\n   ===================================================================================== =====================\n\nConfiguration\n-------------\n\nEngine\n++++++\nThe LRU engine has to be enabled/disabled in the default space only:\n\n.. code-block:: bash\n\n   # enable\n   eos space config default space.lru=on\n   # disable\n   eos space config default space.lru=off\n\nThe current status of the LRU can be seen via:\n\n.. code-block:: bash\n\n   eos -b space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   ...\n   lru                            := off\n   lru.interval                   := 0\n   ...\n\nThe interval in which the LRU engine is running is defined by the **lru.interval**\nspace variable:\n\n.. code-block:: bash\n\n   # run the LRU scan once a week\n   eos space config default space.lru.interval=604800\n\nPolicy\n++++++\n\nVolume based LRU cache with low and high watermark\n``````````````````````````````````````````````````\nTo configure an LRU cache with low and high watermark it is necessary to define\na quota node on the cache directory, set the high and low watermarks and to enable\nthe **atime** feature updating the creation times of files with the current\naccess time.\n\nWhen the cache reaches the high watermark it cleans the oldest files untile low-watermark is reached:\n\n.. code-block:: bash\n\n   # define project quota on the cache directory\n   eos quota set -g 99 -v 1T /eos/instance/cache/\n\n   # define 90 as low and 95 as high watermark\n   eos attr set sys.lru.lowwatermark=90 /eos/instance/cache/\n   eos attr set sys.lru.highwatermark=95 /eos/instance/cache/\n\n   # track atime with a time resolution of 5 minutes\n   eos attr set sys.force.atime=300 /eos/dev/instance/cache/\n\n\nAutomatic time based cleanup of empty directories\n`````````````````````````````````````````````````\nConfigure automatic clean-up of empty directories which have a minimal age.\nThe LRU scan deletes directories with the largest deepness first to be able\nto remove complete empty subtrees in the namespace.\n\n.. code-block:: bash\n\n   # remove automatically empty directories if they are older than 1 hour\n   eos attr set sys.lru.expire.empty=\"1h\" /eos/dev/instance/empty/\n\n\nTime based LRU cache with expiration time settings\n``````````````````````````````````````````````````\nThis policy allows to match files by name with a defined age to be deleted. We\nuse the following convention when specifying the age interval for the various\n\"match\" options:\n\n +---------------+---------------+\n | Symbol        | Meaning       |\n +===============+===============+\n |**s/S**        | seconds       |\n +---------------+---------------+\n |**min/MIN**s   | minutes       |\n +---------------+---------------+\n |**h/H**        | hours         |\n +---------------+---------------+\n |**d/D**        | days          |\n +---------------+---------------+\n |**w/W**        | weeks         |\n +---------------+---------------+\n |**mo/MO**      | months        |\n +---------------+---------------+\n |**y/Y**        | years         |\n +---------------+---------------+\n\nAll the size related symbols refer to the International System of Units, therefore\n1K is 1000 bytes.\n\n.. code-block:: bash\n\n   # files with suffix *.root get removed after a month, files with *.tgz after one week\n   eos attr set sys.lru.expire.match=\"*.root:1mo,*.tgz:1w\"  /eos/dev/instance/scratch/\n\n   # all files older than a day are automatically removed\n   eos attr set sys.lru.expire.match=\"*:1d\" /eos/dev/instance/scratch/\n\nAutomatic time based layout conversion if a file reaches a defined age\n``````````````````````````````````````````````````````````````````````\nThis policy allows to convert a file from the current layout into a defined layout.\nA *placement policy* (cf. :doc:`geoscheduling`) can also be specified.\n\n.. code-block:: bash\n\n   # convert all files older than a month to the layout defined next\n   eos attr set sys.lru.convert.match=\"*:1mo\" /eos/dev/instance/convert/\n\n   # define the conversion layout (hex) for the match rule '*' - this is RAID6 4+2\n   eos attr set sys.conversion.*=20640542 /eos/dev/instance/convert/\n\n   # same thing specifying a placement policy for the replicas/stripes\n   eos attr set sys.conversion.*=20640542|gathered:site1::rack2 /eos/dev/instance/convert/\n\nThe hex layout ID contains also the checksum and blocksize settings. The best is\nto create a file with the desired layout and get the hex layout ID using\n**eos file info <path>**.\n\nAutomatic size based restriction for time based conversion\n``````````````````````````````````````````````````````````\nThis policy addition allows to restrict the time based layout conversion to certain\nfile sizes.\n\n.. code-block:: bash\n\n   # convert all files smaller than 128m in size [ with units E/e,P/p,T/t,G/g,M/m,K/k ]\n   eos attr set sys.lru.convert.match=\"*:1w:<1M\"\n\n   # convert all files bigger than 1G in size\n   eos attr set sys.lru.convert.match=\"*:1w:>1G\"\n\n\nAutomatic time based layout conversion if a file has not been used for specified time\n``````````````````````````````````````````````````````````````````````````````````````\nThis policy allows to convert a file from the current layout to a different layout\nif the file was not accessed for a defined interval. To use this feature one has\nalso to enable the **atime** feature where the access time is stored as the new\nfile creation time. A *placement policy* (cf. :doc:`geoscheduling`) can also be specified.\n\n.. code-block:: bash\n\n     # track atime with a time resolution of one week\n     eos attr set sys.force.atime=1w /eos/dev/instance/convert/\n\n     # convert all files older than a month to the layout defined next\n     eos attr set sys.lru.convert.match=\"*:6mo\" /eos/dev/instance/convert/\n\n     # define the conversion layout (hex) for the match rule '*' - this is RAID6 4+2\n     eos attr set sys.conversion.*=20640542 /eos/dev/instance/convert/\n\n     # same thing specifying a placement policy for the replicas/stripes\n     eos> attr set sys.conversion.*=20640542|gathered:site1::rack2 /eos/dev/instance/convert/\n\nManual File Conversion\n----------------------\nIt is possible to run an asynchronous file conversion using the **EOS CLI**.\n\n.. code-block:: bash\n\n   # convert the referenced file into a file with 3 replica\n   eos file convert /eos/dev/2rep/passwd replica:3\n   info: conversion based layout+stripe arguments\n   success: created conversion job '/eos/dev/proc/conversion/0000000000059b10:default#00650212'\n\n   # same thing mentioning target space and placement policy\n   eos file convert /eos/dev/2rep/passwd replica:3 default gathered:site1::rack1\n   info: conversion based layout+stripe arguments\n   success: created conversion job '/eos/dev/proc/conversion/0000000000059b10:default#00650212'~gathered:site1::rack1\n\n.. code-block:: bash\n\n   # convert the referenced file into a RAID6 file with 6 stripes\n   eos file convert /eos/dev/2rep/passwd raid6:6\n   info: conversion based layout+stripe arguments\n   success: created conversion job '/eos/dev/proc/conversion/0000000000064f61:default#20650542'\n\n   # check that the conversion was successful\n   eos fileinfo /eos/dev/2rep/passwd\n   File: '/eos/dev/2rep/passwd'  Size: 2458\n   Modify: Wed Oct 30 17:03:35 2013 Timestamp: 1383149015.384602000\n   Change: Wed Oct 30 17:03:36 2013 Timestamp: 1383149016.243563000\n     CUid: 0 CGid: 0  Fxid: 00064f63 Fid: 413539    Pid: 1864   Pxid: 00000748\n   XStype: adler    XS: 01 15 4b 52\n   raid6 Stripes: 6 Blocksize: 4M LayoutId: 20650542\n     #Rep: 6\n   <#> <fs-id> #.................................................................................................................\n               #               host  #    schedgroup #      path #    boot # configstatus #    drain # active #         geotag #\n               #.................................................................................................................\n     0     102     lxfsra04a03.cern.ch      default.11     /data12    booted             rw    nodrain   online   eos::cern::mgm\n     1     116     lxfsra02a05.cern.ch      default.11     /data12    booted             rw    nodrain   online   eos::cern::mgm\n     2      94     lxfsra04a02.cern.ch      default.11     /data12    booted             rw    nodrain   online   eos::cern::mgm\n     3      65     lxfsra02a07.cern.ch      default.11     /data12    booted             rw    nodrain   online   eos::cern::mgm\n     4     108     lxfsra02a08.cern.ch      default.11     /data12    booted             rw    nodrain   online   eos::cern::mgm\n     5      77     lxfsra04a01.cern.ch      default.11     /data13    booted             rw    nodrain   online   eos::cern::mgm\n   *******\n\n\nLog Files\n---------\nThe LRU engine has a dedicated log file under ``/var/log/eos/mgm/LRU.log``\nwhich shows triggered actions based on scanned policies. To get more\nverbose information you can change the log level:\n\n.. code-block:: bash\n\n   # switch to debug log level on the MGM\n   eos debug debug\n\n   # switch back to info log level on the MGM\n   eos debug info\n"
  },
  {
    "path": "doc/citrine/configuration/master.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Master/Slave Configuration\n\nMaster/Slave Configuration\n==========================\n\nThe BERYLL release supports to run two MGM's in master/slave configuration with\nrealtime switching from slave=>master role.\nThis can be combined with namespace file online compactification (OC).\n\nSingle MGM/MQ as master\n-----------------------\n\nTo run an existing instance with a single MGM/MQ and the master/slave code there is one necessary configuration step to be done after an update.\n\nAt least once you have to tag (your previous or new) master machine to start\nas a master. This is done via the service scripts:\n\n.. code-block:: bash\n\n   service eos master mgm\n\n   service eos master mq\n\nThen you can startup the instance as usual doing:\n\n.. code-block:: bash\n\n   service eos start\n   service eos status mgm\n   xrootd for role: mgm (pid 23290) is running (as slave) ...\n   service eos status mq\n   xrootd for role: mq (pid 14930) is running (as master) ...\n\nBy default every old or fresh installed MGM is configured as a slave.\nYou can force MGM or MQ into a slave role using service eos slave mgm and service eos slave mq.\n\nYou con use the **eosha** service to keep the MGM services alive or install a cron job doing ``service eos ...``\n\nConfiguration of an MGM/MQ master/slave pair\n--------------------------------------------\n\nThe simplest configuration uses an alias to point round-robin to master and slave machine e.g.\nconfigure a static alias ``eosdev.cern.ch`` resolving to ``eosdevsrv1.cern.ch`` and ``eosdevsrv2.cern.ch``.\nThis name can be used in the FST configuration to define the broker URL and can\nbe used by clients to talk to the instance independent of read or write access.\n\nIf only failover is desired without read-write load share one can use a static\nalias pointing only to either of the two machines.\n\nDeclare one machine to be the master for MGM and MQ,\ndeclare the second machine to be the slave for MGM and MQ:\n\n.. code-block:: bash\n\n   eosdevsrv1:# service eos master mgm\n   eosdevsrv1:# service eos master mq\n   eosdevsrv2:# service eos slave mgm\n   eosdevsrv2:# service eos slave mq\n\nStart the MQ and SYNC service on both machines.\n\n.. code-block:: bash\n\n   eosdevsrv1:# service eos start mq\n   eosdevsrv2:# service eos start mq\n   eosdevsrv1:# service eos start sync\n   eosdevsrv2:# service eos start sync\n   eosdevsrv1:# service eossync start\n   eosdevsrv2:# service eossync start\n\nAfter checking the healthy status of 'eossync'\n\n.. code-block:: bash\n\n   eosdevsrv1:# service eossync status\n\nyou can bring up the MGM service on both machines doing:\n\n.. code-block:: bash\n\n   eosdevsrv1:# service eos start mgm\n\n   eosdevsrv2:# service eos start mgm\n\nNow verify the running state of the two MGM's:\n\n.. code-block:: bash\n\n   eosdevsrv1:# eos -b ns\n\n   # ------------------------------------------------------------------------------------\n   # Namespace Statistic\n   # ------------------------------------------------------------------------------------\n   ALL      Files                            227 [booted] (0s)\n   ALL      Directories                      572\n   # ....................................................................................\n   ALL      Replication                      mode=master-rw state=master-rw master=eosdevsrv1.cern.ch configdir=/var/eos/config/eosdevsrv2.cern.ch/ config=default active=true mgm:eosdevsrv1.cern.ch=ok mgm:mode=ro-slave mq:eosdevsrv1.cern.ch:1097=ok\n   # ....................................................................................\n   ALL      File Changelog Size              18.13 MB\n   ALL      Dir  Changelog Size              86.39 kB\n   # ....................................................................................\n   ALL      avg. File Entry Size             79.85 kB\n   ALL      avg. Dir  Entry Size             151.00 B\n   # ------------------------------------------------------------------------------------\n   ALL      memory virtual                   269.70 MB\n   ALL      memory resident                  57.52 MB\n   ALL      memory share                     5.96 MB\n   ALL      memory growths                  -0.00 B\n   ALL      threads                          28\n   # ------------------------------------------------------------------------------------\n\n   eosdevsrv2:# eos -b ns\n\n   # ------------------------------------------------------------------------------------\n   # Namespace Statistic\n   # ------------------------------------------------------------------------------------\n   ALL      Files                            227 [booted] (0s)ALL      Directories                      572\n   # ....................................................................................\n   ALL      Replication                      mode=slave-ro state=slave-ro master=eosdevsrv1.cern.ch configdir=/var/eos/config/eosdevsrv2.cern.ch/ config=default active=true mgm:eosdevsrv1.cern.ch=ok mgm:mode=rw-master mq:eosdevsrv1.cern.ch:1097=ok\n   ALL      Namespace Latency                0.00 += 0.00 ms\n   # ....................................................................................\n   ALL      File Changelog Size              18.13 MB\n   ALL      Dir  Changelog Size              86.39 kB\n   # ....................................................................................\n   ALL      avg. File Entry Size             79.85 kB\n   ALL      avg. Dir  Entry Size             151.00 B\n   # ------------------------------------------------------------------------------------\n   ALL      memory virtual                   270.75 MB\n   ALL      memory resident                  67.02 MB\n   ALL      memory share                     6.09 MB\n   ALL      memory growths                   1.05 MB\n   ALL      threads                          26\n   # ------------------------------------------------------------------------------------\n   eosdevsrv2:# access ls\n   # ....................................................................................\n   # Redirection Rules ...\n   # ....................................................................................\n   [ 01 ]                         ENOENT:* => eosdevsrv1.cern.ch\n   [ 02 ]                              w:* => eosdevsrv1.cern.ch\n\n\nRun a master<=>slave MGM role change procedure\n----------------------------------------------\n\n.. code-block:: bash\n\n   # switch the master MGM to RO mode\n   eosdevsrv1:# eos -b ns master eosdevsrv2.cern.ch\n\n   # switch the slave MGM to master mode\n   eosdevsrv2:# eos -b ns master eosdevsrv2.cern.ch\n\n   # switch the RO mode master MGM to slave mode\n   eosdevsrv1:# eos -b ns master eosdevsrv2.cern.ch\n\n\n\nMaster/Slave eos shell interface\n--------------------------------\n\n.. code-block:: bash\n\n   eos -b ns : shows the current state of slave/master MGM/MQ and the current configuration\n   eos -b ns master: shows the log file of any master/slave transition command including the initial boot\n   eos -b ns master --log-clear : clean the in-memory logfile\n   eos -b ns master --disable : disable the heartbeat-supervisor thread modifying redirection/stall variables\n   eos -b ns master --enable: enable the heartbeat-supervisor thread modifying redirection/stall variables\n\n\nBounce the MQ Service\n---------------------\n\nTo bounce the MQ service you should make the slave machine also to a master and then declare the other as slave e.g. to move from eosdevsrv1 to eosdevsrv2 you do\n\n.. code-block:: bash\n\n   eosdevsrv2:# service eos master mq\n   eosdevsrv1:# service eos slave mq\n\nIt is important to never declare both machines as slaves at the same time!\nWhile it should work well if the broker alias points to both machines it is\nprobably more efficient to use a dedicated alias for the MQ broker and always\npoint only to one box. This has to be tested.\n\nConfigure Online Compactification\n---------------------------------\n\nOn the MGM master running in RW mode one can configure online compactification\nto compact the namespace once or in defined intervals. The configuration of\nonline compacting is for the moment not persistent e.g. after a service restart\nonline compactification is always disabled. For the rare event of a change-log file corruption\nit is possible to add a '-repair' to the compactification type e.g. 'all-repair', 'all-files', 'all-directories'.\nThe repair skips broken records up to 1kb, otherwise 'eos-log-repair' has to be used offline.\n\nThe interface for online compactification is\n\n.. code-block:: bash\n\n   eos -b ns compact on : schedules online compactification for files immediately. Immedeatly means that the compactification starts within the next minute.\n   eos -b ns compact on 100 : schedules online compactification for files with a delay of 100 seconds. The compactification starts with a delay of 100 to max. 160 seconds.\n   eos -b ns compact on 1 86400 : schedules online compactification for files with a delay of 1 seconds. The compactification is rescheduled always one day later automatically.\n   eos -b ns compact on 60 0 all : schedule online compactification for files and directories once in one minute.\n   eos -b ns compact on 60 0 files : schedule online compactification for files once in one minute.\n   eos -b ns compact on 60 0 directories : schedule online compactification for files once in one minute.\n   eos -b ns compact on 60 0 all-repair : schedule online compactification for files and directories with auto-repair once in one minute.\n\nThe RW MGM signals a RO slave when the compactification starts and when it is\nfinished and triggers a reload of the namespace on the RO MGM once the\ncompacted file is fully resynchronized.\n\nDuring compactification the namespace is set into RO mode for a short time\nand then locked with a write lock for short moment to update the offset\ntable pointing to the compacted namespace file.\n\nThe various stages of compactification can be traced with\n\n.. code-block:: bash\n\n   eos -b ns\n   eos -b ns master\n   EOS Console [root://localhost] |/> ns\n   # ------------------------------------------------------------------------------------\n   # Namespace Statistic\n   # ------------------------------------------------------------------------------------\n   ...\n   # ....................................................................................\n   ALL      Compactification                 status=off waitstart=0 interval=0 ratio=0.0:1\n   # ....................................................................................\n\nWhen the compactification has been enabled:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> ns\n   # ------------------------------------------------------------------------------------\n   # Namespace Statistic\n   # ------------------------------------------------------------------------------------\n   ...\n   # ....................................................................................\n   ALL      Compactification                 status=starting waitstart=0 interval=0 ratio=0.0:1\n   # ....................................................................................\n\nWhen the compactification is running:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> ns\n   # ------------------------------------------------------------------------------------\n   # Namespace Statistic\n   # ------------------------------------------------------------------------------------\n   ...\n   # ....................................................................................\n   ALL      Compactification                 status=compacting waitstart=0 interval=0 ratio=0.0:1\n   # ....................................................................................\n   EOS Console [root://localhost] |/> ns\n\nWhen the compactification is waiting for the next scheduling interval to run:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> ns\n   # ------------------------------------------------------------------------------------\n   # Namespace Statistic\n   # ------------------------------------------------------------------------------------\n   ...\n   # ....................................................................................\n   ALL      Compactification                 status=wait waitstart=85430 interval=86400 ratio=3.4:1\n   # ....................................................................................\n\nThe ratio parameter in the output shows the namespace file compression factor\nachieved during the last compactification run.\n\nIf compactification fails for any reason the namespace boot status is failed !\n\nWhen the namespace is not in RW mode compacting is blocked:\n\n.. code-block:: bash\n\n   # ------------------------------------------------------------------------------------\n   # Namespace Statistic\n   # ------------------------------------------------------------------------------------\n   ...\n   # ....................................................................................\n   ALL      Compactification                 status=blocked waitstart=0 interval=0 ratio=0.0:1\n   # ....................................................................................\n\nIn case records have been repaired with auto-repair enabled, they are reported in the master log.\n"
  },
  {
    "path": "doc/citrine/configuration/master_quarkdb.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Master/Slave QuarkDB Configuration\n\n.. _quarkdbconfig:\n\nMaster/Slave QuarkDB Configuration\n===================================\n\nThe Citrine release starting with version (4.4.0) adds support for single master,\nmultiple slave setup when used with a QuarkDB backend. The master election happens\nautomatically by using the \"lease\" functionality provided by QuarkDB.\n\nHow it works\n------------\n\nWhen an MGM node is started, it will connect to the QuarkDB backend and try to\nacquire the \"master_lease\". If it successfully acquires it then the current\nnode becomes the master. The lease is acquired for a period of 10 seconds. The\ncurrent MGM will renew the lease every 5 seconds. The lease has two important\npieces of information attached to it: the current owner of the lease and the\nvalidity of the lease.\n\nWhen another MGM is started, it will try to acquire the same lease and fail. At\nthis point the new MGM is marked as a slave and will also know who the current\nmaster is, therefore redirecting any \"write\" traffic to the correct master MGM.\n\nExport instance configuration to QuarkDB\n----------------------------------------\n\nIn order to have the master-slave setup properly working the configuration of the\nEOS instance needs to also be stored in QuarkDB. This is done by setting the\nfollowing configuration directive in the ``/etc/xrd.cf.mgm`` file:\n\n.. code-block:: bash\n\n   mgmofs.cfgtype quarkdb\n\nThe configuration will be stored in the same QuarkDB cluster as the namespace\ninformation. Therefore, the following directives are used by both the namespace\nimplementation and the configuration engine:\n\n.. code-block:: bash\n\n  mgmofs.qdbcluster eos-mgm-1.cern.ch:7777 eos-mgm-2.cern.ch:7777 eos-mgm-3.cern.ch:7777\n  mgmofs.qdbpassword_file /etc/eos.keytab\n\nStart the MGM and use the ``eos config export [-f] <path_to_config_file>`` to export the\nconfiguration stored in the pointed file to the current instance of QuarkDB. Note that\nby default when the MGM daemon is started it will create an empty config map in QuarkDB\nand this one needs to be deleted before the export command is launched. This can also be\nachieved by using the \"-f\" (force overwrite) flag in the \"eos config export\" command.\n\n.. code-block:: bash\n\n   redis-cli keys \"eos-config*\" | awk '{print \"redis-cli -p 7777 del \"$1;}' | sh -x\n\nAfter running the export command there should be a key entry in QuarkDB with the\nfollowing name ``eos-config:default`` for the **default** configuration.\n\nAn alternative method to do the configuration export is to use the ``eos-config-inspect`` tool.\nOne can use the following steps to do this:\n\n * Install the eos-ns-inspect package (v4.7.15+)\n * Dry-run (no configuration change yet, just import the current config in QDB for testing) from the current MGM master:\n\n.. code-block:: bash\n\n    eos-config-inspect export --source /var/eos/config/[fqdn_of_current_master]/default.eoscf --members cluster-qdb:7777 --password-file /etc/eos.keytab\n    eos-config-inspect dump --password-file eos.keytab --members cluster-qdb.cern.ch:7777 | tee default.eoscf.qdb\n\n  * Check that the two configs are identical\n  * Stop the MGM and add the ``mgmofs.cfgtype quarkdb`` to ``/etc/xrd.cf.mgm``\n  * Check if there were changes between the in-file config and the in-qdb config after the MGM stopped. If there are differences, re-run the export (with wipe), dump the config again as a file (with the ``eos-config-inspect`` tool), check the diffs again.\n  * If all went well in the previous steps, start the MGM\n\nTo have the QuarkDB HA setup working properly one also needs to set the following\nenvironment variable for both the MGM and MQ daemons:\n\n.. code-block:: bash\n\n   EOS_USE_QDB_MASTER=1\n\n\nMaster-slave status\n-------------------\n\nThe current status of the running MGM daemon can be monitored by using the ``eos ns``\ncommand. The relevant part regarding the master-slave status is displayed in the\n**Replication** section, e.g.:\n\n.. code-block:: bash\n\n   ALL      Replication                is_master=true master_id=eos-mgm-1.cern.ch:1094\n\nThis section details whether the current MGM is the master and also prints the\nidentity of the current master. For a slave MGM the output of the command looks\nlike the following:\n\n.. code-block:: bash\n\n   ALL      Replication                is_master=false master_id=eos-mgm-1.cern.ch:1094\n\nFor a slave MGM the namespace metadata cache is disabled. This is done to avoid\nconfusion for a user connected to the slave MGM and getting stalle information\n- since it might have been updated by the master MGM. Therefore, all the metadata\non the slave is fetched directly from the QuarkDB backend.\n\nForce master-slave transition\n------------------------------\n\nIn order to force a master-slave transition it is sufficient to issue the following\ncommand on the MGM master node: ``eos ns master other``. The \"other\" argument can\nbe replaced by anything else except the current master id. This will cause the current\nMGM node not to update its lease therefore losing its master status. The other\n(slave) MGMs will now compete for the lease and only one of them will become the\nnew master.\n"
  },
  {
    "path": "doc/citrine/configuration/namespace.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Namespace Configuration\n\nNamespace Configuration\n==========================\n\nThe BERYLL release supports to run two MGM's in master/slave namespaces with\nrealtime switching from slave=>master role.\n\nSince Version 0.3.235 there are various performance oriented optimizations available to speed-up the boot process.\n\n\nParallel Boot Configuration\n---------------------------\n\n.. code-block:: bash\n\n   export EOS_NS_BOOT_PARALLEL=1\n\nRun a multi-threaded boot procedure using the maximum number of available core's of a machine. By default an MGM uses a sequential boot running on a single core.\n\nDisable CRC32 Checksumming\n---------------------------\n\n.. code-block:: bash\n\n   export EOS_NS_BOOT_NOCRC32=1\n\nIf you use a filesystem which has internal checksumming there is no need to let the MGM do record checksumming during a boot.\nBe careful with this option since it removes another consistency check.\n\nDisable mapped changelog files\n------------------------------\n\n.. code-block:: bash\n\n   export EOS_NS_BOOT_NOMMAP=1\n\n\nSince version 0.3.235 the MGM mmap's change files in the first phase until a compaction mark is detected.\nIf you are short in memory, you can disable this mmap functionality.\nMapping removes a bottleneck of doing many ::pread calls for small lengths, which bottlenecks the boot performance.\n\n\nEnable subtree accounting\n-------------------------\n\n.. code-block:: bash\n\n   export EOS_NS_ACCOUNTING=1\n\n\nThis will enable subtree accounting for the namespace. When you use a 'fileinfo' command on a directory there is a special field displayed 'Treesize' which is the sum of all logical file sizes in this tree of the namespace. If subtree accounting is enabled, 'ls -l' shows the tree size instead of the directory size.\n\nEnable sync time propagation\n----------------------------\n\n.. code-block:: bash\n\n   export EOS_SYNCTIME_ACCOUNTING=1\n\nEach directory which has the extended attribute 'sys.mtime.propagation=1' set, will propagate its modification time into parent directory sync time.\nThe parent directory sync time is updated if the propagated modification time is newer than the last stored sync time.\nThis mechanism is used to find quickly directories which have modifications as used by Owncloud clients or backup scripts.\nThe 'fileinfo' command displays for directories besides \"Change\", \"Modify\" time a third field with the propagated \"Sync\" time.\n\n\nNamespace Size Preset Variables\n-------------------------------\n\n.. code-block:: bash\n\n   # Set Namespace Preset size\n   export EOS_NS_DIR_SIZE=15000000\n   export EOS_NS_FILE_SIZE=62000000\n\nIt is possible to resize hashmaps to the expected maximum size at the start of the boot process. There is no other advantage besides that the MGM process never needs to resize the hashmap during normal operation ( locking the namespace for several seconds). The boot time of the namespace stays unchanged by these settings.\n"
  },
  {
    "path": "doc/citrine/configuration/permission.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Permission System\n\nPermission System\n=================\n\nOverview\n--------\n\nThe EOS permission system is based on a combination of **ACLs**  and **POSIX** permissions.\n\nThere are two major differences to traditional storage systems:\n\n#. Files don't carry their permissions (only the ownership for quota accounting). They inherit the permissions from the parent directory.\n#. Permissions are only checked in the direct parent, EOS is not walking through the complete directory hierarchy.\n#. For ACLs, a directory ACL applies to all files, except those who have their own ACL.\n\nUNIX Permissions\n\nEOS allows to set user, group and other permissions for read write and browsing defined\nby ``'r'(4), 'w'(2), 'x'(1)``, e.g. ``777 ='rwx'``.\n\nUnlike in POSIX the S_ISGID (2---) indicates that any new directory created should automatically inherit all the\nextended attributes defined at creation time from the parent directory.\n\nIf the parent attributes are changed after creation, they are not automatically\napplied to its children. The inheritance bit is always added to any *chmod* automatically. >\n\nAll modes for *chmod* have to be given in octal format. For details see **eos chmod --help**.\n\nThe S_ISVTX bit (1---) is displayed whenever a directory has any extended attribute defined.\n\nACLs\n----\nACLs are defined on the directory or file level via the extended attributes.\n\n.. code-block:: bash\n\n   sys.acl=<acllist>\n   user.acl=<acllist>\n\n.. note::\n\n   For efficiency, file-level ACLs should only be used sparingly, in favour of directory-level ACLs.\n\nThe sys.acl attribute can only be defined by SUDO members.\nThe user.acl attribute can be defined by the **owner** or SUDO members. It is only evaluated if the sys.eval.useracl attribute is set.\n\nThe sys.acl/user.acl attributes are inherited from the parent at the time a directory is created. Subsequent changes to a directory's ACL\ndo not automatically apply to child directories.\n\n<acllist> is defined as a comma separated list of rules:\n\n.. code-block:: bash\n\n   <acllist> = <rule1>,<rule2>...<ruleN>\n\nA rule is defined in the following way:\n\n.. code-block:: bash\n\n   <rule> = u:<uid|username>|g:<gid|groupname>|egroup:<name>|z::{rwxomqcia(!d)(+d)(!u)(+u)}\n\nA rule has three colon-separated fields. It starts with the type of rule:\nUser (u), Group (g), eGroup (egroup) or all (z). The second field specifies the name or\nthe unix ID of user/group rules and the eGroup name for eGroups\nThe last field contains the rule definition.\n\nThe following tags compose a rule:\n\n.. epigraph::\n\n   === =========================================================================\n   tag definition\n   === =========================================================================\n   r   grant read permission\n   w   grant write permission\n   x   grant browsing permission\n   m   grant change mode permission\n   !m  forbid change mode operation\n   !d  forbid deletion of files and directories\n   +d  overwrite a '!d' rule and allow deletion of files and directories\n   !u  forbid update of files\n   +u  overwrite a '!u' rule and allow updates for files\n   q   grant 'set quota' permissions on a quota node\n   c   grant 'change owner' permission on directory children\n   i   set the immutable flag\n   a   grant archiving permission\n   === =========================================================================\n\n\n\n\nActually, every single-letter permission with the exception of change owner (c) can\nbe explicitly denied ('!'), e.g. '!w!r, re-granted ('+'). Change owner\npermission is only explicitly enabled on grant, so it is denied by default.\nDenials persist after all other rules have been evaluated, i.e. in 'u:fred:!w!r,g:fredsgroup:wrx' the user \"fred\"\nis denied reading and writing although the group he is in has read+write access.\nRights can be re-granted (in sys.acl only) even when denied by specifying e.g. '+d'. Hence,\nwhen sys.acl='g:admins:+d' and then user.acl='z:!d' are evaluated,\nthe group \"admins\" is granted the 'd' right although it is denied to everybody else.\n\nA complex example is shown here:\n\n.. code-block:: bash\n\n   sys.acl=\"u:300:rw!u,g:z2:rwo,egroup:eos-dev:rwx,u:dummy:rwm!d,u:adm:rwxmqc\"\n\n   # user id 300 can read + write, but not update\n   #\n   # group z2 can read + write-once (create new files but can't delete)\n   #\n   # members of egroup 'eos-dev' can read & write & browse\n   #\n   # user name dummy can read + write into directory and modify the permissions\n   # (chmod), but cannot delete directories inside which are not owned by him.\n   #\n   # user name adm can read,write,browse, change-mod, set quota on that\n   # directory and change the ownership of directory children\n\n.. note::\n\n   Write-once and '!d' or '!u' rules remove permissions which can only be regained\n   by a second rule adding the '+u' or '+d' flag e.g. if the matching user ACL\n   forbids deletion it is not granted if a group rule does not forbid deletion!\n\n\nIt is possible to write rules, which apply to everyone:\n\n.. code-block:: bash\n\n   sys.acl=\"z:i\"\n\n   # this directory is immutable for everybody\n\n\nThe user.acl (if defined) is evaluated after the sys.acl, e.g. If we have:\n\n.. code-block:: bash\n\n    sys.acl=’g:admins:+d’ and user.acl=’z:!d’\n\ni.e., the group “admins” is granted the 'd' right although it is denied to everybody else in the user.acl.\n\n\nFinally the ACL can be set via either of the following 2 commands, see `eos acl --help` or `eos attr set --help`. From the operational perspective one may prefer the former command as it acts specifically on the element we change (egroup, user ... etc.) instead of re-specifying the whole permission set of rules (`eos attr set` case). `eos acl` set of commands also allow for specific position to place the rule in when creating or modifying a rule. By default rules are appended at the end of the acl, `--front` flag allows to place a rule at the front, and an integer position starting from 1 (which is equivalent to `--front`) can also be used to explicitly move a rule to a specific position via the `--position` argument.\n\n.. note::\n\nFrom EOS version 5.1.14 and later the behavior of recursive ACL set has changed, keeping in view of very large directory trees. Previously any recursive ACL set command ensures that no directory creation happens during the ACL set, while this is synchronous for very large trees with millions of directories, one can end up blocking everything else. This is moved to fine-grained locks, with the downside that a directory created during the time ACLs are applied may not see the ACLs being applied, in case their parent hasn't inherited yet. The old synchronous behavior can be restored with a `--with-synchronous-write-lock` flag, though it is really not recommended for very large tree hierarchies.\n\n.. code-block:: bash\n\n   eos attr set sys.acl=<rule_a>,<rule_b>.. /eos/mypath\n   eos acl --sys <rule_c> /eos/mypath\n   eos acl --front <rule_d> /eos/mypath\n   eos acl --position 2 <rule_f> /eos/mypath\n\nThe ACLs can be listed by either of these commands as well:\n\n.. code-block:: bash\n\n   eos attr ls /eos/mypath\n   eos acl -l /eos/mypath\n\n\nIf the operator uses the `eos acl --sys <rule> /eos/mypath` command, the <rule> is composed as follows:\n`[u|g|egroup]:<identifier>[:|=]<permission>`. The second delimiter [:|=] can be a \":\" for modifying permissions\nor \"=\" for setting/overwriting permission. Finally a <permission> itself can be added using the \"+\" or removed using the \"-\" operators.\n\nFor example:\n\n.. code-block:: bash\n\n   $ eos attr ls /eos/mypath\n   sys.acl=\"u:99999:rw,egroup:mygroup:rw\"\n   #\n   # if you try to set the deletion permission using ':' modification sign:\n   $ eos acl --sys 'egroup:mygroup:!d' /eos/mypath\n   #\n   # you will get an error since there is no deletion permission defined yet in the original ACL (i.e. nothing to be modified), but\n   # one can add this new !d permission to the existing ACLs by the '+' operator:\n   $ eos acl --sys 'egroup:mygroup:+!d' /eos/mypath\n   #\n   -->\n   #\n   $ eos attr ls /eos/mypath\n   sys.acl=\"egroup:mygroup:rw!d,u:99999:rw\"\n   #\n   # one can also remove this permission by the '-' operator:\n   $eos acl --sys 'egroup:mygroup:-!d' /eos/mypath\n   -->\n   #\n   $ eos attr ls /eos/mypath\n   sys.acl=\"u:99999:rw,egroup:mygroup:rw\"\n   #\n   # or set completely new permission, overwriting all by '=':\n   eos acl --sys 'egroup:mygroup=w' /eos/mypath\n   -->\n   #\n   $ eos attr ls /eos/mypath\n   sys.acl=\"u:99999:rw,egroup:mygroup:w\"\n   # append a new rule to the end\n   $ eos acl --sys u:1002=\\!w /eos/mypath\n   $ eos attr ls /eos/mypath\n   sys.acl=\"u:99999:rw,egroup:mygroup:rw,u:1002:!w\"\n\n   # Move a rule to the front, the full rule needs to be specified\n   $ eos acl --front egroup:mygroup=rw /eos/mypath\n   $ eos attr ls /eos/mypath\n   sys.acl=\"egroup:mygroup:rw,u:99999:rw,u:1002:!w\"\n\n   # Add a new rule at a specific position\n   $ eos acl --position 2  egroup:mygroup2=rwx /eos/mypath\n   $ eos attr ls /eos/mypath\n   sys.acl=\"egroup:mygroup:rw,egroup:mygroup2:rwx,u:99999:rw,u:1002:!w\"\n\n\n.. note::\n\n   * The \"-r 0 0\" can be used to map your account with the sudoers role. This has to be assigned to your account on the EOS instance by the service manager, see `eos vid ls`), e.g. `eos -r 0 0 acl --sys 'egroup:mygroup:!d' /eos/mypath`.\n   * If no '--sys' or '--user' is specified, by default the `eos acl` sets '--sys' permissions.\n\n\nValidity of Permissions\n----------------------------\n\nFile Access\n+++++++++++\nA file ACL (if it exists), or the directory's ACL is evaluated\nfor access rights.\n\nA user can read a file if the ACL grants 'r' access\nto the user's uid/gid pair. If no ACL grants the access,\n[the directory's] UNIX permissions are evaluated for a matching 'r' permission bit.\n\nA user can create a file if the parent directory grants 'w' access via the ACL\nrules to the user's uid/gid pair. A user cannot overwrite a file if the ACL\ngrants 'wo' permission. If the ACL does not grant the access, UNIX permissions\nare evaluated for a matching 'w' permission bit.\n\n.. note::\n\n   The root role (uid=0 gid=0) can always read and write any file.\n   The daemon role (uid=2) can always read any file.\n\nFile Deletion\n+++++++++++++\n\nA file can be deleted if the parent directory grants 'w' access via the ACL\nrules to the user's uid/gid pair. A user cannot delete a file,\nif the ACL grants 'wo' or '!d' permission.\n\n.. note::\n\n   The root role (uid=0 gid=0) can\n   always delete any file.\n\nFile Permission Modification\n++++++++++++++++++++++++++++\n\nFile permissions cannot be changed, they are automatically inherited from the\nparent directory.\n\nFile Ownership\n++++++++++++++\n\nA user can change the ownership of a file if he/she is member of the SUDO group.\nThe root, admin user and admin group role can always change the ownership of a\nfile. See **eos chown --help**  for details.\n\nDirectory Access\n++++++++++++++++\n\nA user can create a directory if they have the UNIX 'wx' permission, or the ACL\nrules grant the 'w' or 'wo' permission. The root role can always create any directory.\n\nA user can list a directory if the UNIX permissions grant 'rx' or the ACL\ngrants 'x' rights.\n\n.. note::\n\n   The root, admin user and admin group role can always\n   browse directories.\n\nDirectory Deletion\n++++++++++++++++++\n\nA user can delete a directory if he/she is the owner of the directory.\nA user can delete a directory if he/she is not the owner of that directory\nin case 'UNIX 'w'permission are granted and '!d' is not defined by a matching\nACL rule.\n\n.. note::\n\n   The root role can always delete any directory.\n\n.. warning::\n\n   Deletion only works if directories are empty!\n\nDirectory Permission Modification\n+++++++++++++++++++++++++++++++++\n\nA user can modify the UNIX permissions if they are the owner of the file\nand/or the parent directory ACL rules grant the 'm' right.\n\n.. note::\n\n   The root, admin\n   user and admin group role can always modify the UNIX permissions.\n\nDirectory ACL Modification\n++++++++++++++++++++++++++\n\nA user can modify a directory's system ACL, if they are a member of the SUDO group.\nA user can modify a directory's user ACL, if they are the owner of the directory or\na member of the SUDO group.\n\nDirectory Ownership\n+++++++++++++++++++\n\nThe root, admin user and admin group role can always change the directory\nowner and group.\nA normal user can change the directory owner if the system ACL allows this, or if the user ACL allows it *and* they change the owner to themselves.\n\n.. warning::\n\n   Otherwise, only privileged users can alter the ownership.\n\nQuota Permission\n++++++++++++++++\n\nA user can do 'quota set' if he is a sudoer, has the 'q' ACL permission set on\nthe quota node or on the proc directory ``/eos/<instance>/proc``.\n\nRichacl Support\n+++++++++++++++\n\nOn systems where \"richacl\"s (a more sophisticated ACL model derived from NFS4 ACLs) are supported, e.g. CentOS7,\nthe translation between EOS ACLs and richacls is by nature incomplete and not always two-ways:\n\nan effort is made for example to derive a file's or directory's :D: (RICHACL_DELETE) right from the parent's 'd' right,\nwhereas the :d: (RICHACL_DELETE_CHILD) right translates to the directory's own 'd'.\nThis helps applications like samba; however, setting\n:D: (RICHACL_DELETE) on a directory does not affect the directory's parent as permissions for individual\nobjects cannot be expressed in EOS ACLs;\n\nthe EOS 'm' (change mode) right becomes :CAW: (RICHACE_WRITE_ACL|RICHACE_WRITE_ATTRIBUTES|RICHACE_WRITE_NAMED_ATTRS);\n\nthe EOS 'u' (update) right becomes :p: (RICHACE_APPEND_DATA), although this is not really equivalent. It implies that :w: (RICHACE_WRITE_DATA) only grants writing of new files,\nnot rewriting parts of existing files.\n\nRichacls are created and retrieved using the {get,set}richacl commands and the relevant richacl library functions\non the fusex-mounted EOS tree. Those utilities act on the user.acl attribute and ignore sys.acl.\n\n\nHow to setup a shared scratch directory\n+++++++++++++++++++++++++++++++++++++++\n\nIf a directory is group writable one should add an ACL entry for this group\nto forbid the deletion of files and directories to non-owners and allow\ndeletion to a dedicated account:\n\nE.g. to define a scratch directory for group 'vl' and the deletion\nuser 'prod' execute:\n\n.. code-block:: bash\n\n   eos attr set sys.acl=g:vl:!d,u:prod:+d /eos/dev/scratchdisk\n\nHow to setup a shared group directory\n+++++++++++++++++++++++++++++++++++++\n\nA directory shared by a <group> with variable members should be setup like this:\n\n.. code-block:: bash\n\n   chmod 550 <groupdir>\n   eos attr set sys.acl=\"egroup:<group>:rw!m\"\n\nSticky Ownership\n+++++++++++++++++++++++++++++++++++++++\n\nThe ACL tag sys.owner.auth allows to tag clients acting as the owner of a directory. The value normally is composed by the authentication method and the user name or can be a wildcard.\nIf a wild card is specified, everybody resulting in having write permission can use the sticky ownership and write into a directory on behalf of the owner e.g. the file is owned by the directory\nowner and not by the authenticated client and quota is booked on the directory owner.\n\n.. code-block:: bash\n\n   eos attr set sys.owner.auth=\"krb5:prod\"\n   eos attr set sys.owner.auth=\"*\"\n\nPermission Masks\n++++++++++++++++\n\nA permission mask which is applied on all chmod requests for directories can be defined via:\n\n.. code-block:: bash\n\n   sys.mask=<octal-mask>\n\nExample:\n\n.. code-block:: bash\n\n   eos attr set sys.mask=\"770\"\n   eos chmod 777 <dir>\n   success: mode of file/directory <dir> is now '770'\n\nWhen the mask attribute is set the !m flag is automatically disabled even if it is given in the ACL.\n\nACL CLI\n+++++++\n\nTo provide atomic add,remove and replacement of permissions one can take advantage of the ``eos acl`` command instead of modifying directly the `sys.acl` attribute:\n\n.. code-block:: bash\n\n   Usage: eos acl [-l|--list] [-R|--recursive][--sys|--user] <rule> <path>\n\n       --help           Print help\n   -R, --recursive      Apply on directories recursively\n   -l, --lists          List ACL rules\n       --user           Set user.acl rules on directory\n       --sys            Set sys.acl rules on directory\n   <rule> is created based on chmod rules.\n   Every rule begins with [u|g|egroup] followed with : and identifier.\n\n   Afterwards can be:\n   = for setting new permission .\n   : for modification of existing permission.\n\n   This is followed by the rule definition.\n   Every ACL flag can be added with + or removed with -, or in case\n   of setting new ACL permission just enter the ACL flag.\n\n\nAnonymous Access\n++++++++++++++++\n\nAnonymous access can be allowed by configuring unix authentication (which maps by default everyone to user nobody). If you want to restrict anonymous access to a certain domain you can configure this via the ``access`` interface:\n\n.. code-block:: bash\n\n   eos access allow domain nobody@cern.ch\n\nAs an additional measure you can limit the deepness of the directory tree where anonymous access is possible using the ``vid`` interface e.g. not more than 4 levels:\n\n.. code-block:: bash\n\n   eos vid publicaccesslevel 4\n\n\nThe default value for the publicaccesslevel is 1024.\n"
  },
  {
    "path": "doc/citrine/configuration/proxys.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Proxies\n\nProxies and firewall entrypoints\n===============================\n\nOverview\n--------\n\nIn EOS, it is possible to configure filesystems so that they require to be accessed by clients through some FSTs acting as proxies.\nSuch *node* are called *proxy*, short for data-proxy.\nSome nodes can also act as firewall entrypoints (fwep) to allow access to data nodes behind a firewall.\n\nConfiguring a proxy\n-------------------\n\nA proxy *node* is just a normal FST daemon running on some machine. It can host standard FST filesystems or no filesystem at all as\nthey are not needed for the purpose of acting as a *proxy*.\n\n*Proxies* are grouped into *proxygroups* whose the name may designate the capacity of the *proxies* in the group. For instance, the *proxygroup*\n``kinetic`` may group *proxies* that can talk XRootD with a client coming to get/put some data and that can talk with Kinetic drives for the storage.\n\nDepending on the version of the software, FSTs can act as proxy for different types of FS.\n\n.. note::\n\n   A given *node* can belong to multiple *proxygroups*\n\nTo manage nodes' membership, the following eos commands can be used:\n\n::\n\n   node proxygroupadd\n   node proxygrouprm\n   node proxygroupclear\n\nTo view nodes' membership, the following eos commands can be used:\n\n::\n\n   node status\n\nHere follows an example.\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/multipath/> node status p05153074617805.cern.ch:1095\n   # ------------------------------------------------------------------------------------\n   # Node Variables\n   # ....................................................................................\n   debug.level                      := info\n   debug.state                      := debug\n   domain                           := MGM\n   gw.ntx                           := 10\n   gw.rate                          := 120\n   kinetic.cluster.default          := base64:...\n   kinetic.location.default         := base64:...\n   kinetic.reload                   := default\n   kinetic.security.default         := base64:...\n   manager                          := p05153074617805.cern.ch:1094\n   proxygroups                      := c5group\n   ...\n\nNote that if ``proxygroups`` is not defined for a node , it means the same as proxygroups being defined and empty.\n\nIt is also possible to review the scheduling snapshots associated to a proxygroup with the command\n\n::\n\n   geosched show snapshot\n\nHere follows an example (partial output).\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/multipath/> geosched show snapshot\n   ### scheduling snapshot for proxy group c5group :\n   --------c5group/( free:2|repl:0|pidx:0|status:OK|ulSc:99|dlSc:99|filR:0|totS:0)\n          `----------nogeotag/( free:2|repl:0|pidx:1|status:OK|ulSc:99|dlSc:99|filR:0|totS:0)\n                    |----------1/( free:1|repl:0|pidx:0|status:RW|ulSc:99|dlSc:99|filR:0|totS:0)@p05153074617805.cern.ch:1095\n                    `----------2/( free:1|repl:0|pidx:0|status:RW|ulSc:99|dlSc:99|filR:0|totS:0)@p05153074625071.cern.ch:1095\n\nConfiguring a filesystem\n------------------------\n\nA filesystem can be:\n\n- A native FST filesystem. In that case, there is no need for a proxy when a client accesses the filesystem.\n\n- An imported filesystem. In that case, a *proxygroup* should be configured.\n\n.. note::\n\n  As of today, supported imported filesystems can be of the following types:\n\n  - XRootd filesystem (another EOS instance for example)\n\n  - Kinetic Drives Cluster\n\n  - RadosFs storage\n\n  - http(s) storage\n\n  - S3(s) storage\n\n\nThe type is configured by setting the mount point a filesystem when calling ``eos fs add``. The path can be a local directory starting with ``/`` or it can be ``s3(s)://`` , ``http(s)://`` , ``kinetic://`` , ``root://`` .\nTo tag a filesystem as requiring an access through a proxy of a given proxygroup, the following eos command can be used:\n\n::\n\n   fs config <fsid> proxygroup=<proxygroup>\n\nNote that the special value <none> is equivalent to proxygroup not being defined i.e. no proxygroup associated to the fs.\n\nIt is possible to review the proxygroup a filesystem relies on using the following eos command:\n\n::\n\n   fs status <fsid>\n\nHere follows an example (partial output).\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/multipath/> fs status 2\n   # ------------------------------------------------------------------------------------\n   # FileSystem Variables\n   # ....................................................................................\n   bootcheck                        := 0\n   bootsenttime                     := 1470773776\n   configstatus                     := rw\n   drainperiod                      := 86400\n   graceperiod                      := 86400\n   host                             := p05153074617805.cern.ch\n   hostport                         := p05153074617805.cern.ch:1095\n   id                               := 2\n   path                             := kinetic://cluster5/\n   port                             := 1095\n   proxygroup                       := c5group\n   queue                            := /eos/p05153074617805.cern.ch:1095/fst\n\nNote that if proxygroup is not define, it is equivalent to proxygroup having the value <none>.\n\nFirewall entrypoints and direct access\n-------------------------------------\n\n\nEOS offers some functionalities to define hosts (gathered in proxygroups) acting as firewall entrypoints (fwep) and when they should be used.\nFirst, it is possible to restrain target geotags that are directly accessible from client geotags (i.e no need to go through a fwep).\nThis can be done using the command\n\n::\n\n   geosched access setdirect\n\n| The direct access rules can be reviewed using\n\n::\n\n   geosched access showdirect\n\n| Please note that direct access rules act as a white list. If norule is defined, it means that all accesses are meant to be direct.\n| Here follows an example of direct access rules\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/geotree/users/gadde/2rep/> geosched access showdirect\n   --------AccessGeotagMapping\n          |----------site1 [site1 => site1]\n          |         `----------rack1 [site1::rack2 => site1,site2::rack2]\n          |\n          `----------site2 [site2 => site2]\n\n| This output means that a client geotagged ``site1`` can directly access a filesystem tagged ``site1``, a client geottaged ``site1::rack1`` can access a filesystem geotagged ``site1`` or ``site2::rack2``.\n| Note that the rule to apply is the first rule met poping tokens from the right of the geotag.\n| In the current example, a client tagged ``site1::rack2`` has no rule for its geotag and it has a rule for ``site1``, it will use it.\n| A client tagged ``site1::rack1`` has a rule attached to its geotag and will use it.\n| There is only one matched rule. For instance, here, the client tagged ``site1::rack1`` can access ``site1::rack2`` and ``site2::rack2`` but cannot access ``site1`` (other than ``site1::rack2``).\n| The client tagged ``site1::rack2`` can access site1 which means any geotag starting with ``site1::``.\n\n\n| If access cannot be direct as by the rules defined earlier, a proxy MUST be found for the access to succeed.\n| A selection rule maps a target geotag to a proxygroup from which an host used as fwep will be selected during the scheduling of the access.\n| Fwep selection rules can be set with the command\n\n\n::\n\n   geosched access setproxygroup\n\n| The rules can be reviewed with the command\n\n::\n\n   geosched access showproxygroup\n\n| Here follows an example of fwep selection.\n\n.. code-block:: bash\n\n   --------AccessGeotagMapping\n          `----------site2 [site2 => ep2]\n                    `----------rack2 [site2::rack2 => ep22]\n\n| Note, that the selection of the rule to apply works the same as for the direct access rules.\n| It means that in our example, a non direct access to a filesystem tagged ``site2`` or ``site2::rack1`` will go through a fwep taken from proxygroup ``ep2``.\n| A non direct access to a filesystem tagged ``site2::rack2`` or ``site2::rack2::whatever`` will go through a fwep taken from proxygroup ``ep22``.\n| A non direct access to a filesystem tagged ``site1`` will FAIL because no proxygroup to find a fwep from can be deduced from the available rules.\n|\n| Machine acting as fweps should be configured in one of the two following ways.\n\nJust another proxy\n~~~~~~~~~~~~~~~~~~\nThe node is just a standard proxy that can access all the possible types of filesystems. It can then be used as a proxy for any fs in the instance.\n\nForwarding gateway\n~~~~~~~~~~~~~~~~~~\n\n\nIt is possible to use an XRootD forwarding daemon together with an FST daemon on fwep nodes.\nWith this configuration, the proxy node might not be able to serve the access to all types of filesystems.\nIf a client is scheduled to a filesystem of which the proxygroup is not supported by the scheduled fwep proxy, the scheduler will use the forwarding gateway running on that machine to forward the access to a proxy from the right proxygroup.\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/multipath/> fs ls\n\n   #...........................................................................................................................................................................\n   #                   host (#...) #   id #                           path #     schedgroup #         geotag #       boot # configstatus #      drain # active #         health\n   #...........................................................................................................................................................................\n    p05153074617805.cern.ch (1095)      1          kinetic://single-drive/          default                        booted             ro      nodrain   online         1/1 (+0)\n    p05153074617805.cern.ch (1095)      2              kinetic://cluster5/          default                        booted             rw      nodrain   online       25/25 (+4)\n\nFile scheduling through proxies\n-------------------------------\nFirst some tools are mentioned to help to make the config right.\nThen, the scheduling procedure is detailed and some additional features are presented.\n\nObserving the state of the scheduler and the properties of the files\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nProxy scheduling is part of the geoscheduling engine. (see :doc:`geoscheduling`)\nAs such, there is an easy way to check if all the proxies are well configured and then taken into account in the geoscheduling system as members of the expected proxygroups.\nProxies are organized in trees, one for each *proxygroup*. Those trees are automatically kept in sync with configurations of the nodes, including the config variable proxygroups.\nTo review the snapshots, the following EOS command can be used.\n\n::\n\n   geosched show snapshot\n\nIt can also be very handy, at least for testing purpose, to be able to list the filesystems the replicas of a files are stored on along with their proxygroups.\nThis can be carried out using the EOS command.\n\n::\n\n   fileinfo <path> --proxy\n\nProxy scheduling logic\n~~~~~~~~~~~~~~~~~~~~~~\n\nHere follows a sketch of the file scheduling algorithm with an emphasize on the proxy part. When an file access or placement is requested, the execution go through the following steps:\n\n- The filesystems are selected according to the layout of the file and some scheduling settings.\n\n- For each filesystem in the selection find a data proxy if one is required (proxygroup defined for the fs) and a fwep proxy (in the proxygroup according to the fwep selection rules) if required the direct access rules by doing :\n\n  * if it is a filesticky scheduling get the proxy associated to the accessed file.\n\n  * if we have a data proxy and if needed, find a fwep proxy as close as possible to the data proxy and we are done. If we don't have a data proxy yet choose a fwep proxy which is as close to the client as possible if the client is geotagged and this behavior is configured ( parameter ``pProxyCloseToFs`` is set 0 in the geoscheduling configuration) or as close to the filesystem otherwise.\n\n  * if we have no data proxy yet, check if the fwep is a member of the required proxygroup. If it is, set it also as the data proxy. If it is not, select a data proxy following the same requirements as in the previous step.\n\n\nFile-sticky proxy scheduling\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nFor some reason, it may be necessary that access to a file goes consistently through one node or a subset of the proxygroup.\nIt is called *file-sticky proxy scheduling*. It is used for instance to maximize performance of some file caching that would be done on the proxy nodes. It involves a filesystem parameter called ``filestickyproxydepth``.\nIt can be set using the eos command:\n\n.. code-block:: bash\n\n   eos fs <fsid> setconfig filestickyproxydepth=<some_integer>\n\nNote that having this variable undefined is equivalent to have it defined with a negative value and it means that the file-sticky proxy scheduling is disabled.\n\nUsually the outcome of a proxy scheduling for a given filesystem would be the best possible and slightly randomized trade-off between proximity of the filesystem (or the client) and of the proxy and availability of resource of the proxy. The algorithm which is used does not depend on the file, only on the geotags of the client, the geotag of the filesystem and the geotag of proxies in the proxygroup to be scheduled from.\n\nWhen using file-sticky proxy scheduling, the behavior is different.\nFirst a starting point for the search is decided. If ``ProxyCloseToFs`` is false and that the client has a geotag, it is the client's geotag. Other wise, it is the filesystem's getag.\nThe starting point is projected on the considered proxygroup's scheduling tree. Then the resulting point is moved ``filestickyproxydepth`` steps uproot.\nAll the proxies in the subtree starting from there are then flated-out in an array. The proxies are sorted by id.\nThe proxy is then selected using the inode number of the file to be accessed.\n\n\nChoosing the value of ``filestickyproxydepth`` depends on where (in terms of geotag) are placed the proxies compared to the filesystems.\n"
  },
  {
    "path": "doc/citrine/configuration/qos.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: QoS Interface\n\nQoS Interface\n=============\n\nEOS provides a QoS interface to manipulate storage properties of files.\nThe interface is composed of the following components:\n  - QoS Class\n  - QoS API\n  - QoS CLI\n\nQoS Class\n---------\n\nA QoS class is an abstraction of the following storage parameters:\n  - layout id\n  - replication level\n  - checksum type\n  - placement policy\n\nQoS classes also offer certain guarantees, such as latency or redundancy provided,\nand list the legal transitions to other QoS classes.\n\nQoS configuration\n+++++++++++++++++\n\nWithin the system, they are defined via a QoS definition file,\nwhich gets loaded at MGM boot-up.\n\nThe following config settings control the QoS behavior:\n\n.. code-block:: bash\n\n  # xrd.cf.mgm\n  mgmofs.qosdir /var/eos/qos/          # QoS directory\n  mgmofs.qoscfg /var/eos/qos/qos.conf  # location of QoS config file\n\n  # Environment variable\n  EOS_ENABLE_QOS=\"\"                    # enable QoS if defined\n\nQoS definition file\n+++++++++++++++++++\n\nQoS classes are defined using JSON and have the following structure:\n  - name\n  - allowed transitions to other QoS classes\n  - metadata guarantees\n  - storage parameters\n\nExample of a definition file:\n\n.. code-block:: bash\n\n  {\n    \"disk_plain\": {\n      \"name\": \"disk_plain\",\n      \"transition\": [\n        \"disk_replica\"\n      ],\n      \"metadata\": {\n        \"cdmi_data_redundancy_provided\": 0,\n        \"cdmi_geographic_placement_provided\": [\n          \"CH\"\n        ],\n        \"cdmi_latency_provided\": 75\n      },\n      \"attributes\": {\n        \"layout\": \"plain\",\n        \"replica\": 1,\n        \"checksum\": \"adler32\",\n        \"placement\": \"scattered\"\n      }\n    },\n\n    \"disk_replica\": {\n      \"name\": \"disk_replica\",\n      \"transition\": [\n        \"disk_plain\"\n      ],\n      \"metadata\": {\n        \"cdmi_data_redundancy_provided\": 1,\n        \"cdmi_geographic_placement_provided\": [\n          \"CH\"\n        ],\n        \"cdmi_latency_provided\": 75\n      },\n      \"attributes\": {\n        \"layout\": \"replica\",\n        \"replica\": 2,\n        \"checksum\": \"adler32\",\n        \"placement\": \"scattered\"\n      }\n    }\n  }\n\nQoS API\n-------\n\nThe QoS API allows the following operations to be performed:\n  - List available QoS classes\n  - Retrieve the QoS class of an entry\n  - Setting the QoS class of an entry\n\nListing QoS classes\n+++++++++++++++++++\n\nThe operation is performed via the CLI and will list all the QoS classes\nloaded on the system. Listing a given QoS class will print the class definition.\n\nExample:\n\n.. code-block:: bash\n\n  > eos -j qos list | jq .\n  {\n    \"name\": [\n      \"disk_plain\",\n      \"disk_replica\"\n    ]\n  }\n\n  > eos -j qos list disk_replica | jq .\n  {\n    \"attributes\": {\n      \"checksum\": \"adler32\",\n      \"layout\": \"replica\",\n      \"placement\": \"scattered\",\n      \"replica\": \"2\"\n    },\n    \"metadata\": {\n      \"cdmi_data_redundancy_provided\": 1,\n      \"cdmi_geographic_placement_provided\": [\n        \"CH\"\n      ],\n      \"cdmi_latency_provided\": 75\n    },\n    \"name\": \"disk_replica\",\n    \"transition\": [\n      \"disk_plain\"\n    ]\n  }\n\nRetrieving QoS class of an entry\n++++++++++++++++++++++++++++++++\n\nThe QoS class of an entry is identified at runtime.\nFor files, this means inspecting the relevant storage parameters.\n\nFor directories, this means extracting the relevant storage parameters\nfrom the directory's extended attributes.\n\nExample:\n\n.. code-block:: bash\n\n  > eos -j qos get /eos/xdc/qos/file\n  {\n    \"attributes\": {\n      \"checksum\": \"adler32\",\n      \"layout\": \"plain\",\n      \"placement\": \"scattered\",\n      \"replica\": \"1\"\n    },\n    \"current_qos\": \"disk_plain\",\n    \"disksize\": \"100000000\",\n    \"id\": \"40092\",\n    \"metadata\": {\n      \"cdmi_data_redundancy_provided\": \"0\",\n      \"cdmi_geographic_placement_provided\": [\n        \"CH\"\n      ],\n      \"cdmi_latency_provided\": \"75\"\n    },\n    \"path\": \"/eos/xdc/qos/file\",\n    \"size\": \"100000000\"\n  }\n\nSetting QoS class of an entry\n+++++++++++++++++++++++++++++\n\nSetting the QoS class of a directory implies changing the storage-related\nextended attributes.\n\nSetting the QoS class of a file implies changing that file's storage parameters.\nThis is done by scheduling a conversion job and setting the following\nextended attribute on the file: `user.eos.qos.target=<qos_class>`.\n\nExample:\n\n.. code-block:: bash\n\n  > eos -j qos set /eos/xdc/qos/file disk_replica\n  {\n    \"conversionid\" : \"0000000000009c9c:default#00650112~scattered\",\n    \"retc\" : 0\n  }\n\n  > eos -j qos get /eos/xdc/qos/file | jq .\n  {\n    \"attributes\": {\n      \"checksum\": \"adler32\",\n      \"layout\": \"plain\",\n      \"placement\": \"scattered\",\n      \"replica\": \"1\"\n    },\n    \"current_qos\": \"disk_plain\",\n    \"disksize\": \"100000000\",\n    \"id\": \"40092\",\n    \"metadata\": {\n      \"cdmi_data_redundancy_provided\": \"0\",\n      \"cdmi_geographic_placement_provided\": [\n        \"CH\"\n      ],\n      \"cdmi_latency_provided\": \"75\"\n    },\n    \"path\": \"/eos/xdc/qos/file\",\n    \"size\": \"100000000\",\n    \"target_qos\": \"disk_replica\"\n  }\n\n  > eos attr ls /eos/xdc/qos/file/\n  user.eos.qos.target=\"disk_replica\"\n\nWhen the file is successfully converted, the target QoS extended attribute is removed.\n\nQuerying this extended attribute is one way to find out whether the QoS transition\ntook place. However, for more details, it is recommended\nto use the provided conversion id.\n\nQoS CLI\n=======\n\nAn `eos qos` command is provided for CLI interaction.\nMore information may be found in the :ref:`clientcommands` section.\n\n.. note::\n\n  All QoS CLI commands also provide JSON output.\n"
  },
  {
    "path": "doc/citrine/configuration/quarkdb.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single:: QuarkDB\n\n.. _quarkdbconf:\n\n\n\nQuarkDB\n=======\n\n`QuarkDB <https://quarkdb.web.cern.ch/docs/master>`_ is a highly available datastore that implements a small subset of the redis command set. It is built on top of rocksdb, an embeddable, transactional key-value store. High availability is achieved through multiple replicated nodes and the raft distributed consensus algorithm.\n\n.. image:: raft.jpg\n   :scale: 100%\n   :align: center\n\nThe EOS CITRINE version allows to persist the namespace in QuarkDB avoiding the high memory footprint of the in-memory namespace. QuarkDB is considered like a database as an external component which you need to install, configure and manage separately.\n\nPreparation\n-----------\n\nQuarkDB has the best performance when storing KV data on an SSD. To guarantee high-availability with predictable performance you should have at least three homogeneous nodes.\n\nInstallation\n------------\n\nWe currently provide RPMs for QuarkDB on CentOS 7.\n\nYou need to configure first a YUM repository file `/etc/yum.repos.d/quarkdb.repo`:\n\n.. code-block:: bash\n\n   [quarkdb-stable]\n   name=QuarkDB stable repository\n   baseurl=https://storage-ci.web.cern.ch/storage-ci/quarkdb/tag/el7/$basearch\n   enabled=1\n   gpgcheck=0\n   protect=1\n\nInstall the relevant RPMs on all three nodes:\n\n.. code-block:: bash\n\n   yum install quarkdb quarkdb-debuginfo redis\n\n.. note::\n\n   QuarkDB has also a dependency on `XRootD <http://xrootd.org>`_ (see :ref:`eos_base_setup_repos` or use XRootD from the EPEL repository).\n   The **redis** package is installed to get access to the **redis-cli** command.\n\nConfiguration\n-------------\n\nOn each of the nodes we have to create a DB directory using `quarkdb-create`. The given path has **not** to exist!\n\nEach node in the cluster has to use an agreed `cluster-id` to allow to peer between the three QuarkDB cluster nodes. The `cluster-id` can be e.g. the EOS instance name or an UUID.\n\n.. code-block:: bash\n\n   // node 1\n   quarkdb-create --path /var/lib/quarkdb/node-1 --clusterID eosfoo.bar --nodes node1foo.bar:7777,node2foo.bar:7777,node3foo.bar:7777\n   chown -R daemon:daemon /var/lib/quarkdb/node-3\n\n   // node 2\n   quarkdb-create --path /var/lib/quarkdb/node-2 --clusterID eosfoo.bar --nodes node1foo.bar:7777,node2foo.bar:7777,node3foo.bar:7777\n   chown -R daemon:daemon /var/lib/quarkdb/node-2\n\n   // node 3\n   quarkdb-create --path /var/lib/quarkdb/node-3 --clusterID eosfoo.bar --nodes node1foo.bar:7777,node2foo.bar:7777,node3foo.bar:7777\n   chown -R daemon:daemon /var/lib/quarkdb/node-3\n\nQuarkDB runs as a protocol plugin inside `XRootD <http://xrootd.org>`_.\n\nTo start QuarkDB as an XRootD service you have first to create one configuration file `/etc/xrootd/xrootd-quarkdb.cf` per node referencing the node with `redis.myself`:\n\n.. code-block:: bash\n\n   # xrootd@quarkdb node 1\n   xrd.port 7777\n   xrd.protocol redis:7777 libXrdQuarkDB.so\n   redis.mode raft\n   redis.database /var/lib/quarkdb/node-1\n   redis.myself node1.foo.bar:7777\n\n.. code-block:: bash\n\n   # xrootd@quarkdb node 2\n   xrd.port 7777\n   xrd.protocol redis:7777 libXrdQuarkDB.so\n   redis.mode raft\n   redis.database /var/lib/quarkdb/node-1\n   redis.myself node2.foo.bar:7777\n\n.. code-block:: bash\n\n   # xrootd@quarkdb node 3\n   xrd.port 7777\n   xrd.protocol redis:7777 libXrdQuarkDB.so\n   redis.mode raft\n   redis.database /var/lib/quarkdb/node-1\n   redis.myself node3.foo.bar:7777\n\nService Management - start and stop\n-----------------------------------\n\nThe QuarkDB service is managed via **systemd** on CentOS 7:\n\n.. code-block:: bash\n\n   # start\n   systemctl start xrootd@quarkdb\n\n   # stop\n   systemctl stop  xrootd@quarkdb\n\n   # status\n   systemctl status xrootd@quarkdb\n\n   # restart\n   systemctl restart xrootd@quarkdb\n\nChecking your cluster\n-----------------------\n\nUsing the raft algorithm the available nodes elect a leader when at least two out of three nodes are available.\n\nYou can verify the state of each QuarkDB node using the redis-cli:\n\n.. code-block:: bash\n\n   redis-cli -p 7777\n\n   127.0.0.1:7777> raft-info\n    1) TERM 6\n    2) LOG-START 0\n    3) LOG-SIZE 21\n    4) LEADER qdb-test-1.cern.ch:7777\n    5) CLUSTER-ID ed174a2c-3c2d-4155-85a4-36b7d1c841e5\n    6) COMMIT-INDEX 20\n    7) LAST-APPLIED 20\n    8) BLOCKED-WRITES 0\n    9) LAST-STATE-CHANGE 155053 (1 days, 19 hours, 4 minutes, 13 seconds)\n   10) ----------\n   11) MYSELF node1.foo.bar:7777\n   12) STATUS LEADER\n   13) ----------\n   14) MEMBERSHIP-EPOCH 0\n   15) NODES node1.foo.bar:7777,node2.foo.bar:7777,node3.foo.bar:7777\n   16) OBSERVERS\n   17) ----------\n   18) REPLICA node2.foo.bar:7777 ONLINE | UP-TO-DATE | NEXT-INDEX 21\n   19) REPLICA node3.foo.bar:7777 ONLINE | UP-TO-DATE | NEXT-INDEX 21\n\nThe above output yields that node1.foo.bar is currently the leader. Most redis commands are typically issued against a leader.\n\nYou can verify that your cluster is operational setting and getting a key on the leader:\n\n.. code-block:: bash\n\n   // on the leader\n   redis-cli -p 7777\n   node1.foo.bar:7777> set testkey hello\n   OK\n   node1.foo.bar:7777> get testkey\n   \"hello\"\n\nRunning a single node cluster\n-----------------------------\n\nIf you want to test a simplified setup, you can do the previous steps on a single node and start the cluster with configuration file referencing `redis.mode standalone`:\n\n.. code-block:: bash\n\n   # xrootd@quarkdb node 1\n   xrd.port 7777\n   xrd.protocol redis:7777 libXrdQuarkDB.so\n   redis.mode standalone\n   redis.database /var/lib/quarkdb/node-1\n   redis.myself node1.foo.bar:7777\n\n\nExtending/Modifying your QuarkDB cluster\n----------------------------------------\n\nSometimes you will need to replace, add or remove a node of your QuarkDB cluster. This can be done without downtime. Please refer to the QuarkDB `Membership update documentation <http://quarkdb.web.cern.ch/quarkdb/docs/master/MEMBERSHIP.html>`_.\n\n\n\nSecurity\n--------\n\n.. warning::\n\n   Currently QuarkDB is deployed without TLS. To make sure no third party accesses or tampers your KV storage you should configure the firewall accordingly that only MGM and FST nodes have direct access to QuarkDB (by default on port 7777). This will be improved in the near future.\n\nSource Code\n-----------\n\nQuarkDB is OpenSource and available on `GitHUB <https://gitlab.cern.ch/eos/quarkdb>`_ and `GitLAB@CERN <https://gitlab.cern.ch/eos/quarkdb>`_.\n\nTo build QuarkDB manually do\n\n.. code-block:: bash\n\n   git clone https://gitlab.cern.ch/eos/quarkdb && cd quarkdb\n   git submodule update --recursive --init\n\n   mkdir build && cd build\n   cmake ..\n   make -j 4\n   ./test/quarkdb-tests\n\nBuild dependencies can be installed using/running `utils/el7-packages.sh`.\n\nFurther documentation\n---------------------\n\nFor details refer to the main `QuarkDB Documentation <http://quarkdb.web.cern.ch/quarkdb/docs/master/>`_.\n"
  },
  {
    "path": "doc/citrine/configuration/quota.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Quota System\n\nQuota System\n============\n\nThe EOS quota system provides user, group and project quota similar to\nfilesystems like EXT4, XFS ... e.g. quota is expressed as max. number of\ninodes(=files) and maximum volume. The implementation of EOS quota uses the\ngiven inode limit as hard quota, while volume is applied as soft quota e.g.\nit can be slightly exceeded.\n\nQuota is attached to a so called 'quota node'. A quota node defines the\nquota rules and counting for a subtree of the namespace. If the subtree\ncontains another quota node in a deeper directory level quota is rooted\non the deeper node.\n\nAs an example we can define two quota nodes:\n\n.. epigraph::\n\n   ============ =======================\n   Node         Path\n   ============ =======================\n   Quota Node 1 /eos/lhc/raw/\n   Quota Node 2 /eos/lhc/raw/analysis/\n   ============ =======================\n\nA file like ``/eos/lhc/raw/2013/raw-higgs.root`` is accounted for in the first\nquota node, while a file ``/eos/lhc/raw/analysis/histo-higgs.root`` is\naccounted for in the second quota node.\n\nThe quota system is easiest explained lookint at the output of\na **quota** command in the EOS shell:\n\n.. code-block:: bash\n\n   eosdevsrv1:# eos quota\n   # _______________________________________________________________________________________________\n   # ==> Quota Node: /eos/dev/2rep/\n   # _______________________________________________________________________________________________\n   user       used bytes logi bytes used files aval bytes aval logib aval files filled[%]  vol-status ino-status\n   adm        2.00 GB    1.00 GB    8.00 -     1.00 TB    0.5 TB     1.00 M-    0.00       ok         ok\n\nThe above configuration defines user quota for user ``adm`` with 1 TB of volume\nquota and 1 Mio inodes under the directory subtree ``/eos/dev/plain``.\nAs you may notice EOS distinguishes between logical bytes and (physical) bytes.\nImagine a quota node subtree is configured to store 2 replica for each file,\nthen a 1 TB quota allows you effectively to store 0.5 TB (aval logib = 0.5 TB!).\n\n.. warning::\n\n   All quota set via the 'quota set' command is defining the (physical) bytes\n   and EOS displays the logical bytes value based on the layout definition on\n   the quota node.\n\nThe volume and inode status is displayed as 'ok' if there is quota left for\nvolume/inodes. If there is less than **5%** left, 'warning' is displayed,\nif there is none left 'exceeded'. If volume and/or inode quota is set to 0\n'ignored' is displayed. In this case a quota setting of 0 signals not to apply\nthe quota however if both are '0' the referenced UID/GID has no quota.\n\nThere are three types of quota defined in EOS: user, group & project quota!\n\nUser Quota\n----------\n\nUser quota defines volume/inode quota based on user id  UID.\nIt is possible to combine user and group quota on a quota node.\nIn this case both have to 'ok' e.g. provide enough space for a file placmment.\n\nGroup Quota\n-----------\nGroup quota defines volume/inode quota based on group id GID.\nAs described before it is possible to combine group and user quota.\nIn this case both have to allow file placement.\n\nProject Quota\n-------------\nProject quota books all volume/inode usage under the project subtree to a single\nproject account. E.g. the recycle bin uses this quota type to measure a subtree\nsize. In the EOS shell interface project quota is currently defined setting\nquota for group 99:\n\n.. code-block:: bash\n\n   eosdevsrv1:# eos set -g 99 -p /eos/lhc/higgs-project/ -v 1P -i 100M\n\nSpace Quota\n-----------\nIt is possible to set a physical space restriction using the space parameter **nominalsize**\n\n.. code-block:: bash\n\n   # restrict the physical space usage to 1P\n   eosdevsrv1:# eos space config default space.nominalsize=1P\n\nThe restriction is only used, if the connected user is not in the **sudoer** list. The current usage and space setting is\ncached for 30s e.g. it might take up to 30s until any change may take effect.\n\nQuota Enforcement\n-----------------\nQuota enforcement is applied when new files are placed and when files in RW mode\nare closed e.g. EOS can reject to store a file if the quota exceeds during an\nupload. If user and group quota is defined, both are applied.\n\nQuota Command Line Interface\n----------------------------\n\nList Quota\n++++++++++\nTo see your quota as a user use:\n\n.. code-block:: bash\n\n   eosdevsrv1:# eos quota\n\nTo see quota of all users (if you are an admin):\n\n.. code-block:: bash\n\n   eosdevsrv1:# eos quota ls\n\nTo see the quota node for a particular directory/subtree:\n\n.. code-block:: bash\n\n   eosdevsrv1:# eos quota ls /eos/lhc/higgs-project/\n\nSet Quota\n+++++++++\n\nThe syntax to set quota is:\n\n.. code-block:: bash\n\n   eos quota set -u <uid>|-g <gid> [-v <bytes>] [-i <inodes>] -p <path>\n\nThe <uid>, <gid> parameter can be numerica or the real name. Volume and Inodes\ncan be specified as **1M**, **1P** etc. or a plain number.\n\n.. ::note\n\n   To set project quota use GID 99!\n\nDelete Quota\n+++++++++++++\n\nA quota setting can be removed using:\n\n.. code-block:: bash\n\n   eos quota rm -u <uid> |-g <gid> -p <path>\n\nOne has to specify to remove the user or the group quota, it is not possible\nto remove both with a single command.\n\n\nDelete Quota Node\n+++++++++++++++++\nSometimes it is necessary to remove completely a quota node.\nThis can be done via:\n\n.. code-block:: bash\n\n   eos quota rmnode -p <path>\n\nThe command will ask for a security code. Be aware the quota is not recalculated\nfrom scratch if the deletion of a node would now leave the accounting to an\nupstream node.\n"
  },
  {
    "path": "doc/citrine/configuration/recyclebin.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Recycle Bin\n\nRecycle Bin\n===========\n\n\nOverview\n--------\n\nThe EOS recycle bin allows to define a FIFO policy for delayed file deletion. \nThis feature is available starting with EOS BERYL.\n\nThe recycling bin is time-based and volume based e.g. the garbage directory \nperforms final deletion after a configurable time delay. The volume in the \nrecycle bin is limited using project quota. If the recycle bin is full no \nfurther deletion is possible and deletions fails until enough space is available.\n\nThe recycling bin supports individual file deletions and recursive bulk \ndeletions (referenced as object deletions in the following). \n\nThe owner of a deleted file or subtree deletion can restore files into the \noriginal location from the recycle bin if he has the required quota. If the original location is 'occupied' the action is rejected. Using the '-f' flag the existing location is renamed and the deleted object is restored to the original name.\n\nIf the parent tree of the restore location is incomplete the user is asked \nto first recreate the parent directory structure before objects are restored.\n\nIf the recycle bin is applicable for a deletion operation the quota is \nimmediately removed from the original quota node and added to the recycle quota. Without recycle bin quota is released once files are physically deleted!\n   \nCommand Line Interface \n----------------------\nIf you want to get the current state and configuration of the recycle bin you \nrun the recycle command:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle\n\n   # _______________________________________________________________________________________________\n   # used 0.00 B out of 100.00 GB (0.00% volume / 0.00% inodes used) Object-Lifetime 86400 [s]\n   # _______________________________________________________________________________________________\n\nThe values are self-explaining.\n\nDefine the object lifetime\n++++++++++++++++++++++++++\n\nIf you want to configure the lifetime of objects in the recycle bin you run r\n**recycle config --lifetime <lifetime>**:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle config --lifetime 86400\n\n<lifetime> can be e.g. just a number 3600, 3600s  (seconds) or 60min \n(60 minutes) 1d (one day), 1w (one week), 1mo (one month), 1y (one year) aso.\n\nThe lifetime has to be at least 60 seconds!\n\nDefine the recycle bin size\n+++++++++++++++++++++++++++\n\nIf you want to configure the size of the recycle bin you run \n**recycle config --size <size>**:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle config --size 100G\n\n<size> can be e.g. just a number 100000000000, 100000M (mega byte) or 100G (giga byte), 1T (one terra) aso.\n\nThe size has to be at least 100G !\n\n\nDefine the inode size of the recycle bin \n++++++++++++++++++++++++++++++++++++++++\n\nIf you want to configure the number of inodes in the recycle bin you run \n**recycle config --inodes <value>[K|M|G]**:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle config --inodes 1M\n\n<value> can be a number or suffixed with K (1000), M (1.000.0000) or G (1.000.000.000).\n\nIt is not mandatory to define the number of inodes to use a recycle bin.\n\n\nDefine an optional threshold ratio\n++++++++++++++++++++++++++++++++++\n\nIf you want to keep files as long as possible you can set a keep ratio on the recycle bin:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle config --ratio 0.8\n\nIn this example the recycle bin can be filled up-to 80%. Once it reaches the watermark it will clean all files matching the\ngiven lifetime policy. The cleaning will free 10% under the given watermark.  This option is not mandatory and probably not always the desired behaviour.\n\nBulk deletions\n++++++++++++++\nA bulk deletion using the recycle bin prints how the deleted files can \nbe restored:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/dev/2rep/subnode/> rm -r tree\n\n   success: you can recycle this deletion using 'recycle restore 00000000000007cf'\n\nAdd recycle policy on a subtree\n+++++++++++++++++++++++++++++++\n\nIf you want to set the policy to use the recycle bin in a subtree of the \nnamespace run:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/dev/2rep/subnode/> recycle config --add-bin /eos/dev/2rep/subnode/tree\n\n   success: set attribute 'sys.recycle'='../recycle' in directory /eos/dev/2rep/subnode/tree/\n\nRemove recycle policy from a subtree\n++++++++++++++++++++++++++++++++++++\n\nTo remove the recycle bin policy in a subtree run:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/dev/2rep/subnode/> recycle config --remove-bin /eos/dev/2rep/subnode/tree\n\n   success: removed attribute 'sys.recycle' from directory /eos/dev/2rep/subnode/tree/\n\nEnforce globally usage of a recycle bin for all deletions\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\nThe global policy is enforced in the default space:\n\n.. code-block:: bash\n\n   # enable\n   EOS Console [root://localhost ]/ space config default space.policy.recycle=on \n\n   # disable\n   EOS Console [root://localhost ]/ space config default space.policy.recycle=off\n\n   # remove policy\n   EOS Console [root://localhost ]/ space config default space.policy.recycle=remove\n\n\nList files in the recycle bin\n+++++++++++++++++++++++++++++++++++\n\nIf you want to list the restorable objects from the recycle bin you run: \n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/dev/2rep/subnode/> recycle ls\n   # Deletion Time            UID      GID      TYPE          RESTORE-KEY      RESTORE-PATH                                                    \n   # ==============================================================================================================================\n   Thu Mar 21 23:02:22 2013   apeters  z2       recursive-dir 00000000000007cf /eos/dev/2rep/subnode/tree\n\nBy default this command displays all user private restorable objects. \nIf you have the root role or are member of the admin group, you can add the ``-g`` flag to list restorable objects of all users.\n\nFor manageability reasons the list is truncated after 100k entries.\n\nRestoring Objects\n+++++++++++++++++\n\nObjects are restored using recycle restore <restore-key>. \nThe <restore-key> is shown by **recycle ls**.\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle restore 00000000000007cf\n\n   error: to recycle this file you have to have the role of the file owner: uid=755 (errc=1) (Operation not permitted)\n\nYou can only restore an object if you have the same uid/gid role \nlike the object owner:\n\n.. code-block:: bash\n   \n   EOS Console [root://localhost] |/eos/> role 755 1395 \n   => selected user role ruid=<755> and group role rgid=<1395>\n\n   EOS Console [root://localhost] |/eos/> recycle restore 00000000000007cf\n   success: restored path=/eos/dev/2rep/subnode/tree\n\nIf the original path has been used in the mean while you will see the following \nafter a restore command:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/dev/2rep/subnode/> recycle restore 00000000000007cf\n   error: the original path is already existing - use '--force-original-name' or '-f' to put the deleted file/tree back and rename the file/tree in place to <name>.<inode> (errc=17) (File exists)\n\nThe file can be restored using the force flag:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/dev/2rep/subnode/> recycle restore -f 00000000000007cf\n   warning: renamed restore path=/eos/dev/2rep/subnode/tree to backup-path=/eos/dev/2rep/subnode/tree.00000000000007d6\n   success: restored path=/eos/dev/2rep/subnode/tree\n\nPurging\n+++++++\n\nOne can force to flush files in the recycle bin before the lifetime policy \nkicks in using recycle purge:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/dev/2rep/subnode/> recycle purge\n   success: purged 1 bulk deletions and 0 individual files from the recycle bin!\n\nNotice that purging only removes files of the current uid/gid role. \nRunning as **root** does not purge the recycle bin of all users by default.\nIf you want to purge the recycle bin completely add the ``-g`` option.\n\nImplementation\n----------------\nThe implementation is hidden to the enduser and is explained to give some \ndeeper insight to administrators. All the functionality is wrapped as demonstrated before in the CLI using the recycle command. \n\nThe recycle bin resides in the namespace under the proc directory under ``/recycle/``.\n\nThe old structure until release 4.3.35 was\n\n``/recycle/<gid>/<uid>/<contracted-path>.<hex-inode>`` for files and\n\n``/recycle/<gid>/<uid>/<contracted-path>.<hex-inode>.d`` for bulk deletions.\n\nThe new structure since release 4.3.36 is\n\n``/recycle/<uid>/<year>/<month>/<date>/<index>/<contracted-path>.<hex-inode>`` for files and\n\n``/recycle/<uid>/<year>/<month>/<date>/<index>/<contracted-path>.<hex-inode>.d`` for bulk deletions.\n\nThe new structure adds to purge the recycle bin easily by date:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/dev/2rep/subnode/> recycle purge 2018/03/01\n   success: purged 12 bulk deletions and 0 individual files from the recycle bin!\n\nThe internal structure is however not relevant or exported to the end-user. \nThe contracted path flattens the full pathname replacing '/' with '#:#'.\n\nThe ``/recycle/`` directory is configured as a quota node with project space \ne.g. all files appearing in there are accounted on a catch-all project quota.\n\nDeletion only succeeds if the recycle quota node has enough space available \nto absorb the deletion object.\n\nA dedicated thread inside the MGM uses an optimized logic to follow the entries \nin the recycle tree and performs unrecoverable deletion according to the \nconfigured lifetime policy. The lifetime policy is defined via the external \nattribute sys.recycle.lifetime tagged on the /recycle directory specifying \nthe file lifetime in seconds.\n\nFile deletions and bulk deletions are moved in the recycle bin if the parent \ndirectory of the deletion object specifies as external attribute ``sys.recycle=../recycle/``.\n\nA restore operation can only succeed if the restore location provides the \nneeded quota for all objects to be restored.\n\nNote that a tree can have files owned by many individuals and restoration \nrequires appropriate quota for all of them. As mentioned the restore operation \nhas be executed with the role of the file or subtree top-level directory \nidentity (uid/gid pair).\n"
  },
  {
    "path": "doc/citrine/configuration/route.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Namespace routing System\n\nRouting System\n==============\n\nThe EOS route system provides a method to redirect paths within an existing namespace to an external namespace.\nIt can be thought of as symbolic links that allow clients to connect to another EOS instance.\n\nThis can be used to create a parent MGM that contains references to other EOS instances in a tree like structure,\nor to connect EOS namespaces together in a mesh like manner.\n\n`vid` policy and other access control still applies as if a user were connecting directly.\n\nA route is defined as a path, that maps to a remote hostname and port, that is the MGM of a remote EOS namespace.\nAccess to this path is via a redirect at the HTTP or xrootd level, and will incur some latency.\n\nWhen the latency penalty of the redirect is not desired, it's better to cache the redirect or use an autofs(8)\nor similar automounting solution for the paths.\n\nThe link always links to the root of the connected namespace.\n\nAs an example we can define three routes:\n\n.. epigraph::\n    \n   ====================================== =======================\n    Path                                   Destination\n   ====================================== =======================\n   /eos/test-namespace-1                   test-mgm-1:1094:8000\n   /eos/test-namespace-2                   test-mgm-2:1094:8000\n   /eos/test-namespace-1/test-namespace-3  test-mgm-3:1094:8000\n   ====================================== =======================\n\nChanging directory to `/eos/test-namespace-1`, would be akin to connecting directly to the mgm at `test-mgm-1:1094`.\n\n.. code-block:: bash\n\n    EOS Console [root://localhost] |/> route link /eos/test-namespace-1 test-mgm-1:1094:8000\n    EOS Console [root://localhost] |/> route link /eos/test-namespace-2 test-mgm-2:1094:8000\n    EOS Console [root://localhost] |/> route link /eos/test-namespace-1/test-namespace-3 test-mgm-3:1094:8000\n    EOS Console [root://localhost] |/> route ls\n    /eos/test-namespace-1/ => test-mgm-1:1094:8000\n    /eos/test-namespace-1/test-namespace-3/ => test-mgm-3:1094:8000\n    /eos/test-namespace-2/ => test-mgm-2:1094:8000\n\n\nThe above configuration defines defines the path configuration in the example above.\n\nIf a port combination is not specified, the route assumes a xrootd port of 1094, and a http port of 8000.\n\nCreating a link\n---------------\n\nA link is created using the `route link` command. It takes the option of a path and a destination host. Optional\nspecification includes the MGM's xrootd port, and the MGM's http port. Unspecified, they default to 1094 and 8000\nrespectively.\n\n.. code-block:: bash\n\n    EOS Console [root://localhost] |/> route link /eos/test-path eosdevsrv2:1094:8000\n    EOS Console [root://localhost] |/> route ls\n    /eos/test-path/ => eosdevsrv2:1094:8000\n\n\nRemoving a link\n---------------\n\nA link is removed using the `route unlink` command. Only a path needs to be specified.\n\n.. code-block:: bash\n\n    EOS Console [root://localhost] |/> route unlink /eos/test-namespace-1\n\n\nDisplaying current links\n------------------------\n\nThe `route ls` command shows current active links. An asterix is displayed in\nfront of the MGM node which acts as a master for that particular mapping.\n\n.. code-block:: bash\n\n    EOS Console [root://localhost] |/> route ls\n    /eos/test-namespace-1/ => *test-mgm-1:1094:8000\n    /eos/test-namespace-1/test-namespace-3/ => *test-mgm-3:1094:8000\n    /eos/test-namespace-2/ => *test-mgm-2:1094:8000\n\n\nMaking links visible to clients\n-------------------------------\n\nEOS will not display the link in a directory listing. This means it's possible to have an invisible link, and\nstat or fileinfo commands will fail against the link path.\n\nCreating a directory for the path will make it visible to clients, but accounting information will not be accurate until\na client changes into the path and queries again.\n\nConnecting clients\n------------------\n\nHTTP and xrootd clients can effectively connect to the top level MGM and will automatically follow redirects. It is\nrecommended for performance reasons to connect FUSE clients via an automounter directly to each MGM, and use either\npath mounting or bind mounts to replicate the tree structure.\n\nAutomounting\n------------\n\nIt is possible to convert the output of `route ls` and place it into a map for an automounter or other process to use.\n"
  },
  {
    "path": "doc/citrine/configuration/s3.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: S3\n\nS3 access\n=======================\n\n**S3** access is very similar to **HTTP** access. The **MGM** runs on port 8000\nand the **FSTs** on port 8001.\n\nThe workflow is the same as with other protocols.\nThe initial point of contact is the MGM which will generate a redirection link\nto communicate with the FST.\n\n\nUser Mapping\n-------------\n\nWhen accessing EOS via S3, a signature verification is done.\nIf the verification succeeds, from this point forward, the S3 access key\nwill be considered the user identity and the system will try to match it\nwith an existing Unix user. Because of this, it is very important\nthat the users declared for S3 access also exist as Unix users on the system.\n\n\nConfiguration\n-------------\n\nWithin the S3 protocol, users are identified by an access key and secret key pair.\nBuckets are associated to users and they constitute the place\nwhere users write or read from.\n\nWithin EOS, users and buckets are registered as attributes of the\n``/eos/<instance>/proc`` path.\n\nTo register users:\n++++++++++++++++++\n\n.. code-block:: bash\n\n   attr set sys.s3.id.<s3user>=<secretkey> /eos/<instance>/proc\n\n*<s3user>* - the access key in S3 terminology\n\nNote: make sure that the <s3user> also exists as a Unix user on the system\n\nTo register buckets:\n++++++++++++++++++++\n\n.. code-block:: bash\n\n   attr set sys.s3.bucket.<s3user>=<bucket> /eos/<instance>/proc\n   attr set sys.s3.path.<bucket>=<eospath> /eos/<instance>/proc\n\nMultiple buckets can be separated using the | separator.\n\nExample\n++++++++\n\n.. code-block:: bash\n\n   attr set sys.s3.id.s3user=<secretkey> /eos/test/proc\n   attr set sys.s3.bucket.s3user=testbucket /eos/test/proc\n   attr set sys.s3.path.testbucket=/eos/test/buckets3 /eos/test/proc\n\nThis will declare *s3user* and assign *testbucket* to him.\nInternally, *testbucket* is mapped to the following path: /eos/test/buckets3.\n"
  },
  {
    "path": "doc/citrine/configuration/scheduler.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Scheduler\n\nScheduler\n=========\n\nThe scheduler distinguishes two algorithms, one for placement, one for access. \n\n\nPlacement\n-------------\nPlacements selects the primary location round-robin over scheduling groups and within scheduling groups. In case filesystems have GEO tags, the scheduler tries to place the first and second replica in different locations. By default the selection of a filesystem is a weighted selection where the weight is computed by current load parameters (network,disk IO). For multiple GEO locations it makes sense to enforce a pre-defined policy. An exact GEO selection/placement can be enforced with the following space variables, which are separated for read and write operations:\n\n.. code-block:: bash\n\n   # enable\n   eos space config default space.geo.access.policy.write.exact=on\n   eos space config default space.geo.access.policy.read.exact=on\n   # disable\n   eos space config default space.geo.access.policy.write.exact=off\n   eos space config default space.geo.access.policy.read.exact=off\n\nThe current status of policy can be seen here (no entry means the policy is off):\n\n.. code-block:: bash\n\n   eos -b space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   ...\n   geo.access.policy.write.exact   := on\n   geo.access.policy.read.exact    := on\n   ...\n\n"
  },
  {
    "path": "doc/citrine/configuration/tracker.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single:: Replica Tracker\n\nReplication Tracker\n===================\n\nThe Replication Tracker follows the workflow of file creations. For each created file a virtual entry is created in the ``proc/tracker`` directory. Entries are removed once a layout is completely committed. The purpose of this tracker is to find inconsistent files after creation and to remove atomic upload relicts automatically after two days.\n\nConfiguration\n-------------\n\nTracker\n+++++++\nThe Replication Tracker has to be enabled/disabled in the default space only:\n\n.. code-block:: bash\n\n   # enable\n   eos space config default space.tracker=on\n   # disable\n   eos space config default space.tracker=off\n\nBy default Replication Tracking is disabled.\n\nThe current status of the Tracker can be seen via:\n\n.. code-block:: bash\n\n   eos space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   ...\n   tracker                        := off\n   ...\n\n\nAutomatic Cleanup\n-----------------\n\nWhen the tracker is enabled, an automatic thread inspects tracking entries and takes care of cleanup of tracking entries and the time based tracking directory hierarchy. Atomic upload files are automatically cleaned after 48 hours when the tracker is enabled.\n\n\nListing Tracking Information\n----------------------------\n\nYou can get the current listing of tracked files using:\n\n.. code-block:: bash\n\n   eos space tracker\n\n   # ------------------------------------------------------------------------------------\n   key=00142888 age=4 (s) delete=0 rep=0/1 atomic=1 reason=REPLOW uri='/eos/test/creations/.sys.a#.f.1.802e6b70-973e-11e9-a687-fa163eb6b6cf'\n   # ------------------------------------------------------------------------------------\n\n\n\nThe displayed reasons are:\n\n* REPLOW - the replica number is too low\n* ATOMIC - the file is an atomic upload\n* KEEPIT - the file is still in flight\n* ENOENT - the tracking entry has no corresponding namespace entry with the given file-id\n* REP_OK - the tracking entry is healthy and can be removed - FUSE files appear here when not replica has been committed yet\n\nThere is convenience command defined in the console:\n\n.. code-block:: bash\n\n   eos tracker # instead of eos space tracker\n\n\n\nLog Files\n---------\nThe Replication Tracker has a dedicated log file under ``/var/log/eos/mgm/ReplicationTracker.log``\nwhich shows the tracking entries and related cleanup activities.\nTo get more verbose information you can change the log level:\n\n.. code-block:: bash\n\n   # switch to debug log level on the MGM\n   eos debug debug\n\n   # switch back to info log level on the MGM\n   eos debug info\n"
  },
  {
    "path": "doc/citrine/configuration/transfer.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Transfer System\n\nTransfer System\n================\n\nOverview\n--------\n\nEOS has three major interfaces to do file transfers.\n\n**eos cp** is an EOS shell command which allows to im- and export files from/to\nEOS using XRootD, http, gsiftp and s3 protocol.\nBy default all traffic flows via the client issuing the command.\nIt is possible to use it in 'async' mode where IO is flowing through a third-party host.\n\n**eos file copy** is a third party transfer interface supporting TPC transfers inside the EOS instance.\n\n**eos transfer** allows to run scheduled transfers.\nIO is bridged via dedicated transfer gateways as explained in the following.\n\n.. ::note\n\n   The **Beryl** version of EOS supports the third-party-copy mechanism in XRootD >=3.3 using the standard\n   **xrdcp --tpc** command.\n\neos cp\n------\n\nAs a first overview we refer to the usage information of the EOS cp command.\nCurrently the support of copy full directory trees is only supported for EOS\ntype storage systems.\n\n.. code-block:: console\n\n   Usage: cp [--async] [--rate=<rate>] [--streams=<n>] [--recursive|-R|-r] [-a] [-n] [-S] [-s|--silent] [-d] [--checksum] <src> <dst>\n\n   '[eos] cp ..' provides copy functionality to EOS.\n   Options:\n   <src>|<dst> can be root://<host>/<path>, a local path /tmp/../ or an eos path /eos/ in the connected instanace...\n   --async         : run an asynchronous transfer via a gateway server (see 'transfer submit --sync' for the full options)\n   --rate          : limit the cp rate to <rate>\n   --streams       : use <#> parallel streams\n   --checksum      : output the checksums\n   -a              : append to the target, don't truncate\n   -n              : hide progress bar\n   -S              : print summary\n   -s --silent     : no output just return code\n   -d              : enable debug information\n   -k | --no-overwrite : disable overwriting of files\n\n.. note::\n\n   If you deal with directories always add a '/' in the end of source or target\n   paths e.g. if the target should be a directory and not a file put a '/' in the end.\n   To copy a directory hierarchy use '-r' and source and target directories terminated with '/' !\n\nExamples\n--------\n\n.. code-block:: console\n\n   eos cp /var/data/myfile /eos/foo/user/data/                   : copy 'myfile' to /eos/foo/user/data/myfile\n   eos cp /var/data/ /eos/foo/user/data/                         : copy all plain files in /var/data to /eos/foo/user/data/\n   eos cp -r /var/data/ /eos/foo/user/data/                      : copy the full hierarchy from /var/data/ to /var/data to /eos/foo/user/data/ => empty directories won't show up on the target!\n   eos cp -r --checksum --silent /var/data/ /eos/foo/user/data/  : copy the full hierarchy and just printout the checksum information for each file copied!\n\nS3\n++\n\nURLs have to be written as:\n\n.. code-block:: bash\n\n   as3://<hostname>/<bucketname>/<filename> as implemented in ROOT\n   or as3:<bucketname>/<filename> with environment variable S3_HOSTNAME set\n   and as3:....?s3.id=<id>&s3.key=<key>\n\nThe access id can be defined in 3 ways:\n\n.. code-block:: bash\n\n   env S3_ACCESS_ID=<access-id>          [as used in ROOT  ]\n   env S3_ACCESS_KEY_ID=<access-id>      [as used in libs3 ]\n\n   <as3-url>?s3.id=<access-id>           [as used in EOS transfers ]\n\n\nThe access key can be defined in 3 ways:\n\n.. code-block:: bash\n\n   env S3_ACCESS_KEY=<access-key>        [as used in ROOT  ]\n   env S3_SECRET_ACCESS_KEY=<access-key> [as used in libs3 ]\n   <as3-url>?s3.key=<access-key>         [as used in EOS transfers ]\n\nIf <src> and <dst> are using S3, we are using the same credentials on both ends\nand the target credentials will overwrite source credentials!\n\n\n\nFurther Examples\n++++++++++++++++\n\nImport a file from an S3 storage into EOS:\n\n.. code-block:: bash\n\n   eos cp as3://swift.cern.ch/eos/bigfile?s3.id=<secret>&s3.key=<secret> /eos/local/bigfile\n\n   [eos-cp] going to copy 1 files and 210.06 MB\n   [eoscp] bigfile                  Total 200.32 MB    |====================| 100.00 % [26.7 MB/s]\n   [eos-cp] copied 1/1 files and 210.06 MB in 8.63 seconds with 24.33 MB/s\n\nRun the same import via a transfer gateway:\n\n.. code-block:: bash\n\n   eos cp --async as3://swift.cern.ch/eos/bigfile?s3.id=<secret>&s3.key=<secret> /eos/local/bigfile\n\n   success: submitted transfer id=128095\n   [eoscp TX] [ done       ]    |====================|  100.0% : 9s\n   [eoscp] #################################################################\n   [eoscp] # Date                     : ( 1343733064 ) Tue Jul 31 13:11:04 2012\n\n   ...\n\nYou can also easily import web files (no upload):\n\n.. code-block:: bash\n\n\n   eos cp http://root.cern.ch/drupal /eos/local/root.cern.ch\n\n\nTransfer Gateways\n-----------------\n\nEvery FST node in EOS can act as gateway.\nIn fact it is possible to deploys FSTs only as gateways without any storage\nattached.\n\nA gateway is enabled via the command:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> node gw gateway1.cern.ch:1095 on\n\nYou can see the configuration state of nodes by doing:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> node ls\n   #-----------------------------------------------------------------------------------------------------------------------------\n   #     type #                       hostport #   status #     status # txgw #gw-queued # gw-ntx #gw-rate # heartbeatdelta #nofs\n   #-----------------------------------------------------------------------------------------------------------------------------\n   nodesview            gateway1.cern.ch:1095     online           on     on          0       10      100                ~     0\n   nodesview            storage1.cern.ch:1095     online           on    off          0       30      120                0    22\n\nDo disable a gateway do:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> node gw gateway1.cern.ch:1095 off\n\nYou see in the output of node ls that each node has two parameters for gateways:\n\n.. epigraph::\n\n   ======== ==================================================================================\n   variable definition\n   ======== ==================================================================================\n   gw-ntx   number of parallel transfers on this node\n   gw-rate  bandwidth limitation used per transfer (if not specified differently by a transfer)\n   ======== ==================================================================================\n\nThese parameters are defined via:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> node config gateway1.cern.ch gw.rate=100\n   EOS Console [root://localhost] |/> node config gateway1.cern.ch gw.ntx=10\n\nYou can get a comprehansive summary of the configuration per node using the\n**eos node status** command:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> node status eosdevsrv1.cern.ch\n   # ------------------------------------------------------------------------------------\n   # Node Variables\n   # ....................................................................................\n   gw.ntx                           := 10\n   gw.rate                          := 100\n   manager                          := eosdev.cern.ch:1094\n   stat.balance.ntx                 := 2\n   stat.balance.rate                := 25\n   stat.gw.queued                   := 0\n   status                           := on\n   symkey                           := G41RrP1y/SLHsf9AhneqbxXaOSU=\n   txgw                             := on\n\n\nTransfer Queue and CLI\n----------------------\n\nThe transfer state machine is as follows:\n\n.. epigraph::\n\n   ============================== =\n   state\n   ============================== =\n   inserted\n   validated\n   scheduled\n   stagein | stageout | running\n   done | failed\n   ============================== =\n\nInteraction with the transfer queue is done via the **eos transfer** CLI.\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> transfer\n   Usage: transfer submit|cancel|ls|enable|disable|reset|clear|resubmit|log ..'[eos] transfer ..' provides the transfer interface of EOS.\n   Options:\n   transfer submit [--rate=<rate>] [--streams=<#>] [--group=<groupname>] [--sync] <URL1> <URL2> :\n   transfer a file from URL1 to URL2\n   <URL> can be root://<host>/<path> or a local path /eos/...\n   --rate          : limit the transfer rate to <rate>\n   --streams       : use <#> parallel streams\n\n   --group         : set the group name for this transfer\n   transfer cancel <id>|--group=<groupname>\n   cancel transfer with ID <id> or by group <groupname>\n   <id>=*          : cancel all transfers (only root can do that)\n\n   transfer ls [-a] [-m] [s] [--group=<groupname>] [id]\n   -a              : list all transfers not only of the current role\n   -m              : list all transfers in monitoring format (key-val pairs)\n   -s              : print transfer summary\n   --group         : list all transfers in this group\n   --sync          : follow the transfer in interactive mode (like interactive third party 'cp')\n   <id> : id of the transfer to list\n\n   transfer enable         : start the transfer engine (you have to be root to do that)\n   transfer disable        : stop the transfer engine (you have to be root to do that)\n   transfer reset [<id>|--group=<groupname>]\n\n                           : reset all transfers to 'inserted' state (you have to be root to do that)\n   transfer clear          : clear's the transfer database (you have to be root to do that)\n   transfer resubmit <id> [--group=<groupname>]\n\n                           : resubmit's a transfer\n   transfer kill <id>|--group=<groupname>\n\n   transfer log <id>       : show the log of transfer <id>\n\n                           : kill a running transfer\n   transfer purge [<id>|--group=<groupname>]\n                           : remove 'failed' transfers from the transfer queue by id, group or all if not specified\n\nWhen a transfer has been submitted using ``transfer submit`` it will be in state inserted. When a transfer has been assigned to a transfer gateway it is in state scheduled. When a transfer is executed it will be either in status stagein (then stageout) or running. Certain protocols need a two stage process to bridge transfers. When transfer is going into status failed IT can be inspected using ``transfer log <id>``. Transfers moving into done state are automatically purged from the queue and put in the transfer archive.The transfer archive is a daily rotated log file in ``/var/log/eos/tx/transfer-archive.log`` storing all transfer logs. It is currently not accessible via the CLI.\n"
  },
  {
    "path": "doc/citrine/configuration/tty.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   pair: Console message Broadcasts; TTY\n\n\nImportant Messages Broadcasts on Terminals\n===========================================\n\nYou can enable console broadcasts on MGM nodes for all desired messages by defining the two following\nenvironment variables in ``/etc/sysconfig/eos``\n\n.. code-block:: bash\n\n   # ------------------------------------------------------------------\n   # MGM TTY Console Broadcast Configuration\n   # ------------------------------------------------------------------\n\n   # define the log file where you want to grep\n   export EOS_TTY_BROADCAST_LISTEN_LOGFILE=\"/var/log/eos/mgm/xrdlog.mgm\"\n\n   # define the log file regex you want to broad cast to all consoles\n   export EOS_TTY_BROACAST_EGREP=\"CRIT,ALERT,EMERG\"\n\n"
  },
  {
    "path": "doc/citrine/configuration/wfe.rst",
    "content": ".. highlight:: wfe\n\n.. index::\n   single:: WFE - Work Flow Engine\n\nWFE Engine\n==========\nThe workflow engine is a versatile event triggered storage process chain. Currently all events are created by file operations.\nThe policy to emit events is described as extended attributes of a parent directory. Each workflow is named. The default workflow\nis named 'default' and used if no workflow name is provided in an URL as `?eos.workflow=default`.\n\nThe workflow engine allows to create chained workflows: E.g. one workflow can trigger an event emission to run the next workflow in the chain and so on...\n\n.. epigraph::\n\n   ==================== ==================================================================================================\n   Event                Description\n   ==================== ==================================================================================================\n   sync::create         event is triggered at the MGM when a file is being created (synchronous event)\n   open                 event is triggered at the MGM when a 'file open'\n                        - if the return of an open call is ENONET a workflow defined stall time is returned\n   sync::prepare        event is triggered at the MGM when a 'prepare' is issued (synchronous event)\n   sync::abort_prepare  event is triggered at the MGM when xrdfs prepare -f issued (synchronous event)\n   sync::offline        event is triggered at the MGM when a 'file open' is issued against an offline file (synchronous\n                        event)\n   retrieve_failed      event is triggered with an error message at the MGM when the retrieval of a file has failed\n   archive_failed       event is triggered with an error message at the MGM when the archival of a file has failed\n   closer               event is triggered via the MGM when a read-open file is closed on an FST.\n   sync::closew         event is triggered via the FST when a write-open file is closed (it has priority over the asynchronous one)\n   closew               event is triggered via the MGM when a write-open file is closed on an FST\n   sync::delete         event is triggered at the MGM when a file has been deleted (synchronous event)\n   ==================== ==================================================================================================\n\nCurrently the workflow engine implements two action targets. The **bash:shell** target is a powerful target.\nIt allows you to execute any shell command as a workflow. This target provides a large set of template parameters\nwhich EOS can give as input arguments to the called shell command. This is described later. The **mail** target\nallows to send an email notification to a specified recipient and mostly used for demonstration.\n\n.. epigraph::\n\n   ============= =============================================================================================\n   Action Target Description\n   ============= =============================================================================================\n   bash:shell    run an arbitrary shell command line with template argument substitution\n   mail          send an email notification to a provided recipient when such an event is triggered\n   ============= =============================================================================================\n\nConfiguration\n-------------\n\nEngine\n++++++\nThe WFE engine has to be enabled/disabled in the default space only:\n\n.. code-block:: bash\n\n   # enable\n   eos space config default space.wfe=on  \n   # disable\n   eos space config default space.wfe=off\n\nThe current status of the WFE can be seen via:\n\n.. code-block:: bash\n\n   eos -b space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   ...\n   wfe                            := off\n   wfe.interval                   := 10\n   ...\n\nThe interval in which the WFE engine is running is defined by the **wfe.interval**\nspace variable. The default is 10 seconds if unspecified.\n\n.. code-block:: bash\n\n   # run the LRU scan once a week\n   eos space config default space.wfe.interval=10\n\nThe thread-pool size of concurrently running workflows is defined by the **wfe.ntx** space variable.\nThe default is to run all workflow jobs sequentially with a single thread.\n\n.. code-block:: bash\n\n   # configure a thread pool of 16 workflow jobs in parallel\n   eos space config default space.wfe.ntx=10\n\nWorkflows are stored in a virtual queue system. The queues display the status of each workflow. By default workflows older than 7 days are cleaned up.\nThis setting can be changed by the **wfe.keeptime** space variable. That is the time in seconds how long workflows are kept in the virtual queue system before\nthey get deleted.\n\n.. code-block:: bash\n\n   # keep workflows for 1 week\n   eos space config default space.wfe.keeptime=604800\n\nWorkflow Configuration\n++++++++++++++++++++++++++++++++\n\nThe **mail** workflow\n`````````````````````\nAs an example we want to send an email to a mailing list, whenever a file is deposited. This workflow can be specified like this:\n\n.. code-block:: bash\n\n   # define a workflow to send when a file is written\n   eos attr set sys.workflow.closew.default=\"mail:eos-project.cern.ch: a file has been written!\" /eos/dev/mail/\n\n   # place a new file\n   eos cp /etc/passwd /eos/dev/mail/passwd\n\n   # eos-project.cern.ch will receive an Email with a subject like: eosdev ( eosdev1.cern.ch ) event=closew fid=000004f7 )\n   # and the text in the body : a file has been written!\n\n\nThe **bash:shell** workflow\n``````````````````````````````````````````````````\n\nMost people want to run a command whenever a file is placed, read or deleted. To invoke a shell command one configures the **bash:shell** workflow.\nAs an example consider this simple echo command, which prints the path when a **closew** event is triggered: \n\n.. code-block:: bash\n\n   # define a workflow to echo the full path when a file is written\n   eos attr set \"sys.workflow.closew.default=sys.workflow.closew.default=\"bash:shell:mylog echo <eos::wfe::path>\" /eos/dev/echo/\n\nThe template parameters ``<eos::wfe::path>`` is replaced with the full logical path of the file, which was written. The third parameters ``mylog`` in **bash:shell:mylog** specifies the name of \nthe log file for this workflow which is found on the MGM under ``/var/log/eos/wfe/mylog.log`` \n\nOnce one uploads a file into the ``echo`` directory, the following log entry is created in ``/var/log/eos/wfe/mylog.log``\n\n.. code-block:: bash\n\n   ----------------------------------------------------------------------------------------------------------------------\n   1466173303 Fri Jun 17 16:21:43 CEST 2016 shell echo /eos/dev/echo/passwd\n   /eos/dev/echo/passwd\n   retc=0\n\nThe full list of static template arguments is given here:\n\n.. epigraph::\n\n   =========================== =============================================================================================\n   Template                    Description\n   =========================== =============================================================================================\n   <eos::wfe::uid>             user id of the file owner\n   <eos::wfe::gid>             group id of the file owner\n   <eos::wfe::username>        user name of the file owner\n   <eos::wfe::groupname>       group name of the file owner\n   <eos::wfe::ruid>            user id invoking the workflow\n   <eos::wfe::rgid>            group id invoking the workflow\n   <eos::wfe::rusername>       user name invoking the workflow\n   <eos::wfe::rgroupname>      group name invoking the workflow\n   <eos::wfe::path>            full absolute file path which has triggered the workflow\n   <eos::wfe::base64:path>     base64 encoded full absolute file path which has triggered the workflow\n   <eos::wfe::turl>            XRootD transfer URL providing access by file id e.g. root://myeos.cern.ch//mydir/myfile?eos.lfn=fxid:00001aaa\n   <eos::wfe::host>            client host name triggering the workflow\n   <eos::wfe::sec.app>         client application triggering the workflow (this is defined externally via the CGI ``?eos.app=myapp``)\n   <eos::wfe::sec.name>        client security credential name triggering the workflow\n   <eos::wfe::sec.prot>        client security protocol triggering the workflow\n   <eos::wfe::sec.grps>        client security groups triggering the workflow\n   <eos::wfe::instance>        EOS instance name\n   <eos::wfe::ctime.s>         file creation time seconds\n   <eos::wfe::ctime.ns>        file creation time nanoseconds\n   <eos::wfe::mtime.s>         file modification time seconds\n   <eos::wfe::mtime.ns>        file modification time nanoseconds\n   <eos::wfe::size>            file size\n   <eos::wfe::cid>             parent container id\n   <eos::wfe::fid>             file id (decimal)\n   <eos::wfe::fxid>            file id (hexacdecimal)\n   <eos::wfe::name>            basename of the file\n   <eos::wfe::base64:name>     base64 encoded basename of the file\n   <eos::wfe::link>            resolved symlink path if the original file path is a symbolic link to a file\n   <eos::wfe::base64:link>     base64 encoded resolved symlink path if the original file path is a symbolic link to a file\n   <eos::wfe::checksum>        checksum string\n   <eos::wfe::checksumtype>    checksum type string\n   <eos::wfe::event>           event name triggering this workflow (e.g. closew)\n   <eos::wfe::queue>           queue name triggering this workflow (e.g. can be 'q' or 'e')\n   <eos::wfe::workflow>        workflow name triggering this workflow (e.g. default)\n   <eos::wfe::now>             current unix timestamp when running this workflow\n   <eos::wfe::when>            scheduling unix timestamp when to run this workflow\n   <eos::wfe::base64:metadata> a full base64 encoded meta data blop with all file metadata and parent metadata including extended attributes\n   <eos::wfe::vpath>           the path of the workflow file in the virtual workflow directory when the workflow is executed\n                               - you can use this to attach messages/log as an extended attribute to a workflow if desired\n   =========================== =============================================================================================\n\n\nExtended attributes of a file and it's parent container can be read with dynamic template arguments:\n\n.. epigraph::\n\n   ================================ ========================================================================================\n   Template                         Description\n   ================================ ========================================================================================\n   <eos::wfe::fxattr:<key>>         Retrieves the value of the extended attribute of the triggering file with name <key>\n                                    - sets UNDEF if not existing\n   <eos::wfe::fxattr:base64:<key>>  Retrieves the base64 encoded value of the extended attribute of the triggering file with name <key>\n                                    - sets UNDEF if not existing\n   <eos::wfe::cxattr:<key>>         Retrieves the value of the extended attribute of parent directory of the triggering file\n                                    - sets UNDEF if not existing\n   ================================ ========================================================================================\n\n\n\nHere is an  example for a dynamic attribute:\n\n.. code-block:: bash\n\n   # define a workflow to echo the meta blob and the acls of the parent directory when a file is written\n   eos attr set \"sys.workflow.closew.default=sys.workflow.closew.default=\"bash:shell:mylog echo <eos::wfe::base64:metadata> <eos::wfe::cxattr:sys.acl>\" /eos/dev/echo/\n\n\nConfiguring retry policies for  **bash:shell** workflows\n````````````````````````````````````````````````````````\n\nIf a **bash:shell** workflow fails e.g. the command returns rc!=0 and no retry policy is defined, the workflow job ends up in the **failed** queue. For each\nworkflow the number of retries and the delay for retry can be defined via extended attributes. To reschedule a workflow after a failure the shell command has to return **EAGAIN** e.g. ``exit(11)``.\nThe number of retries for a failing workflow can be defined as:\n\n.. code-block:: bash\n\n   # define a workflow to return EAGAIN to be retried\n   eos attr set \"sys.workflow.closew.default=sys.workflow.closew.default=\"bash:shell:fail '(exit 11)'\" /eos/dev/echo/\n\n   # set the maximum number of retries\n   eos attr set \"sys.workflow.closew.default.retry.max=3\" /eos/dev/echo/\n\nThe previous workflow will be scheduled three times without delay. If you want to schedule a retry at a later point in time, you can define the delay for retry for a particular workflow like:\n\n.. code-block:: bash\n\n   # configure a workflow retry after 1 hour\n   eos attr set \"sys.workflow.closew.default.retry.delay=3600\" /eos/dev/echo/\n\n\nReturning result attributes \n````````````````````````````\n\nif a **bash::shell** workflow is used, the STDERR of the command is parsed for return attribute tags, which are either tagged on the triggering file (path) or the virtual workflow entry (vpath):\n\n.. epigraph::\n\n   ============================================== =====================================================================================\n   Syntax                                         Resulting Action\n   ============================================== =====================================================================================\n   <eos::wfe::path::fxattr:<key>>=base64:<value>  set a file attribute <key> on <eos::wfe::path> to the base64 decoded value of <value>\n   <eos::wfe::path::fxattr:<key>>=<value>         set a file attribute <key> on <eos::wfe::path> to <value> (value can not contain space)\n   <eos::wfe::vpath::fxattr:<key>>=base64:<value> set a file attribute <key> on <eos::wfe::vpath> to the base64 decoded value of <value>\n   <eos::wfe::vpath::fxattr:<key>>=:<value>       set a file attribute <key> on <eos::wfe::vpath> to <value> (value can not contain space)\n   ============================================== =====================================================================================\n\nVirtual /proc Workflow queue directories\n++++++++++++++++++++++++++++++++++++++++++++\n\nThe virtual directory structure for triggered workflows can be found under ``/eos/<instance>/proc/workflow``. \n\nHere is an example:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/dev/> eos find /eos/dev/proc/workflow/\n   /eos/dev/proc/workflow/20160617/d/\n   /eos/dev/proc/workflow/20160617/d/default/\n   /eos/dev/proc/workflow/20160617/d/default/1466171933:000004f7:closew\n   /eos/dev/proc/workflow/20160617/d/default/1466173303:000004fd:closew\n   /eos/dev/proc/workflow/20160617/f/\n   /eos/dev/proc/workflow/20160617/f/default/\n   /eos/dev/proc/workflow/20160617/f/default/1466171873:000004f4:closew\n   /eos/dev/proc/workflow/20160617/f/default/1466173183:000004fa:closew\n   /eos/dev/proc/workflow/20160617/q/\n   /eos/dev/proc/workflow/20160617/q/default/1466173283:000004fb:closew\n\nThe virtual tree is organized with entries like ``<proc>/workflow/<year-month-day>/<queue>/<workflow>/<unix-timestamp>:<fid>:<event>``.\nWorkflows are scheduled only from the **q** and **e** queues. All other entries describe a ``finale state`` and will be expired as configured by the cleanup policy described in the beginning.\n\nThe existing queues are described here:\n\n.. epigraph::\n\n   =========================== ========================================================================================\n   Queue                       Description\n   =========================== ========================================================================================\n   ../q/..                     all triggered asynchronous workflows appear first in this queue\n   ../s/..                     scheduled asynchronous workflows and triggered synchronous workflows appear in this queue\n   ../r/..                     running workflows appear in this queue\n   ../e/..                     failed workflows with retry policy appear here\n   ../f/..                     failed workflows without retry appear here\n   ../g/..                     workflows with 'gone' files or some global misconfiguration appear here\n   ../d/..                     successful workflows with 0 return code\n   =========================== ========================================================================================\n\n\nSynchronous workflows\n``````````````````````\n\nThe **deletion** and **prepare** workflow are synchronous workflows which are executed in-line. They are stored and tracked as asynchronous workflows in the proc filesystem. The emitted event on deletion is **sync::delete**, the emitted event on prepare is **sync::prepare**. \n\nWorkflow log and return codes\n-----------------------------\n\nThe return codes and log information is tagged on the virtual directory entries in the proc filesystem as extended attributes:\n\n.. code-block:: bash\n\n   sys.wfe.retc=<return code value>\n   sys.wfe.log=<message describing the result of running the workflow>\n\n\n\n\n\n"
  },
  {
    "path": "doc/citrine/configuration.rst",
    "content": ".. _configuration:\n\n.. index::\n   single: Configuring EOS\n\n\nConfiguration\n=============\n\n.. image:: configuration.jpg\n   :width: 530px\n   :align: left\n\nThis chapter discusses several components of EOS and how they are configured.\n\n.. toctree::\n   :maxdepth: 1\n\n   configuration/archive\n   configuration/fsck\n   configuration/balancing\n   configuration/converter\n   configuration/draining\n   configuration/fuse\n   configuration/fusex\n   configuration/geobalancer\n   configuration/geoscheduling\n   configuration/geotags\n   configuration/groupbalancer\n   configuration/http\n   configuration/http_tpc\n   configuration/inspector\n   configuration/kinetic\n   configuration/lru\n   configuration/master\n   configuration/master_quarkdb\n   configuration/namespace\n   configuration/permission\n   configuration/proxies\n   configuration/quarkdb\n   configuration/quota\n   configuration/recyclebin\n   configuration/route\n   configuration/s3\n   configuration/scheduler\n   configuration/tracker\n   configuration/transfer\n   configuration/tty\n   configuration/wfe\n   configuration/egi\n"
  },
  {
    "path": "doc/citrine/contents.rst",
    "content": ".. _contents:\n\nDocumentation\n=============================\n\n.. toctree::\n   :maxdepth: 1\n\n   intro\n   configuration\n   clicommands\n   install\n   quickstart\n   develop\n   clicommands\n   using\n   releases\n   restapi\n"
  },
  {
    "path": "doc/citrine/develop.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Developing EOS\n\nDeveloping EOS\n===============\n\n.. image:: cpp.jpg\n   :scale: 40%\n   :align: left\n\n\nGetting a development machine [CERN specific]\n----------------------------------------------\n\nGo to `CERN Openstack <https://openstack.cern.ch/>`_ and find project 'IT EOS development' (if you are not a member ask your colleagues to give you access). Check in the Menu:Project/Compute/Overview if there are still free resources to be used. If not, coordinate with your colleagues on reviewing if all currently existing machines are needed. If yes, inform your section leader that you would like to ask for more resources, then follow `Request Quota <https://clouddocs.web.cern.ch/projects/project_quota_request.html>`_.\n\nTo get a decent machine, look at Menu\"Project/Compute/Instances and then click \"Launch Instance\" button. Follow the form and when choosing  \"Flavour\" look for 'm2.2xlarge'. If not visible, file a ticket to the Openstack team asking for making it available to you (not publicly available). Machine creation is possible also from a command line using `CERN Openstack Tools <https://clouddocs.web.cern.ch/index.html>`_.\n\nOnce the machine is running, you can log-in from aiadm.cern.ch to your machine (as root for convenience) and upgrade to newest packages/kernel etc. (:code:`yum clean all; yum upgrade; reboot;`).\n\n\nSource Code\n-------------\n\nAsk EOS developers at CERN to give you write permissions to the `EOS repository <https://gitlab.cern.ch/dss/eos.git>`_.\nThe current development model is that anyone with permission can directly commit to master branch. Newcomers are encouraged to create a separate branch and make a merge request with review from one of the main developers.\n\nInstall git, create your working directory (e.g. :code:`/devwork/local`) and clone the EOS repository:\n\n.. code-block:: bash\n\n   yum install git\n   git config --global user.email \"your@email.x\"\n   git config --global user.name \"FirstName LastName\"\n\n   mkdir -p /devwork/local\n   cd /devwork/local\n\n   git clone https://gitlab.cern.ch/dss/eos.git\n\n   cd eos\n   git submodule update --recursive --init\n\n\nCreate a build directory ...\n\n.. code-block:: bash\n\n   mkdir build\n   cd build\n\nCreate your dev environment using utils scripts (Recommended)\n--------------------------------------------------------------\n\nTwo utility scripts have been created to ease the setup of the development environment necessary to build and install EOS on a bare Centos VM.\nThese scripts are located in the :code:`utils` directory. Feel free to modify them as they could be outdated. These scripts automate what is documented in\nthe `Dependencies`_ part of this documentation.\n\nEL7\n----------------\n\nJust run the :code:`./utils/centos7-dev-environment.sh` script.\n\nGo to your :code:`eos` directory and type:\n\n.. code-block:: bash\n\n    sudo ./utils/centos7-dev-environment.sh\n\nIf everything went well, your VM should reboot.\n\nEL8\n----------------\n\nJust **source** the :code:`./utils/centos7-dev-environment.sh` script.\n\nGo to your :code:`eos` directory and type:\n\n.. code-block:: bash\n\n    source ./utils/centos8-dev-environment.sh\n\nIf you haven't used **source**, source your :code:`.bashrc` file. Indeed the :code:`centos8-dev-environment.sh` script script modifies your .bashrc to update your :code:`PATH` variable.\nIf you don't source it, you will not be able to use the good cmake version that has been installed to create the configuration for building EOS.\n\nYou are ready to compile\n------------------------\n\nYou can skip the next parts and go directly to the `Compilation`_ part of this documentation. Though you can also read them if you need to troubleshoot future problems.\n\nDependencies\n-------------\n\n.. warning:: Before compilation of the master branch you have to make sure that you installed all required dependencies.\n\nEL7\n----\n\nThere is a convenience scripts to install all dependencies in the EOS source tree:\n\n.. code-block:: bash\n\n   utils/el7-packages.sh\n\nThis script might not be up to date. To be sure you are having all the dependencies installed consistently with the version of EOS code you just downloaded, one first needs to define the EOS yum repositories as stated in the `Quick Start Guide/Setup YUM Repository <http://eos-docs.web.cern.ch/eos-docs/quickstart/setup_repo.html#eos-base-setup-repos>`_. One may also look for inspiration at the Dockerfiles in the :code:`eos/gitlab-ci/prebuild_OSbase` of your repository, which follows a similar process we will layout below.\n\nIn general\n----------\n\nAsk the developers which cmake version is currently supported (cmake3 as of Dec 2020), then, install the following packages (e.g. ccache for speeding up compiling):\n\n.. code-block:: bash\n\n   yum install --nogpg -y ccache centos-release-scl-rh cmake3 gcc-c++ gdb make rpm-build rpm-sign yum-plugin-priorities && yum clean all\n\n\nRun cmake3 with the DPACKAGEONLY=1 option and make source rpms:\n\n.. code-block:: bash\n\n   cmake3 ../ -DPACKAGEONLY=1 && make srpm\n\n\nNow build the EOS dependencies:\n\n.. code-block:: bash\n\n   yum-builddep --nogpgcheck --setopt=\"cern*.exclude=xrootd*\" -y SRPMS/*\n\nThis will install among other also the devtoolset-8 required for eos development (as of Dec 2020). Add line ‘source /opt/rh/devtoolset-8/enable’ to your bash profile to load each time you log in. This should be confirmed by getting the right path for the compiler, e.g. :code:`which c++` will show :code:`/opt/rh/devtoolset-8/root/usr/bin/c++` as the output.\n\nYou will also need to install *QuarkDB*. Define the yum repository with :code:`/etc/yum.repos.d/quarkdb.repo` file with the following content:\n\n.. code-block:: bash\n\n    [quarkdb-stable]\n    name=QuarkDB repository [stable]\n    baseurl=http://storage-ci.web.cern.ch/storage-ci/quarkdb/tag/el7/x86_64/\n    enabled=1\n    gpgcheck=False\n\n\nThen, run:\n\n.. code-block:: bash\n\n    yum install quarkdb quarkdb-debuginfo redis\n\n\nImportant troubleshooting steps\n--------------------------------\n\nIf you do not succeed enabling devtoolset-8 it might also be that you are using zsh which is incompatible with `scl-utils <https://stackoverflow.com/questions/62958800/enable-devtoolset-8-for-zsh-on-centos-7>`_.\n\nMake sure you have compatible xrootd version installed (:code:`rpm -qa | grep xroot`), currently the above will install you version 5.0.3 which is not yet compatible with EOS <= 4.8.35 (Dec/Jan 2020). Look at the latest version of xrootd in the [eos-citrine-dep] repository (currently 4.12.6) or ask the developers if in doubt.\n\n.. code-block:: bash\n\n    rpm -qa | grep xroot\n    > xrootd-client-libs-5.0.3-2.el7.x86_64\n    > xrootd-server-devel-5.0.3-2.el7.x86_64\n    > xrootd-selinux-5.0.3-2.el7.noarch\n    > xrootd-libs-5.0.3-2.el7.x86_64\n    > xrootd-devel-5.0.3-2.el7.x86_64\n    > xrootd-server-libs-5.0.3-2.el7.x86_64\n    > xrootd-server-5.0.3-2.el7.x86_64\n    > xrootd-private-devel-5.0.3-2.el7.x86_64\n    > xrootd-client-devel-5.0.3-2.el7.x86_64\n    > xrootd-5.0.3-2.el7.x86_64\n\n    # as mentioned above, the version needs to be the latest available in [eos-citrine-dep] repository (defined in steps above); to fix this, do the following:\n    yum remove xrootd-*\n    # it could also be that xrootd 4.12.6 was renamed to xrootd4 in case you have a mix of xrootd and xrootd4 `yum remove xrootd4-*` and make sure you have the right packages:\n    yum install xrootd xrootd-client xrootd-server-devel xrootd-private-devel --disablerepo=\"*\" --enablerepo=eos-citrine-dep\n    # yum install xrootd-4.12.6-1.el7 xrootd-client-4.12.6-1.el7 xrootd-server-devel-4.12.6-1.el7 xrootd-private-devel-4.12.6-1.el7 --disablerepo=\"*\" --enablerepo=eos-citrine-dep\n\n\nIt may also currently install *eos-folly-2020.10.05.00-1.el7.cern.x86_64* which (for EOS <=4.8.35) needs to be *2019.11.11.00-1.el7.cern*, fix this by:\n\n.. code-block:: bash\n\n    yum remove eos-folly eos-folly-deps\n    yum install eos-folly-2019.11.11.00-1.el7.cern\n\n\nOptional\n--------\n\n.. code-block:: bash\n\n    yum install -y moreutils \\\n    yum clean all\n\n\ninstall moreutils just for 'ts', nice to benchmark the build time.\n\n\nCompilation\n------------\n\n\nEOS is a system of libraries which gets loaded by the xrootd executable. In order to run the version you just cloned (or later modified), you have to compile those libraries and then make sure xrootd loads the correct ones. In order to facilitate the deployment, we install ninja-build package (like :code:`make` but faster):\n\n.. code-block:: bash\n\n    yum install ninja-build\n\n\nCreate a new build directory and try to run cmake (see troubleshooting below):\n\n.. code-block:: bash\n\n    mkdir /devwork/local/eos/build-with-ninja\n    cd /devwork/local/eos/build-with-ninja\n    cmake3 ../ -G Ninja -DCMAKE_INSTALL_PREFIX=/usr/ -Wno-dev -DCMAKE_C_COMPILER=/opt/rh/devtoolset-8/root/usr/bin/cc -DCMAKE_CXX_COMPILER=/opt/rh/devtoolset-8/root/usr/bin/c++\n\n\nCompile\n--------\n\n\n.. code-block:: bash\n\n    ninja-build\n    # run some unit tests; should finish with [  PASSED  ] XXX tests message\n    unit_tests/eos-unit-tests\n\n\nTroubleshooting\n----------------\n\nThe following dependencies might not be required (you should be able to ignore these in the cmake3 output):\n\n.. code-block:: bash\n\n    Could NOT find Sphinx\n    Could NOT find fuse3\n    Could NOT find davix\n    Could NOT find GTest\n\n\n.. warning:: yum can automatically update your packages (in yum history you can see:  \"-y --skip-broken update\" in such a case) you can remove this package :code:`yum remove yum-autoupdate` to make sure it does not screw up EOS rpms installed.\n\nIf when executing the unit tests you have errors about the linker that could not find .so files, you can update your :code:`LD_LIBRARY_PATH` to add to it the :code:`common` and the :code:`mq` directory of your EOS build directory.\n\nDeployment\n----------\n\n\nUse Ninja to install EOS on your development machine:\n\n.. code-block:: bash\n\n    cd /devwork/local/eos/build-with-ninja\n    ninja-build install\n\n    # depending on your OS, you can remove the el6 repository\n    # to avoid pulling packages and dependencies from there in the future\n    rm -rf /etc/yum.repos.d/eos-el6*\n    rm -rf /etc/yum.repos.d/eos-el7*\n\n\nAfter you finish your deployment configuration (see below) and you start modifying the source code, to deploy it, you can do:\n\n.. code-block:: bash\n\n    sudo systemctl stop eos@*\n    cd /devwork/local/eos/build-with-ninja\n    # if needed rm files from the build directory and run cmake3 again before the next step\n    ninja-build\n    ninja-build install\n    rm -rf /etc/yum.repos.d/eos-el6*\n    cp /etc/xrd.cf.mgm_bkp /etc/xrd.cf.mgm\n    cp /etc/xrd.cf.mq_bkp /etc/xrd.cf.mq\n    systemctl daemon-reload\n    systemctl start eos\n\n\nDeployment Configuration\n-------------------------\n\nWe need to configure and run the following set of daemons: MGM, MQ (messaging service between MGM and FSTs), several FSTs and QuarkDB.\n\nQuarkDB\n-------\n\nCreate a configuration file :code:`/etc/xrootd/xrootd-quarkdb.cfg` with the following content:\n\n.. code-block:: bash\n\n    xrd.port  7777\n    xrd.protocol redis:7777 libXrdQuarkDB.so\n    redis.mode  standalone\n    redis.database  /var/lib/quarkdb/eosns\n    # redis.myself  localhost:7777\n    redis.password_file  /etc/eos.keytab\n\n\n In production deployment usually raft mode is used instead of standalone (you need at least 2 nodes for such a mode).\n\n\nCreate path to your QuarkDB namespace specified above:\n\n.. code-block:: bash\n\n     install -d -o daemon -g daemon /var/lib/quarkdb\n\nBecause the eos service will run as user 'daemon', you will have to make sure that all relevant files have the correct permissions and change their ownership, i.e. run:\n\n.. code-block:: bash\n\n    chown -R daemon:daemon /var/log/xrootd\n    chown -R daemon:daemon /var/eos/\n    chown -R daemon:daemon /etc/eos.* # + must have permissions 400 !\n    chown -R daemon:daemon /var/run/xrootd\n    chown -R daemon:daemon /var/lib/quarkdb\n    chown -R daemon:daemon /var/spool/xrootd\n\n\nBecause the eos service will run as user \"daemon\", you should run the QuarkDB as the same user, i.e.:\n\nCreate your QuarkDB path (modify eostest and <myhostname>) :\n\n.. code-block:: bash\n\n    UUID=eostest-$(uuidgen); echo $UUID; sudo runuser daemon -s /bin/bash -c \"quarkdb-create --path /var/lib/quarkdb/eosns --clusterID $UUID --nodes localhost:7777\"\n\n\nBefore starting the service, we will need custom config drop-in script. Create the following file path :code:`/etc/systemd/system/xrootd@quarkdb.service.d/custom.conf` with the following content:\n\n.. code-block:: bash\n\n    [Service]\n    User=daemon\n    Group=daemon\n\n\nBefore starting the service, check once again that the keytabs :code:`/etc/eos.*` have permission 400. Then start the service (log can be followed in :code:`/var/log/xrootd/quarkdb/xrootd.log`) and check its status:\n\n.. code-block:: bash\n\n    systemctl start xrootd@quarkdb\n    systemctl status xrootd@quarkdb\n\n\nNote: `QuarkDB Installation documentation <https://quarkdb.web.cern.ch/quarkdb/docs/master/installation/>`_ is a very helpful resource, in particular for troubleshooting!\n\nTest if the QuarkDB runs fine by saving and retrieving key-value pair:\n\n.. code-block:: bash\n\n    redis-cli -p 7777\n    127.0.0.1:7777> set mykey myval\n    OK\n    127.0.0.1:7777> get mykey\n    \"myval\"\n\n\nMGM\n----\n\n\nThe environment configuration will be loaded from :code:`/etc/sysconfig/eos_env` which you need to create from a provided example.\n\n.. code-block:: bash\n\n    mv /etc/sysconfig/eos_env.example /etc/sysconfig/eos_env\n\nInside you need to fill in various pieces of information:\n\n* :code:`XRD_ROLES=\"mq mgm fst1 fst2 fst3\"`, depending on how many fst daemons you plan (here 3) to run, you need to specify them here. Drop :code:`fed` and :code:`sync` as they are not used anymore.\n* HOST_TARGET was used for the :code:`sync` and is not needed anymore.\n* EL8 systems will need :code:`LD_PRELOAD=/usr/lib64/libjemalloc.so.2`\n\n.. code-block:: bash\n\n    # XRD_ROLES depends on what you wish to run, each separate mgm, mq or fst needs to be specified, e.g. for 3 fsts daemons we put fst1 fst2 fst3.\n    # Delete \"fed\" and \"sync\" if present as they are not used anymore.\n    XRD_ROLES=\"mq mgm fst1 fst2 fst3\"\n    EOS_MGM_HOST=<myhostname>.cern.ch`\n    EOS_MGM_HOST_TARGET` # was used for the sync and can be commented out\n    EOS_INSTANCE_NAME=eostest` # has to start with \"eos\" and has the form \"eos<name>\".\n    EOS_MGM_MASTER1=<myhostname>.cern.ch`\n    EOS_MGM_MASTER2=<myhostname>.cern.ch`\n    EOS_MGM_ALIAS=<myhostname>.cern.ch`\n    EOS_MAIL_CC=<email>`\n    EOS_GEOTAG=\"\\:\\:<anything>\"` # needs to be filled\n    EOS_NS_ACCOUNTING=1`\n    EOS_SYNCTIME_ACCOUNTING=1`\n    EOS_USE_SHARED_MUTEX=1`\n\n\nThen you need Kerberos security keys on your machine:\n\n.. code-block:: bash\n\n    cp /etc/krb5.keytab /etc/eos.krb5.keytab\n\n.. warning:: Kerberos and SSS keytab files are different and use different formats.  Read the documentation for the :code:`xrdsssadmin` command or XRootD SSS protocol at https://xrootd.slac.stanford.edu/doc/dev49/sec_config.htm#_Toc517294117\n\nThere is yet another configuration file you will have to modify e.g. :code:`/etc/xrd.cf.mgm` (note aside: These are configuring the xroot daemons that will be running as a service, this has nothing to do with the config of eos itself which is being saved in QuarkDB.). In this file you can change the security settings as needed, e.g.:\n\n.. code-block:: bash\n\n    sec.protocol unix\n    sec.protocol sss -c /etc/eos.client.keytab -s /etc/eos.keytab\n    # Example disable krb5 and gsi\n    #sec.protocol krb5\n    #sec.protocol gsi\n\n    sec.protbind localhost.localdomain unix sss\n    sec.protbind localhost unix sss\n    sec.protbind * only sss unix\n\n\nNote: that the order of sec.protbind matters for host matching (matches from \"bottom up\", i.e. in reverse order of specification, from most specific to least specific).\n\nAlso, activate QuarkDB namespace plugin usage and set other parameters as desired, e.g.:\n\n.. code-block:: bash\n\n    mgmofs.nslib /usr/lib64/libEosNsQuarkdb.so\n    mgmofs.instance eostest\n    mgmofs.qdbcluster localhost:7777\n    mgmofs.qdbpassword_file /etc/eos.keytab\n\nOnce done, backup the result to have it available after the next reinstallation of recompiled EOS:\n\n.. code-block:: bash\n\n    cp /etc/xrd.cf.mgm /etc/xrd.cf.mgm_bkp\n\nStart the service, check the status and log file in :code:`/var/log/eos/mgm/xrdlog.mgm` :\n\n.. code-block:: bash\n\n    systemctl daemon-reload\n    systemctl start eos@mgm\n    systemctl status eos@mgm\n\n\nYou will see errors *RefreshBrokersEndpoints* this is due to the fact that MQ is not yet running:\n\n.. code-block:: bash\n\n    less /var/log/eos/mgm/xrdlog.mgm\n    ...\n    210304 11:34:22 time=1614854062.201983 func=RefreshBrokersEndpoints  level=ERROR logid=static.............................. unit=mgm@eos-ccaffy-dev01.cern.ch:1094 tid=00007f324109f700 source=XrdMqClient:495                tident= sec=(null) uid=99 gid=99 name=- geo=\"\" msg=\"failed to contact broker\" url=\"root://localhost:1097//eos/eos-ccaffy-dev01.cern.ch/mgm_mq_test?xmqclient.advisory.flushbacklog=1&xmqclient.advisory.query=1&xmqclient.advisory.status=1\"\n    ...\n\nTroubleshooting:\n----------------\n\nIf by looking at the :code:`/var/log/eos/mgm/xrdlog.mgm` log file, you see the following error:\n\n.. code-block:: bash\n\n    Seckrb5: Unable to start sequence on the keytab file FILE:/etc/krb5.keytab; Permission denied\n\nThen do\n\n.. code-block:: bash\n\n    chmod a+r /etc/krb5.keytab\n\n\nMQ\n---\n\nLook at :code:`/etc/sysconfig/eos_env` and :code:`/etc/xrd.cf.mq` in case you would want any changes there (this can stay as provided by default).\n\nStart the service, check the status and log file in :code:`/var/log/eos/mq/xrdlog.mq` :\n\n.. code-block:: bash\n\n    systemctl start eos@mq\n    systemctl status eos@mq\n\nYou will see expected errors in connection queue, this is because MQ can not connect to any running file system daemons (FST).\nOn the other hand the *RefreshBrokersEndpoints* of the MGM  :code:`/var/log/eos/mgm/xrdlog.mgm` should now disappear.\n\n\nFST\n----\n\nLook at :code:`/etc/sysconfig/eos_env` in case you would want any changes there (this can stay as provided by default). For each of XRD_ROLES there has to be a configuration file created. For each the file systems you wish to add, you need to create a new configuration file from a provided template :code:`/etc/xrd.cf.fst`. The template can be used directly, but you might want to change the port number in each:\n\n.. code-block:: bash\n\n    for i in {1..3}; do\n        cp /etc/xrd.cf.fst /etc/xrd.cf.fst\"${i}\"\n        sed -i \"s/xrd.port 1095/xrd.port 200${i}/g\" /etc/xrd.cf.fst\"${i}\"\n    done;\n\nAlso make sure the following 2 lines are present in each cfg file created above:\n\n.. code-block:: bash\n\n    fstofs.qdbcluster localhost:7777\n    fstofs.qdbpassword_file /etc/eos.keytab\n\n\nEach file system will run its own FST daemon (which communicates via MQ with the MGM). For each of these daemons, we need to create a special drop-in script specifying the connection port:\n\n.. code-block:: bash\n\n    for i in {1..3}; do\n        mkdir -p /usr/lib/systemd/system/eos@fst\"${i}\".service.d\n        cd /usr/lib/systemd/system/eos@fst\"${i}\".service.d\n        echo \"[Service]\" > custom.conf;\n        echo \"Environment=EOS_FST_HTTP_PORT=900${i}\" >> custom.conf;\n    done;\n\n\nFST is usually represented by a disk server with many disks mounted on it.\nFor our dev purposes, it is usually enough to just create a directory per fst on the local file system (do not forget to grant \"daemon\" the ownership). In each of these directories there has to be 2 hidden files containing the ID:code:`.eosfsid` and UUID :code:`.eosfsuuid` of the FST you are adding (define however convenient for you), e.g.:\n\n.. code-block:: bash\n\n    mkdir -p /fst\n    cd /fst\n    for i in {1..3}; do\n        mkdir data$i\n        echo $i >  data$i/.eosfsid\n        echo fst$i > data$i/.eosfsuuid;\n    done\n    chown daemon:daemon -R /fst\n\n\nNow, start the FST services:\n\n.. code-block:: bash\n\n    systemctl daemon-reload\n    for i in {1..3}; do\n      sudo systemctl start eos@fst\"${i}\"\n    done;\n\n    for i in {1..3}; do\n      sudo systemctl status eos@fst\"${i}\"\n    done;\n\n\nThe :code:`/var/log/eos/fstX/xrdlog.fstX` (replace X with the wanted FST number) you will see lines such as:\n\n.. code-block:: bash\n\n    210116 12:53:13 time=1610797993.062063 func=Storage                  level=INFO  logid=FstOfsStorage unit=fst@<hostname>:2001\n\n\nAlso, you should see all as active and the MQ :code:`/var/log/eos/mq/xrdlog.mq` connecting to the \"nodes\" (the 3 various ports) and new links being added to quarkDB :code:`/var/log/xrootd/quarkdb/xrootd.log`.\n\nIf you now run :code:` eos node ls`, you should see the list of (3) FST nodes as they connected to the MGM.\n\n\nTroubleshooting:\n-----------------\n\nIf you see in the log file :code:`/var/log/eos/fstX/xrdlog.fst1`\n\n.. code-block:: bash\n\n    [QCLIENT - INFO - connectTCP:302] Encountered an error when connecting to <yourmachine>:7777 (IPv4,stream resolved from localhost): Unable to connect (111):Connection refused\n\n\nopen a firewall for port 7777:\n\n\n.. code-block:: bash\n\n     firewall-cmd --zone=public --add-port=7777/tcp --permanent\n     firewall-cmd --reload\n\nNote: If you are running multiple MGM nodes on separate hosts, you may also need to open the respective ports of all EOS daemons. By default they are:\n\n* quarkDB: 7777\n* mgm: 1094\n* fst: 1095\n* mq: 1097\n\n\nEOS namespace configuration\n----------------------------\n\n\nWe have QuarkDB, MQ, MGM, and FST daemons running. Now we need to define the EOS space\nto which we will be adding these file systems. Similarly to production we can define \"spare\"\nspace, just to keep disks waiting unused in spare and \"default\" space which will be in this example from 3 scheduling groups by 1 disk each:\n\n.. code-block:: bash\n\n    eos space define spare 0 0\n    eos space set spare on\n    eos space define default 1 3\n    eos space set default on\n\n    # check the status\n    eos space status default\n    eos space status spare\n\n\nNow we need to register the FST disks with EOS (we first put them in \"spare\"):\n\n.. code-block:: bash\n\n    for i in {1..3}; do eos fs add fst${i} ${HOSTNAME}:200${i} /fst/data${i} spare ;done;\n    # to see them added:\n    eos fs ls\n\nThen drain the disks:\n\n.. code-block:: bash\n\n    eos fs ls spare | awk '/fst/ {print \"eos -b fs config \" $3 \" configstatus=drain\"}' | sh -x\n\n\nMake sure, they all appear as empty in the output of :code:`eos fs ls` after this operation.\n\nThen boot them:\n\n.. code-block:: bash\n\n    for i in {1..3}; do eos fs boot $i;done;\n\nIf they do not boot you can check what is wrong by :code:`eos fs ls -e`.\n\nAnd finally move them to the default space. They will be distributed to the scheduling groups automatically. If you were to add more fsts for development purposes we do not mind having 2 disks form the same node in the same scheduling group (use the --force option below) which is by default forbidden.\n\n.. code-block:: bash\n\n    eos fs ls spare | awk '/fst/ {print \"eos -b fs mv --force \" $3 \" default\"}' | sh -x\n\n\nAfter this you will see the file systems booted, empty and online in the output of :code:`eos fs ls -e`.\n\nSet the disks to 'rw' mode:\n\n.. code-block:: bash\n\n    eos fs ls default | awk '/fst/ {print \"eos -b fs config \" $3 \" configstatus=rw\"}' | sh -x\n    eos fs ls\n\nNow, you should be able to work with your EOS file system:\n\n.. code-block:: bash\n\n    eos ls /eos\n    eos mkdir /eos/<name>/devtests\n    eos attr ls /eos/<name>/devtests\n    xrdcp root://localhost//eos/<name>/proc/whoami -\n    cd /tmp;\n    cat > hello_eos\n    Hello EOS !\n    eos cp hello_eos /eos/<name>/devtests/hello_eos\n    eos ls -l /eos/<name>/devtests\n    rm hello_eos\n    xrdcp \"root://localhost//eos/<name>/devtests/hello_eos\" hello_eos\n    cat hello_eos\n    eos ns\n    # [...]\n    # ALL      files created since boot         1\n    # ALL      container created since boot     1\n    # [...]\n\n\nIf you enabled other authentication mechanisms than sss and unix, you need to enable them, e.g.:\n\n.. code-block:: bash\n\n    eos vid enable gsi\n    eos vid enable krb5\n"
  },
  {
    "path": "doc/citrine/generate_docs.py",
    "content": "#!/usr/bin/env python3\n# ------------------------------------------------------------------------------\n# File: generate_docs.py\n# Author: Joaquim Rocha <jrocha@cern.ch>\n#         Elvin-Alin Sindrilaru <esindril@cern.ch>\n# ------------------------------------------------------------------------------\n#\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2015 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\n\"\"\" This script generates the Sphinx documentation located in /eos/doc/. First\n    it generates the documentation of the CLI and then it runs Sphinx on the\n    rest of the documentation. It should be run like this:\n\n    #> python generate_docs.py\n\n    In principle you don't need to run this explicitly. You generate the docu-\n    mentation after running cmake using:\n\n    #> make doc\n\"\"\"\n\nfrom __future__ import unicode_literals\nfrom __future__ import print_function\nfrom errno import EIO, EINVAL\nimport sys\nimport os\nimport subprocess\nimport shutil\n\nSPHINX_BUILD, __ = subprocess.Popen([\"which sphinx-build\"],\n                                    stdout=subprocess.PIPE,\n                                    stderr=subprocess.PIPE,\n                                    shell=True).communicate()\nSPHINX_BUILD = SPHINX_BUILD.decode().strip()\n\ntry:\n    print(\"SPHINX_BUILD is: {0}\".format(SPHINX_BUILD))\n    os.stat(SPHINX_BUILD)\nexcept OSError as e:\n    print(\"ERROR: Could not find SPHINX executable\", file=sys.stderr)\n    sys.exit(EINVAL)\n\nEOS_EXE = \"/usr/bin/eos\"\nCMD_NAME_CONVERT = {\".q\": \"pointq\", \"?\": \"question\"}\n\ndef get_dict_cmd_info():\n    \"\"\" Return a dictionary of eos commands and their description \"\"\"\n    if not os.path.exists(EOS_EXE):\n        raise OSError(\"could not find \\\"eos\\\" executable\")\n\n    cmds_out, __ = subprocess.Popen([\"{0} help\".format(EOS_EXE)],\n                                    stdout=subprocess.PIPE,\n                                    stderr=subprocess.PIPE,\n                                    shell=True).communicate()\n    cmd_dict = {}\n\n    for line in cmds_out.decode().splitlines()[1:]:\n        [cmd, desc] = line.split(' ',1)\n        cmd_dict[cmd] = desc\n\n    return cmd_dict\n\ndef generate_summary_rst(file_path, cmd_lst):\n    \"\"\" Generate the clicommands.rst containing the list of all the commands\n    for which a description is provided.\n\n    Args:\n        file_path (string): Absolute path to file\n        cmd_lst  (lst): List of all the commands\n    \"\"\"\n    header ='\\n'.join([\".. _clientcommands:\",\n                       \"\",\n                       \"Client Commands\",\n                       \"================\",\n                       \"\",\n                       \".. toctree::\",\n                       \"  :maxdepth: 2\",\n                       \"\", \"\"])\n\n    with open(file_path, 'w') as f:\n        f.write(header)\n\n        for cmd in cmd_lst:\n            if cmd in CMD_NAME_CONVERT:\n                fn_cmd = CMD_NAME_CONVERT[cmd]\n            else:\n                fn_cmd = cmd\n\n            f.write(\"  clicommands/{0}\\n\".format(fn_cmd))\n\ndef format_cmd_help(cmd):\n    \"\"\" Format the help output of the command to be written int the .rst file.\n\n    Args:\n        cmd (string): EOS command\n\n    Return:\n        Contents of the rst file for this command\n    \"\"\"\n    out, __ = subprocess.Popen([EOS_EXE, cmd, \"-h\"],\n                               stdout=subprocess.PIPE,\n                               stderr=subprocess.PIPE).communicate()\n    out = out.decode()\n\n    if '--help-all' in out:\n        sys.stdout.flush()\n        out, __ = subprocess.Popen([EOS_EXE, cmd, \"-H\"],\n                                   stdout=subprocess.PIPE,\n                                   stderr=subprocess.PIPE).communicate()\n    sys.stdout.flush()\n\n    # Some commands print to stdout, others to stderr\n    # Process stderr when stdout is empty\n    if not out:\n        out = __.decode()\n\n    # The first line usually contains the command, so we remove it\n    if out.startswith(cmd):\n        out = out[len(cmd) + 4:]\n\n    blocks = out.split(\"\\n\\n\")\n    out_rst = ''.join([cmd, '\\n', ('-' * len(cmd)), '\\n\\n'])\n    # Decide if the current block of text is a subcommand or not\n    inside_subcmd = True\n\n    for block in blocks:\n        if inside_subcmd:\n            inside_subcmd = False\n            out_rst += \".. code-block:: text\\n\\n\"\n\n        for line in block.splitlines():\n            usage_tag = \"Usage: \"\n            space_tag = \"  \"\n\n            if line.startswith(usage_tag):\n                inside_subcmd = True\n                line = line[len(usage_tag):]\n\n            if line.startswith(space_tag):\n                line = ''.join([space_tag, line.strip()])\n\n            out_rst += ''.join([space_tag, line, '\\n'])\n\n    return out_rst\n\ndef format_cmd_desc(cmd, desc):\n    \"\"\" Format the description output of the command to be written to the .rst\n        file.\n\n    Args:\n        cmd (string): EOS command\n        desc (string): Command description\n\n    Return:\n        Contents of the .rst file for this command.\n    \"\"\"\n    space_tag = \"  \"\n    # Uncapitalize first letter of description\n    desc = desc[0].lower() + desc[1:]\n    title_rst = ''.join([cmd, '\\n', ('-' * len(cmd)), '\\n\\n'])\n    out_rst = ''.join([title_rst, \".. code-block:: text\\n\\n\", space_tag, cmd,\n                       \" : \", desc])\n    return out_rst\n\ndef generate_cmds_rst(root_dir, cmd_dict):\n    \"\"\" Generate .rst description files for the individual commands\n\n    Args:\n        root_dir (string): Absolute path where file are saved\n        cmd_dict (dict): Dictionary with commands and their description\n    \"\"\"\n    # List of commands that don't have \"-h\" flag, so we just put their\n    # description in the .rst file.\n    desc_only_lst = ['console', '?', '.q', 'license', 'version', 'motd', 'pwd',\n                     'silent', 'touch', 'whoami', 'json', 'exit', 'timing', 'help',\n                     'quit', 'member', 'rtlog']\n\n    for (cmd, desc) in cmd_dict.items():\n        print(\"Processing command {0}\".format(cmd), file=sys.stdout)\n\n        if cmd in desc_only_lst:\n            out_rst = format_cmd_desc(cmd, desc)\n        else:\n            out_rst = format_cmd_help(cmd)\n\n        # For strange commands rename the .rst filenames\n        if cmd in CMD_NAME_CONVERT:\n            fn_cmd = CMD_NAME_CONVERT[cmd]\n        else:\n            fn_cmd = cmd\n\n        fpath_rst = ''.join([root_dir, \"/\", fn_cmd, \".rst\"])\n        with open(fpath_rst, 'w') as f:\n            f.write(out_rst)\n\ndef main():\n    \"\"\" Main function \"\"\"\n    try:\n        cmd_dict = get_dict_cmd_info()\n    except OSError as e:\n        print(\"ERROR: {0}\".format(e))\n        sys.exit(EIO)\n\n    # Create summary file clicommands.rst and directory containing the description\n    # of the individual commands\n    cwd = os.getcwd() # this should be ~/eos/doc\n    doc_dest = ''.join([cwd, \"/html\"])\n    cli_cmd_dir = ''.join([cwd, \"/clicommands\"])\n    cli_cmd_file = ''.join([cli_cmd_dir, \".rst\"])\n\n    # Clean up old documentation\n    if (os.path.exists(doc_dest) or os.path.exists(cli_cmd_file)\n        or os.path.exists(cli_cmd_dir)):\n\n        print(\"INFO: Clean up old documentation\", file=sys.stdout)\n\n        for entry in [doc_dest, cli_cmd_dir, cli_cmd_file]:\n            try:\n                if os.path.isdir(entry):\n                    shutil.rmtree(entry)\n                else:\n                    os.remove(entry)\n            except OSError as __:\n                pass # entry already deleted\n\n    try:\n        os.makedirs(cli_cmd_dir)\n    except OSError as e:\n        print(\"ERROR: {0}\".format(e), file=sys.stderr)\n        sys.exit(EIO)\n\n    try:\n        os.mknod(cli_cmd_file)\n    except Exception as e:\n        print(\"ERROR: {0}\".format(e), file=sys.stderr)\n        sys.exit(EIO)\n\n    # Generate the CLI commands documentation\n    cmd_lst = sorted(cmd_dict.keys())\n    generate_summary_rst(cli_cmd_file, cmd_lst)\n    generate_cmds_rst(cli_cmd_dir, cmd_dict)\n\n    # Generate the rest of the documentation using Sphinx\n    print(\"Generating Sphinx documentation ...\")\n    sphinx_proc = subprocess.Popen([SPHINX_BUILD, \"-b\", \"html\", cwd, doc_dest],\n                                   stdout=subprocess.PIPE,\n                                   stderr=subprocess.PIPE)\n    out, err = sphinx_proc.communicate()\n\n    if sphinx_proc.returncode:\n        print(\"ERROR: sphinx-build failed, msg={0}\".format(err))\n        sys.exit(sphinx_proc.returncode)\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "doc/citrine/index.rst",
    "content": ":orphan:\n\n.. _index:\n\n\nEOS - Open Storage Documentation\n================================\n\n.. image:: cc.jpg\n   :height: 100px\n   :width: 530px\n   :align: center\n\n.. epigraph::\n   \n   **EOS** is an open-source storage software solution to manage multi PB storage for the CERN Large Hadron Collider `LHC <https://home.cern/topics/large-hadron-collider>`_. Core of the implementation is the `XRootD <http://xrootd.org>`_ framework providing a feature-rich remote access protocol.\n\n\n.. toctree::\n   :maxdepth: 1\n\n   intro\n   releases\n   quickstart\n   install\n   using\n   develop\n   configuration\n   clicommands\n   restapi\n   taperestapi\n   QuarkDB Documentation <https://quarkdb.web.cern.ch>\n\n.. image:: grafana.jpg\n   :height: 130px\n   :width: 530px\n   :align: center\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "doc/citrine/install.rst",
    "content": ".. highlight:: rst\n\nInstall\n=======================\n\n.. toctree::\n   :maxdepth: 1\n\n   quickstart/docker_image\n   quickstart/kubernetes\t      \n   quickstart/boxed\t      \n   quickstart/install\n   quickstart/ubuntu\n   \n"
  },
  {
    "path": "doc/citrine/intro.rst",
    "content": ".. highlight:: rst\n\nIntroduction\n=======================\n.. image:: lhc.jpg\n   :width: 530px\n   :align: center\n\nHistory\n-------\nThe `EOS <http:://eos.cern.ch>`_ project was started in April 2010 in the CERN IT data storage group. \n\n\nGoal\n----\n\nThe main goal of the project is to provide fast and reliable disk only storage technology for CERN `LHC <https://home.cern/topics/large-hadron-collider>`_ use cases. The following picture demonstrates the main use case at CERN:\n\n.. image:: eos-usage-cern.jpg\n   :width: 530px\n   :align: center\n\n\nSoftware Versions\n-----------------\nThe phasing-out production versions is called **Beryl/Aquamarine** v0.3.X.\n\nThe stable production version called **Citrine** is currently v4.2.X\n\nLicense\n-------\nSince release version 0.1.1 EOS is available under GPL v3 license. \n\n\nArchitecture\n------------\n\nEOS is made by three components:\n\n* **MGM** - metadata server\n* **FST** - storage server\n* **MQ** - message broker for asynchronous messaging\n\n.. image:: eos-base-arch.jpg\n   :width: 530px\n   :align: center\n\nThe initial architecture is using an in-memory namespace implementation with a master-slave high-availability model. This implementation provides very low-latency for meta data access.\n\nSince the CITRINE release, the architecture has been modified to provide an optimal in-memory namespace cache and a KV store for persistency. This was necessary to overcome the scalability limitation of the meta-data service given by the maximum available RAM of **MGM** nodes.\n\nEOS is implemented using the `XRootD Framework <https://xrootd.org>`_.  \n\n.. image:: eos-architecture.jpg\n   :width: 530px\n   :align: center\n\n\n\n\nStorage Concepts\n----------------\n\nEOS uses a storage index concept to identify where and how files are stored. These information is stored inside the meta data of each file. \n\nFiles are stored with a a so called **layout**. The following layouts are supported\n\n* **plain** - a file is stored as a plain file in one filesystem\n* **replica** - a file is stored with a variable number of replicas in `n` filesystems\n* **rain** - reed-solomon encoded files with data and parity blocks \n  \n  * raid6 - a file is chunked into blocks and stored in `n-2` filesystems for data and `2` filesystems for parity blocks\n  * archive - a file is chunked into blocks and stored in `n-3` filesystems for data and `3` filesystems for parity blocks\n\n\nEOS groups storage resources into three logical categories:\n\n* **spaces**\n* **groups**\n* **filesystems**\n\nA **space** is used to reference a physical location when files are placed by the storage system. **spaces** are made by placement **groups**. **groups** consist of one or many filesystems. The EOS scheduler selects a **group** to store all replicas or chunks of a file are stored within a single **group**. Therefore the filesystems within a group should never be on the same node to guarantee availability with node failures.\n\nProtocols and Clients\n---------------------\n\nThe native protocol is the **xrootd** protocol, which provides additional functionalities like vector reads, third party copy transfers etc.\n\nEOS can be used like a filesystem using FUSE clients. There are two implementations available:\n\n* **eosd** - available for BERYL and CITRINE - limited POSIX conformity\n* **eosxd** - available for CITRINE - improved POSIX conformity, cross-client consistency aso. \n\nEOS has been extended to work similar to `Owncloud <owncloud.org>`_ as a sync and share platform. The front-end service is called `CERNBox <https://cernbox.web.cern.ch>`_.\n\n.. IMAGE:: cernbox.jpg\n   :width: 530px\n   :align: center\n\n\nArchitecture Roadmap\n--------------------\n\nThe target architecture for the next major release version is shown in the following figure:\n\n.. IMAGE:: roadmap-2018.jpg\n   :width: 530px\n   :align: center\n\nThe goal is to reach full scalability and high-availability of all service components and to embed better external storage resources like shared filesystems and cloud storage.\n\n\n\n\n\n\n"
  },
  {
    "path": "doc/citrine/quickstart/admin/configure.rst",
    "content": ".. index::\n   single: Admin Configuration\n\n.. _eos_admin_configure:\n\nEOS admin configuration\n=======================\n\nEOS distinguished two type of nodes:\n\n* the MGM node (namespace node)\n* the FST nodes (storage nodes)\n\n.. warning:: This quickstart skips installing a QuarkDB instance for brevity.  In EOS5+, QuarkDB is required and it is highly suggested for most deployments in EOS4 due to the memory and time requirements for large namespaces.  See the conventional deployment for details at https://eos-docs.web.cern.ch/develop.html#deployment.\n\nCopy an example config file to /etc/sysconfig/eos\n\n.. code-block:: bash\n\n   // SysV flavour\n   cp /etc/sysconfig/eos.example /etc/sysconfig/eos\n   // systemd flavour\n   cp /etc/sysconfig/eos_env.example /etc/sysconfig/eos_env\n\nSetup MGM\n---------\nChange the following variables in /etc/sysconfig/eos[_env]\n\nDefine three roles of daemons to run on this node:\n\n.. code-block:: bash\n\n   export XRD_ROLES=\"mq mgm fst\"\n\nIn this example we will run the message broker (MQ), the namespace (MGM) and the storage (FST) service all on one machine.\n\nDefine your EOS instance name\n\n.. note::\n\n   EOS_INSTANCE_NAME has to start with \"eos\" and has the form \"eos<name>\". We will use <name> test in this instruction.\n\n.. code-block:: bash\n\n   export EOS_INSTANCE_NAME=eostest\n\nAdjust the following variables according to your MGM hostname\n\n.. code-block:: bash\n\n   export EOS_BROKER_URL=root://localhost:1097//eos/\n   export EOS_MGM_MASTER1=<MGM hostname>\n   export EOS_MGM_MASTER2=<MGM hostname>\n   export EOS_MGM_ALIAS=<MGM hostname>\n   export EOS_FUSE_MGM_ALIAS=<MGM hostname>\n\nIn /etc/xrd.cf.mgm change security setting as you need\n\n.. code-block:: bash\n\n   # Example disable krb5 and gsi\n   #sec.protocol krb5\n   #sec.protocol gsi\n   sec.protbind * only sss unix\n\nLet's start EOS\n\n.. code-block:: bash\n\n   // SysV flavour\n   bash> service eos start\n\n   // systemd flavour\n   bash> systemctl start eos@*\n\nFor details for `systemd` support see :ref:`systemd`\n\nEnable shared secret `sss` security\n\n.. code-block:: bash\n\n   eos -b vid enable sss\n\nEOS uses a space and scheduling group concept\n\n* spaces = made by groups\n* groups = made by group of filesystems\n* filesystems = mount point\n\nThe `default` space name is **default**. Groups in the **default** space are numbered **default.0** .. **default.<n>**. Each group can contain an arbitrary number of filesystems.\n\n.. note::\n\n   You should have at least as many scheduling groups as the number of filesystems per storage node. The maximum number of\n   filesystems in one EOS instance is limited to 64k.\n\nIf you have 20 disks on storage nodes you create 20 groups in space **default**\n\n.. code-block:: bash\n\n   for name in `seq 1 20`; do eos -b group set default.$name on; done\n\nYou can list your single storage node and your group configuration doing\n\n.. code-block:: bash\n\n   eos node ls\n   eos group ls\n\n.. note::\n\n   We see also our MGM node because we added \"fst\" in XRD_ROLES. You can remove if you don't want to use your MGM machine as a storage srever.\n\nTo start a FUSE mount on MGM you can do\n\n.. code-block:: bash\n\n   bash> mkdir /eos/\n   bash> service eosd start\n\nSetup FST\n---------\n\nWe will now setup a storage node on a different machine than the MGM node.\n\nWe create an empty eos config file in /etc/sysconfig/eos[_env] and change <MGM hostname> accordingly to our MGM hostname\n\n.. code-block:: bash\n\n   // for systemd based configurations omit the export statement\n\n   DAEMON_COREFILE_LIMIT=unlimited\n   export XRD_ROLES=\"fst\"\n   export LD_PRELOAD=/usr/lib64/libjemalloc.so.1\n   export EOS_BROKER_URL=root://<MGM hostname>:1097//eos/\n\n.. code-block:: bash\n\n   // SysV flavour\n   bash> service eos start\n\n   // systemd flavour\n   bash> systemctl start eos@fst\n\nNow on the MGM node we should see a new FST running\n\n.. code-block:: bash\n\n   [root@eosfoo.ch ~]# eos node ls\n   #-----------------------------------------------------------------------------------------------------------------------------\n   #     type #                       hostport #   status #     status # txgw #gw-queued # gw-ntx #gw-rate # heartbeatdelta #nofs\n   #-----------------------------------------------------------------------------------------------------------------------------\n   nodesview                      eosfoo.ch:1095   online           on    off          0       10      120                1     1\n   nodesview                      eosfst.ch:1095   online           on    off          0       10      120                1     1\n\nNow we are going to add filesystems (partitions) to the storage node\n\n.. note::\n\n   Make sure that your data partition is having \"user_xattr\" option on, when you are mounting. Here is example in /etc/fstab\n\n   /dev/sdb1 /data01  ext4    defaults,user_xattr        0 0\n\nLet's assume that you have four partitions ``/data01 /data02 /data03 /data04``. You have to change the ownership of all storage directories to daemon:daemon because all EOS daemons run under the ``daemon`` account.\n\n.. code-block:: bash\n\n   chown -R daemon:daemon /data*\n\nRegister them towards the MGM via\n\n.. code-block:: bash\n\n   eosfstregister /data default:4\n\n.. note::\n\n   If you want to remove filesystems, they have to be in state ``empty`` (see :ref:`draining`) and then can be deleted via `eos fs rm <fsid>`\n\nConfigure MGM\n-------------\n\nTo enable all hosts and filesystems in the **default** space do\n\n.. code-block:: bash\n\n   eos space set default on\n\nYou can check the status of your space with\n\n.. code-block:: bash\n\n   eos space ls\n   eos space status default\n\nIf ``space ls`` shows non zero as **rw** space, you have successfully configured your EOS instance to store data.\n\nConfigure MGM space\n-------------------\n\nThere are some space related parameters to modify the behaviour of EOS\n\n.. code-block:: bash\n\n   # disable quota for first tests\n   eos space quota default off\n\n   # disable balancer\n   eos space config default space.balancer=off\n\n   # set balancer threshold to 5%\n   eos space config default space.balancer.threshold=5\n\n   # set checksum scan interval to 1 week\n   eos space config default space.scaninterval=604800\n\n   # set drain delay for IO errors to 1 hours\n   eos space config default space.graceperiod=3600\n\n   # set max drain time to 1 day\n   eos space config default space.drainperiod=86400\n\nBasic Testing\n-------------\n\nYou can create a test directory, upload and download a file as a first proof of concept test:\n\n.. code-block:: bash\n\n   # create a test directory\n   eos mkdir /eos/testarea/\n   # open the permissions\n   eos chmode 777 /eos/testarea/\n   # upload a test file\n   eos cp /etc/group /eos/testarea/file.1\n   # download a test file\n   eos cp /eos/testarea/file.1 /tmp/group\n   # compare the two files\n   diff /etc/group /tmp/group\n   # inspect the physical location and meta data of the uploaded file\n   eos file info /eos/testarea/file.1\n\n\nEnable kerberos security\n------------------------\n\n.. toctree::\n   :maxdepth: 1\n\n   krb5\n\n\nSetup AUTH service\n------------------\n\nIf an MGM has to handle more than 32k clients it is recommended to deploy a scalable front-end\nservice called AUTH.\n\nSetup AUTH plugin\n*****************\n\nThe authentication plugin is intended to be used as an OFS library with a\nvanilla XRootD server. What it does is to connect using ZMQ sockets to the\nreal MGM nodes (in general it should connect to a master and a slave MGM).\nIt does this by reading out the endpoints it needs to connect to from the\nconfiguration file (/etc/xrd.cf.auth). These need to follow the format:\n\"host:port\" and the first one should be the endpoint corresponding to the\nmaster MGM and the second one to the slave MGM. The EosAuthOfs plugin then\ntries to replay all the requests it receives from the clients to the master\nMGM node. It does this by marshalling the request and identity of the client\nusing ProtocolBuffers and sends this request using ZMQ to the master MGM\nnode. The authentication plugin can run on the same machine as an MGM node or\non a different machine. Once can use several such authentication services\nat the same time.\n\nThere are several tunable parameters for this configuration (auth + MGMs):\n\nAUTH - configuration\n********************\n\n- **eosauth.mastermgm** and **eosauth.slavemgm** - contain the hostnames and the\n   ports to which ZMQ can connect to the MGM nodes so that it can forward\n   requests and receive responses. Only the mastermgm parameter is mandatory\n   the other one is optional and can be left out.\n- **eosauth.numsockets** - once a clients wants to send a request the thread\n    allocated to him in XRootD will require a socket to send the request\n    to the MGM node. Therefore, we set up a pool of sockets from the\n    beginning which can be used to send/receiver requests/responses.\n    The default size is 10 sockets.\n\nMGM - configuration\n*******************\n\n- **mgmofs.auththreads** - since we now receive requests using ZMQ, we no longer\n    use the default thread pool from XRootD and we need threads for dealing\n    with the requests. This parameter sets the thread pool size when starting\n    the MGM node.\n- **mgmofs.authport** - this is the endpoint where the MGM listens for ZMQ\n    requests from any EosAuthOfs plugins. This port needs to be opened also\n    in the firewall.\n\nIn case of a master <=> slave switch the EosAuthOfs plugin adapts\nautomatically based on the information provided by the slave MGM which\nshould redirect all clients with write requests to the master node. Care\nshould be taken when specifying the two endpoints since the switch is done\nONLY IF the redirection HOST matches one of the two endpoints specified in\nthe configuration  of the authentication plugin (namely eosauth.instance).\nOnce the switch is done all requests be them read or write are sent to the\nnew master MGM node.\n"
  },
  {
    "path": "doc/citrine/quickstart/admin/krb5.rst",
    "content": ".. index::\n   single: Kerberos Security \n\n.. _eos_admin_krb5:\n\nEnabling kerberos security\n==========================\n\nThe initial requirement is that your local LINUX accounts correspond to kerberos principal names.\n\nTo start install krb5 packages\n\n.. code-block:: text\n   \n   yum install krb5-workstation\n\nThen you need to ask kerberos admin to create \"host/<mgm hostname>@EXAMPLE.COM\", where EXAMPLE.COM is your REALM (like CERN.CH, SASKE.SK, ...) and create\na keytab file, for example krb5.keytab. The keytab file is stored under /etc/krb5.keytab on the MGM node. To test it you can use ktutil command. The following example is showing keytab contents to be used on MGM host eosfoo.bar.ch@BAR.CH\n\n.. code-block:: text\n\n   [root@eosfoo.bar.ch ~]# ktutil \n   ktutil:\n   ktutil:  read_kt /etc/krb5.keytab\n   ktutil:  list\n   slot KVNO Principal\n   ---- ---- ---------------------------------------------------------------------\n      1    2 host/eosfoo.bar.ch@BAR.CH\n      2    2 host/eosfoo.bar.ch@BAR.CH\n      3    2 host/eosfoo.bar.ch@BAR.CH\n      4    2 host/eosfoo.bar.ch@BAR.CH\n\nOn the MGM in ``/etc/xrd.cf.mgm`` you have to enable kerberos 5 authentication\n\n.. code-block:: text\n\n   sec.protocol krb5 host/<host>@EXAMPLE.COM\n   \n   sec.protbind * only krb5 sss unix\n   \nTo enable krb5 security mapping of user names you do\n\n.. code-block:: text\n   \n   eos -b vid enable krb5\n\n"
  },
  {
    "path": "doc/citrine/quickstart/boxed.rst",
    "content": ".. index::\n   single: Boxed\n\n.. _eos_base_boxed:\n\n.. _boxed_url: http://sciencebox.web.cern.ch/sciencebox\n\nScientific Services Installation: EOS, CERNBox, SWAN and CVMFS\n==============================================================\n\n.. image:: cernlogo.jpg\n   :scale: 35 %\n   :align: left\n\n|\n|\n\nWe have bundled a demonstration setup of four CERN developed cloud and analysis platform services called `Boxed <http://sciencebox.web.cern.ch/sciencebox/>`_. It encapsulates four components:\n\n- `EOS <http://eos.cern.ch>`_ - scalable storage platform with data, metadata and messaging server components\n- `CERNBox <https://cernbox.web.cern.ch>`_ - dropbox-like add-on for sync-and-share services on top of EOS\n- `SWAN <https://swan.web.cern.ch>`_ - service for web based interactive analysis with jupyter notebook interface\n- `CVMFS <https://cvmfs.web.cern.ch>`_ - CernVM file system - a scalable software distribution service\n\n.. image:: boxed.jpg\n   :width: 530px\n   :align: center\n\n\nCheckout the project of your interest and follow the guidelines at http://sciencebox.web.cern.ch/sciencebox\n\n"
  },
  {
    "path": "doc/citrine/quickstart/client/configure.rst",
    "content": ".. index::\n   single: Client Configuration\n\n.. _eos_client_configure:\n\nEOS client configuration\n========================\n\nYou need to setup two variables in your environment to direct the **eos** CLI to connect to a given EOS instance.\n\n.. code-block:: text\n\n   export EOS_MGM_URL=\"root://<mgm hostname>\"\n   export EOS_HOME=\"<home dir in eos space>\"\n\n\nFor example for MGM at eosfoo.ch and home directory in EOS for user bar ``/eos/foo/users/b/bar/``\n\n.. code-block:: text\n\n   export EOS_MGM_URL=\"root://eosfoo.ch\"\n   export EOS_HOME=\"/eos/foo/users/b/bar/\"\n\nand now we can invoce the CLI\n\n.. code-block:: text\n\n   me@myhost.ch ~ $ eos\n   # ---------------------------------------------------------------------------\n   # EOS  Copyright (C) 2018 CERN/Switzerland\n   # This program comes with ABSOLUTELY NO WARRANTY; for details type `license'.\n   # This is free software, and you are welcome to redistribute it\n   # under certain conditions; type `license' for details.\n   # ---------------------------------------------------------------------------\n   EOS_INSTANCE=eosfoo.ch\n   EOS_SERVER_VERSION=4.2.18 EOS_SERVER_RELEASE=1\n   EOS_CLIENT_VERSION=4.2.18 EOS_CLIENT_RELEASE=3\n   EOS Console [root://eosfoo.ch] |/> cd\n   EOS Console [root://eosfoo.ch] |/eos/foo/users/b/bar/>\n\nAnd now you are ready to use any command in EOS. E.G.  find all files under the ``/eos/foo/users/b/bar/`` directory\n\n.. code-block:: text\n\n   EOS Console [root://eosfoo.ch] |/> find -f /eos/foo/users/b/bar/\n   /eos/foo/users/b/bar/test.txt\n\nYou can also mount EOS using FUSE mount from eosfoo.ch. First you need to install the eos-fuse RPM and edit ``/etc/sysconfig/eos[_env]`` on your client machine\n\n.. note::\n\n   To install eos-fuse follow instruction at :ref:`eos_base_install`\n\n.. code-block:: text\n\n   root@lx001.saske.sk ~ $ cat /etc/sysconfig/eos\n   # ------------------------------------------------------------------\n   # FUSE Configuration\n   # ------------------------------------------------------------------\n   # The mount directory for 'eosd'\n   export EOS_FUSE_MOUNTDIR=/eos/\n   # The MGM host from where to do the initial mount\n   export EOS_FUSE_MGM_ALIAS=eosfoo.ch\n\n\nYou can start the mount as the **eosd** service\n\n.. code-block:: text\n\n   # in sl5 and sl6\n   service eosd start\n   # in CentOS 7 or in fedora 18 and above\n   systemclt start eosd\n"
  },
  {
    "path": "doc/citrine/quickstart/docker_image.rst",
    "content": ".. index::\n   single: Docker Image\n\n.. _eos_base_docker_image:\n\n.. _docker: https://docs.docker.com/\n\nEOS Docker Installation\n=======================\n\n.. image:: docker.jpg\n   :scale: 50 %\n   :align: center   \n\nA docker installation is the easiest way to go if you want to try EOS and get up a test instance in short time. \nWe provide EOS docker images with all the necessary components installed and ready to use.\n\n\nPreparation\n-----------\n\n.. note::\n\n   Make sure you have docker_ installed and the docker daemon started on your system.\n\n.. code-block:: bash\n\n   // Example on CentOS 7\n   yum install docker\n   systemctl start docker\n\n\nRun EOS in docker\n-----------------\n\nCheckout the `eos-docker <https://gitlab.cern.ch/eos/eos-docker>`_ project:\n\n.. code-block:: bash\n\n   git clone https://gitlab.cern.ch/eos/eos-docker.git\n   cd eos-docker\n\nTo start a small instance with 6 storage servers (**FST**), 1 namespace server (**MGM**), 1 messaging worker (**MQ**), 1 client and 1 Kerberos **KDC** containers ready to use,\nall you have to do is to use the `start_services <https://gitlab.cern.ch/eos/eos-docker/blob/master/scripts/start_services.sh>`_ script.\n\nThe arguments to provide are the name of the image to use with the **-i** and the number of FST containers (default is 6) with the **-n** flag.\n\nThe containers will reside on the same network knowing about each other and are configured to be working out-of-the-box.\n\nYou are likely to have the best results with the latest release shown in <https://eos-docs.web.cern.ch/releases.html>.  These instructions assume that it is 4.8.39.\n\n.. code-block:: bash\n\n   scripts/start_services.sh -i gitlab-registry.cern.ch/dss/eos/eos-ci:5.1.16 -n 6 -q\n\nTo connect to EOS using the *eos* shell CLI running in the MGM container you can do:\n\n.. code-block:: bash\n\n   docker exec -i eos-mgm1 eos\n   EOS Console [root://localhost] |/> whoami\n   whoami\n   Virtual Identity: uid=0 (2,99,3,0) gid=0 (99,4,0) [authz:sss] sudo* host=localhost\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> version\n   version\n   EOS_INSTANCE=eosdockertest\n   EOS_SERVER_VERSION=5.1.16 EOS_SERVER_RELEASE=1\n   EOS_CLIENT_VERSION=5.1.16 EOS_CLIENT_RELEASE=1\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> node ls\n   node ls\n   ┌──────────┬─────────────────────────────────────┬────────────────┬──────────┬────────────┬──────┬──────────┬────────┬────────┬────────────────┬─────┐\n   │type      │                             hostport│          geotag│    status│      status│  txgw│ gw-queued│  gw-ntx│ gw-rate│  heartbeatdelta│ nofs│\n   └──────────┴─────────────────────────────────────┴────────────────┴──────────┴────────────┴──────┴──────────┴────────┴────────┴────────────────┴─────┘\n    nodesview  eos-fst1.eoscluster.cern.ch:1095      docker-test     online           on    off          0       10      120                2     1 \n    nodesview  eos-fst2.eoscluster.cern.ch:1095      docker-test     online           on    off          0       10      120                2     1 \n    nodesview  eos-fst3.eoscluster.cern.ch:1095      docker-test     online           on    off          0       10      120                2     1 \n    nodesview  eos-fst4.eoscluster.cern.ch:1095      docker-test     online           on    off          0       10      120                2     1 \n    nodesview  eos-fst5.eoscluster.cern.ch:1095      docker-test     online           on    off          0       10      120                2     1 \n    nodesview  eos-fst6.eoscluster.cern.ch:1095      docker-test     online           on    off          0       10      120                2    \n\nYou can mount EOS to the client container using FUSE and KRB5 authentication.\n\n.. code-block:: bash\n\n   docker exec -i eos-cli1 env EOS_MGM_URL=root://eos-mgm1.eoscluster.cern.ch eos fuse mount /eos\n   docker exec -it eos-cli1 bash\n\n   .... trying to create ... /eos\n   ===> Mountpoint   : /eos\n   ===> Fuse-Options : max_readahead=131072,max_write=4194304,fsname=eos-mgm1.eoscluster.cern.ch,url=root://eos-mgm1.eoscluster.cern.ch//eos/\n   ===> fuse readahead        : 1\n   ===> fuse readahead-window : 1048576\n   ===> fuse debug            : 0\n   ===> fuse low-level debug  : 0\n   ===> fuse log-level        : 5 \n   ===> fuse write-cache      : 1\n   ===> fuse write-cache-size : 67108864\n   ===> fuse rm level protect : 1\n   ===> fuse lazy-open-ro     : 0\n   ===> fuse lazy-open-rw     : 1\n   ==== fuse multi-threading  : true\n   info: successfully mounted EOS [root://eos-mgm1.eoscluster.cern.ch] under /eos\n\n.. code-block:: bash\n\n   [root@testmachine eos-docker]# docker exec -it eos-cli1 bash \n   ls -la /eos/\n   total 4\n   drwxrwxr-x.  1 root root    0 Jan  1  1970 .\n   drwxr-xr-x. 18 root root 4096 Mar 14 10:16 ..\n   drwxrwxr-x.  1 root root    0 Jan  1  1970 dockertest\n\nOr by running the EOS instance testsuite\n\n.. code-block:: bash\n\n   docker exec -i eos-mgm1 eos-instance-test\n\n\nDelete and clean\n-------------------\n\nYou can use the  `shutdown_services <https://gitlab.cern.ch/eos/eos-docker/blob/master/scripts/shutdown_services.sh>`_ script to remove these EOS containers from your system.\n\n.. code-block:: bash\n\n   scripts/shutdown_services.sh\n\n\nImage Repository\n-------------------\n\nYou can get the images for each automatic build and for each release.\nThe release images are tagged with the release version. Regular images are tagged with the build id of their originating pipeline.\n\nDocker images are accessible from the project's `registry <https://gitlab.cern.ch/dss/eos/container_registry>`_.\n\n\n.. code-block:: bash\n\n   docker pull gitlab-registry.cern.ch/dss/eos-ci:<tag>\n\nExample for a build\n\n.. code-block:: bash\n\n   docker pull gitlab-registry.cern.ch/dss/eos-ci:206970\n\nExample for the latest release\n\n.. parsed-literal::\n\n   docker pull gitlab-registry.cern.ch/dss/eos-ci:|version| \n\nSelfmade images\n---------------\n\nIn case you would like to create a different setup, you are welcome to browse and reuse the provided scripts under\nthe `image_scripts <https://gitlab.cern.ch/eos/eos-docker/tree/master/image_scripts>`_ directory to get an idea on how to do it.\n"
  },
  {
    "path": "doc/citrine/quickstart/install.rst",
    "content": ".. index::\n   single: Installation\n\n.. _eos_base_install:\n\nRPM installation\n================\n\n.. image:: rpm.jpg\n   :scale: 50%\n   :align: left\n\n|\n|\n\n\n.. note::\n   You need to add the XRootD and EOS repositories.\n   Please follow instruction from :ref:`eos_base_setup_repos`\n\n\n\nInstall EOS via yum\n-------------------\n\nFor client\n\n.. code-block:: text\n\n   yum install eos-client\n\nFor server\n\n.. code-block:: text\n\n   yum install eos-server eos-client eos-testkeytab eos-fuse jemalloc\n\nFor HTTP server\n\n.. code-block:: text\n\n   yum install eos-nginx\n\nFor GridFTP server\n\n.. code-block:: text\n\n   yum install xrootd-dsi\n\nFor EOS test on MGM (namespace node)\n\n.. code-block:: text\n\n   yum install eos-test\n"
  },
  {
    "path": "doc/citrine/quickstart/kubernetes.rst",
    "content": ".. index::\n   single: Kubernetes\n\n.. _eos_base_kubernetes:\n\n.. _kubernetes: https://kubernetes.io/docs/home/\n\nEOS Kubernetes Installation\n===========================\n\n.. image:: kubernetes-logo.png\n   :scale: 50 %\n   :align: center   \n\n\nA Kubernetes installation provides a fully functional distributed EOS instances on top of Kubernetes clusters,\nkeeping the advantages of a straightforward set up and a short deployment time.\nThe EOS team provides docker images with all the necessary components installed and ready to use.\n\n\nPreparation\n-----------\n\n.. note::\n\n   Make sure you have `kubectl <https://kubernetes.io/docs/reference/kubectl/overview/>`_ installed and access rights to a Kubernetes cluster on your system.\n   To start playing around by using a virtual cluster in your local machine, you can use `Minikube <https://kubernetes.io/docs/tasks/tools/install-minikube>`_.\n\n\nRun EOS in Kubernetes\n---------------------\n\nCheckout the eos-on-k8s project:\n\n.. code-block:: bash\n\n   git clone https://gitlab.cern.ch/faluchet/eos-on-k8s.git\n   cd eos-on-k8s\n\n\nTo start a small instance with 8 storage servers (**FST**), 1 namespace server (**MGM**), 1 messaging worker (**MQ**), 1 client and 1 Kerberos **KDC** Kubernetes Pod ready to use, all you have to do is to use the `create-all.sh <https://gitlab.cern.ch/faluchet/eos-on-k8s/blob/master/create-all.sh>`_ script. It is recommended to provide a <namespace> argument, via the -n flag, representing the virtual cluster name the resources will live in. Each EOS entities (\"roles\") will reside on the same network knowing about each other and are configured to be working out-of-the-box.\nWe refer the courious reader to the `official documentation <https://kubernetes.io/docs/home/>`_ to deepen his knowledge about Kubernetes concepts. \n\n.. code-block:: bash\n\n   ./create-all -n <your_namespace>\n\nWait for the resources creation and EOS setup, and you are ready to go. You can check the cluster state in any moment, i.e. with:  \n\n.. code-block:: bash\n\n   kubectl get nodes # get the cluster node list \n   kubectl get all # get all the Kubernetes resources residing on the \"default\" namespace.  \n   kubectl get all -n <your_namespace>\n\n\n.. note::\n\n   From now on we will use the namespace \"tutorial\".  \n\n\nTo connect to EOS using the eos shell CLI running in the MGM container you can do:  \n\n.. code-block:: bash\n\n   kubectl exec -n tutorial -it <mgm_pod_name> -- eos  \n\nIt is a bit verbose though very easy getting the name of a Pod of your interest, through the use of easy-to-remember labels:\n\n.. code-block:: bash\n\n   kubectl get pods -n tutorial --no-headers -o custom-columns=\":metadata.name\" -l app=eos-mgm\n\nSo, all together:  \n\n.. code-block:: bash\n\n   kubectl exec -n tutorial -it $(kubectl get pods -n tutorial --no-headers -o custom-columns=\":metadata.name\" -l app=eos-mgm) -- eos\n   EOS Console [root://localhost] |/> whoami\n   whoami\n   Virtual Identity: uid=0 (2,99,3,0) gid=0 (99,4,0) [authz:sss] sudo* host=localhost\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> version\n   version\n   EOS_INSTANCE=eosdockertest\n   EOS_SERVER_VERSION=4.4.38 EOS_SERVER_RELEASE=1\n   EOS_CLIENT_VERSION=4.4.38 EOS_CLIENT_RELEASE=1\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> node ls\n   ┌──────────┬─────────────────────────────────────────────────┬────────────────┬──────────┬────────────┬──────┬──────────┬────────┬────────┬────────────────┬─────┐\n   │type      │                                         hostport│          geotag│    status│      status│  txgw│ gw-queued│  gw-ntx│ gw-rate│  heartbeatdelta│ nofs│\n   └──────────┴─────────────────────────────────────────────────┴────────────────┴──────────┴────────────┴──────┴──────────┴────────┴────────┴────────────────┴─────┘\n    nodesview  eos-fst1.eos-fst1.tutorial.svc.cluster.local:1095     docker::test     online           on    off          0       10      120                2     1 \n    nodesview  eos-fst2.eos-fst2.tutorial.svc.cluster.local:1095     docker::test     online           on    off          0       10      120                1     1 \n    nodesview  eos-fst3.eos-fst3.tutorial.svc.cluster.local:1095     docker::test     online           on    off          0       10      120                1     1 \n    nodesview  eos-fst4.eos-fst4.tutorial.svc.cluster.local:1095     docker::test     online           on    off          0       10      120                1     1 \n    nodesview  eos-fst5.eos-fst5.tutorial.svc.cluster.local:1095     docker::test     online           on    off          0       10      120                1     1 \n    nodesview  eos-fst6.eos-fst6.tutorial.svc.cluster.local:1095     docker::test     online           on    off          0       10      120                1     1 \n    nodesview  eos-fst7.eos-fst7.tutorial.svc.cluster.local:1095     docker::test     online           on    off          0       10      120                1     1 \n\n\nYou can mount EOS to the client Pods using FUSE and KRB5 authentication:\n\n.. code-block:: bash\n\n   kubectl exec -n tutorial -it $(kubectl get pods -n tutorial --no-headers -o custom-columns=\":metadata.name\" -l app=eos-cli1) -- eos fuse mount /eos\n\n   .... trying to create ... /eos\n   ===> Mountpoint   : /eos\n   ===> Fuse-Options : max_readahead=131072,max_write=4194304,fsname=eos-mgm1.eoscluster.cern.ch,url=root://eos-mgm1.eoscluster.cern.ch//eos/\n   ===> fuse readahead        : 1\n   ===> fuse readahead-window : 1048576\n   ===> fuse debug            : 0\n   ===> fuse low-level debug  : 0\n   ===> fuse log-level        : 5\n   ===> fuse write-cache      : 1\n   ===> fuse write-cache-size : 67108864\n   ===> fuse rm level protect : 1\n   ===> fuse lazy-open-ro     : 0\n   ===> fuse lazy-open-rw     : 1\n   ==== fuse multi-threading  : true\n   info: successfully mounted EOS [root://eos-mgm1.eoscluster.cern.ch] under /eos\n\n.. code-block:: bash\n\n   kubectl exec -n tutorial -it $(kubectl get pods -n tutorial --no-headers -o custom-columns=\":metadata.name\" -l app=eos-cli1) -- bash \n   \n   ls -la /eos/\n   total 4\n   drwxrwxr-x.  1 root root    0 Jan  1  1970 .\n   drwxr-xr-x. 18 root root 4096 Mar 14 10:16 ..\n   drwxrwxr-x.  1 root root    0 Jan  1  1970 dockertest\n\nOr by running the EOS instance testsuite:\n\n.. code-block:: bash\n\n   kubectl exec -n tutorial -i $(kubectl get pods -n tutorial --no-headers -o custom-columns=\":metadata.name\" -l app=eos-mgm) -- eos-instance-test\n\n\nDelete and clean\n----------------\n\nUse the `delete-all.sh <https://gitlab.cern.ch/faluchet/eos-on-k8s/blob/master/delete-all.sh>`_ script to remove the EOS instance from your system.\n\n.. code-block:: bash\n\n   ./delete-all.sh tutorial\n\n\nImage Repository\n----------------\n\nYou can get the images for each automatic build and for each release.\nThe release images are tagged with the release version. Regular images are tagged with the build id of their originating pipeline.\n\nDocker images are accessible from the project's `registry <https://gitlab.cern.ch/dss/eos/container_registry>`_.\n\n.. code-block:: bash\n\n   docker pull gitlab-registry.cern.ch/dss/eos:<tag>\n\nExample for a build\n\n.. code-block:: bash\n\n   docker pull gitlab-registry.cern.ch/dss/eos:777552\n\nExample for the latest release\n\n.. parsed-literal::\n\n   docker pull gitlab-registry.cern.ch/dss/eos:|version| \n\n\nKubernetes-ready images are available since release version 4.4.37\n\n\nSelfmade images\n---------------\n\nIn case you would like to create a different setup, you are welcome to browse and reuse the provided scripts under\nthe `image_scripts <https://gitlab.cern.ch/eos/eos-docker/tree/master/image_scripts>`_ folder of the eos-docker project to get an idea on how to do it.\n"
  },
  {
    "path": "doc/citrine/quickstart/ns_quarkdb.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Namespace in QuarkDB Configuration\n\n.. _ns_quarkdb_configure:\n\nNamespace in QuarkDB configuration\n===================================\n\n.. image:: quarkdb.jpg\n   :scale: 40%\n   :align: left\n\nThe following steps assume we are configuring an MGM node together with a QuarkDB cluster on the same machine.\nThe QuarkDB cluster will consist of three instances running on different ports. The operating system is **CentOS7**.\n\n|\n\nInstalling packages\n--------------------\n\nAdd the YUM repositories for **QuarkDB**, **EOS Citrine** and their dependencies.\n\n.. code-block:: bash\n\n  yum-config-manager --add-repo=http://storage-ci.web.cern.ch/storage-ci/quarkdb/tag/el7/x86_64/\n  Loaded plugins: fastestmirror, kernel-module, ovl, protectbase, versionlock\n  adding repo from: http://storage-ci.web.cern.ch/storage-ci/quarkdb/tag/el7/x86_64/\n\n  [storage-ci.web.cern.ch_storage-ci_quarkdb_tag_el7_x86_64_]\n  name=added from: http://storage-ci.web.cern.ch/storage-ci/quarkdb/tag/el7/x86_64/\n  baseurl=http://storage-ci.web.cern.ch/storage-ci/quarkdb/tag/el7/x86_64/\n  enabled=1\n\n  yum-config-manager --add-repo=http://storage-ci.web.cern.ch/storage-ci/eos/citrine/tag/el-7/x86_64/\n  Loaded plugins: fastestmirror, kernel-module, ovl, protectbase, versionlock\n  adding repo from: http://storage-ci.web.cern.ch/storage-ci/eos/citrine/tag/el-7/x86_64/\n\n  [storage-ci.web.cern.ch_storage-ci_eos_citrine_tag_el-7_x86_64_]\n  name=added from: http://storage-ci.web.cern.ch/storage-ci/eos/citrine/tag/el-7/x86_64/\n  baseurl=http://storage-ci.web.cern.ch/storage-ci/eos/citrine/tag/el-7/x86_64/\n  enabled=1\n\n  yum-config-manager --add-repo=http://storage-ci.web.cern.ch/storage-ci/eos/citrine-depend/el-7/x86_64/\n  Loaded plugins: fastestmirror, kernel-module, ovl, protectbase, versionlock\n  adding repo from: http://storage-ci.web.cern.ch/storage-ci/eos/citrine-depend/el-7/x86_64/\n\n  [storage-ci.web.cern.ch_storage-ci_eos_citrine-depend_el-7_x86_64_]\n  name=added from: http://storage-ci.web.cern.ch/storage-ci/eos/citrine-depend/el-7/x86_64/\n  baseurl=http://storage-ci.web.cern.ch/storage-ci/eos/citrine-depend/el-7/x86_64/\n  enabled=1\n\nInstall the **XRootD** packages (>= 4.8.1) from the *EPEL* repository.\n\n.. code-block:: bash\n\n  yum --disablerepo=\"*\" --enablerepo=\"epel\" install xrootd* --exclude=xrootd-fuse\n\nInstall the **QuarkDB** and **EOS** packages:\n\n.. code-block:: bash\n\n  yum install -y quarkdb quarkdb-debuginfo redis\n  yum install -y --nogpgcheck --disablerepo=\"cern*\" eos-server eos-client eos-rocksdb eos-testkeytab\n\n\nSetup the QuarkDB cluster\n-------------------------\n\nWe will run three instances of **QuarkDB** on ports 7001, 7002 and 7003. The default contents of ``/etc/xrootd/xrootd-quarkdb.cfg`` should be the following:\n\n.. code-block:: bash\n\n  if exec xrootd\n    xrd.port 7777\n    xrd.protocol redis:7777 /usr/lib64/libXrdQuarkDB.so\n    redis.mode raft\n    redis.database /var/quarkdb\n    redis.password_file /etc/eos.keytab\n  fi\n\nIn this example, we're using the EOS keytab file as a password for QuarkDB as well. The entire file contents will be read, and used as the password. No special parsing of the keytab file occurs, the entire thing is considered as the secret string. We're using the EOS keytab just for convenience, in principle any 32+ character string can be used as a password. Check out the `QDB documentation\n<http://quarkdb.web.cern.ch/quarkdb/docs/master/AUTHENTICATION.html>`_ for more information regarding password authentication.\n\nUsing this as a reference, we start customizing the configuration files for our three QuarkDB instances:\n\n.. code-block:: bash\n\n   for i in {1..3}; do\n     # Create one configuration file per instance\n     cp /etc/xrootd/xrootd-quarkdb.cfg /etc/xrootd/xrootd-quarkdb${i}.cfg\n     # Customize the port\n     sed -i 's/7777/700'\"${i}\"'/g' /etc/xrootd/xrootd-quarkdb${i}.cfg\n     # Customize the storage location\n     sed -i 's/\\/var\\/quarkdb/\\/var\\/lib\\/quarkdb\\/qdb'\"${i}\"'/g' /etc/xrootd/xrootd-quarkdb${i}.cfg\n     # Add myself entry to the config\n     sed -i 's/^fi/  redis.myself localhost:700'\"${i}\"'\\n&/' /etc/xrootd/xrootd-quarkdb${i}.cfg\n     # Prepare the log and working directories for the instances\n     mkdir -p /var/log/quarkdb/\n     mkdir -p /var/spool/quarkdb/\n     chown -R daemon:daemon /var/log/quarkdb/\n     chown -R daemon:daemon /var/spool/quarkdb/\n   done\n\nAll instances will run as user *daemon*. The ownership of the storage locations needs to be changed accordingly. For changing the ownership of the processes and the location of the log files, we can customize the systemd start-up script as follows:\n\n.. code-block:: bash\n\n   for i in {1..3}; do\n     mkdir -p /etc/systemd/system/xrootd@quarkdb${i}.service.d\n   echo -e \"[Service] \\nExecStart= \\nExecStart=/usr/bin/xrootd -l /var/log/quarkdb/xrootd.log -c /etc/xrootd/xrootd-%i.cfg -k fifo -s /var/run/quarkdb/xrootd-%i.pid -n %i \\nUser=daemon \\nGroup=daemon \\n\" > /etc/systemd/system/xrootd@quarkdb${i}.service.d/custom.conf\n   done\n\nThe next step is to initialize the **QuarkDB** database directory using the ``quarkdb-create`` command. For more details please consult the `QuarkDB documentation <https://quarkdb.web.cern.ch/quarkdb/docs/master/>`_.\n\n.. code-block:: bash\n\n   for i in {1..3}; do\n     quarkdb-create --path /var/lib/quarkdb/qdb${i}/ --clusterID 0123456789 --nodes localhost:7001,localhost:7002,localhost:7003\n     # Change ownership to daemon:daemon\n     chown -R daemon:daemon /var/lib/quarkdb/qdb${i}/\n   done\n\nWe can now start the **three QuarkDB** instances and they should soon reach a stable configuration with one master and two followers.\n\n.. code-block:: bash\n\n   # Start all the QuarkDB instances\n   for i {1..3}; do\n     systemctl start xrootd@quarkdb${i};\n   done\n\n   sleep 2\n\n   # Check their status\n   for i in {1..3}; do\n     systemctl status xrootd@quarkdb${i}\n   done\n\nAt this point the **QuarkDB** cluster should be up and running. The logs from the individual instances can be found in ``/var/log/quarkdb/quarkdb[1-3]/xrootd.log``. Using the redis command line interface, we can inspect the status of our cluster.\n\n.. code-block:: bash\n\n   redis-cli -p 7001 raft_info\n   1) TERM 324\n   2) LOG-START 0\n   3) LOG-SIZE 2\n   4) LEADER localhost:7001\n   5) CLUSTER-ID 0123456789\n   6) COMMIT-INDEX 1\n   7) LAST-APPLIED 1\n   8) BLOCKED-WRITES 0\n   9) LAST-STATE-CHANGE 48 (48 seconds)\n  10) ----------\n  11) MYSELF localhost:7001\n  12) STATUS LEADER\n  13) ----------\n  14) MEMBERSHIP-EPOCH 0\n  15) NODES localhost:7001,localhost:7002,localhost:7003\n  16) OBSERVERS\n  17) ----------\n  18) REPLICA localhost:7002 ONLINE | UP-TO-DATE | NEXT-INDEX 2\n  19) REPLICA localhost:7003 ONLINE | UP-TO-DATE | NEXT-INDEX 2\n\n\nSetup MGM with namespace in QuarkDB\n-----------------------------------\n\nTo integrate the MGM service with the **QuarkDB** cluster we need to make several modifications to the default configuration file ``/etc/xrd.cf.mgm``.\n\n  * Update the **mgmofs.nslib** directive to load the namespace in QuarkDB implementation:\n\n    .. code-block:: bash\n\n       mgmofs.nslib /usr/lib64/libEosNsQuarkdb.so\n\n  * List the endpoints of the QuarkDB cluster which are used by the MGM daemon to connect to the back-end service:\n\n    .. code-block:: bash\n\n       mgmofs.qdbcluster localhost:7001 localhost:7002 localhost:7003\n       mgmofs.qdbpassword_file /etc/eos.keytab\n\n  * Do the same for the FST configuration:\n\n    .. code-block:: bash\n\n       fstofs.qdbcluster localhost:7001 localhost:7002 localhost:7003\n       fstofs.qdbpassword_file /etc/eos.keytab\n\n  * As well as the MQ service:\n\n    .. code-block:: bash\n\n       mq.qdbcluster localhost:7001 localhost:7002 localhost:7003\n       mq.qdbpassword_file /etc/eos.keytab\n\nStart the MGM daemon as a master:\n\n .. code-block:: bash\n\n    systemctl start eos@master\n    systemctl start eos@mgm\n\nAt this point you should have the following processes running on the local machine:\n\n .. code-block:: bash\n\n    ps aux | grep xrootd\n    daemon    3658  0.5  0.3 1920252 28028 ?       Ssl  Mar15  30:25 /usr/bin/xrootd -l /var/log/quarkdb/xrootd.log -c /etc/xrootd/xrootd-quarkdb1.cfg -k fifo -s /var/run/quarkdb/xrootd-quarkdb1.pid -n quarkdb1\n    daemon    4369  0.1  0.2 1067196 21688 ?       Ssl  Mar15  10:10 /usr/bin/xrootd -l /var/log/quarkdb/xrootd.log -c /etc/xrootd/xrootd-quarkdb2.cfg -k fifo -s /var/run/quarkdb/xrootd-quarkdb2.pid -n quarkdb2\n    daemon    4409  0.1  0.2 1133760 17600 ?       Ssl  Mar15  10:01 /usr/bin/xrootd -l /var/log/quarkdb/xrootd.log -c /etc/xrootd/xrootd-quarkdb3.cfg -k fifo -s /var/run/quarkdb/xrootd-quarkdb3.pid -n quarkdb3\n    daemon    4896  0.0  0.2 110324 18240 ?        Ssl  Mar15   0:00 /usr/bin/xrootd -n sync -c /etc/xrd.cf.sync -l /var/log/eos/xrdlog.sync -s /tmp/xrootd.sync.pid -Rdaemon\n    daemon    5105  0.5  3.0 1457476 225548 ?      Ssl  Mar15  31:22 /usr/bin/xrootd -n mgm -c /etc/xrd.cf.mgm -l /var/log/eos/xrdlog.mgm -s /tmp/xrootd.mgm.pid -Rdaemon\n    daemon    5146  0.0  0.3 340884 22972 ?        S    Mar15   0:00 /usr/bin/xrootd -n mgm -c /etc/xrd.cf.mgm -l /var/log/eos/xrdlog.mgm -s /tmp/xrootd.mgm.pid -Rdaemon\n\n\nIn a production environment the MGM daemon and each of the QuarkDB instances of the cluster should run on different machines. Furthermore, for optimal performance of the **QuarkDB** backend, at least the QuarkDB master should have the ``/var/lib/quarkdb/`` directory stored on an **SSD** partition.\n\n\nConversion of in-memory namespace to QuarkDB namespace\n------------------------------------------------------\n\nThe first step in converting an in-memory namespace to QuarkDB is to compact the file and directory changelog files using the **eos-log-compact** tool:\n\n .. code-block:: bash\n\n  eos-log-compact /var/eos/md/file.mdlog /var/eos/md/compacted_file.mdlog\n  eos-log-compact /var/eos/md/directory.mdlog /var/eos/md/compacted_directory.mdlog\n\nThe conversion process requires that the entire namespace is loaded into memory, just like the normal booting of the namespace. Therefore, one must ensure that the machine used for the conversion has enough RAM to hold the namespace data structures.\nTo achieve optimum performance, it is recommended that both the changelog files and the ``/var/lib/quarkdb/`` directory are stored on an **SSD** backed partition.\n\nTo speed up the initial import, QuarkDB has a special **bulkload** configuration mode in which we're expected to do only write operations towards the backend. In this case the compaction of the data stored in QuarkDB happens only at the end, therefore minimising the number of I/O operations and thus speeding up the entire process. Create the usual QuarkDB directory structure by using the **quarkdb-create** tool. Below is an example of a QuarkDB configuration file that uses the **bulkload** mode:\n\n  .. code-block:: bash\n\n    xrd.port 7777\n    xrd.protocol redis:7777 /usr/lib64/libXrdQuarkDB.so\n    redis.mode bulkload\n    redis.database /var/lib/quarkdb/convert/\n\nAfter starting the QuarkDB service, we can use the **eos-ns-convert** tool to perform the actual conversion of the namespace.\n\n .. code-block:: bash\n\n   eos-ns-convert /var/eos/md/compacted_file.mdlog /var/eos/md/compacted_directory localhost 7777\n\n .. note::\n\n   The **eos-ns-convert** tool must use as input the **compacted** changelog files.\n\n\nOnce the bulkload is done, shut down the instance and create a brand new QuarkDB folder using **quarkdb-create** in a different location, listing the nodes that will make up the new cluster. Further details on how to configure a new QuarkDB cluster can be found here :ref:`quarkdbconf`.\n\nThe newly created QuarkDB raft-journal directory for each of the instances can be deleted. The raft journal stored in ``/var/lib/quarkdb/convert/`` needs to be copied to the QuarkDB directory of the new instances in the cluster. For this operation, one can use simple *cp/scp*. Make sure that the configuration for all of the new QuarkDB instances is in **raft** mode and **NOT bulkmode**. At this point all the instances in the cluster can be started and the system should rapidly reach a stable configuration with one master and several slaves.\n\nFor further information see :ref:`quarkdbconf`.\n"
  },
  {
    "path": "doc/citrine/quickstart/setup_repo.rst",
    "content": ".. index::\n   single: Setup yum repositories\n\n.. _eos_base_setup_repos:\n\nSetup YUM Repository\n====================\n\n.. warning::\n   You have to add line \"exclude=xrootd*,libmicrohttp*\" in epel.repo and epel-testing.repo.repo . This will be removed in future.\n\nEOS Beryl-Aquamarine\n-------------------------------------------\n\nEOS Repo (eventually change el-7 to your platform: el-8 fc-34). Create file /etc/yum.repos.d/eos.repo with following content\n\n.. code-block:: text\n\n   [eos-aquamarine]\n   name=EOS 0.3 Version\n   baseurl=https://dss-ci-repo.web.cern.ch/dss-ci-repo/eos/aquamarine/tag/el-7/x86_64/\n   gpgcheck=0\n\nCreate file /etc/yum.repos.d/eos-dep.repo with following content\n\n.. code-block:: text\n\n   [eos-dep]\n   name=EOS 0.3 Dependencies\n   baseurl=https://dss-ci-repo.web.cern.ch/dss-ci-repo/eos/aquamarine-depend/el-7-x86_64/\n   gpgcheck=0\n\n\nEOS Citrine\n-------------------------------------------\n\nXRootD Repo (eventually change el-7 to your platform: el-8 fc-34). Create file /etc/yum.repos.d/xrootd.repo with following content\n\n.. code-block:: text\n\n  [xrootd-stable]\n  name=XRootD Stable repository\n  baseurl=http://xrootd.org/binaries/stable/slc/7/$basearch http://xrootd.cern.ch/sw/repos/stable/slc/7/$basearch\n  gpgcheck=1\n  enabled=1\n  protect=0\n  gpgkey=http://xrootd.cern.ch/sw/releases/RPM-GPG-KEY.txt\n\nEOS Repo (eventually change el-7 to your platform: el-8 fc-34). Create file /etc/yum.repos.d/eos.repo with following content\n\n.. code-block:: text\n\n   [eos-citrine]\n   name=EOS 4.0 Version\n   baseurl=https://storage-ci.web.cern.ch/storage-ci/eos/citrine/tag/el-7/x86_64/\n   gpgcheck=0\n\n   [eos-citrine-dep]\n   name=EOS 4.0 Dependencies\n   baseurl=https://storage-ci.web.cern.ch/storage-ci/eos/citrine-depend/el-7/x86_64/\n   gpgcheck=0\n\nEOS Diopside\n-------------------------------------------\n\n.. code-block:: text\n\n   [eos-diopside]\n   name=EOS 5 Version\n   baseurl=https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/el-7/x86_64/\n   gpgcheck=0\n\nCreate file /etc/yum.repos.d/eos-dep.repo with following content\n\n.. code-block:: text\n\n   [eos-dep]\n   name=EOS 5 Dependencies\n   baseurl=https://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-7/x86_64/\n   gpgcheck=0\n"
  },
  {
    "path": "doc/citrine/quickstart/uboxed.rst",
    "content": ".. index::\n   single: Uboxed\n\n.. _eos_base_uboxed:\n\n.. _uboxed: https://github.com/cernbox/uboxed\n\nScientific Services Installation: EOS, CERNBox, SWAN and CVMFS\n==============================================================\n\n.. image:: cernlogo.jpg\n   :scale: 35 %\n   :align: left\n\nWe have bundled a demonstration setup of four CERN developed cloud and analysis platform services called `UBoxed <https://github.com/cernbox/uboxed>`_. It encapsulates four components:\n\n- `EOS <http://eos.cern.ch>`_ - scalable storage platform with data, metadata and messaging server components\n- `CERNBox <https://cernbox.web.cern.ch>`_ - dropbox-like add-on for sync-and-share services on top of EOS\n- `SWAN <https://swan.web.cern.ch>`_ - service for web based interactive analysis with jupyter notebook interface\n- `CVMFS <https://cvmfs.web.cern.ch>`_ - CernVM file system - a scalable software distribution service\n\n.. image:: uboxed.jpg\n   :width: 530px\n   :align: center\n\nPreparation\n-----------\n\nThe setup scripts will install all required packages.\n\n.. note::\n   Make sure you have no other web server listening on the standard ports. Make sure you have at least 30GB of free space under ``/var/lib/docker/``.\n\n.. note::\n   The installation requires at least docker version 17.03 - if you have an older one we recommend to uninstall it let\n   the setup script take care of pulling a newer version.\n\n.. note::\n   In certain environments docker container cannot resolve external domain addresses because nameserver access to the default nameserver 8.8.8.8 is blocked. To fix this create a daemon configuration file ``etc/docker/daemon.json`` and restart the docker daemon\n   For the CERN DNS server that would be e.g.\n\n.. code-block:: bash\n\n   cat /etc/docker/daemon.json\n   {\n     \"dns\" : [\"137.138.17.5\", \"137.138.17.5\"]\n   }\n\n   // el7\n   systemctl restart docker\n\nQuick Setup\n-----------\n\nCheckout the `UBoxed <https://github.com/cernbox/uboxed>`_ project:\n\n.. code-block:: bash\n\n   git clone https://github.com/cernbox/uboxed\n   cd uboxed\n\nInstall Services\n++++++++++++++++\n\nThe platform dependent installation script will pull required software and install docker images for the four service components. The procedure is validated on CentOS 7 and Ubuntu platforms. The installation will take few minutes depending on your environment.\n\n.. code-block:: bash\n\n  // CentOS 7\n  ./SetupInstall-Centos7.sh\n\n  // Ubuntu\n  ./SetupInstall-Ubuntu.sh\n\nSetup and Initialize Services\n+++++++++++++++++++++++++++++\n\nThe setup host script will configure and start all four service components and their corresponding containers.\n\n.. code-block:: bash\n\n   ./SetupHost.sh\n\n\nRun a Self Test\n+++++++++++++++\n\n.. code-block:: bash\n\n   ./TestHost.sh\n\n\nConnect to your services\n++++++++++++++++++++++++\n\nOpen https://localhost in a local browser or connect to your docker host machine with with a remote browser and HTTPS. You will land on the **Uboxed** main page which directs you to documentation and how to try the individual services running in your container setup.\n\n\nStop Services\n-------------\n\nIf you started the self test container, first do:\n\n.. code-block:: bash\n\n   docker stop selftest\n   docker rm selftest\n\nTo stop all Uboxed services do:\n\n.. code-block:: bash\n\n   ./StopBox.sh\n\nCleanup docker images and volumes\n---------------------------------\n\nIf you want to remove all Uboxed images and volumes from your local docker installation, you do:\n\n.. warning::\n   This will delete all created user data!\n\n\n.. code-block:: bash\n\n   docker rmi cernbox cernboxgateway eos-controller eos-storage ldap swan_cvmfs swan_eos-fuse swan_jupyterhub selftest cernphsft/systemuser:v2.10 cern/cc7-base:20170920\n\n.. code-block:: bash\n\n   docker volume rm cernbox_shares_db ldap_config ldap_database eos-fst1 eos-fst1_userdata eos-fst2 eos-fst2_userdata eos-fst3 eos-fst3_userdata eos-fst4 eos-fst4_userdata eos-fst5 eos-fst5_userdata eos-fst6 eos-fst6_userdata eos-mgm eos-mq\n"
  },
  {
    "path": "doc/citrine/quickstart/ubuntu.rst",
    "content": ".. index::\n   single: Ubuntu\n\n.. _eos_ubuntu_install:\n\nDebian/Ubuntu installation\n==========================\n\nThe EOS client gets automatically built for recent Ubuntu releases,\ncurrently \"focal\" and \"jammy\"\n\nYou **might** need to have the following packages installed to setup the installation of eos client.\n\n.. code-block:: text\n\n    apt update && apt upgrade\n    apt install -y sudo lsb-release curl gnupg2 \n\n.. note::\n    You need to add the XRootD and EOS repositories to your ``/etc/apt/sources.list``.\n\n    .. code-block:: text\n\n        echo \"deb [arch=$(dpkg --print-architecture)] http://storage-ci.web.cern.ch/storage-ci/debian/xrootd/ $(lsb_release -cs) release\" | sudo tee -a /etc/apt/sources.list.d/cerneos-client.list > /dev/null\n        echo \"deb [arch=$(dpkg --print-architecture)] http://storage-ci.web.cern.ch/storage-ci/debian/eos/diopside/ $(lsb_release -cs) tag\" | sudo tee -a /etc/apt/sources.list.d/cerneos-client.list > /dev/null\n\n    The above snippet will automatically get \"arch\" and \"release\" information for your machine (otherwise, just change arch and distribution name as required).\n\n    e.g., for a \"amd64\" machine with ubuntu \"jammy\" that would be\n\n    .. code-block:: text\n\n        deb [arch=amd64] http://storage-ci.web.cern.ch/storage-ci/debian/xrootd/ jammy release\n        deb [arch=amd64] http://storage-ci.web.cern.ch/storage-ci/debian/eos/diopside/ jammy tag\n\n.. note::\n    Also, to avoid possible conflicts with other releases you need to version-lock xrootd dependency packages (this will we softened in future releases).\n\n    e.g, as of eos version 5.1.5, you need to version-lock xrootd to 5.5.1:\n\n    .. code-block:: text\n\n        echo -e \"Package: xrootd* libxrd* libxrootd*\\nPin: version 5.5.1\\nPin-Priority: 1000\" > /etc/apt/preferences.d/xrootd.pref\n\nInstall EOS client via apt\n--------------------------\n\nOnce the repository are properly configured, you can simply run\n\n.. code-block:: text\n   curl -sL http://storage-ci.web.cern.ch/storage-ci/storageci.key > /tmp/storageci.key\n   gpg --no-default-keyring --keyring ./storageci_keyring.gpg --import /tmp/storageci.key\n   gpg --no-default-keyring --keyring ./storageci_keyring.gpg --export | sudo tee /etc/apt/trusted.gpg.d/storageci.gpg > /dev/null\n   sudo apt update\n   sudo apt install eos-client eos-fusex\n\nIn case EOS access as filesystem is wanted, EOS-FUSEX needs then to be\nconfigured as per\nhttps://gitlab.cern.ch/dss/eos/-/blob/master/fusex/README.md\n"
  },
  {
    "path": "doc/citrine/quickstart/update_eos4to5.rst",
    "content": ".. index::\n   single: Update to EOS 5 from EOS 4\n\n.. _eos_base_update_eos4to5:\n\nUpdate from EOS 4 to EOS 5\n================\n\n.. warning::\n   Warning: With EOS5, the MGM is expecting to have its configuration in QuarkDB already, so, before starting the upgrade, it is really important to follow the instructions for exporting the file configuration into QuarkDB: :ref:`quarkdbconfig`.\n\n.. warning::\n   Before you start: please make sure you already run in HA mode, or that you start the upgrade process with the active MGM, otherwise you risk of running two active MGMs at the same time, with unknown consequences.\n\n.. warning:: \n   When using HA mode, there might still be an issue (< 5.0.17, April 2022) with FSTs losing symkeys (and by that capability to communicate with MGM) during switch over of the master to another headnode. We advise to run in single MGM mode only (i.e. switching off all other MGM processes on the other slave nodes of the headnode cluster until this issue gets resolved). \n\nThe recipe below is tailored to CERN EOS team puppet manifests, but contains general information useful for any EOS 4 to EOS 5 upgrade. \n\nAfter the above has been put in place, one needs to add the right repositories to the manifest:\n\n.. code-block:: text\n\n   eosserver::repo::repo_main_url: https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/testing/el-7/x86_64/\n   eosserver::repo::repo_deps_url: https://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-7/x86_64/\n\n\nPlease cross check the XrdHttp port configuration and mapping to be as in e.g.: `code/manifests/pilot/servers/ns.pp` (or LHCb).  In order to have both XrdHttp and libmicro working side by side (In xrootd 4 the http plugin is installed after the ofs is loaded, while in xrootd 5 the http plugin is installed after the xroot plugin and then the ofs layer will try start libmicro at which point libmiro may fail unless the ports are set correctly). In the sysconfig, you should have: \n\n\n.. code-block:: text\n\n   EOS_MGM_HTTP_PORT: 8443\n   xrd.protocol: XrdHttp:8444 libXrdHttp.so\n\n\nand in `code/manifests/pilot/servers/ns.pp`:\n\n\n.. code-block:: text\n\n  # Redirect 443 to 8444 for XrdHttp traffic\n  ['iptables', 'ip6tables'].each |Integer $index, String $provider| {\n    firewall { \"120-${index} redirect 443 to 8444 for XrdHttp traffic\":\n      chain    => 'PREROUTING',\n      table    => 'nat',\n      jump     => 'REDIRECT',\n      provider => $provider,\n      proto    => 'tcp',\n      dport    => 443,\n      toports  => 8444,\n    }\n  firewall { \"121-${index} redirect 443 to 8444 for XrdHttp local traffic\":\n      chain    => 'OUTPUT',\n      table    => 'nat',\n      jump     => 'REDIRECT',\n      provider => $provider,\n      outiface => 'lo',\n      proto    => 'tcp',\n      dport    => 443,\n      toports  => 8444,\n    }\n  }\n\n  # Redirect 8000 to 8443 for Nginx HTTP traffic\n  ['iptables', 'ip6tables'].each |Integer $index, String $provider| {\n    firewall { \"122-${index} redirect 8000 to 8443 for Nginx HTTP traffic\":\n      chain    => 'PREROUTING',\n      table    => 'nat',\n      jump     => 'REDIRECT',\n      provider => $provider,\n      proto    => 'tcp',\n      dport    => 8000,\n      toports  => 8443,\n    }\n  }\n\n\n\n\nImportant thing to notice is that the quarkdb library comes now with EOS itself `eos-quarkdb`, for this, one needs to have a line like the following one in the manifest which will uninstall old `quarkdb` package and install the new `eos-quarkdb`:  `hg_eos::private::ns_compact::eos5enabled: true`. If not present `eos-quarkdb` will be uninstalled ! This is why we first keep this flag false --> run puppet --> disable puppet --> install new EOS version with `eos-quarkdb` manually (based on eos dependencies) and finally change this flag to true in the manifest and enable puppet again once EOS 5 is actually running. This is to avoid that puppet removed the `quarkdb`/`eos-quarkdb` package while EOS 4/5 is running using it. \n\nThis means, first we change: \n\n.. code-block:: text\n\n   hg_eos::include::versionlock::eosversion: 4.8.74-1.el7.cern\n   hg_eos::include::versionlock::xrootversion: 4.12.8-1.el7\n\n\n\nto\n\n.. code-block:: text\n\n   hg_eos::include::versionlock::eosversion: 5.0.9-1.el7.cern\n   hg_eos::include::versionlock::xrootversion: 5.3.4-1.el7\n\n \nIn addition in servers.yaml (/ns.yaml) change these lines:\n\n\n.. code-block:: text\n\n   http.cadir: /etc/grid-security/certificates/\n   http.cert: /etc/grid-security/daemon//hostcert.pem\n   http.key:  /etc/grid-security/daemon/hostkey.pem\n   http.gridmap: /etc/grid-security/grid-mapfile\n   http.secxtractor: libXrdVoms.so\n   mgmofs.macaroonslib: libXrdMacaroons.so /opt/eos/lib64/libXrdAccSciTokens.so\n\n\nto (for versions < 5.0.16): \n\n.. code-block:: text\n\n   xrd.tls: /etc/grid-security/daemon/hostcert.pem /etc/grid-security/daemon/hostkey.pem\n   xrd.tlsca: certdir /etc/grid-security/certificates/\n   http.gridmap: /etc/grid-security/grid-mapfile\n   http.secxtractor: libXrdHttpVOMS.so\n   mgmofs.macaroonslib: libXrdMacaroons.so libEosAccSciTokens.so\n\n\n\nFor versions 5.0.16+:\n\n\n.. code-block:: text\n\n   xrd.tls: /etc/grid-security/daemon/hostcert.pem /etc/grid-security/daemon/hostkey.pem\n   xrd.tlsca: certdir /etc/grid-security/certificates/\n   http.gridmap: /etc/grid-security/grid-mapfile\n   http.secxtractor: libXrdHttpVOMS.so\n   mgmofs.macaroonslib: libXrdMacaroons.so libXrdAccSciTokens.so\n\n\n\nand make sure the library path states: \n\n.. code-block:: text\n\n   LD_LIBRARY_PATH: \"/opt/eos/xrootd/lib64/:$LD_LIBRARY_PATH\"\n\n\n\nOne need to have `/opt/eos/xrootd/lib64/` in `LD_LIBRARY_PATH` for the `libXrdMacaroons.so` and all the xrootd libs which are loaded when starting by the daemon and searched in the usual locations. On the other hand, e.g. `libEosAccSciTokens.so` is \nalready in `/usr/lib64/` by default since everything that we install from eos-server goes there.\n\nAnd in storage.yaml: \n\nFrom:\n\n.. code-block:: text\n   \n   http.cadir: /etc/grid-security/certificates/\n\n\n\nto\n\n\n.. code-block:: text\n\n   xrd.tls: /etc/grid-security/daemon/hostcert.pem /etc/grid-security/daemon/hostkey.pem\n   xrd.tlsca: certdir /etc/grid-security/certificates/\n\n\nRun puppet and then we disable puppet from running:\n\n\n.. code-block:: text\n\n   puppet agent -tv \n   puppet agent --disable 'MGM upgrade to EOS 5: avoiding removal of future eos-quarkdb package after upgrade to EOS 5'\n\n\n\nRemove few obsolete packaged replaced newly by eos dependencies automatically (this also prevents to pull xrootd4 packages for upgrades from epel which we do not want, the versionlock for xrootd packages can be removed entirely from our manifests later with the caviat of checking the xrootd path for all use-cases, for example for FED functionality of CMS one needs to update the xrootd binary location in the systemd script). \n\nThis is where the instance availability gets affected:\n\n\n.. code-block:: text\n   \n   yum remove xrdhttpvoms\n   yum remove eos-scitokens\n\n\nUpgrade `scitokens-cpp` package (will be having strict dependency in EOS releases > 5.0.19 where this should not be necessary to be done explicitly):\n\n\n.. code-block:: text\n\n   yum upgrade scitokens-cpp\n\n\nCheck that `eos-quarkdb` gets installed based on dependencies resolved in the last command:\n\n\n.. code-block:: text\n\n   yum upgrade \"eos-*\" \"xrootd-*\"\n\n\nUpdate puppet manifest again from: \n\n.. code-block:: text\n\n   hg_eos::private::ns_compact::eos5enabled: false\n\n\nto\n\n.. code-block:: text\n\n   hg_eos::private::ns_compact::eos5enabled: true\n\n\nAnd run puppet:\n\n\n.. code-block:: text\n\n   puppet agent --enable\n   puppet agent -tv \n\n\nCheck the service status and other usual checks\n\n.. code-block:: text\n\n   systemctl status eos@*\n   systemctl status xrootd@quarkdb\n   rpm -qa | grep eos\n   rpm -qa | grep xroot\n\n\n\nOne needs to run `yum reinstall eos-grpc` on all headnodes and FSTs before proceeding with the usual procedure.\n\n\n"
  },
  {
    "path": "doc/citrine/quickstart.rst",
    "content": ".. _quickstart:\n\nQuickstart Guide\n===============================\n\n.. image:: start.jpg\n   :height: 100px\n   :width: 100px\n   :align: left\n   :target: quickstart.html\n \nThere are two easy ways to get an EOS instance installed. The first option is to use a dockerized demo installation - possibly on Kubernetes - which installs either a standalone EOS storage system or the scientific service bundle made of EOS, SWAN, CERNBox and CVMFS.\n\n.. toctree::\n   :maxdepth: 2\n\n   quickstart/docker_image\n   quickstart/kubernetes\n   quickstart/boxed\n\nThe `conventional` service deployment is done via YUM and explained here.\n\n.. toctree::\n   :maxdepth: 2\n\n   quickstart/setup_repo\n   quickstart/install\n   quickstart/update_eos4to5\n   quickstart/ubuntu\n   quickstart/client/configure\n   quickstart/admin/configure\n   quickstart/ns_quarkdb\n   quickstart/uboxed\n"
  },
  {
    "path": "doc/citrine/releases/amber.rst",
    "content": ":orphan:\n\n.. highlight:: rst\n\n.. index::\n   single: Amber\n\nAmber\n========\n\n``Lifetime: 2010-2013``\nThe **Amber** release was the first production version of EOS. It has been used\nsince end of 2010 until 2013.\n\n"
  },
  {
    "path": "doc/citrine/releases/beryl-release.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Beryl(-Aquamarine)-Release\n\n\nBeryl Release Notes\n===================\n\n``V0.3.270 Aquamarine``\n=======================\n\nNew Features\n============\n\n- MGM: add the 'eos fusex' interface and new FUSE client server side support (beta status - be careful with using this)\n- MGM/CONSOLE: add new 'access allow|unallow|ban domain <domain>'\n- NS: add hopscotch map/hash\n\nBug Fixes\n+++++++++\n\n- NS: use murmurhash as string hash function\n- COMMON: fix dead-lock im common/Mapping.cc\n\n``V0.3.268 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM: Mask the block checksum for draining and balancing when there is a layout\n       requesting blockchecksum for replica files. This was blocking all draining,\n       balancing or conversion jobs.\n\n``V0.3.267 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- AUTH: Set the ZMQ_LINGER option on the socket so that messages are not retransmitted\n- NS: add missing initialization of pData leading to random compaction crashes/failures\n- MGM: fix race in mkdir which could return EEXIST\n- MGM: fix race in rm\n- FUSE: fix memory-leak when read-ahead gets disabled during an open/read/close sequence\n\nImprovement\n+++++++++++\n\n- MGM: Return ENETUNREACH in case no diskservers are available (implies different client behavior)\n- MGM: allow recursive deletes for the http bridge using XrdOfs::remdir with ?mgm.option=r\n- MGM: add two new space variables to modify scheduler behaviour\n       \"space.scheduler.skip.overloaded=off\" - by default we don't skip anymore overloaded eth-out nodes)\n       \"space.min.weight=0.1\" - the minimum probability to select an disk or eth-out overloaded node\n- MGM: Collect response time statistics for the authentication front-ends\n- MGM: make the recycle bin work with symbolic links\n\n\n``V0.3.266 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM: avoid recreating block-xs files in balancing and draining due to wrong mask used\n- MGM: avoid increasing number of replicas when balancing very empty groups\n- AUTH: Avoid replay of requests for ZMQ sockets which are deleted. This avoid the 0-size\n  files in the namespace bug.\n\n``V0.3.265 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- Fix issue in EOSATLAS where files where disappearing from the namespace after being confirmed\n  to the client. This is correlated which exceptionally long scheduling times (~ 5min). This in\n  turn is due to the scheduling not finding a suitable node to place the file. When this happens\n  the default XRootD client will try to recover the initial open requests and this leads to a\n  race condition.\n- [EOS-1948] - FST crash with \"terminate called after throwing an instance of 'std::bad_alloc'\"\n- [EOS-1949] - Strange correlated crash in EOSATLAS\n\nImprovement\n+++++++++++\n- [EOS-1947] - Improve error message when trying to delete a directory attached to a quota node\n\n\n``V0.3.264 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- [EOS-1936] - EOS ATLAS lost file due to balancing\n- ARCHIVE: Fix archive endpoint which was constructed only if the MGM node was a master.\n           This approach fails when we have a master slave failover as we never set up\n           the archive endpoint for the slave. Use the same ZMQ contect for both the\n           archive and authentication services.\n- FUSE: Make configurable the maximum number of retries in case a synchronous\n        open operation fails.\n- DOC: update documentatino of wfe's\n\n\n``V0.3.263 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FST: re-establish 2nd path of 'deleteOnClose' functionality broken since 0.3.295\n\n``V0.3.262 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM: fix computation of wake-up time for the recycle bin - old code slept too long before waking up\n\n``V0.3.261 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FST: re-establish 'deleteOnClose' functionality broken since 0.3.295\n\n\n\n``V0.3.260 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM: call 'unlinkAllLocations' instead of 'clearLocations' when trying to re-place an empty already placed file, which didn't remove entries from the filesystem view leaving files forever undrainable\n\n``V0.3.259 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM: Don't drop a file if an FST calls a drop replica on a not committed replica\n\n\n``V0.3.258 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM: Protect if the namespace throws an exception without setting an error number in the readlink functionality\n\n\n``V0.3.257 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FST: protect against 0 pointer access if not local fmd is available for a scanned file\n\n``V0.3.256 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM/CONSOLE: revive 'file layout' command and 'find -layoutstripes'\n\n``V0.3.255 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM: treat attributes not prefixed as sys. like user. attributes (don't allow to set them if we are not the object owner)\n- MGM: many bug fixes/improvements in the AUTH service\n\n\n``V0.3.248 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM: fix recycle bin restore function to forbid to recycle files by fxid/pxid which are not in the recycle bin. Allow to explicitly restore a file or directory (they might overlap in the inode space) by prefixing the key with fxid: or pxid:\n\n\n``V0.3.246 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FUSE: fix shutdown crash by properly canceling/joining the cache cleaner thread\n- NS: fix gcc 4.4. compilation problem\n- MGM: reschedule empty files if current replicas are unavailable\n- MGM: add authentication front-end (backport from CITRINE)\n\n\n``V0.3.244 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FST: don't block Fmd access for an uninitialized filecxerror value (after Resync was called and filecxerror=-1)\n\n\n``V0.3.243 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- NS: fix memory allocation bug in Buffer class\n\n\n``V0.3.242 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FST: fix logical error when to call auto-repair (don't call it for unregsistered files)\n- FUSE: fix double response when returning entries from internal directory cache\n- MGM: fix protection when listing too large recycle bins with 'recycle ls' (> 1Gb output)\n\n``V0.3.241 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FUSE: fix memory leak in opendir function not cleaning dirbuf struct\n\n``V0.3.240 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FST: implement fdellocate function for non-XFS detected filesystems (which used posix_fallocate)\n\n``V0.3.239 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- NS: fix resolution of multiple ../ path changes like ../../XYZ\n- COMMON: fix resolution of multipeo ../ path changes like /X/Y/Z/../../Z\n\n``V0.3.238 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FST: avoid SEGV during startup when calling RemoveGhostEntries (.eosscan exists on data path)\n\n``V0.3.237 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- NS: fix slave follower attachment issue leading to invisible files\n- MGM: fix the logic when to show a slave as booted\n\nNew Feature\n+++++++++++\n\n- NS: add 'pending' counter to show if there are updates on the slave, which cannot be attached\n- NS: show follower progress during the initial scan phase and not only after\n\n\n``V0.3.236 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- NS: set 'pData' pointer to 0 in munmap function to switch back to traditional read function\n\n``V0.3.235 Aquamarine``\n=======================\n\nNew Feature\n+++++++++++\n\n- NS: compile with devtoolset-2 on SLC6\n- NS: make part of boot process parallel (gain 3-6x in boottime) [ enable with export EOS_NS_BOOT_PARALLEL=1 ]\n- NS: mmap changelog files during first scan phase to avoid performance limitation by too many syscalls [ disable with export EOS_NS_BOOT_NOMMAP=1 ]\n- NS: implement pread function for namespace file following using read-ahead caching to avoid too many syscalls\n- NS: allow to disable CRC32 on boot (e.g. when using BTRS/ZFS) [ enable with export EOS_NS_BOOT_NORCRC32=1 ]\n- NS: use murmurhash3 for the main flat indexes avoiding serious performance degradation for high id's in google::dense_hash_map\n- NS: make treesize and tree modification time atomic variables if gcc >=4.8\n- FST: limit 'file open for writing' messages in Verify to once per minute\n- FST: limit 'writer error' message to only once per open/write/close file sequence\n- COMMON: add generic lambda function to run parallel for loops Parallel::For ()\n- UTILS: add yum packages to install devtoolset-2 to compile with gcc 4.8\n\nNew documentation of namespace variables: http://eos.readthedocs.io/en/latest/configuration/namespace.html\n\nBug Fixes\n+++++++++\n\n- NS: fix various bugs in slave follower losing directories, not showing proper treesize aso.\n- NS: start 'eossync' in slave2master transition\n- MGM: avoid Converter::ResetMasterJobs on slaves\n- MGM: don't run slaves in auto-repair mode when scanning the changelog file\n- FUSE: fix 'bad address' errors and show proper 'permission denied' messages when a client has not credential or is forbidden to talk to certain EOS instances\n- CONSOLE: fix 'treesize' output in 'fileinfo'\n\n\n``V0.3.234 Aquamarine``\n=======================\n\n- NS: avoid that the main indexes ever shrink\n- MGM: don't follow symlinks when stating recycle bin entries\n- FUSE/FST: add read-ahead cache consistency to FUSE client and make kernel cache invalidation work properly\n- FST: allow to define the network speed via an environment variable since 'ip route' and ethtool are not equivalent on SLC6/EL7\n\n``V0.3.233 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: remove falsely committed debug return statement disabling stale cache file detection from previous fix\n- FST: extending '.eosscan' functionality to cleanup ghost entries which are neither on disk or memory but can normally only be removed by wiping the local database and rebuild from scratch\n\n``V0.3.232 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix stale kernel cache contents problem if file contents changed but not the file size\n- FUSE: fix stale directory/file attributes for lookup/getattr of cached files/directories (apply attr lifetime)\n- FST: avoid to try to call forever an old master in commit/drop calls which specified an explicit call-back manager - use the broadcasted MGM name after 60 attempts\n\n``V0.3.231 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: stall/redirect access by fid:fxid before trying to translate to a real path (can crash boot procedure)\n\n``V0.3.230 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: deal with unregistered files with the correct replica count in the same way as with orphans when .eosscan is enabled on an FST mount\n\n>>>>>>> beryl_aquamarine\n``V0.3.229 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix bug introduced with retry 'query' mechanism doing double deletes\n- FUSE: fix bug in AuthId manager doing a double lock when session id != process id\n- FUSE: set the link count for files/links to 1 to make applications like gzip work\n- MGM: fix subtree accounting in the slave follower\n- FST: add an .eosorphans directory to each FST mount point and allow to isolate orphans into this directory by creating a tag file  <mnt>/.eosscan. The .eosscan file removes any smearing and sleep time between scans. The original location is tagged as an extended attribute after during the move\n\n``V0.3.228 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix locking strategy bug in the proc cache usage where entries were not locked anymore when used\n\n``V0.3.227 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: fix failover procedure: slave stays forever booting until master sees a change\n- MGM: safte in failover procedure: don't failover if the slave did not follow the changelog to the end\n- MGM: show bytes left to follow in 'ns master' on slave\n- NS: avoid infinite loop in slave follower when looking for a quota node\n- FUSE: fix bug leaving files open when a file was inline repaired\n- DAV: fix webdav bug when a symbolic link is present in a directory listing leading to an error response\n- MGM: fix 'access rm' implementation to remove ENOENT and ENONET redirection\n- DAV: take into account sys.owner.auth when looking for webdav quota\n\n``V0.3.226 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- COMMON: make ShellExecutor thread/interrupt safe\n- FST: reset checksum error flags also after correct 'verify -checksum'\n- FUSE: fix ping timeouts and dependencies, allow sss mounts\n- NS: remove ns file archiving process by default in SLAVE->MASTER transition and fix too early enabling of the namespace for write\n\nNew Feature\n+++++++++++\n\n- MGM: add REST API for 'fileinfo'\n\n\n``V0.3.225 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: fix vulnerability for http GET of '/./' via eos::common::Path\n- COMMON: make '/' the full and parent path of /. /.. /./ /../\n\n``V0.3.224 Aquamarine``\n=======================\n\nNew Feature\n+++++++++++\n\n- FST: allow 'eos.checksum=ignore' for file uploads to avoid checksum computation\n- FST: fix 'eoscp -a' and add 'eoscp -A <offset>' to upload a file to a certain offset\n\n``V0.3.223 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix foreground option for eosd\n- FUSE: shard proc cache to keep memory footprint low for high MAX_PID settings and run AuthId cleanup every 5 minutes\n- FUSE: don't pick up root credentials inside eosd\n- COMMON: fix syslog logging interface using wrong argument list\n\n\n``V0.3.222 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MQ: fix race condition multiplexed/non-multiplexed set\n- FST: fix race condition in filesystem mutex map\n- FUSE: fix wrong default values for query retry sleep time\n- MGM: protect scheduling against scheduling in a space without filesystems\n- MGM: fix 'fileinfo by inode'\n\nNew Feature\n+++++++++++\n\n- FUSE: use proc map sharding to avoid too large mutex maps for machines with high max proc ID settings\n- FUSE: allow to run eosd as a foreground process when specified in /etc/sysconfig/eos\n\n``V0.3.221 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: don't hold (timeout) HTTP requests during compacting\n- FST: fix mutex race condition\n- FUSE: fix memory issues and remove unreachable code\n- FUSE: avoid SEGV on empty XRootD buffer responses\n- FUSE: restructure read-buffer handling and clean-up not used read-buffers in CacheCleanup function - avois significant memory leaking under parallel access\n\n``V0.3.220 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: avoid triggering recreation of xsmap files during draining/balancing for replica layouts\n- FUSE/FST: fix 'critical' bug in async write implementation not collecting async writes errors when flush is called and file exceeds the cache size\n- FUSE: always wait for asynchronous writes in case of file modifications\n\n\nFeature\n+++++++\n\n- COMMON: allow to duplicate EOS log to syslog via export EOS_LOG_SYSLOG=1\n\n\nBug Fix\n+++++++\n\n- COMMON/FUSE: fix base64 encoding of not-string buffers\n- FUSE: fix memory leak in proc cache\n- FUSE: use FORKHANDLER in XrdCl and check mgm before forking the FUSE daemon\n- FUSE: fix shutdown behaviour after MGM ping failure\n- MGM: fix 'fileinfo' for high inode numbers\n\n\n``V0.3.218 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix a bug in auth cache when sid process of a calling pid does not exist anymore\n\n\n``V0.3.217 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: cleanup checksum error flags after \"file verify -checksum\"\n\n``V0.3.216 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: fix OC upload complete condition\n\n\n``V0.3.215 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- ETC: fix typoe introduced by MALLOC_CONF_VARNAME\n\n``V0.3.214 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: fix geobalancer default variable names (were geotagbalancer)\n\nNew Feature\n+++++++++++\n\n- MGM: bounce checksum & open requests without an attached replica to an alive master\n- MGM: add heap profiler\n\n``V0.3.213 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: Fix condition in ShellExecutor leading to deadlock in MGM startup\n- TEST: Adapt the eos-instance test give the modifications done to the default \"replica\" layout i.e. drop of the blockchecksum\n\n``V0.3.212 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: Fix race condition in TPC implementation\n- FST: convert some critical errors to warnings\n- COMMON: add an alarm timer for the ShellExecutor forked process to die on its own if the parent process disappears\n- MGM: fix miscounting quote bug when deleteOnClose is triggered\n- MGM: fix bug introduced by commit 089803efe0b0cde882ed655788985eb166eb4546  triggering a SEGV under load due to out-of-lock access\n- MGM: fix balancer bug which was in case of N full and M empty boxes balancing the M times more from first box instead of all N equally\n\n\nNew Feature\n+++++++++++\n\n- FST: add a connection pool to avoid bottleneck due to slow close blocking other opens to the same target FST - the connection pool size is by default 64 and can be changed by the variable EOS_FST_XRDIO_CONNECTION_POOL_SIZE\n- MGM: add an environment variable allowing read-write-modify to all all users on MGM for RAIN layouts (define EOS_ALLOW_RAIN_RWM)\n- MGM: relax OC chunked upload order restriction - order is irrelevant and retries but the last chunk terminates an upload\n\n``V0.3.211 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: don't set the truncate flag in OpenAsync to avoid increment of inode when async open is done\n- NS: fix copy constructor not duplicationg the pTreeSize variable\n\n``V0.3.210 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: fix 'ScanDir' functionality to deal properly with files which get opened during a scan for update and don't flag them as checksum error files\n- FST: ignore flagged checksum errors when updating a file\n\n``V0.3.209 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: move from passive cache expiration to active write-back cache cleaen-up (by thread) - the maximum allowed default size of wb-file caches is 512 MB\n- MGM: fix acl check if client sends base64 encoded acl values (as EOS 4.X does)\n- FST: fix memory and fd leak triggered by deleteOnClose on files with block checksums\n- FST: silence \"probably already unlinked\" message in XrdFstOss::Unlink\n\n``V0.3.208 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: enable blockchecksums againf for plain layouts if there is an .xsmap file - this avoids bogus errors and still checks the blockchecksum files if they are available\n- CONSOLE: adjust the console command to not add block checksum for \"attr set default=replica\"\n\n``V0.3.207 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: put back the posix_fallocate call since xfs pre-allocation slows down when a truncate is called and produces contention in the Oss::Close handle where xrootd uses a global lock\n- COMMON: disable block checksums for plain and replica layouts by force\n\n``V0.3.206 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: avoid bogus mgm/disk size errors due to still uninitialized disk size values\n\n``V0.3.205 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: avoid double deletion in Fmd code\n\n``V0.3.204 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: protect accessing a 0 pointer in opendir\n- FUSE: store all invisible items in the FUSE stat cache although they are not visible in the listing\n\n``V0.3.203 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: refactor opendir/readdir/closedir consistency and directory caching\n\n\n``V0.3.202 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: fix return code handling of xfs pre-allocation in CheckSum.cc\n\n\n``V0.3.201 Aquamarine``\n=======================\n\n\nBug Fix\n+++++++\n\n- FST: always reset the disk checksum in the meta data db when a file has been modified\n- FST: consider only flagged file/blockchecksum errors to prevent to return meta data objects\n- FST: set /var partition RO threshold to 95% full\n- FUSE: swap lines to avoid valgrind warning about use after erase\n- MGM: return json responses with json response tag\n- DOC: fix commit message for release number\n\n\n``V0.3.200 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix out of lock scope iterator used in error message\n- FUSE: give no validity to attributes coming as fuse-replies to a create call (since uid/gid can be different on MGM side from uid/gid of the caller)\n- FST: prevent deleteOnClose when clients retried an open e.g. open | open | write| close (the XRootD client might replay an open with a new connection and this can lead to file loss)\n- FST: switch filesystems to RO when /var partition is 90% full\n- FST: make deleteOnClose a warning on client disconnect\n\n``V0.3.199 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix wrong lock scope when readdir buffers are retrieved\n\n``V0.3.198 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- HTTP: drop FileClose handler to avoid SEGVs due to interference between FileClose and Complete handler\n- NS: avoid failing compaction when a slave was promoted to be master due to virtual root entry with 0 offset in changelog file\n- ARCHIVE: use MGM alias to reference instances in archives\n- FST: protect against 0-size buffer response bug in XRootD 3.3.6\n\nNew Feature\n+++++++++++\n\n- MGM: add some more information about the currently in-use file/container-id and the id's created since last boot\n- MGM: allow update of 0-size RAIN files to allow lazy-open with RAIN layouts\n\n\n``V0.3.197 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: return correct (also overlaid) mode bits after file creation\n\n``V0.3.196 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- NS: fix slave follower getQuotaNode exception preventing quota accounting on slave\n- FUSE: swap unlock and pool-fd push (which is protected by the same file abstraction rwmutex)\n\n\nNew Feature\n+++++++++++\n\n- MGM: add 'Treesize' to the output of 'file info'\n\n``V0.3.195 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix possible size inconsistency after utimes call storing size=0 in kernel cache\n\nNew Features\n++++++++++++\n\n- TEST: adding eos-fuse-test suite to eos-test RPM (use eos-fuse-test to display individual test categories)\n\n``V0.3.194 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix truncate bug putting a stall file size after truncate into the kernel cache\n\nNew Features\n++++++++++++\n\n- TEST: add test for truncate bug to eos-fuse-test\n\n``V0.3.193 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: add monitoring switch to space,group status function\n- MGM: draing mutex fix and fix double unlock when restarting a drain job\n- MGM: fixes in JSON formatting, reencoding of non-http friendly tags/letters like <>?@\n- MGM: fix possible lock problem in 'eos find' mgm implementation\n- MGM: fix memory leak in fs.Ping (xrootd3 mem leak)\n- MGM: fix bug when revoking sudo privileges\n- MGM: decode all base64 prefixed attr values before storing in attr_set\n- MGM: return base64 encoded attributes in attr_get when called via FUSE\n- NS:  handled uncatched exception in the slave follower when looking for a quota node\n- FST: wait for pending async requests in the close method\n- SPEC: remove directory creation scripting from spec files\n- FUSE: fix bug in 'setxattr' function\n- FUSE: protect against missing response buffer\n\n``V0.3.192 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: fix regression from bug fix in 191\n- FUSE: fix getxattr return value as ENOATTR if attribute not found\n\n\n``V0.3.191 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: honour (rare) xrootd XOFF send on open to retry after <n> seconds to open a file due to contention on xrootd tables\n\n``V0.3.190 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix memory leak when returning readdir from in-memory cache\n\nNew Features\n++++++++++++\n\n- FUSE: update SELINUX policies\n- FUSE: create /var/run/eosd and /var/log/eos/fuse/ directories in eos-fuse-core\n- MGM: allow to change the find query limitations (by default 100k/50k files/dirs) via the 'access' interface. See 'eos access -h'.\n\n``V0.3.189 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- MGM: add JSONP response object format when 'callback=...' is specified in a query URL\n\n``V0.3.188 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: wake up the recycle thread if there is a change of the recycle policy\n- MGM: don't cache unresolved uid/gid with their number, since sssd translation is not 100% successful\n- MGM: allow underscore in user/group names (ACL parsing)\n- MGM: forward errors from find (like query limitation etc.)\n- MGM: don't keep the Stat mutex when translating uid/gids\n- MGM: fix slave follower bug when moving a subtree\n- MGM: fix recursive accounting on slave\n- MGM: resolve symlink when opening a file via non-FUSE clients to resolve to the right quota node\n- MGM: fix bug in creation of shared URLs after introduction of URL encoding\n- CONSOLE: fix recursive copy bug in eos cp\n\nNew Features\n++++++++++++\n\n- FUSE: refactor FUSE rpms into eos-fuse-core & eos-fuse-sysv. The core has only mount scripts and not sysv scripts anymore\n- FUSE: add SELINUX policies in the eos-fuse-core postinstall script\n- MGM: add JSON output formatting for all REST commands\n\nDocumentation\n+++++++++++++\n\n- WFE: document workflow engine\n- REST: document rest api for space, node, group and fs calls\n\n``V0.3.187 Aquamarine``\n=======================\n\n- FUSE: forward correct errno from XrdCl::Open failures\n- FUSE: fix wrong map deletion when unlink/rmdir fails (visible with rsync  --delay-updates)\n- FUSE: add mknod implementation to allow kernel NFS exports\n- MGM: fix SEGV when looking at the changelog file\n\n``V0.3.186 Aquamarine``\n=======================\n\n- FUSE: fix inode mapping after repair and follow new inode\n- FUSE: avoid to force a file open for a utimes setattr call\n- MGM: fix 'map' interface to work with encoded FUSE paths\n- CONSOLE: update 'fs dropdeletion' and deprecate 'fs dropfiles' and MGM redirection behaviour for 'fs dropdeletion'\n\n``V0.3.185 Aquamarine``\n=======================\n\n- FST: correct error codes in eoscp to flag target errors in tranfser queue jobs\n- MGM: allow 'xrd.*' to be present in proc commands (used by FUSE repair)\n\n\n``V0.3.184 Aquamarine``\n=======================\n\n- FUSE: report 1k as maximum file name length in statvfs\n- FUSE: don't trigger recovery if a file is deleted before it is actually written\n- MGM: update directory mtime when a replica drop leads to a file remove\n- FST: don't give a checksum error if a not yet fully created file is read by a second FUSE client\n\n\n\n``V0.3.183 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix lock bug visible since 0.3.182 in the WriteBack cache as a dead-lock (responsible for many previous changes)\n- FUSE: close inconsistent mtime window present during release file (vim editor problem)\n\n``V0.3.182 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix bug introduced in 0.3.181 to force creation of a file before a read open can proceed\n- FUSE: use a standard mutex instead of a rw mutex to protect wb cache map\n- FUSE: fix open(update) wrong mtime behaviour observed when using vim ona a file without local caps\n- COMMON: fix performance relevant ShellCmd::Wait() function to use exponential backoff starting at 1ms to discover if a subprocess has terminated. This has a drastic effect on balancing and draining jobs which was limited to 1Hz due to this implementation\n- FST: when running multiple FST instances store the eoscp log for each instance in their private log directory\n- FST: fix missing tpcClose when a target TPC operation had been terminated\n- MGM: use conditional/scoped lock monitor to avoid any path in the code where the quota mutex could stay read-locked and no new quota node can be created/listed\n\n\nNew Features\n++++++++++++\n\n- MGM: by default don't do a risk analysis for 'fs status' since it can take significant amount of time when millions of files are on a filesystem - previous behaviour using 'fs status -r'\n- MGM: extend 'schedule2balance' call to directly return a balance job to the FST instead of sending it through the asynchronous queue (FST equivalent part is still not committed)\n- FUSE: add an environment variable to simulate slow backend behaviour in the asynchronous part of FUSE (EOS_FUSE_LAZY_LAG=<ms>)\n\n``V0.3.181 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: fix double unlock leading to an abort if a file checksum was found\n- FUSE: fix race condition in locking scheme when adding pieces to the writeback cache\n- FUSE: avoid several memory leaks induced by open/write/close/delete sequences\n- FUSE: avoid possible order inversion of Open[create] file / Open[read] file\n\n``V0.3.180 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: fix particular geo scheduling case which could return ENOSPACE\n- MGM: avoid dead-lock in SetQuota calls\n\n``V0.3.179 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix SEGV introduced by XrdIo memory leak fix in 0.3.177\n\n``V0.3.178 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: fix geotag scheduling when exact switch is enabled/disabled (try always first with exact geo matching, then relax the requirement)\n- FUSE: fix SEGV on krb5 recovery redirection\n- COMMON: fix eternal loop for esoteric .././.././../ path combinations\n\n``V0.3.177 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: reduce lock contention on Sqlite mutex\n- FST: use one Sqlite lock per filestem instead of a global lock for all filesystems\n- ETC: fix use of default mount dir in eosd scripts\n- FUSE: fix invalid modtime calculation disabling directory caching\n- FUSE: fix memory leak in XrdIo when a file was deleted before it was ever opened\n- HTTP: add mutex to avoid parallel loading of grid-map file and possible memory SEGV when parsing\n- NAMESPACE: don't cancel follower threads on the Slave in active code (avoids exceptions on pthread_join)\n\nNew Feature\n+++++++++++\n\n- FUSE: add support to compile eosd3 using libfuse3\n\n``V0.3.176 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: unset KRB5CCNAME only when run as a shared fuse mount ( prevented krb5 for single user mounts via 'eos fuse mount'\n- FUSE: fix XRootD 3.3.6 memory leaks in every synchronous call (AnyObject leak) - not present anymore in XRootD 4.X\n- FUSE: add clean-up to filesystem destructor to clean valgrind reports\n- MGM: remove tight lock on namespace boot in HTTP service\n\nNew Feature\n+++++++++++\n\n- FUSE: by default hide all special files from version/atomic/backup - enable with env EOS_FUSE_SHOW_SPECIAL_FILES=1\n- FUSE: by default configure a 64M shared write-back cache for shared and single-user mounts\n- FUSE: use a blocking flush if the write-back size is larger than the in-memory cache - in this case there is no recovery possible so it is better to see possible errors on the application layer via the flush call\n\n``V0.3.175 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix memory leaks and missing mutex - remove w-open tracking map\n\n``V0.3.174 Aquamarine``\n=======================\n\nNew Feature\n+++++++++++\n\n- FUSE: add 'restore' functionality which recovers file write errors on client side transparently if all the writes are still in the local in-memory cache\n- FUSE: add the option do do an asynchronous open after a lazy open call (by default disabled - still WIP)\n\nBug Fix\n+++++++\n\n- MGM: print fid as decimal number in 'file info'\n- MGM: redirect new 'Redirect' fuse call on the MGM always to a master\n- MGM: keep the replica chain in the same order for FUSE updates (cl=>rep1=>rep2) doing identical scheduling\n- FST: fix 'tried' CGI to append to a list and not overwrite previous tried add-ons\n\n``V0.3.173 Aquamarine``\n\nNew Feature\n+++++++++++\n\n- FUSE: deal properly with security/system.posix_acl attributes in (cp -a errors)\n- FUSE: reduce significantly memory footprint for tight file creation loops - default in-memory cache reduced from 1M to 4k\n- FUSE: cleanup in-memory caches of deleted files immediately\n- FUSE: use asynchronous writes in release call and gain 25% performance\n- FUSE: prefer readlocks when submitting a piece to the wb-cache and refresh iterator if mutex upgrade from r->w is needed\n- WebDAV: return logical bytes as quota\n- RPMS: add dependency for JEMALLOC at runtime for eos-server and eos-fuse rpms\n\nBug Fix\n+++++++\n\n- FUSE: fix bug bypassing the directory cache all the time when doing ls,ls -l ...\n- FUSE: detect meta data updates on directories and refresh the client cache accordingly\n\n``V0.3.172 Aquamarine``\n\nNew Feature\n+++++++++++\n\n- reduce default write-back page size to 256k (was 4M)\n- make the page size configurable via env EOS_FUSE_CACHE_PAGE_SIZE (in bytes)\n\n\n``V0.3.171 Aquamarine``\n\nBug Fixes\n+++++++++\n\n- fix 'd' via ACL for OC access\n\n``V0.3.170 Aquamarine``\n=======================\n\nNew Feature\n+++++++++++\n\n- remove 'chown -R' on FST partitions which was used to compensate a bug visible in 0.3.137 since it might introduce large unnecessary boot times when updating from versions < 0.3.137\n\n``V0.3.169 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix exclusive lock held around fallocate delaying all writes and opens during an fallocate call (FST)\n- fix SEGV in readlink call when an errno is returned (FUSE)\n- fix OC access permission string to include writable for ACL shared directories (MGM)\n- fix race condition when FUSE write-back cache is full - JIRA EOS-1455\n- don't report symlinks as zero replica files\n- fix SEGV in enforced geo placement where no location is available\n\nNew Features\n++++++++++++\n\n- add new FUSE config flags to enable automatic repair of a broken replica if one is still readable - default enabled until 256MB files\n  - export EOS_FUSE_INLINE_REPAIR=1\n  - export EOS_FUSE_MAX_INLINE_REPAIR_SIZE=268435456\n- bypass authentication requirements for 'eos version' call (e.g. when getting the supported features)\n- add IO error simulation for open on FSTs\n\n``V0.3.168 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- initialize container mtime by default with ctime if not defined\n\n\n``V0.3.167 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- add responses for custom namespaces (for new Owncloud clients) HTTP\n- fix race condition for stat after close in FUSE\n- gcc 6.0 warnings\n- don't version module libraries anymore (as done by newer cmake)\n\nNew Features\n++++++++++++\n\n- introduction of 'sys.mask' attribute to apply a default mask to all chmod calls on directories (attribute disables !m in acls)\n\n``V0.3.166 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix 'dumpmd' response for files with empty checksum, which cannot be parsed by the FST\n- convert r=>w lock in FUSE (dir_cache_sync) to fix crashes in readdir\n- protect 'recycle ls' to exceed string size limitation when listing millions of entries - stops at 1GB of console output and displays warning message\n\nNew Features\n++++++++++++\n\n- by default use FUSE in async mode e.g. fsync is not a blocking call - enable sync behaviour via sysconfig EOS_FUSE_SYNC=1\n- by default use new FST fast boot option and disable WAL journaling of SQLITE db - the pedantic boot behaviour can be enforced via sysconfig EOS_FST_NO_FAST_BOOT=1\n- add 'service eos clean fst' and 'service eos resync fst' to enforce a start behaviour (no resync or resync)\n\n``V0.3.165 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix race condition on google_hash_map in FUSE leading\n\nNew Features\n++++++++++++\n\n- don't set/get xattr with \"security.*' keys in FUSE\n\n``V0.3.164 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix serious bug when moving directory subtress (as used by recycle bin) attaching moved trees after a reboot to the source location\n\n.. warning:: it is highly recommended to update the MGM, if possible purge all recursive deletes before reboot from the recycling bin\n\n``V0.3.163 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix dual side/dual fs exact placement\n- fix 'eosd status' script\n\n``V0.3.162 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- mask all special mode bits in FUSE (was breaking writes via CIFS server if no group-w bit set)\n- fix missing lock in TPC handling function in storage nodes\n- apply removed sudoer privileged in running instance\n\nNew Features\n++++++++++++\n\n- add 'service eosd killall' command and fix 'service eosd condrestart'\n\n\n``V0.3.161 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix race condition originating in use of iterator outside locked section for setattr(utime)\n- fix check for encoding support in FUSE client\n\n``V0.3.160 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix file magic in various startup scripts\n- place (u)mount.eos in /sbin\n- fix eosd script and mount script to be compatible with autofs on EL6/7 and systemd\n- fix geo placement for minimal geo case of two sites/two filesystems and 1 replica\n\nNew Features\n++++++++++++\n\n- add new encoding feature allowing full support of all characters via FUSE\n- remove global locks around XrdCl calls in FUSE for better parallelism and less lock contention\n- add version/fsctl call to discover available (FUSE) features of an MGM service\n- add convenience RPMs to configure EOS repositories for YUM installation\n\n``V0.3.159 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix SEGV in directory rename in FUSE\n- fix read-after-write short-read for not aligned read crossing local-cache/remote border in FUSE\n- make '.' and '..' visible in FUSE (again)\n\nNew Features\n++++++++++++\n\n- find honours now also ACLs in all recursive directories\n\n``V0.3.158 Aquamarine``\n=======================\n\n- protect against failing inode reverse lookup\n\n``V0.3.157 Aquamarine``\n=======================\n\n- add mount scripts to eos-fuse RPM\n\n``V0.3.156 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- high speed directory listing in FUSE (enhanded protocol returning stat information with readdir - backward compatible)\n- changing ETAG definition for directories to ino(hex):mtime(s).mtime(ms)\n- allowing arbitrary remote path to local path mounting (no matching prefixes needed)\n- allow to give a mount directory to 'mount -t eos <instance> <local-dir>'\n- documentation for geotags and new fuse features added\n- add 'find --xurl' to get XROotD urls as output\n- refactor FUSE in pure C++\n- use only eosd for single user mounts and shared mounts (fix eosfsd grep in any operation script)\n- generate mtime timestamps locally\n- auto-detect LAZY open capability of mounted server\n\nBug Fixes\n+++++++++\n\n- fix single user mount 'eos fuse mount' prefix\n- removing deprecated env variables in FUSE\n- track open inodes to prevent publishing stall size information from directory/stat cache\n- fix 'mkdir -p' in CLI\n- fix sync time propagation in Commit call\n- fix '-h' behaviour of all shell commands\n- protect against namespace crash with 'file touch /'\n- fix sync time propagation in mkdir and setTMTime\n- fix rm level protection\n- don't report symbolic links a zero-replica files\n- fix SEGV in PIO mode when an error is returned in FUSE client\n- fix FUSE rename\n- fix FUSE utime/mtime behaviour\n- fix FUSE daemonize behaviour killing systemd on EL7\n\n``V0.3.155 Aquamarine``\n=======================\n\n.. warning:: The FUSE implementation in this release is broken in various places. The sync time propagation in this release is broken. Don't use this version in production on client and server side!\n\nBug Fixes\n+++++++++\n\n- fix FUSE memory leak\n- fix esod start-script typo\n- fix HTTP PropFind requests for owncloud - unencoded paths in PropFind request to check quota & access permissions\n\n``V0.3.154 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- disintiguish OC propfind and 'normal' propfind requests to report sync time or modification time of a directory\n- fix 409 ERROR for HTTP PUT on non-existent path\n- don't commit anymore mtime from FSTs for FUSE clients - let the FUSE client execute utime during close\n- encode mtime.tv_nsec in the XRootD stat responses (inside device id) to track mtime with ns precision on open files\n- protect plain-layout read-ahead mechanism with respect to size changing files\n- FUSE: implementation refactoring (will break mtime consistency when used against old instances)\n- => use negative stat cache of the kernel\n- => add temporary and size limited in-memory rw cache per file to avoid waiting for flush of not written out pieces\n- => add creator capability mechanism to assign local cache capability of a newly created file for a limited time to the local FUSE cache\n- => retrieve mtime in ns precision for wopen files from the FST. commit last mtime on FST to MGM in asynchronous close operation\n- => hide write latency completely in asynchronous write chain where open(MGM)=sync, open(FST1..X)=async, write(FST1)=async,flush=async,close=async\n- => print FUSE settings on startup into log file\n- => remove deprecated FUSE options, add new FUSE options to example files and verbose output on startup\n- => point an unconfigured FUSE target url to localhost instead of eosdev\n- => modify default values of FUSE configuration (enable lazy-open-w)\n\n``V0.3.153 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- console add 'rm -rF' allow only root to use the bypass of the recycling policy\n- console revert to use by default host+domain names and add a '-b,--brief' option to all fs,node,group commands to get short hostnames\n\n``V0.3.152 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- re-enable FUSE concurrent opens and close\n- fix FUSE lazy open and negative stat cache broken in the previous release\n- fix wrong timestamping of symlinks\n\n``V0.3.151 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- synchronize with CITRINE FUSE implementation\n\n``V0.3.150 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix wrong mount-prefix handling for deepness>1\n\n``V0.3.149 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- import the CITRINE FUSE implementation and build this one\n- making big writes and local mtime consistency the default behaviour in FUSE\n\n``V0.3.148 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- add progress report on TTY console for all boot steps and estimate of boot time\n- automatically store version in the recycle bin and allow to recall using 'recycle restore -r <key>'\n\nBug Fixes\n+++++++++\n\n- fix FUSE daemonize to work properly with autofs\n\n\n``V0.3.147 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- shorten hostnames (remove domain) in all view functions besides monitoring format\n- add support for multi-delegated proxy certificates\n\n``V0.3.146 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix http upload implementation for large body uploads\n- allow to disable block checksumming via opaque tag\n- use aggregation size in the WebDAV quota response and not the quota accounting\n- track file size to avoid FUSE write-cache flushing on stat and listing\n- merge no-quota-error in xrootd errors response into e-nospace to avoid the client reporting an io error\n\n``V0.3.145 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- add option to exclude all xattrs from being applied on the destination dirs by using the wildcard \"*\".\n- clean-up the python cmake modules and simplify the use of Python related variables\n- remove only the leading \"eos\" string when building the proc path for the MGM\n\n``V0.3.144 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- source sysconfig file inside MGM before running service scripts\n\n``V0.3.142 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- add service alias example in eos.example how to run with systemd\n\n``V0.3.141 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- don't ship fuse.conf on EL7 in eos-fuse RPM\n- fix reporting of subtree copying in 'eos cp'\n\n``V0.3.140 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix missing object in drain lock helper mutex\n- distinguish client and FST methods to prevent having FSTs calling a booting slave with namespace modifications\n- add min/maxfilesize check during the open function, to block too large uploads immediately\n\n``V0.3.139 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- automatically chown files on FST partitions once (to compensate to bug introduced in 0.3.137)\n- make the XRD stream timeout configurable and increase the default to 5 minutes\n\n``V0.3.138 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- allow to specify the network interface to monitor on the FST via environment variable\n- run the FST and MGM again as daemon/daemon and switch only the monitoring thread in ShellCmd to enable ptrace for all spawned sub commands\n\n``V0.3.137 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- don't scan partial read files when also if no blockchecksums are configured\n- fix recursive copy command allowing spaces in path names\n\n``V0.3.136 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- implement 'eos ls -lh' for readable sizes\n- add extended attributes on files\n- add 'file tag' command to manually set/remove locations\n- allow 'file injection' to upload contents into an existing file\n- add optional namespace subtree aggregation and introduce the concept of sync time\n- implement <oc::size> and <oc::permissions> in PROPFIND requests\n- run MGM/FST with effective user ID of root and filesystem ID of daemon/daemon\n\n\nBug Fixes\n+++++++++\n- avoid default auto-repair trigger if not configured\n- fix high system time bug in ShellCmd class\n- don't use fork when doing a stack trace, use ShellCmd class\n- use always the current configured manager from global configuration to avoid eternal looping in case of certain failover scenarios\n- avoid rescheduling of files on a location still in the deletion list\n\n``V0.3.134 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- avoid 'fork' calls in the namespace library using the 'ShellCmd' class\n\n``V0.3.133 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix wrong EXITSTATUS() macro preventing clean Slave2Master transitions\n\n``V0.3.132 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- revert faulty bug fix introduced in 0.3.130 preventing a slave to boot the file namespace\n\n``V0.3.131 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix comparison between FQDN and hostname when registering FSTs with the MGM\n- forward errno to client console when archive/backup command fails\n- fix accidental deletion of opaque info at the MGM for fsctl commands\n- various FUSE bugfixes\n\nNew Features\n++++++++++++\n- add queuing functionality to the archive/backup tool\n\n``V0.3.130 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix eternally booting slave and crazy boot times\n\n``V0.3.129 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix for memory leak by ShellCmd not joining properly threads\n\n``V0.3.128 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- avoid to call pthread_cancel after pthread_join (SEGV) in ShellCmd class\n- fix startup script to align with change in grep on CC7\n- fix gcc 5.1 warning\n\n``V0.3.127 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- several compilation and build fixes (spec) for i386 and CC7\n- fix fuse base64 encoding to not break URL syntax\n\n``V0.3.126 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- major improvements in automatic error recovery for read and writes\n- a failed create due to a faulty disk server is recovered transparently\n- a failed read due to a faulty disk server is recovered transparently\n- an update on a file where not all replicas are available triggers an inline repair if (<1GB) and if configured via attributes an async repair via the configure - FUSE has been adapted to deal with changing inodes during a repaired open\n- distinguish scheduling policies for read and write via `geo.access.policy.read.exact` `geo.access.policy.write.exact` - if `on` for **write** then only groups matching the geo policy and two-site placement policy will be selected for placement and data will flow through the close fst - if `on` for **read** the replica in the same geo location will always be chosen\n\n``V0.3.125 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- allow to disable 'sss' enforcement on FSTs (see /etc/sysconfig/eos.example) - each FST need a prot bind entry on the MGM config file when enabled\n- show the current debug setting in 'node status <node>' as debug.state variable\n- add support for multi-session FUSE connections with uid<1024*1024 and gid<65536 sid<256\n- introduce vid.app, avoid stalling of 'fuse' clients and report application names in 'who -a'\n- implement 'sys.http.index' attribute to allow for static index pages/redirection and support URLs a symbolic link targets\n- follow the 'tried=<>' advice given by the XRootD client not to redirect again to a broken target\n\nBug Fixes\n+++++++++\n- fix 'eos <cmd>' bug where <cmd> is not executed if it has 3 letters and is a local file or directory (due to XrdOucString::endswith bug)\n- update modification for intermediate directories created by MKPATH option of 'xrdcp'\n- fix 'vid rm <key>'\n- revert 'rename' function to apply by default overwrite behaviour\n- allow arbitrary symbolic link targets (relative targets etc.)\n- disable readahead for files that have rd/wr operations\n- allow clean-up via the destructor for chunked upload files\n- fix directory listing ACL bug\n- avoid timing related dead-lock in asynchronous backend flush\n\n``V0.3.121 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- support ALICE tokens in gateway transfers\n- allow to disable enforced authentication for submitted transfers\n- disable direct_io flag on ZFS mounts to avoid disabling filesystems due to scrubbing errors\n\nBug Fixes\n+++++++++\n- replacing system(fork) commands with ShellCmd class fixing virtual memory and fd cloning\n\n``V0.3.120 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- symlink fixes\n- fix round-robin behaviour of scheduler for single and multi-replace placements\n\n``V0.3.119 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- add support symbolic links for files and directories\n- add convenient short console commands for 'ln', 'info', 'mv', 'touch'\n\n``V0.3.118 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- add console broadcasts for important MGM messages\n\nBug Fixes\n+++++++++\n\n- use correct lock type (write) for merge,attr:set calls\n- resolve locking issue when new SpaceQuota objects have to be created\n- implement a fast and successful shutdown procedure for the MGM\n- implement safeguard for the manager name configuration in FSTs\n\n``V0.3.117 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- enable read-ahead in FUSE clients to boost performance (default is off - see /etc/sysconfig/eos.example)\n\n\n``V0.3.116 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix asynchronous egroup refresh query\n\n``V0.3.115 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- reduce verbosity of eosfsd logging\n- support OC special header removing the location header from a WebDAV MOVE response\n\nBug Fixes\n+++++++++\n- fix temporary ro master situation when slave reloads namespace when indicated from compacted master (due to stat redirection)\n\n``V0.3.114 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix temporary ro master situation when slave reloads namespace when indicated from compacted master (due to stat redirection)\n\n``V0.3.112 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- add support for nested EGROUPS\n- add 'member' CLI to check egroup membership\n\nBug Fixes\n+++++++++\n- fix logical quota summary accounting bug\n- fix not working 'file version' command for directories with 'sys.versioning=1' configured\n- fix order violation bug in 'Drop' implementation which might lead to SEGV\n\n``V0.3.111 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- redirect \"file versions' to the master\n\n``V0.3.110 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix copy constructor of ContainerMD impacting slave following (hiding directory contents on slave)\n- fix temp std::string assignment bugs reported by valgrind\n\n``V0.3.109 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix timed read/write locks to use absolute times\n\n``V0.3.108 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- update Drain/Balancer configuration at least every minute to allow following master/slave failover and slot reconfiguration\n\nNew Features\n++++++++++++\n- support for OC-Checksum field in GET/PUT requests\n\n``V0.3.107 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- support for secondary group evaluation in ACLs (enable secondary groups via /etc/sysonfig/eos:export EOS_SECONDARY_GROUPS=1\n\n``V0.3.106 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- update MIME types to reflect most recent mappings for office types\n\n``V0.3.104 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix custom namespace parsing for PROPPATCH requests\n- allow 'eos cp' to copy files/dirs with $\n- fix missing unlock of quota mutex in error return path\n- fix mutex inversion in STATLS function\n\n``V0.3.102 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix 'attr' get' function if no attribute links are used\n- use '_attr_ls' consistently instead of directly namespace map (to enable links everywhere)\n- fix PROPPATCH response to be 'multi-status' 207\n\n``V0.3.101 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- avoid negative sleep times in scrub loops induced by very slow disks\n- apply ANDROID patch for chunked uploads only if 'cbox-chunked-android-issue-900' special header has been added by NGINX proxy\n- make MIME type detection case-insensitive\n\n``V0.3.100 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- add online compaction for directories selectable via 'ns compact' (see help)\n- support for symbolic attributes 'attr link', 'attr unlink', 'attr fold' to reduce directory memory footprint\n\nBug Fixes\n+++++++++\n- fix bug leading to wrong dual master detection after online compaction was running on the master\n\n``V0.3.99 Aquamarine``\n======================\n\nNew Features\n++++++++++++\n- allow 'sys.owner.auth=*' to have sticky uid/gids for such directories\n- new FST proxy redirection to send file IO through a proxy frontend\n- recursive 'rm -r' protection in fuse\n- add MIME type suffix detection\n\nBug Fixes\n+++++++++\n- remove PrivGuards from Transfer cmds enabling krb5/x509 delegation\n- fix HTTP return codes for Put and Range Requests\n\n``V0.3.97 Aquamarine``\n======================\n\nNew Features\n++++++++++++\n- forbid 'rm -r' & 'rm -rf' on a predefined tree deepness\n\nBug Fixes\n+++++++++\n- various fixes in archive daemon\n- improve speed of HTTP HEAD requests with trailing /\n- store proxy and client identity properly in VID structure\n\n``V0.3.96 Aquamarine``\n======================\n\nBug Fixes\n+++++++++\n- fix -1 bug in 'chown'\n\nNew Features\n++++++++++++\n- add dummy responses for LOCK,UNLOCK,PROPPATCH enabling OSX & Windows WebDAV clients\n- allow to modify only group ownership in chown\n\n``V0.3.95 Aquamarine``\n======================\n\nBug Fixes\n+++++++++\n- balancing: seal '&' in capabilities\n- draining: seal '&' in capabilities\n- encode all '&' in meta data synchronization\n- propagate 'disableChecksum' to all replicas during chunked uploads\n- make 'console log' e.g. /var/log/eos/mgm/error.log working again\n- fix substantial memory leak in PUT requests on FSTs\n- fix 's3' lower-case headers\n- disable 'delete-on-close & repair-on-close' for chunked uploads to allow for single chunk retry\n- fix '\\n' encoding for FUSE listing\n- require 'targetsize' in standard HTTP PUT\n- fix documentation of attributes for max/minsize in 'attr help'\n- fix sealing of empty checksum FMD info\n- fix double mapping of propfind requests\n- enable re-entrant https mapping as required by HTTPS Webdav gateways\n- fix JSON format for fsck reports\n- swap HTTP/ROOT share url\n- fix return codes for chunked uploads for cases like no quota etc.\n- add 'open' serialization for identical file paths to avoid open errors using HTTP protocol\n- don't send redirect on FST put's to avoid incomplete files\n- fix missing targetsize for standard oc PUTs to avoid acceptance of incomplete files\n- fix and use atomic CLOEXEC flag in various places\n- add PAM module to NGINX\n- fix PUT error handling (will break connection for all errors happening after 100-continue on FST)\n- various improvements to backup functionality\n- enforce order in chunked uploads\n- disable scanning of w-open files\n- fix 'geotag' client mapping\n- fix 'recycle restore' for overlapping file/directory keys\n- advertise MKCOL,PUT in OPTIONS for WebDAV write access\n- fix SEGV due to illegal mtime settings for HTTP GETs\n- fix copy constructor of Container objects\n\nNew Features\n++++++++++++\n- 'find --purge atomic' to clean-up atomic left-over garbage\n- allow 'file check fxid:.... | fid:...'\n- add 'recycle config --ratio < 0 .. 1.0 >' to set a threadshold based keep ratio in the recycle bin\n\n``V0.3.75 Aquamarine``\n======================\n\n- add support for archive interface to stage-out and migrate a frozen subtree in the namespace to any XRootD enabled archive storage\n\n``V0.3.57 Beryl``\n=================\n\nNew Features\n++++++++++++\n- adding libmicrohttpd build directory\n- support threadpool with EPOLL for embedded http server\n\nBug Fixes\n+++++++++\n- balancing: was never starting\n- scheduler: was skipping scheduling group when one node >95% network-out loaded\n- nginx: don't forward PUT payload to MGM\n- microhttpd: fix virtual memory leaking due to fragmentation\n- http: let HTTP clients see errors on PUT\n\n``V0.3.53 Beryl``\n=================\n\nNew Features\n++++++++++++\n- [webdav] add possibility to exclude directory syncs via 'sys.allow.oc.sync'\n- [webdav] add support to do path replacements provided by two special header flosg CBOX_CLIENT_MAPPING & CBOX_SERVER_MAPPING\n\n``V0.3.51 Beryl``\n=================\n\nBug Fixes\n+++++++++\n- fix gdb stacktrace getting stuck if too much output is produced - stacktrace is stored in /var/eos/md/stacktrace and then reported back into the log\n- fix wrong network traffic variable used in the scheduling implementation (used always 0 instead of real traffic)\n\n``V0.3.49 Beryl``\n=================\n\nBug Fixes\n+++++++++\n- rename: allow whitespace names, fix subpath check, fix encofing in HTTP move\n- various HTTP/DAV related return code fixes\n\nConsolidation\n+++++++++++++\n- the 'eos' shell by default does not run in 'pipe mode' e.g. no background agent\n\nNew Features\n++++++++++++\n- allow FUSE_OPT in /etc/sysconfig/eos e.g. to set a FUSE mount read-only use export FUSE_OPT=\"ro\"\n- enable MacOSX build and add packing script for DMG\n\n``V0.3.47 Beryl``\n=================\n\nBug Fixes\n+++++++++\n- bugfixes in HTTP daemon configuration/startup\n- many bugfixes for owncloud/atomic/version support\n- many bugfixes for mutex order violations\n- fix BUG in FUSE making the mount hang easily\n- fix BUG in FUSE showing alternating mtimes and showing stale directory listings\n- fix BUG in stalling drain/balance\n- fix BUG in drain reset\n- fix FD leak in Master\n- add monitor lock to getpwXXX calls to deal with SSSD dead-lock on SLC6\n- disable FMD size/checksum checks for RAIN files\n\nConsolidation\n+++++++++++++\n- FST don't clean-up transactions if their replica is registered in the MGM\n- make all HTTP header tags case-insensitive\n- HEAD becomes a light-weight operation on large directories\n- new unit tests for owncloud/atomic/version support\n- improve 'quota ls' performance and bypass uid/gid translations as much as possible\n- avoid lock contention in uid/gid translations\n- limit the 'gdb' stack trace to maximum 120s to avoid service lock-up in case of a stuck GDB process\n- FST never give up in calling a manager for errors allowing a retry\n\nNew Features\n++++++++++++\n- update 'eos-deploy' to be able to install from beryl, beryl-testing, aquamarine and citrine YUM repositories\n- adjust 'file adjustreplica' and 'file verify' for RAIN files (file verify made RAIN file inaccessible)\n- extend 'space reset' command\n\n``V0.3.37 Beryl``\n=================\n\n- add support for Owncloud chunked upload\n- add support for immutable namespace directories\n- fix drain/balancing stalls\n- fix memory leak introcuded by asynchronous XrdCl messaging\n- fix node/fs/group unregistering bug\n- make atomic uploads and versioning real 'atomic' operations (no visible state gap between target file exchange)\n- add 'file versions' command to show and recall a previous version\n- fix tight thread locking delaying start-up\n\n``V0.3.35``\n===========\n\nBug Fixes\n+++++++++\n\n- modify behaviour on FST commit timeouts - cleanup transaction and keep the replica to avoid unacknowledged commits (replica loss)\n- fix output of 'vst ls --io'\n- add option 'vst --upd target --self' to publish only the local instance VST statistics to InfluxDB\n\n``V0.3.34``\n===========\n\nNew Features\n++++++++++++\n- add global VST monitoring support - by default all running EOS instances are visible with some basic parameters using the 'vst' command\n- add support to feed VST informatino using UDP into InfluxDB for visualisation with Grafana\n- add global-mq config file to run a global VST broker\n- support 'mtime' propagation as needed by OwnCloud sync client to optimize the sync process\n- better support OwnCloud sync clients\n- restrict OwnCloud sync tree requiring 'sys.allow.oc.sync=1' on the entry directory\n- add support for atomic file uploads - files are visible with the target name when they are complete - disabled for FUSE\n- support LDAP authentication (basic HTTP authentication) in NGINX proxy on port 4443 (by default)\n- add 'file info' command for directories\n- implement 'fsck repair --adjust-replica-nodrop' for safe repair (nothing gets removed - only added)\n- allow 'grep'-like functionality in 'fs ls' commands\n- support encoding models like UTF-8 (set export EOS_UTF8=1 in /etc/sysconfig/eos)\n- accept any checksum configuration in 'xrootd.chksum' config file\n\nConsolidation\n+++++++++++++\n- FUSE (cache) refactoring & FUSE unit tests\n- send all 'monitoring'-like messages purely in async mode (not waiting) for any response e.g. all shared hash states\n\nBug Fixes\n+++++++++\n- fix PWD mapping for names starting with numbers\n- fix Windows compliance for WebDAV implementation (allprop request)\n- fix iterator issue in GeoBalancer and GroupBalancer\n- fix balancing starvation bug\n- fix 'range requests' in HTTP implementation\n- fix embedded HTTP server configuration (thread-per-client model using poll)\n- fix S3 escaping for signature checks (make Cyberduck compatible)\n\n``V0.3.28``\n-----------\n\nNew Features\n++++++++++++\n- allow FUSE mounts against Master and Slave MGM implementing a new stat function and mkdir/create returning the new inode numbers\n- add ETAG to FST GET & PUT requests\n- allow to 'grep' for several view objects in fs,node,group,space ls function\n\nConsolidation\n+++++++++++++\n- improve/fix master/slave failover behaviour\n- display the correct boot state during slave startup\n- improve stack trace to extract responsible stacktrace thread and print again in the end of a log file\n- let hotfile display files age and expire\n- don't allow to remove nodes which are currently sending heartbeats or have not drained filesystems\n\nBug Fixes\n+++++++++\n- fix leak in HTTP access leaving files open\n- fix krb5 keytab permission for xrootd 3.3.6-CERN and eos-deploy\n- fix sync startup in Slave2Master transition\n\n\n``V0.3.25``\n===========\n\nNew Features\n++++++++++++\n- allow to match hostnames in VID interface for gateway machines e.g. vid add gateway lxplus* https\n- broadcast hotfile list per filesystem to the MGM and add interface to this list via ``io ns -f``\n- use inode+checksum for file ETAGs in HTTP, otherwise inode+mtime time - for directories use inode+mtime\n- add support for file versioning using attribute ``sys.versioning`` or via shell interface ``file version ..``\n- make ApMon more flexible to match individual mountpoints via environment match variable ``APMON_STORAGEPATH`` (try df | grep $APMON_STORAGEPATH).\n- eos-deploy script is added to the repository allowing RPM installation of (possibly ALICE enabled) EOS instances with a dual MGM and multi FST setup via a single command\n- allow to list files at risk/offline via ``fs status -l <fs-id>``\n\nConsolidation\n+++++++++++++\n- add space reset to documentation\n- add release notes to documentation\n- restrict daemon account to read everything but no write permission\n- propagate ban/unban/sudo setting from Master to Slave MGM\n- map the root user on a shared FUSE mount to daemon\n- delete space,group,node objects if they contained no filesystem when rm is issued on them\n- add space/group/node create/delete tests\n- make krb5 keytab file accessible to EOS MGM (required by XROOTD 3.6/CERN and 4.0)\n- allow for new TPC protocol where destination's open arrives before the source TPC key is deposited\n- use xrdfs in eos-instance-test instead of xrd\n- add a check for missing fusermount execution permissions to the user FUSE daemon eosfsd\n- add an explicit message to the MGM log AFTER a file is successfully deleted\n- allow to select user and group ID as user and group names e.g. user foo and group bar ``eos -b foo bar``\n- add the node information given by ``ls --sys`` to the monitoring output ``ls -m``\n\nBug Fixes\n+++++++++\n- make krb5 keytab file accessible to EOS MGM\n- fix lock from rw to wr-lock when a space/node group is defined or created\n- fix boradcasting and value application on slave filesystem view\n- add the eos-test RPM to the MGM installation done via eos-deploy\n- fix path reparsing for .. to allow filenames like ..myfile\n- use path filter function in the Attr shell interface to support attr ls . etc.\n- make RAIN recovery/draining usable\n- forbid renaming of a directory into an existing file\n- add browse permission of local drop box directory\n- if no strong auth is available use sss authentication in transfer jobs\n- remove two obsolete tests from eos-instance-test and add bc to RPM dependency of eos-test\n- fix eos-uninstall script\n- don't block slave/master transitions if eosha is enabled\n- start recycle thread only when the namespace is fully booted\n"
  },
  {
    "path": "doc/citrine/releases/beryl.rst",
    "content": ":orphan:\n\n.. highlight:: rst\n\n.. index::\n   single: Beryl\n\nBeryl\n========\n\n``Lifetime: 2013-2015``\n\nThe **Beryl** release is the second generation of the production version of EOS.\nIt has been used since September 2013 and is going to be phased out in 2018.\n\nRelease notes :doc:`./beryl-release`\n"
  },
  {
    "path": "doc/citrine/releases/citrine-release.rst",
    "content": ":orphan:\n\n.. highlight:: rst\n\n.. index::\n   single: Citrine-Release\n\n\nCitrine Release Notes\n=====================\n\n``Version 4 Citrine``\n\nIntroduction\n------------\nThis release is based on XRootD V4 and IPV6 enabled.\n\n\n``v4.8.102 Citrine``\n===================\n\n2023-04-12\n\nBug\n---\n\n* [EOS-5620] MGM: Fix thread delay calculation taking into account parallelism\n* [EOS-5612] MGM: Add an option to restrict ctime limits in find's to start at\n  a given deepness. This is required for the recycle bin query.\n* COMMON: Don't reset the current vid identity when handling KEYS mapping\n  unless we actually have a hit in the map. This was breaking the vid mapping\n  for gsi/http with voms extensions that have the endorsements field in the\n  XrdSecEntity populated and this was interpreted as a key.\n\n\n``v4.8.101 Citrine``\n===================\n\n2023-03-22\n\nBug\n----\n\n* [EOS-5559] - EOS HTTP REST API - no JSON output if authentication is done with Bearer token\n* [EOS-5587] - jwt::decode might throw an exception\n\nImprovement\n-----------\n\n* MGM: Call proxy gateway mapping only if this has an x-forwarded-for header and drop EOS_XRDHTTP_NGINX_PROXY\n* WFE: Set a decimal string instead of an hex string in disk_file_id\n\nNew features\n-------------\n\n* [EOS-5588] - Allow HTTPS gateway functionality using key entries\n\n\n``v4.8.100 Citrine``\n===================\n\n2023-02-17\n\nBug\n----\n\n* FST: Ensure the necessary OC client http headers are passed back to the client and\n  add a couple of helper function for building the header string for the HTTP response.\n\nImprovement\n-----------\n\n* EOS-5524 MGM: Allow https gateway nodes to provide x-forwarded-for headers\n\n\n``v4.8.99 Citrine``\n===================\n\n2023-02-06\n\nBug\n---\n\n* EOS-5509: Fix crash of the MGM when enabling the balancer\n\nImprovements\n------------\n\n* MGM: enable hide-version also when heartbrate has been changed\n\n\n``v4.8.98 Citrine``\n===================\n\n2023-01-19\n\nBug\n---\n\n* COMMON: fix locking in getPhysicalIds which was entering an entry into a hash without lock if uid>1M\n\n\n``v4.8.97 Citrine``\n===================\n\n2023-01-18\n\nBug\n---\n\n* MGM: avoid to have the low-level quota lock when we call a uid/gid translate function\n* MGM: Avoid race condition when there are two metadata flusher objects - 1st of Jan bug\n* EOS-5487 MGM: let root delete files/dires in VTX directories\n\n\n``v4.8.96 Citrine``\n===================\n\n2022-12-14\n\nBug\n---\n\n* MGM: add regfree in FuseServer regex usage to avoid memory leak\n* MGM: unlock the Access mutex when delaying a client to not get problems to get a write lock\n* MGM: reset file statistics after each inspector run for QDB backen\n\nImprovements\n------------\n\n* Invert stall logic to check first user limits and then catch-all rules\n\n\n``v4.8.95 Citrine``\n===================\n\n2022-11-29\n\nBug\n----\n\n* MGM: Fix string to integer conversion in tape WFE code\n\n\n``v4.8.94 Citrine``\n===================\n\n2022-11-14\n\nBug\n----\n\n* [EOS-5436] - Origin Restriction does not work as expected\n\nImprovements\n------------\n\n* MGM: don't stall clients if thread limits are not exhausted\n* COMMON: support tokens, which have been URI encoded\n* ALL: add VTX bit support, correct 'ls -la' output to show t,T,+ to indicate VTX, VTX+ACL, ACL\n\n\n``v4.8.93 Citrine``\n===================\n\n2022-10-28\n\nBug\n----\n\n* [EOS-5424] - EOS grpc: inode from folder is 0\n\nImprovement\n------------\n\n* [EOS-5412] - add qclient performance metrics on monitoring format.\n* [EOS-5413] -  QClient performance: have last 5m, last 1m, etc metrics\n* [EOS-5426] - MGM: force show only physical space via 'spaceinfo'/statvfs\n\n\n``v4.8.92 Citrine``\n===================\n\n2022-10-10\n\nBug\n----\n\n* [EOS-5347] - EOS token not usable via eosxd\n* [EOS-5369] - occasional error during eoscta test \"mismatch between requested fid/fsid and retrieved ones\"\n* [EOS-5371] - Home-i00 crash 25/8/22 13:15\n\n\nNew Feature\n------------\n\n* [EOS-5145] - Extending lock support\n\n\nImprovement\n------------\n\n* [EOS-3297] - Print the deviation used for the group balancer\n* [EOS-5360] - Add EOS log messages for CTA workflow events\n* [EOS-5394] - Modify EOS to fill new fields passed to CTA\n* [EOS-5401] - Return the inode number in FMD responses for GRPC\n\n\n``v4.8.91 Citrine``\n===================\n\n2022-08-18\n\nBug\n---\n\n* [EOS-5366] MGM: Fix possible segv due to shorter than expected string\n* [EOS-5459] MGM: Fix crash due to unprotected access to a map in fusex::client::info\n\n\n``v4.8.90 Citrine``\n===================\n\n2022-08-11\n\nBug\n----\n\n* [EOS-5355] - System ACLs evaluation overruling logic is incorrect\n\n\nNew Feature\n------------\n\n* [EOS-5342] - CREATE cta workflow not triggered when new file created using fusex - DELETE workflow is also missing\n\n\nImprovement\n------------\n\n* [EOS-5343] - Better enforcement of the scattered placement policy\n\n\n``v4.8.89 Citrine``\n===================\n\n2022-07-20\n\nBug\n----\n\n* [EOS-5308] - MGM: Potential double free in LDAP initialize\n* [EOS-5334] - LDAP connection socket leak\n* [EOS-5335] - MGM crash in Fileinfo.cc:97\n* [EOS-5171] - Ensure mv always adopts the target's ownership, regardless the instance\n\n\n``v4.8.88 Citrine``\n===================\n\n2022-06-30\n\nBug\n----\n\n* [EOS-5302] - Iostat domain accounting is broken\n* [EOS-5303] - Shared filesystem file registration feature\n* [EOS-5308] - MGM: Potential double free in LDAP initialize\n\nImprovement\n------------\n\n* [EOS-5321] - Allow to define which errors the fsck repair thread works on\n* [EOS-5305] - Tape REST API - V1 with an option to deactivate STAGE\n\n\n``v4.8.87 Citrine``\n===================\n\n2022-06-09\n\nBug\n---\n\n* [EOS-5286] - Physical quota is not updated when we set EC conversion\n* [EOS-5288] - Wrong layout id after conversion operation leading to wrong physical size\n* MGM: The initial behaviour of xrdfs prepare -s/-a/-e and xrdfs query prepare have been restored\n\nImprovement\n-----------\n\n* MGM: Tape REST API v0.1 release - Support for ArchiveInfo and Release\n  functionality + discovery endpoint\n* [EOS-5282] - Allow converter configuration to persist on restarts\n\n\n``v4.8.86 Citrine``\n===================\n\n2022-05-27\n\nBug\n---\n\n* [EOS-5272] - Fusex crash while handling hardlinks\n\n\n``v4.8.85 Citrine``\n===================\n\n2022-05-27\n\nBug\n----\n\n* [EOS-3713] - sys.eos.mdino should not use old-style inodes\n* [EOS-5230] - Keep xattrs when restoring versions\n* [EOS-5246] - Replica shows 'error_label=none' while having checksum mismatch\n* [EOS-5262] - GeoBalancer not working with the new converter\n\nNew Feature\n------------\n\n* [EOS-4983] - Implementation of the WLCG TAPE REST API on EOS\n* [EOS-5137] - Implementation of the .well-known endpoint of the tape REST API\n* [EOS-5244] - Allow to have the token secret key in a separate file\n\n\nImprovement\n------------\n\n* [EOS-5198] - Add few log lines to MasterLog\n* [EOS-5263] - groupmod is hard limited to 256 groups\n* [EOS-5267] - Provide timestamp in eos convert list failed errors\n\n\n``v4.8.84 Citrine``\n===================\n\n2022-05-10\n\nBug\n----\n\n* [EOS-5199] - Metadata (xattrs) is lost when creating new versions\n* [EOS-5219] - eos fsck report json output does not reflect command line options -l and -i\n* [EOS-5224] - No update is performed when adding a new member to an e-group in EOSATLAS\n* MGM: Fix eternal connect problem in Egroup ldap search\n\nNew Feature\n------------\n\n* [EOS-5178] - Implement Group Drain\n* [EOS-5213] - Introduce a generic Observer class\n\nTask\n-----\n\n* [EOS-5225] - Have a useful GroupDrain Status\n\nImprovements\n------------\n\n* FUSEX: Rewrite the handling of async messages in XrdCl using shared pointers\n* MGM: Improve handling of RAIN files with rep_diff_n errors\n\n\n``v4.8.83 Citrine``\n===================\n\n2022-04-22\n\nBug\n----\n\n* [EOS-5197] - Deleting an xattr via console does not delete the key\n* [EOS-5199] - Metadata (xattrs) is lost when creating new versions\n* MGM: Fix crash in debug message when Env object is null for Access method\n\nNew Feature\n------------\n\n* [EOS-5215] - Fsck handle stripe size inconsistencies for RAIN layouts\n\n\nImprovement\n------------\n\n* [EOS-4955] - Add project quota tests as a part of CI\n* MGM: Iostat performance improvements for summary output\n* MGM: Iostat make extra tables optional by default and add separate\n  flag for displaying them.\n\n\n``v4.8.82 Citrine``\n===================\n\n2022-04-12\n\nBug\n----\n\n* MGM: Ensure IoStat tables are properly formatted\n\nImprovement\n------------\n\n* [EOS-5201] - Allow for more fine grained IO policies\n* [EOS-5204] - Only create files  via FUSEX if there is inode and volume quota and physical space available\n* [EOS-5205] - Distinguish writable space and total space\n* [EOS-5206] - Don't allow to set quota volume lower than the minimum fuse quota booking size\n\n\n``v4.8.81 Citrine``\n===================\n\n2022-04-08\n\nBug\n---\n\n* FST: Forward the opaque info also for the async open API\n* COMMON: Fix crash when checking for eos token when no env object is present\n* MGM: Update conversion string marker for ctime update from \"!\" to \"+\" to avoid\n  failures in non-UTF8 enabled instances.\n* COMMON: make EOS tokens work via GRPC\n* FUSEX: fix logical refactoring bug when introducing splitPath to validate credentials in jails\n\n\n``v4.8.80 Citrine``\n===================\n\n2022-03-30\n\nBug\n----\n\n* [EOS-5181] - Slave to Master redirection creates IO errors on FUSEx mounts\n* [EOS-5176] - Make OAuth tolerant to self-signed//invalid certificates used by identity provider\n* [EOS-5167] - Fix segmentation fault by not starting the BulkRequestProcCleaner\n  threads if the MGM is not the master.\n* MGM: Add support for eos tokens over HTTPS (XrdHttp)\n* MGM: Fix console parsing for schedule/iopriority settings per application\n* FUSEX: fix logical refactoring bug when checking in local jails (container)\n\n\n\nNew Feature\n------------\n\n* NS: Added the possibility for namespace-related operations to communicate about some specific timings.\n\n\n``v4.8.79 Citrine``\n===================\n\n2022-03-18\n\nBug\n----\n\n* FST: Fix reference size for RAIN layouts which needs to match the logical\n  size of the file and not the physical size of the local stripe. This can\n  lead to HTTP errors when trying to read these such files.\n* [EOS-5133] - node ls -b does not remove the domain names\n* [EOS-5153] - EC file written via FUSEx - mismatching checksum\n* FST: Add support for real-time IO priority\n\n\nImprovement\n-----------\n\n* [EOS-5126] - [eos-ns-inspect] Complement `stripediff` output\n* DOC: Add documentation on IO priorities configuration\n\nNew Feature\n------------\n\n* MGM: Added tape REST API support\n* Add eos-iam-gridmap file support for interacting with IAMs\n\n\n``v4.8.78 Citrine``\n===================\n\n2022-02-21\n\nBug\n---\n\n* FST: Make sure promise is still valid even in the event that an exception\n  is thrown in the XrdIoHandler constructor.\n* MGM: Groupbalancer: fix app tag to not create proc directories\n* COMMON: Make sure the BufferManager satisfies buffer requests that don't\n  fit the existing slots. Also increase the default number of slots from 2\n  to 6 which covers buffers up to 64MB.\n\n\n``v4.8.77 Citrine``\n===================\n\n2022-02-18\n\nBug\n----\n\n* FST: Make sure the async write implementation does not exhaust the system\n  memory in case the client(s) are pushing in more data than the machine can\n  distribute further in the cluster for replica layouts.\n* [EOS-5090] - convert clear is not a admin command\n* [EOS-5122] - MD and Find calls via GRPC don't obey ACLs\n\nImprovement\n------------\n\n* [EOS-5108] - workaround: drop forced automount expiry on FUSEX updates\n\n\n``v4.8.76 Citrine``\n===================\n\n2022-02-14\n\nBug\n----\n\n* [EOS-5110] - Consolidate Access control in GRPC MD, MDSTreaming\n* [EOS-5116] - Workaround for XrdOucBuffPool bug\n* [EOS-5118] - eos-ns-inspect scan is initializing maxdepth to 0, even if not used\n* [EOS-5119] - Deadlock scenario in eosxd\n\nImprovement\n------------\n\n* [EOS-5111] - Groupbalancer: newly introduced fields may not have a sane value\n* [EOS-5120] - io stat tag totals\n\n\n``v4.8.75 Citrine``\n===================\n\n2022-02-03\n\nBug\n----\n\n* MGM: Fix deadlock in the GroupBalancer\n* [EOS-5083] - Deletion via tokens deletes the original folder for which token was created\n* [EOS-5088] - newfind in root directory missing `maxdepth 0`\n* [EOS-5089] - newfind missing symlink listing\n* [EOS-5095] - Re-entrant lock triggered by out of quota warning\n* [EOS-5092] - allow removing max.ropen / max.wopen space/filesystem attributes\n\nImprovement\n-----------\n\n* [EOS-5101] - Label all mutex lock locations\n* [EOS-5102] - Display INGRESS and EGRESS performance in summary\n* [EOS-5103] - Add global timeout parameter to eoscp\n\n\n``v4.8.74 Citrine``\n===================\n\n2022-01-27\n\nBug\n---\n\n* [EOS-5062] - Node config command should take fs status into consideration\n* [EOS-5079] - Fix for Groupbalancer size calculation post transfer\n* [EOS-5081] - Align buffers used during various layout rd/wr ops\n\nNew Feature\n-------------\n\n* [EOS-5067] - Groupbalancer now introduces multiple engines & supports min/max\n  file sizes. Check the GroupBalancer docs for details.\n* [EOS-5085] - Allow rate limits of zero\n\nImprovement\n-----------\n\n* [EOS-3275] - Port iostat information into quarkdb\n* [EOS-5049] - Handle draining for files with one replica on tape\n* [EOS-5051] - Benefit from parallelization in layout::open\n* [EOS-5076] - Extend iotype interfaces to be space/directory defined\n* [EOS-5080] - Support eos.app tag in the Converter interface\n* [EOS-5084] - Remove deprecated \"exists\" field in QUERY PREPARE response\n\n\n``v4.8.73 Citrine``\n===================\n\n2022-01-18\n\nBug\n----\n\n* FST: Ensure buffers for write async requests are duplicated and kept until\n  requests are satisfied\n* FST: Fix starvation when deleting a TransferMultiplexer object\n* MGM: Fix crash when trying to convert files without replicas\n* MGM: Fix building of conversion id that was using hex representation for\n  the group indices.\n\nImprovements\n-------------\n\n* MGM: Prefetch the FileSystem contents outside the ns lock for Drop operations\n* FST: Use OS page size aligned buffers for the HeaderCRC objects\n\n\n``v4.8.72 Citrine``\n===================\n\n2022-01-17\n\nBug\n----\n\n* [EOS-5069] - filesystem status in \"rw + failed\"\n* [EOS-5070] - Access::ThreadLimit creates re-entrant lock of the access mutex\n\nImprovement\n------------\n\n* [EOS-5065] - Add create-if-not-exists option in GRPC\n\n\n``v4.8.71 Citrine``\n===================\n\n2022-01-14\n\nBug\n----\n\n* COMMON: Avoid segv due to mutex object set to nullptr in RWLock printout\n* [EOS-4850] - eosxd crash in destructor under metad::pmap::retrieveWithParentTS()\n* [EOS-5057] - Volume quota dispatched to FUSE clients mixes logical and physical bytes\n\n\n``v4.8.70 Citrine``\n===================\n\n2022-01-06\n\nBug\n----\n\n* [EOS-5033] - missing drainperiod from `eos -j fs ls`\n* [EOS-5034] - eos-server missing dependency on perl(Time::HiRes)\n* [EOS-5052] - Repeated open/close sequence leads to failed file state\n* [EOS-5039] - Threads with parens in their name cannot access EOS\n\nImprovement\n-----------\n\n* [EOS-5027] - Handle eviction for multiple staging requests on the same file\n* [EOS-5029] - Allow to apply rate limiting in recursive (server side) command\n* [EOS-5048] - Support direct IO for high performance read/write use cases\n\n\n``v4.8.69 Citrine``\n===================\n\n2021-11-24\n\nImprovements\n------------\n\n* FST: allow to disable any iopriority settings in FSTs using env EOS_FST_NO_IOPRIORITY\n\n\n``v4.8.68 Citrine``\n===================\n\n2021-11-23\n\nBug\n----\n\n* [EOS-5015] - FSTs running versions older than 4.8.67 cannot connect to MQ\n  running version 4.8.67\n\nImprovement\n-----------\n\n* [EOS-5004] - Support sys.acl for file ACLs for RA protocols\n* [EOS-5013] - Make oAuth userinfo configurable\n* [EOS-5018] - Allow to set extended attributes on version folders\n\n\n``v4.8.67 Citrine``\n===================\n\n2021-11-17\n\nBug\n----\n\n* [EOS-4934] - ASAN: fusex: enoent use after free\n* [EOS-4941] - FSCK toggle-repair multiple time crashes MGM\n* [EOS-4952] - Unify the various string split interfaces\n* [EOS-4963] - FST returns 200 status code for Partial Content request instead of 206\n* [EOS-4976] - Fix activity field passed from EOS to CTA\n* [EOS-4986] - eos CLI aborts with \"basic_string::_S_construct null not valid\"\n* [EOS-4992] - FST crashes upon SSI exception\n\nImprovement\n------------\n\n* [EOS-4945] - Use timestamp for saving the stack trace\n* [EOS-4995] - Add flag to 'ls' to add checksum printout in long listing\n* [EOS-5002] - Add a '-c' option to set an extended attribute only if it does not exist already\n\n\n``v4.8.66 Citrine``\n===================\n\n2021-10-05\n\nBug\n----\n\n* [EOS-4936] - GETLK returns EAGAIN instead of lock information\n* [EOS-4937] - Fix reporting for written bytes for RAIN layouts\n* [EOS-4938] - Store report info only in the current MGM master\n\nImprovement\n-----------\n\n* [EOS-4930] - Add support for async writes for replica layout\n\n\n``v4.8.65 Citrine``\n===================\n\n2021-09-29\n\nBug\n---\n\n* MGM: Fix quota accounting for the sum of all groups\n\n\n``v4.8.64 Citrine``\n===================\n\n2021-09-27\n\nBug\n----\n\n* [EOS-4779] - Dead lock in parity computation for RAIN\n* [EOS-4896] - queuing for archive should use MgmOfsAlias instead of mgm.manager\n* [EOS-4912] - fst - read lock held for 10s seconds\n* [EOS-4922] - SEGV on config after shutdown was initiated\n* [EOS-4924] - FST service restarts after calls to std::future, eos::fst::Storage::Publish\n* [EOS-4925] - Typo in mgm/proc/user/Archive.cc\n* [EOS-4926] - discrepancy of accounting report and quota\n\nNew Feature\n-----------\n\n* [EOS-4903] - Add new configuration to setup redirection with Master/Slave QuarkDB Configuration\n\n\nImprovement\n-----------\n\n* [EOS-4889] - Make EOS-CTA tape garbage collector compatible with MGM master/slave configuration\n\n\n``v4.8.63 Citrine``\n===================\n\n2021-09-10\n\nBug\n===\n\n* [EOS-4904] MGM: block proxy headers in XrdHttp by default (add env file + fix typo)\n* [EOS-4905] MGM: pass CGI 'query' to the access function used in XrdHttpMgm to allow token access\n* [EOS-4901] MGM: check for invalid paths before scoping them\n* MGM/CONSOLE: Fix acl command to accept the \"a\" archive flag\n* FST: Make sure to skip checksum if asked to ignore it\n* MGM: Reduce load on the configuration backups when moving a files systems between groups/spaces\n\nImprovements\n============\n* CI: Add ApMon build/publish job for Centos Stream 8\n* DOC: various documentation improvements\n\n``v4.8.62 Citrine``\n===================\n\n2021-08-25\n\nBug\n----\n\n* [EOS-4327] - FST still misses the required capability key - symkey empty\n* [EOS-4852] - Race condition when accounting running console commands\n* [EOS-4878] - Balancing RAIN files stores wrong size in local DB\n\nImprovement\n------------\n\n* [EOS-4858] - Add fsck check for RAIN layout to spot disk size corruptions\n* [EOS-4863] - make eos-hashbench run a single benchmark at a time\n* [EOS-4875] - mgm: Mapping: avoid double lookups on maps\n\n\n``v4.8.61 Citrine``\n===================\n\n2021-08-21\n\nBug\n----\n\n* Revert \"COMMON: drop 'sudo' role after sudo mapping - fixes EOS-4781\"\n\n\n``v4.8.60 Citrine``\n===================\n\n2021-08-11\n\nBug\n----\n\n* [EOS-4480] - HA issue: GridFTP transfers with checksum testing are failing when\n  the DNS alias is not pointing towards the active MGM node\n* [EOS-4633] - 'eos' manpage is empty, rest is missing\n* [EOS-4683] - MGM LRU crash\n* [EOS-4690] - HA: transition to master crashes the future master\n* [EOS-4696] - eos config dump <name> does not work for backup configs\n* [EOS-4803] - FST node status not remaining offline when service is stopped\n* [EOS-4814] - Restore of a version does not work\n* [EOS-4818] - EOSAMS02 crash in DrainTransferJob\n* [EOS-4835] - Strange remdir unformatted lines...\n* [EOS-4843] - Wrong quota after a ns update_quotanode command\n* [EOS-4847] - group translation failing in EOSHOME for def-cg\n* [EOS-4779] FST: reduce file-local dead lock condition after parity computation error\n* [EOS-4835] MGM: fix ever growing '/' in remdir\n\nImprovement\n------------\n\n* [EOS-4411] - disk health check for Linux DM multipath devices\n* [EOS-4586] - RFE\" remove \"pre-configuring default route\" warning for fully-qualified instance+path\n* [EOS-4749] - Remove the extra-output display in eos rm command\n* [EOS-4783] - Size differs only in MGM [WIP in fsck dev]\n* [EOS-4784] - [rep_diff_n] and [rep_missing_n]; Overreplicated file, faulty replica was committed to MGM\n* [EOS-4838] - Check health status refinement\n* [EOS-4839] - Improve balancer shutdown to clean what it was balancing from the tracker queue\n* [EOS-4682] - MGM crash in LRU.hh:252 eos::MetadataProviderShard::retrieveFileMD\n* [EOS-4827] MGM: implement GRAB version functionality in GRPC\n* [EOS-4759] MGM: allow set space specific scheduling and iopriority parameter defaults\n\n\n``v4.8.59 Citrine``\n===================\n\n2021-07-22\n\nBug\n---\n\n* [EOS-4824] MGM: avoid SEGV when loading quota nodes with certain configurations\n\nImprovements\n------------\n\n* [EOS-4823] MGM: eosxd creations support now linked attributes describing file layouts etc.\n* [EOS-4825] COMMON: allow static mapping to local accounts from 'sub' using 'vid set map -oauth2 sub:xyz vuid:localuid'\n\n\n``v4.8.58 Citrine``\n===================\n\n2021-07-19\n\nBug\n---\n\n* [EOS-4775] NS: fixing SearchNode expansion decision taking mechanism\n* [EOS-4779] FST: fix dead lock in parity computation for RAIN\n* [EOS-4806] MGM: protect newfind command against crashes on malformed/buggy input for regex match --name filters\n* MGM: directory listing (XrdMgmOfsDirectory) always checks now ACLs for denials e.g. an ACL denial can supersede a POSIX allow\n\nImprovements\n------------\n\n* [EOS-4819]  MGM: adding server side bandwidth limitation, which can be defined either as a space policy (policy.bandwidth) or by application per space. The key for an empty application is 'space.bw.default' and the limits are given in MB/s\n* [EOS-4781] COMMON: drop 'sudo' role after sudo mapping\n* [EOS-4746] MGM/CONSOLE/GRPC: support ACL positions\n* DOC: improvements of fsck,permission and policy documentation\n* [EOS-4805] MGM: implement negative ACLs for read/write/delete operations\n\n\n``v4.8.57 Citrine``\n===================\n\n2021-06-30\n\nBug\n---\n\n* MGM: silence 'no token' error message in Acl class\n* MGM: silence error message in CommitHelper for atomic versioning, if no file has been versioned\n\n``v4.8.56 Citrine``\n===================\n\n2021-06-27\n\nBug\n---\n\n* [EOS-4764] COMMON:  fix overlap function used in token macro for CLI commands creating a SEGV when doing certain 'file mv' operations\n\nImprovements\n------------\n\n* [EOS-4766] MGM: Don't block HTTP access with EOS tokens in the HTTP bridge code - this allows to mix SciTokens and EosTokens inside the same instance\n\nNew Feature\n------------\n\n* [EOS-4762] MGM: add new filesystem active status online - overload - offline\n* [EOS-4760] FST: implement round-robin scheduling\n* [EOS-4759] FST: add 'eos.iopriority' to stear BFQ/CFQ priorities\n\n\n``v4.8.55 Citrine``\n===================\n\n2021-06-22\n\nBUG\n----\n\n* MGM: silence fprintf statements in InFlightTracker\n* [EOS-4756] MGM: keep recursive deletions exactly as configured by the recycle bin time policy\n\nNew Feature\n------------\n\n* FUSEX: allow to define 'sparse ratio' to disable read-ahead for good if a file has been seen to be sparse read - normally read-ahead can get re-enabled\n* CI: allow ASAN builds to be manually triggered\n\n\n``v4.8.54 Citrine``\n===================\n\n2021-06-18\n\nBug\n----\n\n* [EOS-4755] MGM: fix concurrency issues leading to SEGV in FuseServer/Caps (Imply)\n\n\n``v4.8.53 Citrine``\n===================\n\n2021-06-18\n\nImprovement\n------------\n\n* MGM: support tokens for EOS CLI commands and basic xrdfs functions like mkdir/rmdir/rm\n* MGM: introduce thread pool limits by user and global using 'eos access' and show usage in 'eos ns [stat]'\n* MGM: improve performance of eosxd broadcasts and use a standard mutex to protect the caps objects\n\n\n``v4.8.51 Citrine``\n===================\n\n2021-06-10\n\nBug\n----\n\n* [EOS-4740] MGM: Make sure only the master MGM propagates changes to the configuration engine.\n* SPEC: Fix ownership of archive directories\n* CONSOLE: Prevent to print out twice an error in selected proto commands\n\n\n``v4.8.50 Citrine``\n===================\n\n2021-06-07\n\nBug\n----\n\n* [EOS-4725] - Unknown lock held for many seconds\n* [EOS-4730] - Fix FST crash during shutdown\n* [EOS-4736] - Memory leak when parsing diskstat on CentOS8\n* [EOS-4737] - File systems blocked in booting during mass boot with --syncmgm\n* [EOS-4740] - Inconsistent FsView maps after removing/changing file system\n\nImprovement\n------------\n\n* [EOS-4724] - Support HTTP chunked uploads\n* [EOS-4727] - Add fsck subcommand to cleanup orphans\n* [EOS-4728] - Improve the refresh of fsck stats\n* [EOS-4729] - Improve remove detached for entries with deleted parents\n* [EOS-4735] - Make Egroup queries for non existing users / groups cacheable\n\n\n``v4.8.49 Citrine``\n===================\n\n2021-05-24\n\nBug\n----\n\n* FUSEX: properly support also KERYRING:persistent:%{UID} as default krb5 CCCAHCE\n\n\nImprovement\n-----------\n\n* [EOS-4709] - [eos-ns-inspect] adding --maxdepth to scanning functionality\n* MGM/CONSOLE: allow to scan quota in a subtree for a given uid or gid using\n  e.g. 'eos update_quotanode /eos/tree uid:123'\n* MGM: enhance eosnobody squashfs check to distinguish three instead of two cases:\n  result eosnobody can only stat via eosxd and access squashfs image files, nothing else\n\n\n``v4.8.48 Citrine``\n===================\n\n2021-05-18\n\nBug\n----\n\n* [EOS-4715] - Segv in jemalloc during PathRouting\n* MGM: add bypass for squashfs sss 'eosnobody' file access without ACL entries\n* FUSEX: allow to open a squashfs image file client side even if we don't have R mode on the parent directory\n\n\n``v4.8.47 Citrine``\n===================\n\n2021-05-17\n\nBug\n---\n\n* [EOS-4716] - quota zeroes the counters of used bytes/files from the quota node\n\n\nNew Feature\n------------\n\n* [EOS-4712] - Support LOCK_MAND in eosxd\n\n\n``v4.8.46 Citrine``\n===================\n\n2021-05-07\n\nBug\n----\n\n* FST: Don't free internal jerasure structs, these will be cleaned up when the FST is shutdown\n\n\n``v4.8.45 Citrine``\n===================\n\n2021-05-06\n\nBug\n----\n\n* [EOS-4695] - Select default KRB5 token location\n* [EOS-4697] - LRU uses wrong prefetch type\n* [EOS-4699] - Screen both mappings (uid,gid) in vid set before setting any config value\n* [EOS-4700] - Space policies interfere with conversion jobs\n* [EOS-4702] - Don't redirect to FSTs if not enough locations are available in EC layouts\n* [EOS-4704] - Memory leak when using the jerasure library\n\nNew Feature\n------------\n\n* [EOS-4705] - Block multi-source reading for EC files\n\nTask\n-----\n\n* [EOS-4684] - Make the \"file archived\" GC aware of different EOS spaces\n\nImprovement\n-----------\n\n* [EOS-4691] - Improve the locking primitives in FuseServer caps\n\n\n``v4.8.44 Citrine``\n===================\n\n2021-04-30\n\nBug\n---\n\n* FST: fix bug introduced with a checksum reset in case of non-sequential writing\n\n\n``v4.8.43 Citrine``\n===================\n\n2021-04-21\n\nBug\n---\n\n* [EOS-4669] - eos file verify need to be triggered twice in order to work\n* [EOS-4674] - Empty FSCK report seemingly after FST slow upgrade\n* [EOS-4676] - Crash when checking for recursive deletion\n* [EOS-4677] - FST deadlock when updating the scanner config\n* [EOS-4678] - MGM crash when removing a file system\n* Fix interference between master-slave setup and various internal services\n  like LRU, drainer and converter that should only run in a master MGM.\n\nImprovements\n------------\n\n* Add fileTruncateAsync API to the file IO interface\n\n\n``v4.8.42 Citrine``\n===================\n\n2021-04-14\n\n\nBug\n----\n\n* [EOS-4545] Option for eosxd mounts to block symlinks walking up the hierarchy\n\nImprovements\n------------\n\n* Drop the use of folly concurrent map and use internal implementation\n* Add job for CentOS8 Stream packages\n\n\n``v4.8.41 Citrine``\n===================\n\n2021-04-14\n\n\nBug\n----\n\n* [EOS-4607] - The command eos node config does not accept 'off' when using configstatus\n* [EOS-4627] - FSCK collected time changed after restart\n* [EOS-4629] - Checksum not recomputed after certain truncation operations\n* [EOS-4657] - File in draining with both FST checksums to 0x00\n* [EOS-4659] - Debug command broken\n* [EOS-4653] - Krb5 memory leak in CredentialValidator\n* [EOS-4660] - Potential cross-site scripting vulnerability in the EOS-HTTP\n* [EOS-4639] - Fix possible memory leak when using dense_hash_set objects\n* [EOS-4635] - Failure to share with egroups containing underscore\n* FST: Avoid early return in case of HTTP partial content like for example for range requests\n\nNew Feature\n-----------\n\n* [EOS-4623] - Create an utils script to setup a development environment on CentOS7/8\n* [EOS-4062] - Centos8: support \"KCM\" Kerberos cache\n* [EOS-4609] - Support for excess replicas/stripes\n\nImprovements\n------------\n\n* [EOS-4575] - Error on eos find command when tmp file cannot be created\n* [EOS-4617] - Quota option to provide only the quota of the specified quota node\n* [EOS-4658] - EOS workflow engine should not insist on the W_OK mode bit\n* Fsck improvements when dealing with detached files in general and also handling\n  wired cases where a file is detached but its parent id is not properly marked as 0\n\n\n``v4.8.40 Citrine``\n===================\n\n2021-02-03\n\nBug\n----\n\n* [EOS-4506] - Slowness when changing fs configurations when using eos space\n* [EOS-4540] - FST flips status from online to offline and back when cfg.status=off\n* [EOS-4582] - investigate far-in-the-future mtime, robustify \"eos fileinfo\"\n* MGM: Fix drain for RAIN 0-size files\n\nImprovements\n-------------\n\n* MGM/HTTP: Allow running XrdHttp without the need for token authentication\n* ALL: Improve logging functionality to avoid the long tail of performance\n\nNote\n----\n\n* Upgrade to XRootD-4.12.8\n\n\n``v4.8.39 Citrine``\n===================\n\n2021-02-08\n\nBug\n----\n\n* [EOS-4539] - FST crash on shutdown in eos::common::DbMapT::iterate()\n* [EOS-4574] - Crash in HandleVOMS when role is not present\n\nImprovement\n------------\n\n* Improve buffering and memory operations for RAIN layouts\n* [EOS-4525] - Include in acl man page the difference between sys.acl and user.acl\n* [EOS-4534] - Check compatibility of libXrdVoms.so with the HTTP interface\n* [EOS-4541] - Add a log message when a `ns recompute_quotanode` finishes\n\nNote\n----\n\n* Update to XRootD-4.12.7\n\n\n``v4.8.38 Citrine``\n===================\n\n2021-02-02\n\nBug\n----\n\n* [EOS-4573] - ZMQ threads jump into eternal parsing error state\n* COMMON: Compensate for the missing protocol info for HTTP transfers also in the SecEntity::ToKey method\n* SPEC: Make sure the debug info is not stripped from the binaries\n* MGM: Avoid to refresh directory MD all the time after a deletion\n\nImprovements\n------------\n\n* FST: Allow XRootD env variables to override default XrdCl timeouts in EOS\n* Deal with a list of VOMS roles/groups\n\n\n``v4.8.37 Citrine``\n===================\n\n2021-01-19\n\nImprovements\n-------------\n\nALL: Improve the logging info evaluation which is now done only if the log line\n  is to be actually printed.\nMGM: Add hex dump of ZMQ messages received from the FUSEX clients\n\n\n``v4.8.36 Citrine``\n===================\n\n2021-01-18\n\nBug\n---\n\n* NS: Make sure the dense_hash_maps used for storing file ids for the file systems\n  don't grow forever and call resize(0) to reclaim memory when elements are deleted.\n* MGM: inherit file ACLs when overwriting existing files and add instance test cases\n\n\n``v4.8.35 Citrine``\n===================\n\n2021-01-07\n\nBug\n----\n\n* FST: Fix logic when enabling/disabling async close\n* FST: Properly align the writes for PUT requests\n* CONSOLE: Fix memory corruption issues with eos cp\n* MGM: fix webdav free quota bytes computation\n\nNew Feature\n------------\n\n* [EOS-4545] - Option for eosxd mounts to block symlinks walking up the hierarchy\n\n\n``v4.8.34 Citrine``\n===================\n\n2020-12-17\n\nNote\n----\n\n* Fix spurius errno triggering an exception in proc/mgm/Fusex\n\n\n``v4.8.33 Citrine``\n===================\n\n2020-12-14\n\nNote\n----\n\n* This version is built against XRootD-4.12.6 which contains some important fixes for\n  HTTP TPC transfers.\n\n\n``v4.8.32 Citrine``\n===================\n\n2020-12-11\n\n\nBug\n----\n\n* [EOS-4499] - EOSHOME-i04 crash in eos::fusex::cap::clientuuid ()\n* [EOS-4504] - Persistent ESTAB connections on the FUSEX port from 'bogus' clients\n* [EOS-4536] - SIGSEGV around eos::mgm::FuseServer::Caps::Store\n\n\n``v4.8.31 Citrine``\n===================\n\n2020-12-07\n\nBug\n---\n\n* MGM: Reduce scope of eos::mgm::FuseServer::Client write lock to avoid deadlock\n* MGM: Skip quota updates on the slaves as this might corrupt the ns\n* EOS-4520 MGM: fix treesize changes when moving directory trees via FUSE\n\nImprovements\n------------\n\n* MGM: Add namespace stats entry for newfind\n\n\n``v4.8.30 Citrine``\n===================\n\n2020-12-03\n\nBug\n----\n\n* [EOS-4498] - MGM slowness in eoshome-i02\n* [EOS-4500] - EOSHOME-i01 (Apparently - Deadlock)\n* [EOS-4519] - Namespace deadlock (EOSPUBLIC)\n* [EOS-4524] - EOSCMS unresponsive\n* MGM: Prevent the prefetcher from bypassing the limits on the number of results\n returned when using by the find functionality\n* MGM: enforce eos access interface being only for admins\n\n\n``v4.8.29 Citrine``\n===================\n\n2020-12-01\n\nBug\n----\n\n* [EOS-4505] - Cannot xrdfs prepare -s in EOS with no write access`\n* [EOS-4515] - HTTP PUT stores corrupted file\n* [EOS-4521] - MQ: Crash in the XrdMqOfs::stat method\n\nImprovements\n-------------\n\n* MGM: Improve FuseXCast notifications sent during the rename operation\n* MGM/FUSE: Make the mutex for Caps and Client objects blocking\n* MGM: TGC now uses tgc.freebytesscript if set and not empty\n\n\n``v4.8.28 Citrine``\n===================\n\n2020-11-13\n\nImprovements\n------------\n\n* MGM: Modified RealTapeGcMgm::getSpaceStats() to give the exact same result as `eos space ls spinner -m`\n* FUSEX: decouple stat mutex from disk activiy - reduce mutex scopres in .stats file thread producing statistics output\n* MQ: Do broadcast all stat.* params as some are needed back on the FST side\n\n\n``v4.8.27 Citrine``\n===================\n\n2020-11-12\n\nBug\n----\n\n* [EOS-4410] - intermittent mgm failover and offline FST\n* [EOS-4482] - Converter always uses default.0 as scheduling group\n* [EOS-4484] - Http in/out traffic accounting is broken\n* [EOS-4487] - LRU add switch for the new converter\n* [EOS-4488] - LRU requires the converter to update ctime of converted files\n* [EOS-4492] - Fix ns locking used in the LRU\n* [EOS-4494] - New converter uses only default.0 as scheduling group\n\nImprovement\n-----------\n\n* [EOS-4486] - LRU refresh once the interval is changed\n* [EOS-4489] - Add basic unit tests for the ConvertInfo class\n* [EOS-4490] - Archive should evict files from disk cached after a successful recall\n\n\n``v4.8.26 Citrine``\n===================\n\n2020-11-02\n\nBug\n----\n\n* MGM: Fix crash when accessing file system which is null when iterating over\n  file systems in a group/space.\n\nImprovement\n-----------\n\n* [EOS-4481] - Tape garbage collector should notice file conversion jobs and also open for read requests\n* Enforce check for QuarkDB 0.4.2 minimum version\n\n\n``v4.8.25 Citrine``\n===================\n\n2020-10-27\n\n\nBug\n----\n\n* MGM: Fix quota refresh initialization\n* [EOS-4466] - eos newfind still bogus with \"-f/-d\" filters\n* [EOS-4477] - 'eos ls' bypasses permission check when result is cached\n\nNew feature\n-----------\n\n* FST: Tool to create readv pattern and check the result of readv requests done\n  against different endpoints. Used to check for RAIN readv correctness.\n\n\n``v4.8.24 Citrine``\n===================\n\n2020-10-20\n\nNote\n----\n\n* Release based on XRootD 4.12.5 which addresses a couple of issues in the XrdHttp component\n\nImprovement\n------------\n\n* [EOS-4464] - Latency Investigations on EOSHOME with v 4.8.22\n* [EOS-4468] - Allow open for read requests to trigger implicit prepare requests for offline files\n* [EOS-4470] - EOSCTA prepare logic within the MGM should use mgmofs.alias if set\n* Debug symbols are no longer stripped as this was leading to a crash in gdb and\n  consequently the eos-debuginfo package is no longer created.\n\n\n``v4.8.23 Citrine``\n===================\n\n2020-10-09\n\nBug\n----\n\n* [EOS-4405] - mgm crash on eos::mgm::Stat::PrintOutTotal ()\n* [EOS-4449] - Deadlock triggered when changing eos fs configstatus in a new machine\n* [EOS-4457] - FST: Crash when scanning list of unlinked files\n* [EOS-4460] - MGM does not correctly reply to Xrd config query for TPC delegation\n* [EOS-4461] - FST exception not caught in RequestRateLimit\n\nImprovement\n-----------\n\n* FST: Remove transaction directory/functionality\n* FST: Properly align XrdHttp and EosHtpp buffers during PUT requests\n\nNew Feature\n-----------\n\n* MGM: Add QClient RTT statistics displayed in the \"eos ns\" command\n\n\n``v4.8.22 Citrine``\n===================\n\n2020-09-30\n\nBug\n---\n\n* SPEC: adding missing mount helper scripts (packaging issue)\n* SPEC: Avoid richacl for CentOS 8 until RPMs are provided\"\n* MGM/FST: Stop the libmicrohttp daemon in the destructor of the MGM/FST HttpServer\n  derived classes otherwise the Handler method might still be called after the\n  derived classes are destructed (but before MHD_stop_daemon is called in the\n  common HttpServer) causing a SEGV due to \"pure virtual method called\" EOS-4438\n\nImprovements\n------------\n\n*  MGM: Speed up the shutdown of the routing thread\n\n\n``v4.8.21 Citrine``\n===================\n\n2020-09-29\n\nBug\n---\n\n* COMMON: Fix bug in thread pool implementation\n\n\nImprovements\n------------\n\n* MGM/FUSEX: Add prefetching of namespace metadata where necessary\n* MGM: Fsck - don't mark 0-size files without replicas as rep_missing_n\n* MGM: Fsck - improve handling of m_mem_sz_diff errors\n* MGM/FST: Move debug command out of MQ and use XRootD query command to modify the log level\n* MGM: Move fsck command out of MQ and use XRootD query command to collect the fsck responses\n* MGM/FST: Move resync command out of MQ and use XRootD query to send such requests\n* MGM/FST: Move rtlog command out of MQ and use XRootD query to send such requests\n* MGM/FST: Move deletion scheduling out of MQ and implement it using XRootD query commands\n* MGM/FST: Move verify command out of MQ and use XRootD query command for such requests\n* BUILD: new way to build SELINUX policies\n\nNew Feature\n------------\n\n* [EOS-4431] - 'rm -rf' return directory not empty if query exceeds default user limit of 100k files\n* [EOS-4442] - Add a '-0' option to file touch\n\n\n\n``v4.8.20 Citrine``\n===================\n\n2020-09-22\n\nBug\n---\n\n* MGM: unlimited scope of added missing Access mutex in PROC_BOUNCE_NOT_ALLOWED macro creates mutex inversions\n\n``v4.8.19 Citrine``\n===================\n\n2020-09-21\n\nBug\n---\n\n* COMMON: fix XRootd 4.12.4 user name masking (WARNING: supports now uids only up to 1M)\n\n``v4.8.18 Citrine``\n===================\n\n2020-09-17\n\nBug\n---\n\n* MGM: add missing mutex in access rejection macros\n\nImprovement\n-----------\n\n* MGM: improve mutex contention in Access commands (particular in combination with QDB Config)\n* MGM: adding Prefetcher in various places\n\n``v4.8.17 Citrine``\n===================\n\n2020-09-16\n\nBug\n---\n\nCOMMON: adapt to new * => _ mapping of xrootd connection names for FUSE ID mapping\n\n``v4.8.16 Citrine``\n===================\n\n2020-09-16\n\nBug\n---\n\nMGM: fix bug where a FuseX broadcast is run while the namespace write lock is held\nSELINUX: add missing rules for 'mount' to work with default SE settings\n\nImprovement\n------------\n\n* [EOS-4424] - Parse a second local eosxd configuration file\n* [EOS-4427] - Show where in the code a mutex is held after exceeding a given threshold\n\n\n``v4.8.15 Citrine``\n===================\n\n2020-09-09\n\nImprovement\n------------\n\n* Release based on XRootD 4.12.4\n\n\n``v4.8.14 Citrine``\n===================\n\n2020-09-09\n\nBug\n----\n\n* Release based on XRootD 4.12.3\n* [EOS-4399] - Fusex repair functionality corrupts files\n\n\n``v4.8.13 Citrine``\n===================\n\n2020-09-01\n\nBug\n----\n\n* [EOS-4412] - reduce latency due to scheduling deletions (long lasting view read locking)\n* [EOS-4407] - block volume EDQUOT client-side with the first occurrence of EDQUOT on a directory\n* [EOS-4364] - prefer EEXIST over EACCESS in eosxd mkdir\n* NS: fix command executed by drop-empty-cid\n\nImprovement\n-----------\n\n* [EOS-4408] - add option to hide 'eos.*' attributes in eosxd listxattr\n* FUSEX: load OAUTH ticket file when creating a trusted credential to have the proper jail prefixes used with containerizat\n* MGM: make LRU engine less chatty\n* NS: Implement ns-inspect command to drop empty directories\n\n\n``v4.8.12 Citrine``\n===================\n\n2020-08-25\n\nBug\n----\n\n* [EOS-4389] - EOS does not install on Macs\n* [EOS-4390] - EOS for Mac is missing libssl.1.0.0.dylib\n* [EOS-4391] - EOS for Mac is missing libXrdSecProt.so\n* [EOS-4400] - mgm crash in n __gnu_cxx::__verbose_terminate_handler()\n\nTask\n-----\n\n* [EOS-3998] - Modifying the content of a file only changes mtime (not ctime)\n\n``v4.8.11 Citrine``\n===================\n\n2020-08-05\n\nBug\n----\n\n* [EOS-3711] - XrdMgmOfs::mkdir does not honor mode parameter\n* [EOS-3843] - Avoid to accept \"unacceptable\" block sizes (sys.forced.blocksize)\n* [EOS-3991] - Trying to stat symbolic links in Recycle bin\n* [EOS-4153] - Misleading error for lock order check when using timed locks\n* [EOS-4279] - MGM restart corrupts mtime in a directory after mkdir + quota node creation\n* [EOS-4319] - eos-ns-inspect reports wrong value for some extended attributes\n* [EOS-4367] - eoscp check if hierarchy exists before attempting to create it\n* [EOS-4369] - eos commands try to follow (non-EOS) symlinks\n\nTask\n-----\n\n* [EOS-3775] - Rename stat.drain.* and friends to local.drain.*\n* [EOS-4280] - User with no files and no quota limit should be removed from the list regardless of MGM restart?\n* [EOS-4293] - Add JSON format for `eos who`\n\nImprovement\n------------\n\n* [EOS-4308] - Update documentation for migrating to QDB config\n* [EOS-4318] - Include extended attributes in eos-ns-inspect print\n* [EOS-4371] - \"eos file info inode\": give error on \"hex\" input\n\n\n``v4.8.10 Citrine``\n===================\n\n2020-07-24\n\nBug\n----\n\n* FUSEX: fix the real problem of EOS-4338 which is the destruction of the object before all read-ahead calls had been collected\n\nImprovement\n-----------\n\n* FUSEX: add 'trace' option and enable all debug levels in the xattr interface\n* FUSEX: trace 'slow' flush operations if they take more than 2000ms\n\n\n``v4.8.9 Citrine``\n==================\n\n2020-07-20\n\nBug\n----\n\n* MGM: suppress commit of left-over entry-gateway replica happening during eosxd recovery - fixes EOS-4340\n* FUSEX: bypass recursive rm detection by default if it is not enabled.\n* FUSEX: avoid SEGV when read-ahead callback comes and didn't get a buffer - fixes EOS-4338\n* FUSEX: fix repair when a write error occurs after the file is larger than the pre-fetch size and the first journal was not yet flushed\n* FUSEX: remove 'return' short cut to see timings of readlink\n\n\n``v4.8.8 Citrine``\n==================\n\n2020-07-07\n\nBug\n----\n\n* FUSEX: check in journalcache::reset if there is actually an open journal - fixes EOS-4322\n* FUSEX: disable FST checksum checks for all reads in general, which can break recovery if not\n\nImprovement\n-----------\n\n* FUSEX: close read-only files async in IO flush - fixes EOS-4328\n\n\n``v4.8.7 Citrine``\n===================\n\n2020-07-06\n\nImprovements\n------------\n\n* FUSEX: don't print 'IO blocked' for the root inode, since this frequently happens after wake-up\n* FUSEX: print some user information if GETCAP results in EPERM\n* FUSEX: print some debug information if journal()->reset() fails\n* SPEC: Disable running spec scriplets if file /etc/eos/yum_with_noscripts is present\n\n\n``v4.8.6 Citrine``\n===================\n\n2020-07-02\n\nBug\n----\n\n* MGM: don't place new replicas for read if filesize=0 and a replica is offline\n\n\n``v4.8.5 Citrine``\n===================\n\n2020-07-01\n\nBug\n----\n\n* [EOS-4317] - Don't use repairOnClose for eosxd clients\n* [EOS-3994] - MGM should not require mgmofs.configdir if config is stored in QDB\n\nImprovement\n------------\n\n* [EOS-4311] - filesystem move is slow with in-QDB config and the lock taken triggers high node heartbeats\n* [EOS-4312] - Allow to move a filesystem to a different node via a command\n* [EOS-4313] - _find should only prefetch container metadata if no_files is set\n\n\n``v4.8.4 Citrine``\n===================\n\n2020-06-24\n\nBug\n----\n\n* [EOS-4305] - _remdir sends fusex notifications under namespace lock\n\nImprovement\n------------\n\n* [EOS-3851] - do not `drainwait` group balancing on terminate drain statuses\n* [EOS-4306] - Add namespace mutex acquisition latency stats to \"eos ns\"\n* Add option to store the LevelDB on the data disk rather than root partition\n\n\n``v4.8.3 Citrine``\n===================\n\n2020-06-19\n\nBug\n----\n\n* [EOS-4295] - Folder remove fails while deleting child version files (with Operation not permitted)\n* MGM: remove timeordered caps entries if there insertion time has passed, don't rely on the cap\n  validity because it can be updated in the meanwhile\n* MGM: default max children for eosxd listing to 128k not 128M\n\nNew feature\n------------\n\nMGM: Implement helper method for relocating filesystem to different FST\n\nImprovement\n------------\n\n* Build on top of XRootD 4.12.3 that fixes some HTTP crashes\n* XRootD5 compatibility\n* SCITOKENS: Build libEosAccSciTokens.so as part of the eos release\n* FST: Provide digest information if want-digest header present according to RFC3230\n* [EOS-4299] - ResyncFileFromQdb error after FST upgrade to 4.8.2\n\n\n``v4.8.2 Citrine``\n===================\n\n2020-06-11\n\nBug\n----\n\n* [EOS-4037] - eosxd gets SIGBUS in journalcache::read_journal()\n* [EOS-4083] - eosxd abort() with \"std::bad_alloc\" under journalcache::get_chunks\n* [EOS-4276] - Add extra checks while updating the directory e-tag on 0-size file updates\n* [EOS-4282] - eos-client-4.7.16-1 requires xrootd-server-libs\n* [EOS-4286] - Cannot set `eos.mtime` using xrdcp opaque query\n* [EOS-4288] - `eos file adjustreplica` : error: invalid argument for file placement (errc=22) (Invalid argument)\n* [EOS-4289] - Replicas dropped after a conversion of a non-healthy file\n\nImprovement\n------------\n\n* [EOS-4284] - Allow automatic layout conversion hooks for file injection and file creation\n* [EOS-4285] - negative cache entries are not served from eosxd cache\n\n\n``v4.8.1 Citrine``\n===================\n\n2020-06-02\n\nBug\n----\n\n* SPEC: Fix CentOS8 Koji build\n* MGM: Exclude tape locations from the converter merge procedure\n\n\n``v4.8.0 Citrine``\n===================\n\n2020-06-02\n\nBug\n----\n\n* [EOS-3966] - Fix prefetching especially for RAIN and make it adaptive\n* [EOS-4035] - FST service not starting (timeout) if there are too many log files\n* [EOS-4214] - eos file convert behaviour\n* [EOS-4259] - eosxd crash under metad::add_sync() /  EosFuse::create()\n* [EOS-4260] - eosxd crash data::dmap::ioflush()\n\nTask\n----\n\n* [EOS-3976] - The converter does not honour the source file checksum if sys.forced.checksum is set on /eos/<instance>/proc/conversion\n\n\n``v4.7.16 Citrine``\n===================\n\n2020-05-18\n\nBug\n---\n\n* [EOS-4203] - reading empty missing replica file triggers commit & mtime update\n* [EOS-4215] - ns time printing broken in fileinfo command\n\nImprovements\n-------------\n\n* CMAKE: Refactor and simplify the cmake code to move to a target based approach\n\n\n``v4.7.15 Citrine``\n===================\n\n2020-05-14\n\nBug\n---\n\n* [EOS-4299] Fix stat counters update frequency\n* MGM: Add missing lock to MgmStats in the stall functionality\n* MGM: stat.st_nlink is an UNSIGNED integer.  Replaced dangerous -1 logic with safe unsigned logic\n\n\n``v4.7.14 Citrine``\n===================\n\n2020-05-11\n\nBug\n---\n\n* [EOS-4210] - `eos fs ls -d` shows disks which are actually not in drain (stat.drain is empty)\n\nNew Feature\n-------------\n\n* [EOS-4205] - Be able to hide .sys.v# like folder/files to users\n\nImprovement\n------------\n\n* [EOS-4197] - Show available redundancy in 'ls -y '\n* [EOS-4207] - Add Quota (ls) command to GRPC interface\n* [EOS-4212] - Review POSIX permission behaviour in eosxd & enable overlay behaviour\n\n\n``v4.7.13 Citrine``\n===================\n\n2020-05-08\n\nBug\n----\n\n* [EOS-4084] - 'eos fs mv'  returns 0 even in case of errors\n* [EOS-4171] - GDB seg faults when taking backtraces of EOS daemons\n* [EOS-4182] - FUSEX: 'Invalid argument' instead of 'Permission denied' on non-cached access to restricted directory\n* [EOS-4183] - eosxd: unable to delete, temporary I/O error on directory\n* [EOS-4187] - MGM: fs commands return random \"return codes\"\n* [EOS-4188] - Crash in XrdMgmOfsFile::open\n* [EOS-4189] - EOSHOME-I00 crash on XrdMgmOfsFile::open\n* [EOS-4209] - MGM: sys.acl does not accept denial of some permissions\n\nImprovement\n------------\n\n* [EOS-4113] - log: add fs number to the MGM logs for FST redirections\n* [EOS-4169] - Missing fsids in file info -m and json when NA context (it is not the case in normal file info)\n\n\n``v4.7.12 Citrine``\n===================\n\n2020-04-29\n\nBug\n----\n\n* [EOS-4178] - use 'x' bits from ACL+POSIX for directories, while only from POSIX for files\n\n``v4.7.11 Citrine``\n===================\n\n2020-04-28\n\nBug\n----\n\n* [EOS-3867] - MGM redirecting to itself\n* [EOS-4110] - `eos fs mv` not working properly for multi-fst instances\n* [EOS-4122] - `eos file touch` does not create a file if it not exists\n* [EOS-4131] - MGM: Broken logic in fs add leads to various inconsistencies\n* [EOS-4133] - MGM: Deadlock when booting the in memory namespace\n* [EOS-4137] - MQ: Exceeded message backlog never recovers\n* [EOS-4139] - eosxd sees EIO when rate limiter sends stalls\n* [EOS-4140] - Allow the eos command-line tool to modify the disk layout of a \"tape only\" file\n* [EOS-4150] - MGM: Acl should check for update flag present\n* [EOS-4151] - Broken shutdown sequence for EOS daemons\n* [EOS-4168] - rename & move of symlinks not supported in FuseServer\n\nNew Feature\n------------\n\n* [EOS-3415] - feature: `eos status` view\n\nImprovement\n------------\n\n* [EOS-4011] - Allow \"eos rm\" by fid for weird cases\n* [EOS-4091] - Add LRU caching to XrdMgmOfsDirectory class\n* [EOS-4092] - Add LRU caching to proc::ls function\n* [EOS-4129] - Add STAT equivalent functionality to GRPC\n* [EOS-4142] - Only set filesize in MGM when eosxd has opened a file on FSTs\n* [EOS-4152] - MGM: GroupBalancer improve cancellation/cleanup by using std::thread\n* [EOS-4166] - Enforce wait-for-flush behaviour on file creation for a list of given executables\n* [EOS-4167] - Enhance fsck repair to take an fsid and error type\n\n\n``v4.7.10 Citrine``\n===================\n\n2020-04-17\n\nBug\n----\n\n* [EOS-4103] - FUSEX marks as 0600 file as \"executable\"\n* [EOS-4112] - Deadlock between mdstackfree and data::unlink\n* HTTP/FST: Fix crash by replying with 411 when a PUT without Content-Length is attempted\n\nImprovement\n------------\n\n* [EOS-4108] - Merge tape replicas in conversion jobs\n* [EOS-3913] - eos report is reporting deletion of files that were never transferred in the first place\n* [EOS-4104] - Allow to select, O_DIRECT O_SYNC O_DSYNC via CGI\n\n\n``v4.7.9 Citrine``\n===================\n\n2020-04-08\n\nBug\n----\n\n* [EOS-4095] - MGM crash in `eos::common::Logging::log`\n* [EOS-4096] - Crash due to missing args in FuseServer error message\n\nImprovement\n------------\n\n* NS: Use std::mutex in the NS LRU implementation instead of eos::common::RWMutex\n  for better performance\n* [EOS-4003] - Export sys xattr to trusted machines through FUSEX\n\n\n``v4.7.8 Citrine``\n===================\n\n2020-04-06\n\nBug\n---\n\n* [EOS-4082] MGM: remove sym link files from the file view directly\n* FST: Fix misuse of [] operator on map which can lead to crashes\n* COMMON: Make sure we use the same shared_mutex implementation (cv)\n  everywhere and update qclient\n\nImprovement\n------------\n\n* COMMON: Encapsulate VOMS mapping functionality and reuse it for both gsi\n   and http authentication\n* [EOS-3960] - eos-ns-inspect improvements\n\n\n``v4.7.7 Citrine``\n===================\n\n2020-04-01\n\nBug\n---\n\n* MGM: fix lock order violation in FuseServer file creation\n* NS: Fix inverted condition when calculating etag for md5\n* Fixes bit-flip error when setting rsp.is_on_tape\n\n\nImprovements\n-------------\n\n* MGM: disable fusex versioning on rename - can by defining  xattr 'sys.fusex.versioning'\n* MGM: clone/hard links/recycle bin\n* MGM: Made tape-aware GC persistent between MGM restarts\n* MGM/FST The sys.cta.archive.objectstore.id xattr of a file is now set when it is queued for archival to tape\n\n\n``v4.7.6 Citrine``\n===================\n\n2020-03-30\n\nBug\n----\n\n* [EOS-4063] - Error creating version folder\n* [EOS-4069] - Git clone does not work\n\n\n``v4.7.5 Citrine``\n===================\n\n2020-03-23\n\nBug\n----\n\n* This only fixes a Koji build issue otherwise it's identical to 4.7.4\n\n\n``v4.7.4 Citrine``\n===================\n\n2020-03-23\n\nBug\n----\n\n* [EOS-4013] - EOSBACKUP \"FST still misses the required capability key\"\n* [EOS-4046] - sync client re-downloading files\n\nNew Feature\n------------\n\n* [EOS-4057] - Allow fine-graned stall rules for eosxd access and restic bypass\n\nImprovement\n------------\n\n* [EOS-4056] - Make the TPC key validity configurable\n\n\n``v4.7.3 Citrine``\n===================\n\n2020-03-12\n\nBug\n----\n\n* [EOS-4042] Cannot see the content of a version\n\n\n``v4.7.2 Citrine``\n===================\n\n2020-03-09\n\nBug\n----\n\n* [EOS-3920] - eosxd crash in EosFuse::DumpStatistic()\n* [EOS-4016] - FUSEX: file content mixup / data corruption\n* [EOS-4025] - utimes call does not set cookie in disk cache\n* [EOS-4031] - fst crash in eos::fst::FileSystem::UpdateInconsistencyInfo() while\n  registering fss\n* [EOS-3605] - FUSEX crash in metad::pmap::lru_add()\n* [EOS-4029] - eosxd abort() in Json::Value::isMember - \"Json::Value::find(key, end, found): requires objectValue or nullValue\"\n\nImprovement\n------------\n\n* [EOS-3745] - Allow static mapping of HTTP access to a non-root user\n\n\n``v4.7.1 Citrine``\n===================\n\n2020-03-06\n\nBug\n----\n\n* FST: Disable async close functionality that triggers a bug in XRootD due to memory corruption - seen in EOSPROJECT\n* EOS-4027: RAIN file chunk dropped when chunk drain fails due to node being offline - seen in EOSALICEDAQ\n\n\n``v4.7.0 Citrine``\n===================\n\n2020-02-21\n\nNew Feature\n------------\n\n* Provide backup-clone functionality\n* Provide tape garbage collector base-line implementation\n* [EOS-3956] - Provide the expected checksum per block in the namespace in RAIN files\n\nBug\n----\n\n* [EOS-3377] - find -b shows wrong accounting for RAIN files\n* [EOS-3867] - MGM redirecting to itself\n* [EOS-3912] - Balancing prevented for RAIN files\n* [EOS-3917] - SetNodeConfigDefault might be called before gOFS->mMaster has been initialized\n* [EOS-3954] - eos documentation guides people towards an insecure QDB deployment\n* [EOS-3969] - Bug in NextInodeProvider raises possibility of creating two containers with colliding IDs\n* [EOS-4000] - Spurious errors of fusex-benchmark test 13\n\nTask\n-----\n\n* [EOS-3819] - Create automatically the missing directories when recovering files\n\nImprovement\n------------\n\n* [EOS-3370] - RFE: \"eos file check\" , \"eos file info\" should show 'user.eos.filecxerror' status for full-replica\n* [EOS-3967] - Extend redirection URL length accepted by the MGM\n\n\n``v4.6.8 Citrine``\n===================\n\n2020-01-22\n\nBug\n---\n\n* FUSEX: fix writer starvation triggered by EDQUOT errors\n* [EOS-3872] - FST should delete file on WCLOSE when archive request cannot not be queued\n* [EOS-3873] - Coredump in jerasure_matrix_to_bitmatrix\n* [EOS-3885] - Add \"tape enabled\" configuration attribute to /etc/xrd.cf.mgm\n* [EOS-3915] - FUSEX uses std::stoll instead of std::stoull to parse inodes, breaking new inode encoding scheme\n\nImprovement\n-----------\n\n* FUSEX: support oauth token files - see OS-9604\n* FUSEX: allow to track write buffers using 'eos fusex evict UUID sendlog'\n* FUSEX: add CERN automount script/configs and update SELINUX policies accordingly supporting SquashFS mounting\n* FST: support ISA-L accelerated adler/crc32c checksum\n* FST: add generic eos-checksum command\n* FST: support xxhash64,crc64 and sha256 as checksums\n* ALL: Add basic support for Macaroons and SciTokens\n\n\n``v4.6.7 Citrine``\n===================\n\n2019-12-16\n\nBug\n---\n\n* [EOS-3854] - Fixed SELinux policy regression bug which installed wrong file on SLC6\n\nImprovement\n-----------\n\n* [EOS-3886] - Enrich eosreport in the context of TPC\n\n``v4.6.6 Citrine``\n===================\n\n2019-12-09\n\nBug\n---\n\n* FUSEX: avoid starvation due to no quota error during open in flush-nolock\n* APMON: bump to latest version\n\nImprovement\n-----------\n\n* [EOS-3879] - Adding a field that reports free writable bytes\n* [EOS-3882] - eos report is not reporting deletion timestamp\n* CONSOLE: Suppress routing information for 'quota ls -m' requests\n\n``v4.6.5 Citrine``\n===================\n\n2019-12-05\n\nBug\n---\n\n* [EOS-3611] - MGM unresponsive, does not appear to recover on its own\n* [EOS-3715] - fst offline: Publisher cycle exceeded\n* [EOS-3827] - MGM Upgrade: After restarts prevent storage node heartbeats to increase\n* [EOS-3858] - ARCHIVE: Broken due to utimes silent error\n* [EOS-3864] - unable to boot filesystem after eos fs add\n* MGM: Remove sys.cta.objectstore.id xattr on successful retrieve\n\nImprovement\n------------\n\n* [EOS-3860] - Allow lock-free iteration over long directory listings\n* [EOS-3862] - eos client: hardcode RPM dependency on 'zeromq'\n* [EOS-3875] - Drop use of std::ptr_fun, std::not1\n* [EOS-3880] - RaftReplicator pipelines way too many pending batches inside QClient\n\n\n``v4.6.4 Citrine``\n===================\n\n2019-12-03\n\nBug\n---\n\n* [EOS-3854] MISC: Version SELinux policy files for targeted platforms (SLC6 and CC7)\n\n\n``v4.6.3 Citrine``\n===================\n\n2019-11-20\n\nBug\n---\n\n* [EOS-3717] FUSEX: fix lru_xyz SEGV in eosxd\n* [EOS-3853] NS: more options to filter with inspect command\n* FUSEX: fix WR buffer exhaustion triggered by out-of-quota writes\n\nNew Feature\n-----------\n\n* allow IPC connections via ZMQ to bypass xrd-threadpool for admin commands - usage 'eos ipc:// ...'\n* make the maximum number of listable entries by eosxd configurable: EOS_MGM_FUSEX_MAX_CHILDREN=32768\n\n\n``v4.6.2 Citrine``\n===================\n\n2019-11-18\n\nBug\n---\n\n* fix eosxd messaging for renames, commits, versioning\n* avoid spurious entries in quota map\n* [EOS-3692] print critical messages when FUSEx throws runtime_errors\n* [EOS-3793] prefix recycle restore keys with fxid: and pxid: to avoid ambiguities\n* [EOS-3798] suppress atomic/versioning for 'verify --commit' workflows\n* [EOS-3808] broadcast externally versioned files into fusex network\n* [EOS-3822] avoid SEGV in FUSEx recovery\n* [EOS-3823] avoid infinite loop unlinkAllLoctions\n* [EOS-3829] parsing problem\n* [EOS-3833] avoid SEGV when logfile is not opened\n* [EOS-3834] console char replacement\n* [EOS-3839] avoid deadlock in lock order violation\n* [EOS-3845] create barrier in FST creation to avoid race condition under file creation from two clients\n* [EOS-3848] store exception in future\n* [EOS-3850] avoid SEGV in FUSEx deletion of non-existent objects\n\nNew Feature\n-----------\n\n* cta add-ons for multi-space usage\n* make fsck thread-pool configurable\n* json response format for xrdfs query prepare\n* stall logic for prepares\n* more options in eos-ns-inspect\n* decrease noserver FUSEx timeouts to 15/2 minutes (r/w)\n\n\n``v4.6.1 Citrine``\n===================\n\n2019-10-31\n\nBug\n---\n\n* Fix wrong linking in the eos-client package\n* General restructuring of the link dependencies\n\n\n``v4.6.0 Citrine``\n===================\n\n2019-10-30\n\nBug\n----\n\n* [EOS-2990] - FSCK on QuarkDB causes higher latency\n* [EOS-3437] - FST crash around eos::common::DbMapTypes::Tlogentry::~Tlogentry()\n* [EOS-3469] - no replica information on file check but the physical file is there\n* [EOS-3470] - eos verify: unable to verify ... no local MD stored\n* [EOS-3497] - Avoid ghost entries to fail the draining of a disk\n* [EOS-3689] - MGM crashed in XrdCl::Utils::CheckTPCLite()\n* [EOS-3726] - FST crash in eos::fst::Adler::Add (negative \"length\")\n* [EOS-3736] - FST registration causing locking issue\n* [EOS-3743] - 'eos fs rm' triggers the following error: \"cannot set net parameters on filesystem\"\n* [EOS-3751] - weird behavior of the geoscheduler when some FSTs changed the geotag\n* [EOS-3783] - Miniconda2-latest-Linux-x86_64.sh - no exec bit for 'python' from archive\n* [EOS-3790] - MGM gets stuck when using local QuarkDB MD lock\n* [EOS-3791] - Transfers timeout on EOS\\CERNBox home folders A G J K W\n* [EOS-3792] - eos quota not redirecting to proper home\n* [EOS-3799] - XrdMgmOfs::Emsg() calls strerror() which is NOT thread safe\n* [EOS-3802] - eos acl not setting acl's\n* [EOS-3803] - FUSEX client says \"Directory not empty\" on removal (bad caching?)\n* [EOS-3805] - EOS client links against system XRootD instead of eos-xrootd\n* [EOS-3806] - eoscp won't copy the file if the 'extra' stripes are missing\n\nTask\n----\n\n* [EOS-3583] - Repair logs (useful metadata)\n* [EOS-3591] - 'file info' resolves symlinks and displays info of the referenced file\n* [EOS-3710] - TPC from castor/ceph to EOS not working\n\nImprovement\n-----------\n\n* [EOS-3371] - RFE: update \"user.eos.filecxerror\" on FST checksum verification failures\n* [EOS-3750] - Change error message for adjustreplica\n\n\n``v4.5.13 Citrine``\n===================\n\n2019-11-15\n\nBug\n----\n\n* [EOS-3839] MGM: Fix lock inversion leading to deadlock when calling getmdlocation\n* [EOS-3729] FUSEX: fix bug in wait_flush method leading to a mix-up of rename/unlink records\n* MGM/FUSEX: Fix faulty assumption that getFile would raise an exception (had been\n  changed when Qdb was introduced) - fixes spurious EIO errors and 'Attempt to add\n  an existing file' messages.\n\n\n``v4.5.12 Citrine``\n===================\n\n2019-10-28\n==========\n\n* [EOS-3792] - eos quota not redirecting to proper home\n\nImprovement\n-----------\n\n* [EOS-3800] - Routing mechanism of proto commands\n\n\n``v4.5.11 Citrine``\n===================\n\n2019-10-22\n\nBug\n----\n\n* MGM: fix rare lockups observed due to wrong expectation of an exception thrown\n\n\n``v4.5.10 Citrine``\n===================\n\n2019-10-16\n\nBug\n----\n\n* [EOS-3736] - FST registration causing locking issue\n* [EOS-3737] - Possible eos file verify commands causing deadlock while restarting mgm\n* [EOS-3710] - TPC from castor/ceph to EOS not working\n* [EOS-3774] - FUSEX: fix recovery problem when files are truncated to 0 size\n* FUSEX: fix rc=EPERM for setxattr if not called by uid=0\n* FUSEX: fix possible out-of-memory scenario when applications keep writing on fatal\n  error conditions like out-of-quota\n\n\n``v4.5.9 Citrine``\n===================\n\n2019-09-11\n\nBug\n----\n\n* MGM: Update rights 'u' are implicit in 'w'\n* EOS-3721: Slave MGMs in old-implementation master-slave should refuse to boot on QDB-namespaces\n\n\n``v4.5.8 Citrine``\n===================\n\n2019-09-10\n\nBug\n----\n\n* FST: Fix FST metadata synchronization with the MGM info when delay is not respected\n\nImprovement\n-----------\n\n* FUSEX: Enable safe mode by default - when a file is created the client always gets\n  feedback if the FST open didn't work.\n\n\n``v4.5.7 Citrine``\n===================\n\n2019-09-09\n\nBug\n----\n\n* Fix bug in the MgmSync process which could crash the FST\n* [EOS-3633] - Many new commands are not compatible with old server version\n* [EOS-3696] - shell: \"cd ../../\" does nothing?\n* [EOS-3705] - Error when updating eos-archive\n* [EOS-3703] - FST not starting if mountpoint not present\n* [EOS-3684] - eosxd crash in debug() in EosFuse::readdir()\n* [EOS-3608] - Wrong help for space policy and no error message\n\nImprovement\n------------\n\n* [EOS-2725] - Missing usage example for some space parameters\n* [EOS-3694] - Add eos-fusex-tests to the pipeline\n* [EOS-3706] - Add 1m,1w,daily timebins to versioning similar to DFS\n* GRPC: Add version command implementation and other ns related operations\n\n\n``v4.5.6 Citrine``\n===================\n\n2019-08-26\n\nBug\n----\n\n* [EOS-3315] - eos file adjustreplica selects bad replica for replication\n* [EOS-3572] - Crash while reloading the config in eoslhcb\n* [EOS-3575] - EOSCMS - killed by SIGSEGV (around eos::mgm::GeoTreeEngine::applyBranchDisablings)\n* [EOS-3624] - eosxd SEGV eraseTS\n* [EOS-3669] - Wrong Routing when target path ends as <path>/.\n* [EOS-3678] - space define command doesn't set groupmod\n* [EOS-3680] - Space set subcommand affects all groups and nodes\n* [EOS-3687] - getQuotaNode throws an exception when called on a detached container, instead of returning nullptr\n* [EOS-3700] - eosxd SEGV apply\n* [EOS-3701] - eosxd SEGV lookup\n* [EOS-3704] - rename/stat/open handling of trailing '/'\n\nNew Feature\n------------\n\n* [EOS-3682] - gRPC container insert does not inherit extended attributes\n\nImprovement\n------------\n\n* [EOS-3474] - GroupBalancer logging\n\n\n``v4.5.5 Citrine``\n===================\n\n2019-08-07\n\nBug\n---\n\n* [EOS-3536] - fix hard-link cleanup problems seen with 'rm -rf' on git repositories\n* [EOS-3644] - adjust eosxd cache path filename hashing for physical inodes\n* [EOS-3643] - avoid ghost entries when files are overwritten and support reycle bin for those\n\n\nImprovements\n------------\n\n* [EOS-3638] - introduce file info detached field\n* speed-up shutdown for drain jobs\n* implement ns-reserve-id command\n* don't print byte-range locks per client ( get it with '-k' option )\n* filesystem class refactoring\n* clean-up empty eosxd cache directories\n* support proc results larger than 2G\n* timeout eosxd connections after 24h\n\n\n``v4.5.4 Citrine``\n===================\n\n2019-08-01\n\nBug\n---\n\n* [EOS-3622] - eoscp is not propagating the error code.\n* [EOS-3629] - Provide fallback for the quota command to old implementation\n* [EOS-3631] - port flag is ignored on eosfstregister script\n* [EOS-3632] - mv on FUSEX deterministically loose data\n* [EOS-3633] - Many new commands are not compatible with old server version\n\nQuestion\n---------\n\n* [EOS-3626] - eos mgm cannot contact to external eos instance via eos route\n\n\n``v4.5.3 Citrine``\n===================\n\n2019-07-25\n\nBug\n---\n\n* [EOS-455] - RFE: drop either fid: or fxid:, use the other consistently\n* [EOS-3577] - Crash in ReplicationTracker\n* [EOS-3579] - io stat shows negative values (overflow?)\n* [EOS-3585] - eosxd crash below cap::capflush() / metad::cleanup()\n* [EOS-3604] - Apply path mapping for eos rm command\n* [EOS-3609] - Wrong json format in file info when & are in pathnames\n* Fix bug related to interference between logrotation and QdbMaster setup for\n  high-availability observed at JRC.\n\nImprovements\n------------\n\n* Extend ns cache drop command to drop individual entries\n* Move the following commands to the protobuf implementation: access, quota,\n  config, node and space.\n* [EOS-3602] - Drop automatic conversion attempt from default output to JSON for\n  protobuf commands with JSON flag on. Each proto command will be\n  responsible of providing valid JSON output.\n* [EOS-3606] - Add birth time to a file's metadata when it is created/born\n\n\n``v4.5.2 Citrine``\n===================\n\n2019-06-27\n\nBug\n---\n\n* if eosxd is compiled without ROCKSDB support, it should not touch mdcachedir e.g. it has to stay empty - fixes EOS-3558\n* require eos-rocksdb on SLC6 and EL7 to have support for swapping inodes\n\n``v4.5.1 Citrine``\n===================\n\n2019-06-25\n\nBug\n---\n\n* [ EOS-3546 ] Apply remote quota updates if q-node has no file open\n\nNew Feature\n-----------\n\n* [ EOS-3548 ] Replication Tracker class (see docs/configuration/tracker)\n\n``v4.5.0 Citrine``\n===================\n\n2019-06-21\n\nBug\n---\n\n* [ EOS-3495 ] Handle out-of-quota open correctly in eosxd\n* [ EOS-1755 ] Don't irritate du with . entry size\n* [ EOS-3536 ] Fix hardlink deletion logic to avoid hidden entries after all references have been removed\n* [EOS-3279] - eos fs dumpmd RC wrong\n* [EOS-3396] - File with two 'bad' replicas: one has size mismatch, the other xsum mismatch\n* [EOS-3499] - eos-ns-inspect: Include again the libprotobuf dependency\n* [EOS-3522] - 'eos config dump --vid' prints dummy \"mgm.vid.key=<key>\", cannot  \"eos vid rm'\n* [EOS-3526] - eosxd crash in EosFuse::readlink(), NULL 'md' pointer\n* [EOS-3533] - eos find doesnt work with --fid and -0\n\nNew Feature\n-----------\n\n* [EOS-3532] - Allow default placement policies per space\n\nImprovement\n-----------\n\n* Provide optional GRPC service in MGM\n* Documentation improvements\n* Swap-in-out eosxd inodes with lru table into rocksdb DB\n* Block only running file drains from parallel draining\n* CTA GC monitoring in 'eos ns'\n* [ EOS-3514 ] Implement orphan detection in eos-ns-inspec\n* [ EOS-3490 ] Support printing mctime, ctime in eos-ns-inspec\n* [EOS-3409] - 'bind mount' FUSEX, no credentials: \"No such file or directory\"\n  instead of \"Permission denied\"\n* [EOS-3519] - Add the possibility to do attr ls with the fid/pid\n* [EOS-3520] - add pid to the json output of file info\n* [EOS-2020] - Use Table Formatter for geosched show tree and snapshot commands output\n* [EOS-3513] - Provide an exception when eos dumpmd <fsid> --path is not really empty\n* [EOS-3527] - FSCK dection tool: Classify size errors for not orphan files\n* [EOS-3531] - FSCK detection: Ignore size 0 files in the namespace in replica error detection\n* Move the \"group\" command to the Protobuf implementation\n* Move the \"io\" command to the Protobuf implementation\n* Move the \"debug\" command to the Protobuf implementation\n\n\n``v4.4.47 Citrine``\n===================\n\n2019-05-17\n\nBug\n---\n\n* freeze client RPATH to XRootD location used during build\n\nImprovement\n-----------\n\n* CTA module v 0.41\n* Extended 'prepare' for XRoot 4.4.10 (abort etc.)\n* Report detached files in 'eos-fsck-fs'\n* [ EOS-3483 ] - add container id in output of stripediff option\n* [ EOS-3484 ] - add location to output of stripediff option\n* [ EOS-3532 ] - introduce space default placement policies ( obsoletes per directory extended attributes for default placement policy)\n* use eos-protobuf3 eos-xrootd only on EL7 for tags like x.y.z-0, otherwise only eos-protouf3 on EL7 builds\n\n\n``v4.4.46 Citrine``\n===================\n\n2019-05-15\n\nBug\n---\n\n* Fix FST conversion from NS proto to Fmd\n* Fix RPATH configuration to force linker locations\n\nImprovement\n-----------\n* Implement 'eos fsck search' to forward FSCK from NS to FSTs\n* Expose 'eos resync' and 'eos verify -resync' to force FMD resynchronization on FSTs\n* Refactor ScanDir code\n\n``v4.4.45 Citrine``\n===================\n\n2019-05-14\n\n\nBug\n---\n\n* Introduce obsoletes statement in spec file for eos-protobuf3/eos-xrootd\n\nImprovement\n-----------\n\nFST: Refactor the ScanDir code and add simple unit tests\nFST: Encapsulate the rate limiting code into its own method\nFST: Start publishing individual fs stats\nNS: Add etag, flags to eos-ns-inspect output\n\n``v4.4.44 Citrine``\n===================\n\n2019-05-08\n\nBug\n---\n\n* FST: fix dataloss bug introduced in 4.4.35 when an asynchronous replication fails (adjustreplica cleaning up also the source)\n\n\n``v4.4.43 Citrine``\n===================\n\n2019-05-08\n\nImprovements\n------------\n* FUSEX: add compatibility mode for older server which cannot return getChecksum by file-id\n* CI: build with ubuntu bionic\n* NS: Add mtime, ctime, unlinked locations, and link name to eos-ns-inspect printing\n* CTA: configuration parameters for tapeaware garbage collector\n\n``v4.4.42 Citrine``\n===================\n\n2019-05-07\n\nImprovements\n------------\n\n* FUSEX: lower default IO buffer size to 128M\n* MGM: remove unnecessary plug-incall\n* NS: implement subcmd to change fid attributes\n\n``v4.4.41 Citrine``\n===================\n\n2019-05-07\n\n\nBug\n---\n* [EOS-3462] - FUSEX: suppress concurrent read errors for unrecoverable errors\n* MGM: Fix monitoring output for eos fusex ls -m\n\nImprovements\n------------\n\n* NS: Implement inspect subcommand to run through all file/directory metadata\n* [EOS-3463] - implement stripediff functionality in inspect tool\n* MGM: optimize quota accounting to correct for the given default layout when queried for quota via 'xrdfs ... space query /'\n* FUSEX: if a logfile exceeds 4G, we shrink it back to 2G\n* CTA: various cta related fixes (see commits)\n\n``v4.4.40 Citrine``\n===================\n\n2019-05-03\n\n\nBug\n---\n\n* FUSEX: avoid hanging call-back threads whnen a files is not attached and immedeatly unlinke\n* FUSE:  allow unauthenticated stats on the mount point directory ( for autofs )\n* FUSEX: silence mdstrackfree messages to debug mode\n* [EOS-3446] - CONSOLE: Return errno if set otherwise the XRootD client shell code approximation\n* FST: Don't report RAIN files as d_mem_sz_diff in the fsck output\n* FUSEX: allow setting 'eos.*' attributes by silently ignoring them\n* NS: add detection for container names '.' and '..'\n\n\nImprovements\n-------------\n\n* NS: Report any errors found by ContainerScanner or FileScanner in check-naming-conflicts\n* Adding ' eos-leveldb-inspect' tool\n* MGM: Refactor Fsck\n\n\n``v4.4.39 Citrine``\n===================\n\n2019-04-30\n\n\nBug\n---\n\n* [EOS-3313] - ns master other output looks incorrect\n* [EOS-3378] - double draining into same destination gives corrupted or empty replica\n* [EOS-3407] - Schedule2Balance reports long lasting read locks\n* [EOS-3414] - EOS config file could not be loaded\n* [EOS-3439] - rw filesystems shown with 'fs ls -d'\n* Fix for draining of RAIN file when parity information was not stored back on disk.\n* Enforce checksum verification for all replication operations.\n\nDocumentation\n-------------\n\n* Add documentation for EOS on Kubernetes deployment\n\n\n``v4.4.38 Citrine``\n===================\n\n2019-04-24\n\nBug\n----\n\n* Fix LRU which was looping and taking the FsView lock when disabled\n* [EOS-3427] - getUriFut can overwhelm the folly executor pool, causing slowness and potential deadlocks\n* [EOS-3432] - MGM crash in eos::NamespaceExplorer::buildDfsPath\n\nImprovement\n------------\n\n* [EOS-3431] - MGM: make \"func=performCycleQDB\" log (much) less\n\n\n``v4.4.37 Citrine``\n===================\n\n2019-04-16\n\nBug\n---\n\n* Fix deadlock in the folly executor introduced when using a single folly\n  executor for the entire namespace.\n\nImprovements\n-------------\n\n* Add env variable to control the master-slave transition lease validity.\n  EOS_QDB_MASTER_INIT_LEASE_MS\n\n\n``v4.4.36 Citrine``\n===================\n\n2019-04-16\n\n\nBug\n----\n\n* Fix deadlock in the Iostat class introduced in the previous release.\n* [EOS-2477] - MGM lockedup after enabling LRU - Citrine with new namespace\n* [EOS-3337] - MGM crash around XrdMgmOfs::OrderlyShutdown() on \"orderly\" shutdown\n* [EOS-3405] - MGM switches drain filesystems to empty\n\nImprovement\n------------\n\n* [EOS-3356] - RFE: shut up the 'verbose' recursive \"chown\" under /var/eos\n* [EOS-3389] - review \"error: no drain started for the given fs\": do not trigger this or do not log\n* [EOS-3402] - \"eos node ls\": double 'status' column, white-on-white text\n* [EOS-3412] - silence \"failed to stat recycle path\" error on rename+remove?\n* [EOS-3421] - Flood of \"SOME Listener new notification\" messages in the log since 77cfb51213\n\n\n``v4.4.35 Citrine``\n===================\n\n2019-04-11\n\nBug\n---\n* [EOS-3400] - don't commit any replica with write errors\n* [EOS-3399] - never drop all replicas in reconstruction or injectino failure scenarios\n* [EOS-3398] +\n* [EOS-3237] - never wipe local MD in eosxd with LEASE messages\n* [EOS-3410] - catch JSON exception produced by empty strings\n* [EOS-3408] - fixs prefetch logic in fileReadAsync(XrdIo)\n* fix fading heart-beat problem: re-enable a queue in MQ if a client has cleared backlog\n\nImprovement\n-----------\n\n* add 'eos-fsck-fs' command to run standalone fsck on FSTs\n* add read-ahead test for XrdIo\n* [EOS-3391] - make geotag propagation less verbose\n* [EOS-3406] - move some log messages from error to debug\n* [EOS-3390] - suppress UDP target missing message\n* [EOS-3401] - if scanner is disabled don't even scan files a first time\n* avoid FuseXCasts when _rem is called in FuseServer with recycle bin enabled\n\nRefactoring\n-----------\n\n* fix some more fid/fxid log messages to use the hex format\n* drop use of BackendClient in MetadataProvider\n\n``v4.4.34 Citrine``\n===================\n\n2019-04-05\n\nBug\n---\n\n* [EOS-3394] - automount might fail due to race condition in ShellExecutor/ShellCmd test\n\nImprovement\n-----------\n\n* RAIN placement uses round-robin algorithm to define the entry server\n\n``v4.4.33 Citrine``\n===================\n\n2019-04-04\n\nBug\n----\n\n* Disable prefetching for TPC transfers which might corrupt the data.\n* Put the mgm.checksum opaque info for drain jobs in the unencrypted part of\n  the URL otherwise the checksum check is not enforced.\n* [EOS-3367] - \"eos file verify --checksum\" does not update FMD checksum or ext.attribute\n* [EOS-3372] - MGM \"autorepair\" for corrupted replicas is not working\n* [EOS-3382] - Network monitoring always shows 0 on newer kernel versions\n\nImprovement\n------------\n\n* [EOS-3359] - Graceful cancellation of drain jobs\n* [EOS-3375] - Use eos/conversion as io stat tag\n\nRefactoring\n-----------\n\n* Introduce NamespaceGroup\n\n``v4.4.32 Citrine``\n===================\n\n2019-03-26\n\nBug\n---\n\n* [EOS-3347] - Fix slave follower problem with new mutex implementation due to unlock_shared vs unlock calls\n* [EOS-3348] - openSize used in XrdFstOfsFile::open\n* [EOS-3350] - Fusex lists duplicate items\n* [EOS-3352] - RAIN upload is not failed if a stripe cannot be opened for creation\n* [EOS-3354] - MGM deadlock while loading the configuration\n\n\nRefactoring\n-----------\n\n* Rename VirtualIdentity_t to Virtualidentity\n* Replace Fs2UuidMap maps with FilesystemMapper, drop unused 'nextfsid' global configuration\n\nImprovements\n------------\n\n* Allow to disable partition scrubbing by creating /.eosscrub on the FST partition\n* Add warning messages containing timing information about delayed heartbeat messaging\n\n\n``v4.4.31 Citrine``\n===================\n\n2019-03-21\n\nBug\n---\n\n* HTTP: Extend lifetime of variable pointed to from the XrdSecEntity object\n* CONSOLE: Refactor the RecycleHelper for easier testing. EOS-3345\n* MGM: Display real geotag field in FileInfo JSON format. Additionally, display forcegeotag field when available\n* FST: Fix default geotag to be less than 8 chars\n* FST: Add a check for Geotag length limit. Fixes EOS-3208\n* MGM: Fail file placement if a forced scheduling group is provided and the\n\nRefactoring\n-----------\n\n* MGM: Implement method to allocate new fsid based on uuid in FilesystemUuidMapper\n* MISC: Remove any kinetic reference\n* CONSOLE\n* ALL: enum class for filesystem status - strongly typed\n\nImprovements\n------------\n\n* MGM: add BackUpExists flag for files on CTA\n* MGM: Add estimate for drain TPC copy timeout based on the size of the file and a\n* MGM: Check geotag limit also on fs config forcegeotag command\n* MISC: Basic bash completion script. Fixes EOS-3252\n* MGM: Add tracking for in-flight requests in the MGM code for cleaner master-slave\n* ARCHIVE: Increase the TPC transfer timeout to 1 hour\n\n\n``v4.4.30 Citrine``\n===================\n\n2019-03-18\n\nBug\n---\n\n* FUSEX/MGM: allow all combinations of client/server versions by considering the\n  config entry if 'mdquery' is supported or not\n* FUSEX: fix return code of eos-ioverify in case of any IO error\n\nImprovements\n------------\n\n*  ALL: Drop \"drainstatus\" from the persistent config and use \"stat.drain\" to\n   hold the current status of the draining for a filesystem. This reduces also\n   the number of configuration save operations triggered by the draining and\n   we rely only on \"configstatus\" to decide whether or not draining should\n   be enabled. Note: all \"stat.*\" are filtered out from the persistent config.\n\n\n``v4.4.29 Citrine``\n===================\n\n2019-03-14\n\nBug\n----\n* Release built on top of XRootD 4.8.*\n\n\n``v4.4.28 Citrine``\n===================\n\n2019-03-12\n\nBug\n----\n\n* Fix bug in the namespace conversion tool when computing the quota nodes\n* Fix bug in the QuotaNodeCode copy constructor which was preventing a quota\n  node recomputation\n* [EOS-3316] - Namespace conversion tool suffers from high lock contention on releases 4.4.26, 4.4.27\n\nImprovements\n------------\n\n* Refactor the FuseServer code into various functional pieces\n* Use std::mutex for conversion tool rather than RWMutex which hinders performance\n\n\n``v4.4.27 Citrine``\n===================\n\n2019-03-07\n\nBug\n----\n\n* [EOS-3200] Fix crash in zmq::context_t constructor due to PGM_TIMER env variable\n* [EOS-3308] Drain status shown but machine is in configstatus rw\n* Put back fflush in Logging class to check\n\nImprovements\n------------\n\n* MGM/CONSOLE/DOC: extend LRU engine to specify policies by age and size limitations\n  like 'older than a week and larger then 50G' or 'older than a week and smaller than 1k'\n* NS: Add sharding to MetadataProvider to ease lock contention\n\n\n``v4.4.26 Citrine``\n===================\n\n2019-03-04\n\nBug\n----\n\n* [EOS-3246] - IPv6 addresses parsing broken\n* [EOS-3256] - Add XRootD connection pool to the MGM\n* [EOS-3257] - interactive 'eos' CLI aborts around eos::common::SymKeyStore::~SymKeyStore()\n* [EOS-3261] - EOSBACKUP locked up\n* [EOS-3263] - eosxd does not support seekdir/telldir\n* [EOS-3265] - Node config values never removed\n* [EOS-3266] - First MGM boot on clean namespace does not setup \"/\", \"/eos\", etc if EOS_USE_QDB_MASTER is set\n* [EOS-3267] - Dump files on CERN FSTs goes into a file named /var/eos/mdso.fst.dump.lxfsre10b04.cern.ch:109\n* [EOS-3276] - Inconsistent behavior (and doc) for \"eos fs config\" and \"eos node config\"\n* [EOS-3296] - eoscp crash while copying 'opaque_info' data\n* [EOS-3299] - Workaround for XRootD TPC bug in Converter which leads to data loss.\n               This is not a definitive fix.\n* [EOS-3280] - Logrotate rpm dependency missing for eos-server package\n* [EOS-3303] - Implement InheritChildren method for the QuarkContainerMD which otherwise\n               crashes the MGM for commands like \"eos --json fileinfo /path/to/dir/\".\n\nImprovement\n------------\n\n* [EOS-3249] - Add \"flag\" file for master status\n* [EOS-3251] - Expose Central drain thread pool status in monitoring format\n* [EOS-3269] - path display in `eos file check` output\n* [EOS-3295] - Allow MGMs to retrieve stacktraces and log files from eosxd at runtime\n\nNote\n-----\n\nStarting with this version one can control the xrootd pool of physical connections\nby using the following two env variables:\nEOS_XRD_USE_CONNECTION_POOL - enable the xrootd connection pool\nEOS_XRD_CONNECTION_POOL_SIZE - max number of unique physical connection\ntowards a particular host.\nThis can be use in the MGM daemon to control connection pool for TPC transfers\nused in the Converter and the Central Draining, but also on the FST side for\nFST to FST transfers.\n\nThe following two env variables that proided similar functionality only on the\nFST side are now obsolete:\nEOS_FST_XRDIO_USE_CONNECTION_POOL\nEOS_FST_XRDIO_CONNECTION_POOL_SIZE\n\n\n``v4.4.25 Citrine``\n===================\n\n2019-02-12\n\n* [EOS-3152] - FUSEX: crash below data::datax::peek_pread\n\n\n``v4.4.24 Citrine``\n===================\n\n2019-02-11\n\nBug\n----\n\n* [EOS-3240] - EOSBACKUP crash related somehow to ThreadPool\n* FUSEX: fix logical error in read overlay logic - fixes EOS-3253\n* FUSEX: fix datamap entry leak whenever a file is truncated by name and not via file descriptor\n* FUSEX: fix ugly kernel deadlock appearing in consumer-producer workloads\n\nImprovement\n------------\n\n* FUSEX: reduce the default wr/ra buffer to 256 MB if ram>=2G otherwise ram/8\n\n\n``v4.4.23 Citrine``\n===================\n\n2019-01-31\n\nBug\n----\n\n* [EOS-3231] - Update is not anymore implicit in ACL:w permissions - non-fuse fix\n* FUSE: Stop returning reference to temporary\n\nImprovement\n-----------\n\n* FUSEX: When the unmount handler catches a signal, re-throw in the same thread\n  so that abort handler print a meaningful trace\n\n\n``v4.4.22 Citrine``\n===================\n\n2019-01-24\n\nBug\n----\n\n* [EOS-3231] - Update is not anymore implicit in ACL:w permissions\n* [EOS-3215] - drainstatus not reset when disk put back to rw\n* [EOS-3227] - Missing eosarch python module\n* [EOS-3230] - CmdHelper does not always print error stream as provided by the MGM\n\n\n``v4.4.21 Citrine``\n===================\n\n2019-01-21\n\nBug\n----\n\n* [EOS-3203] - recycle config --size\n* [EOS-3204] - CLI: \"eos acl\" is broken\n* [EOS-3205] - Problem with the draining of zero size file\n* [EOS-3209] - central draining fails on paths containing question marks ('?')\n\n\nImprovement\n------------\n\n* [EOS-2678] - converter/groupbalancer \"recycles\" files found in recycle-enabled directories\n\n\n``v4.4.20 Citrine``\n===================\n\n2019-01-17\n\nBug\n----\n\n* [EOS-3202] - Instance degradation due to client concurrancy and quota refresh\n* MGM: Improve drain source selection by giving priority to replicas of files on other\n  file systems rather than the one currently being drained.\n* [EOS-3198] - Json output from the httpd interface escapes redundant double\n  quotes on values of attr queries\n* [EOS-1733] - eosd segfault in unlink around \"filesystem::is_toplevel()\"\n\nImprovement\n------------\n\n* [EOS-3197] - Improve directory rename/move inside the same quota node\n* MGM: Add command to control the number of threads used in the central draining:\n  eos ns max_drain_thread <num>\n* MGM: Add support for ACLs for single files\n\n\n``v4.4.19 Citrine``\n===================\n\n2018-12-18\n\nBug\n----\n\n* FUSEX: fix race/dead-lock condition when create and delete are racing\n\nImprovements\n------------\n\n* FUSEX: Put 256k as file start cache size\n* FUSEX: Add ignore-containerization flag\n* MGM: Refactor and add unit tests to the Access method\n* UNIT_TEST: Add quarkdb unit tests to the Gitlab pipeline\n* MGM/MQ: Various improvements and fixes to the QuarkDB master-slave setup\n* MGM: Various improvements and refactoring of the WFE functionality related\n       to CTA.\n\n\n``v4.4.18 Citrine``\n===================\n\n2018-12-07\n\nBug\n----\n\n* [EOS-2636] - VERY high negative cache value = 1987040\n* [EOS-2969] - central drain/config: \"eos fs config XYZ configstatus=drain\" hangs\n* [EOS-2974] - EOS new NS (EOSPPS) sudden memory increase → OOM\n* [EOS-3129] - Error following symlink while \"eos cp\"\n* [EOS-3162] - File reported successfully written despites IO errors\n* [EOS-3163] - FuseServer confuses file ID with inode when prefetching under lock\n* [EOS-3168] - \"eos recycle config --remove-bin\" not working anymore\n* [EOS-3170] - Data race in FuseServer when handling client statistics\n\nImprovement\n-----------\n\n* [EOS-2923] - Improve and rationalize Egroup class\n* [EOS-2968] - central drain/config: skip/ignore attempts to set the same configstatus twice (instead of hanging)\n* [EOS-3037] - RFE: draining - randomize order for to-be-drained files on a filesystem\n* [EOS-3138] - RPM packaging: depend on the EPEL repo definitions\n* [EOS-3153] - Reduce MGM shutdown time\n* [EOS-3155] - Write mtime multi-client propagation testsuite\n* [EOS-3166] - Allow chown always if the owner does not change\n\n\n``v4.4.17 Citrine``\n===================\n\n2018-11-29\n\nBug\n---\n\n* [EOS-3151] - fix OpenAsync in async flush thread in case of recovery\n\nImprovement\n-----------\n\n* Support REFRESH callback to force an update individual metadata records, not only bulk by directory\n\n\n``v4.4.16 Citrine``\n===================\n\n2018-11-28\n\nBug\n---\n\n* [EOS-3137] - Add additional permission check when following a symbolic link in XrdOfsFile::open\n* [EOS-3139] - eos chown -r uid:gid follows links\n* [EOS-3144] - Cannot auth with unix with fusex\n* [EOS-3145] - FUSEX: repeated WARN messages about \"doing XOFF\"\n\nImprovement\n-----------\n\n* [EOS-3050] - Add calling process ID and process name possibly to each client and server side log-entry for FUSE\n* [EOS-3096] - Show mount point in 'fusex ls'\n\n``v4.4.15 Citrine``\n===================\n\n2018-11-27\n\nBug\n---\n\n* CONSOLE: Add fallback to old style recycle command for old servers\n* MGM: Fix possible memory leak in capability generation\n\n\n``v4.4.14 Citrine``\n===================\n\n2018-11-20\n\nBug\n---\n\n* [EOS-3089] - Inflight-buffer exceeds maximum number of buffers in flight\n* [EOS-3110] - Looping Open in EOSXD\n* [EOS-3114] - corrupted file cache on eosxd in SWAN\n* [EOS-3116] - FUSEX-4.4.13 - 'zlib' selftest failure on SLC6\n* [EOS-3117] - FUSEX logs \"missing quota node for pino=\" (and \"high rate error messages suppressed\")\n* [EOS-3121] - MQ: Heap-use-after-free on XrdMqOfsFile::close\n* [EOS-3120] - Add eosxd support for persistent kerberos keyrings\n* [EOS-3123] - Parsing issue with \"eos recycle -m\"\n* [EOS-3125] - git clone fails with \"fatal: remote-curl: fetch attempted without a local repo\"\n* [EOS-3134] - fix journalcache memory leak\n\nNew Feature\n-----------\n\n* [EOS-3126] - FUSE: ability to tag traffic with custom tag\n* [EOS-3128] - eosxd usability\n\nImprovement\n-----------\n\n* [EOS-3108] - Move recycle command to protobuf implementation - keep server support for 'old' clients\n* [eos-3113] - Don't stall mount when no read-ahead buffer is available\n* [EOS-3119] - Make eosxd auth subsystem more debuggable for users\n* [EOS-3120] - Add eosxd support for persistent kerberos keyrings\n* [EOS-3122] - Add XrdCl fuzzing\n* improve shutdown behaviour of server\n* move all pthread to std::thread\n* FST no longer sends proto events for sync::closew if file comes from a tape server retrieve operation\n\n\n``v4.4.13 Citrine``\n===================\n\n2018-11-19\n\nBug\n---\n\n* [EOS-3101] - fix EEXIST logic in FuseServer open to race condition and remove double parent lookup\n\nImprovements\n------------\n\n* NS: Add metadata-entries-in-flight to NS cache information\n\n\n``v4.4.12 Citrine``\n===================\n\n2018-11-16\n\nBug\n---\n\n* [EOS-2172] - eosxd aborted, apparently due to diskcache missing xattr\n* [EOS-2865] - Lost some mount points\n* [EOS-3090] - Encoding problems in TPC/Draining\n* [EOS-3069] Use logical quota in prop find requests (displayed by CERNBOX client)\n* [EOS-3092] Don't require an sss keytable for a fuse mount if 'sss' is not configured as THE auth protocol to use\n\nImprovements\n------------\n\n* [EOS-3095] Fail all write access even from localhost in MGM while booting -\n  properly tag RO/WR access in proto buf requests\n* [EOS-3091] allow to ban eosxd clients (=> EPERM)\n* [EOS-3047] add defaulting routing to recycle command\n* Refactor fsctl includes into functions\n* enable eosxd authentication in docker container\n\nNew Feature\n-----------\n\n* [EOS-3094] - Access to eos in a container\n\n\n``v4.4.11 Citrine``\n===================\n\n2018-11-14\n\nBug\n---\n\n* [EOS-3044] Fusex quota update blocks the namespace\n* [EOS-3065] Ubuntu/Debian packaging: \"/etc/fuse.conf.eos\" conflicts between \"eos-fuse\" and \"eos-fusex\"\n* [EOS-3079] MGM Routing Macro should stop bouncing clients to same targets if the target was already tried\n* [EOS-3068] fix to catch missing exception in find, avoid FUSE client heartbeat waiving creating DOS\n* [EOS-3054] add missing '&' separator in deletion reports\n* [EOS-3052] fix typo in report log description\n* [EOS_3048] create group readable reports directory structure\n* [EOS-3045] fix wrong heart-beat interval logic creating tight-loops and default to 0.1Hz\n* [EOS-3043] avoid creating .xsmap files\n* [EOS-3041] add timeout to query in SendMessage, add timeout to open and stat requests\n* [EOS-3033] fix wrong etag in JSON fileinfo response\n* [EOS-3029] disable backward stacktrace in eosd by default possibly creating SEGVs when a long standing mutex is discovered\n* [EOS-3025] fix checksum array reset in Commit operation\n* [EOS-2989] take fsck enable intereval into account\n* [EOS-2872] modify mtime modification in write/truncate/flush to preserve the order of operations in EOSXD\n* [EOS-2599] fix ACLs by key and fully supported trusted and signle ID shared sss mounts supporting endorsement keys\n* [CTA-312]  propagate protobuf call related errors messages through back to clients\n* Don't call 'system' implying fork in FST code\n* Fix Fmd object constructor to use 64-bit file ids\n\nImprovements\n------------\n\n* [EOS-3073] auto-scale IO buffers according to available client memory\n* [EOS-3072] add number of open files to the eosxd statistics output\n* [EOS-3027] allow 'fusex evict' without calling abort handler by default e.g.\n  to force a client mount with a newer version\n* [EOS-2576] add support for clientDNs formatted according to RFC2253\n* FUSEX: Add client IO counter and rates in EOSXD stats file and 'fusex ls -l' output\n* FUSEX: Manage the negative cache actively from eosxd - saves many remote\n  lookups in case of unfound libraries in library lookup path on fuse mount\n* FUSEX: Improve tracebility in FuseServer logging to log by client credential\n  (remove the _static_ log entries)\n* Support deny ACL entries, RICHACL_DELETE from parent\n* CTA: Rename tape gc variable names\n* FST: Use RAII for XrdCl::Buffer response objects in FST code\n\n\n``v4.4.10 Citrine``\n===================\n\n2018-10-25\n\nBug\n---\n\n* [EOS-2500] fix shutdown procedure which might send a kill signal to process id=1 when the watchdog becomes a zombie process\n* [EOS-3015] deal with OpenAsync timeouts in the ioflush thread\n* [EOS-3016] Properly handle URL sources (eg.: starting with root://) in eos cp\n* [EOS-3021] Make function executed by thread noexcept so that we get a proper stack if it throws an exception\n* [EOS-3022] Use uint64_t for storing file ids in the archive command\n* fixes for file ids > 2^31 (int->long long in FST)\n\n\nImprovements\n------------\n\n* update file sizes for ongonig writes in eosxd by default every 5s and as long as the cap is valid\n\n``v4.4.9 Citrine``\n==================\n\n2018-10-22\n\nBug\n---\n\n* [EOS-2947] - MGM crash near eos::HierarchicalView::findLastContainer\n* [EOS-2981] - DrainJob destructor: Thread attempts to join with itself\n* [EOS-3009] - -checksum argument of fileinfo not supported anymore\n* MGM: Fix master-slave propagation of container metadata\n\n\n``v4.4.8 Citrine``\n==================\n\n2018-10-19\n\nBug\n---\n\n* [EOS-3001] - fix clients seeing deleted CWDs after few minutes\n\n\n``v4.4.7 Citrine``\n==================\n\n2018-10-18\n\nBug\n---\n\n* [EOS-2992],[EOS-2994],[EOS-2967] - clients shows empty file list after caps expired\n* [EOS-2997] - GIT usage broken since hard-links are enabled by default\n\n``v4.4.6 Citrine``\n==================\n\n2018-10-10\n\nBug\n---\n\n* [EOS-2816] - eos cp issues\n* [EOS-2894] - FUSEX: \"xauth -q -\" gets stuck in \"D\" state\n* [EOS-2992] - aiadm: Lost all files in EOS home\n* FUSEX: Various fixes\n\n\nTask\n----\n\n* [EOS-2988] - Login hangs forever (with HOME=/eos/user/l/laman)\n\n\n``v4.4.5 Citrine``\n==================\n\n2018-10-10\n\nBug\n---\n\n* [EOS-2931] - Operation confirmation value isn't random\n* [EOS-2962] - table in documentation badly displayed on generated website\n* [EOS-2964] - Heap-use-after-free on new master / slave when booting\n* [EOS-2970] - \"fs mv\" not persisted in config file\n* MGM: Disable by default the QdbMaster implementation and use the env variable\n    EOS_USE_QDB_MASTER to enable it when the QDB namespace is used\n* MGM: Enable broadcast before loading the configuration in the QdbMaster so\n    that the MGM collects broadcast replies from the file systems\n* MGM: Fix possible deadlock at startup when a file system needs to be put\n    in kDrainWait state during configuration loading\n* MGM: Various improvements to the shutdown procedure for a clean exit\n* MQ: Fix memory leak of RSA Objects\n\nImprovement\n------------\n\n* [EOS-2901] - RFE: \"slow\" lock debug - print more info on single line, or disable printing?\n* [EOS-2966] - FUSEX: hardcode RPM dependency on 'zeromq'\n\n\n``v4.4.4 Citrine``\n==================\n\n2018-10-09\n\nBug\n----\n\n* [EOS-2951] - FST crashes while MGM is down\n* MGM: Fix find crash when a broken symlink exists along side a directory with\n  the same name\n* MGM: Fix creation of directories that have the same name as a broken link\n\nImprovement\n-----------\n\n* MGM: Improve shutdown of the MGM and cleanup of threads and resources\n\n\n``v4.4.3 Citrine``\n==================\n\n2018-10-04\n\nBug\n----\n\n* [EOS-2944] - Central Drain Flaws\n* [EOS-2945] - Disks ends up in wrong state with leftover files when central drain is active\n* [EOS-2946] - slave mq seen as down by the master MGM\n\nImprovement\n-----------\n\n* [EOS-2940] - Error message if wrong params for 'eos file info'\n\n\n``v4.4.2 Citrine``\n==================\n\n2018-10-03\n\nBug\n----\n\n* FST: Fix populating the vector of replica URL which can lead to a crash\n\n\n``v4.4.1 Citrine``\n==================\n\n2018-10-03\n\nBug\n----\n\n* [EOS-2936] - configuration file location change\n* [EOS-2937] - eossync does not cope with the change in the config path\n* MGM: Fix http port used for redirection to the FSTs\n\n\n``v4.4.0 Citrine``\n==================\n\n2018-10-02\n\nBug\n----\n\n* [EOS-1952] - eosd crash in FileAbstraction::WaitFinishWrites\n* [EOS-2743] - \"eosd\" segfault .. error 4 in libpthread-2.17.so[...+17000]\n* [EOS-2801] - Heap-use-after-free in LayoutWrapper::WaitAsyncIO\n* [EOS-2836] - Sain file cannot be downloaded when one FS is not present\n* [EOS-2914] - git repo on EOS corruption\n* [EOS-2922] - eos-server.el6 package requires /usr/bin/bash (not provided by any package in SLC6)\n* [EOS-2926] - MGM deadlock due to fusex capability delete operation\n* [EOS-2930] - Core dump in rename path sanity check\n* [EOS-2933] - createrepo fails on large repo\n\nNew Feature\n------------\n\n* [EOS-2928] - FUSEX interference from user deletion and generic removal protection (g:z5:!d)\n\nTask\n----\n\n* [EOS-2721] - UNIX permissions not propagated to the slave (until a slave restart or failover)\n\nImprovement\n------------\n\n* [EOS-2696] - eosarchived systemd configuration\n* [EOS-2799] - eosdropboxd: document, add \"--help\", \"-h\" options -- or hide outside of default path\n* [EOS-2853] - Make background scan rate configurable like scaninterval\n* [EOS-2906] - Add \"fstpath\" to the message written in MGM's report log\n* [EOS-2921] - Support client defined LEASE times\n\nUser Documentation\n-------------------\n\n* [EOS-1723] - Instruction how to migrate to quarkdb namespace\n\n\n``v4.3.14 Citrine``\n===================\n\n2018-09-26\n\nBug\n---\n\n* [EOS-2759] - FST crash on NULL value for stat.sys.keytab, right after machine boot\n* [EOS-2821] - FST has lots of FS' stuck in \"booting\" state\n* [EOS-2904] - eos-client: manpages empty/missing on SLC6\n* [EOS-2912] - FuseServer does not update namespace store after addFile\n* [EOS-2913] - \"newfind --count\" displays empty lines for each entry found\n* [EOS-2916] - Missing server side check for inode quota and wrong eosxd client behaviour\n* [EOS-2917] - Central draining crash ?\n\nTask\n-----\n\n* [EOS-2832] - FST aborts (coredump) if it cannot launch a transferjob (\"Not able to send message to child process\")\n\n\n``v4.3.13 Citrine``\n===================\n\n2018-09-19\n\nBug\n---\n\n* [EOS-2892] - FUSE: Initialize XrdSecPROTOCOL before issuing kXR_query to check MGM features\n* [EOS-2895] - MGM: fix locking when waiting for a booted namespace\n* [EOS-2989] - MGM: Fix queueing logic in Egroup class\n* fix wrong checksum validation for chunked OC uploads from the secondary replicas\n* let FUSEX writes fail after 60s otherwise we can get stuck pwrite calls/hanging forever\n\n\n``v4.3.12 Citrine``\n===================\n\n2018-09-13\n\nBug\n---\n\n* [EOS-2793] - removexattr fails to remove attribute from mgm metadata\n* [EOS-2800] - Relocate check for sys.eval.useracl from fuse client to the Fuseserver\n* [EOS-2850] - avoid directory move into itself when going via symlinks\n* [EOS-2870] - faulty scheduling on offline machine (regression)\n* [EOS-2873] - fix chmod/chown behaviour on executing EOSXD client\n* [EOS-2874] - fix 'adjustreplica' for files containing an '&' sign\n* Thread sanitizer fixes in EOSXD\n* Fix snooze time in WFE\n\nImprovements\n------------\n\n* Default fd limit for shared EOSXD mounts is now 512k\n* Don't open journals for file reads in EOSXD ( divides by 2 number of fds)\n* Add 'fs dropghosts <fsid>' call to get rid of illegal entries in filesystem view without any corresponding meta data object (undrainable filesystems)\n* Use filesystem name as default cache subdirectory in EOSXD (not default)\n* Improve locking in EOSXD notification path - release ns mutex in most places before notifying - add timing counters to all EOSXD counters\n\n\n``v4.3.11 Citrine``\n===================\n\n2018-09-05\n\nBug\n---\n\n* MGM: Fix slots leak of proc commands for which the initial client disconnected\n  before receiving the response\n* MGM/FUSE: Add support for all possible encodings between EOSXD and MGM\n* FUSEX: Fix stack corruption when doing recovery and remove leaking proxy object\n  after recovery\n* FUSEX: Add 'sss' as a possible authentication scheme for eosxd\n\nImprovements\n------------\n\nCI: Add script for promoting tag releases from the testing to the stable repo\n\n\n``v4.3.10 Citrine``\n===================\n\n2018-08-31\n\nBug\n---\n\n* [EOS-2138] - Handling of white spaces in eos commands\n* [EOS-2722] - filR state not propagated to parent branches in a snapshot\n* [EOS-2787] - Fix filesystem ordering for FUSE file creation by geotag, then fsid\n* [EOS-2838] - WFE background thread hammering namespace, running find at 100 Hz\n* [EOS-2839] - Central draining is active on slave MGM\n* [EOS-2843] - FUSEX crash in metad::get(), pmd=NULL.\n* [EOS-2847] - FUSEX: Race between XrdCl::Proxy destructor and OpenAsyncHandler::HandleResponseWithHosts\n* [EOS-2849] - Memory Leaks in FST code\n\nTask\n----\n\n* [EOS-2825] - FUSEX (auto-)unmount not working?\n\nImprovement\n-----------\n\n* [EOS-2852] - MGM: hardcode RPM dependency on 'zeromq'\n* [EOS-2856] - EOSXD marks CWD deleted when invalidating a CAP subscription\n\n\n``v4.3.9 Citrine``\n==================\n\n2018-08-23\n\nBug\n---\n\n* [EOS-2781] - MGM crash during WebDAV copy\n* [EOS-2797] - FUSE aborts in LayoutWrapper::CacheRemove, \".. encountered inode which is not recognized as legacy\"\n* [EOS-2798] - FUSE uses inconsistent datatypes to handle inodes\n* [EOS-2808] - Symlinks on EOSHOME have size of 1 instead of 0\n* [EOS-2817] - eosxd crash in metad::cleanup\n* [EOS-2826] - Cannot create a file via emacs on EOSHOME topdir\n* [EOS-2827] - log/tracing ID has extra '='\n\n\n``v4.3.8 Citrine``\n==================\n\n2018-08-14\n\nBug\n---\n\n* [EOS-2193] - Eosd fuse crash around FileAbstraction::GetMaxWriteOffset\n* [EOS-2292] - eosd crash around \"FileAbstraction::IncNumOpenRW (this=0x0)\"\n* [EOS-2772] - ns compact command doesn't do repairs\n* [EOS-2775] - TPC failing in IPV4/6 mixed setups\n* Fix quota accounting for touched files\n\n\nNew Feature\n-----------\n\n* [EOS-2742] - Add reason when we change the status for file systems and node\n\n\n``v4.3.7 Citrine``\n==================\n\n2018-08-07\n\nBug\n---\n\n* Fix possible deadlock when starting the MGM with more than the maximum allowed\n  number of draining file systems per node.\n\n\n``v4.3.6 Citrine``\n==================\n\n2018-08-06\n\nBug\n---\n\n* [EOS-2752] - FUSE: crashes around \"blockedtracing\" getStacktrace()\n* [EOS-2758] - SLC6 FST crashes on getStacktrace()\n\nTask\n----\n\n* [EOS-2757] - The 4.3.6 pre-release generates FST crashes (SEGFAULT)\n\nImprovement\n-----------\n\n* [EOS-2753] - Logging crashing\n\n\n``v4.3.5 Citrine``\n==================\n\n2018-07-26\n\nBug\n---\n\n* [EOS-2692] - Lock-order-inversion between FsView::ViewMutex and ConfigEngine::mMutex\n* [EOS-2698] - XrdMqSharedObjectManager locks the wrong mutex\n* [EOS-2701] - FsView::SetGlobalConfig corrupts the configuration file during shutdown\n* [EOS-2718] - Commit.cc assigns zero-sized filename during rename, corrupting the namespace queue\n* [EOS-2723] - user.forced.placementpolicy overrules sys.forced.placementpolicy\n* Fix S3 access configuration not getting properly refreshed\n\nImprovement\n-----------\n\n* [EOS-2691] - FUSEX abort in ShellException(\"Unable to open stdout file\")\n* [EOS-2684] - Allow uuid identifier in 'fs boot' command\n* [EOS-2679] - Display xrootd version in 'eos version -m' and 'node ls --sys' commands\n* Documentation for setting up S3 access [Doc > Configuration > S3 access]\n* More helpful error messages for S3 access\n\n``v4.3.4 Citrine``\n==================\n\n2018-07-04\n\nBug\n---\n\n* [EOS-2686] - DrainFs::UpdateProgress maxing out CPU on PPS\n* Fix race conditions and crashes while updating the global config map\n* Fix lock order inversion in the namespace prefetcher code leading to deadlocks\n\nNew feature\n-----------\n\n* FUSEX: Add FIFO support\n\nImprovement\n-----------\n\n* Remove artificial sleep when generating TPC drain jobs since the underlying issue\n  is now fixed in XRootD 4.8.4 - it was creating identical tpc keys.\n* Replace the use of XrdSysTimer with std::this_thread::sleep_for\n\n\n``v4.3.3 Citrine``\n==================\n\n2018-06-29\n\nImprovement\n-----------\n\n* FUSEX: Fix issues with the read-ahead functionality\n* MGM: Extended the routing functionality to detect online and master nodes with\n  automatic stalling if no node is available for a certain route.\n* MGM: Fix race condition when updating the global configuration map\n\n\n``v4.3.2 Citrine``\n==================\n\n2018-06-26\n\nBug\n---\n\n* FUSEX: encode 'name' in requests by <inode>:<name>\n* MGM: decode 'name' in requests by <inode>:<name>\n* MGM: decode routing requests from eosxd which have an URL encoded path name\n\n\n``v4.3.1 Citrine``\n==================\n\n2018-06-25\n\nBug\n---\n\n* FUSEX: make the bulk rm the default\n* FUSEX: by default use 'backtace' handler, fusermount -u and emit received signal again.\n* FUSEX: use bulk 'rm' only if the '-rf' flag and not verbose option has been selected\n* FUSEX: avoid possible dead-lock between calculateDepth and invalidation callbacks\n\n\n``v4.3.0 Citrine``\n==================\n\n2018-06-22\n\nBug\n---\n\n* [EOS-1132] - eosarchived.py, write to closed (log) file?\n* [EOS-2401] - FST crash in eos::fst::ScanDir::CheckFile (EOSPPS)\n* [EOS-2513] - Crash when dumping scheduling groups for display\n* [EOS-2536] - FST\n* [EOS-2557] - disk stats displaying for wrong disks\n* [EOS-2612] - Probom parsing options in \"eos fs ls\"\n* [EOS-2621] - Concurrent access on FUSE can damage date information (as shown by ls -l)\n* [EOS-2623] - EOSXD loses kernel-md record for symbolic link during kernel compilation\n* [EOS-2624] - Crash when removing invalid quota node\n* [EOS-2654] - Unable to start slave with invalid quota node\n* [EOS-2655] - 'eos find' returns different output for dirs and files\n* [EOS-2656] - Quota rmnode should check if there is quota node before deleting and not after\n* [EOS-2659] - IO report enabled via xrd.cf but not collecting until enabled on the shell\n* [EOS-2661] - space config allows fs.configstatus despite error message\n\nNew Feature\n-----------\n\n* [EOS-2313] - Add queuing in the central draining\n\n\nImprovement\n-----------\n\n* [EOS-2297] - MGM: \"boot time\" is wrong, should count from process startup\n* [EOS-2460] - MGM should not return\n* [EOS-2558] - Fodora 28 rpm packages\n* [EOS-2576] - http: x509 cert mapping using legacy format\n* [EOS-2589] - git checkout slow\n* [EOS-2629] - Make VST reporting opt-in instead of opt-out\n* [EOS-2644] - Possibility to configure #files and #dirs on MGM with quarkdb\n\n\n``v4.2.26 Citrine``\n===================\n\n2018-06-20\n\nBug\n---\n\n* [EOS-2662] - ATLAS stuck in stacktrace due to SETV in malloc in table formatter\n* [EOS-2415] - Segmentation fault while building the quota table output\n\n\n``v4.2.25 Citrine``\n===================\n\n2018-06-14\n\nBug\n---\n\n* Put back option to enable external authorization library\n\n\n``v4.2.24 Citrine``\n===================\n\n2018-06-13\n\nBug\n----\n\n* [EOS-2081] - \"eosd\" segfault in sscanf() / filesystem::stat() / EosFuse::lookup\n* [EOS-2600] - Clean FST shutdown wrongly marks local LevelDB as dirty\n\nNew Feature\n-----------\n\n* Use std::shared_timed_mutex for the implementation of RWMutex. This is by default disabled and can be enabled by setting the EOS_USE_SHARED_MUTEX=1 environment var.\n\nImprovement\n-----------\n\n* The FSTs no longer do the dumpmd when booting.\n\n\n``v4.2.23 Citrine``\n===================\n\n2018-05-23\n\nBug\n----\n\n* [EOS-2314] - Central draining traffic is not tagged properly\n* [EOS-2318] - Slave namespace failed to boot (received signal 11)\n* [EOS-2465] - adding quota node on the master kills the slave (which then bootloops trying to apply the same quota)\n* [EOS-2537] - Balancer scheduler broken\n* [EOS-2544] - Setting recycle bin size changes inode quota to default.\n* [EOS-2564] - CITRINE MGM does not retrieve anymore error messages from FSTs in error.log\n* [EOS-2574] - enabling accounting on the slave results in segfault shortly after NS booted\n* [EOS-2575] - used space on /eos/<instance>/proc/conversion is ever increasing\n* [EOS-2579] - Half of the Scheduling groups are selected for  new file placement\n* [EOS-2580] - 'find -ctime' actually reads and compares against 'mtime'\n* [EOS-2582] - Access command inconsistencies\n* [EOS-2585] - EOSFUSE inline-repair not working\n* [EOS-2586] - The client GEOTAG is not taken into account when performing file placement\n\nNew Feature\n------------\n\n* [EOS-2566] - Enable switch to propagate uid only via fuse\n\nTask\n----\n\n* [EOS-2119] - Implement support in central drain for RAIN layouts + reconstruction\n* [EOS-2587] - Fix documentation for docker deployment\n\nImprovement\n-----------\n\n* [EOS-2462] - improve eos ns output\n* [EOS-2571] - Change implementation of atomic uploads`\n* [EOS-2588] - Change default file placement behaviour in case of clients with GEOTAG\n\n\n``v4.2.22 Citrine``\n===================\n\n2018-05-03\n\nBug\n----\n\n* [EOS-2486] - eosxd stuck, last message \"recover reopened file successfully\"\n* [EOS-2512] - FST crash around eos::fst::XrdFstOfsFile::open (soon after start, \"temporary fix\"?)\n* [EOS-2516] - \"eosd\" aborts with std::system_error \"Invalid argument\" on shutdown (SIGTERM)\n* [EOS-2519] - Segmentation fault when receiving empty opaque info\n* [EOS-2529] - eosxd: make renice =setpriority() optional, req for unprivileged containers\n* [EOS-2541] - (eosbackup halt): wrong timeout and fallback in FmdDbMapHandler::ExecuteDumpmd\n* [EOS-2543] - Unable to read 0-size file created with eos touch\n\nNew Feature\n-----------\n\n* [EOS-1811] - RFE: support for \"hard links\" in FUSE\n* [EOS-2505] - RFE: limit number of inodes for FUSEX cache, autoclean\n* [EOS-2518] - EOS WfE should log how long it takes to execute an action\n* [EOS-2542] - Group eossync daemons in eossync.target\n\nImprovement\n-----------\n\n* [EOS-2114] - trashbin behaviour for new eos fuse implementation\n* [EOS-2423] - EOS_FST_NO_SSS_ENFORCEMENT breaks writes\n* [EOS-2532] - Enable recycle bin feature on FUSEX\n* [EOS-2545] - Report metadata cache statistics through \"eos ns\" command\n\nQuestion\n--------\n\n* [EOS-2458] - User quota exceeted and user can write to this directory\n* [EOS-2497] - Repeating eos fusex messages all over\n\nIncident\n--------\n\n* [EOS-2381] - File lost during fail-over ATLAS\n\n\n``v4.2.21 Citrine``\n===================\n\n2018-04-18\n\nBug\n----\n\n* [EOS-2510] - eos native client is not working correctly against eosuser\n\nNew\n----\n\n* XrootD 4.8.2 readiness and required\n\n``v4.2.20 Citrine``\n===================\n\n2018-04-17\n\nImprovements\n------------\n\nFST: make the connection pool configurable by defining EOS_FST_XRDIO_USE_CONNECTION_POOL\nFUSE: avoid that FUSE calls open in a loop for every write in the outgoing write-back cache if the file open failed\nFUSE: remove 'dangerous' recovery functionality which is unnecessary with xrootd 4\nFUSE: Try to reuse connections towards the MGM when using the same credential file\n\n\n``v4.2.19 Citrine``\n===================\n\n2018-04-10\n\nBug\n----\n\n* [EOS-2440] - `eos health` is broken\n* [EOS-2457] - EOSPPS: several problems with `eos node ls -l`\n* [EOS-2466] - 'eos rm' on a file without a container triggers an unhandled error\n* [EOS-2475] - accounting: storagecapacity should be sum of storageshares\n\nTask\n----\n\n* [EOS-1955] - .xsmap file still being created (balancing? recycle bin?), causes \"corrupted block checksum\"\n\n\n``v4.2.18 Citrine``\n===================\n\n2018-03-20\n\nBug\n----\n\n* [EOS-2249] - Citrine generation of corrupted configuration\n* [EOS-2288] - headroom is not propagated from space to fs\n* [EOS-2334] - Failed \"proto:\" workflow notifications do not end up in either the ../e/.. or ../f/.. workflow queues\n* [EOS-2360] - FST aborts with \"pure virtual method called\", \"terminate called without an active exception\" on XrdXrootdProtocol::fsError\n* [EOS-2413] - Crash while handling a protobuf reply\n* [EOS-2419] - Segfault around TableFormatter (when printing FSes)\n* [EOS-2424] - proper automatic lock cleanups\n* [EOS-2428] - draining jobs create .xsmap files on the source and destination FSTs\n* [EOS-2429] - FuseServer does not grant SA_OK permission if ACL only allows to be a writer\n* [EOS-2432] - eosapmond init script for CC7 sources /etc/sysconfig/eos\n* [EOS-2433] - Wrong traffic accounting for TPC/RAIN/Replication\n* [EOS-2436] - FUSEX: permission problem in listing shared folder\n* [EOS-2438] - FUSEX: chmod +x does not work\n* [EOS-2439] - FUSEX: possible issue with sys.auth=*\n* [EOS-2442] - TPC of 0-size file fails\n\nImprovement\n-----------\n\n* [EOS-2423] - EOS_FST_NO_SSS_ENFORCEMENT breaks writes\n* [EOS-2430] - fusex cache should not use /var/eos\n\nQuestion\n--------\n\n* [EOS-2431] - fusex cache cleanup\n\n\n``v4.2.17 Citrine``\n===================\n\n2018-03-15\n\nBug\n---\n\n* [EOS-2292] - eosd 4.2.4-1 segmentation fault in SWAN\n* [EOS-2322] - eosd 4.2.4-1 segmentation fault on swan003\n* [EOS-2388] - Fuse::utimes only honours posix permissions, but not ACLs\n* [EOS-2402] - FST abort in eos::fst::FmdDbMapHandler::ResyncAllFromQdb (EOSPPS)\n* [EOS-2403] - eosd 4.2.4-1 SegFaults on swan001\n* [EOS-2404] - eosd 4.2.4-1 SegFaults on swan002\n\nImprovement\n-----------\n\n* [EOS-2389] - Classify checksum errors during scan\n* [EOS-2398] - Apply quota settings relatively quick in time on the FUSEX clients\n* [EOS-2408] - Proper error messages for user in case of synchronous workflow failure\n\n\n``v4.2.16 Citrine``\n===================\n\n2018-03-02\n\nBug\n---\n\n* [EOS-2142] - eosfstregister fails to get mgm url in CentOS 7\n* [EOS-2370] - EOSATLAS crashed while creating the output for a recursive attr set\n* [EOS-2382] - FUSEX access with concurrency creates orphaned files\n* [EOS-2386] - Vectored IO not accounted by \"io\" commands\n* [EOS-2387] - FST crash in eos::fst::ReedSLayout::AddDataBlock\n\nTask\n----\n\n* [EOS-2383] - eosxd: segfault in inval_inode\n\nImprovement\n-----------\n\n* [EOS-1565] - RFE: turn off SIGSEGV handler on non-MGM EOS components\n\n\n``v4.2.15 Citrine``\n===================\n\n2018-02-22\n\nBug\n---\n\n* [EOS-2353] - git clone with 2GB random reading creates read amplification\n* [EOS-2359] - Deadlock in proto wfe\n* [EOS-2361] - MGM crash after enabling ToggleDeadlock\n* [EOS-2362] - eosfusebind (runuser) broken on slc6\n\n\n``v4.2.14 Citrine``\n===================\n\n2018-02-20\n\nBug\n----\n\n* [EOS-2153] - consistent eosd memory leak\n* [EOS-2348] - ns shows wrong value for resident memory (shows virtual)\n* [EOS-2350] - eosd returns Numerical result out of range when talking to a CITRINE server and out of quota\n\n\n``v4.2.13 Citrine``\n===================\n\n2018-02-19\n\nBug\n----\n\n* [EOS-2057] - Wrong conversion between IEC and Metric multiples\n* [EOS-2299] - WFE can't be switched off\n* [EOS-2309] - Possible memleak in FuseServer::Caps::BroadcastReleaseFromExternal\n* [EOS-2310] - eosadmin wrapper no longer sends role\n* [EOS-2330] - Usernames with 8 characters are wrongly mapped\n* [EOS-2335] - Crash around XrdOucString::insert\n* [EOS-2339] - \"eos\" shell crash around \"eos_console_completion\",\"eos_entry_generator\"\n* [EOS-2340] - \"eos\" crash around \"AclHelper::CheckId\"\n* [EOS-2337] - autofs-ed fuse mounts not working for mountpoint names with matched entries under \"/\"\n\nTask\n----\n\n* [EOS-2329] - protect MGM against memory exhaustion caused by a globbing ls\n\nImprovement\n-----------\n\n* [EOS-2321] - Quota report TiB vs. TB\n* [EOS-2323] - citrine mgm crash\n* [EOS-2336] - Default smart files in the proc filesystem\n\nConfiguration Change\n-------------------+\n\n* [EOS-2279] - eosfusebind error message at login\n\nIncident\n--------\n\n* [EOS-2298] - EOS MGM memory leak\n\n\n\n``v4.2.12 Citrine``\n===================\n\n2018-02-01\n\nBug\n---\n\n* Fix deadlock observerd in EOSATLAS between gFsView.ViewMutex and pAddRmFsMutex from the\n  scheduling part.\n* Fix bug on the FST related to the file id value going beyond 2^32-1\n* [EOS-2275] - Possible data race in ThreadPool\n* [EOS-2290] - increase shutdown timeout for the FSTs\n\nNew Feature\n----------+\n\n* Add skeleton for new \"fs\" command using protobuf requests\n* Add skeleton for CTA integration\n* Enhance the mutex deadlock detection mechanism\n\n\n``v4.2.11 Citrine``\n===================\n\n2018-01-25\n\nBug\n---\n\n* [EOS-2264] - Fix possible insertion of an empty FS in FSView\n* [EOS-2270] - FSCK crashed booting namespace\n* [EOS-2271] - EOSPUBLIC deadlocked\n* [EOS-2261] - \"eos node ls <node>\" with the monitoring flag does not apply the node filter\n* [EOS-2267] - EOSPublic has crashed while recusively setting ACLs\n* [EOS-2268] - Third party copying (on the same instance) fails with big files\n\nImprovement\n-----------\n\n* [EOS-2283] - Double unlock in CITRINE code\n\nTask\n----\n\n* [EOS-2244] - Understand EOSATLAS configuration issue\n\n\n``v4.2.10 Citrine``\n===================\n\n2018-01-24\n\nBug\n---\n\n* [EOS-2264] Fix possible insertion of an empty FS in FSView\n* [EOS-2258] If FST has qdb cluster configuration then to the dumpmd directly against QuarkDB\n* [EOS-2277] fixes 'fake' truncation failing eu-strip in rpm builds of eos\n\nImprovements\n------------\n\n* Refactoring of includes to speed up compilation, various build improvements\n* avoid to call IsKnownNode to discover if an FST talks to the MGM, rely on sss + daemon user\n* use (again) a reader-preferring mutex for the filesystem view\n\n\n``v4.2.9 Citrine``\n===================\n\n2018-01-18\n\nBug\n---\n\n* [EOS-2228] Crash around forceRefreshSched related to pFsId2FsPtr\n\nNew Feature\n-----------\n\n* Filter out xrdcl.secuid/xrdcl.secgid tags on the FSTs to avoid triggering a\n  bug on the xrootd client implementation\n\nImprovements\n------------\n\n* [EOS-2253] Small writes should be aggregated with the journal\n* Refactoring of the includes to speed up compilation\n\n\n``v4.2.8 Citrine``\n===================\n\n2018-01-16\n\nBug\n---\n\n* [EOS-2184] - \"eos ls -l\" doesn't display the setgid bit anymore\n* [EOS-2186] - eos ns reports wrong number of directory\n* [EOS-2187] - Authproxy port only listens on IPv4\n* [EOS-2211] - CITRINE deadlocks on namespace mutex\n* [EOS-2216] - \"binary junk\" logged in func=RemoveGhostEntries (FID?)\n* [EOS-2224] - selinux denials with eosfuse bind.\n* [EOS-2229] - files downloaded with scp show 0 byte contents\n* [EOS-2230] - read-ahead inefficiency\n* [EOS-2231] - ioflush thread serializes file closing and leads to memory aggregation\n* [EOS-2241] - Directory TREE mv does not invalidate source caches\n\nNew Feature\n-----------\n\n* [EOS-2248] - FUSEX has to point ZMQ connection to active master\n\nImprovement\n-----------\n\n* [EOS-2238] - Print a warning for 'node ...' functions when an FST is seen without a GEO tag\n\nSupport\n-------\n* [EOS-2208] - EOS MGM (new NS) aborts with \"pure virtual method called\" on update (restart?)\n\n\n``v4.2.7 Citrine``\n===================\n\n2017-12-18\n\nBug\n---\n\n* [EOS-2207] - Work-around via environment variable to avoid loading too big no-replica sets (export EOS_NS_QDB_SKIP_UNLINKED_FILELIST)\n\n* Many improvements and fixes for eosxd\n  - fixing gateway mount options to work as NFS exports\n  - fixing access function which was not refreshing caps/md objects\n\n``v4.2.6 Citrine``\n===================\n\n2017-12-18\n\nBug\n---\n\n* [EOS-2150] - Repair option for d_mem_sz_diff error files\n* [EOS-2202] - Lock-order-inversion between gAccessMutex and ViewMutex\n\n* Many improvements and fixes for eosxd\n\n``v4.2.5 Citrine``\n===================\n\n2017-12-12\n\nBug\n---\n\n* [EOS-2142] - eosfstregister fails to get mgm url in CentOS 7\n* [EOS-2146] - symlinks have to show the size of the target string\n* [EOS-2147] - listxattr creates SEGV on OSX\n* [EOS-2148] - eosxd on OSX creates empty file when copying with 'cp'\n* [EOS-2159] - An owner of a directory has to get always chmod permissions\n* [EOS-2161] - rm -rf on fusex mount fails to remove all files/subdirectories\n* [EOS-2167] - new file systems added go to 'spare.0'\n* [EOS-2174] - Running out of FDs when using a user mount\n* [EOS-2175] - eos ns command takes 10s on EOSPPS\n* [EOS-2179] - calling verifychecksum issue\n* [EOS-2180] - Unable to access quota space <filename> Read-only file system\n\n* Many improvements and fixes for esoxd\n* Performance improvements and fixes for the namespace and QuarkDB\n\n``v4.2.4 Citrine``\n===================\n\n2017-11-28\n\nBug\n----\n\n* [EOS-2123] - eosxd renice's to lowest possible priority\n* [EOS-2130] - segv while compiling eos\n* [EOS-2137] - JSON output doesn't work anymore\n\nImprovements\n------------\n\n* Many improvements and fixes for eosxd\n* Many improvements and fixes for the namespace on QuarkDB\n\n\n``v4.2.3 Citrine``\n===================\n\n2017-11-17\n\nNew features\n------------\n\n* New centralized draining implementation\n* mgmofs.qdbcluster option in the configuration of the MGM to connect QuarkDB cluster\n\nImprovements\n------------\n\n* Use the flusher also in the quota view of the new namespace\n* Use prefetching for TPC transfers\n\nBug\n---\n* [EOS-2117] - mount.eosx should filter invalid options\n* Fix ns stat statistics\n\n\n``v4.2.2 Citrine``\n===================\n\n2017-11-14\n\nImprovements\n------------\n\n* Many fixes for the eosxd fuse module\n* Add eos_dump_proto_md tool to dump object metada info from QuarkDB\n* Clean-up and improvements of the eos_ns_conversion tool for the new namespace\n* Fix ns stat command not displaying ns info in monitoring format\n\n\n``v4.2.1 Citrine``\n===================\n\n2017-11-10\n\nBug\n---\n\n* [EOS-2017] - MGM crash caused by FSCK\n* [EOS-2061] - converter error in  \"file adjustreplica\" on raid6/archive layouts\n* [EOS-2050] - Scheduling problem with adjustreplica and draining filesystem\n* [EOS-2066] - xrdcp \"Error [3005]\" trying to transfer a \"degraded\" archive/raid6 file\n* [EOS-2068] - Archive should use root identity when collecting files/dirs\n* [EOS-2073] - MGM (citrine 4.1.30) unable to load configuration due to #iostat::udptargets with empty value\n* [EOS-2092] - Auth proxy crashes\n* [EOS-2093] - eos file convert from raid6/archive to replica:2 seems to not work.\n* [EOS-2094] - JSON Return 0 instead of \"NULL\" when space.nominalsize is not defined\n\nTask\n----\n* [EOS-1998] - Allow FST to login even when client traffic is stalled\n\nImprovement\n-----------\n\n* [EOS-2101] - Report logical used-space when using xrootd commands\n* A lot of improvements on the fusex side\n\n\n``v4.2.0 Citrine``\n===================\n\n2017-10-23\n\nBug\n----\n\n* [EOS-1971] - EOS node listing crash\n* [EOS-2015] - Table engine display values issue\n* [EOS-2057] - Wrong conversion between IEC and Metric multiples\n* [EOS-2060] - XrdMgmOfsFile SEGV out of bounds access\n\nNew Feature\n-----------\n\n* [EOS-2030] - Add '.' and '..' directories to file listings\n* Prototype for the new fuse implementation i.e fusex\n* Refactor of the ns command to use ProtoBuf-style commands\n\nTask\n----\n\n* [EOS-2033] - quota id mapping for non-existing users\n\nBug\n----\n\n* [EOS-2016] - avoid SEGV when removing ghost entries on FST\n* [EOS-2017] - avoid creating NULL object in map when resetting draining\n* DOC: various corrections - use solar template with new WEB colour scheme\n\n\n``v4.1.31 Citrine``\n===================\n\n2017-09-19\n\nBug\n----\n\n* [EOS-2016] - avoid SEGV when removing ghost entries on FST\n* [EOS-2017] - avoid creating NULL object in map when resetting draining\n* DOC: various corrections - use solar template with new WEB colour scheme\n\n``v4.1.30 Citrine``\n====================\n\n2017-09-15\n\nBug\n----\n* [EOS-1978] - Preserve converted file ctime and ctime (CITRINE)\n* FUSE: fix significant leak when returning a cached directory listing\n* MGM: Enforce permission check when utime is executed\n* MGM: Fix uid/gid overflow and comparison issues\n* HTTP: fix ipv4/6 connection2ip function\n\n\n``v4.1.29 Citrine``\n===================\n\n2017-09-08\n\nBug\n----\n* Mask the block checksum for draining and balancing when there is layout\n  requesting blockchecksum for replica files.\n* Add protection in case the proxies or the firewalleps vectors are not\n  properly populated and we try to access a location beyond the size of the\n  vector which leads to undefined behaviour.\n* Multiple fixes to the Schedule2Drain code\n* [EOS-1893] - EOS configuration can end up empty or truncated\n* [EOS-1989] - eos file verify <path> -checksum is broken\n* [EOS-1991] - eos-fuse rpm package broken dependency\n* [EOS-1996] - space ls geo output is wrongly formatted\n\n``v4.1.28 Citrine``\n===================\n\n2017-08-30\n\nBug\n---\n* [EOS-1991] - eos-fuse rpm package broken dependency\n\n``v4.1.27 Citrine``\n===================\n\n2017-08-28\n\nBug\n---\n* [EOS-1976] - EOSD client memory leak\n* [EOS-1986] - EOSPUBLIC: Crash when deleting a file entry\n* [EOS-1984] - MGM: only show available fs on geosched show state latency and penalties tables.\n* [EOS-1974] - NS: add missing initialization of pData (might lead to a SEGV during compaction if mmapping is disabled)\n\nImprovement\n-----------\n* [EOS-1791] - RFE: attempt to auto-unmount on eos-fuse-core updates\n* [EOS-1968] - eosd: always preload libjemalloc.so.1\n* [EOS-1983] - Built-in http server should be dual-stack\n\nNew features\n------------\n\n* New accounting command - \"eos accounting\".\n\n``v4.1.26 Citrine``\n===================\n\n2017-08-07\n\nBug\n---\n* [EOS-558] - \"eos fileinfo\" should better indicate non-active machines\n* [EOS-1895] - MGM Crash when the groupscheduler can't place file\n* [EOS-1897] - /var/log/eos/archive/eosarchived.log is world-writeable, should not\n* [EOS-1906] - Incorrect GeoTree engine information\n* [EOS-1936] - EOS ATLAS lost file due to balancing\n\nStory\n-----\n* [EOS-1919] - Bug visible when creating YUM repositories on the FUSE mount in CITRINE instances\n\nImprovement\n------------\n* [EOS-1159] - renaming a \"quota node\" directory gets rid of the quota setting?\n* [EOS-1345] - documentation update - eos fs help\n* [EOS-1875] - RFE: isolate eos client from LD_LIBRARY_PATH via RPATH\n\n* Plus all the fixes from the 0.3.264 and 0.3.265 release form the bery_aquamarine branch.\n\n\n``v4.1.25 Citrine``\n===================\n\n2017-06-29\n\nBugfix\n------\n* [EOS-542] - eos file version filename version modify the permissions of the file\n* [EOS-1259] - MGM eos node ls display\n* [EOS-1292] - \"eos\" hangs for 5min without EOS_MGM_URL - give verbose error message instead\n* [EOS-1317] - command to drop/refresh UID / GID cache is not documented?\n* [EOS-1762] - \"eos attr link origin target\" with a non-existent origin prevents listing of target's attributes\n* [EOS-1887] - Link back with the dynamic version of protobuf3\n* [EOS-1889] - file verify command fails when specifying fsid on a one-replica file\n* [EOS-1893] - EOS configuration can end up empty or truncated\n* [EOS-1888] - FSs wrongly reported as Unavailable by the GeoTreeEngine\n* [EOS-1892] - File copy is scheduled on a full FS\n\nNew Feature\n-----------\n* [EOS-1872] - \"Super\" graceful FST shutdown\n* There is a new dependency on protobuf3 packages both at build time and run time.\n  These packages can be downloaded from the citrine-depend yum repository:\n  http://storage-ci.web.cern.ch/storage-ci/eos/citrine-depend/el-7/x86_64/\n\nImprovement\n-----------\n* [EOS-1581] - RFE: better error messages from the eos client, remove 'error: errc=0 msg=\"\"'\n\n\n``v4.1.24 Citrine``\n===================\n\n2017-06-14\n\nBugfix\n------\n* [EOS-162] - RFE: auto-refill spaces from \"spare\", up to \"nominalsize\"\n* [EOS-455] - RFE: drop either fid: or fxid:, use the other consistently\n* [EOS-1299] - MGM node and fs printout with long hostname\n* [EOS-1716] - MGM: typo/missing whitespace in \"client acting as directory owner\" message\n* [EOS-1859] - PPS crash while listing space\n* [EOS-1877] - eos file drop does not accept fid:XXXX\n* [EOS-1881] - List quota info not working anymore on EOSLHCB\n* Fix fsck bug mixing information from different types of issues\n\nTask\n-----\n* [EOS-1851] - mount.eos assumes sysv or systemd present\n\nImprovement\n-----------\n* [EOS-1875] - RFE: isolate eos client from LD_LIBRARY_PATH via RPATH\n\nSupport\n-------\n* [EOS-1064] - get the year information for EOS file\n\n\n``v4.1.23 Citrine``\n===================\n\n2017-05-17\n\nBugfix\n------\n* MGM: Take headroom into account when scheduling for placement\n* MGM: Add protection in case the bookingsize is explicitly set to 0\n* ARCHIVE: Use the MgmOfsAlias consistently otherwise the newly generated archive file will contain invalid JSON lines.\n\n\n``v4.1.22 Citrine``\n===================\n\n2017-05-15\n\nBugfix\n------\n* Fix response for xrdfs query checksum to display \"adler32\" instead of \"adler\" as checksum type\n* Fix launch of the follower thread for the MGM slave\n\n\n``v4.1.21 Citrine``\n===================\n\n2017-05-12\n\nBugfix\n------\n* [EOS-1833] - eosfuse.cc uses a free'd fuse_req_t -> segfault\n* [EOS-1781] - MGM crash in GeoBalancer\n* [EOS-1642] - \"Bad address\" on EOS FUSE should be \"Permission denied\"\n* [EOS-1830] - Recycle bin list crash when doing full scan (need protection)\n\n\nTask\n----\n* [EOS-1848] - selinux error when uninstalling eos-fuse-core\n\nUser Documentation\n------------------\n* [EOS-1826] - Missing dependencies on the front page\n\nSuggestion\n----------\n* [EOS-1827] - Ancient version of zmq.hpp causing issues when compiling with new zmq.h (taken from system)\n* [EOS-1828] - Utils.hh in qclient #include cannot find header\n* [EOS-1831] - CMAKE, microhttpd, and client\n* [EOS-1832] - Bug in console/commands/com_fuse.cc with handling of environment variable EOS_FUSE_NO_MT\n\n\n``v4.1.3 Citrine``\n==================\n\n2016-09-15\n\nBugfix\n-------\n\n* [EOS-1606] - Reading root files error when using eos 4.1.1\n* [EOS-1609] - eos -b problem : \\*\\*\\* Error in `/usr/bin/eos: free():`\n\n\n``v0.4.31 Citrine``\n===================\n\n2016-07-22\n\nBugfix\n-------\n\n- FUSE: when using krb5 or x509, allow both krb5/x509 and unix so that authentication\n        does not fail on the fst (using only unix) when using XRootD >= 4.4\n\n\n``v0.4.30 Citrine``\n===================\n\n2016-07-21\n\nBugfix\n-------\n\n- SPEC: Add workaround in the %posttrans section of the eos-fuse-core package\n        to keep all the necessary files and directories when doing an update.\n- CMAKE: Remove the /var/eos directory from the eos-fuse-core package and fix\n        type in directory name.\n\n``v0.4.29 Citrine``\n===================\n\nBugfix\n-------\n\n- MGM: add monitoring switch to space,group status function\n- MGM: draing mutex fix and fix double unlock when restarting a drain job\n- MGM: fixes in JSON formatting, reencoding of non-http friendly tags/letters like <>?@\n- FST: wait for pending async requests in the close method\n- SPEC: remove directory creation scripting from spec files\n\nNew Features\n------------\n\n- RPM: build one source RPM which creates by default only client RPMs with less dependencies\n"
  },
  {
    "path": "doc/citrine/releases/citrine.rst",
    "content": ":orphan:\n\n.. highlight:: rst\n\n.. index::\n   single: Citrine\n\nCitrine\n========\n\n``Lifetime: 2014++``\n\n**Citrine** is the release name for the EOS development branch since 2014.\n\nRelease notes: :doc:`./citrine-release`\n"
  },
  {
    "path": "doc/citrine/releases/diopside-release.rst",
    "content": ":orphan:\n\n.. highlight:: rst\n\n.. index::\n   single: Diopside-Release\n\n\nDiopside Release Notes\n=====================\n\n``Version 5 Diopside``\n\nIntroduction\n------------\n\nThis release is based on XRootD V5.\n\n\n``v5.1.16 Diopside``\n====================\n\n2023-04-04\n\nBug\n----\n\n* COMMON: Don't reset the current vid identity when handling KEYS mapping\n  unless we actually have a hit in the map. This was breaking the vid mapping\n  for gsi/http with voms extensions that have the endorsements field in the\n  XrdSecEntity populated and this was interpreted as a key.\n\n\n``v5.1.15 Diopside``\n====================\n\n2023-03-27\n\n\nNote\n----\n\n* Update dependency to eos-xrootd-5.5.8 which also matches XRootD-5.5.4\n\nBug\n----\n\n* [EOS-5577] - MGM crash in eos::mgm::GrpcWncServer::RunWnc()\n* [EOS-5587] - jwt::decode might throw an exception\n* [EOS-5600] - eos group ls outputs wrong filled stats\n\n\nNew Feature\n------------\n\n* [EOS-5588] - Allow HTTPS gateway functionality to use key entries\n\nTask\n----\n\n* [EOS-5522] - Drain status stays in `expired`after setting fs in rw.\n* [EOS-5530] - Send fid as string to CTA\n\nImprovement\n-----------\n\n* [EOS-5578] - Balancer/Drainer/Recycler: reduce sleep info logging\n* [EOS-5592] - Disabling oauth did not actually disabled it\n\n\n``v5.1.14 Diopside``\n====================\n\n2023-03-14\n\nBug\n----\n\n* [EOS-2520] - FST abort (coredump) on shutdown, \"EPoll: Bad file descriptor polling for events\"\n* [EOS-5554] - Deadlock while setting acls recursive\n\nNew Feature\n------------\n\n* [EOS-5571] - Add atime to eos-ns-inspect tool\n* [EOS-5573] - Show if namespace is locked-up\n* [EOS-5576] - MGM: fileinfo -j does not output the file' status\n\n\n``v5.1.13 Diopside``\n====================\n\n2023-03-06\n\nBug\n----\n\n* [EOS-5546] - MGM: IoStat fprintf() stuck\n* [EOS-5555] - FST segfaults around qclient::QSet::srem\n* [EOS-5559] - EOS HTTP REST API - no JSON output if authentication is done with Bearer token\n\nNew features\n------------\n* [EOS-5561] - Create \"eos df\" command\n\n\n``v5.1.12 Diopside``\n====================\n\n2023-02-28\n\nBug\n----\n\n* [EOS-5526] - User Sessions count seems to be wrong\n* [EOS-5534] - LRU should not walk down the recycle bin and apply policies\n* [EOS-5535] - LRU tries to delete all directories having an empty deletion policy\n* [EOS-5542] - Error when accessing directories with wildcards\n\nImprovement\n------------\n\n* [EOS-5536] - LRU code has still in-memory namespace code\n\n\n``v5.1.11 Diopside``\n====================\n\n2023-02-15\n\n\nBug\n----\n\n* [EOS-5516] - Dangling files (possibly) after container is removed\n* [EOS-5520] - eos CLI group resolution changed - INC3372876\n* [EOS-5523] - eosxd recovery failing\n\nImprovement\n------------\n\n* [EOS-5524] - Allow https gateway nodes to provide x-forwarded-for headers\n\n\n``v5.1.10 Diopside``\n====================\n\n2023-02-07\n\nNote\n----\n\n* Update dependency to eos-xrootd-5.5.7 which also matches XRootD-5.5.2\n\nBug\n----\n\n* [EOS-5386] - iostat reports are not processed fast enough\n\nImprovements\n------------\n\n* MGM: Make central balancer configurable at runtime\n* FST: Chunk fsck requests to at most 50k entries per request\n* MGM: enable hide-version also when heartbrate has been changed\n\n\n``v5.1.9 Diopside``\n====================\n\n2023-01-24\n\n\nBug\n----\n\n* [EOS-5487] - sticky bit on version folders makes Recycler not able to clean the files on the recycle bin.\n* [EOS-5488] - New Year's crashes on all projects and homes\n* [EOS-5489] - PropFind fails when namespace mappings should apply\n* [EOS-5494] - eosxd looping when cleaning write queue\n* [EOS-5495] - FST crashing while doing LevelDB->ext_attr conversion on a (not) broken (enough) disk\n* [EOS-5498] - All 0 size files are marked as missing when using xattr fmd\n\n\nNew Feature\n------------\n\n* [EOS-5209] - Fsck removal should just move stripes to a quarantine directory\n\n\nImprovement\n------------\n\n* [EOS-5501] - Allow black and whitelisting of token vouchers (ids)\n\n\n``v5.1.8 Diopside``\n====================\n\n2022-12-14\n\nNote\n----\n\n* Update dependency eos-xrootd-5.5.5\n* Includes an important fix for HTTP TPC PULL transfers.\n\nBug\n----\n\n* [EOS-5467] - Inspector aggregates results instead of resetting the current scan\n* MGM: Add regfree in FuseServer regex usage to avoid memory leak\n* MGM: Unlock the Access mutex when delaying a client to not get problems to get a write lock\n\n\nImprovement\n-----------\n\n* [EOS-5478] - Invert Stall logic to check first user limits and then catch-all rules\n\n\n``v5.1.7 Diopside``\n====================\n\n2022-12-12\n\nBug\n----\n\n* [EOS-5474] - Conversion breaks files with FMD info in xattrs\n\nImprovement\n------------\n\n* [EOS-5469] - Allow to select secondary groups with kerberos authentication and implement AC checks for secondary groups\n* [EOS-5471] - Add atime to EOS\n* [EOS-5458] - Setting a namespace xattr might fail for wopi\n\n\n``v5.1.6 Diopside``\n====================\n\n2022-12-05\n\nBug\n----\n\n* [EOS-5467] - Inspector aggregates results instead of resetting the current scan\n\nImprovement\n------------\n\n* [EOS-5465] - Shoe FUSE application name in 'fusex ls'\n* [EOS-5466] - Add Stall / NoStall host lists to access interface\n\n\n``v5.1.5 Diopside``\n====================\n\n2022-12-02\n\nBug\n----\n\n* MGM: Fix MGM crash when the balancer is configured\n\nImprovement\n-----------\n\n* [EOS-5452] - New metric: Provide I/O errors per transfer in report logs\n* [EOS-5453] - New metric: Namespace contention calculation in ns stat command\n* [EOS-5131] - RFE: honour XRD_APPNAME for xrdcp\n* [EOS-5444] - Provide number of stripes in the inspector command\n* [EOS-5454] - EOS inspector: Provide layout_id in the list output per fxid\n* [EOS-5455] - eos node ls monitoring - Improve sys.uptime value format\n* [EOS-5459] - MGM: avoid blocking cleanup ops while user mapping\n* [EOS-5464] - Have TPC transfers respect the client tpc.ttl value\n\n\n``v5.1.4 Diopside``\n====================\n\n2022-11-22\n\nBug\n----\n\n* [EOS-5442] - eosxd crash (on shutdown) under ShardedCache destructor\n* [EOS-5446] - Failures in setting thread names\n\n\n``v5.1.3 Diopside``\n====================\n\n2022-11-16\n\nBug\n----\n\n* [EOS-5162] - Setting ACL does not work when dir ends with whitespace\n* [EOS-5433] - GroupBalancer: crash when conversions are scheduled before Converter\n* [EOS-5436] - Origin Restriction does not work as expected\n* [EOS-5437] - Fix potential leaks in Mapping::getPhysicalIds\n\nNew Feature\n------------\n\n* [EOS-5145] - Extending lock support\n* [EOS-5438] - Don't stall clients when thread pool is exhausted and a rate limit is reached\n\nImprovement\n------------\n\n* [EOS-5231] - Allow eos attr set to operate on CIDs\n* [EOS-5344] - eos recycle -m: show inode used / max numbers\n* [EOS-5401] - Return the inode number in FMD responses for GRPC\n* [EOS-5412] - add qclient performance metrics on monitoring format.\n* [EOS-5413] -  QClient performance: have last 5m, last 1m, etc metrics\n* [EOS-5439] - Add eosxd3 to all builds when fuse3 is available and ship in the RPM\n\n\n``v5.1.2 Diopside``\n====================\n\n2022-10-04\n\nBug\n----\n\n* [EOS-5399] - FST: Segfaults in FmdConverter\n* [EOS-5400] - FST crash in AccountMissing due to null Fmd object\n\nImprovement\n------------\n\n* [EOS-3297] - Print the deviation used for the group balancer\n\nNew features\n------------\n\n* MGM: Add implementation for central group balancer using TPC\n\n\n``v5.1.1 Diopside``\n====================\n\n2022-09-15\n\nNote\n-----\n\n* Update dependency to eos-xrootd-5.5.1\n* eosd is now deprecated and there are no more RPM packages provided for it\n\nBug\n----\n\n* [EOS-5347] - EOS token not usable via eosxd\n* [EOS-5369] - Occasional error during eoscta test \"mismatch between requested fid/fsid and retrieved ones\"\n* [EOS-5371] - Fix crash of the MGM when listing container entries due to invalidated\n               iterators to the ContainerMap/FileMap objects.\n* FST: eos-xrootd-5.5.1 fixes a bug in XRootD related to async close functionality\n  where the FST would crash if it received another requests for a file which was in\n  the process of being closed.\n\nNew features\n------------\n\n* CTA: Enhance/extend EOS report messages for CTA prepare workflow\n\n\n``v5.1.0 Diopside``\n====================\n\n2022-09-02\n\nNote\n----\n\n* This release comes with XRootD/eos-xrootd 5.5.0 as dependency\n\nBug\n----\n\n* [EOS-5377] - Unhandled exception in the GeoBalancer code\n* [EOS-5367] - Fix IoStat initialization when there is no prior data in QuarkDB\n* MGM: Fsck: correct the calculation of expected number of stripes in RepairFstXsSzDiff\n\n\nImprovement\n-----------\n\n* [EOS-5380] - Qclient: handle folly warnings\n* [EOS-5381] - Fix potential format overflows\n* [EOS-5378] - Fix compilation warnings\n* FUSEX: Add support for json statistics output\n\nNew features\n-------------\n\n* FST: Add support for storing file metadata info as extended attributes\n  of the raw files on disk rather than using the LevelDB on disk.\n  Disabled by default for the moment.\n\n\n``v5.0.31 Diopside``\n===================\n\n2022-08-12\n\nBug\n----\n\n* FST: Properly detect HTTP transfers and skip async close functionality in\n  such cases\n* [EOS-5359] - use after free in fusex::client::info\n* [EOS-5358] - WNC GRPC unserialized global options\n\n\n``v5.0.30 Diopside``\n===================\n\n2022-08-11\n\nBug\n---\n\n* [EOS-5355] - System ACLs evaluation overruling logic is incorrect\n\n\nNew Feature\n------------\n\n* [EOS-5342] - CREATE cta workflow not triggered when new file created using fusex - DELETE workflow is also missing\n\n\nImprovement\n-----------\n\n* [EOS-5343] - Better enforcement of the scattered placement policy\n\n\n``v5.0.29 Diopside``\n===================\n\n2022-07-29\n\nBug\n----\n\n* Fix /usr/bin/python dependency on EL8(S) which is no longer provided by any package,\n  therefore we need to explicitly use /usr/bin/python3\n\n\n``v5.0.28 Diopside``\n===================\n\n2022-07-26\n\nNote\n----\n\n* This version of EOS is based on an internal release of XRootD namely eos-xrootd-5.4.7\n\nBug\n---\n\n* [EOS-5336] - Lot of EOS FST crash (SIGSEGV) in the EOSALICE instance\n* [EOS-5308] - MGM: Potential double free in LDAP initialize\n* [EOS-5334] - LDAP connection socket leak\n* [EOS-5335] - MGM crash in Fileinfo.cc:97\n\n\n``v5.0.27 Diopside``\n===================\n\n2022-06-30\n\n\nBug\n---\n\n* [EOS-5296] - FST segfault around XrdXrootdProtocol::Process2\n* [EOS-5314] - segfault around \"XrdCl::CopyProcess::CleanUpJobs\"\n* [EOS-5302] - Iostat domain accounting is broken\n* [EOS-5303] - Shared filesystem file registration feature\n* [EOS-5308] - MGM: Potential double free in LDAP initialize\n\nImprovement\n------------\n\n* [EOS-5317] - Crash in AssignLBHandler with asan\n* [EOS-5321] - Allow to define which errors the fsck repair thread works on\n* [EOS-5305] - Tape REST API - V1 with an option to deactivate STAGE\n\n\n``v5.0.26 Diopside``\n===================\n\n2022-06-21\n\n\nNote\n----\n\n* XRootD: Based on eos-xrootd-5.4.5 which fixes a couple for important bugs\n  on the xrootd client side.\n\nBug\n----\n\n* [EOS-5302] - Iostat domain accounting is broken\n* [EOS-5303] - Shared filesystem file registration feature\n\nImprovements\n------------\n\n* MGM: Make fsck start up and shutdown more responsive\n* MGM: Add fsck repair procedure for m_mem_sz_diff errors\n\n\n``v5.0.25 Diopside``\n===================\n\n2022-06-09\n\nBug\n----\n\n* [EOS-5278] - Segmentation fault around eos::mgm::GroupDrainer::scheduleTransfer\n* [EOS-5284] - GroupBalancer: spurious logs when no transfers can be scheduled\n* [EOS-5286] - Physical quota is not updated when we set EC conversion\n* [EOS-5288] - Wrong layout id after conversion operation leading to wrong physical size\n* [EOS-5218] - Infinite loop in XrdCl::XRootDMsgHandler::Copy\n* MGM: The initial behaviour of xrdfs prepare -s/-a/-e and xrdfs query prepare have been restored\n\nImprovement\n------------\n\n* [EOS-5277] - Add LockMonitor class wrapping standard mutex\n* [EOS-5282] - Allow converter configuration to persist on restarts\n* [EOS-5285] - GroupDrainer: Allow all transfers to be reset\n* [EOS-5289] - File truncate can be slow especially for RAIN layouts\n* [EOS-5290] - File close operation for RAIN layouts can trigger client timeouts\n* MGM: Tape REST API v0.1 release - Support for ArchiveInfo and Release\n  functionality + discovery endpoint\n* MISC: Allow the eos-iam-mapfile tool to deal with DNs containing commas\n\n\n``v5.0.24 Diopside``\n===================\n\n2022-05-27\n\nBug\n---\n\n* [EOS-3713] - sys.eos.mdino should not use old-style inodes\n* [EOS-5230] - Keep xattrs when restoring versions\n* [EOS-5269] - Certain FSes not picked up by the group drainer\n\nImprovement\n-----------\n\n* [EOS-5263] - groupmod is hard limited to 256 groups\n* [EOS-5267] - Provide timestamp in eos convert list failed errors\n\n\n``v5.0.23 Diopside``\n===================\n\n2022-05-16\n\nNote\n----\n\n* This release uses eos-xrootd-5.4.4 which is based on XRootD-5.4.3-rc3.\n\nBug\n----\n\n* [EOS-5246] - replica show 'error_label=none' while having checksum mismatch.\n\nImprovement\n------------\n\n* [EOS-5184] - Add RedirectCollapse to XrdMgmOfs::Redirect responses\n* [EOS-5198] - Add few log lines to MasterLog\n\n\n``v5.0.22 Diopside``\n===================\n\n2022-05-06\n\nImprovements\n------------\n\nFUSEX: Refactoring async response handling\n\n\n``v5.0.21 Diopside``\n===================\n\n2022-05-06\n\nNotes\n------\n\n* Note: this is a scratch build on top of XRootD-5.4.3-RC1 trying to test\na bug fix concerning vector reads\n* Update dependency to XRootD-5.4.3-RC1\n\n\n``v5.0.20 Diopside``\n===================\n\n2022-05-03\n\nImprovements\n------------\n\nMGM: Improve fsck handling for rain files with rep_diff_n errors\nMGM: Add extra logging in fsck and be more defensive when handling\nunregistered stripes\nMGM: Group drainer prune transfers only once every few minutes\nFST: Silence stat errors for TPC transfers during preparation stages\n\n\n``v5.0.19 Diopside``\n===================\n\n2022-05-02\n\nBug\n---\n\n* MGM: Fix race condition in Converter which can lead to wrong metadata stored\n  in leveldb for converted files.\n* MGM: Fix wrong computation of number of stripes for RAIN layout\n* [EOS-5199] - Metadata (xattrs) is lost when creating new versions\n* [EOS-5219] - eos fsck report json output does not reflect command line options -l and -i\n* [EOS-5224] - No update is performed when adding a new member to an e-group in EOSATLAS\n\n\nNew Feature\n-----------\n\n* [EOS-5178] - Implement Group Drain\n* [EOS-5225] - Have a useful GroupDrain Status\n\n\n``v5.0.18 Diopside``\n===================\n\n2022-04-22\n\nBug\n----\n\n* [EOS-5197] - Deleting an xattr via console does not delete the key\n* [EOS-5199] - Metadata (xattrs) is lost when creating new versions\n* MGM: Fix crash in debug message when Env object is null for Access method\n\nNew Feature\n------------\n\n* [EOS-5215] - Fsck handle stripe size inconsistencies for RAIN layouts\n\n\nImprovement\n------------\n\n* [EOS-4955] - Add project quota tests as a part of CI\n* MGM: Iostat performance improvements for summary output\n* MGM: Iostat make extra tables optional by default and add separate\n  flag for displaying them.\n\n\n``v5.0.17 Diopside``\n===================\n\n2022-04-13\n\nNote\n----\n\n* This version includes add the fixes up to 4.8.82.\n\nImprovement\n------------\n\n* [EOS-5201] - Allow for more fine grained IO policies\n* [EOS-5204] - Only create files  via FUSEX if there is inode and volume quota and physical space available\n* [EOS-5205] - Distinguish writable space and total space\n* [EOS-5206] - Don't allow to set quota volume lower than the minimum fuse quota booking size\n\n\n``v5.0.16 Diopside``\n===================\n\n2022-03-29\n\nBug\n----\n\n* [EOS-5181] - Slave to Master redirection creates IO errors on FUSEx mounts\n* [EOS-5176] - Make OAuth tolerant to self-signed//invalid certificates used by identity provider\n\nImprovement\n-----------\n\n* MGM: Add protection against multi-source retry for RAIN layouts\n* MGM: Rewrite of the IoStat implementation for better accuracy\n* MGM: Remove dependency on eos-scitokens and use the library provided by XRootD framework\n* DOC: Update documentation concerning the MGM configuration for SciTokens support\n* NS: QuarkSyncTimeAccounting - removed namespace lock usage\n\nNew feature\n-----------\n\n* MGM: Add support for eos tokens over https\n\n\n``v5.0.15 Diopside``\n===================\n\n2022-03-22\n\nNote\n-----\n\n* Includes all the changes from 4.8.79\n\nBug\n----\n\n* FUSEX: never keep the deletion mutex when destroying an upload proxy because\n  the destructor still needs a free call back thread to use HandleResponse\n* [EOS-5153] - EC file written via FUSEx - mismatching checksum\n* [EOS-5167] - MGM segv in a non-tape enabled instance\n\n\n\n``v5.0.14 Diopside``\n===================\n\n2022-03-14\n\nBug\n----\n\n* [EOS-5090] - convert clear is not a admin command\n* [EOS-5133] - node ls -b does not remove the domain names\n* FUSEX: Fix deadlocks and race-conditions reported by TSAN\n\nImprovement\n------------\n\n* [EOS-5108] - workaround: drop forced automount expiry on FUSEX updates\n* [EOS-5126] - [eos-ns-inspect] Complement `stripediff` output\n\n\n``v5.0.13 Diopside``\n===================\n\n2022-02-15\n\nNote\n----\n\n* Includes all the changes from 4.8.76\n\nBug\n---\n\n* [EOS-5110] - Consolidate Access control in GRPC MD, MDSTreaming\n* [EOS-5116] - Workaround for XrdOucBuffPool bug\n* [EOS-5118] - eos-ns-inspect scan is initializing maxdepth to 0, even if not used\n* [EOS-5119] - Deadlock scenario in eosxd\n\nImprovement\n-----------\n\n* [EOS-5111] - Groupbalancer: newly introduced fields may not have a sane value\n* [EOS-5120] - io stat tag totals\n\n\n``v5.0.12 Diopside``\n===================\n\n2022-02-04\n\nNote\n----\n\n* Identical to 5.0.11 but re-tagged due to Koji issues\n\n\n``v5.0.11 Diopside``\n===================\n\n2022-02-04\n\nBug\n----\n\n* [EOS-5105] - eosxd crash in cap::quotax::dump\n\n\n``v5.0.10 Diopside``\n===================\n\n2022-02-02\n\nNote\n-----\n\n* This release includes all the changes from 4.8.74 release\n\nBug\n----\n\n* [EOS-5069] - filesystem status in \"rw + failed\"\n* [EOS-5070] - Access::ThreadLimit creates re-entrant lock of the access mutex\n* [EOS-5095] - Re-entrant lock triggered by out of quota warning\n\nImprovement\n------------\n\n* [EOS-5065] - Add create-if-not-exists option in GRPC\n* [EOS-5076] - Extend iotype interfaces to be space/directory defined\n* MGM: Fix missing support for cid/cxid and error output for convert command\n* WNC: Replaced auxiliary ACL function for fileinfo command\n\nNew features\n------------\n\n* WNC: Implemented support for EOS-wnc token, convert, fsck and new find commands\n* WNC: Changed GRPC streaming mechanism for find, ls and transfer commands\n\n\n``v5.0.9 Diopside``\n===================\n\n2022-01-12\n\nBug\n----\n\n* COMMON: Avoid segv due to mutex object set to nullptr in RWLock printout\n* [EOS-4850] - eosxd crash in destructor under metad::pmap::retrieveWithParentTS()\n* [EOS-5057] - Volume quota dispatched to FUSE clients mixes logical and physical bytes\n\n\n``v5.0.8 Diopside``\n===================\n\n2022-01-06\n\nNote\n----\n\n* Note: This release includes all the changes to the 4.8.70 release\n\nBug\n----\n\n* [EOS-5039] - Threads with parens in their name cannot access EOS\n\nImprovement\n-----------\n\n* [EOS-5029] - Allow to apply rate limiting in recursive (server side) command.\n* [EOS-5048] - Support direct IO for high performance read/write use cases\n\n\n``v5.0.7 Diopside``\n===================\n\n2021-12-01\n\nNote\n----\n\n* Release based on XRootD-5.3.4\n\n\nNew features\n------------\n\n* WNC: Implemented support for EOS-wnc member, backup, map and archive command\n\n\n\n``v5.0.6 Diopside``\n===================\n\n2021-11-16\n\nNote\n-----\n\n* Release based on XRootD-5.3.3 which fixes a critical bug concerning \"invalid responses\"\n\n\nBug\n----\n\n* ARCHIVE: Avoid trying to set extended attributes which are empty\n* [EOS-4995] MGM/CONSOLE: add '-c' option to CLI ls to show also the checksum for a listing\n* CTA: Fixed FST crash when connecting to misconfigured ctafrontend endpoint\n\n\n``v5.0.5 Diopside``\n===================\n\n2021-11-04\n\nBug\n----\n\nOSS: Avoid leaking file descriptors for xsmap files which are deleted in the meantime\nMGM: Skip applying fsck config changes at the slave as these will be properly\n\n\n``v5.0.4 Diopside``\n===================\n\n2021-10-27\n\n\nBug\n----\n\n* SPEC: Make sure both libproto* and libXrd* requirements are excluded when\n  building the eos packages since these come from internally build rpms like\n  eos-xrootd and eos-protobuf3 which don't expose the library so names so that\n  they can be installed on a machine along with the official rpms for the\n  corresponding packages if they exist.\n* MGM: Avoid that a slave MGM applies an fsck configuration change in a loop\n\nImprovements\n------------\n\n* EOS-4967: Add ARM64 support for blake3\n\n\n``v5.0.3 Diopside``\n===================\n\n2021-10-27\n\n\nNote\n----\n\n* This version is based on XRootD 5.3.2 that addresses some critical bug observed\n  in the previous version for XRootD.\n\nBug\n----\n\n* MGM: Fix GRPC IPv6 parsing\n* [EOS-4963] - FST: Reply with 206(PARTIAL_CONTENT) for partial content responses\n* [EOS-4962] - MGM: Return FORBIDDEN if there is a public access restriction in PROFIND requests\n* [EOS-4950] - FUSEX: fix race conditions in async callbacks with respect to proxy object deletions\n*\n\nNew features\n------------\n\n* [EOS-4670] - FUSEX: implement file obfuscation and encryption\n\n\n``v5.0.2 Diopside``\n===================\n\n2021-09-06\n\nBug\n----\n\n* [EOS-4809] - Make eos5 work with XrdMacaroons from XRootD5\n* Includes all the fixes from 4.8.65\n\nImprovements\n------------\n\n* WNC: Improvements to the EOS-Drive for fileinfo & health command\n\n\n``v5.0.1 Diopside``\n===================\n\n2021-08-16\n\nNew features\n-------------\n\n* Comtrade WNC contribution for the server side\n* Includes all the fixes from the 4.8.60 release\n\n\n``v5.0.0 Diopside``\n===================\n\n2021-06-11\n\nMajor changes\n--------------\n\n* Based on XRootD 5.2.0\n* Drop support for in-memory namespace\n* Drop support for file based configuration\n* Drop support for old high-availability setup\n* Make fusex classes compatible with the latest protobuf library\n* Integrate QuarkDB as part of the eos release process\n"
  },
  {
    "path": "doc/citrine/releases/diopside.rst",
    "content": ":orphan:\n\n.. highlight:: rst\n\n.. index::\n   single: Diopside\n\nCitrine\n========\n\n``Lifetime: 2021++``\n\n**Diopside** is the release name for the EOS development branch since 2021.\n\nRelease notes: :doc:`./diopside-release`\n"
  },
  {
    "path": "doc/citrine/releases.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Releases\n\nReleases\n========\n\n.. image:: citrine.jpg\n   :scale: 12%\n   :align: left\n   :target: releases/citrine.html\n\nEOS releases are named after gemstones. The actively developed version is called CITRINE.\n\n\n\n.. toctree::\n   :maxdepth: 1\n\n   releases/amber\n   releases/beryl\n   releases/citrine\n\n.. epigraph::\n\n   ================================= =================== =================== =================================\n   Release                           Stable Version      Description         Release Notes\n   ================================= =================== =================== =================================\n   :doc:`releases/amber`             0.2.47              1st EOS Generation\n   :doc:`releases/beryl`             0.3.267             2nd EOS Generation  :doc:`releases/beryl-release`\n   :doc:`releases/citrine`           4.8.102             3rd EOS Generation  :doc:`releases/citrine-release`\n   :doc:`releases/diopside`          5.1.16              4th EOS Generation  :doc:`releases/diopside-release`\n   ================================= =================== =================== =================================\n"
  },
  {
    "path": "doc/citrine/restapi/fileinfo.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Fileinfo API\n\n\n\nfileinfo\n========\n\nGet meta data information about files and directories.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/user/ | root://<host>//proc/user/\n     ?mgm.cmd=fileinfo\n     &mgm.path=/eos/\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n\n   usage: fileinfo <path> [--path] [--fxid] [--fid] [--size] [--checksum] [--fullpath] [-m] [--silent] [--env] :  print file information for <path>\n          fileinfo fxid:<fid-hex>                                           :  print file information for fid <fid-hex>\n          fileinfo fid:<fid-dec>                                            :  print file information for fid <fid-dec>\n          fileinfo inode:<fid-dec>                                          :  print file information for inode (decimal)>\n                                                                    --path  :  selects to add the path information to the output\n                                                                    --fxid  :  selects to add the hex file id information to the output\n                                                                    --fid   :  selects to add the base10 file id information to the output\n                                                                    --size  :  selects to add the size information to the output\n                                                                 --checksum :  selects to add the checksum information to the output\n                                                                 --fullpath :  selects to add the full path information to each replica\n                                                                     -m     :  print single line in monitoring format\n                                                                     --env  :  print in OucEnv format\n                                                                     -s     :  silent - used to run as internal command\n"
  },
  {
    "path": "doc/citrine/restapi/format.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Rest API Formatting\n\n\n\nFormatting\n==========\n\nEOS distinguish user and admin queries. User relevant queries require not particular role in EOS. Admin queries require to have the admin role or some even require the root role.\n\nAdmin Queries\n++++++++++++++\n\n.. code-block:: text\n\n   #root\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.format=json\n     &eos.ruid=0\n     &eos.rgid=0\n     ...\n\n   #admin\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.format=json\n     &eos.ruid=3\n     &eos.rgid=4\n     ...\n\n\nUser Queries\n++++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/user/ | root://<host>//proc/user/\n     ?mgm.format=json\n     ...\n\nJSON Output\n++++++++++++\n\nThe JSON object contains a tag for errors messages 'errormsg' and the return code of the command. The response object is found under the key names of the command/subcommand executed e.g.\n\n.. code-block:: text\n\n   curl \"http://localhost:8000/proc/admin/?mgm.cmd=foo&mgm.subcmd=bar&eos.ruid=0&eos.rgid=0&mgm.format=json\"\n   {\n     \"errormsg\" : \"error: no such admin command 'foo'\",\n     \"foo\" : \n     {\n       \"bar\" : null\n     },\n     \"retc\" : 22\n    }\n\n\n   curl \"http://localhost:8000/proc/admin/?mgm.cmd=foo&eos.ruid=0&eos.rgid=0&mgm.format=json\"\n   {\n     \"errormsg\" : \"error: no such admin command 'foo'\",\n     \"foo\" : null,\n     \"retc\" : 22\n    }\n\n\nJSONP Callbacks\n+++++++++++++++\n\nTo request a JSON callback object one uses a callback query:\n\n.. code-block:: text\n   \n   curl \"http://localhost:8000/proc/admin/?mgm.cmd=foo&mgm.subcmd=bar&eos.ruid=0&eos.rgid=0&callback=cmd\"\n   cmd([\n   {\n     \"errormsg\" : \"error: no such admin command 'foo'\",\n     \"foo\" : \n     {\n       \"bar\" : null\n     },\n     \"retc\" : \"22\"\n   }\n   ]);\n"
  },
  {
    "path": "doc/citrine/restapi/fs.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Filesystem API\n\nfs\n=====\n\nfs ls\n--------\n\nList configured filesystems.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=fs\n     &mgm.subcmd=ls\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     [&mgm.outformat=l|m|e|d|io|fsck]\n     [&mgm.outhost=brief]\n     [&mgm.selection=<match>]\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   fs ls [-m|-l|-e|--io|--fsck|-d|--drain] [-s] [--brief|-b] [ [matchlist] ] :\n      list all filesystems in default output format. <space> is an optional substring match for the space name and can be a comma separated list\n      -m                                  : list all filesystem parameters in monitoring format\n      --b,--brief                         : display host names without domain names\n      -l                                  : display all filesystem parameters in long format\n      -e                                  : display all filesystems in error state\n      --io                                : display all filesystems in IO output format\n      --fsck                              : display filesystem check statistics\n      -d,--drain                          : display all filesystems in drain or draindead status with drain progress and statistics\n      [matchlist]  : [matchlist] can be just the name of the space to display or a comma separated list of spaces e.g 'default,space'\n      [matchlist]  : [matchlist] can be a grep style list to filter certain filesystems e.g. 'fs ls -d drain,bootfailure'\n      [matchlist]  : [matchlist] can be a combination of space filter and grep e.g. 'fs ls -l space:default,drain,bootfailure'\n\n\nfs add\n--------\n\nAdd a new filesystem.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=fs\n     &mgm.subcmd=add\n     &eos.ruid=0\n     &eos.rgid=0\n     [&mgm.fs.fsid=<fsid>]\n     &mgm.fs.uuid=<uuid>\n     &mgm.fs.node=<node-queue>\n     &mgm.fs.mountpoint=<mount-point>\n     &mgm.fs.space=<space>\n     &mgm.fs.configstatus=off|ro|rw\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   fs add [-m|--manual <fsid>] <uuid> <node-queue>|<host>[:<port>] <mountpoint> [<schedgroup>] [<status] :\n      add a filesystem and dynamically assign a filesystem id based on the unique identifier for the disk <uuid>\n      -m,--manual <fsid>                  : add with user specified <fsid> and <schedgroup> - no automatic assignment\n      <fsid>                              : numeric filesystem id 1..65535\n      <uuid>                              : arbitrary string unique to this particular filesystem\n      <node-queue>                        : internal EOS identifier for a node,port,mountpoint description ... /eos/<host>:<port>/fst e.g. /eos/myhost.cern.ch:1095/fst [you should prefer the host:port syntax]\n      <host>                              : fully qualified hostname where the filesystem is mounted\n      <port>                              : port where xrootd is running on the FST [normally 1095]\n      <mountpoint>                        : local path of the mounted filesystem e.g. /data\n      <schedgroup>                        : scheduling group where the filesystem should be inserted ... default is 'default'\n      <status>                            : file system status after the insert ... default is 'off', in most cases should be 'rw'\n\nfs mv\n--------\n\nMove a filesystem between spaces/groups.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=fs\n     &mgm.subcmd=mv\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.space=<dst-space>\n     &mgm.fs.id=<fsid>|<space>\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n\n   fs mv <src-fsid|src-space> <dst-schedgroup|dst-space> :\n      move a filesystem into a different scheduling group\n      <src-fsid>                          : source filesystem id\n      <src-space>                         : source space\n      <dst-schedgroup>                    : destination scheduling group\n      <dst-space>                         : destination space\n   If the source is a <space> a filesystem will be chosen to fit into the destination group or space.\n   If the target is a <space> : a scheduling group is auto-selected where the filesystem can be placed.\n.. code-block:: text\n\nfs config\n---------\n\nConfigure filesystems.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=fs\n     &mgm.subcmd=config\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.fs.identifier=<fsid>\n     &mgm.fs.key=<key>\n     &mgm.fs.value=<value>\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   fs config <host>:<port><path>|<fsid>|<uuid> <key>=<value> :\n      configure filesystem parameter for a single filesystem identified by host:port/path, filesystem id or filesystem UUID.\n   fs config <fsid> configstatus=rw|wo|ro|drain|off :\n      <status> can be\n      rw          : filesystem set in read write mode\n      wo          : filesystem set in write-once mode\n      ro          : filesystem set in read-only mode\n      drain       : filesystem set in drain mode\n      off         : filesystem set disabled\n      empty       : filesystem is set to empty - possible only if there are no files stored anymorefs config <fsid> headroom=<size>\n      <size> can be (>0)[BMGT]    : the headroom to keep per filesystem (e.g. you can write '1G' for 1 GB)\n   fs config <fsid> scaninterval=<seconds>:\n      configures a scanner thread on each FST to recheck the file & block checksums of all stored files every <seconds> seconds. 0 disables the scanning.\n   fs config <fsid> graceperiod=<seconds> :\n      grace period before a filesystem with an operation error gets automatically drained\n   fs config <fsid> drainperiod=<seconds> :\n      drain period a drain job is waiting to finish the drain procedure\n\nfs rm\n--------\n\nRemove a configured filesystem.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=fs\n     &mgm.subcmd=rm\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.fs.id=<fsid|nodequeue>|&mgm.fs.node=<node>&mgm.fs.mountpoint=<mountpoint>\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n\n   fs rm    <fs-id>|<node-queue>|<mount-point>|<hostname> <mountpoint> :\n      remove filesystem configuration by various identifiers\n\nfs boot\n--------\n\nBoot a filesystem.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=fs\n     &mgm.subcmd=boot\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.fsid=<fsid>|&mgm.node=<node>\n     [&mgm.fs.forcemgmsync=1]\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   fs boot  <fs-id>|<node-queue>|* [--syncmgm]:\n      boot filesystem with ID <fs-id> or name <node-queue> or all (*)\n      --syncmgm : force an MGM resynchronization during the boot\n\nfs dropdeletion\n---------------\n\nList configured filesystems.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=fs\n     &mgm.subcmd=ls\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.fs.id=<fsid>\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   fs dropdeletion <fs-id> :\n      allows to drop all pending deletions on <fs-id>\n\nfs dumpmd\n---------\n\nDump meta data of a filesystem.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=fs\n     &mgm.subcmd=dumpmd\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.fsid=<fsid\n     [&mgm.dumpmd.option=m]\n     [&mgm.dumpmd.fid=1]\n     [&mgm.dumpmd.size=1]\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   fs dumpmd [-s|-m] <fs-id> [-fid] [-path] :\n      dump all file meta data on this filesystem in query format\n      -s    : don't printout keep an internal reference\n      -m    : print the full meta data record in env format\n      -fid  : dump only a list of file id's stored on this filesystem\n      -path : dump only a list of file names stored on this filesystem\n\nfs status\n--------\n\nShow status of a filesystem.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=fs\n     &mgm.subcmd=status\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.fs.id=<fsid>|&mgm.fs.node=<nodeid>&mgm.fs.mountpoint=<mountpoint>\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   fs status [-l] <fs-id> :\n      returns all status variables of a filesystem and calculates the risk of data loss if this filesystem gets removed\n   fs status [-l] mount-point> :\n      as before but accepts the mount point as input parameters and set's host=<this host>\n   fs status [-l] <host> <mount-point> :\n      as before but accepts the mount point and hostname as input parameters\n      -l    : list all files at risk and files which are offline\n"
  },
  {
    "path": "doc/citrine/restapi/group.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Group API\n\n\n\ngroup\n=====\n\ngroup ls\n--------\n\nList configured groups.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=group\n     &mgm.subcmd=ls\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     [&mgm.outformat=l|m|io|IO]\n     [&mgm.outhost=brief]\n     [&mgm.selection=<match>]\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   group ls [-s] [-b|--brief] [-m|-l|--io|--IO] [<group>]           : list groups or only <group>. <group> is a substring match and can be a comma separated list\n    -s : silent mode\n    -b,--brief : display host names without domain names\n    -m : monitoring key=value output format\n    -l : long output - list also file systems after each group\n    --io : print IO statistics for the group\n    --IO : print IO statistics for each filesystem\n\ngroup rm\n--------\n\nDelete a group.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=group\n     &mgm.subcmd=rm\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     &mgm.group=<group>\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   group rm <group-name>                                         : remove group\n\ngroup set\n---------\n\nActivate/Deactivate a group.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=group\n     &mgm.subcmd=set\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     &mgm.group=<group>\n     &mgm.group.state=on|off\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   group set <group-name> on|off                                 : activate/deactivate group\n     => when a group is (re-)enabled, the drain pull flag is recomputed for all filesystems within a group\n     => when a group is (re-)disabled, the drain pull flag is removed from all members in the group\n"
  },
  {
    "path": "doc/citrine/restapi/grpc.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: GRPC Server\n\n.. _grpc_reference:\n\nGRPC\n=====\n\nGRPC is a high performance open-source universal RPC framework. See https://grpc.io\n\nServer\n------\n\nConfiguration\n+++++++++++++\n\nThe EOS MGM runs an embedded GRPC server if not disabled via sysconfig configuration.\n\nThe server is configured via `/etc/sysconfig/eos_env` and the following variables:\n\n.. code-block:: text\n\n   # GRPC server port - default is 50051 - 0 disables the GRPC server\n   EOS_MGM_GRPC_PORT=50051\n   # GRPC security - define to enable SSL server\n   EOS_MGM_GRPC_SSL_CERT=/etc/grid-security/daemon/host.cert\n   EOS_MGM_GRPC_SSL_KEY=/etc/grid-security/daemon/privkey.pem\n   EOS_MGM_GRPC_SSL_CA=/etc/grid-security/daemon/ca.cert\n\nIt is not recommended to run the GRPC server without TLS support unless you use\nother measures to restrict access. The server certificate has to match the IPV4 and\nIPV6 host name if applicable.\n\n\nIdentity Handling\n+++++++++++++++++\n\nThe client mapping is configured using the EOS CLI and the vid interface.\n\nThe vid interface allows to map requests to EOS virtual identities. If a GRPC client host\nis not explicitly declared as a GRPC gateway, all requests run as user ``nobody``.\n\nTo allow a GRPC client to map to any other user than ``nobody`` add the IP as a gateway:\n\n.. code-block:: text\n\n   vid add gateway grpc <IPV4-IP|IPV6-IP>\n\nTo map GRPC client requests to a given user, there are two options:\n\n* mapping by certificate common name\n* mapping by authorization key\n\nIf no authorization key (token) is added to the GRPC request, certificate common name mapping will be tried.\nIf an authorization key (token) is present in the GRPC request, mapping by key will be used.\n\nTo add an authorication key use:\n\n.. code-block:: text\n\n   vid set map -grpc <key:secret-key> vuid:<uid> vid:<gid>\n\nThe client has to add this key as the ``authkey`` parameters to each GRPC request.\n\nClient\n------\n\nThe executable ``eos-grpc-ping`` is available to test the GRPC server and display the access latency.\n\nThe syntax of the command options is shown here :\n\n.. code-block:: text\n\n   usage: eos-grpc-ping [--key <ssl-key-file> --cert <ssl-cert-file> --ca <ca-cert-file>] [--endpoint <host:port>] [--token <auth-token>]\n\n   e.g. eos-grpc-ping --key /etc/grid-security/daemon/privkey.pem --cert /etc/grid-security/daemon/host.cert --ca /etc/grid-security/daemon/ca.cert --endpoint foo.bar:50051 --token see_my_token\n\n\nThe xecutable ``eos-grpc-md`` is available to get individual meta data in a JSON dump for a file or container or to get a listing of a JSON dump of the parent and all children.\n\n.. code-block:: text\n\n   usage: eos-grpc-md [ ... TLS parameters see above ] [--endpoint <host:port] [--token <auth-token>] [-l] <eos-path>\n\n   e.g. eos-grpc-ping --key /etc/grid-security/daemon/privkey.pem --cert /etc/grid-security/daemon/host.cert --ca /etc/grid-security/daemon/ca.cert --endpoint foo.bar:50051 -l /eos/\n"
  },
  {
    "path": "doc/citrine/restapi/node.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Node API\n\n\n\nnode\n=====\n\nnode ls\n--------\n\nList available nodes.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=node\n     &mgm.subcmd=ls\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     [&mgm.outformat=l|m|io|sys|fsck]\n     [&mgm.outhost=brief]\n     [&mgm.selection=<match>]\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   node ls [-s] [-b|--brief] [-m|-l|--sys|--io|--fsck] [<node>]  : list all nodes or only <node>. <node> is a substring match and can be a comma separated list\n      -s : silent mode\n      -b,--brief : display host names without domain names\n      -m : monitoring key=value output format\n      -l : long output - list also file systems after each node\n      --io : print IO statistics\n      --sys  : print SYS statistics (memory + threads)\n      --fsck : print filesystem check statistcis\n\nnode config\n-----------\n\nConfigure a node.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=node\n     &mgm.subcmd=config\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     &mgm.node.name=<node>\n     &mgm.node.key=<key>\n     &mgm.node.value=<value>\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   node config <host:port> <key>=<value>                    : configure file system parameters for each filesystem of this node\n      <key> : gw.rate=<mb/s> - set the transfer speed per gateway transfer\n      <key> : gw.ntx=<#>     - set the number of concurrent transfers for a gateway node\n      <key> : error.simulation=io_read|io_write|xs_read|xs_write|fmd_open\n        io_read  : simulate read  errors\n        io_write : simulate write errors\n        xs_read  : simulate checksum errors when reading a file\n        xs_write : simulate checksum errors when writing a file\n        fmd_open : simulate fmd mismatch when opening a file\n        <none>   : disable error simulation (every value than the previous ones are fine!)\n      <key> : publish.interval=<sec> - set the filesystem state publication interval to <sec> seconds\n      <key> : debug.level=<level> - set the node into debug level <level> [default=notice] -> see debug --help for available levels\n      <key> : for other keys see help of 'fs config' for details\n\nnode set\n--------\n\nActivate/Deactivate/Define a node.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=node\n     &mgm.subcmd=set\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     &mgm.node=<node>\n     &mgm.node.state=on|off\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n      node set <queue-name>|<host:port> on|off                 : activate/deactivate node\n\nnode rm\n--------\n\nRemove a node.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=node\n     &mgm.subcmd=rm\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     &mgm.node=<node>\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n    node rm  <queue-name>|<host:port>                        : remove a node\n\nnode register\n-------------\n\nRegister a node.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=node\n     &mgm.subcmd=register\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     &mgm.node.name=<node>\n     &mgm.node.path2register=<path2register>\n     &mgm.node.space2register=<space2register>\n     [&mgm.node.force=true]\n     [&mgm.node.root=true]\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   node register <host:port|*> <path2register> <space2register> [--force] [--root] : register filesystems on node <host:port>\n     <path2register> is used as match for the filesystems to register e.g. /data matches filesystems /data01 /data02 etc. ... /data/ registers all subdirectories in /data/\n     <space2register> is formed as <space>:<n> where <space> is the space name and <n> must be equal to the number of filesystems which are matched by <path2register> e.g. data:4 or spare:22 ...\n      --force : removes any existing filesystem label and re-registers\n      --root  : allows to register paths on the root partition\n\nnode gw\n--------\n\nEnable/Disable a node as a transfer gateway\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=node\n     &mgm.subcmd=gw\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     &mgm.node=<node>\n     &mgm.node.txgw=on|off\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   node gw <queue-name>|<host:port> <on|off>                : enable (on) or disable (off) node as a transfer gateway\n\nnode status\n-----------\n\nShow the status of a node.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=node\n     &mgm.subcmd=status\n     &mgm.format=json\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.node=<node>\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   node status <queue-name>|<host:port>                     : print's all defined variables for a node\n"
  },
  {
    "path": "doc/citrine/restapi/ns.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Namespace API\n\n\n\nns\n===\n\nns stat\n-------\n\nPrint namespace statistics.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=ns\n     &mgm.subcmd=stat\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     &mgm.option=[anr]m\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n       ns stat [-a] [-m] [-n]                                     :  print namespace statistics\n                -a                                                   -  break down by uid/gid\n                -m                                                   -  print in <key>=<val> monitoring format\n                -n                                                   -  print numerical uid/gids\n                --reset                                              -  reset namespace counter (option r)\n"
  },
  {
    "path": "doc/citrine/restapi/putrange.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Range defined PUT requests\n\n\nRange defined PUT requests\n===============================\n\nAllows to update a file range using a PUT request.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   PUT http://<host>:8000/eos/file \n\n\nREST headers\n\n.. code-block:: text\n\n   x-upload-totalsize: <total file size>               - specifies total file size\n   x-upload-mtime: <mtime unixtimestamp>               - specifies mtime to store in the namespace\n   x-upload-range: bytes=<first-offset>-<last-offset>  - <first> specifies the start offset to place the content. <last> has to match the content-length of the uploaded body\n   x-upload-checksum: <type>:<value>                   - <type> is usually adler32 or md5, value is the corresponding hexadeciaml value\n   x-upload-done: true|false                           - if true the checksum will be calculated after this upload. The flag is intrinsic if the range <last-offset> value equals content-length e.g. the last piece always triggers a checksum calculation unless this header is explicitly set to false. \n\n   #CURL Example uploading 3 pieces of a file with x-upload-done implicit\n   curl --header \"x-upload-checksum: adler32:abcdabcd\" --header \"x-upload-totalsize: 2285\" --header \"x-upload-mtime: 1533100000\" --header \"x-upload-range: bytes=0-1023\" -L -X PUT -T \"file.0\" http://localhost:8000/eos/http/file\n   curl --header \"x-upload-checksum: adler32:abcdabcd\" --header \"x-upload-totalsize: 2285\" --header \"x-upload-mtime: 1533100000\" --header \"x-upload-range: bytes=1024-2047\" -L -X PUT -T \"file.1\" http://localhost:8000/eos/http/file\n   curl --header \"x-upload-checksum: adler32:abcdabcd\" --header \"x-upload-totalsize: 2285\" --header \"x-upload-mtime: 1533100000\" --header \"x-upload-range: bytes=2048-2284\" -L -X PUT -T \"file.2\" http://localhost:8000/eos/http/file\n\n   #CURL Example uploading 3 pieces out of order with x-upload-done explicit\n   curl --header \"x-upload-done: false\" --header \"x-upload-checksum: adler32:abcdabcd\" --header \"x-upload-totalsize: 2285\" --header \"x-upload-mtime: 1533100000\" --header \"x-upload-range: bytes=2048-2284\" -L -X PUT -T \"file.2\" http://localhost:8000/eos/http/file\n   curl --header \"x-upload-done: false\" --header \"x-upload-checksum: adler32:abcdabcd\" --header \"x-upload-totalsize: 2285\" --header \"x-upload-mtime: 1533100000\" --header \"x-upload-range: bytes=0-1023\" -L -X PUT -T \"file.0\" http://localhost:8000/eos/http/file\n   curl --header \"x-upload-done: true\" --header \"x-upload-checksum: adler32:abcdabcd\" --header \"x-upload-totalsize: 2285\" --header \"x-upload-mtime: 1533100000\" --header \"x-upload-range: bytes=1024-2047\" -L -X PUT -T \"file.1\" http://localhost:8000/eos/http/file\n\n\n"
  },
  {
    "path": "doc/citrine/restapi/space.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Space API\n\n\n\nspace\n=====\n\nspace ls\n--------\n\nList configured spaces.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=space\n     &mgm.subcmd=ls\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     [&mgm.outformat=l|m|io|fsck]\n     [&mgm.selection=<match>]\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   space ls [-s] [-m|-l|--io|--fsck] [<space>]                   : list in all spaces or select only <space>. <space> is a substring match and can be a comma separated list\n      -s : silent mode\n      -m : monitoring key=value output format\n      -l : long output - list also file systems after each space\n      --io : print IO statistics\n      --fsck : print filesystem check statistics\n\n\nspace config\n------------\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=space\n     &mgm.subcmd=config\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     &mgm.space.name=<space>\n     &mgm.space.key=<key>\n     &mgm.space.value=<value>\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n      space config <space-name> space.nominalsize=<value>           : configure the nominal size for this space\n      space config <space-name> space.balancer=on|off               : enable/disable the space balancer [default=off]\n      space config <space-name> space.balancer.threshold=<percent>  : configure the used bytes deviation which triggers balancing            [ default=20 (%)     ]\n      space config <space-name> space.balancer.node.rate=<MB/s>     : configure the nominal transfer bandwidth per running transfer on a node [ default=25 (MB/s)   ]\n      space config <space-name> space.balancer.node.ntx=<#>         : configure the number of parallel balancing transfers per node          [ default=2 (streams) ]\n      space config <space-name> space.converter=on|off              : enable/disable the space converter [default=off]\n      space config <space-name> space.converter.ntx=<#>             : configure the number of parallel conversions per space                 [ default=2 (streams) ]\n      space config <space-name> space.drainer.node.rate=<MB/s >     : configure the nominal transfer bandwidth per running transfer on a node [ default=25 (MB/s)   ]\n      space config <space-name> space.drainer.node.ntx=<#>          : configure the number of parallel draining transfers per node           [ default=2 (streams) ]\n      space config <space-name> space.lru=on|off                    : enable/disable the LRU policy engine [default=off]\n      space config <space-name> space.lru.interval=<sec>            : configure the default lru scan interval\n      space config <space-name> space.headroom=<size>               : configure the default disk headroom if not defined on a filesystem (see fs for details)\n      space config <space-name> space.scaninterval=<sec>            : configure the default scan interval if not defined on a filesystem (see fs for details)\n      space config <space-name> space.drainperiod=<sec>             : configure the default drain  period if not defined on a filesystem (see fs for details)\n      space config <space-name> space.graceperiod=<sec>             : configure the default grace  period if not defined on a filesystem (see fs for details)\n      space config <space-name> space.autorepair=on|off             : enable auto-repair of faulty replica's/files (the converter has to be enabled too)                                                                       => size can be given also like 10T, 20G, 2P ... without space before the unit\n      space config <space-name> space.geo.access.policy.write.exact=on|off   : if 'on' use exact matching geo replica (if available) , 'off' uses weighting [ for write case ]\n      space config <space-name> space.geo.access.policy.read.exact=on|off    : if 'on' use exact matching geo replica (if available) , 'off' uses weighting [ for read case  ]\n      space config <space-name> fs.<key>=<value>                    : configure file system parameters for each filesystem in this space (see help of 'fs config' for details)\n\nspace define\n------------\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=space\n     &mgm.subcmd=define\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     &mgm.space=<space>\n     &mgm.space.groumod=<groupmod>\n     &mgm.space.groupsize=<groupsize>\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n      space define <space-name> [<groupsize> [<groupmod>]]          : define how many filesystems can end up in one scheduling group <groupsize> [default=0]\n      => <groupsize>=0 means, that no groups are built within a space, otherwise it should be the maximum number of nodes in a scheduling group\n      => <groupmod> defines the maximum number of filesystems per node\n\n\n\nspace reset\n------------\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=space\n     &mgm.subcmd=reset\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     &mgm.space=<space>\n     &[mgm.option=egroup|mapping|drain|scheduledrain|schedulebalance]\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n      space reset <space-name>  [--egroup|mapping|drain|scheduledrain|schedulebalance]\n      : reset a space e.g. recompute the drain state machine\n\nspace status\n------------\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=space\n     &mgm.subcmd=status\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     &mgm.space=<space>\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n      space status <space-name>                                     : print's all defined variables for space\n\nspace set\n---------\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=space\n     &mgm.subcmd=set\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     &mgm.space=<space>\n     &mgm.space.state=on|off\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n      space set <space-name> on|off                                 : enables/disables all groups under that space ( not the nodes !)\n\nspace rm\n--------\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=space\n     &mgm.subcmd=rm\n     &mgm.space=<space>\n     &mgm.format=json\n     &eos.ruid=0\n     &eos.rgid=0\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n      space rm <space-name>                                         : remove space\n\nspace quota\n-----------\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/admin/ | root://<host>//proc/admin/\n     ?mgm.cmd=space\n     &mgm.subcmd=quota\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     &mgm.space=<space>\n     &mgm.space.quota=on|off\n\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n      space quota <space-name> on|off                               : enable/disable quota\n"
  },
  {
    "path": "doc/citrine/restapi/version.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Version information\n\n\n\nversion\n=======\n\n\nShow version information.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/usre/ | root://<host>//proc/user/\n     ?mgm.cmd=version\n     &mgm.option=m\n     &mgm.format=json\n     &eos.ruid=0\n     &eos.rgid=0\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   version [-f] [-m]                                             :  print EOS version number\n                -f                                                   -  print the list of supported features\n                -m                                                   -  print in monitoring format\n"
  },
  {
    "path": "doc/citrine/restapi/who.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Who API\n\n\n\nwho\n===\n\nShow statistics about active users.\n\nREST syntax\n+++++++++++\n\n.. code-block:: text\n\n   http://<host>:8000/proc/user/ | root://<host>//proc/user/\n     ?mgm.cmd=who\n     &eos.ruid=0\n     &eos.rgid=0\n     &mgm.format=json\n     &mgm.option=[cnzas]m\n\nCLI syntax\n++++++++++\n\n.. code-block:: text\n\n   who [-c] [-n] [-z] [-a] [-m] [-s]                             :  print statistics about active users (idle<5min)\n                -c                                                   -  break down by client host\n                -n                                                   -  print id's instead of names\n                -z                                                   -  print auth protocols\n                -a                                                   -  print all\n                -s                                                   -  print summary for clients\n                -m                                                   -  print in monitoring format <key>=<value> \n\n"
  },
  {
    "path": "doc/citrine/restapi.rst",
    "content": ".. _restapi:\n\nREST API\n================\n\n.. toctree::\n   :maxdepth: 2\n\n   restapi/putrange\n   restapi/format\n   restapi/space\n   restapi/group\n   restapi/node\n   restapi/fs\n   restapi/version\n   restapi/who\n   restapi/ns\n   restapi/fileinfo\n   restapi/grpc   \n"
  },
  {
    "path": "doc/citrine/taperestapi/configuration.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Configuration\n\nConfiguration\n=============\n\nEnable XRootD HTTP support on the MGM\n-------------------------------------\n\nEnable XRootD HTTP support by following the :doc:`../configuration/http_tpc`\n\nxrd.cf.mgm configuration\n------------------------\n\nOn the ``/etc/xrd.cf.mgm`` configuration file, the following parameters must be set:\n\n.. code-block:: text\n\n    mgmofs.tapeenabled true\n    taperestapi.sitename cern-cta-xxxx\n\nThe ``taperestapi.sitename`` parameter corresponds to the targeted metadata identifier that will be used by the user\nto pass metadata to CTA for each file staged.\n\nREST API activation/deactivation\n--------------------------------\n\nActivation:\n\n.. code-block:: bash\n\n    eos space config default taperestapi.status=on\n    success: Tape REST API enabled\n\nDeactivation:\n\n.. code-block:: bash\n\n    eos space config default taperestapi.status=off\n    success: Tape REST API disabled\n\nWarning: the tape REST API can only be enabled/disabled on the default space. An error message will be displayed\nin the case one tries to enable it on a different space:\n\n.. code-block:: bash\n\n    eos space config OtherSpace taperestapi.status=on\n    error: the tape REST API STAGE resource can only be enabled or disabled on the default space\n\nREST API STAGE resource activation/deactivation\n--------------------------------\n\nBy default, the STAGE resource of the tape REST API is not activated. If one deactivates the tape REST API and activates it again,\nthe STAGE resource will be deactivated by default.\n\nActivation:\n\n.. code-block:: bash\n\n    eos space config default taperestapi.stage=on\n    success: Tape REST API STAGE resource enabled\n\nDeactivation:\n\n.. code-block:: bash\n\n    eos space config default taperestapi.stage=off\n    success: success: Tape REST API STAGE resource disabled"
  },
  {
    "path": "doc/citrine/taperestapi.rst",
    "content": ".. _taperestapi:\n\nTAPE REST API\n================\n\nThe tape REST API implementation in EOS offers a HTTP REST interface allowing clients\nto manage disk residency of tape stored files and to observe the progress of files being written to tape.\n\n.. toctree::\n    :maxdepth: 2\n\n    taperestapi/configuration"
  },
  {
    "path": "doc/citrine/using/archive.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Archive; Interface\n\nArchive Interface\n=============================\n\nThe archive interface currently has the following signature:\n\n.. code-block:: bash\n\n   archive <subcmd>\n           create <path>                      : create archive file\n           put [--retry] <path>               : copy files from EOS to archive location\n           get [--retry] <path>               : recall archive back to EOS\n           purge[--retry] <path>              : purge files on disk\n           transfers [all|put|get|purge|uuid] : show status of running jobs\n           list [<path>]                      : show status of archived directories in the subtree\n           kill <job_uuid>                    : kill transfer\n           help [--help|-h]                   : display help message\n\nIn order to safely archive an EOS subtree to tape (CTA) the following steps detailed in this document must\nbe performed. Assume we want to archive the EOS subtree rooted at /eos/dir/archive/test. First of all,\nthe user needs to make sure that he/she has the necessary permissions to submit archiving commands.\nThe permissions check is enforced at directory level by using the **sys.acl** extended attribute\nand it allows setting permissions at user, group or egroup level. The **ACL flag** for achieving\nis **'a'**.\n\n.. code-block:: bash\n\n    sys.acl=\"u:tguy:a\"  # user tguy has the right to archive for the current directory\n\nOnce the proper permissions are in place, we need to take a snapshot of all the metadata of the\nfiles and directories under this subtree. For this we use the **archive create** command inside\nthe *EOS Console*:\n\n.. code-block:: bash\n\n   archive create /eos/dir/archive/test\n\nAfter issuing this command the EOS subtree is **immutable** and no updates are allowed either to the\ndata or the metadata. Transferring the data to tape (CTA) is done using the **archive put** command:\n\n.. code-block:: bash\n\n   archive put /eos/dir/archive/test\n\nAt any point during a transfer the user can retrieve the current status of the transfer by issuing an\n**archive transfers** command. Once the transfer finishes there will be two additional files saved at\nthe root of the archived subtree: the **.archive.log** file with contains the logs of the last transfer\n(note the 'dot' in the beginning of the filename - so to list it use **ls -la** in the *EOS Console*)\nand another file called **.archive.<operation>.<outcome>** where operation is one of the following:\nget/put/purge and the outcome can either be **done** or **err**.\n\nWhile an archive operation is ongoing the file stored in EOS is marked with the **err** tag. For\nexample, an ongoing **put** operation, which can take server hours depending on the size of the\nsub-tree being archived to tape, will appear in the **eos ls -la** output as **.archive.put.err**.\nOnce the put operation is successful, this file will be renamed to **.archive.put.done**. Therefore,\nit's important to check the output of the **eos archive transfers** command which is listing the\nstatus of the ongoing archive operations and not rely only on the status file in EOS.\n\nIf an error occurs the user has the possibility to resubmit the transfer by using the **--retry** option.\n\nWhen the put operation is successful one should find a file called **.archive.put.done** at the root\nof the subtree and the user can now issue the purge command which will delete all the data from EOS\nthus freeing the space.\n\n.. code-block:: bash\n\n    archive purge /eos/dir/archive/test\n\nTo get the data back into EOS one should use the archive get command:\n\n.. code-block:: bash\n\n    archive get /eos/dir/archive/test\n\nThe same conventions as before apply when it comes to the progress and the final status of the transfer.\nIf the user would like to retrieve the status of previously archived directories he/she can use the\n**archive list** command which will return the status of all archived directories rooted at the given\ndirectory or if no directory is given then \"/\" is assumed. This command displays also the running\njobs but no detailed information about them is provided - for this you should use the **archive transfers**\ncommand.\n\nIn case the user wants to permanently delete the data saved on **tape (CTA)**, then unless he has root\nprivileges on the EOS instance he will need to contact one of the administrators to perform this operation.\nPermanently deleting the archive will not delete any data from EOS, but only the data saved in CTA.\nTherefore, it is the **user's responsibility** to make sure he/she first gets the data back to EOS before\nrequesting the deletion of the archive.\n"
  },
  {
    "path": "doc/citrine/using/attributelocks.rst",
    "content": ".. highlight:: rst\n\n.. _attributelocks:\n\nUsing extended attribute locks\n========================================\n\nAn extended attribute lock is a simple mechanism to block file opens on locked files to foreigners. Foreigners are not owners. The owner is defined by the username and the application name.\nSo if any of these differs a client is considered a foreigner.\n\nWe define two types of locks:\n- exclusive : no foreigner can open a file with an exclusive lock for reading or writing\n- shared    : foreigner can open a file with an exclusive lock in case they are reading\n\nShared attribute locks are currently not exposed in the CLI.\n\nTo create an exclusive extended attribute lock you do:\n\n.. code-block:: bash\n\n   # create a lock\n   eos -r 100 100 -a myapp file touch -l /eos/dev/lockedfile\n\n   # the owner can read\n   eos -r 100 100 -a myapp cp /eos/dev/lockedfile      - # will succeed\n\n   # a foreigner can not read\n   eos -r 101 101 -a myapp cp /eos/dev/lockedfile      - # will fail\n   eos -r 100 100 -a anotherapp cp /eos/dev/lockedfile - # will fail\n\n   # create a lock with a given lifetime e.g. 1000s\n   eos -r 100 100 -a myapp file touch -l /eos/dev/lockedfile 1000\n\n   # create a lock which only requires the same user to be used\n   eos -r 100 100 -a myapp file touch -l /eos/dev/lockedfile 1000 user\n\n   # create a lock which only requires the same app to be used\n   eos -r 100 100 -a myapp file touch -l /eos/dev/lockedfile 1000 app\n\nBy default locks are taken for 24h. The lifetime can be specified as seen before if needed. The audience can be relaxed to allow same app access or same user.\n\nYou can remove a lock if you are the owner by doing:\n\n.. code-block:: bash\n\n   # remove a lock\n   eos -r 100 100 -a myapp file touch -u /eos/dev/lockedfile\n\n\nThe internal representation of an attribute lock is given here:\n\n.. code-block:: bash\n\n   attr ls /eos/dev/lockedfile | grep sys.app.locks\n   # requiring a strict audience\n   sys.app.lock=\"expires:1665042101,type:exclusive,owner:daemon:myapp\"\n   # requiring same user\n   sys.app.lock=\"expires:1665042101,type:exclusive,owner:daemon:*\"\n   # requiring same app\n   sys.app.lock=\"expires:1665042101,type:exclusive,owner:*:myapp\"\n\nThe high-level functionality for creating/deletion of attribute locks can be circumvented by creating/deleting the *sys.app.locks* attribute using extended attribute interfaces.\n"
  },
  {
    "path": "doc/citrine/using/eos_services.rst",
    "content": ".. highlight:: rst\n\nDaemon Control\n==============\n\n\nControl individual daemons\n--------------------------\n\n.. code-block:: bash\n\n    systemctl start eos@mq   # for starting MQ service\n    systemctl start eos@sync # for starting SYNC service\n    systemctl start eos@mgm  # for starting MGM service\n    systemctl start eos@fst  # for starting FST service\n    systemctl start eos@fed  # for starting FED service\n\nIt's the same for stop, status and restart. You can't start the daemon if it is\nnot configured in \"/etc/sysconfig/eos_env\" config file.\n\n\nControl all daemons from the eos_env config file in the same time\n-----------------------------------------------------------------\n\n.. code-block:: bash\n\n   systemctl start eos     # for starting\n   systemctl stop eos@*    # for stopping all running daemons\n   systemctl status eos@*  # for getting the status of all running daemons\n   systemctl restart eos@* # for restarting all the running daemons\n\nYou can change the list of daemons (mgm|mq|sync|fst|fed)\nin ``/etc/sysconfig/eos_env`` config file.\n\n\nConfigure EOS MGM/MQ as master or slave\n---------------------------------------\n\n.. code-block:: bash\n\n   systemctl start eos@master # to configure MQ or/and MGM on localhost as master\n   systemctl start eos@slave  # to configure MQ or/and MGM on localhost as slave\n   systemctl start eosslave   # making EOS services running in slave mode\n\nYou can configure MQ or/and MGM only if they exist\nin ``/etc/sysconfig/eos_env`` config file.\n\n\nControl FST database\n--------------------\n\n.. code-block:: bash\n\n   systemctl start eosfstdb@clean  # cleaning FST db for fast restart\n   systemctl start eosfstdb@resync # forcing FST db resync for restart\n\nEOS FUSE service\n----------------\n\n.. code-block:: bash\n\n   systemctl start eosd     #   for starting\n   systemctl stop eosd@*    #   for stopping\n   systemctl status eosd@*  #   for status\n   systemctl restart eosd@* #   for restarting\n\nConfig file ``/etc/sysconfig/eosd_env`` is necessary.\n"
  },
  {
    "path": "doc/citrine/using/fusex.rst",
    "content": ".. highlight:: rst\n\neosxd\n=====\n\neosxd log file\n--------------\n\neosxd writes a log file into the fusex log directory ```/var/log/eos/fusex/fuse.<instancename>-<mountdir>.log```. The default verbosity is **warning** level.\n\neosxd statistics file\n----------------------\n\neosxd writes out a statistics file with an update rate of 1Hz into the fusex log directory ```/var/log/eos/fusex/fuse.<instancename>-<mountdir>.stats```.\n\n\nHere is an example:\n\n.. code-block:: bash\n\n\n   ALL     Execution Time                   5.06 +- 16.69 = 5.01s (1270 ops)\n   # -----------------------------------------------------------------------------------------------------------------------\n   who     command                          sum             5s     1min     5min       1h exec(ms) +- sigma(ms)  = cumul(s)\n   # -----------------------------------------------------------------------------------------------------------------------\n   ALL     :sum                                     1271     0.00     0.05     0.01     0.00     -NA- +- -NA-       = 0.00\n   ALL     access                                      4     0.00     0.00     0.00     0.00  1.82825 +- 1.64279    = 0.01\n   ALL     create                                      0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     flush                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     forget                                      0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     fsync                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     getattr                                    17     0.00     0.02     0.00     0.00  1.91859 +- 6.93590    = 0.03\n   ALL     getxattr                                   58     0.00     0.03     0.01     0.00  2.42547 +- 18.15372   = 0.14\n   ALL     link                                        0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     listxattr                                   0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     lookup                                    342     0.00     0.00     0.00     0.00  0.78381 +- 3.70048    = 0.27\n   ALL     mkdir                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     mknod                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     open                                        0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     opendir                                   215     0.00     0.00     0.00     0.00 20.56853 +- 26.64452   = 4.42\n   ALL     read                                        0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     readdir                                   416     0.00     0.00     0.00     0.00  0.05781 +- 0.07550    = 0.02\n   ALL     readlink                                    1     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     release                                     0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     releasedir                                215     0.00     0.00     0.00     0.00  0.00896 +- 0.00425    = 0.00\n   ALL     removexattr                                 0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     rename                                      0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     rm                                          0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     rmdir                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     setattr                                     0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     setattr:chmod                               0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     setattr:chown                               0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     setattr:truncate                            0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     setattr:utimes                              0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     setxattr                                    1     0.00     0.00     0.00     0.00  0.08500 +- -NA-       = 0.00\n   ALL     statfs                                      2     0.00     0.00     0.00     0.00 57.74450 +- 48.80550   = 0.12\n   ALL     symlink                                     0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     unlink                                      0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   ALL     write                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n   # -----------------------------------------------------------------------------------------------------------\n   ALL        inodes              := 375\n   ALL        inodes stack        := 0\n   ALL        inodes-todelete     := 0\n   ALL        inodes-backlog      := 0\n   ALL        inodes-ever         := 3051\n   ALL        inodes-ever-deleted := 0\n   ALL        inodes-open         := 0\n   ALL        inodes-vmap         := 3051\n   ALL        inodes-caps         := 1\n   # -----------------------------------------------------------------------------------------------------------\n   ALL        threads             := 32\n   ALL        visze               := 517.10 Mb\n   ALL        rss                 := 35.63 Mb\n   ALL        pid                 := 1689\n   ALL        log-size            := 409384\n   ALL        wr-buf-inflight     := 0 b\n   ALL        wr-buf-queued       := 0 b\n   ALL        wr-nobuff           := 0\n   ALL        ra-buf-inflight     := 0 b\n   ALL        ra-buf-queued       := 0 b\n   ALL        ra-xoff             := 0\n   ALL        ra-nobuff           := 0\n   ALL        rd-buf-inflight     := 0 b\n   ALL        rd-buf-queued       := 0 b\n   ALL        version             := 4.4.17\n   ALL        fuseversion         := 28\n   ALL        starttime           := 1549548272\n   ALL        uptime              := 66989\n   ALL        total-mem           := 8201658368\n   ALL        free-mem            := 149671936\n   ALL        load                := 1313970496\n   ALL        total-rbytes        := 0\n   ALL        total-wbytes        := 0\n   ALL        total-io-ops        := 1270\n   ALL        read--mb/s          := 0.00\n   ALL        write-mb/s          := 0.00\n   ALL        iops                := 0\n   ALL        xoffs               := 0\n   ALL        instance-url        := myhost.cern.ch:1094\n   ALL        client-uuid         := 4af8154c-2ae1-11e9-8e32-02163e009ce2\n   ALL        server-version      := 4.4.17\n   ALL        automounted         := 0\n   ALL        max-inode-lock-ms   := 0.00\n   # -----------------------------------------------------------------------------------------------------------\n\n\nThe first block contains global averages/sums for total IO time and IO operations:\n\n.. epigraph::\n\n   ======= ================================ =============   ================ ===========\n   tag     description                      avg/dev in ms   cumulative time  sum IOPS\n   ======= ================================ =============   ================ ===========\n   ALL     Execution Time                   4.80 +- 15.56   4.87s            (1267 ops)\n   ======= ================================ =============   ================ ===========\n\nThe second block contains counts for each filesystem operation the average rates in a 5s,1min,5min and 1h window, the average execution time and standard deviation for a given filesystem operation and cumulative seconds spent in each operation.\n\n\n.. epigraph::\n\n   ======= ================================ =============== ====== ======= =========== ====== ======= ============ =============\n   who     filesystem counter name          sum of ops      5s avg 1m avg   5m avg     1h avg avg(ms) siggma(ms)   cumulative(s)\n   ======= ================================ =============== ====== ======= =========== ====== ======= ============ =============\n   ALL     :sum                             1268            0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     access                           4               0.00   0.00    0.00        0.00   1.82825 +- 1.64279   0.01\n   ALL     create                           0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     flush                            0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     forget                           0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     fsync                            0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     getattr                          16              0.00   0.00    0.00        0.00   2.01987 +- 7.13716   0.03\n   ALL     getxattr                         56              0.00   0.00    0.00        0.00   0.02023 +- 0.00463   0.00\n   ALL     link                             0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     listxattr                        0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     lookup                           342             0.00   0.00    0.00        0.00   0.78381 +- 3.70048   0.27\n   ALL     mkdir                            0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     mknod                            0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     open                             0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     opendir                          215             0.00   0.00    0.00        0.00   20.5685 +- 26.64452  4.42\n   ALL     read                             0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     readdir                          416             0.00   0.00    0.00        0.00   0.05781 +- 0.07550   0.02\n   ALL     readlink                         1               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     release                          0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     releasedir                       215             0.00   0.00    0.00        0.00   0.00896 +- 0.00425   0.00\n   ALL     removexattr                      0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     rename                           0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     rm                               0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     rmdir                            0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     setattr                          0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     setattr:chmod                    0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     setattr:chown                    0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     setattr:truncate                 0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     setattr:utimes                   0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     setxattr                         1               0.00   0.00    0.00        0.00   0.08500 +- -NA-      0.00\n   ALL     statfs                           2               0.00   0.00    0.00        0.00   57.7450 +- 48.80550  0.12\n   ALL     symlink                          0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     unlink                           0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ALL     write                            0               0.00   0.00    0.00        0.00   -NA-    +- -NA-      0.00\n   ======= ================================ =============== ====== ======= =========== ====== ======= ============ =============\n\nThe third block displays inode related counts, which are explained inline.\n\n\n.. epigraph::\n\n   ========== ====================== =============== ===========================================================================\n   who        counter name           value           description\n   ========== ====================== =============== ===========================================================================\n   ALL        inodes                 375             currently in-memory known-inodes\n   ALL        inodes stack           0               inodes which could be forgotten, but needed to be kept on the stack\n   ALL        inodes-todelete        0               inodes which still have to be deleted upstream\n   ALL        inodes-backlog         0               inodes which still have to be updated upstream\n   ALL        inodes-ever            3051            inodes ever seen by this mount\n   ALL        inodes-ever-deleted    0               inodes ever deleted by this mount\n   ALL        inodes-open            0               inodes associated with an open file descriptor\n   ALL        inodes-vmap            3051            size of logical inode translation map\n   ALL        inodes-caps            0               inodes with a cache-callback subscription\n   ALL        threads                32              currently running threads\n   ALL        visze                  517.10 Mb       virtual memory used by the running daemon\n   ALL        rss                    35.13 Mb        resident memory used by the running daemon\n   ALL        pid                    1689            process id of the running daemon\n   ALL        log-size               367632          size of the logfile of the running daemon\n   ALL        wr-buf-inflight        0 b             write buffer allocated with data in-flight in writing\n   ALL        wr-buf-queued          0 b             write buffer allocated and kept on the queue for future reuse in writing\n   ALL        wr-nobuff              0               counter how often a 'no available buffer' condition was hit in writing\n   ALL        ra-buf-inflight        0 b             read-ahead buffer allocated with data in-flight in read-ahead\n   ALL        ra-buf-queued          0 b             read-ahead buffer allocated and kept on the queue for future reuse in ra\n   ALL        ra-xoff                0               counter how often we needed to wait for an available read-ahead buffer\n   ALL        ra-nobuff              0               counter how often a 'no available buffer' condition was hit in read-ahead\n   ALL        rd-buf-inflight        0 b             read buffer allocated with data in-flight for reading\n   ALL        rd-buf-queued          0 b             read buffer allocated and kept on the queue for future reuse in reading\n   ALL        version                4.4.17          current version of the daemon\n   ALl        fuseversion            28              current version of the FUSE protocol\n   ALL        starttime              1549548272      starttime as unixtimestamp\n   ALL        uptime                 64772           run time of the daemon in seconds\n   ALL        total-mem              8201658368      total memory of the hosting machine\n   ALL        free-mem               153280512       free memory of the hosting machine\n   ALL        load                   1313946976      1 minute load avg as returned by sysinfo\n   ALL        total-rbytes           0               total number of bytes read on this mount\n   ALL        total-wbytes           0               total number of bytes written on this mount\n   ALL        total-io-ops           1267            total number of io operations done on this mount\n   ALL        read--mb/s             0.00            1 minute average read rate in MB/s\n   ALL        write-mb/s             0.00            1 minute average write rate in MB/s\n   ALL        iops                   0               1 minute average io ops rate\n   ALL        xoffs                  0               counter how often we needed to wait for an available write buffer\n   ALL        instance-url           myhost:1094     hostname and port of the upstream EOS instance\n   ALL        client-uuid            4af8154c.....   unique identifier of this client (UUID)\n   ALL        server-version         4.4.17          server version where this client is connected\n   ALL        automounted            0               indicates if the mount is done via autofs\n   ALL        max-inode-lock-ms      0.00            maximum time any thread in the thread pool is stuck in ms\n   ========== ====================== =============== ===========================================================================\n\nThe statistics file can be printed by any user on request by running:\n\n.. code-block:: bash\n\n   eosxd get eos.stats <mount-point>\n\nThe statistics file counter can be reset by running as root:\n\n.. code-block:: bash\n\n   eosxd set system.eos.resetstat - /eos/\n\nServer Side Configuration\n-------------------------\n\nThe **eosxd** network provides four configuration parameters, which can be shown or modified using **eos fusex conf**\n\n.. code-block:: bash\n\n   [root@eos ]# eos fusex conf\n   info: configured FUSEX broadcast max. client audience 256 listeners\n   info: configured FUSEX broadcast audience to suppress match is '@b[67]'\n   info: configured FUSEX heartbeat interval is 10 seconds\n   info: configured FUSEX quota check interval is 10 seconds\n\nThe default heartbeat interval is 10 seconds. It is the interval each **eosxd** process sends a heartbeat message to the MGM server. The quota check interval is the interval after which the MGM FuseServer checks again if a **eosxd** client went out of quota or back to quota. The default is also 10 seconds.\n\nWhen working with thousands of clients within a single directory the amount of messages in the FuseServer broadcast network can overwhelm the MGM messaging capacity. To reduce the amount of messages sent around while files are open and written, a threshold can be defined after which a certain audience of clients will not receive anymore meta-data update or forced refresh messages. If 1000 clients write 1000 files within a single directory the message rate is 100kHz for file-size updates while the clients are writing. In the example above if a message hits more than 256 listeners and the client names start with b6 or b6 messages will be suppressed. Messages emitted when files are created or committed are not suppressed!\n\n\nNamespace Configuration\n-----------------------\n\nBy default each client sends his desired leastime for directory subscriptions (300s default at time of writing). For certain directories in the hierarchy which are essentially read-only it improves the overall performance to define a longer leasetime. In a home directory hierarchy like **/eos/user/f/foo** the first three directory level could have a longer lease time defined.\n\n.. code-block:: bash\n\n   [root@eos ]# eos attr set sys.forced.leasetime=86400 /eos/\n   [root@eos ]# eos attr set sys.forced.leasetime=86400 /eos/user/\n   [root@eos ]# eos attr set sys.forced.leasetime=86400 /eos/user/f\n\n\nFile State Tracking for eosxd\n-----------------------------\n\n\nThe namespace registers the state changes of a file inside the extended attribute *sys.fusex.state*.\n\nThe extended attribute can track up to 127 operations, then gets truncated to half. A truncation is indicated with a leading *|>* in the attribute.\n\nPossible state flags are:\n\n* C      := File has been created by the FuseServer\n* U      := File has been updated in the FuseServer\n* T      := File has been truncated in the FuseServer or opened with a TRUNCATE flag\n* ±      := File size has been changed\n* R      := File has been renamed in the FuseServer\n* M      := File has been moved in the FuseServer\n* 0      := an invalid operation has been seen in the FuseServer (should never happen)\n* Z      := File recovery has been triggered by a FUSE client\n* +fs    := File replica/stripe has been committed ( multiple entries possible, fs is the filesystem id in decimal)\n* c      := File checksum has been committed\n* s      := File size has been committed\n* v      := Replica has been verified for the checksum\n* V      := Replica has been verified for the size\n* |      := Terminates a commit sequence started with +fs\n* |>     := tracked operations exceeded 127 and the attribute has been truncated\n\n\nExample:\n\n.. code-block:: bash\n\n   [root@eos ]# eos attr ls /eos/file\n                ...\n                sys.fusex.state=\"CU±+2sc|+1v|U±+2sc|+1v|U±+2sc|+1v|\"\n                ...\n\nThis examples show the creation \"C\", the file size update \"U±\", a commit from filesystem 2 with checksum and size \"+2sc\", a commit from filesystem 1 with checksum verification \"+1v\", then subsequent two update sequences to the file resulting in filesize change.\n\n\nReplica/Chunk Tracking\n----------------------\n\nThe namespace registers all replica/stripe create,unlink and delete operation inside the extended attribute *sys.fs.tracking*.\n\nThe extended attribute truncates when it exceeds 127 letters to half. A truncation is nidicated with a leading *|>* in the attribute.\n\nPossible indicators are:\n\n* +fsid  := a replica/stripe was attached on filesystem fsid\n* -fsid  := a replica/stripe has been unlinked for filesystem fsid\n* /fsid  := a replica/stripe has been deleted on filesystem fsid\n\nExample:\n\n.. code-block:: bash\n\n   [root@eos  ]# eos attr ls /eos/file\n                 ...\n                 sys.fs.tracking=\"+1+2+3+4-1-2/1/2\"\n                 ...\n\nThis examples shows the how replicas are attached on filesystems 1,2,3,4, then unlinked on 1,2 and finally deleted on 1,2.\n"
  },
  {
    "path": "doc/citrine/using/oauth2.rst",
    "content": ".. highlight:: rst\n\n.. _oauth2:\n\nUsing OAUTH2 for authentication\n===============================\n\nTo enable OAUTH2 token translation, one has to configure the resource endpoint and enable OAUTH2 mapping:\n\n.. code-block:: bash\n\n   # enable oauth2 mapping\n   eos vid enable oauth2\n   # allow an oauth2 resource in requests\n   eos vid set map -oauth2 key:oauthresource.web.cern.ch/api/User vuid:0\n   # allow an oauth2 resource in requests (OIDC infrastructure)\n   eos vid set map -oauth2 key:auth.cern.ch/auth/realms/cern/protocol/openid-connect/userinfo vuid:0\n\nIf you want to check the audience claim in the ticket, you can add the audience to screen to each oauth2 resource:\n\n.. code-block:: bash\n\n   # allow on oauth2 resource in request for the audience 'eosoauth'\n   eos vid set map -oauth2 key:auth.cern.ch/auth/realms/cern/protocol/openid-connect/userinfo@eosatuch vuid:0\n\nIf you want to use a local account which is mapped in the instance to a local uid, you can define a 'sub' field mapping entry using:\n\n.. code-block:: bash\n\n   # remap the sub '7aa5167f-9c28-4336-8a66-af9145ea847d' to the local user id 1000\n   eos vid set map -oauth2 sub:7aa5167f-9c28-4336-8a66-af9145ea847d vuid:1000\n\n   \nAll XRootD based clients can add the oauth2 token in the endorsement environment variable for sss authentication.\n   \n.. code-block:: bash\n\n   XrdSecsssENDORSEMENT=oauth2:<access_token>:<oauth-resource>\n \nOAUTH2 is enabled by default, but can be explicitly en-/or disabled:\n\n.. code-block:: bash\n\n   # eos CLI/xrdcp etc.\n   env XrdSecPROTOCL=sss\n   env XrdSecsssENDORSEMENT=oauth2:...\n   eos whoami\n\n   # eosxd config file parameter\n\n   \"auth\" : { \n     \"oauth2\" : 1, #default\n     \"ssskeytab\" : \"/etc/eos/fuse.sss.keytab\", #default\n    }\n\n    export OAUTH2_TOKEN=FILE:/tmp/oauthtk_1000\n    # /tmp/oauthtk_1000 contains oauth2:<token>:<oauth-url>\n    ls /eos/ \n   \nOne has to supply an sss key for this communication, however the sss key user can be banned on the instance:\nClient and server should share an sss key for a user, which is actually not authorized to use the instance e.g.\n\n.. code-block:: bash\n\n   ############################\n   # client\n   ############################\n   echo 0 u:nfsnobody g:nfsnobody n:eos-test N:5506672669367468033 c:1282122142 e:0 k:0123456789012345678901234567890123456789012345678901234567890123 > $HOME/.eos.keytab\n   # point to keytab file\n   export XrdSecSSSKT=$HOME/.eos.keytab\n   # enforce sss\n   export XrdSecPROTOCOL=sss\n\n   ############################\n   #server\n   ############################\n\n   # server shares the same keytab entry\n   echo 0 u:nfsnobody g:nfsnobody n:eos-test N:5506672669367468033 c:1282122142 e:0 k:0123456789012345678901234567890123456789\\012345678901234567890123 >> /etc/eos.keytab\n\n   # server bans user nfsnobody or maybe uses already user allow, which bans this user by default\n   eos access ban user nfsnobody\n  \n   ############################\n   # client\n   ############################\n   \n   # exports the token in the environment\n   export XrdSecsssENDORSEMENT=oauth2:.....:auth.cern.ch/auth/realms/cern/protocol/openid-connect/userinfo\n\n   # test the ID\n   [ ~]$ eos whoami\n   Virtual Identity: uid=1234 (1234,65534,99) gid=1234 (1234,99) [authz:oauth2] host=localhost domain=localdomain geo-location=cern key=<oauth2> fullname='Foo Bar' email='foo.bar@cern.ch'\n\n\n\n\n\n"
  },
  {
    "path": "doc/citrine/using/policies.rst",
    "content": ".. highlight:: rst\n\n.. _space-policies:\n\nSpace and Application Policies\n==============================\n\nSpace policies are set using the space configuration CLI.\n\nThe following policies can be configured\n\n.. epigraph::\n\n   ============== ==============================================\n   key            values\n   ============== ==============================================\n   space          default,...\n   layout         plain,replica,raid5,raid6,raiddp,archive,qrain\n   nstripes       1..255\n   checksum       adler,md5,sha1,crc32,crc32c\n   blockchecksum  adler,md5,sha1,crc32,crc32c\n   blocksize      4k,64k,128k,512k,1M,4M,16M,64M\n   bandwidth:r|w  IO limit in MB/s for reader/writer\n   iotype:r|w     io flavour [ direct, sync, csync, dsync ]\n   iopriority:r|w io priority [ rt:0...rt:7,be:0,be:7,idle ]\n   schedule:r|w   fair FST scheduling [1 or 0]\n   ============== ==============================================\n\n\nSetting space policies\n----------------------\n\n\n.. code-block:: bash\n\n   # configure raid6 layout\n   eos space config default space.policy.layout=raid6\n\n   # configure 10 stripes\n   eos space config default space.policy.nstripes=10\n\n   # configure adler file checksumming\n   eos space config default space.policy.checksum=adler\n\n   # configure crc32c block checksumming\n   eos space config default space.policy.blockchecksum=crc32c\n\n   # configure 1M blocksizes\n   eos space config default space.policy.blocksize=1M\n\n   # configure a global bandwidth limitation for all streams of 100 MB/s in a space\n   eos space config default space.policy.bandwidth=100\n\n   # configure FST fair thread scheduling for readers\n   eos space config default space.policy.schedule:r=1\n\n   # configure default FST iopriority for writers\n   eos space config default space.policy.iopriority:w=be:4\n\n   # configure default FST iotype\n   eos space config default space.policy.iotype:w=direct\n\nSetting user,group and application policies\n-------------------------------------\n\nIO policies as iotype,iopriority,bandwidth and schedule can be scoped to a group,user or an application\n\n.. code-block:: bash\n\n   # configure an application specific bandwidth limitations for all reading streams in a space\n   eos space config default space.bandwidth:r.app:myapp=100 # reading streams tagged as ?eos.app=myapp are limited to 100 MB/s\n\n   eos space config default space.iotype:w.user:root=direct # use direct IO for writing streams by user root\n\n   eos space config default space.iopriority:r.group:adm=rt:1 # use IO priority realtime level 1 for the adm group when reading\n\nThe evaluation order is by space (lowest), by group, by user, by app (highest). Finally IO policies can be overwritten by extended **sys.forced** attributes (see the following).\n\nPolicy Selection and Scopes\n----------------------------\n\nClients can select the space ( and its default policies ) by adding ``eos.space=<space>`` to the CGI query of an URL, otherwise the space is taken from **space.policy.space** in the default space or if undefined it uses the **default** space to set space policies.\n\nExamples:\n\n.. code-block:: bash\n\n   ##############\n   # Example 1  #\n   ##############\n   # files uploaded without selecting a space will end up in the replica space unless there is a forced overwrite in the target directory\n\n   # point to the replica space as default policy\n   eos space config default space.policy..space=replica\n   # configure 2 replicas in the replica space\n   eos space config replica space.policy.nstripes=2\n   eos space config replica space.policy.layout=replica\n\n\n   ##############\n   # Example 2  #\n   ##############\n   # files uploaded selecting the rep4 space will be stored with 4 replicas, if non space is selected they will get the default for the target directory or the default space\n\n   # define a space with 4 replica policy\n   eos space config rep4 space.policys.nstripes=4\n   eos space config rep4 space.policy.layout=replica\n\n\nLocal Overwrites\n----------------\n\nThe space policies are overwritten by the local extended attribute settings of the parent directory\n\n.. epigraph::\n\n   ============= ===================================================\n   key           local xattr\n   ============= ===================================================\n   layout        sys.forced.layout, user.forced.layout\n   nstripes      sys.forced.nstripes, user.forced.nstripes\n   checksum      sys.forced.checksum, user.forced.checksum\n   blockchecksum sys.forced.blockchecksum, user.forced.blockchecksum\n   blocksize     sys.forced.blocksize, user.forced.blocksize\n   iopriority    sys.forced.iopriority:r|w\n   iotype        sys.forced.iotype:r|w\n   bandwidth     sys.forced.bandwidth:r|w\n   schedule      sys.forced.schedule:r|w\n   ============= ===================================================\n\n\nDeleting space policies\n-----------------------\n\nPolicies are deleted by setting a space policy with `value=remove` e.g.\n\n.. code-block:: bash\n\n   # delete a policy entry\n   eos space config default space.policy.layout=remove\n\n   # delete an application bandwidth entry\n   eos space config default space.bw.myapp=remove\n\n\nDisplaying space policies\n-------------------------\n\nPolicies are displayed using the ``space status`` command:\n\n.. code-block:: bash\n\n   eos space status default\n\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   autorepair                       := off\n   ...\n   policy.blockchecksum             := crc32c\n   policy.blocksize                 := 1M\n   policy.checksum                  := adler\n   policy.layout                    := replica\n   policy.nstripes                  := 2\n   policy.bandwidth:r               := 100\n   policy.bandwidth:w               := 200\n   policy.iotype:w                  := direct\n   policy.iotype:r                  := direct\n   ...\n   bw.myapp                         := 100\n   bw.eoscp                         := 200\n   ...\n\nAutomatic Conversion Policies\n-----------------------------\n\nAutomatic policy conversion policies allow to trigger a conversion job under two conditions:\n\n* a new file is created with a complete layout (all required replicas/stripes are created)        (use case IO optimization)\n* an existing file is injected with a complete layout (all required replicas/stripes are created) (use case TAPE recall)\n\nAutomatic conversion policy hooks are triggered by the ReplicationTracker. You find conversions triggered in the **ReplicationTracker.log** logfile.\n\nTo use automatic conversion hooks one has to enable policy conversion in the **default** space:\n\n.. code-block:: bash\n\n   eos space config default space.policy.conversion=on\n\nTo disable either remove the entry or set the value to off:\n\n.. code-block:: bash\n\n   #remove\n   eos space config default space.policy.conversion=remove\n   #or disable\n   eos space config default space.policy.conversion=off\n\nIt takes few minutes before the changed state takes effect!\n\n\nTo define a policy conversion whenever a file is uploaded for a specific space you configure:\n\n.. code-block:: bash\n\n   # whenever a file is uploaded to the space **default** a conversion is triggered into the space **replicated** using a **replica::2** layout.\n   eos space config default space.policy.conversion.creation=replica:2@replicated\n\n   # alternative declaration using a hex layout ID\n   eos space config default space.policy.conversion.creation=00100112@replicated\n\nAlso make sure that the converter is enabled:\n\n.. code-block:: bash\n\n   # enable the converter\n   eos space config default space.converter=on\n\nTo define a policy conversion whenever a file is injected into a specific space you configure:\n\n.. code-block:: bash\n\n   # whenever a file is injected to the space **ssd* a conversion is triggered into the space **spinner** using a **raid6:10** layout.\n   eos space config ssd space.policy.conversion.injection=raid6:10@spinner\n\n   # alternative declaration using a hex layout ID: replace raid6:10 with the **hex layoutid** (e.g. see file info of a file).\n\n.. warning::\n   You cannot change the file checksum during a conversion job! Make sure source and target layout have the same checksum type!\n\nYou can define a minimum or maximum size criteria to apply automatic policy conversion depending on the file size.\n\n.. code-block:: bash\n\n   # convert files on creation only if they are at least 100MB\n   eos space config ssd space.policy.conversion.creation.size=>100000000\n\n   # convert files on creation only if they are smaller than 1024 bytes\n   eos space config ssd space.policy.conversion.creation.size=<1024\n\n   # convert files on injection only if they are bigger than 1G\n   eos space config ssd space.policy.conversion.injection.size=>1000000000\n\n   # convert files on injection only if they are smaller than 1M\n   eos space config ssd space.policy.conversion.injection.size=<1000000\n\nShared Filesystem Redirection\n-----------------------------\n\nWhen all FSTs in a space store data into a shared filesystem and clients might have access to all the data for reading, one can enable the redirection to a local filesystem:\n\n.. code-block:: bash\n\n   # define the local redirection policy in the given space called 'nfs'\n   eos space config nfs space.policy.localredirect=1\n\n   # define local redirection on a per directory basis\n   eos attr set sys.forced.localredirect.nfs=1\n\nPlease note: a space defined policy overwrites any directory policy.\n\nLocal redirection is currently supported for single replica files. It is disabled for PIO access with *eoscp* (default)), but works with *xrdcp* and *eoscp -0*.\nIf the client does not see the shared filesystem, the client will fall back to the MGM and read with the FST. If the client sees the shared filesystem but cannot read it, the client will fail.\n\nOne can manually select/disable local redirection using a CGI tag:\n\n.. code-block:: bash\n\n   # enable local redirection via CGI\n   root://localhost//eos/shared/file?eos.localredirect=1\n\n   # disable local redirection via CGI\n   root://localhost//eos/shared/file?eos.localredirect=0\n\nRedirections are accounted in the *eos ns stat* accounting as failed and successful redirection on open:\n\n.. code-block:: bash\n\n   eos ns stat | grep RedirectLocal\n   all OpenFailedRedirectLocal             0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-\n   all OpenRedirectLocal                  14     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-\n"
  },
  {
    "path": "doc/citrine/using/priorities.rst",
    "content": ".. highlight:: rst\n\n.. _io-priorities:\n\nIO Priorities\n=============\n\nIO priorities are currently ony supported by devices using the CFQ (CentOS7) or BFQ (Centos8s) scheduler for reads and direct writes.\nYou can figure out which scheduler is used by inspecting:\n\n.. code-block:: bash\n\n   cat /sys/block/*/queue/scheduler\n\n\nSupported IO Priorities\n-----------------------\n\n* real-time (level 0-7) \n* best-effort (level 0-7)\n* idle (level 0)\n\nReal-time Class\n+++++++++++++++\n\nThis is the real-time I/O class.  This scheduling class is given higher priority than any other class: processes from this class are given first access to the disk every time. Thus, this I/O class needs to be used with some care: one I/O real-time process can starve the entire system. Within the real-time class, there are 8 levels of class data (priority) that determine exactly how much time this process needs the disk for on each service. The highest real-time priority level is 0; the lowest is 7.\nThe priority is defined in EOS f.e. as *rt:0* or *rt:7*.\n\nBest-Effort Class\n+++++++++++++++++\n\nThis is the best-effort scheduling class, which is the default for any process that hasn't set a specific I/O priority.The class data (priority) determines how much I/O bandwidth the process will get.  Best-effort priority levels are analogous to CPU nice values. The priority level determines a priority relative to other processes in the best-effort scheduling class.  Priority levels range from 0 (highest) to 7 (lowest). The priority is defined in EOS f.e. as *be:0* or *be:4*.\n\n\nIdle Class\n++++++++++\n\nThis is the idle scheduling class.  Processes running at this level get I/O time only when no one else needs the disk.  The idle class has no class data, but the configuration requires to configure it in EOS as *idle:0* . Attention is required when assigning this priority class to a process, since it may become starved if higher priority processes are constantly accessing the disk.\n\nSetting IO Priorities\n---------------------\n\nIO priorities can be set in various ways:\n\n.. code-block:: bash\n\n   # via CGI if the calling user is member of the operator role e.g. make 99 member of operator role\n   eos vid set membership 99 -uids 11\n   # use URLs like\n   \"root://localhost//eos/higgs.root?eos.iopriority=be:0\"\n\n   # as a default space policy for readers\n   eos space config default space.policy.iopriority:r=rt:0\n\n   # as a space policy\n   eos space config erasure space.policy.iopriority:w=idle:0\n\n   # as a default application policy e.g. for application foo writers \n   eos space config default space.iopriority:w.app:foo=be:4\n\n   # as a space application policy e.g. for application bar writers\n   eos space config erasure space.iopriority:w.app:bar=be:7\n\nThe CGI (if allowed via the operator role) is overruling any other priority configuration. Otherwise the order of evaluation is shown as in the block above. \n\nFor handling of policies in general (how to show, configure and delete) refer to :ref: `space-policies`.\n\n\n"
  },
  {
    "path": "doc/citrine/using/rain.rst",
    "content": ".. highlight:: rst\n\nRAIN\n====\n\nECC Layout Types\n----------------\n\nEOS supports five types of RAIN layouts:\n\n.. epigraph::\n\n   ========== ============= ================================ ====================================\n   name       redundancy    algorithm                        description\n   ========== ============= ================================ ====================================\n   raid5      N+1           single parity raid               can lose 1 disk without data loss\n   raiddp     4+2           dual parity raid                 can lose 2 disks without data loss\n   raid6      N+2           Erasure Code (Jerasure library)  can lose 2 disks without data loss\n   archive    N+3           Erasure Code (Jerasure library)  can lose 3 disks without data loss\n   qrain      N+4           Erasure Code (Jerasure library)  can lose 4 disks without data loss\n   ========== ============= ================================ ====================================\n\nThe layout is set in a namespace tree via ``eos attr -r set default=<name> <tree>``.\n\nThe minimum number of stripes is currently 6 for all erasure coding layouts (raiddp, raid6, archive, qrain).\n\nThe default layout can be defined using default space policies. See :ref:`space-policies`.\n"
  },
  {
    "path": "doc/citrine/using/reports.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: reports\n\n.. _systemd:\n\nReport log files\n================\n\nThe EOS MGM writes report log files under `/var/eos/report/<YEAR>/<MONTH>/<YEAR><MONTH><DAY>.eosreport`\n\nOn top of a few `xrd.cf.mgm` configuration variables, it must be enabled on the MGM:\n\n.. code-block:: bash\n\n  EOS Console [root://localhost] |/eos/ctaatlaspps/archivetest/> io enable -r\n  success: enabled IO report store\n\n\nFile creation/update records\n----------------------------\n\nEach FST sends for each file replica or piece it writes a record which looks like this:\n\n``log=cb9ae364-4f7c-11e8-8a9a-02163e009ce2&path=/eos/testfile&ruid=0&rgid=0&td=root.13142:52@slc7&host=test.cern.ch\n&lid=1048578&fid=1056332&fsid=1&ots=1525425804&otms=531&cts=1525425804&ctms=533&nrc=0&nwc=1&rb=0&rb_min=0&rb_max=0\n&rb_sigma=0.00&rv_op=0&rvb_min=0&rvb_max=0&rvb_sum=0&rvb_sigma=0.00&rs_op=0&rsb_min=0&rsb_max=0&rsb_sum=0&rsb_sigma=0.00\n&rc_min=0&rc_max=0&rc_sum=0&rc_sigma=0.00&wb=2202&wb_min=2202&wb_max=2202&wb_sigma=0.00&sfwdb=0&sbwdb=0&sxlfwdb=0\n&sxlbwdb=0&nfwds=0&nbwds=0&nxlfwds=0&nxlbwds=0&rt=0.00&rvt=0.00&wt=0.01&osize=0&csize=2202&delete_on_close=0&prio_c=2&prio_l=4&prio_d=1\n&sec.prot=sss&sec.name=daemon&sec.host=localhost&sec.vorg=&sec.grps=daemon&sec.role=&sec.info=&sec.app=eoscp``\n\n.. epigraph::\n\n   ==================== ==================================================================================================\n   TAG                  Description\n   ==================== ==================================================================================================\n   log                  uuid to correlate log entries\n   path                 logical path\n   ruid                 mapped user id\n   rgid                 mapped group id\n   td                   trace identifier: <unix-user>.<pid>.<fd>@<host>.<domain>\n   lid                  layout id\n   fid                  file id\n   fsid                 file system id\n   ots                  open timestamp\n   otms                 open time milliseconds\n   cts                  close timestamp\n   ctms                 close time milliseconds \n   nrc                  number of read calls\n   nwc                  number of write calls\n   rb                   bytes read (non vector reads)\n   rb_min               smallest read call in bytes (non vector reads)\n   rb_max               largest read call in bytes (non vector reads)\n   rb_sigma             standard deviation of read bytes (non vector reads)  \n   rv_op                number of vector operations\n   rvb_min              smallest vector read in bytes\n   rvb_max              largest vector read in bytes\n   rvb_sum              sum of all vector read bytes\n   rvb_sigma            standard deviation of vector read bytes\n   rs_op                number of single reads in vector operations\n   rsb_min              smallest read call in vector operations\n   rsb_max              largest read call in vector operations\n   rsb_sum              sum of all individual read call bytes in vector operations\n   rsb_sigma            standard deviation of single read calls in vector operations\n   rc_min               smallest number of read calls in vector read operations\n   rc_max               largest number of read calls in vector read operations\n   rc_sum               sum of all read call sin vector read operations\n   rc_sigma             standard deviation of number of read calls in vector read operations\n   wb                   bytes written \n   wb_min               smallest write call in bytes\n   wb_max               largest write call in bytes\n   wb_sigma             standard deviation of write call in bytes\n   sfwdb                forward seeked bytes \n   sbwdb                backward seeked bytes\n   sxlfwdb              forward seeked bytes moving at least 128kb per seek\n   sxlbwdb              backward seekd bytes moving at least 128kb per seek\n   nfwds                number of forward seeks\n   nbwds                number of backward seeks\n   nxlfwds              number of large forward seeks (>=128kb)\n   nxlbwds              number of large backward seeks (>=128kb)\n   ot                   time spent in ms to open the file\n   ct                   time spent in ms to close a file (includes waiting for async writes and checksumming)\n   rt                   time spent in ms waiting for disk reads\n   rvt                  time spent in ms waiting for disk reads for vector reads\n   wt                   time spent in ms waiting for disk writes\n   lrt                  time spent in ms waiting for layout reads\n   lrvt                 time spent in ms waiting for layout vector reads\n   lwt                  time spent in ms waiting for layout writes\n   iot                  time spent in total from open to close\n   idt                  idle time from open to close (where no open, close, read,readv or write happens)\n   osize                size of the file when opening\n   csize                size of the file when closing\n   delete_on_close      flag indicating delete on close status\n   prio_c               IO priority class (0:none 1:realtime 2:best effort 3:idle)\n   prio_l               IO priority level 0..7\n   prio_d               1: default values (best effort level 4) 0: explicitly set\n   sec.prot             security protocol e.g. krb5,gsi,sss,unix\n   sec.name             mapped user name e.g. root/daemon\n   sec.host             client host\n   sec.vorg             virtual organisation (only VOMS)\n   sec.grps             virtual group (only VOMS)\n   sec.role             virtual role (only VOMS)\n   sec.info             security information e.g. DN\n   sec.app              application responsible for record e.g. balancing,gridftp,eoscp,fuse\n   tpc.src              TPC source hostname (only on TPC transfers)\n   tpc.dst              TPC destination hostname (only on TPC transfers)\n   tpc.src_lfn          TPC file path at source (only on TPC transfers)\n   ior_err              1 (io error during read) otherwise 0\n   iow_err              1 (io error during write) otherwise 0\n   ==================== ==================================================================================================\n\nNote: In case of TPC transfers, only one of `tpc.src` or `tpc.dst` is available,\ndepending on the type of TPC transfer\n\nFST deletion records\n----------------------------\n\nEach FST sends for a deletion on disk a record which is tagged with application *deletion* :\n`log=619d7b82-4f79-11e8-a96c-02163e009ce2&host=test.cern.ch&fid=1056316&fsid=1&dc_ts=1525425793&dc_tns=968438733&dm_ts=1525425793&dm_tns=968438733&da_ts=1525425793&da_tns=968438733&dsize=2202&sec.app=deletion`\n\n.. epigraph::\n\n   ==================== ==================================================================================================\n   TAG                  Description\n   ==================== ==================================================================================================\n   log                  uuid to correlate log entries\n   host                 FST host name\n   fid                  file id of the file deleted\n   fsid                 filesystem id where the file is deleted\n   del_ts               timestamp when the deletion message was generated\n   del_tns              timestamp in ns when the deletion message was generated\n   dc_ts                change timestamp of the deleted file\n   dc_tns               change timestamp in ns of the deleted file\n   dm_ts                modification timestamp of the deleted file\n   dm_tns               modification timestamp in ns of the deleted file\n   da_ts                access timestamp on local disk of the deleted file\n   da_tns               access timestamp on local disk in ns of the deleted file\n   dsize                size of the file before deletion\n   sec.app              always: deletion\n   ==================== ==================================================================================================\n\nMGM deletion records\n----------------------------\n\nThe MGM sends for each final deletion a record which is tagged with application *rm* :\n`log=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&host=test.cern.ch:1094&fid=1056331&ruid=0&rgid=0dc_ts=1525425819&dc_tns=354463329&dm_ts=1525425804&dm_tns=478169000&dsize=2202&sec.app=rm`\n\nThe MGM sends for each deletion moving a file into the recycle bin a record tagged with application *recycle* :\n`log=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&host=test.cern.ch:1094&fid=1056325&ruid=0&rgid=0dc_ts=1525425819&dc_tns=351463254&dm_ts=1525425804&dm_tns=182997000&dsize=2202&sec.app=recycle`\n\n.. epigraph::\n\n   ==================== ==================================================================================================\n   TAG                  Description\n   ==================== ==================================================================================================\n   log                  always: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n   host                 MGM host name\n   fid                  file id\n   del_ts               timestamp when the deletion message was generated\n   del_tns              timestamp in ns when the deletion message was generated\n   dc_ts                change timestamp of the deleted file\n   dc_tns               change timestamp in ns of the deleted file\n   dm_ts                modification timestamp of the deleted file\n   dm_tns               modification timestamp in ns of the deleted file\n   dsize                size of the file before deletion\n   sec.app              rm,recycle (see above)\n   ==================== ==================================================================================================\n"
  },
  {
    "path": "doc/citrine/using/sharedfs.rst",
    "content": ".. highlight:: rst\n\n.. _sharedfs:\n\nUsing a shared filesystem as FST backend\n========================================\n\nThe EOS FST server can be configured to store data on any (shared) filesystem as storage device which supports extended attributes. To avoid that filesystems sharing a device or a shared filesystem are accounted multiple times in the space and node aggregation, one can label each filesystem with a shared filesystem name to indicated that all devices using this name share the same hardware resource.\n\nThe shared filesystem name can be configured when a filesystem is added e.g. a CephFS filesystem names cephfs1\n\n.. code-block:: bash\n\n   fs add 7a41781f-62dc-4f18-8f64-375e57487578 foo.cern.ch /cephfs/ default rw cephfs1\n\nIf filesystems are already registered or the filesystem name has changed one can use the filesystem config command:\n\n.. code-block:: bash\n\n   fs config 1 sharedfs=cephfs1\n\n"
  },
  {
    "path": "doc/citrine/using/squashfs.rst",
    "content": ".. highlight:: rst\n\n.. _squashfs:\n\nUsing SquashFS images for software distribution\n===============================================\n\nEOS provides support for SquashFS image files, which can be automatically mounted when the image path is traversed.\nThis functionality requires an appropriate automount configuration.\n\nTo create SquashFS images a client needs the EOS shell and a local mount with an identical path prefix as inside the client shell.\nThis means e.g. both commands as shown here point to the same directory:\n\n.. code-block:: bash\n\n   # access inside the shell\n   eos ls -la /eos/foo/bar\n   # access using the FUSE mount\n   ls -la /eos/foo/bar\n\n\nTo really have read-only access to the  contents of SquashFS images, clients have to install the package **cern-eos-autofs-squashfs**.\n\nAll functionality of the SqashFS CLI is displays using the help option:\n\n.. code-block:: bash\n\n   eos squash -h\n\n\nThe functionality can be grouped into two categories:\n\n* Simple SquashFS packages\n* Release SquashFS packages\n\n\nSimple SquashFS Packages\n------------------------\n\nA simple SquashFS package consists of a symbolic link under the package path and a hidden package file in the same directory as the symbolic link.\n\nThe workflow to create a SquashFS package is shown here:\n\nCreate a new package\n++++++++++++++++++++\n.. code-block:: bash\n\n   [root@dev ]# eos mkdir -p /eos/dev/squash/\n   [root@dev ]# eos squash new /eos/dev/squash/mypackage\n   info: ready to install your software under '/eos/dev/squash/mypackage'\n   info: when done run 'eos squash pack /eos/dev/squash/mypackage' to create an image file and a smart link in EOS!\n\n   # see what happend - we have created a symbolic link in EOS with the package pathname and the link points to a local stage directory in /var/tmp/...\n   [root@dev ]# eos ls -la /eos/dev/squash/\n   drwxrwxrw+   1 root     root               59 May 27 13:32 .\n   drwxrwxrw+   1 root     root       4751231651 May 27 13:32 ..\n   lrwxrwxrwx   1 nobody   nobody             59 May 27 13:32 mypackage -> /var/tmp/root/eosxd/mksquash/..eos..dev..squash..mypackage/\n\n\nInstall software into a package\n+++++++++++++++++++++++++++++++\n\n.. code-block:: bash\n\n   # install software into the package, de facto we work on the local disk under /var/tmp/...\n   [root@dev ]# cd /eos/dev/squash/mypackage/\n   [root@dev ]# touch HelloWorld\n\nPack a new package\n++++++++++++++++++\n\n.. code-block:: bash\n\n   # pack the new package\n   [root@dev ]# eos squash pack /eos/dev/squash/mypackage\n\n   # see what happened - the symlink in EOS with the package pathname points to an encoded location for the hidden package file .mypackage.sqsh\n   [root@dev ]# eos ls -la /eos/ajp/squash/\n   drwxrwxrw+   1 root     root             4161 May 27 13:38 .\n   drwxrwxrw+   1 root     root       4751235753 May 27 13:32 ..\n   -rw-r--r--   2 nobody   nobody           4096 May 27 13:38 .mypackage.sqsh\n   lrwxrwxrwx   1 nobody   nobody             65 May 27 13:38 mypackage -> /eos/squashfs/ajp.cern.ch@---eos---ajp---squash---.mypackage.sqsh\n\n\nIf you try to use or access a package on a different client machine before you call **eos squash pack** you will get errors on clients, because the symbolic link points to a non-existing local directory as long as a package is not closed.\n\nIn general you have to treat SquashFS packages as write-once archives. There is the possibility to unpack a packed archive, modify and re-pack, however this is problematic if a package is already accessed on other clients using the automount mechanism. They won't remount an updated package automatically unless the mount is removed by idle timeouts and re-mounted later.\n\n\nPackage information\n++++++++++++++++++\n\n\nFor completeness here are the commands to get information about a package:\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash info /eos/dev/squash/mypackage\n   info: '/eos/dev/squash/.mypackage.sqsh' has a squashfs image with size=4096 bytes\n   info: squashfs image is currently packed - use 'eos squash unpack /eos/dev/squash/mypackage' to open image locally\n\n\nUnpackaging\n+++++++++++\n\nAs mentioned you can unpack an existing package:\n\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash unpack /eos/ajp/squash/mypackage\n   ...\n   info: squashfs image is available unpacked under '/eos/dev/squash/mypackage'\n   info: when done with modifications run 'eos squash pack /eos/dev/squash/mypackage' to create an image file and a smart link in EOS!\n\n\nAnd pack it again:\n\n.. code-block:: bash\n\n   # pack the new package\n   [root@dev ]# eos squash pack /eos/dev/squash/mypackage\n\nDeleting a package\n++++++++++++++++++\n\nTo delete a SquashFS package you run:\n\n.. code-block:: bash\n\n   # delete a package\n   [root@dev ]# eos squash rm /eos/dev/squash/mypackage\n\n\nRelabeling a package\n++++++++++++++++++++\n\nIf a SquashFS package and/or package files has been moved around in the namespace e.g. by doing this ...\n\n.. code-block:: bash\n\n   [root@dev ]# eos mv /eos/dev/squash/ /eos/dev/newsquash/\n\nthe package links are broken. In this case one has to relabel the package doing:\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash relabel /eos/dev/newsquash/mypackage\n\n\nRemote web installation of packages\n++++++++++++++++++++++++++++++++++++\n\nThe CLI provides a convenience function to install a .tar.gz package from a web URL:\n\n.. code-block:: bash\n\n   [roo@dev ]# eos squash install --curl=https://root.cern/download/root_v6.24.00.Linux-centos7-x86_64-gcc4.8.tar.gz /eos/dev/newsquash/root\n\nAfter successful execution the software package is ready for use and no further packaging commands are required.\n\nIf you have the automounter RPM installed on your client you are ready to use the software:\n\n.. code-block:: bash\n\n   cd /eos/dev/newsquash/root/\n   ...\n\n\nRelease SquashFS Packages\n-------------------------\n\nThe **simple** package functionality is sufficient, if properly used. Many times you want to deal with updates and new release/versions of software. In this case the **release** functionality is preferable.\n\nCreating a new release package\n++++++++++++++++++++++++++++++\n\nRelease package management is illustrated in the following:\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash new-release /eos/dev/release/mypackage\n   info: ready to install your software under '/eos/dev/release/mypackage/.archive/mypackage-20210527135506'\n   info: when done run 'eos squash pack /eos/dev/release//mypackage/.archive/mypackage-20210527135506' to create an image file and a smart link in EOS!\n   info: install the new release under '/eos/dev/release/mypackage/next'\n\n\nThis new release is now locally available under **/eos/dev/release/mypackage/next**. You can install your software to this location and then call\n\nPacking a new release package\n+++++++++++++++++++++++++++++++\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash pack-release /eos/dev/release/mypackage\n   ...\n   info: new release available under '/eos/ajp/squash/mypackage/current'\n\nNow we have published the latest version of our release under **/eos/dev/release/mypcakge/current**. Our package name is in the release management mode a directory containing a **current** link, if there is an open new release a **next** link and a hidden **.archive** directory, where all versions of a release are stored.\n\nBy default a release is created with the unix timestamp during **new-release**. For most people it might be more convenient to specify a version number. In this case you call:\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash new-release /eos/dev/release/mypackage v1.0.0\n   ...\n   [root@dev ]# eos squash pack-release /eos/dev/release/mypackage\n   [root@dev ]# eos squash new-release /eos/dev/release/mypackage v1.1.0\n   ...\n   [root@dev ]# eos squash pack-release /eos/dev/release/mypackage\n\nRelease Package Information\n+++++++++++++++++++++++++++\n\nYou can obtain information about all available versions/releases doing:\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash info-release /eos/dev/release/mypackage\n   ---------------------------------------------------------------------------\n   - releases of '/eos/ajp/squash/mypackage'\n   ---------------------------------------------------------------------------\n   /eos/dev/squash/mypackage/.archive/mypackage-v1.0.0\n   /eos/dev/squash/mypackage/.archive/mypackage-v1.1.0\n   /eos/dev/squash/mypackage/current\n---------------------------------------------------------------------------\n\nThe output shows two versions in the **archive** and the **current** link.\n\nTrimming Release Packages\n+++++++++++++++++++++++++++\n\nIf you regulary build software releases, you want to limit the number of versions, which are kept.\n\nYou can trim your softare releases using:\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash trim-release /eos/dev/release/mypackage 100\n\nThis command will keep only versions not older than 100 days.\n\nAdditionally you can specify the maximum number of versions to keep:\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash trim-release /eos/dev/release/mypackage 100 10\n\nIn this case we don't want to keep more than the 10 most recents versions, not older than 100 days.\n\nDeleting Release Packages\n+++++++++++++++++++++++++\n\nFor completeness, there is a command to cleanup a release package. Be aware, that this will deleted all your release versions!\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash rm-release /eos/dev/release/mypackage\n   ---------------------------------------------------------------------------\n   - releases of '/eos/dev/release/mypackage'\n   ---------------------------------------------------------------------------\n   /eos/dev/release/mypackage/.archive/mypackage-v1.0.0\n   /eos/dev/release/mypackage/.archive/mypackage-v1.1.0\n   /eos/dev/release/mypackage/current\n   ---------------------------------------------------------------------------\n   info: wiping squashfs releases under '/eos/dev/release/mypackage'\n   info: wiping links current,next ...\n   info: wiping archive ...\n\nThe main difference between simple and release packages is, that you can create new release while the previous one is in use on any other client.\n"
  },
  {
    "path": "doc/citrine/using/systemd.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: systemd\n\nService control with systemd\n============================\n\nUnlike in case of SysV scripts, the sysconfig file for EOS running with systemd is `/etc/sysconfig/eos_env`.\n\nControl individual daemons\n--------------------------\n\n.. code-block:: bash\n\n   systemctl start eos@mq   - for starting MQ service\n   systemctl start eos@sync - for starting SYNC service\n   systemctl start eos@mgm  - for starting MGM service\n   systemctl start eos@fst  - for starting FST service\n   systemctl start eos@fed  - for starting FED service\n\nIt's the same for stop, status and restart. You can't start the daemon if it is\nnot configured in `/etc/sysconfig/eos_env` config file.\n\n\nBulk control of all daemons\n---------------------------\n\n.. code-block:: bash\n\n   systemctl start eos     - for starting\n   systemctl stop eos@*    - for stopping all running daemons\n   systemctl status eos@*  - for getting the status of all running daemons\n   systemctl restart eos@* - for restarting all the running daemons\n\nYou can change the list of daemons (mgm|mq|sync|fst|fed) handle by wildcard control\nin the `/etc/sysconfig/eos_env` config file.\n\n\nConfigure EOS MGM/MQ as master or slave\n---------------------------------------\n\n.. code-block:: bash\n\n   systemctl start eos@master - to configure MQ or/and MGM on localhost as master\n   systemctl start eos@slave  - to configure MQ or/and MGM on localhost as slave\n   systemctl start eosslave   - making EOS services running in slave mode\n\nYou can configure MQ or/and MGM only if they are defined\nin `/etc/sysconfig/eos_env` config file.\n\n\n\nControl of FST database\n-----------------------\n\n.. code-block:: bash\n\n   systemctl start eosfstdb@clean  - cleaning FST db for fast restart\n   systemctl start eosfstdb@resync - forcing FST db resync for restart\n\n\n\nControl file synchronization service between two MGM machines\n-------------------------------------------------------------\n\n\nEOS FUSE service\n----------------\n\n.. code-block:: bash\n\n   systemctl start eosd     -   for starting\n   systemctl stop eosd@*    -   for stopping\n   systemctl status eosd@*  -   for status\n   systemctl restart eosd@* -   for restarting\n\nConfig file `/etc/sysconfig/eosd_env` is necessary.\n"
  },
  {
    "path": "doc/citrine/using/tokens.rst",
    "content": ".. highlight:: rst\n\n.. _tokens:\n\nUsing EOS Tokens for Authorization\n==================================\n\nWe provide a generic EOS mechanism to delegate permissions to a token bearer with s.c. EOS tokens. \n\nThe JSON representation of an EOS token looks like shown here:\n\n.. code-block:: bash\n\n   {\n    \"token\": {\n     \"permission\": \"rwx\",\n     \"expires\": \"1571319146\",\n     \"owner\": \"\",\n     \"group\": \"\",\n     \"generation\": \"1\",\n     \"path\": \"/eos/dev/token\",\n     \"allowtree\": false,\n     \"vtoken\": \"\",\n     \"voucher\": \"baecb618-f0e4-11e9-85d9-fa163eb6b6cf\",\n     \"requester\": \"[Thu Oct 17 15:47:59 2019] uid:0[root] gid:0[root] tident:root.13809:107@localhost name:daemon dn: prot:sss host:localhost domain:localdomain geo:cern sudo:1\",\n     \"origins\": []\n    },\n    \"signature\": \"daUeOZafRUt6VfQZ+g3FMbR/ZA5WvARELqFwdQxbyFU=\",\n    \"serialized\": \"CgJyeBDq2qHtBTIJL2Vvcy9kZXYvSiRiYWVjYjYxOC1mMGU0LTExZTktODVkOS1mYTE2M2ViNmI2Y2ZSnAFbVGh1IE9jdCAxNyAxNTo0Nzo1OSAyMDE5XSB1aWQ6MFtyb290XSBnaWQ6MFtyb290XSB0aWRlbnQ6cm9vdC4xMzgwOToxMDdAbG9jYWxob3N0IG5hbWU6ZGFlbW9uIGRuOiBwcm90OnNzcyBob3N0OmxvY2FsaG9zdCBkb21haW46bG9jYWxkb21haW4gZ2VvOmFqcCBzdWRvOjE=\",\n    \"seed\": 1399098912\n   }\n\n\nEssentially this token gives the bearer the permission to ``rwx`` for the file /eos/dev/token. The token does not bear an\nowner and group information, which means, that the creations will be accounted on the mapped authenticated user using this token or an enforced ``sys.owner.auth`` entry. If the token should map the authenticated user, one can add ``owner`` and ``group`` fields. In practical terms the token removes existing user and system ACL entries and places the token user/group/permission entries as a system ACL.\n\nTokens are signed, zlib compressed, base64url encoded with a replacement of the '+' and '/' characters with '-' and '_'  and a URL encoding of the '=' character to avoid interferences/confusion with directory and file names.\n\nThe ``voucher`` field is tagged on the file when a file has been created and is also used as the logging id for this file upload. The ``requester`` field reports when, by whom and how a token has been generated.\n\n\nEnabling Token Issuing\n----------------------\n\nTo enable issuing of tokens, the space configuration value ``token.enegeration`` has to be set unequal to 0.\n\n.. code-block:: bash\n\n   eos space config default space.token.generation=1\n\n   \nBy default the signing key is derived from the instance sss keytab. If you want to define your own signature key, you can point to a file containing the key in **/etc/sysconfig/eos_env**:\n\n.. code-block:: bash\n\n   EOS_MGM_TOKEN_KEYFILE=/etc/eos/token.key\n\nThe token key file must be owned by the daemon user and have 400 permission!\n\nToken creation\n--------------\n\nThe CLI interface to create a token is shown here:\n\n.. code-block:: bash\n\n   # create a generic read-only token for a file valid 5 minutes\n   EXPIRE=`date +%s; let LATER=$EXPIRE+300\n\n   eos token --path /eos/myfile --expires $LATER\n   zteos64:MDAwMDAwNzR4nONS4WIuKq8Q-Dlz-ltWI3H91Pxi~cSsAv2S~OzUPP2SeAgtpMAY7f1e31Ts-od-rgcLZ~a2~bhwcZO9cracyhm1b3c6jpRIEWWOws71Ox6xAABeTC8I\n\n   # create a generic read-only token for a directory - mydir has to end with a '/' - valid 5 minutes\n   eos token --path /eos/mydir/ --expires $LATER\n\n   # create a generic read-only token for a directory tree - mytree has to end with a '/' - valid 5 minutes\n   eos token --path /eos/mydir/ --tree --expires $LATER\n\n   # create a generic write token for a file - valid 5 minutes\n   eos token --path /eos/myfile --permission rwx --expires $LATER\n\nToken inspection\n----------------\n\nThe CLI interface to show the contents of a token is shown here:\n\n.. code-block:: bash\n\n   eos token --token zteos64:MDAwMDAwNzR4nONS4WIuKq8Q-Dlz-ltWI3H91Pxi_cSsAv2S_OzUPP2SeAgtpMAY7f1e31Ts-od-rgcLZ_a2_bhwcZO9cracyhm1b3c6jpRIEWWOws7\n\n   TOKEN=\"zteos64:MDAwMDAwNzR4nONS4WIuKq8Q-Dlz-ltWI3H91Pxi_cSsAv2S_OzUPP2SeAgtpMAY7f1e31Ts-od-rgcLZ_a2_bhwcZO9cracy\"\n   \n   env EOSAUTHZ=$TOKEN eos whoami\n   Virtual Identity: uid=0 (99,3,0) gid=0 (99,4,0) [authz:unix] sudo* host=localhost domain=localdomain geo-location=ajp\n   {\n    \"token\": {\n     \"permission\": \"rx\",\n     \"expires\": \"1600000000\",\n     \"owner\": \"\",\n     \"group\": \"\",\n     \"generation\": \"1\",\n     \"path\": \"/eos/myfile\",\n     \"allowtree\": false,\n     \"origins\": []\n    },\n   }\n\nToken usage\n-----------\n\nA file token can be used in two ways:\n\n* as a filename\n* via CGI '?authz=$TOKEN'\n\n.. code-block:: bash\n\n   # as a filename\n   xrdcp root://myeos//zteos64:MDAwMDAwNzR4nONS4WIuKq8Q-Dlz-ltWI3H91Pxi_cSsAv2S_OzUPP2SeAgtpMAY7f1e31Ts-od-rgcLZ_a2_bhwcZO9cracy /tmp/\n\n   # via CGI\n   xrdcp \"root://myeos//eos/myfile?authz=zteos64:MDAwMDAwNzR4nONS4WIuKq8Q-Dlz-ltWI3H91Pxi_cSsAv2S_OzUPP2SeAgtpMAY7f1e31Ts-od+rgcLZ_a2_bhwcZO9cracy\" /tmp/\n\nIf a token contains a subtree permission, the only way to use it for a file access is to use the CGI form. The filename form is practical to hide the filename for up-/downloads.\n\nToken issuing permission\n------------------------\n\nThe ``root`` user can issue any token. Everybody else can only issue tokens for files in existing parent directories or directory trees, where the calling user is the current owner.\n\nToken lifetime \n---------------\n\nThe token lifetime is given as a unix timestamp during the token creation. \n\nToken Revocation\n----------------\n\nTokens are issued with a generation entry. The generation value is a globally configured 64-bit unsigned number. In case of emergency all tokens can be revoked by increasing the generation value. The generation value is configured via the key ``token.generation`` in the default space\n\n.. code-block:: bash\n\n   # change the generation value \n   eos config default space.token.generation=256\n\n   # show the generation value\n   eos space status default | grep token.generation\n   token.generation                 := 256\n\nToken Origin Restrictions\n-------------------------\n\nThe client location from where a token can be used can be restricted by using the ``origins`` entries.\n\n.. code-block:: bash\n\n   # all machines at CERN authenticating via kerberos as user nobody\t\t\n   eos token --path /eos/myfile --origin \\.*.cern.ch:nobody:krb5\"\n\n   # all machines at CERN authenticating via unix as user kubernetes from machine k8s.cern.ch\n   eos token --path /eos/myfile --origin \"k8s.cern.ch:kubernetes:unix\"\n\n   # general syntax is a regexp for origin like <regexp hostname>:<regexp username>:<regexp auth protocol>\n\nThe default origin regexp is ``.*:.*:.*`` accepting all origins. If the regex is invalid, the command will return with an error message.\n\n\nToken via GRPC\n--------------\n\nTokens can be requested and verified using GRPC TokenRequest as shown here with the GRPC CLI. To request a token at least ``path``, ``expires`` and ``permission`` should be defined.\n\n\n.. code-block:: bash\n\n   [root@ajp mgm]# eos-grpc-ns --acl rwx -p /eos/ajp/xrootd token\n   request: \n   {\n    \"authkey\": \"\",\n    \"token\": {\n     \"token\": {\n      \"token\": {\n       \"permission\": \"rwx\",\n       \"expires\": \"1571226882\",\n       \"owner\": \"\",\n       \"group\": \"\",\n       \"generation\": \"0\",\n       \"path\": \"/eos/ajp/xrootd\",\n       \"allowtree\": false,\n       \"vtoken\": \"\",\n       \"origins\": []\n      },\n      \"signature\": \"\",\n      \"serialized\": \"\",\n      \"seed\": 0\n     }\n    }\n   }\n   \n   reply: \n   {\n    \"error\": {\n     \"code\": \"0\",\n     \"msg\": \"zteos64:MDAwMDAwODR4nOPS4WIuKq8QaOqa85ZVii0vPyk_pVIJShvx66fmF-snZhXoVxTl55ekCCk8KMu4qK4Z7_jNTmF5u0_z5hP1J97v3K3G29cid0O4gv-5FEnmKUyavGstGwCiYjHe\"\n    }\n   }\n\n   request took 6226 micro seconds\n\n\nTo verify a token, the ``vtoken`` field should hold the token to decode.\n\n.. code-block:: bash\n\n   [root@ajp mgm]# eos-grpc-ns --ztoken zteos64:MDAwMDAwODR4nOPS4WIuKq8QaOqa85ZVii0vPyk_pVIJShvx66fmF-snZhXoVxTl55ekCCk8KMu4qK4Z7_jNTmF5u0_z5hP1J97v3K3G29cid0O4gv-5FEnmKUyavGstGwCiYjHe token\n   request: \n   {\n    \"authkey\": \"\",\n    \"token\": {\n     \"token\": {\n      \"token\": {\n      \"permission\": \"rx\",\n       \"expires\": \"1571226893\",\n       \"owner\": \"\",\n       \"group\": \"\",\n       \"generation\": \"0\",\n       \"path\": \"\",\n       \"allowtree\": false,\n       \"vtoken\": \"zteos64:MDAwMDAwODR4nOPS4WIuKq8QaOqa85ZVii0vPyk_pVIJShvx66fmF-snZhXoVxTl55ekCCk8KMu4qK4Z7_jNTmF5u0_z5hP1J97v3K3G29cid0O4gv-5FEnmKUyavGstGwCiYjHe\",\n       \"origins\": []\n      },\n      \"signature\": \"\",\n      \"serialized\": \"\",\n     \"seed\": 0\n     }\n    }\n   }\n\n   reply: \n   {\n    \"error\": {\n    \"code\": \"0\",\n    \"msg\": \"{\\n \\\"token\\\": {\\n  \\\"permission\\\": \\\"rwx\\\",\\n  \\\"expires\\\": \\\"1571321093\\\",\\n  \\\"owner\\\": \\\"nobody\\\",\\n  \\\"group\\\": \\\"nobody\\\",\\n  \\\"generation\\\": \\\"0\\\",\\n  \\\"path\\\": \\\"/eos/ajp/xrootd\\\",\\n  \\\"allowtree\\\": false,\\n  \\\"vtoken\\\": \\\"\\\",\\n  \\\"voucher\\\": \\\"6496c338-f0e6-11e9-b81d-fa163eb6b6cf\\\",\\n  \\\"requester\\\": \\\"[Thu Oct 17 15:59:53 2019] uid:99[nobody] gid:99[nobody] tident:.1:46602@[:1] name: dn: prot:grpc host:[:1] domain:localdomain geo:cern sudo:0\\\",\\n  \\\"origins\\\": []\\n },\\n \\\"signature\\\": \\\"2B8qIUfJ6rTusI2NFXKH70AoXZ55wKUUDijFCK3e2bY=\\\",\\n \\\"serialized\\\": \\\"CgNyd3gQheqh7QUaBm5vYm9keSIGbm9ib2R5Mg8vZW9zL2FqcC94cm9vdGRKJDY0OTZjMzM4LWYwZTYtMTFlOS1iODFkLWZhMTYzZWI2YjZjZlKNAVtUaHUgT2N0IDE3IDE1OjU5OjUzIDIwMTldIHVpZDo5OVtub2JvZHldIGdpZDo5OVtub2JvZHldIHRpZGVudDouMTo0NjYwMkBbOjFdIG5hbWU6IGRuOiBwcm90OmdycGMgaG9zdDpbOjFdIGRvbWFpbjpsb2NhbGRvbWFpbiBnZW86YWpwIHN1ZG86MA==\\\",\\n \\\"seed\\\": 844966647\\n}\\n\"\n    }\n   }\n\nThe possible return codes are:\n\n* -EINVAL      : the token cannot be decompressed\n* -EINVAL      : the token cannot be parsed\n* -EACCES      : the generation number inside the token is not valid anymore\n* -EKEYEXPIRED : the token validity has expired\n* -EPERM       : the token signature is not correct\n\nUsing tokens with SSS security\n------------------------------\n\nIt is very useful to issue scoped tokens to applications. To avoid the complication of appending tokens to each and every URL  one can use ``sss`` security to forward a generic token for each request via the ``endorsement`` environment variable.\n\nClient and server should share an sss key for a user, which is actually not authorized to use the instance e.g.\n\n.. code-block:: bash\n\n   ############################\n   # client\n   ############################\n   echo 0 u:nfsnobody g:nfsnobody n:eos-test N:5506672669367468033 c:1282122142 e:0 k:0123456789012345678901234567890123456789012345678901234567890123 > $HOME/.eos.keytab\n   # point to keytab file\n   export XrdSecSSSKT=$HOME/.eos.keytab\n   # enforce sss\n   export XrdSecPROTOCOL=sss\n\n   ############################\n   #server\n   ############################\n\n   # server shares the same keytab entry\n   echo 0 u:nfsnobody g:nfsnobody n:eos-test N:5506672669367468033 c:1282122142 e:0 k:0123456789012345678901234567890123456789\\012345678901234567890123 >> /etc/eos.keytab\n\n   # server bans user nfsnobody or maybe uses already user allow, which bans this user by default\n   eos access ban user nfsnobody\n  \n   # server issues a scoped token binding to a user/group\n   TOKEN=`eos token --path /eos/cms/www/ --permission rwx --expires 1600000000 --owner cmsprod --group zh`\n \n   ############################\n   # client\n   ############################\n   \n   # exports the token in the environment\n   export XrdSecsssENDORSEMENT=zteos64:....\n\n   # test the ID\n   eos whoami\n   Virtual Identity: uid=5410 (65534,99,5410) gid=1339 (65534,99,1338) [authz:sss] host=localhost domain=localdomain geo-location=ajp key=zteos64:....\n   {\n     \"token\": {\n     \"permission\": \"rwx\",\n     \"expires\": \"1000000000\",\n     \"owner\": \"cmsprod\",\n     \"group\": \"zh\",\n     \"generation\": \"0\",\n     \"path\": \"/eos/cms/www/\",\n     \"allowtree\": false,\n     \"vtoken\": \"\",\n     \"origins\": []\n    },\n   }\n\nUsing tokens for scoped eosxd access\n------------------------------------\n\nAs a user you can create a token e.g. for applications like CIs, webservices etc. if the EOS instances it configured to issue tokens.\n\nTo create a token as a user you do:\n\n.. code-block:: bash\n\n   eos token --path /eos/user/f/foo/ci/ --expires 1654328760 --perm rwx --tree\n\n\nIf you create a token as a user, the token puts the calling role as the identity into the token.\n\nYou can inspect your token to verify that it contains what you want using:\n\n.. code-block:: bash\n\n   eos token --token zteos64:...\n\nFinally to use the token on a mount client you define only the following variable:\n\n.. code-block:: bash\n\n   # put the token into your client environment\n   export XrdSecsssENDORSEMENT=zteos64:...\n\n   # you should now have rwx permission on this tree\n   ls /eos/user/f/foo/ci/\n\n\n\n\n\n"
  },
  {
    "path": "doc/citrine/using/versions.rst",
    "content": ".. highlight:: rst\n\n.. _versioning:\n\nFile Versioning\n===============\n\nFile versioning can be triggered as a per directory policy using the extended attribute ``sys.versioning=<n>`` or via the ``eos file version`` command.\n\nThe parameter ``<n>`` in the extended attribute describes the maximum number of versions which should be kept according to a FIFO policy.\n\nAdditionally to the simple FIFO policy where the oldest versions are deleted once ``<n>`` versions are reached there are 11 predefined timebins, for which additional versions exceeding the versioning parameter ``<n>`` are kept.\n\nVersions are kept in a hidden directory (visible with ``ls -la``) which is composed by ``.sys.v#.<basename>``\n\n.. code-block:: bash\n\n   eos ls -la\n   drwxrwxrwx   1 root     root                0 Aug 29 15:33 .sys.v#.myfile\n   -rw-r-----   1 root     root             1824 Aug 29 15:33 myfile\n\nThe 11 time bins are defined as follows:\n\n.. epigraph::\n\n   ============= ===================================================\n   bin           deletion policy\n   ============= ===================================================\n   age<1d        the first version entering this bin survives\n   1d<=age<2d    the first version entering this bin survives\n   2d<=age<3d    the first version entering this bin survives\n   3d<=age<4d    the first version entering this bin survives\n   4d<=age<5d    the first version entering this bin survives\n   5d<=age<6d    the first version entering this bin survives\n   6d<=age<1w    the first version entering this bin survives\n   1w<=age<2w    the first version entering this bin survives\n   2w<=age<3w    the first version entering this bin survives\n   3w<=age<1mo   the first version entering this bin survives\n   ============= ===================================================\n\n\n\nConfiguration of automatic versioning\n-------------------------------------\n\nConfigure each directory which should apply versioning using the extended attribute ``sys.versioning``:\n\n.. code-block:: bash\n\n   # force 10 versions (FIFO)\n   eos attr set sys.versioning=10 version-dir\n\n   # upload initial file\n   eos cp /tmp/file /eos/version-dir/file\n\n   # versions are created on the fly with each upload - now 1 version\n   eos cp /tmp/file /eos/version-dir/file\n\n   # versions are created on the fly with each upload - now 2 versions\n   eos cp /tmp/file /eos/version-dir/file\n\n   # aso ....\n\n\n\nCreating new versions\n---------------------\n\n.. code-block:: bash\n\n   # force a new version\n   eos file version myfile\n\n   # force a new version with max 5 versions\n   eos file version myfile 5\n\nList existing versions\n----------------------\n\n.. code-block:: bash\n\n   eos file versions myfile\n   -rw-r-----   1 root     root             1824 Aug 29 15:17 1567084675.0014ede6\n   -rw-r-----   1 root     root             1824 Aug 29 15:33 1567085591.0014ede7\n   -rw-r-----   1 root     root             1824 Aug 29 15:33 1567085591.0014ede8\n   -rw-r-----   1 root     root             1824 Aug 29 15:33 1567085592.0014ede9\n\n\n\nPurging existing versions\n-------------------------\n\n.. code-block:: bash\n\n   # remove all versions\n   eos file purge myfile 0\n\n   # keep 5 versions (FIFO)\n   eos file purge myfile 5\n"
  },
  {
    "path": "doc/citrine/using.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Using EOS\n\nUsing EOS\n=========\n\n.. toctree::\n   :maxdepth: 1\n\n   using/archive\n   using/eos_services\n   using/rain\n   using/reports\n   using/policies\n   using/priorities\n   using/systemd\n   using/fusex\n   using/versions\n   using/oauth2\n   using/tokens\n   using/squashfs\n   using/sharedfs\n   using/attributelocks\n"
  },
  {
    "path": "doc/diopside/architecture/index.rst",
    "content": ".. index::\n   single: Architecture\n\n\n=====================\nDesign & Architecture\n=====================\n\n**EOS** was designed as a file storage system with low-latency access for physics analysis.\n\nThe core file access protocol in EOS is the **XRootD** protocol: it provides additional features when compared to widespread protocols like FTP, NFS or HTTP(S) e.g\n\n* **strong authentication** mechanisms\n* a versatile **redirection protocol** used to separate metadata and data access, for storage federations and error recovery\n* efficient **remote file access** in LAN and WAN environments through latency compensation techniques using vector read and write requests\n* **third-party copy mechanism** **TPC** including a file integrity check based on file checksums on both ends\n* a wait instruction to let a client replay a request after a given time period and a waitresp instruction to let a client wait for an asynchronous callback response from the server\n\nThe current production version of EOS is named like a gemstone: Diopside.  The Version for the production \nrelease is 5.X (EOS5).\n\nSchema\n------\n\n.. image:: architecture.png\n   :align: center\n   :width: 600px\n\nService Components\n------------------\n\nEOS is composed of four core services \n\n* **MGM** - providing a hierarchical namespace and meta-ata access\n* **MQ** - providing asynchronous messaging between MGM and FST services\n* **FST** - providing file storage service\n* **QDB** - providing a high-available KV store to persiste meta-data of the MGM \n\nEOS provides additional native access protocols using a protocol plugin for **HTTP(S)** (XrdHttp) or via gateways for **S3** (MiniO), **CIFS** (Samba) and **SFTP** (sshfs) using a FUSE client implementation (eosxd). FUSE allows to mount EOS as a filesystem preserving the aforementioned feature set of **XRootD**. In particular, all strong authentication protocols and token authorization are supported also by the FUSE implementation. For user and administrator interaction EOS provides a command-line interface invoked using the EOS shell\n`eos`\nBesides the shell the following native XRootD clients are usable:\n\n* **xrdcp** - copy application\n* **xrd** - filesystem application (listing, deletion ...)\n* **XrdCl** - C++ client library\n\nNamespace\n---------\n\nThe namespace(meta-data service) of EOS5 runs on a single node and is not horizontal scalable. The architecture foresees to have standby nodes, which take over the MGM service if the active node becomes unavailable.\n\nThe namespace is implemented as an LRU driven in-memory cache with a write-back queue and QuarkDB as external KV store for persistency.  QuarkDB is a high-available transactional KV store using the RAFT consensus algorithm implementing a subset of the REDIS protocol. A default QuarkDB setup consists of three nodes, which elect a leader to serve/store data.\nKV data is stored in RocksDB databases on each node. \n\n.. image:: qdb.png\n   :align: center\n   :width: 400px\n\n\nReplication from leader to follower(s) is using RAFT journals. The supported data structures are **hashes, sets, strings, leases, multi** and **pubsub**. \n\nViews\n-----\n\nEOS provides four conceptual views on filesystems storing data:\n* space view\n* node ivew\n* group view\n* fs view\n\n.. image:: space.png\n   :align: center\n   :width: 800px\n\n\nThe *fs view* contains all the configured filesystems. The *node view* arranges filesystems grouped by their hosting FST node. The *group view* arranges filesystems grouped by their scheduling group. The *space view* lists filesystems by placement space. What is called a *space* in EOS is called a *pool* in other storage systems. What is called *scheduling group* in EOS is often called *placement group* in other implementations.\n\nFilesystems are identified by a storage path, the host where they are attached, an internal filesystem ID assigned by EOS (1..65534) and a randomly generated UUID, which serves as an identification label stored on each filesystem.\n\nMapping\n-------\n\nEOS has a very feature rich interface for authentication and user mapping: the virtual ID concept. Clients are mapped based on their chosen authentication method and rules to a virtual identity `vid` (uid/gid pair) inside EOS, which defines the ownership of files and directories. Besides dynamic mapping you can assign `roles` to each `vid`. This is useful if you want to allow people/services to act on behalf of everyone or a subset of identities.\n\n.. image:: mapping.png\n   :align: center\n   :width: 800px\n\n\n\nPolicies\n--------\n\nEOS allows to define storage policies e.g. how files are stored or which checksums are computed for files, when they have been uploaded. These policies are defined with the following scopes:\n* Space policies - they are valid for everything happening within a given space\n* Application/Group/User policies - they are valid for identified client applications/groups/users\n* Directory policies - they are valid for children inside a directory\n* URL policies - they are attached to a single operation as part of a URL\n\nA simple example of a directory policy to define to store files erasure coded on 12 HDDs is shown here:\n\n.. code-block:: bash\n    \n   eos attr ls /eos/attrdir\n   sys.forced.layout=\"raid6\" \n   sys.forced.nstripes=\"12\" \n   sys.forced.space=\"erasure\"\n\n\nGEO Tagging and Scheduling\n--------------------------\n\nEOS allows to assign GEO tags to clients based on IPs and to each node hosting filesystems. The client GEO tags are used to match against filesystem GEO tags to apply various placement policies e.g. a possible placement policy could be to store files as close as possible to a client.\n\nMicroservices\n--------------\n\nEach EOS system contains a set of configurable microservices. \n\n* **Balancers** - three services to balance the volume usage on filesystems under various aspects\n   * Balancer       - Balancing usage between filesystems inside each group\n   * Group Balancer - Balancing usage between groups within a space\n   * Geo Balancer   - Balancing usage between geographic locations\n* **Converter Engine** - a queue system to execute jobs to change how a file is stored e.g. change from single to two-fold replication\n* **Lifecycle Management** - automation for disk/node replacement e.g. empty a disk which should go out of production\n* **LRU Engine** - a service to trigger clean-up or conversion policies by scanning the EOS namespace e.g. create a temporary space where files get automatically cleaned up after 30 days\n* **Inspector** - an accounting service reporting statistics how files are stored in an EOS instance e.g. to see how much data in an instance is stored with replication or erasure coding\n* **Storage Consistency Check and Repair** - a distributed service to identify inconsistencies in data and meta-data and to run automatic repair actions to achieve consistency\n* **Workflow Engine** - a queue system processing storage events e.g. to inform an external service like the CERN Tape Archive service that a new file has been uploaded and should be migrated to tape storage\n\n"
  },
  {
    "path": "doc/diopside/blog/features.rst",
    "content": ".. index::\n   pair: Blog; New Features\n\n.. highlight:: rst\n\n.. _features:\n\n\n=====\nBLOG\n=====\n\nNew Features\n-------------\n\nThis blog is used to track new features added to EOS.\n\nOctober 2024\n\n* The eos CLI provides now a similar command to the du shell command:\n  `eos du -s /eos/mydir` \n  `eos du -s -h /eos/mydir`\n  `eos du -s -h --si /eos/mydir`\n  `eos du -a /eos/mydir`\n  \nSeptember 2023\n^^^^^^^^^^^^^^\n\n* FSTs now publish regulary S.M.A.R.T information and the MGM can provide statistics by storage device or model categories including age, capacity etc.\n  The information is available using the new devices interface:\n\n  `eos devices ls [-l]`\n\n  All the information is also available as JSON output using `eos --json devices ls`! The feature will be available with EOS 5.2!\n\n* `eos df` supports now JSON output e.g. `eos --json df` and prints the performance capacity ratio in GB/s per TB\n  \nAugust 2023\n^^^^^^^^^^\n\n* The EOS group balancer had been rewritten during 2023 and now supports a 'free space engine'\n  Instead of balancing groups to the same usage level (percentage),\n  groups are balanced to have the same amount of free space. This allows\n  to avoid significant performance degradation when an EOS instance is\n  close to be full. \n\nJune 2023\n^^^^^^^^^\n\n* The EOS MGM provides now a meta-data benchmark command to measure MGM performance\n   Example:\n\n   `eos ns benchmark 100 10 10 # see the help in the CLI`\n\n   The tool got added when validating the shared mutex implementation of the Abseil library\n   as a new namespace mutex implementation, which performs much better than the\n   standard C++ implementation. The new mutex will be available with EOS 5.2!\n   \nMay 2023\n^^^^^^^^\n\n* Support external FST hostname (port) aliases by defining\n   * sysconfig: EOS_FST_ALIAS=externalhostname.domain\n   * sysconfig: EOS_FST_PORT_ALIAS=externalport (normally the same)\n\n  The aliases are shown when using\n   * `eos fs ls -l`\n   * `eos fs ls -m`\n\n.. note:: When an alias is removed on an FST, the MGM and FST have to be shutdown and restarted to get rid of the aliases\n\n\nMarch 2023\n^^^^^^^^^^^^^\n\n* HTTPS gateways can use now a shared key to act as gateway for an EOS instance. This simplifies deployment and removes the need\n  to specify the IPV6 addresses of each gateway node using the vid interface.\n\n  Example:\n  * `vid set map -https key:e9d2011d-942a-4ec1-9f78-3643316ed336 vuid:100`\n  * `vid set map -https key:e9d2011d-942a-4ec1-9f78-3643316ed336 vgid:100`\n  * If a client sends the header \"x-gateway-authorization:  e9d2011d-942a-4ec1-9f78-3643316ed336\" he will be authenticated as user 100 in this example.\n  * If user 100 is a sudoer, the client can select the effective user ID specifying a \"remote-user: ...\" header.\n\n* `newfind` has now improved performance and new usability features.\n\n  Examples:\n  * `eos newfind --format uid,gid,checksum,fxid,atime,btime /eos/ #select output format`\n  * `eos newfind --count -d /eos/dev/ #better performance`\n  * `eos newfind --treecount /eos/dev/ #aggregate number of dirs and files`\n  * `eos newfind --cache ... #store items in in-memory MD cache, by default we don't cache meta-data to avoid trashing`\n\n\nJanuary 2023\n^^^^^^^^^^^^^\n\n\n* Token identity mapping can now be controlled using `eos vid tokensudo always|strong|encrypted|never`\n   * `always` - identity in the token is always taken into account\n   * `strong` - identity in the token is not taken into account for unix authenticated clients\n   * `encrypted` - identity in the token is only taken into account for encrypted connections\n   * `never` - identity in the token is never taken into account\n\n* It is now possible to black-/whitelist EOS tokens using the _access__ interface. This allows e.g. to prevent arbitrary token generation by users and implement an approval process. When using the white list mode, all user created tokens appear in the logfile `/var/log/eos/mgm/TokenCmd.log` e.g.\n\n.. code-block:: bash \n\n   230113 11:17:19 WARN  TokenCmd:218                   creating voucher=7630eb7e-932b-11ed-8d40-0071c2181e97 path=/eos/foo/ owner=123 group=123 perm=rx expires=1673605339 token:'{ \"token\": {  \"permission\": \"rx\",  \"expires\": \"1673605339\",  \"owner\": \"bar\",  \"group\": \"bar\",  \"generation\": \"1\",  \"path\": \"/eos/foo/\",  \"allowtree\": true,  \"vtoken\": \"\",  \"origins\": [] },}'\n\nAn admin can now whitelist this token by issuing:\n.. code-block:: bash \n\n   eos access allow token 7630eb7e-932b-11ed-8d40-0071c2181e97\n\nIn blacklist mode it is possible to disable token usage if required using:\n\n.. code-block:: bash \n\n   eos access ban 7630eb7e-932b-11ed-8d40-0071c2181e97\n\n\nDecember 2022\n^^^^^^^^^^^^^\n\n* The file inspector daemon now reports access time and birth time distributions:\n\n.. code-block:: bash \n\n    inspector -l\n    ...\n    ======================================================================================\n     Access time distribution of files\n     0s                               : 1613 (1.59%)\n     24h                              : 6 (0.01%)\n     7d                               : 1 (0.00%)\n     30d                              : 1 (0.00%)\n     2y                               : 5 (0.00%)\n     5y                               : 100.02 k (98.40%)\n    ======================================================================================\n     Access time volume distribution of files\n     0s                               : 81.31 MB (98.73%)\n     24h                              : 15.09 kB (0.02%)\n     7d                               : 0 B (0.00%)\n     30d                              : 1.00 MB (1.21%)\n     2y                               : 10.49 kB (0.01%)\n     5y                               : 24.27 kB (0.03%)\n    ======================================================================================\n     Birth time distribution of files\n     0s                               : 1619 (1.59%)\n     24h                              : 6 (0.01%)\n     7d                               : 100.00 k (98.39%)\n     90d                              : 1 (0.00%)\n     5y                               : 13 (0.01%)\n    ======================================================================================\n     Birth time volume distribution of files\n     0s                               : 81.32 MB (98.74%)\n     24h                              : 1.01 MB (1.23%)\n     7d                               : 25 B (0.00%)\n     90d                              : 2769 B (0.00%)\n     5y                               : 21.48 kB (0.03%)\n    --------------------------------------------------------------------------------------\n    \n    inspector -m\n    key=last layout=00000000 type=plain nominal_stripes=1 checksum=none blockchecksum=none blocksize=4k locations=0 nolocation=12 repdelta:-1=12 unlinkedlocations=0 volume=20480 zerosize=7\n    key=last layout=00100002 type=plain nominal_stripes=1 checksum=adler32 blockchecksum=none blocksize=4k locations=101628 nolocation=1 repdelta:-1=1 repdelta:0=101628 unlinkedlocations=0 volume=82338570 zerosize=100003\n    kay=last tag=accesstime::files 0=1613 86400=6 604800=1 2592000=1 63072000=5 157680000=100015\n    key=last tag=accesstime::volume 0=81309191 86400=15090 604800=0 2592000=1000000 63072000=10495 157680000=24274\n    kay=last tag=birthtime::files 0=1619 86400=6 604800=100002 7776000=1 157680000=13\n\n\n------------\n\n* It is now possible to enable access time tracking e.g. with 1h precision:\n\n.. code-block:: bash \n\n   eos space config default atime=3600\n\n------------\n\n* Supporting now secondary group permission evaluation with sysconfig setting `EOS_SECONDARY_GROUPS=1`\n\n------------\n\n* `eos register` is a new command which can be used to _inject_ meta-data into EOS\n\n.. code-block:: bash \n\n   Usage: register [-u] <path> {tag1,tag2,tag3...}\n              :  when called without the -u flag the parent has to exist while the basename should not exist\n           -u :  if the file exists this will update all the provided meta-data of a file\n    \n           tagN is optional, but can be one or many of:\n                 size=100\n                 uid=101 | username=foo\n                 gid=102 | username=bar\n                 checksum=abcdabcd\n                 layoutid=00100112\n                 location=1 location=2 ...\n                 mode=777\n                 btime=1670334863.101232\n                 atime=1670334863.101232 \n                 ctime=1670334863.110123\n                 mtime=1670334863.11234d\n                 attr=\"sys.acl=u:100:rwx\"\n                 attr=\"user.md=private\"\n                 path=\"/eos/newfile\"   # can be used instead of the regular path argument of the path\n\n* `eos ns` reports now a read and write contention value \n\n.. code-block:: bash \n\n    eos ns stat:\n    ALL      Contention                  :     write:42.11% read:0.00%\n    \n    eos ns stat -m | grep contention\n    uid=all gid=all ns.contention.read=42.11\n    uid=all gid=all ns.contention.write=0\n\n\n------------\n\n\nNovember 2022\n^^^^^^^^^^^^^\n\n* Added a sharded cache for the ID mapping interface to get better parallelism\n\n------------\n\n* Shipping now *eosxd* based on libfuse2 and *eosxd3* on libfuse3\n   * eosxd3 can be started using `-o clone_fd` to have one FUSE connection per thread\n\n------------\n\n* Support for the POSIX VTX bit has been added (e.g. as it is used in /tmp/)\n"
  },
  {
    "path": "doc/diopside/conf.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# complexity documentation build configuration file, created by\n# sphinx-quickstart on Tue Jul  9 22:26:36 2013.\n#\n# This file is execfile()d with the current directory set to its containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport sys, os\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#sys.path.insert(0, os.path.abspath('.'))\n\n# Get the project root dir, which is the parent dir of this\ncwd = os.getcwd()\nproject_root = os.path.dirname(cwd)\n\n# Insert the project root dir as the first element in the PYTHONPATH.\n# This lets us ensure that the source package is imported, and that its\n# version is used.\nsys.path.insert(0, project_root)\n\nimport solar_theme\n\n# -- General configuration -----------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be extensions\n# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_themes']\n\n# The suffix of source filenames.\nsource_suffix = '.rst'\n\n# The encoding of source files.\n#source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = u'EOS'\ncopyright = u'2020, CERN'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = \"5.1.15\"\nrelease = \"DIOPSIDE\"\n\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\n#today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = ['_build']\n\n# The reST default role (used for this markup: `text`) to use for all documents.\n#default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n#modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built documents.\n#keep_warnings = False\n\n\n# -- Options for HTML output ---------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = 'solar_theme'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#html_theme_options = {}\n\n# Add any paths that contain custom themes here, relative to this directory.\nhtml_theme_path = [solar_theme.theme_path]\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n#html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\nhtml_logo = \"images/EOS_logo.jpg\"\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\nhtml_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n#html_domain_indices = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n#html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\nhtml_show_sphinx = False\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\nhtml_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'solardoc'\n\n\n# -- Options for LaTeX output --------------------------------------------------\n\nlatex_elements = {\n# The paper size ('letterpaper' or 'a4paper').\n#'papersize': 'letterpaper',\n\n# The font size ('10pt', '11pt' or '12pt').\n#'pointsize': '10pt',\n\n# Additional stuff for the LaTeX preamble.\n#'preamble': '',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title, author, documentclass [howto/manual]).\nlatex_documents = [\n  ('index', 'eos.tex', u'EOS Documentation',\n   u'CERN', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#latex_use_parts = False\n\n# If true, show page references after internal links.\n#latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_domain_indices = True\n\n\n# -- Options for manual page output --------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    ('index', 'eos', u'EOS Documentation',\n     [u'CERN'], 1)\n]\n\n# If true, show URL addresses after external links.\n#man_show_urls = False\n\n\n# -- Options for Texinfo output ------------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n  ('index', 'eos', u'EOS Documentation',\n   u'CERN', 'eos', 'One line description of project.',\n   'Miscellaneous'),\n]\n\n# Documents to append as an appendix to all manuals.\n#texinfo_appendices = []\n\n# If false, no module index is generated.\n#texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n#texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n#texinfo_no_detailmenu = False\n\n\n"
  },
  {
    "path": "doc/diopside/configuration.rst",
    "content": "\nZSTD-compressed rotating logging\n--------------------------------\n\nEOS supports an optional ZSTD-compressed, time-rotated logging mode that replaces the default stderr/fan-out file writes with compact `.zst` streams.\n\n- Enable via environment:\n\n  - ``EOS_ZSTD_LOGGING=1``: enable compressed logging\n  - ``EOS_ZSTD_ROTATION=<seconds>``: rotation interval (default ``3600``)\n  - ``EOS_ZSTD_LEVEL=<1..19>``: optional compression level (default ``1``)\n\n- Layout:\n\n  - Base directory is taken from ``$XRDLOGDIR`` if set, otherwise ``/var/log/eos/<service>``.\n  - Real compressed segments live under ``<base>/logs/``.\n  - Top-level symlinks remain in ``<base>/`` and are relative to ``logs/``:\n\n    - Main: ``xrdlog.<service>.zstd -> logs/xrdlog.<service>-YYYYmmdd-HHMMSS.zst``\n    - Per-tag: ``<tag>.zstd -> logs/<tag>-YYYYmmdd-HHMMSS.zst``\n\n- Behavior:\n\n  - The logging system flushes a ZSTD frame header on segment open to keep new segments readable.\n  - Each record is flushed to allow ``zstdcat``/``zstdtail``-style tools to follow files in near real time.\n  - In ZSTD mode, legacy fan-out FILE* writes are suppressed and stderr is redirected so that its lines are appended to the main compressed stream.\n  - On startup in ZSTD mode, if a plain ``xrdlog.<service>`` exists with content (e.g. created by XRootD before EOS logging starts), its contents are migrated into the compressed main stream and the plain file is unlinked.\n\n- Per-tag naming:\n\n  - Per-tag compressed streams are only created for the canonical fan-out tag set used by MGM (e.g. ``Grpc``, ``Http``, ``DrainJob``, ``ZMQ`` …). Source-module names are mapped into this set using the same alias rules as the fan-out configuration (e.g. ``HttpHandler`` → ``Http``, ``Drainer`` → ``DrainJob``). Modules without a canonical tag do not get their own `.zst` stream.\n\n"
  },
  {
    "path": "doc/diopside/faq/exotic.rst",
    "content": ".. index::\n   pair: FAQ; Exotic Cases\n\n.. highlight:: rst\n\n.. _exotic:\n\nExotic Cases\n============\n\nI need to change the hostname or the port of an FST\n---------------------------------------------------\n\nIf you need to rename an FST host or change the port number, you can use the `eos-config-inspect` tool.\n\n1. *Shutdown* your MGM\n\n2. *Modify* the configuration of each concerned filesystem by filesystem id\n\nTo relocate filesystem 1 to foo.bar:1095 having QDB on `localhost:7777` you do:\n\n\n.. code-block:: bash \n\n   eos-config-inspect relocate-filesystem --fsid 1 --new-fst-host foo.bar --new-fst-port 1095 --members localhost:7777\n\n3. *Verify/Rewrite* your *vid* rules, in case the hostname was used there \n3. *Restart* your MGM\n4. **Ready**\n"
  },
  {
    "path": "doc/diopside/faq/index.rst",
    "content": ".. index::\n   single: FAQ\n\n.. highlight:: rst\n\n.. _faq:\n\n===\nFAQ\n===\n\n.. toctree::\n   :maxdepth: 2\n   :caption: Contents:\n\n   exotic\n"
  },
  {
    "path": "doc/diopside/index.rst",
    "content": ":orphan:\n\n.. _index:\n\n\nEOS - Open Storage Documentation\n================================\n\n**EOS** is an open-source storage software solution to manage Exabyte storage for the CERN Large Hadron Collidor `LHC <https://home.cern/topics/large-hadron-collider>`_. Core of the implementation is the `XRootD <http://xrootd.org>`_ framework providing a feature-rich remote access protocol. \n\n.. image:: images/community-book.png\n   :align: center\n   :width: 480px\n\n\n\n.. toctree::\n   :numbered: 2\n   :maxdepth: 1\n\n   introduction/index.rst\n   architecture/index.rst\n   releases/index.rst\n   manual/index.rst\n   faq/index.rst\n   blog/features.rst\n\n   A. QuarkDB Documentation <https://quarkdb.web.cern.ch>\n   B. Citrine Documentation <https://eos-docs.web.cern.ch/index-citrine.html>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "doc/diopside/introduction/index.rst",
    "content": ".. _introduction:\n\n.. index::\n   single: Introduction\n\n============\nIntroduction\n============\n\n\n*EOS Open Storage combines and re-enacts concepts of filesystem, archive and cloud storage.*\n\n.. image:: cern.png\n   :width: 800px\n   :align: center\n\nThe `EOS <https://eos.web.cern.ch>`_ storage system was created for the extreme computing requirements at the Large Hadron Collider (`LHC <https://lhc.web.cern.ch>`_) at CERN. EOS stands for EOS Open Storage and started as a disk-only storage system developed since 2010 at CERN. EOS is deployed as storage service at CERN and in dozens of other installations in the Worldwide LHC Computing GRID community (WLCG), at the Joint Research Centre of the European Commission and the Australian Academic and Research Network.\n\n\n.. image:: intro-page.png\n   :align: center\n   :width: 800px\n\nEOS Open Storage at CERN\n------------------------\n\nEOS instances at CERN store more than seven billion files and provide 780 petabytes of disk storage capacity using over 60k hard drives (as of June 2022), matching the exceptional performance of the LHC machine and experiments. \n\n.. image:: intro-usage.png\n   :align: center\n   :width: 800px\n\n\n\nOver 12k scientists of 110 nationalities, from institutes in more than 70 countries have access to data stored in EOS using remote access protocols and a mounted filesystem interface. EOS represents the foundation of many services at CERN. \n\nCTA - Tape Storage\n------------------------\n\nThe CERN Tape Archive `CTA <https://cta.web.cern.ch>`_ uses EOS as the user facing disk storage system which connects to the tape infrastructure and additional tape services. The integrated data stored on tape will exceed 1 exabyte during 2023. \n\nCERNBox - Sync & Share\n------------------------\n\nA second CERN service leveraging EOS functionality is `CERNBox <cernbox.web.cern.ch>`_. CERNBox provides Sync&Share functionality offering at least 1 terabyte of personal space to users or projects. Other features include a web portal, sync clients for most common platforms and reliance on the open-source ownCloud file hosting suite.\n\nHistory\n-------\nThe EOS project was started in April 2010 in the CERN IT data storage group. \n\nDriving Development\n------------------------\nEOS development has been driven over the last decade by requirements coming from the above use cases and a very diverse user community. Several features and functionalities of EOS are provided in neither conventional POSIX oriented nor cloud storage systems. \n\nGoal\n----\n\nThe main goal of the project is to provide fast and reliable disk only storage technology for CERN LHC use cases. The following picture demonstrates the main use case at CERN:\n\n.. image:: dataflow.png\n   :width: 800px\n   :align: center\n\n\nSoftware Versions\n-----------------\n\nThe stable production version called **Diopside** is currently EOS V5.\n\nLicense\n-------\nEOS is available under GPL v3 `license <https://raw.githubusercontent.com/cern-eos/eos/master/License>`_. \n\n\n\n\n\n"
  },
  {
    "path": "doc/diopside/manual/configuration.rst",
    "content": ".. index::\n   single: Configuration\n\n.. highlight:: rst\n\n.. _configuration:\n\nConfiguration\n=============\n\n\nEOS has two parts of configuration:\n\n.. epigraph::\n\n   ======================== ==================================================================================================\n   Configuration File Types Description\n   ======================== ==================================================================================================\n   **External** config file shared library location implementing a component and **static** configuration\n                              - these files are typically once edited and during the lifetime of an instance very infrequently changed - a change of these files **always requires a service restart**!\n\n   **Internal**             configuration of filesystems, policies, mappings aso\n                              - these are stored using the EOS configuration engine inside EOS itself - any change is immediately active and **never requires a server restart**!\n   ======================== ==================================================================================================\n\nThere are two methods to store external configuration: the classical EOS4 and the newer EOS5 approach. The EOS5 configuration aims to make the configuration more transparent and simpler using variable substitution for hostnames aso.\n\n* EOS4/Classical configuration\n* EOS5/New configuration\n\n**NOTE**\nEOS5 configuration is not (yet) used in production instances at CERN.\n\n.. index::\n   pair: Configuration; Classical\n\nClassical Configuration\n-----------------------\n\nThe classical configuration includes the following configuration files:\n\n.. epigraph::\n\n   ============== ============================ ==========================\n   Service        Configuration File           Type\n   ============== ============================ ==========================\n   MGM            /etc/xrd.cf.mgm              XRootD\n   FST            /etc/xrd.cf.fst              XRootD\n   MQ             /etc/xrd.cf.mq               XRootD\n   QDB            /etc/xrootd/xrootd-quarkdb.cfg XRootD\n   ALL            /etc/sysconfig/eos_env       Sysconfig\n   ============== ============================ ==========================\n\n\nYou can find an example `sysconfig` file with explanation of configuration variables under `/etc/sysconfig/eos_env.example`\nThe configuration files coming from an RPM installation have useful default settings. The main changes required in the static xrootd configuration files are concerning plug-ins (e.g. add HTTP(s) plug-ins) or authentication mechanisms and their logical ordering. Individual configuration settings are picked up in topical chapters.\n\n.. index::\n   pair: Configuration; Systems - Classical\n\nClassical `Systemd` Services\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe systemctl services for the four types of daemon are:\n\n.. code-block:: bash\n\n  systemctl start|stop|status xrootd@quarkdb\n  systemctl start|stop|status eos@mq\n  systemctl start|stop|status eos@mgm\n  systemctl start|stop|status eos@fst\n\nThe logical startup order justified by their dependencies should be:\n\n1. QDB\n2. MQ\n3. MGM\n4. FSTs\n\nThe MGM service requires QDB and MQ to be up.\nThe FST Service requires MGM, QDB and MQ to be up.\nQDB and MQ service have no dependencies.\n\nStart `systemd` Services at Boot\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\nTo start EOS services when a machine boots one needs only to enable the corresponding `systemd` service:\n\n.. code-block:: bash\n\n  systemctl enable xrootd@quarkdb\n  systemctl enable eos@mq\n  systemctl enable eos@mgm\n  systemctl enable eos@fst\n\n.. index::\n   pair: Configuration; Log Files - Classical\n\nClassical Service Logfiles\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. epigraph::\n\n   ========= ================================= ======\n   Service   Logfile Location                  Type\n   ========= ================================= ======\n   MGM       /var/log/eos/mgm                  EOS\n   FST       /var/log/eos/fst                  EOS\n   MQ        /var/log/eos/mq                   EOS\n   QDB       /var/log/xrootd/quarkdb           QDB\n   ========= ================================= ======\n\nThe EOS logfiles are usually called xrdlog.(service). In the MGM directory there are sublogfiles, which filter out log lines from the main logfile:\n\n .. epigraph::\n\n    ============================= ============================================================================\n    Logfile                       Contents\n    ============================= ============================================================================\n    Balancer.log                  : log information for the balancer service\n    Clients.log                   : log information for client calls to the MGM\n    Converter.log                 : log information of the converter service\n    DrainJob.log                  : log information for draining\n    eosxd-logtraces.log           : traces requested using 'eos fusex evict ... sendlog' from clients\n    eosxd-stacktraces.log         : traces requested using 'eos fusex evict ... stracktrace' from clients\n    error.log                     : all message with ERROR level from FSTs\n    FileInspector.log             : log information for the file inspector service\n    GeoBalancer.log               : log information for the GEO balancer service\n    GeoTreeEngine.log             : log information for the GEO tree engine\n    GroupBalancer.log             : log information for the group balancer\n    GroupDrainer.log              : log information for the group drainer\n    Grpc.log                      : log information for the GRPC server\n    Http.log                      : log information for the HTTP(S) server\n    logbook.log                   : annotated commands stored in the logbook on user request\n    LRU.log                       : log information for the LRU service\n    Master.log                    : log information for HA master transitions\n    MetadataFlusher.log           : log information for the metadata flusher\n    Mounts.log                    : log information for FUSE mount/umount\n    OAuth.log                     : log information for OAUTH authentication\n    Recycle.log                   : log information for the Recycle (purging) service\n    ReplicationTracker.log        : log information for the Replication tracker service\n    WFE::Job.log                  : log information for Workflow Engine jobs\n    WFE.log                       : log information for the Workflow Engine\n    xrdlog.mgm                    : main log file with all log messages\n    ZMQ.log                       : log information for the ZMQ server\n    ============================= ============================================================================\n\n\n.. index::\n   pair: Configuration; eos5\n\n\nEOS5 Configuration\n-------------------\n\n.. index::\n   pair: Configuration; Configuration Files - eos5\n\nConfiguration Files\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nThe configuration files for the EOS5 service management are located under `/etc/eos/`\n\n.. code-block::\n\n    [root@mgm root]# find /etc/eos/config/\n    /etc/eos/config/\n    /etc/eos/config/mgm\n    /etc/eos/config/mgm/mgm\n    /etc/eos/config/mq\n    /etc/eos/config/mq/mq\n    /etc/eos/config/fst\n    /etc/eos/config/fst/fst\n    /etc/eos/config/qdb\n    /etc/eos/config/qdb/qdb\n    /etc/eos/config/generic\n    /etc/eos/config/generic/all\n\n\n\nConfiguration Sections\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nThey are internally structured into generic sections `init` `sysonfig`:\n\n.. code-block::\n\n    # ------------------------------------------------------------ #\n    [init]\n    # ------------------------------------------------------------ #\n\n\n.. code-block::\n\n    # ------------------------------------------------------------ #\n    [sysconfig]\n    # ------------------------------------------------------------ #\n\n\n.. code-block::\n\n    # ------------------------------------------------------------ #\n    [unshare]\n    # ------------------------------------------------------------ #\n\n\nand daemon specific sections:\n\n.. code-block::\n\n    # ------------------------------------------------------------ #\n    [mgm:xrootd:mgm]\n    # ------------------------------------------------------------ #\n\n\n.. code-block::\n\n    # ------------------------------------------------------------ #\n    [fst:xrootd:fst]\n    # ------------------------------------------------------------ #\n\n\n.. code-block::\n\n    # ------------------------------------------------------------ #\n    [mq:xrootd:mq]\n    # ------------------------------------------------------------ #\n\n.. code-block::\n\n    # ------------------------------------------------------------ #\n    [qdb:xrootd:qdb]\n    # ------------------------------------------------------------ #\n\nThe first tag inside `[daemon:xrootd:name]` `qdb` `mq` `fst` `mgm` references the daemon where this applies. The second tag `xrootd` reflects that this is actually part of the XRootD configuration file generated for the respective daemon type. The last tag is the `name` of the daemon instance. It is possible to run one or several of each daemon type per machine. The default `name` is just the daemon type itself e.g. `qdb` daemon default name is `qdb`. On cane have several instances of one type of daemon e.g. `mgm1` `mgm2` `mgm3` `fst1` `fst2` `fst3` aso.\n\n### Daemon Startup\nIf you want to see the config for a specific daemon you can type:\n\n.. code-block::\n\n    [root@mgm root] eos daemon config fst fst  # show the configuration for the fst daemon and the fst instance name fst\n\nThis spits out the three active sections for init, sysconfig and xrootd configuration:\n\n.. code-block::\n\n    [root@mgm root] eos daemon config mq mq\n    # ---------------------------------------\n    # ------------- i n i t -----------------\n    # ---------------------------------------\n    mkdir -p /var/run/eos/\n    chown daemon:root /var/run/eos/\n    if [ -e /etc/eos.keytab ]; then chown daemon /etc/eos.keytab ; chmod 400 /etc/eos.keytab ; fi\n    mkdir -p /var/eos/md /var/eos/report\n    chmod 755 /var/eos /var/eos/report\n    mkdir -p /var/spool/eos/core/mgm /var/spool/eos/core/mq /var/spool/eos/core/fst /var/spool/eos/core/qdb /var/spool/eos/admin\n    mkdir -p /var/log/eos\n    chown -R daemon /var/spool/eos\n    find /var/log/eos -maxdepth 1 -type d -exec chown daemon {} \\;\n    find /var/eos/ -maxdepth 1 -mindepth 1 -not -path \"/var/eos/fs\" -not -path \"/var/eos/fusex\" -type d -exec chown -R daemon {} \\;\n    chmod -R 775 /var/spool/eos\n    mkdir -p /var/eos/auth /var/eos/stage\n    chown daemon /var/eos/auth /var/eos/stage\n    setfacl -m default:u:daemon:r /var/eos/auth/\n\n    # ---------------------------------------\n    # ------------- s y s c o n f i g -------\n    # ---------------------------------------\n    SERVER_HOST=...\n    INSTANCE_NAME=eosdev\n    GEO_TAG=local\n    EOS_XROOTD=/opt/eos/xrootd/\n    LD_LIBRARY_PATH=/opt/eos/xrootd//lib64\n    LD_PRELOAD=/usr/lib64/libjemalloc.so\n\n    # ---------------------------------------\n    # ------------- x r o o t d  ------------\n    # ---------------------------------------\n    # running config file: /var/run/eos/xrd.cf.mq\n    xrootd.fslib libXrdMqOfs.so\n    all.export /eos/ nolock\n    all.role server\n    xrootd.async off nosf\n    xrootd.seclib libXrdSec.so\n    sec.protocol sss -c /etc/eos.keytab -s /etc/eos.keytab\n    sec.protbind * only sss\n    xrd.sched mint 16 maxt 1024 idle 128\n    xrd.port 1097\n    xrd.network keepalive\n    xrd.timeout idle 120\n    mq.maxmessagebacklog 100000\n    mq.maxqueuebacklog 50000\n    mq.rejectqueuebacklog 100000\n    mq.trace low\n    mq.queue /eos/\n    #########################################\n\n\nInit Sections\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\nThe `init` section are shell commands which are executed on startup. The default `init` sections create some of the required directories and change ownership accordingly. The `init` section of QDB also initializes a new QDB database automatically.\nCommands which should be executed for all daemons you put into `/etc/eos/config/generic/all`. Commands to be executed for a specific daemon you put into the daemon config file e.g. `/etc/eos/config/qdb/qdb`.\n\nSysconfig Sections\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\nThe `sysconfig` section contains variable definitions e.g. `/etc/eos/config/generic/all` contains:\n\n.. code-block:: bash\n\n    # ------------------------------------------------------------ #\n    [sysconfig]\n    # ------------------------------------------------------------ #\n\n    # EOSHOST is replaced by the eos CLI with the current hostname\n    SERVER_HOST=${EOSHOST}\n    INSTANCE_NAME=eosdev\n    GEO_TAG=local\n\n\nThe configuration file syntax allows, that they can work on several hosts without changing host names etc. In this example you see that when you want to reference the machine where you run this command, you just use the variable `${EOSHOST}`, so that you don't have to write myhost1.foo myhost2.foo depending on the machine name. This is also the place where you define the name of your instance.\n\nUnshare Section\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\nThe `unshare` section can be used to create a private mount namespace *inside* the environment of any XRootD process. This is useful if you want to mount a remote filesystem for FSTs, which are only visible to the FST process mount namespace, but to nobody else on the machine itself. A `df` as root will not show this external mount. You just write the needed `mount` command into the `init` section and it will be executed on daemon startup. It is possible also to encrypt commands in the `init` section, in case you have to use a mount key. To get an encrypted command for init sections you use:\n\n.. code-block:: bash\n\n    eos daemon seal \"mount -t nfs ... /nfs/\"\n    enc:fmAWznYjTWqRGfeiDSpfQy3MzQpJOhVI\n\nand you would place `enc:fmAWznYjTWqRGfeiDSpfQy3MzQpJOhVI` into your `init` section.\n\nService Management\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe `systemd` command set to start each single daemon manually on a node is:\n.. code-block::\n\n    systemctl start eos5-qdb@qdb\n    systemctl start eos5-mq@mq\n    systemctl start eos5-mgm@mgm\n    systemctl start eos5-fst@fst\n\nThe syntax is `eos5-daemon@name` e.g. start fst daemon with name fst1: `systemctl start eos5-fst@fst1`\n\nTo enable all daemon on startup, you do:\n\n.. code-block:: bash\n\n    systemctl enable eos5-qdb@qdb\n    systemctl enable eos5-mq@mq\n    systemctl enable eos5-mgm@mgm\n    systemctl enable eos5-fst@fst\n\n.. index::\n   pair: Configuration; Log Files - eos5\n\nEOS5 Service Logfiles\n^^^^^^^^^^^^^^^^^^^^^^\n\n.. epigraph::\n\n   ========= ================================= ======\n   Service   Logfile Location                  Type\n   ========= ================================= ======\n   MGM       /var/log/eos/mgm                  EOS\n   FST       /var/log/eos/fst                  EOS\n   MQ        /var/log/eos/mq                   EOS\n   QDB       /var/log/eos/qdb                  QDB\n   ========= ================================= ======\n\n\nThe EOS logfiles are usually called xrdlog.(service). In the MGM directory there are sublogfiles, which filter out log lines from the main logfile:\n\n .. epigraph::\n\n    ============================= ============================================================================\n    Logfile                       Contents\n    ============================= ============================================================================\n    Balancer.log                  : log information for the balancer service\n    Clients.log                   : log information for client calls to the MGM\n    Converter.log                 : log information of the converter service\n    DrainJob.log                  : log information for draining\n    eosxd-logtraces.log           : traces requested using 'eos fusex evict ... sendlog' from clients\n    eosxd-stacktraces.log         : traces requested using 'eos fusex evict ... stracktrace' from clients\n    error.log                     : all message with ERROR level from FSTs\n    FileInspector.log             : log information for the file inspector service\n    GeoBalancer.log               : log information for the GEO balancer service\n    GeoTreeEngine.log             : log information for the GEO tree engine\n    GroupBalancer.log             : log information for the group balancer\n    GroupDrainer.log              : log information for the group drainer\n    Grpc.log                      : log information for the GRPC server\n    Http.log                      : log information for the HTTP(S) server\n    logbook.log                   : annotated commands stored in the logbook on user request\n    LRU.log                       : log information for the LRU service\n    Master.log                    : log information for HA master transitions\n    MetadataFlusher.log           : log information for the metadata flusher\n    Mounts.log                    : log information for FUSE mount/umount\n    OAuth.log                     : log information for OAUTH authentication\n    Recycle.log                   : log information for the Recycle (purging) service\n    ReplicationTracker.log        : log information for the Replication tracker service\n    WFE::Job.log                  : log information for Workflow Engine jobs\n    WFE.log                       : log information for the Workflow Engine\n    xrdlog.mgm                    : main log file with all log messages\n    ZMQ.log                       : log information for the ZMQ server\n    ============================= ============================================================================\n"
  },
  {
    "path": "doc/diopside/manual/develop.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Developing\n\n.. _Develop:\n\nDeveloping EOS\n--------------\n\n.. image:: cpp.jpg\n   :scale: 40%\n   :align: left\n\n\nGetting a development machine [CERN specific]\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nGo to `CERN Openstack <https://openstack.cern.ch/>`_ and find project 'IT EOS development' (if you are not a member ask your colleagues to give you access). Check in the Menu:Project/Compute/Overview if there are still free resources to be used. If not, coordinate with your colleagues on reviewing if all currently existing machines are needed. If yes, inform your section leader that you would like to ask for more resources, then follow `Request Quota <https://clouddocs.web.cern.ch/projects/project_quota_request.html>`_.\n\nTo get a decent machine, look at Menu\"Project/Compute/Instances and then click \"Launch Instance\" button. Follow the form and when choosing  \"Flavour\" look for 'm2.2xlarge'. If not visible, file a ticket to the Openstack team asking for making it available to you (not publicly available). Machine creation is possible also from a command line using `CERN Openstack Tools <https://clouddocs.web.cern.ch/index.html>`_.\n\nOnce the machine is running, you can log-in from aiadm.cern.ch to your machine (as root for convenience) and upgrade to newest packages/kernel etc. (:code:`yum clean all; yum upgrade; reboot;`).\n\n\nSource Code\n^^^^^^^^^^^^^^\n\nAsk EOS developers at CERN to give you write permissions to the `EOS repository <https://gitlab.cern.ch/dss/eos.git>`_.\nThe current development model is that anyone with permission can directly commit to master branch. Newcomers are encouraged to create a separate branch and make a merge request with review from one of the main developers.\n\nInstall git, create your working directory (e.g. :code:`/devwork/local`) and clone the EOS repository:\n\n.. code-block:: bash\n\n   yum install git\n   git config --global user.email \"your@email.x\"\n   git config --global user.name \"FirstName LastName\"\n\n   mkdir -p /devwork/local\n   cd /devwork/local\n\n   git clone https://gitlab.cern.ch/dss/eos.git\n\n   cd eos\n   git submodule update --recursive --init\n\n\nCreate a build directory ...\n\n.. code-block:: bash\n\n   mkdir build\n   cd build\n\nCreate your dev environment using utils scripts (Recommended)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTwo utility scripts have been created to ease the setup of the development environment necessary to build and install EOS on a bare Centos VM.\nThese scripts are located in the :code:`utils` directory. Feel free to modify them as they could be outdated. These scripts automate what is documented in\nthe `Dependencies`_ part of this documentation.\n\nEL7\n\"\"\"\n\nJust run the :code:`./utils/centos7-dev-environment.sh` script.\n\nGo to your :code:`eos` directory and type:\n\n.. code-block:: bash\n\n    sudo ./utils/centos7-dev-environment.sh\n\nIf everything went well, your VM should reboot.\n\nEL8\n\"\"\"\"\n\nJust **source** the :code:`./utils/centos7-dev-environment.sh` script.\n\nGo to your :code:`eos` directory and type:\n\n.. code-block:: bash\n\n    source ./utils/centos8-dev-environment.sh\n\nIf you haven't used **source**, source your :code:`.bashrc` file. Indeed the :code:`centos8-dev-environment.sh` script script modifies your .bashrc to update your :code:`PATH` variable.\nIf you don't source it, you will not be able to use the good cmake version that has been installed to create the configuration for building EOS.\n\nYou are ready to compile.\nYou can skip the next parts and go directly to the `Compilation`_ part of this documentation. Though you can also read them if you need to troubleshoot future problems.\n\n\nEL9\n\"\"\"\n\nGo to your :code:`eos` directory and type:\n\n.. code-block:: bash\n   bash ./utils/el9-dev-environment.sh\n\nThis should setup eos-depend repo for diopside and install the requisite dependencies. Move on the `Compilation`_ part for the rest of instructions.\n\nDependencies\n^^^^^^^^^^^^^^\n\n.. warning:: Before compilation of the master branch you have to make sure that you installed all required dependencies.\n\nEL7\n\"\"\"\"\"\n\nThere is a convenience scripts to install all dependencies in the EOS source tree:\n\n.. code-block:: bash\n\n   utils/el7-packages.sh\n\nThis script might not be up to date. To be sure you are having all the dependencies installed consistently with the version of EOS code you just downloaded, one first needs to define the EOS yum repositories as stated in the `Quick Start Guide/Setup YUM Repository <http://eos-docs.web.cern.ch/eos-docs/quickstart/setup_repo.html#eos-base-setup-repos>`_. One may also look for inspiration at the Dockerfiles in the :code:`eos/gitlab-ci/prebuild_OSbase` of your repository, which follows a similar process we will layout below.\n\n\nAsk the developers which cmake version is currently supported (cmake3 as of Dec 2020), then, install the following packages (e.g. ccache for speeding up compiling):\n\n.. code-block:: bash\n\n   yum install --nogpg -y ccache centos-release-scl-rh cmake3 gcc-c++ gdb make rpm-build rpm-sign yum-plugin-priorities && yum clean all\n\n\nRun cmake3 with the DPACKAGEONLY=1 option and make source rpms:\n\n.. code-block:: bash\n\n   cmake3 ../ -DPACKAGEONLY=1 && make srpm\n\n\nNow build the EOS dependencies:\n\n.. code-block:: bash\n\n   yum-builddep --nogpgcheck --setopt=\"cern*.exclude=xrootd*\" -y SRPMS/*\n\nThis will install among other also the devtoolset-8 required for eos development (as of Dec 2020). Add line ‘source /opt/rh/devtoolset-8/enable’ to your bash profile to load each time you log in. This should be confirmed by getting the right path for the compiler, e.g. :code:`which c++` will show :code:`/opt/rh/devtoolset-8/root/usr/bin/c++` as the output.\n\nYou will also need to install *QuarkDB*. Define the yum repository with :code:`/etc/yum.repos.d/quarkdb.repo` file with the following content:\n\n.. code-block:: bash\n\n    [quarkdb-stable]\n    name=QuarkDB repository [stable]\n    baseurl=http://storage-ci.web.cern.ch/storage-ci/quarkdb/tag/el7/x86_64/\n    enabled=1\n    gpgcheck=False\n\n\nThen, run:\n\n.. code-block:: bash\n\n    yum install quarkdb quarkdb-debuginfo redis\n\n\nImportant troubleshooting steps\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIf you do not succeed enabling devtoolset-8 it might also be that you are using zsh which is incompatible with `scl-utils <https://stackoverflow.com/questions/62958800/enable-devtoolset-8-for-zsh-on-centos-7>`_.\n\nMake sure you have compatible xrootd version installed (:code:`rpm -qa | grep xroot`), currently the above will install you version 5.0.3 which is not yet compatible with EOS <= 4.8.35 (Dec/Jan 2020). Look at the latest version of xrootd in the [eos-citrine-dep] repository (currently 4.12.6) or ask the developers if in doubt.\n\n.. code-block:: bash\n\n    rpm -qa | grep xroot\n    > xrootd-client-libs-5.0.3-2.el7.x86_64\n    > xrootd-server-devel-5.0.3-2.el7.x86_64\n    > xrootd-selinux-5.0.3-2.el7.noarch\n    > xrootd-libs-5.0.3-2.el7.x86_64\n    > xrootd-devel-5.0.3-2.el7.x86_64\n    > xrootd-server-libs-5.0.3-2.el7.x86_64\n    > xrootd-server-5.0.3-2.el7.x86_64\n    > xrootd-private-devel-5.0.3-2.el7.x86_64\n    > xrootd-client-devel-5.0.3-2.el7.x86_64\n    > xrootd-5.0.3-2.el7.x86_64\n\n    # as mentioned above, the version needs to be the latest available in [eos-citrine-dep] repository (defined in steps above); to fix this, do the following:\n    yum remove xrootd-*\n    # it could also be that xrootd 4.12.6 was renamed to xrootd4 in case you have a mix of xrootd and xrootd4 `yum remove xrootd4-*` and make sure you have the right packages:\n    yum install xrootd xrootd-client xrootd-server-devel xrootd-private-devel --disablerepo=\"*\" --enablerepo=eos-citrine-dep\n    # yum install xrootd-4.12.6-1.el7 xrootd-client-4.12.6-1.el7 xrootd-server-devel-4.12.6-1.el7 xrootd-private-devel-4.12.6-1.el7 --disablerepo=\"*\" --enablerepo=eos-citrine-dep\n\n\nIt may also currently install *eos-folly-2020.10.05.00-1.el7.cern.x86_64* which (for EOS <=4.8.35) needs to be *2019.11.11.00-1.el7.cern*, fix this by:\n\n.. code-block:: bash\n\n    yum remove eos-folly eos-folly-deps\n    yum install eos-folly-2019.11.11.00-1.el7.cern\n\n\nOptional Optimizations\n^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code-block:: bash\n\n    yum install -y moreutils \\\n    yum clean all\n\n\ninstall moreutils just for 'ts', nice to benchmark the build time.\n\n\nCompilation\n^^^^^^^^^^^^^^\n\n\nEOS is a system of libraries which gets loaded by the xrootd executable. In order to run the version you just cloned (or later modified), you have to compile those libraries and then make sure xrootd loads the correct ones. In order to facilitate the deployment, we install ninja-build package (like :code:`make` but faster):\n\n.. code-block:: bash\n\n    yum install ninja-build\n\n\nCreate a new build directory and try to run cmake (see troubleshooting below):\n\n.. code-block:: bash\n\n    mkdir /devwork/local/eos/build-with-ninja\n    cd /devwork/local/eos/build-with-ninja\n    cmake3 ../ -G Ninja -DCMAKE_INSTALL_PREFIX=/usr/ -Wno-dev -DCMAKE_C_COMPILER=/opt/rh/devtoolset-8/root/usr/bin/cc -DCMAKE_CXX_COMPILER=/opt/rh/devtoolset-8/root/usr/bin/c++\n\n\nCompile\n\"\"\"\"\"\"\"\n\n\n.. code-block:: bash\n\n    ninja-build\n    # run some unit tests; should finish with [  PASSED  ] XXX tests message\n    unit_tests/eos-unit-tests\n\n\nTroubleshooting\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe following dependencies might not be required (you should be able to ignore these in the cmake3 output):\n\n.. code-block:: bash\n\n    Could NOT find Sphinx\n    Could NOT find fuse3\n    Could NOT find davix\n    Could NOT find GTest\n\n\n.. warning:: yum can automatically update your packages (in yum history you can see:  \"-y --skip-broken update\" in such a case) you can remove this package :code:`yum remove yum-autoupdate` to make sure it does not screw up EOS rpms installed.\n\nIf when executing the unit tests you have errors about the linker that could not find .so files, you can update your :code:`LD_LIBRARY_PATH` to add to it the :code:`common` and the :code:`mq` directory of your EOS build directory.\n\nDeployment\n^^^^^^^^^^^^^^\n\n\nUse Ninja to install EOS on your development machine:\n\n.. code-block:: bash\n\n    cd /devwork/local/eos/build-with-ninja\n    ninja-build install\n\n    # depending on your OS, you can remove the el6 repository\n    # to avoid pulling packages and dependencies from there in the future\n    rm -rf /etc/yum.repos.d/eos-el6*\n    rm -rf /etc/yum.repos.d/eos-el7*\n\n\nAfter you finish your deployment configuration (see below) and you start modifying the source code, to deploy it, you can do:\n\n.. code-block:: bash\n\n    sudo systemctl stop eos@*\n    cd /devwork/local/eos/build-with-ninja\n    # if needed rm files from the build directory and run cmake3 again before the next step\n    ninja-build\n    ninja-build install\n    rm -rf /etc/yum.repos.d/eos-el6*\n    cp /etc/xrd.cf.mgm_bkp /etc/xrd.cf.mgm\n    cp /etc/xrd.cf.mq_bkp /etc/xrd.cf.mq\n    systemctl daemon-reload\n    systemctl start eos\n\n\nDeployment Configuration\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWe need to configure and run the following set of daemons: MGM, MQ (messaging service between MGM and FSTs), several FSTs and QuarkDB.\n\nQuarkDB\n\"\"\"\"\"\"\"\n\nCreate a configuration file :code:`/etc/xrootd/xrootd-quarkdb.cfg` with the following content:\n\n.. code-block:: bash\n\n    xrd.port  7777\n    xrd.protocol redis:7777 libXrdQuarkDB.so\n    redis.mode  standalone\n    redis.database  /var/lib/quarkdb/eosns\n    # redis.myself  localhost:7777\n    redis.password_file  /etc/eos.keytab\n\n\n In production deployment usually raft mode is used instead of standalone (you need at least 2 nodes for such a mode).\n\n\nCreate path to your QuarkDB namespace specified above:\n\n.. code-block:: bash\n\n     install -d -o daemon -g daemon /var/lib/quarkdb\n\nBecause the eos service will run as user 'daemon', you will have to make sure that all relevant files have the correct permissions and change their ownership, i.e. run:\n\n.. code-block:: bash\n\n    chown -R daemon:daemon /var/log/xrootd\n    chown -R daemon:daemon /var/eos/\n    chown -R daemon:daemon /etc/eos.* # + must have permissions 400 !\n    chown -R daemon:daemon /var/run/xrootd\n    chown -R daemon:daemon /var/lib/quarkdb\n    chown -R daemon:daemon /var/spool/xrootd\n\n\nBecause the eos service will run as user \"daemon\", you should run the QuarkDB as the same user, i.e.:\n\nCreate your QuarkDB path (modify eostest and <myhostname>) :\n\n.. code-block:: bash\n\n    UUID=eostest-$(uuidgen); echo $UUID; sudo runuser daemon -s /bin/bash -c \"quarkdb-create --path /var/lib/quarkdb/eosns --clusterID $UUID --nodes localhost:7777\"\n\n\nBefore starting the service, we will need custom config drop-in script. Create the following file path :code:`/etc/systemd/system/xrootd@quarkdb.service.d/custom.conf` with the following content:\n\n.. code-block:: bash\n\n    [Service]\n    User=daemon\n    Group=daemon\n\n\nBefore starting the service, check once again that the keytabs :code:`/etc/eos.*` have permission 400. Then start the service (log can be followed in :code:`/var/log/xrootd/quarkdb/xrootd.log`) and check its status:\n\n.. code-block:: bash\n\n    systemctl start xrootd@quarkdb\n    systemctl status xrootd@quarkdb\n\n\nNote: `QuarkDB Installation documentation <https://quarkdb.web.cern.ch/quarkdb/docs/master/installation/>`_ is a very helpful resource, in particular for troubleshooting!\n\nTest if the QuarkDB runs fine by saving and retrieving key-value pair:\n\n.. code-block:: bash\n\n    redis-cli -p 7777\n    127.0.0.1:7777> set mykey myval\n    OK\n    127.0.0.1:7777> get mykey\n    \"myval\"\n\n\nMGM\n\"\"\"\n\n\nThe environment configuration will be loaded from :code:`/etc/sysconfig/eos_env` which you need to create from a provided example.\n\n.. code-block:: bash\n\n    mv /etc/sysconfig/eos_env.example /etc/sysconfig/eos_env\n\nInside you need to fill in various pieces of information:\n\n* :code:`XRD_ROLES=\"mq mgm fst1 fst2 fst3\"`, depending on how many fst daemons you plan (here 3) to run, you need to specify them here. Drop :code:`fed` and :code:`sync` as they are not used anymore.\n* HOST_TARGET was used for the :code:`sync` and is not needed anymore.\n* EL8 systems will need :code:`LD_PRELOAD=/usr/lib64/libjemalloc.so.2`\n\n.. code-block:: bash\n\n    # XRD_ROLES depends on what you wish to run, each separate mgm, mq or fst needs to be specified, e.g. for 3 fsts daemons we put fst1 fst2 fst3.\n    # Delete \"fed\" and \"sync\" if present as they are not used anymore.\n    XRD_ROLES=\"mq mgm fst1 fst2 fst3\"\n    EOS_INSTANCE_NAME=eostest` # has to start with \"eos\" and has the form \"eos<name>\".\n    EOS_MGM_ALIAS=<myhostname>.cern.ch`\n    EOS_MAIL_CC=<email>`\n    EOS_GEOTAG=\"\\:\\:<anything>\"` # needs to be filled\n\n\nThen you need Kerberos security keys on your machine:\n\n.. code-block:: bash\n\n    cp /etc/krb5.keytab /etc/eos.krb5.keytab\n\n.. warning:: Kerberos and SSS keytab files are different and use different formats.  Read the documentation for the :code:`xrdsssadmin` command or XRootD SSS protocol at https://xrootd.slac.stanford.edu/doc/dev49/sec_config.htm#_Toc517294117\n\nThere is yet another configuration file you will have to modify e.g. :code:`/etc/xrd.cf.mgm` (note aside: These are configuring the xroot daemons that will be running as a service, this has nothing to do with the config of eos itself which is being saved in QuarkDB.). In this file you can change the security settings as needed, e.g.:\n\n.. code-block:: bash\n\n    sec.protocol unix\n    sec.protocol sss -c /etc/eos.client.keytab -s /etc/eos.keytab\n    # Example disable krb5 and gsi\n    #sec.protocol krb5\n    #sec.protocol gsi\n\n    sec.protbind localhost.localdomain unix sss\n    sec.protbind localhost unix sss\n    sec.protbind * only sss unix\n\n\nNote: that the order of sec.protbind matters for host matching (matches from \"bottom up\", i.e. in reverse order of specification, from most specific to least specific).\n\nAlso, activate QuarkDB namespace plugin usage and set other parameters as desired, e.g.:\n\n.. code-block:: bash\n\n    mgmofs.nslib /usr/lib64/libEosNsQuarkdb.so\n    mgmofs.instance eostest\n    mgmofs.qdbcluster localhost:7777\n    mgmofs.qdbpassword_file /etc/eos.keytab\n\nOnce done, backup the result to have it available after the next reinstallation of recompiled EOS:\n\n.. code-block:: bash\n\n    cp /etc/xrd.cf.mgm /etc/xrd.cf.mgm_bkp\n\nStart the service, check the status and log file in :code:`/var/log/eos/mgm/xrdlog.mgm` :\n\n.. code-block:: bash\n\n    systemctl daemon-reload\n    systemctl start eos@mgm\n    systemctl status eos@mgm\n\n\nYou will see errors *RefreshBrokersEndpoints* this is due to the fact that MQ is not yet running:\n\n.. code-block:: bash\n\n    less /var/log/eos/mgm/xrdlog.mgm\n    ...\n    210304 11:34:22 time=1614854062.201983 func=RefreshBrokersEndpoints  level=ERROR logid=static.............................. unit=mgm@eos-ccaffy-dev01.cern.ch:1094 tid=00007f324109f700 source=XrdMqClient:495                tident= sec=(null) uid=99 gid=99 name=- geo=\"\" msg=\"failed to contact broker\" url=\"root://localhost:1097//eos/eos-ccaffy-dev01.cern.ch/mgm_mq_test?xmqclient.advisory.flushbacklog=1&xmqclient.advisory.query=1&xmqclient.advisory.status=1\"\n    ...\n\nTroubleshooting:\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIf by looking at the :code:`/var/log/eos/mgm/xrdlog.mgm` log file, you see the following error:\n\n.. code-block:: bash\n\n    Seckrb5: Unable to start sequence on the keytab file FILE:/etc/krb5.keytab; Permission denied\n\nThen do\n\n.. code-block:: bash\n\n    chmod a+r /etc/krb5.keytab\n\n\nMQ\n\"\"\"\n\nLook at :code:`/etc/sysconfig/eos_env` and :code:`/etc/xrd.cf.mq` in case you would want any changes there (this can stay as provided by default).\n\nStart the service, check the status and log file in :code:`/var/log/eos/mq/xrdlog.mq` :\n\n.. code-block:: bash\n\n    systemctl start eos@mq\n    systemctl status eos@mq\n\nYou will see expected errors in connection queue, this is because MQ can not connect to any running file system daemons (FST).\nOn the other hand the *RefreshBrokersEndpoints* of the MGM  :code:`/var/log/eos/mgm/xrdlog.mgm` should now disappear.\n\n\nFST\n\"\"\"\n\nLook at :code:`/etc/sysconfig/eos_env` in case you would want any changes there (this can stay as provided by default). For each of XRD_ROLES there has to be a configuration file created. For each the file systems you wish to add, you need to create a new configuration file from a provided template :code:`/etc/xrd.cf.fst`. The template can be used directly, but you might want to change the port number in each:\n\n.. code-block:: bash\n\n    for i in {1..3}; do\n        cp /etc/xrd.cf.fst /etc/xrd.cf.fst\"${i}\"\n        sed -i \"s/xrd.port 1095/xrd.port 200${i}/g\" /etc/xrd.cf.fst\"${i}\"\n    done;\n\nAlso make sure the following 2 lines are present in each cfg file created above:\n\n.. code-block:: bash\n\n    fstofs.qdbcluster localhost:7777\n    fstofs.qdbpassword_file /etc/eos.keytab\n\n\nEach file system will run its own FST daemon (which communicates via MQ with the MGM). For each of these daemons, we need to create a special drop-in script specifying the connection port:\n\n.. code-block:: bash\n\n    for i in {1..3}; do\n        mkdir -p /usr/lib/systemd/system/eos@fst\"${i}\".service.d\n        cd /usr/lib/systemd/system/eos@fst\"${i}\".service.d\n        echo \"[Service]\" > custom.conf;\n        echo \"Environment=EOS_FST_HTTP_PORT=900${i}\" >> custom.conf;\n    done;\n\n\nFST is usually represented by a disk server with many disks mounted on it.\nFor our dev purposes, it is usually enough to just create a directory per fst on the local file system (do not forget to grant \"daemon\" the ownership). In each of these directories there has to be 2 hidden files containing the ID:code:`.eosfsid` and UUID :code:`.eosfsuuid` of the FST you are adding (define however convenient for you), e.g.:\n\n.. code-block:: bash\n\n    mkdir -p /fst\n    cd /fst\n    for i in {1..3}; do\n        mkdir data$i\n        echo $i >  data$i/.eosfsid\n        echo fst$i > data$i/.eosfsuuid;\n    done\n    chown daemon:daemon -R /fst\n\n\nNow, start the FST services:\n\n.. code-block:: bash\n\n    systemctl daemon-reload\n    for i in {1..3}; do\n      sudo systemctl start eos@fst\"${i}\"\n    done;\n\n    for i in {1..3}; do\n      sudo systemctl status eos@fst\"${i}\"\n    done;\n\n\nThe :code:`/var/log/eos/fstX/xrdlog.fstX` (replace X with the wanted FST number) you will see lines such as:\n\n.. code-block:: bash\n\n    210116 12:53:13 time=1610797993.062063 func=Storage                  level=INFO  logid=FstOfsStorage unit=fst@<hostname>:2001\n\n\nAlso, you should see all as active and the MQ :code:`/var/log/eos/mq/xrdlog.mq` connecting to the \"nodes\" (the 3 various ports) and new links being added to quarkDB :code:`/var/log/xrootd/quarkdb/xrootd.log`.\n\nIf you now run :code:` eos node ls`, you should see the list of (3) FST nodes as they connected to the MGM.\n\n\nTroubleshooting:\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIf you see in the log file :code:`/var/log/eos/fstX/xrdlog.fst1`\n\n.. code-block:: bash\n\n    [QCLIENT - INFO - connectTCP:302] Encountered an error when connecting to <yourmachine>:7777 (IPv4,stream resolved from localhost): Unable to connect (111):Connection refused\n\n\nopen a firewall for port 7777:\n\n\n.. code-block:: bash\n\n     firewall-cmd --zone=public --add-port=7777/tcp --permanent\n     firewall-cmd --reload\n\nNote: If you are running multiple MGM nodes on separate hosts, you may also need to open the respective ports of all EOS daemons. By default they are:\n\n* quarkDB: 7777\n* mgm: 1094\n* fst: 1095\n* mq: 1097\n\n\nEOS namespace configuration\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n\nWe have QuarkDB, MQ, MGM, and FST daemons running. Now we need to define the EOS space\nto which we will be adding these file systems. Similarly to production we can define \"spare\"\nspace, just to keep disks waiting unused in spare and \"default\" space which will be in this example from 3 scheduling groups by 1 disk each:\n\n.. code-block:: bash\n\n    eos space define spare 0 0\n    eos space set spare on\n    eos space define default 1 3\n    eos space set default on\n\n    # check the status\n    eos space status default\n    eos space status spare\n\n\nNow we need to register the FST disks with EOS (we first put them in \"spare\"):\n\n.. code-block:: bash\n\n    for i in {1..3}; do eos fs add fst${i} ${HOSTNAME}:200${i} /fst/data${i} spare ;done;\n    # to see them added:\n    eos fs ls\n\nThen drain the disks:\n\n.. code-block:: bash\n\n    eos fs ls spare | awk '/fst/ {print \"eos -b fs config \" $3 \" configstatus=drain\"}' | sh -x\n\n\nMake sure, they all appear as empty in the output of :code:`eos fs ls` after this operation.\n\nThen boot them:\n\n.. code-block:: bash\n\n    for i in {1..3}; do eos fs boot $i;done;\n\nIf they do not boot you can check what is wrong by :code:`eos fs ls -e`.\n\nAnd finally move them to the default space. They will be distributed to the scheduling groups automatically. If you were to add more fsts for development purposes we do not mind having 2 disks form the same node in the same scheduling group (use the --force option below) which is by default forbidden.\n\n.. code-block:: bash\n\n    eos fs ls spare | awk '/fst/ {print \"eos -b fs mv --force \" $3 \" default\"}' | sh -x\n\n\nAfter this you will see the file systems booted, empty and online in the output of :code:`eos fs ls -e`.\n\nSet the disks to 'rw' mode:\n\n.. code-block:: bash\n\n    eos fs ls default | awk '/fst/ {print \"eos -b fs config \" $3 \" configstatus=rw\"}' | sh -x\n    eos fs ls\n\nNow, you should be able to work with your EOS file system:\n\n.. code-block:: bash\n\n    eos ls /eos\n    eos mkdir /eos/<name>/devtests\n    eos attr ls /eos/<name>/devtests\n    xrdcp root://localhost//eos/<name>/proc/whoami -\n    cd /tmp;\n    cat > hello_eos\n    Hello EOS !\n    eos cp hello_eos /eos/<name>/devtests/hello_eos\n    eos ls -l /eos/<name>/devtests\n    rm hello_eos\n    xrdcp \"root://localhost//eos/<name>/devtests/hello_eos\" hello_eos\n    cat hello_eos\n    eos ns\n    # [...]\n    # ALL      files created since boot         1\n    # ALL      container created since boot     1\n    # [...]\n\n\nIf you enabled other authentication mechanisms than sss and unix, you need to enable them, e.g.:\n\n.. code-block:: bash\n\n    eos vid enable gsi\n    eos vid enable krb5\n\n\nFlatScheduler concepts\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe scheduler builds a hierarchical tree for placement, there are 2 types of elements:\n\n*Disk* - The final resulting disks where the writes will eventually happen\n*Bucket* - Any other element in the storage hierarchy - ie. Rack, Row, DC etc.\n\nThe default behaviour uses a simple hierarchy of ROOT - SITE - Group - Disk\n\nHowever it will be possible to do more interesting placements by specifying a\nstrategy where one can choose how many replicas of a particular element one\nneeds to choose. For a cross DC placement it would be simply a matter of\nchoosing 2 DCs at the first bucket and subsequently choosing the disks. This feature\nis being worked on in the 5.3.x series, currently the cross DC placement examples\ncan be found in `unit_tests/mgm/placement/SchedulerTests.cc`.\n"
  },
  {
    "path": "doc/diopside/manual/egi.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   pair: EGI; Releases\n\nEGI Releases\n============\n\nEOS provides scripts that generate the required space accounting information [https://wiki.egi.eu/wiki/APEL/Storage] and info provider data. These scripts are available in the `eos-server` package starting with release 5.0.15.\n\n.. index::\n   pair: EGI; Storage Accounting\n\nStorage Accounting\n----------------------\n\nThis information is provided by the `eos-star-accounting.py` script that looks at the EOS space configuration present in your instance. An example of how to invoke this script is provided below with some demo information:\n\n.. code-block:: bash\n\n   eos-star-accounting.py\n   <sr:StorageUsageRecords xmlns:sr=\"http://eu-emi.eu/namespaces/2011/02/storagerecord\">\n     <sr:StorageUsageRecord>\n       <sr:RecordIdentity sr:createTime=\"2022-03-21T14:22:21Z\" sr:recordId=\"esdss000.cern.ch-52307ef4-a922-11ec-bc51-dc4a3e6b9f27\"/>\n       <sr:StorageSystem>esdss000.cern.ch</sr:StorageSystem>\n       <sr:SubjectIdentity>\n         <sr:Site>eosdev</sr:Site>\n       </sr:SubjectIdentity>\n       <sr:StorageMedia>disk</sr:StorageMedia>\n       <sr:StartTime>2022-03-20T14:22:21Z</sr:StartTime>\n       <sr:EndTime>2022-03-21T14:22:21Z</sr:EndTime>\n       <sr:FileCount>1289</sr:FileCount>\n       <sr:ResourceCapacityUsed>1287017889792</sr:ResourceCapacityUsed>\n       <sr:ResourceCapacityAllocated>1287017889792</sr:ResourceCapacityAllocated>\n       <sr:LogicalCapacityUsed>1287017889792</sr:LogicalCapacityUsed>\n     </sr:StorageUsageRecord>\n   </sr:StorageUsageRecords>\n\n\n.. index::\n   pair: EGI; Info Provider\n\nInfo Provider\n-----------------\n\nThis information is provided by the `eos-info-provider.py` script.\n\n.. code-block:: bash\n\n eos-info-provider.py --sitename eosdev\n version: 1\n dn: GLUE2ServiceID=esdss000.cern.ch/Service,GLUE2GroupID=resource,o=glue\n changetype: add\n objectClass: GLUE2Service\n objectClass: GLUE2StorageService\n GLUE2ServiceID: esdss000.cern.ch/Service\n GLUE2EntityCreationTime: 2022-03-21T14:24:55Z\n GLUE2ServiceQualityLevel: production\n GLUE2ServiceCapability: data.access.flatfiles\n GLUE2ServiceCapability: data.transfer\n GLUE2ServiceCapability: data.management.replica\n GLUE2ServiceCapability: data.management.storage\n GLUE2ServiceCapability: data.management.transfer\n GLUE2ServiceCapability: security.authentication\n GLUE2ServiceCapability: security.authorization\n GLUE2ServiceType: eos\n GLUE2ServiceAdminDomainForeignKey: eosdev\n version: 1\n dn: GLUE2StorageServiceCapacityID=esdss000.cern.ch/StorageServiceCapacity,GLUE\n   2ServiceID=esdss000.cern.ch/Service,GLUE2GroupID=resource,o=glue\n changetype: add\n objectClass: GLUE2StorageServiceCapacity\n GLUE2StorageServiceCapacityUsedSize: 1198\n GLUE2EntityCreationTime: 2022-03-21T14:24:55Z\n GLUE2StorageServiceCapacityType: online\n GLUE2StorageServiceCapacityID: esdss000.cern.ch/StorageServiceCapacity\n GLUE2StorageServiceCapacityFreeSize: 867\n GLUE2StorageServiceCapacityStorageServiceForeignKey: esdss000.cern.ch/Service\n GLUE2StorageServiceCapacityTotalSize: 2065\n version: 1\n dn: GLUE2ManagerID=esdss000.cern.ch/Manager,GLUE2ServiceID=esdss000.cern.ch/Se\n   rvice,GLUE2GroupID=resource,o=glue\n changetype: add\n objectClass: GLUE2StorageManager\n objectClass: GLUE2Manager\n GLUE2ManagerProductName: EOS\n GLUE2EntityCreationTime: 2022-03-21T14:24:58Z\n GLUE2ManagerProductVersion:\n GLUE2StorageManagerStorageServiceForeignKey: esdss000.cern.ch/Service\n GLUE2ManagerServiceForeignKey: esdss000.cern.ch/Service\n GLUE2ManagerID: esdss000.cern.ch/Manager\n version: 1\n dn: GLUE2ResourceID=esdss000.cern.ch/DataStore,GLUE2ManagerID=esdss000.cern.ch\n   /Manager,GLUE2ServiceID=esdss000.cern.ch/Service,GLUE2GroupID=resource,o=glue\n changetype: add\n objectClass: GLUE2DataStore\n GLUE2DataStoreLatency: online\n GLUE2DataStoreFreeSize: 867\n GLUE2ResourceManagerForeignKey: esdss000.cern.ch/Manager\n GLUE2EntityCreationTime: 2022-03-21T14:24:58Z\n GLUE2DataStoreType: disk\n GLUE2DataStoreUsedSize: 1198\n GLUE2DataStoreStorageManagerForeignKey: esdss000.cern.ch/Manager\n GLUE2ResourceID: esdss000.cern.ch/DataStore\n GLUE2DataStoreTotalSize: 2065\n"
  },
  {
    "path": "doc/diopside/manual/formats.rst",
    "content": ".. index::\n   pair: Formats; Reportlogs\n\n.. highlight:: rst\n\n.. _formats:\n\nReport log files\n================\n\nThe EOS MGM writes report log files under\n\n.. code-block:: bash\n\n   /var/eos/report/<YEAR>/<MONTH>/<YEAR><MONTH><DAY>.eosreport\n\n\nOn top of a few [xrd.cf.mgm]{.title-ref} configuration variables, it\nmust be enabled on the MGM:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/ctaatlaspps/archivetest/> io enable -r\n   success: enabled IO report store\n\nCompressed report log files\n--------------------------\n\nZSTD-compressed report logs can be enabled via environment variables in\n[xrd.cf.mgm]{.title-ref}. When enabled, reports are written as compressed,\ntime-rotated segments instead of plain ``.eosreport`` files. Disabled by default.\n\n.. code-block:: bash\n\n   # Enable ZSTD-compressed iostat reports. Disabled by default.\n   # When enabled, reports are written as compressed, time-rotated segments instead of plain .eosreport files.\n   #EOS_ZSTD_REPORTS=0\n   # Rotation interval for compressed reports in seconds (default 86400 = 1 day)\n   #EOS_ZSTD_REPORTS_ROTATION=86400\n   # ZSTD compression level for reports (1..19), default 1\n   # Note: variable name is EOS_ZSTD_REPORTS_LEVE as requested\n   #EOS_ZSTD_REPORTS_LEVE=1\n\nFile creation/update records\n-----------------------------\n\nEach FST sends for each file replica or piece it writes a record which\nlooks like this:\n\n\n``log=cb9ae364-4f7c-11e8-8a9a-02163e009ce2&path=/eos/testfile&ruid=0&rgid=0&td=root.13142:52@slc7&\nhost=test.cern.ch &lid=1048578&fid=1056332&fsid=1&ots=1525425804&otms=531&cts=1525425804&ctms=533&\nnrc=0&nwc=1&rb=0&rb_min=0&rb_max=0 &rb_sigma=0.00&rv_op=0&rvb_min=0&rvb_max=0&rvb_sum=0&rvb_sigma=0.00&\nrs_op=0&rsb_min=0&rsb_max=0&rsb_sum=0&rsb_sigma=0.00 &rc_min=0&rc_max=0&rc_sum=0&rc_sigma=0.00&wb=2202&\nwb_min=2202&wb_max=2202&wb_sigma=0.00&sfwdb=0&sbwdb=0&sxlfwdb=0 &sxlbwdb=0&nfwds=0&nbwds=0&nxlfwds=0&\nnxlbwds=0&rt=0.00&rvt=0.00&wt=0.01&osize=0&csize=2202&delete_on_close=0&prio_c=2&prio_l=4&prio_d=1&\nsec.prot=sss&sec.name=daemon&sec.host=localhost&sec.vorg=&sec.grps=daemon&sec.role=&sec.info=&\nsec.app=eoscp``\n\n\n.. epigraph::\n\n   =================== ==================================================================\n   TAG                 Description                                                      \n   =================== ==================================================================\n   log                 uuid to correlate log entries\n   path                logical path\n   ruid                mapped user id\n   rgid                mapped group id\n   td                  trace identifier: <unix-user>|.<pid>|.<fd|@<host>|.<domain>|\n   lid                 layout id\n   fid                 file id\n   fsid                file system id\n   ots                 open timestamp\n   otms                open time milliseconds\n   cts                 close timestamp\n   ctms                close time milliseconds\n   nrc                 number of read calls\n   nwc                 number of write calls\n   rb                  bytes read (non vector reads)\n   rb_min              smallest read call in bytes (non vector reads)\n   rb_max              largest read call in bytes (non vector reads)\n   rb_sigma            standard deviation of read bytes (non vector reads)\n   rv_op               number of vector operations\n   rvb_min             smallest vector read in bytes\n   rvb_max             largest vector read in bytes\n   rvb_sum             sum of all vector read bytes\n   rvb_sigma           standard deviation of vector read bytes\n   rs_op               number of single reads in vector operations\n   rsb_min             smallest read call in vector operations\n   rsb_max             largest read call in vector operations\n   rsb_sum             sum of all individual read call bytes in vector operations\n   rsb_sigma           standard deviation of single read calls in vector operations\n   rc_min              smallest number of read calls in vector read operations\n   rc_max              largest number of read calls in vector read operations\n   rc_sum              sum of all read call sin vector read operations\n   rc_sigma            standard deviation of number of read calls in vector read operations\n   wb                  bytes written\n   wb_min              smallest write call in bytes\n   wb_max              largest write call in bytes\n   wb_sigma            standard deviation of write call in bytes\n   sfwdb               forward seeked bytes\n   sbwdb               backward seeked bytes\n   sxlfwdb             forward seeked bytes moving at least 128kb per seek\n   sxlbwdb             backward seekd bytes moving at least 128kb per seek\n   nfwds               number of forward seeks\n   nbwds               number of backward seeks\n   nxlfwds             number of large forward seeks ( =128kb)\n   nxlbwds             number of large backward seeks ( =128kb)\n   ot                  time spent in ms to open the file\n   ct                  time spent in ms to close a file (includes waiting for async writes and checksumming)\n   rt                  time spent in ms waiting for disk reads\n   rvt                 time spent in ms waiting for disk reads for vector reads\n   wt                  time spent in ms waiting for disk writes\n   lrt                 time spent in ms waiting for layout reads\n   lrvt                time spent in ms waiting for layout vector reads\n   lwt                 time spent in ms waiting for layout writes\n   iot                 time spent in total from open to close\n   idt                 idle time from open to close (where no open, close, read,readv or write happens)\n   osize               size of the file when opening\n   csize               size of the file when closing\n   delete_on_close     flag indicating delete on close status\n   prio_c              IO priority class (0:none 1:realtime 2:best effort 3:idle)\n   prio_l              IO priority level 0..7\n   prio_d              1: default values (best effort level 4) 0: explicitly set\n   sec.prot            security protocol e.g. krb5,gsi,sss,unix\n   sec.name            mapped user name e.g. root/daemon\n   sec.host            client host\n   sec.vorg            virtual organisation (only VOMS)\n   sec.grps            virtual group (only VOMS)\n   sec.role            virtual role (only VOMS)\n   sec.info            security information e.g. DN\n   sec.app             application responsible for record e.g. balancing,gridftp,eoscp,fuse\n   tpc.src             TPC source hostname (only on TPC transfers)\n   tpc.dst             TPC destination hostname (only on TPC transfers)\n   tpc.src_lfn         TPC file path at source (only on TPC transfers)\n   ior_err             1 (io error during read) otherwise 0\n   iow_err             1 (io error during write) otherwise 0\n   =================== ==================================================================\n\n   \n.. note::  \n    \n   In case of TPC transfers, only one of `tpc.src` or `tpc.dst` is available, depending on the type of TPC transfer\n\nFST deletion records\n^^^^^^^^^^^^^^^^^^^^^^^^\n\nEach FST sends for a deletion on disk a record which is tagged with\napplication *deletion* :\n\n``log=619d7b82-4f79-11e8-a96c-02163e009ce2&host=test.cern.ch&fid=1056316&fsid=1&dc_ts=1525425793&\ndc_tns=968438733&dm_ts=1525425793&dm_tns=968438733&da_ts=1525425793&da_tns=968438733&dsize=2202&\nsec.app=deletion``\n\n.. epigraph:: \n\n   =========== =========================================================== \n   TAG         Description                                              \n   =========== =========================================================== \n   log         uuid to correlate log entries\n   host        FST host name\n   fid         file id of the file deleted\n   fsid        filesystem id where the file is deleted\n   del_ts      timestamp when the deletion message was generated\n   del_tns     timestamp in ns when the deletion message was generated\n   dc_ts       change timestamp of the deleted file\n   dc_tns      change timestamp in ns of the deleted file\n   dm_ts       modification timestamp of the deleted file\n   dm_tns      modification timestamp in ns of the deleted file\n   da_ts       access timestamp on local disk of the deleted file\n   da_tns      access timestamp on local disk in ns of the deleted file\n   dsize       size of the file before deletion\n   sec.app     always: deletion\n   =========== =========================================================== \n \nMGM deletion records\n^^^^^^^^^^^^^^^^^^^^^\n\nThe MGM sends for each final deletion a record which is tagged with\napplication *rm* :\n\n``log=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&host=test.cern.ch:1094&fid=1056331&ruid=0&\nrgid=0dc_ts=1525425819&dc_tns=354463329&dm_ts=1525425804&dm_tns=478169000&dsize=2202&sec.app=rm``\n\n\nThe MGM sends for each deletion moving a file into the recycle bin a\nrecord tagged with application *recycle* :\n``log=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&host=test.cern.ch:1094&fid=1056325&ruid=0&\nrgid=0dc_ts=1525425819&dc_tns=351463254&dm_ts=1525425804&dm_tns=182997000&dsize=2202&sec.app=recycle``\n\n.. epigraph:: \n\n   =========== ========================================================== \n   TAG         Description\n   =========== ========================================================== \n   log         always: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n   host        MGM host name\n   fid         file id\n   del_ts      timestamp when the deletion message was generated\n   del_tns     timestamp in ns when the deletion message was generated\n   dc_ts       change timestamp of the deleted file\n   dc_tns      change timestamp in ns of the deleted file\n   dm_ts       modification timestamp of the deleted file\n   dm_tns      modification timestamp in ns of the deleted file\n   dsize       size of the file before deletion\n   sec.app     rm,recycle (see above)\n   =========== ========================================================== \n"
  },
  {
    "path": "doc/diopside/manual/getting-started.rst",
    "content": ".. index::\n   single: Getting Started\n\n.. highlight:: rst\n\n.. _gettingstarted:\n\nGetting Started\n=================\n\nOur recommended way to install a production ready EOS instance is using RPM configuration.\nA Kubernetes-based demonstrator is available for testing purposes.\n\nEOS installation with Kubernetes and Helm for test / demonstration\n-----------------------------------------------------------------\nThe `EOS Charts repository <https://gitlab.cern.ch/eos/eos-charts>`_ provides Helm charts for the deployment of EOS in Kubernetes for test and demonstration purposes.\nA working Kubernetes cluster (`v1.20.15` or newer) and the Helm package manager (`v3.8.0` or newer) are required.\n\nThe installation is fully automated via the `server` chart, which deploys 1 MGM, 3 QDB instances in cluster mode, and 4 FSTs.\n\n.. code-block:: bash\n\n  helm install eos oci://registry.cern.ch/eos/charts/server\n\nThe resulting cluster will consist of 8 pods:\n\n.. code-block:: bash\n\n  kubectl get pods\n  NAME        READY   STATUS    RESTARTS   AGE\n  eos-fst-0   1/1     Running   0          4m47s\n  eos-fst-1   1/1     Running   0          79s\n  eos-fst-2   1/1     Running   0          69s\n  eos-fst-3   1/1     Running   0          59s\n  eos-mgm-0   2/2     Running   0          4m47s\n  eos-qdb-0   1/1     Running   0          4m47s\n  eos-qdb-1   1/1     Running   0          2m21s\n  eos-qdb-2   1/1     Running   0          2m6s\n\n...and will be configured with relevant defaults to make it a fully-working instance:\n\n.. code-block:: bash\n\n  eos ns\n  # ------------------------------------------------------------------------------------\n  # Namespace Statistics\n  # ------------------------------------------------------------------------------------\n  ALL      Files                            5 [booted] (0s)\n  ALL      Directories                      11\n  ALL      Total boot time                  0 s\n  # ------------------------------------------------------------------------------------\n  ALL      Compactification                 status=off waitstart=0 interval=0 ratio-file=0.0:1 ratio-dir=0.0:1\n  # ------------------------------------------------------------------------------------\n  ALL      Replication                      mode=master-rw state=master-rw master=eos-mgm-0.eos-mgm.default.svc.cluster.local configdir=/var/eos/config/ config=default\n  # ------------------------------------------------------------------------------------\n  {...cut...}\n\n  eos fs ls\n  ┌───────────────────────────────────────────┬────┬──────┬────────────────────────────────┬────────────────┬────────────────┬────────────┬──────────────┬────────────┬────────┬────────────────┐\n  │host                                       │port│    id│                            path│      schedgroup│          geotag│        boot│  configstatus│       drain│  active│          health│\n  └───────────────────────────────────────────┴────┴──────┴────────────────────────────────┴────────────────┴────────────────┴────────────┴──────────────┴────────────┴────────┴────────────────┘\n   eos-fst-0.eos-fst.default.svc.cluster.local 1095      1                     /fst_storage        default.0      docker::k8s       booted             rw      nodrain   online              N/A\n   eos-fst-1.eos-fst.default.svc.cluster.local 1095      2                     /fst_storage        default.1      docker::k8s       booted             rw      nodrain   online              N/A\n   eos-fst-2.eos-fst.default.svc.cluster.local 1095      3                     /fst_storage        default.2      docker::k8s       booted             rw      nodrain   online              N/A\n   eos-fst-3.eos-fst.default.svc.cluster.local 1095      4                     /fst_storage        default.3      docker::k8s       booted             rw      nodrain   online              N/A\n\nEOS up in few minutes\n---------------------\nWe will start setting up a complete EOS installation on a single physical machine using the EOS5 configuration method. Later we will demonstrate the steps to add high-availability to MGMs/QDBs and how to scale-out FST nodes. All commands have to be issued using the `root` account!\n\nGrab a machine preferably with Alma9 (or Alma8 or CentOS7). Only the repository setup differs for these platforms (shown is the Alma9 installation, just replace 9 with 7,8 in the URLs :\n\nInstallation\n------------\n\n.. code-block:: bash\n\n  dnf config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/testing/el-9/x86_64/\"\n  dnf config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-9/x86_64/\"\n  dnf install -y eos-server eos-quarkdb eos-fusex --nogpgcheck\n\nUnique Instance Shared Secret\n-----------------------------\n\nEvery instance should run with a unique instance-private shared secret.\nThis can be easily created using:\n\n.. code-block:: bash\n\n  eos daemon sss recreate\n\nThe command will create a local file `/etc/eos.keytab` storing the instance-specific shared secret needed for MGM,FST,MQ (and additionally a shared secret `/etc/eos/fuse.sss.keytab` useful for clients when doing FUSE mounts in combination with EOS token).\n\nStart Services\n--------------\n\nWe will startup three services in a manual way to get a better understanding about the procedure and the used configuration.\n\nTo shorten the setup we disable the firewall for the moment. The ports to open in the firewall are explained later.\n\n.. code-block:: bash\n\n  systemctl stop firewalld\n\n\n.. note::\n\n  After each `daemon run` the shell should hang with the daemon in foreground. If the startup fails, the process will exit. If the startup is successful use `Control-Z `and type `bg` to put the process in the background and continue with the next service until all four have been started.\n\n.. code-block:: bash\n\n  # start QuarkDB on this host\n  eos daemon run qdb\n  # start MGM on this host\n  eos daemon run mgm\n  # all this host to connect as an FST\n  eos node set `hostname -f`:1095 on\n  # start FST on this host\n  eos daemon run fst\n\n\n.. note::\n\n  Each command prints commands executed during the daemon initialization phase and the XRootD configuration file used. In reality each EOS service is an XRootD server process with dedicated plug-in and configuration. The init phases have been designed to be able to startup a service without doing ANY customized configuration bringing good defaults.\n\nYou should be able to see the running daemons doing:\n\nThe production way to do this is to run\n\n.. code-block:: bash\n\n  # start QuarkDB\n  systemctl start eos5-qdb@qdb\n  # start MGM\n  systemctl start eos5-mgm@mgm\n  # allow this host connect as an FST\n  eos node set `hostname -f`:1095 on\n  # start FST on this host\n  systemctl start eos5-fst@fst\n\nand to enable the services in the boot procedure\n\n.. code-block:: bash\n\n  systemctl enable eos5-qdb@qdb\n  systemctl enable eos5-mgm@mgm\n  systemctl enable eos5-fst@fst\n\n.. code-block:: bash\n\n  ps aux | grep eos\n\n\nUsing the CLI\n-------------\n\nYour EOS installation is now up and running. We are now starting the CLI to inspect and configure our EOS instance:\n\n.. code-block:: bash\n\n  [root@vm root]# eos version\n  EOS_INSTANCE=eosdev\n  EOS_SERVER_VERSION=5.2.5 EOS_SERVER_RELEASE=5.2.5\n  EOS_CLIENT_VERSION=5.2.5 EOS_CLIENT_RELEASE=5.2.5\n\n  [root@vm root]# eos whoami\n  Virtual Identity: uid=0 (0,3,99) gid=0 (0,4,99) [authz:sss] sudo* host=localhost domain=localdomain\n\nYou can navigate the namespace using well known commands:\n\n.. code-block:: bash\n\n  [root@vm root]# eos ls -la /eos/\n  drwxrwx--x   1 root     root            23249 Jan  1  1970 .\n  drwxr-x--x   1 root     root                0 Jan  1  1970 ..\n  drwxrwx--x   1 root     root            23249 Aug 18 17:28 dev\n\n\nThe default EOS instance name is *eosdev* and in every EOS instance you will the find the pre-created directory structure like shown:\n\n.. code-block:: bash\n\n  [root@vm root]# eos find -d /eos/\n  path=/eos/\n  path=/eos/dev/\n  path=/eos/dev/proc/\n  path=/eos/dev/proc/archive/\n  path=/eos/dev/proc/clone/\n  path=/eos/dev/proc/conversion/\n  path=/eos/dev/proc/recycle/\n  path=/eos/dev/proc/tape-rest-api/\n  path=/eos/dev/proc/tape-rest-api/bulkrequests/\n  path=/eos/dev/proc/tape-rest-api/bulkrequests/evict/\n  path=/eos/dev/proc/tape-rest-api/bulkrequests/stage/\n  path=/eos/dev/proc/token/\n  path=/eos/dev/proc/tracker/\n  path=/eos/dev/proc/workflow/\n\nAll EOS instance names have to start with *eos* prefix (eosxyz). If you configure your EOS instance to have name **eosfoo** you will see an automatic structure created during MGM startup which looks like this:\n\n.. code-block:: bash\n\n  /eos/\n  /eos/foo/\n  ...\n\n\nAdding Storage Space\n---------------------\n\nThe first thing we do is to create the `default` space, which will host all our filesystems:\n\n.. code-block:: bash\n\n  eos space define default\n\n\nNow we want to attach local disk space to our EOS instance into the `default` space . In this example we will register six filesystems to our instance. The filesystems can be on a single or individual partitions.\n\n.. code-block:: bash\n\n  # create four directories to be used as separate EOS filesystems and own them with the `daemon` account\n  for name in 01 02 03 04 05 06; do\n      mkdir -p /data/fst/$name;\n  chown daemon:daemon /data/fst/$name\n  done\n\n\n.. code-block:: bash\n\n  # register all sub-directories under /data/fst as EOS filesystems\n  eosfstregister -r localhost /data/fst/ default:6\n\n\nThe `eosfstregister` command lists all directories under `/data/fst/` and assumes that is has to register 6 filesystem to the *default* space indicated by the parameter `default:6` (See `eosfstregister -h` for the command syntax) to the MGM running on `localhost`. Before filesystems are usable, they have to be owned by the `daemon` account.\n\nWe do now one additional step. By default EOS will place each filesystem from the same node to a separate placement group, so it will create 6 scheduling groups `default.0`, `default.1` ... `default.6` and place filesystem 1 in `default.0`, 2 into `default.1` aso ...\nTo write a file EOS selects a group and tries place the file into a single group. If you want now to write files with two replicas you have to have at least 2 filesystems per group, if you want to use erasure coding e.g. RAID6, you would need to have 6 filesystems per group. Therefore we now move all disks into the `default.0` group (disk 1 is already in group `default.0`):\n\n.. code-block:: bash\n\n  for name in 2 3 4 5 6; do eos fs mv --force $name default.0; done\n\n\nExploring EOS Views\n---------------------\n\nNow you are ready to check-out the four views EOS provides:\n\n.. code-block:: bash\n\n  eos space ls\n\n.. code-block:: bash\n\n  eos node ls\n\n\n.. code-block:: bash\n\n  eos group ls\n\n\n.. code-block:: bash\n\n  eos fs ls\n\n\nAll this commands take several additional output options to provide more information e.g. `eos space ls -l` or `eos space ls --io` ...\nYou will notice, that in all this views you either see `active=0` or `offline`.  This is because we have registered filesystems, but we didn't enable them yet.\n\nEnabling EOS Space\n---------------------\n\nThe last step before using our storage setup is to enable the default space:\n\n.. code-block:: bash\n\n  eos space set default on\n\nEnabling the space means to enable all nodes, groups and filesystems in that space.\n\nNow you can now see everything as `online` and `active` in the four views.\n\nRead and Write using CLI\n-------------------------\n\nWe can now upload and download our first file to our storage system. We will create a new directory and define a storage policy, to store files as single replica files (one copy):\n\n.. code-block:: bash\n\n   eos mkdir /eos/dev/test/                            #create directory\n   eos attr set default=replica /eos/dev/test/         #define default replication policy\n   eos attr set sys.forced.nstripes=1 /eos/dev/test/   #define to have one replica only\n   eos chmod 777 /eos/dev/test/                        #allow everybody to write here\n   eos cp /etc/passwd /eos/dev/test/                   #upload a test file\n   eos cp /eos/dev/test/passwd /tmp/passwd             #download the test file\n   diff /etc/passwd /tmp/passwd                        #compare with original file\n\n\nYou can list the directory where the file was stored:\n\n.. code-block:: bash\n\n   eos ls -l /eos/dev/test/\n\n\nand you can find out a lot information about this file e.g. the *adler32* checksum which was configured automatically doing `eos attr set default=replica /eos/dev/test` and the location of our file (on which filesystem the files has been stored):\n\n.. code-block:: bash\n\n  eos file info /eos/dev/test/passwd\n\n\nRead and Write using /eos/ mounts\n---------------------------------\n\nWe can FUSE mount our EOS instance on the same node by just doing:\n\n.. code-block:: bash\n\n  mkdir -p /eos/\n  # put your host.domain name in the command\n  eosxd -ofsname=host.domain:/eos/ /eos/\n\n\nAn alternative to running the *eosxd* executable is to use the FUSE mount type:\n\n.. code-block:: bash\n\n  mount -t fuse eosxd -ofsname=host.domain:/eos/ /eos/\n\n\nIn either way, you should be able to see the mount and the configured space using `df`:\n\n.. code-block:: bash\n\n  df /eos/\n\nAll the usual shell commands will now also work on the FUSE mount.\n\n.. note::\n\n  Be aware that the default FUSE mount does not map the current uid/gid to the same uid/gid inside EOS. Moreover *root* access is always squashed to uid,gid=99 (nobody).\n\nIn summary on this FUSE mount with default configuration on localhost you will be mapped to user *nobody* inside EOS. If you copy a file on this FUSE mount to `/eos/dev/test/` the file will be owned by `99/99`.\n\nFirewall Configuration for external Access\n------------------------------------------\n\nTo make your instance accessible from outside you have to make sure that all the relevant ports are open for incoming traffic.\n\nHere is a list of ports used by the various services:\n\n+----------------+------+\n| Service        | Port |\n+================+======+\n| MGM (XRootD)   | 1094 |\n+----------------+------+\n| MGM (FUSE ZMQ) | 1100 |\n+----------------+------+\n| FST (XRootD)   | 1095 |\n+----------------+------+\n| QDB (REDIS)    | 7777 |\n+----------------+------+\n\nIf port 1100 is not open, FUSE access still works, but FUSE clients are not displayed as being online and they don't receive callbacks for meta-data changes e.g. changes made on another client are not immediately visible.\n\n.. code-block:: bash\n\n  systemctl start firewalld\n  for port in 1094 1095 1097 1100 7777; do\n   firewall-cmd --zone=public --permanent --add-port=$port/tcp\n  done\n\n\nSingle Node Quick Setup Code Snippet\n------------------------------------\n\n.. code-block:: bash\n\n  yum-config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/testing/el-9s/x86_64/\"\n  yum-config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-9s/x86_64/\"\n  yum install -y eos-server eos-quarkdb eos-fusex --nogpgcheck\n\n  systemctl start firewalld\n  for port in 1094 1095 1100 7777; do\n    firewall-cmd --zone=public --permanent --add-port=$port/tcp\n  done\n\n  eos daemon sss recreate\n\n  systemctl start eos5-qdb@qdb\n  systemctl start eos5-mgm@mgm\n  eos node set `hostname -f`:1095 on\n  systemctl start eos5-fst@fst\n\n  sleep 30\n\n  for name in 01 02 03 04 05 06; do\n    mkdir -p /data/fst/$name;\n    chown daemon:daemon /data/fst/$name\n  done\n\n  eos space define default\n\n  eosfstregister -r localhost /data/fst/ default:6\n\n  for name in 2 3 4 5 6; do eos fs mv --force $name default.0; done\n\n  eos space set default on\n\n  eos mkdir /eos/dev/rep-2/\n  eos mkdir /eos/dev/ec-42/\n  eos attr set default=replica /eos/dev/rep-2 /\n  eos attr set default=raid6 /eos/dev/ec-42/\n  eos chmod 777 /eos/dev/rep-2/\n  eos chmod 777 /eos/dev/ec-42/\n\n  mkdir -p /eos/\n  eosxd -ofsname=`hostname -f`:/eos/ /eos/\n\n\nAdding FSTs to a single node setup\n----------------------------------\n\n.. code-block:: bash\n\n  yum-config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/testing/el-9s/x86_64/\"\n  yum-config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-9s/x86_64/\"\n  yum install -y eos-server --nogpgcheck\n\n  systemctl start firewalld\n  for port in 1095; do\n    firewall-cmd --zone=public --permanent --add-port=$port/tcp\n  done\n\n  # On the FST node configure MGM node in /etc/config/eos/generic/all\n  SERVER_HOST=mgmnode.domain\n\n  # Copy /etc/eos.keytab from MGM node to the new FST node to /etc/eos.keytab\n  scp root@mgmnode.domain:/etc/eos.keytab /etc/eos.keytab\n\n  # Allow the new FST on the MGM to connect as an FST\n  @mgm: eos node set fstnode.domain:1095 on\n\n  # Start FST service\n  systemctl start eos5-fst@fst\n  systemctl enable eos5-fst@fst\n\n  # Verify Node online\n  @mgm: eos node ls\n\n\nExpanding single node MGM/QDB setup to HA cluster\n-------------------------------------------------\nIn a production environment we need to have QDB and MGM service high-available. We will show here, how to configure three co-located QDB+MGM nodes.The three nodes are called in the example `node1.domain` `node2.domain` `node3.domain`. We assume you running mgm is node1 and new nodes are node2 and node3.\n\n.. code-block:: bash\n\n  yum-config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/testing/el-9s/x86_64/\"\n  yum-config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-9s/x86_64/\"\n  yum install -y eos-server eos-quarkdb eos-fusex --nogpgcheck\n\n  systemctl start firewalld\n  for port in 1094 1100 7777; do\n   firewall-cmd --zone=public --permanent --add-port=$port/tcp\n  done\n\n  # Copy /etc/eos.keytab from MGM node to the new MGM nodes to /etc/eos.keytab\n  scp root@node1:/etc/eos.keytab /etc/eos.keytab\n\n  # Create observer QDB nodes on node2 and node3\n  eos daemon config qdb qdb new observer\n\n  # Start QDB on node2 and node3\n  systemctl start eos5-qdb@qdb\n  systemctl enable eos5-qdb@qdb\n\n  # Allow node2 & node3 as follower on node 1\n  @node1: redis-cli -p 7777\n  @node1: 127.0.0.1:7777> raft-add-observer node2.domain:7777\n  @node1: 127.0.0.1:7777> raft-add-observer node3.domain:7777\n\n  # ( this is equivalent to 'eos daemon config qdb qdb add node2.domain:7777' but broken in the release version )\n\n  # node2 & node3 get contacted by node1 and start syncing the raft log\n\n  # Promote node2 and node3 as full members\n  @node1: redis-cli -p 7777\n  @node1: 127.0.0.1:7777> raft-promote-observer node2.domain:7777\n  @node1: 127.0.0.1:7777> raft-promote-observer node3.domain:7777\n\n  # ( this is equivalent to 'eos daemon config qdb qdb promote node2.domain:7777 )\n\n  # Verify RAFT status on any QDB node\n  redis-cli -p 777\n  127.0.0.1:7777> raft-info\n\n  # ( this is equivalent to 'eos daemon config qdb qdb info' )\n\n  # Startup MGM services\n  @node2: systemctl start eos5-mgm@mgm\n  @node3: systemctl start eos5-mgm@mgm\n\n  # You can connect on each node using the eos command to the local MGM\n  @node1:  eos ns | grep master\n  ALL      Replication                      is_master=true master_id=node1.domain:1094\n  @node2:  eos ns | grep master\n  ALL      Replication                      is_master=false master_id=node1.domain:1094\n  @node3:  eos ns | grep master\n  ALL      Replication                      is_master=false master_id=node1.domain:1094\n\n  # You can force the QDB leader to a given node e.g.\n  @node2: eos daemon config qdb qdb coup\n\n  # you can force the active MGM to run on a given node by running on the current active MGM:\n  @node1: eos ns master node2.domain:1094\n  success: current master will step down\n\nThree Node Quick Setup Code Snippet\n-----------------------------------\n\nYou can also setup a three node cluster from scratch right from the beginning, which is shown here:\n\n.. code-block:: bash\n\n  # on all three nodes do\n  killall -9 xrootd     # make sure no daemons are running\n  rm -rf /var/lib/qdb/  # wipe previous QDB database\n\n  yum-config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/testing/el-9/x86_64/\"\n  yum-config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-9/x86_64/\"\n  yum install -y eos-server eos-quarkdb eos-fusex --nogpgcheck\n\n  systemctl start firewalld\n  for port in 1094 1095 1097 1100 7777; do\n   firewall-cmd --zone=public --permanent --add-port=$port/tcp\n  done\n\n\nNow edit `/etc/eos/config/qdb/qdb` and change the variable definition with your QDB nodes:\n\n.. code-block:: bash\n\n  QDB_NODES=${QDB_HOST}:${QDB_PORT}\n\nto\n\n.. code-block:: bash\n\n  QDB_NODES=node1.cern.ch:7777,node2.cern.ch:7777,node3.cern.ch:7777\n\n\nCreate new instance sss keys on one node and copy them to the other two nodes:\n\n.. code-block:: bash\n\n  # node 1\n  eos daemon sss recreate\n  # copy to node2,node3\n  scp /etc/eos.keytab root@node2:/etc/eos.keytab\n  scp /eos/eos/fuse.sss.keytab root@node2:/etc/eos.keytab\n  scp /etc/eos.keytab root@node3:/etc/eos.keytab\n  scp /eos/eos/fuse.sss.keytab root@node3:/etc/eos.keytab\n\n\nNow start QDB on all three nodes:\n\n.. code-block:: bash\n\n  systemctl start eos5-qdb@qdb\n\n\nNow you can inspect the RAFT state on all QDBs:\n\n.. code-block:: bash\n\n  eos daemon config qdb qdb info\n\n  1) TERM 1\n  2) LOG-START 0\n  3) LOG-SIZE 2\n  4) LEADER node2.cern.ch:7777\n  5) CLUSTER-ID eosdev\n  6) COMMIT-INDEX 1\n  7) LAST-APPLIED 1\n  8) BLOCKED-WRITES 0\n  9) LAST-STATE-CHANGE 293 (4 minutes, 53 seconds)\n  10) ----------\n  11) MYSELF node2.domain:7777\n  12) VERSION 5.1..5.1.3\n  13) STATUS LEADER\n  14) NODE-HEALTH GREEN\n  15) JOURNAL-FSYNC-POLICY sync-important-updates\n  16) ----------\n  17) MEMBERSHIP-EPOCH 0\n  18) NODES node1.domain:7777,node2.domaina:7777,node3.domain:7777\n  19) OBSERVERS\n  20) QUORUM-SIZE 2\n\n\n\nHere you see that the current LEADER is node2.domain.  If you want to force that node1.cern.ch becomes a leader you can type on node1:\n\n.. code-block:: bash\n\n  [root@node1 ] eos daemon config qdb qdb coup\n\nand verify using\n\n.. code-block:: bash\n\n  eos daemon config qdb qdb info\n\nwho the new LEADER is.\n\nNow we start `mgm` on all three nodes:\n\n.. code-block:: bash\n\n  [root@node1] systemctl start eos5-mgm@mgm\n  [root@node2] systemctl start eos5-mgm@mgm\n  [root@node3] systemctl start eos5-mgm@mgm\n\nYou can connect to the MGM on each node.\n\n.. code-block:: bash\n\n  [root@node1] eos ns | grep Replication\n  ALL      Replication                      is_master=true master_id=node1.cern.ch:1094\n  [root@node2] eos ns | grep Replication\n  ALL      Replication                      is_master=false master_id=node1.cern.ch:1094\n  [root@node3] eos ns | grep Replication\n  ALL      Replication                      is_master=false master_id=node1.cern.ch:1094\n\n\nThe three MGMs use a lease mechanism to acquire the active role. If you want to push manually the active role from `node1` to `node2`, you do:\n\n.. code-block:: bash\n\n  [root@node1 ] eos ns master node2.cern.ch\n\nWhen the default lease time expired, the master should change:\n\n.. code-block:: bash\n\n  [root@node1] eos ns | grep Replication\n  ALL      Replication                      is_master=false master_id=node1.cern.ch:1094\n  [root@node2] eos ns | grep Replication\n  ALL      Replication                      is_master=true master_id=node1.cern.ch:1094\n  [root@node2] eos ns | grep Replication\n  ALL      Replication                      is_master=false master_id=node1.cern.ch:1094\n\n\n.. note::\n\n  Sometimes you might observe changes of the master under heavy load. If this happens too frequently you can increase the lease time modifying the `sysconfig` variable of the mgm daemon e.g. to set other 120s lease time (instead of default 60s) you define:\n\n  .. code-block:: bash\n\n    EOS_QDB_MASTER_LEASE_MS=\"120000\"\n\n\nJoining a node to a QDB raft cluster\n------------------------------------\n\nThe procedure to add the node foo.bar:7777 to a QDB cluster is straight-forward. *QDB_PATH* has to be not existing and the *QDB* service will be down in any case when running this command on the new/unconfigured node:\n\n.. code-block:: bash\n\n  eos daemon config qdb qdb new observer\n\nTo get the node join as a member you run three commands\nOn the new node:\n\n.. code-block:: bash\n\n  1 [ @newnode ] : systemctl start eos5-qdb@qdb\n\n\n=> `eos daemon config qdb qdb info` will not show the new node as an observer. The *QDB* logfile `/var/log/eos/qdb/xrdlog.qdb` will say, this new *QDB* node is still in limbo state and needs to be added!\n\nOn the elected leader node:\n\n.. code-block:: bash\n\n  2 [ @leader  ] : eos daemon config qdb qdb add foo.bar:7777\n\n=> `eos daemon config qdb qdb info` will show the new node as replicating and LAGGING until the synchronization is complete and status will be UP-TO-DATE. The new node is not yet a member of the cluster quorum.\n\nOn the elected leader node:\n\n.. code-block:: bash\n\n  3 [ @leader  ] : eos daemon config qdb qdb promote foo.bar\n\n=> `eos daemon config qdb qdb info` will show the new node as a member of the cluster under NODES.\n\n# 5 Replacing/Removing a node in a 3 node QDB setup\nTo replace or remove node foo.bar:7777 one needs only two to three steps:\nShutdown *QDB* on that node:\n\n.. code-block:: bash\n\n  1 [ @drainnode ] : systemctl stop eos5-qdb@qdb\n\nRemove that node on the leader from the membership:\n\n.. code-block:: bash\n\n  2 [ @leader    ] : eos daemon config qdb qdb remove foo.bar:7777\n\nOptionally delete *QDB* database files on foo.bar:7777 (don't run this on the LEADER !!!!):\n\n.. code-block:: bash\n\n  3 [ @drainnode ] : rm -rf /var/lib/qdb/\n\nNow run the _join_ procedure from the previous section on the node, which should replace the decommissioned member!\n\nBackup your QDB database\n------------------------\n\n\n.. code-block:: bash\n\n  eos daemon config qdb qdb backup\n\n.. Add features using Configuration modules\n.. -----------------------------------------\n\n.. ## With 5.2 Release\n\n\n.. ### Enable http access with a configuration module\n.. This will configure your instance to provide http(s) access.\n\n.. .. code-block:: bash\n\n..   echo \"http\" >> /etc/eos/config/mgm/mgm.modules\n..   eos daemon module-init mgm\n..   systemctl restart eos5-mgm@mgm\n\n\n.. ### Enable alice configuration/access with a configuration module\n.. This will configure your instance to become an ALICE SE with all required settings and namespace setup.\n\n.. .. code-block:: bash\n\n..   echo \"alice\" >> /etc/eos/config/mgm/mgm.modules\n..   eos daemon module-init mgm\n..   systemctl restart eos5-mgm@mgm\n\n\n.. ### Add Kerberos token authentication to your instance with a configuration module\n.. This will add kerberos5 authentication to your instance.\n\n.. .. code-block:: bash\n\n..   echo \"krb5\" >> /etc/eos/config/mgm/mgm.modules\n..   eos daemon module-init mgm\n..   systemctl restart eos5-mgm@mgm\n\n\n.. ### Add GSI proxy authentication to your instance with a configuration module\n.. This will enable GSI authentication with certificates and proxies on your instance.\n\n.. .. code-block:: bash\n\n..   echo \"gsi\" >> /etc/eos/config/mgm/mgm.modules\n..   eos daemon module-init mgm\n..   systemctl restart eos5-mgm@mgm\n"
  },
  {
    "path": "doc/diopside/manual/hardware-installation.rst",
    "content": ".. index::\n   single: Hardware\n\n.. highlight:: rst\n\n.. _hardwareinstallation:\n\nHardware Requirements\n=====================\n\n.. image:: cerncc.jpg\n   :align: center\n   :width: 800px\n\n.. index::\n   pair: Hardware; MGM\n\nMGM Node\n---------\nMGM run heavily multithreaded code. It can be run on a single CPU core, we recommend at least 6-8 CPU cores for a production system. The memory consumption is tunable via thread pool settings and the number of cached namespace entries. The absolute minimum is 8 GB, we recommend at least 32 GB. The disk requirements for an MGM node are to store log files and file access reports. The required size depends essentially on the usage pattern of your EOS instance. HDDs are ok, however we experienced in production systems, that it is better to use SSDs since they don't lead to hick-ups and D state processes during log-rotation.\n\n+-------------+----------+--------+----------------------+\n| Type        |  CPU     | MEMORY |  DISK                |\n+=============+==========+========+======================+\n| Minimum     | 1 core   | 8 GB   | 8 GB HDD             |  \n+-------------+----------+--------+----------------------+\n| Recommended | 6-8 core | 32 GB  | 128 GB HDD           |\n+-------------+----------+--------+----------------------+\n| Performance | 32 core  | 128 GB | 512 GB SSD           |\n+-------------+----------+--------+----------------------+\n\nExample configurations from large CERN deployments:\n\n.. code-block:: \n\n   Intel(R) Xeon(R) Silver 4216 CPU @ 2.10GHz (32 core), 386 GB RAM (2933 MHz DDR4), 1 TB /var partition\n\n.. index::\n   pair: Hardware; QDB\n\nQDB Node\n--------\n\nQDB nodes run RocksDB as a KV store. A QDB node requires IOPS for a cold start, so it should be deployed using SSDs/NVMEs. A rule of thumb is to calculate 0.1-0.2 GB of space per 1M namespace entries e.g. a namespace with up to 1 Billion entries should have 100-200GB of disk space.\nTo run backups there is not much extra space needed. The memory footprint of the QDB daemon is tiny. RAM on a QDB node is useful to cache RocksDB files (RAFT journals) in the buffer cache. Ideally you have as much memory as the maximum expected volume of QDB. \n\n+-------------+----------+--------+----------------------+\n|             | CPU      | MEMORY | DISK                 |\n+=============+==========+========+======================+\n| Minimum     | 1 core   | 8 GB   | 0.1-0.2  GB/Million  |\n+-------------+----------+--------+----------------------+\n| Performance | 4 core   | DB size| 0.1-0.2  GB/Million  |\n+-------------+----------+--------+----------------------+\n\nTo save resources you can run MGM and QDB nodes co-located (both daemons on one node) taking into account the disk and memory requirements of both daemons.\n\nExample configurations from large CERN deployments:\n\n.. code-block:: \n\n   Intel(R) Xeon(R) Silver 4216 CPU @ 2.10GHz (32 core), 386 GB RAM (2933 MHz DDR4), 2x 1.8 TB /var partition (INTEL SSDSC2KB01)\n\n\n.. index::\n   pair: Hardware; FST\n\nFST Node\n--------\n\nDiskspace in EOS is provided via mounted filesystems on FST nodes. The requirements to the mounted filesystem is to have **extended attribute support**! **atime** can be disabled.\n\n### Supported Back-end Filesystems\nExamples of filesystems suitable as storage volumes:\n\n* **XFS** on **JBOD** HDD (physical drive)\n* **XFS** on **RAID** array (hardware or software RAID)\n* **XFS** on **RBD** (virtual drive)\n* **CephFS**, **Lustre**, **GPFS**\n\n**ZFS** is possible, but **not recommended** due to worse random IO performance.\n\nFor installations with very few physical nodes, we recommend to use a conventional **RAID6** configuration. \n\nIf there is a sufficient number of nodes available (8), we recommend to use **EOS erasure coding** to provide high-availability and durability of data. \n\nIf you run EOS in an **OpenStack** environment, you might use virtual drives or a distributed filesystem underneath. Be aware, that virtualized setups might show unexpected intrinsic IO bottlenecks (IOPS,Bandwidth).\n\nHardware\n~~~~~~~~\n\nFor JBOD storage we recommend  1GB of RAM per disk. The memory serves mainly to improve performance using the LINUX buffer cache.\n\nThe CPU usage on FST nodes is low, if replication is used. For erasure coding a modern CPU with SIMD extension should be available.\n\nExample configuration of a **small FST** with replication and JBODs:\n\n.. code-block:: \n\n   Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz, 128 GB RAM (2400 MHz, DDR4), 48x TOSHIBA MG04ACA6(6TB)\n\nExample configuration of a **large FST** for erasure-coding and JBODs:\n\n.. code-block:: \n\n   2x AMD EPYC 7302 16-Core Processor, 128 GB RAM (3200 MHz, DDR4), 96x TOSHIBA MG07ACA1 (14TB)\n\n\n\n.. index::\n   single: Installation\n\nInstallation\n============\nEOS is usually installed from package repositories. For installation from sources, follow :ref:`Develop`.\n\nSupported Platforms\n-------------------\n\nEOS for Client+Server\n~~~~~~~~~~~~~~~~~~~~~\n\nThe following platforms are supported to run EOS client and server:\n* CentOS 7\n* Alma 8\n* Alma 9\n* Fedora Core 38\n\nRPM Repository Configuration\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nYou have to configure a dependency repository and either the tag or commit repository (master branch).\n\n.. figure:: yum.jpg\n   :align: center\n   :figwidth: 480px\n\n.. index::\n   pair: YUM; Packages\n\n\n**Dependency Repository for Tag and Commit Releases**\n\n+-----------------+--------------------------------------------------------------------------------------------------------------------+\n| Platform        | Setup                                                                                                              |\n+=================+====================================================================================================================+\n| CentOS7         | ``yum-config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-7/x86_64/\"``     |\n+-----------------+--------------------------------------------------------------------------------------------------------------------+\n| Alma 8          | ``yum-config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-8/x86_64/\"``     |\n+-----------------+--------------------------------------------------------------------------------------------------------------------+\n| Alma 9          | ``yum-config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-9/x86_64/\"``     |\n+-----------------+--------------------------------------------------------------------------------------------------------------------+\n| Fedora 38       | https://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/fc-38/                                               |\n+-----------------+--------------------------------------------------------------------------------------------------------------------+\n\n**Tag Releases**\n\n+-----------------+-----------------------------------------------------------------------------------------------------------------------+\n| Platform        |  Setup                                                                                                                |\n+=================+=======================================================================================================================+\n| CentOS7         | ``yum-config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/testing/el-7/x86_64/\"``   |\n+-----------------+-----------------------------------------------------------------------------------------------------------------------+\n| Alma 8          | ``yum-config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/testing/el-8/x86_64/\"``   |\n+-----------------+-----------------------------------------------------------------------------------------------------------------------+\n| Alma 9          | ``yum-config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/testing/el-9/x86_64/\"``   |\n|                 |                                                                                                                       |\n+-----------------+-----------------------------------------------------------------------------------------------------------------------+\n| Fedora 38       | https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/testing/fc-38/x86_64/                                      |\n+-----------------+-----------------------------------------------------------------------------------------------------------------------+\n\n\n**Commit Releases**\n\n+-----------------+--------------------------------------------------------------------------------------------------------------------+\n| Platform        |  Setup                                                                                                             |\n+=================+====================================================================================================================+\n| CentOS7         | ``yum-config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside/commit/el-7/x86_64/\"``     |\n+-----------------+--------------------------------------------------------------------------------------------------------------------+\n| Alma 8          | ``yum-config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside/commit/el-8/x86_64/\"``     |\n+-----------------+--------------------------------------------------------------------------------------------------------------------+\n| Alma 9          | ``yum-config-manager --add-repo \"https://storage-ci.web.cern.ch/storage-ci/eos/diopside/commit/el-9/x86_64/\"``     |\n+-----------------+--------------------------------------------------------------------------------------------------------------------+\n| Fedora 38       | https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/testing/fc-38/x86_64/                                   |\n+-----------------+--------------------------------------------------------------------------------------------------------------------+\n\nClient+FUSE RPM Installation\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: bash\n\n   yum install eos-client eos-fusex\n\nServer RPM Installation\n~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: bash\n\n   yum install eos-server\n\n"
  },
  {
    "path": "doc/diopside/manual/index.rst",
    "content": ".. index::\n   single: Manual\n\n.. highlight:: rst\n\n.. _manual:\n\n=================\nManual\n=================\n\n.. toctree::\n   :maxdepth: 2\n   :caption: Contents:\n\n   hardware-installation\n   getting-started\n   configuration\n   interfaces\n   microservices\n   protocols\n   using\n   formats\n   develop\n   egi\n"
  },
  {
    "path": "doc/diopside/manual/interfaces.rst",
    "content": ".. index::\n   single: Interfaces\n\n.. highlight:: rst\n\n.. _interfaces:\n\nInterfaces\n===========\n\n\n.. index::\n   pair: Interfaces; Namespace\n   pair: CLI; eos ns\n\nNamespace Interface\n---------------------\n\nThe namespace interface `eos ns` has the main purpose to provide status and performance information about the namespace.\n\nIt provides optional sub-commands `[stat|mutex|compact|master|cache]` :\n\n* `eos ns stat` - adds namespace rate, counter and execution time measurements for individual functions executed in the namespace\n* `eos ns mutex` - allows to display or modify mutex contention measurements, deadlock and order violation detection\n* `eos ns compact` - this command is deprecated since EOS5 and has no functionality anymore\n* `eos ns master` - allows to inspect the logging of an active/passive MGM and state transitions\n* `eos ns cache` - allows to change the in-memory cache settings for files and directories\n\n.. index::\n   pair: Namespace; Display\n\n4.1.1 Display\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nHere you see a typical output of an `eos ns` command:\n\n .. code-block:: bash\n\n    # ------------------------------------------------------------------------------------\n    # Namespace Statistics\n    # ------------------------------------------------------------------------------------\n    # Namespace Statistics\n    # ------------------------------------------------------------------------------------\n    ALL      Files                            100000 [booted] (0s)\n    ALL      Directories                      66637\n    ALL      Total boot time                  0 s\n    ALL      Contention                       write: 0.00 % read:0.00 %\n    # ------------------------------------------------------------------------------------\n    ALL      Replication                      is_master=true master_id=mgm.cern.ch:1094\n    # ------------------------------------------------------------------------------------\n    ALL      files created since boot         6\n    ALL      container created since boot     0\n    # ------------------------------------------------------------------------------------\n    ALL      current file id                  22\n    ALL      current container id             322347\n    # ------------------------------------------------------------------------------------\n    ALL      eosxd caps                       0 c: 0 cc: 0 cic: 0 ic: 0\n    ALL      eosxd clients                    1\n    ALL      eosxd active clients             0\n    ALL      eosxd locked clients             0\n    # ------------------------------------------------------------------------------------\n    ALL      File cache max num               40000000\n    ALL      File cache occupancy             13\n    ALL      In-flight FileMD                 0\n    ALL      Container cache max num          5000000\n    ALL      Container cache occupancy        16\n    ALL      In-flight ContainerMD            0\n    # ------------------------------------------------------------------------------------\n    ALL      eosViewRWMutex peak-latency      0ms (last) 0ms (1 min) 0ms (2 min) 0ms (5 min)\n    # ------------------------------------------------------------------------------------\n    ALL      QClient overall RTT              0ms (min)  0ms (avg)  18ms (max)\n    ALL      QClient recent peak RTT          1ms (1 min) 1ms (2 min) 1ms (5 min)\n    # ------------------------------------------------------------------------------------\n    ALL      memory virtual                   3.75 GB\n    ALL      memory resident                  195.15 MB\n    ALL      memory share                     48.03 MB\n    ALL      memory growths                   1.48 GB\n    ALL      threads                          379\n    ALL      fds                              428\n    ALL      uptime                           59205\n    # ------------------------------------------------------------------------------------\n    ALL      drain info                       pool=drain          min=10  max=100  size=10   queue_sz=0\n    ALL      fsck info                        pool=fsck           min=2   max=20   size=2    queue_sz=0\n    ALL      converter info                   pool=converter      min=8   max=100  size=8    queue_sz=0\n    ALL      balancer info                    space=default\n    # ------------------------------------------------------------------------------------\n    ALL      tracker info                     tracker=balance size=413\n    ALL      tracker info                     tracker=convert size=0\n    ALL      tracker info                     tracker=drain size=22\n    # ------------------------------------------------------------------------------------\n\n\nWe will now discuss every section and their meaning.\n\n.. index::\n   pair: Namespace; Statistics\n   pair: Namespace; Contention\n\n**Main Section**\n .. code-block:: bash\n\n    # ------------------------------------------------------------------------------------\n    ALL      Files                            100000 [booted] (0s)\n    ALL      Directories                      66637\n    ALL      Total boot time                  0 s\n    ALL      Contention                       write: 0.00 % read:0.00 %\n    # ------------------------------------------------------------------------------------\n\n**Files**\n\nThe `Files` line shows there are 100k files in this namespace and the namespace is up (booted). The time in brackets is the time it took to bring the file namespace up. The part of the boot procedure bringing up the view on all files contains only a very short safety check to see if the stored next free file identifier is correct e.g. there are no higher file ids stored in the namespace already. It is important that the the next free file identifier is correct because otherwise there is a risk to overwrite existing files when new files are created.\n\n**Directories**\n\nThe `Directories` line shows the number of directories currently stored in the namespace.\n\n**Total boot time**\nTotal boot time is the total time to get the namespace into operational state (0s).\n\n**Contention**\nContention expresses the ratio between wait time to obtain the namespace lock for `read` or `write` and the usage time of the namespace lock for `read` or `write` in percent. 100% means, that the average wait time to obtain a mutex is identical to the average time it is used afterwards.\n\n**Replication Section**\n .. code-block:: bash\n\n    # ------------------------------------------------------------------------------------\n    ALL      Replication                      is_master=true master_id=mgm.cern.ch:1094\n    # ------------------------------------------------------------------------------------\n\nThis fields describe if this MGM is currently the master `is_master=true` and output the URL of the currently active MGM node `master_id=mgm.cern.ch:1094`\n\n**Creation Section**\n .. code-block:: bash\n\n    # ------------------------------------------------------------------------------------\n    ALL      files created since boot         6\n    ALL      container created since boot     0\n    # ------------------------------------------------------------------------------------\n    ALL      current file id                  22\n    ALL      current container id             322347\n    # ------------------------------------------------------------------------------------\n\nThese fields are self-explaining: number of files and container (directories) created since the MGM started up. The current IDs are the internal IDs for the next created file/directory. With every creation these number is incremented. IDs are never reused!\n\n.. index::\n   pair: Namespace; FUSE Statistics\n\n**FUSE section**\n .. code-block:: bash\n\n    # ------------------------------------------------------------------------------------\n    ALL      eosxd caps                       0 c: 0 cc: 0 cic: 0 ic: 0\n    ALL      eosxd clients                    1\n    ALL      eosxd active clients             0\n    ALL      eosxd locked clients             0\n    # ------------------------------------------------------------------------------------\n\nThese section describes the situation of FUSE client access.\n*eosxd caps*\n\nThe first line `eosxd caps` is low-level information, which you can ignore in most cases. A `cap` is a per-inode subscription to receive callbacks for remote changes. E.g. if client A appends to file X and client B is using the same file, it needs to receive an update about the changes on file X. The first number (0) is the total number of indodes have currently a subscription. The `c` field is the size of the lookup table to find all caps for a given client (this should be equivalent to the number of clients having any `cap`). The `cic` field is the size of the lookup table to find all inodes with a `cap` for a given client (this should be equivalent to the number of clients having any `cap`). The `ic` field is the size of the lookup table from `inode` to `cap` (this should be equivalent to the number of inodes having a `cap`).\n\n*eosxd clients*\n\nThis is the total number of FUSE clients, which are currently connected to this MGM.\n\n*eosxd active clients*\n\nThis is the total number of FUSE clients, which were active during the last 5 minutes.\n\n*eosxd locked clients*\n\nThis is the total number of FUSE clients, which have any operation which is pending more than 5 minutes. Normally this can indicate either that an access for a given user is stalled, IO is blocked for a given file or a client is dead-locked due to a bug.\n\n.. index::\n   pair: Namespace; Cache Statistics\n\n**Cache Section**\n .. code-block:: bash\n\n    # ------------------------------------------------------------------------------------\n    ALL      File cache max num               40000000\n    ALL      File cache occupancy             13\n    ALL      In-flight FileMD                 0\n    ALL      Container cache max num          5000000\n    ALL      Container cache occupancy        16\n    ALL      In-flight ContainerMD            0\n    # ------------------------------------------------------------------------------------\n\n\nThe cache section shows the current setting for the maximum number of files and container (directories) in the cache `File cache max num` (number of files) + `Container cache max num` (number of directories) and the current filling of the cache `File cache occupancy` (number of files in the cache) and `Container cache occupancy` (number of directories in the cache).\n\n.. index::\n   pair: Namespace; Cache Configuration\n\n\nChanging the Cache Size\n^^^^^^^^^^^^^^^^^^^^^^^\n\nThe maximum number of files/container can be modified using:\n .. code-block:: bash\n\n    eos ns cache set -f 100000000 # set the file cache size\n    eos ns cache set -d 10000000  # set the container cache size\n\n.. index::\n   pair: Namespace; Cache Dropping\n\n\nDropping Caches\n^^^^^^^^^^^^^^^^^^^^^^^\nThe caches can be dropped using:\n .. code-block:: bash\n\n    eos ns cache drop -f # drop the file cache\n    eos ns cache drop -d # drop the container cache\n\nAfter a drop operation the occupancy value should go down to 0 and rise from there.\n\nDropping single files/directories from the cache\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nTo drop a single file or container one can execute:\n .. code-block:: bash\n\n    eos ns cache drop-single-file <fileid>\n\n .. code-block:: bash\n\n    eos ns cache drop-single-container <containerid>\n\n.. index::\n   pair: Namespace; MGM Latencies\n\n**Latency Sections**\n\n.. code-block:: bash\n\n   # ------------------------------------------------------------------------------------\n   ALL      eosViewRWMutex peak-latency      0ms (last) 0ms (1 min) 0ms (2 min) 0ms (5 min)\n   # ------------------------------------------------------------------------------------\n   ALL      QClient overall RTT              0ms (min)  0ms (avg)  18ms (max)\n   ALL      QClient recent peak RTT          1ms (1 min) 1ms (2 min) 1ms (5 min)\n   # ------------------------------------------------------------------------------------\n\n*Namespace Peak Latency*\nThe `eosViewRWMutex peak-latency` values measure the maximum lead time to obtain a write lock on the namespace. The values show the current measurement and the maximum in the last 1,2 or 5 minutes. The value should be most of the time `0ms` but can fluctuate for few hundred milliseconds.\n\n*QClient overall RTT*\nThis sections shows the measurement of the round-trip time for QuarkDB operations with the minimum, average and maximum values.\nFor normal operation the average time should be `0ms`, but the maximum value can exceed a second in certain circumstances.\n\n.. index::\n   pair: Namespace; QClient Latency\n\n*Qclient recent peak RTT*\nThis sections shows the maximum value (peak) for a 1,2 and 5 minutes window in the past.\n\n**Resource Section**\nThis section shows the current usage of memory by the MGM process, the growth of the memory since startup, the number of threads currently used by the MGM, the number of open filedescriptors and the uptime of the MGM process in seconds.\n\n.. code-block:: bash\n\n   # ------------------------------------------------------------------------------------\n   ALL      memory virtual                   3.75 GB\n   ALL      memory resident                  195.15 MB\n   ALL      memory share                     48.03 MB\n   ALL      memory growths                   1.48 GB\n   ALL      threads                          379\n   ALL      fds                              428\n   ALL      uptime                           59205\n   # ------------------------------------------------------------------------------------\n\n\nIn general the number of threads should be few hundred. If the number of threads reaches the maximum setting for the thread-pool (default is 4k threads), this points to a contention or lock problem in the MGM.\n\n.. index::\n   pair: Namespace; Service Information\n\n**Service Section**\n\nThe service section shows the thread-pool settings for the `drain`, `fsck` and `converter` engines and the number of entries in their queues `queue_sz`.\n\n.. code-block:: bash\n\n   # ------------------------------------------------------------------------------------\n   ALL      drain info                       pool=drain          min=10  max=100  size=10   queue_sz=0\n   ALL      fsck info                        pool=fsck           min=2   max=20   size=2    queue_sz=0\n   ALL      converter info                   pool=converter      min=8   max=100  size=8    queue_sz=0\n   ALL      balancer info                    space=default\n   # ------------------------------------------------------------------------------------\n\n\n.. index::\n   pair: Namespace; Tracker Information\n\n**Tracker Section**\n\nWhenever a file is processed by the balancer, conversion or draining engine, it is added to the file tracker for the given engine. This mechanism serves to avoid processing the same file concurrently within these engines.\n\n.. code-block:: bash\n\n   # ------------------------------------------------------------------------------------\n   ALL      tracker info                     tracker=balance size=413\n   ALL      tracker info                     tracker=convert size=0\n   ALL      tracker info                     tracker=drain size=22\n   # ------------------------------------------------------------------------------------\n\n.. index::\n   pair: Namespace; MD Statistics\n\nStatistics Display\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTo add detailed statistic counters, one can use:\n\n.. code-block:: bash\n\n   eos ns stat                     # see aggregated counters,rates,timings for all users aggregated\n   eos ns stat -m                  # output the previous in key-value monitoring format\n   eos ns stat -a                  # see individual user/group measurements\n   eos ns stat -a -m               # output the previous in key-value monitoring format\n   eos ns stat -a [-m] -n          # don't output user/group names, just IDs\n\n\nHere is an example output of `eos ns stat`, the header columns are explained here:\n\n.. code-block:: bash\n\n   * who                  : all => aggregation over all users\n   * sum                  : number of times this command has been called since instance startup\n   * 5s                   : average rate in Hz during the last 5s\n   * 1min                 : average rate in Hz during the last minutes\n   * 5min                 : average rate in Hz during the last 5 minutes\n   * 1h                   : average rate in Hz during the last 1 hour\n   * exec(ms)             : average execution time for this command for the last 100 executions\n   * sigma(ms)            : standard deviation for the average before\n   * 99p(ms)              : 99 percentile for the average before\n   * max(ms)              : maximum for the average before\n   * cumul                : accumulated execution time for all commands executed since instance start\n\n\n.. code-block:: bash\n\n   ┌───┬───────────────────────────────────────────┬────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┬────────┬────────┐\n   │who│command                                    │     sum│      5s│    1min│    5min│      1h│exec(ms)│sigma(ms)│ 99p(ms)│ max(ms)│   cumul│\n   └───┴───────────────────────────────────────────┴────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┴────────┴────────┘\n\n   all Access                                      612.20 M   989.50  1177.05  1568.29  1745.82     -NA-      -NA-     -NA-     -NA-     -NA-\n   all AccessControl                                    283     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all AdjustReplica                                 1.40 K     0.00     0.00     0.00     0.00     1.75      6.97    54.07    44.14       4s\n   all AttrGet                                      21.25 M    20.25    33.44    56.27    47.83     0.17      0.46     4.63     0.74       2h\n   all AttrLs                                        1.22 G  1131.00  2092.27  2429.77  2607.00     0.21      1.59    15.99     0.13      16h\n   all AttrRm                                        2.50 K     0.00     0.00     0.00     0.01     0.21      0.18     1.88     0.49       0s\n   all AttrSet                                     101.75 K     0.00     0.07     0.17     0.13     0.35      1.37    13.83     2.25      20s\n   all BulkRequestBusiness::getBulkRequest                0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all BulkRequestBusiness::getStageBulkRequest           0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all BulkRequestBusiness::saveBulkRequest               0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Cd                                                15     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Checksum                                           0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Chmod                                       158.80 K     0.00     0.15     0.15     0.14     0.42      1.61    16.39     0.70       1m\n   all Chown                                         4.04 K     0.00     0.00     0.01     0.01     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Commit                                       37.76 M    13.50   118.27   106.89    89.53     2.33      5.87    23.52    23.25       2d\n   all CommitFailedFid                                    0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all CommitFailedNamespace                              0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all CommitFailedParameters                             0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all CommitFailedUnlinked                        347.40 K     0.00     0.00     0.72     0.23     -NA-      -NA-     -NA-     -NA-     -NA-\n   all ConversionDone                                     0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all ConversionFailed                                   0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all CopyStripe                                       612     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Delay::threads::101837                        3.74 K     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Delay::threads::102888                        1.55 K     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   ...\n   ...\n   all DrainCentralFailed                            3.42 K     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all DrainCentralStarted                           1.96 M     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all DrainCentralSuccessful                        1.96 M     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Drop                                         14.62 M     4.25    18.68    28.03    28.37     0.29      0.09     0.56     0.56       1d\n   all DropStripe                                       872     0.00     0.00     0.00     0.00     0.29      0.32     2.81     1.41       0s\n   all DumpMd                                        8.37 K     0.00     0.00     0.00     0.03     -NA-      -NA-     -NA-     -NA-     -NA-\n   all EAccess                                      46.58 M    34.50    54.93    56.34    73.61     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Eosxd::ext::BEGINFLUSH                        1.93 M     2.75     2.27     1.97     3.27     0.01      0.00     0.02     0.02      22s\n   all Eosxd::ext::CREATE                           11.12 M     1.00    47.64    36.13    20.73     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Eosxd::ext::CREATELNK                       287.03 K     0.00     0.00     0.02     0.07     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Eosxd::ext::DELETE                            8.56 M     0.00     0.15     2.16     5.81     1.56      1.54    14.19     5.24       6h\n   all Eosxd::ext::DELETELNK                       283.06 K     0.00     0.00     0.02     0.06     0.26      0.15     1.60     0.51       1m\n   all Eosxd::ext::ENDFLUSH                          1.93 M     2.50     2.24     2.45     3.24     0.01      0.00     0.03     0.03      23s\n   all Eosxd::ext::GET                              17.40 M   148.75    30.88    47.78    44.13     0.04      0.04     0.24     0.22       5h\n   all Eosxd::ext::GETCAP                          249.79 M   123.00   113.54   217.52   290.05     0.27      0.89     9.16     0.31      23h\n   all Eosxd::ext::GETLK                             6.62 K     0.00     0.00     0.01     0.01     0.01      0.00     0.03     0.02       0s\n   all Eosxd::ext::LS                              267.49 M   128.50   123.24   230.66   314.07     0.43      0.47     3.03     2.71       2d\n   all Eosxd::ext::LS-Entry                          4.93 G  2515.75  2636.56  4773.92  5748.08     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Eosxd::ext::MKDIR                           860.27 K     0.00     0.49     0.69     2.16     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Eosxd::ext::MV                               62.39 K     0.00     0.00     0.26     0.06     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Eosxd::ext::RENAME                            1.62 M     0.00     0.12     0.53     0.85     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Eosxd::ext::RM                                9.66 M     0.00     0.15     2.30     6.28     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Eosxd::ext::RMDIR                           817.21 K     0.00     0.00     0.11     0.41     1.20      0.82     4.18     3.78      12m\n   all Eosxd::ext::SET                              56.21 M     6.75   119.19    97.08    73.01     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Eosxd::ext::SETDIR                            3.69 M     0.00     0.78     2.22     4.07     0.27      0.56     5.73     0.60      24m\n   all Eosxd::ext::SETFILE                          52.17 M     6.75   118.41    94.85    68.87     0.32      0.41     4.18     0.77      15h\n   all Eosxd::ext::SETLK                             1.78 M     0.00     0.07     2.83     1.50     0.01      0.01     0.05     0.04      28s\n   all Eosxd::ext::SETLKW                            1.10 M     1.00     1.00     1.15     1.17    11.67      0.26    13.96    12.73       3h\n   all Eosxd::ext::SETLNK                          347.46 K     0.00     0.00     0.02     0.08     0.35      0.68     6.98     1.24       2m\n   all Eosxd::ext::UPDATE                           42.08 M     5.75    70.93    59.42    48.72     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Eosxd::int::AuthRevocation                    2.80 M     0.00     0.15     1.68     2.10     0.02      0.03     0.30     0.16      21s\n   all Eosxd::int::BcConfig                        204.40 K     0.25     0.24     0.28     0.35     0.01      0.00     0.03     0.02       2s\n   all Eosxd::int::BcDeletion                        9.56 M     0.00     0.15     2.30     6.28     0.01      0.01     0.05     0.04      28m\n   all Eosxd::int::BcDeletionExt                     3.50 M    20.00     5.76     6.20     4.57     0.01      0.00     0.03     0.02       1m\n   all Eosxd::int::BcDropAll                       198.89 K     0.25     0.24     0.28     0.35     0.02      0.00     0.03     0.03       3s\n   all Eosxd::int::BcMD                             53.24 M     6.50   118.69    88.87    70.83     0.01      0.00     0.02     0.02       3h\n   all Eosxd::int::BcMDSup                         263.14 M     0.00     0.00    25.84    99.11     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Eosxd::int::BcRefresh                        12.39 M     0.00     0.44     3.83     8.18     0.02      0.02     0.10     0.10      11m\n   all Eosxd::int::BcRefreshExt                     27.37 M    80.75    74.95    84.87    68.11     0.01      0.01     0.07     0.06       2h\n   all Eosxd::int::BcRefreshExtSup                 108.03 M     0.00     0.00     8.20    28.24     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Eosxd::int::BcRefreshSup                     47.42 M     0.00     0.00     0.00    20.91     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Eosxd::int::BcRelease                        12.40 M     0.00     0.44     3.90     8.20     0.01      0.01     0.10     0.05      52m\n   all Eosxd::int::BcReleaseExt                     21.32 M    47.25    24.25    71.48    62.38     0.00      0.01     0.05     0.05       5m\n   all Eosxd::int::DeleteEntry                      17.59 M     0.00     0.03     1.19     1.24     0.02      0.00     0.04     0.03       5m\n   all Eosxd::int::FillContainerCAP                611.91 M   411.75   275.78   586.62   715.28     0.17      0.89     9.05     0.13      14h\n   all Eosxd::int::FillContainerMD                   1.12 G  2233.25  1285.92  1171.27  1456.71     0.06      0.20     2.03     0.10       1d\n   all Eosxd::int::FillFileMD                        4.38 G   989.25  1651.12  4043.64  4775.40     0.01      0.00     0.02     0.02      16h\n   all Eosxd::int::Heartbeat                       211.83 M   273.25   394.36   409.23   552.32     0.03      0.11     1.13     0.12       4h\n   all Eosxd::int::MonitorCaps                     759.91 K     1.00     0.95     1.11     1.15    28.66     79.18   281.70   276.66       4h\n   all Eosxd::int::MonitorHeartBeat                773.53 K     0.75     0.98     1.15     1.18     8.30     11.54    46.61    41.56      52m\n   all Eosxd::int::RefreshEntry                     29.23 M    30.00    84.98    66.38    52.00     0.03      0.01     0.06     0.06      20m\n   all Eosxd::int::ReleaseCap                       27.40 M     1.25     0.17     2.98     5.32     0.02      0.01     0.07     0.05       9m\n   all Eosxd::int::SendCAP                           5.69 M     0.00     0.00     0.04     5.44     0.02      0.01     0.07     0.05       1m\n   all Eosxd::int::SendMD                            8.40 M     0.00     2.08     5.14     7.60     0.01      0.01     0.04     0.03       1m\n   all Eosxd::int::Store                           602.26 M   407.50   269.90   504.02   705.10     0.02      0.00     0.03     0.03       4h\n   all Eosxd::int::ValidatePERM                      9.38 M     3.50    15.42    10.64     7.14     0.12      0.29     2.37     1.71       3h\n   all Eosxd::prot::LS                             590.33 M   673.25   350.66   565.11   748.29     0.20      0.37     2.87     1.61       5d\n   all Eosxd::prot::SET                             72.61 M    13.00   124.92   107.26    88.47     0.85      2.49    11.69    11.69       1d\n   all Eosxd::prot::STAT                            72.93 M   423.25   113.90   117.07   144.15     0.23      0.17     1.77     0.47       1d\n   all Eosxd::prot::evicted                         25.36 K     0.50     0.17     0.08     0.07     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Eosxd::prot::mount                          198.89 K     0.25     0.24     0.28     0.35     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Eosxd::prot::offline                         16.10 K     0.00     0.07     0.08     0.07     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Eosxd::prot::umount                         186.20 K     0.25     0.31     0.26     0.24     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Exists                                       32.53 M   100.50    25.19    22.91    29.91     0.18      0.09     0.54     0.52       2h\n   all FileInfo                                     24.22 M    70.75    38.42    41.18    41.16     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Find                                        621.96 K     0.25     0.19     0.51     0.45     1.18      2.89    16.82    14.61       1h\n   all FindEntries                                   4.60 M     0.50     7.37     6.91     2.97     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Fuse                                               0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Fuse-Access                                        0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Fuse-Checksum                               483.31 K     0.00     0.14     0.03     1.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Fuse-Chmod                                         0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Fuse-Chown                                         0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Fuse-Mkdir                                         0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Fuse-Stat                                    44.02 K     0.00     0.07     0.16     0.13     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Fuse-Statvfs                                  2.56 M     5.00     4.03     5.03     5.46     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Fuse-Utimes                                        0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Fuse-XAttr                                         0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all GetFusex                                           0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all GetMd                                        81.03 M   306.75   168.15   230.45   167.20     -NA-      -NA-     -NA-     -NA-     -NA-\n   all GetMdLocation                                      6     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all HashGet                                      20.51 G 16011.00 56173.54 36789.02 33583.27     -NA-      -NA-     -NA-     -NA-     -NA-\n   all HashSet                                       2.90 G  3942.50  4238.42  5160.53  5425.47     -NA-      -NA-     -NA-     -NA-     -NA-\n   all HashSetNoLock                                      0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Http-COPY                                    64.27 K     0.50     0.39     2.65     3.03     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Http-DELETE                                 133.00 K    20.00     4.68     2.55     1.41     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Http-GET                                      2.98 M    24.25    31.00     7.44     3.92     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Http-HEAD                                    98.87 K     2.25     2.86     4.83     6.20     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Http-LOCK                                         16     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Http-MKCOL                                   93.92 K     0.00     0.00     0.10     0.09     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Http-MOVE                                    25.64 K     0.00     0.00     0.02     0.03     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Http-OPTIONS                                       5     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Http-POST                                   540.08 K     6.50     6.10    18.93    24.46     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Http-PROPFIND                                57.52 M    90.75   111.24   157.86   149.35     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Http-PROPPATCH                                     0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Http-PUT                                      2.58 M     1.25     2.02    10.12     9.52     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Http-TRACE                                         0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Http-UNLOCK                                       16     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all IdMap                                         1.19 G  1297.25  1363.63  1452.83  1690.05     0.04      0.01     0.07     0.07       1d\n   all LRUFind                                            0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Ls                                            2.82 K     0.00     0.02     0.00     0.01     -NA-      -NA-     -NA-     -NA-     -NA-\n   all MarkClean                                          0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all MarkDirty                                          0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Mkdir                                         6.94 M    20.00     5.08     4.94     6.91     0.65      0.68     5.39     3.82       8m\n   all Motd                                               7     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all MoveStripe                                         0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Newfind                                            1     0.00     0.00     0.00     0.00  4744.00      0.00  4744.00  4744.00       4s\n   all NewfindEntries                                6.40 K     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all NsLockR                                       9.51 G  8018.00  9588.22 12451.45 13669.57     -NA-      -NA-     -NA-     -NA-     -NA-\n   all NsLockW                                     163.83 M   109.25   324.59   337.82   271.71     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Open                                        119.59 M    91.25   124.71   115.07   132.60     0.65      0.71     7.40     2.36       3d\n   all OpenDir                                     482.14 M    39.00   783.88   734.32   715.38     1.30      3.08    16.92    13.28       1d\n   all OpenDir-Entry                                12.37 G  1068.25  8288.78 10319.15 26755.20     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenFailedCreate                                   1     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenFailedENOENT                                 352     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenFailedExists                                 156     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenFailedNoUpdate                                 0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenFailedPermission                          3.30 K     0.00     0.00     0.02     0.01     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenFailedQuota                             360.72 K     0.00     0.00     0.04     0.24     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenFailedReconstruct                              0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenFailedRedirectLocal                            0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenFileOffline                                  704     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenLayout                                   10.96 K     0.00     0.00     0.01     0.01     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenProc                                    531.80 M   317.75   260.27   476.06   634.66     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenRead                                    104.00 M    83.75    72.64    67.27   100.06     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenRedirectLocal                                  0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenShared                                         0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenStalled                                        0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenWrite                                    12.60 M     1.25    49.88    37.67    22.67     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenWriteCreate                               1.65 M     1.25     1.49     7.70     6.12     -NA-      -NA-     -NA-     -NA-     -NA-\n   all OpenWriteTruncate                             1.32 M     0.00     0.68     2.71     3.75     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Prepare                                          190     0.00     0.00     0.00     0.00     0.49      0.59     5.71     2.08       1s\n   all QuarkSyncTimeAccounting                       7.14 M     8.25     5.03     8.28    10.40     4.12      2.43    16.77    13.52       9m\n   all QueryResync                                        0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Quota                                        93.64 K     0.50     0.07     0.08     0.15     -NA-      -NA-     -NA-     -NA-     -NA-\n   all QuotaLockR                                    1.06 G   588.00  1363.17  1974.57  1846.20     -NA-      -NA-     -NA-     -NA-     -NA-\n   all QuotaLockW                                   25.96 K     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all ReadLink                                           0     0.00     0.00     0.00     0.00     0.36      2.68    27.05     0.62      40m\n   all Recycle                                            0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Redirect                                           0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all RedirectENOENT                                     0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all RedirectENONET                                     0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all RedirectR                                          0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all RedirectR-Master                                   0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all RedirectW                                          0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Rename                                        6.50 M    20.00     4.92     4.32     6.46     1.11      0.36     2.82     2.11       1h\n   all ReplicaFailedChecksum                            863     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all ReplicaFailedSize                                180     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Rm                                            9.03 M    20.00     5.27     5.84     7.69     2.39      0.73     5.47     4.70       5h\n   all RmDir                                       550.83 K     0.00     0.03     0.09     0.17     0.23      0.21     1.42     1.29       2m\n   all Schedule2Balance                              6.66 M    10.25    12.88    16.55    24.56     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Schedule2Delete                               2.35 M     2.75     4.02     4.63     4.51     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Scheduled2Balance                             6.66 M    10.50    12.90    22.60    24.88     4.79      7.75    50.93    50.46       1d\n   all Scheduled2Delete                             14.61 M     8.75    19.58    34.61    33.38     0.49      2.97    21.35    21.25       3h\n   all Scheduled2Drain                                    0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all SchedulingFailedBalance                            0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all SchedulingFailedDrain                              0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Stall                                            610     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Stall::threads::104503                           347     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Stall::threads::104562                             5     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   ...\n   ...\n   all Stat                                          2.39 G  1291.50  4115.97  4238.70  4389.46     0.06      0.03     0.31     0.13      15h\n   all Symlink                                       3.03 M    26.50    33.86    12.27    10.12     -NA-      -NA-     -NA-     -NA-     -NA-\n   all TapeRestApiBusiness::cancelStageBulkRequest        0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all TapeRestApiBusiness::createStageBulkRequest        0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all TapeRestApiBusiness::deleteStageBulkRequest        0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all TapeRestApiBusiness::getFileInfo                   0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all TapeRestApiBusiness::getStageBulkRequest           0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all TapeRestApiBusiness::releasePaths                  0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Touch                                         2.65 K     0.00     0.02     0.00     0.00     2.14      0.98     9.60     6.01       2m\n   all Truncate                                           0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all TxState                                            0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all VerifyStripe                                     403     0.00     0.00     0.00     0.00     0.81      1.27     8.56     8.49       0s\n   all Version                                      13.24 K     0.00     0.05     0.03     0.02     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Versioning                                  600.85 K     0.00     0.68     1.92     1.70     0.88      1.11     9.05     6.34      11m\n   all ViewLockR                                   604.60 M   812.00   956.05  1060.62  1049.79     -NA-      -NA-     -NA-     -NA-     -NA-\n   all ViewLockW                                        916     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n   all Who                                          29.19 K     0.00     0.20     0.44     0.47     -NA-      -NA-     -NA-     -NA-     -NA-\n   all WhoAmI                                        2.64 K     0.00     0.02     0.00     0.00     -NA-      -NA-     -NA-     -NA-     -NA-\n\n\nWhen using the `-a` switch, two more sections are added with user and group specific counters for total number of commands per users and rate measurements for 5s,1min,5min and 1 hours:\n\n.. code-block:: bash\n\n  ┌───────────────┬────────┬────────┬────────┬────────┬────────┬────────┐\n  │user           │command │     sum│      5s│    1min│    5min│      1h│\n  └───────────────┴────────┴────────┴────────┴────────┴────────┴────────┘\n   ...\n\nand\n\n.. code-block:: bash\n\n  ┌─────────────────┬────────┬────────┬────────┬────────┬────────┬────────┐\n  │group            │command │     sum│      5s│    1min│    5min│      1h│\n  └─────────────────┴────────┴────────┴────────┴────────┴────────┴────────┘\n  ...\n\nIt is recommend in case of many users to use the `eos ns stat -a -n` switch to avoid translating user and group names, which can be slow/problematic for non-existing `uid/gids`.\n\n.. epigraph::\n\n    ===========================================  ======================================================================================\n    Counter                                      Description\n    ===========================================  ======================================================================================\n    Access                                       internal access function checking permissions\n    AccessControl                                usage of 'eos access' commands\n    AdjustReplica                                usage of 'eos file adjustreplica' commands\n    AttrGet                                      internal get xattr calls\n    AttrLs                                       internal list xattr calls\n    AttrRm                                       internal delete xattr calls\n    AttrSet                                      internal set xattr calls\n    BulkRequestBusiness::getBulkRequest          CTA get bulk request calls\n    BulkRequestBusiness::getStageBulkRequest     CTA get stage bulk request calls\n    BulkRequestBusiness::saveBulkRequest         CTA save bulk request calls\n    Cd                                           internal cd calls\n    Checksum                                     checksum calls\n    Chmod                                        chmod calls\n    Chown                                        chown calls\n    Commit                                       commit calls (triggered by each replica closed on an FST)\n    CommitFailedFid                              internal meta-data mismatch during commit concerning file id\n    CommitFailedNamespace                        internal failure when committing an update to the namespace\n    CommitFailedParameters                       commit call misses parameters\n    CommitFailedUnlinked                         commit on a file which had already been deleted\n    ConversionDone                               successful conversions\n    ConversionFailed                             failed conversoins\n    CopyStripe                                   calls to copy a stripe from one filesystem to another\n    Delay::threads::101090                       number of times a thread for user 101090 has been delayed to enforce a rate limit\n    Delay::threads::101981                       as above for user 101981 aso ...\n    DrainCentralFailed                           number of failed drain jobs\n    DrainCentralStarted                          number of started drain jobs\n    DrainCentralSuccessful                       number of successful drain jobs\n    Drop                                         calls to drop a replica\n    DropStripe                                   calls of function to drop a stripe via 'eos file drop'\n    DumpMd                                       calls to dump meta-data (unused when meta-data in fst XATTR)\n    EAccess                                      access is forbidden due to some 'eos access' settings\n    Eosxd::ext::BEGINFLUSH                       calls indicating a FUSE client starts to flush its journal\n    Eosxd::ext::CREATE                           calls to create a file via FUSE\n    Eosxd::ext::CREATELNK                        calls to create a symlink via FUSE\n    Eosxd::ext::DELETE                           calls to delete a file via FUSE\n    Eosxd::ext::DELETELNK                        calls to delete a link via FUSE\n    Eosxd::ext::ENDFLUSH                         calls indicating a FUSE client finished flushing its journal\n    Eosxd::ext::GET                              calls to retrieve meta-data from a FUSE client\n    Eosxd::ext::GETCAP                           calls to retrieve a capability from a FUSE client\n    Eosxd::ext::GETLK                            calls to retrieve a lock from a FUSE client\n    Eosxd::ext::LS                               calls to retrieve a directory listing from a FUSE client\n    Eosxd::ext::LS-Entry                         number of entries retrieved by listing calls from FUSE clients\n    Eosxd::ext::MKDIR                            calls to create a directory from FUSE clients\n    Eosxd::ext::MV                               calls to move files between directories from FUSE clients\n    Eosxd::ext::RENAME                           calls to change a file name within a directory from FUSE clients\n    Eosxd::ext::RM                               calls to delete a file from FUSE clients\n    Eosxd::ext::RMDIR                            calls to delete a directory from FUSE clients\n    Eosxd::ext::SET                              calls to create meta-data from FUSE clients\n    Eosxd::ext::SETDIR                           calls to create directory meta-data from FUSE clients\n    Eosxd::ext::SETFILE                          calls to create file meta-data from FUSE clients\n    Eosxd::ext::SETLK                            calls to create a shared lock from FUSE clients\n    Eosxd::ext::SETLKW                           calls to create an exclusive lock from FUSE clients\n    Eosxd::ext::SETLNK                           calls to create a symbolic link\n    Eosxd::ext::UPDATE                           calls to update meta-data\n    Eosxd::int::AuthRevocation                   number of capabilities revoked by FUSE clients\n    Eosxd::int::BcConfig                         broadcast calls with FUSE configuration\n    Eosxd::int::BcDeletion                       broadcast calls with FUSE deletions triggered by FUSE clients\n    Eosxd::int::BcDeletionExt                    broadcast calls with FUSE deletion triggered by non-FUSE clients\n    Eosxd::int::BcDropAll                        broadcast calls to drop all capabilities on a FUSE client\n    Eosxd::int::BcMD                             broadcast calls to send meta-data to a FUSE client\n    Eosxd::int::BcMDSup                          broadcast calls suppressed by suppression rules 'eos fusex conf'\n    Eosxd::int::BcRefresh                        broadcast calls to tell a FUSE client to refresh meta-data\n    Eosxd::int::BcRefreshExt                     broadcast calls to tell a FUSE client to refresh meta-data triggered by non-FUSE clients\n    Eosxd::int::BcRefreshExtSup                  broadcast calls to tell a FUSE client to refresh meta-data triggered by non-FUSE clients suppressed by suppression rules 'eos fusex conf'\n    Eosxd::int::BcRefreshSup                     broadcast calls to tell a FUSE client to refresh meta-data suppressed by suppression rules 'eos fusex conf'\n    Eosxd::int::BcRelease                        calls to release a capability to a FUSE client\n    Eosxd::int::BcReleaseExt                     calls to release a capability to a FUSE client triggered by non-FUSE clients\n    Eosxd::int::DeleteEntry                      calls to delete an entry to a FUSE client\n    Eosxd::int::FillContainerCAP                 calls to fill a capability for a given container for a FUSE client\n    Eosxd::int::FillContainerMD                  calls to fill container meta-data for a FUSE client\n    Eosxd::int::FillFileMD                       calls to fill file meta-data for a FUSE client\n    Eosxd::int::Heartbeat                        heartbeats from FUSE clients received\n    Eosxd::int::MonitorCaps                      internal calls monitoring (expiring) capabilities of FUSE clients\n    Eosxd::int::MonitorHeartBeat                 internal calls monitoring (expiring) FUSE client heartbeats responsible for eviction of clients missing heartbeats\n    Eosxd::int::RefreshEntry                     internal refresh entry calls\n    Eosxd::int::ReleaseCap                       internal release capability calls\n    Eosxd::int::SendCAP                          calls to send a capability to FUSE clients\n    Eosxd::int::SendMD                           calls to send meta-data to FUSE clients\n    Eosxd::int::Store                            calls to store a capability\n    Eosxd::int::ValidatePERM                     calls to validate permissions when an attached capability didn't grant the required permissions\n    Eosxd::prot::LS                              protocol calls to for listing\n    Eosxd::prot::SET                             protocol calls to set meta-data\n    Eosxd::prot::STAT                            protocol calls to stat meta-data\n    Eosxd::prot::evicted                         counter marking a client evicted (2 minutes no heart-beat)\n    Eosxd::prot::mount                           number clients mount the filesystem\n    Eosxd::prot::offline                         counter marking a client as offline (30 seconds no heart-beat)\n    Eosxd::prot::umount                          number clients unmount the filesystem\n    Exists\n    FileInfo\n    Find\n    FindEntries\n    Fuse\n    Fuse-Access\n    Fuse-Checksum\n    Fuse-Chmod\n    Fuse-Chown\n    Fuse-Mkdir\n    Fuse-Stat\n    Fuse-Statvfs\n    Fuse-Utimes\n    Fuse-XAttr\n    GetFusex\n    GetMd\n    GetMdLocation\n    HashGet\n    HashSet\n    HashSetNoLock\n    Http-COPY\n    Http-DELETE\n    Http-GET\n    Http-HEAD\n    Http-LOCK\n    Http-MKCOL\n    Http-MOVE\n    Http-OPTIONS\n    Http-POST\n    Http-PROPFIND\n    Http-PROPPATCH\n    Http-PUT\n    Http-TRACE\n    Http-UNLOCK\n    IdMap\n    LRUFind\n    Ls\n    MarkClean\n    MarkDirty\n    Mkdir\n    Motd\n    MoveStripe\n    NsLockR\n    NsLockW\n    Open\n    OpenDir\n    OpenDir-Entry\n    OpenFailedCreate\n    OpenFailedENOENT\n    OpenFailedExists\n    OpenFailedNoUpdate\n    OpenFailedPermission\n    OpenFailedQuota\n    OpenFailedReconstruct\n    OpenFailedRedirectLocal\n    OpenFileOffline\n    OpenLayout\n    OpenProc\n    OpenRead\n    OpenRedirectLocal\n    OpenShared\n    OpenStalled\n    OpenWrite\n    OpenWriteCreate\n    OpenWriteTruncate\n    Prepare\n    QuarkSyncTimeAccounting\n    QueryResync\n    Quota\n    QuotaLockR\n    QuotaLockW\n    ReadLink\n    Recycle\n    Redirect\n    RedirectENOENT\n    RedirectENONET\n    RedirectR\n    RedirectR-Master\n    RedirectW\n    Rename\n    ReplicaFailedChecksum\n    ReplicaFailedSize\n    Rm\n    RmDir\n    Schedule2Balance\n    Schedule2Delete\n    Scheduled2Balance\n    Scheduled2Delete\n    Scheduled2Drain\n    SchedulingFailedBalance\n    SchedulingFailedDrain\n    Stall\n    Stall::threads::112541\n    Stall::threads::127773\n    Stall::threads::134033\n    Stall::threads::152755\n    Stall::threads::155709\n    Stall::threads::22043\n    Stall::threads::52999\n    Stall::threads::81826\n    Stat\n    Symlink\n    TapeRestApiBusiness::cancelStageBulkRequest\n    TapeRestApiBusiness::createStageBulkRequest\n    TapeRestApiBusiness::deleteStageBulkRequest\n    TapeRestApiBusiness::getFileInfo\n    TapeRestApiBusiness::getStageBulkRequest\n    TapeRestApiBusiness::releasePaths\n    Touch\n    Truncate\n    TxState\n    VerifyStripe\n    Version\n    Versioning\n    ViewLockR\n    ViewLockW\n    Who\n    WhoAmI\n    NsLockRWait:spl\n    NsLockRWait:min\n    NsLockRWait:avg\n    NsLockRWait:max\n    NsLockWWait:spl\n    NsLockWWait:min\n    NsLockWWait:avg\n    NsLockWWait:max\n    QuotaLockRWait:spl\n    QuotaLockRWait:min\n    QuotaLockRWait:avg\n    QuotaLockRWait:max\n    QuotaLockWWait:spl\n    QuotaLockWWait:min\n    QuotaLockWWait:avg\n    QuotaLockWWait:max\n    ViewLockRWait:spl\n    ViewLockRWait:min\n    ViewLockRWait:avg\n    ViewLockRWait:max\n    ViewLockWWait:spl\n    ViewLockWWait:min\n    ViewLockWWait:avg\n    ViewLockWWait:max\n    ===========================================  ======================================================================================\n\n\n.. index::\n   pair: Interfaces; Access\n   pair: CLI; eos access\n\nAccess Interface\n----------------\n\nThe access interface `eos access` has the main purpose to define access control rules to an EOS instance.\n\n.. index::\n   pair: Access; Restricting\n\nRestricting access by users,group, host or domain name\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nThe access interface allows to\n\n* manage blacklists of users, groups, hosts, domains or tokens which cannot access the instance via `eos ban` and `eos unban`\n* manage whitelists of users, groups, hosts, domains or tokens which can access the instance via `eos allow` and `eos unallow`\n\nIf you create a whitelist, only the listed entities are able to connect, otherwise receive an EPERM error. If you have a entry in a whitelist you can still configure a ban on that entry and it will stop access for this entity.\n\nA ban entry returns for any request which is not FUSE or http access a *WAIT* (`come back in 5 minutes`) response, which means banned clients will hang for 5 minutes and might retry - they are not failed with EPERM.\n\n.. index::\n   pair: Access; Allowing\n\nCreating *allow_ entries* (whitelist)\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\nIf we want to allow user `foo` and group `bar` we do:\n\n.. code-block:: bash\n\n   eos allow user foo\n   eos allow group bar\n\n\nIf we want to allow only a particular host we do:\n\n.. code-block:: bash\n\n   eos allow host myhost.my.domain\n\n\nIf we want to allow only a particular domain we do:\n\n.. code-block:: bash\n\n   eos allow my,domain\n\n\n.. WARNING::\n\n   Keep in mind, that if you put an allow rule for a particular token, all other token don't work anymore!\n\n.. index::\n   pair: Access; Info\n\nDisplaying access rules\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\nThe current access definitions can be show using:\n\n.. code-block:: bash\n\n   eos access ls\n   # ....................................................................................\n   # Allowd Users ...\n   # ....................................................................................\n   [ 01 ] foo\n   # ....................................................................................\n   # Allowed Groups...\n   # ....................................................................................\n   [ 01 ] bar\n   # ....................................................................................\n   # Allowed Hosts ...\n   # ....................................................................................\n   [ 01 ] myhost.my.domain\n   # ....................................................................................\n   # Allowed Domains ...\n   # ....................................................................................\n   [ 01 ] my.domain\n\n\nRemoving *allow* entries (whitelist)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nRemove entry after `allow`:\n\n.. code-block:: bash\n\n  eos access unallow user foo\n  eos access unallow group bar\n  eos access unallow host myhost.my.domain\n  eos access unallow domain my.domain\n\n\n.. note::\n\n   Access is open for all users,groups,hosts or domains without an _allow_ entry!\n\nCreating *ban* entries (blacklist)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n*ban* entries define a blacklist.\n\nTo ban a user, group, host, domain or token you use the `ban` subcommand:\n\n.. code-block:: bash\n\n  eos access ban user foo\n  eos access ban group bar\n  eos access ban host myhost.my.domain\n  eos access ban domain my.domain\n  eos access ban token 1079aad2-927c-11ed-be62-0071c2181e97\n\nClients matching any of these rules will be stalled 5 minutes (receive a *WAIT* response).\n\n.. note::\n\n  If you need to ban a token, the token voucher ID which you specify to block it, is shown as logid in the MGM logfile when the _IdMap_ function has been called. If you have the token available you can see the voucher ID by running\n\n  `eos token --token zteos64:...`.\n\nThe defined blacklists are shown using `eos access ls`:\n\n.. code-block::\n\n   eos access ls\n   # ....................................................................................\n   # Banned Users ...\n   # ....................................................................................\n   [ 01 ] foo\n   # ....................................................................................\n   # Banned Groups...\n   # ....................................................................................\n   [ 01 ] bar\n   # ....................................................................................\n   # Banned Hosts ...\n   # ....................................................................................\n   [ 01 ] myhost.my.domain\n   # ....................................................................................\n   # Banned Domains ...\n   # ....................................................................................\n   [ 01 ] my.domain\n   # ....................................................................................\n   # Banned Tokens ...\n   # ....................................................................................\n   [ 01 ] 1079aad2-927c-11ed-be62-0071c2181e97\n   # ....................................................................................\n\nRemoving *ban* entries (blacklist)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTo remove a ban rule, you use the `unban` subcommand:\n\n.. code-block:: bash\n\n  eos access unban user foo\n  eos access unban group bar\n  eos access unban host myhost.my.domain\n  eos access unban domain my.domain\n  eos access unban token 1079aad2-927c-11ed-be62-0071c2181e97\n\n\n.. note::\n    It is not supported to use wildcards in any of the rule definitions for performance reasons!\n\n.. index::\n   pair: Access; Redirection\n\nRedirecting Requests\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nIt is possible to redirect all requests to a new target. Additionally a *hold* time can be specified, which means that before a redirection happens a request is hold for *hold* ms. This mechanism can be used to deploy a throttling front-end.\n\nAdd/Remove a redirects\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\nRedirect all requests to `foo.bar:1094`:\n\n.. code-block:: bash\n\n  eos access set redirect foo.bar:1094\n\nRedirect all requests to `foo.bar:1094` with a 10ms delay:\n\n.. code-block:: bash\n\n  eos access set redirect foo.bar:1094:10\n\n\nRemove a redirection rule:\n\n.. code-block:: bash\n\n  eos access rm redirect\n\n.. index::\n   pair: Access; Thread Limiting\n\n\nThread Limiting Rules\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe EOS *MGM* process provides a thread-pool to handle user requests. The default setting is to have 8 threads always ready and to dynamically expand the thread-pool to 256 threads if needed. These values are defined in `/etc/xrd.cf.mgm` for the old and `/etc/eos/config/mgm/mgm` for the new configuration `xrd.sched mint 8 maxt 256 idle 64`. *mint* is the minimum number of threads ready, *maxt* is the maximum number of threads running, *idle* is the time after which unused threads are retired. To avoid that a single user (who is running many clients doing more parallel requests than *maxt*) hijacks the MGM thread pool and consumes all resources, it is possible to set the maximum number of threads a single user can use at the same time.\nTo define the maximum number of threads per user for any user you do:\n\n.. code-block:: bash\n\n  eos access set limit 500 threads:*\n\nTo define the maximum number of threads for a specific user *foo* you do:\n\n.. code-block:: bash\n\n  eos access set limit 100 threads:foo\n\nTo remove a wildcard rule you do:\n\n.. code-block:: bash\n\n  eos access rm limit threads:*\n\nor a specific:\n\n.. code-block:: bash\n\n  eos access rm limit threads:foo\n\nHint: the state of the thread pool and if limits are applied is shown at the end of the `eos ns stat` output:\n\n.. code-block:: bash\n\n   eos ns stat\n\n   ┌────────┬───────┬────────┬─────┬──────┬─────────┬────────────────┐\n   │     uid│threads│sessions│limit│stalls│stalltime│          status│\n   └────────┴───────┴────────┴─────┴──────┴─────────┴────────────────┘\n       28944       1        1   100      0         1          user-OK\n       80912       8        0    10      0         1          user-OK\n\n\n* *id* - user id\n* *threads* - number of active threads in this moment\n* *sessions* - number of client sessions\n* *limit* - current limit applying to this user (can be a catch all or specific limit)\n* *stalls* - number of times this user has received a stall due to thread-pool excess\n* *stalltime* - time in *seconds* requests are stalles currently for this user\n* *status* - `user-OK`:no limit applies `user-LIMIT`: using more than 90% of allowed `user-OL`: exceeding the allowed limit\n\nA user request hitting a thread-pool (with exceeded user limit!) is not served but receives a *WAIT* response: this request is stalled for *stalltime* seconds and the client can retry the request after the given period has passed.\n\n*Hint:* thread-limits can lead to longer client starvation periods in some cases of long overload periods because\nprevious stalls of requests don't change the probability that a client is repeatedly stalled!\n\n.. index::\n   pair: Access; Rate Limiting\n\nRate Limiting Rules\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nIt is possible to define a maximum *operations/s* rate for individual meta-data operations. These limits can be defined for single users and/or for all users (catch-all wildcard limit). First user limits are checked, then catch-all limits for all users. Limits are defined in Hz as natural numbers. A limit of 0 sends a *WAIT 5s* (smeared +- 5s) response to clients for the configured request type. All this is only valid for non-FUSE clients. FUSE clients receive the error EACCES, when a limit is reached to avoid that mounts lock-up!\n\nExamples:\n\n.. code-block:: bash\n\n   access set limit 100  rate:user:*:OpenRead     : Limit the open for read rate to a frequency of 100 Hz for all users\n   access set limit 0    rate:user:ab:OpenRead    : Limit the open for read rate for the ab user to 0 Hz, to continuously stall it\n   access set limit 2000 rate:group:zp:Stat       : Limit the stat rate for the zp group to 2kHz\n\n\nThe MGM enforces the given operation rate by delaying responses to match the nominal *operation/s* setting for a given operation/user.\nThe behavior changes when a limit is reached and a users exhausts his allowed thread limit or the global thread pool is saturated: the MGM does not regulate anymore the rate but sends *WAIT* responses.\n\n.. note:: 1. When the *WAIT* response mechanism is triggered, it triggers for any kind of operation until no single limit is exceeded anymore! To summarize, if a user excesses his open/s rate and the thread pool, also his deletion commands will get stalled.\n\n.. note:: 2. All limits starting with *Eosxd::* (these are operation counters for FUSE clients!) trigger the severe *WAIT* response mechanism only for the given operation, they have no impact on other operations.\n\nWhite or Blacklisting Hosts which can be stalled\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nFor certain use cases we want to exclude limits depending on the client host issuing the commands. An example are for example protocol gateways (e.g. CIFS server), which we don't want to throttle or a backup machinery, which can easily trigger rate limits.\n\nTo allow this, one can define a whitelist (which hosts can actually be stalled) or a blacklist (which hosts should never be stalled).\n\nExamples to define these are shown here:\n\n.. code-block:: bash\n\n   access stallhosts add stall foo*.bar           : Add foo*.bar to the list of hosts which are stalled by limit rules (white list)\n   access stallhosts remove stall foo*.bar        : Remove foo*.bar from the list of hosts which are stalled by limit rules (white list)\n   access stallhosts add nostall foo*.bar         : Add foo*.bar to the list of hosts which are never stalled by limit rules (black list)\n   access stallhosts remove nostall foo*.bar      : Remove foo*.bar from the list of hosts which are never stalled by limit rules (black list)\n\n\n.. index::\n\n   pair: Interfaces; Space Attributes\n\n\n Space Attributes\n-----------------\n\nSpace attributes allow to define a set of extended attributes which appear in attribute listing of any directory linked to a given space. A directory reference space attributes by *sys.forced.space* or if not defined it will reference attributes in the *default* space. Space attributes allow to reduce the directory-meta data size because attributes have not to be stored with each directory individually. A specialized space attribute is *sys.acl*, which is explained in detail in the permission (ACL) system section. *sys.acl* attributes are syntax checked and provide left- and right-positioned ACL extensions. All other space attributes can either overwrite/define attributes appearing in each space referencing directory or they can be defined as optional default attribute, which is used only if a directory does not define the attribute.\n\nSpace attributes are defined like:\n\n.. code-block:: bash\n\n                # define adler32 for all directories in that space\n                eos space config default space.attr.sys.forced.checksum=adler32\n\nSpace attribute are removed like:\n\n.. code-block:: bash\n\n\t\t# remove checksum settings in the default space\n\t\teos space config rm default space.attr.sys.forced.checksum\n\nSpace attributes are listed in the usual manner:\n\n.. code-block:: bash\n\n                # show configuration of the default space\n                eos space status default\n\n\n\n.. index::\n   pair: Interfaces; Space policies\n   pair: Interfaces; Application policies\n\nSpace and Application Policies\n----------------------------------\n\nSpace policies are set using the space configuration CLI.\n\nThe following policies can be configured\n\n.. epigraph::\n\n    =================== =================================================\n    key                 values\n    =================== =================================================\n    space               default,...\n    altspaces           [space1[,space2[,space3]]]\n    layout              plain,replica,raid5,raid6,raiddp,archive,qrain\n    nstripes            1..255\n    checksum            adler,md5,sha1,crc32,crc32c\n    blockchecksum       adler,md5,sha1,crc32,crc32c\n    blocksize           4k,64k,128k,512k,1M,4M,16M,64M\n    bandwidth:r|w       IO limit in MB/s for reader/writer\n    iotype:r|w          io flavour [ direct, sync, csync, dsync ]\n    iopriority:r|w      io priority [ rt:0...rt:7,be:0,be:7,idle ]\n    schedule:r|w        fair FST scheduling [1 or 0]\n    localredirect       redirect clients to a local filesystem (0,1,never,optional,always)\n    readconversion      read-tiering: read files only from the given layout (space:hex-layout)\n    updateconversion    write-tiering: update files only with the given layout (space:hex-layout)\n    =================== =================================================\n\n\nSetting space policies\n^^^^^^^^^^^^^^^^^^^^^^\n.. code-block:: bash\n\n   # configure raid6 layout\n   eos space config default space.policy.layout=raid6\n\n   # configure 10 stripes\n   eos space config default space.policy.nstripes=10\n\n   # configure adler file checksumming\n   eos space config default space.policy.checksum=adler\n   # configure crc32c block checksumming\n   eos space config default space.policy.blockchecksum=crc32c\n\n   # configure 1M blocksizes\n   eos space config default space.policy.blocksize=1M\n\n   # configure a global bandwidth limitation for all streams of 100 MB/s in a space\n   eos space config default space.policy.bandwidth=100\n\n   # configure FST fair thread scheduling for readers\n   eos space config default space.policy.schedule:r=1\n\n   # configure default FST iopriority for writers\n   eos space config default space.policy.iopriority:w=be:4\n\n   # configure default FST iotype\n   eos space config default space.policy.iotype:w=direct\n\n   # configure local redirects for all clients\n   space config default space.policy.localredirect=always\n\n   # configure local redirects for all clients, which have a '#sharedfs' appended to the application\n   # e.g. eoscp#cephfs to redirect applications anchoring the 'cephfs' named shared filesystem\n   space config default space.policy.localredirect=optional\n\n   # disable local redirects for all clients\n   space config default space.policy.localredirect=never\n\nSetting user,group and application policies\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIO policies as iotype,iopriority,bandwidth and schedule can be scoped to\na group,user or an application\n\n.. code-block:: bash\n\n   # configure an application specific bandwidth limitations for all reading streams in a space\n   eos space config default space.bandwidth:r.app:myapp=100 # reading streams tagged as ?eos.app=myapp are limited to 100 MB/s\n\n   eos space config default space.iotype:w.user:root=direct # use direct IO for writing streams by user root\n\n   eos space config default space.iopriority:r.group:adm=rt:1 # use IO priority realtime level 1 for the adm group when reading\n\n\nThe evaluation order is by space (lowest), by group, by user, by app\n(highest). Finally IO policies can be overwritten by extended\n**sys.forced** attributes (see the following).\n\nPolicy Selection, Scopes and alternative Spaces\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nClients can select the space ( and its default policies ) by adding\n`eos.space=<space>` to the CGI query of an URL, otherwise the space is\ntaken from **space.policy.space** in the default space or if undefined\nit uses the **default** space to set space policies.\n\nExamples:\n\n.. code-block:: bash\n\n   ##############\n   # Example 1  #\n   ##############\n   # files uploaded without selecting a space will end up in the replica space unless there is a forced overwrite in the target directory\n\n   # point to the replica space as default policy\n   eos space config default space.policy.space=replica\n   # configure 2 replicas in the replica space\n   eos space config replica space.policy.nstripes=2\n   eos space config replica space.policy.layout=replica\n\n\n   ##############\n   # Example 2  #\n   ##############\n   # files uploaded selecting the rep4 space will be stored with 4 replicas, if non space is selected they will get the default for the target directory or the default space\n\n   # define a space with 4 replica policy\n   eos space config rep4 space.policy.nstripes=4\n   eos space config rep4 space.policy.layout=replica\n\n\n   ##############\n   # Example 3  #\n   ##############\n   # When the policy is consulted for a write operation, alternative storage spaces can be defined as fallbacks.\n   # These alternatives are used when the available space in the primary storage, as determined by the policy, is exceeded.\n   # For instance, if files are initially placed in an NVME storage space and it becomes full, the system can fall back\n   # to an alternative space, such as an HDD. If the HDD space is also exhausted, another fallback, like OLDHDD, can be tried.\n   # If all alternative spaces are exhausted, the policy will revert to the initially selected space, and the placement\n   # operation will fail due to insufficient space.\n   # It is important that all spaces have a proper nominalsize configuration since this is used to determine which one can\n   # be used. It is also important to have the full layout configurations for each alternative space because they cannot be taken from\n   # directory based sys.forced.* attributes, some for IO policies.\n\n\n   # define as default the NVME space\n   eos space config default space.policy.space=NVME\n\n   # define the HDD space as alternative to the NVME space\n   eos space config NVME space.policy.altspaces=HDD,OLDHDD\n\n   # define several alternative spaces HDD,OLDHDD if the NVME space has no nominal bytes left\n   # HDD is tried first and if there are nominal bytes left that one is taken\n   eos space config NVME space.policy.altspaces=HDD,OLDHDD\n\nExample 3 describes the configuration of alternative spaces. An alternative space is selected if the configured target space for a\nfile has no space left and there is an *space.policy.altspaces* entry in the configured target space. Read the comment on top of this example\nfor more information!\n\nStorage Tiering\n^^^^^^^^^^^^^^^\nCurrently we support two tiering scenarios:\n\n1) conversion on read\n\n.. code-block:: bash\n\n   flash <= disk\n\nWhen a file is stored on **flash**, it should be read there, when a file is stored on **disk**, it should before\nthe read moved to the **flash** space before the file is opened by the application. Since this applies to existing files\nthe policy is not defined on the space level but on parent directories via an extended attribute.\n\nAn example configuration who move the file to **flash** with a single replica layout would be:\n\n.. code-block:: bash\n\n   attr set sys.forced.readconversion=flash:00650012\n\nThis can be combined with an **LRU** policy which moves files of certain sizes and access times to the **disk** tier.\nAs a result you have a dynamic tiering setup where **active** files are on the **flash** space, while\ncold data is on the **disk** tier (which could be for example erasure encoded).\n\n2) conversion on update  :\n\n.. code-block:: bash\n\n   flash <= disk(ec)\n\nWhen a file is stored on an erasure coded space **disk**, it is not possible to update files. This tiering\noption allows to convert a file from an erasure coded layout to a replica based layout e.g. on the **flash** space and\nfiles get updated there. Also this policy is defined on the parent directory via an extended attribute:\n\n.. code-block:: bash\n\n   attr set sys.forced.updateconversion=flash:00650012\n\nThis can be again combined with an **LRU** policy which move certain files (based on size, extension, access time) to\nan erasure coded **disk** space.\n\n.. ::note\n\n   Both methods use the EOS converter micro service. The client triggers with an **open** request a conversion\n   based on the given policy and receives a WAITRESP in the XRootD protocol. Once the conversion jobs finishes,\n   the client receives a response and retries the open. Due to the missing logic in HTTP clients, this works only\n   with XRootD protocol!\n\nLocal Overwrites\n^^^^^^^^^^^^^^^^\n\nThe space policies are overwritten by the local extended attribute\nsettings of the parent directory\n\n\n.. epigraph::\n\n    ================= =======================================================\n    key               local xattr\n    ================= =======================================================\n    layout            sys.forced.layout, user.forced.layout\n    nstripes          sys.forced.nstripes, user.forced.nstripes\n    checksum          sys.forced.checksum, user.forced.checksum\n    blockchecksum     sys.forced.blockchecksum, user.forced.blockchecksum\n    blocksize         sys.forced.blocksize, user.forced.blocksize\n    iopriority        sys.forced.iopriority:r\\|w\n    iotype            sys.forced.iotype:r\\|w\n    bandwidth         sys.forced.bandwidth:r\\|w\n    schedule          sys.forced.schedule:r\\|w\n    ================= =======================================================\n\n\nDeleting space policies\n^^^^^^^^^^^^^^^^^^^^^^^\n\nPolicies are deleted by setting a space policy with\n[value=remove]{.title-ref} e.g.\n\n.. code-block:: bash\n\n   # delete a policy entry\n   eos space config default space.policy.layout=remove\n\n   # delete an application bandwidth entry\n   eos space config default space.bw.myapp=remove\n\n\nDisplaying space policies\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nPolicies are displayed using the `space status` command:\n\n.. code-block:: bash\n\n   eos space status default\n\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   autorepair                       := off\n   ...\n   policy.blockchecksum             := crc32c\n   policy.blocksize                 := 1M\n   policy.checksum                  := adler\n   policy.layout                    := replica\n   policy.nstripes                  := 2\n   policy.bandwidth:r               := 100\n   policy.bandwidth:w               := 200\n   policy.iotype:w                  := direct\n   policy.iotype:r                  := direct\n   ...\n   bw.myapp                         := 100\n   bw.eoscp                         := 200\n   ...\n\n\n.. index::\n   pair: Interfaces; Conversion policies\n\nAutomatic Conversion Policies\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAutomatic policy conversion policies allow to trigger a conversion job\nunder two conditions:\n\n*   a new file is created with a complete layout (all required\n    replicas/stripes are created) (use case TIERING/IO optimization)\n*   an existing file is injected with a complete layout (all required\n    replicas/stripes are created) (use case TAPE recall)\n*   an existing file is accessed  (use case TIERING)\n\nAutomatic conversion policy hooks are triggered by the\nReplicationTracker. You find conversions triggered in the\n**ReplicationTracker.log** logfile.\n\nTo use automatic conversion hooks one has to enable policy conversion in\nthe **default** space:\n\n.. code-block:: bash\n\n   eos space config default space.policy.conversion=on\n\n\nTo disable either remove the entry or set the value to off:\n\n.. code-block:: bash\n\n   #remove\n   eos space config default space.policy.conversion=remove\n   #or disable\n   eos space config default space.policy.conversion=off\n\nIt takes few minutes before the changed state takes effect!\n\nTo define a policy conversion whenever a file is uploaded for a specific\nspace you configure:\n\n.. code-block:: bash\n\n   # whenever a file is uploaded to the space **default** a conversion is triggered into the space **replicated** using a **replica::2** layout.\n   eos space config default space.policy.conversion.creation=replica:2@replicated\n\n   # alternative declaration using a hex layout ID\n   eos space config default space.policy.conversion.creation=00100112@replicated\n\nAlso make sure that the converter is enabled:\n\n.. code-block:: bash\n\n   # enable the converter\n   eos space config default space.converter=on\n\n\nTo define a policy conversion whenever a file is injected into a\nspecific space you configure:\n\n.. code-block:: bash\n\n   # whenever a file is injected to the space **ssd* a conversion is triggered into the space **spinner** using a **raid6:10** layout.\n   eos space config ssd space.policy.conversion.injection=raid6:10@spinner\n\n   # alternative declaration using a hex layout ID: replace raid6:10 with the **hex layoutid** (e.g. see file info of a file).\n\n\n.. warning:: You cannot change the file checksum during a conversion job! Make sure source and target layout have the same checksum type!\n\nYou can define a policy when a file has been accessed (optional with certain size constraints) in a given space:\n\n   # whenever a file is accessed in the space **ec** a conversion is triggered into the space **ssd** using a **replica:2** layout.\n   eos space config ssd space.policy.conversion.injection=replica:2@ssd\n\nYou can define a minimum or maximum size criteria to apply automatic\npolicy conversion depending on the file size.\n\n.. code-block:: bash\n\n   # convert files on creation only if they are at least 100MB\n   eos space config ssd space.policy.conversion.creation.size=>100000000\n\n   # convert files on creation only if they are smaller than 1024 bytes\n   eos space config ssd space.policy.conversion.creation.size=<1024\n\n   # convert files on injection only if they are bigger than 1G\n   eos space config ssd space.policy.conversion.injection.size=>1000000000\n\n   # convert files on injection only if they are smaller than 1M\n   eos space config ssd space.policy.conversion.injection.size=<1000000\n\n   # convert files on access only if they are bigger than 1G\n   eos space config ec space.policy.conversion.access.size=>1000000000\n\n   # convert files on access only if they are smaller than 1M\n   eos space config ec space.policy.conversion.access.size=<1000000\n\n.. index::\n   pair: Shared Filesystem; Redirection\n\n\nShared Filesystem Redirection\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhen all FSTs in a space store data into a shared filesystem and clients\nmight have access to all the data for reading, one can enable the\nredirection to a local filesystem:\n\n.. code-block:: bash\n\n   # define the local redirection policy in the given space called 'nfs'\n   eos space config nfs space.policy.localredirect=1\n   # or\n   eos space config nfs space.policy.localredirect=always\n\n   # define local redirection on a per directory basis\n   eos attr set sys.forced.localredirect.nfs=1\n   # or\n   eos attr set sys.forced.localredirect.nfs=always\n\nPlease note: a space defined policy overwrites any directory policy.\n\nLocal redirection is currently supported for single replica files. It is\ndisabled for PIO access with *eoscp* (default)), but works with *xrdcp*\nand *eoscp -0*. If the client does not see the shared filesystem, the\nclient will fall back to the MGM and read with the FST (due to an XRootD bug\nthis is currently broken). If the client\nsees the shared filesystem but cannot read it, the client will fail.\n\nOne can manually select/disable local redirection using a CGI tag:\n\n.. code-block:: bash\n\n   # enable local redirection via CGI\n   root://localhost//eos/shared/file?eos.localredirect=1\n\n   # disable local redirection via CGI\n   root://localhost//eos/shared/file?eos.localredirect=0\n\nRedirections are accounted in the *eos ns stat* accounting as failed and\nsuccessful redirection on open:\n\n.. code-block:: bash\n\n   eos ns stat | grep RedirectLocal\n   all OpenFailedRedirectLocal             0     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-\n   all OpenRedirectLocal                  14     0.00     0.00     0.00     0.00     -NA-      -NA-     -NA-     -NA-\n\n.. index::\n   pair: Interfaces; IO Priorities\n\nIf you have certain machines which have access to the shared filesystem, but not all of them, you can configure local redirection as 'optional'.\n\n.. code-block:: bash\n\n   # define the local redirection policy in the given space called 'nfs' as optional\n   eos space config nfs space.policy.localredirect=optional\n\nThen local redirection will occur only on clients which have the XRD_APPNAME or EOSAPP environment variable (for xrootd and eos transfers, respectively)\nset to '...#<sharedfsname>', which must match the 'sharedfs' property defined on FSTs which use a shared filesystem for backend storage.\nE.g. if you have a shared filesystem called nfs1 only clients with application tag '...#nfs1' will receive a local redirect!\n\nIO Priorities\n--------------\n\nIO priorities are currently only supported by devices using the CFQ\n(CentOS7) or BFQ (Centos8) scheduler for reads and direct writes. You\ncan figure out which scheduler is used by inspecting:\n\n.. code-block:: bash\n\n   cat /sys/block/*/queue/scheduler\n\n\nSupported IO Priorities\n^^^^^^^^^^^^^^^^^^^^^^^^\n.. epigraph::\n\n    ================== =============\n    name               level\n    ================== =============\n    real-time          0-7\n    best-effort        0-7\n    idle               0\n    ================== =============\n\n.. index::\n   pair: IO Priorities;  Real-time\n\n\nReal-time Class\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThis is the real-time I/O class. This scheduling class is given higher\npriority than any other class: processes from this class are given first\naccess to the disk every time. Thus, this I/O class needs to be used\nwith some care: one I/O real-time process can starve the entire system.\nWithin the real-time class, there are 8 levels of class data (priority)\nthat determine exactly how much time this process needs the disk for on\neach service. The highest real-time priority level is 0; the lowest is\n7. The priority is defined in EOS f.e. as *rt:0* or *rt:7*.\n\n.. index::\n   pair: IO Priorities;  Best-Effort\n\nBest-Effort Class\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\nThis is the best-effort scheduling class, which is the default for any\nprocess that hasn\\'t set a specific I/O priority.The class data\n(priority) determines how much I/O bandwidth the process will get.\nBest-effort priority levels are analogous to CPU nice values. The\npriority level determines a priority relative to other processes in the\nbest-effort scheduling class. Priority levels range from 0 (highest) to\n7 (lowest). The priority is defined in EOS f.e. as *be:0* or *be:4*.\n\n.. index::\n   pair: IO Priorities;  Idle\n\nIdle Class\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThis is the idle scheduling class. Processes running at this level get\nI/O time only when no one else needs the disk. The idle class has no\nclass data, but the configuration requires to configure it in EOS as\n*idle:0* . Attention is required when assigning this priority class to a\nprocess, since it may become starved if higher priority processes are\nconstantly accessing the disk.\n\n.. index::\n   pair: IO Priorities;  Setting Priorities\n\nSetting IO Priorities\n^^^^^^^^^^^^^^^^^^^^^^\n\nIO priorities can be set in various ways:\n\n.. code-block:: bash\n\n   # via CGI if the calling user is member of the operator role e.g. make 99 member of operator role\n   eos vid set membership 99 -uids 11\n   # use URLs like \"root://localhost//eos/higgs.root?eos.iopriority=be:0\"\n\n   # as a default space policy for readers\n   eos space config default space.policy.iopriority:r=rt:0\n   # as a space policy\n   eos space config erasure space.policy.iopriority:w=idle:0\n\n   # as a default application policy e.g. for application foo writers\n   eos space config default space.iopriority:w.app:foo=be:4\n\n   # as a space application policy e.g. for application bar writers\n   eos space config erasure space.iopriority:w.app:bar=be:7\n\n\nThe CGI (if allowed via the operator role) is overruling any other\npriority configuration. Otherwise the order of evaluation is shown as in\nthe block above.\n\nFor handling of policies in general (how to show, configure and delete)\nrefer to Space Policies.\n\n.. index::\n   pair: Interfaces; Quota\n   pair: CLI; eos quota\n\nQuota\n-----\n\nThe EOS quota system provides user, group and project quota similar to\nfilesystems like EXT4, XFS ... e.g. quota is expressed as max. number of\ninodes(=files) and maximum volume. The implementation of EOS quota uses the\ngiven inode limit as hard quota, while volume is applied as soft quota e.g.\nit can be slightly exceeded.\n\nQuota is attached to a so called 'quota node'. A quota node defines the\nquota rules and counting for a subtree of the namespace. If the subtree\ncontains another quota node in a deeper directory level quota is rooted\non the deeper node.\n\nAs an example we can define two quota nodes:\n\n.. epigraph::\n\n   ============ =======================\n   Node         Path\n   ============ =======================\n   Quota Node 1 /eos/lhc/raw/\n   Quota Node 2 /eos/lhc/raw/analysis/\n   ============ =======================\n\nA file like ``/eos/lhc/raw/2013/raw-higgs.root`` is accounted for in the first\nquota node, while a file ``/eos/lhc/raw/analysis/histo-higgs.root`` is\naccounted for in the second quota node.\n\n.. index::\n   pair: Quota; Listing Quotas\n\n\nThe quota system is easiest explained lookint at the output of\na **quota** command in the EOS shell:\n\n.. code-block:: bash\n\n   eosdevsrv1:# eos quota\n   # _______________________________________________________________________________________________\n   # ==> Quota Node: /eos/dev/2rep/\n   # _______________________________________________________________________________________________\n   user       used bytes logi bytes used files aval bytes aval logib aval files filled[%]  vol-status ino-status\n   adm        2.00 GB    1.00 GB    8.00 -     2.00 TB    1.00 TB    1.00 M-    0.00       ok         ok\n\nThe above configuration defines user quota for user ``adm`` with 1 TB of volume\nquota and 1 Mio inodes under the directory subtree ``/eos/dev/plain``.\nAs you may notice EOS distinguishes between logical bytes and (physical) bytes.\nImagine a quota node subtree is configured to store 2 replica for each file,\nthen a 1 TB quota allows you effectively to store 2 TB of raw data.\n\n.. important::\n   All quotas set via the 'quota set' command define volume in raw bytes\n   by default, and EOS displays both logical and raw bytes values, based\n   on the layout definition on the quota node. The environment variable\n   'EOS_MGM_QUOTA_SET_BY_LOGICAL', when set, changes this default to be\n   in terms of logical bytes instead.\n\n.. important::\n   If a quota node contains files with mixed layout, say 2 replica and RAID 6,\n   the raw and logical bytes usage will reflect it accordingly, i.e., you'd see\n   less than twice the logical bytes as raw bytes used. The quota system\n   enforces the *logical bytes usage*, such that in a directory with RAID6 layout,\n   adding files of 2 replica layout may cause the raw usage to exceed the amount\n   set in the quota node without actually running out of quota. You only run out\n   of quota when the logical space is exceeded.\n\nThe volume and inode status is displayed as 'ok' if there is quota left for\nvolume/inodes. If there is less than **5%** left, 'warning' is displayed,\nif there is none left 'exceeded'. If volume and/or inode quota is set to 0\n'ignored' is displayed. In this case a quota setting of 0 signals not to apply\nthe quota however if both are '0' the referenced UID/GID has no quota.\n\nThere are three types of quota defined in EOS: user, group & project quota!\n\n.. index::\n   pair: Quota; User Quota\n\nUser Quota\n^^^^^^^^^^\n\nUser quota defines volume/inode quota based on user id  UID.\nIt is possible to combine user and group quota on a quota node.\nIn this case both have to 'ok' e.g. provide enough space for a file placmment.\n\n.. index::\n   pair: Quota; Group Quota\n\nGroup Quota\n^^^^^^^^^^^\nGroup quota defines volume/inode quota based on group id GID.\nAs described before it is possible to combine group and user quota.\nIn this case both have to allow file placement.\n\n.. index::\n   pair: Quota; Project Quota\n\nProject Quota\n^^^^^^^^^^^^^\nProject quota books all volume/inode usage under the project subtree to a single\nproject account. E.g. the recycle bin uses this quota type to measure a subtree\nsize. In the EOS shell interface project quota is currently defined setting\nquota for group 99:\n\n.. code-block:: bash\n\n   eosdevsrv1:# eos set -g 99 -p /eos/lhc/higgs-project/ -v 1P -i 100M\n\n.. index::\n   pair: Quota; Space Quota\n\nSpace Quota\n^^^^^^^^^^^\nIt is possible to set a physical space restriction using the space parameter **nominalsize**\n\n.. code-block:: bash\n\n   # restrict the physical space usage to 1P\n   eosdevsrv1:# eos space config default space.nominalsize=1P\n\nThe restriction is only used, if the connected user is not in the **sudoer** list. The current usage and space setting is\ncached for 30s e.g. it might take up to 30s until any change may take effect.\n\n.. index::\n   pair: Quota; Quota Enforcement\n\nQuota Enforcement\n^^^^^^^^^^^^^^^^^\nQuota enforcement is applied when new files are placed and when files in RW mode\nare closed e.g. EOS can reject to store a file if the quota exceeds during an\nupload. If user and group quota is defined, both are applied.\n\nQuota Command Line Interface\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. index::\n   pair: Quota; List Quota\n\nList Quota\n\"\"\"\"\"\"\"\"\"\"\nTo see your quota as a user use:\n\n.. code-block:: bash\n\n   eosdevsrv1:# eos quota\n\nTo see quota of all users (if you are an admin):\n\n.. code-block:: bash\n\n   eosdevsrv1:# eos quota ls\n\nTo see the quota node for a particular directory/subtree:\n\n.. code-block:: bash\n\n   eosdevsrv1:# eos quota ls /eos/lhc/higgs-project/\n\n .. index::\n   pair: Quota; Set Quota\n\nSet Quota\n\"\"\"\"\"\"\"\"\"\"\n\nThe syntax to set quota is:\n\n.. code-block:: bash\n\n   eos quota set -u <uid>|-g <gid> [-v <bytes>] [-i <inodes>] -p <path>\n\nThe <uid>, <gid> parameter can be numerica or the real name. Volume and Inodes\ncan be specified as **1M**, **1P** etc. or a plain number.\n\n.. ::note\n\n   To set project quota use GID 99!\n\n.. index::\n   pair: Quota; Delete Quota\n\nDelete Quota\n\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nA quota setting can be removed using:\n\n.. code-block:: bash\n\n   eos quota rm -u <uid> |-g <gid> -p <path>\n\nOne has to specify to remove the user or the group quota, it is not possible\nto remove both with a single command.\n\n.. index::\n   pair: Quota; Delete Quota Node\n\nDelete Quota Node\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\nSometimes it is necessary to remove completely a quota node.\nThis can be done via:\n\n.. code-block:: bash\n\n   eos quota rmnode -p <path>\n\nThe command will ask for a security code. Be aware the quota is not recalculated\nfrom scratch if the deletion of a node would now leave the accounting to an\nupstream node.\n\n\n.. index::\n   pair: Interfaces; Permissions\n   pair: CLI; eos chmod\n   pair: CLI; eos chown\n   pair: CLI; eos acl\n\n\nPermissions\n-----------\n\nOverview\n^^^^^^^^\n\nThe EOS permission system is based on a combination of **ACLs**  and **POSIX** permissions.\n\nThere are two major differences to traditional storage systems:\n\n#. Files don't carry their permissions (only the ownership for quota accounting). They inherit the permissions from the parent directory.\n#. Permissions are only checked in the direct parent, EOS is not walking through the complete directory hierarchy.\n#. For ACLs, a directory ACL applies to all files, except those who have their own ACL.\n\n.. index::\n   pair: Permissions; UNIX Permissions\n\nUNIX Permissions\n\nEOS allows to set user, group and other permissions for read write and browsing defined\nby ``'r'(4), 'w'(2), 'x'(1)``, e.g. ``777 ='rwx'``.\n\nUnlike in POSIX the S_ISGID (2---) indicates that any new directory created should automatically inherit all the\nextended attributes defined at creation time from the parent directory.\n\nIf the parent attributes are changed after creation, they are not automatically\napplied to its children. The inheritance bit is always added to any *chmod* automatically. >\n\nAll modes for *chmod* have to be given in octal format. For details see **eos chmod --help**.\n\nThe S_ISVTX bit (1---) is displayed whenever a directory has any extended attribute defined.\n\n.. index::\n   pair: Permissions; ACLs\n\nACLs\n^^^^\nACLs are defined on the directory or file level via the extended attributes.\n\n.. code-block:: bash\n\n   sys.acl=<acllist>\n   user.acl=<acllist>\n   sys.eval.useracl=1    # when set, user.acl is evaluated\n\n.. note::\n\n   For efficiency, file-level ACLs should only be used sparingly, in favour of directory-level ACLs.\n\nThe sys.acl attribute can only be defined by SUDO members or by users which have an 'A' grant in the existing ACL of the same directory/file.\nThe user.acl attribute can be defined by the **owner** or SUDO members. It is only evaluated if the sys.eval.useracl attribute is set on the\ndirectory or file. When a file has its own user.acl and sys.eval.useracl, the file-level user.acl overrides the directory's user.acl for that file.\n\nThe sys.acl/user.acl attributes are inherited from the parent at the time a directory is created. Subsequent changes to a directory's ACL\ndo not automatically apply to child directories.\n\n**Rule evaluation:** Rules are processed in order: sys.acl first, then user.acl (if sys.eval.useracl is set). If the client presents a valid\ntoken, the token's ACL replaces both sys.acl and user.acl for that request. A rule matches when the subject matches the client's uid, any of\ntheir gids, e-group membership, or z (everyone). The k: subject matches the client's authentication key. Permissions are accumulated; denials\nand reallows (+d, +u) are applied in a final pass, with reallows overriding earlier denials. The +d and +u modifiers are only effective in sys.acl.\n\n<acllist> is defined as a comma separated list of rules:\n\n.. code-block:: bash\n\n   <acllist> = <rule1>,<rule2>...<ruleN>\n\nA rule is defined in the following way:\n\n.. code-block:: bash\n\n   <rule> = u:<uid|username>|g:<gid|groupname>|egroup:<name>|k:<key>|z:<permissions>\n\nA rule has three colon-separated fields. It starts with the type of rule:\nUser (u), Group (g), eGroup (egroup), Key (k) or all (z). The second field specifies the name or\nthe unix ID of user/group rules, the eGroup name for eGroups, or the authentication key for k.\nThe last field contains the permission flags.\n\n\n.. index::\n   pair: ACLs; Rule Syntax\n\n\nThe following tags compose a rule:\n\n.. epigraph::\n\n   === =========================================================================\n   tag definition\n   === =========================================================================\n   r   grant read permission\n   w   grant write permission (implies update)\n   wo  grant write-once permission (create files, no delete)\n   x   grant browsing permission\n   d   delete permission (implicit when not denied)\n   u   grant update permission (modify existing files)\n   m   grant change mode permission\n   !m  forbid change mode operation\n   !d  forbid deletion of files and directories\n   +d  overwrite a '!d' rule and allow deletion (sys.acl only)\n   !u  forbid update of files\n   +u  overwrite a '!u' rule and allow updates (sys.acl only)\n   q   grant 'set quota' permissions on a quota node (sys.acl only)\n   c   grant 'change owner' permission on directory children\n   i   set the immutable flag\n   a   grant archiving permission (sys.acl only)\n   A   grant sys.acl modification permissions (sys.acl only)\n   X   grant sys.* modification permissions (sys.acl only, does not include sys.acl)\n   p   grant prepare/workflow permission (sys.acl only)\n   t   grant token issuing permission; allows the referenced user/group/egroup to\n       issue tokens on that path (sys.acl only)\n   === =========================================================================\n\n\n\n\nActually, every single-letter permission with the exception of change owner (c) can\nbe explicitly denied ('!'), e.g. '!w!r, re-granted ('+'). Change owner\npermission is only explicitly enabled on grant, so it is denied by default.\nThe permissions a, A, X, p, t and q are only valid in sys.acl; they are ignored when specified in user.acl.\nDenials persist after all other rules have been evaluated, i.e. in 'u:fred:!w!r,g:fredsgroup:wrx' the user \"fred\"\nis denied reading and writing although the group he is in has read+write access.\nRights can be re-granted (in sys.acl only) even when denied by specifying e.g. '+d'. Hence,\nwhen sys.acl='g:admins:+d' and then user.acl='z:!d' are evaluated,\nthe group \"admins\" is granted the 'd' right although it is denied to everybody else.\n\nA complex example is shown here:\n\n.. code-block:: bash\n\n   sys.acl=\"u:300:rw!u,g:z2:rwo,egroup:eos-dev:rwx,u:dummy:rwm!d,u:adm:rwxmqc\"\n\n   # user id 300 can read + write, but not update\n   #\n   # group z2 can read + write-once (create new files but can't delete)\n   #\n   # members of egroup 'eos-dev' can read & write & browse\n   #\n   # user name dummy can read + write into directory and modify the permissions\n   # (chmod), but cannot delete directories inside which are not owned by him.\n   #\n   # user name adm can read,write,browse, change-mod, set quota on that\n   # directory and change the ownership of directory children\n\n.. note::\n\n   Write-once and '!d' or '!u' rules remove permissions which can only be regained\n   by a second rule adding the '+u' or '+d' flag e.g. if the matching user ACL\n   forbids deletion it is not granted if a group rule does not forbid deletion!\n\n\nIt is possible to write rules, which apply to everyone:\n\n.. code-block:: bash\n\n   sys.acl=\"z:i\"\n\n   # this directory is immutable for everybody\n\n\nThe user.acl (if defined) is evaluated after the sys.acl, e.g. If we have:\n\n.. code-block:: bash\n\n    sys.acl=’g:admins:+d’ and user.acl=’z:!d’\n\ni.e., the group “admins” is granted the 'd' right although it is denied to everybody else in the user.acl.\n\n\nFinally the ACL can be set via either of the following 2 commands, see `eos acl --help` or `eos attr set --help`. From the operational perspective one may prefer the former command as it acts specifically on the element we change (egroup, user ... etc.) instead of re-specifying the whole permission set of rules (`eos attr set` case). `eos acl` set of commands also allow for specific position to place the rule in when creating or modifying a rule. By default rules are appended at the end of the acl, `--front` flag allows to place a rule at the front, and an integer position starting from 1 (which is equivalent to `--front`) can also be used to explicitly move a rule to a specific position via the `--position` argument.\n\n.. note::\n\n   From EOS version 5.1.14 and later the behavior of recursive ACL set has changed, keeping in view of very large directory trees. Previously any recursive ACL set command ensures that no directory creation happens during the ACL set, while this is synchronous for very large trees with millions of directories, one can end up blocking everything else. This is moved to fine-grained locks, with the downside that a directory created during the time ACLs are applied may not see the ACLs being applied, in case their parent hasn't inherited yet. The old synchronous behavior can be restored with a `--with-synchronous-write-lock` flag, though it is really not recommended for very large tree hierarchies.\n\n.. code-block:: bash\n\n   eos attr set sys.acl=<rule_a>,<rule_b>.. /eos/mypath\n   eos acl --sys <rule_c> /eos/mypath\n   eos acl --front <rule_d> /eos/mypath\n   eos acl --position 2 <rule_f> /eos/mypath\n\nThe ACLs can be listed by either of these commands as well:\n\n.. code-block:: bash\n\n   eos attr ls /eos/mypath\n   eos acl -l /eos/mypath\n\n\nIf the operator uses the `eos acl --sys <rule> /eos/mypath` command, the `<rule>` is composed as follows:\n`\\[u|g|egroup|k\\]:<identifier>\\[:|=\\]<permission>` . The second delimiter `[:|=]` can be a `:` for modifying permissions\nor \"=\" for setting/overwriting permission. Finally a <permission> itself can be added using the \"+\" or removed using the \"-\" operators.\n\nFor example:\n\n.. code-block:: bash\n\n   $ eos attr ls /eos/mypath\n   sys.acl=\"u:99999:rw,egroup:mygroup:rw\"\n   #\n   # if you try to set the deletion permission using ':' modification sign:\n   $ eos acl --sys 'egroup:mygroup:!d' /eos/mypath\n   #\n   # you will get an error since there is no deletion permission defined yet in the original ACL (i.e. nothing to be modified), but\n   # one can add this new !d permission to the existing ACLs by the '+' operator:\n   $ eos acl --sys 'egroup:mygroup:+!d' /eos/mypath\n   #\n   -->\n   #\n   $ eos attr ls /eos/mypath\n   sys.acl=\"egroup:mygroup:rw!d,u:99999:rw\"\n   #\n   # one can also remove this permission by the '-' operator:\n   $eos acl --sys 'egroup:mygroup:-!d' /eos/mypath\n   -->\n   #\n   $ eos attr ls /eos/mypath\n   sys.acl=\"u:99999:rw,egroup:mygroup:rw\"\n   #\n   # or set completely new permission, overwriting all by '=':\n   eos acl --sys 'egroup:mygroup=w' /eos/mypath\n   -->\n   #\n   $ eos attr ls /eos/mypath\n   sys.acl=\"u:99999:rw,egroup:mygroup:w\"\n   # append a new rule to the end\n   $ eos acl --sys u:1002=\\!w /eos/mypath\n   $ eos attr ls /eos/mypath\n   sys.acl=\"u:99999:rw,egroup:mygroup:rw,u:1002:!w\"\n\n   # Move a rule to the front, the full rule needs to be specified\n   $ eos acl --front egroup:mygroup=rw /eos/mypath\n   $ eos attr ls /eos/mypath\n   sys.acl=\"egroup:mygroup:rw,u:99999:rw,u:1002:!w\"\n\n   # Add a new rule at a specific position\n   $ eos acl --position 2  egroup:mygroup2=rwx /eos/mypath\n   $ eos attr ls /eos/mypath\n   sys.acl=\"egroup:mygroup:rw,egroup:mygroup2:rwx,u:99999:rw,u:1002:!w\"\n\n\n.. note::\n\n   * The \"-r 0 0\" can be used to map your account with the sudoers role. This has to be assigned to your account on the EOS instance by the service manager, see `eos vid ls`), e.g. `eos -r 0 0 acl --sys 'egroup:mygroup:!d' /eos/mypath`.\n   * If no '--sys' or '--user' is specified, by default the `eos acl` sets '--sys' permissions.\n\n.. index::\n   pair: Permissions; Validity\n   pair: Permissions; Ordering\n\nValidity of Permissions\n^^^^^^^^^^^^^^^^^^^^^^^\n\nFile Access\n\"\"\"\"\"\"\"\"\"\"\"\nA file ACL (if it exists), or the directory's ACL is evaluated\nfor access rights.\n\nA user can read a file if the ACL grants 'r' access\nto the user's uid/gid pair. If no ACL grants the access,\n[the directory's] UNIX permissions are evaluated for a matching 'r' permission bit.\n\nA user can create a file if the parent directory grants 'w' access via the ACL\nrules to the user's uid/gid pair. A user cannot overwrite a file if the ACL\ngrants 'wo' permission. If the ACL does not grant the access, UNIX permissions\nare evaluated for a matching 'w' permission bit.\n\n.. note::\n\n   The root role (uid=0 gid=0) can always read and write any file.\n   The daemon role (uid=2) can always read any file.\n\nFile Deletion\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nA file can be deleted if the parent directory grants 'w' access via the ACL\nrules to the user's uid/gid pair. A user cannot delete a file,\nif the ACL grants 'wo' or '!d' permission.\n\n.. note::\n\n   The root role (uid=0 gid=0) can\n   always delete any file.\n\nFile Permission Modification\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nFile permissions cannot be changed, they are automatically inherited from the\nparent directory.\n\nFile Ownership\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nA user can change the ownership of a file if he/she is member of the SUDO group.\nThe root, admin user and admin group role can always change the ownership of a\nfile. See **eos chown --help**  for details.\n\nDirectory Access\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nA user can create a directory if they have the UNIX 'wx' permission, or the ACL\nrules grant the 'w' or 'wo' permission. The root role can always create any directory.\n\nA user can list a directory if the UNIX permissions grant 'rx' or the ACL\ngrants 'x' rights.\n\n.. note::\n\n   The root, admin user and admin group role can always\n   browse directories.\n\nDirectory Deletion\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nA user can delete a directory if he/she is the owner of the directory.\nA user can delete a directory if he/she is not the owner of that directory\nin case 'UNIX 'w'permission are granted and '!d' is not defined by a matching\nACL rule.\n\n.. note::\n\n   The root role can always delete any directory.\n\n.. warning::\n\n   Deletion only works if directories are empty!\n\nDirectory Permission Modification\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nA user can modify the UNIX permissions if they are the owner of the file\nand/or the parent directory ACL rules grant the 'm' right.\n\n.. note::\n\n   The root, admin\n   user and admin group role can always modify the UNIX permissions.\n\nDirectory ACL Modification\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nA user can modify a directory's system ACL, if they are a member of the SUDO group or the have an A grant.\nA user can modify a directory's user ACL, if they are the owner of the directory or\na member of the SUDO group.\n\nDirectory Ownership\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe root, admin user and admin group role can always change the directory\nowner and group.\nA normal user can change the directory owner if the system ACL allows this, or if the user ACL allows it *and* they change the owner to themselves.\n\n.. warning::\n\n   Otherwise, only privileged users can alter the ownership.\n\nQuota Permission\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nA user can do 'quota set' if he is a sudoer, has the 'q' ACL permission set on\nthe quota node or on the proc directory ``/eos/<instance>/proc``.\n\nRichacl Support\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nOn systems where \"richacl\"s (a more sophisticated ACL model derived from NFS4 ACLs) are supported, e.g. CentOS7,\nthe translation between EOS ACLs and richacls is by nature incomplete and not always two-ways:\n\nan effort is made for example to derive a file's or directory's :D: (RICHACL_DELETE) right from the parent's 'd' right,\nwhereas the :d: (RICHACL_DELETE_CHILD) right translates to the directory's own 'd'.\nThis helps applications like samba; however, setting\n:D: (RICHACL_DELETE) on a directory does not affect the directory's parent as permissions for individual\nobjects cannot be expressed in EOS ACLs;\n\nthe EOS 'm' (change mode) right becomes :CAW: (RICHACE_WRITE_ACL|RICHACE_WRITE_ATTRIBUTES|RICHACE_WRITE_NAMED_ATTRS);\n\nthe EOS 'u' (update) right becomes :p: (RICHACE_APPEND_DATA), although this is not really equivalent. It implies that :w: (RICHACE_WRITE_DATA) only grants writing of new files,\nnot rewriting parts of existing files.\n\nRichacls are created and retrieved using the {get,set}richacl commands and the relevant richacl library functions\non the fusex-mounted EOS tree. Those utilities act on the user.acl attribute and ignore sys.acl.\n\n\nHow to setup a shared scratch directory\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIf a directory is group writable one should add an ACL entry for this group\nto forbid the deletion of files and directories to non-owners and allow\ndeletion to a dedicated account:\n\nE.g. to define a scratch directory for group 'vl' and the deletion\nuser 'prod' execute:\n\n.. code-block:: bash\n\n   eos attr set sys.acl=g:vl:!d,u:prod:+d /eos/dev/scratchdisk\n\nThe default unix way is to use the VTX bit using the `chmod` interface!\n\nHow to setup a shared group directory\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nA directory shared by a <group> with variable members should be setup like this:\n\n.. code-block:: bash\n\n   chmod 550 <groupdir>\n   eos attr set sys.acl=\"egroup:<group>:rw!m\"\n\n.. index::\n   pair: Permissions; Sticky Ownership\n\nSticky Ownership\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe ACL tag sys.owner.auth allows to tag clients acting as the owner of a directory. The value normally is composed by the authentication method and the user name or can be a wildcard.\nIf a wild card is specified, everybody resulting in having write permission can use the sticky ownership and write into a directory on behalf of the owner e.g. the file is owned by the directory\nowner and not by the authenticated client and quota is booked on the directory owner.\n\n.. code-block:: bash\n\n   eos attr set sys.owner.auth=\"krb5:prod\"\n   eos attr set sys.owner.auth=\"*\"\n\n\n.. index::\n   pair: Permissions; Sys Masks\n\n\nPermission Masks\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nA permission mask which is applied on all chmod requests for directories can be defined via:\n\n.. code-block:: bash\n\n   sys.mask=<octal-mask>\n\nExample:\n\n.. code-block:: bash\n\n   eos attr set sys.mask=\"770\"\n   eos chmod 777 <dir>\n   success: mode of file/directory <dir> is now '770'\n\nWhen the mask attribute is set the !m flag is automatically disabled even if it is given in the ACL.\n\n.. index::\n   pair: Permissions; eos acl\n\nSpace ACLs\n\"\"\"\"\"\"\"\"\"\"\n\nIt is possible to define an extra set of ACLs on the space level, which applies to all directories referencing this space via *sys.forced.space*. If no space is referenced in a directory, ACLs from the default space will be added.\n\nACLs have 4 add-on modes:\n* '=<' first evaluated (position left)\n* '=>' last evaluated (position right)\n* '=|'use if no other sys.acl is present in a directory\n* '=' always overwrite directory sys.acl\n\nFor example ACLs are configured in a space like:\n\n.. code-block:: bash\n\n                # insert space ACL on the left position (first evaluated)\n                space config default space.attr.sys.acl=<u:poweruser:rwxqmcXA\n                # insert space ACL on the right position (last evaluated)\n                space config default space.attr.sys.acl=>u:poweruser:rwxqmcXA\n                # user space ACL if there is no sys.acl on the referenced directory\n                space config default space.attr.sys.acl=|u:poweruser:rwxqmcXA\n                # overwrite all directory ACLs with the space ACL\n                space config default space.attr.sys.acl=u:poweruser:rwxqmxcXA\n\nSpace ACLs are removed in the usual manner:\n\n.. code-block:: bash\n\n                # remove space ACL\n                space config rm default space.attr.sys.acl\n\nSpace ACLs are shown using:\n\n.. code-block:: bash\n\n                # show space configuration\n                space status default\n\nACL CLI\n\"\"\"\"\"\"\"\n\nTo provide atomic add,remove and replacement of permissions one can take advantage of the ``eos acl`` command instead of modifying directly the `sys.acl` attribute:\n\n.. code-block:: bash\n\n   Usage: eos acl [-l|--list] [-R|--recursive][--sys|--user] <rule> <path>\n\n       --help           Print help\n   -R, --recursive      Apply on directories recursively\n   -l, --lists          List ACL rules\n       --user           Set user.acl rules on directory\n       --sys            Set sys.acl rules on directory\n   <rule> is created based on chmod rules.\n   Every rule begins with [u|g|egroup] followed with : and identifier.\n\n   Afterwards can be:\n   = for setting new permission .\n   : for modification of existing permission.\n\n   This is followed by the rule definition.\n   Every ACL flag can be added with + or removed with -, or in case\n   of setting new ACL permission just enter the ACL flag.\n\n.. index::\n   pair: Permissions; Anonymous Access\n   pair: Permissions; Public Access\n\nAnonymous Access\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nAnonymous access can be allowed by configuring unix authentication (which maps by default everyone to user nobody). If you want to restrict anonymous access to a certain domain you can configure this via the ``access`` interface:\n\n.. code-block:: bash\n\n   eos access allow domain nobody@cern.ch\n\nAs an additional measure you can limit the deepness of the directory tree where anonymous access is possible using the ``vid`` interface e.g. not more than 4 levels:\n\n.. code-block:: bash\n\n   eos vid publicaccesslevel 4\n\n\nThe default value for the publicaccesslevel is 1024.\n\n\n.. index::\n   pair: Interfaces; Routing\n   pair: CLI; eos route\n\n\nRouting\n-------\n\nThe EOS route system provides a method to redirect paths within an existing namespace to an external namespace.\nIt can be thought of as symbolic links that allow clients to connect to another EOS instance.\n\nThis can be used to create a parent MGM that contains references to other EOS instances in a tree like structure,\nor to connect EOS namespaces together in a mesh like manner.\n\n`vid` policy and other access control still applies as if a user were connecting directly.\n\nA route is defined as a path, that maps to a remote hostname and port, that is the MGM of a remote EOS namespace.\nAccess to this path is via a redirect at the HTTP or xrootd level, and will incur some latency.\n\nWhen the latency penalty of the redirect is not desired, it's better to cache the redirect or use an autofs(8)\nor similar automounting solution for the paths.\n\nThe link always links to the root of the connected namespace.\n\nAs an example we can define three routes:\n\n.. epigraph::\n\n   ====================================== =======================\n    Path                                   Destination\n   ====================================== =======================\n   /eos/test-namespace-1                   test-mgm-1:1094:8000\n   /eos/test-namespace-2                   test-mgm-2:1094:8000\n   /eos/test-namespace-1/test-namespace-3  test-mgm-3:1094:8000\n   ====================================== =======================\n\nChanging directory to `/eos/test-namespace-1`, would be akin to connecting directly to the mgm at `test-mgm-1:1094`.\n\n.. code-block:: bash\n\n    EOS Console [root://localhost] |/> route link /eos/test-namespace-1 test-mgm-1:1094:8000\n    EOS Console [root://localhost] |/> route link /eos/test-namespace-2 test-mgm-2:1094:8000\n    EOS Console [root://localhost] |/> route link /eos/test-namespace-1/test-namespace-3 test-mgm-3:1094:8000\n    EOS Console [root://localhost] |/> route ls\n    /eos/test-namespace-1/ => test-mgm-1:1094:8000\n    /eos/test-namespace-1/test-namespace-3/ => test-mgm-3:1094:8000\n    /eos/test-namespace-2/ => test-mgm-2:1094:8000\n\n\nThe above configuration defines defines the path configuration in the example above.\n\nIf a port combination is not specified, the route assumes a xrootd port of 1094, and a http port of 8000.\n\nCreating a link\n^^^^^^^^^^^^^^^\n\nA link is created using the `route link` command. It takes the option of a path and a destination host. Optional\nspecification includes the MGM's xrootd port, and the MGM's http port. Unspecified, they default to 1094 and 8000\nrespectively.\n\n.. code-block:: bash\n\n    EOS Console [root://localhost] |/> route link /eos/test-path eosdevsrv2:1094:8000\n    EOS Console [root://localhost] |/> route ls\n    /eos/test-path/ => eosdevsrv2:1094:8000\n\n\nRemoving a link\n^^^^^^^^^^^^^^^^\n\nA link is removed using the `route unlink` command. Only a path needs to be specified.\n\n.. code-block:: bash\n\n    EOS Console [root://localhost] |/> route unlink /eos/test-namespace-1\n\n\nDisplaying current links\n^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe `route ls` command shows current active links. An asterix is displayed in\nfront of the MGM node which acts as a master for that particular mapping.\n\n.. code-block:: bash\n\n    EOS Console [root://localhost] |/> route ls\n    /eos/test-namespace-1/ => *test-mgm-1:1094:8000\n    /eos/test-namespace-1/test-namespace-3/ => *test-mgm-3:1094:8000\n    /eos/test-namespace-2/ => *test-mgm-2:1094:8000\n\n\nMaking links visible to clients\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nEOS will not display the link in a directory listing. This means it's possible to have an invisible link, and\nstat or fileinfo commands will fail against the link path.\n\nCreating a directory for the path will make it visible to clients, but accounting information will not be accurate until\na client changes into the path and queries again.\n\nConnecting clients\n^^^^^^^^^^^^^^^^^^^\n\nHTTP and xrootd clients can effectively connect to the top level MGM and will automatically follow redirects. It is\nrecommended for performance reasons to connect FUSE clients via an automounter directly to each MGM, and use either\npath mounting or bind mounts to replicate the tree structure.\n\nAutomounting\n^^^^^^^^^^^^\n\nIt is possible to convert the output of `route ls` and place it into a map for an automounter or other process to use.\n\n.. index::\n   pair: Interfaces; Archiver\n\n\nArchiver\n--------\n\nArchiving allows to store part of the meta-data and data stored on an EOS instance in an external archive.\n\nThe *archive daemon* is managing the transfer of files to/from EOS from/to a\nremote location offering an XRootD interface.\n\nBefore starting the service there are a few configuration parameters that need to be set.\n\nDaemon Configuration\n^^^^^^^^^^^^^^^^^^^^\nFirst of all, one needs to set the user account under which the daemon will be\nrunning by modifying the script located at ``/etc/sysconfig/eosarchived``. The\ndaemon needs to create a series of temporary files during the transfers which\nwill be saved at the location pointed by the environment variable **EOS_ARCHIVE_DIR**.\nAlso, the location where the daemon log file is saved can be changed by modifying\nthe variable **LOG_DIR**.\n\nIf none of the above variables is modified then the default configuration is as\nfollows:\n\n.. code-block:: bash\n\n  export USER=eosarchi\n  export GROUP=c3\n  export EOS_ARCHIVE_DIR=/var/eos/archive/\n  export LOG_DIR=/var/log/eos/archive/\n\nThe variables set in ``/etc/sysconfig/eosarchived`` are general ones that apply\nto both the daemon process and the individual transfer processes spawned later on.\n\nAnother file which holds further configurable values is\n``/etc/eosarchived.conf``. The advantage of this file is that it can be modified\nwhile the daemon is running and newly submitted transfers will pick-up the new\nconfiguration without the need of a full daemon restart.\n\nThe **LOG_LEVEL** variable is set in ``/etc/eosarchived.conf`` and  must be a\nstring corresponding to the syslog loglevel. The **eosarchived** daemon logs\nare saved by default in ``/var/log/eos/archive/eosarchived.log``.\n\nMGM Configuration\n^^^^^^^^^^^^^^^^^^\n\nThe configuration file for the **MGM** node contains a new directive called\n**mgmofs.archivedir** which needs to point to the same location as the\n**EOS_ARCHIVE_DIR** defined earlier for the **eosarchived** daemon. The two\nlocations must match because the **MGM** and the **eosarchived** daemons\ncommunicate between each other using ZMQ and this is the path where any common\nZMQ files are stored.\n\n.. code-block:: bash\n\n  mgmofs.archivedir /var/eos/archive/  # has to be the same as EOS_ARCHIVE_DIR from eosarchived\n\nAnother variable that needs to be set for the **MGM** node is the location where\nall the archived directories are saved. Care should be taken so that the user\nname under which the **eosarchived** daemon runs, has the proper rights to read\nand write files to this remote location. This environment variables can be set in\nthe ``/etc/sysconfig/eos`` file as follows:\n\n.. code-block:: bash\n\n  export EOS_ARCHIVE_URL=root://castorpps.cern.ch//castor/cern.ch/archives/\n\nKeytab file generation\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nAssuming that the **eosarchived** daemon is running under the account *eosarchi*,\nthen one has to make sure the following files are present at the **MGM**. First of\nall, the eos-server.keytab file must include a new entry for the **eosarchi**:\n\n.. code-block:: console\n\n     [root@dev doc]$ ls -lrt /etc/eos-server.keytab\n     -r--------. 1 daemon daemon 137 Mar 22  2012 /etc/eos-server.keytab\n\n     [root@dev ~]# xrdsssadmin list /etc/eos-server.keytab\n     Number Len Date/Time Created Expires  Keyname User & Group\n     ------ --- --------- ------- -------- -------\n          2  32 09/17/14 19:25:01 -------- archive eosarchi c3\n          1  32 09/17/14 19:24:47 -------- eosinst daemon daemon\n\nThe next file that needs to be present is the eos-archive.keytab file which is\ngoing to be used by the **eosarchived** daemon.\n\n.. code-block:: console\n\n     [root@dev ~]# ls -lrt /etc/eos-archive.keytab\n     -r--------. 1 eosarchi c3 133 Sep 18 09:48 /etc/eos-archive.keytab\n\n     [root@dev ~]# xrdsssadmin list /etc/eos-archive.keytab\n     Number Len Date/Time Created Expires  Keyname User & Group\n     ------ --- --------- ------- -------- -------\n          2  32 09/17/14 19:25:01 -------- archive eosarchi c3\n\nSome important notes about the **eos-archive.keytab** file:\n\n- if renamed or saved in a different location then the variable **XrdSecSSSKT**\n  from ``/etc/sysconfig/eosarchived`` needs to be updated to point to the new\n  location/name\n- the permissions on this keytab file must match the identity under which the\n  eoarchived daemon is running\n\nFurthermore, the **eosarchi** user needs to be added to the **sudoers** list in\nEOS so that it can perform any type of operation while creating or transferring\narchives.\n\n.. code-block:: console\n\n    EOS Console [root://localhost] |/eos/> vid set membership eosarchi +sudo\n\nAs far as the **xrd.cf.mgm** configuration file is concerned, one must ensure\nthat **sss** authentication has precedence over **unix** when it comes to local\nconnections:\n\n.. code-block:: bash\n\n   sec.protbind localhost.localdomain sss unix\n   sec.protbind localhost             sss unix\n\n\n\n.. index::\n   pair: Interfaces; Recycle Bin\n\nRecycle Bin\n-----------\n\n\nOverview\n^^^^^^^^\n\nThe EOS recycle bin allows to define a FIFO policy for delayed file deletion.\nThis feature is available starting with EOS BERYL.\n\nThe recycling bin is time-based and volume based e.g. the garbage directory\nperforms final deletion after a configurable time delay. The volume in the\nrecycle bin is limited using project quota. If the recycle bin is full no\nfurther deletion is possible and deletions fails until enough space is available.\n\nThe recycling bin supports individual file deletions and recursive bulk\ndeletions (referenced as object deletions in the following).\n\nThe owner of a deleted file or subtree deletion can restore files into the\noriginal location from the recycle bin if he has the required quota. If the\noriginal location is 'occupied' the action is rejected. Using the '-f' flag\nthe existing location is renamed and the deleted object is restored to the\noriginal name.\n\nIf the parent tree of the restore location is incomplete the user is asked\nto first recreate the parent directory structure before objects are restored.\n\nIf the recycle bin is applicable for a deletion operation the quota is\nimmediately removed from the original quota node and added to the recycle\nquota. Without recycle bin, quota is released once files are physically deleted!\n\nCommand Line Interface\n^^^^^^^^^^^^^^^^^^^^^^^\nIf you want to get the current state and configuration of the recycle bin you\nrun the recycle command:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle\n\n   # _______________________________________________________________________________________________\n   # used 0.00 B out of 100.00 GB (0.00% volume / 0.00% inodes used) Object-Lifetime 86400 [s]\n   # _______________________________________________________________________________________________\n\nThe values are self-explaining.\n\nDump entire recycle bin configuration\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIf you want to list all the parameter affecting the recycle bin you running\n**recycle config --dump**:\n\n.. code-block:: bash\n\n   EOS Console: [root://localhost] |/eos/> recycle config --dump\n   enforced=on\n   dry_run=yes\n   keep_time_sec=172800\n   space_keep_ratio=0.95\n   low_space_watermark=0\n   low_inode_watermark=0\n   collect_interval_sec=20\n   remove_interval_sec=10\n\nEnable/Disable the recycle bin\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIf you want to enable or disable the recycle bin globally you run\n**recycle config --enable <on|off>**:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle config --enable on\n\n\nDefine the object lifetime\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIf you want to configure the lifetime of objects in the recycle bin you run\n**recycle config --lifetime <lifetime>**:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle config --lifetime 86400\n\n<lifetime> can be e.g. just a number 3600, 3600s  (seconds) or 60min\n(60 minutes) 1d (one day), 1w (one week), 1mo (one month), 1y (one year) aso.\n\nThe lifetime has to be at least 60 seconds!\n\nDefine the recycle bin size\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIf you want to configure the size of the recycle bin you run\n**recycle config --size <size>**:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle config --size 100G\n\n<size> can be e.g. just a number 100000000000, 100000M (mega byte) or 100G (giga byte), 1T (one terra) aso.\n\nThe size has to be at least 100G!\n\nDefine the inode size of the recycle bin\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIf you want to configure the number of inodes in the recycle bin you run\n**recycle config --inodes <value>[K|M|G]**:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle config --inodes 1M\n\n<value> can be a number or suffixed with K (1000), M (1.000.0000) or G (1.000.000.000).\n\nIt is not mandatory to define the number of inodes to use a recycle bin.\n\nDefine an optional threshold ratio\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIf you want to keep files as long as possible you can set a keep ratio on the\nrecycle bin:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle config --ratio 0.8\n\nIn this example the recycle bin can be filled up-to 80%. Once it reaches the\nwatermark it will clean all files matching the given lifetime policy. The\ncleaning will free 10% under the given watermark. This option is not\nmandatory and probably not always the desired behaviour.\n\nDefine dry-run mode for the recycle cleanup\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIf you want the recycle bin to just select and print the entries that would\nbe deleted according to the given policy enforced, then you can enable the\n\"dry-run\" mode:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle config --dry-run\n\n\nIn this case the recycle bin would not delete any files that are selected\nfor clean-up bases either on the size or lifetime policies.\n\nDefine a recycle project for a subtree\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nBy default the recycle bin functionality works on user identities. Therefore,\nonce a user deletes a certain file, that file will end up in the recycle bin\ncorresponding to the original owner of the file. There are certain situations\nwhere this behaviour makes things hard to restore, for example in project\nspaces that are shared by multiple users.\n\nIn this case, the admin of the project can set up a so-called \"recycle project\"\nby using the command **reyclce project <path> --acl <optional_acls>**. This\nwill create a new extended attribute attached to the **<path>** directory\nthat contains the recycle project identifier. Example:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos> recycle project --path /eos/dev/plain --acl u:1234:rx\n\nListing the extended attributes of the given path will display a new pair\ncalled **sys.forced.recycleid** that corresponds to the container identifier\nof the path.\n\n.. code-block:: bash\n\n   [root://localhost] |/eos> eos attr ls  /eos/dev/plain | grep recycle\n   sys.forced.recycleid=\"1007\"\n   [root://localhost] |/eos> eos fileinfo /eos/dev/plain | grep Fid\n   CUid: 0 CGid: 0 Fxid: 000003ef Fid: 1007 Pid: 3 Pxid: 00000003\n\nThis identifier is now used to group all the deletions coming from this\nsub-tree inside the recycle bin, in a dedicated location.\n\n.. code-block:: bash\n   [root://localhost] |/eos> eos ls -lrt /eos/dev/proc/recycle | grep 1007\n   drwx-----+   1 root     root                0 Nov 24 22:37 rid:1007\n\nThis identifier is called the \"recycle id\" and can be used as input for the\nvarious recycle commands that accept such a value.\n\nBulk deletions\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nA bulk deletion using the recycle bin prints how the deleted files can\nbe restored:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/dev/2rep/subnode/> rm -r tree\n\n   success: you can recycle this deletion using 'recycle restore 00000000000007cf'\n\nAdd recycle policy on a subtree\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIf you want to set the policy to use the recycle bin in a subtree of the\nnamespace run:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/dev/2rep/subnode/> recycle config --add-bin /eos/dev/2rep/subnode/tree\n\n   success: set attribute 'sys.recycle'='../recycle' in directory /eos/dev/2rep/subnode/tree/\n\nRemove recycle policy from a subtree\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nTo remove the recycle bin policy in a subtree run:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/dev/2rep/subnode/> recycle config --remove-bin /eos/dev/2rep/subnode/tree\n\n   success: removed attribute 'sys.recycle' from directory /eos/dev/2rep/subnode/tree/\n\n@todo(esindril) Review this part!\nEnforce globally usage of a recycle bin for all deletions\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe global policy is enforced in the default space:\n\n.. code-block:: bash\n\n   # enable\n   EOS Console [root://localhost ]/ space config default space.policy.recycle=on\n\n   # disable\n   EOS Console [root://localhost ]/ space config default space.policy.recycle=off\n\n   # remove policy\n   EOS Console [root://localhost ]/ space config default space.policy.recycle=remove\n\nList files in the recycle bin\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIf you want to list the objects that can be restored from the recycle bin you run:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle ls\n   # Deletion Time            UID      GID      TYPE          RESTORE-KEY            RESTORE-PATH                  DTRACE\n   # =====================================================================================================================\n   Thu Mar 21 23:02:22 2013   apeters  z2       recursive-dir pxid:00000000000007cf /eos/dev/2rep/subnode/tree     {}\n\nBy default this command displays all user private restorable objects.\nIf you have the root role or are member of the admin group, you can add the\n``--all`` flag to list the objects that can be restored for all users.\n\n\nFor performance and maintainability reasons the list is truncated after 100k\nentries. The DTRACE entry contains authentication information of the client\nwho actually deleted the entry (if available).\n\nFor the so-called \"recycle projects\" the user needs to use the \"recycle id\"\nwhen doing the listing:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> eos recycle ls --rid 1007\n   # Deletion Time            UID   GID   SIZE    TYPE   RESTORE-KEY           RESTORE-PATH                DTRACE\n   # ============================================================================================================\n   Fri Dec  5 15:31:55 2025   adm   adm   1294    file   fxid:0000000000002070 /eos/dev/plain/file1.dat    {}\n\nRestoring Objects\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nObjects are restored using **recycle restore <restore-key>**.\nThe <restore-key> is shown by **recycle ls**.\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle restore 00000000000007cf\n   error: to recycle this file you have to have the role of the file owner: uid=755 (errc=1) (Operation not permitted)\n\nYou can only restore an object if you have the same uid/gid role\nlike the object owner:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> role 755 1395\n   => selected user role ruid=<755> and group role rgid=<1395>\n\n   EOS Console [root://localhost] |/eos/> recycle restore 00000000000007cf\n   success: restored path=/eos/dev/2rep/subnode/tree\n\nIf the original path has been used in the mean while you will see the following\nafter a restore command:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle restore 00000000000007cf\n   error: the original path is already existing - use '--force-original-name'\n          or '-f' to put the deleted file/tree back and rename the file/tree\n          in place to <name>.<inode> (errc=17) (File exists)\n\nThe file can be restored using the force flag:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle restore -f 00000000000007cf\n   warning: renamed restore path=/eos/dev/2rep/subnode/tree to backup-path=/eos/dev/2rep/subnode/tree.00000000000007d6\n   success: restored path=/eos/dev/2rep/subnode/tree\n\nFor so-called \"recycle projects\" a user can restore a certain entry if and\nonly if one of the following conditions holds true:\n* they are the owner of the file to be restored\n* they are listed in the ACLs attached to the recycle bin project directory as\n  being allowed to read the entries\n\nThese ACLs can be configured when setting up the \"recycle project\" space and\npassing the ACL option to the **recycle project** command.\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/> recycle project --path /eos/dev/plain --acl u:1234=rx\n   EOS Console [root://localhost] |/eos/> attr get sys.acl /eos/dev/proc/recycle/rid:1007\n   sys.acl=\"u:58602:rx\"\n\n\nPurging\n\"\"\"\"\"\"\"\n\nOne can force to flush files in the recycle bin before the lifetime policy\nkicks in using recycle purge command:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/dev/2rep/subnode/> recycle purge\n   success: purged 1 bulk deletions and 0 individual files from the recycle bin!\n\nNotice that purging only removes files of the current uid/gid role.\nRunning as **root** does not purge the recycle bin of all users by default.\nIf you want to purge the recycle bin completely add the ``--all`` option.\n\nImplementation\n^^^^^^^^^^^^^^\n\nThe implementation is hidden to the enduser and is explained to give some\ndeeper insight to administrators. All the functionality is wrapped as demonstrated before in the CLI using the recycle command.\n\nThe recycle bin resides in the namespace under the proc directory under ``/recycle/``.\n\nThe structure of the recycle bin is as follows:\n\n``/recycle/<uid>/<year>/<month>/<date>/<index>/<contracted-path>.<hex-inode>`` for files and\n\n``/recycle/<uid>/<year>/<month>/<date>/<index>/<contracted-path>.<hex-inode>.d`` for bulk deletions.\n\nThe structure helps to purge the recycle bin easily by date:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/dev/2rep/subnode/> recycle purge 2018/03/01\n   success: purged 12 bulk deletions and 0 individual files from the recycle bin!\n\nThe internal structure is however not relevant or exported to the end-user.\nThe contracted path flattens the full pathname replacing '/' with '#:#'.\n\nThe ``/recycle/`` directory is configured as a quota node with project space\ne.g. all files appearing in there are accounted on a catch-all project quota.\n\nDeletion only succeeds if the recycle quota node has enough space available\nto absorb the deletion object.\n\nA dedicated thread inside the MGM uses an optimized logic to follow the entries\nin the recycle tree and performs unrecoverable deletion according to the\nconfigured lifetime policy. The lifetime policy is defined via the external\nattribute sys.recycle.lifetime tagged on the /recycle directory specifying\nthe file lifetime in seconds.\n\nFile deletions and bulk deletions are moved in the recycle bin if the parent\ndirectory of the deletion object specifies as external attribute ``sys.recycle=../recycle/``.\n\nA restore operation can only succeed if the restore location provides the\nneeded quota for all objects to be restored.\n\nNote that a tree can have files owned by many individuals and restoration\nrequires appropriate quota for all of them. As mentioned, the restore operation\nhas be executed with the role of the file or subtree top-level directory\nidentity (uid/gid pair).\n\n.. highlight:: wfe\n\n.. index::\n      pair: Interfaces; Workflow Engine, WFE\n   single:: WFE\n\nWFE Engine\n----------\nThe workflow engine is a versatile event triggered storage process chain. Currently all events are created by file operations.\nThe policy to emit events is described as extended attributes of a parent directory. Each workflow is named. The default workflow\nis named 'default' and used if no workflow name is provided in an URL as `?eos.workflow=default`.\n\nThe workflow engine allows to create chained workflows: E.g. one workflow can trigger an event emission to run the next workflow in the chain and so on...\n\n.. epigraph::\n\n   ==================== ==================================================================================================\n   Event                Description\n   ==================== ==================================================================================================\n   sync::create         event is triggered at the MGM when a file is being created (synchronous event)\n   open                 event is triggered at the MGM when a 'file open'\n                        - if the return of an open call is ENONET a workflow defined stall time is returned\n   sync::prepare        event is triggered at the MGM when a 'prepare' is issued (synchronous event)\n   sync::abort_prepare  event is triggered at the MGM when xrdfs prepare -f issued (synchronous event)\n   sync::offline        event is triggered at the MGM when a 'file open' is issued against an offline file (synchronous\n                        event)\n   retrieve_failed      event is triggered with an error message at the MGM when the retrieval of a file has failed\n   archive_failed       event is triggered with an error message at the MGM when the archival of a file has failed\n   closer               event is triggered via the MGM when a read-open file is closed on an FST.\n   sync::closew         event is triggered via the FST when a write-open file is closed (it has priority over the asynchronous one)\n   closew               event is triggered via the MGM when a write-open file is closed on an FST\n   sync::delete         event is triggered at the MGM when a file has been deleted (synchronous event)\n   sync::recycle        event is triggered at the MGM when a file has been moved to the recycle bin (synchronous event)\n   ==================== ==================================================================================================\n\nCurrently the workflow engine implements two action targets. The **bash:shell** target is a powerful target.\nIt allows you to execute any shell command as a workflow. This target provides a large set of template parameters\nwhich EOS can give as input arguments to the called shell command. When using the **bash::shell** target you should avoid to\nuse calls to the EOS console CLI since this can lead to deadlock situations. This is described later. The **mail** target\nallows to send an email notification to a specified recipient and mostly used for demonstration.\n\n.. epigraph::\n\n   ============= =============================================================================================\n   Action Target Description\n   ============= =============================================================================================\n   bash:shell    run an arbitrary shell command line with template argument substitution\n   mail          send an email notification to a provided recipient when such an event is triggered\n   notify        asynchronous workflow sending notification via http,activemq,grpc or qclient(redis) protocol\n   ============= =============================================================================================\n\nConfiguration\n^^^^^^^^^^^^^\n\nEngine\n\"\"\"\"\"\"\n\nThe WFE engine has to be enabled/disabled in the default space only:\n\n.. code-block:: bash\n\n   # enable\n   eos space config default space.wfe=on\n   # disable\n   eos space config default space.wfe=off\n\nThe current status of the WFE can be seen via:\n\n.. code-block:: bash\n\n   eos -b space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   ...\n   wfe                            := off\n   wfe.interval                   := 10\n   ...\n\nThe interval in which the WFE engine is running is defined by the **wfe.interval**\nspace variable. The default is 10 seconds if unspecified.\n\n.. code-block:: bash\n\n   # run the LRU scan once a week\n   eos space config default space.wfe.interval=10\n\nThe thread-pool size of concurrently running workflows is defined by the **wfe.ntx** space variable.\nThe default is to run all workflow jobs sequentially with a single thread.\n\n.. code-block:: bash\n\n   # configure a thread pool of 16 workflow jobs in parallel\n   eos space config default space.wfe.ntx=10\n\nWorkflows are stored in a virtual queue system. The queues display the status of each workflow. By default workflows older than 7 days are cleaned up.\nThis setting can be changed by the **wfe.keeptime** space variable. That is the time in seconds how long workflows are kept in the virtual queue system before\nthey get deleted.\n\n.. code-block:: bash\n\n   # keep workflows for 1 week\n   eos space config default space.wfe.keeptime=604800\n\nWorkflow Configuration\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe **notify** workflow\n```````````````````````\nThis notification mechanism can be used to inform external service about events on certain files e.g. when a new file is generated to inform a processing service.\n\nThe supported notification protocols are:\n\n- http(s) (POST)\n- grpc (Notify rpc)\n- activeMQ (Message)\n- redis (PUBLISH)\n\nThe message which is sent upstream contains a JSON document derived from the protobuf definition of eos::rpc::MDNotification e.g.\n\n.. code-block:: bash\n\n   {\n    \"fmd\": {\n        \"id\": \"653201\",\n        \"contId\": \"12174\",\n        \"uid\": \"65534\",\n        \"gid\": \"2\",\n        \"size\": \"3106\",\n        \"layoutId\": 1048578,\n        \"flags\": 416,\n        \"name\": \"ei42MA==\",\n        \"ctime\": {\n            \"sec\": \"1745940835\",\n            \"nSec\": \"386583069\"\n        },\n        \"mtime\": {\n            \"sec\": \"1745940835\",\n            \"nSec\": \"693545000\"\n        },\n        \"checksum\": {\n            \"value\": \"jrQu4gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\",\n            \"type\": \"adler32\"\n        },\n        \"xattrs\": {\n            \"sys.utrace\": \"NWM2MzBjODYtMjUwZi0xMWYwLWI0NzUtZmExNjNlMDdkZTdl\",\n            \"sys.fs.tracking\": \"KzU=\",\n            \"sys.vtrace\": \"W1R1ZSBBcHIgMjkgMTc6MzM6NTUgMjAyNV0gdWlkOjY1NTM0W25vYm9keV0gZ2lkOjJbZGFlbW9uXSB0aWRlbnQ6cm9vdC4yMjQ5NDE6NTYyQGxvY2FsaG9zdDYgbmFtZTpkYWVtb24gZG46IHByb3Q6c3NzIGFwcDogaG9zdDpsb2NhbGhvc3Q2IGRvbWFpbjpsb2NhbGRvbWFpbiBnZW86IHN1ZG86MCB0cmFjZTogb25iZWhhbGY6\",\n            \"sys.eos.btime\": \"MTc0NTk0MDgzNS4zODY1ODMwNjk=\"\n        },\n        \"path\": \"L2Vvcy9kZXYvcHVibGljL25vdGlmeS96LjYw\",\n        \"etag\": \"\\\"175342308294656:8eb42ee2\\\"\",\n        \"inode\": \"175342308294656\"\n    },\n    \"role\": {\n        \"uid\": \"65534\",\n        \"gid\": \"2\",\n        \"username\": \"nobody\",\n        \"groupname\": \"daemon\"\n    }\n   }\n\n\nThe notification is configured on parent directories using the ``sys.workflow.*`` syntax.\n\nHere are some example configurations:\n\n.. code-block:: bash\n\n     # general attribute structure\n     #            sys.workflow.<event>.<space> = notify:<protocol>|<uri>|<port>|<queue>|<timeout>\n\n     # HTTP notification - the port and path to do the POST is part of the <uri> tag, <port> & <queue> are empty\n     eos attr set sys.workflow.closew.default=notify:http|localhost:5000/notify|||2000\n\n     # REDIS pub/sub notification - the port is given as an extra argument\n     eos attr set sys.workflow.closew.default=notify:qclient|localhost|6349|notification|2000\n\n     # REDIS rpush notification - the port is given as an extra argument\n     eos attr set sys.workflow.closew.default=notify:redis|localhost|6349|notification|2000\n\n     # GRPC notification - the port is given as an extra argument, <queue> is empty\n     eos attr set sys.workflow.closew.default=notify:grpc|localhost|55100||2000\n\n\nFor testing HTTP here is a simple Flask HTTP printing notifications listening on localhost port 5000:\n\n.. code-block:: python\n\n   import json\n\n   from flask import Flask, request, jsonify\n\n   app = Flask(__name__)\n\n   @app.route('/notify', methods=['POST'])\n   def handle_post():\n   data = request.get_json()\n   if not data:\n     return jsonify({'error': 'No JSON payload received'}), 400\n\n   pretty_json = json.dumps(data, indent=4)\n   print(pretty_json)\n   return jsonify({'message': 'Data received successfully'}), 200\n\n   if __name__ == '__main__':\n   app.run(debug=True, port=5000)\n\nFor testing REDIS here is a simple CLI listener on the notification queue on localhost for REDIS:\n\n.. code-block:: bash\n\n   #start REDIS\n   systemctl start redis\n\n   redis-cli SUBSCRIBE notification\n\nThe **mail** workflow\n`````````````````````\nAs an example we want to send an email to a mailing list, whenever a file is deposited. This workflow can be specified like this:\n\n.. code-block:: bash\n\n   # define a workflow to send when a file is written\n   eos attr set \"sys.workflow.closew.default=mail:eos-project.cern.ch: a file has been written!\" /eos/dev/mail/\n\n   # place a new file\n   eos cp /etc/passwd /eos/dev/mail/passwd\n\n   # eos-project.cern.ch will receive an Email with a subject like: eosdev ( eosdev1.cern.ch ) event=closew fid=000004f7 )\n   # and the text in the body : a file has been written!\n\n\nThe **bash:shell** workflow\n``````````````````````````````````````````````````\n\nMost people want to run a command whenever a file is placed, read or deleted. To invoke a shell command one configures the **bash:shell** workflow.\nAs an example consider this simple echo command, which prints the path when a **closew** event is triggered:\n\n.. code-block:: bash\n\n   # define a workflow to echo the full path when a file is written\n   eos attr set \"sys.workflow.closew.default=sys.workflow.closew.default=bash:shell:mylog echo <eos::wfe::path>\" /eos/dev/echo/\n\nThe template parameters ``<eos::wfe::path>`` is replaced with the full logical path of the file, which was written. The third parameters ``mylog`` in **bash:shell:mylog** specifies the name of\nthe log file for this workflow which is found on the MGM under ``/var/log/eos/wfe/mylog.log``\n\nOnce one uploads a file into the ``echo`` directory, the following log entry is created in ``/var/log/eos/wfe/mylog.log``\n\n.. code-block:: bash\n\n   ----------------------------------------------------------------------------------------------------------------------\n   1466173303 Fri Jun 17 16:21:43 CEST 2016 shell echo /eos/dev/echo/passwd\n   /eos/dev/echo/passwd\n   retc=0\n\n\n.. warning:: Please be aware that running a synchronous workflow which calls back to the EOS MGM either using the CLI or other clients can create a dead-lock if the threadpool is exhausted!\n\nThe full list of static template arguments is given here:\n\n.. epigraph::\n\n   =========================== =============================================================================================\n   Template                    Description\n   =========================== =============================================================================================\n   <eos::wfe::uid>             user id of the file owner\n   <eos::wfe::gid>             group id of the file owner\n   <eos::wfe::username>        user name of the file owner\n   <eos::wfe::groupname>       group name of the file owner\n   <eos::wfe::ruid>            user id invoking the workflow\n   <eos::wfe::rgid>            group id invoking the workflow\n   <eos::wfe::rusername>       user name invoking the workflow\n   <eos::wfe::rgroupname>      group name invoking the workflow\n   <eos::wfe::path>            full absolute file path which has triggered the workflow\n   <eos::wfe::base64:path>     base64 encoded full absolute file path which has triggered the workflow\n   <eos::wfe::turl>            XRootD transfer URL providing access by file id e.g. root://myeos.cern.ch//mydir/myfile?eos.lfn=fxid:00001aaa\n   <eos::wfe::host>            client host name triggering the workflow\n   <eos::wfe::sec.app>         client application triggering the workflow (this is defined externally via the CGI ``?eos.app=myapp``)\n   <eos::wfe::sec.name>        client security credential name triggering the workflow\n   <eos::wfe::sec.prot>        client security protocol triggering the workflow\n   <eos::wfe::sec.grps>        client security groups triggering the workflow\n   <eos::wfe::instance>        EOS instance name\n   <eos::wfe::ctime.s>         file creation time seconds\n   <eos::wfe::ctime.ns>        file creation time nanoseconds\n   <eos::wfe::mtime.s>         file modification time seconds\n   <eos::wfe::mtime.ns>        file modification time nanoseconds\n   <eos::wfe::size>            file size\n   <eos::wfe::cid>             parent container id\n   <eos::wfe::fid>             file id (decimal)\n   <eos::wfe::fxid>            file id (hexacdecimal)\n   <eos::wfe::name>            basename of the file\n   <eos::wfe::base64:name>     base64 encoded basename of the file\n   <eos::wfe::link>            resolved symlink path if the original file path is a symbolic link to a file\n   <eos::wfe::base64:link>     base64 encoded resolved symlink path if the original file path is a symbolic link to a file\n   <eos::wfe::checksum>        checksum string\n   <eos::wfe::checksumtype>    checksum type string\n   <eos::wfe::event>           event name triggering this workflow (e.g. closew)\n   <eos::wfe::queue>           queue name triggering this workflow (e.g. can be 'q' or 'e')\n   <eos::wfe::workflow>        workflow name triggering this workflow (e.g. default)\n   <eos::wfe::now>             current unix timestamp when running this workflow\n   <eos::wfe::when>            scheduling unix timestamp when to run this workflow\n   <eos::wfe::base64:metadata> a full base64 encoded meta data blop with all file metadata and parent metadata including extended attributes\n   <eos::wfe::vpath>           the path of the workflow file in the virtual workflow directory when the workflow is executed\n                               - you can use this to attach messages/log as an extended attribute to a workflow if desired\n   =========================== =============================================================================================\n\n\nExtended attributes of a file and it's parent container can be read with dynamic template arguments:\n\n.. epigraph::\n\n   ================================ ========================================================================================\n   Template                         Description\n   ================================ ========================================================================================\n   <eos::wfe::fxattr:<key>>         Retrieves the value of the extended attribute of the triggering file with name <key>\n                                    - sets UNDEF if not existing\n   <eos::wfe::fxattr:base64:<key>>  Retrieves the base64 encoded value of the extended attribute of the triggering file with name <key>\n                                    - sets UNDEF if not existing\n   <eos::wfe::cxattr:<key>>         Retrieves the value of the extended attribute of parent directory of the triggering file\n                                    - sets UNDEF if not existing\n   ================================ ========================================================================================\n\n\n\nHere is an  example for a dynamic attribute:\n\n.. code-block:: bash\n\n   # define a workflow to echo the meta blob and the acls of the parent directory when a file is written\n   eos attr set \"sys.workflow.closew.default=bash:shell:mylog echo <eos::wfe::base64:metadata> <eos::wfe::cxattr:sys.acl>\" /eos/dev/echo/\n\n\nConfiguring retry policies for  **bash:shell** workflows\n````````````````````````````````````````````````````````\n\nIf a **bash:shell** workflow fails e.g. the command returns rc!=0 and no retry policy is defined, the workflow job ends up in the **failed** queue. For each\nworkflow the number of retries and the delay for retry can be defined via extended attributes. To reschedule a workflow after a failure the shell command has to return **EAGAIN** e.g. ``exit(11)``.\nThe number of retries for a failing workflow can be defined as:\n\n.. code-block:: bash\n\n   # define a workflow to return EAGAIN to be retried\n   eos attr set \"sys.workflow.closew.default=bash:shell:fail '(exit 11)'\" /eos/dev/echo/\n\n   # set the maximum number of retries\n   eos attr set \"sys.workflow.closew.default.retry.max=3\" /eos/dev/echo/\n\nThe previous workflow will be scheduled three times without delay. If you want to schedule a retry at a later point in time, you can define the delay for retry for a particular workflow like:\n\n.. code-block:: bash\n\n   # configure a workflow retry after 1 hour\n   eos attr set \"sys.workflow.closew.default.retry.delay=3600\" /eos/dev/echo/\n\n\nReturning result attributes\n````````````````````````````\n\nif a **bash::shell** workflow is used, the STDERR of the command is parsed for return attribute tags, which are either tagged on the triggering file (path) or the virtual workflow entry (vpath):\n\n.. epigraph::\n\n   ============================================== =====================================================================================\n   Syntax                                         Resulting Action\n   ============================================== =====================================================================================\n   <eos::wfe::path::fxattr:<key>>=base64:<value>  set a file attribute <key> on <eos::wfe::path> to the base64 decoded value of <value>\n   <eos::wfe::path::fxattr:<key>>=<value>         set a file attribute <key> on <eos::wfe::path> to <value> (value can not contain space)\n   <eos::wfe::vpath::fxattr:<key>>=base64:<value> set a file attribute <key> on <eos::wfe::vpath> to the base64 decoded value of <value>\n   <eos::wfe::vpath::fxattr:<key>>=:<value>       set a file attribute <key> on <eos::wfe::vpath> to <value> (value can not contain space)\n   ============================================== =====================================================================================\n\nVirtual /proc Workflow queue directories\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe virtual directory structure for triggered workflows can be found under ``/eos/<instance>/proc/workflow``.\n\nHere is an example:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/eos/dev/> eos find /eos/dev/proc/workflow/\n   /eos/dev/proc/workflow/20160617/d/\n   /eos/dev/proc/workflow/20160617/d/default/\n   /eos/dev/proc/workflow/20160617/d/default/1466171933:000004f7:closew\n   /eos/dev/proc/workflow/20160617/d/default/1466173303:000004fd:closew\n   /eos/dev/proc/workflow/20160617/f/\n   /eos/dev/proc/workflow/20160617/f/default/\n   /eos/dev/proc/workflow/20160617/f/default/1466171873:000004f4:closew\n   /eos/dev/proc/workflow/20160617/f/default/1466173183:000004fa:closew\n   /eos/dev/proc/workflow/20160617/q/\n   /eos/dev/proc/workflow/20160617/q/default/1466173283:000004fb:closew\n\nThe virtual tree is organized with entries like ``<proc>/workflow/<year-month-day>/<queue>/<workflow>/<unix-timestamp>:<fid>:<event>``.\nWorkflows are scheduled only from the **q** and **e** queues. All other entries describe a ``finale state`` and will be expired as configured by the cleanup policy described in the beginning.\n\nThe existing queues are described here:\n\n.. epigraph::\n\n   =========================== ========================================================================================\n   Queue                       Description\n   =========================== ========================================================================================\n   ../q/..                     all triggered asynchronous workflows appear first in this queue\n   ../s/..                     scheduled asynchronous workflows and triggered synchronous workflows appear in this queue\n   ../r/..                     running workflows appear in this queue\n   ../e/..                     failed workflows with retry policy appear here\n   ../f/..                     failed workflows without retry appear here\n   ../g/..                     workflows with 'gone' files or some global misconfiguration appear here\n   ../d/..                     successful workflows with 0 return code\n   =========================== ========================================================================================\n\n\nSynchronous workflows\n``````````````````````\n\nThe **deletion** and **prepare** workflow are synchronous workflows which are executed in-line. They are stored and tracked as asynchronous workflows in the proc filesystem. The emitted event on deletion is **sync::delete**, the emitted event on prepare is **sync::prepare**.\n\nWorkflow log and return codes\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe return codes and log information is tagged on the virtual directory entries in the proc filesystem as extended attributes:\n\n.. code-block:: bash\n\n   sys.wfe.retc=<return code value>\n   sys.wfe.log=<message describing the result of running the workflow>\n\nDevices Interface\n-----------------\n\n\neos devices\n^^^^^^^^^^^\n\nThe devices interface `eos devices` has the main purpose to track storage devices in EOS. The backend of the devices interfaces is a background thread, which by default every 15 minutes decodes S.M.A.R.T information published every 15 minutes from FST nodes.\n\n.. index::\n   pair: Devices; Storage Devices\n\n\nThe MGM extraction interval used by the async. thread can only be modified in the sysconfig file by defining `EOS_MGM_DEVICES_PUBLISHING_INTERVAL` in seconds.\n\nThe CLI interface by default creates overview statistic about all storage devices configured in the instance:\n\n.. code-block:: bash\n\n   eos devices ls\n   # Fri Sep 22 14:50:05 2023\n   ┌───────────────────┬──────────────┬────────┬─────┬───────┐\n   │model              │avg:age[years]│   bytes│count│  hours│\n   ├───────────────────┴──────────────┴────────┴─────┴───────┤\n   │TOSHIBA:MG07ACA14TE           0.19 56.00 TB     4 6.55 Kh│\n   └─────────────────────────────────────────────────────────┘\n\n   The first line prints the extraction time of the information.\n\nThe fields are:\n\n.. code-block:: bash\n\n   model          : name of storage device (blanks are replaced with :)\n   avg:age[years] : average age of all devices for a given storage model\n   bytes          : storage capacity of all devices for a given storage model\n   hours          : power-on-hours for all devices for a given storage model\n\n\nThe long option of the devices command prints all devices ordered by space. The space name is shown in the top left header\n\n.. code-block:: bash\n\n   eos devices ls -l\n\n   # Fri Sep 22 14:50:05 2023\n   ┌────────────┬───────────────────┬────────────┬────┬────────┬────┬──────────┬─────────────┬─────────┬────────┬────┬────┐\n   │     default│model              │serial      │type│capacity│rpms│poweron[h]│temp[degrees]│S.M.A.R.T│if      │rla │wc  │\n   ├────────────┴───────────────────┴────────────┴────┴────────┴────┴──────────┴─────────────┴─────────┴────────┴────┴────┤\n   │           1 TOSHIBA:MG07ACA14TE 23S0A0MBF94G sat  14.00 TB 7200    1.64 Kh            31     noctl 6.0:Gb/s true true│\n   │           2 TOSHIBA:MG07ACA14TE 23S0B0MBF94G sat  14.00 TB 7200    1.64 Kh            31     noctl 6.0:Gb/s true true│\n   │           3 TOSHIBA:MG07ACA14TE 23S0C0MBF94G sat  14.00 TB 7200    1.64 Kh            31     noctl 6.0:Gb/s true true│\n   │           4 TOSHIBA:MG07ACA14TE 23S0D0MBF94G sat  14.00 TB 7200    1.64 Kh            31     noctl 6.0:Gb/s true true│\n   └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\n\n   ┌───────────────────┬──────────────┬────────┬─────┬───────┬───────┬──────────┬───────┬────────────┬──────────┬──────────┬────────────┐\n   │model              │avg:age[years]│   bytes│count│  hours│smrt:ok│smrt:noctl│smrt:na│smrt:failing│smrt:check│smrt:inval│smrt:unknown│\n   ├───────────────────┴──────────────┴────────┴─────┴───────┴───────┴──────────┴───────┴────────────┴──────────┴──────────┴────────────┤\n   │TOSHIBA:MG07ACA14TE           0.19 56.00 TB     4 6.55 Kh       0          4       0            0          0          0            0│\n   └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\n\n.. code-block:: bash\n\n   1st column     : EOS filesystem ID\n   model          : model name\n   type           : storage HW type\n   capacity       : human readable device capacity\n   rpms           : RPMs in case of HDDs\n   poweron[h]     : hours device has been powerd on\n   S.M.A.R.T.     : device status ({\"ok\", \"noctl\",\"na\",\"failing\",\"check\",\"inval\",\"unknown\"})\n   if             : connection interface speed\n   rla            : read-look-ahead (enabled=true,disabled=false)\n   wc             : write-cache (enabled=true, disabled=false)\n\nThe devices command supports monitoring and JSON format including per device and device statistics.\n\n.. code-block:: bash\n\n   # key:val output for monitoring\n   eos devices ls -m\n\n   # json output for monitoring\n   eos --json devices ls\n\n\nproc/devices\n^^^^^^^^^^^^\n\nThe devices async thread creates for each `serialnumber.filesystem-id` combination an entry in `/eos/.../proc/devices/`. The birth time of these entries is the first time a serialnumber/filesystem-id combination has been stored. Each of these empty file entries carries an extended attribute `sys.smart.json`, which stores the last JSON output provided by `smartctl` running on FSTs. These entries allow on the long term to trace the appearance and disappearance of devices.\n"
  },
  {
    "path": "doc/diopside/manual/microservices.rst",
    "content": ".. index::\n   pair: MGM; Microservices\n\n.. highlight:: rst\n\n.. _microservices:\n\n\nMGM Microservices\n=================\n\nThe EOS MGM service incorporates several embedded sub-services, many of them are disabled by default.\nMost of them are implement in an asynchronous thread running as part of the meta-data service.\n\n.. index::\n   pair: MGM; Converter\n\nConverter\n---------\n\nThe converter functionality serves several purposes:\n\n.. index::\n   pair: Converter; Engine\n\nConverter Engine\n^^^^^^^^^^^^^^^^\n\nThe Converter Engine is responsible for scheduling and performing file\nconversion jobs. A conversion job means rewriting a file with a different\nstorage parameter: layout, replica number, space or placement policy. The\nfunctionality is used for several purposes:\n* for the Balancer to rewrite files to achieve a new placement\n* for the LRU policy to rewrite a file with a new layout e.g. rewrite a\n  file with 2 replica into a RAID-6 like RAIN layout with the benefit of\n  space savings\n\nInternally the converter uses the XRootD third party copy mechanism and\nconsumes one thread in the **MGM** for each running conversion transfer.\n\nThe Converter Engine is split into two main components:\n*Converter Driver* and *Converter Scheduler*.\n\n.. index::\n   pair: Converter; Driver\n\nConverter Driver\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe Converter Driver is the component responsible for performing the actual\nconversion job. This is done using XRootD third party copy between the FSTs.\nThe Converter Driver keeps a threadpool available for conversion jobs,\nthat serves the entire EOS cluster. Periodically, it checks the list of\npending conversion jobs and the retrieved jobs are scheduled, one per\nthread, up to a configurable limit. After each scheduling, a check is\nperformed to identify completed or failed jobs. Each conversion job is\ntracked internally and the list of files being tracked by the converter\ncan be displayed using the following command:\n\n.. code-block:: bash\n\n   eos ns tracker list --name convert\n\nWithin QuarkDB, we keep the list of pending conversion jobs so that\nafter an MGM restart the list of conversion is not lost. For this purpose\nthe following set structure is used inside QuarkDB:\n\n.. code-block:: bash\n\n  eos-conversion-jobs-pending\n\nEach entry in the set has the following structure: *<fid>:<conversion_info>*.\n\n.. index::\n   pair: Conversion; Info\n\nConversion Info\n~~~~~~~~~~~~~~~\n\nA conversion info is defined as following:\n\n.. code-block:: bash\n\n  <fid(016hex)>:<space[.group]>#<layout(08hex)>[~<placement>]\n\n    <fid>       - 16-digit with leading zeroes hexadecimal file id\n    <space>     - space or space.group notation\n    <layout>    - 8-digit with leading zeroes hexadecimal layout id\n    <placement> - the placement policy to apply\n\nThe job info is parsed by the Converter Driver before creating\nthe associated job. Entries with invalid info are simply discarded.\n\n.. index::\n   pair: Conversion; Job\n\nConversion Job\n~~~~~~~~~~~~~~\n\nA conversion job goes through the following steps:\n  - The current file metadata is retrieved\n  - The TPC job is prepared with appropriate opaque info\n  - The TPC job is executed\n  - Once TPC is completed, verify the new file has all fragments according to layout\n  - Verify initial file hasn't changed (checksum is the same)\n  - Merge the conversion entry with the initial file\n  - Mark conversion job as completed\n\nIf at any step a failure is encountered, the conversion job will be flagged\nas failed.\n\n.. index::\n   pair: Converter; Scheduler\n\nConverter Scheduler\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe Converter Scheduler is the component responsible for creating conversion jobs,\naccording to a given set of conversion rules. A conversion rule is placed\non a namespace entry (file or directory), contains optional filters\nand the target storage parameter.\n\n- When a conversion rule is placed on a file, an immediate conversion job is created\n  and pushed to QuarkDB.\n- When a conversion rule is placed on a directory, a tree traversal is initiated\n  and all files which pass the filtering criteria will be scheduled for conversion.\n\n.. index::\n   pair: Converter; Configuration\n\nConfiguration\n^^^^^^^^^^^^^\nThe Converter is enabled/disabled globally on the instance:\n\n.. code-block:: bash\n\n   # enable\n   eos convert config set status=on\n   # disable\n   eos convert config set status=off\n\n.. warning:: Be aware that you have to grant project quota in the converter\n             directory if your instances has quota enabled, otherwise the\n             converter cannot write files because the same quota restrictions\n             apply.\n\nThe current status of the Converter can be seen via:\n\n.. code-block:: bash\n\n   eos convert config list\n   Configuration: status=on max-thread-pool-size=101 max-queue-size=1002\n   Threadpool: pool=converter      min=16  max=100  size=16   queue_sz=0\n   Running jobs: 0\n   Pending jobs: 0\n   Failed jobs : 0\n\n\nThe maximum number of threads in the thread pool as well as the maximum\nqueue size for conversion jobs can be modified using the same CLI:\n\n.. code-block:: bash\n\n   # Set conversion thread pool max size\n   eos convert config set max-thread-pool-size=1000\n\n   # Set the maximum queue size of conversion jobs\n   eos convert config set max-queue-size=250\n\n.. index::\n   pair: Converter; Log Files\n\nTo monitor the activity of the Converter engine, one can look at the\nstatistics related to stated/successful and failed conversion transfers.\nThis command will also print a summary of the converter configuration\nand the number of files tracked by the converter:\n\n.. code-block:: bash\n   eos ns stat | grep -i \"conve\"\n   ALL      converter info                   pool=converter      min=64  max=100  size=64   queue_sz=0\n   ALL      tracker info                     tracker=convert size=0\n   all ConversionJobFailed                         128.38 K       0.00       0.00       0.00       0.00     -NA-      -NA-      -NA-      -NA-     -NA-\n   all ConversionJobStarted                          1.77 M       0.00       0.00       1.72       1.47     -NA-      -NA-      -NA-      -NA-     -NA-\n   all ConversionJobSuccessful                       1.64 M       0.00       0.00       1.72       1.47     -NA-      -NA-      -NA-      -NA-     -NA-\n\n\nLog Files\n^^^^^^^^^\n\nThe Converter has a dedicated log file under ``/var/log/eos/mgm/Converter.log``\nwhich shows scheduled conversions and any potential errors. To get more\nverbose information you can change the log level:\n\n.. code-block:: bash\n\n   # switch to debug log level on the MGM\n   eos debug debug\n\n   # switch back to info log level on the MGM\n   eos debug info\n\n\n.. index::\n   pair: MGM; Balancing\n\nBalancing\n---------\n\nThe rebalacing system is made out of three services:\n\n.. epigraph::\n\n   ========================= ======================================================================\n   Name                      Responsibility\n   ========================= ======================================================================\n   Filesystem Balancer       Balance relative usage between all filesystem within a group\n   Group Balancer            Balance relative usage between groups\n   GEO Balancer              Balance relative usage between geographic locations\n   ========================= ======================================================================\n\n.. index::\n   pair: Balancer; File System Balancer\n\nFilesystem Balancer\n^^^^^^^^^^^^^^^^^^^\n\nOverview\n\"\"\"\"\"\"\"\"\"\n\nThe filesystem balancing system provides a fully automated mechanism to balance the\nvolume usage across a scheduling group. Hence currently the balancing system\ndoes not balance between scheduling groups!\n\nThe balancing system is made up by the cooperation of several components:\n\n* Central File System View with file system usage information and space configuration\n* Centrally running balancer thread steering the filesystem balancer process by computing averages and deviations\n* Balancer Thread on each FST pulling workload to pull files locally to balance filesystems\n\n.. ::note\n\n   Balancing is en-/disabled in each space separately!\n\n.. index::\n   pair: Balancer; Info\n\nBalancing View and Configuration\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nEach filesystem advertises the used volume and the central view allows to see\nthe deviation from the average filesystem usage in each group.\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> group ls\n   #---------------------------------------------------------------------------------------------------------------------\n   #     type #           name #     status #nofs #dev(filled) #avg(filled) #sig(filled) #balancing #  bal-run #drain-run\n   #---------------------------------------------------------------------------------------------------------------------\n   groupview  default.0                  on     8         0.27         0.10         0.12 idle                0          0\n   groupview  default.1                  on     8         0.28         0.10         0.12 idle                0          0\n   groupview  default.10                 on     8         0.29         0.10         0.13 idle                0          0\n   groupview  default.11                 on     8         0.29         0.10         0.13 idle                0          0\n   groupview  default.12                 on     7         0.28         0.11         0.14 idle                0          0\n   groupview  default.13                 on     8         0.28         0.12         0.14 idle                0          0\n   groupview  default.14                 on     8         0.29         0.10         0.13 idle                0          0\n   groupview  default.15                 on     8         0.30         0.10         0.13 idle                0          0\n   groupview  default.16                 on     7         0.26         0.12         0.13 idle                0          0\n   groupview  default.17                 on     8         0.28         0.12         0.14 idle                0          0\n   groupview  default.18                 on     8         0.30         0.10         0.14 idle                0          0\n   groupview  default.19                 on     8        12.42         4.76         6.80 idle                0          0\n   groupview  default.2                  on     8         0.48         0.16         0.23 idle                0          0\n   groupview  default.20                 on     8        14.03         5.43         7.62 idle                0          0\n   groupview  default.21                 on     8         0.48         0.16         0.23 idle                0          0\n   groupview  default.3                  on     8         0.28         0.10         0.12 idle                0          0\n   groupview  default.4                  on     8         0.26         0.11         0.13 idle                0          0\n   groupview  default.5                  on     8         0.27         0.10         0.12 idle                0          0\n   groupview  default.6                  on     8         0.27         0.10         0.12 idle                0          0\n   groupview  default.7                  on     8         0.27         0.09         0.12 idle                0          0\n   groupview  default.8                  on     8         0.27         0.10         0.12 idle                0          0\n   groupview  default.9                  on     8         0.30         0.11         0.14 idle                0          0\n\n\nThe decision parameters to enable balancing in a group is the maximum deviation\nof the filling state (given in %).\nIn this example two groups are unbalanced (12 + 14 %).\n\nThe balancing is configured on the space level and the current configuration\nis displayed using the 'space status' command:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   balancer                         := off\n   balancer.node.ntx                := 10\n   balancer.node.rate               := 10\n   balancer.threshold               := 1\n   ...\n\n.. index::\n   pair: Balancer; Configuration\n\nThe configuration variables are:\n\n.. epigraph::\n\n   ========================= ======================================================================\n   variable                  definition\n   ========================= ======================================================================\n   balancer                  can be off or on to disable or enable the balancing\n   balancer.node.ntx         number of parallel balancer transfers running on each FST\n   balancer.node.rate        rate limitation for each running balancer transfer in MB/s\n   balancer.threshold        percentage at which balancing gets enabled within a scheduling group\n   ========================= ======================================================================\n\nIf balancing is enabled ....\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space config default space.balancer=on\n   success: balancer is enabled!\n\nGroups which are balancing are shown via the **eos group ls** command:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> group ls\n   #---------------------------------------------------------------------------------------------------------------------\n   #     type #           name #     status #nofs #dev(filled) #avg(filled) #sig(filled) #balancing #  bal-run #drain-run\n   #---------------------------------------------------------------------------------------------------------------------\n   groupview  default.0                  on     8         0.27         0.10         0.12 idle                0          0\n   groupview  default.1                  on     8         0.28         0.10         0.12 idle                0          0\n   groupview  default.10                 on     8         0.29         0.10         0.13 idle                0          0\n   groupview  default.11                 on     8         0.29         0.10         0.13 idle                0          0\n   groupview  default.12                 on     7         0.28         0.11         0.14 idle                0          0\n   groupview  default.13                 on     8         0.28         0.12         0.14 idle                0          0\n   groupview  default.14                 on     8         0.29         0.10         0.13 idle                0          0\n   groupview  default.15                 on     8         0.30         0.10         0.13 idle                0          0\n   groupview  default.16                 on     7         0.26         0.12         0.13 idle                0          0\n   groupview  default.17                 on     8         0.28         0.12         0.14 idle                0          0\n   groupview  default.18                 on     8         0.30         0.10         0.14 idle                0          0\n   groupview  default.19                 on     8        12.42         4.76         6.80 balancing          10          0\n   groupview  default.2                  on     8         0.48         0.16         0.23 idle                0          0\n   groupview  default.20                 on     8        14.03         5.43         7.62 balancing          12          0\n   groupview  default.21                 on     8         0.48         0.16         0.23 idle                0          0\n   groupview  default.3                  on     8         0.28         0.10         0.12 idle                0          0\n   groupview  default.4                  on     8         0.26         0.11         0.13 idle                0          0\n   groupview  default.5                  on     8         0.27         0.10         0.12 idle                0          0\n   groupview  default.6                  on     8         0.27         0.10         0.12 idle                0          0\n   groupview  default.7                  on     8         0.27         0.09         0.12 idle                0          0\n   groupview  default.8                  on     8         0.27         0.10         0.12 idle                0          0\n   groupview  default.9                  on     8         0.30         0.11         0.14 idle                0          0\n\nThe current balancing can also be viewed by space or node:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space ls --io\n   #----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   #     name # diskload # diskr-MB/s # diskw-MB/s #eth-MiB/s # ethi-MiB # etho-MiB #ropen #wopen # used-bytes #  max-bytes # used-files # max-files #  bal-run #drain-run\n   #----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   default       0.02        66.00        66.00        862         57         60     31     22      1.99 TB    347.33 TB     805.26 k     16.97 G         51          0\n\n   EOS Console [root://localhost] |/> node ls --io\n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   #               hostport # diskload # diskr-MB/s # diskw-MB/s #eth-MiB/s # ethi-MiB # etho-MiB #ropen #wopen # used-bytes #  max-bytes # used-files # max-files #  bal-run #drain-run\n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   lxfsra02a02.cern.ch:1095       0.08        41.00         0.00        119          0         41     23      0    825.47 GB     41.92 TB     298.80 k      2.05 G          0          0\n   lxfsra02a05.cern.ch:1095       0.03        19.00         0.00        119          0         19      2      0    832.01 GB     43.92 TB     152.14 k      2.15 G          0          0\n   lxfsra02a06.cern.ch:1095       0.01         0.00        11.00        119         12          0      0      6     70.05 GB     43.92 TB      54.77 k      2.15 G         10          0\n   lxfsra02a07.cern.ch:1095       0.01         0.00        11.00        119          9          0      0      3     79.95 GB     43.92 TB      75.91 k      2.15 G         10          0\n   lxfsra02a08.cern.ch:1095       0.01         0.00        11.00        119          9          0      0      2     52.01 GB     43.92 TB      61.25 k      2.15 G          8          0\n   lxfsra04a01.cern.ch:1095       0.01         0.00        10.00        119          9          0      0      1     72.12 GB     41.92 TB      60.92 k      2.05 G          8          0\n   lxfsra04a02.cern.ch:1095       0.01         0.00        10.00        119          9          0      0      7     52.32 GB     43.92 TB      86.72 k      2.15 G         10          0\n   lxfsra04a03.cern.ch:1095       0.01         0.00        10.00        119          9          0      0      5     10.53 GB     43.92 TB      14.80 k      2.15 G          5          0\n\nTo see the usage difference within the group, one can inspect all the group filesystems via **eos group ls --IO** e.g.\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> group ls --IO default.20\n   #---------------------------------------------------------------------------------------------------------------------\n   #     type #           name #     status #nofs #dev(filled) #avg(filled) #sig(filled) #balancing #  bal-run #drain-run\n   #---------------------------------------------------------------------------------------------------------------------\n   groupview  default.20                 on     8        13.71         5.48         7.47 balancing          37          0\n   #.................................................................................................................................................................................................................\n   #                     hostport #  id #     schedgroup # diskload # diskr-MB/s # diskw-MB/s #eth-MiB/s # ethi-MiB # etho-MiB #ropen #wopen # used-bytes #  max-bytes # used-files # max-files #  bal-run #drain-run\n   #.................................................................................................................................................................................................................\n   lxfsra02a05.cern.ch:1095    17       default.20       0.47        12.00         0.00        119          0         21      1      0    383.17 GB      2.00 TB      59.33 k     97.52 M          0          0\n   lxfsra02a06.cern.ch:1095    35       default.20       0.08         0.00         6.00        119         10          0      0      6     26.56 GB      2.00 TB       6.23 k     97.52 M          7          0\n   lxfsra04a01.cern.ch:1095    57       default.20       0.13         0.00         6.00        119          9          0      0      4     25.01 GB      2.00 TB       6.11 k     97.52 M          4          0\n   lxfsra02a08.cern.ch:1095    77       default.20       0.08         0.00         6.00        119         11          0      0      5     27.36 GB      2.00 TB       6.64 k     97.52 M          8          0\n   lxfsra04a02.cern.ch:1095    99       default.20       0.07         0.00         4.00        119         10          0      0      3     26.57 GB      2.00 TB       7.75 k     97.52 M          6          0\n   lxfsra02a02.cern.ch:1095   121       default.20       1.00        22.00         0.00        119          0         41     21      0    351.07 GB      2.00 TB      59.80 k     97.52 M          0          0\n   lxfsra02a07.cern.ch:1095   143       default.20       0.10         0.00         7.00        119          9          0      0      2     28.57 GB      2.00 TB       7.46 k     97.52 M          7          0\n   lxfsra04a03.cern.ch:1095   165       default.20       0.12         0.00         6.00        119         10          0      0      5      7.56 GB      2.00 TB       2.96 k     97.52 M          5          0\n\n\nThe scheduling activity for balancing can be monitored with the **eos ns ls** command:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> ns stat\n   # ------------------------------------------------------------------------------------\n   # Namespace Statistic\n   # ------------------------------------------------------------------------------------\n   ALL      Files                            682781 [booted] (12s)\n   ALL      Directories                      1316\n   # ....................................................................................\n   ALL      File Changelog Size              804.27 MB\n   ALL      Dir  Changelog Size              515.98 kB\n   # ....................................................................................\n   ALL      avg. File Entry Size             1.18 kB\n   ALL      avg. Dir  Entry Size             392.00 B\n   # ------------------------------------------------------------------------------------\n   ALL      Execution Time                   0.40 +- 1.12\n   # -----------------------------------------------------------------------------------------------------------\n   who      command                          sum             5s     1min     5min       1h exec(ms) +- sigma(ms)\n   # -----------------------------------------------------------------------------------------------------------\n   ALL        Access                                      0     0.00     0.00     0.00     0.00     -NA- +- -NA-\n    ....\n   ALL        Schedule2Balance                         6423    11.75    10.81    10.71     1.78     -NA- +- -NA-\n   ALL        Schedule2Drain                              0     0.00     0.00     0.00     0.00     -NA- +- -NA-\n   ALL        Scheduled2Balance                        6423    11.75    10.81    10.71     1.78     4.20 +- 0.57\n   ALL        SchedulingFailedBalance                     0     0.00     0.00     0.00     0.00     -NA- +- -NA-\n\n\nThe relevant counters are:\n\n.. epigraph::\n\n   ============================== =====================================================================\n   state                          definition\n   ============================== =====================================================================\n   Schedule2Balance               counter/rate at which all FSTs ask for a file to balance\n   ScheduledBalance               counter/rate of balancing transfers which have been scheduled to FSTs\n   SchedulingFailedBalance        counter/rate of scheduling requests which could not get any workload\n                                  (e.g. no file matches the target machine)\n   ============================== =====================================================================\n\n.. index::\n   pair: Balancer; Group Balancer\n\nGroup Balancer\n^^^^^^^^^^^^^^\n\nThe group balancer uses the converter mechanism to move files from groups\nabove a given threshold filling state to groups under the threshold filling\nstate. Once the groups fall within the threshold they no longer participate in\nbalancing and thus prevents further oscillations, once the groups are in a\nsettled state.\n\n\n.. index::\n   pair: Group Balancer; Engine\n\n\nGroup Balancer Engine\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nFrom EOS 4.8.74 2 different balancer engines are supported which can be switched\nat runtime. A brief description of the various engines and their features are\ndescribed below. Please note that only one engine can be configured to run at a\ntime.\n\nStd\n~~~\n\nThis is the default engine, which uses deviation from the average groups filled\nto decide which groups are the outliers to be balanced. Both the deviation from\nthe left and right can be configured individually to further fine tune how the\ngroups are picked for balancing. The parameter is to be entered as percent value\nas deviation from average. Groups within the threshold values will not\nparticipate in balancing. Files from groups above the threshold will be picked\nat random within constraints (see `min/max_file_size` config below) and moved to\ngroups below threshold. The parameters expected for the engine are\n`max_threshold` and `min_threshold`, groups above max_threshold deviation from\naverage and below min_threshold deviation from average will be the participating\ngroups. For compatibility the currently ``groupbalancer.threshold`` will be as a\ndefault value in case both ``groupbalancer.min_threshold`` and\n``groupbalancer.max_threshold`` aren't provided. It is recommended to explicitly\nconfigure as this option may be removed in a future release.\n\nMinMax\n~~~~~~\n\nThis engine can be used as a stop gap engine to balance outliers, unlike the\nstd. engine no averages are computed, this engine takes static min & max\nthreshold values which are absolute `%` of groups fill ratio. Groups with usage\nabove the `max_threshold` (for eg 90%) will be chosen for filling to groups with\nusage below `min_threshold`. While for almost all common use cases std. engine\nshould fit the bill, when needing to do targeted balancing only on certain\noutliers this engine can be used as a temporary measure. This engine is only\nrecommended as a quick fix to balance outliers and then it is recommended to run\nthe std. engine to balance for longer periods of time.\n\n.. index::\n   pair: Group Balancer; Configuration\n\nFreespace\n~~~~~~~~~\n\nThis engine can be used in case groups have non uniform total capacities and you\nwant to make the absolute free space equal in all groups. The geoscheduler picks\ngroups in a round robin fashion, so having absolute freespace equal makes it\neasy to keep groups in balance after. The same parameters `max_threshold` and\n`min_threshold` can be used to tweak the spread of total freespace allowed. Additionally a list of groups that do not need to participate in balancing activity can be configured via the key ``groupbalancer.blocklist``. For adding removing the same key needs to be set again to the new value.\n\n\nConfiguration\n\"\"\"\"\"\"\"\"\"\"\"\"\"\nGroupbalancing is enabled/disabled by space:\n\n.. code-block:: bash\n\n   # enable\n   eos space config default space.groupbalancer=on\n   # disable\n   eos space config default space.groupbalancer=off\n\nThe current configuration of Group Balancing can be seen via\n\n.. code-block:: bash\n\n   eos -b space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   ...\n   groupbalancer                    := on\n   groupbalancer.engine             := std\n   groupbalancer.file_attempts      := 50\n   groupbalancer.max_file_size      := 20000000000\n   groupbalancer.min_file_size      := 1000000000\n   groupbalancer.max_threshold      := 5\n   groupbalancer.min_threshold      := 5\n   groupbalancer.ntx                := 1500\n   groupbalancer.threshold          := 1  # Deprecated, this value will not be used if min/max thresholds are set\n   ...\n\nThe ``max_file_size`` and ``min_file_size`` parameter decides the size of files\nto be picked for transfer. The ``file_attempts`` is the number of attempts the\nrandom picker will use to try to find a file within those sizes. For really\nsparse file systems, where the probability of finding a file within the size\nmight be lower, it is possible to tweak this number. The number of concurrent\ntransfers to schedule is defined via the **groupbalancer.ntx** space variable,\nthis is the number of transfers in every cycle of groupbalancer scheduling,\nwhich is every 10s. Hence it is recommended to set a min value in the hundreds\nor around 1000 (and watch the progress occasionally with eos io stat) if the\ngroups are really unbalanced:\n\n.. code-block:: bash\n\n   # schedule 10 transfers in parallel\n   eos space config default space.groupbalancer.ntx=1000\n\nConfigure the groupbalancer engine:\n\n.. code-block:: bash\n\n   # configure the goupbalancer engine\n   eos space config default space.groupbalancer.engine=std\n\nThe threshold in percent is defined via the **groupbalancer.min_threshold** &\n**groupbalancer.max_threshold** variable. For std. balancer engine this is a\npercent deviation from average:\n\n.. code-block:: bash\n\n   # set a 3 percent min threshold & 5 percent max threshold\n   eos space config default space.groupbalancer.min_threshold=3\n   eos space config default space.groupbalancer.max_threshold=5\n\nIn case you want to run the minmax balancer engine, here the values are\nabsolute values\n\n   # set a 3 percent min threshold & 5 percent max threshold\n   eos space config default space.groupbalancer.engine=minmax\n   eos space config default space.groupbalancer.min_threshold=60\n   eos space config default space.groupbalancer.max_threshold=80\n\n\nMake sure that you have enabled the converter and the **converter.ntx** space\nvariable is bigger than **groupbalancer.ntx** :\n\n.. code-block:: bash\n\n   # enable the converter\n   eos space config default space.converter=on\n   # run 20 conversion transfers in parallel\n   eos space config default space.converter.ntx=20\n\nOne can see the same settings and the number of active conversion transfers\n(scroll to the right):\n\n.. code-block:: bash\n\n   eos space ls\n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   #     type #           name #  groupsize #   groupmod #N(fs) #N(fs-rw) #sum(usedbytes) #sum(capacity) #capacity(rw) #nom.capacity #quota #balancing # threshold # converter #  ntx # active #intergroup\n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   spaceview           default           22           22    202       123          2.91 T       339.38 T      245.53 T          0.00     on        off        0.00          on 100.00     0.00         off\n\n\nConfigure blocklisting, ie. groups that do not participate. (Only used in freespace engine currently)\n\n.. code-block:: bash\n\n   # blocklist groups default.2, default.8 in participating\n   eos space config default space.groupbalancer.blocklist=default.2, default.8\n\n.. index::\n   pair: Group Balancer; Info\n\nStatus\n\"\"\"\"\"\"\"\n\nStatus of the groupbalancer engine can be viewed with\n\n.. code-block:: bash\n\n   $ eos space groupbalancer status default\n   Engine configured          : Std\n   Current Computed Average   : 0.397366\n   Min Deviation Threshold    : 0.03\n   Max Deviation Threshold    : 0.05\n   Total Group Size: 25\n   Total Groups Over Threshold: 8\n   Total Groups Under Threshold: 12\n   # Detailed view of groups available with `--detail` switch\n   $ eos space groupbalancer status default --detail\n   engine configured          : Std\n   Current Computed Average   : 0.397258\n   Min Deviation Threshold    : 0.03\n   Max Deviation Threshold    : 0.05\n   Total Group Size: 25\n   Total Groups Over Threshold: 8\n   Total Groups Under Threshold: 12\n   Groups Over Threshold\n   ┌──────────┬──────────┬──────────┬──────────┐\n   │Group     │ UsedBytes│  Capacity│    Filled│\n   ├──────────┴──────────┴──────────┴──────────┤\n   │default.8      2.75 T     6.00 T       0.46│\n   │default.6      5.34 T     6.00 T       0.89│\n   │default.5      2.78 T     6.00 T       0.46│\n   │default.12     2.74 T     6.00 T       0.46│\n   │default.11     2.77 T     6.00 T       0.46│\n   │default.10     2.74 T     6.00 T       0.46│\n   │default.3      2.83 T     6.00 T       0.47│\n   │default.0      5.36 T     6.00 T       0.89│\n   └───────────────────────────────────────────┘\n\n   Groups Under Threshold\n   ┌──────────┬──────────┬──────────┬──────────┐\n   │Group     │ UsedBytes│  Capacity│    Filled│\n   ├──────────┴──────────┴──────────┴──────────┤\n   │default.9      2.19 T     6.00 T       0.36│\n   │default.7      2.18 T     6.00 T       0.36│\n   │default.24     1.78 T     6.00 T       0.30│\n   │default.21     2.20 T     6.00 T       0.37│\n   │default.2      1.47 G     6.00 T       0.00│\n   │default.18     1.86 T     6.00 T       0.31│\n   │default.17     2.17 T     6.00 T       0.36│\n   │default.20     1.81 T     6.00 T       0.30│\n   │default.15     1.80 T     6.00 T       0.30│\n   │default.14     6.10 G     6.00 T       0.00│\n   │default.13     2.15 T     6.00 T       0.36│\n   │default.1      1.75 T     6.00 T       0.29│\n   └───────────────────────────────────────────┘\n\nFor MinMax engines these numbers are absolute percent (for eg this was configured with 45 & 85)\n\n.. code-block:: bash\n\n   $ eos space groupbalancer status default\n   Engine configured: MinMax\n   Min Threshold    : 0.45\n   Max Threshold    : 0.85\n   Total Group Size: 25\n   Total Groups Over Threshold: 9\n   Total Groups Under Threshold: 4\n\nThere is a 60s cache for values, so if values are reconfigured\n\nTraffic from the groupbalancer is tagged as ``eos/groupbalancer`` and visible in iostat\n\n.. code-block:: bash\n\n   eos io stat -x\n    io │             application│    1min│    5min│      1h│     24h\n   └───┴────────────────────────┴────────┴────────┴────────┴────────┘\n   out        eos/groupbalancer  86.41 G 190.89 G   2.95 T  19.15 T\n   out          eos/replication        0   1.49 G  52.96 G  52.96 G\n   out                    other      605   1.33 K  10.77 K  64.73 K\n   in         eos/groupbalancer  18.91 G  85.30 G   2.83 T  19.04 T\n   in           eos/replication        0   1.43 G  52.90 G  52.90 G\n   in                     other      605   1.33 K  10.77 K  64.73 K\n\n.. index::\n   pair: Group Balancer; Log Files\n\nLog Files\n\"\"\"\"\"\"\"\"\"\"\nThe Group Balancer has a dedicated log file under ``/var/log/eos/mgm/GroupBalancer.log``\nwhich shows basic variables used for balancing decisions and scheduled transfers. To get more\nverbose information you can change the log level:\n\n.. code-block:: bash\n\n   # switch to debug log level on the MGM\n   eos debug debug\n\n   # switch back to info log level on the MGM\n   eos debug info\n\n.. index::\n   pair: Balancer; GEO Balancer\n\n\nGEO Balancer\n^^^^^^^^^^^^\n\nThe GEO Balancer uses the converter mechanism to redistribute files according\nto their geographical location. Currently it is only moving files with replica\nlayouts. To avoid oscillations a threshold parameter defines when geo balancing stops e.g.\nthe deviation from the average in a group is less then the threshold parameter.\n\n.. index::\n   pair: GEO Balancer; Configuration\n\nConfiguration\n\"\"\"\"\"\"\"\"\"\"\"\"\"\nGEO balancing uses the relative filling state of a geo tag and not absolute byte\nvalues.\n\nGEO balancing is enabled/disabled by space:\n\n.. code-block:: bash\n\n   # enable\n   eos space config default space.geobalancer=on\n   # disable\n   eos space config default space.geobalancer=off\n\nThe current status of GEO Balancing can be seen via\n\n.. code-block:: bash\n\n   eos -b space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   ...\n   geobalancer                    := off\n   geobalancer.ntx                := 0\n   geobalancer.threshold          := 0.1\n   ...\n\nThe number of concurrent transfers to schedule is defined via the **geobalancer.ntx**\nspace variable:\n\n.. code-block:: bash\n\n   # schedule 10 transfers in parallel\n   eos space config default space.geobalancer.ntx=10\n\nThe threshold in percent is defined via the **geobalancer.threshold** variable:\n\n.. code-block:: bash\n\n   # set a 5 percent threshold\n   eos space config default space.geobalancer.threshold=5\n\nMake sure that you have enabled the converter and the **converter.ntx** space\nvariable is bigger than **geobalancer.ntx** :\n\n.. code-block:: bash\n\n   # enable the converter\n   eos space config default space.converter=on\n   # run 20 conversion transfers in parallel\n   eos space config default space.converter.ntx=20\n\nOne can see the same settings and the number of active conversion transfers\n(scroll to the right):\n\n.. code-block:: bash\n\n   eos space ls\n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   #     type #           name #  groupsize #   groupmod #N(fs) #N(fs-rw) #sum(usedbytes) #sum(capacity) #capacity(rw) #nom.capacity #quota #balancing # threshold # converter #  ntx # active #intergroup\n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   spaceview           default           22           22    202       123          2.91 T       339.38 T      245.53 T          0.00     on        off        0.00          on 100.00     0.00         off\n\n.. warning::\n   You have to configure geo mapping for clients, at least for the MGM machine,\n   otherwise EOS does not apply the geoplacement/scheduling algorithm and GEO\n   Balancing does not give the expected results!\n\n.. index::\n   pair: GEO Balanacer; Log Files\n\nLog Files\n\"\"\"\"\"\"\"\"\"\nThe GEO Balancer has a dedicated log file under ``/var/log/eos/mgm/GeoBalancer.log``\nwhich shows basic variables used for balancing decisions and scheduled transfers. To get more\nverbose information you can change the log level:\n\n.. code-block:: bash\n\n   # switch to debug log level on the MGM\n   eos debug debug\n\n   # switch back to info log level on the MGM\n   eos debug info\n\n.. index::\n   pair: MGM; Draining\n\nDraining\n--------\n\nThe drain system contains two engines:\n\n* Filesystem Draining\n* Group Draining\n\n.. index::\n   pair: Draining; Filesystem Draining\n\nFilesystem Draining\n^^^^^^^^^^^^^^^^^^^\n\nOverview\n\"\"\"\"\"\"\"\"\n\nThe EOS drain system provides a fully automatic mechanism to drain (empty)\nfilesystems under certain error conditions. A file system drain is triggered\nby an IO error on a file system or manually by an operator setting a\nfilesystem in drain mode.\n\nThe drain engine makes use of the GeoTreeEngine component to decide where\nto move the drained replicas. The drain processes are spawned on the MGM and\nrepresent simple XRootD third-party-copy transfers.\n\n.. index::\n   pair: FST; Scrubber\n\n\nFST Scrubber\n~~~~~~~~~~~~\n\nEach FST run's a dedicated thread doing scrubbing. Scrubbing is running if the\nfile system configuration is at least **wo** ( e.g. in write-only or read-write mode),\nthe file system is in **booted** state and the label of the\nfilesystem ``<mountpoint>/.eosfsid + <mountpoint>/.eosfsuuid`` is readable.\nIf the label is not readable the Scrubber broadcasts an IO error for filesystems\nin **ro**, **wo** or **rw** mode and **booted** state with the error text\n\"filesystem seems to be not mounted anymore\".\n\nThe FST scrubber follows the filling size of a disk and writes test pattern\nfiles at 0%, 10%, 20% ... 90% filling with the goal to do tests equally\ndistributed over the physical size of the disk. At each 10% filling position\nthe scrubber creates a write-once file to be re-read in each scrubbing pass\nand a re-write file which is re-written and re-read in each scrubbing pass.\nThe following pattern is written into the test files:\n\n.. code-block:: bash\n\n   scrubPattern[0][i]=0xaaaa5555aaaa5555ULL;\n   scrubPattern[0][i+1]=0x5555aaaa5555aaaaULL;\n   scrubPattern[1][i]=0x5555aaaa5555aaaaULL;\n   scrubPattern[1][i+1]=0xaaaa5555aaaa5555ULL;\n\nPattern 0 or pattern 1 is selected randomly. Each test file has 1MB size and\nthe scrub file names are ``<mountpoint>/scrub.write-once.[0-9]`` and\n``<mountpoint>/scrub.re-write.[0-9]``.\n\nIn case an error is detected, the FST broadcasts an EIO to the MGM with the\nerror text \"filesystem probe error detected\".\n\nYou can see filesystems in error state and the error text on the MGM node doing:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> fs ls -e\n   #...............................................................................................\n   #                   host #   id #     path #       boot # configstatus #      drain #... #errmsg\n   #...............................................................................................\n        lxfsrk51a02.cern.ch   3235    /data05  opserror            empty      drained   5 filesystem seems to be\n                                                                                          not mounted anymore\n        lxfsrk51a04.cern.ch   3372    /data19  opserror            empty      drained   5 filesystem probe error detected\n\n\n.. index::\n   pair: Filesystem; Statemachine\n   pair: Filesystem; View\n\nCentral File System View and State Machine\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nEach filesystem in EOS has a configuration, boot state and drain state.\n\nThe possible configuration states are self explaining:\n\n.. epigraph::\n\n   ============= ======================================================================================\n   state          definition\n   ============= ======================================================================================\n   rw            filesystem set in read write mode\n   wo            filesystem set in write-once mode\n   ro            filesystem set in read-only mode\n   drain         filesystem set in drain mode\n   off           filesystem set disabled\n   empty         filesystem is empty e.g. contains no files any more\n   ============= ======================================================================================\n\nFile systems involved in any kind of IO need to be in boot state booted.\n\nThe configured file systems are shown via:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> fs ls\n\n   #.........................................................................................................................\n   #                   host (#...) #   id #           path #     schedgroup #       boot # configstatus #      drain # active\n   #.........................................................................................................................\n        lxfsra02a05.cern.ch (1095)      1          /data01        default.0       booted             rw      nodrain   online\n        lxfsra02a05.cern.ch (1095)      2          /data02       default.10       booted             rw      nodrain   online\n        lxfsra02a05.cern.ch (1095)      3          /data03        default.1       booted             rw      nodrain   online\n        lxfsra02a05.cern.ch (1095)      4          /data04        default.2       booted             rw      nodrain   online\n        lxfsra02a05.cern.ch (1095)      5          /data05        default.3       booted             rw      nodrain   online\n\nAs shown each file system has also a drain state. Drain states can be:\n\n.. epigraph::\n\n   ================ ==============================================================================================================================================================================\n   state            definition\n   ================ ==============================================================================================================================================================================\n   nodrain          file system is currently not draining\n   prepare          the drain process is prepared - this phase lasts 60 seconds\n   wait             the drain process either waits for the namespace to be booted or it is waiting that the graceperiod has passed (see below)\n   draining         the drain process is enabled - nodes inside the scheduling group start to pull transfers to drop replicas from the filesystem to drain\n   stalling         in the last 5 minutes there was noprogress of the drain procedure. This happens if the files to transfer are very huge or there are only files left which cannot be replicated.\n   expired          the time defined by the drainperiod variable has passed and the drain process is stopped. There are files left on the disk which couldn't be drained.\n   drained          all files have been drained from the filesystem.\n   failed           the drain activity is finished but there are still files on file system that could not be drained and require a manual inspection.\n   ================ ==============================================================================================================================================================================\n\nThe final state can be one of the following: expired, failed or drained.\n\nThe drain and grace periods are defined as a space variables (e.g. automatically\napplied to all filesystems in that space when they are moved into or registered).\n\nOne can see the settings via the space command:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   balancer                         := on\n   balancer.node.ntx                := 10\n   balancer.node.rate               := 10\n   balancer.threshold               := 1\n   drainer.node.ntx                 := 10\n   drainer.node.rate                := 25\n   drainperiod                      := 3600\n   graceperiod                      := 86400\n   groupmod                         := 24\n   groupsize                        := 20\n   headroom                         := 0.00 B\n   quota                            := off\n   scaninterval                     := 1\n\nThey can be modified by setting the *drainperiod* or *graceperiod* variable in\nnumber of seconds:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space config default space.drainperiod=86400\n   success: setting drainperiod=86400\n\n   EOS Console [root://localhost] |/> space config default space.graceperiod=86400\n   success: setting graceperiod=86400\n\n.. warning::\n   This defines the variables only if filesystems are registered or moved into that space.\n\nIf you want to apply this setting to all filesystems in that space,\nyou have additionally to call:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space config default fs.drainperiod=86400\n   EOS Console [root://localhost] |/> space config default fs.graceperiod=86400\n\nIf you want a global overview about running drain processes, you can get the\nnumber of running drain transfers by space, by group, by node and by filesystem:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space ls --io\n   #----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   #     name # diskload # diskr-MB/s # diskw-MB/s #eth-MiB/s # ethi-MiB # etho-MiB #ropen #wopen # used-bytes #  max-bytes # used-files # max-files #  bal-run #drain-run\n   #----------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   default       0.01        32.00        17.00        862         15         14      9      9      6.97 TB    347.33 TB      20.42 M     16.97 G          0         10\n\n   EOS Console [root://localhost] |/> group  ls --io\n   #----------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   #           name # diskload # diskr-MB/s # diskw-MB/s #eth-MiB/s # ethi-MiB # etho-MiB #ropen #wopen # used-bytes #  max-bytes # used-files # max-files #  bal-run #drain-run\n   #----------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   default.0              0.00         0.00         0.00        952        217        199      0      0    338.31 GB     15.97 TB     952.65 k    780.14 M          0          0\n   default.1              0.00         0.00         0.00        952        217        199      0      0    336.07 GB     15.97 TB     927.18 k    780.14 M          0          0\n   default.10             0.00         0.00         0.00        952        217        199      0      0    332.23 GB     15.97 TB     926.45 k    780.14 M          0          0\n   default.11             0.00         0.00         0.00        952        217        199      0      0    325.14 GB     15.97 TB     948.02 k    780.14 M          0          0\n   default.12             0.00         0.00         0.00        833        180        179      0      0     22.39 GB     13.97 TB     898.40 k    682.62 M          0          0\n   default.13             0.00         0.00         1.00        952        217        199      0      0    360.30 GB     15.97 TB     951.05 k    780.14 M          0          0\n   default.14             0.99        96.00       206.00        952        217        199     31     30    330.45 GB     15.97 TB     956.50 k    780.14 M          0         36\n   default.15             0.00         0.00         0.00        952        217        199      0      0    308.26 GB     15.97 TB     939.26 k    780.14 M          0          0\n   default.16             0.00         0.00         0.00        833        188        184      0      0    327.76 GB     13.97 TB     899.97 k    682.62 M          0          0\n   default.17             0.87       100.00       202.00        952        217        199     16     28    368.09 GB     15.97 TB     933.95 k    780.14 M          0         31\n   default.18             0.00         0.00         0.00        952        217        199      0      0    364.27 GB     15.97 TB     953.94 k    780.14 M          0          0\n   default.19             0.00         0.00         0.00        952        217        199      0      0    304.66 GB     15.97 TB     939.24 k    780.14 M          0          0\n   default.2              0.00         0.00         0.00        952        217        199      0      0    333.64 GB     15.97 TB     920.26 k    780.14 M          0          0\n   default.20             0.00         0.00         0.00        952        217        199      0      0    335.00 GB     15.97 TB     957.02 k    780.14 M          0          0\n   default.21             0.00         0.00         0.00        952        217        199      0      0    335.18 GB     15.97 TB     921.75 k    780.14 M          0          0\n   default.3              0.00         0.00         0.00        952        217        199      0      0    319.06 GB     15.97 TB     919.02 k    780.14 M          0          0\n   default.4              0.00         0.00         0.00        952        217        199      0      0    320.18 GB     15.97 TB     826.62 k    780.14 M          0          0\n   default.5              0.00         0.00         0.00        952        217        199      0      0    320.12 GB     15.97 TB     924.60 k    780.14 M          0          0\n   default.6              0.00         0.00         0.00        952        217        199      0      0    333.56 GB     15.97 TB     920.32 k    780.14 M          0          0\n   default.7              0.00         0.00         0.00        952        217        199      0      0    333.42 GB     15.97 TB     922.51 k    780.14 M          0          0\n   default.8              0.00         0.00         0.00        952        217        199      0      0    335.67 GB     15.97 TB     925.39 k    780.14 M          0          0\n   default.9              0.00         0.00         0.00        952        217        199      0      0    325.37 GB     15.97 TB     957.84 k    780.14 M          0          0\n   test                   0.00         0.00         0.00          0          0          0      0      0       0.00 B       0.00 B         0.00        0.00          0          0\n\n   EOS Console [root://localhost] |/> node  ls --io\n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   #               hostport # diskload # diskr-MB/s # diskw-MB/s #eth-MiB/s # ethi-MiB # etho-MiB #ropen #wopen # used-bytes #  max-bytes # used-files # max-files #  bal-run #drain-run\n   #------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n   eosdevsrv1.cern.ch:1095       0.00         0.00         0.00          0          0          0      0      0       0.00 B       0.00 B         0.00        0.00          0          0\n   lxfsra02a02.cern.ch:1095       0.10        19.00        55.00        119         37         20      7      8    935.18 GB     41.92 TB       2.54 M      2.05 G          0         10\n   lxfsra02a05.cern.ch:1095       0.06         5.00        53.00        119         30          5      1     10    968.03 GB     43.92 TB       2.71 M      2.15 G          0         10\n   lxfsra02a06.cern.ch:1095       0.05         0.00        50.00        119         16          0      0      6    872.91 GB     43.92 TB       2.84 M      2.15 G          0          6\n   lxfsra02a07.cern.ch:1095       0.05        33.00        10.00        119         23         33      6      7    882.25 GB     43.92 TB       3.03 M      2.15 G          0          8\n   lxfsra02a08.cern.ch:1095       0.09        41.00        56.00        119         45         42      9      9    947.68 GB     43.92 TB       2.78 M      2.15 G          0         10\n   lxfsra04a01.cern.ch:1095       0.09        15.00       101.00        119         29         15      2      8    818.77 GB     41.92 TB       2.02 M      2.05 G          0         10\n   lxfsra04a02.cern.ch:1095       0.09        27.00        83.00        119         37         27      2     10    837.91 GB     43.92 TB       2.30 M      2.15 G          0         10\n   lxfsra04a03.cern.ch:1095       0.05        56.00         1.00        119          0         57     20      0    746.40 GB     43.92 TB       2.21 M      2.15 G          0          0\n\n   EOS Console [root://localhost] |/> fs ls --io\n\n   #.................................................................................................................................................................................................................\n   #                     hostport #  id #     schedgroup # diskload # diskr-MB/s # diskw-MB/s #eth-MiB/s # ethi-MiB # etho-MiB #ropen #wopen # used-bytes #  max-bytes # used-files # max-files #  bal-run #drain-run\n   #.................................................................................................................................................................................................................\n\n   ...\n\n   lxfsra04a02.cern.ch:1095   109       default.14       0.21         0.00        15.00        119         21          0      0      8     59.35 GB      2.00 TB     102.85 k     97.52 M          0          8\n\n   ...\n\n\n\nDrain Threads MGM\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nEach filesystem shown in the drain view in a non-final state has a thread on the\nMGM associated to it.\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> fs ls -d\n\n   #......................................................................................................................\n   #                   host (#...) #   id #           path #      drain #   progress #      files # bytes-left #  timeleft\n   #......................................................................................................................\n   lxfsra02a05.cern.ch (1095)     20          /data20      prepare            0         0.00       0.00 B          24\n\nA drain thread is steering the drain of each filesystem in non-final state and\nis responsible of spawning drain processes directly on the MGM node. These logical\ndrain jobs use the GeoTreeEngine to select the destination file system are queued\nin case the limits per node are reached. The drain parameters can be configured at\nthe space level:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space status default\n\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   ..\n\n   drainer.node.nfs                 := 10\n   drainer.fs.ntx                   := 10\n   drainperiod                      := 3600\n   graceperiod                      := 86400\n   ..\n\nBy default max 5 file systems per node can be drained in parallel with max 5\nparallel transfers per file system.\n\nThe values can be modified via:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> space config default space.drainer.node.nfs=20\n   EOS Console [root://localhost] |/> space config default space.drainer.fs.ntx=50\n\n\nExample Drain Process\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nWe need to drain filesystem 20. However the file system is still fully operational\nhence we use status drain.\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> fs config 20 configstatus=drain\n   EOS Console [root://localhost] |/> fs ls -d\n\n   #......................................................................................................................\n   #                   host (#...) #   id #           path #      drain #   progress #      files # bytes-left #  timeleft\n   #......................................................................................................................\n   lxfsra02a05.cern.ch (1095)     20          /data20      prepare            0         0.00       0.00 B          24\n\nAfter 60 seconds a drain filesystem changes into state draining if the drain\nmode was manually set. If a graceperiod is defined, it will stay in status\nwaiting for the length of the grace period.\n\nIn this example the defined drain period is 1 day:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> fs ls -d\n\n   #......................................................................................................................\n   #                   host (#...) #   id #           path #      drain #   progress #      files # bytes-left #  timeleft\n   #......................................................................................................................\n   lxfsra04a03.cern.ch (1095)    20           /data20     draining            5        75.00     37.29 GB       86269\n\n   When the drain has successfully completed, the output looks like this:\n\n   EOS Console [root://localhost] |/> fs ls -d\n\n   #......................................................................................................................\n   #                   host (#...) #   id #           path #      drain #   progress #      files # bytes-left #  timeleft\n   #......................................................................................................................\n   lxfsra02a05.cern.ch (1095)     20          /data20      drained            0         0.00       0.00 B           0\n\n\nIf the drain can not complete you will see this after the drain period has passed:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> fs ls -d\n\n   #......................................................................................................................\n   #                   host (#...) #   id #           path #      drain #   progress #      files # bytes-left #  timeleft\n   #......................................................................................................................\n   l\n   lxfsra04a03.cern.ch (1095)     20          /data20      expired           56        34.00     27.22 GB       86050\n\nYou can now investigate the origin by doing:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> fs status 20\n\n   ...\n\n   # ....................................................................................\n   # Risk Analysis\n   # ....................................................................................\n   number of files                  :=         34 (100.00%)\n   files healthy                    :=          0 (0.00%)\n   files at risk                    :=          0 (0.00%)\n   files inaccessbile               :=         34 (100.00%)\n   # ------------------------------------------------------------------------------------\n\nHere all remaining files are inaccessible because all replicas are down.\n\nIn case files are claimed to be accessible you have to look directory at the remaining files:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> fs dumpmd 20 -path\n   path=/eos/dev/2rep/sub12/lxplus403.cern.ch_10/0/0/7.root\n   path=/eos/dev/2rep/sub12/lxplus403.cern.ch_10/0/2/8.root\n   path=/eos/dev/2rep/sub12/lxplus406.cern.ch_4/0/1/0.root\n   path=/eos/dev/2rep/sub12/lxplus403.cern.ch_43/0/2/8.root\n   ...\n\nCheck these files using 'file check':\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> file check /eos/dev/2rep/sub12/lxplus403.cern.ch_10/0/0/7.root\n   path=\"/eos/dev/2rep/sub12/lxplus403.cern.ch_10/0/0/7.root\" fid=\"0002d989\" size=\"291241984\" nrep=\"2\" checksumtype=\"adler\" checksum=\"0473000100000000000000000000000000000000\"\n   nrep=\"00\" fsid=\"20\" host=\"lxfsra02a05.cern.ch:1095\" fstpath=\"/data08/00000012/0002d989\" size=\"291241984\" checksum=\"0473000100000000000000000000000000000000\"\n   nrep=\"01\" fsid=\"53\" host=\"lxfsra04a01.cern.ch:1095\" fstpath=\"/data09/00000012/0002d989\" size=\"291241984\" checksum=\"0000000000000000000000000000000000000000\"\n\nIn this case the second replica didn't commit a checksum and cannot be read.\n\nThis you might fix like this:\n\n.. code-block:: bash\n\n   EOS Console [root://localhost] |/> file verify /eos/dev/2rep/sub12/lxplus403.cern.ch_10/0/0/7.root -checksum -commitchecksum\n\n\nIf you just want to force the remove of files remaining on a non-drained filesystem,\nyou can drop all files on a particular filesystem using **eos fs dropfiles**.\nIf you use the '-f' flag all references to these files will be removed immediately\nand EOS won't try to delete any file anymore.\n\n.. code-block:: console\n\n   EOS Console [root://localhost] |/> fs dropfiles 170 -f\n   Do you really want to delete ALL 24 replica's from filesystem 170 ?\n   Confirm the deletion by typing => 1434841745\n   => 1434841745\n\n   Deletion confirmed\n\n.. index::\n   pair: Draining; Group Draining\n\nGroup Drainer\n^^^^^^^^^^^^^\n\nThe group drainer uses the converter mechanism to drain files from groups to target groups.\nFailed transfers are retried a configurable number of times before finally reaching either a\ndrained or drainfail status for a group. It uses an architecture similar to GroupBalancer with a\nspecial Drainer Engine which only looks for groups marked as *drain* as source groups. The target\ngroups are by default chosen as a threshold below the total group fillness average. Similar to\nconverter and groupbalancer this is enabled/disabled at a space level.\n\n.. index::\n   pair: Group Drainer; Configuration\n\n\nConfiguration\n\"\"\"\"\"\"\"\"\"\"\"\"\"\n\n.. code-block:: bash\n\n   # enable/disable\n   eos space config space.groupbalancer = <on/off>\n\n   # force a group to drain\n   eos group set <groupname> drain\n\n\n\n   # The list of various configuration flags supported in the eos cli\n   space config <space-name> space.groupdrainer=on|off                   : enable/disable the group drainer [ default=on ]\n   space config <space-name> space.groupdrainer.threshold=<threshold>    : configure the threshold(%) for picking target groups\n   space config <space-name> space.groupdrainer.group_refresh_interval   : configure time in seconds for refreshing cached groups info [default=300]\n   space config <space-name> space.groupdrainer.retry_interval           : configure time in seconds for retrying failed drains [default=4*3600]\n   space config <space-name> space.groupdrainer.retry_count              : configure the amount of retries for failed drains [default=5]\n   space config <space-name> space.groupdrainer.ntx                      : configure the max file transfer queue size [default=10000]\n\n\nThe `threshold` param by default is a percent threshold below the total computed average of all group fillness. If you want to ignore this and target\nevery available group, then threshold=0 will do that.\nThe `group refresh interval` determines how often we refresh the list of groups in the system, since this is not expected to change that often by\ndefault we only do it every 5 minutes (or when any groupdrainer config sees a change)\nThe `ntx` is the maximum amount of transfers we keep as active, it is okay to set this value higher than converter's ntx so that a healthy queue is maintained\nand the converter is kept busy. However if you want to reduce throughput, reducing the ntx will essentially throttle the files we schedule for transfers\nThe `retry_interval` and `retry_count` determine the amount of retries we do for a failed transfer. By default we try upto 5 times before giving up and\neventually marking the FS as drainfailed. This will need manual intervention similar to handling regular FS drains.\n\n.. index::\n   pair: Group Drainer; Info\n\nStatus\n\"\"\"\"\"\"\"\n\nCurrently a very minimal status command is implemented, which only informs about\nthe total transfers in queue and failed being tracked currently, in addition to\nthe count of groups in drain state and target groups. This is expected to change\nin the future with more information about the progress of the drain.\n\nThis command can be accessed via\n\n.. code-block:: bash\n\n   eos space groupdrainer status <spacename>\n\n\nRecommendations\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIt is recommended not to drain FS individually within the groups that are marked as in drain state\nas the groupdrainer may target the same files targeted by the regular drainer and similarly they\nmay compete on drain complete statuses.\n\nGroupBalancer only targets groups that are not in drain state, so in groups in drain state will not\nbe picked as either source or target groups by the GroupBalancer. However if no threshold is configured\nthen we might end up in scenarios where a file is being targeted by GroupDrainer to a group that is\nrelatively full eventually forcing the GroupBalancer to also balance. To avoid this it is recommended to\nset the threshold so that only groups below average are targeted by GroupDrainer.\n\n\nCompletion\n\"\"\"\"\"\"\"\"\"\"\"\n\nIn a groupdrain scenario:\nAn individual FS is marked as either drained/drainfailed\n- When all the files in the FS are converted ie. transferred to other groups (`drained`)\n- There are some files which even after `retry_count` attempts were failing transfer (`drainfailed`)\n\n\nA groupdrain is marked as complete when all the FSes in a group are in drained or drainfailed mode.\nIn this scenario the group status is set as `drained` or `drainfailed`, which should be visible in the\n`eos group ls` command.\n\n.. index::\n   pair: MGM; Inspector\n\n\nFile Inspector\n--------------\n\nThe File Inspector is a slow agent scanning all files in a namespace and collects statistics per layout type. Additionally it adds statistic about replication inconsistencies per layout. The target interval to scan all files is user defined. The default cycle is 4 hours, which can create a too high load in large namespaces and should be adjusted accordingly.\n\n.. index::\n   pair: Inspector; Configuration\n\nConfiguration\n^^^^^^^^^^^^^\n\nFile Inspector\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\nThe File Inspector has to be enabled/disabled in the default space only:\n\n.. code-block:: bash\n\n   # enable\n   eos space config default space.inspector=on\n   # disable\n   eos space config default space.inspector=off\n\nBy default Replication Tracking is disabled.\n\nThe current status of the Tracker can be seen via:\n\n.. code-block:: bash\n\n   eos space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   ...\n   inspector                        := off\n   ...\n\n\nInspector Interval\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe default inspector interval to scan all files is 4 hours. The interval can be set using:\n\n.. code-block:: bash\n\n   # set interval to 1d\n   eos space config default space.inspector.interval=86400\n\n\n.. index::\n   pair: Inspector; Info\n\n\nInspector Status\n^^^^^^^^^^^^^^^^\n\nYou can get the inspector status and an estimate for the run time using\n\n.. code-block:: bash\n\n   eos space inspector\n\n   # or\n\n   eos inspector\n\n   # ------------------------------------------------------------------------------------\n   # 2019-07-12T08:38:24Z\n   # 28 % done - estimate to finish: 2575 seconds\n   # ------------------------------------------------------------------------------------\n\nInspector Output\n^^^^^^^^^^^^^^^^\n\nYou can see the current statistics of the inspector run using\n\n.. code-block:: bash\n\n   eos inspector -c\n   eos inspector --current\n\n   # ------------------------------------------------------------------------------------\n   # 2019-07-12T08:39:55Z\n   # 28 % done - estimate to finish: 2574 seconds\n   # current scan: 2019-07-12T08:25:42Z\n    not-found-during-scan            : 0\n   ======================================================================================\n   layout=00000000 type=plain         checksum=none     blockchecksum=none     blocksize=4k\n\n   locations                        : 0\n   nolocation                       : 223004\n   repdelta:-1                      : 223004\n   unlinkedlocations                : 0\n   zerosize                         : 223004\n\n   ======================================================================================\n   layout=00100001 type=plain         checksum=none     blockchecksum=none     blocksize=4k\n\n   locations                        : 2\n   repdelta:0                       : 2\n   unlinkedlocations                : 0\n   volume                           : 3484\n\n   ...\n\n\nThe reports tags are:\n\n.. code-block:: bash\n\n   locations         : number of replicas (or stripes) in this layout categorie\n   nolocation        : number of files without any location attached\n   repdelta:-N       : number of files with -N replicas missing\n   repdelta:0        : number of files with correct replicat count\n   repdelate:+N      : number of files with +N replicas in excess\n   zerosize          : number of files with 0 size\n   volume            : logical bytes stored in this layout type\n   unlinkedlocations : number replicas still to be deleted\n   shadowdeletions   : number of files with a replica pointing to a not configured filesystem for deletion\n   shodowlocation    : number of files with a replica pointing to a not configured filesystem\n\n.. index::\n   pair: Inspector; Statistics\n   pair: Inspector; Access Time Distribution\n   pair: Inspector; Birth Time Distribution\n\nYou can get the statistics of the last completed run using\n\n.. code-block:: bash\n\n   eos inspector -l\n   eos inspector --last\n\nThis will additionally include birth and access time distributions:\n\n.. code-block:: bash\n\n    eos inspector -l\n    ...\n    ======================================================================================\n     Access time distribution of files\n     0s                               : 1613 (1.59%)\n     24h                              : 6 (0.01%)\n     7d                               : 1 (0.00%)\n     30d                              : 1 (0.00%)\n     2y                               : 5 (0.00%)\n     5y                               : 100.02 k (98.40%)\n    ======================================================================================\n     Access time volume distribution of files\n     0s                               : 81.31 MB (98.73%)\n     24h                              : 15.09 kB (0.02%)\n     7d                               : 0 B (0.00%)\n     30d                              : 1.00 MB (1.21%)\n     2y                               : 10.49 kB (0.01%)\n     5y                               : 24.27 kB (0.03%)\n    ======================================================================================\n     Birth time distribution of files\n     0s                               : 1619 (1.59%)\n     24h                              : 6 (0.01%)\n     7d                               : 100.00 k (98.39%)\n     90d                              : 1 (0.00%)\n     5y                               : 13 (0.01%)\n    ======================================================================================\n     Birth time volume distribution of files\n     0s                               : 81.32 MB (98.74%)\n     24h                              : 1.01 MB (1.23%)\n     7d                               : 25 B (0.00%)\n     90d                              : 2769 B (0.00%)\n     5y                               : 21.48 kB (0.03%)\n    --------------------------------------------------------------------------------------\n\nTo get access time distributions you have to have the access time tracking enabled in the space configuration:\ne.g. with 1h resolution: ``eos space config default atime=3600``\n\nYou can print the current and last run statistics in monitoring format:\n\n.. code-block:: bash\n\n   eos inspector -c -m\n   ...\n\n   eos inspector -l -m\n\n   key=last layout=00100002 type=plain checksum=adler32 blockchecksum=none blocksize=4k locations=638871 repdelta:+1=1 repdelta:0=638869 unlinkedlocations=0 volume=10802198338 zerosize=550002\n   key=last layout=00100012 type=replica checksum=adler32 blockchecksum=none blocksize=4k locations=42 repdelta:0=42 unlinkedlocations=0 volume=21008942\n   key=last layout=00100014 type=replica checksum=md5 blockchecksum=none blocksize=4k locations=1 repdelta:0=1 unlinkedlocations=0 volume=1701\n   key=last layout=00100015 type=replica checksum=sha1 blockchecksum=none blocksize=4k locations=1 repdelta:0=1 unlinkedlocations=0 volume=1701\n   key=last layout=00100112 type=replica checksum=adler32 blockchecksum=none blocksize=4k locations=44 repdelta:0=22 unlinkedlocations=0 volume=10506283\n   key=last layout=00640112 type=replica checksum=adler32 blockchecksum=none blocksize=1M locations=2 repdelta:0=1 unlinkedlocations=0 volume=1783\n   key=last layout=20640342 type=raid6 checksum=adler32 blockchecksum=crc32c blocksize=1M locations=0 nolocation=6 repdelta:-4=6 unlinkedlocations=0 zerosize=6\n   key=last layout=3b9ac9ff type=none checksum=none blockchecksum=none blocksize=illegal unfound=0\n   kay=last tag=accesstime::files 0=1613 86400=6 604800=1 2592000=1 63072000=5 157680000=100015\n   key=last tag=accesstime::volume 0=81309191 86400=15090 604800=0 2592000=1000000 63072000=10495 157680000=24274\n   kay=last tag=birthtime::files 0=1619 86400=6 604800=100002 7776000=1 157680000=13\n\nThe list of file ids with an inconsistency can be extracted using:\n\n.. code-block:: bash\n\n   # print the list of file ids\n   eos inspector -c -p #current run\n\n   fxid:00140237 repdelta:-1\n   fxid:001410ff repdelta:-1\n   fxid:00141807 repdelta:-1\n   fxid:0013da42 repdelta:-4\n   fxid:0013da43 repdelta:-4\n   fxid:0013da44 repdelta:-4\n   fxid:0013da45 repdelta:-4\n   fxid:0013da57 repdelta:-4\n   fxid:0013da68 repdelta:-4\n   ...\n\n\n   eos inspector -l -p #last run\n   ...\n\n   # export the list of file ids on the mgm\n   eos inspector -c -e #current run\n   # ------------------------------------------------------------------------------------\n   # 2019-07-12T08:53:14Z\n   # 100 % done - estimate to finish: 0 seconds\n   # file list exported on MGM to '/var/log/eos/mgm/FileInspector.1562921594.list'\n   # ------------------------------------------------------------------------------------\n\n   eos inspector -l -e #last run\n   # ------------------------------------------------------------------------------------\n   # 2019-07-12T08:53:33Z\n   # 100 % done - estimate to finish: 0 seconds\n   # file list exported on MGM to '/var/log/eos/mgm/FileInspector.1562921613.list'\n   # -----------------------------------------------------------------------\n\n\nLog Files\n^^^^^^^^^\nThe File Inspector has a dedicated log file under ``/var/log/eos/mgm/FileInspector.log``\nwhich shows the scan activity and potential errors. To get more\nverbose information you can change the log level:\n\n.. code-block:: bash\n\n   # switch to debug log level on the MGM\n   eos debug debug\n\n   # switch back to info log level on the MGM\n   eos debug info\n\n.. index::\n   pair: MGM; LRU\n\nLRU Engine\n----------\n\nThe LRU system serves to apply various conversion or deletion policies. It scans in a defined interval the full directory hierarchy and applies\nthe following LRU policies:\n\n.. epigraph::\n\n   ===================================================================================== =====================\n   Policy                                                                                Basis\n   ===================================================================================== =====================\n   Volume based LRU cache with low and high watermark                                    volume/threshold/time\n   Automatic time based cleanup of empty directories                                     ctime\n   Time based LRU cache with expiration time settings                                    ctime\n   Automatic time based layout conversion if a file reaches a defined age                ctime\n   Automatic size based layout conversion if a file fulfills a given size rule          size\n   Automatic time based layout conversion if a file has not been used for specified time mtime\n   ===================================================================================== =====================\n\n.. index::\n   pair: LRU; Configuration\n   pair: LRU; Engine\n\nConfiguration\n^^^^^^^^^^^^^\n\nEngine\n\"\"\"\"\"\"\"\nThe LRU engine has to be enabled/disabled in the default space only:\n\n.. code-block:: bash\n\n   # enable\n   eos space config default space.lru=on\n   # disable\n   eos space config default space.lru=off\n\nThe current status of the LRU can be seen via:\n\n.. code-block:: bash\n\n   eos -b space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   ...\n   lru                            := off\n   lru.interval                   := 0\n   ...\n\nThe interval in which the LRU engine is running is defined by the **lru.interval**\nspace variable:\n\n.. code-block:: bash\n\n   # run the LRU scan once a week\n   eos space config default space.lru.interval=604800\n\n.. index::\n   pair: LRU; Policy\n\nPolicy\n~~~~~~\n\nVolume based LRU cache with low and high watermark\n``````````````````````````````````````````````````\nTo configure an LRU cache with low and high watermark it is necessary to define\na quota node on the cache directory, set the high and low watermarks and to enable\nthe **atime** feature updating the creation times of files with the current\naccess time.\n\nWhen the cache reaches the high watermark it cleans the oldest files untile low-watermark is reached:\n\n.. code-block:: bash\n\n   # define project quota on the cache directory\n   eos quota set -g 99 -v 1T /eos/instance/cache/\n\n   # define 90 as low and 95 as high watermark\n   eos attr set sys.lru.lowwatermark=90 /eos/instance/cache/\n   eos attr set sys.lru.highwatermark=95 /eos/instance/cache/\n\n   # track atime with a time resolution of 5 minutes (space configuration parameter)\n   eos space config default space.atime=300\n\n.. index::\n   pair: LRU; Clean Empty Directories\n\n\nAutomatic time based cleanup of empty directories\n`````````````````````````````````````````````````\nConfigure automatic clean-up of empty directories which have a minimal age.\nThe LRU scan deletes directories with the largest deepness first to be able\nto remove complete empty subtrees in the namespace.\n\n.. code-block:: bash\n\n   # remove automatically empty directories if they are older than 1 hour\n   eos attr set sys.lru.expire.empty=\"1h\" /eos/dev/instance/empty/\n\n\nTime based LRU cache with expiration time settings\n``````````````````````````````````````````````````\nThis policy allows to match files by name with a defined age to be deleted. We\nuse the following convention when specifying the age interval for the various\n\"match\" options:\n\n +---------------+---------------+\n | Symbol        | Meaning       |\n +===============+===============+\n | **s/S**       | seconds       |\n +---------------+---------------+\n | **min/MIN**   | minutes       |\n +---------------+---------------+\n | **h/H**       | hours         |\n +---------------+---------------+\n | **d/D**       | days          |\n +---------------+---------------+\n | **w/W**       | weeks         |\n +---------------+---------------+\n | **mo/MO**     | months        |\n +---------------+---------------+\n | **y/Y**       | years         |\n +---------------+---------------+\n\nAll the size related symbols refer to the International System of Units, therefore\n1K is 1000 bytes.\n\n.. code-block:: bash\n\n   # files with suffix *.root get removed after a month or after 6 days if their size is bigger than 1GB, files with *.tgz after one week\n   eos attr set sys.lru.expire.match=\"*.root:1mo,*.tgz:1w,*.root:6d:>1G\"  /eos/dev/instance/scratch/\n\n   # all files older than a day are automatically removed\n   eos attr set sys.lru.expire.match=\"*:1d\" /eos/dev/instance/scratch/\n\nAutomatic time based layout conversion if a file reaches a defined age\n``````````````````````````````````````````````````````````````````````\nThis policy allows to convert a file from the current layout into a defined layout.\nA *placement policy* can also be specified.\n\n.. code-block:: bash\n\n   # convert all files older than a month to the layout defined next\n   eos attr set sys.lru.convert.match=\"*:1mo\" /eos/dev/instance/convert/\n\n   # define the conversion layout (hex) for the match rule '*' - this is RAID6 4+2\n   eos attr set sys.conversion.*=20640542 /eos/dev/instance/convert/\n\n   # same thing specifying a placement policy for the replicas/stripes\n   eos attr set sys.conversion.*=20640542|gathered:site1::rack2 /eos/dev/instance/convert/\n\nThe hex layout ID contains also the checksum and blocksize settings. The best is\nto create a file with the desired layout and get the hex layout ID using\n**eos file info <path>**.\n\nAutomatic size based restriction for time based conversion\n``````````````````````````````````````````````````````````\nThis policy addition allows to restrict the time based layout conversion to certain\nfile sizes.\n\n.. code-block:: bash\n\n   # convert all files smaller than 128m in size [ with units E/e,P/p,T/t,G/g,M/m,K/k ]\n   eos attr set sys.lru.convert.match=\"*:1w:<1M\"\n\n   # convert all files bigger than 1G in size\n   eos attr set sys.lru.convert.match=\"*:1w:>1G\"\n\n\nAutomatic time based layout conversion if a file has not been used for specified time\n``````````````````````````````````````````````````````````````````````````````````````\nThis policy allows to convert a file from the current layout to a different layout\nif the file was not accessed for a defined interval. To use this feature one has\nalso to enable the **atime** feature where the access time is stored as the new\nfile creation time. A *placement policy* can also be specified.\n\n.. code-block:: bash\n\n     # track atime with a time resolution of one week ( space configuration parameter )\n     eos space config default space.atime=604800\n\n     # convert all files older than a month to the layout defined next\n     eos attr set sys.lru.convert.match=\"*:6mo\" /eos/dev/instance/convert/\n\n     # define the conversion layout (hex) for the match rule '*' - this is RAID6 4+2\n     eos attr set sys.conversion.*=20640542 /eos/dev/instance/convert/\n\n     # same thing specifying a placement policy for the replicas/stripes\n     eos> attr set sys.conversion.*=20640542|gathered:site1::rack2 /eos/dev/instance/convert/\n\n.. index::\n   pair: File; Conversion\n\n\nManual File Conversion\n^^^^^^^^^^^^^^^^^^^^^^\nIt is possible to run an asynchronous file conversion using the **EOS CLI**.\n\n.. code-block:: bash\n\n   # convert the referenced file into a file with 3 replica\n   eos file convert /eos/dev/2rep/passwd replica:3\n   info: conversion based layout+stripe arguments\n   success: created conversion job '/eos/dev/proc/conversion/0000000000059b10:default#00650212'\n\n   # same thing mentioning target space and placement policy\n   eos file convert /eos/dev/2rep/passwd replica:3 default gathered:site1::rack1\n   info: conversion based layout+stripe arguments\n   success: created conversion job '/eos/dev/proc/conversion/0000000000059b10:default#00650212'~gathered:site1::rack1\n\n.. code-block:: bash\n\n   # convert the referenced file into a RAID6 file with 6 stripes\n   eos file convert /eos/dev/2rep/passwd raid6:6\n   info: conversion based layout+stripe arguments\n   success: created conversion job '/eos/dev/proc/conversion/0000000000064f61:default#20650542'\n\n   # check that the conversion was successful\n   eos fileinfo /eos/dev/2rep/passwd\n   File: '/eos/dev/2rep/passwd'  Size: 2458\n   Modify: Wed Oct 30 17:03:35 2013 Timestamp: 1383149015.384602000\n   Change: Wed Oct 30 17:03:36 2013 Timestamp: 1383149016.243563000\n     CUid: 0 CGid: 0  Fxid: 00064f63 Fid: 413539    Pid: 1864   Pxid: 00000748\n   XStype: adler    XS: 01 15 4b 52\n   raid6 Stripes: 6 Blocksize: 4M LayoutId: 20650542\n     #Rep: 6\n   <#> <fs-id> #.................................................................................................................\n               #               host  #    schedgroup #      path #    boot # configstatus #    drain # active #         geotag #\n               #.................................................................................................................\n     0     102     lxfsra04a03.cern.ch      default.11     /data12    booted             rw    nodrain   online   eos::cern::mgm\n     1     116     lxfsra02a05.cern.ch      default.11     /data12    booted             rw    nodrain   online   eos::cern::mgm\n     2      94     lxfsra04a02.cern.ch      default.11     /data12    booted             rw    nodrain   online   eos::cern::mgm\n     3      65     lxfsra02a07.cern.ch      default.11     /data12    booted             rw    nodrain   online   eos::cern::mgm\n     4     108     lxfsra02a08.cern.ch      default.11     /data12    booted             rw    nodrain   online   eos::cern::mgm\n     5      77     lxfsra04a01.cern.ch      default.11     /data13    booted             rw    nodrain   online   eos::cern::mgm\n   *******\n\n.. index::\n   pair: LRU; Log Files\n\nLog Files\n^^^^^^^^^\nThe LRU engine has a dedicated log file under ``/var/log/eos/mgm/LRU.log``\nwhich shows triggered actions based on scanned policies. To get more\nverbose information you can change the log level:\n\n.. code-block:: bash\n\n   # switch to debug log level on the MGM\n   eos debug debug\n\n   # switch back to info log level on the MGM\n   eos debug info\n\n\n.. index::\n   pair: MGM; FSCK\n   pair: MGM; Consistency\n\nFSCK\n-----\n\nFSCK (File System Consistency Check) is the service reporting and possibly repairing inconsistencies in an EOS instance.\n\nThis section describles how the internal file system consistency checks (FSCK) are configured and work.\n\n.. index::\n   pair: FSCK; FST Scan\n\n\nEnable FST Scan\n^^^^^^^^^^^^^^^\n\nTo enable the FST scan you have to set the variable **scaninterval** on the space and\non all file systems:\n\n.. code-block:: bash\n\n   # set it on the space to inherit a value for all new filesystems in this space every 14 days (time has to be in seconds)\n   space config default space.scaninterval=1209600\n\n   # set it on an existing filesystem (fsid 23) to 14 days (time has to be in seconds)\n   fs config 23 space.scaninterval=1209600\n\n   # set the scaninterval for all the existing file systems already registered in the given space\n   space config default fs.scaninterval=1209601\n\n.. note::\n\n   The *scaninterval* time has to be given in seconds!\n\n\nCaveats\n^^^^^^^\n\nFor FSCK engine to function correctly, FSTs must be able to connect to QuarkDB directly (and to the MGM).\n\n\nOverview\n^^^^^^^^\n\nHigh level summary\n^^^^^^^^^^^^^^^^^^\n\n#) error collection happens in the FST in defined intervals, no action/trigger by MGM is required for this\n\n#) the locally saved results will be collected by the fsck collection thread of fsck engine\n\n#) if the fsck repair thread is  enabled, the mgm will trigger repair actions (i.e. create / delete replica)\nas required (based on collected error data)\n\nIntervals and config parameters for file systems(FS)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThese values are set as global defaults on the space. A file system should get the values from the space when it is newly created.\nBelow you can find a brief description of the parameters influencing the scanning procedure.\n\n===================  ===============   ===========================================================\nName                 Default           Description\n===================  ===============   ===========================================================\nscan_disk_interval   14400 [s] (4h)    interval at which files in the FS should be scanned, by the FST itself\nscan_ns_interval     259200 [s] (3d)   interval at which files in the FS are compares against the\n                                       namespace information from QuarkDB\nscaninterval         604800 [s] (7d)   target interval at which all files should be scanned\nscan_rain_interval   2419200 [s] (4w)  target interval at which all rain files should be scanned\nscan_ns_rate         50 [Hz]           rate limit the requests to QuarkDB for the namespace scans\nscanrate             100 [MB/s]        rate limit bandwidth used by the scanner when reading files\n                                       from disk\n===================  ===============   ===========================================================\n\n**scan_disk_interval** and **scan_ns_interval** are skewed by a random factor per FS so that not all disks become busy at the same time.\n\nThe scan jobs are started with a lower IO priority class (using Linux ioprio_set) within EOS to decrease the impact on normal filesystem access, i.e. check logs for set io priority to 7 (lowest best-effort).\n\n.. code-block:: bash\n\n   210211 12:41:40 time=1613043700.017295 func=RunDiskScan              level=NOTE\n   logid=1af8cd9e-6c5e-11eb-ae37-3868dd2a6fb0 unit=fst@fst-9.eos.grid.vbc.ac.at:1095 tid=00007f98bebff700 source=ScanDir:446\n   tident=<service> sec=   uid=0 gid=0 name= geo=\"\" msg=\"set io priority to 7(lowest best-effort)\" pid=221712\n\n\nScan Duration\n^^^^^^^^^^^^^\n\nThe first scan of a larger (fuller) FS can take several hours. Following scans will be much faster, within minutes (10-30min).\nSubsequent scans will only look at file that have not been scanned since scaninterval . i.e. each scan iteration will only look at a fraction of the files on disk, compare the logs for such a scan. (see the last line “scannedfiles” vs “skippedfiles” and the scanduration of 293s.)\n\n.. code-block:: bash\n\n   210211 12:49:44 time=1613044184.957472 func=RunDiskScan              level=NOTE  logid=1827f5ea-6c5e-11eb-ae37-3868dd2a6fb0    unit=fst@fst-9.eos.grid.vbc.ac.at:1095 tid=00007f993afff700 source=ScanDir:504                    tident=<service> sec=      uid=0 gid=0 name= geo=\"\" [ScanDir] Directory: /srv/data/data.01 files=147957 scanduration=293 [s] scansize=23732973568 [Bytes] [ 23733 MB ] scannedfiles=391 corruptedfiles=0 hwcorrupted=0 skippedfiles=147557\n\n.. index::\n   pair: FSCK; Error Types\n\nError Types detected by FSCK\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n(in decreasing priority)\n\n=============  ====================================================  ================================================================================================================\nError          Description                                           Fixed by\n=============  ====================================================  ================================================================================================================\nstripe_err     stripe is unable to reconstruct original file         FsckRepairJob\nd_mem_sz_diff  disk and reference size mismatch                      FsckRepairJob\nm_mem_sz_diff  MGM and reference size mismatch                       inspecting all the replicas or saved for manual inspection\nd_cx_diff      disk and reference checksum mismatch                  FsckRepairJob\nm_cx_diff      MGM and reference checksum mismatch                   inspecting all the replicas or saved for manual inspection\nunreg_n        unregistered file / replica                           (i.e. file on FS that has no entry in MGM) register replica if metadata match or drop if not needed\nrep_missing_n  missing replica for a file                            replica is registered on mgm but not on disk - FsckRepairJob\nrep_diff_n     replica count is not nominal (too high or too low)    fixed by dropping replicas or creating new ones through FsckRepairJob\norphans_n      orphan files (no record for replica/file in mgm)      no action at the MGM, files not referenced by MGM at all, moved to to .eosorphans directory on FS mountpoint\n=============  ====================================================  ================================================================================================================\n\n.. index::\n   pair: FSCK; Configuration\n\nConfiguration\n^^^^^^^^^^^^^\n\nSpace\n\"\"\"\"\"\n\nSome config items on the space are global, some are defaults (i.e. for newly created filesystems), see https://eos-docs.web.cern.ch/configuration/autorepair.html\n\nTo enable the FST scan you have to set the variable **scaninterval** on the space and on all file systems.\n\nThe intervals other than `scaninterval` are defaults for newly created filesystems. For an explanation. of the intervals see above.\n\n\n.. code-block:: bash\n\n   [root@mgm-1 ~]# eos space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   autorepair                       := on\n   [...]\n   scan_disk_interval               := 14400\n   scan_ns_interval                 := 259200\n   scan_ns_rate                     := 50\n   scaninterval                     := 604800\n   scan_rain_interval               := 2419200\n   scanrate                         := 100\n   [...]\n\n\n\nFilesystem(FS)\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nTo enable the FST scan you have to set the variable `scaninterval` on the space and on all file systems\n\n.. code-block:: bash\n\n   [root@mgm-1 ~]# eos fs status 1\n   # ------------------------------------------------------------------------------------\n   # FileSystem Variables\n   # ------------------------------------------------------------------------------------\n   bootcheck                        := 0\n   bootsenttime                     := 1612456466\n   configstatus                     := rw\n   host                             := fst-1.eos.grid.vbc.ac.at\n   hostport                         := fst-1.eos.grid.vbc.ac.at:1095\n   id                               := 1\n   local.drain                      := nodrain\n   path                             := /srv/data/data.00\n   port                             := 1095\n   queue                            := /eos/fst-1.eos.grid.vbc.ac.at:1095/fst\n   queuepath                        := /eos/fst-1.eos.grid.vbc.ac.at:1095/fst/srv/data/data.00\n\n   [...] defaults for these are taken from MGM, scanterval must be set!\n   scan_disk_interval               := 14400\n   scan_ns_interval                 := 259200\n   scan_ns_rate                     := 50\n   scaninterval                     := 604800\n   scan_rain_interval               := 2419200\n   scanrate                         := 100\n\n   [...] various stat values reported back by the FST\n   stat.fsck.blockxs_err            := 1\n   stat.fsck.d_cx_diff              := 0\n   stat.fsck.d_mem_sz_diff          := 0\n   stat.fsck.d_sync_n               := 148520\n   stat.fsck.m_cx_diff              := 0\n   stat.fsck.m_mem_sz_diff          := 0\n   stat.fsck.m_sync_n               := 148025\n   stat.fsck.mem_n                  := 148526\n   stat.fsck.orphans_n              := 497\n   stat.fsck.rep_diff_n             := 5006\n   stat.fsck.rep_missing_n          := 0\n   stat.fsck.unreg_n                := 5003\n   [...]\n\n\nFSCK Settings\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nWith the settings above, stats are collected on the FST (and reported in fs status) but no further action is taken. To setup of the fsck mechanism, see the eos fsck subcommands:\n\n`fsck stat`\n\"\"\"\"\"\"\"\"\"\"\"\n\nGives a quick status of error stats collection and if the repair thread is active. The `eos fsck toggle-repair` and `toggle-collect` are really toggles. Use **eos fsck stat** to verify the correctness of your settings!\n\n.. code-block:: bash\n\n   [root@mgm-1 ~]# eos fsck stat\n   Info: collection thread status -> enabled\n   Info: repair thread status     -> enabled\n   210211 15:54:09 1613055249.712603 Start error collection\n   210211 15:54:09 1613055249.712635 Filesystems to check: 252\n   210211 15:54:10 1613055250.769177 blockxs_err                    : 118\n   210211 15:54:10 1613055250.769208 orphans_n                      : 92906\n   210211 15:54:10 1613055250.769221 rep_diff_n                     : 1226274\n   210211 15:54:10 1613055250.769224 rep_missing_n                  : 6\n   210211 15:54:10 1613055250.769231 unreg_n                        : 1221521\n   210211 15:54:10 1613055250.769235 Finished error collection\n   210211 15:54:10 1613055250.769237 Next run in 30 minutes\n\nThe collection thread will interrogate the FSTs for locally collected error stats at configured intervals (default: 30 minutes).\n\n`fsck report`\n\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nFor a more comprehensive error report, use **eos fsck report** this will only contain data once the error collection has started (also note the switch -a to show errors per filesystem FS)\n\n.. code-block:: bash\n\n   [root@mgm-1 ~]# eos fsck report\n   timestamp=1613055250 tag=\"blockxs_err\" count=43\n   timestamp=1613055250 tag=\"orphans_n\" count=29399\n   timestamp=1613055250 tag=\"rep_diff_n\" count=181913\n   timestamp=1613055250 tag=\"rep_missing_n\" count=4\n   timestamp=1613055250 tag=\"unreg_n\" count=180971\n\n\n.. index::\n   pair: FSCK; Repair\n\nRepair\n^^^^^^\n\nMost of the repair operations are implemented using the DrainTransferJob functionality.\n\nOperations\n^^^^^^^^^^\n\nInspect FST local Error Statistics\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nUse **eos-leveldb-inspect** command to inspect the contents of the local database on the FSTs.\nThe local database contains all information (fxid, error type, etc) that will be collected\nby the mgm (compare the eos fs status <fsid> output).\n\n.. code-block:: bash\n\n   [root@fst-9 ~]# eos-leveldb-inspect  --dbpath /var/eos/md/fmd.0225.LevelDB --fsck\n   Num. entries in DB[mem_n]:                     148152\n   Num. files synced from disk[d_sync_n]:         148150\n   Num, files synced from MGM[m_sync_n]:          147723\n   Disk/reference size mismatch[d_mem_sz_diff]:   0\n   MGM/reference size mismatch[m_mem_sz_diff]:   140065\n   Disk/reference checksum mismatch[d_cx_diff]:  0\n   MGM/reference checksum mismatch[m_cx_diff]:   0\n   Num. of orphans[orphans_n]:                    427\n   Num. of unregistered replicas[unreg_n]:        5078\n   Files with num. replica mismatch[rep_diff_n]: 5081\n   Files missing on disk[rep_missing_n]:          0\n\nCheck fsck repair activity\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nSee if the fsck repair thread is active and how log its work queue is (cross check with log activity on mgm):\n\n.. code-block:: bash\n\n   [root@mgm-1 ~]# eos ns | grep fsck\n   ALL      fsck info                        thread_pool=fsck min=2 max=20 size=20 queue_size=562\n   ALL      tracker info                     tracker=fsck size=582\n   compare namespace stats for total count of fsck operations:\n\n\n   [root@mgm-1 ~]# eos ns stat | grep -i fsck\n   ALL      fsck info                        thread_pool=fsck min=2 max=20 size=20 queue_size=168\n   ALL      tracker info                     tracker=fsck size=188\n   all FsckRepairFailed              71.58 K     0.00     0.03     1.35     0.87     -NA-      -NA-\n   all FsckRepairStarted             63.19 M   857.75  1107.25  1112.05   918.32     -NA-      -NA-\n   all FsckRepairSuccessful          63.12 M   857.75  1106.88  1110.64   917.44     -NA-      -NA-\n\nLog examples\n\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nStartup of FST service and initializing fsck threads:\n\n\n.. code-block:: bash\n\n    210211 12:41:39 time=1613043699.997897 func=ConfigScanner level=INFO  logid=1af5b7a8-6c5e-11eb-ae37-3868dd2a6fb0\n    unit=fst@fst-9.eos.grid.vbc.ac.at:1095 tid=00007f99497ff700 source=FileSystem:159 tident=<service> sec= uid=0 gid=0\n    name= geo=\"\" msg=\"started ScanDir thread with default parameters\" fsid=238\n\n   # NS scanner thread with random skew\n   210211 12:41:50 time=1613043710.000322 func=RunNsScan  level=INFO  logid=1af62382-6c5e-11eb-ae37-3868dd2a6fb0\n   unit=fst@fst-9.eos.grid.vbc.ac.at:1095 tid=00007f98e6bfe700 source=ScanDir:224 tident=<service> sec= uid=0 gid=0\n   name= geo=\"\" msg=\"delay ns scan thread by 38889 seconds\" fsid=239 dirpath=\"/srv/data/data.14\"\n\n\nsystemd ScanDir results\n~~~~~~~~~~~~~~~~~~~~~~~\n\nThese logs are also written to /var/log/eos/fst/xrdlog.fst\n\n.. code-block:: bash\n\n   Feb 11 12:41:33 fst-9.eos.grid.vbc.ac.at eos_start.sh[220738]: Using xrootd binary: /opt/eos/xrootd/bin/xrootd\n   Feb 11 12:49:44 fst-9.eos.grid.vbc.ac.at scandir[220738]: skipping scan w-open file: localpath=/srv/data/data.01/000006e3/010d045d fsid=226 fxid=010d045d\n   Feb 11 12:49:44 fst-9.eos.grid.vbc.ac.at scandir[220738]: [ScanDir] Directory: /srv/data/data.01 files=147957 scanduration=293 [s] scansize=23732973568 [Bytes] [ 23733 MB ] scanned...iles=147557\n   Feb 11 13:07:55 fst-9.eos.grid.vbc.ac.at scandir[220738]: [ScanDir] Directory: /srv/data/data.18 files=148074 scanduration=263 [s] scansize=17977114624 [Bytes] [ 17977.1 MB ] scann...iles=147730\n   Feb 11 13:08:36 fst-9.eos.grid.vbc.ac.at scandir[220738]: [ScanDir] Directory: /srv/data/data.22 files=147905 scanduration=258 [s] scansize=19978055680 [Bytes] [ 19978.1 MB ] scann...iles=147498\n   Feb 11 13:14:56 fst-9.eos.grid.vbc.ac.at scandir[220738]: [ScanDir] Directory: /srv/data/data.27 files=147445 scanduration=249 [s] scansize=15998377984 [Bytes] [ 15998.4 MB ] scann...iles=147119\n   fsck repairs. success/failure on MGM\n\n   210211 13:58:17 time=1613048297.294157 func=RepairReplicaInconsistencies level=INFO  logid=cf14c90e-6c68-11eb-becb-3868dd28d0c0 unit=mgm@mgm-1.eos.grid.vbc.ac.at:1094 tid=00007efd53bff700 source=FsckEntry:689                  tident=<service> sec=      uid=0 gid=0 name= geo=\"\" msg=\"file replicas consistent\" fxid=0028819b\n   210211 13:58:17 time=1613048297.294294 func=RepairReplicaInconsistencies level=INFO  logid=cf14c54e-6c68-11eb-becb-3868dd28d0c0 unit=mgm@mgm-1.eos.grid.vbc.ac.at:1094 tid=00007efd51bfb700 source=FsckEntry:689                  tident=<service> sec=      uid=0 gid=0 name= geo=\"\" msg=\"file replicas consistent\" fxid=00ef5955\n   210211 13:59:18 time=1613048358.345753 func=RepairReplicaInconsistencies level=ERROR logid=cf14c7ce-6c68-11eb-becb-3868dd28d0c0 unit=mgm@mgm-1.eos.grid.vbc.ac.at:1094 tid=00007efd523fc700 source=FsckEntry:663                  tident=<service> sec=      uid=0 gid=0 name= geo=\"\" msg=\"replica inconsistency repair failed\" fxid=0079b4d0 src_fsid=244\n\n\nNo repair action, file is being deleted\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe file has an FsckEntry i.e. is marked from repair, and was previously listed on the collected errors, but\n\n.. code-block:: bash\n\n   210211 16:27:45 time=1613057265.418302 func=Repair                   level=INFO  logid=b077de7c-6c7d-11eb-becb-3868dd28d0c0 unit=mgm@mgm-1.eos.grid.vbc.ac.at:1094 tid=00007efd95bff700 source=FsckEntry:773                  tident=<service> sec=      uid=0 gid=0 name= geo=\"\"\n   msg=\"no repair action, file is being deleted\" fxid=00033673\n   The file is noted as “being deleted” as its container (directory) does not exist anymore, i.e.\n\n\n   [root@mgm-1 ~]# eos fileinfo fxid:00033673\n   File: 'fxid:00033673'  Flags: 0600  Clock: 1662bb7c74f01d9f\n   Size: 0\n   Modify: Fri Jul 24 11:32:15 2020 Timestamp: 1595583135.037235673\n   Change: Fri Jul 24 11:32:15 2020 Timestamp: 1595583135.037235673\n   Birth: Fri Jul 24 11:32:15 2020 Timestamp: 1595583135.037235673\n   CUid: 12111 CGid: 11788 Fxid: 00033673 Fid: 210547 Pid: 0 Pxid: 00000000\n   XStype: adler    XS: 00 00 00 00    ETAGs: \"56518279954432:00000000\"\n   Layout: raid6 Stripes: 7 Blocksize: 1M LayoutId: 20640642 Redundancy: d0::t0\n   #Rep: 0\n   *******\n   error: cannot retrieve file meta data - Container #0 not found (errc=0) (Success)\n\n\nDiscrepancy reported errors\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n... between fsck report summary / per filesystem and fsck stat.\nEOS fsck report is giving different numbers for total report and per filesystem summary. This is expected.\n\nPer filesystem reports may contain error counts for individual replicas of a single file stored in EOS.\n**eos fsck stat** will reflect the per replica count, **eos fsck report** will show lower numbers,\nnot counting per each replica of a file.\n\n**example script**\n\n.. code-block:: bash\n\n   echo \"summed up by filesystem\"\n   ERR_TYPES=\"blockxs_err orphans_n rep_diff_n rep_missing_n unreg_n\"\n   for ETYPE in $ERR_TYPES; do\n   echo -n \"$ETYPE: \"\n   eos fsck report -a | grep $ETYPE  | awk '{print $4;}' | awk 'BEGIN{ FS=\"=\"; total=0}; { total=total+$2; } END{print total;}'\n   done\n\n   echo \"\"\n\n   echo \"eos fsck summary report\"\n   eos fsck report\n\n**output example**\n\n.. code-block:: bash\n\n   [root@mgm-1 ~]# ./eos_fsck_miscount.sh\n   summed up by filesystem\n   blockxs_err: 115\n   orphans_n: 95056\n   rep_diff_n: 1251566\n   rep_missing_n: 30\n   unreg_n: 1246475\n\n   eos fsck summary report\n   timestamp=1613069473 tag=\"blockxs_err\" count=43\n   timestamp=1613069473 tag=\"orphans_n\" count=29602\n   timestamp=1613069473 tag=\"rep_diff_n\" count=181913\n   timestamp=1613069473 tag=\"rep_missing_n\" count=28\n   timestamp=1613069473 tag=\"unreg_n\" count=180998\n\n.. index::\n   pair: Tracker; Replication Tracker\n\n\nReplication Tracker\n-------------------\n\nThe Replication Tracker follows the workflow of file creations. For each created file a virtual entry is created in the ``proc/tracker`` directory. Entries are removed once a layout is completely committed. The purpose of this tracker is to find inconsistent files after creation and to remove atomic upload relicts automatically after two days.\n\n\n.. warning:: Please note that using the tracker will increase the meta-data operation load on the MGM!\n\n.. index::\n   pair: Tracker; Configuration\n\nConfiguration\n^^^^^^^^^^^^^\n\nTracker\n\"\"\"\"\"\"\"\nThe Replication Tracker has to be enabled/disabled in the default space only:\n\n.. code-block:: bash\n\n   # enable\n   eos space config default space.tracker=on\n   # disable\n   eos space config default space.tracker=off\n\nBy default Replication Tracking is disabled.\n\nThe current status of the Tracker can be seen via:\n\n.. code-block:: bash\n\n   eos space status default\n   # ------------------------------------------------------------------------------------\n   # Space Variables\n   # ....................................................................................\n   ...\n   tracker                        := off\n   ...\n\n\nAutomatic Cleanup\n^^^^^^^^^^^^^^^^^\n\nWhen the tracker is enabled, an automatic thread inspects tracking entries and takes care of cleanup of tracking entries and the time based tracking directory hierarchy. Atomic upload files are automatically cleaned after 48 hours when the tracker is enabled.\n\n.. index::\n   pair: Tracker; Info\n\nListing Tracking Information\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nYou can get the current listing of tracked files using:\n\n.. code-block:: bash\n\n   eos space tracker\n\n   # ------------------------------------------------------------------------------------\n   key=00142888 age=4 (s) delete=0 rep=0/1 atomic=1 reason=REPLOW uri='/eos/test/creations/.sys.a#.f.1.802e6b70-973e-11e9-a687-fa163eb6b6cf'\n   # ------------------------------------------------------------------------------------\n\n\n\nThe displayed reasons are:\n\n* REPLOW - the replica number is too low\n* ATOMIC - the file is an atomic upload\n* KEEPIT - the file is still in flight\n* ENOENT - the tracking entry has no corresponding namespace entry with the given file-id\n* REP_OK - the tracking entry is healthy and can be removed - FUSE files appear here when not replica has been committed yet\n\nThere is convenience command defined in the console:\n\n.. code-block:: bash\n\n   eos tracker # instead of eos space tracker\n\n\n.. index::\n   pair: Tracker; Log Files\n\nLog Files\n^^^^^^^^^\nThe Replication Tracker has a dedicated log file under ``/var/log/eos/mgm/ReplicationTracker.log``\nwhich shows the tracking entries and related cleanup activities. To get more\nverbose information you can change the log level:\n\n.. code-block:: bash\n\n   # switch to debug log level on the MGM\n   eos debug debug\n\n   # switch back to info log level on the MGM\n   eos debug info\n\n.. index::\n   pair: MGM; Audit Logging\n\nAudit Logging\n-------------\n\n.. index::\n   pair: Audit Logging; Overview\n\nOverview\n^^^^^^^^\n\nEOS implements structured audit logging for successful operations that modify the namespace or file metadata. Audit entries are encoded as JSON (one record per line), written directly into ZSTD-compressed log segments, and rotated every 1 hour by default. A symlink ``audit.zstd`` always points to the current active segment.\n\nThis audit logging provides comprehensive tracking of all namespace-affecting operations performed by identified users, enabling security monitoring, compliance reporting, and operational analysis.\n\nFor full documentation, see ``AUDIT.md`` in the EOS source tree.\n\n.. index::\n   pair: Audit Logging; Configuration\n\nConfiguration and Enabling\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAudit logging is controlled by environment variables read by the MGM at startup. The main variable is ``EOS_MGM_AUDIT``:\n\n**``EOS_MGM_AUDIT``** — overall audit level:\n\n- ``off``, ``none``, ``false``, ``no``, or empty: disable all auditing (audit logger not created)\n- ``default``: audit modifications (CREATE, DELETE, RENAME, etc.) and READ for document-style files (txt, pdf, doc, etc.); LIST off\n- ``modifications``: audit only modifications; no READ, no LIST\n- ``detail``: audit modifications and READ for all files; LIST off\n- ``all``: audit everything including LIST and READ for all files\n- ``attribute``: create the audit logger but disable global auditing; enable per directory via ``sys.audit`` extended attribute\n\n**``EOS_MGM_AUDIT_READ_SUFFIX``** — override the READ suffix filter (comma-separated, case-insensitive):\n\n- Example: ``pdf,docx,json``\n- Use ``*`` to audit READ for all files\n- If unset, the built-in document-style list is used\n\n**``EOS_AUDIT_ROTATION``** — segment rotation interval in seconds (default: 3600):\n\n- Example: ``EOS_AUDIT_ROTATION=300`` for 5-minute rotation\n\n**Example: enable audit with default settings**\n\n.. code-block:: bash\n\n   export EOS_MGM_AUDIT=default\n   # MGM will audit modifications and READ for document files\n\n**Example: disable audit**\n\n.. code-block:: bash\n\n   export EOS_MGM_AUDIT=off\n\n**Per-directory auditing (``sys.audit``)**\n\nWhen ``EOS_MGM_AUDIT=attribute``, global auditing is disabled and auditing is enabled per directory via the extended attribute ``sys.audit`` on the parent directory (for files) or the directory itself (for LIST). Valid values (case-insensitive):\n\n- ``none``, ``no``, ``false``, ``off``: disable auditing for that directory\n- ``modifications``: modifications only\n- ``default``: modifications and READ (default document suffixes)\n- ``detail``: modifications and READ for all files\n- ``all``: everything including LIST and READ for all files\n\n.. index::\n   pair: Audit Logging; Scope\n\nScope: What gets logged\n^^^^^^^^^^^^^^^^^^^^^^^\n\n**Successful namespace-affecting operations by identified users:**\n\n- **Files**: CREATE, DELETE, RENAME/MOVE, TRUNCATE, WRITE (commit), UPDATE (open for write without create/truncate)\n- **Directories**: MKDIR, RMDIR, RENAME/MOVE\n- **Symlinks**: SYMLINK creation, DELETE\n- **Metadata**: CHMOD, CHOWN, SET_XATTR, RM_XATTR, SET_ACL\n\n**Excluded:** Failed attempts, internal non-human activities (e.g. purge/version housekeeping), and by default READ/LIST operations (can be enabled if needed despite volume).\n\n.. index::\n   pair: Audit Logging; Record Format\n\nRecord Format\n^^^^^^^^^^^^^\n\nEach audit line is a JSON serialization of the ``eos.audit.AuditRecord`` protobuf. Key elements:\n\n**Common fields**\n\n- ``timestamp`` (int64): seconds since epoch (server time)\n- ``path`` (string): absolute path to object; directory paths end with '/'\n- ``operation`` (enum): CREATE, DELETE, RENAME, WRITE, TRUNCATE, SET_XATTR, RM_XATTR, SET_ACL, CHMOD, CHOWN, MKDIR, RMDIR, SYMLINK, UPDATE\n- ``client_ip``, ``account``\n- ``auth`` (mechanism string + attributes map)\n- ``authorization`` (reasons[])\n- ``trace_id`` (string): server trace id\n- ``target`` (string): for rename/symlink target path\n- ``uuid`` (string): client/session id\n- ``tid`` (string): client trace identifier\n- ``app`` (string): client application\n- ``svc`` (string): emitting service (e.g. \"mgm\")\n\n**State snapshots**\n\n- ``before`` / ``after`` (Stat): include ``ctime``, ``mtime``, ``uid``, ``gid``, ``mode``, ``mode_octal``, ``size``, ``checksum`` (files)\n- ``attrs`` (repeated AttrChange): ``{ name, before, after }`` for xattr changes\n\n**Nanosecond resolution times**\n\n- ``Stat.ctime_ns`` and ``Stat.mtime_ns`` provide full-resolution strings (e.g. ``1730985600.123456789``)\n\n**Source and version metadata**\n\n- ``software`` (string): source location and version in format ``filename:line@version``\n\nExample JSON record:\n\n.. code-block:: json\n\n   {\n     \"timestamp\": 1730985600,\n     \"path\": \"/eos/user/a/alice/data/file.txt\",\n     \"operation\": \"WRITE\",\n     \"client_ip\": \"192.0.2.10\",\n     \"account\": \"alice\",\n     \"auth\": { \"mechanism\": \"krb5\", \"attributes\": {\"principal\": \"alice@EXAMPLE.ORG\"} },\n     \"authorization\": { \"reasons\": [\"uid-match\"] },\n     \"trace_id\": \"srv-abc123\",\n     \"uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n     \"tid\": \"cli-xyz789\",\n     \"app\": \"eoscp\",\n     \"svc\": \"mgm\",\n     \"before\": { \"ctime\": 1730980000, \"mtime\": 1730981000, \"uid\": 1000, \"gid\": 1000, \"mode\": 420, \"mode_octal\": \"0100644\", \"size\": 1024, \"checksum\": \"a1b2...\" },\n     \"after\":  { \"ctime\": 1730980000, \"mtime\": 1730985600, \"ctime_ns\": \"1730980000.000000000\", \"mtime_ns\": \"1730985600.123456789\", \"uid\": 1000, \"gid\": 1000, \"mode\": 420, \"mode_octal\": \"0100644\", \"size\": 4096, \"checksum\": \"dead...\" },\n     \"software\": \"Server.cc:2600@5.3.25\"\n   }\n\n.. index::\n   pair: Audit Logging; Log Files\n\nLog Files and Location\n^^^^^^^^^^^^^^^^^^^^^^\n\n**Location**: ``<logdir>/audit/`` where ``logdir`` is derived from ``XRDLOGDIR`` (see ``mgm/XrdMgmOfsConfigure.cc``).\n\n- Directory is created on startup if missing; mode 0755; owned appropriately by the service user.\n- **Active segment symlink**: ``<logdir>/audit/audit.zstd`` points to the current segment file.\n- **Segments**: Files are ZSTD-compressed; rotated every 1 hour by default (configurable via ``EOS_AUDIT_ROTATION=<seconds>``).\n- **Filenames**: ``audit-YYYYMMDD-HHMMSS.zst`` (includes seconds for uniqueness)\n- **Symlink update**: Atomically updated on rotation to point to the new segment.\n\nThe ``logdir`` is typically ``/var/log/eos/<servicename>/``, where ``<servicename>`` defaults to ``mgm`` but can be modified (e.g. ``mgm1``, ``mgm2``) to support multiple MGM instances.\n\n.. index::\n   pair: Audit Logging; Parsing\n\nParsing and Consumption\n^^^^^^^^^^^^^^^^^^^^^^^\n\n**Stream current audit records:**\n\n.. code-block:: bash\n\n   zstdcat <logdir>/audit/audit.zstd | jq '.'\n\n**Follow audit logs across rotations (like `tail -F`):**\n\n.. code-block:: bash\n\n   zstdtail <logdir>/audit/audit.zstd\n   # Or with filtering:\n   zstdtail <logdir>/audit/audit.zstd -- jq 'select(.operation == \"DELETE\")'\n\n**Historical segments:**\n\nEach segment file contains standalone JSON records. Process line-by-line:\n\n.. code-block:: bash\n\n   zstdcat <logdir>/audit/audit-20241106-164000.zst | jq 'select(.operation == \"DELETE\")'\n\n.. index::\n   pair: Audit Logging; Implementation\n\nImplementation Details\n^^^^^^^^^^^^^^^^^^^^^^\n\n- **Core components**: ``common/Audit.{hh,cc}`` - thread-safe ZSTD writer with rotation\n- **Protobuf schema**: ``proto/Audit.proto``\n- **MGM integration**: ``XrdMgmOfs`` class with ``mAudit`` member; initialized in ``XrdMgmOfsConfigure.cc``\n- **FUSE integration**: ``mgm/FuseServer/Server.cc`` for additional file operations\n\n**Integration points:**\n\n- **Core MGM operations**: Rm, Mkdir, Chmod, Chown, Attr, Link, Rename, XrdMgmOfsFile\n- **FUSE server operations**: OpSetFile, OpSetDirectory, OpDeleteFile, OpDeleteDirectory, OpSetLink\n\n.. index::\n   pair: Audit Logging; Notes\n\nNotes and Caveats\n^^^^^^^^^^^^^^^^^\n\n- Only successful operations are logged\n- Directory paths include trailing ``/`` for unambiguous parsing\n- Mode stored as integer and octal string for convenience\n- Audit writer flushes after each record for operational visibility\n- READ/LIST intentionally omitted by default due to volume\n- All timestamps use server time (seconds since epoch)\n\n.. index::\n   pair: Traffic Shaping; Shaping\n\nTraffic Shaping\n---------------\n\n.. index::\n   pair: Traffic Shaping; Overview\n\nOverview\n^^^^^^^^\n\nThe EOS Traffic Shaping feature provides real-time I/O monitoring and control at the cluster level. It allows administrators to observe I/O throughput grouped by application, user, group, or storage node. Furthermore, it provides a mechanism to dynamically limit I/O for specific entities to prioritize critical traffic.\n\nFor a detailed presentation on the motivation, architecture, and design of this feature, see the 2026 EOS Workshop contribution: https://indico.cern.ch/event/1622471/contributions/6947157/\n\n.. index::\n   pair: Traffic Shaping; Architecture\n\nArchitecture\n^^^^^^^^^^^^\n\nThe Traffic Shaping implementation relies on a fast, low-overhead loop between the storage nodes (FSTs) and the management node (MGM):\n\n* **FST (Storage Node):** The storage nodes record I/O bytes read and written per app, user, and group. They send lightweight protobuf reports (containing only updates) to the MGM. FSTs are also responsible for applying limits by temporarily blocking I/O threads based on delays calculated by the MGM.\n* **MGM (Management Node):** A dedicated ``TrafficShapingEngine`` processes the incoming FST reports using atomic accumulators. Background threads periodically calculate moving averages (such as a 1s EMA or 60s SMA) and compare the real-time throughput against configured limits. If throughput exceeds a limit, the MGM computes a dampening delay (in microseconds) and broadcasts it to the FSTs.\n\n.. index::\n   pair: Traffic Shaping; Configuration\n\nConfiguration and Enabling\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTraffic Shaping can be enabled or disabled globally from the console:\n\n.. code-block:: bash\n\n   # Enable traffic shaping\n   eos io shaping enable\n\n   # Disable traffic shaping\n   eos io shaping disable\n\nYou can view and modify the internal thread loop periods and time windows used by the shaping engine:\n\n.. code-block:: bash\n\n   # View current configuration\n   eos io shaping config ls\n\n   # Modify the system window and thread periods (in milliseconds/seconds)\n   eos io shaping config set --system-window 15 --estimators-period 100 --policy-period 100 --report-period 100\n\n.. index::\n   pair: Traffic Shaping; Policies\n\nSetting I/O Limits (Policies)\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nI/O limits can be applied to applications, users (UID), or groups (GID).\n\nTo list currently configured policies:\n\n.. code-block:: bash\n\n   eos io shaping policy ls\n\nTo set or update a policy:\n\n.. code-block:: bash\n\n   # Set a 10 MB/s read limit for a specific UID\n   eos io shaping policy set --uid 133153 --limit-read 10M\n\n   # Set a 16 MB/s write limit for a specific application\n   eos io shaping policy set --app eoscp --limit-write 16M\n\n   # Enable a previously disabled policy\n   eos io shaping policy set --uid 133153 --enable\n\nTo remove a policy:\n\n.. code-block:: bash\n\n   eos io shaping policy rm --uid 133153\n\n.. index::\n   pair: Traffic Shaping; Monitoring\n\nMonitoring Throughput\n^^^^^^^^^^^^^^^^^^^^^\n\nYou can retrieve real-time I/O rates directly from the console. The output is smoothed over a moving average window (default 60s, but 1s, 5s, 15s, and 300s are available).\n\n.. code-block:: bash\n\n   # Show throughput grouped by application (default)\n   eos io shaping ls\n\n   # Show throughput grouped by users (UID)\n   eos io shaping ls --users\n\n   # Show throughput grouped by groups (GID)\n   eos io shaping ls --groups\n\n   # Show throughput grouped by FST nodes\n   eos io shaping ls --nodes\n\n   # Specify a different moving average window (e.g., 5 seconds)\n   eos io shaping ls --window 5\n\n.. index::\n   pair: Traffic Shaping; Observability\n\nObservability and Exporters\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTraffic Shaping metrics are natively exposed via JSON (using the ``--json`` flag). These metrics are fully integrated into the Prometheus ``eos_exporter``, allowing seamless aggregation of application, user, group, and cluster-total throughput in monitoring dashboards like Grafana.\n"
  },
  {
    "path": "doc/diopside/manual/protocols.rst",
    "content": ".. highlight:: rst\n\n.. index::\n   single: Protocols\n\n\nProtocols & APIs\n----------------\n\nHTTP access\n^^^^^^^^^^^\n\n.. index::\n   pair: HTTP; WebDAV\n\n**HTTP** access is provided using the XrdHttp plug-in running on the **MGM** recommended on\nport **8443** and on **FSTs** on port **8444**.\nClients are mapped to 'nobody' if the authorization/token are missing.\n\n.. index::\n   pair: HTTP; NGINX\n   pair: HTTP; Proxy\n\n.. NOTE::\n\n   The XrdHttp configuration is describe under http-configuration_!\n\nConfiguring an NGINX Proxy\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nPreconditions\n+++++++++++++\n.. note::\n   To run the HTTPS proxy you need to have the **eos-nginx** RPM installed.\n\n.. code-block:: bash\n\n   yum install eos-nginx\n\nThe configuration for the NGINX HTTPS proxy server is ``/etc/sysconfig/nginx``.\nEach field in the configuration file is well documented.\n\nThe most important settings you might want to change are described in the following.\n\n\n .. index::\n   pair: HTTP; Certificates\n\n\n.. index::\n   pair: NGINX; Certificates\n\nCertificates\n++++++++++++\nLocation of host key and host certificate:\n\n.. code-block:: bash\n\n   export EOS_NGINX_SSL_CERTIFICATE=/etc/grid-security/hostcert.pem\n   export EOS_NGINX_SSL_KEY=/etc/grid-security/hostkey.pem\n\nPort of the HTTPS server with X509 certificate authentication:\n\n.. code-block:: bash\n\n   export EOS_NGINX_CLIENT_SSL_PORT=443\n\n.. index::\n   pair: NGINX; Kerberos\n\nKerberos Authentication\n+++++++++++++++++++++++\nPort of the HTTPS server with Kerberos5 authentication:\n\n.. code-block:: bash\n\n   export EOS_NGINX_CLIENT_SSL_PORT=443\n\nKerberos REALM and keytab file:\n\n.. code-block:: bash\n\n   export EOS_NGINX_GSS_KEYTAB=/etc/krb5.keytab\n   export EOS_NGINX_GSS_REALM=CERN.CH\n\nThe kerberos keytab file must be readable by the daemon account!\n\n.. index::\n   pair: NGINX; Frontend-Redirecition\n\n\n\nFrontend- or Backend- Redirection\n+++++++++++++++++++++++++++++++++\nNGINX is configured by default to forward redirects to the client.\nHowever many WebDAV clients don't follow redirects. You can enable\ninternal (backend-) redirection proxying the full traffic like this:\n\n.. code-block:: bash\n\n   export EOS_NGINX_REDIRECT_EXTERNALLY=0\n\n.. index::\n   pair: NGINX; Backend-Redirecition\n\n.. index::\n   pair: NGINX; Deployment\n\n\nDeployment on MGM or Gateway machines\n+++++++++++++++++++++++++++++++++++++\nIf you want to run a proxy on a different host than the MGM, you have to modify\n``/etc/nginx/nginx.eos.conf.template`` and replace **localhost** with the MGM host\nname.\n\n.. warning::\n   Make sure to configure appropriate firewall rules for *non-MGM* HTTPS proxy\n   deployments!\n\n.. code-block:: bash\n\n                  proxy_pass         http://localhost:8443/;\n\n.. index::\n   pair: HTTP; User Mapping\n\n\nUser Mapping\n\"\"\"\"\"\"\"\"\"\"\"\"\"\nThe **MGM** HTTP module does the user mapping based on the NGINX added authentication header.\nKerberos names are trivially mapped from their principal name, X509 users are mapped using\nthe default gridmapfile ``/etc/grid-security/grid-mapfile``.\nBy default all HTTP(S) traffic is mapped to nobody. To map users according to\ntheir authentication token enable HTTPS mapping in the virtual identity interface:\n\n.. code-block:: bash\n\n   eosdevsrv1 # eos -b vid enable https\n\n.. index::\n   pair: HTTP; Log Files\n\nLog Files\n\"\"\"\"\"\"\"\"\"\nIf you didn't modify the NGINX configuration file, NGINX will produce two log information\nfiles with the access and error log ``/var/log/nginx/access.log`` and ``/var/log/nginx/error.log``.\n\nThe **MGM** writes a HTTP related log file under ``/var/log/eos/mgm/Http.log``.\n\nTo get more\nverbose information you can change the log level:\n\n.. code-block:: bash\n\n   # switch to debug log level on the MGM\n   eos debug debug\n\n   # switch back to info log level on the MGM\n   eos debug info\n\n.. index::\n   pair: HTTP; SE Linux\n\nSE Linux\n\"\"\"\"\"\"\"\"\"\n\nMake sure that your proxy host is not blocking outgoing connections for NGINX to the backend MGM/FST servers (port 8000/8001). The same is true for the incoming\nconnections on the HTTPS ports.\n\nProxy Certificates\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\n.. warning::\n   NGINX supports proxy certificates ony if they are RFC compliant!\n\nYou should create them e.g. with **grid-proxy-init** using the **-rfc** flag:\n\n.. code-block:: bash\n\n   grid-proxy-init -rfc\n\n.. index::\n   pair: HTTP; File Sharing Links\n   pair: HTTP; pre-signed URLs\n\nFile Sharing Links\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nROOT or HTTP URLs can be obtained with the EOS shell using\n\n.. code-block:: bash\n\n   eos file share myfile\n\n\n.. index::\n   pair: HTTP; TPC\n\n.. index::\n   single: TPC\n\nThird Party Copy TPC\n^^^^^^^^^^^^^^^^^^^^\n\n.. index::\n   pair: TPC; Delegated Credentials\n\n\nHTTP and XRootD TPC with delegated credentials\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThere are several ways in which a third-party transfer can be triggered in an\nXRootD based system like EOS. Currently EOS supports third-party-copy transfers\nfor both the XRootD and HTTP protocol.\n\nDepending on the authentication/authorization model there are several ways in which\na third-party-copy transfer can proceed but they fall in the following broad\ncategories:\n\n* `XRootD TPC without delegated credentials`_\n* `XRootD TPC with delegated credentials`_\n* `HTTP(S) support and HTTP TPC with token authentication`_\n\n.. index::\n   pair: TPC; Credentials\n\nXRootD TPC without delegated credentials\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nEOS enforces authentication and authorization of client on the MGM node and\nsupports the following authentication mechanisms:\n* `KRB5 (Kerberos 5) <https://xrootd.slac.stanford.edu/doc/dev49/sec_config.htm#_Toc517294110>`_\n* `GSI certificates <https://xrootd.slac.stanford.edu/doc/dev49/sec_config.htm#_Toc517294098>`_\n* `SSS (Simple Shared Secret) <https://xrootd.slac.stanford.edu/doc/dev49/sec_config.htm#_Toc517294117>`_\n\nThe storage nodes (FSTs) on the other hand, accept **unix** and **SSS**\nauthentication, relying on the encrypted opaque information that the MGM\nprovides to the client when redirecting to decide if the transfer is allowed\nor not.\n\nBy default, all outbound connections from the FST daemon to any other endpoint\nhave the **SSS** authentication mechanism enforced. Due to this, a TPC transfer\nbetween EOS instances that don't share the same SSS key is impossible. On the\nother hand, TPC transfers within the same instance will always work and this\nfunctionality is heavily used internally for draining, balancing and other\nmaintenance operations. To relax this constraint and allow non-secure connection\nfrom the FSTs nodes to other endpoints, the service manager can set the following\nenvironment variable to disable **SSS** enforcement.\n\n.. code-block:: bash\n\n  # File /etc/sysconfig/eos_env\n  EOS_FST_NO_SSS_ENFORCEMENT=1\n\nWhile this option can easily be enabled in different EOS services managed by\nthe same organization, this becomes impossible when one of the TPC endpoints\nis not an EOS instance or is managed by a different entity.\n\nThe TPC model in XRootD is pull based. Therefore, TPC transfers that have the\nEOS endpoint as source of the transfer work no matter the configuration setup,\nwhile TPC transfers with EOS as the destination will fail without disabling the\nSSS enforcement. A simple way to trigger a TPC transfer is by using the **xrdcp**\ncommand with the following options:\n\n.. code-block:: bash\n\n   xrdcp --tpc only root://eos1.cern.ch//path/to/source root://eos1.cern.ch//path/to/destination\n\n\n.. index::\n   pair: TPC; Delegated Credentials\n\nXRootD TPC with delegated credentials\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIn order to enable more complex scenarios and to provide a viable alternative\nto the GridFTP service, the XRootD client starting with version 4.10.0 supports\nclient credential delegation. Direct transfers with delegated credentials against\nEOS instances work out of the box without any configuration changes.\n\nOn the other hand, for TPC transfers with delegated credentials to be supported\nby an EOS instance there are several modifications needed. All these changes are\nneed to accommodate the fact that there is no actual authentication of the client\non the FST side, therefore there is no credential information to be delegated.\n\nFirst of all, the EOS service manager will need to deploy a new XRootD Proxy\nservice that will act as a gateway for incoming TPC traffic. As mentioned in the\nprevious section, TPC transfers where EOS is the source work perfectly fine\nwithout any configuration changes. The gateway is a vanilla **XRootD PSS**\nservice with the following reference configuration:\n\n.. code-block:: bash\n   :linenos:\n\n   ofs.osslib  libXrdPss.so\n   ofs.ckslib  * libXrdPss.so\n   xrootd.chksum  adler32\n   xrootd.seclib  libXrdSec.so\n   pss.origin  eos-target-instance.cern.ch:1094\n   all.export  /eos/\n   all.adminpath  /var/spool/xrootd\n   all.pidpath  /var/run/xrootd\n   sec.protocol  gsi -dlgpxy:1 -exppxy:=creds -crl:1 -moninfo:1 -cert:/etc/grid-security/daemon/gridftp-cert.pem -key:/etc/grid-security/daemon/gridftp-key.pem -gridmap:/etc/grid-security/grid-mapfile -d:1 -gmapopt:2\n   sec.protbind  * gsi\n   ofs.tpc  autorm fcreds gsi =X509_USER_PROXY ttl 60 60 xfr 9 pgm /usr/local/bin/xrootd-third-party-copy.sh\n\n\nThe only configuratino option to be modified for new setups is the **pss.origin**\nthat needs to point to the EOS MGM node. Particular care should be taken when\ntyping the **ofs.tpc** directive to follow the exact format of the options present\nin the example above. Support for delegated credentials also requires subtile\nchanges to the **sec.protocol** directive that are clearly explained in the\nXRootD documentation and already present in the provided example.\n\n.. The :ref:`helper script <xrootd-third-party-copy>` referenced in the configuration\n\nThe `xrootd-third-party-copy.sh` referenced in the configuration makes use of specific environment variables exported by the XRootD PSS service\nin the context of the TPC process doing the transfer.\n\n.. :caption: Contents of the xrootd-third-party-copy.sh file\n.. :name: xrootd-third-party-copy\n\n.. code-block:: bash\n\n     #! /usr/bin/env bash\n     OPTS=(\"${@:1:$#-2}\")\n     shift $(($# - 2))\n     SRC=$1\n     DST=$2\n\n     if [[ -n \"${XRDXROOTD_ORIGIN}\" ]]; then\n       DST=\"root://${XRDXROOTD_ORIGIN}/${DST}\"\n     fi\n\n     xrdcp --server \"${OPTS[@]}\" \"${SRC}\" \"${DST}\"\n     STATUS=$?\n\n     if [[ ${STATUS} -ne 0 ]]; then\n       logger -p err  -t xrdcp-tpc \"transfer: xrdcp --server ${OPTS[*]} ${SRC} ${DST} FAILED [exit code: ${STATUS}]\"\n     fi\n\n     exit ${STATUS}\n\nOnce the XRootD gateway is setup, the EOS MGM configuration needs to be updated\nso that any incoming TPC transfers with delegated credentials where EOS is the\ndestination endpoint are redirected to the gateway node. This is done by adding\nthe following directive to the default EOS MGM configuration file located in\n``/etc/xrd.cf.mgm``:\n\n.. code-block:: bash\n\n   ofs.tpc  redirect delegated eos-gateway-node.cern.ch:1094\n\nIn order to trigger a TPC transfer with delegated credentials the user needs to\nhave a valid X509 certificate that the xrdcp command can use during the transfer.\nThe xrdcp command will automatically pick up the user certificate by using the\nfollowing environment variables:\n\n.. code-block:: bash\n\n   # Set the path for X509 user \"foo\"\n   export X509_USER_CERT=/home/foo/.globus/usercert.pem\n   export X509_USER_KEY=/home/foo/.globus/userkey.pem\n\nThe xrdcp command can also use a user proxy certificate to trigger a TPC transfer\nwith delegated credentials. The easiest way for a user to obtain a proxy\ncertificate is to use the ``voms-proxy-init`` tool form the ``voms-client-cpp``\npackage.\n\n.. code-block:: bash\n\n   voms-proxy-init\n   voms-proxy-info\n   subject   : /DC=ch/DC=cern/OU=Organic Units/OU=Users/CN=foo/CN=007/CN=Foo Bar/CN=220482279\n   issuer    : /DC=ch/DC=cern/OU=Organic Units/OU=Users/CN=foo/CN=007/CN=Foo Bar\n   identity  : /DC=ch/DC=cern/OU=Organic Units/OU=Users/CN=foo/CN=007/CN=Foo Bar\n   type      : RFC compliant proxy\n   strength  : 512 bits\n   path      : /tmp/x509up_u1001\n   timeleft  : 11:53:16\n\nTo make sure we enforce GSI authentication and trigger the delegation of\ncredentians we can also set the **XrdSecPROTOCOL** environment variable together\nwith the following options for the xrdcp command:\n\n.. code-block:: bash\n\n   XrdSecPROTOCOL=gsi,unix xrdcp --tpc delegate only root://eos1.cern.ch//path/to/source root://other.world.com//path/to/destination\n\nThe minimum requirements for this setup to work correctly are the following:\n\n  - XRootD PSS gateway >= 4.11.1\n  - EOS instance >= 4.6.8\n  - User XRootD client triggering the TPC transfer >= 4.11.1\n\n\n.. index::\n   pair: HTTP; Token Authentication\n\nHTTP(S) support and HTTP TPC with token authentication\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nEOS supports HTTP access by making use of the XrdHttp plugin which comes by\ndefault with XRootD. There are several configuration changes that need to be\nmade both on the MGM side and on the FST side to have this setup working.\n\n.. index::\n   pair: HTTP; VOMS\n\n\nApart from basic HTTP(S) access with client certificates, EOS also supports\nHTTP(S) with token authentication starting with version 4.6.8. There\nare several extra packages that need to be installed on the MGM node to\nenable this feature:\n\n  - **xrdhttpvoms** package which allows the HTTP module to handle proxy\n    certificates from the clients. This can be found in the EPEL repository.\n  - **eos-scitokens** and **eos-scitokens-debuginfo** packages to enable\n    support for SciTokens in EOS. These packages can be found in the\n    `eos-depend repository <http://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-7/x86_64/>`_.\n    Starting with EOS 5.0.16 there is no need to install the *eos-scitokens*\n    package as EOS can use the default library provided by the XRootD framework,\n    namely *libXrdAccSciTokens.so* coming from the *xrootd-server* package.\n\n.. index::\n   pair: HTTP; Sci Tokens\n\nThe following packages are not mandatory but they provide convenient tools\nfor testing the token support against the EOS instance:\n\n  - **x509-scitokens-issuer** and **x509-scitokens-issuer-client** that provide\n    tools like **macaroon-init** useful when trying to acquire a macaroons for\n    testing purposes. They can be found here: http://koji.chtc.wisc.edu/kojifiles/packages/\n\nSupport for HTTP(S) access in EOS is provided through an HTTP external handler\nplug-in library which is distributed by default with any EOS version called\n**libEosMgmHttp.so**.\n\nBelow you can find a reference configuration file that will enable HTTP(S) support\nand HTTP TPC with both macaroons and scitokens on the MGM. Each line\ncontains a description of the functionality provided.\n\nHTTP Configuration\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\n.. index::\n   pair: HTTP; Configuration\n\n\n\n.. :caption: Contents of /etc/xrd.cf.mgm file\n.. :linenos:\n\n.. _http-configuration:\n\n.. code-block:: bash\n\n   # Load and enable HTTP(S) access on port 9000 on the current instance\n   xrd.protocol XrdHttp:9000 /usr/lib64/libXrdHttp.so\n   # Directory containing CA certificates to be used by the server\n   xrd.tlsca certdir /etc/grid-security/certificates/\n   # The x509 server certificate and key\n   xrd.tls /etc/grid-security/daemon/hostcert.pem /etc/grid-security/daemon/hostkey.pem\n   # Path to the \"grid map file\" to be used for mapping users to specific identities\n   http.gridmap /etc/grid-security/grid-mapfile\n   # Load security extractor plugin able to deal with proxy certificates and VOMS credentials\n   http.secxtractor libXrdVoms.so\n   # Optionally enable tracing on the HTTP plugin\n   http.trace all\n   # Load the XrdTpc external handler which deals only with COPY and OPTIONS http\n   # verbs and provides the default HTTP TPC functionality\n   http.exthandler xrdtpc /usr/lib64/libXrdHttpTPC.so\n   # Load the EOS specific HTTP external handler libEosMgmHttp.so and also specify\n   # the option is HTTP traffic is to be redirected to HTTP(S)\n   http.exthandler EosMgmHttp /usr/lib64/libEosMgmHttp.so eos::mgm::http::redirect-to-https=0\n   # The following two external library plugins are used to provide support for\n   # token based authentication with Macaroons and SciTokens. Presence of the\n   # second library is optional. When the SciTokens library is present and the\n   # XrdMacaroons can not deal with the request then this is delegated to the\n   # SciTokens library.\n   # Note: Until eos-5.0.15 one needs to use the EOS specific SciTokens library\n   # mgmofs.macaroonslib /usr/lib64/libXrdMacaroons.so /usr/lib64/libEosAccSciTokens.so\n   # Starting with eos-5.0.16 one can use the XRootD provided SciTokens library\n   mgmofs.macaroonslib /usr/lib64/libXrdMacaroons.so /usr/lib64/libXrdAccSciTokens.so\n   # Base64-encoded secret key used for generating macroons. A simple way to\n   # generate such a secret key is to use the following command:\n   # openssl rand -base64 -out /etc/eos.macaroon.secret 64\n   macaroons.secretkey /etc/eos.macaroon.secret\n   # Optionally enable tracing for the XrdMacaroons plugin\n   macaroons.trace all\n   # Mandatory sitename configuration for the XrdMacaroons library which is also\n   # embedded in the macaroons attributes\n   all.sitename eosdev\n\nA simple method of generating a valid ``/etc/eos.macaroon.secret`` file is:\n\n.. :caption: Generating an /etc/eos.macaroon.secret file\n\n.. code-block:: bash\n\n   openssl rand -base64 -out /etc/eos.macaroon.secret 64\n\n\nThe **XrdAccSciTokens** library relies on the default **XRootD Authorization**\nplugin to be loaded, which in turn checks that the file ``/opt/xrd/etc/Authfile``\nfile exists. Therefore, one needs to ensure the path exists and that the file is\nowned by daemon:daemon user under which the MGM service runs. The service\nmanager also needs to put in place the basic configuration for SciTokens support\nthat relies on the ``/etc/xrootd/scitokens.cfg`` file. This file contains\ninformation about the IAM (Identity and Access Management) provider that the\nclient/MGM service will contact for SciTokens support. A reference ``scitokens.cfg``\nfile is provided below:\n\n.. :caption: Contents of the /etc/xrootd/scitokens.cfg file\n\n.. code-block:: bash\n\n   [Global]\n   audience = https://wlcg.cern.ch/jwt/v1/any\n\n   [Issuer OSG-Connect]\n   issuer = https://wlcg.cloud.cnaf.infn.it/\n   base_path = /\n   map_subject = False\n   default_user = dteam001\n\nAn important configuration option is the **default_user** field which specifies\nthe local username (i.e. known to the MGM) that any token issued by the given IAM\nis mapped to.\n\nApart from the **MGM**, all the **FST** configurations also need to be updated in\norder to support HTTP(XrdHttp) and HTTP TPC access.\n\n.. :caption: Contents of the /etc/xrd.cf.fst file relevant for HTTP config\n\n.. code-block:: bash\n\n   # Enable the XrdHttp plugin and listen on port 9001 for connections\n   xrd.protocol XrdHttp:9001 /usr/lib64/libXrdHttp.so\n   # Load the libEosFstHttp external handler\n   http.exthandler EosFstHttp /usr/lib64/libEosFstHttp.so none\n   # Load the XrdTpc external handler which deals with COPY and OPTIONS http\n   # verbs and provides the default HTTP TPC functionality\n   http.exthandler xrdtpc /usr/lib64/libXrdHttpTPC-4.so\n\nThe port specified int the **xrd.protocol** directive is specific to the XrdHttp\nplugin implementation and must be properly configured depending on the\nenvironment variable **EOS_FST_HTTP_PORT**. The XrdHttp target port redirection\nis advertised from the FST to the MGM and represents the port location\nwhere MGM will redirect incoming clients requesting HTTP(S) access to the data.\n\nThis can easily be done by adding a systemd custom configuration file for the\nFST service in ``/usr/lib/systemd/system/eos@fst.service.d/custom.conf``.\n\n.. :caption: Contents of the custom.conf file\n\n.. code-block:: bash\n\n   [Service]\n   Environment=EOS_FST_HTTP_PORT=9001\n\nAfter starting the EOS service, one can check for the actual value of the HTTP\nport advertised by the individual FSTs by executing the following command:\n\n.. code-block:: bash\n\n   eos fs status 1 | grep http\n   stat.http.port 9001\n\nIn order to have the identity embedded in the tokens (macaroon/scitoken) properly\nmapped to the local identity used in EOS, one also needs to enable the **https vid**\nmapping:\n\n.. :caption: Enable vid https mapping\n\n.. code-block:: bash\n\n   eos vid enable https\n\n\nPractical examples for HTTP(S) transfers\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThis section contains several examples of HTTP transfers done against an EOS\ninstance configured with support for certificates, token authorization and\nwith HTTP TPC. To trigger such transfers we'll make use of the **curl** command\nwhich one of the most feature rich and reliable tools for testing HTTP access\nand is also used in it's turn by other client tools that enable HTTP transfers\nlike for example **davix**.\n\n\n.. index::\n   pair: HTTP; X509\n\nHTTP transfers with X509 credentials\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe assumption here is that the client has a valid certificate and decoded private\nkey available. To trigger a simple upload to EOS one can use the following command:\n\n.. code-block:: bash\n\n   curl -L -v --capath /etc/grid-security/certificates --cert ~/.globus/usercert.pem --cacert ~/.globus/usercert.pem --key ~/.globus/userkey.pem https://e0.cern.ch:9000//eos/dev/replica/file1.dat --upload-file /etc/passwd\n\n\n   [esindril@esdss000 build_clang_ninja]$ sudo eos fileinfo /eos/dev/replica/file1.dat\n   File: '/eos/dev/replica/file1.dat'  Flags: 0644\n   Size: 3314\n   Modify: Wed Jan 29 14:54:20 2020 Timestamp: 1580306060.468009000\n   Change: Wed Jan 29 14:54:20 2020 Timestamp: 1580306060.459330223\n   Birth : Wed Jan 29 14:54:20 2020 Timestamp: 1580306060.459330223\n   CUid: 58602 CGid: 1028  Fxid: 00015ac5 Fid: 88773    Pid: 11   Pxid: 0000000b\n   XStype: adler    XS: 74 d7 7c 3a    ETAGs: \"23829820735488:74d77c3a\"\n   Layout: replica Stripes: 2 Blocksize: 4k LayoutId: 00100112\n   #Rep: 2\n   ┌───┬─────┬───────────┬──────────┬──────────────┬───────┬────────────┬────────┬──────┬──────┐\n   │no.│fs-id│       host│schedgroup│          path│   boot│configstatus│   drain│active│geotag│\n   └───┴─────┴───────────┴──────────┴──────────────┴───────┴────────────┴────────┴──────┴──────┘\n    0       5  e0.cern.ch  default.0 /home/../fst5  booted            rw nodrain  online  elvin\n    1       1  e0.cern.ch  default.0 /home/../fst1  booted            rw nodrain  online  elvin\n\nWhen doing such a transfer the \"grid map file\" specified in the configuration of\nthe MGM node is used to map the client DN to a known local identity.\n\n.. index::\n   pair: HTTP; Macaroons\n\nHTTP transfers with Macaroon authentication\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nTo trigger a HTTP transfer using a Macaroon token, we first need to acquire a\nMacaroon from the EOS MGM endpoint using our X509 certificate and then use this\nmacarron to authenticate/authorize the transfer. The macaroon token will embed\nthe username from the X509 certificate (or the mapped identity from the\n\"grid map file)\" so that when the token request is issued the client identity\non the server side will be mapped to this username.\n\n.. :caption: Requesting a macaroon using a X509 certificate.\n\n.. code-block:: bash\n\n   # Make sure the following environment variables point to the client\n   # certificate and private key\n   X509_USER_CERT=/home/esindril/.globus/usercert.pem\n   X509_USER_KEY=/home/esindril/.globus/userkey.pem\n   # Use the macaroon-init tool to request a macaroon\n   macaroon-init https://esdss000.cern.ch:9000//eos/ 60 DOWNLOAD,UPLOAD\n   MDAxNGxvY2F0aW9uIGVvc2RldgowMDM0aWRlbnRpZmllciBiYzhiZWRmZC0wNzJjLTRmZWEtYjNiYy0wNDJjZjczZDhiYjMKMDAxNmNpZCBuYW1lOmVzaW5kcmlsCjAwMWZjaWQgYWN0aXZpdHk6UkVBRF9NRVRBREFUQQowMDI4Y2lkIGFjdGl2aXR5OkRPV05MT0FELFVQTE9BRCxNQU5BR0UKMDAxM2NpZCBwYXRoOi9lb3MvCjAwMjRjaWQgYmVmb3JlOjIwMjAtMDEtMjlUMTU6MTM6MzVaCjAwMmZzaWduYXR1cmUguNm15NCbrb62KCIvxxDlSgrwgMZKjGPrO7NwxFQwIycK\n   # Export the token as an environment variable for easier use later on\n   export MACAROON=MDAxNGxvY2F0aW9uIGVvc2RldgowMDM0aWRlbnRpZmllciBiYzhiZWRmZC0wNzJjLTRmZWEtYjNiYy0wNDJjZjczZDhiYjMKMDAxNmNpZCBuYW1lOmVzaW5kcmlsCjAwMWZjaWQgYWN0aXZpdHk6UkVBRF9NRVRBREFUQQowMDI4Y2lkIGFjdGl2aXR5OkRPV05MT0FELFVQTE9BRCxNQU5BR0UKMDAxM2NpZCBwYXRoOi9lb3MvCjAwMjRjaWQgYmVmb3JlOjIwMjAtMDEtMjlUMTU6MTM6MzVaCjAwMmZzaWduYXR1cmUguNm15NCbrb62KCIvxxDlSgrwgMZKjGPrO7NwxFQwIycK\n   # Use the curl command to trigger the transfer (download) and properly\n   # populate the header information with the authentication information\n   curl -v -L -H \"Authorization: Bearer $MACAROON\" https://esdss000.cern.ch:9000/eos/dev/replica/file1.dat\n\nFor debugging purposes or just simple curiosity the client can inspect the\ncontents of the macaroon if they have access to the ``/etc/eos.macaroon.secret``\nfile. This can easily be done by installing the **python2-macaroons** package\nfrom EPEL and launching a python shell as follows:\n\n.. :caption: Python script to decode a Macaroon token\n\n.. code-block:: python\n\n   >>> import macaroons\n   >>> secret = open(\"/etc/eos.macaroon.secret\", 'r').read()\n   >>> mtoken = \"MDAxNGxvY2F0aW9uIGVvc2RldgowMDM0aWRlbnRpZmllciBiYzhiZWRmZC0wNzJjLTRmZWEtYjNiYy0wNDJjZjczZDhiYjMKMDAxNmNpZCBuYW1lOmVzaW5kcmlsCjAwMWZjaWQgYWN0aXZpdHk6UkVBRF9NRVRBREFUQQowMDI4Y2lkIGFjdGl2aXR5OkRPV05MT0FELFVQTE9BRCxNQU5BR0UKMDAxM2NpZCBwYXRoOi9lb3MvCjAwMjRjaWQgYmVmb3JlOjIwMjAtMDEtMjlUMTU6MTM6MzVaCjAwMmZzaWduYXR1cmUguNm15NCbrb62KCIvxxDlSgrwgMZKjGPrO7NwxFQwIycK\"\n   >>> M = macaroons.deserialize(mtoken)\n   >>> print M.inspect()\n   location eosdev\n   identifier bc8bedfd-072c-4fea-b3bc-042cf73d8bb3\n   cid name:esindril\n   cid activity:READ_METADATA\n   cid activity:DOWNLOAD,UPLOAD,MANAGE\n   cid path:/eos/\n   cid before:2020-01-29T15:13:35Z\n   signature b8d9b5e4d09badbeb628222fc710e54a0af080c64a8c63eb3bb370c454302327\n\n\n.. index::\n   pair: HTTP; SciToken\n\n\nHTTP transfers with SciToken authentication\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nHTTP transfers with SciTokens work in a similar way to Macaroon tokens. In order\nto get a SciToken, one needs to be registered with an IAM provider and install\nthe **oidc-agent** package which provides the client tools to register and request\ntokens. An RPM package for CentOS7 is already available from the\n`GitHub releases page of the project <https://github.com/indigo-dc/oidc-agent/releases>`_.\n\nTo configure the **oidc-agent**, you can follow these steps:\n\n.. code-block:: bash\n\n   # Start the oidc-agent in the background\n   eval $(oidc-agent)\n   oidc-gen WLCG-<your_username> -w device\n   # Put as issuer https://wlcg.cloud.cnaf.infn.it/ and configure the set of\n   # scopes as \"max\". Then connect the agent to the IAM provide which will\n   # prompt you for the password you set up earlier.\n   oidc-add WLCG-<your_username>\n   # Request a token from the IAM and save it as an environment variable for\n   # later use\n   export SCI_TOKEN=`oidc-token WLCG-<your_username>`\n   # Trigger a HTTP download using the SciToken information\n   curl -v -L -H \"Authorization: Bearer $SCI_TOKEN\" https://esdss000.cern.ch:9000/eos/dev/replica/file1.dat\n\n\nTo inspect the contents of a SciToken, one can use the following commands:\n\n.. code-block:: bash\n\n    echo $SCI_TOKEN | cut -d. -f2 | base64 --decode | jq .\n    {\n      \"wlcg.ver\": \"1.0\",\n      \"sub\": \"faded49c-e1bc-4208-9634-682b2b8d16e5\",\n      \"aud\": \"https://wlcg.cern.ch/jwt/v1/any\",\n      \"nbf\": 1613993622,\n      \"scope\": \"address storage.create:/ phone openid offline_access profile storage.read:/ storage.modify:/ email wlcg wlcg.groups\",\n      \"iss\": \"https://wlcg.cloud.cnaf.infn.it/\",\n      \"exp\": 1613997222,\n      \"iat\": 1613993622,\n      \"jti\": \"ea07cad1-f504-4c16-9e22-da5de2876ca7\",\n      \"client_id\": \"710b4313-5ff7-4992-a59d-d404ea9d4ac5\",\n      \"wlcg.groups\": [\n                \"/wlcg\",\n                \"/wlcg/xfers\"\n       ]\n    }\n\nHTTP TPC PULL transfers with CURL\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe following snippet provides the steps necessary for obtaining the necessary tokens for doing a HTTP TPC PULL transfer.\n\n.. code-block:: bash\n\n   export SRC=https://esdss000.cern.ch//eos/opstest/esindril/file.dat\n   export DST=https://esdss000.cern.ch//eos/opstest/esindril/file1.dat\n   # Get macaroon for source\n   export TSRC=$(curl --silent --cert /tmp/x509up_u$(id -u) --key /tmp/x509up_u$(id -u) --cacert /tmp/x509up_u$(id -u) --capath /etc/grid-security/certificates -X POST -H 'Content-Type: application/macaroon-request' -d '{\"caveats\": [\"activity:DOWNLOAD\"], \"validity\": \"PT3000M\"}' \"$SRC\" | jq -r '.macaroon')\n   # Get macaroon for destination\n   export TDST=$(curl --silent --cert /tmp/x509up_u$(id -u) --key /tmp/x509up_u$(id -u) --cacert /tmp/x509up_u$(id -u) --capath /etc/grid-security/certificates -X POST -H 'Content-Type: application/macaroon-request' -d '{\"caveats\": [\"activity:UPLOAD,DELETE,LIST\"], \"validity\": \"PT3000M\"}' \"$DST\" | jq -r '.macaroon')\n   # Trigger HTTP TPC PULL\n   curl -v --capath /etc/grid-security/certificates -L -X COPY -H 'Secure-Redirection: 1' -H 'X-No-Delegate: 1' -H 'Credentials: none' -H \"Authorization: Bearer $TDST\" -H \"TransferHeaderAuthorization: Bearer $TSRC\" -H \"TransferHeaderTest: Test\" -H \"Source: $SRC\" \"$DST\"\n\nThe same thing now but for a HTTP TPC PUSH transfer.\n\n.. code-block:: bash\n\n   export SRC=https://esdss000.cern.ch//eos/opstest/esindril/xfile.dat\n   export DST=https://esdss000.cern.ch//eos/opstest/esindril/xfile1.dat\n   # Get macaroon for source\n   export TSRC=$(curl --silent --cert /tmp/x509up_u$(id -u) --key /tmp/x509up_u$(id -u) --cacert /tmp/x509up_u$(id -u) --capath /etc/grid-security/certificates -X POST -H 'Content-Type: application/macaroon-request' -d '{\"caveats\": [\"activity:DOWNLOAD\"], \"validity\": \"PT3000M\"}' \"$SRC\" | jq -r '.macaroon')\n   # Get macaroon for destination\n   export TDST=$(curl --silent --cert /tmp/x509up_u$(id -u) --key /tmp/x509up_u$(id -u) --cacert /tmp/x509up_u$(id -u) --capath /etc/grid-security/certificates -X POST -H 'Content-Type: application/macaroon-request' -d '{\"caveats\": [\"activity:UPLOAD,DELETE,LIST\"], \"validity\": \"PT3000M\"}' \"$DST\" | jq -r '.macaroon')\n   # Trigger HTTP TPC PUSH\n   curl -v --capath /etc/grid-security/certificates -L -X COPY -H 'Secure-Redirection: 1' -H 'X-No-Delegate: 1' -H 'Credentials: none' -H \"Authorization: Bearer $TSRC\" -H \"TransferHeaderAuthorization: Bearer $TDST\" -H \"Destination: $DST\" \"$SRC\"\n\n\n.. only:: adminmode\n\n   HTTP TPC transfer triggered by FTS\n   \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nScitags and packet marking\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nMore information about scitags can be found here: https://scitags.org/\n\nEnable packet marking for SciTags support\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe packet marking needs to be enabled on all the FSTs via the FST configuration file:\n\n.. code-block:: bash\n\n    # in xrd.cf.fst\n    xrootd.pmark  use firefly scitag\n    xrootd.pmark  domain any\n    xrootd.pmark  ffdest <FIREFLY_COLLECTOR_IP>:<FIREFLY_COLLECTOR_PORT>\n    xrootd.pmark  defsfile curl https://scitags.docs.cern.ch/api.json\n\nMore configuration can be found in the XRootD documentation related to packet marking: https://xrootd.web.cern.ch/doc/dev6/xrd_config.htm#_Toc204020828\n"
  },
  {
    "path": "doc/diopside/manual/using.rst",
    "content": ".. index::\n   single: Using\n\n.. highlight:: rst\n\n.. _using:\n\nUsing EOS\n===========\n\n.. index::\n   pair: Tokens; EOS Tokens\n   pair: Using; EOS Tokens\n\n\nEOS Tokens for Authorization\n----------------------------------\n\nWe provide a generic EOS mechanism to delegate permissions to a token\nbearer with s.c. EOS tokens.\n\n.. index::\n   pair: EOS Tokens; JSON Format\n\n\nThe JSON representation of an EOS token looks like this:\n\n.. code-block:: bash\n\n  {\n  \"token\": {\n    \"permission\": \"rwx\",\n    \"expires\": \"1571319146\",\n    \"owner\": \"\",\n    \"group\": \"\",\n    \"generation\": \"1\",\n    \"path\": \"/eos/dev/token\",\n    \"allowtree\": false,\n    \"vtoken\": \"\",\n    \"voucher\": \"baecb618-f0e4-11e9-85d9-fa163eb6b6cf\",\n    \"requester\": \"[Thu Oct 17 15:47:59 2019] uid:0[root] gid:0[root] tident:root.13809:107@localhost name:daemon dn: prot:sss host:localhost domain:localdomain geo:cern sudo:1\",\n    \"origins\": []\n  },\n  \"signature\": \"daUeOZafRUt6VfQZ+g3FMbR/ZA5WvARELqFwdQxbyFU=\",\n  \"serialized\": \"CgJyeBDq2qHtBTIJL2Vvcy9kZXYvSiRiYWVjYjYxOC1mMGU0LTExZTktODVkOS1mYTE2M2ViNmI2Y2ZSnAFbVGh1IE9jdCAxNyAxNTo0Nzo1OSAyMDE5XSB1aWQ6MFtyb290XSBnaWQ6MFtyb290XSB0aWRlbnQ6cm9vdC4xMzgwOToxMDdAbG9jYWxob3N0IG5hbWU6ZGFlbW9uIGRuOiBwcm90OnNzcyBob3N0OmxvY2FsaG9zdCBkb21haW46bG9jYWxkb21haW4gZ2VvOmFqcCBzdWRvOjE=\",\n  \"seed\": 1399098912\n  }\n\n\nEssentially this token gives the bearer the permission to `rwx` for the\nfile /eos/dev/token. The token might not bear any owner or group\ninformation, which means that the creations will be accounted on the\nmapped authenticated user using this token or an enforced\n`sys.owner.auth` entry. If the token should map the authenticated user,\none can add `owner` and `group` fields. In practical terms the token\nremoves existing user and system ACL entries and places the token\nuser/group/permission entries as a system ACL. If a user creates a token,\nthe `owner` and `group` fields are always added. To have 'dynamic' user tokens, you need to take the `root` role.\n\nTokens are signed, zlib compressed, base64url encoded with a replacement\nof the `+` and `/` characters with `-` and `_` and a URL\nencoding of the `=` character to avoid collision with\ndirectory and file names.\n\nThe `voucher` field is tagged on the file when a file has been created\nand is also used as the logging id for this file upload. The `requester`\nfield reports when, by whom and how a token has been generated.\n\n.. index::\n   pair: EOS Tokens; Issuing\n\nEnabling Token Issuing\n^^^^^^^^^^^^^^^^^^^^^^^\n\nTo enable issuing of tokens, the space configuration value\n`token.generation` has to be set unequal to 0.\n\n.. code-block:: bash\n\n   eos space config default space.token.generation=1\n\n\nBy default the signing key is derived from the instance sss keytab. If\nyou want to define your own signature key, you can point to a file\ncontaining the key in **/etc/sysconfig/eos_env**:\n\n.. code-block:: bash\n\n   EOS_MGM_TOKEN_KEYFILE=/etc/eos/token.key\n\n\nThe token key file must be owned by the daemon user and have 400\npermission!\n\nToken creation\n^^^^^^^^^^^^^^\n\nThe CLI interface to create a token is shown here:\n\n.. code-block:: bash\n\n   # create a generic read-only token for a file valid 5 minutes\n   EXPIRE=`date +%s; let LATER=$EXPIRE+300\n\n   eos token --path /eos/myfile --expires $LATER\n   zteos64:MDAwMDAwNzR4nONS4WIuKq8Q-Dlz-ltWI3H91Pxi~cSsAv2S~OzUPP2SeAgtpMAY7f1e31Ts-od-rgcLZ~a2~bhwcZO9cracyhm1b3c6jpRIEWWOws71Ox6xAABeTC8I\n\n   # create a generic read-only token for a directory - mydir has to end with a '/' - valid 5 minutes\n   eos token --path /eos/mydir/ --expires $LATER\n\n   # create a generic read-only token for a directory tree - mytree has to end with a '/' - valid 5 minutes\n   eos token --path /eos/mydir/ --tree --expires $LATER\n\n   # create a generic write token for a file - valid 5 minutes\n   eos token --path /eos/myfile --permission rwx --expires $LATER\n\n\n.. index::\n   pair: EOS Tokens; Inspection\n\nToken inspection\n^^^^^^^^^^^^^^^^^^\n\nThe CLI interface to show the contents of a token is shown here:\n\n.. code-block:: bash\n\n  eos token --token zteos64:MDAwMDAwNzR4nONS4WIuKq8Q-Dlz-ltWI3H91Pxi_cSsAv2S_OzUPP2SeAgtpMAY7f1e31Ts-od-rgcLZ_a2_bhwcZO9cracyhm1b3c6jpRIEWWOws7\n\n  TOKEN=\"zteos64:MDAwMDAwNzR4nONS4WIuKq8Q-Dlz-ltWI3H91Pxi_cSsAv2S_OzUPP2SeAgtpMAY7f1e31Ts-od-rgcLZ_a2_bhwcZO9cracy\"\n\n  env EOSAUTHZ=$TOKEN eos whoami\n  Virtual Identity: uid=0 (99,3,0) gid=0 (99,4,0) [authz:unix] sudo* host=localhost domain=localdomain geo-location=ajp\n  {\n  \"token\": {\n    \"permission\": \"rx\",\n    \"expires\": \"1600000000\",\n    \"owner\": \"root\",\n    \"group\": \"root\",\n    \"generation\": \"1\",\n    \"path\": \"/eos/myfile\",\n    \"allowtree\": false,\n    \"origins\": []\n  },\n  ...\n\n.. index::\n   pair: EOS Tokens; Usage\n\nToken usage\n^^^^^^^^^^^\n\nA file token can be used in two ways:\n\n* as a filename\n* via CGI ```?authz=$TOKEN```\n\n.. code-block:: bash\n\n\n   # as a filename\n   xrdcp root://myeos//zteos64:MDAwMDAwNzR4nONS4WIuKq8Q-Dlz-ltWI3H91Pxi_cSsAv2S_OzUPP2SeAgtpMAY7f1e31Ts-od-rgcLZ_a2_bhwcZO9cracy /tmp/\n\n   # via CGI\n   xrdcp \"root://myeos//eos/myfile?authz=zteos64:MDAwMDAwNzR4nONS4WIuKq8Q-Dlz-ltWI3H91Pxi_cSsAv2S_OzUPP2SeAgtpMAY7f1e31Ts-od+rgcLZ_a2_bhwcZO9cracy\" /tmp/\n\n\nIf a token contains a subtree permission, the only way to use it for\nfile access is to use the CGI form. The filename form is practical to\nhide the filename for up-/downloads.\n\n.. index::\n   pair: EOS Tokens; Permissions\n\nToken issuing permission\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe `root` user can issue any token. Everybody else can only issue\ntokens for files in existing parent directories or directory trees,\nwhere the calling user is the current owner.\n\n.. index::\n   pair: EOS Tokens; Lifetime\n\nToken lifetime\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe token lifetime is given as a UNIX timestamp during the token\ncreation.\n\n.. index::\n   pair: EOS Tokens; Revoaction\n\nToken Revocation\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTokens are issued with a generation entry. The generation value is a\nglobally configured 64-bit unsigned number. In case of emergency all\ntokens can be revoked by increasing the generation value. The generation\nvalue is configured via the key `token.generation` in the default space.\n\n.. code-block:: bash\n\n   # change the generation value\n   eos config default space.token.generation=256\n\n   # show the generation value\n   eos space status default | grep token.generation\n   token.generation                 := 256\n\n.. index::\n   pair: EOS Tokens; Origin Restrictions\n\nToken Origin Restrictions\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe client location from where a token can be used can be restricted by\nusing the `origins` entries.\n\n.. code-block:: bash\n\n   # general syntax is a regexp for origin like <regexp hostname>#<regexp username>#<regexp auth protocol>\n   # all machines at CERN authenticating via kerberos as user nobody\n   eos token --path /eos/myfile --origin \".*.cern.ch:nobody#krb5\"\n\n   # all machines at CERN authenticating via unix as user kubernetes from machine k8s.cern.ch\n   eos token --path /eos/myfile --origin \"k8s.cern.ch#kubernetes#unix\"\n\n\nThe default origin regexp is `.*#.*#.*` accepting all origins. If the\nregex is invalid, the command will return with an error message.\n\n.. index::\n   pair: EOS Tokens; GRPC\n\nMulti-path Tokens\n^^^^^^^^^^^^^^^^^\n\nEOS supports not multipath-tokens which carry the same owner/group and ACLs for several distinct paths.\n\nTo issue a multipath token you concatenate your paths using '://:' as a delimiter e.g.\n\n.. code-block:: bash\n\n   # create a token for a generic EOS path and to call the Tape Rest API endpoitn\n\n   eos token --path /eos/://:/api/\n\n   # allow 'rwx' in /eos/user/ and /eos/group/ to 1234:1234\n   eos token --path /eos/user/://:/eos/group/ --permission rwx --owner 1234 --group 1234\n\n.. index::\n   pair: EOS Tokens; Multi-path Token\n\n\n\n\nToken Mapping\n^^^^^^^^^^^^^\n\nThe `tokensudo` functionality can be configured on space level. The purpose is to define, which connections are allowed to use tokens and apply the owner/group information (if embedded in the token). The default is `always`.\n\n.. code-block:: bash\n\n   eos vid tokensudo always|strong|encrypted|never`\n\n- `always` - identity in the token is always taken into account (all auth protocols)\n- `strong` - identity in the token is not taken into account for unix authenticated clients (all but linux))\n- `encrypted` - identity in the token is only taken into account for encrypted connections (https,grpc,ztn,sss)\n- `never` - identity in the token is never taken into account (never assimilate the identity from the token)\n\nToken via GRPC\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTokens can be requested and verified using GRPC TokenRequest as shown\nhere with the GRPC CLI. To request a token at least `path`, `expires`\nand `permission` should be defined.\n\n.. code-block:: bash\n\n    [root@dev mgm]# eos-grpc-ns --acl rwx -p /eos/dev/xrootd token\n    request:\n    {\n    \"authkey\": \"\",\n    \"token\": {\n      \"token\": {\n      \"token\": {\n        \"permission\": \"rwx\",\n        \"expires\": \"1571226882\",\n        \"owner\": \"root\",\n        \"group\": \"root\",\n        \"generation\": \"0\",\n        \"path\": \"/eos/dev/xrootd\",\n        \"allowtree\": false,\n        \"vtoken\": \"\",\n        \"origins\": []\n      },\n      \"signature\": \"\",\n      \"serialized\": \"\",\n      \"seed\": 0\n      }\n    }\n    }\n\n    reply:\n    {\n    \"error\": {\n      \"code\": \"0\",\n      \"msg\": \"zteos64:MDAwMDAwODR4nOPS4WIuKq8QaOqa85ZVii0vPyk_pVIJShvx66fmF-snZhXoVxTl55ekCCk8KMu4qK4Z7_jNTmF5u0_z5hP1J97v3K3G29cid0O4gv-5FEnmKUyavGstGwCiYjHe\"\n    }\n    }\n\n    request took 6226 micro seconds\n\n\nTo verify a token, the `vtoken` field should hold the token to decode.\n\n.. code-block:: bash\n\n\n    [root@dev mgm]# eos-grpc-ns --ztoken zteos64:MDAwMDAwODR4nOPS4WIuKq8QaOqa85ZVii0vPyk_pVIJShvx66fmF-snZhXoVxTl55ekCCk8KMu4qK4Z7_jNTmF5u0_z5hP1J97v3K3G29cid0O4gv-5FEnmKUyavGstGwCiYjHe token\n    request:\n    {\n    \"authkey\": \"\",\n    \"token\": {\n      \"token\": {\n      \"token\": {\n      \"permission\": \"rx\",\n        \"expires\": \"1571226893\",\n        \"owner\": \"root\",\n        \"group\": \"root\",\n        \"generation\": \"0\",\n        \"path\": \"\",\n        \"allowtree\": false,\n        \"vtoken\": \"zteos64:MDAwMDAwODR4nOPS4WIuKq8QaOqa85ZVii0vPyk_pVIJShvx66fmF-snZhXoVxTl55ekCCk8KMu4qK4Z7_jNTmF5u0_z5hP1J97v3K3G29cid0O4gv-5FEnmKUyavGstGwCiYjHe\",\n        \"origins\": []\n      },\n      \"signature\": \"\",\n      \"serialized\": \"\",\n      \"seed\": 0\n      }\n    }\n    }\n\n    reply:\n    {\n    \"error\": {\n    \"code\": \"0\",\n    \"msg\": \"{\\n \\\"token\\\": {\\n  \\\"permission\\\": \\\"rwx\\\",\\n  \\\"expires\\\": \\\"1571321093\\\",\\n  \\\"owner\\\": \\\"root\\\",\\n  \\\"group\\\": \\\"root\\\",\\n  \\\"generation\\\": \\\"0\\\",\\n  \\\"path\\\": \\\"/eos/dev/xrootd\\\",\\n  \\\"allowtree\\\": false,\\n  \\\"vtoken\\\": \\\"\\\",\\n  \\\"voucher\\\": \\\"6496c338-f0e6-11e9-b81d-fa163eb6b6cf\\\",\\n  \\\"requester\\\": \\\"[Thu Oct 17 15:59:53 2019] uid:99[nobody] gid:99[nobody] tident:.1:46602@[:1] name: dn: prot:grpc host:[:1] domain:localdomain geo:cern sudo:0\\\",\\n  \\\"origins\\\": []\\n },\\n \\\"signature\\\": \\\"2B8qIUfJ6rTusI2NFXKH70AoXZ55wKUUDijFCK3e2bY=\\\",\\n \\\"serialized\\\": \\\"CgNyd3gQheqh7QUaBm5vYm9keSIGbm9ib2R5Mg8vZW9zL2FqcC94cm9vdGRKJDY0OTZjMzM4LWYwZTYtMTFlOS1iODFkLWZhMTYzZWI2YjZjZlKNAVtUaHUgT2N0IDE3IDE1OjU5OjUzIDIwMTldIHVpZDo5OVtub2JvZHldIGdpZDo5OVtub2JvZHldIHRpZGVudDouMTo0NjYwMkBbOjFdIG5hbWU6IGRuOiBwcm90OmdycGMgaG9zdDpbOjFdIGRvbWFpbjpsb2NhbGRvbWFpbiBnZW86YWpwIHN1ZG86MA==\\\",\\n \\\"seed\\\": 844966647\\n}\\n\"\n    }\n    }\n\n\nThe possible return codes are:\n\n.. epigraph::\n\n   ============== ============================================================\n   Error          Meaning\n   ============== ============================================================\n   `-EINVAL`      the token cannot be decompressed\n   `-EINVAL`      the token cannot be parsed\n   `-EACCES`      the generation number inside the token is not valid anymore\n   `-EKEYEXPIRED` the token validity has expired\n   `-EPERM`       the token signature is not correct\n   ============== ============================================================\n\n.. index::\n   pair: EOS Tokens; Tokens over SSS\n\nUsing tokens with SSS security\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIt is very useful to issue scoped tokens to applications. To avoid the\ncomplication of appending tokens to each and every URL one can use `sss`\nsecurity to forward a generic token for each request via the\n`endorsement` environment variable.\n\nClient and server should share an sss key for a user, which is actually\nnot authorized to use the instance e.g.\n\n\n.. code-block:: bash\n\n    ############################\n    # client\n    ############################\n    echo 0 u:nfsnobody g:nfsnobody n:eos-test N:5506672669367468033 c:1282122142 e:0 k:0123456789012345678901234567890123456789012345678901234567890123 > $HOME/.eos.keytab\n    # point to keytab file\n    export XrdSecSSSKT=$HOME/.eos.keytab\n    # enforce sss\n    export XrdSecPROTOCOL=sss\n\n    ############################\n    #server\n    ############################\n\n    # server shares the same keytab entry\n    echo 0 u:nfsnobody g:nfsnobody n:eos-test N:5506672669367468033 c:1282122142 e:0 k:0123456789012345678901234567890123456789\\012345678901234567890123 >> /etc/eos.keytab\n\n    # server bans user nfsnobody or maybe uses already user allow, which bans this user by default\n    eos access ban user nfsnobody\n\n    # server issues a scoped token binding to a user/group\n    TOKEN=`eos token --path /eos/cms/www/ --permission rwx --expires 1600000000 --owner cmsprod --group zh`\n\n    ############################\n    # client\n    ############################\n\n    # exports the token in the environment\n    export XrdSecsssENDORSEMENT=zteos64:....\n\n    # test the ID\n    eos whoami\n    Virtual Identity: uid=5410 (65534,99,5410) gid=1339 (65534,99,1338) [authz:sss] host=localhost domain=localdomain geo-location=dev key=zteos64:....\n    {\n      \"token\": {\n      \"permission\": \"rwx\",\n      \"expires\": \"1000000000\",\n      \"owner\": \"cmsprod\",\n      \"group\": \"zh\",\n      \"generation\": \"0\",\n      \"path\": \"/eos/cms/www/\",\n      \"allowtree\": false,\n      \"vtoken\": \"\",\n      \"origins\": []\n    },\n    }\n\n.. index::\n   pair: EOS Tokens; Token /eos access\n\nUsing tokens for scoped eosxd access\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAs a user you can create a token e.g. for applications like CIs,\nwebservices etc. if the EOS instance is configured to issue tokens.\n\nTo create a token as a user you do:\n\n.. code-block:: bash\n\n   eos token --path /eos/user/f/foo/ci/ --expires 1654328760 --perm rwx --tree\n\n\nIf you create a token as a user, the token puts the calling role as the\nidentity into the token.\n\nYou can inspect your token to verify that it contains what you want\nusing:\n\n.. code-block:: bash\n\n   eos token --token zteos64:...\n\n\nFinally to use the token on a mount client you define only the following\nvariable:\n\n.. code-block:: bash\n\n    # put the token into your client environment\n    export XrdSecsssENDORSEMENT=zteos64:...\n\n    # you should now have rwx permission on this tree\n    ls /eos/user/f/foo/ci/\n\n\n.. index::\n   pair: EOS Tokens; Token ZTN auth\n\nUsing EOS tokens via ZTN authentication\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nSince EOS 5.1.15 it is possible to configure ZTN authentication in EOS and use this as a token transport mechanism. This is simpler than using `sss` authentication and distributing shared secrets. However the MGM configuration needs to disable token validation for the default SciToken library setting:\n\n.. code-block:: bash\n\n   sec.protocol ztn -tokenlib none\n\nThe XRootD client picks up tokens as documented under Section `2.5.7.1 Default token discovery mechanism and augmentation <https://xrootd.slac.stanford.edu/doc/dev56/sec_config.htm#_Toc119617461>`_\n\nThe support for ZTN token transport and eosxd was added with EOS version 5.2.\n\n\n.. index::\n   pair: Tokens; OAUTH2\n\n\nOAUTH2 for authentication\n-------------------------------\n\nTo enable OAUTH2 token translation, one has to configure the resource endpoint and enable OAUTH2 mapping:\n\n.. code-block:: bash\n\n   # enable oauth2 mapping\n   eos vid enable oauth2\n   # allow an oauth2 resource in requests\n   eos vid set map -oauth2 key:oauthresource.web.cern.ch/api/User vuid:0\n   # allow an oauth2 resource in requests (OIDC infrastructure)\n   eos vid set map -oauth2 key:auth.cern.ch/auth/realms/cern/protocol/openid-connect/userinfo vuid:0\n\nIf you want to check the audience claim in the ticket, you can add the audience to screen to each OAUTH2 resource:\n\n.. code-block:: bash\n\n   # allow on oauth2 resource in request for the audience 'eosoauth'\n   eos vid set map -oauth2 key:auth.cern.ch/auth/realms/cern/protocol/openid-connect/userinfo@eosatuch vuid:0\n\nIf you want to use a local account which is mapped in the instance to a local uid, you can define a 'sub' field mapping entry using:\n\n.. code-block:: bash\n\n   # remap the sub '7aa5167f-9c28-4336-8a66-af9145ea847d' to the local user id 1000\n   eos vid set map -oauth2 sub:7aa5167f-9c28-4336-8a66-af9145ea847d vuid:1000\n\n\nAll XRootD based clients can add the OAUTH2 token in the endorsement environment variable for sss authentication.\n\n.. code-block:: bash\n\n   XrdSecsssENDORSEMENT=oauth2:<access_token>:<oauth-resource>\n\nOAUTH2 is enabled by default, but can be explicitly enabled or disabled:\n\n.. code-block:: bash\n\n   # eos CLI/xrdcp etc.\n   env XrdSecPROTOCL=sss\n   env XrdSecsssENDORSEMENT=oauth2:...\n   eos whoami\n\n   # eosxd config file parameter\n\n   \"auth\" : {\n     \"oauth2\" : 1, #default\n     \"ssskeytab\" : \"/etc/eos/fuse.sss.keytab\", #default\n    }\n\n    export OAUTH2_TOKEN=FILE:/tmp/oauthtk_1000\n    # /tmp/oauthtk_1000 contains oauth2:<token>:<oauth-url>\n    ls /eos/\n\nOne has to supply an sss key for this communication, however the sss key user should be banned on the instance as a security precaution.\nClient and server should share an sss key for a user, which is actually not authorized to use the instance e.g.\n\n.. code-block:: bash\n\n   ############################\n   # client\n   ############################\n   echo 0 u:nfsnobody g:nfsnobody n:eos-test N:5506672669367468033 c:1282122142 e:0 k:0123456789012345678901234567890123456789012345678901234567890123 > $HOME/.eos.keytab\n   # point to keytab file\n   export XrdSecSSSKT=$HOME/.eos.keytab\n   # enforce sss\n   export XrdSecPROTOCOL=sss\n\n   ############################\n   #server\n   ############################\n\n   # server shares the same keytab entry\n   echo 0 u:nfsnobody g:nfsnobody n:eos-test N:5506672669367468033 c:1282122142 e:0 k:0123456789012345678901234567890123456789\\012345678901234567890123 >> /etc/eos.keytab\n\n   # server bans user nfsnobody or maybe uses already user allow, which bans this user by default\n   eos access ban user nfsnobody\n\n   ############################\n   # client\n   ############################\n\n   # exports the token in the environment\n   export XrdSecsssENDORSEMENT=oauth2:.....:auth.cern.ch/auth/realms/cern/protocol/openid-connect/userinfo\n\n   # test the ID\n   [ ~]$ eos whoami\n   Virtual Identity: uid=1234 (1234,65534,99) gid=1234 (1234,99) [authz:oauth2] host=localhost domain=localdomain geo-location=cern key=<oauth2> fullname='Foo Bar' email='foo.bar@cern.ch'\n\n\n.. index::\n   pair: File; Versioning\n   pair: Using; Versioning\n\nVOMS Role Mapping\n-------------------------------\n\nA VOMS proxy uses X509 extensions which are signed by a VOMS server to attach roles to a proxy certificate. The extraction process on EOS verifies the signatures of these extension using the local VOMS configuration. The roles defined by the extensions can be mapped inside EOS using the *vid* interface. In the following we show a VOMS configuration for the CMS experiment as an example.\n\nInstalling VOMS configuration for CMS\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code-block:: bash\n\n   yum localinstall https://linuxsoft.cern.ch/wlcg/centos7/x86_64/wlcg-iam-lsc-cms-2.0.0-1.el7.noarch.rpm -y --nogpgcheck\n   yum localinstall https://linuxsoft.cern.ch/wlcg/centos7/x86_64/wlcg-voms-cms-2.0.0-1.el7.noarch.rpm -y --nogpgcheck\n\nConfiguring VOMS role extraction\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nVOMS extraction is configured as a GSI protocol configuration option in the MGM configuration file adding `-vomsat:1 -vomsfun:default` to `sec.protocol gsi`:\n\n.. code-block:: bash\n\n  sec.protocol  gsi -crl:1 -moninfo:1 -cert:/etc/grid-security/daemon/hostcert.pem -key:/etc/grid-security/daemon/hostkey.pem -gridmap:/etc/grid-security/grid-mapfile -d:1 -gmapopt:1 -vomsat:1 -vomsfun:default\n\n\nConfiguring mappings using the CLI\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n.. code-block:: bash\n\n   eos vid set map -voms /cms:production vuid:`id -u cmsprod` vgid:`id -g cmsprod`\n   eos vid set map -voms /cms:cmsprod vuid:`id -u cmsprod` vgid:`id -u cmsprod`\n   eos vid set map -voms /cms:t1production vuid:`id -u cmsprod` vgid:`id -u cmsprod`\n   eos vid set map -voms /cms/muon:production vuid:`id -u cmsprod` vgid:`id -u cmsprod`\n   eos vid set map -voms /cms:cmsphedex vuid:`id -u cmsprod` vgid:`id -u cmsprod`\n   eos vid set map -voms /cms:lcgadmin vuid:`id -u cmssam` vgid:`id -u cmssam`\n   eos vid set map -voms /cms/uscms:lcgadmin vuid:`id -u cmssam` vgid:`id -u cmssam`\n   eos vid set map -voms /cms:pilot vuid:`id -u cmspilot` vgid:`id -u cmspilot`\n   eos vid set map -voms /cms:uscms:pilot vuid:`id -u cmspilot` vgid:`id -u cmspilot`\n   eos vid set map -voms /cms:priorityuser vuid:`id -u cmsuser` vgid:`id -u cmsuser`\n   eos vid set map -voms /cms:hiproduction vuid:`id -u cmsuser` vgid:`id -u cmsuser`\n   eos vid set map -voms /cms: vuid:`id -u cmsuser` vgid:`id -u cmsuser\n\n\nConfiguring gridmap from IAM\n----------------------------\n\nIn case the deployment needs to rely on ``/etc/grid-security/gridmapfile``,\nthese can still be generated from the IAM portal using ``eos-iam-mapfile``\nscript, which is shipped as a part of ``eos-server`` packages.\n\n.. code-block:: bash\n\n   eos-iam-mapfile -h\n   usage: eos-iam-mapfile [-h] [-v [{DEBUG,INFO,WARNING,ERROR,CRITICAL}]] [-s SERVER CLIENT_ID CLIENT_KEY] [-c CONFIG] [-t TARGETS] [-i IFILE] [-o OFILE] [-l LGRIDMAP] [-a ACCOUNT] [-p PATTERN] [-C] [-u] [-f [{MAPFILE,GRIDMAP}]] [--cleanup]\n                       [--log-file LOG_FILE]\n\n   GRID Map file generation from IAM Server\n\n\nFor testing the script one needs a client-id and a client-key which has ``scim:read`` permissions.\n\n\n.. code-block:: bash\n\n   $ echo -e '[myiamserver.ch]\\nclient-id=client-1234-uuid\\nclient-secret=client-secret123\\naccount=acc4usermapping' > myiam.conf\n   $ eos-iam-mapfile -c iam.conf\n   \"/DC=ch/DC=cern/..CN=testuser1\" acc4usermapping\n\n\nConfiguring eos-iam-mapfile\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n``eos-iam-mapfile`` can be configured via a simple INI file: a ``[DEFAULT]``\nsection takes global parameters, multiple IAM server sections can follow, which\nwill be evaluated in the order which they appear in the config. A\n``localgridmap`` can be configured which will add & override DNs in the\nlocalgridmap in case they are already mapped from previous IAM server outputs.\n\nAn example configuration is given below. Note that these are **NOT** the default config options in case the script isn't configured. The only default is ``log_level``  which is set to WARNING level.\n\n.. code-block:: bash\n\n   [DEFAULT]\n   cleanup = True # Cleanup intermediate temporary files\n   localgridmap = /etc/localgridmap.conf,/etc/localgridmap2.conf\n   log_level = WARNING # choose between DEBUG,INFO,WARNING,ERROR,CRITICAL\n   log_file = /var/log/eos/grid/eos-iam-map.log\n\n   [myiamserver1.ch]\n   client-id = client-uuid1\n   client-secret = secret123\n   account = account1\n\n\n   [myiamserver2.ch]\n   client-id = client-uuid2\n   client-secret = secret234\n   account = account2\n\nFile Versioning\n---------------\n\nFile versioning can be triggered as a per-directory policy using the extended attribute ``sys.versioning=<n>`` or via the ``eos file version`` command.\n\nThe parameter ``<n>`` in the extended attribute describes the maximum number of versions which should be kept according to a FIFO policy. In addition, there are 11 predefined timebins, for which additional versions exceeding the versioning parameter ``<n>`` are kept.\n\nVersions are kept in a hidden directory (visible with ``ls -la``) which is composed by ``.sys.v#.<basename>``\n\n.. code-block:: bash\n\n   eos ls -la\n   drwxrwxrwx   1 root     root                0 Aug 29 15:33 .sys.v#.myfile\n   -rw-r-----   1 root     root             1824 Aug 29 15:33 myfile\n\nThe 11 time bins are defined as follows:\n\n.. index::\n   pair: Versioning; Timebins\n\n.. epigraph::\n\n   ============= ===================================================\n   bin           deletion policy\n   ============= ===================================================\n   age<1d        the first version entering this bin survives\n   1d<=age<2d    the first version entering this bin survives\n   2d<=age<3d    the first version entering this bin survives\n   3d<=age<4d    the first version entering this bin survives\n   4d<=age<5d    the first version entering this bin survives\n   5d<=age<6d    the first version entering this bin survives\n   6d<=age<1w    the first version entering this bin survives\n   1w<=age<2w    the first version entering this bin survives\n   2w<=age<3w    the first version entering this bin survives\n   3w<=age<1mo   the first version entering this bin survives\n   ============= ===================================================\n\n\n.. index::\n   pair: Versioning; Automatic Versioning\n\nConfiguration of automatic versioning\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nConfigure each directory which should apply versioning using the extended attribute ``sys.versioning``:\n\n.. code-block:: bash\n\n   # force 10 versions (FIFO)\n   eos attr set sys.versioning=10 version-dir\n\n   # upload initial file\n   eos cp /tmp/file /eos/version-dir/file\n\n   # versions are created on the fly with each upload - now 1 version\n   eos cp /tmp/file /eos/version-dir/file\n\n   # versions are created on the fly with each upload - now 2 versions\n   eos cp /tmp/file /eos/version-dir/file\n\n\n.. index::\n   pair: Versioning; Creating\n\nCreating new versions\n^^^^^^^^^^^^^^^^^^^^^\n\n.. code-block:: bash\n\n   # force a new version\n   eos file version myfile\n\n   # force a new version with max 5 versions\n   eos file version myfile 5\n\n.. index::\n   pair: Versioning; Listing\n\nList existing versions\n^^^^^^^^^^^^^^^^^^^^^^\n\n.. code-block:: bash\n\n   eos file versions myfile\n   -rw-r-----   1 root     root             1824 Aug 29 15:17 1567084675.0014ede6\n   -rw-r-----   1 root     root             1824 Aug 29 15:33 1567085591.0014ede7\n   -rw-r-----   1 root     root             1824 Aug 29 15:33 1567085591.0014ede8\n   -rw-r-----   1 root     root             1824 Aug 29 15:33 1567085592.0014ede9\n\n\n.. index::\n   pair: Versioning; Purging Versions\n\nPurging existing versions\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code-block:: bash\n\n   # remove all versions\n   eos file purge myfile 0\n\n   # keep 5 versions (FIFO)\n   eos file purge myfile 5\n\n\n.. index::\n   pair: File; Erasure Coding\n   pair: Using; RAIN\n   pair: Using; Erasure Coding\n   pair: EC; Erasure Coding\n\nRAIN - Erasure Coding\n---------------------\n\n.. index::\n   pair: EC; RAIN Layout DbMapTypes\n\n\nECC Layout Types\n^^^^^^^^^^^^^^^^\n\nEOS supports five types of RAIN layouts:\n\n.. epigraph::\n\n   ========== ============= ================================ ====================================\n   name       redundancy    algorithm                        description\n   ========== ============= ================================ ====================================\n   raid5      N+1           single parity raid               can lose 1 disk without data loss\n   raiddp     4+2           dual parity raid                 can lose 2 disks without data loss\n   raid6      N+2           Erasure Code (Jerasure library)  can lose 2 disks without data loss\n   archive    N+3           Erasure Code (Jerasure library)  can lose 3 disks without data loss\n   qrain      N+4           Erasure Code (Jerasure library)  can lose 4 disks without data loss\n   ========== ============= ================================ ====================================\n\nThe layout is set in a namespace tree via ``eos attr -r set default=<name> <tree>``.\n\nThe minimum number of stripes is currently 6 for all erasure coding layouts (raiddp, raid6, archive, qrain).\n\nThe default layout can be defined using default space policies.\n\n\n.. index::\n   pair: FUSE; eosxd\n   pair: Using; FUSE Mounting\n   pair: Using; eosxd\n\nFUSE - mounting EOS with eosxd\n------------------------------\n\n**eosxd** is the executable responsible for running the EOS FUSE mount. FUSE (Filesystem in Userspace) is a software interface for Unix and Unix-like computer operating systems that lets non-privileged users create their own file systems without editing kernel code. This is achieved by running file system code in user space while the FUSE module provides only a bridge to the actual kernel interfaces.\n\nEOS supports **eosxd** based on `libfuse2` and `libfuse3`. `libfuse3` has some additional bulk interfaces and performance improvements compared to `libfuse`.\nThe executable for `libfuse3` is called **eosxd3**.\n\n.. warning:: To have eosxd working properly with many writers you have to modify the MGM configuration file /etc/xrd.cf.mgm to configure the nolock option: 'all.export / nolock'\n\n\nThere are two FUSEx client modes available:\n\n.. epigraph::\n\n   =========== ================ ===============================================================\n    daemon     daemon user-id   description\n   =========== ================ ===============================================================\n   eosxd       !root            An end-user private mount which is not shared between users\n   eosxd       root             A system-wide mount shared between users\n   =========== ================ ===============================================================\n\n.. index::\n   pair: FUSE; Mounting by hostname\n\n\nMounting an EOS instance\n^^^^^^^^^^^^^^^^^^^^^^^^\n\nMounting by hostname\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\nThe easiest way to mount EOS is to do:\n\n.. code-block:: bash\n\n   mkdir -p /eos/\n   eosxd -ofsname=myeos.cern.ch:/eos/ /eos/\n\nor using `mount`:\n\n.. code-block:: bash\n\n   mount -t fuse eosxd /eos/\n\n.. index::\n   pair: FUSE; Mounting by name\n\nMounting with configuration files\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\nThe configuration file name for an unnamed instance is /etc/eos/fuse.conf.\nTo mount an unnamed instance you do:\n\n.. code-block:: bash\n\n   eosxd /eos/\n\nor using `mount`:\n\n.. code-block:: bash\n\n   mount -t fuse eosxd -ofsname=myeos.cern.ch:/eos/ /eos/\n\n\nThe configuration file for a named instance is `/etc/eos/fuse.<name>.conf`.\n\nYou can select a named instance adding `-ofsname=<name>` to the argument list.\n\n.. index::\n   pair: FUSE; http-configuration_\n\nConfiguration File Syntax\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\n\nThe default configuration parameters are shown here:\n\n.. code-block:: bash\n\n    {\n      \"name\" : \"\",\n      \"hostport\" : \"localhost:1094\",\n      \"remotemountdir\" : \"/eos/\",\n      \"localmountdir\" : \"/eos/\",\n      \"statisticfile\" : \"stats\",\n      \"mdcachedir\" : \"/var/cache/eos/fusex/md\",\n      \"mdzmqtarget\" : \"tcp://localhost:1100\",\n      \"mdzmqidentity\" : \"eosxd\",\n      \"appname\" : \"\",\n      \"options\" : {\n        \"debug\" : 1,\n        \"debuglevel\" : 4,\n        \"jsonstats\" : 1,\n        \"backtrace\" : 1,\n        \"hide-versions\" : 1,\n        \"protect-directory-symlink-loops\" : 0,\n        \"md-kernelcache\" : 1,\n        \"md-kernelcache.enoent.timeout\" : 0,\n        \"md-backend.timeout\" : 86400,\n        \"md-backend.put.timeout\" : 120,\n        \"data-kernelcache\" : 1,\n        \"rename-is-sync\" : 1,\n        \"rmdir-is-sync\" : 0,\n        \"global-flush\" : 0,\n        \"flush-wait-open\" : 1, // 1 = flush waits for open when updating - 2 = flush waits for open when creating - 0 flush never waits\n        \"flush-wait-open-size\" : 262144 , // file size for which we force to wait that files are opened on FSTs\n        \"flush-wait-umount\" : 120, // seconds to wait for write-back data to be flushed out before terminating the mount - 0 disables waiting for flush\n        \"flush-nowait-executables\" : [ \"/tar\", \"/touch\" ],\n        \"global-locking\" : 1,\n        \"fd-limit\" : 524288,\n        \"no-fsync\" : [ \".db\", \".db-journal\", \".sqlite\", \".sqlite-journal\", \".db3\", \".db3-journal\", \"*.o\" ],\n        \"overlay-mode\" : \"000\",\n        \"rm-is-sync\" : 0,\n        \"rename-is-sync\" : 1,\n        \"rm-rf-protect-levels\" : 0,\n        \"rm-rf-bulk\" : 0,\n        \"show-tree-size\" : 0,\n        \"cpu-core-affinity\" : 0,\n        \"no-xattr\" : 0,\n        \"no-eos-xattr-listing\" : 0,\n        \"no-link\" : 0,\n        \"nocache-graceperiod\" : 5,\n        \"leasetime\" : 300,\n        \"write-size-flush-interval\" : 10,\n        \"submounts\" : 0,\n        \"inmemory-inodes\" : 16384\n      },\n      \"auth\" : {\n        \"shared-mount\" : 1,\n        \"krb5\" : 1,\n        \"gsi-first\" : 0,\n        \"sss\" : 1,\n        \"ssskeytab\" : \"/etc/eos/fuse.sss.keytab\",\n        \"oauth2\" : 1,\n        \"unix\" : 0,\n        \"unix-root\" : 0,\n        \"ignore-containerization\" : 0,\n        \"credential-store\" : \"/var/cache/eos/fusex/credential-store/\"\n        \"environ-deadlock-timeout\" : 100,\n        \"forknoexec-heuristic\" : 1\n      },\n      \"inline\" : {\n        \"max-size\" : 0,\n        \"default-compressor\" : \"none\"\n      },\n      \"fuzzing\" : {\n        \"open-async-submit\" : 0,\n        \"open-async-return\" : 0,\n        \"open-async-submit-fatal\" : 0,\n        \"open-async-return-fatal\" : 0,\n        \"read-async-submit\" : 0\n      }\n    }\n\nYou also need to define a local cache directory (location) where small files are cached and an optional journal directory to improve the write speed (journal):\n\n.. code-block:: bash\n\n    \"cache\" : {\n      \"type\" : \"disk\",\n      \"size-mb\" : 512,\n      \"size-ino\" : 65536,\n      \"journal-mb\" : 2048,\n      \"journal-ino\" : 65536,\n      \"clean-threshold\" : 85.0,\n      \"location\" : \"/var/cache/eos/fusex/cache/\",\n      \"journal\" : \"/var/cache/eos/fusex/journal/\",\n      \"read-ahead-strategy\" : \"dynamic\",\n      \"read-ahead-bytes-nominal\" : 262144,\n      \"read-ahead-bytes-max\" : 1048576,\n      \"read-ahead-blocks-max\" : 16,\n      \"read-ahead-sparse-ratio\" : 0.0,\n      \"max-read-ahead-buffer\" : 134217728,\n      \"max-write-buffer\" : 134217728\n    }\n\n\nThe available read-ahead strategies are `dynamic`, `static` or `none`. `dynamic` read-ahead doubles the read-ahead window from nominal to max if the strategy provides cache hits. The default is a dynamic read-ahead starting with 512kb and using 2,4,8,16 blocks resizing blocks up to 2M.\n\nThe daemon automatically appends a directory to the mdcachedir, location and journal path and automatically creates these directories with mode=700 owned by root.\n\nYou can modify some of the XrdCl variables, however it is recommended not to change these:\n\n.. code-block:: bash\n\n    \"xrdcl\" : {\n      \"TimeoutResolution\" : 1,\n      \"ConnectionWindow\": 10,\n      \"ConnectionRetry\" : 0,\n      \"StreamErrorWindow\" : 120,\n      \"RequestTimeout\" : 60,\n      \"StreamTimeout\" : 120,\n      \"RedirectLimit\" : 2,\n      \"LogLevel\" : \"None\"\n    },\n\nThe recovery settings are defined in the following section:\n\n.. code-block:: bash\n\n    \"recovery\" : {\n      \"read-open\" : 1,\n      \"read-open-noserver\" : 1,\n      \"read-open-noserver-retrywindow\" : 15,\n      \"write-open\" : 1,\n      \"write-open-noserver\" : 1,\n      \"write-open-noserver-retrywindow\" : 15,\n    }\n\n\nIt is possible to overwrite the settings of any standard config files using a second configuration file: /etc/eos/fuse.local.conf or/etc/eos/fuse.<name>.local.conf. This is useful to ship a standard configuration via a package and gives users the opportunity to change individual parameters.\n\n.. index::\n   pair: FUSE; configuration defaults\n\n\nConfiguration default values and avoiding configuration files\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nEvery configuration value has a corresponding default value. As explained the configuration file name is taken from the fsname option given on the command line:\n\n.. code-block:: bash\n\n    root> eosxd -ofsname=foo #loads /etc/eos/fuse.foo.conf\n    root> eosxd              #loads /etc/eos/fuse.conf\n\n    user> eosxd -ofsname=foo #loads $HOME/.eos/fuse.foo.conf\n\n\nOne can avoid using configuration files if the defaults are fine providing the remote host and remote mount directory via the fsname:\n\n.. code-block:: bash\n\n    root> eosxd -ofsname=eos.cern.ch:/eos/ $HOME/eos                  # mounts the /eos/ directory from eos.cern.ch shared under $HOME/eos/\n    user> eosxd -ofsname=user@eos.cern.ch:/eos/user/u/user/ $home/eos # mounts /eos/user/u/user from eos.cern.ch private under $HOME/eos/\n\n\nIf this is a user-private mount the syntax `foo@cern.ch` should be used to distinguish private mounts of individual users in the `df` output.\n\n.. NOTE:: Please note, that root mounts are by default shared mounts with kerberos configuration, user mounts are private mounts with kerberos configuration!\n\n.. index::\n   pair: FUSE; Log FIles\n\neosxd Logfile\n^^^^^^^^^^^^^\n\neosxd writes a logfile into the fusex log directory `/var/log/eos/fusex/fuse.<instancename>-<mountdir>.log` . The\ndefault verbosity is **warning** level.\n\n.. index::\n   pair: FUSE; Statistics File\n\neosxd Statistics file\n^^^^^^^^^^^^^^^^^^^^^\n\neosxd writes out a statistics file with an update rate of 1Hz into the\nfusex log directory `/var/log/eos/fusex/fuse.<instancename>-<mountdir>.stats` .\n\nHere is an example:\n\n.. code-block:: bash\n\n    ALL     Execution Time                   5.06 +- 16.69 = 5.01s (1270 ops)\n    # -----------------------------------------------------------------------------------------------------------------------\n    who     command                          sum             5s     1min     5min       1h exec(ms) +- sigma(ms)  = cumul(s)\n    # -----------------------------------------------------------------------------------------------------------------------\n    ALL     :sum                                     1271     0.00     0.05     0.01     0.00     -NA- +- -NA-       = 0.00\n    ALL     access                                      4     0.00     0.00     0.00     0.00  1.82825 +- 1.64279    = 0.01\n    ALL     create                                      0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     flush                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     forget                                      0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     fsync                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     getattr                                    17     0.00     0.02     0.00     0.00  1.91859 +- 6.93590    = 0.03\n    ALL     getxattr                                   58     0.00     0.03     0.01     0.00  2.42547 +- 18.15372   = 0.14\n    ALL     link                                        0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     listxattr                                   0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     lookup                                    342     0.00     0.00     0.00     0.00  0.78381 +- 3.70048    = 0.27\n    ALL     mkdir                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     mknod                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     open                                        0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     opendir                                   215     0.00     0.00     0.00     0.00 20.56853 +- 26.64452   = 4.42\n    ALL     read                                        0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     readdir                                   416     0.00     0.00     0.00     0.00  0.05781 +- 0.07550    = 0.02\n    ALL     readlink                                    1     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     release                                     0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     releasedir                                215     0.00     0.00     0.00     0.00  0.00896 +- 0.00425    = 0.00\n    ALL     removexattr                                 0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     rename                                      0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     rm                                          0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     rmdir                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     setattr                                     0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     setattr:chmod                               0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     setattr:chown                               0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     setattr:truncate                            0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     setattr:utimes                              0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     setxattr                                    1     0.00     0.00     0.00     0.00  0.08500 +- -NA-       = 0.00\n    ALL     statfs                                      2     0.00     0.00     0.00     0.00 57.74450 +- 48.80550   = 0.12\n    ALL     symlink                                     0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     unlink                                      0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    ALL     write                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-       = 0.00\n    # -----------------------------------------------------------------------------------------------------------\n    ALL        inodes              := 375\n    ALL        inodes stack        := 0\n    ALL        inodes-todelete     := 0\n    ALL        inodes-backlog      := 0\n    ALL        inodes-ever         := 3051\n    ALL        inodes-ever-deleted := 0\n    ALL        inodes-open         := 0\n    ALL        inodes-vmap         := 3051\n    ALL        inodes-caps         := 1\n    # -----------------------------------------------------------------------------------------------------------\n    ALL        threads             := 32\n    ALL        visze               := 517.10 Mb\n    ALL        rss                 := 35.63 Mb\n    ALL        pid                 := 1689\n    ALL        log-size            := 409384\n    ALL        wr-buf-inflight     := 0 b\n    ALL        wr-buf-queued       := 0 b\n    ALL        wr-nobuff           := 0\n    ALL        ra-buf-inflight     := 0 b\n    ALL        ra-buf-queued       := 0 b\n    ALL        ra-xoff             := 0\n    ALL        ra-nobuff           := 0\n    ALL        rd-buf-inflight     := 0 b\n    ALL        rd-buf-queued       := 0 b\n    ALL        version             := 4.4.17\n    ALL        fuseversion         := 28\n    ALL        starttime           := 1549548272\n    ALL        uptime              := 66989\n    ALL        total-mem           := 8201658368\n    ALL        free-mem            := 149671936\n    ALL        load                := 1313970496\n    ALL        total-rbytes        := 0\n    ALL        total-wbytes        := 0\n    ALL        total-io-ops        := 1270\n    ALL        read--mb/s          := 0.00\n    ALL        write-mb/s          := 0.00\n    ALL        iops                := 0\n    ALL        xoffs               := 0\n    ALL        instance-url        := myhost.cern.ch:1094\n    ALL        client-uuid         := 4af8154c-2ae1-11e9-8e32-02163e009ce2\n    ALL        server-version      := 4.4.17\n    ALL        automounted         := 0\n    ALL        max-inode-lock-ms   := 0.00\n    # -----------------------------------------------------------------------------------------------------------\n\n\nThe first block contains global averages/sums for total IO time and IO\noperations:\n\n.. epigraph::\n\n   =========== ================= ================= =================== ==============\n   tag         description       avg/dev in ms     cumulative time     sum IOPS\n   =========== ================= ================= =================== ==============\n   ALL         Execution Time    4.80 +- 15.56     4.87s               (1267 ops)\n   =========== ================= ================= =================== ==============\n\nThe second block contains counts for each filesystem operation, the\naverage rates in 5s, 1min, 5min and 1h windows, the average execution\ntime and standard deviation for a given filesystem operation, and\ncumulative seconds spent in each operation.\n\n.. epigraph::\n\n    ====== ========================== ============= ========= ========= ========== ========== =========== =============== ===============\n    who    filesystem counter name    sum of ops    5s avg    1m avg    5m avg   | 1h avg     avg(ms)     sigma(ms)       cumulative(s)\n    ====== ========================== ============= ========= ========= ========== ========== =========== =============== ===============\n    ALL    :sum                       1268          0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    access                     4             0.00      0.00      0.00       0.00       1.82825     +- 1.64279      0.01\n    ALL    create                     0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    flush                      0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    forget                     0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    fsync                      0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    getattr                    16            0.00      0.00      0.00       0.00       2.01987     +- 7.13716      0.03\n    ALL    getxattr                   56            0.00      0.00      0.00       0.00       0.02023     +- 0.00463      0.00\n    ALL    link                       0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    listxattr                  0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    lookup                     342           0.00      0.00      0.00       0.00       0.78381     +- 3.70048      0.27\n    ALL    mkdir                      0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    mknod                      0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    open                       0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    opendir                    215           0.00      0.00      0.00       0.00       20.5685     +- 26.64452     4.42\n    ALL    read                       0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    readdir                    416           0.00      0.00      0.00       0.00       0.05781     +- 0.07550      0.02\n    ALL    readlink                   1             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    release                    0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    releasedir                 215           0.00      0.00      0.00       0.00       0.00896     +- 0.00425      0.00\n    ALL    removexattr                0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    rename                     0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    rm                         0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    rmdir                      0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    setattr                    0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    setattr:chmod              0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    setattr:chown              0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    setattr:truncate           0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    setattr:utimes             0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    setxattr                   1             0.00      0.00      0.00       0.00       0.08500     +- -NA-         0.00\n    ALL    statfs                     2             0.00      0.00      0.00       0.00       57.7450     +- 48.80550     0.12\n    ALL    symlink                    0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    unlink                     0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ALL    write                      0             0.00      0.00      0.00       0.00       -NA-        +- -NA-         0.00\n    ====== ========================== ============= ========= ========= ========== ========== =========== =============== ===============\n\nThe third block displays inode related counts, which are explained\ninline.\n\n .. epigraph::\n\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    who   | counter name          | value            | description                                                                |\n    +==========+=======================+==================+============================================================================+\n    |    ALL   | inodes                | 375              | currently in-memory known-inodes                                           |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | inodes stack          | 0                | inodes which could be forgotten, but needed to be kept on the stack        |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | inodes-todelete       | 0                | inodes which still have to be deleted upstream                             |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | inodes-backlog        | 0                | inodes which still have to be updated upstream                             |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | inodes-ever           | 3051             | inodes ever seen by this mount                                             |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | inodes-ever-deleted   | 0                | inodes ever deleted by this mount                                          |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | inodes-open           | 0                | inodes associated with an open file descriptor                             |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | inodes-vmap           | 3051             | size of logical inode translation map                                      |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | inodes-caps           | 0                | inodes with a cache-callback subscription                                  |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | threads               | 32               | currently running threads                                                  |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | visze                 | 517.10 Mb        | virtual memory used by the running daemon                                  |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | rss                   | 35.13 Mb         | resident memory used by the running daemon                                  |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | pid                   | 1689             | process id of the running daemon                                           |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | log-size              | 367632           | size of the logfile of the running daemon                                  |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | wr-buf-inflight       | 0 b              | write buffer allocated with data in-flight in writing                      |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | wr-buf-queued         | 0 b              | write buffer allocated and kept on the queue for future reuse in writing   |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | wr-nobuff             | 0                | counter how often a \\'no available buffer\\' condition was hit in writing   |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | ra-buf-inflight       | 0 b              | read-ahead buffer allocated with data in-flight in read-ahead              |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | ra-buf-queued         | 0 b              | read-ahead buffer allocated and kept on the queue for future reuse in ra   |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | ra-xoff               | 0                | counter how often we needed to wait for an available read-ahead buffer     |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | ra-nobuff             | 0                | counter how often a \\'no available buffer\\' condition was hit in read-ahead|\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | rd-buf-inflight       | 0 b              | read buffer allocated with data in-flight for reading                      |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | rd-buf-queued         | 0 b              | read buffer allocated and kept on the queue for future reuse in reading    |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | version               | 4.4.17           | current version of the daemon                                              |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | fuseversion           | 28               | current version of the FUSE protocol                                       |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | starttime             | 1549548272       | starttime as unixtimestamp                                                 |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | uptime                | 64772            | run time of the daemon in seconds                                          |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | total-mem             | 8201658368       | total memory of the hosting machine                                        |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | free-mem              | 153280512        | free memory of the hosting machine                                         |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | load                  | 1313946976       | 1 minute load avg as returned by sysinfo                                   |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | total-rbytes          | 0                | total number of bytes read on this mount                                   |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | total-wbytes          | 0                | total number of bytes written on this mount                                |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | total-io-ops          | 1267             | total number of io operations done on this mount                           |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | read-mb/s             | 0.00             | 1 minute average read rate in MB/s                                         |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | write-mb/s            | 0.00             | 1 minute average write rate in MB/s                                        |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | iops                  | 0                | 1 minute average io ops rate                                               |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | xoffs                 | 0                | counter how often we needed to wait for an available write buffer          |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | instance-url          | myhost:1094      | hostname and port of the upstream EOS instance                             |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | client-uuid           | 4af8154c.....    | unique identifier of this client (UUID)                                    |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | server-version        | 4.4.17           | server version where this client is connected                              |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | automounted           | 0                | indicates if the mount is done via autofs                                  |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n    |    ALL   | max-inode-lock-ms     | 0.00             | maximum time any thread in the thread pool is stuck in ms                  |\n    +----------+-----------------------+------------------+----------------------------------------------------------------------------+\n\nThe statistics file can be printed by any user on request by running:\n\n.. code-block:: bash\n\n   eosxd get eos.stats <mount-point>\n\n\nThe statistics file counter can be reset by running this command as root:\n\n.. code-block:: bash\n\n   eosxd set system.eos.resetstat - /eos/\n\n\n.. index::\n   pair: FUSE; Virtual Attributes\n\n\nVirtual attributes on EOS mounts\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nVirtual attributes allow getting information which is not exposed by POSIX APIs.\n\n\nVirtual Attribute Getters\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe following shows all the defined attributes and their meaning:\n\n.. code-block:: bash\n\n    eosxd get\n    usage CLI   : eosxd get <key> [<path>]\n\n                        eos.btime <path>                   : show inode birth time\n                        eos.ttime <path>                   : show lastest mtime in tree\n                        eos.tsize <path>                   : show size of directory tree\n                        eos.dsize <path>                   : show total size of files inside a directory\n                        eos.dcount <path>                  : show total number of directories inside a directory\n                        eos.fcount <path>                  : show total number of files inside a directory\n                        eos.name <path>                    : show EOS instance name for given path\n                        eos.md_ino <path>                  : show inode number valid on MGM\n                        eos.hostport <path>                : show MGM connection host + port for given path\n                        eos.mgmurl <path>                  : show MGM URL for a given path\n                        eos.stats <path>                   : show mount statistics\n                        eos.stacktrace <path>              : test thread stack trace functionality\n                        eos.quota <path>                   : show user quota information for a given path\n                        eos.url.xroot                      : show the root:// protocol transport url for the given file\n                        eos.reconnect <mount>              : reconnect and dump the connection credentials\n                        eos.reconnectparent <mount>        : reconnect parent process and dump the connection credentials\n                        eos.identity <mount>               : show credential assignment of the calling process\n                        eos.identityparent <mount>         : show credential assignment of the executing shell\n\n    as root             system.eos.md  <path>              : dump meta data for given path\n                        system.eos.cap <path>              : dump cap for given path\n                        system.eos.caps <mount>            : dump all caps\n                        system.eos.vmap <mount>            : dump virtual inode translation table\n\n\n\nVirtual Attribute Setters\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe following shows all attributes, which can be set to trigger internal functions changing the state of a mount:\n\n.. code-block:: bash\n\n    eosxd set\n    usage CLI   : eosxd set <key> <value> [<path>]\n\n    as root             system.eos.debug <level> <mount>   : set debug level with <level>=crit|warn|err|notice|info|debug|trace\n                        system.eos.dropcap - <mount>       : drop capability of the given path\n                        system.eos.dropcaps - <mount>      : drop call capabilities for given mount\n                        system.eos.resetstat - <mount>     : reset the statistic counters\n                        system.eos.log <mode> <mount>      : make logfile public or private with <mode>=public|private\n                        system.eos.fuzz all|config <mount> : enabling fuzzing in all modes with scaler 1 (all) or switch back to the initial configuration (config)\n\n\n\n.. index::\n   pair: FUSE; Serverce-side Configuration\n\nServer Side Configuration\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe **eosxd** network provides four configuration parameters, which can\nbe shown or modified using **eos fusex conf**\n\n.. code-block:: bash\n\n    [root@eos ]# eos fusex conf\n    info: configured FUSEX broadcast max. client audience 256 listeners\n    info: configured FUSEX broadcast audience to suppress match is '@b[67]'\n    info: configured FUSEX heartbeat interval is 10 seconds\n    info: configured FUSEX quota check interval is 10 seconds\n\n\nThe default heartbeat interval is 10 seconds, upon which each\n**eosxd** process sends a heartbeat message to the MGM server. The quota\ncheck interval is the interval after which the MGM FuseServer checks\nagain if a **eosxd** client went out of quota or back to quota. The\ndefault is also 10 seconds.\n\nWhen working with thousands of clients within a single directory the\namount of messages in the FuseServer broadcast network can overwhelm the\nMGM messaging capacity. To reduce the amount of messages sent around\nwhile files are open and written, a threshold can be defined after which\na certain audience of clients will not receive any more meta-data update\nor forced refresh messages. If 1000 clients write 1000 files within a\nsingle directory the message rate is 100kHz for file-size updates while\nthe clients are writing. In the example above if a message hits more\nthan 256 listeners and the client names start with b6 or b7 messages\nwill be suppressed. Messages emitted when files are created or\ncommitted are not suppressed!\n\nLimiting Server Side FUSEx access\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\n**eosxd** client rates can be limited using the rate limiter interface\navailable via the **access** command in the CLI.\n\n.. code-block:: bash\n\n    # limit the access for listing to 100 Hz per user\n    eos access set limit 100 rate:user:\\*:Eosxd::prot::LS\n\n    # limit the access for stats to 1000 Hz per user\n    eos access set limit 1000 rate:user:\\*:Eosxd::prot::STAT\n\n    # limit the access for returning list entries to 10 kHz per user\n    eos access set limit 10000 rate:user:\\*:Eosxd::ext::LS-Entry\n\n    # limit the access for meta-data updates to 1 kHz per user\n    eos access set limit 1000 rate:user:\\*:Eosxd::prot::SET\n\n\nLS, STAT and SET limits are applied by the corresponding server side\nprotocol methods. LS-Entry is applied when another LS call is requested.\nPlease note the difference in the naming of the **prot** and **ext**\ncounter types.\n\n\n\nNamespace Configuration\n^^^^^^^^^^^^^^^^^^^^^^^\n\nBy default each client sends its desired leasetime for directory\nsubscriptions (300s default at time of writing). For certain directories\nin the hierarchy which are essentially read-only it improves the overall\nperformance to define a longer leasetime. In a home directory hierarchy\nlike **/eos/user/f/foo** the first three directory levels could have a\nlonger lease time defined.\n\n.. code-block:: bash\n\n    [root@eos ]# eos attr set sys.forced.leasetime=86400 /eos/\n    [root@eos ]# eos attr set sys.forced.leasetime=86400 /eos/user/\n    [root@eos ]# eos attr set sys.forced.leasetime=86400 /eos/user/f\n\n\n.. index::\n   pair: FUSE; File State Tracking\n\nFile State Tracking for eosxd\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe namespace registers the state changes of a file inside the extended\nattribute *sys.fusex.state*.\n\nThe extended attribute can track up to 127 operations, then gets\ntruncated to half. A truncation is indicated with a leading *||* in\nthe attribute.\n\nPossible state flags are:\n\n-   C := File has been created by the FuseServer\n\n-   U := File has been updated in the FuseServer\n\n-   T := File has been truncated in the FuseServer or opened with a\n    TRUNCATE flag\n\n-   ± := File size has been changed\n\n-   R := File has been renamed in the FuseServer\n\n-   M := File has been moved in the FuseServer\n\n-   0 := an invalid operation has been seen in the FuseServer (should\n    never happen)\n\n-   Z := File recovery has been triggered by a FUSE client\n\n-   +fs := File replica/stripe has been committed ( multiple entries\n    possible, fs is the filesystem id in decimal)\n\n-   c := File checksum has been committed\n\n-   s := File size has been committed\n\n-   v := Replica has been verified for the checksum\n\n-   V := Replica has been verified for the size\n\n-   \\| := Terminates a commit sequence started with +fs\n\n-   || := tracked operations exceeded 127 and the attribute has been\n    truncated\n\nExample:\n\n.. code-block:: bash\n\n    [root@eos ]# eos attr ls /eos/file\n                ...\n                sys.fusex.state=\"CU±+2sc|+1v|U±+2sc|+1v|U±+2sc|+1v|\"\n                ...\n\n\nThis examples show the creation \\\"C\\\", the file size update \\\"U±\\\", a\ncommit from filesystem 2 with checksum and size \\\"+2sc\\\", a commit from\nfilesystem 1 with checksum verification \\\"+1v\\\", then two\nupdate sequences to the file resulting in filesize change.\n\nReplica/Chunk Tracking\n^^^^^^^^^^^^^^^^^^^^^^\n\nThe namespace registers all replica/stripe create,unlink and delete\noperation inside the extended attribute *sys.fs.tracking*.\n\nThe extended attribute is truncated by half when it exceeds 127 characters. A\ntruncation is indicated with a leading *\\|\\|* in the attribute.\n\nPossible indicators are:\n\n.. epigraph::\n\n   ========= ========================================================\n   indicator description\n   ========= ========================================================\n   +fsid     a replica/stripe was attached on filesystem fsid\n   -fsid     a replica/stripe has been unlinked for filesystem fsid\n    fsid     a replica/stripe has been deleted on filesystem fsid\n   ========= ========================================================\n\nExample:\n\n.. code-block:: bash\n\n    [root@eos  ]# eos attr ls /eos/file\n                  ...\n                  sys.fs.tracking=\"+1+2+3+4-1-2/1/2\"\n                  ...\n\n\nThis examples shows the how replicas are attached on filesystems\n1,2,3,4, then unlinked on 1,2 and finally deleted on 1,2.\n\n\n.. index::\n   pair: Using; EOS FUSE Ubuntu\n\n\nEOS FUSE mount on Ubuntu\n------------------------\n\nThe following releases of Ubuntu are currently supported:\n\n* Ubuntu 22.04.5 LTS (Jammy Jellyfish)\n* Ubuntu 24.04.3 LTS (Noble Numbat)\n* Ubuntu 25.10 (Questing Quokka)\n\n\nFollow these steps to configure the necessary APT repositories and install\nthe EOS client and FUSE packages:\n\n.. code-blocK:: bash\n\n   # make sure we have a few utilities\n   sudo apt update\n   sudo apt install -y curl gpg lsb-release\n   # Setup the APT repositories holding the EOS package:\n   # Import the EOS GPG key of the repository\n   curl -sL http://storage-ci.web.cern.ch/storage-ci/storageci.key | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/storage-ci.gpg\n   # Create the APT repository configuration\n   echo \"deb [arch=$(dpkg --print-architecture)] http://storage-ci.web.cern.ch/storage-ci/debian/eos/diopside $(lsb_release -cs) $(lsb_release -cs)/tag $(lsb_release -cs)/commit\" | sudo tee /etc/apt/sources.list.d/eos-client.list > /dev/null\n\n\nInstall the EOS packages and their dependencies. Root privileges are required. Also\ncreate the local directory for the local mounts:\n\n.. code-block:: bash\n\n   sudo apt update\n   sudo apt install -y eos-fusex\n   sudo mkdir /eos/\n\n\nCreate the configuration files for the EOS FUSE mountpoints. Depending on the EOS\ninstances that need to be accessed, one can create one configuration file per\ninstance, using the following convention:\n\n* Configuration for accessing data stored on CERNBox can be placed in\n  `/etc/eos/fuse.home-<initial>.conf` where <initial> should be replaced by a\n  sigle letter eg. 'e', 'a', etc. The contents of this file should at least\n  contain the following (note: this needs to be a valid JSON object):\n\n  .. code-block:: bash\n\n     {\"name\": \"home-<initial>\", \"hostport\":\"eoshome-<initial>.cern.ch\", \"remotemountdir\":\"/eos/user/<initial>/\"}\n\n\nExample:\n\n* Mount configuration for user account \"userx\" whose data is stored in CERNBox\n\n  .. code-block:: bash\n\n     {\"name\": \"home-u\", \"hostport\": \"eoshome-u.cern.ch\", \"remotemountdir\": \"/eos/user/u/userx/\"}\n\n\n* Mount configuration for project \"asdf\" whose data is stored in the EOSPROJECT instance\n\n  .. code-block:: bash\n\n     {\"name\": \"project-a\", \"hostport\": \"eosproject-a.cern.ch\", \"remotemountdir\": \"/eos/project/a/asdf/\"}\n\n\n* Mount configuration for accessing the EOSCMS instance\n\n  .. code-block:: bash\n\n     {\"name\": \"cms\", \"hostport\":\"eoscms.cern.ch\", \"remotemountdir\":\"/eos/cms/\"}\n\n\nWith the above configuration in place, one can setup automount to take care of managing the mountpoints.\n\n.. code-block:: bash\n\n   # Ensure the autofs package is installed:\n   sudo apt install -y autofs\n\n   # Check that the autofs service is up and running\n   sudo systemctl status autofs\n\n   # Create a file called \"/etc/auto.eos\" which containts the mountpoints to be managed by autofs.\n   # Example contents of /etc/auto.eos\n   home-a -fstype=eosx,fsname=home-a :eosxd\n   # ... same for each user letter\n   home-z -fstype=eosx,fsname=home-z :eosxd\n   project-a -fstype=eosx,fsname=project-a :eosxd\n   # ... some for each project letter\n   project-z -fstype=eosx,fsname=project-z :eosxd\n   cms -fstype=eosx,fsname=cms :eosxd\n\n   #Create a file called \"/etc/auto.master.d/eos.autofs\" like this:\n   echo \"/eos /etc/auto.eos\" > /etc/auto.master.d/eos.autofs\n   sudo systemctl restart autofs\n\n\nAt this point the mountpoints are managed automatically by the autofs daemon.\nTherefore, trying to access the local path `/eos/home-u/` given the above\nconfiguration, would display \"userx\" CERNBox contents.\n\nAll mounts will be created inside the `/eos/` directory on the local file\nsystem and can be accessed by concatenating the first column in the\n`/etc/auto.eos` with the `/eos/` path.\n\nKerberos is commonly used to authenticate with the mount points. To have\nthe kerberos client available:\n\n.. code-block:: bash\n\n   # Ensure the autofs package is installed:\n   sudo apt install -y krb5-user\n\n\nAfter which \"kinit\" could be used to obtain a ticket. For further configuration options\nwhen it comes to handling EOS FUSE mountpoints please consult the following document:\nhttps://gitlab.cern.ch/dss/eos/-/blob/master/fusex/README.md\n\n\nRecovering from a stuck EOS FUSE mount\n--------------------------------------\n\nThe intent is that EOS FUSE mounts be robust and stable. However it is\npossible that the user space component, eosxd, could run into problems and crash or\nperhaps hang. In that case the filesystem will respond with an error or stop\nresponding, with the problem not resolving by itself. The whole machine on which it\nwas mounted could be impacted, as even operations that don't directly use the mounted\nfilesystem may indirectly wait for something that does. Sometimes several\nsteps are needed to clear away a stuck mount and unblock waiting processes.\nSubsequently it should be possible to restart the EOS mount.\n\nHere are steps which have been found to usually unblock and remove a FUSE mount:\n\nIdentify the problematic EOS mount: Hopefully it will be clear which mount is stuck,\nusually operations on a given mount point will hang or return an error. Processes hanging\nmay appear in \"D\" (uninterruptible) state when listing them with \"ps\". Another symptom\nmay be that listing free space for all mounted filesystems, e.g. \"df -h\" will block at a\ncertain point when listing, just before the problem filesystem.\n\nIdentify the minor device number of the mount: For example for a problematic mount\nat /eos/home-a\n\n.. code-block:: bash\n\n   cat /proc/self/mountinfo | grep /eos/home-a\n   # output like\n   # 1924 1389 0:76 / /eos/home-a rw,nosuid,nodev,relatime shared:826 - fuse home-a rw,user_id=0,group_id=0,allow_other\n\nIn the above the minor device number is 76, visible in the third column.\n\nEnsure the fuse control filesystem is available. Often this will already be mounted. On AlmaLinux\nit can be usually be found at /sys/fs/fuse/connections. If not, for example if the connections\ndirectory is not visible, it can be mounted with:\n\n.. code-block:: bash\n\n   mount -t fusectl none /sys/fs/fuse/connections\n\nEnsure the eosxd processes for the mount are no longer running or are exiting. There are usually two\nprocesses associated to a mount.\n\n.. code-block:: bash\n\n   ps auxgww | grep eosxd | grep home-a\n   # may give output like\n   # root     3804740  0.6  0.0 772588 68116 ?        S<sl 15:29   0:08 /usr/bin/eosxd /eos/home-a -o rw,fsname=home-a -oautofs\n   # root     3804742  0.0  0.0  87152 20936 ?        S    15:29   0:00 /usr/bin/eosxd /eos/home-a -o rw,fsname=home-a -oautofs\n   kill -9 3804740 3804742\n\nNow request FUSE to abort any remaining operations by using the control filesystem:\n\n.. code-block:: bash\n\n   # where the '76' below is the device number found above\n   echo 1 > /sys/fs/fuse/connections/76/abort\n\nFinally attempt a 'lazy' unmount of the filesystem:\n\n.. code-block:: bash\n\n   umount -l /eos/home-a\n\nThis should clear both the mount and any blocked processed that were waiting,\ndirectly or indirectly on the previously hung mount. Assuming all is clear the mount can be\nrestarted, or if using the automounter attempt to access the mount point to restart it.\n\nSquashFS images for software distribution\n-----------------------------------------\n\nEOS provides support for SquashFS image files, which can be automatically mounted when the image path is traversed. This functionality requires an appropriate automount configuration.\n\nTo create SquashFS images a client needs the EOS shell and a local mount with a path prefix identical to that inside the client shell.\nThis means e.g. both commands as shown here point to the same directory:\n\n.. code-block:: bash\n\n   # access inside the shell\n   eos ls -la /eos/foo/bar\n   # access using the FUSE mount\n   ls -la /eos/foo/bar\n\n\nTo really have read-only access to the  contents of SquashFS images, clients have to install the package **cern-eos-autofs-squashfs**.\n\nAll functionality of the SquashFS CLI is displayed using the help option:\n\n.. code-block:: bash\n\n   eos squash -h\n\n\nThe functionality can be grouped into two categories:\n\n* Simple SquashFS packages\n* Release SquashFS packages\n\n.. index::\n   pair: SquashFS; Simple Packages\n\nSimple SquashFS Packages\n^^^^^^^^^^^^^^^^^^^^^^^^\n\nA simple SquashFS package consists of a symbolic link under the package path and a hidden package file in the same directory as the symbolic link.\n\nThe workflow to create a SquashFS package is shown here:\n\nCreate a new package\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n.. code-block:: bash\n\n   [root@dev ]# eos mkdir -p /eos/dev/squash/\n   [root@dev ]# eos squash new /eos/dev/squash/mypackage\n   info: ready to install your software under '/eos/dev/squash/mypackage'\n   info: when done run 'eos squash pack /eos/dev/squash/mypackage' to create an image file and a smart link in EOS!\n\n   # see what happened - we have created a symbolic link in EOS with the package pathname and the link points to a local stage directory in /var/tmp/...\n   [root@dev ]# eos ls -la /eos/dev/squash/\n   drwxrwxrw+   1 root     root               59 May 27 13:32 .\n   drwxrwxrw+   1 root     root       4751231651 May 27 13:32 ..\n   lrwxrwxrwx   1 nobody   nobody             59 May 27 13:32 mypackage -> /var/tmp/root/eosxd/mksquash/..eos..dev..squash..mypackage/\n\n\nInstall software into a package\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\n.. code-block:: bash\n\n   # install software into the package, de facto we work on the local disk under /var/tmp/...\n   [root@dev ]# cd /eos/dev/squash/mypackage/\n   [root@dev ]# touch HelloWorld\n\nPack a new package\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\n.. code-block:: bash\n\n   # pack the new package\n   [root@dev ]# eos squash pack /eos/dev/squash/mypackage\n\n   # see what happened - the symlink in EOS with the package pathname points to an encoded loction for the hidden package file .mypackage.sqsh\n   [root@dev ]# eos ls -la /eos/ajp/squash/\n   drwxrwxrw+   1 root     root             4161 May 27 13:38 .\n   drwxrwxrw+   1 root     root       4751235753 May 27 13:32 ..\n   -rw-r--r--   2 nobody   nobody           4096 May 27 13:38 .mypackage.sqsh\n   lrwxrwxrwx   1 nobody   nobody             65 May 27 13:38 mypackage -> /eos/squashfs/ajp.cern.ch@---eos---ajp---squash---.mypackage.sqsh\n\n\nIf you try to use or access a package on a different client machine before you call **eos squash pack** you will get errors on clients, because the symbolic link points to a non-existing local directory as long as a package is not closed.\n\nIn general you have to treat SquashFS packages as write-once archives. There is the possibility to unpack a packed archive, modify and re-pack, however this is problematic if a package is already accessed on other clients using the automount mechanism. They won't remount an updated package automatically unless the mount is removed by idle timeouts and re-mounted later.\n\n\nPackage information\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\n\nFor completeness here are the commands to get information about a package:\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash info /eos/dev/squash/mypackage\n   info: '/eos/dev/squash/.mypackage.sqsh' has a squashfs image with size=4096 bytes\n   info: squashfs image is currently packed - use 'eos squash unpack /eos/dev/squash/mypackage' to open image locally\n\n\nUnpackaging\n\"\"\"\"\"\"\"\"\"\"\"\n\nAs mentioned you can unpack an existing package:\n\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash unpack /eos/ajp/squash/mypackage\n   ...\n   info: squashfs image is available unpacked under '/eos/dev/squash/mypackage'\n   info: when done with modifications run 'eos squash pack /eos/dev/squash/mypackage' to create an image file and a smart link in EOS!\n\n\nAnd pack it again:\n\n.. code-block:: bash\n\n   # pack the new package\n   [root@dev ]# eos squash pack /eos/dev/squash/mypackage\n\nDeleting a package\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nTo delete a SquashFS package you run:\n\n.. code-block:: bash\n\n   # delete a package\n   [root@dev ]# eos squash rm /eos/dev/squash/mypackage\n\n\nRelabeling a package\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIf a SquashFS package and/or package files has been moved around in the namespace e.g. by doing this:\n\n.. code-block:: bash\n\n   [root@dev ]# eos mv /eos/dev/squash/ /eos/dev/newsquash/\n\nthen the package links are broken. In this case one has to relabel the package:\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash relabel /eos/dev/newsquash/mypackage\n\n\nRemote web installation of packages\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nThe CLI provides a convenience function to install a .tar.gz package from a web URL:\n\n.. code-block:: bash\n\n   [roo@dev ]# eos squash install --curl=https://root.cern/download/root_v6.24.00.Linux-centos7-x86_64-gcc4.8.tar.gz /eos/dev/newsquash/root\n\nAfter successful execution the software package is ready for use and no further packaging commands are required.\n\nIf you have the automounter RPM installed on your client you are ready to use the software:\n\n.. code-block:: bash\n\n   cd /eos/dev/newsquash/root/\n   ...\n\n.. index::\n   pair: SquashFS; Release Packages\n\nRelease SquashFS Packages\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe **simple** package functionality is sufficient, if properly used. Many times you want to deal with updates and new release/versions of software. In this case the **release** functionality is preferable.\n\nCreating a new release package\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nRelease package management is illustrated in the following:\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash new-release /eos/dev/release/mypackage\n   info: ready to install your software under '/eos/dev/release/mypackage/.archive/mypackage-20210527135506'\n   info: when done run 'eos squash pack /eos/dev/release//mypackage/.archive/mypackage-20210527135506' to create an image file and a smart link in EOS!\n   info: install the new release under '/eos/dev/release/mypackage/next'\n\n\nThis new release is now locally available under **/eos/dev/release/mypackage/next**. You can install your software to this location and then call\n\nPacking a new release package\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash pack-release /eos/dev/release/mypackage\n   ...\n   info: new release available under '/eos/ajp/squash/mypackage/current'\n\nNow we have published the latest version of our release under **/eos/dev/release/mypcakge/current**. Our package name is in the release management mode a directory containing a **current** link, if there is an open new release a **next** link and a hidden **.archive** directory, where all versions of a release are stored.\n\nBy default a release is created with the unix timestamp during **new-release**. For most people it might be more convenient to specify a version number. In this case you call:\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash new-release /eos/dev/release/mypackage v1.0.0\n   ...\n   [root@dev ]# eos squash pack-release /eos/dev/release/mypackage\n   [root@dev ]# eos squash new-release /eos/dev/release/mypackage v1.1.0\n   ...\n   [root@dev ]# eos squash pack-release /eos/dev/release/mypackage\n\nRelease Package Information\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nYou can obtain information about all available versions/releases doing:\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash info-release /eos/dev/release/mypackage\n   ---------------------------------------------------------------------------\n   - releases of '/eos/ajp/squash/mypackage'\n   ---------------------------------------------------------------------------\n   /eos/dev/squash/mypackage/.archive/mypackage-v1.0.0\n   /eos/dev/squash/mypackage/.archive/mypackage-v1.1.0\n   /eos/dev/squash/mypackage/current\n   ---------------------------------------------------------------------------\n\nThe output shows two versions in the **archive** and the **current** link.\n\nTrimming Release Packages\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nIf you regularly build software releases, you want to limit the number of versions which are kept.\n\nYou can trim your software releases using:\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash trim-release /eos/dev/release/mypackage 100\n\nThis command will keep only versions not older than 100 days.\n\nAdditionally you can specify the maximum number of versions to keep:\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash trim-release /eos/dev/release/mypackage 100 10\n\nIn this case we don't want to keep more than the 10 most recent versions, not older than 100 days.\n\nDeleting Release Packages\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nFor completeness, there is a command to cleanup a release package. Be aware that this will delete all your release versions!\n\n.. code-block:: bash\n\n   [root@dev ]# eos squash rm-release /eos/dev/release/mypackage\n   ---------------------------------------------------------------------------\n   - releases of '/eos/dev/release/mypackage'\n   ---------------------------------------------------------------------------\n   /eos/dev/release/mypackage/.archive/mypackage-v1.0.0\n   /eos/dev/release/mypackage/.archive/mypackage-v1.1.0\n   /eos/dev/release/mypackage/current\n   ---------------------------------------------------------------------------\n   info: wiping squashfs releases under '/eos/dev/release/mypackage'\n   info: wiping links current,next ...\n   info: wiping archive ...\n\nThe main difference between simple and release packages is that you can create a new release while the previous one is in use on any other client.\n\nShared filesystems as FST backends\n----------------------------------\n\nThe EOS FST server can be configured to store data on any (shared) filesystem as storage device which supports extended attributes. To prevent filesystems sharing a device or a shared filesystem from being accounted multiple times in the space and node aggregation, one can label each filesystem with a shared filesystem name to indicated that all devices using this name share the same hardware resource.\n\nThe shared filesystem name can be configured when a filesystem is added e.g. a CephFS filesystem named cephfs1:\n\n.. code-block:: bash\n\n   fs add 7a41781f-62dc-4f18-8f64-375e57487578 foo.cern.ch /cephfs/ default rw cephfs1\n\nIf filesystems are already registered or the filesystem name has changed one can use the filesystem config command:\n\n.. code-block:: bash\n\n   fs config 1 sharedfs=cephfs1\n\n\n\nExtended attribute locks\n------------------------\n\nAn extended attribute lock is a simple mechanism to block file opens on locked files to foreigners. Foreigners are not owners. The owner is defined by the username and the application name.\nSo if any of these differ a client is considered a foreigner.\n\nWe define two types of locks:\n\n-   exclusive : no foreigner can open a file with an exclusive lock for reading or writing\n\n-   shared    : foreigner can open a file with an exclusive lock in case they are reading\n\nShared attribute locks are currently not exposed in the CLI.\n\nTo create an exclusive extended attribute lock:\n\n.. code-block:: bash\n\n   # create a lock\n   eos -r 100 100 -a myapp file touch -l /eos/dev/lockedfile\n\n   # the owner can read\n   eos -r 100 100 -a myapp cp /eos/dev/lockedfile      - # will succeed\n\n   # a foreigner can not read\n   eos -r 101 101 -a myapp cp /eos/dev/lockedfile      - # will fail\n   eos -r 100 100 -a anotherapp cp /eos/dev/lockedfile - # will fail\n\n   # create a lock with a given lifetime e.g. 1000s\n   eos -r 100 100 -a myapp file touch -l /eos/dev/lockedfile 1000\n\n   # create a lock which only requires the same user to be used\n   eos -r 100 100 -a myapp file touch -l /eos/dev/lockedfile 1000 user\n\n   # create a lock which only requires the same app to be used\n   eos -r 100 100 -a myapp file touch -l /eos/dev/lockedfile 1000 app\n\nBy default locks are taken for 24h. The lifetime can be specified as seen before if needed. The audience can be relaxed to allow same app access or same user.\n\nYou can remove a lock if you are the owner by doing:\n\n.. code-block:: bash\n\n   # remove a lock\n   eos -r 100 100 -a myapp file touch -u /eos/dev/lockedfile\n\n\nThe internal representation of an attribute lock is given here:\n\n.. code-block:: bash\n\n   attr ls /eos/dev/lockedfile | grep sys.app.locks\n   # requiring a strict audience\n   sys.app.lock=\"expires:1665042101,type:exclusive,owner:daemon:myapp\"\n   # requiring same user\n   sys.app.lock=\"expires:1665042101,type:exclusive,owner:daemon:*\"\n   # requiring same app\n   sys.app.lock=\"expires:1665042101,type:exclusive,owner:*:myapp\"\n\nThe high-level functionality for creation/deletion of attribute locks can be circumvented by creating/deleting the *sys.app.locks* attribute using extended attribute interfaces.\n\n.. index::\n   pair: Using; Archiving\n\nArchiving Directory Subtrees\n----------------------------\n\nThe archive CLI supports the following commands:\n\n.. code-block:: bash\n\n   archive <subcmd>\n           create <path>                      : create archive file\n           put [--retry] <path>               : copy files from EOS to archive location\n           get [--retry] <path>               : recall archive back to EOS\n           purge[--retry] <path>              : purge files on disk\n           transfers [all|put|get|purge|uuid] : show status of running jobs\n           list [<path>]                      : show status of archived directories in the subtree\n           kill <job_uuid>                    : kill transfer\n           help [--help|-h]                   : display help message\n\nIn order to safely archive an EOS subtree to tape (CTA) the following steps detailed in this document must\nbe performed. Assume we want to archive the EOS subtree rooted at /eos/dir/archive/test. First of all,\nthe user needs to make sure that he/she has the necessary permissions to submit archiving commands.\nThe permissions check is enforced at directory level by using the **sys.acl** extended attribute\nand it allows setting permissions at user, group or egroup level. The **ACL flag** for archiving\nis **'a'**.\n\n.. code-block:: bash\n\n    sys.acl=\"u:tguy:a\"  # user tguy has the right to archive for the current directory\n\nThe archive ACL entry is only required on the root of the archiving subtree.\nOnce the proper permissions are in place, we need to take a snapshot of all the metadata of the\nfiles and directories under this subtree. For this we use the **archive create** command inside\nthe *EOS Console*:\n\n.. code-block:: bash\n\n   archive create /eos/dir/archive/test\n\nThere are some restrictions that apply to the contents for the archived hierarchy. These come\neither from restrictions imposed by the tape backend or the type of files that can be created\non an EOS instance from an external client - as it's the case for the archive daemon. Therefore,\nthe archive creation will fail if any of the following types of files are present in the hierarchy.\nBelow you have different types of files that are **NOT** supported by the archive tool and\nthe corresponding eos command that you can run to determine if there are any such files in\nyour target hierarchy:\n\n* 0 size files\n\n.. code-bloc:: bash\n\n   eos find --format fid,checksumtype,size /eos/<instance>/archive_dir/ | grep \" size=0 \"\n\n\n* symlink/hardlink files\n\n.. code-block:: bash\n\n   eos find --format fid,checksumtype,size /eos/<instance>/archive_dir/ | grep \" checksumtype=none \"\n\n\n* atomic, version or atomic-version files\n\n.. code-block:: bash\n\n   eos find --name \".sys.*\" /eos/<instance>/archive_dir/\n\n\nAfter creating an archive the EOS subtree is **immutable** and no updates are allowed either to the\ndata or the metadata. Transferring the data to tape (CTA) is done using the **archive put** command:\n\n.. code-block:: bash\n\n   archive put /eos/dir/archive/test\n\nAt any point during a transfer the user can retrieve the current status of the transfer by issuing an\n**archive transfers** command. Once the transfer finishes there will be two additional files saved at\nthe root of the archived subtree: the **.archive.log** file which contains the logs of the last transfer\n(note the 'dot' in the beginning of the filename, so to list it use **ls -la** in the *EOS Console*)\nand another file called **.archive.<operation>.<outcome>** where operation is one of the following:\nget/put/purge and the outcome can either be **done** or **err**.\n\nWhile an archive operation is ongoing the file stored in EOS is marked with the **err** tag. For\nexample, an ongoing **put** operation, which can take several hours depending on the size of the\nsub-tree being archived to tape, will appear in the **eos ls -la** output as **.archive.put.err**.\nOnce the put operation is successful, this file will be renamed to **.archive.put.done**. Therefore,\nit's important to check the output of the **eos archive transfers** command which is listing the\nstatus of the ongoing archive operations and not rely only on the status file in EOS.\n\nIf an error occurs the user has the possibility to resubmit the transfer by using the **--retry** option.\n\nWhen the put operation is successful one should find a file called **.archive.put.done** at the root\nof the subtree and the user can now issue the purge command which will delete all the data from EOS\nthus freeing the space.\n\n.. code-block:: bash\n\n    archive purge /eos/dir/archive/test\n\nTo get the data back into EOS use the archive get command:\n\n.. code-block:: bash\n\n    archive get /eos/dir/archive/test\n\nThe same conventions as before apply when it comes to the progress and the final status of the transfer.\nIf the user would like to retrieve the status of previously archived directories he/she can use the\n**archive list** command which will return the status of all archived directories rooted at the given\ndirectory or if no directory is given then \"/\" is assumed. This command displays also the running\njobs but no detailed information about them is provided - for this you should use the **archive transfers**\ncommand.\n\nIn case the user wants to permanently delete the data saved on **tape (CTA)**, then unless he has root\nprivileges on the EOS instance he will need to contact one of the administrators to perform this operation.\nPermanently deleting the achive will not delete any data from EOS, but only the data saved in CTA.\nTherefore, it is the **user's responsibility** to make sure he/she first gets the data back to EOS before\nrequesting the deletion of the archive.\n\nData Obufscation and Encryption\n-------------------------------\n\nWe provide a generic EOS mechanism to obfuscate or encrypt data files stored on storage nodes, which does not require encrypted disk partitions. Each file is obfuscated/encrypted individually.\nEncryption uses an obfuscation key (start vector) which is transformed into an encryption key using a client secret to compute an HMAC value of the obfuscation key. Data are then encrypted with a simple block cipher algorithm (ECB). This is the fastest way of encryption for random access without any read-write-amplification - but does not meet the highest security standards. Obfuscation keys are stored as an extended attribute of each file. It is not possible to view obfuscation keys using the EOS CLI. A low resolution fingerprint of the encryption key is also stored as an invisible extended attribute when encryption is done using a FUSE mount. This allows identifying incorrect client side keys and avoids returning unreadable content to clients on FUSE mounts. This mechanism is not used for remote access protocols.\n\n\n.. index::\n   pair: Encryption; Obfuscation\n\nTo enable obfuscation for individual files using remote protocols, one can use the CGI `&eos.obfuscate=1` when creating a new file.\n\nTo enable obfuscation for all new files created in in a directory use:\n\n.. code-block:: bash\n\n                [root@host~] eos attr set sys.file.obfuscate=1 /eos/obfuscate/\n\n\nObfuscated files are accessible with any protocol. For remote access protocols like xrdcp,eoscp,http files are unobfuscated by the FST gateway node. For FUSE mounts files are unobfuscated by the FUSE client.\n\n\n.. NOTE:: Only new files are obfuscated when the obfuscation attribute was (re-)defined. Existing files will stay unobfuscated. You can use `eos convert --rewrite filename` to rewrite an existing file obfuscated.\n\n\nEncryption requires obfuscation to be enabled! This is done by defining on the target directory:\n\n.. code-block:: bash\n\n                [root@host~] eos attr set sys.file.obfuscate=1 /eos/encryption/\n\nEncryption is additionally enabled client-side by defining the environment variable `EOS_FUSE_SECRET`. It is used automatically by the `eoscp` command or the `eosxd` FUSE mounts, but not when using `xrdcp` or `http` access:\n\n.. code-block:: bash\n\n                [root@host~] eos attr set sys.file.obfuscate=1 /eos/encryption/\n                [root@host~] export EOS_FUSE_SECRET=858aa9f8-545f-4b10-a823-3b7d822291a3\n                [root@host~] eosxd get eos.reconnect /eos/ #after defining a new encryption key you have to reconnect the FUSE mount or create a new subshell\n                [root@host~] eos cp /tmp/file root://localhost//eos/encryption/encrypted-file\n                [root@host~] eos file info /eos/encryption/encrypted-file\n                  File: '/eos/encryption/encrypted-file'  Flags: 0640\n                  Size: 13\n                 ...\n                  #Rep: 1\n                 Crypt: encrypted\n                 ...\n\n                [root@host~] cat /eos/encryption/encrypted-file\n                Hello World!\n\n\nWhen using `eoscp` files are decrypted by the FST and the encryption has to be forwared to the FST as part of the encrypted capability issued by the MGM node. When using FUSE mounts, encryption keys never leave the clients and decryption is done only on client side.\n\n\n.. NOTE:: There is no way to recover contents of encrypted files if you lose the `EOS_FUSE_SECRET` key, which was used to encrypt a file!\n\n.. NOTE:: To access encrypted files with remote protocols using `xrdcp` or `curl` you can define the encryption key using CGI e.g. `&eos.key=858aa9f8-545f-4b10-a823-3b7d822291a3`\n\nStarting with EOS v5.2 it is possible to define a global encryption key in the fuse configuration file which is shared by all calling client applications. The configuration has to have mode 0400 and for shared mounts has to be owned by root:root, for private mounts it is has to be owned by the user/group id of the mounting user.\n\nThe syntax in the FUSE configuration file is as shown:\n\n.. code-block:: bash\n\n                cat /etc/eos/fuse.my.conf\n                {\"encryptionkey\":\"655361ab-5af9-4697-8a32-8069ade18a27\"}\n\n.. NOTE:: To create an unencrypted and encrypted area using single FUSE mounts, it is sufficient to define an encryption key in the FUSE configuration file, have a storage area where the `sys.eos.obfuscate` extended attribute is not defined (unencrypted) and one where the `sys.eos.obfuscate` attribute is defined (encrypted).\n\n\nRunning Authentication Front-ends\n---------------------------------\n\nThe MGM supports servicing requests from a front-end XRootD authentication server. An authentication front-end server is an XRootD server running the EosAuthOfs plug-in. Using this plug-in the front-end server connects to a standard MGM service (back-end) over ZMQ protocol.\nAn authentication front-end allows one to configure a subset of authentication methods and to partition connections of certain use cases to this daemon, shielding the standard MGM service from direct connections.\n\nTo enable a standard MGM to allow connections from an authentication front-end use the following MGM configuration variables:\n\n.. code-block:: bash\n\n                #-------------------------------------------------------------------------------\n                # Configuration for the authentication plugin EosAuth\n                #-------------------------------------------------------------------------------\n                # Set the number of authentication worker threads running on the MGM\n                mgmofs.auththreads 64\n\n                # Set the front end port number for incoming authentication requests\n                mgmofs.authport 15555\n\n                # Only listen on localhost connections\n                mgmofs.authlocal 1\n\n\nIf you want to run your authentication front-end on a separate machine from the MGM service, you can use ```mgm.authlocal 0```.\nBe aware that you have to protect the given port from 'unwanted' access. There is no authentication involved in the communication from\nfront-end to back-end MGM.\n\nAn example configuration file for a front-end server on the back-end MGM node looks like this:\n\n.. code-block:: bash\n\n                # ------------------------------------------------------------ #\n                [mgm:xrootd:auth]\n                # ------------------------------------------------------------ #\n                xrd.port 2094\n                all.export /\n\n                # the back-end server - localhost in our case\n                eosauth.mgm localhost:15555\n                # number of socket connections - should match thread-pool size if only one front-end exists\n                eosauth.numsockets 64\n                # loglevel\n                eosauth.loglevel info\n\n                xrootd.fslib /usr/lib64/libEosAuthOfs.so\n                xrootd.seclib libXrdSec.so\n                xrootd.chksum adler\n\n                # UNIX authentication + any other type of authentication wanted\n                sec.protocol unix\n                sec.protbind localhost.localdomain unix\n                sec.protbind localhost unix\n                sec.protbind * only unix\n\n\nIf an authentication front-end receives a redirection e.g. from a passive to an active MGM due to HA changes,\nthe front-end server uses redirect-collapse and redirects on the same port as the accessed front-end service - in case of this example this is port 2094!\n\nOne can overwrite the port used for a collapsing redirection using:\n\n\n.. code-block:: bash\n\n                eosauth.collapseport 3094\n\n\n.. NOTE:: This feature is useful if you want to run several front-ends on the same back-end node.\n\n\n\nQClient Configuration\n---------------------\n\n5.3.0 release of EOS introduces features to potentially speed up metadata using\nincreased parallelism. Currently we don't recommend setting these features on\nyour production clusters and only for your testing needs, especially if parallel file creation/deletion\nis a bottleneck encountered in your clusters.\n\n.. code-block:: bash\n\n                #-------------------------------------------------------------------------------\n                # Configuration for Qclient settings\n                #-------------------------------------------------------------------------------\n                # flusher type controls the temporary backend storage for qclient, which can\n                # be utilized in case of crashes where the not acknowledged QuarkDB messages\n                # are replayed. For purely developer clusters options like MEMORY &\n                # MEMORY_MULTI provide faster interfaces which can help debug performance\n                # problems and aid in future development; also TESTING_NULL_UNSAFE_IN_PROD\n                # completely eliminates journalling for pure interface adherence tests\n    # In multithreaded scenarios, we currently track the highest acknowledged message\n    # this behaviour can be controlled by setting ROCKSDB_MULIT:LOW or HIGH respectively\n\n                mgmofs.qclient_flusher_type ROCKSDB # choose between ROCKSDB (default) & ROCKSDB_MULTI\n\n    # For tuning ROCKSDB itself, we provide the following option, please exercise caution\n    # eg: \"write_buffer_size=1073741824;max_write_buffer_number=5;min_write_buffer_number_to_merge=2\"\n    mgmofs.qclient_rocksdb_options  # NOT CONFIGURED by default\n\n    # Path where the persistent storage lives; Only needed when you really need to drop and recreate rocksdb\n    # which is almost never\n    mgmofs.queue_path /var/eos/ns-queue\n\n\nFlatScheduler configuration\n---------------------------\n\nEOS v5.2.0 release introduces a new scheduler where scheduling strategies can be\nconfigured at runtime. These can be enabled on a per space level. The scheduler\nis also weights aware, where a disk is allotted different weights according to\nits capacity, so for groups with heterogeneous disks one has a better filling of\ndisk capacities. Currently we fallback to the classical geoscheduler in case\nvalid placements using any of the scheduling strategies isn't found.\n\n\nScheduling Strategies\n^^^^^^^^^^^^^^^^^^^^^\n\nThe following strategies are currently offered:\n\n+------------------+----------------------------------------------------------------------------------------------+\n| Strategy         | Description                                                                                  |\n+==================+==============================================================================================+\n| ``geo``          | The classical geotree engine, this is the default option                                     |\n+------------------+----------------------------------------------------------------------------------------------+\n| ``weightedrr``   | A strategy that fills weights for disks and distributes roundrobin according to weights      |\n+------------------+----------------------------------------------------------------------------------------------+\n| ``weightedrandom`` | Uses a weightedrandom engine, for randomly choosing according to weights                   |\n+------------------+----------------------------------------------------------------------------------------------+\n| ``random``       | Randomly chooses disks within groups, useful for homogeneous groups                          |\n+------------------+----------------------------------------------------------------------------------------------+\n| ``roundrobin``   | Goes in a roundrobin fashion choosing disks within groups, useful for homogeneous groups     |\n+------------------+----------------------------------------------------------------------------------------------+\n| ``tlrr``         | A more performant version of the roundrobin algorithm, where each MGM thread has its own     |\n|                  | roundrobin. While not as coordinated as a global roundrobin, it should be more or less       |\n|                  | amortized for large enough placements. Useful for homogeneous groups                         |\n+------------------+----------------------------------------------------------------------------------------------+\n\n\n.. code-block:: bash\n\n   # configure scheduler type for a space\n   eos space config <spacename> space.scheduler.type=weightedrandom\n\n\n\nDisk Weight configuration\n^^^^^^^^^^^^^^^^^^^^^^^^^\nFor weighted scheduling, by default disks are weighted as a short unsigned integer, where the number is equivalent to\nthe disk capacity in TB, ie. a disk with 4 TB capacity is allotted a weight of 4 and so forth. This can be\nchanged at runtime to for eg. do certain draining/maintenance operations where the weight can be lowered\nto attract less writes.\n\n.. code-block:: bash\n\n   # configure weight as 0 for disk with fsid 10\n   eos sched configure weight default 10 0\n\n\nAlternative checksums\n---------------------\n\nThe 5.4.0 release introduces support for **alternative checksums**. This feature allows for the computation and storage of multiple checksums for each file (e.g., MD5, SHA-256) in addition to the default one.\n\nThe desired checksums are configured on a **per-directory basis** using an extended attribute. The actual computation can be performed in one of two ways:\n\n* Synchronously: the checksum is computed when the file is uploaded.\n* Asynchronously: the checksum is computed later by a background process on the storage node.\n\n\nEnabling Alternative Checksums on a Directory\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nTo specify which alternative checksums should be computed for files within a directory, set the `sys.altxs`` extended attribute. The value should be a comma-separated list of the desired checksum algorithms.\n\nFor example, to compute MD5, SHA-1, and SHA-256 checksums for all new files in the `/eos/dev/altxs` directory, you would run:\n\n.. code-block:: bash\n\n   eos attr set sys.altxs=\"md5,sha1,sha256\" /eos/dev/altxs\n\n\nAdministrator Configuration\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe following parameters control the behavior of the alternative checksums feature from the administrator's side.\n\nSynchronous computation (during file upload) is controlled at the space level. To enable it for a specific space, set the `altxs` variable to on.\n\n.. code-block:: bash\n\n   eos space config <space_name> space.altxs=on\n\nThe following settings control the asynchronous computation and policy synchronization for the entire filesystem.\n\n.. code-block:: bash\n\n   # Enables or disables the periodic synchronization of alternative checksum\n   # policies (from 'sys.altxs' attributes) from the namespace.\n   # Set to 1 to enable, 0 to disable.\n   eos fs config <fsid> altxs_sync=0 # 0 by default\n\n   # Time interval in seconds after which checksum settings are refreshed from the\n   # namespace. If set to 0, synchronization only happens once.\n   # This parameter is only effective if 'altxs_sync' is enabled.\n   eos fs config <fsid> altxs_sync_interval=0 # default is 0 (sync only once)\n\n   # Time interval in seconds for the background thread that scans the namespace\n   # for files needing asynchronous alternative checksum computation.\n   eos fs config <fsid> scan_altxs_interval=3600\n\n   # The maximum rate (in namespace entries per second) at which the scanner\n   # checks for files that need alternative checksums computed.\n   eos fs config <fsid> scan_altxs_rate=1000\n"
  },
  {
    "path": "doc/diopside/my-changes.patch",
    "content": "diff --git a/console/parser b/console/parser\n--- a/console/parser\n+++ b/console/parser\n@@ -1 +1 @@\n-Subproject commit bfffd37e1f804ca4fae1caae106935791696b6a9\n+Subproject commit bfffd37e1f804ca4fae1caae106935791696b6a9-dirty\ndiff --git a/doc/diopside/manual/microservices.rst b/doc/diopside/manual/microservices.rst\nindex 81cd63cb5..cff7b1aa7 100644\n--- a/doc/diopside/manual/microservices.rst\n+++ b/doc/diopside/manual/microservices.rst\n@@ -2346,10 +2346,62 @@ Audit Logging\n Overview\n ^^^^^^^^\n \n-EOS implements structured audit logging for successful operations that modify the namespace or file metadata. Audit entries are encoded as JSON (one record per line), written directly into ZSTD-compressed log segments, and rotated every 5 minutes. A symlink ``audit.zstd`` always points to the current active segment.\n+EOS implements structured audit logging for successful operations that modify the namespace or file metadata. Audit entries are encoded as JSON (one record per line), written directly into ZSTD-compressed log segments, and rotated every 1 hour by default. A symlink ``audit.zstd`` always points to the current active segment.\n \n This audit logging provides comprehensive tracking of all namespace-affecting operations performed by identified users, enabling security monitoring, compliance reporting, and operational analysis.\n \n+For full documentation, see ``AUDIT.md`` in the EOS source tree.\n+\n+.. index::\n+   pair: Audit Logging; Configuration\n+\n+Configuration and Enabling\n+^^^^^^^^^^^^^^^^^^^^^^^^^^\n+\n+Audit logging is controlled by environment variables read by the MGM at startup. The main variable is ``EOS_MGM_AUDIT``:\n+\n+**``EOS_MGM_AUDIT``** — overall audit level:\n+\n+- ``off``, ``none``, ``false``, ``no``, or empty: disable all auditing (audit logger not created)\n+- ``default``: audit modifications (CREATE, DELETE, RENAME, etc.) and READ for document-style files (txt, pdf, doc, etc.); LIST off\n+- ``modifications``: audit only modifications; no READ, no LIST\n+- ``detail``: audit modifications and READ for all files; LIST off\n+- ``all``: audit everything including LIST and READ for all files\n+- ``attribute``: create the audit logger but disable global auditing; enable per directory via ``sys.audit`` extended attribute\n+\n+**``EOS_MGM_AUDIT_READ_SUFFIX``** — override the READ suffix filter (comma-separated, case-insensitive):\n+\n+- Example: ``pdf,docx,json``\n+- Use ``*`` to audit READ for all files\n+- If unset, the built-in document-style list is used\n+\n+**``EOS_AUDIT_ROTATION``** — segment rotation interval in seconds (default: 3600):\n+\n+- Example: ``EOS_AUDIT_ROTATION=300`` for 5-minute rotation\n+\n+**Example: enable audit with default settings**\n+\n+.. code-block:: bash\n+\n+   export EOS_MGM_AUDIT=default\n+   # MGM will audit modifications and READ for document files\n+\n+**Example: disable audit**\n+\n+.. code-block:: bash\n+\n+   export EOS_MGM_AUDIT=off\n+\n+**Per-directory auditing (``sys.audit``)**\n+\n+When ``EOS_MGM_AUDIT=attribute``, global auditing is disabled and auditing is enabled per directory via the extended attribute ``sys.audit`` on the parent directory (for files) or the directory itself (for LIST). Valid values (case-insensitive):\n+\n+- ``none``, ``no``, ``false``, ``off``: disable auditing for that directory\n+- ``modifications``: modifications only\n+- ``default``: modifications and READ (default document suffixes)\n+- ``detail``: modifications and READ for all files\n+- ``all``: everything including LIST and READ for all files\n+\n .. index::\n    pair: Audit Logging; Scope\n \n"
  },
  {
    "path": "doc/diopside/releases/#diopside-release.rst#",
    "content": ":orphan:\n\n.. highlight:: rst\n\n.. index::\n   pair: Releases; Diopside\n\n\nDiopside Release Notes\n===========================\n\n``Version 5 Diopside``\n\nIntroduction\n------------\n\nThis release is based on XRootD V5.\n\n``v5.4.1 Diopside``\n===================\n\n2026-03-18\n\nBug\n----\n\n* [EOS-6570] - Fuse file move does not update tree size and no. of files\n* [EOS-6571] - FST crashes due to SIGFPE\n* [EOS-6576] - FST individual stripe checksum not properly bandwidth limited\n\nImprovement\n------------\n\n* [EOS-3963] - Retire Schedule2Delete in favor of the FST manually checking its own unlinked file list\n\nUser Documentation\n--------------------\n\n* [EOS-6574] - Document FUSE Client recovery avoiding Reboot for FNAL\n\n\n``v5.4.0 Diopside``\n===================\n\n2026-02-27\n=======\n``v5.4.0 Diopside``\n===================\n\n2025-12-08\n>>>>>>> 05d4121e2 (DOC: Update documentation with respect to recycle bin configuration changes)\n\nMajor Change\n------------\n\n* **Recycle Bin Configuration Update**: The recycle bin configuration has been refactored.\n  Please see :doc:`5.4.0/recycle_bin_config` for important migration instructions regarding new configuration commands and cleanup of legacy attributes.\n\n\n``v5.3.31 Diopside``\n====================\n\n2026-02-27\n\nFeature\n-------\n\n* [EOS-6563] MGM: LRU - Add LRU option to clean files based on their size and age\n* MGM: Quota: Do not multiply bookingsize by number of filesystems\n\n``v5.3.30 Diopside``\n====================\n\n2026-02-17\n\nBug\n---\n\n* [EOS-6566] MGM: Avoid dropping replicas sitting on unmounted/unavailable disk during fsck repair procedure.\n\n\n``v5.3.29 Diopside``\n====================\n\n2026-02-09\n\n* Update dependency to xrootd/eos-xrootd 5.9.1 release.\n\n\n``v5.3.28 Diopside``\n====================\n\n2026-02-05\n\nBug\n----\n\n* MGM: Fix handling of secondary groups that might be missing when an internal cache clean-up is done.\n* FUSE: Serialise fetching metadata of same inode. Fixes a race condition between listing/no-listing requests seen by JRC.\n=======\n>>>>>>> 05d4121e2 (DOC: Update documentation with respect to recycle bin configuration changes)\n\n\n``v5.3.27 Diopside``\n====================\n\n2025-11-26\n\nBug\n----\n\nMGM: Delete the configuration engine object during the MGM shutdown\nto avoid any possibility that this is reused during the destruction\nof the FsView that might in turn lead to deletion of file systems\nfrom the configuration.\n\n\n``v5.3.26 Diopside``\n====================\n\n2025-11-13\n\nBug\n----\n\n* [EOS-6514] - Make more robust file inspector stats load\n* [EOS-6534] - Rename via gRPC does not respect ACLs\n* [EOS-6536] - eosxd crash on el10 node\n* [EOS-6540] - SIGSEGV in recompute_tree_size\n\n\n``v5.3.25 Diopside``\n====================\n\n2025-10-30\n\nBug\n----\n\n* [EOS-6525] - EOS token scope not set for directory listings\n* [EOS-6527] - FST: HTTP - Range-Request with outer value > file size should not send back an error\n* [EOS-6528] - CLI: Acl - Cannot set 't' ACL via eos acl command\n* [EOS-6529] - space-level \"sys.acl\" gets persistently duplicated after further ACL changes\n\nNew Feature\n------------\n\n* [EOS-6276] - Track and enforce EOS quotas by logical bytes instead of raw physical bytes\n* [EOS-6520] - Create a command that does move and symlink at once\n\nImprovement\n------------\n\n* [EOS-6426] - NS: Replace with the new NS locking recompute_tree_size\n\n\n``v5.3.24 Diopside``\n====================\n\n2025-10-08\n\nBug\n----\n\n* [EOS-6500] - Malformed OAUTH token can crash MGM\n\n``v5.3.23 Diopside``\n====================\n\n2025-10-02\n\nBug\n----\n\n* [EOS-6500] - Malformed OAUTH token can crash MGM\n* [EOS-6501] - [MGM] EosToken - Do not validate path for tape REST API access\n* [EOS-6503] - GRPC find request with token ACL gets error - path outside token scope\n\nImprovement\n------------\n\n* [EOS-6508] - Support multi-path EOS tokens with shared ACL\n\n\n``v5.3.22 Diopside``\n====================\n\n2025-09-19\n\nBug\n----\n\n* [EOS-6487] - [HTTP] HTML-escape body of error messages before replying to the client\n* [EOS-6489] - TPC for direct I/O enabled FSTs fails\n* [EOS-6495] - Support EOS tokens to run find command\n* [EOS-6497] - Support EOSAUTHZ env for 'eos cp' commands\n* [EOS-6498] - Disable synchronously stripe checksum computation\n\nImprovement\n------------\n\n* [EOS-6483] - Allow Space attributes to define WFE trigger\n* [EOS-6488] - Rate limit scan of stripes\n\n\n``v5.3.21 Diopside``\n====================\n\n2025-09-02\n\nBug\n----\n\n* [EOS-6449] - Stripe checksum rescanned after upload\n* [EOS-6482] - [MGM] HTTP - Cannot set eos.app in case grpc/https authentication is used\n* FST: Properly register recovered stripes for RAIN files when recovery does\n  not happen at the entry server. Fixes recovery of under-replicated RAIN files.\n\n\n``v5.3.20 Diopside``\n====================\n\n2025-08-28\n\nBug\n----\n\n* [EOS-6480] - FuseServer needs to use Space ACLs in FillContainerCAP & ValidatePERM\n* [EOS-6481] - Listing file names longer than 4k crashes the MGM\n\n\n``v5.3.19 Diopside``\n====================\n\n2025-08-22\n\nBug\n----\n\n* FST: Make sure that during RAIN file recovery also the newly created stripes\n  are properly registered in the namespace.\n\n\n``v5.3.18 Diopside``\n====================\n\n2025-08-21\n\nNote\n-----\n\n* Update dependency eos-xrootd/xrootd to version 5.8.4.\n\nBug\n----\n\n* [EOS-6473] - FST \"failed to parse metadata info\" for corrupted namespace entries\n* [EOS-6474] - FST \"failed to parse metadata info\" for \"checksum=none\"\n* [EOS-6478] - Filesystem setup fails to create .eosfsid file\n\n\n``v5.3.17 Diopside``\n====================\n\n2025-08-07\n\nBug\n----\n\n* [EOS-6467] -  EOS file encryption : easy to lose content when using file encryption\n* [EOS-6468] - \"eos fsck report --error ...\" mis-counts entries\n* [EOS-6471] - FST validity expires after stalls of more than 60 seconds\n\n\nNew Feature\n------------\n\n* [EOS-6458] - RFE: \"EOS token\" file per FUSEX mountpoint\n\nImprovement\n------------\n\n* [EOS-6466] - RFE: \"best effort\" repair to recreate replicas for empty files (missing all replicas)\n\n\n``v5.3.16 Diopside``\n====================\n\n2025-08-01\n\nBug\n----\n\n* [EOS-6283] - non-default \"max_drain_threads\" not persisted over MGM restarts\n* [EOS-6433] - FUSEX \"touch\" causes \"Status: fuse::missingcommmits\"\n* [EOS-6440] - left-behind namespace entry with \"Container #0 not found\", for deleted file\n* [EOS-6445] - Repair failed due to unscanned replica\n* [EOS-6456] - Converter uses filename internally, fails on '?' '&'\n* [EOS-6457] - drainfailure: wrong (=empty) MGM+FST1 checksum prevents drain\n* [EOS-6460] - Race condition in EOS set ACL\n* FST: Disable individual stripe checksums for RAIN files until existing\n  performance limitations are addressed.\n\nImprovement\n------------\n\n* [EOS-6100] - \"file adjustreplica\" says \"No such file or directory\", should say \"file already tracked\"\n* [EOS-6257] - RFE: \"quota ls\" without need for 'adm' membership or 'q' ACL\n* [EOS-6313] - RFE: auto-retry draining for files with \"Operation expired\"\n* [EOS-6396] - One replica to be fixed by fsck best effort\n* [EOS-6446] - fsck best effort fix one replica existing when mgm believes is xs 0\n* [EOS-6455] - MGM: WFE - prepare - don't update the requestID if it does not change\n* [EOS-6454] - MGM,FST: Add jwt to grpc WFE calls\n\nNew Feature\n------------\n\n* MGM: Add support for space level extended attributes\n\n\n``v5.3.15 Diopside``\n====================\n\n2025-06-23\n\nImprovements\n------------\n\n* [EOS-6434] - MGM: PROPFIND - Display D flag if ACL forbids deletion but user is owner\n* [EOS-4720] - FST: Return EAGAIN instead of EBUSY in case a file is opened for writing via HTTP\n* [EOS-6436] - MGM: Acl - Log the ACL modification that took place\n* [EOS-6432] - MGM: Remove unnecessary if statement\n\n\n``v5.3.14 Diopside``\n====================\n\n2025-06-10\n\nBug\n----\n\n* FST: Fix memory leak when scanning RAIN files\n* FST: Fix possible crash (ABRT) when wrong type of object is being printed\n* FST: Fix crash when scanning RAIN files with less than the expected number of stripes\n* [EOS-6423] - Avoid creating orphan leftovers on open with create flag fails\n\n\n``v5.3.13 Diopside``\n====================\n\n2025-05-26\n\nBug\n----\n\n* FST: Fix undefined behaviour due to order of evaluation of the method\nparameters which was leading to files not having the checksum value set.\n\n\n``v5.3.12 Diopside``\n====================\n\n2025-05-21\n\nNote\n-----\n\n* Update eos-xrootd dependency to version 5.8.2 which matches xrootd-5.8.2\nplus an important fix for missing responses in the XRootD client.\n\n\nBug\n----\n\n* [EOS-6316] - Crash when handling Report regex\n* [EOS-6406] - Fix eoscta report log formatting\n* [EOS-6408] - Potential mutex deadlock while issuing eos df command\n* [EOS-6409] - rapid \"rep_missing_n\" increase\n\nImprovement\n-----------\n\n* [EOS-6300] - Improve RAIN scanning load by using full stripe checksums or other mechanisms\n* [EOS-6407] - Add possibility to recover QDB backup in raft-mode\n* [EOS-6414] - Use adler32 for stripe checksum\n* Improved namespace locking for bulk file deletion with recycle-bin policies\n\n\n``v5.3.11 Diopside``\n====================\n\n2025-05-07\n\nBug\n----\n\n* [EOS-6042] - QDB stuck in publishing\n* [EOS-6358] - MGM: Null group in FsView::mGroupView (segfault in heartbeat check)\n* [EOS-6360] - Removal of xattr not possible via gRPC\n* [EOS-6361] - Propagate update to FSTs when symkey is update on the MGM\n* [EOS-6379] - RAID5 requires 6 stripes\n* [EOS-6383] - eoscp PIO open mode does not work with larger block sizes\n* [EOS-6398] - [MGM] A user should be able to \"rename\" a file they own regardless of !d ACL\n\nNew Feature\n------------\n\n* [EOS-6386] - Add thread names where relevant\n* [EOS-6392] - Force overriding an existing symlink\n\nImprovement\n------------\n\n* [EOS-6359] - On the HTTP interface do not return HTML/CSS formatted\n* [EOS-6363] - Clean up old removed nodes from the global config map\n* [EOS-6399] - MGM: Use ADM_UID and ADM_GID\n\n\n``v5.3.10 Diopside``\n====================\n\n2025-04-07\n\nNote\n-----\n\n* Update eos-xrootd/xrootd dependency to version 5.8.0\n\n\nBug\n----\n\n* [EOS-6356] - Mapping: constant hit of LDAP for secondary accounts\n* [EOS-6364] - [MGM] EOS ACL recursive settings breaks ACLs already set\n\n\n\nNew Feature\n------------\n\n* [EOS-6368] - Implement eos-ports-block and eos-ports-reset-default\n* [EOS-6370] - https gfal-ls with redirector does not work\n* [EOS-6371] - Add gRPC support for MGM/FST - CTA Frontend protobuf communication\n\n\n``v5.3.9 Diopside``\n====================\n\n2025-03-06\n\nBug\n----\n\n* [EOS-6330] - HEAD requests are not executed on FSTs\n* [EOS-6348] - GRPC: set attribute does not honor the recursive flag\n\nNew Feature\n------------\n\n* [EOS-6349] - Support qclient persistency layer type in config/ns output\n\nImprovement\n-----------\n\n* [EOS-6331] - Print start and stop of file sync operations in the FST log\n* [EOS-6353] - RFE: GRPC with TLS but _not_ certificate auth\n\n``v5.3.8 Diopside``\n====================\n\n2025-03-04\n\nBug\n----\n\n* [EOS-6217] - eosxd looping in async open during write recovery\n* [EOS-6326] - fusex: crash in eosxd (fusex) around Proxy::Factory\n* [EOS-6347] - Conversion policies should better handle N/A file systems\n* MGM: Improvements to the geotree update process when running without MQ.\n* MGM/FST: Fix fs registration bug when running with MQ due to the deletion\n  of the shared hash object.\n\n\n``v5.3.7 Diopside``\n====================\n\n2025-02-26\n\nBug\n-----\n\n* [EOS-6339] - Do not abort if a configuration entry is wrong\n\nNew Feature\n-------------\n\n* Add a very basic eos-diagnostic-tool\n\n``v5.3.6 Diopside``\n====================\n\n2025-02-24\n\nBug\n-----\n\n* [EOS-6338] - Gateway REST API hangs on p2\n\n``v5.3.4 Diopside``\n====================\n\n2025-02-17\n\nBug\n-----\n\n* [EOS-6327] - MGM: MGM crash in eos::common::FileSystem::getCoreParams(this=0x0)\n\n``v5.3.5 Diopside``\n====================\n\n2025-02-21\n\nBug\n----\n\n* [EOS-6332] - MGM crash in ReplicationTracker\n* [EOS-6333] - GeoScheduler views are not always updated with no-MQ\n* [EOS-6334] - MGM slave tries to save Iostat configuration\n* [EOS-6336] - Possible FST deadlock on the mFsMutex\n* [EOS-6337] - Memory leak when handling HTTP chunk uploads\n\n\n``v5.3.4 Diopside``\n====================\n\n2025-02-17\n\nBug\n----\n\n* [EOS-6327] - MGM crash in eos::common::FileSystem::getCoreParams(this=0x0)\n\n\n``v5.3.3 Diopside``\n====================\n\n2025-02-14\n\nBug\n-----\n\n* [EOS-6243] - MGM stuck on EOSHOME-I04 (no reply to \"ns stat\")\n* [EOS-6247] - non-removable \"eos access\" rule\n* [EOS-6277] - FS error status not reset at successful boot\n* [EOS-6317] - Starting conditional EOS services: Too few arguments.\n* [EOS-6318] - _access lines >52% of xrdlog.mgm\n* [EOS-6322] - Regression in CTA archiving\n* [EOS-6324] - eos find on / fails\n* [EOS-6325] - MGM crash around DrainFs::UpdateFinishedJob\n* [EOS-5992] - RFE: faster MGM graceful restart (\"systemctl restart eos@mgm\")\n\nNew Feature\n-------------\n\n* [EOS-6310] - MGM: HTTP - Allow users to overwrite eos.app tag via HTTP path opaque query\n\n\n``v5.3.2 Diopside``\n====================\n\n2025-02-10\n\nBug\n----\n\n* MGM: Fix possible deadlock during draining and fix drain counters\n\n\n``v5.3.1 Diopside``\n====================\n\n2025-02-06\n\nNote\n-----\n\n* Update eos-xrootd/xrootd dependency to version 5.7.3\n* Update eos-grpc-gateway dependency to version 0.2.0\n\n\nBug\n----\n\n* [EOS-6269] - e-group membership does not seem to synchronize\n* [EOS-6279] - GRPC: honor the \"app\" attribute on upload and setAttr\n* [EOS-6282] - \"eos whoami\" abort()s\n* [EOS-6294] - eos: ipc socket protection from user crafted input\n* [EOS-6306] - FST keeps deleted SharedHash obj in memory\n* [EOS-6311] - file read handle caching used for full file http GET\n* [EOS-6314] - SIGUSR2 overwrites stacktraces\n\nImprovement\n------------\n\n* [EOS-6182] - GRPC: extend all requests to improve traceability\n* [EOS-6248] - Persist last run of inspector\n* [EOS-6271] - RFE: log \"banned\" identity\n* [EOS-6288] - RFE: align GRPC to other clients when dealing with hardlinks and tombstones\n* [EOS-6301] - RFE: \"eos find --purge atomic\" should bypass recycle bin\n* [EOS-6303] - Clients.log: review logline \"::open   acl= r= w= wo= egroup= shared= mutable= facl=\"\n\n\n``v5.3.0 Diopside``\n====================\n\n2024-12-03\n\nBug\n----\n\n* [EOS-4297] - mkdir in CLI does not throw EEXIST\n* [EOS-5012] - \"recycle config –lifetime\" only accepts value in seconds\n* [EOS-5266] - Wrong password file sends eos-ns-inspect into an endless error loop\n* [EOS-5307] - recycle bin purging cannot delete files with '->' in the name\n* [EOS-5748] - TPC job timeout can corrupt the RAIN stripes it should recover\n* [EOS-5847] - FST bootfailures (due to race condition?)\n* [EOS-5909] - high rate of CRIT: \"Attempted to add file with name..while a different file exists already there.\"\n* [EOS-5936] - quarkdb-validate-checkpoint aborts when opening \"too many\" .sst files\n* [EOS-5940] - MGM lockup for several minutes (but recovered)\n* [EOS-5950] - Undrainable \"cannot retrieve file meta data\"-files\n* [EOS-6014] - WIP: Inconsistencies between old and new find\n* [EOS-6031] - several eosViewRWMutex \"locked\" episodes after MGM restart\n* [EOS-6042] - QDB stuck in publishing\n* [EOS-6118] - \"eos fs mv\" between FSTs should keep existing \"group\"\n* [EOS-6126] - Recovery OpenAsync cannot open file anymore in eosxd\n* [EOS-6128] - Files written with UTF8 characters when not allowed\n* [EOS-6144] - Filenames with a special word break the EOS CLI\n* [EOS-6146] - undrainable \".sys.a\" files (wrong checksum), possibly after \"atomic\" upload from CERNBox\n* [EOS-6152] - Find for path that contains symlink fails\n* [EOS-6153] - fs boot command remove the default disk sync flag\n* [EOS-6155] - Touch should NOT require 10737418240 bytes as booking size\n* [EOS-6158] - Drain race condition leaving files in the tracker\n* [EOS-6173] - Corrupted file entries after namespace failover\n* [EOS-6178] - Misleading error message \"Invalid argument\" for command eos cp\n* [EOS-6179] - Cannot remove gid membership via eos vid rm membership\n* [EOS-6181] - eos -j JSON format changed\n* [EOS-6187] - Some 0-length files are not reported as being on disk\n* [EOS-6189] - [Acl] Recursive setting of ACL stops if at least one _attr_set() failure happens on a directory\n* [EOS-6191] - Silent fail when removing file with weird characters\n* [EOS-6192] - eos ls can not display files containing ampersand characters\n* [EOS-6195] - [FST] Write recovery - Avoid deleting a file that successfully got written during the write recovery transfer\n* [EOS-6198] - MGM - Globbing does not properly work\n* [EOS-6202] - eos file tag not working with fid:/fxid:\n* [EOS-6204] - SIGUSR1 stacktraces (/var/eos/md/stacktrace.TIME) should not be world-writeable\n* [EOS-6205] - FUSEX: timing-related access issue (initial \"No such file or directory\" (Kerberos, ACRON)\n* [EOS-6211] - fst segfault or hang, async close triggered during XrdFstOfsFile destructor\n* [EOS-6217] - eosxd looping in async open during write recovery\n* [EOS-6220] - Balancing should take into consideration the FileSystem configstatus\n* [EOS-6233] - MGM stuck on EOSHOME-I00 for 8min\n* [EOS-6234] - Persist redirect access configuration\n* [EOS-6235] - [MGM] Potential deadlock on rename during quota nodes fetch\n\nNew Feature\n------------\n\n* [EOS-5648] - FSCK: Contemplates files (and containers!?) that are detached from the namespace tree\n* [EOS-6165] - Limit number of staging requests allowed on EOSCTA\n* [EOS-6201] - [MGM] Tape REST API - Implement \"default\" targeted metadata handling\n* [EOS-6256] - MGM/FST: Adding retry mechanism for failed CTA Frontend DNS resolution\n\nTask\n-----\n\n* [EOS-6132] - HTTP - Return 424 \"Failed dependency\" for files stored on tape with no disk copy\n* [EOS-6170] - Push EL9 docker images to registry\n* [EOS-6180] - [eoscp] Preserve file' creation timestamp with --preserve option\n* [EOS-6200] - MGM - HTTP Take into account OpenWriteCreate limit\n* [EOS-6228] - [FST] HTTP - Add pmark.appname to adapt with the new scitags specifications\n\nImprovement\n------------\n\n* [EOS-3064] - QuarkDB: use common logging format, incl human-readable timestamps\n* [EOS-3319] - Drop usage of rand() throughout eos\n* [EOS-3538] - Add detection of files in \"deletion limbo\" to eos-ns-inspect\n* [EOS-3601] - Remove stdOut, stdErr and retc variables from IProcCommand interface\n* [EOS-4584] - RFE: \"eos acl --list\" to return both 'user' and 'sys' ACLs by default, allow specifying both\n* [EOS-4640] - eos-ns-inspect force exit when crosstalk errors happen\n* [EOS-5078] - eos member command argument check\n* [EOS-5310] - Shard conversion files in the top level `/eos/.../proc/conversion/` directory\n* [EOS-5311] - Reduce ConverterDriver dependency on QDB and improve performance\n* [EOS-5639] - Add file metadata to file deletion requests in eosreport\n* [EOS-5726] - \"vid gateway add/remove\" and \"vid ls\" output format(s)\n* [EOS-5828] - Propagte number of files/dirs (treeCount)\n* [EOS-5846] - \"rename\" (between directories) should honour \"!d policy\" (others?)\n* [EOS-5994] - faster shutdown of \"recycler server\"\n* [EOS-5997] - faster shutdown after \"finalizing namespace views\": gOFS->namespaceGroup.reset()\n* [EOS-6000] - Add 'paranoid' repair option to FSCK\n* [EOS-6093] - Add ns command to display the list of tracked files\n* [EOS-6123] - RFE: do not \"recycle\", \"drain\", \"balance\" atomic files - just delete, avoid creating them\n* [EOS-6127] - FSCK repair besteffort for MGM checksum 0 and only one replica\n* [EOS-6130] - RFE: metric for NS caches \"hit rate\"\n* [EOS-6137] - FST slow boot: heavy stat() from eos::fst::FmdAttrHandler::ResetDiskInformation ?\n* [EOS-6188] - NS Locking opt - Refactor ContainerAccounting's queue for update to avoid deadlocks\n* [EOS-6196] - RFE: allow to turn off \"globbing\"\n* [EOS-6206] - eos archive should handle retries for various CTA failures\n* [EOS-6215] - RFE: do no require \"sudoer\" role for internal components+already-privileged accounts\n* [EOS-6231] - Remove old Recycle implementation\n* [EOS-6236] - Add eos space config rm command\n* [EOS-6249] - high-rate logs: FuseServer::Clients::RefreshEntry\n* [EOS-6250] - high-rate logs: ::ProcessReq msg=\"normalize hdr\"\n* [EOS-6258] - high-rate logs: HttpHandler::HandleRequest() header logging\n* [EOS-5985] - Improve eos rmdir error message\n\n\n``v5.2.28 Diopside``\n====================\n\n2024-10-17\n\nBug\n----\n\n* [EOS-6065] - MGM memory increase/leak (EOSHOMEs)\n* [EOS-6217] - eosxd looping in async open during write recovery\n\n\n``v5.2.27 Diopside``\n====================\n\n2024-10-01\n\nNote\n-----\n\n* This release is targeted for the CTA use-case as it's built with eos-xrootd/xrood 5.7.1\n  that contains some HTTP header passing functionality required for CTA.\n* Built with eos-xrootd/xrootd 5.7.1\n\n\n``v5.2.26 Diopside``\n====================\n\n2024-10-01\n\nBug\n----\n\n* [EOS-6205] - FUSEX: timing-related access issue (initial \"No such file or directory\" (Kerberos, ACRON)\n* [EOS-6207] - eos fusex crash\n* [EOS-6211] - fst segfault or hang, async close triggered during XrdFstOfsFile destructor\n\nNew feature\n------------\n\n* [EOS-6200] - MGM - HTTP Take into account OpenWriteCreate limit\n\n\n``v5.2.25 Diopside``\n====================\n\n2024-07-05\n\nNote\n----\n\n* This EOS release is based on eos-xrootd-5.6.11 which itself bring important fixes like\n  - memory leaks in the XRootD python bindings\n  - fixes to crashes seen in production with EOS etc.\n\nBug\n----\n\n* [EOS-6087] - [eoscp] Intermittent segmentation faults in LHCb datamovers\n* [EOS-6155] - Touch should NOT require 10737418240 bytes as booking size\n* [EOS-6172] - man eos-ls wrong formatting\n* [EOS-6197] - Report: Undefined behavior in constructor if sec.host is an empty string (deletion)\n* [EOS-6126] - Recovery OpenAsync cannot open file anymore in eosxd\n\n\n``v5.2.24 Diopside``\n====================\n\n2024-05-23\n\nBug\n---\n\n* [EOS-6112] - Remove reliance on 'errno' from _dropallstripes() and other functions MGM(CTA)\n* [EOS-6148] - Too many levels of symbolic links unexpectedly reported on eosxd mounted fs\n\nNew Feature\n------------\n\n* [EOS-6150] - Print archive metadata in eoscta report MGM(CTA)\n* Add new eos-mgm-monitoring package containing a series of helper scripts for monitoring.\n\nImprovement\n------------\n\n* [EOS-6139] - MGM - HTTP GET issues 2 consecutive stats instead of only one\n\n\n``v5.2.23 Diopside``\n====================\n\n2024-04-30\n\nNote\n----\n\n* Update eos-xrootd dependency to 5.6.10 - this version includes important\n  optimizations for the use of OpenSSL 3.\n\nBug\n----\n\n* [EOS-5972] - rising \"HB is stuck\" time, apparent deadlock wait_upstream/mdcflush\n* [EOS-6109] - Rename - Deadlock with concurrent renames\n* [EOS-6120] - deadlock during EosFuse::mkdir\n\nImprovement\n------------\n\n* ALL: Many compilation warning fixes\n\n\n``v5.2.22 Diopside``\n====================\n\n2024-04-09\n\nBug\n----\n\n* [EOS-6116] - FUSEX: fix eosxd callback handler when a file is moved on top of an existing file\n* [EOS-6115] - FUSEX: fix invisible directories if the name had been put into the ENOENT cache\n* [EOS-6111] - FST: mark readV errors as read IO errors in the report log\n* [EOS-6110] - MGM: fix loop in devices thread in non-master MGMs\n* FST - fix interface speed reading\n\n\nImprovement\n------------\n\n* [EOS-6117] - FST: ErrorReports are suppressed on FSTs when over 4 Hz to 1Hz + marker\n* [EOS-6114] - FUSEX: eosxd and MGM share the same assumption, that as an owner of directory you can delete a file of another person even if !d was specified for the group\n\n\n``v5.2.21 Diopside``\n====================\n\n2024-03-25\n\nBUG\n\n* [EOS-6105] - fix credential validation in ALMA9 container under chroot environments\n\n``v5.2.20 Diopside``\n====================\n\n2024-03-21\n\nBug\n---\n\n* [EOS-6091] - Update PersistentSharedHash before publishing updates\n* [EOS-6101] - fs rm no longer sends a notification to the FST\n\n\n``v5.2.19 Diopside``\n====================\n\n2024-03-12\n\n\nNote\n----\n\n* Update dependency to xrootd/eos-xrootd 5.6.9\n\nBug\n----\n\n* [EOS-6085] - EOSPUBLIC mgm crash during BroadcastDeletionFromExternal in rename\n* [EOS-6088] - MGM aborts with \"what():  std::bad_alloc\" under eos::mgm::FuseServer::Caps::BroadcastDeletionFromExternal\n\n\n``v5.2.18 Diopside``\n====================\n\n2024-03-07\n\nBug\n----\n\n* [EOS-6075] - [eoscp] memory leaks and context errors\n* [EOS-6078] - eos archive segv in xrootd prepare\n* [EOS-6079] - Credential validation fails in chroot container with non local jail lookup\n* [EOS-6080] - \"eos find --purge atomic\" can lock up namespace\n* [EOS-6081] - \"eos find --purge atomic\" can cause slow restarts (FSCK loads one big hash at startup)\n* [EOS-6082] - MGM crash from early \"eos ns stat\" command (under eos::common::ThreadPool::GetInfo)\n* [EOS-6084] - \"Scheduler is not yet initialized\" from early setDiskStatus() (possible: drain?)\n\n\nNew Feature\n------------\n\n* [EOS-6045] - Monitor number or kworker processes with 'eos node ls --sys'\n\n\nImprovement\n------------\n\n* [EOS-5185] - FUSEX can not write to logical quotas <= 5GB (hardcoded limit)\n* [EOS-5835] - MGM: remove internal redirect for \"/\" to port 8443\n\n\n``v5.2.17 Diopside``\n====================\n\n2024-02-29\n\nNote\n----\n\n* Update dependency XRootD/eos-xrootd to 5.6.8\n\n\nBug\n----\n\n* [EOS-6061] - Disk drain failure, replicas are on disk, but adjustreplica fails to replicate\n* [EOS-6062] - MGM: \"fs mv\" randomly \"forgets\" filesystems\n* [EOS-6064] - MGM stuck (namespace locking)\n* [EOS-6066] - eos cp -r (recursive copy) uses \"find\", does not work on redirection (?)\n* [EOS-6070] - FST aborts with \"what():  basic_string::_S_construct null not valid\" under eos::fst::ScanDir::CheckFile()\n* [EOS-6074] - Crash in FlatScheduler\n\nImprovement\n------------\n\n* [EOS-6048] - RFE: FST should not \"check for Fmd xattr conversion\" at boot\n\n\n``v5.2.16 Diopside``\n====================\n\n2024-02-16\n\nBug\n----\n\n* [EOS-6051] - MGM: fix crash in FSScheduler caused by edgecases at boot time\n\n\n``v5.2.15 Diopside``\n====================\n\n2024-02-15\n\nBug\n----\n\n* [EOS-6044] - FUSEX: fix 0-pointer access into data object map - fixes EOS-6044\n* [EOS-6046] - MGM: flat scheduler know honours configuration changes on filesystems immediately\n\nNew Feature\n-----------\n\n* MGM - return EBUSY and HTTP::CONFLICT when opening a file locked via the xattr interface (collaborative editing)\n\n  ``v5.2.14 Diopside``\n====================\n\n2024-02-13\n\nBug\n----\n\n* [EOS-6009] - FUSEX: don't overwrite FILE:/!tmp locations as KRB5 default location\n* NS: Catch exception in FutureVectorIterator destructor\n\n\n``v5.2.13 Diopside``\n====================\n\n2024-02-12\n\nBug\n----\n\n* [EOS-3898] - EOS permissions system incorrectly requires an explicit '+u' privilege for the root user\n* [EOS-4763] - ACL set argument 'foo:foo:+d' does not work\n* [EOS-4796] - Not consistent behaviour when setting user.acl with attr set and acl --user\n* [EOS-6009] - FUSEX: fix retrieval of default kerberos crednetial location if not under FILE:/tmp/\n* [EOS-6013] - FUSEX: fix hash function used to cache connections to distinguish container credentials using identical internval paths\n* [EOS-6016] - MGM crash during shutdown in eos::mgm::ConverterDriver::ScheduleJob()\n* [EOS-6025] - MGM: accumulating \"atomic\" version files (from sync client) if out of volume quota\n* [EOS-6029] - MGM (subprocess?) crash in qclient::FollyFutureHandler::stage()\n* [EOS-6038] - MGM misses broadcast message to deal with renames\n* MGM: fix 'find --fileinfo --cache'\n* FST: fix publishing of 'xrootd' version in 'node ls --sys'\n* CONSOLE: fix broken 'eos report' for reads\n\n\nNew Feature\n------------\n\n* [EOS-5614] - FUSEX: bypass deletion through recycle bin, if a file is deleted while still open for writign\n* [EOS-5879] - [eoscp] Add the possibility to see the version of the command\n* [EOS-5956] - Implement default XRootD Attribute functions for xrootd prefixes\n* [EOS-6040] - GRPC: implement reycle bin listing with date/index filter\n* FUSEX: code refactoring allowing to reuse functionality of eosxd authentication in eoscfsd\n* CFSD: adding POSIX passthrough filesystem implementation packaged in new RPM eos-cfsd\n\nImprovement\n------------\n\n* [EOS-2373] - Inconsistent handling of linked attributes in attr_ls and attr_get\n* [EOS-5614] - Fuse skip recycle bin for known broken files\n* [EOS-5717] - [eos-archive] Review the workflow + files with no checksum on destination make the tool crash\n\nReverted\n--------\n\n* MGM/CONSOLE: reverted removing 'eos old find' implementation\n\n\n``v5.2.12 Diopside``\n=========================\n\n2024-02-11\n\nBug\n---\n\n* FST: Fix overflow when reading file larger than 4GB during rain-check\n* FST: Fix reading of the network speed value\n* MGM: avoid parallel computation of the currently used physical space and cache for 2 minutes\n* REVERT: COMMON: RWMutex: lock the mutex name map before finding items\n\n\n``v5.2.11 Diopside``\n=========================\n\n2024-02-06\n\nNote\n----\n\n* Update eos-xrootd/xrootd dependency to 5.6.7\n\nBug\n----\n\n* [EOS-6028] - EOS: ACL command help displays wrong option\n\n\n``v5.2.10 Diopside``\n=========================\n\n2024-02-02\n\nBug\n----\n\n* [EOS-6022] - mkdir -p does not broadcast properly to eosxd clients\n\n\n``v5.2.9 Diopside``\n=========================\n\n2024-02-02\n\nBug\n----\n\n* [EOS-6012] - Fix crash in eos::mgm::ConversionJob::Merge() when logging error message\n\n\n``v5.2.8 Diopside``\n=========================\n\n2024-01-29\n\nBug\n----\n\n* MGM: Add legacy find command implementation for old clients.\n\n\n``v5.2.7 Diopside``\n=========================\n\n2024-01-26\n\nNote\n----\n\n* Update eos-xrootd/xrootd dependency to 5.6.6\n\nBug\n---\n\n* [EOS-5770] - \"eos node ls --sys\" - messed-up formatting (newline after \"sockets\"?)\n* [EOS-5877] - MGM crash while registering new FST\n* [EOS-5934] - FST \"failed to parse metadata info\" for existing filenames prevents EA conversion\n* [EOS-5949] - undrainable \"fuse::needsflush\" file - outdated \"mgmsize\" does not match on-disk size\n* [EOS-5986] - Add support for long filename (> 2kB) for Getfmd requests\n* [EOS-5987] - RWMutex: concurrent modification of the Mutex Name map\n* [EOS-5988] - MGM: concurrent modification of sync Time Accounting class\n* [EOS-5989] - concurrent modification of RWMutex at configure stage\n* [EOS-5993] - MGM: do not log SYMKEY on start\n* [EOS-5998] - FST crash under eos::fst::RainMetaLayout::Open()\n* [EOS-5999] - Connection Idle timeouts create broken FUSE replicas\n* [EOS-6006] - EOS MGM lockup/unresponsive on EOSPROJECT-I00\n\nNew Feature\n-----------\n\n* [EOS-5970] - Implement scitags in EOS for HTTP transfers\n* [EOS-5971] - Add RX/TX errors and dropped pack errors to FST monitoring\n* [EOS-6010] - CLI: Remove eos oldfind from the console\n\nTask\n----\n\n* [EOS-6003] - eos: sched ls output doesn't list all disks\n* [EOS-6004] - eos: scheduler: active status not taken into consideration\n\nImprovement\n-----------\n\n* [EOS-5744] - Forbid archival of directories that contain symlinks\n* [EOS-5745] - Forbid archival of directories with 0 size files\n* [EOS-5982] - Skip checksumming files with FUSE\n* [EOS-5990] - Add FSCK reset\n\n\n``v5.2.6 Diopside``\n==========================\n\n2024-01-15\n\nBug\n---\n\n* [EOS-5977] - NS: Double check md object is not null before constructing md locked object\n\n\n\n``v5.2.5 Diopside``\n==========================\n\n2024-01-09\n\nBug\n---\n\nSPEC: Fix missing target when building in client mode only\n\n\n``v5.2.4 Diopside``\n==========================\n\n2023-12-18\n\nNote\n----\n\n* Update eos-xrootd/xrootd dependency to 5.6.4\n* Update eos-rocksdb dependency to 8.8.1\n\n\nBug\n----\n\n* [EOS-5657] - Overreplication in EC preventing reading files\n* [EOS-5937] - Fix 'EOS command 'evict'/'stagerrm' not deleting files on FST'\n* [EOS-5965] - FUSEX: TSAN data race on setting pid in shared mdx object\n* CONSOLE/MGM: Fix EOS command evict/stagerrm not deleting files on FSTs [CTA]\n\nNew Feature\n------------\n\n* [EOS-5511] - suggestion: rate limit on errors\n\n\nImprovement\n------------\n\n* [EOS-5718] - Fsck request to repair overreplicated files in EC\n* [EOS-5919] - Disable fallocate on FSTs when filesystem != XFS by default\n\n\n``v5.2.3 Diopside``\n==========================\n\n2023-12-13\n\nBug\n----\n\n* FST: Http chunk upload - avoid infinite loop for misbehaving clients\n\n\n``v5.2.2 Diopside``\n==========================\n\n2023-11-08\n\nBug\n----\n\n* MGM: Make sure token information is passed to all namespace operations\n* MGM: Avoid re-entrant lock in space ls\n* SPEC: Add eos-grpc-gateway as an explicit requirement\n\n\n``v5.2.1 Diopside``\n==========================\n\n2023-11-06\n\nBug\n----\n\n* [EOS-5849] - MGM crash, possibly around eos::QuarkHierarchicalView::getUriInternal()\n* [EOS-5858] - FlatScheduler: groups are not retried\n* [EOS-5861] - MGM crash (corrupted free memory?)\n* [EOS-5862] - Files with strange state after editing on two places at the same time via FUSE\n* [EOS-5866] - Invalid NS entry when a file is renamed on top of a hard-link with recycle bin enabled\n* [EOS-5872] - NS: IFileMD::unlinkLocation() takes a read lock instead of a write lock\n* [EOS-5895] - MGM memory increase (EOSHOMEs)\n* [EOS-5902] - XrdHttp access throws 500 when file name contains a '#'\n* [EOS-5903] - Left over fst.ioping.XXXX files on FSTs\n* [EOS-5904] - Fix unsafe modification in Qdb Master logging\n* [EOS-5906] - 5.2 FST don't start because of benchmark files irritating LevelDB check code\n\nImprovement\n------------\n\n* [EOS-5792] - Document the possibility of moving fs between nodes in the help and the eos official documentation\n* [EOS-5894] - MGM memory increase with aggressive parameters for balancing\n\n\n``v5.2.0 Diopside``\n==========================\n\n2023-10-10\n\nNote\n----\n\n* Update dependency to eos-xrootd-5.6.2 that matches XRootD-5.6.2.\n* New eos-grpc-1.56.1 dependency that obsoletes any previous eos-protobuf3 packages.\n\n\nBug\n----\n\n* [EOS-5429] - [TAPE REST API] Modify STAGE polling (GET) logic to take into account files not queued on CTA\n* [EOS-5680] - MQ overloaded when deleting a large number of EC files\n* [EOS-5687] - CtaUtils: GCC12 FTBFS\n* [EOS-5694] - chunked upload fails on EOS5 + XrdHTTP\n* [EOS-5699] - request retries discarded on RAIN layout\n* [EOS-5700] - readv errors ReedSLayout claims corrupted but file is ok\n* [EOS-5704] - RAIN layouts don't enable XrdIo read-ahead\n* [EOS-5732] - removexattr fails with ENOENT when trying to remove any of the extended attributes from a created file\n* [EOS-5784] - /etc/cron.d/eos-reports : do not use \"bc\"\n* [EOS-5791] - Force physical space info for xrdfs spaceinfo command not working\n* [EOS-5798] - FST abort() on \"no manager name\" shutdown: \"terminate called without an active exception\"\n* [EOS-5825] - eosxd heartbeat stuck, duration slowly rising (maybe mdcflush deadlock)\n* [EOS-5826] - eosxd rising heartbeat time, suspected mdx left locked by exited thread\n* [EOS-5832] - FUSEX crash around cap::capx::lifetime(this=0x0)\n* [EOS-5842] - FUSEX: throw in data::datax::attach\n* [EOS-5843] - Wrong quota checks when recycling directories with EC files\n* [EOS-5855] - Cannot remove access limits already introduced by username\n\nNew Feature\n------------\n\n* [EOS-5613] - Store in xattr who deleted a file\n* [EOS-5716] - [eoscp] Create JSON output in addition to the text output\n* [EOS-5857] - Add support for HTTP REST API via grpc-gateway\n\n\nTask\n----\n\n* [EOS-5530] - Send fid as string to CTA\n* [EOS-5856] - Libmicrohttpd support disabled by default\n\nImprovement\n------------\n\n* [EOS-5537] - RS layouts don't use read-ahead anymore\n* [EOS-5703] - Modifications to eos `evict`/`stagerrm` command\n* [EOS-5707] - eos-config-inspect dump: allow to choose a particular config backup\n* [EOS-5734] - eos recycle -m, revert usage of underscore on keys\n* [EOS-5739] - RFE: honour sys.app.lock also when serving flock operations via FUSE\n* [EOS-5779] - EOS: server rpm upgrades shouldn't affect quarkdb\n* [EOS-5819] - Forbid quota set cli on recycle bin\n* [EOS-5831] - Add Birthtime vs Accesstime distributions to inspector output\n* [EOS-5840] - Add 'du' command to CLI\n\n\n``v5.1.30 Diopside``\n==========================\n\n2023-09-27\n\nBug\n---\n* [EOS-5834] - Corrected MGM Namespace mutex tracking\n\nNew feature\n-----------\n\n* MGM: add 'eos ns benchmark' command to run inside the MGM a multithreaded benchmark\n\n``v5.1.29 Diopside``\n==========================\n\n2023-09-14\n\nBug\n----\n\n* [EOS-5771] - HTTP transfers of a file with no disk replicas create a zero-length file\n* [EOS-5813] - Show physical space info for xrdfs spaceinfo query\n* [EOS-5818] - FST crash in eos::fst::FmdConverter::ConvertFS\n\nImprovement\n-----------\n\n* [EOS-5530] - Send fid as string to CTA\n* [EOS-5822] - Implement JSON output for eoscp command\n\n\n``v5.1.28 Diopside``\n==========================\n\n2023-09-01\n\nNew Feature\n-----------\n\n* [EOS-5803] - Introduce New groupbalancer engine - freespace which balances on\n  absolute freespace Additionally blocklisting groups is now supported in this\n  engine.\n\n``v5.1.27 Diopside``\n==========================\n\n2023-08-04\n\nNote\n----\n\n* Pin down the eos-grpc dependency package version to 1.41.0 to better control the update process in the future.\n\nBug\n---\n\n* [EOS-5763] - eosxd: occasional very large max-inode-lock-ms reported\n* [EOS-5776] - Blocked IO measurement can be wrong in case of multithreaded readers on same inode\n* [EOS-5768]: File write recovery can lead to file loss\n* FUSEX: put back md-cache auto-cleanup on umount, which was removed since 5.1.25\n\n\n``v5.1.26 Diopside``\n==========================\n\n2023-07-26\n\nBug\n---\n\n* FUSEX: protect against inserting md objects with ino=0\n* FUSEX: check the md err code of entries returned by the server before using\n* FUSEX: add sanity check to not dump a swapped-out meta-data object which is in the LRU list\n* FUSEX: avoid writing into swapped-out MD objects\n* FUSEX: remove dead code deleting old cache entries\n\n\n``v5.1.25 Diopside``\n==========================\n\n2023-07-20\n\nBug\n----\n\n* [EOS-5753] - Crash in LRU remove function\n* [EOS-5754] - cp -a gives \"preserving times for .. : Invalid argument\" - negative accesstime?\n* [EOS-5748] - MGM: Disable TPC timeout estimates as this can lead to corruption of RAIN\n  stripes for slow transfers - temporary workaround.\n\n\n``v5.1.24 Diopside``\n==========================\n\n2023-07-14\n\nBug\n----\n\n* [EOS-5652] - eosxd abrtd reports from lxplus\n* [EOS-5480] - eosxd crash under count() / metad::lookup() / EosFuse::lookup()\n* [EOS-5486] - eosxd crash with SIGABRT\n* [EOS-5667] - eosxd abtrd reports from lxplus705\n* [EOS-5668] - Input/output error on FUSE mount, client ok\n* FUSEX: don't return EFAULT with invalid statvfs responses\n* FUSEX: avoid some further concurrent access to md attr field\n\n\n``v5.1.23 Diopside``\n==========================\n\nBug\n----\n\n* [EOS-5695] - some Fsts not booting into EOS after upgrade to 5\n* [EOS-5696] - Allow 0-sized CTA files to be deleted from EOS namespace\n* [EOS-5699] - request retries discarded on RAIN layout\n\nNew Feature\n------------\n\n* [EOS-5697] - [eoscp] Add checksum comparison between source and destination\n\n\n``v5.1.22 Diopside``\n==========================\n\n2023-05-24\n\nBug\n----\n\n* COMMON: Serialize calls to setgrent/getgrent/endgrent since they are not thread-safe and can cause a crash\n\n\n``v5.1.21 Diopside``\n==========================\n\n2023-05-24\n\nBug\n----\n\n* COMMON: Fix handling of eos token when passed as HTTPS bearer authorization header\n\n\n``v5.1.20 Diopside``\n==========================\n\n2023-05-10\n\nThis release is based on eos-xrootd-5.5.10/xrootd-5.5.5\n\nBug\n---\n* This release updates to using eos-xrootd-5.5.10 which includes\na fix for a regression when higher fdlimits are needed\n\n\n``v5.1.19 Diopside``\n==========================\n\n2023-05-10\n\nThis release is based on eos-xrootd-5.5.9/xrootd-5.5.5\n\nBug\n---\n* MGM: Do special handling for HEAD requests\n\nImprovement\n------------\n* [EOS-5658] - support external host/port alias for FSTs\n\n\n``v5.1.18 Diopside``\n==========================\n\n2023-05-08\n\nBug\n----\n\n* SPEC: Fix dependency to point to eos-xrootd-5.5.9/xrootd-5.5.5\n\n\n``v5.1.17 Diopside``\n==========================\n\n2023-05-08\n\nBug\n---\n\n* [EOS-5515] - EC file with undrained stripes that looks fine\n* [EOS-5612] - Recycle bin setting change disables cleanup\n* [EOS-5633] - Eos inspector: Considers a space already deleted\n* [EOS-5601] - eos cp: Fix memory leaks in eos_roles_opaque\n* FUSEX: fix permission denied errors for slow MGM requests\n* FUSEX: fix ctime setting in eosxd3, enable write-back cache\n* FUSEX: fix blocked statistic output when backen-end waits for a flush\n\nImprovement\n------------\n* [EOS-5563] - add monitoring format to `eos fsck stat`\n* [EOS-5626] - Converter - Rain file failed to convert (100GB)\n* [EOS-5641] - Have Macaroons take into account vid VOMS mapping when determining client identit\n* DOC: refactor documentation for Diopside releases\n\n\n``v5.1.16 Diopside``\n==========================\n\n2023-04-04\n\nBug\n----\n\n* COMMON: Don't reset the current vid identity when handling KEYS mapping\n  unless we actually have a hit in the map. This was breaking the vid mapping\n  for gsi/http with voms extensions that have the endorsements field in the\n  XrdSecEntity populated and this was interpreted as a key.\n\n\n``v5.1.15 Diopside``\n=========================\n\n2023-03-27\n\n\nNote\n----\n\n* Update dependency to eos-xrootd-5.5.8 which also matches XRootD-5.5.4\n\nBug\n----\n\n* [EOS-5577] - MGM crash in eos::mgm::GrpcWncServer::RunWnc()\n* [EOS-5587] - jwt::decode might throw an exception\n* [EOS-5600] - eos group ls outputs wrong filled stats\n\n\nNew Feature\n------------\n\n* [EOS-5588] - Allow HTTPS gateway functionality to use key entries\n\nTask\n----\n\n* [EOS-5522] - Drain status stays in `expired` after setting fs in rw.\n* [EOS-5530] - Send fid as string to CTA\n\nImprovement\n-----------\n\n* [EOS-5578] - Balancer/Drainer/Recycler: reduce sleep info logging\n* [EOS-5592] - Disabling oauth did not actually disabled it\n\n\n``v5.1.14 Diopside``\n=========================\n\n2023-03-14\n\nBug\n----\n\n* [EOS-2520] - FST abort (coredump) on shutdown, \"EPoll: Bad file descriptor polling for events\"\n* [EOS-5554] - Deadlock while setting acls recursive\n\nNew Feature\n------------\n\n* [EOS-5571] - Add atime to eos-ns-inspect tool\n* [EOS-5573] - Show if namespace is locked-up\n* [EOS-5576] - MGM: fileinfo -j does not output the file' status\n\n\n``v5.1.13 Diopside``\n=========================\n\n2023-03-06\n\nBug\n----\n\n* [EOS-5546] - MGM: IoStat fprintf() stuck\n* [EOS-5555] - FST segfaults around qclient::QSet::srem\n* [EOS-5559] - EOS HTTP REST API - no JSON output if authentication is done with Bearer token\n\nNew features\n------------\n* [EOS-5561] - Create \"eos df\" command\n\n\n``v5.1.12 Diopside``\n=========================\n\n2023-02-28\n\nBug\n----\n\n* [EOS-5526] - User Sessions count seems to be wrong\n* [EOS-5534] - LRU should not walk down the recycle bin and apply policies\n* [EOS-5535] - LRU tries to delete all directories having an empty deletion policy\n* [EOS-5542] - Error when accessing directories with wildcards\n\nImprovement\n------------\n\n* [EOS-5536] - LRU code has still in-memory namespace code\n\n\n``v5.1.11 Diopside``\n=========================\n\n2023-02-15\n\n\nBug\n----\n\n* [EOS-5516] - Dangling files (possibly) after container is removed\n* [EOS-5520] - eos CLI group resolution changed - INC3372876\n* [EOS-5523] - eosxd recovery failing\n\nImprovement\n------------\n\n* [EOS-5524] - Allow https gateway nodes to provide x-forwarded-for headers\n\n\n``v5.1.10 Diopside``\n=========================\n\n2023-02-07\n\nNote\n----\n\n* Update dependency to eos-xrootd-5.5.7 which also matches XRootD-5.5.2\n\nBug\n----\n\n* [EOS-5386] - iostat reports are not processed fast enough\n\nImprovements\n------------\n\n* MGM: Make central balancer configurable at runtime\n* FST: Chunk fsck requests to at most 50k entries per request\n* MGM: enable hide-version also when heartbrate has been changed\n\n\n``v5.1.9 Diopside``\n=========================\n\n2023-01-24\n\n\nBug\n----\n\n* [EOS-5487] - sticky bit on version folders makes Recycler not able to clean the files on the recycle bin.\n* [EOS-5488] - New Year's crashes on all projects and homes\n* [EOS-5489] - PropFind fails when namespace mappings should apply\n* [EOS-5494] - eosxd looping when cleaning write queue\n* [EOS-5495] - FST crashing while doing LevelDB->ext_attr conversion on a (not) broken (enough) disk\n* [EOS-5498] - All 0 size files are marked as missing when using xattr fmd\n\n\nNew Feature\n------------\n\n* [EOS-5209] - Fsck removal should just move stripes to a quarantine directory\n\n\nImprovement\n------------\n\n* [EOS-5501] - Allow black and whitelisting of token vouchers (ids)\n\n\n``v5.1.8 Diopside``\n=========================\n\n2022-12-14\n\nNote\n----\n\n* Update dependency eos-xrootd-5.5.5\n* Includes an important fix for HTTP TPC PULL transfers.\n\nBug\n----\n\n* [EOS-5467] - Inspector aggregates results instead of resetting the current scan\n* MGM: Add regfree in FuseServer regex usage to avoid memory leak\n* MGM: Unlock the Access mutex when delaying a client to not get problems to get a write lock\n\n\nImprovement\n-----------\n\n* [EOS-5478] - Invert Stall logic to check first user limits and then catch-all rules\n\n\n``v5.1.7 Diopside``\n=========================\n\n2022-12-12\n\nBug\n----\n\n* [EOS-5474] - Conversion breaks files with FMD info in xattrs\n\nImprovement\n------------\n\n* [EOS-5469] - Allow to select secondary groups with kerberos authentication and implement AC checks for secondorary groups\n* [EOS-5471] - Add atime to EOS\n* [EOS-5458] - Setting a namespace xattr might fail for wopi\n\n\n``v5.1.6 Diopside``\n=========================\n\n2022-12-05\n\nBug\n----\n\n* [EOS-5467] - Inspector aggregates results instead of resetting the current scan\n\nImprovement\n------------\n\n* [EOS-5465] - Shoe FUSE application name in 'fusex ls'\n* [EOS-5466] - Add Stall / NoStall host lists to access interface\n\n\n``v5.1.5 Diopside``\n=========================\n\n2022-12-02\n\nBug\n----\n\n* MGM: Fix MGM crash when the balancer is configured\n\nImprovement\n-----------\n\n* [EOS-5452] - New metric: Provide I/O errors per transfer in report logs\n* [EOS-5453] - New metric: Namespace contention calculation in ns stat command\n* [EOS-5131] - RFE: honour XRD_APPNAME for xrdcp\n* [EOS-5444] - Provide number of stripes in the inspector command\n* [EOS-5454] - EOS inspector: Provide layout_id in the list output per fxid\n* [EOS-5455] - eos node ls monitoring - Improve sys.uptime value format\n* [EOS-5459] - MGM: avoid blocking cleanup ops while user mapping\n* [EOS-5464] - Have TPC transfers respect the client tpc.ttl value\n\n\n``v5.1.4 Diopside``\n=========================\n\n2022-11-22\n\nBug\n----\n\n* [EOS-5442] - eosxd crash (on shutdown) under ShardedCache destructor\n* [EOS-5446] - Failures in setting thread names\n\n\n``v5.1.3 Diopside``\n=========================\n\n2022-11-16\n\nBug\n----\n\n* [EOS-5162] - Setting ACL does not work when dir ends with whitespace\n* [EOS-5433] - GroupBalancer: crash when conversions are scheduled before Converter\n* [EOS-5436] - Origin Restriction does not work as expected\n* [EOS-5437] - Fix potential leaks in Mapping::getPhysicalIds\n\nNew Feature\n------------\n\n* [EOS-5145] - Extending lock support\n* [EOS-5438] - Don't stall clients when thread pool is exhausted and a rate limit is reached\n\nImprovement\n------------\n\n* [EOS-5231] - Allow eos attr set to operate on CIDs\n* [EOS-5344] - eos recycle -m: show inode used / max numbers\n* [EOS-5401] - Return the inode number in FMD responses for GRPC\n* [EOS-5412] - add qclient performance metrics on monitoring format.\n* [EOS-5413] -  QClient performance: have last 5m, last 1m, etc metrics\n* [EOS-5439] - Add eosxd3 to all builds when fuse3 is available and ship in the RPM\n\n\n``v5.1.2 Diopside``\n=========================\n\n2022-10-04\n\nBug\n----\n\n* [EOS-5399] - FST: Segfaults in FmdConverter\n* [EOS-5400] - FST crash in AccountMissing due to null Fmd object\n\nImprovement\n------------\n\n* [EOS-3297] - Print the deviation used for the group balancer\n\nNew features\n------------\n\n* MGM: Add implementation for central group balancer using TPC\n\n\n``v5.1.1 Diopside``\n=========================\n\n2022-09-15\n\nNote\n-----\n\n* Update dependency to eos-xrootd-5.5.1\n* eosd is now deprecated and there are no more RPM packages provided for it\n\nBug\n----\n\n* [EOS-5347] - EOS token not usable via eosxd\n* [EOS-5369] - Occasional error during eoscta test \"mismatch between requested fid/fsid and retrieved ones\"\n* [EOS-5371] - Fix crash of the MGM when listing container entries due to invalidated\n               iterators to the ContainerMap/FileMap objects.\n* FST: eos-xrootd-5.5.1 fixes a bug in XRootD related to async close functionality\n  where the FST would crash if it received another requests for a file which was in\n  the process of being closed.\n\nNew features\n------------\n\n* CTA: Enhance/extend EOS report messages for CTA prepare workflow\n\n\n``v5.1.0 Diopside``\n=========================\n\n2022-09-02\n\nNote\n----\n\n* This release comes with XRootD/eos-xrootd 5.5.0 as dependency\n\nBug\n----\n\n* [EOS-5377] - Unhandled exception in the GeoBalancer code\n* [EOS-5367] - Fix IoStat initialization when there is no prior data in QuarkDB\n* MGM: Fsck: correct the calculation of expected number of stripes in RepairFstXsSzDiff\n\n\nImprovement\n-----------\n\n* [EOS-5380] - Qclient: handle folly warnings\n* [EOS-5381] - Fix potential format overflows\n* [EOS-5378] - Fix compilation warnings\n* FUSEX: Add support for json statistics output\n\nNew features\n-------------\n\n* FST: Add support for storing file metadata info as extended attributes\n  of the raw files on disk rather than using the LevelDB on disk.\n  Disabled by default for the moment.\n\n\n``v5.0.31 Diopside``\n=======================\n\n2022-08-12\n\nBug\n----\n\n* FST: Properly detect HTTP transfers and skip async close functionality in\n  such cases\n* [EOS-5359] - use after free in fusex::client::info\n* [EOS-5358] - WNC GRPC unserialized global options\n\n\n``v5.0.30 Diopside``\n=======================\n\n2022-08-11\n\nBug\n---\n\n* [EOS-5355] - System ACLs evaluation overruling logic is incorrect\n\n\nNew Feature\n------------\n\n* [EOS-5342] - CREATE cta workflow not triggered when new file created using fusex - DELETE workflow is also missing\n\n\nImprovement\n-----------\n\n* [EOS-5343] - Better enforcement of the scattered placement policy\n\n\n``v5.0.29 Diopside``\n=======================\n\n2022-07-29\n\nBug\n----\n\n* Fix /usr/bin/python dependency on EL8(S) which is no longer provided by any package,\n  therefore we need to explicitly use /usr/bin/python3\n\n\n``v5.0.28 Diopside``\n=======================\n\n2022-07-26\n\nNote\n----\n\n* This version of EOS is based on an internal release of XRootD namely eos-xrootd-5.4.7\n\nBug\n---\n\n* [EOS-5336] - Lot of EOS FST crash (SIGSEGV) in the EOSALICE instance\n* [EOS-5308] - MGM: Potential double free in LDAP initialize\n* [EOS-5334] - LDAP connection socket leak\n* [EOS-5335] - MGM crash in Fileinfo.cc:97\n\n\n``v5.0.27 Diopside``\n=======================\n\n2022-06-30\n\n\nBug\n---\n\n* [EOS-5296] - FST segfault around XrdXrootdProtocol::Process2\n* [EOS-5314] - segfault around \"XrdCl::CopyProcess::CleanUpJobs\"\n* [EOS-5302] - Iostat domain accounting is broken\n* [EOS-5303] - Shared filesystem file registration feature\n* [EOS-5308] - MGM: Potential double free in LDAP initialize\n\nImprovement\n------------\n\n* [EOS-5317] - Crash in AssignLBHandler with asan\n* [EOS-5321] - Allow to define which errors the fsck repair thread works on\n* [EOS-5305] - Tape REST API - V1 with an option to deactivate STAGE\n\n\n``v5.0.26 Diopside``\n=======================\n\n2022-06-21\n\n\nNote\n----\n\n* XRootD: Based on eos-xrootd-5.4.5 which fixes a couple for important bugs\n  on the xrootd client side.\n\nBug\n----\n\n* [EOS-5302] - Iostat domain accounting is broken\n* [EOS-5303] - Shared filesystem file registration feature\n\nImprovements\n------------\n\n* MGM: Make fsck start up and shutdown more responsive\n* MGM: Add fsck repair procedure for m_mem_sz_diff errors\n\n\n``v5.0.25 Diopside``\n=======================\n\n2022-06-09\n\nBug\n----\n\n* [EOS-5278] - Segmentation fault around eos::mgm::GroupDrainer::scheduleTransfer\n* [EOS-5284] - GroupBalancer: spurious logs when no transfers can be scheduled\n* [EOS-5286] - Physical quota is not updated when we set EC conversion\n* [EOS-5288] - Wrong layout id after conversion operation leading to wrong physical size\n* [EOS-5218] - Infinite loop in XrdCl::XRootDMsgHandler::Copy\n* MGM: The initial behaviour of xrdfs prepare -s/-a/-e and xrdfs query prepare have been restored\n\nImprovement\n------------\n\n* [EOS-5277] - Add LockMonitor class wrapping standard mutex\n* [EOS-5282] - Allow converter configuration to persist on restarts\n* [EOS-5285] - GroupDrainer: Allow all transfers to be reset\n* [EOS-5289] - File truncate can be slow especially for RAIN layouts\n* [EOS-5290] - File close operation for RAIN layouts can trigger client timeouts\n* MGM: Tape REST API v0.1 release - Support for ArchiveInfo and Release\n  functionality + discovery endpoint\n* MISC: Allow the eos-iam-mapfile tool to deal with DNs containing commas\n\n\n``v5.0.24 Diopside``\n=======================\n\n2022-05-27\n\nBug\n---\n\n* [EOS-3713] - sys.eos.mdino should not use old-style inodes\n* [EOS-5230] - Keep xattrs when restoring versions\n* [EOS-5269] - Certain FSes not picked up by the group drainer\n\nImprovement\n-----------\n\n* [EOS-5263] - groupmod is hard limited to 256 groups\n* [EOS-5267] - Provide timestamp in eos convert list failed errors\n\n\n``v5.0.23 Diopside``\n=======================\n\n2022-05-16\n\nNote\n----\n\n* This release uses eos-xrootd-5.4.4 which is based on XRootD-5.4.3-rc3.\n\nBug\n----\n\n* [EOS-5246] - replica show 'error_label=none' while having checksum mismatch.\n\nImprovement\n------------\n\n* [EOS-5184] - Add RedirectCollapse to XrdMgmOfs::Redirect responses\n* [EOS-5198] - Add few log lines to MasterLog\n\n\n``v5.0.22 Diopside``\n=======================\n\n2022-05-06\n\nImprovements\n------------\n\nFUSEX: Refactoring async response handling\n\n\n``v5.0.21 Diopside``\n=======================\n\n2022-05-06\n\nNotes\n------\n\n* Note: this is a scratch build on top of XRootD-5.4.3-RC1 trying to test a bug fix concerning vector reads\n* Update dependency to XRootD-5.4.3-RC1\n\n\n\n``v5.0.20 Diopside``\n=======================\n\n2022-05-03\n\nImprovements\n------------\n\nMGM: Improve fsck handling for rain files with rep_diff_n errors\nMGM: Add extra logging in fsck and be more defensive when handling\nunregistered stripes\nMGM: Group drainer prune transfers only once every few minutes\nFST: Silence stat errors for TPC transfers during preparation stages\n\n\n``v5.0.19 Diopside``\n=======================\n\n2022-05-02\n\nBug\n---\n\n* MGM: Fix race condition in Converter which can lead to wrong metadata stored\n  in leveldb for converted files.\n* MGM: Fix wrong computation of number of stripes for RAIN layout\n* [EOS-5199] - Metadata (xattrs) is lost when creating new versions\n* [EOS-5219] - eos fsck report json output does not reflect command line options -l and -i\n* [EOS-5224] - No update is performed when adding a new member to an e-group in EOSATLAS\n\n\nNew Feature\n-----------\n\n* [EOS-5178] - Implement Group Drain\n* [EOS-5225] - Have a useful GroupDrain Status\n\n\n``v5.0.18 Diopside``\n=======================\n\n2022-04-22\n\nBug\n----\n\n* [EOS-5197] - Deleting an xattr via console does not delete the key\n* [EOS-5199] - Metadata (xattrs) is lost when creating new versions\n* MGM: Fix crash in debug message when Env object is null for Access method\n\nNew Feature\n------------\n\n* [EOS-5215] - Fsck handle stripe size inconsistencies for RAIN layouts\n\n\nImprovement\n------------\n\n* [EOS-4955] - Add project quota tests as a part of CI\n* MGM: Iostat performance improvements for summary output\n* MGM: Iostat make extra tables optional by default and add separate\n  flag for displaying them.\n\n\n``v5.0.17 Diopside``\n=======================\n\n2022-04-13\n\nNote\n----\n\n* This version includes add the fixes up to 4.8.82.\n\nImprovement\n------------\n\n* [EOS-5201] - Allow for more fine grained IO policies\n* [EOS-5204] - Only create files  via FUSEX if there is inode and volume quota and physical space available\n* [EOS-5205] - Distinguish writable space and total space\n* [EOS-5206] - Don't allow to set quota volume lower than the minimum fuse quota booking size\n\n\n``v5.0.16 Diopside``\n=======================\n\n2022-03-29\n\nBug\n----\n\n* [EOS-5181] - Slave to Master redirection creates IO errors on FUSEx mounts\n* [EOS-5176] - Make OAuth tolerant to self-signed//invalid certificates used by identity provider\n\nImprovement\n-----------\n\n* MGM: Add protection against multi-source retry for RAIN layouts\n* MGM: Rewrite of the IoStat implementation for better accuracy\n* MGM: Remove dependency on eos-scitokens and use the library provided by XRootD framework\n* DOC: Update documentation concerning the MGM configuration for SciTokens support\n* NS: QuarkSyncTimeAccounting - removed namespace lock usage\n\nNew feature\n-----------\n\n* MGM: Add support for eos tokens over https\n\n\n``v5.0.15 Diopside``\n=======================\n\n2022-03-22\n\nNote\n-----\n\n* Includes all the changes from 4.8.79\n\nBug\n----\n\n* FUSEX: never keep the deletion mutex when destroying an upload proxy because\n  the destructor still needs a free call back thread to use HandleResponse\n* [EOS-5153] - EC file written via FUSEx - mismatching checksum\n* [EOS-5167] - MGM segv in a non-tape enabled instance\n\n\n\n``v5.0.14 Diopside``\n=======================\n\n2022-03-14\n\nBug\n----\n\n* [EOS-5090] - convert clear is not a admin command\n* [EOS-5133] - node ls -b does not remove the domain names\n* FUSEX: Fix deadlocks and race-conditions reported by TSAN\n\nImprovement\n------------\n\n* [EOS-5108] - workaround: drop forced automount expiry on FUSEX updates\n* [EOS-5126] - [eos-ns-inspect] Complement `stripediff` output\n\n\n``v5.0.13 Diopside``\n=======================\n\n2022-02-15\n\nNote\n----\n\n* Includes all the changes from 4.8.76\n\nBug\n---\n\n* [EOS-5110] - Consolidate Access control in GRPC MD, MDSTreaming\n* [EOS-5116] - Workaround for XrdOucBuffPool bug\n* [EOS-5118] - eos-ns-inspect scan is initializing maxdepth to 0, even if not used\n* [EOS-5119] - Deadlock scenario in eosxd\n\nImprovement\n-----------\n\n* [EOS-5111] - Groupbalancer: newly introduced fields may not have a sane value\n* [EOS-5120] - io stat tag totals\n\n\n``v5.0.12 Diopside``\n=======================\n\n2022-02-04\n\nNote\n----\n\n* Identical to 5.0.11 but re-tagged due to Koji issues\n\n\n``v5.0.11 Diopside``\n=======================\n\n2022-02-04\n\nBug\n----\n\n* [EOS-5105] - eosxd crash in cap::quotax::dump\n\n\n``v5.0.10 Diopside``\n=======================\n\n2022-02-02\n\nNote\n-----\n\n* This release includes all the changes from 4.8.74 release\n\nBug\n----\n\n* [EOS-5069] - filesystem status in \"rw + failed\"\n* [EOS-5070] - Access::ThreadLimit creates re-entrant lock of the access mutex\n* [EOS-5095] - Re-entrant lock triggered by out of quota warning\n\nImprovement\n------------\n\n* [EOS-5065] - Add create-if-not-exists option in GRPC\n* [EOS-5076] - Extend iotype interfaces to be space/directory defined\n* MGM: Fix missing support for cid/cxid and error output for convert command\n* WNC: Replaced auxiliary ACL function for fileinfo command\n\nNew features\n------------\n\n* WNC: Implemented support for EOS-wnc token, convert, fsck and new find commands\n* WNC: Changed GRPC streaming mechanism for find, ls and transfer commands\n\n\n``v5.0.9 Diopside``\n=======================\n\n2022-01-12\n\nBug\n----\n\n* COMMON: Avoid segv due to mutex object set to nullptr in RWLock printout\n* [EOS-4850] - eosxd crash in destructor under metad::pmap::retrieveWithParentTS()\n* [EOS-5057] - Volume quota dispatched to FUSE clients mixes logical and physical bytes\n\n\n``v5.0.8 Diopside``\n=======================\n\n2022-01-06\n\nNote\n----\n\n* Note: This release includes all the changes to the 4.8.70 release\n\nBug\n----\n\n* [EOS-5039] - Threads with parens in their name cannot access EOS\n\nImprovement\n-----------\n\n* [EOS-5029] - Allow to apply rate limiting in recursive (server side) command.\n* [EOS-5048] - Support direct IO for high performance read/write use cases\n\n\n``v5.0.7 Diopside``\n=======================\n\n2021-12-01\n\nNote\n----\n\n* Release based on XRootD-5.3.4\n\n\nNew features\n------------\n\n* WNC: Implemented support for EOS-wnc member, backup, map and archive command\n\n\n\n``v5.0.6 Diopside``\n=======================\n\n2021-11-16\n\nNote\n-----\n\n* Release based on XRootD-5.3.3 which fixes a critical bug concerning \"invalid responses\"\n\n\nBug\n----\n\n* ARCHIVE: Avoid trying to set extended attributes which are empty\n* [EOS-4995] MGM/CONSOLE: add '-c' option to CLI ls to show also the checksum for a listing\n* CTA: Fixed FST crash when connecting to misconfigured ctafrontend endpoint\n\n\n``v5.0.5 Diopside``\n=======================\n\n2021-11-04\n\nBug\n----\n\nOSS: Avoid leaking file descriptors for xsmap files which are deleted in the meantime\nMGM: Skip applying fsck config changes at the slave as these will be properly\n\n\n``v5.0.4 Diopside``\n=======================\n\n2021-10-27\n\n\nBug\n----\n\n* SPEC: Make sure both libproto* and libXrd* requirements are excluded when\n  building the eos packages since these come from internally build rpms like\n  eos-xrootd and eos-protobuf3 which don't expose the library so names so that\n  they can be installed on a machine along with the official rpms for the\n  corresponding packages if they exist.\n* MGM: Avoid that a slave MGM applies an fsck configuration change in a loop\n\nImprovements\n------------\n\n* EOS-4967: Add ARM64 support for blake3\n\n\n``v5.0.3 Diopside``\n=======================\n\n2021-10-27\n\n\nNote\n----\n\n* This version is based on XRootD 5.3.2 that addresses some critical bug observed\n  in the previous version for XRootD.\n\nBug\n----\n\n* MGM: Fix GRPC IPv6 parsing\n* [EOS-4963] - FST: Reply with 206(PARTIAL_CONTENT) for partial content responses\n* [EOS-4962] - MGM: Return FORBIDDEN if there is a public access restriction in PROFIND requests\n* [EOS-4950] - FUSEX: fix race conditions in async callbacks with respect to proxy object deletions\n*\n\nNew features\n------------\n\n* [EOS-4670] - FUSEX: implement file obfuscation and encryption\n\n\n``v5.0.2 Diopside``\n=======================\n\n2021-09-06\n\nBug\n----\n\n* [EOS-4809] - Make eos5 work with XrdMacaroons from XRootD5\n* Includes all the fixes from 4.8.65\n\nImprovements\n------------\n\n* WNC: Improvements to the EOS-Drive for fileinfo & health command\n\n\n``v5.0.1 Diopside``\n=======================\n\n2021-08-16\n\nNew features\n-------------\n\n* Comtrade WNC contribution for the server side\n* Includes all the fixes from the 4.8.60 release\n\n\n``v5.0.0 Diopside``\n=======================\n\n2021-06-11\n\nMajor changes\n--------------\n\n* Based on XRootD 5.2.0\n* Drop support for in-memory namespace\n* Drop support for file based configuration\n* Drop support for old high-availability setup\n* Make fusex classes compatible with the latest protobuf library\n* Integrate QuarkDB as part of the eos release process\n"
  },
  {
    "path": "doc/diopside/releases/5.4.0/recycle_bin_config.rst",
    "content": "Recycle Bin Configuration Update\n================================\n\n.. important::\n   **Configuration Migration Required**\n\n   The recycle bin configuration has moved from extended attributes on the recycle directory to the global EOS configuration.\n\nNew Configuration Commands\n--------------------------\n\nUse the ``eos recycle config`` command to configure the recycle bin policy.\n\n**Enable/Disable:**\n\n.. code-block:: bash\n\n   # Enable the recycle bin globally\n   eos recycle config --enable on\n\n   # Disable the recycle bin globally\n   eos recycle config --enable off\n\n\n**Set Policy Parameters:**\n\n.. code-block:: bash\n\n   # Set keep time in seconds (e.g., 1 day)\n   eos recycle config --lifetime 86400\n\n   # Set space keep ratio (0.0 - 1.0)\n   eos recycle config --ratio 0.8\n\n   # Set collection interval in seconds\n   eos recycle config --collect-interval 300\n\n\nCleanup Legacy Configuration\n----------------------------\n\nOld extended attributes on the recycle directory (typically ``/eos/<instance>/proc/recycle/`` or configured path) are **NO LONGER USED** for policy enforcement and should be removed to avoid confusion.\n\n**Attributes to Remove:**\n\n* ``sys.recycle.keeptime``\n* ``sys.recycle.keepratio``\n* ``sys.recycle.collectinterval``\n* ``sys.recycle.removeinterval``\n\n**How to Remove:**\n\nUse the ``eos attr rm`` command on your recycle bin directory (check your specific path, commonly ``/eos/<instance>/proc/recycle/``):\n\n.. code-block:: bash\n\n   eos attr rm sys.recycle.keeptime /eos/<instance>/proc/recycle/\n   eos attr rm sys.recycle.keepratio /eos/<instance>/proc/recycle/\n   eos attr rm sys.recycle.collectinterval /eos/<instance>/proc/recycle/\n   eos attr rm sys.recycle.removeinterval /eos/<instance>/proc/recycle/\n\n.. note::\n   The ``sys.recycle`` attribute on individual directories is **STILL SUPPORTED** to explicitly mark a subtree for recycling even if global enforcement is off. You do not need to remove ``sys.recycle`` from user directories.\n\nSpace Policy Notice\n-------------------\n\nThe ``eos space config ... policy.recycle=on`` command is **DEPRECATED** and has been removed. Use ``eos recycle config --enable on`` instead.\n"
  },
  {
    "path": "doc/diopside/releases/amber.rst",
    "content": ":orphan:\n\n.. highlight:: rst\n\n.. index::\n   single: Amber\n\nAmber\n========\n\n``Lifetime: 2010-2013``\nThe **Amber** release was the first production version of EOS. It has been used\nsince end of 2010 until 2013.\n\n"
  },
  {
    "path": "doc/diopside/releases/beryl-release.rst",
    "content": ":orphan:\n\n.. highlight:: rst\n\n.. index::\n   pair: Releases; Beryl\n\n\n\nBeryl Release Notes\n===================\n\n``V0.3.270 Aquamarine``\n=======================\n\nNew Features\n============\n\n- MGM: add the 'eos fusex' interface and new FUSE client server side support (beta status - be careful with using this)\n- MGM/CONSOLE: add new 'access allow|unallow|ban domain <domain>'\n- NS: add hopscotch map/hash\n\nBug Fixes\n+++++++++\n\n- NS: use murmurhash as string hash function\n- COMMON: fix dead-lock im common/Mapping.cc\n\n``V0.3.268 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM: Mask the block checksum for draining and balancing when there is a layout\n       requesting blockchecksum for replica files. This was blocking all draining,\n       balancing or conversion jobs.\n\n``V0.3.267 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- AUTH: Set the ZMQ_LINGER option on the socket so that messages are not retransmitted\n- NS: add missing initialization of pData leading to random compaction crashes/failures\n- MGM: fix race in mkdir which could return EEXIST\n- MGM: fix race in rm\n- FUSE: fix memory-leak when read-ahead gets disabled during an open/read/close sequence\n\nImprovement\n+++++++++++\n\n- MGM: Return ENETUNREACH in case no diskservers are available (implies different client behavior)\n- MGM: allow recursive deletes for the http bridge using XrdOfs::remdir with ?mgm.option=r\n- MGM: add two new space variables to modify scheduler behaviour\n       \"space.scheduler.skip.overloaded=off\" - by default we don't skip anymore overloaded eth-out nodes)\n       \"space.min.weight=0.1\" - the minimum probability to select an disk or eth-out overloaded node\n- MGM: Collect response time statistics for the authentication front-ends\n- MGM: make the recycle bin work with symbolic links\n\n\n``V0.3.266 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM: avoid recreating block-xs files in balancing and draining due to wrong mask used\n- MGM: avoid increasing number of replicas when balancing very empty groups\n- AUTH: Avoid replay of requests for ZMQ sockets which are deleted. This avoid the 0-size\n  files in the namespace bug.\n\n``V0.3.265 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- Fix issue in EOSATLAS where files where disappearing from the namespace after being confirmed\n  to the client. This is correlated which exceptionally long scheduling times (~ 5min). This in\n  turn is due to the scheduling not finding a suitable node to place the file. When this happens\n  the default XRootD client will try to recover the initial open requests and this leads to a\n  race condition.\n- [EOS-1948] - FST crash with \"terminate called after throwing an instance of 'std::bad_alloc'\"\n- [EOS-1949] - Strange correlated crash in EOSATLAS\n\nImprovement\n+++++++++++\n- [EOS-1947] - Improve error message when trying to delete a directory attached to a quota node\n\n\n``V0.3.264 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- [EOS-1936] - EOS ATLAS lost file due to balancing\n- ARCHIVE: Fix archive endpoint which was constructed only if the MGM node was a master.\n           This approach fails when we have a master slave failover as we never set up\n           the archive endpoint for the slave. Use the same ZMQ context for both the\n           archive and authentication services.\n- FUSE: Make configurable the maximum number of retries in case a synchronous\n        open operation fails.\n- DOC: update documentatino of wfe's\n\n\n``V0.3.263 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FST: re-establish 2nd path of 'deleteOnClose' functionality broken since 0.3.295\n\n``V0.3.262 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM: fix computation of wake-up time for the recycle bin - old code slept too long before waking up\n\n``V0.3.261 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FST: re-establish 'deleteOnClose' functionality broken since 0.3.295\n\n\n\n``V0.3.260 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM: call 'unlinkAllLocations' instead of 'clearLocations' when trying to re-place an empty already placed file, which didn't remove entries from the filesystem view leaving files forever undrainable\n\n``V0.3.259 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM: Don't drop a file if an FST calls a drop replica on a not committed replica\n\n\n``V0.3.258 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM: Protect if the namespace throws an exception without setting an error number in the readlink functionality\n\n\n``V0.3.257 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FST: protect against 0 pointer access if not local fmd is available for a scanned file\n\n``V0.3.256 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM/CONSOLE: revive 'file layout' command and 'find -layoutstripes'\n\n``V0.3.255 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM: treat attributes not prefixed as sys. like user. attributes (don't allow to set them if we are not the object owner)\n- MGM: many bug fixes/improvements in the AUTH service\n\n\n``V0.3.248 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- MGM: fix recycle bin restore function to forbid to recycle files by fxid/pxid which are not in the recycle bin. Allow to explicitly restore a file or directory (they might overlap in the inode space) by prefixing the key with fxid: or pxid:\n\n\n``V0.3.246 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FUSE: fix shutdown crash by properly canceling/joining the cache cleaner thread\n- NS: fix gcc 4.4. compilation problem\n- MGM: reschedule empty files if current replicas are unavailable\n- MGM: add authentication front-end (backport from CITRINE)\n\n\n``V0.3.244 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FST: don't block Fmd access for an uninitialized filecxerror value (after Resync was called and filecxerror=-1)\n\n\n``V0.3.243 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- NS: fix memory allocation bug in Buffer class\n\n\n``V0.3.242 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FST: fix logical error when to call auto-repair (don't call it for unregsistered files)\n- FUSE: fix double response when returning entries from internal directory cache\n- MGM: fix protection when listing too large recycle bins with 'recycle ls' (> 1Gb output)\n\n``V0.3.241 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FUSE: fix memory leak in opendir function not cleaning dirbuf struct\n\n``V0.3.240 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FST: implement fdellocate function for non-XFS detected filesystems (which used posix_fallocate)\n\n``V0.3.239 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- NS: fix resolution of multiple ../ path changes like ../../XYZ\n- COMMON: fix resolution of multipeo ../ path changes like /X/Y/Z/../../Z\n\n``V0.3.238 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- FST: avoid SEGV during startup when calling RemoveGhostEntries (.eosscan exists on data path)\n\n``V0.3.237 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- NS: fix slave follower attachment issue leading to invisible files\n- MGM: fix the logic when to show a slave as booted\n\nNew Feature\n+++++++++++\n\n- NS: add 'pending' counter to show if there are updates on the slave, which cannot be attached\n- NS: show follower progress during the initial scan phase and not only after\n\n\n``V0.3.236 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- NS: set 'pData' pointer to 0 in munmap function to switch back to traditional read function\n\n``V0.3.235 Aquamarine``\n=======================\n\nNew Feature\n+++++++++++\n\n- NS: compile with devtoolset-2 on SLC6\n- NS: make part of boot process parallel (gain 3-6x in boottime) [ enable with export EOS_NS_BOOT_PARALLEL=1 ]\n- NS: mmap changelog files during first scan phase to avoid performance limitation by too many syscalls [ disable with export EOS_NS_BOOT_NOMMAP=1 ]\n- NS: implement pread function for namespace file following using read-ahead caching to avoid too many syscalls\n- NS: allow to disable CRC32 on boot (e.g. when using BTRS/ZFS) [ enable with export EOS_NS_BOOT_NORCRC32=1 ]\n- NS: use murmurhash3 for the main flat indexes avoiding serious performance degradation for high id's in google::dense_hash_map\n- NS: make treesize and tree modification time atomic variables if gcc >=4.8\n- FST: limit 'file open for writing' messages in Verify to once per minute\n- FST: limit 'writer error' message to only once per open/write/close file sequence\n- COMMON: add generic lambda function to run parallel for loops Parallel::For ()\n- UTILS: add yum packages to install devtoolset-2 to compile with gcc 4.8\n\nNew documentation of namespace variables: http://eos.readthedocs.io/en/latest/configuration/namespace.html\n\nBug Fixes\n+++++++++\n\n- NS: fix various bugs in slave follower losing directories, not showing proper treesize aso.\n- NS: start 'eossync' in slave2master transition\n- MGM: avoid Converter::ResetMasterJobs on slaves\n- MGM: don't run slaves in auto-repair mode when scanning the changelog file\n- FUSE: fix 'bad address' errors and show proper 'permission denied' messages when a client has not credential or is forbidden to talk to certain EOS instances\n- CONSOLE: fix 'treesize' output in 'fileinfo'\n\n\n``V0.3.234 Aquamarine``\n=======================\n\n- NS: avoid that the main indexes ever shrink\n- MGM: don't follow symlinks when stating recycle bin entries\n- FUSE/FST: add read-ahead cache consistency to FUSE client and make kernel cache invalidation work properly\n- FST: allow to define the network speed via an environment variable since 'ip route' and ethtool are not equivalent on SLC6/EL7\n\n``V0.3.233 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: remove falsely committed debug return statement disabling stale cache file detection from previous fix\n- FST: extending '.eosscan' functionality to cleanup ghost entries which are neither on disk or memory but can normally only be removed by wiping the local database and rebuild from scratch\n\n``V0.3.232 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix stale kernel cache contents problem if file contents changed but not the file size\n- FUSE: fix stale directory/file attributes for lookup/getattr of cached files/directories (apply attr lifetime)\n- FST: avoid to try to call forever an old master in commit/drop calls which specified an explicit call-back manager - use the broadcasted MGM name after 60 attempts\n\n``V0.3.231 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: stall/redirect access by fid:fxid before trying to translate to a real path (can crash boot procedure)\n\n``V0.3.230 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: deal with unregistered files with the correct replica count in the same way as with orphans when .eosscan is enabled on an FST mount\n\n>>>>>>> beryl_aquamarine\n``V0.3.229 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix bug introduced with retry 'query' mechanism doing double deletes\n- FUSE: fix bug in AuthId manager doing a double lock when session id != process id\n- FUSE: set the link count for files/links to 1 to make applications like gzip work\n- MGM: fix subtree accounting in the slave follower\n- FST: add an .eosorphans directory to each FST mount point and allow to isolate orphans into this directory by creating a tag file  <mnt>/.eosscan. The .eosscan file removes any smearing and sleep time between scans. The original location is tagged as an extended attribute after during the move\n\n``V0.3.228 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix locking strategy bug in the proc cache usage where entries were not locked anymore when used\n\n``V0.3.227 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: fix failover procedure: slave stays forever booting until master sees a change\n- MGM: safte in failover procedure: don't failover if the slave did not follow the changelog to the end\n- MGM: show bytes left to follow in 'ns master' on slave\n- NS: avoid infinite loop in slave follower when looking for a quota node\n- FUSE: fix bug leaving files open when a file was inline repaired\n- DAV: fix webdav bug when a symbolic link is present in a directory listing leading to an error response\n- MGM: fix 'access rm' implementation to remove ENOENT and ENONET redirection\n- DAV: take into account sys.owner.auth when looking for webdav quota\n\n``V0.3.226 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- COMMON: make ShellExecutor thread/interrupt safe\n- FST: reset checksum error flags also after correct 'verify -checksum'\n- FUSE: fix ping timeouts and dependencies, allow sss mounts\n- NS: remove ns file archiving process by default in SLAVE->MASTER transition and fix too early enabling of the namespace for write\n\nNew Feature\n+++++++++++\n\n- MGM: add REST API for 'fileinfo'\n\n\n``V0.3.225 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: fix vulnerability for http GET of '/./' via eos::common::Path\n- COMMON: make '/' the full and parent path of /. /.. /./ /../\n\n``V0.3.224 Aquamarine``\n=======================\n\nNew Feature\n+++++++++++\n\n- FST: allow 'eos.checksum=ignore' for file uploads to avoid checksum computation\n- FST: fix 'eoscp -a' and add 'eoscp -A <offset>' to upload a file to a certain offset\n\n``V0.3.223 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix foreground option for eosd\n- FUSE: shard proc cache to keep memory footprint low for high MAX_PID settings and run AuthId cleanup every 5 minutes\n- FUSE: don't pick up root credentials inside eosd\n- COMMON: fix syslog logging interface using wrong argument list\n\n\n``V0.3.222 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MQ: fix race condition multiplexed/non-multiplexed set\n- FST: fix race condition in filesystem mutex map\n- FUSE: fix wrong default values for query retry sleep time\n- MGM: protect scheduling against scheduling in a space without filesystems\n- MGM: fix 'fileinfo by inode'\n\nNew Feature\n+++++++++++\n\n- FUSE: use proc map sharding to avoid too large mutex maps for machines with high max proc ID settings\n- FUSE: allow to run eosd as a foreground process when specified in /etc/sysconfig/eos\n\n``V0.3.221 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: don't hold (timeout) HTTP requests during compacting\n- FST: fix mutex race condition\n- FUSE: fix memory issues and remove unreachable code\n- FUSE: avoid SEGV on empty XRootD buffer responses\n- FUSE: restructure read-buffer handling and clean-up not used read-buffers in CacheCleanup function - avois significant memory leaking under parallel access\n\n``V0.3.220 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: avoid triggering recreation of xsmap files during draining/balancing for replica layouts\n- FUSE/FST: fix 'critical' bug in async write implementation not collecting async writes errors when flush is called and file exceeds the cache size\n- FUSE: always wait for asynchronous writes in case of file modifications\n\n\nFeature\n+++++++\n\n- COMMON: allow to duplicate EOS log to syslog via export EOS_LOG_SYSLOG=1\n\n\nBug Fix\n+++++++\n\n- COMMON/FUSE: fix base64 encoding of not-string buffers\n- FUSE: fix memory leak in proc cache\n- FUSE: use FORKHANDLER in XrdCl and check mgm before forking the FUSE daemon\n- FUSE: fix shutdown behaviour after MGM ping failure\n- MGM: fix 'fileinfo' for high inode numbers\n\n\n``V0.3.218 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix a bug in auth cache when sid process of a calling pid does not exist anymore\n\n\n``V0.3.217 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: cleanup checksum error flags after \"file verify -checksum\"\n\n``V0.3.216 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: fix OC upload complete condition\n\n\n``V0.3.215 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- ETC: fix typo introduced by MALLOC_CONF_VARNAME\n\n``V0.3.214 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: fix geobalancer default variable names (were geotagbalancer)\n\nNew Feature\n+++++++++++\n\n- MGM: bounce checksum & open requests without an attached replica to an alive master\n- MGM: add heap profiler\n\n``V0.3.213 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: Fix condition in ShellExecutor leading to deadlock in MGM startup\n- TEST: Adapt the eos-instance test give the modifications done to the default \"replica\" layout i.e. drop of the blockchecksum\n\n``V0.3.212 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: Fix race condition in TPC implementation\n- FST: convert some critical errors to warnings\n- COMMON: add an alarm timer for the ShellExecutor forked process to die on its own if the parent process disappears\n- MGM: fix miscounting quote bug when deleteOnClose is triggered\n- MGM: fix bug introduced by commit 089803efe0b0cde882ed655788985eb166eb4546  triggering a SEGV under load due to out-of-lock access\n- MGM: fix balancer bug which was in case of N full and M empty boxes balancing the M times more from first box instead of all N equally\n\n\nNew Feature\n+++++++++++\n\n- FST: add a connection pool to avoid bottleneck due to slow close blocking other opens to the same target FST - the connection pool size is by default 64 and can be changed by the variable EOS_FST_XRDIO_CONNECTION_POOL_SIZE\n- MGM: add an environment variable allowing read-write-modify to all all users on MGM for RAIN layouts (define EOS_ALLOW_RAIN_RWM)\n- MGM: relax OC chunked upload order restriction - order is irrelevant and retries but the last chunk terminates an upload\n\n``V0.3.211 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: don't set the truncate flag in OpenAsync to avoid increment of inode when async open is done\n- NS: fix copy constructor not duplicationg the pTreeSize variable\n\n``V0.3.210 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: fix 'ScanDir' functionality to deal properly with files which get opened during a scan for update and don't flag them as checksum error files\n- FST: ignore flagged checksum errors when updating a file\n\n``V0.3.209 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: move from passive cache expiration to active write-back cache cleaen-up (by thread) - the maximum allowed default size of wb-file caches is 512 MB\n- MGM: fix acl check if client sends base64 encoded acl values (as EOS 4.X does)\n- FST: fix memory and fd leak triggered by deleteOnClose on files with block checksums\n- FST: silence \"probably already unlinked\" message in XrdFstOss::Unlink\n\n``V0.3.208 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: enable blockchecksums againf for plain layouts if there is an .xsmap file - this avoids bogus errors and still checks the blockchecksum files if they are available\n- CONSOLE: adjust the console command to not add block checksum for \"attr set default=replica\"\n\n``V0.3.207 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: put back the posix_fallocate call since xfs pre-allocation slows down when a truncate is called and produces contention in the Oss::Close handle where xrootd uses a global lock\n- COMMON: disable block checksums for plain and replica layouts by force\n\n``V0.3.206 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: avoid bogus mgm/disk size errors due to still uninitialized disk size values\n\n``V0.3.205 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: avoid double deletion in Fmd code\n\n``V0.3.204 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: protect accessing a 0 pointer in opendir\n- FUSE: store all invisible items in the FUSE stat cache although they are not visible in the listing\n\n``V0.3.203 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: refactor opendir/readdir/closedir consistency and directory caching\n\n\n``V0.3.202 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: fix return code handling of xfs pre-allocation in CheckSum.cc\n\n\n``V0.3.201 Aquamarine``\n=======================\n\n\nBug Fix\n+++++++\n\n- FST: always reset the disk checksum in the meta data db when a file has been modified\n- FST: consider only flagged file/blockchecksum errors to prevent to return meta data objects\n- FST: set /var partition RO threshold to 95% full\n- FUSE: swap lines to avoid valgrind warning about use after erase\n- MGM: return json responses with json response tag\n- DOC: fix commit message for release number\n\n\n``V0.3.200 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix out of lock scope iterator used in error message\n- FUSE: give no validity to attributes coming as fuse-replies to a create call (since uid/gid can be different on MGM side from uid/gid of the caller)\n- FST: prevent deleteOnClose when clients retried an open e.g. open | open | write| close (the XRootD client might replay an open with a new connection and this can lead to file loss)\n- FST: switch filesystems to RO when /var partition is 90% full\n- FST: make deleteOnClose a warning on client disconnect\n\n``V0.3.199 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix wrong lock scope when readdir buffers are retrieved\n\n``V0.3.198 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- HTTP: drop FileClose handler to avoid SEGVs due to interference between FileClose and Complete handler\n- NS: avoid failing compaction when a slave was promoted to be master due to virtual root entry with 0 offset in changelog file\n- ARCHIVE: use MGM alias to reference instances in archives\n- FST: protect against 0-size buffer response bug in XRootD 3.3.6\n\nNew Feature\n+++++++++++\n\n- MGM: add some more information about the currently in-use file/container-id and the id's created since last boot\n- MGM: allow update of 0-size RAIN files to allow lazy-open with RAIN layouts\n\n\n``V0.3.197 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: return correct (also overlaid) mode bits after file creation\n\n``V0.3.196 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- NS: fix slave follower getQuotaNode exception preventing quota accounting on slave\n- FUSE: swap unlock and pool-fd push (which is protected by the same file abstraction rwmutex)\n\n\nNew Feature\n+++++++++++\n\n- MGM: add 'Treesize' to the output of 'file info'\n\n``V0.3.195 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix possible size inconsistency after utimes call storing size=0 in kernel cache\n\nNew Features\n++++++++++++\n\n- TEST: adding eos-fuse-test suite to eos-test RPM (use eos-fuse-test to display individual test categories)\n\n``V0.3.194 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix truncate bug putting a stall file size after truncate into the kernel cache\n\nNew Features\n++++++++++++\n\n- TEST: add test for truncate bug to eos-fuse-test\n\n``V0.3.193 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: add monitoring switch to space,group status function\n- MGM: draining mutex fix and fix double unlock when restarting a drain job\n- MGM: fixes in JSON formatting, reencoding of non-http friendly tags/letters like <>?@\n- MGM: fix possible lock problem in 'eos find' mgm implementation\n- MGM: fix memory leak in fs.Ping (xrootd3 mem leak)\n- MGM: fix bug when revoking sudo privileges\n- MGM: decode all base64 prefixed attr values before storing in attr_set\n- MGM: return base64 encoded attributes in attr_get when called via FUSE\n- NS:  handled uncatched exception in the slave follower when looking for a quota node\n- FST: wait for pending async requests in the close method\n- SPEC: remove directory creation scripting from spec files\n- FUSE: fix bug in 'setxattr' function\n- FUSE: protect against missing response buffer\n\n``V0.3.192 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: fix regression from bug fix in 191\n- FUSE: fix getxattr return value as ENOATTR if attribute not found\n\n\n``V0.3.191 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: honour (rare) xrootd XOFF send on open to retry after <n> seconds to open a file due to contention on xrootd tables\n\n``V0.3.190 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix memory leak when returning readdir from in-memory cache\n\nNew Features\n++++++++++++\n\n- FUSE: update SELINUX policies\n- FUSE: create /var/run/eosd and /var/log/eos/fuse/ directories in eos-fuse-core\n- MGM: allow to change the find query limitations (by default 100k/50k files/dirs) via the 'access' interface. See 'eos access -h'.\n\n``V0.3.189 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- MGM: add JSONP response object format when 'callback=...' is specified in a query URL\n\n``V0.3.188 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: wake up the recycle thread if there is a change of the recycle policy\n- MGM: don't cache unresolved uid/gid with their number, since sssd translation is not 100% successful\n- MGM: allow underscore in user/group names (ACL parsing)\n- MGM: forward errors from find (like query limitation etc.)\n- MGM: don't keep the Stat mutex when translating uid/gids\n- MGM: fix slave follower bug when moving a subtree\n- MGM: fix recursive accounting on slave\n- MGM: resolve symlink when opening a file via non-FUSE clients to resolve to the right quota node\n- MGM: fix bug in creation of shared URLs after introduction of URL encoding\n- CONSOLE: fix recursive copy bug in eos cp\n\nNew Features\n++++++++++++\n\n- FUSE: refactor FUSE rpms into eos-fuse-core & eos-fuse-sysv. The core has only mount scripts and not sysv scripts anymore\n- FUSE: add SELINUX policies in the eos-fuse-core postinstall script\n- MGM: add JSON output formatting for all REST commands\n\nDocumentation\n+++++++++++++\n\n- WFE: document workflow engine\n- REST: document rest api for space, node, group and fs calls\n\n``V0.3.187 Aquamarine``\n=======================\n\n- FUSE: forward correct errno from XrdCl::Open failures\n- FUSE: fix wrong map deletion when unlink/rmdir fails (visible with rsync  --delay-updates)\n- FUSE: add mknod implementation to allow kernel NFS exports\n- MGM: fix SEGV when looking at the changelog file\n\n``V0.3.186 Aquamarine``\n=======================\n\n- FUSE: fix inode mapping after repair and follow new inode\n- FUSE: avoid to force a file open for a utimes setattr call\n- MGM: fix 'map' interface to work with encoded FUSE paths\n- CONSOLE: update 'fs dropdeletion' and deprecate 'fs dropfiles' and MGM redirection behaviour for 'fs dropdeletion'\n\n``V0.3.185 Aquamarine``\n=======================\n\n- FST: correct error codes in eoscp to flag target errors in tranfser queue jobs\n- MGM: allow 'xrd.*' to be present in proc commands (used by FUSE repair)\n\n\n``V0.3.184 Aquamarine``\n=======================\n\n- FUSE: report 1k as maximum file name length in statvfs\n- FUSE: don't trigger recovery if a file is deleted before it is actually written\n- MGM: update directory mtime when a replica drop leads to a file remove\n- FST: don't give a checksum error if a not yet fully created file is read by a second FUSE client\n\n\n\n``V0.3.183 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix lock bug visible since 0.3.182 in the WriteBack cache as a dead-lock (responsible for many previous changes)\n- FUSE: close inconsistent mtime window present during release file (vim editor problem)\n\n``V0.3.182 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix bug introduced in 0.3.181 to force creation of a file before a read open can proceed\n- FUSE: use a standard mutex instead of a rw mutex to protect wb cache map\n- FUSE: fix open(update) wrong mtime behaviour observed when using vim ona a file without local caps\n- COMMON: fix performance relevant ShellCmd::Wait() function to use exponential backoff starting at 1ms to discover if a subprocess has terminated. This has a drastic effect on balancing and draining jobs which was limited to 1Hz due to this implementation\n- FST: when running multiple FST instances store the eoscp log for each instance in their private log directory\n- FST: fix missing tpcClose when a target TPC operation had been terminated\n- MGM: use conditional/scoped lock monitor to avoid any path in the code where the quota mutex could stay read-locked and no new quota node can be created/listed\n\n\nNew Features\n++++++++++++\n\n- MGM: by default don't do a risk analysis for 'fs status' since it can take significant amount of time when millions of files are on a filesystem - previous behaviour using 'fs status -r'\n- MGM: extend 'schedule2balance' call to directly return a balance job to the FST instead of sending it through the asynchronous queue (FST equivalent part is still not committed)\n- FUSE: add an environment variable to simulate slow backend behaviour in the asynchronous part of FUSE (EOS_FUSE_LAZY_LAG=<ms>)\n\n``V0.3.181 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: fix double unlock leading to an abort if a file checksum was found\n- FUSE: fix race condition in locking scheme when adding pieces to the writeback cache\n- FUSE: avoid several memory leaks induced by open/write/close/delete sequences\n- FUSE: avoid possible order inversion of Open[create] file / Open[read] file\n\n``V0.3.180 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: fix particular geo scheduling case which could return ENOSPACE\n- MGM: avoid dead-lock in SetQuota calls\n\n``V0.3.179 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix SEGV introduced by XrdIo memory leak fix in 0.3.177\n\n``V0.3.178 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- MGM: fix geotag scheduling when exact switch is enabled/disabled (try always first with exact geo matching, then relax the requirement)\n- FUSE: fix SEGV on krb5 recovery redirection\n- COMMON: fix eternal loop for esoteric .././.././../ path combinations\n\n``V0.3.177 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FST: reduce lock contention on Sqlite mutex\n- FST: use one Sqlite lock per filestem instead of a global lock for all filesystems\n- ETC: fix use of default mount dir in eosd scripts\n- FUSE: fix invalid modtime calculation disabling directory caching\n- FUSE: fix memory leak in XrdIo when a file was deleted before it was ever opened\n- HTTP: add mutex to avoid parallel loading of grid-map file and possible memory SEGV when parsing\n- NAMESPACE: don't cancel follower threads on the Slave in active code (avoids exceptions on pthread_join)\n\nNew Feature\n+++++++++++\n\n- FUSE: add support to compile eosd3 using libfuse3\n\n``V0.3.176 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: unset KRB5CCNAME only when run as a shared fuse mount ( prevented krb5 for single user mounts via 'eos fuse mount'\n- FUSE: fix XRootD 3.3.6 memory leaks in every synchronous call (AnyObject leak) - not present anymore in XRootD 4.X\n- FUSE: add clean-up to filesystem destructor to clean valgrind reports\n- MGM: remove tight lock on namespace boot in HTTP service\n\nNew Feature\n+++++++++++\n\n- FUSE: by default hide all special files from version/atomic/backup - enable with env EOS_FUSE_SHOW_SPECIAL_FILES=1\n- FUSE: by default configure a 64M shared write-back cache for shared and single-user mounts\n- FUSE: use a blocking flush if the write-back size is larger than the in-memory cache - in this case there is no recovery possible so it is better to see possible errors on the application layer via the flush call\n\n``V0.3.175 Aquamarine``\n=======================\n\nBug Fix\n+++++++\n\n- FUSE: fix memory leaks and missing mutex - remove w-open tracking map\n\n``V0.3.174 Aquamarine``\n=======================\n\nNew Feature\n+++++++++++\n\n- FUSE: add 'restore' functionality which recovers file write errors on client side transparently if all the writes are still in the local in-memory cache\n- FUSE: add the option do do an asynchronous open after a lazy open call (by default disabled - still WIP)\n\nBug Fix\n+++++++\n\n- MGM: print fid as decimal number in 'file info'\n- MGM: redirect new 'Redirect' fuse call on the MGM always to a master\n- MGM: keep the replica chain in the same order for FUSE updates (cl=>rep1=>rep2) doing identical scheduling\n- FST: fix 'tried' CGI to append to a list and not overwrite previous tried add-ons\n\n``V0.3.173 Aquamarine``\n\nNew Feature\n+++++++++++\n\n- FUSE: deal properly with security/system.posix_acl attributes in (cp -a errors)\n- FUSE: reduce significantly memory footprint for tight file creation loops - default in-memory cache reduced from 1M to 4k\n- FUSE: cleanup in-memory caches of deleted files immediately\n- FUSE: use asynchronous writes in release call and gain 25% performance\n- FUSE: prefer readlocks when submitting a piece to the wb-cache and refresh iterator if mutex upgrade from r->w is needed\n- WebDAV: return logical bytes as quota\n- RPMS: add dependency for JEMALLOC at runtime for eos-server and eos-fuse rpms\n\nBug Fix\n+++++++\n\n- FUSE: fix bug bypassing the directory cache all the time when doing ls,ls -l ...\n- FUSE: detect meta data updates on directories and refresh the client cache accordingly\n\n``V0.3.172 Aquamarine``\n\nNew Feature\n+++++++++++\n\n- reduce default write-back page size to 256k (was 4M)\n- make the page size configurable via env EOS_FUSE_CACHE_PAGE_SIZE (in bytes)\n\n\n``V0.3.171 Aquamarine``\n\nBug Fixes\n+++++++++\n\n- fix 'd' via ACL for OC access\n\n``V0.3.170 Aquamarine``\n=======================\n\nNew Feature\n+++++++++++\n\n- remove 'chown -R' on FST partitions which was used to compensate a bug visible in 0.3.137 since it might introduce large unnecessary boot times when updating from versions < 0.3.137\n\n``V0.3.169 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix exclusive lock held around fallocate delaying all writes and opens during an fallocate call (FST)\n- fix SEGV in readlink call when an errno is returned (FUSE)\n- fix OC access permission string to include writable for ACL shared directories (MGM)\n- fix race condition when FUSE write-back cache is full - JIRA EOS-1455\n- don't report symlinks as zero replica files\n- fix SEGV in enforced geo placement where no location is available\n\nNew Features\n++++++++++++\n\n- add new FUSE config flags to enable automatic repair of a broken replica if one is still readable - default enabled until 256MB files\n  - export EOS_FUSE_INLINE_REPAIR=1\n  - export EOS_FUSE_MAX_INLINE_REPAIR_SIZE=268435456\n- bypass authentication requirements for 'eos version' call (e.g. when getting the supported features)\n- add IO error simulation for open on FSTs\n\n``V0.3.168 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- initialize container mtime by default with ctime if not defined\n\n\n``V0.3.167 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- add responses for custom namespaces (for new Owncloud clients) HTTP\n- fix race condition for stat after close in FUSE\n- gcc 6.0 warnings\n- don't version module libraries anymore (as done by newer cmake)\n\nNew Features\n++++++++++++\n\n- introduction of 'sys.mask' attribute to apply a default mask to all chmod calls on directories (attribute disables !m in acls)\n\n``V0.3.166 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix 'dumpmd' response for files with empty checksum, which cannot be parsed by the FST\n- convert r=>w lock in FUSE (dir_cache_sync) to fix crashes in readdir\n- protect 'recycle ls' to exceed string size limitation when listing millions of entries - stops at 1GB of console output and displays warning message\n\nNew Features\n++++++++++++\n\n- by default use FUSE in async mode e.g. fsync is not a blocking call - enable sync behaviour via sysconfig EOS_FUSE_SYNC=1\n- by default use new FST fast boot option and disable WAL journaling of SQLITE db - the pedantic boot behaviour can be enforced via sysconfig EOS_FST_NO_FAST_BOOT=1\n- add 'service eos clean fst' and 'service eos resync fst' to enforce a start behaviour (no resync or resync)\n\n``V0.3.165 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix race condition on google_hash_map in FUSE leading\n\nNew Features\n++++++++++++\n\n- don't set/get xattr with \"security.*' keys in FUSE\n\n``V0.3.164 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix serious bug when moving directory subtress (as used by recycle bin) attaching moved trees after a reboot to the source location\n\n.. warning:: it is highly recommended to update the MGM, if possible purge all recursive deletes before reboot from the recycling bin\n\n``V0.3.163 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix dual side/dual fs exact placement\n- fix 'eosd status' script\n\n``V0.3.162 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- mask all special mode bits in FUSE (was breaking writes via CIFS server if no group-w bit set)\n- fix missing lock in TPC handling function in storage nodes\n- apply removed sudoer privileged in running instance\n\nNew Features\n++++++++++++\n\n- add 'service eosd killall' command and fix 'service eosd condrestart'\n\n\n``V0.3.161 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix race condition originating in use of iterator outside locked section for setattr(utime)\n- fix check for encoding support in FUSE client\n\n``V0.3.160 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix file magic in various startup scripts\n- place (u)mount.eos in /sbin\n- fix eosd script and mount script to be compatible with autofs on EL6/7 and systemd\n- fix geo placement for minimal geo case of two sites/two filesystems and 1 replica\n\nNew Features\n++++++++++++\n\n- add new encoding feature allowing full support of all characters via FUSE\n- remove global locks around XrdCl calls in FUSE for better parallelism and less lock contention\n- add version/fsctl call to discover available (FUSE) features of an MGM service\n- add convenience RPMs to configure EOS repositories for YUM installation\n\n``V0.3.159 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix SEGV in directory rename in FUSE\n- fix read-after-write short-read for not aligned read crossing local-cache/remote border in FUSE\n- make '.' and '..' visible in FUSE (again)\n\nNew Features\n++++++++++++\n\n- find honours now also ACLs in all recursive directories\n\n``V0.3.158 Aquamarine``\n=======================\n\n- protect against failing inode reverse lookup\n\n``V0.3.157 Aquamarine``\n=======================\n\n- add mount scripts to eos-fuse RPM\n\n``V0.3.156 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- high speed directory listing in FUSE (enhanded protocol returning stat information with readdir - backward compatible)\n- changing ETAG definition for directories to ino(hex):mtime(s).mtime(ms)\n- allowing arbitrary remote path to local path mounting (no matching prefixes needed)\n- allow to give a mount directory to 'mount -t eos <instance> <local-dir>'\n- documentation for geotags and new fuse features added\n- add 'find --xurl' to get XROotD urls as output\n- refactor FUSE in pure C++\n- use only eosd for single user mounts and shared mounts (fix eosfsd grep in any operation script)\n- generate mtime timestamps locally\n- auto-detect LAZY open capability of mounted server\n\nBug Fixes\n+++++++++\n\n- fix single user mount 'eos fuse mount' prefix\n- removing deprecated env variables in FUSE\n- track open inodes to prevent publishing stall size information from directory/stat cache\n- fix 'mkdir -p' in CLI\n- fix sync time propagation in Commit call\n- fix '-h' behaviour of all shell commands\n- protect against namespace crash with 'file touch /'\n- fix sync time propagation in mkdir and setTMTime\n- fix rm level protection\n- don't report symbolic links a zero-replica files\n- fix SEGV in PIO mode when an error is returned in FUSE client\n- fix FUSE rename\n- fix FUSE utime/mtime behaviour\n- fix FUSE daemonize behaviour killing systemd on EL7\n\n``V0.3.155 Aquamarine``\n=======================\n\n.. warning:: The FUSE implementation in this release is broken in various places. The sync time propagation in this release is broken. Don't use this version in production on client and server side!\n\nBug Fixes\n+++++++++\n\n- fix FUSE memory leak\n- fix esod start-script typo\n- fix HTTP PropFind requests for owncloud - unencoded paths in PropFind request to check quota & access permissions\n\n``V0.3.154 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- disintiguish OC propfind and 'normal' propfind requests to report sync time or modification time of a directory\n- fix 409 ERROR for HTTP PUT on non-existent path\n- don't commit anymore mtime from FSTs for FUSE clients - let the FUSE client execute utime during close\n- encode mtime.tv_nsec in the XRootD stat responses (inside device id) to track mtime with ns precision on open files\n- protect plain-layout read-ahead mechanism with respect to size changing files\n- FUSE: implementation refactoring (will break mtime consistency when used against old instances)\n- => use negative stat cache of the kernel\n- => add temporary and size limited in-memory rw cache per file to avoid waiting for flush of not written out pieces\n- => add creator capability mechanism to assign local cache capability of a newly created file for a limited time to the local FUSE cache\n- => retrieve mtime in ns precision for wopen files from the FST. commit last mtime on FST to MGM in asynchronous close operation\n- => hide write latency completely in asynchronous write chain where open(MGM)=sync, open(FST1..X)=async, write(FST1)=async,flush=async,close=async\n- => print FUSE settings on startup into log file\n- => remove deprecated FUSE options, add new FUSE options to example files and verbose output on startup\n- => point an unconfigured FUSE target url to localhost instead of eosdev\n- => modify default values of FUSE configuration (enable lazy-open-w)\n\n``V0.3.153 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- console add 'rm -rF' allow only root to use the bypass of the recycling policy\n- console revert to use by default host+domain names and add a '-b,--brief' option to all fs,node,group commands to get short hostnames\n\n``V0.3.152 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- re-enable FUSE concurrent opens and close\n- fix FUSE lazy open and negative stat cache broken in the previous release\n- fix wrong timestamping of symlinks\n\n``V0.3.151 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- synchronize with CITRINE FUSE implementation\n\n``V0.3.150 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix wrong mount-prefix handling for deepness>1\n\n``V0.3.149 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- import the CITRINE FUSE implementation and build this one\n- making big writes and local mtime consistency the default behaviour in FUSE\n\n``V0.3.148 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- add progress report on TTY console for all boot steps and estimate of boot time\n- automatically store version in the recycle bin and allow to recall using 'recycle restore -r <key>'\n\nBug Fixes\n+++++++++\n\n- fix FUSE daemonize to work properly with autofs\n\n\n``V0.3.147 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- shorten hostnames (remove domain) in all view functions besides monitoring format\n- add support for multi-delegated proxy certificates\n\n``V0.3.146 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- fix http upload implementation for large body uploads\n- allow to disable block checksumming via opaque tag\n- use aggregation size in the WebDAV quota response and not the quota accounting\n- track file size to avoid FUSE write-cache flushing on stat and listing\n- merge no-quota-error in xrootd errors response into e-nospace to avoid the client reporting an io error\n\n``V0.3.145 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- add option to exclude all xattrs from being applied on the destination dirs by using the wildcard \"*\".\n- clean-up the python cmake modules and simplify the use of Python related variables\n- remove only the leading \"eos\" string when building the proc path for the MGM\n\n``V0.3.144 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n\n- source sysconfig file inside MGM before running service scripts\n\n``V0.3.142 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- add service alias example in eos.example how to run with systemd\n\n``V0.3.141 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- don't ship fuse.conf on EL7 in eos-fuse RPM\n- fix reporting of subtree copying in 'eos cp'\n\n``V0.3.140 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix missing object in drain lock helper mutex\n- distinguish client and FST methods to prevent having FSTs calling a booting slave with namespace modifications\n- add min/maxfilesize check during the open function, to block too large uploads immediately\n\n``V0.3.139 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- automatically chown files on FST partitions once (to compensate to bug introduced in 0.3.137)\n- make the XRD stream timeout configurable and increase the default to 5 minutes\n\n``V0.3.138 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- allow to specify the network interface to monitor on the FST via environment variable\n- run the FST and MGM again as daemon/daemon and switch only the monitoring thread in ShellCmd to enable ptrace for all spawned sub commands\n\n``V0.3.137 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- don't scan partial read files when also if no blockchecksums are configured\n- fix recursive copy command allowing spaces in path names\n\n``V0.3.136 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- implement 'eos ls -lh' for readable sizes\n- add extended attributes on files\n- add 'file tag' command to manually set/remove locations\n- allow 'file injection' to upload contents into an existing file\n- add optional namespace subtree aggregation and introduce the concept of sync time\n- implement <oc::size> and <oc::permissions> in PROPFIND requests\n- run MGM/FST with effective user ID of root and filesystem ID of daemon/daemon\n\n\nBug Fixes\n+++++++++\n- avoid default auto-repair trigger if not configured\n- fix high system time bug in ShellCmd class\n- don't use fork when doing a stack trace, use ShellCmd class\n- use always the current configured manager from global configuration to avoid eternal looping in case of certain failover scenarios\n- avoid rescheduling of files on a location still in the deletion list\n\n``V0.3.134 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- avoid 'fork' calls in the namespace library using the 'ShellCmd' class\n\n``V0.3.133 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix wrong EXITSTATUS() macro preventing clean Slave2Master transitions\n\n``V0.3.132 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- revert faulty bug fix introduced in 0.3.130 preventing a slave to boot the file namespace\n\n``V0.3.131 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix comparison between FQDN and hostname when registering FSTs with the MGM\n- forward errno to client console when archive/backup command fails\n- fix accidental deletion of opaque info at the MGM for fsctl commands\n- various FUSE bugfixes\n\nNew Features\n++++++++++++\n- add queuing functionality to the archive/backup tool\n\n``V0.3.130 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix eternally booting slave and crazy boot times\n\n``V0.3.129 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix for memory leak by ShellCmd not joining properly threads\n\n``V0.3.128 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- avoid to call pthread_cancel after pthread_join (SEGV) in ShellCmd class\n- fix startup script to align with change in grep on CC7\n- fix gcc 5.1 warning\n\n``V0.3.127 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- several compilation and build fixes (spec) for i386 and CC7\n- fix fuse base64 encoding to not break URL syntax\n\n``V0.3.126 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- major improvements in automatic error recovery for read and writes\n- a failed create due to a faulty disk server is recovered transparently\n- a failed read due to a faulty disk server is recovered transparently\n- an update on a file where not all replicas are available triggers an inline repair if (<1GB) and if configured via attributes an async repair via the configure - FUSE has been adapted to deal with changing inodes during a repaired open\n- distinguish scheduling policies for read and write via `geo.access.policy.read.exact` `geo.access.policy.write.exact` - if `on` for **write** then only groups matching the geo policy and two-site placement policy will be selected for placement and data will flow through the close fst - if `on` for **read** the replica in the same geo location will always be chosen\n\n``V0.3.125 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- allow to disable 'sss' enforcement on FSTs (see /etc/sysconfig/eos.example) - each FST need a prot bind entry on the MGM config file when enabled\n- show the current debug setting in 'node status <node>' as debug.state variable\n- add support for multi-session FUSE connections with uid<1024*1024 and gid<65536 sid<256\n- introduce vid.app, avoid stalling of 'fuse' clients and report application names in 'who -a'\n- implement 'sys.http.index' attribute to allow for static index pages/redirection and support URLs a symbolic link targets\n- follow the 'tried=<>' advice given by the XRootD client not to redirect again to a broken target\n\nBug Fixes\n+++++++++\n- fix 'eos <cmd>' bug where <cmd> is not executed if it has 3 letters and is a local file or directory (due to XrdOucString::endswith bug)\n- update modification for intermediate directories created by MKPATH option of 'xrdcp'\n- fix 'vid rm <key>'\n- revert 'rename' function to apply by default overwrite behaviour\n- allow arbitrary symbolic link targets (relative targets etc.)\n- disable readahead for files that have rd/wr operations\n- allow clean-up via the destructor for chunked upload files\n- fix directory listing ACL bug\n- avoid timing related dead-lock in asynchronous backend flush\n\n``V0.3.121 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- support ALICE tokens in gateway transfers\n- allow to disable enforced authentication for submitted transfers\n- disable direct_io flag on ZFS mounts to avoid disabling filesystems due to scrubbing errors\n\nBug Fixes\n+++++++++\n- replacing system(fork) commands with ShellCmd class fixing virtual memory and fd cloning\n\n``V0.3.120 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- symlink fixes\n- fix round-robin behaviour of scheduler for single and multi-replace placements\n\n``V0.3.119 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- add support symbolic links for files and directories\n- add convenient short console commands for 'ln', 'info', 'mv', 'touch'\n\n``V0.3.118 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- add console broadcasts for important MGM messages\n\nBug Fixes\n+++++++++\n\n- use correct lock type (write) for merge,attr:set calls\n- resolve locking issue when new SpaceQuota objects have to be created\n- implement a fast and successful shutdown procedure for the MGM\n- implement safeguard for the manager name configuration in FSTs\n\n``V0.3.117 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- enable read-ahead in FUSE clients to boost performance (default is off - see /etc/sysconfig/eos.example)\n\n\n``V0.3.116 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix asynchronous egroup refresh query\n\n``V0.3.115 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- reduce verbosity of eosfsd logging\n- support OC special header removing the location header from a WebDAV MOVE response\n\nBug Fixes\n+++++++++\n- fix temporary ro master situation when slave reloads namespace when indicated from compacted master (due to stat redirection)\n\n``V0.3.114 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix temporary ro master situation when slave reloads namespace when indicated from compacted master (due to stat redirection)\n\n``V0.3.112 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n\n- add support for nested EGROUPS\n- add 'member' CLI to check egroup membership\n\nBug Fixes\n+++++++++\n- fix logical quota summary accounting bug\n- fix not working 'file version' command for directories with 'sys.versioning=1' configured\n- fix order violation bug in 'Drop' implementation which might lead to SEGV\n\n``V0.3.111 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- redirect \"file versions' to the master\n\n``V0.3.110 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix copy constructor of ContainerMD impacting slave following (hiding directory contents on slave)\n- fix temp std::string assignment bugs reported by valgrind\n\n``V0.3.109 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix timed read/write locks to use absolute times\n\n``V0.3.108 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- update Drain/Balancer configuration at least every minute to allow following master/slave failover and slot reconfiguration\n\nNew Features\n++++++++++++\n- support for OC-Checksum field in GET/PUT requests\n\n``V0.3.107 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- support for secondary group evaluation in ACLs (enable secondary groups via /etc/sysonfig/eos:export EOS_SECONDARY_GROUPS=1\n\n``V0.3.106 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- update MIME types to reflect most recent mappings for office types\n\n``V0.3.104 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix custom namespace parsing for PROPPATCH requests\n- allow 'eos cp' to copy files/dirs with $\n- fix missing unlock of quota mutex in error return path\n- fix mutex inversion in STATLS function\n\n``V0.3.102 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- fix 'attr' get' function if no attribute links are used\n- use '_attr_ls' consistently instead of directly namespace map (to enable links everywhere)\n- fix PROPPATCH response to be 'multi-status' 207\n\n``V0.3.101 Aquamarine``\n=======================\n\nBug Fixes\n+++++++++\n- avoid negative sleep times in scrub loops induced by very slow disks\n- apply ANDROID patch for chunked uploads only if 'cbox-chunked-android-issue-900' special header has been added by NGINX proxy\n- make MIME type detection case-insensitive\n\n``V0.3.100 Aquamarine``\n=======================\n\nNew Features\n++++++++++++\n- add online compaction for directories selectable via 'ns compact' (see help)\n- support for symbolic attributes 'attr link', 'attr unlink', 'attr fold' to reduce directory memory footprint\n\nBug Fixes\n+++++++++\n- fix bug leading to wrong dual master detection after online compaction was running on the master\n\n``V0.3.99 Aquamarine``\n======================\n\nNew Features\n++++++++++++\n- allow 'sys.owner.auth=*' to have sticky uid/gids for such directories\n- new FST proxy redirection to send file IO through a proxy frontend\n- recursive 'rm -r' protection in fuse\n- add MIME type suffix detection\n\nBug Fixes\n+++++++++\n- remove PrivGuards from Transfer cmds enabling krb5/x509 delegation\n- fix HTTP return codes for Put and Range Requests\n\n``V0.3.97 Aquamarine``\n======================\n\nNew Features\n++++++++++++\n- forbid 'rm -r' & 'rm -rf' on a predefined tree deepness\n\nBug Fixes\n+++++++++\n- various fixes in archive daemon\n- improve speed of HTTP HEAD requests with trailing /\n- store proxy and client identity properly in VID structure\n\n``V0.3.96 Aquamarine``\n======================\n\nBug Fixes\n+++++++++\n- fix -1 bug in 'chown'\n\nNew Features\n++++++++++++\n- add dummy responses for LOCK,UNLOCK,PROPPATCH enabling OSX & Windows WebDAV clients\n- allow to modify only group ownership in chown\n\n``V0.3.95 Aquamarine``\n======================\n\nBug Fixes\n+++++++++\n- balancing: seal '&' in capabilities\n- draining: seal '&' in capabilities\n- encode all '&' in meta data synchronization\n- propagate 'disableChecksum' to all replicas during chunked uploads\n- make 'console log' e.g. /var/log/eos/mgm/error.log working again\n- fix substantial memory leak in PUT requests on FSTs\n- fix 's3' lower-case headers\n- disable 'delete-on-close & repair-on-close' for chunked uploads to allow for single chunk retry\n- fix '\\n' encoding for FUSE listing\n- require 'targetsize' in standard HTTP PUT\n- fix documentation of attributes for max/minsize in 'attr help'\n- fix sealing of empty checksum FMD info\n- fix double mapping of propfind requests\n- enable re-entrant https mapping as required by HTTPS Webdav gateways\n- fix JSON format for fsck reports\n- swap HTTP/ROOT share url\n- fix return codes for chunked uploads for cases like no quota etc.\n- add 'open' serialization for identical file paths to avoid open errors using HTTP protocol\n- don't send redirect on FST put's to avoid incomplete files\n- fix missing targetsize for standard oc PUTs to avoid acceptance of incomplete files\n- fix and use atomic CLOEXEC flag in various places\n- add PAM module to NGINX\n- fix PUT error handling (will break connection for all errors happening after 100-continue on FST)\n- various improvements to backup functionality\n- enforce order in chunked uploads\n- disable scanning of w-open files\n- fix 'geotag' client mapping\n- fix 'recycle restore' for overlapping file/directory keys\n- advertise MKCOL,PUT in OPTIONS for WebDAV write access\n- fix SEGV due to illegal mtime settings for HTTP GETs\n- fix copy constructor of Container objects\n\nNew Features\n++++++++++++\n- 'find --purge atomic' to clean-up atomic left-over garbage\n- allow 'file check fxid:.... | fid:...'\n- add 'recycle config --ratio < 0 .. 1.0 >' to set a threadshold based keep ratio in the recycle bin\n\n``V0.3.75 Aquamarine``\n======================\n\n- add support for archive interface to stage-out and migrate a frozen subtree in the namespace to any XRootD enabled archive storage\n\n``V0.3.57 Beryl``\n=================\n\nNew Features\n++++++++++++\n- adding libmicrohttpd build directory\n- support threadpool with EPOLL for embedded http server\n\nBug Fixes\n+++++++++\n- balancing: was never starting\n- scheduler: was skipping scheduling group when one node >95% network-out loaded\n- nginx: don't forward PUT payload to MGM\n- microhttpd: fix virtual memory leaking due to fragmentation\n- http: let HTTP clients see errors on PUT\n\n``V0.3.53 Beryl``\n=================\n\nNew Features\n++++++++++++\n- [webdav] add possibility to exclude directory syncs via 'sys.allow.oc.sync'\n- [webdav] add support to do path replacements provided by two special header flosg CBOX_CLIENT_MAPPING & CBOX_SERVER_MAPPING\n\n``V0.3.51 Beryl``\n=================\n\nBug Fixes\n+++++++++\n- fix gdb stacktrace getting stuck if too much output is produced - stacktrace is stored in /var/eos/md/stacktrace and then reported back into the log\n- fix wrong network traffic variable used in the scheduling implementation (used always 0 instead of real traffic)\n\n``V0.3.49 Beryl``\n=================\n\nBug Fixes\n+++++++++\n- rename: allow whitespace names, fix subpath check, fix encofing in HTTP move\n- various HTTP/DAV related return code fixes\n\nConsolidation\n+++++++++++++\n- the 'eos' shell by default does not run in 'pipe mode' e.g. no background agent\n\nNew Features\n++++++++++++\n- allow FUSE_OPT in /etc/sysconfig/eos e.g. to set a FUSE mount read-only use export FUSE_OPT=\"ro\"\n- enable MacOSX build and add packing script for DMG\n\n``V0.3.47 Beryl``\n=================\n\nBug Fixes\n+++++++++\n- bugfixes in HTTP daemon configuration/startup\n- many bugfixes for owncloud/atomic/version support\n- many bugfixes for mutex order violations\n- fix BUG in FUSE making the mount hang easily\n- fix BUG in FUSE showing alternating mtimes and showing stale directory listings\n- fix BUG in stalling drain/balance\n- fix BUG in drain reset\n- fix FD leak in Master\n- add monitor lock to getpwXXX calls to deal with SSSD dead-lock on SLC6\n- disable FMD size/checksum checks for RAIN files\n\nConsolidation\n+++++++++++++\n- FST don't clean-up transactions if their replica is registered in the MGM\n- make all HTTP header tags case-insensitive\n- HEAD becomes a light-weight operation on large directories\n- new unit tests for owncloud/atomic/version support\n- improve 'quota ls' performance and bypass uid/gid translations as much as possible\n- avoid lock contention in uid/gid translations\n- limit the 'gdb' stack trace to maximum 120s to avoid service lock-up in case of a stuck GDB process\n- FST never give up in calling a manager for errors allowing a retry\n\nNew Features\n++++++++++++\n- update 'eos-deploy' to be able to install from beryl, beryl-testing, aquamarine and citrine YUM repositories\n- adjust 'file adjustreplica' and 'file verify' for RAIN files (file verify made RAIN file inaccessible)\n- extend 'space reset' command\n\n``V0.3.37 Beryl``\n=================\n\n- add support for Owncloud chunked upload\n- add support for immutable namespace directories\n- fix drain/balancing stalls\n- fix memory leak introcuded by asynchronous XrdCl messaging\n- fix node/fs/group unregistering bug\n- make atomic uploads and versioning real 'atomic' operations (no visible state gap between target file exchange)\n- add 'file versions' command to show and recall a previous version\n- fix tight thread locking delaying start-up\n\n``V0.3.35``\n===========\n\nBug Fixes\n+++++++++\n\n- modify behaviour on FST commit timeouts - cleanup transaction and keep the replica to avoid unacknowledged commits (replica loss)\n- fix output of 'vst ls --io'\n- add option 'vst --upd target --self' to publish only the local instance VST statistics to InfluxDB\n\n``V0.3.34``\n===========\n\nNew Features\n++++++++++++\n- add global VST monitoring support - by default all running EOS instances are visible with some basic parameters using the 'vst' command\n- add support to feed VST informatino using UDP into InfluxDB for visualisation with Grafana\n- add global-mq config file to run a global VST broker\n- support 'mtime' propagation as needed by OwnCloud sync client to optimize the sync process\n- better support OwnCloud sync clients\n- restrict OwnCloud sync tree requiring 'sys.allow.oc.sync=1' on the entry directory\n- add support for atomic file uploads - files are visible with the target name when they are complete - disabled for FUSE\n- support LDAP authentication (basic HTTP authentication) in NGINX proxy on port 4443 (by default)\n- add 'file info' command for directories\n- implement 'fsck repair --adjust-replica-nodrop' for safe repair (nothing gets removed - only added)\n- allow 'grep'-like functionality in 'fs ls' commands\n- support encoding models like UTF-8 (set export EOS_UTF8=1 in /etc/sysconfig/eos)\n- accept any checksum configuration in 'xrootd.chksum' config file\n\nConsolidation\n+++++++++++++\n- FUSE (cache) refactoring & FUSE unit tests\n- send all 'monitoring'-like messages purely in async mode (not waiting) for any response e.g. all shared hash states\n\nBug Fixes\n+++++++++\n- fix PWD mapping for names starting with numbers\n- fix Windows compliance for WebDAV implementation (allprop request)\n- fix iterator issue in GeoBalancer and GroupBalancer\n- fix balancing starvation bug\n- fix 'range requests' in HTTP implementation\n- fix embedded HTTP server configuration (thread-per-client model using poll)\n- fix S3 escaping for signature checks (make Cyberduck compatible)\n\n``V0.3.28``\n-----------\n\nNew Features\n++++++++++++\n- allow FUSE mounts against Master and Slave MGM implementing a new stat function and mkdir/create returning the new inode numbers\n- add ETAG to FST GET & PUT requests\n- allow to 'grep' for several view objects in fs,node,group,space ls function\n\nConsolidation\n+++++++++++++\n- improve/fix master/slave failover behaviour\n- display the correct boot state during slave startup\n- improve stack trace to extract responsible stacktrace thread and print again in the end of a log file\n- let hotfile display files age and expire\n- don't allow to remove nodes which are currently sending heartbeats or have not drained filesystems\n\nBug Fixes\n+++++++++\n- fix leak in HTTP access leaving files open\n- fix krb5 keytab permission for xrootd 3.3.6-CERN and eos-deploy\n- fix sync startup in Slave2Master transition\n\n\n``V0.3.25``\n===========\n\nNew Features\n++++++++++++\n- allow to match hostnames in VID interface for gateway machines e.g. vid add gateway lxplus* https\n- broadcast hotfile list per filesystem to the MGM and add interface to this list via ``io ns -f``\n- use inode+checksum for file ETAGs in HTTP, otherwise inode+mtime time - for directories use inode+mtime\n- add support for file versioning using attribute ``sys.versioning`` or via shell interface ``file version ..``\n- make ApMon more flexible to match individual mountpoints via environment match variable ``APMON_STORAGEPATH`` (try df | grep $APMON_STORAGEPATH).\n- eos-deploy script is added to the repository allowing RPM installation of (possibly ALICE enabled) EOS instances with a dual MGM and multi FST setup via a single command\n- allow to list files at risk/offline via ``fs status -l <fs-id>``\n\nConsolidation\n+++++++++++++\n- add space reset to documentation\n- add release notes to documentation\n- restrict daemon account to read everything but no write permission\n- propagate ban/unban/sudo setting from Master to Slave MGM\n- map the root user on a shared FUSE mount to daemon\n- delete space,group,node objects if they contained no filesystem when rm is issued on them\n- add space/group/node create/delete tests\n- make krb5 keytab file accessible to EOS MGM (required by XROOTD 3.6/CERN and 4.0)\n- allow for new TPC protocol where destination's open arrives before the source TPC key is deposited\n- use xrdfs in eos-instance-test instead of xrd\n- add a check for missing fusermount execution permissions to the user FUSE daemon eosfsd\n- add an explicit message to the MGM log AFTER a file is successfully deleted\n- allow to select user and group ID as user and group names e.g. user foo and group bar ``eos -b foo bar``\n- add the node information given by ``ls --sys`` to the monitoring output ``ls -m``\n\nBug Fixes\n+++++++++\n- make krb5 keytab file accessible to EOS MGM\n- fix lock from rw to wr-lock when a space/node group is defined or created\n- fix boradcasting and value application on slave filesystem view\n- add the eos-test RPM to the MGM installation done via eos-deploy\n- fix path reparsing for .. to allow filenames like ..myfile\n- use path filter function in the Attr shell interface to support attr ls . etc.\n- make RAIN recovery/draining usable\n- forbid renaming of a directory into an existing file\n- add browse permission of local drop box directory\n- if no strong auth is available use sss authentication in transfer jobs\n- remove two obsolete tests from eos-instance-test and add bc to RPM dependency of eos-test\n- fix eos-uninstall script\n- don't block slave/master transitions if eosha is enabled\n- start recycle thread only when the namespace is fully booted\n"
  },
  {
    "path": "doc/diopside/releases/beryl.rst",
    "content": ":orphan:\n\n.. highlight:: rst\n\n.. index::\n   single: Beryl\n\nBeryl\n========\n\n``Lifetime: 2013-2015``\n\nThe **Beryl** release is the second generation of the production version of EOS.\nIt has been used since September 2013 and is going to be phased out in 2018.\n\nRelease notes :doc:`./beryl-release`\n"
  },
  {
    "path": "doc/diopside/releases/citrine-release.rst",
    "content": ":orphan:\n\n.. highlight:: rst\n\n.. index::\n   pair: Releases; Citrine\n\n\nCitrine Release Notes\n=========================\n\n``Version 4 Citrine``\n\nIntroduction\n------------\nThis release is based on XRootD V4 and IPV6 enabled.\n\n\n``v4.8.101 Citrine``\n=======================\n\n2023-03-22\n\nBug\n----\n\n* [EOS-5559] - EOS HTTP REST API - no JSON output if authentication is done with Bearer token\n* [EOS-5587] - jwt::decode might throw an exception\n\nImprovement\n-----------\n\n* MGM: Call proxy gateway mapping only if this has an x-forwarded-for header and drop EOS_XRDHTTP_NGINX_PROXY\n* WFE: Set a decimal string instead of an hex string in disk_file_id\n\nNew features\n-------------\n\n* [EOS-5588] - Allow HTTPS gateway functionality using key entries\n\n\n``v4.8.100 Citrine``\n=======================\n\n2023-02-17\n\nBug\n----\n\n* FST: Ensure the necessary OC client http headers are passed back to the client and\n  add a couple of helper function for building the header string for the HTTP response.\n\nImprovement\n-----------\n\n* EOS-5524 MGM: Allow https gateway nodes to provide x-forwarded-for headers\n\n\n``v4.8.99 Citrine``\n=======================\n\n2023-02-06\n\nBug\n---\n\n* EOS-5509: Fix crash of the MGM when enabling the balancer\n\nImprovements\n------------\n\n* MGM: enable hide-version also when heartbrate has been changed\n\n\n``v4.8.98 Citrine``\n=======================\n\n2023-01-19\n\nBug\n---\n\n* COMMON: fix locking in getPhysicalIds which was entering an entry into a hash without lock if uid>1M\n\n\n``v4.8.97 Citrine``\n=======================\n\n2023-01-18\n\nBug\n---\n\n* MGM: avoid to have the low-level quota lock when we call a uid/gid translate function\n* MGM: Avoid race condition when there are two metadata flusher objects - 1st of Jan bug\n* EOS-5487 MGM: let root delete files/dires in VTX directories\n\n\n``v4.8.96 Citrine``\n=======================\n\n2022-12-14\n\nBug\n---\n\n* MGM: add regfree in FuseServer regex usage to avoid memory leak\n* MGM: unlock the Access mutex when delaying a client to not get problems to get a write lock\n* MGM: reset file statistics after each inspector run for QDB backen\n\nImprovements\n------------\n\n* Invert stall logic to check first user limits and then catch-all rules\n\n\n``v4.8.95 Citrine``\n=======================\n\n2022-11-29\n\nBug\n----\n\n* MGM: Fix string to integer conversion in tape WFE code\n\n\n``v4.8.94 Citrine``\n=======================\n\n2022-11-14\n\nBug\n----\n\n* [EOS-5436] - Origin Restriction does not work as expected\n\nImprovements\n------------\n\n* MGM: don't stall clients if thread limits are not exhausted\n* COMMON: support tokens, which have been URI encoded\n* ALL: add VTX bit support, correct 'ls -la' output to show t,T,+ to indicate VTX, VTX+ACL, ACL\n\n\n``v4.8.93 Citrine``\n=======================\n\n2022-10-28\n\nBug\n----\n\n* [EOS-5424] - EOS grpc: inode from folder is 0\n\nImprovement\n------------\n\n* [EOS-5412] - add qclient performance metrics on monitoring format.\n* [EOS-5413] -  QClient performance: have last 5m, last 1m, etc metrics\n* [EOS-5426] - MGM: force show only physical space via 'spaceinfo'/statvfs\n\n\n``v4.8.92 Citrine``\n=======================\n\n2022-10-10\n\nBug\n----\n\n* [EOS-5347] - EOS token not usable via eosxd\n* [EOS-5369] - occasional error during eoscta test \"mismatch between requested fid/fsid and retrieved ones\"\n* [EOS-5371] - Home-i00 crash 25/8/22 13:15\n\n\nNew Feature\n------------\n\n* [EOS-5145] - Extending lock support\n\n\nImprovement\n------------\n\n* [EOS-3297] - Print the deviation used for the group balancer\n* [EOS-5360] - Add EOS log messages for CTA workflow events\n* [EOS-5394] - Modify EOS to fill new fields passed to CTA\n* [EOS-5401] - Return the inode number in FMD responses for GRPC\n\n\n``v4.8.91 Citrine``\n=======================\n\n2022-08-18\n\nBug\n---\n\n* [EOS-5366] MGM: Fix possible segv due to shorter than expected string\n* [EOS-5459] MGM: Fix crash due to unprotected access to a map in fusex::client::info\n\n\n``v4.8.90 Citrine``\n=======================\n\n2022-08-11\n\nBug\n----\n\n* [EOS-5355] - System ACLs evaluation overruling logic is incorrect\n\n\nNew Feature\n------------\n\n* [EOS-5342] - CREATE cta workflow not triggered when new file created using fusex - DELETE workflow is also missing\n\n\nImprovement\n------------\n\n* [EOS-5343] - Better enforcement of the scattered placement policy\n\n\n``v4.8.89 Citrine``\n=======================\n\n2022-07-20\n\nBug\n----\n\n* [EOS-5308] - MGM: Potential double free in LDAP initialize\n* [EOS-5334] - LDAP connection socket leak\n* [EOS-5335] - MGM crash in Fileinfo.cc:97\n* [EOS-5171] - Ensure mv always adopts the target's ownership, regardless the instance\n\n\n``v4.8.88 Citrine``\n=======================\n\n2022-06-30\n\nBug\n----\n\n* [EOS-5302] - Iostat domain accounting is broken\n* [EOS-5303] - Shared filesystem file registration feature\n* [EOS-5308] - MGM: Potential double free in LDAP initialize\n\nImprovement\n------------\n\n* [EOS-5321] - Allow to define which errors the fsck repair thread works on\n* [EOS-5305] - Tape REST API - V1 with an option to deactivate STAGE\n\n\n``v4.8.87 Citrine``\n=======================\n\n2022-06-09\n\nBug\n---\n\n* [EOS-5286] - Physical quota is not updated when we set EC conversion\n* [EOS-5288] - Wrong layout id after conversion operation leading to wrong physical size\n* MGM: The initial behaviour of xrdfs prepare -s/-a/-e and xrdfs query prepare have been restored\n\nImprovement\n-----------\n\n* MGM: Tape REST API v0.1 release - Support for ArchiveInfo and Release\n  functionality + discovery endpoint\n* [EOS-5282] - Allow converter configuration to persist on restarts\n\n\n``v4.8.86 Citrine``\n=======================\n\n2022-05-27\n\nBug\n---\n\n* [EOS-5272] - Fusex crash while handling hardlinks\n\n\n``v4.8.85 Citrine``\n=======================\n\n2022-05-27\n\nBug\n----\n\n* [EOS-3713] - sys.eos.mdino should not use old-style inodes\n* [EOS-5230] - Keep xattrs when restoring versions\n* [EOS-5246] - Replica shows 'error_label=none' while having checksum mismatch\n* [EOS-5262] - GeoBalancer not working with the new converter\n\nNew Feature\n------------\n\n* [EOS-4983] - Implementation of the WLCG TAPE REST API on EOS\n* [EOS-5137] - Implementation of the .well-known endpoint of the tape REST API\n* [EOS-5244] - Allow to have the token secret key in a separate file\n\n\nImprovement\n------------\n\n* [EOS-5198] - Add few log lines to MasterLog\n* [EOS-5263] - groupmod is hard limited to 256 groups\n* [EOS-5267] - Provide timestamp in eos convert list failed errors\n\n\n``v4.8.84 Citrine``\n=======================\n\n2022-05-10\n\nBug\n----\n\n* [EOS-5199] - Metadata (xattrs) is lost when creating new versions\n* [EOS-5219] - eos fsck report json output does not reflect command line options -l and -i\n* [EOS-5224] - No update is performed when adding a new member to an e-group in EOSATLAS\n* MGM: Fix eternal connect problem in Egroup ldap search\n\nNew Feature\n------------\n\n* [EOS-5178] - Implement Group Drain\n* [EOS-5213] - Introduce a generic Observer class\n\nTask\n-----\n\n* [EOS-5225] - Have a useful GroupDrain Status\n\nImprovements\n------------\n\n* FUSEX: Rewrite the handling of async messages in XrdCl using shared pointers\n* MGM: Improve handling of RAIN files with rep_diff_n errors\n\n\n``v4.8.83 Citrine``\n=======================\n\n2022-04-22\n\nBug\n----\n\n* [EOS-5197] - Deleting an xattr via console does not delete the key\n* [EOS-5199] - Metadata (xattrs) is lost when creating new versions\n* MGM: Fix crash in debug message when Env object is null for Access method\n\nNew Feature\n------------\n\n* [EOS-5215] - Fsck handle stripe size inconsistencies for RAIN layouts\n\n\nImprovement\n------------\n\n* [EOS-4955] - Add project quota tests as a part of CI\n* MGM: Iostat performance improvements for summary output\n* MGM: Iostat make extra tables optional by default and add separate\n  flag for displaying them.\n\n\n``v4.8.82 Citrine``\n=======================\n\n2022-04-12\n\nBug\n----\n\n* MGM: Ensure IoStat tables are properly formatted\n\nImprovement\n------------\n\n* [EOS-5201] - Allow for more fine grained IO policies\n* [EOS-5204] - Only create files  via FUSEX if there is inode and volume quota and physical space available\n* [EOS-5205] - Distinguish writable space and total space\n* [EOS-5206] - Don't allow to set quota volume lower than the minimum fuse quota booking size\n\n\n``v4.8.81 Citrine``\n=======================\n\n2022-04-08\n\nBug\n---\n\n* FST: Forward the opaque info also for the async open API\n* COMMON: Fix crash when checking for eos token when no env object is present\n* MGM: Update conversion string marker for ctime update from \"!\" to \"+\" to avoid\n  failures in non-UTF8 enabled instances.\n* COMMON: make EOS tokens work via GRPC\n* FUSEX: fix logical refactoring bug when introducing splitPath to validate credentials in jails\n\n\n``v4.8.80 Citrine``\n=======================\n\n2022-03-30\n\nBug\n----\n\n* [EOS-5181] - Slave to Master redirection creates IO errors on FUSEx mounts\n* [EOS-5176] - Make OAuth tolerant to self-signed//invalid certificates used by identity provider\n* [EOS-5167] - Fix segmentation fault by not starting the BulkRequestProcCleaner\n  threads if the MGM is not the master.\n* MGM: Add support for eos tokens over HTTPS (XrdHttp)\n* MGM: Fix console parsing for schedule/iopriority settings per application\n* FUSEX: fix logical refactoring bug when checking in local jails (container)\n\n\n\nNew Feature\n------------\n\n* NS: Added the possibility for namespace-related operations to communicate about some specific timings.\n\n\n``v4.8.79 Citrine``\n=======================\n\n2022-03-18\n\nBug\n----\n\n* FST: Fix reference size for RAIN layouts which needs to match the logical\n  size of the file and not the physical size of the local stripe. This can\n  lead to HTTP errors when trying to read these such files.\n* [EOS-5133] - node ls -b does not remove the domain names\n* [EOS-5153] - EC file written via FUSEx - mismatching checksum\n* FST: Add support for real-time IO priority\n\n\nImprovement\n-----------\n\n* [EOS-5126] - [eos-ns-inspect] Complement `stripediff` output\n* DOC: Add documentation on IO priorities configuration\n\nNew Feature\n------------\n\n* MGM: Added tape REST API support\n* Add eos-iam-gridmap file support for interacting with IAMs\n\n\n``v4.8.78 Citrine``\n=======================\n\n2022-02-21\n\nBug\n---\n\n* FST: Make sure promise is still valid even in the event that an exception\n  is thrown in the XrdIoHandler constructor.\n* MGM: Groupbalancer: fix app tag to not create proc directories\n* COMMON: Make sure the BufferManager satisfies buffer requests that don't\n  fit the existing slots. Also increase the default number of slots from 2\n  to 6 which covers buffers up to 64MB.\n\n\n``v4.8.77 Citrine``\n=======================\n\n2022-02-18\n\nBug\n----\n\n* FST: Make sure the async write implementation does not exhaust the system\n  memory in case the client(s) are pushing in more data than the machine can\n  distribute further in the cluster for replica layouts.\n* [EOS-5090] - convert clear is not a admin command\n* [EOS-5122] - MD and Find calls via GRPC don't obey ACLs\n\nImprovement\n------------\n\n* [EOS-5108] - workaround: drop forced automount expiry on FUSEX updates\n\n\n``v4.8.76 Citrine``\n=======================\n\n2022-02-14\n\nBug\n----\n\n* [EOS-5110] - Consolidate Access control in GRPC MD, MDSTreaming\n* [EOS-5116] - Workaround for XrdOucBuffPool bug\n* [EOS-5118] - eos-ns-inspect scan is initializing maxdepth to 0, even if not used\n* [EOS-5119] - Deadlock scenario in eosxd\n\nImprovement\n------------\n\n* [EOS-5111] - Groupbalancer: newly introduced fields may not have a sane value\n* [EOS-5120] - io stat tag totals\n\n\n``v4.8.75 Citrine``\n=======================\n\n2022-02-03\n\nBug\n----\n\n* MGM: Fix deadlock in the GroupBalancer\n* [EOS-5083] - Deletion via tokens deletes the original folder for which token was created\n* [EOS-5088] - newfind in root directory missing `maxdepth 0`\n* [EOS-5089] - newfind missing symlink listing\n* [EOS-5095] - Re-entrant lock triggered by out of quota warning\n* [EOS-5092] - allow removing max.ropen / max.wopen space/filesystem attributes\n\nImprovement\n-----------\n\n* [EOS-5101] - Label all mutex lock locations\n* [EOS-5102] - Display INGRESS and EGRESS performance in summary\n* [EOS-5103] - Add global timeout parameter to eoscp\n\n\n``v4.8.74 Citrine``\n=======================\n\n2022-01-27\n\nBug\n---\n\n* [EOS-5062] - Node config command should take fs status into consideration\n* [EOS-5079] - Fix for Groupbalancer size calculation post transfer\n* [EOS-5081] - Align buffers used during various layout rd/wr ops\n\nNew Feature\n-------------\n\n* [EOS-5067] - Groupbalancer now introduces multiple engines & supports min/max\n  file sizes. Check the GroupBalancer docs for details.\n* [EOS-5085] - Allow rate limits of zero\n\nImprovement\n-----------\n\n* [EOS-3275] - Port iostat information into quarkdb\n* [EOS-5049] - Handle draining for files with one replica on tape\n* [EOS-5051] - Benefit from parallelization in layout::open\n* [EOS-5076] - Extend iotype interfaces to be space/directory defined\n* [EOS-5080] - Support eos.app tag in the Converter interface\n* [EOS-5084] - Remove deprecated \"exists\" field in QUERY PREPARE response\n\n\n``v4.8.73 Citrine``\n=======================\n\n2022-01-18\n\nBug\n----\n\n* FST: Ensure buffers for write async requests are duplicated and kept until\n  requests are satisfied\n* FST: Fix starvation when deleting a TransferMultiplexer object\n* MGM: Fix crash when trying to convert files without replicas\n* MGM: Fix building of conversion id that was using hex representation for\n  the group indices.\n\nImprovements\n-------------\n\n* MGM: Prefetch the FileSystem contents outside the ns lock for Drop operations\n* FST: Use OS page size aligned buffers for the HeaderCRC objects\n\n\n``v4.8.72 Citrine``\n=======================\n\n2022-01-17\n\nBug\n----\n\n* [EOS-5069] - filesystem status in \"rw + failed\"\n* [EOS-5070] - Access::ThreadLimit creates re-entrant lock of the access mutex\n\nImprovement\n------------\n\n* [EOS-5065] - Add create-if-not-exists option in GRPC\n\n\n``v4.8.71 Citrine``\n=======================\n\n2022-01-14\n\nBug\n----\n\n* COMMON: Avoid segv due to mutex object set to nullptr in RWLock printout\n* [EOS-4850] - eosxd crash in destructor under metad::pmap::retrieveWithParentTS()\n* [EOS-5057] - Volume quota dispatched to FUSE clients mixes logical and physical bytes\n\n\n``v4.8.70 Citrine``\n=======================\n\n2022-01-06\n\nBug\n----\n\n* [EOS-5033] - missing drainperiod from `eos -j fs ls`\n* [EOS-5034] - eos-server missing dependency on perl(Time::HiRes)\n* [EOS-5052] - Repeated open/close sequence leads to failed file state\n* [EOS-5039] - Threads with parens in their name cannot access EOS\n\nImprovement\n-----------\n\n* [EOS-5027] - Handle eviction for multiple staging requests on the same file\n* [EOS-5029] - Allow to apply rate limiting in recursive (server side) command\n* [EOS-5048] - Support direct IO for high performance read/write use cases\n\n\n``v4.8.69 Citrine``\n=======================\n\n2021-11-24\n\nImprovements\n------------\n\n* FST: allow to disable any iopriority settings in FSTs using env EOS_FST_NO_IOPRIORITY\n\n\n``v4.8.68 Citrine``\n=======================\n\n2021-11-23\n\nBug\n----\n\n* [EOS-5015] - FSTs running versions older than 4.8.67 cannot connect to MQ\n  running version 4.8.67\n\nImprovement\n-----------\n\n* [EOS-5004] - Support sys.acl for file ACLs for RA protocols\n* [EOS-5013] - Make oAuth userinfo configurable\n* [EOS-5018] - Allow to set extended attributes on version folders\n\n\n``v4.8.67 Citrine``\n=======================\n\n2021-11-17\n\nBug\n----\n\n* [EOS-4934] - ASAN: fusex: enoent use after free\n* [EOS-4941] - FSCK toggle-repair multiple time crashes MGM\n* [EOS-4952] - Unify the various string split interfaces\n* [EOS-4963] - FST returns 200 status code for Partial Content request instead of 206\n* [EOS-4976] - Fix activity field passed from EOS to CTA\n* [EOS-4986] - eos CLI aborts with \"basic_string::_S_construct null not valid\"\n* [EOS-4992] - FST crashes upon SSI exception\n\nImprovement\n------------\n\n* [EOS-4945] - Use timestamp for saving the stack trace\n* [EOS-4995] - Add flag to 'ls' to add checksum printout in long listing\n* [EOS-5002] - Add a '-c' option to set an extended attribute only if it does not exist already\n\n\n``v4.8.66 Citrine``\n=======================\n\n2021-10-05\n\nBug\n----\n\n* [EOS-4936] - GETLK returns EAGAIN instead of lock information\n* [EOS-4937] - Fix reporting for written bytes for RAIN layouts\n* [EOS-4938] - Store report info only in the current MGM master\n\nImprovement\n-----------\n\n* [EOS-4930] - Add support for async writes for replica layout\n\n\n``v4.8.65 Citrine``\n=======================\n\n2021-09-29\n\nBug\n---\n\n* MGM: Fix quota accounting for the sum of all groups\n\n\n``v4.8.64 Citrine``\n=======================\n\n2021-09-27\n\nBug\n----\n\n* [EOS-4779] - Dead lock in parity computation for RAIN\n* [EOS-4896] - queuing for archive should use MgmOfsAlias instead of mgm.manager\n* [EOS-4912] - fst - read lock held for 10s seconds\n* [EOS-4922] - SEGV on config after shutdown was initiated\n* [EOS-4924] - FST service restarts after calls to std::future, eos::fst::Storage::Publish\n* [EOS-4925] - Typo in mgm/proc/user/Archive.cc\n* [EOS-4926] - discrepancy of accounting report and quota\n\nNew Feature\n-----------\n\n* [EOS-4903] - Add new configuration to setup redirection with Master/Slave QuarkDB Configuration\n\n\nImprovement\n-----------\n\n* [EOS-4889] - Make EOS-CTA tape garbage collector compatible with MGM master/slave configuration\n\n\n``v4.8.63 Citrine``\n=======================\n\n2021-09-10\n\nBug\n===\n\n* [EOS-4904] MGM: block proxy headers in XrdHttp by default (add env file + fix typo)\n* [EOS-4905] MGM: pass CGI 'query' to the access function used in XrdHttpMgm to allow token access\n* [EOS-4901] MGM: check for invalid paths before scoping them\n* MGM/CONSOLE: Fix acl command to accept the \"a\" archive flag\n* FST: Make sure to skip checksum if asked to ignore it\n* MGM: Reduce load on the configuration backups when moving a files systems between groups/spaces\n\nImprovements\n============\n* CI: Add ApMon build/publish job for Centos Stream 8\n* DOC: various documentation improvements\n\n``v4.8.62 Citrine``\n=======================\n\n2021-08-25\n\nBug\n----\n\n* [EOS-4327] - FST still misses the required capability key - symkey empty\n* [EOS-4852] - Race condition when accounting running console commands\n* [EOS-4878] - Balancing RAIN files stores wrong size in local DB\n\nImprovement\n------------\n\n* [EOS-4858] - Add fsck check for RAIN layout to spot disk size corruptions\n* [EOS-4863] - make eos-hashbench run a single benchmark at a time\n* [EOS-4875] - mgm: Mapping: avoid double lookups on maps\n\n\n``v4.8.61 Citrine``\n=======================\n\n2021-08-21\n\nBug\n----\n\n* Revert \"COMMON: drop 'sudo' role after sudo mapping - fixes EOS-4781\"\n\n\n``v4.8.60 Citrine``\n=======================\n\n2021-08-11\n\nBug\n----\n\n* [EOS-4480] - HA issue: GridFTP transfers with checksum testing are failing when\n  the DNS alias is not pointing towards the active MGM node\n* [EOS-4633] - 'eos' manpage is empty, rest is missing\n* [EOS-4683] - MGM LRU crash\n* [EOS-4690] - HA: transition to master crashes the future master\n* [EOS-4696] - eos config dump <name> does not work for backup configs\n* [EOS-4803] - FST node status not remaining offline when service is stopped\n* [EOS-4814] - Restore of a version does not work\n* [EOS-4818] - EOSAMS02 crash in DrainTransferJob\n* [EOS-4835] - Strange remdir unformatted lines...\n* [EOS-4843] - Wrong quota after a ns update_quotanode command\n* [EOS-4847] - group translation failing in EOSHOME for def-cg\n* [EOS-4779] FST: reduce file-local dead lock condition after parity computation error\n* [EOS-4835] MGM: fix ever growing '/' in remdir\n\nImprovement\n------------\n\n* [EOS-4411] - disk health check for Linux DM multipath devices\n* [EOS-4586] - RFE\" remove \"pre-configuring default route\" warning for fully-qualified instance+path\n* [EOS-4749] - Remove the extra-output display in eos rm command\n* [EOS-4783] - Size differs only in MGM [WIP in fsck dev]\n* [EOS-4784] - [rep_diff_n] and [rep_missing_n]; Overreplicated file, faulty replica was committed to MGM\n* [EOS-4838] - Check health status refinement\n* [EOS-4839] - Improve balancer shutdown to clean what it was balancing from the tracker queue\n* [EOS-4682] - MGM crash in LRU.hh:252 eos::MetadataProviderShard::retrieveFileMD\n* [EOS-4827] MGM: implement GRAB version functionality in GRPC\n* [EOS-4759] MGM: allow set space specific scheduling and iopriority parameter defaults\n\n\n``v4.8.59 Citrine``\n=======================\n\n2021-07-22\n\nBug\n---\n\n* [EOS-4824] MGM: avoid SEGV when loading quota nodes with certain configurations\n\nImprovements\n------------\n\n* [EOS-4823] MGM: eosxd creations support now linked attributes describing file layouts etc.\n* [EOS-4825] COMMON: allow static mapping to local accounts from 'sub' using 'vid set map -oauth2 sub:xyz vuid:localuid'\n\n\n``v4.8.58 Citrine``\n=======================\n\n2021-07-19\n\nBug\n---\n\n* [EOS-4775] NS: fixing SearchNode expansion decision taking mechanism\n* [EOS-4779] FST: fix dead lock in parity computation for RAIN\n* [EOS-4806] MGM: protect newfind command against crashes on malformed/buggy input for regex match --name filters\n* MGM: directory listing (XrdMgmOfsDirectory) always checks now ACLs for denials e.g. an ACL denial can supersede a POSIX allow\n\nImprovements\n------------\n\n* [EOS-4819]  MGM: adding server side bandwidth limitation, which can be defined either as a space policy (policy.bandwidth) or by application per space. The key for an empty application is 'space.bw.default' and the limits are given in MB/s\n* [EOS-4781] COMMON: drop 'sudo' role after sudo mapping\n* [EOS-4746] MGM/CONSOLE/GRPC: support ACL positions\n* DOC: improvements of fsck,permission and policy documentation\n* [EOS-4805] MGM: implement negative ACLs for read/write/delete operations\n\n\n``v4.8.57 Citrine``\n=======================\n\n2021-06-30\n\nBug\n---\n\n* MGM: silence 'no token' error message in Acl class\n* MGM: silence error message in CommitHelper for atomic versioning, if no file has been versioned\n\n``v4.8.56 Citrine``\n=======================\n\n2021-06-27\n\nBug\n---\n\n* [EOS-4764] COMMON:  fix overlap function used in token macro for CLI commands creating a SEGV when doing certain 'file mv' operations\n\nImprovements\n------------\n\n* [EOS-4766] MGM: Don't block HTTP access with EOS tokens in the HTTP bridge code - this allows to mix SciTokens and EosTokens inside the same instance\n\nNew Feature\n------------\n\n* [EOS-4762] MGM: add new filesystem active status online - overload - offline\n* [EOS-4760] FST: implement round-robin scheduling\n* [EOS-4759] FST: add 'eos.iopriority' to stear BFQ/CFQ priorities\n\n\n``v4.8.55 Citrine``\n=======================\n\n2021-06-22\n\nBUG\n----\n\n* MGM: silence fprintf statements in InFlightTracker\n* [EOS-4756] MGM: keep recursive deletions exactly as configured by the recycle bin time policy\n\nNew Feature\n------------\n\n* FUSEX: allow to define 'sparse ratio' to disable read-ahead for good if a file has been seen to be sparse read - normally read-ahead can get re-enabled\n* CI: allow ASAN builds to be manually triggered\n\n\n``v4.8.54 Citrine``\n=======================\n\n2021-06-18\n\nBug\n----\n\n* [EOS-4755] MGM: fix concurrency issues leading to SEGV in FuseServer/Caps (Imply)\n\n\n``v4.8.53 Citrine``\n=======================\n\n2021-06-18\n\nImprovement\n------------\n\n* MGM: support tokens for EOS CLI commands and basic xrdfs functions like mkdir/rmdir/rm\n* MGM: introduce thread pool limits by user and global using 'eos access' and show usage in 'eos ns [stat]'\n* MGM: improve performance of eosxd broadcasts and use a standard mutex to protect the caps objects\n\n\n``v4.8.51 Citrine``\n=======================\n\n2021-06-10\n\nBug\n----\n\n* [EOS-4740] MGM: Make sure only the master MGM propagates changes to the configuration engine.\n* SPEC: Fix ownership of archive directories\n* CONSOLE: Prevent to print out twice an error in selected proto commands\n\n\n``v4.8.50 Citrine``\n=======================\n\n2021-06-07\n\nBug\n----\n\n* [EOS-4725] - Unknown lock held for many seconds\n* [EOS-4730] - Fix FST crash during shutdown\n* [EOS-4736] - Memory leak when parsing diskstat on CentOS8\n* [EOS-4737] - File systems blocked in booting during mass boot with --syncmgm\n* [EOS-4740] - Inconsistent FsView maps after removing/changing file system\n\nImprovement\n------------\n\n* [EOS-4724] - Support HTTP chunked uploads\n* [EOS-4727] - Add fsck subcommand to cleanup orphans\n* [EOS-4728] - Improve the refresh of fsck stats\n* [EOS-4729] - Improve remove detached for entries with deleted parents\n* [EOS-4735] - Make Egroup queries for non existing users / groups cacheable\n\n\n``v4.8.49 Citrine``\n=======================\n\n2021-05-24\n\nBug\n----\n\n* FUSEX: properly support also KERYRING:persistent:%{UID} as default krb5 CCCAHCE\n\n\nImprovement\n-----------\n\n* [EOS-4709] - [eos-ns-inspect] adding --maxdepth to scanning functionality\n* MGM/CONSOLE: allow to scan quota in a subtree for a given uid or gid using\n  e.g. 'eos update_quotanode /eos/tree uid:123'\n* MGM: enhance eosnobody squashfs check to distinguish three instead of two cases:\n  result eosnobody can only stat via eosxd and access squashfs image files, nothing else\n\n\n``v4.8.48 Citrine``\n=======================\n\n2021-05-18\n\nBug\n----\n\n* [EOS-4715] - Segv in jemalloc during PathRouting\n* MGM: add bypass for squashfs sss 'eosnobody' file access without ACL entries\n* FUSEX: allow to open a squashfs image file client side even if we don't have R mode on the parent directory\n\n\n``v4.8.47 Citrine``\n=======================\n\n2021-05-17\n\nBug\n---\n\n* [EOS-4716] - quota zeroes the counters of used bytes/files from the quota node\n\n\nNew Feature\n------------\n\n* [EOS-4712] - Support LOCK_MAND in eosxd\n\n\n``v4.8.46 Citrine``\n=======================\n\n2021-05-07\n\nBug\n----\n\n* FST: Don't free internal jerasure structs, these will be cleaned up when the FST is shutdown\n\n\n``v4.8.45 Citrine``\n=======================\n\n2021-05-06\n\nBug\n----\n\n* [EOS-4695] - Select default KRB5 token location\n* [EOS-4697] - LRU uses wrong prefetch type\n* [EOS-4699] - Screen both mappings (uid,gid) in vid set before setting any config value\n* [EOS-4700] - Space policies interfere with conversion jobs\n* [EOS-4702] - Don't redirect to FSTs if not enough locations are available in EC layouts\n* [EOS-4704] - Memory leak when using the jerasure library\n\nNew Feature\n------------\n\n* [EOS-4705] - Block multi-source reading for EC files\n\nTask\n-----\n\n* [EOS-4684] - Make the \"file archived\" GC aware of different EOS spaces\n\nImprovement\n-----------\n\n* [EOS-4691] - Improve the locking primitives in FuseServer caps\n\n\n``v4.8.44 Citrine``\n=======================\n\n2021-04-30\n\nBug\n---\n\n* FST: fix bug introduced with a checksum reset in case of non-sequential writing\n\n\n``v4.8.43 Citrine``\n=======================\n\n2021-04-21\n\nBug\n---\n\n* [EOS-4669] - eos file verify need to be triggered twice in order to work\n* [EOS-4674] - Empty FSCK report seemingly after FST slow upgrade\n* [EOS-4676] - Crash when checking for recursive deletion\n* [EOS-4677] - FST deadlock when updating the scanner config\n* [EOS-4678] - MGM crash when removing a file system\n* Fix interference between master-slave setup and various internal services\n  like LRU, drainer and converter that should only run in a master MGM.\n\nImprovements\n------------\n\n* Add fileTruncateAsync API to the file IO interface\n\n\n``v4.8.42 Citrine``\n=======================\n\n2021-04-14\n\n\nBug\n----\n\n* [EOS-4545] Option for eosxd mounts to block symlinks walking up the hierarchy\n\nImprovements\n------------\n\n* Drop the use of folly concurrent map and use internal implementation\n* Add job for CentOS8 Stream packages\n\n\n``v4.8.41 Citrine``\n=======================\n\n2021-04-14\n\n\nBug\n----\n\n* [EOS-4607] - The command eos node config does not accept 'off' when using configstatus\n* [EOS-4627] - FSCK collected time changed after restart\n* [EOS-4629] - Checksum not recomputed after certain truncation operations\n* [EOS-4657] - File in draining with both FST checksums to 0x00\n* [EOS-4659] - Debug command broken\n* [EOS-4653] - Krb5 memory leak in CredentialValidator\n* [EOS-4660] - Potential cross-site scripting vulnerability in the EOS-HTTP\n* [EOS-4639] - Fix possible memory leak when using dense_hash_set objects\n* [EOS-4635] - Failure to share with egroups containing underscore\n* FST: Avoid early return in case of HTTP partial content like for example for range requests\n\nNew Feature\n-----------\n\n* [EOS-4623] - Create an utils script to setup a development environment on CentOS7/8\n* [EOS-4062] - Centos8: support \"KCM\" Kerberos cache\n* [EOS-4609] - Support for excess replicas/stripes\n\nImprovements\n------------\n\n* [EOS-4575] - Error on eos find command when tmp file cannot be created\n* [EOS-4617] - Quota option to provide only the quota of the specified quota node\n* [EOS-4658] - EOS workflow engine should not insist on the W_OK mode bit\n* Fsck improvements when dealing with detached files in general and also handling\n  wired cases where a file is detached but its parent id is not properly marked as 0\n\n\n``v4.8.40 Citrine``\n=======================\n\n2021-02-03\n\nBug\n----\n\n* [EOS-4506] - Slowness when changing fs configurations when using eos space\n* [EOS-4540] - FST flips status from online to offline and back when cfg.status=off\n* [EOS-4582] - investigate far-in-the-future mtime, robustify \"eos fileinfo\"\n* MGM: Fix drain for RAIN 0-size files\n\nImprovements\n-------------\n\n* MGM/HTTP: Allow running XrdHttp without the need for token authentication\n* ALL: Improve logging functionality to avoid the long tail of performance\n\nNote\n----\n\n* Upgrade to XRootD-4.12.8\n\n\n``v4.8.39 Citrine``\n=======================\n\n2021-02-08\n\nBug\n----\n\n* [EOS-4539] - FST crash on shutdown in eos::common::DbMapT::iterate()\n* [EOS-4574] - Crash in HandleVOMS when role is not present\n\nImprovement\n------------\n\n* Improve buffering and memory operations for RAIN layouts\n* [EOS-4525] - Include in acl man page the difference between sys.acl and user.acl\n* [EOS-4534] - Check compatibility of libXrdVoms.so with the HTTP interface\n* [EOS-4541] - Add a log message when a `ns recompute_quotanode` finishes\n\nNote\n----\n\n* Update to XRootD-4.12.7\n\n\n``v4.8.38 Citrine``\n=======================\n\n2021-02-02\n\nBug\n----\n\n* [EOS-4573] - ZMQ threads jump into eternal parsing error state\n* COMMON: Compensate for the missing protocol info for HTTP transfers also in the SecEntity::ToKey method\n* SPEC: Make sure the debug info is not stripped from the binaries\n* MGM: Avoid to refresh directory MD all the time after a deletion\n\nImprovements\n------------\n\n* FST: Allow XRootD env variables to override default XrdCl timeouts in EOS\n* Deal with a list of VOMS roles/groups\n\n\n``v4.8.37 Citrine``\n=======================\n\n2021-01-19\n\nImprovements\n-------------\n\nALL: Improve the logging info evaluation which is now done only if the log line is to be actually printed.\nMGM: Add hex dump of ZMQ messages received from the FUSEX clients\n\n\n\n``v4.8.36 Citrine``\n=======================\n\n2021-01-18\n\nBug\n---\n\n* NS: Make sure the dense_hash_maps used for storing file ids for the file systems\n  don't grow forever and call resize(0) to reclaim memory when elements are deleted.\n* MGM: inherit file ACLs when overwriting existing files and add instance test cases\n\n\n``v4.8.35 Citrine``\n=======================\n\n2021-01-07\n\nBug\n----\n\n* FST: Fix logic when enabling/disabling async close\n* FST: Properly align the writes for PUT requests\n* CONSOLE: Fix memory corruption issues with eos cp\n* MGM: fix webdav free quota bytes computation\n\nNew Feature\n------------\n\n* [EOS-4545] - Option for eosxd mounts to block symlinks walking up the hierarchy\n\n\n``v4.8.34 Citrine``\n=======================\n\n2020-12-17\n\nNote\n----\n\n* Fix spurius errno triggering an exception in proc/mgm/Fusex\n\n\n``v4.8.33 Citrine``\n=======================\n\n2020-12-14\n\nNote\n----\n\n* This version is built against XRootD-4.12.6 which contains some important fixes for\n  HTTP TPC transfers.\n\n\n``v4.8.32 Citrine``\n=======================\n\n2020-12-11\n\n\nBug\n----\n\n* [EOS-4499] - EOSHOME-i04 crash in eos::fusex::cap::clientuuid ()\n* [EOS-4504] - Persistent ESTAB connections on the FUSEX port from 'bogus' clients\n* [EOS-4536] - SIGSEGV around eos::mgm::FuseServer::Caps::Store\n\n\n``v4.8.31 Citrine``\n=======================\n\n2020-12-07\n\nBug\n---\n\n* MGM: Reduce scope of eos::mgm::FuseServer::Client write lock to avoid deadlock\n* MGM: Skip quota updates on the slaves as this might corrupt the ns\n* EOS-4520 MGM: fix treesize changes when moving directory trees via FUSE\n\nImprovements\n------------\n\n* MGM: Add namespace stats entry for newfind\n\n\n``v4.8.30 Citrine``\n=======================\n\n2020-12-03\n\nBug\n----\n\n* [EOS-4498] - MGM slowness in eoshome-i02\n* [EOS-4500] - EOSHOME-i01 (Apparently - Deadlock)\n* [EOS-4519] - Namespace deadlock (EOSPUBLIC)\n* [EOS-4524] - EOSCMS unresponsive\n* MGM: Prevent the prefetcher from bypassing the limits on the number of results returned when using by the find functionality\n* MGM: enforce eos access interface being only for admins\n\n\n``v4.8.29 Citrine``\n=======================\n\n2020-12-01\n\nBug\n----\n\n* [EOS-4505] - Cannot xrdfs prepare -s in EOS with no write access`\n* [EOS-4515] - HTTP PUT stores corrupted file\n* [EOS-4521] - MQ: Crash in the XrdMqOfs::stat method\n\nImprovements\n-------------\n\n* MGM: Improve FuseXCast notifications sent during the rename operation\n* MGM/FUSE: Make the mutex for Caps and Client objects blocking\n* MGM: TGC now uses tgc.freebytesscript if set and not empty\n\n\n``v4.8.28 Citrine``\n=======================\n\n2020-11-13\n\nImprovements\n------------\n\n* MGM: Modified RealTapeGcMgm::getSpaceStats() to give the exact same result as `eos space ls spinner -m`\n* FUSEX: decouple stat mutex from disk activiy - reduce mutex scopres in .stats file thread producing statistics output\n* MQ: Do broadcast all stat.* params as some are needed back on the FST side\n\n\n``v4.8.27 Citrine``\n=======================\n\n2020-11-12\n\nBug\n----\n\n* [EOS-4410] - intermittent mgm failover and offline FST\n* [EOS-4482] - Converter always uses default.0 as scheduling group\n* [EOS-4484] - Http in/out traffic accounting is broken\n* [EOS-4487] - LRU add switch for the new converter\n* [EOS-4488] - LRU requires the converter to update ctime of converted files\n* [EOS-4492] - Fix ns locking used in the LRU\n* [EOS-4494] - New converter uses only default.0 as scheduling group\n\nImprovement\n-----------\n\n* [EOS-4486] - LRU refresh once the interval is changed\n* [EOS-4489] - Add basic unit tests for the ConvertInfo class\n* [EOS-4490] - Archive should evict files from disk cached after a successful recall\n\n\n``v4.8.26 Citrine``\n=======================\n\n2020-11-02\n\nBug\n----\n\n* MGM: Fix crash when accessing file system which is null when iterating over\n  file systems in a group/space.\n\nImprovement\n-----------\n\n* [EOS-4481] - Tape garbage collector should notice file conversion jobs and also open for read requests\n* Enforce check for QuarkDB 0.4.2 minimum version\n\n\n``v4.8.25 Citrine``\n=======================\n\n2020-10-27\n\n\nBug\n----\n\n* MGM: Fix quota refresh initialization\n* [EOS-4466] - eos newfind still bogus with \"-f/-d\" filters\n* [EOS-4477] - 'eos ls' bypasses permission check when result is cached\n\nNew feature\n-----------\n\n* FST: Tool to create readv pattern and check the result of readv requests done\n  against different endpoints. Used to check for RAIN readv correctness.\n\n\n``v4.8.24 Citrine``\n=======================\n\n2020-10-20\n\nNote\n----\n\n* Release based on XRootD 4.12.5 which addresses a couple of issues in the XrdHttp component\n\nImprovement\n------------\n\n* [EOS-4464] - Latency Investigations on EOSHOME with v 4.8.22\n* [EOS-4468] - Allow open for read requests to trigger implicit prepare requests for offline files\n* [EOS-4470] - EOSCTA prepare logic within the MGM should use mgmofs.alias if set\n* Debug symbols are no longer stripped as this was leading to a crash in gdb and\n  consequently the eos-debuginfo package is no longer created.\n\n\n``v4.8.23 Citrine``\n=======================\n\n2020-10-09\n\nBug\n----\n\n* [EOS-4405] - mgm crash on eos::mgm::Stat::PrintOutTotal ()\n* [EOS-4449] - Deadlock triggered when changing eos fs configstatus in a new machine\n* [EOS-4457] - FST: Crash when scanning list of unlinked files\n* [EOS-4460] - MGM does not correctly reply to Xrd config query for TPC delegation\n* [EOS-4461] - FST exception not caught in RequestRateLimit\n\nImprovement\n-----------\n\n* FST: Remove transaction directory/functionality\n* FST: Properly align XrdHttp and EosHtpp buffers during PUT requests\n\nNew Feature\n-----------\n\n* MGM: Add QClient RTT statistics displayed in the \"eos ns\" command\n\n\n``v4.8.22 Citrine``\n=======================\n\n2020-09-30\n\nBug\n---\n\n* SPEC: adding missing mount helper scripts (packaging issue)\n* SPEC: Avoid richacl for CentOS 8 until RPMs are provided\"\n* MGM/FST: Stop the libmicrohttp daemon in the destructor of the MGM/FST HttpServer\n  derived classes otherwise the Handler method might still be called after the\n  derived classes are destructed (but before MHD_stop_daemon is called in the\n  common HttpServer) causing a SEGV due to \"pure virtual method called\" EOS-4438\n\nImprovements\n------------\n\n*  MGM: Speed up the shutdown of the routing thread\n\n\n``v4.8.21 Citrine``\n=======================\n\n2020-09-29\n\nBug\n---\n\n* COMMON: Fix bug in thread pool implementation\n\n\nImprovements\n------------\n\n* MGM/FUSEX: Add prefetching of namespace metadata where necessary\n* MGM: Fsck - don't mark 0-size files without replicas as rep_missing_n\n* MGM: Fsck - improve handling of m_mem_sz_diff errors\n* MGM/FST: Move debug command out of MQ and use XRootD query command to modify the log level\n* MGM: Move fsck command out of MQ and use XRootD query command to collect the fsck responses\n* MGM/FST: Move resync command out of MQ and use XRootD query to send such requests\n* MGM/FST: Move rtlog command out of MQ and use XRootD query to send such requests\n* MGM/FST: Move deletion scheduling out of MQ and implement it using XRootD query commands\n* MGM/FST: Move verify command out of MQ and use XRootD query command for such requests\n* BUILD: new way to build SELINUX policies\n\nNew Feature\n------------\n\n* [EOS-4431] - 'rm -rf' return directory not empty if query exceeds default user limit of 100k files\n* [EOS-4442] - Add a '-0' option to file touch\n\n\n\n``v4.8.20 Citrine``\n=======================\n\n2020-09-22\n\nBug\n---\n\n* MGM: unlimited scope of added missing Access mutex in PROC_BOUNCE_NOT_ALLOWED macro creates mutex inversions\n\n``v4.8.19 Citrine``\n=======================\n\n2020-09-21\n\nBug\n---\n\n* COMMON: fix XRootd 4.12.4 user name masking (WARNING: supports now uids only up to 1M)\n\n``v4.8.18 Citrine``\n=======================\n\n2020-09-17\n\nBug\n---\n\n* MGM: add missing mutex in access rejection macros\n\nImprovement\n-----------\n\n* MGM: improve mutex contention in Access commands (particular in combination with QDB Config)\n* MGM: adding Prefetcher in various places\n\n``v4.8.17 Citrine``\n=======================\n\n2020-09-16\n\nBug\n---\n\nCOMMON: adapt to new * => _ mapping of xrootd connection names for FUSE ID mapping\n\n``v4.8.16 Citrine``\n=======================\n\n2020-09-16\n\nBug\n---\n\nMGM: fix bug where a FuseX broadcast is run while the namespace write lock is held\nSELINUX: add missing rules for 'mount' to work with default SE settings\n\nImprovement\n------------\n\n* [EOS-4424] - Parse a second local eosxd configuration file\n* [EOS-4427] - Show where in the code a mutex is held after exceeding a given threshold\n\n\n``v4.8.15 Citrine``\n=======================\n\n2020-09-09\n\nImprovement\n------------\n\n* Release based on XRootD 4.12.4\n\n\n``v4.8.14 Citrine``\n=======================\n\n2020-09-09\n\nBug\n----\n\n* Release based on XRootD 4.12.3\n* [EOS-4399] - Fusex repair functionality corrupts files\n\n\n``v4.8.13 Citrine``\n=======================\n\n2020-09-01\n\nBug\n----\n\n* [EOS-4412] - reduce latency due to scheduling deletions (long lasting view read locking)\n* [EOS-4407] - block volume EDQUOT client-side with the first occurrence of EDQUOT on a directory\n* [EOS-4364] - prefer EEXIST over EACCESS in eosxd mkdir\n* NS: fix command executed by drop-empty-cid\n\nImprovement\n-----------\n\n* [EOS-4408] - add option to hide 'eos.*' attributes in eosxd listxattr\n* FUSEX: load OAUTH ticket file when creating a trusted credential to have the proper jail prefixes used with containerizat\n* MGM: make LRU engine less chatty\n* NS: Implement ns-inspect command to drop empty directories\n\n\n``v4.8.12 Citrine``\n=======================\n\n2020-08-25\n\nBug\n----\n\n* [EOS-4389] - EOS does not install on Macs\n* [EOS-4390] - EOS for Mac is missing libssl.1.0.0.dylib\n* [EOS-4391] - EOS for Mac is missing libXrdSecProt.so\n* [EOS-4400] - mgm crash in n __gnu_cxx::__verbose_terminate_handler()\n\nTask\n-----\n\n* [EOS-3998] - Modifying the content of a file only changes mtime (not ctime)\n\n``v4.8.11 Citrine``\n=======================\n\n2020-08-05\n\nBug\n----\n\n* [EOS-3711] - XrdMgmOfs::mkdir does not honor mode parameter\n* [EOS-3843] - Avoid to accept \"unacceptable\" block sizes (sys.forced.blocksize)\n* [EOS-3991] - Trying to stat symbolic links in Recycle bin\n* [EOS-4153] - Misleading error for lock order check when using timed locks\n* [EOS-4279] - MGM restart corrupts mtime in a directory after mkdir + quota node creation\n* [EOS-4319] - eos-ns-inspect reports wrong value for some extended attributes\n* [EOS-4367] - eoscp check if hierarchy exists before attempting to create it\n* [EOS-4369] - eos commands try to follow (non-EOS) symlinks\n\nTask\n-----\n\n* [EOS-3775] - Rename stat.drain.* and friends to local.drain.*\n* [EOS-4280] - User with no files and no quota limit should be removed from the list regardless of MGM restart?\n* [EOS-4293] - Add JSON format for `eos who`\n\nImprovement\n------------\n\n* [EOS-4308] - Update documentation for migrating to QDB config\n* [EOS-4318] - Include extended attributes in eos-ns-inspect print\n* [EOS-4371] - \"eos file info inode\": give error on \"hex\" input\n\n\n``v4.8.10 Citrine``\n=======================\n\n2020-07-24\n\nBug\n----\n\n* FUSEX: fix the real problem of EOS-4338 which is the destruction of the object before all read-ahead calls had been collected\n\nImprovement\n-----------\n\n* FUSEX: add 'trace' option and enable all debug levels in the xattr interface\n* FUSEX: trace 'slow' flush operations if they take more than 2000ms\n\n\n``v4.8.9 Citrine``\n==================\n\n2020-07-20\n\nBug\n----\n\n* MGM: suppress commit of left-over entry-gateway replica happening during eosxd recovery - fixes EOS-4340\n* FUSEX: bypass recursive rm detection by default if it is not enabled.\n* FUSEX: avoid SEGV when read-ahead callback comes and didn't get a buffer - fixes EOS-4338\n* FUSEX: fix repair when a write error occurs after the file is larger than the pre-fetch size and the first journal was not yet flushed\n* FUSEX: remove 'return' short cut to see timings of readlink\n\n\n``v4.8.8 Citrine``\n==================\n\n2020-07-07\n\nBug\n----\n\n* FUSEX: check in journalcache::reset if there is actually an open journal - fixes EOS-4322\n* FUSEX: disable FST checksum checks for all reads in general, which can break recovery if not\n\nImprovement\n-----------\n\n* FUSEX: close read-only files async in IO flush - fixes EOS-4328\n\n\n``v4.8.7 Citrine``\n=======================\n\n2020-07-06\n\nImprovements\n------------\n\n* FUSEX: don't print 'IO blocked' for the root inode, since this frequently happens after wake-up\n* FUSEX: print some user information if GETCAP results in EPERM\n* FUSEX: print some debug information if journal()->reset() fails\n* SPEC: Disable running spec scriplets if file /etc/eos/yum_with_noscripts is present\n\n\n``v4.8.6 Citrine``\n=======================\n\n2020-07-02\n\nBug\n----\n\n* MGM: don't place new replicas for read if filesize=0 and a replica is offline\n\n\n``v4.8.5 Citrine``\n=======================\n\n2020-07-01\n\nBug\n----\n\n* [EOS-4317] - Don't use repairOnClose for eosxd clients\n* [EOS-3994] - MGM should not require mgmofs.configdir if config is stored in QDB\n\nImprovement\n------------\n\n* [EOS-4311] - filesystem move is slow with in-QDB config and the lock taken triggers high node heartbeats\n* [EOS-4312] - Allow to move a filesystem to a different node via a command\n* [EOS-4313] - _find should only prefetch container metadata if no_files is set\n\n\n``v4.8.4 Citrine``\n=======================\n\n2020-06-24\n\nBug\n----\n\n* [EOS-4305] - _remdir sends fusex notifications under namespace lock\n\nImprovement\n------------\n\n* [EOS-3851] - do not `drainwait` group balancing on terminate drain statuses\n* [EOS-4306] - Add namespace mutex acquisition latency stats to \"eos ns\"\n* Add option to store the LevelDB on the data disk rather than root partition\n\n\n``v4.8.3 Citrine``\n=======================\n\n2020-06-19\n\nBug\n----\n\n* [EOS-4295] - Folder remove fails while deleting child version files (with Operation not permitted)\n* MGM: remove timeordered caps entries if there insertion time has passed, don't rely on the cap\n  validity because it can be updated in the meanwhile\n* MGM: default max children for eosxd listing to 128k not 128M\n\nNew feature\n------------\n\nMGM: Implement helper method for relocating filesystem to different FST\n\nImprovement\n------------\n\n* Build on top of XRootD 4.12.3 that fixes some HTTP crashes\n* XRootD5 compatibility\n* SCITOKENS: Build libEosAccSciTokens.so as part of the eos release\n* FST: Provide digest information if want-digest header present according to RFC3230\n* [EOS-4299] - ResyncFileFromQdb error after FST upgrade to 4.8.2\n\n\n``v4.8.2 Citrine``\n=======================\n\n2020-06-11\n\nBug\n----\n\n* [EOS-4037] - eosxd gets SIGBUS in journalcache::read_journal()\n* [EOS-4083] - eosxd abort() with \"std::bad_alloc\" under journalcache::get_chunks\n* [EOS-4276] - Add extra checks while updating the directory e-tag on 0-size file updates\n* [EOS-4282] - eos-client-4.7.16-1 requires xrootd-server-libs\n* [EOS-4286] - Cannot set `eos.mtime` using xrdcp opaque query\n* [EOS-4288] - `eos file adjustreplica` : error: invalid argument for file placement (errc=22) (Invalid argument)\n* [EOS-4289] - Replicas dropped after a conversion of a non-healthy file\n\nImprovement\n------------\n\n* [EOS-4284] - Allow automatic layout conversion hooks for file injection and file creation\n* [EOS-4285] - negative cache entries are not served from eosxd cache\n\n\n``v4.8.1 Citrine``\n=======================\n\n2020-06-02\n\nBug\n----\n\n* SPEC: Fix CentOS8 Koji build\n* MGM: Exclude tape locations from the converter merge procedure\n\n\n``v4.8.0 Citrine``\n=======================\n\n2020-06-02\n\nBug\n----\n\n* [EOS-3966] - Fix prefetching especially for RAIN and make it adaptive\n* [EOS-4035] - FST service not starting (timeout) if there are too many log files\n* [EOS-4214] - eos file convert behaviour\n* [EOS-4259] - eosxd crash under metad::add_sync() /  EosFuse::create()\n* [EOS-4260] - eosxd crash data::dmap::ioflush()\n\nTask\n----\n\n* [EOS-3976] - The converter does not honour the source file checksum if sys.forced.checksum is set on /eos/<instance>/proc/conversion\n\n\n``v4.7.16 Citrine``\n=======================\n\n2020-05-18\n\nBug\n---\n\n* [EOS-4203] - reading empty missing replica file triggers commit & mtime update\n* [EOS-4215] - ns time printing broken in fileinfo command\n\nImprovements\n-------------\n\n* CMAKE: Refactor and simplify the cmake code to move to a target based approach\n\n\n``v4.7.15 Citrine``\n=======================\n\n2020-05-14\n\nBug\n---\n\n* [EOS-4299] Fix stat counters update frequency\n* MGM: Add missing lock to MgmStats in the stall functionality\n* MGM: stat.st_nlink is an UNSIGNED integer.  Replaced dangerous -1 logic with safe unsigned logic\n\n\n``v4.7.14 Citrine``\n=======================\n\n2020-05-11\n\nBug\n---\n\n* [EOS-4210] - `eos fs ls -d` shows disks which are actually not in drain (stat.drain is empty)\n\nNew Feature\n-------------\n\n* [EOS-4205] - Be able to hide .sys.v# like folder/files to users\n\nImprovement\n------------\n\n* [EOS-4197] - Show available redundancy in 'ls -y '\n* [EOS-4207] - Add Quota (ls) command to GRPC interface\n* [EOS-4212] - Review POSIX permission behaviour in eosxd & enable overlay behaviour\n\n\n``v4.7.13 Citrine``\n=======================\n\n2020-05-08\n\nBug\n----\n\n* [EOS-4084] - 'eos fs mv'  returns 0 even in case of errors\n* [EOS-4171] - GDB seg faults when taking backtraces of EOS daemons\n* [EOS-4182] - FUSEX: 'Invalid argument' instead of 'Permission denied' on non-cached access to restricted directory\n* [EOS-4183] - eosxd: unable to delete, temporary I/O error on directory\n* [EOS-4187] - MGM: fs commands return random \"return codes\"\n* [EOS-4188] - Crash in XrdMgmOfsFile::open\n* [EOS-4189] - EOSHOME-I00 crash on XrdMgmOfsFile::open\n* [EOS-4209] - MGM: sys.acl does not accept denial of some permissions\n\nImprovement\n------------\n\n* [EOS-4113] - log: add fs number to the MGM logs for FST redirections\n* [EOS-4169] - Missing fsids in file info -m and json when NA context (it is not the case in normal file info)\n\n\n``v4.7.12 Citrine``\n=======================\n\n2020-04-29\n\nBug\n----\n\n* [EOS-4178] - use 'x' bits from ACL+POSIX for directories, while only from POSIX for files\n\n``v4.7.11 Citrine``\n=======================\n\n2020-04-28\n\nBug\n----\n\n* [EOS-3867] - MGM redirecting to itself\n* [EOS-4110] - `eos fs mv` not working properly for multi-fst instances\n* [EOS-4122] - `eos file touch` does not create a file if it not exists\n* [EOS-4131] - MGM: Broken logic in fs add leads to various inconsistencies\n* [EOS-4133] - MGM: Deadlock when booting the in memory namespace\n* [EOS-4137] - MQ: Exceeded message backlog never recovers\n* [EOS-4139] - eosxd sees EIO when rate limiter sends stalls\n* [EOS-4140] - Allow the eos command-line tool to modify the disk layout of a \"tape only\" file\n* [EOS-4150] - MGM: Acl should check for update flag present\n* [EOS-4151] - Broken shutdown sequence for EOS daemons\n* [EOS-4168] - rename & move of symlinks not supported in FuseServer\n\nNew Feature\n------------\n\n* [EOS-3415] - feature: `eos status` view\n\nImprovement\n------------\n\n* [EOS-4011] - Allow \"eos rm\" by fid for weird cases\n* [EOS-4091] - Add LRU caching to XrdMgmOfsDirectory class\n* [EOS-4092] - Add LRU caching to proc::ls function\n* [EOS-4129] - Add STAT equivalent functionality to GRPC\n* [EOS-4142] - Only set filesize in MGM when eosxd has opened a file on FSTs\n* [EOS-4152] - MGM: GroupBalancer improve cancellation/cleanup by using std::thread\n* [EOS-4166] - Enforce wait-for-flush behaviour on file creation for a list of given executables\n* [EOS-4167] - Enhance fsck repair to take an fsid and error type\n\n\n``v4.7.10 Citrine``\n=======================\n\n2020-04-17\n\nBug\n----\n\n* [EOS-4103] - FUSEX marks as 0600 file as \"executable\"\n* [EOS-4112] - Deadlock between mdstackfree and data::unlink\n* HTTP/FST: Fix crash by replying with 411 when a PUT without Content-Length is attempted\n\nImprovement\n------------\n\n* [EOS-4108] - Merge tape replicas in conversion jobs\n* [EOS-3913] - eos report is reporting deletion of files that were never transferred in the first place\n* [EOS-4104] - Allow to select, O_DIRECT O_SYNC O_DSYNC via CGI\n\n\n``v4.7.9 Citrine``\n=======================\n\n2020-04-08\n\nBug\n----\n\n* [EOS-4095] - MGM crash in `eos::common::Logging::log`\n* [EOS-4096] - Crash due to missing args in FuseServer error message\n\nImprovement\n------------\n\n* NS: Use std::mutex in the NS LRU implementation instead of eos::common::RWMutex\n  for better performance\n* [EOS-4003] - Export sys xattr to trusted machines through FUSEX\n\n\n``v4.7.8 Citrine``\n=======================\n\n2020-04-06\n\nBug\n---\n\n* [EOS-4082] MGM: remove sym link files from the file view directly\n* FST: Fix misuse of [] operator on map which can lead to crashes\n* COMMON: Make sure we use the same shared_mutex implementation (cv)\n  everywhere and update qclient\n\nImprovement\n------------\n\n* COMMON: Encapsulate VOMS mapping functionality and reuse it for both gsi\n   and http authentication\n* [EOS-3960] - eos-ns-inspect improvements\n\n\n``v4.7.7 Citrine``\n=======================\n\n2020-04-01\n\nBug\n---\n\n* MGM: fix lock order violation in FuseServer file creation\n* NS: Fix inverted condition when calculating etag for md5\n* Fixes bit-flip error when setting rsp.is_on_tape\n\n\nImprovements\n-------------\n\n* MGM: disable fusex versioning on rename - can by defining  xattr 'sys.fusex.versioning'\n* MGM: clone/hard links/recycle bin\n* MGM: Made tape-aware GC persistent between MGM restarts\n* MGM/FST The sys.cta.archive.objectstore.id xattr of a file is now set when it is queued for archival to tape\n\n\n``v4.7.6 Citrine``\n=======================\n\n2020-03-30\n\nBug\n----\n\n* [EOS-4063] - Error creating version folder\n* [EOS-4069] - Git clone does not work\n\n\n``v4.7.5 Citrine``\n=======================\n\n2020-03-23\n\nBug\n----\n\n* This only fixes a Koji build issue otherwise it's identical to 4.7.4\n\n\n``v4.7.4 Citrine``\n=======================\n\n2020-03-23\n\nBug\n----\n\n* [EOS-4013] - EOSBACKUP \"FST still misses the required capability key\"\n* [EOS-4046] - sync client re-downloading files\n\nNew Feature\n------------\n\n* [EOS-4057] - Allow fine-graned stall rules for eosxd access and restic bypass\n\nImprovement\n------------\n\n* [EOS-4056] - Make the TPC key validity configurable\n\n\n``v4.7.3 Citrine``\n=======================\n\n2020-03-12\n\nBug\n----\n\n* [EOS-4042] Cannot see the content of a version\n\n\n``v4.7.2 Citrine``\n=======================\n\n2020-03-09\n\nBug\n----\n\n* [EOS-3920] - eosxd crash in EosFuse::DumpStatistic()\n* [EOS-4016] - FUSEX: file content mixup / data corruption\n* [EOS-4025] - utimes call does not set cookie in disk cache\n* [EOS-4031] - fst crash in eos::fst::FileSystem::UpdateInconsistencyInfo() while\n  registering fss\n* [EOS-3605] - FUSEX crash in metad::pmap::lru_add()\n* [EOS-4029] - eosxd abort() in Json::Value::isMember - \"Json::Value::find(key, end, found): requires objectValue or nullValue\"\n\nImprovement\n------------\n\n* [EOS-3745] - Allow static mapping of HTTP access to a non-root user\n\n\n``v4.7.1 Citrine``\n=======================\n\n2020-03-06\n\nBug\n----\n\n* FST: Disable async close functionality that triggers a bug in XRootD due to memory corruption - seen in EOSPROJECT\n* EOS-4027: RAIN file chunk dropped when chunk drain fails due to node being offline - seen in EOSALICEDAQ\n\n\n``v4.7.0 Citrine``\n=======================\n\n2020-02-21\n\nNew Feature\n------------\n\n* Provide backup-clone functionality\n* Provide tape garbage collector base-line implementation\n* [EOS-3956] - Provide the expected checksum per block in the namespace in RAIN files\n\nBug\n----\n\n* [EOS-3377] - find -b shows wrong accounting for RAIN files\n* [EOS-3867] - MGM redirecting to itself\n* [EOS-3912] - Balancing prevented for RAIN files\n* [EOS-3917] - SetNodeConfigDefault might be called before gOFS->mMaster has been initialized\n* [EOS-3954] - eos documentation guides people towards an insecure QDB deployment\n* [EOS-3969] - Bug in NextInodeProvider raises possibility of creating two containers with colliding IDs\n* [EOS-4000] - Spurious errors of fusex-benchmark test 13\n\nTask\n-----\n\n* [EOS-3819] - Create automatically the missing directories when recovering files\n\nImprovement\n------------\n\n* [EOS-3370] - RFE: \"eos file check\" , \"eos file info\" should show 'user.eos.filecxerror' status for full-replica\n* [EOS-3967] - Extend redirection URL length accepted by the MGM\n\n\n``v4.6.8 Citrine``\n=======================\n\n2020-01-22\n\nBug\n---\n\n* FUSEX: fix writer starvation triggered by EDQUOT errors\n* [EOS-3872] - FST should delete file on WCLOSE when archive request cannot not be queued\n* [EOS-3873] - Coredump in jerasure_matrix_to_bitmatrix\n* [EOS-3885] - Add \"tape enabled\" configuration attribute to /etc/xrd.cf.mgm\n* [EOS-3915] - FUSEX uses std::stoll instead of std::stoull to parse inodes, breaking new inode encoding scheme\n\nImprovement\n-----------\n\n* FUSEX: support oauth token files - see OS-9604\n* FUSEX: allow to track write buffers using 'eos fusex evict UUID sendlog'\n* FUSEX: add CERN automount script/configs and update SELINUX policies accordingly supporting SquashFS mounting\n* FST: support ISA-L accelerated adler/crc32c checksum\n* FST: add generic eos-checksum command\n* FST: support xxhash64,crc64 and sha256 as checksums\n* ALL: Add basic support for Macaroons and SciTokens\n\n\n``v4.6.7 Citrine``\n=======================\n\n2019-12-16\n\nBug\n---\n\n* [EOS-3854] - Fixed SELinux policy regression bug which installed wrong file on SLC6\n\nImprovement\n-----------\n\n* [EOS-3886] - Enrich eosreport in the context of TPC\n\n``v4.6.6 Citrine``\n=======================\n\n2019-12-09\n\nBug\n---\n\n* FUSEX: avoid starvation due to no quota error during open in flush-nolock\n* APMON: bump to latest version\n\nImprovement\n-----------\n\n* [EOS-3879] - Adding a field that reports free writable bytes\n* [EOS-3882] - eos report is not reporting deletion timestamp\n* CONSOLE: Suppress routing information for 'quota ls -m' requests\n\n``v4.6.5 Citrine``\n=======================\n\n2019-12-05\n\nBug\n---\n\n* [EOS-3611] - MGM unresponsive, does not appear to recover on its own\n* [EOS-3715] - fst offline: Publisher cycle exceeded\n* [EOS-3827] - MGM Upgrade: After restarts prevent storage node heartbeats to increase\n* [EOS-3858] - ARCHIVE: Broken due to utimes silent error\n* [EOS-3864] - unable to boot filesystem after eos fs add\n* MGM: Remove sys.cta.objectstore.id xattr on successful retrieve\n\nImprovement\n------------\n\n* [EOS-3860] - Allow lock-free iteration over long directory listings\n* [EOS-3862] - eos client: hardcode RPM dependency on 'zeromq'\n* [EOS-3875] - Drop use of std::ptr_fun, std::not1\n* [EOS-3880] - RaftReplicator pipelines way too many pending batches inside QClient\n\n\n``v4.6.4 Citrine``\n=======================\n\n2019-12-03\n\nBug\n---\n\n* [EOS-3854] MISC: Version SELinux policy files for targeted platforms (SLC6 and CC7)\n\n\n``v4.6.3 Citrine``\n=======================\n\n2019-11-20\n\nBug\n---\n\n* [EOS-3717] FUSEX: fix lru_xyz SEGV in eosxd\n* [EOS-3853] NS: more options to filter with inspect command\n* FUSEX: fix WR buffer exhaustion triggered by out-of-quota writes\n\nNew Feature\n-----------\n\n* allow IPC connections via ZMQ to bypass xrd-threadpool for admin commands - usage 'eos ipc:// ...'\n* make the maximum number of listable entries by eosxd configurable: EOS_MGM_FUSEX_MAX_CHILDREN=32768\n\n\n``v4.6.2 Citrine``\n=======================\n\n2019-11-18\n\nBug\n---\n\n* fix eosxd messaging for renames, commits, versioning\n* avoid spurious entries in quota map\n* [EOS-3692] print critical messages when FUSEx throws runtime_errors\n* [EOS-3793] prefix recycle restore keys with fxid: and pxid: to avoid ambiguities\n* [EOS-3798] suppress atomic/versioning for 'verify --commit' workflows\n* [EOS-3808] broadcast externally versioned files into fusex network\n* [EOS-3822] avoid SEGV in FUSEx recovery\n* [EOS-3823] avoid infinite loop unlinkAllLoctions\n* [EOS-3829] parsing problem\n* [EOS-3833] avoid SEGV when logfile is not opened\n* [EOS-3834] console char replacement\n* [EOS-3839] avoid deadlock in lock order violation\n* [EOS-3845] create barrier in FST creation to avoid race condition under file creation from two clients\n* [EOS-3848] store exception in future\n* [EOS-3850] avoid SEGV in FUSEx deletion of non-existent objects\n\nNew Feature\n-----------\n\n* cta add-ons for multi-space usage\n* make fsck thread-pool configurable\n* json response format for xrdfs query prepare\n* stall logic for prepares\n* more options in eos-ns-inspect\n* decrease noserver FUSEx timeouts to 15/2 minutes (r/w)\n\n\n``v4.6.1 Citrine``\n=======================\n\n2019-10-31\n\nBug\n---\n\n* Fix wrong linking in the eos-client package\n* General restructuring of the link dependencies\n\n\n``v4.6.0 Citrine``\n=======================\n\n2019-10-30\n\nBug\n----\n\n* [EOS-2990] - FSCK on QuarkDB causes higher latency\n* [EOS-3437] - FST crash around eos::common::DbMapTypes::Tlogentry::~Tlogentry()\n* [EOS-3469] - no replica information on file check but the physical file is there\n* [EOS-3470] - eos verify: unable to verify ... no local MD stored\n* [EOS-3497] - Avoid ghost entries to fail the draining of a disk\n* [EOS-3689] - MGM crashed in XrdCl::Utils::CheckTPCLite()\n* [EOS-3726] - FST crash in eos::fst::Adler::Add (negative \"length\")\n* [EOS-3736] - FST registration causing locking issue\n* [EOS-3743] - 'eos fs rm' triggers the following error: \"cannot set net parameters on filesystem\"\n* [EOS-3751] - weird behavior of the geoscheduler when some FSTs changed the geotag\n* [EOS-3783] - Miniconda2-latest-Linux-x86_64.sh - no exec bit for 'python' from archive\n* [EOS-3790] - MGM gets stuck when using local QuarkDB MD lock\n* [EOS-3791] - Transfers timeout on EOS\\CERNBox home folders A G J K W\n* [EOS-3792] - eos quota not redirecting to proper home\n* [EOS-3799] - XrdMgmOfs::Emsg() calls strerror() which is NOT thread safe\n* [EOS-3802] - eos acl not setting acl's\n* [EOS-3803] - FUSEX client says \"Directory not empty\" on removal (bad caching?)\n* [EOS-3805] - EOS client links against system XRootD instead of eos-xrootd\n* [EOS-3806] - eoscp won't copy the file if the 'extra' stripes are missing\n\nTask\n----\n\n* [EOS-3583] - Repair logs (useful metadata)\n* [EOS-3591] - 'file info' resolves symlinks and displays info of the referenced file\n* [EOS-3710] - TPC from castor/ceph to EOS not working\n\nImprovement\n-----------\n\n* [EOS-3371] - RFE: update \"user.eos.filecxerror\" on FST checksum verification failures\n* [EOS-3750] - Change error message for adjustreplica\n\n\n``v4.5.13 Citrine``\n=======================\n\n2019-11-15\n\nBug\n----\n\n* [EOS-3839] MGM: Fix lock inversion leading to deadlock when calling getmdlocation\n* [EOS-3729] FUSEX: fix bug in wait_flush method leading to a mix-up of rename/unlink records\n* MGM/FUSEX: Fix faulty assumption that getFile would raise an exception (had been\n  changed when Qdb was introduced) - fixes spurious EIO errors and 'Attempt to add\n  an existing file' messages.\n\n\n``v4.5.12 Citrine``\n=======================\n\n2019-10-28\n==========\n\n* [EOS-3792] - eos quota not redirecting to proper home\n\nImprovement\n-----------\n\n* [EOS-3800] - Routing mechanism of proto commands\n\n\n``v4.5.11 Citrine``\n=======================\n\n2019-10-22\n\nBug\n----\n\n* MGM: fix rare lockups observed due to wrong expectation of an exception thrown\n\n\n``v4.5.10 Citrine``\n=======================\n\n2019-10-16\n\nBug\n----\n\n* [EOS-3736] - FST registration causing locking issue\n* [EOS-3737] - Possible eos file verify commands causing deadlock while restarting mgm\n* [EOS-3710] - TPC from castor/ceph to EOS not working\n* [EOS-3774] - FUSEX: fix recovery problem when files are truncated to 0 size\n* FUSEX: fix rc=EPERM for setxattr if not called by uid=0\n* FUSEX: fix possible out-of-memory scenario when applications keep writing on fatal\n  error conditions like out-of-quota\n\n\n``v4.5.9 Citrine``\n=======================\n\n2019-09-11\n\nBug\n----\n\n* MGM: Update rights 'u' are implicit in 'w'\n* EOS-3721: Slave MGMs in old-implementation master-slave should refuse to boot on QDB-namespaces\n\n\n``v4.5.8 Citrine``\n=======================\n\n2019-09-10\n\nBug\n----\n\n* FST: Fix FST metadata synchronization with the MGM info when delay is not respected\n\nImprovement\n-----------\n\n* FUSEX: Enable safe mode by default - when a file is created the client always gets\n  feedback if the FST open didn't work.\n\n\n``v4.5.7 Citrine``\n=======================\n\n2019-09-09\n\nBug\n----\n\n* Fix bug in the MgmSync process which could crash the FST\n* [EOS-3633] - Many new commands are not compatible with old server version\n* [EOS-3696] - shell: \"cd ../../\" does nothing?\n* [EOS-3705] - Error when updating eos-archive\n* [EOS-3703] - FST not starting if mountpoint not present\n* [EOS-3684] - eosxd crash in debug() in EosFuse::readdir()\n* [EOS-3608] - Wrong help for space policy and no error message\n\nImprovement\n------------\n\n* [EOS-2725] - Missing usage example for some space parameters\n* [EOS-3694] - Add eos-fusex-tests to the pipeline\n* [EOS-3706] - Add 1m,1w,daily timebins to versioning similar to DFS\n* GRPC: Add version command implementation and other ns related operations\n\n\n``v4.5.6 Citrine``\n=======================\n\n2019-08-26\n\nBug\n----\n\n* [EOS-3315] - eos file adjustreplica selects bad replica for replication\n* [EOS-3572] - Crash while reloading the config in eoslhcb\n* [EOS-3575] - EOSCMS - killed by SIGSEGV (around eos::mgm::GeoTreeEngine::applyBranchDisablings)\n* [EOS-3624] - eosxd SEGV eraseTS\n* [EOS-3669] - Wrong Routing when target path ends as <path>/.\n* [EOS-3678] - space define command doesn't set groupmod\n* [EOS-3680] - Space set subcommand affects all groups and nodes\n* [EOS-3687] - getQuotaNode throws an exception when called on a detached container, instead of returning nullptr\n* [EOS-3700] - eosxd SEGV apply\n* [EOS-3701] - eosxd SEGV lookup\n* [EOS-3704] - rename/stat/open handling of trailing '/'\n\nNew Feature\n------------\n\n* [EOS-3682] - gRPC container insert does not inherit extended attributes\n\nImprovement\n------------\n\n* [EOS-3474] - GroupBalancer logging\n\n\n``v4.5.5 Citrine``\n=======================\n\n2019-08-07\n\nBug\n---\n\n* [EOS-3536] - fix hard-link cleanup problems seen with 'rm -rf' on git repositories\n* [EOS-3644] - adjust eosxd cache path filename hashing for physical inodes\n* [EOS-3643] - avoid ghost entries when files are overwritten and support reycle bin for those\n\n\nImprovements\n------------\n\n* [EOS-3638] - introduce file info detached field\n* speed-up shutdown for drain jobs\n* implement ns-reserve-id command\n* don't print byte-range locks per client ( get it with '-k' option )\n* filesystem class refactoring\n* clean-up empty eosxd cache directories\n* support proc results larger than 2G\n* timeout eosxd connections after 24h\n\n\n``v4.5.4 Citrine``\n=======================\n\n2019-08-01\n\nBug\n---\n\n* [EOS-3622] - eoscp is not propagating the error code.\n* [EOS-3629] - Provide fallback for the quota command to old implementation\n* [EOS-3631] - port flag is ignored on eosfstregister script\n* [EOS-3632] - mv on FUSEX deterministically loose data\n* [EOS-3633] - Many new commands are not compatible with old server version\n\nQuestion\n---------\n\n* [EOS-3626] - eos mgm cannot contact to external eos instance via eos route\n\n\n``v4.5.3 Citrine``\n=======================\n\n2019-07-25\n\nBug\n---\n\n* [EOS-455] - RFE: drop either fid: or fxid:, use the other consistently\n* [EOS-3577] - Crash in ReplicationTracker\n* [EOS-3579] - io stat shows negative values (overflow?)\n* [EOS-3585] - eosxd crash below cap::capflush() / metad::cleanup()\n* [EOS-3604] - Apply path mapping for eos rm command\n* [EOS-3609] - Wrong json format in file info when & are in pathnames\n* Fix bug related to interference between logrotation and QdbMaster setup for\n  high-availability observed at JRC.\n\nImprovements\n------------\n\n* Extend ns cache drop command to drop individual entries\n* Move the following commands to the protobuf implementation: access, quota,\n  config, node and space.\n* [EOS-3602] - Drop automatic conversion attempt from default output to JSON for\n  protobuf commands with JSON flag on. Each proto command will be\n  responsible of providing valid JSON output.\n* [EOS-3606] - Add birth time to a file's metadata when it is created/born\n\n\n``v4.5.2 Citrine``\n=======================\n\n2019-06-27\n\nBug\n---\n\n* if eosxd is compiled without ROCKSDB support, it should not touch mdcachedir e.g. it has to stay empty - fixes EOS-3558\n* require eos-rocksdb on SLC6 and EL7 to have support for swapping inodes\n\n``v4.5.1 Citrine``\n=======================\n\n2019-06-25\n\nBug\n---\n\n* [ EOS-3546 ] Apply remote quota updates if q-node has no file open\n\nNew Feature\n-----------\n\n* [ EOS-3548 ] Replication Tracker class (see docs/configuration/tracker)\n\n``v4.5.0 Citrine``\n=======================\n\n2019-06-21\n\nBug\n---\n\n* [ EOS-3495 ] Handle out-of-quota open correctly in eosxd\n* [ EOS-1755 ] Don't irritate du with . entry size\n* [ EOS-3536 ] Fix hardlink deletion logic to avoid hidden entries after all references have been removed\n* [EOS-3279] - eos fs dumpmd RC wrong\n* [EOS-3396] - File with two 'bad' replicas: one has size mismatch, the other xsum mismatch\n* [EOS-3499] - eos-ns-inspect: Include again the libprotobuf dependency\n* [EOS-3522] - 'eos config dump --vid' prints dummy \"mgm.vid.key=<key>\", cannot  \"eos vid rm'\n* [EOS-3526] - eosxd crash in EosFuse::readlink(), NULL 'md' pointer\n* [EOS-3533] - eos find does not work with --fid and -0\n\nNew Feature\n-----------\n\n* [EOS-3532] - Allow default placement policies per space\n\nImprovement\n-----------\n\n* Provide optional GRPC service in MGM\n* Documentation improvements\n* Swap-in-out eosxd inodes with lru table into rocksdb DB\n* Block only running file drains from parallel draining\n* CTA GC monitoring in 'eos ns'\n* [ EOS-3514 ] Implement orphan detection in eos-ns-inspec\n* [ EOS-3490 ] Support printing mctime, ctime in eos-ns-inspec\n* [EOS-3409] - 'bind mount' FUSEX, no credentials: \"No such file or directory\"\n  instead of \"Permission denied\"\n* [EOS-3519] - Add the possibility to do attr ls with the fid/pid\n* [EOS-3520] - add pid to the json output of file info\n* [EOS-2020] - Use Table Formatter for geosched show tree and snapshot commands output\n* [EOS-3513] - Provide an exception when eos dumpmd <fsid> --path is not really empty\n* [EOS-3527] - FSCK detection tool: Classify size errors for not orphan files\n* [EOS-3531] - FSCK detection: Ignore size 0 files in the namespace in replica error detection\n* Move the \"group\" command to the Protobuf implementation\n* Move the \"io\" command to the Protobuf implementation\n* Move the \"debug\" command to the Protobuf implementation\n\n\n``v4.4.47 Citrine``\n=======================\n\n2019-05-17\n\nBug\n---\n\n* freeze client RPATH to XRootD location used during build\n\nImprovement\n-----------\n\n* CTA module v 0.41\n* Extended 'prepare' for XRoot 4.4.10 (abort etc.)\n* Report detached files in 'eos-fsck-fs'\n* [ EOS-3483 ] - add container id in output of stripediff option\n* [ EOS-3484 ] - add location to output of stripediff option\n* [ EOS-3532 ] - introduce space default placement policies ( obsoletes per directory extended attributes for default placement policy)\n* use eos-protobuf3 eos-xrootd only on EL7 for tags like x.y.z-0, otherwise only eos-protouf3 on EL7 builds\n\n\n``v4.4.46 Citrine``\n=======================\n\n2019-05-15\n\nBug\n---\n\n* Fix FST conversion from NS proto to Fmd\n* Fix RPATH configuration to force linker locations\n\nImprovement\n-----------\n* Implement 'eos fsck search' to forward FSCK from NS to FSTs\n* Expose 'eos resync' and 'eos verify -resync' to force FMD resynchronization on FSTs\n* Refactor ScanDir code\n\n``v4.4.45 Citrine``\n=======================\n\n2019-05-14\n\n\nBug\n---\n\n* Introduce obsoletes statement in spec file for eos-protobuf3/eos-xrootd\n\nImprovement\n-----------\n\nFST: Refactor the ScanDir code and add simple unit tests\nFST: Encapsulate the rate limiting code into its own method\nFST: Start publishing individual fs stats\nNS: Add etag, flags to eos-ns-inspect output\n\n``v4.4.44 Citrine``\n=======================\n\n2019-05-08\n\nBug\n---\n\n* FST: fix dataloss bug introduced in 4.4.35 when an asynchronous replication fails (adjustreplica cleaning up also the source)\n\n\n``v4.4.43 Citrine``\n=======================\n\n2019-05-08\n\nImprovements\n------------\n* FUSEX: add compatibility mode for older server which cannot return getChecksum by file-id\n* CI: build with ubuntu bionic\n* NS: Add mtime, ctime, unlinked locations, and link name to eos-ns-inspect printing\n* CTA: configuration parameters for tapeaware garbage collector\n\n``v4.4.42 Citrine``\n=======================\n\n2019-05-07\n\nImprovements\n------------\n\n* FUSEX: lower default IO buffer size to 128M\n* MGM: remove unnecessary plug-incall\n* NS: implement subcmd to change fid attributes\n\n``v4.4.41 Citrine``\n=======================\n\n2019-05-07\n\n\nBug\n---\n* [EOS-3462] - FUSEX: suppress concurrent read errors for unrecoverable errors\n* MGM: Fix monitoring output for eos fusex ls -m\n\nImprovements\n------------\n\n* NS: Implement inspect subcommand to run through all file/directory metadata\n* [EOS-3463] - implement stripediff functionality in inspect tool\n* MGM: optimize quota accounting to correct for the given default layout when queried for quota via 'xrdfs ... space query /'\n* FUSEX: if a logfile exceeds 4G, we shrink it back to 2G\n* CTA: various cta related fixes (see commits)\n\n``v4.4.40 Citrine``\n=======================\n\n2019-05-03\n\n\nBug\n---\n\n* FUSEX: avoid hanging call-back threads whnen a files is not attached and immedeatly unlinke\n* FUSE:  allow unauthenticated stats on the mount point directory ( for autofs )\n* FUSEX: silence mdstrackfree messages to debug mode\n* [EOS-3446] - CONSOLE: Return errno if set otherwise the XRootD client shell code approximation\n* FST: Don't report RAIN files as d_mem_sz_diff in the fsck output\n* FUSEX: allow setting 'eos.*' attributes by silently ignoring them\n* NS: add detection for container names '.' and '..'\n\n\nImprovements\n-------------\n\n* NS: Report any errors found by ContainerScanner or FileScanner in check-naming-conflicts\n* Adding ' eos-leveldb-inspect' tool\n* MGM: Refactor Fsck\n\n\n``v4.4.39 Citrine``\n=======================\n\n2019-04-30\n\n\nBug\n---\n\n* [EOS-3313] - ns master other output looks incorrect\n* [EOS-3378] - double draining into same destination gives corrupted or empty replica\n* [EOS-3407] - Schedule2Balance reports long lasting read locks\n* [EOS-3414] - EOS config file could not be loaded\n* [EOS-3439] - rw filesystems shown with 'fs ls -d'\n* Fix for draining of RAIN file when parity information was not stored back on disk.\n* Enforce checksum verification for all replication operations.\n\nDocumentation\n-------------\n\n* Add documentation for EOS on Kubernetes deployment\n\n\n``v4.4.38 Citrine``\n=======================\n\n2019-04-24\n\nBug\n----\n\n* Fix LRU which was looping and taking the FsView lock when disabled\n* [EOS-3427] - getUriFut can overwhelm the folly executor pool, causing slowness and potential deadlocks\n* [EOS-3432] - MGM crash in eos::NamespaceExplorer::buildDfsPath\n\nImprovement\n------------\n\n* [EOS-3431] - MGM: make \"func=performCycleQDB\" log (much) less\n\n\n``v4.4.37 Citrine``\n=======================\n\n2019-04-16\n\nBug\n---\n\n* Fix deadlock in the folly executor introduced when using a single folly\n  executor for the entire namespace.\n\nImprovements\n-------------\n\n* Add env variable to control the master-slave transition lease validity.\n  EOS_QDB_MASTER_INIT_LEASE_MS\n\n\n``v4.4.36 Citrine``\n=======================\n\n2019-04-16\n\n\nBug\n----\n\n* Fix deadlock in the Iostat class introduced in the previous release.\n* [EOS-2477] - MGM lockedup after enabling LRU - Citrine with new namespace\n* [EOS-3337] - MGM crash around XrdMgmOfs::OrderlyShutdown() on \"orderly\" shutdown\n* [EOS-3405] - MGM switches drain filesystems to empty\n\nImprovement\n------------\n\n* [EOS-3356] - RFE: shut up the 'verbose' recursive \"chown\" under /var/eos\n* [EOS-3389] - review \"error: no drain started for the given fs\": do not trigger this or do not log\n* [EOS-3402] - \"eos node ls\": double 'status' column, white-on-white text\n* [EOS-3412] - silence \"failed to stat recycle path\" error on rename+remove?\n* [EOS-3421] - Flood of \"SOME Listener new notification\" messages in the log since 77cfb51213\n\n\n``v4.4.35 Citrine``\n=======================\n\n2019-04-11\n\nBug\n---\n* [EOS-3400] - don't commit any replica with write errors\n* [EOS-3399] - never drop all replicas in reconstruction or injectino failure scenarios\n* [EOS-3398] +\n* [EOS-3237] - never wipe local MD in eosxd with LEASE messages\n* [EOS-3410] - catch JSON exception produced by empty strings\n* [EOS-3408] - fixs prefetch logic in fileReadAsync(XrdIo)\n* fix fading heart-beat problem: re-enable a queue in MQ if a client has cleared backlog\n\nImprovement\n-----------\n\n* add 'eos-fsck-fs' command to run standalone fsck on FSTs\n* add read-ahead test for XrdIo\n* [EOS-3391] - make geotag propagation less verbose\n* [EOS-3406] - move some log messages from error to debug\n* [EOS-3390] - suppress UDP target missing message\n* [EOS-3401] - if scanner is disabled don't even scan files a first time\n* avoid FuseXCasts when _rem is called in FuseServer with recycle bin enabled\n\nRefactoring\n-----------\n\n* fix some more fid/fxid log messages to use the hex format\n* drop use of BackendClient in MetadataProvider\n\n``v4.4.34 Citrine``\n=======================\n\n2019-04-05\n\nBug\n---\n\n* [EOS-3394] - automount might fail due to race condition in ShellExecutor/ShellCmd test\n\nImprovement\n-----------\n\n* RAIN placement uses round-robin algorithm to define the entry server\n\n``v4.4.33 Citrine``\n=======================\n\n2019-04-04\n\nBug\n----\n\n* Disable prefetching for TPC transfers which might corrupt the data.\n* Put the mgm.checksum opaque info for drain jobs in the unencrypted part of\n  the URL otherwise the checksum check is not enforced.\n* [EOS-3367] - \"eos file verify --checksum\" does not update FMD checksum or ext.attribute\n* [EOS-3372] - MGM \"autorepair\" for corrupted replicas is not working\n* [EOS-3382] - Network monitoring always shows 0 on newer kernel versions\n\nImprovement\n------------\n\n* [EOS-3359] - Graceful cancellation of drain jobs\n* [EOS-3375] - Use eos/conversion as io stat tag\n\nRefactoring\n-----------\n\n* Introduce NamespaceGroup\n\n``v4.4.32 Citrine``\n=======================\n\n2019-03-26\n\nBug\n---\n\n* [EOS-3347] - Fix slave follower problem with new mutex implementation due to unlock_shared vs unlock calls\n* [EOS-3348] - openSize used in XrdFstOfsFile::open\n* [EOS-3350] - Fusex lists duplicate items\n* [EOS-3352] - RAIN upload is not failed if a stripe cannot be opened for creation\n* [EOS-3354] - MGM deadlock while loading the configuration\n\n\nRefactoring\n-----------\n\n* Rename VirtualIdentity_t to Virtualidentity\n* Replace Fs2UuidMap maps with FilesystemMapper, drop unused 'nextfsid' global configuration\n\nImprovements\n------------\n\n* Allow to disable partition scrubbing by creating /.eosscrub on the FST partition\n* Add warning messages containing timing information about delayed heartbeat messaging\n\n\n``v4.4.31 Citrine``\n=======================\n\n2019-03-21\n\nBug\n---\n\n* HTTP: Extend lifetime of variable pointed to from the XrdSecEntity object\n* CONSOLE: Refactor the RecycleHelper for easier testing. EOS-3345\n* MGM: Display real geotag field in FileInfo JSON format. Additionally, display forcegeotag field when available\n* FST: Fix default geotag to be less than 8 chars\n* FST: Add a check for Geotag length limit. Fixes EOS-3208\n* MGM: Fail file placement if a forced scheduling group is provided and the\n\nRefactoring\n-----------\n\n* MGM: Implement method to allocate new fsid based on uuid in FilesystemUuidMapper\n* MISC: Remove any kinetic reference\n* CONSOLE\n* ALL: enum class for filesystem status - strongly typed\n\nImprovements\n------------\n\n* MGM: add BackUpExists flag for files on CTA\n* MGM: Add estimate for drain TPC copy timeout based on the size of the file and a\n* MGM: Check geotag limit also on fs config forcegeotag command\n* MISC: Basic bash completion script. Fixes EOS-3252\n* MGM: Add tracking for in-flight requests in the MGM code for cleaner master-slave\n* ARCHIVE: Increase the TPC transfer timeout to 1 hour\n\n\n``v4.4.30 Citrine``\n=======================\n\n2019-03-18\n\nBug\n---\n\n* FUSEX/MGM: allow all combinations of client/server versions by considering the\n  config entry if 'mdquery' is supported or not\n* FUSEX: fix return code of eos-ioverify in case of any IO error\n\nImprovements\n------------\n\n*  ALL: Drop \"drainstatus\" from the persistent config and use \"stat.drain\" to\n   hold the current status of the draining for a filesystem. This reduces also\n   the number of configuration save operations triggered by the draining and\n   we rely only on \"configstatus\" to decide whether or not draining should\n   be enabled. Note: all \"stat.*\" are filtered out from the persistent config.\n\n\n``v4.4.29 Citrine``\n=======================\n\n2019-03-14\n\nBug\n----\n* Release built on top of XRootD 4.8.*\n\n\n``v4.4.28 Citrine``\n=======================\n\n2019-03-12\n\nBug\n----\n\n* Fix bug in the namespace conversion tool when computing the quota nodes\n* Fix bug in the QuotaNodeCode copy constructor which was preventing a quota\n  node recomputation\n* [EOS-3316] - Namespace conversion tool suffers from high lock contention on releases 4.4.26, 4.4.27\n\nImprovements\n------------\n\n* Refactor the FuseServer code into various functional pieces\n* Use std::mutex for conversion tool rather than RWMutex which hinders performance\n\n\n``v4.4.27 Citrine``\n=======================\n\n2019-03-07\n\nBug\n----\n\n* [EOS-3200] Fix crash in zmq::context_t constructor due to PGM_TIMER env variable\n* [EOS-3308] Drain status shown but machine is in configstatus rw\n* Put back fflush in Logging class to check\n\nImprovements\n------------\n\n* MGM/CONSOLE/DOC: extend LRU engine to specify policies by age and size limitations\n  like 'older than a week and larger then 50G' or 'older than a week and smaller than 1k'\n* NS: Add sharding to MetadataProvider to ease lock contention\n\n\n``v4.4.26 Citrine``\n=======================\n\n2019-03-04\n\nBug\n----\n\n* [EOS-3246] - IPv6 addresses parsing broken\n* [EOS-3256] - Add XRootD connection pool to the MGM\n* [EOS-3257] - interactive 'eos' CLI aborts around eos::common::SymKeyStore::~SymKeyStore()\n* [EOS-3261] - EOSBACKUP locked up\n* [EOS-3263] - eosxd does not support seekdir/telldir\n* [EOS-3265] - Node config values never removed\n* [EOS-3266] - First MGM boot on clean namespace does not setup \"/\", \"/eos\", etc if EOS_USE_QDB_MASTER is set\n* [EOS-3267] - Dump files on CERN FSTs goes into a file named /var/eos/mdso.fst.dump.lxfsre10b04.cern.ch:109\n* [EOS-3276] - Inconsistent behavior (and doc) for \"eos fs config\" and \"eos node config\"\n* [EOS-3296] - eoscp crash while copying 'opaque_info' data\n* [EOS-3299] - Workaround for XRootD TPC bug in Converter which leads to data loss.\n               This is not a definitive fix.\n* [EOS-3280] - Logrotate rpm dependency missing for eos-server package\n* [EOS-3303] - Implement InheritChildren method for the QuarkContainerMD which otherwise\n               crashes the MGM for commands like \"eos --json fileinfo /path/to/dir/\".\n\nImprovement\n------------\n\n* [EOS-3249] - Add \"flag\" file for master status\n* [EOS-3251] - Expose Central drain thread pool status in monitoring format\n* [EOS-3269] - path display in `eos file check` output\n* [EOS-3295] - Allow MGMs to retrieve stacktraces and log files from eosxd at runtime\n\nNote\n-----\n\nStarting with this version one can control the xrootd pool of physical connections\nby using the following two env variables:\nEOS_XRD_USE_CONNECTION_POOL - enable the xrootd connection pool\nEOS_XRD_CONNECTION_POOL_SIZE - max number of unique physical connection\ntowards a particular host.\nThis can be use in the MGM daemon to control connection pool for TPC transfers\nused in the Converter and the Central Draining, but also on the FST side for\nFST to FST transfers.\n\nThe following two env variables that proided similar functionality only on the\nFST side are now obsolete:\nEOS_FST_XRDIO_USE_CONNECTION_POOL\nEOS_FST_XRDIO_CONNECTION_POOL_SIZE\n\n\n``v4.4.25 Citrine``\n=======================\n\n2019-02-12\n\n* [EOS-3152] - FUSEX: crash below data::datax::peek_pread\n\n\n``v4.4.24 Citrine``\n=======================\n\n2019-02-11\n\nBug\n----\n\n* [EOS-3240] - EOSBACKUP crash related somehow to ThreadPool\n* FUSEX: fix logical error in read overlay logic - fixes EOS-3253\n* FUSEX: fix datamap entry leak whenever a file is truncated by name and not via file descriptor\n* FUSEX: fix ugly kernel deadlock appearing in consumer-producer workloads\n\nImprovement\n------------\n\n* FUSEX: reduce the default wr/ra buffer to 256 MB if ram>=2G otherwise ram/8\n\n\n``v4.4.23 Citrine``\n=======================\n\n2019-01-31\n\nBug\n----\n\n* [EOS-3231] - Update is not anymore implicit in ACL:w permissions - non-fuse fix\n* FUSE: Stop returning reference to temporary\n\nImprovement\n-----------\n\n* FUSEX: When the unmount handler catches a signal, re-throw in the same thread\n  so that abort handler print a meaningful trace\n\n\n``v4.4.22 Citrine``\n=======================\n\n2019-01-24\n\nBug\n----\n\n* [EOS-3231] - Update is not anymore implicit in ACL:w permissions\n* [EOS-3215] - drainstatus not reset when disk put back to rw\n* [EOS-3227] - Missing eosarch python module\n* [EOS-3230] - CmdHelper does not always print error stream as provided by the MGM\n\n\n``v4.4.21 Citrine``\n=======================\n\n2019-01-21\n\nBug\n----\n\n* [EOS-3203] - recycle config --size\n* [EOS-3204] - CLI: \"eos acl\" is broken\n* [EOS-3205] - Problem with the draining of zero size file\n* [EOS-3209] - central draining fails on paths containing question marks ('?')\n\n\nImprovement\n------------\n\n* [EOS-2678] - converter/groupbalancer \"recycles\" files found in recycle-enabled directories\n\n\n``v4.4.20 Citrine``\n=======================\n\n2019-01-17\n\nBug\n----\n\n* [EOS-3202] - Instance degradation due to client concurrancy and quota refresh\n* MGM: Improve drain source selection by giving priority to replicas of files on other\n  file systems rather than the one currently being drained.\n* [EOS-3198] - Json output from the httpd interface escapes redundant double\n  quotes on values of attr queries\n* [EOS-1733] - eosd segfault in unlink around \"filesystem::is_toplevel()\"\n\nImprovement\n------------\n\n* [EOS-3197] - Improve directory rename/move inside the same quota node\n* MGM: Add command to control the number of threads used in the central draining:\n  eos ns max_drain_thread <num>\n* MGM: Add support for ACLs for single files\n\n\n``v4.4.19 Citrine``\n=======================\n\n2018-12-18\n\nBug\n----\n\n* FUSEX: fix race/dead-lock condition when create and delete are racing\n\nImprovements\n------------\n\n* FUSEX: Put 256k as file start cache size\n* FUSEX: Add ignore-containerization flag\n* MGM: Refactor and add unit tests to the Access method\n* UNIT_TEST: Add quarkdb unit tests to the Gitlab pipeline\n* MGM/MQ: Various improvements and fixes to the QuarkDB master-slave setup\n* MGM: Various improvements and refactoring of the WFE functionality related\n       to CTA.\n\n\n``v4.4.18 Citrine``\n=======================\n\n2018-12-07\n\nBug\n----\n\n* [EOS-2636] - VERY high negative cache value = 1987040\n* [EOS-2969] - central drain/config: \"eos fs config XYZ configstatus=drain\" hangs\n* [EOS-2974] - EOS new NS (EOSPPS) sudden memory increase → OOM\n* [EOS-3129] - Error following symlink while \"eos cp\"\n* [EOS-3162] - File reported successfully written despites IO errors\n* [EOS-3163] - FuseServer confuses file ID with inode when prefetching under lock\n* [EOS-3168] - \"eos recycle config --remove-bin\" not working anymore\n* [EOS-3170] - Data race in FuseServer when handling client statistics\n\nImprovement\n-----------\n\n* [EOS-2923] - Improve and rationalize Egroup class\n* [EOS-2968] - central drain/config: skip/ignore attempts to set the same configstatus twice (instead of hanging)\n* [EOS-3037] - RFE: draining - randomize order for to-be-drained files on a filesystem\n* [EOS-3138] - RPM packaging: depend on the EPEL repo definitions\n* [EOS-3153] - Reduce MGM shutdown time\n* [EOS-3155] - Write mtime multi-client propagation testsuite\n* [EOS-3166] - Allow chown always if the owner does not change\n\n\n``v4.4.17 Citrine``\n=======================\n\n2018-11-29\n\nBug\n---\n\n* [EOS-3151] - fix OpenAsync in async flush thread in case of recovery\n\nImprovement\n-----------\n\n* Support REFRESH callback to force an update individual metadata records, not only bulk by directory\n\n\n``v4.4.16 Citrine``\n=======================\n\n2018-11-28\n\nBug\n---\n\n* [EOS-3137] - Add additional permission check when following a symbolic link in XrdOfsFile::open\n* [EOS-3139] - eos chown -r uid:gid follows links\n* [EOS-3144] - Cannot auth with unix with fusex\n* [EOS-3145] - FUSEX: repeated WARN messages about \"doing XOFF\"\n\nImprovement\n-----------\n\n* [EOS-3050] - Add calling process ID and process name possibly to each client and server side log-entry for FUSE\n* [EOS-3096] - Show mount point in 'fusex ls'\n\n``v4.4.15 Citrine``\n=======================\n\n2018-11-27\n\nBug\n---\n\n* CONSOLE: Add fallback to old style recycle command for old servers\n* MGM: Fix possible memory leak in capability generation\n\n\n``v4.4.14 Citrine``\n=======================\n\n2018-11-20\n\nBug\n---\n\n* [EOS-3089] - Inflight-buffer exceeds maximum number of buffers in flight\n* [EOS-3110] - Looping Open in EOSXD\n* [EOS-3114] - corrupted file cache on eosxd in SWAN\n* [EOS-3116] - FUSEX-4.4.13 - 'zlib' selftest failure on SLC6\n* [EOS-3117] - FUSEX logs \"missing quota node for pino=\" (and \"high rate error messages suppressed\")\n* [EOS-3121] - MQ: Heap-use-after-free on XrdMqOfsFile::close\n* [EOS-3120] - Add eosxd support for persistent kerberos keyrings\n* [EOS-3123] - Parsing issue with \"eos recycle -m\"\n* [EOS-3125] - git clone fails with \"fatal: remote-curl: fetch attempted without a local repo\"\n* [EOS-3134] - fix journalcache memory leak\n\nNew Feature\n-----------\n\n* [EOS-3126] - FUSE: ability to tag traffic with custom tag\n* [EOS-3128] - eosxd usability\n\nImprovement\n-----------\n\n* [EOS-3108] - Move recycle command to protobuf implementation - keep server support for 'old' clients\n* [eos-3113] - Don't stall mount when no read-ahead buffer is available\n* [EOS-3119] - Make eosxd auth subsystem more debuggable for users\n* [EOS-3120] - Add eosxd support for persistent kerberos keyrings\n* [EOS-3122] - Add XrdCl fuzzing\n* improve shutdown behaviour of server\n* move all pthread to std::thread\n* FST no longer sends proto events for sync::closew if file comes from a tape server retrieve operation\n\n\n``v4.4.13 Citrine``\n=======================\n\n2018-11-19\n\nBug\n---\n\n* [EOS-3101] - fix EEXIST logic in FuseServer open to race condition and remove double parent lookup\n\nImprovements\n------------\n\n* NS: Add metadata-entries-in-flight to NS cache information\n\n\n``v4.4.12 Citrine``\n=======================\n\n2018-11-16\n\nBug\n---\n\n* [EOS-2172] - eosxd aborted, apparently due to diskcache missing xattr\n* [EOS-2865] - Lost some mount points\n* [EOS-3090] - Encoding problems in TPC/Draining\n* [EOS-3069] Use logical quota in prop find requests (displayed by CERNBOX client)\n* [EOS-3092] Don't require an sss keytable for a fuse mount if 'sss' is not configured as THE auth protocol to use\n\nImprovements\n------------\n\n* [EOS-3095] Fail all write access even from localhost in MGM while booting -\n  properly tag RO/WR access in proto buf requests\n* [EOS-3091] allow to ban eosxd clients (=> EPERM)\n* [EOS-3047] add defaulting routing to recycle command\n* Refactor fsctl includes into functions\n* enable eosxd authentication in docker container\n\nNew Feature\n-----------\n\n* [EOS-3094] - Access to eos in a container\n\n\n``v4.4.11 Citrine``\n=======================\n\n2018-11-14\n\nBug\n---\n\n* [EOS-3044] Fusex quota update blocks the namespace\n* [EOS-3065] Ubuntu/Debian packaging: \"/etc/fuse.conf.eos\" conflicts between \"eos-fuse\" and \"eos-fusex\"\n* [EOS-3079] MGM Routing Macro should stop bouncing clients to same targets if the target was already tried\n* [EOS-3068] fix to catch missing exception in find, avoid FUSE client heartbeat waiving creating DOS\n* [EOS-3054] add missing '&' separator in deletion reports\n* [EOS-3052] fix typo in report log description\n* [EOS_3048] create group readable reports directory structure\n* [EOS-3045] fix wrong heart-beat interval logic creating tight-loops and default to 0.1Hz\n* [EOS-3043] avoid creating .xsmap files\n* [EOS-3041] add timeout to query in SendMessage, add timeout to open and stat requests\n* [EOS-3033] fix wrong etag in JSON fileinfo response\n* [EOS-3029] disable backward stacktrace in eosd by default possibly creating SEGVs when a long standing mutex is discovered\n* [EOS-3025] fix checksum array reset in Commit operation\n* [EOS-2989] take fsck enable intereval into account\n* [EOS-2872] modify mtime modification in write/truncate/flush to preserve the order of operations in EOSXD\n* [EOS-2599] fix ACLs by key and fully supported trusted and single ID shared sss mounts supporting endorsement keys\n* [CTA-312]  propagate protobuf call related errors messages through back to clients\n* Don't call 'system' implying fork in FST code\n* Fix Fmd object constructor to use 64-bit file ids\n\nImprovements\n------------\n\n* [EOS-3073] auto-scale IO buffers according to available client memory\n* [EOS-3072] add number of open files to the eosxd statistics output\n* [EOS-3027] allow 'fusex evict' without calling abort handler by default e.g.\n  to force a client mount with a newer version\n* [EOS-2576] add support for clientDNs formatted according to RFC2253\n* FUSEX: Add client IO counter and rates in EOSXD stats file and 'fusex ls -l' output\n* FUSEX: Manage the negative cache actively from eosxd - saves many remote\n  lookups in case of unfound libraries in library lookup path on fuse mount\n* FUSEX: Improve tracebility in FuseServer logging to log by client credential\n  (remove the _static_ log entries)\n* Support deny ACL entries, RICHACL_DELETE from parent\n* CTA: Rename tape gc variable names\n* FST: Use RAII for XrdCl::Buffer response objects in FST code\n\n\n``v4.4.10 Citrine``\n=======================\n\n2018-10-25\n\nBug\n---\n\n* [EOS-2500] fix shutdown procedure which might send a kill signal to process id=1 when the watchdog becomes a zombie process\n* [EOS-3015] deal with OpenAsync timeouts in the ioflush thread\n* [EOS-3016] Properly handle URL sources (eg.: starting with root://) in eos cp\n* [EOS-3021] Make function executed by thread noexcept so that we get a proper stack if it throws an exception\n* [EOS-3022] Use uint64_t for storing file ids in the archive command\n* fixes for file ids > 2^31 (int->long long in FST)\n\n\nImprovements\n------------\n\n* update file sizes for ongonig writes in eosxd by default every 5s and as long as the cap is valid\n\n``v4.4.9 Citrine``\n==================\n\n2018-10-22\n\nBug\n---\n\n* [EOS-2947] - MGM crash near eos::HierarchicalView::findLastContainer\n* [EOS-2981] - DrainJob destructor: Thread attempts to join with itself\n* [EOS-3009] - -checksum argument of fileinfo not supported anymore\n* MGM: Fix master-slave propagation of container metadata\n\n\n``v4.4.8 Citrine``\n==================\n\n2018-10-19\n\nBug\n---\n\n* [EOS-3001] - fix clients seeing deleted CWDs after few minutes\n\n\n``v4.4.7 Citrine``\n==================\n\n2018-10-18\n\nBug\n---\n\n* [EOS-2992],[EOS-2994],[EOS-2967] - clients shows empty file list after caps expired\n* [EOS-2997] - GIT usage broken since hard-links are enabled by default\n\n``v4.4.6 Citrine``\n==================\n\n2018-10-10\n\nBug\n---\n\n* [EOS-2816] - eos cp issues\n* [EOS-2894] - FUSEX: \"xauth -q -\" gets stuck in \"D\" state\n* [EOS-2992] - aiadm: Lost all files in EOS home\n* FUSEX: Various fixes\n\n\nTask\n----\n\n* [EOS-2988] - Login hangs forever (with HOME=/eos/user/l/laman)\n\n\n``v4.4.5 Citrine``\n==================\n\n2018-10-10\n\nBug\n---\n\n* [EOS-2931] - Operation confirmation value isn't random\n* [EOS-2962] - table in documentation badly displayed on generated website\n* [EOS-2964] - Heap-use-after-free on new master / slave when booting\n* [EOS-2970] - \"fs mv\" not persisted in config file\n* MGM: Disable by default the QdbMaster implementation and use the env variable\n    EOS_USE_QDB_MASTER to enable it when the QDB namespace is used\n* MGM: Enable broadcast before loading the configuration in the QdbMaster so\n    that the MGM collects broadcast replies from the file systems\n* MGM: Fix possible deadlock at startup when a file system needs to be put\n    in kDrainWait state during configuration loading\n* MGM: Various improvements to the shutdown procedure for a clean exit\n* MQ: Fix memory leak of RSA Objects\n\nImprovement\n------------\n\n* [EOS-2901] - RFE: \"slow\" lock debug - print more info on single line, or disable printing?\n* [EOS-2966] - FUSEX: hardcode RPM dependency on 'zeromq'\n\n\n``v4.4.4 Citrine``\n==================\n\n2018-10-09\n\nBug\n----\n\n* [EOS-2951] - FST crashes while MGM is down\n* MGM: Fix find crash when a broken symlink exists along side a directory with\n  the same name\n* MGM: Fix creation of directories that have the same name as a broken link\n\nImprovement\n-----------\n\n* MGM: Improve shutdown of the MGM and cleanup of threads and resources\n\n\n``v4.4.3 Citrine``\n==================\n\n2018-10-04\n\nBug\n----\n\n* [EOS-2944] - Central Drain Flaws\n* [EOS-2945] - Disks ends up in wrong state with leftover files when central drain is active\n* [EOS-2946] - slave mq seen as down by the master MGM\n\nImprovement\n-----------\n\n* [EOS-2940] - Error message if wrong params for 'eos file info'\n\n\n``v4.4.2 Citrine``\n==================\n\n2018-10-03\n\nBug\n----\n\n* FST: Fix populating the vector of replica URL which can lead to a crash\n\n\n``v4.4.1 Citrine``\n==================\n\n2018-10-03\n\nBug\n----\n\n* [EOS-2936] - configuration file location change\n* [EOS-2937] - eossync does not cope with the change in the config path\n* MGM: Fix http port used for redirection to the FSTs\n\n\n``v4.4.0 Citrine``\n==================\n\n2018-10-02\n\nBug\n----\n\n* [EOS-1952] - eosd crash in FileAbstraction::WaitFinishWrites\n* [EOS-2743] - \"eosd\" segfault .. error 4 in libpthread-2.17.so[...+17000]\n* [EOS-2801] - Heap-use-after-free in LayoutWrapper::WaitAsyncIO\n* [EOS-2836] - Sain file cannot be downloaded when one FS is not present\n* [EOS-2914] - git repo on EOS corruption\n* [EOS-2922] - eos-server.el6 package requires /usr/bin/bash (not provided by any package in SLC6)\n* [EOS-2926] - MGM deadlock due to fusex capability delete operation\n* [EOS-2930] - Core dump in rename path sanity check\n* [EOS-2933] - createrepo fails on large repo\n\nNew Feature\n------------\n\n* [EOS-2928] - FUSEX interference from user deletion and generic removal protection (g:z5:!d)\n\nTask\n----\n\n* [EOS-2721] - UNIX permissions not propagated to the slave (until a slave restart or failover)\n\nImprovement\n------------\n\n* [EOS-2696] - eosarchived systemd configuration\n* [EOS-2799] - eosdropboxd: document, add \"--help\", \"-h\" options -- or hide outside of default path\n* [EOS-2853] - Make background scan rate configurable like scaninterval\n* [EOS-2906] - Add \"fstpath\" to the message written in MGM's report log\n* [EOS-2921] - Support client defined LEASE times\n\nUser Documentation\n-------------------\n\n* [EOS-1723] - Instruction how to migrate to quarkdb namespace\n\n\n``v4.3.14 Citrine``\n=======================\n\n2018-09-26\n\nBug\n---\n\n* [EOS-2759] - FST crash on NULL value for stat.sys.keytab, right after machine boot\n* [EOS-2821] - FST has lots of FS' stuck in \"booting\" state\n* [EOS-2904] - eos-client: manpages empty/missing on SLC6\n* [EOS-2912] - FuseServer does not update namespace store after addFile\n* [EOS-2913] - \"newfind --count\" displays empty lines for each entry found\n* [EOS-2916] - Missing server side check for inode quota and wrong eosxd client behaviour\n* [EOS-2917] - Central draining crash ?\n\nTask\n-----\n\n* [EOS-2832] - FST aborts (coredump) if it cannot launch a transferjob (\"Not able to send message to child process\")\n\n\n``v4.3.13 Citrine``\n=======================\n\n2018-09-19\n\nBug\n---\n\n* [EOS-2892] - FUSE: Initialize XrdSecPROTOCOL before issuing kXR_query to check MGM features\n* [EOS-2895] - MGM: fix locking when waiting for a booted namespace\n* [EOS-2989] - MGM: Fix queueing logic in Egroup class\n* fix wrong checksum validation for chunked OC uploads from the secondary replicas\n* let FUSEX writes fail after 60s otherwise we can get stuck pwrite calls/hanging forever\n\n\n``v4.3.12 Citrine``\n=======================\n\n2018-09-13\n\nBug\n---\n\n* [EOS-2793] - removexattr fails to remove attribute from mgm metadata\n* [EOS-2800] - Relocate check for sys.eval.useracl from fuse client to the Fuseserver\n* [EOS-2850] - avoid directory move into itself when going via symlinks\n* [EOS-2870] - faulty scheduling on offline machine (regression)\n* [EOS-2873] - fix chmod/chown behaviour on executing EOSXD client\n* [EOS-2874] - fix 'adjustreplica' for files containing an '&' sign\n* Thread sanitizer fixes in EOSXD\n* Fix snooze time in WFE\n\nImprovements\n------------\n\n* Default fd limit for shared EOSXD mounts is now 512k\n* Don't open journals for file reads in EOSXD ( divides by 2 number of fds)\n* Add 'fs dropghosts <fsid>' call to get rid of illegal entries in filesystem view without any corresponding meta data object (undrainable filesystems)\n* Use filesystem name as default cache subdirectory in EOSXD (not default)\n* Improve locking in EOSXD notification path - release ns mutex in most places before notifying - add timing counters to all EOSXD counters\n\n\n``v4.3.11 Citrine``\n=======================\n\n2018-09-05\n\nBug\n---\n\n* MGM: Fix slots leak of proc commands for which the initial client disconnected\n  before receiving the response\n* MGM/FUSE: Add support for all possible encodings between EOSXD and MGM\n* FUSEX: Fix stack corruption when doing recovery and remove leaking proxy object\n  after recovery\n* FUSEX: Add 'sss' as a possible authentication scheme for eosxd\n\nImprovements\n------------\n\nCI: Add script for promoting tag releases from the testing to the stable repo\n\n\n``v4.3.10 Citrine``\n=======================\n\n2018-08-31\n\nBug\n---\n\n* [EOS-2138] - Handling of white spaces in eos commands\n* [EOS-2722] - filR state not propagated to parent branches in a snapshot\n* [EOS-2787] - Fix filesystem ordering for FUSE file creation by geotag, then fsid\n* [EOS-2838] - WFE background thread hammering namespace, running find at 100 Hz\n* [EOS-2839] - Central draining is active on slave MGM\n* [EOS-2843] - FUSEX crash in metad::get(), pmd=NULL.\n* [EOS-2847] - FUSEX: Race between XrdCl::Proxy destructor and OpenAsyncHandler::HandleResponseWithHosts\n* [EOS-2849] - Memory Leaks in FST code\n\nTask\n----\n\n* [EOS-2825] - FUSEX (auto-)unmount not working?\n\nImprovement\n-----------\n\n* [EOS-2852] - MGM: hardcode RPM dependency on 'zeromq'\n* [EOS-2856] - EOSXD marks CWD deleted when invalidating a CAP subscription\n\n\n``v4.3.9 Citrine``\n==================\n\n2018-08-23\n\nBug\n---\n\n* [EOS-2781] - MGM crash during WebDAV copy\n* [EOS-2797] - FUSE aborts in LayoutWrapper::CacheRemove, \".. encountered inode which is not recognized as legacy\"\n* [EOS-2798] - FUSE uses inconsistent datatypes to handle inodes\n* [EOS-2808] - Symlinks on EOSHOME have size of 1 instead of 0\n* [EOS-2817] - eosxd crash in metad::cleanup\n* [EOS-2826] - Cannot create a file via emacs on EOSHOME topdir\n* [EOS-2827] - log/tracing ID has extra '='\n\n\n``v4.3.8 Citrine``\n==================\n\n2018-08-14\n\nBug\n---\n\n* [EOS-2193] - Eosd fuse crash around FileAbstraction::GetMaxWriteOffset\n* [EOS-2292] - eosd crash around \"FileAbstraction::IncNumOpenRW (this=0x0)\"\n* [EOS-2772] - ns compact command doesn't do repairs\n* [EOS-2775] - TPC failing in IPV4/6 mixed setups\n* Fix quota accounting for touched files\n\n\nNew Feature\n-----------\n\n* [EOS-2742] - Add reason when we change the status for file systems and node\n\n\n``v4.3.7 Citrine``\n==================\n\n2018-08-07\n\nBug\n---\n\n* Fix possible deadlock when starting the MGM with more than the maximum allowed\n  number of draining file systems per node.\n\n\n``v4.3.6 Citrine``\n==================\n\n2018-08-06\n\nBug\n---\n\n* [EOS-2752] - FUSE: crashes around \"blockedtracing\" getStacktrace()\n* [EOS-2758] - SLC6 FST crashes on getStacktrace()\n\nTask\n----\n\n* [EOS-2757] - The 4.3.6 pre-release generates FST crashes (SEGFAULT)\n\nImprovement\n-----------\n\n* [EOS-2753] - Logging crashing\n\n\n``v4.3.5 Citrine``\n==================\n\n2018-07-26\n\nBug\n---\n\n* [EOS-2692] - Lock-order-inversion between FsView::ViewMutex and ConfigEngine::mMutex\n* [EOS-2698] - XrdMqSharedObjectManager locks the wrong mutex\n* [EOS-2701] - FsView::SetGlobalConfig corrupts the configuration file during shutdown\n* [EOS-2718] - Commit.cc assigns zero-sized filename during rename, corrupting the namespace queue\n* [EOS-2723] - user.forced.placementpolicy overrules sys.forced.placementpolicy\n* Fix S3 access configuration not getting properly refreshed\n\nImprovement\n-----------\n\n* [EOS-2691] - FUSEX abort in ShellException(\"Unable to open stdout file\")\n* [EOS-2684] - Allow uuid identifier in 'fs boot' command\n* [EOS-2679] - Display xrootd version in 'eos version -m' and 'node ls --sys' commands\n* Documentation for setting up S3 access [Doc > Configuration > S3 access]\n* More helpful error messages for S3 access\n\n``v4.3.4 Citrine``\n==================\n\n2018-07-04\n\nBug\n---\n\n* [EOS-2686] - DrainFs::UpdateProgress maxing out CPU on PPS\n* Fix race conditions and crashes while updating the global config map\n* Fix lock order inversion in the namespace prefetcher code leading to deadlocks\n\nNew feature\n-----------\n\n* FUSEX: Add FIFO support\n\nImprovement\n-----------\n\n* Remove artificial sleep when generating TPC drain jobs since the underlying issue\n  is now fixed in XRootD 4.8.4 - it was creating identical tpc keys.\n* Replace the use of XrdSysTimer with std::this_thread::sleep_for\n\n\n``v4.3.3 Citrine``\n==================\n\n2018-06-29\n\nImprovement\n-----------\n\n* FUSEX: Fix issues with the read-ahead functionality\n* MGM: Extended the routing functionality to detect online and master nodes with\n  automatic stalling if no node is available for a certain route.\n* MGM: Fix race condition when updating the global configuration map\n\n\n``v4.3.2 Citrine``\n==================\n\n2018-06-26\n\nBug\n---\n\n* FUSEX: encode 'name' in requests by <inode>:<name>\n* MGM: decode 'name' in requests by <inode>:<name>\n* MGM: decode routing requests from eosxd which have an URL encoded path name\n\n\n``v4.3.1 Citrine``\n==================\n\n2018-06-25\n\nBug\n---\n\n* FUSEX: make the bulk rm the default\n* FUSEX: by default use 'backtace' handler, fusermount -u and emit received signal again.\n* FUSEX: use bulk 'rm' only if the '-rf' flag and not verbose option has been selected\n* FUSEX: avoid possible dead-lock between calculateDepth and invalidation callbacks\n\n\n``v4.3.0 Citrine``\n==================\n\n2018-06-22\n\nBug\n---\n\n* [EOS-1132] - eosarchived.py, write to closed (log) file?\n* [EOS-2401] - FST crash in eos::fst::ScanDir::CheckFile (EOSPPS)\n* [EOS-2513] - Crash when dumping scheduling groups for display\n* [EOS-2536] - FST\n* [EOS-2557] - disk stats displaying for wrong disks\n* [EOS-2612] - Probom parsing options in \"eos fs ls\"\n* [EOS-2621] - Concurrent access on FUSE can damage date information (as shown by ls -l)\n* [EOS-2623] - EOSXD loses kernel-md record for symbolic link during kernel compilation\n* [EOS-2624] - Crash when removing invalid quota node\n* [EOS-2654] - Unable to start slave with invalid quota node\n* [EOS-2655] - 'eos find' returns different output for dirs and files\n* [EOS-2656] - Quota rmnode should check if there is quota node before deleting and not after\n* [EOS-2659] - IO report enabled via xrd.cf but not collecting until enabled on the shell\n* [EOS-2661] - space config allows fs.configstatus despite error message\n\nNew Feature\n-----------\n\n* [EOS-2313] - Add queuing in the central draining\n\n\nImprovement\n-----------\n\n* [EOS-2297] - MGM: \"boot time\" is wrong, should count from process startup\n* [EOS-2460] - MGM should not return\n* [EOS-2558] - Fodora 28 rpm packages\n* [EOS-2576] - http: x509 cert mapping using legacy format\n* [EOS-2589] - git checkout slow\n* [EOS-2629] - Make VST reporting opt-in instead of opt-out\n* [EOS-2644] - Possibility to configure #files and #dirs on MGM with quarkdb\n\n\n``v4.2.26 Citrine``\n=======================\n\n2018-06-20\n\nBug\n---\n\n* [EOS-2662] - ATLAS stuck in stacktrace due to SETV in malloc in table formatter\n* [EOS-2415] - Segmentation fault while building the quota table output\n\n\n``v4.2.25 Citrine``\n=======================\n\n2018-06-14\n\nBug\n---\n\n* Put back option to enable external authorization library\n\n\n``v4.2.24 Citrine``\n=======================\n\n2018-06-13\n\nBug\n----\n\n* [EOS-2081] - \"eosd\" segfault in sscanf() / filesystem::stat() / EosFuse::lookup\n* [EOS-2600] - Clean FST shutdown wrongly marks local LevelDB as dirty\n\nNew Feature\n-----------\n\n* Use std::shared_timed_mutex for the implementation of RWMutex. This is by default disabled and can be enabled by setting the EOS_USE_SHARED_MUTEX=1 environment var.\n\nImprovement\n-----------\n\n* The FSTs no longer do the dumpmd when booting.\n\n\n``v4.2.23 Citrine``\n=======================\n\n2018-05-23\n\nBug\n----\n\n* [EOS-2314] - Central draining traffic is not tagged properly\n* [EOS-2318] - Slave namespace failed to boot (received signal 11)\n* [EOS-2465] - adding quota node on the master kills the slave (which then bootloops trying to apply the same quota)\n* [EOS-2537] - Balancer scheduler broken\n* [EOS-2544] - Setting recycle bin size changes inode quota to default.\n* [EOS-2564] - CITRINE MGM does not retrieve anymore error messages from FSTs in error.log\n* [EOS-2574] - enabling accounting on the slave results in segfault shortly after NS booted\n* [EOS-2575] - used space on /eos/<instance>/proc/conversion is ever increasing\n* [EOS-2579] - Half of the Scheduling groups are selected for  new file placement\n* [EOS-2580] - 'find -ctime' actually reads and compares against 'mtime'\n* [EOS-2582] - Access command inconsistencies\n* [EOS-2585] - EOSFUSE inline-repair not working\n* [EOS-2586] - The client GEOTAG is not taken into account when performing file placement\n\nNew Feature\n------------\n\n* [EOS-2566] - Enable switch to propagate uid only via fuse\n\nTask\n----\n\n* [EOS-2119] - Implement support in central drain for RAIN layouts + reconstruction\n* [EOS-2587] - Fix documentation for docker deployment\n\nImprovement\n-----------\n\n* [EOS-2462] - improve eos ns output\n* [EOS-2571] - Change implementation of atomic uploads`\n* [EOS-2588] - Change default file placement behaviour in case of clients with GEOTAG\n\n\n``v4.2.22 Citrine``\n=======================\n\n2018-05-03\n\nBug\n----\n\n* [EOS-2486] - eosxd stuck, last message \"recover reopened file successfully\"\n* [EOS-2512] - FST crash around eos::fst::XrdFstOfsFile::open (soon after start, \"temporary fix\"?)\n* [EOS-2516] - \"eosd\" aborts with std::system_error \"Invalid argument\" on shutdown (SIGTERM)\n* [EOS-2519] - Segmentation fault when receiving empty opaque info\n* [EOS-2529] - eosxd: make renice =setpriority() optional, req for unprivileged containers\n* [EOS-2541] - (eosbackup halt): wrong timeout and fallback in FmdDbMapHandler::ExecuteDumpmd\n* [EOS-2543] - Unable to read 0-size file created with eos touch\n\nNew Feature\n-----------\n\n* [EOS-1811] - RFE: support for \"hard links\" in FUSE\n* [EOS-2505] - RFE: limit number of inodes for FUSEX cache, autoclean\n* [EOS-2518] - EOS WfE should log how long it takes to execute an action\n* [EOS-2542] - Group eossync daemons in eossync.target\n\nImprovement\n-----------\n\n* [EOS-2114] - trashbin behaviour for new eos fuse implementation\n* [EOS-2423] - EOS_FST_NO_SSS_ENFORCEMENT breaks writes\n* [EOS-2532] - Enable recycle bin feature on FUSEX\n* [EOS-2545] - Report metadata cache statistics through \"eos ns\" command\n\nQuestion\n--------\n\n* [EOS-2458] - User quota exceeted and user can write to this directory\n* [EOS-2497] - Repeating eos fusex messages all over\n\nIncident\n--------\n\n* [EOS-2381] - File lost during fail-over ATLAS\n\n\n``v4.2.21 Citrine``\n=======================\n\n2018-04-18\n\nBug\n----\n\n* [EOS-2510] - eos native client is not working correctly against eosuser\n\nNew\n----\n\n* XrootD 4.8.2 readiness and required\n\n``v4.2.20 Citrine``\n=======================\n\n2018-04-17\n\nImprovements\n------------\n\nFST: make the connection pool configurable by defining EOS_FST_XRDIO_USE_CONNECTION_POOL\nFUSE: avoid that FUSE calls open in a loop for every write in the outgoing write-back cache if the file open failed\nFUSE: remove 'dangerous' recovery functionality which is unnecessary with xrootd 4\nFUSE: Try to reuse connections towards the MGM when using the same credential file\n\n\n``v4.2.19 Citrine``\n=======================\n\n2018-04-10\n\nBug\n----\n\n* [EOS-2440] - `eos health` is broken\n* [EOS-2457] - EOSPPS: several problems with `eos node ls -l`\n* [EOS-2466] - 'eos rm' on a file without a container triggers an unhandled error\n* [EOS-2475] - accounting: storagecapacity should be sum of storageshares\n\nTask\n----\n\n* [EOS-1955] - .xsmap file still being created (balancing? recycle bin?), causes \"corrupted block checksum\"\n\n\n``v4.2.18 Citrine``\n=======================\n\n2018-03-20\n\nBug\n----\n\n* [EOS-2249] - Citrine generation of corrupted configuration\n* [EOS-2288] - headroom is not propagated from space to fs\n* [EOS-2334] - Failed \"proto:\" workflow notifications do not end up in either the ../e/.. or ../f/.. workflow queues\n* [EOS-2360] - FST aborts with \"pure virtual method called\", \"terminate called without an active exception\" on XrdXrootdProtocol::fsError\n* [EOS-2413] - Crash while handling a protobuf reply\n* [EOS-2419] - Segfault around TableFormatter (when printing FSes)\n* [EOS-2424] - proper automatic lock cleanups\n* [EOS-2428] - draining jobs create .xsmap files on the source and destination FSTs\n* [EOS-2429] - FuseServer does not grant SA_OK permission if ACL only allows to be a writer\n* [EOS-2432] - eosapmond init script for CC7 sources /etc/sysconfig/eos\n* [EOS-2433] - Wrong traffic accounting for TPC/RAIN/Replication\n* [EOS-2436] - FUSEX: permission problem in listing shared folder\n* [EOS-2438] - FUSEX: chmod +x does not work\n* [EOS-2439] - FUSEX: possible issue with sys.auth=*\n* [EOS-2442] - TPC of 0-size file fails\n\nImprovement\n-----------\n\n* [EOS-2423] - EOS_FST_NO_SSS_ENFORCEMENT breaks writes\n* [EOS-2430] - fusex cache should not use /var/eos\n\nQuestion\n--------\n\n* [EOS-2431] - fusex cache cleanup\n\n\n``v4.2.17 Citrine``\n=======================\n\n2018-03-15\n\nBug\n---\n\n* [EOS-2292] - eosd 4.2.4-1 segmentation fault in SWAN\n* [EOS-2322] - eosd 4.2.4-1 segmentation fault on swan003\n* [EOS-2388] - Fuse::utimes only honours posix permissions, but not ACLs\n* [EOS-2402] - FST abort in eos::fst::FmdDbMapHandler::ResyncAllFromQdb (EOSPPS)\n* [EOS-2403] - eosd 4.2.4-1 SegFaults on swan001\n* [EOS-2404] - eosd 4.2.4-1 SegFaults on swan002\n\nImprovement\n-----------\n\n* [EOS-2389] - Classify checksum errors during scan\n* [EOS-2398] - Apply quota settings relatively quick in time on the FUSEX clients\n* [EOS-2408] - Proper error messages for user in case of synchronous workflow failure\n\n\n``v4.2.16 Citrine``\n=======================\n\n2018-03-02\n\nBug\n---\n\n* [EOS-2142] - eosfstregister fails to get mgm url in CentOS 7\n* [EOS-2370] - EOSATLAS crashed while creating the output for a recursive attr set\n* [EOS-2382] - FUSEX access with concurrency creates orphaned files\n* [EOS-2386] - Vectored IO not accounted by \"io\" commands\n* [EOS-2387] - FST crash in eos::fst::ReedSLayout::AddDataBlock\n\nTask\n----\n\n* [EOS-2383] - eosxd: segfault in inval_inode\n\nImprovement\n-----------\n\n* [EOS-1565] - RFE: turn off SIGSEGV handler on non-MGM EOS components\n\n\n``v4.2.15 Citrine``\n=======================\n\n2018-02-22\n\nBug\n---\n\n* [EOS-2353] - git clone with 2GB random reading creates read amplification\n* [EOS-2359] - Deadlock in proto wfe\n* [EOS-2361] - MGM crash after enabling ToggleDeadlock\n* [EOS-2362] - eosfusebind (runuser) broken on slc6\n\n\n``v4.2.14 Citrine``\n=======================\n\n2018-02-20\n\nBug\n----\n\n* [EOS-2153] - consistent eosd memory leak\n* [EOS-2348] - ns shows wrong value for resident memory (shows virtual)\n* [EOS-2350] - eosd returns Numerical result out of range when talking to a CITRINE server and out of quota\n\n\n``v4.2.13 Citrine``\n=======================\n\n2018-02-19\n\nBug\n----\n\n* [EOS-2057] - Wrong conversion between IEC and Metric multiples\n* [EOS-2299] - WFE can't be switched off\n* [EOS-2309] - Possible memleak in FuseServer::Caps::BroadcastReleaseFromExternal\n* [EOS-2310] - eosadmin wrapper no longer sends role\n* [EOS-2330] - Usernames with 8 characters are wrongly mapped\n* [EOS-2335] - Crash around XrdOucString::insert\n* [EOS-2339] - \"eos\" shell crash around \"eos_console_completion\",\"eos_entry_generator\"\n* [EOS-2340] - \"eos\" crash around \"AclHelper::CheckId\"\n* [EOS-2337] - autofs-ed fuse mounts not working for mountpoint names with matched entries under \"/\"\n\nTask\n----\n\n* [EOS-2329] - protect MGM against memory exhaustion caused by a globbing ls\n\nImprovement\n-----------\n\n* [EOS-2321] - Quota report TiB vs. TB\n* [EOS-2323] - citrine mgm crash\n* [EOS-2336] - Default smart files in the proc filesystem\n\nConfiguration Change\n-------------------+\n\n* [EOS-2279] - eosfusebind error message at login\n\nIncident\n--------\n\n* [EOS-2298] - EOS MGM memory leak\n\n\n\n``v4.2.12 Citrine``\n=======================\n\n2018-02-01\n\nBug\n---\n\n* Fix deadlock observerd in EOSATLAS between gFsView.ViewMutex and pAddRmFsMutex from the\n  scheduling part.\n* Fix bug on the FST related to the file id value going beyond 2^32-1\n* [EOS-2275] - Possible data race in ThreadPool\n* [EOS-2290] - increase shutdown timeout for the FSTs\n\nNew Feature\n----------+\n\n* Add skeleton for new \"fs\" command using protobuf requests\n* Add skeleton for CTA integration\n* Enhance the mutex deadlock detection mechanism\n\n\n``v4.2.11 Citrine``\n=======================\n\n2018-01-25\n\nBug\n---\n\n* [EOS-2264] - Fix possible insertion of an empty FS in FSView\n* [EOS-2270] - FSCK crashed booting namespace\n* [EOS-2271] - EOSPUBLIC deadlocked\n* [EOS-2261] - \"eos node ls <node>\" with the monitoring flag does not apply the node filter\n* [EOS-2267] - EOSPublic has crashed while recursively setting ACLs\n* [EOS-2268] - Third party copying (on the same instance) fails with big files\n\nImprovement\n-----------\n\n* [EOS-2283] - Double unlock in CITRINE code\n\nTask\n----\n\n* [EOS-2244] - Understand EOSATLAS configuration issue\n\n\n``v4.2.10 Citrine``\n=======================\n\n2018-01-24\n\nBug\n---\n\n* [EOS-2264] Fix possible insertion of an empty FS in FSView\n* [EOS-2258] If FST has qdb cluster configuration then to the dumpmd directly against QuarkDB\n* [EOS-2277] fixes 'fake' truncation failing eu-strip in rpm builds of eos\n\nImprovements\n------------\n\n* Refactoring of includes to speed up compilation, various build improvements\n* avoid to call IsKnownNode to discover if an FST talks to the MGM, rely on sss + daemon user\n* use (again) a reader-preferring mutex for the filesystem view\n\n\n``v4.2.9 Citrine``\n=======================\n\n2018-01-18\n\nBug\n---\n\n* [EOS-2228] Crash around forceRefreshSched related to pFsId2FsPtr\n\nNew Feature\n-----------\n\n* Filter out xrdcl.secuid/xrdcl.secgid tags on the FSTs to avoid triggering a\n  bug on the xrootd client implementation\n\nImprovements\n------------\n\n* [EOS-2253] Small writes should be aggregated with the journal\n* Refactoring of the includes to speed up compilation\n\n\n``v4.2.8 Citrine``\n=======================\n\n2018-01-16\n\nBug\n---\n\n* [EOS-2184] - \"eos ls -l\" doesn't display the setgid bit anymore\n* [EOS-2186] - eos ns reports wrong number of directory\n* [EOS-2187] - Authproxy port only listens on IPv4\n* [EOS-2211] - CITRINE deadlocks on namespace mutex\n* [EOS-2216] - \"binary junk\" logged in func=RemoveGhostEntries (FID?)\n* [EOS-2224] - selinux denials with eosfuse bind.\n* [EOS-2229] - files downloaded with scp show 0 byte contents\n* [EOS-2230] - read-ahead inefficiency\n* [EOS-2231] - ioflush thread serializes file closing and leads to memory aggregation\n* [EOS-2241] - Directory TREE mv does not invalidate source caches\n\nNew Feature\n-----------\n\n* [EOS-2248] - FUSEX has to point ZMQ connection to active master\n\nImprovement\n-----------\n\n* [EOS-2238] - Print a warning for 'node ...' functions when an FST is seen without a GEO tag\n\nSupport\n-------\n* [EOS-2208] - EOS MGM (new NS) aborts with \"pure virtual method called\" on update (restart?)\n\n\n``v4.2.7 Citrine``\n=======================\n\n2017-12-18\n\nBug\n---\n\n* [EOS-2207] - Work-around via environment variable to avoid loading too big no-replica sets (export EOS_NS_QDB_SKIP_UNLINKED_FILELIST)\n\n* Many improvements and fixes for eosxd\n  - fixing gateway mount options to work as NFS exports\n  - fixing access function which was not refreshing caps/md objects\n\n``v4.2.6 Citrine``\n=======================\n\n2017-12-18\n\nBug\n---\n\n* [EOS-2150] - Repair option for d_mem_sz_diff error files\n* [EOS-2202] - Lock-order-inversion between gAccessMutex and ViewMutex\n\n* Many improvements and fixes for eosxd\n\n``v4.2.5 Citrine``\n=======================\n\n2017-12-12\n\nBug\n---\n\n* [EOS-2142] - eosfstregister fails to get mgm url in CentOS 7\n* [EOS-2146] - symlinks have to show the size of the target string\n* [EOS-2147] - listxattr creates SEGV on OSX\n* [EOS-2148] - eosxd on OSX creates empty file when copying with 'cp'\n* [EOS-2159] - An owner of a directory has to get always chmod permissions\n* [EOS-2161] - rm -rf on fusex mount fails to remove all files/subdirectories\n* [EOS-2167] - new file systems added go to 'spare.0'\n* [EOS-2174] - Running out of FDs when using a user mount\n* [EOS-2175] - eos ns command takes 10s on EOSPPS\n* [EOS-2179] - calling verifychecksum issue\n* [EOS-2180] - Unable to access quota space <filename> Read-only file system\n\n* Many improvements and fixes for esoxd\n* Performance improvements and fixes for the namespace and QuarkDB\n\n``v4.2.4 Citrine``\n=======================\n\n2017-11-28\n\nBug\n----\n\n* [EOS-2123] - eosxd renice's to lowest possible priority\n* [EOS-2130] - segv while compiling eos\n* [EOS-2137] - JSON output doesn't work anymore\n\nImprovements\n------------\n\n* Many improvements and fixes for eosxd\n* Many improvements and fixes for the namespace on QuarkDB\n\n\n``v4.2.3 Citrine``\n=======================\n\n2017-11-17\n\nNew features\n------------\n\n* New centralized draining implementation\n* mgmofs.qdbcluster option in the configuration of the MGM to connect QuarkDB cluster\n\nImprovements\n------------\n\n* Use the flusher also in the quota view of the new namespace\n* Use prefetching for TPC transfers\n\nBug\n---\n* [EOS-2117] - mount.eosx should filter invalid options\n* Fix ns stat statistics\n\n\n``v4.2.2 Citrine``\n=======================\n\n2017-11-14\n\nImprovements\n------------\n\n* Many fixes for the eosxd fuse module\n* Add eos_dump_proto_md tool to dump object metada info from QuarkDB\n* Clean-up and improvements of the eos_ns_conversion tool for the new namespace\n* Fix ns stat command not displaying ns info in monitoring format\n\n\n``v4.2.1 Citrine``\n=======================\n\n2017-11-10\n\nBug\n---\n\n* [EOS-2017] - MGM crash caused by FSCK\n* [EOS-2061] - converter error in  \"file adjustreplica\" on raid6/archive layouts\n* [EOS-2050] - Scheduling problem with adjustreplica and draining filesystem\n* [EOS-2066] - xrdcp \"Error [3005]\" trying to transfer a \"degraded\" archive/raid6 file\n* [EOS-2068] - Archive should use root identity when collecting files/dirs\n* [EOS-2073] - MGM (citrine 4.1.30) unable to load configuration due to #iostat::udptargets with empty value\n* [EOS-2092] - Auth proxy crashes\n* [EOS-2093] - eos file convert from raid6/archive to replica:2 seems to not work.\n* [EOS-2094] - JSON Return 0 instead of \"NULL\" when space.nominalsize is not defined\n\nTask\n----\n* [EOS-1998] - Allow FST to login even when client traffic is stalled\n\nImprovement\n-----------\n\n* [EOS-2101] - Report logical used-space when using xrootd commands\n* A lot of improvements on the fusex side\n\n\n``v4.2.0 Citrine``\n=======================\n\n2017-10-23\n\nBug\n----\n\n* [EOS-1971] - EOS node listing crash\n* [EOS-2015] - Table engine display values issue\n* [EOS-2057] - Wrong conversion between IEC and Metric multiples\n* [EOS-2060] - XrdMgmOfsFile SEGV out of bounds access\n\nNew Feature\n-----------\n\n* [EOS-2030] - Add '.' and '..' directories to file listings\n* Prototype for the new fuse implementation i.e fusex\n* Refactor of the ns command to use ProtoBuf-style commands\n\nTask\n----\n\n* [EOS-2033] - quota id mapping for non-existing users\n\nBug\n----\n\n* [EOS-2016] - avoid SEGV when removing ghost entries on FST\n* [EOS-2017] - avoid creating NULL object in map when resetting draining\n* DOC: various corrections - use solar template with new WEB colour scheme\n\n\n``v4.1.31 Citrine``\n=======================\n\n2017-09-19\n\nBug\n----\n\n* [EOS-2016] - avoid SEGV when removing ghost entries on FST\n* [EOS-2017] - avoid creating NULL object in map when resetting draining\n* DOC: various corrections - use solar template with new WEB colour scheme\n\n``v4.1.30 Citrine``\n========================\n\n2017-09-15\n\nBug\n----\n* [EOS-1978] - Preserve converted file ctime and ctime (CITRINE)\n* FUSE: fix significant leak when returning a cached directory listing\n* MGM: Enforce permission check when utime is executed\n* MGM: Fix uid/gid overflow and comparison issues\n* HTTP: fix ipv4/6 connection2ip function\n\n\n``v4.1.29 Citrine``\n=======================\n\n2017-09-08\n\nBug\n----\n* Mask the block checksum for draining and balancing when there is layout\n  requesting blockchecksum for replica files.\n* Add protection in case the proxies or the firewalleps vectors are not\n  properly populated and we try to access a location beyond the size of the\n  vector which leads to undefined behaviour.\n* Multiple fixes to the Schedule2Drain code\n* [EOS-1893] - EOS configuration can end up empty or truncated\n* [EOS-1989] - eos file verify <path> -checksum is broken\n* [EOS-1991] - eos-fuse rpm package broken dependency\n* [EOS-1996] - space ls geo output is wrongly formatted\n\n``v4.1.28 Citrine``\n=======================\n\n2017-08-30\n\nBug\n---\n* [EOS-1991] - eos-fuse rpm package broken dependency\n\n``v4.1.27 Citrine``\n=======================\n\n2017-08-28\n\nBug\n---\n* [EOS-1976] - EOSD client memory leak\n* [EOS-1986] - EOSPUBLIC: Crash when deleting a file entry\n* [EOS-1984] - MGM: only show available fs on geosched show state latency and penalties tables.\n* [EOS-1974] - NS: add missing initialization of pData (might lead to a SEGV during compaction if mmapping is disabled)\n\nImprovement\n-----------\n* [EOS-1791] - RFE: attempt to auto-unmount on eos-fuse-core updates\n* [EOS-1968] - eosd: always preload libjemalloc.so.1\n* [EOS-1983] - Built-in http server should be dual-stack\n\nNew features\n------------\n\n* New accounting command - \"eos accounting\".\n\n``v4.1.26 Citrine``\n=======================\n\n2017-08-07\n\nBug\n---\n* [EOS-558] - \"eos fileinfo\" should better indicate non-active machines\n* [EOS-1895] - MGM Crash when the groupscheduler can't place file\n* [EOS-1897] - /var/log/eos/archive/eosarchived.log is world-writeable, should not\n* [EOS-1906] - Incorrect GeoTree engine information\n* [EOS-1936] - EOS ATLAS lost file due to balancing\n\nStory\n-----\n* [EOS-1919] - Bug visible when creating YUM repositories on the FUSE mount in CITRINE instances\n\nImprovement\n------------\n* [EOS-1159] - renaming a \"quota node\" directory gets rid of the quota setting?\n* [EOS-1345] - documentation update - eos fs help\n* [EOS-1875] - RFE: isolate eos client from LD_LIBRARY_PATH via RPATH\n\n* Plus all the fixes from the 0.3.264 and 0.3.265 release form the bery_aquamarine branch.\n\n\n``v4.1.25 Citrine``\n=======================\n\n2017-06-29\n\nBugfix\n------\n* [EOS-542] - eos file version filename version modify the permissions of the file\n* [EOS-1259] - MGM eos node ls display\n* [EOS-1292] - \"eos\" hangs for 5min without EOS_MGM_URL - give verbose error message instead\n* [EOS-1317] - command to drop/refresh UID / GID cache is not documented?\n* [EOS-1762] - \"eos attr link origin target\" with a non-existent origin prevents listing of target's attributes\n* [EOS-1887] - Link back with the dynamic version of protobuf3\n* [EOS-1889] - file verify command fails when specifying fsid on a one-replica file\n* [EOS-1893] - EOS configuration can end up empty or truncated\n* [EOS-1888] - FSs wrongly reported as Unavailable by the GeoTreeEngine\n* [EOS-1892] - File copy is scheduled on a full FS\n\nNew Feature\n-----------\n* [EOS-1872] - \"Super\" graceful FST shutdown\n* There is a new dependency on protobuf3 packages both at build time and run time.\n  These packages can be downloaded from the citrine-depend yum repository:\n  http://storage-ci.web.cern.ch/storage-ci/eos/citrine-depend/el-7/x86_64/\n\nImprovement\n-----------\n* [EOS-1581] - RFE: better error messages from the eos client, remove 'error: errc=0 msg=\"\"'\n\n\n``v4.1.24 Citrine``\n=======================\n\n2017-06-14\n\nBugfix\n------\n* [EOS-162] - RFE: auto-refill spaces from \"spare\", up to \"nominalsize\"\n* [EOS-455] - RFE: drop either fid: or fxid:, use the other consistently\n* [EOS-1299] - MGM node and fs printout with long hostname\n* [EOS-1716] - MGM: typo/missing whitespace in \"client acting as directory owner\" message\n* [EOS-1859] - PPS crash while listing space\n* [EOS-1877] - eos file drop does not accept fid:XXXX\n* [EOS-1881] - List quota info not working anymore on EOSLHCB\n* Fix fsck bug mixing information from different types of issues\n\nTask\n-----\n* [EOS-1851] - mount.eos assumes sysv or systemd present\n\nImprovement\n-----------\n* [EOS-1875] - RFE: isolate eos client from LD_LIBRARY_PATH via RPATH\n\nSupport\n-------\n* [EOS-1064] - get the year information for EOS file\n\n\n``v4.1.23 Citrine``\n=======================\n\n2017-05-17\n\nBugfix\n------\n* MGM: Take headroom into account when scheduling for placement\n* MGM: Add protection in case the bookingsize is explicitly set to 0\n* ARCHIVE: Use the MgmOfsAlias consistently otherwise the newly generated archive file will contain invalid JSON lines.\n\n\n``v4.1.22 Citrine``\n=======================\n\n2017-05-15\n\nBugfix\n------\n* Fix response for xrdfs query checksum to display \"adler32\" instead of \"adler\" as checksum type\n* Fix launch of the follower thread for the MGM slave\n\n\n``v4.1.21 Citrine``\n=======================\n\n2017-05-12\n\nBugfix\n------\n* [EOS-1833] - eosfuse.cc uses a free'd fuse_req_t -> segfault\n* [EOS-1781] - MGM crash in GeoBalancer\n* [EOS-1642] - \"Bad address\" on EOS FUSE should be \"Permission denied\"\n* [EOS-1830] - Recycle bin list crash when doing full scan (need protection)\n\n\nTask\n----\n* [EOS-1848] - selinux error when uninstalling eos-fuse-core\n\nUser Documentation\n------------------\n* [EOS-1826] - Missing dependencies on the front page\n\nSuggestion\n----------\n* [EOS-1827] - Ancient version of zmq.hpp causing issues when compiling with new zmq.h (taken from system)\n* [EOS-1828] - Utils.hh in qclient #include cannot find header\n* [EOS-1831] - CMAKE, microhttpd, and client\n* [EOS-1832] - Bug in console/commands/com_fuse.cc with handling of environment variable EOS_FUSE_NO_MT\n\n\n``v4.1.3 Citrine``\n==================\n\n2016-09-15\n\nBugfix\n-------\n\n* [EOS-1606] - Reading root files error when using eos 4.1.1\n* [EOS-1609] - eos -b problem : \\*\\*\\* Error in `/usr/bin/eos: free():`\n\n\n``v0.4.31 Citrine``\n=======================\n\n2016-07-22\n\nBugfix\n-------\n\n- FUSE: when using krb5 or x509, allow both krb5/x509 and unix so that authentication\n        does not fail on the fst (using only unix) when using XRootD >= 4.4\n\n\n``v0.4.30 Citrine``\n=======================\n\n2016-07-21\n\nBugfix\n-------\n\n- SPEC: Add workaround in the %posttrans section of the eos-fuse-core package\n        to keep all the necessary files and directories when doing an update.\n- CMAKE: Remove the /var/eos directory from the eos-fuse-core package and fix\n        type in directory name.\n\n``v0.4.29 Citrine``\n=======================\n\nBugfix\n-------\n\n- MGM: add monitoring switch to space,group status function\n- MGM: draining mutex fix and fix double unlock when restarting a drain job\n- MGM: fixes in JSON formatting, reencoding of non-http friendly tags/letters like <>?@\n- FST: wait for pending async requests in the close method\n- SPEC: remove directory creation scripting from spec files\n\nNew Features\n------------\n\n- RPM: build one source RPM which creates by default only client RPMs with less dependencies\n"
  },
  {
    "path": "doc/diopside/releases/citrine.rst",
    "content": ":orphan:\n\n.. highlight:: rst\n\n.. index::\n   single: Citrine\n\nCitrine\n========\n\n``Lifetime: 2014++``\n\n**Citrine** is the release name for the EOS development branch since 2014.\n\nRelease notes: :doc:`./citrine-release`\n"
  },
  {
    "path": "doc/diopside/releases/diopside-release.rst",
    "content": ":orphan:\n\n.. highlight:: rst\n\n.. index::\n   pair: Releases; Diopside\n\n\nDiopside Release Notes\n===========================\n\n``Version 5 Diopside``\n\nIntroduction\n------------\n\nThis release is based on XRootD V5.\n\n``v5.4.2 Diopside``\n===================\n\n2026-04-13\n\nBug\n----\n\n* MGM: Remove FsView write lock that does nothing\n\n``v5.4.1 Diopside``\n===================\n\n2026-03-18\n\nBug\n----\n\n* [EOS-6570] - Fuse file move does not update tree size and no. of files\n* [EOS-6571] - FST crashes due to SIGFPE\n* [EOS-6576] - FST individual stripe checksum not properly bandwidth limited\n\nImprovement\n------------\n\n* [EOS-3963] - Retire Schedule2Delete in favor of the FST manually checking its own unlinked file list\n\nUser Documentation\n--------------------\n\n* [EOS-6574] - Document FUSE Client recovery avoiding Reboot for FNAL\n\n\n``v5.4.0 Diopside``\n===================\n\n2026-02-27\n\nMajor Change\n------------\n\n* **Recycle Bin Configuration Update**: The recycle bin configuration has been refactored.\n  Please see :doc:`5.4.0/recycle_bin_config` for important migration instructions regarding new configuration commands and cleanup of legacy attributes.\n<<<<<<< HEAD\n* **MQ removal**: The MQ support has been dropped.\n  Upgrading from an EOS version with MQ support to EOS 5.4.0 will be disruptive! Follow the instructions (not provided yet).\n\nBug\n---\n* [EOS-5973] - old(+broken) recycle bin file is not being automatically purged\n* [EOS-6203] - MGM: Touch - return \"Is a directory\" instead of \"file exists\"\n* [EOS-6476] - MGM deadlocked due to slow FST config propagation\n* [EOS-6484] - eos and eoscp command line tools do not support roots:// protocol\n* [EOS-6505] - Investigate orphans in EOSATLAS\n* [EOS-6519] - MGM \"Stall\" messages tend to break log format\n* [EOS-6530] - EOS fails to detect broken disk with failed S.M.A.R.T. test\n* [EOS-6535] - directory listing cache unsafe in Multi threaded scenarios\n* [EOS-6537] - Cannot boot EOS filesystem with \"eos fs boot\"\n* [EOS-6541] - Steady increase in orphan files in EOS CMS after upgrade to EOS 5.3.25\n* [EOS-6543] - FUSE booking size requires n times more space than it should\n* [EOS-6552] - Invalid URI encoding in Location header violates RFC 3986 and RFC 9110\n* [EOS-6553] - Group drainer looping when file systems hold ghost entries\n* [EOS-6554] - LRU scan sometimes being spammed\n* [EOS-6555] - filesystem reboot is required when enabling scheduling group\n\nNew Feature\n-----------\n\n* [EOS-6384] - MGM: Redirect clients upon Macaroons request if MGM is a redirector\n* [EOS-6516] - RFE: shared recycle bin\n\nTask\n----\n\n* [EOS-6491] - MGM: error: unable to push conversion job to QuarkDB (errc=0) (Success)\n* [EOS-6507] - FST: HTTPHandler's use of XrdSecEntity fields\n* [EOS-6526] - [MGM] FlatScheduler does not take quota into account\n* [EOS-6547] - Allow Remote-User header to contain uid\n\nImprovement\n-----------\n\n* [EOS-5430] - Improve printed information for hard-links\n* [EOS-5672] - Hardlinks have a inconsistent number of stripes!\n* [EOS-5939] - IOBW & IOPING\n* [EOS-6275] - RFE: replace \"eos fsck config toggle-repair\" with explicit \"on\" and \"off\"\n* [EOS-6299] - Add support for multiple checksums\n* [EOS-6367] - Improve Recycler to avoid doing a full find\n* [EOS-6442] - RFE: better support for symlinks in \"eos fileinfo\": do not follow, print type.\n* [EOS-6509] - Add in the EOS Archive docs which fails are not entitled for archiving\n* [EOS-6510] - RFE: hard/soft links to not be reported as \"plain\" layout in \"space inspector\"\n* [EOS-6517] - RFE: GRPC to use common logging format (or log less)\n* [EOS-6545] - fs add with -r option\n* [EOS-6546] - Remove MQ\n* [EOS-6548] - Disallow modifying the obfuscate and encrypted xattrs from fusex\n* [EOS-6551] - Dump average/threshold stats used by groupdrainer\n\nUser Documentation\n------------------\n\n* [EOS-6345] - Add section about XRootD pmark configuration\n\n\n``v5.3.31 Diopside``\n====================\n\n2026-02-27\n\nFeature\n-------\n\n* [EOS-6563] MGM: LRU - Add LRU option to clean files based on their size and age\n* MGM: Quota: Do not multiply bookingsize by number of filesystems\n\n``v5.3.30 Diopside``\n====================\n\n2026-02-17\n\nBug\n---\n\n* [EOS-6566] MGM: Avoid dropping replicas sitting on unmounted/unavailable disk during fsck repair procedure.\n\n\n``v5.3.29 Diopside``\n====================\n\n2026-02-09\n\n* Update dependency to xrootd/eos-xrootd 5.9.1 release.\n\n\n``v5.3.28 Diopside``\n====================\n\n2026-02-05\n\nBug\n----\n\n* MGM: Fix handling of secondary groups that might be missing when an internal cache clean-up is done.\n* FUSE: Serialise fetching metadata of same inode. Fixes a race condition between listing/no-listing requests seen by JRC.\n=======\n>>>>>>> 05d4121e2 (DOC: Update documentation with respect to recycle bin configuration changes)\n\n\n``v5.3.27 Diopside``\n====================\n\n2025-11-26\n\nBug\n----\n\nMGM: Delete the configuration engine object during the MGM shutdown\nto avoid any possibility that this is reused during the destruction\nof the FsView that might in turn lead to deletion of file systems\nfrom the configuration.\n\n\n``v5.3.26 Diopside``\n====================\n\n2025-11-13\n\nBug\n----\n\n* [EOS-6514] - Make more robust file inspector stats load\n* [EOS-6534] - Rename via gRPC does not respect ACLs\n* [EOS-6536] - eosxd crash on el10 node\n* [EOS-6540] - SIGSEGV in recompute_tree_size\n\n\n``v5.3.25 Diopside``\n====================\n\n2025-10-30\n\nBug\n----\n\n* [EOS-6525] - EOS token scope not set for directory listings\n* [EOS-6527] - FST: HTTP - Range-Request with outer value > file size should not send back an error\n* [EOS-6528] - CLI: Acl - Cannot set 't' ACL via eos acl command\n* [EOS-6529] - space-level \"sys.acl\" gets persistently duplicated after further ACL changes\n\nNew Feature\n------------\n\n* [EOS-6276] - Track and enforce EOS quotas by logical bytes instead of raw physical bytes\n* [EOS-6520] - Create a command that does move and symlink at once\n\nImprovement\n------------\n\n* [EOS-6426] - NS: Replace with the new NS locking recompute_tree_size\n\n\n``v5.3.24 Diopside``\n====================\n\n2025-10-08\n\nBug\n----\n\n* [EOS-6500] - Malformed OAUTH token can crash MGM\n\n``v5.3.23 Diopside``\n====================\n\n2025-10-02\n\nBug\n----\n\n* [EOS-6500] - Malformed OAUTH token can crash MGM\n* [EOS-6501] - [MGM] EosToken - Do not validate path for tape REST API access\n* [EOS-6503] - GRPC find request with token ACL gets error - path outside token scope\n\nImprovement\n------------\n\n* [EOS-6508] - Support multi-path EOS tokens with shared ACL\n\n\n``v5.3.22 Diopside``\n====================\n\n2025-09-19\n\nBug\n----\n\n* [EOS-6487] - [HTTP] HTML-escape body of error messages before replying to the client\n* [EOS-6489] - TPC for direct I/O enabled FSTs fails\n* [EOS-6495] - Support EOS tokens to run find command\n* [EOS-6497] - Support EOSAUTHZ env for 'eos cp' commands\n* [EOS-6498] - Disable synchronously stripe checksum computation\n\nImprovement\n------------\n\n* [EOS-6483] - Allow Space attributes to define WFE trigger\n* [EOS-6488] - Rate limit scan of stripes\n\n\n``v5.3.21 Diopside``\n====================\n\n2025-09-02\n\nBug\n----\n\n* [EOS-6449] - Stripe checksum rescanned after upload\n* [EOS-6482] - [MGM] HTTP - Cannot set eos.app in case grpc/https authentication is used\n* FST: Properly register recovered stripes for RAIN files when recovery does\n  not happen at the entry server. Fixes recovery of under-replicated RAIN files.\n\n\n``v5.3.20 Diopside``\n====================\n\n2025-08-28\n\nBug\n----\n\n* [EOS-6480] - FuseServer needs to use Space ACLs in FillContainerCAP & ValidatePERM\n* [EOS-6481] - Listing file names longer than 4k crashes the MGM\n\n\n``v5.3.19 Diopside``\n====================\n\n2025-08-22\n\nBug\n----\n\n* FST: Make sure that during RAIN file recovery also the newly created stripes\n  are properly registered in the namespace.\n\n\n``v5.3.18 Diopside``\n====================\n\n2025-08-21\n\nNote\n-----\n\n* Update dependency eos-xrootd/xrootd to version 5.8.4.\n\nBug\n----\n\n* [EOS-6473] - FST \"failed to parse metadata info\" for corrupted namespace entries\n* [EOS-6474] - FST \"failed to parse metadata info\" for \"checksum=none\"\n* [EOS-6478] - Filesystem setup fails to create .eosfsid file\n\n\n``v5.3.17 Diopside``\n====================\n\n2025-08-07\n\nBug\n----\n\n* [EOS-6467] -  EOS file encryption : easy to lose content when using file encryption\n* [EOS-6468] - \"eos fsck report --error ...\" mis-counts entries\n* [EOS-6471] - FST validity expires after stalls of more than 60 seconds\n\n\nNew Feature\n------------\n\n* [EOS-6458] - RFE: \"EOS token\" file per FUSEX mountpoint\n\nImprovement\n------------\n\n* [EOS-6466] - RFE: \"best effort\" repair to recreate replicas for empty files (missing all replicas)\n\n\n``v5.3.16 Diopside``\n====================\n\n2025-08-01\n\nBug\n----\n\n* [EOS-6283] - non-default \"max_drain_threads\" not persisted over MGM restarts\n* [EOS-6433] - FUSEX \"touch\" causes \"Status: fuse::missingcommmits\"\n* [EOS-6440] - left-behind namespace entry with \"Container #0 not found\", for deleted file\n* [EOS-6445] - Repair failed due to unscanned replica\n* [EOS-6456] - Converter uses filename internally, fails on '?' '&'\n* [EOS-6457] - drainfailure: wrong (=empty) MGM+FST1 checksum prevents drain\n* [EOS-6460] - Race condition in EOS set ACL\n* FST: Disable individual stripe checksums for RAIN files until existing\n  performance limitations are addressed.\n\nImprovement\n------------\n\n* [EOS-6100] - \"file adjustreplica\" says \"No such file or directory\", should say \"file already tracked\"\n* [EOS-6257] - RFE: \"quota ls\" without need for 'adm' membership or 'q' ACL\n* [EOS-6313] - RFE: auto-retry draining for files with \"Operation expired\"\n* [EOS-6396] - One replica to be fixed by fsck best effort\n* [EOS-6446] - fsck best effort fix one replica existing when mgm believes is xs 0\n* [EOS-6455] - MGM: WFE - prepare - don't update the requestID if it does not change\n* [EOS-6454] - MGM,FST: Add jwt to grpc WFE calls\n\nNew Feature\n------------\n\n* MGM: Add support for space level extended attributes\n\n\n``v5.3.15 Diopside``\n====================\n\n2025-06-23\n\nImprovements\n------------\n\n* [EOS-6434] - MGM: PROPFIND - Display D flag if ACL forbids deletion but user is owner\n* [EOS-4720] - FST: Return EAGAIN instead of EBUSY in case a file is opened for writing via HTTP\n* [EOS-6436] - MGM: Acl - Log the ACL modification that took place\n* [EOS-6432] - MGM: Remove unnecessary if statement\n\n\n``v5.3.14 Diopside``\n====================\n\n2025-06-10\n\nBug\n----\n\n* FST: Fix memory leak when scanning RAIN files\n* FST: Fix possible crash (ABRT) when wrong type of object is being printed\n* FST: Fix crash when scanning RAIN files with less than the expected number of stripes\n* [EOS-6423] - Avoid creating orphan leftovers on open with create flag fails\n\n\n``v5.3.13 Diopside``\n====================\n\n2025-05-26\n\nBug\n----\n\n* FST: Fix undefined behaviour due to order of evaluation of the method\nparameters which was leading to files not having the checksum value set.\n\n\n``v5.3.12 Diopside``\n====================\n\n2025-05-21\n\nNote\n-----\n\n* Update eos-xrootd dependency to version 5.8.2 which matches xrootd-5.8.2\nplus an important fix for missing responses in the XRootD client.\n\n\nBug\n----\n\n* [EOS-6316] - Crash when handling Report regex\n* [EOS-6406] - Fix eoscta report log formatting\n* [EOS-6408] - Potential mutex deadlock while issuing eos df command\n* [EOS-6409] - rapid \"rep_missing_n\" increase\n\nImprovement\n-----------\n\n* [EOS-6300] - Improve RAIN scanning load by using full stripe checksums or other mechanisms\n* [EOS-6407] - Add possibility to recover QDB backup in raft-mode\n* [EOS-6414] - Use adler32 for stripe checksum\n* Improved namespace locking for bulk file deletion with recycle-bin policies\n\n\n``v5.3.11 Diopside``\n====================\n\n2025-05-07\n\nBug\n----\n\n* [EOS-6042] - QDB stuck in publishing\n* [EOS-6358] - MGM: Null group in FsView::mGroupView (segfault in heartbeat check)\n* [EOS-6360] - Removal of xattr not possible via gRPC\n* [EOS-6361] - Propagate update to FSTs when symkey is update on the MGM\n* [EOS-6379] - RAID5 requires 6 stripes\n* [EOS-6383] - eoscp PIO open mode does not work with larger block sizes\n* [EOS-6398] - [MGM] A user should be able to \"rename\" a file they own regardless of !d ACL\n\nNew Feature\n------------\n\n* [EOS-6386] - Add thread names where relevant\n* [EOS-6392] - Force overriding an existing symlink\n\nImprovement\n------------\n\n* [EOS-6359] - On the HTTP interface do not return HTML/CSS formatted\n* [EOS-6363] - Clean up old removed nodes from the global config map\n* [EOS-6399] - MGM: Use ADM_UID and ADM_GID\n\n\n``v5.3.10 Diopside``\n====================\n\n2025-04-07\n\nNote\n-----\n\n* Update eos-xrootd/xrootd dependency to version 5.8.0\n\n\nBug\n----\n\n* [EOS-6356] - Mapping: constant hit of LDAP for secondary accounts\n* [EOS-6364] - [MGM] EOS ACL recursive settings breaks ACLs already set\n\n\n\nNew Feature\n------------\n\n* [EOS-6368] - Implement eos-ports-block and eos-ports-reset-default\n* [EOS-6370] - https gfal-ls with redirector does not work\n* [EOS-6371] - Add gRPC support for MGM/FST - CTA Frontend protobuf communication\n\n\n``v5.3.9 Diopside``\n====================\n\n2025-03-06\n\nBug\n----\n\n* [EOS-6330] - HEAD requests are not executed on FSTs\n* [EOS-6348] - GRPC: set attribute does not honor the recursive flag\n\nNew Feature\n------------\n\n* [EOS-6349] - Support qclient persistency layer type in config/ns output\n\nImprovement\n-----------\n\n* [EOS-6331] - Print start and stop of file sync operations in the FST log\n* [EOS-6353] - RFE: GRPC with TLS but _not_ certificate auth\n\n``v5.3.8 Diopside``\n====================\n\n2025-03-04\n\nBug\n----\n\n* [EOS-6217] - eosxd looping in async open during write recovery\n* [EOS-6326] - fusex: crash in eosxd (fusex) around Proxy::Factory\n* [EOS-6347] - Conversion policies should better handle N/A file systems\n* MGM: Improvements to the geotree update process when running without MQ.\n* MGM/FST: Fix fs registration bug when running with MQ due to the deletion\n  of the shared hash object.\n\n\n``v5.3.7 Diopside``\n====================\n\n2025-02-26\n\nBug\n-----\n\n* [EOS-6339] - Do not abort if a configuration entry is wrong\n\nNew Feature\n-------------\n\n* Add a very basic eos-diagnostic-tool\n\n``v5.3.6 Diopside``\n====================\n\n2025-02-24\n\nBug\n-----\n\n* [EOS-6338] - Gateway REST API hangs on p2\n\n``v5.3.4 Diopside``\n====================\n\n2025-02-17\n\nBug\n-----\n\n* [EOS-6327] - MGM: MGM crash in eos::common::FileSystem::getCoreParams(this=0x0)\n\n``v5.3.5 Diopside``\n====================\n\n2025-02-21\n\nBug\n----\n\n* [EOS-6332] - MGM crash in ReplicationTracker\n* [EOS-6333] - GeoScheduler views are not always updated with no-MQ\n* [EOS-6334] - MGM slave tries to save Iostat configuration\n* [EOS-6336] - Possible FST deadlock on the mFsMutex\n* [EOS-6337] - Memory leak when handling HTTP chunk uploads\n\n\n``v5.3.4 Diopside``\n====================\n\n2025-02-17\n\nBug\n----\n\n* [EOS-6327] - MGM crash in eos::common::FileSystem::getCoreParams(this=0x0)\n\n\n``v5.3.3 Diopside``\n====================\n\n2025-02-14\n\nBug\n-----\n\n* [EOS-6243] - MGM stuck on EOSHOME-I04 (no reply to \"ns stat\")\n* [EOS-6247] - non-removable \"eos access\" rule\n* [EOS-6277] - FS error status not reset at successful boot\n* [EOS-6317] - Starting conditional EOS services: Too few arguments.\n* [EOS-6318] - _access lines >52% of xrdlog.mgm\n* [EOS-6322] - Regression in CTA archiving\n* [EOS-6324] - eos find on / fails\n* [EOS-6325] - MGM crash around DrainFs::UpdateFinishedJob\n* [EOS-5992] - RFE: faster MGM graceful restart (\"systemctl restart eos@mgm\")\n\nNew Feature\n-------------\n\n* [EOS-6310] - MGM: HTTP - Allow users to overwrite eos.app tag via HTTP path opaque query\n\n\n``v5.3.2 Diopside``\n====================\n\n2025-02-10\n\nBug\n----\n\n* MGM: Fix possible deadlock during draining and fix drain counters\n\n\n``v5.3.1 Diopside``\n====================\n\n2025-02-06\n\nNote\n-----\n\n* Update eos-xrootd/xrootd dependency to version 5.7.3\n* Update eos-grpc-gateway dependency to version 0.2.0\n\n\nBug\n----\n\n* [EOS-6269] - e-group membership does not seem to synchronize\n* [EOS-6279] - GRPC: honor the \"app\" attribute on upload and setAttr\n* [EOS-6282] - \"eos whoami\" abort()s\n* [EOS-6294] - eos: ipc socket protection from user crafted input\n* [EOS-6306] - FST keeps deleted SharedHash obj in memory\n* [EOS-6311] - file read handle caching used for full file http GET\n* [EOS-6314] - SIGUSR2 overwrites stacktraces\n\nImprovement\n------------\n\n* [EOS-6182] - GRPC: extend all requests to improve traceability\n* [EOS-6248] - Persist last run of inspector\n* [EOS-6271] - RFE: log \"banned\" identity\n* [EOS-6288] - RFE: align GRPC to other clients when dealing with hardlinks and tombstones\n* [EOS-6301] - RFE: \"eos find --purge atomic\" should bypass recycle bin\n* [EOS-6303] - Clients.log: review logline \"::open   acl= r= w= wo= egroup= shared= mutable= facl=\"\n\n\n``v5.3.0 Diopside``\n====================\n\n2024-12-03\n\nBug\n----\n\n* [EOS-4297] - mkdir in CLI does not throw EEXIST\n* [EOS-5012] - \"recycle config –lifetime\" only accepts value in seconds\n* [EOS-5266] - Wrong password file sends eos-ns-inspect into an endless error loop\n* [EOS-5307] - recycle bin purging cannot delete files with '->' in the name\n* [EOS-5748] - TPC job timeout can corrupt the RAIN stripes it should recover\n* [EOS-5847] - FST bootfailures (due to race condition?)\n* [EOS-5909] - high rate of CRIT: \"Attempted to add file with name..while a different file exists already there.\"\n* [EOS-5936] - quarkdb-validate-checkpoint aborts when opening \"too many\" .sst files\n* [EOS-5940] - MGM lockup for several minutes (but recovered)\n* [EOS-5950] - Undrainable \"cannot retrieve file meta data\"-files\n* [EOS-6014] - WIP: Inconsistencies between old and new find\n* [EOS-6031] - several eosViewRWMutex \"locked\" episodes after MGM restart\n* [EOS-6042] - QDB stuck in publishing\n* [EOS-6118] - \"eos fs mv\" between FSTs should keep existing \"group\"\n* [EOS-6126] - Recovery OpenAsync cannot open file anymore in eosxd\n* [EOS-6128] - Files written with UTF8 characters when not allowed\n* [EOS-6144] - Filenames with a special word break the EOS CLI\n* [EOS-6146] - undrainable \".sys.a\" files (wrong checksum), possibly after \"atomic\" upload from CERNBox\n* [EOS-6152] - Find for path that contains symlink fails\n* [EOS-6153] - fs boot command remove the default disk sync flag\n* [EOS-6155] - Touch should NOT require 10737418240 bytes as booking size\n* [EOS-6158] - Drain race condition leaving files in the tracker\n* [EOS-6173] - Corrupted file entries after namespace failover\n* [EOS-6178] - Misleading error message \"Invalid argument\" for command eos cp\n* [EOS-6179] - Cannot remove gid membership via eos vid rm membership\n* [EOS-6181] - eos -j JSON format changed\n* [EOS-6187] - Some 0-length files are not reported as being on disk\n* [EOS-6189] - [Acl] Recursive setting of ACL stops if at least one _attr_set() failure happens on a directory\n* [EOS-6191] - Silent fail when removing file with weird characters\n* [EOS-6192] - eos ls can not display files containing ampersand characters\n* [EOS-6195] - [FST] Write recovery - Avoid deleting a file that successfully got written during the write recovery transfer\n* [EOS-6198] - MGM - Globbing does not properly work\n* [EOS-6202] - eos file tag not working with fid:/fxid:\n* [EOS-6204] - SIGUSR1 stacktraces (/var/eos/md/stacktrace.TIME) should not be world-writeable\n* [EOS-6205] - FUSEX: timing-related access issue (initial \"No such file or directory\" (Kerberos, ACRON)\n* [EOS-6211] - fst segfault or hang, async close triggered during XrdFstOfsFile destructor\n* [EOS-6217] - eosxd looping in async open during write recovery\n* [EOS-6220] - Balancing should take into consideration the FileSystem configstatus\n* [EOS-6233] - MGM stuck on EOSHOME-I00 for 8min\n* [EOS-6234] - Persist redirect access configuration\n* [EOS-6235] - [MGM] Potential deadlock on rename during quota nodes fetch\n\nNew Feature\n------------\n\n* [EOS-5648] - FSCK: Contemplates files (and containers!?) that are detached from the namespace tree\n* [EOS-6165] - Limit number of staging requests allowed on EOSCTA\n* [EOS-6201] - [MGM] Tape REST API - Implement \"default\" targeted metadata handling\n* [EOS-6256] - MGM/FST: Adding retry mechanism for failed CTA Frontend DNS resolution\n\nTask\n-----\n\n* [EOS-6132] - HTTP - Return 424 \"Failed dependency\" for files stored on tape with no disk copy\n* [EOS-6170] - Push EL9 docker images to registry\n* [EOS-6180] - [eoscp] Preserve file' creation timestamp with --preserve option\n* [EOS-6200] - MGM - HTTP Take into account OpenWriteCreate limit\n* [EOS-6228] - [FST] HTTP - Add pmark.appname to adapt with the new scitags specifications\n\nImprovement\n------------\n\n* [EOS-3064] - QuarkDB: use common logging format, incl human-readable timestamps\n* [EOS-3319] - Drop usage of rand() throughout eos\n* [EOS-3538] - Add detection of files in \"deletion limbo\" to eos-ns-inspect\n* [EOS-3601] - Remove stdOut, stdErr and retc variables from IProcCommand interface\n* [EOS-4584] - RFE: \"eos acl --list\" to return both 'user' and 'sys' ACLs by default, allow specifying both\n* [EOS-4640] - eos-ns-inspect force exit when crosstalk errors happen\n* [EOS-5078] - eos member command argument check\n* [EOS-5310] - Shard conversion files in the top level `/eos/.../proc/conversion/` directory\n* [EOS-5311] - Reduce ConverterDriver dependency on QDB and improve performance\n* [EOS-5639] - Add file metadata to file deletion requests in eosreport\n* [EOS-5726] - \"vid gateway add/remove\" and \"vid ls\" output format(s)\n* [EOS-5828] - Propagte number of files/dirs (treeCount)\n* [EOS-5846] - \"rename\" (between directories) should honour \"!d policy\" (others?)\n* [EOS-5994] - faster shutdown of \"recycler server\"\n* [EOS-5997] - faster shutdown after \"finalizing namespace views\": gOFS->namespaceGroup.reset()\n* [EOS-6000] - Add 'paranoid' repair option to FSCK\n* [EOS-6093] - Add ns command to display the list of tracked files\n* [EOS-6123] - RFE: do not \"recycle\", \"drain\", \"balance\" atomic files - just delete, avoid creating them\n* [EOS-6127] - FSCK repair besteffort for MGM checksum 0 and only one replica\n* [EOS-6130] - RFE: metric for NS caches \"hit rate\"\n* [EOS-6137] - FST slow boot: heavy stat() from eos::fst::FmdAttrHandler::ResetDiskInformation ?\n* [EOS-6188] - NS Locking opt - Refactor ContainerAccounting's queue for update to avoid deadlocks\n* [EOS-6196] - RFE: allow to turn off \"globbing\"\n* [EOS-6206] - eos archive should handle retries for various CTA failures\n* [EOS-6215] - RFE: do no require \"sudoer\" role for internal components+already-privileged accounts\n* [EOS-6231] - Remove old Recycle implementation\n* [EOS-6236] - Add eos space config rm command\n* [EOS-6249] - high-rate logs: FuseServer::Clients::RefreshEntry\n* [EOS-6250] - high-rate logs: ::ProcessReq msg=\"normalize hdr\"\n* [EOS-6258] - high-rate logs: HttpHandler::HandleRequest() header logging\n* [EOS-5985] - Improve eos rmdir error message\n\n\n``v5.2.28 Diopside``\n====================\n\n2024-10-17\n\nBug\n----\n\n* [EOS-6065] - MGM memory increase/leak (EOSHOMEs)\n* [EOS-6217] - eosxd looping in async open during write recovery\n\n\n``v5.2.27 Diopside``\n====================\n\n2024-10-01\n\nNote\n-----\n\n* This release is targeted for the CTA use-case as it's built with eos-xrootd/xrood 5.7.1\n  that contains some HTTP header passing functionality required for CTA.\n* Built with eos-xrootd/xrootd 5.7.1\n\n\n``v5.2.26 Diopside``\n====================\n\n2024-10-01\n\nBug\n----\n\n* [EOS-6205] - FUSEX: timing-related access issue (initial \"No such file or directory\" (Kerberos, ACRON)\n* [EOS-6207] - eos fusex crash\n* [EOS-6211] - fst segfault or hang, async close triggered during XrdFstOfsFile destructor\n\nNew feature\n------------\n\n* [EOS-6200] - MGM - HTTP Take into account OpenWriteCreate limit\n\n\n``v5.2.25 Diopside``\n====================\n\n2024-07-05\n\nNote\n----\n\n* This EOS release is based on eos-xrootd-5.6.11 which itself bring important fixes like\n  - memory leaks in the XRootD python bindings\n  - fixes to crashes seen in production with EOS etc.\n\nBug\n----\n\n* [EOS-6087] - [eoscp] Intermittent segmentation faults in LHCb datamovers\n* [EOS-6155] - Touch should NOT require 10737418240 bytes as booking size\n* [EOS-6172] - man eos-ls wrong formatting\n* [EOS-6197] - Report: Undefined behavior in constructor if sec.host is an empty string (deletion)\n* [EOS-6126] - Recovery OpenAsync cannot open file anymore in eosxd\n\n\n``v5.2.24 Diopside``\n====================\n\n2024-05-23\n\nBug\n---\n\n* [EOS-6112] - Remove reliance on 'errno' from _dropallstripes() and other functions MGM(CTA)\n* [EOS-6148] - Too many levels of symbolic links unexpectedly reported on eosxd mounted fs\n\nNew Feature\n------------\n\n* [EOS-6150] - Print archive metadata in eoscta report MGM(CTA)\n* Add new eos-mgm-monitoring package containing a series of helper scripts for monitoring.\n\nImprovement\n------------\n\n* [EOS-6139] - MGM - HTTP GET issues 2 consecutive stats instead of only one\n\n\n``v5.2.23 Diopside``\n====================\n\n2024-04-30\n\nNote\n----\n\n* Update eos-xrootd dependency to 5.6.10 - this version includes important\n  optimizations for the use of OpenSSL 3.\n\nBug\n----\n\n* [EOS-5972] - rising \"HB is stuck\" time, apparent deadlock wait_upstream/mdcflush\n* [EOS-6109] - Rename - Deadlock with concurrent renames\n* [EOS-6120] - deadlock during EosFuse::mkdir\n\nImprovement\n------------\n\n* ALL: Many compilation warning fixes\n\n\n``v5.2.22 Diopside``\n====================\n\n2024-04-09\n\nBug\n----\n\n* [EOS-6116] - FUSEX: fix eosxd callback handler when a file is moved on top of an existing file\n* [EOS-6115] - FUSEX: fix invisible directories if the name had been put into the ENOENT cache\n* [EOS-6111] - FST: mark readV errors as read IO errors in the report log\n* [EOS-6110] - MGM: fix loop in devices thread in non-master MGMs\n* FST - fix interface speed reading\n\n\nImprovement\n------------\n\n* [EOS-6117] - FST: ErrorReports are suppressed on FSTs when over 4 Hz to 1Hz + marker\n* [EOS-6114] - FUSEX: eosxd and MGM share the same assumption, that as an owner of directory you can delete a file of another person even if !d was specified for the group\n\n\n``v5.2.21 Diopside``\n====================\n\n2024-03-25\n\nBUG\n\n* [EOS-6105] - fix credential validation in ALMA9 container under chroot environments\n\n``v5.2.20 Diopside``\n====================\n\n2024-03-21\n\nBug\n---\n\n* [EOS-6091] - Update PersistentSharedHash before publishing updates\n* [EOS-6101] - fs rm no longer sends a notification to the FST\n\n\n``v5.2.19 Diopside``\n====================\n\n2024-03-12\n\n\nNote\n----\n\n* Update dependency to xrootd/eos-xrootd 5.6.9\n\nBug\n----\n\n* [EOS-6085] - EOSPUBLIC mgm crash during BroadcastDeletionFromExternal in rename\n* [EOS-6088] - MGM aborts with \"what():  std::bad_alloc\" under eos::mgm::FuseServer::Caps::BroadcastDeletionFromExternal\n\n\n``v5.2.18 Diopside``\n====================\n\n2024-03-07\n\nBug\n----\n\n* [EOS-6075] - [eoscp] memory leaks and context errors\n* [EOS-6078] - eos archive segv in xrootd prepare\n* [EOS-6079] - Credential validation fails in chroot container with non local jail lookup\n* [EOS-6080] - \"eos find --purge atomic\" can lock up namespace\n* [EOS-6081] - \"eos find --purge atomic\" can cause slow restarts (FSCK loads one big hash at startup)\n* [EOS-6082] - MGM crash from early \"eos ns stat\" command (under eos::common::ThreadPool::GetInfo)\n* [EOS-6084] - \"Scheduler is not yet initialized\" from early setDiskStatus() (possible: drain?)\n\n\nNew Feature\n------------\n\n* [EOS-6045] - Monitor number or kworker processes with 'eos node ls --sys'\n\n\nImprovement\n------------\n\n* [EOS-5185] - FUSEX can not write to logical quotas <= 5GB (hardcoded limit)\n* [EOS-5835] - MGM: remove internal redirect for \"/\" to port 8443\n\n\n``v5.2.17 Diopside``\n====================\n\n2024-02-29\n\nNote\n----\n\n* Update dependency XRootD/eos-xrootd to 5.6.8\n\n\nBug\n----\n\n* [EOS-6061] - Disk drain failure, replicas are on disk, but adjustreplica fails to replicate\n* [EOS-6062] - MGM: \"fs mv\" randomly \"forgets\" filesystems\n* [EOS-6064] - MGM stuck (namespace locking)\n* [EOS-6066] - eos cp -r (recursive copy) uses \"find\", does not work on redirection (?)\n* [EOS-6070] - FST aborts with \"what():  basic_string::_S_construct null not valid\" under eos::fst::ScanDir::CheckFile()\n* [EOS-6074] - Crash in FlatScheduler\n\nImprovement\n------------\n\n* [EOS-6048] - RFE: FST should not \"check for Fmd xattr conversion\" at boot\n\n\n``v5.2.16 Diopside``\n====================\n\n2024-02-16\n\nBug\n----\n\n* [EOS-6051] - MGM: fix crash in FSScheduler caused by edgecases at boot time\n\n\n``v5.2.15 Diopside``\n====================\n\n2024-02-15\n\nBug\n----\n\n* [EOS-6044] - FUSEX: fix 0-pointer access into data object map - fixes EOS-6044\n* [EOS-6046] - MGM: flat scheduler know honours configuration changes on filesystems immediately\n\nNew Feature\n-----------\n\n* MGM - return EBUSY and HTTP::CONFLICT when opening a file locked via the xattr interface (collaborative editing)\n\n  ``v5.2.14 Diopside``\n====================\n\n2024-02-13\n\nBug\n----\n\n* [EOS-6009] - FUSEX: don't overwrite FILE:/!tmp locations as KRB5 default location\n* NS: Catch exception in FutureVectorIterator destructor\n\n\n``v5.2.13 Diopside``\n====================\n\n2024-02-12\n\nBug\n----\n\n* [EOS-3898] - EOS permissions system incorrectly requires an explicit '+u' privilege for the root user\n* [EOS-4763] - ACL set argument 'foo:foo:+d' does not work\n* [EOS-4796] - Not consistent behaviour when setting user.acl with attr set and acl --user\n* [EOS-6009] - FUSEX: fix retrieval of default kerberos crednetial location if not under FILE:/tmp/\n* [EOS-6013] - FUSEX: fix hash function used to cache connections to distinguish container credentials using identical internval paths\n* [EOS-6016] - MGM crash during shutdown in eos::mgm::ConverterDriver::ScheduleJob()\n* [EOS-6025] - MGM: accumulating \"atomic\" version files (from sync client) if out of volume quota\n* [EOS-6029] - MGM (subprocess?) crash in qclient::FollyFutureHandler::stage()\n* [EOS-6038] - MGM misses broadcast message to deal with renames\n* MGM: fix 'find --fileinfo --cache'\n* FST: fix publishing of 'xrootd' version in 'node ls --sys'\n* CONSOLE: fix broken 'eos report' for reads\n\n\nNew Feature\n------------\n\n* [EOS-5614] - FUSEX: bypass deletion through recycle bin, if a file is deleted while still open for writign\n* [EOS-5879] - [eoscp] Add the possibility to see the version of the command\n* [EOS-5956] - Implement default XRootD Attribute functions for xrootd prefixes\n* [EOS-6040] - GRPC: implement reycle bin listing with date/index filter\n* FUSEX: code refactoring allowing to reuse functionality of eosxd authentication in eoscfsd\n* CFSD: adding POSIX passthrough filesystem implementation packaged in new RPM eos-cfsd\n\nImprovement\n------------\n\n* [EOS-2373] - Inconsistent handling of linked attributes in attr_ls and attr_get\n* [EOS-5614] - Fuse skip recycle bin for known broken files\n* [EOS-5717] - [eos-archive] Review the workflow + files with no checksum on destination make the tool crash\n\nReverted\n--------\n\n* MGM/CONSOLE: reverted removing 'eos old find' implementation\n\n\n``v5.2.12 Diopside``\n=========================\n\n2024-02-11\n\nBug\n---\n\n* FST: Fix overflow when reading file larger than 4GB during rain-check\n* FST: Fix reading of the network speed value\n* MGM: avoid parallel computation of the currently used physical space and cache for 2 minutes\n* REVERT: COMMON: RWMutex: lock the mutex name map before finding items\n\n\n``v5.2.11 Diopside``\n=========================\n\n2024-02-06\n\nNote\n----\n\n* Update eos-xrootd/xrootd dependency to 5.6.7\n\nBug\n----\n\n* [EOS-6028] - EOS: ACL command help displays wrong option\n\n\n``v5.2.10 Diopside``\n=========================\n\n2024-02-02\n\nBug\n----\n\n* [EOS-6022] - mkdir -p does not broadcast properly to eosxd clients\n\n\n``v5.2.9 Diopside``\n=========================\n\n2024-02-02\n\nBug\n----\n\n* [EOS-6012] - Fix crash in eos::mgm::ConversionJob::Merge() when logging error message\n\n\n``v5.2.8 Diopside``\n=========================\n\n2024-01-29\n\nBug\n----\n\n* MGM: Add legacy find command implementation for old clients.\n\n\n``v5.2.7 Diopside``\n=========================\n\n2024-01-26\n\nNote\n----\n\n* Update eos-xrootd/xrootd dependency to 5.6.6\n\nBug\n---\n\n* [EOS-5770] - \"eos node ls --sys\" - messed-up formatting (newline after \"sockets\"?)\n* [EOS-5877] - MGM crash while registering new FST\n* [EOS-5934] - FST \"failed to parse metadata info\" for existing filenames prevents EA conversion\n* [EOS-5949] - undrainable \"fuse::needsflush\" file - outdated \"mgmsize\" does not match on-disk size\n* [EOS-5986] - Add support for long filename (> 2kB) for Getfmd requests\n* [EOS-5987] - RWMutex: concurrent modification of the Mutex Name map\n* [EOS-5988] - MGM: concurrent modification of sync Time Accounting class\n* [EOS-5989] - concurrent modification of RWMutex at configure stage\n* [EOS-5993] - MGM: do not log SYMKEY on start\n* [EOS-5998] - FST crash under eos::fst::RainMetaLayout::Open()\n* [EOS-5999] - Connection Idle timeouts create broken FUSE replicas\n* [EOS-6006] - EOS MGM lockup/unresponsive on EOSPROJECT-I00\n\nNew Feature\n-----------\n\n* [EOS-5970] - Implement scitags in EOS for HTTP transfers\n* [EOS-5971] - Add RX/TX errors and dropped pack errors to FST monitoring\n* [EOS-6010] - CLI: Remove eos oldfind from the console\n\nTask\n----\n\n* [EOS-6003] - eos: sched ls output doesn't list all disks\n* [EOS-6004] - eos: scheduler: active status not taken into consideration\n\nImprovement\n-----------\n\n* [EOS-5744] - Forbid archival of directories that contain symlinks\n* [EOS-5745] - Forbid archival of directories with 0 size files\n* [EOS-5982] - Skip checksumming files with FUSE\n* [EOS-5990] - Add FSCK reset\n\n\n``v5.2.6 Diopside``\n==========================\n\n2024-01-15\n\nBug\n---\n\n* [EOS-5977] - NS: Double check md object is not null before constructing md locked object\n\n\n\n``v5.2.5 Diopside``\n==========================\n\n2024-01-09\n\nBug\n---\n\nSPEC: Fix missing target when building in client mode only\n\n\n``v5.2.4 Diopside``\n==========================\n\n2023-12-18\n\nNote\n----\n\n* Update eos-xrootd/xrootd dependency to 5.6.4\n* Update eos-rocksdb dependency to 8.8.1\n\n\nBug\n----\n\n* [EOS-5657] - Overreplication in EC preventing reading files\n* [EOS-5937] - Fix 'EOS command 'evict'/'stagerrm' not deleting files on FST'\n* [EOS-5965] - FUSEX: TSAN data race on setting pid in shared mdx object\n* CONSOLE/MGM: Fix EOS command evict/stagerrm not deleting files on FSTs [CTA]\n\nNew Feature\n------------\n\n* [EOS-5511] - suggestion: rate limit on errors\n\n\nImprovement\n------------\n\n* [EOS-5718] - Fsck request to repair overreplicated files in EC\n* [EOS-5919] - Disable fallocate on FSTs when filesystem != XFS by default\n\n\n``v5.2.3 Diopside``\n==========================\n\n2023-12-13\n\nBug\n----\n\n* FST: Http chunk upload - avoid infinite loop for misbehaving clients\n\n\n``v5.2.2 Diopside``\n==========================\n\n2023-11-08\n\nBug\n----\n\n* MGM: Make sure token information is passed to all namespace operations\n* MGM: Avoid re-entrant lock in space ls\n* SPEC: Add eos-grpc-gateway as an explicit requirement\n\n\n``v5.2.1 Diopside``\n==========================\n\n2023-11-06\n\nBug\n----\n\n* [EOS-5849] - MGM crash, possibly around eos::QuarkHierarchicalView::getUriInternal()\n* [EOS-5858] - FlatScheduler: groups are not retried\n* [EOS-5861] - MGM crash (corrupted free memory?)\n* [EOS-5862] - Files with strange state after editing on two places at the same time via FUSE\n* [EOS-5866] - Invalid NS entry when a file is renamed on top of a hard-link with recycle bin enabled\n* [EOS-5872] - NS: IFileMD::unlinkLocation() takes a read lock instead of a write lock\n* [EOS-5895] - MGM memory increase (EOSHOMEs)\n* [EOS-5902] - XrdHttp access throws 500 when file name contains a '#'\n* [EOS-5903] - Left over fst.ioping.XXXX files on FSTs\n* [EOS-5904] - Fix unsafe modification in Qdb Master logging\n* [EOS-5906] - 5.2 FST don't start because of benchmark files irritating LevelDB check code\n\nImprovement\n------------\n\n* [EOS-5792] - Document the possibility of moving fs between nodes in the help and the eos official documentation\n* [EOS-5894] - MGM memory increase with aggressive parameters for balancing\n\n\n``v5.2.0 Diopside``\n==========================\n\n2023-10-10\n\nNote\n----\n\n* Update dependency to eos-xrootd-5.6.2 that matches XRootD-5.6.2.\n* New eos-grpc-1.56.1 dependency that obsoletes any previous eos-protobuf3 packages.\n\n\nBug\n----\n\n* [EOS-5429] - [TAPE REST API] Modify STAGE polling (GET) logic to take into account files not queued on CTA\n* [EOS-5680] - MQ overloaded when deleting a large number of EC files\n* [EOS-5687] - CtaUtils: GCC12 FTBFS\n* [EOS-5694] - chunked upload fails on EOS5 + XrdHTTP\n* [EOS-5699] - request retries discarded on RAIN layout\n* [EOS-5700] - readv errors ReedSLayout claims corrupted but file is ok\n* [EOS-5704] - RAIN layouts don't enable XrdIo read-ahead\n* [EOS-5732] - removexattr fails with ENOENT when trying to remove any of the extended attributes from a created file\n* [EOS-5784] - /etc/cron.d/eos-reports : do not use \"bc\"\n* [EOS-5791] - Force physical space info for xrdfs spaceinfo command not working\n* [EOS-5798] - FST abort() on \"no manager name\" shutdown: \"terminate called without an active exception\"\n* [EOS-5825] - eosxd heartbeat stuck, duration slowly rising (maybe mdcflush deadlock)\n* [EOS-5826] - eosxd rising heartbeat time, suspected mdx left locked by exited thread\n* [EOS-5832] - FUSEX crash around cap::capx::lifetime(this=0x0)\n* [EOS-5842] - FUSEX: throw in data::datax::attach\n* [EOS-5843] - Wrong quota checks when recycling directories with EC files\n* [EOS-5855] - Cannot remove access limits already introduced by username\n\nNew Feature\n------------\n\n* [EOS-5613] - Store in xattr who deleted a file\n* [EOS-5716] - [eoscp] Create JSON output in addition to the text output\n* [EOS-5857] - Add support for HTTP REST API via grpc-gateway\n\n\nTask\n----\n\n* [EOS-5530] - Send fid as string to CTA\n* [EOS-5856] - Libmicrohttpd support disabled by default\n\nImprovement\n------------\n\n* [EOS-5537] - RS layouts don't use read-ahead anymore\n* [EOS-5703] - Modifications to eos `evict`/`stagerrm` command\n* [EOS-5707] - eos-config-inspect dump: allow to choose a particular config backup\n* [EOS-5734] - eos recycle -m, revert usage of underscore on keys\n* [EOS-5739] - RFE: honour sys.app.lock also when serving flock operations via FUSE\n* [EOS-5779] - EOS: server rpm upgrades shouldn't affect quarkdb\n* [EOS-5819] - Forbid quota set cli on recycle bin\n* [EOS-5831] - Add Birthtime vs Accesstime distributions to inspector output\n* [EOS-5840] - Add 'du' command to CLI\n\n\n``v5.1.30 Diopside``\n==========================\n\n2023-09-27\n\nBug\n---\n* [EOS-5834] - Corrected MGM Namespace mutex tracking\n\nNew feature\n-----------\n\n* MGM: add 'eos ns benchmark' command to run inside the MGM a multithreaded benchmark\n\n``v5.1.29 Diopside``\n==========================\n\n2023-09-14\n\nBug\n----\n\n* [EOS-5771] - HTTP transfers of a file with no disk replicas create a zero-length file\n* [EOS-5813] - Show physical space info for xrdfs spaceinfo query\n* [EOS-5818] - FST crash in eos::fst::FmdConverter::ConvertFS\n\nImprovement\n-----------\n\n* [EOS-5530] - Send fid as string to CTA\n* [EOS-5822] - Implement JSON output for eoscp command\n\n\n``v5.1.28 Diopside``\n==========================\n\n2023-09-01\n\nNew Feature\n-----------\n\n* [EOS-5803] - Introduce New groupbalancer engine - freespace which balances on\n  absolute freespace Additionally blocklisting groups is now supported in this\n  engine.\n\n``v5.1.27 Diopside``\n==========================\n\n2023-08-04\n\nNote\n----\n\n* Pin down the eos-grpc dependency package version to 1.41.0 to better control the update process in the future.\n\nBug\n---\n\n* [EOS-5763] - eosxd: occasional very large max-inode-lock-ms reported\n* [EOS-5776] - Blocked IO measurement can be wrong in case of multithreaded readers on same inode\n* [EOS-5768]: File write recovery can lead to file loss\n* FUSEX: put back md-cache auto-cleanup on umount, which was removed since 5.1.25\n\n\n``v5.1.26 Diopside``\n==========================\n\n2023-07-26\n\nBug\n---\n\n* FUSEX: protect against inserting md objects with ino=0\n* FUSEX: check the md err code of entries returned by the server before using\n* FUSEX: add sanity check to not dump a swapped-out meta-data object which is in the LRU list\n* FUSEX: avoid writing into swapped-out MD objects\n* FUSEX: remove dead code deleting old cache entries\n\n\n``v5.1.25 Diopside``\n==========================\n\n2023-07-20\n\nBug\n----\n\n* [EOS-5753] - Crash in LRU remove function\n* [EOS-5754] - cp -a gives \"preserving times for .. : Invalid argument\" - negative accesstime?\n* [EOS-5748] - MGM: Disable TPC timeout estimates as this can lead to corruption of RAIN\n  stripes for slow transfers - temporary workaround.\n\n\n``v5.1.24 Diopside``\n==========================\n\n2023-07-14\n\nBug\n----\n\n* [EOS-5652] - eosxd abrtd reports from lxplus\n* [EOS-5480] - eosxd crash under count() / metad::lookup() / EosFuse::lookup()\n* [EOS-5486] - eosxd crash with SIGABRT\n* [EOS-5667] - eosxd abtrd reports from lxplus705\n* [EOS-5668] - Input/output error on FUSE mount, client ok\n* FUSEX: don't return EFAULT with invalid statvfs responses\n* FUSEX: avoid some further concurrent access to md attr field\n\n\n``v5.1.23 Diopside``\n==========================\n\nBug\n----\n\n* [EOS-5695] - some Fsts not booting into EOS after upgrade to 5\n* [EOS-5696] - Allow 0-sized CTA files to be deleted from EOS namespace\n* [EOS-5699] - request retries discarded on RAIN layout\n\nNew Feature\n------------\n\n* [EOS-5697] - [eoscp] Add checksum comparison between source and destination\n\n\n``v5.1.22 Diopside``\n==========================\n\n2023-05-24\n\nBug\n----\n\n* COMMON: Serialize calls to setgrent/getgrent/endgrent since they are not thread-safe and can cause a crash\n\n\n``v5.1.21 Diopside``\n==========================\n\n2023-05-24\n\nBug\n----\n\n* COMMON: Fix handling of eos token when passed as HTTPS bearer authorization header\n\n\n``v5.1.20 Diopside``\n==========================\n\n2023-05-10\n\nThis release is based on eos-xrootd-5.5.10/xrootd-5.5.5\n\nBug\n---\n* This release updates to using eos-xrootd-5.5.10 which includes\na fix for a regression when higher fdlimits are needed\n\n\n``v5.1.19 Diopside``\n==========================\n\n2023-05-10\n\nThis release is based on eos-xrootd-5.5.9/xrootd-5.5.5\n\nBug\n---\n* MGM: Do special handling for HEAD requests\n\nImprovement\n------------\n* [EOS-5658] - support external host/port alias for FSTs\n\n\n``v5.1.18 Diopside``\n==========================\n\n2023-05-08\n\nBug\n----\n\n* SPEC: Fix dependency to point to eos-xrootd-5.5.9/xrootd-5.5.5\n\n\n``v5.1.17 Diopside``\n==========================\n\n2023-05-08\n\nBug\n---\n\n* [EOS-5515] - EC file with undrained stripes that looks fine\n* [EOS-5612] - Recycle bin setting change disables cleanup\n* [EOS-5633] - Eos inspector: Considers a space already deleted\n* [EOS-5601] - eos cp: Fix memory leaks in eos_roles_opaque\n* FUSEX: fix permission denied errors for slow MGM requests\n* FUSEX: fix ctime setting in eosxd3, enable write-back cache\n* FUSEX: fix blocked statistic output when backen-end waits for a flush\n\nImprovement\n------------\n* [EOS-5563] - add monitoring format to `eos fsck stat`\n* [EOS-5626] - Converter - Rain file failed to convert (100GB)\n* [EOS-5641] - Have Macaroons take into account vid VOMS mapping when determining client identit\n* DOC: refactor documentation for Diopside releases\n\n\n``v5.1.16 Diopside``\n==========================\n\n2023-04-04\n\nBug\n----\n\n* COMMON: Don't reset the current vid identity when handling KEYS mapping\n  unless we actually have a hit in the map. This was breaking the vid mapping\n  for gsi/http with voms extensions that have the endorsements field in the\n  XrdSecEntity populated and this was interpreted as a key.\n\n\n``v5.1.15 Diopside``\n=========================\n\n2023-03-27\n\n\nNote\n----\n\n* Update dependency to eos-xrootd-5.5.8 which also matches XRootD-5.5.4\n\nBug\n----\n\n* [EOS-5577] - MGM crash in eos::mgm::GrpcWncServer::RunWnc()\n* [EOS-5587] - jwt::decode might throw an exception\n* [EOS-5600] - eos group ls outputs wrong filled stats\n\n\nNew Feature\n------------\n\n* [EOS-5588] - Allow HTTPS gateway functionality to use key entries\n\nTask\n----\n\n* [EOS-5522] - Drain status stays in `expired` after setting fs in rw.\n* [EOS-5530] - Send fid as string to CTA\n\nImprovement\n-----------\n\n* [EOS-5578] - Balancer/Drainer/Recycler: reduce sleep info logging\n* [EOS-5592] - Disabling oauth did not actually disabled it\n\n\n``v5.1.14 Diopside``\n=========================\n\n2023-03-14\n\nBug\n----\n\n* [EOS-2520] - FST abort (coredump) on shutdown, \"EPoll: Bad file descriptor polling for events\"\n* [EOS-5554] - Deadlock while setting acls recursive\n\nNew Feature\n------------\n\n* [EOS-5571] - Add atime to eos-ns-inspect tool\n* [EOS-5573] - Show if namespace is locked-up\n* [EOS-5576] - MGM: fileinfo -j does not output the file' status\n\n\n``v5.1.13 Diopside``\n=========================\n\n2023-03-06\n\nBug\n----\n\n* [EOS-5546] - MGM: IoStat fprintf() stuck\n* [EOS-5555] - FST segfaults around qclient::QSet::srem\n* [EOS-5559] - EOS HTTP REST API - no JSON output if authentication is done with Bearer token\n\nNew features\n------------\n* [EOS-5561] - Create \"eos df\" command\n\n\n``v5.1.12 Diopside``\n=========================\n\n2023-02-28\n\nBug\n----\n\n* [EOS-5526] - User Sessions count seems to be wrong\n* [EOS-5534] - LRU should not walk down the recycle bin and apply policies\n* [EOS-5535] - LRU tries to delete all directories having an empty deletion policy\n* [EOS-5542] - Error when accessing directories with wildcards\n\nImprovement\n------------\n\n* [EOS-5536] - LRU code has still in-memory namespace code\n\n\n``v5.1.11 Diopside``\n=========================\n\n2023-02-15\n\n\nBug\n----\n\n* [EOS-5516] - Dangling files (possibly) after container is removed\n* [EOS-5520] - eos CLI group resolution changed - INC3372876\n* [EOS-5523] - eosxd recovery failing\n\nImprovement\n------------\n\n* [EOS-5524] - Allow https gateway nodes to provide x-forwarded-for headers\n\n\n``v5.1.10 Diopside``\n=========================\n\n2023-02-07\n\nNote\n----\n\n* Update dependency to eos-xrootd-5.5.7 which also matches XRootD-5.5.2\n\nBug\n----\n\n* [EOS-5386] - iostat reports are not processed fast enough\n\nImprovements\n------------\n\n* MGM: Make central balancer configurable at runtime\n* FST: Chunk fsck requests to at most 50k entries per request\n* MGM: enable hide-version also when heartbrate has been changed\n\n\n``v5.1.9 Diopside``\n=========================\n\n2023-01-24\n\n\nBug\n----\n\n* [EOS-5487] - sticky bit on version folders makes Recycler not able to clean the files on the recycle bin.\n* [EOS-5488] - New Year's crashes on all projects and homes\n* [EOS-5489] - PropFind fails when namespace mappings should apply\n* [EOS-5494] - eosxd looping when cleaning write queue\n* [EOS-5495] - FST crashing while doing LevelDB->ext_attr conversion on a (not) broken (enough) disk\n* [EOS-5498] - All 0 size files are marked as missing when using xattr fmd\n\n\nNew Feature\n------------\n\n* [EOS-5209] - Fsck removal should just move stripes to a quarantine directory\n\n\nImprovement\n------------\n\n* [EOS-5501] - Allow black and whitelisting of token vouchers (ids)\n\n\n``v5.1.8 Diopside``\n=========================\n\n2022-12-14\n\nNote\n----\n\n* Update dependency eos-xrootd-5.5.5\n* Includes an important fix for HTTP TPC PULL transfers.\n\nBug\n----\n\n* [EOS-5467] - Inspector aggregates results instead of resetting the current scan\n* MGM: Add regfree in FuseServer regex usage to avoid memory leak\n* MGM: Unlock the Access mutex when delaying a client to not get problems to get a write lock\n\n\nImprovement\n-----------\n\n* [EOS-5478] - Invert Stall logic to check first user limits and then catch-all rules\n\n\n``v5.1.7 Diopside``\n=========================\n\n2022-12-12\n\nBug\n----\n\n* [EOS-5474] - Conversion breaks files with FMD info in xattrs\n\nImprovement\n------------\n\n* [EOS-5469] - Allow to select secondary groups with kerberos authentication and implement AC checks for secondorary groups\n* [EOS-5471] - Add atime to EOS\n* [EOS-5458] - Setting a namespace xattr might fail for wopi\n\n\n``v5.1.6 Diopside``\n=========================\n\n2022-12-05\n\nBug\n----\n\n* [EOS-5467] - Inspector aggregates results instead of resetting the current scan\n\nImprovement\n------------\n\n* [EOS-5465] - Shoe FUSE application name in 'fusex ls'\n* [EOS-5466] - Add Stall / NoStall host lists to access interface\n\n\n``v5.1.5 Diopside``\n=========================\n\n2022-12-02\n\nBug\n----\n\n* MGM: Fix MGM crash when the balancer is configured\n\nImprovement\n-----------\n\n* [EOS-5452] - New metric: Provide I/O errors per transfer in report logs\n* [EOS-5453] - New metric: Namespace contention calculation in ns stat command\n* [EOS-5131] - RFE: honour XRD_APPNAME for xrdcp\n* [EOS-5444] - Provide number of stripes in the inspector command\n* [EOS-5454] - EOS inspector: Provide layout_id in the list output per fxid\n* [EOS-5455] - eos node ls monitoring - Improve sys.uptime value format\n* [EOS-5459] - MGM: avoid blocking cleanup ops while user mapping\n* [EOS-5464] - Have TPC transfers respect the client tpc.ttl value\n\n\n``v5.1.4 Diopside``\n=========================\n\n2022-11-22\n\nBug\n----\n\n* [EOS-5442] - eosxd crash (on shutdown) under ShardedCache destructor\n* [EOS-5446] - Failures in setting thread names\n\n\n``v5.1.3 Diopside``\n=========================\n\n2022-11-16\n\nBug\n----\n\n* [EOS-5162] - Setting ACL does not work when dir ends with whitespace\n* [EOS-5433] - GroupBalancer: crash when conversions are scheduled before Converter\n* [EOS-5436] - Origin Restriction does not work as expected\n* [EOS-5437] - Fix potential leaks in Mapping::getPhysicalIds\n\nNew Feature\n------------\n\n* [EOS-5145] - Extending lock support\n* [EOS-5438] - Don't stall clients when thread pool is exhausted and a rate limit is reached\n\nImprovement\n------------\n\n* [EOS-5231] - Allow eos attr set to operate on CIDs\n* [EOS-5344] - eos recycle -m: show inode used / max numbers\n* [EOS-5401] - Return the inode number in FMD responses for GRPC\n* [EOS-5412] - add qclient performance metrics on monitoring format.\n* [EOS-5413] -  QClient performance: have last 5m, last 1m, etc metrics\n* [EOS-5439] - Add eosxd3 to all builds when fuse3 is available and ship in the RPM\n\n\n``v5.1.2 Diopside``\n=========================\n\n2022-10-04\n\nBug\n----\n\n* [EOS-5399] - FST: Segfaults in FmdConverter\n* [EOS-5400] - FST crash in AccountMissing due to null Fmd object\n\nImprovement\n------------\n\n* [EOS-3297] - Print the deviation used for the group balancer\n\nNew features\n------------\n\n* MGM: Add implementation for central group balancer using TPC\n\n\n``v5.1.1 Diopside``\n=========================\n\n2022-09-15\n\nNote\n-----\n\n* Update dependency to eos-xrootd-5.5.1\n* eosd is now deprecated and there are no more RPM packages provided for it\n\nBug\n----\n\n* [EOS-5347] - EOS token not usable via eosxd\n* [EOS-5369] - Occasional error during eoscta test \"mismatch between requested fid/fsid and retrieved ones\"\n* [EOS-5371] - Fix crash of the MGM when listing container entries due to invalidated\n               iterators to the ContainerMap/FileMap objects.\n* FST: eos-xrootd-5.5.1 fixes a bug in XRootD related to async close functionality\n  where the FST would crash if it received another requests for a file which was in\n  the process of being closed.\n\nNew features\n------------\n\n* CTA: Enhance/extend EOS report messages for CTA prepare workflow\n\n\n``v5.1.0 Diopside``\n=========================\n\n2022-09-02\n\nNote\n----\n\n* This release comes with XRootD/eos-xrootd 5.5.0 as dependency\n\nBug\n----\n\n* [EOS-5377] - Unhandled exception in the GeoBalancer code\n* [EOS-5367] - Fix IoStat initialization when there is no prior data in QuarkDB\n* MGM: Fsck: correct the calculation of expected number of stripes in RepairFstXsSzDiff\n\n\nImprovement\n-----------\n\n* [EOS-5380] - Qclient: handle folly warnings\n* [EOS-5381] - Fix potential format overflows\n* [EOS-5378] - Fix compilation warnings\n* FUSEX: Add support for json statistics output\n\nNew features\n-------------\n\n* FST: Add support for storing file metadata info as extended attributes\n  of the raw files on disk rather than using the LevelDB on disk.\n  Disabled by default for the moment.\n\n\n``v5.0.31 Diopside``\n=======================\n\n2022-08-12\n\nBug\n----\n\n* FST: Properly detect HTTP transfers and skip async close functionality in\n  such cases\n* [EOS-5359] - use after free in fusex::client::info\n* [EOS-5358] - WNC GRPC unserialized global options\n\n\n``v5.0.30 Diopside``\n=======================\n\n2022-08-11\n\nBug\n---\n\n* [EOS-5355] - System ACLs evaluation overruling logic is incorrect\n\n\nNew Feature\n------------\n\n* [EOS-5342] - CREATE cta workflow not triggered when new file created using fusex - DELETE workflow is also missing\n\n\nImprovement\n-----------\n\n* [EOS-5343] - Better enforcement of the scattered placement policy\n\n\n``v5.0.29 Diopside``\n=======================\n\n2022-07-29\n\nBug\n----\n\n* Fix /usr/bin/python dependency on EL8(S) which is no longer provided by any package,\n  therefore we need to explicitly use /usr/bin/python3\n\n\n``v5.0.28 Diopside``\n=======================\n\n2022-07-26\n\nNote\n----\n\n* This version of EOS is based on an internal release of XRootD namely eos-xrootd-5.4.7\n\nBug\n---\n\n* [EOS-5336] - Lot of EOS FST crash (SIGSEGV) in the EOSALICE instance\n* [EOS-5308] - MGM: Potential double free in LDAP initialize\n* [EOS-5334] - LDAP connection socket leak\n* [EOS-5335] - MGM crash in Fileinfo.cc:97\n\n\n``v5.0.27 Diopside``\n=======================\n\n2022-06-30\n\n\nBug\n---\n\n* [EOS-5296] - FST segfault around XrdXrootdProtocol::Process2\n* [EOS-5314] - segfault around \"XrdCl::CopyProcess::CleanUpJobs\"\n* [EOS-5302] - Iostat domain accounting is broken\n* [EOS-5303] - Shared filesystem file registration feature\n* [EOS-5308] - MGM: Potential double free in LDAP initialize\n\nImprovement\n------------\n\n* [EOS-5317] - Crash in AssignLBHandler with asan\n* [EOS-5321] - Allow to define which errors the fsck repair thread works on\n* [EOS-5305] - Tape REST API - V1 with an option to deactivate STAGE\n\n\n``v5.0.26 Diopside``\n=======================\n\n2022-06-21\n\n\nNote\n----\n\n* XRootD: Based on eos-xrootd-5.4.5 which fixes a couple for important bugs\n  on the xrootd client side.\n\nBug\n----\n\n* [EOS-5302] - Iostat domain accounting is broken\n* [EOS-5303] - Shared filesystem file registration feature\n\nImprovements\n------------\n\n* MGM: Make fsck start up and shutdown more responsive\n* MGM: Add fsck repair procedure for m_mem_sz_diff errors\n\n\n``v5.0.25 Diopside``\n=======================\n\n2022-06-09\n\nBug\n----\n\n* [EOS-5278] - Segmentation fault around eos::mgm::GroupDrainer::scheduleTransfer\n* [EOS-5284] - GroupBalancer: spurious logs when no transfers can be scheduled\n* [EOS-5286] - Physical quota is not updated when we set EC conversion\n* [EOS-5288] - Wrong layout id after conversion operation leading to wrong physical size\n* [EOS-5218] - Infinite loop in XrdCl::XRootDMsgHandler::Copy\n* MGM: The initial behaviour of xrdfs prepare -s/-a/-e and xrdfs query prepare have been restored\n\nImprovement\n------------\n\n* [EOS-5277] - Add LockMonitor class wrapping standard mutex\n* [EOS-5282] - Allow converter configuration to persist on restarts\n* [EOS-5285] - GroupDrainer: Allow all transfers to be reset\n* [EOS-5289] - File truncate can be slow especially for RAIN layouts\n* [EOS-5290] - File close operation for RAIN layouts can trigger client timeouts\n* MGM: Tape REST API v0.1 release - Support for ArchiveInfo and Release\n  functionality + discovery endpoint\n* MISC: Allow the eos-iam-mapfile tool to deal with DNs containing commas\n\n\n``v5.0.24 Diopside``\n=======================\n\n2022-05-27\n\nBug\n---\n\n* [EOS-3713] - sys.eos.mdino should not use old-style inodes\n* [EOS-5230] - Keep xattrs when restoring versions\n* [EOS-5269] - Certain FSes not picked up by the group drainer\n\nImprovement\n-----------\n\n* [EOS-5263] - groupmod is hard limited to 256 groups\n* [EOS-5267] - Provide timestamp in eos convert list failed errors\n\n\n``v5.0.23 Diopside``\n=======================\n\n2022-05-16\n\nNote\n----\n\n* This release uses eos-xrootd-5.4.4 which is based on XRootD-5.4.3-rc3.\n\nBug\n----\n\n* [EOS-5246] - replica show 'error_label=none' while having checksum mismatch.\n\nImprovement\n------------\n\n* [EOS-5184] - Add RedirectCollapse to XrdMgmOfs::Redirect responses\n* [EOS-5198] - Add few log lines to MasterLog\n\n\n``v5.0.22 Diopside``\n=======================\n\n2022-05-06\n\nImprovements\n------------\n\nFUSEX: Refactoring async response handling\n\n\n``v5.0.21 Diopside``\n=======================\n\n2022-05-06\n\nNotes\n------\n\n* Note: this is a scratch build on top of XRootD-5.4.3-RC1 trying to test a bug fix concerning vector reads\n* Update dependency to XRootD-5.4.3-RC1\n\n\n\n``v5.0.20 Diopside``\n=======================\n\n2022-05-03\n\nImprovements\n------------\n\nMGM: Improve fsck handling for rain files with rep_diff_n errors\nMGM: Add extra logging in fsck and be more defensive when handling\nunregistered stripes\nMGM: Group drainer prune transfers only once every few minutes\nFST: Silence stat errors for TPC transfers during preparation stages\n\n\n``v5.0.19 Diopside``\n=======================\n\n2022-05-02\n\nBug\n---\n\n* MGM: Fix race condition in Converter which can lead to wrong metadata stored\n  in leveldb for converted files.\n* MGM: Fix wrong computation of number of stripes for RAIN layout\n* [EOS-5199] - Metadata (xattrs) is lost when creating new versions\n* [EOS-5219] - eos fsck report json output does not reflect command line options -l and -i\n* [EOS-5224] - No update is performed when adding a new member to an e-group in EOSATLAS\n\n\nNew Feature\n-----------\n\n* [EOS-5178] - Implement Group Drain\n* [EOS-5225] - Have a useful GroupDrain Status\n\n\n``v5.0.18 Diopside``\n=======================\n\n2022-04-22\n\nBug\n----\n\n* [EOS-5197] - Deleting an xattr via console does not delete the key\n* [EOS-5199] - Metadata (xattrs) is lost when creating new versions\n* MGM: Fix crash in debug message when Env object is null for Access method\n\nNew Feature\n------------\n\n* [EOS-5215] - Fsck handle stripe size inconsistencies for RAIN layouts\n\n\nImprovement\n------------\n\n* [EOS-4955] - Add project quota tests as a part of CI\n* MGM: Iostat performance improvements for summary output\n* MGM: Iostat make extra tables optional by default and add separate\n  flag for displaying them.\n\n\n``v5.0.17 Diopside``\n=======================\n\n2022-04-13\n\nNote\n----\n\n* This version includes add the fixes up to 4.8.82.\n\nImprovement\n------------\n\n* [EOS-5201] - Allow for more fine grained IO policies\n* [EOS-5204] - Only create files  via FUSEX if there is inode and volume quota and physical space available\n* [EOS-5205] - Distinguish writable space and total space\n* [EOS-5206] - Don't allow to set quota volume lower than the minimum fuse quota booking size\n\n\n``v5.0.16 Diopside``\n=======================\n\n2022-03-29\n\nBug\n----\n\n* [EOS-5181] - Slave to Master redirection creates IO errors on FUSEx mounts\n* [EOS-5176] - Make OAuth tolerant to self-signed//invalid certificates used by identity provider\n\nImprovement\n-----------\n\n* MGM: Add protection against multi-source retry for RAIN layouts\n* MGM: Rewrite of the IoStat implementation for better accuracy\n* MGM: Remove dependency on eos-scitokens and use the library provided by XRootD framework\n* DOC: Update documentation concerning the MGM configuration for SciTokens support\n* NS: QuarkSyncTimeAccounting - removed namespace lock usage\n\nNew feature\n-----------\n\n* MGM: Add support for eos tokens over https\n\n\n``v5.0.15 Diopside``\n=======================\n\n2022-03-22\n\nNote\n-----\n\n* Includes all the changes from 4.8.79\n\nBug\n----\n\n* FUSEX: never keep the deletion mutex when destroying an upload proxy because\n  the destructor still needs a free call back thread to use HandleResponse\n* [EOS-5153] - EC file written via FUSEx - mismatching checksum\n* [EOS-5167] - MGM segv in a non-tape enabled instance\n\n\n\n``v5.0.14 Diopside``\n=======================\n\n2022-03-14\n\nBug\n----\n\n* [EOS-5090] - convert clear is not a admin command\n* [EOS-5133] - node ls -b does not remove the domain names\n* FUSEX: Fix deadlocks and race-conditions reported by TSAN\n\nImprovement\n------------\n\n* [EOS-5108] - workaround: drop forced automount expiry on FUSEX updates\n* [EOS-5126] - [eos-ns-inspect] Complement `stripediff` output\n\n\n``v5.0.13 Diopside``\n=======================\n\n2022-02-15\n\nNote\n----\n\n* Includes all the changes from 4.8.76\n\nBug\n---\n\n* [EOS-5110] - Consolidate Access control in GRPC MD, MDSTreaming\n* [EOS-5116] - Workaround for XrdOucBuffPool bug\n* [EOS-5118] - eos-ns-inspect scan is initializing maxdepth to 0, even if not used\n* [EOS-5119] - Deadlock scenario in eosxd\n\nImprovement\n-----------\n\n* [EOS-5111] - Groupbalancer: newly introduced fields may not have a sane value\n* [EOS-5120] - io stat tag totals\n\n\n``v5.0.12 Diopside``\n=======================\n\n2022-02-04\n\nNote\n----\n\n* Identical to 5.0.11 but re-tagged due to Koji issues\n\n\n``v5.0.11 Diopside``\n=======================\n\n2022-02-04\n\nBug\n----\n\n* [EOS-5105] - eosxd crash in cap::quotax::dump\n\n\n``v5.0.10 Diopside``\n=======================\n\n2022-02-02\n\nNote\n-----\n\n* This release includes all the changes from 4.8.74 release\n\nBug\n----\n\n* [EOS-5069] - filesystem status in \"rw + failed\"\n* [EOS-5070] - Access::ThreadLimit creates re-entrant lock of the access mutex\n* [EOS-5095] - Re-entrant lock triggered by out of quota warning\n\nImprovement\n------------\n\n* [EOS-5065] - Add create-if-not-exists option in GRPC\n* [EOS-5076] - Extend iotype interfaces to be space/directory defined\n* MGM: Fix missing support for cid/cxid and error output for convert command\n* WNC: Replaced auxiliary ACL function for fileinfo command\n\nNew features\n------------\n\n* WNC: Implemented support for EOS-wnc token, convert, fsck and new find commands\n* WNC: Changed GRPC streaming mechanism for find, ls and transfer commands\n\n\n``v5.0.9 Diopside``\n=======================\n\n2022-01-12\n\nBug\n----\n\n* COMMON: Avoid segv due to mutex object set to nullptr in RWLock printout\n* [EOS-4850] - eosxd crash in destructor under metad::pmap::retrieveWithParentTS()\n* [EOS-5057] - Volume quota dispatched to FUSE clients mixes logical and physical bytes\n\n\n``v5.0.8 Diopside``\n=======================\n\n2022-01-06\n\nNote\n----\n\n* Note: This release includes all the changes to the 4.8.70 release\n\nBug\n----\n\n* [EOS-5039] - Threads with parens in their name cannot access EOS\n\nImprovement\n-----------\n\n* [EOS-5029] - Allow to apply rate limiting in recursive (server side) command.\n* [EOS-5048] - Support direct IO for high performance read/write use cases\n\n\n``v5.0.7 Diopside``\n=======================\n\n2021-12-01\n\nNote\n----\n\n* Release based on XRootD-5.3.4\n\n\nNew features\n------------\n\n* WNC: Implemented support for EOS-wnc member, backup, map and archive command\n\n\n\n``v5.0.6 Diopside``\n=======================\n\n2021-11-16\n\nNote\n-----\n\n* Release based on XRootD-5.3.3 which fixes a critical bug concerning \"invalid responses\"\n\n\nBug\n----\n\n* ARCHIVE: Avoid trying to set extended attributes which are empty\n* [EOS-4995] MGM/CONSOLE: add '-c' option to CLI ls to show also the checksum for a listing\n* CTA: Fixed FST crash when connecting to misconfigured ctafrontend endpoint\n\n\n``v5.0.5 Diopside``\n=======================\n\n2021-11-04\n\nBug\n----\n\nOSS: Avoid leaking file descriptors for xsmap files which are deleted in the meantime\nMGM: Skip applying fsck config changes at the slave as these will be properly\n\n\n``v5.0.4 Diopside``\n=======================\n\n2021-10-27\n\n\nBug\n----\n\n* SPEC: Make sure both libproto* and libXrd* requirements are excluded when\n  building the eos packages since these come from internally build rpms like\n  eos-xrootd and eos-protobuf3 which don't expose the library so names so that\n  they can be installed on a machine along with the official rpms for the\n  corresponding packages if they exist.\n* MGM: Avoid that a slave MGM applies an fsck configuration change in a loop\n\nImprovements\n------------\n\n* EOS-4967: Add ARM64 support for blake3\n\n\n``v5.0.3 Diopside``\n=======================\n\n2021-10-27\n\n\nNote\n----\n\n* This version is based on XRootD 5.3.2 that addresses some critical bug observed\n  in the previous version for XRootD.\n\nBug\n----\n\n* MGM: Fix GRPC IPv6 parsing\n* [EOS-4963] - FST: Reply with 206(PARTIAL_CONTENT) for partial content responses\n* [EOS-4962] - MGM: Return FORBIDDEN if there is a public access restriction in PROFIND requests\n* [EOS-4950] - FUSEX: fix race conditions in async callbacks with respect to proxy object deletions\n*\n\nNew features\n------------\n\n* [EOS-4670] - FUSEX: implement file obfuscation and encryption\n\n\n``v5.0.2 Diopside``\n=======================\n\n2021-09-06\n\nBug\n----\n\n* [EOS-4809] - Make eos5 work with XrdMacaroons from XRootD5\n* Includes all the fixes from 4.8.65\n\nImprovements\n------------\n\n* WNC: Improvements to the EOS-Drive for fileinfo & health command\n\n\n``v5.0.1 Diopside``\n=======================\n\n2021-08-16\n\nNew features\n-------------\n\n* Comtrade WNC contribution for the server side\n* Includes all the fixes from the 4.8.60 release\n\n\n``v5.0.0 Diopside``\n=======================\n\n2021-06-11\n\nMajor changes\n--------------\n\n* Based on XRootD 5.2.0\n* Drop support for in-memory namespace\n* Drop support for file based configuration\n* Drop support for old high-availability setup\n* Make fusex classes compatible with the latest protobuf library\n* Integrate QuarkDB as part of the eos release process\n"
  },
  {
    "path": "doc/diopside/releases/diopside.rst",
    "content": ":orphan:\n\n.. highlight:: rst\n\n.. index::\n   single: Diopside\n\nDiopside\n========\n\n``Lifetime: 2021++``\n\n**Diopside** is the release name for the EOS development branch since 2021.\n\nRelease notes: :doc:`./diopside-release`\n"
  },
  {
    "path": "doc/diopside/releases/index.rst",
    "content": ".. index::\n   single: Releases\n\n.. highlight:: rst\n\n========\nReleases\n========\n\nEOS releases are named after gemstones. The actively developed version\nis called Diopside. The Citrine version still gets backports of important bug fixes during 2023.\n\n.. toctree::\n   :maxdepth: 1\n\n   amber\n   beryl\n   citrine\n   diopside\n\n.. epigraph::\n\n   ================================= =================== =================== =================================\n   Release                           Stable Version      Description         Release Notes\n   ================================= =================== =================== =================================\n   :doc:`amber`                      0.2.47              1st EOS Generation\n   :doc:`beryl`                      0.3.267             2nd EOS Generation  :doc:`beryl-release`\n   :doc:`citrine`                    4.8.101             3rd EOS Generation  :doc:`citrine-release`\n   :doc:`diopside`                   5.4.2              4th EOS Generation  :doc:`diopside-release`\n   ================================= =================== =================== =================================\n"
  },
  {
    "path": "elrepopackage.spec",
    "content": "Name:           eos-citrine-repo\nVersion:        1\nRelease:        1\nUrl:            http://cern.ch/eos/\nSummary:        EOS YUM repository definition for the 'citrine' releases\nLicense:        none\nGroup:          Applications/System\nBuildArch:      noarch\n%description\nThis package contains the YUM configuration for the EOS 'citrine'\nreleases and their third-party dependencies.  RPMs are signed with\n    http://storage-ci.web.cern.ch/storage-ci/storageci.key\n\n%install\nmkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/yum.repos.d/\ncat <<EOFrepo >${RPM_BUILD_ROOT}%{_sysconfdir}/yum.repos.d/eos-citrine.repo\n[eos-citrine]\nname=EOS binaries from EOS project, citrine branch\nbaseurl=http://cern.ch/storage-ci/eos/citrine/tag/el-\\$releasever/\\$basearch/\nenabled=1\ngpgcheck=1\npriority=10 \n\n[eos-citrine-deps]\nname=EOS dependencies from EOS project, citrine branch\nbaseurl=http://cern.ch/storage-ci/eos/citrine-depend/el-\\$releasever/\\$basearch/\nenabled=1\ngpgcheck=0\npriority=10 \n\nEOFrepo\n\n\n\n%files\n%defattr(-,root,root,-)\n%config(noreplace) %{_sysconfdir}/yum.repos.d/eos-citrine.repo\n"
  },
  {
    "path": "eos.spec.in",
    "content": "#-------------------------------------------------------------------------------\n# Helper macros and variables\n#-------------------------------------------------------------------------------\n%define _unpackaged_files_terminate_build 0\n%define devtoolset devtoolset-9\n%define debug_package %{nil}\n%global __os_install_post %{nil}\n\n# By default we build the eos client SRPMS, if the entire build is required\n# then pass the \"--with server\" flag to the rpmbuild command\n%bcond_with server\n\n# By default we build without clang. To enable it,\n# then pass the \"--with clang\" flag to the rpmbuild command\n%bcond_with clang\n\n# By default we build without AddressSanitizer. To enable it,\n# pass the \"--with asan\" flag to the rpmbuild command\n%bcond_with asan\n\n# By default we build without ThreadSanitizer. To enable it,\n# pass the \"--with tsan\" flag to the rpmbuild command\n%bcond_with tsan\n\n# By default we build without code coverage. To enable it,\n# pass the \"--with coverage\" flag to the rpmbuild command\n%bcond_with coverage\n\n# By default we don't use eosxrootd for EL7\n%bcond_with eos_xrootd_rh\n\n# By default build without eos-grpc-gateway support\n%bcond_with eos_grpc_gateway\n\n# Define required dependencies\n%define eos_xrootd_version_min 0:5.9.1\n%define xrootd_version_min 1:5.9.1\n%define eos_grpc_version 1.56.1\n%define eos_rocksdb_version 8.8.1\n%define eos_grpc_gateway_version 0.2.0\n\n%define major_version @CPACK_PACKAGE_VERSION_MAJOR@\n%define release_version @CPACK_PACKAGE_RELEASE@\n\n# Enable automatically to build with eos-xrootd for RHEL [7,10]\n%if 0%{?rhel} >= 7 && 0%{?rhel} <= 10\n%define with_eos_xrootd_rh 1\n%endif\n\n%define __cmake cmake\n%define __python /usr/bin/python3\n\n#-------------------------------------------------------------------------------\n# Compute additional macros based on environment or existing definitions\n#-------------------------------------------------------------------------------\n%define compiler gcc\n\n%if %{with clang}\n  %define compiler clang\n%endif\n\n#-------------------------------------------------------------------------------\n# Package definitions\n#-------------------------------------------------------------------------------\nSummary: The EOS project\nName: @CPACK_PACKAGE_NAME@\nVersion: @CPACK_PACKAGE_VERSION@\nRelease: @CPACK_PACKAGE_RELEASE@%{dist}%{?_with_asan:.asan}%{?_with_tsan:.tsan}\nPrefix: /usr\nLicense: none\nGroup: Applications/File\nSource0: %{name}-%{version}-@CPACK_PACKAGE_RELEASE@.tar.gz\nBuildRoot: %{_tmppath}/%{name}-root\n\n# Add EPEL repository explicitly which holds many of the other dependencies\n%if 0%{?rhel} >= 7 && 0%{?rhel} <= 10\nBuildRequires: epel-release\nRequires: epel-release\n%endif\n\n%if %{with asan}\nBuildRequires: libasan\nRequires: libasan\n%endif\n\n%if %{with tsan}\nBuildRequires: libtsan\nRequires: libtsan\n%endif\n\n# Initialize to empty, might be overwritten later\n%define xrootd_requires_exclude %{nil}\n\n# Select xrootd package\n%if %{with eos_xrootd_rh}\n# Install eos-xrootd\n%if 0%{?rhel} >= 7 && 0%{?rhel} <= 10\nBuildRequires: eos-xrootd = %{eos_xrootd_version_min}\n# Don't put an explicit dependency on xrootd because eos-xrootd hides the\n# shared libraries from provide. Make sure we don't exclude any EOS specific\n# libraries.\n%define xrootd_requires_exclude ^libXrdCl.*$|^libXrdHttp.*$|^libXrdPosix.*$|^libXrdServer.*$|^libXrdSsiLib.*$|^libXrdUtils.*$|^libXrdXml.*$|^libXrdCrypto.*$\n%else\n# Add default xrootd dependencies\nBuildRequires: xrootd >= %{xrootd_version_min}\nBuildRequires: xrootd-client-devel >= %{xrootd_version_min}\nBuildRequires: xrootd-server-devel >= %{xrootd_version_min}\nBuildRequires: xrootd-private-devel >= %{xrootd_version_min}\n%endif\n%else\n# Add default xrootd dependencies\nBuildRequires: xrootd >= %{xrootd_version_min}\nBuildRequires: xrootd-client-devel >= %{xrootd_version_min}\nBuildRequires: xrootd-server-devel >= %{xrootd_version_min}\nBuildRequires: xrootd-private-devel >= %{xrootd_version_min}\n%endif\n\n# Add eos-grpc dependencies\nBuildRequires: eos-grpc = %{eos_grpc_version}\nBuildRequires: eos-grpc-devel = %{eos_grpc_version}\nBuildRequires: eos-grpc-plugins = %{eos_grpc_version}\n\n# Don't put an explicit dependency on libraries provided by eos-grpc because\n# the package hides shared libraries from the list of provided capabilities.\n%define grpc_requires_exclude ^libprot.*$|^libabsl.*$|^libgrpc.*$|^libaddress_sorting.*$|^libre2.*$|^libgpr.*$|^libupb.*$\n%define __requires_exclude %{grpc_requires_exclude}|%{xrootd_requires_exclude}\n\n%if %{with eos_grpc_gateway}\nBuildRequires: eos-grpc-gateway = %{eos_grpc_gateway_version}\nRequires: eos-grpc-gateway = %{eos_grpc_gateway_version}\n%endif\n\nBuildRequires: cmake >= 3.17\nBuildRequires: git, readline-devel\nBuildRequires: openssl, openssl-devel\nBuildRequires: ncurses, ncurses-devel\nBuildRequires: zlib, zlib-devel\nBuildRequires: fuse-devel, fuse >= 2.5\nBuildRequires: fuse3-devel, fuse3 >= 3.0\nBuildRequires: krb5-devel\nBuildRequires: redhat-rpm-config\nBuildRequires: libattr-devel, xfsprogs-devel\nBuildRequires: gcc gcc-c++\nBuildRequires: jsoncpp, jsoncpp-devel\nBuildRequires: glibc-headers\nBuildRequires: binutils-devel\nBuildRequires: help2man\nBuildRequires: libzstd-devel\nBuildRequires: lz4-devel\nBuildRequires: bzip2-devel\nBuildRequires: snappy-devel\nRequires: libzstd, zstd, lz4, snappy\nBuildRequires: libcap-devel\nBuildRequires: gflags gflags-devel\nBuildRequires: systemd\nBuildRequires: fmt-devel\nBuildRequires: activemq-cpp, activemq-cpp-devel\n\n%if 0%{?rhel} != 10\nBuildRequires: jemalloc, jemalloc-devel\n%endif\n\n# NFS support dependencies\nBuildRequires: libnfs, libnfs-devel\nRequires: libnfs\n\n%if %{?rhel:1}%{!?rhel:0} && 0%{?rhel} <= 7\nBuildRequires: openssl-static, ncurses-static\n%endif\n\n%if %{with server}\nBuildRequires: eos-rocksdb = %{eos_rocksdb_version}\nBuildRequires: openldap-devel\nBuildRequires: e2fsprogs-devel\n%endif\n\nBuildRequires: eos-grpc = %{eos_grpc_version}\nBuildRequires: eos-grpc-devel = %{eos_grpc_version}\nBuildRequires: eos-grpc-plugins = %{eos_grpc_version}\nRequires: eos-grpc = %{eos_grpc_version}\n\nBuildRequires: bzip2-devel\nRequires: bzip2-devel\n\nBuildRequires: elfutils-devel\nRequires: elfutils-devel\n\nBuildRequires: procps-ng-devel >= 3.3.10\nRequires: procps-ng >= 3.3.10\n\nBuildRequires: zlib-static,\nBuildRequires: libcurl, libcurl-devel\nBuildRequires: libuuid-devel, sparsehash-devel\nBuildRequires: zeromq, zeromq-devel\nBuildRequires: libevent, libevent-devel\nBuildRequires: bzip2-devel\nRequires: bzip2\n\nBuildRequires: scitokens-cpp-devel\nRequires: scitokens-cpp\n\n# ISA-L[_crypto], XXHash dependencies for CC7 and CS8/9\n%if %{with server}\n%if 0%{?rhel} == 7\nBuildRequires: libisa-l-devel, libisa-l_crypto-devel\nRequires: libisa-l, libisa-l_crypto\n%endif\n%endif\n\n%if \"%{compiler}\" == \"gcc\"\nBuildRequires: binutils-devel\n%if 0%{?rhel} >= 7 && 0%{?rhel} <= 10\n# We want swap-support on eosxd - requires rocksdb KV\nBuildRequires: eos-rocksdb = %{eos_rocksdb_version}\n%endif\n%endif\n\n%if \"%{compiler}\" == \"clang\"\nBuildRequires: clang\nBuildRequires: compiler-rt\nBuildRequires: libatomic\nBuildRequires: gcc-toolset-14-libatomic-devel\nRequires: libatomic\n%if 0%{?fedora} >= 38\nBuildRequires: compiler-rt\n%endif\n%endif\n\n\n%description\nThe EOS software package.\n%prep\n%setup -n %{name}-%{version}-@CPACK_PACKAGE_RELEASE@\n%global build_type RelWithDebInfo\n%global build_flags -DBUILD_MANPAGES=1\n%if %{without server}\n%global build_flags %{build_flags} -DCLIENT=1\n%endif\n%if %{with asan}\n%global build_flags %{build_flags} -DASAN=1\n%endif\n%if %{with tsan}\n%global build_flags %{build_flags} -DTSAN=1\n%endif\n%if %{with coverage}\n%global build_type Debug\n%global build_flags %{build_flags} -DCOVERAGE=1 -DCOV_CROSS_PROFILE=1\n%endif\n\n%build\ntest -e $RPM_BUILD_ROOT && rm -r $RPM_BUILD_ROOT\nmkdir -p build\ncd build\n\n%if \"%{?compiler}\" == \"gcc\"\n  %if 0%{?rhel} >= 7 && 0%{?rhel} <= 10\n    %{__cmake} ../ -DRELEASE=@CPACK_PACKAGE_RELEASE@ -DCMAKE_BUILD_TYPE=%{build_type} -DXROOTD_ROOT=/opt/eos/xrootd/ %{build_flags}\n  %else\n    %{__cmake} ../ -DRELEASE=@CPACK_PACKAGE_RELEASE@ -DCMAKE_BUILD_TYPE=%{build_type} %{build_flags}\n  %endif\n%else\n  source /opt/rh/gcc-toolset-14/enable\n  CC=clang CXX=clang++ %{__cmake} ../ -DRELEASE=@CPACK_PACKAGE_RELEASE@ -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=true %{build_flags}\n%endif\n\n%{__make} %{_smp_mflags}\n\n%install\ncd build\n%{__make} install DESTDIR=$RPM_BUILD_ROOT\n\nexport QA_RPATHS=3\necho \"Installed!\"\n\n%clean\nrm -rf $RPM_BUILD_ROOT\n\n%if %{with server}\n#-------------------------------------------------------------------------------\n# Package eos-server\n#-------------------------------------------------------------------------------\n%package -n eos-server\nSummary: The EOS server installation\nGroup: Applications/File\nExclusiveArch: x86_64 aarch64\n\n# Select xrootd package\n%if %{with eos_xrootd_rh}\n# Install eos-xrootd\n%if 0%{?rhel} >= 7 && 0%{?rhel} <= 10\nRequires: eos-xrootd = %{eos_xrootd_version_min}\n%else\nRequires: xrootd >= %{xrootd_version_min}\nRequires: xrootd-client >= %{xrootd_version_min}\n%endif\n%else\nRequires: xrootd >= %{xrootd_version_min}\nRequires: xrootd-client >= %{xrootd_version_min}\n%endif\n\n%if %{with eos_grpc_gateway}\nRequires: eos-grpc-gateway = %{eos_grpc_gateway_version}\n%endif\n\nRequires: eos-client = @CPACK_PACKAGE_VERSION@\nRequires: acl\nRequires: gdb\nRequires: jemalloc, jemalloc-devel\nRequires: jsoncpp\nRequires: psmisc\nRequires: libcurl\nRequires: logrotate\nRequires: eos-grpc = %{eos_grpc_version}\nRequires: systemd\n\nBuildRequires: zeromq, zeromq-devel\nRequires: zeromq\nBuildRequires: eos-folly = 2019.11.11.00\nRequires: eos-folly = 2019.11.11.00\nBuildRequires: perl-generators\nBuildRequires: xxhash-devel\nRequires: xxhash-libs\n\n%description -n eos-server\nThe EOS server installation\n\n%files -n eos-server\n%defattr(-, root, root, -)\n%{_bindir}/eos-ns-convert-to-locality-hashes\n%{_bindir}/zstdtail\n%{_sbindir}/eos-tty-broadcast\n%{_sbindir}/eosfstregister\n%{_sbindir}/eosfstinfo\n%{_sbindir}/eosadmin\n%{_sbindir}/eos-iam-mapfile\n%{_sbindir}/eos-check-blockxs\n%{_sbindir}/eos-udp-dumper\n%{_sbindir}/eos-compute-blockxs\n%{_sbindir}/eos-scan-fs\n%{_sbindir}/eos-adler32\n%{_sbindir}/eos-checksum\n%{_sbindir}/eos-mmap\n%{_sbindir}/eos-repair-tool\n%{_sbindir}/eos-ioping\n%{_sbindir}/eos-fmd-tool\n%{_sbindir}/eos-rain-hd-dump\n%{_sbindir}/eos-rain-check\n%{_sbindir}/eos-filter-stacktrace\n%{_sbindir}/eos-ports-block\n%{_sbindir}/eos-status\n%{_sbindir}/eos-diagnostic-tool\n%{_libdir}/libEosCommonServer.so.%{version}\n%{_libdir}/libEosCommonServer.so.%{major_version}\n%{_libdir}/libEosCommonServer.so\n%{_libdir}/libEosFstIo.so.%{version}\n%{_libdir}/libEosFstIo.so.%{major_version}\n%{_libdir}/libEosFstIo.so\n%{_libdir}/libEosNsCommon.so.%{version}\n%{_libdir}/libEosNsCommon.so.%{major_version}\n%{_libdir}/libEosNsCommon.so\n%{_libdir}/libEosAuthOfs-*.so\n%{_libdir}/libEosFstOss.so\n%{_libdir}/libEosFstCss.so\n%{_libdir}/libEosFstHttp.so\n%{_libdir}/libXrdEosFst-*.so\n%{_libdir}/libXrdEosMgm-*.so\n%{_libdir}/libEosMgmHttp-*.so\n%{_libdir}/libEosNsQuarkdb.so\n%config(noreplace) %{_sysconfdir}/xrd.cf.fst\n%config(noreplace) %{_sysconfdir}/xrd.cf.mgm\n%config(noreplace) %{_sysconfdir}/xrd.cf.sync\n%config(noreplace) %{_sysconfdir}/xrd.cf.fed\n%config(noreplace) %{_sysconfdir}/xrd.cf.prefix\n%config(noreplace) %{_sysconfdir}/xrd.cf.quarkdb\n%config(noreplace) %{_sysconfdir}/eos/config/mgm/mgm\n%config(noreplace) %{_sysconfdir}/eos/config/mgm/mgm.modules\n%config(noreplace) %{_sysconfdir}/eos/config/fst/fst\n%config(noreplace) %{_sysconfdir}/eos/config/generic/all\n%config(noreplace) %{_sysconfdir}/eos/config/qdb/qdb\n%config(noreplace) %{_sysconfdir}/eos/config/modules/alice\n%config %{_sysconfdir}/sysconfig/eos_env.example\n%{_prefix}/lib/systemd/system/eos.target\n%{_prefix}/lib/systemd/system/eos.service\n%{_prefix}/lib/systemd/system/eos@.service\n%{_prefix}/lib/systemd/system/eos@master.service\n%{_prefix}/lib/systemd/system/eos@slave.service\n%{_prefix}/lib/systemd/system/eos5-fst@.service\n%{_prefix}/lib/systemd/system/eos5-mgm@.service\n%{_prefix}/lib/systemd/system/eos5-qdb@.service\n%{_prefix}/lib/systemd/system/eos5.service\n%{_sbindir}/eos_start_pre.sh\n%{_sbindir}/eos_start.sh\n%config(noreplace) %{_sysconfdir}/cron.d/eos-logs\n%config(noreplace) %{_sysconfdir}/cron.d/eos-reports\n%config(noreplace) %{_sysconfdir}/logrotate.d/eos-logs\n%dir %attr(700,daemon,daemon) %{_localstatedir}/eos\n%dir %attr(700,daemon,daemon) %{_localstatedir}/eos/wfe\n%dir %attr(700,daemon,daemon) %{_localstatedir}/eos/wfe/bash/\n%dir %attr(700,daemon,daemon) %{_localstatedir}/eos/ns-queue\n%dir %attr(700,daemon,daemon) %{_localstatedir}/eos/ns-queue/default\n%dir %attr(755,daemon,daemon) %{_localstatedir}/log/eos\n%dir %attr(755,daemon,daemon) %{_localstatedir}/log/eos/tx\n%attr(555,daemon,daemon) %{_localstatedir}/eos/wfe/bash/shell\n\n%post -n eos-server\nif [ ! -f /etc/sysconfig/eos-yum-noscripts ]; then\n  echo \"Starting conditional EOS services\"\n  systemctl daemon-reload > /dev/null 2>&1 || :\n  systemctl --no-legend list-units \"eos@*\" | grep -v \"q.*db\" | awk '{print $1}' | xargs --no-run-if-empty -n1 systemctl restart || :\nfi\n\n%preun -n eos-server\nif [ ! -f /etc/sysconfig/eos-yum-noscripts ]; then\nif [ $1 = 0 ]; then\n  echo \"Stopping EOS services\"\n  systemctl stop eos@* > /dev/null 2>&1 || :\nfi\nfi\n\n#-------------------------------------------------------------------------------\n# Package eos-ns-inspect\n#-------------------------------------------------------------------------------\n%package -n eos-ns-inspect\nSummary: EOS namespace inspection tool for instance administrators\nGroup: Applications/File\nExclusiveArch: x86_64 aarch64\n\n# Select xrootd package\n%if %{with eos_xrootd_rh}\n# Install eos-xrootd\n%if 0%{?rhel} >= 7 && 0%{?rhel} <= 10\nRequires: eos-xrootd = %{eos_xrootd_version_min}\n%else\nRequires: xrootd-client >= %{xrootd_version_min}\n%endif\n%else\nRequires: xrootd-client >= %{xrootd_version_min}\n%endif\n\nRequires: eos-grpc = %{eos_grpc_version}\nBuildRequires: eos-folly = 2019.11.11.00\nRequires: eos-folly = 2019.11.11.00\n\n%description -n eos-ns-inspect\nEOS namespace inspection tool for instance administrators\n\n%files -n eos-ns-inspect\n%defattr(-, root, root, -)\n%{_bindir}/eos-ns-inspect\n%{_bindir}/eos-config-inspect\n%{_bindir}/eos-fid-to-path\n%{_bindir}/eos-inode-to-fid\n%endif\n\n#-------------------------------------------------------------------------------\n# Package eos-client\n#-------------------------------------------------------------------------------\n%package -n eos-client\nSummary: The EOS shell client\nGroup: Applications/File\nExclusiveArch: x86_64 aarch64\nRequires: zeromq\nRequires: squashfs-tools\nRequires: elfutils\nRequires: eos-grpc = %{eos_grpc_version}\nRequires: systemd\n\n# Select xrootd package\n%if %{with eos_xrootd_rh}\n# Install eos-xrootd\n%if 0%{?rhel} >= 7 && 0%{?rhel} <= 10\nRequires: eos-xrootd = %{eos_xrootd_version_min}\n%else\nRequires: xrootd-client >= %{xrootd_version_min}\n%endif\n%else\nRequires: xrootd-client >= %{xrootd_version_min}\n%endif\n\n%description -n eos-client\nThe EOS shell client.\n\n%files -n eos-client\n%defattr(-, root, root, -)\n%{_bindir}/eos\n%{_bindir}/eos-tui\n%{_bindir}/eoscp\n%{_libdir}/libEosCommon.so.%{version}\n%{_libdir}/libEosCommon.so.%{major_version}\n%{_libdir}/libEosCommon.so\n%{_sysconfdir}/bash_completion.d/eos\n%{_sysconfdir}/profile.d/eos-completion.sh\n%{_datadir}/bash-completion/completions/eos\n%{_datadir}/zsh/site-functions/_eos\n%{_sysconfdir}/fuse.conf.eos\n\n# Documentation\n%doc %{_mandir}/man1/\n\n#-------------------------------------------------------------------------------\n# Package eos-fusex\n#-------------------------------------------------------------------------------\n%package -n eos-fusex\nSummary: The new EOS fuse client\nGroup: Applications/File\nExclusiveArch: x86_64 aarch64\nRequires: eos-fusex-core = @CPACK_PACKAGE_VERSION@\nRequires: eos-fusex-selinux = @CPACK_PACKAGE_VERSION@\nRequires: fuse3\nObsoletes: eos-fuse <= %{version}\nObsoletes: eos-fuse-core <= %{version}\nObsoletes: eos-fuse-sysv <= %{version}\n%description -n eos-fusex\nThe new EOS fuse client bundle.\n\n%files -n eos-fusex\n%defattr(-, root, root, -)\n\n#-------------------------------------------------------------------------------\n# Package eos-fusex-core\n#-------------------------------------------------------------------------------\n%package -n eos-fusex-core\nSummary: The new EOS fuse client\nGroup: Applications/File\nExclusiveArch: x86_64 aarch64\n\n# Select xrootd package\n%if %{with eos_xrootd_rh}\n# Install eos-xrootd\n%if 0%{?rhel} >= 7 && 0%{?rhel} <= 10\nRequires: eos-xrootd = %{eos_xrootd_version_min}\n%else\nRequires: xrootd-client >= %{xrootd_version_min}\n%endif\n%else\nRequires: xrootd-client >= %{xrootd_version_min}\n%endif\n\nRequires: eos-client = @CPACK_PACKAGE_VERSION@\nRequires: fuse\nRequires: attr\nRequires: zeromq\n\n%if 0%{?rhel} != 10\nRequires: jemalloc, jemalloc-devel\n%endif\n\n%description -n eos-fusex-core\nThe EOS fuse core containing eosxd/eosxd3.\n\n%files -n eos-fusex-core\n%defattr(-, root, root, -)\n%{_bindir}/eosxd\n%{_bindir}/eosxd3\n%{_bindir}/eosfusebind\n%{_sbindir}/mount.eosx\n%{_sbindir}/mount.eosx3\n%{_tmpfilesdir}/eos-fusex-core.conf\n%{_sysconfdir}/logrotate.d/eos-fusex-logs\n%dir %attr(755,daemon,daemon) %{_localstatedir}/log/eos/\n%dir %attr(755,daemon,daemon) %{_localstatedir}/log/eos/fusex/\n%dir %attr(755,daemon,daemon) %{_localstatedir}/cache/eos/\n%dir %attr(755,daemon,daemon) %{_localstatedir}/cache/eos/fusex/\n# Ensure /run/eos/... creation\n%dir %attr(0755, root, root) %{_localstatedir}%{_rundir}/eos\n%dir %attr(1777, root, root) %{_localstatedir}%{_rundir}/eos/credentials\n%dir %attr(1777, root, root) %{_localstatedir}%{_rundir}/eos/credentials/store\n\n#-------------------------------------------------------------------------------\n# Package eos-fusex-selinux\n#-------------------------------------------------------------------------------\n%package -n eos-fusex-selinux\nSummary: The new EOS fuse client selinux configuration\nGroup: Applications/File\nExclusiveArch: x86_64 aarch64\n\n%description -n eos-fusex-selinux\nThe new EOS fuse core containing selinux definitions.\n\n%files -n eos-fusex-selinux\n%defattr(-, root, root, -)\n/usr/share/selinux/targeted/eosfuse.pp\n/usr/share/selinux/mls/eosfuse.pp\n/usr/share/selinux/strict/eosfuse.pp\n\n%post -n eos-fusex-selinux\nif [ \"$1\" -le \"1\" ]; then # First install\n  # Note: don't push bash variables between {} since they will be empty!!!\n  for VARIANT in mls strict targeted\n  do\n    /usr/sbin/semodule -i %{_datarootdir}/selinux/$VARIANT/eosfuse.pp || :\n  done\nfi\n\n%preun -n eos-fusex-selinux\nif [ \"$1\" -eq  \"0\" ]; then # Final removal\n  /usr/sbin/semodule -r eosfuse || :\nfi\n\n%postun -n eos-fusex-selinux\nif [ \"$1\" -ge \"1\" ]; then # Upgrade\n  for VARIANT in mls strict targeted\n  do\n    /usr/sbin/semodule -i %{_datarootdir}/selinux/$VARIANT/eosfuse.pp || :\n  done\nfi\n\n#-------------------------------------------------------------------------------\n# Package eos-cfsd\n#-------------------------------------------------------------------------------\n%package -n eos-cfsd\nSummary: The new EOS client filesystem daemon\nGroup: Applications/File\nExclusiveArch: x86_64 aarch64\n\n# Select xrootd package\n%if %{with eos_xrootd_rh}\n# Install eos-xrootd\n%if 0%{?rhel} >= 7 && 0%{?rhel} <= 10\nRequires: eos-xrootd = %{eos_xrootd_version_min}\n%else\nRequires: xrootd-client >= %{xrootd_version_min}\n%endif\n%else\nRequires: xrootd-client >= %{xrootd_version_min}\n%endif\n\nRequires: eos-client = @CPACK_PACKAGE_VERSION@\nRequires: fuse3\nRequires: autofs\n\n%if 0%{?rhel} != 10\nRequires: jemalloc, jemalloc-devel\n%endif\n\n%description -n eos-cfsd\nThe new EOS client filesystem daemon\n\n%files -n eos-cfsd\n%defattr(-, root, root, -)\n%{_bindir}/eoscfsd\n%{_sbindir}/mount.eoscfs\n%{_sbindir}/umount.fuse\n%config(noreplace) %{_sysconfdir}/eos/cfsd/eoscfsd.conf\n%config(noreplace) %{_sysconfdir}/auto.cfsd\n%config(noreplace) %{_sysconfdir}/auto.master.d/cfsd.autofs\n%dir %attr(755,daemon,daemon) %{_localstatedir}/log/eos/\n%dir %attr(755,daemon,daemon) %{_localstatedir}/log/eos/cfsd/\n%dir %attr(755,daemon,daemon) %{_localstatedir}/cache/eos/\n%dir %attr(755,daemon,daemon) %{_localstatedir}/cache/eos/cfsd/\n\n%if %{with server}\n\n#-------------------------------------------------------------------------------\n# Package eos-quarkdb\n#-------------------------------------------------------------------------------\n%package -n eos-quarkdb\nSummary: EOS QuarkDB package\nGroup: Applications/File\n\nObsoletes: quarkdb <= 0.4.2\nRequires: lz4-devel, lz4, elfutils-devel\nRequires: jemalloc, jemalloc-devel\n\n# Select xrootd package\n%if %{with eos_xrootd_rh}\n%if 0%{?rhel} >= 7 && 0%{?rhel} <= 10\nRequires: eos-xrootd = %{eos_xrootd_version_min}\n%else\nRequires: xrootd-server >= %{xrootd_version_min}\n%endif\n%else\nRequires: xrootd-server >= %{xrootd_version_min}\n%endif\n\n%description -n eos-quarkdb\nEOS Quarkdb package\n\n%files -n eos-quarkdb\n%defattr(-, root, root, -)\n%{_libdir}/libXrdQuarkDB.so\n%{_bindir}/quarkdb-tests\n%{_bindir}/quarkdb-stress-tests\n%{_bindir}/quarkdb-sudo-tests\n%{_bindir}/quarkdb-bench\n%{_bindir}/quarkdb-create\n%{_bindir}/quarkdb-ldb\n%{_bindir}/quarkdb-recovery\n%{_bindir}/quarkdb-server\n%{_bindir}/quarkdb-sst-inspect\n%{_bindir}/quarkdb-validate-checkpoint\n\n#-------------------------------------------------------------------------------\n# Package eos-testkeytab\n#-------------------------------------------------------------------------------\n%package -n eos-testkeytab\nSummary: The EOS testkeytab package\nGroup: Applications/File\nExclusiveArch: x86_64 aarch64\nRequires: shadow-utils\n\n%description -n eos-testkeytab\nContains an example keytab file and create the eosnobody ans eosdev users\n\n%files -n eos-testkeytab\n%config(noreplace) %attr(-, daemon, daemon) %{_sysconfdir}/eos.keytab\n%config(noreplace) %attr(-, daemon, daemon) %{_sysconfdir}/eos.client.keytab\n\n%post -n eos-testkeytab\n\ngetent passwd eosnobody || useradd eosnobody\ngetent passwd eosdev || useradd eosdev\n\n#-------------------------------------------------------------------------------\n# Package eos-archive only for >= CC7\n#-------------------------------------------------------------------------------\n%if 0%{?rhel} >= 7\n%package -n eos-archive\nSummary: The EOS archive daemon\nGroup: Applications/File\nExclusiveArch: x86_64 aarch64\n\nBuildRequires: python%{python3_pkgversion}-devel\nRequires: python3-zmq\n\n%if 0%{?rhel} == 9\nBuildRequires:  python-rpm-macros\n%endif\n\n\n# Select xrootd package\n%if %{with eos_xrootd_rh}\n# Install eos-xrootd\n%if 0%{?rhel} >= 7 && 0%{?rhel} <= 10\nRequires: eos-xrootd = %{eos_xrootd_version_min}\n%else\nRequires: xrootd-python >= %{xrootd_version_min}\n%endif\n%else\nRequires: xrootd-python >= %{xrootd_version_min}\n%endif\n\n%description -n eos-archive\nThe EOS archive daemon.\n\n%files -n eos-archive\n%{_sbindir}/eos-backup\n%{_sbindir}/eos-backup-browser\n%defattr(-, eosarchi, c3, -)\n%{_bindir}/eosarchived.py\n%{_bindir}/eosarch_run.py\n%{_bindir}/eosarch_reconstruct.py\n%{python3_sitelib}/eosarch*\n%config(noreplace) %{_sysconfdir}/eosarchived.conf\n%{_prefix}/lib/systemd/system/eosarchived.service\n%config(noreplace) %{_sysconfdir}/sysconfig/eosarchived_env\n%dir %attr(770,eosarchi,daemon) %{_localstatedir}/eos/archive/\n%dir %attr(770,eosarchi,c3)     %{_localstatedir}/log/eos/archive/\n# To make sure python3 also uses /opt/ to search for modules\n%if %{with eos_xrootd_rh}\n%config(noreplace) %{python3_sitelib}/opt-eos-xrootd.pth\n%endif\n\n%pre -n eos-archive\necho \"Make sure eosarchi user and c3 group exist\"\ngetent group c3 >/dev/null 2>&1 || groupadd -r -g 1028 c3\ngetent passwd eosarchi >/dev/null 2>&1 || useradd -r -u 72811 eosarchi\n\n%post -n eos-archive\ncase \"$1\" in\n  1)\n    # Initial installation\n    echo \"Starting EOS archive services\"\n    systemctl daemon-reload > /dev/null 2>&1 || :\n    systemctl restart eosarchived > /dev/null 2>&1 || :\n  ;;\n  2)\n    # Upgrade\n    echo \"Restarting EOS archive services\"\n    systemctl daemon-reload > /dev/null 2>&1 || :\n    systemctl restart eosarchived > /dev/null 2>&1 || :\n  ;;\nesac\n\n%preun -n eos-archive\ncase \"$1\" in\n  0)\n    # Uninstall\n    echo \"Stopping EOS archive services\"\n    systemctl stop eosarchived > /dev/null 2>&1 || :\n  ;;\n  1)\n    # Upgrade - nothing to do\n  ;;\nesac\n\n#-------------------------------------------------------------------------------\n# Package eos-scitokenapi only for >= CC7\n#-------------------------------------------------------------------------------\n%if 0%{?rhel} >= 7\n%package -n eos-scitokenapi\nSummary: The EOS scitokenapi in C,C++ and PYTHON\nGroup: Applications/File\nExclusiveArch: x86_64 aarch64\n\nBuildRequires: python%{python3_pkgversion}-devel\nBuildRequires: python%{python3_pkgversion}-setuptools\n\n%description -n eos-scitokenapi\nThe EOS scitoken API for C,C++ and PYTHON.\n\n%files -n eos-scitokenapi\n%{python3_sitearch}/eosscitoken*\n\n%endif\n%endif\n\n\n#-------------------------------------------------------------------------------\n# Package eos-test\n#-------------------------------------------------------------------------------\n%package -n eos-test\nSummary: The EOS test package\nGroup: Applications/File\nExclusiveArch: x86_64 aarch64\nRequires: bc davix jq dmidecode perf perl\n\n%description -n eos-test\nContains an instance and fuse test script and some test executables and test archives.\n\n%files -n eos-test\n%defattr(-, root, root, -)\n%{_bindir}/eos-grpc-ping\n%{_bindir}/eos-grpc-md\n%{_bindir}/eos-grpc-ns\n%{_bindir}/eos-grpc-insert\n%{_bindir}/eos-shared-hash-test\n%{_sbindir}/eos-instance-test\n%{_sbindir}/eos-instance-test-ci\n%{_sbindir}/eos-accounting-test\n%{_sbindir}/eos-file-cont-detached-test\n%{_sbindir}/eos-lru-test\n%{_sbindir}/eos-rain-test\n%{_sbindir}/eos-drain-test\n%{_sbindir}/eos-balance-test\n%{_sbindir}/eos-altxs-test\n%{_sbindir}/eos-convert-test\n%{_sbindir}/eos-test-utils\n%{_sbindir}/eos-fsck-test\n%{_sbindir}/eos-recycle-test\n%{_sbindir}/eos-fst-close-test\n%{_sbindir}/eos-rename-test\n%{_sbindir}/eos-acl-concurrent\n%{_sbindir}/eos-grpc-test\n%{_sbindir}/eos-groupdrain-test\n%{_sbindir}/eos-traffic-shaping-test\n%{_sbindir}/eos-token-test\n%{_sbindir}/eos-squash-test\n%{_sbindir}/eos-quota-test\n%{_sbindir}/eos-defaultcc-test\n%{_sbindir}/eos-bash\n%{_sbindir}/eos-synctime-test\n%{_sbindir}/eos-rclone-test\n%{_sbindir}/eos-timestamp-test\n%{_sbindir}/eos-macaroon-init\n%{_sbindir}/eos-http-upload-test\n%{_sbindir}/eos-https-functional-test\n%{_sbindir}/eoscp-rain-test\n%{_sbindir}/eos-fusex-tests\n%{_sbindir}/eos-fusex-functional-test\n%{_sbindir}/eos-oc-test\n%{_sbindir}/fusex-benchmark\n%{_sbindir}/eos-fusex-certify\n%{_sbindir}/eos-fusex-ioverify\n%{_sbindir}/eos-fusex-recovery\n%{_sbindir}/eos-test-credential-bindings\n%{_sbindir}/eos-checksum-benchmark\n%{_sbindir}/test-eos-iam-mapfile.py\n%{_sbindir}/xrdcpnonstreaming\n%{_sbindir}/xrdcpabort\n%{_sbindir}/xrdcpappend\n%{_sbindir}/xrdcpappendoverlap\n%{_sbindir}/xrdcpposixcache\n%{_sbindir}/xrdcpextend\n%{_sbindir}/xrdcpholes\n%{_sbindir}/xrdcpbackward\n%{_sbindir}/xrdcpdownloadrandom\n%{_sbindir}/xrdcprandom\n%{_sbindir}/xrdcpshrink\n%{_sbindir}/xrdcptruncate\n%{_sbindir}/xrdcppartial\n%{_sbindir}/xrdcpslowwriter\n%{_sbindir}/xrdcpupdate\n%{_sbindir}/xrdstress\n%{_sbindir}/xrdstress.exe\n%{_sbindir}/eos-io-test\n%{_sbindir}/eos-io-tool\n%{_sbindir}/eos-io-benchmark\n%{_sbindir}/eos-unit-tests\n%{_sbindir}/eos-unit-tests-with-instance\n%{_sbindir}/eos-unit-tests-fst\n%{_sbindir}/eos-unit-tests-with-qdb\n%{_sbindir}/eos-ns-quarkdb-tests\n%{_sbindir}/qclient-tests\n%{_sbindir}/eos-make-flamegraph\n%{_sbindir}/eos-util-flamegraph\n%{_sbindir}/eos-util-stackcollapse\n%attr(444,daemon,daemon) %{_localstatedir}/eos/test/fuse/untar/untar.tgz\n%attr(444,daemon,daemon) %{_localstatedir}/eos/test/fuse/untar/xrootd.tgz\n%if %{with asan}\n%attr(644,daemon,daemon) %{_localstatedir}/eos/test/LeakSanitizer.supp\n%endif\n\n%if %{with coverage}\n#-------------------------------------------------------------------------------\n# Package eos-coverage\n#-------------------------------------------------------------------------------\n%package -n eos-coverage\nSummary: The EOS coverage package\nGroup: Applications/File\nExclusiveArch: x86_64 aarch64\n\n%description -n eos-coverage\nContains all the \".gcno\" files needed to produce coverage data for EOS server.\n\n%files -n eos-coverage\n%defattr(-, root, root, -)\n\n%attr(755,daemon,daemon) %{_localstatedir}/eos/coverage/\n%endif\n\n#-------------------------------------------------------------------------------\n# Package eos-cleanup\n#-------------------------------------------------------------------------------\n%package -n eos-cleanup\nSummary: The EOS test package\nGroup: Applications/File\nExclusiveArch: x86_64 aarch64\n\n%description -n eos-cleanup\nContains an clean-up scripts to remove 'left-overs' of an EOS instance for FST/MGM/FUSE etc.\n\n%files -n eos-cleanup\n%defattr(-, root, root, -)\n%{_sbindir}/eos-uninstall\n%{_sbindir}/eos-log-clean\n%{_sbindir}/eos-fst-clean\n%{_sbindir}/eos-mgm-clean\n%endif\n\n#-------------------------------------------------------------------------------\n# Package eos-mgm-monitoring\n#-------------------------------------------------------------------------------\n%package -n eos-mgm-monitoring\n\nSummary: The EOS mgm monitoring package\nGroup: Applications/File\nRequires: eos-client\n\n%description -n eos-mgm-monitoring\nThe EOS mgm monitoring package creating /var/eos/md/ user specific output\n\n%files -n eos-mgm-monitoring\n%defattr(-, root, root, -)\n%{_sbindir}/eos-mdreport\n%{_sbindir}/eos-mdstat\n%{_sbindir}/eos-reportstat\n%{_sbindir}/eos-inspectorreport\n%{_sbindir}/eos-inspectorstat\n%{_sbindir}/eos-prom-push\n%config(noreplace) %{_sysconfdir}/cron.d/eos-mgm-monitoring\n"
  },
  {
    "path": "fst/CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ninclude_directories(\n  ${CMAKE_SOURCE_DIR}/namespace/ns_quarkdb/qclient/include\n  ${CMAKE_SOURCE_DIR}/common/xrootd-ssi-protobuf-interface/include\n  ${CMAKE_SOURCE_DIR}/common/jwt-cpp/include/\n  ${CMAKE_SOURCE_DIR}/common)\n\n#-------------------------------------------------------------------------------\n# Add XrdCl RAIN plugin library only if explicitly requested by using the\n# BUILD_XRDCL_RAIN_PLUGIN flag.\n#-------------------------------------------------------------------------------\nif(BUILD_XRDCL_RAIN_PLUGIN)\n  add_subdirectory(xrdcl_plugins)\nendif()\n\n#-------------------------------------------------------------------------------\n# Add css plugin library only if explicitly requested by using the\n# BUILD_CSS_PLUGIN flag.\n#-------------------------------------------------------------------------------\nif(BUILD_CSS_PLUGIN)\n  add_subdirectory(css_plugin)\n  install(TARGETS EosFstCss\n  LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n  ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\nendif()\n\n#-------------------------------------------------------------------------------\n# Gfcomplete objects\n#-------------------------------------------------------------------------------\nset(GFCOMPLETE_SRCS\n  layout/gf-complete/src/gf.c\n  layout/gf-complete/src/gf_w128.c\n  layout/gf-complete/src/gf_rand.c\n  layout/gf-complete/src/gf_method.c\n  layout/gf-complete/src/gf_general.c\n  layout/gf-complete/src/gf_w16.c\n  layout/gf-complete/src/gf_w32.c\n  layout/gf-complete/src/gf_w8.c\n  layout/gf-complete/src/gf_w64.c\n  layout/gf-complete/src/gf_w4.c\n  layout/gf-complete/src/gf_wgen.c)\n\nset(GFCOMPLETE_HDRS\n  layout/gf-complete/include/gf_complete.h\n  layout/gf-complete/include/gf_rand.h\n  layout/gf-complete/include/gf_method.h\n  layout/gf-complete/include/gf_int.h\n  layout/gf-complete/include/gf_general.h\n  layout/gf-complete/include/gf_w8.h\n  layout/gf-complete/include/gf_w64.h\n  layout/gf-complete/include/gf_w4.h\n  layout/gf-complete/include/gf_w32.h\n  layout/gf-complete/include/gf_w16.h)\n\nadd_library(GfComplete-Objects OBJECT\n  ${GFCOMPLETE_SRCS}\n  ${GFCOMPLETE_HDRS})\n\ntarget_include_directories(GfComplete-Objects PUBLIC\n  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/layout/gf-complete/include>)\n\ntarget_compile_definitions(GfComplete-Objects PRIVATE\n  -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64)\n\nset_target_properties(GfComplete-Objects PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\n#-------------------------------------------------------------------------------\n# jerasure 2.0 objects\n#-------------------------------------------------------------------------------\nset(JERASURE_SRCS\n  layout/jerasure/src/cauchy.c\n  layout/jerasure/src/galois.c\n  layout/jerasure/src/jerasure.c\n  layout/jerasure/src/liberation.c\n  layout/jerasure/src/reed_sol.c\n  layout/jerasure/src/timing.c)\n\nset(JERASURE_HDRS\n  layout/jerasure/include/cauchy.h\n  layout/jerasure/include/galois.h\n  layout/jerasure/include/jerasure.h\n  layout/jerasure/include/liberation.h\n  layout/jerasure/include/reed_sol.h\n  layout/jerasure/include/timing.h)\n\nadd_library(Jerasure-Objects OBJECT\n  ${JERASURE_SRCS}\n  ${JERASURE_HDRS})\n\ntarget_link_libraries(Jerasure-Objects PUBLIC\n  GfComplete-Objects)\n\ntarget_include_directories(Jerasure-Objects PUBLIC\n  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/layout/jerasure/include>)\n\ntarget_compile_definitions(Jerasure-Objects PRIVATE\n  -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64)\n\nset_target_properties(Jerasure-Objects PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\n#-------------------------------------------------------------------------------\n# EosFstIo-Objects library\n#-------------------------------------------------------------------------------\nadd_library(EosFstIo-Objects OBJECT\n  # File IO interface\n  io/FileIo.cc                   io/FileIo.hh\n  io/local/FsIo.cc               io/local/FsIo.hh\n  io/davix/DavixIo.cc            io/davix/DavixIo.hh\n  io/nfs/NfsIo.cc                io/nfs/NfsIo.hh\n  io/xrd/XrdIo.cc                io/xrd/XrdIo.hh\n  io/xrd/ResponseCollector.cc    io/xrd/ResponseCollector.hh\n  io/AsyncMetaHandler.cc         io/AsyncMetaHandler.hh\n  io/ChunkHandler.cc             io/ChunkHandler.hh\n  io/VectChunkHandler.cc         io/VectChunkHandler.hh\n  io/SimpleHandler.cc            io/SimpleHandler.hh\n  io/FileIoPlugin.cc             io/FileIoPlugin.hh\n  # Checksum interface\n  checksum/CheckSum.cc           checksum/CheckSum.hh\n  checksum/Adler.cc              checksum/Adler.hh\n  # File layout interface\n  layout/LayoutPlugin.cc         layout/LayoutPlugin.hh\n  layout/Layout.cc               layout/Layout.hh\n  layout/PlainLayout.cc          layout/PlainLayout.hh\n  layout/HeaderCRC.cc            layout/HeaderCRC.hh\n  layout/ReplicaParLayout.cc     layout/ReplicaParLayout.hh\n  layout/RainBlock.cc            layout/RainBlock.hh\n  layout/RainGroup.cc            layout/RainGroup.hh\n  layout/RainMetaLayout.cc       layout/RainMetaLayout.hh\n  layout/RaidDpLayout.cc         layout/RaidDpLayout.hh\n  layout/ReedSLayout.cc          layout/ReedSLayout.hh\n  utils/FSPathHandler.cc\n  utils/IoPriority.cc\n  utils/ScanRate.cc)\n\ntarget_link_libraries(EosFstIo-Objects PUBLIC\n  Jerasure-Objects\n  EosCommon\n  DAVIX::DAVIX\n  XROOTD::PRIVATE\n  NFS::NFS)\n\ntarget_compile_definitions(EosFstIo-Objects PRIVATE\n  -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64)\n\ntarget_include_directories(EosFstIo-Objects PUBLIC\n  $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>)\n\nset_target_properties(EosFstIo-Objects PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\nadd_library(EosFstIo SHARED $<TARGET_OBJECTS:EosFstIo-Objects>)\n\ntarget_link_libraries(EosFstIo PUBLIC\n  EosFstIo-Objects\n  Jerasure-Objects\n  GfComplete-Objects\n  EosCrc32c-Objects\n  EosBlake3-Objects\n  ISAL::ISAL\n  ISAL::ISAL_CRYPTO\n  XXHASH::XXHASH\n  NFS::NFS)\n\nset_target_properties(EosFstIo PROPERTIES\n  VERSION ${VERSION}\n  SOVERSION ${VERSION_MAJOR}\n  MACOSX_RPATH TRUE)\n\ninstall(TARGETS EosFstIo\n  LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n  ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\n\n#-------------------------------------------------------------------------------\n# EosFstIo-Static library\n#-------------------------------------------------------------------------------\nadd_library(EosFstIo-Static STATIC $<TARGET_OBJECTS:EosFstIo-Objects>)\n\ntarget_link_libraries(EosFstIo-Static PUBLIC\n  EosFstIo-Objects\n  Jerasure-Objects\n  GfComplete-Objects\n  EosCrc32c-Objects\n  EosBlake3-Objects\n  ISAL::ISAL\n  ISAL::ISAL_CRYPTO\n  XXHASH::XXHASH\n  NFS::NFS)\n\ntarget_compile_definitions(EosFstIo-Static PRIVATE\n  -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64)\n\nset_target_properties(EosFstIo-Static PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\n#-------------------------------------------------------------------------------\n# eoscp executable\n#-------------------------------------------------------------------------------\nadd_executable(eoscp eoscp.cc)\n\ntarget_link_libraries(eoscp PRIVATE EosFstIo-Static)\n\ntarget_compile_definitions(eoscp PRIVATE\n  -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64)\n\nset_target_properties(\n  eoscp PROPERTIES\n  INSTALL_RPATH \"${EOS_RPATH}\"\n  SKIP_RPATH FALSE\n  SKIP_BUILD_RPATH FALSE\n  BUILD_WITH_INSTALL_RPATH TRUE\n  INSTALL_RPATH_USE_LINK_PATH TRUE)\n\ninstall(TARGETS eoscp\n  LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n  ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\n\nif(NOT CLIENT)\n#-------------------------------------------------------------------------------\n# EosFstOss library\n#-------------------------------------------------------------------------------\nadd_library(EosFstOss MODULE\n  XrdFstOss.cc XrdFstOss.hh\n  XrdFstOssFile.cc XrdFstOssFile.hh\n  checksum/CheckSum.cc checksum/CheckSum.hh\n  checksum/Adler.cc checksum/Adler.hh\n  utils/ScanRate.cc utils/ScanRate.hh\n  $<TARGET_OBJECTS:EosCrc32c-Objects>\n  $<TARGET_OBJECTS:EosBlake3-Objects>)\n\ntarget_link_libraries(EosFstOss PRIVATE\n  EosCommon\n  ISAL::ISAL\n  ISAL::ISAL_CRYPTO\n  XXHASH::XXHASH)\n\nadd_library(EosFstOss-Static STATIC\n  XrdFstOss.cc XrdFstOss.hh\n  XrdFstOssFile.cc XrdFstOssFile.hh\n  checksum/CheckSum.cc checksum/CheckSum.hh\n  checksum/Adler.cc checksum/Adler.hh\n  utils/ScanRate.cc utils/ScanRate.hh\n  $<TARGET_OBJECTS:EosCrc32c-Objects>\n  $<TARGET_OBJECTS:EosBlake3-Objects>)\n\ntarget_link_libraries(EosFstOss-Static PRIVATE\n  EosCommon\n  ISAL::ISAL\n  ISAL::ISAL_CRYPTO\n  XXHASH::XXHASH)\n\n#-------------------------------------------------------------------------------\n# XrdEosFst library\n#-------------------------------------------------------------------------------\nset(XRDEOSFST_SRCS\n  Config.cc\n  Load.cc\n  Health.cc\n  ScanDir.cc\n  io/FileIoPlugin-Server.cc\n  # OFS layer implementation\n  XrdFstOfs.cc                   XrdFstOfs.hh\n  XrdFstOfsFile.cc               XrdFstOfsFile.hh\n  # Storage interface\n  storage/Communicator.cc\n  storage/ErrorReport.cc\n  storage/FileSystem.cc          storage/MgmSyncer.cc\n  storage/Publish.cc             storage/Remover.cc\n  storage/Report.cc              storage/Scrub.cc\n  storage/Storage.cc             storage/Supervisor.cc\n  storage/Verify.cc\n  # Utils\n  utils/OpenFileTracker.cc\n  # File metadata interface\n  filemd/FmdHandler.cc\n  filemd/FmdMgm.cc\n  filemd/FmdAttr.cc\n  # HTTP interface\n  http/HttpServer.cc    http/HttpServer.hh\n  http/HttpHandler.cc   http/HttpHandler.hh\n  http/HttpHandlerFstFileCache.cc   http/HttpHandlerFstFileCache.hh\n  http/s3/S3Handler.cc  http/s3/S3Handler.hh\n  # EosFstIo interface\n  io/local/LocalIo.cc  io/local/LocalIo.hh\n  utils/XrdOfsPathHandler.cc\n  utils/DiskMeasurements.cc\n  storage/TrafficShaping.hh storage/TrafficShaping.cc\n)\n\nadd_library(XrdEosFst-Objects OBJECT ${XRDEOSFST_SRCS})\n\n#-------------------------------------------------------------------------------\n# Disable -Wsign-compare warnings due to\n# grpcpp/support/proto_buffer_reader.h:157:24: warning: comparison of\n# integer expressions of different signedness: ‘uint64_t’ {aka ‘long\n# unsigned int’} and ‘int’ [-Wsign-compare\n#-------------------------------------------------------------------------------\ntarget_compile_options(XrdEosFst-Objects PUBLIC -Wno-sign-compare)\n\ntarget_link_libraries(XrdEosFst-Objects PUBLIC\n  EosFstProto-Objects\n  EosCliProto-Objects\n  EosNsQuarkdbProto-Objects\n  XrdSsiPbEosCta-Objects\n  EosConsoleHelpers-Objects\n  EosFstIo\n  EosCommonServer\n  EosNsCommon\n  XROOTD::UTILS\n  XROOTD::PRIVATE\n  XROOTD::SERVER\n  XFS::XFS)\n\nif (Linux)\n  target_link_libraries(XrdEosFst-Objects PUBLIC stdc++fs)\nendif()\n\nif(TARGET PROCPS::PROCPS)\n  target_compile_definitions(XrdEosFst-Objects PRIVATE PROCPS3)\n  target_link_libraries(XrdEosFst-Objects PUBLIC PROCPS::PROCPS)\nendif()\n\nif(TARGET procps::libproc2)\n  target_link_libraries(XrdEosFst-Objects PUBLIC procps::libproc2)\nendif()\n\nset_target_properties(XrdEosFst-Objects PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\ntarget_compile_definitions(XrdEosFst-Objects PUBLIC\n  -DDAEMONUID=${DAEMONUID} -DDAEMONGID=${DAEMONGID})\n\nadd_library(XrdEosFst-${XRDPLUGIN_SOVERSION} MODULE\n  $<TARGET_OBJECTS:XrdEosFst-Objects>)\n\ntarget_link_libraries(XrdEosFst-${XRDPLUGIN_SOVERSION} PRIVATE\n  XrdEosFst-Objects\n  EosConsoleHelpers-Objects\n  XROOTD::SSI)\n\ntarget_compile_definitions(XrdEosFst-${XRDPLUGIN_SOVERSION} PUBLIC\n  -DDAEMONUID=${DAEMONUID} -DDAEMONGID=${DAEMONGID})\n\nadd_library(XrdEosFst-Static STATIC $<TARGET_OBJECTS:XrdEosFst-Objects>)\n\ntarget_compile_definitions(XrdEosFst-Static PUBLIC\n  -DDAEMONUID=${DAEMONUID} -DDAEMONGID=${DAEMONGID})\n\ntarget_link_libraries(XrdEosFst-Static PUBLIC\n  XrdEosFst-Objects\n  EosConsoleHelpers-Objects\n  EosCommonServer\n  EosFstIo\n  EosNsCommon\n  XROOTD::SSI)\n\ninstall(TARGETS EosFstOss XrdEosFst-${XRDPLUGIN_SOVERSION}\n  LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n  ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\n\n#-------------------------------------------------------------------------------\n# EosFstHttp library\n#-------------------------------------------------------------------------------\nadd_library(EosFstHttp MODULE\n  http/xrdhttp/EosFstHttpHandler.hh\n  http/xrdhttp/EosFstHttpHandler.cc)\n\ntarget_link_libraries(EosFstHttp PUBLIC\n  EosConsoleHelpers-Objects\n  EosFstIo\n  # @todo(gbitzes) move calculateEtag to common so that we don't have to couple\n  # the fst lib with the ns one\n  EosNsCommon\n  EosCommonServer\n  XROOTD::SSI\n  XROOTD::HTTP)\n\ninstall(TARGETS EosFstHttp\n  LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n  ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\n\n#-------------------------------------------------------------------------------\n# Other executables\n#-------------------------------------------------------------------------------\nadd_executable(eos-rain-check tools/RainCheck.cc)\ntarget_link_libraries(eos-rain-check PRIVATE EosFstIo-Static)\ntarget_compile_definitions(eos-rain-check PRIVATE\n  -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64)\n\nadd_executable(eos-rain-hd-dump  tools/RainHdrDump.cc)\ntarget_link_libraries(eos-rain-hd-dump PRIVATE EosFstIo XROOTD::SERVER)\n\nadd_executable(eos-check-blockxs\n  tools/CheckBlockXS.cc\n  checksum/Adler.cc\n  checksum/CheckSum.cc)\n\nadd_executable(eos-compute-blockxs\n  tools/ComputeBlockXS.cc\n  checksum/Adler.cc\n  checksum/CheckSum.cc)\n\nadd_executable(eos-scan-fs\n  tools/ScanXS.cc\n  ScanDir.cc\n  Load.cc\n  filemd/FmdHandler.cc\n  filemd/FmdMgm.cc\n  Config.cc\n  checksum/Adler.cc\n  checksum/CheckSum.cc)\n\ntarget_compile_definitions(eos-scan-fs PUBLIC -D_NOOFS=1)\n\nadd_executable(eos-adler32\n  tools/Adler32.cc\n  checksum/Adler.cc\n  checksum/CheckSum.cc)\n\nadd_executable(eos-checksum\n  tools/CheckSum.cc\n  checksum/CheckSum.cc)\n\nadd_executable(eos-ioping tools/IoPing.c)\ntarget_compile_options(eos-ioping PRIVATE -std=gnu99)\ntarget_link_libraries(eos-ioping PRIVATE GLIBC::RT GLIBC::M)\ntarget_link_libraries(eos-check-blockxs PRIVATE EosFstIo XROOTD::SERVER)\ntarget_link_libraries(eos-compute-blockxs PRIVATE EosFstIo XROOTD::SERVER)\ntarget_link_libraries(eos-adler32 PRIVATE EosFstIo XROOTD::SERVER)\ntarget_link_libraries(eos-checksum PRIVATE EosFstIo XROOTD::SERVER)\ntarget_link_libraries(eos-scan-fs PRIVATE\n  EosFstIo EosCommonServer EosNsCommon-Static XROOTD::SERVER)\n\nif (Linux)\n  target_link_libraries(eos-scan-fs PRIVATE stdc++fs)\nendif()\n\nadd_executable(eos-create-file-pattern\n  utils/CreateFileWithPattern.cc)\n\nadd_executable(eos-readv-pattern\n  utils/CheckFileReadWithPattern.cc)\n\ntarget_link_libraries(eos-readv-pattern PRIVATE XROOTD::CL)\n\nadd_executable(eos-fmd-tool\n  tools/ConvertFileMD.cc\n  # File metadata interface\n  filemd/FmdHandler.cc\n  filemd/FmdAttr.cc\n  filemd/FmdMgm.cc\n  utils/FSPathHandler.cc)\n\ntarget_link_libraries(eos-fmd-tool PRIVATE\n  EosFstIo\n  EosCommonServer\n  XrdEosFst-Static)\n\nadd_executable(eos-disk-measurements\n  utils/DiskMeasurementsMain.cc\n  utils/DiskMeasurements.cc)\n\ntarget_link_libraries(eos-disk-measurements PRIVATE EosCommon)\n\ninstall(PROGRAMS\n  tools/eosfstregister\n  tools/eosfstinfo\n  DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR})\n\ninstall(TARGETS\n  eos-ioping eos-adler32 eos-checksum eos-rain-hd-dump\n  eos-check-blockxs eos-compute-blockxs eos-scan-fs\n  eos-create-file-pattern eos-readv-pattern eos-fmd-tool\n  eos-rain-check\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR})\n\nendif()\n"
  },
  {
    "path": "fst/Config.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Config.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/Config.hh\"\n\n#include \"common/AssistedThread.hh\"\n#include \"common/InstanceName.hh\"\n#include \"common/Logging.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/utils/RandUtils.hh\"\n#include <chrono>\n#include <thread>\n#include <vector>\n\nEOSFSTNAMESPACE_BEGIN\n\n// Static initialization\nConfig gConfig;\n\n\n//------------------------------------------------------------------------------\n// Get the current manager hostname and port\n//------------------------------------------------------------------------------\nstd::string\nConfig::GetManager() const\n{\n  XrdSysMutexHelper scope_lock(Mutex);\n  return gConfig.Manager.c_str();\n}\n\n//------------------------------------------------------------------------------\n// Wait for the current manager hostname and port\n//------------------------------------------------------------------------------\nstd::string\nConfig::WaitManager() const\n{\n  do {\n    {\n      XrdSysMutexHelper scope_lock(Mutex);\n\n      if (gConfig.Manager.length()) {\n        return gConfig.Manager.c_str();\n      }\n    }\n    eos_static_info(\"%s\", \"msg=\\\"wait for manager info ...\\\"\");\n    std::this_thread::sleep_for(std::chrono::seconds(1));\n  } while (true);\n}\n\n//------------------------------------------------------------------------------\n// Get node config queue\n//------------------------------------------------------------------------------\nXrdOucString\nConfig::getFstNodeConfigQueue(const std::string& location, bool blocking,\n                              ThreadAssistant* assistant)\n{\n  while (!configQueueInitialized && blocking) {\n    eos_static_info(\"msg=\\\"waiting for config queue in %s ...\\\"\",\n                    location.c_str());\n    std::this_thread::sleep_for(std::chrono::seconds(2));\n\n    if (assistant && assistant->terminationRequested()) {\n      break;\n    }\n  }\n\n  std::unique_lock<std::mutex> lock(mConfigQueueMtx);\n  return FstNodeConfigQueue;\n}\n\n//------------------------------------------------------------------------------\n// Set node config queue\n//------------------------------------------------------------------------------\nvoid Config::setFstNodeConfigQueue(const std::string& value)\n{\n  std::unique_lock<std::mutex> lock(mConfigQueueMtx);\n\n  if (configQueueInitialized) {\n    return;\n  }\n\n  FstNodeConfigQueue = value.c_str();\n  std::vector<std::string> parts =\n    common::StringTokenizer::split<std::vector<std::string>>(value.c_str(), '/');\n  common::InstanceName::set(parts[1]);\n  mNodeHashLocator = common::SharedHashLocator(parts[1],\n                     common::SharedHashLocator::Type::kNode, parts[3]);\n  configQueueInitialized = true;\n}\n\n//------------------------------------------------------------------------------\n// Get node hash locator\n//------------------------------------------------------------------------------\ncommon::SharedHashLocator\nConfig::getNodeHashLocator(const std::string& location, bool blocking)\n{\n  while (!configQueueInitialized && blocking) {\n    std::this_thread::sleep_for(std::chrono::seconds(2));\n    eos_static_info(\"Waiting for config queue in %s ... \", location.c_str());\n  }\n\n  if (configQueueInitialized) {\n    return mNodeHashLocator;\n  }\n\n  return {};\n}\n\n//------------------------------------------------------------------------------\n// Get publishing interval\n//------------------------------------------------------------------------------\nstd::chrono::seconds Config::getPublishInterval()\n{\n  XrdSysMutexHelper lock(Mutex);\n  int localInterval = PublishInterval;\n  lock.UnLock();\n\n  if (localInterval < 2 || localInterval > 3600) {\n    // Strange value, default to 10\n    return std::chrono::seconds(10);\n  }\n\n  return std::chrono::seconds(localInterval);\n}\n\n//------------------------------------------------------------------------------\n// Get randomized  publishing interval\n//------------------------------------------------------------------------------\nstd::chrono::milliseconds Config::getRandomizedPublishInterval()\n{\n  std::chrono::seconds interval = getPublishInterval();\n  return std::chrono::milliseconds(\n      eos::common::getRandom(interval.count() * 500, interval.count() * 1500));\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/Config.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Config.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"fst/Namespace.hh\"\n#include \"common/Locators.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdSys/XrdSysPthread.hh>\n#include <atomic>\n#include <chrono>\n#include <mutex>\n\n//! Forward declaration\nclass ThreadAssistant;\n\nEOSFSTNAMESPACE_BEGIN\n\nclass Config\n{\npublic:\n  bool autoBoot; // -> indicates if the node tries to boot automatically or waits for a boot message from a master\n  XrdOucString FstMetaLogDir; //  Directory containing the meta data log files\n  XrdOucString FstAuthDir; // Directory needed for file transfers among FSTs\n  XrdOucString FstOfsBrokerUrl; // Url of the message broker\n  XrdOucString\n  FstDefaultReceiverQueue; // Queue where we are sending to by default\n  XrdOucString FstQueue; // our queue name\n  XrdOucString FstQueueWildcard; // our queue match name\n  XrdOucString FstConfigQueueWildcard; // our configuration queue match name\n  XrdOucString FstHostPort; // <host>:<port>\n  XrdOucString FstS3Credentials; // S3 storage credentials <access>:<secret>\n  XrdOucString Manager; // <host>:<port>\n  XrdOucString KernelVersion; // kernel version of the host\n  std::string ProtoWFEndpoint; // proto wf endpoint (typically CTA frontend)\n  std::string ProtoWFResource; //  proto wf resource (typically CTA frontend)\n  int PublishInterval; // Interval after which filesystem information should be published\n  XrdOucString StartDate; // Time when daemon was started\n  XrdOucString KeyTabAdler; // adler string of the keytab file\n  XrdOucString HostAlias; // alias for this hostname to use in redirection\n  XrdOucString PortAlias; // alias for the port to use in redirection\n  mutable XrdSysMutex Mutex; // lock for dynamic updates like 'Manager'\n  bool protowfusegrpc; // use the xrootd or the grpc protocol\n  std::string JwtTokenPath; // where to find the JWT to be used in WFE calls for authentication when gRPC is used\n  bool protowfusegrpctls = false; // use TLS encrypted connections or plaintext connections for grpc\n\n  Config()\n  {\n    autoBoot = false;\n    PublishInterval = 10;\n    Manager = \"\";\n  }\n\n  ~Config() = default;\n\n  //----------------------------------------------------------------------------\n  //! Get the current manager hostname and port\n  //----------------------------------------------------------------------------\n  std::string GetManager() const;\n\n  //----------------------------------------------------------------------------\n  //! Wait for the current manager hostname and port\n  //----------------------------------------------------------------------------\n  std::string WaitManager() const;\n\n  XrdOucString\n  getFstNodeConfigQueue(const std::string& location = \"\", bool blocking = true,\n                        ThreadAssistant* assistant = nullptr);\n\n  common::SharedHashLocator getNodeHashLocator(const std::string& location = \"\",\n      bool blocking = true);\n\n  void setFstNodeConfigQueue(const std::string& value);\n  std::chrono::seconds getPublishInterval();\n\n  // Return a random number, uniformly distributed within\n  // [(1/2) publishInterval, (3/2) publishInterval]\n  std::chrono::milliseconds getRandomizedPublishInterval();\n\nprivate:\n  //! Queue holding this node's configuration settings\n  std::mutex mConfigQueueMtx;\n  XrdOucString FstNodeConfigQueue;\n  std::atomic<bool> configQueueInitialized {false};\n  eos::common::SharedHashLocator mNodeHashLocator;\n  // Random number generator\n  std::mutex generatorMutex;\n};\n\nextern Config gConfig;\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/Deletion.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Deletion.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"fst/Namespace.hh\"\n#include \"common/FileId.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucTokenizer.hh>\n#include <vector>\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class Deletion\n//------------------------------------------------------------------------------\nclass Deletion\n{\npublic:\n  std::vector<unsigned long long> mFidVect;\n  unsigned long mFsid;\n\n  //------------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param id_vect file ids to delete\n  //! @param fsid filesystem id\n  //------------------------------------------------------------------------------\n  Deletion(std::vector<unsigned long long> id_vect, unsigned long fsid):\n    mFidVect(id_vect), mFsid(fsid)\n  {}\n\n  //------------------------------------------------------------------------------\n  //! Destructor\n  //------------------------------------------------------------------------------\n  ~Deletion() = default;\n\n  //------------------------------------------------------------------------------\n  //! Create deletion object from the provided opaque information\n  //!\n  //! @param opaque opaque info\n  //!\n  //! @return deletion object\n  //------------------------------------------------------------------------------\n  static std::unique_ptr<Deletion>\n  Create(XrdOucEnv* capOpaque)\n  {\n    XrdOucString hexfids = \"\";\n    XrdOucString hexfid = \"\";\n    XrdOucString access = \"\";\n    const char* sfsid = 0;\n    std::vector <unsigned long long> idvector;\n    hexfids = capOpaque->Get(\"mgm.fids\");\n    sfsid = capOpaque->Get(\"mgm.fsid\");\n    access = capOpaque->Get(\"mgm.access\");\n\n    if ((access != \"delete\") || !hexfids.length() || !sfsid) {\n      return nullptr;\n    }\n\n    while (hexfids.replace(\",\", \" \")) {\n    };\n\n    XrdOucTokenizer subtokenizer((char*) hexfids.c_str());\n\n    subtokenizer.GetLine();\n\n    while (true) {\n      hexfid = subtokenizer.GetToken();\n\n      if (hexfid.length()) {\n        idvector.push_back(eos::common::FileId::Hex2Fid(hexfid.c_str()));\n      } else {\n        break;\n      }\n    }\n\n    unsigned long fsid = atoi(sfsid);\n    return std::make_unique<Deletion>(std::move(idvector), fsid);\n  }\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/Health.cc",
    "content": "//------------------------------------------------------------------------------\n// File: Health.cc\n// Author: Paul Hermann Lensing <paul.lensing@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"Health.hh\"\n#include \"fst/storage/FileSystem.hh\"\n#include \"common/ShellCmd.hh\"\n#include <unordered_set>\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//                        **** Class DiskHealth ****\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Get health information about a certain device\n//------------------------------------------------------------------------------\nstd::map<std::string, std::string>\nDiskHealth::getHealth(const std::string& devpath)\n{\n  std::string dev = Load::DevMap(devpath);\n\n  if (dev.empty()) {\n    return std::map<std::string, std::string>();\n  }\n\n  // RAID setups; (device-mapper) multipath will be looked up as dm-XX\n  if (dev[0] == 'm') {\n    return parse_mdstat(dev);\n  }\n\n  // Remove partition digits, we need the actual device name for smartctl...\n  // only for /dev/sdaX devices, NOT /dev/dm-13 and comparables, \n  // where the trailing number is not a partition indicator\n  // edge case would be /dev/sdm1 where dev == \"sdm1\"\n  if (dev.find(\"dm-\") != 0) {\n    while (isdigit(dev.back())) {\n      dev.pop_back();\n    }\n  }\n\n  std::lock_guard<std::mutex> lock(mMutex);\n  return smartctl_results[dev];\n}\n\n//------------------------------------------------------------------------------\n// Update health information for all the registered devices\n//------------------------------------------------------------------------------\nvoid\nDiskHealth::Measure()\n{\n  std::unordered_set<std::string> dev_names;\n  std::map<std::string, std::map<std::string, std::string>> tmp;\n  {\n    // Collect the name of the devices\n    std::lock_guard<std::mutex> lock(mMutex);\n\n    for (const auto& elem : smartctl_results) {\n      dev_names.insert(elem.first);\n    }\n  }\n\n  for (const auto& elem : dev_names) {\n    tmp[elem][\"summary\"] = smartctl(elem.c_str());\n    tmp[elem][\"attributes\"] = smartattributes(elem.c_str());\n  }\n\n  std::lock_guard<std::mutex> lock(mMutex);\n  std::swap(smartctl_results, tmp);\n}\n\n//------------------------------------------------------------------------------\n// Parse /proc/mdstat to obtain raid health\n//------------------------------------------------------------------------------\nstd::map<std::string, std::string>\nDiskHealth::parse_mdstat(const std::string& device,\n                         const std::string& mdstat_path)\n{\n  std::string line, buffer;\n  std::map<std::string, std::string> health;\n  std::ifstream file(mdstat_path.c_str());\n  health[\"summary\"] = \"no mdstat\";\n\n  while (std::getline(file, line)) {\n    auto pos = line.find(device + \" : \");\n\n    if (pos == std::string::npos) {\n      continue;\n    }\n\n    buffer = line;\n\n    // Read in also the next lines until empty\n    while (std::getline(file, line)) {\n      // Trim whitespaces\n      while (!line.empty() && line.back() == ' ') {\n        line.pop_back();\n      }\n\n      if (line.empty()) {\n        break;\n      }\n\n      buffer += \"\\n\";\n      buffer += line;\n    }\n\n    int redundancy_factor;\n    pos = buffer.find(\"raid\", pos);\n\n    // Skip line if not holding raid info\n    if (pos == std::string::npos) {\n      continue;\n    }\n\n    auto c = buffer[pos + 4];\n\n    switch (c) {\n    case '0' :\n      health[\"redundancy_factor\"] = \"0\";\n      redundancy_factor = 0;\n      break;\n\n    case '1' :\n    case '5' :\n      health[\"redundancy_factor\"] = \"1\";\n      redundancy_factor = 1;\n      break;\n\n    case '6' :\n      health[\"redundancy_factor\"] = \"2\";\n      redundancy_factor = 2;\n      break;\n\n    default:\n      health[\"summary\"] = \"unknown raid\";\n      return health;\n    }\n\n    pos = buffer.find(\"blocks\", pos);\n\n    if (pos == std::string::npos) {\n      break;\n    }\n\n    pos = buffer.find(\"[\", pos);\n\n    if (pos == std::string::npos) {\n      break;\n    }\n\n    health[\"drives_total\"] = buffer.substr(pos + 1,\n                                           buffer.find(\"/\", pos) - pos - 1);\n    auto drives_total = strtoll(health[\"drives_total\"].c_str(), 0, 10);\n    pos = buffer.find(\"/\", pos);\n    health[\"drives_healthy\"] = buffer.substr(pos + 1, buffer.find(\"]\",\n                               pos) - pos - 1);\n    auto drives_healthy = strtoll(health[\"drives_healthy\"].c_str(), 0, 10);\n    auto drives_failed = drives_total - drives_healthy;\n    health[\"drives_failed\"] = std::to_string((long long int) drives_failed);\n    auto end = buffer.find(\"md\", pos);\n    pos = buffer.find(\"recovery\", pos);\n    health[\"indicator\"] = pos < end ? \"1\" : \"0\";\n    // Build summary string\n    std::string summary;\n\n    if (health[\"indicator\"] == \"1\") {\n      summary += \"! \";\n    }\n\n    summary += health[\"drives_healthy\"] + \"/\" + health[\"drives_total\"];\n    summary += \" (\";\n\n    if (redundancy_factor >= drives_failed) {\n      summary += \"+\";\n    }\n\n    summary += std::to_string((long long int) redundancy_factor -\n                              (drives_total - drives_healthy));\n    summary += \")\";\n    health[\"summary\"] = summary;\n    break;\n  }\n\n  return health;\n}\n\n//------------------------------------------------------------------------------\n// Obtain health of a single locally attached storage device by evaluating\n// S.M.A.R.T values.\n//------------------------------------------------------------------------------\nstd::string DiskHealth::smartctl(const char* device)\n{\n  std::string command(\"smartctl -q silent -a /dev/\");\n\n  // dev name starts with mpath, it's scsi multipath from linux device mapper,\n  // i.e. /dev/dm-XY\n  if(std::string(device).find(\"dm-\") == 0) {\n    command = std::string(\"smartctl -q silent --device=scsi -a /dev/\");\n  }\n\n  command += device;\n  eos::common::ShellCmd scmd(command.c_str());\n  eos::common::cmd_status rc = scmd.wait(5);\n\n  if (rc.exit_code == 0) {\n    return \"OK\";\n  }\n\n  if (rc.exit_code == 127) {\n    return \"no smartctl\";\n  }\n\n  int mask = 1;\n\n  for (int i = 0; i < 8; i++) {\n    if (rc.exit_code & mask) {\n      switch (i) {\n      case 0:\n      case 1:\n      case 2:\n        return \"N/A\";\n\n      case 3:\n        return \"FAILING\";\n\n      case 4:\n        return \"Check\";\n\n      // Bit 5: SMART status check returned \"DISK OK\" but we found that\n      // some (usage or prefail) Attributes have been <= threshold at some\n      // time in the past.\n      // -> once bit 5 is set, this is game over for the rest of the disk\n      // life again just like bit 6.\n      //\n      // Bit 6: The device error log contains records of errors.\n      // -> some disks have 1 error log entry that was created upon first\n      // start (powertime hour 0 day 0) and this cannot be deleted. The\n      // disk is stuck in Check state for its entire life...\n      case 5:\n      case 6:\n        return \"OK\";\n\n      case 7:\n        return \"Check\";\n      }\n    }\n\n    mask = mask << 1;\n  }\n\n  return \"invalid\";\n}\n\n//------------------------------------------------------------------------------\n// Obtain smart attributes of an attached local storage device\n//------------------------------------------------------------------------------\nstd::string DiskHealth::smartattributes(const char* device)\n{\n  std::string command(\"smartctl -x -j /dev/\");\n\n  // dev name starts with mpath, it's scsi multipath from linux device mapper,\n  // i.e. /dev/dm-XY\n  if(std::string(device).find(\"dm-\") == 0) {\n    command = std::string(\"smartctl -x -j --device=scsi -a /dev/\");\n  }\n\n  command += device;\n  // write output into a dedicated file\n  command += \" > \";\n  std::string tmpname = \"/tmp/.smartattributes-\";\n  tmpname += device;\n  command += tmpname;\n  \n  eos::common::ShellCmd scmd(command.c_str());\n  eos::common::cmd_status rc = scmd.wait(5);\n\n  if (rc.exit_code != 0) {\n    return \"\";\n  }\n\n  if (rc.exit_code == 127) {\n    return \"{}\";\n  }\n\n  std::string retval;\n  eos::common::StringConversion::LoadFileIntoString(tmpname.c_str(), retval);\n  return retval;\n}\n\n//------------------------------------------------------------------------------\n//                        **** Class Health ****\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nHealth::Health(unsigned int ival_minutes):\n  mSkip(false), mIntervalMin(ival_minutes)\n{\n  if (mIntervalMin == 0) {\n    mIntervalMin = 1;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nHealth::~Health()\n{\n}\n\n//------------------------------------------------------------------------------\n// Method starting the health monitoring thread\n//------------------------------------------------------------------------------\nvoid\nHealth::Monitor()\n{\n  monitoringThread.reset(&Health::Measure, this);\n  monitoringThread.setName(\"Health-Monitor\");\n}\n\n//------------------------------------------------------------------------------\n// Loop run by the monitoring thread to keep updated the disk health info.\n//------------------------------------------------------------------------------\nvoid\nHealth::Measure(ThreadAssistant& assistant)\n{\n  while (!assistant.terminationRequested()) {\n    mDiskHealth.Measure();\n\n    for (unsigned int i = 0; i < mIntervalMin; i++) {\n      if (assistant.terminationRequested()) {\n        return;\n      }\n\n      assistant.wait_for(std::chrono::seconds(60));\n\n      if (mSkip) {\n        mSkip = false;\n        break;\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get disk health information for a specific device. If no measurements are\n// available then trigger the monitoring thread to do an update.\n//------------------------------------------------------------------------------\nstd::map<std::string, std::string>\nHealth::getDiskHealth(const std::string& devpath)\n{\n  auto result = mDiskHealth.getHealth(devpath);\n\n  // If we don't have any result, don't wait for interval timeout to measure\n  if (result.empty()) {\n    mSkip = true; // this doesn't need a mutex, worst case we wait 1 more min.\n  }\n\n  return result;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/Health.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Health.hh\n//! @author Paul Hermann Lensing <paul.lensing@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_HEALTH_HH__\n#define __EOSFST_HEALTH_HH__\n\n#include \"fst/Namespace.hh\"\n#include <mutex>\n#include <map>\n#include <atomic>\n#include \"common/AssistedThread.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class retrieving disk health information\n//------------------------------------------------------------------------------\nclass DiskHealth\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Get health information about a certain device\n  //!\n  //! @param devpath path of the targeted device\n  //!\n  //! @return map of health parameters and values\n  //----------------------------------------------------------------------------\n  std::map<std::string, std::string> getHealth(const std::string& devpath);\n\n  //----------------------------------------------------------------------------\n  //! Update health information for all the registered devices\n  //----------------------------------------------------------------------------\n  void Measure();\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Obtain health of a single locally attached storage device by evaluating\n  //! S.M.A.R.T values.\n  //!\n  //! @param device targeted device\n  //!\n  //! @return string representin the result of smartctl run on the targeted\n  //!         device. Can be one of the following: OK, no smartclt, N/A, Check,\n  //!         invalid, FAILING.\n  //----------------------------------------------------------------------------\n  std::string smartctl(const char* device);\n\n  //----------------------------------------------------------------------------\n  //! Obtain smart attributes\n  //!\n  //! @param device targeted device\n  //!\n  //! @return json output with all attributes\n  //----------------------------------------------------------------------------\n  std::string smartattributes(const char* device);\n\n  //! Map holding the smartclt results\n  std::map<std::string, std::map<std::string, std::string>> smartctl_results;\n  std::mutex mMutex; ///< Protect acces to the smartctl_results/attributes map\n\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  //----------------------------------------------------------------------------\n  //! Parse /proc/mdstat to obtain raid health. Existing indicator shows\n  //! rebuild in progress.\n  //!\n  //! @param device targeted device\n  //! @param mdstat_path path of the mdstat file\n  //!\n  //! @return map of health parameters and values\n  //----------------------------------------------------------------------------\n  std::map<std::string, std::string>\n  parse_mdstat(const std::string& device,\n               const std::string& mdstat_path = \"/proc/mdstat\");\n\n};\n\n//------------------------------------------------------------------------------\n//! Class Health\n//------------------------------------------------------------------------------\nclass Health\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  Health(unsigned int ival_minutes = 15);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~Health();\n\n  //----------------------------------------------------------------------------\n  //! Method starting the health monitoring thread\n  //----------------------------------------------------------------------------\n  void Monitor();\n\n  //----------------------------------------------------------------------------\n  //! Loop run by the monitoring thread to keep updated the disk health info.\n  //----------------------------------------------------------------------------\n  void Measure(ThreadAssistant &assistant);\n\n  //----------------------------------------------------------------------------\n  //! Get disk health information for a specific device. If no measurements\n  //! are available then trigger the monitoring thread to do an update.\n  //!\n  //! @param devpath targeted device\n  //!\n  //! @return map of health parameters and values\n  //----------------------------------------------------------------------------\n  std::map<std::string, std::string> getDiskHealth(const std::string& devpath);\n  uint64_t getPowerOnHours(const std::string& devpath);\n  \nprivate:\n  ///< Trigger update thread without waiting for the whole interval to elapse\n  std::atomic<bool> mSkip;\n  AssistedThread monitoringThread; ///< Monitoring thread\n  unsigned int mIntervalMin; ///< Minutes interval when monitoring thread runs\n  DiskHealth mDiskHealth; ///< Objecting collecting disk (health) information\n};\n\nEOSFSTNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "fst/Load.cc",
    "content": "//------------------------------------------------------------------------------\n// File: Load.cc\n// Author: Andreas-Joachim Peters <apeters@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/Load.hh\"\n#include \"common/Timing.hh\"\n#include \"common/StringConversion.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <fstream>\n#include <cstdio>\n#include <cstdlib>\n#include <errno.h>\n#include <sys/stat.h>\n\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//                              Load Class\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nLoad::Load(unsigned int ival):\n  mTid(0)\n{\n  mInterval = ival;\n\n  if (mInterval == 0) {\n    mInterval = 1;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nLoad::~Load()\n{\n  if (mTid) {\n    XrdSysThread::Cancel(mTid);\n    XrdSysThread::Join(mTid, 0);\n    mTid = 0;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get device name mounted at the given path\n//-----------------------------------------------------------------------------\nconst std::string\nLoad::DevMap(const std::string& dev_path)\n{\n  static time_t loadtime = 0;\n  static std::map<std::string, std::string> dev_map;\n  static XrdSysMutex mutex_map; // Protect access to the dev_map\n  std::string mapped_dev;\n\n  if (!dev_path.empty() && dev_path[0] == '/') {\n    struct stat stbuf;\n    XrdSysMutexHelper scope_lock(&mutex_map);\n\n    if (!(stat(\"/etc/mtab\", &stbuf))) {\n      if (stbuf.st_mtime != loadtime) {\n        loadtime = stbuf.st_mtime;\n        dev_map.clear();\n        FILE* fd = fopen(\"/etc/mtab\", \"r\");\n        char line[1025];\n        char val[6][1024];\n        line[0] = 0;\n\n        while (fd && fgets(line, 1024, fd)) {\n          if ((sscanf(line, \"%1023s %1023s %1023s %1023s %1023s %1023s\\n\",\n                      val[0], val[1], val[2], val[3], val[4], val[5])) == 6) {\n            std::string sdev = val[0];\n            std::string spath = val[1];\n\n            // before truncating /dev/ prefix, follow possible symlink of device-mapper i.e. /dev/mapper/mpathX -> /dev/dm-YY\n            // note: the actual symlink is likely /dev/mapper/mpathX -> ../dm-Y\n            // this is relevant to further string processing here\n            char buf_link[1024];\n            ssize_t size_link = readlink(sdev.c_str(), buf_link, sizeof(buf_link));\n            if (size_link > 0) {\n                buf_link[size_link] = '\\0';\n                sdev = buf_link;\n                // this might be something like ../dm-7\n                if (sdev.find(\"../\") == 0) {\n                    sdev.erase(0, 3);\n                    dev_map[sdev] = spath;\n                }\n            }\n\n            // the default case, directly find device in i.e. /dev/sdX\n            // fprintf(stderr,\"%s => %s\\n\", sdev.c_str(), spath.c_str());\n            if (sdev.find(\"/dev/\") == 0) {\n              sdev.erase(0, 5);\n              dev_map[sdev] = spath;\n            }\n          }\n        }\n\n        if (fd) {\n          fclose(fd);\n        }\n      }\n    }\n\n    std::string path = \"\";\n\n    for (auto it = dev_map.begin(); it != dev_map.end(); ++it) {\n      std::string dpath = it->second.c_str();\n      std::string match = dev_path;\n\n      if (dpath.length() <= match.length()) {\n        match.erase(dpath.length());\n      }\n\n      // fprintf(stderr,\"%s <=> %s\\n\",match.c_str(),dpath.c_str());\n      if (match == dpath) {\n        if (dpath.length() > path.length()) {\n          mapped_dev = it->first.c_str();\n          path = dpath;\n        }\n      }\n    }\n  }\n\n  return mapped_dev;\n}\n\n//------------------------------------------------------------------------------\n// Get disk rate type for a particular device\n//------------------------------------------------------------------------------\ndouble\nLoad::GetDiskRate(const char* dev_path, const char* tag)\n{\n  std::string dev = DevMap(dev_path);\n  //fprintf(stderr,\"**** Device is %s\\n\", dev);\n  double val = fDiskStat.GetRate(dev.c_str(), tag);\n  return val;\n}\n\n//------------------------------------------------------------------------------\n// Get net rate type for a particular device\n//------------------------------------------------------------------------------\ndouble\nLoad::GetNetRate(const char* dev, const char* tag)\n{\n  double val = fNetStat.GetRate(dev, tag);\n  return val;\n}\n\n//------------------------------------------------------------------------------\n// Method run by scrubber thread to  measurement both disk and network values\n// on regular intervals.\n//------------------------------------------------------------------------------\nvoid\nLoad::Measure()\n{\n  while (true) {\n    XrdSysThread::SetCancelOff();\n\n    if (!fDiskStat.Measure()) {\n      fprintf(stderr, \"error: cannot get disk IO statistic\\n\");\n    }\n\n    if (!fNetStat.Measure()) {\n      fprintf(stderr, \"error: cannot get network IO statistic\\n\");\n    }\n\n    XrdSysThread::SetCancelOn();\n    sleep(mInterval);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Static method used to start the scrubber thread\n//------------------------------------------------------------------------------\nvoid*\nLoad::StartLoadThread(void* pp)\n{\n  Load* load = (Load*) pp;\n  load->Measure();\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Start scrubber thread\n//------------------------------------------------------------------------------\nbool\nLoad::Monitor()\n{\n  int rc = 0;\n\n  if ((rc = XrdSysThread::Run(&mTid, Load::StartLoadThread,\n                              static_cast<void*>(this),\n                              XRDSYSTHREAD_HOLD, \"Scrubber\"))) {\n    return false;\n  } else {\n    return true;\n  }\n}\n\n\n//------------------------------------------------------------------------------\n//                              DiskStat\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nDiskStat::DiskStat()\n{\n  mTags = {\"type\", \"number\", \"device\", \"readReq\", \"mergedReadReq\",\n           \"readSectors\", \"millisRead\", \"writeReqs\", \"mergedWriteReq\",\n           \"writeSectors\", \"millisWrite\", \"concurrentIO\", \"millisIO\",\n           \"weightedMillisIO\"\n          };\n  t1.tv_sec = 0;\n  t2.tv_sec = 0;\n  t1.tv_nsec = 0;\n  t2.tv_nsec = 0;\n}\n\n//------------------------------------------------------------------------------\n// Get rate type for device\n//------------------------------------------------------------------------------\ndouble\nDiskStat::GetRate(const char* dev, const char* key)\n{\n  std::string tag = key;\n  std::string device = dev;\n  XrdSysRWLockHelper rd_lock(&mMutexRW, true);\n\n  if (mRates.find(device) != mRates.end()) {\n    return mRates[device][tag];\n  } else {\n    return 0;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get disk measurement values\n//------------------------------------------------------------------------------\nbool\nDiskStat::Measure(const std::string& fn_path)\n{\n  std::ifstream stream(fn_path);\n\n  if (!stream) {\n    return false;\n  }\n\n  bool scanned = false;\n  std::string line;\n  std::vector<std::string> val;\n\n  while (std::getline(stream, line)) {\n    val.clear();\n    eos::common::StringConversion::Tokenize(line, val);\n\n    if (val.size() < 14) {\n      return false;\n    }\n\n    scanned = true;\n    eos::common::Timing::GetTimeSpec(t2);\n    std::string dev_name = val[2];\n\n    for (unsigned int i = 3; i < mTags.size(); i++) {\n      values_t2[dev_name][mTags[i]] = val[i];\n    }\n\n    if (t1.tv_sec != 0) {\n      float tdif = ((t2.tv_sec - t1.tv_sec) * 1000.0) +\n                   ((t2.tv_nsec - t1.tv_nsec) / 1000000.0);\n\n      for (unsigned int i = 3; i < mTags.size(); i++) {\n        if (tdif > 0) {\n          mRates[dev_name][mTags[i]] =\n            1000.0 * (strtoll(values_t2[dev_name][mTags[i]].c_str(), 0, 10) -\n                      strtoll(values_t1[dev_name][mTags[i]].c_str(), 0, 10)) / tdif;\n        } else {\n          mRates[dev_name][mTags[i]] = 0.0;\n        }\n      }\n\n      for (unsigned int i = 3; i < mTags.size(); i++) {\n        values_t1[dev_name][mTags[i]] = values_t2[dev_name][mTags[i]];\n      }\n    } else {\n      for (auto it = mTags.begin(); it != mTags.end(); it++) {\n        mRates[dev_name][*it] = 0.0;\n      }\n\n      for (unsigned int i = 3; i < mTags.size(); i++) {\n        values_t1[dev_name][mTags[i]] = values_t2[dev_name][mTags[i]];\n      }\n    }\n  }\n\n  if (scanned) {\n    t1.tv_sec = t2.tv_sec;\n    t1.tv_nsec = t2.tv_nsec;\n    return true;\n  }\n\n  return false;\n}\n\n\n//------------------------------------------------------------------------------\n//                                 NetStat Class\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nNetStat::NetStat()\n{\n  mTags.push_back(\"face\");\n  mTags.push_back(\"rxbytes\");\n  mTags.push_back(\"rxpackets\");\n  mTags.push_back(\"rxerrs\");\n  mTags.push_back(\"rxdrop\");\n  mTags.push_back(\"rxfifo\");\n  mTags.push_back(\"rxframe\");\n  mTags.push_back(\"rxcompressed\");\n  mTags.push_back(\"rxmulticast\");\n  mTags.push_back(\"txbytes\");\n  mTags.push_back(\"txpackets\");\n  mTags.push_back(\"txerrs\");\n  mTags.push_back(\"txdrop\");\n  mTags.push_back(\"txfifo\");\n  mTags.push_back(\"txframe\");\n  mTags.push_back(\"txcompressed\");\n  mTags.push_back(\"txrmulticast\");\n  t1.tv_sec = 0;\n  t2.tv_sec = 0;\n  t1.tv_nsec = 0;\n  t2.tv_nsec = 0;\n}\n\n//------------------------------------------------------------------------------\n// Get rate type for device\n//------------------------------------------------------------------------------\ndouble\nNetStat::GetRate(const char* dev, const char* key)\n{\n  std::string tag = key;\n  std::string device = dev;\n  XrdSysRWLockHelper rd_lock(&mMutexRW, true);\n\n  if (mRates.find(device) != mRates.end()) {\n    return mRates[device][tag];\n  } else {\n    return 0;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get network measurement values\n//------------------------------------------------------------------------------\nbool\nNetStat::Measure()\n{\n  FILE* fd = fopen(\"/proc/net/dev\", \"r\");\n\n  if (fd != 0) {\n    XrdSysRWLockHelper wr_lock(&mMutexRW, false);\n    int items = 0;\n    char val[18][1024];\n    char garbage[4096];\n    errno = 0;\n    int n = 0;\n\n    do {\n      garbage[0] = 0;\n\n      if (fgets(garbage, 1024, fd)) {\n        char* dpos = 0;\n\n        if ((dpos = strchr(garbage, ':'))) {\n          *dpos = ' ';\n        }\n      }\n\n      if (n >= 2) {\n        items = sscanf(garbage, \"%1023s %1023s %1023s %1023s %1023s %1023s \"\n                       \"%1023s %1023s %1023s %1023s %1023s %1023s %1023s \"\n                       \"%1023s %1023s %1023s %1023s\\n\",\n                       val[0], val[1], val[2], val[3], val[4], val[5], val[6],\n                       val[7], val[8], val[9], val[10], val[11], val[12],\n                       val[13], val[14], val[15], val[16]);\n      } else {\n        items = 0;\n      }\n\n      if (items == 17) {\n        size_t dev_len = strlen(val[0]);\n\n        if (dev_len && (val[0][dev_len - 1] == ':')) {\n          // newer kernel use <device-name>: in this field ... sigh ...\n          val[0][dev_len - 1] = 0;\n        }\n\n#ifdef __APPLE__\n        struct timeval tv;\n        gettimeofday(&tv, 0);\n        t2.tv_sec = tv.tv_sec;\n        t2.tv_nsec = tv.tv_usec * 1000;\n#else\n        clock_gettime(CLOCK_REALTIME, &t2);\n#endif\n        std::string dev_name = val[0];\n\n        for (unsigned int i = 1; i < mTags.size(); i++) {\n          values_t2[dev_name][mTags[i]] = val[i];\n        }\n\n        if (t1.tv_sec != 0) {\n          float tdif = ((t2.tv_sec - t1.tv_sec) * 1000.0) +\n                       ((t2.tv_nsec - t1.tv_nsec) / 1000000.0);\n\n          for (unsigned int i = 1; i < mTags.size(); i++) {\n            if (tdif > 0) {\n              mRates[dev_name][mTags[i]] =\n                1000.0 * (strtoll(values_t2[dev_name][mTags[i]].c_str(), 0, 10) -\n                          strtoll(values_t1[dev_name][mTags[i]].c_str(), 0, 10)) / tdif;\n            } else {\n              mRates[dev_name][mTags[i]] = 0.0;\n            }\n          }\n\n          for (unsigned int i = 1; i < mTags.size(); i++) {\n            values_t1[dev_name][mTags[i]] = values_t2[dev_name][mTags[i]];\n          }\n        } else {\n          for (auto it = mTags.begin(); it != mTags.end(); it++) {\n            mRates[dev_name][*it] = 0.0;\n          }\n\n          for (unsigned int i = 1; i < mTags.size(); i++) {\n            values_t1[dev_name][mTags[i]] = values_t2[dev_name][mTags[i]];\n          }\n        }\n      } else {\n        if (items < 0) {\n          fclose(fd);\n          t1.tv_sec = t2.tv_sec;\n          t1.tv_nsec = t2.tv_nsec;\n          return true;\n        }\n      }\n\n      n++;\n    } while (1);\n  } else {\n    return false;\n  }\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/Load.hh",
    "content": "//------------------------------------------------------------------------------\n// File: Load.hh\n// Author: Andreas-Joachim Peters <apeters@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_LOAD_HH__\n#define __EOSFST_LOAD_HH__\n\n#include \"fst/Namespace.hh\"\n#include <XrdSys/XrdSysPthread.hh>\n#include <vector>\n#include <map>\n#include <string>\n#include <sys/time.h>\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class collecting disk statistics\n//------------------------------------------------------------------------------\nclass DiskStat\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  DiskStat();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~DiskStat() {}\n\n  //----------------------------------------------------------------------------\n  //! Get disk measurement values\n  //!\n  //! @param fn_path abs path to file to be read\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool Measure(const std::string& fn_path = \"/proc/diskstats\");\n\n  //----------------------------------------------------------------------------\n  //! Get rate type for device\n  //!\n  //! @param dev device identifier\n  //! @param key rate type\n  //!\n  //! @return rate value\n  //----------------------------------------------------------------------------\n  double GetRate(const char* dev, const char* key);\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  //! Map from device name to map of key/values\n  std::map<std::string, std::map<std::string, std::string > > values_t2;\n  std::map<std::string, std::map<std::string, std::string > > values_t1;\n  std::map<std::string, std::map<std::string, double > > mRates;\n  struct timespec t1; ///< Timestamp 1\n  struct timespec t2; ///< Timestamp 2 for calculating the rates\n  std::vector<std::string> mTags; ///< Disk statistics tags\n  XrdSysRWLock mMutexRW; ///< RW mutex for protecting accces to the rates map\n};\n\n//------------------------------------------------------------------------------\n//! Class collecting network statistics\n//------------------------------------------------------------------------------\nclass NetStat\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  NetStat();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~NetStat() {};\n\n  //----------------------------------------------------------------------------\n  //! Get disk measurement values\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool Measure();\n\n  //----------------------------------------------------------------------------\n  //! Get rate type for device\n  //!\n  //! @param dev device identifier\n  //! @param key rate type\n  //!\n  //! @return rate value\n  //----------------------------------------------------------------------------\n  double GetRate(const char* dev, const char* key);\n\nprivate:\n  //! Map from device name to map of key/values\n  std::map<std::string, std::map<std::string, std::string > > values_t2;\n  std::map<std::string, std::map<std::string, std::string > > values_t1;\n  std::map<std::string, std::map<std::string, double > > mRates;\n  struct timespec t1; ///< Timestamp 1\n  struct timespec t2; ///< Timestamp 2 for calculating the rates\n  std::vector<std::string> mTags; ///< Disk statistics tags\n  XrdSysRWLock mMutexRW; ///< RW mutex for protecting accces to the rates map\n};\n\n//------------------------------------------------------------------------------\n//! Class Load\n//------------------------------------------------------------------------------\nclass Load\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Get device name mounted at the given path\n  //!\n  //! @param dev_path device mount path\n  //!\n  //! @return name of the devices which is mounted at the given path or empty\n  //!         string if no device found\n  //----------------------------------------------------------------------------\n  static const std::string DevMap(const std::string& dev_path);\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  Load(unsigned int ival = 15);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~Load();\n\n  //----------------------------------------------------------------------------\n  //! Start scrubber thread\n  //!\n  //! @return true if thread started successfully, otherwise false\n  //----------------------------------------------------------------------------\n  bool Monitor();\n\n  //----------------------------------------------------------------------------\n  //! Method run by scurbber thread to  measurement both disk and network values\n  //! on regular intervals.\n  //----------------------------------------------------------------------------\n  void Measure();\n\n  //----------------------------------------------------------------------------\n  //! Get disk rate type for a particular device\n  //!\n  //! @param dev_path device path\n  //! @param tag type of disk rate retrieved\n  //!\n  //! @return disk rate value\n  //----------------------------------------------------------------------------\n  virtual double GetDiskRate(const char* dev_path, const char* tag);\n\n  //----------------------------------------------------------------------------\n  //! Get net rate type for a particular device\n  //!\n  //! @param dev device\n  //! @param tag type of net rate retrieved\n  //!\n  //! @return net rate value\n  //----------------------------------------------------------------------------\n  double GetNetRate(const char* dev, const char* tag);\n\n  //----------------------------------------------------------------------------\n  //! Static method used to start the scrubber thread\n  //----------------------------------------------------------------------------\n  static void* StartLoadThread(void* pp);\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  pthread_t mTid; ///< Monitor thread id\n  unsigned int mInterval; ///< Sampling interval for the monitor thread\n  DiskStat fDiskStat; ///< Disk statistics\n  NetStat fNetStat; ///< Network statistics\n};\n\nEOSFSTNAMESPACE_END\n\n#endif // __EOSFST_LOAD_HH__\n"
  },
  {
    "path": "fst/Namespace.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Namespace.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_NAMESPACE_HH__\n#define __EOSFST_NAMESPACE_HH__\n\n#define EOSFSTNAMESPACE_BEGIN namespace eos { namespace fst {\n#define EOSFSTNAMESPACE_END }}\n\n#endif\n"
  },
  {
    "path": "fst/ScanDir.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ScanDir.cc\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/ScanDir.hh\"\n#include \"ContainerMd.pb.h\"\n#include \"LayoutId.hh\"\n#include \"common/Constants.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/thread_id.hh\"\n#include \"console/commands/helpers/FsHelper.hh\"\n#include \"fst/Config.hh\"\n#include \"fst/Deletion.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/checksum/ChecksumGroup.hh\"\n#include \"fst/checksum/ChecksumPlugins.hh\"\n#include \"fst/filemd/FmdMgm.hh\"\n#include \"fst/io/FileIoPluginCommon.hh\"\n#include \"fst/layout/HeaderCRC.hh\"\n#include \"fst/layout/LayoutPlugin.hh\"\n#include \"fst/layout/RaidDpLayout.hh\"\n#include \"fst/layout/ReedSLayout.hh\"\n#include \"fst/storage/FileSystem.hh\"\n#include \"fst/utils/IoPriority.hh\"\n#include \"fst/utils/ScanRate.hh\"\n#include \"mgm/misc/Constants.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"qclient/structures/QSet.hh\"\n#include <fcntl.h>\n#include <fts.h>\n#include <memory>\n#include <sys/stat.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#include <syslog.h>\n#include <unistd.h>\n#ifndef __APPLE__\n#include <sys/syscall.h>\n#include <asm/unistd.h>\n#include <algorithm>\n#include <memory>\n#endif\n\n\nnamespace\n{\nstd::chrono::seconds getScanTimestamp(eos::fst::FileIo* io,\n                                      const std::string& key)\n{\n  std::string scan_ts_sec = \"0\";\n  io->attrGet(key, scan_ts_sec);\n\n  // Handle the old format in microseconds, truncate to seconds\n  if (scan_ts_sec.length() > 10) {\n    scan_ts_sec.erase(10);\n  }\n\n  try {\n    std::int64_t seconds = std::stoll(scan_ts_sec);\n    return std::chrono::seconds(seconds);\n  } catch (...) {\n    return std::chrono::seconds(-1);\n  }\n}\n\n// Helper function used for both the synchronization of altxs metadata from MGM\n// and on altxs computation, to decide if <fsid> is responsible of running the\n// procedures. It garantees that only one of the FS storing the replicas/stripes\n// is selected to run the procedures.\nbool IsCandidateFS(const eos::common::FmdHelper& fmd,\n                   eos::common::FileSystem::fsid_t fsid)\n{\n  auto loc = fmd.GetLocations();\n\n  if (loc.empty()) {\n    // Still waiting the sync of location from MGM\n    return false;\n  }\n\n  std::vector<eos::common::FileSystem::fsid_t> sorted(loc.begin(), loc.end());\n  auto idx = fmd.mProtoFmd.fid() % sorted.size();\n  return sorted[idx] == fsid;\n}\n}\n\nEOSFSTNAMESPACE_BEGIN\n\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nScanDir::ScanDir(const char* dirpath, eos::common::FileSystem::fsid_t fsid,\n                 eos::fst::Load* fstload, bool bgthread,\n                 long int file_rescan_interval, int ratebandwidth,\n                 bool fake_clock) :\n  mFstLoad(fstload), mFsId(fsid), mDirPath(dirpath),\n  mRateBandwidth(ratebandwidth),\n  mNsInterval(DEFAULT_NS_INTERVAL),\n  mDiskInterval(DEFAULT_DISK_INTERVAL),\n  mEntryInterval(file_rescan_interval),\n  mRainEntryInterval(DEFAULT_RAIN_RESCAN_INTERVAL),\n  mAltXsDoSync(false), // by default sync is disabled\n  mAltXsSyncInterval(0), // by default the sync is done only once\n  mAltXsInterval(0), // by default it's disabled\n  mNumScannedFiles(0), mNumCorruptedFiles(0),\n  mNumHWCorruptedFiles(0),  mTotalScanSize(0), mNumTotalFiles(0),\n  mNumSkippedFiles(0), mBuffer(nullptr),\n  mBufferSize(0), mBgThread(bgthread), mClock(fake_clock), mRateLimit(nullptr)\n{\n  long alignment = pathconf((mDirPath[0] != '/') ? \"/\" : mDirPath.c_str(),\n                            _PC_REC_XFER_ALIGN);\n\n  if (alignment > 0) {\n    mBufferSize = 256 * alignment;\n\n    if (posix_memalign((void**) &mBuffer, alignment, mBufferSize)) {\n      fprintf(stderr, \"error: error calling posix_memaling on dirpath=%s. \\n\",\n              mDirPath.c_str());\n      std::abort();\n    }\n  } else {\n    mBufferSize = 256 * 1024;\n    mBuffer = (char*) malloc(mBufferSize);\n    fprintf(stderr,\n            \"error: OS does not provide alignment or path does not exist\\n\");\n  }\n\n  if (mBgThread) {\n    openlog(\"scandir\", LOG_PID | LOG_NDELAY, LOG_USER);\n    mDiskThread.reset(&ScanDir::RunDiskScan, this);\n#ifndef _NOOFS\n    mRateLimit.reset(new eos::common::RequestRateLimit());\n    mRateLimit->SetRatePerSecond(sDefaultNsScanRate);\n    mNsThread.reset(&ScanDir::RunNsScan, this);\n    mAltXsThread.reset(&ScanDir::RunAltXsScan, this);\n#endif\n  }\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nScanDir::~ScanDir()\n{\n  if (mBgThread) {\n    mDiskThread.join();\n    mNsThread.join();\n    mAltXsThread.join();\n    closelog();\n  }\n\n  if (mBuffer) {\n    free(mBuffer);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Update scanner configuration\n//------------------------------------------------------------------------------\nvoid\nScanDir::SetConfig(const std::string& key, long long value)\n{\n  eos_info(\"msg=\\\"update scanner configuration\\\" key=\\\"%s\\\" value=\\\"%s\\\" fsid=%lu\",\n           key.c_str(), std::to_string(value).c_str(), mFsId);\n\n  if (key == eos::common::SCAN_IO_RATE_NAME) {\n    mRateBandwidth.store(static_cast<int>(value), std::memory_order_relaxed);\n  } else if (key == eos::common::SCAN_ENTRY_INTERVAL_NAME) {\n    mEntryInterval.store(value, std::memory_order_relaxed);\n  } else if (key == eos::common::SCAN_RAIN_ENTRY_INTERVAL_NAME) {\n    mRainEntryInterval.store(value, std::memory_order_relaxed);\n  } else if (key == eos::common::SCAN_DISK_INTERVAL_NAME) {\n    mDiskInterval.set(value);\n  } else if (key == eos::common::SCAN_NS_INTERVAL_NAME) {\n#ifndef _NOOFS\n    mNsInterval.set(value);\n#endif\n  } else if (key == eos::common::SCAN_NS_RATE_NAME) {\n    mRateLimit->SetRatePerSecond(value);\n  } else if (key == eos::common::SCAN_ALTXS_INTERVAL_NAME) {\n    mAltXsInterval.set(value);\n  } else if (key == eos::common::ALTXS_SYNC) {\n    mAltXsDoSync.store(value, std::memory_order_relaxed);\n  } else if (key == eos::common::ALTXS_SYNC_INTERVAL) {\n    mAltXsSyncInterval.store(value, std::memory_order_relaxed);\n  }\n}\n\n#ifndef _NOOFS\n//------------------------------------------------------------------------------\n// Infinite loop doing the scanning of namespace entries\n//------------------------------------------------------------------------------\nvoid\nScanDir::RunNsScan(ThreadAssistant& assistant) noexcept\n{\n  using namespace std::chrono;\n  using eos::common::FileId;\n  eos_info(\"msg=\\\"started the ns scan thread\\\" fsid=%lu dirpath=\\\"%s\\\" \"\n           \"ns_scan_interval_sec=%llu\", mFsId, mDirPath.c_str(), mNsInterval.get());\n\n  if (gOFS.mQcl == nullptr) {\n    eos_notice(\"%s\", \"msg=\\\"no qclient present, skipping ns scan\\\"\");\n    return;\n  }\n\n  // Wait for the corresponding file system to boot before starting\n  while ((gOFS.Storage->ExistsFs(mFsId) == false) ||\n         gOFS.Storage->IsFsBooting(mFsId)) {\n    assistant.wait_for(std::chrono::seconds(5));\n\n    if (assistant.terminationRequested()) {\n      eos_info(\"%s\", \"msg=\\\"stopping ns scan thread\\\"\");\n      return;\n    }\n  }\n\n  mNsInterval.random_wait(assistant, true, [this](uint64_t sleep_time) {\n    eos_info(\"msg=\\\"delay ns scan thread by %llu seconds\\\" fsid=%lu dirpath=\\\"%s\\\"\",\n             sleep_time, mFsId, mDirPath.c_str());\n  });\n\n  while (!assistant.terminationRequested()) {\n    AccountMissing();\n    CleanupUnlinked();\n    mNsInterval.wait(assistant);\n  }\n}\n\nnamespace\n{\n\n// Sets on the Fmd the altxs config stored in the container.\n// In memory only.\nvoid SetAltXsConfigFromNS(const eos::ns::ContainerMdProto& container,\n                          eos::common::FmdHelper& fmd)\n{\n  if (!container.xattrs().contains(eos::mgm::SYS_ALTCHECKSUMS)) {\n    return;\n  }\n\n  const auto altxs_str = container.xattrs().at(eos::mgm::SYS_ALTCHECKSUMS);\n  std::vector<std::string> tkns;\n  eos::common::StringConversion::Tokenize(altxs_str, tkns, \",\");\n  fmd.mProtoFmd.clear_altxs();\n\n  for (const auto& tkn : tkns) {\n    auto xs_type = eos::common::LayoutId::GetChecksumFromString(tkn);\n    fmd.mProtoFmd.mutable_altxs()->Add(xs_type);\n  }\n}\n\n// Sets on the Fmd the altxs computed and stored on the file.\n// In memory only.\nvoid SetAltXsOnFile(const eos::ns::FileMdProto& file,\n                    eos::common::FmdHelper& fmd)\n{\n  fmd.mProtoFmd.mutable_mgmaltxs()->Clear();\n\n  for (const auto& [xs, _] : file.altchecksums()) {\n    fmd.mProtoFmd.mutable_mgmaltxs()->Add(xs);\n  }\n}\n\n// Create the map of alternative checksums that will be commited\n// to the MGM.\nstd::map<eos::common::LayoutId::eChecksum, std::string>\nPrepareAltXsResponse(eos::fst::ChecksumGroup* xs)\n{\n  std::map<eos::common::LayoutId::eChecksum, std::string> altxs;\n  auto computed = xs->GetAlternatives();\n\n  for (const auto& [type, xs] : computed) {\n    altxs.insert({type, xs->GetHexChecksum()});\n  }\n\n  return altxs;\n}\n\n// Commit the alternative checksums for the file identified by the file id <fid>.\n// If the list to_delete is not empty, it will instruct the MGM to delete\n// those checksums from the namespace.\nbool CommitAlternativeChecksums(uint64_t fid,\n                                const std::map<eos::common::LayoutId::eChecksum, std::string>& alt_xs,\n                                const std::set<unsigned int>* to_delete = nullptr)\n{\n#ifndef _NOOFS\n\n  if (alt_xs.empty() && (to_delete == nullptr || to_delete->empty())) {\n    return true;\n  }\n\n  XrdOucString capOpaqueFile = \"\";\n  XrdOucString mTimeString = \"\";\n  capOpaqueFile += \"/?\";\n  capOpaqueFile += \"&mgm.pcmd=commit\";\n  capOpaqueFile += \"&mgm.fid=\";\n  capOpaqueFile += eos::common::FileId::Fid2Hex(fid).c_str();\n  capOpaqueFile += \"&mgm.commit.altxs=1\";\n\n  if (!alt_xs.empty()) {\n    std::vector<std::string> alt;\n\n    for (auto const& [type, xs] : alt_xs) {\n      alt.emplace_back(std::string(eos::common::LayoutId::GetChecksumString(\n                                     type)) + \":\" + xs);\n    }\n\n    capOpaqueFile += \"&mgm.altxs=\";\n    capOpaqueFile += eos::common::StringConversion::Join(alt, \",\").c_str();\n  }\n\n  if (to_delete != nullptr && !to_delete->empty()) {\n    capOpaqueFile += \"&mgm.altxs.delete=\";\n    std::vector<std::string> del;\n\n    for (auto const& xs : *to_delete) {\n      del.emplace_back(std::string(eos::common::LayoutId::GetChecksumString(xs)));\n    }\n\n    capOpaqueFile += eos::common::StringConversion::Join(del, \",\").c_str();\n  }\n\n  XrdOucErrInfo error;\n  int rc = gOFS.CallManager(&error, nullptr, nullptr, capOpaqueFile);\n\n  if (rc) {\n    eos_static_err(\"unable to commit alternative checksums fxid=%08llx\", fid);\n    return false;\n  }\n\n#endif\n  return true;\n}\n};\n\n//------------------------------------------------------------------------------\n// Infinite loop doing the computation of alternative checksums\n//------------------------------------------------------------------------------\nvoid ScanDir::RunAltXsScan(ThreadAssistant& assistant) noexcept\n{\n  auto compute_alt_xs = [this](const std::string & fpath) {\n    eos_debug(\"msg=\\\"running alt xs scan for file\\\" path=\\\"%s\\\" fsid=%d\",\n              fpath.c_str(),\n              mFsId);\n    auto fid = eos::common::FileId::PathToFid(fpath.c_str());\n\n    if (!fid) {\n      eos_static_info(\"msg=\\\"skip file which is not a eos data file\\\", \"\n                      \"path=\\\"%s\\\"\", fpath.c_str());\n      return;\n    }\n\n    auto fmd = gOFS.mFmdHandler->LocalGetFmd(fid, mFsId, true);\n\n    if (!fmd) {\n      eos_warning(\"msg=\\\"cannot find fmd object on file\\\" fxid=%08llx fsid=%d\", fid,\n                  mFsId);\n      return;\n    }\n\n    if (!IsCandidateFS(*fmd.get(), mFsId)) {\n      eos_debug(\"msg=\\\"skipped scanning of file\\\" fxid=%08llx fsid=%d\", fid, mFsId);\n      return;\n    }\n\n    auto cfg = fmd->mProtoFmd.altxs();\n    auto on_file = fmd->mProtoFmd.mgmaltxs();\n    std::set<unsigned int> missing;\n    std::set<unsigned int> common;\n    std::set_intersection(cfg.begin(), cfg.end(), on_file.begin(), on_file.end(),\n                          std::inserter(common, common.begin()));\n    std::set_difference(cfg.begin(), cfg.end(), common.begin(), common.end(),\n                        std::inserter(missing, missing.begin()));\n    eos_debug(\"msg=\\\"%d alt xs to compute for file\\\" fxid=%08llx fsid=%d\",\n              missing.size(),\n              fid, mFsId);\n\n    if (missing.size() == 0) {\n      // nothing to compute\n      if (on_file.size() != cfg.size()) {\n        // we have to delete the ones that are on the files and not in the config anymore\n        std::set<unsigned int> to_delete;\n        std::set_difference(on_file.begin(), on_file.end(), cfg.begin(), cfg.end(),\n                            std::inserter(to_delete, to_delete.begin()));\n\n        if (CommitAlternativeChecksums(fid, {}, &to_delete)) {\n          fmd->mProtoFmd.mutable_mgmaltxs()->CopyFrom(cfg);\n          gOFS.mFmdHandler->Commit(fmd.get());\n        }\n      }\n\n      return;\n    }\n\n    auto xs = std::make_unique<eos::fst::ChecksumGroup>();\n\n    for (auto m : cfg) {\n      xs->AddAlternative(static_cast<eos::common::LayoutId::eChecksum>(m));\n    }\n\n    std::string fullpath = SSTR(\"root://\" << gConfig.GetManager() <<\n                                \"//?eos.lfn=fxid:\" << eos::common::FileId::Fid2Hex(\n                                  fid) << \"&xrd.wantprot=sss\");\n    auto f = std::make_unique<XrdCl::File>();\n    auto status = f->Open(fullpath, XrdCl::OpenFlags::Read);\n\n    if (!status.IsOK()) {\n      eos_err(\"msg=\\\"error opening file\\\" fullpath='%s' fsid=%d error='%s'\",\n              fullpath.c_str(),\n              mFsId, status.GetErrorMessage().c_str());\n      return;\n    }\n\n    uint64_t offset = 0;\n    static eos::common::BufferManager sBuffMgr(8 * eos::common::GB);\n    constexpr size_t buff_size = 256 * 1024;\n    eos::common::ManagedBuffer managed_buf(sBuffMgr, buff_size);\n    auto buffer = managed_buf.GetBuffer();\n    uint32_t nread = 0;\n\n    while (true) {\n      status = f->Read(offset, buff_size, buffer->GetDataPtr(), nread);\n\n      if (!status.IsOK()) {\n        eos_err(\"msg=\\\"error reading file\\\" fullpath='%s'\\\" fsid=%d error='%s'\",\n                fullpath.c_str(),\n                mFsId, status.GetErrorMessage().c_str());\n        return;\n      }\n\n      if (nread == 0) {\n        // end of file\n        break;\n      }\n\n      xs->Add(buffer->GetDataPtr(), nread, offset);\n      offset += nread;\n    }\n\n    xs->Finalize();\n    auto alt_xs = PrepareAltXsResponse(xs.get());\n\n    if (CommitAlternativeChecksums(fid, alt_xs)) {\n      fmd->mProtoFmd.mutable_mgmaltxs()->CopyFrom(cfg);\n      gOFS.mFmdHandler->Commit(fmd.get());\n    }\n  };\n  eos_info(\"msg=\\\"started the alt xs scan thread\\\" fsid=%lu dirpath=\\\"%s\\\" \"\n           \"altxs_scan_interval_sec=%llu\", mFsId, mDirPath.c_str(),\n           mAltXsInterval.get());\n\n  if (gOFS.mQcl == nullptr) {\n    eos_notice(\"%s\", \"msg=\\\"no qclient present, skipping ns scan\\\"\");\n    return;\n  }\n\n  // Wait for the corresponding file system to boot before starting\n  while ((gOFS.Storage->ExistsFs(mFsId) == false) ||\n         gOFS.Storage->IsFsBooting(mFsId)) {\n    assistant.wait_for(std::chrono::seconds(5));\n\n    if (assistant.terminationRequested()) {\n      eos_info(\"%s\", \"msg=\\\"stopping alt xs scan thread\\\"\");\n      return;\n    }\n  }\n\n  mAltXsInterval.random_wait(assistant, true, [this](uint64_t sleep_time) {\n    eos_info(\"msg=\\\"pausing alt xs scan thread\\\" time=%d fsid=%d\", sleep_time,\n             mFsId);\n  });\n\n  while (!assistant.terminationRequested()) {\n    eos_info(\"msg=\\\"starting alt xs scan thread loop\\\" fsid=%d\", mFsId);\n    ScanFsTree(assistant, compute_alt_xs, true, &mAltXsInterval);\n    eos_info(\"msg=\\\"finished alt xs scan thread loop\\\" fsid=%d\", mFsId);\n    mAltXsInterval.wait(assistant, true);\n  }\n}\n\n//----------------------------------------------------------------------------\n// Account for missing replicas\n//----------------------------------------------------------------------------\nvoid\nScanDir::AccountMissing()\n{\n  using eos::common::FileId;\n  struct stat info;\n  eos::common::FsckErrsPerFsMap errs_map;\n  auto fids = CollectNsFids(eos::fsview::sFilesSuffix);\n  eos_info(\"msg=\\\"scanning %llu attached namespace entries\\\"\", fids.size());\n\n  while (!fids.empty()) {\n    // Tag any missing replicas\n    eos::IFileMD::id_t fid = fids.front();\n    fids.pop_front();\n    std::string fpath =\n      FileId::FidPrefix2FullPath(FileId::Fid2Hex(fid).c_str(), mDirPath.c_str());\n\n    if (stat(fpath.c_str(), &info)) {\n      // Double check that this not a file which was deleted in the meantime\n      try {\n        if (IsBeingDeleted(fid)) {\n          // Give it one more kick by dropping the file from disk and QDB\n          XrdOucErrInfo tmp_err;\n\n          if (gOFS._rem(\"/DELETION_FSCK\", tmp_err, nullptr, nullptr, fpath.c_str(),\n                        fid, mFsId, true)) {\n            eos_err(\"msg=\\\"failed to remove local file\\\" path=%s fxid=%08llx \"\n                    \"fsid=%lu\", fpath.c_str(), fid, mFsId);\n          }\n        } else {\n          // File missing on disk, mark it but check the MGM info since the\n          // file might be 0-size so we need to remove the kMissing flag\n          eos::common::FmdHelper ns_fmd;\n          auto file = eos::MetadataFetcher::getFileFromId(*gOFS.mQcl.get(),\n                      eos::FileIdentifier(fid));\n          FmdMgmHandler::NsFileProtoToFmd(std::move(file).get(), ns_fmd);\n\n          // Mark as missing only if this is not a zero size file and if the\n          // file metadata entry at the MGM contains the current fsid as one\n          // of the locations.\n          if ((ns_fmd.mProtoFmd.mgmsize() != 0) && ns_fmd.HasLocation(mFsId)) {\n            // Mark as missing and also mark the current fsid\n            ns_fmd.mProtoFmd.set_fsid(mFsId);\n            ns_fmd.mProtoFmd.set_layouterror(ns_fmd.mProtoFmd.layouterror() |\n                                             LayoutId::kMissing);\n          }\n\n          CollectInconsistencies(ns_fmd, mFsId, errs_map);\n        }\n      } catch (eos::MDException& e) {\n        // No file on disk, no ns file metadata object but we have a ghost entry\n        // in the file system view - delete it\n        if (!DropGhostFid(mFsId, fid)) {\n          eos_err(\"msg=\\\"failed to drop ghost entry\\\" fxid=%08llx fsid=%lu\",\n                  fid, mFsId);\n        }\n      }\n    }\n\n    // Rate limit enforced for the current disk\n    mRateLimit->Allow();\n  }\n\n  // Push collected errors to QDB\n  if (!gOFS.Storage->PushToQdb(mFsId, errs_map)) {\n    eos_err(\"msg=\\\"failed to push fsck errors to QDB\\\" fsid=%lu\", mFsId);\n  }\n}\n\n//----------------------------------------------------------------------------\n// Cleanup unlinked replicas older than 10 min still laying around\n//----------------------------------------------------------------------------\nvoid\nScanDir::CleanupUnlinked()\n{\n  using eos::common::FileId;\n  // Loop over the unlinked files and force unlink them if too old\n  auto unlinked_fids = CollectNsFids(eos::fsview::sUnlinkedSuffix);\n  eos_info(\"msg=\\\"scanning %llu unlinked namespace entries\\\"\",\n           unlinked_fids.size());\n\n  while (!unlinked_fids.empty()) {\n    eos::IFileMD::id_t fid = unlinked_fids.front();\n    unlinked_fids.pop_front();\n\n    try {\n      if (IsBeingDeleted(fid) == false) {\n        // Put the fid in the queue of files to be deleted and this should\n        // clean both the disk file and update the namespace entry\n        eos_info(\"msg=\\\"resubmit for deletion\\\" fxid=%08llx fsid=%lu\",\n                 fid, mFsId);\n        std::vector<unsigned long long> id_vect {fid};\n        auto deletion = std::make_unique<Deletion>(id_vect, mFsId);\n        gOFS.Storage->AddDeletion(std::move(deletion));\n      }\n    } catch (eos::MDException& e) {\n      // There is no file metadata object so we delete any potential file from\n      // the local disk and also drop the ghost entry from the file system view\n      eos_info(\"msg=\\\"cleanup ghost unlinked file\\\" fxid=%08llx fsid=%lu\",\n               fid, mFsId);\n      std::string fpath =\n        FileId::FidPrefix2FullPath(FileId::Fid2Hex(fid).c_str(), mDirPath.c_str());\n      // Drop the file from disk and local DB\n      XrdOucErrInfo tmp_err;\n\n      if (gOFS._rem(\"/DELETION_FSCK\", tmp_err, nullptr, nullptr, fpath.c_str(),\n                    fid, mFsId, true)) {\n        eos_err(\"msg=\\\"failed remove local file\\\" path=%s fxid=%08llx fsid=%lu\",\n                fpath.c_str(), fid, mFsId);\n      }\n\n      if (!DropGhostFid(mFsId, fid)) {\n        eos_err(\"msg=\\\"failed to drop ghost entry\\\" fxid=%08llx fsid=%lu\",\n                fid, mFsId);\n      }\n    }\n\n    mRateLimit->Allow();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Drop ghost fid from the given file system id\n//------------------------------------------------------------------------------\nbool\nScanDir::DropGhostFid(const eos::common::FileSystem::fsid_t fsid,\n                      const eos::IFileMD::id_t fid) const\n{\n  GlobalOptions opts;\n  opts.mForceSss = true;\n  FsHelper fs_cmd(opts);\n\n  if (fs_cmd.ParseCommand(SSTR(\"fs dropghosts \" << fsid\n                               << \" --fid \" << fid).c_str())) {\n    eos_err(\"%s\", \"msg=\\\"failed to parse fs dropghosts command\\\"\");\n    return false;\n  }\n\n  if (fs_cmd.ExecuteWithoutPrint()) {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Check if file is unlinked from the namespace and in the process of being\n// deleted from the disk. Files that are unlinked for more than 10 min\n// definetely have a problem and we don't account them as in the process of\n// being deleted.\n//------------------------------------------------------------------------------\nbool\nScanDir::IsBeingDeleted(const eos::IFileMD::id_t fid) const\n{\n  using namespace std::chrono;\n  auto file_fut = eos::MetadataFetcher::getFileFromId(*gOFS.mQcl.get(),\n                  eos::FileIdentifier(fid));\n  // Throws an exception if file metadata object doesn't exist\n  eos::ns::FileMdProto fmd = std::move(file_fut).get();\n  return (fmd.cont_id() == 0ull);\n}\n\n//------------------------------------------------------------------------------\n// Collect all file ids present on the current file system from the NS view\n//------------------------------------------------------------------------------\nstd::deque<eos::IFileMD::id_t>\nScanDir::CollectNsFids(const std::string& type) const\n{\n  std::deque<eos::IFileMD::id_t> queue;\n\n  if ((type != eos::fsview::sFilesSuffix) &&\n      (type != eos::fsview::sUnlinkedSuffix)) {\n    eos_err(\"msg=\\\"unsupported type %s\\\"\", type.c_str());\n    return queue;\n  }\n\n  std::ostringstream oss;\n  oss << eos::fsview::sPrefix << mFsId << \":\" << type;\n  const std::string key = oss.str();\n  qclient::QSet qset(*gOFS.mQcl.get(), key);\n\n  try {\n    for (qclient::QSet::Iterator it = qset.getIterator(); it.valid(); it.next()) {\n      try {\n        queue.push_back(std::stoull(it.getElement()));\n      } catch (...) {\n        eos_err(\"msg=\\\"failed to convert fid entry\\\" data=\\\"%s\\\"\",\n                it.getElement().c_str());\n      }\n    }\n  } catch (const std::runtime_error& e) {\n    // There is no such set in QDB\n  }\n\n  return queue;\n}\n\n#endif\n\n//------------------------------------------------------------------------------\n// Infinite loop doing the scanning\n//------------------------------------------------------------------------------\nvoid\nScanDir::RunDiskScan(ThreadAssistant& assistant) noexcept\n{\n  eos_info(\"msg=\\\"started the disk scan thread\\\" fsid=%lu dirpath=\\\"%s\\\" \"\n           \"disk_scan_interval_sec=%llu\", mFsId, mDirPath.c_str(), mDiskInterval.get());\n  using namespace std::chrono;\n  pid_t tid = 0;\n\n  if (mBgThread) {\n    tid = eos::common::thread_id();\n    int retc = 0;\n\n    if ((retc = ioprio_set(IOPRIO_WHO_PROCESS,\n                           IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 7)))) {\n      eos_err(\"msg=\\\"cannot set io priority to lowest best effort\\\" \"\n              \"retc=%d errno=%d\\n\", retc, errno);\n    } else {\n      eos_notice(\"msg=\\\"set io priority to 7(lowest best-effort)\\\" pid=%u \"\n                 \"fsid=%lu\", tid, mFsId);\n    }\n  }\n\n#ifndef _NOOFS\n\n  // Wait for the corresponding file system to boot before starting\n  while (gOFS.Storage->IsFsBooting(mFsId)) {\n    assistant.wait_for(std::chrono::seconds(5));\n\n    if (assistant.terminationRequested()) {\n      eos_info(\"%s\", \"msg=\\\"stopping disk scan thread\\\"\");\n      return;\n    }\n  }\n\n  if (gOFS.mQcl == nullptr) {\n    eos_notice(\"%s\", \"msg=\\\"no qclient present, skipping disk scan\\\"\");\n    return;\n  }\n\n#endif\n\n  if (mBgThread) {\n    mDiskInterval.random_wait(assistant, true, [this](uint64_t sleep_time) {\n      eos_info(\"msg=\\\"pausing disk scanning\\\" fsid=%lu init_delay_sec=%llu\", mFsId,\n               sleep_time);\n    });\n  }\n\n  while (!assistant.terminationRequested()) {\n    mNumScannedFiles =  mTotalScanSize =  mNumCorruptedFiles = 0;\n    mNumHWCorruptedFiles =  mNumTotalFiles = mNumSkippedFiles = 0;\n\n    if (mDiskInterval.get()) {\n      auto start_ts = std::chrono::system_clock::now();\n      // Do the heavy work\n      CheckTree(assistant);\n      auto finish_ts = std::chrono::system_clock::now();\n      seconds duration = duration_cast<seconds>(finish_ts - start_ts);\n      std::string log_msg =\n          SSTR(\"[ScanDir] Directory: \"\n               << mDirPath << \" files=\" << mNumTotalFiles << \" scanduration=\"\n               << duration.count() << \" [s] scansize=\" << mTotalScanSize << \" [Bytes] [ \"\n               << (mTotalScanSize / 1e6) << \" MB ] scannedfiles=\" << mNumScannedFiles\n               << \" corruptedfiles=\" << mNumCorruptedFiles << \" hwcorrupted=\"\n               << mNumHWCorruptedFiles << \" skippedfiles=\" << mNumSkippedFiles\n               << \" disk_scan_interval_sec=\" << mDiskInterval.get());\n\n      if (mBgThread) {\n        syslog(LOG_ERR, \"%s\\n\", log_msg.c_str());\n        eos_notice(\"%s\", log_msg.c_str());\n      } else {\n        fprintf(stderr, \"%s\\n\", log_msg.c_str());\n      }\n    }\n\n    if (mBgThread) {\n      if (mDiskInterval.get()) {\n        mDiskInterval.wait(assistant);\n      } else {\n        assistant.wait_for(std::chrono::hours(1));\n      }\n    } else {\n      break;\n    }\n  }\n\n  eos_notice(\"msg=\\\"done disk scan\\\" pid=%u fsid=%lu\", tid, mFsId);\n}\n\nvoid ScanDir::ScanFsTree(ThreadAssistant& assistant, ScanFunc f,\n                         bool skip_internal, const WaitInterval* interval) noexcept\n{\n  std::unique_ptr<FileIo> io(FileIoPluginHelper::GetIoObject(\n                               mDirPath.c_str()));\n\n  if (!io) {\n    LogMsg(LOG_ERR, \"msg=\\\"no IO plug-in available\\\" url=\\\"%s\\\"\",\n           mDirPath.c_str());\n    return;\n  }\n\n  std::unique_ptr<FileIo::FtsHandle> handle {io->ftsOpen(FTS_NOSTAT)};\n\n  if (!handle) {\n    LogMsg(LOG_ERR, \"msg=\\\"fts_open failed\\\" dir=%s\", mDirPath.c_str());\n    return;\n  }\n\n  std::string fpath;\n\n  while ((fpath = io->ftsRead(handle.get())) != \"\") {\n    if (!mBgThread) {\n      fprintf(stderr, \"[ScanDir] processing file %s\\n\", fpath.c_str());\n    }\n\n    if (interval) {\n      interval->wait_if_zero(assistant);\n    }\n\n    if (assistant.terminationRequested()) {\n      if (io->ftsClose(handle.get())) {\n        LogMsg(LOG_ERR, \"msg=\\\"fts_close failed\\\" dir=%s\", mDirPath.c_str());\n      }\n\n      return;\n    }\n\n    if (skip_internal) {\n      // Skip scanning orphan files\n      if (fpath.find(\"/.eosorphans\") != std::string::npos) {\n        eos_debug(\"msg=\\\"skip orphan file\\\" path=\\\"%s\\\"\", fpath.c_str());\n        continue;\n      }\n\n      // Skip scanning our scrub files (/scrub.write-once.X, /scrub.re-write.X)\n      if ((fpath.find(\"/scrub.\") != std::string::npos) ||\n          (fpath.find(\".xsmap\") != std::string::npos)) {\n        eos_debug(\"msg=\\\"skip scrub/xs file\\\" path=\\\"%s\\\"\", fpath.c_str());\n        continue;\n      }\n    }\n\n    f(fpath);\n  }\n\n  if (io->ftsClose(handle.get())) {\n    LogMsg(LOG_ERR, \"msg=\\\"fts_close failed\\\" dir=%s\", mDirPath.c_str());\n  }\n}\n\nvoid ScanDir::CheckTree(ThreadAssistant& assistant) noexcept\n{\n  eos::common::FsckErrsPerFsMap errs_map;\n  auto scan_func = [this, &errs_map](const std::string & fpath) {\n    std::unique_ptr<FileIo> io(FileIoPluginHelper::GetIoObject(fpath));\n    // Collect fsck errors and save them to be sent later on to QDB\n#ifndef _NOOFS\n    auto fid = eos::common::FileId::PathToFid(fpath.c_str());\n\n    if (!fid) {\n      eos_static_info(\"msg=\\\"skip file which is not a eos data file\\\", \"\n                      \"path=\\\"%s\\\"\", fpath.c_str());\n      return;\n    }\n\n    auto fmd = gOFS.mFmdHandler->LocalGetFmd(fid, mFsId, true, false);\n#endif\n\n    if (CheckFile(io.get(), fpath)) {\n#ifndef _NOOFS\n\n      if (fmd) {\n        CollectInconsistencies(*fmd.get(), mFsId, errs_map);\n      }\n\n#endif\n    }\n\n#ifndef _NOOFS\n\n    if (fmd) {\n      UpdateLocalAltXsMetadata(*fmd.get());\n    }\n\n#endif\n    return;\n  };\n  ScanFsTree(assistant, scan_func);\n#ifndef _NOOFS\n\n  // Push collected errors to QDB\n  if (!gOFS.Storage->PushToQdb(mFsId, errs_map)) {\n    eos_err(\"msg=\\\"failed to push fsck errors to QDB\\\" fsid=%lu\", mFsId);\n  }\n\n#endif\n}\n\nvoid ScanDir::UpdateLocalAltXsMetadata(eos::common::FmdHelper& fmd)\n{\n#ifndef _NOOFS\n\n  if (!DoAltXsSync(fmd)) {\n    eos_debug(\"msg=\\\"skipping local altxs metadata synchronization\\\" fxid=%08llx fsid=%lu\",\n              fmd.mProtoFmd.fid(), mFsId);\n    return;\n  }\n\n  // Get fmd from namespace\n  eos::ns::ContainerMdProto container;\n  eos::ns::FileMdProto file;\n\n  try {\n    auto container_fut = eos::MetadataFetcher::getContainerFromId(\n                           *gOFS.mQcl.get(),\n                           eos::ContainerIdentifier(fmd.mProtoFmd.cid()));\n    auto file_fut = eos::MetadataFetcher::getFileFromId(*gOFS.mQcl.get(),\n                    eos::FileIdentifier(fmd.mProtoFmd.fid()));\n    container = std::move(container_fut).get();\n    file = std::move(file_fut).get();\n  } catch (const eos::MDException& e) {\n    return;\n  }\n\n  SetAltXsConfigFromNS(container, fmd);\n  SetAltXsOnFile(file, fmd);\n  auto now_ts = std::chrono::duration_cast<std::chrono::seconds>\n                (std::chrono::system_clock::now().time_since_epoch()).count();\n  fmd.mProtoFmd.set_altxssync(now_ts);\n  gOFS.mFmdHandler->Commit(&fmd);\n#endif\n  return;\n}\n\nbool ScanDir::DoAltXsSync(const eos::common::FmdHelper& fmd)\n{\n  if (!mAltXsDoSync) {\n    return false;\n  }\n\n  // Only run the sync on one of the replica/stripe.\n  // For the rest, it's disabled to not add useless load on qdb.\n  if (!IsCandidateFS(fmd, mFsId)) {\n    return false;\n  }\n\n  auto last_sync = std::chrono::seconds(fmd.mProtoFmd.altxssync());\n\n  if (last_sync.count() <= 0) {\n    // The sync has never been done\n    return true;\n  }\n\n  auto sync_interval = mAltXsSyncInterval.load();\n\n  if (sync_interval == 0) {\n    // The sync has been done once\n    return false;\n  }\n\n  std::chrono::system_clock::time_point now_ts(std::chrono::system_clock::now());\n  std::chrono::system_clock::time_point sync_ts(last_sync);\n  uint64_t elapsed_sec = std::chrono::duration_cast<std::chrono::seconds>\n                         (now_ts - sync_ts).count();\n  return elapsed_sec >= sync_interval;\n}\n\n//------------------------------------------------------------------------------\n// Check the given RAIN file\n//------------------------------------------------------------------------------\nbool ScanDir::CheckReplicaFile(eos::fst::FileIo* io,\n                               eos::common::FileId::fileid_t fid,\n                               time_t mtime)\n{\n  auto last_scan = getScanTimestamp(io, \"user.eos.timestamp\");\n\n  if (!DoRescan(last_scan)) {\n    ++mNumSkippedFiles;\n    return false;\n  }\n\n#ifndef _NOOFS\n  gOFS.mFmdHandler->ClearErrors(fid, mFsId, false);\n#endif\n  return ScanFile(io, io->GetPath(), fid, last_scan, mtime);\n}\n\n#ifndef _NOOFS\n//------------------------------------------------------------------------------\n// Check the given replica file\n//------------------------------------------------------------------------------\nbool ScanDir::CheckRainFile(eos::fst::FileIo* io,\n                            eos::common::FmdHelper* fmd)\n{\n  auto last_scan = getScanTimestamp(io, \"user.eos.rain_timestamp\");\n\n  if (!DoRescan(last_scan, true)) {\n    ++mNumSkippedFiles;\n    return false;\n  }\n\n  gOFS.mFmdHandler->ClearErrors(fmd->mProtoFmd.fid(), mFsId, true);\n  return ScanRainFile(io, fmd, last_scan);\n}\n#endif\n\n//------------------------------------------------------------------------------\n// Check the given file for errors and properly account them both at the\n// scanner level and also by setting the proper xattrs on the file.\n//------------------------------------------------------------------------------\nbool\nScanDir::CheckFile(eos::fst::FileIo* io, const std::string& fpath)\n{\n  using eos::common::LayoutId;\n  eos_debug(\"msg=\\\"start file check\\\" path=\\\"%s\\\"\", fpath.c_str());\n  auto fid = eos::common::FileId::PathToFid(fpath.c_str());\n\n  if (!fid) {\n    eos_static_info(\"msg=\\\"skip file which is not an eos data file\\\", \"\n                    \"path=\\\"%s\\\"\", fpath.c_str());\n    return false;\n  }\n\n  // Get last modification time\n  struct stat info;\n\n  if ((io->fileOpen(0, 0)) || io->fileStat(&info)) {\n    LogMsg(LOG_ERR, \"msg=\\\"open/stat failed\\\" path=\\\"%s\\\"\", fpath.c_str());\n    return false;\n  }\n\n  ++mNumTotalFiles;\n#ifndef _NOOFS\n\n  if (mBgThread) {\n    if (gOFS.openedForWriting.isOpen(mFsId, fid)) {\n      syslog(LOG_ERR, \"skipping scan w-open file: localpath=%s fsid=%d fxid=%08llx\\n\",\n             fpath.c_str(), mFsId, fid);\n      eos_warning(\"msg=\\\"skipping scan of w-open file\\\" localpath=%s fsid=%d \"\n                  \"fxid=%08llx\", fpath.c_str(), mFsId, fid);\n      return false;\n    }\n  }\n\n  auto fmd = gOFS.mFmdHandler->LocalGetFmd(fid, mFsId, true, false);\n\n  if (!fmd) {\n    if (info.st_size == 0) {\n      // The file doesn't have an attached fmd object and the size is 0\n      // It might be a leftover from an Open with create flag: EOS-6423\n      if (io->fileRemove()) {\n        LogMsg(LOG_ERR, \"msg=\\\"failed to remove file\\\" path=\\\"%s\\\"\", fpath.c_str());\n      }\n    }\n\n    return false;\n  }\n\n  if (LayoutId::IsRain(fmd->mProtoFmd.lid())) {\n    return CheckRainFile(io, fmd.get());\n  }\n\n#endif\n  return CheckReplicaFile(io, fid, info.st_mtime);\n}\n\n//------------------------------------------------------------------------------\n// Get block checksum object for the given file. First we need to check if\n// there is a block checksum file (.xsmap) corresponding to the given raw file.\n//------------------------------------------------------------------------------\nstd::unique_ptr<eos::fst::CheckSum>\nScanDir::GetBlockXS(const std::string& file_path)\n{\n  using eos::common::LayoutId;\n  std::string str_bxs_type, str_bxs_size;\n  std::string filexs_path = file_path + \".xsmap\";\n  std::unique_ptr<eos::fst::FileIo> io(FileIoPluginHelper::GetIoObject(\n                                         filexs_path));\n  struct stat info;\n\n  if (!io->fileStat(&info, 0)) {\n    io->attrGet(\"user.eos.blockchecksum\", str_bxs_type);\n    io->attrGet(\"user.eos.blocksize\", str_bxs_size);\n\n    if (str_bxs_type.compare(\"\")) {\n      unsigned long bxs_type = LayoutId::GetBlockChecksumFromString(str_bxs_type);\n      int bxs_size = atoi(str_bxs_size.c_str());\n      int bxs_size_type = LayoutId::BlockSizeEnum(bxs_size);\n      auto layoutid = LayoutId::GetId(LayoutId::kPlain, LayoutId::kNone, 0,\n                                      bxs_size_type, bxs_type);\n      std::unique_ptr<eos::fst::CheckSum> checksum =\n        eos::fst::ChecksumPlugins::GetChecksumObject(layoutid, true);\n\n      if (checksum) {\n        if (checksum->OpenMap(filexs_path.c_str(), info.st_size, bxs_size, false)) {\n          return checksum;\n        } else {\n          return nullptr;\n        }\n      } else {\n        LogMsg(LOG_ERR, \"%s\", SSTR(\"msg=\\\"failed to get checksum object\\\" \"\n                                   << \"layoutid=\" << std::hex << layoutid\n                                   << std::dec << \"path=\" << filexs_path).c_str());\n      }\n    } else {\n      LogMsg(LOG_ERR, \"%s\", SSTR(\"msg=\\\"file has no blockchecksum xattr\\\"\"\n                                 << \" path=\" << filexs_path).c_str());\n    }\n  }\n\n  return nullptr;\n}\n\n//------------------------------------------------------------------------------\n// Decide if a rescan is needed based on the timestamp provided and the\n// configured rescan interval\n//------------------------------------------------------------------------------\nbool\nScanDir::DoRescan(std::chrono::seconds last_scan, bool rain_ts) const\n{\n  using namespace std::chrono;\n  uint64_t rescan_interval = rain_ts ? mRainEntryInterval.load() :\n                             mEntryInterval.load();\n\n  if (rescan_interval == 0) {\n    // scan is disabled when this setting is 0\n    return false;\n  }\n\n  if (last_scan.count() <= 0) {\n    return true;\n  }\n\n  uint64_t elapsed_sec {0ull};\n\n  // Used only during testing\n  if (mClock.IsFake()) {\n    steady_clock::time_point old_ts(last_scan);\n    steady_clock::time_point now_ts(mClock.GetTime());\n    elapsed_sec = duration_cast<seconds>(now_ts - old_ts).count();\n  } else {\n    system_clock::time_point old_ts(last_scan);\n    system_clock::time_point now_ts(system_clock::now());\n    elapsed_sec = duration_cast<seconds>(now_ts - old_ts).count();\n  }\n\n  return elapsed_sec >= rescan_interval;\n}\n\n//------------------------------------------------------------------------------\n// Check the given file for errors and properly account them both at the\n// scanner level and also by setting the proper xattrs on the file.\n//------------------------------------------------------------------------------\nbool\nScanDir::ScanFile(eos::fst::FileIo* io,\n                  const std::string& fpath,\n                  eos::common::FileId::fileid_t fid,\n                  std::chrono::seconds last_scan,\n                  time_t mtime)\n{\n  std::string lfn, previous_xs_err;\n  io->attrGet(\"user.eos.lfn\", lfn);\n  io->attrGet(\"user.eos.filecxerror\", previous_xs_err);\n  bool was_healthy = (previous_xs_err == \"0\");\n  // Flag if file has been modified since the last time we scanned it\n  bool didnt_change = (mtime < last_scan.count());\n  bool blockxs_err = false;\n  bool filexs_err = false;\n  unsigned long long scan_size{0ull};\n  std::string scan_xs_hex;\n\n  if (!ScanFileLoadAware(io, scan_size, scan_xs_hex, filexs_err, blockxs_err)) {\n    return false;\n  }\n\n  bool reopened = false;\n#ifndef _NOOFS\n\n  if (mBgThread) {\n    if (gOFS.openedForWriting.isOpen(mFsId, fid)) {\n      eos_err(\"msg=\\\"file reopened during the scan, ignore checksum error\\\" \"\n              \"path=%s\", fpath.c_str());\n      reopened = true;\n    }\n  }\n\n#endif\n  // If file changed or opened for update in the meantime then skip the scan\n  struct stat info;\n\n  if (reopened || io->fileStat(&info) || (mtime != info.st_mtime)) {\n    LogMsg(LOG_ERR, \"msg=\\\"[ScanDir] skip file modified during scan path=%s\",\n           fpath.c_str());\n    return false;\n  }\n\n  if (filexs_err) {\n    if (mBgThread) {\n      syslog(LOG_ERR, \"corrupted file checksum path=%s lfn=%s\\n\",\n             fpath.c_str(), lfn.c_str());\n      eos_err(\"msg=\\\"corrupted file checksum\\\" path=\\\"%s\\\" lfn=\\\"%s\\\"\",\n              fpath.c_str(), lfn.c_str());\n    } else {\n      fprintf(stderr, \"[ScanDir] corrupted file checksum path=%s lfn=%s\\n\",\n              fpath.c_str(), lfn.c_str());\n    }\n\n    if (was_healthy && didnt_change) {\n      ++mNumHWCorruptedFiles;\n\n      if (mBgThread) {\n        syslog(LOG_ERR, \"HW corrupted file found path=%s lfn=%s\\n\",\n               fpath.c_str(), lfn.c_str());\n      } else {\n        fprintf(stderr, \"HW corrupted file found path=%s lfn=%s\\n\",\n                fpath.c_str(), lfn.c_str());\n      }\n    }\n  }\n\n  // Collect statistics\n  mTotalScanSize += scan_size;\n\n  if ((io->attrSet(\"user.eos.timestamp\", GetTimestampSmearedSec())) ||\n      (io->attrSet(\"user.eos.filecxerror\", filexs_err ? \"1\" : \"0\")) ||\n      (io->attrSet(\"user.eos.blockcxerror\", blockxs_err ? \"1\" : \"0\"))) {\n    LogMsg(LOG_ERR, \"msg=\\\"failed to set xattrs\\\" path=%s\", fpath.c_str());\n  }\n\n#ifndef _NOOFS\n\n  if (mBgThread) {\n    gOFS.mFmdHandler->UpdateWithScanInfo(fid, mFsId, fpath, scan_size,\n                                         scan_xs_hex, gOFS.mQcl);\n  }\n\n#endif\n  return true;\n}\n//------------------------------------------------------------------------------\n// Scan file taking the load into consideration\n//------------------------------------------------------------------------------\nbool\nScanDir::ScanFileLoadAware(eos::fst::FileIo* io,\n                           unsigned long long& scan_size,\n                           std::string& scan_xs_hex,\n                           bool& filexs_err, bool& blockxs_err)\n{\n  scan_size = 0ull;\n  filexs_err = blockxs_err = false;\n  int scan_rate = mRateBandwidth.load(std::memory_order_relaxed);\n  std::string file_path = io->GetPath();\n  struct stat info;\n\n  if (io->fileStat(&info)) {\n    eos_err(\"msg=\\\"failed stat\\\" path=%s\\\"\", file_path.c_str());\n    return false;\n  }\n\n  // Get checksum type and value\n  std::string xs_type;\n  char xs_val[SHA256_DIGEST_LENGTH];\n  memset(xs_val, 0, sizeof(xs_val));\n  size_t xs_len = SHA256_DIGEST_LENGTH;\n  io->attrGet(\"user.eos.checksumtype\", xs_type);\n  io->attrGet(\"user.eos.checksum\", xs_val, xs_len);\n  auto comp_file_xs = eos::fst::ChecksumPlugins::GetXsObj(xs_type);\n  std::unique_ptr<eos::fst::CheckSum> blockXS {GetBlockXS(file_path)};\n\n  if (comp_file_xs) {\n    comp_file_xs->Reset();\n  } else {\n    eos_static_warning(\"msg=\\\"file has no checksum type xattr\\\", path=\\\"%s\\\"\",\n                       file_path.c_str());\n    //@todo(esindril) if this happens we should get the checksum type\n    // from the Fmd information attached also as an xattr.\n  }\n\n  int64_t nread = 0;\n  off_t offset = 0;\n  const auto open_ts = std::chrono::system_clock::now();\n\n  do {\n    nread = io->fileRead(offset, mBuffer, mBufferSize);\n\n    if (nread < 0) {\n      if (blockXS) {\n        blockXS->CloseMap();\n      }\n\n      eos_err(\"msg=\\\"failed read\\\" offset=%llu path=%s\", offset,\n              file_path.c_str());\n      return false;\n    }\n\n    if (nread) {\n      if (nread > mBufferSize) {\n        eos_err(\"msg=\\\"read returned more than the buffer size\\\" buff_sz=%llu \"\n                \"nread=%lli\\\"\", mBufferSize, nread);\n\n        if (blockXS) {\n          blockXS->CloseMap();\n        }\n\n        return false;\n      }\n\n      if (blockXS && (blockxs_err == false)) {\n        if (!blockXS->CheckBlockSum(offset, mBuffer, nread)) {\n          blockxs_err = true;\n        }\n      }\n\n      if (comp_file_xs) {\n        comp_file_xs->Add(mBuffer, nread, offset);\n      }\n\n      offset += nread;\n      eos::fst::utils::EnforceAndAdjustScanRate(offset, open_ts, scan_rate,\n          mFstLoad, mDirPath.c_str(),\n          mRateBandwidth.load());\n    }\n  } while (nread == mBufferSize);\n\n  scan_size = (unsigned long long) offset;\n  const auto close_ts = std::chrono::system_clock().now();\n  auto tx_duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>\n                        (close_ts - open_ts).count();\n  eos_static_debug(\"path=%s size_bytes=%llu duration_ms=%llu rate_MB/s=%.02f\",\n                   file_path.c_str(), scan_size, tx_duration_ms,\n                   (((1.0 * offset) / (1024 * 1024)) * 1000) / tx_duration_ms)\n\n  // Check file checksum only for replica layouts\n  if (comp_file_xs) {\n    comp_file_xs->Finalize();\n    scan_xs_hex = comp_file_xs->GetHexChecksum();\n\n    if (!comp_file_xs->Compare(xs_val)) {\n      auto exp_file_xs = eos::fst::ChecksumPlugins::GetXsObj(xs_type);\n      exp_file_xs->SetBinChecksum(xs_val, xs_len);\n      LogMsg(LOG_ERR, \"msg=\\\"file checksum error\\\" expected_file_xs=%s \"\n             \"computed_file_xs=%s scan_size=%llu path=%s\",\n             exp_file_xs->GetHexChecksum(), comp_file_xs->GetHexChecksum(),\n             scan_size, file_path.c_str());\n      ++mNumCorruptedFiles;\n      filexs_err = true;\n    }\n  }\n\n  // Check block checksum\n  if (blockxs_err) {\n    LogMsg(LOG_ERR, \"msg=\\\"corrupted block checksum\\\" path=%s \"\n           \"blockxs_path=%s.xsmap\", file_path.c_str(), file_path.c_str());\n\n    if (mBgThread) {\n      syslog(LOG_ERR, \"corrupted block checksum path=%s blockxs_path=%s.xsmap\\n\",\n             file_path.c_str(), file_path.c_str());\n    }\n  }\n\n  if (blockXS) {\n    blockXS->CloseMap();\n  }\n\n  ++mNumScannedFiles;\n  return true;\n}\n\n#ifndef _NOOFS\n//------------------------------------------------------------------------------\n// Check the given file for rain stripes errors\n//------------------------------------------------------------------------------\nbool ScanDir::ScanRainFile(eos::fst::FileIo* io, eos::common::FmdHelper* fmd,\n                           std::chrono::seconds last_scan)\n{\n  auto fid = fmd->mProtoFmd.fid();\n  auto path = io->GetPath();\n  std::set<eos::common::FileSystem::fsid_t> invalid_fsid;\n  eos_debug(\"msg=\\\"starting scanning rain file\\\" path=%s fsid=%d fxid=%08llx\",\n            path.c_str(), mFsId, fid);\n\n  if (mBgThread) {\n    //  Skip check if file is open for reading, as this can mean we are in the\n    //  middle of a recovery operation, and another stripe is open for write\n    if (gOFS.openedForReading.isOpen(mFsId, fid) ||\n        gOFS.openedForWriting.isOpen(mFsId, fid)) {\n      syslog(LOG_ERR, \"skipping rain scan rd/wr-open file: localpath=%s fsid=%d \"\n             \"fxid=%08lx\\n\", path.c_str(), mFsId, fid);\n      eos_warning(\"msg=\\\"skipping rain scan of rd/wr-open file\\\" localpath=%s \"\n                  \"fsid=%d fxid=%08llx\", path.c_str(), mFsId, fid);\n      return false;\n    }\n  }\n\n  struct stat info_before;\n\n  if (io->fileStat(&info_before)) {\n    LogMsg(LOG_ERR, \"msg=\\\"stat failed\\\" path=%s\\\"\", path.c_str());\n    return false;\n  }\n\n  if (info_before.st_ctime < last_scan.count()) {\n    eos_static_debug(\"msg=\\\"skip rain check for unmodified file\\\" path=\\\"%s\\\"\",\n                     path.c_str());\n    return false;\n  }\n\n  std::unique_ptr<HeaderCRC> hd(new HeaderCRC(0, 0));\n\n  if (!hd) {\n    eos_static_err(\"msg=\\\"failed to allocate header\\\" path=\\\"%s\\\"\",\n                   path.c_str());\n    return false;\n  }\n\n  hd->ReadFromFile(io, 0);\n\n  if (!hd->IsValid()) {\n    // the stripe is corrupted\n    eos_debug(\"msg=\\\"header file is not valid\\\" path=%s fsid=%d fxid=%08llx\",\n              path.c_str(), mFsId, fid);\n    ReportInvalidFsid(io, fid, {mFsId});\n    return true;\n  }\n\n  if (fmd->GetLocations().size() > LayoutId::GetStripeNumber(\n        fmd->mProtoFmd.lid()) + 1) {\n    if (!ScanRainFileLoadAware(fid, invalid_fsid)) {\n      return false;\n    }\n\n    if (ShouldSkipAfterCheck(io, fid, info_before)) {\n      return false;\n    }\n\n    ReportInvalidFsid(io, fid, invalid_fsid);\n    return true;\n  }\n\n  // We use the new fast method only if the checksum has been computed\n  // for the stripe, otherwise we use the old method, since we don't have\n  // enough information to get the invalid fsid.\n  // The fast method is run by each FSTs, where each of them is checking\n  // the stripe that is storing.\n  // The old method will only be run by the replica 0 file, and the unitchecksum\n  // computation will be triggered.\n  std::chrono::milliseconds scantime {0};\n  unsigned long long scansize {0ull};\n  std::unique_ptr<eos::fst::CheckSum>\n  xs {ChecksumPlugins::GetXsObj(eos::common::LayoutId::eChecksum::kAdler)};\n\n  if (!xs->ScanFile(path.c_str(), scansize, scantime, mRateBandwidth.load(),\n                    hd->GetSize(), mFstLoad, mDirPath, mRateBandwidth.load())) {\n    eos_err(\"msg=\\\"checksum scanning failed\\\" path=%s\", path.c_str());\n    return false;\n  }\n\n  XrdOucString sizestring;\n  eos_debug(\"info=\\\"scanned stripe checksum\\\" path=%s size_bytes=%s \"\n            \"duration_ms=%llu rate_MB/s=%.02f comp_xs=\\\"%s\\\"\", path.c_str(),\n            eos::common::StringConversion::GetReadableSizeString(sizestring,\n                scansize, \"B\"),\n            scantime.count(), (((1.0 * scansize) / (1024 * 1024)) * 1000) /\n            (scantime.count() ? scantime.count() : 99999999999999LL),\n            xs->GetHexChecksum());\n\n  if (fmd->mProtoFmd.has_stripechecksum()) {\n    eos_debug(\"msg=\\\"stripe checksum available\\\" xs=%s path=%s fsid=%d fxid=%08llx\",\n              xs->GetHexChecksum(), path.c_str(), mFsId, fid);\n\n    if (xs->GetHexChecksum() != fmd->mProtoFmd.stripechecksum()) {\n      eos_debug(\"msg=\\\"checksums do not match\\\" expected_xs=%s computed_xs=%s \"\n                \"path=%s fsid=%d fxid=%08llx\",\n                fmd->mProtoFmd.stripechecksum().c_str(), xs->GetHexChecksum(),\n                path.c_str(), mFsId, fid);\n      invalid_fsid.insert(mFsId);\n    }\n  } else {\n#ifndef _NOOFS\n    // The stripe checkum is not stored in the file header\n    // So we fallback to the old procedure, storing the checksum\n    // for the future checks.\n    fmd->mProtoFmd.set_stripechecksum(xs->GetHexChecksum());\n    gOFS.mFmdHandler->Commit(fmd);\n\n    // Run the full RAIN check only on the FST storing the first stripe\n    if (hd->GetIdStripe() != 0) {\n      return false;\n    }\n\n    if (!ScanRainFileLoadAware(fid, invalid_fsid)) {\n      return false;\n    }\n\n#endif\n  }\n\n  if (ShouldSkipAfterCheck(io, fid, info_before)) {\n    return false;\n  }\n\n  // Update last RAIN scan timestamp on the file\n  if (io->attrSet(\"user.eos.rain_timestamp\", GetTimestampSmearedSec(true))) {\n    eos_static_err(\"msg=\\\"failed to set xattr rain_timestamp\\\" path=\\\"%s\\\"\",\n                   io->GetPath().c_str());\n  }\n\n  ReportInvalidFsid(io, fid, invalid_fsid);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Check if the file was open or update during the scan\n//------------------------------------------------------------------------------\nbool\nScanDir::ShouldSkipAfterCheck(eos::fst::FileIo* io,\n                              eos::common::FileId::fileid_t fid,\n                              const struct stat& stat_before)\n{\n  if (mBgThread) {\n    if (gOFS.openedForWriting.isOpen(mFsId, fid)) {\n      eos_static_err(\"msg=\\\"file reopened during the scan, ignore stripe \"\n                     \"error\\\" path=\\\"%s\\\"\", io->GetPath().c_str());\n      return true;\n    }\n  }\n\n  struct stat info_after;\n\n  if (io->fileStat(&info_after) ||\n      (stat_before.st_ctime != info_after.st_ctime)) {\n    eos_static_err(\"msg=\\\"skip file modified during scan\\\" path=\\\"%s\\\"\",\n                   io->GetPath().c_str());\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Update local fmd with info from the stripe check\n//------------------------------------------------------------------------------\nvoid\nScanDir::ReportInvalidFsid(eos::fst::FileIo* io,\n                           eos::common::FileId::fileid_t fid,\n                           const std::set<eos::common::FileSystem::fsid_t>& invalid_fsid)\n{\n  if (!invalid_fsid.empty() && mBgThread) {\n    gOFS.mFmdHandler->UpdateWithStripeCheckInfo(fid, mFsId, invalid_fsid);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check if a given stripe combination can recreate the original file\n//------------------------------------------------------------------------------\nbool\nScanDir::IsValidStripeCombination(\n  const std::vector<std::pair<int, std::string>>& stripes,\n  const std::string& xs_val, std::unique_ptr<CheckSum>& xs_obj,\n  LayoutId::layoutid_t layout, const std::string& opaqueInfo)\n{\n  std::unique_ptr<RainMetaLayout> redundancyObj;\n\n  if (LayoutId::GetLayoutType(layout) == LayoutId::kRaidDP) {\n    redundancyObj = std::make_unique<RaidDpLayout>\n                    (nullptr, layout, nullptr, nullptr,\n                     stripes.front().second.c_str(), nullptr, 0, false);\n  } else {\n    redundancyObj = std::make_unique<ReedSLayout>\n                    (nullptr, layout, nullptr, nullptr,\n                     stripes.front().second.c_str(), nullptr, 0, false);\n  }\n\n  if (redundancyObj->OpenPio(stripes, 0, 0, opaqueInfo.c_str())) {\n    eos_static_err(\"msg=\\\"unable to pio open\\\" opaque=\\\"%s\\\"\", opaqueInfo.c_str());\n    redundancyObj->Close();\n    return false;\n  }\n\n  off_t offsetXrd = 0;\n  const auto open_ts = std::chrono::system_clock::now();\n  int scan_rate = mRateBandwidth.load(std::memory_order_relaxed);\n  xs_obj->Reset();\n\n  while (true) {\n    int64_t nread = redundancyObj->Read(offsetXrd, mBuffer, mBufferSize);\n\n    if (nread == 0) {\n      break;\n    }\n\n    if (nread == -1) {\n      redundancyObj->Close();\n      return false;\n    }\n\n    xs_obj->Add(mBuffer, nread, offsetXrd);\n    offsetXrd += nread;\n    eos::fst::utils::EnforceAndAdjustScanRate(offsetXrd, open_ts, scan_rate,\n        mFstLoad, mDirPath.c_str(),\n        mRateBandwidth.load());\n  }\n\n  redundancyObj->Close();\n  xs_obj->Finalize();\n  return !strcmp(xs_obj->GetHexChecksum(), xs_val.c_str());\n}\n\n//------------------------------------------------------------------------------\n// Return the list of stripes for the file\n//------------------------------------------------------------------------------\nbool ScanDir::GetPioOpenInfo(eos::common::FileId::fileid_t fid,\n                             std::vector<stripe_s>& stripes,\n                             std::string& opaqueInfo,\n                             uint32_t num_locations)\n{\n  const std::string mgr = gConfig.GetManager();\n\n  if (mgr.empty()) {\n    eos_static_err(\"%s\", \"msg=\\\"no manager info available\\\"\");\n    return false;\n  }\n\n  const std::string address = SSTR(\"root://\" << mgr << \"/\");\n  const XrdCl::URL url(address.c_str());\n\n  if (!url.IsValid()) {\n    eos_static_err(\"msg=\\\"invalid url\\\" url=\\\"%s\\\"\", address.c_str());\n    return false;\n  }\n\n  // Query MGM for list of stripes to open\n  XrdCl::Buffer arg;\n  XrdCl::Buffer* resp_raw = nullptr;\n  const std::string opaque = SSTR(\"/.fxid:\" << std::hex << fid << std::dec\n                                  << \"?mgm.pcmd=open\"\n                                  << \"&eos.ruid=\" << DAEMONUID\n                                  << \"&eos.rgid=\" << DAEMONGID\n                                  << \"&xrd.wantprot=sss\"\n                                  << \"&eos.app=eos/fsck_scan\");\n  arg.FromString(opaque);\n  XrdCl::FileSystem fs(url);\n  const XrdCl::XRootDStatus status =\n    fs.Query(XrdCl::QueryCode::OpaqueFile, arg, resp_raw);\n\n  if (!status.IsOK()) {\n    eos_static_err(\"msg=\\\"MGM query failed: '%s'\\\" opaque=\\\"%s\\\"\",\n                   status.ToString().c_str(), opaque.c_str());\n    delete resp_raw;\n    return false;\n  }\n\n  const std::string response(resp_raw->GetBuffer(), resp_raw->GetSize());\n  delete resp_raw;\n  // @note: fragile design as it depends on the location of mgm.logid!\n  const char* ptr = strstr(response.c_str(), \"&mgm.logid\");\n\n  if (ptr == nullptr) {\n    eos_static_err(\"msg=\\\"MGM open reply not in the expected format, maybe \"\n                   \"a redirect\\\" reply_data=\\\"%s\\\"\", response.c_str());\n    return false;\n  }\n\n  opaqueInfo = ptr;\n  std::unique_ptr<XrdOucEnv> openOpaque(new XrdOucEnv(response.c_str()));\n  XrdOucEnv* raw_cap_opaque = nullptr;\n  eos::common::SymKey::ExtractCapability(openOpaque.get(), raw_cap_opaque);\n  std::unique_ptr<XrdOucEnv> capOpaque(raw_cap_opaque);\n\n  if (!capOpaque->Get(\"mgm.path\")) {\n    eos_static_err(\"msg=\\\"no path in mgm cap response\\\" response=\\\"%s\\\"\",\n                   response.c_str());\n    return false;\n  }\n\n  const std::string ns_path = capOpaque->Get(\"mgm.path\");\n  FileSystem::fsid_t stripeFsId = 0;\n  std::string stripeUrl;\n  std::string pio;\n  std::string tag;\n\n  for (unsigned long i = 0; i < num_locations; ++i) {\n    tag = SSTR(\"pio.\" << i);\n\n    // Skip files with missing replicas, they will be detected elsewhere.\n    if (!openOpaque->Get(tag.c_str())) {\n      return false;\n    }\n\n    pio = openOpaque->Get(tag.c_str());\n    stripeUrl = SSTR(\"root://\" << pio << \"/\" << ns_path);\n    stripeFsId = capOpaque->GetInt(SSTR(\"mgm.fsid\" << i).c_str());\n    // Start by marking all stripes invalid. Mark them unknown once we\n    // have successfully read their headers.\n    stripes.push_back({stripeFsId, stripeUrl, stripe_s::Invalid, 0});\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Check for stripes that are unable to reconstruct the original file\n//------------------------------------------------------------------------------\nbool\nScanDir::ScanRainFileLoadAware(eos::common::FileId::fileid_t fid,\n                               std::set<eos::common::FileSystem::fsid_t>&\n                               invalid_fsid)\n{\n  eos_static_debug(\"msg=\\\"scan rain file load aware\\\" fxid=%08llx\", fid);\n  const auto start_ts = std::chrono::steady_clock::now();\n  std::string xs_mgm;\n  uint32_t num_locations;\n  LayoutId::layoutid_t layout;\n  {\n    // Reduce scope of the FmdHelper object\n    auto fmd = gOFS.mFmdHandler->LocalGetFmd(fid, mFsId, true, false);\n\n    if (!fmd) {\n      eos_static_err(\"msg=\\\"could not get fmd from manager\\\" fxid=%08llx\", fid);\n      return false;\n    }\n\n    layout = fmd->mProtoFmd.lid();\n\n    if (!LayoutId::IsRain(layout)) {\n      eos_static_err(\"msg=\\\"layout is not rain\\\" fixd=%08llx\", fid);\n      return false;\n    }\n\n    num_locations = fmd->GetLocations().size();\n    xs_mgm = fmd->mProtoFmd.mgmchecksum();\n  }\n\n  if (xs_mgm.empty() || (num_locations == 0)) {\n    eos_static_err(\"msg=\\\"mgm checksum empty or no locations\\\" fxid=%08llx\",\n                   fid);\n    return false;\n  }\n\n  const auto nStripes = LayoutId::GetStripeNumber(layout) + 1;\n  const auto nParityStripes = LayoutId::GetRedundancyStripeNumber(layout);\n  const auto nDataStripes = nStripes - nParityStripes;\n  std::vector<stripe_s> stripes;\n  stripes.reserve(num_locations);\n  std::string opaqueInfo;\n\n  if (!GetPioOpenInfo(fid, stripes, opaqueInfo, num_locations)) {\n    eos_static_err(\"msg=\\\"skip rain file scan due to missing open info\\\" \"\n                   \"fxid=%08llx\", fid);\n    return false;\n  }\n\n  std::unique_ptr<HeaderCRC> hd {new HeaderCRC(0, 0)};\n\n  if (!hd) {\n    eos_static_err(\"%s\", \"msg=\\\"failed to instantiate header object\\\"\");\n    return false;\n  }\n\n  // Read each header to check if it is valid\n  std::map<unsigned int, std::set<unsigned long>> mapPL;\n\n  for (unsigned long i = 0; i < stripes.size(); ++i) {\n    std::unique_ptr<FileIo> file{FileIoPlugin::GetIoObject(stripes[i].url)};\n\n    if (file) {\n      const std::string new_opaque =\n        SSTR(opaqueInfo << \"&mgm.replicaindex=\" << i);\n      file->fileOpen(SFS_O_RDONLY, 0, new_opaque);\n      hd->ReadFromFile(file.get(), 0);\n      file->fileClose();\n\n      // If stripe id is greater than nStripe, it's invalid\n      if (hd->IsValid() && (hd->GetIdStripe() < nStripes)) {\n        stripes[i].id = hd->GetIdStripe();\n        stripes[i].state = stripe_s::Unknown;\n        mapPL[hd->GetIdStripe()].insert(i);\n      }\n    }\n  }\n\n  if (mapPL.size() < nDataStripes) {\n    eos_static_err(\"msg=\\\"not enough valid stripes to reconstruct\\\" \"\n                   \"fxid=%08llx\", fid);\n    invalid_fsid.insert(0);\n    return true;\n  }\n\n  std::unique_ptr<CheckSum> xs_obj(ChecksumPlugins::GetChecksumObject(layout));\n\n  if (!xs_obj) {\n    eos_static_err(\"msg=\\\"invalid xs_type\\\" fxid=%08llx\", fid);\n    return false;\n  }\n\n  std::vector<bool> combinations(num_locations, false);\n  std::fill(combinations.begin(), combinations.begin() + nDataStripes, true);\n  std::vector<std::pair<int, std::string>>\n                                        stripeCombination(nParityStripes, std::make_pair(0, \"root://__offline_\"));\n  stripeCombination.reserve(nStripes);\n\n  // Try to find a valid stripe combination\n  do {\n    stripeCombination.erase(stripeCombination.begin() + nParityStripes,\n                            stripeCombination.end());\n\n    for (unsigned long i = 0; i < combinations.size(); ++i) {\n      if (combinations[i]) {\n        // Skip combinations with invalid stripes\n        if (stripes[i].state == stripe_s::Invalid) {\n          break;\n        }\n\n        // Skip if multiple duplicated stripes are in the same combination\n        auto HasDuplicate = [i, &combinations](unsigned long j) {\n          return i != j && combinations[j];\n        };\n        auto& stripe_loc = mapPL[stripes[i].id];\n\n        if (std::find_if(stripe_loc.begin(), stripe_loc.end(), HasDuplicate) !=\n            stripe_loc.end()) {\n          break;\n        }\n\n        stripeCombination.emplace_back(i, stripes[i].url);\n      }\n    }\n\n    // Skip combination if we exited early from previous loop\n    if (stripeCombination.size() != nStripes) {\n      continue;\n    }\n\n    if (IsValidStripeCombination(stripeCombination, xs_mgm, xs_obj,\n                                 layout, opaqueInfo)) {\n      for (unsigned long i = 0; i < combinations.size(); ++i) {\n        if (combinations[i]) {\n          stripes[i].state = stripe_s::Valid;\n        }\n      }\n\n      break;\n    }\n  } while (std::prev_permutation(combinations.begin(), combinations.end()));\n\n  auto isValid = [](const stripe_s & s) {\n    return s.state == stripe_s::Valid;\n  };\n\n  if (std::none_of(stripes.begin(), stripes.end(), isValid)) {\n    eos_static_err(\"msg=\\\"not enough valid stripes for reconstruction\\\" \"\n                   \"fxid=%08llx\", fid);\n    invalid_fsid.insert(0);\n    return true;\n  }\n\n  // Found a valid combination, check the rest of the stripes\n  for (unsigned long i = 0; i < stripes.size(); ++i) {\n    if (stripes[i].state == stripe_s::Unknown) {\n      stripeCombination.erase(stripeCombination.begin() + nParityStripes,\n                              stripeCombination.end());\n      // Try combinations with 1 unknown stripe and nDataStripes - 1 valid stripes.\n      // Exclude duplicates from the combination.\n      stripeCombination.emplace_back(i, stripes[i].url);\n      auto& skipStripes = mapPL[stripes[i].id];\n\n      for (unsigned long j = 0; j < stripes.size(); ++j) {\n        if (stripes[j].state == stripe_s::Valid &&\n            skipStripes.find(j) == skipStripes.end()) {\n          stripeCombination.emplace_back(j, stripes[j].url);\n\n          if (stripeCombination.size() == nStripes) {\n            break;\n          }\n        }\n      }\n\n      if (IsValidStripeCombination(stripeCombination, xs_mgm, xs_obj,\n                                   layout, opaqueInfo)) {\n        stripes[i].state = stripe_s::Valid;\n      } else {\n        stripes[i].state = stripe_s::Invalid;\n      }\n    }\n  }\n\n  // Collect the fsids of all the invalid stripes\n  for (unsigned long i = 0; i < stripes.size(); i++) {\n    if (stripes[i].state == stripe_s::Invalid) {\n      eos_static_err(\"msg=\\\"stripe %d on fst %d is invalid\\\" fxid=%08llx\", i,\n                     stripes[i].fsid, fid);\n      invalid_fsid.insert(stripes[i].fsid);\n    }\n  }\n\n  // Collect the fsids of all the duplicated stripes, keeping the replica\n  // with the lowest fsid\n  for (auto [_, replicas] : mapPL) {\n    if (replicas.size() > 1) {\n      // Used to get the index of the replica which is both valid\n      // and has the lowest fsid.\n      auto fsidCmp = [&stripes](const auto & a, const auto & b) {\n        return stripes[b].state != stripe_s::Valid ||\n               (stripes[a].state == stripe_s::Valid &&\n                stripes[a].fsid < stripes[b].fsid);\n      };\n      auto min = *std::min_element(replicas.begin(), replicas.end(), fsidCmp);\n\n      // Skip if all replicas are invalid\n      if (stripes[min].state == stripe_s::Valid) {\n        for (auto i : replicas) {\n          if ((i != min) && (stripes[i].state == stripe_s::Valid)) {\n            eos_static_info(\"msg=\\\"marking excess stripe %d on fst %d as \"\n                            \"invalid\\\" fxid=%08llx\", i, stripes[i].fsid, fid);\n            invalid_fsid.insert(stripes[i].fsid);\n          }\n        }\n      }\n    }\n  }\n\n  const auto end_ts = std::chrono::steady_clock::now();\n  const auto duration_sec = std::chrono::duration_cast<std::chrono::seconds>\n                            (end_ts - start_ts);\n  eos_static_info(\"msg=\\\"full scan rain file done\\\" fxid=%08llx duration=%llus\",\n                  fid, duration_sec.count());\n  return true;\n}\n#endif\n\n//------------------------------------------------------------------------------\n// Get timestamp smeared +/-20% of mEntryIntervalSec around the current\n// timestamp value\n//------------------------------------------------------------------------------\nstd::string\nScanDir::GetTimestampSmearedSec(bool rain_ts) const\n{\n  using namespace std::chrono;\n  uint64_t entry_interval_sec;\n\n  if (rain_ts) {\n    entry_interval_sec = mRainEntryInterval.load();\n  } else {\n    entry_interval_sec = mEntryInterval.load();\n  }\n\n  int64_t smearing =\n    (int64_t)(0.2 * 2 * entry_interval_sec * random() / RAND_MAX) -\n    (int64_t)(0.2 * entry_interval_sec);\n  uint64_t ts_sec;\n\n  if (mClock.IsFake()) {\n    ts_sec = duration_cast<seconds>(mClock.GetTime().time_since_epoch()).count();\n  } else {\n    ts_sec = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();\n  }\n\n  // Avoid underflow when using the steady_clock for testing\n  if ((uint64_t)std::abs(smearing) < ts_sec) {\n    ts_sec += smearing;\n  }\n\n  return std::to_string(ts_sec);\n}\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/ScanDir.hh",
    "content": "//------------------------------------------------------------------------------\n// File: ScanDir.hh\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"fst/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include \"common/FileSystem.hh\"\n#include \"common/FileId.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/SteadyClock.hh\"\n#include \"common/RateLimit.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/WaitInterval.hh\"\n#include \"common/Fmd.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/ns_quarkdb/persistency/MetadataFetcher.hh\"\n#include <deque>\n\nEOSFSTNAMESPACE_BEGIN\n\nclass Load;\nclass FileIo;\nclass CheckSum;\n\nstruct stripe_s {\n  eos::common::FileSystem::fsid_t fsid;\n  std::string url;\n  enum { Unknown, Valid, Invalid } state;\n  unsigned int id; // logical stripe id\n};\n\nconstexpr uint64_t DEFAULT_RAIN_RESCAN_INTERVAL = 4 * 7 * 24 * 3600;\nconstexpr uint64_t DEFAULT_DISK_INTERVAL = 4 * 3600;\nconstexpr uint64_t DEFAULT_NS_INTERVAL = 3 * 24 * 3600;\n\n//------------------------------------------------------------------------------\n//! Class ScanDir\n//! @brief Scan a directory tree and checks checksums (and blockchecksums if\n//! present) on a regular interval with limited bandwidth\n//------------------------------------------------------------------------------\nclass ScanDir: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ScanDir(const char* dirpath, eos::common::FileSystem::fsid_t fsid,\n          eos::fst::Load* fstload, bool bgthread = true,\n          long int file_rescan_interval = 60, int ratebandwidth = 50,\n          bool fake_clock = false);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~ScanDir();\n\n  //----------------------------------------------------------------------------\n  //! Update scanner configuration\n  //!\n  //! @param key configuration type\n  //! @param value configuration value\n  //----------------------------------------------------------------------------\n  void SetConfig(const std::string&, long long value);\n\n  //------------------------------------------------------------------------------\n  //! Infinite loop doing the scanning and verification of disk entries\n  //!\n  //! @param assistant thread running the job\n  //------------------------------------------------------------------------------\n  void RunDiskScan(ThreadAssistant& assistant) noexcept;\n\n#ifndef _NOOFS\n  //------------------------------------------------------------------------------\n  //! Infinite loop doing the scanning of namespace entries\n  //!\n  //! @param assistant thread running the job\n  //------------------------------------------------------------------------------\n  void RunNsScan(ThreadAssistant& assistant) noexcept;\n\n  //------------------------------------------------------------------------------\n  //! Infinite loop doing the computation of alternative checksums\n  //!\n  //! @param assistant thread running the job\n  //------------------------------------------------------------------------------\n  void RunAltXsScan(ThreadAssistant& assistant) noexcept;\n#endif\n\n  using ScanFunc = std::function<void(const std::string&)>;\n\n  //----------------------------------------------------------------------------\n  //! Method traversing all the files in the subtree\n  //!\n  //! @param assistant thread running the job\n  //----------------------------------------------------------------------------\n  void ScanFsTree(ThreadAssistant& assistant, ScanFunc f,\n                  bool skip_internal = true, const WaitInterval* interval = nullptr) noexcept;\n\n  void CheckTree(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Decide if a rescan is needed based on the timestamp provided and the\n  //! configured rescan interval\n  //!\n  //! @param timestamp_us timestamp in seconds\n  //! @param rain_ts if true it refers to a rain scan timestamp, otherwise to a\n  //!        regular scan timestamp\n  //!\n  //! @return true if file is to be rescanned, otherwise false\n  //----------------------------------------------------------------------------\n  bool DoRescan(std::chrono::seconds last_scan, bool rain_ts = false) const;\n\n  //----------------------------------------------------------------------------\n  //! Check the given file for errors and properly account them both at the\n  //! scanner level and also by setting the proper xattrs on the file.\n  //!\n  //! @param fpath file path\n  //!\n  //! @return true if file check, otherwise false\n  //----------------------------------------------------------------------------\n  bool CheckFile(eos::fst::FileIo* io, const std::string& fpath);\n\n  //----------------------------------------------------------------------------\n  //! Get block checksum object for the given file. First we need to check if\n  //! there is a block checksum file (.xsmap) correspnding to the given raw\n  //! file.\n  //!\n  //! @param file_path full path to raw file\n  //!\n  //! @return block checksum object\n  //----------------------------------------------------------------------------\n  std::unique_ptr<eos::fst::CheckSum>\n  GetBlockXS(const std::string& file_path);\n\n  //----------------------------------------------------------------------------\n  //! Check the given file for errors and properly account them both at the\n  //! scanner level and also by setting the proper xattrs on the file.\n  //!\n  //! @param io io object attached to the file\n  //! @param fpath file path\n  //! @param fid file id\n  //! @param last_scan time file was last checked\n  //! @param mtime time file contents was last modified\n  //!\n  //! @return true if file check, otherwise false\n  //----------------------------------------------------------------------------\n  bool ScanFile(eos::fst::FileIo* io,\n                const std::string& fpath, eos::common::FileId::fileid_t fid,\n                std::chrono::seconds last_scan, time_t mtime);\n\n  //----------------------------------------------------------------------------\n  //! Scan the given file for checksum errors taking the load into consideration\n  //!\n  //! @param io io object attached to the file\n  //! @param scan_size final scan size\n  //! @param scan_xs_hex scanned file checksum in hex\n  //! @param filexs_err set to true if file has a checksum error\n  //! @param blockxs_err set to true if file has a block checksum errror\n  //!\n  //! @return true if file is correct, otherwise false if file does not exist,\n  //!        or there is any type of checksum error\n  //----------------------------------------------------------------------------\n  bool ScanFileLoadAware(eos::fst::FileIo* io,\n                         unsigned long long& scan_size,\n                         std::string& scan_xs_hex,\n                         bool& filexs_err, bool& blockxs_err);\n\n  //----------------------------------------------------------------------------\n  //! Check the given replica file for errors.\n  //!\n  //! @param io io object attached to the file\n  //! @param fid file id\n  //! @param mtime time file contents was last modified\n  //!\n  //! @return true if file check, otherwise false\n  bool CheckReplicaFile(eos::fst::FileIo* io,\n                        eos::common::FileId::fileid_t fid,\n                        time_t mtime);\n\n#ifndef _NOOFS\n  //----------------------------------------------------------------------------\n  //! Check the given rain file for errors.\n  //!\n  //! @param io io object attached to the file\n  //! @param fid file id\n  //! @param mtime time file contents was last modified\n  //!\n  //! @return true if file check, otherwise false\n  bool CheckRainFile(eos::fst::FileIo* io, eos::common::FmdHelper* fmd);\n\n  //----------------------------------------------------------------------------\n  //! Check for stripes that are unable to reconstruct the original file\n  //!\n  //! @param stripes list of replica index and stripe urls\n  //! @param xs_val expected checksum\n  //! @param xs_obj checksum object used to calculate the checksum\n  //! @param layout layout id\n  //! @param opaqueInfo oopaque information\n  //!\n  //! @return true if file has expected checksum, false otherwise\n  //----------------------------------------------------------------------------\n  bool IsValidStripeCombination(\n    const std::vector<std::pair<int, std::string>>& stripes,\n    const std::string& xs_val, std::unique_ptr<CheckSum>& xs_obj,\n    eos::common::LayoutId::layoutid_t layout, const std::string& opaqueInfo);\n\n  //----------------------------------------------------------------------------\n  //! Return the list of stripes for the file\n  //!\n  //! @param fid file id\n  //! @param stripes list of stripes\n  //! @param opaqueInfo opaque information\n  //! @param num_locations expected number of stripes\n  //!\n  //! @return true if no errors, false otherwise\n  //----------------------------------------------------------------------------\n  bool GetPioOpenInfo(eos::common::FileId::fileid_t fid,\n                      std::vector<stripe_s>& stripes,\n                      std::string& opaqueInfo,\n                      uint32_t num_locations);\n\n  //----------------------------------------------------------------------------\n  //! Update local fmd with info from the stripe check\n  //!\n  //! @param io io object attached to the file\n  //! @param fid file id\n  //! @param invalid_fsid list of invalid stripes' locations\n  //!\n  //! @return true if no errors, false otherwise\n  //----------------------------------------------------------------------------\n  void ReportInvalidFsid(eos::fst::FileIo* io,\n                         eos::common::FileId::fileid_t fid,\n                         const std::set<eos::common::FileSystem::fsid_t>& invalid_fsid);\n\n  //----------------------------------------------------------------------------\n  //! Check if the file was open or update during the scan\n  //!\n  //! @param io io object attached to the file\n  //! @param fid file id\n  //! @param stat_before stat info before going the scan\n  //!\n  //! @return true if file was updated/opened after the scan\n  //----------------------------------------------------------------------------\n  bool ShouldSkipAfterCheck(eos::fst::FileIo* io,\n                            eos::common::FileId::fileid_t fid,\n                            const struct stat& stat_before);\n\n  //----------------------------------------------------------------------------\n  //! Check each stripe to verify if they can reconstruct the original file\n  //!\n  //! @param fid file id\n  //! @param invalid_fsid fsids of invalid stripes\n  //!\n  //! @return true if check happened, false if error occurred\n  //----------------------------------------------------------------------------\n  bool\n  ScanRainFileLoadAware(eos::common::FileId::fileid_t fid,\n                        std::set<eos::common::FileSystem::fsid_t>& invalid_fsid);\n#endif\n  //----------------------------------------------------------------------------\n  //! Check the given file for rain stripes errors\n  //!\n  //! @param io io object attached to the file\n  //! @param fpath file path\n  //! @param fid file id\n  //! @param last_scan time file was last checked\n  //!\n  //! @return true if file check, otherwise false\n  //----------------------------------------------------------------------------\n  bool\n  ScanRainFile(eos::fst::FileIo* io, eos::common::FmdHelper* fmd,\n               std::chrono::seconds last_scan);\n\n  //----------------------------------------------------------------------------\n  //! Get clock reference for testing purposes\n  //----------------------------------------------------------------------------\n  inline eos::common::SteadyClock& GetClock()\n  {\n    return mClock;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get timestamp in seconds smeared +/-20% of\n  //! mEntryIntervalSec/mRainEntryIntervalSec around the current timestamp value\n  //!\n  //! @param rain_ts if true it refers to a rain scan timestamp, otherwise to a\n  //!        regular scan timestamp\n  //!\n  //! @return string representing timestamp in seconds since epoch\n  //----------------------------------------------------------------------------\n  std::string GetTimestampSmearedSec(bool rain_ts = false) const;\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n\n#ifndef _NOOFS\n  //----------------------------------------------------------------------------\n  //! Collect all file ids present on the current file system from the NS view\n  //!\n  //! @param type can be either eos::fsview::sFilesSuffix or\n  //!        eos::fsview::sUnlinkedSuffix\n  //!\n  //! @return queue holding the file ids\n  //----------------------------------------------------------------------------\n  std::deque<eos::IFileMD::id_t> CollectNsFids(const std::string& type) const;\n\n  //----------------------------------------------------------------------------\n  //! Account for missing replicas\n  //----------------------------------------------------------------------------\n  void AccountMissing();\n\n  //----------------------------------------------------------------------------\n  //! Cleanup unlinked replicas which are older than 1 hour\n  //----------------------------------------------------------------------------\n  void CleanupUnlinked();\n\n#endif\n\n  //! Default ns scan rate is bound by the number of IO ops a disk can handle\n  //! and we set it to half the average max IOOPS for HDD which is 100.\n  static constexpr unsigned long long sDefaultNsScanRate {50};\n\n  //----------------------------------------------------------------------------\n  //! Check if file is unlinked from the namespace and in the process of being\n  //! deleted from the disk. Files that are unlinked for more than 30 min\n  //! definetely have a problem and we don't account them as in the process of\n  //! being deleted.\n  //!\n  //! @param fid file identifier\n  //!\n  //! @return true if file is being deleted, otherwise false\n  //----------------------------------------------------------------------------\n  bool IsBeingDeleted(const eos::IFileMD::id_t fid) const;\n\n  //----------------------------------------------------------------------------\n  //! Drop ghost fid from the given file system id\n  //!\n  //! @param fsid file system id\n  //! @param fid file identifier\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool DropGhostFid(const eos::common::FileSystem::fsid_t fsid,\n                    const eos::IFileMD::id_t fid) const;\n\n  void UpdateLocalAltXsMetadata(eos::common::FmdHelper& fmd);\n\n  bool DoAltXsSync(const eos::common::FmdHelper& fmd);\n\n  void SetAltXsSynced(eos::fst::FileIo* io);\n\n  //----------------------------------------------------------------------------\n  //! Print log message - depending on whether or not we run in standalone mode\n  //! or inside the FST daemon\n  //!\n  //! @param log_level log level used for the printout\n  //----------------------------------------------------------------------------\n  template <typename ... Args>\n  void LogMsg(int log_level, Args&& ... args)\n  {\n    if (mBgThread) {\n      eos_static_log(log_level, std::forward<Args>(args) ...);\n    } else {\n      if ((log_level == LOG_INFO) || (log_level == LOG_DEBUG)) {\n        fprintf(stdout, std::forward<Args>(args) ...);\n      } else {\n        fprintf(stderr, std::forward<Args>(args) ...);\n        fprintf(stderr, \"%s\", \"\\n\");\n      }\n    }\n  }\n\n  eos::fst::Load* mFstLoad; ///< Object for providing load information\n  eos::common::FileSystem::fsid_t mFsId; ///< Corresponding file system id\n  std::string mDirPath; ///< Root directory used by the scanner\n  std::atomic<int> mRateBandwidth; ///< Max scan IO rate in MB/s\n\n  //! Time interval after which the scanner will run again, default 3 days\n  WaitInterval mNsInterval;\n  //! Time interval after which the disk scanner will run again, default 4h\n  WaitInterval mDiskInterval;\n  //! Time interval after which a file is rescanned in seconds, if 0 then\n  //! rescanning is completely disabled\n  std::atomic<uint64_t> mEntryInterval;\n  //! Time interval after which a rain file is rescanned in seconds, if 0 then\n  //! rescanning is completely disabled\n  std::atomic<uint64_t> mRainEntryInterval;\n  //! Whether or not make the synchronization of the altxs's metadata\n  std::atomic<bool> mAltXsDoSync;\n\n  std::atomic<uint64_t> mAltXsSyncInterval;\n\n  // Configuration for alternative checksums computation\n  //! Time interval after which the thread for alternative checksyms will run again, default 30 days\n  WaitInterval mAltXsInterval;\n\n  // Statistics\n  long int mNumScannedFiles;\n  long int mNumCorruptedFiles;\n  long int mNumHWCorruptedFiles;\n  long long int mTotalScanSize;\n  long int mNumTotalFiles;\n  long int mNumSkippedFiles;\n  char* mBuffer; ///< Buffer used for reading\n  uint32_t mBufferSize; ///< Size of the reading buffer\n  bool mBgThread; ///< If true running as background thread inside the FST\n  AssistedThread mDiskThread; ///< Thread doing the scanning of the disk\n  AssistedThread mNsThread; ///< Thread doing the scanning of NS entries\n  AssistedThread mAltXsThread; ///< Thread computing the alternative checksums\n  eos::common::SteadyClock mClock; ///< Clock wrapper used for testing\n  //! Rate limiter for ns scanning which actually limits the number of stat\n  //! requests send across the disks in one FSTs.\n  std::unique_ptr<eos::common::IRateLimit> mRateLimit;\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/Verify.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Verify.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __XRDFSTOFS_VERIFY_HH__\n#define __XRDFSTOFS_VERIFY_HH__\n#include \"fst/Namespace.hh\"\n#include \"common/FileId.hh\"\n#include \"common/StringConversion.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <vector>\n\nclass XrdOucEnv;\n\nEOSFSTNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\nclass Verify\n{\npublic:\n\n  unsigned long long fId;\n  unsigned long fsId;\n  unsigned long cId;\n  unsigned long lId;\n\n  XrdOucString managerId;\n  XrdOucString opaque;\n  XrdOucString container;\n  XrdOucString path;\n\n  bool computeChecksum;\n  bool commitChecksum;\n  std::vector<eos::common::LayoutId::eChecksum> altchecksums;\n  bool commitSize;\n  bool commitFmd;\n\n  unsigned int verifyRate;\n\n  Verify(unsigned long long fid, unsigned long fsid,\n         const char* managerid, const char* inopaque, const char* incontainer,\n         unsigned long incid, unsigned long inlid, const char* inpath,\n         bool inComputeChecksum, bool inCommitChecksum, bool inCommitSize,\n         bool inCommitFmd, unsigned int inVerifyRate,\n         std::vector<eos::common::LayoutId::eChecksum> altChecksums)\n  {\n    fId = fid;\n    fsId = fsid;\n    managerId = managerid;\n    opaque = inopaque;\n    container = incontainer;\n    cId = incid;\n    path = inpath;\n    lId = inlid;\n    computeChecksum = inComputeChecksum;\n    commitChecksum = inCommitChecksum;\n    commitSize = inCommitSize;\n    verifyRate = inVerifyRate;\n    commitFmd = inCommitFmd;\n    altchecksums = altChecksums;\n  }\n\n  static Verify*\n  Create(XrdOucEnv* capOpaque)\n  {\n    // decode the opaque tags\n    XrdOucString hexfids = \"\";\n    XrdOucString hexfid = \"\";\n    XrdOucString access = \"\";\n    const char* container = 0;\n    const char* scid = 0;\n    const char* layout = 0;\n    const char* path = 0;\n    bool computeChecksum = false;\n    bool commitChecksum = false;\n    bool commitSize = false;\n    bool commitFmd = false;\n    const char* sfsid = 0;\n    const char* smanager = 0;\n    unsigned long long fid = 0;\n    unsigned long fsid = 0;\n    unsigned long cid = 0;\n    unsigned long lid = 0;\n    unsigned int verifyRate = 0;\n    std::vector<eos::common::LayoutId::eChecksum> altchecksums;\n\n    if (!capOpaque) {\n      return 0;\n    }\n\n    hexfid = capOpaque->Get(\"mgm.fid\");\n    sfsid = capOpaque->Get(\"mgm.fsid\");\n    smanager = capOpaque->Get(\"mgm.manager\");\n    access = capOpaque->Get(\"mgm.access\");\n    container = capOpaque->Get(\"mgm.container\");\n    scid = capOpaque->Get(\"mgm.cid\");\n    path = capOpaque->Get(\"mgm.path\");\n    layout = capOpaque->Get(\"mgm.lid\");\n\n    if (capOpaque->Get(\"mgm.verify.compute.checksum\")) {\n      computeChecksum = atoi(capOpaque->Get(\"mgm.verify.compute.checksum\"));\n    }\n\n    if (capOpaque->Get(\"mgm.verify.commit.checksum\")) {\n      commitChecksum = atoi(capOpaque->Get(\"mgm.verify.commit.checksum\"));\n    }\n\n    if (capOpaque->Get(\"mgm.verify.commit.size\")) {\n      commitSize = atoi(capOpaque->Get(\"mgm.verify.commit.size\"));\n    }\n\n    if (capOpaque->Get(\"mgm.verify.commit.fmd\")) {\n      commitFmd = atoi(capOpaque->Get(\"mgm.verify.commit.fmd\"));\n    }\n\n    if (capOpaque->Get(\"mgm.verify.rate\")) {\n      verifyRate = atoi(capOpaque->Get(\"mgm.verify.rate\"));\n    }\n\n    if (capOpaque->Get(\"mgm.verify.compute.altchecksum\")) {\n      std::vector<std::string> list;\n      eos::common::StringConversion::Tokenize(\n        capOpaque->Get(\"mgm.verify.compute.altchecksum\"), list, \",\");\n\n      for (auto xs : list) {\n        altchecksums.emplace_back(static_cast<eos::common::LayoutId::eChecksum>\n                                  (eos::common::LayoutId::GetChecksumFromString(xs)));\n      }\n    }\n\n    // permission check\n    if (access != \"verify\") {\n      return 0;\n    }\n\n    if (!hexfid.length() || !sfsid || !smanager || !layout || !scid) {\n      return 0;\n    }\n\n    cid = strtoul(scid, 0, 10);\n    lid = strtoul(layout, 0, 10);\n    int envlen = 0;\n    fid = eos::common::FileId::Hex2Fid(hexfid.c_str());\n    fsid = atoi(sfsid);\n    return new Verify(fid, fsid, smanager, capOpaque->Env(envlen),\n                      container, cid, lid, path, computeChecksum, commitChecksum, commitSize,\n                      commitFmd, verifyRate, altchecksums);\n  };\n\n  ~Verify() { };\n\n  //----------------------------------------------------------------------------\n  //! Display information about current verification job\n  //----------------------------------------------------------------------------\n  void\n  Show(const char* show = \"\")\n  {\n    eos_static_info(\"Verify fxid=%08llx on fs=%u path=%s compute_checksum=%d \"\n                    \"commit_checksum=%d commit_size=%d commit_fmd=%d \"\n                    \"verify_rate=%d %s\", fId, fsId, path.c_str(),\n                    computeChecksum, commitChecksum, commitSize, commitFmd,\n                    verifyRate, show);\n  }\n};\n\nEOSFSTNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "fst/XrdFstOfs.cc",
    "content": "// ----------------------------------------------------------------------\n// Fileq: XrdFstOfs.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/XrdFstOss.hh\"\n#include \"fst/Config.hh\"\n#include \"fst/filemd/FmdAttr.hh\"\n#include \"fst/checksum/ChecksumPlugins.hh\"\n#include \"fst/http/HttpServer.hh\"\n#include \"fst/storage/FileSystem.hh\"\n#include \"fst/storage/Storage.hh\"\n#include \"fst/Deletion.hh\"\n#include \"fst/Verify.hh\"\n#include \"fst/utils/XrdOfsPathHandler.hh\"\n#include \"qclient/structures/QSet.hh\"\n#ifdef HAVE_NFS\n#include \"fst/io/nfs/NfsIo.hh\"\n#endif\n#include \"common/Utils.hh\"\n#include \"common/PasswordHandler.hh\"\n#include \"common/FileId.hh\"\n#include \"common/FileSystem.hh\"\n#include \"common/Path.hh\"\n#include \"common/Statfs.hh\"\n#include \"common/SyncAll.hh\"\n#include \"common/StackTrace.hh\"\n#include \"common/Timing.hh\"\n#include \"common/eos_cta_pb/EosCtaAlertHandler.hh\"\n#include \"common/Constants.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/XattrCompat.hh\"\n#include \"common/ShellCmd.hh\"\n#include \"common/BufferManager.hh\"\n#include \"common/async/ExecutorMgr.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/ns_quarkdb/persistency/RequestBuilder.hh\"\n#include \"private/XrdSfs/XrdSfsFAttr.hh\"\n#include <XrdNet/XrdNetOpts.hh>\n#include <XrdNet/XrdNetUtils.hh>\n#include <XrdOfs/XrdOfs.hh>\n#include <XrdOuc/XrdOucHash.hh>\n#include <XrdOuc/XrdOucTrace.hh>\n#include <XrdSfs/XrdSfsAio.hh>\n#include <Xrd/XrdScheduler.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdCl/XrdClDefaultEnv.hh>\n#include <XrdVersion.hh>\n#include \"qclient/Members.hh\"\n#include \"qclient/QClient.hh\"\n#include \"qclient/shared/SharedManager.hh\"\n#include \"proto/Delete.pb.h\"\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <errno.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <math.h>\n#include <stdio.h>\n#include <execinfo.h>\n#include <signal.h>\n#include <stdlib.h>\n#include <sstream>\n#include <thread>\n#include <cctype>\n#include <algorithm>\n#include <sys/prctl.h>\n#include <sys/utsname.h>\n\n// The global OFS handle\neos::fst::XrdFstOfs eos::fst::gOFS;\n\nextern XrdSysError OfsEroute;\nextern XrdOss* XrdOfsOss;\nextern XrdOfs* XrdOfsFS;\nextern XrdSysTrace OfsTrace;\n\n// Set the version information\nXrdVERSIONINFO(XrdSfsGetFileSystem2, FstOfs);\n\n#ifdef COVERAGE_BUILD\n// Forward declaration of gcov flush API\nextern \"C\" void __gcov_dump(void);\n#endif\n\n//------------------------------------------------------------------------------\n// XRootD OFS interface implementation\n//------------------------------------------------------------------------------\nextern \"C\"\n{\n  XrdSfsFileSystem* XrdSfsGetFileSystem2(XrdSfsFileSystem* nativeFS,\n                                         XrdSysLogger*     Logger,\n                                         const char*       configFn,\n                                         XrdOucEnv*        envP)\n  {\n    if (XrdOfsFS) {\n      return XrdOfsFS;\n    }\n\n    OfsEroute.SetPrefix(\"FstOfs_\");\n    OfsEroute.logger(Logger);\n    // Disable XRootD log rotation\n    Logger->setRotate(0);\n    std::ostringstream oss;\n    oss << \"FstOfs (Object Storage File System) \" << VERSION;\n    XrdOucString version = \"FstOfs (Object Storage File System) \";\n    OfsEroute.Say(\"++++++ (c) 2010 CERN/IT-DSS \", oss.str().c_str());\n    // Initialize the subsystems\n    eos::fst::gOFS.ConfigFN = (configFn && *configFn ? strdup(configFn) : 0);\n    fprintf(stderr, \"pcrctl= %d\\n\", prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0));\n\n    if (eos::fst::gOFS.Configure(OfsEroute, envP)) {\n      return 0;\n    }\n\n    XrdOfsFS = &eos::fst::gOFS;\n    return &eos::fst::gOFS;\n  }\n}\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Get simulation error offset. Parse the last characters and return the\n// desired offset e.g. io_read_8M should return 8MB\n//------------------------------------------------------------------------------\nuint64_t\nXrdFstOfs::GetSimulationErrorOffset(const std::string& input)\n{\n  uint64_t offset {0ull};\n  size_t num = std::count(input.begin(), input.end(), '_');\n\n  if ((num < 2) || (*input.rbegin() == '_')) {\n    return offset;\n  }\n\n  size_t pos = input.rfind('_');\n  std::string soff = input.substr(pos + 1);\n  offset = eos::common::StringConversion::GetDataSizeFromString(soff.c_str());\n  return offset;\n}\n\n//------------------------------------------------------------------------------\n// Get simulation delay value. If none set then return 10 by default.\n//------------------------------------------------------------------------------\nuint32_t\nXrdFstOfs::GetSimulationDelay(const std::string& input)\n{\n  uint32_t delay = 10;\n  size_t num = std::count(input.begin(), input.end(), '_');\n\n  if ((num < 2) || (*input.rbegin() == '_')) {\n    return delay;\n  }\n\n  size_t pos = input.rfind('_');\n  std::string soff = input.substr(pos + 1);\n\n  try {\n    delay = std::stoul(soff);\n  } catch (...) {\n    // nothing to do\n  }\n\n  return delay;\n}\n\n//------------------------------------------------------------------------------\n// Get stacktrace from crashing process\n//------------------------------------------------------------------------------\nvoid\nXrdFstOfs::xrdfstofs_stacktrace(int sig)\n{\n  (void) signal(SIGINT, SIG_IGN);\n  (void) signal(SIGTERM, SIG_IGN);\n  (void) signal(SIGQUIT, SIG_IGN);\n  void* array[10];\n  size_t size;\n  // Get void*'s for all entries on the stack\n  size = backtrace(array, 10);\n  // Print out all the frames to stderr\n  fprintf(stderr, \"error: received signal %d:\\n\", sig);\n  backtrace_symbols_fd(array, size, 2);\n  eos::common::StackTrace::GdbTrace(0, getpid(), \"thread apply all bt\");\n\n  if (getenv(\"EOS_CORE_DUMP\")) {\n    eos::common::StackTrace::GdbTrace(0, getpid(), \"generate-core-file\");\n  }\n\n  // Now we put back the initial handler and send the signal again\n  signal(sig, SIG_DFL);\n  kill(getpid(), sig);\n  int wstatus = 0;\n  wait(&wstatus);\n}\n\n//------------------------------------------------------------------------------\n// Print coverage data\n//------------------------------------------------------------------------------\nvoid\nXrdFstOfs::xrdfstofs_coverage(int sig)\n{\n#ifdef COVERAGE_BUILD\n  eos_static_notice(\"msg=\\\"printing coverage data\\\"\");\n  __gcov_dump();\n  return;\n#endif\n  eos_static_notice(\"msg=\\\"compiled without coverage support\\\"\");\n}\n\n//------------------------------------------------------------------------------\n// FST shutdown procedure\n//------------------------------------------------------------------------------\nvoid\nXrdFstOfs::xrdfstofs_shutdown(int sig)\n{\n  static XrdSysMutex ShutDownMutex;\n  ShutDownMutex.Lock(); // this handler goes only one-shot .. sorry !\n  gOFS.sShutdown = true;\n  pid_t watchdog;\n  pid_t ppid = getpid();\n\n  if (!(watchdog = fork())) {\n    eos::common::SyncAll::AllandClose();\n    // Sleep for an amount of time proportional to the number of filesystems\n    // on the current machine\n    auto timeout = std::chrono::seconds(gOFS.Storage->GetFSCount() * 5);\n    std::this_thread::sleep_for(timeout);\n    fprintf(stderr, \"@@@@@@ 00:00:00 op=shutdown msg=\\\"shutdown timedout after \"\n            \"%li seconds, signal=%i\\n\", timeout.count(), sig);\n\n    if (ppid > 1) {\n      kill(ppid, 9);\n    }\n\n    fprintf(stderr, \"@@@@@@ 00:00:00 %s\", \"op=shutdown status=forced-complete\\n\");\n    kill(getpid(), 9);\n  }\n\n  eos_static_warning(\"%s\", \"op=shutdown msg=\\\"stopped messaging\\\"\");\n  gOFS.Storage->Shutdown();\n  eos_static_warning(\"%s\", \"op=shutdown msg=\\\"stopped storage activities\\\"\");\n\n  if (watchdog > 1) {\n    kill(watchdog, 9);\n  }\n\n  int wstatus = 0;\n  wait(&wstatus);\n  // Close all file descriptors we can sync or are sockets\n  eos::common::SyncAll::AllandCloseFileSocks();\n  eos_static_warning(\"%s\", \"op=shutdown status=completed\");\n  // harakiri - yes!\n  (void) signal(SIGABRT, SIG_IGN);\n  (void) signal(SIGINT,  SIG_IGN);\n  (void) signal(SIGTERM, SIG_IGN);\n  (void) signal(SIGQUIT, SIG_IGN);\n  kill(getpid(), 9);\n}\n\n//------------------------------------------------------------------------------\n// FST \"graceful\" shutdown procedure\n//------------------------------------------------------------------------------\nvoid\nXrdFstOfs::xrdfstofs_graceful_shutdown(int sig)\n{\n  using namespace eos::common;\n  eos_static_info(\"entering the \\\"graceful\\\" shutdown procedure\");\n  pid_t watchdog;\n  static XrdSysMutex grace_shutdown_mtx;\n  grace_shutdown_mtx.Lock();\n  gOFS.sShutdown = true;\n  const char* swait = getenv(\"EOS_GRACEFUL_SHUTDOWN_TIMEOUT\");\n  std::int64_t wait = (swait ? std::strtol(swait, nullptr, 10) : 390);\n  pid_t ppid = getpid();\n\n  if (!(watchdog = fork())) {\n    std::this_thread::sleep_for(std::chrono::seconds(wait));\n    SyncAll::AllandClose();\n    std::this_thread::sleep_for(std::chrono::seconds(15));\n    fprintf(stderr, \"@@@@@@ 00:00:00 %s %li seconds\\\"\\n\",\n            \"op=shutdown msg=\\\"shutdown timedout after \", wait);\n\n    if (ppid > 1) {\n      kill(ppid, 9);\n    }\n\n    fprintf(stderr, \"@@@@@@ 00:00:00 %s\", \"op=shutdown status=forced-complete\");\n    kill(getpid(), 9);\n  }\n\n  // Wait for 60 seconds heartbeat timeout (see mgm/FsView) + 30 seconds\n  // for in-flight redirections\n  eos_static_warning(\"op=shutdown msg=\\\"wait 90 seconds for configuration \"\n                     \"propagation\\\"\");\n  std::chrono::seconds config_timeout(60 + 30);\n  std::this_thread::sleep_for(config_timeout);\n  std::chrono::seconds io_timeout((std::int64_t)(wait * 0.9));\n\n  if (gOFS.WaitForOngoingIO(io_timeout)) {\n    eos_static_warning(\"%s\", \"op=shutdown msg=\\\"successful graceful IO shutdown\\\"\");\n  } else {\n    eos_static_err(\"%s\", \"op=shutdown msg=\\\"failed graceful IO shutdown\\\"\");\n  }\n\n  eos_static_warning(\"%s\", \"op=shutdown msg=\\\"storage object shutdown\\\"\");\n  gOFS.Storage->Shutdown();\n\n  if (watchdog > 1) {\n    kill(watchdog, 9);\n  }\n\n  int wstatus = 0;\n  ::wait(&wstatus);\n  // Close all file descriptors we can sync or are sockets\n  SyncAll::AllandCloseFileSocks();\n  eos_static_warning(\"%s\", \"op=shutdown status=completed\");\n  // harakiri - yes!\n  (void) signal(SIGABRT, SIG_IGN);\n  (void) signal(SIGINT,  SIG_IGN);\n  (void) signal(SIGTERM, SIG_IGN);\n  (void) signal(SIGQUIT, SIG_IGN);\n  (void) signal(SIGUSR1, SIG_IGN);\n  kill(getpid(), 9);\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nXrdFstOfs::XrdFstOfs() :\n  eos::common::LogId(), Storage(nullptr),\n  mHostName(NULL), mHttpd(nullptr),\n  mGeoTag(\"nogeotag\"), mXrdBuffPool(eos::common::KB, 32 * eos::common::MB),\n  mAsyncOpThreadPool(8, 64, 5, 6, 5, \"async_op\"),\n  mMgmXrdPool(nullptr),\n  mSimOpenDelay(false), mSimOpenDelaySec(120),\n  mSimFmdOpenErr(false), mSimIoReadErr(false),\n  mSimReadDelay(false), mSimReadDelaySec(10),\n  mSimIoWriteErr(false), mSimXsReadErr(false),\n  mSimXsWriteErr(false), mSimXsWriteErrDelay(0ull),\n  mSimErrIoReadOff(0ull), mSimErrIoWriteOff(0ull),\n  mSimDiskWriting(false), mSimCloseErr(false),\n  mSimCloseCommitMgmErr(false), mSimUnresponsive(false)\n{\n  Eroute = 0;\n  TransferScheduler = 0;\n  TpcMap.resize(2);\n  TpcMap[0].set_deleted_key(\"\"); // readers\n  TpcMap[1].set_deleted_key(\"\"); // writers\n\n  if (!getenv(\"EOS_NO_SHUTDOWN\")) {\n    // Add shutdown handler\n    (void) signal(SIGINT, xrdfstofs_shutdown);\n    (void) signal(SIGTERM, xrdfstofs_shutdown);\n    (void) signal(SIGQUIT, xrdfstofs_shutdown);\n    // Add graceful shutdown handler\n    (void) signal(SIGUSR1, xrdfstofs_graceful_shutdown);\n  }\n\n  if (getenv(\"EOS_COVERAGE_REPORT\")) {\n    // Add coverage report handler\n    (void) signal(SIGPROF, xrdfstofs_coverage);\n  }\n\n  if (getenv(\"EOS_FST_ENABLE_STACKTRACE\")) {\n    // Add stacktrace handler - this is useful for crashes inside containers\n    // where abrtd is not configured\n    (void) signal(SIGSEGV, xrdfstofs_stacktrace);\n    (void) signal(SIGABRT, xrdfstofs_stacktrace);\n    (void) signal(SIGBUS, xrdfstofs_stacktrace);\n  }\n\n  if (getenv(\"EOS_MGM_ALIAS\")) {\n    // Use MGM alias if available\n    mMgmAlias = getenv(\"EOS_MGM_ALIAS\");\n  }\n\n  if (getenv(\"EOS_FST_ALIAS\")) {\n    gConfig.HostAlias = getenv(\"EOS_FST_ALIAS\");\n    fprintf(stderr, \"Setting host alias to %s\\n\", gConfig.HostAlias.c_str());\n  }\n\n  if (getenv(\"EOS_FST_PORT_ALIAS\")) {\n    gConfig.PortAlias = getenv(\"EOS_FST_PORT_ALIAS\");\n  }\n\n  // Initialize the google sparse hash maps\n  gOFS.WNoDeleteOnCloseFid.clear_deleted_key();\n  gOFS.WNoDeleteOnCloseFid.set_deleted_key(0);\n  setenv(\"EOSFSTOFS\", std::to_string((unsigned long long)this).c_str(), 1);\n\n  if (getenv(\"EOS_FST_CALL_MANAGER_XRD_POOL\")) {\n    int max_size = 10;\n    const char* csize {nullptr};\n\n    if ((csize = getenv(\"EOS_FST_CALL_MANAGER_XRD_POOL_SIZE\"))) {\n      try {\n        max_size = std::stoi(csize);\n\n        if (max_size < 1) {\n          max_size = 1;\n        }\n\n        if (max_size > 32) {\n          max_size = 32;\n        }\n      } catch (...) {\n        // ignore\n      }\n    }\n\n    mMgmXrdPool.reset(new eos::common::XrdConnPool(true, max_size));\n    fprintf(stderr, \"Config Enabled CallManager xrootd connection pool with \"\n            \"size=%i\\n\", max_size);\n  }\n\n  if (getenv(\"EOS_FST_FSCK_DELETE_BY_MOVE\")) {\n    mEnvFsckDeleteByMove = true;\n  }\n\n  UpdateTpcKeyValidity();\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nXrdFstOfs::~XrdFstOfs()\n{\n  if (mHostName) {\n    free(const_cast<char*>(mHostName));\n  }\n\n  // Free configuration file name allocated via strdup during initialization\n  if (ConfigFN) {\n    free(ConfigFN);\n    ConfigFN = nullptr;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get a new OFS directory object - not implemented\n//------------------------------------------------------------------------------\nXrdSfsDirectory*\nXrdFstOfs::newDir(char* user, int MonID)\n{\n  return (XrdSfsDirectory*)(0);\n}\n\n//------------------------------------------------------------------------------\n// Get a new OFS file object\n//-----------------------------------------------------------------------------\nXrdSfsFile*\nXrdFstOfs::newFile(char* user, int MonID)\n{\n  return static_cast<XrdSfsFile*>(new XrdFstOfsFile(user, MonID));\n}\n\n//------------------------------------------------------------------------------\n// OFS layer configuration\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::Configure(XrdSysError& Eroute, XrdOucEnv* envP)\n{\n  char* var;\n  const char* val;\n  int cfgFD;\n  int NoGo = 0;\n  eos::common::StringConversion::InitLookupTables();\n  {\n    // Run a dummy command so that the ShellExecutor is forked before any XrdCl\n    // is initialized. Otherwise it might segv due to the following bug:\n    // https://github.com/xrootd/xrootd/issues/1515\n    eos::common::ShellCmd dummy_cmd(\"uname -a\");\n  }\n\n  if (XrdOfs::Configure(Eroute, envP)) {\n    Eroute.Emsg(\"Config\", \"default OFS configuration failed\");\n    return SFS_ERROR;\n  }\n\n  // Enforcing 'sss' authentication for all communications\n  if (!getenv(\"EOS_FST_NO_SSS_ENFORCEMENT\")) {\n    setenv(\"XrdSecPROTOCOL\", \"sss\", 1);\n    Eroute.Say(\"=====> fstofs enforces SSS authentication for XROOT clients\");\n  } else {\n    Eroute.Say(\"=====> fstofs does not enforce SSS authentication for XROOT\"\n               \" clients - make sure MGM enforces sss for this FST!\");\n  }\n\n  // Get the hostname\n  const char* errtext = 0;\n  mHostName = XrdNetUtils::MyHostName(0, &errtext);\n\n  if (!mHostName) {\n    Eroute.Emsg(\"Config\", \"hostname is invalid : %s\", mHostName);\n    return 1;\n  }\n\n  TransferScheduler = new XrdScheduler(&Eroute, &OfsTrace, 8, 128, 60);\n  TransferScheduler->Start();\n  gConfig.autoBoot = false;\n  gConfig.FstOfsBrokerUrl = \"root://localhost:1097//eos/\";\n\n  if (getenv(\"EOS_BROKER_URL\")) {\n    gConfig.FstOfsBrokerUrl = getenv(\"EOS_BROKER_URL\");\n  }\n\n  // Handle geotag configuration\n  char* ptr_geotag = getenv(\"EOS_GEOTAG\");\n\n  if (ptr_geotag) {\n    mGeoTag = eos::common::SanitizeGeoTag(ptr_geotag);\n\n    if (mGeoTag != ptr_geotag) {\n      Eroute.Emsg(\"Config\", mGeoTag.c_str());\n      return 1;\n    }\n  }\n\n  {\n    // set the start date as string\n    XrdOucString out = \"\";\n    time_t t = time(NULL);\n    struct tm* timeinfo;\n    timeinfo = localtime(&t);\n    out = asctime(timeinfo);\n    out.erase(out.length() - 1);\n    gConfig.StartDate = out.c_str();\n  }\n\n  gConfig.FstMetaLogDir = \"/var/tmp/eos/md/\";\n  gConfig.FstAuthDir = \"/var/eos/auth/\";\n  setenv(\"XrdClientEUSER\", \"daemon\", 1);\n  SetXrdClConfig();\n  // Extract the manager from the config file\n  XrdOucStream Config(&Eroute, getenv(\"XRDINSTANCE\"));\n\n  if (!ConfigFN || !*ConfigFN) {\n    // this error will be reported by XrdOfsFS.Configure\n  } else {\n    // Try to open the configuration file.\n    if ((cfgFD = open(ConfigFN, O_RDONLY, 0)) < 0) {\n      return Eroute.Emsg(\"Config\", errno, \"open config file fn=\", ConfigFN);\n    }\n\n    Config.Attach(cfgFD);\n\n    // Now start reading records until eof.\n    while ((var = Config.GetMyFirstWord())) {\n      if (!strncmp(var, \"fstofs.\", 7)) {\n        var += 7;\n\n        // we parse config variables here\n        if (!strcmp(\"broker\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument 2 for broker missing. Should be \"\n                        \"URL like root://<host>/<queue>/\");\n            NoGo = 1;\n          } else {\n            if (getenv(\"EOS_BROKER_URL\")) {\n              gConfig.FstOfsBrokerUrl = getenv(\"EOS_BROKER_URL\");\n            } else {\n              gConfig.FstOfsBrokerUrl = val;\n            }\n          }\n        }\n\n        if (!strcmp(\"trace\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument 2 for trace missing. Can be 'client'\");\n            NoGo = 1;\n          } else {\n            //EnvPutInt( NAME_DEBUG, 3);\n          }\n        }\n\n        if (!strcmp(\"autoboot\", var)) {\n          if ((!(val = Config.GetWord())) ||\n              (strcmp(\"true\", val) && strcmp(\"false\", val) &&\n               strcmp(\"1\", val) && strcmp(\"0\", val))) {\n            Eroute.Emsg(\"Config\", \"argument 2 for autobootillegal or missing. \"\n                        \"Must be <true>,<false>,<1> or <0>!\");\n            NoGo = 1;\n          } else {\n            if ((!strcmp(\"true\", val) || (!strcmp(\"1\", val)))) {\n              gConfig.autoBoot = true;\n            }\n          }\n        }\n\n        // Use gRPC calls instead of xrootd notifications?\n        if (!strcmp(\"protowfusegrpc\", var)) {\n          if ((!(val = Config.GetWord())) ||\n              (strcmp(\"true\", val) && strcmp(\"false\", val) &&\n               strcmp(\"1\", val) && strcmp(\"0\", val))) {\n            Eroute.Emsg(\"Config\", \"argument for protowfusegrpc is invalid. \"\n                        \"Must be <true>, <false>, <1> or <0>!\");\n            NoGo = 1;\n          } else {\n            gConfig.protowfusegrpc = false;\n\n            if ((!strcmp(\"true\", val) || (!strcmp(\"1\", val)))) {\n              gConfig.protowfusegrpc = true;\n            }\n\n            Eroute.Say(\"=====> fstofs.protowfusegrpc : \", val);\n          }\n        }\n\n        if (!strcmp(\"protowfusegrpctls\", var)) {\n          if ((!(val = Config.GetWord())) ||\n              (strcmp(\"true\", val) && strcmp(\"false\", val) &&\n               strcmp(\"1\", val) && strcmp(\"0\", val))) {\n            Eroute.Emsg(\"Config\", \"argument for protowfusegrpctls is invalid. \"\n                        \"Must be <true>, <false>, <1> or <0>!\");\n            NoGo = 1;\n          } else {\n            /* false already set when declared */\n            if ((!strcmp(\"true\", val) || (!strcmp(\"1\", val)))) {\n              gConfig.protowfusegrpctls = true;\n            }\n\n            Eroute.Say(\"=====> fstofs.protowfusegrpctls : \", val);\n          }\n        }\n\n        if (!strcmp(\"jwttokenpath\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument 2 for JwtTokenPath missing. Should be \"\n                        \"an absolute path like /etc/grid-security/jwt-token-grpc\");\n            NoGo = 1;\n          } else {\n            gConfig.JwtTokenPath = val;\n          }\n\n          Eroute.Say(\"=====> fstofs.jwttokenpath : \", val);\n        }\n\n        if (!strcmp(\"metalog\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument 2 for metalog missing\");\n            NoGo = 1;\n          } else {\n            if (strlen(val)) {\n              gConfig.FstMetaLogDir = val;\n\n              if (val[strlen(val) - 1] != '/') {\n                gConfig.FstMetaLogDir += '/';\n              }\n            }\n          }\n        }\n\n        if (!strcmp(\"authdir\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument 2 for authdir missing\");\n            NoGo = 1;\n          } else {\n            if (strlen(val)) {\n              gConfig.FstAuthDir = val;\n\n              if (val[strlen(val) - 1] != '/') {\n                gConfig.FstAuthDir += '/';\n              }\n            }\n          }\n        }\n\n        if (!strcmp(\"protowfendpoint\", var)) {\n          if ((val = Config.GetWord())) {\n            gConfig.ProtoWFEndpoint = val;\n          }\n        }\n\n        if (!strcmp(\"protowfresource\", var)) {\n          if ((val = Config.GetWord())) {\n            gConfig.ProtoWFResource = val;\n          }\n        }\n\n        if (!strcmp(\"qdbcluster\", var)) {\n          std::string qdb_cluster;\n\n          while ((val = Config.GetWord())) {\n            qdb_cluster += val;\n            qdb_cluster += \" \";\n          }\n\n          Eroute.Say(\"=====> fstofs.qdbcluster : \", qdb_cluster.c_str());\n\n          if (!qdb_cluster.empty()) {\n            if (!mQdbContactDetails.members.parse(qdb_cluster)) {\n              Eroute.Emsg(\"Config\", \"failed to parse qdbcluster members\");\n              NoGo = 1;\n            }\n          }\n        }\n\n        if (!strcmp(\"qdbpassword\", var)) {\n          while ((val = Config.GetWord())) {\n            mQdbContactDetails.password += val;\n          }\n\n          // Trim whitespace at the end\n          common::PasswordHandler::rightTrimWhitespace(mQdbContactDetails.password);\n          std::string pwlen = std::to_string(mQdbContactDetails.password.size());\n          Eroute.Say(\"=====> fstofs.qdbpassword length : \", pwlen.c_str());\n        }\n\n        if (!strcmp(\"qdbpassword_file\", var)) {\n          std::string path;\n\n          while ((val = Config.GetWord())) {\n            path += val;\n          }\n\n          if (!common::PasswordHandler::readPasswordFile(path,\n              mQdbContactDetails.password)) {\n            Eroute.Emsg(\"Config\", \"failed to open path pointed to by qdbpassword_file\");\n            NoGo = 1;\n          }\n\n          std::string pwlen = std::to_string(mQdbContactDetails.password.size());\n          Eroute.Say(\"=====> fstofs.qdbpassword length : \", pwlen.c_str());\n        }\n      }\n    }\n\n    Config.Close();\n    close(cfgFD);\n  }\n\n  if (NoGo) {\n    return 1;\n  }\n\n  // Make sure we have a proper QuarkDB configuration present\n  if (mQdbContactDetails.empty()) {\n    Eroute.Say(\"=====> ERROR: No QuarkDB configuration - missing fstofs.qdbcluster!\");\n    return 1;\n  }\n\n  if (mQdbContactDetails.password.empty()) {\n    Eroute.Say(\"=====> ERROR: No QuarkDB password configuration - missing \"\n               \"fstofs.qdbpassword/fstofs.qdbpassword_file!\");\n    Eroute.Say(\"=====> ERROR: EOS will not connect to a QuarkDB instance \"\n               \"which is not protected by a password!\");\n    return 1;\n  }\n\n  if (gConfig.autoBoot) {\n    Eroute.Say(\"=====> fstofs.autoboot : true\");\n  } else {\n    Eroute.Say(\"=====> fstofs.autoboot : false\");\n  }\n\n  // Create the qclient shared by all file systems for doing the ns scan for\n  // the fsck consistency checks\n  if (!mQdbContactDetails.empty()) {\n    mQcl.reset(new qclient::QClient(mQdbContactDetails.members,\n                                    mQdbContactDetails.constructOptions()));\n  }\n\n  if (!gConfig.FstOfsBrokerUrl.endswith(\"/\")) {\n    gConfig.FstOfsBrokerUrl += \"/\";\n  }\n\n  if (!mFmdHandler) {\n    mFmdHandler.reset(new FmdAttrHandler(makeFSPathHandler(this)));\n    Eroute.Say(\"=====> fstofs.filemd_handler : attr\");\n  }\n\n  gConfig.FstDefaultReceiverQueue = gConfig.FstOfsBrokerUrl;\n  gConfig.FstOfsBrokerUrl += mHostName;\n  gConfig.FstOfsBrokerUrl += \":\";\n  gConfig.FstOfsBrokerUrl += myPort;\n  gConfig.FstOfsBrokerUrl += \"/fst\";\n  gConfig.FstHostPort = mHostName;\n  gConfig.FstHostPort += \":\";\n  gConfig.FstHostPort += myPort;\n  gConfig.KernelVersion = GetKernelRelease().c_str();\n  Eroute.Say(\"=====> fstofs.broker : \", gConfig.FstOfsBrokerUrl.c_str(), \"\");\n  // Extract our queue name\n  gConfig.FstQueue = gConfig.FstOfsBrokerUrl;\n  {\n    int pos1 = gConfig.FstQueue.find(\"//\");\n    int pos2 = gConfig.FstQueue.find(\"//\", pos1 + 2);\n\n    if (pos2 != STR_NPOS) {\n      gConfig.FstQueue.erase(0, pos2 + 1);\n    } else {\n      Eroute.Emsg(\"Config\", \"cannot determine my queue name: \",\n                  gConfig.FstQueue.c_str());\n      return 1;\n    }\n  }\n  // Create our wildcard broadcast name\n  gConfig.FstQueueWildcard = gConfig.FstQueue;\n  gConfig.FstQueueWildcard += \"/*\";\n  // Create our wildcard config broadcast name\n  gConfig.FstConfigQueueWildcard = \"*/\";\n  gConfig.FstConfigQueueWildcard += mHostName;\n  gConfig.FstConfigQueueWildcard += \":\";\n  gConfig.FstConfigQueueWildcard += myPort;\n  // Set logging parameters\n  XrdOucString unit = \"fst@\";\n  unit += mHostName;\n  unit += \":\";\n  unit += myPort;\n  // Setup the circular in-memory log buffer\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n  g_logging.SetLogPriority(LOG_INFO);\n  g_logging.SetUnit(unit.c_str());\n  // Get the XRootD log directory\n  char* logdir = 0;\n  XrdOucEnv::Import(\"XRDLOGDIR\", logdir);\n\n  if (logdir) {\n    eoscpTransferLog = logdir;\n    eoscpTransferLog += \"eoscp.log\";\n  }\n\n  Eroute.Say(\"=====> eoscp-log : \", eoscpTransferLog.c_str());\n  // Compute checksum of the keytab file\n  const std::string keytab_fn = \"/etc/eos.keytab\";\n  std::string keytab_xs = \"unaccessible\";\n\n  if (!eos::common::GetFileAdlerXs(keytab_xs, keytab_fn)) {\n    eos_static_crit(\"msg=\\\"failed keytab checksum computation\\\" fn=\\\"%s\\\"\",\n                    keytab_fn.c_str());\n    return 1;\n  }\n\n  gConfig.KeyTabAdler = keytab_xs.c_str();\n  // Create the messaging object(recv thread)\n  gConfig.FstDefaultReceiverQueue += \"*/mgm\";\n  int pos1 = gConfig.FstDefaultReceiverQueue.find(\"//\");\n  int pos2 = gConfig.FstDefaultReceiverQueue.find(\"//\",\n             pos1 + 2);\n\n  if (pos2 != STR_NPOS) {\n    gConfig.FstDefaultReceiverQueue.erase(0, pos2 + 1);\n  }\n\n  Eroute.Say(\"=====> fstofs.defaultreceiverqueue : \",\n             gConfig.FstDefaultReceiverQueue.c_str(), \"\");\n  {\n    // Setup auth dir\n    XrdOucString scmd = \"mkdir -p \";\n    scmd += gConfig.FstAuthDir;\n    scmd += \" ; chown -R daemon \";\n    scmd += gConfig.FstAuthDir;\n    scmd += \" ; chmod 700 \";\n    scmd += gConfig.FstAuthDir;\n    int src = system(scmd.c_str());\n\n    if (src) {\n      eos_err(\"%s returned %d\", scmd.c_str(), src);\n    }\n\n    if (access(gConfig.FstAuthDir.c_str(),\n               R_OK | W_OK | X_OK)) {\n      Eroute.Emsg(\"Config\", \"cannot access the auth directory for r/w: \",\n                  gConfig.FstAuthDir.c_str());\n      return 1;\n    }\n\n    Eroute.Say(\"=====> fstofs.authdir : \",\n               gConfig.FstAuthDir.c_str());\n  }\n  eos_static_notice(\"%s\", \"msg=\\\"running SharedManager via QDB i.e NO-MQ\\\"\");\n  qclient::SharedManager* qsm =\n    new qclient::SharedManager(mQdbContactDetails.members,\n                               mQdbContactDetails.constructSubscriptionOptions());\n  mMessagingRealm.reset(new mq::MessagingRealm(qsm));\n  // Attach Storage to the meta log dir\n  Storage = eos::fst::Storage::Create(\n              gConfig.FstMetaLogDir.c_str());\n  Eroute.Say(\"=====> fstofs.metalogdir : \",\n             gConfig.FstMetaLogDir.c_str());\n\n  if (!Storage) {\n    Eroute.Emsg(\"Config\", \"cannot setup meta data storage using directory: \",\n                gConfig.FstMetaLogDir.c_str());\n    return 1;\n  }\n\n  // Start the embedded HTTP server\n  mHttpdPort = 8001;\n\n  if (getenv(\"EOS_FST_HTTP_PORT\")) {\n    try {\n      mHttpdPort = std::stol(getenv(\"EOS_FST_HTTP_PORT\"));\n    } catch (...) {\n      // no change\n    }\n  }\n\n  mHttpd.reset(new eos::fst::HttpServer(mHttpdPort));\n\n  if (!mHttpd) {\n    eos_static_crit(\"%s\", \"msg=\\\"failed to allocate HttpServer object\\\"\");\n    NoGo = 1;\n  }\n\n  // Setup the concatenated CA file (done by the XRootD server)\n  if (getenv(\"XRDADMINPATH\")) {\n    std::string adminPath = getenv(\"XRDADMINPATH\");\n    ConcatenatedServerRootCA = adminPath + \".xrdtls/ca_file.pem\";\n    Eroute.Say(\"Concatenated CA file location: \",\n               ConcatenatedServerRootCA->c_str());\n  }\n\n  eos_notice(\"FST_HOST=%s FST_PORT=%ld FST_HTTP_PORT=%d VERSION=%s RELEASE=%s \"\n             \"KEYTABADLER=%s\", mHostName, myPort, mHttpdPort, VERSION, RELEASE,\n             keytab_xs.c_str());\n  return NoGo;\n}\n\n//------------------------------------------------------------------------------\n// Define error bool variables to en-/disable error simulation in the OFS layer\n//------------------------------------------------------------------------------\nvoid\nXrdFstOfs::SetSimulationError(const std::string& input)\n{\n  mSimFmdOpenErr = mSimOpenDelay = false;\n  mSimIoReadErr = mSimXsReadErr = mSimReadDelay = false;\n  mSimIoWriteErr =  mSimXsWriteErr = false;\n  mSimDiskWriting = mSimCloseErr = mSimUnresponsive = false;\n  mSimCloseCommitMgmErr = false;\n  mSimErrIoReadOff = mSimErrIoWriteOff = 0ull;\n  mSimReadDelaySec = 10;\n  mSimXsWriteErrDelay = 0;\n\n  if (input == \"xs_read\") {\n    mSimXsReadErr = true;\n  } else if (input == \"fmd_open\") {\n    mSimFmdOpenErr = true;\n  } else if (input == \"fake_write\") {\n    mSimDiskWriting = true;\n  } else if (input == \"close_commit_mgm\")  {\n    mSimCloseCommitMgmErr = true;\n  } else if (input == \"close\") {\n    mSimCloseErr = true;\n  } else if (input == \"unresponsive\") {\n    mSimUnresponsive = true;\n  } else if (input.find(\"open_delay\") == 0) {\n    mSimOpenDelay = true;\n\n    if (input.length() > std::strlen(\"open_delay\")) {\n      mSimOpenDelaySec = GetSimulationDelay(input);\n    }\n  } else if (input.find(\"read_delay\") == 0) {\n    mSimReadDelay = true;\n\n    if (input.length() > std::strlen(\"read_delay\")) {\n      mSimReadDelaySec = GetSimulationDelay(input);\n    }\n  } else if (input.find(\"io_read\") == 0) {\n    mSimIoReadErr = true;\n    mSimErrIoReadOff = GetSimulationErrorOffset(input);\n  } else if (input.find(\"io_write\") == 0) {\n    mSimIoWriteErr = true;\n    mSimErrIoWriteOff = GetSimulationErrorOffset(input);\n  } else if (input.find(\"xs_write\") == 0) {\n    mSimXsWriteErr = true;\n\n    if (input.length() > std::strlen(\"xs_write\")) {\n      mSimXsWriteErrDelay = GetSimulationDelay(input);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Stat path\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::stat(const char* path,\n                struct stat* buf,\n                XrdOucErrInfo& out_error,\n                const XrdSecEntity* client,\n                const char* opaque)\n{\n  EPNAME(\"stat\");\n  memset(buf, 0, sizeof(struct stat));\n  XrdOucString url = path;\n\n  if (url.beginswith(\"/#/\")) {\n    url.replace(\"/#/\", \"\");\n    XrdOucString url64;\n    eos::common::SymKey::DeBase64(url, url64);\n    fprintf(stderr, \"doing stat for %s\\n\", url64.c_str());\n    // use an IO object to stat this ...\n    std::unique_ptr<FileIo> io(eos::fst::FileIoPlugin::GetIoObject(url64.c_str()));\n\n    if (io) {\n      if (io->fileStat(buf)) {\n        return gOFS.Emsg(epname, out_error, errno, \"stat file\", url64.c_str());\n      } else {\n        return SFS_OK;\n      }\n    } else {\n      return gOFS.Emsg(epname, out_error, EINVAL,\n                       \"stat file - IO object not supported\", url64.c_str());\n    }\n  }\n\n  if (!XrdOfsOss->Stat(path, buf)) {\n    // we store the mtime.ns time in st_dev ... sigh@Xrootd ...\n#ifdef __APPLE__\n    unsigned long nsec = buf->st_mtimespec.tv_nsec;\n#else\n    unsigned long nsec = buf->st_mtim.tv_nsec;\n#endif\n    // mask for 10^9\n    nsec &= 0x7fffffff;\n    // enable bit 32 as indicator\n    nsec |= 0x80000000;\n    // overwrite st_dev\n    buf->st_dev = nsec;\n    return SFS_OK;\n  } else {\n    return gOFS.Emsg(epname, out_error, errno, \"stat file\", path);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Perform a filesystem extended attribute function\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::FAttr(XrdSfsFACtl* faReq,\n                 XrdOucErrInfo& error,\n                 const XrdSecEntity* client)\n{\n  static std::map<XrdSfsFACtl::RQST, Access_Operation> s_map {\n    {XrdSfsFACtl::RQST::faDel, AOP_Update},\n    {XrdSfsFACtl::RQST::faGet, AOP_Read},\n    {XrdSfsFACtl::RQST::faLst, AOP_Read},\n    {XrdSfsFACtl::RQST::faSet, AOP_Update}\n  };\n\n  // Check if we only need to return support information\n  if (!faReq) {\n    eos_static_info(\"%s\", \"msg=\\\"fattr support info request\\\"\");\n    XrdOucEnv* env = error.getEnv();\n\n    if (!env) {\n      error.setErrInfo(ENOTSUP, \"Not supported\");\n      return SFS_ERROR;\n    }\n\n    env->PutInt(\"usxMaxNsz\", kXR_faMaxNlen);\n    env->PutInt(\"usxMaxVsz\", kXR_faMaxVlen);\n    return SFS_OK;\n  }\n\n  const char* tident = error.getErrUser();\n  const char* inpath = (faReq->path ? faReq->path : \"\");\n  const char* ininfo = (faReq->pcgi ? faReq->pcgi : \"\");\n  eos_static_info(\"msg=\\\"fattr request redirect to MGM\\\" \"\n                  \"path=\\\"%s\\\" tident=\\\"%s\\\",info=\\\"%s\\\"\",\n                  (inpath ? inpath : \"\"), (tident ? tident : \"\"),\n                  (ininfo ? ininfo : \"\"));\n  int ecode = 1094;\n  XrdOucString rdr_mgr;\n  {\n    XrdSysMutexHelper lock(gConfig.Mutex);\n    rdr_mgr = gConfig.Manager;\n  }\n  int pos = rdr_mgr.find(\":\");\n\n  if (pos != STR_NPOS) {\n    rdr_mgr.erase(pos);\n  }\n\n  return Redirect(error, rdr_mgr.c_str(), ecode);\n}\n\n//------------------------------------------------------------------------------\n// Callback MGM - XrdOucString version\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::CallManager(XrdOucErrInfo* error, const char* path,\n                       const char* manager, const std::string& opaque,\n                       unsigned short timeout,\n                       bool use_xrd_conn_pool, bool retry)\n{\n  XrdOucString ouc_opaque = opaque.c_str();\n  int rc = CallManager(error, path, manager, ouc_opaque, timeout,\n                       use_xrd_conn_pool, retry);\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Callback MGM - XrdOucString version\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::CallManager(XrdOucErrInfo* error, const char* path,\n                       const char* manager, XrdOucString& capOpaqueFile,\n                       unsigned short timeout,\n                       bool use_xrd_conn_pool, bool retry)\n{\n  EPNAME(\"CallManager\");\n  int rc = SFS_OK;\n  XrdOucString msg = \"\";\n  XrdCl::Buffer arg;\n  XrdCl::XRootDStatus status;\n  XrdOucString address = \"root://\";\n  XrdOucString lManager;\n  size_t tried = 0;\n\n  if (!manager) {\n    // Use broadcast manager name\n    XrdSysMutexHelper lock(gConfig.Mutex);\n    lManager = gConfig.Manager.c_str();\n    address += lManager.c_str();\n  } else {\n    address += manager;\n  }\n\n  address += \"//dummy?xrd.wantprot=sss\";\n  XrdCl::URL url(address.c_str());\n\n  if (!url.IsValid()) {\n    eos_err(\"error=URL is not valid: %s\", address.c_str());\n    return EINVAL;\n  }\n\n  // Use xrd connection pool if is requested by the caller and this is\n  // allowed globally.\n  std::unique_ptr<eos::common::XrdConnIdHelper> conn_helper;\n\n  if (use_xrd_conn_pool) {\n    if (getenv(\"EOS_FST_CALL_MANAGER_XRD_POOL\")) {\n      conn_helper.reset(new eos::common::XrdConnIdHelper(*mMgmXrdPool, url));\n\n      if (conn_helper->HasNewConnection()) {\n        eos_info(\"msg=\\\"using url=%s\\\"\", url.GetURL().c_str());\n      }\n    }\n  }\n\n  // Request sss authentication on the MGM side\n  std::string opaque = capOpaqueFile.c_str();\n  // Get XrdCl::FileSystem object\n  // !!! WATCH OUT: GOTO ANCHOR !!!\n  std::unique_ptr<XrdCl::FileSystem> fs;\n  std::unique_ptr<XrdCl::Buffer> response;\n  XrdCl::Buffer* responseRaw = nullptr;\nagain:\n  fs.reset(new XrdCl::FileSystem(url));\n\n  if (!fs) {\n    eos_err(\"error=failed to get new FS object\");\n\n    if (error) {\n      gOFS.Emsg(epname, *error, ENOMEM,\n                \"allocate FS object calling the manager node for fn=\", path);\n    }\n\n    return EINVAL;\n  }\n\n  arg.FromString(opaque);\n  status = fs->Query(XrdCl::QueryCode::OpaqueFile, arg, responseRaw, timeout);\n  response.reset(responseRaw);\n  responseRaw = nullptr;\n\n  if (status.IsOK()) {\n    eos_static_debug(\"msg=\\\"MGM query succeeded\\\" opaque=\\\"%s\\\"\", opaque.c_str());\n    rc = SFS_OK;\n  } else {\n    eos_static_err(\"msg=\\\"MGM query failed\\\" opaque=\\\"%s\\\"\", opaque.c_str());\n    msg = (status.GetErrorMessage().c_str());\n    rc = SFS_ERROR;\n\n    if (msg.find(\"[EIDRM]\") != STR_NPOS) {\n      rc = EIDRM;\n    }\n\n    if (msg.find(\"[EBADE]\") != STR_NPOS) {\n      rc = EBADE;\n    }\n\n    if (msg.find(\"[EBADR]\") != STR_NPOS) {\n      rc = EBADR;\n    }\n\n    if (msg.find(\"[EINVAL]\") != STR_NPOS) {\n      rc = EINVAL;\n    }\n\n    if (msg.find(\"[EADV]\") != STR_NPOS) {\n      rc = EADV;\n    }\n\n    if (msg.find(\"[EAGAIN]\") != STR_NPOS) {\n      rc = EAGAIN;\n    }\n\n    if (msg.find(\"[ENOTCONN]\") != STR_NPOS) {\n      rc = ENOTCONN;\n    }\n\n    if (msg.find(\"[EPROTO]\") != STR_NPOS) {\n      rc = EPROTO;\n    }\n\n    if (msg.find(\"[EREMCHG]\") != STR_NPOS) {\n      rc = EREMCHG;\n    }\n\n    if (rc != SFS_ERROR) {\n      return gOFS.Emsg(epname, *error, rc, msg.c_str(), path);\n    } else {\n      eos_static_err(\"msg=\\\"query error\\\" status=%d code=%d\", status.status,\n                     status.code);\n\n      if (retry && (status.code >= 100) && (status.code <= 300) && (!timeout)) {\n        // implement automatic retry - network errors will be cured at some point\n        std::this_thread::sleep_for(std::chrono::seconds(1));\n        tried++;\n        eos_static_info(\"msg=\\\"retry query\\\" query=\\\"%s\\\"\", opaque.c_str());\n\n        if (!manager || (tried > 60)) {\n          // use the broadcasted manager name in the repeated try\n          XrdSysMutexHelper lock(gConfig.Mutex);\n          lManager = gConfig.Manager.c_str();\n          address = \"root://\";\n          address += lManager.c_str();\n          address += \"//dummy\";\n          url.Clear();\n          url.FromString((address.c_str()));\n        }\n\n        goto again;\n      }\n\n      return gOFS.Emsg(epname, *error, ECOMM, msg.c_str(), path);\n    }\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Remove entry - interface function\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::rem(const char* path,\n               XrdOucErrInfo& error,\n               const XrdSecEntity* client,\n               const char* opaque)\n{\n  EPNAME(\"rem\");\n  XrdOucString stringOpaque = opaque;\n  stringOpaque.replace(\"?\", \"&\");\n  stringOpaque.replace(\"&&\", \"&\");\n  XrdOucEnv openOpaque(stringOpaque.c_str());\n  XrdOucEnv* capOpaque = 0;\n  int caprc = 0;\n\n  if ((caprc = eos::common::SymKey::ExtractCapability(&openOpaque, capOpaque))) {\n    // No capability - go away!\n    if (capOpaque) {\n      delete capOpaque;\n      capOpaque = 0;\n    }\n\n    return gOFS.Emsg(epname, error, caprc, \"remove - capability illegal\", path);\n  }\n\n  int envlen;\n\n  if (capOpaque) {\n    eos_info(\"path=%s info=%s capability=%s\", path, opaque,\n             capOpaque->Env(envlen));\n  } else {\n    eos_info(\"path=%s info=%s\", path, opaque);\n    return gOFS.Emsg(epname, error, caprc, \"remove - empty capability\", path);\n  }\n\n  int rc = _rem(path, error, client, capOpaque);\n\n  if (capOpaque) {\n    delete capOpaque;\n    capOpaque = 0;\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Remove entry - low level function\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::_rem(const char* path, XrdOucErrInfo& error,\n                const XrdSecEntity* client, XrdOucEnv* capOpaque,\n                const char* fstpath, unsigned long long fid,\n                unsigned long fsid, bool ignoreifnotexist,\n                std::string* deletion_report)\n{\n  EPNAME(\"rem\");\n  std::string fstPath = \"\";\n  const char* hexfid = 0;\n  const char* sfsid = 0;\n\n  if ((!fstpath) && (!fsid) && (!fid)) {\n    // Standard deletion brings all information via the opaque info\n    if (!(hexfid = capOpaque->Get(\"mgm.fid\"))) {\n      return gOFS.Emsg(epname, error, EINVAL,\n                       \"remove - no file id in capability\", path);\n    }\n\n    if (!(sfsid = capOpaque->Get(\"mgm.fsid\"))) {\n      return gOFS.Emsg(epname, error, EINVAL,\n                       \"remove - no file system id in capability\", path);\n    }\n\n    try {\n      fsid = std::stoull(sfsid);\n    } catch (...) {\n      return gOFS.Emsg(epname, error, EINVAL,\n                       \"remove - file system id not numeric\", path);\n    }\n\n    const std::string local_prefix = Storage->GetStoragePath(fsid);\n    fstPath = eos::common::FileId::FidPrefix2FullPath(hexfid,\n              local_prefix.c_str());\n    fid = eos::common::FileId::Hex2Fid(hexfid);\n  } else {\n    // Deletion during close provides the local storage path, fid & fsid\n    fstPath = fstpath;\n  }\n\n  int rc = 0;\n  errno = 0; // If file not found this will be ENOENT\n  struct stat sbd;\n  sbd.st_size = 0;\n\n  // Unlink file and possible blockxs file - for local files we need to go\n  // through XrdOfs::rem to also clean up any potential blockxs files\n  if (eos::common::LayoutId::GetIoType(fstPath.c_str()) ==\n      eos::common::LayoutId::kLocal) {\n    // Get the size before deletion for the report\n    (void) XrdOfs::stat(fstPath.c_str(), &sbd, error, client, 0);\n    rc = XrdOfs::rem(fstPath.c_str(), error, client, 0);\n\n    if (rc) {\n      eos_info(\"rc=%i, errno=%i\", rc, errno);\n    }\n  } else {\n    // Check for additional opaque info to create remote IO object\n    std::string sFstPath = fstPath.c_str();\n    std::string s3credentials = gOFS.Storage->GetFileSystemConfig(fsid,\n                                \"s3credentials\");\n\n    if (!s3credentials.empty()) {\n      sFstPath += \"?s3credentials=\" + s3credentials;\n    }\n\n    std::unique_ptr<FileIo> io(eos::fst::FileIoPlugin::GetIoObject(\n                                 sFstPath.c_str()));\n\n    if (!io) {\n      return gOFS.Emsg(epname, error, EINVAL, \"open - no IO plug-in avaialble\",\n                       sFstPath.c_str());\n    }\n\n    // get the size before deletion\n    io->fileStat(&sbd);\n    rc = io->fileRemove();\n  }\n\n  if (rc) {\n    if (errno == ENOENT) {\n      // Ignore error if a file to be deleted doesn't exist\n      if (ignoreifnotexist) {\n        rc = 0;\n      } else {\n        eos_notice(\"msg=\\\"file already deleted\\\" path=\\%s\\\" fst_path=\\\"%s\\\" \"\n                   \"fsid=%lu fid=%\", path, fstPath.c_str(), fsid, fid);\n      }\n    }\n  }\n\n  if (rc) {\n    return gOFS.Emsg(epname, error, errno, \"delete file\", fstPath.c_str());\n  } else {\n    // make a deletion report entry\n    if (deletion_report) {\n      // just return the report to the caller e.g. storage::Remover\n      *deletion_report = MakeDeletionReport(fsid, fid, sbd, false);\n    } else {\n      // send the report via MQ\n      MakeDeletionReport(fsid, fid, sbd, true);\n    }\n  }\n\n  mFmdHandler->LocalDeleteFmd(fid, fsid);\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Query file system information\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::fsctl(const int cmd, const char* args, XrdOucErrInfo& error,\n                 const XrdSecEntity* client)\n{\n  static const char* epname = \"fsctl\";\n  const char* tident = error.getErrUser();\n\n  if ((cmd == SFS_FSCTL_LOCATE)) {\n    char locResp[4096];\n    char rType[3], *Resp[] = {rType, locResp};\n    rType[0] = 'S';\n    rType[1] = 'r'; //(fstat.st_mode & S_IWUSR            ? 'w' : 'r');\n    rType[2] = '\\0';\n    sprintf(locResp, \"[::%s:%d] \", mHostName, myPort);\n    error.setErrInfo(strlen(locResp) + 3, (const char**) Resp, 2);\n    ZTRACE(fsctl, \"located at headnode: \" << locResp);\n    return SFS_DATA;\n  }\n\n  std::string scmd = SSTR(\"cmd=\" << cmd << \" args=\\\"\" << args << \"\\\"\").c_str();\n  return gOFS.Emsg(epname, error, EPERM, \"execute fsctl function\", scmd.c_str());\n}\n\n//------------------------------------------------------------------------------\n// Function dealing with plugin calls\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::FSctl(const int cmd, XrdSfsFSctl& args, XrdOucErrInfo& error,\n                 const XrdSecEntity* client)\n{\n  using eos::common::FileId;\n  char ipath[16384];\n  char iopaque[16384];\n  static const char* epname = \"FSctl\";\n  const char* tident = error.getErrUser();\n\n  if ((cmd == SFS_FSCTL_LOCATE)) {\n    char locResp[4096];\n    char rType[3], *Resp[] = {rType, locResp};\n    rType[0] = 'S';\n    rType[1] = 'r'; //(fstat.st_mode & S_IWUSR ? 'w' : 'r');\n    rType[2] = '\\0';\n    sprintf(locResp, \"[::%s:%d] \", mHostName, myPort);\n    error.setErrInfo(strlen(locResp) + 3, (const char**) Resp, 2);\n    ZTRACE(fsctl, \"located at headnode: \" << locResp);\n    return SFS_DATA;\n  }\n\n  // Accept only plugin calls!\n  if (cmd != SFS_FSCTL_PLUGIN) {\n    return gOFS.Emsg(epname, error, EPERM, \"execute non-plugin function\", \"\");\n  }\n\n  if (args.Arg1Len) {\n    if (args.Arg1Len < 16384) {\n      strncpy(ipath, args.Arg1, args.Arg1Len);\n      ipath[args.Arg1Len] = 0;\n    } else {\n      return gOFS.Emsg(epname, error, EINVAL,\n                       \"convert path argument - string too long\", \"\");\n    }\n  } else {\n    ipath[0] = 0;\n  }\n\n  if (args.Arg2Len) {\n    if (args.Arg2Len < 16384) {\n      strncpy(iopaque, args.Arg2, args.Arg2Len);\n      iopaque[args.Arg2Len] = 0;\n    } else {\n      return gOFS.Emsg(epname, error, EINVAL,\n                       \"convert opaque argument - string too long\", \"\");\n    }\n  } else {\n    iopaque[0] = 0;\n  }\n\n  // From here on we can deal with XrdOucString which is more 'comfortable'\n  XrdOucString path = ipath;\n  XrdOucString opaque = iopaque;\n  XrdOucString result = \"\";\n  XrdOucEnv env(opaque.c_str());\n  eos_static_debug(\"msg=\\\"handle query\\\" tident=%s path=\\\"%s\\\" opaque=\\\"%s\\\" \"\n                   \"prot=\\\"%s\\\"\", tident, path.c_str(), opaque.c_str(), client->prot);\n  const char* scmd {nullptr};\n\n  if ((scmd = env.Get(\"fst.pcmd\"))) {\n    XrdOucString execmd = scmd;\n\n    if (execmd == \"debug\") {\n      return HandleDebug(env, error);\n    }\n\n    if (execmd == \"resync\") {\n      return HandleResync(env, error);\n    }\n\n    if (execmd == \"rtlog\") {\n      return HandleRtlog(env, error);\n    }\n\n    if (execmd == \"verify\") {\n      return HandleVerify(env, error);\n    }\n\n    if (execmd == \"drop\") {\n      return HandleDropFile(env, error);\n    }\n\n    if (execmd == \"clean_orphans\") {\n      return HandleCleanOrphans(env, error);\n    }\n\n    if (execmd == \"getfmd\") {\n      char* afid = env.Get(\"fst.getfmd.fid\");\n      char* afsid = env.Get(\"fst.getfmd.fsid\");\n\n      if ((!afid) || (!afsid)) {\n        return Emsg(epname, error, EINVAL, \"execute FSctl command\", path.c_str());\n      }\n\n      unsigned long long fileid = eos::common::FileId::Hex2Fid(afid);\n      unsigned long fsid = atoi(afsid);\n      auto fmd = mFmdHandler->LocalGetFmd(fileid, fsid, true);\n\n      if (!fmd) {\n        eos_static_err(\"msg=\\\"no FMD record found\\\" fxid=%08llx fsid=%lu\", fileid,\n                       fsid);\n        const char* err = \"ERROR\";\n        error.setErrInfo(strlen(err) + 1, err);\n        return SFS_DATA;\n      }\n\n      auto fmdenv = fmd->FmdToEnv();\n      int envlen;\n      XrdOucString fmdenvstring = fmdenv->Env(envlen);\n      error.setErrInfo(fmdenvstring.length() + 1, fmdenvstring.c_str());\n      return SFS_DATA;\n    }\n\n    if (execmd == \"getxattr\") {\n      char* key = env.Get(\"fst.getxattr.key\");\n      char* path = env.Get(\"fst.getxattr.path\");\n\n      if (!key) {\n        eos_static_err(\"no key specified as attribute name\");\n        const char* err = \"ERROR\";\n        error.setErrInfo(strlen(err) + 1, err);\n        return SFS_DATA;\n      }\n\n      if (!path) {\n        eos_static_err(\"no path specified to get the attribute from\");\n        const char* err = \"ERROR\";\n        error.setErrInfo(strlen(err) + 1, err);\n        return SFS_DATA;\n      }\n\n      char value[1024];\n#ifdef __APPLE__\n      ssize_t attr_length = getxattr(path, key, value, sizeof(value), 0, 0);\n#else\n      ssize_t attr_length = getxattr(path, key, value, sizeof(value));\n#endif\n\n      if (attr_length > 0) {\n        value[1023] = 0;\n        XrdOucString skey = key;\n        XrdOucString attr = \"\";\n\n        if (skey == \"user.eos.checksum\") {\n          // Checksum's are binary and need special reformatting (we swap the\n          // byte order if they are 4 bytes long )\n          if (attr_length == 4) {\n            for (ssize_t k = 0; k < 4; k++) {\n              char hex[4];\n              snprintf(hex, sizeof(hex) - 1, \"%02x\", (unsigned char) value[3 - k]);\n              attr += hex;\n            }\n          } else {\n            for (ssize_t k = 0; k < attr_length; k++) {\n              char hex[4];\n              snprintf(hex, sizeof(hex) - 1, \"%02x\", (unsigned char) value[k]);\n              attr += hex;\n            }\n          }\n        } else {\n          attr = value;\n        }\n\n        error.setErrInfo(attr.length() + 1, attr.c_str());\n        return SFS_DATA;\n      } else {\n        eos_static_err(\"getxattr failed for path=%s\", path);\n        const char* err = \"ERROR\";\n        error.setErrInfo(strlen(err) + 1, err);\n        return SFS_DATA;\n      }\n    }\n\n    if (execmd == \"local_rename\") {\n      if (strncmp(client->prot, \"sss\", 3) != 0) {\n        eos_static_err(\"%s\", \"msg=\\\"only sss authenticated clients can trigger\"\n                       \" a local rename\");\n        return gOFS.Emsg(epname, error, EPERM, \"do local rename\",\n                         \"- needs sss authentication\");\n      }\n\n      std::string sfsid = (env.Get(\"fst.rename.fsid\") ?\n                           env.Get(\"fst.rename.fsid\") : \"\");\n      std::string sold_fid = (env.Get(\"fst.rename.ofid\") ?\n                              env.Get(\"fst.rename.ofid\") : \"\");\n      std::string snew_fid = (env.Get(\"fst.rename.nfid\") ?\n                              env.Get(\"fst.rename.nfid\") : \"\");\n      std::string ns_path = (env.Get(\"fst.nspath\") ?\n                             env.Get(\"fst.nspath\") : \"\");\n      unsigned long fsid {0ul};\n      eos::IFileMD::id_t old_fid {0ull};\n      eos::IFileMD::id_t new_fid {0ull};\n\n      try {\n        fsid = std::stoul(sfsid);\n        // File identifier are in hex!\n        old_fid = std::stoull(sold_fid, 0, 16);\n        new_fid = std::stoull(snew_fid, 0, 16);\n      } catch (...) {}\n\n      if (!fsid || !old_fid || !new_fid) {\n        eos_static_err(\"msg=\\\"failed local rename, unexpected input \\\" \"\n                       \"fsid=\\\"%s\\\" old_fid=\\\"%s\\\" new_fid=\\\"%s\\\"\",\n                       sfsid.c_str(), sold_fid.c_str(), snew_fid.c_str());\n        return gOFS.Emsg(epname, error, EINVAL, \"do local rename\", \"\");\n      }\n\n      // Get the local mount point for the given file system id\n      std::string fs_prefix;\n      {\n        eos::common::RWMutexReadLock lock(gOFS.Storage->mFsMutex);\n\n        if (fsid && gOFS.Storage->mFsMap.count(fsid)) {\n          fs_prefix = gOFS.Storage->mFsMap[fsid]->GetPath().c_str();\n        }\n      }\n\n      if (fs_prefix.empty()) {\n        eos_static_err(\"msg=\\\"failed to get local prefix for file system\\\" \"\n                       \"fsid=%08llx\", fsid);\n        return gOFS.Emsg(epname, error, EINVAL, \"do local rename\", \"\");\n      }\n\n      std::string old_path =\n        FileId::FidPrefix2FullPath(FileId::Fid2Hex(old_fid).c_str(),\n                                   fs_prefix.c_str());\n      std::string new_path =\n        FileId::FidPrefix2FullPath(FileId::Fid2Hex(new_fid).c_str(),\n                                   fs_prefix.c_str());\n      // Check that new path doesn't exist already\n      struct stat info;\n\n      if (FileIo::fsStat(new_path.c_str(), info) == 0) {\n        eos_static_err(\"msg=\\\"new path already exists on filesystem\\\" \"\n                       \"fsid=%08llx new_path=%s\", fsid, new_path.c_str());\n        return gOFS.Emsg(epname, error, EEXIST, \"do local rename\", \"\");\n      }\n\n      // Make sure the directory component of the new location exists\n      mode_t mode = 0755;\n      std::string dirs {new_path};\n      size_t pos = dirs.rfind('/');\n\n      if (pos != std::string::npos) {\n        dirs.erase(dirs.rfind('/'));\n      }\n\n      if (!CreateDirHierarchy(dirs, mode)) {\n        eos_static_err(\"msg=\\\"failed creating directory hierarchy\\\" \"\n                       \"fsid=%08llx new_path=%s\", fsid, new_path.c_str());\n        return gOFS.Emsg(epname, error, EEXIST, \"do local rename\", \"\");\n      }\n\n      if (FileIo::fsRename(old_path, new_path) != 0) {\n        eos_static_err(\"msg=\\\"rename failed\\\" old_path=%s new_path=%s errno=%d\",\n                       old_path.c_str(), new_path.c_str(), errno);\n        return gOFS.Emsg(epname, error, EEXIST, \"do local rename\", \"\");\n      } else {\n        // Update the filemd info to point to the original fille identifier\n        if (!mFmdHandler->UpdateFmd(new_path, new_fid)) {\n          eos_static_err(\"msg=\\\"failed to update fid for the fmd object\\\" \"\n                         \"path=%s new_fxid=%08llx\", new_path.c_str(), new_fid);\n          // Clean up the file on disk\n          (void) unlink(new_path.c_str());\n          return gOFS.Emsg(epname, error, EEXIST, \"do local rename\", \"\");\n        }\n\n        if (!ns_path.empty()) {\n          // Update the user.eos.lfn attribute to point to the original\n          // namespace file name\n          if (setxattr(new_path.c_str(), \"user.eos.lfn\", ns_path.c_str(),\n                       ns_path.length(), 0)) {\n            eos_static_warning(\"msg=\\\"failed to update the user.eos.lfn xattr\\\"\"\n                               \" local_path=\\\"%s\\\" ns_path=\\\"%s\\\"\",\n                               new_path.c_str(), ns_path.c_str());\n          }\n\n          // Rename any potential block xs files\n          std::string old_xs_path = old_path + \".xsmap\";\n          std::string new_xs_path = new_path + \".xsmap\";\n\n          if (::stat(old_xs_path.c_str(), &info) == 0) {\n            if (::rename(old_xs_path.c_str(), new_xs_path.c_str())) {\n              eos_static_err(\"msg=\\\"block xs file rename failed\\\" \"\n                             \"old_xs_path=%s new_xs_path=%s errno=%d\",\n                             old_xs_path.c_str(), new_xs_path.c_str(), errno);\n              return gOFS.Emsg(epname, error, EEXIST, \"do local rename\", \"\");\n            }\n          }\n        }\n      }\n\n      const char* done = \"OK\";\n      error.setErrInfo(strlen(done) + 1, done);\n      return SFS_DATA;\n    }\n  }\n\n  return Emsg(epname, error, EINVAL, \"execute FSctl command\", path.c_str());\n}\n\n//------------------------------------------------------------------------------\n// Stall message for the client\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::Stall(XrdOucErrInfo& error,  // Error text & code\n                 int stime, // Seconds to stall\n                 const char* msg) // Message to give\n{\n  XrdOucString smessage = msg;\n  smessage += \"; come back in \";\n  smessage += stime;\n  smessage += \" seconds!\";\n  EPNAME(\"Stall\");\n  const char* tident = error.getErrUser();\n  ZTRACE(delay, \"Stall \" << stime << \": \" << smessage.c_str());\n  // Place the error message in the error object and return\n  error.setErrInfo(0, smessage.c_str());\n  // All done\n  return stime;\n}\n\n//------------------------------------------------------------------------------\n// Redirect message for the client\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::Redirect(XrdOucErrInfo& error,  // Error text & code\n                    const char* host,\n                    int& port)\n{\n  EPNAME(\"Redirect\");\n  const char* tident = error.getErrUser();\n  ZTRACE(delay, \"Redirect \" << host << \":\" << port);\n  // Place the error message in the error object and return\n  error.setErrInfo(port, host);\n  // All done\n  return SFS_REDIRECT;\n}\n\n//------------------------------------------------------------------------------\n// When getting queried for checksum at the diskserver redirect to the MGM\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::chksum(XrdSfsFileSystem::csFunc Func, const char* csName,\n                  const char* inpath, XrdOucErrInfo& error,\n                  const XrdSecEntity* client, const char* ininfo)\n{\n  int ecode = 1094;\n  XrdOucString RedirectManager;\n  {\n    XrdSysMutexHelper lock(gConfig.Mutex);\n    RedirectManager = gConfig.Manager;\n  }\n  int pos = RedirectManager.find(\":\");\n\n  if (pos != STR_NPOS) {\n    RedirectManager.erase(pos);\n  }\n\n  return gOFS.Redirect(error, RedirectManager.c_str(), ecode);\n}\n\n//------------------------------------------------------------------------------\n// Wait for ongoing IO operations to finish\n//------------------------------------------------------------------------------\nbool\nXrdFstOfs::WaitForOngoingIO(std::chrono::seconds timeout)\n{\n  bool all_done = true;\n  std::chrono::seconds check_interval(5);\n  auto deadline = std::chrono::steady_clock::now() + timeout;\n\n  while (std::chrono::steady_clock::now() <= deadline) {\n    all_done = true;\n    {\n      XrdSysMutexHelper scope_lock(OpenFidMutex);\n      all_done = ! openedForWriting.isAnyOpen();\n\n      if (all_done) {\n        all_done = ! openedForReading.isAnyOpen();\n      }\n\n      if (all_done) {\n        break;\n      }\n    }\n    std::this_thread::sleep_for(check_interval);\n  }\n\n  return all_done;\n}\n\n//------------------------------------------------------------------------------\n// Report file deletion\n//------------------------------------------------------------------------------\nstd::string\nXrdFstOfs::MakeDeletionReport(eos::common::FileSystem::fsid_t fsid,\n                              unsigned long long fid,\n                              struct stat& deletion_stat,\n                              bool viamq)\n{\n  struct timespec ts_now;\n  char report[16384];\n  eos::common::Timing::GetTimeSpec(ts_now);\n  snprintf(report, sizeof(report) - 1,\n           \"log=%s&\"\n           \"host=%s&fid=%llu&fxid=%08llx&fsid=%u&\"\n           \"del_ts=%lu&del_tns=%lu&\"\n           \"dc_ts=%lu&dc_tns=%lu&\"\n           \"dm_ts=%lu&dm_tns=%lu&\"\n           \"da_ts=%lu&da_tns=%lu&\"\n           \"dsize=%li&sec.app=deletion\"\n           , this->logId, gOFS.mHostName, fid, fid, fsid\n           , ts_now.tv_sec, ts_now.tv_nsec\n#ifdef __APPLE__\n           , deletion_stat.st_ctimespec.tv_sec\n           , deletion_stat.st_ctimespec.tv_nsec\n           , deletion_stat.st_mtimespec.tv_sec\n           , deletion_stat.st_mtimespec.tv_nsec\n           , deletion_stat.st_atimespec.tv_sec\n           , deletion_stat.st_atimespec.tv_nsec\n#else\n           , deletion_stat.st_ctim.tv_sec\n           , deletion_stat.st_ctim.tv_nsec\n           , deletion_stat.st_mtim.tv_sec\n           , deletion_stat.st_mtim.tv_nsec\n           , deletion_stat.st_atim.tv_sec\n           , deletion_stat.st_atim.tv_nsec\n#endif\n           , deletion_stat.st_size);\n\n  if (viamq) {\n    XrdOucString reportString = report;\n    gOFS.ReportQueueMutex.Lock();\n    gOFS.ReportQueue.push(reportString);\n    gOFS.ReportQueueMutex.UnLock();\n  }\n\n  return report;\n}\n\n//----------------------------------------------------------------------------\n// Update the TPC key min/max validity values, default [2, 15] min\n//----------------------------------------------------------------------------\nvoid\nXrdFstOfs::UpdateTpcKeyValidity()\n{\n  const char* ptr = getenv(\"EOS_FST_TPC_KEY_MIN_VALIDITY_SEC\");\n\n  if (ptr && strlen(ptr)) {\n    std::string_view str(ptr);\n    unsigned int min_validity = 0ul;\n\n    if (eos::common::StringToNumeric(str, min_validity)) {\n      if (min_validity < 60) {\n        min_validity = 60;\n      }\n\n      if (min_validity > 3600) {\n        min_validity = 3600;\n      }\n\n      mTpcKeyMinValidity = std::chrono::seconds(min_validity);\n      fprintf(stderr, \"=====> Update TPC key min validity to %li seconds\\n\",\n              mTpcKeyMinValidity.count());\n    }\n  }\n\n  ptr = getenv(\"EOS_FST_TPC_KEY_MAX_VALIDITY_SEC\");\n\n  if (ptr && strlen(ptr)) {\n    std::string_view str(ptr);\n    unsigned int max_validity = 0ul;\n\n    if (eos::common::StringToNumeric(str, max_validity)) {\n      if (max_validity < mTpcKeyMinValidity.count()) {\n        max_validity = mTpcKeyMinValidity.count();\n      }\n\n      if (max_validity > std::chrono::seconds(3600).count()) {\n        max_validity = 3600;\n      }\n\n      mTpcKeyMaxValidity = std::chrono::seconds(max_validity);\n      fprintf(stderr, \"=====> Update TPC key max validity to %li seconds\\n\",\n              mTpcKeyMaxValidity.count());\n    }\n  }\n\n  if (mTpcKeyMaxValidity.count() < mTpcKeyMinValidity.count()) {\n    mTpcKeyMaxValidity = mTpcKeyMinValidity;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Create directory hierarchy\n//------------------------------------------------------------------------------\nbool\nXrdFstOfs::CreateDirHierarchy(std::string dir_hierarchy,\n                              mode_t mode) const\n{\n  struct stat info;\n\n  if (dir_hierarchy.find(\"nfs:/\") == 0) {\n    dir_hierarchy.erase(0, 5);\n  }\n\n  std::string path = \"/\";\n  auto lst_dirs = eos::common::StringTokenizer::split<std::list<std::string>>\n                  (dir_hierarchy, '/');\n\n  for (const auto& dir : lst_dirs) {\n    path += dir;\n    path += \"/\";\n\n    if (::stat(path.c_str(), &info) == 0) {\n      continue;\n    }\n\n    if (::mkdir(path.c_str(), mode) != 0) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Handle debug query\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::HandleDebug(XrdOucEnv& env, XrdOucErrInfo& err_obj)\n{\n  std::string dbg_level = (env.Get(\"fst.debug.level\") ?\n                           env.Get(\"fst.debug.level\") : \"\");\n  std::string dbg_filter = (env.Get(\"fst.debug.filter\") ?\n                            env.Get(\"fst.debug.filter\") : \"\");\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n  int dbg_val = g_logging.GetPriorityByString(dbg_level.c_str());\n\n  if (dbg_val < 0) {\n    std::string msg = SSTR(\"unknown debug level <\" << dbg_level << \">\");\n    eos_err(\"msg=\\\"%s\\\"\", msg.c_str());\n    err_obj.setErrInfo(EINVAL, msg.c_str());\n    return SFS_ERROR;\n  }\n\n  g_logging.SetLogPriority(dbg_val);\n  eos_notice(\"msg=\\\"setting debug level to <%s>\\\"\", dbg_level.c_str());\n\n  if (dbg_filter.length()) {\n    g_logging.SetFilter(dbg_filter.c_str());\n    eos_notice(\"setting message logid filter to <%s>\", dbg_filter.c_str());\n  }\n\n  // @todo(esindril) once xrootd bug regarding handling of SFS_OK response\n  // in XrdXrootdXeq is fixed we can just return SFS_OK (>= XRootD 5)\n  // return SFS_OK;\n  const char* done = \"OK\";\n  err_obj.setErrInfo(strlen(done) + 1, done);\n  return SFS_DATA;\n}\n\n//------------------------------------------------------------------------------\n// Handle resync query\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::HandleResync(XrdOucEnv& env, XrdOucErrInfo& err_obj)\n{\n  using eos::common::FileId;\n\n  if ((env.Get(\"fst.resync.fsid\") == nullptr) ||\n      (env.Get(\"fst.resync.fxid\") == nullptr) ||\n      (env.Get(\"fst.resync.force\") == nullptr)) {\n    eos_static_err(\"%s\", \"msg=\\\"discard resync with missing arguments\\\"\");\n    err_obj.setErrInfo(EINVAL, \"resync missing arguments\");\n    return SFS_ERROR;\n  }\n\n  bool force {false};\n  FileId::fileid_t fid = FileId::Hex2Fid(env.Get(\"fst.resync.fxid\"));\n  FileSystem::fsid_t fsid = strtoul(env.Get(\"fst.resync.fsid\"), 0,\n                                    10);\n  char* ptr = env.Get(\"fst.resync.force\");\n\n  if (ptr && (strncmp(ptr, \"1\", 1) == 0)) {\n    force = true;\n  }\n\n  if ((ptr == nullptr) || (fsid == 0ul)) {\n    eos_static_err(\"msg=\\\"resync with invalid args\\\" fsid=%lu fxid=%08llx\",\n                   (unsigned long) fsid, fid);\n    err_obj.setErrInfo(EINVAL, \"resync with invalid args\");\n    return SFS_ERROR;\n  }\n\n  if (!fid) {\n    eos_static_warning(\"msg=\\\"deleting fmd\\\" fsid=%lu fxid=%08llx\", fsid, fid);\n    mFmdHandler->LocalDeleteFmd(fid, fsid);\n  } else {\n    auto fmd = mFmdHandler->LocalGetFmd(fid, fsid, true, force);\n\n    if (fmd) {\n      if (force) {\n        eos_static_info(\"msg=\\\"force resync\\\" fxid=%08llx fsid=%lu\", fid, fsid);\n        std::string fpath = eos::common::FileId::FidPrefix2FullPath\n                            (eos::common::FileId::Fid2Hex(fid).c_str(),\n                             gOFS.Storage->GetStoragePath(fsid).c_str());\n\n        if (mFmdHandler->ResyncDisk(fpath.c_str(), fsid, false) == 0) {\n          if (!mFmdHandler->ResyncMgm(fsid, fid, nullptr)) {\n            eos_static_err(\"msg=\\\"resync mgm failed\\\" fxid=%08llx fsid=%lu\",\n                           fid, fsid);\n          }\n        } else {\n          eos_static_err(\"msg=\\\"resync disk failed\\\" fxid=%08llx fsid=%lu\",\n                         fid, fsid);\n        }\n      } else {\n        // Resync of meta data from the MGM by storing the FmdHelper in the\n        // WrittenFilesQueue to have it done asynchronously\n        std::unique_lock<std::mutex> lock(gOFS.WrittenFilesQueueMutex);\n        gOFS.WrittenFilesQueue.push(*fmd.get());\n      }\n    }\n  }\n\n  // @todo(esindril) once xrootd bug regarding handling of SFS_OK response\n  // in XrdXrootdXeq is fixed we can just return SFS_OK (>= XRootD 5)\n  // return SFS_OK;\n  const char* done = \"OK\";\n  err_obj.setErrInfo(strlen(done) + 1, done);\n  return SFS_DATA;\n}\n\n//------------------------------------------------------------------------------\n// Handle rtlog query\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::HandleRtlog(XrdOucEnv& env, XrdOucErrInfo& err_obj)\n{\n  XrdOucString tag = env.Get(\"mgm.rtlog.tag\");\n  XrdOucString lines = env.Get(\"mgm.rtlog.lines\");\n  XrdOucString filter = env.Get(\"mgm.rtlog.filter\");\n  XrdOucString response = \"\";\n\n  if (!filter.length()) {\n    filter = \" \";\n  }\n\n  if ((!lines.length()) || (!tag.length())) {\n    eos_static_err(\"msg=\\\"rtlog illegal parameter\\\" lines=%s tag=%s\",\n                   lines.c_str(), tag.c_str());\n    err_obj.setErrInfo(EINVAL, \"rtlog illegal parameter\");\n    return SFS_ERROR;\n  }\n\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n\n  if (g_logging.GetPriorityByString(tag.c_str()) == -1) {\n    eos_static_err(\"%s\", \"msg=\\\"unknown rtlog tag\\\"\");\n    err_obj.setErrInfo(EINVAL, \"rtlog unknown tag\");\n    return SFS_ERROR;\n  }\n\n  int logtagindex = g_logging.GetPriorityByString(tag.c_str());\n\n  for (int j = 0; j <= logtagindex; j++) {\n    for (int i = 1; i <= atoi(lines.c_str()); i++) {\n      g_logging.gMutex.Lock();\n      XrdOucString logline = g_logging.gLogMemory[j][\n                               (g_logging.gLogCircularIndex[j] - i +\n                                g_logging.gCircularIndexSize) %\n                               g_logging.gCircularIndexSize].c_str();\n      g_logging.gMutex.UnLock();\n\n      if (logline.length() && ((logline.find(filter.c_str())) != STR_NPOS)) {\n        response += logline;\n        response += \"\\n\";\n      }\n\n      if (!logline.length()) {\n        break;\n      }\n    }\n  }\n\n  // Use XrdOucBuffPool to manage XrdOucBuffer objects that can hold redirection\n  // info >= 2kb but not bigger than MaxSize\n  const uint32_t aligned_sz = eos::common::GetPowerCeil(response.length() + 1);\n  XrdOucBuffer* buff = mXrdBuffPool.Alloc(aligned_sz);\n\n  if (buff == nullptr) {\n    eos_static_err(\"msg=\\\"requested rtlog result buffer too big\\\" req_sz=%llu \"\n                   \"max_sz=%i\", response.length(), mXrdBuffPool.MaxSize());\n    err_obj.setErrInfo(ENOMEM, \"rtlog result buffer too big\");\n    return SFS_ERROR;\n  }\n\n  eos_static_debug(\"msg=\\\"rtlog reply\\\" data=\\\"%s\\\"\", response.c_str());\n  (void) strcpy(buff->Buffer(), response.c_str());\n  buff->SetLen(response.length() + 1);\n  err_obj.setErrInfo(buff->DataLen(), buff);\n  return SFS_DATA;\n}\n\n//------------------------------------------------------------------------------\n// Handle verify query\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::HandleVerify(XrdOucEnv& env, XrdOucErrInfo& err_obj)\n{\n  int envlen = 0;\n  eos_static_info(\"ms=\\\"verify opaque\\\" data=\\\"%s\\\"\", env.Env(envlen));\n  Verify* new_verify = Verify::Create(&env);\n\n  if (new_verify) {\n    Storage->PushVerification(new_verify);\n  } else {\n    eos_static_err(\"%s\", \"msg=\\\"failed verify, illegal opaque info\\\"\");\n  }\n\n  // @todo(esindril) once xrootd bug regarding handling of SFS_OK response\n  // in XrdXrootdXeq is fixed we can just return SFS_OK (>= XRootD 5)\n  // return SFS_OK;\n  const char* done = \"OK\";\n  err_obj.setErrInfo(strlen(done) + 1, done);\n  return SFS_DATA;\n}\n\n//------------------------------------------------------------------------------\n// Handle drop file query\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::HandleDropFile(XrdOucEnv& env, XrdOucErrInfo& err_obj)\n{\n  int caprc = 0;\n  XrdOucEnv* capOpaque {nullptr};\n  bool is_fsck = false;\n  char* ptr = env.Get(\"fst.drop.type\");\n\n  // Check if drop request comes from an fsck operation\n  if (ptr && (strncmp(ptr, \"fsck\", 4) == 0)) {\n    is_fsck = true;\n  }\n\n  if ((caprc = eos::common::SymKey::ExtractCapability(&env, capOpaque))) {\n    eos_static_err(\"msg=\\\"extract capability failed for deletion\\\" errno=%d\",\n                   caprc);\n    return SFS_ERROR;\n  } else {\n    int envlen = 0;\n    eos_static_debug(\"opaque=\\\"%s\\\"\", capOpaque->Env(envlen));\n    std::unique_ptr<Deletion> new_del = Deletion::Create(capOpaque);\n\n    if (new_del) {\n      if (mEnvFsckDeleteByMove && is_fsck) {\n        gOFS.Storage->DeleteByMove(std::move(new_del));\n      } else {\n        gOFS.Storage->AddDeletion(std::move(new_del));\n      }\n    } else {\n      eos_static_err(\"%s\", \"msg=\\\"illegal drop opaque information\\\"\");\n      return SFS_ERROR;\n    }\n  }\n\n  delete capOpaque;\n  // @todo(esindril) once xrootd bug regarding handling of SFS_OK response\n  // in XrdXrootdXeq is fixed we can just return SFS_OK (>= XRootD 5)\n  // return SFS_OK;\n  const char* done = \"OK\";\n  err_obj.setErrInfo(strlen(done) + 1, done);\n  return SFS_DATA;\n}\n\n//------------------------------------------------------------------------------\n// Handle clean orphans query\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::HandleCleanOrphans(XrdOucEnv& env, XrdOucErrInfo& err_obj)\n{\n  const char* ptr = env.Get(\"fst.fsid\");\n  eos::common::FileSystem::fsid_t fsid = 0ul;\n\n  if (ptr) {\n    std::string sfsid {ptr};\n\n    try {\n      size_t pos = 0;\n      fsid = std::stoul(sfsid, &pos);\n\n      if (pos != sfsid.length()) {\n        throw std::invalid_argument(\"fsid conversion failed\");\n      }\n    } catch (...) {\n      err_obj.setErrInfo(EINVAL, \"fsid is not numeric\");\n      return SFS_ERROR;\n    }\n  } else {\n    err_obj.setErrInfo(EINVAL, \"query missing fst.fsid key \");\n    return SFS_ERROR;\n  }\n\n  std::ostringstream err_msg;\n\n  if (!Storage->CleanupOrphans(fsid, err_msg)) {\n    err_obj.setErrInfo(EINVAL, err_msg.str().c_str());\n    return SFS_ERROR;\n  }\n\n  // @todo(esindril) once xrootd bug regarding handling of SFS_OK response\n  // in XrdXrootdXeq is fixed we can just return SFS_OK (>= XRootD 5)\n  // return SFS_OK;\n  const char* done = \"OK\";\n  err_obj.setErrInfo(strlen(done) + 1, done);\n  return SFS_DATA;\n}\n\n//------------------------------------------------------------------------------\n// Queue file for MGM sync operation\n//------------------------------------------------------------------------------\nvoid\nXrdFstOfs::QueueForMgmSync(eos::common::FmdHelper& fmd)\n{\n  std::unique_lock<std::mutex> lock(WrittenFilesQueueMutex);\n  WrittenFilesQueue.push(fmd);\n}\n\n//------------------------------------------------------------------------------\n// Query QDB for deletion list\n//------------------------------------------------------------------------------\nint\nXrdFstOfs::Query2Delete()\n{\n  eos_static_debug(\"%s\", \"msg=\\\"querying QDB for deletions\\\"\");\n\n  if (!mQcl) {\n    eos_static_err(\"%s\", \"msg=\\\"QDB client not available\\\"\");\n    return SFS_ERROR;\n  }\n\n  if (!Storage) {\n    eos_static_err(\"%s\", \"msg=\\\"Storage object not available\\\"\");\n    return SFS_ERROR;\n  }\n\n  // Iterate through all local file systems\n  eos::common::RWMutexReadLock rd_lock(Storage->mFsMutex);\n\n  for (const auto& elem : Storage->mFsMap) {\n    eos::common::FileSystem::fsid_t fsid = elem.first;\n    // Get unlinked files for this filesystem from QDB\n    std::vector<unsigned long long> unlinked_fids;\n\n    if (!GetUnlinkedFilesFromQDB(fsid, unlinked_fids)) {\n      eos_static_warning(\"msg=\\\"failed to get unlinked files\\\" fsid=%lu\", fsid);\n      continue;\n    }\n\n    if (unlinked_fids.empty()) {\n      continue;\n    }\n\n    eos_static_info(\"msg=\\\"scheduling deletions\\\" fsid=%lu count=%zu\", fsid,\n                    unlinked_fids.size());\n\n    try {\n      Storage->AddDeletion(std::make_unique<Deletion>(std::move(unlinked_fids),\n                           fsid));\n    } catch (const std::exception& ex) {\n      eos_static_err(\"msg=\\\"failed to schedule deletion\\\" fsid=%lu error=\\\"%s\\\"\",\n                     fsid,\n                     ex.what());\n    }\n  }\n\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Get unlinked files from QDB\n//------------------------------------------------------------------------------\nbool\nXrdFstOfs::GetUnlinkedFilesFromQDB(eos::common::FileSystem::fsid_t fsid,\n                                   std::vector<unsigned long long>& fids,\n                                   size_t max_batch)\n{\n  if (!mQcl) {\n    eos_static_err(\"msg=\\\"QDB client not available\\\"\");\n    return false;\n  }\n\n  std::string key = eos::RequestBuilder::keyFilesystemUnlinked(fsid);\n\n  try {\n    // Check if there are any pending deletions\n    auto card_reply = mQcl->exec(\"SCARD\", key).get();\n\n    if (!card_reply || card_reply->type != REDIS_REPLY_INTEGER ||\n        card_reply->integer == 0) {\n      eos_static_debug(\"msg=\\\"no unlinked files\\\" fsid=%lu\", fsid);\n      return true;\n    }\n\n    eos_static_debug(\"msg=\\\"unlinked files exist\\\" fsid=%lu count=%lld\", fsid,\n                     card_reply->integer);\n    // Iterate through the set\n    qclient::QSet unlinked_set(*mQcl, key);\n    auto cursor = unlinked_set.getIterator();\n    size_t count = 0;\n\n    while (cursor.valid() && count < max_batch) {\n      try {\n        unsigned long long fid = std::stoull(cursor.getElement());\n        fids.push_back(fid);\n        count++;\n      } catch (const std::exception& ex) {\n        eos_static_warning(\"msg=\\\"invalid fid in unlinked list\\\" fsid=%lu \"\n                           \"value=\\\"%s\\\" error=\\\"%s\\\"\",\n                           fsid, cursor.getElement().c_str(), ex.what());\n      }\n\n      cursor.next();\n    }\n\n    eos_static_debug(\"msg=\\\"retrieved unlinked files from QDB\\\" fsid=%lu \"\n                     \"count=%zu\",\n                     fsid, fids.size());\n    return true;\n  } catch (const std::exception& ex) {\n    eos_static_err(\"msg=\\\"failed to query QDB for unlinked files\\\" fsid=%lu \"\n                   \"error=\\\"%s\\\"\",\n                   fsid, ex.what());\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Set various XrdCl config options more appropriate for the EOS use-case but\n// still allow the env variables to override them.\n//------------------------------------------------------------------------------\nvoid\nXrdFstOfs::SetXrdClConfig()\n{\n  char* ptr {nullptr};\n  int env_value {0};\n  std::map<std::string, int> map_settings {\n    {\"TimeoutResolution\", 1}, {\"ConnectionWindow\", 5}, {\"ConnectionRetry\", 1},\n    {\"StreamErrorWindow\", 0}, {\"MetalinkProcessing\", 0}, {\"ParallelEvtLoop\", 8} };\n\n  for (auto& elem : map_settings) {\n    std::string env_name = \"XRD_\" + elem.first;\n    std::transform(env_name.begin(), env_name.end(), env_name.begin(),\n                   ::toupper);\n    ptr = getenv(env_name.c_str());\n    env_value = elem.second;\n\n    // Env variable overrides default values\n    if (ptr) {\n      try {\n        env_value = std::stoi(std::string(ptr));\n      } catch (...) {\n        eos_static_err(\"msg=\\\"invalid value for env var %s keeping the defaule\\\" \"\n                       \"default_value=1\", env_value);\n      }\n    }\n\n    XrdCl::DefaultEnv::GetEnv()->PutInt(elem.first, env_value);\n    eos_static_info(\"msg=\\\"update xrootd client timeouts\\\" name=%s value=%i\",\n                    elem.first.c_str(), env_value);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get Kernel relase information\n//------------------------------------------------------------------------------\nstd::string\nXrdFstOfs::GetKernelRelease()\n{\n  std::string kernel_release;\n  struct utsname buf;\n  int rc = uname(&buf);\n\n  if (!rc) {\n    kernel_release = buf.release;\n  }\n\n  return kernel_release;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/XrdFstOfs.hh",
    "content": "// ----------------------------------------------------------------------\n// File: XrdFstOfs.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __XRDFSTOFS_FSTOFS_HH__\n#define __XRDFSTOFS_FSTOFS_HH__\n\n#include \"common/Fmd.hh\"\n#include \"common/Logging.hh\"\n#include \"common/ThreadPool.hh\"\n#include \"common/XrdConnPool.hh\"\n#include \"fst/Namespace.hh\"\n#include \"fst/storage/TrafficShaping.hh\"\n#include \"fst/utils/OpenFileTracker.hh\"\n#include \"fst/utils/TpcInfo.hh\"\n#include \"mq/MessagingRealm.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"qclient/shared/SharedManager.hh\"\n#include <XrdOfs/XrdOfs.hh>\n#include <XrdOfs/XrdOfsTrace.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <chrono>\n#include <google/sparse_hash_map>\n#include <memory>\n#include <optional>\n#include <queue>\n\n//------------------------------------------------------------------------------\n//! Apple does not know these errnos\n//------------------------------------------------------------------------------\n#ifdef __APPLE__\n#define EBADE 52\n#define EBADR 53\n#define EADV 68\n#define EREMOTEIO 121\n#define ENOKEY 126\n#endif\n\nnamespace qclient\n{\nclass QClient;\n}\n\nnamespace eos::common\n{\nclass ExecutorMgr;\n}\n\nclass XrdOucEnv;\nclass XrdScheduler;\n\nEOSFSTNAMESPACE_BEGIN\n\n// Forward declarations\nclass ReplicaParLayout;\nclass RainMetaLayout;\nclass HttpServer;\nclass Storage;\nclass XrdFstOfsFile;\nclass FmdHandler;\n\n//------------------------------------------------------------------------------\n//! Class XrdFstOfs\n//------------------------------------------------------------------------------\nclass XrdFstOfs : public XrdOfs, public eos::common::LogId\n{\n  friend class XrdFstOfsFile;\n  friend class ReplicaParLayout;\n  friend class RainMetaLayout;\n\npublic:\n  std::atomic<bool> sShutdown; ///< True if shutdown procedure is running\n\n  //----------------------------------------------------------------------------\n  //! FST shutdown procedure\n  //!\n  //! @param sig\n  //----------------------------------------------------------------------------\n  static void xrdfstofs_shutdown(int sig);\n\n  //----------------------------------------------------------------------------\n  //! Get stacktrace from crashing process\n  //!\n  //! @param sig\n  //----------------------------------------------------------------------------\n  static void xrdfstofs_stacktrace(int sig);\n\n  //----------------------------------------------------------------------------\n  //! Print coverage data\n  //!\n  //! @param sig\n  //----------------------------------------------------------------------------\n  static void xrdfstofs_coverage(int sig);\n\n  //----------------------------------------------------------------------------\n  //! FST \"graceful\" shutdown procedure. The FST will wait for a configurable\n  //! amount of time for readers and writers before shutting down.\n  //!\n  //! @param sig signal to trigger this handler\n  //----------------------------------------------------------------------------\n  static void xrdfstofs_graceful_shutdown(int sig);\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  XrdFstOfs();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~XrdFstOfs();\n\n  //----------------------------------------------------------------------------\n  //! Get new OFS directory object\n  //!\n  //! @param user User information\n  //! @param MonID Monitoring ID\n  //!\n  //! @return OFS directory object (NULL)\n  //----------------------------------------------------------------------------\n  XrdSfsDirectory* newDir(char* user = 0, int MonID = 0);\n\n  //----------------------------------------------------------------------------\n  //! Get new OFS file object\n  //!\n  //! @param user User information\n  //! @param MonID Monitoring ID\n  //!\n  //! @return OFS file object\n  //----------------------------------------------------------------------------\n  XrdSfsFile* newFile(char* user = 0, int MonID = 0);\n\n  //----------------------------------------------------------------------------\n  //! Configure OFS object\n  //!\n  //! @param error error output object\n  //! @param envP environment for the configuration\n  //!\n  //! @return 0 if successful, otherwise -1\n  //----------------------------------------------------------------------------\n  int Configure(XrdSysError& error, XrdOucEnv* envP);\n\n  //----------------------------------------------------------------------------\n  //! fsctl command\n  //----------------------------------------------------------------------------\n  int fsctl(const int cmd, const char* args, XrdOucErrInfo& out_error,\n            const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //         ****** Here we mask all illegal operations ******\n  //----------------------------------------------------------------------------\n\n  //----------------------------------------------------------------------------\n  //! Return memory mapping for file, if any.\n  //!\n  //! @param Addr Address of memory location\n  //! @param Size Size of the file or zero if not memory mapped.\n  //----------------------------------------------------------------------------\n  int\n  getMap(void** Addr, off_t& Size)\n  {\n    return SFS_OK;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Chmod\n  //----------------------------------------------------------------------------\n  int\n  chmod(const char* path,\n        XrdSfsMode Mode,\n        XrdOucErrInfo& out_error,\n        const XrdSecEntity* client,\n        const char* opaque = 0)\n  {\n    EPNAME(\"chmod\");\n    return Emsg(epname, out_error, ENOSYS, epname, path);\n  }\n\n  //----------------------------------------------------------------------------\n  //! File exists\n  //----------------------------------------------------------------------------\n  int\n  exists(const char* path,\n         XrdSfsFileExistence& exists_flag,\n         XrdOucErrInfo& out_error,\n         const XrdSecEntity* client,\n         const char* opaque = 0)\n  {\n    EPNAME(\"exists\");\n    return Emsg(epname, out_error, ENOSYS, epname, path);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Mkdir\n  //----------------------------------------------------------------------------\n  int mkdir(const char* path,\n            XrdSfsMode Mode,\n            XrdOucErrInfo& out_error,\n            const XrdSecEntity* client,\n            const char* opaque = 0)\n  {\n    EPNAME(\"mkdir\");\n    return Emsg(epname, out_error, ENOSYS, epname, path);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Prepare request\n  //----------------------------------------------------------------------------\n  int\n  prepare(XrdSfsPrep& pargs,\n          XrdOucErrInfo& out_error,\n          const XrdSecEntity* client = 0)\n  {\n    EPNAME(\"prepare\");\n    return Emsg(epname, out_error, ENOSYS, epname);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Remove directory\n  //----------------------------------------------------------------------------\n  int remdir(const char* path,\n             XrdOucErrInfo& out_error,\n             const XrdSecEntity* client,\n             const char* info = 0)\n  {\n    EPNAME(\"remdir\");\n    return Emsg(epname, out_error, ENOSYS, epname, path);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Rename\n  //----------------------------------------------------------------------------\n  int rename(const char* oldFileName,\n             const char* newFileName,\n             XrdOucErrInfo& out_error,\n             const XrdSecEntity* client,\n             const char* infoO = 0,\n             const char* infoN = 0)\n  {\n    EPNAME(\"rename\");\n    return Emsg(epname, out_error, ENOSYS, epname, oldFileName);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Remove path - interface function\n  //----------------------------------------------------------------------------\n  int rem(const char* path,\n          XrdOucErrInfo& out_error,\n          const XrdSecEntity* client,\n          const char* info = 0);\n\n  //----------------------------------------------------------------------------\n  //! Remove path - low-level function\n  //----------------------------------------------------------------------------\n  int _rem(const char* path,\n           XrdOucErrInfo& out_error,\n           const XrdSecEntity* client,\n           XrdOucEnv* info = 0,\n           const char* fstPath = 0,\n           unsigned long long fid = 0,\n           unsigned long fsid = 0,\n           bool ignoreifnotexist = false,\n           std::string* deletion_report = 0);\n\n  //----------------------------------------------------------------------------\n  //! Get checksum - we publish checksums at the MGM\n  //! @brief retrieve a checksum\n  //!\n  //! @param func function to be performed 'csCalc','csGet' or 'csSize'\n  //! @param csName name of the checksum\n  //! @param error error object\n  //! @param client XRootD authentication object\n  //! @param ininfo CGI\n  //!\n  //! @return SFS_OK on success otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int chksum(XrdSfsFileSystem::csFunc Func,\n             const char* csName,\n             const char* Path,\n             XrdOucErrInfo& out_error,\n             const XrdSecEntity* client = 0,\n             const char* opaque = 0);\n\n  //----------------------------------------------------------------------------\n  //! Stat path\n  //----------------------------------------------------------------------------\n  int stat(const char* path,\n           struct stat* buf,\n           XrdOucErrInfo& out_error,\n           const XrdSecEntity* client,\n           const char* opaque = 0);\n\n  //----------------------------------------------------------------------------\n  //! Perform a filesystem extended attribute function.\n  //!\n  //! @param  faReq  - pointer to the request object (see XrdSfsFAttr.hh). If\n  //!                  the pointer is null, simply return whether or not\n  //!                  extended attributes are supported.\n  //! @param  eInfo  - The object where error info or results are to be returned.\n  //! @param  client - Client's identify (see common description).\n  //!\n  //! @return SFS_OK   a null response is sent.\n  //! @return SFS_DATA error.code    length of the data to be sent.\n  //!                  error.message contains the data to be sent.\n  //! @return SFS_STARTED Operation started result will be returned via callback.\n  //!         o/w      one of SFS_ERROR, SFS_REDIRECT, or SFS_STALL.\n  //----------------------------------------------------------------------------\n  int FAttr(XrdSfsFACtl* faReq, XrdOucErrInfo& eInfo,\n            const XrdSecEntity* client = 0) override;\n\n\n  //----------------------------------------------------------------------------\n  //! Callback to MGM node - XrdOucString version\n  //----------------------------------------------------------------------------\n  int CallManager(XrdOucErrInfo* error,\n                  const char* path,\n                  const char* manager,\n                  XrdOucString& capOpaqueFile,\n                  unsigned short timeout = 0,\n                  bool use_xrd_conn_pool = false,\n                  bool retry = true);\n\n  //----------------------------------------------------------------------------\n  //! Callback to MGM node - std::string version\n  //----------------------------------------------------------------------------\n  int CallManager(XrdOucErrInfo* error,\n                  const char* path,\n                  const char* manager,\n                  const std::string& capOpaqueFile,\n                  unsigned short timeout = 0,\n                  bool use_xrd_conn_pool = false,\n                  bool retry = true);\n\n  //----------------------------------------------------------------------------\n  //! Function dealing with plugin calls\n  //----------------------------------------------------------------------------\n  int FSctl(int, XrdSfsFSctl&, XrdOucErrInfo&, const XrdSecEntity*);\n\n  //----------------------------------------------------------------------------\n  //! Wait for ongoing IO operations to finish\n  //!\n  //! @param timeout maximum timeout you're willing to wait\n  //!\n  //! @return true if all operations finished within the timeout, otherwise\n  //!         false\n  //----------------------------------------------------------------------------\n  bool WaitForOngoingIO(std::chrono::seconds timeout);\n\n  //----------------------------------------------------------------------------\n  //! Allows to switch on error simulation in the OFS stack\n  //!\n  //! @param tag type of simulation error\n  //----------------------------------------------------------------------------\n  void SetSimulationError(const std::string& tag);\n\n  //----------------------------------------------------------------------------\n  //! Get node geotag (each token needs to be less then 8 characters)\n  //!\n  //! @return geotag value\n  //----------------------------------------------------------------------------\n  inline std::string GetGeoTag() const\n  {\n    return mGeoTag;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Query QDB for the list of deletions\n  //!\n  //! @param return SFS_ERROR if failed, otherwise SFS_OK\n  //----------------------------------------------------------------------------\n  int Query2Delete();\n\n  int Stall(XrdOucErrInfo& error, int stime, const char* msg);\n\n  int Redirect(XrdOucErrInfo& error, const char* host, int& port);\n\n  std::string MakeDeletionReport(eos::common::FileSystem::fsid_t fsid,\n                                 unsigned long long fid,\n                                 struct stat& deletion_stat,\n                                 bool viamq = true);\n\n  XrdSysError* Eroute;\n  eos::fst::Storage* Storage; ///< Meta data & filesystem store object\n  mutable XrdSysMutex OpenFidMutex;\n\n  eos::fst::OpenFileTracker openedForWriting;\n  eos::fst::OpenFileTracker openedForReading;\n  eos::fst::OpenFileTracker runningCreation;\n\n  //! Map to forbid deleteOnClose for creates if 1+X open had a successful close\n  google::sparse_hash_map<eos::common::FileSystem::fsid_t,\n         google::sparse_hash_map<unsigned long long,\n         bool> > WNoDeleteOnCloseFid;\n\n  XrdSysMutex XSLockFidMutex;\n  google::sparse_hash_map<eos::common::FileSystem::fsid_t,\n         google::sparse_hash_map<unsigned long long,\n         unsigned int> > XSLockFid;\n\n  //! Queue where file transaction reports get stored and picked up by a\n  //! thread running in the Storage class.\n  XrdSysMutex ReportQueueMutex;\n  std::queue<XrdOucString> ReportQueue;\n  //! Queue where log error are stored and picked up by a thread running in Storage\n  std::mutex WrittenFilesQueueMutex;\n  std::queue<eos::common::FmdHelper> WrittenFilesQueue;\n  std::unique_ptr<mq::MessagingRealm> mMessagingRealm;\n  XrdScheduler* TransferScheduler; ///< TransferScheduler\n  XrdSysMutex TransferSchedulerMutex; ///< protecting the TransferScheduler\n  XrdOucString eoscpTransferLog; ///< eoscp.log full path\n  const char* mHostName; ///< FST hostname\n  QdbContactDetails mQdbContactDetails; ///< QDB contact details\n  std::shared_ptr<qclient::QClient> mQcl; ///< Qclient\n  int mHttpdPort; ///< listening port of the http server\n  //! Embedded http server if available\n  std::unique_ptr<eos::fst::HttpServer> mHttpd;\n  std::chrono::seconds mTpcKeyMinValidity {2 * 60}; ///< TPC key minimum validity\n  std::chrono::seconds mTpcKeyMaxValidity {15 * 60}; ///< TPC key maximum validity\n  std::string mMgmAlias; ///< MGM alias\n  std::shared_ptr<FmdHandler> mFmdHandler; // <File Metadata Handler\n  //! Mark if Fsck deletions should be done by moving files to a quarantine\n  //! directory called .eosdeletions on the file system root mount\n  bool mEnvFsckDeleteByMove {false};\n  //! Concatenated root CA (generated by xrootd)\n  std::optional<std::string> ConcatenatedServerRootCA;\n  fst::traffic_shaping::IoStatsCollector mIoStatsCollector;\n  fst::traffic_shaping::IoDelayConfig mIoDelayConfig;\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  std::string mGeoTag; ///< Node geotag\n  //! XrdOucBuffPool object for managing redirection buffers >= 2kb\n  XrdOucBuffPool mXrdBuffPool;\n  //! Thread pool for async file  operations\n  eos::common::ThreadPool mAsyncOpThreadPool;\n  //! Xrd connection pool for interaction with the MGM, used from CallManager\n  std::unique_ptr<eos::common::XrdConnPool> mMgmXrdPool;\n  std::atomic<bool> mSimOpenDelay; ///< simulate an open timeout for client\n  std::atomic<uint32_t> mSimOpenDelaySec; ///< open delay in seconds\n  std::atomic<bool> mSimFmdOpenErr; ///< simulate a fmd mismatch error on open\n  std::atomic<bool> mSimIoReadErr; ///< simulate an IO error on read\n  std::atomic<bool> mSimReadDelay; ///< simulate read delay\n  std::atomic<uint32_t> mSimReadDelaySec; ///< read delay in seconds\n  std::atomic<bool> mSimIoWriteErr; ///< simulate an IO error on write\n  std::atomic<bool> mSimXsReadErr; ///< simulate a checksum error on read\n  std::atomic<bool> mSimXsWriteErr; ///< simulate a checksum error on write\n  //! Delay value for simulated xs error on write\n  std::atomic<uint32_t>  mSimXsWriteErrDelay;\n  std::atomic<uint64_t> mSimErrIoReadOff; ///< Simulate IO error offset on rd\n  std::atomic<uint64_t> mSimErrIoWriteOff;///< Simulate IO error offset on wr\n  std::atomic<bool> mSimDiskWriting;///< Do not really write IO to disk\n  std::atomic<bool> mSimCloseErr; ///< simulate an error during close\n  //! Simulate an error during closw while doing the MGM commit call\n  std::atomic<bool> mSimCloseCommitMgmErr;\n  std::atomic<bool> mSimUnresponsive; ///< simulate timeouts in the OFS layer\n\n  //! A vector map pointing from tpc key => tpc information for reads, [0]\n  //! are readers [1] are writers\n  std::vector<google::sparse_hash_map<std::string, struct TpcInfo >> TpcMap;\n  XrdSysMutex TpcMapMutex; ///< Mutex protecting the Tpc map\n\n  //----------------------------------------------------------------------------\n  //! Get simulation error offset. Parse the last characters and return the\n  //! desired offset e.g. io_read_8M should return 8MB\n  //!\n  //! @param input string encoding the error type and optionally the offset\n  //!\n  //! @return return offset from which the error should be reported or 0 if no\n  //!         such offset if provided\n  //----------------------------------------------------------------------------\n  static uint64_t GetSimulationErrorOffset(const std::string& input);\n\n  //------------------------------------------------------------------------------\n  //! Get simulation delay value. If none set then return 10 by default.\n  //!\n  //! @param input string encoding operation delay and optionally a delay value\n  //!              in seconds e.g. read_delay_10\n  //!\n  //! @return delay value in seconds or 10 by default if no delay specified\n  //------------------------------------------------------------------------------\n  static uint32_t GetSimulationDelay(const std::string& input);\n\n  //----------------------------------------------------------------------------\n  //! Compute adler checksum of given keytab file\n  //!\n  //! @param kt_path absolute path to keytab file\n  //!\n  //! @return string representing the checksum or \"unaccessible\" if keytab\n  //!         is unavailable\n  //----------------------------------------------------------------------------\n  std::string GetKeytabChecksum(const std::string& kt_path) const;\n\n  //----------------------------------------------------------------------------\n  //! Update the TPC key min/max validity values. By default these are 2 and 15\n  //! minutes by can be overwritten by the environment variables.\n  //----------------------------------------------------------------------------\n  void UpdateTpcKeyValidity();\n\n  //----------------------------------------------------------------------------\n  //! Create directory hierarchy\n  //!\n  //! @param dir_hierarchy given directory hierarchy\n  //! @param mode mode bits for the newly created directories\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool CreateDirHierarchy(std::string dir_hierarchy, mode_t mode) const;\n\n  //----------------------------------------------------------------------------\n  //! Handle debug query\n  //!\n  //! @param env ecoding of the query command\n  //! @param err_obj object holding the response for the query\n  //!\n  //! @param return SFS_ERROR if failed, otherwise SFS_OK or SFS_DATA and the\n  //!        err_obj is populated with the response\n  //----------------------------------------------------------------------------\n  int HandleDebug(XrdOucEnv& env, XrdOucErrInfo& err_obj);\n\n  //----------------------------------------------------------------------------\n  //! Handle resync query\n  //!\n  //! @param env ecoding of the query command\n  //! @param err_obj object holding the response for the query\n  //!\n  //! @param return SFS_ERROR if failed, otherwise SFS_OK or SFS_DATA and the\n  //!        err_obj is populated with the response\n  //----------------------------------------------------------------------------\n  int HandleResync(XrdOucEnv& env, XrdOucErrInfo& err_obj);\n\n  //----------------------------------------------------------------------------\n  //! Handle rtlog query\n  //!\n  //! @param env ecoding of the query command\n  //! @param err_obj object holding the response for the query\n  //!\n  //! @param return SFS_ERROR if failed, otherwise SFS_DATA and the err_obj is\n  //!        populated with the response\n  //----------------------------------------------------------------------------\n  int HandleRtlog(XrdOucEnv& env, XrdOucErrInfo& err_obj);\n\n  //----------------------------------------------------------------------------\n  //! Handle verify query\n  //!\n  //! @param env ecoding of the query command\n  //! @param err_obj object holding the response for the query\n  //!\n  //! @param return SFS_ERROR if failed, otherwise SFS_DATA and the err_obj is\n  //!        populated with the response\n  //----------------------------------------------------------------------------\n  int HandleVerify(XrdOucEnv& env, XrdOucErrInfo& err_obj);\n\n  //----------------------------------------------------------------------------\n  //! Handle drop file query\n  //!\n  //! @param env ecoding of the query command\n  //! @param err_obj object holding the response for the query\n  //!\n  //! @param return SFS_ERROR if failed, otherwise SFS_DATA and the err_obj is\n  //!        populated with the response\n  //----------------------------------------------------------------------------\n  int HandleDropFile(XrdOucEnv& env, XrdOucErrInfo& err_obj);\n\n  //----------------------------------------------------------------------------\n  //! Handle clean orphans query\n  //!\n  //! @param env ecoding of the query command\n  //! @param err_obj object holding the response for the query\n  //!\n  //! @param return SFS_ERROR if failed, otherwise SFS_DATA and the err_obj is\n  //!        populated with the response. \"OK\" if successful.\n  //----------------------------------------------------------------------------\n  int HandleCleanOrphans(XrdOucEnv& env, XrdOucErrInfo& err_obj);\n\n  //----------------------------------------------------------------------------\n  //! Queue file for MGM sync operation\n  //!\n  //! @param fmd FmdHelper object to queue\n  //----------------------------------------------------------------------------\n  void QueueForMgmSync(eos::common::FmdHelper& fmd);\n\n  //----------------------------------------------------------------------------\n  //! Set various XrdCl config options more appropriate for the EOS use-case\n  //! but still allow the env variables to override them\n  //----------------------------------------------------------------------------\n  static void SetXrdClConfig();\n\n  //----------------------------------------------------------------------------\n  //! Get Kernel relase information\n  //!\n  //! @return kernel release info or empty if failed\n  //----------------------------------------------------------------------------\n  static std::string GetKernelRelease();\n\n  //----------------------------------------------------------------------------\n  //! Query QDB directly for unlinked files on a given filesystem\n  //!\n  //! @param fsid file system id\n  //! @param fids vector to populate with unlinked file ids\n  //! @param max_batch maximum number of fids to retrieve (default 1024)\n  //!\n  //! @return true if successful, false otherwise\n  //----------------------------------------------------------------------------\n  bool GetUnlinkedFilesFromQDB(eos::common::FileSystem::fsid_t fsid,\n                               std::vector<unsigned long long>& fids,\n                               size_t max_batch = 1024);\n};\n\n//------------------------------------------------------------------------------\n//! Global OFS handle\n//------------------------------------------------------------------------------\nextern XrdFstOfs gOFS;\n\nEOSFSTNAMESPACE_END\n#endif\n"
  },
  {
    "path": "fst/XrdFstOfsFile.cc",
    "content": "//------------------------------------------------------------------------------\n// File: XrdFstOfsFile.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied waDon'trranty of  *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#include \"fst/XrdFstOfsFile.hh\"\n#include \"common/Constants.hh\"\n#include \"common/CtaCommon.hh\"\n#include \"common/Path.hh\"\n#include \"common/SecEntity.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/Timing.hh\"\n#include \"common/WFEClient.hh\"\n#include \"common/http/OwnCloud.hh\"\n#include \"common/xrootd-ssi-protobuf-interface/eos_cta/include/CtaFrontendApi.hpp\"\n#include \"cta_frontend.grpc.pb.h\"\n#include \"cta_frontend.pb.h\"\n#include \"fst/Config.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/checksum/ChecksumPlugins.hh\"\n#include \"fst/io/FileIoPluginCommon.hh\"\n#include \"fst/layout/Layout.hh\"\n#include \"fst/layout/LayoutPlugin.hh\"\n#include \"fst/storage/FileSystem.hh\"\n#include \"fst/storage/TrafficShaping.hh\"\n#include \"fst/utils/IoPriority.hh\"\n#include \"namespace/utils/Etag.hh\"\n#include <XrdOss/XrdOssApi.hh>\n#include <XrdOuc/XrdOucPgrwUtils.hh>\n#include <grpc++/grpc++.h>\n\nextern XrdOss* XrdOfsOss;\n\nEOSFSTNAMESPACE_BEGIN\n\nconstexpr uint16_t XrdFstOfsFile::msDefaultTimeout;\nthread_local int t_iopriority = 0;\n\n//------------------------------------------------------------------------------\n// Get TPC key expiration timestamp\n//------------------------------------------------------------------------------\ntime_t\nXrdFstOfsFile::GetTpcKeyExpireTS(std::string_view tpc_ttl, time_t now_ts)\n{\n  using namespace std::chrono;\n  time_t now = time(nullptr);\n\n  if (now_ts) {\n    now = now_ts;\n  }\n\n  time_t expire_ts = now + gOFS.mTpcKeyMinValidity.count();\n\n  if (!tpc_ttl.empty()) {\n    unsigned int ttl_val = 0ul;\n\n    if (eos::common::StringToNumeric(tpc_ttl, ttl_val)) {\n      if ((ttl_val >= gOFS.mTpcKeyMinValidity.count()) &&\n          (ttl_val <= gOFS.mTpcKeyMaxValidity.count())) {\n        expire_ts = now + ttl_val;\n      } else {\n        if (ttl_val < gOFS.mTpcKeyMinValidity.count()) {\n          expire_ts = now + gOFS.mTpcKeyMinValidity.count();\n        } else {\n          expire_ts = now + gOFS.mTpcKeyMaxValidity.count();\n        }\n      }\n    }\n  }\n\n  eos_static_debug(\"msg=\\\"tpc key validity\\\" seconds=%u\", expire_ts - now);\n  return expire_ts;\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nXrdFstOfsFile::XrdFstOfsFile(const char* user, int MonID) :\n  XrdOfsFileBase(user, MonID), eos::common::LogId(),\n  mOpenOpaque(nullptr), mCapOpaque(nullptr), mFstPath(\"\"), mBookingSize(0),\n  mTargetSize(0), mMinSize(0), mMaxSize(0), viaDelete(false),\n  mWrDelete(false), mRainSize(0), mNsPath(\"\"), mLocalPrefix(\"\"),\n  mRdrManager(\"\"), mTapeEnabled(false), mSecString(\"\"), mEtag(\"\"),\n  mFileId(0), mFsId(0), mLid(0), mCid(0), mForcedMtime(1), mForcedMtime_ms(0),\n  mFusex(false), mFusexIsUnlinked(false), mClosed(false), mCloseRc(0),\n  mOpened(false), mHasWrite(false), mHasWriteErr(false), mHasReadErr(false),\n  mIsRW(false), mIsDevNull(false), mIsCreation(false), mIsReplication(false),\n  noAtomicVersioning(false),\n  mIsInjection(false), mRainReconstruct(false), mDelOnClose(false),\n  mRepairOnClose(false), mIsOCchunk(false), writeErrorFlag(false),\n  mEventOnClose(false), mSyncOnClose(false), mEventWorkflow(\"default\"),\n  mSyncEventOnClose(false), mFmd(nullptr),\n  mLayout(nullptr), mMaxOffsetWritten(0ull),\n  mWritePosition(0ull), mOpenSize(0),\n  mCloseSize(0), mTpcThreadStatus(EINVAL), mTpcState(kTpcIdle),\n  mTpcFlag(kTpcNone), mTpcKey(\"\"), mIsTpcDst(false), mTpcRetc(0),\n  mTpcCancel(false), mTpcFileSize(0ull), mIsHttp(false)\n{\n  rBytes = wBytes = sFwdBytes = sBwdBytes = sXlFwdBytes\n                                = sXlBwdBytes = rOffset = wOffset = 0;\n  rStart.tv_sec = wStart.tv_sec = rvStart.tv_sec = rTime.tv_sec = lrTime.tv_sec =\n                                    rvTime.tv_sec = lrvTime.tv_sec = 0;\n  rStart.tv_usec = wStart.tv_usec = rvStart.tv_usec = rTime.tv_usec =\n                                      lrTime.tv_usec = rvTime.tv_usec = lrvTime.tv_usec = 0;\n  wTime.tv_sec = lwTime.tv_sec = cTime.tv_sec = 0;\n  wTime.tv_usec = lwTime.tv_usec = cTime.tv_usec = 0;\n  rCalls = wCalls = nFwdSeeks = nBwdSeeks = nXlFwdSeeks = nXlBwdSeeks = 0;\n  closeTime.tv_sec = closeTime.tv_usec = 0;\n  cacheITime.tv_sec = cacheITime.tv_usec = 0;\n  currentTime.tv_sec = openTime.tv_usec = 0;\n  openTime.tv_sec = openTime.tv_usec = 0;\n  totalBytes = 0;\n  msSleep = 0;\n  mBandwidth = 0;\n  timeToOpen = 0;\n  timeToClose = 0;\n  timeToRead = 0;\n  timeToReadV = 0;\n  timeToWrite = 0;\n  tz.tz_dsttime = tz.tz_minuteswest = 0;\n  mIoPriorityValue = 0;\n  mIoPriorityClass = IOPRIO_CLASS_NONE;\n  mIoPriorityErrorReported = false;\n  mChecksumGroup.reset(new ChecksumGroup);\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nXrdFstOfsFile::~XrdFstOfsFile()\n{\n  viaDelete = true;\n\n  if (!mClosed) {\n    close();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Open file\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::open(const char* path, XrdSfsFileOpenMode open_mode,\n                    mode_t create_mode, const XrdSecEntity* client,\n                    const char* opaque)\n{\n  EPNAME(\"open\");\n  eos::common::Timing tm(\"open\");\n  COMMONTIMING(\"begin\", &tm);\n  const char* tident = error.getErrUser();\n  SetLogId(ExtractLogId(opaque).c_str(), client, tident);\n  mTident = error.getErrUser();\n  char* val = 0;\n  mIsRW = false;\n  int retc = SFS_OK;\n  int envlen = 0;\n  mNsPath = path;\n  gettimeofday(&openTime, &tz);\n  bool hasCreationMode = (open_mode & (SFS_O_CREAT | SFS_O_TRUNC));\n  bool isRepairRead = false;\n  // Mask some opaque parameters to shorten the logging\n  XrdOucString maskOpaque = opaque ? opaque : \"\";\n  eos::common::StringConversion::MaskTag(maskOpaque, \"cap.sym\");\n  eos::common::StringConversion::MaskTag(maskOpaque, \"cap.msg\");\n  eos::common::StringConversion::MaskTag(maskOpaque, \"authz\");\n  eos_info(\"path=%s info=%s open_mode=%x\", mNsPath.c_str(),\n           maskOpaque.c_str(), open_mode);\n  // Process and filter open opaque information\n  std::string in_opaque = (opaque ? opaque : \"\");\n  in_opaque += \"&mgm.path=\";\n  in_opaque += mNsPath.c_str();\n  // Process TPC information - after this mOpenOpaque and mCapOpaque will be\n  // properly populated and decrypted.\n  int tpc_retc = ProcessTpcOpaque(in_opaque, client);\n\n  if (tpc_retc == SFS_ERROR) {\n    eos_err(\"msg=\\\"failed while processing TPC/open opaque\\\" \"\n            \"path=\\\"%s\\\"\", path);\n    return SFS_ERROR;\n  } else if (tpc_retc >= SFS_STALL) {\n    return tpc_retc; // this is stall time in seconds\n  }\n\n  if (ProcessOpenOpaque()) {\n    eos_err(\"msg=\\\"failed while processing open opaque info\\\" \"\n            \"path=\\\"%s\\\"\", path);\n    return SFS_ERROR;\n  }\n\n  eos::common::VirtualIdentity vid;\n\n  if (ProcessCapOpaque(isRepairRead, vid)) {\n    eos_err(\"msg=\\\"failed while processing cap opaque info\\\" \"\n            \"path=\\\"%s\\\"\", path);\n    return SFS_ERROR;\n  }\n\n  if (ProcessMixedOpaque()) {\n    eos_err(\"msg=\\\"failed while processing mixed opaque info\\\" \"\n            \"path=\\\"%s\\\"\", path);\n    return SFS_ERROR;\n  }\n\n  // For RAIN layouts if the opaque information contains the tag mgm.rain.store=1\n  // the corrupted files are recovered back on disk. There is no other way to make\n  // the distinction between an open for write and open for recovery\n  if (mCapOpaque && (val = mCapOpaque->Get(\"mgm.rain.store\"))) {\n    if (strncmp(val, \"1\", 1) == 0) {\n      eos_info(\"msg=\\\"enabling RAIN store recovery\\\" fxid=%08llx\", mFileId);\n      open_mode = SFS_O_RDWR;\n      mRainReconstruct = true;\n      mHasWrite = true;\n\n      // Get logical file size\n      if ((val = mCapOpaque->Get(\"mgm.rain.size\"))) {\n        try {\n          mRainSize = std::stoull(val);\n        } catch (...) {\n          // ignore\n        }\n      } else {\n        eos_warning(\"msg=\\\"unknown RAIN file size during reconstruction\\\" \"\n                    \"fxid=%08llx\", mFileId);\n      }\n    }\n  }\n\n  if ((open_mode & (SFS_O_WRONLY | SFS_O_RDWR | SFS_O_CREAT | SFS_O_TRUNC))) {\n    mIsRW = true;\n  }\n\n  if (mNsPath.beginswith(\"/replicate:\")) {\n    if (gOFS.openedForWriting.isOpen(mFsId, mFileId)) {\n      eos_err(\"msg=\\\"forbid replica open, file %s opened in RW mode\\\"\",\n              mNsPath.c_str());\n      return gOFS.Emsg(epname, error, ETXTBSY, \"open - cannot replicate: file \"\n                       \"is opened in RW mode\", mNsPath.c_str());\n    }\n\n    // File is supposed to act as a sink, used for draining\n    if (mNsPath == \"/replicate:0\") {\n      if (!mIsRW) {\n        eos_err(\"msg=\\\"replicate file can only be opened for RW\\\" fxid=%08llx \"\n                \"path=\\\"%s\\\"\", mFileId, mNsPath.c_str());\n        return gOFS.Emsg(epname, error, EIO, \"open - replicate file can only \"\n                         \"be opened in RW mode\", mNsPath.c_str());\n      } else {\n        eos_info(\"%s\", \"msg=\\\"file fxid=0 acting as a sink i.e. /dev/null\\\"\");\n        mIsDevNull = true;\n        return SFS_OK;\n      }\n    }\n\n    mIsReplication = true;\n  }\n\n  COMMONTIMING(\"path::print\", &tm);\n\n  // Check if this is an open for HTTP\n  if (!mIsRW && ((std::string(client->tident) == \"http\"))) {\n    if (gOFS.openedForWriting.isOpen(mFsId, mFileId)) {\n      eos_err(\"msg=\\\"forbid replica open for synchronization, file %s \"\n              \"opened in RW mode\\\"\", mNsPath.c_str());\n      // Return resource temporarily unavailable as EBUSY cannot be used due to XRootD hack\n      // in Emsg() that returns the integer \"5\" (proxy hack)\n      return gOFS.Emsg(epname, error, EAGAIN, \"open - cannot synchronize \"\n                       \"file opened in RW mode\", mNsPath.c_str());\n    }\n  }\n\n  // Get the layout object\n  mLayout.reset(eos::fst::LayoutPlugin::GetLayoutObject\n                (this, mLid, client, &error, mFstPath.c_str(), gOFS.mFmdHandler.get(),\n                 msDefaultTimeout, mRainReconstruct,\n                 gOFS.Storage->mComputeStripeChecksum.load()));\n\n  if (mLayout == nullptr) {\n    int envlen;\n    eos_err(\"msg=\\\"unable to handle layout for %s\\\"\", mCapOpaque->Env(envlen));\n    return gOFS.Emsg(epname, error, EINVAL, \"open - illegal layout specified \",\n                     mCapOpaque->Env(envlen));\n  }\n\n  mLayout->SetLogId(logId, client, tident);\n  errno = 0;\n\n  if ((mRainReconstruct && (mTpcFlag == kTpcSrcCanDo)) ||\n      (mTpcFlag == kTpcSrcSetup)) {\n    eos_info(\"msg=\\\"kTpcSrcSetup return SFS_OK\\\" fxid=%08llx\", mFileId);\n    return SFS_OK;\n  }\n\n  COMMONTIMING(\"creation::barrier\", &tm);\n  OpenFileTracker::CreationBarrier creationSerialization(gOFS.runningCreation,\n      mFsId, mFileId);\n  COMMONTIMING(\"layout::exists\", &tm);\n\n  if ((retc = mLayout->GetFileIo()->fileExists())) {\n    // We have to distinguish if an Exists call fails or returns ENOENT,\n    // otherwise we might trigger an automatic clean-up of a file!!!\n    if (errno != ENOENT) {\n      return gOFS.Emsg(epname, error, EIO, \"open - unable to check for \"\n                       \"existence of file \", mCapOpaque->Env(envlen));\n    }\n\n    if (mIsRW || (mCapOpaque->Get(\"mgm.zerosize\"))) {\n      if (!mIsRW && mCapOpaque->Get(\"mgm.zerosize\")) {\n        // this commit should not call the versioning/atomic functionality\n        noAtomicVersioning = true;\n      }\n\n      // File does not exist, keep the create flag for writers and readers\n      // with 0-size at MGM\n      mIsRW = true;\n      mIsCreation = true;\n      mOpenSize = 0;\n      mWritePosition = 0;\n      // Used to indicate if a file was written in the meanwhile by someone else\n      updateStat.st_mtime = 0;\n      open_mode |= SFS_O_CREAT;\n      create_mode |= SFS_O_MKPTH;\n      eos_debug(\"msg=\\\"adding creation flag\\\" retc=%d errno=%d fxid=%08llx\",\n                retc, errno, mFileId);\n    } else {\n      // The open will fail but the client will get a recoverable error,\n      // therefore it will try to read again from the other replicas.\n      eos_warning(\"msg=\\\"open for read, local file does not exists\\\" \"\n                  \"fxid=%08llx path=\\\"%s\\\"\", mFileId, mFstPath.c_str());\n      return gOFS.Emsg(epname, error, ENOENT, \"open, file does not exist \",\n                       mCapOpaque->Env(envlen));\n    }\n  } else {\n    eos_debug(\"msg=\\\"removing creation flag\\\" retc=%d errno=%d fxid=%08llx\",\n              retc, errno, mFileId);\n\n    // Remove the creat flag\n    if (open_mode & SFS_O_CREAT) {\n      open_mode -= SFS_O_CREAT;\n    }\n  }\n\n  if (!mIsCreation) {\n    creationSerialization.Release();\n  }\n\n  // Capability access distinction\n  if (mIsRW) {\n    if (mIsCreation) {\n      if (mCapOpaque->Get(\"mgm.zerosize\") == nullptr) {\n        if (!mCapOpaque->Get(\"mgm.access\")\n            || ((strcmp(mCapOpaque->Get(\"mgm.access\"), \"create\")) &&\n                (strcmp(mCapOpaque->Get(\"mgm.access\"), \"write\")) &&\n                (strcmp(mCapOpaque->Get(\"mgm.access\"), \"update\")))) {\n          return gOFS.Emsg(epname, error, EPERM, \"open - capability does not \"\n                           \"allow to create/write/update this file\", path);\n        }\n      }\n    } else {\n      if (!mCapOpaque->Get(\"mgm.access\")\n          || ((strcmp(mCapOpaque->Get(\"mgm.access\"), \"create\")) &&\n              (strcmp(mCapOpaque->Get(\"mgm.access\"), \"write\")) &&\n              (strcmp(mCapOpaque->Get(\"mgm.access\"), \"update\")))) {\n        return gOFS.Emsg(epname, error, EPERM, \"open - capability does not \"\n                         \"allow to update/write/create this file\", path);\n      }\n    }\n  } else {\n    if (!mCapOpaque->Get(\"mgm.access\")\n        || ((strcmp(mCapOpaque->Get(\"mgm.access\"), \"read\")) &&\n            (strcmp(mCapOpaque->Get(\"mgm.access\"), \"create\")) &&\n            (strcmp(mCapOpaque->Get(\"mgm.access\"), \"write\")) &&\n            (strcmp(mCapOpaque->Get(\"mgm.access\"), \"update\")))) {\n      return gOFS.Emsg(epname, error, EPERM, \"open - capability does not allow \"\n                       \"to read this file\", path);\n    }\n  }\n\n  // Get IO priority\n  if (mCapOpaque->Get(\"mgm.iopriority\")) {\n    std::string key;\n    std::string value;\n    std::string kv = mCapOpaque->Get(\"mgm.iopriority\");\n\n    if (eos::common::StringConversion::SplitKeyValue(kv, key, value, \":\")) {\n      mIoPriorityClass = ioprio_class(key);\n      mIoPriorityValue = ioprio_value(value);\n    }\n  }\n\n  if (mCapOpaque->Get(\"mgm.iobw\")) {\n    mBandwidth = strtoull(mCapOpaque->Get(\"mgm.iobw\"), 0, 10);\n    eos_info(\"msg=\\\"bandwidth limited\\\" bw=%d, fxid=%08llx\",\n             mBandwidth, mFileId);\n  }\n\n  // Bookingsize is only needed for file creation\n  if (mIsRW && mIsCreation && !mFusex) {\n    const char* sbookingsize = 0;\n    const char* stargetsize = 0;\n    // If fsid=0 then all replicas/stripes are dropped and the logical file\n    // is also removed, otherwise only the current mFsId is dropped.\n    eos::common::FileSystem::fsid_t drop_fsid =\n      ((!mIsReplication && !mIsInjection && !mRainReconstruct) ? 0u : mFsId);\n\n    if (!(sbookingsize = mCapOpaque->Get(\"mgm.bookingsize\"))) {\n      DropFromMgm(mFileId, drop_fsid, mNsPath.c_str(), mRdrManager.c_str());\n      eos_err(\"msg=\\\"no bookingsize in capability\\\" fxid=%08llx\", mFileId);\n      return gOFS.Emsg(epname, error, EINVAL, \"open - no booking size in capability\",\n                       mNsPath.c_str());\n    } else {\n      mBookingSize = strtoull(mCapOpaque->Get(\"mgm.bookingsize\"), 0, 10);\n\n      if (errno == ERANGE) {\n        DropFromMgm(mFileId, drop_fsid, mNsPath.c_str(), mRdrManager.c_str());\n        eos_err(\"msg=\\\"invalid bookingsize in capability\\\" fxid=%08llx \"\n                \"bookingsize=\\\"%s\\\"\", mFileId, sbookingsize);\n        return gOFS.Emsg(epname, error, EINVAL, \"open - invalid bookingsize \"\n                         \"in capability\", mNsPath.c_str());\n      }\n    }\n\n    if ((stargetsize = mCapOpaque->Get(\"mgm.targetsize\"))) {\n      mTargetSize = strtoull(mCapOpaque->Get(\"mgm.targetsize\"), 0, 10);\n\n      if (errno == ERANGE) {\n        DropFromMgm(mFileId, drop_fsid, mNsPath.c_str(), mRdrManager.c_str());\n        eos_err(\"msg=\\\"invalid targetsize in capability\\\" fxid=%08llx \"\n                \"targetsize=%s\", mFileId, stargetsize);\n        return gOFS.Emsg(epname, error, EINVAL, \"open - invalid targetsize \"\n                         \"in capability\", mNsPath.c_str());\n      }\n    }\n\n    // Check if the booking size violates the min/max-size criteria\n    if (mBookingSize && mMaxSize) {\n      if (mBookingSize > mMaxSize) {\n        DropFromMgm(mFileId, drop_fsid, mNsPath.c_str(), mRdrManager.c_str());\n        eos_err(\"msg=\\\"invalid bookingsize specified - violates maximum \"\n                \"file size criteria\\\" booking_sz=%llu\", mBookingSize);\n        return gOFS.Emsg(epname, error, ENOSPC, \"open - bookingsize violates \"\n                         \"maximum allowed filesize\", mNsPath.c_str());\n      }\n    }\n\n    if (mBookingSize && mMinSize) {\n      if (mBookingSize < mMinSize) {\n        DropFromMgm(mFileId, drop_fsid, mNsPath.c_str(), mRdrManager.c_str());\n        eos_err(\"msg=\\\"invalid bookingsize specified - violates minimum \"\n                \"file size criteria\\\" fxid=%08llx booking_sz=%llu\",\n                mFileId, mBookingSize);\n        return gOFS.Emsg(epname, error, ENOSPC, \"open - bookingsize violates \"\n                         \"minimum allowed filesize\", mNsPath.c_str());\n      }\n    }\n  }\n\n  if (gOFS.mSimFmdOpenErr) {\n    eos_warning(\"msg=\\\"simulate FMD open error\\\" fxid=%08llx\", mFileId);\n    return gOFS.Emsg(epname, error, ENOENT, \"open - no FMD record found, \"\n                     \"simulated error\");\n  }\n\n  COMMONTIMING(\"clone::fst\", &tm);\n  char* sCloneFST = mCapOpaque->Get(\"mgm.cloneFST\");\n  int clone_create_rc = 1;\n\n  if (sCloneFST) {\n    std::string mc_fst_path = eos::common::FileId::FidPrefix2FullPath(sCloneFST,\n                              mLocalPrefix.c_str());\n    struct stat clone_stat;\n    int clonerc = ::stat(mc_fst_path.c_str(), &clone_stat) ? errno : 0;\n    eos_info(\"fstpath=%s clonepath=%s clonerc=%d len=%d\", mFstPath.c_str(),\n             mc_fst_path.c_str(), clonerc, clonerc ? -1 : clone_stat.st_size);\n\n    /* clone handling:\n     * if read-write and clone does not exist, create it\n     * if read-only switch to clone if it exists\n     * (note: if several clones were allowed, we'd might have to search!)\n     */\n    if (mIsRW && clonerc != 0) { /* for RW, only if clone not yet created */\n      if (open_mode & SFS_O_TRUNC) {\n        /* rename data file to clone, it will be re-created */\n        clone_create_rc = ::rename(mFstPath.c_str(), mc_fst_path.c_str()) ? errno : 0;\n        eos_info(\"copy-on-write: rename %s %s rc=%d\", mFstPath.c_str(),\n                 mc_fst_path.c_str(), clone_create_rc);\n      } else {\n        /* copy data file to clone before modyfying */\n        char sbuff[1024];\n        snprintf(sbuff, sizeof(sbuff),\n                 \"cp --preserve=xattr,ownership,mode --reflink=auto %s %s\",\n                 mFstPath.c_str(), mc_fst_path.c_str());\n        clone_create_rc = system(sbuff);\n        eos_info(\"copy-on-write: %s rc=%d\", sbuff, clone_create_rc);\n      }\n    }\n  }\n\n  XrdOucString oss_opaque = \"\";\n  oss_opaque += \"&mgm.lid=\";\n  oss_opaque += std::to_string(mLid).c_str();\n  oss_opaque += \"&mgm.bookingsize=\";\n  oss_opaque += std::to_string(mBookingSize).c_str();\n\n  if (!(val = mCapOpaque->Get(\"mgm.iotype\"))) {\n    // provided by a client\n    if ((val = mOpenOpaque->Get(\"eos.iotype\"))) {\n      oss_opaque += \"&mgm.ioflag=\";\n      oss_opaque += val;\n\n      if (std::string(val) == \"csync\") {\n        // cannot be done in the OSS\n        mSyncOnClose = true;\n      }\n    }\n  } else {\n    // forced by the MGM configuration\n    oss_opaque += \"&mgm.ioflag=\";\n    oss_opaque += val;\n\n    if (std::string(val) == \"csync\") {\n      // cannot be done in the OSS\n      mSyncOnClose = true;\n    }\n  }\n\n  // Open layout implementation\n  eos_info(\"path=%s open-mode=%x create-mode=%x layout-name=%s oss-opaque=%s\",\n           mFstPath.c_str(), open_mode, create_mode, mLayout->GetName(),\n           oss_opaque.c_str());\n  COMMONTIMING(\"layout::open\", &tm);\n  int rc = mLayout->Open(open_mode, create_mode, oss_opaque.c_str());\n  COMMONTIMING(\"layout::opened\", &tm);\n\n  if (rc) {\n    // If we have local errors in open we don't disable the filesystem -\n    // this is done by the Scrub thread if necessary!\n    if (mLayout->IsEntryServer() && !mIsReplication) {\n      eos_warning(\"msg=\\\"open error return recoverable error \"\n                  \"EIO(kXR_IOError)\\\" fxid=%08llx\", mFileId);\n\n      // Clean-up before re-bouncing\n      if (hasCreationMode && !mRainReconstruct && !mIsInjection) {\n        DropFromMgm(mFileId, 0ul, path, mRdrManager.c_str());\n        mDelOnClose = true;\n      }\n    }\n\n    return gOFS.Emsg(epname, error, EIO, \"open - failed open\");\n  }\n\n  if (gOFS.mSimOpenDelay) {\n    eos_warning(\"msg=\\\"simulate open timeout that becomes a client error\\\" \"\n                \"fxid=%08llx\", mFileId);\n    std::this_thread::sleep_for(std::chrono::seconds(gOFS.mSimOpenDelaySec.load()));\n  }\n\n  COMMONTIMING(\"get::localfmd\", &tm);\n  mFmd = gOFS.mFmdHandler->LocalGetFmd(mFileId, mFsId, isRepairRead, mIsRW,\n                                       vid.uid, vid.gid, mLid);\n  COMMONTIMING(\"resync::localfmd\", &tm);\n\n  if (mFmd == nullptr) {\n    if (gOFS.mFmdHandler->ResyncMgm(mFsId, mFileId, mRdrManager.c_str())) {\n      eos_info(\"msg=\\\"resync ok\\\" fsid=%u fxid=%08llx\", mFsId, mFileId);\n      mFmd = gOFS.mFmdHandler->LocalGetFmd(mFileId, mFsId, isRepairRead,\n                                           mIsRW, vid.uid, vid.gid, mLid);\n      std::string dummy_xs;\n      int rc = 0;\n\n      if ((rc = gOFS.mFmdHandler->ResyncDisk(mFstPath.c_str(), mFsId, false, 0,\n                                             dummy_xs))) {\n        eos_err(\"msg=\\\"failed to resync from disk\\\" fsid=%lu fxid=%llx \"\n                \"path=%s rc=%d\", mFsId, mFileId, mFstPath.c_str(), rc);\n      } else {\n        eos_info(\"msg=\\\"resync from disk\\\" path=%s\", mFstPath.c_str());\n      }\n    } else {\n      eos_err(\"msg=\\\"resync failed\\\" fsid=%u fxid=%08llx\", mFsId, mFileId);\n    }\n  }\n\n  if (mFmd == nullptr) {\n    eos_err(\"msg=\\\"no FMD record found\\\" fsid=%u fxid=%08llx\", mFsId, mFileId);\n\n    if (!mIsRW || (mLayout->IsEntryServer() && !mIsReplication)) {\n      eos_warning(\"msg=\\\"failed to get FMD record\\\" fsid=%u fxid=%08llx \"\n                  \"path=\\\"%s\\\" rc=ENOENT(kXR_NotFound)\", mFsId, mFileId,\n                  mFstPath.c_str());\n\n      if (hasCreationMode && !mRainReconstruct && !mIsInjection) {\n        DropFromMgm(mFileId, 0ul, path, mRdrManager.c_str());\n      }\n    }\n\n    // Return an error that can be recovered at the MGM\n    return gOFS.Emsg(epname, error, ENOENT, \"open - no FMD record found\");\n  }\n\n  // Update the fmd information for any clone objects\n  if (sCloneFST) {\n    if (mIsRW && (clone_create_rc == 0)) {\n      // Populate local DB (future reads need it)\n      unsigned long long clFid = eos::common::FileId::Hex2Fid(sCloneFST);\n      auto lfmd = gOFS.mFmdHandler->LocalGetFmd(clFid, mFsId, false, mIsRW,\n                  vid.uid, vid.gid, mLid);\n\n      if (lfmd == nullptr) {\n        // We have an invalid FMD, drop and try again!\n        gOFS.mFmdHandler->LocalDeleteFmd(clFid, mFsId);\n        lfmd = gOFS.mFmdHandler->LocalGetFmd(clFid, mFsId, false, mIsRW,\n                                             vid.uid, vid.gid, mLid);\n\n        // FIXME: maybe we don't need to exit here?\n        if (!lfmd) {\n          return gOFS.Emsg(epname, error, ENOENT, \"open unable to create FMD\");\n        }\n      }\n\n      lfmd->mProtoFmd.set_checksum(mFmd->mProtoFmd.checksum());\n      lfmd->mProtoFmd.set_diskchecksum(mFmd->mProtoFmd.diskchecksum());\n      lfmd->mProtoFmd.set_mgmchecksum(mFmd->mProtoFmd.mgmchecksum());\n\n      if (!gOFS.mFmdHandler->Commit(lfmd.get())) {\n        eos_err(\"copy-on-write unable to commit meta data to local database\");\n        (void) gOFS.Emsg(epname, this->error, EIO,\n                         \"copy-on-write - unable to commit meta data\", mNsPath.c_str());\n      }\n\n      eos_debug(\"fid %lld cs %s diskcs %s mgmcs %s\", lfmd->mProtoFmd.fid(),\n                lfmd->mProtoFmd.checksum().c_str(), lfmd->mProtoFmd.diskchecksum().c_str(),\n                lfmd->mProtoFmd.mgmchecksum().c_str());\n    } else {\n      eos_debug(\"fid %lld cs %s diskcs %s mgmcs %s\", mFmd->mProtoFmd.fid(),\n                mFmd->mProtoFmd.checksum().c_str(), mFmd->mProtoFmd.diskchecksum().c_str(),\n                mFmd->mProtoFmd.mgmchecksum().c_str());\n    }\n  }\n\n  if (mIsCreation) {\n    creationSerialization.Release();\n  }\n\n  COMMONTIMING(\"layout::stat\", &tm);\n\n  if (!mIsCreation && mIsReplication) {\n    mLayout->Stat(&updateStat);\n  }\n\n  if (mIsCreation && mBookingSize) {\n    COMMONTIMING(\"full::mutex\", &tm);\n    // Check if the file system is full\n    XrdSysMutexHelper lock(gOFS.Storage->mFsFullMapMutex);\n\n    if (gOFS.Storage->mFsFullMap[mFsId]) {\n      if (mLayout->IsEntryServer() && !mIsReplication) {\n        writeErrorFlag = kOfsDiskFullError;\n        mLayout->Remove();\n        eos_warning(\"msg=\\\"not enough space\\\" fsid=%u fxid=%08llx \"\n                    \"rc=ENODEV(kXR_FSError)\", mFsId, mFileId);\n\n        if (hasCreationMode && !mRainReconstruct && !mIsInjection) {\n          // Clean-up all stripes\n          DropFromMgm(mFileId, 0ul, path, mRdrManager.c_str());\n        }\n\n        // Return an error that can be recovered at the MGM\n        return gOFS.Emsg(epname, error, ENODEV, \"open - not enough space\");\n      }\n\n      return gOFS.Emsg(epname, error, ENOSPC, \"create file - disk space \"\n                       \"(headroom) exceeded fn=\", mFstPath.c_str());\n    }\n\n    COMMONTIMING(\"layout::fallocate\", &tm);\n    rc = mLayout->Fallocate(mBookingSize);\n    COMMONTIMING(\"layout::fallocated\", &tm);\n\n    if (rc) {\n      eos_crit(\"msg=\\\"file allocation failed\\\" fsid=%u fxid=%08llx retc=%d \"\n               \"errno=%d size=%llu\", mFsId, mFileId, rc, errno, mBookingSize);\n\n      if (mLayout->IsEntryServer() && !mIsReplication) {\n        mLayout->Remove();\n        eos_warning(\"msg=\\\"not enough space, file allocation failed\\\" fsid=%lu \"\n                    \"fxid=%08llx rc=ENODEV(kXR_FSError\", mFsId, mFileId);\n\n        if (hasCreationMode && !mRainReconstruct && !mIsInjection) {\n          // Clean-up all stripes\n          DropFromMgm(mFileId, 0ul, path, mRdrManager.c_str());\n        }\n\n        // Return an error that can be recovered at the MGM\n        return gOFS.Emsg(epname, error, ENODEV, \"open - file allocation failed\");\n      }\n\n      mLayout->Remove();\n      return gOFS.Emsg(epname, error, ENOSPC, \"open - cannot allocate \"\n                       \"required space\", mNsPath.c_str());\n    }\n  }\n\n  if (mIsCreation) {\n    gOFS.mFmdHandler->Commit(mFmd.get());\n  } else {\n    COMMONTIMING(\"layout::stat\", &tm);\n    // Get the real size of the file, not the local stripe size!\n    struct stat statinfo {};\n\n    if ((retc = mLayout->Stat(&statinfo))) {\n      return gOFS.Emsg(epname, error, EIO, \"open - cannot stat layout to \"\n                       \"determine file size\", mNsPath.c_str());\n    }\n\n    // We feed the layout size, not the physical on disk!\n    eos_info(\"msg=\\\"layout size\\\" fxid=%08llx disk_size=%zu db_size=%llu\", mFileId,\n             statinfo.st_size, mFmd->mProtoFmd.size());\n    mOpenSize = mFmd->mProtoFmd.size();\n    mWritePosition = mOpenSize;\n\n    if (!eos::common::LayoutId::IsRain(mLayout->GetLayoutId())) {\n      // If replica layout and physical size of replica difference from the\n      // fmd_size it means the file is being written to, so we save the actual\n      // size from disk.\n      if ((off_t) statinfo.st_size != (off_t) mFmd->mProtoFmd.size()) {\n        mOpenSize = statinfo.st_size;\n        mWritePosition = mOpenSize;\n      }\n    }\n\n    // Preset with the last known checksum\n    if (mIsRW && mChecksumGroup->HasChecksums() && !mIsOCchunk) {\n      eos_info(\"msg=\\\"checksum reset init\\\" fxid=%08llx file-xs=%s\",\n               mFileId, mFmd->mProtoFmd.checksum().c_str());\n      mChecksumGroup->ResetInitDefault(0, mOpenSize,\n                                       mFmd->mProtoFmd.checksum().c_str());\n    }\n  }\n\n  // For RAIN layouts we enable full file checksum only at the entry server for\n  // write operations. For the rest of the cases we rely on the block and parity\n  // checking.\n  if (eos::common::LayoutId::IsRain(mLid) &&\n      !(mIsRW && mLayout->IsEntryServer())) {\n    mChecksumGroup->Clear();\n  }\n\n  // Set the eos lfn as extended attribute\n  std::unique_ptr<FileIo> io\n  (FileIoPlugin::GetIoObject(mLayout->GetLocalReplicaPath(), this));\n  COMMONTIMING(\"fileio::object\", &tm);\n\n  if (mIsRW) {\n    if (mNsPath.beginswith(\"/replicate:\") || mNsPath.beginswith(\"/fusex-open\")) {\n      if (mCapOpaque->Get(\"mgm.path\")) {\n        XrdOucString unsealedpath = mCapOpaque->Get(\"mgm.path\");\n        XrdOucString sealedpath = path;\n\n        if (io->attrSet(std::string(\"user.eos.lfn\"),\n                        std::string(unsealedpath.c_str()))) {\n          eos_err(\"msg=\\\"unable to set extended attr <eos.lfn> \\\" \"\n                  \"fxid=%08llx errno=%d\", mFileId, errno);\n        }\n      } else {\n        eos_err(\"msg=\\\"no lfn in replication capability\\\" fxid=%08lls\",\n                mFileId);\n      }\n    } else {\n      if (io->attrSet(std::string(\"user.eos.lfn\"), std::string(mNsPath.c_str()))) {\n        eos_err(\"msg=\\\"unable to set extended attr <eos.lfn>\\\" \"\n                \"fxid=%08llx errno=%d\", mFileId, errno);\n      }\n    }\n  } else {\n    // For reading of replica file check for xs errors unless this\n    // is a fuse client!\n    if (!mFusex && eos::common::LayoutId::IsReplica(mLid) &&\n        gOFS.mFmdHandler->FileHasXsError(mLayout->GetLocalReplicaPath(), mFsId)) {\n      eos_err(\"msg=\\\"open failed due to checksum mismatch\\\" fxid=%08llx \"\n              \"path=\\\"%s\\\"\", mFileId, mNsPath.c_str());\n      return gOFS.Emsg(epname, error, EIO, \"open - replica checksum mismatch\",\n                       mNsPath.c_str());\n    }\n  }\n\n  COMMONTIMING(\"open::accounting\", &tm);\n  ProcessAltXsRequest();\n\n  if (mIsRW) {\n    gOFS.openedForWriting.up(mFsId, mFileId);\n  } else {\n    gOFS.openedForReading.up(mFsId, mFileId);\n  }\n\n  mOpened = true;\n  COMMONTIMING(\"end\", &tm);\n  timeToOpen = tm.RealTime();\n\n  // Report slow open as errors if longer than 1000ms\n  if (timeToOpen > 1000) {\n    eos_err(\"msg=\\\"slow open operation\\\" open-duration=%.03fms fxid=%08llx \"\n            \"path=\\\"%s\\\" %s\", timeToOpen, mFileId, mNsPath.c_str(),\n            tm.Dump().c_str());\n  }\n\n  eos_info(\"open-duration=%.03fms path=\\\"%s\\\" fxid=%08llx %s\", timeToOpen,\n           mNsPath.c_str(), mFileId, tm.Dump().c_str());\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Pre-read into file system cache\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::read(XrdSfsFileOffset fileOffset, XrdSfsXferSize amount)\n{\n  int rc = XrdOfsFile::read(fileOffset, amount);\n  eos_debug(\"rc=%d offset=%lu size=%llu\", rc, fileOffset, amount);\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Read from file\n//------------------------------------------------------------------------------\nXrdSfsXferSize\nXrdFstOfsFile::read(XrdSfsFileOffset fileOffset, char* buffer,\n                    XrdSfsXferSize buffer_size)\n{\n  gettimeofday(&rStart, &tz);\n  // use RR scheduling if there is a round-robin app name\n  std::mutex* mutex = nullptr;\n\n  if (!mAppRR.empty()) {\n    if (mIsRW) {\n      mutex = gOFS.openedForWriting.scheduleRR(mFsId, mAppRR);\n    } else {\n      mutex = gOFS.openedForReading.scheduleRR(mFsId, mAppRR);\n    }\n  }\n\n  auto lockScope = (mutex == nullptr) ?\n                   std::unique_lock<std::mutex>() :\n                   std::unique_lock<std::mutex>(*mutex);\n  eos_debug(\"fileOffset=%lli, buffer_size=%i\", fileOffset, buffer_size);\n\n  if (mTpcFlag == kTpcSrcRead) {\n    if (!(rCalls % 10)) {\n      if (!TpcValid()) {\n        eos_err(\"msg=\\\"tcp interrupted by control-c - cancel tcp read\\\" key=%s\",\n                mTpcKey.c_str());\n        return gOFS.Emsg(\"read\", error, EINTR, \"read - tpc transfer interrupted\"\n                         \" by client disconnect\", FName());\n      }\n    }\n  }\n\n  if (const uint64_t sleep_time_micro_sec =\n          gOFS.mIoDelayConfig.GetReadDelayForAppUidGid(vid);\n      sleep_time_micro_sec > 0) {\n    std::this_thread::sleep_for(std::chrono::microseconds(sleep_time_micro_sec));\n  }\n\n  if (mBandwidth) {\n    gettimeofday(&currentTime, &tz);\n    float abs_time = static_cast<float>((currentTime.tv_sec -\n                                         openTime.tv_sec) * 1000 +\n                                        (currentTime.tv_usec - openTime.tv_usec) / 1000);\n    // Regulate the io - sleep as desired\n    float exp_time = totalBytes / mBandwidth / 1000.0;\n\n    if (abs_time < exp_time) {\n      msSleep += (exp_time - abs_time);\n      std::int64_t thisSleep = msSleep;\n      std::this_thread::sleep_for(std::chrono::milliseconds(thisSleep));\n    }\n  }\n\n  if (const uint64_t sleep_time_micro_sec =\n          gOFS.mIoDelayConfig.GetWriteDelayForAppUidGid(vid);\n      sleep_time_micro_sec > 0) {\n    std::this_thread::sleep_for(std::chrono::microseconds(sleep_time_micro_sec));\n  }\n\n  const uint64_t rc = mLayout->Read(fileOffset, buffer, buffer_size);\n  if (rc > 0) {\n    gOFS.mIoStatsCollector.RecordRead(vid.app, vid.uid, vid.gid, rc);\n  }\n  eos_debug(\"layout read %d checkSum %d\", rc,\n            mChecksumGroup ? nullptr : mChecksumGroup->GetDefault());\n\n  if (gOFS.mSimReadDelay) {\n    eos_warning(\"msg=\\\"apply read delay\\\" delay=%is fxid=%08llx\",\n                gOFS.mSimReadDelaySec.load(), mFileId);\n    std::this_thread::sleep_for(std::chrono::seconds(gOFS.mSimReadDelaySec.load()));\n  }\n\n  /* maintaining a checksum is tricky if there have been writes,\n   * but the read + append case can be supported in \"Add\" */\n  if ((rc > 0) && (mChecksumGroup->HasChecksums()) && (!mHasWrite)) {\n    mChecksumGroup->Add(buffer, static_cast<size_t>(rc),\n                        static_cast<off_t>(fileOffset));\n  }\n\n  if (rc > 0) {\n    // if required, unobfuscate a buffer server side\n    if (mLayout->IsEntryServer() && mHmac.key.length()) {\n      eos::common::SymKey::UnobfuscateBuffer(const_cast<char*>(buffer), rc,\n                                             fileOffset, mHmac);\n    }\n\n    if (mLayout->IsEntryServer() || eos::common::LayoutId::IsRain(mLid)) {\n      XrdSysMutexHelper vecLock(vecMutex);\n      rvec.push_back(rc);\n    }\n\n    rOffset = fileOffset + rc;\n    totalBytes += rc;\n  }\n\n  if (rc < 0) {\n    // Here we might take some other action\n    int envlen = 0;\n    eos_crit(\"block-read error=%d offset=%llu len=%llu file=%s\",\n             error.getErrInfo(),\n             static_cast<unsigned long long>(fileOffset),\n             static_cast<unsigned long long>(buffer_size),\n             FName(), mCapOpaque ? mCapOpaque->Env(envlen) : FName());\n    // Used to understand if a reconstruction of a RAIN file worked\n    mHasReadErr = true;\n  }\n\n  eos_debug(\"rc=%d offset=%lu size=%llu\", rc, fileOffset,\n            static_cast<unsigned long long>(buffer_size));\n\n  if ((fileOffset + buffer_size) >= mOpenSize) {\n    if (mChecksumGroup->HasChecksums() && !mHasWrite) {\n      /* even if there were only reads up to here the file may still be modified if opened R/W. As\n       * VerifyChecksum \"finalises\" the context, this has to be handled in write anyway;\n       * but not finalising now speeds up the slightly less marginal case of \"write a lot\" +\n       * \"read a little\" + \"write a little\" (seen in \"git\") */\n      if (!mChecksumGroup->NeedsRecalculation()) {\n        // If this is the last read of sequential reading, we can verify\n        // the checksum now (unless we're writing as well)\n        if (VerifyChecksum()) {\n          return gOFS.Emsg(\"read\", error, EIO, \"read file - wrong file \"\n                           \"checksum fn=\", FName());\n        }\n      }\n    }\n  }\n\n  AddLayoutReadTime();\n  return rc;\n}\n\n//----------------------------------------------------------------------------\n// Read file pages into a buffer and return corresponding checksums\n//----------------------------------------------------------------------------\nXrdSfsXferSize\nXrdFstOfsFile::pgRead(XrdSfsFileOffset offset, char* buffer,\n                      XrdSfsXferSize rdlen, uint32_t* csvec, uint64_t opts)\n{\n  eos_debug(\"offset=%lli len=%i\", offset, rdlen);\n  XrdSfsXferSize bytes;\n\n  // Read the data into the buffer\n  if ((bytes = read(offset, buffer, rdlen)) <= 0) {\n    return bytes;\n  }\n\n  // Generate the crc's\n  XrdOucPgrwUtils::csCalc(buffer, offset, bytes, csvec);\n  return bytes;\n}\n\n\n//------------------------------------------------------------------------------\n// Vector read\n//------------------------------------------------------------------------------\nXrdSfsXferSize\nXrdFstOfsFile::readv(XrdOucIOVec* readV, int readCount)\n{\n  eos_debug(\"msg=\\\"readv request\\\" count=%i\", readCount);\n\n  gettimeofday(&rvStart, &tz);\n  std::string output_init, output_final;\n  auto print_readv_request = [](XrdOucIOVec * readv, int num_chunks) {\n    std::ostringstream oss;\n\n    for (int i = 0; i < num_chunks; ++i) {\n      oss << \"index=\" << i\n          << \" offset=\" << readv[i].offset\n          << \" length=\" << readv[i].size\n          << std::endl;\n    }\n\n    return oss.str();\n  };\n\n  if (EOS_LOGS_DEBUG) {\n    output_init = print_readv_request(readV, readCount);\n    eos_debug(\"msg=\\\"initial readv request\\\" obj=%p content=\\\"%s\\\"\",\n              readV, output_init.c_str());\n  }\n\n  // Copy the XrdOucIOVec structure to XrdCl::ChunkList\n  uint32_t total_read = 0;\n  XrdCl::ChunkList chunkList;\n  chunkList.reserve(readCount);\n\n  for (int i = 0; i < readCount; ++i) {\n    total_read += (uint32_t)readV[i].size;\n    chunkList.push_back(XrdCl::ChunkInfo((uint64_t)readV[i].offset,\n                                         (uint32_t)readV[i].size,\n                                         (void*)readV[i].data));\n  }\n\n  if (const uint64_t sleep_time_micro_sec =\n          gOFS.mIoDelayConfig.GetReadDelayForAppUidGid(vid);\n      sleep_time_micro_sec > 0) {\n    std::this_thread::sleep_for(std::chrono::microseconds(sleep_time_micro_sec));\n  }\n\n  const int64_t rv = mLayout->ReadV(chunkList, total_read);\n  totalBytes += rv;\n  if (rv > 0) {\n    gOFS.mIoStatsCollector.RecordRead(vid.app, vid.uid, vid.gid, rv);\n  }\n\n  if (EOS_LOGS_DEBUG) {\n    output_final = print_readv_request(readV, readCount);\n    eos_debug(\"msg=\\\"final readv request\\\" obj=%p content=\\\"%s\\\"\",\n              readV, output_final.c_str());\n\n    if (output_init != output_final) {\n      eos_crit(\"%s\", \"msg=\\\"readv object corrupted\\\"\");\n    }\n  }\n\n  if (rv < 0) {\n    eos_crit(\"readv error=%d cnt=%d file=%s\", rv, readCount, FName());\n    mHasReadErr = true;\n  }\n\n  AddLayoutReadVTime();\n  return rv;\n}\n\n//------------------------------------------------------------------------------\n// Write to file\n//------------------------------------------------------------------------------\nXrdSfsXferSize\nXrdFstOfsFile::write(XrdSfsFileOffset fileOffset, const char* buffer,\n                     XrdSfsXferSize buffer_size)\n{\n  gettimeofday(&wStart, &tz);\n\n  if (gOFS.mSimUnresponsive) {\n    eos_warning(\"msg=\\\"simulate unresponsive write, delay by 120s\\\" \"\n                \"fxid=%08llx\", mFileId);\n    std::this_thread::sleep_for(std::chrono::seconds(120));\n  }\n\n  if (mIsDevNull) {\n    eos_debug(\"offset=%llu, length=%li discarded for sink file\", fileOffset,\n              buffer_size);\n    mMaxOffsetWritten = fileOffset + buffer_size;\n    AddLayoutWriteTime();\n    return buffer_size;\n  }\n\n  {\n    // use global RR serialization (we just use fsid 0 for that)\n    std::mutex* mutex = nullptr;\n\n    if (!mAppRR.empty()) {\n      if (mIsRW) {\n        mutex = gOFS.openedForWriting.scheduleRR(0, mAppRR);\n      } else {\n        mutex = gOFS.openedForReading.scheduleRR(0, mAppRR);\n      }\n    }\n\n    auto lockScope = (mutex == nullptr) ?\n                     std::unique_lock<std::mutex>() :\n                     std::unique_lock<std::mutex>(*mutex);\n  }\n\n  // use RR scheduling if there is a round-robin app name per filesystem\n  std::mutex* mutex = nullptr;\n\n  if (!mAppRR.empty()) {\n    if (mIsRW) {\n      mutex = gOFS.openedForWriting.scheduleRR(mFsId, mAppRR);\n    } else {\n      mutex = gOFS.openedForReading.scheduleRR(mFsId, mAppRR);\n    }\n  }\n\n  auto lockScope = (mutex == nullptr) ?\n                   std::unique_lock<std::mutex>() :\n                   std::unique_lock<std::mutex>(*mutex);\n\n  if (const uint64_t sleep_time_micro_sec =\n          gOFS.mIoDelayConfig.GetWriteDelayForAppUidGid(vid);\n      sleep_time_micro_sec > 0) {\n    std::this_thread::sleep_for(std::chrono::microseconds(sleep_time_micro_sec));\n  }\n\n  if (mBandwidth) {\n    gettimeofday(&currentTime, &tz);\n    float abs_time = static_cast<float>((currentTime.tv_sec -\n                                         openTime.tv_sec) * 1000 +\n                                        (currentTime.tv_usec - openTime.tv_usec) / 1000);\n    // Regulate the io - sleep as desired\n    float exp_time = totalBytes / mBandwidth / 1000.0;\n\n    if (abs_time < exp_time) {\n      msSleep += (exp_time - abs_time);\n      std::int64_t thisSleep = msSleep;\n      std::this_thread::sleep_for(std::chrono::milliseconds(thisSleep));\n    }\n  }\n\n  // if the write position moves the checksum is dirty\n  if (mChecksumGroup->HasChecksums()) {\n    if (mWritePosition != fileOffset) {\n      mChecksumGroup->SetDirty();\n    }\n\n    // store next write position\n    mWritePosition = fileOffset + buffer_size;\n  }\n\n  // if required, obfuscate a buffer server side\n  if (mLayout->IsEntryServer() && mHmac.key.length()) {\n    eos::common::SymKey::ObfuscateBuffer(const_cast<char*>(buffer),\n                                         const_cast<char*>(buffer), buffer_size, fileOffset, mHmac);\n  }\n\n  int rc = mLayout->Write(fileOffset, const_cast<char*>(buffer), buffer_size);\n  eos_debug(\"rc=%d offset=%lu size=%lu\", rc, fileOffset,\n            static_cast<unsigned long>(buffer_size));\n  if (rc > 0) {\n    gOFS.mIoStatsCollector.RecordWrite(vid.app, vid.uid, vid.gid, rc);\n  }\n  // If we see a remote IO error, we don't fail, we just call repair afterwards,\n  // only for replica layouts and not for FuseX clients\n  if ((rc < 0) && mIsCreation && !mFusex &&\n      eos::common::LayoutId::IsReplica(mLid) &&\n      (mLayout->GetErrObj()->getErrInfo() == EREMOTEIO)) {\n    mRepairOnClose = true;\n    rc = buffer_size;\n  }\n\n  // In case we have a remote write error for a replica, the local replica is still ok!\n  if ((rc < 0) && (eos::common::LayoutId::IsReplica(mLid) &&\n                   (mLayout->GetErrObj()->getErrInfo() == EREMOTEIO))) {\n    rc = buffer_size;\n  }\n\n  // Evt. add checksum\n  if (rc > 0) {\n    if (mChecksumGroup->HasChecksums()) {\n      mChecksumGroup->Add(buffer, static_cast<size_t>(rc),\n                          static_cast<off_t>(fileOffset));\n    }\n\n    totalBytes += rc;\n\n    if (static_cast<unsigned long long>(fileOffset + buffer_size) >\n        static_cast<unsigned long long>(mMaxOffsetWritten)) {\n      mMaxOffsetWritten = (fileOffset + buffer_size);\n    }\n  }\n\n  if (rc < 0) {\n    int envlen = 0;\n\n    if (!mHasWriteErr || EOS_LOGS_DEBUG) {\n      eos_crit(\"block-write error=%d offset=%llu len=%llu file=%s\",\n               mLayout->GetErrObj()->getErrInfo(),\n               static_cast<unsigned long long>(fileOffset),\n               static_cast<unsigned long long>(buffer_size),\n               FName(), mCapOpaque ? mCapOpaque->Env(envlen) : FName());\n    }\n\n    mHasWriteErr = true;\n  } else {\n    mHasWrite = true;\n\n    if (mLayout->IsEntryServer() || mIsReplication) {\n      XrdSysMutexHelper lock(vecMutex);\n      wvec.push_back(rc);\n    }\n  }\n\n  if (rc < 0) {\n    int envlen = 0;\n    // Indicate the deletion flag for write errors\n    mWrDelete = true;\n    XrdOucString errdetail;\n\n    if (mIsCreation) {\n      XrdOucString newerr;\n      // Add to the error message that this file has been removed after the error,\n      // which happens for creations\n      newerr = error.getErrText();\n\n      if (writeErrorFlag == kOfsSimulatedIoError) {\n        // Simulated IO error\n        errdetail += \" => file has been removed because of a simulated IO error\";\n      } else {\n        if (writeErrorFlag == kOfsDiskFullError) {\n          // Disk full error\n          errdetail +=\n            \" => file has been removed because the target filesystem  was full\";\n        } else {\n          if (writeErrorFlag == kOfsMaxSizeError) {\n            // Maximum file size error\n            errdetail += \" => file has been removed because the maximum target \"\n                         \"filesize defined for that subtree was exceeded (maxsize=\";\n            char smaxsize[16];\n            snprintf(smaxsize, sizeof(smaxsize) - 1, \"%llu\", (unsigned long long) mMaxSize);\n            errdetail += smaxsize;\n            errdetail += \" bytes)\";\n          } else {\n            if (writeErrorFlag == kOfsIoError) {\n              // Generic IO error\n              errdetail +=\n                \" => file has been removed due to an IO error on the target filesystem\";\n            } else {\n              errdetail += \" => file has been removed due to an IO error (unspecified)\";\n            }\n          }\n        }\n      }\n\n      newerr += errdetail.c_str();\n      error.setErrData(newerr.c_str());\n    }\n\n    eos_err(\"block-write error=%d offset=%llu len=%llu file=%s error=\\\"%s\\\"\",\n            error.getErrInfo(),\n            (unsigned long long) fileOffset,\n            (unsigned long long) buffer_size, FName(),\n            mCapOpaque ? mCapOpaque->Env(envlen) : FName(),\n            errdetail.c_str());\n  }\n\n  AddLayoutWriteTime();\n  return rc;\n}\n\n//----------------------------------------------------------------------------\n// Write file pages into a file with corresponding checksums.\n//----------------------------------------------------------------------------\nXrdSfsXferSize\nXrdFstOfsFile::pgWrite(XrdSfsFileOffset offset, char* buffer,\n                       XrdSfsXferSize wrlen, uint32_t* csvec, uint64_t opts)\n{\n  eos_debug(\"offset=%lli len=%i\", offset, wrlen);\n\n  // If we have a checksum vector and verify is on, do verification.\n  if (opts & Verify) {\n    XrdOucPgrwUtils::dataInfo dInfo(buffer, csvec, offset, wrlen);\n    off_t badoff;\n    int   badlen;\n\n    if (!XrdOucPgrwUtils::csVer(dInfo, badoff, badlen)) {\n      char eMsg[512];\n      snprintf(eMsg, sizeof(eMsg), \"Checksum error at offset %lld\",\n               (long long) badoff);\n      error.setErrInfo(EDOM, eMsg);\n      return SFS_ERROR;\n    }\n  }\n\n  return write(offset, buffer, wrlen);\n}\n\n//------------------------------------------------------------------------------\n// Get file stat information\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::stat(struct stat* buf)\n{\n  EPNAME(\"stat\");\n  int rc = SFS_OK;\n\n  if (mIsDevNull) {\n    buf->st_size = mMaxOffsetWritten;\n    return rc;\n  }\n\n  if (mRainReconstruct) {\n    buf->st_size = mRainSize;\n    return rc;\n  }\n\n  if (mLayout) {\n    if ((rc = mLayout->Stat(buf)))\n      rc = gOFS.Emsg(epname, error, EIO, \"stat - cannot stat layout to determine\"\n                     \" file size \", mNsPath.c_str());\n  } else {\n    rc = gOFS.Emsg(epname, error, ENXIO, \"stat - no layout to determine file size \",\n                   mNsPath.c_str());\n  }\n\n  // store the file id as inode number\n  if (!rc) {\n    buf->st_ino = eos::common::FileId::FidToInode(mFileId);\n  }\n\n  // we store the mtime.ns time in st_dev ... sigh@Xrootd ...\n#ifdef __APPLE__\n  unsigned long nsec = buf->st_mtimespec.tv_nsec;\n#else\n  unsigned long nsec = buf->st_mtim.tv_nsec;\n#endif\n  // mask for 10^9\n  nsec &= 0x7fffffff;\n  // enable bit 32 as indicator\n  nsec |= 0x80000000;\n  // overwrite st_dev\n  buf->st_dev = nsec;\n#ifdef __APPLE__\n  eos_info(\"path=%s fxid=%08llx size=%lu mtime=%lu.%lu\", mNsPath.c_str(), mFileId,\n           (unsigned long) buf->st_size, buf->st_mtimespec.tv_sec,\n           buf->st_dev & 0x7ffffff);\n#else\n  eos_info(\"path=%s fxid=%08llx size=%lu mtime=%lu.%lu\", mNsPath.c_str(), mFileId,\n           (unsigned long) buf->st_size, buf->st_mtim.tv_sec, buf->st_dev & 0x7ffffff);\n#endif\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Sync file\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::sync()\n{\n  eos_debug(\"msg=\\\"sync request\\\", fxid=%08llx\", mFileId);\n\n  // TPC transfer\n  if (mTpcFlag == kTpcDstSetup) {\n    XrdSysMutexHelper scope_lock(&mTpcJobMutex);\n\n    if (mTpcState == kTpcIdle) {\n      eos_info(\"msg=\\\"tpc enabled -> 1st sync\\\"\");\n      mTpcThreadStatus = XrdSysThread::Run(&mTpcThread,\n                                           XrdFstOfsFile::StartDoTpcTransfer,\n                                           static_cast<void*>(this), XRDSYSTHREAD_HOLD,\n                                           \"TPC Transfer Thread\");\n\n      if (mTpcThreadStatus == 0) {\n        mTpcState = kTpcRun;\n        scope_lock.UnLock();\n        return SFS_OK;\n      } else {\n        eos_err(\"msg=\\\"failed to start TPC job thread\\\" fxid=%08llx\", mFileId);\n        mTpcState = kTpcDone;\n\n        if (mTpcInfo.Key) {\n          free(mTpcInfo.Key);\n        }\n\n        mTpcInfo.Key = strdup(\"Copy failed, could not start job\");\n        return mTpcInfo.Fail(&error, \"could not start job\", ECANCELED);\n      }\n    } else if (mTpcState == kTpcRun) {\n      if (mTpcInfo.SetCB(&error)) {\n        eos_info(\"msg=\\\"tpc running -> 2nd sync failed\\\" fxid=%08llx\", mFileId);\n        return SFS_ERROR;\n      }\n\n      // By default tell the client to wait 1 hour for the trasfer to complete,\n      // if we have information about the size of the file to be transferred\n      // then we compute the wait time for this operation using 5MB/s as the\n      // average transfer speed and use it only if it's bigger than 1 hour.\n      int cb_wait_sec = 3600;\n\n      if (mTpcFileSize) {\n        cb_wait_sec = eos::common::FileId::EstimateTpcTimeout\n                      (mTpcFileSize.load(), 5).count();\n\n        if (cb_wait_sec < 3600) {\n          cb_wait_sec = 3600;\n        }\n      }\n\n      eos_info(\"msg=\\\"tpc running -> 2nd sync\\\" fxid=%08llx tpc_fsize=%llu \"\n               \"wait_time=%isec\", mFileId, mTpcFileSize.load(), cb_wait_sec);\n      error.setErrCode(cb_wait_sec);\n      mTpcInfo.Engage();\n      return SFS_STARTED;\n    } else if (mTpcState == kTpcDone) {\n      eos_info(\"msg=\\\"tpc already finished\\\" fxid=%08llx retc=%i\\\"\",\n               mFileId, mTpcRetc);\n\n      if (mTpcRetc) {\n        error.setErrInfo(mTpcRetc, (mTpcInfo.Key ? mTpcInfo.Key : \"failed tpc\"));\n        return SFS_ERROR;\n      } else {\n        return SFS_OK;\n      }\n    } else {\n      eos_err(\"msg=\\\"unknown tpc state\\\" fxid=%08llx\", mFileId);\n      error.setErrInfo(EINVAL, \"unknown TPC state\");\n      return SFS_ERROR;\n    }\n  } else {\n    // Standard file sync\n    static bool async_sync_cfg = IsAsyncSyncConfigured();\n\n    if (!async_sync_cfg || DoSyncSync()) {\n      eos::common::Timing tm(\"sync\");\n      COMMONTIMING(\"begin\", &tm);\n      int rc = mLayout->Sync();\n      COMMONTIMING(\"end\", &tm);\n\n      if (tm.RealTime() > 2000) {\n        eos_warning(\"msg=\\\"slow sync operation\\\" fxid=%08llx\", mFileId);\n      }\n\n      return rc;\n    }\n\n    // Delegate sync call to a differet thread while the client is waiting for\n    // the callback (SFS_STARTED)\n    eos_info(\"msg=\\\"sync delegated to async thread\\\" fxid=%08llx path=\\\"%s\\\" \"\n             \"fst_path=\\\"%s\\\"\", mFileId, mNsPath.c_str(), mFstPath.c_str());\n    auto sync_cb = std::make_shared<XrdOucCallBack>();\n    sync_cb->Init(&error);\n    error.setErrInfo(600, \"delay client up to 10 min for sync call\");\n    gOFS.mAsyncOpThreadPool.PushTask<void>([&, sync_cb]() -> void {\n      // Make a local copy since the XrdFstOfsFile object is destroyed after\n      // the callback reply is called!\n      const auto fid = mFileId;\n      eos_info(\"msg=\\\"doing sync in async thread\\\", fxid=%08llx\", fid);\n      eos::common::Timing tm(\"sync\");\n      COMMONTIMING(\"begin\", &tm);\n      int rc = mLayout->Sync();\n      COMMONTIMING(\"end\", &tm);\n\n      if (tm.RealTime() > 2000)\n      {\n        eos_warning(\"msg=\\\"slow sync operation\\\" fxid=%08llx\", fid);\n      }\n\n      int reply_rc = sync_cb->Reply(rc, (rc ? error.getErrInfo() : 0),\n                                    (rc ? error.getErrText() : \"\"));\n\n      if (reply_rc)\n      {\n        eos_err(\"msg=\\\"sync callback reply failed\\\" fxid=%08llx\", fid);\n      }\n    });\n    return SFS_STARTED;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Truncate file\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::truncate(XrdSfsFileOffset fsize)\n{\n  eos_info(\"mOpenSize=%llu fsize=%llu \", mOpenSize, fsize);\n\n  if (mIsDevNull) {\n    return SFS_OK;\n  }\n\n  if (mChecksumGroup->HasChecksums() &&  fsize != mOpenSize &&\n      mWritePosition != fsize) {\n    mChecksumGroup->SetDirty();\n  }\n\n  int rc = mLayout->Truncate(fsize);\n\n  if (!rc) {\n    if (fsize != mOpenSize) {\n      mHasWrite = true;\n    }\n\n    mWritePosition = fsize;\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Close file\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::close()\n{\n  gettimeofday(&closeStart, &tz);\n\n  if (gOFS.mSimUnresponsive) {\n    eos_warning(\"msg=\\\"simulate unresponsive close, delay by 120s\\\" \"\n                \"fxid=%08llx\", mFileId);\n    std::this_thread::sleep_for(std::chrono::seconds(120));\n  }\n\n  // Reset the error.getErrInfo() value to 0 since this was hijacked by the\n  // XrdXrootdFile object to store the actual file descriptor corresponding to\n  // the current object. This was confusing when logging the error.getErrInfo()\n  // value at the end of the close.\n  error.setErrCode(0);\n  static bool async_close_cfg = IsAsyncCloseConfigured();\n\n  if (!async_close_cfg || DoSyncClose()) {\n    return _close();\n  }\n\n  // Delegate close to a different thread while the client is waiting for the\n  // callback (SFS_STARTED). This only happens for written files with size\n  // bigger than min size bytes.\n  eos_info(\"msg=\\\"close delegated to async thread\\\" fxid=%08llx \"\n           \"path=\\\"%s\\\" fst_path=\\\"%s\\\"\", mFileId, mNsPath.c_str(),\n           mFstPath.c_str());\n  // Create a close callback and put the client in waiting mode\n  auto close_cb = std::make_shared<XrdOucCallBack>();\n  close_cb->Init(&error);\n  error.setErrInfo(1800, \"delay client up to 30 minutes for close\");\n  gOFS.mAsyncOpThreadPool.PushTask<void>([&, close_cb]() -> void {\n    // Make a local copy since the XrdFstOfsFile object is destroyed after\n    // the callback reply is called!\n    const auto fid = mFileId;\n    eos_info(\"msg=\\\"doing close in the async thread\\\" fxid=%08llx\", fid);\n    int rc = _close();\n    // During Reply() we expect the enclosing XrdFstOfsFile to be destroyed,\n    // so we don't refer to anything captured by reference once done\n    int reply_rc = close_cb->Reply(rc, (rc ? error.getErrInfo() : 0),\n                                   (rc ? error.getErrText() : \"\"));\n\n    if (reply_rc == 0)\n    {\n      eos_err(\"msg=\\\"close callback reply failed\\\" fxid=%08llx\", fid);\n    }\n  });\n  return SFS_STARTED;\n}\n\n//------------------------------------------------------------------------------\n// Close file - internal method\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::_close()\n{\n  EPNAME(\"_close\");\n  int rc = 0; // return code\n\n  // Any close on a file opened in TPC mode invalidates tpc keys\n  if (!mTpcKey.empty()) {\n    TpcCleanup();\n  }\n\n  // This must be done after the TPC cleanup no to leak tpc keys\n  if (mIsDevNull) {\n    eos_debug(\"%s\", \"msg=\\\"closing sink file i.e. /dev/null\\\"\");\n    mClosed = true;\n    return SFS_OK;\n  }\n\n  if (!mOpened && mDelOnClose && !mClosed) {\n    eos_debug(\"msg=\\\"cleaning up eventual files created on failed open\\\" fxid=%08llx\",\n              mFileId);\n    mLayout->Remove();\n    mClosed = true;\n    return SFS_OK;\n  }\n\n  // Enter close logic only once, as we can get an explicit close or a close\n  // via the destructor\n  if (!mOpened || mClosed) {\n    eos_info(\"msg=\\\"close already done\\\" fxid=%08llx close_rc=%i\",\n             mFileId, mCloseRc);\n    mClosed = true;\n    return mCloseRc;\n  }\n\n  mCloseSize = mOpenSize;\n\n  if (mIsRW) {\n    if ((rc = _close_wr()) == 0) {\n      gOFS.QueueForMgmSync(*mFmd.get());\n    }\n  } else {\n    rc = _close_rd();\n  }\n\n  mClosed = true;\n\n  // Prepare a report and add to the report queue\n  if (mTpcFlag != kTpcSrcCanDo) {\n    // We don't want a report for the source tpc setup. The kTpcSrcRead\n    // stage actually uses the opaque info from kTpcSrcSetup and that's\n    // why we also generate a report at this stage.\n    XrdOucString report = \"\";\n    gettimeofday(&closeStop, &tz);\n    CloseTime();\n    gettimeofday(&closeTime, &tz);\n\n    // if we were kept in a cache (e.g. HttpHandlerFstFileCache)\n    // use the last time we put placed in the cache as the closetime\n    // (as this was the last time the user sent a request) for the\n    // purpose of the stats\n    if (cacheITime.tv_sec != 0) {\n      closeTime = cacheITime;\n      const unsigned long mus = timeToClose * 1000.0;\n      closeTime.tv_sec += (mus / 1000000);\n      closeTime.tv_usec += (mus % 1000000);\n\n      if (closeTime.tv_usec >= 1000000) {\n        closeTime.tv_sec++;\n        closeTime.tv_usec -= 1000000;\n      }\n    }\n\n    MakeReportEnv(report);\n    eos_static_info(\"msg=\\\"%s\\\"\", report.c_str());\n\n    if (eos::common::LayoutId::IsRain(mLid) && !mLayout->IsEntryServer()) {\n      // Non-entry RAIN stripes do not report any statistics\n    } else {\n      gOFS.ReportQueueMutex.Lock();\n      gOFS.ReportQueue.push(report);\n      gOFS.ReportQueueMutex.UnLock();\n    }\n  }\n\n  // CTA: Trigger an MGM event from the entry server\n  if ((rc == 0) && mLayout->IsEntryServer() &&\n      (mEventOnClose || mSyncEventOnClose)) {\n    rc = TriggerEventOnClose(mArchiveReqId);\n  }\n\n  // Mask close error for fuse, if the file has been removed already\n  if (mFusex && mFusexIsUnlinked) {\n    error.setErrCode(0);\n    rc = 0;\n  }\n\n  eos_info(\"msg=\\\"done close\\\" rc=%i errc=%d\", rc, error.getErrInfo());\n  mCloseRc = rc;\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Close file opened for read\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::_close_rd()\n{\n  EPNAME(\"close_rd\");\n  bool checksum_err = VerifyChecksum();\n\n  if (gOFS.mSimXsReadErr) {\n    eos_warning(\"msg=\\\"simulate read xs error\\\" fxid=%08llx\", mFileId);\n    checksum_err = true;\n  }\n\n  int close_rc = ModifiedWhileInUse();\n  close_rc |= mLayout->Close();\n\n  if (gOFS.mSimCloseErr) {\n    eos_warning(\"msg=\\\"simulate close error\\\" fxid=%08llx\", mFileId);\n    close_rc = SFS_ERROR;\n  }\n\n  gOFS.openedForReading.down(mFsId, mFileId);\n\n  if (checksum_err) {\n    if (!gOFS.mSimXsReadErr) {\n      auto xs = mChecksumGroup->GetDefault();\n\n      if (xs) {\n        const std::string computed_xs = xs->GetHexChecksum();\n        eos_warning(\"msg=\\\"updating diskchecksum after read checksum mismatch\\\" \"\n                    \"fxid=%08llx fsid=%u xs_computed=%s xs_local=%s\",\n                    mFileId, mFsId, computed_xs.c_str(),\n                    mFmd->mProtoFmd.diskchecksum().c_str());\n        mFmd->mProtoFmd.set_diskchecksum(computed_xs);\n        std::unique_ptr<eos::fst::FileIo> io(\n            eos::fst::FileIoPlugin::GetIoObject(mLayout->GetLocalReplicaPath(), this));\n        if (io->attrSet(\"user.eos.filecxerror\", \"1\")) {\n          eos_err(\"msg=\\\"unable to set extended attr <eos.filecxerror>\\\" \"\n                  \"fxid=%08llx errno=%d\",\n                  mFileId, errno);\n        }\n\n        if (!gOFS.mFmdHandler->Commit(mFmd.get())) {\n          eos_err(\"msg=\\\"failed to commit updated diskchecksum\\\" \"\n                  \"fxid=%08llx fsid=%u\",\n                  mFileId, mFsId);\n        }\n      }\n    }\n\n    int envlen = 0;\n    eos_crit(\"msg=\\\"file checksum error detected\\\" info=\\\"%s\\\"\",\n             mCapOpaque->Env(envlen));\n    return gOFS.Emsg(epname, error, EIO, \"verify checksum - checksum \"\n                     \"error fn=\", mNsPath.c_str());\n  }\n\n  return close_rc;\n}\n\n//------------------------------------------------------------------------------\n// Close file opened for write\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::_close_wr()\n{\n  EPNAME(\"close_wr\");\n  bool checksum_err = false; // full file checksum error\n  bool target_sz_err = false; // final target file size error\n  bool min_sz_err = false; // minimum file size policy error\n  bool queuing_err = false; // queuing error for archive\n  bool consistency_err = false; // consistency error at the MGM\n  bool atomic_overlap = false;\n  std::string queuing_msg;\n  int rc = 0;\n  // Check if the file close comes from a client disconnect e.g. the destructor\n  eos_info(\"viaDelete=%d writeDelete=%d mIsCreation=%d\",\n           viaDelete, mWrDelete, mIsCreation);\n  bool last_writer = (gOFS.openedForWriting.getUseCount(mFsId, mFileId) <= 1);\n\n  if ((viaDelete || mWrDelete) && !mFusex &&\n      (mIsCreation || mIsReplication || mIsInjection || mIsOCchunk)) {\n    if (last_writer) {\n      // It is closed by the destructor e.g. no proper close\n      // or the specified checksum does not match the computed one\n      if (viaDelete) {\n        eos_info(\"msg=\\\"(unpersist) deleting file\\\" reason=\\\"client disconnect\\\"\"\n                 \" fxid=%08llx fsid=%u\", mFileId, mFsId);\n      }\n\n      if (mWrDelete) {\n        eos_info(\"msg=\\\"(unpersist) deleting file\\\" reason=\\\"write/policy error\\\"\"\n                 \" fxid=%08llx fsid=%u\", mFileId, mFsId);\n      }\n\n      mDelOnClose = true;\n    } else {\n      eos_info(\"msg=\\\"(unpersist) suppress delete on close\\\" reason=\\\"several \"\n               \"writers\\\" fxid=%08llx fsid=%u\", mFileId, mFsId);\n    }\n  }\n\n  if (!mDelOnClose) {\n    // Check if this was a newly created file\n    if (mIsCreation) {\n      // If space allocated truncated to the real size of the file\n      if (eos::common::LayoutId::IsRain(mLayout->GetLayoutId())) {\n        // For RAIN only the entry server truncates, unless this is a recovery\n        if (mLayout->IsEntryServer() && !mRainReconstruct) {\n          eos_info(\"msg=\\\"truncate RAIN file\\\" offset=%llu\", mMaxOffsetWritten);\n          mLayout->Truncate(mMaxOffsetWritten);\n        }\n\n        //@note: there is a small probability here to have a race condition when\n        // computing the checksum for RAIN file in non-streaming mode. We should\n        // first collect all write replies and then re-read the file for the xs.\n      } else {\n        if (mMaxOffsetWritten > mOpenSize) {\n          // Check if we have to deallocate something for this file transaction\n          if (mBookingSize && (mBookingSize > mMaxOffsetWritten)) {\n            eos_debug(\"msg=\\\"deallocationg %llu bytes\\\" fxid=%08llx\",\n                      mBookingSize - mMaxOffsetWritten, mFileId);\n            mLayout->Truncate(mMaxOffsetWritten);\n            mLayout->Fdeallocate(mMaxOffsetWritten, mBookingSize);\n          }\n        }\n      }\n\n      if (!eos::common::LayoutId::IsRain(mLayout->GetLayoutId())) {\n        // Check target and minimum size policy only for non RAIN files\n        target_sz_err = (mTargetSize) ? (mTargetSize != mMaxOffsetWritten) : false;\n        min_sz_err = (mMinSize) ? ((off_t) mMaxOffsetWritten < mMinSize) : false;\n      }\n    }\n\n    checksum_err = VerifyChecksum();\n    eos_debug(\"checksum_err=%i target_sz_err=%i max_offset_written=%zu \"\n              \"target_size=%lli\", checksum_err, target_sz_err,\n              mMaxOffsetWritten, mTargetSize);\n\n    // Error simulation for checksum errors\n    if (gOFS.mSimXsWriteErr) {\n      eos_warning(\"msg=\\\"simulate write xs error\\\" fxid=%08llx\", mFileId);\n      checksum_err = true;\n\n      if (gOFS.mSimXsWriteErrDelay) {\n        std::this_thread::sleep_for(std::chrono::seconds(\n                                      gOFS.mSimXsWriteErrDelay.load()));\n      }\n    }\n\n    if (checksum_err || target_sz_err || min_sz_err) {\n      mDelOnClose = true;\n    }\n  }\n\n  // When doing RAIN reconstruction and we are at the entry server we have\n  // any read errors or we read less then the full file size it means the\n  // recovery failed. This can also be a side effect of a timeout.\n  if (mRainReconstruct && mLayout->IsEntryServer()) {\n    if (mHasReadErr || (rOffset != mRainSize)) {\n      eos_warning(\"msg=\\\"failed RAIN reconstruct trigger delete on close\\\" \"\n                  \"mHasReadErr=%i rd_off=%llu fsize=%llu fxid=%08llx\",\n                  mHasReadErr, rOffset, mRainSize, mFileId);\n      mDelOnClose = true;\n    }\n  }\n\n  if (!mDelOnClose && (mIsCreation || mHasWrite)) {\n    // Commit meta data\n    struct stat statinfo;\n\n    if ((rc = mLayout->Stat(&statinfo))) {\n      eos_err(\"msg=\\\"failed to stat file\\\" fxid=%08llx\", mFileId);\n      rc = gOFS.Emsg(epname, error, EIO, \"close - cannot stat closed layout\"\n                     \" to determine file size\", mNsPath.c_str());\n    } else {\n      // CTA: Attempt archive queueing if tape support enabled\n      if (mTapeEnabled && mSyncEventOnClose &&\n          mIsCreation && mLayout->IsEntryServer() &&\n          (mEventWorkflow != common::RETRIEVE_WRITTEN_WORKFLOW_NAME)) {\n        // Queueing error: queueing for archive failed\n        queuing_err = !QueueForArchiving(statinfo, queuing_msg, mArchiveReqId);\n        mDelOnClose = queuing_err;\n      }\n\n      if (!mDelOnClose) {\n        // Update size\n        mCloseSize = statinfo.st_size;\n\n        if (CommitToLocalFmd(statinfo)) {\n          eos_err(\"msg=\\\"failed to commit fmd info\\\" fxid=%08llx\", mFileId);\n          mDelOnClose = true;\n        } else {\n          // In case we are doing a RAIN reconstruct delay the commit to MGM\n          // until after we have the result of the CLOSE otherwise we risk\n          // dropping a good replica for a failed reconstruction which we\n          // can not get back.\n          if ((mRainReconstruct == false) && (rc = CommitToMgm())) {\n            if ((error.getErrInfo() == EIDRM) ||\n                (error.getErrInfo() == EBADE) ||\n                (error.getErrInfo() == EBADR) ||\n                (error.getErrInfo() == EREMCHG)) {\n              if (error.getErrInfo() == EIDRM) {\n                // File has been deleted in the meanwhile ... we can unlink\n                eos_err(\"msg=\\\"unlink file since already removed in the ns\\\"  \"\n                        \"fxid=%08llx path=\\\"%s\\\"\", mFileId, mNsPath.c_str());\n                mFusexIsUnlinked = true;\n              }\n\n              if (error.getErrInfo() == EBADE) {\n                eos_err(\"msg=\\\"unlink file since size does not match \"\n                        \"reference\\\" fxid=%08llx path=\\\"%s\\\"\",\n                        mFileId, mNsPath.c_str());\n                consistency_err = true;\n              }\n\n              if (error.getErrInfo() == EBADR) {\n                eos_err(\"msg=\\\"unlink file since checksum does not match \"\n                        \"reference\\\" fxid=%08llx path=\\\"%s\\\"\",\n                        mFileId, mNsPath.c_str());\n                consistency_err = true;\n              }\n\n              if (error.getErrInfo() == EREMCHG) {\n                eos_err(\"msg=\\\"unlinking fxid=%08llx path=%s - \"\n                        \"overlapping atomic upload - discarding this one\\\"\",\n                        mFmd->mProtoFmd.fid(), mNsPath.c_str());\n                atomic_overlap = true;\n              }\n\n              // Any of the above errors will trigger a delete on close\n              mDelOnClose = true;\n            } else {\n              eos_crit(\"msg=\\\"commit returned unknown error (maybe timeout), \"\n                       \"close transaction to keep file safe\\\" msg=\\\"%s\\\" rc=%d\",\n                       error.getErrText(), rc);\n            }\n          }\n        }\n      }\n    }\n  }\n\n  // Recompute our ETag\n  eos::calculateEtag(static_cast<bool>(mChecksumGroup), mFmd->mProtoFmd, mEtag);\n  int commit_rc = rc; // return of the commit/stat before the layout close\n\n  if (mSyncOnClose) {\n    eos_info(\"msg=\\\"syncing layout for iotype=csync\\\" fxid=%08llx\", mFileId);\n    rc |= mLayout->Sync();\n  }\n\n  int close_rc = mLayout->Close();\n\n  if (gOFS.mSimCloseErr) {\n    eos_warning(\"msg=\\\"simulate close error\\\" fxid=%08llx\", mFileId);\n    close_rc = SFS_ERROR;\n  }\n\n  rc |= close_rc;\n\n  if (close_rc) {\n    eos_info(\"msg=\\\"layout close failed\\\" rc=%i\", close_rc);\n\n    // For RAIN layouts if there is an error on close when writing then we\n    // delete the whole file. For RAIN reconstruction we clean the local stripe.\n    if (eos::common::LayoutId::IsRain(mLayout->GetLayoutId())) {\n      mDelOnClose = true;\n    } else {\n      // Some (remote) replica didn't make it through ... trigger an auto-repair\n      if (!mDelOnClose) {\n        mRepairOnClose = true;\n      }\n    }\n  }\n\n  // If target file system is in some non-operational mode, then abort commit\n  if (mIsCreation && !gOFS.Storage->IsFsOperational(mFsId)) {\n    eos_notice(\"msg=\\\"fail transfer since filesystem is in non-operational \"\n               \"state\\\" fxid=%08llx fsid=%u\", mFileId, mFsId);\n    mDelOnClose = true;\n  }\n\n  {\n    XrdSysMutexHelper scope_lock(gOFS.OpenFidMutex);\n\n    if ((rc == 0) && (mDelOnClose == false) &&\n        (mIsRW || mIsInjection || mIsOCchunk) &&\n        (gOFS.openedForWriting.getUseCount(mFsId, mFileId) > 1)) {\n      // Indicate that this file was closed properly and disable further\n      // delete on close for concurrent write operations\n      gOFS.WNoDeleteOnCloseFid[mFsId][mFileId] = true;\n    }\n\n    gOFS.openedForWriting.down(mFsId, mFileId);\n\n    if (mDelOnClose && gOFS.WNoDeleteOnCloseFid[mFsId].count(mFileId)) {\n      eos_notice(\"msg=\\\"prohibit delete on close since we had a previous \"\n                 \"successful close\\\" fxid=%08llx path=\\\"%s\\\"\",\n                 mFileId, mNsPath.c_str());\n      mDelOnClose = false;\n    }\n\n    if (gOFS.openedForWriting.isOpen(mFsId, mFileId) == false) {\n      // When the last writer is gone we can remove the prohibiting entry\n      gOFS.WNoDeleteOnCloseFid[mFsId].erase(mFileId);\n      gOFS.WNoDeleteOnCloseFid[mFsId].resize(0);\n    }\n  }\n\n  // Commit to MGM in case of rain reconstruction and not del on close\n  if (mRainReconstruct && mLayout->IsEntryServer() && !mDelOnClose) {\n    if ((rc = CommitToMgm())) {\n      if ((error.getErrInfo() == EIDRM) ||\n          (error.getErrInfo() == EBADE) ||\n          (error.getErrInfo() == EBADR) ||\n          (error.getErrInfo() == EREMCHG)) {\n        eos_err(\"msg=\\\"failed commit to MGM for RAIN reconstruct\\\" \"\n                \"fxid=%08llx\", mFileId);\n        mDelOnClose = true;\n      }\n    }\n  }\n\n  if (mDelOnClose && !mFusex &&\n      ( // Match a newly created simple replica/stripe\n        (mIsCreation && !mRainReconstruct) ||\n        // Match a reconstructed RAIN stripe so that we never delete stripes\n        // that are part of the reconstruction process but are not the target\n        // of the recovery!\n        (mIsCreation && mRainReconstruct) ||\n        mIsReplication || mIsInjection || mIsOCchunk)) {\n    rc = SFS_ERROR;\n    eos_err(\"msg=\\\"delete on close\\\" fxid=%08llx ns_path=\\\"%s\\\" \", mFileId,\n            mNsPath.c_str());\n    int retc = gOFS._rem(mNsPath.c_str(), error, 0, mCapOpaque.get(),\n                         mFstPath.c_str(), mFileId, mFsId, true);\n\n    if (retc) {\n      eos_debug(\"msg=\\\"local remove operation\\\" fxid=%08llx retc=%d\",\n                mFileId, retc);\n    }\n\n    // Unlink file or just current replica from the MGM\n    bool drop_all = false;\n\n    // If mDelOnClose at the gateway then we drop all replicas\n    if (mLayout->IsEntryServer() && mIsCreation &&\n        !mIsReplication && !mIsInjection &&\n        !mIsOCchunk && !mRainReconstruct) {\n      drop_all = true;\n    }\n\n    DropFromMgm(mFileId, (drop_all ? 0u : mFsId), mNsPath.c_str(),\n                mRdrManager.c_str());\n\n    if (min_sz_err) {\n      // Minimum size criteria not fullfilled\n      gOFS.Emsg(epname, error, EIO, \"store file - file has been cleaned \"\n                \"because it is smaller than the required minimum file size\"\n                \" in that directory\", mNsPath.c_str());\n      eos_warning(\"info=\\\"deleting on close\\\" fn=%s fstpath=%s reason=\"\n                  \"\\\"minimum file size criteria\\\"\", mNsPath.c_str(),\n                  mFstPath.c_str());\n    } else if (checksum_err) {\n      // Checksum error\n      gOFS.Emsg(epname, error, EIO, \"store file - file has been cleaned \"\n                \"because of a checksum error \", mNsPath.c_str());\n      eos_warning(\"info=\\\"deleting on close\\\" fn=%s fstpath=%s reason=\"\n                  \"\\\"checksum error\\\"\", mNsPath.c_str(), mFstPath.c_str());\n    } else if (writeErrorFlag == kOfsSimulatedIoError) {\n      // Simulated write error\n      gOFS.Emsg(epname, error, EIO, \"store file - file has been cleaned \"\n                \"because of a simulated IO error \", mNsPath.c_str());\n      eos_warning(\"info=\\\"deleting on close\\\" fn=%s fstpath=%s reason=\"\n                  \"\\\"simulated IO error\\\"\", mNsPath.c_str(), mFstPath.c_str());\n    } else if (writeErrorFlag == kOfsMaxSizeError) {\n      // Maximum size criteria not fullfilled\n      gOFS.Emsg(epname, error, EIO, \"store file - file has been cleaned \"\n                \"because you exceeded the maximum file size settings for \"\n                \"this namespace branch\", mNsPath.c_str());\n      eos_warning(\"info=\\\"deleting on close\\\" fn=%s fstpath=%s reason=\"\n                  \"\\\"maximum file size criteria\\\"\", mNsPath.c_str(),\n                  mFstPath.c_str());\n    } else if (writeErrorFlag == kOfsDiskFullError) {\n      // Disk full detected during write\n      gOFS.Emsg(epname, error, EIO, \"store file - file has been cleaned\"\n                \" because the target disk filesystem got full and you \"\n                \"didn't use reservation\", mNsPath.c_str());\n      eos_warning(\"info=\\\"deleting on close\\\" fn=%s fstpath=%s reason=\"\n                  \"\\\"filesystem full\\\"\", mNsPath.c_str(), mFstPath.c_str());\n    } else if (writeErrorFlag == kOfsIoError) {\n      // Generic IO error on the underlying device\n      gOFS.Emsg(epname, error, EIO, \"store file - file has been cleaned because\"\n                \" of an IO error during a write operation\", mNsPath.c_str());\n      eos_crit(\"info=\\\"deleting on close\\\" fn=%s fstpath=%s reason=\"\n               \"\\\"write IO error\\\"\", mNsPath.c_str(), mFstPath.c_str());\n    } else if (writeErrorFlag == kOfsFsRemovedError) {\n      // Filesystem has been unregistered\n      gOFS.Emsg(epname, error, EIO, \"store file - file has been cleaned because\"\n                \" the target filesystem has been unregistered\", mNsPath.c_str());\n      eos_crit(\"info=\\\"deleting on close\\\" fn=%s fstpath=%s reason=\"\n               \"\\\"FS removed\\\"\", mNsPath.c_str(), mFstPath.c_str());\n    } else if (target_sz_err) {\n      // Target size is different from the uploaded file size\n      gOFS.Emsg(epname, error, EIO, \"store file - file has been \"\n                \"cleaned because the stored file does not match \"\n                \"the provided targetsize\", mNsPath.c_str());\n      eos_warning(\"info=\\\"deleting on close\\\" fn=%s fstpath=%s reason=\"\n                  \"\\\"target size mismatch\\\"\", mCapOpaque->Get(\"mgm.path\"),\n                  mFstPath.c_str());\n    } else if (consistency_err) {\n      gOFS.Emsg(epname, error, EIO, \"store file - file has been \"\n                \"cleaned because the stored file does not match \"\n                \"the reference meta-data size/checksum\", mNsPath.c_str());\n      eos_crit(\"info=\\\"deleting on close\\\" fn=%s fstpath=%s reason=\"\n               \"\\\"meta-data size/checksum mismatch\\\"\", mNsPath.c_str(),\n               mFstPath.c_str());\n    } else if (atomic_overlap) {\n      gOFS.Emsg(epname, error, EIO, \"store file - file has been \"\n                \"cleaned because of an overlapping atomic upload \"\n                \"and we are not the last uploader\", mNsPath.c_str());\n      eos_crit(\"info=\\\"deleting on close\\\" fn=%s fstpath=%s reason=\"\n               \"\\\"suppressed atomic upload\\\"\", mNsPath.c_str(),\n               mFstPath.c_str());\n    } else if (queuing_err) {\n      std::string message =\n        SSTR(\"store file - file has been cleaned because of a queueing \"\n             << \"to archive error; reason=\\\"\" << queuing_msg << \"\\\"\");\n      gOFS.Emsg(epname, error, EIO, message.c_str(), mNsPath.c_str());\n      eos_warning(\"info=\\\"deleting on close\\\" fn=%s fstpath=%s reason=\\\"%s\\\"\",\n                  mNsPath.c_str(), mFstPath.c_str(), queuing_msg.c_str());\n    } else if (close_rc == SFS_ERROR) {\n      gOFS.Emsg(epname, error, EIO, \"store file - file has been \"\n                \"cleaned or recovery aborted because of an error on close\",\n                mNsPath.c_str());\n      eos_crit(\"info=\\\"deleting on close\\\" fn=%s fstpath=%s reason=\"\n               \"\\\"failed layout close\\\"\", mNsPath.c_str(), mFstPath.c_str());\n    } else {\n      // Client has disconnected and file is cleaned-up\n      gOFS.Emsg(epname, error, EIO, \"store file - file has been \"\n                \"cleaned because of a client disconnect\", mNsPath.c_str());\n      eos_warning(\"info=\\\"deleting on close\\\" fn=%s fstpath=%s \"\n                  \"reason=\\\"client disconnect\\\"\", mNsPath.c_str(),\n                  mFstPath.c_str());\n    }\n  }\n\n  if (mRepairOnClose && !mIsOCchunk) {\n    // Trigger adjust replica for the uploaded file\n    std::ostringstream oss;\n    oss << \"/?mgm.pcmd=adjustreplica&mgm.path=\" << mNsPath;\n    eos_info(\"msg=\\\"repair on close\\\" fxid=%08llx path=%s\", mFileId,\n             mNsPath.c_str());\n\n    if (gOFS.CallManager(&error, mNsPath.c_str(), mRdrManager.c_str(),\n                         oss.str())) {\n      eos_err(\"msg=\\\"failed adjustreplica\\\" fxid=%08llx path=%s\",\n              mFileId, mNsPath.c_str());\n      gOFS.Emsg(epname, error, EIO, \"create all replicas - uploaded file is at \"\n                \"risk - only one replica has been successfully stored for fn=\",\n                mNsPath.c_str());\n    } else {\n      eos_warning(\"msg=\\\"executed adjustreplica, file is at low risk due to \"\n                  \"missing replicas\\\" fxid=%08llx path=%s\", mFileId,\n                  mNsPath.c_str());\n\n      if (commit_rc == 0) {\n        // Reset the return code and clean error message\n        error.setErrInfo(0, \"\");\n        rc = 0;\n      }\n    }\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Implementation dependant commands\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::fctl(const int cmd, int alen, const char* args,\n                    const XrdSecEntity* client)\n{\n  eos_debug(\"cmd=%i, args=%s\", cmd, args);\n\n  if (cmd == SFS_FCTL_SPEC1) {\n    if (strncmp(args, \"delete\", alen) == 0) {\n      eos_warning(\"Setting deletion flag for file %s\", mFstPath.c_str());\n      // This indicates to delete the file during the close operation\n      viaDelete = true;\n      return SFS_OK;\n    } else if (strncmp(args, \"nochecksum\", alen) == 0) {\n      int retc = SFS_OK;\n      eos_warning(\"Setting nochecksum flag for file %s\", mFstPath.c_str());\n      mChecksumGroup->Clear();\n\n      // Propagate command to all the replicas/stripes\n      if (mLayout) {\n        retc = mLayout->Fctl(std::string(args), client);\n      }\n\n      return retc;\n    }\n  }\n\n  error.setErrInfo(ENOTSUP, \"fctl command not supported\");\n  return SFS_ERROR;\n}\n\n//------------------------------------------------------------------------------\n// Low-level open calling the default XrdOfs plugin\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::openofs(const char* path,\n                       XrdSfsFileOpenMode open_mode,\n                       mode_t create_mode,\n                       const XrdSecEntity* client,\n                       const char* opaque)\n{\n  int retc = 0;\n\n  while ((retc = XrdOfsFile::open(path, open_mode, create_mode, client,\n                                  opaque)) > 0) {\n    eos_static_notice(\"msg\\\"xrootd-lock-table busy - snoozing & retry\\\" \"\n                      \"delay=%d errno=%d\", retc, errno);\n    std::this_thread::sleep_for(std::chrono::seconds(retc));\n  }\n\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Low-level read calling the default XrdOfs plugin\n//------------------------------------------------------------------------------\nXrdSfsXferSize\nXrdFstOfsFile::readofs(XrdSfsFileOffset fileOffset, char* buffer,\n                       XrdSfsXferSize buffer_size)\n{\n  //  EPNAME(\"read\");\n  gettimeofday(&cTime, &tz);\n  rCalls++;\n\n  if (!getenv(\"EOS_FST_NO_IOPRIORITY\")) {\n    if (ioprio_begin(IOPRIO_WHO_PROCESS, IOPRIO_PRIO_VALUE(mIoPriorityClass,\n                     mIoPriorityValue), t_iopriority)) {\n      if (!mIoPriorityErrorReported) {\n        eos_warning(\"failed to set IO priority to %d:%d - errno=%d\\n\", mIoPriorityClass,\n                    mIoPriorityValue, errno);\n      }\n    }\n  }\n\n  int rc = XrdOfsFile::read(fileOffset, buffer, buffer_size);\n  eos_debug(\"read %llu %llu %i rc=%d\", this, fileOffset, buffer_size, rc);\n\n  if (!getenv(\"EOS_FST_NO_IOPRIORITY\")) {\n    t_iopriority = ioprio_end(IOPRIO_WHO_PROCESS,\n                              IOPRIO_PRIO_VALUE(mIoPriorityClass, mIoPriorityValue));\n  }\n\n  if (gOFS.mSimIoReadErr) {\n    if ((gOFS.mSimErrIoReadOff == 0) ||\n        (gOFS.mSimErrIoReadOff <= (uint64_t)fileOffset)) {\n      eos_warning(\"msg=\\\"simulate read IO error\\\" fxid=%08llx\", mFileId);\n      return gOFS.Emsg(\"readofs\", error, EIO, \"read file - simulated IO error fn=\",\n                       mNsPath.c_str());\n    }\n  }\n\n  if (mFsId) {\n    if (!gOFS.Storage->mFsMap.count(mFsId)) {\n      return gOFS.Emsg(\"readeofs\", error, EBADF,\n                       \"read file - filesystem has been unregistered\");\n    }\n  }\n\n  // Account seeks for monitoring\n  if (rOffset != static_cast<unsigned long long>(fileOffset)) {\n    if (rOffset < static_cast<unsigned long long>(fileOffset)) {\n      nFwdSeeks++;\n      sFwdBytes += (fileOffset - rOffset);\n    } else {\n      nBwdSeeks++;\n      sBwdBytes += (rOffset - fileOffset);\n    }\n\n    if ((rOffset + (EOS_FSTOFS_LARGE_SEEKS)) < (static_cast<unsigned long long>\n        (fileOffset))) {\n      sXlFwdBytes += (fileOffset - rOffset);\n      nXlFwdSeeks++;\n    }\n\n    if ((static_cast<unsigned long long>(rOffset) > (EOS_FSTOFS_LARGE_SEEKS)) &&\n        (rOffset - (EOS_FSTOFS_LARGE_SEEKS)) > (static_cast<unsigned long long>\n            (fileOffset))) {\n      sXlBwdBytes += (rOffset - fileOffset);\n      nXlBwdSeeks++;\n    }\n  }\n\n  gettimeofday(&lrTime, &tz);\n  AddReadTime();\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Low-level vector read calling the default XrdOfs plugin\n//------------------------------------------------------------------------------\nXrdSfsXferSize\nXrdFstOfsFile::readvofs(XrdOucIOVec* readV, uint32_t readCount)\n{\n  eos_debug(\"read count=%i\", readCount);\n  gettimeofday(&cTime, &tz);\n  XrdSfsXferSize sz = XrdOfsFile::readv(readV, readCount);\n  gettimeofday(&lrvTime, &tz);\n  AddReadVTime();\n\n  // Collect monitoring info only if sz is > 0\n  if (sz > 0) {\n    XrdSysMutexHelper scope_lock(vecMutex);\n\n    for (uint32_t i = 0; i < readCount; ++i) {\n      monReadSingleBytes.push_back(readV[i].size);\n    }\n\n    monReadvBytes.push_back(sz);\n    monReadvCount.push_back(readCount);\n  }\n\n  return sz;\n}\n\n//------------------------------------------------------------------------------\n// Low-level write calling the default XrdOfs plugin\n//------------------------------------------------------------------------------\nXrdSfsXferSize\nXrdFstOfsFile::writeofs(XrdSfsFileOffset fileOffset, const char* buffer,\n                        XrdSfsXferSize buffer_size)\n{\n  if (gOFS.mSimIoWriteErr) {\n    if ((gOFS.mSimErrIoWriteOff == 0) ||\n        (gOFS.mSimErrIoWriteOff <= (uint64_t)fileOffset)) {\n      writeErrorFlag = kOfsSimulatedIoError;\n      eos_warning(\"msg=\\\"simulate write IO error\\\" fxid=%08llx\", mFileId);\n      return gOFS.Emsg(\"writeofs\", error, EIO, \"write file - simulated IO error fn=\",\n                       mNsPath.c_str());\n    }\n  }\n\n  if (mFsId) {\n    if ((mTargetSize && (mTargetSize == mBookingSize)) ||\n        (mBookingSize >= fileOffset + buffer_size)) {\n      // Space has been successfully pre-allocated, let client write\n    } else {\n      // Check if the file system is full\n      bool isfull = false;\n      {\n        XrdSysMutexHelper lock(gOFS.Storage->mFsFullMapMutex);\n        isfull = gOFS.Storage->mFsFullMap[mFsId];\n      }\n\n      if (isfull) {\n        writeErrorFlag = kOfsDiskFullError;\n        return gOFS.Emsg(\"writeofs\", error, ENOSPC, \"write file - disk space \"\n                         \"(headroom) exceeded fn=\", mCapOpaque ?\n                         (mCapOpaque->Get(\"mgm.path\") ? mCapOpaque->Get(\"mgm.path\") :\n                          FName()) : FName());\n      }\n    }\n\n    // check if the filesystem was unregistered in the meanwhile\n    eos::common::RWMutexReadLock lock(gOFS.Storage->mFsMutex);\n\n    if (!gOFS.Storage->mFsMap.count(mFsId)) {\n      writeErrorFlag = kOfsFsRemovedError;\n      return gOFS.Emsg(\"writeofs\", error, EBADF,\n                       \"write file - filesystem has been unregistered\");\n    }\n  }\n\n  if (mMaxSize) {\n    // Check that the user didn't exceed the maximum file size policy\n    if ((fileOffset + buffer_size) > mMaxSize) {\n      writeErrorFlag = kOfsMaxSizeError;\n      return gOFS.Emsg(\"writeofs\", error, ENOSPC, \"write file - your file \"\n                       \"exceeds the maximum file size setting of bytes<=\",\n                       mCapOpaque ? (mCapOpaque->Get(\"mgm.maxsize\") ?\n                                     mCapOpaque->Get(\"mgm.maxsize\") : \"<undef>\") : \"undef\");\n    }\n  }\n\n  gettimeofday(&cTime, &tz);\n  wCalls++;\n  int rc;\n\n  if (!getenv(\"EOS_FST_NO_IOPRIORITY\")) {\n    if (ioprio_begin(IOPRIO_WHO_PROCESS, IOPRIO_PRIO_VALUE(mIoPriorityClass,\n                     mIoPriorityValue), t_iopriority)) {\n      if (!mIoPriorityErrorReported) {\n        eos_warning(\"failed to set IO priority to %d:%d - errno=%d\\n\", mIoPriorityClass,\n                    mIoPriorityValue, errno);\n        mIoPriorityErrorReported = true;\n      }\n    }\n  }\n\n  if (gOFS.mSimDiskWriting) {\n    // Simulate disk writing by only truncating\n    eos_warning(\"msg=\\\"simulate disk writing - do truncate\\\" fxid=%08llx\",\n                mFileId);\n    XrdFstOfsFile::truncateofs(fileOffset);\n    rc = buffer_size;\n  } else {\n    rc = XrdOfsFile::write(fileOffset, buffer, buffer_size);\n  }\n\n  if (!getenv(\"EOS_FST_NO_IOPRIORITY\")) {\n    t_iopriority = ioprio_end(IOPRIO_WHO_PROCESS,\n                              IOPRIO_PRIO_VALUE(mIoPriorityClass, mIoPriorityValue));\n  }\n\n  if (rc != buffer_size) {\n    // Tag an io error\n    writeErrorFlag = kOfsIoError;\n  }\n\n  // Account seeks for monitoring\n  if (wOffset != static_cast<unsigned long long>(fileOffset)) {\n    if (wOffset < static_cast<unsigned long long>(fileOffset)) {\n      nFwdSeeks++;\n      sFwdBytes += (fileOffset - wOffset);\n    } else {\n      nBwdSeeks++;\n      sBwdBytes += (wOffset - fileOffset);\n    }\n\n    if ((wOffset + (EOS_FSTOFS_LARGE_SEEKS)) < (static_cast<unsigned long long>\n        (fileOffset))) {\n      sXlFwdBytes += (fileOffset - wOffset);\n      nXlFwdSeeks++;\n    }\n\n    if ((static_cast<unsigned long long>(wOffset) > (EOS_FSTOFS_LARGE_SEEKS)) &&\n        (wOffset - (EOS_FSTOFS_LARGE_SEEKS)) > (static_cast<unsigned long long>\n            (fileOffset))) {\n      sXlBwdBytes += (wOffset - fileOffset);\n      nXlBwdSeeks++;\n    }\n  }\n\n  if (rc > 0) {\n    wOffset = fileOffset + rc;\n  }\n\n  gettimeofday(&lwTime, &tz);\n  AddWriteTime();\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Low-level sync calling the default XrdOfs plugin\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::syncofs()\n{\n  return XrdOfsFile::sync();\n}\n\n//------------------------------------------------------------------------------\n// Low-level truncate calling the default XrdOfs plugin\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::truncateofs(XrdSfsFileOffset fileOffset)\n{\n  // Truncation moves the max offset written\n  eos_debug(\"value=%llu\", (unsigned long long) fileOffset);\n  mMaxOffsetWritten = fileOffset;\n  struct stat buf;\n\n  // stat the current file size\n  // if the file has the proper size we don't truncate\n  if (!::stat(mFstPath.c_str(), &buf)) {\n    // if the file has the proper size we don't truncate\n    if (buf.st_size == fileOffset) {\n      return SFS_OK;\n    }\n  }\n\n  return XrdOfsFile::truncate(fileOffset);\n}\n\n//------------------------------------------------------------------------------\n// Low-level close calling the default XrdOfs plugin\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::closeofs()\n{\n  return XrdOfsFile::close();\n}\n\n//------------------------------------------------------------------------------\n// Return FMD checksum\n//------------------------------------------------------------------------------\nstd::string\nXrdFstOfsFile::GetFmdChecksum() const\n{\n  if (mFmd) {\n    return mFmd->mProtoFmd.checksum();\n  } else {\n    return std::string();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Verify if a TPC key is still valid\n//------------------------------------------------------------------------------\nbool\nXrdFstOfsFile::TpcValid() const\n{\n  XrdSysMutexHelper scope_lock(gOFS.TpcMapMutex);\n\n  if (mTpcKey.length() &&  gOFS.TpcMap[mIsTpcDst].count(mTpcKey)) {\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Process open opaque information - this can come directly from the client\n// or from the MGM redirection and it's not encrypted but sent in plain\n// text in the URL\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::ProcessOpenOpaque()\n{\n  using namespace std::chrono;\n  EPNAME(\"open\");\n\n  if (!mOpenOpaque) {\n    eos_warning(\"msg=\\\"no open opaque info to process\\\"\");\n    return SFS_OK;\n  }\n\n  char* val = nullptr;\n\n  // Handle various tags which are sent in plain text e.g. mgm.etag\n  // Extract ETag from the redirection URL if available\n  if ((val = mOpenOpaque->Get(\"mgm.etag\"))) {\n    mEtag = val;\n  }\n\n  // mgm.mtime=0 we set the mtime externaly. This indicates that during commit,\n  // it should not update the mtime as in the case of FUSE clients which will\n  // call utimes.\n  if ((val = mOpenOpaque->Get(\"mgm.mtime\"))) {\n    time_t mtime = (time_t)strtoull(val, 0, 10);\n\n    if (mtime == 0) {\n      mForcedMtime = 0;\n      mForcedMtime_ms = 0;\n    } else {\n      mForcedMtime = mtime;\n      mForcedMtime_ms = 0;\n    }\n  }\n\n  // mgm.fusex=1 - Suppress the file close broadcast to the fusex network\n  // during the file close\n  if ((val = mOpenOpaque->Get(\"mgm.fusex\"))) {\n    mFusex = true;\n  }\n\n  // Handle workflow events\n  if ((val = mOpenOpaque->Get(\"mgm.event\"))) {\n    std::string event = val;\n\n    if (event == \"closew\") {\n      mEventOnClose = true;\n    } else if (event == \"sync::closew\") {\n      mSyncEventOnClose = true;\n    }\n\n    if ((val = mOpenOpaque->Get(\"mgm.workflow\"))) {\n      mEventWorkflow = val;\n    }\n\n    val = mOpenOpaque->Get(\"mgm.instance\");\n    mEventInstance = val ? val : \"\";\n    val = mOpenOpaque->Get(\"mgm.owner_uid\");\n    mEventOwnerUid = val ? std::stoul(val) : 99;\n    val = mOpenOpaque->Get(\"mgm.owner_gid\");\n    mEventOwnerGid = val ? std::stoul(val) : 99;\n    val = mOpenOpaque->Get(\"mgm.requestor\");\n    mEventRequestor = val ? val : \"\";\n    val = mOpenOpaque->Get(\"mgm.requestorgroup\");\n    mEventRequestorGroup = val ? val : \"\";\n    val = mOpenOpaque->Get(\"mgm.attributes\");\n    mEventAttributes = val ? val : \"\";\n  }\n\n  if ((val = mOpenOpaque->Get(\"eos.injection\"))) {\n    mIsInjection = true;\n  }\n\n  // enable round-robin scheduling per application/fsid on request\n  if ((val = mOpenOpaque->Get(\"eos.schedule\"))) {\n    mAppRR = mSecMap[\"app\"];\n  }\n\n  // Tag as an OC chunk upload\n  if (eos::common::OwnCloud::isChunkUpload(*mOpenOpaque.get())) {\n    mIsOCchunk = true;\n  }\n\n  if ((val = mOpenOpaque->Get(\"x-upload-range\"))) {\n    // For partial range uploads via HTTP we run the same business logic as\n    // for OC chunk uploads\n    mIsOCchunk = true;\n  }\n\n  // Check if transfer is still valid to avoid any open replays\n  if ((val = mOpenOpaque->Get(\"fst.valid\"))) {\n    try {\n      std::string sval = val;\n      int64_t valid_sec = std::stoll(sval);\n      auto now = system_clock::now();\n      auto now_sec = time_point_cast<seconds>(now).time_since_epoch().count();\n\n      if (valid_sec < now_sec) {\n        eos_err(\"msg=\\\"fst validity expired, avoid open replay\\\"\");\n        return gOFS.Emsg(epname, error, EINVAL, \"open - fst validity expired\",\n                         mNsPath.c_str());\n      }\n    } catch (...) {\n      // ignore\n    }\n  }\n\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Process capability opaque information - this is encrypted information sent\n// by the MGM to the FST\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::ProcessCapOpaque(bool& is_repair_read,\n                                eos::common::VirtualIdentity& vid)\n{\n  EPNAME(\"open\");\n\n  if (!mCapOpaque) {\n    eos_warning(\"msg=\\\"no cap opaque info to process\\\"\");\n    return SFS_OK;\n  }\n\n  int envlen {0};\n  XrdOucString maskOpaque = mCapOpaque->Env(envlen);\n  eos::common::StringConversion::MaskTag(maskOpaque, \"mgm.obfuscate.key\");\n  eos::common::StringConversion::MaskTag(maskOpaque, \"mgm.encryption.key\");\n  eos_info(\"capability=%s\", maskOpaque.c_str());\n  char* val = nullptr;\n  const char* hexfid = 0;\n  const char* slid = 0;\n  const char* secinfo = 0;\n  const char* scid = 0;\n  const char* smanager = 0;\n\n  // Determine whether or not support for tape is enabled in the MGM\n  if (mCapOpaque->Get(\"tapeenabled\")) {\n    mTapeEnabled = true;\n  }\n\n  // Handle file id info\n  if (!(hexfid = mCapOpaque->Get(\"mgm.fid\"))) {\n    return gOFS.Emsg(epname, error, EINVAL, \"open - no file id in capability\",\n                     mNsPath.c_str());\n  }\n\n  mFileId = eos::common::FileId::Hex2Fid(hexfid);\n\n  // Handle security info\n  if (!(secinfo = mCapOpaque->Get(\"mgm.sec\"))) {\n    return gOFS.Emsg(epname, error, EINVAL,\n                     \"open - no security information in capability\", mNsPath.c_str());\n  } else {\n    mSecString = secinfo;\n    mSecMap = eos::common::SecEntity::KeyToMap(std::string(secinfo));\n  }\n\n  // Handle min size value\n  if ((val = mCapOpaque->Get(\"mgm.minsize\"))) {\n    errno = 0;\n    mMinSize = strtoull(val, 0, 10);\n\n    if (errno) {\n      eos_err(\"illegal minimum file size specified <%s>- restricting to 1 byte\", val);\n      mMinSize = 1;\n    }\n  } else {\n    mMinSize = 0;\n  }\n\n  // Handle max size value\n  if ((val = mCapOpaque->Get(\"mgm.maxsize\"))) {\n    errno = 0;\n    mMaxSize = strtoull(val, 0, 10);\n\n    if (errno) {\n      eos_err(\"illegal maximum file size specified <%s>- restricting to 1 byte\", val);\n      mMaxSize = 1;\n    }\n  } else {\n    mMaxSize = 0;\n  }\n\n  // Handle repair read flag\n  if ((val = mCapOpaque->Get(\"mgm.repairread\"))) {\n    is_repair_read = true;\n  }\n\n  // Handle layout id\n  if (!(slid = mCapOpaque->Get(\"mgm.lid\"))) {\n    return gOFS.Emsg(epname, error, EINVAL, \"open - no layout id in capability\",\n                     mNsPath.c_str());\n  }\n\n  mLid = atoi(slid);\n\n  // Handle container id\n  if (!(scid = mCapOpaque->Get(\"mgm.cid\"))) {\n    return gOFS.Emsg(epname, error, EINVAL, \"open - no container id in capability\",\n                     mNsPath.c_str());\n  }\n\n  mCid = strtoull(scid, 0, 10);\n\n  // Handle the redirect manager\n  if (!(smanager = mCapOpaque->Get(\"mgm.manager\"))) {\n    return gOFS.Emsg(epname, error, EINVAL, \"open - no manager name in capability\",\n                     mNsPath.c_str());\n  }\n\n  mRdrManager = smanager;\n  {\n    // evt. update the shared hash manager entry\n    XrdSysMutexHelper lock(gConfig.Mutex);\n    XrdOucString ConfigManager = gConfig.Manager;\n\n    if (ConfigManager != mRdrManager) {\n      eos_warning(\"msg=\\\"MGM master seems to have changed - adjusting global \"\n                  \"config\\\" old-manager=\\\"%s\\\" new-manager=\\\"%s\\\"\",\n                  ConfigManager.c_str(), mRdrManager.c_str());\n      gConfig.Manager = mRdrManager;\n    }\n  }\n  // Handle virtual identity\n  vid = eos::common::VirtualIdentity::Nobody();\n  vid.app = mSecMap[\"app\"];\n\n  if ((val = mCapOpaque->Get(\"mgm.ruid\"))) {\n    vid.uid = atoi(val);\n  } else {\n    return gOFS.Emsg(epname, error, EINVAL, \"open - sec ruid missing\",\n                     mNsPath.c_str());\n  }\n\n  if ((val = mCapOpaque->Get(\"mgm.rgid\"))) {\n    vid.gid = atoi(val);\n  } else {\n    return gOFS.Emsg(epname, error, EINVAL, \"open - sec rgid missing\",\n                     mNsPath.c_str());\n  }\n\n  if ((val = mCapOpaque->Get(\"mgm.uid\"))) {\n    vid.allowed_uids.clear();\n    vid.allowed_uids.insert(atoi(val));\n  } else {\n    return gOFS.Emsg(epname, error, EINVAL, \"open - sec uid missing\",\n                     mNsPath.c_str());\n  }\n\n  if ((val = mCapOpaque->Get(\"mgm.gid\"))) {\n    vid.allowed_gids.clear();\n    vid.allowed_gids.insert(atoi(val));\n  } else {\n    return gOFS.Emsg(epname, error, EINVAL, \"open - sec gid missing\",\n                     mNsPath.c_str());\n  }\n\n  // enable round-robin scheduling per application/fsid on request\n  if ((val = mCapOpaque->Get(\"mgm.schedule\"))) {\n    mAppRR = mSecMap[\"app\"];\n  }\n\n  std::string obfuscation_key;\n  std::string encryption_key;\n\n  // handle obfuscation keys\n  if ((val = mCapOpaque->Get(\"mgm.obfuscate.key\"))) {\n    obfuscation_key = val;\n  }\n\n  // handl encryption keys\n  if ((val = mCapOpaque->Get(\"mgm.encryption.key\"))) {\n    encryption_key = val;\n  }\n\n  mHmac.set(obfuscation_key, encryption_key);\n  SetLogId(logId, vid, mTident.c_str());\n  return SFS_OK;\n}\n\n//----------------------------------------------------------------------------\n// Process mixed opaque information - decisions that need to be taken based\n// on both the encrypted and un-encrypted opaque info\n//----------------------------------------------------------------------------\nint\nXrdFstOfsFile::ProcessMixedOpaque()\n{\n  EPNAME(\"open\");\n  using eos::common::FileId;\n  // Handle checksum request\n  std::string opaqueCheckSum, opaqueCheckSumTypeReq;\n  char* val = nullptr;\n\n  if (mOpenOpaque == nullptr || mCapOpaque == nullptr) {\n    eos_warning(\"msg=\\\"open or cap opaque are empty\\\"\");\n    return SFS_OK;\n  }\n\n  if ((val = mOpenOpaque->Get(\"mgm.checksum\"))) {\n    opaqueCheckSum = val;\n  }\n\n  std::unique_ptr<eos::fst::CheckSum> mCheckSum;\n\n  // Call the checksum factory function with the selected layout\n  if (opaqueCheckSum != \"ignore\") {\n    if (!opaqueCheckSum.empty() &&\n        (val = mOpenOpaque->Get(\"mgm.checksumtypereq\"))) {\n      // the mgm.checksum has been set and the user requested a checksum type\n      opaqueCheckSumTypeReq = val;\n      mCheckSum = eos::fst::ChecksumPlugins::GetXsObj(opaqueCheckSumTypeReq);\n\n      if (!mCheckSum) {\n        // The checksum type was requested but does not exist, return an error to the client instead of copying the file without the checksum check\n        return gOFS.Emsg(epname, error, EINVAL,\n                         \"open - the checksum type requested does not exist\",\n                         opaqueCheckSumTypeReq.c_str());\n      }\n    } else {\n      mCheckSum = eos::fst::ChecksumPlugins::GetChecksumObject(mLid);\n    }\n\n    eos_debug(\"msg=\\\"checksum requested\\\" xs_ptr=%p lid=%u mgm.checksum=\\\"%s\\\" mgm.checksumtypereq=\\\"%s\\\"\",\n              mCheckSum.get(), mLid, opaqueCheckSum.c_str(), opaqueCheckSumTypeReq.c_str());\n    // set the default checksum, i.e. the one specified in the layout\n    mChecksumGroup->SetDefault(std::move(mCheckSum),\n                               static_cast<eos::common::LayoutId::eChecksum>\n                               (eos::common::LayoutId::GetChecksum(mLid)));\n    eos_debug(\"msg=\\\"checksum requested\\\" xs_ptr=%p lid=%u mgm.checksum=\\\"%s\\\"\",\n              mChecksumGroup->GetDefault(), mLid, opaqueCheckSum.c_str());\n  }\n\n  // Handle file system id and local prefix - If we open a replica we have to\n  // take the right filesystem id and filesystem prefix for that replica\n  const char* sfsid = 0;\n\n  if (!(sfsid = mCapOpaque->Get(\"mgm.fsid\"))) {\n    return gOFS.Emsg(epname, error, EINVAL,\n                     \"open - no file system id in capability\", mNsPath.c_str());\n  }\n\n  if (mOpenOpaque->Get(\"mgm.replicaindex\")) {\n    XrdOucString replicafsidtag = \"mgm.fsid\";\n    replicafsidtag += (int) atoi(mOpenOpaque->Get(\"mgm.replicaindex\"));\n\n    if (mCapOpaque->Get(replicafsidtag.c_str())) {\n      sfsid = mCapOpaque->Get(replicafsidtag.c_str());\n    }\n  }\n\n  // Extract the local path prefix from the broadcasted configuration\n  if (mOpenOpaque->Get(\"mgm.fsprefix\")) {\n    mLocalPrefix = mOpenOpaque->Get(\"mgm.fsprefix\");\n    mLocalPrefix.replace(\"#COL#\", \":\");\n  } else {\n    // Extract the local path prefix from the broadcasted configuration!\n    mFsId = atoi(sfsid ? sfsid : \"0\");\n    mLocalPrefix = gOFS.Storage->GetStoragePath(mFsId).c_str();\n  }\n\n  // @note: the mLocalPrefix implementation does not work for gateway machines\n  if (!mLocalPrefix.length()) {\n    return gOFS.Emsg(epname, error, EINVAL, \"open - cannot determine the prefix\"\n                     \" path to use for the given filesystem id\", mNsPath.c_str());\n  }\n\n  mFsId = atoi(sfsid);\n  mFstPath = FileId::FidPrefix2FullPath(FileId::Fid2Hex(mFileId).c_str(),\n                                        mLocalPrefix.c_str());\n  return SFS_OK;\n}\n\nvoid XrdFstOfsFile::ProcessAltXsRequest()\n{\n  bool compute = false;\n\n  if (mCapOpaque->Get(\"mgm.altxs.compute\")) {\n    compute = std::strncmp(mCapOpaque->Get(\"mgm.altxs.compute\"), \"1\", 1) == 0;\n  }\n\n  std::string altStr = mCapOpaque->Get(\"mgm.altxs\") ? mCapOpaque->Get(\"mgm.altxs\")\n                       : \"0\";\n\n  if (!altStr.empty()) {\n    auto io = mLayout->GetFileIo();\n    io->attrSet(\"user.eos.altxs\", altStr);\n    io->attrSet(\"user.eos.altxs_sync\", std::to_string(openTime.tv_sec));\n\n    if (compute) {\n      io->attrSet(\"user.eos.altxs_mgm\", altStr);\n      std::vector<std::string> alternatives;\n      eos::common::StringConversion::Tokenize(altStr, alternatives, \",\");\n\n      for (const auto& xs : alternatives) {\n        if (xs.empty()) {\n          continue;\n        }\n\n        mChecksumGroup->AddAlternative(eos::fst::ChecksumPlugins::GetXsObj(xs), xs);\n        eos_debug(\"msg=\\\"alternative checksum requested\\\" name=\\\"%s\\\"\", xs.c_str());\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Process TPC (third-party copy) opaque information i.e handle tags like\n// tpc.key, tpc.dst, tpc.stage etc.\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::ProcessTpcOpaque(std::string& opaque, const XrdSecEntity* client)\n{\n  EPNAME(__FUNCTION__);\n  mIsHttp = (client->tident ? (strncmp(client->tident, \"http\", 4) == 0) : false);\n  eos::common::StringConversion::ReplaceStringInPlace(opaque, \"?\", \"&\");\n  eos::common::StringConversion::ReplaceStringInPlace(opaque, \"&&\", \"&\");\n  XrdOucEnv env(opaque.c_str());\n  std::string tpc_stage = env.Get(\"tpc.stage\") ? env.Get(\"tpc.stage\") : \"\";\n  std::string tpc_key = env.Get(\"tpc.key\") ? env.Get(\"tpc.key\") : \"\";\n  std::string tpc_src = env.Get(\"tpc.src\") ? env.Get(\"tpc.src\") : \"\";\n  std::string tpc_dst = env.Get(\"tpc.dst\") ? env.Get(\"tpc.dst\") : \"\";\n  std::string tpc_org = env.Get(\"tpc.org\") ? env.Get(\"tpc.org\") : \"\";\n  std::string tpc_lfn = env.Get(\"tpc.lfn\") ? env.Get(\"tpc.lfn\") : \"\";\n  // Remove any TPC flags from now on\n  FilterTagsInPlace(opaque, {\"tpc.stage\", \"tpc.key\", \"tpc.src\", \"tpc.dst\",\n                             \"tpc.org\", \"tpc.lfn\"\n                            });\n\n  // Determine the TPC step that we are in\n  if (tpc_stage == \"placement\") {\n    mTpcFlag = kTpcSrcCanDo;\n    mIsTpcDst = false;\n  } else if ((tpc_stage == \"copy\") && tpc_key.length() && tpc_dst.length()) {\n    mTpcFlag = kTpcSrcSetup;\n    mIsTpcDst = false;\n  } else if ((tpc_stage == \"copy\") && tpc_key.length() && tpc_src.length()) {\n    mTpcFlag = kTpcDstSetup;\n    mIsTpcDst = true;\n  } else if (tpc_key.length() && tpc_org.length()) {\n    // @note(esindril) The above condition should be as follows but for backwards\n    // compatibility we keep it as it is. Consider changing it after 1st Jan 2024.\n    // else if ((tpc_stage == \"copy\") && tpc_key.length() && tpc_org.length()) {\n    mTpcFlag = kTpcSrcRead;\n    mIsTpcDst = false;\n  }\n\n  if ((mTpcFlag == kTpcSrcSetup) || (mTpcFlag == kTpcDstSetup)) {\n    // Create a TPC entry in the TpcMap\n    XrdSysMutexHelper tpc_lock(gOFS.TpcMapMutex);\n\n    if (gOFS.TpcMap[mIsTpcDst].count(tpc_key)) {\n      return gOFS.Emsg(epname, error, EPERM, \"open - tpc key replayed\",\n                       mNsPath.c_str());\n    }\n\n    // Compute the tpc origin e.g. <name>:<pid>@<host.domain>\n    // @todo(esindril) Xrootd 4.0\n    // std::string origin_host = client->addrInfo->Name();\n    std::string origin_host = client->host ? client->host : \"<sss-auth>\";\n    std::string origin_tident = client->tident;\n    origin_tident.erase(origin_tident.find(\":\"));\n    tpc_org = origin_tident;\n    tpc_org += \"@\";\n    tpc_org += origin_host;\n    // Store the TPC initialization\n    gOFS.TpcMap[mIsTpcDst][tpc_key].key = tpc_key;\n    gOFS.TpcMap[mIsTpcDst][tpc_key].org = tpc_org;\n    gOFS.TpcMap[mIsTpcDst][tpc_key].src = tpc_src;\n    gOFS.TpcMap[mIsTpcDst][tpc_key].dst = tpc_dst;\n    gOFS.TpcMap[mIsTpcDst][tpc_key].path = mNsPath.c_str();\n    gOFS.TpcMap[mIsTpcDst][tpc_key].lfn = tpc_lfn;\n\n    // Set tpc key expiration time, only relevant for the TPC source\n    if (!mIsTpcDst) {\n      std::string_view tpc_ttl = env.Get(\"tpc.ttl\") ? env.Get(\"tpc.ttl\") : \"\";\n      gOFS.TpcMap[mIsTpcDst][tpc_key].expires = GetTpcKeyExpireTS(tpc_ttl);\n    } else {\n      gOFS.TpcMap[mIsTpcDst][tpc_key].expires = time(nullptr) + 3600 - 60;\n    }\n\n    mFstTpcInfo = gOFS.TpcMap[mIsTpcDst][tpc_key];\n    mTpcKey = tpc_key;\n\n    if (mTpcFlag == kTpcDstSetup) {\n      if (!tpc_lfn.length()) {\n        return gOFS.Emsg(epname, error, EINVAL, \"open - tpc lfn missing\",\n                         mNsPath.c_str());\n      }\n\n      eos_info(\"msg=\\\"tpc dst session\\\" key=%s, org=%s, src=%s path=%s lfn=%s \"\n               \"expires=%llu\", gOFS.TpcMap[mIsTpcDst][tpc_key].key.c_str(),\n               gOFS.TpcMap[mIsTpcDst][tpc_key].org.c_str(),\n               gOFS.TpcMap[mIsTpcDst][tpc_key].src.c_str(),\n               gOFS.TpcMap[mIsTpcDst][tpc_key].path.c_str(),\n               gOFS.TpcMap[mIsTpcDst][tpc_key].lfn.c_str(),\n               gOFS.TpcMap[mIsTpcDst][tpc_key].expires);\n    } else if (mTpcFlag == kTpcSrcSetup) {\n      // Store the opaque info but without any tpc.* info\n      gOFS.TpcMap[mIsTpcDst][tpc_key].opaque = opaque.c_str();\n      // Store also the decoded capability info\n      XrdOucEnv tmp_env(opaque.c_str());\n      XrdOucEnv* cap_env {nullptr};\n      int caprc = eos::common::SymKey::ExtractCapability(&tmp_env, cap_env);\n\n      if (caprc == ENOKEY) {\n        delete cap_env;\n        return gOFS.Emsg(epname, error, caprc, \"open - missing capability\");\n      } else if (caprc != 0) {\n        delete cap_env;\n        return gOFS.Emsg(epname, error, caprc, \"open - capability illegal\",\n                         mNsPath.c_str());\n      } else {\n        int envlen = 0;\n        gOFS.TpcMap[mIsTpcDst][tpc_key].capability = cap_env->Env(envlen);\n        delete cap_env;\n      }\n\n      eos_info(\"msg=\\\"tpc src session\\\" key=%s, org=%s, dst=%s path=%s expires=%llu\",\n               gOFS.TpcMap[mIsTpcDst][tpc_key].key.c_str(),\n               gOFS.TpcMap[mIsTpcDst][tpc_key].org.c_str(),\n               gOFS.TpcMap[mIsTpcDst][tpc_key].dst.c_str(),\n               gOFS.TpcMap[mIsTpcDst][tpc_key].path.c_str(),\n               gOFS.TpcMap[mIsTpcDst][tpc_key].expires);\n    }\n  } else if (mTpcFlag == kTpcSrcRead) {\n    // Verify a TPC entry in the TpcMap since the destination's open can now\n    // come before the transfer has been setup we have to give some time for\n    // the TPC client to deposit the key the not so nice side effect is that\n    // this thread stays busy during that time\n    bool exists = false;\n\n    for (size_t i = 0; i < 150; ++i) {\n      {\n        // Briefly take lock and release it\n        XrdSysMutexHelper tpcLock(gOFS.TpcMapMutex);\n\n        if (gOFS.TpcMap[mIsTpcDst].count(tpc_key)) {\n          exists = true;\n          break;\n        }\n      }\n\n      if (!exists) {\n        std::this_thread::sleep_for(std::chrono::milliseconds(100));\n      }\n    }\n\n    XrdSysMutexHelper tpc_lock(gOFS.TpcMapMutex);\n    time_t now = time(NULL);\n\n    if (!gOFS.TpcMap[mIsTpcDst].count(tpc_key)) {\n      eos_err(\"msg=\\\"tpc key not valid\\\" key=%s\", tpc_key.c_str());\n      return gOFS.Emsg(epname, error, EPERM, \"open - tpc key not valid\",\n                       mNsPath.c_str());\n    }\n\n    if (gOFS.TpcMap[mIsTpcDst][tpc_key].expires < now) {\n      eos_err(\"msg=\\\"tpc key expired\\\" key=%s\", tpc_key.c_str());\n      return gOFS.Emsg(epname, error, EPERM, \"open - tpc key expired\",\n                       mNsPath.c_str());\n    }\n\n    // We trust 'sss' anyway and we miss the host name in the 'sss' entity\n    std::string sec_prot = client->prot;\n\n    if ((sec_prot != \"sss\")) {\n      // Extract hostname from tident to avoid IPV4/6 fqdn mismatch errors\n      std::string exp_org, cur_org;\n\n      if (!GetHostFromTident(gOFS.TpcMap[mIsTpcDst][tpc_key].org, exp_org) ||\n          !GetHostFromTident(tpc_org, cur_org)) {\n        eos_err(\"failed to parse host from tpc_org=%s or cached_org=%s\",\n                tpc_org.c_str(), gOFS.TpcMap[mIsTpcDst][tpc_key].org.c_str());\n        return gOFS.Emsg(epname, error, EPERM, \"open - tpc origin parse error\",\n                         mNsPath.c_str());\n      }\n\n      if (exp_org != cur_org) {\n        eos_err(\"tpc origin missmatch tpc_org=%s, cached_org=%s\",\n                tpc_org.c_str(), gOFS.TpcMap[mIsTpcDst][tpc_key].org.c_str());\n        return gOFS.Emsg(epname, error, EPERM, \"open - tpc origin mismatch\",\n                         mNsPath.c_str());\n      }\n    }\n\n    eos_info(\"msg=\\\"tpc read\\\" key=%s, org=%s, dst=%s path=%s expires=%llu\",\n             gOFS.TpcMap[mIsTpcDst][tpc_key].key.c_str(),\n             gOFS.TpcMap[mIsTpcDst][tpc_key].org.c_str(),\n             gOFS.TpcMap[mIsTpcDst][tpc_key].dst.c_str(),\n             gOFS.TpcMap[mIsTpcDst][tpc_key].path.c_str(),\n             gOFS.TpcMap[mIsTpcDst][tpc_key].expires);\n    // Grab the open information\n    mNsPath = gOFS.TpcMap[mIsTpcDst][tpc_key].path.c_str();\n    opaque = gOFS.TpcMap[mIsTpcDst][tpc_key].opaque.c_str();\n    SetLogId(ExtractLogId(opaque.c_str()).c_str());\n    // Store the provided origin to compare with our local connection\n    // gOFS.TpcMap[mIsTpcDst][tpc_key].org = tpc_org;\n    mFstTpcInfo = gOFS.TpcMap[mIsTpcDst][tpc_key];\n    mTpcKey = tpc_key;\n    // Save open opaque env\n    mOpenOpaque.reset(new XrdOucEnv(opaque.c_str()));\n\n    if (gOFS.TpcMap[mIsTpcDst][tpc_key].capability.length()) {\n      mCapOpaque.reset(new XrdOucEnv(\n                         gOFS.TpcMap[mIsTpcDst][tpc_key].capability.c_str()));\n    } else {\n      return gOFS.Emsg(epname, error, EINVAL, \"open - capability not found \"\n                       \"for tpc key %s\", tpc_key.c_str());\n    }\n  }\n\n  // Expire keys which are more than one 1 hours expired\n  if (mTpcFlag > kTpcNone) {\n    time_t now = time(NULL);\n    XrdSysMutexHelper tpcLock(gOFS.TpcMapMutex);\n    auto it = (gOFS.TpcMap[mIsTpcDst]).begin();\n    auto del = (gOFS.TpcMap[mIsTpcDst]).begin();\n\n    while (it != (gOFS.TpcMap[mIsTpcDst]).end()) {\n      del = it;\n      it++;\n\n      if (now > (del->second.expires + 3600)) {\n        eos_info(\"msg=\\\"expire tpc key\\\" key=%s\", del->second.key.c_str());\n        gOFS.TpcMap[mIsTpcDst].erase(del);\n      }\n    }\n  }\n\n  // For non-TPC transfer, src placement and destination TPCs we need to save\n  // and decrypt the open and capability opaque info\n  if ((mTpcFlag == kTpcNone) ||\n      (mTpcFlag == kTpcDstSetup) ||\n      (mTpcFlag == kTpcSrcSetup) ||\n      (mTpcFlag == kTpcSrcCanDo)) {\n    mOpenOpaque.reset(new XrdOucEnv(opaque.c_str()));\n    XrdOucEnv* ptr_opaque {nullptr};\n    int caprc = eos::common::SymKey::ExtractCapability(mOpenOpaque.get(),\n                ptr_opaque);\n    mCapOpaque.reset(ptr_opaque);\n\n    if (caprc) {\n      // If we just miss the key, better stall the client\n      if (caprc == ENOKEY) {\n        eos_err(\"msg=\\\"FST still misses the required capability key\\\"\");\n        return gOFS.Stall(error, 10, \"FST still misses the required capability key\");\n      }\n\n      return gOFS.Emsg(epname, error, caprc, \"open - capability illegal\",\n                       mNsPath.c_str());\n    }\n  }\n\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Compute close time\n//------------------------------------------------------------------------------\nvoid\nXrdFstOfsFile::CloseTime()\n{\n  unsigned long mus = (closeStop.tv_sec - closeStart.tv_sec) * 1000000 +\n                      (closeStop.tv_usec - closeStart.tv_usec);\n  timeToClose = mus / 1000.0;\n}\n//------------------------------------------------------------------------------\n// Account for total read time\n//------------------------------------------------------------------------------\nvoid\nXrdFstOfsFile::AddReadTime()\n{\n  unsigned long mus = (lrTime.tv_sec - cTime.tv_sec) * 1000000 +\n                      (lrTime.tv_usec - cTime.tv_usec);\n  rTime.tv_sec += (mus / 1000000);\n  rTime.tv_usec += (mus % 1000000);\n}\n\nvoid\nXrdFstOfsFile::AddLayoutReadTime()\n{\n  struct timeval nowtime;\n  gettimeofday(&nowtime, &tz);\n  timeToRead += ((nowtime.tv_sec - rStart.tv_sec) * 1000) + ((\n                  nowtime.tv_usec - rStart.tv_usec) / 1000.0);\n}\n\n//------------------------------------------------------------------------------\n// Account for total readv time\n//------------------------------------------------------------------------------\nvoid\nXrdFstOfsFile::AddReadVTime()\n{\n  unsigned long mus = (lrvTime.tv_sec - cTime.tv_sec) * 1000000 +\n                      (lrvTime.tv_usec - cTime.tv_usec);\n  rvTime.tv_sec += (mus / 1000000);\n  rvTime.tv_usec += (mus % 1000000);\n}\n\nvoid\nXrdFstOfsFile::AddLayoutReadVTime()\n{\n  struct timeval nowtime;\n  gettimeofday(&nowtime, &tz);\n  timeToReadV += ((nowtime.tv_sec - rvStart.tv_sec) * 1000) + ((\n                   nowtime.tv_usec - rvStart.tv_usec) / 1000.0);\n}\n\n//------------------------------------------------------------------------------\n// Account for total write time\n//------------------------------------------------------------------------------\nvoid\nXrdFstOfsFile::AddWriteTime()\n{\n  unsigned long mus = ((lwTime.tv_sec - cTime.tv_sec) * 1000000) +\n                      lwTime.tv_usec - cTime.tv_usec;\n  wTime.tv_sec += (mus / 1000000);\n  wTime.tv_usec += (mus % 1000000);\n}\n\nvoid\nXrdFstOfsFile::AddLayoutWriteTime()\n{\n  struct timeval nowtime;\n  gettimeofday(&nowtime, &tz);\n  timeToWrite += ((nowtime.tv_sec - wStart.tv_sec) * 1000) + ((\n                   nowtime.tv_usec - wStart.tv_usec) / 1000.0);\n}\n\n//------------------------------------------------------------------------------\n// Make report\n//------------------------------------------------------------------------------\nvoid\nXrdFstOfsFile::MakeReportEnv(XrdOucString& reportString)\n{\n  // compute avg, min, max, sigma for read and written bytes\n  unsigned long long rmin, rmax, rsum;\n  unsigned long long rvmin, rvmax, rvsum; // readv bytes\n  unsigned long long rsmin, rsmax, rssum; // read single bytes\n  unsigned long rcmin, rcmax, rcsum;      // readv count\n  unsigned long long wmin, wmax, wsum;\n  double rsigma, rvsigma, rssigma, rcsigma, wsigma;\n  bool ioprio_default = false;\n  {\n    XrdSysMutexHelper vecLock(vecMutex);\n    ComputeStatistics(rvec, rmin, rmax, rsum, rsigma);\n    ComputeStatistics(wvec, wmin, wmax, wsum, wsigma);\n    ComputeStatistics(monReadvBytes, rvmin, rvmax, rvsum, rvsigma);\n    ComputeStatistics(monReadSingleBytes, rsmin, rsmax, rssum, rssigma);\n    ComputeStatistics(monReadvCount, rcmin, rcmax, rcsum, rcsigma);\n    bool sec_tpc = ((mTpcFlag == kTpcDstSetup) || (mTpcFlag == kTpcSrcRead));\n    char report[16384];\n    float iot = (float)(((closeTime.tv_sec - openTime.tv_sec) * 1000.0) + ((\n                          closeTime.tv_usec - openTime.tv_usec) / 1000.0));\n    float rt = ((rTime.tv_sec * 1000.0) + (rTime.tv_usec / 1000.0));\n    float rvt = ((rvTime.tv_sec * 1000.0) + (rvTime.tv_usec / 1000.0));\n    float wt = ((wTime.tv_sec * 1000.0) + (wTime.tv_usec / 1000.0));\n    float idt = iot - timeToOpen - timeToClose - timeToRead - timeToReadV -\n                timeToWrite;\n    float usage = 100.0 - (100.0 * idt / iot);\n\n    if (rmin == 0xffffffff) {\n      rmin = 0;\n    }\n\n    if (wmin == 0xffffffff) {\n      wmin = 0;\n    }\n\n    if (!mIoPriorityClass || mIoPriorityErrorReported) {\n      mIoPriorityClass = IOPRIO_CLASS_BE;\n      mIoPriorityValue = 4;\n      ioprio_default = true;\n    }\n\n    std::string path = (mCapOpaque->Get(\"mgm.path\") ?\n                        mCapOpaque->Get(\"mgm.path\") : mNsPath.c_str());\n    std::string sanitized_path = eos::common::StringConversion::SealXrdPath(path);\n    snprintf(report, sizeof(report) - 1,\n             \"log=%s&path=%s&fstpath=%s&ruid=%u&rgid=%u&td=%s&\"\n             \"host=%s&lid=%lu&fid=%llu&fsid=%u&\"\n             \"ots=%lu&otms=%lu&\"\n             \"cts=%lu&ctms=%lu&\"\n             \"nrc=%lu&nwc=%lu&\"\n             \"rb=%llu&rb_min=%llu&rb_max=%llu&rb_sigma=%.02f&\"\n             \"rv_op=%llu&rvb_min=%llu&rvb_max=%llu&rvb_sum=%llu&rvb_sigma=%.02f&\"\n             \"rs_op=%llu&rsb_min=%llu&rsb_max=%llu&rsb_sum=%llu&rsb_sigma=%.02f&\"\n             \"rc_min=%lu&rc_max=%lu&rc_sum=%lu&rc_sigma=%.02f&\"\n             \"wb=%llu&wb_min=%llu&wb_max=%llu&wb_sigma=%.02f&\"\n             \"sfwdb=%llu&sbwdb=%llu&sxlfwdb=%llu&sxlbwdb=%llu&\"\n             \"nfwds=%lu&nbwds=%lu&nxlfwds=%lu&nxlbwds=%lu&\"\n             \"usage=%.02f&iot=%.03f&idt=%.03f&lrt=%.03f&lrvt=%.03f&\"\n             \"lwt=%.03f&ot=%.03f&ct=%.03f&rt=%.02f&rvt=%.02f&wt=%.02f&\"\n             \"osize=%llu&csize=%llu&delete_on_close=%d&prio_c=%d&prio_l=%d&\"\n             \"prio_d=%d&forced_bw=%d&ms_sleep=%llu&ior_err=%d&iow_err=%d&%s\"\n             , this->logId\n             , sanitized_path.c_str()\n             , mFstPath.c_str()\n             , this->vid.uid, this->vid.gid, mTident.c_str()\n             , gOFS.mHostName, mLid, mFileId, mFsId\n             , openTime.tv_sec, (unsigned long) openTime.tv_usec / 1000\n             , closeTime.tv_sec, (unsigned long) closeTime.tv_usec / 1000\n             , rCalls, wCalls\n             , rsum, rmin, rmax, rsigma\n             , (unsigned long long)monReadvBytes.size(), rvmin, rvmax, rvsum, rvsigma\n             , (unsigned long long)monReadSingleBytes.size(), rsmin, rsmax, rssum, rssigma\n             , rcmin, rcmax, rcsum, rcsigma\n             , wsum\n             , wmin\n             , wmax\n             , wsigma\n             , sFwdBytes\n             , sBwdBytes\n             , sXlFwdBytes\n             , sXlBwdBytes\n             , nFwdSeeks\n             , nBwdSeeks\n             , nXlFwdSeeks\n             , nXlBwdSeeks\n             , usage\n             , iot\n             , idt\n             , timeToRead\n             , timeToReadV\n             , timeToWrite\n             , timeToOpen\n             , timeToClose\n             , rt\n             , rvt\n             , wt\n             , (unsigned long long) mOpenSize\n             , (unsigned long long) mCloseSize\n             , (mDelOnClose) ? 1 : 0\n             , mIoPriorityClass\n             , mIoPriorityValue\n             , ioprio_default\n             , mBandwidth\n             , msSleep\n             , mHasReadErr\n             , mHasWriteErr\n             , eos::common::SecEntity::ToEnv(mSecString.c_str(),\n                 (sec_tpc ? \"tpc\" : 0)).c_str());\n    reportString = report;\n  }\n\n  if ((mTpcFlag > kTpcNone) && (mTpcFlag != kTpcSrcCanDo)) {\n    XrdSysMutexHelper tpc_lock(gOFS.TpcMapMutex);\n    std::ostringstream sstpc;\n\n    if (mTpcFlag == kTpcDstSetup) {\n      sstpc << \"&tpc.src=\" << mFstTpcInfo.src\n            << \"&tpc.src_lfn=\" << mFstTpcInfo.lfn;\n    } else if ((mTpcFlag == kTpcSrcSetup) || (mTpcFlag == kTpcSrcRead)) {\n      sstpc << \"&tpc.dst=\" << mFstTpcInfo.dst\n            << \"&tpc.src_lfn=\" << mFstTpcInfo.path;\n    }\n\n    reportString += sstpc.str().c_str();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Drop stripe/replica(s) from the MGM\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::DropFromMgm(const eos::common::FileId::fileid_t fid,\n                           const eos::common::FileSystem::fsid_t fsid,\n                           const std::string& path, const std::string& manager)\n{\n  const std::string hex_fid = eos::common::FileId::Fid2Hex(fid);\n  XrdOucErrInfo lerror;\n  std::ostringstream oss;\n  oss << \"/?mgm.pcmd=drop&mgm.fid=\" << hex_fid << \"&mgm.fsid=\" << fsid;\n\n  // Drop all stripes\n  if (fsid == 0ul) {\n    oss << \"&mgm.dropall=1\";\n  }\n\n  int rcode = gOFS.CallManager(&lerror, path.c_str(), manager.c_str(), oss.str());\n\n  if (rcode && (error.getErrInfo() != EIDRM)) {\n    eos_warning(\"msg=\\\"failed to drop at manager\\\" fxid=%08llx fsid=%u \"\n                \"manager=%s\", fid, fsid, manager.c_str());\n  }\n\n  eos_info(\"msg=\\\"drop on manager\\\" fxid=%08llx fsid=%u drop_all=%d rc=%d \"\n           \"manager=%s\", fid, fsid, (fsid == 0ul), rcode, manager.c_str());\n  return rcode;\n}\n\n//------------------------------------------------------------------------------\n// Check if file has been modified while in use\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::ModifiedWhileInUse()\n{\n  int rc = 0;\n  bool fileExists = true;\n  struct stat statinfo;\n\n  if (mLayout) {\n    if (mLayout->Stat(&statinfo)) {\n      fileExists = false;\n    }\n  } else {\n    if (XrdOfsOss->Stat(mFstPath.c_str(), &statinfo)) {\n      fileExists = false;\n    }\n  }\n\n  // Check if the file could have been changed in the meanwhile ...\n  if (!mIsRW && fileExists && mIsReplication) {\n    if (gOFS.openedForWriting.isOpen(mFsId, mFileId)) {\n      eos_err(\"file is now open for writing - discarding replication \"\n              \"[wopen=%d]\", gOFS.openedForWriting.getUseCount(mFsId, mFileId));\n      rc = gOFS.Emsg(\"closeofs\", error, EIO, \"guarantee correctness - \"\n                     \"file has been opened for writing during replication\",\n                     mNsPath.c_str());\n    }\n\n    if ((statinfo.st_mtime != updateStat.st_mtime)) {\n      eos_err(\"file has been modified during replication\");\n      rc = gOFS.Emsg(\"closeofs\", error, EIO, \"guarantee correctness -\"\n                     \"file has been modified during replication\",\n                     mNsPath.c_str());\n    }\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Layout read callback\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::LayoutReadCB(eos::fst::CheckSum::ReadCallBack::callback_data_t*\n                            cbd)\n{\n  return ((Layout*) cbd->caller)->Read(cbd->offset, cbd->buffer, cbd->size);\n}\n\n//------------------------------------------------------------------------------\n// File read callback\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::FileIoReadCB(eos::fst::CheckSum::ReadCallBack::callback_data_t*\n                            cbd)\n{\n  return ((FileIo*) cbd->caller)->fileRead(cbd->offset, cbd->buffer, cbd->size);\n}\n\n//------------------------------------------------------------------------------\n// Verify checksum method\n//------------------------------------------------------------------------------\nbool\nXrdFstOfsFile::VerifyChecksum()\n{\n  bool checksumerror = false;\n  int checksumlen = 0;\n\n  // Deal with checksums\n  if (mChecksumGroup->HasChecksums()) {\n    mChecksumGroup->Finalize();\n\n    if (mChecksumGroup->NeedsRecalculation()) {\n      if ((!mIsRW) && ((sFwdBytes + sBwdBytes) ||\n                       (mChecksumGroup->GetMaxOffset() != mOpenSize))) {\n        // We don't rescan files if they are read non-sequential or only\n        // partially\n        eos_debug(\"info=\\\"skipping checksum (re-scan) for non-sequential \"\n                  \"reading ...\\\"\");\n        mChecksumGroup->Clear();\n        return false;\n      }\n    } else {\n      eos_debug(\"isrw=%d max-offset=%lld opensize=%lld\", mIsRW,\n                mChecksumGroup->GetMaxOffset(), mOpenSize);\n\n      if ((!mIsRW) &&\n          ((mChecksumGroup->GetMaxOffset() != mOpenSize) ||\n           (mChecksumGroup->GetMaxOffset() == 0))) {\n        eos_debug(\"info=\\\"skipping checksum (re-scan) for access without any IO or \"\n                  \"partial sequential read IO from the beginning...\\\"\");\n        mChecksumGroup->Clear();\n        return false;\n      }\n    }\n\n    // -------------------------------------------------------------------------------------------------------------------\n    // !!! CAUTION !!!\n    // be careful with adler checksum - finalize can remove the dirty flag if all pieces of a file until the max checksum\n    // offset were written - however if the file size is diffrent from the max checksum offset, the checksum is dirty\n    // because the ending part of a file was not written\n    // -------------------------------------------------------------------------------------------------------------------\n    if (mIsRW && mHasWrite && mChecksumGroup->GetMaxOffset() &&\n        (mChecksumGroup->GetMaxOffset() != (off_t)mMaxOffsetWritten)) {\n      // If there was a write which was not extending the file the checksum\n      // is dirty!\n      mChecksumGroup->SetDirty();\n    }\n\n    if (gOFS.openedForWriting.hadMultiOpen(mFsId, mFileId)) {\n      // If there were several writers on the file, we should set the checksum dirty\n      mChecksumGroup->SetDirty();\n    }\n\n    // If checksum is not completely computed\n    if (mChecksumGroup->NeedsRecalculation()) {\n      unsigned long long scansize = 0;\n      std::chrono::milliseconds scantime {9999999999999999};\n\n      if (!XrdOfsFile::fctl(SFS_FCTL_GETFD, 0, error)) {\n        // Rescan the file\n        eos::fst::CheckSum::ReadCallBack::callback_data_t cbd;\n        cbd.caller = (void*) mLayout.get();\n        eos::fst::CheckSum::ReadCallBack cb(LayoutReadCB, cbd);\n\n        if (mChecksumGroup->ScanFile(cb, scansize, scantime)) {\n          XrdOucString size_str;\n          (void) eos::common::StringConversion::GetReadableSizeString(size_str,\n              scansize, \"B\");\n          float rate = ((1.0 * scansize) / (1024 * 1024) * 1000) / scantime.count();\n          eos_info(\"info=\\\"rescanned checksum\\\" size=%s duration_ms=%llu \"\n                   \"rate_MB/s=%.02f %s\",\n                   size_str.c_str(), scantime.count(), rate,\n                   mChecksumGroup->GetDefault()->GetHexChecksum());\n        } else {\n          eos_err(\"msg=\\\"checksum rescanning failed\\\" fxid=%08llx\", mFileId);\n          mChecksumGroup->Clear();\n          return false;\n        }\n      } else {\n        eos_err(\"msg=\\\"failed to get file descriptor\\\" fxid=%08llx\", mFileId);\n        mChecksumGroup->Clear();\n        return false;\n      }\n    } else {\n      // This was prefect streaming I/O\n      if ((!mIsRW) && (mChecksumGroup->GetMaxOffset() != mOpenSize)) {\n        eos_info(\"info=\\\"skipping checksum (re-scan) since file was not read \"\n                 \"completely %llu %llu...\\\"\", mChecksumGroup->GetMaxOffset(), mOpenSize);\n        mChecksumGroup->Clear();\n        return false;\n      }\n    }\n\n    if (mIsRW) {\n      auto xs = mChecksumGroup->GetDefault();\n      eos_info(\"(write) checksum type=\\\"%s\\\" checksum hex=\\\"%s\\\" \"\n               \"requested-checksum hex=\\\"%s\\\"\", xs->GetName(),\n               xs->GetHexChecksum(),\n               mOpenOpaque->Get(\"mgm.checksum\") ?\n               mOpenOpaque->Get(\"mgm.checksum\") : \"-none-\");\n\n      // Check if the check sum for the file was given at upload time\n      if (mOpenOpaque->Get(\"mgm.checksum\")) {\n        XrdOucString opaqueChecksum = mOpenOpaque->Get(\"mgm.checksum\");\n        XrdOucString hexChecksum = xs->GetHexChecksum();\n\n        if ((opaqueChecksum != \"disable\") && (opaqueChecksum != hexChecksum)) {\n          eos_err(\"requested checksum %s does not match checksum %s of uploaded\"\n                  \" file\", opaqueChecksum.c_str(), hexChecksum.c_str());\n          mChecksumGroup->Clear();\n          return true;\n        }\n      }\n\n      mFmd->mProtoFmd.set_checksum(mChecksumGroup->GetDefault()->GetHexChecksum());\n\n      if (mHasWrite) {\n        // If we have no write, we don't set this attributes (xrd3cp!)\n        // set the eos checksum extended attributes\n        std::unique_ptr<eos::fst::FileIo> io(eos::fst::FileIoPlugin::GetIoObject(\n                                               mFstPath.c_str(), this));\n\n        if (!eos::common::LayoutId::IsRain(mLid)) {\n          // Don't put file checksum tags for complex layouts like raid6,readdp, archive\n          if (io->attrSet(std::string(\"user.eos.checksumtype\"),\n                          std::string(xs->GetName()))) {\n            eos_err(\"msg=\\\"unable to set extended attr <eos.checksumtype>\\\" \"\n                    \"fxid=%08llx errno=%d\", mFileId, errno);\n          }\n\n          const char* ptr = mChecksumGroup->GetDefault()->GetBinChecksum(checksumlen);\n\n          if (io->attrSet(\"user.eos.checksum\", ptr, checksumlen)) {\n            eos_err(\"msg=\\\"unable to set extended attr <eos.checksum> \\\" \"\n                    \"fxid=%08llx errno=%d\", mFileId, errno);\n          }\n        }\n\n        // Reset any tagged error\n        if (io->attrSet(\"user.eos.filecxerror\", \"0\")) {\n          eos_err(\"msg=\\\"unable to set extended attr <eos.filecxerror>\\\" \"\n                  \"fxid=%08llx errno=%d\", mFileId, errno);\n        }\n\n        if (io->attrSet(\"user.eos.blockcxerror\", \"0\")) {\n          eos_err(\"msg=\\\"unable to set extended attr <eos.blockcxerror>\\\" \"\n                  \"fxid=%08llx errno=%d\", mFileId, errno);\n        }\n      }\n    } else {\n      // This is a read with checksum check, compare with mFmd\n      bool isopenforwrite = gOFS.openedForWriting.isOpen(mFsId, mFileId);\n\n      if (isopenforwrite) {\n        eos_info(\"msg=\\\"read disable checksum check, file being written\\\" \"\n                 \"fxid=%08llx\", mFileId);\n        return false;\n      }\n\n      auto xs = mChecksumGroup->GetDefault();\n      std::string computed_xs = xs->GetHexChecksum();\n      eos_info(\"msg=\\\"read checksum info\\\" xs_type=%s xs_computed=%s \"\n               \"xs_local=%s fxid=%08llx fsid=%u\", xs->GetName(),\n               computed_xs.c_str(), mFmd->mProtoFmd.checksum().c_str(),\n               mFileId, mFsId);\n\n      // We might fetch an unitialized value, that is not to be considered\n      // a checksum error yet.\n      if (mFmd->mProtoFmd.checksum() != \"none\") {\n        if (computed_xs != mFmd->mProtoFmd.checksum().c_str()) {\n          checksumerror = true;\n        }\n      }\n    }\n  }\n\n  return checksumerror;\n}\n\n//------------------------------------------------------------------------------\n// Queue file for CTA archiving\n//------------------------------------------------------------------------------\nbool\nXrdFstOfsFile::QueueForArchiving(const struct stat& statinfo,\n                                 std::string& queueing_errmsg,\n                                 std::string& archive_req_id)\n{\n  std::string decodedAttributes;\n  eos::common::SymKey::Base64Decode(mEventAttributes.c_str(), decodedAttributes);\n  std::map<std::string, std::string> attributes;\n  eos::common::StringConversion::GetKeyValueMap(decodedAttributes.c_str(),\n      attributes,\n      eos::common::WF_CUSTOM_ATTRIBUTES_TO_FST_EQUALS,\n      eos::common::WF_CUSTOM_ATTRIBUTES_TO_FST_SEPARATOR, nullptr);\n  std::string mgm_hostname;\n\n  if (!gOFS.mMgmAlias.empty()) {\n    mgm_hostname = gOFS.mMgmAlias;\n  } else {\n    const char* ptr = mCapOpaque->Get(\"mgm.manager\");\n\n    if (ptr != nullptr) {\n      mgm_hostname = ptr;\n    } else {\n      eos_err(\"%s\", \"msg=\\\"count not determine value of MGM hostname\");\n      return false;\n    }\n  }\n\n  const int notifyRc = NotifyProtoWfEndPointClosew(mFmd->mProtoFmd.fid(),\n                       mFmd->mProtoFmd.lid(),\n                       statinfo.st_size,\n                       mFmd->mProtoFmd.checksum(),\n                       mEventOwnerUid,\n                       mEventOwnerGid,\n                       mEventRequestor,\n                       mEventRequestorGroup,\n                       mEventInstance,\n                       mCapOpaque->Get(\"mgm.path\"),\n                       mgm_hostname,\n                       attributes,\n                       queueing_errmsg,\n                       archive_req_id);\n\n  // Note: error variable defined in XrdSfsFile interface\n  if (notifyRc == 0) {\n    error.setErrCode(0);\n    eos_info(\"Return code rc=%i errc=%d\", SFS_OK, error.getErrInfo());\n    return true;\n  } else if (SendArchiveFailedToManager(mFmd->mProtoFmd.fid(), queueing_errmsg)) {\n    eos_crit(\"msg=\\\"Failed to send archive failed event to manager\\\" \"\n             \"queueing_errmsg=\\\"%s\\\"\", queueing_errmsg.c_str());\n  }\n\n  error.setErrCode(EIO);\n  eos_info(\"Return code rc=%i errc=%d\", SFS_ERROR, error.getErrInfo());\n  return false;\n}\n\n//----------------------------------------------------------------------------\n// Static method used to start an asynchronous thread which is doing the TPC\n// transfer\n//----------------------------------------------------------------------------\nvoid*\nXrdFstOfsFile::StartDoTpcTransfer(void* arg)\n{\n  return reinterpret_cast<XrdFstOfsFile*>(arg)->DoTpcTransfer();\n}\n\n//------------------------------------------------------------------------------\n// Run method for the thread doing the TPC transfer\n//------------------------------------------------------------------------------\nvoid*\nXrdFstOfsFile::DoTpcTransfer()\n{\n  eos_info(\"msg=\\\"tpc now running - 1st sync\\\"\");\n  static eos::common::BufferManager sBuffMgrTpc(8 * eos::common::GB);\n  std::string src_url = \"\";\n  std::string src_cgi = \"\";\n\n  // The sync initiates the third party copy\n  if (!TpcValid()) {\n    eos_err(\"msg=\\\"tpc session invalidated during sync\\\"\");\n    XrdSysMutexHelper scope_lock(mTpcJobMutex);\n    mTpcState = kTpcDone;\n    mTpcRetc = ECONNABORTED;\n    mTpcInfo.Reply(SFS_ERROR, ECONNABORTED, \"sync TPC session closed by \"\n                   \"disconnect\");\n    return 0;\n  }\n\n  {\n    XrdSysMutexHelper tpcLock(gOFS.TpcMapMutex);\n    // Construct the source URL\n    src_url = \"root://\";\n    src_url += gOFS.TpcMap[mIsTpcDst][mTpcKey].src;\n    src_url += \"/\";\n    src_url += gOFS.TpcMap[mIsTpcDst][mTpcKey].lfn;\n    src_url += \"?fst.readahead=true\";\n    src_cgi = \"tpc.key=\";\n    src_cgi += mTpcKey;\n    src_cgi += \"&tpc.org=\";\n    src_cgi += gOFS.TpcMap[mIsTpcDst][mTpcKey].org;\n    src_cgi += \"&tpc.stage=copy\";\n  }\n\n  XrdIo tpcIO(src_url);\n  tpcIO.SetLogId(logId);\n  eos_info(\"sync-url=%s sync-cgi=%s\", src_url.c_str(), src_cgi.c_str());\n\n  if (tpcIO.fileOpen(0, 0, src_cgi)) {\n    eos_err(\"msg=\\\"TPC open failed\\\" src_url=%s src_cgi=%s\", src_url.c_str(),\n            src_cgi.c_str());\n    XrdSysMutexHelper scope_lock(mTpcJobMutex);\n    mTpcState = kTpcDone;\n    mTpcRetc = EFAULT;\n    mTpcInfo.Reply(SFS_ERROR, EFAULT,\n                   SSTR(\"sync - TPC open failed for src_url=\" << src_url).c_str());\n    return 0;\n  }\n\n  if (!TpcValid()) {\n    tpcIO.fileClose();\n    eos_err(\"msg=\\\"tpc session invalidated during sync\\\"\");\n    XrdSysMutexHelper scope_lock(mTpcJobMutex);\n    mTpcState = kTpcDone;\n    mTpcRetc = ECONNABORTED;\n    mTpcInfo.Reply(SFS_ERROR, ECONNABORTED,\n                   SSTR(\"sync - TPC session closed by disconnect src_url=\"\n                        << src_url).c_str());\n    return 0;\n  }\n\n  int64_t rbytes = 0;\n  int64_t wbytes = 0;\n  off_t offset = 0;\n  eos_info(\"msg=\\\"tpc pull\\\" \");\n  struct stat st_info;\n\n  if (tpcIO.fileStat(&st_info)) {\n    eos_err(\"%s\", \"msg=\\\"failed to stat remote file\\\" src_url=%s\",\n            src_url.c_str());\n    XrdSysMutexHelper scope_lock(mTpcJobMutex);\n    mTpcState = kTpcDone;\n    mTpcRetc = EIO;\n    mTpcInfo.Reply(SFS_ERROR, mTpcRetc, \"sync - TPC remote stat failed\");\n    return 0;\n  }\n\n  mTpcFileSize = st_info.st_size;\n  int64_t nread {0ull};\n  eos::common::ManagedBuffer managed_buf(sBuffMgrTpc, tpcIO.GetBlockSize());\n  std::shared_ptr<eos::common::Buffer> buffer = managed_buf.GetBuffer();\n\n  while (offset < mTpcFileSize) {\n    // Read the remote file in chunks and check after each chunk if the TPC\n    // has been aborted already\n    if (mTpcFileSize - offset >= tpcIO.GetBlockSize()) {\n      nread = tpcIO.GetBlockSize();\n    } else {\n      nread = mTpcFileSize - offset;\n    }\n\n    if (getenv(\"EOS_FST_TPC_READASYNC\")) {\n      rbytes = tpcIO.fileReadPrefetch(offset, buffer->GetDataPtr(), nread, 30);\n    } else {\n      rbytes = tpcIO.fileRead(offset, buffer->GetDataPtr(), nread);\n    }\n\n    eos_debug(\"msg=\\\"tpc read\\\" rbytes=%lli request=%llu\",\n              rbytes, tpcIO.GetBlockSize());\n\n    if ((rbytes == -1) || (rbytes != nread)) {\n      (void) tpcIO.fileClose();\n      eos_err(\"msg=\\\"tpc transfer terminated - remote read failed\\\"\");\n      XrdSysMutexHelper scope_lock(mTpcJobMutex);\n      mTpcState = kTpcDone;\n      mTpcRetc = EIO;\n      mTpcInfo.Reply(SFS_ERROR, mTpcRetc,\n                     SSTR(\"sync - TPC remote read failed src_url=\"\n                          << src_url).c_str());\n      return 0;\n    }\n\n    // Write the buffer out through the local object\n    wbytes = write(offset, buffer->GetDataPtr(), rbytes);\n\n    // Write message every 8GB ie. 1 << 33 = 8GB\n    if (offset >> 33 != (offset + rbytes) >> 33) {\n      eos_info(\"msg=\\\"tcp write\\\" offset=%llu\", offset);\n    }\n\n    if (rbytes != wbytes) {\n      (void) tpcIO.fileClose();\n      eos_err(\"%s\", \"msg=\\\"tpc transfer terminated - local write failed\\\"\");\n      XrdSysMutexHelper scope_lock(mTpcJobMutex);\n      mTpcState = kTpcDone;\n      mTpcRetc = EIO;\n      mTpcInfo.Reply(SFS_ERROR, mTpcRetc, \"sync - TPC local write failed\");\n      return 0;\n    }\n\n    offset += rbytes;\n\n    // Got an \"ofs.tpc cancel\" request from the client who triggered it\n    if (mTpcCancel) {\n      eos_err(\"%s\", \"msg=\\\"tpc transfer cancelled by the client\\\"\");\n      XrdSysMutexHelper scope_lock(mTpcJobMutex);\n      mTpcState = kTpcDone;\n      mTpcRetc = ECANCELED;\n      mTpcInfo.Reply(SFS_ERROR, mTpcRetc,\n                     SSTR(\"sync - TPC cancelled by client src_url=\"\n                          << src_url).c_str());\n      return 0;\n    }\n\n    // Check validity of the TPC key\n    if (!TpcValid()) {\n      (void) tpcIO.fileClose();\n      eos_err(\"msg=\\\"tpc transfer invalidated during sync\\\"\");\n      XrdSysMutexHelper scope_lock(mTpcJobMutex);\n      mTpcState = kTpcDone;\n      mTpcRetc = ECONNABORTED;\n      mTpcInfo.Reply(SFS_ERROR, mTpcRetc, \"sync - TPC session closed \"\n                     \"by diconnect\");\n      return 0;\n    }\n  }\n\n  // Close the remote file\n  int close_rc = tpcIO.fileClose();\n  eos_info(\"msg=\\\"done tpc transfer, close remote file\\\" is_ok=%s src_url=%s\",\n           (close_rc ? \"false\" : \"true\"), src_url.c_str());\n  XrdSysMutexHelper scope_lock(mTpcJobMutex);\n  mTpcState = kTpcDone;\n\n  if (close_rc != SFS_OK) {\n    mTpcRetc = tpcIO.GetLastErrNo();\n    mTpcInfo.Reply(SFS_ERROR, mTpcRetc,\n                   SSTR(\"sync - TPC failed source close src_url=\" << src_url\n                        << \" src_err=\" << tpcIO.GetLastErrMsg()).c_str());\n  } else {\n    mTpcInfo.Reply(SFS_OK, 0, \"\");\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// TPC clean up - invalidates the TPC keys at the end of a TPC transfer and\n// also joins the TPC helper thread\n//------------------------------------------------------------------------------\nvoid\nXrdFstOfsFile::TpcCleanup()\n{\n  if (!mTpcKey.empty()) {\n    {\n      XrdSysMutexHelper tpcLock(gOFS.TpcMapMutex);\n\n      if (gOFS.TpcMap[mIsTpcDst].count(mTpcKey)) {\n        eos_info(\"msg=\\\"remove tpc key\\\" key=%s\", mTpcKey.c_str());\n        gOFS.TpcMap[mIsTpcDst].erase(mTpcKey);\n\n        try {\n          gOFS.TpcMap[mIsTpcDst].resize(0);\n        } catch (const std::length_error& e) {}\n      }\n    }\n\n    // TPC thread is doing the data transfer pull and only runs on the dst\n    if (mTpcFlag == kTpcDstSetup) {\n      if (!mTpcThreadStatus) {\n        int retc = XrdSysThread::Join(mTpcThread, NULL);\n        eos_debug(\"msg=\\\"TPC thread joined\\\" rc=%i fxid=%08llx\", retc, mFileId);\n        mTpcThreadStatus = EINVAL;\n      } else {\n        eos_warning(\"msg=\\\"TPC thread already joined or never started \"\n                    \"successfully\\\" fxid=%08llx\", mFileId);\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Filter out particular tags from the opaque information\n//------------------------------------------------------------------------------\nvoid\nXrdFstOfsFile::FilterTagsInPlace(std::string& opaque,\n                                 const std::set<std::string> tags)\n{\n  bool found = false;\n  std::ostringstream oss;\n  std::list<std::string> tokens = eos::common::StringTokenizer::split\n                                  <std::list<std::string>>(opaque, '&');\n\n  for (const auto& token : tokens) {\n    found = false;\n\n    for (const auto& tag : tags) {\n      if (token.find(tag) == 0) {\n        found = true;\n        break;\n      }\n    }\n\n    if (!found && !token.empty()) {\n      oss << token << \"&\";\n    }\n  }\n\n  opaque = oss.str();\n\n  if (!opaque.empty()) {\n    opaque.pop_back();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Return current mtime while open\n//------------------------------------------------------------------------------\ntime_t\nXrdFstOfsFile::GetMtime() const\n{\n  if (!mIsRW) {\n    // this is to report the MGM mtime to http get requests\n    if (mForcedMtime != 1) {\n      return mForcedMtime;\n    }\n  }\n\n  if (mFmd) {\n    return mFmd->mProtoFmd.mtime();\n  } else {\n    return 0;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Extract logid from the opaque info\n//------------------------------------------------------------------------------\nstd::string\nXrdFstOfsFile::ExtractLogId(const char* opaque) const\n{\n  std::string log_id = \"unknown\";\n\n  if (opaque == nullptr) {\n    return log_id;\n  }\n\n  std::string sopaque = opaque;\n  const std::string tag = \"mgm.logid=\";\n  size_t pos_begin = sopaque.find(tag);\n\n  if (pos_begin != std::string::npos) {\n    pos_begin += tag.length();\n    size_t pos_end = sopaque.find('&', pos_begin);\n\n    if (pos_end != std::string::npos) {\n      pos_end -= pos_begin;\n    }\n\n    log_id = sopaque.substr(pos_begin, pos_end);\n  }\n\n  return log_id;\n}\n\n//------------------------------------------------------------------------------\n// Notify the workflow protobuf endpoint of closew event\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::NotifyProtoWfEndPointClosew(uint64_t file_id,\n    uint32_t file_lid, uint64_t file_size,\n    const std::string& file_checksum,\n    uint32_t owner_uid, uint32_t owner_gid,\n    const std::string& requestor_name,\n    const std::string& requestor_groupname,\n    const std::string& instance_name,\n    const std::string& fullpath,\n    const std::string& manager_name,\n    const std::map<std::string, std::string>& xattrs,\n    std::string& errmsg_wfe,\n    std::string& archive_req_id)\n{\n  using namespace eos::common;\n  cta::xrd::Request request;\n  auto notification = request.mutable_notification();\n  notification->mutable_cli()->mutable_user()->set_username(requestor_name);\n  notification->mutable_cli()->mutable_user()->set_groupname(requestor_groupname);\n  notification->mutable_file()->mutable_owner()->set_uid(owner_uid);\n  notification->mutable_file()->mutable_owner()->set_gid(owner_gid);\n  notification->mutable_file()->set_size(file_size);\n  // Insert a single checksum into the checksum blob\n  CtaCommon::SetChecksum(notification->mutable_file()->mutable_csb()->add_cs(),\n                         file_lid, file_checksum);\n  notification->mutable_wf()->set_event(cta::eos::Workflow::CLOSEW);\n  notification->mutable_wf()->mutable_instance()->set_name(instance_name);\n  auto xrdname = getenv(\"XRDNAME\");\n  auto requester_instance = std::string(gOFS.mHostName) + \":\" +\n                            (xrdname ? std::string(xrdname) : \"NULL\");\n  notification->mutable_wf()->set_requester_instance(requester_instance);\n  notification->mutable_file()->set_lpath(fullpath);\n  notification->mutable_file()->set_disk_file_id(std::to_string(file_id));\n  auto fxidString = StringConversion::FastUnsignedToAsciiHex(file_id);\n  std::string ctaArchiveFileId = \"none\";\n  std::string storageClass = \"\";\n\n  for (const auto& attrPair : xattrs) {\n    google::protobuf::MapPair<std::string, std::string> attr(attrPair.first,\n        attrPair.second);\n    notification->mutable_file()->mutable_xattr()->insert(attr);\n\n    if (attrPair.first ==\n        ARCHIVE_FILE_ID_ATTR_NAME) { // sys.archive.file_id xattr corresponds to archive_file_id first-class attribute\n      ctaArchiveFileId = attrPair.second;\n    }\n\n    if (attrPair.first == ARCHIVE_STORAGE_CLASS_ATTR_NAME) {\n      storageClass = attrPair.second;\n    }\n  }\n\n  // also make sure to pass the right attribute, don't just use the extended ones (old format), fill in the new ones\n  notification->mutable_file()->set_storage_class(storageClass);\n  notification->mutable_file()->set_archive_file_id(std::strtoul(\n        ctaArchiveFileId.c_str(), nullptr, 10));\n  // Build query strings\n  std::ostringstream srcStream;\n  std::ostringstream reportStream;\n  std::ostringstream errorReportStream;\n  srcStream << \"root://\" << manager_name << \"/\" << fullpath << \"?eos.lfn=fxid:\"\n            << fxidString;\n  notification->mutable_wf()->mutable_instance()->set_url(srcStream.str());\n  reportStream << \"eosQuery://\" << manager_name\n               << \"//eos/wfe/passwd?mgm.pcmd=event&mgm.fid=\" << fxidString\n               << \"&mgm.logid=cta&mgm.event=sync::archived&mgm.workflow=default&mgm.path=/dummy_path&mgm.ruid=0&mgm.rgid=0\"\n               \"&cta_archive_file_id=\" << ctaArchiveFileId;\n  notification->mutable_transport()->set_report_url(reportStream.str());\n  errorReportStream << \"eosQuery://\" << manager_name\n                    << \"//eos/wfe/passwd?mgm.pcmd=event&mgm.fid=\" << fxidString\n                    << \"&mgm.logid=cta&mgm.event=\" << ARCHIVE_FAILED_WORKFLOW_NAME\n                    << \"&mgm.workflow=default&mgm.path=/dummy_path&mgm.ruid=0&\"\n                    << \"mgm.rgid=0&cta_archive_file_id=\" << ctaArchiveFileId\n                    << \"&mgm.errmsg=\";\n  notification->mutable_transport()->set_error_report_url(\n    errorReportStream.str());\n  // Communication with service\n  std::string endPoint;\n  std::string resource;\n  bool protowfusegrpc;\n  std::string tokenPath;\n  bool protowfusegrpctls;\n  {\n    XrdSysMutexHelper lock(gConfig.Mutex);\n    endPoint = gConfig.ProtoWFEndpoint;\n    resource = gConfig.ProtoWFResource;\n    protowfusegrpc = gConfig.protowfusegrpc;\n    tokenPath = gConfig.JwtTokenPath;\n    protowfusegrpctls = gConfig.protowfusegrpctls;\n  }\n\n  if (endPoint.empty() || resource.empty()) {\n    eos_static_err(\"%s\", \"msg=\\\"you are running proto wf jobs without \"\n                   \"specifying fstofs.protowfendpoint or \"\n                   \"fstofs.protowfresource in the FST config file\\\"\");\n    return ENOTCONN;\n  }\n\n  cta::xrd::Response response;\n  cta::xrd::Response::ResponseType response_type =\n    cta::xrd::Response::RSP_INVALID;\n  auto root_certs = gOFS.ConcatenatedServerRootCA;\n\n  try {\n    // Instantiate service object only once, static is also thread-safe\n    // If static initialization throws an exception, it will be retried next time\n    static std::unique_ptr<WFEClient> request_sender = CreateRequestSender(\n          protowfusegrpc, endPoint, resource, root_certs, tokenPath, protowfusegrpctls);\n    auto sentAt = std::chrono::steady_clock::now();\n    response_type = request_sender->send(request, response);\n    auto receivedAt = std::chrono::steady_clock::now();\n    auto timeSpent = std::chrono::duration_cast<std::chrono::milliseconds>\n                     (receivedAt - sentAt);\n    eos_static_info(\"WFEClient send time for sync::closew=%ld\", timeSpent.count());\n  } catch (std::runtime_error& err) {\n    eos_static_err(\"Could not send request to outside service. Reason: %s\",\n                   err.what());\n    return ENOTCONN;\n  }\n\n  // also make sure to check not only the extended attribute but also the actual first-class attribute\n  switch (response_type) {\n  case cta::xrd::Response::RSP_SUCCESS: {\n    auto archiveReqIdItor = response.xattr().find(\"sys.cta.objectstore.id\");\n\n    if (response.xattr().end() != archiveReqIdItor) {\n      archive_req_id = archiveReqIdItor->second;\n    } else {\n      eos_static_err(\"msg=\\\"Failed to extract sys.cta.objectstore.id from response to closew notification to\"\n                     \" protowfendpoint\\\" path=\\\"%s\\\"\", fullpath.c_str());\n    }\n  }\n\n  return 0;\n\n  case cta::xrd::Response::RSP_ERR_CTA:\n  case cta::xrd::Response::RSP_ERR_USER:\n  case cta::xrd::Response::RSP_ERR_PROTOBUF:\n  case cta::xrd::Response::RSP_INVALID:\n    errmsg_wfe = response.message_txt();\n    eos_static_err(\"%s for file %s. Reason: %s\",\n                   CtaCommon::ctaResponseCodeToString(response_type).c_str(),\n                   fullpath.c_str(), response.message_txt().c_str());\n    return EPROTO;\n\n  default:\n    eos_static_err(\"Response:\\n%s\", response.DebugString().c_str());\n    return EPROTO;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Send archive failed event to the manager\n//------------------------------------------------------------------------------\nint XrdFstOfsFile::SendArchiveFailedToManager(const uint64_t fid,\n    const std::string& errmsg)\n{\n  const auto fxidString = eos::common::StringConversion::FastUnsignedToAsciiHex(\n                            fid);\n  std::string encodedErrMsg;\n\n  if (!common::SymKey::Base64Encode(errmsg.c_str(), errmsg.length(),\n                                    encodedErrMsg)) {\n    // \"Failed to encode message using base64\" in base64 is\n    // RmFpbGVkIHRvIGVuY29kZSBtZXNzYWdlIHVzaW5nIGJhc2U2NA==\n    encodedErrMsg = \"RmFpbGVkIHRvIGVuY29kZSBtZXNzYWdlIHVzaW5nIGJhc2U2NA==\";\n  }\n\n  XrdOucString errorReportOpaque = \"\";\n  errorReportOpaque += \"/?\";\n  errorReportOpaque += \"mgm.pcmd=event\";\n  errorReportOpaque += \"&mgm.fid=\";\n  errorReportOpaque += fxidString.c_str();\n  errorReportOpaque += \"&mgm.logid=cta\";\n  errorReportOpaque += \"&mgm.event=\";\n  errorReportOpaque += common::ARCHIVE_FAILED_WORKFLOW_NAME;\n  errorReportOpaque += \"&mgm.workflow=default\";\n  errorReportOpaque += \"&mgm.path=/dummy_path\";\n  errorReportOpaque += \"&mgm.ruid=0\";\n  errorReportOpaque += \"&mgm.rgid=0\";\n  errorReportOpaque += \"&mgm.errmsg=\";\n  errorReportOpaque += encodedErrMsg.c_str();\n  eos_info(\"msg=\\\"sending error message to manager\\\" path=\\\"%s\\\" manager=\\\"%s\\\" \"\n           \"errorReportOpaque=\\\"%s\\\"\", mCapOpaque->Get(\"mgm.path\"),\n           mCapOpaque->Get(\"mgm.manager\"), errorReportOpaque.c_str());\n  return gOFS.CallManager(&error, mCapOpaque->Get(\"mgm.path\"),\n                          mCapOpaque->Get(\"mgm.manager\"),\n                          errorReportOpaque, 30, mSyncEventOnClose, false);\n}\n\n//------------------------------------------------------------------------------\n// Get hostname from tident\n//------------------------------------------------------------------------------\nbool\nXrdFstOfsFile::GetHostFromTident(const std::string& tident,\n                                 std::string& hostname)\n{\n  hostname.clear();\n  size_t pos = tident.find('@');\n\n  if ((pos == std::string::npos) || (pos + 1 == tident.length())) {\n    return false;\n  }\n\n  size_t dot_pos = tident.find('.', pos + 1);\n  hostname = tident.substr(pos + 1, dot_pos - pos - 1);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Check if async close is configured\n//------------------------------------------------------------------------------\nbool\nXrdFstOfsFile::IsAsyncCloseConfigured()\n{\n  const char* ptr = getenv(\"EOS_FST_ASYNC_CLOSE\");\n\n  if (!ptr || (strncmp(ptr, \"1\", 1) != 0)) {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Check if async sync is configured\n//------------------------------------------------------------------------------\nbool\nXrdFstOfsFile::IsAsyncSyncConfigured()\n{\n  const char* ptr = getenv(\"EOS_FST_ASYNC_SYNC\");\n\n  if (!ptr || (strncmp(ptr, \"1\", 1) != 0)) {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Decide if close should be done synchronously. There are cases when close\n// should happen in the same thread eg. read, http tx, sink writes etc.\n//------------------------------------------------------------------------------\nbool\nXrdFstOfsFile::DoSyncClose()\n{\n  static uint64_t min_size_async_close = GetAsyncCloseMinSize();\n\n  // Even if async close is enabled there are some cases when close happens in\n  // the same XRootD thread\n  if (viaDelete || mWrDelete || mIsDevNull || (mIsRW == false) || mIsHttp ||\n      (mIsRW && (mMaxOffsetWritten <= (long long) min_size_async_close))) {\n    return true;\n  }\n\n  // For RAIN layouts especially only the entry server should do an async close.\n  // If all stripes are on the same FST (which is not a good idea) there is a\n  // risk that the thread pool for handling close requests will deadlock as\n  // some close requests will wait forever for queued depended close ops.\n  if (mIsRW && mLayout && !mLayout->IsEntryServer()) {\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Decide if sync should be done synchronously - keep the same boundary\n// conditions as for async close on purpose!\n//------------------------------------------------------------------------------\nbool\nXrdFstOfsFile::DoSyncSync()\n{\n  static uint64_t min_size_async_sync = GetAsyncCloseMinSize(); // on purpose!\n\n  // Even if async close is enabled there are some cases when close happens in\n  // the same XRootD thread\n  if (viaDelete || mWrDelete || mIsDevNull || (mIsRW == false) || mIsHttp ||\n      (mIsRW && !mRainReconstruct &&\n       (mMaxOffsetWritten <= (long long) min_size_async_sync))) {\n    return true;\n  }\n\n  // For RAIN layouts especially only the entry server should do an async sync.\n  // If all stripes are on the same FST (which is not a good idea) there is a\n  // risk that the thread pool for handling close requests will deadlock as\n  // some close requests will wait forever for queued depended close ops.\n  if (mIsRW && mLayout && !mLayout->IsEntryServer()) {\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Get configured minimum file size for which the asynchronous close method\n// is called.\n//------------------------------------------------------------------------------\nuint64_t\nXrdFstOfsFile::GetAsyncCloseMinSize()\n{\n  uint64_t min_size_async_close = 0ull;\n  const char* ptr = getenv(\"EOS_FST_ASYNC_CLOSE_MIN_SIZE_BYTES\");\n\n  if (ptr) {\n    if (!eos::common::StringToNumeric(std::string(ptr), min_size_async_close,\n                                      0ul)) {\n      eos_static_err(\"%s\", \"msg=\\\"failed to convert \"\n                     \"EOS_ASYNC_CLOSE_MIN_SIZE_BYTES, using by default 0\\\"\");\n    }\n  }\n\n  return min_size_async_close;\n}\n\n//------------------------------------------------------------------------------\n// Populate and commit FMD info locally\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::CommitToLocalFmd(const struct stat& info)\n{\n  EPNAME(\"close\");\n  mFmd->mProtoFmd.set_size(info.st_size);\n  mFmd->mProtoFmd.set_disksize\n  (eos::common::LayoutId::ExpectedStripeSize(mLid, info.st_size));\n  // Reset the diskchecksum after an update otherwise we might falsely report\n  // a checksum error. The diskchecksum will be updated by the scanner.\n  mFmd->mProtoFmd.set_diskchecksum(\"\");\n  mFmd->mProtoFmd.set_mgmsize(eos::common::FmdHelper::UNDEF);\n  mFmd->mProtoFmd.set_mgmchecksum(\"\"); // now again empty\n  mFmd->mProtoFmd.set_layouterror(0); // reset layout errors\n  mFmd->mProtoFmd.set_locations(\"\"); // reset locations\n  mFmd->mProtoFmd.set_filecxerror(0);\n  mFmd->mProtoFmd.set_blockcxerror(0);\n  mFmd->mProtoFmd.set_locations(\"\"); // reset locations\n  mFmd->mProtoFmd.set_cid(mCid);\n  mFmd->mProtoFmd.set_mtime(info.st_mtime);\n#ifdef __APPLE__\n  mFmd->mProtoFmd.set_mtime_ns(0);\n#else\n  mFmd->mProtoFmd.set_mtime_ns(info.st_mtim.tv_nsec);\n#endif\n\n  if (mCapOpaque->Get(\"mgm.source.lid\")) {\n    try {\n      std::string data = mCapOpaque->Get(\"mgm.source.lid\");\n      eos::common::LayoutId::layoutid_t src_lid = std::stoul(data);\n      mFmd->mProtoFmd.set_lid(src_lid);\n\n      // For RAIN files size is the size of the logical file and not\n      // the size of the current stripe on disk\n      if (eos::common::LayoutId::IsRain(src_lid) &&\n          mCapOpaque->Get(\"mgm.bookingsize\") && mIsReplication) {\n        data = mCapOpaque->Get(\"mgm.bookingsize\");\n        mFmd->mProtoFmd.set_size(std::stoull(data));\n      }\n    } catch (...) {\n      eos_err(\"msg=\\\"failure to convert layout id or bookingsize\\\" \"\n              \"lid=\\\"%s\\\" bookingsize=\\\"%s\\\"\",\n              mCapOpaque->Get(\"mgm.source.lid\"),\n              mCapOpaque->Get(\"mgm.bookingsize\"));\n    }\n  }\n\n  if (mCapOpaque->Get(\"mgm.source.ruid\")) {\n    mFmd->mProtoFmd.set_uid(atoi(mCapOpaque->Get(\"mgm.source.ruid\")));\n  }\n\n  if (mCapOpaque->Get(\"mgm.source.rgid\")) {\n    mFmd->mProtoFmd.set_gid(atoi(mCapOpaque->Get(\"mgm.source.rgid\")));\n  }\n\n  // Commit local\n  if (!gOFS.mFmdHandler->Commit(mFmd.get())) {\n    eos_err(\"msg=\\\"unable to commit fmd info\\\" fxid=%08llx\", mFileId);\n    return gOFS.Emsg(epname, error, EIO, \"close - unable to \"\n                     \"commit meta data\", mNsPath.c_str());\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Commit file information to MGM\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::CommitToMgm()\n{\n  using eos::common::StringConversion;\n  std::ostringstream oss;\n  bool added_fsid = false;\n  const std::string smtime = StringConversion::GetSizeString((unsigned long long)\n                             (mForcedMtime != 1) ?\n                             mForcedMtime :\n                             mFmd->mProtoFmd.mtime());\n  const std::string smtime_ns = StringConversion::GetSizeString((\n                                  unsigned long long)\n                                (mForcedMtime != 1) ?\n                                mForcedMtime_ms :\n                                mFmd->mProtoFmd.mtime_ns());\n  int envlen = 0;\n  char* ptr = nullptr;\n  oss << \"/?\" << mCapOpaque->Env(envlen)\n      << \"&mgm.pcmd=commit\"\n      << \"&mgm.size=\" << mCloseSize\n      << \"&mgm.mtime=\" << smtime\n      << \"&mgm.mtime_ns=\" << smtime_ns\n      << \"&mgm.logid=\" << logId;\n\n  if (mChecksumGroup->HasChecksums()) {\n    oss << \"&mgm.checksum=\" << mChecksumGroup->GetDefault()->GetHexChecksum();\n    const auto alt = mChecksumGroup->GetAlternatives();\n\n    if (alt.size()) {\n      oss << \"&mgm.altxs=\";\n      std::vector<std::string> tkn;\n      tkn.reserve(alt.size());\n\n      for (const auto &[xsType, xs] : alt) {\n        const auto name = eos::fst::LayoutId::GetChecksumString(xsType);\n        tkn.push_back(SSTR(name << \":\" << xs->GetHexChecksum()));\n      }\n\n      oss << eos::common::StringConversion::Join(tkn, \",\");\n    }\n  }\n\n  if (mFusex) {\n    oss << \"&mgm.fusex=1\";\n  }\n\n  if (mHasWrite) {\n    oss << \"&mgm.modified=1\";\n  }\n\n  // Prevent atomic/versioning on commmit\n  if (noAtomicVersioning) {\n    oss << \"&mgm.commit.verify=1\";\n  }\n\n  // If <drainfsid> is set, we can issue a drop replica\n  ptr = mCapOpaque->Get(\"mgm.drainfsid\");\n\n  if (ptr) {\n    oss << \"&mgm.drop.fsid=\" << ptr;\n  }\n\n  if (mRainReconstruct) {\n    // Indicate that this is a commit of a RAIN reconstruction\n    if (mLayout->IsEntryServer()) {\n      oss << \"&mgm.reconstruction=1\";\n      ptr = mOpenOpaque->Get(\"eos.pio.recfs\");\n\n      if ((mHasReadErr == false) && ptr) {\n        if (strncmp(ptr, \"0\", 1) == 0) {\n          // Rain reconstruction with possibly adding new fses which\n          // are present in the opaque information\n          ptr = mOpenOpaque->Get(\"eos.pio.addfs\");\n\n          if (ptr) {\n            oss << \"&mgm.add.fsid=\";\n            std::vector<std::string> fsid_tokens;\n            eos::common::StringConversion::Tokenize(ptr, fsid_tokens, \",\");\n\n            for (auto id : fsid_tokens) {\n              oss << id;\n              added_fsid = true;\n              break;\n            }\n          }\n        } else {\n          oss << \"&mgm.drop.fsid=\" << ptr;\n        }\n      }\n    }\n  } else {\n    if (mLayout->IsEntryServer() && !mIsReplication && !mIsInjection) {\n      // The entry server commits size and checksum\n      oss << \"&mgm.commit.size=1\";\n\n      if (mChecksumGroup->HasChecksums()) {\n        oss << \"&mgm.commit.checksum=1\";\n      }\n    } else {\n      bool last_writer = (gOFS.openedForWriting.getUseCount(mFmd->mProtoFmd.fsid(),\n                          mFmd->mProtoFmd.fid()) <= 1);\n\n      if (last_writer) {\n        if (mChecksumGroup->HasChecksums()) {\n          // If we computed a checksum, we verify ONLY IF there is a single\n          // writer. Otherwise, if there are several writers we have a\n          // significant inconsistency window during commit between replicas.\n          oss << \"&mgm.replication=1&mgm.verify.checksum=1\";\n        } else {\n          // If no checksum was computed, we disable checksum verification\n          // and we indicate replication only if we are the last active\n          // writer.\n          oss << \"&mgm.replication=1&mgm.verify.checksum=0\";\n        }\n      }\n    }\n  }\n\n  if (!added_fsid) {\n    oss << \"&mgm.add.fsid=\" << mFmd->mProtoFmd.fsid();\n  }\n\n  // Evt. tag as an OC-Chunk commit\n  if (mIsOCchunk) {\n    oss << eos::common::OwnCloud::FilterOcQuery(mOpenOpaque->Env(envlen));\n  }\n\n  int retc = gOFS.CallManager(&error, mNsPath.c_str(), mRdrManager.c_str(),\n                              oss.str(), 0, true);\n\n  if (gOFS.mSimCloseCommitMgmErr) {\n    eos_err(\"msg=\\\"simulate error during MGM commit\\\" path=\\\"%s\\\"\",\n            mNsPath.c_str());\n    error.setErrCode(EREMCHG);\n    return SFS_ERROR;\n  }\n\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Trigger event on close\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::TriggerEventOnClose(const std::string& archive_req_id)\n{\n  std::string event_t = \"closer\";\n\n  if (mIsRW) {\n    event_t = (mSyncEventOnClose ? \"sync::closew\" : \"closew\");\n  }\n\n  std::ostringstream oss;\n  int envlen = 0;\n  oss << \"/?\"\n      << mCapOpaque->Env(envlen)\n      << \"&mgm.pcmd=event\"\n      << \"&mgm.event=\" << event_t\n      << \"&mgm.logid=\" << logId\n      << \"&mgm.ruid=\" << mCapOpaque->Get(\"mgm.ruid\")\n      << \"&mgm.rgid=\" << mCapOpaque->Get(\"mgm.rgid\")\n      << \"&mgm.sec=\" << mSecString;\n\n  if (mEventWorkflow.length()) {\n    oss << \"&mgm.workflow=\" << mEventWorkflow;\n  }\n\n  if (!archive_req_id.empty()) {\n    oss << \"&mgm.archive_req_id=\" << archive_req_id;\n  }\n\n  eos_info(\"msg=\\\"notify\\\" event=\\\"%s\\\" workflow=\\\"%s\\\" fxid=%08llx\",\n           event_t.c_str(), mEventWorkflow.c_str(), mFileId);\n  return gOFS.CallManager(&error, mNsPath.c_str(), mRdrManager.c_str(),\n                          oss.str(), 30, mSyncEventOnClose, false);\n}\n\n//----------------------------------------------------------------------------\n// Read AIO - not supported\n//----------------------------------------------------------------------------\nint\nXrdFstOfsFile::read(XrdSfsAio* aioparm)\n{\n  return gOFS.Emsg(\"read_aio\", error, ENOTSUP, \"read aio - operation not \"\n                   \"supported\");\n}\n\n//----------------------------------------------------------------------------\n// Read file pages and checksums using asynchronous I/O - no supported\n//-----------------------------------------------------------------------------\nint\nXrdFstOfsFile::pgRead(XrdSfsAio* aioparm, uint64_t opts)\n{\n  return gOFS.Emsg(\"pgRead_aio\", error, ENOTSUP, \"pgRead aio - operation not \"\n                   \"supported\");\n}\n\n//----------------------------------------------------------------------------\n// Write AIO - no supported\n//----------------------------------------------------------------------------\nint\nXrdFstOfsFile::write(XrdSfsAio* aioparm)\n{\n  return gOFS.Emsg(\"write_aio\", error, ENOTSUP, \"write aio - operation not \"\n                   \"supported\");\n}\n\n//----------------------------------------------------------------------------\n// Write file pages and checksums using asynchronous I/O - not supported\n//----------------------------------------------------------------------------\nint\nXrdFstOfsFile::pgWrite(XrdSfsAio* aioparm, uint64_t opts)\n{\n  return gOFS.Emsg(\"pgWrite_aio\", error, ENOTSUP, \"pgWrite aio - operation not \"\n                   \"supported\");\n}\n\n//------------------------------------------------------------------------------\n// Sync AIO - not supported\n//------------------------------------------------------------------------------\nint\nXrdFstOfsFile::sync(XrdSfsAio* aiop)\n{\n  return gOFS.Emsg(\"sync_aio\", error, ENOTSUP, \"sync aio - operation not \"\n                   \"supported\");\n}\n\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/XrdFstOfsFile.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file: XrdFstOfsFile.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_FSTOFSFILE_HH__\n#define __EOSFST_FSTOFSFILE_HH__\n\n#include \"fst/Namespace.hh\"\n#include \"fst/storage/Storage.hh\"\n#include \"fst/checksum/CheckSum.hh\"\n#include \"fst/checksum/ChecksumGroup.hh\"\n#include \"fst/utils/TpcInfo.hh\"\n#include \"common/Fmd.hh\"\n#include \"common/FileId.hh\"\n#include \"common/SymKeys.hh\"\n#include <XrdVersion.hh>\n#include <XrdOfs/XrdOfs.hh>\n#include <XrdOfs/XrdOfsTPCInfo.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <numeric>\n\nnamespace eos\n{\nnamespace fst\n{\nclass HttpHandler;\nclass HttpServer;\nclass S3Handler;\n}\n};\n\nnamespace eos\n{\nnamespace common\n{\nclass FmdHelper;\n}\n}\n\nEOSFSTNAMESPACE_BEGIN;\n\n// This defines for reports what is a large seek e.g. > 128 kB = default RA size\n#define EOS_FSTOFS_LARGE_SEEKS 128*1024ll\n\n// Forward declaration\nclass Layout;\nclass CheckSum;\n\n#if XrdMajorVNUM( XrdVNUMBER ) > 4\n#define XrdOfsFileBase XrdOfsFileFull\n#else\n#define XrdOfsFileBase XrdOfsFile\n#endif\n\n//------------------------------------------------------------------------------\n//! Class XrdFstOfsFile\n//------------------------------------------------------------------------------\nclass XrdFstOfsFile : public XrdOfsFileBase, public eos::common::LogId\n{\n  friend class ReplicaParLayout;\n  friend class RainMetaLayout;\n  friend class RaidDpLayout;\n  friend class ReedSLayout;\n  friend class LocalIo;\n  friend class HttpHandler;\n  friend class HttpServer;\n  friend class S3Handler;\n\npublic:\n\n  static constexpr uint16_t msDefaultTimeout {300};\n  static int LayoutReadCB(eos::fst::CheckSum::ReadCallBack::callback_data_t* cbd);\n  static int FileIoReadCB(eos::fst::CheckSum::ReadCallBack::callback_data_t* cbd);\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param user user identity (tident)\n  //! @param MonID monitoring id\n  //----------------------------------------------------------------------------\n  XrdFstOfsFile(const char* user, int MonID = 0);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~XrdFstOfsFile();\n\n  //----------------------------------------------------------------------------\n  //! Open file - see XrdSfsInterface.hh for description of parameters\n  //!\n  //! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, SFS_STALL, or SFS_STARTED\n  //----------------------------------------------------------------------------\n  int open(const char* fileName, XrdSfsFileOpenMode openMode,\n           mode_t createMode, const XrdSecEntity* client,\n           const char* opaque = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Regule the bandwidth with the Scaler data from the IoAggregateMap\n  //!\n  //! @param std::string \"read\" or \"write\" to know the context\n  //! @return the scaler\n  //----------------------------------------------------------------------------\n  std::uint64_t reguleBandwidth(const std::string) const;\n\n  //----------------------------------------------------------------------------\n  //! Read from file\n  //----------------------------------------------------------------------------\n  XrdSfsXferSize read(XrdSfsFileOffset fileOffset, char* buffer,\n                      XrdSfsXferSize buffer_size) override;\n\n  //----------------------------------------------------------------------------\n  //! Read AIO - not supported\n  //----------------------------------------------------------------------------\n  int read(XrdSfsAio* aioparm) override;\n\n  //----------------------------------------------------------------------------\n  //! Read file pages into a buffer and return corresponding checksums.\n  //!\n  //! @param  offset  - The offset where the read is to start. It may be\n  //!                   unaligned with certain caveats relative to csvec.\n  //! @param  buffer  - pointer to buffer where the bytes are to be placed.\n  //! @param  rdlen   - The number of bytes to read. The amount must be an\n  //!                   integral number of XrdSfsPage::Size bytes.\n  //! @param  csvec   - A vector of entries to be filled with the cooresponding\n  //!                   CRC32C checksum for each page. However, if the offset is\n  //!                   unaligned, then csvec[0] contains the crc for the page\n  //!                   fragment that brings it to alignment for csvec[1].\n  //!                   It must be sized to hold all aligned XrdSys::Pagesize\n  //!                   crc's plus additional ones for leading and ending page\n  //!                   fragments, if any.\n  //! @param  opts    - Processing options (see above).\n  //!\n  //! @return >= 0      The number of bytes that placed in buffer.\n  //! @return SFS_ERROR File could not be read, error holds the reason.\n  //----------------------------------------------------------------------------\n  XrdSfsXferSize pgRead(XrdSfsFileOffset offset, char* buffer,\n                        XrdSfsXferSize rdlen, uint32_t* csvec,\n                        uint64_t opts = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Read file pages and checksums using asynchronous I/O - NOT SUPPORTED\n  //!\n  //! @param  aioparm - Pointer to async I/O object controlling the I/O.\n  //! @param  opts    - Processing options (see above).\n  //!\n  //! @return SFS_OK    Request accepted and will be scheduled.\n  //! @return SFS_ERROR File could not be read, error holds the reason.\n  //-----------------------------------------------------------------------------\n  int pgRead(XrdSfsAio* aioparm, uint64_t opts = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Pre-read blocks into file system cache\n  //----------------------------------------------------------------------------\n  int read(XrdSfsFileOffset fileOffset, XrdSfsXferSize amount) override;\n\n  //----------------------------------------------------------------------------\n  //! Vector read - OFS interface method\n  //!\n  //! @param readV vector read structure\n  //! @param readCount number of entries in the vector read structure\n  //!\n  //! @return number of bytes read upon success, otherwise SFS_ERROR\n  //!\n  //----------------------------------------------------------------------------\n  XrdSfsXferSize readv(XrdOucIOVec* readV, int readCount) override;\n\n  //----------------------------------------------------------------------------\n  //! Write to file\n  //----------------------------------------------------------------------------\n  XrdSfsXferSize write(XrdSfsFileOffset fileOffset, const char* buffer,\n                       XrdSfsXferSize buffer_size) override;\n\n  //----------------------------------------------------------------------------\n  //! Write AIO - no supported\n  //----------------------------------------------------------------------------\n  int write(XrdSfsAio* aioparm) override;\n\n  //----------------------------------------------------------------------------\n  //! Write file pages into a file with corresponding checksums.\n  //!\n  //! @param  offset  - The offset where the write is to start. It may be\n  //!                   unaligned with certain caveats relative to csvec.\n  //! @param  buffer  - pointer to buffer containing the bytes to write.\n  //! @param  wrlen   - The number of bytes to write. If amount is not an\n  //!                   integral number of XrdSys::PageSize bytes, then this must\n  //!                   be the last write to the file at or above the offset.\n  //! @param  csvec   - A vector which contains the corresponding CRC32 checksum\n  //!                   for each page or page fragment. If offset is unaligned\n  //!                   then csvec[0] is the crc of the leading fragment to\n  //!                   align the subsequent full page who's crc is in csvec[1].\n  //!                   It must be sized to hold all aligned XrdSys::Pagesize\n  //!                   crc's plus additional ones for leading and ending page\n  //!                   fragments, if any.\n  //! @param  opts    - Processing options (see above).\n  //!\n  //! @return >= 0      The number of bytes written.\n  //! @return SFS_ERROR File could not be read, error holds the reason.\n  //----------------------------------------------------------------------------\n  XrdSfsXferSize pgWrite(XrdSfsFileOffset offset, char* buffer,\n                         XrdSfsXferSize wrlen, uint32_t* csvec,\n                         uint64_t opts = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Write file pages and checksums using asynchronous I/O - NOT SUPPORTED\n  //!\n  //! @param  aioparm - Pointer to async I/O object controlling the I/O.\n  //! @param  opts    - Processing options (see above).\n  //!\n  //! @return SFS_OK    Request accepted and will be scheduled.\n  //! @return SFS_ERROR File could not be read, error holds the reason.\n  //----------------------------------------------------------------------------\n  int pgWrite(XrdSfsAio* aioparm, uint64_t opts = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Get file stat information\n  //!\n  //! @param buf pointer to the structure where info it to be returned\n  //!\n  //! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, or SFS_STALL. When\n  //!         SFS_OK is returned, buf must hold stat information\n  //----------------------------------------------------------------------------\n  int stat(struct stat* buf) override;\n\n  //----------------------------------------------------------------------------\n  //! Sync file\n  //!\n  //! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, SFS_STALL, or SFS_STARTED\n  //----------------------------------------------------------------------------\n  int sync() override;\n\n  //----------------------------------------------------------------------------\n  //! Sync AIO\n  //----------------------------------------------------------------------------\n  int sync(XrdSfsAio* aiop) override;\n\n  //----------------------------------------------------------------------------\n  //! Truncate file\n  //!\n  //! @param fsize truncate size of the file\n  //!\n  //! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, or SFS_STALL\n  //----------------------------------------------------------------------------\n  int truncate(XrdSfsFileOffset fileOffset) override;\n\n  //----------------------------------------------------------------------------\n  //! Close file\n  //----------------------------------------------------------------------------\n  int close() override;\n\n  //----------------------------------------------------------------------------\n  //! Execute special operation on the file (version 2)\n  //!\n  //! @param  cmd    - The operation to be performed:\n  //!                  SFS_FCTL_SPEC1    Perform implementation defined action\n  //! @param  alen   - Length of data pointed to by args.\n  //! @param  args   - Data sent with request, zero if alen is zero.\n  //! @param  client - Client's identify (see common description).\n  //!\n  //! @return SFS_OK   a null response is sent.\n  //! @return SFS_DATA error.code    length of the data to be sent.\n  //!                  error.message contains the data to be sent.\n  //!         o/w      one of SFS_ERROR, SFS_REDIRECT, or SFS_STALL.\n  //----------------------------------------------------------------------------\n  int fctl(const int cmd, int alen, const char* args,\n           const XrdSecEntity* client = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Return logical path\n  //----------------------------------------------------------------------------\n  std::string GetPath() const\n  {\n    return mNsPath.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the file system id\n  //----------------------------------------------------------------------------\n  unsigned long GetFileSystemId() const\n  {\n    return mFsId;\n  }\n\n  //----------------------------------------------------------------------------\n  //! We're being placed in a cache. We record this time so we can report more\n  //! relevant information when we are eventually closed.\n  //----------------------------------------------------------------------------\n  void OnCacheInsert()\n  {\n    gettimeofday(&cacheITime, &tz);\n  }\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  eos::common::SymKey::hmac_t mHmac;\n  std::unique_ptr<XrdOucEnv> mOpenOpaque; ///< Open opaque info (encrypted)\n  std::unique_ptr<XrdOucEnv> mCapOpaque; ///< Capability opaque info (decrypted)\n  std::string mFstPath; ///< Physical path on the FST\n  long long mBookingSize;\n  off_t mTargetSize;\n  off_t mMinSize;\n  off_t mMaxSize;\n  bool viaDelete;\n  bool mWrDelete; ///< Flag file to be deleted due to write errors\n  uint64_t mRainSize; ///< Rain file size used during reconstruction\n  XrdOucString mNsPath; /// Logical file path (from the namespace)\n  XrdOucString mLocalPrefix; ///< Prefix on the local storage\n  XrdOucString mRdrManager; ///< Manager host where we bounce back\n  bool mTapeEnabled; ///< True if tape support is enabled\n  XrdOucString mSecString; ///< string containing security summary\n  std::map<std::string, std::string> mSecMap; ///< map of all sec keys\n  std::string mEtag; ///< Current and new ETag (recomputed in close)\n  unsigned long long mFileId; //! file id\n  eos::common::FileSystem::fsid_t mFsId; //! file system id\n  unsigned long mLid; //! layout id\n  unsigned long long mCid; //! container id\n  unsigned long long mForcedMtime;\n  unsigned long long mForcedMtime_ms;\n  bool mFusex; //! indicator that we are committing from a fusex client\n  bool mFusexIsUnlinked; //! indicator for an already unlinked file\n  bool mClosed; //! indicator the file is closed\n  //! Close return code that we might need to return twice in case of TPC\n  int mCloseRc;\n  bool mOpened; //! indicator that file is opened\n  bool mHasWrite; //! indicator that file was written/modified\n  bool mHasWriteErr;// indicator for write errors to avoid message flooding\n  bool mHasReadErr; //! indicator if a RAIN file could be reconstructed or not\n  bool mIsRW; //! indicator that file is opened for rw\n  bool mIsDevNull; ///< If true file act as a sink i.e. /dev/null\n  bool mIsCreation; //! indicator that a new file is created\n  bool mIsReplication; //! indicator that the opened file is a replica transfer\n  bool noAtomicVersioning; //! indicate to disable atomic/versioning during commit\n  //! Indicate that the opened file is a file injection where the size and\n  //! checksum must match\n  bool mIsInjection;\n  bool mRainReconstruct; ///< indicator that the opened file is in a RAIN reconstruction process\n  bool mDelOnClose; ///< indicator that the file has to be cleaned on close\n  bool mRepairOnClose; ///< indicator that the file should get repaired on close\n  bool mIsOCchunk; //! indicator this is an OC chunk upload\n  int writeErrorFlag; //! uses kOFSxx enums to specify an error condition\n  bool mEventOnClose; ///< Indicator to send a specified event to MGM on close\n  bool mSyncOnClose; ///< Indicator to run a fsync on close\n  //! Indicates the workflow to be triggered by an event\n  XrdOucString mEventWorkflow;\n  bool mSyncEventOnClose; //! Indicator to send a specified event to the mgm on close\n  std::string mEventInstance;\n  uint32_t mEventOwnerUid;\n  uint32_t mEventOwnerGid;\n  std::string mEventRequestor;\n  std::string mEventRequestorGroup;\n  std::string mEventAttributes;\n  std::string mArchiveReqId;\n  int mIoPriorityValue;\n  int mIoPriorityClass;\n  bool mIoPriorityErrorReported;\n  std::string mAppRR;\n\n  enum {\n    kOfsIoError = 1, //! generic IO error\n    kOfsMaxSizeError = 2, //! maximum file size error\n    kOfsDiskFullError = 3, //! disk full error\n    kOfsSimulatedIoError = 4, //! simulated IO error\n    kOfsFsRemovedError = 5, //! filesystem has been unregistered\n    kOfsSimulatedCloseError = 6, //! simulated error when closing the file\n    kOfsSimulatedUnresponsive = 7 // ! simulated unresponsivness of an FST\n  };\n\n  ///< In-memory file meta data object\n  std::unique_ptr<eos::common::FmdHelper> mFmd;\n  std::unique_ptr<eos::fst::ChecksumGroup> mChecksumGroup;\n  // @todo(esindril) this is not properly enforced everywhere ...\n  std::unique_ptr<Layout> mLayout; ///< Layout object\n  std::string mTident; ///< Client identity using the file object\n  // File statistics for monitoring purposes\n  //! Largest byte position written of a newly created file\n  long long mMaxOffsetWritten;\n  long long mWritePosition;\n  long long mOpenSize; //! file size when the file was opened\n  long long mCloseSize; //! file size when the file was closed\n  struct timeval openTime; //! time when a file was opened\n  struct timeval currentTime; //! time when a write occurs\n  unsigned long long totalBytes; //! total bytes IO\n  unsigned long long msSleep; //! total ms sleeping during io\n  struct timeval closeTime; //! time when a file was closed\n  struct timeval closeStart; //! time when a file close started\n  struct timeval closeStop; //! time when a file close stopped\n  struct timeval cacheITime; //! time when object was put into a cache\n  struct timezone tz; //! timezone\n  int mBandwidth; //! bandwidth limitation setting\n  XrdSysMutex vecMutex; //! mutex protecting the rvec/wvec variables\n  //! vector with all read  sizes -> to compute sigma,min,max,total\n  std::vector<unsigned long long> rvec;\n  //! vector with all write sizes -> to compute sigma,min,max,total\n  std::vector<unsigned long long> wvec;\n  unsigned long long rBytes; //! sum bytes read\n  unsigned long long wBytes; //! sum bytes written\n  unsigned long long sFwdBytes; //! sum bytes seeked forward\n  unsigned long long sBwdBytes; //! sum bytes seeked backward\n  //! sum bytes with large forward seeks (> EOS_FSTOFS_LARGE_SEEKS)\n  unsigned long long sXlFwdBytes;\n  //! sum bytes with large backward seeks (> EOS_FSTOFS_LARGE_SEEKS)\n  unsigned long long sXlBwdBytes;\n  unsigned long rCalls; //! number of read calls\n  unsigned long wCalls; //! number of write calls\n  unsigned long nFwdSeeks; //! number of seeks forward\n  unsigned long nBwdSeeks; //! number of seeks backward\n  unsigned long nXlFwdSeeks; //! number of seeks forward\n  unsigned long nXlBwdSeeks; //! number of seeks backward\n  unsigned long long rOffset; //! offset since last read operation on this file\n  unsigned long long wOffset; //! offset since last write operation on this file\n  //! Vector with all readv sizes -> to compute min,max,etc.\n  std::vector<unsigned long long> monReadvBytes;\n  //! Size of each read call coming from readv requests -> to compute min,max, etc.\n  std::vector<unsigned long long> monReadSingleBytes;\n  //! Number of individual read op. in each readv call -> to compute min,max, etc.\n  std::vector<unsigned long> monReadvCount;\n\n  struct timeval cTime; ///< current time\n  struct timeval rStart; ///< start of last read (layout)\n  struct timeval rvStart; ///< start of last readv (layout)\n  struct timeval wStart; ///< start of last read (layout)\n  struct timeval lrTime; ///< last read time\n  struct timeval lrvTime; ///< last readv time\n  struct timeval lwTime; ///< last write time\n  struct timeval rTime; ///< sum time to serve read requests in ms\n  struct timeval rvTime; ///< sum time to server readv requests in ms\n  struct timeval wTime; ///< sum time to serve write requests in ms\n  //! Stat struct to check if a file is updated between open-close\n  struct stat updateStat;\n\n  double timeToOpen; ///< time the open call took\n  double timeToClose; ///< time the close call took\n  double timeToRead; ///< time of all layout reads\n  double timeToReadV; ///< time of all layout readvs\n  double timeToWrite; ///< time of all layout writes\n  //! TPC related types and variables\n  enum TpcType_t {\n    kTpcNone = 0, ///< No TPC access\n    kTpcSrcSetup = 1, ///< Access setting up a source TPC session\n    kTpcDstSetup = 2, ///< Access setting up a destination TPC session\n    kTpcSrcRead = 3, ///< Read access from a TPC destination\n    kTpcSrcCanDo = 4, ///< Read access to evaluate if source available\n  };\n\n  enum TpcState_t {\n    kTpcIdle = 0, ///< TPC is not enabled (1st sync)\n    kTpcRun = 1, ///< TPC is running (2nd sync)\n    kTpcDone = 2, ///< TPC has finished\n  };\n\n  int mTpcThreadStatus; ///< Status of the TPC thread - 0 valid otherwise error\n  pthread_t mTpcThread; ///< Thread doing the TPC transfer\n  TpcState_t mTpcState; ///< TPC transfer status\n  TpcType_t mTpcFlag; ///< TPC access type\n  XrdOfsTPCInfo mTpcInfo; ///< TPC info object used for callback\n  XrdSysMutex mTpcJobMutex; ///< TPC job mutex\n  std::string mTpcKey; ///< TPC key for a tpc file operation\n  TpcInfo mFstTpcInfo; ///< FST TPC info struct\n  bool mIsTpcDst; ///< If true this is a TPC destination, otherwise a source\n  int mTpcRetc; ///< TPC job return code\n  std::atomic<bool> mTpcCancel; ///< Mark TPC cancellation request\n  //! Size of the file to be transferred via TPC, this is used to compute\n  //! the estimated transfer time.\n  std::atomic<uint64_t> mTpcFileSize;\n  uint16_t mTimeout; ///< timeout for layout operations\n  bool mIsHttp; ///< Mark if this is HTTP acceess\n\n  //----------------------------------------------------------------------------\n  //! Get configured minimum file size for which the asynchronous close method\n  //! is called.\n  //!\n  //! @return min file size\n  //----------------------------------------------------------------------------\n  static uint64_t GetAsyncCloseMinSize();\n\n  //----------------------------------------------------------------------------\n  //! Check if async close is configured\n  //!\n  //! @return true if enabled, otherwise false\n  //----------------------------------------------------------------------------\n  static bool IsAsyncCloseConfigured();\n\n  //----------------------------------------------------------------------------\n  //! Check if async sycn is configured - a bit of an oxymoron ...\n  //!\n  //! @return true if enabled, otherwise false\n  //----------------------------------------------------------------------------\n  static bool IsAsyncSyncConfigured();\n\n  //----------------------------------------------------------------------------\n  //! Get hostname from tident. This is used when checking the origin match for\n  //! TPC transfers. It only extract the hostname without domain to avoid\n  //! mismatch in cases where the same machine provides both IPV4 and IPV6\n  //! interfaces. Eg. root.1.2@eosbackup.cern.ch and\n  //! root.1.2@eosbackup.ipv6.cern.ch should match\n  //!\n  //! @param tident xrootd like tident\n  //! @param hostname output parameter holding the hostname\n  //!\n  //! @return true if parsing successful and hostname stores the desired value,\n  //!         otherwise false\n  //----------------------------------------------------------------------------\n  static bool GetHostFromTident(const std::string& tident,\n                                std::string& hostname);\n\n  //----------------------------------------------------------------------------\n  //! Filter out particular tags from the opaque information\n  //!\n  //! @param opaque opaque information to process\n  //! @param tags set of tags to be filtered out\n  //----------------------------------------------------------------------------\n  static void FilterTagsInPlace(std::string& opaque,\n                                const std::set<std::string> tags);\n\n  //----------------------------------------------------------------------------\n  //! Get TPC key expiration timestamp by combinding the client specified\n  //! tpc.ttl value with the system TPC key minimum validity. The resulting\n  //! value should always be between the system TPC key minimum validity and\n  //! 15 minutes, unless the former the biggern than 15 min and then this will\n  //! apply.\n  //!\n  //! @param tpc_tll client specified key ttl\n  //! @param now_ts used for testing\n  //!\n  //! @return expiration timestamp for the current key\n  //----------------------------------------------------------------------------\n  static time_t GetTpcKeyExpireTS(std::string_view tpc_ttl,\n                                  time_t now_ts = 0);\n\n  //----------------------------------------------------------------------------\n  //! Close internal method that can be called synchronously (from XRootD) or\n  //! asynchronously from the thread pool for long running close operations.\n  //!\n  //! @return SFS_OK if close successful, otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int _close();\n\n  //----------------------------------------------------------------------------\n  //! Close file opened for reading - internal\n  //!\n  //! @return SFS_OK if close successful, otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int _close_rd();\n\n  //----------------------------------------------------------------------------\n  //! Close file opened for writing - internal\n  //!\n  //! @return SFS_OK if close successful, otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int _close_wr();\n\n  //----------------------------------------------------------------------------\n  //! Low-level open calling the default XrdOfs plugin and being called from\n  //! one of the layout implementations.\n  //----------------------------------------------------------------------------\n  int openofs(const char* fileName, XrdSfsFileOpenMode openMode,\n              mode_t createMode, const XrdSecEntity* client,\n              const char* opaque = 0);\n\n  //----------------------------------------------------------------------------\n  //! Low-level read calling the default XrdOfs plugin\n  //----------------------------------------------------------------------------\n  XrdSfsXferSize readofs(XrdSfsFileOffset fileOffset, char* buffer,\n                         XrdSfsXferSize buffer_size);\n\n  //----------------------------------------------------------------------------\n  //! Low-level vector read calling the default XrdOfs plugin\n  //----------------------------------------------------------------------------\n  XrdSfsXferSize readvofs(XrdOucIOVec* readV, uint32_t readCount);\n\n  //----------------------------------------------------------------------------\n  //! Low-level write calling the default XrdOfs plugin\n  //----------------------------------------------------------------------------\n  XrdSfsXferSize writeofs(XrdSfsFileOffset fileOffset, const char* buffer,\n                          XrdSfsXferSize buffer_size);\n\n  //----------------------------------------------------------------------------\n  //! Low-level sync calling the default XrdOfs plugin\n  //----------------------------------------------------------------------------\n  int syncofs();\n\n  //----------------------------------------------------------------------------\n  //! Low-level truncate calling the default XrdOfs plugin\n  //----------------------------------------------------------------------------\n  int truncateofs(XrdSfsFileOffset fileOffset);\n\n  //----------------------------------------------------------------------------\n  //! Low-level close calling the default XrdOfs plugin\n  //----------------------------------------------------------------------------\n  int closeofs();\n\n  //----------------------------------------------------------------------------\n  //! Get physical path on the FST (local)\n  //!\n  //! @return local physical path\n  //----------------------------------------------------------------------------\n  inline std::string GetFstPath() const\n  {\n    return mFstPath.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the Etag\n  //----------------------------------------------------------------------------\n  const char* GetETag() const\n  {\n    return mEtag.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Enforce an mtime on close\n  //----------------------------------------------------------------------------\n  void SetForcedMtime(unsigned long long mtime, unsigned long long mtime_ms)\n  {\n    mForcedMtime = mtime;\n    mForcedMtime_ms = mtime_ms;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return current mtime while open\n  //----------------------------------------------------------------------------\n  time_t GetMtime() const;\n\n  //----------------------------------------------------------------------------\n  //! Return the file size seen at open time\n  //----------------------------------------------------------------------------\n  inline off_t GetOpenSize() const\n  {\n    return mOpenSize;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the file id\n  //----------------------------------------------------------------------------\n  unsigned long long GetFileId() const\n  {\n    return mFileId;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return checksum\n  //----------------------------------------------------------------------------\n  eos::fst::CheckSum* GetChecksum() const\n  {\n    if (!mChecksumGroup->HasChecksums()) {\n      return nullptr;\n    }\n\n    return mChecksumGroup->GetDefault();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return FMD checksum\n  //----------------------------------------------------------------------------\n  std::string GetFmdChecksum() const;\n\n  //----------------------------------------------------------------------------\n  //! Check for chunked upload flag\n  //----------------------------------------------------------------------------\n  bool IsChunkedUpload() const\n  {\n    return mIsOCchunk;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if the TpcKey is still valid e.g. member of gOFS.TpcMap\n  //----------------------------------------------------------------------------\n  bool TpcValid() const;\n\n  //----------------------------------------------------------------------------\n  //! Process TPC (third-party-copy) opaque information i.e handle tags like\n  //! tpc.key, tpc.dst, tpc.stage etc and also extract and decrypt the cap\n  //! opaque info\n  //!\n  //! @param opaque opaque information\n  //! @param client XrdSecEntity of client\n  //!\n  //! @return SFS_OK if successful, otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int ProcessTpcOpaque(std::string& opaque, const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Process open opaque information - this comes directly from the client\n  //! or from the MGM redirection but it's not encrypted but sent in plain\n  //! text in the URL\n  //!\n  //! @return SFS_OK if successful, otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int ProcessOpenOpaque();\n\n  //----------------------------------------------------------------------------\n  //! Process cap opaque information - decisions that need to be taken based\n  //! on the encrypted opaque info\n  //!\n  //! @param is_repair_read flag if this is a repair read\n  //! @param vid client virtual identity\n  //!\n  //! @return SFS_OK if successful, otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int ProcessCapOpaque(bool& is_repair_read,\n                       eos::common::VirtualIdentity& vid);\n\n  //----------------------------------------------------------------------------\n  //! Process mixed opaque information - decisions that need to be taken based\n  //! on both the ecrypted and un-encrypted opaque info\n  //!\n  //! @return SFS_OK if successful, otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int ProcessMixedOpaque();\n\n  //----------------------------------------------------------------------------\n  //! Process the request for computing the alternative checksums\n  //----------------------------------------------------------------------------\n  void ProcessAltXsRequest();\n\n  //----------------------------------------------------------------------------\n  //! Compute time to close a file\n  //----------------------------------------------------------------------------\n  void CloseTime();\n\n  //----------------------------------------------------------------------------\n  //! Compute total time to serve read requests\n  //----------------------------------------------------------------------------\n  void AddReadTime();\n  void AddLayoutReadTime();\n\n  //----------------------------------------------------------------------------\n  //! Compute total time to serve vector read requests\n  //----------------------------------------------------------------------------\n  void AddReadVTime();\n  void AddLayoutReadVTime();\n\n  //----------------------------------------------------------------------------\n  //! Compute total time to serve write requests\n  //----------------------------------------------------------------------------\n  void AddWriteTime();\n  void AddLayoutWriteTime();\n\n  //----------------------------------------------------------------------------\n  //! Compute general statistics on a set of input values\n  //!\n  //! @param vect input collection\n  //! @param min minimum element\n  //! @param max maximum element\n  //! @param sum sum of the elements\n  //! @param avg average value\n  //! @param sigma sigma of the elements\n  //----------------------------------------------------------------------------\n  template <typename T>\n  void ComputeStatistics(const std::vector<T>& vect, T& min, T& max,\n                         T& sum, double& sigma);\n\n  //----------------------------------------------------------------------------\n  //! Create report as a string\n  //----------------------------------------------------------------------------\n  void MakeReportEnv(XrdOucString& reportString);\n\n  //----------------------------------------------------------------------------\n  //! Static method used to start an asynchronous thread which is doing the\n  //! TPC transfer\n  //!\n  //! @param arg XrdFstOfsFile instance object\n  //----------------------------------------------------------------------------\n  static void* StartDoTpcTransfer(void* arg);\n\n  //----------------------------------------------------------------------------\n  //! Do TPC transfer\n  //----------------------------------------------------------------------------\n  void* DoTpcTransfer();\n\n  //----------------------------------------------------------------------------\n  //! TPC clean up - invalidates the TPC keys at the end of a TPC transfer and\n  //! also joins the TPC helper thread\n  //----------------------------------------------------------------------------\n  void TpcCleanup();\n\n  //----------------------------------------------------------------------------\n  //! Extract logid from the opaque info i.e. mgm.logid\n  //!\n  //! @param opaque opaque info\n  //----------------------------------------------------------------------------\n  std::string ExtractLogId(const char* opaque) const;\n\n  //----------------------------------------------------------------------------\n  //! Drop stripe/replica(s) from the MGM\n  //!\n  //! @param fid file identifier\n  //! @param fsid file system id to drop, if 0 then drop all stripes\n  //! @param path file logical path\n  //! @param manager MGM hostname\n  //!\n  //! @return 0 if successful, otherwise error code\n  //----------------------------------------------------------------------------\n  int DropFromMgm(eos::common::FileId::fileid_t fid,\n                  eos::common::FileSystem::fsid_t fsid,\n                  const std::string& path, const std::string& manager);\n\n  //----------------------------------------------------------------------------\n  //! Check if file has been modified while in use\n  //!\n  //! @return -1 if modified, otherwise 0\n  //----------------------------------------------------------------------------\n  int ModifiedWhileInUse();\n\n  //----------------------------------------------------------------------------\n  //! Verify checksum\n  //!\n  //! @return true if ok, otherwise false\n  //----------------------------------------------------------------------------\n  bool VerifyChecksum();\n\n  //----------------------------------------------------------------------------\n  //! Queue file for CTA archiving\n  //!\n  //! @param statinfo The file stat structure\n  //! @param queueing_errmsg Error message from CTA queueing\n  //! @param archive_req_id Output parameter: The archive request ID returned by\n  //! the ProtoEfEndPoint\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool QueueForArchiving(const struct stat& statinfo,\n                         std::string& queueing_errmsg,\n                         std::string& archive_req_id);\n\n  //----------------------------------------------------------------------------\n  //! Notify the workflow protobuf endpoint that the user has closed a file that\n  //! they were writing to\n  //!\n  //! @param file_id The id of the file\n  //! @param file_lid The layout id of the file\n  //! @param file_size The size of the file\n  //! @param file_checksum The checksum of the file\n  //! @param owner_uid The user id of the file owner\n  //! @param owner_gid The group id of the file owner\n  //! @param requestor_name Tha name of the user that closed the file\n  //! @param requestor_groupname The name of the group that closed the file\n  //! @param instance_name Tha name of the EOS instance\n  //! @param fullpath The full path of the file\n  //! @param manager_name The name of the EOS manager\n  //! @param xattrs The extended attributes of teh file to be passed to the\n  //! workflow protobuf endpoint\n  //! @param errmsg_wfe Output parameter: Error message back from the workflow\n  //! protobuf endpoint\n  //! @param archive_req_id Output parameter: The archive request ID returned by\n  //! the ProtoEfEndPoint\n  //!\n  //! @return 0 if successful, error code otherwise\n  //----------------------------------------------------------------------------\n  int NotifyProtoWfEndPointClosew(uint64_t file_id,\n                                  uint32_t file_lid, uint64_t file_size,\n                                  const std::string& file_checksum,\n                                  uint32_t owner_uid, uint32_t owner_gid,\n                                  const std::string& requestor_name,\n                                  const std::string& requestor_groupname,\n                                  const std::string& instance_name,\n                                  const std::string& fullpath,\n                                  const std::string& manager_name,\n                                  const std::map<std::string, std::string>& xattrs,\n                                  std::string& errmsg_wfe,\n                                  std::string& archive_req_id);\n\n  //----------------------------------------------------------------------------\n  //! Send archive failed event to the manager\n  //!\n  //! @param fid The file identifier\n  //! @param errmsg The error message to enclosed in the archive failed event\n  //!\n  //! @return SFS_OK if successful\n  //----------------------------------------------------------------------------\n  int SendArchiveFailedToManager(const uint64_t fid,\n                                 const std::string& errmsg);\n\n  //----------------------------------------------------------------------------\n  //! Decide if close should be done synchronously. There are cases when close\n  //! should happen in the same thread eg. read, http tx, sink writes etc.\n  //!\n  //! @return true if close is synchronous, otherwise false\n  //----------------------------------------------------------------------------\n  bool DoSyncClose();\n\n  //----------------------------------------------------------------------------\n  //! Decide if sync should be done synchronously. There are cases when sync\n  //! should happen in the same thread eg. read, http tx, sink writes etc.\n  //! Keep the same boundary conditions as async close on purpose!\n  //!\n  //! @return true if sync is synchronous, otherwise false\n  //----------------------------------------------------------------------------\n  bool DoSyncSync();\n\n  //----------------------------------------------------------------------------\n  //! Populate and commit FMD info locally\n  //!\n  //! @param info stat info about the logical file\n  //!\n  //! @return 0 if successful, otherwise -1\n  //----------------------------------------------------------------------------\n  int CommitToLocalFmd(const struct stat& info);\n\n  //----------------------------------------------------------------------------\n  //! Commit file information to MGM\n  //!\n  //! @return 0 if successful, otherwise -1 or errno\n  //----------------------------------------------------------------------------\n  int CommitToMgm();\n\n  //----------------------------------------------------------------------------\n  //! Trigger event on close\n  //!\n  //! @param archive_req_id archive request id\n  //!\n  //! @return 0 if successful, other != 0\n  //----------------------------------------------------------------------------\n  int TriggerEventOnClose(const std::string& archive_req_id);\n};\n\n//------------------------------------------------------------------------------\n// Compute general statistics on a set of input values\n//------------------------------------------------------------------------------\ntemplate <typename T>\nvoid XrdFstOfsFile::ComputeStatistics(const std::vector<T>& vect, T& min,\n                                      T& max, T& sum, double& sigma)\n{\n  double avg, sum2;\n  max = sum = sum2 = avg = sigma = 0;\n  min = 0xffffffff;\n  sum = std::accumulate(vect.begin(), vect.end(),\n                        static_cast<unsigned long long>(0));\n  avg = vect.size() ? (1.0 * sum / vect.size()) : 0;\n\n  for (auto it = vect.begin(); it != vect.end(); ++it) {\n    if (*it > max) {\n      max = *it;\n    }\n\n    if (*it < min) {\n      min = *it;\n    }\n\n    sum2 += std::pow((*it - avg), 2);\n  }\n\n  sigma = vect.size() ? (sqrt(sum2 / vect.size())) : 0;\n\n  if (min == 0xffffffff) {\n    min = 0;\n  }\n}\n\nEOSFSTNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "fst/XrdFstOss.cc",
    "content": "//------------------------------------------------------------------------------\n// File XrdFstOss.cc\n// Author Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <fcntl.h>\n#include <strings.h>\n#include <utime.h>\n#include <sys/time.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/resource.h>\n#include <algorithm>\n#include <XrdVersion.hh>\n#include <XrdOuc/XrdOucUtils.hh>\n#include <XrdOuc/XrdOuca2x.hh>\n#include <XrdOuc/XrdOucStream.hh>\n#include \"fst/XrdFstOss.hh\"\n#include \"fst/checksum/ChecksumPlugins.hh\"\n#include \"fst/XrdFstOssFile.hh\"\n\nextern XrdSysError OssEroute;\n\n// Set the version information\nXrdVERSIONINFO(XrdOssGetStorageSystem, FstOss);\n\nextern \"C\"\n{\n  XrdOss*\n  XrdOssGetStorageSystem(XrdOss* native_oss,\n                         XrdSysLogger* Logger,\n                         const char* config_fn,\n                         const char* parms)\n  {\n    OssEroute.SetPrefix(\"FstOss_\");\n    OssEroute.logger(Logger);\n    eos::fst::XrdFstOss* fstOss = new eos::fst::XrdFstOss();\n    return (fstOss->Init(Logger, config_fn) ? 0 : (XrdOss*) fstOss);\n  }\n}\n\nEOSFSTNAMESPACE_BEGIN\n\n#define XrdFstOssFDMINLIM  64\n\n// pointer to the current OSS implementation to be used by the oss files\nXrdFstOss* XrdFstSS = 0;\n\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nXrdFstOss::XrdFstOss() :\n  eos::common::LogId(),\n  mFdFence(-1),\n  mFdLimit(-1),\n  mPrBytes(0),\n  mPrActive(0),\n  mPrDepth(0),\n  mPrQSize(0)\n{\n  eos_debug(\"Calling the constructor of XrdFstOss.\");\n  mPrPBits = (long long)sysconf(_SC_PAGESIZE);\n  mPrPSize = static_cast<int>(mPrPBits);\n  mPrPBits--;\n  mPrPMask = ~mPrPBits;\n}\n\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nXrdFstOss::~XrdFstOss()\n{\n  // empty\n}\n\n\n//------------------------------------------------------------------------------\n// Init function\n//------------------------------------------------------------------------------\nint\nXrdFstOss::Init(XrdSysLogger* lp, const char* configfn)\n{\n  int NoGo = 0;\n  XrdFstSS = this;\n  // Set logging parameters\n  XrdOucString unit = \"fstoss@\";\n  unit += \"localhost\";\n  // Setup the circular in-memory log buffer\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n  g_logging.SetLogPriority(LOG_INFO);\n  g_logging.SetUnit(unit.c_str());\n  eos_debug(\"info=\\\"oss logging configured\\\"\");\n  // Process the configuration file\n  OssEroute.logger(lp);\n  NoGo = Configure(configfn, OssEroute);\n  // Establish the FD limit\n  struct rlimit rlim;\n\n  if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) {\n    eos_warning(\"can not get resource limits, errno=\", errno);\n    mFdLimit = XrdFstOssFDMINLIM;\n  } else {\n    mFdLimit = rlim.rlim_cur;\n  }\n\n  if (mFdFence < 0 || mFdFence >= mFdLimit) {\n    mFdFence = mFdLimit >> 1;\n  }\n\n  return NoGo;\n}\n\n\n//------------------------------------------------------------------------------\n// Configuration function for the oss plugin\n//------------------------------------------------------------------------------\nint\nXrdFstOss::Configure(const char* configfn, XrdSysError& Eroute)\n{\n  char* var;\n  int cfgFD;\n  int NoGo = 0;\n  XrdOucEnv myEnv;\n  XrdOucStream Config(&Eroute, getenv(\"XRDINSTANCE\"), &myEnv, \"=====> \");\n\n  // If there is no config file, return with the defaults sets\n  if (!configfn || !*configfn) {\n    Eroute.Say(\"Config warning: config file not specified; defaults assumed.\");\n    return NoGo;\n  }\n\n  // Try to open the configuration file\n  if ((cfgFD = open(configfn, O_RDONLY, 0)) < 0) {\n    Eroute.Emsg(\"Config\", errno, \"open config file\", configfn);\n    return 1;\n  }\n\n  Config.Attach(cfgFD);\n\n  // Now start reading records until eof\n  while ((var = Config.GetMyFirstWord())) {\n    if (!strncmp(var, \"oss.\", 4)) {\n      if (!strncmp(var + 4, \"preread\", 7)) {\n        NoGo = xprerd(Config, Eroute);\n      }\n    }\n  }\n\n  eos_info(\"preread depth=%i, queue_size=%i and bytes=%i\",\n           mPrDepth, mPrQSize, mPrBytes);\n  Config.Close();\n  (void) close(cfgFD);\n  return NoGo;\n}\n\n\n//------------------------------------------------------------------------------\n// Function xprerd to parse the preread directive\n//------------------------------------------------------------------------------\nint\nXrdFstOss::xprerd(XrdOucStream& Config, XrdSysError& Eroute)\n{\n  static const long long m16 = 16777216LL;\n  char* val;\n  long long lim = 1048576;\n  int depth, qeq = 0, qsz = 128;\n\n  if (!(val = Config.GetWord())) {\n    Eroute.Emsg(\"Config\", \"preread depth not specified\");\n    return 1;\n  }\n\n  if (!strcmp(val, \"on\")) {\n    depth = 3;\n  } else if (XrdOuca2x::a2i(Eroute, \"preread depth\", val, &depth, 0, 1024)) {\n    return 1;\n  }\n\n  while ((val = Config.GetWord())) {\n    if (!strcmp(val, \"limit\")) {\n      if (!(val = Config.GetWord())) {\n        Eroute.Emsg(\"Config\", \"preread limit not specified\");\n        return 1;\n      }\n\n      if (XrdOuca2x::a2sz(Eroute, \"preread limit\", val, &lim, 0, m16)) {\n        return 1;\n      }\n    } else if (!strcmp(val, \"qsize\")) {\n      if (!(val = Config.GetWord())) {\n        Eroute.Emsg(\"Config\", \"preread qsize not specified\");\n        return 1;\n      }\n\n      if (XrdOuca2x::a2i(Eroute, \"preread qsize\", val, &qsz, 0, 1024)) {\n        return 1;\n      }\n\n      if (qsz < depth) {\n        Eroute.Emsg(\"Config\", \"preread qsize must be >= depth\");\n        return 1;\n      }\n    } else {\n      Eroute.Emsg(\"Config\", \"invalid preread option -\", val);\n      return 1;\n    }\n  }\n\n  if (lim < mPrPSize || !qsz) {\n    depth = 0;\n  }\n\n  if (!qeq && depth) {\n    qsz = qsz / (depth / 2 + 1);\n\n    if (qsz < depth) {\n      qsz = depth;\n    }\n  }\n\n  mPrDepth = depth;\n  mPrQSize = qsz;\n  mPrBytes = lim;\n  return 0;\n}\n\n\n//------------------------------------------------------------------------------\n// New file\n//------------------------------------------------------------------------------\nXrdOssDF*\nXrdFstOss::newFile(const char* tident)\n{\n  return (XrdOssDF*) new XrdFstOssFile(tident);\n}\n\n\n//------------------------------------------------------------------------------\n// New directory\n//------------------------------------------------------------------------------\nXrdOssDF*\nXrdFstOss::newDir(const char* tident)\n{\n  eos_debug(\"Calling XrdFstOss::newDir - not used in EOS\");\n  return NULL;\n}\n\n\n//------------------------------------------------------------------------------\n// Unlink file and its block checksum if needed\n//------------------------------------------------------------------------------\nint\nXrdFstOss::Unlink(const char* path, int opts, XrdOucEnv* ep)\n{\n  int retc = 0;\n  struct stat statinfo;\n  // Unlink the block checksum files - this is not the 'best' solution,\n  // but we don't have any info about block checksums\n  Adler xs; // the type does not matter here\n  const char* xs_path = xs.MakeBlockXSPath(path);\n\n  if ((Stat(xs_path, &statinfo))) {\n    eos_debug(\"error=cannot stat closed file - probably already unlinked: %s\",\n              xs_path);\n  } else {\n    if (!xs.UnlinkXSPath()) {\n      eos_debug(\"info=\\\"removed block-xs\\\" path=%s.\", path);\n    }\n  }\n\n  // Unlink the file\n  int i;\n  char local_path[MAXPATHLEN + 1 + 8];\n  strncpy(local_path, path, (size_t)(MAXPATHLEN + 8));\n  local_path[MAXPATHLEN + 8] = '\\0';\n\n  if (lstat(local_path, &statinfo)) {\n    retc = (errno == ENOENT ? 0 : -errno);\n  } else if ((statinfo.st_mode & S_IFMT) == S_IFLNK) {\n    retc = BreakLink(local_path, statinfo);\n  } else if ((statinfo.st_mode & S_IFMT) == S_IFDIR) {\n    i = strlen(local_path);\n\n    if (local_path[i - 1] != '/') {\n      strcpy(local_path + i, \"/\");\n    }\n\n    if ((retc = rmdir(local_path))) {\n      retc = -errno;\n    }\n\n    return retc;\n  }\n\n  if (!retc) {\n    if (unlink(local_path)) {\n      retc = -errno;\n    } else {\n      retc = XrdOssOK;\n    }\n  }\n\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Delete a link file\n//------------------------------------------------------------------------------\nint\nXrdFstOss::BreakLink(const char* local_path, struct stat& statbuff)\n{\n  char lnkbuff[MAXPATHLEN + 64];\n  int lnklen, retc = XrdOssOK;\n\n  // Read the contents of the link\n  if ((lnklen = readlink(local_path, lnkbuff, sizeof(lnkbuff) - 1)) < 0) {\n    return -errno;\n  }\n\n  // Return the actual stat information on the target (which might not exist\n  lnkbuff[lnklen] = '\\0';\n\n  if (stat(lnkbuff, &statbuff)) {\n    statbuff.st_size = 0;\n  } else if (unlink(lnkbuff) && errno != ENOENT) {\n    retc = -errno;\n    OssEroute.Emsg(\"BreakLink\", retc, \"unlink symlink target\", lnkbuff);\n  }\n\n  return retc;\n}\n\n\n//--------------------------------------------------------------------------\n// Chmod on a file\n//--------------------------------------------------------------------------\nint\nXrdFstOss::Chmod(const char* path, mode_t mode, XrdOucEnv* eP)\n{\n  return (chmod(path, mode) ? -errno : XrdOssOK);\n}\n\n\n//--------------------------------------------------------------------------\n// Create a file named 'path' with 'mode' access mode bits set\n//--------------------------------------------------------------------------\nint\nXrdFstOss::Create(const char* tident,\n                  const char* path,\n                  mode_t mode,\n                  XrdOucEnv& env,\n                  int opts)\n{\n  int retc = 0;\n  int datfd;\n  int is_link = 0;\n  int missing = 1;\n  char local_path[MAXPATHLEN + 1], *p, pc;\n  struct stat buf;\n  const int AMode = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH; // 775\n\n  if (strlen(path) >= MAXPATHLEN) {\n    return -ENAMETOOLONG;\n  }\n\n  strncpy(local_path, path, (size_t)(MAXPATHLEN));\n  local_path[MAXPATHLEN] = '\\0';\n\n  // Determine the state of the file. We will need this information as we go on\n  if ((missing = lstat(path, &buf))) {\n    retc = errno;\n  } else {\n    if ((is_link = ((buf.st_mode & S_IFMT) == S_IFLNK))) {\n      if (stat(path, &buf)) {\n        if (errno != ENOENT) {\n          return -errno;\n        }\n\n        OssEroute.Emsg(\"Create\", \"removing dangling link\", path);\n\n        if (unlink(path)) {\n          retc = errno;\n        }\n\n        missing = 1;\n        is_link = 0;\n      }\n    }\n  }\n\n  if (retc && (retc != ENOENT)) {\n    return -retc;\n  }\n\n  // The file must not exist if it's declared \"new\". Otherwise, reuse the space\n  if (!missing) {\n    if (opts & XRDOSS_new) {\n      return -EEXIST;\n    }\n\n    if ((buf.st_mode & S_IFMT) == S_IFDIR) {\n      return -EISDIR;\n    }\n\n    do {\n      datfd = open(local_path, opts >> 8, mode);\n    } while (datfd < 0 && errno == EINTR);\n\n    if (datfd < 0) {\n      return -errno;\n    } else {\n      close(datfd);\n    }\n\n    if (opts >> 8 & O_TRUNC && buf.st_size && is_link) {\n      buf.st_mode = (buf.st_mode & ~S_IFMT) | S_IFLNK;\n    }\n\n    return XrdOssOK;\n  }\n\n  // If the path is to be created, make sure the path exists at this point\n  if ((opts & XRDOSS_mkpath) && (p = rindex(local_path, '/'))) {\n    p++;\n    pc = *p;\n    *p = '\\0';\n    XrdOucUtils::makePath(local_path, AMode);\n    *p = pc;\n  }\n\n  // Simply open the file in the local filesystem, creating it if need be\n  do {\n    datfd = open(local_path, opts >> 8, mode);\n  } while (datfd < 0 && errno == EINTR);\n\n  if (datfd < 0) {\n    return -errno;\n  } else {\n    close(datfd);\n  }\n\n  return XrdOssOK;\n}\n\n\n//------------------------------------------------------------------------------\n// Create directory\n//------------------------------------------------------------------------------\nint\nXrdFstOss::Mkdir(const char* path,\n                 mode_t mode,\n                 int mkpath,\n                 XrdOucEnv* eP)\n{\n  // Operation not supported in EOS\n  return -ENOTSUP;\n}\n\n\n//------------------------------------------------------------------------------\n// Delete a directory from the namespace\n//------------------------------------------------------------------------------\nint\nXrdFstOss::Remdir(const char* path,\n                  int opts,\n                  XrdOucEnv* eP)\n{\n  // Operation not supported in EOS\n  return -ENOTSUP;\n}\n\n\n//------------------------------------------------------------------------------\n// Renames a file with name 'old_name' to 'new_name'\n//------------------------------------------------------------------------------\nint\nXrdFstOss::Rename(const char* oldname,\n                  const char* newname,\n                  XrdOucEnv* old_env,\n                  XrdOucEnv* new_env)\n{\n  int retc2;\n  int retc = XrdOssOK;\n  char local_path_old[MAXPATHLEN + 8];\n  char local_path_new[MAXPATHLEN + 8];\n  char* slash_plus, sPChar;\n  struct stat statbuff;\n  static const mode_t pMode = S_IRWXU | S_IRWXG;\n  strncpy(local_path_old, oldname, (size_t)(MAXPATHLEN + 7));\n  local_path_old[MAXPATHLEN + 7] = '\\0';\n  strncpy(local_path_new, newname, (size_t)(MAXPATHLEN + 7));\n  local_path_new[MAXPATHLEN + 7] = '\\0';\n  // Make sure that the target file does not exist\n  retc2 = lstat(local_path_new, &statbuff);\n\n  if (!retc2) {\n    return -EEXIST;\n  }\n\n  // We need to create the directory path if it does not exist.\n  if (!(slash_plus = rindex(local_path_new, '/'))) {\n    return -EINVAL;\n  }\n\n  slash_plus++;\n  sPChar = *slash_plus;\n  *slash_plus = '\\0';\n  retc2 = XrdOucUtils::makePath(local_path_new, pMode);\n  *slash_plus = sPChar;\n\n  if (retc2) {\n    return retc2;\n  }\n\n  // Check if this path is really a symbolic link elsewhere\n  if (lstat(local_path_old, &statbuff)) {\n    retc = -errno;\n  } else if (rename(local_path_old, local_path_new)) {\n    retc = -errno;\n  }\n\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Determine if file 'path' actually exists\n//------------------------------------------------------------------------------\nint\nXrdFstOss::Stat(const char* path,\n                struct stat* buff,\n                int opts,\n                XrdOucEnv* EnvP)\n{\n  int retc;\n  char local_path[MAXPATHLEN + 1];\n  strncpy(local_path, path, (size_t)(MAXPATHLEN));\n  local_path[MAXPATHLEN] = '\\0';\n\n  // Stat the file in the local filesystem and update access time if so requested\n  if (!stat(local_path, buff)) {\n    if (opts & XRDOSS_updtatm && (buff->st_mode & S_IFMT) == S_IFREG) {\n      struct utimbuf times;\n      times.actime = time(0);\n      times.modtime = buff->st_mtime;\n      utime(local_path, &times);\n    }\n\n    retc = XrdOssOK;\n  } else {\n    retc = (errno ? -errno : -ENOMSG);\n  }\n\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Truncate a file\n//------------------------------------------------------------------------------\nint\nXrdFstOss::Truncate(const char* path,\n                    unsigned long long size,\n                    XrdOucEnv* envP)\n{\n  struct stat statbuff;\n  char local_path[MAXPATHLEN + 1];\n  strncpy(local_path, path, (size_t)(MAXPATHLEN));\n  local_path[MAXPATHLEN] = '\\0';\n\n  if (lstat(local_path, &statbuff)) {\n    return -errno;\n  } else if ((statbuff.st_mode & S_IFMT) == S_IFDIR) {\n    return -EISDIR;\n  } else if ((statbuff.st_mode & S_IFMT) == S_IFLNK) {\n    struct stat buff;\n\n    if (stat(local_path, &buff)) {\n      return -errno;\n    }\n  }\n\n  if (truncate(local_path, size)) {\n    return -errno;\n  }\n\n  return XrdOssOK;\n}\n\n\n//------------------------------------------------------------------------------\n// Add new entry to file name <-> blockchecksum map\n//------------------------------------------------------------------------------\nXrdSysRWLock*\nXrdFstOss::AddMapping(const std::string& fileName,\n                      CheckSum*& blockXs,\n                      bool isRW)\n{\n  XrdSysRWLockHelper wr_lock(mRWMap, 0); // --> wrlock map\n  std::pair<XrdSysRWLock*, CheckSum*> pair_value;\n  eos_debug(\"Initial map size: %i and filename: %s\",\n            mMapFileXs.size(), fileName.c_str());\n\n  if (mMapFileXs.count(fileName)) {\n    pair_value = mMapFileXs[fileName];\n    XrdSysRWLockHelper wr_xslock(pair_value.first, 0); // --> wrlock xs obj\n\n    // If no. ref 0 then the obj is closed and waiting to be deleted so we can\n    // add the new one, else return the old one\n    if (pair_value.second->GetTotalRef() == 0) {\n      pair_value.second->CloseMap();\n      delete pair_value.second;\n      pair_value = std::make_pair(pair_value.first, blockXs);\n      mMapFileXs[fileName] = pair_value;\n      eos_debug(\"Update old entry, map size: %i. \", mMapFileXs.size());\n    } else {\n      blockXs->CloseMap();\n      delete blockXs;\n      blockXs = pair_value.second;\n    }\n\n    blockXs->IncrementRef(isRW);\n    return pair_value.first;\n  } else {\n    XrdSysRWLock* mutex_xs = new XrdSysRWLock();\n    pair_value = std::make_pair(mutex_xs, blockXs);\n    // Can increment without the lock as no one knows about this obj. yet\n    blockXs->IncrementRef(isRW);\n    mMapFileXs[fileName] = pair_value;\n    eos_debug(\"Add completely new obj, map size: %i and filename: %s\",\n              mMapFileXs.size(), fileName.c_str());\n    return mutex_xs;\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Get blockchecksum object for a filname\n//------------------------------------------------------------------------------\nstd::pair<XrdSysRWLock*, CheckSum*>\nXrdFstOss::GetXsObj(const std::string& fileName, bool isRW)\n{\n  XrdSysRWLockHelper rd_lock(mRWMap); // --> rdlock map\n  std::pair<XrdSysRWLock*, CheckSum*> pair_value;\n\n  if (mMapFileXs.count(fileName)) {\n    pair_value = mMapFileXs[fileName];\n    XrdSysRWLock* mutex_xs = pair_value.first;\n    CheckSum* xs_obj = pair_value.second;\n    // Lock xs obj as multiple threads can update the value here\n    XrdSysRWLockHelper xs_wrlock(mutex_xs, 0); // --> wrlock xs obj\n    eos_debug(\"\\nXs obj no ref: %i.\\n\", xs_obj->GetTotalRef());\n\n    if (xs_obj->GetTotalRef() != 0) {\n      xs_obj->IncrementRef(isRW);\n      return std::make_pair(mutex_xs, xs_obj);\n    } else {\n      // If no refs., it means the obj was closed and waiting to be deleted\n      return std::make_pair<XrdSysRWLock*, CheckSum*>(NULL, NULL);\n    }\n  }\n\n  return std::make_pair<XrdSysRWLock*, CheckSum*>(NULL, NULL);\n}\n\n\n//------------------------------------------------------------------------------\n// Drop blockchecksum object for a file name\n//------------------------------------------------------------------------------\nvoid\nXrdFstOss::DropXs(const std::string& fileName, bool force)\n{\n  XrdSysRWLockHelper wr_lock(mRWMap, 0); // --> wrlock map\n  std::pair<XrdSysRWLock*, CheckSum*> pair_value;\n  eos_debug(\"Oss map size before drop: %i.\", mMapFileXs.size());\n\n  if (mMapFileXs.count(fileName)) {\n    pair_value = mMapFileXs[fileName];\n    // If no refs to the checksum, we can safely delete it\n    pair_value.first->WriteLock(); // --> wrlock xs obj\n    eos_debug(\"Xs obj no ref: %i.\", pair_value.second->GetTotalRef());\n\n    if ((pair_value.second->GetTotalRef() == 0) || force) {\n      pair_value.second->CloseMap();\n      pair_value.first->UnLock(); // <-- unlock xs obj\n      delete pair_value.first;\n      delete pair_value.second;\n      mMapFileXs.erase(fileName);\n    } else {\n      eos_debug(\"Do not drop the mapping\");\n      pair_value.first->UnLock(); // <-- unlock xs obj\n    }\n  }\n\n  eos_debug(\"Oss map size after drop: %i.\", mMapFileXs.size());\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/XrdFstOss.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file XrdFstOss.hh\n//! @author Elvin-Alin Sindrilaru - CERN\n//! @brief Oss plugin for EOS doing block checksumming for files\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_FSTOSS_HH__\n#define __EOSFST_FSTOSS_HH__\n\n#include <map>\n#include <string>\n#include \"fst/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Namespace.hh\"\n#include <XrdOss/XrdOss.hh>\n\nclass XrdOucStream;\n\nEOSFSTNAMESPACE_BEGIN\n\n//! Forward declaration\nclass XrdFstOssFile;\nclass CheckSum;\n\n//------------------------------------------------------------------------------\n//! Class XrdFstOss\n//------------------------------------------------------------------------------\nclass XrdFstOss : public XrdOss, public eos::common::LogId\n{\n  friend class XrdFstOssFile;\n\npublic:\n\n  int mFdFence; ///< smalest file FD number allowed\n  int mFdLimit; ///< largest file FD number allowed\n\n  //--------------------------------------------------------------------------\n  //! Constuctor\n  //--------------------------------------------------------------------------\n  XrdFstOss();\n\n\n  //--------------------------------------------------------------------------\n  //! Destructor\n  //--------------------------------------------------------------------------\n  virtual ~XrdFstOss();\n\n\n  //--------------------------------------------------------------------------\n  //! New file\n  //!\n  //! @param tident\n  //!\n  //! @return new oss file object\n  //!\n  //--------------------------------------------------------------------------\n  virtual XrdOssDF* newFile(const char* tident);\n\n\n  //--------------------------------------------------------------------------\n  //! New directory\n  //!\n  //! @param tident\n  //!\n  //! @return new oss directory object\n  //!\n  //--------------------------------------------------------------------------\n  virtual XrdOssDF* newDir(const char* tident);\n\n\n  //--------------------------------------------------------------------------\n  //! Init function\n  //!\n  //! @param lp system logger\n  //! @param configfn configuration file\n  //!\n  //! @return 0 upon success, -errno otherwise\n  //--------------------------------------------------------------------------\n  int Init(XrdSysLogger* lp, const char* configfn);\n\n\n  //--------------------------------------------------------------------------\n  //! Configuration function for the oss plugin\n  //!\n  //! @param configfn configuration file name\n  //! @param eroute error object\n  //!\n  //! @return 0 upon success, !0 otherwise\n  //!\n  //--------------------------------------------------------------------------\n  int Configure(const char* configfn, XrdSysError& eroute);\n\n\n  //--------------------------------------------------------------------------\n  //! Unlink a file\n  //!\n  //! @param path fully qualified name of the file to be removed\n  //! @param opts extra options\n  //! @param ep enviroment information\n  //!\n  //! @return XrdOssOK upon success, -errno otherwise\n  //!\n  //--------------------------------------------------------------------------\n  virtual int Unlink(const char* path, int opts = 0, XrdOucEnv* ep = 0);\n\n\n  //--------------------------------------------------------------------------\n  //! Chmod for a file\n  //!\n  //! @param path file path\n  //! @param mode permission to be set\n  //! @param eP environmental information\n  //!\n  //! @return XrdOssOK upon success and (-errno) upon failure.\n  //!\n  //--------------------------------------------------------------------------\n  virtual int Chmod(const char*, mode_t mode, XrdOucEnv* eP = 0);\n\n\n  //--------------------------------------------------------------------------\n  //! Create a file named 'path' with 'mode' access mode bits set\n  //!\n  //! @param tident client identity\n  //! @param path name of the file to be created\n  //! @param mode access mode bits to be set\n  //! @param env environmental variable\n  //! @param opts Set as follows:\n  //!             XRDOSS_mkpath - create dir path if it does not exist.\n  //!             XRDOSS_new    - the file must not already exist.\n  //!             x00000000     - x are standard open flags (<<8)\n  //!\n  //! @return XrdOssOK upon success and (-errno) otherwise.\n  //!\n  //--------------------------------------------------------------------------\n  virtual int Create(const char* tident,\n                     const char* path,\n                     mode_t mode,\n                     XrdOucEnv& env,\n                     int opts = 0);\n\n\n  //--------------------------------------------------------------------------\n  //! Create a directory\n  //!\n  //! @param path the fully qualified name of the new directory\n  //! @param mode the new mode that the directory is to have\n  //! @param mkpath if true then it makes the full path\n  //! @param envP environmental information\n  //!\n  //! @return XrdOssOK upon success and (-errno) otherwise\n  //!\n  //--------------------------------------------------------------------------\n  virtual int Mkdir(const char* path,\n                    mode_t mode,\n                    int mkpath = 0,\n                    XrdOucEnv* eP = 0);\n\n\n  //--------------------------------------------------------------------------\n  //! Delete a directory from the namespace.\n  //!\n  //! @param path the fully qualified name of the dir to be removed\n  //! @param opts options\n  //! @param envP environmental information\n  //!\n  //! @return XrdOssOK upon success and (-errno) otherwise\n  //--------------------------------------------------------------------------\n  virtual int Remdir(const char* path,\n                     int opts = 0,\n                     XrdOucEnv* eP = 0);\n\n\n  //--------------------------------------------------------------------------\n  //! Renames a file with name 'old_name' to 'new_name'.\n  //!\n  //! @param old_name the fully qualified name of the file to be renamed\n  //! @param new_name the fully qualified name that the file is to have\n  //! @param old_env environmental information for old_name\n  //! @param new_env environmental information for new_name\n  //!\n  //! @return XrdOssOK upon success and -errno otherwise\n  //--------------------------------------------------------------------------\n  int Rename(const char* oldname,\n             const char* newname,\n             XrdOucEnv* old_env,\n             XrdOucEnv* new_env);\n\n\n  //--------------------------------------------------------------------------\n  //! Determine if file 'path' actually exists\n  //!\n  //! @param path the fully qualified name of the file to be tested\n  //! @param buff pointer to a 'stat' structure to hold the file attributes\n  //! @param opts options\n  //! @param env environmental information\n  //!\n  //! @return XrdOssOK upon success and (-errno) otherwise\n  //!\n  //--------------------------------------------------------------------------\n  virtual int Stat(const char* path,\n                   struct stat* buff,\n                   int opts = 0,\n                   XrdOucEnv* eP = 0);\n\n\n  //--------------------------------------------------------------------------\n  //! Truncate a file\n  //!\n  //! @param path the fully qualified name of the target file\n  //! @param size the new size that the file is to have\n  //! @param envP environmental information\n  //!\n  //! @return XrdOssOK upon success and (-errno) otherwise\n  //!\n  //--------------------------------------------------------------------------\n  virtual int Truncate(const char* path,\n                       unsigned long long size,\n                       XrdOucEnv* eP = 0);\n\n\n  //--------------------------------------------------------------------------\n  //! Add new entry to file <-> block checksum map\n  //!\n  //! @param fileName name of the file added to the mapping\n  //! @param blockXs the blockxs object\n  //! @param isRW tell if file is opened in read/write mode\n  //!\n  //! @return mutex for accessing the blockxs object\n  //!\n  //--------------------------------------------------------------------------\n  XrdSysRWLock* AddMapping(const std::string& fileName,\n                           CheckSum*& blockXs,\n                           bool isRW);\n\n\n  //--------------------------------------------------------------------------\n  //! Get block checksum object for a file name\n  //!\n  //! @param fileName file name for which we search for a xs obj\n  //! @param isRW mark if file is opened in read/write mode\n  //!\n  //! @return pair containing the the boockxs obj and its corresponding mutex\n  //!\n  //--------------------------------------------------------------------------\n  std::pair<XrdSysRWLock*, CheckSum*> GetXsObj(const std::string& fileName,\n      bool isRW);\n\n\n  //--------------------------------------------------------------------------\n  //! Drop block checksum object for a filname\n  //!\n  //! @param fileName file name entry to be dropped from the map\n  //! @param force mark if removal is to be forced\n  //!\n  //--------------------------------------------------------------------------\n  void DropXs(const std::string& fileName, bool force = false);\n\n\nprivate:\n\n  XrdSysRWLock mRWMap; ///< rw lock for the file <-> xs map\n  //! map between file names and block xs objects\n  std::map< std::string, std::pair<XrdSysRWLock*, CheckSum*> > mMapFileXs;\n\n  // Parameters for pre-reading (i.e. for fadvise)\n  long long mPrPBits; ///< page lo order bit mask\n  long long mPrPMask; ///< page hi order bit mask\n  int mPrPSize; ///< preread page size\n  int mPrBytes; ///< preread byte limit\n  std::atomic<int> mPrActive; ///< preread activity count\n  short mPrDepth; ///< preread depth\n  short mPrQSize; ///< preread maximum allowed\n\n  //--------------------------------------------------------------------------\n  //! Delete link\n  //!\n  //! @param path the path of the link\n  //! @param statbuff info about the target file\n  //!\n  //! @return XrdOssOK if successful and (-errno) otherwise\n  //!\n  //--------------------------------------------------------------------------\n  int BreakLink(const char* local_path, struct stat& statbuff);\n\n\n  //--------------------------------------------------------------------------\n  //! Function xprerd to parse the directive\n  //!\n  //! preread {<depth> | on} [limit <bytes>]\n  //!         [ qsize [=]<qsz> ]\n  //!\n  //!         <depth>  the number of request to preread ahead of the read.\n  //!                  A value of 0, the inital default, turns off prereads.\n  //!                  Specifying \"on\" sets the value (currently) to 3.\n  //!         <bytes>  Maximum number of bytes to preread. Prereading stops,\n  //!                  regardless of depth, once <bytes> have been preread.\n  //!                  The default is 1M (i.e.1 megabyte). The max is 16M.\n  //!         <qsz>    the queue size after which preread blocking would occur.\n  //!                  The value must be greater than or equal to <depth>.\n  //!                  The value is adjusted to max(<qsz>/(<depth>/2+1),<depth>)\n  //!                  unless the number is preceeded by an equal sign. The\n  //!                  default <qsz> is 128.\n  //!\n  //! @param Config configuration file stream object\n  //! @param Eroute error object\n  //!\n  //! @return 0 upon success or !0 upon failure.\n  //!\n  //--------------------------------------------------------------------------\n  int xprerd(XrdOucStream& Config, XrdSysError& Eroute);\n\n};\n\nEOSFSTNAMESPACE_END\n\n#endif // __EOSFST_FSTOSS_HH__\n"
  },
  {
    "path": "fst/XrdFstOssFile.cc",
    "content": "//------------------------------------------------------------------------------\n// File XrdFstOssFile.cc\n// Author Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/XrdFstOss.hh\"\n#include \"fst/XrdFstOssFile.hh\"\n#include \"fst/checksum/ChecksumPlugins.hh\"\n#include \"common/BufferManager.hh\"\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <algorithm>\n\nnamespace\n{\neos::common::BufferManager gOssBuffMgr(16 * eos::common::MB, 1,\n                                       4  * eos::common::KB);\n}\n\nEOSFSTNAMESPACE_BEGIN\n\n#ifdef __APPLE__\n#define O_LARGEFILE 0\n#endif\n\n//! Pointer to the current OSS implementation to be used by the oss files\nextern XrdFstOss* XrdFstSS;\n\n//------------------------------------------------------------------------------\n// Constuctor\n//------------------------------------------------------------------------------\nXrdFstOssFile::XrdFstOssFile(const char* tid) :\n  XrdOssDF(),\n  eos::common::LogId(),\n  mIsRW(false),\n  mRWLockXs(0),\n  mBlockXs(0),\n  fdDirect(-1)\n{}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nXrdFstOssFile::~XrdFstOssFile()\n{\n  if (fd >= 0) {\n    close(fd);\n  }\n\n  if (fdDirect >= 0) {\n    close(fdDirect);\n  }\n\n  fd = -1;\n  fdDirect = -1;\n}\n\n\n//------------------------------------------------------------------------------\n// Open function\n//------------------------------------------------------------------------------\nint\nXrdFstOssFile::Open(const char* path, int flags, mode_t mode, XrdOucEnv& env)\n{\n  int newfd;\n  const char* val = 0;\n  unsigned long lid = 0;\n  off_t booking_size = 0;\n  eos_debug(\"path=%s\", path);\n  mPath = path;\n  bool directIO = false;\n\n  if (fd >= 0) {\n    return -EBADF;\n  }\n\n  if ((val = env.Get(\"mgm.lid\"))) {\n    lid = atol(val);\n  }\n\n  if ((val = env.Get(\"mgm.bookingsize\"))) {\n    booking_size = strtoull(val, 0, 10);\n\n    if (errno == ERANGE) {\n      eos_err(\"error=invalid bookingsize in capability: %s\", val);\n      return -EINVAL;\n    }\n  }\n\n  // add support for IO flags like synchronous or direct IO\n  if ((val = env.Get(\"mgm.ioflag\"))) {\n    if (!strcmp(val, \"direct\")) {\n      directIO = true;\n      //      flags |= O_DIRECT;\n    } else {\n      if (!strcmp(val, \"sync\")) {\n        // data + meta data\n        flags |= O_SYNC;\n      } else {\n        // data\n        if (!strcmp(val, \"dsync\")) {\n          flags |= O_DSYNC;\n        }\n      }\n    }\n  }\n\n  if ((flags & (O_WRONLY | O_RDWR | O_CREAT | O_TRUNC)) != 0) {\n    mIsRW = true;\n  }\n\n  if ((eos::common::LayoutId::GetBlockChecksum(lid) !=\n       eos::common::LayoutId::kNone) && (mPath[0] == '/')) {\n    std::pair<XrdSysRWLock*, CheckSum*> pair_value;\n    pair_value = XrdFstSS->GetXsObj(path, mIsRW);\n    mRWLockXs = pair_value.first;\n    mBlockXs = pair_value.second;\n\n    if (!mBlockXs) {\n      auto xs_ptr = ChecksumPlugins::GetChecksumObject(lid, true);\n      mBlockXs = xs_ptr.get();\n      // Management of the xs object lifetime is handled by the OSS class\n      xs_ptr.release();\n\n      if (mBlockXs) {\n        XrdOucString xs_path = mBlockXs->MakeBlockXSPath(mPath.c_str());\n        struct stat buf;\n        int retc = XrdFstSS->Stat(mPath.c_str(), &buf);\n\n        if (!mBlockXs->OpenMap(xs_path.c_str(),\n                               (retc ? booking_size : buf.st_size),\n                               eos::common::LayoutId::OssXsBlockSize, mIsRW)) {\n          eos_err(\"error=unable to open blockxs file: %s\", xs_path.c_str());\n          return -EIO;\n        }\n\n        //......................................................................\n        mRWLockXs = XrdFstSS->AddMapping(path, mBlockXs, mIsRW);\n      } else {\n        eos_err(\"error=unable to create the blockxs obj\");\n        return -EIO;\n      }\n    }\n  }\n\n  do {\n#if defined(O_CLOEXEC)\n    fd = open(path, flags | O_LARGEFILE | O_CLOEXEC, mode);\n#else\n    fd = open(path, flags | O_LARGEFILE, mode);\n#endif\n  } while ((fd < 0) && (errno == EINTR));\n\n  if (directIO) {\n    do {\n#if defined(O_CLOEXEC)\n      fdDirect = open(path, flags | O_DIRECT | O_LARGEFILE | O_CLOEXEC, mode);\n#else\n      fdDirect = open(path, flags | O_DIRECT | O_LARGEFILE, mode);\n#endif\n    } while ((fdDirect < 0) && (errno == EINTR));\n  }\n\n  if (fd >= 0) {\n    if (fd < XrdFstSS->mFdFence) {\n#if defined(__linux__) && defined(SOCK_CLOEXEC) && defined(O_CLOEXEC)\n\n      // Relocate the file descriptor if need be and make sure file is closed\n      // on exec\n      if ((newfd = fcntl(fd, F_DUPFD_CLOEXEC, XrdFstSS->mFdFence)) < 0) {\n#else\n\n      if ((newfd = fcntl(fd, F_DUPFD, XrdFstSS->mFdFence)) < 0) {\n#endif\n        eos_err(\"error= unable to reloc FD for \", path);\n      } else {\n        close(fd);\n        fd = newfd;\n      }\n    }\n  }\n\n  if (fdDirect >= 0) {\n    if (fdDirect < XrdFstSS->mFdFence) {\n#if defined(__linux__) && defined(SOCK_CLOEXEC) && defined(O_CLOEXEC)\n\n      // Relocate the file descriptor if need be and make sure file is closed\n      // on exec\n      if ((newfd = fcntl(fdDirect, F_DUPFD_CLOEXEC, XrdFstSS->mFdFence)) < 0) {\n#else\n\n      if ((newfd = fcntl(fdDirect, F_DUPFD, XrdFstSS->mFdFence)) < 0) {\n#endif\n        eos_err(\"error= unable to reloc FD for \", path);\n      } else {\n        close(fdDirect);\n        fdDirect = newfd;\n      }\n    }\n  }\n\n  eos_debug(\"fd=%d fdDirect=%d flags=%x\", fd, fdDirect, flags);\n  return (fd < 0 ? fd : XrdOssOK);\n}\n\n//------------------------------------------------------------------------------\n// Read\n//------------------------------------------------------------------------------\nssize_t\nXrdFstOssFile::Read(void* buffer, off_t offset, size_t length)\n{\n  ssize_t retval = 0;\n  ssize_t nread;\n  std::vector<XrdOucIOVec> pieces;\n  std::shared_ptr<eos::common::Buffer> start_piece, end_piece;\n  eos_debug(\"off=%ji len=%ji\", offset, length);\n\n  if (fd < 0) {\n    return static_cast<ssize_t>(-EBADF);\n  }\n\n  if (!mBlockXs) {\n    // If we don't have blockxs enabled then there is no point in aligning\n    XrdOucIOVec piece = {(long long)offset, (int)length, 0, (char*)buffer};\n    pieces.push_back(piece);\n  } else {\n    // Align to the block checksum offset by possibly reading two extra\n    // pieces in the beginning and/or at the end of the requested piece\n    pieces = AlignBuffer(buffer, offset, length, start_piece, end_piece);\n  }\n\n  // Loop through all the pieces and read them in\n  for (auto piece = pieces.begin(); piece != pieces.end(); ++piece) {\n    int rfd = fd;\n\n    if ((fdDirect >= 0)) {\n      if (\n        (!(piece->offset % 512)) &&\n        (!(piece->size % 512))) {\n        rfd = fdDirect;\n      } else {\n        // we don't want cache data, but we cannot use direct IO\n        posix_fadvise(rfd, piece->offset, piece->size, POSIX_FADV_DONTNEED);\n      }\n    }\n\n    do {\n      nread = pread(rfd, piece->data, piece->size, piece->offset);\n    } while ((nread < 0) && (errno == EINTR));\n\n    if (mBlockXs) {\n      XrdSysRWLockHelper wr_lock(mRWLockXs, 0);\n\n      if ((nread > 0) &&\n          (!mBlockXs->CheckBlockSum(piece->offset, piece->data, nread))) {\n        eos_err(\"error=read block-xs error offset=%zu, length=%zu\",\n                piece->offset, piece->size);\n        retval = -EIO;\n        break;\n      }\n    }\n\n    if (nread < 0) {\n      eos_err(\"msg=\\\"failed read\\\" offset=%zu length=%zu\", piece->offset,\n              piece->size);\n      retval = -EIO;\n      break;\n    }\n\n    if (nread > 0) {\n      char* ptr_buff, *ptr_piece;\n      off_t off_copy;\n      size_t len_copy;\n\n      if (piece->offset < offset) {\n        // Copy back begin edge\n        ptr_buff = (char*)buffer;\n        off_copy = offset - piece->offset;\n        len_copy = std::min((size_t)(nread - off_copy), length);\n        ptr_piece = piece->data + off_copy;\n        ptr_buff = (char*)memcpy((void*)ptr_buff, ptr_piece, len_copy);\n        retval += len_copy;\n      } else if ((piece->offset >= offset) &&\n                 (piece->offset + nread >= (ssize_t)(offset + length))) {\n        // Copy back end edge\n        len_copy = std::min((ssize_t)(offset + length - piece->offset), nread);\n        ptr_buff = (char*)buffer + (piece->offset - offset);\n\n        if (ptr_buff != piece->data) {\n          ptr_buff = (char*)memcpy((void*)ptr_buff, piece->data, len_copy);\n        }\n\n        retval += len_copy;\n      } else {\n        retval += nread;\n      }\n    }\n  }\n\n  // Recycle any buffer used for the blockxs alignment\n  gOssBuffMgr.Recycle(start_piece);\n  gOssBuffMgr.Recycle(end_piece);\n\n  if (retval > (ssize_t)length) {\n    eos_err(\"msg=\\\"read more than requested\\\" ret=%ji length=%ju\", retval, length);\n    return -EIO;\n  }\n\n  return retval;\n}\n\n//------------------------------------------------------------------------------\n// Align request to the blockchecksum offset so that the whole request is\n// checksummed\n//------------------------------------------------------------------------------\nstd::vector<XrdOucIOVec>\nXrdFstOssFile::AlignBuffer(void* buffer, off_t offset, size_t length,\n                           std::shared_ptr<eos::common::Buffer>& start_piece,\n                           std::shared_ptr<eos::common::Buffer>& end_piece)\n{\n  XrdOucIOVec piece;\n  std::vector<XrdOucIOVec> resp;\n  resp.reserve(3); // worst case\n  uint64_t blk_size = eos::common::LayoutId::OssXsBlockSize;\n  off_t chunk_end = offset + length;\n  off_t align_start = (offset / blk_size) * blk_size;\n  off_t align_end = (chunk_end / blk_size) * blk_size;\n\n  if (align_start < offset) {\n    // Extra piece at the beginning\n    start_piece = gOssBuffMgr.GetBuffer(eos::common::LayoutId::OssXsBlockSize);\n\n    if (start_piece == nullptr) {\n      throw std::bad_alloc();\n    }\n\n    piece = {(long long) align_start, (int) blk_size, 0,\n             start_piece->GetDataPtr()\n            };\n    resp.push_back(piece);\n    align_start += blk_size;\n  }\n\n  // Add rest of pieces if this was not all\n  if (align_start < chunk_end) {\n    if (align_start != align_end) {\n      // Add the main piece\n      char* ptr_buff = (char*)buffer + (align_start - offset);\n      piece = {(long long) align_start, (int)(align_end - align_start),\n               0,  ptr_buff\n              };\n      resp.push_back(piece);\n    }\n\n    if (((off_t)align_end < chunk_end) &&\n        ((off_t)(align_end + blk_size) > chunk_end)) {\n      // Extra piece at the end\n      end_piece = gOssBuffMgr.GetBuffer(eos::common::LayoutId::OssXsBlockSize);\n\n      if (end_piece == nullptr) {\n        throw std::bad_alloc();\n      }\n\n      piece = {(long long) align_end, (int) blk_size, 0,\n               end_piece->GetDataPtr()\n              };\n      resp.push_back(piece);\n    }\n  }\n\n  return resp;\n}\n\n//------------------------------------------------------------------------------\n// Read raw\n//------------------------------------------------------------------------------\nssize_t\nXrdFstOssFile::ReadRaw(void* buffer, off_t offset, size_t length)\n{\n  return Read(buffer, offset, length);\n}\n\n//------------------------------------------------------------------------------\n// Write\n//------------------------------------------------------------------------------\nssize_t\nXrdFstOssFile::ReadV(XrdOucIOVec* readV, int n)\n{\n  ssize_t rdsz;\n  ssize_t totBytes = 0;\n#if defined(__linux__)\n  long long begOff, endOff, begLst = -1, endLst = -1;\n  int nPR = n;\n\n  // Indicate we are in preread state and see if we have exceeded the limit\n  if ((fdDirect == -1) && XrdFstSS->mPrDepth\n      && ((XrdFstSS->mPrActive++) < XrdFstSS->mPrQSize)\n      && (n > 2)) {\n    int faBytes = 0;\n\n    for (nPR = 0; (nPR < XrdFstSS->mPrDepth) &&\n         (faBytes < XrdFstSS->mPrBytes); nPR++)\n      if (readV[nPR].size > 0) {\n        begOff = XrdFstSS->mPrPMask & readV[nPR].offset;\n        endOff = XrdFstSS->mPrPBits | (readV[nPR].offset + readV[nPR].size);\n        rdsz = endOff - begOff + 1;\n\n        if ((begOff > endLst || endOff < begLst) && (rdsz < XrdFstSS->mPrBytes)) {\n          (void) posix_fadvise(fd, begOff, rdsz, POSIX_FADV_WILLNEED);\n          eos_debug(\"fadvise fd=%i off=%lli len=%ji\", fd, begOff, rdsz);\n          faBytes += rdsz;\n        }\n\n        begLst = begOff;\n        endLst = endOff;\n      }\n  }\n\n#endif\n\n  // Read in the vector and do a pre-advise if we support that\n  for (int i = 0; i < n; i++) {\n    // Use normal block read since it also does the blockxs and we have the\n    // guarantee that the previous advice was issued for the full block to\n    // be read even with the 4K alignment since fadvice does this on its own\n    rdsz = Read(readV[i].data, readV[i].offset, readV[i].size);\n\n    if (rdsz < 0 || rdsz != readV[i].size) {\n      totBytes = (rdsz < 0 ? -errno : -ESPIPE);\n      break;\n    }\n\n    totBytes += rdsz;\n#if defined(__linux__)\n\n    if (nPR < n && readV[nPR].size > 0) {\n      begOff = XrdFstSS->mPrPMask &  readV[nPR].offset;\n      endOff = XrdFstSS->mPrPBits | (readV[nPR].offset + readV[nPR].size);\n      rdsz = endOff - begOff + 1;\n\n      if ((begOff > endLst || endOff < begLst)\n          &&  rdsz <= XrdFstSS->mPrBytes) {\n        posix_fadvise(fd, begOff, rdsz, POSIX_FADV_WILLNEED);\n        eos_debug(\"fadvise fd=%i off=%lli len=%ji\", fd, begOff, rdsz);\n      }\n\n      begLst = begOff;\n      endLst = endOff;\n    }\n\n    nPR++;\n#endif\n  }\n\n// All done, return bytes read.\n#if defined(__linux__)\n\n  if (XrdFstSS->mPrDepth) {\n    XrdFstSS->mPrActive--;\n  }\n\n#endif\n  return totBytes;\n}\n\n\n//------------------------------------------------------------------------------\n// Vector write\n//------------------------------------------------------------------------------\nssize_t\nXrdFstOssFile::WriteV(XrdOucIOVec* writeV, int n)\n{\n  ssize_t nbytes = 0;\n  ssize_t curCount = 0;\n\n  for (int i = 0; i < n; i++) {\n    curCount = Write((void*)writeV[i].data,\n                     (off_t)writeV[i].offset,\n                     (size_t)writeV[i].size);\n\n    if (curCount != writeV[i].size) {\n      if (curCount < 0) {\n        return curCount;\n      }\n\n      return -ESPIPE;\n    }\n\n    nbytes += curCount;\n  }\n\n  return nbytes;\n}\n//------------------------------------------------------------------------------\n// Chmod function\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\nssize_t\nXrdFstOssFile::Write(const void* buffer, off_t offset, size_t length)\n{\n  ssize_t retval;\n\n  if (fd < 0) {\n    return static_cast<ssize_t>(-EBADF);\n  }\n\n  if ((fdDirect >= 0) &&\n      (!(offset % 512)) &&\n      (!(length % 512))) {\n    // tell the kernel to drop cache pages for the buffered fd\n    posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);\n\n    // try direct IO\n    do {\n      retval = pwrite(fdDirect, buffer, length, offset);\n    } while ((retval < 0) && (errno == EINTR));\n  } else {\n    // buffered IO\n    do {\n      retval = pwrite(fd, buffer, length, offset);\n    } while ((retval < 0) && (errno == EINTR));\n\n    if ((retval > 0) && (fdDirect >= 0)) {\n      // force the data flush out of the buffer cache\n      if (fdatasync(fd)) {\n        retval = -1;\n      }\n    }\n  }\n\n  if ((retval > 0) && mBlockXs) {\n    XrdSysRWLockHelper wr_lock(mRWLockXs, 0);\n    mBlockXs->AddBlockSum(offset, static_cast<const char*>(buffer), retval);\n  }\n\n  return (retval >= 0 ? retval : static_cast<ssize_t>(-errno));\n}\n\n//------------------------------------------------------------------------------\n// Get file status\n//------------------------------------------------------------------------------\n\nint\nXrdFstOssFile::Fchmod(mode_t mode)\n{\n  return (fchmod(fd, mode) ? -errno : XrdOssOK);\n}\n\n\n//------------------------------------------------------------------------------\n// Sync file to local disk\n//------------------------------------------------------------------------------\nint\nXrdFstOssFile::Fstat(struct stat* statinfo)\n{\n  return (fstat(fd, statinfo) ? -errno : XrdOssOK);\n}\n\n\n//------------------------------------------------------------------------------\n// Fsync the file\n//------------------------------------------------------------------------------\nint\nXrdFstOssFile::Fsync()\n{\n  return (fsync(fd) ? -errno : XrdOssOK);\n}\n\n\n//............................................................................\n// Note that space adjustment will occur when the file is closed, not here\n//............................................................................\nint\nXrdFstOssFile::Ftruncate(unsigned long long flen)\n{\n  off_t newlen = flen;\n\n  if ((sizeof(newlen) < sizeof(flen)) && (flen >> 31)) {\n    return -EOVERFLOW;\n  }\n\n  return (ftruncate(fd, newlen) ? -errno : XrdOssOK);\n}\n\n//------------------------------------------------------------------------------\n// Get file descriptor\n//------------------------------------------------------------------------------\n\nint\nXrdFstOssFile::getFD()\n{\n  return fd;\n}\n\n\n//------------------------------------------------------------------------------\n// Close function\n//------------------------------------------------------------------------------\nint\nXrdFstOssFile::Close(long long* retsz)\n{\n  bool delete_mapping = false;\n  bool unlinked = false;\n\n  if (fd < 0) {\n    return -EBADF;\n  }\n\n  //............................................................................\n  // Code dealing with block checksums\n  //............................................................................\n  if (mBlockXs) {\n    struct stat statinfo;\n\n    if ((XrdFstSS->Stat(mPath.c_str(), &statinfo))) {\n      eos_err(\"error=close - cannot stat unlinked file: %s\", mPath.c_str());\n      unlinked = true;\n    }\n\n    XrdSysRWLockHelper wr_lock(mRWLockXs, 0); // ---> wrlock xs obj\n    mBlockXs->DecrementRef(mIsRW);\n\n    if (mBlockXs->GetTotalRef() >= 1) {\n      //........................................................................\n      // If multiple references\n      //........................................................................\n      if ((mBlockXs->GetNumRef(true) == 0) && mIsRW) {\n        //......................................................................\n        // If one last writer and this is the current one\n        //......................................................................\n        if (! unlinked) {\n          if (!mBlockXs->ChangeMap(statinfo.st_size, true)) {\n            eos_err(\"error=unable to change block checksum map for file %s\", mPath.c_str());\n          } else {\n            eos_info(\"info=\\\"adjusting block XS map\\\"\");\n          }\n\n          if (!mBlockXs->AddBlockSumHoles(getFD())) {\n            eos_warning(\"warning=unable to fill holes of block checksum map for file %s\",\n                        mPath.c_str());\n          }\n        }\n      }\n    } else {\n      //........................................................................\n      // Just one reference left (the current one)\n      //........................................................................\n      if (mIsRW && !unlinked) {\n        if (!mBlockXs->ChangeMap(statinfo.st_size, true)) {\n          eos_err(\"error=Unable to change block checksum map for file %s\", mPath.c_str());\n        } else {\n          eos_info(\"info=\\\"adjusting block XS map\\\"\");\n        }\n\n        if (!mBlockXs->AddBlockSumHoles(getFD())) {\n          eos_warning(\"warning=unable to fill holes of block checksum map for file %s\",\n                      mPath.c_str());\n        }\n      }\n\n      if (!mBlockXs->CloseMap()) {\n        eos_err(\"error=unable to close block checksum map for file %s\", mPath.c_str());\n      }\n\n      delete_mapping = true;\n    }\n  }\n\n  //............................................................................\n  if (delete_mapping) {\n    eos_debug(\"Delete entry from oss map for file %s\", mPath.c_str());\n    XrdFstSS->DropXs(mPath.c_str());\n  } else {\n    eos_debug(\"No delete from oss map for file %s\", mPath.c_str());\n  }\n\n  if (unlinked) {\n    close(fd);\n    fd = -1;\n\n    if (fdDirect >= 0) {\n      close(fdDirect);\n      fdDirect = -1;\n    }\n\n    return -EIO;\n  }\n\n  //............................................................................\n  if (close(fd)) {\n    if (fdDirect >= 0) {\n      close(fdDirect);\n    }\n\n    return -errno;\n  }\n\n  if (fdDirect >= 0) {\n    if (close(fdDirect)) {\n      return -errno;\n    }\n  }\n\n  fd = -1;\n  fdDirect = -1;\n  return XrdOssOK;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/XrdFstOssFile.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file XrdFstOssFile.hh\n//! @author Elvin-Alin Sindrilaru - CERN\n//! @brief Oss plugin for EOS dealing with files and adding block checksuming\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_FSTOSSFILE_HH__\n#define __EOSFST_FSTOSSFILE_HH__\n\n#include \"fst/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include <XrdOss/XrdOss.hh>\n#include <map>\n#include <string>\n\n//! Forward declarations\nnamespace eos\n{\nnamespace common\n{\nclass Buffer;\n}\n}\n\nEOSFSTNAMESPACE_BEGIN\n\nclass CheckSum;\n\n//------------------------------------------------------------------------------\n//! Class XrdFstOssFile using blockxs information\n//------------------------------------------------------------------------------\nclass XrdFstOssFile : public XrdOssDF, public eos::common::LogId\n{\npublic:\n  //--------------------------------------------------------------------------\n  //! Constuctor\n  //!\n  //! @param tid\n  //--------------------------------------------------------------------------\n  XrdFstOssFile(const char* tid);\n\n  //--------------------------------------------------------------------------\n  //! Destructor\n  //--------------------------------------------------------------------------\n  virtual ~XrdFstOssFile();\n\n  //--------------------------------------------------------------------------\n  //! Open function\n  //!\n  //! @param path file path\n  //! @param flags open flags\n  //! @param mode open mode\n  //! @param env env variables passed to the function\n  //!\n  //! @return XrdOssOK upon success, -errno otherwise\n  //--------------------------------------------------------------------------\n  virtual int Open(const char* path, int flags, mode_t mode, XrdOucEnv& env);\n\n  //--------------------------------------------------------------------------\n  //! Read\n  //!\n  //! @param buffer data container for read operation\n  //! @param offset file offet\n  //! @param length read length\n  //!\n  //! @return number of bytes read\n  //--------------------------------------------------------------------------\n  ssize_t Read(void* buffer, off_t offset, size_t length);\n\n  //--------------------------------------------------------------------------\n  //! Read raw\n  //!\n  //! @param buffer data container for read operation\n  //! @param offset file offet\n  //! @param length read length\n  //!\n  //! @return number of bytes read\n  //--------------------------------------------------------------------------\n  ssize_t ReadRaw(void* buffer, off_t offset, size_t length);\n\n  //--------------------------------------------------------------------------\n  //! Vector read\n  //!\n  //! @param readV generic data structure for vector reads\n  //! @param n number of individual reads in the vector request\n  //!\n  //! @return is successful total number of bytes read, otherwise -ESPIPE\n  //--------------------------------------------------------------------------\n  ssize_t ReadV(XrdOucIOVec* readV, int n);\n\n  //--------------------------------------------------------------------------\n  //! Vector write\n  //!\n  //! @param writeV generic data structure for vector writes\n  //! @param n number of individual reads in the vector request\n  //!\n  //! @return is successful total number of bytes written, otherwise -ESPIPE\n  //--------------------------------------------------------------------------\n  ssize_t WriteV(XrdOucIOVec* writeV, int n);\n\n  //--------------------------------------------------------------------------\n  //! Write\n  //!\n  //! @param buffer data container for write operation\n  //! @param offset file offet\n  //! @param length write length\n  //!\n  //! @return number of byes written\n  //--------------------------------------------------------------------------\n  ssize_t Write(const void* buffer, off_t offset, size_t length);\n\n  //--------------------------------------------------------------------------\n  //! Chmod function\n  //!\n  //! @param mode the mode to set\n  //!\n  //! @return XrdOssOK upon success, (-errno) upon failure\n  //--------------------------------------------------------------------------\n  int Fchmod(mode_t mode);\n\n  //--------------------------------------------------------------------------\n  //! Get file status\n  //!\n  //! @param statinfo stat info structure\n  //!\n  //! @return XrdOssOK upon success, (-errno) upon failure\n  //--------------------------------------------------------------------------\n  int Fstat(struct stat* statinfo);\n\n  //--------------------------------------------------------------------------\n  //! Sync file to local disk\n  //--------------------------------------------------------------------------\n  int Fsync();\n\n  //--------------------------------------------------------------------------\n  //! Truncate the file\n  //!\n  //! @param offset truncate offset\n  //!\n  //! @return XrdOssOK upon success, -1 upon failure\n  //--------------------------------------------------------------------------\n  int Ftruncate(unsigned long long offset);\n\n  //--------------------------------------------------------------------------\n  //! Get file descriptor\n  //--------------------------------------------------------------------------\n  int getFD();\n\n  //--------------------------------------------------------------------------\n  //! Close function\n  //!\n  //! @param retsz\n  //!\n  //! @return XrdOssOK upon success, -1 otherwise\n  //--------------------------------------------------------------------------\n  virtual int Close(long long* retsz = 0);\n\nprivate:\n  XrdOucString mPath; ///< path of the file\n  bool mIsRW; ///< mark if opened for rw operations\n  XrdSysRWLock* mRWLockXs; ///< rw lock for the block xs\n  CheckSum* mBlockXs; ///< block xs object\n  int fdDirect; ///< fd for direct IO\n\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  //--------------------------------------------------------------------------\n  //! Align request to the blockchecksum offset so that the whole request is\n  //! checksummed\n  //!\n  //! @param buffer buffer holding the data\n  //! @param offset request offset\n  //! @param lenght request length\n  //! @param start_piece extra piece of buffer used to align at the\n  //!        beginning to the blockxs boundary for the given read request\n  //! @param end_piece extrace piece of buffer used to align at the end to\n  //!        the blockxs boundary for the given read request\n  //!\n  //! @return vector of aligned requests. There should never be more than 3\n  //!         requests since in worst case both ends are unaligned\n  //--------------------------------------------------------------------------\n  std::vector<XrdOucIOVec>\n  AlignBuffer(void* buffer, off_t offset, size_t length,\n              std::shared_ptr<eos::common::Buffer>& start_piece,\n              std::shared_ptr<eos::common::Buffer>& end_piece);\n};\n\nEOSFSTNAMESPACE_END\n\n#endif // __EOSFST_FSTOSSFILE_HH__\n"
  },
  {
    "path": "fst/checksum/Adler.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Adler.cc\n// Author: Andreas-Joachim Peters/Elvin Sindrilaru - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"fst/checksum/Adler.hh\"\n\n#ifdef ISAL_FOUND\n#include <isa-l.h>\n#endif\n\nEOSFSTNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\nbool\nAdler::Add(const char* buffer, size_t length, off_t offset)\n{\n  std::lock_guard<std::mutex> lock(mMutex);\n\n  if (offset < 0) {\n    offset = adleroffset;\n  }\n\n  if (offset != adleroffset) {\n    needsRecalculation = true;\n  }\n\n  if (finalized) {              /* handle read/append case, no problem in this case */\n    finalized = false;\n  }\n\n  adler = adler32(0L, Z_NULL, 0);\n  Chunk currChunk;\n#ifdef ISAL_FOUND\n  adler = isal_adler32(adler, (const unsigned char*) buffer, length);\n#else\n  adler = adler32(adler, (const Bytef*) buffer, length);\n#endif\n  adleroffset = offset + length;\n\n  if (adleroffset > maxoffset) {\n    maxoffset = adleroffset;\n  }\n\n  currChunk.offset = offset;\n  currChunk.length = length;\n  currChunk.adler = adler;\n  map = AddElementToMap(map, currChunk);\n  return true;\n}\n\n/*----------------------------------------------------------------------------*/\nMapChunks&\nAdler::AddElementToMap(MapChunks& map, Chunk& chunk)\n{\n  off_t offEndChunk = chunk.offset + chunk.length;\n  IterMap iter = map.find(offEndChunk);\n\n  if (iter != map.end()) {\n    map.erase(iter);\n    map.insert(std::pair<off_t, Chunk > (offEndChunk, chunk));\n  } else {\n    map.insert(std::pair<off_t, Chunk > (offEndChunk, chunk));\n  }\n\n  return map;\n}\n\n/*----------------------------------------------------------------------------*/\nconst char*\nAdler::GetHexChecksum() const\n{\n  char sadler[1024];\n  sprintf(sadler, \"%08x\", adler);\n  Checksum = sadler;\n  return Checksum.c_str();\n}\n\n/*----------------------------------------------------------------------------*/\nconst char*\nAdler::GetBinChecksum(int& len) const\n{\n  len = sizeof(unsigned int);\n  return (char*) &adler;\n}\n\n/*----------------------------------------------------------------------------*/\n\n/* compute the adler value of the map if we have the full map\n * (starts from 0 and there are no holes)\n */\nvoid\nAdler::ValidateAdlerMap() const\n{\n  unsigned int value;\n  adler = adler32(0L, Z_NULL, 0);\n\n  if (map.begin() == map.end()) {\n    adler = adler32(0L, Z_NULL, 0);\n    return;\n  }\n\n  IterMap iter1 = map.begin();\n  IterMap iter2 = iter1;\n  value = iter1->second.adler;\n  IterMap iter3;\n  //  for (iter3= map.begin(); iter3!= map.end(); iter3++) {\n  //    fprintf(stderr,\"ADLER VALIDATE %llu %llu %llu %x\\n\", iter3->first,iter3->second.offset, iter3->second.length, iter3->second.adler);\n  //  }\n\n  if (iter1->second.offset != 0) {\n    // the first chunk is not at the beginning\n    needsRecalculation = true;\n    adler = adler32(0L, Z_NULL, 0);\n    return;\n  }\n\n  if (map.begin() == map.end()) {\n    //we have no chunk\n    adler = adler32(0L, Z_NULL, 0);\n    return;\n  }\n\n  needsRecalculation = false;\n  iter2++;\n\n  if (iter2 == map.end()) {\n    if ((iter1->first) != maxoffset) {\n      // there was probably some overwrite\n      needsRecalculation = true;\n    } else {\n      if (iter1->second.offset != 0) {\n        needsRecalculation = true;\n      }\n    }\n\n    //we have one chunk\n    adler = value;\n    return;\n  }\n\n  off_t appliedoffset = 0;\n\n  for (; iter2 != map.end(); iter1++, iter2++) {\n    appliedoffset = iter2->first;\n    value = adler32_combine(value, iter2->second.adler, iter2->second.length);\n\n    if (iter1->first != iter2->second.offset) {\n      needsRecalculation = true;\n      break;\n    }\n  }\n\n  if (appliedoffset != maxoffset) {\n    // there was probably some overwrite\n    needsRecalculation = true;\n  }\n\n  if (!needsRecalculation) {\n    adler = value;\n  } else {\n    adler = adler32(0L, Z_NULL, 0);\n  }\n\n  fflush(stdout);\n  return;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nAdler::Finalize() const\n{\n  if (!finalized) {\n    ValidateAdlerMap();\n    finalized = true;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/checksum/Adler.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Adler.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_ADLER_HH__\n#define __EOSFST_ADLER_HH__\n\n#include \"fst/Namespace.hh\"\n#include \"fst/checksum/CheckSum.hh\"\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <zlib.h>\n#include <map>\n\nEOSFSTNAMESPACE_BEGIN\n\nstruct ltoff_t {\n\n  bool operator()(off_t s1, off_t s2) const\n  {\n    return (s1 < s2);\n  }\n};\n\ntypedef struct {\n  off_t offset;\n  size_t length;\n  unsigned int adler;\n} Chunk;\n\ntypedef std::map<off_t, Chunk, ltoff_t> MapChunks;\ntypedef std::map<off_t, Chunk, ltoff_t>::iterator IterMap;\n\nclass Adler : public CheckSum\n{\nprivate:\n  off_t adleroffset;\n  off_t maxoffset;\n  mutable unsigned int adler;\n  mutable MapChunks map;\n\npublic:\n  Adler() : CheckSum(\"adler\")\n  {\n    Reset();\n  }\n\n  unsigned int GetAdler()\n  {\n    return adler;\n  }\n\n  bool Add(const char* buffer, size_t length, off_t offset);\n  MapChunks& AddElementToMap(MapChunks& map, Chunk& chunk);\n\n  off_t\n  GetLastOffset() const override\n  {\n    return adleroffset;\n  }\n\n  off_t\n  GetMaxOffset() const override\n  {\n    return maxoffset;\n  }\n\n  int\n  GetCheckSumLen() const override\n  {\n    return sizeof(unsigned int);\n  }\n  void ValidateAdlerMap() const;\n\n  const char* GetHexChecksum() const override;\n  const char* GetBinChecksum(int& len) const override;\n\n  void Finalize() const override;\n\n  void\n  Reset()\n  {\n    map.clear();\n    adleroffset = 0;\n    adler = adler32(0L, Z_NULL, 0);\n    needsRecalculation = false;\n    maxoffset = 0;\n    finalized = false;\n  }\n\n  void\n  ResetInit(off_t offsetInit, size_t lengthInit, const char* checksumInitHex)\n  {\n    Chunk currChunk;\n    maxoffset = 0;\n    adleroffset = offsetInit + lengthInit;\n\n    // Theck if this is actually a valid pointer or a filled string\n    if ((checksumInitHex == NULL) || (!strlen(checksumInitHex))) {\n      return;\n    }\n\n    int checksumInitBin = strtol(checksumInitHex, 0, 16);\n\n    // if a file is truncated we get 0,0,<some checksum => reset to 0\n    if (lengthInit != 0) {\n      adler = checksumInitBin;\n    } else {\n      adler = adler32(0L, Z_NULL, 0);\n    }\n\n    currChunk.offset = offsetInit;\n    currChunk.length = lengthInit;\n    currChunk.adler = adler;\n    map.clear();\n    map = AddElementToMap(map, currChunk);\n    maxoffset = (offsetInit + lengthInit);\n    needsRecalculation = false;\n  }\n\n  virtual\n  ~Adler() { };\n\n};\n\nEOSFSTNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "fst/checksum/BLAKE3.hh",
    "content": "// ----------------------------------------------------------------------\n// File: BLAKE3.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_BLAKE3_HH__\n#define __EOSFST_BLAKE3_HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"fst/Namespace.hh\"\n#include \"fst/checksum/CheckSum.hh\"\n#include \"common/blake3/blake3.h\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdSys/XrdSysPthread.hh>\n/*----------------------------------------------------------------------------*/\n#include <zlib.h>\n\n#ifdef ISAL_FOUND\n#include <isa-l.h>\n#endif\n\n/*----------------------------------------------------------------------------*/\n\nEOSFSTNAMESPACE_BEGIN\n\nclass BLAKE3 : public CheckSum\n{\nprivate:\n  blake3_hasher hasher;\n  mutable uint8_t blake3checksum[BLAKE3_OUT_LEN];\n  off_t blake3offset;\n  mutable bool finalized;\n\npublic:\n\n  BLAKE3() : CheckSum(\"blake3\")\n  {\n    Reset();\n  }\n\n  off_t\n  GetLastOffset() const override\n  {\n    return blake3offset;\n  }\n\n  bool\n  Add(const char* buffer, size_t length, off_t offset)\n  {\n    std::lock_guard<std::mutex> lock(mMutex);\n\n    if (offset < 0) {\n      offset = blake3offset;\n    }\n\n    if (offset != blake3offset) {\n      needsRecalculation = true;\n      return false;\n    }\n\n    if (finalized) {\n      return false;\n    }\n\n    blake3_hasher_update(&hasher, buffer, length);\n    blake3offset += length;\n    return true;\n  }\n\n  const char*\n  GetHexChecksum() const override\n  {\n    if (!finalized) {\n      Finalize();\n    }\n\n    Checksum = \"\";\n\n    for (size_t i = 0; i < BLAKE3_OUT_LEN; ++i) {\n      char b3[3];\n      sprintf(b3, \"%02x\", blake3checksum[i]);\n      Checksum += b3;\n    }\n\n    return Checksum.c_str();\n  }\n\n  const char*\n  GetBinChecksum(int& len) const override\n  {\n    if (!finalized) {\n      Finalize();\n    }\n\n    len = BLAKE3_OUT_LEN;\n    return (char*) &blake3checksum;\n  }\n\n  int\n  GetCheckSumLen() const override\n  {\n    return BLAKE3_OUT_LEN;\n  }\n\n  void\n  Reset()\n  {\n    blake3_hasher_init(&hasher);\n    memset(&blake3checksum, 0, BLAKE3_OUT_LEN);\n    blake3offset = 0;\n    needsRecalculation = 0;\n    finalized = false;\n  }\n\n  void\n  Finalize() const override\n  {\n    if (!finalized) {\n      blake3_hasher_finalize(&hasher, blake3checksum, BLAKE3_OUT_LEN);\n      finalized = true;\n    }\n  }\n\n  virtual\n  ~BLAKE3() { };\n\n};\n\nEOSFSTNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "fst/checksum/CRC32.hh",
    "content": "// ----------------------------------------------------------------------\n// File: CRC32.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_CRC32_HH__\n#define __EOSFST_CRC32_HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"fst/Namespace.hh\"\n#include \"fst/checksum/CheckSum.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucString.hh>\n/*----------------------------------------------------------------------------*/\n#include <zlib.h>\n\n/*----------------------------------------------------------------------------*/\n\nEOSFSTNAMESPACE_BEGIN\n\nclass CRC32 : public CheckSum\n{\nprivate:\n  off_t crc32offset;\n  unsigned int crcsum;\n\npublic:\n\n  CRC32() : CheckSum(\"crc32\")\n  {\n    Reset();\n  }\n\n  off_t\n  GetLastOffset() const override\n  {\n    return crc32offset;\n  }\n\n  bool\n  Add(const char* buffer, size_t length, off_t offset)\n  {\n    std::lock_guard<std::mutex> lock(mMutex);\n\n    if (offset < 0) {\n      offset = crc32offset;\n    }\n\n    if (offset != crc32offset) {\n      needsRecalculation = true;\n      return false;\n    }\n\n    crcsum = crc32(crcsum, (const Bytef*) buffer, length);\n    crc32offset += length;\n    return true;\n  }\n\n  const char*\n  GetHexChecksum() const override\n  {\n    char scrc32[1024];\n    sprintf(scrc32, \"%08x\", crcsum);\n    Checksum = scrc32;\n    return Checksum.c_str();\n  }\n\n  const char*\n  GetBinChecksum(int& len) const override\n  {\n    len = sizeof(unsigned int);\n    return (char*) &crcsum;\n  }\n\n  int\n  GetCheckSumLen() const override\n  {\n    return sizeof(unsigned int);\n  }\n\n  void\n  Reset()\n  {\n    crc32offset = 0;\n    crcsum = crc32(0L, Z_NULL, 0);\n    needsRecalculation = 0;\n    finalized = false;\n  }\n\n  virtual\n  ~CRC32() { };\n\n};\n\nEOSFSTNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "fst/checksum/CRC32C.hh",
    "content": "// ----------------------------------------------------------------------\n// File: CRC32C.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_CRC32C_HH__\n#define __EOSFST_CRC32C_HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"fst/Namespace.hh\"\n#include \"fst/checksum/CheckSum.hh\"\n#include \"common/crc32c/crc32c.h\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdSys/XrdSysPthread.hh>\n/*----------------------------------------------------------------------------*/\n#include <zlib.h>\n\n#ifdef ISAL_FOUND\n#include <isa-l.h>\n#endif\n\n/*----------------------------------------------------------------------------*/\n\nEOSFSTNAMESPACE_BEGIN\n\nclass CRC32C : public CheckSum\n{\nprivate:\n  off_t crc32coffset;\n  mutable uint32_t crcsum;\n\npublic:\n\n  CRC32C() : CheckSum(\"crc32c\")\n  {\n    Reset();\n  }\n\n  off_t\n  GetLastOffset() const override\n  {\n    return crc32coffset;\n  }\n\n  bool\n  Add(const char* buffer, size_t length, off_t offset)\n  {\n    std::lock_guard<std::mutex> lock(mMutex);\n\n    if (offset < 0) {\n      offset = crc32coffset;\n    }\n\n    if (offset != crc32coffset) {\n      needsRecalculation = true;\n      return false;\n    }\n\n    if (finalized) {                /* handle read + append case, a little hackish. Alternative: set needsRecalculation */\n      crcsum = ~crcsum;           /* undo what Finalize did under the hood */\n      finalized = false;\n    }\n\n#ifdef ISAL_FOUND\n    crcsum = crc32_iscsi((unsigned char*) buffer, length, crcsum);\n#else\n    crcsum = checksum::crc32c(crcsum, (const Bytef*) buffer, length);\n#endif\n    crc32coffset += length;\n    return true;\n  }\n\n  const char*\n  GetHexChecksum() const override\n  {\n    if (!finalized) {\n      Finalize();\n    }\n\n    char scrc32[1024];\n    sprintf(scrc32, \"%08x\", crcsum);\n    Checksum = scrc32;\n    return Checksum.c_str();\n  }\n\n  const char*\n  GetBinChecksum(int& len) const override\n  {\n    if (!finalized) {\n      Finalize();\n    }\n\n    len = sizeof(unsigned int);\n    return (char*) &crcsum;\n  }\n\n  int\n  GetCheckSumLen() const override\n  {\n    return sizeof(unsigned int);\n  }\n\n  void\n  Reset()\n  {\n    crcsum = checksum::crc32cInit();\n    crc32coffset = 0;\n    needsRecalculation = 0;\n    finalized = false;\n  }\n\n  void\n  Finalize() const override\n  {\n    if (!finalized) {\n      crcsum = checksum::crc32cFinish(crcsum);\n      finalized = true;\n    }\n  }\n\n  virtual\n  ~CRC32C() { };\n\n};\n\nEOSFSTNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "fst/checksum/CRC64.hh",
    "content": "// ----------------------------------------------------------------------\n// File: CRC64.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_CRC64_HH__\n#define __EOSFST_CRC64_HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"fst/Namespace.hh\"\n#include \"fst/checksum/CheckSum.hh\"\n#include \"common/crc32c/crc32c.h\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdSys/XrdSysPthread.hh>\n/*----------------------------------------------------------------------------*/\n#include <zlib.h>\n/*----------------------------------------------------------------------------*/\n\nEOSFSTNAMESPACE_BEGIN\n\nstatic const uint64_t crc64_tab[256] = {\n  0x0000000000000000ULL, 0x42f0e1eba9ea3693ULL,\n  0x85e1c3d753d46d26ULL, 0xc711223cfa3e5bb5ULL,\n  0x493366450e42ecdfULL, 0x0bc387aea7a8da4cULL,\n  0xccd2a5925d9681f9ULL, 0x8e224479f47cb76aULL,\n  0x9266cc8a1c85d9beULL, 0xd0962d61b56fef2dULL,\n  0x17870f5d4f51b498ULL, 0x5577eeb6e6bb820bULL,\n  0xdb55aacf12c73561ULL, 0x99a54b24bb2d03f2ULL,\n  0x5eb4691841135847ULL, 0x1c4488f3e8f96ed4ULL,\n  0x663d78ff90e185efULL, 0x24cd9914390bb37cULL,\n  0xe3dcbb28c335e8c9ULL, 0xa12c5ac36adfde5aULL,\n  0x2f0e1eba9ea36930ULL, 0x6dfeff5137495fa3ULL,\n  0xaaefdd6dcd770416ULL, 0xe81f3c86649d3285ULL,\n  0xf45bb4758c645c51ULL, 0xb6ab559e258e6ac2ULL,\n  0x71ba77a2dfb03177ULL, 0x334a9649765a07e4ULL,\n  0xbd68d2308226b08eULL, 0xff9833db2bcc861dULL,\n  0x388911e7d1f2dda8ULL, 0x7a79f00c7818eb3bULL,\n  0xcc7af1ff21c30bdeULL, 0x8e8a101488293d4dULL,\n  0x499b3228721766f8ULL, 0x0b6bd3c3dbfd506bULL,\n  0x854997ba2f81e701ULL, 0xc7b97651866bd192ULL,\n  0x00a8546d7c558a27ULL, 0x4258b586d5bfbcb4ULL,\n  0x5e1c3d753d46d260ULL, 0x1cecdc9e94ace4f3ULL,\n  0xdbfdfea26e92bf46ULL, 0x990d1f49c77889d5ULL,\n  0x172f5b3033043ebfULL, 0x55dfbadb9aee082cULL,\n  0x92ce98e760d05399ULL, 0xd03e790cc93a650aULL,\n  0xaa478900b1228e31ULL, 0xe8b768eb18c8b8a2ULL,\n  0x2fa64ad7e2f6e317ULL, 0x6d56ab3c4b1cd584ULL,\n  0xe374ef45bf6062eeULL, 0xa1840eae168a547dULL,\n  0x66952c92ecb40fc8ULL, 0x2465cd79455e395bULL,\n  0x3821458aada7578fULL, 0x7ad1a461044d611cULL,\n  0xbdc0865dfe733aa9ULL, 0xff3067b657990c3aULL,\n  0x711223cfa3e5bb50ULL, 0x33e2c2240a0f8dc3ULL,\n  0xf4f3e018f031d676ULL, 0xb60301f359dbe0e5ULL,\n  0xda050215ea6c212fULL, 0x98f5e3fe438617bcULL,\n  0x5fe4c1c2b9b84c09ULL, 0x1d14202910527a9aULL,\n  0x93366450e42ecdf0ULL, 0xd1c685bb4dc4fb63ULL,\n  0x16d7a787b7faa0d6ULL, 0x5427466c1e109645ULL,\n  0x4863ce9ff6e9f891ULL, 0x0a932f745f03ce02ULL,\n  0xcd820d48a53d95b7ULL, 0x8f72eca30cd7a324ULL,\n  0x0150a8daf8ab144eULL, 0x43a04931514122ddULL,\n  0x84b16b0dab7f7968ULL, 0xc6418ae602954ffbULL,\n  0xbc387aea7a8da4c0ULL, 0xfec89b01d3679253ULL,\n  0x39d9b93d2959c9e6ULL, 0x7b2958d680b3ff75ULL,\n  0xf50b1caf74cf481fULL, 0xb7fbfd44dd257e8cULL,\n  0x70eadf78271b2539ULL, 0x321a3e938ef113aaULL,\n  0x2e5eb66066087d7eULL, 0x6cae578bcfe24bedULL,\n  0xabbf75b735dc1058ULL, 0xe94f945c9c3626cbULL,\n  0x676dd025684a91a1ULL, 0x259d31cec1a0a732ULL,\n  0xe28c13f23b9efc87ULL, 0xa07cf2199274ca14ULL,\n  0x167ff3eacbaf2af1ULL, 0x548f120162451c62ULL,\n  0x939e303d987b47d7ULL, 0xd16ed1d631917144ULL,\n  0x5f4c95afc5edc62eULL, 0x1dbc74446c07f0bdULL,\n  0xdaad56789639ab08ULL, 0x985db7933fd39d9bULL,\n  0x84193f60d72af34fULL, 0xc6e9de8b7ec0c5dcULL,\n  0x01f8fcb784fe9e69ULL, 0x43081d5c2d14a8faULL,\n  0xcd2a5925d9681f90ULL, 0x8fdab8ce70822903ULL,\n  0x48cb9af28abc72b6ULL, 0x0a3b7b1923564425ULL,\n  0x70428b155b4eaf1eULL, 0x32b26afef2a4998dULL,\n  0xf5a348c2089ac238ULL, 0xb753a929a170f4abULL,\n  0x3971ed50550c43c1ULL, 0x7b810cbbfce67552ULL,\n  0xbc902e8706d82ee7ULL, 0xfe60cf6caf321874ULL,\n  0xe224479f47cb76a0ULL, 0xa0d4a674ee214033ULL,\n  0x67c58448141f1b86ULL, 0x253565a3bdf52d15ULL,\n  0xab1721da49899a7fULL, 0xe9e7c031e063acecULL,\n  0x2ef6e20d1a5df759ULL, 0x6c0603e6b3b7c1caULL,\n  0xf6fae5c07d3274cdULL, 0xb40a042bd4d8425eULL,\n  0x731b26172ee619ebULL, 0x31ebc7fc870c2f78ULL,\n  0xbfc9838573709812ULL, 0xfd39626eda9aae81ULL,\n  0x3a28405220a4f534ULL, 0x78d8a1b9894ec3a7ULL,\n  0x649c294a61b7ad73ULL, 0x266cc8a1c85d9be0ULL,\n  0xe17dea9d3263c055ULL, 0xa38d0b769b89f6c6ULL,\n  0x2daf4f0f6ff541acULL, 0x6f5faee4c61f773fULL,\n  0xa84e8cd83c212c8aULL, 0xeabe6d3395cb1a19ULL,\n  0x90c79d3fedd3f122ULL, 0xd2377cd44439c7b1ULL,\n  0x15265ee8be079c04ULL, 0x57d6bf0317edaa97ULL,\n  0xd9f4fb7ae3911dfdULL, 0x9b041a914a7b2b6eULL,\n  0x5c1538adb04570dbULL, 0x1ee5d94619af4648ULL,\n  0x02a151b5f156289cULL, 0x4051b05e58bc1e0fULL,\n  0x87409262a28245baULL, 0xc5b073890b687329ULL,\n  0x4b9237f0ff14c443ULL, 0x0962d61b56fef2d0ULL,\n  0xce73f427acc0a965ULL, 0x8c8315cc052a9ff6ULL,\n  0x3a80143f5cf17f13ULL, 0x7870f5d4f51b4980ULL,\n  0xbf61d7e80f251235ULL, 0xfd913603a6cf24a6ULL,\n  0x73b3727a52b393ccULL, 0x31439391fb59a55fULL,\n  0xf652b1ad0167feeaULL, 0xb4a25046a88dc879ULL,\n  0xa8e6d8b54074a6adULL, 0xea16395ee99e903eULL,\n  0x2d071b6213a0cb8bULL, 0x6ff7fa89ba4afd18ULL,\n  0xe1d5bef04e364a72ULL, 0xa3255f1be7dc7ce1ULL,\n  0x64347d271de22754ULL, 0x26c49cccb40811c7ULL,\n  0x5cbd6cc0cc10fafcULL, 0x1e4d8d2b65facc6fULL,\n  0xd95caf179fc497daULL, 0x9bac4efc362ea149ULL,\n  0x158e0a85c2521623ULL, 0x577eeb6e6bb820b0ULL,\n  0x906fc95291867b05ULL, 0xd29f28b9386c4d96ULL,\n  0xcedba04ad0952342ULL, 0x8c2b41a1797f15d1ULL,\n  0x4b3a639d83414e64ULL, 0x09ca82762aab78f7ULL,\n  0x87e8c60fded7cf9dULL, 0xc51827e4773df90eULL,\n  0x020905d88d03a2bbULL, 0x40f9e43324e99428ULL,\n  0x2cffe7d5975e55e2ULL, 0x6e0f063e3eb46371ULL,\n  0xa91e2402c48a38c4ULL, 0xebeec5e96d600e57ULL,\n  0x65cc8190991cb93dULL, 0x273c607b30f68faeULL,\n  0xe02d4247cac8d41bULL, 0xa2dda3ac6322e288ULL,\n  0xbe992b5f8bdb8c5cULL, 0xfc69cab42231bacfULL,\n  0x3b78e888d80fe17aULL, 0x7988096371e5d7e9ULL,\n  0xf7aa4d1a85996083ULL, 0xb55aacf12c735610ULL,\n  0x724b8ecdd64d0da5ULL, 0x30bb6f267fa73b36ULL,\n  0x4ac29f2a07bfd00dULL, 0x08327ec1ae55e69eULL,\n  0xcf235cfd546bbd2bULL, 0x8dd3bd16fd818bb8ULL,\n  0x03f1f96f09fd3cd2ULL, 0x41011884a0170a41ULL,\n  0x86103ab85a2951f4ULL, 0xc4e0db53f3c36767ULL,\n  0xd8a453a01b3a09b3ULL, 0x9a54b24bb2d03f20ULL,\n  0x5d45907748ee6495ULL, 0x1fb5719ce1045206ULL,\n  0x919735e51578e56cULL, 0xd367d40ebc92d3ffULL,\n  0x1476f63246ac884aULL, 0x568617d9ef46bed9ULL,\n  0xe085162ab69d5e3cULL, 0xa275f7c11f7768afULL,\n  0x6564d5fde549331aULL, 0x279434164ca30589ULL,\n  0xa9b6706fb8dfb2e3ULL, 0xeb46918411358470ULL,\n  0x2c57b3b8eb0bdfc5ULL, 0x6ea7525342e1e956ULL,\n  0x72e3daa0aa188782ULL, 0x30133b4b03f2b111ULL,\n  0xf7021977f9cceaa4ULL, 0xb5f2f89c5026dc37ULL,\n  0x3bd0bce5a45a6b5dULL, 0x79205d0e0db05dceULL,\n  0xbe317f32f78e067bULL, 0xfcc19ed95e6430e8ULL,\n  0x86b86ed5267cdbd3ULL, 0xc4488f3e8f96ed40ULL,\n  0x0359ad0275a8b6f5ULL, 0x41a94ce9dc428066ULL,\n  0xcf8b0890283e370cULL, 0x8d7be97b81d4019fULL,\n  0x4a6acb477bea5a2aULL, 0x089a2aacd2006cb9ULL,\n  0x14dea25f3af9026dULL, 0x562e43b4931334feULL,\n  0x913f6188692d6f4bULL, 0xd3cf8063c0c759d8ULL,\n  0x5dedc41a34bbeeb2ULL, 0x1f1d25f19d51d821ULL,\n  0xd80c07cd676f8394ULL, 0x9afce626ce85b507ULL,\n};\n\nclass CRC64 : public CheckSum\n{\nprivate:\n  off_t crc64offset;\n  uint64_t crcsum;\n\npublic:\n\n  CRC64() : CheckSum(\"crc64\")\n  {\n    Reset();\n  }\n\n  uint64_t crc64(uint64_t crc, const unsigned char* s, uint64_t l)\n  {\n    while (l) {\n      int i = ((int)(crc >> 56) ^ *s++) & 0xFF;\n      crc = crc64_tab[i] ^ (crc << 8);\n      l--;\n    }\n\n    return crc;\n  }\n\n\n\n  off_t\n  GetLastOffset() const override\n  {\n    return crc64offset;\n  }\n\n  bool\n  Add(const char* buffer, size_t length, off_t offset)\n  {\n    std::lock_guard<std::mutex> lock(mMutex);\n\n    if (offset < 0) {\n      offset = crc64offset;\n    }\n\n    if (offset != crc64offset) {\n      needsRecalculation = true;\n      return false;\n    }\n\n    crcsum = crc64(crcsum, (unsigned char*) buffer, length);\n    crc64offset += length;\n    return true;\n  }\n\n  const char*\n  GetHexChecksum() const override\n  {\n    if (!finalized) {\n      Finalize();\n    }\n\n    char scrc64[1024];\n    sprintf(scrc64, \"%016lx\", crcsum);\n    Checksum = scrc64;\n    return Checksum.c_str();\n  }\n\n  const char*\n  GetBinChecksum(int& len) const override\n  {\n    if (!finalized) {\n      Finalize();\n    }\n\n    len = sizeof(unsigned int);\n    return (char*) &crcsum;\n  }\n\n  int\n  GetCheckSumLen() const override\n  {\n    return sizeof(unsigned int);\n  }\n\n  void\n  Reset()\n  {\n    crcsum = 0;\n    crc64offset = 0;\n    needsRecalculation = 0;\n    finalized = false;\n  }\n\n  void\n  Finalize() const override\n  {\n    if (!finalized) {\n      finalized = true;\n    }\n  }\n\n  virtual\n  ~CRC64() { };\n\n};\n\nEOSFSTNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "fst/checksum/CheckSum.cc",
    "content": "// ----------------------------------------------------------------------\n// File: CheckSum.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/checksum/CheckSum.hh\"\n#include \"fst/checksum/Adler.hh\"\n#include \"fst/checksum/BLAKE3.hh\"\n#include \"fst/checksum/CRC32.hh\"\n#include \"fst/checksum/CRC32C.hh\"\n#include \"fst/checksum/CheckSum.hh\"\n#include \"fst/checksum/MD5.hh\"\n#include \"fst/checksum/SHA1.hh\"\n#include \"fst/utils/ScanRate.hh\"\n#include \"common/XattrCompat.hh\"\n#include \"common/Path.hh\"\n#include \"common/Logging.hh\"\n#include \"common/CloExec.hh\"\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n#include <sys/mman.h>\n#include <fcntl.h>\n#include <thread>\n\n#ifdef __APPLE__\n#define SYSGETTID (std::hash<std::thread::id>()(std::this_thread::get_id()))\n#else\n#include \"common/thread_id.hh\"\n#include <sys/syscall.h>\n#define SYSGETTID (eos::common::thread_id())\n#endif\n\nEOSFSTNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\n// Static variable + sig handler to deal with SIGBUS error\n/*----------------------------------------------------------------------------*/\nstatic sigjmp_buf sj_env[65536];\n\n/*----------------------------------------------------------------------------*/\nstatic void\nsigbus_hdl(int sig, siginfo_t* siginfo, void* ptr)\n{\n  // jump to the saved program state to catch SIGBUS caused by illegal mmapped memory access\n  siglongjmp(sj_env[ SYSGETTID % 65536], 1);\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nCheckSum::Compare(const char* refchecksum) const\n{\n  bool result = true;\n\n  for (int i = 0; i < GetCheckSumLen(); i++) {\n    int len;\n\n    if (refchecksum[i] != GetBinChecksum(len)[i]) {\n      result = false;\n    }\n  }\n\n  return result;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nCheckSum::Compare(const CheckSum* other) const\n{\n  if (GetCheckSumLen() != other->GetCheckSumLen()) {\n    return false;\n  }\n\n  int len;\n  auto thisBinChecksum = GetBinChecksum(len);\n  auto otherBinChecksum = other->GetBinChecksum(len);\n\n  for (int i = 0; i < GetCheckSumLen(); i++) {\n    if (thisBinChecksum[i] != otherBinChecksum[i]) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\n/*----------------------------------------------------------------------------*/\n\n/* scan of a complete file */\nbool\nCheckSum::ScanFile(const char* path, unsigned long long& scansize,\n                   std::chrono::milliseconds& scantime, int rate, off_t offset,\n                   Load* fstload, const std::string& dirpath, int max_rate)\n{\n  int fd = open(path, O_RDONLY);\n\n  if (fd < 0) {\n    return false;\n  }\n\n  (void) eos::common::CloExec::Set(fd);\n  bool scan = ScanFile(fd, scansize, scantime, rate,\n                       (std::string(path) == \"/dev/stdin\") ? true : false, offset,\n                       fstload, dirpath, max_rate);\n  (void) close(fd);\n  return scan;\n}\n\n/*----------------------------------------------------------------------------*/\n\n/* scan of a complete file */\nbool\nCheckSum::ScanFile(int fd, unsigned long long& scansize,\n                   std::chrono::milliseconds& scantime,\n                   int rate, bool is_stdin, off_t offset,\n                   Load* fstload, const std::string& dirpath,\n                   int max_rate)\n{\n  static int buffersize = 1024 * 1024;\n  scansize = 0;\n  scantime = std::chrono::milliseconds::zero();\n  auto opentime = std::chrono::system_clock::now();\n  Reset();\n  int nread = 0;\n  char* buffer = 0;\n\n  if (posix_memalign((void**) &buffer, 256 * 1024, buffersize)) {\n    fprintf(stderr, \"warning: failed to use posix_memaling \\n\");\n    buffer = (char*) malloc(buffersize);\n  }\n\n  if (!buffer) {\n    return false;\n  }\n\n  if (offset > 0 && !is_stdin) {\n    if (lseek(fd, offset, SEEK_SET) < 0) {\n      free(buffer);\n      return false;\n    }\n  }\n\n  do {\n    errno = 0;\n    nread = read(fd, buffer, buffersize);\n\n    if (nread < 0) {\n      free(buffer);\n      return false;\n    }\n\n    if (nread > 0) {\n      Add(buffer, nread);\n      scansize += nread;\n    }\n\n    if (rate) {\n      // regulate the verification rate\n      eos::fst::utils::EnforceAndAdjustScanRate(scansize, opentime, rate,\n          fstload, dirpath.c_str(), max_rate);\n    }\n  } while (nread != 0);\n\n  auto currenttime = std::chrono::system_clock::now();\n  scantime = std::chrono::duration_cast<std::chrono::milliseconds>\n             (currenttime - opentime);\n  Finalize();\n  free(buffer);\n  return true;\n}\n\n/*----------------------------------------------------------------------------*/\n\n/* scan of a complete file using an opened layout*/\n\n\nbool\nCheckSum::ScanFile(ReadCallBack rcb, unsigned long long& scansize,\n                   std::chrono::milliseconds& scantime, int rate,\n                   Load* fstload, const std::string& dirpath,\n                   int max_rate)\n{\n  static int buffersize = 1024 * 1024;\n  scansize = 0;\n  scantime = std::chrono::milliseconds::zero();\n  auto opentime = std::chrono::system_clock::now();\n  Reset();\n  //move at the right location in the  file\n  int nread = 0;\n  off_t offset = 0;\n  char* buffer = (char*) malloc(buffersize);\n\n  if (!buffer) {\n    return false;\n  }\n\n  do {\n    errno = 0;\n    rcb.data.offset = offset;\n    rcb.data.buffer = buffer;\n    rcb.data.size = buffersize;\n    nread = rcb.call(&rcb.data);\n\n    if (nread < 0) {\n      free(buffer);\n      return false;\n    }\n\n    if (nread > 0) {\n      Add(buffer, nread, offset);\n      offset += nread;\n    }\n\n    if (rate) {\n      // regulate the verification rate\n      eos::fst::utils::EnforceAndAdjustScanRate(offset, opentime, rate,\n          fstload, dirpath.c_str(), max_rate);\n    }\n  } while (nread == buffersize);\n\n  auto currenttime = std::chrono::system_clock::now();\n  scantime = std::chrono::duration_cast<std::chrono::milliseconds>\n             (currenttime - opentime);\n  scansize = (unsigned long long) offset;\n  Finalize();\n  free(buffer);\n  return true;\n}\n\n/*----------------------------------------------------------------------------*/\n\n/* scan of a file for which we already have computed a partial checksum */\nbool\nCheckSum::ScanFile(const char* path, off_t offsetInit, size_t lengthInit,\n                   const char* checksumInit,\n                   unsigned long long& scansize, std::chrono::milliseconds& scantime, int rate,\n                   Load* fstload, const std::string& dirpath,\n                   int max_rate)\n{\n  static int buffersize = 1024 * 1024;\n  scansize = 0;\n  scantime = std::chrono::milliseconds::zero();\n  auto opentime = std::chrono::system_clock::now();\n  int fd = open(path, O_RDONLY);\n\n  if (fd < 0) {\n    return false;\n  }\n\n  (void) eos::common::CloExec::Set(fd);\n  ResetInit(offsetInit, lengthInit, checksumInit);\n\n  //move at the right location in the  file\n  if (lseek(fd, offsetInit + lengthInit, SEEK_SET) < 0) {\n    (void) close(fd);\n    return false;\n  }\n\n  int nread = 0;\n  off_t offset = 0;\n  char* buffer = (char*) malloc(buffersize);\n\n  if (!buffer) {\n    (void) close(fd);\n    return false;\n  }\n\n  do {\n    errno = 0;\n    nread = read(fd, buffer, buffersize);\n\n    if (nread < 0) {\n      close(fd);\n      free(buffer);\n      return false;\n    }\n\n    Add(buffer, nread, offset);\n    offset += nread;\n    // Regulate the verification rate\n    eos::fst::utils::EnforceAndAdjustScanRate(offset, opentime, rate,\n        fstload, dirpath.c_str(),\n        max_rate);\n  } while (nread == buffersize);\n\n  auto currenttime = std::chrono::system_clock::now();\n  scantime = std::chrono::duration_cast<std::chrono::milliseconds>\n             (currenttime - opentime);\n  scansize = (unsigned long long) offset;\n  Finalize();\n  close(fd);\n  free(buffer);\n  return true;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nCheckSum::OpenMap(const char* mapfilepath, size_t maxfilesize, size_t blocksize,\n                  bool isRW)\n{\n  CheckSumMapFile = mapfilepath;\n  struct stat buf;\n  eos::common::Path cPath(mapfilepath);\n\n  // check if the directory exists\n  if (::stat(cPath.GetParentPath(), &buf)) {\n    if ((::mkdir(cPath.GetParentPath(),\n                 S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) && (errno != EEXIST)) {\n      return false;\n    }\n\n    if (::chown(cPath.GetParentPath(), 2, 2)) {\n      return false;\n    }\n  }\n\n  BlockSize = blocksize;\n\n  if (!BlockSize) {\n    fprintf(stderr, \"Fatal: [CheckSum::OpenMap] blocksize=0\\n\");\n    return false;\n  }\n\n  // we always open for rw mode\n  ChecksumMapFd = ::open(mapfilepath, O_RDWR | O_CREAT,\n                         S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);\n\n  //  fprintf(stderr,\"rw=%d u=%d g=%d errno=%d\\n\", isRW, geteuid(), getegid(), errno);\n  if (ChecksumMapFd < 0) {\n    return false;\n  }\n\n  (void) eos::common::CloExec::Set(ChecksumMapFd);\n  char sblocksize[1024];\n  snprintf(sblocksize, sizeof(sblocksize) - 1, \"%llu\",\n           (unsigned long long) blocksize);\n  std::string sBlockSize = sblocksize;\n  std::string sBlockCheckSum = Name.c_str();\n#ifdef __APPLE__\n\n  if (fsetxattr(ChecksumMapFd, \"user.eos.blocksize\", sBlockSize.c_str(),\n                sBlockSize.size(), 0, 0) ||\n      fsetxattr(ChecksumMapFd, \"user.eos.blockchecksum\", sBlockCheckSum.c_str(),\n                sBlockCheckSum.size(), 0, 0))\n#else\n  if (fsetxattr(ChecksumMapFd, \"user.eos.blocksize\", sBlockSize.c_str(),\n                sBlockSize.size(), 0) ||\n      fsetxattr(ChecksumMapFd, \"user.eos.blockchecksum\", sBlockCheckSum.c_str(),\n                sBlockCheckSum.size(), 0))\n#endif\n  {\n    // fprintf(stderr,\"CheckSum::OpenMap => cannot set extended attributes errno=%d!\\n\", errno);\n    close(ChecksumMapFd);\n    return false;\n  }\n\n  ChecksumMapSize = ((maxfilesize / blocksize) + 1) * (GetCheckSumLen());\n  // Need this in case we have to deallocate!\n  ChecksumMapOpenSize = ChecksumMapSize;\n\n  if (isRW) {\n    int rc = 0;\n    // Truncate the blockxs file to the new size based on the real file size of\n    // the data file\n    rc = ftruncate(ChecksumMapFd, ChecksumMapSize);\n#ifdef __APPLE__\n    rc = ftruncate(ChecksumMapFd, ChecksumMapSize);\n#else\n    rc = posix_fallocate(ChecksumMapFd, 0, ChecksumMapSize);\n#endif\n\n    if (rc) {\n      close(ChecksumMapFd);\n      //      fprintf(stderr,\"posix allocate failed\\n\")\n      return false;\n    }\n\n    ChecksumMap = (char*) mmap(0, ChecksumMapSize, PROT_READ | PROT_WRITE,\n                               MAP_SHARED, ChecksumMapFd, 0);\n  } else {\n    // make sure the file on disk is large enough\n    struct stat xsstat;\n    xsstat.st_size = 0;\n    // Don't need to check the rc, it is covered by the logic afterwards\n    (void) fstat(ChecksumMapFd, &xsstat);\n\n    if (xsstat.st_size < (off_t) ChecksumMapSize) {\n      if (ftruncate(ChecksumMapFd, ChecksumMapSize)) {\n        ChecksumMapSize = 0;\n        //    fprintf(stderr,\"CheckSum:ChangeMap ftruncate failed\\n\");\n        close(ChecksumMapFd);\n        return false;\n      }\n    } else {\n      ChecksumMapSize = xsstat.st_size;\n    }\n\n    ChecksumMap = (char*) mmap(0, ChecksumMapSize, PROT_READ | PROT_WRITE,\n                               MAP_SHARED, ChecksumMapFd, 0);\n  }\n\n  if (ChecksumMap == MAP_FAILED) {\n    close(ChecksumMapFd);\n    fprintf(stderr, \"Fatal: [CheckSum::OpenMap] mmap failed\\n\");\n    return false;\n  }\n\n  // instantiate a signal handler for SIGBUS\n  struct sigaction act;\n  memset(&act, 0, sizeof(act));\n  act.sa_sigaction = eos::fst::sigbus_hdl;\n  act.sa_flags = SA_SIGINFO;\n\n  if (sigaction(SIGBUS, &act, 0)) {\n    fprintf(stderr, \"Fatal: [CheckSum::OpenMap] sigaction failed\\n\");\n    close(ChecksumMapFd);\n    return false;\n  }\n\n  //  fprintf(stderr,\"[Checksum::OpenMap] %d %llu %llu\\n\", ChecksumMapFd,\n  // ChecksumMap, ChecksumMapSize);\n  return true;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nCheckSum::SyncMap()\n{\n  if (ChecksumMapFd) {\n    if (ChecksumMap) {\n      if (!msync(ChecksumMap, ChecksumMapSize, MS_ASYNC)) {\n        return true;\n      }\n\n      fprintf(stderr, \"Fatal: [CheckSum::SyncMap] fd=%d errno=%d %llu %llu\\n\",\n              (int) ChecksumMapFd, (int) errno, (unsigned long long) ChecksumMap,\n              (unsigned long long) ChecksumMapSize);\n    } else {\n      fprintf(stderr, \"Fatal: [CheckSum::SyncMap] fd=%d map=0\\n\", ChecksumMapFd);\n    }\n  } else {\n    fprintf(stderr, \"Fatal: [CheckSum::SyncMap] fd=0\\n\");\n  }\n\n  return false;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nCheckSum::ChangeMap(size_t newsize, bool shrink)\n{\n  // newsize is the real file size\n  newsize = ((newsize / BlockSize) + 1) * (GetCheckSumLen());\n\n  if ((!ChecksumMapFd) || (!ChecksumMap)) {\n    fprintf(stderr, \"Fatal: [CheckSum:ChangeMap] no fd/map %d %llu\\n\",\n            (int) ChecksumMapFd, (unsigned long long) ChecksumMap);\n    return false;\n  }\n\n  if (ChecksumMapSize == newsize) {\n    return true;\n  }\n\n  if ((!shrink) && (ChecksumMapSize > newsize)) {\n    return true;\n  }\n\n  if ((!shrink) && ((newsize - ChecksumMapSize) < (64 * 1024))) {\n    // to avoid to many truncs/msync's here we increase the desired value by 64k\n    newsize = ChecksumMapSize + (64 * 1024);\n  }\n\n  if (!SyncMap()) {\n    fprintf(stderr,\n            \"Fatal: [CheckSum:ChangeMap] sync failed [ fd=%d map=%llu mapsize=%llu\\n\",\n            (int) ChecksumMapFd, (unsigned long long) ChecksumMap,\n            (unsigned long long) ChecksumMapSize);\n    return false;\n  }\n\n  //  fprintf(stderr,\"truncating %d to %llu\\n\", ChecksumMapFd, newsize);\n  if (ftruncate(ChecksumMapFd, newsize)) {\n    ChecksumMapSize = 0;\n    fprintf(stderr,\n            \"Fatal: [CheckSum:ChangeMap] ftruncate failed [ fd=%d map=%llu mapsize=%llu errno=%d]\\n\",\n            (int) ChecksumMapFd, (unsigned long long) ChecksumMap,\n            (unsigned long long) ChecksumMapSize, (int) errno);\n    return false;\n  }\n\n  //  fprintf(stderr,\"remapping %d %llu %llu %llu\\n\", ChecksumMapFd, ChecksumMap, ChecksumMapSize, newsize);\n#ifdef __APPLE__\n\n  if ((munmap(ChecksumMap, ChecksumMapSize)) ||\n      (ChecksumMap = (char*) mmap(ChecksumMap, newsize, PROT_READ | PROT_WRITE,\n                                  MAP_SHARED, ChecksumMapFd, 0))) {\n#else\n  ChecksumMap = (char*) mremap(ChecksumMap, ChecksumMapSize, newsize,\n                               MREMAP_MAYMOVE);\n\n  if (ChecksumMap == MAP_FAILED) {\n#endif\n    fprintf(stderr, \"Fatal: [CheckSum::ChangeMap] mremap [ errno=%d ]\\n\", errno);\n    ChecksumMapSize = 0;\n    ChecksumMap = 0;\n    return false;\n  }\n\n  ChecksumMapSize = newsize;\n  //  fprintf(stderr,\"remapped %d %llu %llu %llu\\n\", ChecksumMapFd, ChecksumMap, ChecksumMapSize, newsize);\n  return true;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nCheckSum::CloseMap()\n{\n  //  fprintf(stderr,\"[Checksum::CloseMap] %d %llu %llu\\n\", ChecksumMapFd, ChecksumMap, ChecksumMapSize);\n  if (ChecksumMapFd) {\n    if (ChecksumMap) {\n      SyncMap();\n\n      if (munmap(ChecksumMap, ChecksumMapSize)) {\n        close(ChecksumMapFd);\n        ChecksumMap = 0;\n        return false;\n      } else {\n        close(ChecksumMapFd);\n        ChecksumMap = 0;\n        return true;\n      }\n    }\n  }\n\n  ChecksumMap = 0;\n  ChecksumMapFd = 0;\n  return false;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nCheckSum::AlignBlockExpand(off_t offset, size_t len, off_t& aligned_offset,\n                           size_t& aligned_len)\n{\n  aligned_offset = offset - (offset % BlockSize);\n  aligned_len = len + (offset % BlockSize);\n\n  if (aligned_len % BlockSize) {\n    aligned_len += ((BlockSize - (aligned_len % BlockSize)));\n  }\n\n  return;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nCheckSum::AlignBlockShrink(off_t offset, size_t len, off_t& aligned_offset,\n                           size_t& aligned_len)\n{\n  off_t start = offset;\n  off_t stop = offset + len;\n\n  if (start % BlockSize) {\n    start = start + BlockSize - (start % BlockSize);\n  }\n\n  if (stop % BlockSize) {\n    stop = stop - (stop % BlockSize);\n  }\n\n  aligned_offset = start;\n\n  if (stop - start < 0) {\n    aligned_len = 0;\n  } else {\n    aligned_len = stop - start;\n  }\n\n  return;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nCheckSum::AddBlockSum(off_t offset, const char* buffer, size_t len)\n{\n  // --------------------------------------------------------------------------------------\n  // !this only calculates the checksum on full blocks, not matching edge is not calculated\n  // --------------------------------------------------------------------------------------\n  off_t aligned_offset;\n  size_t aligned_len;\n  // -----------------------------------------------------------------------------\n  // first wipe out the concerned pages (set to 0)\n  // -----------------------------------------------------------------------------\n  AlignBlockExpand(offset, len, aligned_offset, aligned_len);\n\n  if (aligned_len) {\n    off_t endoffset = aligned_offset + aligned_len;\n    off_t position = offset;\n    const char* bufferptr = buffer + (aligned_offset - offset);\n\n    // loop over all blocks\n    for (position = aligned_offset; position < endoffset; position += BlockSize) {\n      Reset();\n      Finalize();\n\n      if (!SetXSMap(position)) {\n        return false;\n      }\n\n      bufferptr += BlockSize;\n    }\n  }\n\n  // -----------------------------------------------------------------------------\n  // write the inner matching page\n  // -----------------------------------------------------------------------------\n  AlignBlockShrink(offset, len, aligned_offset, aligned_len);\n\n  if (aligned_len) {\n    off_t endoffset = aligned_offset + aligned_len;\n    off_t position = offset;\n    const char* bufferptr = buffer + (aligned_offset - offset);\n\n    // loop over all blocks\n    for (position = aligned_offset; position < endoffset; position += BlockSize) {\n      // checksum this block\n      Reset();\n      Add(bufferptr, BlockSize, 0);\n      Finalize();\n\n      // write the checksum page\n      if (!SetXSMap(position)) {\n        return false;\n      }\n\n      nXSBlocksWritten++;\n      bufferptr += BlockSize;\n    }\n  }\n\n  return true;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nCheckSum::CheckBlockSum(off_t offset, const char* buffer, size_t len)\n{\n  // --------------------------------------------------------------------------------------\n  // !this only checks the checksum on full blocks, not matching edge is not calculated\n  // --------------------------------------------------------------------------------------\n  off_t aligned_offset;\n  size_t aligned_len;\n  AlignBlockShrink(offset, len, aligned_offset, aligned_len);\n\n  if (aligned_len) {\n    off_t endoffset = aligned_offset + aligned_len;\n    off_t position = offset;\n    const char* bufferptr = buffer + (aligned_offset - offset);\n\n    // loop over all blocks\n    for (position = aligned_offset; position < endoffset; position += BlockSize) {\n      // checksum this block\n      Reset();\n      Add(bufferptr, BlockSize, 0);\n      Finalize();\n\n      // compare the checksum page\n      if (!VerifyXSMap(position)) {\n        return false;\n      }\n\n      nXSBlocksChecked++;\n      bufferptr += BlockSize;\n    }\n  }\n\n  return true;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nCheckSum::SetXSMap(off_t offset)\n{\n  if (!ChangeMap((offset + BlockSize), false)) {\n    return false;\n  }\n\n  off_t mapoffset = (offset / BlockSize) * GetCheckSumLen();\n  int len = 0;\n  const char* cks = GetBinChecksum(len);\n\n  if (!sigsetjmp(sj_env[ SYSGETTID % 65536], 1)) {\n    for (int i = 0; i < len; i++) {\n      ChecksumMap[i + mapoffset] = cks[i];\n    }\n  } else {\n    // return point from signal handler\n    fprintf(stderr,\n            \"Fatal: [CheckSum::SetXSMap] recovered SIGBUS by illegal write access to mmaped XS map file [ len=%d mapoffset=%llu offset=%llu map=%llu mapsize=%llu ]\\n\",\n            (int) len, (unsigned long long) mapoffset, (unsigned long long) offset,\n            (unsigned long long) ChecksumMap, (unsigned long long) ChecksumMapSize);\n    return false;\n  }\n\n  return true;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nCheckSum::VerifyXSMap(off_t offset)\n{\n  if (!ChangeMap((offset + BlockSize), false)) {\n    fprintf(stderr, \"Fatal: [CheckSum::VerifyXSMap] ChangeMap failed\\n\");\n    return false;\n  }\n\n  off_t mapoffset = (offset / BlockSize) * GetCheckSumLen();\n  //  fprintf(stderr,\"Verifying %llu %llu %d %llu %llu\\n\", offset, mapoffset, ChecksumMapFd, ChecksumMap, ChecksumMapSize);\n  int len = 0;\n  const char* cks = GetBinChecksum(len);\n\n  if (!sigsetjmp(sj_env[ SYSGETTID % 65536], 1)) {\n    for (int i = 0; i < len; i++) {\n      //    fprintf(stderr,\"Compare %llu %llu\\n\", ChecksumMap[i+mapoffset], cks[i]);\n      if ((ChecksumMap[i + mapoffset]) && ((ChecksumMap[i + mapoffset] != cks[i]))) {\n        //      fprintf(stderr,\"Failed %llu %llu %llu\\n\", offset + i, ChecksumMap[i+mapoffset], cks[i]);\n        return false;\n      }\n    }\n  } else {\n    // return point from signal handler\n    fprintf(stderr,\n            \"Fatal: [CheckSum::VerifyXSMap] recovered SIGBUS by illegal read access to mmaped XS map file [ offset=%llu mapoffset=%llu fd=%d map=%llu mapsize=%llu ]\\n\",\n            (unsigned long long) offset, (unsigned long long) mapoffset,\n            (int) ChecksumMapFd, (unsigned long long) ChecksumMap,\n            (unsigned long long) ChecksumMapSize);\n    return false;\n  }\n\n  return true;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nCheckSum::AddBlockSumHoles(int fd)\n{\n  // ---------------------------------------------------------------------------\n  // ! this routine (re-)computes all the checksums for blocks with '0' checksum\n  // ! you have to call this after OpenMap and before CloseMap\n  // ---------------------------------------------------------------------------\n  struct stat buf;\n\n  if (fstat(fd, &buf)) {\n    //    fprintf(stderr,\"AddBlockSumHoles: stat failed\\n\");\n    return false;\n  } else {\n    if (!ChangeMap(buf.st_size, false)) {\n      //      fprintf(stderr,\"AddBlockSumHoles: changemap failed %llu\\n\", buf.st_size);\n      return false;\n    }\n\n    char* buffer = (char*) malloc(BlockSize);\n\n    if (buffer) {\n      size_t len = GetCheckSumLen();\n      size_t nblocks = ChecksumMapSize / len;\n      bool iszero;\n\n      if (!sigsetjmp(sj_env[ SYSGETTID % 65536], 1)) {\n        for (size_t i = 0; i < nblocks; i++) {\n          iszero = true;\n\n          for (size_t n = 0; n < len; n++) {\n            if (ChecksumMap[(i * len) + n ]) {\n              iszero = false;\n              break;\n            }\n          }\n\n          if (iszero) {\n            int nrbytes = pread(fd, buffer, BlockSize, i * BlockSize);\n\n            if (nrbytes < 0) {\n              continue;\n            }\n\n            if (nrbytes < (int) BlockSize) {\n              // fill the last block\n              memset(buffer + nrbytes, 0, BlockSize - nrbytes);\n              nrbytes = BlockSize;\n            }\n\n            if (!AddBlockSum(i * BlockSize, buffer, nrbytes)) {\n              //            fprintf(stderr,\"AddBlockSumHoles: checksumming failed\\n\");\n              free(buffer);\n              return false;\n            }\n\n            nXSBlocksWrittenHoles++;\n          }\n        }\n      } else {\n        // return point from signal handler\n        fprintf(stderr,\n                \"Fatal: [CheckSum::AddBlockSumHoles] recovered SIGBUS by illegal write access to mmaped XS map file [ nblocks=%u map=%llu ]\\n\",\n                (unsigned int) nblocks, (unsigned long long) ChecksumMapSize);\n        free(buffer);\n        return false;\n      }\n\n      free(buffer);\n      return true;\n    } else {\n      //      fprintf(stderr,\"AddBlockSumHoles: malloc failed\\n\");\n      return false;\n    }\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Get number of rd/wr references\n//------------------------------------------------------------------------------\n\nunsigned int\nCheckSum::GetNumRef(bool isRW)\n{\n  if (isRW) {\n    return mNumWr;\n  } else {\n    return mNumRd;\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Increment the number of references\n//-----------------------------------------------------------------------------\n\nvoid\nCheckSum::IncrementRef(bool isRW)\n{\n  if (isRW) {\n    mNumWr++;\n  } else {\n    mNumRd++;\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Decrement the number of references\n//------------------------------------------------------------------------------\n\nvoid\nCheckSum::DecrementRef(bool isRW)\n{\n  if (isRW) {\n    if (mNumWr) {\n      mNumWr--;\n    }\n  } else {\n    if (mNumRd) {\n      mNumRd--;\n    }\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/checksum/CheckSum.hh",
    "content": "// ----------------------------------------------------------------------\n// File: CheckSum.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_CHECKSUM_HH__\n#define __EOSFST_CHECKSUM_HH__\n\n#include \"fst/Namespace.hh\"\n#include \"fst/Load.hh\"\n#include \"common/LayoutId.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <google/sparse_hash_map>\n#include <setjmp.h>\n#include <signal.h>\n#include <mutex>\n#include <chrono>\n\nEOSFSTNAMESPACE_BEGIN\n\n\nclass CheckSum\n{\nprotected:\n  XrdOucString Name;\n  mutable XrdOucString Checksum;\n\n  mutable bool needsRecalculation;\n  mutable bool finalized;\n  char* ChecksumMap;\n  size_t ChecksumMapSize;\n  size_t ChecksumMapOpenSize;\n  int ChecksumMapFd;\n  size_t BlockSize;\n\n  XrdOucString BlockXSPath;\n  unsigned long long nXSBlocksChecked;\n  unsigned long long nXSBlocksWritten;\n  unsigned long long nXSBlocksWrittenHoles;\n  std::mutex mMutex;\n\npublic:\n\n  CheckSum()\n  {\n    Name = \"\";\n    ChecksumMap = 0;\n    mNumRd = 0;\n    mNumWr = 0;\n    finalized = false;\n  }\n\n  CheckSum(const char* name)\n  {\n    Name = name;\n    needsRecalculation = false;\n    ChecksumMap = 0;\n    ChecksumMapSize = 0;\n    ChecksumMapOpenSize = 0;\n    BlockSize = 0;\n    nXSBlocksChecked = 0;\n    nXSBlocksWritten = 0;\n    nXSBlocksWrittenHoles = 0;\n    BlockXSPath = \"\";\n    ChecksumMapFd = -1;\n    mNumRd = 0;\n    mNumWr = 0;\n    finalized = false;\n  }\n\n  //-------------------------------------------------------------------------------\n  //! Add adds more data to the checksum object.\n  //!\n  //! @param buffer buffer containing the data to be added\n  //! @param lenght buffer size\n  //! @param offset position in the file where the buffer starts. If set to < 0\n  //!               the data is assumed to be contiguous with the previous Add call\n  //-------------------------------------------------------------------------------\n  virtual bool Add(const char* buffer, size_t length, off_t offset = -1) = 0;\n\n  virtual void\n  Finalize() const\n  {\n    finalized = true;\n  };\n  virtual void Reset() = 0;\n\n  virtual void\n  ResetInit(off_t offsetInit, size_t lengthInit, const char* checksumInitHex) { };\n\n  virtual void\n  SetDirty()\n  {\n    needsRecalculation = true;\n  }\n\n  virtual const char* GetHexChecksum() const = 0;\n  virtual const char* GetBinChecksum(int& len) const = 0;\n\n  virtual bool\n  SetBinChecksum(const void* buffer, int len)\n  {\n    if (len < GetCheckSumLen()) {\n      return false;\n    }\n\n    needsRecalculation = false;\n    int ilen = 0;\n    memcpy((void*) GetBinChecksum(ilen), buffer, GetCheckSumLen());\n    return true;\n  }\n\n  virtual bool Compare(const char* refchecksum) const;\n  virtual bool Compare(const CheckSum* other) const;\n\n  virtual off_t GetLastOffset() const = 0;\n\n  virtual off_t\n  GetMaxOffset() const\n  {\n    return GetLastOffset();\n  }\n  virtual int GetCheckSumLen() const = 0;\n\n  const char*\n  GetName() const\n  {\n    return Name.c_str();\n  }\n\n  bool\n  NeedsRecalculation() const\n  {\n    return needsRecalculation;\n  }\n\n  class ReadCallBack\n  {\n  public:\n\n    typedef struct callback_data {\n      callback_data() : caller(0), path(0), offset(0), buffer(0), size(0), retc(-1)\n      {\n      }\n      void* caller;\n      const char* path;\n      off_t offset;\n      char* buffer;\n      size_t size;\n      int retc;\n    } callback_data_t;\n\n    typedef int (*callback_t)(callback_data_t*);\n\n    ReadCallBack()\n    {\n      call = 0;\n    }\n\n    ReadCallBack(callback_t tocall, callback_data_t& info)\n    {\n      call = tocall;\n      data = info;\n      data.retc = 0;\n    }\n\n    virtual ~ReadCallBack()\n    {\n    };\n\n    ReadCallBack(const ReadCallBack& obj)\n    {\n      call = obj.call;\n      data = obj.data;\n    }\n\n    callback_t call;\n    callback_data_t data;\n  };\n\n  virtual bool ScanFile(const char* path, unsigned long long& scansize,\n                        std::chrono::milliseconds& scantime, int rate = 0,\n                        off_t offset = 0, Load* fstload = nullptr,\n                        const std::string& dirpath = \"\", int max_rate = 0);\n  virtual bool ScanFile(ReadCallBack rcb, unsigned long long& scansize,\n                        std::chrono::milliseconds& scantime, int rate = 0,\n                        Load* fstload = nullptr,\n                        const std::string& dirpath = \"\", int max_rate = 0);\n  virtual bool ScanFile(int fd, unsigned long long& scansize,\n                        std::chrono::milliseconds& scantime,\n                        int rate = 0, bool is_stdin = false, off_t offset = 0,\n                        Load* fstload = nullptr,\n                        const std::string& dirpath = \"\", int max_rate = 0);\n  virtual bool ScanFile(const char* path, off_t offsetInit, size_t lengthInit,\n                        const char* partialChecksum,\n                        unsigned long long& scansize,\n                        std::chrono::milliseconds& scantime,\n                        int rate = 0, Load* fstload = nullptr,\n                        const std::string& dirpath = \"\", int max_rate = 0);\n  virtual bool VerifyXSMap(off_t offset);\n\n  virtual bool OpenMap(const char* mapfilepath, size_t maxfilesize,\n                       size_t blocksize, bool isRW);\n  virtual bool ChangeMap(size_t newsize, bool shrink = true);\n  virtual bool SyncMap();\n  virtual bool CloseMap();\n\n  virtual void AlignBlockExpand(off_t offset, size_t len, off_t& aligned_offset,\n                                size_t& aligend_len);\n  virtual void AlignBlockShrink(off_t offset, size_t len, off_t& aligned_offset,\n                                size_t& aligend_len);\n  virtual bool AddBlockSum(off_t offset, const char* buffer,\n                           size_t buffersize);  // this only calculates the checksum on full blocks, not matching edge is not calculated\n  virtual bool CheckBlockSum(off_t offset, const char* buffer,\n                             size_t buffersizem); // this only verifies the checksum on full blocks, not matching edge is not calculated\n  virtual bool AddBlockSumHoles(int fd);\n\n  virtual const char*\n  MakeBlockXSPath(const char* filepath)\n  {\n    if ((!filepath)) {\n      return 0;\n    }\n\n    BlockXSPath = filepath;\n    BlockXSPath += \".xsmap\";\n    return BlockXSPath.c_str();\n  }\n\n  virtual bool\n  UnlinkXSPath()\n  {\n    // return 0 if success\n    if (BlockXSPath.length()) {\n      return ::unlink(BlockXSPath.c_str());\n    }\n\n    return true;\n  }\n\n  virtual unsigned long long\n  GetXSBlocksChecked()\n  {\n    return nXSBlocksChecked;\n  }\n\n  virtual unsigned long long\n  GetXSBlocksWritten()\n  {\n    return nXSBlocksWritten;\n  }\n\n  virtual unsigned long long\n  GetXSBlocksWrittenHoles()\n  {\n    return nXSBlocksWrittenHoles;\n  }\n\n  virtual\n  ~CheckSum() { };\n\n  virtual void\n  Print()\n  {\n    fprintf(stderr, \"%s\\n\", GetHexChecksum());\n  }\n\n\n  //----------------------------------------------------------------------------\n  //! Get total number of references\n  //!\n  //! @return total number of references to this xs obj\n  //!\n  //----------------------------------------------------------------------------\n\n  inline unsigned int\n  GetTotalRef()\n  {\n    return (mNumWr + mNumRd);\n  };\n\n\n  //----------------------------------------------------------------------------\n  //! Get number of rd/wr references\n  //!\n  //! @param isRW the type of references returned rd/wr\n  //!\n  //! @return number of rd/wr references to this xs obj\n  //!\n  //----------------------------------------------------------------------------\n  unsigned int GetNumRef(bool isRW);\n\n\n  //----------------------------------------------------------------------------\n  //! Increment the number of references\n  //!\n  //! @param isRW the type of references added rd/wr\n  //!\n  //----------------------------------------------------------------------------\n  void IncrementRef(bool isRW);\n\n\n  //----------------------------------------------------------------------------\n  //! Decrement the number of references\n  //!\n  //! @param isRW the type of references subtracted rd/wr\n  //!\n  //----------------------------------------------------------------------------\n  void DecrementRef(bool isRW);\n\n\n  std::string CheckSumMapFile;\n\nprivate:\n  virtual bool SetXSMap(off_t offset);\n\n  unsigned int mNumRd; ///< number of reader references\n  unsigned int mNumWr; ///< number of writer references\n};\n\nEOSFSTNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "fst/checksum/ChecksumGroup.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ChecksumGroup.hh\n// Author: Gianmaria Del Monte - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"fst/Load.hh\"\n#include \"fst/checksum/CheckSum.hh\"\n#include \"fst/checksum/ChecksumPlugins.hh\"\n#include \"fst/utils/ScanRate.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n//! @brief Manages a group of different checksum calculations.\n//!\n//! This class holds a map of CheckSum objects, each identified by its type.\n//! It allows designating one checksum type as the \"default\" and performing\n//! operations across all managed checksums.\n//----------------------------------------------------------------------------\nclass ChecksumGroup\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Get the default checksum\n  //!\n  //! @return checksum object\n  //----------------------------------------------------------------------------\n  inline CheckSum* GetDefault() const\n  {\n    if (mDefaultType == eos::common::LayoutId::eChecksum::kNone) {\n      return nullptr;\n    }\n\n    return mChecksums.at(mDefaultType).get();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set the default checksum for the group\n  //! The object takes ownership of the checksum object\n  //!\n  //! @param xs ptr to checksum object\n  //! @param xsType checksum type\n  //----------------------------------------------------------------------------\n  inline void SetDefault(CheckSum* xs,\n                         eos::common::LayoutId::eChecksum xsType)\n  {\n    if (!xs) {\n      return;\n    }\n\n    SetDefault(std::unique_ptr<CheckSum>(xs), xsType);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set the default checksum for the group\n  //!\n  //! @param xs ptr to checksum object\n  //! @param xsType checksum type\n  //----------------------------------------------------------------------------\n  inline void SetDefault(std::unique_ptr<CheckSum>&& xs,\n                         eos::common::LayoutId::eChecksum xsType)\n  {\n    if (!xs) {\n      return;\n    }\n\n    mDefaultType = xsType;\n    mChecksums[xsType] = std::move(xs);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Add an alternative checksum to the group\n  //! The object takes ownership of the checksum object\n  //!\n  //! @param xs ptr to checksum object\n  //! @param xsType checksum type\n  //----------------------------------------------------------------------------\n  void AddAlternative(CheckSum* xs,\n                      eos::common::LayoutId::eChecksum xsType)\n  {\n    mChecksums[xsType] = std::unique_ptr<CheckSum>(xs);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Add an alternative checksum to the group\n  //!\n  //! @param xs ptr to checksum object\n  //! @param xsType checksum type\n  //----------------------------------------------------------------------------\n  void AddAlternative(std::unique_ptr<CheckSum>&& xs,\n                      const std::string xsType)\n  {\n    auto t = static_cast<eos::common::LayoutId::eChecksum>\n             (eos::common::LayoutId::GetChecksumFromString(xsType));\n    mChecksums[t] = std::move(xs);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Add an alternative checksum to the group\n  //!\n  //! @param xs checksum type\n  //----------------------------------------------------------------------------\n  void AddAlternative(eos::common::LayoutId::eChecksum xs)\n  {\n    auto obj = ChecksumPlugins::GetXsObj(xs);\n\n    if (obj) {\n      AddAlternative(obj, xs);\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Add a data chunk to all managed checksums\n  //!\n  //! @param xs buffer pointer to data buffer\n  //! @param length size of the data chunk in bytes\n  //! @param offset offst of the data chunk within the overall data stream\n  //----------------------------------------------------------------------------\n  bool Add(const char* buffer, size_t length, off_t offset)\n  {\n    for (auto& [_, xs] : mChecksums) {\n      xs->Add(buffer, length, offset);\n    }\n\n    return true;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Initialize the default checksum with a prior state\n  //!\n  //! @param offsetInit the starting offset for the checksum calculation\n  //! @param lengthInit the total expected length of the data for the checksum\n  //! @param xsInitHex a hexadecimal string representing the initial checksum value\n  //----------------------------------------------------------------------------\n  void ResetInitDefault(off_t offsetInit, size_t lengthInit,\n                        const char* xsInitHex)\n  {\n    ResetInit(mDefaultType, offsetInit, lengthInit, xsInitHex);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Initialize the specific checksum with a prior state\n  //!\n  //! @param xsType type of the checksum to initialize\n  //! @param offsetInit the starting offset for the checksum calculation\n  //! @param lengthInit the total expected length of the data for the checksum\n  //! @param xsInitHex a hexadecimal string representing the initial checksum value\n  //----------------------------------------------------------------------------\n  void ResetInit(eos::common::LayoutId::eChecksum xsType, off_t offsetInit,\n                 size_t lengthInit, const char* xsInitHex)\n  {\n    if (!mChecksums.count(xsType)) {\n      return;\n    }\n\n    mChecksums[xsType]->ResetInit(offsetInit, lengthInit, xsInitHex);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Initialize the specific checksum with a prior state\n  //!\n  //! @param name name of the checksum to initialize\n  //! @param offsetInit the starting offset for the checksum calculation\n  //! @param lengthInit the total expected length of the data for the checksum\n  //! @param xsInitHex a hexadecimal string representing the initial checksum value\n  //----------------------------------------------------------------------------\n  void ResetInit(std::string name, off_t offsetInit,\n                 size_t lengthInit, const char* xsInitHex)\n  {\n    auto t = static_cast<eos::common::LayoutId::eChecksum>\n             (eos::common::LayoutId::GetChecksumFromString(name));\n    return ResetInit(t, offsetInit, lengthInit, xsInitHex);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Removes all checksum objects from the group\n  //----------------------------------------------------------------------------\n  void Clear()\n  {\n    mDefaultType = eos::common::LayoutId::eChecksum::kNone;\n    mChecksums.clear();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Checks if the group contains any checksum objects\n  //----------------------------------------------------------------------------\n  bool HasChecksums() const\n  {\n    return mChecksums.size();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Checks if any checksum in the group requires recalculation\n  //----------------------------------------------------------------------------\n  bool NeedsRecalculation() const\n  {\n    for (const auto &[_, xs] : mChecksums) {\n      if (xs->NeedsRecalculation()) {\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Marks all the checksums in the group as dirty\n  //----------------------------------------------------------------------------\n  void SetDirty()\n  {\n    for (auto &[_, xs] : mChecksums) {\n      xs->SetDirty();\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Finalizes the calculation for all checksums in the group\n  //----------------------------------------------------------------------------\n  void Finalize()\n  {\n    for (auto &[_, xs] : mChecksums) {\n      xs->Finalize();\n    }\n  }\n\n  off_t\n  GetMaxOffset() const\n  {\n    return GetDefault()->GetMaxOffset();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Retrieves a map of all the alternative checksums\n  //----------------------------------------------------------------------------\n  std::map<eos::common::LayoutId::eChecksum, CheckSum*>\n  GetAlternatives() const\n  {\n    std::map<eos::common::LayoutId::eChecksum, CheckSum*> res;\n\n    for (auto &[xsType, xs] : mChecksums) {\n      if (xsType != mDefaultType) {\n        res[xsType] = xs.get();\n      }\n    }\n\n    return res;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Resets the checksums in the group\n  //----------------------------------------------------------------------------\n  void Reset()\n  {\n    for (auto& [_, xs] : mChecksums) {\n      xs->Reset();\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Scan of a complete file using an opened layout\n  //----------------------------------------------------------------------------\n  bool\n  ScanFile(CheckSum::ReadCallBack rcb, unsigned long long& scansize,\n           std::chrono::milliseconds& scantime, int rate = 0, Load* fstload = nullptr,\n           const std::string& dirpath = \"\")\n  {\n    // @note: not nice, since this is a copy of the CheckSum implementation\n    static int buffersize = 1024 * 1024;\n    scansize = 0ull;\n    scantime = std::chrono::milliseconds::zero();\n    auto open_ts = std::chrono::system_clock::now();\n    Reset();\n    //@todo(esindril) this should use the internal BufferPool to avoid\n    // fragmentation of the memory\n    char* buffer;\n\n    // Make sure the buffer is properly aligned to work also for direct IO\n    if (posix_memalign((void**)&buffer, 256 * 1024, buffersize)) {\n      fprintf(stderr, \"warning: failed to use posix_memaling \\n\");\n      buffer = (char*)malloc(buffersize);\n    }\n\n    if (!buffer) {\n      return false;\n    }\n\n    int nread = 0;\n    off_t offset = 0;\n\n    do {\n      errno = 0;\n      rcb.data.offset = offset;\n      rcb.data.buffer = buffer;\n      rcb.data.size = buffersize;\n      nread = rcb.call(&rcb.data);\n\n      if (nread < 0) {\n        free(buffer);\n        return false;\n      }\n\n      if (nread > 0) {\n        Add(buffer, nread, offset);\n        offset += nread;\n      }\n\n      if (rate) {\n        // regulate the verification rate\n        eos::fst::utils::EnforceAndAdjustScanRate(scansize, open_ts, rate, fstload,\n                                                  dirpath.c_str());\n      }\n    } while (nread == buffersize);\n\n    auto current_ts = std::chrono::system_clock::now();\n    scantime =\n        std::chrono::duration_cast<std::chrono::milliseconds>(current_ts - open_ts);\n    scansize = (unsigned long long) offset;\n    Finalize();\n    free(buffer);\n    return true;\n  }\n\nprivate:\n  eos::common::LayoutId::eChecksum mDefaultType {eos::common::LayoutId::eChecksum::kNone};\n  std::map<eos::common::LayoutId::eChecksum, std::unique_ptr<CheckSum>>\n      mChecksums;\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/checksum/ChecksumPlugins.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ChecksumPlugins.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"fst/Namespace.hh\"\n#include \"common/LayoutId.hh\"\n#include \"fst/checksum/CheckSum.hh\"\n#include \"fst/checksum/Adler.hh\"\n#include \"fst/checksum/BLAKE3.hh\"\n#include \"fst/checksum/CRC32.hh\"\n#include \"fst/checksum/CRC32C.hh\"\n#include \"fst/checksum/MD5.hh\"\n#include \"fst/checksum/SHA1.hh\"\n#include \"fst/checksum/CRC64.hh\"\n#include \"fst/checksum/SHA256.hh\"\n#ifndef __APPLE__\n#include \"fst/checksum/HWH64.hh\"\n#endif\n\n#ifdef XXHASH_FOUND\n#include \"fst/checksum/XXHASH64.hh\"\n#endif\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class ChecksumPluging\n//------------------------------------------------------------------------------\nclass ChecksumPlugins\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Get checksum object depending on the given type\n  //!\n  //! @param xs_type checksum type given usigned long\n  //!\n  //! @return checksum object\n  //----------------------------------------------------------------------------\n  static CheckSum*\n  GetXsObj(unsigned long xs_type)\n  {\n    if (xs_type == eos::common::LayoutId::kAdler) {\n      return static_cast<CheckSum*>(new Adler());\n    } else if (xs_type == eos::common::LayoutId::kBLAKE3) {\n      return static_cast<CheckSum*>(new BLAKE3());\n    } else if (xs_type == eos::common::LayoutId::kCRC32) {\n      return static_cast<CheckSum*>(new CRC32());\n    } else if (xs_type == eos::common::LayoutId::kCRC32C) {\n      return static_cast<CheckSum*>(new CRC32C());\n    } else if (xs_type == eos::common::LayoutId::kMD5) {\n      return static_cast<CheckSum*>(new MD5());\n    } else if (xs_type == eos::common::LayoutId::kSHA1) {\n      return static_cast<CheckSum*>(new SHA1());\n    } else if (xs_type == eos::common::LayoutId::kCRC64) {\n      return static_cast<CheckSum*>(new CRC64());\n    } else if (xs_type == eos::common::LayoutId::kSHA256) {\n      return static_cast<CheckSum*>(new SHA256());\n#ifndef __APPLE__\n    } else if (xs_type == eos::common::LayoutId::kHWH64) {\n      return static_cast<CheckSum*>(new HWH64());\n#endif\n#ifdef XXHASH_FOUND\n    } else if (xs_type == eos::common::LayoutId::kXXHASH64) {\n      return static_cast<CheckSum*>(new XXHASH64());\n#endif\n    }\n\n    return nullptr;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get checksum object depending on the given type\n  //!\n  //! @param xs_type checksum type given as string\n  //!\n  //! @return checksum object\n  //----------------------------------------------------------------------------\n  static std::unique_ptr<CheckSum>\n  GetXsObj(const std::string& xs_type)\n  {\n    return std::unique_ptr<CheckSum>\n           (GetXsObj(eos::common::LayoutId::GetChecksumFromString(xs_type)));\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get checksum object given the layoutid\n  //!\n  //! @param layoutid layout id endcoding see eos::common::LayoutId\n  //! @param blockchecksum if true then return the checksum object for the\n  //!        block checksum part encoded in the layout id\n  //!\n  //! @return checksum object\n  //----------------------------------------------------------------------------\n  static std::unique_ptr<CheckSum>\n  GetChecksumObject(unsigned long layoutid, bool blockchecksum = false)\n  {\n    unsigned int xs_type = (blockchecksum ?\n                            eos::common::LayoutId::GetBlockChecksum(layoutid) :\n                            eos::common::LayoutId::GetChecksum(layoutid));\n    return std::unique_ptr<CheckSum>(GetXsObj(xs_type));\n  }\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/checksum/HWH64.hh",
    "content": "// ----------------------------------------------------------------------\n// File: HWH64.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_HWH64_HH__\n#define __EOSFST_HWH64_HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"fst/Namespace.hh\"\n#include \"fst/checksum/CheckSum.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucString.hh>\n/*----------------------------------------------------------------------------*/\n#include \"common/highwayhash/highwayhash.h\"\n/*----------------------------------------------------------------------------*/\n\nEOSFSTNAMESPACE_BEGIN\n\n#define HWH64_DIGEST_LENGTH 8\n\nusing namespace highwayhash;\n\nclass HWH64 : public CheckSum\n{\n\nprivate:\n  const HHKey key HH_ALIGNAS(32) = {1, 2, 3, 4};\n  mutable HighwayHashCatT<HH_TARGET_PREFERRED> ctx;\n  mutable HHResult64 result;\n\n  off_t hwhoffset;\npublic:\n  HWH64() : CheckSum(\"hwh\"), ctx(key)\n  {\n    Reset();\n  }\n\n  off_t GetLastOffset() const override\n  {\n    return hwhoffset;\n  }\n\n  bool Add(const char* buffer, size_t length, off_t offset)\n  {\n    std::lock_guard<std::mutex> lock(mMutex);\n\n    if (offset < 0) {\n      offset = hwhoffset;\n    }\n\n    if (offset != hwhoffset || finalized) {\n      needsRecalculation = true;\n      return false;\n    }\n\n    ctx.Append((const char*) buffer, (unsigned long) length);\n    hwhoffset += length;\n    return true;\n  }\n\n  const char* GetHexChecksum() const override\n  {\n    Checksum = \"\";\n    char hexs[17];\n    sprintf(hexs, \"%016\" PRIx64, result);\n    Checksum += hexs;\n    return Checksum.c_str();\n  }\n\n  const char* GetBinChecksum(int& len) const override\n  {\n    len = HWH64_DIGEST_LENGTH;\n    return (char*) &result;\n  }\n\n  int GetCheckSumLen() const override\n  {\n    return HWH64_DIGEST_LENGTH;\n  }\n\n  void Finalize() const override\n  {\n    if (!finalized) {\n      ctx.Finalize(&result);\n      finalized = true;\n    }\n  }\n\n  void Reset()\n  {\n    ctx.Reset(key);\n    hwhoffset = 0;\n    needsRecalculation = 0;\n    finalized = false;\n  }\n\n  virtual ~HWH64() {};\n\n};\n\nEOSFSTNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "fst/checksum/MD5.hh",
    "content": "// ----------------------------------------------------------------------\n// File: MD5.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_MD5_HH__\n#define __EOSFST_MD5_HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"fst/Namespace.hh\"\n#include \"fst/checksum/CheckSum.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucString.hh>\n/*----------------------------------------------------------------------------*/\n#include <openssl/md5.h>\n/*----------------------------------------------------------------------------*/\n\nEOSFSTNAMESPACE_BEGIN\n\nclass MD5 : public CheckSum\n{\nprivate:\n  mutable MD5_CTX ctx;\n  off_t md5offset;\n  mutable unsigned char md5[MD5_DIGEST_LENGTH + 1];\n  unsigned char md5hex[(MD5_DIGEST_LENGTH * 2) + 1];\npublic:\n  MD5() : CheckSum(\"md5\")\n  {\n    Reset();\n  }\n\n  off_t GetLastOffset() const override\n  {\n    return md5offset;\n  }\n\n  bool Add(const char* buffer, size_t length, off_t offset)\n  {\n    std::lock_guard<std::mutex> lock(mMutex);\n\n    if (offset < 0) {\n      offset = md5offset;\n    }\n\n    if (offset != md5offset || finalized) {\n      needsRecalculation = true;\n      return false;\n    }\n\n    MD5_Update(&ctx, (const void*) buffer, (unsigned long) length);\n    md5offset += length;\n    return true;\n  }\n\n  const char* GetHexChecksum() const override\n  {\n    Checksum = \"\";\n    char hexs[16];\n\n    for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {\n      sprintf(hexs, \"%02x\", md5[i]);\n      Checksum += hexs;\n    }\n\n    return Checksum.c_str();\n  }\n\n  const char* GetBinChecksum(int& len) const override\n  {\n    len = MD5_DIGEST_LENGTH;\n    return (char*) &md5;\n  }\n\n  int GetCheckSumLen() const override\n  {\n    return MD5_DIGEST_LENGTH;\n  }\n\n  void Finalize() const override\n  {\n    if (!finalized) {\n      MD5_Final(md5, &ctx);\n      md5[MD5_DIGEST_LENGTH] = 0;\n      finalized = true;\n    }\n  }\n\n  void Reset()\n  {\n    md5offset = 0;\n    MD5_Init(&ctx);\n    memset(md5, 0, MD5_DIGEST_LENGTH + 1);\n    needsRecalculation = 0;\n    md5hex[0] = 0;\n    finalized = false;\n  }\n\n  virtual ~MD5() {};\n\n};\n\nEOSFSTNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "fst/checksum/SHA1.hh",
    "content": "// ----------------------------------------------------------------------\n// File: SHA1.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_SHA1_HH__\n#define __EOSFST_SHA1_HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"fst/Namespace.hh\"\n#include \"fst/checksum/CheckSum.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucString.hh>\n/*----------------------------------------------------------------------------*/\n#include <openssl/sha.h>\n\n/*----------------------------------------------------------------------------*/\n\nEOSFSTNAMESPACE_BEGIN\n\nclass SHA1 : public CheckSum\n{\nprivate:\n  mutable SHA_CTX ctx;\n\n  off_t sha1offset;\n  mutable unsigned char sha1[SHA_DIGEST_LENGTH + 1];\npublic:\n\n  SHA1() : CheckSum(\"sha1\")\n  {\n    Reset();\n  }\n\n  off_t\n  GetLastOffset() const override\n  {\n    return sha1offset;\n  }\n\n  bool\n  Add(const char* buffer, size_t length, off_t offset)\n  {\n    std::lock_guard<std::mutex> lock(mMutex);\n\n    if (offset < 0) {\n      offset = sha1offset;\n    }\n\n    if (offset != sha1offset) {\n      needsRecalculation = true;\n      return false;\n    }\n\n    SHA1_Update(&ctx, (const void*) buffer, (unsigned long) length);\n    sha1offset += length;\n    return true;\n  }\n\n  const char*\n  GetHexChecksum() const override\n  {\n    Checksum = \"\";\n    char hexs[16];\n\n    for (int i = 0; i < SHA_DIGEST_LENGTH; i++) {\n      sprintf(hexs, \"%02x\", sha1[i]);\n      Checksum += hexs;\n    }\n\n    return Checksum.c_str();\n  }\n\n  const char*\n  GetBinChecksum(int& len) const override\n  {\n    len = SHA_DIGEST_LENGTH;\n    return (char*) &sha1;\n  }\n\n  int\n  GetCheckSumLen() const override\n  {\n    return SHA_DIGEST_LENGTH;\n  }\n\n  void\n  Finalize() const override\n  {\n    if (!finalized) {\n      SHA1_Final(sha1, &ctx);\n      sha1[SHA_DIGEST_LENGTH] = 0;\n      finalized = true;\n    }\n  }\n\n  void\n  Reset()\n  {\n    sha1offset = 0;\n    SHA1_Init(&ctx);\n    memset(sha1, 0, SHA_DIGEST_LENGTH + 1);\n    needsRecalculation = 0;\n    sha1[0] = 0;\n    finalized = false;\n  }\n\n  virtual\n  ~SHA1() { };\n\n};\n\nEOSFSTNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "fst/checksum/SHA256.hh",
    "content": "// ----------------------------------------------------------------------\n// File: SHA256.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_SHA256_HH__\n#define __EOSFST_SHA256_HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"fst/Namespace.hh\"\n#include \"fst/checksum/CheckSum.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucString.hh>\n/*----------------------------------------------------------------------------*/\n#include <openssl/sha.h>\n\n/*----------------------------------------------------------------------------*/\n\nEOSFSTNAMESPACE_BEGIN\n\nclass SHA256 : public CheckSum\n{\nprivate:\n  mutable SHA256_CTX ctx;\n\n  off_t sha256offset;\n  mutable unsigned char sha256[SHA256_DIGEST_LENGTH + 1];\npublic:\n\n  SHA256() : CheckSum(\"sha256\")\n  {\n    Reset();\n  }\n\n  off_t\n  GetLastOffset() const override\n  {\n    return sha256offset;\n  }\n\n  bool\n  Add(const char* buffer, size_t length, off_t offset)\n  {\n    std::lock_guard<std::mutex> lock(mMutex);\n\n    if (offset < 0) {\n      offset = sha256offset;\n    }\n\n    if (offset != sha256offset) {\n      needsRecalculation = true;\n      return false;\n    }\n\n    SHA256_Update(&ctx, (const void*) buffer, (unsigned long) length);\n    sha256offset += length;\n    return true;\n  }\n\n  const char*\n  GetHexChecksum() const override\n  {\n    Checksum = \"\";\n    char hexs[16];\n\n    for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {\n      sprintf(hexs, \"%02x\", sha256[i]);\n      Checksum += hexs;\n    }\n\n    return Checksum.c_str();\n  }\n\n  const char*\n  GetBinChecksum(int& len) const override\n  {\n    len = SHA256_DIGEST_LENGTH;\n    return (char*) &sha256;\n  }\n\n  int\n  GetCheckSumLen() const override\n  {\n    return SHA256_DIGEST_LENGTH;\n  }\n\n  void\n  Finalize() const override\n  {\n    if (!finalized) {\n      SHA256_Final(sha256, &ctx);\n      sha256[SHA256_DIGEST_LENGTH] = 0;\n      finalized = true;\n    }\n  }\n\n  void\n  Reset()\n  {\n    sha256offset = 0;\n    SHA256_Init(&ctx);\n    memset(sha256, 0, SHA256_DIGEST_LENGTH + 1);\n    needsRecalculation = 0;\n    sha256[0] = 0;\n    finalized = false;\n  }\n\n  virtual\n  ~SHA256() { };\n\n};\n\nEOSFSTNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "fst/checksum/XXHASH64.hh",
    "content": "// ----------------------------------------------------------------------\n// File: XXHASH64.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_XXHASH64_HH__\n#define __EOSFST_XXHASH64_HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"fst/Namespace.hh\"\n#include \"fst/checksum/CheckSum.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucString.hh>\n/*----------------------------------------------------------------------------*/\n#include <xxhash.h>\n\n/*----------------------------------------------------------------------------*/\n\nEOSFSTNAMESPACE_BEGIN\n\nclass XXHASH64 : public CheckSum\n{\nprivate:\n  off_t xxhash64offset;\n  mutable uint64_t crcsum;\n  mutable XXH64_state_t* state;\n\npublic:\n\n  XXHASH64() : CheckSum(\"xxhash64\"), state(0)\n  {\n    Reset();\n  }\n\n  off_t\n  GetLastOffset() const override\n  {\n    return xxhash64offset;\n  }\n\n  bool\n  Add(const char* buffer, size_t length, off_t offset)\n  {\n    std::lock_guard<std::mutex> lock(mMutex);\n\n    if (offset < 0) {\n      offset = xxhash64offset;\n    }\n\n    if (offset != xxhash64offset) {\n      needsRecalculation = true;\n      return false;\n    }\n\n    crcsum = XXH64_update(state, (const Bytef*) buffer, length);\n    xxhash64offset += length;\n    return true;\n  }\n\n  const char*\n  GetHexChecksum() const override\n  {\n    char sxxhash64[1024];\n    sprintf(sxxhash64, \"%16lx\", crcsum);\n    Checksum = sxxhash64;\n    return Checksum.c_str();\n  }\n\n  const char*\n  GetBinChecksum(int& len) const override\n  {\n    len = sizeof(unsigned int);\n    return (char*) &crcsum;\n  }\n\n  int\n  GetCheckSumLen() const override\n  {\n    return sizeof(unsigned int);\n  }\n\n  void\n  Finalize() const override\n  {\n    if (!finalized) {\n      crcsum = XXH64_digest(state);\n    }\n  }\n\n  void\n  Reset()\n  {\n    if (state) {\n      XXH64_freeState(state);\n    }\n\n    state = XXH64_createState();\n    XXH64_reset(state, 0);\n    xxhash64offset = 0;\n    crcsum = 0;\n    needsRecalculation = 0;\n    finalized = false;\n  }\n\n  virtual\n  ~XXHASH64() { };\n\n};\n\nEOSFSTNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "fst/checksum/cycletimer.h",
    "content": "#ifndef LOGGING_CYCLETIMER_H__\n#define LOGGING_CYCLETIMER_H__\n\n#include <stdint.h>\n\nnamespace checksum {\n\nclass CycleTimer {\npublic:\n    void start() {\n        cpuSync();\n        start_ = rdtsc();\n    }\n\n    void end() {\n        cpuSync();\n        end_ = rdtsc();\n    }\n\n    uint32_t getCycles() {\n        return end_ - start_;\n    }\n\nprivate:\n    uint32_t rdtsc() {\n#ifdef __x86_64__\n        uint32_t low;\n        uint32_t high;\n        asm volatile(\"rdtsc\" : \"=a\"(low), \"=d\" (high));\n        return low;\n#elif defined(__i386__)\n        uint64_t tsc;\n        asm volatile(\"rdtsc\" : \"=A\" (tsc));\n        return (uint32_t) tsc;\n#else\n#error unsupported\n#endif\n    }\n\n    void cpuSync() {\n        // Calls CPUID to force the pipeline to be flushed\n        uint32_t eax;\n        uint32_t ebx;\n        uint32_t ecx;\n        uint32_t edx;\n#ifdef __PIC__\n        // PIC: Need to save and restore ebx See:\n        // http://sam.zoy.org/blog/2007-04-13-shlib-with-non-pic-code-have-inline-assembly-and-pic-mix-well\n        asm(\"pushl %%ebx\\n\\t\" /* save %ebx */\n                \"cpuid\\n\\t\"\n                \"movl %%ebx, %[ebx]\\n\\t\" /* save what cpuid just put in %ebx */\n                \"popl %%ebx\" : \"=a\"(eax), [ebx] \"=r\"(ebx), \"=c\"(ecx), \"=d\"(edx) : \"a\" (0)\n                : \"cc\");\n#else\n        asm(\"cpuid\" : \"=a\" (eax), \"=b\" (ebx), \"=c\" (ecx), \"=d\" (edx) : \"a\" (0));\n#endif\n    }\n\n    uint32_t start_;\n    uint32_t end_;\n};\n\n}\n#endif\n"
  },
  {
    "path": "fst/eoscp.cc",
    "content": "//------------------------------------------------------------------------------\n// File: eoscp.cc\n// Author: Elvin-Alin Sindrilaru / Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <set>\n#include <string>\n#include <algorithm>\n#include <math.h>\n#include <unistd.h>\n#include <errno.h>\n#include <pwd.h>\n#include <grp.h>\n#include <sys/types.h>\n#include <sys/time.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <stdarg.h>\n#include <iostream>\n#include <chrono>\n#include <openssl/md5.h>\n#include <optional>\n#include <getopt.h>\n#include <XrdCl/XrdClFile.hh>\n#include <XrdCl/XrdClDefaultEnv.hh>\n#include <XrdCl/XrdClPostMaster.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include \"common/XrdErrorMap.hh\"\n#include \"common/Timing.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/StringSplit.hh\"\n#include \"common/json/Jsonifiable.hh\"\n#include \"common/json/JsonCppJsonifier.hh\"\n#include \"fst/layout/RaidDpLayout.hh\"\n#include \"fst/layout/ReedSLayout.hh\"\n#include \"fst/io/AsyncMetaHandler.hh\"\n#include \"fst/io/ChunkHandler.hh\"\n#include \"fst/io/xrd/XrdIo.hh\"\n#include \"fst/io/FileIo.hh\"\n#include \"fst/io/FileIoPluginCommon.hh\"\n#include \"fst/checksum/ChecksumPlugins.hh\"\n\n#define PROGRAM \"eoscp\"\n#define DEFAULTBUFFERSIZE 4*1024*1024\n#define MAXSRCDST    32\n\nusing eos::common::LayoutId;\n\ntypedef std::vector<std::pair<std::string, std::string> > VectLocationType;\n\nenum AccessType {\n  LOCAL_ACCESS, ///< local access\n  RAID_ACCESS, ///< xroot protocol but with raid layout\n  XRD_ACCESS, ///< xroot protocol\n  RIO_ACCESS, ///< any File IO plug-in remote protocol\n  CONSOLE_ACCESS ///< input/output to console\n};\n\nclass XferSummary : public eos::common::Jsonifiable<XferSummary>\n{\npublic:\n  std::vector<std::string> sources;\n  std::vector<std::string> destinations;\n  time_t rawtime;\n  std::string astime;\n  std::optional<std::string> xrdsecprotocol;\n  std::optional<std::string> krb5ccname;\n  std::optional<std::string> x509userproxy;\n  std::string src_clientinfo;\n  std::string dst_clientinfo;\n  uint64_t bytescopied;\n  uint64_t totalbytescopied;\n  float abs_time;\n  float realtime;\n  float copyrate;\n  double ingress_rate;\n  double egress_rate;\n  double ingress_microseconds;\n  double egress_microseconds;\n  float bandwidth;\n  std::optional<std::string> checksum_type;\n  std::optional<std::string> checksum_value;\n  off_t write_start;\n  off_t write_stop;\n  long long read_start;\n  long long read_stop;\n  int ndst;\n  virtual ~XferSummary() = default;\n};\n\nclass XferSummaryJson : public eos::common::JsonCppJsonifier<XferSummary>\n{\npublic:\n  virtual ~XferSummaryJson() = default;\n  void jsonify(const XferSummary* obj, std::stringstream& oss) override\n  {\n    Json::Value root;\n    root[\"unixtime\"] = Json::UInt64(obj->rawtime);\n    root[\"date\"] = obj->astime;\n    root[\"auth\"] = obj->xrdsecprotocol ? Json::Value(*obj->xrdsecprotocol) :\n                   Json::nullValue;\n    root[\"krb5\"] = obj->krb5ccname ? Json::Value(*obj->krb5ccname) :\n                   Json::nullValue;\n    root[\"x509userproxy\"] = obj->x509userproxy ? Json::Value(\n                              *obj->x509userproxy) : Json::nullValue;\n    initializeArray(root[\"sources\"]);\n\n    for (size_t i = 0; i < obj->sources.size(); ++i) {\n      root[\"sources\"].append(obj->sources[i]);\n    }\n\n    for (size_t i = 0; i < obj->destinations.size(); ++i) {\n      root[\"destinations\"].append(obj->destinations[i]);\n    }\n\n    root[\"bytes_copied\"] = Json::UInt64(obj->bytescopied);\n\n    if (obj->ndst > 1) {\n      root[\"totalbytes_copied\"] = Json::UInt64(obj->totalbytescopied);\n    }\n\n    root[\"realtime\"] = obj->realtime;\n    root[\"copy_rate\"] = obj->copyrate;\n    root[\"ingress_rate\"] = obj->ingress_rate;\n    root[\"egress_rate\"] = obj->egress_rate;\n    root[\"ingress_server_info\"] = !obj->src_clientinfo.empty() ? Json::Value(\n                                    obj->src_clientinfo) : Json::nullValue;\n    root[\"egress_server_info\"] = !obj->dst_clientinfo.empty() ? Json::Value(\n                                   obj->dst_clientinfo) : Json::nullValue;\n    root[\"bandwidth\"] = obj->bandwidth ? Json::Value(obj->bandwidth) :\n                        Json::nullValue;\n    root[\"checksum_type\"] = obj->checksum_type ? Json::Value(\n                              *obj->checksum_type) : Json::nullValue;\n    root[\"checksum_value\"] = obj->checksum_value ? Json::Value(\n                               *obj->checksum_value) : Json::nullValue;\n    root[\"write_start\"] = Json::UInt64(obj->write_start);\n    root[\"write_stop\"] = Json::UInt64(obj->write_stop);\n    root[\"read_start\"] = obj->read_start >= 0 ? Json::UInt64(\n                           obj->read_start) : Json::nullValue;\n    root[\"read_stop\"] = obj->read_start >= 0 ? Json::UInt64(\n                          obj->read_stop) : Json::nullValue;\n    oss << root;\n  }\n};\n\nconst char* protocols[] = {\"file\", \"raid\", \"xroot\", \"rio\", NULL};\nconst char* xs[] = {\"adler\", \"md5\", \"sha1\", \"crc32\", \"crc32c\"};\nstd::set<std::string> xsTypeSet(xs, xs + 5);\n\n///! vector of source file descriptors or IO objects\nstd::vector<std::pair<int, void*> > src_handler;\n\n///! vector of destination file descriptors or IO objects\nstd::vector<std::pair<int, void*> > dst_handler;\n\n///! vector of source host address and path file\nVectLocationType src_location;\n\n///! vector of destination host address and path file\nVectLocationType dst_location;\n\nstd::vector<AccessType> src_type; ///< vector of source type access\nstd::vector<AccessType> dst_type; ///< vector of destination type access\n\nint verbose = 0;\nint debug = 0;\nint monitoring = 0;\nint jsonoutput = 0;\nint trylocal = 0;\nint progbar = 1;\nint summary = 1;\nint nopio = 0;\n\nunsigned long long targetsize = 0;\nint euid = -1;\nint egid = -1;\nint nsrc = 1;\nint ndst = 1;\nint createdir = 0;\nint transparentstaging = 0;\nint appendmode = 0;\nlong long startbyte = -1;\nlong long stopbyte = -1;\noff_t startwritebyte = 0;\noff_t stopwritebyte = 0;\nchar symlinkname[4096];\nint dosymlink = 0;\nint replicamode = 0;\nfloat bandwidth = 0;\nXrdOucString cpname = \"\";\nXrdCl::XRootDStatus status;\nint retc = 0;\nuint32_t buffersize = DEFAULTBUFFERSIZE;\n\ndouble read_wait = 0; ///< statistics about total read time\ndouble write_wait = 0; ///< statistics about total write time\nchar* buffer = NULL; ///< used for doing the reading\nbool first_time = true; ///< first time prefetch two blocks\nbool nooverwrite = false; ///< buy default we overwrite the target files\nint gtimeout = 0; ///< copy process timeout in seconds\nint cksumcomparison =\n  0; ///< performs a checksum comparison between the source and the destination, returns an error to the user in the case it happens\nint cksummismatchdelete =\n  0; ///< performs a deletion of the destination file if the checksum of the source and the destination mismatch\n\n//..............................................................................\n// RAID related variables\n//..............................................................................\noff_t stripeWidth = 1024 * 1024;\nuint64_t offsetXrd = 0;\nint nparitystripes = 0;\n\nbool isRaidTransfer = false; ///< true if we currently handle a RAID transfer\nbool isSrcRaid = false; ///< meaningful only for RAID transfers\nbool isStreamFile = false; ///< the file is streamed\nbool doStoreRecovery = false; ///< store recoveries if the file is corrupted\nstd::string opaqueInfo; ///< opaque info containing the capabilities\n///< necessary to do a parallel IO open\n//! Preserve creation and modification timestamps (source local file,\n//! destination xrootd file)\nbool preserve = false;\n\nstd::string replicationType = \"\";\n//TODO: deal with the case when both the source and the destination are RAIN files\neos::fst::RainMetaLayout* redundancyObj = NULL;\n\nstd::string dst_lasturl;\nstd::string src_lasturl;\n\n//..............................................................................\n// Checksum variables\n//..............................................................................\noff_t offsetXS = 0;\nbool computeXS = false;\nstd::string xsString = \"\";\nstd::string xsValue = \"\";\nstd::unique_ptr<eos::fst::CheckSum> xsObj;\n\n\n//..............................................................................\n// To compute throughput etc\n//..............................................................................\nstruct timeval abs_start_time;\nstruct timeval abs_stop_time;\nstruct timezone tz;\ndouble ingress_microseconds = 0;\ndouble egress_microseconds = 0;\n\nstd::string progressFile = \"\";\n\nchar* source[MAXSRCDST];\nchar* destination[MAXSRCDST];\n\n//------------------------------------------------------------------------------\n// Usage command\n//------------------------------------------------------------------------------\n\nvoid\nusage()\n{\n  fprintf(stderr,\n          \"Usage: %s [--version] [-5] [-0] [-X <type>] [-t <mb/s>] [-h] [-x] [-v] [-V] [-d] [-l] [-j] [-b <size>] [-T <size>] [-Y] [-n] [-s] [-u <id>] [-g <id>] [-S <#>] [-D <#>] [-O <filename>] [-N <name>]<src1> [src2...] <dst1> [dst2...]\\n\",\n          PROGRAM);\n  fprintf(stderr, \"       -h           : help\\n\");\n  fprintf(stderr, \"       --version    : eoscp software version\\n\");\n  fprintf(stderr, \"       -d           : debug mode\\n\");\n  fprintf(stderr, \"       -v           : verbose mode\\n\");\n  fprintf(stderr, \"       -V           : write summary as key value pairs\\n\");\n  fprintf(stderr, \"       -l           : try to force the destination to the \"\n          \"local disk server [not supported]\\n\");\n  fprintf(stderr, \"       -a           : append to the file rather than truncate\"\n          \" an existing file\\n\");\n  fprintf(stderr, \"       -A <offset>  : append/overwrite at offset\\n\");\n  fprintf(stderr,\n          \"       -b <size>    : use <size> as buffer size for copy operations\\n\");\n  fprintf(stderr,\n          \"       -T <size>    : use <size> as target size for copies from STDIN\\n\");\n  fprintf(stderr,\n          \"       -m <mode>    : set the mode for the destination file\\n\");\n  fprintf(stderr, \"       -n           : hide progress bar\\n\");\n  fprintf(stderr, \"       -N           : set name for progress printout\\n\");\n  fprintf(stderr, \"       -s           : hide summary\\n\");\n  fprintf(stderr,\n          \"       -j           : JSON output (flags -V -d -v -s are ignored)\\n\");\n  fprintf(stderr,\n          \"       -u <uid|name>: use <uid> as UID to execute the operation -  (user)<name> is mapped to unix UID if possible\\n\");\n  fprintf(stderr,\n          \"       -g <gid|name>: use <gid> as GID to execute the operation - (group)<name> is mapped to unix GID if possible\\n\");\n  fprintf(stderr,\n          \"       -t <mb/s>    : reduce the traffic to an average of <mb/s> mb/s\\n\");\n  fprintf(stderr, \"       -S <#>       : read from <#> sources in 'parallel'\\n\");\n  fprintf(stderr, \"       -D <#>       : write to <#> sources in 'parallel'\\n\");\n  fprintf(stderr, \"       -q <s>               : quit copy after <s> seconds\\n\");\n  fprintf(stderr,\n          \"       -O <file>    : write progress file to <file> (0.00 - 100.00%%)\\n\");\n  fprintf(stderr, \"       -i           : enable transparent staging\\n\");\n  fprintf(stderr,\n          \"       -p           : create all needed subdirectories for destination paths\\n\");\n  fprintf(stderr, \"       <srcN>       : path/url or - for STDIN\\n\");\n  fprintf(stderr, \"       <dstN>       : path/url or - for STDOUT\\n\");\n  fprintf(stderr, \"       -5           : compute md5\\n\");\n  fprintf(stderr,\n          \"       -r <start>:<stop> : read only the range from <start> bytes to <stop> bytes\\n\");\n  fprintf(stderr,\n          \"       -L <linkname>: create a symbolic link to the 1st target file with name <linkname>\\n\");\n  fprintf(stderr,\n          \"       -R           : replication mode - avoid dir creation and stat's\\n\");\n  fprintf(stderr,\n          \"       -X           : checksum type: adler, crc32, crc32c, sha1, md5\\n\");\n  fprintf(stderr,\n          \"       -e           : RAID layouts - error correction layout: raiddp/reeds\\n\");\n  fprintf(stderr,\n          \"       -P           : RAID layouts - number of parity stripes\\n\");\n  fprintf(stderr,\n          \"       -f           : RAID layouts - store the modifications in case of errors\\n\");\n  fprintf(stderr,\n          \"       -c           : RAID layouts - force check and recover any corruptions in any stripe\\n\");\n  fprintf(stderr, \"       -Y           : RAID layouts - streaming file\\n\");\n  fprintf(stderr,\n          \"       -0           : RAID layouts - don't use parallel IO mode\\n\");\n  fprintf(stderr, \"       -x           : don't overwrite an existing file\\n\");\n  fprintf(stderr,\n          \"       -C           : fail if checksum comparison between source and destination fails (XRootD destination only)\\n\");\n  fprintf(stderr,\n          \"       -E           : automatically delete the destination file if checksum comparison between source and destination fails (XRootD destination only) \\n\");\n  fprintf(stderr,\n          \"       --preserve   : preserves source file creation date (local file source and xrootd destination only)\\n\");\n  exit(-1);\n}\n\n/**\n * Display the eoscp software information.\n * For now, only displays the EOS_CLIENT_VERSION and the EOS_CLIENT_RELEASE the same\n * way it is done by eos -v\n */\nvoid displayInformation()\n{\n  std::stringstream infos;\n  infos << \"EOS \" << VERSION << std::endl << std::endl;\n  infos << \"Developed by the CERN IT storage group\" << std::endl;\n  fprintf(stdout, \"%s\", infos.str().c_str());\n  exit(0);\n}\n\nextern \"C\"\n{\n  //----------------------------------------------------------------------------\n  // Function + macros to allow formatted print via cout,cerr\n  //----------------------------------------------------------------------------\n\n  void\n  cout_print(const char* format, ...)\n  {\n    char cout_buff[4096];\n    va_list args;\n    va_start(args, format);\n    vsprintf(cout_buff, format, args);\n    va_end(args);\n    std::cout << cout_buff;\n  }\n\n  void\n  cerr_print(const char* format, ...)\n  {\n    char cerr_buff[4096];\n    va_list args;\n    va_start(args, format);\n    vsprintf(cerr_buff, format, args);\n    va_end(args);\n    std::cerr << cerr_buff;\n  }\n\n#define COUT(s) do {                            \\\n    cout_print s;                               \\\n  } while (0)\n\n#define CERR(s) do {                            \\\n    cerr_print s;                               \\\n  } while (0)\n\n}\n\nXferSummary createXferSummary(const VectLocationType& src,\n                              const VectLocationType& dst, unsigned long long bytesread)\n{\n  XferSummary xferSummary;\n  xferSummary.setJsonifier(std::make_shared<XferSummaryJson>());\n  std::string src_clientinfo;\n  std::string dst_clientinfo;\n\n  if (src_lasturl.length()) {\n    XrdCl::URL url(src_lasturl);\n    XrdCl::URL::ParamsMap cgi = url.GetParams();\n    std::string zclientinfo = cgi[\"eos.clientinfo\"];\n    eos::common::SymKey::ZDeBase64(zclientinfo, src_clientinfo);\n    xferSummary.src_clientinfo = src_clientinfo;\n  }\n\n  if (dst_lasturl.length()) {\n    XrdCl::URL url(dst_lasturl);\n    XrdCl::URL::ParamsMap cgi = url.GetParams();\n    std::string zclientinfo = cgi[\"eos.clientinfo\"];\n    eos::common::SymKey::ZDeBase64(zclientinfo, dst_clientinfo);\n    xferSummary.dst_clientinfo = dst_clientinfo;\n  }\n\n  gettimeofday(&abs_stop_time, &tz);\n  float abs_time = ((float)((abs_stop_time.tv_sec - abs_start_time.tv_sec) * 1000\n                            +\n                            (abs_stop_time.tv_usec - abs_start_time.tv_usec) / 1000));\n  xferSummary.abs_time = abs_time;\n\n  for (unsigned int i = 0; i < src.size(); i++) {\n    xferSummary.sources.push_back(\"\");\n    auto& srcStr = xferSummary.sources.back();\n    srcStr += src[i].first.c_str();\n    srcStr += src[i].second.c_str();\n    size_t pos = srcStr.rfind('?');\n\n    if (pos != std::string::npos) {\n      srcStr.erase(pos);\n    }\n\n    if (srcStr.find(\"//replicate:\") != std::string::npos) {\n      // disable client redirection eoscp\n      XrdCl::DefaultEnv::GetEnv()->PutInt(\"RedirectLimit\", 1);\n    }\n  }\n\n  for (unsigned int i = 0; i < dst.size(); i++) {\n    xferSummary.destinations.push_back(\"\");\n    auto& dstStr = xferSummary.destinations.back();\n    dstStr += dst[i].first.c_str();\n    dstStr += dst[i].second.c_str();\n    size_t pos = dstStr.rfind('?');\n\n    if (pos != std::string::npos) {\n      dstStr.erase(pos);\n    }\n\n    if (dstStr.find(\"//replicate:\") != std::string::npos) {\n      // disable client redirection eoscp\n      XrdCl::DefaultEnv::GetEnv()->PutInt(\"RedirectLimit\", 1);\n    }\n  }\n\n  time_t rawtime;\n  struct tm* timeinfo;\n  time(&rawtime);\n  timeinfo = localtime(&rawtime);\n  XrdOucString astime = asctime(timeinfo);\n  astime.erase(astime.length() - 1);\n  xferSummary.rawtime = rawtime;\n  xferSummary.astime = astime.c_str();\n  xferSummary.xrdsecprotocol = getenv(\"XrdSecPROTOCOL\") ?\n                               std::optional<std::string>(getenv(\"XrdSecPROTOCOL\")) : std::nullopt;\n  xferSummary.krb5ccname = getenv(\"KRB5CCNAME\") ? std::optional<std::string>\n                           (getenv(\"KRB5CCNAME\")) : std::nullopt;\n  xferSummary.x509userproxy = getenv(\"X509_USER_PROXY\") ?\n                              std::optional<std::string>(getenv(\"X509_USER_PROXY\")) : std::nullopt;\n  xferSummary.bytescopied = bytesread;\n  xferSummary.totalbytescopied = bytesread * ndst;\n  xferSummary.realtime = xferSummary.abs_time / 1000.0;\n  xferSummary.copyrate = xferSummary.abs_time > 0 ? xferSummary.bytescopied /\n                         xferSummary.abs_time / 1000.0 : 0;\n  xferSummary.ingress_microseconds = ingress_microseconds;\n  xferSummary.egress_microseconds = egress_microseconds;\n  xferSummary.ingress_rate = xferSummary.ingress_microseconds  ? bytesread /\n                             xferSummary.ingress_microseconds : 0;\n  xferSummary.egress_rate = xferSummary. egress_microseconds  ? bytesread /\n                            xferSummary.egress_microseconds : 0;\n  xferSummary.bandwidth = bandwidth;\n\n  if (computeXS) {\n    xferSummary.checksum_type = xsString;\n    xferSummary.checksum_value = xsValue;\n  }\n\n  xferSummary.write_start = startwritebyte;\n  xferSummary.write_stop = stopwritebyte;\n  xferSummary.read_start = startbyte;\n  xferSummary.read_stop = stopbyte;\n  xferSummary.ndst = ndst;\n  return xferSummary;\n}\n\n//------------------------------------------------------------------------------\n// Printing summary header\n//------------------------------------------------------------------------------\nvoid\nprint_summary_header(const XferSummary& xferSummary)\n{\n  if (!monitoring) {\n    COUT((\"[eoscp] #################################################################\\n\"));\n    COUT((\"[eoscp] # Date                     : ( %lu ) %s\\n\",\n          (unsigned long) xferSummary.rawtime, xferSummary.astime.c_str()));\n    COUT((\"[eoscp] # auth forced=%s krb5=%s gsi=%s\\n\",\n          xferSummary.xrdsecprotocol ? xferSummary.xrdsecprotocol->c_str() : \"<none>\",\n          xferSummary.krb5ccname ? xferSummary.krb5ccname->c_str() : \"<none>\",\n          xferSummary.x509userproxy ? xferSummary.x509userproxy->c_str() : \"<none>\"));\n\n    for (unsigned int i = 0; i < xferSummary.sources.size(); i++) {\n      COUT((\"[eoscp] # Source Name [%02d]         : %s\\n\", i,\n            xferSummary.sources[i].c_str()));\n    }\n\n    for (unsigned int i = 0; i < xferSummary.destinations.size(); i++) {\n      COUT((\"[eoscp] # Destination Name [%02d]    : %s\\n\", i,\n            xferSummary.destinations[i].c_str()));\n    }\n  } else {\n    COUT((\"unixtime=%lu date=\\\"%s\\\" auth=\\\"%s\\\" \",\n          (unsigned long) xferSummary.rawtime,\n          xferSummary.astime.c_str(),\n          xferSummary.xrdsecprotocol ? xferSummary.xrdsecprotocol->c_str() : \"(null)\"));\n\n    for (unsigned int i = 0; i < xferSummary.sources.size(); i++) {\n      COUT((\"src_%d=%s \", i, xferSummary.sources[i].c_str()));\n    }\n\n    for (unsigned int i = 0; i < xferSummary.destinations.size(); i++) {\n      COUT((\"dst_%d=%s \", i, xferSummary.destinations[i].c_str()));\n    }\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Print summary\n//------------------------------------------------------------------------------\n\nvoid\nprint_summary(const XferSummary& xferSummary)\n{\n  print_summary_header(xferSummary);\n\n  if (!monitoring) {\n    // This is a quick-and-dirty trick to keep the ':' after the checksum type label aligned with the rest\n    // of the output (part 1)\n    std::string key = \"[eoscp] # Data Copied [bytes]      \";\n    const size_t keyLen = key.size();\n    key += \": %lld\\n\";\n    COUT((key.c_str(), xferSummary.totalbytescopied));\n\n    if (xferSummary.ndst > 1) {\n      COUT((\"[eoscp] # Tot. Data Copied [bytes] : %lld\\n\",\n            xferSummary.totalbytescopied));\n    }\n\n    COUT((\"[eoscp] # Realtime [s]             : %.03f\\n\", xferSummary.realtime));\n\n    if (xferSummary.abs_time > 0) {\n      COUT((\"[eoscp] # Eff.Copy. Rate[MB/s]     : %.02f\\n\",\n            xferSummary.copyrate));\n    }\n\n    if (xferSummary.ingress_microseconds) {\n      COUT((\"[eoscp] # INGRESS [MB/s]           : %.02f\\n\",\n            xferSummary.ingress_rate));\n    }\n\n    if (xferSummary.egress_microseconds) {\n      COUT((\"[eoscp] # EGRESS [MB/s]            : %.02f\\n\",\n            xferSummary.egress_rate));\n    }\n\n    if (xferSummary.bandwidth) {\n      COUT((\"[eoscp] # Bandwidth[MB/s]          : %d\\n\",\n            (int) xferSummary.bandwidth));\n    }\n\n    if (xferSummary.checksum_type) {\n      // This is a quick-and-dirty trick to keep the ':' after the checksum type label aligned with the rest\n      // of the output (part 2)\n      std::string cksumTypeTitle = \"[eoscp] # Checksum Type \" +\n                                   *xferSummary.checksum_type;\n      size_t paddingSize = int(keyLen - cksumTypeTitle.length()) > 0 ? keyLen -\n                           cksumTypeTitle.length() : 0;\n\n      if (paddingSize) {\n        cksumTypeTitle += std::string(paddingSize, ' ');\n      }\n\n      COUT(((cksumTypeTitle + std::string(\": \")).c_str()));\n      COUT((\"%s\", xferSummary.checksum_value->c_str()));\n      COUT((\"\\n\"));\n    }\n\n    COUT((\"[eoscp] # Write Start Position     : %lld\\n\", xferSummary.write_start));\n    COUT((\"[eoscp] # Write Stop  Position     : %lld\\n\", xferSummary.write_stop));\n\n    if (xferSummary.read_start >= 0) {\n      COUT((\"[eoscp] # Read  Start Position     : %lld\\n\", xferSummary.read_start));\n      COUT((\"[eoscp] # Read  Stop  Position     : %lld\\n\", xferSummary.read_stop));\n    }\n\n    if (!xferSummary.src_clientinfo.empty()) {\n      COUT((\"[eoscp] # INGRESS Server Info      : %s\\n\",\n            xferSummary.src_clientinfo.c_str()));\n    }\n\n    if (!xferSummary.dst_clientinfo.empty()) {\n      COUT((\"[eoscp] # EGRESS  Server info      : %s\\n\",\n            xferSummary.dst_clientinfo.c_str()));\n    }\n  } else {\n    COUT((\"bytes_copied=%lld \", xferSummary.bytescopied));\n\n    if (ndst > 1) {\n      COUT((\"totalbytes_copied=%lld \", xferSummary.totalbytescopied));\n    }\n\n    COUT((\"realtime=%.02f \", xferSummary.abs_time / 1000.0));\n\n    if (xferSummary.abs_time > 0) {\n      COUT((\"copy_rate=%f \", xferSummary.bytescopied / xferSummary.abs_time /\n            1000.0));\n    }\n\n    if (xferSummary.ingress_microseconds) {\n      COUT((\"ingress_rate=%f \",\n            xferSummary.ingress_rate));\n    }\n\n    if (xferSummary.egress_microseconds) {\n      COUT((\"egress_rate=%f \",\n            xferSummary.egress_rate));\n    }\n\n    if (xferSummary.bandwidth) {\n      COUT((\"bandwidth=%d \", (int) xferSummary.bandwidth));\n    }\n\n    if (xferSummary.checksum_type) {\n      COUT((\"checksum_type=%s \", xferSummary.checksum_type->c_str()));\n      COUT((\"checksum=%s \", xferSummary.checksum_value->c_str()));\n    }\n\n    COUT((\"write_start=%lld \", xferSummary.write_start));\n    COUT((\"write_stop=%lld \", xferSummary.write_stop));\n\n    if (xferSummary.read_start >= 0) {\n      COUT((\"read_start=%lld \", xferSummary.read_start));\n      COUT((\"read_stop=%lld \", xferSummary.read_stop));\n    }\n  }\n}\n\nvoid print_json_summary(const XferSummary& xferSummary)\n{\n  std::stringstream ss;\n  xferSummary.jsonify(ss);\n  COUT((ss.str().c_str()));\n}\n\n//------------------------------------------------------------------------------\n// Printing progress bar\n//------------------------------------------------------------------------------\n\nvoid\nprint_progbar(unsigned long long bytesread, unsigned long long size)\n{\n  if (!size) {\n    bytesread = size = 1; // fake 100% in that case\n  }\n\n  CERR((\"[eoscp] %-24s Total %.02f MB\\t|\", cpname.c_str(),\n        (float) size / 1024 / 1024));\n\n  for (int l = 0; l < 20; l++) {\n    if (l < ((int)(20.0 * bytesread / size))) {\n      CERR((\"=\"));\n    }\n\n    if (l == ((int)(20.0 * bytesread / size))) {\n      CERR((\">\"));\n    }\n\n    if (l > ((int)(20.0 * bytesread / size))) {\n      CERR((\".\"));\n    }\n  }\n\n  float abs_time = ((float)((abs_stop_time.tv_sec - abs_start_time.tv_sec) * 1000\n                            +\n                            (abs_stop_time.tv_usec - abs_start_time.tv_usec) / 1000));\n  CERR((\"| %.02f %% [%.01f MB/s]\\r\", 100.0 * bytesread / size,\n        bytesread / abs_time / 1000.0));\n}\n\n\n//------------------------------------------------------------------------------\n// Write progress\n//------------------------------------------------------------------------------\n\nvoid\nwrite_progress(unsigned long long bytesread, unsigned long long size)\n{\n  static double lastprogress = 0;\n  double progress = 100 * bytesread / (double)(size ? size : 1);\n\n  if (progress > 100) {\n    progress = 100;\n  }\n\n  if ((fabs(progress - lastprogress) <= 1.0) && (progress != 100.)) {\n    // skip this update\n    return;\n  }\n\n  std::string pf = progressFile;\n  pf += \".tmp\";\n  FILE* fd = fopen(pf.c_str(), \"w+\");\n\n  if (fd) {\n    fprintf(fd, \"%.02f %llu %llu\\n\", progress, bytesread, size);\n    fclose(fd);\n\n    if (rename(pf.c_str(), progressFile.c_str())) {\n      fprintf(stderr, \"error: renaming of progress file failed (%s=>%s)\\n\",\n              pf.c_str(), progressFile.c_str());\n    }\n  }\n}\n\nstruct CompareCksumResult {\n  bool cksumMismatch = true;\n  uint32_t xrdErrno = 0;\n  std::string errMsg = \"\";\n};\n\nCompareCksumResult compareChecksum(XrdCl::FileSystem& fs,\n                                   const std::string& destFilePath, std::string srcCksumType,\n                                   const std::string& srcCksumValue)\n{\n  CompareCksumResult result;\n  // Get the checksum of the file that got uploaded to the destination\n  XrdCl::Buffer* responseRaw = 0;\n\n  if (srcCksumType == \"adler\") {\n    // xrootd adler32 checksum is called \"adler32\"\n    srcCksumType = \"adler32\";\n  }\n\n  XrdCl::Buffer arg;\n  arg.FromString(destFilePath + \"?cks.type=\" + srcCksumType);\n  XrdCl::XRootDStatus status = fs.Query(XrdCl::QueryCode::Checksum, arg,\n                                        responseRaw);\n  std::unique_ptr<XrdCl::Buffer> response(responseRaw);\n\n  if (status.IsOK()) {\n    // we got the checksum of the destination file\n    // compare the checksums between source and destination\n    std::string queryCksumResp = response->GetBuffer();\n    auto splittedResp = eos::common::StringSplit(queryCksumResp, \" \");\n\n    if (splittedResp.size() == 2) {\n      // The checksum response we received has a proper format\n      const std::string destCksumType(splittedResp[0]);\n      const std::string destCksumValue(splittedResp[1]);\n\n      if (destCksumType == srcCksumType) {\n        // Same checksum type between the source and the destination\n        if (destCksumValue == srcCksumValue) {\n          // Checksum match!\n          result.cksumMismatch = false;\n        } else {\n          // Checksum mismatch\n          result.xrdErrno = EIO;\n          result.errMsg = \"error: checksum mismatch between source (\" + srcCksumValue +\n                          \") and destination (\" + destCksumValue + \")\";\n        }\n      } else {\n        // Different checksum type between source and destination\n        result.errMsg =\n          \"error while extracting destination checksum: received a different checksum type from the destination (\"\n          + destCksumType + \") compared to the one computed on the source (\" +\n          srcCksumType + \")\";\n        result.xrdErrno = EINVAL;\n      }\n    } else {\n      // Wrong response format received\n      result.errMsg =\n        \"error while extracting the destination checksum: expected 'destCksumType destCksumValue', received:\"\n        + queryCksumResp;\n      result.xrdErrno = EINVAL;\n    }\n  } else {\n    // Problem while querying the destination checksum\n    result.errMsg = \"error while getting the destination checksum: \" +\n                    status.ToStr();\n    result.xrdErrno = status.errNo;\n  }\n\n  return result;\n}\n\n\n//------------------------------------------------------------------------------\n// Abort handler\n//------------------------------------------------------------------------------\n\nvoid\nabort_handler(int)\n{\n  //  print_summary_header(src_location, dst_location);\n  fprintf(stdout, \"error: [eoscp] has been aborted\\n\");\n  exit(EINTR);\n}\n\n//------------------------------------------------------------------------------\n// Alarm handler\n//------------------------------------------------------------------------------\n\nvoid\nalarm_handler(int)\n{\n  //  print_summary_header(src_location, dst_location);\n  fprintf(stdout, \"error: [eoscp] has timedout after %d seconds\\n\", gtimeout);\n  exit(ETIMEDOUT);\n}\n\n\n//------------------------------------------------------------------------------\n// Main function\n//------------------------------------------------------------------------------\n\nint\nmain(int argc, char* argv[])\n{\n  int c;\n  mode_t dest_mode[MAXSRCDST];\n  int set_mode = 0;\n  extern char* optarg;\n  extern int optind;\n  // Ugly temporary hack for stopping the XRootD PostMaster environment no matter what happens (https://its.cern.ch/jira/projects/EOS/issues/EOS-6087)\n  auto stopPostMaster = [&](void*) {\n    XrdCl::DefaultEnv::GetPostMaster()->Stop();\n  };\n  std::unique_ptr<void, decltype(stopPostMaster)> stopPostMasterDeleter((void*)1,\n      stopPostMaster);\n  XrdCl::DefaultEnv::GetEnv()->PutInt(\"MetalinkProcessing\", 0);\n  XrdCl::DefaultEnv::GetEnv()->PutInt(\"ParallelEvtLoop\",\n                                      8);  // needed for high performance on 100GE\n  // Define long options using struct option\n  struct option long_options[] = {\n    {\"version\", no_argument, nullptr, 'I'},\n    {\"preserve\", no_argument, nullptr, 'F'},\n    {nullptr, 0, nullptr, 0} // Required end marker\n  };\n\n  while ((c = getopt_long(argc, argv,\n                          \"CEnshxdvlipfcje:P:X:b:m:u:g:t:S:D:5aA:r:N:L:RT:O:V0q:\", long_options,\n                          nullptr)) != -1) {\n    switch (c) {\n    case 'v':\n      verbose = 1;\n      break;\n\n    case 'V':\n      monitoring = 1;\n      break;\n\n    case 'j':\n      jsonoutput = 1;\n      break;\n\n    case 'd':\n      debug = 1;\n      break;\n\n    case 'l':\n      trylocal = 1;\n      break;\n\n    case 'n':\n      progbar = 0;\n      break;\n\n    case 'p':\n      createdir = 1;\n      break;\n\n    case 's':\n      summary = 0;\n      break;\n\n    case 'i':\n      transparentstaging = 1;\n      break;\n\n    case 'a':\n      appendmode = 1;\n      break;\n\n    case 'A':\n      appendmode = 1;\n      startwritebyte = strtoull(optarg, 0, 10);\n      break;\n\n    case 'c':\n      doStoreRecovery = true;\n      offsetXrd = -1;\n      break;\n\n    case 'f':\n      break;\n\n    case 'x':\n      nooverwrite = true;\n      break;\n\n    case 'e':\n      replicationType = optarg;\n\n      if ((replicationType != \"raiddp\") && (replicationType != \"reeds\")) {\n        fprintf(stderr, \"error: no such RAID layout\\n\");\n        exit(-1);\n      }\n\n      isRaidTransfer = true;\n      break;\n\n    case 'X': {\n      xsString = optarg;\n\n      if (find(xsTypeSet.begin(), xsTypeSet.end(), xsString) == xsTypeSet.end()) {\n        fprintf(stderr, \"error: no such checksum type: %s\\n\", optarg);\n        exit(-1);\n      }\n\n      int layout = 0;\n      unsigned long layoutId = 0;\n\n      if (xsString == \"adler\") {\n        layoutId = LayoutId::GetId(layout, LayoutId::kAdler);\n      } else if (xsString == \"crc32\") {\n        layoutId = LayoutId::GetId(layout, LayoutId::kCRC32);\n      } else if (xsString == \"md5\") {\n        layoutId = LayoutId::GetId(layout, LayoutId::kMD5);\n      } else if (xsString == \"sha1\") {\n        layoutId = LayoutId::GetId(layout, LayoutId::kSHA1);\n      } else if (xsString == \"crc32c\") {\n        layoutId = LayoutId::GetId(layout, LayoutId::kCRC32C);\n      }\n\n      xsObj = eos::fst::ChecksumPlugins::GetChecksumObject(layoutId);\n\n      if (xsObj) {\n        xsObj->Reset();\n        computeXS = true;\n      }\n\n      break;\n    }\n\n    case 'P':\n      nparitystripes = atoi(optarg);\n\n      if (nparitystripes < 2) {\n        fprintf(stderr, \"error: number of parity stripes >= 2\\n\");\n        exit(-1);\n      }\n\n      break;\n\n    case '0':\n      nopio = true;\n      break;\n\n    case 'O':\n      progressFile = optarg;\n      break;\n\n    case 'u':\n      euid = atoi(optarg);\n      char tuid[128];\n      sprintf(tuid, \"%d\", euid);\n\n      if (strcmp(tuid, optarg)) {\n        // this is not a number, try to map it with getpwnam\n        struct passwd* pwinfo = getpwnam(optarg);\n\n        if (pwinfo) {\n          euid = pwinfo->pw_uid;\n\n          if (debug) {\n            fprintf(stdout, \"[eoscp]: mapping user  %s=>UID:%d\\n\", optarg, euid);\n          }\n        } else {\n          fprintf(stderr, \"error: cannot map user %s to any unix id!\\n\", optarg);\n          exit(-ENOENT);\n        }\n      }\n\n      break;\n\n    case 'g':\n      egid = atoi(optarg);\n      char tgid[128];\n      sprintf(tgid, \"%d\", egid);\n\n      if (strcmp(tgid, optarg)) {\n        // this is not a number, try to map it with getgrnam\n        struct group* grinfo = getgrnam(optarg);\n\n        if (grinfo) {\n          egid = grinfo->gr_gid;\n\n          if (debug) {\n            fprintf(stdout, \"[eoscp]: mapping group %s=>GID:%d\\n\", optarg, egid);\n          }\n        } else {\n          fprintf(stderr, \"error: cannot map group %s to any unix id!\\n\", optarg);\n          exit(-ENOENT);\n        }\n      }\n\n      break;\n\n    case 't':\n      bandwidth = atoi(optarg);\n\n      if ((bandwidth < 1) || (bandwidth > 2000)) {\n        fprintf(stderr, \"error: bandwidth can only be 1 <= bandwidth <= 2000 Mb/s\\n\");\n        exit(-1);\n      }\n\n      break;\n\n    case 'q':\n      gtimeout = atoi(optarg);\n      break;\n\n    case 'S':\n      nsrc = atoi(optarg);\n\n      if ((nsrc < 1) || (nsrc > MAXSRCDST)) {\n        fprintf(stderr, \"error: # of sources must be 1 <= # <= %d\\n\", MAXSRCDST);\n        exit(-1);\n      }\n\n      break;\n\n    case 'D':\n      ndst = atoi(optarg);\n\n      if ((ndst < 1) || (ndst > MAXSRCDST)) {\n        fprintf(stderr, \"error: # of sources must be 1 <= # <= %d\\n\", MAXSRCDST);\n        exit(-1);\n      }\n\n      break;\n\n    case 'N':\n      cpname = optarg;\n      break;\n\n    case 'b':\n      buffersize = atoi(optarg);\n\n      if ((buffersize < 4096) || (buffersize > 100 * 1024 * 1024)) {\n        fprintf(stderr, \"error: buffer size can only 4k <= size <= 100 M\\n\");\n        exit(-1);\n      }\n\n      break;\n\n    case 'T':\n      targetsize = strtoull(optarg, 0, 10);\n      break;\n\n    case 'm':\n      for (int i = 0; i < MAXSRCDST; i++) {\n        dest_mode[i] = strtol(optarg, 0, 8);\n      }\n\n      set_mode = 1;\n      break;\n\n    case 'r':\n      char* colon;\n      colon = strchr(optarg, ':');\n\n      if (colon == NULL) {\n        fprintf(stderr, \"error: range has to be given in the format \"\n                \"<startbyte>:<stopbyte> e.g. 0:100000\\n\");\n        exit(-1);\n      }\n\n      *colon = 0;\n      startbyte = strtoll(optarg, 0, 0);\n      stopbyte = strtoll(colon + 1, 0, 0);\n\n      if (debug) {\n        fprintf(stdout, \"[eoscp]: reading range start=%lld stop=%lld\\n\", startbyte,\n                stopbyte);\n      }\n\n      break;\n\n    case 'L':\n      sprintf(symlinkname, \"%s\", optarg);\n      dosymlink = 1;\n      break;\n\n    case 'R':\n      replicamode = 1;\n      break;\n\n    case 'Y':\n      isStreamFile = true;\n      break;\n\n    case 'C':\n      cksumcomparison = 1;\n      break;\n\n    case 'E':\n      cksummismatchdelete = 1;\n      break;\n\n    case 'I':\n      displayInformation();\n      break;\n\n    case 'F':\n      preserve = true;\n      break;\n\n    case 'h':\n    default:\n      usage();\n      ;\n    }\n  }\n\n  if (jsonoutput) {\n    summary = 1;\n    monitoring = 0;\n    debug = 0;\n    verbose = 0;\n    progbar = 0;\n  }\n\n  if (debug) {\n    eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n    g_logging.SetLogPriority(LOG_DEBUG);\n  }\n\n  if (optind - 1 + nsrc + ndst >= argc) {\n    usage();\n  }\n\n  if (gtimeout) {\n    signal(SIGALRM, alarm_handler);\n    alarm(gtimeout);\n  }\n\n  //............................................................................\n  // Allocate the buffer used for copy\n  //............................................................................\n  buffer = new char[2 * buffersize];\n\n  if ((!buffer)) {\n    fprintf(stderr, \"error: cannot allocate buffer of size %u\\n\", 2 * buffersize);\n    exit(-ENOMEM);\n  }\n\n  if (debug) {\n    fprintf(stderr, \"[eoscp]: allocate copy buffer with %u bytes\\n\",\n            2 * buffersize);\n  }\n\n  //.............................................................................\n  // Get the address and the file path from the input\n  //.............................................................................\n  std::string location;\n  std::string address;\n  std::string file_path;\n\n  for (int i = 0; i < nsrc; i++) {\n    location = argv[optind + i];\n    size_t pos = location.find(\"://\");\n    pos = location.find(\"//\", pos + 3);\n\n    if (pos == std::string::npos) {\n      address = \"\";\n      file_path = location;\n    } else {\n      address = std::string(location, 0, pos + 1);\n      file_path = std::string(location, pos + 1);\n    }\n\n    src_location.push_back(std::make_pair(address, file_path));\n\n    if (verbose || debug) {\n      if (i == 0) {\n        fprintf(stdout, \"[eoscp] \");\n      }\n\n      fprintf(stdout, \"src<%d>=%s \", i, location.c_str());\n    }\n  }\n\n  for (int i = 0; i < ndst; i++) {\n    location = argv[optind + nsrc + i];\n    size_t pos = location.find(\"://\");\n    pos = location.find(\"//\", pos + 3);\n\n    if (pos == std::string::npos) {\n      address = \"\";\n      file_path = location;\n    } else {\n      address = std::string(location, 0, pos + 1);\n      file_path = std::string(location, pos + 1);\n    }\n\n    dst_location.push_back(std::make_pair(address, file_path));\n\n    if (verbose || debug) {\n      fprintf(stdout, \"dst<%d>=%s \", i, location.c_str());\n    }\n  }\n\n  if (verbose || debug) {\n    fprintf(stdout, \"\\n\");\n  }\n\n  if (cksumcomparison || cksummismatchdelete) {\n    if (src_location.size() != 1 && dst_location.size() != 1) {\n      fprintf(stderr,\n              \"error: only one source and one destination can be provided if the destination checksum check option is enabled (-C or -E)\\n\");\n      exit(-EINVAL);\n    }\n\n    if (cksummismatchdelete && !cksumcomparison) {\n      fprintf(stderr,\n              \"error: source and destination checksum comparison (-C) not enabled, automatic deletion option (-E) cannot be enabled\\n\");\n      exit(-EINVAL);\n    }\n  }\n\n  if (preserve) {\n    if ((src_location.size() != 1) || (dst_location.size() != 1)) {\n      fprintf(stderr, \"error: only one source and one destination can be \"\n              \"specified when --preserve flag is provided\\n\");\n      exit(-EINVAL);\n    }\n  }\n\n  //.............................................................................\n  // Get the type of access we will be doing\n  //.............................................................................\n  if (isRaidTransfer) {\n    if (!nparitystripes) {\n      fprintf(stderr, \"error: number of parity stripes undefined\\n\");\n      exit(-EINVAL);\n    }\n\n    if (nsrc > ndst) {\n      isSrcRaid = true;\n    } else {\n      isSrcRaid = false;\n    }\n  }\n\n  int stat_failed = 0;\n  struct stat st[MAXSRCDST];\n  LayoutId::layoutid_t layout_src {0};\n\n  //.............................................................................\n  // Get sources access type\n  //.............................................................................\n  for (int i = 0; i < nsrc; i++) {\n    bool is_root = (src_location[i].first.find(\"root://\") == 0);\n    bool is_roots = (src_location[i].first.find(\"roots://\") == 0);\n\n    if (is_root || is_roots) {\n      if (isRaidTransfer && isSrcRaid) {\n        src_type.push_back(RAID_ACCESS);\n      } else {\n        // If we don't need to recover the source and we were not told explicitly\n        // that this is a RAIN transfer\n        if (!isRaidTransfer && !doStoreRecovery) {\n          //.......................................................................\n          // Test if we can do parallel IO access\n          //.......................................................................\n          bool doPIO = false;\n          XrdCl::Buffer arg;\n          XrdCl::Buffer* response = 0;\n          XrdCl::XRootDStatus status;\n          file_path = src_location[i].first + src_location[i].second;\n\n          if (file_path.find(\"//eos/\") != std::string::npos) {\n            // for any other URL it does not make sense to do the PIO access\n            if (!nopio) {\n              doPIO = true;\n            }\n          }\n\n          size_t spos = file_path.rfind(\"//\");\n          std::string address = file_path.substr(0, spos + 1);\n          XrdCl::URL url(address);\n\n          if (!url.IsValid()) {\n            fprintf(stderr, \"URL is invalid: %s\", address.c_str());\n            exit(-1);\n          }\n\n          XrdCl::FileSystem fs(url);\n\n          if (spos != std::string::npos) {\n            file_path.erase(0, spos + 1);\n          }\n\n          std::string request = file_path;\n\n          if ((file_path.find(\"?\") == std::string::npos)) {\n            request += \"?mgm.pcmd=open\";\n          } else {\n            request += \"&mgm.pcmd=open\";\n          }\n\n          arg.FromString(request);\n          st[0].st_size = 0;\n          st[0].st_mode = 0;\n\n          if (doPIO) {\n            status = fs.Query(XrdCl::QueryCode::OpaqueFile, arg, response);\n          }\n\n          if (doPIO && status.IsOK()) {\n            if (!getenv(\"EOS_FST_XRDIO_READAHEAD\")) {\n              setenv(\"EOS_FST_XRDIO_READAHEAD\", \"1\", 1);\n            }\n\n            if (!getenv(\"EOS_FST_XRDIO_BLOCK_SIZE\")) {\n              setenv(\"EOS_FST_XRDIO_BLOCK_SIZE\", \"4194304 \", 1);\n            }\n\n            XrdCl::StatInfo* statresponse = 0;\n            status = fs.Stat(file_path.c_str(), statresponse);\n\n            if (status.IsOK()) {\n              st[0].st_size = statresponse->GetSize();\n              st[0].st_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;\n\n              if (statresponse->TestFlags(XrdCl::StatInfo::IsWritable)) {\n                st[0].st_mode |= S_IWGRP;\n              }\n            }\n\n            delete statresponse;\n\n            //..................................................................\n            // Parse output\n            //..................................................................\n            if (verbose || debug) {\n              fprintf(stderr, \"[eoscp] having PIO_ACCESS for source location=%i size=%llu \\n\",\n                      i, (unsigned long long) st[0].st_size);\n            }\n\n            XrdOucString tag;\n            XrdOucString stripe_path;\n            std::string origResponse(response->GetBuffer(), response->GetSize());\n            XrdOucString stringOpaque = origResponse.c_str();\n\n            while (stringOpaque.replace(\"?\", \"&\")) {\n            }\n\n            while (stringOpaque.replace(\"&&\", \"&\")) {\n            }\n\n            XrdOucEnv* openOpaque = new XrdOucEnv(stringOpaque.c_str());\n            char* opaque_info = (char*) strstr(origResponse.c_str(), \"&mgm.logid\");\n\n            if (opaque_info == nullptr) {\n              fprintf(stderr, \"error: failed to parse opaque information from \"\n                      \"PIO request.\\n\");\n              exit(-EINVAL);\n            }\n\n            opaqueInfo = opaque_info;\n\n            //..................................................................\n            // Now that parallel IO is possible, we add the new stripes to the\n            // src_location vector, we update the number of source files and then\n            // we can use the RAID-like access mode where the stripe files are\n            // given as input to the command line\n            //...................................................................\n            if (opaque_info) {\n              layout_src = openOpaque->GetInt(\"mgm.lid\");\n              std::string orig_file = file_path;\n\n              if (eos::common::LayoutId::GetLayoutType(layout_src) ==\n                  eos::common::LayoutId::kRaidDP) {\n                nsrc = eos::common::LayoutId::GetStripeNumber(layout_src) + 1;\n                nparitystripes = 2;\n                isRaidTransfer = true;\n                isSrcRaid = true;\n                src_location.clear();\n                replicationType = \"raiddp\";\n              } else if (eos::common::LayoutId::IsRain(layout_src)) {\n                nsrc = eos::common::LayoutId::GetStripeNumber(layout_src) + 1;\n                nparitystripes = eos::common::LayoutId::GetRedundancyStripeNumber(layout_src);\n                isRaidTransfer = true;\n                isSrcRaid = true;\n                src_location.clear();\n                replicationType = \"reeds\";\n              } else {\n                nsrc = 1;\n                src_type.push_back(XRD_ACCESS);\n                replicationType = \"replica\";\n              }\n\n              if (replicationType != \"replica\") {\n                int qpos = orig_file.rfind(\"?\");\n\n                if (qpos != STR_NPOS) {\n                  opaqueInfo += \"&\";\n                  opaqueInfo += orig_file.substr(qpos + 1);\n                  file_path.erase(qpos);\n                }\n\n                for (int j = 0; j < nsrc; j++) {\n                  tag = \"pio.\";\n                  tag += j;\n                  stripe_path = (is_roots ? \"roots://\" : \"root://\");\n                  stripe_path += openOpaque->Get(tag.c_str());\n                  stripe_path += \"/\";\n                  stripe_path += orig_file.c_str();\n                  int pos = stripe_path.rfind(\"//\");\n\n                  if (pos == STR_NPOS) {\n                    address = \"\";\n                    file_path = stripe_path.c_str();\n                  } else {\n                    address = std::string(stripe_path.c_str(), 0, pos + 1);\n                    file_path = std::string(stripe_path.c_str(), pos + 1,\n                                            stripe_path.length() - pos - 1);\n                  }\n\n                  // remove the ?xyz from the individual source URL\n                  int qpos = file_path.rfind(\"?\");\n\n                  if (qpos != STR_NPOS) {\n                    file_path.erase(qpos);\n                  }\n\n                  src_location.push_back(std::make_pair(address, file_path));\n                  src_type.push_back(RAID_ACCESS);\n\n                  if (verbose || debug) {\n                    fprintf(stdout, \"[eoscp] src<%d>=%s [%s]\\n\", j,\n                            src_location.back().second.c_str(), src_location.back().first.c_str());\n                  }\n                }\n              } else {\n                //.....................................................................\n                // The file is not suitable for PIO access, do normal XRD access\n                //.....................................................................\n                src_type.push_back(XRD_ACCESS);\n\n                if (verbose || debug) {\n                  fprintf(stdout, \"[eoscp] doing standard access...\\n\");\n                }\n              }\n            } else {\n              fprintf(stderr,\n                      \"Error while parsing the opaque information from PIO request.\\n\");\n              exit(-1);\n            }\n\n            delete openOpaque;\n            delete response;\n            break;\n          } else {\n            //.....................................................................\n            // The file is not suitable for PIO access, do normal XRD access\n            //.....................................................................\n            src_type.push_back(XRD_ACCESS);\n          }\n\n          delete response;\n        } else {\n          //.....................................................................\n          // Recovering a file in place or forcing recovery can not be done in\n          // PIO mode, do normal XRD access (RAIN in gateway mode)\n          //.....................................................................\n          src_type.push_back(XRD_ACCESS);\n        }\n      }\n    } else if (src_location[i].second == \"-\") {\n      src_type.push_back(CONSOLE_ACCESS);\n\n      if (i > 0) {\n        fprintf(stderr, \"error: you cannot read with several sources from stdin\\n\");\n        exit(-EPERM);\n      }\n    } else if (src_location[i].first.find(\":/\") != std::string::npos) {\n      src_type.push_back(RIO_ACCESS);\n    } else {\n      src_type.push_back(LOCAL_ACCESS);\n    }\n  }\n\n  //............................................................................\n  // Get destinations access type\n  //............................................................................\n  for (int i = 0; i < ndst; i++) {\n    bool is_root = (dst_location[i].first.find(\"root://\") == 0);\n    bool is_roots = (dst_location[i].first.find(\"roots://\") == 0);\n\n    if (is_root || is_roots) {\n      if (isRaidTransfer && !isSrcRaid) {\n        dst_type.push_back(RAID_ACCESS);\n      } else {\n        // Here we rely on the fact that all destinations must be of the same type\n        dst_type.push_back(XRD_ACCESS);\n      }\n    } else if (dst_location[i].second == \"-\") {\n      dst_type.push_back(CONSOLE_ACCESS);\n    } else if (dst_location[i].first.find(\":/\") != std::string::npos) {\n      dst_type.push_back(RIO_ACCESS);\n    } else {\n      dst_type.push_back(LOCAL_ACCESS);\n    }\n\n    //..........................................................................\n    // Print the types of protocols involved\n    //..........................................................................\n    if (verbose || debug) {\n      fprintf(stdout, \"[eoscp]: copy protocol \");\n\n      for (int j = 0; j < nsrc; j++) {\n        fprintf(stdout, \"%s:\", protocols[src_type[j]]);\n      }\n\n      fprintf(stdout, \"=>\");\n\n      for (int j = 0; j < ndst; j++) {\n        fprintf(stdout, \"%s:\", protocols[dst_type[j]]);\n      }\n\n      fprintf(stdout, \"\\n\");\n    }\n  }\n\n  if (preserve &&\n      ((src_type.back() != LOCAL_ACCESS) || (dst_type.back() != XRD_ACCESS))) {\n    fprintf(stderr, \"error: When using --preserve, the source file should \"\n            \"be a local file and the destination file should be an xrootd \"\n            \"file.\\n\");\n    exit(-EINVAL);\n  }\n\n  if (cksumcomparison) {\n    size_t dst_type_sz = dst_type.size();\n\n    if (dst_type_sz > 1) {\n      fprintf(stderr,\n              \"error: too many destination provided. Checksum comparison between source and destination cannot be enabled.\\n\");\n      exit(-EINVAL);\n    }\n\n    if (dst_type_sz == 1 && dst_type[0] != XRD_ACCESS) {\n      fprintf(stderr,\n              \"error: source and checksum comparison (-C) only allowed for destination using root protocol.\\n\");\n      exit(-EINVAL);\n    }\n  }\n\n  if (verbose || debug) {\n    fprintf(stderr, \"\\n\");\n  }\n\n  if (egid >= 0) {\n    if (setgid(egid)) {\n      fprintf(stdout, \"error: cannot change identity to gid %d\\n\", egid);\n      exit(-EPERM);\n    }\n  }\n\n  if (euid >= 0) {\n    if (setuid(euid)) {\n      fprintf(stdout, \"error: cannot change identity to uid %d\\n\", euid);\n      exit(-EPERM);\n    }\n  }\n\n  //............................................................................\n  // Start the performance measurement\n  //............................................................................\n  gettimeofday(&abs_start_time, &tz);\n  bool got_rain_flags = false;\n  int raid_url_failed_count = 0;\n\n  if (!replicamode) {\n    for (int i = 0; i < nsrc; i++) {\n      // stat the source\n      switch (src_type[i]) {\n      case LOCAL_ACCESS: {\n        if (debug) {\n          fprintf(stdout, \"[eoscp]: doing POSIX stat on %s\\n\",\n                  src_location[i].second.c_str());\n        }\n\n        stat_failed = lstat(src_location[i].second.c_str(), &st[i]);\n      }\n      break;\n\n      // TODO: Improve stat call for RAID_ACCESS\n      //         - should stat the FST file physical path\n      //         - stat_failed should affect even RAID access\n      //       Possible merge with XRD_ACCESS stat call\n      case RAID_ACCESS:\n        if (!got_rain_flags) {\n          XrdCl::URL url(src_location[i].first);\n\n          if (!url.IsValid()) {\n            fprintf(stderr, \"warn: the url address is not valid url=%s\\n\",\n                    src_location[i].first.c_str());\n            raid_url_failed_count++;\n            continue;\n          }\n\n          XrdCl::FileSystem fs(url);\n          XrdCl::StatInfo* response = 0;\n          status = fs.Stat(src_location[i].second, response);\n\n          if (!status.IsOK()) {\n            stat_failed = 1;\n          } else {\n            stat_failed = 0;\n            st[i].st_size = response->GetSize();\n            st[i].st_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;\n\n            if (response->TestFlags(XrdCl::StatInfo::IsWritable)) {\n              st[i].st_mode |= S_IWGRP;\n            }\n\n            got_rain_flags = true;\n          }\n\n          if (got_rain_flags) {\n            for (int j = 0; j < nsrc; j++) {\n              if (j != i) {\n                st[j].st_size = st[i].st_size;\n                st[j].st_mode = st[i].st_mode;\n              }\n            }\n          }\n        }\n\n        break;\n\n      case XRD_ACCESS: {\n        if (debug) {\n          fprintf(stdout, \"[eoscp]: doing XROOT/RAIDIO stat on %s\\n\",\n                  src_location[i].second.c_str());\n        }\n\n        XrdCl::URL url(src_location[i].first);\n\n        if (!url.IsValid()) {\n          fprintf(stderr, \"error: the url address is not valid url=%s\\n\",\n                  src_location[i].first.c_str());\n          exit(-EPERM);\n        }\n\n        XrdCl::FileSystem fs(url);\n        XrdCl::StatInfo* response = 0;\n        status = fs.Stat(src_location[i].second, response);\n\n        if (!status.IsOK()) {\n          stat_failed = 1;\n        } else {\n          stat_failed = 0;\n          st[i].st_size = response->GetSize();\n          st[i].st_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;\n\n          if (response->TestFlags(XrdCl::StatInfo::IsWritable)) {\n            st[i].st_mode |= S_IWGRP;\n          }\n        }\n\n        delete response;\n      }\n      break;\n\n      case CONSOLE_ACCESS:\n        stat_failed = 0;\n        break;\n\n      case RIO_ACCESS:\n        stat_failed = 0;\n        break;\n      }\n\n      if (!isRaidTransfer && stat_failed) {\n        fprintf(stderr, \"error: cannot stat source %s[%s]\\n\",\n                src_location[i].first.c_str(), src_location[i].second.c_str());\n        exit(-ENOENT);\n      }\n    }\n  }\n\n  //............................................................................\n  // Start consistency check\n  //............................................................................\n  if ((isRaidTransfer) && (raid_url_failed_count > nparitystripes)) {\n    fprintf(stderr,\n            \"error: not enough replicas for XROOT(RAIDIO) read\");\n    exit(-EINVAL);\n  }\n\n  if ((!isRaidTransfer) && (nsrc > 1)) {\n    for (int i = 1; i < nsrc; i++) {\n      if (st[0].st_size != st[i].st_size) {\n        fprintf(stderr, \"error: source files differ in size !\\n\");\n        exit(-EINVAL);\n      }\n    }\n  }\n\n  //............................................................................\n  // Check if this is a range link\n  //............................................................................\n  if (!replicamode) {\n    for (int i = 0; i < nsrc; i++) {\n      if (S_ISLNK(st[i].st_mode)) {\n        int readlink_size = 0;\n        char* readlinkbuff = (char*) malloc(4096);\n\n        if (!readlinkbuff) {\n          fprintf(stderr, \"error: cannot allocate link buffer\\n\");\n          exit(-ENOMEM);\n        }\n\n        readlinkbuff[0] = 0;\n\n        switch (src_type[i]) {\n        case LOCAL_ACCESS:\n          if (debug) {\n            fprintf(stdout, \"[eoscp]: doing POSIX readlink on %s\\n\",\n                    src_location[i].second.c_str());\n          }\n\n          readlink_size = readlink(src_location[i].second.c_str(), readlinkbuff, 4096);\n          break;\n\n        case RAID_ACCESS:\n        case XRD_ACCESS:\n        case RIO_ACCESS:\n          readlink_size = 1;\n          break;\n\n        case CONSOLE_ACCESS:\n          readlink_size = 0;\n          break;\n        }\n\n        if (readlink_size < 0) {\n          fprintf(stderr, \"error: cannot read the link of %s\\n\",\n                  src_location[i].second.c_str());\n          exit(-errno);\n        }\n\n        char* space = strchr(readlinkbuff, ' ');\n\n        if (space) {\n          *space = 0;\n          char* colon = strchr(space + 1, ':');\n\n          if (colon) {\n            *colon = 0;\n            // yep, this is a range link\n            startbyte = strtoll(space + 1, 0, 0);\n            stopbyte = strtoll(colon + 1, 0, 0);\n            src_location[i] = std::make_pair(\"\", readlinkbuff);\n\n            if (debug) {\n              fprintf(stdout, \"[eoscp]: setting range to destination %s %lld:%lld\\n\",\n                      src_location[i].second.c_str(), startbyte, stopbyte);\n            }\n          }\n        }\n      }\n    }\n  }\n\n  //............................................................................\n  // If transparent staging is not enabled, we need to check if files are online\n  //............................................................................\n  if (!transparentstaging) {\n    for (int i = 0; i < nsrc; i++) {\n      switch (src_type[i]) {\n      case LOCAL_ACCESS:\n        if (debug) {\n          fprintf(stdout,\n                  \"[eoscp]: POSIX is transparent for staging - nothing to check\\n\");\n        }\n\n        break;\n\n      case RAID_ACCESS:\n        if (debug) {\n          fprintf(stdout,\n                  \"[eoscp]: XROOT(RAIDIO) is transparent for staging - nothing to check\\n\");\n        }\n\n        break;\n\n      case XRD_ACCESS:\n        if (debug) {\n          fprintf(stdout,\n                  \"[eoscp]: XROOT is transparent for staging - nothing to check\\n\");\n        }\n\n        break;\n\n      case RIO_ACCESS:\n        if (debug) {\n          fprintf(stdout, \"[eoscp]: RIO is transparent for staging - nothing to check\\n\");\n        }\n\n        break;\n\n      case CONSOLE_ACCESS:\n        if (debug) {\n          fprintf(stdout,\n                  \"[eoscp]: STDIN is transparent for staging - nothing to check\\n\");\n        }\n\n        break;\n      }\n    }\n  }\n\n  //............................................................................\n  // For the '-p' flag we create the needed destination directory tree\n  //............................................................................\n  struct stat dstst[MAXSRCDST];\n\n  if ((!replicamode) && createdir) {\n    mode_t mode = S_IRWXU | S_IRGRP | S_IROTH | S_IXGRP | S_IXOTH;\n\n    //..........................................................................\n    // Loop over the destination paths\n    //..........................................................................\n    for (int i = 0; i < ndst; i++) {\n      int pos = 0;\n      int mkdir_failed = 0;\n      int chown_failed = 0;\n      XrdOucString file_path = dst_location[i].second.c_str();\n      XrdOucString opaque = dst_location[i].second.c_str();\n      int npos;\n\n      if ((npos = opaque.find(\"?\")) != STR_NPOS) {\n        opaque.erase(0, npos);\n      } else {\n        opaque = \"\";\n      }\n\n      while ((pos = file_path.find(\"/\", pos + 1)) != STR_NPOS) {\n        XrdOucString subpath = file_path;\n        subpath.erase(pos);\n\n        switch (dst_type[i]) {\n        case LOCAL_ACCESS: {\n          if (debug) {\n            fprintf(stdout, \"[eoscp]: doing POSIX stat on %s\\n\", subpath.c_str());\n          }\n\n          stat_failed = stat(const_cast<char*>(subpath.c_str()), &dstst[i]);\n\n          if (stat_failed) {\n            if (debug) {\n              fprintf(stdout, \"[eoscp]: doing POSIX mkdir on %s\\n\", subpath.c_str());\n            }\n\n            mkdir_failed = mkdir(const_cast<char*>(subpath.c_str()), mode);\n\n            //..................................................................\n            // The root user can also set the user/group as in the target location\n            //..................................................................\n            if (getuid() == 0) {\n              if (!subpath.beginswith(\"/dev/\")) {\n                chown_failed = chown(const_cast<char*>(subpath.c_str()), st[0].st_uid,\n                                     st[0].st_gid);\n              }\n            }\n          }\n        }\n        break;\n\n        case RAID_ACCESS:\n        case XRD_ACCESS: {\n          if (debug) {\n            fprintf(stdout, \"[eoscp]: doing XROOT(RAIDIO) stat on %s\\n\", subpath.c_str());\n          }\n\n          subpath += opaque.c_str();\n          XrdCl::URL url(dst_location[i].first.c_str());\n          XrdCl::FileSystem fs(url);\n          XrdCl::StatInfo* response = 0;\n          status = fs.Stat(subpath.c_str(), response);\n\n          if (!status.IsOK()) {\n            if (debug) {\n              fprintf(stdout, \"[eoscp]: doing XROOT mkdir on %s\\n\", subpath.c_str());\n            }\n\n            status = fs.MkDir(subpath.c_str(), XrdCl::MkDirFlags::None,\n                              (XrdCl::Access::Mode)mode);\n\n            if (!status.IsOK()) {\n              mkdir_failed = 1;\n            }\n          }\n\n          delete response;\n          // Chown not supported by the standard xroot\n        }\n        break;\n\n        case RIO_ACCESS:\n          break;\n\n        case CONSOLE_ACCESS:\n          break;\n        }\n\n        if (mkdir_failed) {\n          std::string errmsg = (status.IsOK()) ?\n                               (\"cannot create destination sub-directory \" + subpath).c_str()\n                               : status.GetErrorMessage();\n          fprintf(stderr, \"error: %s\\n\", errmsg.c_str());\n          exit(-EPERM);\n        }\n\n        if (chown_failed) {\n          fprintf(stderr, \"error: cannot set owner=%d/group=%d for %s\\n\",\n                  st[i].st_uid, st[i].st_gid, subpath.c_str());\n          exit(-EPERM);\n        }\n      }\n    }\n  }\n\n  //............................................................................\n  // Open source files\n  //............................................................................\n  for (int i = 0; i < nsrc; i++) {\n    switch (src_type[i]) {\n    case LOCAL_ACCESS: {\n      if (debug) {\n        fprintf(stdout, \"[eoscp]: doing POSIX open to read  %s\\n\",\n                src_location[i].second.c_str());\n      }\n\n      src_handler.push_back(std::make_pair(open(src_location[i].second.c_str(),\n                                           O_RDONLY),\n                                           static_cast<XrdCl::File*>(NULL)));\n    }\n    break;\n\n    case RAID_ACCESS: {\n      if (isSrcRaid) {\n        int flags;\n        mode_t mode_sfs = 0;\n        std::vector<std::string> vectUrl;\n\n        if (doStoreRecovery) {\n          flags = SFS_O_RDWR;\n        } else {\n          flags = SFS_O_RDONLY;\n        }\n\n        for (int j = 0; j < nsrc; j++) {\n          location = src_location[j].first + src_location[j].second;\n          vectUrl.push_back(location);\n        }\n\n        if (replicationType == \"raiddp\") {\n          redundancyObj = new eos::fst::RaidDpLayout(NULL, layout_src, NULL,\n              NULL, location.c_str(), 0, doStoreRecovery);\n        } else if (replicationType == \"reeds\") {\n          try {\n            redundancyObj = new eos::fst::ReedSLayout(\n                NULL, layout_src, NULL, NULL, location.c_str(), 0, doStoreRecovery);\n          } catch (const std::runtime_error& e) {\n            redundancyObj = nullptr;\n          }\n        }\n\n        if (debug) {\n          fprintf(stdout, \"[eoscp]: doing XROOT(RAID-PIO) open with flags: %x\\n\", flags);\n        }\n\n        if (!redundancyObj) {\n          fprintf(stderr, \"error: failed to create RAID object for read/write\\n\");\n          exit(-EINVAL);\n        }\n\n        if (redundancyObj->OpenPio(vectUrl, flags, mode_sfs, opaqueInfo.c_str())) {\n          fprintf(stderr, \"error: can not open RAID object for read/write\\n\");\n          exit(-EIO);\n        }\n      }\n    }\n    break;\n\n    case XRD_ACCESS: {\n      if (debug) {\n        fprintf(stdout, \"[eoscp]: doing XROOT open to read  %s\\n\",\n                src_location[i].second.c_str());\n      }\n\n      location = src_location[i].first + src_location[i].second;\n      XrdCl::OpenFlags::Flags xrdcl_flags = XrdCl::OpenFlags::Read;\n      XrdCl::Access::Mode xrdcl_mode = XrdCl::Access::UR |  XrdCl::Access::UW |\n                                       XrdCl::Access::GR | XrdCl::Access::OR;\n\n      if (doStoreRecovery) {\n        xrdcl_flags = XrdCl::OpenFlags::Update;\n\n        if ((location.find(\"?\") == std::string::npos)) {\n          location += \"?eos.rain.store=1\";\n        } else {\n          location += \"&eos.rain.store=1\";\n        }\n      }\n\n      if (getenv(\"EOS_FUSE_SECRET\")) {\n        if ((location.find(\"?\") == std::string::npos)) {\n          location += \"?eos.key=\";\n        } else {\n          location += \"&eos.key=\";\n        }\n\n        location += getenv(\"EOS_FUSE_SECRET\");\n      }\n\n      XrdCl::File* file = new XrdCl::File();\n      status = file->Open(location, xrdcl_flags, xrdcl_mode);\n\n      if (!status.IsOK()) {\n        std::string errmsg;\n        errmsg = status.GetErrorMessage();\n        fprintf(stderr, \"error: %s\\n\", status.ToStr().c_str());\n        exit(-status.errNo ? -status.errNo : -EIO);\n      } else {\n        file->GetProperty(\"LastURL\", src_lasturl);\n      }\n\n      src_handler.push_back(std::make_pair(0, (void*)file));\n    }\n    break;\n\n    case RIO_ACCESS: {\n      if (debug) {\n        fprintf(stdout, \"[eoscp]: doing RIO open to read  %s\\n\",\n                src_location[i].second.c_str());\n      }\n\n      location = src_location[i].first + src_location[i].second;\n\n      if (location.substr(0, 3) == \"xrd\") {\n        location.replace(0, 3, \"root\");\n      }\n\n      eos::fst::FileIo* file = eos::fst::FileIoPluginHelper::GetIoObject(\n                                 location.c_str());\n\n      if (!file) {\n        fprintf(stderr, \"error: failed to get IO object for %s\\n\", location.c_str());\n        exit(-1);\n      }\n\n      retc = file->fileOpen(0);\n\n      if (retc) {\n        eos::common::error_retc_map(file->GetLastErrNo());\n        fprintf(stderr, \"error: source file open failed - errno=%d : %s [%s]\\n\", errno,\n                strerror(errno),\n                file->GetLastErrMsg().c_str());\n        exit(-errno);\n      } else {\n        src_lasturl = file->GetLastUrl();\n      }\n\n      src_handler.push_back(std::make_pair(0, (void*)file));\n    }\n    break;\n\n    case CONSOLE_ACCESS:\n      src_handler.push_back(std::make_pair(fileno(stdin),\n                                           static_cast<XrdCl::File*>(NULL)));\n      break;\n    }\n\n    if ((!isRaidTransfer) &&\n        (src_handler[i].first < 0) &&\n        (src_handler[i].second == NULL)) {\n      std::string errmsg;\n      errmsg = status.GetErrorMessage();\n      fprintf(stderr, \"error: %s\\n\", status.ToStr().c_str());\n      exit(-status.errNo ? -status.errNo : -EIO);\n    }\n\n    if (isRaidTransfer && isSrcRaid) {\n      break;\n    }\n  }\n\n  //............................................................................\n  // Seek the required start position\n  //............................................................................\n  if (startbyte > 0) {\n    for (int i = 0; i < nsrc; i++) {\n      if (debug) {\n        fprintf(stdout, \"[eoscp]: seeking in %s to position %lld\\n\",\n                src_location[i].second.c_str(), startbyte);\n      }\n\n      switch (src_type[i]) {\n      case LOCAL_ACCESS: {\n        startbyte = lseek(src_handler[i].first, startbyte, SEEK_SET);\n        offsetXS = startbyte;\n      }\n      break;\n\n      case RAID_ACCESS: {\n        offsetXrd = startbyte;\n        offsetXS = startbyte;\n      }\n      break;\n\n      case XRD_ACCESS: {\n        //TODO::\n        //startbyte = XrdPosixXrootd::Lseek( srcfd[i], startbyte, SEEK_SET );\n        offsetXS = startbyte;\n      }\n      break;\n\n      case RIO_ACCESS:\n        offsetXrd = startbyte;\n        offsetXS = startbyte;\n        break;\n\n      case CONSOLE_ACCESS:\n        break;\n      }\n\n      if (startbyte < 0) {\n        fprintf(stderr, \"error: cannot seek start position of file %s %d\\n\",\n                src_location[i].second.c_str(), errno);\n        exit(-EIO);\n      }\n    }\n  }\n\n  //............................................................................\n  // Open destination files\n  //............................................................................\n  for (int i = 0; i < ndst; i++) {\n    retc = 0;\n\n    switch (dst_type[i]) {\n    case LOCAL_ACCESS: {\n      if (debug) {\n        fprintf(stdout, \"[eoscp]: doing POSIX open to write  %s\\n\",\n                dst_location[i].second.c_str());\n      }\n\n      if (nooverwrite) {\n        struct stat buf;\n\n        if (!stat(dst_location[i].second.c_str(), &buf)) {\n          fprintf(stderr, \"error: target file exists already!\\n\");\n          exit(-EEXIST);\n        }\n      }\n\n      if (appendmode) {\n        dst_handler.push_back(std::make_pair(open(dst_location[i].second.c_str(),\n                                             O_WRONLY | O_CREAT, st[i].st_mode),\n                                             static_cast<eos::fst::XrdIo*>(NULL)));\n      } else {\n        dst_handler.push_back(std::make_pair(open(dst_location[i].second.c_str(),\n                                             O_WRONLY | O_TRUNC | O_CREAT, st[i].st_mode),\n                                             static_cast<eos::fst::XrdIo*>(NULL)));\n      }\n    }\n    break;\n\n    case RAID_ACCESS: {\n      if (!isSrcRaid) {\n        int flags;\n        std::vector<std::string> vectUrl;\n        flags = SFS_O_CREAT | SFS_O_WRONLY;\n\n        for (int j = 0; j < ndst; j++) {\n          location = dst_location[j].first + dst_location[j].second;\n          vectUrl.push_back(location);\n        }\n\n        LayoutId::layoutid_t layout_dst = 0;\n\n        if (replicationType == \"raiddp\") {\n          layout_dst = LayoutId::GetId(LayoutId::kRaidDP,\n                                       1, ndst,\n                                       LayoutId::BlockSizeEnum(stripeWidth),\n                                       LayoutId::OssXsBlockSize,\n                                       0, nparitystripes);\n          redundancyObj = new eos::fst::RaidDpLayout(NULL, layout_dst, NULL, NULL,\n              location.c_str(), NULL, 0, doStoreRecovery, isStreamFile);\n        } else if (replicationType == \"reeds\") {\n          layout_dst = LayoutId::GetId(LayoutId::GetReedSLayoutByParity(nparitystripes),\n                                       1, ndst,\n                                       LayoutId::BlockSizeEnum(stripeWidth),\n                                       LayoutId::OssXsBlockSize,\n                                       0, nparitystripes);\n          try {\n            redundancyObj =\n                new eos::fst::ReedSLayout(NULL, layout_dst, NULL, NULL, location.c_str(),\n                                          NULL, 0, doStoreRecovery, isStreamFile);\n          } catch (const std::runtime_error& e) {\n            redundancyObj = nullptr;\n          }\n        }\n\n        if (debug) {\n          fprintf(stdout, \"[eoscp]: doing XROOT(RAIDIO-PIO) open with flags: %x\\n\",\n                  flags);\n        }\n\n        if (!redundancyObj) {\n          fprintf(stderr, \"error: failed to create RAID object for read/write\\n\");\n          exit(-EINVAL);\n        }\n\n        if (redundancyObj->OpenPio(vectUrl, flags)) {\n          fprintf(stderr, \"error: can not open RAID object for write\\n\");\n          exit(-EIO);\n        }\n      }\n    }\n    break;\n\n    case XRD_ACCESS: {\n      if (debug) {\n        fprintf(stdout, \"[eoscp]: doing XROOT open to write  %s\\n\",\n                dst_location[i].second.c_str());\n      }\n\n      location = dst_location[i].first + dst_location[i].second;\n      bool hasOpaque = (location.find(\"?\") != std::string::npos);\n      std::stringstream opaque;\n\n      if (getenv(\"EOS_FUSE_SECRET\")) {\n        opaque << (hasOpaque ? \"&\" : \"?\")\n               << \"eos.key=\" << getenv(\"EOS_FUSE_SECRET\");\n        hasOpaque = true;\n      }\n\n      if (preserve) {\n        opaque << (hasOpaque ? \"&\" : \"?\") << \"eos.ctime=\"\n#ifdef __LINUX__\n               << std::to_string(st[i].st_ctim.tv_sec) << \".\"\n               << std::to_string(st[i].st_ctim.tv_nsec)\n#else\n               << std::to_string(st[i].st_ctimespec.tv_sec) << \".\"\n               << std::to_string(st[i].st_ctimespec.tv_nsec)\n#endif\n               << \"&eos.mtime=\"\n#ifdef __LINUX__\n               << std::to_string(st[i].st_mtim.tv_sec) << \".\"\n               << std::to_string(st[i].st_mtim.tv_nsec);\n#else\n               << std::to_string(st[i].st_mtimespec.tv_sec) << \".\"\n               << std::to_string(st[i].st_mtimespec.tv_nsec);\n#endif\n\thasOpaque = true;\n      }\n\n      location += opaque.str();\n      eos::fst::XrdIo* file = new eos::fst::XrdIo(location.c_str(), false);\n\n      if (appendmode || nooverwrite) {\n        XrdCl::URL url(dst_location[i].first);\n\n        if (!url.IsValid()) {\n          fprintf(stderr, \"error: the destination url address is not valid url=%s\\n\",\n                  dst_location[i].first.c_str());\n          exit(-EPERM);\n        }\n\n        XrdCl::FileSystem fs(url);\n        XrdCl::StatInfo* response = 0;\n        status = fs.Stat(dst_location[i].second, response);\n\n        if (status.IsOK()) {\n          if (nooverwrite) {\n            fprintf(stderr, \"error: target file exists already!\\n\");\n            exit(-EEXIST);\n          }\n\n          retc = file->fileOpen(SFS_O_RDWR, st[i].st_mode, \"\");\n        } else {\n          retc = file->fileOpen(SFS_O_CREAT | SFS_O_RDWR,\n                                S_IRUSR | S_IWUSR | S_IRGRP, \"\");\n        }\n\n        if (!startwritebyte && response) {\n          startwritebyte = response->GetSize();\n        }\n\n        delete response;\n      } else {\n        retc = file->fileOpen(SFS_O_CREAT | SFS_O_RDWR,\n                              S_IRUSR | S_IWUSR | S_IRGRP, \"\");\n      }\n\n      if (retc) {\n        eos::common::error_retc_map(file->GetLastErrNo());\n        fprintf(stderr, \"error: target file open failed - errno=%d : %s [%s]\\n\",\n                errno, strerror(errno),\n                file->GetLastErrMsg().c_str());\n        exit(-errno);\n      } else {\n        dst_lasturl = file->GetLastUrl();\n      }\n\n      dst_handler.push_back(std::make_pair(0, file));\n    }\n    break;\n\n    case RIO_ACCESS: {\n      if (debug) {\n        fprintf(stdout, \"[eoscp]: doing open to write  %s\\n\",\n                dst_location[i].second.c_str());\n      }\n\n      location = dst_location[i].first + dst_location[i].second;\n\n      if (location.substr(0, 3) == \"xrd\") {\n        location.replace(0, 3, \"root\");\n      }\n\n      eos::fst::FileIo* file = eos::fst::FileIoPluginHelper::GetIoObject(\n                                 location.c_str());\n      location = src_location[i].first + src_location[i].second;\n\n      if (!file->fileExists()) {\n        if (nooverwrite) {\n          fprintf(stderr, \" error; target file exists already!\\n\");\n          exit(-EEXIST);\n        }\n\n        retc = file->fileOpen(SFS_O_RDWR, st[i].st_mode, \"\");\n      } else {\n        retc = file->fileOpen(SFS_O_CREAT | SFS_O_RDWR,\n                              st[i].st_mode, \"\");\n      }\n\n      if (retc) {\n        eos::common::error_retc_map(file->GetLastErrNo());\n        fprintf(stderr, \"error: target file open failed - errno=%d : %s\\n\", errno,\n                strerror(errno));\n        exit(-errno);\n      } else {\n        dst_lasturl = file->GetLastUrl();\n      }\n\n      dst_handler.push_back(std::make_pair(0, file));\n    }\n    break;\n\n    case CONSOLE_ACCESS:\n      dst_handler.push_back(std::make_pair(fileno(stdout),\n                                           static_cast<eos::fst::XrdIo*>(NULL)));\n      break;\n    }\n\n    if ((!isRaidTransfer) &&\n        (dst_handler[i].first <= 0) &&\n        (dst_handler[i].second == NULL)) {\n      std::string errmsg;\n      errmsg = status.GetErrorMessage();\n\n      if (status.errNo) {\n        fprintf(stderr, \"error: errc=%d msg=\\\"%s\\\"\\n\", status.errNo, errmsg.c_str());\n      } else {\n        fprintf(stderr, \"error: errc=%d msg=\\\"%s\\\"\\n\", errno ? errno : EINVAL,\n                strerror(errno ? errno : EINVAL));\n      }\n\n      exit(-status.errNo ? -status.errNo : -1);\n    }\n\n    if (isRaidTransfer && !isSrcRaid) {\n      break;\n    }\n  }\n\n  //............................................................................\n  // In case the file exists, seek the end and print the offset\n  //............................................................................\n  if (appendmode) {\n    for (int i = 0; i < ndst; i++) {\n      switch (dst_type[i]) {\n      case LOCAL_ACCESS:\n        startwritebyte = lseek(dst_handler[i].first, 0, SEEK_END);\n        break;\n\n      case RAID_ACCESS:\n        // Not supported\n        break;\n\n      case XRD_ACCESS:\n        break;\n\n      case RIO_ACCESS:\n        break;\n\n      case CONSOLE_ACCESS:\n        // Not supported\n        break;\n      }\n\n      if (startwritebyte < 0) {\n        fprintf(stderr, \"error: cannot seek from end to beginning of file %s\\n\",\n                dst_location[i].second.c_str());\n        exit(-EIO);\n      }\n    }\n  }\n\n  //............................................................................\n  // Set the source mode or a specified one for the destination\n  //............................................................................\n  for (int i = 0; i < ndst; i++) {\n    int chmod_failed = 0;\n    int chown_failed = 0;\n\n    switch (dst_type[i]) {\n    case LOCAL_ACCESS: {\n      if (!set_mode) {\n        //........................................................................\n        // If not specified on the command line, take the source mode\n        //........................................................................\n        if (S_ISREG(dstst[i].st_mode)) {\n          // only for files !\n          dest_mode[i] = st[0].st_mode;\n        }\n      }\n\n      if ((S_ISREG(dstst[i].st_mode) &&\n           (dst_location[i].second.substr(0, 5) != \"/dev/\"))) {\n        chmod_failed = chmod(dst_location[i].second.c_str(), dest_mode[i]);\n\n        if (getuid() == 0) {\n          chown_failed = chown(dst_location[i].second.c_str(), st[0].st_uid,\n                               st[0].st_gid);\n        }\n      }\n    }\n    break;\n\n    case RAID_ACCESS:\n    case XRD_ACCESS:\n    case RIO_ACCESS:\n    case CONSOLE_ACCESS:\n      //........................................................................\n      // Not supported, no such functionality in the standard xroot or console\n      //........................................................................\n      break;\n    }\n\n    if (chmod_failed) {\n      fprintf(stderr, \"error: cannot set permissions to %d for file %s\\n\",\n              dest_mode[i], dst_location[i].second.c_str());\n      exit(-EPERM);\n    }\n\n    if (chown_failed) {\n      fprintf(stderr, \"error: cannot set owner=%d/group=%d for %s\\n\",\n              st[i].st_uid, st[i].st_gid, dst_location[i].second.c_str());\n      exit(-EPERM);\n    }\n  }\n\n  //............................................................................\n  // Do the actual copy operation\n  //............................................................................\n  char* ptr_buffer = buffer;\n  long long totalbytes = 0;\n  double wait_time = 0;\n  struct timespec start, end;\n  stopwritebyte = startwritebyte;\n\n  while (1) {\n    if (progressFile.length()) {\n      write_progress(totalbytes, st[0].st_size);\n    }\n\n    if (progbar) {\n      gettimeofday(&abs_stop_time, &tz);\n\n      for (int i = 0; i < nsrc; i++) {\n        if ((src_type[i] == XRD_ACCESS) && (targetsize)) {\n          st[i].st_size = targetsize;\n        }\n      }\n\n      print_progbar(totalbytes, st[0].st_size);\n    }\n\n    if (bandwidth) {\n      gettimeofday(&abs_stop_time, &tz);\n      float abs_time = static_cast<float>((abs_stop_time.tv_sec -\n                                           abs_start_time.tv_sec) * 1000 +\n                                          (abs_stop_time.tv_usec - abs_start_time.tv_usec) / 1000);\n      //........................................................................\n      // Regulate the io - sleep as desired\n      //........................................................................\n      float exp_time = totalbytes / bandwidth / 1000.0;\n\n      if (abs_time < exp_time) {\n        usleep((int)(1000 * (exp_time - abs_time)));\n      }\n    }\n\n    //..........................................................................\n    // For ranges we have to adjust the last buffersize\n    //..........................................................................\n    if ((stopbyte >= 0) &&\n        (((stopbyte - startbyte) - totalbytes) < buffersize)) {\n      buffersize = (stopbyte - startbyte) - totalbytes;\n    }\n\n    int nread = -1;\n    auto mReadStart = std::chrono::steady_clock::now();\n\n    switch (src_type[0]) {\n    case LOCAL_ACCESS:\n    case CONSOLE_ACCESS:\n      nread = read(src_handler[0].first,\n                   static_cast<void*>(ptr_buffer),\n                   buffersize);\n      break;\n\n    case RAID_ACCESS: {\n      nread = redundancyObj->Read(offsetXrd, ptr_buffer, buffersize);\n      offsetXrd += nread;\n    }\n    break;\n\n    case XRD_ACCESS: {\n      eos::common::Timing::GetTimeSpec(start);\n      uint32_t xnread = 0;\n      status = static_cast<XrdCl::File*>(src_handler[0].second)->Read(offsetXrd,\n               buffersize, ptr_buffer, xnread);\n      nread = xnread;\n\n      if (!status.IsOK()) {\n        fprintf(stderr, \"Error while doing reading. \\n\");\n        exit(-1);\n      }\n\n      eos::common::Timing::GetTimeSpec(end);\n      wait_time = static_cast<double>((end.tv_sec * 1000 + end.tv_nsec / 1000000) -\n                                      (start.tv_sec * 1000 + start.tv_nsec / 1000000));\n      read_wait += wait_time;\n      offsetXrd += nread;\n\n      if (debug) {\n        fprintf(stderr, \"[eoscp] read=%d\\n\", nread);\n      }\n    }\n    break;\n\n    case RIO_ACCESS: {\n      eos::common::Timing::GetTimeSpec(start);\n      int64_t nread64;\n      nread64 = static_cast<eos::fst::FileIo*>(src_handler[0].second)->fileRead(\n                  offsetXrd, ptr_buffer, buffersize);\n\n      if (nread64 < 0) {\n        nread = -1;\n      } else {\n        nread = (int) nread64;\n      }\n\n      eos::common::Timing::GetTimeSpec(end);\n      wait_time = static_cast<double>((end.tv_sec * 1000 + end.tv_nsec / 1000000) -\n                                      (start.tv_sec * 1000 + start.tv_nsec / 1000000));\n      read_wait += wait_time;\n      offsetXrd += nread;\n\n      if (debug) {\n        fprintf(stderr, \"[eoscp] read=%d\\n\", nread);\n      }\n    }\n    break;\n    }\n\n    auto mReadStop = std::chrono::steady_clock::now();\n    ingress_microseconds += std::chrono::duration_cast<std::chrono::microseconds>\n                            (mReadStop - mReadStart).count();\n\n    if (nread < 0) {\n      fprintf(stderr, \"error: read failed on file %s - destination file \"\n              \"is incomplete!\\n\", src_location[0].second.c_str());\n      exit(-EIO);\n    }\n\n    if (nread == 0) {\n      // end of file\n      break;\n    }\n\n    if (computeXS && xsObj) {\n      xsObj->Add(static_cast<const char*>(ptr_buffer), nread, offsetXS);\n      offsetXS += nread;\n    }\n\n    auto mWriteStart = std::chrono::steady_clock::now();\n    int64_t nwrite = 0;\n\n    for (int i = 0; i < ndst; i++) {\n      switch (dst_type[i]) {\n      case LOCAL_ACCESS:\n      case CONSOLE_ACCESS:\n        nwrite = write(dst_handler[i].first, ptr_buffer, nread);\n        break;\n\n      case RAID_ACCESS: {\n        if (i == 0) {\n          nwrite = redundancyObj->Write(stopwritebyte, ptr_buffer, nread);\n          i = ndst;\n        }\n      }\n      break;\n\n      case XRD_ACCESS: {\n        // Do writes in async mode\n        eos::common::Timing::GetTimeSpec(start);\n        nwrite = static_cast<eos::fst::FileIo*>(dst_handler[i].second)->fileWriteAsync(\n                   stopwritebyte, ptr_buffer, nread);\n        eos::common::Timing::GetTimeSpec(end);\n        wait_time = static_cast<double>((end.tv_sec * 1000 + end.tv_nsec / 1000000) -\n                                        (start.tv_sec * 1000 + start.tv_nsec / 1000000));\n        write_wait += wait_time;\n\n        if (debug) {\n          fprintf(stderr, \"[eoscp] write=%li\\n\", nwrite);\n        }\n      }\n      break;\n\n      case RIO_ACCESS: {\n        eos::common::Timing::GetTimeSpec(start);\n        int64_t nwrite64;\n        nwrite64 = static_cast<eos::fst::FileIo*>(dst_handler[i].second)->fileWrite(\n                     stopwritebyte, ptr_buffer, nread);\n\n        if (nwrite64 < 0) {\n          nwrite = -1;\n        } else {\n          nwrite = (int) nwrite64;\n        }\n\n        eos::common::Timing::GetTimeSpec(end);\n        wait_time = static_cast<double>((end.tv_sec * 1000 + end.tv_nsec / 1000000) -\n                                        (start.tv_sec * 1000 + start.tv_nsec / 1000000));\n        write_wait += wait_time;\n\n        if (debug) {\n          fprintf(stderr, \"[eoscp] write=%li\\n\", nwrite);\n        }\n      }\n      break;\n      }\n\n      if (nwrite != nread) {\n        fprintf(stderr, \"error: write failed on destination file %s - \"\n                \"wrote %lld/%lld bytes - destination file is incomplete!\\n\",\n                dst_location[i].second.c_str(), (long long) nwrite, (long long) nread);\n        exit(-EIO);\n      }\n    }\n\n    auto mWriteStop = std::chrono::steady_clock::now();\n    egress_microseconds += std::chrono::duration_cast<std::chrono::microseconds>\n                           (mWriteStop - mWriteStart).count();\n    totalbytes += nwrite;\n    stopwritebyte += nwrite;\n  } // end while(1)\n\n  // Wait for all async write requests before moving on\n  eos::common::Timing::GetTimeSpec(start);\n  eos::fst::AsyncMetaHandler* ptr_handler = 0;\n  bool write_error = false;\n\n  for (int i = 0; i < ndst; i++) {\n    if (dst_type[i] == XRD_ACCESS) {\n      if (dst_handler[i].second) {\n        ptr_handler = static_cast<eos::fst::AsyncMetaHandler*>(\n                        static_cast<eos::fst::FileIo*>(dst_handler[i].second)->fileGetAsyncHandler());\n\n        if (ptr_handler) {\n          uint16_t error_type = ptr_handler->WaitOK();\n\n          if (error_type != XrdCl::errNone) {\n            fprintf(stderr, \"Error while doing the async writing.\");\n            write_error = true;\n          }\n        }\n      }\n    }\n  }\n\n  eos::common::Timing::GetTimeSpec(end);\n  wait_time = static_cast<double>((end.tv_sec * 1000 + end.tv_nsec / 1000000) -\n                                  (start.tv_sec * 1000 + start.tv_nsec / 1000000));\n  write_wait += wait_time;\n\n  if (computeXS && xsObj) {\n    xsObj->Finalize();\n    xsValue = xsObj->GetHexChecksum();\n  }\n\n  if (progbar) {\n    gettimeofday(&abs_stop_time, &tz);\n\n    for (int i = 0; i < nsrc; i++) {\n      if (src_type[i] == XRD_ACCESS) {\n        st[i].st_size = totalbytes;\n      }\n    }\n\n    print_progbar(totalbytes, st[0].st_size);\n    std::cout << std::endl;\n  }\n\n  auto xferSummary = createXferSummary(src_location, dst_location, totalbytes);\n\n  if (jsonoutput) {\n    print_json_summary(xferSummary);\n  } else {\n    if (summary) {\n      print_summary(xferSummary);\n    }\n  }\n\n  //............................................................................\n  // Close all files\n  //............................................................................\n  for (int i = 0; i < nsrc; i++) {\n    switch (src_type[i]) {\n    case LOCAL_ACCESS:\n      close(src_handler[i].first);\n      break;\n\n    case RAID_ACCESS:\n      if (i == 0) {\n        redundancyObj->Close();\n        i = nsrc;\n        delete redundancyObj;\n      }\n\n      break;\n\n    case XRD_ACCESS:\n      status = static_cast<XrdCl::File*>(src_handler[i].second)->Close();\n\n      if (!status.IsOK()) {\n        fprintf(stderr,\n                \"error: close failed on source - file modified during replication\\n\");\n        exit(-EIO);\n      }\n\n      delete static_cast<XrdCl::File*>(src_handler[i].second);\n      break;\n\n    case RIO_ACCESS:\n      retc = static_cast<eos::fst::FileIo*>(src_handler[i].second)->fileClose();\n\n      if (retc) {\n        fprintf(stderr,\n                \"error: close failed on source - file modified during replication\\n\");\n        exit(-EIO);\n      }\n\n      delete static_cast<eos::fst::FileIo*>(src_handler[i].second);\n      break;\n\n    case CONSOLE_ACCESS:\n      break;\n    }\n  }\n\n  for (int i = 0; i < ndst; i++) {\n    switch (dst_type[i]) {\n    case LOCAL_ACCESS:\n      close(dst_handler[i].first);\n      break;\n\n    case RAID_ACCESS:\n      if (i == 0) {\n        errno = 0;\n        redundancyObj->Close();\n\n        if (errno) {\n          fprintf(stderr, \"error: %s\\n\", redundancyObj->GetLastErrMsg().c_str());\n        }\n\n        i = ndst;\n        delete redundancyObj;\n      }\n\n      break;\n\n    case XRD_ACCESS:\n      retc = static_cast<eos::fst::FileIo*>(dst_handler[i].second)->fileClose();\n\n      if (retc) {\n        fprintf(stderr, \"error: %s\\n\", status.ToStr().c_str());\n        exit(-EIO);\n      }\n\n      delete static_cast<eos::fst::FileIo*>(dst_handler[i].second);\n      break;\n\n    case RIO_ACCESS:\n      retc = static_cast<eos::fst::FileIo*>(dst_handler[i].second)->fileClose();\n\n      if (retc) {\n        fprintf(stderr, \"error: close failed on target\\n\");\n        exit(-EIO);\n      }\n\n      delete static_cast<eos::fst::FileIo*>(dst_handler[i].second);\n      break;\n\n    case CONSOLE_ACCESS:\n      //........................................................................\n      // Nothing to do\n      //........................................................................\n      break;\n    }\n  }\n\n  if (dosymlink) {\n    int symlink_failed = 0;\n    char rangedestname[4096];\n\n    if (appendmode) {\n      sprintf(rangedestname, \"%s %llu:%llu\",\n              dst_location[0].second.c_str(),\n              static_cast<unsigned long long>(startwritebyte),\n              static_cast<unsigned long long>(stopwritebyte));\n    } else {\n      sprintf(rangedestname, \"%s\", dst_location[0].second.c_str());\n    }\n\n    if (debug) {\n      fprintf(stdout, \"[eoscp]: creating symlink %s->%s\\n\", symlinkname,\n              rangedestname);\n    }\n\n    switch (dst_type[0]) {\n    case LOCAL_ACCESS: {\n      unlink(symlinkname);\n      symlink_failed = symlink(rangedestname, symlinkname);\n    }\n    break;\n\n    case RAID_ACCESS:\n    case XRD_ACCESS:\n    case RIO_ACCESS:\n    case CONSOLE_ACCESS:\n      //........................................................................\n      // Noting to do, xrootd has no symlink support in posix\n      //........................................................................\n      break;\n    }\n\n    if (symlink_failed) {\n      fprintf(stderr, \"error: cannot creat symlink from %s -> %s\\n\",\n              symlinkname, rangedestname);\n      exit(-ESPIPE);\n    }\n  }\n\n  if (debug) {\n    fprintf(stderr, \"[eoscp] # Total read wait time     : %f ms  \\n\",\n            read_wait);\n    fprintf(stderr, \"[eoscp] # Total write wait time    : %f ms \\n\",\n            write_wait);\n  }\n\n  if (cksumcomparison) {\n    // The client asked for some checksum comparison between the source and the destination\n    std::string destServer = dst_location[0].first;\n    std::string destFilePath = dst_location[0].second;\n    XrdCl::URL url(destServer);\n    // No need to check the URL consistency as the transfer already happened\n    XrdCl::FileSystem fs(url);\n    CompareCksumResult res = compareChecksum(fs, destFilePath, xsString, xsValue);\n\n    if (res.cksumMismatch) {\n      // Checksum mismatch, print related error\n      fprintf(stderr, \"%s\\n\", res.errMsg.c_str());\n\n      if (cksummismatchdelete) {\n        // The user wants to delete the file if the checksum mismatch between source and destination\n        fprintf(stderr, \"Deleting the file from the destination %s%s\\n\",\n                destServer.c_str(), destFilePath.c_str());\n        status = fs.Rm(destFilePath);\n\n        if (!status.IsOK()) {\n          fprintf(stderr,\n                  \"error while trying to delete the file from the destination (%s): %s\\n\",\n                  destFilePath.c_str(), status.ToStr().c_str());\n          exit(-status.errNo ? -status.errNo : -1);\n        }\n      }\n\n      // Just return the error code set during the checksum checking\n      exit(-res.xrdErrno ? -res.xrdErrno : -1);\n    }\n  }\n\n  // Free memory\n  delete[] buffer;\n\n  if (write_error) {\n    return -EIO;\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "fst/filemd/FmdAttr.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file FmdAttr.hh\n//! @author Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"FmdAttr.hh\"\n#include \"FmdHandler.hh\"\n#include \"fst/io/FileIoPluginCommon.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/utils/FTSWalkTree.hh\"\n#include \"fst/utils/FSPathHandler.hh\"\n#include \"fst/utils/TransformAttr.hh\"\n#include <functional>\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFmdAttrHandler::FmdAttrHandler(std::unique_ptr<FSPathHandler>&& _FSPathHandler)\n  : mFSPathHandler(std::move(_FSPathHandler))\n{}\n\n//------------------------------------------------------------------------------\n// Low level Fmd retrieve method\n//------------------------------------------------------------------------------\nstd::pair<bool, eos::common::FmdHelper>\nFmdAttrHandler::LocalRetrieveFmd(eos::common::FileId::fileid_t fid,\n                                 eos::common::FileSystem::fsid_t fsid,\n                                 const std::string& path)\n{\n  if (path.empty()) {\n    return LocalRetrieveFmd(mFSPathHandler->GetPath(fid, fsid));\n  } else {\n    return LocalRetrieveFmd(path);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Low level Fmd retrieve method by path\n//------------------------------------------------------------------------------\nstd::pair<bool, eos::common::FmdHelper>\nFmdAttrHandler::LocalRetrieveFmd(const std::string& path)\n{\n  std::string attrval;\n  std::unique_ptr<FileIo> io(FileIoPluginHelper::GetIoObject(path));\n  int result = io->attrGet(gFmdAttrName, attrval);\n\n  if (result != 0) {\n    eos_debug(\"msg=\\\"failed to retrieve fmd attribute\\\" path=\\\"%s\\\" errno=%d\",\n              path.c_str(), errno);\n    return {false, eos::common::FmdHelper{}};\n  }\n\n  eos::common::FmdHelper fmd;\n  bool status = fmd.mProtoFmd.ParsePartialFromString(attrval);\n\n  if (!status) {\n    eos_err(\"msg=\\\"failed parsing fmd attribute\\\" attr_sz=%lu\", attrval.size());\n  }\n\n  return {status, std::move(fmd)};\n}\n\n//------------------------------------------------------------------------------\n// Set Fmd xattr for the given file identifier\n//------------------------------------------------------------------------------\nbool\nFmdAttrHandler::LocalPutFmd(const eos::common::FmdHelper& fmd,\n                            eos::common::FileId::fileid_t fid,\n                            eos::common::FileSystem::fsid_t fsid,\n                            const std::string& path)\n{\n  if (path.empty()) {\n    return LocalPutFmd(fmd, mFSPathHandler->GetPath(fid, fsid));\n  } else {\n    return LocalPutFmd(fmd, path);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Set Fmd xattr for the corresponding file path\n//------------------------------------------------------------------------------\nbool\nFmdAttrHandler::LocalPutFmd(const eos::common::FmdHelper& fmd,\n                            const std::string& path)\n{\n  struct stat info;\n  std::unique_ptr<FileIo> io(FileIoPluginHelper::GetIoObject(path));\n\n  if (io->fileStat(&info)) {\n    eos_err(\"msg=\\\"file not existing\\\" path=\\\"%s\\\"\", path.c_str());\n    return false;\n  }\n\n  std::string attrval;\n  fmd.mProtoFmd.SerializePartialToString(&attrval);\n  int rc = io->attrSet(gFmdAttrName, attrval.c_str(), attrval.length());\n\n  if (rc != 0) {\n    eos_err(\"msg=\\\"failed to set xattr\\\" path=\\\"%s\\\" errno=%d\",\n            path.c_str(), errno);\n  }\n\n  return rc == 0;\n}\n\n//------------------------------------------------------------------------------\n// Delete Fmd xattr for the corresponding file identifier\n//------------------------------------------------------------------------------\nvoid\nFmdAttrHandler::LocalDeleteFmd(eos::common::FileId::fileid_t fid,\n                               eos::common::FileSystem::fsid_t fsid,\n                               bool drop_file)\n{\n  return LocalDeleteFmd(mFSPathHandler->GetPath(fid, fsid), drop_file);\n}\n\n//------------------------------------------------------------------------------\n// Delete Fmd xattr for the corresponding file path\n//------------------------------------------------------------------------------\nvoid\nFmdAttrHandler::LocalDeleteFmd(const std::string& path, bool drop_file)\n{\n  std::unique_ptr<FileIo> io(FileIoPluginHelper::GetIoObject(path));\n\n  if (drop_file) {\n    int rc = io->fileRemove();\n\n    if (rc && errno != ENOENT) {\n      eos_err(\"Failed to drop file at path=%s, errno=%d\", path.c_str(), errno)\n    }\n\n    return;\n  }\n\n  if (int rc = io->attrDelete(gFmdAttrName);\n      rc != 0) {\n    if (errno == ENOATTR || errno == ENOENT) {\n      return;\n    }\n\n    eos_err(\"Failed to Delete Fmd Attribute at path:%s, rc=%d\", path.c_str(),\n            errno);\n  }\n}\n\n\nbool\nFmdAttrHandler::Commit(eos::common::FmdHelper* fmd, bool lockit,\n                       std::string* path)\n{\n  struct timeval tv;\n  struct timezone tz;\n  gettimeofday(&tv, &tz);\n  fmd->mProtoFmd.set_mtime(tv.tv_sec);\n  fmd->mProtoFmd.set_atime(tv.tv_sec);\n  fmd->mProtoFmd.set_mtime_ns(tv.tv_usec * 1000);\n  fmd->mProtoFmd.set_atime_ns(tv.tv_usec * 1000);\n\n  if (path != nullptr) {\n    return LocalPutFmd(*fmd, *path);\n  }\n\n  return LocalPutFmd(*fmd, fmd->mProtoFmd.fid(), fmd->mProtoFmd.fsid());\n}\n\nstd::unique_ptr<eos::common::FmdHelper>\nFmdAttrHandler::LocalGetFmd(eos::common::FileId::fileid_t fid,\n                            eos::common::FileSystem::fsid_t fsid,\n                            bool force_retrieve, bool do_create,\n                            uid_t uid, gid_t gid,\n                            eos::common::LayoutId::layoutid_t layoutid)\n{\n  auto [status, _fmd] = LocalRetrieveFmd(fid, fsid);\n\n  if (!status && !do_create) {\n    eos_warning(\"msg=\\\"no fmd record found\\\" fxid=%08llx fsid=%lu\", fid, fsid);\n    return nullptr;\n  }\n\n  // Check the various conditions if we have a fmd attr already\n  if (status) {\n    auto fmd = std::make_unique<eos::common::FmdHelper>(std::move(_fmd.mProtoFmd));\n\n    if ((fmd->mProtoFmd.fid() != fid) || (fmd->mProtoFmd.fsid() != fsid)) {\n      eos_crit(\"msg=\\\"mismatch between requested fid/fsid and retrieved ones\\\" \"\n               \"fxid=%08llx retrieved_fxid=%08llx fsid=%lu retrieved_fsid=%lu\",\n               fid, fmd->mProtoFmd.fid(), fsid, fmd->mProtoFmd.fsid());\n\n      if (!force_retrieve) {\n        return nullptr;\n      }\n    }\n\n    if (force_retrieve) {\n      return fmd;\n    }\n\n    if (!eos::common::LayoutId::IsRain(fmd->mProtoFmd.lid())) {\n      if (!do_create &&\n          ((fmd->mProtoFmd.disksize() &&\n            (fmd->mProtoFmd.disksize() != eos::common::FmdHelper::UNDEF) &&\n            (fmd->mProtoFmd.disksize() != fmd->mProtoFmd.size())) ||\n           (fmd->mProtoFmd.mgmsize() &&\n            (fmd->mProtoFmd.mgmsize() != eos::common::FmdHelper::UNDEF) &&\n            (fmd->mProtoFmd.mgmsize() != fmd->mProtoFmd.size())))) {\n        eos_crit(\"msg=\\\"size mismatch disk/mgm vs memory\\\" fxid=%08llx \"\n                 \"fsid=%lu size=%llu disksize=%llu mgmsize=%llu\",\n                 fid, (unsigned long) fsid, fmd->mProtoFmd.size(),\n                 fmd->mProtoFmd.disksize(), fmd->mProtoFmd.mgmsize());\n        return nullptr;\n      }\n\n      if (!do_create &&\n          ((fmd->mProtoFmd.filecxerror() == 1) ||\n           (fmd->mProtoFmd.mgmchecksum().length() &&\n            (fmd->mProtoFmd.mgmchecksum() != fmd->mProtoFmd.checksum())))) {\n        eos_crit(\"msg=\\\"checksum error flagged/detected\\\" fxid=%08llx \"\n                 \"fsid=%lu checksum=%s diskchecksum=%s mgmchecksum=%s \"\n                 \"filecxerror=%d blockcxerror=%d\", fid,\n                 (unsigned long) fsid, fmd->mProtoFmd.checksum().c_str(),\n                 fmd->mProtoFmd.diskchecksum().c_str(),\n                 fmd->mProtoFmd.mgmchecksum().c_str(),\n                 fmd->mProtoFmd.filecxerror(),\n                 fmd->mProtoFmd.blockcxerror());\n      }\n    } else {\n      if (fmd->mProtoFmd.blockcxerror() == 1) {\n        eos_crit(\"msg=\\\"blockxs error detected\\\" fxid=%08llx fsid=%lu\",\n                 fid, fsid);\n        return nullptr;\n      }\n    }\n\n    return fmd;\n  } // status || force_retrieve\n\n  // Creating an fmd\n  auto fmd = std::make_unique<common::FmdHelper>();\n  fmd->mProtoFmd.set_uid(uid);\n  fmd->mProtoFmd.set_gid(gid);\n  fmd->mProtoFmd.set_lid(layoutid);\n  fmd->mProtoFmd.set_fsid(fsid);\n  fmd->mProtoFmd.set_fid(fid);\n  struct timeval tv;\n  struct timezone tz;\n  gettimeofday(&tv, &tz);\n  fmd->mProtoFmd.set_ctime(tv.tv_sec);\n  fmd->mProtoFmd.set_ctime_ns(tv.tv_usec * 1000);\n\n  if (Commit(fmd.get(), false)) {\n    eos_debug(\"msg=\\\"return fmd object\\\" fxid=%08llx fsid=%lu\", fid, fsid);\n    return fmd;\n  }\n\n  eos_crit(\"msg=\\\"failed to commit fmd to storage\\\" fxid=%08llx fsid=%lu\",\n           fid, fsid);\n  return nullptr;\n}\n\nbool\nFmdAttrHandler::ResetDiskInformation(eos::common::FileSystem::fsid_t fsid)\n{\n  std::error_code ec;\n  WalkFSTree(mFSPathHandler->GetFSPath(fsid),\n  [](std::string path) {\n    TransformAttr(path, gFmdAttrName,\n                  &FmdHandler::ResetFmdDiskInfo);\n  }, ec);\n\n  if (ec) {\n    eos_err(\"msg=\\\"Failed to walk FST Tree\\\" error=%s\", ec.message().c_str());\n  }\n\n  return !ec;\n}\n\nbool\nFmdAttrHandler::ResetMgmInformation(eos::common::FileSystem::fsid_t fsid)\n{\n  std::error_code ec;\n  WalkFSTree(mFSPathHandler->GetFSPath(fsid),\n  [](std::string path) {\n    TransformAttr(path, gFmdAttrName,\n                  &FmdHandler::ResetFmdMgmInfo);\n  }, ec);\n\n  if (ec) {\n    eos_err(\"msg=\\\"Failed to walk FST Tree\\\" error=%s\", ec.message().c_str());\n  }\n\n  return !ec;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/filemd/FmdAttr.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file FmdAttr.hh\n//! @author Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"fst/Namespace.hh\"\n#include \"fst/filemd/FmdHandler.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\nstatic constexpr auto gFmdAttrName = \"user.eos.fmd\";\n\n//! Forward declarations\nclass FSPathHandler;\nclass FileIo;\n\n//------------------------------------------------------------------------------\n//! Class FmdAttrHandler\n//----------------------------------------------------------------------------\nclass FmdAttrHandler final: public FmdHandler\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  FmdAttrHandler(std::unique_ptr<FSPathHandler>&& _FSPathHandler);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~FmdAttrHandler() = default;\n\n  void LocalDeleteFmd(eos::common::FileId::fileid_t fid,\n                      eos::common::FileSystem::fsid_t fsid,\n                      bool drop_file = false) override;\n\n  bool Commit(eos::common::FmdHelper* fmd, bool lockit = false,\n              std::string* path = nullptr) override;\n\n  std::unique_ptr<eos::common::FmdHelper>\n  LocalGetFmd(eos::common::FileId::fileid_t fid,\n              eos::common::FileSystem::fsid_t fsid,\n              bool force_retrieve = false, bool do_create = false,\n              uid_t uid = 0, gid_t gid = 0,\n              eos::common::LayoutId::layoutid_t layoutid = 0) override;\n\n  std::pair<bool, eos::common::FmdHelper>\n  LocalRetrieveFmd(eos::common::FileId::fileid_t fid,\n                   eos::common::FileSystem::fsid_t fsid,\n                   const std::string& path = \"\") override;\n\n  std::pair<bool, eos::common::FmdHelper>\n  LocalRetrieveFmd(const std::string& path);\n\nprivate:\n  std::unique_ptr<FSPathHandler> mFSPathHandler;\n\n  //----------------------------------------------------------------------------\n  //! Attach Fmd metadata info to the current file identifier\n  //!\n  //! @param fmd file metadata info protobuf object\n  //! @param fid file identifier\n  //! @param fsid file system identifier\n  //! @param path local file absolute path\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool LocalPutFmd(const eos::common::FmdHelper& fmd,\n                   eos::common::FileId::fileid_t fid,\n                   eos::common::FileSystem::fsid_t fsid,\n                   const std::string& path = \"\") override;\n\n  bool LocalPutFmd(const eos::common::FmdHelper& fmd,\n                   const std::string& path);\n\n  void LocalDeleteFmd(const std::string& path, bool drop_file);\n\n  bool ResetDiskInformation(eos::common::FileSystem::fsid_t fsid) override;\n\n  bool ResetMgmInformation(eos::common::FileSystem::fsid_t fsid) override;\n\n  void SetSyncStatus(eos::common::FileSystem::fsid_t, bool) override {}\n};\n\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/filemd/FmdHandler.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file FmdHandler.hh\n//! @author Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Path.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/ns_quarkdb/FileMD.hh\"\n#include \"namespace/ns_quarkdb/persistency/FileMDSvc.hh\"\n#include \"namespace/ns_quarkdb/persistency/MetadataFetcher.hh\"\n#include \"namespace/ns_quarkdb/persistency/RequestBuilder.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"qclient/structures/QSet.hh\"\n#include \"fst/utils/FTSWalkTree.hh\"\n#include \"fst/checksum/ChecksumPlugins.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/io/local/LocalIo.hh\"\n#include \"fst/io/FileIoPluginCommon.hh\"\n#include \"fst/filemd/FmdMgm.hh\"\n#include \"fst/filemd/FmdAttr.hh\"\n#include \"fst/filemd/FmdHandler.hh\"\nEOSFSTNAMESPACE_BEGIN\n\n\n//------------------------------------------------------------------------------\n// Check if entry has a file checksum error\n//------------------------------------------------------------------------------\nbool\nFmdHandler::FileHasXsError(const std::string& lpath,\n                           eos::common::FileSystem::fsid_t fsid)\n{\n  bool has_xs_err = false;\n  // First check the local db for any filecxerror flags\n  auto fid = eos::common::FileId::PathToFid(lpath.c_str());\n  auto fmd = LocalGetFmd(fid, fsid, true);\n\n  if (fmd && fmd->mProtoFmd.filecxerror()) {\n    has_xs_err = true;\n  }\n\n  // If no error found then also check the xattr on the physical file\n  if (!has_xs_err) {\n    std::unique_ptr<FileIo> io(FileIoPluginHelper::GetIoObject(lpath.c_str()));\n    std::string xattr_xs_err = \"0\";\n\n    if (io->attrGet(\"user.eos.filecxerror\", xattr_xs_err) == 0) {\n      has_xs_err = (xattr_xs_err == \"1\");\n    }\n  }\n\n  return has_xs_err;\n}\n\n//------------------------------------------------------------------------------\n// Update file metadata object with new fid information\n//------------------------------------------------------------------------------\nbool\nFmdHandler::UpdateFmd(const std::string& path,\n                      eos::common::FileId::fileid_t fid)\n{\n  // We rely on the path to retrieve the Fmd object\n  auto [status, fmd] = LocalRetrieveFmd(0ull, 0ul, path);\n\n  if (!status) {\n    return false;\n  }\n\n  fmd.mProtoFmd.set_fid(fid);\n  return LocalPutFmd(fmd, 0ull, 0ul, path);\n}\n\n\n//------------------------------------------------------------------------------\n// Update fmd with disk info i.e. physical file extended attributes\n//------------------------------------------------------------------------------\nbool\nFmdHandler::UpdateWithDiskInfo(eos::common::FileSystem::fsid_t fsid,\n                               eos::common::FileId::fileid_t fid,\n                               unsigned long long disk_size,\n                               const std::string& disk_xs,\n                               unsigned long check_ts_sec,\n                               bool filexs_err,\n                               bool blockxs_err,\n                               bool layout_err)\n{\n  using eos::common::LayoutId;\n\n  if (!fid) {\n    eos_err(\"%s\", \"msg=\\\"skipping insert of file with fid=0\\\"\");\n    return false;\n  }\n\n  eos_debug(\"fsid=%lu fxid=%08llx disksize=%llu diskchecksum=%s checktime=%llu \"\n            \"fcxerror=%d bcxerror=%d flaglayouterror=%d\",\n            fsid, fid, disk_size, disk_xs.c_str(), check_ts_sec,\n            filexs_err, blockxs_err, layout_err);\n  auto [status, valfmd] = LocalRetrieveFmd(fid, fsid);\n  valfmd.mProtoFmd.set_fid(fid);\n  valfmd.mProtoFmd.set_fsid(fsid);\n  valfmd.mProtoFmd.set_disksize(disk_size);\n  valfmd.mProtoFmd.set_checktime(check_ts_sec);\n  valfmd.mProtoFmd.set_filecxerror(filexs_err ? 1 : 0);\n  valfmd.mProtoFmd.set_blockcxerror(blockxs_err ? 1 : 0);\n\n  // Update reference size only if undefined\n  if (valfmd.mProtoFmd.size() == eos::common::FmdHelper::UNDEF) {\n    // This is done only for non-rain layouts\n    if (!LayoutId::IsRain(valfmd.mProtoFmd.lid())) {\n      valfmd.mProtoFmd.set_size(disk_size);\n    }\n  }\n\n  if (disk_xs.empty() && (disk_size == 0))  {\n    valfmd.mProtoFmd.set_diskchecksum\n    (LayoutId::GetEmptyFileHexChecksum(valfmd.mProtoFmd.lid()));\n  } else {\n    valfmd.mProtoFmd.set_diskchecksum(disk_xs);\n  }\n\n  // Update the reference checksum only if empty\n  if (valfmd.mProtoFmd.checksum().empty()) {\n    valfmd.mProtoFmd.set_checksum(valfmd.mProtoFmd.diskchecksum());\n  }\n\n  if (layout_err) {\n    // If the mgm sync is run afterwards, every disk file is by construction an\n    // orphan, until it is synced from the mgm\n    valfmd.mProtoFmd.set_layouterror(LayoutId::kOrphan);\n  }\n\n  return LocalPutFmd(valfmd, fid, fsid);\n}\n\n\n//------------------------------------------------------------------------------\n// Update fmd from MGM metadata\n//------------------------------------------------------------------------------\nbool\nFmdHandler::UpdateWithMgmInfo(eos::common::FileSystem::fsid_t fsid,\n                              eos::common::FileId::fileid_t fid,\n                              eos::common::FileId::fileid_t cid,\n                              eos::common::LayoutId::layoutid_t lid,\n                              unsigned long long mgmsize,\n                              std::string mgmchecksum,\n                              uid_t uid, gid_t gid,\n                              unsigned long long ctime,\n                              unsigned long long ctime_ns,\n                              unsigned long long mtime,\n                              unsigned long long mtime_ns,\n                              int layouterror, std::string locations)\n{\n  if (!fid) {\n    eos_err(\"msg=\\\"skip inserting file with fid=0\\\"\");\n    return false;\n  }\n\n  eos_debug(\"fxid=%08llx fsid=%lu cid=%llu lid=%lx mgmsize=%llu \"\n            \"mgmchecksum=%s layouterror=%i\", fid, fsid, cid, lid,\n            mgmsize, mgmchecksum.c_str(), layouterror);\n  auto [status, valfmd] = LocalRetrieveFmd(fid, fsid);\n\n  if (!status) {\n    eos_err(\"msg=\\\"failed to retrieve filemd to update mgm info\\\" fxid=%08llx fsid=%lu\",\n            fid, fsid);\n    return false;\n  }\n\n  valfmd.mProtoFmd.set_mgmsize(mgmsize);\n  valfmd.mProtoFmd.set_mgmchecksum(mgmchecksum);\n  valfmd.mProtoFmd.set_cid(cid);\n  valfmd.mProtoFmd.set_lid(lid);\n  valfmd.mProtoFmd.set_uid(uid);\n  valfmd.mProtoFmd.set_gid(gid);\n  valfmd.mProtoFmd.set_ctime(ctime);\n  valfmd.mProtoFmd.set_ctime_ns(ctime_ns);\n  valfmd.mProtoFmd.set_mtime(mtime);\n  valfmd.mProtoFmd.set_mtime_ns(mtime_ns);\n  valfmd.mProtoFmd.set_layouterror(layouterror);\n  valfmd.mProtoFmd.set_locations(locations);\n  // Truncate the checksum to the right length\n  size_t cslen = LayoutId::GetChecksumLen(lid) * 2;\n  valfmd.mProtoFmd.set_mgmchecksum(std::string(\n                                     valfmd.mProtoFmd.mgmchecksum()).erase\n                                   (std::min(valfmd.mProtoFmd.mgmchecksum().length(), cslen)));\n\n  // Update reference size only if undefined\n  if (valfmd.mProtoFmd.size() == eos::common::FmdHelper::UNDEF) {\n    valfmd.mProtoFmd.set_size(mgmsize);\n  } else {\n    // For RAIN layouts the logical size (should) matche the MGM size\n    // even if it is already set\n    if (eos::common::LayoutId::IsRain(lid)) {\n      valfmd.mProtoFmd.set_size(mgmsize);\n    }\n  }\n\n  // Update the reference checksum only if empty\n  if (valfmd.mProtoFmd.checksum().empty()) {\n    valfmd.mProtoFmd.set_checksum(valfmd.mProtoFmd.mgmchecksum());\n  }\n\n  return LocalPutFmd(valfmd, fid, fsid);\n}\n\n//------------------------------------------------------------------------------\n// Update local fmd with info from the rain stripes scanner\n//-----------------------------------------------------------------------------\nvoid\nFmdHandler::UpdateWithStripeCheckInfo(\n  eos::common::FileId::fileid_t fid, eos::common::FileSystem::fsid_t fsid,\n  const std::set<eos::common::FileSystem::fsid_t>& invalid_stripes)\n{\n  auto fmd = LocalGetFmd(fid, fsid, true);\n\n  if (fmd) {\n    fmd->mProtoFmd.clear_stripeerror();\n\n    for (auto invalid_fsid : invalid_stripes) {\n      fmd->mProtoFmd.add_stripeerror(invalid_fsid);\n    }\n\n    Commit(fmd.get());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Update local fmd with info from the scanner\n//------------------------------------------------------------------------------\nvoid\nFmdHandler::UpdateWithScanInfo(eos::common::FileId::fileid_t fid,\n                               eos::common::FileSystem::fsid_t fsid,\n                               const std::string& fpath,\n                               uint64_t scan_sz,\n                               const std::string& scan_xs_hex,\n                               std::shared_ptr<qclient::QClient> qcl)\n{\n  eos_debug(\"msg=\\\"resyncing qdb and disk info\\\" fxid=%08llx fsid=%lu\",\n            fid, fsid);\n\n  if (ResyncFileFromQdb(fid, fsid, fpath, qcl)) {\n    return;\n  }\n\n  int rd_rc = ResyncDisk(fpath.c_str(), fsid, false, scan_sz, scan_xs_hex);\n\n  if (rd_rc) {\n    if (rd_rc == ENOENT) {\n      // File no longer on disk - mark it as missing unless it's a 0-size file\n      auto fmd = LocalGetFmd(fid, fsid, true);\n\n      if (fmd && fmd->mProtoFmd.mgmsize()) {\n        fmd->mProtoFmd.set_layouterror(fmd->mProtoFmd.layouterror() |\n                                       LayoutId::kMissing);\n        Commit(fmd.get());\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Clear errors on local fmd\n//------------------------------------------------------------------------------\nvoid\nFmdHandler::ClearErrors(eos::common::FileId::fileid_t fid,\n                        eos::common::FileSystem::fsid_t fsid,\n                        bool clear_stripe_err)\n{\n  auto fmd = LocalGetFmd(fid, fsid, true);\n\n  if (fmd) {\n    if (clear_stripe_err) {\n      fmd->mProtoFmd.clear_stripeerror();\n    } else {\n      fmd->mProtoFmd.set_layouterror(0);\n      fmd->mProtoFmd.set_blockcxerror(0);\n      fmd->mProtoFmd.set_filecxerror(0);\n    }\n\n    Commit(fmd.get());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Resync a single entry from disk\n//------------------------------------------------------------------------------\nint\nFmdHandler::ResyncDisk(const char* path,\n                       eos::common::FileSystem::fsid_t fsid,\n                       bool flaglayouterror,\n                       uint64_t scan_sz, const std::string& scan_xs_hex)\n{\n  eos::common::Path cPath(path);\n  eos::common::FileId::fileid_t fid =\n    eos::common::FileId::Hex2Fid(cPath.GetName());\n\n  if (fid == 0) {\n    eos_err(\"msg=\\\"unable to sync fid=0\\\" path=\\\"%s\\\"\", path);\n    return EINVAL;\n  }\n\n  std::unique_ptr<eos::fst::FileIo>\n  io(eos::fst::FileIoPluginHelper::GetIoObject(path));\n\n  if (io == nullptr) {\n    eos_crit(\"msg=\\\"failed to get IO object\\\" path=%s\", path);\n    return ENOMEM;\n  }\n\n  struct stat buf;\n\n  if ((!io->fileStat(&buf)) && S_ISREG(buf.st_mode)) {\n    std::string sxs_type, scheck_stamp, filexs_err, blockxs_err;\n    char xs_val[SHA256_DIGEST_LENGTH];\n    size_t xs_len = SHA256_DIGEST_LENGTH;\n    memset(xs_val, 0, sizeof(xs_val));\n    io->attrGet(\"user.eos.checksumtype\", sxs_type);\n    io->attrGet(\"user.eos.filecxerror\", filexs_err);\n    io->attrGet(\"user.eos.blockcxerror\", blockxs_err);\n    io->attrGet(\"user.eos.timestamp\", scheck_stamp);\n\n    // Handle the old format in microseconds, truncate to seconds\n    if (scheck_stamp.length() > 10) {\n      scheck_stamp.erase(10);\n    }\n\n    unsigned long check_ts_sec {0ul};\n\n    try {\n      check_ts_sec = std::stoul(scheck_stamp);\n    } catch (...) {\n      // ignore\n    }\n\n    std::string disk_xs_hex;\n    off_t disk_size {0ull};\n\n    if (scan_sz && !scan_xs_hex.empty()) {\n      disk_size = scan_sz;\n      disk_xs_hex = scan_xs_hex;\n    } else {\n      disk_size = buf.st_size;\n\n      if (io->attrGet(\"user.eos.checksum\", xs_val, xs_len) == 0) {\n        std::unique_ptr<CheckSum> xs_obj {ChecksumPlugins::GetXsObj(sxs_type)};\n\n        if (xs_obj) {\n          if (xs_obj->SetBinChecksum(xs_val, xs_len)) {\n            disk_xs_hex = xs_obj->GetHexChecksum();\n          }\n        }\n      }\n    }\n\n    // Update the DB\n    if (!UpdateWithDiskInfo(fsid, fid, disk_size, disk_xs_hex, check_ts_sec,\n                            (filexs_err == \"1\"), (blockxs_err == \"1\"),\n                            flaglayouterror)) {\n      return false;\n    }\n  } else {\n    eos_err(\"msg=\\\"failed stat or entry is not a file\\\" path=%s\", path);\n    return ENOENT;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Resync files under path into DB\n//------------------------------------------------------------------------------\nbool\nFmdHandler::ResyncAllDisk(const char* path,\n                          eos::common::FileSystem::fsid_t fsid,\n                          bool flaglayouterror)\n{\n  if (flaglayouterror) {\n    SetSyncStatus(fsid, true);\n  }\n\n  if (!ResetDiskInformation(fsid)) {\n    eos_err(\"failed to reset the disk information before resyncing fsid=%lu\",\n            fsid);\n    return false;\n  }\n\n  uint64_t scan_sz = 0;\n  std::string scan_xs_hex;\n  std::error_code ec;\n  WalkFSTree(path,\n  [&](const char* path) {\n    this->ResyncDisk(path, fsid, flaglayouterror, scan_sz, scan_xs_hex);\n  }, ec);\n\n  if (ec) {\n    eos_err(\"msg=\\\"Walk FST tree failed\\\" error=%s\", ec.message().c_str());\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Resync file meta data from MGM into local database\n//------------------------------------------------------------------------------\nbool\nFmdHandler::ResyncMgm(eos::common::FileSystem::fsid_t fsid,\n                      eos::common::FileId::fileid_t fid,\n                      const char* manager)\n{\n  eos::ns::FileMdProto file;\n  int rc = FmdMgmHandler::GetMgmFmd((manager ? manager : \"\"), fid, file);\n\n  if ((rc == 0) || (rc == ENODATA)) {\n    if (rc == ENODATA) {\n      eos_warning(\"msg=\\\"file not found on MGM\\\" fxid=%08llx\", fid);\n\n      if (fid == 0) {\n        eos_warning(\"msg=\\\"removing fxid=0 entry\\\"\");\n        LocalDeleteFmd(fid, fsid, false);\n        return true;\n      }\n    }\n\n    eos::common::FmdHelper fMd;\n    FmdMgmHandler::NsFileProtoToFmd(std::move(file), fMd);\n    fMd.mProtoFmd.set_layouterror(fMd.LayoutError(fsid));\n    // Get an existing record without creating the record !!!\n    std::unique_ptr<eos::common::FmdHelper> fmd {\n      LocalGetFmd(fMd.mProtoFmd.fid(), fsid, true, false, fMd.mProtoFmd.uid(),\n                  fMd.mProtoFmd.gid(), fMd.mProtoFmd.lid())};\n\n    if (fmd) {\n      // Check if exists on disk\n      if (fmd->mProtoFmd.disksize() == eos::common::FmdHelper::UNDEF) {\n        if (fMd.mProtoFmd.layouterror() & LayoutId::kUnregistered) {\n          // There is no replica supposed to be here and there is nothing on\n          // disk, so remove it from the database\n          eos_warning(\"msg=\\\"removing ghost fmd from db\\\" fsid=%u fxid=%08llx\",\n                      fsid, fid);\n          LocalDeleteFmd(fMd.mProtoFmd.fid(), fsid, false);\n          return true;\n        }\n      }\n    } else {\n      // No file locally and also not registered with the MGM\n      if ((fMd.mProtoFmd.layouterror() & LayoutId::kUnregistered) ||\n          (fMd.mProtoFmd.layouterror() & LayoutId::kOrphan)) {\n        return true;\n      }\n    }\n\n    // Get/create a record\n    fmd = LocalGetFmd(fMd.mProtoFmd.fid(), fsid, true, true,\n                      fMd.mProtoFmd.uid(), fMd.mProtoFmd.gid(),\n                      fMd.mProtoFmd.lid());\n\n    if (fmd) {\n      // Check if it exists on disk\n      if ((fmd->mProtoFmd.disksize() == eos::common::FmdHelper::UNDEF) &&\n          (fMd.mProtoFmd.mgmsize())) {\n        fMd.mProtoFmd.set_layouterror(fMd.mProtoFmd.layouterror() | LayoutId::kMissing);\n        eos_warning(\"msg=\\\"mark missing replica\\\" fxid=%08llx on fsid=%u\",\n                    fid, fsid);\n      }\n\n      if (!UpdateWithMgmInfo(fsid, fMd.mProtoFmd.fid(), fMd.mProtoFmd.cid(),\n                             fMd.mProtoFmd.lid(), fMd.mProtoFmd.mgmsize(),\n                             fMd.mProtoFmd.mgmchecksum(), fMd.mProtoFmd.uid(),\n                             fMd.mProtoFmd.gid(), fMd.mProtoFmd.ctime(),\n                             fMd.mProtoFmd.ctime_ns(), fMd.mProtoFmd.mtime(),\n                             fMd.mProtoFmd.mtime_ns(), fMd.mProtoFmd.layouterror(),\n                             fMd.mProtoFmd.locations())) {\n        eos_err(\"msg=\\\"failed to update fmd with mgm info\\\" fxid=%08llx\", fid);\n        return false;\n      }\n\n      // Check if it exists on disk and at the mgm\n      if ((fmd->mProtoFmd.disksize() == eos::common::FmdHelper::UNDEF) &&\n          (fMd.mProtoFmd.mgmsize() == eos::common::FmdHelper::UNDEF)) {\n        // There is no replica supposed to be here and there is nothing on\n        // disk, so remove it from the database\n        eos_warning(\"removing <ghost> entry for fxid=%08llx on fsid=%u\", fid,\n                    (unsigned long) fsid);\n        LocalDeleteFmd(fMd.mProtoFmd.fid(), fsid, false);\n        return true;\n      }\n    } else {\n      eos_err(\"failed to create fmd for fxid=%08llx\", fid);\n      return false;\n    }\n  } else {\n    eos_err(\"failed to retrieve MGM fmd for fxid=%08llx\", fid);\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Move given file to orphans directory and also set its extended attribute\n// to reflect the original path to the file.\n//------------------------------------------------------------------------------\nvoid\nFmdHandler::MoveToOrphans(const std::string& fpath)\n{\n  eos::common::Path cpath(fpath.c_str());\n  size_t cpath_sz = cpath.GetSubPathSize();\n\n  if (cpath_sz <= 2) {\n    eos_static_err(\"msg=\\\"failed to extract FST mount/fid hex\\\" path=%s\",\n                   fpath.c_str());\n    return;\n  }\n\n  std::string fid_hex = cpath.GetName();\n  std::ostringstream oss;\n  oss << cpath.GetSubPath(cpath_sz - 2) << \".eosorphans/\" << fid_hex;\n  std::string forphan = oss.str();\n  // Store the original path name as an extended attribute in case ...\n  std::unique_ptr<FileIo> io(FileIoPluginHelper::GetIoObject(fpath));\n  io->attrSet(\"user.eos.orphaned\", fpath.c_str());\n\n  // If orphan move it into the orphaned directory\n  if (!rename(fpath.c_str(), forphan.c_str())) {\n    eos_static_warning(\"msg=\\\"orphaned/unregistered quarantined\\\" \"\n                       \"fst-path=%s orphan-path=%s\", fpath.c_str(), forphan.c_str());\n  } else {\n    eos_static_err(\"msg=\\\"failed to quarantine orphaned/unregistered\\\" \"\n                   \"fst-path=%s orphan-path=%s\", fpath.c_str(), forphan.c_str());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Resync file meta data from QuarkDB into local database\n//------------------------------------------------------------------------------\nint\nFmdHandler::ResyncFileFromQdb(eos::common::FileId::fileid_t fid,\n                              eos::common::FileSystem::fsid_t fsid,\n                              const std::string& fpath,\n                              std::shared_ptr<qclient::QClient> qcl)\n{\n  using eos::common::FileId;\n\n  if (qcl == nullptr) {\n    eos_notice(\"msg=\\\"no qclient present, skipping file resync\\\" fxid=%08llx\"\n               \" fid=%lu\", fid, fsid);\n    return EINVAL;\n  }\n\n  eos::common::FmdHelper ns_fmd;\n  auto file_fut = eos::MetadataFetcher::getFileFromId(*qcl.get(),\n                  eos::FileIdentifier(fid));\n\n  try {\n    FmdMgmHandler::NsFileProtoToFmd(std::move(file_fut).get(), ns_fmd);\n  } catch (const eos::MDException& e) {\n    eos_err(\"msg=\\\"failed to get metadata from QDB: %s\\\" fxid=%08llx\",\n            e.what(), fid);\n\n    // If there is any transient error with QDB then we skip this file,\n    // otherwise it might be wronly marked as orphan below.\n    if (e.getErrno() != ENOENT) {\n      eos_err(\"msg=\\\"skip file update due to QDB error\\\" msg_err=\\\"%s\\\" \"\n              \"fxid=%08llx\", e.what(), fid);\n      return e.getErrno();\n    }\n  }\n\n  // Mark any possible layout error, if fid not found in QDB then this is\n  // marked as orphan\n  ns_fmd.mProtoFmd.set_layouterror(ns_fmd.LayoutError(fsid));\n  // Get an existing local record without creating the record!!!\n  std::unique_ptr<eos::common::FmdHelper> local_fmd {\n    LocalGetFmd(fid, fsid, true, false)};\n\n  if (!local_fmd) {\n    // Create the local record\n    if (!(local_fmd = LocalGetFmd(fid, fsid, true, true))) {\n      eos_err(\"msg=\\\"failed to create local fmd entry\\\" fxid=%08llx fsid=%u\",\n              fid, fsid);\n      return EINVAL;\n    }\n  }\n\n  // Orphan files get moved to a special directory .eosorphans\n  if (ns_fmd.mProtoFmd.layouterror() & eos::common::LayoutId::kOrphan) {\n    local_fmd->mProtoFmd.set_layouterror(LayoutId::kOrphan);\n\n    if (!Commit(local_fmd.get())) {\n      eos_err(\"msg=\\\"failed to mark orphan entry\\\" fxid=%08llx fsid=%u\",\n              fid, fsid);\n    }\n\n    FmdHandler::MoveToOrphans(fpath);\n#ifndef _NOOFS\n    gOFS.Storage->PublishFsckError(fid, fsid, eos::common::FsckErr::Orphans);\n#endif\n    return ENOENT;\n  }\n\n  // Never mark an ns 0-size file without replicas on disk as missing\n  if (ns_fmd.mProtoFmd.mgmsize() == 0) {\n    ns_fmd.mProtoFmd.set_layouterror(ns_fmd.mProtoFmd.layouterror() &\n                                     ~LayoutId::kMissing);\n  } else {\n    // If file is not on disk or already marked as missing then keep the\n    // missing flag\n    if ((local_fmd->mProtoFmd.disksize() == eos::common::FmdHelper::UNDEF) ||\n        (local_fmd->mProtoFmd.layouterror() & LayoutId::kMissing)) {\n      eos_warning(\"msg=\\\"mark missing replica\\\" fxid=%08llx fsid=%u\", fid, fsid);\n      ns_fmd.mProtoFmd.set_layouterror(ns_fmd.mProtoFmd.layouterror() |\n                                       LayoutId::kMissing);\n    }\n  }\n\n  if (!UpdateWithMgmInfo(fsid, fid, ns_fmd.mProtoFmd.cid(),\n                         ns_fmd.mProtoFmd.lid(), ns_fmd.mProtoFmd.mgmsize(),\n                         ns_fmd.mProtoFmd.mgmchecksum(), ns_fmd.mProtoFmd.uid(),\n                         ns_fmd.mProtoFmd.gid(), ns_fmd.mProtoFmd.ctime(),\n                         ns_fmd.mProtoFmd.ctime_ns(), ns_fmd.mProtoFmd.mtime(),\n                         ns_fmd.mProtoFmd.mtime_ns(), ns_fmd.mProtoFmd.layouterror(),\n                         ns_fmd.mProtoFmd.locations())) {\n    eos_err(\"msg=\\\"failed to update fmd with qdb info\\\" fxid=%08llx\", fid);\n    return EINVAL;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Resync all meta data from QuarkdDB\n//------------------------------------------------------------------------------\nbool\nFmdHandler::ResyncAllFromQdb(const QdbContactDetails& contact_details,\n                             eos::common::FileSystem::fsid_t fsid)\n{\n  using namespace std::chrono;\n\n  if (!ResetMgmInformation(fsid)) {\n    eos_err(\"%s\", \"msg=\\\"failed to reset the mgm info before resyncing\\\"\");\n    SetSyncStatus(fsid, false);\n    return false;\n  }\n\n  // Collect all file ids on the desired file system\n  auto start = steady_clock::now();\n  qclient::QClient qcl(contact_details.members,\n                       contact_details.constructOptions());\n  std::unordered_set<eos::IFileMD::id_t> file_ids;\n  qclient::QSet qset(qcl, eos::RequestBuilder::keyFilesystemFiles(fsid));\n\n  try {\n    for (qclient::QSet::Iterator its = qset.getIterator(); its.valid();\n         its.next()) {\n      try {\n        file_ids.insert(std::stoull(its.getElement()));\n      } catch (...) {\n        eos_err(\"msg=\\\"failed to convert fid entry\\\" data=\\\"%s\\\"\",\n                its.getElement().c_str());\n      }\n    }\n  } catch (const std::runtime_error& e) {\n    // There are no files on current filesystem\n  }\n\n  uint64_t total = file_ids.size();\n  eos_info(\"msg=\\\"resyncing %llu files for file_system %u\\\"\", total, fsid);\n  uint64_t num_files = 0;\n  auto it = file_ids.begin();\n  std::list<std::pair<eos::common::FileId::fileid_t,\n      folly::Future<eos::ns::FileMdProto>>> files;\n\n  // Pre-fetch the first 1000 files\n  while ((it != file_ids.end()) && (num_files < 1000)) {\n    ++num_files;\n    files.emplace_back(*it, MetadataFetcher::getFileFromId(qcl,\n                       FileIdentifier(*it)));\n    ++it;\n  }\n\n  while (!files.empty()) {\n    eos::common::FmdHelper ns_fmd;\n    eos::common::FileId::fileid_t fid = files.front().first;\n\n    try {\n      FmdMgmHandler::NsFileProtoToFmd(std::move(files.front().second).get(), ns_fmd);\n    } catch (const eos::MDException& e) {\n      eos_err(\"msg=\\\"failed to get metadata from QDB: %s\\\"\", e.what());\n    }\n\n    files.pop_front();\n    // Mark any possible layout error, if fid not found in QDB then this is\n    // marked as orphan\n    ns_fmd.mProtoFmd.set_layouterror(ns_fmd.LayoutError(fsid));\n    // Get an existing local record without creating the record!!!\n    std::unique_ptr<eos::common::FmdHelper> local_fmd {\n      LocalGetFmd(fid, fsid, true, false)};\n\n    if (!local_fmd) {\n      // Create the local record\n      if (!(local_fmd = LocalGetFmd(fid, fsid, true, true))) {\n        eos_err(\"msg=\\\"failed to create local fmd entry\\\" fxid=%08llx\", fid);\n        continue;\n      }\n    }\n\n    // If file does not exist on disk and is not 0-size then mark as missing\n    if ((local_fmd->mProtoFmd.disksize() == eos::common::FmdHelper::UNDEF) &&\n        (ns_fmd.mProtoFmd.mgmsize())) {\n      ns_fmd.mProtoFmd.set_layouterror(ns_fmd.mProtoFmd.layouterror() |\n                                       LayoutId::kMissing);\n      eos_warning(\"msg=\\\"mark missing replica\\\" fxid=%08llx fsid=%u\", fid, fsid);\n    }\n\n    if (!UpdateWithMgmInfo(fsid, fid, ns_fmd.mProtoFmd.cid(),\n                           ns_fmd.mProtoFmd.lid(), ns_fmd.mProtoFmd.mgmsize(),\n                           ns_fmd.mProtoFmd.mgmchecksum(), ns_fmd.mProtoFmd.uid(),\n                           ns_fmd.mProtoFmd.gid(), ns_fmd.mProtoFmd.ctime(),\n                           ns_fmd.mProtoFmd.ctime_ns(), ns_fmd.mProtoFmd.mtime(),\n                           ns_fmd.mProtoFmd.mtime_ns(), ns_fmd.mProtoFmd.layouterror(),\n                           ns_fmd.mProtoFmd.locations())) {\n      eos_err(\"msg=\\\"failed to update fmd with qdb info\\\" fxid=%08llx\", fid);\n      continue;\n    }\n\n    if (it != file_ids.end()) {\n      files.emplace_back(*it, MetadataFetcher::getFileFromId(qcl,\n                         FileIdentifier(*it)));\n      ++num_files;\n      ++it;\n    }\n\n    if (num_files % 10000 == 0) {\n      double rate = 0;\n      auto duration = steady_clock::now() - start;\n      auto ms = duration_cast<milliseconds>(duration);\n\n      if (ms.count()) {\n        rate = (num_files * 1000.0) / (double)ms.count();\n      }\n\n      eos_info(\"fsid=%u resynced %llu/%llu files at a rate of %.2f Hz\",\n               fsid, num_files, total, rate);\n    }\n  }\n\n  double rate = 0;\n  auto duration = steady_clock::now() - start;\n  auto ms = duration_cast<milliseconds>(duration);\n\n  if (ms.count()) {\n    rate = (num_files * 1000.0) / (double)ms.count();\n  }\n\n  SetSyncStatus(fsid, false);\n  eos_info(\"msg=\\\"fsid=%u resynced %llu/%llu files at a rate of %.2f Hz\\\"\",\n           fsid, num_files, total, rate);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Reset the disk info related to the encoded Fmd object\n//------------------------------------------------------------------------------\nstd::string\nFmdHandler::ResetFmdDiskInfo(const std::string& input)\n{\n  eos::common::FmdHelper f;\n\n  if (!f.mProtoFmd.ParseFromString(input))\n    return {};\n\n  f.mProtoFmd.set_disksize(eos::common::FmdHelper::UNDEF);\n\n  f.mProtoFmd.set_diskchecksum(\"\");\n\n  f.mProtoFmd.set_checktime(0);\n\n  f.mProtoFmd.set_filecxerror(0);\n\n  f.mProtoFmd.set_blockcxerror(0);\n\n  f.mProtoFmd.clear_stripeerror();\n\n  std::string out;\n\n  f.mProtoFmd.SerializeToString(&out);\n\n  return out;\n}\n\n//------------------------------------------------------------------------------\n// Reset the MGM info related to the encoded Fmd object\n//------------------------------------------------------------------------------\nstd::string\nFmdHandler::ResetFmdMgmInfo(const std::string& input)\n{\n  eos::common::FmdHelper f;\n\n  if (!f.mProtoFmd.ParseFromString(input))\n    return {};\n\n  f.mProtoFmd.set_mgmsize(eos::common::FmdHelper::UNDEF);\n\n  f.mProtoFmd.set_mgmchecksum(\"\");\n\n  f.mProtoFmd.set_locations(\"\");\n\n  std::string out;\n\n  f.mProtoFmd.SerializeToString(&out);\n\n  return out;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/filemd/FmdHandler.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file FmdHandler.hh\n//! @author Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"fst/Namespace.hh\"\n#include \"common/Fmd.hh\"\n#include \"common/FileId.hh\"\n#include \"common/LayoutId.hh\"\n#include <set>\n\n//! Forward declaration\nnamespace eos\n{\nclass QdbContactDetails;\n}\n\nnamespace qclient\n{\nclass QClient;\n}\n\nEOSFSTNAMESPACE_BEGIN\n\nclass FmdHandler: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Reset the disk info related to the encoded Fmd object\n  //!\n  //! @param input a string that can serialize to FmdProtobuf object\n  //!\n  //! @return string value after resetting info or an empty string if\n  //!         serialization fails\n  //----------------------------------------------------------------------------\n  static std::string ResetFmdDiskInfo(const std::string& input);\n\n  //----------------------------------------------------------------------------\n  //! Reset the mgm info related to the encoded Fmd object\n  //!\n  //! @param input a string that can serialize to FmdProtobuf object\n  //!\n  //! @return string value after resetting info or an empty string if\n  //!         serialization fails\n  //----------------------------------------------------------------------------\n  static std::string ResetFmdMgmInfo(const std::string& input);\n\n  //----------------------------------------------------------------------------\n  //! Move given file to orphans directory and also set its extended attribute\n  //! to reflect the original path to the file.\n  //!\n  //! @param fpath file to move\n  //----------------------------------------------------------------------------\n  static void MoveToOrphans(const std::string& fpath);\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  FmdHandler() = default;\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~FmdHandler() = default;\n\n  //----------------------------------------------------------------------------\n  //! Check if entry has a file checksum error\n  //!\n  //! @param lpath file local path\n  //! @param fsid file system identifier\n  //!\n  //! @return true if file has checksum error, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool FileHasXsError(const std::string& lpath,\n                              eos::common::FileSystem::fsid_t fsid);\n\n  // Meta data handling functions\n\n  //----------------------------------------------------------------------------\n  //! Return/create an Fmd struct for the given file/filesystem from the local\n  //! database\n  //!\n  //! @param fid file id\n  //! @param fsid filesystem id\n  //! @param force_retrieve get object even in the presence of inconsistencies\n  //! @param do_create if true create a non-existing Fmd if needed\n  //! @param uid user id of the caller\n  //! @param gid group id of the caller\n  //! @param layoutid layout id used to store during creation\n  //!\n  //! @return pointer to Fmd struct if successful, otherwise nullptr\n  //----------------------------------------------------------------------------\n  virtual std::unique_ptr<eos::common::FmdHelper>\n  LocalGetFmd(eos::common::FileId::fileid_t fid,\n              eos::common::FileSystem::fsid_t fsid,\n              bool force_retrieve = false, bool do_create = false,\n              uid_t uid = 0, gid_t gid = 0,\n              eos::common::LayoutId::layoutid_t layoutid = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Delete a record associated with fid and filesystem fsid\n  //!\n  //! @param fid file id\n  //! @param fsid filesystem id\n  //----------------------------------------------------------------------------\n  virtual void LocalDeleteFmd(eos::common::FileId::fileid_t fid,\n                              eos::common::FileSystem::fsid_t fsid,\n                              bool drop_file = false) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Commit modified Fmd record to the local database\n  //!\n  //! @param fmd pointer to Fmd\n  //!\n  //! @return true if record was committed, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool Commit(eos::common::FmdHelper* fmd,\n                      bool lockit = true,\n                      std::string* path = nullptr) = 0;\n\n\n  //----------------------------------------------------------------------------\n  //! Low level Fmd retrieve method\n  //!\n  //! @param fid file identifier\n  //! @param fsid file system identifier\n  //! @param path optional file path\n  //!\n  //! @return true if found and the corresponding FmdHelper object otherwise\n  //!         false\n  //----------------------------------------------------------------------------\n  virtual std::pair<bool, eos::common::FmdHelper>\n  LocalRetrieveFmd(eos::common::FileId::fileid_t fid,\n                   eos::common::FileSystem::fsid_t fsid,\n                   const std::string& path = \"\") = 0;\n\n  //----------------------------------------------------------------------------\n  //! Update file metadata object with new fid information\n  //!\n  //! @param path full path to file\n  //! @param fid new file identifier\n  //!\n  //! @return true if succesful, otherwise false\n  //----------------------------------------------------------------------------\n  bool UpdateFmd(const std::string& path, eos::common::FileId::fileid_t fid);\n\n  //----------------------------------------------------------------------------\n  //! Update local fmd with info from the disk i.e. physical file extended\n  //! attributes\n  //!\n  //! @param fsid file system id\n  //! @param fid  file id to update\n  //! @param disk_size size of the file on disk\n  //! @param disk_xs checksum of the file on disk\n  //! @param check_ts_sec time of the last check of that file\n  //! @param filexs_err indicator for file checksum error\n  //! @param blockxs_err inidicator for block checksum error\n  //! @param layout_err indication for layout error\n  //!\n  //! @return true if record has been committed\n  //----------------------------------------------------------------------------\n  bool UpdateWithDiskInfo(eos::common::FileSystem::fsid_t fsid,\n                          eos::common::FileId::fileid_t fid,\n                          unsigned long long disk_size,\n                          const std::string& disk_xs,\n                          unsigned long check_ts_sec, bool filexs_err,\n                          bool blockxs_err, bool layout_err);\n\n  //----------------------------------------------------------------------------\n  //! Update local fmd with info from the MGM\n  //!\n  //! @param fsid file system id\n  //! @param fid  file id to update\n  //! @param cid  container id\n  //! @param lid  layout id\n  //! @param mgmsize size of the file in the mgm namespace\n  //! @param mgmchecksum checksum of the file in the mgm namespace\n  //!\n  //! @return true if record has been committed\n  //----------------------------------------------------------------------------\n  bool UpdateWithMgmInfo(eos::common::FileSystem::fsid_t fsid,\n                         eos::common::FileId::fileid_t fid,\n                         eos::common::FileId::fileid_t cid,\n                         eos::common::LayoutId::layoutid_t lid,\n                         unsigned long long mgmsize,\n                         std::string mgmchecksum,\n                         uid_t uid, gid_t gid,\n                         unsigned long long ctime,\n                         unsigned long long ctime_ns,\n                         unsigned long long mtime,\n                         unsigned long long mtime_ns,\n                         int layouterror, std::string locations);\n\n  //----------------------------------------------------------------------------\n  //! Update local fmd with info from the stripe check\n  //!\n  //! @param fid file identifier\n  //! @param fsid file system id\n  //! @param invalid_stripes list of fsid with invalid stripes\n  //!\n  //----------------------------------------------------------------------------\n  void UpdateWithStripeCheckInfo(\n    eos::common::FileId::fileid_t fid, eos::common::FileSystem::fsid_t fsid,\n    const std::set<eos::common::FileSystem::fsid_t>& invalid_stripes);\n\n  //----------------------------------------------------------------------------\n  //! Update local fmd with info from the scanner\n  //!\n  //! @param fid file identifier\n  //! @param fsid file system id\n  //! @param fpath local file path\n  //! @param scan_sz size of the file computed by the scanner\n  //! @param scan_xs_hex hex checksum of the file computed by the scanner\n  //! @param qcl QClient used to communicate to QDB backend\n  //!\n  //! @note: the qclient should favor followers as we're doing only read\n  //!        operations and this should reduce the load on the master QDB\n  //----------------------------------------------------------------------------\n  void UpdateWithScanInfo(eos::common::FileId::fileid_t fid,\n                          eos::common::FileSystem::fsid_t fsid,\n                          const std::string& fpath,\n                          uint64_t scan_sz, const std::string& scan_xs_hex,\n                          std::shared_ptr<qclient::QClient> qcl);\n\n  //----------------------------------------------------------------------------\n  //! Clear errors on local fmd\n  //!\n  //! @param fid file identifier\n  //! @param fsid file system id\n  //! @param clear_stripe_err if true then clear only stripe errors otherwise,\n  //!        clear all the others\n  //----------------------------------------------------------------------------\n  void ClearErrors(eos::common::FileId::fileid_t fid,\n                   eos::common::FileSystem::fsid_t fsid,\n                   bool clear_stripe_err);\n\n  //----------------------------------------------------------------------------\n  //! Resync a single entry from disk\n  //!\n  //! @param fstpath file system location\n  //! @param fsid filesystem id\n  //! @param flaglayouterror indicates a layout error\n  //! @param scan_sz size of file computed by the scanner\n  //! @param scan_xs_hex hex checksum of the file computed by the scanner\n  //!\n  //! @return 0 if successful, otherwise errno\n  //----------------------------------------------------------------------------\n  int ResyncDisk(const char* fstpath,\n                 eos::common::FileSystem::fsid_t fsid,\n                 bool flaglayouterror, uint64_t scan_sz = 0ull,\n                 const std::string& scan_xs_hex = \"\");\n\n\n  //----------------------------------------------------------------------------\n  //! Resync files under path into local database\n  //!\n  //! @param path path to scan\n  //! @param fsid file system id\n  //! @param flaglayouterror flag to indicate a layout error\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ResyncAllDisk(const char* path,\n                     eos::common::FileSystem::fsid_t fsid,\n                     bool flaglayouterror);\n\n  //----------------------------------------------------------------------------\n  //! Resync file meta data from MGM into local database\n  //!\n  //! @param fsid filesystem id\n  //! @param fid file id\n  //! @param manager manager hostname\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ResyncMgm(eos::common::FileSystem::fsid_t fsid,\n                 eos::common::FileId::fileid_t fid, const char* manager);\n\n  //------------------------------------------------------------------------------\n  //! Resync file meta data from QuarkDB into local database\n  //!\n  //! @param fid file identifier\n  //! @param fsid file system identifier\n  //! @param fpath local file path\n  //! @param qcl QClient object used to connect to QuarkDB (this should have a\n  //!        preference to connect to followers as it's doing only read ops.)\n  //!\n  //! @return 0 if successful, otherwise errno\n  //------------------------------------------------------------------------------\n  int ResyncFileFromQdb(eos::common::FileId::fileid_t fid,\n                        eos::common::FileSystem::fsid_t fsid,\n                        const std::string& fpath,\n                        std::shared_ptr<qclient::QClient> qcl);\n\n  //----------------------------------------------------------------------------\n  //! Resync all meta data from QuarkdDB\n  //!\n  //! @param contact_details QDB contact details\n  //! @param fsid filesystem id\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ResyncAllFromQdb(const QdbContactDetails& contact_details,\n                        eos::common::FileSystem::fsid_t fsid);\n\nprivate:\n  //----------------------------------------------------------------------------\n  // Virtual private methods are overrideable in derived classes, this allows\n  // for the interface to remain the same while the specific implementation is\n  // done in the derived class\n  //----------------------------------------------------------------------------\n\n  //----------------------------------------------------------------------------\n  //! Attach Fmd metadata info to the current file identifier\n  //!\n  //! @param fmd file metadata info protobuf object\n  //! @param fid file identifier\n  //! @param fsid file system identifier\n  //! @param path local file absolute path\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool LocalPutFmd(const eos::common::FmdHelper& fmd,\n                           eos::common::FileId::fileid_t fid,\n                           eos::common::FileSystem::fsid_t fsid,\n                           const std::string& path = \"\") = 0;\n\n  //----------------------------------------------------------------------------\n  //!\n  //----------------------------------------------------------------------------\n  virtual bool ResetDiskInformation(eos::common::FileSystem::fsid_t fsid) = 0;\n\n  //----------------------------------------------------------------------------\n  //!\n  //----------------------------------------------------------------------------\n  virtual bool ResetMgmInformation(eos::common::FileSystem::fsid_t fsid) = 0;\n\n  // TODO: Technically we could hold move the mIsSyncing map & mutex to this class. Do\n  // this if AttrHandler also needs a syncing lock vs noop\n  virtual void SetSyncStatus(eos::common::FileSystem::fsid_t fsid,\n                             bool is_syncing) = 0;\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/filemd/FmdMgm.cc",
    "content": "#include \"fst/filemd/FmdMgm.hh\"\n#include \"fst/Config.hh\"\n#include \"common/ShellCmd.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/LayoutId.hh\"\n#include \"proto/FileMd.pb.h\"\n#include \"proto/ConsoleRequest.pb.h\"\n#include \"namespace/interface/IFileMD.hh\"\n#include <XrdCl/XrdClFileSystem.hh>\n\nnamespace\n{\n\n//------------------------------------------------------------------------------\n// Parses a comma-separated string of location IDs and adds them to a FileMdProto object\n//------------------------------------------------------------------------------\nvoid ParseLocations(std::string locations, eos::ns::FileMdProto& fmd)\n{\n  std::vector<std::string> location_vector;\n  eos::common::StringConversion::Tokenize(locations, location_vector, \",\");\n\n  for (const auto& elem : location_vector) {\n    if (elem.empty()) {\n      continue;\n    }\n\n    if (elem[0] == '!') {\n      fmd.add_unlink_locations(std::stoull(elem.c_str() + 1));\n    } else {\n      fmd.add_locations(std::stoull(elem.c_str()));\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Converts a hexadecimal string into its raw binary string representation\n//------------------------------------------------------------------------------\nstd::string ParseChecksum(const std::string& hexStr)\n{\n  size_t size;\n  auto xs = eos::common::StringConversion::Hex2BinDataChar(hexStr, size);\n\n  if (xs) {\n    return std::string(xs.release(), size);\n  }\n\n  return std::string();\n}\n\n//------------------------------------------------------------------------------\n// Constructs the binary representation of a timestamp\n//------------------------------------------------------------------------------\nstd::string ParseFileMDTime(XrdOucEnv& env, const char* key, const char* key_ns)\n{\n  char buff[sizeof(eos::IFileMD::ctime_t)];\n  eos::IFileMD::ctime_t time;\n  time.tv_sec = std::stoul(env.Get(key));\n  time.tv_nsec = std::stoul(env.Get(key_ns));\n  (void) memcpy(buff, &time, sizeof(time));\n  return std::string(buff);\n}\n}\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Exclude unlinked locations from the given string representation\n//------------------------------------------------------------------------------\nstd::string\nFmdMgmHandler::ExcludeUnlinkedLoc(const std::string& slocations)\n{\n  std::ostringstream oss;\n  std::vector<std::string> location_vector;\n  eos::common::StringConversion::Tokenize(slocations, location_vector, \",\");\n\n  for (const auto& elem : location_vector) {\n    if (!elem.empty() && elem[0] != '!') {\n      oss << elem << \",\";\n    }\n  }\n\n  return oss.str();\n}\n\n\n//------------------------------------------------------------------------------\n// Convert an MGM env representation to an Fmd struct\n//------------------------------------------------------------------------------\nbool\nFmdMgmHandler::EnvMgmToFmd(XrdOucEnv& env, eos::ns::FileMdProto& fmd)\n{\n  // &name=random&id=705&ctime=1738852302&ctime_ns=871468404&mtime=1738852312&\n  //mtime_ns=619640000&size=2117825536&cid=90&uid=0&gid=0&lid=543425858&flags=416&link=&location=9,2,1,6,7,3,\n  //&checksum=0e27ecd900000000000000000000000000000000000000000000000000000000&container=/eos/dev/rain/\n  // Check that all tags are present - except name which could be empty\n  if (!env.Get(\"id\") ||\n      !env.Get(\"cid\") ||\n      !env.Get(\"ctime\") ||\n      !env.Get(\"ctime_ns\") ||\n      !env.Get(\"mtime\") ||\n      !env.Get(\"mtime_ns\") ||\n      !env.Get(\"size\") ||\n      !env.Get(\"checksum\") ||\n      !env.Get(\"lid\") ||\n      !env.Get(\"uid\") ||\n      !env.Get(\"gid\") ||\n      !env.Get(\"location\")) {\n    return false;\n  }\n\n  try {\n    if (env.Get(\"name\")) {\n      fmd.set_name(env.Get(\"name\"));\n    }\n\n    if (env.Get(\"link\")) {\n      fmd.set_link_name(env.Get(\"link\"));\n    }\n\n    fmd.set_id(std::stoull(env.Get(\"id\")));\n    fmd.set_cont_id(std::stoull(env.Get(\"cid\")));\n    fmd.set_uid(std::stoull(env.Get(\"uid\")));\n    fmd.set_gid(std::stoull(env.Get(\"gid\")));\n    fmd.set_size(std::stoull(env.Get(\"size\")));\n    fmd.set_layout_id(std::stoul(env.Get(\"lid\")));\n    fmd.set_ctime(ParseFileMDTime(env, \"ctime\", \"ctime_ns\"));\n    fmd.set_mtime(ParseFileMDTime(env, \"mtime\", \"mtime_ns\"));\n    std::string xsVal = env.Get(\"checksum\");\n\n    if (xsVal != \"none\") {\n      fmd.set_checksum(ParseChecksum(xsVal));\n    } else {\n      fmd.set_checksum(\"\");\n    }\n\n    ParseLocations(env.Get(\"location\"), fmd);\n  } catch (...) {\n    // not valid\n    return false;\n  }\n\n  return true;\n}\n\n//----------------------------------------------------------------------------\n// Convert namespace file proto object to an Fmd struct\n//----------------------------------------------------------------------------\nbool\nFmdMgmHandler::NsFileProtoToFmd(eos::ns::FileMdProto&& filemd,\n                                eos::common::FmdHelper& fmd)\n{\n  fmd.mProtoFmd.set_fid(filemd.id());\n  fmd.mProtoFmd.set_cid(filemd.cont_id());\n  eos::IFileMD::ctime_t ctime;\n  (void) memcpy(&ctime, filemd.ctime().data(), sizeof(ctime));\n  eos::IFileMD::ctime_t mtime;\n  (void) memcpy(&mtime, filemd.mtime().data(), sizeof(mtime));\n  fmd.mProtoFmd.set_ctime(ctime.tv_sec);\n  fmd.mProtoFmd.set_ctime_ns(ctime.tv_nsec);\n  fmd.mProtoFmd.set_mtime(mtime.tv_sec);\n  fmd.mProtoFmd.set_mtime_ns(mtime.tv_nsec);\n  fmd.mProtoFmd.set_mgmsize(filemd.size());\n  fmd.mProtoFmd.set_lid(filemd.layout_id());\n  fmd.mProtoFmd.set_uid(filemd.uid());\n  fmd.mProtoFmd.set_gid(filemd.gid());\n  std::string str_xs;\n  uint8_t size = filemd.checksum().size();\n\n  for (uint8_t i = 0; i < size; i++) {\n    char hx[3];\n    hx[0] = 0;\n    snprintf(static_cast<char*>(hx), sizeof(hx), \"%02x\",\n             *(unsigned char*)(filemd.checksum().data() + i));\n    str_xs += static_cast<char*>(hx);\n  }\n\n  size_t cslen = eos::common::LayoutId::GetChecksumLen(filemd.layout_id()) * 2;\n  // Truncate the checksum to the right string length\n  str_xs.erase(std::min(str_xs.length(), cslen));\n  fmd.mProtoFmd.set_mgmchecksum(str_xs);\n  std::string slocations;\n\n  for (const auto& loc : filemd.locations()) {\n    slocations += std::to_string(loc);\n    slocations += \",\";\n  }\n\n  if (!slocations.empty()) {\n    slocations.pop_back();\n  }\n\n  fmd.mProtoFmd.set_locations(slocations);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Return Fmd from MGM doing getfmd command\n//------------------------------------------------------------------------------\nint\nFmdMgmHandler::GetMgmFmd(const std::string& manager,\n                         eos::common::FileId::fileid_t fid,\n                         eos::ns::FileMdProto& fmd)\n{\n  if (!fid) {\n    return EINVAL;\n  }\n\n  int rc = 0;\n  std::string mgr;\n  XrdCl::Buffer arg;\n  std::string query = SSTR(\"/?mgm.pcmd=getfmd&mgm.getfmd.fid=\" << fid).c_str();\n  std::unique_ptr<XrdCl::Buffer> response;\n  XrdCl::Buffer* resp_raw = nullptr;\n  XrdCl::XRootDStatus status;\n\n  do {\n    mgr = manager;\n\n    if (mgr.empty()) {\n      mgr = gConfig.GetManager();\n\n      if (mgr.empty()) {\n        eos_static_err(\"msg=\\\"no manager info available\\\"\");\n        return EINVAL;\n      }\n    }\n\n    std::string address = SSTR(\"root://\" << mgr <<\n                               \"//dummy?xrd.wantprot=sss\").c_str();\n    XrdCl::URL url(address.c_str());\n\n    if (!url.IsValid()) {\n      eos_static_err(\"msg=\\\"invalid URL=%s\\\"\", address.c_str());\n      return EINVAL;\n    }\n\n    std::unique_ptr<XrdCl::FileSystem> fs {new XrdCl::FileSystem(url)};\n\n    if (!fs) {\n      eos_static_err(\"%s\", \"msg=\\\"failed to allocate FS object\\\"\");\n      return EINVAL;\n    }\n\n    arg.FromString(query.c_str());\n    uint16_t timeout = 10;\n    status = fs->Query(XrdCl::QueryCode::OpaqueFile, arg, resp_raw, timeout);\n    response.reset(resp_raw);\n    resp_raw = nullptr;\n\n    if (status.IsOK()) {\n      rc = 0;\n      eos_static_debug(\"msg=\\\"got metadata from mgm\\\" manager=%s fxid=%08llx\",\n                       mgr.c_str(), fid);\n    } else {\n      eos_static_err(\"msg=\\\"query error\\\" fxid=%08llx status=%d code=%d\", fid,\n                     status.status, status.code);\n\n      if ((status.code >= 100) &&\n          (status.code <= 300)) {\n        std::this_thread::sleep_for(std::chrono::seconds(1));\n        eos_static_info(\"msg=\\\"retry query\\\" fxid=%08llx query=\\\"%s\\\"\", fid,\n                        query.c_str());\n      } else {\n        eos_static_err(\"msg=\\\"failed to retrieve metadata from mgm\\\" manager=%s \"\n                       \"fxid=%08llx\", mgr.c_str(), fid);\n        rc = ECOMM;\n      }\n    }\n  } while ((status.code >= 100) && (status.code <= 300));\n\n  if (rc) {\n    return EIO;\n  }\n\n  // Check if response contains any data\n  if (!response->GetBuffer()) {\n    eos_static_err(\"msg=\\\"empty response buffer\\\" manager=%s fxid=%08llx\",\n                   mgr.c_str(), fid);\n    return ENODATA;\n  }\n\n  std::string sresult = response->GetBuffer();\n  std::string search_tag = \"getfmd: retc=0 \";\n\n  if ((sresult.find(search_tag)) == std::string::npos) {\n    eos_static_info(\"msg=\\\"no metadata info at the mgm\\\" manager=%s fxid=%08llx \"\n                    \" resp_buff=\\\"%s\\\"\", mgr.c_str(), fid, response->GetBuffer());\n    return ENODATA;\n  } else {\n    sresult.erase(0, search_tag.length());\n  }\n\n  // Get the remote file meta data into an env hash\n  XrdOucEnv fmd_env(sresult.c_str());\n\n  if (!EnvMgmToFmd(fmd_env, fmd)) {\n    int envlen;\n    eos_static_err(\"msg=\\\"failed to parse metadata info\\\" data=\\\"%s\\\" fxid=%08llx\",\n                   fmd_env.Env(envlen), fid);\n    return EIO;\n  }\n\n  if (fmd.id() != fid) {\n    eos_static_err(\"msg=\\\"received wrong meta data from mgm\\\" fxid=%08llx \"\n                   \"recv_fxid=%08llx\", fmd.id(), fid);\n    return EIO;\n  }\n\n  return 0;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/filemd/FmdMgm.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file FmdMgm.hh\n//! @author Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#pragma once\n\n#include \"fst/Namespace.hh\"\n#include \"common/Fmd.hh\"\n\nnamespace eos::ns\n{\nclass FileMdProto;\n}\n\nEOSFSTNAMESPACE_BEGIN\n\nclass FmdMgmHandler\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Convert an FST env representation to an Fmd struct\n  //!\n  //! @param env env representation\n  //! @param fmd reference to Fmd struct\n  //!\n  //! @return true if successful otherwise false\n  //----------------------------------------------------------------------------\n  static bool EnvMgmToFmd(XrdOucEnv& env, eos::ns::FileMdProto& fmd);\n\n  //----------------------------------------------------------------------------\n  //! Convert namespace file proto md to an Fmd struct\n  //!\n  //! @param file namespace file proto object\n  //! @param fmd reference to Fmd struct\n  //!\n  //! @return true if successful otherwise false\n  //----------------------------------------------------------------------------\n  static bool NsFileProtoToFmd(eos::ns::FileMdProto&& filemd,\n                               eos::common::FmdHelper& fmd);\n\n  //----------------------------------------------------------------------------\n  //! Return Fmd from MGM doing getfmd command\n  //!\n  //! @parm manager manager hostname:port\n  //! @param fid file id\n  //! @param fmd reference to the Fmd struct to store Fmd\n  //!\n  //! @return 0 if successful, otherwise errno\n  //----------------------------------------------------------------------------\n  static int GetMgmFmd(const std::string& manager,\n                       eos::common::FileId::fileid_t fid,\n                       eos::ns::FileMdProto& fmd);\n\n  //----------------------------------------------------------------------------\n  //! Exclude unlinked locations from the given string representation\n  //!\n  //! @param slocations string of locations separated by commad with unlinked\n  //!        locations having an ! in front\n  //!\n  //! @return string with the linked locations excluded\n  //----------------------------------------------------------------------------\n  static std::string ExcludeUnlinkedLoc(const std::string& slocations);\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/http/HttpHandler.cc",
    "content": "// ----------------------------------------------------------------------\n// File: HttpHandler.cc\n// Author: Andreas-Joachim Peters & Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/http/HttpHandler.hh\"\n#include \"fst/http/HttpServer.hh\"\n#include \"fst/checksum/Adler.hh\"\n#include \"common/Path.hh\"\n#include \"common/Timing.hh\"\n#include \"common/http/HttpResponse.hh\"\n#include \"common/http/OwnCloud.hh\"\n#include \"common/http/PlainHttpResponse.hh\"\n#include \"common/http/MimeTypes.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/XrdFstOfsFile.hh\"\n#include <XrdSys/XrdSysPthread.hh>\n#include <XrdSfs/XrdSfsInterface.hh>\n#include <algorithm>\n\nEOSFSTNAMESPACE_BEGIN\n\nXrdSysMutex HttpHandler::mOpenMutexMapMutex;\nstd::map<unsigned short, XrdSysMutex*> HttpHandler::mOpenMutexMap;\neos::common::MimeTypes HttpHandler::gMime;\nHttpHandlerFstFileCache HttpHandler::sFileCache;\nstatic constexpr const char* HTTP_TIDENT= \"http\";\n\n/*----------------------------------------------------------------------------*/\nHttpHandler::~HttpHandler()\n{\n  if (mFile) {\n    delete mFile;\n    mFile = nullptr;\n  }\n  if (mClient.name) {\n    free(mClient.name);\n    mClient.name = nullptr;\n  }\n  if (mClient.host) {\n    free(mClient.host);\n    mClient.host = nullptr;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nHttpHandler::Matches(const std::string& meth, HeaderMap& headers)\n{\n  int method = ParseMethodString(meth);\n\n  // We only support GET, HEAD and PUT on the FST (CREATE is used by XrdHttp)\n  if (method == GET || method == HEAD || method == PUT || method == CREATE) {\n    eos_static_info(\"%s\", \"msg=\\\"Matched HTTP protocol for request\\\"\");\n    return true;\n  } else {\n    return false;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nHttpHandler::HandleRequest(eos::common::HttpRequest* request)\n{\n  eos_static_debug(\"Handling HTTP request\");\n\n  if (!mFile) {\n    Initialize(request);\n  }\n\n  if (!mFile) {\n    // default modes are for GET=read\n    XrdSfsFileOpenMode open_mode = 0;\n    mode_t create_mode = 0;\n    XrdOucString openUrl = request->GetUrl().c_str();\n    XrdOucString query = request->GetQuery().c_str();\n\n    if (request->GetHeaders().count(\"x-upload-range\")) {\n      // we need to indicate to XrdFstOfsFile that this is a partial upload\n      query += \"&x-upload-range=\";\n      query += request->GetHeaders()[\"x-upload-range\"].c_str();\n    }\n\n    if ((request->GetMethod() == \"PUT\") ||\n        (request->GetMethod() == \"CREATE\")) {\n      // use the proper creation/open flags for PUT's\n      open_mode |= SFS_O_CREAT;\n\n      if (EOS_LOGS_DEBUG) {\n        for (auto it = request->GetHeaders().begin(); it != request->GetHeaders().end();\n             ++it) {\n          eos_static_debug(\"header %s <=> %s\", it->first.c_str(), it->second.c_str());\n        }\n      }\n\n      // Avoid truncation of chunked uploads\n      if (!request->GetHeaders().count(\"oc-chunked\") &&\n          !request->GetHeaders().count(\"x-upload-range\")) {\n        open_mode |= SFS_O_TRUNC;\n      } else {\n        eos_static_info(\"%s\", \"msg=\\\"removing truncation flag\\\"\");\n      }\n\n      open_mode |= SFS_O_RDWR;\n      open_mode |= SFS_O_MKPTH;\n      create_mode |= (SFS_O_MKPTH | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);\n    }\n\n    // if we are opening for reading see if we already have an opened file\n    // in the cache\n    HttpHandlerFstFileCache::Key cachekey(mClient.name, openUrl.c_str(),\n                                          query.c_str(), open_mode);\n    mFileCacheEntry.clear();\n\n    if (open_mode == 0) {\n      mFileCacheEntry = sFileCache.remove(cachekey);\n\n      if ((mFile = mFileCacheEntry.getfp())) {\n        eos_static_debug(\"path=%s found in open-file cache fp=%p\",\n                         openUrl.c_str(), mFile);\n        mRc = SFS_OK;\n      }\n    }\n\n    // if no cached file open a new one\n    if (!mFile) {\n      XrdSysMutex* hMutex = 0;\n      {\n        // use path dependent locks for opens\n        // we will accumulate up to 64k mutexes for this\n        Adler lHash;\n        lHash.Add(openUrl.c_str(), openUrl.length(), 0);\n        lHash.Finalize();\n        const unsigned short cks = lHash.GetAdler() % 65521;\n        {\n          XrdSysMutexHelper oLock(mOpenMutexMapMutex);\n\n          if (!mOpenMutexMap.count(cks)) {\n            hMutex = new XrdSysMutex();\n            mOpenMutexMap[cks] = hMutex;\n          } else {\n            hMutex = mOpenMutexMap[cks];\n          }\n        }\n      }\n      mFile = (XrdFstOfsFile*) gOFS.newFile(mClient.name);\n      {\n        XrdSysMutexHelper oLock(*hMutex);\n        mRc = mFile->open(openUrl.c_str(),\n                          open_mode,\n                          create_mode,\n                          &mClient,\n                          query.c_str());\n      }\n    }\n\n    mFileSize = mFile->GetOpenSize();\n    mFileId = mFile->GetFileId();\n    mLogId = mFile->logId;\n\n    // check for range requests\n    if (request->GetHeaders().count(\"range\")) {\n      if (!DecodeByteRange(request->GetHeaders()[\"range\"],\n                           mOffsetMap,\n                           mRangeRequestSize,\n                           mFileSize)) {\n        // indicate range decoding error\n        mRangeDecodingError = true;\n      } else {\n        mRangeRequest = true;\n      }\n    }\n\n    // if this file wasn't in opened-file cache and it's for reading\n    // in a range request, save it to the cache once we're finished.\n    // (perhaps we'll soon have another read-range request for the same file)\n    if (open_mode == 0 && mRc == SFS_OK &&\n        mRangeRequest && mFileCacheEntry.getfp() != mFile) {\n      eos_static_debug(\"path=%s eligible to be saved in open-file cache fp=%p\",\n                       openUrl.c_str(), mFile);\n      mFileCacheEntry.set(cachekey, mFile);\n    }\n\n    // check for range requests\n    if (request->GetHeaders().count(\"x-upload-range\") &&\n        request->GetHeaders().count(\"x-upload-totalsize\")) {\n      if (!DecodeByteRange(request->GetHeaders()[\"x-upload-range\"],\n                           mOffsetMap,\n                           mRangeRequestSize,\n                           std::stoul(request->GetHeaders()[\"x-upload-totalsize\"]))) {\n        // indicate range decoding error\n        mRangeDecodingError = true;\n      } else {\n        mRangeRequest = true;\n      }\n    }\n\n    if (!mRangeRequest) {\n      // we put the file size as request size if this is not a range request\n      // aka full file download\n      mRangeRequestSize = mFile->GetOpenSize();\n    }\n  }\n\n  delete mHttpResponse;\n  mHttpResponse = 0;\n\n  if (request->GetMethod() == \"GET\") {\n    // call the HttpHandler::Get method\n    mHttpResponse = Get(request);\n  }\n\n  if (request->GetMethod() == \"HEAD\") {\n    mHttpResponse = Head(request);\n  }\n\n  if (request->GetMethod() == \"CREATE\") {\n    // fake method for XrdHttp bridge\n    mHttpResponse = new eos::common::PlainHttpResponse();\n    mHttpResponse->SetResponseCode(0);\n    return;\n  }\n\n  if (request->GetMethod() == \"PUT\") {\n    if (((mUploadLeftSize > (1 * 1024 * 1024)) &&\n         ((*request->GetBodySize()) < (1 * 1024 * 1024)))) {\n      // we want more bytes, we don't process this\n      eos_static_debug(\"msg=\\\"wait for more bytes\\\" leftsize=%llu uploadsize=%llu\",\n                       mUploadLeftSize, *request->GetBodySize());\n      mHttpResponse = new eos::common::PlainHttpResponse();\n      mHttpResponse->SetResponseCode(0);\n      return;\n    }\n\n    mHttpResponse = Put(request);\n\n    if (!mHttpResponse || (*request->GetBodySize()) == 0) {\n      // clean-up left-over objects on error or end-of-put\n      if (mFile) {\n        delete mFile;\n        mFile = 0;\n      }\n    }\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nHttpHandler::Initialize(eos::common::HttpRequest* request)\n{\n  if (request->GetCookies().count(\"EOSCAPABILITY\")) {\n    // if we have a capability we don't use the query CGI but that one\n    request->SetQuery(request->GetCookies()[\"EOSCAPABILITY\"]);\n  }\n\n  if (request->GetHeaders().count(\"content-length\")) {\n    mContentLength = strtoull(request->GetHeaders()[\"content-length\"].c_str(),\n                              0, 10);\n    mUploadLeftSize = mContentLength;\n  }\n\n  std::string query = request->GetQuery();\n  HttpServer::DecodeURI(query); // unescape '+' '/' '='\n  request->SetQuery(query);\n  eos_static_debug(\"path=%s query=%s\", request->GetUrl().c_str(),\n                   request->GetQuery().c_str());\n  // define the client sec entity object\n  strncpy(mClient.prot, \"unix\", XrdSecPROTOIDSIZE - 1);\n  mClient.prot[XrdSecPROTOIDSIZE - 1] = '\\0';\n  mClient.name = strdup(\"nobody\");\n  mClient.host = strdup(\"localhost\");\n  mClient.tident = HTTP_TIDENT;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nHttpHandler::Get(eos::common::HttpRequest* request)\n{\n  eos::common::HttpResponse* response = nullptr;\n\n  if (mRangeDecodingError) {\n    mErrCode = eos::common::HttpResponse::REQUESTED_RANGE_NOT_SATISFIABLE;\n    mErrText = \"Illegal Range request\";\n    response = HttpServer::HttpError(mErrText.c_str(), mErrCode);\n  } else {\n    if (mErrCode) {\n      eos_static_err(\"msg=\\\"return stored error\\\" errc=%d errmsg=\\\"%s\\\"\", mErrCode,\n                     mErrText.c_str());\n      response = HttpServer::HttpError(mErrText.c_str(), mErrCode);\n      return response;\n    }\n\n    if (mRc != SFS_OK) {\n      if (mRc == SFS_REDIRECT) {\n        mErrCode = eos::common::HttpResponse::INTERNAL_SERVER_ERROR;\n        mErrText = mFile->error.getErrText();\n        response = HttpServer::HttpError(mErrText.c_str(), mErrCode);\n      } else if (mRc == SFS_ERROR) {\n        mErrCode = mFile->error.getErrInfo();\n        mErrText = mFile->error.getErrText();\n        response = HttpServer::HttpError(mErrText.c_str(), mErrCode);\n      } else if (mRc == SFS_DATA) {\n        response = HttpServer::HttpData(mFile->error.getErrText(),\n                                        mFile->error.getErrInfo());\n      } else if (mRc == SFS_STALL) {\n        response = HttpServer::HttpStall(mFile->error.getErrText(),\n                                         mFile->error.getErrInfo());\n      } else {\n        response = HttpServer::HttpError(\"unexpected result from file open\",\n                                         EOPNOTSUPP);\n      }\n\n      delete mFile;\n      mFile = 0;\n    } else {\n      response = new eos::common::PlainHttpResponse();\n\n      if (mRangeRequest) {\n        CreateMultipartHeader(\"application/octet-stream\");\n        eos_static_debug(Print());\n        char clength[16];\n        snprintf(clength, sizeof(clength) - 1, \"%llu\",\n                 (unsigned long long) mRequestSize);\n\n        if (mOffsetMap.size() == 1) {\n          // if there is only one range we don't send a multipart response\n          response->AddHeader(\"Content-Type\", gMime.Match(request->GetUrl()));\n          response->AddHeader(\"Content-Range\", mSinglepartHeader);\n        } else {\n          // for several ranges we send a multipart response\n          response->AddHeader(\"Content-Type\", mMultipartHeader);\n        }\n\n        response->AddHeader(\"Content-Length\", clength);\n        response->mResponseLength = mRequestSize;\n        response->SetResponseCode(eos::common::HttpResponse::PARTIAL_CONTENT);\n      } else {\n        // successful http open\n        char clength[16];\n        snprintf(clength, sizeof(clength) - 1, \"%llu\",\n                 (unsigned long long) mFile->GetOpenSize());\n        mRequestSize = mFile->GetOpenSize();\n        response->mResponseLength = mRequestSize;\n        response->AddHeader(\"Content-Type\", gMime.Match(request->GetUrl()));\n        response->AddHeader(\"Content-Length\", clength);\n\n        // retrieve a checksum when file is still open\n        if (mFile->GetChecksum()) {\n          std::string checksum_name = mFile->GetChecksum()->GetName();\n          std::string checksum_val = mFile->GetFmdChecksum();\n\n          while (checksum_val[0] == '0') {\n            checksum_val.erase(0, 1);\n          }\n\n          std::string checksum_string = eos::common::OwnCloud::GetChecksumString(\n                                          checksum_name,\n                                          checksum_val);\n          response->AddHeader(\"OC-Checksum\", checksum_string);\n          const auto& hdrs = request->GetHeaders();\n          auto it = hdrs.find(\"want-digest\");\n\n          if (it != hdrs.end()) {\n            // According to RFC 3230 the Digest reponse needs to have the\n            // following format:\n            // instance-digest = digest-algorithm \"=\" <encoded digest output>\n            std::replace(checksum_string.begin(), checksum_string.end(),\n                         ':', '=');\n            response->AddHeader(\"Digest\", checksum_string);\n          }\n        }\n      }\n\n      std::string query = request->GetQuery();\n\n      if (query.find(\"mgm.etag\") != std::string::npos) {\n        XrdOucEnv queryenv(query.c_str());\n        const char* etag = 0;\n\n        if ((etag = queryenv.Get(\"mgm.etag\"))) {\n          response->AddHeader(\"ETag\", etag);\n        }\n\n        if (mRangeRequest) {\n          response->SetResponseCode(eos::common::HttpResponse::PARTIAL_CONTENT);\n        } else {\n          response->SetResponseCode(eos::common::HttpResponse::OK);\n        }\n      }\n    }\n  }\n\n  if (mFile) {\n    time_t mtime = mFile->GetMtime();\n    response->AddHeader(\"Last-Modified\", eos::common::Timing::utctime(mtime));\n    // We want to use the file callbacks\n    response->mUseFileReaderCallback = true;\n  }\n\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nHttpHandler::Head(eos::common::HttpRequest* request)\n{\n  eos::common::HttpResponse* response = Get(request);\n  response->SetBody(\"\");\n  response->mUseFileReaderCallback = false;\n\n  if (mFile) {\n    FileClose(CanCache::NO);\n    delete mFile;\n    mFile = 0;\n  }\n\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nHttpHandler::Put(eos::common::HttpRequest* request)\n{\n  eos_static_info(\"method=PUT offset=%llu size=%llu size_ptr=%llu range-map-size:%u\",\n                  mCurrentCallbackOffset,\n                  request->GetBodySize() ? *request->GetBodySize() : 0,\n                  request->GetBodySize(),\n                  mOffsetMap.size());\n  eos::common::HttpResponse* response = nullptr;\n  bool checksumError = false;\n  bool checksumMatch = false;\n\n  if (mRangeDecodingError) {\n    mErrCode = eos::common::HttpResponse::REQUESTED_RANGE_NOT_SATISFIABLE;\n    mErrText = \"Illegal Range request\";\n  } else {\n    if (mRangeRequest) {\n      auto it = mOffsetMap.begin();\n\n      if ((it->second) != (off_t)std::stoul(\n            request->GetHeaders()[\"content-length\"])) {\n        mErrCode = eos::common::HttpResponse::REQUESTED_RANGE_NOT_SATISFIABLE;\n        mErrText = \"Illegal Range request - not matching content length\";\n        eos_static_err(\"range: [%lu:%lu] content-length: %lu\", it->first, it->second,\n                       std::stoul(request->GetHeaders()[\"content-length\"]));\n      }\n    }\n  }\n\n  if (mErrCode) {\n    eos_static_err(\"msg=\\\"return stored error\\\" errc=%d errmsg=\\\"%s\\\"\", mErrCode,\n                   mErrText.c_str());\n    response = HttpServer::HttpError(mErrText.c_str(), mErrCode);\n    delete mFile;\n    mFile = 0;\n    return response;\n  }\n\n  if (mRc) {\n    if (mRc != SFS_OK) {\n      if (mRc == SFS_REDIRECT) {\n        // we cannot redirect the PUT at this point, just send an error back\n        mErrCode = eos::common::HttpResponse::INTERNAL_SERVER_ERROR;\n        mErrText = mFile->error.getErrText();\n        response = HttpServer::HttpError(mErrText.c_str(), mErrCode);\n      } else if (mRc == SFS_ERROR) {\n        mErrCode = mFile->error.getErrInfo();\n        mErrText = mFile->error.getErrText();\n        response = HttpServer::HttpError(mErrText.c_str(), mErrCode);\n      } else if (mRc == SFS_DATA) {\n        response = HttpServer::HttpData(mFile->error.getErrText(),\n                                        mFile->error.getErrInfo());\n      } else if (mRc == SFS_STALL) {\n        response = HttpServer::HttpStall(mFile->error.getErrText(),\n                                         mFile->error.getErrInfo());\n      } else {\n        response = HttpServer::HttpError(\"unexpected result from file open\",\n                                         EOPNOTSUPP);\n      }\n\n      delete mFile;\n      mFile = 0;\n      return response;\n    }\n  } else {\n    // check for chunked uploads\n    if (!mCurrentCallbackOffset && request->GetHeaders().count(\"oc-chunked\")) {\n      int chunk_n = 0;\n      int chunk_max = 0;\n      XrdOucString chunk_uuid;\n\n      if ((!request->GetHeaders().count(\"cbox-chunked-android-issue-900\")) &&\n          (!eos::common::OwnCloud::getContentSize(request))) {\n        // -------------------------------------------------------\n        // WARNING:\n        // there is buggy ANDROID client not providing this header\n        // but we let it pass if a special cbox header allows a\n        // bypass\n        // -------------------------------------------------------\n        mErrCode = eos::common::HttpResponse::BAD_REQUEST;\n        mErrText = \"Missing total length in OC request\";\n        response = HttpServer::HttpError(mErrText.c_str(), mErrCode);\n        delete mFile;\n        mFile = 0;\n        return response;\n      }\n\n      eos::common::OwnCloud::GetChunkInfo(request->GetQuery().c_str(),\n                                          chunk_n, chunk_max, chunk_uuid);\n\n      if (chunk_n >= chunk_max) {\n        // there is something inconsistent here\n        // HTTP write error\n        mErrCode = eos::common::HttpResponse::BAD_REQUEST;\n        mErrText = \"Illegal chunks specified in OC request\";\n        response = HttpServer::HttpError(mErrText.c_str(), mErrCode);\n        delete mFile;\n        mFile = 0;\n        return response;\n      }\n\n      unsigned long long contentlength =\n        eos::common::StringConversion::GetSizeFromString(\n          request->GetHeaders()[\"content-length\"]);\n\n      // recompute offset where to write\n      if ((chunk_n + 1) < chunk_max) {\n        // the first n-1 chunks have a straight forward offset\n        mCurrentCallbackOffset = contentlength * chunk_n;\n        eos_static_debug(\"setting to false %lld\", mCurrentCallbackOffset);\n        mLastChunk = false;\n      } else {\n        // -------------------------------------------------------\n        // WARNING:\n        // there is buggy ANDROID client not providing this header\n        // in this case we have to assume 1MB chunks\n        // -------------------------------------------------------\n        // the last chunks has to be written at offset=total-length - chunk-length\n        if (eos::common::StringConversion::GetSizeFromString(\n              eos::common::OwnCloud::getContentSize(request))) {\n          mCurrentCallbackOffset =\n            eos::common::StringConversion::GetSizeFromString(\n              eos::common::OwnCloud::getContentSize(request));\n          mCurrentCallbackOffset -= contentlength;\n        } else {\n          mCurrentCallbackOffset = (chunk_n * (1 * 1024 * 1000)); // ANDROID client\n        }\n\n        eos_static_debug(\"setting to true %lld\", mCurrentCallbackOffset);\n        mLastChunk = true;\n      }\n    }\n\n    // check for content range PUT\n    if (mOffsetMap.size() == 1) {\n      auto it = mOffsetMap.begin();\n\n      // there is a range header\n      if (mUploadLeftSize == mContentLength) {\n        // place the offset to the initial range\n        mCurrentCallbackOffset = it->first;\n      }\n\n      if (!mUploadLeftSize &&\n          ((off_t) std::stoul(request->GetHeaders()[\"x-upload-totalsize\"]) ==\n           mCurrentCallbackOffset)) {\n        mLastChunk = true;\n      }\n\n      if (!mUploadLeftSize) {\n        if (request->GetHeaders()[\"x-upload-done\"] == \"true\") {\n          mLastChunk = true;\n        }\n\n        if (request->GetHeaders()[\"x-upload-done\"] == \"false\") {\n          mLastChunk = false;\n        }\n      }\n\n      eos_static_debug(\"c-offset=%lu body-size=%lu ranget-offset=%lu range-size=%lu last-chunk=%d\",\n                       mCurrentCallbackOffset, *request->GetBodySize(), it->first, it->second,\n                       mLastChunk);\n    }\n\n    // File streaming in\n    size_t* bodySize = request->GetBodySize();\n\n    if (request->GetBody().c_str() && bodySize && (*bodySize)) {\n      size_t stored = mFile->write(mCurrentCallbackOffset,\n                                   request->GetBody().c_str(),\n                                   *request->GetBodySize());\n\n      if (stored != *request->GetBodySize()) {\n        eos_static_err(\"stored %lu of %lu bytes\", stored, *request->GetBodySize());\n        // HTTP write error\n        mErrCode = eos::common::HttpResponse::INTERNAL_SERVER_ERROR;\n        mErrText = \"Write error occured\";\n        response = HttpServer::HttpError(mErrText.c_str(), mErrCode);\n        delete mFile;\n        mFile = 0;\n        return response;\n      } else {\n        eos_static_info(\"msg=\\\"stored requested bytes\\\" offset=%lld\",\n                        mCurrentCallbackOffset);\n        // decrease the upload left data size\n        mUploadLeftSize -= *request->GetBodySize();\n        mCurrentCallbackOffset += *request->GetBodySize();\n        response = new eos::common::PlainHttpResponse();\n        std::string query = request->GetQuery();\n        XrdOucEnv queryenv(query.c_str());\n        const char* etag = 0;\n\n        if ((etag = queryenv.Get(\"mgm.etag\"))) {\n          response->AddHeader(\"ETag\", etag);\n        }\n\n        response->SetResponseCode(eos::common::HttpResponse::CREATED);\n        return response;\n      }\n    } else {\n      eos_static_info(\"entering close handler\");\n      eos::common::HttpRequest::HeaderMap header = request->GetHeaders();\n\n      if (header.count(\"x-upload-mtime\")) {\n        header[\"x-oc-mtime\"] = header[\"x-upload-mtime\"];\n      }\n\n      if (mOffsetMap.size()) {\n        header[\"oc-chunked\"] = \"true\";\n      }\n\n      if (header.count(\"x-oc-mtime\")) {\n        // there is an X-OC-Mtime header to force the mtime for that file\n        mFile->SetForcedMtime(strtoull(header[\"x-oc-mtime\"].c_str(), 0, 10), 0);\n      }\n\n      if ((!mLastChunk) && (header.count(\"oc-chunked\"))) {\n        // WARNING: this assumes that the last chunk is the last uploaded\n        std::string cmd = \"nochecksum\";\n\n        if (mFile->fctl(SFS_FCTL_SPEC1, cmd.length(), cmd.c_str(), 0)) {\n          mErrCode = eos::common::HttpResponse::INTERNAL_SERVER_ERROR;\n          mErrText = \"Failed to disable checksum\";\n          response = HttpServer::HttpError(mErrText.c_str(), mErrCode);\n          delete mFile;\n          mFile = 0;\n          return response;\n        }\n      } else {\n        if (mFile->GetChecksum()) {\n          // retrieve a checksum when file is still open\n          eos_static_debug(\"enabled checksum lastchunk=%d checksum=%x\", mLastChunk,\n                           mFile->GetChecksum());\n          // Call explicitly the checksum verification\n          mFile->VerifyChecksum();\n\n          if (mFile->GetChecksum()) {\n            std::string checksum_name = mFile->GetChecksum()->GetName();\n            std::string checksum_val = mFile->GetChecksum()->GetHexChecksum();\n\n            while (checksum_val[0] == '0') {\n              checksum_val.erase(0, 1);\n            }\n\n            eos::common::OwnCloud::checksum_t checksum = std::make_pair(\n                  checksum_name,\n                  checksum_val);\n            // inspect if there is checksum provided\n            eos::common::OwnCloud::checksum_t client_checksum =\n              eos::common::OwnCloud::GetChecksum(request,\n                                                 header.count(\"x-upload-checksum\") ? \"x-upload-checksum\" : \"oc-checksum\");\n            eos_static_debug(\"client-checksum-type=%s client-checksum-value=%s \"\n                             \"server-checksum-type=%s server-checksum-value=%s\",\n                             client_checksum.first.c_str(),\n                             client_checksum.second.c_str(),\n                             checksum.first.c_str(),\n                             checksum.second.c_str());\n\n            if (client_checksum.first != \"\") {\n              if (client_checksum.first == checksum.first) {\n                // compare only if the algorithm is the same\n                if (client_checksum.second != checksum.second) {\n                  eos_static_err(\"msg=\\\"invalid checksum\\\" client-checksum-type=%s\"\n                                 \" client-checksum-value=%s server-checksum-type=%s\"\n                                 \" server-checksum-value=%s\",\n                                 client_checksum.first.c_str(),\n                                 client_checksum.second.c_str(),\n                                 checksum.first.c_str(), checksum.second.c_str());\n                  checksumError = true;\n                }\n\n                checksumMatch = true;\n              } else {\n                eos_static_warning(\"msg=\\\"client required different checksum\\\" \"\n                                   \"client-checksum-type=%s client-checksum-value=%s \"\n                                   \"server-checksum-type=%s server-checksum-value=%s\",\n                                   client_checksum.first.c_str(),\n                                   client_checksum.second.c_str(),\n                                   checksum.first.c_str(),\n                                   checksum.second.c_str());\n              }\n            }\n          }\n        }\n      }\n\n      if (checksumError) {\n        response = new eos::common::PlainHttpResponse();\n        response->SetResponseCode(eos::common::HttpResponse::PRECONDITION_FAILED);\n        delete mFile;\n        mFile = 0;\n        return response;\n      }\n\n      // currently put would never be eligible for caching: but in case it was,\n      // here we could not cache, as we continue to use mFile and in addition\n      // once cached the file could be used but another thread.\n      FileClose(CanCache::NO);\n\n      if (mCloseCode) {\n        mErrCode = eos::common::HttpResponse::INTERNAL_SERVER_ERROR;\n        mErrText = \"File close failed\";\n        response = HttpServer::HttpError(mErrText.c_str(), mErrCode);\n        delete mFile;\n        mFile = 0;\n        return response;\n      } else {\n        response = new eos::common::PlainHttpResponse();\n\n        // Add the etag only if we are not an intermediary chunk upload,\n        // otherwise the cernbox client interprets it as the end of the\n        // transfers and gets an error.\n        if (header.count(\"x-oc-mtime\") == 0) {\n          response->AddHeader(\"ETag\", mFile->GetETag());\n        }\n\n        if (header.count(\"x-oc-mtime\") &&\n            (mLastChunk || (!header.count(\"oc-chunked\")))) {\n          // only normal uploads or the last chunk receive these extra response headers\n          response->AddHeader(\"ETag\", mFile->GetETag());\n\n          if (!mOffsetMap.size()) {\n            response->AddHeader(\"X-OC-Mtime\", \"accepted\");\n            // return the OC-FileId header\n            std::string ocid;\n            eos::common::StringConversion::GetSizeString(ocid,\n                eos::common::FileId::FidToInode(mFileId));\n            response->AddHeader(\"OC-FileId\", ocid);\n\n            if (checksumMatch && request->GetHeaders().count(\"oc-checksum\")) {\n              response->AddHeader(\"OC-Checksum\", request->GetHeaders()[\"oc-checksum\"]);\n            }\n          } else {\n            // PUT with range\n            time_t mtime = mFile->GetMtime();\n            response->AddHeader(\"Last-Modified\", eos::common::Timing::utctime(mtime));\n            std::string inode;\n            eos::common::StringConversion::GetSizeString(inode,\n                eos::common::FileId::FidToInode(mFileId));\n            response->AddHeader(\"x-eos-inode\", inode);\n\n            if (checksumMatch && request->GetHeaders().count(\"x-upload-checksum\")) {\n              response->AddHeader(\"x-eos-checksum\",\n                                  request->GetHeaders()[\"x-upload-checksum\"]);\n            }\n          }\n        }\n\n        response->SetResponseCode(eos::common::HttpResponse::CREATED);\n        return response;\n      }\n    }\n  }\n\n  // Should never get here\n  mErrCode = eos::common::HttpResponse::INTERNAL_SERVER_ERROR;\n  mErrText = \"Internal Server Error\";\n  response = HttpServer::HttpError(mErrText.c_str(), mErrCode);\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nHttpHandler::DecodeByteRange(std::string rangeheader,\n                             std::map<off_t, ssize_t>& offsetmap,\n                             off_t& requestsize,\n                             off_t filesize)\n{\n  std::vector<std::string> tokens;\n\n  if (rangeheader.substr(0, 6) != \"bytes=\") {\n    // this is an illegal header\n    return false;\n  } else {\n    rangeheader.erase(0, 6);\n  }\n\n  eos::common::StringConversion::Tokenize(rangeheader, tokens, \",\");\n\n  // decode the string parts\n  for (size_t i = 0; i < tokens.size(); i++) {\n    eos_static_debug(\"decoding %s\", tokens[i].c_str());\n    off_t start = 0;\n    off_t stop = 0;\n    off_t length = 0;\n    size_t mpos = tokens[i].find(\"-\");\n\n    if (mpos == std::string::npos) {\n      // there must always be a '-'\n      return false;\n    }\n\n    std::string sstop = tokens[i];\n    std::string sstart = tokens[i];\n    sstart.erase(mpos);\n    sstop.erase(0, mpos + 1);\n\n    if (sstart.length()) {\n      start = strtoull(sstart.c_str(), 0, 10);\n    } else {\n      start = 0;\n    }\n\n    if (sstop.length()) {\n      stop = strtoull(sstop.c_str(), 0, 10);\n    } else {\n      if (filesize > 0) {\n        stop = filesize - 1;\n      } else {\n        stop = 0;\n      }\n    }\n\n    if (!sstart.length()) {\n      // case '-X' = the last X bytes\n      start = filesize - stop;\n      stop = filesize - 1;\n    }\n\n    if ((start > filesize)) {\n      return false;\n    }\n\n    // RFC https://datatracker.ietf.org/doc/html/rfc7233 , do not return an error if the outer part of the range is > than the file size\n    // Set the outer part of the range to the end of the file.\n    if (stop >= filesize) {\n      stop = filesize - 1;\n    }\n\n    if (stop >= start) {\n      length = (stop - start) + 1;\n    } else {\n      continue;\n    }\n\n    if (offsetmap.count(start)) {\n      if (offsetmap[start] < length) {\n        // a previous block has been replaced with a longer one\n        offsetmap[start] = length;\n      }\n    } else {\n      offsetmap[start] = length;\n    }\n  }\n\n  // now merge overlapping requests\n  bool merged = true;\n\n  while (merged) {\n    requestsize = 0;\n\n    if (offsetmap.begin() == offsetmap.end()) {\n      // if there is nothing in the map just return with error\n      eos_static_err(\"msg=\\\"range map is empty\\\"\");\n      return false;\n    }\n\n    for (auto it = offsetmap.begin(); it != offsetmap.end(); it++) {\n      eos_static_debug(\"offsetmap %llu:%llu\", it->first, it->second);\n      auto next = it;\n      next++;\n\n      if (next != offsetmap.end()) {\n        // check if we have two overlapping requests\n        if ((it->first + it->second) >= (next->first)) {\n          merged = true;\n          // merge this two\n          it->second = next->first + next->second - it->first;\n          offsetmap.erase(next);\n          break;\n        } else {\n          merged = false;\n        }\n      } else {\n        merged = false;\n      }\n\n      // compute the total size\n      requestsize += it->second;\n    }\n  }\n\n  return true;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nHttpHandler::FileClose(enum HttpHandler::CanCache cache)\n{\n  if (mFile && cache == CanCache::YES && mFileCacheEntry.getfp() == mFile) {\n    if (sFileCache.insert(mFileCacheEntry)) {\n      eos_static_debug(\"path=%s saved in open-file cache fp=%p\",\n                       mFileCacheEntry.key_.url_.c_str(), mFile);\n      // must not refer to mFile again as it could already be\n      // in use by another thread\n      mFile = nullptr;\n      mCloseCode = 0;\n    }\n  }\n\n  mFileCacheEntry.clear();\n\n  if (mFile) {\n    mCloseCode = mFile->close();\n  }\n\n  return;\n}\n\n/*----------------------------------------------------------------------------*/\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/http/HttpHandler.hh",
    "content": "// ----------------------------------------------------------------------\n// File: HttpHandler.hh\n// Author: Andreas-Joachim Peters & Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   HttpHandler.hh\n *\n * @brief  Keeps a handle to an HTTP file/range request\n */\n\n#pragma once\n#include \"common/http/HttpHandler.hh\"\n#include \"common/http/MimeTypes.hh\"\n#include \"fst/http/HttpHandlerFstFileCache.hh\"\n#include \"fst/Namespace.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include <string>\n#include <map>\n\nEOSFSTNAMESPACE_BEGIN\n\nclass HttpResponse;\n\nclass HttpHandler : public eos::common::HttpHandler\n{\npublic:\n  int                        mRc;                 //< return code of a file open\n  XrdSecEntity\n  mClient;             //< the sec entity of the connected client\n  XrdFstOfsFile*             mFile;               //< handle to a file\n  std::map<off_t, ssize_t>\n  mOffsetMap;          //< map with offset+length of range requests\n  std::map<int, std::string> mMultipartHeaderMap; //< multipart header map\n  off_t                      mRangeRequestSize;   //< sum of all range requests\n  off_t                      mFileSize;           //< total file size\n  off_t\n  mRequestSize;        //< size of the total output including headers\n  off_t\n  mContentLength;      //< size of the content provided by client\n  off_t                      mLastUploadSize;     //< size of the last upload call\n  off_t                      mUploadLeftSize;     //< size of data still to upload\n\n  bool\n  mRangeDecodingError; //< indicating an invalid range request\n  bool\n  mRangeRequest;       //< indication if httpHandle has a range request\n  std::string\n  mBoundary;           //< boundary \"EOSMULTIPARBOUNDARY\"\n  std::string\n  mBoundaryEnd;        //< end boundary \"--EOSMULTIPARTBOUNDARY--\"\n  std::string                mMultipartHeader;    //< multipart Content tag\n  std::string\n  mSinglepartHeader;   //< singlepart range used if there is only one entry in mOffsetMap;\n  size_t\n  mCurrentCallbackOffsetIndex; //< current index to use in the callback\n  off_t\n  mCurrentCallbackOffset; //< next offset from where to read in the offset map at position index\n  bool\n  mLastChunk; //< indicates the last chunk in a chunked upload\n  bool\n  mBoundaryEndSent;    //< true when the boundary end was sent\n  std::string\n  mPrint;              //< print buffer to print the handle contents\n  int\n  mCloseCode;          //< close code to return if file upload was successful\n  unsigned long long\n  mFileId;             //< file id used in EOS - determined after Ofs::Open\n  std::string\n  mLogId;              //< log id used in EOS - determined after Ofs::Open\n  int                        mErrCode;            //< first seen error code\n  std::string                mErrText;            //< error text\n  HttpHandlerFstFileCache::Entry mFileCacheEntry;\n\n  static XrdSysMutex mOpenMutexMapMutex;\n  static std::map<unsigned short, XrdSysMutex*> mOpenMutexMap;\n  static eos::common::MimeTypes gMime;\n  static HttpHandlerFstFileCache sFileCache;\n\n  enum class CanCache {\n    NO,\n    YES\n  };\n\n  /**\n   * Constructor\n   */\n  HttpHandler()\n  {\n    mFile                   = 0;\n    mRangeRequestSize       = 0;\n    mRangeDecodingError     = 0;\n    mRangeRequest           = false;\n    mRequestSize            = 0;\n    mFileSize               = 0;\n    mFileId                 = 0;\n    mBoundaryEnd            = \"\\n--EOSMULTIPARTBOUNDARY--\\n\";\n    mBoundary               = \"--EOSMULTIPARTBOUNDARY\\n\";\n    mMultipartHeader        = \"multipart/byteranges; boundary=EOSMULTIPARTBOUNDARY\";\n    mCurrentCallbackOffsetIndex = 0;\n    mCurrentCallbackOffset  = 0;\n    mBoundaryEndSent        = false;\n    mSinglepartHeader       = \"\";\n    mCloseCode              = 0;\n    mRc                     = 0;\n    mContentLength          = 0;\n    mLastUploadSize         = 0;\n    mUploadLeftSize         = 0;\n    mLastChunk              = false;\n    mErrCode                = 0;\n  }\n\n  /**\n   * Destructor\n   */\n  virtual\n  ~HttpHandler();\n\n  /**\n   * Check whether the given method and headers are a match for this protocol.\n   *\n   * @param method  the request verb used by the client (GET, PUT, etc)\n   * @param headers the map of request headers\n   *\n   * @return true if the protocol matches, false otherwise\n   */\n  static bool\n  Matches(const std::string& method, HeaderMap& headers);\n\n  /**\n   * Build a response to the given HTTP request.\n   *\n   * @param request  the map of request headers sent by the client\n   * @param method   the request verb used by the client (GET, PUT, etc)\n   * @param url      the URL requested by the client\n   * @param query    the GET request query string (if any)\n   * @param body     the request body data sent by the client\n   * @param bodysize the size of the request body\n   * @param cookies  the map of cookie headers\n   */\n  void\n  HandleRequest(eos::common::HttpRequest* request);\n\n  /**\n   * Print a representation of this handler's range request data\n   *\n   * @return pointer to HTTP printout string\n   */\n  const char*\n  Print()\n  {\n    char line[4096];\n    snprintf(line, sizeof(line) - 1,\n             \"range-request=%llu range-request-size=%llu \"\n             \"request-size=%llu file-size=%llu\",\n             (unsigned long long) mRangeRequest,\n             (unsigned long long) mRangeRequestSize,\n             (unsigned long long) mRequestSize,\n             (unsigned long long) mFileSize);\n    mPrint = line;\n    return mPrint.c_str();\n  }\n\n  /**\n   * Create the map of multipart headers for each offset/length pair\n   *\n   * @param contenttype content type to put into the multipart header\n   */\n  void\n  CreateMultipartHeader(std::string contenttype)\n  {\n    mRequestSize = mRangeRequestSize;\n\n    if (mOffsetMap.size() != 1) {\n      mRequestSize += mBoundaryEnd.length();\n    }\n\n    size_t index = 0;\n\n    for (auto it = mOffsetMap.begin(); it != mOffsetMap.end(); it++) {\n      std::string header = \"\\n--EOSMULTIPARTBOUNDARY\\nContent-Type: \";\n      header += contenttype;\n      header += \"\\nContent-Range: \";\n      char srange[256];\n      snprintf(srange,\n               sizeof(srange) - 1,\n               \"bytes %llu-%llu/%llu\",\n               (unsigned long long) it->first,\n               (unsigned long long)((it->second) ? (it->first + it->second - 1)\n                                    : mRangeRequestSize),\n               (unsigned long long) mFileSize\n              );\n\n      if (mOffsetMap.size() == 1) {\n        mSinglepartHeader = srange;\n      }\n\n      header += srange;\n      header += \"\\n\\n\";\n      mMultipartHeaderMap[index] = header;\n\n      if (mOffsetMap.size() != 1) {\n        mRequestSize += mMultipartHeaderMap[index].length();\n      }\n\n      index++;\n    }\n  }\n\n  /**\n   * Decode the range header tag and create canonical merged map with\n   * offset/len\n   *\n   * @param rangeheader  the range header tag\n   * @param offsetmap    canonical map with offset/length by reference\n   * @param requestsize  sum of non overlapping bytes to serve\n   * @param filesize     size of file\n   *\n   * @return true if valid request, otherwise false\n   */\n  bool\n  DecodeByteRange(std::string               rangeheader,\n                  std::map<off_t, ssize_t>& offsetmap,\n                  off_t&                  requestsize,\n                  off_t                     filesize);\n\n  /**\n   * Initialize this HTTP handler\n   *\n   * @param request  the client request object\n   */\n  void\n  Initialize(eos::common::HttpRequest* request);\n\n  /**\n   * Handle an HTTP GET request.\n   *\n   * @param request  the client request object\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Get(eos::common::HttpRequest* request);\n\n  /**\n   * Handle an HTTP HEAD request.\n   *\n   * @param request  the client request object\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Head(eos::common::HttpRequest* request);\n\n  /**\n   * Handle an HTTP PUT request.\n   *\n   * @param request  the client request object\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Put(eos::common::HttpRequest* request);\n\n  void FileClose(enum HttpHandler::CanCache cache);\n\n};\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/http/HttpHandlerFstFileCache.cc",
    "content": "// ----------------------------------------------------------------------\n// File: HttpHandlerFstFileCache.cc\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"common/Timing.hh\"\n#include \"fst/http/HttpHandlerFstFileCache.hh\"\n#include <algorithm>\n\nEOSFSTNAMESPACE_BEGIN\n\nHttpHandlerFstFileCache::HttpHandlerFstFileCache()\n{\n  mThreadActive = false;\n  mMaxEntries = 1000;\n  mMaxIdletimeMs = 300'000;\n  mIdletimeResMs = 5'000;\n\n  if (getenv(\"EOS_FST_HTTP_FHCACHE_MAXENTRIES\")) {\n    try {\n      mMaxEntries = std::stoull(getenv(\"EOS_FST_HTTP_FHCACHE_MAXENTRIES\"));\n    } catch (...) {\n      // no change\n    }\n  }\n\n  if (getenv(\"EOS_FST_HTTP_FHCACHE_IDLETIME\")) {\n    try {\n      mMaxIdletimeMs = static_cast<uint64_t>(\n        std::stof(getenv(\"EOS_FST_HTTP_FHCACHE_IDLETIME\")) * 1000.0);\n    } catch (...) {\n      // no change\n    }\n  }\n\n  if (getenv(\"EOS_FST_HTTP_FHCACHE_IDLERES\")) {\n    try {\n      mIdletimeResMs = static_cast<uint64_t>(\n        std::stof(getenv(\"EOS_FST_HTTP_FHCACHE_IDLERES\")) * 1000.0);\n    } catch (...) {\n      // no change\n    }\n  }\n}\n\nHttpHandlerFstFileCache::~HttpHandlerFstFileCache()\n{\n  mThreadId.join();\n}\n\n//------------------------------------------------------------------------------\n//! insert entry into the cache. The entry contains the key, which can later\n//! be used to remove the entry again.\n//------------------------------------------------------------------------------\nbool HttpHandlerFstFileCache::insert(const HttpHandlerFstFileCache::Entry &ein)\n{\n  if (!ein) return false;\n  if (!mMaxEntries || !mMaxIdletimeMs || !mIdletimeResMs) return false;\n\n  if (ein.fp_)\n    ein.fp_->OnCacheInsert();\n\n  std::list<EntryGuard> todel;\n  {\n    XrdSysMutexHelper cLock(mCacheLock);\n    if (!mThreadActive) {\n      mThreadId.reset(&HttpHandlerFstFileCache::Run, this);\n      mThreadActive = true;\n    }\n\n    const uint64_t inow = eos::common::getEpochInMilliseconds().count();\n\n    // new entry into list\n    mQueue.emplace_back(ein);\n    const size_t len = mQueue.size();\n\n    // set insert time and add entry to the map\n    {\n      Entry &e = mQueue.back().get();\n      e.itime_ = inow;\n\n      // we rely on mQueue to be ordered on insert time.\n      // with a clock change this could be violated, so check if\n      // the second to last entry had a later insert time.\n      GuardList::iterator cit;\n      if (len > 1) {\n        cit = mQueue.end();\n        std::advance(cit, -2); // last element before one we just added\n        if ( (*cit)->itime_ > inow ) {\n          e.itime_ = (*cit)->itime_;\n        }\n      }\n\n      cit = mQueue.end();\n      std::advance(cit, -1); // element just added\n      // insert the Key -> GuardList::iterator in the map\n      const auto newmapitr = mQmap.insert({e.key_, cit});\n      // add a backpoitner in the Entry to the map\n      e.mapitr_ = newmapitr;\n    }\n\n    if (len > mMaxEntries) {\n      const GuardList::iterator it = mQueue.begin();\n      const KeyMap::iterator    mapitr = (*it)->mapitr_;\n      // remove oldest entry\n      mQmap.erase(mapitr);\n      todel.splice(todel.begin(), mQueue, mQueue.begin());\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n//! remove an entry with key k from the cache. In case no such key is found\n//! an empty Entry is returned.\n//------------------------------------------------------------------------------\nHttpHandlerFstFileCache::Entry HttpHandlerFstFileCache::remove(const HttpHandlerFstFileCache::Key &k)\n{\n  Entry e;\n  if (!k) return e;\n\n  XrdSysMutexHelper cLock(mCacheLock);\n  auto maprange = mQmap.equal_range(k);\n  if (maprange.first == maprange.second) return e;\n\n  // we will use the most recently inserted entry with matching Key\n  const KeyMap::iterator mapitr = std::prev(maprange.second,1);\n  const GuardList::iterator it = mapitr->second;\n  e = it->release();\n  mQmap.erase(mapitr);\n  mQueue.erase(it);\n\n  return e;\n}\n\n\n//------------------------------------------------------------------------------\n//! this thread worker runs while there are some cached entries. It periodically\n//! checks for entries which have remained in the cache too long, or are\n//! recorded as inserted in the future and closes and removes them.\n//------------------------------------------------------------------------------\nvoid HttpHandlerFstFileCache::Run(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"http_fhcache_gc\");\n\n  while (!assistant.terminationRequested())\n  {\n    size_t ntot = 0, ndel = 0;\n    uint64_t waitms = 1;\n\n    {\n      std::list<EntryGuard> todel;\n      XrdSysMutexHelper cLock(mCacheLock);\n      ntot = mQmap.size();\n      const uint64_t inow = eos::common::getEpochInMilliseconds().count();\n\n      // check for too old entries at the beginning of mQueue, we want to\n      // remove any with insert time older than limit; i.e.\n      // itime < (inow - mMaxIdletimeMs)\n\n      const GuardList::iterator lbound = std::find_if(mQueue.begin(), mQueue.end(),\n        [inow,maxIdletimeMs = mMaxIdletimeMs](const EntryGuard &x) {\n          return inow < x->itime_ + maxIdletimeMs + 1;\n        });\n      // remove from [begin, lbound)\n      for(GuardList::iterator it = mQueue.begin(); it != lbound; ++it) {\n        ndel++;\n        mQmap.erase((*it)->mapitr_);\n      }\n      todel.splice(todel.begin(), mQueue, mQueue.begin(), lbound);\n\n      // check for entry insert time in the future at end of queue, we want\n      // to remove any with insert time more recent than now; i.e.\n      // itime > inow\n\n      const GuardList::reverse_iterator uboundr =\n        std::find_if(mQueue.rbegin(), mQueue.rend(),\n          [inow](const EntryGuard &x) {\n            return x->itime_ < inow + 1;\n          });\n\n      const GuardList::iterator ubound = uboundr.base();\n      // ubound is a forward itertor refering to the element after uboundr.\n      // remove from [ubound, end)\n      for(GuardList::iterator it = ubound; it != mQueue.end(); ++it) {\n        ndel++;\n        mQmap.erase((*it)->mapitr_);\n      }\n      todel.splice(todel.begin(), mQueue, ubound, mQueue.end());\n\n      if (ntot == ndel) {\n        mThreadId.stop();\n        mThreadActive = false;\n      }\n\n      if (ntot > ndel) {\n        const uint64_t oldest = mQueue.front()->itime_;\n        // wait time needed for oldest entry to become too old if it\n        // doesn't get used again.\n        // 'if' below should always be true\n        if (oldest+mMaxIdletimeMs >= inow) waitms = oldest+mMaxIdletimeMs+1 - inow;\n      }\n      // any close+deletes of fh happen below when we go out of scope of todel\n    }\n\n    waitms = ((waitms + mIdletimeResMs - 1) / mIdletimeResMs) * mIdletimeResMs;\n\n    eos_static_debug(\"HttpHandlerFstFileCache watcher thread ntot=%ld ndel=%ld waitms=%ld\", ntot, ndel, waitms);\n    assistant.wait_for(std::chrono::milliseconds(waitms));\n  }\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/http/HttpHandlerFstFileCache.hh",
    "content": "// ----------------------------------------------------------------------\n// File: HttpHandlerFstFileCache.hh\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include \"common/AssistedThread.hh\"\n#include \"fst/XrdFstOfsFile.hh\"\n#include <XrdSfs/XrdSfsInterface.hh>\n#include <XrdSys/XrdSysPthread.hh>\n#include <sys/time.h>\n#include <list>\n#include <map>\n#include <memory>\n#include <string>\n\nEOSFSTNAMESPACE_BEGIN\n\n/**\n * Class responsible for caching XrdFstOfsFile* of open files.\n * The HttpHandler uses this when handling read requests for byte\n * ranges. It is assumed that another request will shortly\n * arrive for the same file for a different byte range and it is less\n * costly to keep the file open than to re-open it for each request.\n * HttpHandler uses one instance of this class.\n *\n * This cache may contain multiple open XrdFstOfsFile* for the same file\n * because there could be concurrent requests for a given file and\n * it is not safe to issue read on the same object concurrently.\n * For this reason entries are removed from the cache while being used.\n * After use each of these XrdFstOfsFile* may be added or re-added\n * (still open) to the cache.\n *\n * Both the filename and the query portion of the url are a\n * part of the cache Entries' Keys, so that they Key includes the\n * cap.msg (i.e. the key will be specific to an MGM redirection).\n * For this caching to be useful it is needed that the client uses\n * something like Davix's Redirect Cache, to repeatly issue requests\n * for a file without going back to the MGM each time. We may keep\n * the cached file open longer than the cap validity time, as the cap\n * only needs to be valid at the time the file was opened.\n *\n * XrdFstOfsFile* are inserted in the cache while not being used, and removed\n * from the cache to be used. For Keys with multiple Entries the\n * most recetntly inserted is removed and returned.\n *\n * This cache is implemented using two containers. A std::list of\n * Entry keeps entries ordered by insert time (oldest at the front).\n * A std::multimap maps Key to an iterator pointing to an element\n * in the std::list. The Entry in the list also contains a backpointer\n * to the multimap. Within the multimap Entries with identical Keys\n * will be kept in the order inserted, i.e. oldest at the front of an\n * equal_range. The map is used to find and remove an entry given a Key.\n * The list is used to remove old entries because too full or the\n * entry is classed as unused.\n *\n * Removal of entries which are unused (too old) is done by a watcher\n * thread.\n */\nclass HttpHandlerFstFileCache\n{\npublic:\n\n  struct EntryGuard;\n\n  /**\n   * Object to represent Entry's Key.\n   */\n  struct Key {\n    Key() : omode_(0) { }\n\n    Key(const std::string& name, const std::string& url,\n        const std::string& query, XrdSfsFileOpenMode omode) :\n      name_(name), url_(url), query_(query), omode_(omode) { }\n\n    void clear()\n    {\n      name_.clear();\n      url_.clear();\n      query_.clear();\n      omode_ = 0;\n    }\n\n    explicit operator bool() const\n    {\n      return !url_.empty();\n    }\n\n    bool operator==(const Key& other) const\n    {\n      return omode_ == other.omode_ &&\n             url_   == other.url_   &&\n             query_ == other.query_ &&\n             name_  == other.name_;\n    }\n\n    bool operator<(const Key& rhs) const\n    {\n      if (omode_ < rhs.omode_) {\n        return true;\n      }\n\n      if (omode_ > rhs.omode_) {\n        return false;\n      }\n\n      int c = url_.compare(rhs.url_);\n\n      if (c < 0) {\n        return true;\n      }\n\n      if (c > 0) {\n        return false;\n      }\n\n      c = query_.compare(rhs.query_);\n\n      if (c < 0) {\n        return true;\n      }\n\n      if (c > 0) {\n        return false;\n      }\n\n      c = name_.compare(rhs.name_);\n\n      if (c < 0) {\n        return true;\n      }\n\n      return false;\n    }\n\n    std::string name_;\n    std::string url_;\n    std::string query_;\n    XrdSfsFileOpenMode omode_;\n  };\n\n  /**\n   * Object to represent cache Entry data.\n   */\n  struct Entry {\n    Entry() : itime_(0), fp_(0) { }\n\n    void set(const Key& k, XrdFstOfsFile* const v)\n    {\n      key_ = k;\n      fp_ = v;\n      itime_ = 0;\n    }\n\n    void clear()\n    {\n      key_.clear();\n      itime_ = 0;\n      fp_ = 0;\n    }\n\n    XrdFstOfsFile* getfp()\n    {\n      return fp_;\n    }\n\n    explicit operator bool() const\n    {\n      return key_ && fp_;\n    }\n\n    Key key_;\n    uint64_t itime_;\n    XrdFstOfsFile* fp_;\n    std::multimap<Key, std::list<EntryGuard>::iterator>::iterator mapitr_;\n  };\n\n  /**\n   * Object that is inserted into the cache. This contains an Entry and acts as\n   * an lifetime guared for the XrdFstOfsFile* part of the entry while it is cached.\n   */\n  struct EntryGuard {\n    EntryGuard(const Entry& e):\n      own_(true), entry_(e)\n    { }\n\n    ~EntryGuard()\n    {\n      if (own_ && entry_.fp_) {\n        entry_.fp_->close();\n        delete entry_.fp_;\n      }\n    }\n\n    // no copying\n    EntryGuard(const EntryGuard&) = delete;\n    EntryGuard& operator=(const EntryGuard&) = delete;\n\n    Entry release()\n    {\n      own_ = false;\n      return entry_;\n    }\n\n    Entry& get()\n    {\n      return entry_;\n    }\n\n    const Entry* operator->() const\n    {\n      return &entry_;\n    }\n\n    bool own_;\n    Entry entry_;\n  };\n\n  HttpHandlerFstFileCache();\n  ~HttpHandlerFstFileCache();\n\n  /**\n   * Run the file cache watcher thread\n   */\n  void Run(ThreadAssistant& assistant) noexcept;\n\n  /**\n   * Insert and entry into the cache.\n   */\n  bool           insert(const Entry& e);\n\n  /**\n   * Remove an entry from the cache.\n   */\n  Entry          remove(const Key& k);\n\n  typedef std::list<EntryGuard> GuardList;\n  typedef std::multimap<Key, std::list<EntryGuard>::iterator> KeyMap;\n\nprivate:\n  XrdSysMutex    mCacheLock;\n  AssistedThread mThreadId;\n  bool           mThreadActive;\n  size_t         mMaxEntries;\n  uint64_t       mMaxIdletimeMs;\n  uint64_t       mIdletimeResMs;\n  GuardList      mQueue;\n  KeyMap         mQmap;\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/http/HttpServer.cc",
    "content": "// ----------------------------------------------------------------------\n// File: HttpServer.cc\n// Author: Andreas-Joachim Peters & Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/http/HttpServer.hh\"\n#include \"fst/http/ProtocolHandlerFactory.hh\"\n#include \"fst/XrdFstOfsFile.hh\"\n#include \"common/http/ProtocolHandler.hh\"\n#include \"common/SecEntity.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include <XrdSys/XrdSysPthread.hh>\n#include <XrdSfs/XrdSfsInterface.hh>\n\nEOSFSTNAMESPACE_BEGIN\n\n#ifdef EOS_MICRO_HTTPD\n/*----------------------------------------------------------------------------*/\n\nint\nHttpServer::Handler(void* cls,\n                    struct MHD_Connection* connection,\n                    const char* url,\n                    const char* method,\n                    const char* version,\n                    const char* uploadData,\n                    size_t* uploadDataSize,\n                    void** ptr)\n{\n  // The handler function is called in a 'stateless' fashion, so to keep state\n  // the implementation stores a HttpHandler object using **ptr.\n  // libmicrohttpd moreover deals with 100-continue responses used by PUT/POST\n  // in the upper protocol level, so the handler has to return for GET requests\n  // just MHD_YES if there is not yet an HttpHandler and for PUT requests\n  // should only create a response object if the open for the PUT fails for\n  // whatever reason.\n  // So when the HTTP header have arrived Handler is called the first time\n  // and in following Handler calls we should not decode the headers again and\n  // again for performance reasons. So there is a difference between handling of\n  // GET and PUT because in GET we just don't do anything but return and decode\n  // the HTTP headers with the second call, while for PUT we do it in the first\n  // call and open the output file immediately to return evt. an error.\n  std::map<std::string, std::string> headers;\n\n  // If this is the first call, create an appropriate protocol handler based\n  // on the headers and store it in *ptr. We should only return MHD_YES here\n  // (unless error)\n  if (*ptr == 0) {\n    // Get the headers\n    MHD_get_connection_values(connection, MHD_HEADER_KIND,\n                              &HttpServer::BuildHeaderMap, (void*) &headers);\n    eos::common::ProtocolHandler* handler;\n    ProtocolHandlerFactory factory = ProtocolHandlerFactory();\n    handler = factory.CreateProtocolHandler(method, headers, 0);\n\n    if (!handler) {\n      eos_static_err(\"msg=No matching protocol for request\");\n      return MHD_NO;\n    }\n\n    *ptr = handler;\n    return MHD_YES;\n  }\n\n  // Retrieve the protocol handler stored in *ptr\n  eos::common::ProtocolHandler* protocolHandler = (eos::common::ProtocolHandler*)\n      * ptr;\n\n  // For requests which have a body (i.e. uploadDataSize != 0) we must handle\n  // the body data on the second reentrant call to this function. We must\n  // create the response and store it inside the protocol handler, but we must\n  // NOT queue the response until the third call.\n  if (!protocolHandler->GetResponse() ||\n      !protocolHandler->GetResponse()->GetResponseCode()) {\n    // Get the request headers again\n    MHD_get_connection_values(connection, MHD_HEADER_KIND,\n                              &HttpServer::BuildHeaderMap, (void*) &headers);\n    // Get the request query string\n    std::string query;\n    MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND,\n                              &HttpServer::BuildQueryString, (void*) &query);\n    // Get the cookies\n    std::map<std::string, std::string> cookies;\n    MHD_get_connection_values(connection, MHD_COOKIE_KIND,\n                              &HttpServer::BuildHeaderMap, (void*) &cookies);\n    // Make a request object\n    std::string body(uploadData, *uploadDataSize);\n    eos::common::HttpRequest* request = new eos::common::HttpRequest(\n      headers, method, url,\n      query.c_str() ? query : \"\",\n      body, uploadDataSize, cookies);\n    eos_static_debug(\"\\n\\n%s\", request->ToString().c_str());\n    // Handle the request and build a response based on the specific protocol\n    protocolHandler->HandleRequest(request);\n    delete request;\n  }\n\n  eos::common::HttpResponse* response = protocolHandler->GetResponse();\n\n  if (!response) {\n    eos_static_crit(\"msg=\\\"response creation failed\\\"\");\n    return MHD_NO;\n  }\n\n  if (*uploadDataSize != 0) {\n    eos_static_debug(\"returning MHD_NO response-code=%d to stop upload\",\n                     response->GetResponseCode());\n\n    if (response->GetResponseCode()) {\n      eos_static_debug(\"setting uploadDataSize to 0\");\n      *uploadDataSize = 0;\n\n      if (response->GetResponseCode() >= 300) {\n        eos_static_debug(\"failing request with response code %d\",\n                         response->GetResponseCode());\n        protocolHandler->DeleteResponse();\n        return MHD_NO;\n      }\n    }\n\n    protocolHandler->DeleteResponse();\n    return MHD_YES;\n  }\n\n  eos_static_debug(\"\\n\\n%s\", response->ToString().c_str());\n  // Create the MHD response\n  struct MHD_Response* mhdResponse;\n\n  if (response->mUseFileReaderCallback) {\n    eos_static_debug(\"response length=%d\", response->mResponseLength);\n    mhdResponse = MHD_create_response_from_callback(response->mResponseLength,\n                  4 * 1024 * 1024, /* 4M page size */\n                  &HttpServer::FileReaderCallback,\n                  (void*) protocolHandler, 0);\n  } else {\n    mhdResponse = MHD_create_response_from_buffer(response->GetBodySize(),\n                  (void*) response->GetBody().c_str(),\n                  MHD_RESPMEM_PERSISTENT);\n  }\n\n  if (mhdResponse) {\n    // Add all the response header tags\n    headers = response->GetHeaders();\n\n    for (auto it = headers.begin(); it != headers.end(); it++) {\n      MHD_add_response_header(mhdResponse, it->first.c_str(), it->second.c_str());\n    }\n\n    // Queue the response\n    int ret = MHD_queue_response(connection, response->GetResponseCode(),\n                                 mhdResponse);\n    eos_static_debug(\"MHD_queue_response ret=%d\", ret);\n    MHD_destroy_response(mhdResponse);\n    return ret;\n  } else {\n    eos_static_crit(\"msg=\\\"response creation failed\\\"\");\n    return MHD_NO;\n  }\n}\n\nvoid\nHttpServer::CompleteHandler(void*                              cls,\n                            struct MHD_Connection*             connection,\n                            void**                             con_cls,\n                            enum MHD_RequestTerminationCode    toe)\n{\n  std::string scode = \"\";\n\n  if (toe == MHD_REQUEST_TERMINATED_COMPLETED_OK) {\n    scode = \"OK\";\n  }\n\n  if (toe == MHD_REQUEST_TERMINATED_WITH_ERROR) {\n    scode = \"Error\";\n  }\n\n  if (toe == MHD_REQUEST_TERMINATED_TIMEOUT_REACHED) {\n    scode = \"Timeout\";\n  }\n\n  if (toe == MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN) {\n    scode = \"Shutdown\";\n  }\n\n  if (toe == MHD_REQUEST_TERMINATED_READ_ERROR) {\n    scode = \"ReadError\";\n  }\n\n  eos_static_info(\"msg=\\\"http connection disconnect\\\" reason=\\\"Request %s\\\" \",\n                  scode.c_str());\n  eos::fst::HttpHandler* httpHandle = 0;\n\n  if ((con_cls && (*con_cls))) {\n    eos::common::ProtocolHandler* handler =\n      static_cast<eos::common::ProtocolHandler*>(*con_cls);\n    httpHandle = dynamic_cast<eos::fst::HttpHandler*>(handler);\n  }\n\n  if (httpHandle) {\n    // deal with delete-on-close logic\n    if ((toe != MHD_REQUEST_TERMINATED_COMPLETED_OK)) {\n      eos_static_info(\"msg=\\\"http connection disconnect\\\" action=\\\"Cleanup\\\" \");\n\n      if (httpHandle && httpHandle->mFile) {\n        eos_static_err(\"msg=\\\"clean-up interrupted PUT/GET request\\\" path=\\\"%s\\\"\",\n                       httpHandle->mFile->GetPath().c_str());\n\n        // we have to disable delete-on-close for chunked uploads since files are stateful\n        if (httpHandle->mFile->IsChunkedUpload()) {\n          httpHandle->mFile->close();\n        }\n      }\n    }\n\n    // clean-up file objects\n    if (httpHandle->mFile) {\n      delete(httpHandle->mFile);\n      httpHandle->mFile = 0;\n    }\n\n    delete httpHandle;\n    *con_cls = 0;\n  }\n}\n\n#endif\n\n/*----------------------------------------------------------------------------*/\nssize_t\nHttpServer::FileReader(eos::common::ProtocolHandler* handler, uint64_t pos,\n                       char* buf, size_t max)\n{\n  return HttpServer::FileReaderCallback(handler, pos, buf, max);\n}\n\n/*----------------------------------------------------------------------------*/\nssize_t\nHttpServer::FileWriter(eos::common::ProtocolHandler* handler,\n                       std::string& method,\n                       std::string& uri,\n                       std::map<std::string, std::string>& headers,\n                       std::string& query,\n                       std::map<std::string, std::string>& cookies,\n                       std::string& body)\n\n{\n  eos::fst::HttpHandler* httpHandle = dynamic_cast<eos::fst::HttpHandler*>\n                                      (handler);\n  size_t uploadSize = body.size();\n  std::unique_ptr<eos::common::HttpRequest> request(new eos::common::HttpRequest(\n        headers, method, uri,\n        query.c_str(),\n        body, &uploadSize, cookies,\n        true));\n  eos_static_debug(\"\\n\\n%s\", request->ToString().c_str());\n  // Handle the request and build a response based on the specific protocol\n  httpHandle->HandleRequest(request.get());\n  eos::common::HttpResponse* response = handler->GetResponse();\n\n  if (response->GetResponseCode() == response->CREATED) {\n    return 0;\n  } else {\n    return -1;\n  }\n}\n\n\n/*----------------------------------------------------------------------------*/\nssize_t\nHttpServer::FileClose(eos::common::ProtocolHandler* handler, int rc, bool eskip)\n{\n  eos::fst::HttpHandler* httpHandle = dynamic_cast<eos::fst::HttpHandler*>\n                                      (handler);\n\n  if (httpHandle && httpHandle->mFile) {\n    if (rc) {\n      eos_static_err(\"msg=\\\"clean-up interrupted or IO error related PUT/GET request\\\" path=\\\"%s\\\"\",\n                     httpHandle->mFile->GetPath().c_str());\n\n      // we have to disable delete-on-close for chunked uploads since files are stateful\n      if (httpHandle->mFile->IsChunkedUpload()) {\n        httpHandle->FileClose(HttpHandler::CanCache::YES);\n      } else if (!eskip) {\n        // under error eskip avoids closing the file before destorying\n        // (closing may cause httpHandler to cache the file handle)\n        httpHandle->FileClose(HttpHandler::CanCache::YES);\n      }\n    } else {\n      httpHandle->FileClose(HttpHandler::CanCache::YES);\n    }\n\n    // clean-up file objects\n    if (httpHandle->mFile) {\n      delete(httpHandle->mFile);\n      httpHandle->mFile = 0;\n    }\n  }\n\n  return 0;\n}\n\n\n/*----------------------------------------------------------------------------*/\nssize_t\nHttpServer::FileReaderCallback(void* cls, uint64_t pos, char* buf, size_t max)\n{\n  // Ugly ugly casting hack\n  eos::common::ProtocolHandler* handler =\n    static_cast<eos::common::ProtocolHandler*>(cls);\n  eos::fst::HttpHandler* httpHandle = dynamic_cast<eos::fst::HttpHandler*>\n                                      (handler);\n\n  if (!httpHandle) {\n    eos_static_err(\"error: dynamic cast to eos::fst::HttpHandler failed\");\n    return -1;\n  }\n\n  eos_static_debug(\"pos=%llu max=%llu current-index=%d current-offset=%llu\",\n                   (unsigned long long) pos,\n                   (unsigned long long) max,\n                   httpHandle->mCurrentCallbackOffsetIndex,\n                   httpHandle->mCurrentCallbackOffset);\n  size_t readsofar = 0;\n\n  if (httpHandle && httpHandle->mFile) {\n    if (httpHandle->mRangeRequest) {\n      // range request\n      if (httpHandle->mCurrentCallbackOffsetIndex < httpHandle->mOffsetMap.size()) {\n        size_t toread = 0;\n\n        // if the currentoffset is 0 we have to place the multipart header first\n        if ((httpHandle->mOffsetMap.size() > 1) &&\n            (httpHandle->mCurrentCallbackOffset == 0)) {\n          eos_static_debug(\"place=%s\", httpHandle->mMultipartHeaderMap\n                           [httpHandle->mCurrentCallbackOffsetIndex].c_str());\n          toread = httpHandle->mMultipartHeaderMap\n                   [httpHandle->mCurrentCallbackOffsetIndex].length();\n          // this is the start of a range request, copy the multipart header\n          memcpy(buf,\n                 httpHandle->mMultipartHeaderMap\n                 [httpHandle->mCurrentCallbackOffsetIndex].c_str(),\n                 toread\n                );\n          readsofar += toread;\n        }\n\n        auto it = httpHandle->mOffsetMap.begin();\n        // advance to the index position\n        std::advance(it, httpHandle->mCurrentCallbackOffsetIndex);\n        int nread = 0;\n\n        // now read from offset\n        do {\n          off_t offset = it->first;\n          off_t indexoffset = httpHandle->mCurrentCallbackOffset;\n          toread = max - readsofar;\n\n          // see how much we can still read from this offsetmap\n          if (toread > (size_t)(it->second - indexoffset)) {\n            toread = (it->second - indexoffset);\n          }\n\n          eos_static_debug(\"toread=%llu\", (unsigned long long) toread);\n          // read the block\n          nread = httpHandle->mFile->read(offset + indexoffset,\n                                          buf + readsofar, toread);\n\n          // there is a read error here!\n          if (toread && (nread != (int) toread)) {\n            return -1;\n          }\n\n          if (nread > 0) {\n            readsofar += nread;\n          }\n\n          if ((it->second - indexoffset) == nread) {\n            eos_static_debug(\"leaving\");\n            // we have to move to the next index;\n            it++;\n            httpHandle->mCurrentCallbackOffsetIndex++;\n            httpHandle->mCurrentCallbackOffset = 0;\n            // we stop the loop\n            break;\n          } else {\n            if (nread > 0) {\n              httpHandle->mCurrentCallbackOffset += nread;\n            }\n\n            eos_static_debug(\"callback-offset(now)=%llu\", (unsigned long long)\n                             httpHandle->mCurrentCallbackOffset);\n          }\n        } while ((nread > 0) &&\n                 (readsofar < max) &&\n                 (it != httpHandle->mOffsetMap.end()));\n\n        eos_static_debug(\"read=%llu\", (unsigned long long) readsofar);\n        return readsofar;\n      } else {\n        if (httpHandle->mOffsetMap.size() > 1) {\n          if (httpHandle->mBoundaryEndSent) {\n            // we are done here\n            return 0;\n          } else {\n            httpHandle->mBoundaryEndSent = true;\n            memcpy(buf, httpHandle->mBoundaryEnd.c_str(),\n                   httpHandle->mBoundaryEnd.length());\n            eos_static_debug(\"read=%llu [boundary-end]\", (unsigned long long)\n                             httpHandle->mBoundaryEnd.length());\n            return httpHandle->mBoundaryEnd.length();\n          }\n        } else {\n          return 0;\n        }\n      }\n    } else {\n      // file streaming\n      if (max) {\n        size_t nread = httpHandle->mFile->read(pos, buf, max);\n\n        if (nread == 0) {\n          return -1;\n        } else {\n          return nread;\n        }\n      } else {\n        return -1;\n      }\n    }\n  }\n\n  return 0;\n}\n\nstd::unique_ptr<eos::common::ProtocolHandler>\nHttpServer::XrdHttpHandler(std::string& method,\n                           std::string& uri,\n                           std::map<std::string, std::string>& headers,\n                           std::string& query,\n                           std::map<std::string, std::string>& cookies,\n                           std::string& body,\n                           const XrdSecEntity& client)\n{\n  if (client.moninfo && strlen(client.moninfo)) {\n    headers[\"ssl_client_s_dn\"] = client.moninfo;\n    headers[\"x-real-ip\"] = client.host;\n  }\n\n  ProtocolHandlerFactory factory = ProtocolHandlerFactory();\n  std::unique_ptr<eos::common::ProtocolHandler> handler(\n    factory.CreateProtocolHandler(method, headers, 0));\n\n  if (!handler) {\n    eos_static_err(\"msg=\\\"no matching protocol for request method %s\\\"\",\n                   method.c_str());\n    return 0;\n  }\n\n  size_t bodySize = body.length();\n  // Retrieve the protocol handler stored in *ptr\n  std::unique_ptr<eos::common::HttpRequest> request(new eos::common::HttpRequest(\n        headers, method, uri,\n        query.c_str() ? query : \"\",\n        body, &bodySize, cookies, true));\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"\\n\\n%s\\n%s\\n\", request->ToString().c_str(),\n                     request->GetBody().c_str());\n  }\n\n  handler->HandleRequest(request.get());\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"method=%s uri='%s' %s (warning this is not the mapped identity)\",\n                     method.c_str(), uri.c_str(), eos::common::SecEntity::ToString(&client,\n                         \"xrdhttp\").c_str());\n  }\n\n  return handler;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/http/HttpServer.hh",
    "content": "// ----------------------------------------------------------------------\n// File: HttpServer.hh\n// Author: Andreas-Joachim Peters & Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file HttpServer.hh\n *\n * @brief creates an HTTP redirector instance running on the FST\n */\n\n#pragma once\n#include \"fst/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include \"common/http/HttpServer.hh\"\n#include \"common/http/ProtocolHandler.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class HttpServer\n//------------------------------------------------------------------------------\nclass HttpServer : public eos::common::HttpServer\n{\npublic:\n  /**\n   * Constructor\n   */\n  HttpServer(int port = 8001) :\n    eos::common::HttpServer::HttpServer(port)\n  {}\n\n  /**\n   * Destructor\n   */\n  virtual ~HttpServer()\n  {\n    eos_static_info(\"%s\", \"msg=\\\"FST HttpServer destructor\\\"\");\n    mThreadId.join();\n  }\n\n#ifdef EOS_MICRO_HTTPD\n  /**\n   * HTTP object handler function on FST\n   *\n   * @return see implementation\n   */\n  virtual int\n  Handler(void*                  cls,\n          struct MHD_Connection* connection,\n          const char*            url,\n          const char*            method,\n          const char*            version,\n          const char*            upload_data,\n          size_t*                upload_data_size,\n          void**                 ptr);\n\n  /**\n   * HTTP complete handler function\n   *\n   * @return nothing\n   */\n  virtual void\n  CompleteHandler(void*                  cls,\n                  struct MHD_Connection* connection,\n                  void**                 con_cls,\n                  enum MHD_RequestTerminationCode toe);\n\n#endif\n\n  /**\n   * File Read Callback function\n   *\n   * @param cls XrdOfsFile* object\n   * @param pos offset to read from\n   * @param buf buffer to write to\n   * @param max size to read\n   *\n   * @return number of bytes read\n   */\n  static ssize_t\n  FileReaderCallback(void* cls, uint64_t pos, char* buf, size_t max);\n\n  virtual ssize_t\n  FileReader(eos::common::ProtocolHandler* handler, uint64_t pos, char* buf,\n             size_t max);\n\n  virtual ssize_t\n  FileWriter(eos::common::ProtocolHandler* handler,\n             std::string& method,\n             std::string& uri,\n             std::map<std::string, std::string>& headers,\n             std::string& query,\n             std::map<std::string, std::string>& cookies,\n             std::string& body);\n\n  virtual ssize_t\n  FileClose(eos::common::ProtocolHandler* handler, int rc, bool eskip);\n\n\n  /**\n   * HTTP object handler function on FST called by XrdHttp\n   *\n   * @return see implementation\n   */\n  virtual std::unique_ptr<eos::common::ProtocolHandler>\n  XrdHttpHandler(std::string& method,\n                 std::string& uri,\n                 std::map<std::string, std::string>& headers,\n                 std::string& query,\n                 std::map<std::string, std::string>& cookies,\n                 std::string& body,\n                 const XrdSecEntity& client);\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/http/ProtocolHandlerFactory.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ProtocolHandlerFactory.hh\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   ProtocolHandlerFactory.hh\n *\n * @brief  Factory class to create an appropriate protocol handler for the\n *         FST.\n */\n\n#ifndef __EOSFST_PROTOCOLHANDLERFACTORY__HH__\n#define __EOSFST_PROTOCOLHANDLERFACTORY__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"fst/Namespace.hh\"\n#include \"fst/http/HttpHandler.hh\"\n#include \"fst/http/s3/S3Handler.hh\"\n#include \"common/http/ProtocolHandlerFactory.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n#include <map>\n#include <string>\n/*----------------------------------------------------------------------------*/\n\nEOSFSTNAMESPACE_BEGIN\n\nclass ProtocolHandlerFactory : public eos::common::ProtocolHandlerFactory\n{\npublic:\n\n  ProtocolHandlerFactory () {};\n  virtual ~ProtocolHandlerFactory () {};\n\n  /**\n   * Factory function to create an appropriate object which will handle this\n   * request based on the method and headers.\n   *\n   * @param method  the request verb used by the client (GET, PUT, etc)\n   * @param headers the map of request headers\n   * @param vid     the mapped virtual identity of this client\n   *\n   * @return a concrete ProtocolHandler, or NULL if no matching protocol found\n   */\n  eos::common::ProtocolHandler*\n  CreateProtocolHandler (const std::string                     &method,\n                         std::map<std::string, std::string>    &headers,\n                         eos::common::VirtualIdentity          *vid)\n  {\n    if (S3Handler::Matches(method, headers))\n    {\n      return new S3Handler();\n    }\n    else if (HttpHandler::Matches(method, headers))\n    {\n      return new HttpHandler();\n    }\n\n    else return NULL;\n  };\n};\n\n/*----------------------------------------------------------------------------*/\nEOSFSTNAMESPACE_END\n\n#endif /* __EOSFST_PROTOCOLHANDLERFACTORY__HH__ */\n"
  },
  {
    "path": "fst/http/s3/S3Handler.cc",
    "content": "// ----------------------------------------------------------------------\n// File: S3Handler.cc\n// Author: Andreas-Joachim Peters & Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"fst/http/s3/S3Handler.hh\"\n#include \"fst/http/HttpServer.hh\"\n#include \"fst/XrdFstOfsFile.hh\"\n#include \"common/http/PlainHttpResponse.hh\"\n#include \"common/http/s3/S3Response.hh\"\n#include \"common/Logging.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n#include <string>\n#include <map>\n/*----------------------------------------------------------------------------*/\n\nEOSFSTNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\nbool\nS3Handler::Matches(const std::string& method, HeaderMap& headers)\n{\n  if (headers.count(\"authorization\")) {\n    if (headers[\"authorization\"].substr(0, 3) == \"AWS\") {\n      eos_static_info(\"info=Matched S3 protocol for request\");\n      return true;\n    }\n  }\n\n  return false;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nS3Handler::HandleRequest(eos::common::HttpRequest* request)\n{\n  eos_static_info(\"msg=\\\"handling s3 request\\\"\");\n\n  if (!mFile) {\n    Initialize(request);\n  }\n\n  if (!mFile) {\n    mFile = (XrdFstOfsFile*) gOFS.newFile(mClient.name);\n    // default modes are for GET=read\n    XrdSfsFileOpenMode open_mode = 0;\n    mode_t create_mode = 0;\n\n    if ((request->GetMethod() == \"PUT\") ||\n        (request->GetMethod() == \"CREATE\")) {\n      // use the proper creation/open flags for PUT's\n      open_mode |= SFS_O_CREAT;\n      open_mode |= SFS_O_TRUNC;\n      open_mode |= SFS_O_RDWR;\n      open_mode |= SFS_O_MKPTH;\n      create_mode |= (SFS_O_MKPTH | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);\n    }\n\n    mRc = mFile->open(request->GetUrl().c_str(),\n                      open_mode,\n                      create_mode,\n                      &mClient,\n                      request->GetQuery().c_str());\n    mFileSize = mFile->GetOpenSize();\n    mFileId = mFile->GetFileId();\n    mLogId = mFile->logId;\n\n    // check for range requests\n    if (request->GetHeaders().count(\"range\")) {\n      if (!DecodeByteRange(request->GetHeaders()[\"range\"],\n                           mOffsetMap,\n                           mRangeRequestSize,\n                           mFileSize)) {\n        // indicate range decoding error\n        mRangeDecodingError = true;\n      } else {\n        mRangeRequest = true;\n      }\n    }\n\n    if (!mRangeRequest) {\n      // we put the file size as request size if this is not a range request\n      // aka full file download\n      mRangeRequestSize = mFile->GetOpenSize();\n    }\n  }\n\n  if (request->GetMethod() == \"GET\") {\n    // call the HttpHandler::Get method\n    mHttpResponse = Get(request);\n  }\n\n  if (request->GetMethod() == \"CREATE\") {\n    // fake method for XrdHttp bridge\n    mHttpResponse = new eos::common::PlainHttpResponse();\n    mHttpResponse->SetResponseCode(0);\n    return;\n  }\n\n  if (request->GetMethod() == \"PUT\") {\n    // if (((mUploadLeftSize > (10 * 1024 * 1024)) &&\n    //      ((*request->GetBodySize()) < (10 * 1024 * 1024)))) {\n    //   // we want more bytes, we don't process this\n    //   eos_static_info(\"msg=\\\"wait for more bytes\\\" leftsize=%llu uploadsize=%llu\",\n    //                   mUploadLeftSize, *request->GetBodySize());\n    //   mHttpResponse = new eos::common::PlainHttpResponse();\n    //   mHttpResponse->SetResponseCode(eos::common::HttpResponse::CREATED);\n    //   return;\n    // }\n    // call the HttpHandler::Put method\n    mHttpResponse = Put(request);\n\n    if (!mHttpResponse || request->GetBodySize() == 0) {\n      // clean-up left-over objects on error or end-of-put\n      if (mFile) {\n        delete mFile;\n        mFile = 0;\n      }\n    }\n  }\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nS3Handler::Get(eos::common::HttpRequest* request)\n{\n  int mhd_response = eos::common::HttpResponse::OK;\n  std::string result;\n  std::map<std::string, std::string> responseheader;\n  eos::common::HttpResponse* response = 0;\n\n  if (mRangeDecodingError) {\n    response = RestErrorResponse(416, \"InvalidRange\", \"Illegal Range request\",\n                                 request->GetHeaders()[\"range\"].c_str(), \"\");\n  } else {\n    if (mRc != SFS_OK) {\n      if (mFile->error.getErrInfo() == ENOENT) {\n        response = RestErrorResponse(404, \"NoSuchKey\", \"The specified key does \"\n                                     \"not exist\", GetPath(), \"\");\n      } else if (mFile->error.getErrInfo() == EPERM) {\n        response = RestErrorResponse(403, \"AccessDenied\", \"Access Denied\",\n                                     GetPath(), \"\");\n      } else {\n        response = RestErrorResponse(500, \"InternalError\", \"File currently \"\n                                     \"unavailable\", GetPath(), \"\");\n      }\n\n      delete mFile;\n      mFile = 0;\n    } else {\n      response = new eos::common::S3Response();\n\n      if (mRangeRequest) {\n        CreateMultipartHeader(eos::common::HttpResponse::ContentType(GetPath()));\n        eos_static_info(Print());\n        char clength[16];\n        snprintf(clength, sizeof(clength) - 1, \"%llu\",\n                 (unsigned long long) mRequestSize);\n\n        if (mOffsetMap.size() == 1) {\n          // if there is only one range we don't send a multipart response\n          responseheader[\"Content-Type\"] = response->ContentType(GetPath());\n          responseheader[\"Content-Range\"] = mSinglepartHeader;\n        } else {\n          // for several ranges we send a multipart response\n          responseheader[\"Content-Type\"] = mMultipartHeader;\n        }\n\n        responseheader[\"Content-Length\"] = clength;\n        mhd_response = response->PARTIAL_CONTENT;\n      } else {\n        // successful http open\n        char clength[16];\n        snprintf(clength, sizeof(clength) - 1, \"%llu\",\n                 (unsigned long long) mFile->GetOpenSize());\n        mRequestSize = mFile->GetOpenSize();\n        response->mResponseLength = mRequestSize;\n        responseheader[\"Content-Type\"] = response->ContentType(GetPath());\n        responseheader[\"Content-Length\"] = clength;\n        mhd_response = response->OK;\n      }\n    }\n  }\n\n  if (mFile) {\n    // We want to use the file callbacks\n    response->mUseFileReaderCallback = true;\n  }\n\n  response->SetHeaders(responseheader);\n  response->SetResponseCode(mhd_response);\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nS3Handler::Put(eos::common::HttpRequest* request)\n{\n  std::map<std::string, std::string> responseheader;\n  eos::common::HttpResponse* response = 0;\n  eos_static_info(\"method=PUT offset=%llu size=%llu size_ptr=%llu\",\n                  mCurrentCallbackOffset,\n                  request->GetBodySize() ? *request->GetBodySize() : 0,\n                  request->GetBodySize());\n\n  if (mRc) {\n    // check for open errors\n    // create S3 error responses\n    if (mRc != SFS_OK) {\n      if (mFile->error.getErrInfo() == EPERM) {\n        response = RestErrorResponse(403, \"AccessDenied\", \"Access Denied\",\n                                     GetPath(), \"\");\n      } else {\n        response = RestErrorResponse(500, \"InternalError\", \"File currently \"\n                                     \"unwritable\", GetPath(), \"\");\n      }\n\n      delete mFile;\n      mFile = 0;\n    }\n  } else {\n    // file streaming in\n    size_t* bodySize = request->GetBodySize();\n\n    if (request->GetBody().c_str() && bodySize && (*bodySize)) {\n      size_t stored = mFile->write(mCurrentCallbackOffset,\n                                   request->GetBody().c_str(), *bodySize);\n\n      if (stored != *bodySize) {\n        // S3 write error\n        response = RestErrorResponse(500, \"InternalError\", \"File currently \"\n                                     \"unwritable (write failed)\", GetPath(), \"\");\n        delete mFile;\n        mFile = 0;\n      } else {\n        eos_static_info(\"msg=\\\"stored requested bytes\\\"\");\n        // decrease the upload left data size\n        mUploadLeftSize -= *bodySize;\n        mCurrentCallbackOffset += *bodySize;\n        response = new eos::common::PlainHttpResponse();\n        response->SetResponseCode(eos::common::HttpResponse::CREATED);\n        return response;\n      }\n    } else {\n      eos_static_info(\"entering close handler\");\n      mCloseCode = mFile->close();\n\n      if (mCloseCode) {\n        response = HttpServer::HttpError(\"File close failed\",\n                                         response->SERVICE_UNAVAILABLE);\n        mCloseCode = 0; // we don't want to create a second response down\n      } else {\n        response = new eos::common::PlainHttpResponse();\n        response->SetResponseCode(eos::common::HttpResponse::CREATED);\n        return response;\n      }\n    }\n  }\n\n  char sFileId[16];\n  snprintf(sFileId, sizeof(sFileId) - 1, \"%llu\", mFileId);\n  // add some S3 specific tags to the response object\n  responseheader[\"x-amz-version-id\"] = sFileId;\n  responseheader[\"x-amz-request-id\"] = mLogId;\n  responseheader[\"Server\"] = gOFS.mHostName;\n  responseheader[\"Connection\"] = \"close\";\n  responseheader[\"ETag\"] = sFileId;\n\n  if (response) {\n    delete response;\n  }\n\n  response = new eos::common::S3Response();\n  response->SetHeaders(responseheader);\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/http/s3/S3Handler.hh",
    "content": "// ----------------------------------------------------------------------\n// File: S3Handler.hh\n// Author: Andreas-Joachim Peters & Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   S3Handler.hh\n *\n * @brief  Dealing with all S3Handler goodies\n */\n\n#ifndef __EOSFST_S3_HANDLER__HH__\n#define __EOSFST_S3_HANDLER__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/http/s3/S3Handler.hh\"\n#include \"fst/http/HttpHandler.hh\"\n#include \"fst/Namespace.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n#include <string>\n#include <map>\n/*----------------------------------------------------------------------------*/\n\nEOSFSTNAMESPACE_BEGIN\n\nclass S3Store;\n\nclass S3Handler : public eos::common::S3Handler, public eos::fst::HttpHandler\n{\n\npublic:\n\n  /**\n   * Constructor\n   */\n  S3Handler () {};\n\n  /**\n   * Destructor\n   */\n  virtual ~S3Handler () {};\n\n  /**\n   * Check whether the given method and headers are a match for this protocol.\n   *\n   * @param method  the request verb used by the client (GET, PUT, etc)\n   * @param headers the map of request headers\n   *\n   * @return true if the protocol matches, false otherwise\n   */\n  static bool\n  Matches (const std::string &method, HeaderMap &headers);\n\n  /**\n   * Build a response to the given S3 request.\n   *\n   * @param request  the map of request headers sent by the client\n   * @param method   the request verb used by the client (GET, PUT, etc)\n   * @param url      the URL requested by the client\n   * @param query    the GET request query string (if any)\n   * @param body     the request body data sent by the client\n   * @param bodysize the size of the request body\n   * @param cookies  the map of cookie headers\n   */\n  void\n  HandleRequest (eos::common::HttpRequest *request);\n\n  /**\n   * Handle an S3 GET request.\n   *\n   * @param request  the client request object\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Get (eos::common::HttpRequest *request);\n\n  /**\n   * Handle an S3 PUT request.\n   *\n   * @param request  the client request object\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Put (eos::common::HttpRequest *request);\n\n};\n\n/*----------------------------------------------------------------------------*/\nEOSFSTNAMESPACE_END\n\n#endif\n\n"
  },
  {
    "path": "fst/http/xrdhttp/EosFstHttpHandler.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file EosFstHttpHandler.hh\n//! @author Andreas-Joachim Peters & Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <stdio.h>\n#include <XrdSfs/XrdSfsInterface.hh>\n#include \"common/Logging.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/http/HttpServer.hh\"\n#include \"common/http/ProtocolHandler.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/Timing.hh\"\n#include \"EosFstHttpHandler.hh\"\n\nXrdVERSIONINFO(XrdSfsGetFileSystem, EosFstHttp);\n\n//------------------------------------------------------------------------------\n// Helper function to convert hex to decimal\n//------------------------------------------------------------------------------\nstatic int decode_hex(int ch)\n{\n  if ('0' <= ch && ch <= '9') {\n    return ch - '0';\n  } else if ('A' <= ch && ch <= 'F') {\n    return ch - 'A' + 0xa;\n  } else if ('a' <= ch && ch <= 'f') {\n    return ch - 'a' + 0xa;\n  } else {\n    return -1;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Initialize handler\n//------------------------------------------------------------------------------\nint\nEosFstHttpHandler::Init(const char* cfgfile)\n{\n  if (getenv(\"EOSFSTOFS\")) {\n    OFS = (eos::fst::XrdFstOfs*)(strtoull(getenv(\"EOSFSTOFS\"), 0, 10));\n  }\n\n  std::string cfg;\n  eos::common::StringConversion::LoadFileIntoString(cfgfile, cfg);\n  size_t fpos = cfg.find(\"xrd.protocol XrdHttp:\");\n\n  if (fpos != std::string::npos) {\n    size_t epos = cfg.find(\" \", fpos + 21);\n\n    if (epos != std::string::npos) {\n      std::string port = cfg.substr(fpos + 21, epos - fpos - 21);\n      setenv(\"EOSFSTXRDHTTP\", port.c_str(), 1);\n      eos_static_notice(\"publishing XrdHttp port: %s\", port.c_str());\n    }\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Check if request should be handled by the current handler\n//------------------------------------------------------------------------------\nbool\nEosFstHttpHandler::MatchesPath(const char* verb, const char* path)\n{\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"verb=%s path=%s\", verb, path);\n  }\n\n  // Leave the XrdHttpTPC plugin deal with COPY/OPTIONS verbs\n  if ((strcmp(verb, \"COPY\") == 0) || (strcmp(verb, \"OPTIONS\") == 0)) {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Process current request\n//------------------------------------------------------------------------------\nint\nEosFstHttpHandler::ProcessReq(XrdHttpExtReq& req)\n{\n  using eos::common::HttpResponse;\n  std::string body;\n\n  if (!OFS) {\n    eos_static_crit(\"%s\", \"msg=\\\"OFS not accessible\\\"\");\n    return -1;\n  }\n\n  std::map<std::string, std::string> cookies;\n  std::map<std::string, std::string> normalized_headers;\n\n  // Normalize the input headers to lower-case\n  for (const auto& hdr : req.headers) {\n    eos_static_info(\"msg\\\"normalize hdr\\\" key=\\\"%s\\\" value=\\\"%s\\\"\",\n                    hdr.first.c_str(),  hdr.second.c_str());\n    normalized_headers[LC_STRING(hdr.first)] = hdr.second;\n  }\n\n  std::string verb = req.verb;\n  std::string query = normalized_headers.count(\"xrd-http-query\") ?\n                      normalized_headers[\"xrd-http-query\"] : \"\";\n\n  if (req.verb == \"PUT\") {\n    // CREATE makes sure the handler just opens the file and all writes\n    // are done later\n    verb = \"CREATE\";\n  }\n\n  std::unique_ptr<eos::common::ProtocolHandler>\n  handler = OFS->mHttpd->XrdHttpHandler(verb, req.resource, normalized_headers,\n                                        query, cookies, body, req.GetSecEntity());\n\n  if (handler == nullptr) {\n    std::string errmsg = \"failed to create handler\";\n    return req.SendSimpleResp(500, errmsg.c_str(), nullptr, errmsg.c_str(),\n                              errmsg.length());\n  }\n\n  eos::common::HttpResponse* response = handler->GetResponse();\n\n  if (!response) {\n    std::string errmsg = \"failed to create response object\";\n    return req.SendSimpleResp(500, errmsg.c_str(), nullptr, errmsg.c_str(),\n                              errmsg.length());\n  }\n\n  response->AddHeader(\"Date\",  eos::common::Timing::utctime(time(NULL)));\n  eos_static_debug(\"req-verb=\\\"%s\\\" resp-code=%i resp-desc=\\\"%s\\\" \"\n                   \"resp-headers: %s\", req.verb.c_str(),\n                   response->GetResponseCode(),\n                   response->GetResponseCodeDescription().c_str(),\n                   response->GetHdrsWithFilter({}).c_str());\n\n  if (req.verb == \"HEAD\") {\n    // @note: SendSimpleResp will overwrite the Content-Length header with\n    // the size of the body if body is not null and the Content-Length must\n    // not already be present in the response headers - mind blowing ...\n    return req.SendSimpleResp(response->GetResponseCode(),\n                              response->GetResponseCodeDescription().c_str(),\n                              response->GetHdrsWithFilter({HttpResponse::kContentLength}).c_str(),\n                              nullptr, response->mResponseLength);\n  }\n\n  if (req.verb == \"GET\") {\n    auto pmarkHandle = getPMarkHandle(req, normalized_headers, req.verb);\n\n    if ((response->GetResponseCode() != response->OK) &&\n        (response->GetResponseCode() != response->PARTIAL_CONTENT)) {\n      return req.SendSimpleResp(response->GetResponseCode(),\n                                response->GetResponseCodeDescription().c_str(),\n                                response->GetHdrsWithFilter({HttpResponse::kContentLength}).c_str(),\n                                response->GetBody().c_str(),\n                                response->GetBody().length());\n    } else {\n      int retc = 0;\n      // Need to update the content length determined while opening the file\n      long long content_length = 0ll;\n      auto it_hd = response->GetHeaders().find(\"Content-Length\");\n\n      if (it_hd != response->GetHeaders().end()) {\n        try {\n          content_length = std::stoll(it_hd->second);\n        } catch (...) {}\n      }\n\n      if (response->GetResponseCode() == response->PARTIAL_CONTENT) {\n        retc = req.SendSimpleResp(response->GetResponseCode(),\n                                  response->GetResponseCodeDescription().c_str(),\n                                  response->GetHdrsWithFilter({HttpResponse::kContentLength}).c_str(),\n                                  nullptr, content_length);\n      } else {\n        retc = req.SendSimpleResp(0, response->GetResponseCodeDescription().c_str(),\n                                  response->GetHdrsWithFilter({HttpResponse::kContentLength}).c_str(),\n                                  nullptr, content_length);\n      }\n\n      if (retc) {\n        return retc;\n      }\n\n      ssize_t nread = 0;\n      off_t pos = 0;\n      // allocate an IO buffer of 1M or if smaller the required content length\n      std::vector<char> buffer(content_length > (1024 * 1024) ?\n                               (1024 * 1024) : content_length);\n      bool eskip = false;\n\n      do {\n        eos_static_debug(\"pos=%llu size=%u\", pos, buffer.capacity());\n        nread = OFS->mHttpd->FileReader(handler.get(), pos, &buffer[0],\n                                        buffer.capacity());\n\n        if (nread >= 0) {\n          pos += nread;\n          retc |= req.SendSimpleResp(1, nullptr, nullptr, &buffer[0], nread);\n          eos_static_debug(\"retc=%d\", retc);\n        } else {\n          retc = -1;\n          // an error from read; something may be wrong with the file\n          // handle; skip close of file before destroying,\n          // which may stop httpHandler caching file handle.\n          eskip = true;\n        }\n      } while ((pos != content_length) && (nread > 0) && !retc);\n\n      OFS->mHttpd->FileClose(handler.get(), retc, eskip);\n      return retc;\n    }\n  }\n\n  if (req.verb == \"PUT\") {\n    auto pmarkHandle = getPMarkHandle(req, normalized_headers, req.verb);\n    bool is_chunked = (normalized_headers.count(\"transfer-encoding\") &&\n                       (normalized_headers[\"transfer-encoding\"] == \"chunked\"));\n\n    // If no content-length provided then return an error\n    if ((normalized_headers.count(\"content-length\") == 0) && !is_chunked) {\n      response->SetResponseCode(eos::common::HttpResponse::LENGTH_REQUIRED);\n    }\n\n    eos_static_debug(\"response-code=%d\", response->GetResponseCode());\n\n    if ((response->GetResponseCode() != 0) &&\n        (response->GetResponseCode() != 200)) {\n      return req.SendSimpleResp(response->GetResponseCode(),\n                                response->GetResponseCodeDescription().c_str(),\n                                response->GetHdrsWithFilter({HttpResponse::kContentLength}).c_str(),\n                                response->GetBody().c_str(),\n                                response->GetBody().length());\n    }\n\n    if (is_chunked) {\n      if (!HandleChunkUpload(req, handler.get(), normalized_headers, cookies,\n                             query)) {\n        return req.SendSimpleResp(500, \"fatal internal error\", \"during chunk upload\",\n                                  nullptr, 0);\n      }\n    } else {\n      long long content_length = 0ll;\n\n      try {\n        content_length = std::stoll(normalized_headers[\"content-length\"]);\n      } catch (...) {}\n\n      if ((response->GetResponseCode() == 0) &&\n          (normalized_headers.count(\"expect\") &&\n           (normalized_headers[\"expect\"] == \"100-continue\"))) {\n        // reply to 100-CONTINUE request\n        eos_static_debug(\"%s\", \"msg=\\\"sending 100-continue\\\"\");\n        req.SendSimpleResp(100, nullptr,\n                           response->GetHdrsWithFilter({HttpResponse::kContentLength}).c_str(),\n                           nullptr, 0);\n      }\n\n      int retc = 0;\n      long long content_left = content_length;\n      const long long eoshttp_sz = 1024 * 1024;\n      const long long xrdhttp_sz = 256 * 1024;\n      std::string body;\n\n      do {\n        long long content_read = std::min(eoshttp_sz, content_left);\n        body.clear();\n        body.reserve(content_read);\n        char* ptr = nullptr;\n        long long read_len = 0;\n\n        do {\n          size_t chunk_len = std::min(xrdhttp_sz, content_read - read_len);\n          int rb = req.BuffgetData(chunk_len, &ptr, true);\n          eos_static_debug(\"content-read=%lli rb=%i body=%u content_left=%lli\",\n                           content_read, rb, body.size(), content_left);\n\n          if (rb > 0) {\n            body.append(ptr, rb);\n            read_len += rb;\n          } else {\n            break;\n          }\n        } while (read_len < content_read);\n\n        if (read_len != content_read) {\n          eos_static_crit(\"msg=\\\"short read during PUT, expected %lu bytes\"\n                          \" but got %lu bytes\", content_read, read_len);\n          retc = -1;\n        } else {\n          retc |= OFS->mHttpd->FileWriter(handler.get(), req.verb, req.resource,\n                                          normalized_headers, query, cookies, body);\n\n          if (!retc) {\n            content_left -= content_read;\n          }\n        }\n      } while (!retc && content_left);\n\n      eos_static_debug(\"retc=%d\", retc);\n\n      if (!retc) {\n        // trigger the close handler by calling with empty body\n        body.clear();\n        retc |= OFS->mHttpd->FileWriter(handler.get(), req.verb, req.resource,\n                                        normalized_headers, query, cookies, body);\n      }\n    }\n\n    eos::common::HttpResponse* response = handler->GetResponse();\n\n    if (response && response->GetResponseCode()) {\n      return req.SendSimpleResp(response->GetResponseCode(),\n                                response->GetResponseCodeDescription().c_str(),\n                                response->GetHdrsWithFilter({HttpResponse::kContentLength}).c_str(),\n                                response->GetBody().c_str(),\n                                response->GetBody().length());\n    } else {\n      return req.SendSimpleResp(500, \"fatal internal error\", nullptr, nullptr, 0);\n    }\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Handle chunk upload operation\n//------------------------------------------------------------------------------\nbool\nEosFstHttpHandler::HandleChunkUpload(XrdHttpExtReq& req,\n                                     eos::common::ProtocolHandler* handler,\n                                     std::map<std::string, std::string>& norm_hdrs,\n                                     std::map<std::string, std::string>& cookies,\n                                     std::string& query)\n{\n  bool success = false;\n  const unsigned long long xrdhttp_sz = 256 * 1024;\n  const int max_size = 4096;\n  std::string ssize;\n  std::string chunk;\n  char* ptr = nullptr;\n  eos::common::Timing tm(\"ChunkUpload\");\n  COMMONTIMING(\"START\", &tm);\n\n  while (true) {\n    bool has_size = false;\n    ssize.clear();\n    // Counter of the amount of times we did not receive any data from the client\n    // if it is greater than a certain threshold, we will stop trying to read data from the socket\n    unsigned int noDataReceivedCpt = 0;\n    unsigned int noDataReceivedCptThreshold = max_size * 2;\n\n    // Read in line containing the chunk size\n    while (ssize.length() < max_size &&\n           noDataReceivedCpt <= noDataReceivedCptThreshold) {\n      if (req.BuffgetData(1, &ptr, true) == 1) {\n        ssize.append(ptr, 1);\n      }\n\n      size_t len = ssize.length();\n\n      if (len == 0) {\n        noDataReceivedCpt++;\n      } else {\n        noDataReceivedCpt = 0;\n      }\n\n      if ((len >= 2) && (ssize[len - 2] == '\\r') && (ssize[len - 1] == '\\n')) {\n        ssize.erase(len - 2);\n        has_size = true;\n        break;\n      }\n    }\n\n    if (noDataReceivedCpt > noDataReceivedCptThreshold) {\n      std::stringstream ss;\n      ss << \"msg=\\\"no data received from the client after \" <<\n         noDataReceivedCptThreshold << \" attempts\\\"\";\n      eos_static_err(\"%s\", ss.str().c_str());\n    }\n\n    if (!has_size) {\n      break;\n    }\n\n    // Get numeric value for the chunk size\n    unsigned long long chunk_sz = 0ull;\n\n    try {\n      size_t pos = 0;\n      chunk_sz =  std::stoull(ssize, &pos, 16);\n\n      if (pos != ssize.length()) {\n        throw std::runtime_error(\"failed to convert chunk size\");\n      }\n    } catch (...) {\n      eos_static_err(\"msg=\\\"chunk size is not a number\\\" data=\\\"%s\\\"\",\n                     ssize.c_str());\n      break;\n    }\n\n    chunk.clear();\n    chunk.reserve(chunk_sz);\n\n    // This is the final byte, read in the CRLF (\"\\r\\n\")\n    if (chunk_sz == 0) {\n      if (req.BuffgetData(2, &ptr, true) != 2) {\n        eos_static_err(\"%s\", \"msg=\\\"failed reading end message for chunk upload\\\"\");\n        break;\n      }\n\n      if ((*ptr != '\\r') || (*(++ptr) != '\\n')) {\n        eos_static_err(\"%s\", \"msg=\\\"chunk upload end message not what we expected\\\"\");\n        break;\n      }\n    } else { // This is normal chunk with data, read it in and write to the file\n      unsigned long long read_len = 0ull;\n\n      do {\n        size_t block_len = std::min(xrdhttp_sz, chunk_sz);\n        int rb = req.BuffgetData(block_len, &ptr, true);\n\n        if (rb > 0) {\n          chunk.append(ptr, rb);\n          read_len += rb;\n        } else {\n          eos_static_err(\"msg=\\\"failed to read chunk block\\\" block_len=%llu\",\n                         block_len);\n          break;\n        }\n      } while (read_len < chunk_sz);\n\n      // We read less than we expected, malformed chunk request\n      if (read_len != chunk_sz) {\n        eos_static_err(\"msg=\\\"chunk size less than what we expected\\\" len=%llu \"\n                       \"expected=%llu\", read_len, chunk_sz);\n        break;\n      }\n\n      // Read also the line separator CRLF (\"\\r\\n\")\n      if (req.BuffgetData(2, &ptr, true) != 2) {\n        eos_static_err(\"%s\", \"msg=\\\"failed reading end message for chunk upload\\\"\");\n        break;\n      }\n\n      if ((*ptr != '\\r') || (*(++ptr) != '\\n')) {\n        eos_static_err(\"%s\", \"msg=\\\"chunk upload end message not what we expected\\\"\");\n        break;\n      }\n    }\n\n    // Write the chunk to the file. Last chunk with size 0 will trigger the\n    // close handler\n    //eos_static_info(\"msg=\\\"writing chunk\\\" len=%llu data=\\\"%s\\\"\",\n    //                chunk.length(), chunk.c_str());\n    size_t wb = (size_t) OFS->mHttpd->FileWriter(handler, req.verb, req.resource,\n                norm_hdrs, query, cookies, chunk);\n\n    if (wb) {\n      eos_static_err(\"msg=\\\"failed writing chunk to file\\\" chunk_sz=%llu\",\n                     chunk.length());\n      break;\n    }\n\n    if (chunk.length() == 0) {\n      success = true;\n      break;\n    }\n  }\n\n  COMMONTIMING(\"done\", &tm);\n\n  if (EOS_LOGS_DEBUG) {\n    tm.Print();\n  }\n\n  return success;\n}\n\n//------------------------------------------------------------------------------\n// Handle chunk upload operation - optimised version\n//------------------------------------------------------------------------------\nbool\nEosFstHttpHandler::HandleChunkUpload2(XrdHttpExtReq& req,\n                                      eos::common::ProtocolHandler* handler,\n                                      std::map<std::string, std::string>& norm_hdrs,\n                                      std::map<std::string, std::string>& cookies,\n                                      std::string& query)\n{\n  enum {CHUNK_SIZE, CHUNK_CLRF1, CHUNK_CLRF2, CHUNK_DATA, ERROR};\n  int retries = 0;\n  const int max_retries = 5;\n  const unsigned long long xrdhttp_sz = 256 * 1024;\n  const unsigned long long eoshttp_sz = 1024 * 1024;\n  char* ptr = nullptr, *end_ptr = nullptr;\n  std::string chunk;\n  chunk.reserve(eoshttp_sz);\n  eos::common::Timing tm(\"ChunkUpload\");\n  COMMONTIMING(\"START\", &tm);\n  int state = CHUNK_SIZE;\n  int nread = 0;\n  int hex_count = 0;\n  long int chunk_sz = 0;\n  bool final_chunk = false;\n\n  while (true) {\n    eos_static_info(\"%s\", \"msg=\\\"calling BuffgetData\\\"\");\n    nread = req.BuffgetData(xrdhttp_sz, &ptr, false);\n    end_ptr = ptr + nread;\n    eos_static_info(\"msg=\\\"http read\\\" nread=%li\", nread);\n\n    if (nread < 0) {\n      eos_static_err(\"%s\", \"msg=\\\"got a socket error from XrdHttp\\\"\");\n      state = ERROR;\n      break;\n    } else if (nread == 0) {\n      ++retries;\n\n      if (retries > max_retries) {\n        eos_static_err(\"%s\", \"msg=\\\"reached the maximum number of retries\\\"\");\n        state = ERROR;\n        break;\n      } else {\n        eos_static_warning(\"msg=\\\"wait for more data\\\" retry=%i\", retries);\n        std::this_thread::sleep_for(std::chrono::milliseconds(500));\n        continue;\n      }\n    }\n\n    while (end_ptr - ptr != 0) {\n      switch (state) {\n      case CHUNK_SIZE:\n        int v;\n\n        if ((v = decode_hex(*ptr)) == -1) {\n          if (hex_count == 0) {\n            state = ERROR;\n          } else {\n            eos_static_info(\"msg=\\\"got chunk size\\\" chunk_sz=%li\", chunk_sz);\n            state = CHUNK_CLRF1;\n          }\n        } else {\n          chunk_sz = chunk_sz * 16 + v;\n          ++hex_count;\n          ++ptr;\n        }\n\n        break;\n\n      case CHUNK_CLRF1:\n        if (*ptr != '\\r') {\n          state = ERROR;\n        } else {\n          state = CHUNK_CLRF2;\n          ++ptr;\n        }\n\n        break;\n\n      case CHUNK_CLRF2:\n        if (*ptr != '\\n') {\n          state = ERROR;\n        } else {\n          eos_static_info(\"%s\", \"msg=\\\"done reading CLRF\\\"\");\n          ++ptr;\n\n          if (hex_count) {\n            // Entering after CHUNK_SIZE\n            hex_count = 0;\n            state = CHUNK_DATA;\n          } else {\n            // Entering after CHUNK_DATA\n            state = CHUNK_SIZE;\n          }\n        }\n\n        break;\n\n      case CHUNK_DATA:\n        if (chunk_sz == 0) {\n          if (final_chunk) {\n            eos_static_info(\"%s\", \"msg=\\\"done reading final chunk\\\"\");\n            break;\n          } else {\n            // This is the final chunk\n            final_chunk = true;\n            state = CHUNK_CLRF1;\n            eos_static_info(\"%s\", \"msg=\\\"do read final chunk\\\"\");\n          }\n        } else if (chunk_sz <= end_ptr - ptr) {\n          eos_static_info(\"msg=\\\"add data to chunk [1]\\\" sz=%li\", chunk_sz);\n          chunk.append(ptr, chunk_sz);\n          ptr += chunk_sz;\n          chunk_sz = 0;\n          state = CHUNK_CLRF1;\n        } else {\n          eos_static_info(\"msg=\\\"add data to chunk [2]\\\" sz=%li\", (end_ptr - ptr));\n          chunk.append(ptr, end_ptr - ptr);\n          chunk_sz -= (end_ptr - ptr);\n          ptr = end_ptr;\n        }\n\n        break;\n\n      case ERROR:\n        break;\n      }\n\n      if ((state == ERROR) ||\n          (final_chunk && (state = CHUNK_DATA))) {\n        break;\n      }\n    }\n\n    if (state == ERROR) {\n      eos_static_err(\"%s\", \"msg=\\\"error state\\\"\");\n      break;\n    }\n\n    // Write the chunk to the file. Last chunk with size 0 will trigger the\n    // close handler\n    if ((final_chunk && (state == CHUNK_DATA)) ||\n        (chunk.size() >= eoshttp_sz)) {\n      eos_static_info(\"msg=\\\"writing chunk\\\" len=%llu\", chunk.length());\n      size_t wb = (size_t) OFS->mHttpd->FileWriter(handler, req.verb, req.resource,\n                  norm_hdrs, query, cookies, chunk);\n\n      if (wb) {\n        eos_static_err(\"msg=\\\"failed writing chunk to file\\\" chunk_sz=%llu\",\n                       chunk.length());\n        state = ERROR;\n        break;\n      }\n\n      chunk.clear();\n\n      // For final chunk also trigger write of 0 length which closes the file\n      if (final_chunk) {\n        size_t wb = (size_t) OFS->mHttpd->FileWriter(handler, req.verb, req.resource,\n                    norm_hdrs, query, cookies, chunk);\n\n        if (wb) {\n          eos_static_err(\"msg=\\\"failed writing chunk to file\\\" chunk_sz=%llu\",\n                         chunk.length());\n          state = ERROR;\n        }\n\n        break;\n      }\n    }\n  }\n\n  COMMONTIMING(\"done\", &tm);\n\n  if (EOS_LOGS_DEBUG) {\n    tm.Print();\n  }\n\n  return (state == CHUNK_DATA);\n}\n\nstd::unique_ptr<XrdNetPMark::Handle> EosFstHttpHandler::getPMarkHandle(\n  XrdHttpExtReq& req, const std::map<std::string, std::string>&\n  normalized_headers, const std::string& verb)\n{\n  if (req.pmark && normalized_headers.count(\"scitag\")) {\n    // With the new scitag specifications, we now have to tell XRootD PMark handler code whether the HTTP transfer is a GET or a PUT\n    // so the different fields populated in the firefly matches the new specifications (i.e: fireflies are emitted on behalf of the data sender part of a transfer)\n    std::string scitagOpaque = \"scitag.flow=\" + std::to_string(\n                                 req.mSciTag) + \"&pmark.appname=\" + verb == \"GET\" ? \"http-get\" : \"http-put\";\n    return std::unique_ptr<XrdNetPMark::Handle>(req.pmark->Begin(*\n           (const_cast<XrdSecEntity*>(&req.GetSecEntity())),\n           req.resource.c_str(),\n           scitagOpaque.c_str(),\n           \"http\"));\n  }\n\n  return nullptr;\n}\n"
  },
  {
    "path": "fst/http/xrdhttp/EosFstHttpHandler.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file EosFstHttpHandler.cc\n//! @author Andreas-Joachim Peters & Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include <map>\n#include <string>\n#include <XrdHttp/XrdHttpExtHandler.hh>\n#include <XrdVersion.hh>\n\nXrdVERSIONINFO(XrdHttpGetExtHandler, EOSFSTHTTP);\n\nclass XrdLink;\nclass XrdSecEntity;\nclass XrdHttpReq;\nclass XrdHttpProtocol;\n\n//------------------------------------------------------------------------------\n//! Class EosFstHttpHandler\n//------------------------------------------------------------------------------\nclass EosFstHttpHandler : public XrdHttpExtHandler\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  EosFstHttpHandler() = default;\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~EosFstHttpHandler() = default;\n\n  bool MatchesPath(const char* verb, const char* path);\n\n  int ProcessReq(XrdHttpExtReq&);\n\n  int Init(const char* cfgfile);\n\nprivate:\n  eos::fst::XrdFstOfs* OFS;\n\n  //----------------------------------------------------------------------------\n  //! Handle chunk upload operation\n  //!\n  //! @param req http external request object\n  //! @param handler eos protocol handler object for file operations\n  //! @param norm_hdrs normalized headers\n  //! @param cookies cookies\n  //! @param query query string\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool HandleChunkUpload(XrdHttpExtReq& req,\n                         eos::common::ProtocolHandler* handler,\n                         std::map<std::string, std::string>& norm_hdrs,\n                         std::map<std::string, std::string>& cookies,\n                         std::string& query);\n\n  //----------------------------------------------------------------------------\n  //! Handle chunk upload operation\n  //!\n  //! @param req http external request object\n  //! @param handler eos protocol handler object for file operations\n  //! @param norm_hdrs normalized headers\n  //! @param cookies cookies\n  //! @param query query string\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool HandleChunkUpload2(XrdHttpExtReq& req,\n                          eos::common::ProtocolHandler* handler,\n                          std::map<std::string, std::string>& norm_hdrs,\n                          std::map<std::string, std::string>& cookies,\n                          std::string& query);\n\n  //----------------------------------------------------------------------------\n  //! Convenient function to get a packet marking handle\n  //!\n  //! @param req http external request object\n  //! @param normalized_headers the normalized headers coming from the client\n  //! @param verb the HTTP verb allowing to determine if that transfer is a PUT or a GET\n  //!\n  //! @return the packet marking handle if the packet marking is enabled and if the normalized_headers contain\n  //! a scitag, nullptr otherwise\n  //----------------------------------------------------------------------------\n  std::unique_ptr<XrdNetPMark::Handle> getPMarkHandle(XrdHttpExtReq& req,const std::map<std::string, std::string> & normalized_headers, const std::string & verb);\n};\n\n/******************************************************************************/\n/*                    X r d H t t p G e t E x t H a n d l e   r               */\n/******************************************************************************/\n\n//------------------------------------------------------------------------------\n//! Obtain an instance of the XrdHttpExtHandler object.\n//!\n//! This extern \"C\" function is called when a shared library plug-in containing\n//! implementation of this class is loaded. It must exist in the shared library\n//! and must be thread-safe.\n//!\n//! @param  eDest -> The error object that must be used to print any errors or\n//!                  other messages (see XrdSysError.hh).\n//! @param  confg -> Name of the configuration file that was used. This pointer\n//!                  may be null though that would be impossible.\n//! @param  parms -> Argument string specified on the namelib directive. It may\n//!                  be null or point to a null string if no parms exist.\n//! @param  myEnv -> Environment variables for configuring the external handler;\n//!                  it my be null.\n//!\n//! @return Success: A pointer to an instance of the XrdHttpSecXtractor object.\n//!         Failure: A null pointer which causes initialization to fail.\n//!\n//------------------------------------------------------------------------------\nclass XrdSysError;\nclass XrdOucEnv;\n\n#define XrdHttpExtHandlerArgs XrdSysError       *eDest, \\\n                              const char        *confg, \\\n                              const char        *parms, \\\n                              XrdOucEnv         *myEnv\n\nextern \"C\" XrdHttpExtHandler* XrdHttpGetExtHandler(XrdHttpExtHandlerArgs)\n{\n  XrdHttpExtHandler* handler = new EosFstHttpHandler();\n  handler->Init(confg);\n  return handler;\n}\n"
  },
  {
    "path": "fst/http/xrdhttp/README.md",
    "content": "XrdHttp\n-------\n\nHTTP(S) using the XRootD thread-pool and XrdHttp can be enabled in ```/etc/xrd.cf.fst``` like:\n\n\n```\nif exec xrootd\n   xrd.protocol XrdHttp:9000 /usr/lib64/libXrdHttp-4.so\n   http.exthandler EosFstHttp /usr/lib64/libEosFstHttp.so none\n   http.cert /etc/grid-security/daemon/host.cert\n   http.key /etc/grid-security/daemon/privkey.pem\n   http.cafile /etc/grid-security/daemon/ca.cert\nfi\n```\n\nTo disable HTTPS you can remove the cert/key/cafile directives. \nThe targetport in redirection is currently taken from the sysconfig file and uses ```EOS_FST_HTTP_PORT+1000```.\nThe protocol used for data transfers is configured on the MGM. By defaul HTTPS access redirects to HTTP on the data server if not modified in the MGM configuration file.\n\n\n\n"
  },
  {
    "path": "fst/io/AsyncMetaHandler.cc",
    "content": "//------------------------------------------------------------------------------\n// File: AsyncMetaHandler.cc\n// Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/io/ChunkHandler.hh\"\n#include \"fst/io/VectChunkHandler.hh\"\n#include \"fst/io/AsyncMetaHandler.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n///! maximum number of obj in cache used for recycling\nconst unsigned int AsyncMetaHandler::msMaxNumAsyncObj = 20;\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nAsyncMetaHandler::AsyncMetaHandler() :\n  eos::common::LogId(),\n  mErrorType(XrdCl::errNone),\n  mAsyncReq(0),\n  mAsyncVReq(0),\n  mHandlerDel(NULL),\n  mVHandlerDel(NULL)\n{\n  mCond = XrdSysCondVar(0);\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nAsyncMetaHandler::~AsyncMetaHandler()\n{\n  // Drop all chunks handlers\n  ChunkHandler* ptr_chunk = NULL;\n\n  while (!mQRecycle.empty()) {\n    if (mQRecycle.try_pop(ptr_chunk)) {\n      delete ptr_chunk;\n      ptr_chunk = 0;\n    }\n  }\n\n  // Drop all vector handlers\n  VectChunkHandler* ptr_vchunk = NULL;\n\n  while (!mQVRecycle.empty()) {\n    if (mQVRecycle.try_pop(ptr_vchunk)) {\n      delete ptr_vchunk;\n      ptr_vchunk = 0;\n    }\n  }\n\n  if (mHandlerDel) {\n    delete mHandlerDel;\n    mHandlerDel = 0;\n  }\n\n  if (mVHandlerDel) {\n    delete mVHandlerDel;\n    mVHandlerDel = 0;\n  }\n\n  mErrors.clear();\n}\n\n//------------------------------------------------------------------------------\n// Register a new handler for the current file\n//------------------------------------------------------------------------------\nChunkHandler*\nAsyncMetaHandler::Register(uint64_t offset, uint32_t length, char* buffer,\n                           bool isWrite)\n{\n  ChunkHandler* ptr_chunk = NULL;\n  mCond.Lock();  // -->\n\n  // If any of the the previous requests failed with a timeout then stop trying\n  // and return an error\n  if (mErrorType == XrdCl::errOperationExpired) {\n    mCond.UnLock(); // <--\n    return NULL;\n  }\n\n  mAsyncReq++;\n\n  if (mQRecycle.size() + mAsyncReq >= msMaxNumAsyncObj) {\n    mCond.UnLock();   // <--\n    mQRecycle.wait_pop(ptr_chunk);\n    ptr_chunk->Update(this, offset, length, buffer, isWrite);\n  } else {\n    // Create new request\n    mCond.UnLock();   // <--\n    ptr_chunk = new ChunkHandler(this, offset, length, buffer, isWrite);\n  }\n\n  return ptr_chunk;\n}\n\n//------------------------------------------------------------------------------\n// Register a new vector request for the current file\n//------------------------------------------------------------------------------\nVectChunkHandler*\nAsyncMetaHandler::Register(XrdCl::ChunkList& chunkList, const char* wrBuf,\n                           bool isWrite)\n{\n  VectChunkHandler* ptr_vchunk = NULL;\n  mCond.Lock();  // -->\n\n  // If any of the the previous requests failed with a timeout then stop trying\n  // and return an error\n  if (mErrorType == XrdCl::errOperationExpired) {\n    mCond.UnLock(); // <--\n    return NULL;\n  }\n\n  mAsyncVReq++;\n\n  if (mQVRecycle.size() + mAsyncVReq >= msMaxNumAsyncObj) {\n    mCond.UnLock();   // <--\n    mQVRecycle.wait_pop(ptr_vchunk);\n    ptr_vchunk->Update(this, chunkList, wrBuf, isWrite);\n  } else {\n    // Create new request\n    mCond.UnLock();   // <--\n    ptr_vchunk = new VectChunkHandler(this, chunkList, wrBuf, isWrite);\n  }\n\n  return ptr_vchunk;\n}\n\n//------------------------------------------------------------------------------\n// Handle response\n//------------------------------------------------------------------------------\nvoid\nAsyncMetaHandler::HandleResponse(XrdCl::XRootDStatus* pStatus,\n                                 ChunkHandler* chunk)\n{\n  mCond.Lock(); // -->\n\n  // See last comment for motivation\n  if (mHandlerDel) {\n    delete mHandlerDel;\n    mHandlerDel = NULL;\n  }\n\n  if (pStatus->status != XrdCl::stOK) {\n    eos_debug(\"msg=\\\"got error message\\\" status=%u code=%u errNo=%lu\",\n              pStatus->status, pStatus->code, (unsigned long)pStatus->errNo);\n    mErrors.push_back(XrdCl::ChunkInfo(chunk->GetOffset(),\n                                       chunk->GetLength(),\n                                       (void*)chunk->GetBuffer()));\n\n    // If we got a timeout in the previous requests then we keep the error code\n    if (mErrorType != XrdCl::errOperationExpired) {\n      mErrorType = pStatus->code;\n\n      if (mErrorType == XrdCl::errOperationExpired) {\n        eos_debug(\"msg=\\\"got a timeout for request\\\" off=%zu, len=%lu\",\n                  chunk->GetOffset(), (unsigned long)chunk->GetLength());\n      }\n    }\n  }\n\n  if (--mAsyncReq == 0) {\n    mCond.Broadcast();\n  }\n\n  if (!mQRecycle.push_size(chunk, msMaxNumAsyncObj)) {\n    // Save the pointer to the chunk object to be deleted by the next arriving\n    // response. This can not be done here as we are currently called from the\n    // same chunk handler object that we want to delete.\n    mHandlerDel = chunk;\n  }\n\n  mCond.UnLock();  // <--\n}\n\n//------------------------------------------------------------------------------\n//! Handle response vector response\n//------------------------------------------------------------------------------\nvoid\nAsyncMetaHandler::HandleResponse(XrdCl::XRootDStatus* pStatus,\n                                 VectChunkHandler* vhandler)\n{\n  mCond.Lock(); // -->\n\n  // See last comment for motivation\n  if (mVHandlerDel) {\n    delete mVHandlerDel;\n    mVHandlerDel = NULL;\n  }\n\n  if (pStatus->status != XrdCl::stOK) {\n    eos_debug(\"msg=\\\"got error message for readv\\\" status=%u code=%u errNo=%lu\",\n              pStatus->status, pStatus->code, (unsigned long)pStatus->errNo);\n    // Add all the chunks of the current failed vector read to the list of\n    // errrors to be recovered\n    XrdCl::ChunkList chunkList = vhandler->GetChunkList();\n    mErrors.insert(mErrors.end(), chunkList.begin(), chunkList.end());\n\n    // If we got a timeout in the previous requests then we keep the error code\n    if (mErrorType != XrdCl::errOperationExpired) {\n      mErrorType = pStatus->code;\n\n      if (mErrorType == XrdCl::errOperationExpired) {\n        eos_debug(\"%s\", \"msg=\\\"got  timeout error for readv\\\"\");\n      }\n    }\n  }\n\n  if (--mAsyncVReq == 0) {\n    mCond.Broadcast();\n  }\n\n  if (!mQVRecycle.push_size(vhandler, msMaxNumAsyncObj)) {\n    // Save the pointer to the chunk object to be deleted by the next arriving\n    // response. This can not be done here as we are currently called from the\n    // same chunk handler object that we want to delete.\n    mVHandlerDel = vhandler;\n  }\n\n  mCond.UnLock();  // <--\n}\n\n//------------------------------------------------------------------------------\n// Get map of errors\n//------------------------------------------------------------------------------\nconst XrdCl::ChunkList&\nAsyncMetaHandler::GetErrors()\n{\n  return mErrors;\n}\n\n//------------------------------------------------------------------------------\n// Wait for responses\n//------------------------------------------------------------------------------\nuint16_t\nAsyncMetaHandler::WaitOK()\n{\n  uint16_t ret = XrdCl::errNone;\n  mCond.Lock();   // -->\n\n  while (mAsyncReq > 0) {\n    mCond.Wait();\n  }\n\n  while (mAsyncVReq > 0) {\n    mCond.Wait();\n  }\n\n  ret = mErrorType;\n  mCond.UnLock(); // <--\n  return ret;\n}\n\n//------------------------------------------------------------------------------\n// Reset\n//------------------------------------------------------------------------------\nvoid\nAsyncMetaHandler::Reset()\n{\n  mCond.Lock();\n  mErrorType = XrdCl::errNone;\n  mAsyncReq = 0;\n  mAsyncVReq = 0;\n  mErrors.clear();\n  mCond.UnLock();\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/io/AsyncMetaHandler.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file AsyncMetaHandler.hh\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Class for handling async responses from xrootd for one file\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/Namespace.hh\"\n#include <XrdCl/XrdClXRootDResponses.hh>\n#include \"common/ConcurrentQueue.hh\"\n#include \"common/Logging.hh\"\n\n#ifndef __EOS_FST_ASYNCMETAHANDLER_HH__\n#define __EOS_FST_ASYNCMETAHANDLER_HH__\n\nEOSFSTNAMESPACE_BEGIN\n\n//! Forward declaration\nclass ChunkHandler;\nclass VectChunkHandler;\n\n//------------------------------------------------------------------------------\n//! Class for handling async responses\n//------------------------------------------------------------------------------\nclass AsyncMetaHandler: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  AsyncMetaHandler();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~AsyncMetaHandler();\n\n  //----------------------------------------------------------------------------\n  //! Register a new request for the current file\n  //!\n  //! @param offset request offset\n  //! @param length request length\n  //! @param buffer holder for the data\n  //! @param isWrite set if it is a write request\n  //!\n  //! @return new chunk async handler object\n  //----------------------------------------------------------------------------\n  ChunkHandler* Register(uint64_t offset, uint32_t length, char* buffer,\n                         bool isWrite);\n\n  //----------------------------------------------------------------------------\n  //! Register a new vector request for the current file\n  //!\n  //! @param chunks list of chunks used for the vector request\n  //! @param wrBuff write buffer, ignored if it's a read operation\n  //! @param isWrite set if it is a write request\n  //!\n  //! @return new vector chunk async handler object\n  //----------------------------------------------------------------------------\n  VectChunkHandler* Register(XrdCl::ChunkList& chunks, const char* wrBuf,\n                             bool isWrite);\n\n  //----------------------------------------------------------------------------\n  //! Handle response normal response\n  //!\n  //! @param pStatus status of the request\n  //! @param chunk received chunk response\n  //----------------------------------------------------------------------------\n  virtual void HandleResponse(XrdCl::XRootDStatus* pStatus,\n                              ChunkHandler* chunk);\n\n  //----------------------------------------------------------------------------\n  //! Handle response vector response\n  //!\n  //! @param pStatus status of the request\n  //! @param chunks received vector response\n  //!\n  //----------------------------------------------------------------------------\n  virtual void HandleResponse(XrdCl::XRootDStatus* pStatus,\n                              VectChunkHandler* chunks);\n\n  //----------------------------------------------------------------------------\n  //! Wait for responses\n  //!\n  //! @return error type, if no error occurs return XrdCl::errNone\n  //!  For further details on possible error codes look into XrdClStatus.hh\n  //----------------------------------------------------------------------------\n  uint16_t WaitOK();\n\n  //----------------------------------------------------------------------------\n  //! Get map of errors\n  //!\n  //! @return map of errors\n  //----------------------------------------------------------------------------\n  const XrdCl::ChunkList& GetErrors();\n\n  //----------------------------------------------------------------------------\n  //! Reset\n  //----------------------------------------------------------------------------\n  void Reset();\n\nprivate:\n  uint16_t mErrorType; ///< type of error, we are mostly interested in timeouts\n  //! number of async requests in flight (for which no response was received)\n  uint32_t mAsyncReq;\n  //! number of async VECTOR req. in flight (for which no response was received)\n  uint32_t mAsyncVReq;\n  //! condition variable to signal the receival of all responses\n  XrdSysCondVar mCond;\n  ChunkHandler* mHandlerDel; ///< pointer to handler to be deleted\n  VectChunkHandler* mVHandlerDel; ///< pointer to VECTOR handler to be deleted\n  //! recyclable chunk handlers\n  eos::common::ConcurrentQueue<ChunkHandler*> mQRecycle;\n  //! recyclable vector handlers\n  eos::common::ConcurrentQueue<VectChunkHandler*> mQVRecycle;\n  XrdCl::ChunkList mErrors; ///< chunks for which the request failed\n  //! Maxium number of async requests in flight and also the maximum number\n  //! of ChunkHandler object that can be saved in cache\n  static const unsigned int msMaxNumAsyncObj;\n};\n\nEOSFSTNAMESPACE_END\n\n#endif // __EOS_FST_ASYNCMETAHANDLER_HH__\n"
  },
  {
    "path": "fst/io/ChunkHandler.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ChunkHandler.cc\n// Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/io/AsyncMetaHandler.hh\"\n#include \"fst/io/ChunkHandler.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nChunkHandler::ChunkHandler(AsyncMetaHandler* metaHandler, uint64_t offset,\n                           uint32_t length, char* buff, bool isWrite) :\n  XrdCl::ResponseHandler(),\n  mBuffer(buff),\n  mMetaHandler(metaHandler),\n  mOffset(offset),\n  mLength(length),\n  mCapacity(0),\n  mRespLength(0),\n  mIsWrite(isWrite)\n{\n  if (mIsWrite) {\n    mCapacity = length;\n    mBuffer = static_cast<char*>(calloc(mCapacity, sizeof(char)));\n\n    if (mBuffer) {\n      mBuffer = static_cast<char*>(memcpy(mBuffer, buff, length));\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nChunkHandler::~ChunkHandler()\n{\n  if (mIsWrite && mBuffer) {\n    free(mBuffer);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Update function\n//------------------------------------------------------------------------------\nvoid\nChunkHandler::Update(AsyncMetaHandler* metaHandler,  uint64_t offset,\n                     uint32_t length, char* buff, bool isWrite)\n{\n  mMetaHandler = metaHandler;\n  mOffset = offset;\n  mLength = length;\n  mRespLength = 0;\n\n  if (mIsWrite && !isWrite) {\n    // write -> read\n    free(mBuffer);\n    mBuffer = buff;\n    mCapacity = 0;\n  } else if (!mIsWrite && !isWrite) {\n    // read -> read\n    mBuffer = buff;\n  } else if (mIsWrite && isWrite) {\n    // write -> write\n    if (length > mCapacity) {\n      mCapacity = length;\n      mBuffer = static_cast<char*>(realloc(mBuffer, mCapacity));\n    }\n\n    mBuffer = static_cast<char*>(memcpy(mBuffer, buff, length));\n  } else {\n    // read -> write\n    mCapacity = length;\n    mBuffer = static_cast<char*>(calloc(mCapacity, sizeof(char)));\n    mBuffer = static_cast<char*>(memcpy(mBuffer, buff, length));\n  }\n\n  mIsWrite = isWrite;\n}\n\n//------------------------------------------------------------------------------\n// Handle response\n//------------------------------------------------------------------------------\nvoid\nChunkHandler::HandleResponse(XrdCl::XRootDStatus* pStatus,\n                             XrdCl::AnyObject* pResponse)\n{\n  // Do some extra check for the read case\n  if ((mIsWrite == false) && (pResponse)) {\n    XrdCl::ChunkInfo* chunk = 0;\n    pResponse->Get(chunk);\n    mRespLength = chunk->length;\n\n    // Notice if we received less then we initially requested - usually this means\n    // we reached the end of the file, but we will treat it as an error\n    if (mLength != mRespLength) {\n      pStatus->status = XrdCl::stError;\n      pStatus->code = XrdCl::errErrorResponse;\n    }\n  }\n\n  if (pResponse) {\n    delete pResponse;\n  }\n\n  mMetaHandler->HandleResponse(pStatus, this);\n  delete pStatus;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/io/ChunkHandler.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file ChunkHandler.hh\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Class holding information about an asynchronous request and a pointer\n//!        to the file the request belongs to. This class notifies the\n//!        AsyncMetaHandler corresponding to the file object of any errors during\n//!        transfers\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOS_FST_CHUNKHANDLER_HH__\n#define __EOS_FST_CHUNKHANDLER_HH__\n\n#include <XrdCl/XrdClFile.hh>\n#include \"fst/Namespace.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\nclass AsyncMetaHandler;\n\n//------------------------------------------------------------------------------\n//! Class holding information about an asynchronous request\n//------------------------------------------------------------------------------\nclass ChunkHandler : public XrdCl::ResponseHandler\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param reqHandler handler to the file meta handler\n  //! @param offset request offset\n  //! @param length request length\n  //! @param buff pointer to the read or write buffer\n  //! @param isWrite chunk belongs to a write request\n  //----------------------------------------------------------------------------\n  ChunkHandler(AsyncMetaHandler* reqHandler, uint64_t offset, uint32_t length,\n               char* buff, bool isWrite);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~ChunkHandler();\n\n  //----------------------------------------------------------------------------\n  //! Update function\n  //!\n  //! @param reqHandler handler to the file meta handler\n  //! @param offset request offset\n  //! @param length request length\n  //! @param buffer pointer to the read or write buffer\n  //! @param isWrite chunk belongs to a write request\n  //----------------------------------------------------------------------------\n  void Update(AsyncMetaHandler* reqHandler, uint64_t offset, uint32_t length,\n              char* buff, bool isWrite);\n\n  //----------------------------------------------------------------------------\n  //! Handle response\n  //!\n  //! @param pStatus status of the response\n  //! @param pResponse object containing extra info about the response\n  //----------------------------------------------------------------------------\n  virtual void HandleResponse(XrdCl::XRootDStatus* pStatus,\n                              XrdCl::AnyObject* pResponse);\n\n  //----------------------------------------------------------------------------\n  //! Get buffer pointer\n  //----------------------------------------------------------------------------\n  inline char*\n  GetBuffer() const\n  {\n    return mBuffer;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Get request chunk offset\n  //----------------------------------------------------------------------------\n  inline uint64_t\n  GetOffset() const\n  {\n    return mOffset;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get request chunk length\n  //----------------------------------------------------------------------------\n  inline uint32_t\n  GetLength() const\n  {\n    return mLength;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get response chunk length\n  //----------------------------------------------------------------------------\n  inline uint32_t\n  GetRespLength() const\n  {\n    return mRespLength;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Test if chunk is from a write operation\n  //----------------------------------------------------------------------------\n  inline bool\n  IsWrite() const\n  {\n    return mIsWrite;\n  }\n\nprivate:\n  char* mBuffer;  ///< holder for data for write requests\n  AsyncMetaHandler* mMetaHandler; ///< handler to the whole file meta handler\n  uint64_t mOffset; ///< offset of the request\n  uint32_t mLength; ///< length of the request\n  uint32_t mCapacity; ///< capacity of the buffer\n  uint32_t mRespLength; ///< length of response received, only for reads\n  bool mIsWrite; ///< operation type is write\n};\n\nEOSFSTNAMESPACE_END\n\n#endif   //  __EOS_FST_CHUNKHANDLER_HH__\n"
  },
  {
    "path": "fst/io/FileIo.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file FileIo.cc\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Abstract class modelling an IO plugin\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/io/FileIo.hh\"\n#ifdef HAVE_NFS\n#include \"fst/io/nfs/NfsIo.hh\"\n#endif\n\nEOSFSTNAMESPACE_BEGIN\n\n//--------------------------------------------------------------------------\n//! Rename operation\n//--------------------------------------------------------------------------\nint FileIo::fsRename(std::string old_path, std::string new_path)\n{\n  if ((old_path.find(\"nfs:/\") == 0) || (new_path.find(\"nfs:/\") == 0)) {\n#ifdef HAVE_NFS\n    return NfsIo::fsRename(old_path, new_path);\n#endif\n    eos_static_crit(\"%s\", \"msg=\\\"no NFS built-in support!\\\"\");\n    return ENOTSUP;\n  }\n\n  return ::rename(old_path.c_str(), new_path.c_str());\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/io/FileIo.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file FileIo.hh\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Abstract class modelling an IO plugin\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOS_FST_FILEIO_HH__\n#define __EOS_FST_FILEIO_HH__\n\n#include \"common/Logging.hh\"\n#include \"common/Statfs.hh\"\n#include \"fst/Namespace.hh\"\n#include \"fst/XrdFstOfsFile.hh\"\n#include <string>\n#include <list>\n#include <future>\n\nEOSFSTNAMESPACE_BEGIN\n\nclass FileIo : public eos::common::LogId\n{\npublic:\n  //--------------------------------------------------------------------------\n  //! Default constructor\n  //--------------------------------------------------------------------------\n  FileIo() {}\n\n  //--------------------------------------------------------------------------\n  //! Constructor with paramters\n  //!\n  //! @param path the path associated with this plugin instance\n  //! @param ioType the type of this plugin instance\n  //--------------------------------------------------------------------------\n  FileIo(std::string path, std::string ioType) :\n    eos::common::LogId(),\n    mFilePath(path),\n    mType(ioType),\n    mLastUrl(\"\"),\n    mLastErrMsg(\"\"),\n    mLastErrCode(0),\n    mLastErrNo(0),\n    mIsOpen(false)\n  {}\n\n  //--------------------------------------------------------------------------\n  //! Destructor\n  //--------------------------------------------------------------------------\n  virtual ~FileIo() {}\n\n  //--------------------------------------------------------------------------\n  //! Get stat information about the file\n  //!\n  //! @param path file path (can contain protocol specific info eg. nfs://)\n  //!\n  //! @return 0 if successful, otherwise non-zero\n  //--------------------------------------------------------------------------\n  static int fsStat(std::string path, struct stat& info)\n  {\n    if (path.find(\"nfs:/\") == 0) {\n      path.erase(0, 5); // remove \"nfs:/\" prefix\n    }\n\n    return ::stat(path.c_str(), &info);\n  }\n\n  //--------------------------------------------------------------------------\n  //! Rename operation\n  //!\n  //! @param old_path old path\n  //! @param new_path new path\n  //!\n  //! @return 0 if successful, otherwise non-zero\n  //--------------------------------------------------------------------------\n  static int fsRename(std::string old_path, std::string new_path);\n\n  //--------------------------------------------------------------------------\n  //! Open file\n  //!\n  //! @param flags open flags\n  //! @param mode open mode\n  //! @param opaque opaque information\n  //! @param timeout timeout value\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  virtual int fileOpen(XrdSfsFileOpenMode flags,\n                       mode_t mode = 0,\n                       const std::string& opaque = \"\",\n                       uint16_t timeout = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Open file asynchronously\n  //!\n  //! @param flags open flags\n  //! @param mode open mode\n  //! @param opaque opaque info to be appended to the request\n  //! @param timeout operation timeout\n  //!\n  //! @return future holding the status response\n  //--------------------------------------------------------------------------\n  virtual std::future<XrdCl::XRootDStatus>\n  fileOpenAsync(XrdSfsFileOpenMode flags, mode_t mode = 0,\n                const std::string& opaque = \"\", uint16_t timeout = 0) = 0;\n\n  //--------------------------------------------------------------------------\n  //! Read from file - sync\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read or -1 if error\n  //--------------------------------------------------------------------------\n  virtual int64_t fileRead(XrdSfsFileOffset offset, char* buffer,\n                           XrdSfsXferSize length, uint16_t timeout = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Read from file with prefetching\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read or -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t fileReadPrefetch(XrdSfsFileOffset offset, char* buffer,\n                                   XrdSfsXferSize length, uint16_t timeout = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Read from file asynchronously\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read or -1 if error\n  //! @note The buffer given by the user is not neccessarily populated with\n  //!       any meaningful data when this function returns. The user should call\n  //!       fileWaitAsyncIO to enforce this guarantee.\n  //----------------------------------------------------------------------------\n  virtual int64_t fileReadAsync(XrdSfsFileOffset offset, char* buffer,\n                                XrdSfsXferSize length, uint16_t timeout = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Vector read - sync\n  //!\n  //! @param chunkList list of chunks for the vector read\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read of -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t fileReadV(XrdCl::ChunkList& chunkList,\n                            uint16_t timeout = 0) = 0;\n\n  //------------------------------------------------------------------------------\n  //! Vector read - async\n  //!\n  //! @param chunkList list of chunks for the vector read\n  //! @param timeout timeout value\n  //!\n  //! @return 0(SFS_OK) if request successfully sent, otherwise -1(SFS_ERROR)\n  //------------------------------------------------------------------------------\n  virtual int64_t fileReadVAsync(XrdCl::ChunkList& chunkList,\n                                 uint16_t timeout = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Write to file - sync\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes written or -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t fileWrite(XrdSfsFileOffset offset, const char* buffer,\n                            XrdSfsXferSize length, uint16_t timeout = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Write to file - async\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes written or -1 if error\n  //--------------------------------------------------------------------------\n  virtual int64_t fileWriteAsync(XrdSfsFileOffset offset, const char* buffer,\n                                 XrdSfsXferSize length,\n                                 uint16_t timeout = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Write to file - async\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //!\n  //! @return future holding the status response\n  //--------------------------------------------------------------------------\n  virtual std::future<XrdCl::XRootDStatus>\n  fileWriteAsync(const char* buffer, XrdSfsFileOffset offset,\n                 XrdSfsXferSize length) = 0;\n\n  //--------------------------------------------------------------------------\n  //! Wait for all async IO\n  //!\n  //! @return global return code of async IO\n  //--------------------------------------------------------------------------\n  virtual int fileWaitAsyncIO()\n  {\n    return 0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Truncate\n  //!\n  //! @param offset truncate file to this value\n  //! @param timeout timeout value\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  virtual int fileTruncate(XrdSfsFileOffset offset, uint16_t timeout = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Truncate asynchronous\n  //!\n  //! @param offset truncate file to this value\n  //! @param timeout timeout value\n  //!\n  //! @return future holding the status response\n  //----------------------------------------------------------------------------\n  virtual std::future<XrdCl::XRootDStatus>\n  fileTruncateAsync(XrdSfsFileOffset offset, uint16_t timeout = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Allocate file space\n  //!\n  //! @param length space to be allocated\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileFallocate(XrdSfsFileOffset length) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Deallocate file space\n  //!\n  //! @param fromOffset offset start\n  //! @param toOffset offset end\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileFdeallocate(XrdSfsFileOffset fromOffset,\n                              XrdSfsFileOffset toOffset) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove file\n  //!\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileRemove(uint16_t timeout = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Sync file to disk\n  //!\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileSync(uint16_t timeout = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get pointer to async meta handler object\n  //!\n  //! @return pointer to async handler, NULL otherwise\n  //----------------------------------------------------------------------------\n  virtual void* fileGetAsyncHandler() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Check for the existence of a file\n  //!\n  //! @param path to the file\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileExists() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Close file\n  //!\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileClose(uint16_t timeout = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get stats about the file\n  //!\n  //! @param buf stat buffer\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileStat(struct stat* buf, uint16_t timeout = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Execute implementation dependant commands\n  //!\n  //! @param buf stat buffer\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileFctl(const std::string& cmd, uint16_t timeout = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set a binary attribute (name has to start with 'user.' !!!)\n  //!\n  //! @param name attribute name\n  //! @param value attribute value\n  //! @param len value length\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int attrSet(const char* name, const char* value, size_t len) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set a binary attribute (name has to start with 'user.' !!!)\n  //!\n  //! @param name attribute name\n  //! @param value attribute value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int attrSet(std::string name, std::string value) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get a binary attribute by name\n  //!\n  //! @param name attribute name\n  //! @param value contains attribute value upon success\n  //! @param size the buffer size, after success the value size\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int attrGet(const char* name, char* value, size_t& size) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get a binary attribute by name\n  //!\n  //! @param name attribute name\n  //! @param value contains attribute value upon success\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int attrGet(std::string name, std::string& value) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Delete a binary attribute by name\n  //!\n  //! @param name attribute name\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int attrDelete(const char* name) = 0;\n\n  //----------------------------------------------------------------------------\n  //! List all attributes for the associated path\n  //!\n  //! @param list contains all attribute names for the set path upon success\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int attrList(std::vector<std::string>& list) = 0;\n\n\n  //----------------------------------------------------------------------------\n  //! FtsHandle nested class\n  //----------------------------------------------------------------------------\n  class FtsHandle\n  {\n  public:\n    FtsHandle(const char* dirp) : mPath(dirp) {}\n    virtual ~FtsHandle() {}\n\n  protected:\n    std::string mPath;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Open a cursor to traverse a storage system\n  //!\n  //! @param options options for traversing the hierarchy\n  //!\n  //! @return returns implementation dependent handle or 0 in case of error\n  //----------------------------------------------------------------------------\n  virtual FileIo::FtsHandle* ftsOpen(int options = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get pointer to async meta handler object\n  //!\n  //! @param fts_handle cursor obtained by ftsOpen\n  //!\n  //! @return returns implementation dependent handle or 0 in case of error\n  //----------------------------------------------------------------------------\n  virtual std::string ftsRead(FtsHandle* handle) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Close a traversal cursor\n  //!\n  //! @param fts_handle cursor to close\n  //!\n  //! @return 0 if fts_handle was an open cursor, otherwise -1\n  //----------------------------------------------------------------------------\n  virtual int ftsClose(FtsHandle* handle) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Plug-in function to fill a statfs structure about the storage filling\n  //! state\n  //!\n  //! @param path to statfs\n  //! @param statfs return struct\n  //!\n  //! @return 0 if successful otherwise errno\n  //----------------------------------------------------------------------------\n  virtual int Statfs(struct statfs* statFs) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Return our own, custom Statfs object, instead of a raw statfs struct.\n  //! @return nullptr if unsuccessful\n  //----------------------------------------------------------------------------\n  std::unique_ptr<eos::common::Statfs> GetStatfs()\n  {\n    struct statfs rawStatfs;\n    int rc = Statfs(&rawStatfs);\n\n    if (rc != 0) {\n      // Could not retrieve statfs\n      eos_static_err(\"msg=\\\"failed statfs\\\" rc=%i errno=%d\", rc, errno);\n      return nullptr;\n    }\n\n    std::unique_ptr<eos::common::Statfs> retval;\n    retval.reset(new eos::common::Statfs(rawStatfs));\n    return retval;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the IO type\n  //----------------------------------------------------------------------------\n  std::string GetIoType()\n  {\n    return mType;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get last URL\n  //----------------------------------------------------------------------------\n  std::string GetLastUrl()\n  {\n    return mLastUrl;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get last URL\n  //----------------------------------------------------------------------------\n  std::string GetLastTriedUrl()\n  {\n    return mLastTriedUrl;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get path\n  //----------------------------------------------------------------------------\n  std::string GetPath()\n  {\n    return mFilePath;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get last error message\n  //----------------------------------------------------------------------------\n  const std::string& GetLastErrMsg()\n  {\n    return mLastErrMsg;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get last error code\n  //----------------------------------------------------------------------------\n  const int&\n  GetLastErrCode()\n  {\n    return mLastErrCode;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get last error number\n  //----------------------------------------------------------------------------\n  const int&\n  GetLastErrNo()\n  {\n    return mLastErrNo;\n  }\n\nprotected:\n  std::string mFilePath; ///< path to current physical file\n  const std::string mType; ///< type\n  std::string mLastUrl; ///< last used url if remote file\n  std::string mLastTriedUrl; ///< last tried url if remote file\n  std::string mLastErrMsg; ///< last error message seen\n  int mLastErrCode; ///< last error code\n  int mLastErrNo; ///< last error no\n  bool mIsOpen; ///< Mark if file is opened, so that we close it properly\n};\n\nEOSFSTNAMESPACE_END\n\n#endif  // __EOS_FST_FILEIO_HH__\n"
  },
  {
    "path": "fst/io/FileIoPlugin-Server.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file FileIoPlugin.cc\n//! @author Geoffray Adde - CERN\n//! @brief Implementation of the FileIoPlugin for a client\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <fst/XrdFstOfs.hh>\n#include <fst/storage/FileSystem.hh>\n#include \"fst/io/FileIoPlugin.hh\"\n#include \"fst/io/FileIoPluginCommon.hh\"\n#include \"fst/io/local/LocalIo.hh\"\n#include \"fst/io/davix/DavixIo.hh\"\n#include \"fst/io/nfs/NfsIo.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\nusing eos::common::LayoutId;\n\n//------------------------------------------------------------------------------\n// Get IO object\n//------------------------------------------------------------------------------\nFileIo*\nFileIoPlugin::GetIoObject(std::string path,\n                          XrdFstOfsFile* file,\n                          const XrdSecEntity* client)\n{\n  auto ioType = eos::common::LayoutId::GetIoType(path.c_str());\n\n  if (ioType == LayoutId::kLocal) {\n    return static_cast<FileIo*>(new LocalIo(path, file, client));\n  } else if (ioType == LayoutId::kXrdCl) {\n    return static_cast<FileIo*>(new XrdIo(path));\n  } else if (ioType == LayoutId::kDavix) {\n#ifdef HAVE_DAVIX\n    std::string s3credentials = \"\";\n\n    // Attempt to retrieve S3 credentials from the filesystem\n    if (file) {\n      s3credentials =\n        gOFS.Storage->GetFileSystemConfig(file->GetFileSystemId(),\n                                          \"s3credentials\");\n    }\n\n    return static_cast<FileIo*>(new DavixIo(path, s3credentials));\n#endif // HAVE_DAVIX\n    eos_static_warning(\"EOS has been compiled without DAVIX support.\");\n    return NULL;\n  } else if (ioType == LayoutId::kNfs) {\n#ifdef HAVE_NFS\n    return static_cast<FileIo*>(new NfsIo(path, file, client));\n#endif // HAVE_NFS\n    eos_static_warning(\"EOS has been compiled without NFS support.\");\n    return NULL;\n  } else { \n    return FileIoPluginHelper::GetIoObject(path, file, client);\n  }\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/io/FileIoPlugin.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file FileIoPlugin.cc\n//! @author Geoffray Adde - CERN\n//! @brief Implementation of the FileIoPlugin for a client\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/io/FileIoPlugin.hh\"\n#include \"fst/io/FileIoPluginCommon.hh\"\n#include \"fst/io/local/LocalIo.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Get IO object\n//------------------------------------------------------------------------------\nFileIo*\nFileIoPlugin::GetIoObject(std::string path,\n                          XrdFstOfsFile* file,\n                          const XrdSecEntity* client)\n{\n  return FileIoPluginHelper::GetIoObject(path, file, client);\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/io/FileIoPlugin.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file FileIoPlugin.hh\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Class generating an IO plugin object\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOS_FST_FILEIOPLUGIN_HH__\n#define __EOS_FST_FILEIOPLUGIN_HH__\n\n#include \"fst/Namespace.hh\"\n#include <string>\n\nclass XrdSecEntity;\n\nEOSFSTNAMESPACE_BEGIN\n\n//! Forward declaration\nclass XrdFstOfsFile;\nclass FileIo;\n\n//------------------------------------------------------------------------------\n//! Class used to obtain a IO plugin object\n//------------------------------------------------------------------------------\nclass FileIoPlugin\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  FileIoPlugin() {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~FileIoPlugin() {}\n\n  //----------------------------------------------------------------------------\n  //! Get IO object\n  //!\n  //! @param file file handler\n  //! @param layoutId layout id type\n  //! @param error error information\n  //!\n  //! @return requested layout type object\n  //----------------------------------------------------------------------------\n  static FileIo*\n  GetIoObject(std::string path,\n              XrdFstOfsFile* file = 0,\n              const XrdSecEntity* client = 0);\n};\n\nEOSFSTNAMESPACE_END\n\n#endif // __EOS_FST_FILEIOPLUGIN_HH__\n"
  },
  {
    "path": "fst/io/FileIoPluginCommon.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file FileIoPluginHelper.hh\n//! @author Geoffray Adde - CERN\n//! @brief Class generating an IO plugin object\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOS_FST_FILEIOPLUGINHELPER_HH__\n#define __EOS_FST_FILEIOPLUGINHELPER_HH__\n\n#include \"fst/io/FileIo.hh\"\n#include \"fst/io/local/FsIo.hh\"\n#include \"fst/io/xrd/XrdIo.hh\"\n#include \"fst/io/davix/DavixIo.hh\"\n#include \"fst/io/nfs/NfsIo.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/Logging.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\nusing eos::common::LayoutId;\n\n//! Forward declaration\nclass XrdFstOfsFile;\n\n//------------------------------------------------------------------------------\n//! Class used to obtain a IO plugin object\n//------------------------------------------------------------------------------\nclass FileIoPluginHelper\n{\npublic:\n  //--------------------------------------------------------------------------\n  //! Constructor\n  //--------------------------------------------------------------------------\n  FileIoPluginHelper() {}\n\n  //--------------------------------------------------------------------------\n  //! Destructor\n  //--------------------------------------------------------------------------\n  ~FileIoPluginHelper() {}\n\n  //--------------------------------------------------------------------------\n  //! Get IO object\n  //!\n  //! @param file file handler\n  //! @param layoutId layout id type\n  //! @param error error information\n  //!\n  //! @return requested layout type object\n  //--------------------------------------------------------------------------\n  static FileIo*\n  GetIoObject(std::string path, XrdFstOfsFile* file = 0,\n              const XrdSecEntity* client = 0)\n  {\n    auto ioType = eos::common::LayoutId::GetIoType(path.c_str());\n\n    if (ioType == LayoutId::kLocal) {\n      return static_cast<FileIo*>(new FsIo(path));\n    } else if (ioType == LayoutId::kXrdCl) {\n      return static_cast<FileIo*>(new XrdIo(path));\n    } else if (ioType == LayoutId::kDavix) {\n#ifdef HAVE_DAVIX\n      return static_cast<FileIo*>(new DavixIo(path));\n#endif // HAVE_DAVIX\n      eos_static_warning(\"%s\", \"msg=\\\"EOS has been compiled without DAVIX support\\\"\");\n      return nullptr;\n    } else if (ioType == LayoutId::kNfs) {\n#ifdef HAVE_NFS\n      return static_cast<FileIo*>(new NfsIo(path, file, client));\n#endif // HAVE_NFS\n      eos_static_warning(\"%s\", \"msg=\\\"EOS has been compiled without NFS support\\\"\");\n      return nullptr;\n    }\n\n    return nullptr;\n  }\n};\n\nEOSFSTNAMESPACE_END\n\n#endif // __ EOS_FST_FILEIOPLUGINHELPER_HH__\n"
  },
  {
    "path": "fst/io/SimpleHandler.cc",
    "content": "//------------------------------------------------------------------------------\n// File: SimpleHandler.cc\n// Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/io/SimpleHandler.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nSimpleHandler::SimpleHandler(uint64_t offset, int32_t length, bool isWrite) :\n  eos::common::LogId(),\n  XrdCl::ResponseHandler(),\n  mOffset(offset),\n  mLength(length),\n  mRespLength(0),\n  mIsWrite(isWrite),\n  mRespOK(false),\n  mReqDone(false),\n  mHasReq(false)\n{\n  mCond = XrdSysCondVar(0);\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nSimpleHandler::~SimpleHandler()\n{}\n\n//------------------------------------------------------------------------------\n// Update function\n//------------------------------------------------------------------------------\nvoid\nSimpleHandler::Update(uint64_t offset, int32_t length, bool isWrite)\n{\n  mOffset = offset;\n  mLength = length;\n  mRespLength = 0;\n  mIsWrite = isWrite;\n  XrdSysCondVarHelper scope_lock(&mCond);\n  mRespOK = false;\n  mReqDone = false;\n  mHasReq = true;\n}\n\n//------------------------------------------------------------------------------\n// Handle response\n//------------------------------------------------------------------------------\nvoid\nSimpleHandler::HandleResponse(XrdCl::XRootDStatus* pStatus,\n                              XrdCl::AnyObject* pResponse)\n{\n  // Do some extra check for the read case\n  if ((mIsWrite == false) && (pResponse)) {\n    XrdCl::ChunkInfo* chunk = 0;\n    pResponse->Get(chunk);\n    mRespLength = chunk->length;\n  }\n\n  mCond.Lock();\n  mRespOK = pStatus->IsOK();\n  mReqDone = true;\n  mCond.Signal(); //signal\n  mCond.UnLock();\n  delete pStatus;\n\n  if (pResponse) {\n    delete pResponse;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Wait for responses\n//------------------------------------------------------------------------------\nbool\nSimpleHandler::WaitOK()\n{\n  bool req_status;\n  mCond.Lock();\n\n  while (!mReqDone) {\n    mCond.Wait();\n  }\n\n  req_status = mRespOK;\n  mHasReq = false;\n  mCond.UnLock();\n  return req_status;\n}\n\n//------------------------------------------------------------------------------\n// Get if there is any request to process\n//------------------------------------------------------------------------------\nbool\nSimpleHandler::HasRequest()\n{\n  bool ret = false;\n  mCond.Lock();\n  ret = mHasReq;\n  mCond.UnLock();\n  return ret;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/io/SimpleHandler.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file SimpleHandler.hh\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @breif Asynchronous chunk handler used only for reading with prefetching\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOS_FST_SIMPLEHANDLER_HH__\n#define __EOS_FST_SIMPLEHANDLER_HH__\n\n#include \"fst/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include <XrdCl/XrdClXRootDResponses.hh>\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class holding information about an asynchronous request\n//------------------------------------------------------------------------------\nclass SimpleHandler : public eos::common::LogId, public XrdCl::ResponseHandler\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param offset request offset\n  //! @param length request length\n  //! @param isWrite chunk belongs to a write request\n  //----------------------------------------------------------------------------\n  SimpleHandler(uint64_t offset = 0, int32_t length = 0, bool isWrite = false);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~SimpleHandler();\n\n  //----------------------------------------------------------------------------\n  //! Update function\n  //!\n  //! @param offset request offset\n  //! @param length request length\n  //! @param isWrite chunk belongs to a write request\n  //----------------------------------------------------------------------------\n  void Update(uint64_t offset, int32_t length, bool isWrite = false);\n\n  //----------------------------------------------------------------------------\n  //! Wait for request to be done\n  //!\n  //! @return status of the request\n  //----------------------------------------------------------------------------\n  bool WaitOK();\n\n  //----------------------------------------------------------------------------\n  //! Get if there is any request to process\n  //!\n  //! @return true if there is a request, false otherwise\n  //----------------------------------------------------------------------------\n  bool HasRequest();\n\n  //----------------------------------------------------------------------------\n  //! Get request chunk offset\n  //----------------------------------------------------------------------------\n  inline uint64_t\n  GetOffset() const\n  {\n    return mOffset;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get request chunk length\n  //----------------------------------------------------------------------------\n  inline int32_t\n  GetLength() const\n  {\n    return mLength;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get response chunk length\n  //----------------------------------------------------------------------------\n  inline int32_t\n  GetRespLength() const\n  {\n    return mRespLength;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get response chunk status\n  //----------------------------------------------------------------------------\n  inline bool\n  GetRespStatus() const\n  {\n    return mRespOK;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Test if chunk is from a write operation\n  //----------------------------------------------------------------------------\n  inline bool\n  IsWrite() const\n  {\n    return mIsWrite;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Handle response\n  //!\n  //! @param pStatus status of the response\n  //! @param pResponse object containing extra info about the response\n  //----------------------------------------------------------------------------\n  virtual void HandleResponse(XrdCl::XRootDStatus* pStatus,\n                              XrdCl::AnyObject* pResponse);\n\nprotected:\n  uint64_t mOffset; ///< offset of the request\n  int32_t mLength; ///< length of the request\n  uint32_t mRespLength; ///< length of response received, only for reads\n  bool mIsWrite; ///< operation type is write\n  bool mRespOK; ///< mark if the resp status is ok\n  bool mReqDone; ///< mark if the request was done\n  bool mHasReq; ///< mark if there is any request to proceess\n  XrdSysCondVar mCond; ///< cond. variable used for synchronisation\n};\n\nEOSFSTNAMESPACE_END\n\n#endif   // __EOS_FST_SIMPLEHANDLER_HH__\n"
  },
  {
    "path": "fst/io/VectChunkHandler.cc",
    "content": "//------------------------------------------------------------------------------\n// File: VectChunkHandler.cc\n// Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/io/VectChunkHandler.hh\"\n#include \"fst/io/AsyncMetaHandler.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nVectChunkHandler::VectChunkHandler(AsyncMetaHandler* metaHandler,\n                                   XrdCl::ChunkList& chunkList,\n                                   const char* wrBuf,\n                                   bool isWrite) :\n  XrdCl::ResponseHandler(),\n  mBuffer(0),\n  mMetaHandler(metaHandler),\n  mCapacity(0),\n  mLength(0),\n  mRespLength(0),\n  mIsWrite(isWrite)\n{\n  // Copy the list of chunks and compute buffer size\n  for (auto chunk = chunkList.begin(); chunk != chunkList.end(); ++chunk) {\n    mLength += chunk->length;\n    mChunkList.push_back(*chunk);\n  }\n\n  mCapacity = mLength;\n  /*\n  NOTE: Vector writes are not supported yet\n  if (mIsWrite)\n  {\n    // Copy the write buffer to the local one\n    mBuffer = static_cast<char*>(calloc(mCapacity, sizeof(char)));\n\n    if (mBuffer)\n      mBuffer = static_cast<char*>(memcpy(mBuffer, wrBuf, mLength));\n  }\n  */\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nVectChunkHandler::~VectChunkHandler()\n{\n  if (mBuffer) {\n    free(mBuffer);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Update function\n//------------------------------------------------------------------------------\nvoid\nVectChunkHandler::Update(AsyncMetaHandler* metaHandler,\n                         XrdCl::ChunkList& chunkList,\n                         const char* wrBuf, bool isWrite)\n{\n  mMetaHandler = metaHandler;\n  mRespLength = 0;\n  mLength = 0;\n  mIsWrite = isWrite;\n  mChunkList.clear();\n\n  // Copy the list of chunks and compute buffer size\n  for (auto chunk = chunkList.begin(); chunk != chunkList.end(); ++chunk) {\n    mLength += chunk->length;\n    mChunkList.push_back(*chunk);\n  }\n\n  /*\n  NOTE: vector writes are not supported yet\n  if (mIsWrite)\n  {\n    if (mLength > mCapacity)\n    {\n      mCapacity = mLength;\n      mBuffer = static_cast<char*>(realloc(mBuffer, mCapacity));\n    }\n\n    mBuffer = static_cast<char*>(memcpy(mBuffer, wrBuf, mLength));\n  }\n  */\n}\n\n//------------------------------------------------------------------------------\n// Handle response\n//------------------------------------------------------------------------------\nvoid\nVectChunkHandler::HandleResponse(XrdCl::XRootDStatus* pStatus,\n                                 XrdCl::AnyObject* pResponse)\n{\n  // Do some extra check for the read case\n  if ((mIsWrite == false) && (pResponse)) {\n    XrdCl::VectorReadInfo* vrd_info = 0;\n    pResponse->Get(vrd_info);\n    mRespLength = vrd_info->GetSize();\n\n    // Notice if we receive less then we initially requested - for readv it\n    // means there was an error\n    if (mLength != mRespLength) {\n      pStatus->status = XrdCl::stError;\n      pStatus->code = XrdCl::errErrorResponse;\n    }\n  }\n\n  if (pResponse) {\n    delete pResponse;\n  }\n\n  mMetaHandler->HandleResponse(pStatus, this);\n  delete pStatus;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/io/VectChunkHandler.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file VectChunkHandler.hh\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Class holding information about an asynchronous vector request and\n//!        a pointer to the file the request belongs to. This class notifies\n//!        the AsyncMetaHandler corresponding to the file object of of any\n//!        errors during transfer.\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOS_FST_VECTCHUNKHANDLER_HH__\n#define __EOS_FST_VECTCHUNKHANDLER_HH__\n\n#include \"fst/Namespace.hh\"\n#include <XrdCl/XrdClXRootDResponses.hh>\n\nEOSFSTNAMESPACE_BEGIN\n\nclass AsyncMetaHandler;\n\n//------------------------------------------------------------------------------\n//! Class holding information about an asynchronous vector request\n//------------------------------------------------------------------------------\nclass VectChunkHandler : public XrdCl::ResponseHandler\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param reqHandler handler to the file meta handler\n  //! @param chunkList chunks concerning the vector operation\n  //! @param isWrite chunk belongs to a write request\n  //----------------------------------------------------------------------------\n  VectChunkHandler(AsyncMetaHandler* reqHandler, XrdCl::ChunkList& chunkList,\n                   const char* wrBuf, bool isWrite);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~VectChunkHandler();\n\n  //----------------------------------------------------------------------------\n  //! Update function\n  //!\n  //! @param reqHandler handler to the file meta handler\n  //! @param offset request offset\n  //! @param length request length\n  //! @param buffer holder for data\n  //! @param isWrite chunk belongs to a write request\n  //----------------------------------------------------------------------------\n  void Update(AsyncMetaHandler* reqHandler, XrdCl::ChunkList& chunks,\n              const char* wrBuf, bool isWrite);\n\n  //----------------------------------------------------------------------------\n  //! Handle response\n  //!\n  //! @param pStatus status of the response\n  //! @param pResponse object containing extra info about the response\n  //----------------------------------------------------------------------------\n  virtual void HandleResponse(XrdCl::XRootDStatus* pStatus,\n                              XrdCl::AnyObject* pResponse);\n\n  //----------------------------------------------------------------------------\n  //! Get buffer\n  //----------------------------------------------------------------------------\n  inline char*\n  GetBuffer() const\n  {\n    return mBuffer;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Get response chunk length\n  //----------------------------------------------------------------------------\n  inline uint32_t\n  GetRespLength() const\n  {\n    return mRespLength;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Get total length of vector request\n  //----------------------------------------------------------------------------\n  inline uint32_t\n  GetLength() const\n  {\n    return mLength;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Get the list of chunks\n  //----------------------------------------------------------------------------\n  inline XrdCl::ChunkList&\n  GetChunkList()\n  {\n    return mChunkList;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Test if chunk is from a write operation\n  //----------------------------------------------------------------------------\n  inline bool\n  IsWrite() const\n  {\n    return mIsWrite;\n  };\n\nprivate:\n  char* mBuffer;  ///< holder for data for write requests\n  AsyncMetaHandler* mMetaHandler; ///< handler to the whole file meta handler\n  XrdCl::ChunkList mChunkList; ///< vector operation chunks\n  uint32_t mCapacity; ///< capacity of the buffer\n  uint32_t mLength; ///< length of the vector request\n  uint32_t mRespLength; ///< length of response received, only for reads\n  bool mIsWrite; ///< operation type is write\n};\n\nEOSFSTNAMESPACE_END\n\n#endif // __EOS_FST_VECTCHUNKHANDLER_HH__\n"
  },
  {
    "path": "fst/io/davix/DavixIo.cc",
    "content": "//------------------------------------------------------------------------------\n// File: DavixIo.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifdef HAVE_DAVIX\n#include \"fst/XrdFstOfsFile.hh\"\n#include \"fst/io/davix/DavixIo.hh\"\n#include \"common/Path.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n#define DAVIX_QUOTA_FILE \".dav.quota\"\n\nDavix::Context DavixIo::gContext;\n\nnamespace\n{\nstd::string getAttrUrl(std::string path)\n{\n  size_t rfind = path.rfind(\"/\");\n\n  if (rfind != std::string::npos) {\n    path.insert(rfind + 1, \".\");\n  }\n\n  path += \".xattr\";\n  return path;\n}\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\n\nDavixIo::DavixIo(std::string path, std::string s3credentials)\n  : FileIo(path, \"DavixIo\"),\n    mDav(&DavixIo::gContext)\n{\n  //............................................................................\n  // In this case the logical file is the same as the local physical file\n  //............................................................................\n  seq_offset = 0;\n  mCreated = false;\n  mShortRead = false;\n  std::string lFilePath = mFilePath;\n  size_t qpos;\n\n  //............................................................................\n  // Opaque info can be part of the 'path'\n  //............................................................................\n  if (((qpos = mFilePath.find(\"?\")) != std::string::npos)) {\n    mOpaque = mFilePath.substr(qpos + 1);\n    lFilePath.erase(qpos);\n  } else {\n    mOpaque = \"\";\n  }\n\n  //............................................................................\n  // Set url for xattr requests\n  //............................................................................\n  mAttrUrl = getAttrUrl(lFilePath.c_str());\n\n  //............................................................................\n  // Prepare Keys for S3 access\n  //............................................................................\n  if ((path.substr(0, 3) == \"s3:\") || (path.substr(0, 4) == \"s3s:\")) {\n    std::string id, key, credSource = \"fsconfig\";\n    mIsS3 = true;\n\n    // Passed-in credentials take priority over opaque provided\n    if (s3credentials.empty() && mOpaque.length()) {\n      XrdOucEnv* opaqueEnv = new XrdOucEnv(mOpaque.c_str());\n\n      if (opaqueEnv->Get(\"s3credentials\")) {\n        s3credentials = opaqueEnv->Get(\"s3credentials\");\n      }\n    }\n\n    if (s3credentials.length()) {\n      size_t pos = s3credentials.find(':');\n      id = s3credentials.substr(0, pos);\n      key = s3credentials.substr(pos + 1);\n    } else {\n      // Attempt to retrieve S3 credentials from the global environment\n      id = getenv(\"EOS_FST_S3_ACCESS_KEY\") ?\n           getenv(\"EOS_FST_S3_ACCESS_KEY\") : \"\";\n      key = getenv(\"EOS_FST_S3_SECRET_KEY\") ?\n            getenv(\"EOS_FST_S3_SECRET_KEY\") : \"\";\n      credSource = \"globalEnv\";\n    }\n\n    if (id.empty() || key.empty()) {\n      eos_warning(\"msg=\\\"s3 configuration missing\\\" \"\n                  \"s3-access-key=\\\"%s\\\" s3-secret-key=\\\"%s\\\"\",\n                  id.c_str(), key.c_str());\n    } else {\n      mParams.setAwsAuthorizationKeys(key.c_str(), id.c_str());\n      eos_debug(\"s3-access-key=\\\"%s\\\" s3-secret-key=\\\"%s\\\" (source=%s)\",\n                id.c_str(), key.c_str(), credSource.c_str());\n    }\n  } else {\n    mIsS3 = false;\n  }\n\n  mParams.setOperationRetry(0);\n  // Use path-based S3 URLs\n  mParams.setAwsAlternate(true);\n  setAttrSync(false);// by default sync attributes lazily\n  mAttrLoaded = false;\n  mAttrDirty = false;\n}\n\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\n\nDavixIo::~DavixIo()\n{\n  // deal with asynchronous dirty attributes\n  if (!mAttrSync && mAttrDirty) {\n    std::string lMap = mFileMap.Trim();\n\n    if (!DavixIo::Upload(mAttrUrl, lMap)) {\n      mAttrDirty = false;\n    } else {\n      eos_static_err(\"msg=\\\"unable to upload to remote file map\\\" url=\\\"%s\\\"\",\n                     mAttrUrl.c_str());\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Convert DAVIX errors\n//------------------------------------------------------------------------------\n\nint\nDavixIo::SetErrno(int errcode, Davix::DavixError** err, bool free_error)\n{\n  eos_debug(\"\");\n\n  if (errcode == 0) {\n    errno = 0;\n\n    if (free_error && err && *err) {\n      Davix::DavixError::clearError(err);\n    }\n\n    return 0;\n  }\n\n  if (!err || !*err) {\n    errno = EIO;\n    return -1;\n  }\n\n  switch ((*err)->getStatus()) {\n  case Davix::StatusCode::OK:\n    errno = EIO;\n    break;\n\n  case Davix::StatusCode::AuthenticationError:\n  case Davix::StatusCode::LoginPasswordError:\n  case Davix::StatusCode::CredentialNotFound:\n  case Davix::StatusCode::PermissionRefused:\n    errno = EACCES;\n    break;\n\n  case Davix::StatusCode::IsADirectory:\n    errno = EISDIR;\n    break;\n\n  case Davix::StatusCode::FileExist:\n    errno = EEXIST;\n    break;\n\n  case Davix::StatusCode::InvalidArgument:\n    errno = EINVAL;\n    break;\n\n  case Davix::StatusCode::TimeoutRedirectionError:\n    errno = ETIMEDOUT;\n    break;\n\n  case Davix::StatusCode::OperationNonSupported:\n    errno = ENOTSUP;\n    break;\n\n  case Davix::StatusCode::FileNotFound:\n    errno = ENOENT;\n    break;\n\n  default:\n    errno = EIO;\n  }\n\n  if (free_error) {\n    Davix::DavixError::clearError(err);\n  }\n\n  return -1;\n}\n\n\n//------------------------------------------------------------------------------\n// Returns the s3 credentials in-use by this davix client\n//------------------------------------------------------------------------------\n\nstd::string\nDavixIo::RetrieveS3Credentials()\n{\n  std::string credentials = \"\";\n\n  if (mIsS3) {\n    std::pair<Davix::AwsSecretKey, Davix::AwsAccessKey>\n    credPair = mParams.getAwsAutorizationKeys();\n    credentials = credPair.second + \":\" + credPair.first;\n  }\n\n  return credentials;\n}\n\n//------------------------------------------------------------------------------\n// Open file\n//------------------------------------------------------------------------------\nint\nDavixIo::fileOpen(XrdSfsFileOpenMode flags, mode_t mode,\n                  const std::string& opaque, uint16_t timeout)\n{\n  eos_debug(\"\");\n  eos_info(\"flags=%x\", flags);\n  Davix::DavixError* err = 0;\n  mParent = mFilePath.c_str();\n  mParent.erase(mFilePath.rfind(\"/\"));\n  int pflags = 0;\n\n  if (flags & SFS_O_CREAT) {\n    pflags |= (O_CREAT | O_RDWR);\n  }\n\n  if ((flags & SFS_O_RDWR) || (flags & SFS_O_WRONLY)) {\n    pflags |= (O_RDWR);\n  }\n\n  if (!mIsS3) {\n    DavixIo lParent(mParent.c_str());\n\n    // create at least the direct parent path\n    if ((pflags & O_CREAT) && lParent.fileExists()) {\n      eos_info(\"msg=\\\"creating parent directory\\\" parent=\\\"%s\\\"\", mParent.c_str());\n\n      if (Mkdir(mParent.c_str(), mode)) {\n        eos_err(\"url=\\\"%s\\\" msg=\\\"failed to create parent directory\\\"\",\n                mParent.c_str());\n        return -1;\n      }\n    }\n  }\n\n  eos_info(\"open=%s flags=%x\", mFilePath.c_str(), pflags);\n  mFd = mDav.open(&mParams, mFilePath, pflags, &err);\n\n  if (pflags & O_CREAT) {\n    mCreated = true;\n  }\n\n  if (mFd != NULL) {\n    return 0;\n  }\n\n  int rc = SetErrno(-1, &err, false);\n\n  if (errno != ENOENT) {\n    eos_err(\"url=\\\"%s\\\" msg=\\\"%s\\\" errno=%d \", mFilePath.c_str(),\n            err->getErrMsg().c_str(), errno);\n  }\n\n  if (err) {\n    delete err;\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Open file asynchronously\n//------------------------------------------------------------------------------\nstd::future<XrdCl::XRootDStatus>\nDavixIo::fileOpenAsync(XrdSfsFileOpenMode flags, mode_t mode,\n                       const std::string& opaque, uint16_t timeout)\n{\n  std::promise<XrdCl::XRootDStatus> open_promise;\n  std::future<XrdCl::XRootDStatus> open_future = open_promise.get_future();\n\n  if (fileOpen(flags, mode, opaque, timeout) != SFS_OK) {\n    open_promise.set_value(XrdCl::XRootDStatus(XrdCl::stError,\n                           XrdCl::errUnknown,\n                           EIO, \"failed open\"));\n  } else {\n    open_promise.set_value(XrdCl::XRootDStatus(XrdCl::stOK, \"\"));\n  }\n\n  return open_future;\n}\n\n//------------------------------------------------------------------------------\n// Read from file - sync\n//------------------------------------------------------------------------------\nint64_t\nDavixIo::fileRead(XrdSfsFileOffset offset,\n                  char* buffer,\n                  XrdSfsXferSize length,\n                  uint16_t timeout)\n{\n  eos_debug(\"offset = %lld, length = %lld\",\n            static_cast<int64_t>(offset),\n            static_cast<int64_t>(length));\n  Davix::DavixError* err = 0;\n\n  if (mShortRead) {\n    if (offset >= short_read_offset) {\n      // return an EOF read;\n      return 0;\n    }\n  }\n\n  int retval = mDav.pread(mFd, buffer, length, offset, &err);\n\n  if (-1 == retval) {\n    eos_err(\"url=\\\"%s\\\" msg=\\\"%s\\\"\", mFilePath.c_str(), err->getErrMsg().c_str());\n    return SetErrno(-1, &err);\n  }\n\n  if (retval != length) {\n    // mark the offset when a short read happened ...\n    short_read_offset = offset + retval;\n    mShortRead = true;\n  }\n\n  return retval;\n}\n\n//------------------------------------------------------------------------------\n// Read from file async - falls back on synchronous mode\n//------------------------------------------------------------------------------\nint64_t\nDavixIo::fileReadPrefetch(XrdSfsFileOffset offset, char* buffer,\n                          XrdSfsXferSize length, uint16_t timeout)\n{\n  return fileRead(offset, buffer, length, timeout);\n}\n\n//------------------------------------------------------------------------------\n// Read from file asynchronously - falls back to synchronous mode\n//------------------------------------------------------------------------------\nint64_t\nDavixIo::fileReadAsync(XrdSfsFileOffset offset, char* buffer,\n                       XrdSfsXferSize length, uint16_t timeout)\n{\n  return fileRead(offset, buffer, length, timeout);\n}\n\n//------------------------------------------------------------------------------\n// Write to file - sync\n//------------------------------------------------------------------------------\nint64_t\nDavixIo::fileWrite(XrdSfsFileOffset offset,  const char* buffer,\n                   XrdSfsXferSize length, uint16_t timeout)\n{\n  eos_debug(\"offset = %lld, length = %lld\",\n            static_cast<int64_t>(offset),\n            static_cast<int64_t>(length));\n  errno = 0;\n\n  if (offset != seq_offset) {\n    eos_err(\"msg=\\\"non sequential writes are not supported\\\"\");\n    errno = ENOTSUP;\n    return -1;\n  }\n\n  Davix::DavixError* err = 0;\n  int retval = mDav.write(mFd, buffer, length, &err);\n\n  if (-1 == retval) {\n    eos_err(\"url=\\\"%s\\\" msg=\\\"%s\\\"\", mFilePath.c_str(), err->getErrMsg().c_str());\n    return SetErrno(-1, &err);\n  }\n\n  seq_offset += length;\n  return retval;\n}\n\n//------------------------------------------------------------------------------\n// Write to file async - falls back on synchronous mode\n//------------------------------------------------------------------------------\n\nint64_t\nDavixIo::fileWriteAsync(XrdSfsFileOffset offset,\n                        const char* buffer,\n                        XrdSfsXferSize length,\n                        uint16_t timeout)\n{\n  return fileWrite(offset, buffer, length, timeout);\n}\n\n//----------------------------------------------------------------------------\n// Write to file - async\n//--------------------------------------------------------------------------\nstd::future<XrdCl::XRootDStatus>\nDavixIo::fileWriteAsync(const char* buffer, XrdSfsFileOffset offset,\n                        XrdSfsXferSize length)\n{\n  std::promise<XrdCl::XRootDStatus> wr_promise;\n  std::future<XrdCl::XRootDStatus> wr_future = wr_promise.get_future();\n  int64_t nwrite = fileWrite(offset, buffer, length);\n\n  if (nwrite != length) {\n    wr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errUnknown,\n                         EIO, \"failed write\"));\n  } else {\n    wr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stOK, \"\"));\n  }\n\n  return wr_future;\n}\n\n//--------------------------------------------------------------------------\n//! Close file\n//--------------------------------------------------------------------------\nint\nDavixIo::fileClose(uint16_t timeout)\n{\n  mCreated = false;\n  eos_debug(\"\");\n  Davix::DavixError* err = 0;\n  int retval = mDav.close(mFd, &err);\n\n  if (-1 == retval) {\n    eos_err(\"url=\\\"%s\\\" msg=\\\"%s\\\"\", mFilePath.c_str(), err->getErrMsg().c_str());\n    return SetErrno(-1, &err);\n  }\n\n  return retval;\n}\n\n//------------------------------------------------------------------------------\n// Truncate file\n//------------------------------------------------------------------------------\nint\nDavixIo::fileTruncate(XrdSfsFileOffset offset, uint16_t timeout)\n{\n  eos_debug(\"offset = %lld\",\n            static_cast<int64_t>(offset));\n  eos_err(\"msg=\\\"truncate is not supported by WebDAV\\\"\");\n  errno = -ENOTSUP;\n  return -1;\n}\n\n//------------------------------------------------------------------------------\n// Truncate asynchronous\n//------------------------------------------------------------------------------\nstd::future<XrdCl::XRootDStatus>\nDavixIo::fileTruncateAsync(XrdSfsFileOffset offset, uint16_t timeout)\n{\n  std::promise<XrdCl::XRootDStatus> tr_promise;\n  std::future<XrdCl::XRootDStatus> tr_future = tr_promise.get_future();\n  int retc  = fileTruncate(offset, timeout);\n\n  if (retc) {\n    tr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errUnknown,\n                         EIO, \"failed truncate\"));\n  } else {\n    tr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stOK, \"\"));\n  }\n\n  return tr_future;\n}\n\n//------------------------------------------------------------------------------\n// Get stats about the file\n//------------------------------------------------------------------------------\n\nint\nDavixIo::fileStat(struct stat* buf, uint16_t timeout)\n{\n  eos_debug(\"url=%s\", mFilePath.c_str());\n  Davix::DavixError* err = 0;\n\n  if (mCreated) {\n    memset(buf, 0, sizeof(struct stat));\n    buf->st_size = seq_offset;\n    eos_debug(\"st-size=%llu\", buf->st_size);\n    return 0;\n  }\n\n  int result = mDav.stat(&mParams, mFilePath, buf, &err);\n\n  if (-1 == result) {\n    eos_info(\"url=\\\"%s\\\" msg=\\\"%s\\\"\", mFilePath.c_str(), err->getErrMsg().c_str());\n    return SetErrno(-1, &err);\n  }\n\n  return result;\n}\n\n//------------------------------------------------------------------------------\n// Remove file\n//------------------------------------------------------------------------------\n\nint\nDavixIo::fileRemove(uint16_t timeout)\n{\n  eos_debug(\"\");\n  Davix::DavixError* err1 = 0;\n  Davix::DavixError* err2 = 0;\n  // remove xattr file (errors are ignored)\n  int rc = mDav.unlink(&mParams, mAttrUrl, &err1);\n  SetErrno(rc, &err1);\n  // remove file and return error code\n  rc = mDav.unlink(&mParams, mFilePath, &err2);\n  return SetErrno(rc, &err2);\n}\n\n//------------------------------------------------------------------------------\n// Check for existence by path\n//------------------------------------------------------------------------------\n\nint\nDavixIo::fileExists()\n{\n  eos_debug(\"\");\n  Davix::DavixError* err = 0;\n  std::string url = mFilePath;\n  struct stat st;\n  int result = mDav.stat(&mParams, url, &st, &err);\n\n  if (-1 == result) {\n    eos_info(\"url=\\\"%s\\\" msg=\\\"%s\\\"\", url.c_str(), err->getErrMsg().c_str());\n    return SetErrno(-1, &err);\n  }\n\n  return result;\n}\n\n//------------------------------------------------------------------------------\n// Delete by path\n//------------------------------------------------------------------------------\n\nint\nDavixIo::fileDelete(const char* path)\n{\n  eos_debug(\"\");\n  eos_info(\"path=\\\"%s\\\"\", path);\n  Davix::DavixError* err = 0;\n  std::string davpath = path;\n  int rc = mDav.unlink(&mParams, davpath.c_str(), &err);\n  return SetErrno(rc, &err);\n}\n\n//--------------------------------------------------------------------------\n//! Create a directory\n//--------------------------------------------------------------------------\n\nint\nDavixIo::Mkdir(const char* path, mode_t mode)\n{\n  eos_debug(\"\");\n  eos_info(\"path=\\\"%s\\\"\", path);\n  Davix::DavixError* err = 0;\n  XrdOucString davpath = path;\n  int rc = mDav.mkdir(&mParams, davpath.c_str(), mode, &err);\n  return SetErrno(rc, &err);\n}\n\n//--------------------------------------------------------------------------\n//! Delete a directory\n//--------------------------------------------------------------------------\n\nint\nDavixIo::Rmdir(const char* path)\n{\n  eos_debug(\"\");\n  Davix::DavixError* err = 0;\n  return SetErrno(mDav.rmdir(&mParams, path, &err), &err);\n}\n\n\n//------------------------------------------------------------------------------\n// Sync file - meaningless in HTTP PUT\n//------------------------------------------------------------------------------\nint\nDavixIo::fileSync(uint16_t timeout)\n{\n  eos_debug(\"\");\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Get pointer to async meta handler object\n//------------------------------------------------------------------------------\n\nvoid*\nDavixIo::fileGetAsyncHandler()\n{\n  eos_debug(\"\");\n  return 0;\n}\n\n//--------------------------------------------------------------------------\n//! Download a remote file into a string object\n//--------------------------------------------------------------------------\n\nint\nDavixIo::Download(std::string url, std::string& download)\n{\n  eos_static_debug(\"\");\n  errno = 0;\n  static int s_blocksize = 65536;\n  DavixIo io(url.c_str(), DavixIo::RetrieveS3Credentials());\n  off_t offset = 0;\n  std::string opaque;\n\n  if (!io.fileOpen(0, 0, opaque, 10)) {\n    ssize_t rbytes = 0;\n    download.resize(s_blocksize);\n\n    do {\n      rbytes = io.fileRead(offset, (char*) download.c_str(), s_blocksize, 30);\n\n      if (rbytes == s_blocksize) {\n        download.resize(download.size() + 65536);\n      }\n\n      if (rbytes > 0) {\n        offset += rbytes;\n      }\n    } while (rbytes == s_blocksize);\n\n    io.fileClose();\n    download.resize(offset);\n    return 0;\n  }\n\n  if (errno == ENOENT) {\n    return 0;\n  }\n\n  return -1;\n}\n\n//--------------------------------------------------------------------------\n//! Upload a string object into a remote file\n//--------------------------------------------------------------------------\n\nint\nDavixIo::Upload(std::string url, std::string& upload)\n{\n  eos_static_debug(\"\");\n  errno = 0;\n  DavixIo io(url.c_str(), DavixIo::RetrieveS3Credentials());\n  std::string opaque;\n  int rc = 0;\n  io.fileRemove();\n\n  if (!io.fileOpen(SFS_O_WRONLY | SFS_O_CREAT, S_IRWXU | S_IRGRP | SFS_O_MKPTH,\n                   opaque,\n                   10)) {\n    eos_static_info(\"opened %s\", url.c_str());\n\n    if ((io.fileWrite(0, upload.c_str(), upload.length(),\n                      30)) != (ssize_t) upload.length()) {\n      eos_static_err(\"failed to write %d\", upload.length());\n      rc = -1;\n    } else {\n      eos_static_info(\"uploaded %d\\n\", upload.length());\n    }\n\n    io.fileClose();\n  } else {\n    eos_static_err(\"failed to open %s\", url.c_str());\n    rc = -1;\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Attribute Interface\n//------------------------------------------------------------------------------\n\n\n//----------------------------------------------------------------\n//! Set a binary attribute (name has to start with 'user.' !!!)\n// ------------------------------------------------------------------------\n\nint\nDavixIo::attrSet(const char* name, const char* value, size_t len)\n{\n  eos_debug(\"\");\n  std::string lBlob;\n  errno = 0;\n\n  if (!mAttrSync && mAttrLoaded) {\n    std::string key = name;\n    std::string val;\n    val.assign(value, len);\n\n    if (val == \"#__DELETE_ATTR_#\") {\n      mFileMap.Remove(key);\n    } else {\n      // just modify\n      mFileMap.Set(key, val);\n    }\n\n    mAttrDirty = true;\n    return 0;\n  }\n\n  // download\n  if (!DavixIo::Download(mAttrUrl, lBlob) || errno == ENOENT) {\n    mAttrLoaded = true;\n\n    if (mFileMap.Load(lBlob)) {\n      std::string key = name;\n      std::string val;\n      val.assign(value, len);\n\n      if (val == \"#__DELETE_ATTR_#\") {\n        mFileMap.Remove(key);\n      } else {\n        mFileMap.Set(key, val);\n      }\n\n      mAttrDirty = true;\n\n      if (mAttrSync) {\n        std::string lMap = mFileMap.Trim();\n\n        if (!DavixIo::Upload(mAttrUrl, lMap)) {\n          mAttrDirty = false;\n          return 0;\n        } else {\n          eos_static_err(\"msg=\\\"unable to upload to remote file map\\\" url=\\\"%s\\\"\",\n                         mAttrUrl.c_str());\n        }\n      }\n\n      return 0;\n    } else {\n      eos_static_err(\"msg=\\\"unable to parse remote file map\\\" url=\\\"%s\\\"\",\n                     mAttrUrl.c_str());\n      errno = EINVAL;\n    }\n  } else {\n    eos_static_err(\"msg=\\\"unable to download remote file map\\\" url=\\\"%s\\\"\",\n                   mAttrUrl.c_str());\n  }\n\n  return -1;\n}\n\n// ------------------------------------------------------------------------\n//! Set a string attribute (name has to start with 'user.' !!!)\n// ------------------------------------------------------------------------\n\nint\nDavixIo::attrSet(std::string key, std::string value)\n{\n  return attrSet(key.c_str(), value.c_str(), value.length());\n}\n\n\n// ------------------------------------------------------------------------\n//! Get a binary attribute by name (name has to start with 'user.' !!!)\n// ------------------------------------------------------------------------\n\nint\nDavixIo::attrGet(const char* name, char* value, size_t& size)\n{\n  eos_debug(\"\");\n  errno = 0;\n\n  if (!mAttrSync && mAttrLoaded) {\n    std::string val = mFileMap.Get(name);\n    size_t len = val.length() + 1;\n\n    if (len > size) {\n      len = size;\n    }\n\n    memcpy(value, val.c_str(), len);\n    eos_static_info(\"key=%s value=%s\", name, value);\n    return 0;\n  }\n\n  std::string lBlob;\n\n  if (!DavixIo::Download(mAttrUrl, lBlob) || errno == ENOENT) {\n    mAttrLoaded = true;\n\n    if (mFileMap.Load(lBlob)) {\n      std::string val = mFileMap.Get(name);\n      size_t len = val.length() + 1;\n\n      if (len > size) {\n        len = size;\n      }\n\n      memcpy(value, val.c_str(), len);\n      eos_static_info(\"key=%s value=%s\", name, value);\n      return 0;\n    }\n  } else {\n    eos_static_err(\"msg=\\\"unable to download remote file map\\\" url=\\\"%s\\\"\",\n                   mAttrUrl.c_str());\n  }\n\n  return -1;\n}\n\n// ------------------------------------------------------------------------\n//! Get a string attribute by name (name has to start with 'user.' !!!)\n// ------------------------------------------------------------------------\n\nint\nDavixIo::attrGet(std::string name, std::string& value)\n{\n  eos_debug(\"\");\n  errno = 0;\n\n  if (!mAttrSync && mAttrLoaded) {\n    value = mFileMap.Get(name);\n    return 0;\n  }\n\n  std::string lBlob;\n\n  if (!DavixIo::Download(mAttrUrl, lBlob) || errno == ENOENT) {\n    mAttrLoaded = true;\n\n    if (mFileMap.Load(lBlob)) {\n      value = mFileMap.Get(name);\n      return 0;\n    }\n  } else {\n    eos_static_err(\"msg=\\\"unable to download remote file map\\\" url=\\\"%s\\\"\",\n                   mAttrUrl.c_str());\n  }\n\n  return -1;\n}\n\n// ------------------------------------------------------------------------\n//! Delete a binary attribute by name\n// ------------------------------------------------------------------------\nint\nDavixIo::attrDelete(const char* name)\n{\n  eos_debug(\"\");\n  errno = 0;\n  return attrSet(name, \"#__DELETE_ATTR_#\");\n}\n\n// ------------------------------------------------------------------------\n//! List all attributes for the associated path\n// ------------------------------------------------------------------------\nint\nDavixIo::attrList(std::vector<std::string>& list)\n{\n  eos_debug(\"\");\n\n  if (!mAttrSync && mAttrLoaded) {\n    std::map<std::string, std::string> lMap = mFileMap.GetMap();\n\n    for (auto it = lMap.begin(); it != lMap.end(); ++it) {\n      list.push_back(it->first);\n    }\n\n    return 0;\n  }\n\n  std::string lBlob;\n\n  if (!DavixIo::Download(mAttrUrl, lBlob) || errno == ENOENT) {\n    mAttrLoaded = true;\n\n    if (mFileMap.Load(lBlob)) {\n      std::map<std::string, std::string> lMap = mFileMap.GetMap();\n\n      for (auto it = lMap.begin(); it != lMap.end(); ++it) {\n        list.push_back(it->first);\n      }\n\n      return 0;\n    }\n  } else {\n    eos_static_err(\"msg=\\\"unable to download remote file map\\\" url=\\\"%s\\\"\",\n                   mAttrUrl.c_str());\n  }\n\n  return -1;\n}\n\n//------------------------------------------------------------------------------\n// Statfs function calling quota propfind command\n//------------------------------------------------------------------------------\n\nint\nDavixIo::Statfs(struct statfs* sfs)\n{\n  eos_debug(\"msg=\\\"davixio class statfs called\\\"\");\n  std::string url = mFilePath;;\n  url += \"/\";\n  url += DAVIX_QUOTA_FILE;\n  std::string opaque;\n\n  if (mFilePath.substr(0, 2) == \"s3\") {\n    unsigned long long s3_size = 4000ll * 1000ll * 1000ll * 1000ll;\n    s3_size *= 1000ll;\n\n    if (getenv(\"EOS_FST_S3_STORAGE_SIZE\")) {\n      s3_size = strtoull(getenv(\"EOS_FST_S3_STORAGE_SIZE\"), 0, 10);\n    }\n\n#ifdef __APPLE__\n    sfs->f_iosize = 4096;\n    sfs->f_bsize = sfs->f_iosize;\n    sfs->f_blocks = (fsblkcnt_t)(s3_size / sfs->f_iosize);\n    sfs->f_bavail = (fsblkcnt_t)(s3_size / sfs->f_iosize);\n#else\n    sfs->f_frsize = 4096;\n    sfs->f_bsize = sfs->f_frsize;\n    sfs->f_blocks = (fsblkcnt_t)(s3_size / sfs->f_frsize);\n    sfs->f_bavail = (fsblkcnt_t)(s3_size / sfs->f_frsize);\n#endif\n    sfs->f_bfree = sfs->f_bavail;\n    sfs->f_files = 1000000000ll;\n    sfs->f_ffree = 1000000000ll;\n    eos_debug(\"msg=\\\"emulating s3 quota\\\"\");\n    return 0;\n  }\n\n  DavixIo io(url);;\n  int fd = io.fileOpen((XrdSfsFileOpenMode) 0, (mode_t) 0, opaque, (uint16_t) 0);\n\n  if (fd < 0) {\n    eos_err(\"msg=\\\"failed to get quota file\\\" path=\\\"%s\\\"\", url.c_str());\n    return -ENODATA;\n  }\n\n  char buffer[65536];\n  memset(buffer, 0, sizeof(buffer));\n\n  if (io.fileRead(0, buffer, 65536) > 0) {\n    eos_debug(\"quota-buffer=\\\"%s\\\"\", buffer);\n  } else {\n    eos_err(\"msg=\\\"failed to get the quota file\\\"\");\n  }\n\n  std::map<std::string, std::string> map;\n  std::vector<std::string> keyvector;\n  unsigned long long total_bytes = 0;\n  unsigned long long free_bytes = 0;\n  unsigned long long total_files = 0;\n  unsigned long long free_files = 0;\n\n  if (eos::common::StringConversion::GetKeyValueMap(buffer,\n      map,\n      \"=\",\n      \"\\n\")\n      &&\n      map.count(\"dav.total.bytes\") &&\n      map.count(\"dav.free.bytes\") &&\n      map.count(\"dav.total.files\") &&\n      map.count(\"dav.free.files\")) {\n    total_bytes = strtoull(map[\"dav.total.bytes\"].c_str(), 0, 10);\n    free_bytes = strtoull(map[\"dav.free.bytes\"].c_str(), 0, 10);\n    total_files = strtoull(map[\"dav.total.files\"].c_str(), 0, 10);\n    free_files = strtoull(map[\"dav.free.files\"].c_str(), 0, 10);\n  } else {\n    eos_err(\"msg=\\\"failed to parse key-val quota map\\\"\");\n  }\n\n#ifdef __APPLE__\n  sfs->f_iosize = 4096;\n  sfs->f_bsize = sfs->f_iosize;\n  sfs->f_blocks = (fsblkcnt_t)(total_bytes / sfs->f_iosize);\n  sfs->f_bavail = (fsblkcnt_t)(total_bytes / sfs->f_iosize);\n#else\n  sfs->f_frsize = 4096;\n  sfs->f_bsize = sfs->f_frsize;\n  sfs->f_blocks = (fsblkcnt_t)(total_bytes / sfs->f_frsize);\n  sfs->f_bavail = (fsblkcnt_t)(free_bytes / sfs->f_frsize);\n#endif\n  sfs->f_bfree = sfs->f_bavail;\n  sfs->f_files = total_files;\n  sfs->f_ffree = free_files;\n  return 0;\n}\n\nEOSFSTNAMESPACE_END\n#endif // HAVE_DAVIX\n\n"
  },
  {
    "path": "fst/io/davix/DavixIo.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file DavixIo.hh\n//! @author Andreas-Joachim Peters - CERN\n//! @brief Abstract class modelling an IO plugin\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#ifdef HAVE_DAVIX\n#include <string>\n#include \"fst/io/FileIo.hh\"\n#include \"common/FileMap.hh\"\n#include <davix/davix.hpp>\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! DAVIX Web IO plug-in\n//------------------------------------------------------------------------------\n\nclass DavixIo : public FileIo\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param url url to use\n  //!\n  //----------------------------------------------------------------------------\n  DavixIo(std::string url, std::string s3credentials = \"\");\n\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~DavixIo();\n\n\n  //--------------------------------------------------------------------------\n  //! Open file\n  //!\n  //! @param flags open flags\n  //! @param mode open mode\n  //! @param opaque opaque information\n  //! @param timeout timeout value\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  int fileOpen(XrdSfsFileOpenMode flags,\n               mode_t mode = 0,\n               const std::string& opaque = \"\",\n               uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Open file asynchronously\n  //!\n  //! @param flags open flags\n  //! @param mode open mode\n  //! @param opaque opaque info to be appended to the request\n  //! @param timeout operation timeout\n  //!\n  //! @return future holding the status response\n  //--------------------------------------------------------------------------\n  std::future<XrdCl::XRootDStatus>\n  fileOpenAsync(XrdSfsFileOpenMode flags, mode_t mode = 0,\n                const std::string& opaque = \"\", uint16_t timeout = 0) override;\n\n  //--------------------------------------------------------------------------\n  //! Read from file - sync\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //! @return number of bytes read or -1 if error\n  //--------------------------------------------------------------------------\n  int64_t fileRead(XrdSfsFileOffset offset,\n                   char* buffer,\n                   XrdSfsXferSize length,\n                   uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Read from file asynchronously\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read or -1 if error\n  //----------------------------------------------------------------------------\n  int64_t fileReadAsync(XrdSfsFileOffset offset, char* buffer,\n                        XrdSfsXferSize length, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Read from file with prefetching\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read or -1 if error\n  //----------------------------------------------------------------------------\n  int64_t fileReadPrefetch(XrdSfsFileOffset offset, char* buffer,\n                           XrdSfsXferSize length, uint16_t timeout = 0);\n\n\n  //----------------------------------------------------------------------------\n  //! Vector read - sync\n  //!\n  //! @param chunkList list of chunks for the vector read\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read of -1 if error\n  //----------------------------------------------------------------------------\n  int64_t fileReadV(XrdCl::ChunkList& chunkList,\n                    uint16_t timeout = 0)\n  {\n    // Operation not supported in DavixIo\n    return -ENOTSUP;\n  }\n\n  //------------------------------------------------------------------------------\n  //! Vector read - async\n  //!\n  //! @param chunkList list of chunks for the vector read\n  //! @param timeout timeout value\n  //!\n  //! @return 0(SFS_OK) if request successfully sent, otherwise -1(SFS_ERROR)\n  //------------------------------------------------------------------------------\n  int64_t fileReadVAsync(XrdCl::ChunkList& chunkList,\n                         uint16_t timeout = 0)\n  {\n    // Operation not supported in DavixIo\n    return -ENOTSUP;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Write to file - sync\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //! @param timeout timeout value\n  //! @return number of bytes written or -1 if error\n  //--------------------------------------------------------------------------\n  int64_t fileWrite(XrdSfsFileOffset offset,\n                    const char* buffer,\n                    XrdSfsXferSize length,\n                    uint16_t timeout = 0);\n\n  //--------------------------------------------------------------------------\n  //! Write to file - async\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //! @param timeout timeout value\n  //! @return number of bytes written or -1 if error\n  //--------------------------------------------------------------------------\n  int64_t fileWriteAsync(XrdSfsFileOffset offset,\n                         const char* buffer,\n                         XrdSfsXferSize length,\n                         uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Write to file - async\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //!\n  //! @return future holding the status response\n  //--------------------------------------------------------------------------\n  std::future<XrdCl::XRootDStatus>\n  fileWriteAsync(const char* buffer, XrdSfsFileOffset offset,\n                 XrdSfsXferSize length);\n\n  //--------------------------------------------------------------------------\n  //! Sync file to disk\n  //!\n  //! @param timeout timeout value\n  //! @return 0 on success, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  int fileSync(uint16_t timeout = 0);\n\n  //--------------------------------------------------------------------------\n  //! Get pointer to async meta handler object\n  //!\n  //! @return pointer to async handler, NULL otherwise\n  //--------------------------------------------------------------------------\n  void* fileGetAsyncHandler();\n\n  //--------------------------------------------------------------------------\n  //! Truncate\n  //!\n  //! @param offset truncate file to this value\n  //! @param timeout timeout value\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  int fileTruncate(XrdSfsFileOffset offset, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Truncate asynchronous\n  //!\n  //! @param offset truncate file to this value\n  //! @param timeout timeout value\n  //!\n  //! @return future holding the status response\n  //----------------------------------------------------------------------------\n  std::future<XrdCl::XRootDStatus>\n  fileTruncateAsync(XrdSfsFileOffset offset, uint16_t timeout = 0);\n\n  //--------------------------------------------------------------------------\n  //! Allocate file space\n  //!\n  //! @param length space to be allocated\n  //! @return 0 on success, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  int fileFallocate(XrdSfsFileOffset length)\n  {\n    return 0;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Deallocate file space\n  //!\n  //! @param fromOffset offset start\n  //! @param toOffset offset end\n  //! @return 0 on success, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  int fileFdeallocate(XrdSfsFileOffset fromOffset, XrdSfsFileOffset toOffset)\n  {\n    return 0;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Remove file\n  //!\n  //! @param timeout timeout value\n  //! @return 0 on success, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  int fileRemove(uint16_t timeout = 0);\n\n  //--------------------------------------------------------------------------\n  int fileDelete(const char* url);\n\n  //--------------------------------------------------------------------------\n  //! Check for the existence of a file\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  int fileExists();\n\n  //--------------------------------------------------------------------------\n  int Mkdir(const char* path, mode_t mode);\n  int Rmdir(const char* path);\n\n  //--------------------------------------------------------------------------\n  //! Close file\n  //!\n  //! @param timeout timeout value\n  //! @return 0 on success, -1 otherwise and error code is e is set\n  // ------------------------------------------------------------------------\n  int fileClose(uint16_t  timeout = 0);\n\n  //--------------------------------------------------------------------------\n  //! Get stats about the file\n  //!\n  //! @param buf stat buffer\n  //! @param timeout timeout value\n  //! @return 0 on success, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  int fileStat(struct stat* buf, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Execute implementation dependant commands\n  //!\n  //! @param buf stat buffer\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileFctl(const std::string& cmd, uint16_t timeout = 0)\n  {\n    // Operation not supported in DavixIO\n    return -ENOTSUP;\n  };\n\n  //--------------------------------------------------------------------------\n  //! Download a remote file into a string object\n  //! @param url from where to download\n  //! @param download string where to place the contents\n  //! @return 0 success, otherwise -1 and errno\n  //--------------------------------------------------------------------------\n  int Download(std::string url, std::string& download);\n\n  //--------------------------------------------------------------------------\n  //! Upload a string object into a remote file\n  //! @param url from where to upload\n  //! @param upload string to store into remote file\n  //! @return 0 success, otherwise -1 and errno\n  //--------------------------------------------------------------------------\n  int Upload(std::string url, std::string& upload);\n\n  // ------------------------------------------------------------------------\n  //! Set a binary attribute (name has to start with 'user.' !!!)\n  //!\n  //! @param name attribute name\n  //! @param value attribute value\n  //! @param len value length\n  //! @return 0 on success, -1 otherwise and error code is set\n  // ------------------------------------------------------------------------\n  int attrSet(const char* name, const char* value, size_t len);\n\n  // ------------------------------------------------------------------------\n  //! Set a binary attribute (name has to start with 'user.' !!!)\n  //!\n  //! @param name attribute name\n  //! @param value attribute value\n  //! @return 0 on success, -1 otherwise and error code is set\n  // ------------------------------------------------------------------------\n  int attrSet(std::string name, std::string value);\n\n  // ------------------------------------------------------------------------\n  //! Get a binary attribute by name\n  //!\n  //! @param name attribute name\n  //! @param value contains attribute value upon success\n  //! @param size the buffer size, after success the value size\n  //! @return 0 on success, -1 otherwise and error code is set\n  // ------------------------------------------------------------------------\n  int attrGet(const char* name, char* value, size_t& size);\n\n  // ------------------------------------------------------------------------\n  //! Get a binary attribute by name\n  //!\n  //! @param name attribute name\n  //! @param value contains attribute value upon success\n  //! @return 0 on success, -1 otherwise and error code is set\n  // ------------------------------------------------------------------------\n  int attrGet(std::string name, std::string& value);\n\n  // ------------------------------------------------------------------------\n  //! Delete a binary attribute by name\n  //!\n  //! @param name attribute name\n  //! @return 0 on success, -1 otherwise and error code is set\n  // ------------------------------------------------------------------------\n  int attrDelete(const char* name);\n\n  // ------------------------------------------------------------------------\n  //! List all attributes for the associated path\n  //!\n  //! @param list contains all attribute names for the set path upon success\n  //! @return 0 on success, -1 otherwise and error code is set\n  // ------------------------------------------------------------------------\n  int attrList(std::vector<std::string>& list);\n\n  // ------------------------------------------------------------------------\n  //! Set attribute synchronization mode\n  //!\n  //! @param mode if true, every set attributes runs 'pull-modify-push',\n  //!             otherwise runs just once in the destructor,\n  //!             doing a 'pull-modify-modify-....-push' sequence\n  // ------------------------------------------------------------------------\n  void setAttrSync(bool mode = false)\n  {\n    mAttrSync = mode;\n  }\n\n  //--------------------------------------------------------------------------\n  //! traversing filesystem/storage routines\n  //--------------------------------------------------------------------------\n\n  class FtsHandle : public FileIo::FtsHandle\n  {\n  public:\n\n    FtsHandle(const char* dirp) : FileIo::FtsHandle(dirp)\n    {\n    }\n\n    virtual ~FtsHandle();\n  };\n\n  //--------------------------------------------------------------------------\n  //! Open a cursor to traverse a storage system\n  //!\n  //! @param options options for traversing the hierarchy\n  //\n  //! @return returns implementation dependent handle or 0 in case of error\n  //--------------------------------------------------------------------------\n  FileIo::FtsHandle* ftsOpen(int options = 0) override\n  {\n    return 0;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return the next path related to a traversal cursor obtained with ftsOpen\n  //! @param fts_handle cursor obtained by ftsOpen\n  //! @return returns implementation dependent handle or 0 in case of error\n  //--------------------------------------------------------------------------\n  std::string ftsRead(FileIo::FtsHandle* fts_handle) override\n  {\n    return \"\";\n  }\n\n  //--------------------------------------------------------------------------\n  //! Close a traversal cursor\n  //! @param fts_handle cursor to close\n  //! @return 0 if fts_handle was an open cursor, otherwise -1\n  //--------------------------------------------------------------------------\n  int ftsClose(FileIo::FtsHandle* fts_handle) override\n  {\n    return -1;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Plug-in function to fill a statfs structure about the storage filling\n  //! state\n  //! @param statfs return struct\n  //! @return 0 if successful otherwise errno\n  //--------------------------------------------------------------------------\n\n  int Statfs(struct statfs* statFs);\n\n  static Davix::Context gContext;\n\nprivate:\n  int SetErrno(int errcode, Davix::DavixError** err, bool free_error = true);\n  std::string RetrieveS3Credentials();\n  bool mCreated;\n  std::string mAttrUrl;\n  std::string mOpaque;\n  std::string mParent;\n  off_t seq_offset;\n  off_t short_read_offset;\n  bool mShortRead;\n\n  Davix::DavPosix mDav;\n  DAVIX_FD* mFd;\n  bool mAttrLoaded;\n  bool mAttrDirty;\n  bool mAttrSync;\n\n  Davix::RequestParams mParams;;\n\n  eos::common::FileMap mFileMap; ///< extended attribute file map\n  bool mIsS3; ///< indicates an s3 protocol flavour\n};\n\nEOSFSTNAMESPACE_END\n\n#endif  // HAVE_DAVIX\n\n"
  },
  {
    "path": "fst/io/local/FsIo.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FsIo.cc\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/XrdFstOfsFile.hh\"\n#include \"fst/io/local/FsIo.hh\"\n#include \"common/XattrCompat.hh\"\n\n#ifndef __APPLE__\n#include <xfs/xfs.h>\n#endif\n#undef __USE_FILE_OFFSET64\n#include <fts.h>\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFsIo::FsIo(std::string path) :\n  FileIo(path, \"FsIo\"), mFd(-1)\n{\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFsIo::FsIo(std::string path, std::string iotype) :\n  FileIo(path, iotype), mFd(-1)\n{\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nFsIo::~FsIo()\n{\n  if (mFd != -1) {\n    fileClose(mFd);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Open file\n//------------------------------------------------------------------------------\nint\nFsIo::fileOpen(XrdSfsFileOpenMode flags, mode_t mode, const std::string& opaque,\n               uint16_t timeout)\n{\n  mFd = ::open(mFilePath.c_str(), flags, mode);\n\n  if (mFd > 0) {\n    return 0;\n  } else {\n    mFd = -1;\n    return -1;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Open file asynchronously\n//------------------------------------------------------------------------------\nstd::future<XrdCl::XRootDStatus>\nFsIo::fileOpenAsync(XrdSfsFileOpenMode flags, mode_t mode,\n                    const std::string& opaque, uint16_t timeout)\n{\n  std::promise<XrdCl::XRootDStatus> open_promise;\n  std::future<XrdCl::XRootDStatus> open_future = open_promise.get_future();\n\n  if (fileOpen(flags, mode, opaque, timeout) != SFS_OK) {\n    open_promise.set_value(XrdCl::XRootDStatus(XrdCl::stError,\n                           XrdCl::errUnknown,\n                           EIO, \"failed open\"));\n  } else {\n    open_promise.set_value(XrdCl::XRootDStatus(XrdCl::stOK, \"\"));\n  }\n\n  return open_future;\n}\n\n//------------------------------------------------------------------------------\n// Read from file - sync\n//------------------------------------------------------------------------------\nint64_t\nFsIo::fileRead(XrdSfsFileOffset offset, char* buffer, XrdSfsXferSize length,\n               uint16_t timeout)\n{\n  return ::pread(mFd, buffer, length, offset);\n}\n\n//------------------------------------------------------------------------------\n// Read from file async - falls back on synchronous mode\n//------------------------------------------------------------------------------\nint64_t\nFsIo::fileReadPrefetch(XrdSfsFileOffset offset, char* buffer,\n                       XrdSfsXferSize length, uint16_t timeout)\n{\n  return fileRead(offset, buffer, length, timeout);\n}\n\n//------------------------------------------------------------------------------\n// Read from file asynchronously - falls back to synchronous mode\n//------------------------------------------------------------------------------\nint64_t\nFsIo::fileReadAsync(XrdSfsFileOffset offset, char* buffer,\n                    XrdSfsXferSize length, uint16_t timeout)\n{\n  return fileRead(offset, buffer, length, timeout);\n}\n\n//------------------------------------------------------------------------------\n// Write to file - sync\n//------------------------------------------------------------------------------\nint64_t\nFsIo::fileWrite(XrdSfsFileOffset offset, const char* buffer,\n                XrdSfsXferSize length, uint16_t timeout)\n{\n  return ::pwrite(mFd, buffer, length, offset);\n}\n\n//------------------------------------------------------------------------------\n// Write to file async - falls back on synchronous mode\n//------------------------------------------------------------------------------\nint64_t\nFsIo::fileWriteAsync(XrdSfsFileOffset offset, const char* buffer,\n                     XrdSfsXferSize length, uint16_t timeout)\n{\n  return fileWrite(offset, buffer, length, timeout);\n}\n\n//----------------------------------------------------------------------------\n// Write to file - async\n//--------------------------------------------------------------------------\nstd::future<XrdCl::XRootDStatus>\nFsIo::fileWriteAsync(const char* buffer, XrdSfsFileOffset offset,\n                     XrdSfsXferSize length)\n{\n  std::promise<XrdCl::XRootDStatus> wr_promise;\n  std::future<XrdCl::XRootDStatus> wr_future = wr_promise.get_future();\n  int64_t nwrite = fileWrite(offset, buffer, length);\n\n  if (nwrite != length) {\n    wr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errUnknown,\n                         EIO, \"failed write\"));\n  } else {\n    wr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stOK, \"\"));\n  }\n\n  return wr_future;\n}\n\n//------------------------------------------------------------------------------\n// Truncate file\n//------------------------------------------------------------------------------\nint\nFsIo::fileTruncate(XrdSfsFileOffset offset, uint16_t timeout)\n{\n  return ::ftruncate(mFd, offset);\n}\n\n//------------------------------------------------------------------------------\n// Truncate asynchronous\n//------------------------------------------------------------------------------\nstd::future<XrdCl::XRootDStatus>\nFsIo::fileTruncateAsync(XrdSfsFileOffset offset, uint16_t timeout)\n{\n  std::promise<XrdCl::XRootDStatus> tr_promise;\n  std::future<XrdCl::XRootDStatus> tr_future = tr_promise.get_future();\n  int retc  = fileTruncate(offset, timeout);\n\n  if (retc) {\n    tr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errUnknown,\n                         EIO, \"failed truncate\"));\n  } else {\n    tr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stOK, \"\"));\n  }\n\n  return tr_future;\n}\n\n//------------------------------------------------------------------------------\n// Allocate space for file\n//------------------------------------------------------------------------------\nint\nFsIo::fileFallocate(XrdSfsFileOffset length)\n{\n  eos_debug(\"fallocate with length = %lli\", length);\n#ifdef __APPLE__\n  // no pre-allocation\n  return 0;\n#else\n\n  if (platform_test_xfs_fd(mFd) && !getenv(\"EOS_FST_DISABLE_XFS_FALLOCATE\")) {\n    // Select the fast XFS allocation function if available\n    xfs_flock64_t fl;\n    fl.l_whence = 0;\n    fl.l_start = 0;\n    fl.l_len = (off64_t) length;\n    return xfsctl(NULL, mFd, XFS_IOC_RESVSP64, &fl);\n  } else {\n    if (getenv(\"EOS_FST_POSIX_FALLOCATE\")) {\n      // only fallocate if defined\n      return posix_fallocate(mFd, 0, length);\n    } else {\n      // don't fallocate\n      return 0;\n    }\n  }\n\n#endif\n  return SFS_ERROR;\n}\n\n//------------------------------------------------------------------------------\n// Deallocate space reserved for file\n//------------------------------------------------------------------------------\nint\nFsIo::fileFdeallocate(XrdSfsFileOffset fromOffset,\n                      XrdSfsFileOffset toOffset)\n{\n  eos_debug(\"fdeallocate from = %lli to = %lli\", fromOffset, toOffset);\n#ifdef __APPLE__\n  // no de-allocation\n  return 0;\n#else\n\n  if (mFd > 0) {\n    if (platform_test_xfs_fd(mFd)) {\n      // Select the fast XFS deallocation function if available\n      xfs_flock64_t fl;\n      fl.l_whence = 0;\n      fl.l_start = fromOffset;\n      fl.l_len = (off64_t) toOffset - fromOffset;\n      return xfsctl(NULL, mFd, XFS_IOC_UNRESVSP64, &fl);\n    } else {\n      return 0;\n    }\n  }\n\n  return SFS_ERROR;\n#endif\n}\n\n//------------------------------------------------------------------------------\n// Sync file to disk\n//------------------------------------------------------------------------------\nint\nFsIo::fileSync(uint16_t timeout)\n{\n  return ::fsync(mFd);\n}\n\n//------------------------------------------------------------------------------\n// Get stats about the file\n//------------------------------------------------------------------------------\nint\nFsIo::fileStat(struct stat* buf, uint16_t timeout)\n{\n  if (mFd > 0) {\n    return ::fstat(mFd, buf);\n  } else {\n    return ::stat(mFilePath.c_str(), buf);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Close file\n//------------------------------------------------------------------------------\nint\nFsIo::fileClose(uint16_t timeout)\n{\n  int rc = ::close(mFd);\n  mFd = -1;\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Remove file\n//------------------------------------------------------------------------------\nint\nFsIo::fileRemove(uint16_t timeout)\n{\n  struct stat buf;\n\n  if (!fileStat(&buf)) {\n    return ::unlink(mFilePath.c_str());\n  }\n\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Check for existence by path\n//------------------------------------------------------------------------------\nint\nFsIo::fileExists()\n{\n  struct stat buf;\n  return ::stat(mFilePath.c_str(), &buf);\n}\n\n//------------------------------------------------------------------------------\n// Get pointer to async meta handler object\n//------------------------------------------------------------------------------\nvoid*\nFsIo::fileGetAsyncHandler()\n{\n  return NULL;\n}\n\n//------------------------------------------------------------------------------\n// Open a cursor to traverse a storage system to find files\n//------------------------------------------------------------------------------\nFileIo::FtsHandle*\nFsIo::ftsOpen(int options)\n{\n  FtsHandle* handle = (new FtsHandle(mFilePath.c_str()));\n  handle->paths[0] = (char*) mFilePath.c_str();\n  handle->paths[1] = 0;\n  handle->tree = (void*) fts_open(handle->paths, FTS_NOCHDIR | options, 0);\n\n  if (!handle->tree) {\n    delete handle;\n    return NULL;\n  }\n\n  return dynamic_cast<FileIo::FtsHandle*>(handle);\n}\n\n//------------------------------------------------------------------------------\n// Return the next path related to a traversal cursor obtained with ftsOpen\n//------------------------------------------------------------------------------\nstd::string\nFsIo::ftsRead(FileIo::FtsHandle* fts_handle)\n{\n  FTSENT* node;\n  FtsHandle* handle = dynamic_cast<FtsHandle*>(fts_handle);\n\n  if (handle) {\n    while ((node = fts_read((FTS*) handle->tree))) {\n      if (node->fts_level > 0 && node->fts_name[0] == '.') {\n        fts_set((FTS*) handle->tree, node, FTS_SKIP);\n      } else {\n        if (node->fts_info == FTS_F) {\n          XrdOucString filePath = node->fts_accpath;\n\n          if (!filePath.matches(\"*.xsmap\")) {\n            return filePath.c_str();\n          }\n        }\n      }\n    }\n  }\n\n  // no file anymore\n  return \"\";\n}\n\n//------------------------------------------------------------------------------\n// Close a traversal cursor\n//------------------------------------------------------------------------------\nint\nFsIo::ftsClose(FileIo::FtsHandle* fts_handle)\n{\n  FtsHandle* handle = dynamic_cast<FtsHandle*>(fts_handle);\n\n  if (handle) {\n    int rc = fts_close((FTS*) handle->tree);\n    return rc;\n  }\n\n  return -1;\n}\n\n//------------------------------------------------------------------------------\n// Get statfs information\n//------------------------------------------------------------------------------\nint\nFsIo::Statfs(struct statfs* statFs)\n{\n  return ::statfs(mFilePath.c_str(), statFs);\n}\n\n//------------------------------------------------------------------------------\n// Set attr\n//------------------------------------------------------------------------------\nint FsIo::attrSet(const char* name, const char* value, size_t len)\n{\n  if ((!name) || (!value) || mFilePath.empty()) {\n    errno = EINVAL;\n    return SFS_ERROR;\n  }\n\n#ifdef __APPLE__\n  return setxattr(mFilePath.c_str(), name, value, len, 0, 0);\n#else\n  return lsetxattr(mFilePath.c_str(), name, value, len, 0);\n#endif\n}\n\n//------------------------------------------------------------------------------\n// Set attr\n//------------------------------------------------------------------------------\nint FsIo::attrSet(std::string name, std::string value)\n{\n  return attrSet(name.c_str(), value.c_str(), value.length());\n}\n\n//------------------------------------------------------------------------------\n// Get attr\n//------------------------------------------------------------------------------\nint FsIo::attrGet(const char* name, char* value, size_t& size)\n{\n  if ((!name) || (!value) || mFilePath.empty()) {\n    errno = EINVAL;\n    return SFS_ERROR;\n  }\n\n#ifdef __APPLE__\n  int retc = getxattr(mFilePath.c_str(), name, value, size, 0, 0);\n#else\n  int retc = lgetxattr(mFilePath.c_str(), name, value, size);\n#endif\n\n  if (retc != -1) {\n    size = retc;\n    return SFS_OK;\n  }\n\n  return SFS_ERROR;\n}\n\n//------------------------------------------------------------------------------\n// Get attr\n//------------------------------------------------------------------------------\nint FsIo::attrGet(std::string name, std::string& value)\n{\n  char buffer[1024];\n  size_t size = sizeof(buffer);\n\n  if (!attrGet(name.c_str(), buffer, size)) {\n    value.assign(buffer, size);\n    return SFS_OK;\n  }\n\n  return SFS_ERROR;\n}\n\n//------------------------------------------------------------------------------\n// Delete attr\n//------------------------------------------------------------------------------\nint FsIo::attrDelete(const char* name)\n{\n  if ((!name) || mFilePath.empty()) {\n    errno = EINVAL;\n    return SFS_ERROR;\n  }\n\n#ifdef __APPLE__\n  return removexattr(mFilePath.c_str(), name, 0);\n#else\n  return lremovexattr(mFilePath.c_str(), name);\n#endif\n}\n\n//------------------------------------------------------------------------------\n// List attr\n//------------------------------------------------------------------------------\nint FsIo::attrList(std::vector<std::string>& list)\n{\n  if (mFilePath.empty()) {\n    errno = EINVAL;\n    return SFS_ERROR;\n  }\n\n  char* pointer = NULL;\n#ifdef __APPLE__\n  auto size = listxattr(mFilePath.c_str(), pointer, 0, XATTR_NOFOLLOW);\n#else\n  auto size = llistxattr(mFilePath.c_str(), pointer, 0);\n#endif\n\n  if (size <= 0) {\n    return size;\n  }\n\n  std::vector<char> buffer(size);\n#ifdef __APPLE__\n  size = listxattr(mFilePath.c_str(), buffer.data(), buffer.size(),\n                   XATTR_NOFOLLOW);\n#else\n  size = llistxattr(mFilePath.c_str(), buffer.data(), buffer.size());\n#endif\n\n  if (size <= 0) {\n    return size;\n  }\n\n  pointer = buffer.data();\n\n  while (pointer - buffer.data() < size) {\n    list.push_back(std::string(pointer));\n    pointer += list.back().length() + 2;\n  }\n\n  return SFS_OK;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/io/local/FsIo.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file FsIo.hh\n//! @author Elvin-Alin Sindrilaru - CERN\n//! @brief Class used for doing local IO operations\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_FSFILEIO__HH__\n#define __EOSFST_FSFILEIO__HH__\n\n#include \"fst/io/FileIo.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n//------------------------------------------------------------------------------\n//! Class used for doing local IO operations\n//------------------------------------------------------------------------------\nclass FsIo : public FileIo\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param path file path\n  //----------------------------------------------------------------------------\n  FsIo(std::string path);\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param path file path\n  //! @param iotype type of underlying file\n  //----------------------------------------------------------------------------\n  FsIo(std::string path, std::string iotype);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~FsIo();\n\n  //----------------------------------------------------------------------------\n  //! Open file\n  //!\n  //! @param flags open flags\n  //! @param mode open mode\n  //! @param opaque opaque information\n  //! @param timeout timeout value\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileOpen(XrdSfsFileOpenMode flags,\n                       mode_t mode = 0,\n                       const std::string& opaque = \"\",\n                       uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Open file asynchronously\n  //!\n  //! @param flags open flags\n  //! @param mode open mode\n  //! @param opaque opaque info to be appended to the request\n  //! @param timeout operation timeout\n  //!\n  //! @return future holding the status response\n  //--------------------------------------------------------------------------\n  virtual std::future<XrdCl::XRootDStatus>\n  fileOpenAsync(XrdSfsFileOpenMode flags, mode_t mode = 0,\n                const std::string& opaque = \"\", uint16_t timeout = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Read from file - sync\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read or -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t fileRead(XrdSfsFileOffset offset,\n                           char* buffer,\n                           XrdSfsXferSize length,\n                           uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Read from file asynchronously\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read or -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t fileReadAsync(XrdSfsFileOffset offset, char* buffer,\n                                XrdSfsXferSize length, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Read from file with prefetching\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read or -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t fileReadPrefetch(XrdSfsFileOffset offset, char* buffer,\n                                   XrdSfsXferSize length, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Write to file - sync\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes written or -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t fileWrite(XrdSfsFileOffset offset,\n                            const char* buffer,\n                            XrdSfsXferSize length,\n                            uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Vector read - sync\n  //!\n  //! @param chunkList list of chunks for the vector read\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read of -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t fileReadV(XrdCl::ChunkList& chunkList,\n                            uint16_t timeout = 0)\n  {\n    errno = EOPNOTSUPP;\n    return -1;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Vector read - async\n  //!\n  //! @param chunkList list of chunks for the vector read\n  //! @param timeout timeout value\n  //!\n  //! @return 0(SFS_OK) if request successfully sent, otherwise -1 (SFS_ERROR)\n  //----------------------------------------------------------------------------\n  virtual int64_t fileReadVAsync(XrdCl::ChunkList& chunkList,\n                                 uint16_t timeout = 0)\n  {\n    errno = EOPNOTSUPP;\n    return -1;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Write to file - async\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes written or -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t fileWriteAsync(XrdSfsFileOffset offset,\n                                 const char* buffer,\n                                 XrdSfsXferSize length,\n                                 uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Write to file - async\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //!\n  //! @return future holding the status response\n  //--------------------------------------------------------------------------\n  virtual std::future<XrdCl::XRootDStatus>\n  fileWriteAsync(const char* buffer, XrdSfsFileOffset offset,\n                 XrdSfsXferSize length);\n\n  //----------------------------------------------------------------------------\n  //! Truncate\n  //!\n  //! @param offset truncate file to this value\n  //! @param timeout timeout value\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileTruncate(XrdSfsFileOffset offset, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Truncate asynchronous\n  //!\n  //! @param offset truncate file to this value\n  //! @param timeout timeout value\n  //!\n  //! @return future holding the status response\n  //----------------------------------------------------------------------------\n  virtual std::future<XrdCl::XRootDStatus>\n  fileTruncateAsync(XrdSfsFileOffset offset, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Allocate file space\n  //!\n  //! @param length space to be allocated\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileFallocate(XrdSfsFileOffset length);\n\n  //----------------------------------------------------------------------------\n  //! Deallocate file space\n  //!\n  //! @param fromOffset offset start\n  //! @param toOffset offset end\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileFdeallocate(XrdSfsFileOffset fromOffset,\n                              XrdSfsFileOffset toOffset);\n\n  //----------------------------------------------------------------------------\n  //! Remove file\n  //!\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileRemove(uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Sync file to disk\n  //!\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileSync(uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Get pointer to async meta handler object\n  //!\n  //! @return pointer to async handler, NULL otherwise\n  //----------------------------------------------------------------------------\n  virtual void* fileGetAsyncHandler();\n\n  //----------------------------------------------------------------------------\n  //! Check for the existence of a file\n  //!\n  //! @param path to the file\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileExists();\n\n  //----------------------------------------------------------------------------\n  //! Close file\n  //!\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileClose(uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Get stats about the file\n  //!\n  //! @param buf stat buffer\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileStat(struct stat* buf, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Execute implementation dependant commands\n  //!\n  //! @param buf stat buffer\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int fileFctl(const std::string& cmd, uint16_t timeout = 0)\n  {\n    return SFS_OK;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set a binary attribute (name has to start with 'user.' !!!)\n  //!\n  //! @param name attribute name\n  //! @param value attribute value\n  //! @param len value length\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int attrSet(const char* name, const char* value, size_t len);\n\n  //----------------------------------------------------------------------------\n  //! Set a binary attribute (name has to start with 'user.' !!!)\n  //!\n  //! @param name attribute name\n  //! @param value attribute value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int attrSet(std::string name, std::string value);\n\n  //----------------------------------------------------------------------------\n  //! Get a binary attribute by name\n  //!\n  //! @param name attribute name\n  //! @param value contains attribute value upon success\n  //! @param size the buffer size, after success the value size\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int attrGet(const char* name, char* value, size_t& size);\n\n  //----------------------------------------------------------------------------\n  //! Get a binary attribute by name\n  //!\n  //! @param name attribute name\n  //! @param value contains attribute value upon success\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int attrGet(std::string name, std::string& value);\n\n  //----------------------------------------------------------------------------\n  //! Delete a binary attribute by name\n  //!\n  //! @param name attribute name\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int attrDelete(const char* name);\n\n  //----------------------------------------------------------------------------\n  //! List all attributes for the associated path\n  //!\n  //! @param list contains all attribute names for the set path upon success\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int attrList(std::vector<std::string>& list);\n\n  //----------------------------------------------------------------------------\n  //! Plug-in function to fill a statfs structure about the storage filling\n  //! state\n  //!\n  //! @param path to statfs\n  //! @param statfs return struct\n  //!\n  //! @return 0 if successful otherwise errno\n  //----------------------------------------------------------------------------\n  virtual int Statfs(struct statfs* statFs);\n\n  //----------------------------------------------------------------------------\n  //! Class implementing extended attribute support\n  //----------------------------------------------------------------------------\n  class FtsHandle : public FileIo::FtsHandle\n  {\n    friend class FsIo;\n\n  protected:\n    char** paths;\n    void* tree;\n  public:\n\n    FtsHandle(const char* dirp) : FileIo::FtsHandle(dirp)\n    {\n      paths = (char**) calloc(2, sizeof(char*));\n      paths[0] = (char*) dirp;\n      paths[1] = 0;\n      tree = 0;\n    }\n\n    virtual ~FtsHandle()\n    {\n      if (paths) {\n        free(paths);\n        paths = nullptr;\n      }\n    }\n  };\n\n  //----------------------------------------------------------------------------\n  //! Open a cursor to traverse a storage system\n  //!\n  //! @param options options for traversing the hierarchy\n  //!\n  //! @return returns implementation dependent handle or 0 in case of error\n  //----------------------------------------------------------------------------\n  virtual FileIo::FtsHandle* ftsOpen(int options = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Return the next path related to a traversal cursor obtained with ftsOpen\n  //!\n  //! @param fts_handle cursor obtained by ftsOpen\n  //!\n  //! @return returns implementation dependent handle or 0 in case of error\n  //----------------------------------------------------------------------------\n  virtual std::string ftsRead(FileIo::FtsHandle* fts_handle) override;\n\n  //----------------------------------------------------------------------------\n  //! Close a traversal cursor\n  //!\n  //! @param fts_handle cursor to close\n  //!\n  //! @return 0 if fts_handle was an open cursor, otherwise -1\n  //----------------------------------------------------------------------------\n  virtual int ftsClose(FileIo::FtsHandle* fts_handle) override;\n\nprivate:\n  int mFd; //< file descriptor to filesystem file\n\n  //----------------------------------------------------------------------------\n  //! Disable copy constructor\n  //----------------------------------------------------------------------------\n  FsIo(const FsIo&) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Disable assign operator\n  //----------------------------------------------------------------------------\n  FsIo& operator = (const FsIo&) = delete;\n};\n\nEOSFSTNAMESPACE_END\n\n#endif  // __EOSFST_FSFILEIO_HH__\n"
  },
  {
    "path": "fst/io/local/LocalIo.cc",
    "content": "//------------------------------------------------------------------------------\n// File: LocalIo.cc\n// Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/XrdFstOfsFile.hh\"\n#include \"fst/io/local/LocalIo.hh\"\n#include \"fst/io/local/FsIo.hh\"\n#include \"common/XattrCompat.hh\"\n\n#ifndef __APPLE__\n#include <xfs/xfs.h>\n#endif\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nLocalIo::LocalIo(std::string path, XrdFstOfsFile* file,\n                 const XrdSecEntity* client):\n  FsIo(path, \"LocalIo\"),\n  mLogicalFile(file),\n  mSecEntity(client)\n{\n  mIsOpen = false;\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nLocalIo::~LocalIo()\n{\n  if (mIsOpen) {\n    fileClose();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Open file\n//------------------------------------------------------------------------------\nint\nLocalIo::fileOpen(XrdSfsFileOpenMode flags, mode_t mode,\n                  const std::string& opaque, uint16_t timeout)\n{\n  if (!mLogicalFile) {\n    eos_err(\"%s\", \"msg=\\\"logical file must exist already\\\"\");\n    return SFS_ERROR;\n  }\n\n  errno = 0;\n  eos_info(\"flags=%x path=%s\", flags, mFilePath.c_str());\n  int retc = mLogicalFile->openofs(mFilePath.c_str(), flags, mode, mSecEntity,\n                                   opaque.c_str());\n\n  if (retc != SFS_OK) {\n    eos_err(\"msg=\\\"openofs failed\\\" errno=%d retc=%d\", errno, retc);\n  } else {\n    mIsOpen = true;\n  }\n\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Open file asynchronously\n//----------------------------------------------------------------------------\nstd::future<XrdCl::XRootDStatus>\nLocalIo::fileOpenAsync(XrdSfsFileOpenMode flags, mode_t mode,\n                       const std::string& opaque, uint16_t timeout)\n{\n  std::promise<XrdCl::XRootDStatus> open_promise;\n  std::future<XrdCl::XRootDStatus> open_future = open_promise.get_future();\n\n  if (fileOpen(flags, mode, opaque, timeout) != SFS_OK) {\n    open_promise.set_value(XrdCl::XRootDStatus(XrdCl::stError,\n                           XrdCl::errUnknown,\n                           EIO, \"failed open\"));\n  } else {\n    open_promise.set_value(XrdCl::XRootDStatus(XrdCl::stOK, \"\"));\n  }\n\n  return open_future;\n}\n\n//------------------------------------------------------------------------------\n// Read from file - sync\n//------------------------------------------------------------------------------\nint64_t\nLocalIo::fileRead(XrdSfsFileOffset offset, char* buffer, XrdSfsXferSize length,\n                  uint16_t timeout)\n{\n  eos_debug(\"offset = %lld, length = %lld\",\n            static_cast<int64_t>(offset),\n            static_cast<int64_t>(length));\n  return mLogicalFile->readofs(offset, buffer, length);\n}\n\n//------------------------------------------------------------------------------\n// Read from file async - falls back on synchronous mode\n//------------------------------------------------------------------------------\nint64_t\nLocalIo::fileReadPrefetch(XrdSfsFileOffset offset, char* buffer,\n                          XrdSfsXferSize length, uint16_t timeout)\n{\n  return fileRead(offset, buffer, length, timeout);\n}\n\n//------------------------------------------------------------------------------\n// Read from file asynchronously - falls back to sync mode\n//------------------------------------------------------------------------------\nint64_t\nLocalIo::fileReadAsync(XrdSfsFileOffset offset, char* buffer,\n                       XrdSfsXferSize length, uint16_t timeout)\n{\n  return fileRead(offset, buffer, length, timeout);\n}\n\n//------------------------------------------------------------------------------\n// Vector read - sync\n//------------------------------------------------------------------------------\nint64_t\nLocalIo::fileReadV(XrdCl::ChunkList& chunkList, uint16_t timeout)\n{\n  // Copy ChunkList structure to XrdOucVectIO\n  eos_debug(\"read count=%i\", chunkList.size());\n  XrdOucIOVec* readV = new XrdOucIOVec[chunkList.size()];\n\n  for (uint32_t i = 0; i < chunkList.size(); ++i) {\n    readV[i].offset = (long long)chunkList[i].offset;\n    readV[i].size = (int)chunkList[i].length;\n    readV[i].data = (char*)chunkList[i].buffer;\n  }\n\n  XrdSfsXferSize szReadV = mLogicalFile->readvofs(readV, chunkList.size());\n  int64_t nread = szReadV > 0 ? szReadV : 0;\n  delete[] readV;\n  return nread;\n}\n\n//--------------------------------------------------------------------------\n// Vector read - async - in this case it is the same as the sync one\n//--------------------------------------------------------------------------\nint64_t\nLocalIo::fileReadVAsync(XrdCl::ChunkList& chunkList, uint16_t timeout)\n{\n  return fileReadV(chunkList, timeout);\n}\n\n//------------------------------------------------------------------------------\n// Write to file - sync\n//------------------------------------------------------------------------------\nint64_t\nLocalIo::fileWrite(XrdSfsFileOffset offset, const char* buffer,\n                   XrdSfsXferSize length, uint16_t timeout)\n{\n  eos_debug(\"offset = %lld, length = %lld\",\n            static_cast<int64_t>(offset),\n            static_cast<int64_t>(length));\n  return mLogicalFile->writeofs(offset, buffer, length);\n}\n\n//------------------------------------------------------------------------------\n// Write to file async - falls back on synchronous mode\n//------------------------------------------------------------------------------\nint64_t\nLocalIo::fileWriteAsync(XrdSfsFileOffset offset, const char* buffer,\n                        XrdSfsXferSize length, uint16_t timeout)\n{\n  return fileWrite(offset, buffer, length, timeout);\n}\n\n//----------------------------------------------------------------------------\n// Write to file - async\n//--------------------------------------------------------------------------\nstd::future<XrdCl::XRootDStatus>\nLocalIo::fileWriteAsync(const char* buffer, XrdSfsFileOffset offset,\n                        XrdSfsXferSize length)\n{\n  std::promise<XrdCl::XRootDStatus> wr_promise;\n  std::future<XrdCl::XRootDStatus> wr_future = wr_promise.get_future();\n  int64_t nwrite = fileWrite(offset, buffer, length);\n\n  if (nwrite != length) {\n    wr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errUnknown,\n                         EIO, \"failed write\"));\n  } else {\n    wr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stOK, \"\"));\n  }\n\n  return wr_future;\n}\n\n//------------------------------------------------------------------------------\n// Truncate file\n//------------------------------------------------------------------------------\nint\nLocalIo::fileTruncate(XrdSfsFileOffset offset, uint16_t timeout)\n{\n  return mLogicalFile->truncateofs(offset);\n}\n\n//------------------------------------------------------------------------------\n// Truncate asynchronous\n//------------------------------------------------------------------------------\nstd::future<XrdCl::XRootDStatus>\nLocalIo::fileTruncateAsync(XrdSfsFileOffset offset, uint16_t timeout)\n{\n  std::promise<XrdCl::XRootDStatus> tr_promise;\n  std::future<XrdCl::XRootDStatus> tr_future = tr_promise.get_future();\n  int retc  = fileTruncate(offset, timeout);\n\n  if (retc) {\n    tr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errUnknown,\n                         EIO, \"failed truncate\"));\n  } else {\n    tr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stOK, \"\"));\n  }\n\n  return tr_future;\n}\n\n//------------------------------------------------------------------------------\n// Allocate space for file\n//------------------------------------------------------------------------------\nint\nLocalIo::fileFallocate(XrdSfsFileOffset length)\n{\n  eos_debug(\"msg=\\\"fallocate\\\" length=%lli\", length);\n  XrdOucErrInfo error;\n\n  if (mLogicalFile->XrdOfsFile::fctl(SFS_FCTL_GETFD, 0, error)) {\n    return SFS_ERROR;\n  }\n\n#ifdef __APPLE__\n  // no pre-allocation\n  return 0;\n#else\n  int fd = error.getErrInfo();\n\n  if (platform_test_xfs_fd(fd) && !getenv(\"EOS_FST_DISABLE_XFS_FALLOCATE\")) {\n    // Select the fast XFS allocation function if available\n    xfs_flock64_t fl;\n    fl.l_whence = 0;\n    fl.l_start = 0;\n    fl.l_len = (off64_t) length;\n    return xfsctl(NULL, fd, XFS_IOC_RESVSP64, &fl);\n  } else {\n    if (getenv(\"EOS_FST_POSIX_FALLOCATE\")) {\n      // only fallocate if defined\n      return posix_fallocate(fd, 0, length);\n    } else {\n      // don't fallocate\n      return 0;\n    }\n  }\n\n#endif\n  return SFS_ERROR;\n}\n\n//------------------------------------------------------------------------------\n// Deallocate space reserved for file\n//------------------------------------------------------------------------------\nint\nLocalIo::fileFdeallocate(XrdSfsFileOffset fromOffset,\n                         XrdSfsFileOffset toOffset)\n{\n  eos_debug(\"msg=\\\"fdeallocate\\\" from=%lli to=%lli\", fromOffset, toOffset);\n  XrdOucErrInfo error;\n\n  if (mLogicalFile->XrdOfsFile::fctl(SFS_FCTL_GETFD, 0, error)) {\n    return SFS_ERROR;\n  }\n\n#ifdef __APPLE__\n  // no de-allocation\n  return 0;\n#else\n  int fd = error.getErrInfo();\n\n  if (fd > 0) {\n    if (platform_test_xfs_fd(fd)) {\n      // Select the fast XFS deallocation function if available\n      xfs_flock64_t fl;\n      fl.l_whence = 0;\n      fl.l_start = fromOffset;\n      fl.l_len = (off64_t) toOffset - fromOffset;\n      return xfsctl(NULL, fd, XFS_IOC_UNRESVSP64, &fl);\n    } else {\n      // Posix_fallocate truncates a file to the reserved size, we have\n      // to truncate back to the beginning of the unwritten extent\n      return ftruncate(fd, fromOffset);\n    }\n  }\n\n  return SFS_ERROR;\n#endif\n}\n\n//------------------------------------------------------------------------------\n// Sync file to disk\n//------------------------------------------------------------------------------\nint\nLocalIo::fileSync(uint16_t timeout)\n{\n  return mLogicalFile->syncofs();\n}\n\n//------------------------------------------------------------------------------\n// Get stats about the file\n//------------------------------------------------------------------------------\nint\nLocalIo::fileStat(struct stat* buf, uint16_t timeout)\n{\n  XrdOfsFile* pOfsFile = mLogicalFile;\n\n  if (pOfsFile && mIsOpen) {\n    return pOfsFile->XrdOfsFile::stat(buf);\n  } else {\n    return ::stat(mFilePath.c_str(), buf);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check for existence of the file\n//------------------------------------------------------------------------------\nint\nLocalIo::fileExists()\n{\n  struct stat buf;\n  return fileStat(&buf);\n}\n\n//------------------------------------------------------------------------------\n// Close file\n//------------------------------------------------------------------------------\nint\nLocalIo::fileClose(uint16_t timeout)\n{\n  mIsOpen = false;\n  return mLogicalFile->closeofs();\n}\n\n//------------------------------------------------------------------------------\n// Remove file\n//------------------------------------------------------------------------------\nint\nLocalIo::fileRemove(uint16_t timeout)\n{\n  struct stat buf;\n\n  if (!fileStat(&buf)) {\n    // Only try to delete if there is something to delete!\n    if (mLogicalFile) {\n      return unlink(mLogicalFile->GetFstPath().c_str());\n    } else {\n      return ::unlink(mFilePath.c_str());\n    }\n  }\n\n  return SFS_OK;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/io/local/LocalIo.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file LocalIo.hh\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Class used for doing local IO operations\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_LOCALFILEIO__HH__\n#define __EOSFST_LOCALFILEIO__HH__\n\n#include \"fst/io/FileIo.hh\"\n#include \"FsIo.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class used for doing local IO operations\n//------------------------------------------------------------------------------\nclass LocalIo : public FsIo\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param handle to logical file\n  //! @param client security entity\n  //----------------------------------------------------------------------------\n  LocalIo(std::string path, XrdFstOfsFile* file = 0,\n          const XrdSecEntity* client = 0);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~LocalIo();\n\n  //--------------------------------------------------------------------------\n  //! Open file\n  //!\n  //! @param flags open flags\n  //! @param mode open mode\n  //! @param opaque opaque information\n  //! @param timeout timeout value\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  int fileOpen(XrdSfsFileOpenMode flags,\n               mode_t mode = 0,\n               const std::string& opaque = \"\",\n               uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Open file asynchronously\n  //!\n  //! @param flags open flags\n  //! @param mode open mode\n  //! @param opaque opaque info to be appended to the request\n  //! @param timeout operation timeout\n  //!\n  //! @return future holding the status response\n  //--------------------------------------------------------------------------\n  std::future<XrdCl::XRootDStatus>\n  fileOpenAsync(XrdSfsFileOpenMode flags, mode_t mode = 0,\n                const std::string& opaque = \"\", uint16_t timeout = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Read from file - sync\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read or -1 if error\n  //--------------------------------------------------------------------------\n  int64_t fileRead(XrdSfsFileOffset offset,\n                   char* buffer,\n                   XrdSfsXferSize length,\n                   uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Read from file asynchronously\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read or -1 if error\n  //! @note falls back to synchronous read\n  //----------------------------------------------------------------------------\n  int64_t fileReadAsync(XrdSfsFileOffset offset, char* buffer,\n                        XrdSfsXferSize length, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Read from file with prefetching\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read or -1 if error\n  //----------------------------------------------------------------------------\n  int64_t fileReadPrefetch(XrdSfsFileOffset offset, char* buffer,\n                           XrdSfsXferSize length, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Vector read - sync\n  //!\n  //! @param chunkList list of chunks for the vector read\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read of -1 if error\n  //----------------------------------------------------------------------------\n  int64_t fileReadV(XrdCl::ChunkList& chunkList, uint16_t timeout = 0);\n\n  //------------------------------------------------------------------------------\n  //! Vector read - async\n  //!\n  //! @param chunkList list of chunks for the vector read\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read of -1 if error; this actually calls the\n  //!         ReadV sync method\n  //------------------------------------------------------------------------------\n  int64_t fileReadVAsync(XrdCl::ChunkList& chunkList, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Write to file - sync\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes written or -1 if error\n  //----------------------------------------------------------------------------\n  int64_t fileWrite(XrdSfsFileOffset offset, const char* buffer,\n                    XrdSfsXferSize length, uint16_t timeout = 0);\n\n  //--------------------------------------------------------------------------\n  //! Write to file - async\n  //!\n  //! @return number of bytes written or -1 if error\n  //--------------------------------------------------------------------------\n  int64_t fileWriteAsync(XrdSfsFileOffset offset, const char* buffer,\n                         XrdSfsXferSize length, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Write to file - async\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //!\n  //! @return future holding the status response\n  //--------------------------------------------------------------------------\n  std::future<XrdCl::XRootDStatus>\n  fileWriteAsync(const char* buffer, XrdSfsFileOffset offset,\n                 XrdSfsXferSize length);\n\n  //----------------------------------------------------------------------------\n  //! Truncate\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileTruncate(XrdSfsFileOffset offset, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Truncate asynchronous\n  //!\n  //! @param offset truncate file to this value\n  //! @param timeout timeout value\n  //!\n  //! @return future holding the status response\n  //----------------------------------------------------------------------------\n  std::future<XrdCl::XRootDStatus>\n  fileTruncateAsync(XrdSfsFileOffset offset, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Allocate file space\n  //!\n  //! @param length space to be allocated\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileFallocate(XrdSfsFileOffset length);\n\n  //----------------------------------------------------------------------------\n  //! Deallocate file space\n  //!\n  //! @param fromOffset offset start\n  //! @param toOffset offset end\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileFdeallocate(XrdSfsFileOffset fromOffset, XrdSfsFileOffset toOffset);\n\n  //----------------------------------------------------------------------------\n  //! Remove file\n  //!\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileRemove(uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Sync file to disk\n  //!\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileSync(uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Get pointer to async meta handler object\n  //!\n  //! @return pointer to async handler, NULL otherwise\n  //----------------------------------------------------------------------------\n  void* fileGetAsyncHandler()\n  {\n    return 0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check for the existence of a file\n  //!\n  //! @param path to the file\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileExists();\n\n  //----------------------------------------------------------------------------\n  //! Close file\n  //!\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileClose(uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Get stats about the file\n  //!\n  //! @param buf stat buffer\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileStat(struct stat* buf, uint16_t timeout = 0);\n\nprivate:\n  XrdFstOfsFile* mLogicalFile; ///< handler to logical file\n  const XrdSecEntity* mSecEntity; ///< security entity\n\n  //----------------------------------------------------------------------------\n  //! Disable copy constructor\n  //----------------------------------------------------------------------------\n  LocalIo(const LocalIo&) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Disable assign operator\n  //----------------------------------------------------------------------------\n  LocalIo& operator = (const LocalIo&) = delete;\n};\n\nEOSFSTNAMESPACE_END\n\n#endif  // __EOSFST_LOCALFILEIO_HH__\n"
  },
  {
    "path": "fst/io/nfs/NfsIo.cc",
    "content": "//------------------------------------------------------------------------------\n// File: NfsIo.cc\n// Author: Robert-Paul Pasca - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifdef HAVE_NFS\n#include \"fst/XrdFstOfsFile.hh\"\n#include \"fst/io/nfs/NfsIo.hh\"\n#include \"common/Path.hh\"\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <sys/statvfs.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <errno.h>\n\nEOSFSTNAMESPACE_BEGIN\n\n#define NFS_QUOTA_FILE \".nfs.quota\"\n\nnamespace\n{\nstd::string getAttrPath(std::string path)\n{\n  size_t rfind = path.rfind(\"/\");\n\n  if (rfind != std::string::npos) {\n    path.insert(rfind + 1, \".\");\n  }\n\n  path += \".xattr\";\n  return path;\n}\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\n\nNfsIo::NfsIo(std::string path, XrdFstOfsFile* file, const XrdSecEntity* client)\n  : FileIo(path, \"NfsIo\")\n{\n  eos_debug(\"NfsIo::NfsIo called with path=%s\", path.c_str());\n\n  //............................................................................\n  // Prepare the file path\n  //............................................................................\n  if (path.find(\"nfs:/\") == 0) {\n    mFilePath = path.substr(5); // Remove \"nfs:/\" prefix\n  } else {\n    eos_warning(\"msg=\\\"NFS path does not start with 'nfs:' or '/nfs'\\\" path=\\\"%s\\\"\",\n                path.c_str());\n    mFilePath = path;\n  }\n\n  eos_debug(\"msg=\\\"NfsIo initialized\\\" original_path=%s, parsed_path=%s\",\n            path.c_str(), mFilePath.c_str());\n  // Standard initialization\n  mFd = -1;\n  seq_offset = 0;\n  mCreated = false;\n  mOpen = false;\n  setAttrSync(false); // by default sync attributes lazily\n  mAttrLoaded = false;\n  mAttrDirty = false;\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\n\nNfsIo::~NfsIo()\n{\n  // Close file descriptor if still open\n  if (mFd >= 0) {\n    close(mFd);\n    mFd = -1;\n  }\n\n  // deal with asynchronous dirty attributes\n  if (!mAttrSync && mAttrDirty) {\n    flushAttrFile();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Open file\n//------------------------------------------------------------------------------\nint\nNfsIo::fileOpen(XrdSfsFileOpenMode flags, mode_t mode,\n                const std::string& opaque, uint16_t timeout)\n{\n  eos_info(\"flags=%x, mode=%o, mFilePath=%s\", flags, mode, mFilePath.c_str());\n\n  if (mFd >= 0) {\n    eos_warning(\"msg=\\\"File already open, closing first\\\"\");\n    close(mFd);\n    mFd = -1;\n  }\n\n  int pflags = 0;\n  XrdOucEnv openEnv(opaque.c_str());\n  const char* val;\n\n  if ((val = openEnv.Get(\"mgm.ioflag\"))) {\n    if (!strcmp(val, \"direct\")) {\n      pflags |= O_DIRECT;\n    } else if (!strcmp(val, \"sync\")) {\n      pflags |= O_SYNC;\n    } else if (!strcmp(val, \"dsync\")) {\n      pflags |= O_DSYNC;\n    }\n  }\n\n  if (flags & SFS_O_CREAT) {\n    pflags |= (O_CREAT | O_RDWR);\n  }\n\n  if ((flags & SFS_O_RDWR) || (flags & SFS_O_WRONLY)) {\n    pflags |= (O_RDWR);\n  }\n\n  if (flags & SFS_O_RDONLY) {\n    pflags |= O_RDONLY;\n  }\n\n  if (mFilePath.empty()) {\n    eos_err(\"msg=\\\"File path is empty\\\"\");\n    errno = ENOENT;\n    return SFS_ERROR;\n  }\n\n  // Create parent directory if needed\n  if (pflags & O_CREAT) {\n    std::string parent = mFilePath;\n    size_t pos = parent.rfind(\"/\");\n\n    if (pos != std::string::npos) {\n      parent.erase(pos);\n\n      if (!parent.empty()) {\n        struct stat st;\n\n        if (stat(parent.c_str(), &st) != 0) {\n          eos_info(\"msg=\\\"creating parent directory\\\" parent=\\\"%s\\\"\", parent.c_str());\n\n          if (Mkdir(parent.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) {\n            eos_err(\"msg=\\\"failed to create parent directory\\\" parent=\\\"%s\\\"\",\n                    parent.c_str());\n            return SFS_ERROR;\n          }\n        }\n      }\n    }\n  }\n\n  eos_info(\"msg=\\\"opening file\\\" path=\\\"%s\\\" flags=%x mode=%o\", mFilePath.c_str(),\n           pflags, mode);\n  mFd = open(mFilePath.c_str(), pflags, mode);\n\n  if (mFd < 0) {\n    eos_err(\"msg=\\\"failed to open file\\\" path=\\\"%s\\\" errno=%d\", mFilePath.c_str(),\n            errno);\n    return SFS_ERROR;\n  }\n\n  if (pflags & O_CREAT) {\n    mCreated = true;\n  }\n\n  mOpen = true;\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Open file asynchronously\n//------------------------------------------------------------------------------\nstd::future<XrdCl::XRootDStatus>\nNfsIo::fileOpenAsync(XrdSfsFileOpenMode flags, mode_t mode,\n                     const std::string& opaque, uint16_t timeout)\n{\n  eos_info(\"msg=\\\"opening file asynchronously\\\" path=\\\"%s\\\"\", mFilePath.c_str());\n  std::promise<XrdCl::XRootDStatus> open_promise;\n  std::future<XrdCl::XRootDStatus> open_future = open_promise.get_future();\n\n  if (fileOpen(flags, mode, opaque, timeout) != SFS_OK) {\n    open_promise.set_value(XrdCl::XRootDStatus(XrdCl::stError,\n                           XrdCl::errUnknown,\n                           EIO, \"failed open\"));\n  } else {\n    open_promise.set_value(XrdCl::XRootDStatus(XrdCl::stOK, \"\"));\n  }\n\n  return open_future;\n}\n\n//------------------------------------------------------------------------------\n// Read from file - sync\n//------------------------------------------------------------------------------\nint64_t\nNfsIo::fileRead(XrdSfsFileOffset offset,\n                char* buffer,\n                XrdSfsXferSize length,\n                uint16_t timeout)\n{\n  eos_debug(\"offset = %lld, length = %lld\",\n            static_cast<int64_t>(offset),\n            static_cast<int64_t>(length));\n\n  if (!mOpen || mFd < 0) {\n    errno = EBADF;\n    // File not open or invalid file descriptor\n    return -1;\n  }\n\n  ssize_t retval = pread(mFd, buffer, length, offset);\n\n  if (-1 == retval) {\n    eos_err(\"msg=\\\"failed to read file\\\" path=\\\"%s\\\" errno=%d\", mFilePath.c_str(),\n            errno);\n    return -1;\n  }\n\n  return retval;\n}\n\n//------------------------------------------------------------------------------\n// Read from file async - falls back on synchronous mode\n//------------------------------------------------------------------------------\nint64_t\nNfsIo::fileReadPrefetch(XrdSfsFileOffset offset, char* buffer,\n                        XrdSfsXferSize length, uint16_t timeout)\n{\n  return fileRead(offset, buffer, length, timeout);\n}\n\n//------------------------------------------------------------------------------\n// Read from file asynchronously - falls back to synchronous mode\n//------------------------------------------------------------------------------\nint64_t\nNfsIo::fileReadAsync(XrdSfsFileOffset offset, char* buffer,\n                     XrdSfsXferSize length, uint16_t timeout)\n{\n  return fileRead(offset, buffer, length, timeout);\n}\n\n//------------------------------------------------------------------------------\n// Write to file - sync\n//------------------------------------------------------------------------------\nint64_t\nNfsIo::fileWrite(XrdSfsFileOffset offset, const char* buffer,\n                 XrdSfsXferSize length, uint16_t timeout)\n{\n  eos_debug(\"offset = %lld, length = %lld\",\n            static_cast<int64_t>(offset),\n            static_cast<int64_t>(length));\n  errno = 0;\n\n  if (!mOpen || mFd < 0) {\n    eos_err(\"msg=\\\"file not open or invalid fd\\\"\");\n    errno = EBADF;\n    return -1;\n  }\n\n  if (offset != seq_offset) {\n    eos_err(\"msg=\\\"non sequential write not supported\\\" offset=%lld seq_offset=%lld\",\n            static_cast<int64_t>(offset), static_cast<int64_t>(seq_offset));\n    errno = ENOTSUP;\n    return -1;\n  }\n\n  ssize_t retval = write(mFd, buffer, length);\n\n  if (-1 == retval) {\n    eos_err(\"msg=\\\"failed to write file\\\" path=\\\"%s\\\" errno=%d\", mFilePath.c_str(),\n            errno);\n    return -1;\n  }\n\n  seq_offset += length;\n  return retval;\n}\n\n//------------------------------------------------------------------------------\n// Write to file async - falls back on synchronous mode\n//------------------------------------------------------------------------------\nint64_t\nNfsIo::fileWriteAsync(XrdSfsFileOffset offset,\n                      const char* buffer,\n                      XrdSfsXferSize length,\n                      uint16_t timeout)\n{\n  return fileWrite(offset, buffer, length, timeout);\n}\n\n//----------------------------------------------------------------------------\n// Write to file - async\n//--------------------------------------------------------------------------\nstd::future<XrdCl::XRootDStatus>\nNfsIo::fileWriteAsync(const char* buffer, XrdSfsFileOffset offset,\n                      XrdSfsXferSize length)\n{\n  std::promise<XrdCl::XRootDStatus> wr_promise;\n  std::future<XrdCl::XRootDStatus> wr_future = wr_promise.get_future();\n  int64_t nwrite = fileWrite(offset, buffer, length);\n\n  if (nwrite != length) {\n    wr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errUnknown,\n                         EIO, \"failed write\"));\n  } else {\n    wr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stOK, \"\"));\n  }\n\n  return wr_future;\n}\n\n//--------------------------------------------------------------------------\n//! Close file\n//--------------------------------------------------------------------------\nint\nNfsIo::fileClose(uint16_t timeout)\n{\n  mCreated = false;\n  mOpen = false;\n  eos_debug(\"\");\n\n  // Flush any dirty attributes before closing\n  if (!mAttrSync && mAttrDirty) {\n    flushAttrFile();\n  }\n\n  if (mFd >= 0) {\n    int retval = close(mFd);\n    mFd = -1;\n\n    if (-1 == retval) {\n      eos_err(\"path=\\\"%s\\\" msg=\\\"%s\\\"\", mFilePath.c_str(), strerror(errno));\n      return -1;\n    }\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Truncate file\n//------------------------------------------------------------------------------\nint\nNfsIo::fileTruncate(XrdSfsFileOffset offset, uint16_t timeout)\n{\n  eos_debug(\"offset = %lld\", static_cast<int64_t>(offset));\n\n  if (mFd >= 0) {\n    int retval = ftruncate(mFd, offset);\n\n    if (-1 == retval) {\n      eos_err(\"path=\\\"%s\\\" msg=\\\"%s\\\"\", mFilePath.c_str(), strerror(errno));\n      return -1;\n    }\n\n    // Update seq_offset to reflect the new file size\n    seq_offset = offset;\n    return 0;\n  } else {\n    int retval = truncate(mFilePath.c_str(), offset);\n\n    if (-1 == retval) {\n      eos_err(\"path=\\\"%s\\\" msg=\\\"%s\\\"\", mFilePath.c_str(), strerror(errno));\n      return -1;\n    }\n\n    // Update seq_offset to reflect the new file size\n    seq_offset = offset;\n    return 0;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Truncate asynchronous\n//------------------------------------------------------------------------------\nstd::future<XrdCl::XRootDStatus>\nNfsIo::fileTruncateAsync(XrdSfsFileOffset offset, uint16_t timeout)\n{\n  std::promise<XrdCl::XRootDStatus> tr_promise;\n  std::future<XrdCl::XRootDStatus> tr_future = tr_promise.get_future();\n  int retc = fileTruncate(offset, timeout);\n\n  if (retc) {\n    tr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errUnknown,\n                         EIO, \"failed truncate\"));\n  } else {\n    tr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stOK, \"\"));\n  }\n\n  return tr_future;\n}\n\n//------------------------------------------------------------------------------\n// Get stats about the file\n//------------------------------------------------------------------------------\nint\nNfsIo::fileStat(struct stat* buf, uint16_t timeout)\n{\n  eos_debug(\"path=%s\", mFilePath.c_str());\n\n  if (mCreated) {\n    memset(buf, 0, sizeof(struct stat));\n    buf->st_size = seq_offset;\n    eos_debug(\"st-size=%llu\", buf->st_size);\n    return 0;\n  }\n\n  int result = stat(mFilePath.c_str(), buf);\n\n  if (-1 == result) {\n    eos_info(\"path=\\\"%s\\\" msg=\\\"%s\\\"\", mFilePath.c_str(), strerror(errno));\n    return -1;\n  }\n\n  return result;\n}\n\n//------------------------------------------------------------------------------\n// Remove file\n//------------------------------------------------------------------------------\nint\nNfsIo::fileRemove(uint16_t timeout)\n{\n  eos_debug(\"\");\n  eos_info(\"msg=\\\"fileRemove called\\\" path=\\\"%s\\\"\", mFilePath.c_str());\n  std::string attrPath = xattrPath();\n  int attr_rc = unlink(attrPath.c_str());\n\n  if (attr_rc == 0) {\n    eos_info(\"msg=\\\"deleted attribute file\\\" path=\\\"%s\\\"\", attrPath.c_str());\n  } else if (errno != ENOENT) {\n    eos_warning(\"msg=\\\"failed to delete attribute file\\\" path=\\\"%s\\\" errno=%d error=\\\"%s\\\"\",\n                attrPath.c_str(), errno, strerror(errno));\n  }\n\n  int rc = unlink(mFilePath.c_str());\n\n  if (-1 == rc) {\n    if (errno == ENOENT) {\n      eos_info(\"msg=\\\"file not found, skipping delete\\\" path=\\\"%s\\\"\",\n               mFilePath.c_str());\n      return 0;\n    }\n\n    eos_err(\"path=\\\"%s\\\" msg=\\\"%s\\\"\", mFilePath.c_str(), strerror(errno));\n    return -1;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Check for existence by path\n//------------------------------------------------------------------------------\nint\nNfsIo::fileExists()\n{\n  eos_debug(\"\");\n  struct stat st;\n  int result = stat(mFilePath.c_str(), &st);\n\n  if (-1 == result) {\n    eos_info(\"path=\\\"%s\\\" msg=\\\"%s\\\"\", mFilePath.c_str(), strerror(errno));\n    return -1;\n  }\n\n  return result;\n}\n\n//------------------------------------------------------------------------------\n// Delete by path\n//------------------------------------------------------------------------------\nint\nNfsIo::fileDelete(const char* path)\n{\n  eos_debug(\"\");\n  std::string path_str = std::string(path);\n\n  if (path_str.find(\"nfs:/\") == 0) {\n    path_str.erase(0, 5); // Remove \"nfs:/\" prefix\n  }\n\n  // Delete xattr file\n  std::string attrPath = getAttrPath(path_str);\n  int attr_rc = unlink(attrPath.c_str());\n\n  if (attr_rc == 0) {\n    eos_info(\"msg=\\\"deleted attribute file\\\" path=\\\"%s\\\"\", attrPath.c_str());\n  } else if (errno != ENOENT) {\n    eos_warning(\"msg=\\\"failed to delete attribute file\\\" path=\\\"%s\\\" \"\n                \"errno=%d error=\\\"%s\\\"\", attrPath.c_str(), errno,\n                strerror(errno));\n  }\n\n  // Delete the main file\n  int rc = unlink(path);\n\n  if (-1 == rc) {\n    eos_err(\"path=\\\"%s\\\" msg=\\\"%s\\\"\", path, strerror(errno));\n    return -1;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Rename by path\n//------------------------------------------------------------------------------\nint\nNfsIo::fsRename(std::string old_name, std::string new_name)\n{\n  std::string oldPath = old_name;\n  std::string newPath = new_name;\n\n  if (oldPath.find(\"nfs:/\") == 0) {\n    oldPath = oldPath.substr(5); // Remove \"nfs:/\" prefix\n  }\n\n  if (newPath.find(\"nfs:/\") == 0) {\n    newPath = newPath.substr(5); // Remove \"nfs:/\" prefix\n  }\n\n  // Rename the main file\n  int rc = rename(oldPath.c_str(), newPath.c_str());\n\n  if (-1 == rc) {\n    eos_static_err(\"msg=\\\"failed to rename main file\\\" old_path=\\\"%s\\\" \"\n                   \"new_path=\\\"%s\\\" errno=%d error=\\\"%s\\\"\",\n                   oldPath.c_str(), newPath.c_str(), errno, strerror(errno));\n    return -1;\n  }\n\n  // Rename the attribute files\n  std::string oldAttrPath = getAttrPath(oldPath);\n  std::string newAttrPath = getAttrPath(newPath);\n  struct stat st;\n\n  if (stat(oldAttrPath.c_str(), &st) == 0) {\n    int attr_rc = rename(oldAttrPath.c_str(), newAttrPath.c_str());\n\n    if (attr_rc != 0) {\n      eos_static_err(\"msg=\\\"failed to rename attribute file\\\" old_path=\\\"%s\\\" \"\n                     \"new_path=\\\"%s\\\" errno=%d error=\\\"%s\\\"\",\n                     oldAttrPath.c_str(), newAttrPath.c_str(), errno, strerror(errno));\n      return -1;\n    }\n  }\n\n  eos_static_info(\"msg=\\\"renamed attribute file\\\" old_path=\\\"%s\\\" \"\n                  \"new_path=\\\"%s\\\"\", oldAttrPath.c_str(),\n                  newAttrPath.c_str());\n  return 0;\n}\n\n//--------------------------------------------------------------------------\n//! Create a directory\n//--------------------------------------------------------------------------\nint\nNfsIo::Mkdir(const char* path, mode_t mode)\n{\n  eos_debug(\"\");\n  eos_info(\"path=\\\"%s\\\"\", path);\n\n  if (!path || strlen(path) == 0) {\n    errno = EINVAL;\n    return -1;\n  }\n\n  // Try to create the directory\n  if (mkdir(path, mode) == 0) {\n    eos_info(\"msg=\\\"successfully created directory\\\" path=\\\"%s\\\"\", path);\n    return 0;\n  }\n\n  if (errno == EEXIST) {\n    eos_info(\"msg=\\\"directory already exists\\\" path=\\\"%s\\\"\", path);\n    return 0;\n  }\n\n  // If parent doesn't exist, try to create it recursively\n  if (errno == ENOENT) {\n    std::string pathStr(path);\n    size_t pos = pathStr.rfind('/');\n\n    if (pos != std::string::npos && pos > 0) {\n      std::string parent = pathStr.substr(0, pos);\n\n      if (Mkdir(parent.c_str(), mode) != 0) {\n        eos_err(\"msg=\\\"failed to create parent directory\\\" path=\\\"%s\\\"\",\n                parent.c_str());\n        return -1;\n      }\n\n      if (mkdir(path, mode) == 0) {\n        eos_info(\"msg=\\\"successfully created directory\\\" path=\\\"%s\\\"\", path);\n        return 0;\n      }\n\n      if (errno == EEXIST) {\n        return 0;\n      }\n    }\n  }\n\n  eos_err(\"msg=\\\"failed to create directory\\\" path=\\\"%s\\\" error=\\\"%s\\\"\", path,\n          strerror(errno));\n  return -1;\n}\n\n//--------------------------------------------------------------------------\n//! Delete a directory\n//--------------------------------------------------------------------------\nint\nNfsIo::Rmdir(const char* path)\n{\n  eos_debug(\"\");\n  eos_info(\"path=\\\"%s\\\"\", path);\n  int rc = rmdir(path);\n\n  if (-1 == rc) {\n    eos_err(\"path=\\\"%s\\\" msg=\\\"%s\\\"\", path, strerror(errno));\n    return -1;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Sync file - use fsync\n//------------------------------------------------------------------------------\nint\nNfsIo::fileSync(uint16_t timeout)\n{\n  eos_debug(\"\");\n\n  if (mFd >= 0) {\n    int rc = fsync(mFd);\n\n    if (-1 == rc) {\n      eos_err(\"path=\\\"%s\\\" msg=\\\"%s\\\"\", mFilePath.c_str(), strerror(errno));\n      return -1;\n    }\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Get pointer to async meta handler object\n//------------------------------------------------------------------------------\n\nvoid*\nNfsIo::fileGetAsyncHandler()\n{\n  eos_debug(\"\");\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Attribute Interface\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Helper functions for attribute management\n//------------------------------------------------------------------------------\nstd::string\nNfsIo::xattrPath() const\n{\n  return getAttrPath(mFilePath);\n}\n\nint\nNfsIo::loadAttrFile()\n{\n  if (mAttrLoaded) {\n    return 0;\n  }\n\n  std::string attrPath = xattrPath();\n  std::string content;\n  int fd = open(attrPath.c_str(), O_RDONLY);\n\n  if (fd < 0) {\n    if (errno == ENOENT) {\n      mAttrLoaded = true;\n      return 0;\n    }\n\n    return -1;\n  }\n\n  char buffer[65536];\n  ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);\n  close(fd);\n\n  if (bytes_read >= 0) {\n    buffer[bytes_read] = '\\0';\n    content = buffer;\n\n    if (mFileMap.Load(content)) {\n      mAttrLoaded = true;\n      return 0;\n    }\n  }\n\n  return -1;\n}\n\nint\nNfsIo::flushAttrFile()\n{\n  if (!mAttrDirty) {\n    eos_debug(\"msg=\\\"no attributes to flush\\\" path=\\\"%s\\\"\", mFilePath.c_str());\n    return 0;\n  }\n\n  // Skip flush if main file is missing\n  struct stat stMain;\n\n  if (stat(mFilePath.c_str(), &stMain) != 0 && errno == ENOENT) {\n    mAttrDirty = false;\n    mAttrLoaded = false;\n    return 0;\n  }\n\n  std::string attrPath = xattrPath();\n  std::string content = mFileMap.Trim();\n  int fd = open(attrPath.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);\n\n  if (fd < 0) {\n    eos_err(\"msg=\\\"unable to open attribute file\\\" path=\\\"%s\\\" errno=%d\",\n            attrPath.c_str(), errno);\n    return -1;\n  }\n\n  ssize_t written = write(fd, content.c_str(), content.length());\n  close(fd);\n\n  if (written == (ssize_t)content.length()) {\n    eos_debug(\"msg=\\\"successfully wrote attribute file\\\" path=\\\"%s\\\" written=%zd\",\n              attrPath.c_str(), written);\n    mAttrDirty = false;\n    return 0;\n  }\n\n  eos_err(\"msg=\\\"unable to write to attribute file\\\" path=\\\"%s\\\" written=%zd expected=%zu errno=%d\",\n          attrPath.c_str(), written, content.length(), errno);\n  return -1;\n}\n\n//----------------------------------------------------------------\n//! Set a binary attribute (name has to start with 'user.' !!!)\n// ------------------------------------------------------------------------\n\nint\nNfsIo::attrSet(const char* name, const char* value, size_t len)\n{\n  eos_debug(\"\");\n  std::string lBlob;\n  errno = 0;\n\n  if (!mAttrSync && mAttrLoaded) {\n    std::string key = name;\n    std::string val;\n    val.assign(value, len);\n\n    if (val == \"#__DELETE_ATTR_#\") {\n      mFileMap.Remove(key);\n    } else {\n      mFileMap.Set(key, val);\n    }\n\n    mAttrDirty = true;\n    return 0;\n  }\n\n  // Load attributes from local file if not already loaded\n  if (loadAttrFile() != 0) {\n    eos_static_err(\"msg=\\\"unable to load attribute file\\\" path=\\\"%s\\\"\",\n                   mFilePath.c_str());\n    return -1;\n  }\n\n  std::string key = name;\n  std::string val;\n  val.assign(value, len);\n\n  if (val == \"#__DELETE_ATTR_#\") {\n    mFileMap.Remove(key);\n  } else {\n    mFileMap.Set(key, val);\n  }\n\n  mAttrDirty = true;\n  // Flush attributes to file\n  int result = flushAttrFile();\n\n  if (result != 0) {\n    eos_static_err(\"msg=\\\"failed to flush attribute file\\\" path=\\\"%s\\\" errno=%d\",\n                   mFilePath.c_str(), errno);\n  }\n\n  return result;\n}\n\n// ------------------------------------------------------------------------\n//! Set a string attribute (name has to start with 'user.' !!!)\n// ------------------------------------------------------------------------\n\nint\nNfsIo::attrSet(std::string key, std::string value)\n{\n  return attrSet(key.c_str(), value.c_str(), value.length());\n}\n\n\n// ------------------------------------------------------------------------\n//! Get a binary attribute by name (name has to start with 'user.' !!!)\n// ------------------------------------------------------------------------\n\nint\nNfsIo::attrGet(const char* name, char* value, size_t& size)\n{\n  eos_debug(\"\");\n  errno = 0;\n\n  if (!mAttrSync && mAttrLoaded) {\n    std::string val = mFileMap.Get(name);\n    size_t len = val.length() + 1;\n\n    if (len > size) {\n      len = size;\n    }\n\n    memcpy(value, val.c_str(), len);\n    eos_static_info(\"key=%s value=%s\", name, value);\n    return 0;\n  }\n\n  std::string lBlob;\n\n  // Load attributes from local file if not already loaded\n  if (loadAttrFile() != 0) {\n    eos_static_err(\"msg=\\\"unable to load attribute file\\\"\");\n    return -1;\n  }\n\n  std::string val = mFileMap.Get(name);\n  size_t len = val.length() + 1;\n\n  if (len > size) {\n    len = size;\n  }\n\n  memcpy(value, val.c_str(), len);\n  eos_static_info(\"key=%s value=%s\", name, value);\n  return 0;\n}\n\n// ------------------------------------------------------------------------\n//! Get a string attribute by name (name has to start with 'user.' !!!)\n// ------------------------------------------------------------------------\n\nint\nNfsIo::attrGet(std::string name, std::string& value)\n{\n  eos_debug(\"\");\n  errno = 0;\n\n  if (!mAttrSync && mAttrLoaded) {\n    std::map<std::string, std::string> lMap = mFileMap.GetMap();\n\n    if (lMap.count(name)) {\n      value = mFileMap.Get(name);\n      return 0;\n    } else {\n      errno = ENOATTR;\n      return -1;\n    }\n  }\n\n  // Load attributes from local file if not already loaded\n  if (loadAttrFile() != 0) {\n    eos_static_err(\"msg=\\\"unable to load attribute file\\\"\");\n    return -1;\n  }\n\n  std::map<std::string, std::string> lMap = mFileMap.GetMap();\n\n  if (lMap.count(name)) {\n    value = mFileMap.Get(name);\n    return 0;\n  } else {\n    errno = ENOATTR;\n    return -1;\n  }\n}\n\n// ------------------------------------------------------------------------\n//! Delete a binary attribute by name\n// ------------------------------------------------------------------------\nint\nNfsIo::attrDelete(const char* name)\n{\n  eos_debug(\"\");\n  errno = 0;\n  return attrSet(name, \"#__DELETE_ATTR_#\");\n}\n\n// ------------------------------------------------------------------------\n//! List all attributes for the associated path\n// ------------------------------------------------------------------------\nint\nNfsIo::attrList(std::vector<std::string>& list)\n{\n  eos_debug(\"\");\n\n  if (!mAttrSync && mAttrLoaded) {\n    std::map<std::string, std::string> lMap = mFileMap.GetMap();\n\n    for (auto it = lMap.begin(); it != lMap.end(); ++it) {\n      list.push_back(it->first);\n    }\n\n    return 0;\n  }\n\n  // Load attributes from local file if not already loaded\n  if (loadAttrFile() != 0) {\n    eos_static_err(\"msg=\\\"unable to load attribute file\\\"\");\n    return -1;\n  }\n\n  std::map<std::string, std::string> lMap = mFileMap.GetMap();\n\n  for (auto it = lMap.begin(); it != lMap.end(); ++it) {\n    list.push_back(it->first);\n  }\n\n  return 0;\n}\n\nint NfsIo::Statfs(struct statfs* sfs)\n{\n  eos_debug(\"msg=\\\"nfsio class statfs called\\\"\");\n  struct statvfs vfs;\n  int retval = statvfs(mFilePath.c_str(), &vfs);\n\n  if (retval != 0) {\n    eos_err(\"path=\\\"%s\\\" msg=\\\"%s\\\"\", mFilePath.c_str(), strerror(errno));\n    return -1;\n  }\n\n#ifdef __APPLE__\n  sfs->f_iosize = vfs.f_bsize;\n  sfs->f_bsize = sfs->f_iosize;\n  sfs->f_blocks = (fsblkcnt_t)(vfs.f_blocks);\n  sfs->f_bavail = (fsblkcnt_t)(vfs.f_bavail);\n#else\n  sfs->f_frsize = vfs.f_frsize;\n  sfs->f_bsize = sfs->f_frsize;\n  sfs->f_blocks = (fsblkcnt_t)(vfs.f_blocks);\n  sfs->f_bavail = (fsblkcnt_t)(vfs.f_bavail);\n#endif\n  sfs->f_bfree = (fsblkcnt_t)(vfs.f_bfree);\n  sfs->f_files = (fsfilcnt_t)(vfs.f_files);\n  sfs->f_ffree = (fsfilcnt_t)(vfs.f_ffree);\n  sfs->f_namelen = vfs.f_namemax;\n  unsigned long long total_bytes = vfs.f_blocks * vfs.f_bsize;\n  unsigned long long free_bytes = vfs.f_bavail * vfs.f_bsize;\n  unsigned long long total_files = vfs.f_files;\n  unsigned long long free_files = vfs.f_ffree;\n  eos_info(\"msg=\\\"statfs info\\\" total_bytes=%llu free_bytes=%llu \"\n           \"total_files=%llu free_files=%llu\",\n           total_bytes, free_bytes, total_files, free_files);\n  return 0;\n}\n\nEOSFSTNAMESPACE_END\n#endif // HAVE_NFS\n"
  },
  {
    "path": "fst/io/nfs/NfsIo.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file NfsIo.hh\n//! @author Robert-Paul Pasca - CERN\n//! @brief Abstract class modelling an IO plugin\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#ifdef HAVE_NFS\n#include \"fst/io/FileIo.hh\"\n#include \"common/FileMap.hh\"\n#include <string>\n#include <mutex>\n#include <nfsc/libnfs.h>\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! NFS IO plug-in\n//------------------------------------------------------------------------------\n\nclass NfsIo : public FileIo\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param path path to use\n  //! @param file XrdFstOfsFile pointer\n  //! @param client XrdSecEntity pointer\n  //!\n  //----------------------------------------------------------------------------\n  NfsIo(std::string path, XrdFstOfsFile* file, const XrdSecEntity* client);\n\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~NfsIo();\n\n  //--------------------------------------------------------------------------\n  //! Rename file\n  //!\n  //! @param old_name old file name\n  //! @param new_name new file name\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  static int fsRename(std::string old_name, std::string new_name);\n\n  //--------------------------------------------------------------------------\n  //! Open file\n  //!\n  //! @param flags open flags\n  //! @param mode open mode\n  //! @param opaque opaque information\n  //! @param timeout timeout value\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  int fileOpen(XrdSfsFileOpenMode flags,\n               mode_t mode = 0,\n               const std::string& opaque = \"\",\n               uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Open file asynchronously\n  //!\n  //! @param flags open flags\n  //! @param mode open mode\n  //! @param opaque opaque info to be appended to the request\n  //! @param timeout operation timeout\n  //!\n  //! @return future holding the status response\n  //--------------------------------------------------------------------------\n  std::future<XrdCl::XRootDStatus>\n  fileOpenAsync(XrdSfsFileOpenMode flags, mode_t mode = 0,\n                const std::string& opaque = \"\", uint16_t timeout = 0) override;\n\n  //--------------------------------------------------------------------------\n  //! Read from file - sync\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read or -1 if error\n  //--------------------------------------------------------------------------\n  int64_t fileRead(XrdSfsFileOffset offset,\n                   char* buffer,\n                   XrdSfsXferSize length,\n                   uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Read from file asynchronously\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read or -1 if error\n  //----------------------------------------------------------------------------\n  int64_t fileReadAsync(XrdSfsFileOffset offset, char* buffer,\n                        XrdSfsXferSize length, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Read from file with prefetching\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read or -1 if error\n  //----------------------------------------------------------------------------\n  int64_t fileReadPrefetch(XrdSfsFileOffset offset, char* buffer,\n                           XrdSfsXferSize length, uint16_t timeout = 0);\n\n\n  //----------------------------------------------------------------------------\n  //! Vector read - sync\n  //!\n  //! @param chunkList list of chunks for the vector read\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read of -1 if error\n  //----------------------------------------------------------------------------\n  int64_t fileReadV(XrdCl::ChunkList& chunkList,\n                    uint16_t timeout = 0)\n  {\n    // Operation not supported in NfsIo\n    return -ENOTSUP;\n  }\n\n  //------------------------------------------------------------------------------\n  //! Vector read - async\n  //!\n  //! @param chunkList list of chunks for the vector read\n  //! @param timeout timeout value\n  //!\n  //! @return 0(SFS_OK) if request successfully sent, otherwise -1(SFS_ERROR)\n  //------------------------------------------------------------------------------\n  int64_t fileReadVAsync(XrdCl::ChunkList& chunkList,\n                         uint16_t timeout = 0)\n  {\n    // Operation not supported in NfsIo\n    return -ENOTSUP;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Write to file - sync\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //! @param timeout timeout value\n  //! @return number of bytes written or -1 if error\n  //--------------------------------------------------------------------------\n  int64_t fileWrite(XrdSfsFileOffset offset,\n                    const char* buffer,\n                    XrdSfsXferSize length,\n                    uint16_t timeout = 0);\n\n  //--------------------------------------------------------------------------\n  //! Write to file - async\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes written or -1 if error\n  //--------------------------------------------------------------------------\n  int64_t fileWriteAsync(XrdSfsFileOffset offset,\n                         const char* buffer,\n                         XrdSfsXferSize length,\n                         uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Write to file - async\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //!\n  //! @return future holding the status response\n  //--------------------------------------------------------------------------\n  std::future<XrdCl::XRootDStatus>\n  fileWriteAsync(const char* buffer, XrdSfsFileOffset offset,\n                 XrdSfsXferSize length);\n\n  //--------------------------------------------------------------------------\n  //! Sync file to disk\n  //!\n  //! @param timeout timeout value\n  //! @return 0 on success, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  int fileSync(uint16_t timeout = 0);\n\n  //--------------------------------------------------------------------------\n  //! Get pointer to async meta handler object\n  //!\n  //! @return pointer to async handler, NULL otherwise\n  //--------------------------------------------------------------------------\n  void* fileGetAsyncHandler();\n\n  //--------------------------------------------------------------------------\n  //! Truncate\n  //!\n  //! @param offset truncate file to this value\n  //! @param timeout timeout value\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  int fileTruncate(XrdSfsFileOffset offset, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Truncate asynchronous\n  //!\n  //! @param offset truncate file to this value\n  //! @param timeout timeout value\n  //!\n  //! @return future holding the status response\n  //----------------------------------------------------------------------------\n  std::future<XrdCl::XRootDStatus>\n  fileTruncateAsync(XrdSfsFileOffset offset, uint16_t timeout = 0);\n\n  //--------------------------------------------------------------------------\n  //! Allocate file space\n  //!\n  //! @param length space to be allocated\n  //! @return 0 on success, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  int fileFallocate(XrdSfsFileOffset length)\n  {\n    return 0;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Deallocate file space\n  //!\n  //! @param fromOffset offset start\n  //! @param toOffset offset end\n  //! @return 0 on success, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  int fileFdeallocate(XrdSfsFileOffset fromOffset, XrdSfsFileOffset toOffset)\n  {\n    return 0;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Remove file\n  //!\n  //! @param timeout timeout value\n  //! @return 0 on success, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  int fileRemove(uint16_t timeout = 0);\n\n  //--------------------------------------------------------------------------\n  int fileDelete(const char* path);\n\n  //--------------------------------------------------------------------------\n  //! Check for the existence of a file\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  int fileExists();\n\n  //--------------------------------------------------------------------------\n  int Mkdir(const char* path, mode_t mode);\n  int Rmdir(const char* path);\n\n  //--------------------------------------------------------------------------\n  //! Close file\n  //!\n  //! @param timeout timeout value\n  //! @return 0 on success, -1 otherwise and error code is e is set\n  // ------------------------------------------------------------------------\n  int fileClose(uint16_t  timeout = 0);\n\n  //--------------------------------------------------------------------------\n  //! Get stats about the file\n  //!\n  //! @param buf stat buffer\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  int fileStat(struct stat* buf, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Execute implementation dependant commands\n  //!\n  //! @param buf stat buffer\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileFctl(const std::string& cmd, uint16_t timeout = 0)\n  {\n    // Operation not supported in NfsIO\n    return -ENOTSUP;\n  };\n\n  // ------------------------------------------------------------------------\n  //! Set a binary attribute (name has to start with 'user.' !!!)\n  //!\n  //! @param name attribute name\n  //! @param value attribute value\n  //! @param len value length\n  //! @return 0 on success, -1 otherwise and error code is set\n  // ------------------------------------------------------------------------\n  int attrSet(const char* name, const char* value, size_t len);\n\n  // ------------------------------------------------------------------------\n  //! Set a binary attribute (name has to start with 'user.' !!!)\n  //!\n  //! @param name attribute name\n  //! @param value attribute value\n  //! @return 0 on success, -1 otherwise and error code is set\n  // ------------------------------------------------------------------------\n  int attrSet(std::string name, std::string value);\n\n  // ------------------------------------------------------------------------\n  //! Get a binary attribute by name\n  //!\n  //! @param name attribute name\n  //! @param value contains attribute value upon success\n  //! @param size the buffer size, after success the value size\n  //! @return 0 on success, -1 otherwise and error code is set\n  // ------------------------------------------------------------------------\n  int attrGet(const char* name, char* value, size_t& size);\n\n  // ------------------------------------------------------------------------\n  //! Get a binary attribute by name\n  //!\n  //! @param name attribute name\n  //! @param value contains attribute value upon success\n  //! @return 0 on success, -1 otherwise and error code is set\n  // ------------------------------------------------------------------------\n  int attrGet(std::string name, std::string& value);\n\n  // ------------------------------------------------------------------------\n  //! Delete a binary attribute by name\n  //!\n  //! @param name attribute name\n  //! @return 0 on success, -1 otherwise and error code is set\n  // ------------------------------------------------------------------------\n  int attrDelete(const char* name);\n\n  // ------------------------------------------------------------------------\n  //! List all attributes for the associated path\n  //!\n  //! @param list contains all attribute names for the set path upon success\n  //! @return 0 on success, -1 otherwise and error code is set\n  // ------------------------------------------------------------------------\n  int attrList(std::vector<std::string>& list);\n\n  // ------------------------------------------------------------------------\n  //! Set attribute synchronization mode\n  //!\n  //! @param mode if true, every set attributes runs 'pull-modify-push',\n  //!             otherwise runs just once in the destructor,\n  //!             doing a 'pull-modify-modify-....-push' sequence\n  // ------------------------------------------------------------------------\n  void setAttrSync(bool mode = false)\n  {\n    mAttrSync = mode;\n  }\n\n  //--------------------------------------------------------------------------\n  //! traversing filesystem/storage routines\n  //--------------------------------------------------------------------------\n\n  class FtsHandle : public FileIo::FtsHandle\n  {\n  public:\n\n    FtsHandle(const char* dirp) : FileIo::FtsHandle(dirp)\n    {\n    }\n\n    virtual ~FtsHandle();\n  };\n\n  //--------------------------------------------------------------------------\n  //! Open a cursor to traverse a storage system\n  //!\n  //! @param options options for traversing the hierarchy\n  //\n  //! @return returns implementation dependent handle or 0 in case of error\n  //--------------------------------------------------------------------------\n  FileIo::FtsHandle* ftsOpen(int options = 0) override\n  {\n    return 0;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Return the next path related to a traversal cursor obtained with ftsOpen\n  //! @param fts_handle cursor obtained by ftsOpen\n  //! @return returns implementation dependent handle or 0 in case of error\n  //--------------------------------------------------------------------------\n  std::string ftsRead(FileIo::FtsHandle* fts_handle) override\n  {\n    return \"\";\n  }\n\n  //--------------------------------------------------------------------------\n  //! Close a traversal cursor\n  //! @param fts_handle cursor to close\n  //! @return 0 if fts_handle was an open cursor, otherwise -1\n  //--------------------------------------------------------------------------\n  int ftsClose(FileIo::FtsHandle* fts_handle) override\n  {\n    return -1;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Plug-in function to fill a statfs structure about the storage filling\n  //! state\n  //! @param statfs return struct\n  //! @return 0 if successful otherwise errno\n  //--------------------------------------------------------------------------\n\n  int Statfs(struct statfs* statFs);\n\n  // NFS context used when you have a NFS Server configuration - not supported\n  // static struct nfs_context* gContext;\n  // static std::mutex gContextMutex; ///< mutex for thread-safe context initialization\n  // static std::string gMountedPath; ///< path where NFS is mounted\n\nprivate:\n  std::string xattrPath() const; //< path to xattr file\n  int loadAttrFile(); ///< pull attributes from disk\n  int flushAttrFile(); ///< push modified attributes\n\n  int mFd {-1};\n  bool mCreated {false};\n  bool mOpen {false};\n  off_t seq_offset {0};\n  off_t short_read_offset {0};\n\n  std::string mFilePath; ///< file path\n  bool mAttrLoaded;\n  bool mAttrDirty;\n  bool mAttrSync;\n\n  eos::common::FileMap mFileMap; ///< extended attribute file map\n};\n\nEOSFSTNAMESPACE_END\n\n#endif  // HAVE_NFS\n"
  },
  {
    "path": "fst/io/xrd/ResponseCollector.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file ResponseCollector.cc\n//! @author Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/io/xrd/ResponseCollector.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Collect future object\n//------------------------------------------------------------------------------\nvoid\nResponseCollector::CollectFuture(std::future<XrdCl::XRootDStatus> fut)\n{\n  std::unique_lock<std::mutex> lock(mMutex);\n  mResponses.push_back(std::move(fut));\n}\n\n//------------------------------------------------------------------------------\n// Check the status of the responses\n//------------------------------------------------------------------------------\nbool\nResponseCollector::CheckResponses(bool wait_all, uint32_t max_pending)\n{\n  bool ok = true;\n  // If more then max_pending responses in-flight then wait for at least\n  // half of them.\n  bool wait_partial = false;\n  std::unique_lock<std::mutex> lock(mMutex);\n\n  if (mResponses.size() > max_pending) {\n    wait_partial = true;\n  }\n\n  while (!mResponses.empty()) {\n    auto& fut = mResponses.front();\n\n    if (!fut.valid()) {\n      ok = false;\n      mResponses.pop_front();\n      continue;\n    }\n\n    if (wait_all) {\n      fut.wait();\n    } else {\n      if (wait_partial) {\n        if (mResponses.size() > (max_pending >> 1)) {\n          fut.wait();\n        } else {\n          break;\n        }\n      } else {\n        // If current response is available then retrieve it\n        if (fut.wait_for(std::chrono::seconds(0)) != std::future_status::ready) {\n          break;\n        }\n      }\n    }\n\n    XrdCl::XRootDStatus status = fut.get();\n\n    if (!status.IsOK()) {\n      ok = false;\n    }\n\n    mResponses.pop_front();\n  }\n\n  return ok;\n}\n\n//------------------------------------------------------------------------------\n// Get number of registered responses\n//------------------------------------------------------------------------------\nuint32_t\nResponseCollector::GetNumResponses() const\n{\n  std::unique_lock<std::mutex> lock(mMutex);\n  return mResponses.size();\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/io/xrd/ResponseCollector.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file ResponseCollector.hh\n//! @author Elvin-Alin Sindrilaru - CERN\n//! @brief Object used for handling async responses\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"fst/Namespace.hh\"\n#include <XrdCl/XrdClXRootDResponses.hh>\n#include <future>\n#include <list>\n#include <mutex>\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class ResponseCollector\n//------------------------------------------------------------------------------\nclass ResponseCollector\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ResponseCollector() = default;\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~ResponseCollector()\n  {\n    // Make sure all futures are handled\n    (void) CheckResponses(true);\n  };\n\n  //----------------------------------------------------------------------------\n  //! Move assignment operator\n  //----------------------------------------------------------------------------\n  ResponseCollector& operator =(ResponseCollector&& other) noexcept\n  {\n    if (this != &other) {\n      mResponses.swap(other.mResponses);\n    }\n\n    return *this;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Move constructor\n  //----------------------------------------------------------------------------\n  ResponseCollector(ResponseCollector&& other) noexcept\n  {\n    *this = std::move(other);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Collect future object\n  //!\n  //@ @param fut future object\n  //----------------------------------------------------------------------------\n  void CollectFuture(std::future<XrdCl::XRootDStatus> fut);\n\n  //----------------------------------------------------------------------------\n  //! Check the status of the responses\n  //!\n  //! @param wait_all if true then block waiting for replies, otherwise only\n  //!        check replies that are ready\n  //! @param max_pending maximum number of pending responses, if this is\n  //!        reached then we wait for at least max_pending / 2\n  //!\n  //! @return true if all responses successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool CheckResponses(bool wait_all, uint32_t max_pending = 40);\n\n  //----------------------------------------------------------------------------\n  //! Get number of registered responses\n  //----------------------------------------------------------------------------\n  uint32_t GetNumResponses() const;\n\nprivate:\n  mutable std::mutex mMutex;\n  std::list<std::future<XrdCl::XRootDStatus>> mResponses;\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/io/xrd/XrdIo.cc",
    "content": "//------------------------------------------------------------------------------\n// File: XrdIo.cc\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <stdint.h>\n#include <cstdlib>\n#include \"fst/io/xrd/XrdIo.hh\"\n#include \"fst/io/ChunkHandler.hh\"\n#include \"fst/io/VectChunkHandler.hh\"\n#include \"fst/io/AsyncMetaHandler.hh\"\n#include \"common/FileMap.hh\"\n#include \"common/Logging.hh\"\n#include <XrdCl/XrdClDefaultEnv.hh>\n#include <XrdCl/XrdClBuffer.hh>\n#include <XrdCl/XrdClConstants.hh>\n#include <XrdSfs/XrdSfsInterface.hh>\n\n// Linux compat for Apple\n#ifdef __APPLE__\n#ifndef EREMOTEIO\n#define EREMOTEIO 121\n#endif\n#endif\n\nnamespace\n{\n//----------------------------------------------------------------------------\n//! InitReadahead\n//!\n//! @return true if readahead is enabled, otherwise false\n//----------------------------------------------------------------------------\nbool InitReadahead()\n{\n  char* ptr = getenv(\"EOS_FST_XRDIO_READAHEAD\");\n  return (ptr ? strtol(ptr, 0, 10) ? true : false : false);\n}\n\n//----------------------------------------------------------------------------\n//! InitReadaheadForceDisable\n//!\n//! @return true if readahead is force disabled, otherwise false\n//----------------------------------------------------------------------------\nbool InitReadaheadForceDisable()\n{\n  char* ptr = getenv(\"EOS_FST_XRDIO_READAHEAD_FORCE_DISABLE\");\n  return (ptr ? strtol(ptr, 0, 10) ? true : false : false);\n}\n\n//----------------------------------------------------------------------------\n//! InitInitNumRdAheadBlocks\n//!\n//! @return number of blocks that should be read ahead\n//----------------------------------------------------------------------------\nuint32_t InitNumRdAheadBlocks()\n{\n  char* ptr = getenv(\"EOS_FST_XRDIO_READAHEAD_BLOCKS\");\n  // default is 2 if envar is not set\n  return (ptr ? strtoul(ptr, 0, 10) : 2ul);\n}\n\n//----------------------------------------------------------------------------\n//! InitBlocksize\n//!\n//! @return read-ahead block size\n//----------------------------------------------------------------------------\nint32_t InitBlocksize()\n{\n  char* ptr = getenv(\"EOS_FST_XRDIO_BLOCK_SIZE\");\n  //default is 1M if the envar is not set\n  return (ptr ? strtol(ptr, 0, 10) : 1024 * 1024);\n}\n\n\nconst bool sReadaheadForceDisable = InitReadaheadForceDisable();\nconst bool sReadahead = InitReadahead();\nconst int32_t sBlockSize = InitBlocksize();\nconst uint32_t sNumRdAheadBlocks = InitNumRdAheadBlocks();\neos::common::BufferManager gBuffMgr(2 * eos::common::GB);\n}\n\nEOSFSTNAMESPACE_BEGIN\n\n// Static variables\neos::common::XrdConnPool XrdIo::mXrdConnPool;\n\nnamespace\n{\nstd::string getAttrUrl(std::string path)\n{\n  size_t qfind = path.rfind(\"?\");\n  size_t rfind = path.rfind(\"/\", qfind);\n\n  if (rfind != std::string::npos) {\n    path.insert(rfind + 1, \".\");\n  }\n\n  path += \".xattr\";\n  return path;\n}\n}\n\n//------------------------------------------------------------------------------\n// Constuctor for ReadaheadBlock\n//------------------------------------------------------------------------------\nReadaheadBlock::ReadaheadBlock(uint64_t blocksize,\n                               eos::common::BufferManager* buf_mgr,\n                               SimpleHandler* hd):\n  mBufMgr(buf_mgr)\n{\n  if (mBufMgr) {\n    mBuffer = mBufMgr->GetBuffer(blocksize);\n  } else {\n    mBuffer = std::make_shared<eos::common::Buffer>(blocksize);\n  }\n\n  if (mBuffer == nullptr) {\n    throw std::bad_alloc();\n  }\n\n  if (hd) {\n    mHandler.reset(hd);\n  } else {\n    mHandler = std::make_unique<SimpleHandler>();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get pointer to the underlying data buffer\n//------------------------------------------------------------------------------\nchar* ReadaheadBlock::GetDataPtr()\n{\n  return mBuffer->GetDataPtr();\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nReadaheadBlock::~ReadaheadBlock()\n{\n  if (mBufMgr) {\n    mBufMgr->Recycle(mBuffer);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Handle asynchronous open responses\n//------------------------------------------------------------------------------\nvoid\nAsyncIoOpenHandler::HandleResponseWithHosts(XrdCl::XRootDStatus* status,\n    XrdCl::AnyObject* response,\n    XrdCl::HostList* hostList)\n{\n  delete hostList;\n\n  // Response shoud be nullptr in general\n  if (response) {\n    delete response;\n  }\n\n  mFileIO->mXrdFile->GetProperty(\"LastURL\", mFileIO->mLastTriedUrl);\n\n  if (status->IsOK()) {\n    // Store the last URL we are connected after open\n    mFileIO->mXrdFile->GetProperty(\"LastURL\", mFileIO->mLastUrl);\n    mFileIO->mIsOpen = true;\n  }\n\n  mLayoutOpenHandler->HandleResponseWithHosts(status, 0, 0);\n  delete this;\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nXrdIo::XrdIo(std::string path, bool add_url_validity) :\n  FileIo(path, \"XrdIo\"),\n  mDoReadahead(sReadahead),\n  mNumRdAheadBlocks(sNumRdAheadBlocks),\n  mBlocksize(sBlockSize),\n  mXrdFile(NULL),\n  mMetaHandler(new AsyncMetaHandler()),\n  mXrdIdHelper(nullptr),\n  mPrefetchOffset(0ull),\n  mPrefetchHits(0ull),\n  mPrefetchBlocks(0ull),\n  mAddUrlValidity(add_url_validity)\n{\n  // Set the TimeoutResolution to 1\n  XrdCl::Env* env = XrdCl::DefaultEnv::GetEnv();\n  env->PutInt(\"TimeoutResolution\", 1);\n  size_t qpos;\n\n  // Opaque info can be part of the 'path'\n  if (((qpos = mFilePath.find(\"?\")) != std::string::npos)) {\n    mOpaque = mFilePath.substr(qpos + 1);\n    mFilePath.erase(qpos);\n  } else {\n    mOpaque = \"\";\n  }\n\n  // Set url for xattr requests\n  mAttrUrl = getAttrUrl(mFilePath.c_str());\n  setAttrSync(false);// by default sync attributes lazyly\n  mAttrLoaded = false;\n  mAttrDirty = false;\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nXrdIo::~XrdIo()\n{\n  if (mIsOpen) {\n    fileClose();\n  }\n\n  while (!mQueueBlocks.empty()) {\n    ReadaheadBlock* ptr_readblock = mQueueBlocks.front();\n    mQueueBlocks.pop();\n    delete ptr_readblock;\n  }\n\n  while (!mMapBlocks.empty()) {\n    delete mMapBlocks.begin()->second;\n    mMapBlocks.erase(mMapBlocks.begin());\n  }\n\n  delete mMetaHandler;\n\n  // deal with asynchrnous dirty attributes\n  if (!mAttrSync && mAttrDirty) {\n    std::string lMap = mFileMap.Trim();\n\n    if (!XrdIo::Upload(mAttrUrl, lMap)) {\n      mAttrDirty = false;\n    } else {\n      eos_static_err(\"msg=\\\"unable to upload to remote file map\\\" url=\\\"%s\\\"\",\n                     mAttrUrl.c_str());\n    }\n  }\n\n  if (mXrdFile) {\n    delete mXrdFile;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Open file - synchronously\n//------------------------------------------------------------------------------\nint\nXrdIo::fileOpen(XrdSfsFileOpenMode flags,\n                mode_t mode,\n                const std::string& opaque,\n                uint16_t timeout)\n{\n  const char* val = nullptr;\n  mWriteStatus = XrdCl::XRootDStatus();\n\n  if (!opaque.empty()) {\n    if (mOpaque.empty()) {\n      mOpaque = opaque;\n    } else {\n      mOpaque = mOpaque + \"&\" + opaque;\n    }\n  }\n\n  XrdOucEnv env_opaque(mOpaque.c_str());\n\n  // Decide if readahead is used and the block size\n  if (!sReadaheadForceDisable &&\n      (val = env_opaque.Get(\"fst.readahead\")) &&\n      (strncmp(val, \"true\", 4) == 0)) {\n    eos_debug(\"%s\", \"msg=\\\"enabling the readahead\\\"\");\n    mDoReadahead = true;\n    val = 0;\n\n    if ((val = env_opaque.Get(\"fst.blocksize\"))) {\n      mBlocksize = static_cast<uint64_t>(atoll(val));\n    }\n  }\n\n  if (mXrdFile) {\n    delete mXrdFile;\n    mXrdFile = NULL;\n  }\n\n  mXrdFile = new XrdCl::File();\n  // Final path + opaque info used in the open\n  mTargetUrl.FromString(BuildRequestUrl());\n  mXrdIdHelper.reset(new eos::common::XrdConnIdHelper(mXrdConnPool, mTargetUrl));\n\n  if (mXrdIdHelper->HasNewConnection()) {\n    eos_info(\"xrd_connection_id=%s\", mTargetUrl.GetHostId().c_str());\n  }\n\n  // Disable recovery on read and write\n  if (!mXrdFile->SetProperty(\"ReadRecovery\", \"false\") ||\n      !mXrdFile->SetProperty(\"WriteRecovery\", \"false\")) {\n    eos_warning(\"%s\",\n                \"msg=failed to set XrdCl::File properties read recovery and write \"\n                \"recovery to false\\\"\");\n  }\n\n  XrdCl::OpenFlags::Flags flags_xrdcl = eos::common::LayoutId::MapFlagsSfs2XrdCl(\n                                          flags);\n  XrdCl::Access::Mode mode_xrdcl = eos::common::LayoutId::MapModeSfs2XrdCl(mode);\n  XrdCl::XRootDStatus status = mXrdFile->Open(mTargetUrl.GetURL().c_str(),\n                               flags_xrdcl, mode_xrdcl,\n                               timeout);\n  mXrdFile->GetProperty(\"LastURL\", mLastTriedUrl);\n\n  if (!status.IsOK()) {\n    mLastErrMsg = status.ToStr().c_str();\n    mLastErrCode = status.code;\n    mLastErrNo = status.errNo;\n    eos_err(\"error= \\\"open failed url=%s, errno=%i, errc=%i, msg=%s\\\"\",\n            mTargetUrl.GetURL().c_str(), mLastErrNo, mLastErrCode,\n            mLastErrMsg.c_str());\n\n    if (!mLastErrNo) {\n      eos_warning(\"%s\",\n                  \"msg=\\\"error encountered despite errno=0; setting errno=22\\\"\");\n      mLastErrNo = EINVAL;\n    }\n\n    errno = mLastErrNo;\n    return SFS_ERROR;\n  } else {\n    errno = 0;\n    mIsOpen = true;\n  }\n\n  // Save the last URL we are connected after open\n  mXrdFile->GetProperty(\"LastURL\", mLastUrl);\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Open file asynchronously\n//------------------------------------------------------------------------------\nstd::future<XrdCl::XRootDStatus>\nXrdIo::fileOpenAsync(XrdSfsFileOpenMode flags, mode_t mode,\n                     const std::string& opaque, uint16_t timeout)\n{\n  using eos::common::LayoutId;\n  std::promise<XrdCl::XRootDStatus> open_promise;\n  std::future<XrdCl::XRootDStatus> open_future = open_promise.get_future();\n\n  if (!opaque.empty()) {\n    if (mOpaque.empty()) {\n      mOpaque = opaque;\n    } else {\n      mOpaque = mOpaque + \"&\" + opaque;\n    }\n  }\n\n  XrdOucEnv env_opaque(mOpaque.c_str());\n  const char* val = nullptr;\n\n  // Decide if readahead is used and the block size\n  if (!sReadaheadForceDisable &&\n      (val = env_opaque.Get(\"fst.readahead\")) &&\n      (strncmp(val, \"true\", 4) == 0)) {\n    eos_debug(\"%s\", \"msg=\\\"enabling the readahead\\\"\");\n    mDoReadahead = true;\n    val = 0;\n\n    if ((val = env_opaque.Get(\"fst.blocksize\"))) {\n      mBlocksize = static_cast<uint64_t>(atoll(val));\n    }\n  }\n\n  if (mXrdFile) {\n    delete mXrdFile;\n    mXrdFile = NULL;\n  }\n\n  mXrdFile = new XrdCl::File();\n  // Final path + opaque info used in the open\n  mTargetUrl.FromString(BuildRequestUrl());\n  mXrdIdHelper.reset(new eos::common::XrdConnIdHelper(mXrdConnPool, mTargetUrl));\n\n  if (mXrdIdHelper->HasNewConnection()) {\n    eos_info(\"xrd_connection_id=%s\", mTargetUrl.GetHostId().c_str());\n  }\n\n  if (!mXrdFile->SetProperty(\"ReadRecovery\", \"false\") ||\n      !mXrdFile->SetProperty(\"WriteRecovery\", \"false\")) {\n    eos_warning(\"%s\", \"msg=\\\"failed to disable file read and write recovery\\\"\");\n  }\n\n  XrdIoHandler* open_handler = new XrdIoHandler(std::move(open_promise),\n      XrdIoHandler::OpType::Open);\n  XrdCl::OpenFlags::Flags flags_xrdcl = LayoutId::MapFlagsSfs2XrdCl(flags);\n  XrdCl::Access::Mode mode_xrdcl = LayoutId::MapModeSfs2XrdCl(mode);\n  XrdCl::XRootDStatus status = mXrdFile->Open(mTargetUrl.GetURL().c_str(),\n                               flags_xrdcl, mode_xrdcl,\n                               open_handler, timeout);\n\n  if (!status.IsOK()) {\n    open_handler->HandleResponse(new XrdCl::XRootDStatus(status), nullptr);\n  }\n\n  return open_future;\n}\n\n//------------------------------------------------------------------------------\n// Read from file - sync\n//------------------------------------------------------------------------------\nint64_t\nXrdIo::fileRead(XrdSfsFileOffset offset, char* buffer, XrdSfsXferSize length,\n                uint16_t timeout)\n{\n  eos_debug(\"offset=%llu length=%llu\", static_cast<uint64_t>(offset),\n            static_cast<uint64_t>(length));\n  uint32_t bytes_read = 0;\n\n  if (!mXrdFile) {\n    errno = EIO;\n    return SFS_ERROR;\n  }\n\n  XrdCl::XRootDStatus status = mXrdFile->Read(static_cast<uint64_t>(offset),\n                               static_cast<uint32_t>(length),\n                               buffer, bytes_read, timeout);\n\n  if (!status.IsOK()) {\n    errno = status.errNo;\n    mLastErrMsg = status.ToStr().c_str();\n    mLastErrCode  = status.code;\n    mLastErrNo  = status.errNo;\n    return SFS_ERROR;\n  }\n\n  return bytes_read;\n}\n\n//------------------------------------------------------------------------------\n// Read with prefetching\n//------------------------------------------------------------------------------\nint64_t\nXrdIo::fileReadPrefetch(XrdSfsFileOffset offset, char* buffer,\n                        XrdSfsXferSize length, uint16_t timeout)\n{\n  eos_debug(\"offset=%lli length=%i\", offset, length);\n\n  if (!mXrdFile) {\n    errno = EIO;\n    return SFS_ERROR;\n  }\n\n  if (!mDoReadahead) {\n    eos_debug(\"%s\", \"msg=\\\"readahead is disabled\\\"\");\n    return fileRead(offset, buffer, length, timeout);\n  }\n\n  int64_t fread = 0; // direct reads\n  int64_t nread = 0; // total read for current request\n  XrdSysMutexHelper lock(mPrefetchMutex);\n  char* ptr_buff = buffer;\n\n  while (length) {\n    auto iter = FindBlock(offset);\n\n    if (iter == mMapBlocks.end()) {\n      RecycleBlocks(iter);\n      // Read directly the current block and prefetch the next one\n      fread = fileRead(offset, ptr_buff, length);\n\n      if (offset && (offset != eos::common::LayoutId::OssXsBlockSize)) {\n        eos_info(\"msg=\\\"disable readahead\\\" offset=%lli\", offset);\n        mDoReadahead = false;\n      }\n\n      if ((fread == length) && mDoReadahead) {\n        if (!PrefetchBlock(offset + length, timeout)) {\n          eos_err(\"msg=\\\"failed to send prefetch request\\\" offset=%lli\",\n                  offset + length);\n          mDoReadahead = false;\n        }\n      }\n\n      nread += fread;\n      return nread;\n    }\n\n    // Update prefetch statistics\n    if (iter->first != mPrefetchOffset) {\n      mPrefetchOffset = iter->first;\n      ++mPrefetchBlocks;\n    }\n\n    SimpleHandler* sh = iter->second->mHandler.get();\n    uint64_t shift = offset - iter->first;\n    RecycleBlocks(iter);\n    PrefetchBlock(mMapBlocks.rbegin()->first + mBlocksize);\n\n    if (!sh->WaitOK()) {\n      // Error while prefetching, remove block from map\n      eos_err(\"%s\", \"msg=\\\"prefetching failed, disable it and clean blocks\\\"\");\n      mDoReadahead = false;\n      RecycleBlocks(mMapBlocks.end());\n      fread = fileRead(offset, ptr_buff, length);\n      nread += fread;\n      return nread;\n    }\n\n    eos_debug(\"msg=\\\"read from prefetched block\\\" blk_off=%lld, req_off= %lld\",\n              iter->first, offset);\n\n    if (sh->GetRespLength() <= 0) {\n      // The request got a response but it read 0 bytes\n      eos_debug(\"%s\", \"msg=\\\"response contains 0 bytes\\\"\");\n      return nread;\n    }\n\n    uint32_t aligned_length = sh->GetRespLength() - shift;\n    uint64_t read_length = ((uint32_t) length < aligned_length) ? length :\n                           aligned_length;\n    ptr_buff = static_cast<char*>(memcpy(ptr_buff,\n                                         iter->second->GetDataPtr() + shift,\n                                         read_length));\n    ptr_buff += read_length;\n    offset += read_length;\n    length -= read_length;\n    nread += read_length;\n\n    // If prefetch block smaller than mBlocksize and current offset at the end\n    // of the prefetch block then we reached the end of file\n    if ((sh->GetRespLength() != mBlocksize) &&\n        ((uint64_t) offset >= iter->first + sh->GetRespLength())) {\n      break;\n    }\n  }\n\n  ++mPrefetchHits;\n  return nread;\n}\n\n//------------------------------------------------------------------------------\n// Vector read - sync\n//------------------------------------------------------------------------------\nint64_t\nXrdIo::fileReadV(XrdCl::ChunkList& chunkList, uint16_t timeout)\n{\n  eos_debug(\"read count=%i\", chunkList.size());\n  int64_t nread = 0;\n\n  if (!mXrdFile) {\n    errno = EIO;\n    return SFS_ERROR;\n  }\n\n  XrdCl::VectorReadInfo* vReadInfo = 0;\n  XrdCl::XRootDStatus status = mXrdFile->VectorRead(chunkList, 0,\n                               vReadInfo, timeout);\n\n  if (!status.IsOK())  {\n    errno = status.errNo;\n    mLastErrMsg = status.ToStr().c_str();\n    mLastErrCode  = status.code;\n    mLastErrNo  = status.errNo;\n    return SFS_ERROR;\n  }\n\n  nread = vReadInfo->GetSize();\n  delete vReadInfo;\n  return nread;\n}\n\n//------------------------------------------------------------------------------\n// Vector read - async\n//------------------------------------------------------------------------------\nint64_t\nXrdIo::fileReadVAsync(XrdCl::ChunkList& chunkList, uint16_t timeout)\n{\n  if (!mXrdFile) {\n    errno = EIO;\n    return SFS_ERROR;\n  }\n\n  // Get vector handler and send async request\n  VectChunkHandler* vhandler = 0;\n  XrdCl::XRootDStatus status;\n  eos_debug(\"read count=%i\", chunkList.size());\n  vhandler = mMetaHandler->Register(chunkList, NULL, false);\n\n  if (!vhandler) {\n    eos_err(\"%s\", \"msg=\\\"unable to get vector handler\\\"\");\n    return SFS_ERROR;\n  }\n\n  int64_t nread = vhandler->GetLength();\n  status = mXrdFile->VectorRead(chunkList, static_cast<void*>(0),\n                                static_cast<XrdCl::ResponseHandler*>(vhandler),\n                                timeout);\n\n  if (!status.IsOK()) {\n    // TODO: for the time being we call this ourselves but this should be\n    // dropped once XrdCl will call the handler for a request as it knows it\n    // has already failed\n    mMetaHandler->HandleResponse(&status, vhandler);\n    mLastErrMsg = status.ToStr().c_str();\n    mLastErrCode  = status.code;\n    mLastErrNo  = status.errNo;\n    return SFS_ERROR;\n  }\n\n  return nread;\n}\n\n//------------------------------------------------------------------------------\n// Write to file - sync\n//------------------------------------------------------------------------------\nint64_t\nXrdIo::fileWrite(XrdSfsFileOffset offset, const char* buffer,\n                 XrdSfsXferSize length, uint16_t timeout)\n{\n  eos_debug(\"offset=%llu length=%llu\", static_cast<uint64_t>(offset),\n            static_cast<uint64_t>(length));\n\n  if (!mXrdFile) {\n    errno = EIO;\n    return SFS_ERROR;\n  }\n\n  XrdCl::XRootDStatus status = mXrdFile->Write(static_cast<uint64_t>(offset),\n                               static_cast<uint32_t>(length),\n                               buffer, timeout);\n\n  if (!status.IsOK()) {\n    errno = status.errNo;\n    mLastErrMsg = status.ToStr().c_str();\n    mLastErrCode  = status.code;\n    mLastErrNo  = status.errNo;\n    return SFS_ERROR;\n  }\n\n  return length;\n}\n\n//------------------------------------------------------------------------------\n// Write to file - async\n//------------------------------------------------------------------------------\nint64_t\nXrdIo::fileWriteAsync(XrdSfsFileOffset offset, const char* buffer,\n                      XrdSfsXferSize length, uint16_t timeout)\n{\n  eos_static_debug(\"offset=%llu length=%i\", offset, length);\n\n  if (!mXrdFile) {\n    errno = EIO;\n    return SFS_ERROR;\n  }\n\n  if (!mWriteStatus.IsOK()) {\n    // if there was any async write error, we always return it again\n    return SFS_ERROR;\n  }\n\n  ChunkHandler* handler = mMetaHandler->Register(offset, length, (char*)buffer,\n                          true);\n\n  // If previous write requests failed then we won't get a new handler\n  // and we return directly an error\n  if (!handler) {\n    return SFS_ERROR;\n  }\n\n  // Obs: Use the handler buffer for write requests\n  XrdCl::XRootDStatus status = mXrdFile->Write(static_cast<uint64_t>(offset),\n                               static_cast<uint32_t>(length),\n                               handler->GetBuffer(),\n                               handler, timeout);\n\n  if (!status.IsOK()) {\n    // remember write failures 'forever'\n    mWriteStatus = status;\n    mMetaHandler->HandleResponse(&status, handler);\n    return SFS_ERROR;\n  }\n\n  return length;\n}\n\n//------------------------------------------------------------------------------\n// Write to file - async\n//------------------------------------------------------------------------------\nstd::future<XrdCl::XRootDStatus>\nXrdIo::fileWriteAsync(const char* buffer, XrdSfsFileOffset offset,\n                      XrdSfsXferSize length)\n{\n  eos_static_debug(\"offset=%llu length=%i\", offset, length);\n  std::promise<XrdCl::XRootDStatus> wr_promise;\n  std::future<XrdCl::XRootDStatus> wr_future = wr_promise.get_future();\n\n  if (!mXrdFile) {\n    errno = EIO;\n    wr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errOSError,\n                         errno));\n    return wr_future;\n  }\n\n  XrdIoHandler* wr_handler = nullptr;\n\n  try {\n    wr_handler = new XrdIoHandler(std::move(wr_promise),\n                                  XrdIoHandler::OpType::Write,\n                                  &gBuffMgr, buffer, length);\n  } catch (const BufferAllocateException& e) {\n    errno = ENOMEM;\n    eos_err(\"msg=\\\"%s\\\" offset=%lli, length=%li\", e.what(), offset, length);\n    wr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errOSError,\n                         errno));\n    return wr_future;\n  }\n\n  XrdCl::XRootDStatus status = mXrdFile->Write(static_cast<uint64_t>(offset),\n                               static_cast<uint32_t>(length),\n                               wr_handler->GetDataPtr(), wr_handler);\n\n  if (!status.IsOK()) {\n    wr_handler->HandleResponse(new XrdCl::XRootDStatus(status), nullptr);\n  }\n\n  return wr_future;\n}\n\n//------------------------------------------------------------------------------\n// Wait for async IO\n//------------------------------------------------------------------------------\nint\nXrdIo::fileWaitAsyncIO()\n{\n  bool async_ok = true;\n  {\n    XrdSysMutexHelper scope_lock(mPrefetchMutex);\n\n    // Wait for any requests on the fly and then close\n    while (!mMapBlocks.empty()) {\n      SimpleHandler* shandler = mMapBlocks.begin()->second->mHandler.get();\n\n      if (shandler->HasRequest()) {\n        async_ok = shandler->WaitOK();\n      }\n\n      delete mMapBlocks.begin()->second;\n      mMapBlocks.erase(mMapBlocks.begin());\n    }\n  }\n\n  // Wait for any async requests before closing\n  if (mMetaHandler) {\n    if (mMetaHandler->WaitOK() != XrdCl::errNone) {\n      eos_err(\"error=async requests failed for file path=%s\", mFilePath.c_str());\n      async_ok = false;\n    }\n  }\n\n  if (async_ok) {\n    return 0;\n  } else {\n    errno = EIO;\n    return -1;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Truncate file\n//------------------------------------------------------------------------------\nint\nXrdIo::fileTruncate(XrdSfsFileOffset offset, uint16_t timeout)\n{\n  if (!mXrdFile) {\n    errno = EIO;\n    return SFS_ERROR;\n  }\n\n  XrdCl::XRootDStatus status = mXrdFile->Truncate(static_cast<uint64_t>(offset),\n                               timeout);\n\n  if (!status.IsOK()) {\n    errno = status.errNo;\n    mLastErrMsg = status.ToStr().c_str();\n    mLastErrCode  = status.code;\n    mLastErrNo  = status.errNo;\n    return SFS_ERROR;\n  }\n\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Truncate asynchronous\n//------------------------------------------------------------------------------\nstd::future<XrdCl::XRootDStatus>\nXrdIo::fileTruncateAsync(XrdSfsFileOffset offset, uint16_t timeout)\n{\n  eos_static_debug(\"offset=%llu\", offset);\n  std::promise<XrdCl::XRootDStatus> tr_promise;\n  std::future<XrdCl::XRootDStatus> tr_future = tr_promise.get_future();\n\n  if (!mXrdFile) {\n    errno = EIO;\n    tr_promise.set_value(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errUnknown,\n                         EIO));\n    return tr_future;\n  }\n\n  XrdIoHandler* tr_handler = new XrdIoHandler(std::move(tr_promise),\n      XrdIoHandler::OpType::Truncate);\n  XrdCl::XRootDStatus status = mXrdFile->Truncate(static_cast<uint64_t>(offset),\n                               tr_handler, timeout);\n\n  if (!status.IsOK()) {\n    errno = status.errNo;\n    tr_handler->HandleResponse(new XrdCl::XRootDStatus(status), nullptr);\n  }\n\n  return tr_future;\n}\n\n//------------------------------------------------------------------------------\n// Sync file to disk\n//------------------------------------------------------------------------------\nint\nXrdIo::fileSync(uint16_t timeout)\n{\n  if (!mXrdFile) {\n    errno = EIO;\n    return SFS_ERROR;\n  }\n\n  XrdCl::XRootDStatus status = mXrdFile->Sync(timeout);\n\n  if (!status.IsOK()) {\n    errno = status.errNo;\n    mLastErrMsg = status.ToStr().c_str();\n    mLastErrCode  = status.code;\n    mLastErrNo  = status.errNo;\n    return SFS_ERROR;\n  }\n\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Get stats about the file\n//------------------------------------------------------------------------------\nint\nXrdIo::fileStat(struct stat* buf, uint16_t timeout)\n{\n  if (!mXrdFile) {\n    eos_err(\"%s\", \"msg=\\\"underlying XrdClFile object doesn't exist\\\"\");\n    errno = EIO;\n    return SFS_ERROR;\n  }\n\n  int rc = SFS_ERROR;\n  XrdCl::StatInfo* stat = 0;\n  XrdCl::XRootDStatus status = mXrdFile->Stat(true, stat, timeout);\n\n  if (!status.IsOK()) {\n    errno = status.errNo;\n    mLastErrMsg = status.ToStr().c_str();\n    mLastErrCode  = status.code;\n    mLastErrNo  = status.errNo;\n    eos_info(\"errcode=%i, errno=%i, errmsg=%s\", mLastErrCode, mLastErrNo,\n             mLastErrMsg.c_str());\n  } else {\n    buf->st_dev = static_cast<dev_t>(atoi(stat->GetId().c_str()));\n    buf->st_mode = static_cast<mode_t>(stat->GetFlags());\n    buf->st_size = static_cast<off_t>(stat->GetSize());\n    buf->st_mtime = static_cast<time_t>(stat->GetModTime());\n    rc = SFS_OK;\n  }\n\n  if (stat) {\n    delete stat;\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Execute implementation dependant commands\n//------------------------------------------------------------------------------\nint\nXrdIo::fileFctl(const std::string& cmd, uint16_t timeout)\n{\n  if (!mXrdFile) {\n    eos_info(\"underlying XrdClFile object doesn't exist\");\n    errno = EIO;\n    return SFS_ERROR;\n  }\n\n  XrdCl::Buffer arg;\n  XrdCl::Buffer* response = 0;\n  (void) arg.FromString(cmd);\n  XrdCl::XRootDStatus status = mXrdFile->Fcntl(arg, response, timeout);\n  delete response;\n  return status.status;\n}\n\n//------------------------------------------------------------------------------\n// Close file\n//------------------------------------------------------------------------------\nint\nXrdIo::fileClose(uint16_t timeout)\n{\n  if (!mXrdFile) {\n    errno = EIO;\n    return SFS_ERROR;\n  }\n\n  XrdCl::XRootDStatus okstatus;\n  mWriteStatus = okstatus;\n  bool async_ok = true;\n  mIsOpen = false;\n\n  if (fileWaitAsyncIO()) {\n    async_ok = false;\n  }\n\n  XrdCl::XRootDStatus status = mXrdFile->Close(timeout);\n\n  if (!status.IsOK()) {\n    errno = status.errNo;\n    mLastErrMsg = status.ToStr().c_str();\n    mLastErrCode  = status.code;\n    mLastErrNo  = status.errNo;\n    return SFS_ERROR;\n  }\n\n  // If any of the async requests failed then we have an error\n  if (!async_ok) {\n    return SFS_ERROR;\n  }\n\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Remove file\n//------------------------------------------------------------------------------\nint\nXrdIo::fileRemove(uint16_t timeout)\n{\n  if (!mXrdFile) {\n    errno = EIO;\n    return SFS_ERROR;\n  }\n\n  // Send opaque coamand to file object to mark it for deletion\n  XrdCl::Buffer arg;\n  XrdCl::Buffer* response = 0;\n  (void) arg.FromString(\"delete\");\n  XrdCl::XRootDStatus status = mXrdFile->Fcntl(arg, response, timeout);\n  delete response;\n\n  if (!status.IsOK()) {\n    eos_err(\"failed to mark the file for deletion:%s\", mFilePath.c_str());\n    return SFS_ERROR;\n  }\n\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Check for existence\n//------------------------------------------------------------------------------\nint\nXrdIo::fileExists()\n{\n  XrdCl::URL xUrl(mFilePath);\n  XrdCl::FileSystem fs(xUrl);\n  XrdCl::StatInfo* stat;\n  XrdCl::XRootDStatus status = fs.Stat(xUrl.GetPath(), stat);\n  errno = 0;\n\n  if (!status.IsOK()) {\n    if (status.errNo == kXR_NotFound) {\n      errno = ENOENT;\n      mLastErrMsg = \"no such file or directory\";\n      mLastErrCode  = status.code;\n      mLastErrNo  = status.errNo;\n    } else {\n      errno = EIO;\n      mLastErrMsg = \"failed to check for existence\";\n      mLastErrCode  = status.code;\n      mLastErrNo  = status.errNo;\n    }\n\n    return SFS_ERROR;\n  }\n\n  if (stat) {\n    delete stat;\n    return SFS_OK;\n  } else {\n    errno = ENODATA;\n    return SFS_ERROR;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Delete file by path\n//------------------------------------------------------------------------------\nint\nXrdIo::fileDelete(const char* url)\n{\n  XrdCl::URL xUrl(url);\n  std::string attrurl = getAttrUrl(url);\n  XrdCl::URL xAttrUrl(attrurl);\n  XrdCl::FileSystem fs(xUrl);\n  XrdCl::XRootDStatus status = fs.Rm(xUrl.GetPath());\n  XrdCl::XRootDStatus status_attr = fs.Rm(xAttrUrl.GetPath());\n  errno = 0;\n\n  if (!status.IsOK()) {\n    eos_err(\"error=failed to delete file - %s\", url);\n    mLastErrMsg = \"failed to delete file\";\n    mLastErrCode  = status.code;\n    mLastErrNo  = status.errNo;\n    errno = EIO;\n    return SFS_ERROR;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Read from file asynchronously\n// @note The buffer given by the user is not neccessarily populated with\n//       any meaningful data when this function returns. The user should call\n//       fileWaitAsyncIO to enforce this guarantee.\n//------------------------------------------------------------------------------\nint64_t\nXrdIo::fileReadAsync(XrdSfsFileOffset offset, char* buffer,\n                     XrdSfsXferSize length, uint16_t timeout)\n{\n  // @todo(esindril) fall back to sync mode for the time being\n  return fileRead(offset, buffer, length, timeout);\n}\n\n//------------------------------------------------------------------------------\n// Try to find a block in cache which contains the required offset\n//------------------------------------------------------------------------------\nPrefetchMap::iterator\nXrdIo::FindBlock(uint64_t offset)\n{\n  if (mMapBlocks.empty()) {\n    return mMapBlocks.end();\n  }\n\n  PrefetchMap::iterator iter = mMapBlocks.lower_bound(offset);\n\n  if ((iter != mMapBlocks.end()) && (iter->first == offset)) {\n    // Found exactly the block needed\n    return iter;\n  } else {\n    if (iter == mMapBlocks.begin()) {\n      // Only blocks with bigger offsets, return pointer to end of the map\n      return mMapBlocks.end();\n    } else {\n      // Check if the previous block, we know the map is not empty\n      iter--;\n\n      if ((iter->first <= offset) && (offset < (iter->first + mBlocksize))) {\n        return iter;\n      } else {\n        return mMapBlocks.end();\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Prefetch block using the readahead mechanism\n//------------------------------------------------------------------------------\nbool\nXrdIo::PrefetchBlock(int64_t offset, uint16_t timeout)\n{\n  ReadaheadBlock* block {nullptr};\n  eos_debug(\"msg=\\\"try to prefetch\\\" offset=%lli length=%i\",\n            offset, mBlocksize);\n\n  // Block is already prefetched\n  if (FindBlock(offset) != mMapBlocks.end()) {\n    return true;\n  }\n\n  if (mQueueBlocks.empty()) {\n    if (mMapBlocks.size() < mNumRdAheadBlocks) {\n      try {\n        block = new ReadaheadBlock(mBlocksize, &gBuffMgr);\n      } catch (const std::bad_alloc& e) {\n        eos_static_err(\"%s\", \"msg=\\\"failed to allocate a prefetch block\\\"\");\n        return false;\n      }\n    } else {\n      return false;\n    }\n  } else {\n    block = mQueueBlocks.front();\n    mQueueBlocks.pop();\n  }\n\n  block->mHandler->Update(offset, mBlocksize);\n  XrdCl::XRootDStatus status = mXrdFile->Read(offset, mBlocksize,\n                               block->GetDataPtr(),\n                               block->mHandler.get(), timeout);\n\n  if (!status.IsOK()) {\n    // Create tmp status which is deleted in the HandleResponse method\n    XrdCl::XRootDStatus* tmp_status = new XrdCl::XRootDStatus(status);\n    block->mHandler->HandleResponse(tmp_status, NULL);\n    mQueueBlocks.push(block);\n    return false;\n  } else {\n    mMapBlocks.insert(std::make_pair(offset, block));\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Recycle blocks from the map that are not useful since the current offset\n// is already grater then their offset\n//------------------------------------------------------------------------------\nvoid\nXrdIo::RecycleBlocks(std::map<uint64_t, ReadaheadBlock*>::iterator iter)\n{\n  for (auto it = mMapBlocks.begin(); it != iter; ++it) {\n    // Remove all elements from map so that we can align with the new\n    // requests and prefetch a new block. But first we need to collect any\n    // responses which are in-flight as otherwise these response might\n    // arrive later on, when we are expecting replies for other blocks\n    SimpleHandler* sh = it->second->mHandler.get();\n\n    if (sh->HasRequest()) {\n      // Not interested in the result - discard it\n      sh->WaitOK();\n    }\n\n    mQueueBlocks.push(it->second);\n  }\n\n  mMapBlocks.erase(mMapBlocks.begin(), iter);\n}\n\n\n//------------------------------------------------------------------------------\n// Get pointer to async meta handler object\n//------------------------------------------------------------------------------\nvoid*\nXrdIo::fileGetAsyncHandler()\n{\n  return static_cast<void*>(mMetaHandler);\n}\n\n//------------------------------------------------------------------------------\n// Run a space query command as statfs\n//------------------------------------------------------------------------------\nint\nXrdIo::Statfs(struct statfs* sfs)\n{\n  XrdCl::URL xUrl(mFilePath);\n  XrdCl::FileSystem fs(xUrl);\n  XrdCl::Buffer* response = 0;\n  XrdCl::Buffer arg(xUrl.GetPath().size());\n  arg.FromString(xUrl.GetPath());\n  XrdCl::XRootDStatus status = fs.Query(XrdCl::QueryCode::Space, arg,\n                                        response, (uint16_t) 15);\n  errno = 0;\n\n  if (!status.IsOK()) {\n    eos_err(\"msg=\\\"failed to statfs remote XRootD\\\" url=\\\"%s\\\"\", mFilePath.c_str());\n    mLastErrMsg = \"failed to statfs remote XRootD\";\n    mLastErrCode  = status.code;\n    mLastErrNo  = status.errNo;\n    errno = EREMOTEIO;\n    return errno;\n  }\n\n  if (response) {\n    // oss.cgroup=default&oss.space=469799256416256&oss.free=468894771826688&\n    // oss.maxf=68719476736&oss.used=904484589568&oss.quota=469799256416256\n    XrdOucEnv spaceEnv(response->ToString().c_str());\n    unsigned long long free_bytes = 0;\n    unsigned long long total_bytes = 0;\n\n    if (spaceEnv.Get(\"oss.free\")) {\n      free_bytes = strtoull(spaceEnv.Get(\"oss.free\"), 0, 10);\n    } else {\n      errno = EINVAL;\n      return errno;\n    }\n\n    if (spaceEnv.Get(\"oss.space\")) {\n      total_bytes = strtoull(spaceEnv.Get(\"oss.space\"), 0, 10);\n    } else {\n      errno = EINVAL;\n      return errno;\n    }\n\n#ifdef __APPLE__\n    sfs->f_iosize = 4096;\n    sfs->f_bsize = sfs->f_iosize;\n    sfs->f_blocks = (fsblkcnt_t)(total_bytes / sfs->f_iosize);\n    sfs->f_bavail = (fsblkcnt_t)(free_bytes / sfs->f_iosize);\n#else\n    sfs->f_frsize = 4096;\n    sfs->f_bsize = sfs->f_frsize;\n    sfs->f_blocks = (fsblkcnt_t)(total_bytes / sfs->f_frsize);\n    sfs->f_bavail = (fsblkcnt_t)(free_bytes / sfs->f_frsize);\n#endif\n    sfs->f_bfree = sfs->f_bavail;\n    sfs->f_files = 1000000;\n    sfs->f_ffree = 1000000;\n    delete response;\n    return 0;\n  } else {\n    errno = EREMOTEIO;\n    return errno;\n  }\n}\n\n//------------------------------------------------------------------------------\n//                      **** Attribute Interface ****\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Set attr\n//------------------------------------------------------------------------------\nint\nXrdIo::attrSet(const char* name, const char* value, size_t len)\n{\n  if (!mAttrSync && mAttrLoaded) {\n    std::string key = name;\n    std::string val;\n    val.assign(value, len);\n\n    if (val == \"#__DELETE_ATTR_#\") {\n      mFileMap.Remove(key);\n    } else {\n      // just modify\n      mFileMap.Set(key, val);\n    }\n\n    mAttrDirty = true;\n    return 0;\n  }\n\n  std::string lBlob;\n\n  // download\n  if (!XrdIo::Download(mAttrUrl, lBlob) || errno == ENOENT) {\n    mAttrLoaded = true;\n\n    if (mFileMap.Load(lBlob)) {\n      std::string key = name;\n      std::string val;\n\n      if (val == \"#__DELETE_ATTR_#\") {\n        mFileMap.Remove(key);\n      } else {\n        val.assign(value, len);\n        mFileMap.Set(key, val);\n      }\n\n      mAttrDirty = true;\n\n      if (mAttrSync) {\n        std::string lMap = mFileMap.Trim();\n\n        if (!XrdIo::Upload(mAttrUrl, lMap)) {\n          mAttrDirty = false;\n          return SFS_OK;\n        } else {\n          eos_static_err(\"msg=\\\"unable to upload to remote file map\\\" url=\\\"%s\\\"\",\n                         mAttrUrl.c_str());\n        }\n      }\n    } else {\n      eos_static_err(\"msg=\\\"unable to parse remote file map\\\" url=\\\"%s\\\"\",\n                     mAttrUrl.c_str());\n      errno = EINVAL;\n    }\n  } else {\n    eos_static_err(\"msg=\\\"unable to download remote file map\\\" url=\\\"%s\\\"\",\n                   mAttrUrl.c_str());\n  }\n\n  return SFS_ERROR;\n}\n\n//------------------------------------------------------------------------------\n// Set a string attribute (name has to start with 'user.' !!!)\n//------------------------------------------------------------------------------\nint\nXrdIo::attrSet(std::string name, std::string value)\n{\n  return attrSet(name.c_str(), value.c_str(), value.length());\n}\n\n//------------------------------------------------------------------------------\n// Get a binary attribute by name (name has to start with 'user.' !!!)\n//------------------------------------------------------------------------------\nint\nXrdIo::attrGet(const char* name, char* value, size_t& size)\n{\n  errno = 0;\n\n  if (!mAttrSync && mAttrLoaded) {\n    std::string val = mFileMap.Get(name);\n    size_t len = val.length() + 1;\n\n    if (len > size) {\n      len = size;\n    }\n\n    memcpy(value, val.c_str(), len);\n    eos_static_info(\"key=%s value=%s\", name, value);\n    return 0;\n  }\n\n  std::string lBlob;\n\n  if (!XrdIo::Download(mAttrUrl, lBlob) || errno == ENOENT) {\n    mAttrLoaded = true;\n\n    if (mFileMap.Load(lBlob)) {\n      std::string val = mFileMap.Get(name);\n      size_t len = val.length() + 1;\n\n      if (len > size) {\n        len = size;\n      }\n\n      memcpy(value, val.c_str(), len);\n      eos_static_info(\"key=%s value=%s\", name, value);\n      return SFS_OK;\n    }\n  } else {\n    eos_static_err(\"msg=\\\"unable to download remote file map\\\" url=\\\"%s\\\"\",\n                   mAttrUrl.c_str());\n  }\n\n  return SFS_ERROR;\n}\n\n///------------------------------------------------------------------------------\n// Get a string attribute by name (name has to start with 'user.' !!!)\n//------------------------------------------------------------------------------\nint\nXrdIo::attrGet(std::string name, std::string& value)\n{\n  errno = 0;\n\n  if (!mAttrSync && mAttrLoaded) {\n    value = mFileMap.Get(name);\n    return SFS_OK;\n  }\n\n  std::string lBlob;\n\n  if (!XrdIo::Download(mAttrUrl, lBlob) || errno == ENOENT) {\n    mAttrLoaded = true;\n\n    if (mFileMap.Load(lBlob)) {\n      value = mFileMap.Get(name);\n      return SFS_OK;\n    }\n  } else {\n    eos_static_err(\"msg=\\\"unable to download remote file map\\\" url=\\\"%s\\\"\",\n                   mAttrUrl.c_str());\n  }\n\n  return SFS_ERROR;\n}\n\n//------------------------------------------------------------------------------\n// Delete a binary attribute by name\n//------------------------------------------------------------------------------\nint\nXrdIo::attrDelete(const char* name)\n{\n  errno = 0;\n  return attrSet(name, \"#__DELETE_ATTR_#\");\n}\n\n//------------------------------------------------------------------------------\n// List all attributes for the associated path\n//------------------------------------------------------------------------------\nint\nXrdIo::attrList(std::vector<std::string>& list)\n{\n  if (!mAttrSync && mAttrLoaded) {\n    std::map<std::string, std::string> lMap = mFileMap.GetMap();\n\n    for (auto it = lMap.begin(); it != lMap.end(); ++it) {\n      list.push_back(it->first);\n    }\n\n    return 0;\n  }\n\n  std::string lBlob;\n\n  if (!XrdIo::Download(mAttrUrl, lBlob) || errno == ENOENT) {\n    mAttrLoaded = true;\n\n    if (mFileMap.Load(lBlob)) {\n      std::map<std::string, std::string> lMap = mFileMap.GetMap();\n\n      for (auto it = lMap.begin(); it != lMap.end(); ++it) {\n        list.push_back(it->first);\n      }\n\n      return 0;\n    }\n  } else {\n    eos_static_err(\"msg=\\\"unable to download remote file map\\\" url=\\\"%s\\\"\",\n                   mAttrUrl.c_str());\n  }\n\n  return -1;\n}\n\n//--------------------------------------------------------------------------\n//          **** Traversing filesystem/storage routines ****\n//--------------------------------------------------------------------------\n\n//--------------------------------------------------------------------------\n// Open a cursor to traverse a storage system\n//--------------------------------------------------------------------------\nFileIo::FtsHandle*\nXrdIo::ftsOpen(int options)\n{\n  XrdCl::URL url(mFilePath.c_str());\n  XrdCl::FileSystem fs(url);\n  std::vector<std::string> files;\n  std::vector<std::string> directories;\n  XrdCl::XRootDStatus status =\n    XrdIo::GetDirList(&fs, url, &files, &directories);\n\n  if (!status.IsOK()) {\n    eos_err(\"error=listing remote XrdClFile - %s\", status.ToStr().c_str());\n    errno = status.errNo;\n    mLastErrMsg = status.ToStr().c_str();\n    mLastErrCode  = status.code;\n    mLastErrNo  = status.errNo;\n    return 0;\n  }\n\n  FtsHandle* handle = new FtsHandle(mFilePath.c_str());\n\n  if (!handle) {\n    return 0;\n  }\n\n  for (auto it = files.begin(); it != files.end(); ++it) {\n    XrdOucString fname = it->c_str();\n\n    // Skip attribute files\n    if (fname.beginswith(\".\") && fname.endswith(\".xattr\")) {\n      continue;\n    }\n\n    handle->found_files.push_back(mFilePath + *it);\n  }\n\n  for (auto it = directories.begin(); it != directories.end(); ++it) {\n    eos_info(\"adding dir=%s deepness=%d\", (mFilePath + *it + \"/\").c_str(),\n             handle->deepness);\n    handle->found_dirs[0].push_back(mFilePath + *it + \"/\");\n  }\n\n  return (FileIo::FtsHandle*)(handle);\n}\n\n//------------------------------------------------------------------------------\n// Return the next path related to a traversal cursor obtained with ftsOpen\n//------------------------------------------------------------------------------\nstd::string\nXrdIo::ftsRead(FileIo::FtsHandle* fts_handle)\n{\n  FtsHandle* handle = (FtsHandle*) fts_handle;\n\n  if (!handle->found_files.size()) {\n    do {\n      XrdCl::XRootDStatus status;\n      std::vector<std::string> files;\n      std::vector<std::string> directories;\n      auto dit = handle->found_dirs[handle->deepness].begin();\n      bool found = true;\n\n      while (dit == handle->found_dirs[handle->deepness].end()) {\n        // move to next level\n        handle->deepness++;\n        handle->found_dirs.resize(handle->deepness + 1);\n\n        if (!handle->found_dirs[handle->deepness].size()) {\n          found = false;\n          break;\n        } else {\n          dit = handle->found_dirs[handle->deepness].begin();\n        }\n      }\n\n      if (!found) {\n        break;\n      }\n\n      eos_info(\"searching at deepness=%d directory=%s\", handle->deepness,\n               dit->c_str());\n      std::string surl_dir = *dit;\n      XrdCl::URL url(surl_dir);\n      XrdCl::FileSystem fs(url);\n      status = XrdIo::GetDirList(&fs, url, &files, &directories);\n\n      if (!status.IsOK()) {\n        eos_err(\"error=listing remote XrdClFile - %s\", status.ToStr().c_str());\n        errno = status.errNo;\n        mLastErrMsg = status.ToStr().c_str();\n        mLastErrCode  = status.code;\n        mLastErrNo  = status.errNo;\n        return \"\";\n      } else {\n        handle->found_dirs[handle->deepness].erase(dit);\n      }\n\n      std::string new_file{\"\"};\n      std::string new_dir{\"\"};\n\n      for (auto it = files.begin(); it != files.end(); ++it) {\n        XrdOucString fname = it->c_str();\n\n        if (fname.beginswith(\".\") && fname.endswith(\".xattr\")) {\n          continue;\n        }\n\n        new_file = surl_dir + *it;\n        eos_info(\"adding file=%s\", new_file.c_str());\n        handle->found_files.push_back(new_file);\n      }\n\n      for (auto it = directories.begin(); it != directories.end(); ++it) {\n        new_dir = surl_dir + *it + \"/\";\n        eos_info(\"adding dir=%s deepness=%d\", new_dir.c_str(),\n                 handle->deepness + 1);\n        handle->found_dirs[handle->deepness + 1].push_back(new_dir);\n      }\n    } while (!handle->found_files.size());\n  }\n\n  if (handle->found_files.size()) {\n    std::string new_path = handle->found_files.front();\n    handle->found_files.pop_front();\n    return new_path;\n  }\n\n  return \"\";\n}\n\n//------------------------------------------------------------------------------\n// Close a traversal cursor\n//------------------------------------------------------------------------------\nint\nXrdIo::ftsClose(FileIo::FtsHandle* fts_handle)\n{\n  FtsHandle* handle = (FtsHandle*) fts_handle;\n  handle->found_files.clear();\n  handle->found_dirs.resize(1);\n  handle->found_dirs[0].resize(1);\n  handle->deepness = 0;\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Download a remote file into a string object\n//------------------------------------------------------------------------------\nint\nXrdIo::Download(std::string url, std::string& download)\n{\n  errno = 0;\n  static int s_blocksize = 65536;\n  XrdIo io(url.c_str());\n  off_t offset = 0;\n  std::string opaque;\n\n  if (!io.fileOpen(0, 0, opaque, 10)) {\n    ssize_t rbytes = 0;\n    download.resize(s_blocksize);\n\n    do {\n      rbytes = io.fileRead(offset, (char*) download.c_str(), s_blocksize, 30);\n\n      if (rbytes == s_blocksize) {\n        download.resize(download.size() + 65536);\n      }\n\n      if (rbytes > 0) {\n        offset += rbytes;\n      }\n    } while (rbytes == s_blocksize);\n\n    io.fileClose();\n    download.resize(offset);\n    return 0;\n  }\n\n  if (errno == 3011) {\n    return 0;\n  }\n\n  return -1;\n}\n\n//------------------------------------------------------------------------------\n// Upload a string object into a remote file\n//------------------------------------------------------------------------------\nint\nXrdIo::Upload(std::string url, std::string& upload)\n{\n  errno = 0;\n  XrdIo io(url.c_str());\n  std::string opaque;\n  int rc = 0;\n\n  if (!io.fileOpen(SFS_O_WRONLY | SFS_O_CREAT, S_IRWXU | S_IRGRP | SFS_O_MKPTH,\n                   opaque, 10)) {\n    eos_static_info(\"opened %s\", url.c_str());\n\n    if ((io.fileWrite(0, upload.c_str(), upload.length(),\n                      30)) != (ssize_t) upload.length()) {\n      eos_static_err(\"failed to write %d\", upload.length());\n      rc = -1;\n    } else {\n      eos_static_info(\"uploaded %d\\n\", upload.length());\n    }\n\n    io.fileClose();\n  } else {\n    eos_static_err(\"failed to open %s\", url.c_str());\n    rc = -1;\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Get a list of files and a list of directories inside a remote directory\n//------------------------------------------------------------------------------\nXrdCl::XRootDStatus\nXrdIo::GetDirList(XrdCl::FileSystem* fs, const XrdCl::URL& url,\n                  std::vector<std::string>* files,\n                  std::vector<std::string>* directories)\n{\n  eos_info(\"url=%s\", url.GetURL().c_str());\n  using namespace XrdCl;\n  DirectoryList* list;\n  XrdCl::XRootDStatus status;\n  status = fs->DirList(url.GetPath(), DirListFlags::Stat, list);\n\n  if (!status.IsOK()) {\n    return status;\n  }\n\n  for (DirectoryList::Iterator it = list->Begin(); it != list->End(); ++it) {\n    if ((*it)->GetStatInfo()->TestFlags(StatInfo::IsDir)) {\n      std::string directory = (*it)->GetName();\n      directories->push_back(directory);\n    } else {\n      std::string file = (*it)->GetName();\n      files->push_back(file);\n    }\n  }\n\n  return XRootDStatus();\n}\n\n//------------------------------------------------------------------------------\n// Process opaque info\n//------------------------------------------------------------------------------\nstd::string\nXrdIo::BuildRequestUrl() const\n{\n  using namespace std::chrono;\n  std::ostringstream oss;\n  oss << mFilePath;\n\n  if (!mOpaque.empty()) {\n    oss << \"?\" << mOpaque;\n  }\n\n  if (mAddUrlValidity) {\n    // Add extra capability expiration time based on the XRD_STREAMTIMEOUT value\n    uint64_t xrdcl_streamtimeout = XrdCl::DefaultStreamTimeout;\n    std::string env_val;\n\n    if (XrdCl::DefaultEnv::GetEnv()->GetString(\"StreamTimeout\", env_val)) {\n      try {\n        xrdcl_streamtimeout = std::stoull(env_val);\n      } catch (...) {}\n    }\n\n    auto now = system_clock::now();\n    auto valid_sec = time_point_cast<seconds>(now).time_since_epoch().count()\n                     + xrdcl_streamtimeout - 1;\n\n    if (mOpaque.empty()) {\n      oss << \"?fst.valid=\" << valid_sec;\n    } else {\n      oss << \"&fst.valid=\" << valid_sec;\n    }\n  }\n\n  return oss.str();\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/io/xrd/XrdIo.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file XrdIo.hh\n//! @author Elvin-Alin Sindrilaru - CERN\n//! @brief Class used for doing remote IO operations unsing the xrd client\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_XRDFILEIO_HH__\n#define __EOSFST_XRDFILEIO_HH__\n\n#include \"fst/io/FileIo.hh\"\n#include \"fst/io/SimpleHandler.hh\"\n#include \"common/FileMap.hh\"\n#include \"common/XrdConnPool.hh\"\n#include \"common/BufferManager.hh\"\n#include <XrdCl/XrdClFile.hh>\n#include <queue>\n\nnamespace eos\n{\nnamespace common\n{\nclass BufferManager;\nclass Buffer;\n}\n}\n\nEOSFSTNAMESPACE_BEGIN\n\n//! Forward declarations\nclass XrdIo;\nclass AsyncMetaHandler;\n\n//------------------------------------------------------------------------------\n//! Struct that holds a readahead buffer and corresponding handler\n//------------------------------------------------------------------------------\nstruct ReadaheadBlock {\n\n  //----------------------------------------------------------------------------\n  //! Constuctor\n  //!\n  //! @param blocksize the size of the readahead\n  //! @param buf_mgr buffer manager, if null buffers are allocated on demand\n  //! @param handler pre-allocated handler\n  //----------------------------------------------------------------------------\n  ReadaheadBlock(uint64_t blocksize,\n                 eos::common::BufferManager* buf_mgr = nullptr,\n                 SimpleHandler* hd = nullptr);\n\n  //----------------------------------------------------------------------------\n  //! Update current request\n  //!\n  //! @param offset offset\n  //! @param length length\n  //! @param isWrite true if write request, otherwise false\n  //----------------------------------------------------------------------------\n  void Update(uint64_t offset, uint32_t length, bool isWrite)\n  {\n    mHandler->Update(offset, length, isWrite);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get pointer to the underlying data buffer\n  //----------------------------------------------------------------------------\n  char* GetDataPtr();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~ReadaheadBlock();\n\n  eos::common::BufferManager* mBufMgr; ///< Buffer manager object\n  std::shared_ptr<eos::common::Buffer> mBuffer; ///< Current data block\n  std::unique_ptr<SimpleHandler> mHandler; ///< Async handler for the requests\n};\n\ntypedef std::map<uint64_t, ReadaheadBlock*> PrefetchMap;\n\n//------------------------------------------------------------------------------\n//! Class used for handling asynchronous open responses\n//------------------------------------------------------------------------------\nclass AsyncIoOpenHandler: public XrdCl::ResponseHandler,\n  public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param io_file file object\n  //! @param layout_handler handler for the layout object\n  //----------------------------------------------------------------------------\n  AsyncIoOpenHandler(XrdIo* io_file, XrdCl::ResponseHandler* layout_handler) :\n    mFileIO(io_file), mLayoutOpenHandler(layout_handler) {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~AsyncIoOpenHandler() {}\n\n  //----------------------------------------------------------------------------\n  //! Called when a response to associated request arrives or an error  occurs\n  //!\n  //! @param status   status of the request\n  //! @param response an object associated with the response (request dependent)\n  //! @param hostList list of hosts the request was redirected to\n  //---------------------------------------------------------------------------\n  virtual void HandleResponseWithHosts(XrdCl::XRootDStatus* status,\n                                       XrdCl::AnyObject* response,\n                                       XrdCl::HostList* hostList);\n\nprivate:\n  XrdIo* mFileIO; ///< File IO object corresponding to this handler\n  XrdCl::ResponseHandler* mLayoutOpenHandler; ///< Open handler for the layout\n};\n\n//------------------------------------------------------------------------------\n//! Class used for doing remote IO operations using the Xrd client\n//------------------------------------------------------------------------------\nclass XrdIo : public FileIo\n{\n  friend class AsyncIoOpenHandler;\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param path path or URI for the file\n  //! @param add_url_validity if true add fst.validity opaque info to the built\n  //!        URL, otherwise skip it\n  //----------------------------------------------------------------------------\n  XrdIo(std::string path, bool add_url_validity = true);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~XrdIo();\n\n  //----------------------------------------------------------------------------\n  //! Open file - synchronously\n  //!\n  //! @param flags open flags\n  //! @param mode open mode\n  //! @param opaque opaque information\n  //! @param timeout timeout value\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileOpen(XrdSfsFileOpenMode flags, mode_t mode = 0,\n               const std::string& opaque = \"\", uint16_t timeout = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Open file asynchronously\n  //!\n  //! @param flags open flags\n  //! @param mode open mode\n  //! @param opaque opaque info to be appended to the request\n  //! @param timeout operation timeout\n  //!\n  //! @return future holding the status response\n  //--------------------------------------------------------------------------\n  std::future<XrdCl::XRootDStatus>\n  fileOpenAsync(XrdSfsFileOpenMode flags, mode_t mode = 0,\n                const std::string& opaque = \"\", uint16_t timeout = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Read from file - sync\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read or -1 if error\n  //----------------------------------------------------------------------------\n  int64_t fileRead(XrdSfsFileOffset offset, char* buffer,\n                   XrdSfsXferSize length, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Write to file - sync\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes written or -1 if error\n  //----------------------------------------------------------------------------\n  int64_t fileWrite(XrdSfsFileOffset offset, const char* buffer,\n                    XrdSfsXferSize length, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Vector read - sync\n  //!\n  //! @param chunkList list of chunks for the vector read\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read of -1 if error\n  //----------------------------------------------------------------------------\n  int64_t fileReadV(XrdCl::ChunkList& chunkList, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Vector read - async\n  //!\n  //! @param chunkList list of chunks for the vector read\n  //! @param timeout timeout value\n  //!\n  //! @return 0(SFS_OK) if request successfully sent, otherwise -1(SFS_ERROR)\n  //----------------------------------------------------------------------------\n  int64_t fileReadVAsync(XrdCl::ChunkList& chunkList, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Read from file asynchronously\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read or -1 if error\n  //! @note The buffer given by the user is not neccessarily populated with\n  //!       any meaningful data when this function returns. The user should call\n  //!       fileWaitAsyncIO to enforce this guarantee.\n  //----------------------------------------------------------------------------\n  int64_t fileReadAsync(XrdSfsFileOffset offset, char* buffer,\n                        XrdSfsXferSize length, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Read from file with prefetching\n  //!\n  //! @param offset offset in file\n  //! @param buffer where the data is read\n  //! @param length read length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes read or -1 if error\n  //----------------------------------------------------------------------------\n  int64_t fileReadPrefetch(XrdSfsFileOffset offset, char* buffer,\n                           XrdSfsXferSize length, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Write to file - async\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //! @param timeout timeout value\n  //!\n  //! @return number of bytes written or -1 if error\n  //----------------------------------------------------------------------------\n  int64_t fileWriteAsync(XrdSfsFileOffset offset, const char* buffer,\n                         XrdSfsXferSize length, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Write to file - async\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //!\n  //! @return future holding the status response\n  //--------------------------------------------------------------------------\n  std::future<XrdCl::XRootDStatus>\n  fileWriteAsync(const char* buffer, XrdSfsFileOffset offset,\n                 XrdSfsXferSize length);\n\n  //--------------------------------------------------------------------------\n  //! Wait for all async IO\n  //!\n  //! @return global return code of async IO\n  //--------------------------------------------------------------------------\n  virtual int fileWaitAsyncIO();\n\n  //----------------------------------------------------------------------------\n  //! Truncate\n  //!\n  //! @param offset truncate file to this value\n  //! @param timeout timeout value\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileTruncate(XrdSfsFileOffset offset, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Truncate asynchronous\n  //!\n  //! @param offset truncate file to this value\n  //! @param timeout timeout value\n  //!\n  //! @return future holding the status response\n  //----------------------------------------------------------------------------\n  std::future<XrdCl::XRootDStatus>\n  fileTruncateAsync(XrdSfsFileOffset offset, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Allocate file space\n  //!\n  //! @param length space to be allocated\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileFallocate(XrdSfsFileOffset length)\n  {\n    return 0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Deallocate file space\n  //!\n  //! @param fromOffset offset start\n  //! @param toOffset offset end\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileFdeallocate(XrdSfsFileOffset fromOffset, XrdSfsFileOffset toOffset)\n  {\n    return 0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Remove file\n  //!\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileRemove(uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Delete not openedfile\n  //!\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileDelete(const char* url);\n\n  //----------------------------------------------------------------------------\n  //! Sync file to disk\n  //!\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileSync(uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Get pointer to async meta handler object\n  //!\n  //! @return pointer to async handler, NULL otherwise\n  //----------------------------------------------------------------------------\n  void* fileGetAsyncHandler();\n\n  //----------------------------------------------------------------------------\n  //! Check for the existence of a file\n  //!\n  //! @param path to the file\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileExists();\n\n  //----------------------------------------------------------------------------\n  //! Close file\n  //!\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileClose(uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Get stats about the file\n  //!\n  //! @param buf stat buffer\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileStat(struct stat* buf, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Execute implementation dependant commands\n  //!\n  //! @param buf stat buffer\n  //! @param timeout timeout value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int fileFctl(const std::string& cmd, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Set a binary attribute (name has to start with 'user.' !!!)\n  //!\n  //! @param name attribute name\n  //! @param value attribute value\n  //! @param len value length\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int attrSet(const char* name, const char* value, size_t len);\n\n  //----------------------------------------------------------------------------\n  //! Set a binary attribute (name has to start with 'user.' !!!)\n  //!\n  //! @param name attribute name\n  //! @param value attribute value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int attrSet(std::string name, std::string value);\n\n  //----------------------------------------------------------------------------\n  //! Get a binary attribute by name\n  //!\n  //! @param name attribute name\n  //! @param value contains attribute value upon success\n  //! @param size the buffer size, after success the value size\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int attrGet(const char* name, char* value, size_t& size);\n\n  //----------------------------------------------------------------------------\n  //! Get a binary attribute by name\n  //!\n  //! @param name attribute name\n  //! @param value contains attribute value upon success\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int attrGet(std::string name, std::string& value);\n\n  //----------------------------------------------------------------------------\n  //! Delete a binary attribute by name\n  //!\n  //! @param name attribute name\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int attrDelete(const char* name);\n\n  //----------------------------------------------------------------------------\n  //! List all attributes for the associated path\n  //!\n  //! @param list contains all attribute names for the set path upon success\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  int attrList(std::vector<std::string>& list);\n\n  //----------------------------------------------------------------------------\n  //! Set attribute synchronization mode\n  //!\n  //! @param on if true - every set attributes runs 'pull-modify-push',\n  //!        otherwise the destructor finished a 'pull-modify-modify-....-push'\n  //!        sequence\n  //----------------------------------------------------------------------------\n  void setAttrSync(bool mode = false)\n  {\n    mAttrSync = mode;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get block size used for read-ahead\n  //!\n  //! @return : default block size\n  //----------------------------------------------------------------------------\n  int32_t GetBlockSize()\n  {\n    return mBlocksize;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Plug-in function to fill a statfs structure about the storage filling\n  //! state\n  //!\n  //! @param path to statfs\n  //! @param statfs return struct\n  //!\n  //! @return 0 if successful otherwise errno\n  //----------------------------------------------------------------------------\n  int Statfs(struct statfs* statFs);\n\n  //----------------------------------------------------------------------------\n  //! Traversing filesystem/storage routines - FTS search handle\n  //----------------------------------------------------------------------------\n  class FtsHandle\n  {\n    friend class XrdIo;\n  protected:\n    std::vector< std::vector<std::string> > found_dirs;\n    std::deque< std::string> found_files;\n    size_t deepness;\n\n  public:\n    FtsHandle(const char* dirp)\n    {\n      found_dirs.resize(1);\n      deepness = 0;\n    };\n\n    virtual ~FtsHandle() = default;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Open a curser to traverse a storage system\n  //!\n  //! @param options options for traversing the hierarchy\n  //!\n  //! @return returns implementation dependent handle or 0 in case of error\n  //----------------------------------------------------------------------------\n  FileIo::FtsHandle* ftsOpen(int options = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Return the next path related to a traversal cursor obtained with ftsOpen\n  //!\n  //! @param fts_handle cursor obtained by ftsOpen\n  //!\n  //! @return returns implementation dependent handle or 0 in case of error\n  //----------------------------------------------------------------------------\n  std::string ftsRead(FileIo::FtsHandle* fts_handle) override;\n\n  //----------------------------------------------------------------------------\n  //! Close a traversal cursor\n  //!\n  //! @param fts_handle cursor to close\n  //!\n  //! @return 0 if fts_handle was an open cursor, otherwise -1\n  //----------------------------------------------------------------------------\n  virtual int ftsClose(FileIo::FtsHandle* fts_handle) override;\n\n#ifdef IN_TEST_HARNESS\npublic:\n#else\nprivate:\n#endif\n  static eos::common::XrdConnPool mXrdConnPool; ///< Xrd connection pool\n  bool mDoReadahead; ///< mark if readahead is enabled\n  const uint32_t mNumRdAheadBlocks; ///< no. of blocks used for readahead\n  int32_t mBlocksize; ///< block size for rd/wr opertations\n  XrdCl::File* mXrdFile; ///< handler to xrd file\n  AsyncMetaHandler* mMetaHandler; ///< async requests meta handler\n  PrefetchMap mMapBlocks; ///< map of block read/prefetched\n  std::queue<ReadaheadBlock*> mQueueBlocks; ///< queue containing available blocks\n  XrdSysMutex mPrefetchMutex; ///< mutex to serialise the prefetch step\n  eos::common::FileMap mFileMap; ///< extended attribute file map\n  std::string mAttrUrl; ///< extended attribute url\n  std::string mOpaque; ///< opaque tags in original url\n  bool mAttrLoaded; ///< mark if remote attributes have been loaded\n  bool mAttrDirty; ///< mark if local attr modfied and not committed\n  bool mAttrSync; ///< mark if attributes are updated synchronously\n  XrdCl::URL mTargetUrl; ///< URL used to avoid physical connection sharing\n  ///< RAAI helper for connection ids\n  std::unique_ptr<eos::common::XrdConnIdHelper> mXrdIdHelper;\n  XrdCl::XRootDStatus mWriteStatus;\n  uint64_t mPrefetchOffset; ///< Last block offset of a prefetch hit\n  uint64_t mPrefetchHits; ///< Number of prefetch hits\n  uint64_t mPrefetchBlocks; ///< Number of prefetched blocks\n  bool mAddUrlValidity; ///< If true append fst.validity to built URL\n\n  //----------------------------------------------------------------------------\n  //! Method used to prefetch the next block using the readahead mechanism\n  //!\n  //! @param offset begin offset of the current block we are reading\n  //! @param timeout timeout value\n  //!\n  //! @return true if prefetch request was sent, otherwise false\n  //----------------------------------------------------------------------------\n  bool PrefetchBlock(int64_t offset, uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Try to find a block in cache with contains the provided offset\n  //!\n  //! @param offset offset to be searched for\n  //!\n  //! @return iterator to the block containing the offset or if no such block\n  //!         is found we return the iterator to the end of the map\n  //----------------------------------------------------------------------------\n  PrefetchMap::iterator FindBlock(uint64_t offset);\n\n  //------------------------------------------------------------------------------\n  //! Recycle blocks from the map that are not useful since the current offset\n  //! is already grater then their offset\n  //!\n  //! @param iter iterator in the map of prefetched blocks\n  //------------------------------------------------------------------------------\n  void RecycleBlocks(std::map<uint64_t, ReadaheadBlock*>::iterator iter);\n\n  //----------------------------------------------------------------------------\n  //! Download a remote file into a string object\n  //!\n  //! @param url from where to download\n  //! @param download string where to place the contents\n  //!\n  //! @return 0 success, otherwise -1 and errno\n  //----------------------------------------------------------------------------\n  static int Download(std::string url, std::string& download);\n\n  //----------------------------------------------------------------------------\n  //! Upload a string object into a remote file\n  //!\n  //! @param url from where to upload\n  //! @param upload string to store into remote file\n  //!\n  //! @return 0 success, otherwise -1 and errno\n  //----------------------------------------------------------------------------\n  static int Upload(std::string url, std::string& upload);\n\n  //----------------------------------------------------------------------------\n  //! Get a directory listing - taken from XrdCl sources\n  //----------------------------------------------------------------------------\n  XrdCl::XRootDStatus GetDirList(XrdCl::FileSystem* fs,\n                                 const XrdCl::URL& url,\n                                 std::vector<std::string>* files,\n                                 std::vector<std::string>* directories);\n\n  //----------------------------------------------------------------------------\n  //! Build request URL containing the full endpoint with path and any extra\n  //! opaque information\n  //!\n  //! @param opaque input opaque information\n  //! @param out output containing the path with any additional opaque info\n  //----------------------------------------------------------------------------\n  std::string BuildRequestUrl() const;\n\n  //----------------------------------------------------------------------------\n  //! Disable copy constructor\n  //----------------------------------------------------------------------------\n  XrdIo(const XrdIo&) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Disable assign operator\n  //----------------------------------------------------------------------------\n  XrdIo& operator = (const XrdIo&) = delete;\n};\n\n//------------------------------------------------------------------------------\n//! Class BufferAllocateException\n//------------------------------------------------------------------------------\nclass BufferAllocateException: public std::exception\n{\npublic:\n  const char* what() const noexcept override\n  {\n    return \"failed to allocate buffer\";\n  }\n};\n\n\n//------------------------------------------------------------------------------\n//! Class XrdIoHandler\n//------------------------------------------------------------------------------\nclass XrdIoHandler: public XrdCl::ResponseHandler\n{\npublic:\n  enum class OpType {\n    None,\n    Write,\n    Truncate,\n    Open\n  };\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param promise promise object used to notify when the answer arrives\n  //! @param op type of operation for current handler\n  //! @param buf_mgr buffer manager object that supplies/recycles buffers\n  //! @param buffer user buffer object to be dulicated if valid and needed\n  //! @param buffer_len useful contents of the buffer to be used\n  //----------------------------------------------------------------------------\n  XrdIoHandler(std::promise<XrdCl::XRootDStatus>&& promise, OpType op,\n               eos::common::BufferManager* buf_mgr = nullptr,\n               const char* buffer = nullptr,\n               unsigned long long buffer_len = 0ull):\n    mOperationType(op), mBufMgr(buf_mgr), mBuffer(nullptr)\n  {\n    if (mBufMgr && buffer && buffer_len) {\n      int attempts = 5;\n\n      do {\n        mBuffer = mBufMgr->GetBuffer(buffer_len);\n\n        if (mBuffer) {\n          mBuffer->mLength = buffer_len;\n          (void) memcpy(mBuffer->GetDataPtr(), buffer, buffer_len);\n          break;\n        } else {\n          std::this_thread::sleep_for(std::chrono::seconds(1));\n        }\n      } while (--attempts);\n\n      if (!mBuffer) {\n        throw BufferAllocateException();\n      }\n    }\n\n    // The promise must be swaped after any potential exception is thrown\n    // otherwise the caller will be left with an invalid promise.\n    std::swap(mPromise, promise);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~XrdIoHandler()\n  {\n    if (mBufMgr && mBuffer) {\n      mBufMgr->Recycle(mBuffer);\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Handle response\n  //!\n  //! @param pStatus status of the response\n  //! @param pResponse object containing extra info about the response\n  //----------------------------------------------------------------------------\n  virtual void HandleResponse(XrdCl::XRootDStatus* pStatus,\n                              XrdCl::AnyObject* pResponse)\n  {\n    if (pStatus) {\n      mPromise.set_value(*pStatus);\n      delete pStatus;\n    } else {\n      mPromise.set_value(XrdCl::XRootDStatus(XrdCl::stError, XrdCl::errUnknown,\n                                             EINVAL, \"no status returned\"));\n    }\n\n    if (pResponse) {\n      delete pResponse;\n    }\n\n    delete this;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get pointer to the underlying data buffer\n  //----------------------------------------------------------------------------\n  char* GetDataPtr()\n  {\n    if (mBuffer) {\n      return mBuffer->GetDataPtr();\n    }\n\n    return nullptr;\n  }\n\nprivate:\n  OpType mOperationType;\n  std::promise<XrdCl::XRootDStatus> mPromise;\n  eos::common::BufferManager* mBufMgr;\n  std::shared_ptr<eos::common::Buffer> mBuffer;\n};\n\nEOSFSTNAMESPACE_END\n\n#endif  // __EOSFST_XRDFILEIO_HH__\n"
  },
  {
    "path": "fst/layout/HeaderCRC.cc",
    "content": "//------------------------------------------------------------------------------\n// File: HeaderCRC.cc\n// Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/layout/HeaderCRC.hh\"\n#include \"fst/io/FileIo.hh\"\n#include \"common/BufferManager.hh\"\n#include <stdint.h>\n#include <stdlib.h>\n\nEOSFSTNAMESPACE_BEGIN\n\nchar HeaderCRC::msTagName[] = \"_HEADER__RAIDIO_\";\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nHeaderCRC::HeaderCRC(int sizeHeader, int sizeBlock) :\n  mValid(false),\n  mNumBlocks(-1),\n  mIdStripe(-1),\n  mSizeLastBlock(-1),\n  mSizeBlock(sizeBlock),\n  mSizeHeader(sizeHeader)\n{\n  if (mSizeHeader == 0) {\n    mSizeHeader = eos::common::LayoutId::OssXsBlockSize;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Constructor with parameter\n//------------------------------------------------------------------------------\nHeaderCRC::HeaderCRC(int sizeHeader, long long numBlocks, int sizeBlock) :\n  mValid(false),\n  mNumBlocks(numBlocks),\n  mIdStripe(-1),\n  mSizeLastBlock(-1),\n  mSizeBlock(sizeBlock),\n  mSizeHeader(sizeHeader)\n{\n  (void) memcpy(mTag, msTagName, strlen(msTagName));\n\n  if (mSizeHeader == 0) {\n    mSizeHeader = eos::common::LayoutId::OssXsBlockSize;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Read header from generic file\n//------------------------------------------------------------------------------\nbool\nHeaderCRC::ReadFromFile(FileIo* pFile, uint16_t timeout)\n{\n  mValid = false;\n  long int offset = 0;\n  size_t read_sizeblock = 0;\n  auto buff = eos::common::GetAlignedBuffer(mSizeHeader);\n\n  if (buff == nullptr) {\n    eos_static_err(\"msg=\\\"failed to allocate buffer\\\" size=%lu\", mSizeHeader);\n    return mValid;\n  }\n\n  if (pFile->fileRead(offset, buff.get(), mSizeHeader, timeout) !=\n      static_cast<uint32_t>(mSizeHeader)) {\n    return mValid;\n  }\n\n  memcpy(mTag, buff.get(), sizeof mTag);\n\n  if (strncmp(mTag, msTagName, strlen(msTagName))) {\n    return mValid;\n  }\n\n  offset += sizeof mTag;\n  memcpy(&mIdStripe, buff.get() + offset, sizeof mIdStripe);\n  offset += sizeof mIdStripe;\n  memcpy(&mNumBlocks, buff.get() + offset, sizeof mNumBlocks);\n  offset += sizeof mNumBlocks;\n  memcpy(&mSizeLastBlock, buff.get() + offset, sizeof mSizeLastBlock);\n  offset += sizeof mSizeLastBlock;\n  memcpy(&read_sizeblock, buff.get() + offset, sizeof read_sizeblock);\n\n  if (mSizeBlock == 0) {\n    mSizeBlock = read_sizeblock;\n  } else if (mSizeBlock != read_sizeblock) {\n    eos_static_err(\"msg=\\\"read block size does not match expected size\\\" \"\n                   \"got=%lu expected=%lu\", read_sizeblock, mSizeBlock);\n    return mValid;\n  }\n\n  mValid = true;\n  return mValid;\n}\n\n//------------------------------------------------------------------------------\n// Write header to generic file\n//------------------------------------------------------------------------------\nbool\nHeaderCRC::WriteToFile(FileIo* pFile, uint16_t timeout)\n{\n  mValid = false;\n  int offset = 0;\n  auto buff = eos::common::GetAlignedBuffer(mSizeHeader);\n\n  if (buff == nullptr) {\n    eos_static_err(\"msg=\\\"failed to allocate buffer\\\" size=%lu\", mSizeHeader);\n    return mValid;\n  }\n\n  memcpy(buff.get() + offset, msTagName, strlen(msTagName));\n  offset += strlen(msTagName);\n  memcpy(buff.get() + offset, &mIdStripe, sizeof mIdStripe);\n  offset += sizeof mIdStripe;\n  memcpy(buff.get() + offset, &mNumBlocks, sizeof mNumBlocks);\n  offset += sizeof mNumBlocks;\n  memcpy(buff.get() + offset, &mSizeLastBlock, sizeof mSizeLastBlock);\n  offset += sizeof mSizeLastBlock;\n  memcpy(buff.get() + offset, &mSizeBlock, sizeof mSizeBlock);\n  offset += sizeof mSizeBlock;\n  memset(buff.get() + offset, 0, mSizeHeader - offset);\n\n  if (pFile->fileWrite(0, buff.get(), mSizeHeader, timeout) > 0) {\n    mValid = true;\n  }\n\n  return mValid;\n}\n\n//------------------------------------------------------------------------------\n// Dump header info in readable format\n//------------------------------------------------------------------------------\nstd::string\nHeaderCRC::DumpInfo() const\n{\n  std::ostringstream oss;\n\n  if (!mValid) {\n    oss << \"ERROR: RAIN header not valid!\";\n    return oss.str();\n  }\n\n  oss << \"Stripe index    : \" << mIdStripe << std::endl\n      << \"Num. blocks     : \" << mNumBlocks << std::endl\n      << \"Block size      : \" << mSizeBlock << std::endl\n      << \"Size last block : \" << mSizeLastBlock << std::endl;\n  return oss.str();\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/layout/HeaderCRC.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file HeaderCRC.hh\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Header information present at the start of each stripe file\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include <XrdCl/XrdClFile.hh>\n#include \"common/Logging.hh\"\n#include \"fst/Namespace.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\nclass FileIo;\n\n//------------------------------------------------------------------------------\n//! Header information present at the start of each stripe file\n//------------------------------------------------------------------------------\nclass HeaderCRC : public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @sizeHeader maximum size the header will have in the beginning of the file\n  //! @sizeBlock the size of the stripe block\n  //!\n  //----------------------------------------------------------------------------\n  HeaderCRC(int sizeHeader, int sizeBlock);\n\n  //----------------------------------------------------------------------------\n  //! Constructor with parameter\n  //!\n  //! @sizeHeader maximum size the header will have in the beginning of the file\n  //! @numBlocks number of data blocks in the current file\n  //! @sizeBlock the size of the stripe block\n  //----------------------------------------------------------------------------\n  HeaderCRC(int sizeHeader, long long int numBlocks, int sizeBlock);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~HeaderCRC() = default;\n\n  //----------------------------------------------------------------------------\n  //! Write header to file\n  //!\n  //! @param pFile file to which to header will be written\n  //! @param timeout timeout value\n  //!\n  //! @return status of the operation\n  //----------------------------------------------------------------------------\n  bool WriteToFile(FileIo* pFile, uint16_t timeout);\n\n\n  //----------------------------------------------------------------------------\n  //! Read header from file\n  //!\n  //! @param pFile file from which the header will be read\n  //! @param timeout timeout value\n  //!\n  //! @return status of the operation\n  //----------------------------------------------------------------------------\n  bool ReadFromFile(FileIo* pFile, uint16_t timeout);\n\n  //----------------------------------------------------------------------------\n  //! Get tag of the header\n  //----------------------------------------------------------------------------\n  inline const char* GetTag() const\n  {\n    return mTag;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get size of header\n  //----------------------------------------------------------------------------\n  inline int GetSize() const\n  {\n    return mSizeHeader;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get block size the file contains\n  //----------------------------------------------------------------------------\n  inline size_t GetSizeBlock() const\n  {\n    return mSizeBlock;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get size of last block in file\n  //----------------------------------------------------------------------------\n  inline size_t GetSizeLastBlock() const\n  {\n    return mSizeLastBlock;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get number of blocks in file\n  //----------------------------------------------------------------------------\n  inline long int GetNoBlocks() const\n  {\n    return mNumBlocks;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get id of the stripe the header belongs to\n  //----------------------------------------------------------------------------\n  inline unsigned int GetIdStripe() const\n  {\n    return mIdStripe;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set number of blocks in the file\n  //----------------------------------------------------------------------------\n  inline void SetNoBlocks(long long int numBlocks)\n  {\n    mNumBlocks = numBlocks;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set size of last block in the file\n  //----------------------------------------------------------------------------\n  inline void SetSizeLastBlock(size_t sizeLastBlock)\n  {\n    mSizeLastBlock = sizeLastBlock;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set id of the stripe the header belongs to\n  //----------------------------------------------------------------------------\n  inline void SetIdStripe(unsigned int stripe)\n  {\n    mIdStripe = stripe;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Test if header is valid\n  //----------------------------------------------------------------------------\n  inline bool IsValid() const\n  {\n    return mValid;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set the header state (valid/corrupted)\n  //----------------------------------------------------------------------------\n  inline void SetState(bool state)\n  {\n    mValid = state;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get size of the file based on the info in the header\n  //----------------------------------------------------------------------------\n  off_t GetSizeFile() const\n  {\n    if (mNumBlocks) {\n      return static_cast<off_t>((mNumBlocks - 1) * mSizeBlock +\n                                mSizeLastBlock);\n    }\n\n    return 0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Dump header info in readable format\n  //----------------------------------------------------------------------------\n  std::string DumpInfo() const;\n\nprivate:\n  char mTag[16]; ///< layout tag\n  bool mValid; ///< status of the file\n  long long int mNumBlocks; ///< total number of blocks\n  unsigned int mIdStripe; ///< index of the stripe the header belongs to\n  size_t mSizeLastBlock; ///< size of the last block of data\n  size_t mSizeBlock; ///< size of a block of data\n  int mSizeHeader; ///< size of the header\n  static char msTagName[]; ///< default tag name\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/layout/Layout.cc",
    "content": "//------------------------------------------------------------------------------\n// File: Layout.cc\n// Author: Elvin-Alin Sindrilaru / Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/layout/Layout.hh\"\n#include \"fst/XrdFstOfsFile.hh\"\n#include \"common/Strerror_r_wrapper.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nLayout::Layout(XrdFstOfsFile* file, eos::fst::FmdHandler* fmdHandler) :\n  mIsEntryServer(false), mLayoutId(0), mName(\"\"),\n  mLastErrCode(0), mLastErrNo(0), mOfsFile(file), mError(0), mSecEntity(0),\n  mIoType(eos::common::LayoutId::kLocal), mTimeout(0), mFileIO(nullptr),\n  mFmdHandler(fmdHandler)\n{}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nLayout::Layout(XrdFstOfsFile* file,\n               unsigned long lid,\n               const XrdSecEntity* client,\n               XrdOucErrInfo* outError,\n               const char* path,\n               eos::fst::FmdHandler* fmdHandler,\n               uint16_t timeout) :\n  eos::common::LogId(), mIsEntryServer(false), mLayoutId(lid),\n  mLastErrCode(0), mLastErrNo(0), mOfsFile(file), mError(outError),\n  mTimeout(timeout), mFmdHandler(fmdHandler)\n{\n  mSecEntity = const_cast<XrdSecEntity*>(client);\n  mIoType = eos::common::LayoutId::GetIoType(path);\n  mName = eos::common::LayoutId::GetLayoutTypeString(mLayoutId);\n  mLocalPath = (path ? path : \"\");\n  mFileIO.reset(FileIoPlugin::GetIoObject((path ? path : \"\"), mOfsFile,\n                                          mSecEntity));\n}\n\n//------------------------------------------------------------------------------\n// Return error message\n//------------------------------------------------------------------------------\nint\nLayout::Emsg(const char* pfx, XrdOucErrInfo& einfo,\n             int ecode, const char* op, const char* target)\n{\n  char etext[128], buffer[4096];\n\n  // Get the reason for the error\n  if (ecode < 0) {\n    ecode = -ecode;\n  }\n\n  if (eos::common::strerror_r(ecode, etext, sizeof(etext))) {\n    sprintf(etext, \"reason unknown (%d)\", ecode);\n  }\n\n  // Format the error message\n  snprintf(buffer, sizeof(buffer), \"Unable to %s %s; %s\", op, target, etext);\n\n  if ((ecode == EIDRM) || (ecode == ENODATA)) {\n    eos_static_debug(\"Unable to %s %s; %s\", op, target, etext);\n  } else {\n    if ((!strcmp(op, \"stat\")) || (((!strcmp(pfx, \"attr_get\")) ||\n                                   (!strcmp(pfx, \"attr_ls\")) ||\n                                   (!strcmp(pfx, \"FuseX\"))) && (ecode == ENOENT))) {\n      eos_static_debug(\"Unable to %s %s; %s\", op, target, etext);\n    } else {\n      eos_static_err(\"Unable to %s %s; %s\", op, target, etext);\n    }\n  }\n\n  // Place the error message in the error object and return\n  einfo.setErrInfo(ecode, buffer);\n  return SFS_ERROR;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/layout/Layout.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Layout.hh\n//! @author Andreas-Joachim Peters - CERN\n//! @brief Abstraction of the physical layout of a file\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_LAYOUT_HH__\n#define __EOSFST_LAYOUT_HH__\n\n#include <sys/types.h>\n#include \"common/LayoutId.hh\"\n#include \"common/Logging.hh\"\n#include \"fst/Namespace.hh\"\n#include \"fst/io/FileIoPlugin.hh\"\n#include \"fst/io/FileIo.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdSfs/XrdSfsInterface.hh>\n#include <XrdCl/XrdClXRootDResponses.hh>\n\nEOSFSTNAMESPACE_BEGIN\n\n//! Forward declaration\nclass XrdFstOfsFile;\n\n//------------------------------------------------------------------------------\n//! Class which abstracts the physical layout of the file\n//------------------------------------------------------------------------------\nclass Layout : public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param file file handler\n  //! @param fmdHandler file meta data handler\n  //----------------------------------------------------------------------------\n  Layout(XrdFstOfsFile* file, eos::fst::FmdHandler* fmdHandler);\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param file handler to current file\n  //! @param lid layout id\n  //! @param client security information\n  //! @param outError error information\n  //! @param path local path\n  //! @param fmdHandler file meta data handler\n  //! @param timeout timeout value\n  //----------------------------------------------------------------------------\n  Layout(XrdFstOfsFile* file,\n         unsigned long lid,\n         const XrdSecEntity* client,\n         XrdOucErrInfo* outError,\n         const char* path,\n         eos::fst::FmdHandler* fmdHandler,\n         uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~Layout() = default;\n\n  //----------------------------------------------------------------------------\n  //! Get the name of the layout\n  //--------------------------------------------------------------------------\n  const char* GetName()\n  {\n    return mName.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get path to the local replica\n  //--------------------------------------------------------------------------\n  const char* GetLocalReplicaPath()\n  {\n    return mLocalPath.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get layout id\n  //--------------------------------------------------------------------------\n  inline unsigned int GetLayoutId()\n  {\n    return mLayoutId;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Get last remote URL (if available)\n  //--------------------------------------------------------------------------\n  const std::string& GetLastUrl()\n  {\n    return mLastUrl;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Get last remote URL (if available)\n  //--------------------------------------------------------------------------\n  const std::string& GetLastTriedUrl()\n  {\n    return mLastTriedUrl;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Get last errCode\n  //--------------------------------------------------------------------------\n  const int& GetLastErrCode()\n  {\n    return mLastErrCode;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Get last errCode\n  //--------------------------------------------------------------------------\n  const int& GetLastErrNo()\n  {\n    return mLastErrNo;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Get error object\n  //--------------------------------------------------------------------------\n  XrdOucErrInfo* GetErrObj() const\n  {\n    return mError;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Test if we are at the entry server\n  //--------------------------------------------------------------------------\n  virtual bool IsEntryServer()\n  {\n    return mIsEntryServer;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Open a file of the current layout type\n  //!\n  //! @param flags open flags\n  //! @param mode open mode\n  //! @param opaque opaque information\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Open(XrdSfsFileOpenMode flags, mode_t mode,\n                   const char* opaque) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Read from file\n  //!\n  //! @param offset offset\n  //! @param buffer place to hold the read data\n  //! @param length length\n  //!\n  //! @return number of bytes read or -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t Read(XrdSfsFileOffset offset,\n                       char* buffer,\n                       XrdSfsXferSize length,\n                       bool readahead = false) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Vector read\n  //!\n  //! @param chunkList list of chunks for the vector read\n  //! @param len total length of the vector read\n  //!\n  //! @return number of bytes read of -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t ReadV(XrdCl::ChunkList& chunkList,\n                        uint32_t len) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Write to file\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //!\n  //! @return number of bytes written or -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t Write(XrdSfsFileOffset offset,\n                        const char* buffer,\n                        XrdSfsXferSize length) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Truncate\n  //!\n  //! @param offset truncate file to this value\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Truncate(XrdSfsFileOffset offset) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Allocate file space\n  //!\n  //! @param length space to be allocated\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  virtual int Fallocate(XrdSfsFileOffset lenght)\n  {\n    return 0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Deallocate file space\n  //!\n  //! @param fromOffset offset start\n  //! @param toOffset offset end\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  virtual int Fdeallocate(XrdSfsFileOffset fromOffset,\n                          XrdSfsFileOffset toOffset)\n  {\n    return 0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Remove file\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  virtual int Remove()\n  {\n    return 0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Sync file to disk\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Sync() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Close file\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Close() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get stats about the file\n  //!\n  //! @param buf stat buffer\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Stat(struct stat* buf) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Execute implementation dependant command\n  //!\n  //! @param cmd command\n  //! @param client client identity\n  //!\n  //! @return 0 if successful, -1 otherwise\n  //----------------------------------------------------------------------------\n  virtual int Fctl(const std::string& cmd, const XrdSecEntity* client) = 0;\n\n  //--------------------------------------------------------------------------\n  //! Get stats about the file\n  //!\n  //! @param buf stat buffer\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //--------------------------------------------------------------------------\n  FileIo* GetFileIo()\n  {\n    return mFileIO.get();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Redirect given path\n  //----------------------------------------------------------------------------\n  virtual void Redirect(const char* path)\n  {\n    mFileIO.reset(FileIoPlugin::GetIoObject(path, mOfsFile, mSecEntity));\n  }\n\n  //----------------------------------------------------------------------------\n  //! Populate error object with information\n  //!\n  //! @param pfx string prefix\n  //! @param einfo error object\n  //! @param ecode error code\n  //! @param op operation type\n  //! @param target free text information\n  //!\n  //! @return SFS_ERROR\n  // ---------------------------------------------------------------------------\n  int Emsg(const char* pfx, XrdOucErrInfo& einfo, int ecode, const char* op,\n           const char* target = \"\");\n\nprotected:\n  std::atomic<bool> mIsEntryServer; ///< Mark entry server\n  unsigned long mLayoutId; ///< layout id\n  XrdOucString mName; ///< layout name\n  std::string mLastUrl; ///< last URL for remote files\n  std::string mLastTriedUrl; ///< last tried URL for remote files\n  int mLastErrCode; ///< last errCode\n  int mLastErrNo; ///< last errno\n  XrdFstOfsFile* mOfsFile; ///< handler to logical file\n  std::string mLocalPath; ///< path to local file\n  XrdOucErrInfo* mError; ///< error information\n  XrdSecEntity* mSecEntity; ///< security information\n  eos::common::LayoutId::eIoType mIoType; ///< type of access ( ofs/xrd )\n  uint16_t mTimeout; ///< timeout value used for all operations on this file\n  XrdSysMutex mExclAccess; ///< mutex to ensure exclusive access\n  std::unique_ptr<FileIo> mFileIO; //< IO object as entry server\n  eos::fst::FmdHandler* mFmdHandler; // <File Metadata Handler\n};\n\nEOSFSTNAMESPACE_END\n\n#endif  // __EOSFST_LAYOUT_HH__\n"
  },
  {
    "path": "fst/layout/LayoutPlugin.cc",
    "content": "//------------------------------------------------------------------------------\n// File: LayoutPlugin.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/layout/LayoutPlugin.hh\"\n#include \"fst/XrdFstOfsFile.hh\"\n#include \"fst/layout/PlainLayout.hh\"\n#include \"fst/layout/ReplicaParLayout.hh\"\n#include \"fst/layout/RaidDpLayout.hh\"\n#include \"fst/layout/ReedSLayout.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Get layout object\n//------------------------------------------------------------------------------\nLayout*\nLayoutPlugin::GetLayoutObject(XrdFstOfsFile* file,\n                              unsigned long layoutId,\n                              const XrdSecEntity* client,\n                              XrdOucErrInfo* error,\n                              const char* path,\n                              eos::fst::FmdHandler* fmdHandler,\n                              uint16_t timeout,\n                              bool storeRecovery,\n                              bool computeStripeChecksum)\n{\n  if (LayoutId::GetLayoutType(layoutId) == LayoutId::kPlain) {\n    return static_cast<Layout*>(new PlainLayout(file, layoutId, client, error, path,\n                                fmdHandler, timeout));\n  }\n\n  if (LayoutId::GetLayoutType(layoutId) == LayoutId::kReplica) {\n    return static_cast<Layout*>(new ReplicaParLayout(file, layoutId, client, error,\n                                path, fmdHandler, timeout));\n  }\n\n  if (LayoutId::GetLayoutType(layoutId) == LayoutId::kRaidDP) {\n    return static_cast<Layout*>(new RaidDpLayout(file, layoutId, client, error,\n                                path, fmdHandler, timeout, storeRecovery, 0, \"oss.size\",\n                                computeStripeChecksum));\n  }\n\n  try {\n    if ((LayoutId::GetLayoutType(layoutId) == LayoutId::kRaid5) ||\n        (LayoutId::GetLayoutType(layoutId) == LayoutId::kRaid6) ||\n        (LayoutId::GetLayoutType(layoutId) == LayoutId::kArchive) ||\n        (LayoutId::GetLayoutType(layoutId) == LayoutId::kQrain)) {\n      return static_cast<Layout*>(new ReedSLayout(file, layoutId, client, error, path,\n                                                  fmdHandler, timeout, storeRecovery, 0,\n                                                  \"oss.size\", computeStripeChecksum));\n    }\n  } catch (const std::runtime_error& e) {\n    eos_static_err(\"msg=\\\"%s\\\"\", e.what());\n    return nullptr;\n  }\n\n  return nullptr;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/layout/LayoutPlugin.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file LayoutPlugin.hh\n//! @author Andreas-Joachim Peters - CERN\n//! @brief Class generating a layout plugin object\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/LayoutId.hh\"\n#include \"fst/Namespace.hh\"\n#include \"fst/filemd/FmdHandler.hh\"\n#include <XrdSec/XrdSecEntity.hh>\n\nEOSFSTNAMESPACE_BEGIN\n\nclass XrdFstOfsFile;\nclass Layout;\n\nusing eos::common::LayoutId;\n\n//------------------------------------------------------------------------------\n//! Class used to obtain a layout plugin object\n//------------------------------------------------------------------------------\nclass LayoutPlugin\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  LayoutPlugin() = default;\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~LayoutPlugin() = default;\n\n  //----------------------------------------------------------------------------\n  //! Get layout object\n  //!\n  //! @param file file handler\n  //! @param layoutId layout id type\n  //! @param client security entity\n  //! @param error error information\n  //! @param accessType access type ( ofs/xrd )\n  //! @param timeout timeout value\n  //! @param storeRecovery store recovered blocks\n  //!\n  //! @return requested layout type object\n  //!\n  //----------------------------------------------------------------------------\n  static Layout* GetLayoutObject(XrdFstOfsFile* file,\n                                 unsigned long layoutId,\n                                 const XrdSecEntity* client,\n                                 XrdOucErrInfo* error,\n                                 const char* path,\n                                 eos::fst::FmdHandler* fmdHandler,\n                                 uint16_t timeout = 0,\n                                 bool storeRecovery = false,\n                                 bool computeStripeChecksum = false);\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/layout/PlainLayout.cc",
    "content": "//------------------------------------------------------------------------------\n// File: PlainLayout.cc\n// Author: Elvin-Alin Sindrilaru / Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/layout/PlainLayout.hh\"\n#include \"fst/io/FileIoPlugin.hh\"\n#include \"fst/io/AsyncMetaHandler.hh\"\n#include \"fst/XrdFstOfsFile.hh\"\n#include \"fst/io/xrd/XrdIo.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nPlainLayout::PlainLayout(XrdFstOfsFile* file,\n                         unsigned long lid,\n                         const XrdSecEntity* client,\n                         XrdOucErrInfo* outError,\n                         const char* path,\n                         eos::fst::FmdHandler* fmdHandler,\n                         uint16_t timeout) :\n  Layout(file, lid, client, outError, path, fmdHandler, timeout),\n  mFileSize(0), mDisableRdAhead(false), mFlags(0)\n{\n  mIsEntryServer = true;\n}\n\n//------------------------------------------------------------------------------\n// Redirect to a new target\n//------------------------------------------------------------------------------\nvoid PlainLayout::Redirect(const char* path)\n{\n  mFileIO.reset(FileIoPlugin::GetIoObject(path, mOfsFile, mSecEntity));\n  mLocalPath = path;\n}\n\n//------------------------------------------------------------------------------\n// Open File\n//------------------------------------------------------------------------------\nint\nPlainLayout::Open(XrdSfsFileOpenMode flags, mode_t mode, const char* opaque)\n{\n  int retc = mFileIO->fileOpen(flags, mode, opaque, mTimeout);\n  mLastUrl = mFileIO->GetLastUrl();\n  mLastTriedUrl = mFileIO->GetLastTriedUrl();\n  mFlags = flags;\n  mLastErrCode = mFileIO->GetLastErrCode();\n  mLastErrNo = mFileIO->GetLastErrNo();\n\n  // If open for read succeeded then get initial file size\n  if (!retc && !(mFlags & (SFS_O_CREAT | SFS_O_TRUNC))) {\n    struct stat st_info;\n    int retc_stat = mFileIO->fileStat(&st_info);\n\n    if (retc_stat) {\n      eos_err(\"failed stat for file=%s\", mLocalPath.c_str());\n      return SFS_ERROR;\n    }\n\n    mFileSize = st_info.st_size;\n  }\n\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Read from file\n//------------------------------------------------------------------------------\nint64_t\nPlainLayout::Read(XrdSfsFileOffset offset, char* buffer,\n                  XrdSfsXferSize length, bool readahead)\n{\n  if (readahead && !mDisableRdAhead) {\n    if (mIoType == eos::common::LayoutId::eIoType::kXrdCl) {\n      if ((uint64_t)(offset + length) > mFileSize) {\n        length = mFileSize - offset;\n      }\n\n      if (length < 0) {\n        length = 0;\n      }\n\n      eos_static_info(\"read offset=%llu length=%lu\", offset, length);\n      int64_t nread = mFileIO->fileReadPrefetch(offset, buffer, length);\n      // Wait for any async requests\n      AsyncMetaHandler* ptr_handler = static_cast<AsyncMetaHandler*>\n                                      (mFileIO->fileGetAsyncHandler());\n\n      if (ptr_handler) {\n        uint16_t error_type = ptr_handler->WaitOK();\n\n        if (error_type != XrdCl::errNone) {\n          return SFS_ERROR;\n        }\n      }\n\n      if ((nread + offset) > (off_t)mFileSize) {\n        mFileSize = nread + offset;\n      }\n\n      if ((nread != length) && ((nread + offset) < (int64_t)mFileSize)) {\n        mFileSize = nread + offset;\n      }\n\n      return nread;\n    }\n  }\n\n  return mFileIO->fileRead(offset, buffer, length, mTimeout);\n}\n\n//------------------------------------------------------------------------------\n// Vector read\n//------------------------------------------------------------------------------\nint64_t\nPlainLayout::ReadV(XrdCl::ChunkList& chunkList, uint32_t len)\n{\n  return mFileIO->fileReadV(chunkList);\n}\n\n\n//------------------------------------------------------------------------------\n// Write to file\n//------------------------------------------------------------------------------\nint64_t\nPlainLayout::Write(XrdSfsFileOffset offset, const char* buffer,\n                   XrdSfsXferSize length)\n{\n  mDisableRdAhead = true;\n\n  if ((uint64_t)(offset + length) > mFileSize) {\n    mFileSize = offset + length;\n  }\n\n  return mFileIO->fileWriteAsync(offset, buffer, length, mTimeout);\n}\n\n//------------------------------------------------------------------------------\n// Truncate file\n//------------------------------------------------------------------------------\nint\nPlainLayout::Truncate(XrdSfsFileOffset offset)\n{\n  mFileSize = offset;\n  return mFileIO->fileTruncate(offset, mTimeout);\n}\n\n//------------------------------------------------------------------------------\n// Reserve space for file\n//------------------------------------------------------------------------------\nint\nPlainLayout::Fallocate(XrdSfsFileOffset length)\n{\n  return mFileIO->fileFallocate(length);\n}\n\n//------------------------------------------------------------------------------\n// Deallocate reserved space\n//------------------------------------------------------------------------------\nint\nPlainLayout::Fdeallocate(XrdSfsFileOffset fromOffset, XrdSfsFileOffset toOffset)\n{\n  return mFileIO->fileFdeallocate(fromOffset, toOffset);\n}\n\n//------------------------------------------------------------------------------\n// Sync file to disk\n//------------------------------------------------------------------------------\n\nint\nPlainLayout::Sync()\n{\n  return mFileIO->fileSync(mTimeout);\n}\n\n//------------------------------------------------------------------------------\n// Get stats for file\n//------------------------------------------------------------------------------\n\nint\nPlainLayout::Stat(struct stat* buf)\n{\n  return mFileIO->fileStat(buf, mTimeout);\n}\n\n//------------------------------------------------------------------------------\n// Close file\n//------------------------------------------------------------------------------\nint\nPlainLayout::Close()\n{\n  return mFileIO->fileClose(mTimeout);\n}\n\n//------------------------------------------------------------------------------\n// Remove file\n//------------------------------------------------------------------------------\nint\nPlainLayout::Remove()\n{\n  return mFileIO->fileRemove();\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/layout/PlainLayout.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file PlainLayout.hh\n//! @author Elvin-Alin Sindrilaru / Andreas-Joachim Peters - CERN\n//! @brief Layout of a plain file without any replication or striping\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_PLAINLAYOUT_HH__\n#define __EOSFST_PLAINLAYOUT_HH__\n\n#include \"fst/layout/Layout.hh\"\n#include <XrdCl/XrdClXRootDResponses.hh>\n#include <pthread.h>\n\nEOSFSTNAMESPACE_BEGIN\n\n//! Forward declaration\nclass PlainLayout;\n\n//------------------------------------------------------------------------------\n//! Class abstracting the physical layout of a plain file\n//------------------------------------------------------------------------------\nclass PlainLayout : public Layout\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param file file handler\n  //! @param lid layout id\n  //! @param client security information\n  //! @param error error information\n  //! @param io io access type ( ofs/xrd )\n  //! @param timeout timeout value\n  //!\n  //----------------------------------------------------------------------------\n  PlainLayout(XrdFstOfsFile* file, unsigned long lid, const XrdSecEntity* client,\n              XrdOucErrInfo* outError, const char* path, eos::fst::FmdHandler* fmdHandler,\n              uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~PlainLayout() = default;\n\n  // -------------------------------------------------------------------------\n  // Redirect to new target\n  // -------------------------------------------------------------------------\n  virtual void Redirect(const char* path);\n\n  //--------------------------------------------------------------------------\n  //! Open file\n  //!\n  //! @param flags open flags\n  //! @param mode open mode\n  //! @param opaque opaque information\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Open(XrdSfsFileOpenMode flags, mode_t mode,\n                   const char* opaque = \"\");\n\n  //--------------------------------------------------------------------------\n  //! Read from file\n  //!\n  //! @param offset offset\n  //! @param buffer place to hold the read data\n  //! @param length length\n  //! @param readahead readahead switch\n  //!\n  //! @return number of bytes read or -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t Read(XrdSfsFileOffset offset,\n                       char* buffer,\n                       XrdSfsXferSize length,\n                       bool readahead = false);\n\n  //----------------------------------------------------------------------------\n  //! Vector read\n  //!\n  //! @param chunkList list of chunks for the vector read\n  //! @param len total length of the vector read\n  //!\n  //! @return number of bytes read of -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t ReadV(XrdCl::ChunkList& chunkList, uint32_t len);\n\n  //----------------------------------------------------------------------------\n  //! Write to file\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //!\n  //! @return number of bytes written or -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t Write(XrdSfsFileOffset offset,\n                        const char* buffer,\n                        XrdSfsXferSize length);\n\n  //----------------------------------------------------------------------------\n  //! Truncate\n  //!\n  //! @param offset truncate file to this value\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Truncate(XrdSfsFileOffset offset);\n\n\n  //----------------------------------------------------------------------------\n  //! Allocate file space\n  //!\n  //! @param length space to be allocated\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Fallocate(XrdSfsFileOffset length);\n\n\n  //----------------------------------------------------------------------------\n  //! Deallocate file space\n  //!\n  //! @param fromOffset offset start\n  //! @param toOffset offset end\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Fdeallocate(XrdSfsFileOffset fromOffset,\n                          XrdSfsFileOffset toOffset);\n\n\n  //----------------------------------------------------------------------------\n  //! Remove file\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Remove();\n\n\n  //----------------------------------------------------------------------------\n  //! Sync file to disk\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Sync();\n\n\n  //----------------------------------------------------------------------------\n  //! Get stats about the file\n  //!\n  //! @param buf stat buffer\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Stat(struct stat* buf);\n\n  //----------------------------------------------------------------------------\n  //! Execute implementation dependant command\n  //!\n  //! @param cmd command\n  //! @param client client identity\n  //!\n  //! @return 0 (SFS_OK) if successful, -1 (SFS_ERROR) otherwise\n  //----------------------------------------------------------------------------\n  virtual int Fctl(const std::string& cmd, const XrdSecEntity* client)\n  {\n    return mFileIO->fileFctl(cmd);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Close file\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Close();\n\nprivate:\n\n  uint64_t mFileSize; ///< file size\n  bool mDisableRdAhead; ///< if any write operations is done, disable rdahead\n  XrdSfsFileOpenMode mFlags; ///< Open flags\n\n  //----------------------------------------------------------------------------\n  //! Disable copy constructor\n  //----------------------------------------------------------------------------\n  PlainLayout(const PlainLayout&) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Disable assign operator\n  //----------------------------------------------------------------------------\n  PlainLayout& operator = (const PlainLayout&) = delete;\n};\n\nEOSFSTNAMESPACE_END\n\n#endif  // __EOSFST_PLAINLAYOUT_HH__\n"
  },
  {
    "path": "fst/layout/RaidDpLayout.cc",
    "content": "//------------------------------------------------------------------------------\n// File: RaidDpLayout.cc\n// Author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/layout/RaidDpLayout.hh\"\n#include \"fst/io/AsyncMetaHandler.hh\"\n#include <cmath>\n#include <map>\n#include <sys/types.h>\n\nEOSFSTNAMESPACE_BEGIN\n\ntypedef long v2do __attribute__((vector_size(VECTOR_SIZE)));\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nRaidDpLayout::RaidDpLayout(XrdFstOfsFile* file,\n                           unsigned long lid,\n                           const XrdSecEntity* client,\n                           XrdOucErrInfo* outError,\n                           const char* path,\n                           eos::fst::FmdHandler* fmdHandler,\n                           uint16_t timeout,\n                           bool storeRecovery,\n                           off_t targetSize,\n                           std::string bookingOpaque,\n                           bool computeStripeChecksum) :\n  RainMetaLayout(file, lid, client, outError, path, timeout,\n                 storeRecovery, targetSize, bookingOpaque, fmdHandler, computeStripeChecksum)\n{\n  mNbDataBlocks = static_cast<int>(pow((double) mNbDataFiles, 2));\n  mNbTotalBlocks = mNbDataBlocks + 2 * mNbDataFiles;\n  mSizeGroup = mNbDataBlocks * mStripeWidth;\n  mSizeLine = mNbDataFiles * mStripeWidth;\n}\n\n//------------------------------------------------------------------------------\n// Compute simple and double parity blocks\n//------------------------------------------------------------------------------\nbool\nRaidDpLayout::ComputeParity(std::shared_ptr<eos::fst::RainGroup>& grp)\n{\n  eos::fst::RainGroup& data_blocks = *grp.get();\n\n  // Compute simple parity\n  for (unsigned int i = 0; i < mNbDataFiles; i++) {\n    int index_pblock = (i + 1) * mNbDataFiles + 2 * i;\n    int current_block = i * (mNbDataFiles + 2); //beginning of current line\n    OperationXOR(data_blocks[current_block](),\n                 data_blocks[current_block + 1](),\n                 data_blocks[index_pblock](),\n                 mStripeWidth);\n    current_block += 2;\n\n    while (current_block < index_pblock) {\n      OperationXOR(data_blocks[index_pblock](),\n                   data_blocks[current_block](),\n                   data_blocks[index_pblock](),\n                   mStripeWidth);\n      current_block++;\n    }\n  }\n\n  // Compute double parity\n  unsigned int jump_blocks = mNbTotalFiles + 1;\n  std::vector<int> used_blocks;\n\n  for (unsigned int i = 0; i < mNbDataFiles; i++) {\n    unsigned int index_dpblock = (i + 1) * (mNbDataFiles + 1) + i;\n    used_blocks.push_back(index_dpblock);\n  }\n\n  for (unsigned int i = 0; i < mNbDataFiles; i++) {\n    unsigned int index_dpblock = (i + 1) * (mNbDataFiles + 1) + i;\n    unsigned int next_block = i + jump_blocks;\n    OperationXOR(data_blocks[i](),\n                 data_blocks[next_block](),\n                 data_blocks[index_dpblock](),\n                 mStripeWidth);\n    used_blocks.push_back(i);\n    used_blocks.push_back(next_block);\n\n    for (unsigned int j = 0; j < mNbDataFiles - 2; j++) {\n      unsigned int aux_block = next_block + jump_blocks;\n\n      if ((aux_block < mNbTotalBlocks) &&\n          (std::find(used_blocks.begin(), used_blocks.end(),\n                     aux_block) == used_blocks.end())) {\n        next_block = aux_block;\n      } else {\n        next_block++;\n\n        while (std::find(used_blocks.begin(), used_blocks.end(),\n                         next_block) != used_blocks.end()) {\n          next_block++;\n        }\n      }\n\n      OperationXOR(data_blocks[index_dpblock](),\n                   data_blocks[next_block](),\n                   data_blocks[index_dpblock](),\n                   mStripeWidth);\n      used_blocks.push_back(next_block);\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// XOR the two blocks using 128 bits and return the result\n//------------------------------------------------------------------------------\nvoid\nRaidDpLayout::OperationXOR(char* pBlock1, char* pBlock2, char* pResult,\n                           size_t totalBytes)\n{\n  v2do* xor_res;\n  v2do* idx1;\n  v2do* idx2;\n  char* byte_res;\n  char* byte_idx1;\n  char* byte_idx2;\n  long int noPices = -1;\n  idx1 = (v2do*) pBlock1;\n  idx2 = (v2do*) pBlock2;\n  xor_res = (v2do*) pResult;\n  noPices = totalBytes / sizeof(v2do);\n\n  for (unsigned int i = 0; i < noPices; idx1++, idx2++, xor_res++, i++) {\n    *xor_res = *idx1 ^ *idx2;\n  }\n\n  // If the block does not devide perfectly to 128 ...\n  if (totalBytes % sizeof(v2do) != 0) {\n    byte_res = (char*) xor_res;\n    byte_idx1 = (char*) idx1;\n    byte_idx2 = (char*) idx2;\n\n    for (unsigned int i = noPices * sizeof(v2do);\n         i < totalBytes;\n         byte_res++, byte_idx1++, byte_idx2++, i++) {\n      *byte_res = *byte_idx1 ^ *byte_idx2;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Use simple and double parity to recover corrupted pieces in the curerent\n// group, all errors in the map belong to the same group\n//------------------------------------------------------------------------------\nbool\nRaidDpLayout::RecoverPiecesInGroup(XrdCl::ChunkList& grp_errs)\n{\n  int64_t nread = 0;\n  bool ret = true;\n  uint64_t offset_local;\n  unsigned int stripe_id;\n  unsigned int physical_id;\n  std::set<int> corrupt_ids;\n  std::set<int> exclude_ids;\n  uint64_t offset = grp_errs.begin()->offset;\n  uint64_t offset_group = (offset / mSizeGroup) * mSizeGroup;\n  AsyncMetaHandler* phandler = 0;\n  XrdCl::ChunkList found_errs;\n  std::vector<unsigned int> simple_parity = GetSimpleParityIndices();\n  std::vector<unsigned int> double_parity = GetDoubleParityIndices();\n  std::vector<bool> status_blocks(mNbTotalBlocks, false);\n  std::shared_ptr<eos::fst::RainGroup> grp = GetGroup(offset_group);\n  eos::fst::RainGroup& data_blocks = *grp.get();\n\n  // Reset all the async handlers\n  for (unsigned int i = 0; i < mStripe.size(); i++) {\n    if (mStripe[i]) {\n      phandler  = static_cast<AsyncMetaHandler*>(mStripe[i]->fileGetAsyncHandler());\n\n      if (phandler) {\n        phandler->Reset();\n      }\n    }\n  }\n\n  // Read the current group of blocks\n  for (unsigned int i = 0; i < mNbTotalBlocks; i++) {\n    status_blocks[i] = true;\n    stripe_id = i % mNbTotalFiles;\n    physical_id = mapLP[stripe_id];\n    offset_local = (offset_group / mSizeLine) * mStripeWidth +\n                   ((i / mNbTotalFiles) * mStripeWidth);\n    offset_local += mSizeHeader;\n\n    // Read data from stripe\n    if (mStripe[physical_id]) {\n      // Enable readahead\n      nread = mStripe[physical_id]->fileReadPrefetch(offset_local, data_blocks[i](),\n              mStripeWidth, mTimeout);\n\n      if (nread != (int64_t)mStripeWidth) {\n        status_blocks[i] = false;\n        corrupt_ids.insert(i);\n      }\n    } else {\n      status_blocks[i] = false;\n      corrupt_ids.insert(i);\n    }\n  }\n\n  // Mark the corrupted blocks\n  for (unsigned int i = 0; i < mStripe.size(); i++) {\n    if (mStripe[i]) {\n      phandler  = static_cast<AsyncMetaHandler*>(mStripe[i]->fileGetAsyncHandler());\n\n      if (phandler) {\n        uint16_t error_type = phandler->WaitOK();\n\n        if (error_type != XrdCl::errNone) {\n          // Get type of error and the errors map\n          found_errs = phandler->GetErrors();\n\n          for (auto chunk = found_errs.begin(); chunk != found_errs.end(); chunk++) {\n            offset_local = chunk->offset - mSizeHeader;\n            int line = ((offset_local % mSizeLine) / mStripeWidth);\n            int index = line * mNbTotalFiles + mapPL[i];\n            status_blocks[index] = false;\n            corrupt_ids.insert(index);\n          }\n\n          found_errs.clear();\n\n          // If timeout error, then disable current file\n          if (error_type == XrdCl::errOperationExpired) {\n            mStripe[i]->fileClose(mTimeout);\n            mStripe[i] = nullptr;\n          }\n        }\n\n        if (mStripe[i]) {\n          phandler->Reset();\n        }\n      }\n    }\n  }\n\n  if (corrupt_ids.empty()) {\n    eos_warning(\"%s\", \"msg=\\\"no corrupted blocks, although we saw some before\\\"\");\n    RecycleGroup(grp);\n    return true;\n  }\n\n  // Recovery algorithm\n  int64_t nwrite;\n  unsigned int id_corrupted;\n  std::vector<unsigned int> horizontal_stripe;\n  std::vector<unsigned int> diagonal_stripe;\n\n  while (!corrupt_ids.empty()) {\n    auto iter = corrupt_ids.begin();\n    id_corrupted = *iter;\n    corrupt_ids.erase(iter);\n\n    if (ValidHorizStripe(horizontal_stripe, status_blocks, id_corrupted)) {\n      data_blocks[id_corrupted].FillWithZeros(true);\n\n      for (unsigned int ind = 0; ind < horizontal_stripe.size(); ind++) {\n        if (horizontal_stripe[ind] != id_corrupted) {\n          OperationXOR(data_blocks[id_corrupted](),\n                       data_blocks[horizontal_stripe[ind]](),\n                       data_blocks[id_corrupted](),\n                       mStripeWidth);\n        }\n      }\n\n      // Return recovered block and also write it to the file\n      stripe_id = id_corrupted % mNbTotalFiles;\n      physical_id = mapLP[stripe_id];\n      offset_local = ((offset_group / mSizeLine) * mStripeWidth) +\n                     ((id_corrupted / mNbTotalFiles) * mStripeWidth);\n      offset_local += mSizeHeader;\n\n      if ((mForceRecovery || mStoreRecoveryRW) && mStripe[physical_id]) {\n        nwrite = mStripe[physical_id]->fileWriteAsync(offset_local,\n                 data_blocks[id_corrupted](),\n                 mStripeWidth,\n                 mTimeout);\n\n        if (nwrite != (int64_t)mStripeWidth) {\n          eos_err(\"msg=\\\"failed write operation\\\" offset=%llu stripe_id=%i\",\n                  offset_local, stripe_id);\n          ret = false;\n        }\n\n        // Add the data contained into the buffer to compute the\n        // unit checksum, skipping the header\n        AddDataToStripeChecksum(data_blocks[stripe_id](), nwrite, offset_local);\n        mStripeSize = offset_local + nwrite < mStripeSize ? mStripeSize : offset_local +\n                      nwrite;\n      }\n\n      // Return corrected information to the buffer\n      for (auto chunk = grp_errs.begin(); chunk != grp_errs.end(); chunk++) {\n        offset = chunk->offset;\n\n        // If not SP or DP, maybe we have to return it\n        if (std::find(simple_parity.begin(), simple_parity.end(),\n                      id_corrupted) == simple_parity.end() &&\n            std::find(double_parity.begin(), double_parity.end(),\n                      id_corrupted) == double_parity.end()) {\n          if ((offset >= (offset_group + MapBigToSmall(id_corrupted) * mStripeWidth)) &&\n              (offset < (offset_group + (MapBigToSmall(id_corrupted) + 1) * mStripeWidth))) {\n            chunk->buffer = static_cast<char*>\n                            (memcpy(chunk->buffer, data_blocks[id_corrupted]() + (offset % mStripeWidth),\n                                    chunk->length));\n          }\n        }\n      }\n\n      // Copy the unrecoverd blocks back in the queue\n      if (!exclude_ids.empty()) {\n        corrupt_ids.insert(exclude_ids.begin(), exclude_ids.end());\n        exclude_ids.clear();\n      }\n\n      status_blocks[id_corrupted] = true;\n    } else {\n      // Try to recover using double parity\n      if (ValidDiagStripe(diagonal_stripe, status_blocks, id_corrupted)) {\n        data_blocks[id_corrupted].FillWithZeros(true);\n\n        for (unsigned int ind = 0; ind < diagonal_stripe.size(); ind++) {\n          if (diagonal_stripe[ind] != id_corrupted) {\n            OperationXOR(data_blocks[id_corrupted](),\n                         data_blocks[diagonal_stripe[ind]](),\n                         data_blocks[id_corrupted](),\n                         mStripeWidth);\n          }\n        }\n\n        // Return recovered block and also write them to the files\n        stripe_id = id_corrupted % mNbTotalFiles;\n        physical_id = mapLP[stripe_id];\n        offset_local = ((offset_group / mSizeLine) * mStripeWidth) +\n                       ((id_corrupted / mNbTotalFiles) * mStripeWidth);\n        offset_local += mSizeHeader;\n\n        if ((mForceRecovery || mStoreRecoveryRW) && mStripe[physical_id]) {\n          nwrite = mStripe[physical_id]->fileWriteAsync(offset_local,\n                   data_blocks[id_corrupted](),\n                   mStripeWidth,\n                   mTimeout);\n\n          if (nwrite != (int64_t)mStripeWidth) {\n            eos_err(\"msg=\\\"failed write operation\\\" offset=%llu stripe=%u\",\n                    offset_local, stripe_id);\n            ret = false;\n          }\n        }\n\n        // Return corrected information to the buffer\n        for (auto chunk = grp_errs.begin(); chunk != grp_errs.end(); chunk++) {\n          offset = chunk->offset;\n\n          // If not SP or DP, maybe we have to return it\n          if (std::find(simple_parity.begin(), simple_parity.end(),\n                        id_corrupted) == simple_parity.end() &&\n              std::find(double_parity.begin(), double_parity.end(),\n                        id_corrupted) == double_parity.end()) {\n            if ((offset >= (offset_group + MapBigToSmall(id_corrupted) * mStripeWidth)) &&\n                (offset < (offset_group + (MapBigToSmall(id_corrupted) + 1) * mStripeWidth))) {\n              chunk->buffer = static_cast<char*>\n                              (memcpy(chunk->buffer, data_blocks[id_corrupted]() + (offset % mStripeWidth),\n                                      chunk->length));\n            }\n          }\n        }\n\n        // Copy the unrecoverd blocks back in the queue\n        if (!exclude_ids.empty()) {\n          corrupt_ids.insert(exclude_ids.begin(), exclude_ids.end());\n          exclude_ids.clear();\n        }\n\n        status_blocks[id_corrupted] = true;\n      } else {\n        // Current block can not be recoverd in this configuration\n        exclude_ids.insert(id_corrupted);\n      }\n    }\n  }\n\n  // Wait for write responses and reset all handlers\n  for (unsigned int i = 0; i < mStripe.size(); i++) {\n    if ((mForceRecovery || mStoreRecoveryRW) && mStripe[i]) {\n      phandler = static_cast<AsyncMetaHandler*>(mStripe[i]->fileGetAsyncHandler());\n\n      if (phandler) {\n        uint16_t error_type = phandler->WaitOK();\n\n        if (error_type != XrdCl::errNone) {\n          eos_err(\"failed write on stripe %u\", i);\n          ret = false;\n\n          if (error_type == XrdCl::errOperationExpired) {\n            mStripe[i]->fileClose(mTimeout);\n            mStripe[i] = nullptr;\n          }\n        }\n      }\n    }\n  }\n\n  if (corrupt_ids.empty() && !exclude_ids.empty()) {\n    eos_err(\"msg=\\\"exclude ids not empty\\\" size=%d\", exclude_ids.size());\n    ret = false;\n  }\n\n  RecycleGroup(grp);\n  return ret;\n}\n\n//------------------------------------------------------------------------------\n// Write the parity blocks from mDataBlocks to the corresponding file stripes\n//------------------------------------------------------------------------------\nint\nRaidDpLayout::WriteParityToFiles(std::shared_ptr<eos::fst::RainGroup>& grp)\n{\n  uint64_t off_parity_local;\n  unsigned int physical_pindex = mapLP[mNbTotalFiles - 2];\n  unsigned int physical_dpindex = mapLP[mNbTotalFiles - 1];\n\n  if (!mStripe[physical_pindex] || !mStripe[physical_dpindex]) {\n    eos_static_err(\"%s\", \"msg=\\\"file not opened for simple parity write\\\"\");\n    return SFS_ERROR;\n  }\n\n  eos::fst::RainGroup& data_blocks = *grp.get();\n  uint64_t grp_off = grp->GetGroupOffset();\n\n  for (unsigned int i = 0; i < mNbDataFiles; i++) {\n    unsigned int index_pblock = (i + 1) * mNbDataFiles + 2 * i;\n    unsigned int index_dpblock = (i + 1) * (mNbDataFiles + 1) + i;\n    off_parity_local = (grp_off / mNbDataFiles) + (i * mStripeWidth);\n    off_parity_local += mSizeHeader;\n    // Writing simple and double parity\n    grp->StoreFuture(mStripe[physical_pindex]->\n                     fileWriteAsync(data_blocks[index_pblock](),\n                                    off_parity_local, mStripeWidth));\n    grp->StoreFuture(mStripe[physical_dpindex]\n                     ->fileWriteAsync(data_blocks[index_dpblock](),\n                                      off_parity_local, mStripeWidth));\n  }\n\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Return the indices of the simple parity blocks from a group\n//------------------------------------------------------------------------------\nstd::vector<unsigned int>\nRaidDpLayout::GetSimpleParityIndices()\n{\n  unsigned int val = mNbDataFiles;\n  std::vector<unsigned int> values;\n  values.push_back(val);\n  val++;\n\n  for (unsigned int i = 1; i < mNbDataFiles; i++) {\n    val += (mNbDataFiles + 1);\n    values.push_back(val);\n    val++;\n  }\n\n  return values;\n}\n\n//------------------------------------------------------------------------------\n// Return the indices of the double parity blocks from a group\n//------------------------------------------------------------------------------\nstd::vector<unsigned int>\nRaidDpLayout::GetDoubleParityIndices()\n{\n  unsigned int val = mNbDataFiles;\n  std::vector<unsigned int> values;\n  val++;\n  values.push_back(val);\n\n  for (unsigned int i = 1; i < mNbDataFiles; i++) {\n    val += (mNbDataFiles + 1);\n    val++;\n    values.push_back(val);\n  }\n\n  return values;\n}\n\n//------------------------------------------------------------------------------\n// Check if the diagonal stripe is valid in the sense that there is at most one\n// corrupted block in the current stripe and this is not the ommited diagonal\n//------------------------------------------------------------------------------\nbool\nRaidDpLayout::ValidDiagStripe(std::vector<unsigned int>& rStripes,\n                              const std::vector<bool>& pStatusBlocks,\n                              unsigned int blockId)\n{\n  int corrupted = 0;\n  rStripes.clear();\n  rStripes = GetDiagonalStripe(blockId);\n\n  if (rStripes.size() == 0) {\n    return false;\n  }\n\n  // The ommited diagonal contains the block with index mNbDataFilesBlocks\n  if (std::find(rStripes.begin(), rStripes.end(),\n                mNbDataFiles) != rStripes.end()) {\n    return false;\n  }\n\n  for (auto iter = rStripes.begin(); iter != rStripes.end(); ++iter) {\n    if (pStatusBlocks[*iter] == false) {\n      corrupted++;\n    }\n\n    if (corrupted >= 2) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Check if the HORIZONTAL stripe is valid in the sense that there is at\n// most one corrupted block in the current stripe\n//------------------------------------------------------------------------------\nbool\nRaidDpLayout::ValidHorizStripe(std::vector<unsigned int>& rStripes,\n                               const std::vector<bool>& pStatusBlock,\n                               unsigned int blockId)\n{\n  int corrupted = 0;\n  long int base_id = (blockId / mNbTotalFiles) * mNbTotalFiles;\n  rStripes.clear();\n\n  // If double parity block then no horizontal stripes\n  if (blockId == (base_id + mNbDataFiles + 1)) {\n    return false;\n  }\n\n  for (unsigned int i = 0; i < mNbTotalFiles - 1; i++) {\n    rStripes.push_back(base_id + i);\n  }\n\n  // Check if it is valid\n  for (std::vector<unsigned int>::iterator iter = rStripes.begin();\n       iter != rStripes.end();\n       ++iter) {\n    if (pStatusBlock[*iter] == false) {\n      corrupted++;\n    }\n\n    if (corrupted >= 2) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Return the blocks corrsponding to the diagonal stripe of blockId\n//------------------------------------------------------------------------------\nstd::vector<unsigned int>\nRaidDpLayout::GetDiagonalStripe(unsigned int blockId)\n{\n  bool dp_added = false;\n  std::vector<unsigned int> last_column = GetDoubleParityIndices();\n  unsigned int next_block;\n  unsigned int jump_blocks;\n  unsigned int idLastBlock;\n  unsigned int previous_block;\n  std::vector<unsigned int> stripe;\n\n  // If we are on the ommited diagonal, return\n  if (blockId == mNbDataFiles) {\n    stripe.clear();\n    return stripe;\n  }\n\n  stripe.push_back(blockId);\n\n  // If we start with a dp index, then construct the diagonal in a special way\n  if (std::find(last_column.begin(), last_column.end(),\n                blockId) != last_column.end()) {\n    blockId = blockId % (mNbDataFiles + 1);\n    stripe.push_back(blockId);\n    dp_added = true;\n  }\n\n  previous_block = blockId;\n  jump_blocks = mNbDataFiles + 3;\n  idLastBlock = mNbTotalBlocks - 1;\n\n  for (unsigned int i = 0; i < mNbDataFiles - 1; i++) {\n    next_block = previous_block + jump_blocks;\n\n    if (next_block > idLastBlock) {\n      next_block %= idLastBlock;\n\n      if (next_block >= mNbDataFiles + 1) {\n        next_block = (previous_block + jump_blocks) % jump_blocks;\n      }\n    } else if (std::find(last_column.begin(), last_column.end(),\n                         next_block) != last_column.end()) {\n      next_block = previous_block + 2;\n    }\n\n    stripe.push_back(next_block);\n    previous_block = next_block;\n\n    // If on the ommited diagonal return\n    if (next_block == mNbDataFiles) {\n      stripe.clear();\n      return stripe;\n    }\n  }\n\n  // Add the index from the double parity block\n  if (!dp_added) {\n    next_block = GetDParityBlock(stripe);\n    stripe.push_back(next_block);\n  }\n\n  return stripe;\n}\n\n//------------------------------------------------------------------------------\n// Return the id of stripe from a mNbTotalBlocks representation to a mNbDataBlocks\n// representation in which we exclude the parity and double parity blocks\n//------------------------------------------------------------------------------\nunsigned int\nRaidDpLayout::MapBigToSmall(unsigned int idBig)\n{\n  if ((idBig % (mNbDataFiles + 2) == mNbDataFiles) ||\n      (idBig % (mNbDataFiles + 2) == mNbDataFiles + 1)) {\n    return -1;\n  } else\n    return ((idBig / (mNbDataFiles + 2)) * mNbDataFiles +\n            (idBig % (mNbDataFiles + 2)));\n}\n\n//------------------------------------------------------------------------------\n// Return the id of stripe from a mNbDataBlocks representation in a mNbTotalBlocks\n// representation\n//------------------------------------------------------------------------------\nunsigned int\nRaidDpLayout::MapSmallToBig(unsigned int idSmall)\n{\n  if (idSmall >= mNbDataBlocks) {\n    eos_err(\"%s\", \"msg=\\\"idSmall bigger than expected\\\"\");\n    return -1;\n  }\n\n  return (idSmall / mNbDataFiles) * (mNbDataFiles + 2) + idSmall % mNbDataFiles;\n}\n\n//------------------------------------------------------------------------------\n// Return the id (out of mNbTotalBlocks) for the parity block corresponding to\n// the current block\n//------------------------------------------------------------------------------\nunsigned int\nRaidDpLayout::GetSParityBlock(unsigned int elemFromStripe)\n{\n  return (mNbDataFiles + (elemFromStripe / (mNbDataFiles + 2))\n          * (mNbDataFiles + 2));\n}\n\n//------------------------------------------------------------------------------\n// Return the id (out of mNbTotalBlocks) for the double parity block corresponding\n// to the current block\n//------------------------------------------------------------------------------\nunsigned int\nRaidDpLayout::GetDParityBlock(std::vector<unsigned int>& rStripe)\n{\n  int min = *(std::min_element(rStripe.begin(), rStripe.end()));\n  return ((min + 1) * (mNbDataFiles + 1) + min);\n}\n\n//------------------------------------------------------------------------------\n// Allocate file space (reserve)\n//------------------------------------------------------------------------------\nint\nRaidDpLayout::Fallocate(XrdSfsFileOffset length)\n{\n  int64_t size = ceil((1.0 * length) / mSizeGroup) * mSizeLine + mSizeHeader;\n  return mStripe[0]->fileFallocate(size);\n}\n\n//------------------------------------------------------------------------------\n// Deallocate file space\n//------------------------------------------------------------------------------\nint\nRaidDpLayout::Fdeallocate(XrdSfsFileOffset fromOffset,\n                          XrdSfsFileOffset toOffset)\n{\n  int64_t from_size = ceil((1.0 * fromOffset) / mSizeGroup) * mSizeLine +\n                      mSizeHeader;\n  int64_t to_size = ceil((1.0 * toOffset) / mSizeGroup) * mSizeLine + mSizeHeader;\n  return mStripe[0]->fileFdeallocate(from_size, to_size);\n}\n\n//------------------------------------------------------------------------------\n// Convert a global offset (from the inital file) to a local offset within\n// a stripe file. The initial block does *NOT* span multiple chunks (stripes)\n// therefore if the original length is bigger than one chunk the splitting\n// must be done before calling this method.\n//------------------------------------------------------------------------------\nstd::pair<int, uint64_t>\nRaidDpLayout::GetLocalOff(uint64_t global_off)\n{\n  uint64_t local_off = (global_off / mSizeGroup) * mSizeLine +\n                       ((global_off % mSizeGroup) / mSizeLine) * mStripeWidth +\n                       (global_off % mStripeWidth);\n  int stripe_id = (global_off / mStripeWidth) % mNbDataFiles;\n  return std::make_pair(stripe_id, local_off);\n}\n\n//------------------------------------------------------------------------------\n// Convert a local position (from a stripe file) to a global position\n// within the initial file file\n//------------------------------------------------------------------------------\nuint64_t\nRaidDpLayout::GetGlobalOff(int stripe_id, uint64_t local_off)\n{\n  uint64_t global_off = (local_off / mSizeLine) * mSizeGroup +\n                        ((local_off % mSizeLine) / mStripeWidth) * mSizeLine +\n                        (stripe_id * mStripeWidth) + (local_off % mStripeWidth);\n  return global_off;\n}\n\n//------------------------------------------------------------------------------\n// Get truncate offset for stripe\n//------------------------------------------------------------------------------\nuint64_t\nRaidDpLayout::GetStripeTruncateOffset(uint64_t offset)\n{\n  return static_cast<uint64_t>(ceil((offset * 1.0) / mSizeGroup) *\n                               mSizeLine + mSizeHeader);\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/layout/RaidDpLayout.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file RaidDpLayout.hh\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Implementation of the RAID-double parity layout\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n#include \"fst/layout/RainMetaLayout.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//! Used for computing XOR or 128 bits = 8 * 16\n#define VECTOR_SIZE 16\n\n\n//------------------------------------------------------------------------------\n//! Implementation of the RAID-double parity layout\n//------------------------------------------------------------------------------\nclass RaidDpLayout : public RainMetaLayout\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param file handler to current file\n  //! @param lid layout id\n  //! @param client security information\n  //! @param outError error information\n  //! @param io access type\n  //! @param timeout timeout value\n  //! @param storeRecovery if true write back the recovered blocks to file\n  //! @param targetSize expected final size\n  //! @param bookingOpaque opaque information\n  //----------------------------------------------------------------------------\n  RaidDpLayout(XrdFstOfsFile* file,\n               unsigned long lid,\n               const XrdSecEntity* client,\n               XrdOucErrInfo* outError,\n               const char* path,\n               eos::fst::FmdHandler* fmdHandler,\n               uint16_t timeout = 0,\n               bool storeRecovery = false,\n               off_t targetSize = 0,\n               std::string bookingOpaque = \"oss.size\",\n               bool computeStripeChecksum = false);\n\n  //----------------------------------------------------------------------------\n  //! Allocate file space\n  //!\n  //! @param length space to be allocated\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Fallocate(XrdSfsFileOffset lenght);\n\n  //----------------------------------------------------------------------------\n  //! Deallocate file space\n  //!\n  //! @param fromOffset offset start\n  //! @param toOffset offset end\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Fdeallocate(XrdSfsFileOffset fromOffset,\n                          XrdSfsFileOffset toOffset);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~RaidDpLayout() = default;\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Disable copy/move assign/constructor operators\n  //----------------------------------------------------------------------------\n  RaidDpLayout& operator = (const RaidDpLayout&) = delete;\n  RaidDpLayout(const RaidDpLayout&) = delete;\n  RaidDpLayout& operator = (RaidDpLayout&&) = delete;\n  RaidDpLayout(RaidDpLayout&&) = delete;\n\n  //------------------------------------------------------------------------------\n  //! Compute error correction blocks\n  //!\n  //! @param grp group object for parity computation\n  //!\n  //! @return true if parity info computed successfully, otherwise false\n  //------------------------------------------------------------------------------\n  virtual bool ComputeParity(std::shared_ptr<eos::fst::RainGroup>& grp);\n\n  //----------------------------------------------------------------------------\n  //! Write parity information corresponding to a group to files\n  //!\n  //! @param grp group object\n  //!\n  //! @return 0 if successful, otherwise error\n  //----------------------------------------------------------------------------\n  virtual int WriteParityToFiles(std::shared_ptr<eos::fst::RainGroup>& grp);\n\n  //----------------------------------------------------------------------------\n  //! Compute XOR operation for two blocks of any size\n  //!\n  //! @param pBlock1 first input block\n  //! @param pBlock2 second input block\n  //! @param pResult result of XOR operation\n  //! @param totalBytes size of input blocks\n  //----------------------------------------------------------------------------\n  void OperationXOR(char* pBlock1, char* pBlock2, char* pResult,\n                    size_t totalBytes);\n\n  //----------------------------------------------------------------------------\n  //! Recover corrupted chunks from the current group\n  //!\n  //! @param grp_errs chunks to be recovered\n  //!\n  //! @return true if recovery successful, false otherwise\n  //----------------------------------------------------------------------------\n  virtual bool RecoverPiecesInGroup(XrdCl::ChunkList& grp_errs);\n\n  //----------------------------------------------------------------------------\n  //! Return diagonal stripe corresponding to current block\n  //!\n  //! @param blockId block id\n  //!\n  //! @return vector containing the blocks on the diagonal stripe\n  //----------------------------------------------------------------------------\n  std::vector<unsigned int> GetDiagonalStripe(unsigned int blockId);\n\n  //----------------------------------------------------------------------------\n  //! Validate horizontal stripe for a block index\n  //!\n  //! @param rStripes horizontal stripe for current block id\n  //! @param pStatusBlock status of the blocks\n  //! @param blockId current block index\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ValidHorizStripe(std::vector<unsigned int>& rStripes,\n                        const std::vector<bool>& pStatusBlock,\n                        unsigned int blockId);\n\n  //----------------------------------------------------------------------------\n  //! Validate diagonal stripe for a block index\n  //!\n  //! @param rStripes diagonal stripe for current block id\n  //! @param pStatusBlock vector of block's status\n  //! @param blockId current block index\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ValidDiagStripe(std::vector<unsigned int>& rStripes,\n                       const std::vector<bool>& pStatusBlock,\n                       unsigned int blockId);\n\n  //----------------------------------------------------------------------------\n  //! Get indices of the simple parity blocks\n  //!\n  //! @return vecttor containing the values of the simple parity indices\n  //----------------------------------------------------------------------------\n  std::vector<unsigned int> GetSimpleParityIndices();\n\n  //----------------------------------------------------------------------------\n  //! Get indices of the double parity blocks\n  //!\n  //! @return vector containing the values of the double parity indices\n  //----------------------------------------------------------------------------\n  std::vector<unsigned int> GetDoubleParityIndices();\n\n  //----------------------------------------------------------------------------\n  //! Get simple parity block corresponding to current block\n  //!\n  //! @param elemFromStripe any element from the current stripe\n  //!\n  //! @return value of the simple parity index\n  //----------------------------------------------------------------------------\n  unsigned int GetSParityBlock(unsigned int elemFromStripe);\n\n  //----------------------------------------------------------------------------\n  //! Get double parity blocks corresponding to current stripe\n  //!\n  //! @param rStripe elements from the current stripe\n  //!\n  //! @return value of the double parity block\n  //----------------------------------------------------------------------------\n  unsigned int GetDParityBlock(std::vector<unsigned int>& rStripe);\n\n  //----------------------------------------------------------------------------\n  //! Map index from nTotalBlocks representation to nDataBlocks\n  //!\n  //! @param idBig with values between 0 ans 23\n  //!\n  //! @return index with values between 0 and 15, -1 if error\n  //----------------------------------------------------------------------------\n  unsigned int MapBigToSmall(unsigned int idBig);\n\n  //----------------------------------------------------------------------------\n  //! Map index from nDataBlocks representation to nTotalBlocks\n  //!\n  //! @param idSmall with values between 0 and 15\n  //!\n  //! @return index with values between 0 and 23, -1 if error\n  //----------------------------------------------------------------------------\n  unsigned int MapSmallToBig(unsigned int idSmall) override;\n\n  //----------------------------------------------------------------------------\n  //! Convert a global offset (from the inital file) to a local offset within\n  //! a stripe data file. The initial block does *NOT* span multiple chunks\n  //! (stripes) therefore if the original length is bigger than one chunk the\n  //! splitting must be done before calling this method.\n  //!\n  //! @param global_off initial offset\n  //!\n  //! @return tuple made up of the logical index of the stripe data file the\n  //!         piece belongs to and the local offset within that file.\n  //----------------------------------------------------------------------------\n  std::pair<int, uint64_t> GetLocalOff(uint64_t global_off) override;\n\n  //----------------------------------------------------------------------------\n  //! Convert a local position (from a stripe data file) to a global position\n  //! within the initial file file. Note that the local offset has to come\n  //! from a stripe data file since there is no corresponde in the original\n  //! file for a piece which is in the parity stripe.\n  //!\n  //! @param stripe_id logical stripe index\n  //! @param local_off local offset\n  //!\n  //! @return offset in the initial file of the local given piece\n  //----------------------------------------------------------------------------\n  uint64_t GetGlobalOff(int stripe_id, uint64_t local_off) override;\n\n  //----------------------------------------------------------------------------\n  //! Get truncate offset for stripe\n  //!\n  //! @param offset logical file truncate offset\n  //!\n  //! @return local stripe truncate offset\n  //----------------------------------------------------------------------------\n  uint64_t GetStripeTruncateOffset(uint64_t offset) override;\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/layout/RainBlock.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file RainBlock.cc\n//! @author Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/layout/RainBlock.hh\"\n#include \"common/BufferManager.hh\"\n\nnamespace\n{\n// Max 2GB of memory with blocks of at most 64MB each\neos::common::BufferManager gRainBuffMgr(2 * eos::common::GB, 6);\n}\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nRainBlock::RainBlock(uint32_t capacity):\n  mCapacity(capacity), mLastOffset(0ul)\n{\n  mBuffer = gRainBuffMgr.GetBuffer(mCapacity);\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nRainBlock::~RainBlock()\n{\n  gRainBuffMgr.Recycle(mBuffer);\n}\n\n//----------------------------------------------------------------------------\n// Save data in the current block\n//----------------------------------------------------------------------------\nchar*\nRainBlock::Write(const char* buffer, uint64_t offset, uint32_t length)\n{\n  if ((offset >= mCapacity) ||\n      (offset + length > mCapacity)) {\n    eos_static_err(\"msg=\\\"block can not hold so much data\\\" capcity=%llu \"\n                   \"data_off=%llu data_len=%llu\", mCapacity, offset, length);\n    return nullptr;\n  }\n\n  if (offset > mLastOffset) {\n    mHasHoles = true;\n  }\n\n  if (offset + length > mLastOffset) {\n    mLastOffset = offset + length;\n  }\n\n  char* ptr = mBuffer->GetDataPtr();\n  ptr += offset;\n  (void) memcpy(ptr, buffer, length);\n  return ptr;\n}\n\n//----------------------------------------------------------------------------\n// Fill the remaining part of the buffer with zeros and mark it as complete\n//----------------------------------------------------------------------------\nbool\nRainBlock::FillWithZeros(bool force)\n{\n  if (mHasHoles) {\n    return false;\n  }\n\n  uint64_t len = mCapacity;\n  char* ptr = mBuffer->GetDataPtr();\n\n  if (force) {\n    (void) memset(ptr, '\\0', len);\n  } else {\n    if (mLastOffset < mCapacity) {\n      len = mCapacity - mLastOffset;\n      ptr += mLastOffset;\n      (void) memset(ptr, '\\0', len);\n    }\n  }\n\n  mLastOffset = mCapacity;\n  return true;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/layout/RainBlock.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file RainBlock.hh\n//! @author Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"fst/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include \"common/BufferManager.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class RainBlock\n//------------------------------------------------------------------------------\nclass RainBlock: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param capacity maximum size of the current block\n  //----------------------------------------------------------------------------\n  RainBlock(uint32_t capacity);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~RainBlock();\n\n  //----------------------------------------------------------------------------\n  //! Move assignment operator\n  //----------------------------------------------------------------------------\n  RainBlock& operator =(RainBlock&& other) noexcept\n  {\n    if (this != &other) {\n      mCapacity = other.mCapacity;\n      mLastOffset = other.mLastOffset;\n      mHasHoles = other.mHasHoles;\n      mBuffer = other.mBuffer;\n      other.mBuffer = nullptr;\n    }\n\n    return *this;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Move constructor\n  //----------------------------------------------------------------------------\n  RainBlock(RainBlock&& other) noexcept\n  {\n    *this = std::move(other);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Override operator ()\n  //----------------------------------------------------------------------------\n  char* operator()()\n  {\n    return mBuffer->GetDataPtr();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Write data in the current block\n  //!\n  //! @param buffer buffer containing the data\n  //! @param offset offset withing the current block\n  //! @param length lenght of the data\n  //!\n  //! @return pointer to the internal buffer where current price was written\n  //!         or nullptr otherwise\n  //----------------------------------------------------------------------------\n  char* Write(const char* buffer, uint64_t offset, uint32_t lenght);\n\n  //----------------------------------------------------------------------------\n  //! Fill the remaining (unused) part of the buffer with zeros and mark it\n  //! as complete\n  //!\n  //! @param force if true when fill the entire block with \\0\n  //!\n  //! @retrun true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool FillWithZeros(bool force = false);\n\n  //----------------------------------------------------------------------------\n  //! Get pointer to the undelying data\n  //----------------------------------------------------------------------------\n  inline char* GetDataPtr()\n  {\n    return mBuffer->GetDataPtr();\n  }\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  uint32_t mCapacity; ///< Max size of the current block\n  uint32_t mLastOffset; ///< Last written offset\n  uint32_t mLength {0ull}; ///< Length of useful data, relevant if no holes\n  bool mHasHoles {false}; ///< Mark if block contains holes\n  std::shared_ptr<eos::common::Buffer> mBuffer; ///< Actual data buffer\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/layout/RainGroup.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file RainGroup.hh\n//! @author Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/layout/RainGroup.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nRainGroup::RainGroup(uint64_t grp_offset, int size, uint32_t block_sz):\n  mOffset(grp_offset)\n{\n  for (int i = 0; i < size; ++i) {\n    mBlocks.emplace_back(block_sz);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Override [] operator\n//------------------------------------------------------------------------------\neos::fst::RainBlock&\nRainGroup::operator [](unsigned int i)\n{\n  return mBlocks[i];\n}\n\n//------------------------------------------------------------------------------\n// Fill the blocks with zeros if they are not fully written\n//------------------------------------------------------------------------------\nbool\nRainGroup::FillWithZeros()\n{\n  bool ret = true;\n\n  for (auto& block : mBlocks) {\n    ret = ret && block.FillWithZeros();\n  }\n\n  return ret;\n}\n\n//------------------------------------------------------------------------------\n// Save future of async requests\n//------------------------------------------------------------------------------\nvoid\nRainGroup::StoreFuture(std::future<XrdCl::XRootDStatus>&& future)\n{\n  mFutures.push_back(std::move(future));\n}\n\n//----------------------------------------------------------------------------\n// Wait for completion of all registered futures and check if they were all\n// successful.\n//----------------------------------------------------------------------------\nbool\nRainGroup::WaitAsyncOK()\n{\n  bool all_ok = true;\n\n  for (auto& fut : mFutures) {\n    XrdCl::XRootDStatus status = fut.get();\n\n    if (!status.IsOK()) {\n      all_ok = false;\n    }\n  }\n\n  mFutures.clear();\n  return all_ok;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/layout/RainGroup.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file RainGroup.hh\n//! @author Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"fst/layout/RainBlock.hh\"\n#include <XrdCl/XrdClXRootDResponses.hh>\n#include <future>\n#include <list>\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class RainGroup\n//------------------------------------------------------------------------------\nclass RainGroup: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  RainGroup(uint64_t grp_offset, int size, uint32_t block_sz);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~RainGroup() = default;\n\n  //----------------------------------------------------------------------------\n  //! Override [] operator\n  //----------------------------------------------------------------------------\n  eos::fst::RainBlock& operator [](unsigned int i);\n\n  //----------------------------------------------------------------------------\n  //! Get group offset of the current object\n  //----------------------------------------------------------------------------\n  inline uint64_t GetGroupOffset() const\n  {\n    return mOffset;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Lock group\n  //----------------------------------------------------------------------------\n  inline void Lock() const\n  {\n    mMutex.lock();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Lock group\n  //----------------------------------------------------------------------------\n  inline void Unlock() const\n  {\n    mMutex.unlock();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Fill the blocks with zeros if they are not fully written\n  //----------------------------------------------------------------------------\n  bool FillWithZeros();\n\n  //----------------------------------------------------------------------------\n  //! Save future of async requests\n  //!\n  //! @param future future object\n  //----------------------------------------------------------------------------\n  void StoreFuture(std::future<XrdCl::XRootDStatus>&& future);\n\n  //----------------------------------------------------------------------------\n  //! Wait for completion of all registered futures and check if they were all\n  //! successful.\n  //!\n  //! @return true if all successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool WaitAsyncOK();\n\nprivate:\n  uint64_t mOffset; ///< Group offset of the current object\n  std::vector<eos::fst::RainBlock> mBlocks;\n  //! List of futures for async requests\n  std::list<std::future<XrdCl::XRootDStatus>> mFutures;\n  mutable std::mutex mMutex;\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/layout/RainMetaLayout.cc",
    "content": "//------------------------------------------------------------------------------\n// File: RainMetaLayout.cc\n// Author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <cmath>\n#include <string>\n#include <utility>\n#include <stdint.h>\n#include \"Logging.hh\"\n#include \"common/Timing.hh\"\n#include \"fst/layout/RainMetaLayout.hh\"\n#include \"fst/io/AsyncMetaHandler.hh\"\n#include \"fst/layout/HeaderCRC.hh\"\n#include \"RainMetaLayout.hh\"\n\n// Linux compat for Apple\n#ifdef __APPLE__\n#ifndef EREMOTEIO\n#define EREMOTEIO 121\n#endif\n#endif\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nRainMetaLayout::RainMetaLayout(XrdFstOfsFile* file,\n                               unsigned long lid,\n                               const XrdSecEntity* client,\n                               XrdOucErrInfo* outError,\n                               const char* path,\n                               uint16_t timeout,\n                               bool force_recovery,\n                               off_t targetSize,\n                               std::string bookingOpaque,\n                               eos::fst::FmdHandler* fmdHandler,\n                               bool computeStripeChecksum) :\n  Layout(file, lid, client, outError, path, fmdHandler, timeout),\n  mIsRw(false),\n  mIsOpen(false),\n  mIsPio(false),\n  mDoTruncate(false),\n  mDoneRecovery(false),\n  mIsStreaming(true),\n  mForceRecovery(force_recovery),\n  mStoreRecoveryRW(false),\n  mComputeStripeChecksum(computeStripeChecksum),\n  mStripeHead(-1),\n  mNbTotalFiles(0),\n  mNbDataBlocks(0),\n  mNbTotalBlocks(0),\n  mLastWriteOffset(0),\n  mStripeSize(0),\n  mFileSize(0),\n  mSizeLine(0),\n  mSizeGroup(0),\n  mStripeChecksum(nullptr),\n  mIsTruncated(false)\n{\n  mStripeWidth = eos::common::LayoutId::GetBlocksize(lid);\n  mNbTotalFiles = eos::common::LayoutId::GetStripeNumber(lid) + 1;\n  mNbParityFiles = eos::common::LayoutId::GetRedundancyStripeNumber(lid);\n  mNbDataFiles = mNbTotalFiles - mNbParityFiles;\n  mSizeHeader = eos::common::LayoutId::OssXsBlockSize;\n  mPhysicalStripeIndex = -1;\n  mIsEntryServer = false;\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nRainMetaLayout::~RainMetaLayout()\n{\n  while (!mHdrInfo.empty()) {\n    HeaderCRC* hd = mHdrInfo.back();\n    mHdrInfo.pop_back();\n    delete hd;\n  }\n\n  mStripe.clear();\n  StopParityThread();\n}\n\n//------------------------------------------------------------------------------\n// Redirect to new target\n//------------------------------------------------------------------------------\nvoid RainMetaLayout::Redirect(const char* path)\n{\n  mFileIO.reset(FileIoPlugin::GetIoObject(path, mOfsFile, mSecEntity));\n}\n\n//------------------------------------------------------------------------------\n// Perform basic layout checks\n//------------------------------------------------------------------------------\nbool\nRainMetaLayout::BasicLayoutChecks()\n{\n  // Do some minimal checkups\n  if (mNbTotalFiles < 5) {\n    eos_err(\"msg=\\\"failed open, stripe size must be at least 5\\\" \"\n            \"stripe_size=%u\", mNbTotalFiles);\n    return false;\n  }\n\n  if (mStripeWidth < 64) {\n    eos_err(\"msg=\\\"failed open, stripe width must be at least 64\\\" \"\n            \"stripe_width=%llu\", mStripeWidth);\n    return false;\n  }\n\n  // Get the index of the current stripe\n  const char* index = mOfsFile->mOpenOpaque->Get(\"mgm.replicaindex\");\n\n  if (index) {\n    mPhysicalStripeIndex = atoi(index);\n\n    if ((mPhysicalStripeIndex < 0) || (mPhysicalStripeIndex > 255)) {\n      eos_err(\"msg=\\\"illegal stripe index %d\\\"\", mPhysicalStripeIndex);\n      return false;\n    }\n  } else {\n    eos_err(\"%s\", \"msg=\\\"replica index missing\\\"\");\n    return false;\n  }\n\n  if (mOfsFile == nullptr) {\n    eos_err(\"%s\", \"msg=\\\"no raw OFS file available\\\"\");\n    return false;\n  }\n\n  // Get the index of the head stripe\n  const char* head = mOfsFile->mOpenOpaque->Get(\"mgm.replicahead\");\n\n  if (head) {\n    mStripeHead = atoi(head);\n\n    if ((mStripeHead < 0) || (mStripeHead > 255)) {\n      eos_err(\"msg=\\\"illegal stripe head %d\\\"\", mStripeHead);\n      return false;\n    }\n  } else {\n    eos_err(\"%s\", \"msg=\\\"stripe head missing\\\"\");\n    return false;\n  }\n\n  if (!mStripe.empty()) {\n    eos_err(\"%s\", \"msg=\\\"vector of stripe files is not empty\\\"\");\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Open file layout\n//------------------------------------------------------------------------------\nint\nRainMetaLayout::Open(XrdSfsFileOpenMode flags, mode_t mode, const char* opaque)\n{\n  if (!BasicLayoutChecks()) {\n    errno = EINVAL;\n    return SFS_ERROR;\n  }\n\n  if (mPhysicalStripeIndex == mStripeHead) {\n    mIsEntryServer = true;\n  }\n\n  // When recovery enabled we open the files in RDWR mode\n  if (mForceRecovery) {\n    flags = SFS_O_RDWR;\n    mIsRw = true;\n  } else if (flags & (SFS_O_RDWR | SFS_O_TRUNC | SFS_O_WRONLY)) {\n    mStoreRecoveryRW = true;\n    mIsRw = true;\n    // Files are never open in update mode!\n    flags |= (SFS_O_RDWR | SFS_O_TRUNC);\n  } else {\n    mode = 0;\n  }\n\n  eos_debug(\"flags=%x isrw=%i truncate=%d\", flags, mIsRw,\n            ((mForceRecovery && mIsEntryServer) ? 1 : 0));\n  // Add opaque information to enable readahead\n  std::string enhanced_opaque = opaque;\n  enhanced_opaque += \"&fst.readahead=true\";\n  enhanced_opaque += \"&fst.blocksize=\";\n  enhanced_opaque += std::to_string(mStripeWidth);\n  // Local stripe is always on the first position\n  std::string local_url = SSTR(mLocalPath << \"?\" << enhanced_opaque).c_str();\n  std::vector<std::string> stripe_urls;\n  stripe_urls.push_back(local_url);\n  XrdOucString ns_path = mOfsFile->mOpenOpaque->Get(\"mgm.path\");\n\n  // Operations done only by the entry server\n  if (mIsEntryServer) {\n    unsigned int nmissing = 0;\n\n    // @note: for TPC transfers we open the remote stipes only in the\n    // kTpcSrcRead or kTpcDstSetup stages.\n    if ((mOfsFile->mTpcFlag == XrdFstOfsFile::kTpcSrcRead) ||\n        (mOfsFile->mTpcFlag == XrdFstOfsFile::kTpcDstSetup) ||\n        (mOfsFile->mTpcFlag == XrdFstOfsFile::kTpcNone)) {\n      // Build stripe urls and check minimal requirements\n      for (unsigned int i = 0; i < mNbTotalFiles; i++) {\n        if (i != (unsigned int)mPhysicalStripeIndex) {\n          // Extract xrootd endpoint\n          std::string stripe_url;\n          std::string stripe_tag = \"mgm.url\" + std::to_string(i);\n          const char* stripe = mOfsFile->mCapOpaque->Get(stripe_tag.c_str());\n\n          if (!stripe) {\n            nmissing++;\n\n            // For read we tolerate at most mNbParityFiles missing, for write none\n            if ((mIsRw && nmissing) || (!mIsRw && (nmissing > mNbParityFiles))) {\n              eos_err(\"msg=\\\"failed open, %i stripes missing and parity is %i\\\"\",\n                      nmissing, mNbParityFiles);\n              errno = EINVAL;\n              return SFS_ERROR;\n            }\n\n            stripe_urls.push_back(\"\");\n            continue;\n          } else {\n            stripe_url = stripe;\n          }\n\n          // Build path and opaque info for remote stripes\n          stripe_url += ns_path.c_str();\n          stripe_url += \"?\";\n          int envlen;\n          const char* val;\n          XrdOucString new_opaque = mOfsFile->mOpenOpaque->Env(envlen);\n\n          if ((val = mOfsFile->mOpenOpaque->Get(\"mgm.replicaindex\"))) {\n            XrdOucString oldindex = \"mgm.replicaindex=\";\n            XrdOucString newindex = \"mgm.replicaindex=\";\n            oldindex += val;\n            newindex += static_cast<int>(i);\n            new_opaque.replace(oldindex.c_str(), newindex.c_str());\n          } else {\n            new_opaque += \"&mgm.replicaindex=\";\n            new_opaque += static_cast<int>(i);\n          }\n\n          enhanced_opaque = new_opaque.c_str();\n          enhanced_opaque += \"&fst.readahead=true\";\n          enhanced_opaque += \"&fst.blocksize=\";\n          enhanced_opaque += std::to_string(mStripeWidth);\n          stripe_url += enhanced_opaque;\n          stripe_urls.push_back(stripe_url);\n        }\n      }\n    }\n  }\n\n  // Initialize stripe checksum only if it's enabled by the config\n  if (mComputeStripeChecksum) {\n    mStripeChecksum = std::make_unique<eos::fst::Adler>();\n    eos_debug(\"msg=\\\"stripe checksum enabled\\\" fxid=%08llx\",\n              mOfsFile->GetFileId());\n  } else {\n    eos_debug(\"msg=\\\"stripe checksum disabled\\\" fxid=%08llx\",\n              mOfsFile->GetFileId());\n  }\n\n  unsigned int num_failures = 0u;\n  std::vector<std::future<XrdCl::XRootDStatus>> open_futures;\n\n  // Open stripes\n  for (unsigned int i = 0; i < stripe_urls.size(); ++i) {\n    if (stripe_urls[i].empty()) {\n      open_futures.emplace_back();\n      mStripe.push_back(nullptr);\n    } else {\n      size_t pos = stripe_urls[i].find('?');\n      std::string stripe_url = stripe_urls[i].substr(0, pos);\n      std::string stripe_opaque;\n\n      if ((pos != std::string::npos) && (*stripe_urls[i].rbegin() != '?')) {\n        stripe_opaque = stripe_urls[i].substr(pos + 1);\n      }\n\n      std::unique_ptr<FileIo> file\n      {FileIoPlugin::GetIoObject(stripe_url, mOfsFile, mSecEntity)};\n\n      if (file) {\n        if ((i == 0) && mIsRw) {\n          struct stat info;\n\n          if (file->fileStat(&info)) {\n            // The local stripe is expected to be reconstructed during\n            // recovery and since it might not exist, it gets created\n            flags |= SFS_O_CREAT;\n          } else {\n            // If local stripe exists then we load the stripe checksum\n            mStripeSize = info.st_size;\n\n            if (mStripeChecksum && mStripeSize) {\n              auto xs = GetStripeChecksum();\n\n              if (xs.has_value()) {\n                mStripeChecksum->ResetInit(0, mStripeSize, xs.value().c_str());\n              } else {\n                mStripeChecksum->SetDirty();\n              }\n            }\n          }\n        }\n\n        open_futures.push_back(file->fileOpenAsync(flags, mode, stripe_opaque,\n                               mTimeout));\n        mStripe.push_back(std::move(file));\n      } else {\n        open_futures.emplace_back();\n        mStripe.push_back(nullptr);\n      }\n    }\n  }\n\n  // Collect open replies and read header information\n  for (unsigned int i = 0; i < mStripe.size(); ++i) {\n    HeaderCRC* hd = new HeaderCRC(mSizeHeader, mStripeWidth);\n    mHdrInfo.push_back(hd);\n\n    if (open_futures[i].valid()) {\n      if (open_futures[i].get().IsOK()) {\n        if (!hd->ReadFromFile(mStripe[i].get(), mTimeout) &&\n            ((flags & SFS_O_CREAT) == 0)) {\n          eos_warning(\"msg=\\\"failed reading header\\\" url=\\\"%s\\\"\",\n                      stripe_urls[i].c_str());\n        }\n      } else {\n        eos_warning(\"msg=\\\"failed open stripe\\\" url=\\\"%s\\\"\",\n                    stripe_urls[i].c_str());\n        mStripe[i] = nullptr;\n        ++num_failures;\n      }\n    } else {\n      eos_warning(\"msg=\\\"failed open stripe\\\" url=\\\"%s\\\"\",\n                  stripe_urls[i].c_str());\n      mStripe[i] = nullptr;\n      ++num_failures;\n    }\n  }\n\n  // Check if there are any fatal errors\n  if ((mIsRw && num_failures) ||\n      (!mIsEntryServer && !mIsRw && num_failures) ||\n      (mIsEntryServer && !mIsRw && (num_failures > mNbParityFiles))) {\n    eos_err(\"msg=\\\"failed to open some file objects\\\" num_failures=%i \"\n            \"path=%s is_rw=%i\", num_failures, ns_path.c_str(), mIsRw);\n    errno = EINVAL;\n    return SFS_ERROR;\n  }\n\n  // Only the head node does the validation of the headers\n  if (mIsEntryServer) {\n    if ((mOfsFile->mTpcFlag == XrdFstOfsFile::kTpcSrcRead) ||\n        (mOfsFile->mTpcFlag == XrdFstOfsFile::kTpcDstSetup) ||\n        (mOfsFile->mTpcFlag == XrdFstOfsFile::kTpcNone)) {\n      if (!ValidateHeader()) {\n        eos_err(\"%s\", \"msg=\\\"fail open due to invalid headers\\\"\");\n        errno = EIO;\n        return SFS_ERROR;\n      }\n    }\n\n    // Only entry server in RW mode starts the parity thread helper\n    if (mIsRw) {\n      mHasParityThread = true;\n      mParityThread.reset(&RainMetaLayout::StartParityThread, this);\n    }\n  }\n\n  // Get file size based on the data stored in the local stripe header\n  mFileSize = -1;\n\n  if (mHdrInfo[0]->IsValid()) {\n    mFileSize = mHdrInfo[0]->GetSizeFile();\n  } else {\n    // For the entry server we just need to re-read the header as it was\n    // recovered in the above ValidateHeader method. For the rest of the\n    // stripes it doesn't matter if they have or not the correct file size -\n    // anyway we can not recover here :D\n    // @note: for TPC transfers we open the remote stipes only in the\n    // kTpcSrcRead or kTpcDstSetup stages.\n    if (mIsEntryServer) {\n      if (mHdrInfo[0]->IsValid()) {\n        mFileSize = mHdrInfo[0]->GetSizeFile();\n      } else {\n        eos_err(\"%s\", \"msg=\\\"head node can not compute the file size\\\"\");\n        return SFS_ERROR;\n      }\n    }\n  }\n\n  eos_debug(\"msg=\\\"open successful\\\" file_size=%llu\", mFileSize);\n  mIsOpen = true;\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Get stripe checksum value\n//------------------------------------------------------------------------------\nstd::optional<std::string> RainMetaLayout::GetStripeChecksum()\n{\n  auto [ok, fmd] = mFmdHandler->LocalRetrieveFmd(mOfsFile->mFileId,\n                   mOfsFile->mFsId);\n\n  if (!ok) {\n    return std::nullopt;\n  }\n\n  if (!fmd.mProtoFmd.has_stripechecksum() ||\n      fmd.mProtoFmd.stripechecksum() == \"\") {\n    return std::nullopt;\n  }\n\n  return fmd.mProtoFmd.stripechecksum();\n}\n\n//------------------------------------------------------------------------------\n// Set stripe checksum value in the FMD object attached to this physical file\n//------------------------------------------------------------------------------\nbool RainMetaLayout::SetStripeChecksum(std::string checksumHex)\n{\n  auto [ok, fmd] = mFmdHandler->LocalRetrieveFmd(mOfsFile->mFileId,\n                   mOfsFile->mFsId);\n\n  if (!ok) {\n    return false;\n  }\n\n  fmd.mProtoFmd.set_stripechecksum(checksumHex);\n  return mFmdHandler->Commit(&fmd);\n}\n\n//------------------------------------------------------------------------------\n// Open file using parallel IO - helper\n//------------------------------------------------------------------------------\nint\nRainMetaLayout::OpenPio(const std::vector<std::string>& stripe_urls,\n                        XrdSfsFileOpenMode flags, mode_t mode,\n                        const char* opaque)\n{\n  std::vector<std::pair<int, std::string>> stripes;\n  stripes.reserve(stripe_urls.size());\n\n  for (unsigned long i = 0; i < stripe_urls.size(); i++) {\n    stripes.emplace_back(static_cast<int>(i), stripe_urls[i]);\n  }\n\n  return OpenPio(stripes, flags, mode, opaque);\n}\n\n//------------------------------------------------------------------------------\n// Open file using paralled IO\n//------------------------------------------------------------------------------\nint\nRainMetaLayout::OpenPio(const std::vector<std::pair<int, std::string>>&\n                        stripe_urls,\n                        XrdSfsFileOpenMode flags, mode_t mode, const char* opaque)\n{\n  // Do some minimal checkups\n  if (mNbTotalFiles < 2) {\n    eos_err(\"msg=\\\"failed open layout, stripe size at least 2\\\" stripes=%u\",\n            mNbTotalFiles);\n    return SFS_ERROR;\n  }\n\n  if (mStripeWidth < 64) {\n    eos_err(\"msg=\\\"failed open layout, stripe width at least 64\\\" \"\n            \"stripe_width=%llu\", mStripeWidth);\n    return SFS_ERROR;\n  }\n\n  //!!!!\n  // TODO: allow open only in read only mode\n  // Set the correct open flags for the stripe\n  if (mForceRecovery) {\n    flags = SFS_O_RDWR;\n    mIsRw = true;\n    eos_debug(\"%s\", \"msg=\\\"write recovery case\\\"\");\n  } else if (flags & (SFS_O_CREAT | SFS_O_WRONLY | SFS_O_RDWR | SFS_O_TRUNC)) {\n    mStoreRecoveryRW = true;\n    mIsRw = true;\n    eos_debug(\"%s\", \"msg=\\\"write case\\\"\");\n  } else {\n    mode = 0;\n    eos_debug(\"%s\", \"msg=\\\"read case\\\"\");\n  }\n\n  std::vector<std::future<XrdCl::XRootDStatus>> open_futures;\n\n  // Open stripes\n  for (auto [replicaIndex, url] : stripe_urls) {\n    XrdOucString new_opaque = opaque;\n    new_opaque += \"&mgm.replicaindex=\";\n    new_opaque += replicaIndex;\n    new_opaque += \"&fst.readahead=true\";\n    new_opaque += \"&fst.blocksize=\";\n    new_opaque += static_cast<int>(mStripeWidth);\n    std::unique_ptr<FileIo> file {FileIoPlugin::GetIoObject(url)};\n\n    if (file) {\n      open_futures.push_back(file->fileOpenAsync(flags, mode, new_opaque.c_str()));\n      mStripe.push_back(std::move(file));\n    } else {\n      open_futures.emplace_back();\n      mStripe.push_back(nullptr);\n    }\n  }\n\n  // Collect open replies and read header information\n  for (unsigned int i = 0; i < stripe_urls.size(); ++i) {\n    HeaderCRC* hd = new HeaderCRC(mSizeHeader, mStripeWidth);\n    mHdrInfo.push_back(hd);\n\n    if (open_futures[i].valid()) {\n      if (open_futures[i].get().IsOK()) {\n        if (!hd->ReadFromFile(mStripe[i].get(), mTimeout)) {\n          eos_warning(\"msg=\\\"failed reading header\\\" url=\\\"%s\\\"\",\n                      stripe_urls[i].second.c_str());\n        }\n      } else {\n        // If flag is SFS_RDWR then we can try to create the file otherwise\n        // just mark it as a failure\n        if (flags & SFS_O_RDWR) {\n          XrdSfsFileOpenMode tmp_flags = flags | SFS_O_CREAT;\n          mode_t tmp_mode = mode | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;\n          XrdOucString new_opaque = opaque;\n          new_opaque += \"&mgm.replicaindex=\";\n          new_opaque += stripe_urls[i].first;\n          new_opaque += \"&fst.readahead=true\";\n          new_opaque += \"&fst.blocksize=\";\n          new_opaque += static_cast<int>(mStripeWidth);\n          int ret = mStripe[i]->fileOpen(tmp_flags, tmp_mode, new_opaque.c_str());\n\n          if (ret == SFS_ERROR) {\n            eos_err(\"msg=\\\"failed open create stripe\\\" url=%s\",\n                    stripe_urls[i].second.c_str());\n            mStripe[i] = nullptr;\n          }\n        } else {\n          mStripe[i] = nullptr;\n        }\n      }\n    }\n  }\n\n  // For PIO if header invalid then we abort\n  if (!ValidateHeader()) {\n    eos_err(\"%s\", \"msg=\\\"headers invalid, fail open\\\"\");\n    return SFS_ERROR;\n  }\n\n  // Get the size of the file\n  mFileSize = -1;\n\n  for (unsigned int i = 0; i < mHdrInfo.size(); i++) {\n    if (mHdrInfo[i]->IsValid()) {\n      mFileSize = mHdrInfo[i]->GetSizeFile();\n      break;\n    }\n  }\n\n  eos_debug(\"msg=\\\"pio open done\\\" open_size=%llu\", mFileSize);\n  mIsPio = true;\n  mIsOpen = true;\n  mIsEntryServer = true;\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Test and recover if headers are corrupted\n//------------------------------------------------------------------------------\nbool\nRainMetaLayout::ValidateHeader()\n{\n  bool new_file = true;\n  bool all_hd_valid = true;\n  unsigned int hd_id_valid = -1;\n  std::vector<unsigned int> physical_ids_invalid;\n  std::set<unsigned int> used_stripes;\n\n  for (unsigned int i = 0; i < mHdrInfo.size(); i++) {\n    if (mHdrInfo[i]->IsValid()) {\n      unsigned int sid = mHdrInfo[i]->GetIdStripe();\n\n      if (used_stripes.count(sid)) {\n        eos_err(\"msg=\\\"two physical files with the same stripe id\\\" \"\n                \"local_path=\\\"%s\\\"\", mLocalPath.c_str());\n        return false;\n      }\n\n      mapPL[i] = sid;\n      mapLP[sid] = i;\n      used_stripes.insert(sid);\n      hd_id_valid = i;\n      new_file = false;\n    } else {\n      all_hd_valid = false;\n      physical_ids_invalid.push_back(i);\n    }\n  }\n\n  if (new_file || all_hd_valid) {\n    eos_debug(\"msg=\\\"file is either new or there are no corruptions\\\" \"\n              \"local_path=\\\"%s\\\"\", mLocalPath.c_str());\n\n    if (new_file) {\n      for (unsigned int i = 0; i < mHdrInfo.size(); i++) {\n        mHdrInfo[i]->SetState(true); //set valid header\n        mHdrInfo[i]->SetNoBlocks(0);\n        mHdrInfo[i]->SetSizeLastBlock(0);\n        mapPL[i] = i;\n        mapLP[i] = i;\n      }\n    }\n\n    return true;\n  }\n\n  // Can not recover from more than mNbParityFiles corruptions\n  if (physical_ids_invalid.size() > mNbParityFiles) {\n    eos_err(\"msg=\\\"can not recover more than %u corruptions\\\" num_corrupt=%i \"\n            \"local_path=\\\"%s\\\"\", mNbParityFiles, physical_ids_invalid.size(),\n            mLocalPath.c_str());\n    return false;\n  }\n\n  while (physical_ids_invalid.size()) {\n    unsigned int physical_id = physical_ids_invalid.back();\n    physical_ids_invalid.pop_back();\n\n    for (unsigned int i = 0; i < mNbTotalFiles; i++) {\n      if (find(used_stripes.begin(), used_stripes.end(), i) == used_stripes.end()) {\n        // Add the new mapping\n        mapPL[physical_id] = i;\n        used_stripes.insert(i);\n        mHdrInfo[physical_id]->SetIdStripe(i);\n        mHdrInfo[physical_id]->SetState(true);\n        mHdrInfo[physical_id]->SetNoBlocks(mHdrInfo[hd_id_valid]->GetNoBlocks());\n        mHdrInfo[physical_id]->SetSizeLastBlock(\n          mHdrInfo[hd_id_valid]->GetSizeLastBlock());\n\n        // If file successfully opened, we need to store the info\n        if ((mForceRecovery || mStoreRecoveryRW) && mStripe[physical_id]) {\n          eos_info(\"msg=\\\"recovered header for stripe %i\\\" local_path=\\\"%s\\\"\",\n                   mapPL[physical_id], mLocalPath.c_str());\n          mHdrInfo[physical_id]->WriteToFile(mStripe[physical_id].get(), mTimeout);\n        }\n\n        break;\n      }\n    }\n  }\n\n  used_stripes.clear();\n\n  // Populate the stripe url map\n  for (unsigned int i = 0; i < mNbTotalFiles; i++) {\n    mapLP[mapPL[i]] = i;\n    eos_debug(\"msg=\\\"stripe physical=%i mapped to logical=%i\\\" \"\n              \"local_path=\\\"%s\\\"\", i, mapPL[i], mLocalPath.c_str());\n  }\n\n  mDoneRecovery = true;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Read from file\n//------------------------------------------------------------------------------\nint64_t\nRainMetaLayout::Read(XrdSfsFileOffset offset, char* buffer,\n                     XrdSfsXferSize length, bool readahead)\n{\n  eos_debug(\"offset=%llu, length=%i\", offset, length);\n  XrdSysMutexHelper scope_lock(mExclAccess);\n  eos::common::Timing rt(\"read\");\n  COMMONTIMING(\"start\", &rt);\n  unsigned int physical_id;\n  int64_t read_length = 0;\n  uint64_t off_local = 0;\n  uint64_t end_raw_offset = (uint64_t)(offset + length);\n  XrdCl::ChunkList all_errs;\n\n  if (!mIsEntryServer) {\n    // Non-entry server doing only local read operation\n    if (mStripe[0]) {\n      read_length = mStripe[0]->fileRead(offset, buffer, length, mTimeout);\n    }\n  } else {\n    // Only entry server does this\n    if ((uint64_t)offset > mFileSize) {\n      eos_warning(\"msg=\\\"read past end-of-file\\\" offset=%lld file_size=%llu\",\n                  offset, mFileSize);\n      return 0;\n    }\n\n    if (end_raw_offset > mFileSize) {\n      eos_warning(\"msg=\\\"read too big resizing the read length\\\" \"\n                  \"end_offset=%lli file_size=%llu\", end_raw_offset,\n                  mFileSize);\n      length = static_cast<int>(mFileSize - offset);\n\n      if (length == 0) {\n        return 0;\n      }\n    }\n\n    if (mForceRecovery) {\n      read_length = ReadForceRecovery(offset, buffer, length);\n    } else {\n      // Split original read in chunks which can be read from one stripe and return\n      // their relative offsets in the original file\n      int64_t nbytes = 0;\n      bool do_recovery = false;\n      bool got_error = false;\n      std::vector<XrdCl::ChunkInfo> split_chunk = SplitRead((uint64_t)offset,\n          (uint32_t)length, buffer);\n\n      for (auto chunk = split_chunk.begin(); chunk != split_chunk.end(); ++chunk) {\n        COMMONTIMING(\"read remote in\", &rt);\n        got_error = false;\n        auto local_pos = GetLocalOff(chunk->offset);\n        physical_id = mapLP[local_pos.first];\n        off_local = local_pos.second + mSizeHeader;\n\n        if (mStripe[physical_id]) {\n          eos_debug(\"msg=\\\"read\\\" stripe_id=%i offset=%llu stripe_off=%llu \"\n                    \"stripe_len=%d\", local_pos.first, chunk->offset, off_local,\n                    chunk->length);\n          nbytes = mStripe[physical_id]->fileReadPrefetch(off_local,\n                   (char*)chunk->buffer,\n                   chunk->length, mTimeout);\n\n          if (nbytes != chunk->length) {\n            got_error = true;\n          }\n        } else {\n          // File not opened, we register it as a read error\n          got_error = true;\n        }\n\n        // Save errors in the map to be recovered\n        if (got_error) {\n          if (mStripe[physical_id]) {\n            eos_err(\"msg=\\\"read error\\\" offset=%llu length=%d msg=\\\"%s\\\"\",\n                    chunk->offset, chunk->length,\n                    mStripe[physical_id]->GetLastErrMsg().c_str());\n          }\n\n          all_errs.push_back(*chunk);\n          do_recovery = true;\n        }\n      }\n\n      // Try to recover any corrupted blocks\n      if (do_recovery) {\n        if (!RecoverPieces(all_errs)) {\n          eos_err(\"msg=\\\"failed read recovery\\\" offset=%llu lenght=%d\",\n                  offset, length);\n          return SFS_ERROR;\n        }\n      }\n\n      read_length = length;\n    }\n  }\n\n  COMMONTIMING(\"read return\", &rt);\n  // rt.Print();\n  return read_length;\n}\n\n//------------------------------------------------------------------------------\n// Read from stripes - used only for the rain check tool\n//------------------------------------------------------------------------------\nint64_t\nRainMetaLayout::ReadStripe(XrdSfsFileOffset offset, char* buffer,\n                           XrdSfsXferSize length, int stripeIdx)\n{\n  eos_debug(\"offset=%llu, length=%i\", offset, length);\n  XrdSysMutexHelper scope_lock(mExclAccess);\n  eos::common::Timing rt(\"read\");\n  COMMONTIMING(\"start\", &rt);\n  uint64_t end_raw_offset = offset + length;\n  uint64_t const stripeSize =\n    mSizeHeader +\n    mStripeWidth * (1 + ((mFileSize - 1) / (mStripeWidth * mNbDataFiles)));\n\n  if ((uint64_t)offset > stripeSize) {\n    eos_warning(\"msg=\\\"read past end-of-file\\\" offset=%lld file_size=%llu\",\n                offset, stripeSize);\n    return 0;\n  }\n\n  if (end_raw_offset > stripeSize) {\n    eos_warning(\"msg=\\\"read too big resizing the read length\\\" \"\n                \"end_offset=%lli file_size=%llu\",\n                end_raw_offset, stripeSize);\n    length = static_cast<int>(stripeSize - offset);\n\n    if (length == 0) {\n      return 0;\n    }\n  }\n\n  COMMONTIMING(\"read remote in\", &rt);\n\n  if (mStripe[stripeIdx]) {\n    int64_t nbytes =\n      mStripe[stripeIdx]->fileReadPrefetch(offset, buffer, length, mTimeout);\n\n    if (nbytes == length) {\n      COMMONTIMING(\"read return\", &rt);\n      // rt.Print();\n      return length;\n    }\n  }\n\n  if (mStripe[stripeIdx]) {\n    eos_err(\"msg=\\\"read error\\\" offset=%llu length=%d msg=\\\"%s\\\"\", offset,\n            length, mStripe[stripeIdx]->GetLastErrMsg().c_str());\n  }\n\n  return -1;\n}\n\n//----------------------------------------------------------------------------\n//! Read operation that triggers a forced recovery per group\n//----------------------------------------------------------------------------\nint64_t\nRainMetaLayout::ReadForceRecovery(XrdSfsFileOffset offset,\n                                  char* buffer,\n                                  XrdSfsXferSize length)\n{\n  eos_debug(\"msg=\\\"force file recover mode\\\" path=%s offset=%llu\",\n            mOfsFile->mOpenOpaque->Get(\"mgm.path\"), offset);\n  uint64_t grp_indx = (offset / mSizeGroup);\n  {\n    std::unique_lock<std::mutex> lock(mMtxRecoveredGrps);\n\n    // If group already recovered then skip it, we don't care about the\n    // contents of the data returned i.e. the buffer is unpopulated\n    if (mRecoveredGrpIndx.find(grp_indx) != mRecoveredGrpIndx.end()) {\n      return length;\n    } else {\n      eos_info(\"msg=\\\"recover group index\\\" grp_indx=%llu\", grp_indx);\n      mRecoveredGrpIndx.insert(grp_indx);\n    }\n  }\n  XrdSfsFileOffset grp_offset = grp_indx * mSizeGroup;\n  std::unique_ptr<RainBlock> recover_block {new RainBlock(mStripeWidth)};\n  XrdCl::ChunkList all_errs {\n    XrdCl::ChunkInfo((uint64_t) grp_offset, (uint32_t) mStripeWidth,\n                     (void*)recover_block->GetDataPtr())};\n\n  if (!RecoverPieces(all_errs)) {\n    eos_err(\"msg=\\\"failed recovery\\\" offset=%llu length=%d\", offset, length);\n    return SFS_ERROR;\n  }\n\n  eos_debug(\"msg=\\\"done forced group recovery\\\" path=%s offset=%llu \"\n            \"grp_indx=%llu\", mOfsFile->mOpenOpaque->Get(\"mgm.path\"),\n            offset, grp_indx);\n  return length;\n}\n\n//------------------------------------------------------------------------------\n// Vector read\n//------------------------------------------------------------------------------\nint64_t\nRainMetaLayout::ReadV(XrdCl::ChunkList& chunkList, uint32_t len)\n{\n  int64_t nread = 0;\n  AsyncMetaHandler* phandler = 0;\n  XrdCl::ChunkList all_errs;\n\n  if (!mIsEntryServer) {\n    // Non-entry server doing local readv operations\n    if (mStripe[0]) {\n      nread = mStripe[0]->fileReadV(chunkList);\n\n      if (nread != len) {\n        eos_err(\"%s\", \"msg=\\\"failed local vector read\\\"\");\n        return SFS_ERROR;\n      }\n    }\n  } else {\n    // Reset all the async handlers\n    for (unsigned int i = 0; i < mStripe.size(); i++) {\n      if (mStripe[i]) {\n        phandler = static_cast<AsyncMetaHandler*>(mStripe[i]->fileGetAsyncHandler());\n\n        if (phandler) {\n          phandler->Reset();\n        }\n      }\n    }\n\n    // Entry server splits requests per stripe returning the relative position of\n    // each chunks inside the stripe file including the header offset\n    bool do_recovery = false;\n    uint32_t stripe_id;\n    uint32_t physical_id;\n    std::vector<XrdCl::ChunkList> stripe_chunks = SplitReadV(chunkList,\n        mSizeHeader);\n\n    for (stripe_id = 0; stripe_id < stripe_chunks.size(); ++stripe_id) {\n      bool got_error = false;\n\n      if (stripe_chunks[stripe_id].size() == 0) {\n        continue;\n      }\n\n      physical_id = mapLP[stripe_id];\n\n      if (mStripe[physical_id]) {\n        eos_debug(\"msg=\\\"readv\\\" stripe_id=%u read_count=%i physical_id=%u\",\n                  stripe_id, stripe_chunks[stripe_id].size(), physical_id);\n        nread = mStripe[physical_id]->fileReadVAsync(stripe_chunks[stripe_id],\n                mTimeout);\n\n        if (nread == SFS_ERROR) {\n          eos_err(\"msg=\\\"readv error\\\" msg=\\\"%s\\\" physical_id=%u\",\n                  mStripe[physical_id]->GetLastErrMsg().c_str(), physical_id);\n          got_error = true;\n        }\n      } else {\n        // File not opened, we register it as a read error\n        got_error = true;\n      }\n\n      // Save errors in the map to be recovered\n      if (got_error) {\n        do_recovery = true;\n\n        for (auto chunk = stripe_chunks[stripe_id].begin();\n             chunk != stripe_chunks[stripe_id].end(); ++chunk) {\n          chunk->offset = GetGlobalOff(stripe_id, chunk->offset - mSizeHeader);\n\n          if (mStripe[physical_id]) {\n            eos_err(\"msg=\\\"vector read error\\\" offset=%llu length=%d \"\n                    \"physical_id=%u\", chunk->offset, chunk->length,\n                    physical_id);\n          }\n\n          all_errs.push_back(*chunk);\n        }\n      }\n    }\n\n    // Collect errors\n    XrdCl::ChunkList local_errs;\n\n    for (unsigned int j = 0; j < mStripe.size(); j++) {\n      if (mStripe[j]) {\n        phandler = static_cast<AsyncMetaHandler*>(mStripe[j]->fileGetAsyncHandler());\n\n        if (phandler) {\n          uint16_t error_type = phandler->WaitOK();\n\n          if (error_type != XrdCl::errNone) {\n            // Get the type of error and the map\n            local_errs = phandler->GetErrors();\n            stripe_id = mapPL[j];\n\n            for (auto chunk = local_errs.begin(); chunk != local_errs.end(); chunk++) {\n              chunk->offset = GetGlobalOff(stripe_id, chunk->offset - mSizeHeader);\n              eos_err(\"msg=\\\"vector read error\\\" offset=%llu length=%d \"\n                      \"xrdcl_errno=%u physical_id=%u\", chunk->offset,\n                      chunk->length, error_type, j);\n              all_errs.push_back(*chunk);\n            }\n\n            do_recovery = true;\n\n            // If timeout error, then disable current file as we asume that\n            // the server is down\n            if (error_type == XrdCl::errOperationExpired) {\n              eos_debug(\"msg=\\\"calling close after timeout error\\\" \"\n                        \"physical_id=%u\", j);\n              mStripe[j]->fileClose(mTimeout);\n              mStripe[j] = nullptr;\n            }\n          }\n        }\n      }\n    }\n\n    // Try to recover any corrupted blocks\n    if (do_recovery && (!RecoverPieces(all_errs))) {\n      char eMsg[512];\n      snprintf(eMsg, sizeof(eMsg), \"readv recovery failed count=%lu\",\n               chunkList.size());\n      eos_err(\"msg=\\\"%s\\\"\", eMsg);\n      return Emsg(\"RainReadV\", *mError, EFAULT, \"readv recovery failed\",\n                  mOfsFile->mOpenOpaque->Get(\"mgm.path\"));\n    }\n  }\n\n  return (uint64_t)len;\n}\n\n\nvoid RainMetaLayout::AddDataToStripeChecksum(char* buffer, size_t size,\n    size_t stripe_offset)\n{\n  if (!mStripeChecksum) {\n    return;\n  }\n\n  // In the stripe checksum computation the header part is removed.\n  // This means that the actual offset the checksum object is aware\n  // of, is actually the current file offset - size of the header.\n  //\n  // During the first write, the buffer can contain the header segment\n  // and this should be removed from the buffer.\n  if (stripe_offset < mSizeHeader) {\n    const size_t padding = mSizeHeader - stripe_offset;\n\n    if (size <= padding) {\n      // There is no data to give to the checksum obj\n      return;\n    }\n\n    buffer += padding;\n    size -= padding;\n    stripe_offset = padding > stripe_offset ? 0 : stripe_offset - padding;\n  } else {\n    stripe_offset -= mSizeHeader;\n  }\n\n  mStripeChecksum->Add(buffer, size, stripe_offset);\n}\n\n//------------------------------------------------------------------------------\n// Write to file\n//------------------------------------------------------------------------------\nint64_t\nRainMetaLayout::Write(XrdSfsFileOffset offset,\n                      const char* buffer,\n                      XrdSfsXferSize length)\n{\n  XrdSysMutexHelper scope_lock(mExclAccess);\n  eos::common::Timing wt(\"write\");\n  COMMONTIMING(\"start\", &wt);\n  int64_t nwrite;\n  int64_t nbytes;\n  int64_t write_length = 0;\n  uint64_t off_local;\n  uint64_t offset_end = offset + length;\n  unsigned int physical_id;\n  eos_debug(\"offset=%llu length=%d\", offset, length);\n\n  if (!mIsEntryServer) {\n    // Non-entry server doing only local operations\n    if (mStripe[0]) {\n      write_length = mStripe[0]->fileWrite(offset, buffer, length, mTimeout);\n      AddDataToStripeChecksum((char*)buffer, write_length, offset);\n      mStripeSize = offset + (uint64_t)write_length < mStripeSize ? mStripeSize :\n                    offset + (uint64_t)write_length;\n      mLastWriteOffset += length;\n    }\n  } else {\n    // Detect if this is a non-streaming write\n    if (mIsStreaming && ((uint64_t)offset != mLastWriteOffset)) {\n      eos_debug(\"%s\", \"msg=\\\"enable non-streaming mode\\\"\");\n      mIsStreaming = false;\n      // @todo(esindril) check the return value of any flushed writes from\n      // the groups pending parity computation\n    }\n\n    if (mHasParityErr) {\n      eos_err(\"msg=\\\"failed due to previous parity computation error\\\" \"\n              \"off=%llu len=%li\", offset, length);\n      return SFS_ERROR;\n    }\n\n    mLastWriteOffset += length;\n\n    while (length) {\n      auto pos = GetLocalOff(offset);\n      physical_id = mapLP[pos.first];\n      off_local = pos.second;\n      off_local += mSizeHeader;\n      nwrite = (length < (int64_t)mStripeWidth) ? length : mStripeWidth;\n\n      if (!mStripe[physical_id]) {\n        eos_err(\"msg=\\\"failed write, stripe file is null\\\" offset=%llu \"\n                \"length=%d physical_id=%i\", offset, length, physical_id);\n        write_length = SFS_ERROR;\n        break;\n      }\n\n      // Deal with the case when offset is not aligned (sparse writing) and the\n      // length goes beyond the current stripe that we are writing to\n      if ((offset % mStripeWidth != 0) &&\n          (offset / mStripeWidth) != ((offset + nwrite) / mStripeWidth)) {\n        nwrite = mStripeWidth - (offset % mStripeWidth);\n      }\n\n      COMMONTIMING(\"write remote\", &wt);\n\n      // By default we assume the file is written in streaming mode but we also\n      // save the pieces in the map in case the write turns out not to be in\n      // streaming mode. In this way, we can recompute the parity later on by\n      // using the map of pieces written.\n      if (mIsStreaming) {\n        if (!AddDataBlock(offset, buffer, nwrite, mStripe[physical_id].get(),\n                          off_local)) {\n          write_length = SFS_ERROR;\n          break;\n        }\n      } else {\n        // Write to stripe\n        if (mStripe[physical_id]) {\n          nbytes = mStripe[physical_id]->fileWriteAsync(off_local, buffer,\n                   nwrite, mTimeout);\n\n          if (nbytes != nwrite) {\n            eos_err(\"msg=\\\"failed write operation\\\" offset=%llu length=%d\",\n                    offset, length);\n            write_length = SFS_ERROR;\n            break;\n          }\n        }\n      }\n\n      // we compute the stripe checksum only on the entry server\n      // since for the other stripes this is computed by the other FSTs\n      if (physical_id == 0) {\n        AddDataToStripeChecksum((char*)buffer, nwrite, off_local);\n        mStripeSize = off_local + nwrite < mStripeSize ? mStripeSize : off_local +\n                      nwrite;\n      }\n\n      AddPiece(offset, nwrite);\n      offset += nwrite;\n      length -= nwrite;\n      buffer += nwrite;\n      write_length += nwrite;\n    }\n\n    // Non-streaming mode - try to compute parity if enough data\n    if (!mIsStreaming && !SparseParityComputation(false)) {\n      eos_err(\"%s\", \"msg=\\\"failed while doing SparseParityComputation\\\"\");\n      return SFS_ERROR;\n    }\n\n    if (offset_end > mFileSize) {\n      eos_debug(\"msg=\\\"update file size\\\" mFileSize=%llu offset_end=%llu\",\n                mFileSize, offset_end);\n      mFileSize = offset_end;\n      mDoTruncate = true;\n    }\n  }\n\n  COMMONTIMING(\"end\", &wt);\n  //  wt.Print();\n  return write_length;\n}\n\n//------------------------------------------------------------------------------\n// Add a new data used to compute parity block\n//------------------------------------------------------------------------------\nbool\nRainMetaLayout::AddDataBlock(uint64_t offset, const char* buffer,\n                             uint32_t length, eos::fst::FileIo* file,\n                             uint64_t file_offset)\n{\n  uint64_t grp_off = (offset / mSizeGroup) * mSizeGroup;\n  uint64_t offset_in_group = offset % mSizeGroup;\n  uint64_t offset_in_block = offset_in_group % mStripeWidth;\n  int indx_block = MapSmallToBig(offset_in_group / mStripeWidth);\n  eos_debug(\"offset=%llu length=%lu, grp_offset=%llu\",\n            offset, length, grp_off);\n  char* ptr {nullptr};\n  {\n    // If an error already happened then there is no point in trying to write\n    if (mHasParityErr) {\n      return false;\n    }\n\n    // Reduce the scope for the eos::fst::RainGroup object to properly account\n    // the number of references and trigger the Recycle procedure.\n    std::shared_ptr<eos::fst::RainGroup> grp = GetGroup(offset);\n\n    // The GetGroup call might block if the parity thread runs into timeouts\n    // and many (currently 32) groups accumulate. Once the parity thread manages\n    // to make progress then a group might be available but there might have been\n    // an error already so there is no point in continuing.\n    if (mHasParityErr) {\n      RecycleGroup(grp);\n      return false;\n    }\n\n    eos::fst::RainGroup& data_blocks = *grp.get();\n    ptr = data_blocks[indx_block].Write(buffer, offset_in_block, length);\n    offset_in_group = (offset + length) % mSizeGroup;\n\n    if (ptr == nullptr) {\n      eos_err(\"msg=\\\"failed to store data in group\\\" off=%llu len=%lu\",\n              offset, length);\n      return false;\n    }\n\n    grp->StoreFuture(file->fileWriteAsync(ptr, file_offset, length));\n  }\n\n  // Group completed - compute and write parity info\n  if (offset_in_group == 0) {\n    if (mHasParityThread) {\n      mQueueGrps.push(grp_off);\n    } else {\n      if (!DoBlockParity(grp_off)) {\n        return false;\n      }\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Compute and write parity blocks to files\n//------------------------------------------------------------------------------\nbool\nRainMetaLayout::DoBlockParity(uint64_t grp_off)\n{\n  bool done = false;\n  eos::common::Timing up(\"parity\");\n  COMMONTIMING(\"Compute-In\", &up);\n  eos_debug(\"msg=\\\"group parity\\\" grp_off=%llu\", grp_off);\n  std::shared_ptr<eos::fst::RainGroup> grp = GetGroup(grp_off);\n  grp->Lock();\n  grp->FillWithZeros();\n\n  // Compute parity blocks\n  if ((done = ComputeParity(grp))) {\n    COMMONTIMING(\"Compute-Out\", &up);\n\n    if (WriteParityToFiles(grp) == SFS_ERROR) {\n      done = false;\n    }\n\n    COMMONTIMING(\"WriteParity\", &up);\n  }\n\n  if (!grp->WaitAsyncOK()) {\n    eos_err(\"msg=\\\"some async operations failed\\\" grp_off=%llu\",\n            grp->GetGroupOffset());\n    done = false;\n  }\n\n  if (!done) {\n    mHasParityErr = true;\n  }\n\n  grp->Unlock();\n  RecycleGroup(grp);\n  //  up.Print();\n  return done;\n}\n\n//------------------------------------------------------------------------------\n// Recover pieces from the whole file. The map contains the original position of\n// the corrupted pieces in the initial file.\n//------------------------------------------------------------------------------\nbool\nRainMetaLayout::RecoverPieces(XrdCl::ChunkList& errs)\n{\n  bool success = true;\n  XrdCl::ChunkList grp_errs;\n\n  while (!errs.empty()) {\n    uint64_t group_off = (errs.begin()->offset / mSizeGroup) * mSizeGroup;\n\n    for (auto chunk = errs.begin(); chunk != errs.end(); /**/) {\n      if ((chunk->offset >= group_off) &&\n          (chunk->offset < group_off + mSizeGroup)) {\n        grp_errs.push_back(*chunk);\n        chunk = errs.erase(chunk);\n      } else {\n        ++chunk;\n      }\n    }\n\n    if (!grp_errs.empty()) {\n      success = success && RecoverPiecesInGroup(grp_errs);\n      grp_errs.clear();\n    } else {\n      eos_warning(\"%s\", \"msg=\\\"no elements, although we saw some before\\\"\");\n    }\n  }\n\n  mDoneRecovery = true;\n  return success;\n}\n\n//------------------------------------------------------------------------------\n// Add a new piece to the map of pieces written to the file\n//------------------------------------------------------------------------------\nvoid\nRainMetaLayout::AddPiece(uint64_t offset, uint32_t length)\n{\n  auto it = mMapPieces.find(offset);\n\n  if (it != mMapPieces.end()) {\n    if (length > it->second) {\n      it->second = length;\n    }\n  } else {\n    mMapPieces.insert(std::make_pair(offset, length));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Merge pieces in the map\n//------------------------------------------------------------------------------\nvoid\nRainMetaLayout::MergePieces()\n{\n  uint64_t off_end;\n  auto it1 = mMapPieces.begin();\n  auto it2 = it1;\n  it2++;\n\n  while (it2 != mMapPieces.end()) {\n    off_end = it1->first + it1->second;\n\n    if (off_end >= it2->first) {\n      if (off_end >= it2->first + it2->second) {\n        mMapPieces.erase(it2++);\n      } else {\n        it1->second += (it2->second - (off_end - it2->first));\n        mMapPieces.erase(it2++);\n      }\n    } else {\n      it1++;\n      it2++;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Read data from the current group for parity computation\n//------------------------------------------------------------------------------\nbool\nRainMetaLayout::ReadGroup(uint64_t grp_off)\n{\n  unsigned int physical_id;\n  uint64_t off_local;\n  bool ret = true;\n  unsigned int id_stripe;\n  int64_t nread = 0;\n  AsyncMetaHandler* phandler = 0;\n  std::shared_ptr<RainGroup> grp = GetGroup(grp_off);\n\n  // Collect all the write the responses and reset all the handlers\n  for (unsigned int i = 0; i < mStripe.size(); i++) {\n    if (mStripe[i]) {\n      phandler = static_cast<AsyncMetaHandler*>(mStripe[i]->fileGetAsyncHandler());\n\n      if (phandler) {\n        if (phandler->WaitOK() != XrdCl::errNone) {\n          eos_err(\"%s\", \"msg=\\\"write failed in previous requests\\\"\");\n          return false;\n        }\n\n        phandler->Reset();\n      }\n    }\n  }\n\n  for (unsigned int i = 0; i < mNbDataBlocks; i++) {\n    id_stripe = i % mNbDataFiles;\n    physical_id = mapLP[id_stripe];\n    off_local = ((grp_off / mSizeLine) + (i / mNbDataFiles)) * mStripeWidth;\n    off_local += mSizeHeader;\n\n    if (mStripe[physical_id]) {\n      // Do read operation - chunk info is not interesting at this point\n      // !!!Here we can only do normal async requests without readahead as this\n      // would lead to corruptions in the parity information computed!!!\n      nread = mStripe[physical_id]->fileReadAsync(off_local,\n              (*grp.get())[MapSmallToBig(i)](),\n              mStripeWidth, mTimeout);\n\n      if (nread != (int64_t)mStripeWidth) {\n        eos_err(\"msg=\\\"failed reading data block\\\" stripe=%u\", id_stripe);\n        ret = false;\n        break;\n      }\n    } else {\n      eos_err(\"msg=\\\"file is null\\\" stripe_id=%u\", id_stripe);\n      ret = false;\n      break;\n    }\n  }\n\n  // Collect read responses only for the data files as we only read from these\n  for (unsigned int i = 0; i < mNbDataFiles; i++) {\n    physical_id = mapLP[i];\n\n    if (mStripe[physical_id]) {\n      phandler = static_cast<AsyncMetaHandler*>\n                 (mStripe[physical_id]->fileGetAsyncHandler());\n\n      if (phandler && (phandler->WaitOK() != XrdCl::errNone)) {\n        eos_err(\"msg=\\\"failed reading blocks\\\" stripe=%u\", i);\n        ret = false;\n      }\n    }\n  }\n\n  return ret;\n}\n\n//------------------------------------------------------------------------------\n// Get a list of the group offsets for which we can compute the parity info\n//------------------------------------------------------------------------------\nvoid\nRainMetaLayout::GetOffsetGroups(std::set<uint64_t>& grps_off, bool forceAll)\n{\n  size_t length;\n  uint64_t offset;\n  uint64_t off_group;\n  uint64_t off_piece_end;\n  bool done_delete;\n  auto it = mMapPieces.begin();\n\n  while (it != mMapPieces.end()) {\n    done_delete = false;\n    offset = it->first;\n    length = it->second;\n    off_piece_end = offset + length;\n    off_group = (offset / mSizeGroup) * mSizeGroup;\n\n    if (forceAll) {\n      mMapPieces.erase(it++);\n\n      while (off_group < off_piece_end) {\n        grps_off.insert(off_group);\n        off_group += mSizeGroup;\n      }\n    } else {\n      if (off_group < offset) {\n        off_group += mSizeGroup;\n      }\n\n      bool once = true;\n      std::pair<uint64_t, uint32_t> elem;\n\n      while ((off_group < off_piece_end) &&\n             (off_group + mSizeGroup <= off_piece_end)) {\n        if (!done_delete) {\n          mMapPieces.erase(it++);\n          done_delete = true;\n        }\n\n        if (once && (off_group > offset)) {\n          once = false;\n          elem = std::make_pair(offset, (off_group - offset));\n        }\n\n        // Save group offset in the list\n        grps_off.insert(off_group);\n        off_group += mSizeGroup;\n      }\n\n      if (!once) {\n        mMapPieces.insert(elem);\n      }\n\n      if (done_delete && (off_group + mSizeGroup > off_piece_end)) {\n        mMapPieces.insert(std::make_pair(off_group, off_piece_end - off_group));\n      }\n\n      if (!done_delete) {\n        it++;\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Compute parity for the non-streaming case and write it to files\n//------------------------------------------------------------------------------\nbool\nRainMetaLayout::SparseParityComputation(bool force)\n{\n  bool done = true;\n  std::set<uint64_t> off_grps;\n\n  if (mMapPieces.empty()) {\n    return false;\n  }\n\n  MergePieces();\n  GetOffsetGroups(off_grps, force);\n\n  for (auto off = off_grps.begin(); off != off_grps.end(); off++) {\n    if (ReadGroup(*off)) {\n      done = DoBlockParity(*off);\n\n      if (!done) {\n        break;\n      }\n    } else {\n      done = false;\n      break;\n    }\n  }\n\n  return done;\n}\n\n//------------------------------------------------------------------------------\n// Sync files to disk\n//------------------------------------------------------------------------------\nint\nRainMetaLayout::Sync()\n{\n  int ret = SFS_OK;\n\n  if (mIsOpen) {\n    // Sync local file\n    if (mStripe[0]) {\n      if (mStripe[0]->fileSync(mTimeout)) {\n        eos_err(\"%s\", \"msg=\\\"local file could not be synced\\\"\");\n        ret = SFS_ERROR;\n      }\n    } else {\n      eos_warning(\"%s\", \"msg=\\\"null local file could not be synced\\\"\");\n    }\n\n    if (mIsEntryServer) {\n      // Sync remote files\n      for (unsigned int i = 1; i < mStripe.size(); i++) {\n        if (mStripe[i]) {\n          if (mStripe[i]->fileSync(mTimeout)) {\n            eos_err(\"msg=\\\"file could not be synced\\\", stripe_id=%u\", i);\n            ret = SFS_ERROR;\n          }\n        } else {\n          eos_warning(\"%s\", \"msg=\\\"null remote file could not be synced\");\n        }\n      }\n    }\n  } else {\n    eos_err(\"%s\", \"msg=\\\"file not opened\\\"\");\n    ret = SFS_ERROR;\n  }\n\n  return ret;\n}\n\n//------------------------------------------------------------------------------\n// Unlink all connected pieces\n//------------------------------------------------------------------------------\nint\nRainMetaLayout::Remove()\n{\n  eos_debug(\"%s\", \"msg=\\\"calling method\\\"\");\n  int ret = SFS_OK;\n\n  if (mIsEntryServer) {\n    // Unlink remote stripes\n    for (unsigned int i = 1; i < mStripe.size(); i++) {\n      if (mStripe[i]) {\n        if (mStripe[i]->fileRemove(mTimeout)) {\n          eos_err(\"msg=\\\"failed to remove remote stripe\\\" stripe_id=%i\", i);\n          ret = SFS_ERROR;\n        }\n      } else {\n        eos_warning(\"%s\", \"msg=\\\"null remote file could not be removed\");\n      }\n    }\n  }\n\n  // Unlink local stripe\n  if (mStripe[0]) {\n    if (mStripe[0]->fileRemove(mTimeout)) {\n      eos_err(\"%s\", \"msg=\\\"failed to remove local stripe\\\"\");\n      ret = SFS_ERROR;\n    }\n  } else {\n    eos_warning(\"%s\", \"msg=\\\"null local file could not be removed\\\"\");\n  }\n\n  return ret;\n}\n\n//------------------------------------------------------------------------------\n// Get stat about file\n//------------------------------------------------------------------------------\nint\nRainMetaLayout::Stat(struct stat* buf)\n{\n  eos_debug(\"%s\", \"msg=\\\"calling method\\\"\");\n  int rc = SFS_OK;\n  bool found = false;\n\n  if (mIsOpen) {\n    if (mIsEntryServer) {\n      for (unsigned int i = 0; i < mStripe.size(); i++) {\n        if (mStripe[i]) {\n          if (mStripe[i]->fileStat(buf, mTimeout) == SFS_OK) {\n            found = true;\n            break;\n          }\n        } else {\n          eos_warning(\"msg=\\\"null file can not be stat\\\" stripe_id=%i\", i);\n        }\n      }\n    } else {\n      if (mStripe[0]) {\n        if (mStripe[0]->fileStat(buf, mTimeout) == SFS_OK) {\n          found = true;\n        }\n      } else {\n        eos_warning(\"%s\", \"msg=\\\"null local file can not be stat\\\"\");\n      }\n    }\n\n    // Obs: when we can not compute the file size, we take it from fmd\n    buf->st_size = mFileSize;\n\n    if (!found) {\n      eos_err(\"msg=\\\"no valid file found for stat\\\" local_path=%\",\n              mLocalPath.c_str());\n      rc = SFS_ERROR;\n    }\n  } else {\n    // When file is not opened this means the info is only used internally\n    // by XRootD. There is no good way to get the real RAIN size without first\n    // opening the stripes. This can happen in the TPC preparation stages.\n    buf->st_size = 0x19deadbeef; // 111110110959 bytes\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Truncate file\n//------------------------------------------------------------------------------\nint\nRainMetaLayout::Truncate(XrdSfsFileOffset offset)\n{\n  int rc = SFS_OK;\n  std::vector<std::future<XrdCl::XRootDStatus>> truncate_futures;\n  uint64_t truncate_offset = GetStripeTruncateOffset(offset);\n  eos_debug(\"msg=\\\"rain truncate\\\" logical_offset=%lli stripe_offset=%zu\",\n            offset, truncate_offset);\n  eos::common::Timing tm(\"truncate\");\n  COMMONTIMING(\"begin\", &tm);\n\n  if (mStripeChecksum && (truncate_offset < mStripeSize)) {\n    mStripeChecksum->Reset();\n    mStripeChecksum->SetDirty();\n  }\n\n  for (unsigned int i = 0; i < mStripe.size(); ++i) {\n    if (!mStripe[i]) {\n      eos_err(\"msg=\\\"failed to truncate null stripe\\\", stripe_id=%u\", i);\n      rc = SFS_ERROR;\n      break;\n    }\n\n    uint64_t tr_offset = offset;\n\n    if (mIsPio || (i == 0)) {\n      tr_offset = truncate_offset;\n    }\n\n    truncate_futures.push_back(mStripe[i]->fileTruncateAsync(tr_offset, mTimeout));\n  }\n\n  COMMONTIMING(\"async_req\", &tm);\n\n  for (unsigned int i = 0; i < truncate_futures.size(); ++i) {\n    if (truncate_futures[i].valid()) {\n      XrdCl::XRootDStatus st = truncate_futures[i].get();\n\n      if (!st.IsOK()) {\n        rc = SFS_ERROR;\n        eos_err(\"msg=\\\"failed truncate stripe\\\" stripe_id=%u err=\\\"%s\\\"\",\n                i, st.GetErrorMessage().c_str());\n      }\n    } else {\n      eos_err(\"msg=\\\"failed truncate stripe\\\" stripe_id=%u\", i);\n      rc = SFS_ERROR;\n    }\n  }\n\n  COMMONTIMING(\"end\", &tm);\n  eos_info(\"msg=\\\"done truncate\\\" %s\", tm.Dump().c_str());\n  // *!!!* Reset the mMaxOffsetWritten from XrdFstOfsFile to logical offset\n  mFileSize = offset;\n  mStripeSize = truncate_offset;\n  mIsTruncated = true;\n\n  if (!mIsPio) {\n    mOfsFile->mMaxOffsetWritten = offset;\n  }\n\n  return rc;\n}\n\nbool RainMetaLayout::PrepareStripeChecksum()\n{\n  if (!mStripeChecksum) {\n    return false;\n  }\n\n  // If the stripe file has been extended (using Truncate())\n  // we need to account this is the checksum calculation.\n  if (mIsTruncated && mStripeChecksum &&\n      mStripeChecksum->GetLastOffset() + mSizeHeader < mStripeSize &&\n      !mStripeChecksum->NeedsRecalculation()) {\n    // If the file has been extended, the extended part is filled\n    // with 0s bytes, meaning that we need to add this extended part\n    // to the stripe checksum calculation.\n    const size_t buff_size = mStripeSize - mStripeChecksum->GetLastOffset() -\n                             mSizeHeader;\n    std::unique_ptr<char[]> buff = std::make_unique<char[]>(buff_size);\n    std::memset(buff.get(), 0, buff_size);\n    AddDataToStripeChecksum(buff.get(), buff_size,\n                            mStripeChecksum->GetLastOffset() + mSizeHeader);\n  }\n\n  if (mStripeChecksum->NeedsRecalculation()) {\n    eos_debug(\"msg=\\\"unit checksum needs recalculation\\\" fxid=%08llx\",\n              mOfsFile->GetFileId());\n    unsigned long long scansize = 0;\n    std::chrono::milliseconds scantime {0};\n\n    if (mStripeChecksum->ScanFile(mOfsFile->GetFstPath().c_str(), scansize,\n                                  scantime, 0, mSizeHeader)) {\n      XrdOucString sizestring;\n      eos_info(\"msg=\\\"rescanned unit checksum\\\" path=%s fxid=%08llx size=%s time=%.02f ms rate=%.02f MB/s %s\",\n               mLocalPath.c_str(), mOfsFile->GetFileId(),\n               eos::common::StringConversion::GetReadableSizeString(\n                 sizestring,\n                 scansize, \"B\"),\n               scantime,\n               1.0 * scansize / 1000 / (scantime.count() ?\n                                        (scantime.count() / 1000) : 99999999999999LL),\n               mStripeChecksum->GetHexChecksum());\n    } else {\n      eos_err(\"msg=\\\"unit checksum rescanning failed\\\" fxid=%08llx\",\n              mOfsFile->GetFileId());\n      mStripeChecksum->Reset();\n      return true;\n    }\n  } else {\n    mStripeChecksum->Finalize();\n    eos_debug(\"msg=\\\"unit checksum finalized\\\" fxid=%08llx stripexs=\\\"%s\\\"\",\n              mOfsFile->GetFileId(), mStripeChecksum->GetHexChecksum());\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Close file\n//------------------------------------------------------------------------------\nint\nRainMetaLayout::Close()\n{\n  XrdSysMutexHelper scope_lock(mExclAccess);\n  eos::common::Timing ct(\"close\");\n  COMMONTIMING(\"start\", &ct);\n  int rc = SFS_OK;\n\n  if (mIsOpen) {\n    if (mIsEntryServer) {\n      if (mForceRecovery || mStoreRecoveryRW) {\n        if (mDoneRecovery || mDoTruncate) {\n          eos_debug(\"%s\", \"msg=\\\"truncating after recovery or at end of write\\\"\");\n          mDoTruncate = false;\n          mDoneRecovery = false;\n\n          if (Truncate(mFileSize)) {\n            eos_err(\"msg=\\\"failed to truncate\\\" off=%llu\", mFileSize);\n            rc = SFS_ERROR;\n          }\n        }\n\n        StopParityThread();\n\n        // Check if we still have to compute parity for the last group of blocks\n        if (mIsStreaming) {\n          if (mHasParityErr) {\n            rc = SFS_ERROR;\n          } else {\n            // Handle any group left to compute the parity information\n            auto lst_grps = GetAllGroupOffsets();\n\n            for (auto grp_off : lst_grps) {\n              if (!DoBlockParity(grp_off)) {\n                eos_err(\"msg=\\\"failed parity computation\\\" grp_off=%llu\",\n                        grp_off);\n                rc = SFS_ERROR;\n              }\n            }\n          }\n        } else {\n          SparseParityComputation(true);\n        }\n\n        // Collect all the write responses and reset all the handlers\n        for (unsigned int i = 0; i < mStripe.size(); i++) {\n          if (mStripe[i]) {\n            AsyncMetaHandler* phandler =\n              static_cast<AsyncMetaHandler*>(mStripe[i]->fileGetAsyncHandler());\n\n            if (phandler) {\n              if (phandler->WaitOK() != XrdCl::errNone) {\n                eos_err(\"%s\", \"msg=\\\"previous async request failed\\\"\");\n                rc = SFS_ERROR;\n              }\n\n              phandler->Reset();\n            }\n          }\n        }\n\n        // Update the header information and write it to all stripes\n        long int num_blocks = ceil((mFileSize * 1.0) / mStripeWidth);\n        size_t size_last_block = mFileSize % mStripeWidth;\n        eos_debug(\"num_blocks=%li size_last_block=%lu\", num_blocks,\n                  size_last_block);\n\n        if (size_last_block == 0) {\n          num_blocks++;\n        }\n\n        bool update_header = false;\n\n        for (unsigned int i = 0; i < mHdrInfo.size(); i++) {\n          if (num_blocks != mHdrInfo[i]->GetNoBlocks()) {\n            mHdrInfo[i]->SetNoBlocks(num_blocks);\n            update_header = true;\n          }\n\n          if (size_last_block != mHdrInfo[i]->GetSizeLastBlock()) {\n            mHdrInfo[i]->SetSizeLastBlock(size_last_block);\n            update_header = true;\n          }\n        }\n\n        COMMONTIMING(\"updateheader\", &ct);\n\n        if (update_header) {\n          for (unsigned int i = 0; i < mStripe.size(); i++) {\n            mHdrInfo[i]->SetIdStripe(mapPL[i]);\n\n            if (mStripe[i]) {\n              if (!mHdrInfo[i]->WriteToFile(mStripe[i].get(), mTimeout)) {\n                eos_err(\"msg=\\\"failed write header\\\" stripe_id=%i\", i);\n                rc =  SFS_ERROR;\n              }\n            } else {\n              eos_warning(\"%s\", \"msg=\\\"failed write header to null file\\\"\");\n            }\n          }\n        }\n      }\n\n      // Close remote files\n      for (unsigned int i = 1; i < mStripe.size(); i++) {\n        if (mStripe[i]) {\n          if (mStripe[i]->fileClose(mTimeout)) {\n            eos_err(\"msg=\\\"failed remote file close\\\" stripe_id=%i\", i);\n            rc = SFS_ERROR;\n          }\n        } else {\n          eos_warning(\"%s\", \"msg=\\\"failed close for null file\\\"\");\n        }\n      }\n    }\n\n    // Close local file\n    if (mStripe[0]) {\n      if (mIsRw) {\n        if (PrepareStripeChecksum()) {\n          eos_err(\"msg=\\\"error verifying stripe checksum\\\"\");\n          rc = SFS_ERROR;\n        } else {\n          if (mStripeChecksum) {\n            const char* stripeChecksum = mStripeChecksum->GetHexChecksum();\n\n            if (!SetStripeChecksum(stripeChecksum)) {\n              eos_err(\"msg=\\\"error setting stripe checksum\\\"\");\n              rc = SFS_ERROR;\n            }\n          }\n        }\n      }\n\n      if (mStripe[0]->fileClose(mTimeout)) {\n        eos_err(\"%s\", \"msg=\\\"failed to close local file\\\"\");\n        rc = SFS_ERROR;\n      }\n    }\n  } else {\n    eos_err(\"%s\", \"msg=\\\"file is not opened\\\"\");\n    rc = SFS_ERROR;\n  }\n\n  mIsOpen = false;\n  return rc;\n}\n\n//----------------------------------------------------------------------------\n// Execute implementation dependant command\n//----------------------------------------------------------------------------\nint\nRainMetaLayout::Fctl(const std::string& cmd, const XrdSecEntity* client)\n{\n  int retc = SFS_OK;\n\n  for (unsigned int i = 0; i < mStripe.size(); ++i) {\n    eos_debug(\"msg=\\\"send fsctl\\\" cmd=\\\"%s\\\" stripe_id=%i\", cmd.c_str(), i);\n\n    if (mStripe[i]) {\n      if (mStripe[i]->fileFctl(cmd, mTimeout)) {\n        eos_err(\"msg=\\\"failed command\\\" cmd=\\\"%s\\\"\", cmd.c_str());\n        retc = SFS_ERROR;\n      }\n    }\n  }\n\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Split read request into requests spanning just one chunk so that each\n// one is read from its corresponding stripe file\n//------------------------------------------------------------------------------\nXrdCl::ChunkList\nRainMetaLayout::SplitRead(uint64_t off, uint32_t len, char* buff)\n{\n  uint32_t sz;\n  uint64_t block_end;\n  char* ptr_data = buff;\n  XrdCl::ChunkList split_read;\n  split_read.reserve((len / mStripeWidth) + 2); // worst case\n\n  while (((off / mStripeWidth) != ((off + len) / mStripeWidth)) || (len)) {\n    block_end = ((off / mStripeWidth) + 1) * mStripeWidth;\n    sz = static_cast<uint32_t>(block_end - off);\n\n    // Deal with last piece case\n    if (sz > len) {\n      sz = len;\n    }\n\n    // Add piece\n    split_read.push_back(XrdCl::ChunkInfo(off, sz, (void*)ptr_data));\n    off += sz;\n    len -= sz;\n    ptr_data += sz;\n  }\n\n  return split_read;\n}\n\n//------------------------------------------------------------------------------\n// Split vector read request into LOCAL request for each of the data stripes\n//------------------------------------------------------------------------------\nstd::vector<XrdCl::ChunkList>\nRainMetaLayout::SplitReadV(XrdCl::ChunkList& chunkList, uint32_t sizeHdr)\n{\n  std::vector<XrdCl::ChunkList> stripe_readv; ///< readV request per stripe files\n  stripe_readv.reserve(mNbDataFiles);\n\n  for (unsigned int i = 0; i < mNbDataFiles; ++i) {\n    stripe_readv.push_back(XrdCl::ChunkList());\n  }\n\n  // Split any pieces spanning more than one chunk\n  for (auto chunk = chunkList.begin(); chunk != chunkList.end(); ++chunk) {\n    std::vector<XrdCl::ChunkInfo> split_read = SplitRead(chunk->offset,\n        chunk->length,\n        static_cast<char*>(chunk->buffer));\n\n    // Split each readV request to the corresponding stripe file from which we\n    // need to read it, adjusting the relative offset inside the stripe file\n    for (auto iter = split_read.begin(); iter != split_read.end(); ++iter) {\n      auto pos = GetLocalOff(iter->offset);\n      iter->offset = pos.second + sizeHdr;\n      stripe_readv[pos.first].push_back(*iter);\n    }\n  }\n\n  return stripe_readv;\n}\n\n//------------------------------------------------------------------------------\n// Get group corresponding to the given offset or create one if it doesn't\n// exist. Also if there are already mMaxGroups in the map this will block\n// waiting for a slot to be freed.\n//------------------------------------------------------------------------------\nstd::shared_ptr<eos::fst::RainGroup>\nRainMetaLayout::GetGroup(uint64_t offset)\n{\n  uint64_t grp_off = (offset / mSizeGroup) * mSizeGroup;\n  std::unique_lock<std::mutex> lock(mMutexGroups);\n  // if the group exists already, we don't care about mMaxGroups\n  auto it = mMapGroups.find(grp_off);\n\n  if (it != mMapGroups.end()) {\n    return it->second;\n  }\n\n  if (mMapGroups.size() > mMaxGroups) {\n    eos_info(\"msg=\\\"waiting for available slot group\\\" file=\\\"%s\\\"\",\n             mLocalPath.c_str());\n    mCvGroups.wait(lock, [&]() {\n      return (mMapGroups.size() < mMaxGroups);\n    });\n  }\n\n  std::shared_ptr<eos::fst::RainGroup> grp\n  (new eos::fst::RainGroup(grp_off, mNbTotalBlocks, mStripeWidth));\n  auto pair = mMapGroups.emplace(grp_off, grp);\n  return (pair.first)->second;\n}\n\n//------------------------------------------------------------------------------\n// Get a list of all the groups in the map\n//------------------------------------------------------------------------------\nstd::list<uint64_t>\nRainMetaLayout::GetAllGroupOffsets() const\n{\n  std::list<uint64_t> lst;\n  std::unique_lock<std::mutex> lock(mMutexGroups);\n\n  for (auto& elem : mMapGroups) {\n    lst.push_back(elem.first);\n  }\n\n  return lst;\n}\n\n//------------------------------------------------------------------------------\n// Recycle given group by removing the group object from the map if there are\n// no more references to it. It will eventually be deleted and the RainBlocks\n// will also be recycled.\n//------------------------------------------------------------------------------\nvoid\nRainMetaLayout::RecycleGroup(std::shared_ptr<eos::fst::RainGroup>& group)\n{\n  {\n    std::unique_lock<std::mutex> lock(mMutexGroups);\n\n    if (group.use_count() > 2) {\n      eos_info(\"msg=\\\"skip group recycle\\\" grp_off=%llu\",\n               group->GetGroupOffset());\n      return;\n    }\n\n    auto it = mMapGroups.find(group->GetGroupOffset());\n\n    if (it == mMapGroups.end()) {\n      eos_crit(\"msg=\\\"trying to recycle a group which does not \"\n               \"exist in the map\\\" grp_off=%llu\", group->GetGroupOffset());\n      return;\n    } else {\n      eos_debug(\"msg=\\\"do group recycle\\\" grp_off=%llu\",\n                group->GetGroupOffset());\n      mMapGroups.erase(it);\n    }\n  }\n  mCvGroups.notify_all();\n}\n\n//------------------------------------------------------------------------------\n// Thread handling parity information\n//------------------------------------------------------------------------------\nvoid\nRainMetaLayout::StartParityThread(ThreadAssistant& assistant) noexcept\n{\n  uint64_t grp_off = 0ull;\n\n  while (true) {\n    mQueueGrps.wait_pop(grp_off);\n\n    if (grp_off == std::numeric_limits<unsigned long long>::max()) {\n      eos_info(\"%s\", \"msg=\\\"parity thread exiting\\\"\");\n      break;\n    }\n\n    if (!DoBlockParity(grp_off)) {\n      eos_err(\"msg=\\\"failed parity computation\\\" grp_off=%llu\", grp_off);\n      break;\n    } else {\n      eos_debug(\"msg=\\\"successful parity computation\\\" grp_off=%llu\", grp_off);\n    }\n  }\n\n  // Make sure all pending groups are released to avoid any deadlock with\n  // a pending write that requires a group\n  while (mQueueGrps.try_pop(grp_off)) {\n    std::shared_ptr<eos::fst::RainGroup> grp = GetGroup(grp_off);\n    RecycleGroup(grp);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Stop parity thread\n//------------------------------------------------------------------------------\nvoid\nRainMetaLayout::StopParityThread()\n{\n  uint64_t sentinel = std::numeric_limits<unsigned long long>::max();\n  mQueueGrps.push(sentinel);\n  mParityThread.join();\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/layout/RainMetaLayout.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file RainMetaLayout.hh\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Generic class to read/write RAID-like layout files using a gateway\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"fst/layout/Layout.hh\"\n#include \"fst/layout/RainGroup.hh\"\n#include \"fst/checksum/Adler.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/ConcurrentQueue.hh\"\n#include <vector>\n#include <string>\n#include <list>\n\nclass XrdFstOfsFile;\n\nEOSFSTNAMESPACE_BEGIN\n\nclass HeaderCRC;\n\n//------------------------------------------------------------------------------\n//! Generic class to read/write different RAID-like layout files\n//------------------------------------------------------------------------------\nclass RainMetaLayout : public Layout\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param file handler to current file\n  //! @param lid layout id\n  //! @param client security information\n  //! @param outError error information\n  //! @param io access type\n  //! @param timeout timeout value\n  //! @param force_recovery force writing back the recovered blocks to the files\n  //! @param targetSize initial file size\n  //! @param bookingOpaque opaque information\n  //! @param fmdHandler fmd handler obj\n  //! @param computeStripeChecksum true if stripe checksum needs to be computed\n  //!                              synchronously (default is false)\n  //----------------------------------------------------------------------------\n  RainMetaLayout(XrdFstOfsFile* file, unsigned long lid,\n                 const XrdSecEntity* client, XrdOucErrInfo* outError,\n                 const char* path, uint16_t timeout, bool force_recovery,\n                 off_t targetSize, std::string bookingOpaque,\n                 eos::fst::FmdHandler* fmdHandler, bool computeStripeChecksum = false);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~RainMetaLayout();\n\n  //--------------------------------------------------------------------------\n  //! Redirect to new target\n  //--------------------------------------------------------------------------\n  virtual void Redirect(const char*);\n\n  //--------------------------------------------------------------------------\n  //! Open file using a gateway\n  //!\n  //! @param flags flags O_RDWR/O_RDONLY/O_WRONLY\n  //! @param mode creation permissions\n  //! @param opaque opaque information\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Open(XrdSfsFileOpenMode flags, mode_t mode, const char* opaque)\n  override;\n\n  //----------------------------------------------------------------------------\n  //! Open file using parallel IO\n  //!\n  //! @param stripeUrls map of replica index to stripeUrl\n  //! @param flags flags O_RDWR/O_RDONLY/O_WRONLY\n  //! @param mode creation permissions\n  //! @param opaque opaque information\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int OpenPio(const std::vector<std::pair<int, std::string>>& stripeUrls,\n                      XrdSfsFileOpenMode flags, mode_t mode = 0,\n                      const char* opaque = \"fst.pio\");\n\n  //----------------------------------------------------------------------------\n  //! Open file using parallel IO - helper\n  //!\n  //  @param stripe_urls vector of stripe URLs for open\n  //! @param flags flags O_RDWR/O_RDONLY/O_WRONLY\n  //! @param mode creation permissions\n  //! @param opaque opaque information\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int OpenPio(const std::vector<std::string>& stripeUrls,\n                      XrdSfsFileOpenMode flags, mode_t mode = 0,\n                      const char* opaque = \"fst.pio\");\n\n  //----------------------------------------------------------------------------\n  //! Read from file\n  //!\n  //! @param offset offset\n  //! @param buffer place to hold the read data\n  //! @param length length\n  //! @param readahead not used!\n  //!\n  //! @return number of bytes read or -1 if error\n  //--------------------------------------------------------------------------\n  virtual int64_t Read(XrdSfsFileOffset offset, char* buffer,\n                       XrdSfsXferSize length, bool readahead = false) override;\n\n  //----------------------------------------------------------------------------\n  //! Read from stripe - offset and length are relative to the given stripe\n  //!\n  //! @param offset offset\n  //! @param buffer place to hold the read data\n  //! @param length length\n  //! @param stripeIdx idx of the stripe\n  //!\n  //! @return number of bytes read or -1 if error\n  //--------------------------------------------------------------------------\n  int64_t ReadStripe(XrdSfsFileOffset offset, char* buffer,\n                     XrdSfsXferSize length, int stripeIdx);\n\n  //----------------------------------------------------------------------------\n  //! Vector read\n  //!\n  //! @param chunkList list of chunks for the vector read\n  //! @param len total length of the vector read\n  //!\n  //! @return number of bytes read of -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t ReadV(XrdCl::ChunkList& chunkList, uint32_t len) override;\n\n  //----------------------------------------------------------------------------\n  //! Write to file\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //!\n  //! @return number of bytes written or -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t Write(XrdSfsFileOffset offset, const char* buffer,\n                        XrdSfsXferSize length) override;\n\n  //----------------------------------------------------------------------------\n  //! Truncate\n  //!\n  //! @param offset truncate file to this value\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Truncate(XrdSfsFileOffset offset);\n\n  //----------------------------------------------------------------------------\n  //! Allocate file space\n  //!\n  //! @param length space to be allocated\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Fallocate(XrdSfsFileOffset lenght) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Deallocate file space\n  //!\n  //! @param fromOffset offset start\n  //! @param toOffset offset end\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Fdeallocate(XrdSfsFileOffset fromOffset,\n                          XrdSfsFileOffset toOffset) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Execute implementation dependant command\n  //!\n  //! @param cmd command\n  //! @param client client identity\n  //!\n  //! @return 0 if successful, -1 otherwise\n  //----------------------------------------------------------------------------\n  virtual int Fctl(const std::string& cmd, const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Remove file\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Remove();\n\n  //----------------------------------------------------------------------------\n  //! Sync file to disk\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Sync();\n\n  //----------------------------------------------------------------------------\n  //! Close file\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Close();\n\n  //----------------------------------------------------------------------------\n  //! Get stats about the file\n  //!\n  //! @param buf stat buffer\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Stat(struct stat* buf);\n\n  //--------------------------------------------------------------------------\n  //! Get last error message\n  //--------------------------------------------------------------------------\n  inline const std::string&\n  GetLastErrMsg()\n  {\n    return mLastErrMsg;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Split vector read request into requests for each of the data stripes with\n  //! the offset and length of the new chunks adjusted to the LOCAL file stripe\n  //!\n  //! @param chunkList list of chunks to read from the whole file\n  //! @param sizeHdr header size for local file which needs to be added to the\n  //!        final local offset value\n  //!\n  //! @return vector of ChunkInfo structures containing the readv requests\n  //!         corresponding to each of the stripe files making up the original\n  //!         file.\n  //----------------------------------------------------------------------------\n  std::vector<XrdCl::ChunkList> SplitReadV(XrdCl::ChunkList& chunkList,\n      uint32_t sizeHdr = 0);\n\nprotected:\n  bool mIsRw; ///< mark for writing\n  bool mIsOpen; ///< mark if open\n  bool mIsPio; ///< mark if opened for parallel IO access\n  bool mDoTruncate; ///< mark if there is a need to truncate\n  bool mDoneRecovery; ///< mark if recovery done\n  bool mIsStreaming; ///< file is written in streaming mode\n  //! Set if recovery also triggers writing back to the files, this also means\n  //! that all files must be available\n  bool mForceRecovery;\n  //! Store recovery flag due to file begin opened in RW mode\n  bool mStoreRecoveryRW;\n  bool mComputeStripeChecksum;\n  int mStripeHead; ///< head stripe value\n  int mPhysicalStripeIndex; ///< physical index of the current stripe\n  unsigned int mNbParityFiles; ///< number of parity files\n  unsigned int mNbDataFiles; ///< number of data files\n  unsigned int mNbTotalFiles; ///< total number of files ( data + parity )\n  unsigned int mNbDataBlocks; ///< no. data blocks in a group\n  unsigned int mNbTotalBlocks; ///< no. data and parity blocks in a group\n  uint64_t mLastWriteOffset; ///< offset of the last write request\n  uint64_t mStripeSize; ///< current size of the stripe (header + data)\n  uint64_t mStripeWidth; ///< stripe width\n  uint64_t mSizeHeader; ///< size of header = 4KB\n  uint64_t mFileSize; ///< total size of current file\n  //! Size of a line in a group\n  uint64_t mSizeLine;\n  //! Size of a group of blockseg. RAIDDP: group = noDataStr^2 blocks\n  uint64_t mSizeGroup;\n  std::vector<std::unique_ptr<FileIo>>\n                                    mStripe; ///< file IO layout obj for each stripe\n  std::vector<HeaderCRC*> mHdrInfo; ///< headers of the stripe files\n  std::map<unsigned int, unsigned int> mapLP; ///< map of url to stripes\n  std::map<unsigned int, unsigned int> mapPL; ///< map of stripes to url\n  ///< Map of pieces written for which parity has not been done yet\n  std::map<uint64_t, uint32_t> mMapPieces;\n  std::string mLastErrMsg; ///< last error messages seen\n  uint8_t mMaxGroups {32};\n  mutable std::mutex mMutexGroups;\n  std::condition_variable mCvGroups;\n  std::map<uint64_t, std::shared_ptr<eos::fst::RainGroup>> mMapGroups;\n  std::unique_ptr<eos::fst::Adler> mStripeChecksum; //< Checksum of the stripe\n\n  //----------------------------------------------------------------------------\n  //! Get group corresponding to the given offset or create one if it doesn't\n  //! exist. Also if there are already mMaxGroups in the map this will block\n  //! waiting for a slot to be freed.\n  //!\n  //! @param offset given offset\n  //----------------------------------------------------------------------------\n  std::shared_ptr<eos::fst::RainGroup> GetGroup(uint64_t offset);\n\n  //----------------------------------------------------------------------------\n  //! Get a list of all the groups in the map\n  //----------------------------------------------------------------------------\n  std::list<uint64_t> GetAllGroupOffsets() const;\n\n  //----------------------------------------------------------------------------\n  //! Add new data block to the current group for parity computation. The pice\n  //! must already be aligned so that it fits in one block of the group. This\n  //! is specially used when writing in streaming mode.\n  //!\n  //! @param offset offset of the block added\n  //! @param buffer data contained in the block\n  //! @param length length of the data\n  //! @param file file where this piece should be written\n  //! @param file_off offset in file\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool AddDataBlock(uint64_t offset, const char* buffer, uint32_t length,\n                    eos::fst::FileIo* file, uint64_t file_offset);\n\n  //----------------------------------------------------------------------------\n  //! Recycle given group by destroying the group object if there are no more\n  //! reference to it.\n  //!\n  //! @param group shared object referring to the group\n  //----------------------------------------------------------------------------\n  void RecycleGroup(std::shared_ptr<eos::fst::RainGroup>& group);\n\n  //----------------------------------------------------------------------------\n  //! Test and recover any corrupted headers in the stripe files\n  //----------------------------------------------------------------------------\n  virtual bool ValidateHeader();\n\n  //----------------------------------------------------------------------------\n  //! Recover corrupted chunks from the whole file\n  //!\n  //! @param errs list of chunks for which recovery is to be done\n  //!\n  //! @return true if recovery successful, false otherwise\n  //----------------------------------------------------------------------------\n  virtual bool RecoverPieces(XrdCl::ChunkList& errs);\n\n  //----------------------------------------------------------------------------\n  //! Compute and write parity blocks corresponding to a group of blocks\n  //!\n  //! @param grp_off group offset\n  //!\n  //! @return true if successfully computed the parity and wrote it to the\n  //!         corresponding files, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool DoBlockParity(uint64_t grp_off);\n\n  //----------------------------------------------------------------------------\n  //! Recover corrupted chunks from the current group\n  //!\n  //! @param grp_errs chunks to be recovered\n  //!\n  //! @return true if recovery successful, false otherwise\n  //----------------------------------------------------------------------------\n  virtual bool RecoverPiecesInGroup(XrdCl::ChunkList& grp_errs) = 0;\n\n  //------------------------------------------------------------------------------\n  //! Compute error correction blocks\n  //!\n  //! @param grp group object for parity computation\n  //!\n  //! @return true if parity info computed successfully, otherwise false\n  //------------------------------------------------------------------------------\n  virtual bool ComputeParity(std::shared_ptr<eos::fst::RainGroup>& grp) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Write parity information corresponding to a group to files\n  //!\n  //! @param grp group object\n  //!\n  //! @return 0 if successful, otherwise error\n  //----------------------------------------------------------------------------\n  virtual int WriteParityToFiles(std::shared_ptr<eos::fst::RainGroup>& grp) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Map index from mNbDataBlocks representation to mNbTotalBlocks\n  //!\n  //! @param idSmall with values between 0 and 15, for exmaple in RAID-DP\n  //!\n  //! @return index with values between 0 and 23, -1 if error\n  //----------------------------------------------------------------------------\n  virtual unsigned int MapSmallToBig(unsigned int idSmall) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Non-streaming operation\n  //! Compute parity for the non-streaming case and write it to files\n  //!\n  //! @param force if true force parity computation of incomplete groups,\n  //!              this means that parity will be computed even if there are\n  //!              still some pieces missing - this is useful at the end of\n  //!              a write operation when closing the file\n  //!\n  //! @return true if successful, otherwise error\n  //----------------------------------------------------------------------------\n  bool SparseParityComputation(bool force);\n\n  //----------------------------------------------------------------------------\n  //! Get truncate offset for stripe\n  //!\n  //! @param offset logical file truncate offset\n  //!\n  //! @return local stripe truncate offset\n  //----------------------------------------------------------------------------\n  virtual uint64_t GetStripeTruncateOffset(uint64_t offset) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Convert a global offset (from the inital file) to a local offset within\n  //! a stripe data file. The initial block does *NOT* span multiple chunks\n  //! (stripes) therefore if the original length is bigger than one chunk the\n  //! splitting must be done before calling this method.\n  //!\n  //! @param global_off initial offset\n  //!\n  //! @return tuple made up of the logical index of the stripe data file the\n  //!         piece belongs to and the local offset within that file.\n  //----------------------------------------------------------------------------\n  virtual std::pair<int, uint64_t> GetLocalOff(uint64_t global_off) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Convert a local position (from a stripe data file) to a global position\n  //! within the initial file file. Note that the local offset has to come\n  //! from a stripe data file since there is no corresponde in the original\n  //! file for a piece which is in the parity stripe.\n  //!\n  //! @param stripe_id logical stripe index\n  //! @param local_off local offset\n  //!\n  //! @return offset in the initial file of the local given piece\n  //----------------------------------------------------------------------------\n  virtual uint64_t GetGlobalOff(int stripe_id, uint64_t local_off) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Add the data contained in the buffer to the stripe checksum.\n  //! The function takes in consideration the fact that the buffer might\n  //! contain the header, that is then skipped and not added to the\n  //! stripe checksum.\n  //!\n  //! @param buffer buffer containing the data\n  //! @param size size of the buffer\n  //! @param file_offset offset in the file\n  //----------------------------------------------------------------------------\n  void AddDataToStripeChecksum(char* buffer, size_t size, size_t file_offset);\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Disable copy/move assign/constructor operators\n  //----------------------------------------------------------------------------\n  RainMetaLayout& operator = (const RainMetaLayout&) = delete;\n  RainMetaLayout(const RainMetaLayout&) = delete;\n  RainMetaLayout& operator = (RainMetaLayout&&) = delete;\n  RainMetaLayout(RainMetaLayout&&) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Start thread handling parity information\n  //----------------------------------------------------------------------------\n  void StartParityThread(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Stop parity thread\n  //----------------------------------------------------------------------------\n  void StopParityThread();\n\n  //----------------------------------------------------------------------------\n  //! Non-streaming operation\n  //! Add a new piece to the map of pieces written to the file\n  //!\n  //! @param offset offset of the new piece added\n  //! @param length length of the new piece added\n  //----------------------------------------------------------------------------\n  void AddPiece(uint64_t offset, uint32_t length);\n\n  //----------------------------------------------------------------------------\n  //! Non-streaming operation\n  //! Merge in place the pieces from the map\n  //----------------------------------------------------------------------------\n  void MergePieces();\n\n  //----------------------------------------------------------------------------\n  //! Non-streaming operation\n  //! Get a list of the group offsets for which we can compute the parity info\n  //!\n  //! @param offsetGroups set of group offsets\n  //! @param forceAll if true return also offsets of incomplete groups\n  //----------------------------------------------------------------------------\n  void GetOffsetGroups(std::set<uint64_t>& offsetGroups, bool forceAll);\n\n  //----------------------------------------------------------------------------\n  //! Non-streaming operation\n  //! Read data from the current group for parity computation\n  //!\n  //! @param offsetGroup offset of the group about to be read\n  //!\n  //! @return true if operation successful, otherwise error\n  //----------------------------------------------------------------------------\n  bool ReadGroup(uint64_t offsetGroup);\n\n  //----------------------------------------------------------------------------\n  //! Split read request into requests spanning just one chunk so that each\n  //! one is read from its corresponding stripe file. The offset values are\n  //! GLOBAL i.e. they are relative to their position in the original file\n  //!\n  //! @param off read offset\n  //! @param len read length\n  //! @param buff buffer hoding the read data\n  //!\n  //! @return vector of ChunkInfo structures containing the read requests\n  //!         corresponding to each of the chunks making up the original file\n  //----------------------------------------------------------------------------\n  XrdCl::ChunkList SplitRead(uint64_t off, uint32_t len, char* buff);\n\n  //----------------------------------------------------------------------------\n  //! Perform basic layout checks\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool BasicLayoutChecks();\n\n  //----------------------------------------------------------------------------\n  //! Read operation that triggers a forced recovery\n  //!\n  //! @param offset read offset\n  //! @param buffer read buffer\n  //! @param length read length\n  //!\n  //! @return number of bytes read or -1 if error\n  //----------------------------------------------------------------------------\n  int64_t ReadForceRecovery(XrdSfsFileOffset offset, char* buffer,\n                            XrdSfsXferSize length);\n\n  //----------------------------------------------------------------------------\n  //! Prepare stripe checksum\n  //!\n  //! @return true if ok, otherwise false\n  //----------------------------------------------------------------------------\n  bool PrepareStripeChecksum();\n\n  //----------------------------------------------------------------------------\n  //! Get stripe checksum value\n  //!\n  //! @return checksum value in hex if it exists\n  //----------------------------------------------------------------------------\n  std::optional<std::string> GetStripeChecksum();\n\n  //----------------------------------------------------------------------------\n  //! Set stripe checksum value in the FMD object attached to this physical file\n  //!\n  //! @param checkumHex checksum in hex\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool SetStripeChecksum(std::string checksumHex);\n\n  AssistedThread mParityThread; ///< Thread computing and wrintg parity\n  //! Queue holding group offsets to be used for parity computation\n  eos::common::ConcurrentQueue<uint64_t> mQueueGrps;\n  std::atomic<bool> mHasParityErr {false};\n  std::atomic<bool> mHasParityThread {false};\n  //! Set of groups already recovered or being processed\n  std::set<uint64_t> mRecoveredGrpIndx;\n  //! Mutex protecting the set of recovered groups\n  std::mutex mMtxRecoveredGrps;\n  bool mIsTruncated; ///< flag indicating that the file has been truncated\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/layout/ReedSLayout.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ReedSLayout.cc\n// Author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <cmath>\n#include <map>\n#include <set>\n#include <algorithm>\n#include \"common/Timing.hh\"\n#include \"fst/layout/ReedSLayout.hh\"\n#include \"fst/io/AsyncMetaHandler.hh\"\n#include \"fst/layout/jerasure/include/jerasure.h\"\n#include \"fst/layout/jerasure/include/reed_sol.h\"\n#include \"fst/layout/jerasure/include/galois.h\"\n#include \"fst/layout/jerasure/include/cauchy.h\"\n#include \"fst/layout/jerasure/include/liberation.h\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nReedSLayout::ReedSLayout(XrdFstOfsFile* file,\n                         unsigned long lid,\n                         const XrdSecEntity* client,\n                         XrdOucErrInfo* outError,\n                         const char* path,\n                         eos::fst::FmdHandler* fmdHandler,\n                         uint16_t timeout,\n                         bool storeRecovery,\n                         off_t targetSize,\n                         std::string bookingOpaque,\n                         bool computeStripeChecksum) :\n  RainMetaLayout(file, lid, client, outError, path, timeout, storeRecovery,\n                 targetSize, bookingOpaque, fmdHandler, computeStripeChecksum),\n  mPacketSize(0), matrix(0), bitmatrix(0), schedule(0)\n{\n  mNbDataBlocks = mNbDataFiles;\n  mNbTotalBlocks = mNbDataFiles + mNbParityFiles;\n  mSizeGroup = mNbDataFiles * mStripeWidth;\n  mSizeLine = mSizeGroup;\n  // Basic checks to make sure Jerasure can be properly initialized\n  if (mNbTotalFiles < 5) {\n    eos_err(\"msg=\\\"ReedSLayout stripe number must be at least 5\\\" \"\n            \"stripe_size=%u\",\n            mNbTotalFiles);\n    throw std::runtime_error(\"Jerasure stripe number too low\");\n  }\n\n  if (mStripeWidth < 64) {\n    eos_err(\"msg=\\\"ReedSLayout stripe width must be at least 64\\\" \"\n            \"stripe_width=%llu\",\n            mStripeWidth);\n    throw std::runtime_error(\"Jerasure stripe size too low\");\n  }\n\n  // Set the parameters for the Jerasure codes\n  w = 8;      // \"word size\" this can be adjusted between 4..32\n  InitialiseJerasure();\n}\n\n//------------------------------------------------------------------------------\n// Initialise the Jerasure data structures\n//------------------------------------------------------------------------------\nvoid\nReedSLayout::InitialiseJerasure()\n{\n  if (mDoneInit) {\n    return;\n  }\n\n  // Initialise Jerasure data structures\n  static std::mutex jerasure_init_mutex;\n  std::lock_guard<std::mutex> lock(jerasure_init_mutex);\n\n  // Avoid any possible race condition\n  if (mDoneInit) {\n    return;\n  }\n\n  mDoneInit = true;\n  mPacketSize = mSizeLine / (mNbDataBlocks * w * sizeof(int));\n  eos_debug(\"mStripeWidth=%zu, mSizeLine=%zu, mNbDataBlocks=%u, mNbParityFiles=%u,\"\n            \" w=%u, mPacketSize=%u\", mStripeWidth, mSizeLine, mNbDataBlocks,\n            mNbParityFiles, w, mPacketSize);\n\n  if (mSizeLine % mPacketSize != 0) {\n    eos_crit(\"%s\", \"msg=\\\"packet size could not be computed correctly\\\"\");\n    throw std::runtime_error(\"Jerasure initialization failed\");\n  }\n\n  matrix = cauchy_good_general_coding_matrix(mNbDataBlocks, mNbParityFiles, w);\n  bitmatrix = jerasure_matrix_to_bitmatrix(mNbDataBlocks, mNbParityFiles, w,\n              matrix);\n  schedule = jerasure_smart_bitmatrix_to_schedule(mNbDataBlocks, mNbParityFiles,\n             w, bitmatrix);\n\n  if ((matrix == nullptr) || (bitmatrix == nullptr) ||\n      (schedule == nullptr)) {\n    eos_crit(\"%s\", \"msg=\\\"Jerasure initialization failed\\\"\");\n    throw std::runtime_error(\"Jerasure initialization failed\");\n  }\n}\n\n//------------------------------------------------------------------------------\n// Deallocated any Jerasure structures used for encoding and decoding\n//------------------------------------------------------------------------------\nvoid\nReedSLayout::FreeJerasure()\n{\n  if (!mDoneInit) {\n    return;\n  }\n\n  /*\n   * jerasure allocates some internal data structures for caching\n   * fields. It will allocate one for w, and if we do anything that\n   * needs to xor a region >= 16 bytes, it will also allocate one\n   * for 32. Fortunately we can safely uninit any value; if it\n   * wasn't inited it will be ignored.\n   */\n  free(matrix);\n  free(bitmatrix);\n  matrix = bitmatrix = nullptr;\n  // NOTE, based on an inspection of the jerasure code used to build the\n  // the schedule array, it appears that the sentinal used to signal the end\n  // of the array is a value of -1 in the first int field in the dereferenced\n  // value. We use this to determine when to stop free-ing elements. See the\n  // jerasure_smart_bitmatrix_to_schedule and\n  // jerasure_dumb_bitmatrix_to_schedule functions in jerasure.c for the\n  // details.\n  int i = 0;\n  bool end_of_array = false;\n\n  if (schedule != NULL) {\n    while (!end_of_array) {\n      if (schedule[i] == NULL || schedule[i][0] == -1) {\n        end_of_array = true;\n      }\n\n      free(schedule[i]);\n      i++;\n    }\n  }\n\n  free(schedule);\n  schedule = nullptr;\n}\n\n//------------------------------------------------------------------------------\n// Compute the error correction blocks\n//------------------------------------------------------------------------------\nbool\nReedSLayout::ComputeParity(std::shared_ptr<eos::fst::RainGroup>& grp)\n{\n  InitialiseJerasure();\n  // Get pointers to data and parity informatio\n  char* data[mNbDataFiles];\n  char* coding[mNbParityFiles];\n  eos::fst::RainGroup& data_blocks = *grp.get();\n\n  for (unsigned int i = 0; i < mNbDataFiles; ++i) {\n    data[i] = data_blocks[i]();\n  }\n\n  for (unsigned int i = 0; i < mNbParityFiles; ++i) {\n    coding[i] = data_blocks[mNbDataFiles + i]();\n  }\n\n  // Encode the blocks\n  jerasure_schedule_encode(mNbDataBlocks, mNbParityFiles, w, schedule, data,\n                           coding, mStripeWidth, mPacketSize);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Recover corrupted pieces in the current group, all errors in the map\n// belonging to the same group\n//------------------------------------------------------------------------------\nbool\nReedSLayout::RecoverPiecesInGroup(XrdCl::ChunkList& grp_errs)\n{\n  InitialiseJerasure();\n  bool ret = true;\n  int64_t nread = 0;\n  int64_t nwrite = 0;\n  unsigned int physical_id;\n  // Use \"set\" as we might add the same stripe index twice as a result of an early\n  // error detected when sending the request and by the async handler\n  std::set<unsigned int> invalid_ids;\n  uint64_t offset = grp_errs.begin()->offset;\n  uint64_t offset_local = (offset / mSizeGroup) * mStripeWidth;\n  uint64_t offset_group = (offset / mSizeGroup) * mSizeGroup;\n  std::shared_ptr<eos::fst::RainGroup> grp = GetGroup(offset_group);\n  eos::fst::RainGroup& data_blocks = *grp.get();\n  AsyncMetaHandler* phandler = 0;\n  offset_local += mSizeHeader;\n\n  for (unsigned int i = 0; i < mNbTotalFiles; i++) {\n    physical_id = mapLP[i];\n\n    // Read data from stripe\n    if (mStripe[physical_id]) {\n      phandler = static_cast<AsyncMetaHandler*>\n                 (mStripe[physical_id]->fileGetAsyncHandler());\n\n      if (phandler) {\n        phandler->Reset();\n      }\n\n      // Enable readahead\n      nread = mStripe[physical_id]->fileReadPrefetch(offset_local, data_blocks[i](),\n              mStripeWidth, mTimeout);\n\n      if (nread != (int64_t)mStripeWidth) {\n        eos_debug(\"msg=\\\"read block corrupted\\\" stripe=%u\", i);\n        invalid_ids.insert(i);\n      }\n    } else {\n      // File not opened, register it as an error\n      invalid_ids.insert(i);\n    }\n  }\n\n  // Wait for read responses and mark corrupted blocks\n  for (unsigned int i = 0; i < mStripe.size(); i++) {\n    physical_id = mapLP[i];\n\n    if (mStripe[physical_id]) {\n      phandler = static_cast<AsyncMetaHandler*>\n                 (mStripe[physical_id]->fileGetAsyncHandler());\n\n      if (phandler) {\n        uint16_t error_type = phandler->WaitOK();\n\n        if (error_type != XrdCl::errNone) {\n          std::pair< uint16_t, std::map<uint64_t, uint32_t> > pair_err;\n          eos_debug(\"msg=\\\"remote block corrupted\\\" id=%u\", i);\n          invalid_ids.insert(i);\n\n          if (error_type == XrdCl::errOperationExpired) {\n            mStripe[physical_id]->fileClose(mTimeout);\n            mStripe[physical_id] = nullptr;\n          }\n        }\n      }\n    }\n  }\n\n  if (invalid_ids.size() == 0) {\n    RecycleGroup(grp);\n    return true;\n  } else if (invalid_ids.size() > mNbParityFiles) {\n    eos_err(\"msg=\\\"more blocks corrupted than the maximum number \"\n            \"supported\\\" parity=%d corrupted=%d\", mNbParityFiles,\n            invalid_ids.size());\n    RecycleGroup(grp);\n    return false;\n  }\n\n  // Get pointers to data and parity information\n  char* coding[mNbParityFiles];\n  char* data[mNbDataFiles];\n\n  for (unsigned int i = 0; i < mNbDataFiles; i++) {\n    data[i] = data_blocks[i]();\n  }\n\n  for (unsigned int i = 0; i < mNbParityFiles; i++) {\n    coding[i] = data_blocks[mNbDataFiles + i]();\n  }\n\n  // Array of ids of erased pieces (corrupted)\n  int* erasures = new int[invalid_ids.size() + 1];\n  int index = 0;\n\n  for (auto iter = invalid_ids.begin();\n       iter != invalid_ids.end(); ++iter, ++index) {\n    erasures[index] = *iter;\n  }\n\n  erasures[invalid_ids.size()] = -1;\n  // ******* DECODE ******\n  int decode = jerasure_schedule_decode_lazy(mNbDataBlocks, mNbParityFiles, w,\n               bitmatrix, erasures, data, coding,\n               mStripeWidth, mPacketSize, 1);\n  // Free memory\n  delete[] erasures;\n\n  if (decode == -1) {\n    eos_err(\"msg=\\\"decoding was unsuccessful\\\"\");\n    RecycleGroup(grp);\n    return false;\n  }\n\n  // Update the files in which we found invalid blocks\n  unsigned int stripe_id;\n\n  for (auto iter = invalid_ids.begin(); iter != invalid_ids.end(); ++iter) {\n    stripe_id = *iter;\n    physical_id = mapLP[stripe_id];\n\n    if ((mForceRecovery || mStoreRecoveryRW) && mStripe[physical_id]) {\n      phandler = static_cast<AsyncMetaHandler*>\n                 (mStripe[physical_id]->fileGetAsyncHandler());\n\n      if (phandler) {\n        phandler->Reset();\n      }\n\n      nwrite = mStripe[physical_id]->fileWriteAsync(offset_local,\n               data_blocks[stripe_id](),\n               mStripeWidth,\n               mTimeout);\n\n      if (nwrite != (int64_t)mStripeWidth) {\n        eos_err(\"msg=\\\"failed write\\\" stripe=%u, offset=%lli\",\n                stripe_id, offset_local);\n        ret = false;\n        break;\n      }\n\n      // Add the data contained into the buffer to compute the\n      // unit checksum, skipping the header\n      AddDataToStripeChecksum(data_blocks[stripe_id](), nwrite, offset_local);\n      mStripeSize = offset_local + nwrite < mStripeSize ? mStripeSize : offset_local +\n                    nwrite;\n    }\n\n    // Write the correct block to the reading buffer, if it is not parity info\n    if (stripe_id < mNbDataFiles) {\n      // If one of the data blocks\n      for (auto chunk = grp_errs.begin(); chunk != grp_errs.end(); chunk++) {\n        offset = chunk->offset;\n\n        if ((offset >= (offset_group + stripe_id * mStripeWidth)) &&\n            (offset < (offset_group + (stripe_id + 1) * mStripeWidth))) {\n          chunk->buffer = static_cast<char*>(memcpy(chunk->buffer,\n                                             data_blocks[stripe_id]() + (offset % mStripeWidth),\n                                             chunk->length));\n        }\n      }\n    }\n  }\n\n  // Wait for write responses\n  for (auto iter = invalid_ids.begin(); iter != invalid_ids.end(); ++iter) {\n    physical_id = mapLP[*iter];\n\n    if ((mForceRecovery || mStoreRecoveryRW) && mStripe[physical_id]) {\n      phandler = static_cast<AsyncMetaHandler*>\n                 (mStripe[physical_id]->fileGetAsyncHandler());\n\n      if (phandler) {\n        uint16_t error_type = phandler->WaitOK();\n\n        if (error_type != XrdCl::errNone) {\n          eos_err(\"msg=\\\"failed write\\\" stripe=%u\", *iter);\n          ret = false;\n\n          if (error_type == XrdCl::errOperationExpired) {\n            mStripe[physical_id]->fileClose(mTimeout);\n            mStripe[physical_id] = nullptr;\n          }\n        }\n      }\n    }\n  }\n\n  mDoneRecovery = true;\n  RecycleGroup(grp);\n  return ret;\n}\n\n//------------------------------------------------------------------------------\n// Write the parity blocks from group to the corresponding file stripes\n//------------------------------------------------------------------------------\nint\nReedSLayout::WriteParityToFiles(std::shared_ptr<eos::fst::RainGroup>& grp)\n{\n  uint64_t offset_local = (grp->GetGroupOffset() / mNbDataFiles);\n  eos::fst::RainGroup& data_blocks = *grp.get();\n  offset_local += mSizeHeader;\n\n  for (unsigned int i = mNbDataFiles; i < mNbTotalFiles; i++) {\n    unsigned int physical_id = mapLP[i];\n\n    // Write parity block\n    if (mStripe[physical_id]) {\n      grp->StoreFuture(mStripe[physical_id]->fileWriteAsync(data_blocks[i](),\n                       offset_local,\n                       mStripeWidth));\n    } else {\n      return SFS_ERROR;\n    }\n  }\n\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Return the same index in the Reed-Solomon case\n//------------------------------------------------------------------------------\nunsigned int\nReedSLayout::MapSmallToBig(unsigned int idSmall)\n{\n  if (idSmall >= mNbDataBlocks) {\n    eos_err(\"idSmall bigger than expected\");\n    return -1;\n  }\n\n  return idSmall;\n}\n\n//------------------------------------------------------------------------------\n// Allocate file space (reserve)\n//------------------------------------------------------------------------------\nint\nReedSLayout::Fallocate(XrdSfsFileOffset length)\n{\n  int64_t size = ceil((1.0 * length) / mSizeGroup) * mStripeWidth + mSizeHeader;\n  return mStripe[0]->fileFallocate(size);\n}\n\n//------------------------------------------------------------------------------\n// Deallocate file space\n//------------------------------------------------------------------------------\nint\nReedSLayout::Fdeallocate(XrdSfsFileOffset fromOffset,\n                         XrdSfsFileOffset toOffset)\n{\n  int64_t from_size = ceil((1.0 * fromOffset) / mSizeGroup) * mStripeWidth +\n                      mSizeHeader;\n  int64_t to_size = ceil((1.0 * toOffset) / mSizeGroup) * mStripeWidth +\n                    mSizeHeader;\n  return mStripe[0]->fileFdeallocate(from_size, to_size);\n}\n\n//------------------------------------------------------------------------------\n// Convert a global offset (from the inital file) to a local offset within\n// a stripe file. The initial block does *NOT* span multiple chunks (stripes)\n// therefore if the original length is bigger than one chunk the splitting\n// must be done before calling this method.\n//------------------------------------------------------------------------------\nstd::pair<int, uint64_t>\nReedSLayout::GetLocalOff(uint64_t global_off)\n{\n  uint64_t local_off = (global_off / mSizeLine) * mStripeWidth +\n                       (global_off % mStripeWidth);\n  int stripe_id = (global_off / mStripeWidth) % mNbDataFiles;\n  return std::make_pair(stripe_id, local_off);\n}\n\n//------------------------------------------------------------------------------\n// Convert a local position (from a stripe file) to a global position\n// within the initial file file\n//------------------------------------------------------------------------------\nuint64_t\nReedSLayout::GetGlobalOff(int stripe_id, uint64_t local_off)\n{\n  uint64_t global_off = (local_off / mStripeWidth) * mSizeLine +\n                        (stripe_id * mStripeWidth) +\n                        (local_off % mStripeWidth);\n  return global_off;\n}\n\n//------------------------------------------------------------------------------\n// Get truncate offset for stripe\n//------------------------------------------------------------------------------\nuint64_t\nReedSLayout::GetStripeTruncateOffset(uint64_t offset)\n{\n  return static_cast<uint64_t>(ceil((offset * 1.0) / mSizeGroup) *\n                               mStripeWidth + mSizeHeader);\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/layout/ReedSLayout.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file ReedSLayout.hh\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Implementation of the Reed-Solomon layout\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"fst/layout/RainMetaLayout.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Implementation of the Reed-Solomon layout - this uses the Jerasure code\n//! for implementing Cauchy Reed-Solomon\n//------------------------------------------------------------------------------\nclass ReedSLayout : public RainMetaLayout\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param file handler to current file\n  //! @param lid layout id\n  //! @param client security information\n  //! @param outError error information\n  //! @param io access type\n  //! @param timeout timeout value\n  //! @param storeRecovery if true write back the recovered blocks to file\n  //! @param targetSize expected final size\n  //! @param bookingOpaque opaque information\n  //!\n  //----------------------------------------------------------------------------\n  ReedSLayout(XrdFstOfsFile* file, unsigned long lid,\n              const XrdSecEntity* client, XrdOucErrInfo* outError,\n              const char* path, eos::fst::FmdHandler* fmdHandler, uint16_t timeout = 0,\n              bool storeRecovery = false, off_t targetSize = 0,\n              std::string bookingOpaque = \"oss.size\", bool computeStripeChecksum = false);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~ReedSLayout()\n  {\n    FreeJerasure();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Allocate file space\n  //!\n  //! @param length space to be allocated\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Fallocate(XrdSfsFileOffset lenght);\n\n  //----------------------------------------------------------------------------\n  //! Deallocate file space\n  //!\n  //! @param fromOffset offset start\n  //! @param toOffset offset end\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Fdeallocate(XrdSfsFileOffset fromOffset, XrdSfsFileOffset toOffset);\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Disable copy/move assign/constructor operators\n  //----------------------------------------------------------------------------\n  ReedSLayout& operator = (const ReedSLayout&) = delete;\n  ReedSLayout(const ReedSLayout&) = delete;\n  ReedSLayout& operator = (ReedSLayout&&) = delete;\n  ReedSLayout(ReedSLayout&&) = delete;\n\n  //! Values use by Jerasure codes\n  unsigned int w;           ///< word size for Jerasure\n  unsigned int mPacketSize; ///< packet size for Jerasure\n  int* matrix;\n  int* bitmatrix;\n  int** schedule;\n  std::atomic<bool> mDoneInit {false}; ///< Mark Jerasure initialization\n\n  //----------------------------------------------------------------------------\n  //! Initialise the Jerasure structures used for encoding and decoding\n  //----------------------------------------------------------------------------\n  void InitialiseJerasure();\n\n  //----------------------------------------------------------------------------\n  //! Deallocated any Jerasure structures used for encoding and decoding\n  //----------------------------------------------------------------------------\n  void FreeJerasure();\n\n  //------------------------------------------------------------------------------\n  //! Compute error correction blocks\n  //!\n  //! @param grp group object for parity computation\n  //!\n  //! @return true if parity info computed successfully, otherwise false\n  //------------------------------------------------------------------------------\n  bool ComputeParity(std::shared_ptr<eos::fst::RainGroup>& grp);\n\n  //----------------------------------------------------------------------------\n  //! Write parity information corresponding to a group to files\n  //!\n  //! @param grp group object\n  //!\n  //! @return 0 if successful, otherwise error\n  //----------------------------------------------------------------------------\n  int WriteParityToFiles(std::shared_ptr<eos::fst::RainGroup>& grp);\n\n  //--------------------------------------------------------------------------\n  //! Recover corrupted chunks from the current group\n  //!\n  //! @param grp_errs chunks to be recovered\n  //!\n  //! @return true if recovery successful, false otherwise\n  //--------------------------------------------------------------------------\n  bool RecoverPiecesInGroup(XrdCl::ChunkList& grp_errs);\n\n  //--------------------------------------------------------------------------\n  //! Map index from nDataBlocks representation to nTotalBlocks\n  //!\n  //! @param idSmall with values between 0 and nDataBlocks\n  //!\n  //! @return index with the same values as idSmall, identical function\n  //--------------------------------------------------------------------------\n  unsigned int MapSmallToBig(unsigned int idSmall);\n\n  //--------------------------------------------------------------------------\n  //! Convert a global offset (from the inital file) to a local offset within\n  //! a stripe data file. The initial block does *NOT* span multiple chunks\n  //! (stripes) therefore if the original length is bigger than one chunk the\n  //! splitting must be done before calling this method.\n  //!\n  //! @param global_off initial offset\n  //!\n  //! @return tuple made up of the logical index of the stripe data file the\n  //!         piece belongs to and the local offset within that file.\n  //--------------------------------------------------------------------------\n  std::pair<int, uint64_t> GetLocalOff(uint64_t global_off) override;\n\n  //--------------------------------------------------------------------------\n  //! Convert a local position (from a stripe data file) to a global position\n  //! within the initial file file. Note that the local offset has to come\n  //! from a stripe data file since there is no corresponde in the original\n  //! file for a piece which is in the parity stripe.\n  //!\n  //! @param stripe_id logical stripe index\n  //! @param local_off local offset\n  //!\n  //! @return offset in the initial file of the local given piece\n  //--------------------------------------------------------------------------\n  uint64_t GetGlobalOff(int stripe_id, uint64_t local_off) override;\n\n  //----------------------------------------------------------------------------\n  //! Get truncate offset for stripe\n  //!\n  //! @param offset logical file truncate offset\n  //!\n  //! @return local stripe truncate offset\n  //----------------------------------------------------------------------------\n  uint64_t GetStripeTruncateOffset(uint64_t offset) override;\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/layout/ReplicaParLayout.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ReplicaParLayout.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/layout/ReplicaParLayout.hh\"\n#include \"fst/XrdFstOfs.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nReplicaParLayout::ReplicaParLayout(XrdFstOfsFile* file,\n                                   unsigned long lid,\n                                   const XrdSecEntity* client,\n                                   XrdOucErrInfo* outError,\n                                   const char* path,\n                                   eos::fst::FmdHandler* fmdHandler,\n                                   uint16_t timeout) :\n  Layout(file, lid, client, outError, path, fmdHandler, timeout),\n  // this 1=0x0 16=0xf :-)\n  mNumReplicas(eos::common::LayoutId::GetStripeNumber(lid) + 1),\n  mHasWriteErr(false), mDoAsyncWrite(false)\n{\n  if (getenv(\"EOS_FST_REPLICA_ASYNC_WRITE\")) {\n    mDoAsyncWrite = true;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Redirect to new target\n//------------------------------------------------------------------------------\nvoid ReplicaParLayout::Redirect(const char* path)\n{\n  mFileIO.reset(FileIoPlugin::GetIoObject(path, mOfsFile, mSecEntity));\n  mLocalPath = path;\n}\n\n//------------------------------------------------------------------------------\n// Open file\n//------------------------------------------------------------------------------\nint\nReplicaParLayout::Open(XrdSfsFileOpenMode flags, mode_t mode,\n                       const char* opaque)\n{\n  int replica_index = -1;\n  int replica_head = -1;\n  const char* index = mOfsFile->mOpenOpaque->Get(\"mgm.replicaindex\");\n\n  if (index) {\n    replica_index = atoi(index);\n\n    if ((replica_index < 0) || (replica_index > 255)) {\n      eos_err(\"msg=\\\"illegal replica index %d\\\"\", replica_index);\n      return Emsg(\"ReplicaPar::Open\", *mError, EINVAL, \"open replica - \"\n                  \"illegal replica index found\", index);\n    }\n  } else {\n    eos_err(\"%s\", \"msg=\\\"replica index missing\\\"\");\n    return Emsg(\"ReplicaPar::Open\", *mError, EINVAL, \"open replica - \"\n                \"no replica index defined\");\n  }\n\n  const char* head = mOfsFile->mOpenOpaque->Get(\"mgm.replicahead\");\n\n  if (head) {\n    replica_head = atoi(head);\n\n    if ((replica_head < 0) || (replica_head > 255)) {\n      eos_err(\"msg=\\\"illegal replica head %d\\\"\", replica_head);\n      return Emsg(\"ReplicaParOpen\", *mError, EINVAL, \"open replica - \"\n                  \"illegal replica head found\", head);\n    }\n  } else {\n    eos_err(\"%s\", \"msg=\\\"replica head missing\\\"\");\n    return Emsg(\"ReplicaPar::Open\", *mError, EINVAL, \"open replica - \"\n                \"no replica head defined\");\n  }\n\n  // Define the replication head\n  eos_debug(\"replica_head=%i, replica_index=%i\", replica_head, replica_index);\n\n  if (replica_index == replica_head) {\n    mIsEntryServer = true;\n  }\n\n  int envlen;\n  XrdOucString ns_path = mOfsFile->mOpenOpaque->Get(\"mgm.path\");\n  // Local replica is always on the first position in the vector\n  mReplicaUrl.push_back(mLocalPath);\n\n  // Only entry server needs to contact others and only for write ops\n  if (mIsEntryServer && mOfsFile->mIsRW) {\n    for (int i = 0; i < mNumReplicas; ++i) {\n      if (i != replica_index) {\n        const std::string rep_tag = \"mgm.url\" + std::to_string(i);\n        const char* rep = mOfsFile->mCapOpaque->Get(rep_tag.c_str());\n\n        if (!rep) {\n          if (mOfsFile->mIsRW) {\n            eos_err(\"msg=\\\"failed to open replica for writing, missing url \"\n                    \"for replica %s\\\"\", rep_tag.c_str());\n            return Emsg(\"ReplicaParOpen\", *mError, EINVAL, \"open stripes - \"\n                        \"missing url for replica \", rep_tag.c_str());\n          } else {\n            // For read we can handle one of the replicas missing\n            continue;\n          }\n        }\n\n        // Prepare the index for the next target\n        XrdOucString oldindex = \"mgm.replicaindex=\";\n        XrdOucString newindex = \"mgm.replicaindex=\";\n        oldindex += index;\n        newindex += i;\n        XrdOucString new_opaque = mOfsFile->mOpenOpaque->Env(envlen);\n        new_opaque.replace(oldindex.c_str(), newindex.c_str());\n        std::string replica_url = rep;\n        replica_url += ns_path.c_str();\n        replica_url += \"?\";\n        replica_url += new_opaque.c_str();\n        mReplicaUrl.push_back(replica_url);\n        eos_debug(\"msg=\\\"add replica\\\" replica_url=%s, index=%i\",\n                  replica_url.c_str(), i);\n      }\n    }\n  }\n\n  std::list<std::future<XrdCl::XRootDStatus>> open_futures;\n  std::list<XrdCl::XRootDStatus> open_replies;\n\n  for (const auto& replica_url : mReplicaUrl) {\n    std::unique_ptr<FileIo> file\n    {FileIoPlugin::GetIoObject(replica_url, mOfsFile, mSecEntity)};\n\n    if (file) {\n      open_futures.push_back(file->fileOpenAsync(flags, mode, opaque, mTimeout));\n      mReplicaFile.push_back(std::move(file));\n    } else {\n      // Wait and discard any pending replies\n      for (auto& fut : open_futures) {\n        (void) fut.get();\n      }\n\n      eos_err(\"msg=\\\"failed to allocate file object\\\" path=\\\"%s\\\"\",\n              replica_url.c_str());\n      return Emsg(\"ReplicaParOpen\", *mError, EINVAL, \"open stripes - \"\n                  \"failed to allocate file object\");\n    }\n  }\n\n  for (auto& fut : open_futures) {\n    open_replies.push_back(fut.get());\n    // Populate vector of responses for write ops - to be dropped with eosd\n    mResponses.emplace_back();\n  }\n\n  int count = 0;\n\n  for (const auto& status : open_replies) {\n    if (!status.IsOK()) {\n      bool is_local = (count == 0);\n      bool is_rw = mOfsFile->mIsRW;\n      XrdOucString maskUrl = (mReplicaUrl[count].c_str() ?\n                              mReplicaUrl[count].c_str() : \"\");\n      // Mask some opaque parameters to shorten the logging\n      eos::common::StringConversion::MaskTag(maskUrl, \"cap.sym\");\n      eos::common::StringConversion::MaskTag(maskUrl, \"cap.msg\");\n      eos::common::StringConversion::MaskTag(maskUrl, \"authz\");\n      eos_err(\"msg=\\\"failed %s %s open\\\" path=\\\"%s\\\"\",\n              (is_local ? \"local\" : \"remote\"), (is_rw ? \"write\" : \"read\"),\n              maskUrl.c_str());\n      return Emsg(\"ReplicaParOpen\", *mError, (is_local ? EIO : EREMOTEIO),\n                  \"open stripes - open failed \", maskUrl.c_str());\n    }\n\n    ++count;\n  }\n\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Read from file\n//------------------------------------------------------------------------------\nint64_t\nReplicaParLayout::Read(XrdSfsFileOffset offset, char* buffer,\n                       XrdSfsXferSize length, bool readahead)\n{\n  int64_t rc = 0;\n\n  for (unsigned int i = 0; i < mReplicaFile.size(); i++) {\n    rc = mReplicaFile[i]->fileRead(offset, buffer, length, mTimeout);\n\n    if (rc == SFS_ERROR) {\n      XrdOucString maskUrl = mReplicaUrl[i].c_str() ? mReplicaUrl[i].c_str() : \"\";\n      // mask some opaque parameters to shorten the logging\n      eos::common::StringConversion::MaskTag(maskUrl, \"cap.sym\");\n      eos::common::StringConversion::MaskTag(maskUrl, \"cap.msg\");\n      eos::common::StringConversion::MaskTag(maskUrl, \"authz\");\n      eos_warning(\"Failed to read from replica off=%lld, length=%i, mask_url=%s\",\n                  offset, length, maskUrl.c_str());\n      continue;\n    } else {\n      // Read was successful no need to read from another replica\n      break;\n    }\n  }\n\n  if (rc == SFS_ERROR) {\n    eos_err(\"Failed to read from any replica offset=%lld, length=%i\",\n            offset, length);\n    return Emsg(\"ReplicaParRead\", *mError, EREMOTEIO,\n                \"read replica - read failed\");\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Vector read\n//------------------------------------------------------------------------------\nint64_t\nReplicaParLayout::ReadV(XrdCl::ChunkList& chunkList, uint32_t len)\n{\n  int64_t rc = 0;\n  eos_debug(\"msg=\\\"readv\\\" count_chunks=%i\", chunkList.size());\n\n  for (unsigned int i = 0; i < mReplicaFile.size(); i++) {\n    rc = mReplicaFile[i]->fileReadV(chunkList, mTimeout);\n\n    if (rc == SFS_ERROR) {\n      XrdOucString maskUrl = mReplicaUrl[i].c_str() ? mReplicaUrl[i].c_str() : \"\";\n      // Mask some opaque parameters to shorten the logging\n      eos::common::StringConversion::MaskTag(maskUrl, \"cap.sym\");\n      eos::common::StringConversion::MaskTag(maskUrl, \"cap.msg\");\n      eos::common::StringConversion::MaskTag(maskUrl, \"authz\");\n      eos_warning(\"msg=\\\"failed replica readv \\\" url=\\\"%s\\\"\", maskUrl.c_str());\n      continue;\n    } else {\n      // Read was successful no need to read from another replica\n      break;\n    }\n  }\n\n  if (rc == SFS_ERROR) {\n    eos_err(\"%s\", \"msg=\\\"failed to readv from any replica\\\"\");\n    return Emsg(\"ReplicaParRead\", *mError, EREMOTEIO, \"readv replica failed\");\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Write to file\n//------------------------------------------------------------------------------\nint64_t\nReplicaParLayout::Write(XrdSfsFileOffset offset, const char* buffer,\n                        XrdSfsXferSize length)\n{\n  if (mDoAsyncWrite) {\n    return WriteAsync(offset, buffer, length);\n  }\n\n  for (unsigned int i = 0; i < mReplicaFile.size(); ++i) {\n    int64_t rc = mReplicaFile[i]->fileWrite(offset, buffer, length, mTimeout);\n\n    if (rc != length) {\n      XrdOucString maskUrl = mReplicaUrl[i].c_str() ? mReplicaUrl[i].c_str() : \"\";\n      // mask some opaque parameters to shorten the logging\n      eos::common::StringConversion::MaskTag(maskUrl, \"cap.sym\");\n      eos::common::StringConversion::MaskTag(maskUrl, \"cap.msg\");\n      eos::common::StringConversion::MaskTag(maskUrl, \"authz\");\n      errno = (i == 0) ? EIO : EREMOTEIO;\n\n      // show only the first write error as an error to broadcast upstream\n      if (mHasWriteErr) {\n        eos_err(\"[NB] Failed to write replica %i - write failed -%llu %s\",\n                i, offset, maskUrl.c_str());\n      } else {\n        eos_err(\"Failed to write replica %i - write failed - %llu %s\",\n                i, offset, maskUrl.c_str());\n      }\n\n      mHasWriteErr = true;\n      return Emsg(\"ReplicaWrite\", *mError, errno, \"write replica failed\",\n                  maskUrl.c_str());\n    }\n  }\n\n  return length;\n}\n\n//------------------------------------------------------------------------------\n// Write using async requests\n//------------------------------------------------------------------------------\nint64_t\nReplicaParLayout::WriteAsync(XrdSfsFileOffset offset, const char* buffer,\n                             XrdSfsXferSize length)\n{\n  for (unsigned int i = 0; i < mReplicaFile.size(); ++i) {\n    mResponses[i].CollectFuture(mReplicaFile[i]->fileWriteAsync\n                                (buffer, offset, length));\n\n    // Collect available responses\n    if (!mResponses[i].CheckResponses(false)) {\n      XrdOucString maskUrl = mReplicaUrl[i].c_str() ? mReplicaUrl[i].c_str() : \"\";\n      eos::common::StringConversion::MaskTag(maskUrl, \"cap.sym\");\n      eos::common::StringConversion::MaskTag(maskUrl, \"cap.msg\");\n      eos::common::StringConversion::MaskTag(maskUrl, \"authz\");\n\n      // Show only the first write error as an error to broadcast upstream\n      if (mHasWriteErr) {\n        eos_err(\"msg=\\\"[NB] write failed for replica %i\\\" offset=%llu url=%s\",\n                i, offset, maskUrl.c_str());\n      } else {\n        eos_err(\"msg=\\\"write failed for replica %i\\\" offset=%llu url=%s\",\n                i, offset, maskUrl.c_str());\n      }\n\n      mHasWriteErr = true;\n      errno = (i == 0) ? EIO : EREMOTEIO;\n      return Emsg(\"ReplicaWrite\", *mError, errno, \"write replica failed\",\n                  maskUrl.c_str());\n    }\n  }\n\n  return length;\n}\n\n//------------------------------------------------------------------------------\n// Truncate file\n//------------------------------------------------------------------------------\nint\nReplicaParLayout::Truncate(XrdSfsFileOffset offset)\n{\n  int rc = SFS_OK;\n\n  for (unsigned int i = 0; i < mReplicaFile.size(); i++) {\n    rc = mReplicaFile[i]->fileTruncate(offset, mTimeout);\n\n    if (rc != SFS_OK) {\n      errno = (i == 0) ? EIO : EREMOTEIO;\n      XrdOucString maskUrl = mReplicaUrl[i].c_str() ? mReplicaUrl[i].c_str() : \"\";\n      // mask some opaque parameters to shorten the logging\n      eos::common::StringConversion::MaskTag(maskUrl, \"cap.sym\");\n      eos::common::StringConversion::MaskTag(maskUrl, \"cap.msg\");\n      eos::common::StringConversion::MaskTag(maskUrl, \"authz\");\n      eos_err(\"Failed to truncate replica %i\", i);\n      return Emsg(\"ReplicaParTuncate\", *mError, errno, \"truncate failed\",\n                  maskUrl.c_str());\n    }\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Get stats for file\n//------------------------------------------------------------------------------\nint\nReplicaParLayout::Stat(struct stat* buf)\n{\n  int rc = 0;\n\n  for (unsigned int i = 0; i < mReplicaFile.size(); i++) {\n    rc = mReplicaFile[i]->fileStat(buf, mTimeout);\n\n    // Stop at the first stat which works\n    if (!rc) {\n      break;\n    }\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Sync file to disk\n//------------------------------------------------------------------------------\nint\nReplicaParLayout::Sync()\n{\n  int rc = 0;\n\n  for (unsigned int i = 0; i < mReplicaFile.size(); i++) {\n    XrdOucString maskUrl = mReplicaUrl[i].c_str() ? mReplicaUrl[i].c_str() : \"\";\n    // mask some opaque parameters to shorten the logging\n    eos::common::StringConversion::MaskTag(maskUrl, \"cap.sym\");\n    eos::common::StringConversion::MaskTag(maskUrl, \"cap.msg\");\n    eos::common::StringConversion::MaskTag(maskUrl, \"authz\");\n    rc = mReplicaFile[i]->fileSync(mTimeout);\n\n    if (rc != SFS_OK) {\n      errno = (i == 0) ? EIO : EREMOTEIO;\n      eos_err(\"error=failed to sync replica %i\", i);\n      return Emsg(\"ReplicaParSync\", *mError, errno, \"sync failed\",\n                  maskUrl.c_str());\n    }\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Remove file and all replicas\n//------------------------------------------------------------------------------\nint\nReplicaParLayout::Remove()\n{\n  int rc = SFS_OK;\n  bool got_error = false;\n\n  for (unsigned int i = 0; i < mReplicaFile.size(); i++) {\n    rc = mReplicaFile[i]->fileRemove();\n\n    if (rc != SFS_OK) {\n      got_error = true;\n      errno = (i == 0) ? EIO : EREMOTEIO;\n      eos_err(\"msg=\\\"failed to remove replica %i\\\"\", i);\n    }\n  }\n\n  if (got_error) {\n    return Emsg(\"ReplicaParRemove\", *mError, errno, \"remove failed\");\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Close file\n//------------------------------------------------------------------------------\nint\nReplicaParLayout::Close()\n{\n  int rc = SFS_OK;\n  int rc_close = SFS_OK;\n\n  for (unsigned int i = 0; i < mReplicaFile.size(); i++) {\n    // Wait for any async requests before closing\n    if (mReplicaFile[i]) {\n      if (mOfsFile->mIsRW && mDoAsyncWrite) {\n        if (!mResponses[i].CheckResponses(true)) {\n          eos_err(\"msg=\\\"some async write requests failed for replica %i\\\"\", i);\n          ++rc;\n        }\n      }\n\n      rc_close = mReplicaFile[i]->fileClose(mTimeout);\n      rc += rc_close;\n\n      if (rc_close != SFS_OK) {\n        eos_err(\"msg=\\\"failed to close replica %i\\\" url=\\\"%s\\\"\",\n                i, mReplicaUrl[i].c_str());\n\n        if (errno != EIO) {\n          errno = ((i == 0) ? EIO : EREMOTEIO);\n        }\n      }\n    }\n  }\n\n  if (rc != SFS_OK) {\n    return Emsg(\"ReplicaParClose\", *mError, errno, \"close failed\", \"\");\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Execute implementation dependant command\n//------------------------------------------------------------------------------\nint\nReplicaParLayout::Fctl(const std::string& cmd, const XrdSecEntity* client)\n{\n  int retc = SFS_OK;\n\n  for (unsigned int i = 0; i < mReplicaFile.size(); i++) {\n    retc += mReplicaFile[i]->fileFctl(cmd);\n  }\n\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Reserve space for file\n//------------------------------------------------------------------------------\nint\nReplicaParLayout::Fallocate(XrdSfsFileOffset length)\n{\n  return mReplicaFile[0]->fileFallocate(length);\n}\n\n//------------------------------------------------------------------------------\n// Deallocate reserved space\n//------------------------------------------------------------------------------\nint\nReplicaParLayout::Fdeallocate(XrdSfsFileOffset fromOffset,\n                              XrdSfsFileOffset toOffset)\n{\n  return mReplicaFile[0]->fileFdeallocate(fromOffset, toOffset);\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/layout/ReplicaParLayout.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file ReplicaParLayout.hh\n//! @author Andreas-Joachim Peters - CERN\n//! @brief Physical layout of a file with replicas\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_REPLICAPARLAYOUT_HH__\n#define __EOSFST_REPLICAPARLAYOUT_HH__\n\n#include \"fst/layout/Layout.hh\"\n#include \"fst/io/xrd/ResponseCollector.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class abstracting the physical layout of a file with replicas\n//------------------------------------------------------------------------------\nclass ReplicaParLayout: public Layout\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param file file handler\n  //! @param lid layout id\n  //! @param client security information\n  //! @param error error information\n  //! @param io io access type ( ofs/xrd )\n  //! @param timeout timeout value\n  //----------------------------------------------------------------------------\n  ReplicaParLayout(XrdFstOfsFile* file,\n                   unsigned long lid,\n                   const XrdSecEntity* client,\n                   XrdOucErrInfo* outError,\n                   const char* path,\n                   eos::fst::FmdHandler* fmdHandler,\n                   uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~ReplicaParLayout() = default;\n\n  //--------------------------------------------------------------------------\n  // Redirect to new target\n  //--------------------------------------------------------------------------\n  virtual void Redirect(const char* path);\n\n  //----------------------------------------------------------------------------\n  //! Open file\n  //!\n  //! @param path file path\n  //! @param flags open flags\n  //! @param mode open mode\n  //! @param opaque opaque information\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Open(XrdSfsFileOpenMode flags, mode_t mode, const char* opaque);\n\n  //----------------------------------------------------------------------------\n  //! Read from file\n  //!\n  //! @param offset offset\n  //! @param buffer place to hold the read data\n  //! @param length length\n  //! @param readahead readahead switch\n  //!\n  //! @return number of bytes read or -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t Read(XrdSfsFileOffset offset,\n                       char* buffer,\n                       XrdSfsXferSize length,\n                       bool readahead = false);\n\n  //----------------------------------------------------------------------------\n  //! Vector read\n  //!\n  //! @param chunkList list of chunks for the vector read\n  //! @param len total length of the vector read\n  //!\n  //! @return number of bytes read of -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t ReadV(XrdCl::ChunkList& chunkList,\n                        uint32_t len);\n\n  //----------------------------------------------------------------------------\n  //! Write to file\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //!\n  //! @return number of bytes written or -1 if error\n  //----------------------------------------------------------------------------\n  virtual int64_t Write(XrdSfsFileOffset offset,\n                        const char* buffer,\n                        XrdSfsXferSize length);\n\n  //----------------------------------------------------------------------------\n  //! Truncate\n  //!\n  //! @param offset truncate file to this value\n  //!\n  //! @return 0 on success, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Truncate(XrdSfsFileOffset offset);\n\n  //----------------------------------------------------------------------------\n  //! Allocate file space\n  //!\n  //! @param length space to be allocated\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Fallocate(XrdSfsFileOffset length);\n\n  //----------------------------------------------------------------------------\n  //! Deallocate file space\n  //!\n  //! @param fromOffset offset start\n  //! @param toOffset offset end\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Fdeallocate(XrdSfsFileOffset fromOffset,\n                          XrdSfsFileOffset toOffset);\n\n  //----------------------------------------------------------------------------\n  //! Remove file\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Remove();\n\n  //----------------------------------------------------------------------------\n  //! Sync file to disk\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Sync();\n\n  //----------------------------------------------------------------------------\n  //! Get stats about the file\n  //!\n  //! @param buf stat buffer\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Stat(struct stat* buf);\n\n  //----------------------------------------------------------------------------\n  //! Execute implementation dependant command\n  //!\n  //! @param cmd command\n  //! @param client client identity\n  //!\n  //! @return 0 if successful, -1 otherwise\n  //----------------------------------------------------------------------------\n  virtual int Fctl(const std::string& cmd, const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Close file\n  //!\n  //! @return 0 if successful, -1 otherwise and error code is set\n  //----------------------------------------------------------------------------\n  virtual int Close();\n\nprivate:\n  int mNumReplicas; ///< Number of replicas for current file\n  std::atomic<bool> mHasWriteErr;\n  std::atomic<bool> mDoAsyncWrite;\n  ///! Replica file object, index 0 is the local file\n  std::vector<std::unique_ptr<FileIo>> mReplicaFile;\n  ///! URLs for all the replica files\n  std::vector<std::string> mReplicaUrl;\n  ///! Vector of reponse collector for all replicas\n  std::vector<ResponseCollector> mResponses;\n\n  //----------------------------------------------------------------------------\n  //! Write using async requests\n  //!\n  //! @param offset offset\n  //! @param buffer data to be written\n  //! @param length length\n  //!\n  //! @return number of bytes written or -1 if error\n  //----------------------------------------------------------------------------\n  int64_t WriteAsync(XrdSfsFileOffset offset, const char* buffer,\n                     XrdSfsXferSize length);\n};\n\nEOSFSTNAMESPACE_END\n\n#endif  // __EOSFST_REPLICAPARLAYOUT_HH__\n"
  },
  {
    "path": "fst/layout/gf-complete/.gitignore",
    "content": "Makefile\nMakefile.in\n/autom4te.cache\n/aclocal.m4\n/compile\n/configure\n/depcomp\n/install-sh\n/missing\ninclude/config.h\ninclude/config.h.in\ninclude/config.h.in~\ninclude/stamp-h1\n\n# Object files\n*.o\n*.ko\n*.obj\n*.elf\n\n# Libraries\n*.lib\n*.la\n*.a\n\n# Shared objects (inc. Windows DLLs)\n*.dll\n*.lo\n*.so\n*.so.*\n*.dylib\n\n# Executables\n*.exe\n*.out\n*.app\n*.i*86\n*.x86_64\n*.hex\n\n# Other stuff\n.deps/\n.libs/\n/config.log\n/config.status\n/libtool\nINSTALL\nconfig.guess\nconfig.sub\nltmain.sh\nm4/libtool.m4\nm4/ltversion.m4\nsrc/.dirstamp\ntest-driver\n\nexamples/gf_example_1\nexamples/gf_example_2\nexamples/gf_example_3\nexamples/gf_example_4\nexamples/gf_example_5\nexamples/gf_example_6\nexamples/gf_example_7\ntest/gf_unit\ntools/gf_add\ntools/gf_div\ntools/gf_inline_time\ntools/gf_methods\ntools/gf_mult\ntools/gf_poly\ntools/gf_time\n"
  },
  {
    "path": "fst/layout/gf-complete/AUTHORS",
    "content": ""
  },
  {
    "path": "fst/layout/gf-complete/COPYING",
    "content": "Copyright (c) 2013, James S. Plank, Ethan L. Miller, Kevin M. Greenan,\nBenjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n - Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n\n - Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in\n   the documentation and/or other materials provided with the\n   distribution.\n\n - Neither the name of the University of Tennessee nor the names of its\n   contributors may be used to endorse or promote products derived\n   from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\nINCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\nBUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\nOF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\nAND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\nWAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "fst/layout/gf-complete/ChangeLog",
    "content": ""
  },
  {
    "path": "fst/layout/gf-complete/License.txt",
    "content": "Copyright (c) 2013, James S. Plank, Ethan L. Miller, Kevin M. Greenan,\nBenjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n - Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n\n - Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in\n   the documentation and/or other materials provided with the\n   distribution.\n\n - Neither the name of the University of Tennessee nor the names of its\n   contributors may be used to endorse or promote products derived\n   from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\nINCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\nBUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\nOF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\nAND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\nWAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "fst/layout/gf-complete/Makefile.am",
    "content": "# Top-level GF-Complete AM file\n# Distributes headers\n\nSUBDIRS = src tools test examples\nACLOCAL_AMFLAGS = -I m4\n\ninclude_HEADERS = include/gf_complete.h include/gf_method.h include/gf_rand.h include/gf_general.h\n\n"
  },
  {
    "path": "fst/layout/gf-complete/NEWS",
    "content": ""
  },
  {
    "path": "fst/layout/gf-complete/README",
    "content": "This is GF-Complete, Revision 1.03.   January 1, 2015.\n\nAuthors: James S. Plank (University of Tennessee)\n         Ethan L. Miller (UC Santa Cruz)\n         Kevin M. Greenan (Box)\n         Benjamin A. Arnold (University of Tennessee)\n         John A. Burnum (University of Tennessee)\n         Adam W. Disney (University of Tennessee,\n         Allen C. McBride (University of Tennessee)\n\nThe user's manual is in the file Manual.pdf.  \n\nThe online home for GF-Complete is:\n\n  - https://jerasure.org/jerasure/gf-complete\n\nTo compile, do:\n\n   ./configure\n   make\n   sudo make install\n"
  },
  {
    "path": "fst/layout/gf-complete/README.txt",
    "content": "This is GF-Complete, Revision 1.03.   January 1, 2015.\n\nAuthors: James S. Plank (University of Tennessee)\n         Ethan L. Miller (UC Santa Cruz)\n         Kevin M. Greenan (Box)\n         Benjamin A. Arnold (University of Tennessee)\n         John A. Burnum (University of Tennessee)\n         Adam W. Disney (University of Tennessee,\n         Allen C. McBride (University of Tennessee)\n\nThe user's manual is in the file Manual.pdf.  \n\nThe online home for GF-Complete is:\n\n  - http://jerasure.org/jerasure/gf-complete\n\nTo compile, do:\n\n   ./configure\n   make\n   sudo make install\n"
  },
  {
    "path": "fst/layout/gf-complete/autogen.sh",
    "content": "#!/bin/sh\nautoreconf --force --install -I m4\n"
  },
  {
    "path": "fst/layout/gf-complete/configure.ac",
    "content": "# gf-complete autoconf template\n\n# FIXME - add project url as the last argument\nAC_INIT(gf-complete, 1.0)\n\n# Override default CFLAGS\n: ${CFLAGS=\"-Wall -Wpointer-arith -O3 -g\"}\n\nAC_PREREQ([2.61])\n\nAM_INIT_AUTOMAKE([no-dependencies foreign parallel-tests])\nLT_INIT # libtool\n\nAC_CONFIG_HEADER(include/config.h)\n\ndnl Needed when reconfiguring with 'autoreconf -i -s'\nAC_CONFIG_MACRO_DIR([m4])\n\n# This prevents './configure; make' from trying to run autotools.\nAM_MAINTAINER_MODE([disable])\n\ndnl Compiling with per-target flags requires AM_PROG_CC_C_O.\nAC_PROG_CC\n\n# Check for functions to provide aligned memory\n#\nAC_CHECK_FUNCS([posix_memalign],\n [found_memalign=yes; break])\n\nAS_IF([test \"x$found_memalign\" != \"xyes\"], [AC_MSG_WARN([No function for aligned memory allocation found])])\n\nAX_EXT()\n\nAC_ARG_ENABLE([neon],\n              AS_HELP_STRING([--disable-neon], [Build without NEON optimizations]))\n\nAS_IF([test \"x$enable_neon\" != \"xno\"],\n      [noneon_CPPFLAGS=$CPPFLAGS\n       CPPFLAGS=\"$CPPFLAGS $SIMD_FLAGS\"\n       AC_CHECK_HEADER([arm_neon.h],\n                       [have_neon=yes],\n                       [have_neon=no\n                        CPPFLAGS=$noneon_CPPFLAGS])],\n      [have_neon=no\n       AS_IF([test \"x$ax_cv_have_neon_ext\" = \"xyes\"],\n             [SIMD_FLAGS=\"\"])\n      ])\n\nAS_IF([test \"x$have_neon\" = \"xno\"],\n      [AS_IF([test \"x$enable_neon\" = \"xyes\"],\n             [AC_MSG_ERROR([neon requested but arm_neon.h not found])])\n      ])\nAM_CONDITIONAL([HAVE_NEON], [test \"x$have_neon\" = \"xyes\"])\n\nAC_ARG_ENABLE([sse],\n              AS_HELP_STRING([--disable-sse], [Build without SSE optimizations]),\n              [if   test \"x$enableval\" = \"xno\" ; then\n                SIMD_FLAGS=\"\"\n                echo \"DISABLED SSE!!!\"\n              fi]\n)\n\nAC_CONFIG_FILES([Makefile src/Makefile tools/Makefile test/Makefile examples/Makefile])\nAC_OUTPUT\n"
  },
  {
    "path": "fst/layout/gf-complete/examples/Makefile.am",
    "content": "# GF-Complete 'examples' AM file\n\nAM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include\nAM_CFLAGS = -O3 $(SIMD_FLAGS) -fPIC\n\nbin_PROGRAMS = gf_example_1 gf_example_2 gf_example_3 gf_example_4 \\\n               gf_example_5 gf_example_6 gf_example_7\n\ngf_example_1_SOURCES = gf_example_1.c\n#gf_example_1_LDFLAGS = -lgf_complete\ngf_example_1_LDADD = ../src/libgf_complete.la\n\ngf_example_2_SOURCES = gf_example_2.c\n#gf_example_2_LDFLAGS = -lgf_complete\ngf_example_2_LDADD = ../src/libgf_complete.la\n\ngf_example_3_SOURCES = gf_example_3.c\n#gf_example_3_LDFLAGS = -lgf_complete\ngf_example_3_LDADD = ../src/libgf_complete.la\n\ngf_example_4_SOURCES = gf_example_4.c\n#gf_example_4_LDFLAGS = -lgf_complete\ngf_example_4_LDADD = ../src/libgf_complete.la\n\ngf_example_5_SOURCES = gf_example_5.c\n#gf_example_5_LDFLAGS = -lgf_complete\ngf_example_5_LDADD = ../src/libgf_complete.la\n\ngf_example_6_SOURCES = gf_example_6.c\n#gf_example_6_LDFLAGS = -lgf_complete\ngf_example_6_LDADD = ../src/libgf_complete.la\n\ngf_example_7_SOURCES = gf_example_7.c\n#gf_example_7_LDFLAGS = -lgf_complete\ngf_example_7_LDADD = ../src/libgf_complete.la\n\n\n"
  },
  {
    "path": "fst/layout/gf-complete/examples/gf_example_1.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_example_1.c\n *\n * Demonstrates using the procedures for examples in GF(2^w) for w <= 32.\n */\n\n#include <stdio.h>\n#include <getopt.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n\n#include \"gf_complete.h\"\n#include \"gf_rand.h\"\n\nvoid usage(char *s)\n{\n  fprintf(stderr, \"usage: gf_example_1 w - w must be between 1 and 32\\n\");\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  uint32_t a, b, c;\n  int w;\n  gf_t gf;\n\n  if (argc != 2) usage(NULL);\n  w = atoi(argv[1]);\n  if (w <= 0 || w > 32) usage(\"Bad w\");\n\n  /* Get two random numbers in a and b */\n\n  MOA_Seed(time(0));\n  a = MOA_Random_W(w, 0);\n  b = MOA_Random_W(w, 0);\n \n  /* Create the proper instance of the gf_t object using defaults: */\n\n  gf_init_easy(&gf, w);\n\n  /* And multiply a and b using the galois field: */\n\n  c = gf.multiply.w32(&gf, a, b);\n  printf(\"%u * %u = %u\\n\", a, b, c); \n\n  /* Divide the product by a and b */\n\n  printf(\"%u / %u = %u\\n\", c, a, gf.divide.w32(&gf, c, a));\n  printf(\"%u / %u = %u\\n\", c, b, gf.divide.w32(&gf, c, b));\n  \n  exit(0);\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/examples/gf_example_2.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_example_2.c\n *\n * Demonstrates using the procedures for examples in GF(2^w) for w <= 32.\n */\n\n#include <stdio.h>\n#include <getopt.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n\n#include \"gf_complete.h\"\n#include \"gf_rand.h\"\n\nvoid usage(char *s)\n{\n  fprintf(stderr, \"usage: gf_example_2 w - w must be between 1 and 32\\n\");\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  uint32_t a, b, c;\n  uint8_t *r1, *r2;\n  uint16_t *r16 = NULL;\n  uint32_t *r32 = NULL;\n  int w, i;\n  gf_t gf;\n\n  if (argc != 2) usage(NULL);\n  w = atoi(argv[1]);\n  if (w <= 0 || w > 32) usage(\"Bad w\");\n\n  /* Get two random numbers in a and b */\n\n  MOA_Seed(time(0));\n  a = MOA_Random_W(w, 0);\n  b = MOA_Random_W(w, 0);\n\n  /* Create the proper instance of the gf_t object using defaults: */\n\n  gf_init_easy(&gf, w);\n\n  /* And multiply a and b using the galois field: */\n\n  c = gf.multiply.w32(&gf, a, b);\n  printf(\"%u * %u = %u\\n\", a, b, c);\n\n  /* Divide the product by a and b */\n\n  printf(\"%u / %u = %u\\n\", c, a, gf.divide.w32(&gf, c, a));\n  printf(\"%u / %u = %u\\n\", c, b, gf.divide.w32(&gf, c, b));\n\n  /* If w is 4, 8, 16 or 32, do a very small region operation */\n\n  if (w == 4 || w == 8 || w == 16 || w == 32) {\n    r1 = (uint8_t *) malloc(16);\n    r2 = (uint8_t *) malloc(16);\n\n    if (w == 4 || w == 8) {\n      r1[0] = b;\n      for (i = 1; i < 16; i++) r1[i] = MOA_Random_W(8, 1);\n    } else if (w == 16) {\n      r16 = (uint16_t *) r1;\n      r16[0] = b;\n      for (i = 1; i < 8; i++) r16[i] = MOA_Random_W(16, 1);\n    } else {\n      r32 = (uint32_t *) r1;\n      r32[0] = b;\n      for (i = 1; i < 4; i++) r32[i] = MOA_Random_W(32, 1);\n    }\n\n    gf.multiply_region.w32(&gf, r1, r2, a, 16, 0);\n  \n    printf(\"\\nmultiply_region by 0x%x (%u)\\n\\n\", a, a);\n    printf(\"R1 (the source):  \");\n    if (w == 4) {\n      for (i = 0; i < 16; i++) printf(\" %x %x\", r1[i] >> 4, r1[i] & 0xf);\n    } else if (w == 8) {\n      for (i = 0; i < 16; i++) printf(\" %02x\", r1[i]);\n    } else if (w == 16) {\n      for (i = 0; i < 8; i++) printf(\" %04x\", r16[i]);\n    } else if (w == 32) {\n      for (i = 0; i < 4; i++) printf(\" %08x\", r32[i]);\n    }\n    printf(\"\\nR2 (the product): \");\n    if (w == 4) {\n      for (i = 0; i < 16; i++) printf(\" %x %x\", r2[i] >> 4, r2[i] & 0xf);\n    } else if (w == 8) {\n      for (i = 0; i < 16; i++) printf(\" %02x\", r2[i]);\n    } else if (w == 16) {\n      r16 = (uint16_t *) r2;\n      for (i = 0; i < 8; i++) printf(\" %04x\", r16[i]);\n    } else if (w == 32) {\n      r32 = (uint32_t *) r2;\n      for (i = 0; i < 4; i++) printf(\" %08x\", r32[i]);\n    }\n    printf(\"\\n\");\n  }\n  exit(0);\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/examples/gf_example_3.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_example_3.c\n *\n * Identical to example_2 except it works in GF(2^64)\n */\n\n#include <stdio.h>\n#include <getopt.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n\n#include \"gf_complete.h\"\n#include \"gf_rand.h\"\n\nvoid usage(char *s)\n{\n  fprintf(stderr, \"usage: gf_example_3\\n\");\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  uint64_t a, b, c;\n  uint64_t *r1, *r2;\n  int i;\n  gf_t gf;\n\n  if (argc != 1) usage(NULL);\n\n  /* Get two random numbers in a and b */\n\n  MOA_Seed(time(0));\n  a = MOA_Random_64();\n  b = MOA_Random_64();\n\n  /* Create the proper instance of the gf_t object using defaults: */\n\n  gf_init_easy(&gf, 64);\n\n  /* And multiply a and b using the galois field: */\n\n  c = gf.multiply.w64(&gf, a, b);\n  printf(\"%llx * %llx = %llx\\n\", (long long unsigned int) a, (long long unsigned int) b, (long long unsigned int) c);\n\n  /* Divide the product by a and b */\n\n  printf(\"%llx / %llx = %llx\\n\", (long long unsigned int) c, (long long unsigned int) a, (long long unsigned int) gf.divide.w64(&gf, c, a));\n  printf(\"%llx / %llx = %llx\\n\", (long long unsigned int) c, (long long unsigned int) b, (long long unsigned int) gf.divide.w64(&gf, c, b));\n\n  r1 = (uint64_t *) malloc(32);\n  r2 = (uint64_t *) malloc(32);\n\n  r1[0] = b;\n\n  for (i = 1; i < 4; i++) r1[i] = MOA_Random_64();\n\n  gf.multiply_region.w64(&gf, r1, r2, a, 32, 0);\n\n  printf(\"\\nmultiply_region by %llx\\n\\n\", (long long unsigned int) a);\n  printf(\"R1 (the source):  \");\n  for (i = 0; i < 4; i++) printf(\" %016llx\", (long long unsigned int) r1[i]);\n\n  printf(\"\\nR2 (the product): \");\n  for (i = 0; i < 4; i++) printf(\" %016llx\", (long long unsigned int) r2[i]);\n  printf(\"\\n\");\n\n  exit(0);\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/examples/gf_example_4.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_example_4.c\n *\n * Identical to example_3 except it works in GF(2^128)\n */\n\n#include <stdio.h>\n#include <getopt.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n\n#include \"gf_complete.h\"\n#include \"gf_rand.h\"\n\n#define LLUI (long long unsigned int) \n\nvoid usage(char *s)\n{\n  fprintf(stderr, \"usage: gf_example_3\\n\");\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  uint64_t a[2], b[2], c[2];\n  uint64_t *r1, *r2;\n  int i;\n  gf_t gf;\n\n  if (argc != 1) usage(NULL);\n\n  /* Get two random numbers in a and b */\n\n  MOA_Seed(time(0));\n  MOA_Random_128(a);\n  MOA_Random_128(b);\n\n  /* Create the proper instance of the gf_t object using defaults: */\n\n  gf_init_easy(&gf, 128);\n\n  /* And multiply a and b using the galois field: */\n\n  gf.multiply.w128(&gf, a, b, c);\n  printf(\"%016llx%016llx * %016llx%016llx =\\n%016llx%016llx\\n\", \n      LLUI a[0], LLUI a[1], LLUI b[0], LLUI b[1], LLUI c[0], LLUI c[1]);\n\n  r1 = (uint64_t *) malloc(32);\n  r2 = (uint64_t *) malloc(32);\n\n  for (i = 0; i < 4; i++) r1[i] = MOA_Random_64();\n\n  gf.multiply_region.w128(&gf, r1, r2, a, 32, 0);\n\n  printf(\"\\nmultiply_region by %016llx%016llx\\n\\n\", LLUI a[0], LLUI a[1]);\n  printf(\"R1 (the source):  \");\n  for (i = 0; i < 4; i += 2) printf(\" %016llx%016llx\", LLUI r1[i], LLUI r1[i+1]);\n\n  printf(\"\\nR2 (the product): \");\n  for (i = 0; i < 4; i += 2) printf(\" %016llx%016llx\", LLUI r2[i], LLUI r2[i+1]);\n  printf(\"\\n\");\n  exit(0);\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/examples/gf_example_5.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_example_5.c\n *\n * Demonstrating altmap and extract_word\n */\n\n#include <stdio.h>\n#include <getopt.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n\n#include \"gf_complete.h\"\n#include \"gf_rand.h\"\n\nvoid usage(char *s)\n{\n  fprintf(stderr, \"usage: gf_example_5\\n\");\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  uint16_t *a, *b;\n  int i, j;\n  gf_t gf;\n\n  if (gf_init_hard(&gf, 16, GF_MULT_SPLIT_TABLE, GF_REGION_ALTMAP, GF_DIVIDE_DEFAULT, \n                   0, 16, 4, NULL, NULL) == 0) {\n    fprintf(stderr, \"gf_init_hard failed\\n\");\n    exit(1);\n  }\n\n  a = (uint16_t *) malloc(200);\n  b = (uint16_t *) malloc(200);\n\n  a += 6;\n  b += 6;\n\n  MOA_Seed(0);\n\n  for (i = 0; i < 30; i++) a[i] = MOA_Random_W(16, 1);\n\n  gf.multiply_region.w32(&gf, a, b, 0x1234, 30*2, 0);\n\n  printf(\"a: 0x%lx    b: 0x%lx\\n\", (unsigned long) a, (unsigned long) b);\n\n  for (i = 0; i < 30; i += 10) {\n    printf(\"\\n\");\n    printf(\"  \");\n    for (j = 0; j < 10; j++) printf(\" %4d\", i+j);\n    printf(\"\\n\");\n\n    printf(\"a:\");\n    for (j = 0; j < 10; j++) printf(\" %04x\", a[i+j]);\n    printf(\"\\n\");\n\n    printf(\"b:\");\n    for (j = 0; j < 10; j++) printf(\" %04x\", b[i+j]);\n    printf(\"\\n\");\n    printf(\"\\n\");\n  }\n\n  for (i = 0; i < 15; i ++) {\n    printf(\"Word %2d: 0x%04x * 0x1234 = 0x%04x    \", i,\n           gf.extract_word.w32(&gf, a, 30*2, i),\n           gf.extract_word.w32(&gf, b, 30*2, i));\n    printf(\"Word %2d: 0x%04x * 0x1234 = 0x%04x\\n\", i+15,\n           gf.extract_word.w32(&gf, a, 30*2, i+15),\n           gf.extract_word.w32(&gf, b, 30*2, i+15));\n  }\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/examples/gf_example_6.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_example_6.c\n *\n * Demonstrating altmap and extract_word\n */\n\n#include <stdio.h>\n#include <getopt.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n\n#include \"gf_complete.h\"\n#include \"gf_rand.h\"\n\nvoid usage(char *s)\n{\n  fprintf(stderr, \"usage: gf_example_6\\n\");\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  uint32_t *a, *b;\n  int i, j;\n  gf_t gf, gf_16;\n\n  if (gf_init_hard(&gf_16, 16, GF_MULT_LOG_TABLE, GF_REGION_DEFAULT, GF_DIVIDE_DEFAULT,\n                   0, 0, 0, NULL, NULL) == 0) {\n    fprintf(stderr, \"gf_init_hard (6) failed\\n\");\n    exit(1);\n  }\n\n  if (gf_init_hard(&gf, 32, GF_MULT_COMPOSITE, GF_REGION_ALTMAP, GF_DIVIDE_DEFAULT, \n                   0, 2, 0, &gf_16, NULL) == 0) {\n    fprintf(stderr, \"gf_init_hard (32) failed\\n\");\n    exit(1);\n  }\n\n  a = (uint32_t *) malloc(200);\n  b = (uint32_t *) malloc(200);\n\n  a += 3;\n  b += 3;\n\n  MOA_Seed(0);\n\n  for (i = 0; i < 30; i++) a[i] = MOA_Random_W(32, 1);\n\n  gf.multiply_region.w32(&gf, a, b, 0x12345678, 30*4, 0);\n\n  printf(\"a: 0x%lx    b: 0x%lx\\n\", (unsigned long) a, (unsigned long) b);\n\n  for (i = 0; i < 30; i += 10) {\n    printf(\"\\n\");\n    printf(\"  \");\n    for (j = 0; j < 10; j++) printf(\" %8d\", i+j);\n    printf(\"\\n\");\n\n    printf(\"a:\");\n    for (j = 0; j < 10; j++) printf(\" %08x\", a[i+j]);\n    printf(\"\\n\");\n\n    printf(\"b:\");\n    for (j = 0; j < 10; j++) printf(\" %08x\", b[i+j]);\n    printf(\"\\n\");\n    printf(\"\\n\");\n  }\n\n  for (i = 0; i < 15; i ++) {\n    printf(\"Word %2d: 0x%08x * 0x12345678 = 0x%08x    \", i,\n           gf.extract_word.w32(&gf, a, 30*4, i),\n           gf.extract_word.w32(&gf, b, 30*4, i));\n    printf(\"Word %2d: 0x%08x * 0x12345678 = 0x%08x\\n\", i+15,\n           gf.extract_word.w32(&gf, a, 30*4, i+15),\n           gf.extract_word.w32(&gf, b, 30*4, i+15));\n  }\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/examples/gf_example_7.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_example_7.c\n *\n * Demonstrating extract_word and Cauchy\n */\n\n#include <stdio.h>\n#include <getopt.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n\n#include \"gf_complete.h\"\n#include \"gf_rand.h\"\n\nvoid usage(char *s)\n{\n  fprintf(stderr, \"usage: gf_example_7\\n\");\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  uint8_t *a, *b;\n  int i, j;\n  gf_t gf;\n\n  if (gf_init_hard(&gf, 3, GF_MULT_TABLE, GF_REGION_CAUCHY, GF_DIVIDE_DEFAULT, 0, 0, 0, NULL, NULL) == 0) {\n    fprintf(stderr, \"gf_init_hard failed\\n\");\n    exit(1);\n  }\n\n  a = (uint8_t *) malloc(3);\n  b = (uint8_t *) malloc(3);\n\n  MOA_Seed(0);\n\n  for (i = 0; i < 3; i++) a[i] = MOA_Random_W(8, 1);\n\n  gf.multiply_region.w32(&gf, a, b, 5, 3, 0);\n\n  printf(\"a: 0x%lx    b: 0x%lx\\n\", (unsigned long) a, (unsigned long) b);\n\n  printf(\"\\n\");\n  printf(\"a: 0x%02x 0x%02x 0x%02x\\n\", a[0], a[1], a[2]);\n  printf(\"b: 0x%02x 0x%02x 0x%02x\\n\", b[0], b[1], b[2]);\n  printf(\"\\n\");\n\n  printf(\"a bits:\");\n  for (i = 0; i < 3; i++) {\n    printf(\" \");\n    for (j = 7; j >= 0; j--) printf(\"%c\", (a[i] & (1 << j)) ? '1' : '0');\n  }\n  printf(\"\\n\");\n\n  printf(\"b bits:\");\n  for (i = 0; i < 3; i++) {\n    printf(\" \");\n    for (j = 7; j >= 0; j--) printf(\"%c\", (b[i] & (1 << j)) ? '1' : '0');\n  }\n  printf(\"\\n\");\n\n  printf(\"\\n\");\n  for (i = 0; i < 8; i++) {\n    printf(\"Word %2d: %d * 5 = %d\\n\", i,\n           gf.extract_word.w32(&gf, a, 3, i),\n           gf.extract_word.w32(&gf, b, 3, i));\n  }\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/include/gf_complete.h",
    "content": "/* \n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_complete.h\n *\n * The main include file for gf_complete. \n */\n\n#ifndef _GF_COMPLETE_H_\n#define _GF_COMPLETE_H_\n#include <stdint.h>\n\n#ifdef INTEL_SSE4\n  #ifdef __SSE4_2__\n    #include <nmmintrin.h>\n  #endif\n  #ifdef __SSE4_1__\n    #include <smmintrin.h>\n  #endif\n#endif\n\n#ifdef INTEL_SSSE3\n  #include <tmmintrin.h>\n#endif\n\n#ifdef INTEL_SSE2\n  #include <emmintrin.h>\n#endif\n\n#ifdef INTEL_SSE4_PCLMUL\n  #include <wmmintrin.h>\n#endif\n\n#if defined(ARM_NEON)\n  #include <arm_neon.h>\n#endif\n\n\n/* These are the different ways to perform multiplication.\n   Not all are implemented for all values of w.\n   See the paper for an explanation of how they work. */\n\ntypedef enum {GF_MULT_DEFAULT,\n              GF_MULT_SHIFT,\n              GF_MULT_CARRY_FREE,\n              GF_MULT_CARRY_FREE_GK,\n              GF_MULT_GROUP,\n              GF_MULT_BYTWO_p,\n              GF_MULT_BYTWO_b,\n              GF_MULT_TABLE,\n              GF_MULT_LOG_TABLE,\n              GF_MULT_LOG_ZERO,\n              GF_MULT_LOG_ZERO_EXT,\n              GF_MULT_SPLIT_TABLE,\n              GF_MULT_COMPOSITE } gf_mult_type_t;\n\n/* These are the different ways to optimize region \n   operations.  They are bits because you can compose them.\n   Certain optimizations only apply to certain gf_mult_type_t's.  \n   Again, please see documentation for how to use these */\n   \n#define GF_REGION_DEFAULT      (0x0)\n#define GF_REGION_DOUBLE_TABLE (0x1)\n#define GF_REGION_QUAD_TABLE   (0x2)\n#define GF_REGION_LAZY         (0x4)\n#define GF_REGION_SIMD         (0x8)\n#define GF_REGION_SSE          (0x8)\n#define GF_REGION_NOSIMD       (0x10)\n#define GF_REGION_NOSSE        (0x10)\n#define GF_REGION_ALTMAP       (0x20)\n#define GF_REGION_CAUCHY       (0x40)\n\ntypedef uint32_t gf_region_type_t;\n\n/* These are different ways to implement division.\n   Once again, it's best to use \"DEFAULT\".  However,\n   there are times when you may want to experiment\n   with the others. */\n\ntypedef enum { GF_DIVIDE_DEFAULT,\n               GF_DIVIDE_MATRIX,\n               GF_DIVIDE_EUCLID } gf_division_type_t;\n\n/* We support w=4,8,16,32,64 and 128 with their own data types and\n   operations for multiplication, division, etc.  We also support\n   a \"gen\" type so that you can do general gf arithmetic for any \n   value of w from 1 to 32.  You can perform a \"region\" operation\n   on these if you use \"CAUCHY\" as the mapping. \n */\n\ntypedef uint32_t    gf_val_32_t;\ntypedef uint64_t    gf_val_64_t;\ntypedef uint64_t   *gf_val_128_t;\n\nextern int _gf_errno;\nextern void gf_error();\n\ntypedef struct gf *GFP;\n\ntypedef union gf_func_a_b {\n    gf_val_32_t  (*w32) (GFP gf, gf_val_32_t a,  gf_val_32_t b);\n    gf_val_64_t  (*w64) (GFP gf, gf_val_64_t a,  gf_val_64_t b);\n    void         (*w128)(GFP gf, gf_val_128_t a, gf_val_128_t b, gf_val_128_t c);\n} gf_func_a_b;\n  \ntypedef union {\n  gf_val_32_t  (*w32) (GFP gf, gf_val_32_t a);\n  gf_val_64_t  (*w64) (GFP gf, gf_val_64_t a);\n  void         (*w128)(GFP gf, gf_val_128_t a, gf_val_128_t b);\n} gf_func_a;\n  \ntypedef union {\n  void  (*w32) (GFP gf, void *src, void *dest, gf_val_32_t val,  int bytes, int add);\n  void  (*w64) (GFP gf, void *src, void *dest, gf_val_64_t val,  int bytes, int add);\n  void  (*w128)(GFP gf, void *src, void *dest, gf_val_128_t val, int bytes, int add);\n} gf_region;\n\ntypedef union {\n  gf_val_32_t  (*w32) (GFP gf, void *start, int bytes, int index);\n  gf_val_64_t  (*w64) (GFP gf, void *start, int bytes, int index);\n  void         (*w128)(GFP gf, void *start, int bytes, int index, gf_val_128_t rv);\n} gf_extract;\n\ntypedef struct gf {\n  gf_func_a_b    multiply;\n  gf_func_a_b    divide;\n  gf_func_a      inverse;\n  gf_region      multiply_region;\n  gf_extract     extract_word;\n  void           *scratch;\n} gf_t;\n    \n/* Initializes the GF to defaults.  Pass it a pointer to a gf_t.\n   Returns 0 on failure, 1 on success. */\n\nextern int gf_init_easy(GFP gf, int w);\n\n/* Initializes the GF changing the defaults.\n   Returns 0 on failure, 1 on success.\n   Pass it a pointer to a gf_t.\n   For mult_type and divide_type, use one of gf_mult_type_t gf_divide_type_t .  \n   For region_type, OR together the GF_REGION_xxx's defined above.  \n   Use 0 as prim_poly for defaults.  Otherwise, the leading 1 is optional.\n   Use NULL for scratch_memory to have init_hard allocate memory.  Otherwise,\n   use gf_scratch_size() to determine how big scratch_memory has to be.\n */\n\nextern int gf_init_hard(GFP gf, \n                        int w, \n                        int mult_type, \n                        int region_type, \n                        int divide_type, \n                        uint64_t prim_poly,\n                        int arg1, \n                        int arg2,\n                        GFP base_gf,\n                        void *scratch_memory);\n\n/* Determines the size for scratch_memory.  \n   Returns 0 on failure and non-zero on success. */\n\nextern int gf_scratch_size(int w, \n                           int mult_type, \n                           int region_type, \n                           int divide_type, \n                           int arg1, \n                           int arg2);\n\n/* This reports the gf_scratch_size of a gf_t that has already been created */\n\nextern int gf_size(GFP gf);\n\n/* Frees scratch memory if gf_init_easy/gf_init_hard called malloc.\n   If recursive = 1, then it calls itself recursively on base_gf. */\n\nextern int gf_free(GFP gf, int recursive);\n\n/* This is support for inline single multiplications and divisions.\n   I know it's yucky, but if you've got to be fast, you've got to be fast.\n   We support inlining for w=4, w=8 and w=16.  \n\n   To use inline multiplication and division with w=4 or 8, you should use the \n   default gf_t, or one with a single table.  Otherwise, gf_w4/8_get_mult_table()\n   will return NULL. Similarly, with w=16, the gf_t must be LOG */\n\nuint8_t *gf_w4_get_mult_table(GFP gf);\nuint8_t *gf_w4_get_div_table(GFP gf);\n\n#define GF_W4_INLINE_MULTDIV(table, a, b) (table[((a)<<4)|(b)])\n\nuint8_t *gf_w8_get_mult_table(GFP gf);\nuint8_t *gf_w8_get_div_table(GFP gf);\n\n#define GF_W8_INLINE_MULTDIV(table, a, b) (table[(((uint32_t) (a))<<8)|(b)])\n\nuint16_t *gf_w16_get_log_table(GFP gf);\nuint16_t *gf_w16_get_mult_alog_table(GFP gf);\nuint16_t *gf_w16_get_div_alog_table(GFP gf);\n\n#define GF_W16_INLINE_MULT(log, alog, a, b) ((a) == 0 || (b) == 0) ? 0 : (alog[(uint32_t)log[a]+(uint32_t)log[b]])\n#define GF_W16_INLINE_DIV(log, alog, a, b) ((a) == 0 || (b) == 0) ? 0 : (alog[(int)log[a]-(int)log[b]])\n#endif\n"
  },
  {
    "path": "fst/layout/gf-complete/include/gf_general.h",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_general.h\n *\n * This file has helper routines for doing basic GF operations with any\n * legal value of w.  The problem is that w <= 32, w=64 and w=128 all have\n * different data types, which is a pain.  The procedures in this file try\n * to alleviate that pain.  They are used in gf_unit and gf_time.\n */\n\n#pragma once\n\n#include <stdio.h>\n#include <getopt.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n\n#include \"gf_complete.h\"\n\ntypedef union {\n  uint32_t w32;\n  uint64_t w64;\n  uint64_t w128[2];\n} gf_general_t;\n\nvoid gf_general_set_zero(gf_general_t *v, int w);\nvoid gf_general_set_one(gf_general_t *v, int w);\nvoid gf_general_set_two(gf_general_t *v, int w);\n\nint gf_general_is_zero(gf_general_t *v, int w);\nint gf_general_is_one(gf_general_t *v, int w);\nint gf_general_are_equal(gf_general_t *v1, gf_general_t *v2, int w);\n\nvoid gf_general_val_to_s(gf_general_t *v, int w, char *s, int hex);\nint  gf_general_s_to_val(gf_general_t *v, int w, char *s, int hex);\n\nvoid gf_general_set_random(gf_general_t *v, int w, int zero_ok);\n\nvoid gf_general_add(gf_t *gf, gf_general_t *a, gf_general_t *b, gf_general_t *c);\nvoid gf_general_multiply(gf_t *gf, gf_general_t *a, gf_general_t *b, gf_general_t *c);\nvoid gf_general_divide(gf_t *gf, gf_general_t *a, gf_general_t *b, gf_general_t *c);\nvoid gf_general_inverse(gf_t *gf, gf_general_t *a, gf_general_t *b);\n\nvoid gf_general_do_region_multiply(gf_t *gf, gf_general_t *a, \n                                   void *ra, void *rb, \n                                   int bytes, int xor);\n\nvoid gf_general_do_region_check(gf_t *gf, gf_general_t *a, \n                                void *orig_a, void *orig_target, void *final_target, \n                                int bytes, int xor);\n\n\n/* Which is M, D or I for multiply, divide or inverse. */\n\nvoid gf_general_set_up_single_timing_test(int w, void *ra, void *rb, int size);\nint  gf_general_do_single_timing_test(gf_t *gf, void *ra, void *rb, int size, char which);\n"
  },
  {
    "path": "fst/layout/gf-complete/include/gf_int.h",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_int.h\n *\n * Internal code for Galois field routines.  This is not meant for \n * users to include, but for the internal GF files to use. \n */\n\n#pragma once\n\n#include \"gf_complete.h\"\n\n#include <string.h>\n\nextern void     timer_start (double *t);\nextern double   timer_split (const double *t);\nextern void     galois_fill_random (void *buf, int len, unsigned int seed);\n\ntypedef struct {\n  int mult_type;\n  int region_type;\n  int divide_type;\n  int w;\n  uint64_t prim_poly;\n  int free_me;\n  int arg1;\n  int arg2;\n  gf_t *base_gf;\n  void *private;\n} gf_internal_t;\n\nextern int gf_w4_init (gf_t *gf);\nextern int gf_w4_scratch_size(int mult_type, int region_type, int divide_type, int arg1, int arg2);\n\nextern int gf_w8_init (gf_t *gf);\nextern int gf_w8_scratch_size(int mult_type, int region_type, int divide_type, int arg1, int arg2);\n\nextern int gf_w16_init (gf_t *gf);\nextern int gf_w16_scratch_size(int mult_type, int region_type, int divide_type, int arg1, int arg2);\n\nextern int gf_w32_init (gf_t *gf);\nextern int gf_w32_scratch_size(int mult_type, int region_type, int divide_type, int arg1, int arg2);\n\nextern int gf_w64_init (gf_t *gf);\nextern int gf_w64_scratch_size(int mult_type, int region_type, int divide_type, int arg1, int arg2);\n\nextern int gf_w128_init (gf_t *gf);\nextern int gf_w128_scratch_size(int mult_type, int region_type, int divide_type, int arg1, int arg2);\n\nextern int gf_wgen_init (gf_t *gf);\nextern int gf_wgen_scratch_size(int w, int mult_type, int region_type, int divide_type, int arg1, int arg2);\n\nvoid gf_wgen_cauchy_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor);\ngf_val_32_t gf_wgen_extract_word(gf_t *gf, void *start, int bytes, int index);\n\nextern void gf_alignment_error(char *s, int a);\n\nextern uint32_t gf_bitmatrix_inverse(uint32_t y, int w, uint32_t pp);\n\n/* This returns the correct default for prim_poly when base is used as the base\n   field for COMPOSITE.  It returns 0 if we don't have a default prim_poly. */\n\nextern uint64_t gf_composite_get_default_poly(gf_t *base);\n\n/* This structure lets you define a region multiply.  It helps because you can handle\n   unaligned portions of the data with the procedures below, which really cleans\n   up the code. */\n\ntypedef struct {\n  gf_t *gf;\n  void *src;\n  void *dest;\n  int bytes;\n  uint64_t val;\n  int xor;\n  int align;           /* The number of bytes to which to align. */\n  void *s_start;       /* The start and the top of the aligned region. */\n  void *d_start;\n  void *s_top;\n  void *d_top;\n} gf_region_data;\n\n/* This lets you set up one of these in one call. It also sets the start/top pointers. */\n\nvoid gf_set_region_data(gf_region_data *rd,\n                        gf_t *gf,\n                        void *src,\n                        void *dest,\n                        int bytes,\n                        uint64_t val,\n                        int xor,\n                        int align);\n\n/* This performs gf->multiply.32() on all of the unaligned bytes in the beginning of the region */\n\nextern void gf_do_initial_region_alignment(gf_region_data *rd);\n\n/* This performs gf->multiply.32() on all of the unaligned bytes in the end of the region */\n\nextern void gf_do_final_region_alignment(gf_region_data *rd);\n\nextern void gf_two_byte_region_table_multiply(gf_region_data *rd, uint16_t *base);\n\nextern void gf_multby_zero(void *dest, int bytes, int xor);\nextern void gf_multby_one(void *src, void *dest, int bytes, int xor);\n\ntypedef enum {GF_E_MDEFDIV, /* Dev != Default && Mult == Default */\n              GF_E_MDEFREG, /* Reg != Default && Mult == Default */\n              GF_E_MDEFARG, /* Args != Default && Mult == Default */\n              GF_E_DIVCOMP, /* Mult == Composite && Div != Default */\n              GF_E_CAUCOMP, /* Mult == Composite && Reg == CAUCHY */\n              GF_E_DOUQUAD, /* Reg == DOUBLE && Reg == QUAD */\n              GF_E_SIMD_NO, /* Reg == SIMD && Reg == NOSIMD */\n              GF_E_CAUCHYB, /* Reg == CAUCHY && Other Reg */\n              GF_E_CAUGT32, /* Reg == CAUCHY && w > 32*/\n              GF_E_ARG1SET, /* Arg1 != 0 && Mult \\notin COMPOSITE/SPLIT/GROUP */\n              GF_E_ARG2SET, /* Arg2 != 0 && Mult \\notin SPLIT/GROUP */\n              GF_E_MATRIXW, /* Div == MATRIX && w > 32 */\n              GF_E_BAD___W, /* Illegal w */\n              GF_E_DOUBLET, /* Reg == DOUBLE && Mult != TABLE */\n              GF_E_DOUBLEW, /* Reg == DOUBLE && w \\notin {4,8} */\n              GF_E_DOUBLEJ, /* Reg == DOUBLE && other Reg */\n              GF_E_DOUBLEL, /* Reg == DOUBLE & LAZY but w = 4 */\n              GF_E_QUAD__T, /* Reg == QUAD && Mult != TABLE */\n              GF_E_QUAD__W, /* Reg == QUAD && w != 4 */\n              GF_E_QUAD__J, /* Reg == QUAD && other Reg */\n              GF_E_LAZY__X, /* Reg == LAZY && not DOUBLE or QUAD*/\n              GF_E_ALTSHIF, /* Mult == Shift && Reg == ALTMAP */\n              GF_E_SSESHIF, /* Mult == Shift && Reg == SIMD|NOSIMD */\n              GF_E_ALT_CFM, /* Mult == CARRY_FREE && Reg == ALTMAP */\n              GF_E_SSE_CFM, /* Mult == CARRY_FREE && Reg == SIMD|NOSIMD */\n              GF_E_PCLMULX, /* Mult == Carry_Free && No PCLMUL */\n              GF_E_ALT_BY2, /* Mult == Bytwo_x && Reg == ALTMAP */\n              GF_E_BY2_SSE, /* Mult == Bytwo_x && Reg == SSE && No SSE2 */\n              GF_E_LOGBADW, /* Mult == LOGx, w too big*/\n              GF_E_LOG___J, /* Mult == LOGx, && Reg == SSE|ALTMAP|NOSSE */\n              GF_E_ZERBADW, /* Mult == LOG_ZERO, w \\notin {8,16} */\n              GF_E_ZEXBADW, /* Mult == LOG_ZERO_EXT, w != 8 */\n              GF_E_LOGPOLY, /* Mult == LOG & poly not primitive */\n              GF_E_GR_ARGX, /* Mult == GROUP, Bad arg1/2 */\n              GF_E_GR_W_48, /* Mult == GROUP, w \\in { 4, 8 } */\n              GF_E_GR_W_16, /* Mult == GROUP, w == 16, arg1 != 4 || arg2 != 4 */\n              GF_E_GR_128A, /* Mult == GROUP, w == 128, bad args */\n              GF_E_GR_A_27, /* Mult == GROUP, either arg > 27 */\n              GF_E_GR_AR_W, /* Mult == GROUP, either arg > w  */\n              GF_E_GR____J, /* Mult == GROUP, Reg == SSE|ALTMAP|NOSSE */\n              GF_E_TABLE_W, /* Mult == TABLE, w too big */\n              GF_E_TAB_SSE, /* Mult == TABLE, SIMD|NOSIMD only apply to w == 4 */\n              GF_E_TABSSE3, /* Mult == TABLE, Need SSSE3 for SSE */\n              GF_E_TAB_ALT, /* Mult == TABLE, Reg == ALTMAP */\n              GF_E_SP128AR, /* Mult == SPLIT, w=128, Bad arg1/arg2 */\n              GF_E_SP128AL, /* Mult == SPLIT, w=128, SSE requires ALTMAP */\n              GF_E_SP128AS, /* Mult == SPLIT, w=128, ALTMAP requires SSE */\n              GF_E_SP128_A, /* Mult == SPLIT, w=128, ALTMAP only with 4/128 */\n              GF_E_SP128_S, /* Mult == SPLIT, w=128, SSE only with 4/128 */\n              GF_E_SPLIT_W, /* Mult == SPLIT, Bad w (8, 16, 32, 64, 128)  */\n              GF_E_SP_16AR, /* Mult == SPLIT, w=16, Bad arg1/arg2 */\n              GF_E_SP_16_A, /* Mult == SPLIT, w=16, ALTMAP only with 4/16 */\n              GF_E_SP_16_S, /* Mult == SPLIT, w=16, SSE only with 4/16 */\n              GF_E_SP_32AR, /* Mult == SPLIT, w=32, Bad arg1/arg2 */\n              GF_E_SP_32AS, /* Mult == SPLIT, w=32, ALTMAP requires SSE */\n              GF_E_SP_32_A, /* Mult == SPLIT, w=32, ALTMAP only with 4/32 */\n              GF_E_SP_32_S, /* Mult == SPLIT, w=32, SSE only with 4/32 */\n              GF_E_SP_64AR, /* Mult == SPLIT, w=64, Bad arg1/arg2 */\n              GF_E_SP_64AS, /* Mult == SPLIT, w=64, ALTMAP requires SSE */\n              GF_E_SP_64_A, /* Mult == SPLIT, w=64, ALTMAP only with 4/64 */\n              GF_E_SP_64_S, /* Mult == SPLIT, w=64, SSE only with 4/64 */\n              GF_E_SP_8_AR, /* Mult == SPLIT, w=8, Bad arg1/arg2 */\n              GF_E_SP_8__A, /* Mult == SPLIT, w=8, no ALTMAP */\n              GF_E_SP_SSE3, /* Mult == SPLIT, Need SSSE3 for SSE */\n              GF_E_COMP_A2, /* Mult == COMP, arg1 must be = 2 */\n              GF_E_COMP_SS, /* Mult == COMP, SIMD|NOSIMD */\n              GF_E_COMP__W, /* Mult == COMP, Bad w. */\n              GF_E_UNKFLAG, /* Unknown flag in create_from.... */\n              GF_E_UNKNOWN, /* Unknown mult_type. */\n              GF_E_UNK_REG, /* Unknown region_type. */\n              GF_E_UNK_DIV, /* Unknown divide_type. */\n              GF_E_CFM___W, /* Mult == CFM,  Bad w. */\n              GF_E_CFM4POL, /* Mult == CFM & Prim Poly has high bits set. */\n              GF_E_CFM8POL, /* Mult == CFM & Prim Poly has high bits set. */\n              GF_E_CF16POL, /* Mult == CFM & Prim Poly has high bits set. */\n              GF_E_CF32POL, /* Mult == CFM & Prim Poly has high bits set. */\n              GF_E_CF64POL, /* Mult == CFM & Prim Poly has high bits set. */\n              GF_E_FEWARGS, /* Too few args in argc/argv. */\n              GF_E_BADPOLY, /* Bad primitive polynomial -- too many bits set. */\n              GF_E_COMP_PP, /* Bad primitive polynomial -- bigger than sub-field. */\n              GF_E_COMPXPP, /* Can't derive a default pp for composite field. */\n              GF_E_BASE__W, /* Composite -- Base field is the wrong size. */\n              GF_E_TWOMULT, /* In create_from... two -m's. */\n              GF_E_TWO_DIV, /* In create_from... two -d's. */\n              GF_E_POLYSPC, /* Bad numbera after -p. */\n              GF_E_SPLITAR, /* Ran out of arguments in SPLIT */\n              GF_E_SPLITNU, /* Arguments not integers in SPLIT. */\n              GF_E_GROUPAR, /* Ran out of arguments in GROUP */\n              GF_E_GROUPNU, /* Arguments not integers in GROUP. */\n              GF_E_DEFAULT } gf_error_type_t;\n\n"
  },
  {
    "path": "fst/layout/gf-complete/include/gf_method.h",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_method.h\n *\n * Parses argv to figure out the flags and arguments.  Creates the gf.\n */\n\n#pragma once\n\n#include \"gf_complete.h\"\n\n/* Parses argv starting at \"starting\".  \n   \n   Returns 0 on failure.\n   On success, it returns one past the last argument it read in argv. */\n\nextern int create_gf_from_argv(gf_t *gf, int w, int argc, char **argv, int starting);\n"
  },
  {
    "path": "fst/layout/gf-complete/include/gf_rand.h",
    "content": "/* \n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_rand.h\n *\n * Random number generation, using the \"Mother of All\" random number generator.  */\n\n#pragma once\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n/* These are all pretty self-explanatory */\nuint32_t MOA_Random_32();\nuint64_t MOA_Random_64();\nvoid     MOA_Random_128(uint64_t *x);\nuint32_t MOA_Random_W(int w, int zero_ok);\nvoid MOA_Fill_Random_Region (void *reg, int size);   /* reg should be aligned to 4 bytes, but\n                                                        size can be anything. */\nvoid     MOA_Seed(uint32_t seed);\n"
  },
  {
    "path": "fst/layout/gf-complete/include/gf_w16.h",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_w16.h\n *\n * Defines and data structures for 16-bit Galois fields\n */\n\n#ifndef GF_COMPLETE_GF_W16_H\n#define GF_COMPLETE_GF_W16_H\n\n#include <stdint.h>\n\n#define GF_FIELD_WIDTH (16)\n#define GF_FIELD_SIZE (1 << GF_FIELD_WIDTH)\n#define GF_MULT_GROUP_SIZE GF_FIELD_SIZE-1\n\n#define GF_BASE_FIELD_WIDTH (8)\n#define GF_BASE_FIELD_SIZE       (1 << GF_BASE_FIELD_WIDTH)\n\nstruct gf_w16_logtable_data {\n    uint16_t      log_tbl[GF_FIELD_SIZE];\n    uint16_t      antilog_tbl[GF_FIELD_SIZE * 2];\n    uint16_t      inv_tbl[GF_FIELD_SIZE];\n    uint16_t      *d_antilog;\n};\n\nstruct gf_w16_zero_logtable_data {\n    int           log_tbl[GF_FIELD_SIZE];\n    uint16_t      _antilog_tbl[GF_FIELD_SIZE * 4];\n    uint16_t      *antilog_tbl;\n    uint16_t      inv_tbl[GF_FIELD_SIZE];\n};\n\nstruct gf_w16_lazytable_data {\n    uint16_t      log_tbl[GF_FIELD_SIZE];\n    uint16_t      antilog_tbl[GF_FIELD_SIZE * 2];\n    uint16_t      inv_tbl[GF_FIELD_SIZE];\n    uint16_t      *d_antilog;\n    uint16_t      lazytable[GF_FIELD_SIZE];\n};\n\nstruct gf_w16_bytwo_data {\n    uint64_t prim_poly;\n    uint64_t mask1;\n    uint64_t mask2;\n};\n\nstruct gf_w16_split_8_8_data {\n    uint16_t      tables[3][256][256];\n};\n\nstruct gf_w16_group_4_4_data {\n    uint16_t reduce[16];\n    uint16_t shift[16];\n};\n\nstruct gf_w16_composite_data {\n  uint8_t *mult_table;\n};\n\nvoid gf_w16_neon_split_init(gf_t *gf);\n\n#endif /* GF_COMPLETE_GF_W16_H */\n"
  },
  {
    "path": "fst/layout/gf-complete/include/gf_w32.h",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_w32.h\n *\n * Defines and data structures for 32-bit Galois fields\n */\n\n#ifndef GF_COMPLETE_GF_W32_H\n#define GF_COMPLETE_GF_W32_H\n\n#include <stdint.h>\n\n#define GF_FIELD_WIDTH (32)\n#define GF_FIRST_BIT (1 << 31)\n\n#define GF_BASE_FIELD_WIDTH (16)\n#define GF_BASE_FIELD_SIZE       (1 << GF_BASE_FIELD_WIDTH)\n#define GF_BASE_FIELD_GROUP_SIZE  GF_BASE_FIELD_SIZE-1\n#define GF_MULTBY_TWO(p) (((p) & GF_FIRST_BIT) ? (((p) << 1) ^ h->prim_poly) : (p) << 1)\n\nstruct gf_split_2_32_lazy_data {\n    uint32_t      tables[16][4];\n    uint32_t      last_value;\n};\n\nstruct gf_w32_split_8_8_data {\n    uint32_t      tables[7][256][256];\n    uint32_t      region_tables[4][256];\n    uint32_t      last_value;\n};\n\nstruct gf_w32_group_data {\n    uint32_t *reduce;\n    uint32_t *shift;\n    int      tshift;\n    uint64_t rmask;\n    uint32_t *memory;\n};\n\nstruct gf_split_16_32_lazy_data {\n    uint32_t      tables[2][(1<<16)];\n    uint32_t      last_value;\n};\n\nstruct gf_split_8_32_lazy_data {\n    uint32_t      tables[4][256];\n    uint32_t      last_value;\n};\n\nstruct gf_split_4_32_lazy_data {\n    uint32_t      tables[8][16];\n    uint32_t      last_value;\n};\n\nstruct gf_w32_bytwo_data {\n    uint64_t prim_poly;\n    uint64_t mask1;\n    uint64_t mask2;\n};\n\nstruct gf_w32_composite_data {\n  uint16_t *log;\n  uint16_t *alog;\n};\n\nvoid gf_w32_neon_split_init(gf_t *gf);\n\n#endif /* GF_COMPLETE_GF_W32_H */\n"
  },
  {
    "path": "fst/layout/gf-complete/include/gf_w4.h",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_w4.h\n *\n * Defines and data structures for 4-bit Galois fields\n */\n\n#ifndef GF_COMPLETE_GF_W4_H\n#define GF_COMPLETE_GF_W4_H\n\n#include <stdint.h>\n\n#define GF_FIELD_WIDTH      4\n#define GF_DOUBLE_WIDTH     (GF_FIELD_WIDTH*2)\n#define GF_FIELD_SIZE       (1 << GF_FIELD_WIDTH)\n#define GF_MULT_GROUP_SIZE       (GF_FIELD_SIZE-1)\n\n/* ------------------------------------------------------------\n   JSP: Each implementation has its own data, which is allocated\n   at one time as part of the handle. For that reason, it\n   shouldn't be hierarchical -- i.e. one should be able to\n   allocate it with one call to malloc. */\n\nstruct gf_logtable_data {\n    uint8_t      log_tbl[GF_FIELD_SIZE];\n    uint8_t      antilog_tbl[GF_FIELD_SIZE * 2];\n    uint8_t      *antilog_tbl_div;\n};\n\nstruct gf_single_table_data {\n    uint8_t      mult[GF_FIELD_SIZE][GF_FIELD_SIZE];\n    uint8_t      div[GF_FIELD_SIZE][GF_FIELD_SIZE];\n};\n\nstruct gf_double_table_data {\n    uint8_t      div[GF_FIELD_SIZE][GF_FIELD_SIZE];\n    uint8_t      mult[GF_FIELD_SIZE][GF_FIELD_SIZE*GF_FIELD_SIZE];\n};\nstruct gf_quad_table_data {\n    uint8_t      div[GF_FIELD_SIZE][GF_FIELD_SIZE];\n    uint16_t     mult[GF_FIELD_SIZE][(1<<16)];\n};\n\nstruct gf_quad_table_lazy_data {\n    uint8_t      div[GF_FIELD_SIZE][GF_FIELD_SIZE];\n    uint8_t      smult[GF_FIELD_SIZE][GF_FIELD_SIZE];\n    uint16_t     mult[(1 << 16)];\n};\n\nstruct gf_bytwo_data {\n    uint64_t prim_poly;\n    uint64_t mask1;\n    uint64_t mask2;\n};\n\n// ARM NEON init functions\nint gf_w4_neon_cfm_init(gf_t *gf);\nvoid gf_w4_neon_single_table_init(gf_t *gf);\n\n#endif /* GF_COMPLETE_GF_W4_H */\n"
  },
  {
    "path": "fst/layout/gf-complete/include/gf_w64.h",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_w64.h\n *\n * Defines and data structures for 64-bit Galois fields\n */\n\n#ifndef GF_COMPLETE_GF_W64_H\n#define GF_COMPLETE_GF_W64_H\n\n#include <stdint.h>\n\n#define GF_FIELD_WIDTH (64)\n#define GF_FIRST_BIT (1ULL << 63)\n\n#define GF_BASE_FIELD_WIDTH (32)\n#define GF_BASE_FIELD_SIZE       (1ULL << GF_BASE_FIELD_WIDTH)\n#define GF_BASE_FIELD_GROUP_SIZE  GF_BASE_FIELD_SIZE-1\n\nstruct gf_w64_group_data {\n    uint64_t *reduce;\n    uint64_t *shift;\n    uint64_t *memory;\n};\n\nstruct gf_split_4_64_lazy_data {\n    uint64_t      tables[16][16];\n    uint64_t      last_value;\n};\n\nstruct gf_split_8_64_lazy_data {\n    uint64_t      tables[8][(1<<8)];\n    uint64_t      last_value;\n};\n\nstruct gf_split_16_64_lazy_data {\n    uint64_t      tables[4][(1<<16)];\n    uint64_t      last_value;\n};\n\nstruct gf_split_8_8_data {\n    uint64_t      tables[15][256][256];\n};\n\nvoid gf_w64_neon_split_init(gf_t *gf);\n\n#endif /* GF_COMPLETE_GF_W64_H */\n"
  },
  {
    "path": "fst/layout/gf-complete/include/gf_w8.h",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_w8.c\n *\n * Defines and data stuctures for 8-bit Galois fields\n */\n\n#ifndef GF_COMPLETE_GF_W8_H\n#define GF_COMPLETE_GF_W8_H\n\n#include \"gf_int.h\"\n#include <stdint.h>\n\n#define GF_FIELD_WIDTH (8)\n#define GF_FIELD_SIZE       (1 << GF_FIELD_WIDTH)\n#define GF_HALF_SIZE       (1 << (GF_FIELD_WIDTH/2))\n#define GF_MULT_GROUP_SIZE       GF_FIELD_SIZE-1\n\n#define GF_BASE_FIELD_WIDTH (4)\n#define GF_BASE_FIELD_SIZE       (1 << GF_BASE_FIELD_WIDTH)\n\nstruct gf_w8_logtable_data {\n    uint8_t         log_tbl[GF_FIELD_SIZE];\n    uint8_t         antilog_tbl[GF_FIELD_SIZE * 2];\n    uint8_t         inv_tbl[GF_FIELD_SIZE];\n};\n\nstruct gf_w8_logzero_table_data {\n    short           log_tbl[GF_FIELD_SIZE];  /* Make this signed, so that we can divide easily */\n    uint8_t         antilog_tbl[512+512+1];\n    uint8_t         *div_tbl;\n    uint8_t         *inv_tbl;\n};\n\nstruct gf_w8_logzero_small_table_data {\n    short           log_tbl[GF_FIELD_SIZE];  /* Make this signed, so that we can divide easily */\n    uint8_t         antilog_tbl[255*3];\n    uint8_t         inv_tbl[GF_FIELD_SIZE];\n    uint8_t         *div_tbl;\n};\n\nstruct gf_w8_composite_data {\n  uint8_t *mult_table;\n};\n\n/* Don't change the order of these relative to gf_w8_half_table_data */\n\nstruct gf_w8_default_data {\n  uint8_t     high[GF_FIELD_SIZE][GF_HALF_SIZE];\n  uint8_t     low[GF_FIELD_SIZE][GF_HALF_SIZE];\n  uint8_t     divtable[GF_FIELD_SIZE][GF_FIELD_SIZE];\n  uint8_t     multtable[GF_FIELD_SIZE][GF_FIELD_SIZE];\n};\n\nstruct gf_w8_half_table_data {\n  uint8_t     high[GF_FIELD_SIZE][GF_HALF_SIZE];\n  uint8_t     low[GF_FIELD_SIZE][GF_HALF_SIZE];\n};\n\nstruct gf_w8_single_table_data {\n  uint8_t     divtable[GF_FIELD_SIZE][GF_FIELD_SIZE];\n  uint8_t     multtable[GF_FIELD_SIZE][GF_FIELD_SIZE];\n};\n\nstruct gf_w8_double_table_data {\n    uint8_t         div[GF_FIELD_SIZE][GF_FIELD_SIZE];\n    uint16_t        mult[GF_FIELD_SIZE][GF_FIELD_SIZE*GF_FIELD_SIZE];\n};\n\nstruct gf_w8_double_table_lazy_data {\n    uint8_t         div[GF_FIELD_SIZE][GF_FIELD_SIZE];\n    uint8_t         smult[GF_FIELD_SIZE][GF_FIELD_SIZE];\n    uint16_t        mult[GF_FIELD_SIZE*GF_FIELD_SIZE];\n};\n\nstruct gf_w4_logtable_data {\n    uint8_t         log_tbl[GF_BASE_FIELD_SIZE];\n    uint8_t         antilog_tbl[GF_BASE_FIELD_SIZE * 2];\n    uint8_t         *antilog_tbl_div;\n};\n\nstruct gf_w4_single_table_data {\n    uint8_t         div[GF_BASE_FIELD_SIZE][GF_BASE_FIELD_SIZE];\n    uint8_t         mult[GF_BASE_FIELD_SIZE][GF_BASE_FIELD_SIZE];\n};\n\nstruct gf_w8_bytwo_data {\n    uint64_t prim_poly;\n    uint64_t mask1;\n    uint64_t mask2;\n};\n\nint gf_w8_neon_cfm_init(gf_t *gf);\nvoid gf_w8_neon_split_init(gf_t *gf);\n\n#endif /* GF_COMPLETE_GF_W8_H */\n"
  },
  {
    "path": "fst/layout/gf-complete/m4/ax_check_compile_flag.m4",
    "content": "# ===========================================================================\n#   http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS])\n#\n# DESCRIPTION\n#\n#   Check whether the given FLAG works with the current language's compiler\n#   or gives an error.  (Warnings, however, are ignored)\n#\n#   ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on\n#   success/failure.\n#\n#   If EXTRA-FLAGS is defined, it is added to the current language's default\n#   flags (e.g. CFLAGS) when the check is done.  The check is thus made with\n#   the flags: \"CFLAGS EXTRA-FLAGS FLAG\".  This can for example be used to\n#   force the compiler to issue an error when a bad flag is given.\n#\n#   NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this\n#   macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.\n#\n# LICENSE\n#\n#   Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>\n#   Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>\n#\n#   This program is free software: you can redistribute it and/or modify it\n#   under the terms of the GNU General Public License as published by the\n#   Free Software Foundation, either version 3 of the License, or (at your\n#   option) any later version.\n#\n#   This program is distributed in the hope that it will be useful, but\n#   WITHOUT ANY WARRANTY; without even the implied warranty of\n#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\n#   Public License for more details.\n#\n#   You should have received a copy of the GNU General Public License along\n#   with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n#   As a special exception, the respective Autoconf Macro's copyright owner\n#   gives unlimited permission to copy, distribute and modify the configure\n#   scripts that are the output of Autoconf when processing the Macro. You\n#   need not follow the terms of the GNU General Public License when using\n#   or distributing such scripts, even though portions of the text of the\n#   Macro appear in them. The GNU General Public License (GPL) does govern\n#   all other use of the material that constitutes the Autoconf Macro.\n#\n#   This special exception to the GPL applies to versions of the Autoconf\n#   Macro released by the Autoconf Archive. When you make and distribute a\n#   modified version of the Autoconf Macro, you may extend this special\n#   exception to the GPL to apply to your modified version as well.\n\n#serial 2\n\nAC_DEFUN([AX_CHECK_COMPILE_FLAG],\n[AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX\nAS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl\nAC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [\n  ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS\n  _AC_LANG_PREFIX[]FLAGS=\"$[]_AC_LANG_PREFIX[]FLAGS $4 $1\"\n  AC_COMPILE_IFELSE([AC_LANG_PROGRAM()],\n    [AS_VAR_SET(CACHEVAR,[yes])],\n    [AS_VAR_SET(CACHEVAR,[no])])\n  _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])\nAS_IF([test x\"AS_VAR_GET(CACHEVAR)\" = xyes],\n  [m4_default([$2], :)],\n  [m4_default([$3], :)])\nAS_VAR_POPDEF([CACHEVAR])dnl\n])dnl AX_CHECK_COMPILE_FLAGS\n"
  },
  {
    "path": "fst/layout/gf-complete/m4/ax_ext.m4",
    "content": "#\n# Updated by KMG to support -DINTEL_SSE for GF-Complete\n#\n# ===========================================================================\n#          http://www.gnu.org/software/autoconf-archive/ax_ext.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_EXT\n#\n# DESCRIPTION\n#\n#   Find supported SIMD extensions by requesting cpuid. When an SIMD\n#   extension is found, the -m\"simdextensionname\" is added to SIMD_FLAGS if\n#   compiler supports it. For example, if \"sse2\" is available, then \"-msse2\"\n#   is added to SIMD_FLAGS.\n#\n#   This macro calls:\n#\n#     AC_SUBST(SIMD_FLAGS)\n#\n#   And defines:\n#\n#     HAVE_MMX / HAVE_SSE / HAVE_SSE2 / HAVE_SSE3 / HAVE_SSSE3 / HAVE_SSE4.1 / HAVE_SSE4.2 / HAVE_AVX\n#\n# LICENSE\n#\n#   Copyright (c) 2007 Christophe Tournayre <turn3r@users.sourceforge.net>\n#   Copyright (c) 2013 Michael Petch <mpetch@capp-sysware.com>\n#\n#   Copying and distribution of this file, with or without modification, are\n#   permitted in any medium without royalty provided the copyright notice\n#   and this notice are preserved. This file is offered as-is, without any\n#   warranty.\n\n#serial 12\n\nAC_DEFUN([AX_EXT],\n[\n  AC_REQUIRE([AC_CANONICAL_HOST])\n\n  case $host_cpu in\n    aarch64*)\n      AC_DEFINE(HAVE_ARCH_AARCH64,,[targeting AArch64])\n      SIMD_FLAGS=\"$SIMD_FLAGS -DARCH_AARCH64\"\n\n      AC_CACHE_CHECK([whether NEON is supported], [ax_cv_have_neon_ext],\n          [\n            # TODO: detect / cross-compile\n            ax_cv_have_neon_ext=yes\n          ])\n      AC_CACHE_CHECK([whether cryptographic extension is supported], [ax_cv_have_arm_crypt_ext],\n          [\n            # TODO: detect / cross-compile\n            ax_cv_have_arm_crypt_ext=yes\n          ])\n\n      if test \"$ax_cv_have_arm_crypt_ext\" = yes; then\n        AC_DEFINE(HAVE_ARM_CRYPT_EXT,,[Support ARM cryptographic extension])\n      fi\n\n      if test \"$ax_cv_have_neon_ext\" = yes; then\n        AC_DEFINE(HAVE_NEON,,[Support NEON instructions])\n      fi\n\n      if test \"$ax_cv_have_arm_crypt_ext\" = yes && test \"$ax_cv_have_neon_ext\" = yes; then\n          AX_CHECK_COMPILE_FLAG(-march=armv8-a+simd+crypto,\n                                SIMD_FLAGS=\"$SIMD_FLAGS -march=armv8-a+simd+crypto -DARM_CRYPT -DARM_NEON\", [])\n      elif test \"$ax_cv_have_arm_crypt_ext\" = yes; then\n          AX_CHECK_COMPILE_FLAG(-march=armv8-a+crypto,\n                                SIMD_FLAGS=\"$SIMD_FLAGS -march=armv8-a+crypto -DARM_CRYPT\", [])\n      elif test \"$ax_cv_have_neon_ext\" = yes; then\n          AX_CHECK_COMPILE_FLAG(-march=armv8-a+simd,\n                                SIMD_FLAGS=\"$SIMD_FLAGS -march=armv8-a+simd -DARM_NEON\", [])\n      fi\n    ;;\n\n    arm*)\n      AC_CACHE_CHECK([whether NEON is supported], [ax_cv_have_neon_ext],\n          [\n            # TODO: detect / cross-compile\n            ax_cv_have_neon_ext=yes\n          ])\n\n      if test \"$ax_cv_have_neon_ext\" = yes; then\n        AC_DEFINE(HAVE_NEON,,[Support NEON instructions])\n        AX_CHECK_COMPILE_FLAG(-mfpu=neon,\n                                SIMD_FLAGS=\"$SIMD_FLAGS -mfpu=neon -DARM_NEON\", [])\n      fi\n    ;;\n\n    powerpc*)\n      AC_CACHE_CHECK([whether altivec is supported], [ax_cv_have_altivec_ext],\n          [\n            if test `/usr/sbin/sysctl -a 2>/dev/null| grep -c hw.optional.altivec` != 0; then\n                if test `/usr/sbin/sysctl -n hw.optional.altivec` = 1; then\n                  ax_cv_have_altivec_ext=yes\n                fi\n            fi\n          ])\n\n          if test \"$ax_cv_have_altivec_ext\" = yes; then\n            AC_DEFINE(HAVE_ALTIVEC,,[Support Altivec instructions])\n            AX_CHECK_COMPILE_FLAG(-faltivec, SIMD_FLAGS=\"$SIMD_FLAGS -faltivec\", [])\n          fi\n    ;;\n\n\n    i[[3456]]86*|x86_64*|amd64*)\n\n      AC_REQUIRE([AX_GCC_X86_CPUID])\n      AC_REQUIRE([AX_GCC_X86_AVX_XGETBV])\n\n      AX_GCC_X86_CPUID(0x00000001)\n      ecx=`echo $ax_cv_gcc_x86_cpuid_0x00000001 | cut -d \":\" -f 3`\n      edx=`echo $ax_cv_gcc_x86_cpuid_0x00000001 | cut -d \":\" -f 4`\n\n      AC_CACHE_CHECK([whether mmx is supported], [ax_cv_have_mmx_ext],\n      [\n        ax_cv_have_mmx_ext=no\n        if test \"$((0x$edx>>23&0x01))\" = 1; then\n          ax_cv_have_mmx_ext=yes\n        fi\n      ])\n\n      AC_CACHE_CHECK([whether sse is supported], [ax_cv_have_sse_ext],\n      [\n        ax_cv_have_sse_ext=no\n        if test \"$((0x$edx>>25&0x01))\" = 1; then\n          ax_cv_have_sse_ext=yes\n        fi\n      ])\n\n      AC_CACHE_CHECK([whether sse2 is supported], [ax_cv_have_sse2_ext],\n      [\n        ax_cv_have_sse2_ext=no\n        if test \"$((0x$edx>>26&0x01))\" = 1; then\n          ax_cv_have_sse2_ext=yes\n        fi\n      ])\n\n      AC_CACHE_CHECK([whether sse3 is supported], [ax_cv_have_sse3_ext],\n      [\n        ax_cv_have_sse3_ext=no\n        if test \"$((0x$ecx&0x01))\" = 1; then\n          ax_cv_have_sse3_ext=yes\n        fi\n      ])\n      \n      AC_CACHE_CHECK([whether pclmuldq is supported], [ax_cv_have_pclmuldq_ext],\n      [\n        ax_cv_have_pclmuldq_ext=no\n        if test \"$((0x$ecx>>1&0x01))\" = 1; then\n          ax_cv_have_pclmuldq_ext=yes\n        fi\n      ])\n\n      AC_CACHE_CHECK([whether ssse3 is supported], [ax_cv_have_ssse3_ext],\n      [\n        ax_cv_have_ssse3_ext=no\n        if test \"$((0x$ecx>>9&0x01))\" = 1; then\n          ax_cv_have_ssse3_ext=yes\n        fi\n      ])\n\n      AC_CACHE_CHECK([whether sse4.1 is supported], [ax_cv_have_sse41_ext],\n      [\n        ax_cv_have_sse41_ext=no\n        if test \"$((0x$ecx>>19&0x01))\" = 1; then\n          ax_cv_have_sse41_ext=yes\n        fi\n      ])\n\n      AC_CACHE_CHECK([whether sse4.2 is supported], [ax_cv_have_sse42_ext],\n      [\n        ax_cv_have_sse42_ext=no\n        if test \"$((0x$ecx>>20&0x01))\" = 1; then\n          ax_cv_have_sse42_ext=yes\n        fi\n      ])\n\n      AC_CACHE_CHECK([whether avx is supported by processor], [ax_cv_have_avx_cpu_ext],\n      [\n        ax_cv_have_avx_cpu_ext=no\n        if test \"$((0x$ecx>>28&0x01))\" = 1; then\n          ax_cv_have_avx_cpu_ext=yes\n        fi\n      ])\n\n      if test x\"$ax_cv_have_avx_cpu_ext\" = x\"yes\"; then\n        AX_GCC_X86_AVX_XGETBV(0x00000000)\n\n        xgetbv_eax=\"0\"\n        if test x\"$ax_cv_gcc_x86_avx_xgetbv_0x00000000\" != x\"unknown\"; then\n          xgetbv_eax=`echo $ax_cv_gcc_x86_avx_xgetbv_0x00000000 | cut -d \":\" -f 1`\n        fi\n\n        AC_CACHE_CHECK([whether avx is supported by operating system], [ax_cv_have_avx_ext],\n        [\n          ax_cv_have_avx_ext=no\n\n          if test \"$((0x$ecx>>27&0x01))\" = 1; then\n            if test \"$((0x$xgetbv_eax&0x6))\" = 6; then\n              ax_cv_have_avx_ext=yes\n            fi\n          fi\n        ])\n        if test x\"$ax_cv_have_avx_ext\" = x\"no\"; then\n          AC_MSG_WARN([Your processor supports AVX, but your operating system doesn't])\n        fi\n      fi\n\n      if test \"$ax_cv_have_mmx_ext\" = yes; then\n        AX_CHECK_COMPILE_FLAG(-mmmx, ax_cv_support_mmx_ext=yes, [])\n        if test x\"$ax_cv_support_mmx_ext\" = x\"yes\"; then\n          SIMD_FLAGS=\"$SIMD_FLAGS -mmmx\"\n          AC_DEFINE(HAVE_MMX,,[Support mmx instructions])\n        else\n          AC_MSG_WARN([Your processor supports mmx instructions but not your compiler, can you try another compiler?])\n        fi\n      fi\n\n      if test \"$ax_cv_have_sse_ext\" = yes; then\n        AX_CHECK_COMPILE_FLAG(-msse, ax_cv_support_sse_ext=yes, [])\n        if test x\"$ax_cv_support_sse_ext\" = x\"yes\"; then\n          SIMD_FLAGS=\"$SIMD_FLAGS -msse -DINTEL_SSE\"\n          AC_DEFINE(HAVE_SSE,,[Support SSE (Streaming SIMD Extensions) instructions])\n        else\n          AC_MSG_WARN([Your processor supports sse instructions but not your compiler, can you try another compiler?])\n        fi\n      fi\n\n      if test \"$ax_cv_have_sse2_ext\" = yes; then\n        AX_CHECK_COMPILE_FLAG(-msse2, ax_cv_support_sse2_ext=yes, [])\n        if test x\"$ax_cv_support_sse2_ext\" = x\"yes\"; then\n          SIMD_FLAGS=\"$SIMD_FLAGS -msse2 -DINTEL_SSE2\"\n          AC_DEFINE(HAVE_SSE2,,[Support SSE2 (Streaming SIMD Extensions 2) instructions])\n        else\n          AC_MSG_WARN([Your processor supports sse2 instructions but not your compiler, can you try another compiler?])\n        fi\n      fi\n\n      if test \"$ax_cv_have_sse3_ext\" = yes; then\n        AX_CHECK_COMPILE_FLAG(-msse3, ax_cv_support_sse3_ext=yes, [])\n        if test x\"$ax_cv_support_sse3_ext\" = x\"yes\"; then\n          SIMD_FLAGS=\"$SIMD_FLAGS -msse3 -DINTEL_SSE3\"\n          AC_DEFINE(HAVE_SSE3,,[Support SSE3 (Streaming SIMD Extensions 3) instructions])\n        else\n          AC_MSG_WARN([Your processor supports sse3 instructions but not your compiler, can you try another compiler?])\n        fi\n      fi\n      \n      if test \"$ax_cv_have_pclmuldq_ext\" = yes; then\n        AX_CHECK_COMPILE_FLAG(-mpclmul, ax_cv_support_pclmuldq_ext=yes, [])\n        if test x\"$ax_cv_support_pclmuldq_ext\" = x\"yes\"; then\n          SIMD_FLAGS=\"$SIMD_FLAGS -mpclmul -DINTEL_SSE4_PCLMUL\"\n          AC_DEFINE(HAVE_PCLMULDQ,,[Support (PCLMULDQ) Carry-Free Muliplication])\n        else\n          AC_MSG_WARN([Your processor supports pclmuldq instructions but not your compiler, can you try another compiler?])\n        fi\n      fi\n\n      if test \"$ax_cv_have_ssse3_ext\" = yes; then\n        AX_CHECK_COMPILE_FLAG(-mssse3, ax_cv_support_ssse3_ext=yes, [])\n        if test x\"$ax_cv_support_ssse3_ext\" = x\"yes\"; then\n          SIMD_FLAGS=\"$SIMD_FLAGS -mssse3 -DINTEL_SSSE3\"\n          AC_DEFINE(HAVE_SSSE3,,[Support SSSE3 (Supplemental Streaming SIMD Extensions 3) instructions])\n        else\n          AC_MSG_WARN([Your processor supports ssse3 instructions but not your compiler, can you try another compiler?])\n        fi\n      fi\n\n      if test \"$ax_cv_have_sse41_ext\" = yes; then\n        AX_CHECK_COMPILE_FLAG(-msse4.1, ax_cv_support_sse41_ext=yes, [])\n        if test x\"$ax_cv_support_sse41_ext\" = x\"yes\"; then\n          SIMD_FLAGS=\"$SIMD_FLAGS -msse4.1 -DINTEL_SSE4\"\n          AC_DEFINE(HAVE_SSE4_1,,[Support SSSE4.1 (Streaming SIMD Extensions 4.1) instructions])\n        else\n          AC_MSG_WARN([Your processor supports sse4.1 instructions but not your compiler, can you try another compiler?])\n        fi\n      fi\n\n      if test \"$ax_cv_have_sse42_ext\" = yes; then\n        AX_CHECK_COMPILE_FLAG(-msse4.2, ax_cv_support_sse42_ext=yes, [])\n        if test x\"$ax_cv_support_sse42_ext\" = x\"yes\"; then\n          SIMD_FLAGS=\"$SIMD_FLAGS -msse4.2 -DINTEL_SSE4\"\n          AC_DEFINE(HAVE_SSE4_2,,[Support SSSE4.2 (Streaming SIMD Extensions 4.2) instructions])\n        else\n          AC_MSG_WARN([Your processor supports sse4.2 instructions but not your compiler, can you try another compiler?])\n        fi\n      fi\n\n      if test \"$ax_cv_have_avx_ext\" = yes; then\n        AX_CHECK_COMPILE_FLAG(-mavx, ax_cv_support_avx_ext=yes, [])\n        if test x\"$ax_cv_support_avx_ext\" = x\"yes\"; then\n          SIMD_FLAGS=\"$SIMD_FLAGS -mavx\"\n          AC_DEFINE(HAVE_AVX,,[Support AVX (Advanced Vector Extensions) instructions])\n        else\n          AC_MSG_WARN([Your processor supports avx instructions but not your compiler, can you try another compiler?])\n        fi\n      fi\n\n  ;;\n  esac\n\n  AC_SUBST(SIMD_FLAGS)\n])\n"
  },
  {
    "path": "fst/layout/gf-complete/m4/ax_gcc_x86_avx_xgetbv.m4",
    "content": "# ===========================================================================\n#   http://www.gnu.org/software/autoconf-archive/ax_gcc_x86_avx_xgetbv.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_GCC_X86_AVX_XGETBV\n#\n# DESCRIPTION\n#\n#   On later x86 processors with AVX SIMD support, with gcc or a compiler\n#   that has a compatible syntax for inline assembly instructions, run a\n#   small program that executes the xgetbv instruction with input OP. This\n#   can be used to detect if the OS supports AVX instruction usage.\n#\n#   On output, the values of the eax and edx registers are stored as\n#   hexadecimal strings as \"eax:edx\" in the cache variable\n#   ax_cv_gcc_x86_avx_xgetbv.\n#\n#   If the xgetbv instruction fails (because you are running a\n#   cross-compiler, or because you are not using gcc, or because you are on\n#   a processor that doesn't have this instruction),\n#   ax_cv_gcc_x86_avx_xgetbv_OP is set to the string \"unknown\".\n#\n#   This macro mainly exists to be used in AX_EXT.\n#\n# LICENSE\n#\n#   Copyright (c) 2013 Michael Petch <mpetch@capp-sysware.com>\n#\n#   This program is free software: you can redistribute it and/or modify it\n#   under the terms of the GNU General Public License as published by the\n#   Free Software Foundation, either version 3 of the License, or (at your\n#   option) any later version.\n#\n#   This program is distributed in the hope that it will be useful, but\n#   WITHOUT ANY WARRANTY; without even the implied warranty of\n#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\n#   Public License for more details.\n#\n#   You should have received a copy of the GNU General Public License along\n#   with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n#   As a special exception, the respective Autoconf Macro's copyright owner\n#   gives unlimited permission to copy, distribute and modify the configure\n#   scripts that are the output of Autoconf when processing the Macro. You\n#   need not follow the terms of the GNU General Public License when using\n#   or distributing such scripts, even though portions of the text of the\n#   Macro appear in them. The GNU General Public License (GPL) does govern\n#   all other use of the material that constitutes the Autoconf Macro.\n#\n#   This special exception to the GPL applies to versions of the Autoconf\n#   Macro released by the Autoconf Archive. When you make and distribute a\n#   modified version of the Autoconf Macro, you may extend this special\n#   exception to the GPL to apply to your modified version as well.\n\n#serial 1\n\nAC_DEFUN([AX_GCC_X86_AVX_XGETBV],\n[AC_REQUIRE([AC_PROG_CC])\nAC_LANG_PUSH([C])\nAC_CACHE_CHECK(for x86-AVX xgetbv $1 output, ax_cv_gcc_x86_avx_xgetbv_$1,\n [AC_RUN_IFELSE([AC_LANG_PROGRAM([#include <stdio.h>], [\n     int op = $1, eax, edx;\n     FILE *f;\n      /* Opcodes for xgetbv */\n      __asm__(\".byte 0x0f, 0x01, 0xd0\"\n        : \"=a\" (eax), \"=d\" (edx)\n        : \"c\" (op));\n     f = fopen(\"conftest_xgetbv\", \"w\"); if (!f) return 1;\n     fprintf(f, \"%x:%x\\n\", eax, edx);\n     fclose(f);\n     return 0;\n])],\n     [ax_cv_gcc_x86_avx_xgetbv_$1=`cat conftest_xgetbv`; rm -f conftest_xgetbv],\n     [ax_cv_gcc_x86_avx_xgetbv_$1=unknown; rm -f conftest_xgetbv],\n     [ax_cv_gcc_x86_avx_xgetbv_$1=unknown])])\nAC_LANG_POP([C])\n])\n"
  },
  {
    "path": "fst/layout/gf-complete/m4/ax_gcc_x86_cpuid.m4",
    "content": "# ===========================================================================\n#     http://www.gnu.org/software/autoconf-archive/ax_gcc_x86_cpuid.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_GCC_X86_CPUID(OP)\n#\n# DESCRIPTION\n#\n#   On Pentium and later x86 processors, with gcc or a compiler that has a\n#   compatible syntax for inline assembly instructions, run a small program\n#   that executes the cpuid instruction with input OP. This can be used to\n#   detect the CPU type.\n#\n#   On output, the values of the eax, ebx, ecx, and edx registers are stored\n#   as hexadecimal strings as \"eax:ebx:ecx:edx\" in the cache variable\n#   ax_cv_gcc_x86_cpuid_OP.\n#\n#   If the cpuid instruction fails (because you are running a\n#   cross-compiler, or because you are not using gcc, or because you are on\n#   a processor that doesn't have this instruction), ax_cv_gcc_x86_cpuid_OP\n#   is set to the string \"unknown\".\n#\n#   This macro mainly exists to be used in AX_GCC_ARCHFLAG.\n#\n# LICENSE\n#\n#   Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>\n#   Copyright (c) 2008 Matteo Frigo\n#\n#   This program is free software: you can redistribute it and/or modify it\n#   under the terms of the GNU General Public License as published by the\n#   Free Software Foundation, either version 3 of the License, or (at your\n#   option) any later version.\n#\n#   This program is distributed in the hope that it will be useful, but\n#   WITHOUT ANY WARRANTY; without even the implied warranty of\n#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\n#   Public License for more details.\n#\n#   You should have received a copy of the GNU General Public License along\n#   with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n#   As a special exception, the respective Autoconf Macro's copyright owner\n#   gives unlimited permission to copy, distribute and modify the configure\n#   scripts that are the output of Autoconf when processing the Macro. You\n#   need not follow the terms of the GNU General Public License when using\n#   or distributing such scripts, even though portions of the text of the\n#   Macro appear in them. The GNU General Public License (GPL) does govern\n#   all other use of the material that constitutes the Autoconf Macro.\n#\n#   This special exception to the GPL applies to versions of the Autoconf\n#   Macro released by the Autoconf Archive. When you make and distribute a\n#   modified version of the Autoconf Macro, you may extend this special\n#   exception to the GPL to apply to your modified version as well.\n\n#serial 7\n\nAC_DEFUN([AX_GCC_X86_CPUID],\n[AC_REQUIRE([AC_PROG_CC])\nAC_LANG_PUSH([C])\nAC_CACHE_CHECK(for x86 cpuid $1 output, ax_cv_gcc_x86_cpuid_$1,\n [AC_RUN_IFELSE([AC_LANG_PROGRAM([#include <stdio.h>], [\n     int op = $1, eax, ebx, ecx, edx;\n     FILE *f;\n      __asm__(\"cpuid\"\n        : \"=a\" (eax), \"=b\" (ebx), \"=c\" (ecx), \"=d\" (edx)\n        : \"a\" (op));\n     f = fopen(\"conftest_cpuid\", \"w\"); if (!f) return 1;\n     fprintf(f, \"%x:%x:%x:%x\\n\", eax, ebx, ecx, edx);\n     fclose(f);\n     return 0;\n])],\n     [ax_cv_gcc_x86_cpuid_$1=`cat conftest_cpuid`; rm -f conftest_cpuid],\n     [ax_cv_gcc_x86_cpuid_$1=unknown; rm -f conftest_cpuid],\n     [ax_cv_gcc_x86_cpuid_$1=unknown])])\nAC_LANG_POP([C])\n])\n"
  },
  {
    "path": "fst/layout/gf-complete/m4/ltoptions.m4",
    "content": "# Helper functions for option handling.                    -*- Autoconf -*-\n#\n#   Copyright (C) 2004, 2005, 2007, 2008, 2009 Free Software Foundation,\n#   Inc.\n#   Written by Gary V. Vaughan, 2004\n#\n# This file is free software; the Free Software Foundation gives\n# unlimited permission to copy and/or distribute it, with or without\n# modifications, as long as this notice is preserved.\n\n# serial 7 ltoptions.m4\n\n# This is to help aclocal find these macros, as it can't see m4_define.\nAC_DEFUN([LTOPTIONS_VERSION], [m4_if([1])])\n\n\n# _LT_MANGLE_OPTION(MACRO-NAME, OPTION-NAME)\n# ------------------------------------------\nm4_define([_LT_MANGLE_OPTION],\n[[_LT_OPTION_]m4_bpatsubst($1__$2, [[^a-zA-Z0-9_]], [_])])\n\n\n# _LT_SET_OPTION(MACRO-NAME, OPTION-NAME)\n# ---------------------------------------\n# Set option OPTION-NAME for macro MACRO-NAME, and if there is a\n# matching handler defined, dispatch to it.  Other OPTION-NAMEs are\n# saved as a flag.\nm4_define([_LT_SET_OPTION],\n[m4_define(_LT_MANGLE_OPTION([$1], [$2]))dnl\nm4_ifdef(_LT_MANGLE_DEFUN([$1], [$2]),\n        _LT_MANGLE_DEFUN([$1], [$2]),\n    [m4_warning([Unknown $1 option `$2'])])[]dnl\n])\n\n\n# _LT_IF_OPTION(MACRO-NAME, OPTION-NAME, IF-SET, [IF-NOT-SET])\n# ------------------------------------------------------------\n# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise.\nm4_define([_LT_IF_OPTION],\n[m4_ifdef(_LT_MANGLE_OPTION([$1], [$2]), [$3], [$4])])\n\n\n# _LT_UNLESS_OPTIONS(MACRO-NAME, OPTION-LIST, IF-NOT-SET)\n# -------------------------------------------------------\n# Execute IF-NOT-SET unless all options in OPTION-LIST for MACRO-NAME\n# are set.\nm4_define([_LT_UNLESS_OPTIONS],\n[m4_foreach([_LT_Option], m4_split(m4_normalize([$2])),\n\t    [m4_ifdef(_LT_MANGLE_OPTION([$1], _LT_Option),\n\t\t      [m4_define([$0_found])])])[]dnl\nm4_ifdef([$0_found], [m4_undefine([$0_found])], [$3\n])[]dnl\n])\n\n\n# _LT_SET_OPTIONS(MACRO-NAME, OPTION-LIST)\n# ----------------------------------------\n# OPTION-LIST is a space-separated list of Libtool options associated\n# with MACRO-NAME.  If any OPTION has a matching handler declared with\n# LT_OPTION_DEFINE, dispatch to that macro; otherwise complain about\n# the unknown option and exit.\nm4_defun([_LT_SET_OPTIONS],\n[# Set options\nm4_foreach([_LT_Option], m4_split(m4_normalize([$2])),\n    [_LT_SET_OPTION([$1], _LT_Option)])\n\nm4_if([$1],[LT_INIT],[\n  dnl\n  dnl Simply set some default values (i.e off) if boolean options were not\n  dnl specified:\n  _LT_UNLESS_OPTIONS([LT_INIT], [dlopen], [enable_dlopen=no\n  ])\n  _LT_UNLESS_OPTIONS([LT_INIT], [win32-dll], [enable_win32_dll=no\n  ])\n  dnl\n  dnl If no reference was made to various pairs of opposing options, then\n  dnl we run the default mode handler for the pair.  For example, if neither\n  dnl `shared' nor `disable-shared' was passed, we enable building of shared\n  dnl archives by default:\n  _LT_UNLESS_OPTIONS([LT_INIT], [shared disable-shared], [_LT_ENABLE_SHARED])\n  _LT_UNLESS_OPTIONS([LT_INIT], [static disable-static], [_LT_ENABLE_STATIC])\n  _LT_UNLESS_OPTIONS([LT_INIT], [pic-only no-pic], [_LT_WITH_PIC])\n  _LT_UNLESS_OPTIONS([LT_INIT], [fast-install disable-fast-install],\n  \t\t   [_LT_ENABLE_FAST_INSTALL])\n  ])\n])# _LT_SET_OPTIONS\n\n\n## --------------------------------- ##\n## Macros to handle LT_INIT options. ##\n## --------------------------------- ##\n\n# _LT_MANGLE_DEFUN(MACRO-NAME, OPTION-NAME)\n# -----------------------------------------\nm4_define([_LT_MANGLE_DEFUN],\n[[_LT_OPTION_DEFUN_]m4_bpatsubst(m4_toupper([$1__$2]), [[^A-Z0-9_]], [_])])\n\n\n# LT_OPTION_DEFINE(MACRO-NAME, OPTION-NAME, CODE)\n# -----------------------------------------------\nm4_define([LT_OPTION_DEFINE],\n[m4_define(_LT_MANGLE_DEFUN([$1], [$2]), [$3])[]dnl\n])# LT_OPTION_DEFINE\n\n\n# dlopen\n# ------\nLT_OPTION_DEFINE([LT_INIT], [dlopen], [enable_dlopen=yes\n])\n\nAU_DEFUN([AC_LIBTOOL_DLOPEN],\n[_LT_SET_OPTION([LT_INIT], [dlopen])\nAC_DIAGNOSE([obsolete],\n[$0: Remove this warning and the call to _LT_SET_OPTION when you\nput the `dlopen' option into LT_INIT's first parameter.])\n])\n\ndnl aclocal-1.4 backwards compatibility:\ndnl AC_DEFUN([AC_LIBTOOL_DLOPEN], [])\n\n\n# win32-dll\n# ---------\n# Declare package support for building win32 dll's.\nLT_OPTION_DEFINE([LT_INIT], [win32-dll],\n[enable_win32_dll=yes\n\ncase $host in\n*-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-cegcc*)\n  AC_CHECK_TOOL(AS, as, false)\n  AC_CHECK_TOOL(DLLTOOL, dlltool, false)\n  AC_CHECK_TOOL(OBJDUMP, objdump, false)\n  ;;\nesac\n\ntest -z \"$AS\" && AS=as\n_LT_DECL([], [AS],      [1], [Assembler program])dnl\n\ntest -z \"$DLLTOOL\" && DLLTOOL=dlltool\n_LT_DECL([], [DLLTOOL], [1], [DLL creation program])dnl\n\ntest -z \"$OBJDUMP\" && OBJDUMP=objdump\n_LT_DECL([], [OBJDUMP], [1], [Object dumper program])dnl\n])# win32-dll\n\nAU_DEFUN([AC_LIBTOOL_WIN32_DLL],\n[AC_REQUIRE([AC_CANONICAL_HOST])dnl\n_LT_SET_OPTION([LT_INIT], [win32-dll])\nAC_DIAGNOSE([obsolete],\n[$0: Remove this warning and the call to _LT_SET_OPTION when you\nput the `win32-dll' option into LT_INIT's first parameter.])\n])\n\ndnl aclocal-1.4 backwards compatibility:\ndnl AC_DEFUN([AC_LIBTOOL_WIN32_DLL], [])\n\n\n# _LT_ENABLE_SHARED([DEFAULT])\n# ----------------------------\n# implement the --enable-shared flag, and supports the `shared' and\n# `disable-shared' LT_INIT options.\n# DEFAULT is either `yes' or `no'.  If omitted, it defaults to `yes'.\nm4_define([_LT_ENABLE_SHARED],\n[m4_define([_LT_ENABLE_SHARED_DEFAULT], [m4_if($1, no, no, yes)])dnl\nAC_ARG_ENABLE([shared],\n    [AS_HELP_STRING([--enable-shared@<:@=PKGS@:>@],\n\t[build shared libraries @<:@default=]_LT_ENABLE_SHARED_DEFAULT[@:>@])],\n    [p=${PACKAGE-default}\n    case $enableval in\n    yes) enable_shared=yes ;;\n    no) enable_shared=no ;;\n    *)\n      enable_shared=no\n      # Look at the argument we got.  We use all the common list separators.\n      lt_save_ifs=\"$IFS\"; IFS=\"${IFS}$PATH_SEPARATOR,\"\n      for pkg in $enableval; do\n\tIFS=\"$lt_save_ifs\"\n\tif test \"X$pkg\" = \"X$p\"; then\n\t  enable_shared=yes\n\tfi\n      done\n      IFS=\"$lt_save_ifs\"\n      ;;\n    esac],\n    [enable_shared=]_LT_ENABLE_SHARED_DEFAULT)\n\n    _LT_DECL([build_libtool_libs], [enable_shared], [0],\n\t[Whether or not to build shared libraries])\n])# _LT_ENABLE_SHARED\n\nLT_OPTION_DEFINE([LT_INIT], [shared], [_LT_ENABLE_SHARED([yes])])\nLT_OPTION_DEFINE([LT_INIT], [disable-shared], [_LT_ENABLE_SHARED([no])])\n\n# Old names:\nAC_DEFUN([AC_ENABLE_SHARED],\n[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[shared])\n])\n\nAC_DEFUN([AC_DISABLE_SHARED],\n[_LT_SET_OPTION([LT_INIT], [disable-shared])\n])\n\nAU_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)])\nAU_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)])\n\ndnl aclocal-1.4 backwards compatibility:\ndnl AC_DEFUN([AM_ENABLE_SHARED], [])\ndnl AC_DEFUN([AM_DISABLE_SHARED], [])\n\n\n\n# _LT_ENABLE_STATIC([DEFAULT])\n# ----------------------------\n# implement the --enable-static flag, and support the `static' and\n# `disable-static' LT_INIT options.\n# DEFAULT is either `yes' or `no'.  If omitted, it defaults to `yes'.\nm4_define([_LT_ENABLE_STATIC],\n[m4_define([_LT_ENABLE_STATIC_DEFAULT], [m4_if($1, no, no, yes)])dnl\nAC_ARG_ENABLE([static],\n    [AS_HELP_STRING([--enable-static@<:@=PKGS@:>@],\n\t[build static libraries @<:@default=]_LT_ENABLE_STATIC_DEFAULT[@:>@])],\n    [p=${PACKAGE-default}\n    case $enableval in\n    yes) enable_static=yes ;;\n    no) enable_static=no ;;\n    *)\n     enable_static=no\n      # Look at the argument we got.  We use all the common list separators.\n      lt_save_ifs=\"$IFS\"; IFS=\"${IFS}$PATH_SEPARATOR,\"\n      for pkg in $enableval; do\n\tIFS=\"$lt_save_ifs\"\n\tif test \"X$pkg\" = \"X$p\"; then\n\t  enable_static=yes\n\tfi\n      done\n      IFS=\"$lt_save_ifs\"\n      ;;\n    esac],\n    [enable_static=]_LT_ENABLE_STATIC_DEFAULT)\n\n    _LT_DECL([build_old_libs], [enable_static], [0],\n\t[Whether or not to build static libraries])\n])# _LT_ENABLE_STATIC\n\nLT_OPTION_DEFINE([LT_INIT], [static], [_LT_ENABLE_STATIC([yes])])\nLT_OPTION_DEFINE([LT_INIT], [disable-static], [_LT_ENABLE_STATIC([no])])\n\n# Old names:\nAC_DEFUN([AC_ENABLE_STATIC],\n[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[static])\n])\n\nAC_DEFUN([AC_DISABLE_STATIC],\n[_LT_SET_OPTION([LT_INIT], [disable-static])\n])\n\nAU_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)])\nAU_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)])\n\ndnl aclocal-1.4 backwards compatibility:\ndnl AC_DEFUN([AM_ENABLE_STATIC], [])\ndnl AC_DEFUN([AM_DISABLE_STATIC], [])\n\n\n\n# _LT_ENABLE_FAST_INSTALL([DEFAULT])\n# ----------------------------------\n# implement the --enable-fast-install flag, and support the `fast-install'\n# and `disable-fast-install' LT_INIT options.\n# DEFAULT is either `yes' or `no'.  If omitted, it defaults to `yes'.\nm4_define([_LT_ENABLE_FAST_INSTALL],\n[m4_define([_LT_ENABLE_FAST_INSTALL_DEFAULT], [m4_if($1, no, no, yes)])dnl\nAC_ARG_ENABLE([fast-install],\n    [AS_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@],\n    [optimize for fast installation @<:@default=]_LT_ENABLE_FAST_INSTALL_DEFAULT[@:>@])],\n    [p=${PACKAGE-default}\n    case $enableval in\n    yes) enable_fast_install=yes ;;\n    no) enable_fast_install=no ;;\n    *)\n      enable_fast_install=no\n      # Look at the argument we got.  We use all the common list separators.\n      lt_save_ifs=\"$IFS\"; IFS=\"${IFS}$PATH_SEPARATOR,\"\n      for pkg in $enableval; do\n\tIFS=\"$lt_save_ifs\"\n\tif test \"X$pkg\" = \"X$p\"; then\n\t  enable_fast_install=yes\n\tfi\n      done\n      IFS=\"$lt_save_ifs\"\n      ;;\n    esac],\n    [enable_fast_install=]_LT_ENABLE_FAST_INSTALL_DEFAULT)\n\n_LT_DECL([fast_install], [enable_fast_install], [0],\n\t [Whether or not to optimize for fast installation])dnl\n])# _LT_ENABLE_FAST_INSTALL\n\nLT_OPTION_DEFINE([LT_INIT], [fast-install], [_LT_ENABLE_FAST_INSTALL([yes])])\nLT_OPTION_DEFINE([LT_INIT], [disable-fast-install], [_LT_ENABLE_FAST_INSTALL([no])])\n\n# Old names:\nAU_DEFUN([AC_ENABLE_FAST_INSTALL],\n[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[fast-install])\nAC_DIAGNOSE([obsolete],\n[$0: Remove this warning and the call to _LT_SET_OPTION when you put\nthe `fast-install' option into LT_INIT's first parameter.])\n])\n\nAU_DEFUN([AC_DISABLE_FAST_INSTALL],\n[_LT_SET_OPTION([LT_INIT], [disable-fast-install])\nAC_DIAGNOSE([obsolete],\n[$0: Remove this warning and the call to _LT_SET_OPTION when you put\nthe `disable-fast-install' option into LT_INIT's first parameter.])\n])\n\ndnl aclocal-1.4 backwards compatibility:\ndnl AC_DEFUN([AC_ENABLE_FAST_INSTALL], [])\ndnl AC_DEFUN([AM_DISABLE_FAST_INSTALL], [])\n\n\n# _LT_WITH_PIC([MODE])\n# --------------------\n# implement the --with-pic flag, and support the `pic-only' and `no-pic'\n# LT_INIT options.\n# MODE is either `yes' or `no'.  If omitted, it defaults to `both'.\nm4_define([_LT_WITH_PIC],\n[AC_ARG_WITH([pic],\n    [AS_HELP_STRING([--with-pic@<:@=PKGS@:>@],\n\t[try to use only PIC/non-PIC objects @<:@default=use both@:>@])],\n    [lt_p=${PACKAGE-default}\n    case $withval in\n    yes|no) pic_mode=$withval ;;\n    *)\n      pic_mode=default\n      # Look at the argument we got.  We use all the common list separators.\n      lt_save_ifs=\"$IFS\"; IFS=\"${IFS}$PATH_SEPARATOR,\"\n      for lt_pkg in $withval; do\n\tIFS=\"$lt_save_ifs\"\n\tif test \"X$lt_pkg\" = \"X$lt_p\"; then\n\t  pic_mode=yes\n\tfi\n      done\n      IFS=\"$lt_save_ifs\"\n      ;;\n    esac],\n    [pic_mode=default])\n\ntest -z \"$pic_mode\" && pic_mode=m4_default([$1], [default])\n\n_LT_DECL([], [pic_mode], [0], [What type of objects to build])dnl\n])# _LT_WITH_PIC\n\nLT_OPTION_DEFINE([LT_INIT], [pic-only], [_LT_WITH_PIC([yes])])\nLT_OPTION_DEFINE([LT_INIT], [no-pic], [_LT_WITH_PIC([no])])\n\n# Old name:\nAU_DEFUN([AC_LIBTOOL_PICMODE],\n[_LT_SET_OPTION([LT_INIT], [pic-only])\nAC_DIAGNOSE([obsolete],\n[$0: Remove this warning and the call to _LT_SET_OPTION when you\nput the `pic-only' option into LT_INIT's first parameter.])\n])\n\ndnl aclocal-1.4 backwards compatibility:\ndnl AC_DEFUN([AC_LIBTOOL_PICMODE], [])\n\n## ----------------- ##\n## LTDL_INIT Options ##\n## ----------------- ##\n\nm4_define([_LTDL_MODE], [])\nLT_OPTION_DEFINE([LTDL_INIT], [nonrecursive],\n\t\t [m4_define([_LTDL_MODE], [nonrecursive])])\nLT_OPTION_DEFINE([LTDL_INIT], [recursive],\n\t\t [m4_define([_LTDL_MODE], [recursive])])\nLT_OPTION_DEFINE([LTDL_INIT], [subproject],\n\t\t [m4_define([_LTDL_MODE], [subproject])])\n\nm4_define([_LTDL_TYPE], [])\nLT_OPTION_DEFINE([LTDL_INIT], [installable],\n\t\t [m4_define([_LTDL_TYPE], [installable])])\nLT_OPTION_DEFINE([LTDL_INIT], [convenience],\n\t\t [m4_define([_LTDL_TYPE], [convenience])])\n"
  },
  {
    "path": "fst/layout/gf-complete/m4/ltsugar.m4",
    "content": "# ltsugar.m4 -- libtool m4 base layer.                         -*-Autoconf-*-\n#\n# Copyright (C) 2004, 2005, 2007, 2008 Free Software Foundation, Inc.\n# Written by Gary V. Vaughan, 2004\n#\n# This file is free software; the Free Software Foundation gives\n# unlimited permission to copy and/or distribute it, with or without\n# modifications, as long as this notice is preserved.\n\n# serial 6 ltsugar.m4\n\n# This is to help aclocal find these macros, as it can't see m4_define.\nAC_DEFUN([LTSUGAR_VERSION], [m4_if([0.1])])\n\n\n# lt_join(SEP, ARG1, [ARG2...])\n# -----------------------------\n# Produce ARG1SEPARG2...SEPARGn, omitting [] arguments and their\n# associated separator.\n# Needed until we can rely on m4_join from Autoconf 2.62, since all earlier\n# versions in m4sugar had bugs.\nm4_define([lt_join],\n[m4_if([$#], [1], [],\n       [$#], [2], [[$2]],\n       [m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift(m4_shift($@)))])])\nm4_define([_lt_join],\n[m4_if([$#$2], [2], [],\n       [m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift(m4_shift($@)))])])\n\n\n# lt_car(LIST)\n# lt_cdr(LIST)\n# ------------\n# Manipulate m4 lists.\n# These macros are necessary as long as will still need to support\n# Autoconf-2.59 which quotes differently.\nm4_define([lt_car], [[$1]])\nm4_define([lt_cdr],\n[m4_if([$#], 0, [m4_fatal([$0: cannot be called without arguments])],\n       [$#], 1, [],\n       [m4_dquote(m4_shift($@))])])\nm4_define([lt_unquote], $1)\n\n\n# lt_append(MACRO-NAME, STRING, [SEPARATOR])\n# ------------------------------------------\n# Redefine MACRO-NAME to hold its former content plus `SEPARATOR'`STRING'.\n# Note that neither SEPARATOR nor STRING are expanded; they are appended\n# to MACRO-NAME as is (leaving the expansion for when MACRO-NAME is invoked).\n# No SEPARATOR is output if MACRO-NAME was previously undefined (different\n# than defined and empty).\n#\n# This macro is needed until we can rely on Autoconf 2.62, since earlier\n# versions of m4sugar mistakenly expanded SEPARATOR but not STRING.\nm4_define([lt_append],\n[m4_define([$1],\n\t   m4_ifdef([$1], [m4_defn([$1])[$3]])[$2])])\n\n\n\n# lt_combine(SEP, PREFIX-LIST, INFIX, SUFFIX1, [SUFFIX2...])\n# ----------------------------------------------------------\n# Produce a SEP delimited list of all paired combinations of elements of\n# PREFIX-LIST with SUFFIX1 through SUFFIXn.  Each element of the list\n# has the form PREFIXmINFIXSUFFIXn.\n# Needed until we can rely on m4_combine added in Autoconf 2.62.\nm4_define([lt_combine],\n[m4_if(m4_eval([$# > 3]), [1],\n       [m4_pushdef([_Lt_sep], [m4_define([_Lt_sep], m4_defn([lt_car]))])]]dnl\n[[m4_foreach([_Lt_prefix], [$2],\n\t     [m4_foreach([_Lt_suffix],\n\t\t]m4_dquote(m4_dquote(m4_shift(m4_shift(m4_shift($@)))))[,\n\t[_Lt_sep([$1])[]m4_defn([_Lt_prefix])[$3]m4_defn([_Lt_suffix])])])])])\n\n\n# lt_if_append_uniq(MACRO-NAME, VARNAME, [SEPARATOR], [UNIQ], [NOT-UNIQ])\n# -----------------------------------------------------------------------\n# Iff MACRO-NAME does not yet contain VARNAME, then append it (delimited\n# by SEPARATOR if supplied) and expand UNIQ, else NOT-UNIQ.\nm4_define([lt_if_append_uniq],\n[m4_ifdef([$1],\n\t  [m4_if(m4_index([$3]m4_defn([$1])[$3], [$3$2$3]), [-1],\n\t\t [lt_append([$1], [$2], [$3])$4],\n\t\t [$5])],\n\t  [lt_append([$1], [$2], [$3])$4])])\n\n\n# lt_dict_add(DICT, KEY, VALUE)\n# -----------------------------\nm4_define([lt_dict_add],\n[m4_define([$1($2)], [$3])])\n\n\n# lt_dict_add_subkey(DICT, KEY, SUBKEY, VALUE)\n# --------------------------------------------\nm4_define([lt_dict_add_subkey],\n[m4_define([$1($2:$3)], [$4])])\n\n\n# lt_dict_fetch(DICT, KEY, [SUBKEY])\n# ----------------------------------\nm4_define([lt_dict_fetch],\n[m4_ifval([$3],\n\tm4_ifdef([$1($2:$3)], [m4_defn([$1($2:$3)])]),\n    m4_ifdef([$1($2)], [m4_defn([$1($2)])]))])\n\n\n# lt_if_dict_fetch(DICT, KEY, [SUBKEY], VALUE, IF-TRUE, [IF-FALSE])\n# -----------------------------------------------------------------\nm4_define([lt_if_dict_fetch],\n[m4_if(lt_dict_fetch([$1], [$2], [$3]), [$4],\n\t[$5],\n    [$6])])\n\n\n# lt_dict_filter(DICT, [SUBKEY], VALUE, [SEPARATOR], KEY, [...])\n# --------------------------------------------------------------\nm4_define([lt_dict_filter],\n[m4_if([$5], [], [],\n  [lt_join(m4_quote(m4_default([$4], [[, ]])),\n           lt_unquote(m4_split(m4_normalize(m4_foreach(_Lt_key, lt_car([m4_shiftn(4, $@)]),\n\t\t      [lt_if_dict_fetch([$1], _Lt_key, [$2], [$3], [_Lt_key ])])))))])[]dnl\n])\n"
  },
  {
    "path": "fst/layout/gf-complete/m4/lt~obsolete.m4",
    "content": "# lt~obsolete.m4 -- aclocal satisfying obsolete definitions.    -*-Autoconf-*-\n#\n#   Copyright (C) 2004, 2005, 2007, 2009 Free Software Foundation, Inc.\n#   Written by Scott James Remnant, 2004.\n#\n# This file is free software; the Free Software Foundation gives\n# unlimited permission to copy and/or distribute it, with or without\n# modifications, as long as this notice is preserved.\n\n# serial 5 lt~obsolete.m4\n\n# These exist entirely to fool aclocal when bootstrapping libtool.\n#\n# In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN)\n# which have later been changed to m4_define as they aren't part of the\n# exported API, or moved to Autoconf or Automake where they belong.\n#\n# The trouble is, aclocal is a bit thick.  It'll see the old AC_DEFUN\n# in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us\n# using a macro with the same name in our local m4/libtool.m4 it'll\n# pull the old libtool.m4 in (it doesn't see our shiny new m4_define\n# and doesn't know about Autoconf macros at all.)\n#\n# So we provide this file, which has a silly filename so it's always\n# included after everything else.  This provides aclocal with the\n# AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything\n# because those macros already exist, or will be overwritten later.\n# We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6. \n#\n# Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here.\n# Yes, that means every name once taken will need to remain here until\n# we give up compatibility with versions before 1.7, at which point\n# we need to keep only those names which we still refer to.\n\n# This is to help aclocal find these macros, as it can't see m4_define.\nAC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])])\n\nm4_ifndef([AC_LIBTOOL_LINKER_OPTION],\t[AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])])\nm4_ifndef([AC_PROG_EGREP],\t\t[AC_DEFUN([AC_PROG_EGREP])])\nm4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH],\t[AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])])\nm4_ifndef([_LT_AC_SHELL_INIT],\t\t[AC_DEFUN([_LT_AC_SHELL_INIT])])\nm4_ifndef([_LT_AC_SYS_LIBPATH_AIX],\t[AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])])\nm4_ifndef([_LT_PROG_LTMAIN],\t\t[AC_DEFUN([_LT_PROG_LTMAIN])])\nm4_ifndef([_LT_AC_TAGVAR],\t\t[AC_DEFUN([_LT_AC_TAGVAR])])\nm4_ifndef([AC_LTDL_ENABLE_INSTALL],\t[AC_DEFUN([AC_LTDL_ENABLE_INSTALL])])\nm4_ifndef([AC_LTDL_PREOPEN],\t\t[AC_DEFUN([AC_LTDL_PREOPEN])])\nm4_ifndef([_LT_AC_SYS_COMPILER],\t[AC_DEFUN([_LT_AC_SYS_COMPILER])])\nm4_ifndef([_LT_AC_LOCK],\t\t[AC_DEFUN([_LT_AC_LOCK])])\nm4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE],\t[AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])])\nm4_ifndef([_LT_AC_TRY_DLOPEN_SELF],\t[AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])])\nm4_ifndef([AC_LIBTOOL_PROG_CC_C_O],\t[AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])])\nm4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])])\nm4_ifndef([AC_LIBTOOL_OBJDIR],\t\t[AC_DEFUN([AC_LIBTOOL_OBJDIR])])\nm4_ifndef([AC_LTDL_OBJDIR],\t\t[AC_DEFUN([AC_LTDL_OBJDIR])])\nm4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])])\nm4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP],\t[AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])])\nm4_ifndef([AC_PATH_MAGIC],\t\t[AC_DEFUN([AC_PATH_MAGIC])])\nm4_ifndef([AC_PROG_LD_GNU],\t\t[AC_DEFUN([AC_PROG_LD_GNU])])\nm4_ifndef([AC_PROG_LD_RELOAD_FLAG],\t[AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])])\nm4_ifndef([AC_DEPLIBS_CHECK_METHOD],\t[AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])])\nm4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])])\nm4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])])\nm4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])])\nm4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS],\t[AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])])\nm4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP],\t[AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])])\nm4_ifndef([LT_AC_PROG_EGREP],\t\t[AC_DEFUN([LT_AC_PROG_EGREP])])\nm4_ifndef([LT_AC_PROG_SED],\t\t[AC_DEFUN([LT_AC_PROG_SED])])\nm4_ifndef([_LT_CC_BASENAME],\t\t[AC_DEFUN([_LT_CC_BASENAME])])\nm4_ifndef([_LT_COMPILER_BOILERPLATE],\t[AC_DEFUN([_LT_COMPILER_BOILERPLATE])])\nm4_ifndef([_LT_LINKER_BOILERPLATE],\t[AC_DEFUN([_LT_LINKER_BOILERPLATE])])\nm4_ifndef([_AC_PROG_LIBTOOL],\t\t[AC_DEFUN([_AC_PROG_LIBTOOL])])\nm4_ifndef([AC_LIBTOOL_SETUP],\t\t[AC_DEFUN([AC_LIBTOOL_SETUP])])\nm4_ifndef([_LT_AC_CHECK_DLFCN],\t\t[AC_DEFUN([_LT_AC_CHECK_DLFCN])])\nm4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER],\t[AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])])\nm4_ifndef([_LT_AC_TAGCONFIG],\t\t[AC_DEFUN([_LT_AC_TAGCONFIG])])\nm4_ifndef([AC_DISABLE_FAST_INSTALL],\t[AC_DEFUN([AC_DISABLE_FAST_INSTALL])])\nm4_ifndef([_LT_AC_LANG_CXX],\t\t[AC_DEFUN([_LT_AC_LANG_CXX])])\nm4_ifndef([_LT_AC_LANG_F77],\t\t[AC_DEFUN([_LT_AC_LANG_F77])])\nm4_ifndef([_LT_AC_LANG_GCJ],\t\t[AC_DEFUN([_LT_AC_LANG_GCJ])])\nm4_ifndef([AC_LIBTOOL_LANG_C_CONFIG],\t[AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])])\nm4_ifndef([_LT_AC_LANG_C_CONFIG],\t[AC_DEFUN([_LT_AC_LANG_C_CONFIG])])\nm4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG],\t[AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])])\nm4_ifndef([_LT_AC_LANG_CXX_CONFIG],\t[AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])])\nm4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG],\t[AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])])\nm4_ifndef([_LT_AC_LANG_F77_CONFIG],\t[AC_DEFUN([_LT_AC_LANG_F77_CONFIG])])\nm4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG],\t[AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])])\nm4_ifndef([_LT_AC_LANG_GCJ_CONFIG],\t[AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])])\nm4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG],\t[AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])])\nm4_ifndef([_LT_AC_LANG_RC_CONFIG],\t[AC_DEFUN([_LT_AC_LANG_RC_CONFIG])])\nm4_ifndef([AC_LIBTOOL_CONFIG],\t\t[AC_DEFUN([AC_LIBTOOL_CONFIG])])\nm4_ifndef([_LT_AC_FILE_LTDLL_C],\t[AC_DEFUN([_LT_AC_FILE_LTDLL_C])])\nm4_ifndef([_LT_REQUIRED_DARWIN_CHECKS],\t[AC_DEFUN([_LT_REQUIRED_DARWIN_CHECKS])])\nm4_ifndef([_LT_AC_PROG_CXXCPP],\t\t[AC_DEFUN([_LT_AC_PROG_CXXCPP])])\nm4_ifndef([_LT_PREPARE_SED_QUOTE_VARS],\t[AC_DEFUN([_LT_PREPARE_SED_QUOTE_VARS])])\nm4_ifndef([_LT_PROG_ECHO_BACKSLASH],\t[AC_DEFUN([_LT_PROG_ECHO_BACKSLASH])])\nm4_ifndef([_LT_PROG_F77],\t\t[AC_DEFUN([_LT_PROG_F77])])\nm4_ifndef([_LT_PROG_FC],\t\t[AC_DEFUN([_LT_PROG_FC])])\nm4_ifndef([_LT_PROG_CXX],\t\t[AC_DEFUN([_LT_PROG_CXX])])\n"
  },
  {
    "path": "fst/layout/gf-complete/manual/gf-complete.html",
    "content": "<html>\r\n\r\n<head>\r\n\r\n<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\">\r\n\r\n</head>\r\n\r\n<body>\r\n\r\n<div id=\"box\">\r\n\r\n<h1>\r\nGF-Complete: A Comprehensive Open Source Library for Galois </br>\r\nField Arithmetic\r\n</h1>\r\n\r\n<h1> Version 1.02  </h1>\r\n\r\n<h4>James S. Plank* &nbsp&nbsp&nbsp&nbsp&nbsp&nbsp Ethan L. Miller \r\nKevin M. Greenan &nbsp&nbsp&nbsp&nbsp&nbsp&nbsp Benjamin A. Arnold<br>\r\nJohn A. Burnum &nbsp&nbsp&nbsp&nbsp&nbsp&nbsp Adam W. Disney  &nbsp&nbsp&nbsp&nbsp&nbsp&nbsp\r\nAllen C. McBride\r\n\r\n</h4> <br>\r\n\r\n\r\n\r\n<a href=\"\">\r\n\r\nhttps://bitbucket.org/jimplank/gf-complete\r\n\r\n </a><br><br>\r\n<a href=\"\"> \r\nhttp://web.eecs.utk.edu/~plank/plank/papers/GF-Complete-Manual-1.02.pdf\r\n\r\n\r\n </a> <br> <br> \r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n</div>\r\n\r\n\r\n<div id=\"pages_paragraphs_2\">\r\n\r\nThis is a user's manual for GF-Complete, version 1.02. This release supersedes version 0.1 and represents the first\r\nmajor release of GF-Complete. To our knowledge, this library implements every Galois Field multiplication technique\r\napplicable to erasure coding for storage, which is why we named it GF-Complete. The primary goal of this library is\r\nto allow storage system researchers and implementors to utilize very fast Galois Field arithmetic for Reed-Solomon\r\ncoding and the like in their storage installations. The secondary goal is to allow those who want to explore different\r\nways to perform Galois Field arithmetic to be able to do so effectively.\r\n\r\n\r\n<p>\r\nIf you wish to cite GF-Complete, please cite technical report UT-CS-13-716: [PMG<sup>+</sup>13].\r\n\r\n</p>\r\n\r\n\r\n<h2>If You Use This Library or Document </h2>\r\n\r\n\r\n\r\nPlease send me an email to let me know how it goes. Or send me an email just to let me know you are using the\r\nlibrary. One of the ways in which we are evaluated both internally and externally is by the impact of our work, and if\r\nyou have found this library and/or this document useful, we would like to be able to document it. Please send mail to\r\n<em>plank@cs.utk.edu.</em> Please send bug reports to that address as well.\r\n\r\n\r\n\r\n<p>\r\nThe library itself is protected by the New BSD License. It is free to use and modify within the bounds of this\r\nlicense. To the authors' knowledge, none of the techniques implemented in this library have been patented, and the\r\nauthors are not pursing patents. </p> <br>\r\n\r\n </div>\r\n<div id=\"footer\"> \r\n \r\n<span id=\"footer_bar\">&nbsp&nbsp&nbsp&nbsp.*plank@cs.utk.edu (University of Tennessee), el  </span> <em>m@cs.ucsc.edu </em>(UC Santa Cruz), <em>kmgreen2@gmail.com </em> (Box). This material\r\nis based upon work supported by the National Science Foundation under grants CNS-0917396, IIP-0934401 and CSR-1016636, plus REU supplements\r\nCNS-1034216, CSR-1128847 and CSR-1246277. Thanks to Jens Gregor for helping us wade through compilation issues, and for Will\r\nHouston for his initial work on this library.\r\n\r\n</div>\r\n\r\n<b>Finding the Code </b>\r\n<br><br>\r\nThis code is actively maintained on bitbucket:<a href=\"\"> https://bitbucket.org/jimplank/gf-complete. </a> There are\r\nprevious versions on my UTK site as a technical report; however, that it too hard to maintain, so the main version is\r\non bitbucket.<br><br>\r\n\r\n\r\n<b>Two Related Papers </b> <br><br>\r\n\r\nThis software acccompanies a large paper that describes these implementation techniques in detail [PGM13a]. We\r\nwill refer to this as <em> \"The Paper.\" </em> You do not have to read The Paper to use the software. However, if you want to\r\nstart exploring the various implementations, then The Paper is where you'll want to go to learn about the techniques\r\nin detail.\r\n\r\n\r\n\r\n<p>This library implements the techniques described in the paper \"Screaming Fast Galois Field Arithmetic Using Intel\r\nSIMD Instructions,\" [PGM13b]. The Paper describes all of those techniques as well.\r\n</p><br><br>\r\n\r\n<b>If You Would Like HelpWith the Software </b><br><br>\r\n\r\nPlease contact the first author of this manual.<br><br>\r\n\r\n<b>Changes from Revision 1.01</b>\r\n<br><br>\r\nThe major change is that we are using autoconf to aid with compilation, thus obviating the need for the old <b>flag_tester</b>\r\ncode. Additionally, we have added a quick timing tool, and we have modified <b>gf_methods</b> so that it may be used to\r\nrun the timing tool and the unit tester.\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\nCONTENT  <span class=\"aligning_page_number\"> 3 </span> \r\n<h2>Contents </h2>\r\n<div class=\"index\">\r\n1 <span class=\"aligning_numbers\">Introduction </span> <span class=\"aligning_page_number\"> 5 </span>\r\n  <br><br> \r\n2 <span class=\"aligning_numbers\">Files in the Library </span>\t<span class=\"aligning_page_number\"> 6  </span>  <br> </div>\r\n\r\n<div class=\"sub_indices\">\r\n2.1 Header files in the directory <b>\"include\"</b>  . .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . <span class=\"aligning_page_number\"> 6 </span>  <br>\r\n2.2 Source files in the <b>\"src\"</b> directory . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . .<span class=\"aligning_page_number\">   7  </span> <br>\r\n2.3 Library tools files in the <b>\"tools\"</b> directory  . . . . . . . . . . ..  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . .   <span class=\"aligning_page_number\"> 7   </span> <br>\r\n2.4 The unit tester in the <b>\"test\"</b> directory. . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . <span class=\"aligning_page_number\">  8  </span>  <br>\r\n2.5 Example programs in the <b>\"examples\"</b> directory . . . .  . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . .<span class=\"aligning_page_number\"> 8  </span> \r\n\r\n</div>\r\n<br>\r\n<div class=\"index\">\r\n\r\n3 <span class=\"aligning_numbers\">Compilation </span><span class=\"aligning_page_number\">  8 </span>  <br> <br>\r\n4 <span class=\"aligning_numbers\">Some Tools and Examples to Get You Started </span><span class=\"aligning_page_number\">  8 </span> <br><br>  </div> \r\n\r\n\r\n\r\n<div class=\"sub_indices\">\r\n4.1 Three Simple Command Line Tools: gf mult, gf div and gf add . . . . . . . . . .  . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . <span class=\"aligning_page_number\"> 8</span>  <br>\r\n4.2 Quick Starting Example #1: Simple multiplication and division . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . <span class=\"aligning_page_number\">   9  </span> <br>\r\n4.3 Quick Starting Example #2: Multiplying a region by a constant    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . .  <span class=\"aligning_page_number\"> 10   </span> <br>\r\n4.4 Quick Starting Example #3: Using w = 64 . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . <span class=\"aligning_page_number\">  11  </span>  <br>\r\n4.5 Quick Starting Example #4: Using w = 128. . . .  . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . <span class=\"aligning_page_number\"> 11  </span> \r\n</div>\r\n<br>\r\n\r\n\r\n<div class=\"index\">\r\n5 <span class=\"aligning_numbers\"> Important Information on Alignment when Multiplying Regions </span><span class=\"aligning_page_number\"> 12</span> <br><br>\r\n\r\n6 <span class=\"aligning_numbers\"> The Defaults</span><span class=\"aligning_page_number\"> 13 </span> <br>\r\n\r\n</div>\r\n\r\n<div class=\"sub_indices\">\r\n6.1 Changing the Defaults . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . .<span class=\"aligning_page_number\">   14  </span> <br>\r\n\r\n\r\n<ul style=\"list-style-type:none;\">\r\n<li>6.1.1 Changing the Components of a Galois Field with <b> create_gf_from_argv() </b>   . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . .  <span class=\"aligning_page_number\"> 15   </span> <br>\r\n</li>\r\n<li>\r\n6.1.2 Changing the Polynomial. . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . . . <span class=\"aligning_page_number\">  16  </span>  <br>\r\n</li>\r\n<li>\r\n6.1.3 Changing the Multiplication Technique. . . .  . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . .<span class=\"aligning_page_number\"> 17  </span> \r\n</li>\r\n\r\n\r\n<li>\r\n6.1.4 Changing the Division Technique . . . . . .  . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  <span class=\"aligning_page_number\"> 19  </span> \r\n</li>\r\n\r\n\r\n<li>\r\n6.1.5 Changing the Region Technique. . . .  . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . .  . . . ..<span class=\"aligning_page_number\"> 19  </span> \r\n</li>\r\n</ul>\r\n6.2 Determining Supported Techniques with <b>gf_methods</b> . . . . . . . . . .  . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . <span class=\"aligning_page_number\"> 20</span>  <br>\r\n\r\n6.3 Testing with <b>gf_unit, gf_time,</b> and <b>time_tool.sh </b>. . . . . . . . . .  . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . .  . . <span class=\"aligning_page_number\"> 21</span>\r\n\r\n<ul style=\"list-style-type:none;\">\r\n<li>\r\n6.3.1 <b>time_tool.sh</b> . . . . . .  . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . . .  <span class=\"aligning_page_number\"> 22 </span> \r\n</li>\r\n\r\n<li>\r\n6.3.2 An example of <b>gf_methods</b> and <b>time_tool.sh</b> . . . . . .  . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . .. . . . . . . .  . .. .  .<span class=\"aligning_page_number\"> 23  </span> \r\n</li>\r\n\r\n</ul>\r\n\r\n6.4 Calling <b>gf_init_hard()</b> . . . . . . . . . .  . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . . . .. . . . . . . . .. . . . . . . .  .. . . . . . . .  . . .  <span class=\"aligning_page_number\"> 24</span>  <br>\r\n\r\n6.5 <b>gf_size()</b> . . . . . . . . . .  . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . . .  .. . . . . . . . .. . . . . . . .  .. . . . . . . . .. . . . . . . . . . ..  .  <span class=\"aligning_page_number\"> 26</span>  <br><br>\r\n</div>\r\n\r\n\r\n<div class=\"index\">\r\n8 <span class=\"aligning_numbers\">  Further Information on Options and Algorithms </span><span class=\"aligning_page_number\">   26 </span> </div> <br><br> </div>\r\n<div class=\"sub_indices\">\r\n7.1 Inlining Single Multiplication and Division for Speed   . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . <span class=\"aligning_page_number\"> 26 </span> <br>\r\n7.2 Using different techniques for single and region multiplication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  .  <span class=\"aligning_page_number\"> 27 </span> <br>\r\n7.3 General <em>w</em> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . .  . <span class=\"aligning_page_number\"> 28  </span><br>\r\n\r\n7.4 Arguments to <b>\"SPLIT\"</b> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . <span class=\"aligning_page_number\"> 28</span>  <br>\r\n7.5 Arguments to <b>\"GROUP\"</b> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  <span class=\"aligning_page_number\">29 </span> <br>\r\n7.6 Considerations with <b>\"COMPOSITE\"</b> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . .  .  <span class=\"aligning_page_number\">30 </span> <br>\r\n7.7 <b>\"CARRY FREE\"</b> and the Primitive Polynomial  . . . . . . . . . . . . . . .  . . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . .   <span class=\"aligning_page_number\">31 </span> <br>\r\n7.8 More on Primitive Polynomials . .  . . . . . . . . . . . . . . . . . . .  . . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . ..   . . . . . . . . .  <span class=\"aligning_page_number\">31 </span> <br>\r\n\r\n\r\n<ul style=\"list-style-type:none;\">\r\n<li>\r\n7.8.1 Primitive Polynomials that are not Primitive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . .  . . .  <span class=\"aligning_page_number\"> 31</span>  <br>\r\n\r\n</li>\r\n<li>7.8.2 Default Polynomials for Composite Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . .  <span class=\"aligning_page_number\"> 32</span>  <br>\r\n\r\n</li>\r\n</ul>\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\nCONTENT  <span class=\"aligning_page_number\"> 4 </span> \r\n\r\n<div class=\"sub_indices\">\r\n<ul style=\"list-style-type:none\">\r\n<li> 7.8.3 The Program <b>gf_poly</b> for Verifying Irreducibility of Polynomials </span><span class=\"aligning_page_number\">  33 </span> \r\n</li>\r\n</ul>\r\n\r\n\r\n7.9<span class=\"aligning_numbers\"><b>\"ALTMAP\"</b> considerations and <b>extract_word()</b> </span><span class=\"aligning_page_number\">  34 </span>  \r\n<ul style=\"list-style-type:none\">\r\n<li>\r\n\r\n7.9.1 Alternate mappings with <b>\"SPLIT\"</b> . . . . . . . . . .  . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .<span class=\"aligning_page_number\"> 34</span>  <br>\r\n</li>\r\n<li>\r\n7.9.2 Alternate mappings with <b>\"COMPOSITE\"</b> . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . . . <span class=\"aligning_page_number\">   36  </span> <br>\r\n</li>\r\n<li>\r\n7.9.3 The mapping of <b>\"CAUCHY\"</b>    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . .. . . . . .. . . . . . . . . .  . ..  <span class=\"aligning_page_number\"> 37   </span> <br>\r\n</li>\r\n</ul>\r\n</div>\r\n\r\n\r\n8 <span class=\"aligning_numbers\"><b>Thread Safety </b></span><span class=\"aligning_page_number\">  37 </span> <br><br>  </div> \r\n\r\n9 <span class=\"aligning_numbers\"><b>Listing of Procedures</b> </span><span class=\"aligning_page_number\">  37 </span> <br><br>  </div> \r\n\r\n10 <span class=\"aligning_numbers\"><b>Troubleshooting</b> </span><span class=\"aligning_page_number\">  38 </span> <br><br>  </div> \r\n11 <span class=\"aligning_numbers\"><b>Timings</b> </span><span class=\"aligning_page_number\">  41 </span> <br><br>  </div> \r\n\r\n<div class=\"sub_indices\">\r\n11.1 Multiply() . . . . . . . . . .  . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . . . .. . . . . . . . .. . . . . . . . .. . . . . . . . . . . . .  . . . .. . . . <span class=\"aligning_page_number\"> 42</span>  <br>\r\n11.2 Divide() . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . .. . . . . . . . .. . . . . . . . .. . . . . . . . .. . . . . . . . . . . .. . . . . <span class=\"aligning_page_number\">   42  </span> <br>\r\n11.3 Multiply Region()    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . .. . . . . . . . .. . . . . . . . .. . . . . . . . . . . . .  . . . . .  <span class=\"aligning_page_number\"> 43   </span> <br>\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\nINTRODUCTION  <span class=\"aligning_page_number\"> 5 </span> \r\n\r\n\r\n<h3>1 Introduction </h3>\r\n\r\nGalois Field arithmetic forms the backbone of erasure-coded storage systems, most famously the Reed-Solomon\r\nerasure code. A Galois Field is defined over w-bit words and is termed <em>GF(2<sup>w</sup>).</em> As such, the elements of a Galois\r\nField are the integers 0, 1, . . ., 2<sup>w</sup> - 1. Galois Field arithmetic defines addition and multiplication over these closed\r\nsets of integers in such a way that they work as you would hope they would work. Specifically, every number has a\r\nunique multiplicative inverse. Moreover, there is a value, typically the value 2, which has the property that you can\r\nenumerate all of the non-zero elements of the field by taking that value to successively higher powers.\r\n\r\n\r\n<p>Addition in a Galois Field is equal to the bitwise exclusive-or operation. That's nice and convenient. Multiplication\r\nis a little more complex, and there are many, many ways to implement it. The Paper describes them all, and the\r\nfollowing references providemore supporting material: [Anv09, GMS08, LHy08, LD00, LBOX12, Pla97]. The intent\r\nof this library is to implement all of the techniques. That way, their performancemay be compared, and their tradeoffs\r\nmay be analyzed. <p>\r\n\r\n\r\n\r\n\r\n<ol>\r\n\r\nWhen used for erasure codes, there are typically five important operations:<br>\r\n<li> <b>Adding two numbers in </b> GF(2<sup>w</sup>). That's bitwise exclusive-or. </li>\r\n<li> <b>Multiplying two numbers in</b> GF(2<sup>w</sup>). Erasure codes are usually based on matrices in GF(2<sup>w</sup>), and constructing\r\nthese matrices requires both addition and multiplication.</li>\r\n<li> <b>Dividing two numbers in </b>GF(2<sup>w</sup>). Sometimes you need to divide to construct matrices (for example, Cauchy\r\nReed-Solomon codes [BKK<sup>+</sup>95, Rab89]). More often, though, you use division to invert matrices for decoding.\r\nSometimes it is easier to find a number's inverse than it is to divide. In that case, you can divide by multiplying\r\nby an inverse. </li>\r\n\r\n<li><b>adding two regions of numbers in</b> GF(2<sup>w</sup>), which will be explained along with... </li>\r\n<li> <b>Mutiplying a region of numbers in </b>GF(2<sup>w</sup>) by a constant in GF(2<sup>w</sup>). Erasure coding typically boils down\r\nto performing dot products in GF(2<sup>w</sup>). For example, you may define a coding disk using the equation: </li><br>\r\n\r\n\r\n\r\n\r\n<center>c<em><sub>0</sub></em>= d<em><sub>0</sub></em> + 2d<em><sub>1</sub></em> + 4d<em><sub>2</sub></em> + 8d<em><sub>3</sub></em>.</sup> </center><br>\r\n\r\nThat looks like three multiplications and three additions However, the way ' implemented in a disk system\r\nlooks as in Figure 1. Large regions of disks are partitioned into w-bit words in GF(2<sup>w</sup>). In the example, let us\r\nsuppose that <em>w</em> = 8, and therefore that words are bytes. Then the regions pictured are 1 KB from each disk.\r\nThe bytes on disk Di are labeled d<sub>i,0,</sub> d<sub>i,1, . . . ,</sub> d<sub>i,1023,</sub> and the equation above is replicated 1024 times. For\r\n0 &#8804 j < 1024:\r\n<br><br>\r\n<center>c<em><sub>0,j</sub></em> = d<em><sub>0,j</sub></em> + 2d<em><sub>1,j</sub></em> + 4d<em><sub>2,j</sub></em> + 8d<em><sub>3,j</sub></em> . </center>\r\n<br>\r\n\r\n\r\nWhile it's possible to implement each of these 1024 equations independently, using the single multiplication\r\nand addition operations above, it is often much more efficient to aggregate. For example, most computer architectures\r\nsupport bitwise exclusive-or of 64 and 128 bit words. Thus, it makes much more sense to add regions\r\nof numbers in 64 or 128 bit chunks rather than as words in GF(2<sup>w</sup>). Multiplying a region by a constant can\r\nleverage similar optimizations. </ol>\r\n\r\n\r\n<p>GF-Complete supports multiplication and division of single values for all values of <em>w</em> &#8804 32, plus <em>w</em> = 64 and <em>w</em> =\r\n128. It also supports adding two regions of memory (for any value of <em>w</em>, since addition equals XOR), and multiplying\r\na region by a constant in <em>GF(2<sup>4</sup>), GF(2<sup>8</sup>), GF(2<sup>16</sup>), GF(2<sup>32</sup>), GF(2<sup>64</sup>) and GF(2<sup>128</sup>).</em> These values are chosen\r\nbecause words in GF(2<sup>w</sup>) fit into machine words with these values of <em>w.</em> Other values of w don't lend themselves\r\nto efficient multiplication of regions by constants (although see the <b>\"CAUCHY\"</b> option in section 6.1.5 for a way to\r\nmultiply regions for other values of <em>w</em>).</p>\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n2 &nbsp &nbsp  <em>  FILES IN THE LIBRARY     </em>   <span id=\"index_number\">6  </span> <br><br><br>\r\n\r\n\r\n\r\n<div class=\"image-cell_1\"> </div>  <br><br><br>\r\n\r\nFigure 1: An example of adding two regions of numbers, and multiplying a region of numbers by a constant\r\nin <em>GF(2<sup>w</sup>) </em>. In this example, <em>w</em> = 8, and each disk is holding a 1KB region. The same coding equation -\r\nc<sub>0,j</sub></b> = d<sub>0,j</sub> + ad<sub>1,j</sub> + a<sup>2</sup>d<sub>2,j</sub> + a<sup>3</sup>d<sub>3,j</sub> is applied 1024 times. However, rather than executing this equation 1024\r\ntimes, it is more efficient to implement this with three region-constant multiplications and three region-region additions.\r\n\r\n<h3>2 &nbsp&nbsp&nbsp Files in the Library </h3>\r\nThis section provides an overview of the files that compose GF-Complete. They are partitioned among multiple\r\ndirectories.\r\n\r\n<h4> <b>2.1 &nbsp&nbsp&nbsp Header files in the directory  \"include\"</b> </h4>\r\n\r\nThe following header files are part of GF-Complete.\r\n<ul>\r\n<li><b>gf_complete.h:</b> This is the header file that applications should include. It defines the gf_t type, which holds\r\nall of the data that you need to perform the various operations in GF(2<sup>w</sup>). It also defines all of the arithmetic\r\noperations. For an application to use this library, you should include gf_complete.h and then compile with the\r\nlibrary src/libgf_complete.la. </li><br>\r\n\r\n<li><b>gf_method.h:</b> If you are wanting to modify the implementation techniques from the defaults, this file provides\r\na \"helper\" function so that you can do it from the Unix command line.\r\n</li><br>\r\n\r\n<li><b>gf_general.h:</b> This file has helper routines for doing basic Galois Field operations with any legal value of <em>w.</em>\r\nThe problem is that <em>w </em> &#8804 32, <em>w </em> = 64 and <em> w </em> = 128 all have different data types, which is a pain. The procedures\r\nin this file try to alleviate that pain. They are used in <b>gf_mult, gf_unit</b> and <b>gf_time.</b> I'm guessing that most\r\napplications won't use them, as most applications use <em>w</em> &#8804 32. </li><br>\r\n\r\n<li><b>gf_rand.h:</b> I've learned that <b>srand48()</b> and its kin are not supported in all C installations. Therefore, this file\r\ndefines some randomnumber generators to help test the programs. The randomnumber generator is the \"Mother\r\n</li>\r\n\r\n</ul>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n2 &nbsp &nbsp  <em>  FILES IN THE LIBRARY     </em>   <span id=\"index_number\">7  </span> <br><br><br>\r\n<ul>\r\n\r\nof All\" random number generator [Mar94] which we've selected because it has no patent issues. <b>gf_unit</b> and\r\ngf time use these random number generators.<br><br>\r\n<li><b>gf_int.h:</b> This is an internal header file that the various source files use. This is <em>not</em> intended for applications to\r\ninclude.</li><br>\r\n<li><b>config.xx</b> and <b>stamp-h1</b> are created by autoconf, and should be ignored by applications. </li>\r\n</ul>\r\n\r\n<h3>2.2 &nbsp &nbsp <b> Source files in the \"src\" directory\" </b> </h3>\r\n<ul>\r\nThe following C files compose <b>gf_complete.a,</b> and they are in the direcoty src. You shouldn't have to mess with these\r\nfiles, but we include them in case you have to:<br><br>\r\n<li><b> gf_.c:</b> This implements all of the procedures in both <b>gf_complete.h</b> and <b>gf_int.h.</b> </li><br>\r\n<li><b> gf_w4.c:</b> Procedures specific to <em>w </em> = 4. </li><br>\r\n<li> <b>gf_w8.c:</b> Procedures specific to <em>w </em> = 8</li><br>\r\n<li> <b>gf_w16.c:</b> Procedures specific to <em>w </em> = 16</li><br>\r\n<li> <b>gf_w32.c:</b> Procedures specific to <em>w </em> = 32</li><br>\r\n<li><b>gf_w64.c:</b> Procedures specific to <em>w </em> = 64</li><br>\r\n<li> <b>gf_w128.c:</b> Procedures specific to <em>w </em> = 128</li><br>\r\n<li> <b>gf_wgen.c:</b> Procedures specific to other values of <em>w </em> between 1 and 31</li><br>\r\n<li> <b>gf_general.c:</b> Procedures that let you manipulate general values, regardless of whether <em>w </em> &#8804 32, <em>w </em> = 64\r\nor <em>w </em> = 128. (I.e. the procedures defined in <b>gf_ general.h</b>)</li><br>\r\n<li> <b>gf_method.c:</b> Procedures to help you switch between the various implementation techniques. (I.e. the procedures\r\ndefined in <b>gf_method.h</b>)</li><br>\r\n<li> <b>gf_ rand.c:</b>\"The Mother of all\" random number generator. (I.e. the procedures defined in <b>gf_rand.h</b>)</li><br> </ul>\r\n\r\n<h3>2.3 &nbsp &nbsp Library tools files in the \"tools\" directory </h3>\r\n\r\n<ul>\r\nThe following are tools to help you with Galois Field arithmetic, and with the library. They are explained in greater\r\ndetail elsewhere in this manual.<br><br>\r\n<li> <b>gf_mult.c, gf_ div.c</b> and <b>gf_ add:</b> Command line tools to do multiplication, division and addition by single numbers</li><br>\r\n<li> <b>gf_time.c:</b> A program that times the procedures for given values of <em>w </em> and implementation options</li><br>\r\n<li> <b>time tool.sh:</b> A shell script that helps perform rough timings of the various multiplication, division and region\r\noperations in GF-Complete</li><br>\r\n<li> <b>gf_methods.c:</b> A program that enumerates most of the implementation methods supported by GF-Complete</li><br>\r\n<li> <b> gf_poly.c:</b> A program to identify irreducible polynomials in regular and composite Galois Fields</li><br>\r\n\r\n</ul>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n3 &nbsp &nbsp  <em>  COMPILATION     </em>   <span id=\"index_number\">8  </span> <br><br><br>\r\n\r\n\r\n<h3>2.4 &nbsp &nbsp The unit tester in the \"test\" directory </h3>\r\n\r\nThe test directory contains the proram <b>gf_unit.c,</b> which performs a battery of unit tests on GF-Complete. This is\r\nexplained in more detail in section 6.3.\r\n\r\n\r\n<h3>2.5&nbsp &nbsp Example programs in the \"examples\" directory </h3>\r\n\r\nThere are seven example programs to help you understand various facets of GF-Complete. They are in the files\r\n<b>gf_example x.c </b> in the <b>examples</b> directory. They are explained in sections 4.2 through 4.5, and section 7.9.<br><br>\r\n\r\n<h2>3 &nbsp &nbsp Compilation </h2>\r\n\r\n<em>From revision 1.02 forward, we are using autoconf. The old \"flag tester\" directory is now gone, as it is no longer in\r\nuse. </em><br><br>\r\nTo compile and install, you should do the standard operations that you do with most open source Unix code:<br><br>\r\n\r\nUNIX> ./configure <br>\r\n... <br>\r\nUNIX> make <br>\r\n... <br>\r\nUNIX> sudo make install <br><br>\r\n\r\n\r\n<p>If you perform the <b>install,</b> then the header, source, tool, and library files will be moved to system locations. In\r\nparticular, you may then compile the library by linking with the flag <b>-lgf_complete,</b> and you may use the tools from a\r\nglobal executable directory (like <b>/usr/local/bin</b>). </p>\r\n\r\n<p>\r\nIf you don't perform the install, then the header and tool files will be in their respective directories, and the library\r\nwill be in <b>src/libgf_complete.la.</b> </p>\r\n<p>\r\nIf your system supports the various Intel SIMD instructions, the compiler will find them, and GF-Complete will\r\nuse them by default. </p>\r\n\r\n\r\n\r\n<h2>4 &nbsp &nbsp Some Tools and Examples to Get You Started </h2> \r\n<h3>4.1 Three Simple Command Line Tools: gf_mult, gf_div and gf_add </h3>\r\n\r\n\r\nBefore delving into the library, it may be helpful to explore Galois Field arithmetic with the command line tools:\r\n<b>gf_mult, gf_div </b> and <b>gf_add.</b> These perform multiplication, division and addition on elements in <em>GF(2<sup>w</sup>).</em> If these are\r\nnot installed on your system, then you may find them in the tools directory. Their syntax is:\r\n<ul>\r\n<li><b>gf_mult a b</b> <em>w </em> - Multiplies a and b in <em> GF(2<sup>w</sup>)</em>. </li><br>\r\n<li> <b>gf_div a b </b><em>w </em> - Divides a by b in GF(2<em><sup>w </sup></em>). </li><br>\r\n<li><b>gf_add a b </b> <em>w </em> - Adds a and b in GF(2<em><sup>w </sup> </em>). </li><br>\r\n\r\nYou may use any value of <em>w </em> from 1 to 32, plus 64 and 128. By default, the values are read and printed in decimal;\r\nhowever, if you append an 'h' to <em>w </em>, then <em>a, b </em> and the result will be printed in hexadecimal. For <em>w </em> = 128, the 'h' is\r\nmandatory, and all values will be printed in hexadecimal.\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n4 &nbsp &nbsp  <em>   SOME TOOLS AND EXAMPLES TO GET YOU STARTED 9     </em>   <span id=\"index_number\">9  </span> <br><br><br>\r\n\r\n\r\n<p>Try them out on some examples like the ones below. You of course don't need to know that, for example, 5 * 4 = 7\r\nin <em>GF(2<sup>4 </sup>) </em>; however, once you know that, you know that 7/\r\n5 = 4 and 7/4 = 5. You should be able to verify the <b>gf_add</b>\r\nstatements below in your head. As for the other <b>gf_mult's</b>, you can simply verify that division and multiplication work\r\nwith each other as you hope they would. </p>\r\n<br><br>\r\n<div id=\"number_spacing\">\r\n\r\nUNIX> gf_mult 5 4 4  <br>\r\n7 <br>\r\nUNIX> gf_div 7 5 4 <br>\r\n4 <br>\r\nUNIX> gf_div 7 4 4 <br>\r\n5   <br>\r\nUNIX> gf_mult 8000 2 16h <br>\r\n100b  <br>\r\nUNIX> gf_add f0f0f0f0f0f0f0f0 1313131313131313 64h <br>\r\ne3e3e3e3e3e3e3e3 <br>\r\nUNIX> gf_mult f0f0f0f0f0f0f0f0 1313131313131313 64h <br>\r\n8da08da08da08da0 <br>\r\nUNIX> gf_div 8da08da08da08da0 1313131313131313 64h <br>\r\nf0f0f0f0f0f0f0f0  <br>\r\nUNIX> gf_add f0f0f0f0f0f0f0f01313131313131313 1313131313131313f0f0f0f0f0f0f0f0 128h <br>\r\ne3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3 <br>\r\nUNIX> gf_mult f0f0f0f0f0f0f0f01313131313131313 1313131313131313f0f0f0f0f0f0f0f0 128h <br>\r\n786278627862784982d782d782d7816e <br>\r\nUNIX> gf_div 786278627862784982d782d782d7816e f0f0f0f0f0f0f0f01313131313131313 128h <br>\r\n1313131313131313f0f0f0f0f0f0f0f0 <br>\r\nUNIX> <br><br>\r\n\r\n</div>\r\n\r\n\r\nDon't bother trying to read the source code of these programs yet. Start with some simpler examples  like the ones\r\nbelow. <br><br>\r\n\r\n<h3>4.2 Quick Starting Example #1: Simple multiplication and division </h3>\r\n\r\nThe source files for these examples are in the examples directory.\r\n<p>These two examples are intended for those who just want to use the library without getting too complex. The\r\nfirst example is <b>gf_example 1,</b> and it takes one command line argument - w, which must be between 1 and 32. It\r\ngenerates two random non-zero numbers in <em>GF(2<sup>w </sup>) </em> and multiplies them. After doing that, it divides the product by\r\neach number. </p>\r\n<p>\r\nTo perform multiplication and division in <em>GF(2<sup>w </sup>) </em>, you must declare an instance of the gf_t type, and then initialize\r\nit for <em>GF(2<sup>w </sup>) </em> by calling <b>gf_init_easy().</b> This is done in <b>gf_example 1.c</b> with the following lines: </p><br><br>\r\n\r\ngf_t gf; <br><br>r\r\n... <br><br>\r\nif (!gf_init_easy(&gf, w)) { <br>\r\nfprintf(stderr, \"Couldn't initialize GF structure.\\n\"); <br>\r\nexit(0); <br>\r\n}  <br>\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n4 &nbsp &nbsp  <em>   SOME TOOLS AND EXAMPLES TO GET YOU STARTED      </em>   <span id=\"index_number\">10  </span> <br><br><br>\r\n\r\n<p>Once <b>gf</b> is initialized, you may use it for multiplication and division with the function pointers <b>multiply.w32</b> and\r\n<b>divide.w32.</b> These work for any element of <em>GF(2<sup>w</sup>)</em> so long as w &#8804 32. </p> <br><br>\r\n\r\n<div id=\"number_spacing\">\r\n<div style=\"padding-left:54px\">\r\nc = gf.multiply.w32(&gf, a, b);<br>\r\nprintf(\"%u * %u = %u\\n\", a, b, c);<br><br>\r\nprintf(\"%u / %u = %u\\n\", c, a, gf.divide.w32(&gf, c, a));<br>\r\nprintf(\"%u / %u = %u\\n\", c, b, gf.divide.w32(&gf, c, b));<br>\r\n\r\n\r\n</div> </div>\r\n<br><br>\r\nGo ahead and test this program out. You can use <b>gf_mult</b> and <b>gf_div</b> to verify the results:<br><br>\r\n\r\n<div id=\"number_spacing\">\r\nUNIX> gf_example_1 4 <br>\r\n12 * 4 = 5  <br>\r\n5 / 12 = 4  <br>\r\n5 / 4 = 12  <br>\r\nUNIX> gf_mult 12 4 4 <br>\r\n5  <br>\r\nUNIX> gf_example_1 16 <br>\r\n14411 * 60911 = 44568 <br>\r\n44568 / 14411 = 60911 <br>\r\n44568 / 60911 = 14411  <br>\r\nUNIX> gf_mult 14411 60911 16 <br>\r\n44568 <br>\r\nUNIX>  <br><br>\r\n</div>\r\n\r\n<b>gf_init_easy()</b> (and <b>later_gf_init_hard()</b>) do call <b>malloc()</b> to implement internal structures. To release memory, call\r\n<b>gf_free().</b> Please see section 6.4 to see how to call <b>gf_init_hard()</b> in such a way that it doesn't call <b>malloc().</b> <br><br>\r\n\r\n\r\n\r\n<h3>4.3 &nbsp &nbsp &nbspQuick Starting Example #2: Multiplying a region by a constant </h3>\r\n\r\n\r\nThe program <b>gf_example</b> 2 expands on <b>gf_example</b> 1. If <em>w</em> is equal to 4, 8, 16 or 32, it performs a region multiply\r\noperation. It allocates two sixteen byte regions, <b>r1</b> and <b>r2,</b> and then multiples <b>r1</b> by a and puts the result in <b>r2</b> using\r\nthe <b>multiply_region.w32</b> function pointer: <br><br>\r\n\r\n<div style=\"padding-left:52px\">\r\ngf.multiply_region.w32 (&gf, r1, r2, a, 16, 0); <br><br>\r\n</div>\r\n\r\nThat last argument specifies whether to simply place the product into r2 or to XOR it with the contents that are already\r\nin r2. Zero means to place the product there. When we run it, it prints the results of the <b>multiply_region.w32</b> in\r\nhexadecimal. Again, you can verify it using gf mult:<br><br>\r\n<div id=\"number_spacing\">\r\nUNIX> gf_example_2 4 <br>\r\n12 * 2 = 11 <br>\r\n11 / 12 = 2 <br>\r\n11 / 2 = 12 <br><br>\r\nmultiply_region by 0xc (12) <br><br>\r\nR1 (the source): 0 2 d 9 d 6 8 a 8 d b 3 5 c 1 8 8 e b 0 6 1 5 a 2 c 4 b 3 9 3 6 <br>\r\nR2 (the product): 0 b 3 6 3 e a 1 a 3 d 7 9 f c a a 4 d 0 e c 9 1 b f 5 d 7 6 7 e <br>\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n4 &nbsp &nbsp  <em>   SOME TOOLS AND EXAMPLES TO GET YOU STARTED      </em>   <span id=\"index_number\">11  </span> <br><br><br>\r\n\r\n<div id=\"number_spacing\">\r\n<table cellpadding=\"6\">\r\n<tr><td>UNIX></td> <td colspan=\"4\"> gf_example_2 16 </td> </tr>\r\n\r\n<tr>\r\n\r\n<td>49598</td> <td> * </td> <td> 35999</td> <td> = </td> <td>19867 </td> </tr>\r\n\r\n<tr><td>19867 </td><td>/ </td> <td> 49598 </td> <td> =  </td> <td>35999 </td> </tr>\r\n<tr><td>19867</td><td> /</td> <td> 35999 </td> <td> = </td> <td> 49598 </td> </tr>  </table><br>\r\n\r\n\r\n&nbsp multiply_region by 0xc1be (49598) <br><br>\r\n\r\n\r\n<table cellpadding=\"6\" >\r\n<tr>\r\n<td>R1 (the source):</td> <td> 8c9f </td> <td> b30e </td> <td> 5bf3 </td> <td> 7cbb </td> <td>16a9 </td> <td> 105d </td> <td> 9368 </td> <td> 4bbe </td> </tr>\r\n<td>R2 (the product):</td> <td> 4d9b</td> <td> 992d </td> <td> 02f2 </td> <td> c95c </td> <td> 228e </td> <td> ec82 </td> <td> 324e </td> <td> 35e4 </td></tr>\r\n</table>\r\n</div>\r\n<div id=\"number_spacing\">\r\n<div style=\"padding-left:9px\">\r\nUNIX> gf_mult c1be 8c9f 16h<br>\r\n4d9b <br>\r\nUNIX> gf_mult c1be b30e 16h <br>\r\n992d <br>\r\nUNIX> <br><br>\r\n</div>\r\n</div>\r\n\r\n<h3>4.4 &nbsp &nbsp &nbsp Quick Starting Example #3: Using <em>w </em>= 64 </h3>\r\nThe program in <b>gf_example 3.c </b> is identical to the previous program, except it uses <em> GF(2<sup>64 </sup>). </em> Now <em>a, b</em> and <em> c </em> are\r\n<b>uint64 t'</b>s, and you have to use the function pointers that have <b>w64</b> extensions so that the larger types may be employed.\r\n<br><br>\r\n<div id=\"number_spacing\">\r\n\r\nUNIX> gf_example_31 \r\n<table cellpadding=\"6\">\r\n<tr>\r\n\r\n<td>a9af3adef0d23242 </td> <td> * </td> <td> 61fd8433b25fe7cd</td> <td> = </td> <td>bf5acdde4c41ee0c </td> </tr>\r\n\r\n<td>bf5acdde4c41ee0c </td> <td> / </td> <td> a9af3adef0d23242 </td> <td> = </td> <td>61fd8433b25fe7cd </td> </tr>\r\n<td>bf5acdde4c41ee0c </td> <td> / </td> <td> 61fd8433b25fe7cd  </td> <td>= </td> <td>a9af3adef0d23242 </td> </tr>\r\n</table><br><br>\r\n\r\n&nbsp multiply_region by a9af3adef0d23242<br><br>\r\n<table cellpadding=\"6\" >\r\n<tr>\r\n<td>R1 (the source): </td> <td> 61fd8433b25fe7cd </td> <td>272d5d4b19ca44b7 </td> <td> 3870bf7e63c3451a </td> <td> 08992149b3e2f8b7 </td> </tr>\r\n<tr><td>R2 (the product): </td> <td> bf5acdde4c41ee0c </td> <td> ad2d786c6e4d66b7 </td> <td> 43a7d857503fd261 </td> <td> d3d29c7be46b1f7c </td> </tr>\r\n</table>\r\n\r\n<div style=\"padding-left:9px\">\r\n\r\nUNIX> gf_mult a9af3adef0d23242 61fd8433b25fe7cd 64h <br>\r\nbf5acdde4c41ee0c<br>\r\nUNIX><br><br>\r\n</div>\r\n</div>\r\n<h3>4.5 &nbsp &nbsp &nbsp Quick Starting Example #4: Using <em>w </em>= 128 </h3>\r\nFinally, the program in <b>gf_example_4.c</b> uses  <em>GF(2<sup>128</sup>).</em> Since there is not universal support for uint128 t, the library\r\nrepresents 128-bit numbers as arrays of two uint64 t's. The function pointers for multiplication, division and region\r\nmultiplication now accept the return values as arguments:<br><br>\r\n\r\ngf.multiply.w128(&gf, a, b, c); <br><br>\r\n\r\nAgain, we can use <b>gf_mult </b> and <b>gf_div </b>to verify the results:<br><br>\r\n<div id=\"number_spacing\">\r\n<div style=\"padding-left:9px\">\r\nUNIX> gf_example_4 </div>\r\n<table cellpadding=\"6\" >\r\n<tr>\r\n\r\n<td>e252d9c145c0bf29b85b21a1ae2921fa </td> <td> * </td> <td> b23044e7f45daf4d70695fb7bf249432 </td> <td> = </td> </tr>\r\n<tr><td>7883669ef3001d7fabf83784d52eb414 </td> </tr>\r\n\r\n</table>\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n4 &nbsp &nbsp  <em>   IMPORTANT INFORMATION ON ALIGNMENT WHEN MULTIPLYING REGIONS      </em>   <span id=\"index_number\">12  </span> <br><br><br>\r\n\r\n<div id=\"number_spacing\">\r\nmultiply_region by e252d9c145c0bf29b85b21a1ae2921fa <br>\r\nR1 (the source): f4f56f08fa92494c5faa57ddcd874149 b4c06a61adbbec2f4b0ffc68e43008cb <br>\r\nR2 (the product): b1e34d34b031660676965b868b892043 382f12719ffe3978385f5d97540a13a1 <br>\r\nUNIX> gf_mult e252d9c145c0bf29b85b21a1ae2921fa f4f56f08fa92494c5faa57ddcd874149 128h <br>\r\nb1e34d34b031660676965b868b892043 <br>\r\nUNIX> gf_div 382f12719ffe3978385f5d97540a13a1 b4c06a61adbbec2f4b0ffc68e43008cb 128h<br>\r\ne252d9c145c0bf29b85b21a1ae2921fa<br>\r\nUNIX><br><br>\r\n\r\n</div>\r\n\r\n\r\n<h2>5 &nbsp &nbsp &nbspImportant Information on Alignment when Multiplying Regions </h2>\r\n\r\n\r\n\r\nIn order to make multiplication of regions fast, we often employ 64 and 128 bit instructions. This has ramifications\r\nfor pointer alignment, because we want to avoid bus errors, and because on many machines, loading and manipulating\r\naligned quantities is much faster than unalinged quantities.<br><br>\r\n\r\n\r\nWhen you perform multiply_region.wxx(<em>gf, source, dest, value, size, add </em>), there are three requirements:\r\n<ol>\r\n<li>\r\n The pointers <em>source</em> and <em>dest </em> must be aligned for <em>w</em>-bit words. For <em>w </em> = 4 and <em>w </em> = 8, there is no restriction;\r\nhowever for <em>w </em> = 16, the pointers must be multiples of 2, for <em>w </em> = 32, they must be multiples of 4, and for\r\n<em>w </em> &#1013; {64, 128}, they must be multiples of 8. </li><br>\r\n\r\n<li> The <em>size</em> must be a multiple of &#91; <em>w /\r\n</em> \r\n8 .&#93;\r\n With <em>w </em> = 4 and <em>w </em> = 8, <em>w/ </em>\r\n8  = 1 and there is no restriction. The other\r\nsizes must be multiples of <em>w </em>/\r\n8  because you have to be multiplying whole elements of <em> GF(2<sup>w </sup>) </em>. </li><br>\r\n\r\n<li> The <b>source</b> and <b>dest</b> pointers must be aligned identically with respect to each other for the implementation\r\nchosen. This is subtle, and we explain it in detail in the next few paragraphs. However, if you'd rather not figure\r\nit out, the following recommendation will <em>always </em> work in GF-Complete: </li>\r\n\r\n</ol>\r\n\r\n\r\n\r\n<div style=\"padding-left:100px\">\r\n<b>If you want to be safe, make sure that source and dest are both multiples of 16. That is not a\r\nstrict requirement, but it will always work! </b> <br><br>\r\n</div>\r\n\r\n\r\nIf you want to relax the above recommendation, please read further.\r\n<p>When performing <b>multiply_region.wxx() </b>, the implementation is typically optimized for a region of bytes whose\r\nsize must be a multiple of a variable <em>s </em> ,, and which must be aligned to a multiple of another variable <em>t </em>. For example,\r\nwhen doing <b>multiply_region.w32() </b> in <em> GF(2<sup>16 </sup>) </em> with SSE enabled, the implementation is optimized for regions of\r\n32 bytes, which must be aligned on a 16-byte quantity. Thus, <em>s </em> = 32 and <em>t</em> = 16. However, we don't want <b>multiply_\r\nregion.w32() </b> to be too restrictive, so instead of requiring <em>source</em> and <em> dest </em> to be aligned to 16-byte regions, we\r\nrequire that (<em>source </em> mod 16) equal (<em>dest</em> mod 16). Or, in general, that (<em>source</em> mod t) equal (<em>dest</em> mod <em>t</em>). </p>\r\n\r\n\r\n<p>\r\nThen, <b>multiply_region.wxx()</b> proceeds in three phases. In the first phase,<b> multiply.wxx()</b> is called on successive\r\nwords until (<em>source</em> mod <em>t</em>) equals zero. The second phase then performs the optimized region multiplication on\r\nchunks of <em> s  </em>bytes, until the remaining part of the region is less than s bytes. At that point, the third phase calls\r\n<em>multiply.wxx() </em> on the last part of the region. </p>\r\n\r\nA detailed example helps to illustrate. Suppose we make the following call in <em>GF(2<sup>16</sup>) </em> with SSE enabled:<br><br>\r\n<center><b>multiply region.w32(gf, 0x10006, 0x20006, a, 274, 0)</b> </center>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n2 &nbsp &nbsp  <em>  FILES IN THE LIBRARY     </em>   <span id=\"index_number\">13  </span> <br><br><br>\r\n\r\n\r\n\r\n<div class=\"image-cell_2\"> </div>  <br><br><br>\r\n\r\nFigure 2: Example of multiplying a region of 274 bytes in GF(216) when (source mod 16) = (dest mod 16) = 6. The\r\nalignment parameters are s = 32 and t = 16. The multiplication is in three phases, which correspond to the initial\r\nunaligned region (10 bytes), the aligned region of s-byte chunks (256 bytes), and the final leftover region (8 bytes).\r\n\r\n\r\n<p>First, note that <em>source</em> and <em>dest</em> are aligned on two-byte quantities, which they must be in <em>GF(2<sup>16</sup>).</em> Second, note\r\nthat size is a multiple of &#91; 16/\r\n8 &#93 = 2. And last, note that (<em>source</em> mod 16) equals (<em>dest</em> mod 16). We illustrate the three\r\nphases of region multiplication in Figure 2. Because (<em>source</em> mod 16) = 6, there are 10 bytes of unaligned words that\r\nare multiplied with five calls to <b>multiply.w32()</b> in the first phase. The second phase multiplies 256 bytes (eight chunks\r\nof <em>s</em> = 32 bytes) using the SSE instructions. That leaves 8 bytes remaining for the third phase.\r\n</p>\r\n\r\n<p>\r\nWhen we describe the defaults and the various implementation options, we specify s and t as \"alignment parameters.\"\r\n</p>\r\n<p>\r\nOne of the advanced region options is using an alternate mapping of words to memory (\"ALTMAP\"). These interact\r\nin a more subtle manner with alignment. Please see Section 7.9 for details.\r\n</p>\r\n\r\n<h3> 6 &nbsp &nbspThe Defaults </h3>\r\n\r\n\r\nGF-Complete implements a wide variety of techniques for multiplication, division and region multiplication. We have\r\nset the defaults with three considerations in mind:\r\n<ol>\r\n<li>\r\n<b>Speed:</b> Obviously, we want the implementations to be fast. Therefore, we choose the fastest implementations\r\nthat don’t violate the other considerations. The compilation environment is considered. For example, if SSE is\r\nenabled, region multiplication in <em> GF(2<sup>4 </sup>) </em> employs a single multiplication table. If SSE is not enabled, then a\r\n\"double\" table is employed that performs table lookup two bytes at a time. </li><br>\r\n<li>\r\n<b>Memory Consumption:</b> We try to keep the memory footprint of GF-Complete low. For example, the fastest\r\nway to perform <b>multiply.w32()</b> in <em>GF(2<sup>32</sup>) </em> is to employ 1.75 MB of multiplication tables (see Section 7.4\r\nbelow). We do not include this as a default, however, because we want to keep the default memory consumption\r\nof GF-Complete low.\r\n</li>\r\n\r\n</ul>\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n6 &nbsp &nbsp  <em>  THE DEFAULTS     </em>   <span id=\"index_number\">14  </span> <br><br><br>\r\n\r\n<ul>\r\n\r\n3. &nbsp <b>Compatibility with \"standard\" implementations:</b> While there is no <em>de facto</em> standard of Galois Field arithmetic,\r\nmost libraries implement the same fields. For that reason, we have not selected composite fields, alternate\r\npolynomials or memory layouts for the defaults, even though these would be faster. Again, see section 7.7 for\r\nmore information.\r\n\r\n</ul>\r\n\r\n<p>Table 1 shows the default methods used for each power-of-two word size, their alignment parameters <em>s</em> and <em> t,</em> their\r\nmemory consumption and their rough performance. The performance tests are on an Intel Core i7-3770 running at\r\n3.40 GHz, and are included solely to give a flavor of performance on a standard microprocessor. Some processors\r\nwill be faster with some techniques and others will be slower, so we only put numbers in so that you can ballpark it.\r\nFor other values of <em>w</em> between 1 and 31, we use table lookup when w &#8804 8, discrete logarithms when w &#8804 16 and\r\n\"Bytwop\" for w &#8804 32. </p>\r\n<br><br>\r\n<center> With SSE \r\n<div id=\"data1\">\r\n<table cellpadding=\"6\" cellspacing=\"0\">\r\n<tr>\r\n<th>w </th><th class=\"double_border\" >Memory <br> Usage </br> </th><th>multiply() <br> Implementation</th><th>Performance <br>(Mega Ops / s) </th><th>multiply region() <br> Implementation </th>\r\n<th>s </th> <th>t </th> <th> Performance <br>(MB/s)</th>\r\n</tr>\r\n<tr>\r\n<td>4 </td><td class=\"double_border\"><1K </td><td>Table</td><td>501</td><td>Table</td>\r\n<td>16 </td><td>16 </td> <td>11,659</td> </tr>\r\n\r\n<tr>\r\n<td>8 </td><td class=\"double_border\">136K </td><td>Table</td><td>501</td><td>Split Table (8,4)</td>\r\n<td>16 </td><td>16 </td> <td>11,824</td> </tr>\r\n\r\n<tr>\r\n<td>16 </td><td class=\"double_border\">896K </td><td>Log</td><td>260</td><td>Split Table (16,4)</td>\r\n<td>32 </td><td>16 </td> <td>7,749</td> </tr>\r\n\r\n<tr>\r\n<td>32 </td><td class=\"double_border\"><1K </td><td>Carry-Free</td><td>48</td><td>Split Table (32,4)</td>\r\n<td>64 </td><td>16 </td> <td>5,011</td> </tr>\r\n\r\n<tr>\r\n<td>64 </td><td class=\"double_border\">2K </td><td>Carry-Free</td><td>84</td><td>Split Table (64,4)</td>\r\n<td>128 </td><td>16 </td> <td>2,402</td> </tr>\r\n\r\n<tr>\r\n<td>128 </td><td class=\"double_border\">64K </td><td>Carry-Free</td><td>48</td><td>Split Table (128,4)</td>\r\n<td>16 </td><td>16 </td> <td>833</td> </tr>\r\n</table></div>\r\n\r\n\r\n<div id=\"data1\">\r\n<center>Without SE </center>\r\n<table cellpadding=\"6\" cellspacing=\"0\">\r\n<tr>\r\n<th>w </th><th>Memory <br> Usage </br> </th><th>multiply() <br> Implementation</th><th>Performance <br>(Mega Ops / s) </th><th>multiply region() <br> Implementation </th>\r\n<th>s </th> <th>t </th> <th> Performance <br>(MB/s)</th>\r\n</tr>\r\n<tr>\r\n<td>4 </td><td>4K </td><td>Table</td><td>501</td><td>Double Table</td>\r\n<td>16 </td><td>16 </td> <td>11,659</td> </tr>\r\n\r\n<tr>\r\n<td>8 </td><td>128K </td><td>Table</td><td>501</td><td>Table</td>\r\n<td>1 </td><td>1 </td> <td>1,397</td> </tr>\r\n\r\n<tr>\r\n<td>16 </td><td>896K </td><td>Log</td><td>266</td><td>Split Table (16,8)</td>\r\n<td>32 </td><td>16 </td> <td>2,135</td> </tr>\r\n\r\n<tr>\r\n<td>32 </td><td>4K </td><td>Bytwop</td><td>19</td><td>Split Table (32,4)</td>\r\n<td>4 </td><td>4 </td> <td>1,149</td> </tr>\r\n\r\n<tr>\r\n<td>64 </td><td>16K </td><td>Bytwop</td><td>9</td><td>Split Table (64,4)</td>\r\n<td>8 </td><td>8 </td> <td>987</td> </tr>\r\n\r\n<tr>\r\n<td>128 </td><td>64K </td><td>Bytwop</td><td>1.4</td><td>Split Table (128,4)</td>\r\n<td>16 </td><td>8 </td> <td>833</td> </tr>\r\n</table>\r\n</div>\r\n</center>\r\n<br><br>\r\nTable 1: The default implementations, memory consumption and rough performance when w is a power of two. The\r\nvariables s and t are alignment variables described in Section 5.\r\n<p>\r\nA few comments on Table 1 are in order. First, with SSE, the performance of <b>multiply()</b> is faster when <em> w </em> = 64\r\nthan when<em> w </em> = 32. That is because the primitive polynomial for <em> w  </em>= 32, that has historically been used in Galois\r\nField implementations, is sub-ideal for using carry-free multiplication (PCLMUL). You can change this polynomial\r\n(see section 7.7) so that the performance matches <em>w </em> = 64. </p>\r\n<p>\r\nThe region operations for <em> w  </em>= 4 and <em>w </em>= 8 without SSE have been selected to have a low memory footprint. There\r\nare better options that consume more memory, or that only work on large memory regions (see section 6.1.5).\r\n</p>\r\n\r\nThere are times that you may want to stray from the defaults. For example:\r\n<ul>\r\n<li>\r\nYou may want better performance.\r\n</li>\r\n\r\n</ul>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n6 &nbsp &nbsp  <em>  THE DEFAULTS     </em>   <span id=\"index_number\">15  </span> <br><br><br>\r\n\r\n<ul>\r\n<li>You may want a lower memory footprint.</li>\r\n<li>You may want to use a different Galois Field or even a ring.</li>\r\n<li>You only care about multiplying a region by the value two.</li>\r\n\r\n</ul>\r\n\r\n\r\n<p>\r\nOur command line tools allow you to deviate from the defaults, and we have two C functions <b>-gf_init_hard()</b>\r\nand <b>create_gf_from_argv()</b> that can be called from application code to override the default methods. There are six\r\ncommand-line tools that can be used to explore the many techniques implemented in GF-Complete: </p>\r\n\r\n<ul><br>\r\n\r\n<li> <b>gf_methods</b> is a tool that enumerates most of the possible command-line arguments that can be sent to the other\r\ntools</li><br>\r\n<li> <b>gf_mult</b> and <b>gf_div</b> are explained above. You may change the multiplication and division technique in these\r\ntools if you desire</li><br>\r\n<li> <b>gf_unit</b> performs unit tests on a set of techniques to verify correctness</li><br>\r\n<li> <b> gf_time measures </b> the performance of a particular set of techniques</li><br>\r\n<li> <b>time_tool.sh </b> makes some quick calls to <b>gf_time</b> so that you may gauge rough performance.</li><br>\r\n<li> <b>gf_poly</b> tests the irreducibility of polynomials in a Galois Field</li><br>\r\n</ul>\r\n\r\n\r\n<p>To change the default behavior in application code, you need to call <b>gf_init_hard()</b> rather than <b>gf_init_easy().</b>\r\nAlternatively, you can use <b>create_g_from_argv(),</b> included from <b>gf_method.h,</b> which uses an <b>argv</b>-style array of\r\nstrings to specify the options that you want. The procedure in <b>gf_method.c</b> parses the array and makes the proper\r\n<b>gf_init_hard()</b> procedure call. This is the technique used to parse the command line in <b> gf_mult, gf_div, gf_unit </b><em>et al.</em> </p>\r\n\r\n\r\n<h2>6.1.1 Changing the Components of a Galois Field with create <b>gf_from_argv()</b> </h2>\r\nThere are five main components to every Galois Field instance:\r\n<ul>\r\n<li> <em>w </em> </li>\r\n<li> Multiplication technique </li>\r\n<li> Division technique  </li>\r\n<li> Region technique(s) </li>\r\n<li> Polynomial </li>\r\n</ul>\r\n\r\n<p>The procedures <b>gf_init_hard()</b> and <b> create_gf_from_argv()</b> allow you to specify these parameters when you create\r\nyour Galois Field instance. We focus first on <b>create_gf_from_argv(),</b> because that is how the tools allow you to specify\r\nthe components. The prototype of <b>create_gf_from_argv()</b> is as follows: </p><br>\r\n\r\n<div id=\"number_spacing\">\r\nint create_gf_from_argv(gf_t *gf, int w, int argc, char **argv, int starting);<br><br> </div>\r\n\r\nYou pass it a pointer to a gf_t, which it will initialize. You specify the word size with the parameter <em><b>w,</b></em> and then you\r\npass it an <b>argc/argv</b> pair as in any C or C++ program. You also specify a <b>starting</b> argument, which is where in <b>argv</b>\r\nthe specifications begin. If it successfully parses <b>argc</b> and <b>argv,</b> then it creates the <b>gf_t</b> using <b>gf_init_hard()</b> (described\r\nbelow in section 6.4). It returns one past the last index of <b>argv</b> that it considered when creating the <b>gf_t.</b> If it fails, then\r\nit returns zero, and the <b>gf_t</b> is unmodified.\r\n\r\n\r\n\r\n<p>For example, <b>gf_mult.c</b> calls create gf_from_argv() by simply passing <b>argc</b> and <b>argv</b> from its <b>main()</b> declaration,\r\nand setting starting to 4.</p>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n6 &nbsp &nbsp  <em>  THE DEFAULTS     </em>   <span id=\"index_number\">16  </span> <br><br><br>\r\n\r\n<p>\r\nTo choose defaults, <b>argv[starting]</b> should equal \"-\". Otherwise, you specify the component that you are changing\r\nwith \"-m\" for multiplication technique, \"-d\" for division technique, \"-r\" for region technique, and \"-p\" for the\r\npolynomial. You may change multiple components. You end your specification with a single dash. For example, the\r\nfollowing call multiplies 6 and 5 in <em>GF(2<sup>4</sup>)</em> with polynomial 0x19 using the \"SHIFT\" technique for multiplication\r\n(we'll explain these parameters later):\r\n</p><br><br>\r\n\r\n<div id=\"number_spacing\">\r\nUNIX> ./gf_mult 6 5 4 -p 0x19 -m SHIFT -<br>\r\n7 <br>\r\nUNIX> <br><br>\r\n</div>\r\n\r\n<p>If <b>create_gf_from_argv()</b> fails, then you can call the procedure <b>gf_error(),</b> which prints out the reason why <b>create_\r\ngf_from_argv()</b> failed. </p>\r\n\r\n\r\n<h2>6.1.2 Changing the Polynomial </h2>\r\n\r\nGalois Fields are typically implemented by representing numbers as polynomials with binary coefficients, and then\r\nusing the properties of polynomials to define addition and multiplication. You do not need to understand any of that to\r\nuse this library. However, if you want to learn more about polynomial representations and how they construct fields,\r\nplease refer to The Paper.\r\n\r\n<p>Multiplication is based on a special polynomial that we will refer to here as the \"defining polynomial.\" This\r\npolynomial has binary coefficients and is of degree <em> w.</em> You may change the polynomial with \"-p\" and then a number\r\nin hexadecimal (the leading \"0x\" is optional). It is assumed that the <em>w</em>-th bit of the polynomial is set - you may include\r\nit or omit it. For example, if you wish to set the polynomial for GF(2<sup>16</sup>) to x<sup>16</sup> + x<sup>5</sup> + x<sup>3</sup> + x<sup>2</sup> + 1, rather than its\r\ndefault of x<sup>16</sup> + x<sup>12</sup> + x<sup>3</sup> + x + 1, you may say \"-p 0x1002d,\" \"-p 1002d,\" \"-p 0x2d\" or \"-p 2d.\"\r\nWe discuss changing the polynomial for three reasons in other sections: </p>\r\n<ul>\r\n<li>Leveraging carry-free multiplication (section 7.7). </li>\r\n<li>Defining composite fields (section 7.6). </li>\r\n<li>Implementing rings (section 7.8.1). </li>\r\n\r\n</ul>\r\n\r\n<p>\r\nSome words about nomenclature with respect to the polynomial. A Galois Field requires the polynomial to be\r\n<em>irreducible </em>.. That means that it cannot be factored. For example, when the coefficients are binary, the polynomial x<sup>5</sup>+\r\nx<sup>4</sup>+x+1 may be factored as (x<sup>4</sup>+1)(x+1). Therefore it is not irreducible and cannot be used to define a Galois Field.\r\nIt may, however, be used to define a ring. Please see section 7.8.1 for a discussion of ring support in GF-Complete. </p>\r\n<p>\r\nThere is a subset of irreducible polynomials called primitive. These have an important property that one may enumerate\r\nall of the elements of the field by raising 2 to successive posers. All of the default polynomials in GF-Complete \r\nare primitive. However, so long as a polynomial is irreducible, it defines a Galois Field. Please see section 7.7 for a\r\nfurther discussion of the polynomial. </p>\r\n\r\n<p>\r\nOne thing that we want to stress here is that changing the polynomial changes the field, so fields with different\r\npolynomialsmay not be used interchangeably. So long as the polynomial is irreducible, it generates a Galois Field that\r\nis isomorphic to all other Galois Fields; however the multiplication and division of elements will differ. For example,\r\nthe polynomials 0x13 (the default) and 0x19 in <em>GF(2<sup>4</sup>) </em> are both irreducible, so both generate valid Galois Fields.\r\nHowever, their multiplication differs: </p><br>\r\n\r\n<div id=\"number_spacing\">\r\nUNIX> gf_mult 8 2 4 -p 0x13 - <br>\r\n3 <br>\r\nUNIX> gf_mult 8 2 4 -p 0x19 - <br>\r\n9 <br>\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n6 &nbsp &nbsp  <em>  THE DEFAULTS     </em>   <span id=\"index_number\">17  </span> <br><br><br>\r\n\r\n<div id=\"number_spacing\">\r\nUNIX> gf_div 3 8 4 -p 0x13 -<br>\r\n2 <br>\r\nUNIX> gf_div 9 8 4 -p 0x19 - <br>\r\n2 <br>\r\nUNIX> <br>\r\n\r\n</div>\r\n\r\n\r\n<h3>6.1.3 &nbsp &nbsp Changing the Multiplication Technique </h3>\r\nThe following list describes the multiplication techinques that may be changed with \"-m\". We keep the description\r\nhere brief. Please refer to The Paper for detailed descriptions of these techniques.<br><br>\r\n\r\n\r\n<li><b> \"TABLE:\" </b> Multiplication and division are implemented with tables. The tables consume quite a bit of memory\r\n(2<sup>w</sup> &#215 2 <sup>w</sup> &#215  <sup>w</sup>/\r\n8  bytes), so they are most useful when <em>w</em> is small. Please see <b>\"SSE,\" \"LAZY,\" \"DOUBLE\"</b> and\r\n\r\n<b>\"QUAD\"</b> under region techniques below for further modifications to <b>\"TABLE\"</b> to perform <b>multiply_region()</b></li><br>\r\n\r\n\r\n<li> <b>\"LOG:\"</b> This employs discrete (or \"Zeph\") logarithm <b>tables</b> to implement multiplication and division. The\r\nmemory usage is roughly (3 &#215 2<sup>w</sup> &#215 w /\r\n8  bytes), so they are most useful when w is small, but they tolerate\r\nlarger <em>w</em> than <b>\"TABLE.\"</b> If the polynomial is not primitive (see section 6.1.2), then you cannot use <b>\"LOG\"</b> as\r\nan implementation. In that case,<b> gf_init_hard()</b> or <b>create_gf_from_argv()</b> will fail</li><br>\r\n\r\n\r\n<li><b> \"LOG ZERO:\"</b> Discrete logarithm tables which include extra room for zero entries. This more than doubles\r\nthe memory consumption to remove an <b>if</b> statement (please see [GMS08] or The Paper for more description). It\r\ndoesn’t really make a huge deal of difference in performance</li><br>\r\n\r\n<li> <b>\"LOG ZERO EXT:\"</b> This expends even more memory to remove another <b>if</b> statement. Again, please see The\r\nPaper for an explanation. As with <b>\"LOG ZERO,\"</b> the performance difference is negligible</li><br>\r\n\r\n<li> <b>\"SHIFT:\"</b> Implementation straight from the definition of Galois Field multiplication, by shifting and XOR-ing,\r\nthen reducing the product using the polynomial. This is <em>slooooooooow,</em> so we don’t recommend you use it</li><br>\r\n\r\n\r\n<li> <b>\"CARRY FREE:\"</b> This is identical to <b>\"SHIFT,\"</b> however it leverages the SSE instruction PCLMUL to perform\r\ncarry-freemultiplications in single instructions. As such, it is the fastest way to perform multiplication for large\r\nvalues of <em>w</em> when that instruction is available. Its performance depends on the polynomial used. See The Paper\r\nfor details, and see section 7.7 below for the speedups available when <em>w </em>= 16 and <em>w</em> = 32 if you use a different\r\npolynomial than the default one</li><br>\r\n\r\n\r\n<li> <b>\"BYTWO p:\"</b> This implements multiplication by successively multiplying the product by two and selectively\r\nXOR-ing the multiplicand. See The Paper for more detail. It can leverage Anvin’s optimization that multiplies\r\n64 and 128 bits of numbers in <em>GF(2<sup>w</sup>) </em> by two with just a few instructions. The SSE version requires SSE2</li><br>\r\n\r\n\r\n<li> <b>\"BYTWO b:\"</b> This implements multiplication by successively multiplying the multiplicand by two and selectively\r\nXOR-ing it into the product. It can also leverage Anvin's optimization, and it has the feature that when\r\nyou're multiplying a region by a very small constant (like 2), it can terminate the multiplication early. As such,\r\nif you are multiplying regions of bytes by two (as in the Linux RAID-6 Reed-Solomon code [Anv09]), this is\r\nthe fastest of the techniques, regardless of the value of <em>w.</em> The SSE version requires SSE2</li><br>\r\n\r\n\r\n<li> <b>\"SPLIT:\"</b> Split multiplication tables (like the LR tables in [GMS08], or the SIMD tables for w &#8804 8 in [LHy08,\r\nAnv09, PGM13b]). This argument must be followed by two more arguments, w<sub>a</sub> and w<sub>b</sub>, which are the index\r\nsizes of the sub-tables. This implementation reduces the size of the table from <b>\"TABLE,\"</b> but requires multiple\r\n</li><br>\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n6 &nbsp &nbsp  <em>  THE DEFAULTS     </em>   <span id=\"index_number\">18 </span> <br><br><br>\r\n<ul>\r\ntable lookups. For example, the following multiplies 100 and 200 in <em>GF(2<sup>8</sup>) </em> using two 4K tables, as opposed \r\nto one 64K table when you use <b>\"TABLE:\"</b><br><br>\r\n<div id=\"number_spacing\">\r\nUNIX> ./gf_mult 100 200 8 -m SPLIT 8 4 - <br>\r\n79<br>\r\nUNIX><br><br>\r\n</div>\r\n\r\nSee section 7.4 for additional information on the arguments to <b>\"SPLIT.\"</b> The SSE version typically requires\r\nSSSE3.<br><br>\r\n\r\n\r\n<li> <b>\"GROUP:\"</b> This implements the \"left-to-right comb\" technique [LBOX12]. I'm afraid we don't like that name,\r\nso we call it <b>\"GROUP,\"</b> because it performs table lookup on groups of bits for shifting (left) and reducing (right).\r\nIt takes two additional arguments - g<sub>s,</sub> which is the number of bits you use while shifting (left) and g<sub>r</sub>, which\r\nis the number of bits you use while reducing (right). Increasing these arguments can you higher computational\r\nspeed, but requires more memory. SSE version exists only for <em> w </em> = 128 and it requires SSE4. For more\r\ndescription on the arguments g<sub>s</sub> and g<sub>r</sub>, see section 7.5. For a full description of <b>\"GROUP\"</b> algorithm, please\r\nsee The Paper.\r\n</li><br>\r\n\r\n<li> <b>\"COMPOSITE:\"</b> This allows you to perform operations on a composite Galois Field, <em> GF((2<sup>l</sup>)<sup>k</sup>)</em> as described\r\nin [GMS08], [LBOX12] and The Paper. The field size <em>w </em> is equal to <em>lk.</em> It takes one argument, which is <em>k,</em> and\r\nthen a specification of the base field. Currently, the only value of <em>k</em> that is supported is two. However, that may\r\nchange in a future revision of the library. </li><br>\r\n\r\n\r\nIn order to specify the base field, put appropriate flags after specifying <em>k.</em> The single dash ends the base field,\r\nand after that, you may continue making specifications for the composite field. This process can be continued\r\nfor multiple layers of <b>\"COMPOSITE.\"</b> As an example, the following multiplies 1000000 and 2000000\r\nin <em>GF((2<sup>16</sup>)<sup>2</sup>),</em> where the base field uses <b>BYTWO_p</b> for multiplication: <br><br>\r\n<center>./gf mult 1000000 2000000 32 -m COMPOSITE 2 <span style=\"color:red\">-m BYTWO p - -</span> </center><br>\r\n\r\nIn the above example, the red text applies to the base field, and the black text applies to the composite field.\r\nComposite fields have two defining polynomials - one for the composite field, and one for the base field. Thus, if\r\nyou want to change polynomials, you should change both. The polynomial for the composite field must be of the\r\nform x<sup>2</sup>+sx+1, where s is an element of <em>GF(2<sup>k</sup>).</em> To change it, you specify s (in hexadecimal)with \"-p.\" In the\r\nexample below, we multiply 20000 and 30000 in <em>GF((2<sup>8</sup>)<sup>2</sup>) </em>, setting s to three, and using x<sup>8</sup>+x<sup>4</sup>+x<sup>3</sup>+x<sup>2</sup>+1\r\nas the polynomial for the base field: <br><br>\r\n\r\n<center>./gf mult 20000 30000 16 -m COMPOSITE 2 <span style=\"color:red\">-p 0x11d </span> - -p 0x3 - </center> <br><br>\r\n\r\nIf you use composite fields, you should consider using <b>\"ALTMAP\"</b> as well. The reason is that the region\r\noperations will go much faster. Please see section 7.6.<br><br>\r\nAs with changing the polynomial, when you use a composite field, <em> GF((2<sup>l</sup>)<sup>k</sup>)</em>, you are using a different field\r\nthan the \"standard\" field for <em> GF((2<sup>l</sup>)<sup>k</sup>)</em>. All Galois Fields are isomorphic to each other, so they all have the\r\ndesired properties; however, the fields themselves change when you use composite fields.<br><br>\r\n</ul>\r\n<p>\r\nWith the exception of <b>\"COMPOSITE\"</b>, only one multiplication technique can be provided for a given Galois\r\nField instance. Composite fields may use composite fields as their base fields, in which case the specification will be\r\nrecursive. </p>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n6 &nbsp &nbsp  <em>  THE DEFAULTS     </em>   <span id=\"index_number\">19 </span> <br><br><br>\r\n\r\n<h3>6.1.4 &nbsp &nbsp &nbsp Changing the Division Technique </h3>\r\n\r\nThere are two techniques for division that may be set with \"-d\". If \"-d\" is not specified, then appropriate defaults\r\nare employed. For example, when the multiplication technique is <b>\"TABLE,\"</b> a table is created for division as well as\r\nmultiplication. When <b>\"LOG\"</b> is specified, the logarithm tables are used for division. With <b>\"COMPOSITE,\"</b> a special\r\nvariant of Euclid's algorithm is employed that performs division using multiplication and division in the base field.\r\nOtherwise, Euclid's algorithm is used. Please see The Paper for a description of Euclid's algorithm applied to Galois\r\nFields.\r\n\r\n<p>If you use \"-d\", you must also specify the multiplication technique with \"-m.\" </p>\r\n<p>To force Euclid's algorithm instead of the defaults, you may specify it with \"-d EUCLID.\" If instead, you would\r\nrather convert elements of a Galois Field to a binary matrix and find an element's inverse by inverting the matrix,\r\nthen specify \"-d MATRIX.\" In all of our tests, <b>\"MATRIX\"</b> is slower than <b>\"EUCLID.\" \"MATRIX\" </b> is also not defined\r\nfor <em>w </em> > 32.\r\n</p>\r\n\r\n\r\n<h3>6.1.5  &nbsp&nbsp&nbsp Changing the Region Technique </h3>\r\nThe following are the region multiplication options (\"-r\"):\r\n<ul>\r\n<li>\r\n<b>\"SSE:\"</b> Use SSE instructions. Initialization will fail if the instructions aren't supported. Table 2 details the\r\nmultiplication techniques which can leverage SSE instructions and which versions of SSE are required. </li><br>\r\n\r\n<center>\r\n<div id=\"data1\">\r\n<table cellpadding=\"6\" cellspacing=\"0\" style=\"text-align:center;font-size:19px\">\r\n<tr>\r\n<th>Multiplication <br> Technique</th><th>multiply() </th><th>multiply_region() </th><th>SSE Version </th><th>Comments</th>\r\n\r\n</tr>\r\n<tr>\r\n<td><b>\"TABLE\"</b></td><td >- </td><td>Yes</td><td>SSSE3</td><td>Only for <em>GF(2<sup>4</sup>). </em></td>\r\n\r\n<tr>\r\n<td><b>\"SPLIT\"</b></td><td>-</td><td>Yes</td><td>SSSE3</td><td>Only when the second argument equals 4.</td>\r\n\r\n<tr>\r\n<td><b>\"SPLIt\"</b></td><td>- </td><td>Yes</td><td>SSE4</td><td>When <em>w </em> = 64 and not using <b>\"ALTMAP\".</b></td>\r\n\r\n<tr>\r\n<td><b>\"BYTWO p\"</b></td><td>- </td><td>Yes</td><td>SSE2</td><td></td>\r\n\r\n<tr>\r\n<td><b>\"BYTWO p\"</b></td><td>- </td><td>Yes</td><td>SSE2</td><td></td>\r\n\r\n</table></div> <br><br>\r\nTable 2: Multiplication techniques which can leverage SSE instructions when they are available.\r\n</center> <br><br>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<li> <b>\"NOSSE:\"</b> Force non-SSE version </li><br>\r\n\r\n<li> <b> \"DOUBLE:\"</b> Use a table that is indexed on two words rather than one. This applies only to <em>w  </em> = 4, where\r\nthe table is indexed on bytes rather than 4-bit quantities, and to <em>w </em> = 8, where the table is indexed on shorts\r\nrather than bytes. In each case, the table lookup performs two multiplications at a time, which makes region\r\nmultiplication faster. It doubles the size of the lookup table. </li><br>\r\n\r\n<li> <b>\"QUAD:\"</b> Use a table that is indexed on four words rather than two or one. This only applies to <em>w </em> = 4, where\r\nthe table is indexed on shorts. The \"Quad\" table may be lazily created or created ahead of time (the default). If\r\nthe latter, then it consumes 2<sup>4</sup> &#215 2<sup>16</sup> &#215 2 = 2 MB of memory. </li><br>\r\n\r\n<li> <b> \"LAZY:\"</b> Typically it's clear whether tables are constructed upon initialization or lazily when a region operation\r\nis performed. There are two times where it is ambiguous: <b>\"QUAD\"</b> when <em>w </em> = 4 and <b>\"DOUBLE\"</b> when <em>w </em> = 8.\r\nIf you don't specify anything, these tables are created upon initialization, consuming a lot of memory. If you\r\nspecify <b>\"LAZY,\"</b> then the necessary row of the table is created lazily when you call <b>\"multiply_region().</b>\r\n</li>\r\n\r\n</ul>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n6 &nbsp &nbsp  <em>  THE DEFAULTS     </em>   <span id=\"index_number\">20 </span> <br><br><br>\r\n<ul>\r\n\r\n<li> <b>\"ALTMAP:\"</b> Use an alternate mapping, where words are split across different subregions of memory. There\r\nare two places where this matters. The first is when implementing \"<b>SPLIT</b> <em>w </em> 4\" using SSE when <em>w </em> > 8. In\r\nthese cases, each byte of the word is stored in a different 128-bit vector, which allows the implementation to\r\nbetter leverage 16-byte table lookups. See section 7.4 for examples, and The Paper or [PGM13b] for detailed\r\nexplanations.<br><br> </li>\r\n\r\nThe second place where it matters is when using <b>\"COMPOSITE.\"</b> In this case, it is advantageous to split each\r\nmemory region into two chunks, and to store half of each word in a different chunk. This allows us to call\r\n<b>region_multiply() </b> recursively on the base field, which is <em>much </em> faster than the alternative. See Section 7.6 for\r\nexamples, and The Paper for an explanation.<br><br>\r\n\r\nIt is important to note that with <b>\"ALTMAP,\"</b> the words are not \"converted\" from a standard mapping to an\r\nalternate mapping and back again. They are assumed to always be in the alternate mapping. This typically\r\ndoesn't matter, so long as you always use the same <b>\"ALTMAP\"</b> calls. Please see section 7.9 for further details\r\non <b>\"ALTMAP,\"</b> especially with respect to alignment.<br><br>\r\n\r\n<li> <b>\"CAUCHY:\"</b> Break memory into <em>w </em> subregions and perform only XOR's as in Cauchy Reed-Solomon coding\r\n[BKK<sup>+</sup>95] (also described in The Paper). This works for <em>any</em> value of <em>w </em> &#8804 32, even those that are not\r\npowers of two. If SSE2 is available, then XOR's work 128 bits at a time. For <b>\"CAUCHY\"</b> to work correctly,\r\n<em>size</em> must be a multiple of <em>w </em>.</li> </ul>\r\n\r\n\r\n\r\n<p>It is possible to combine region multiplication options. This is fully supported as long as <b>gf_methods</b> has the combination\r\nlisted. If multiple region options are required, they should be specified independently (as flags for <b>gf_init_hard()</b>\r\nand independent options for command-line tools and <b>create_gf_from_argv()).</b> </p>\r\n\r\n\r\n<h3>6.2  &nbsp&nbsp&nbspDetermining Supported Techniques with gf methods </h3>\r\n\r\n\r\nThe program <b>gf_methods</b> prints a list of supported methods on standard output. It is called as follows:<br><br>\r\n<div id=\"number_spacing\">\r\n<center>./gf methods <em>w </em> -BADC -LUMDRB <br><br> </center> </div>\r\n\r\nThe first argument is <em>w </em>, which may be any legal value of <em>w </em>. The second argument has the following flags: <br><br>\r\n<ul>\r\n\r\n<li> <b>\"B:\"</b> This only prints out \"basic\" methods that are useful for the given value of <em>w </em>. It omits <b>\"SHIFT\"</b> and other\r\nmethods that are never really going to be useful.</li><br>\r\n\r\n<li> <b> \"A:\"</b> In constrast, this specifies to print \"all\" methods. </li><br>\r\n\r\n<li> <b>\"D:\"</b> This includes the <b>\"EUCLID\"</b> and <b>\"MATRIX\"</b> methods for division. By default, they are not included. </li><br>\r\n\r\n<li> <b>\"C:\"</b> This includes the <b>\"CAUCHY\"</b> methods for region multiplication. By default, it is not included.</li> <br>\r\n</ul>\r\n<p>\r\nYou may specify multiple of these as the second argument. If you include both <b>\"B\"</b> and <b>\"A,\"</b> then it uses the last\r\none specified. </p>\r\n<p>\r\nThe last argument determines the output format of <b>gf_methods.</b> If it is <b>\"L,\"</b> then it simply lists methods. If it\r\nis <b>\"U,\"</b> then the output contains <b>gf_unit</b> commands for each of the methods. For the others, the output contains\r\n<b>gf_time_tool.sh</b> commands for <b>M </b>ultiplication,<b>D</b>ivision,<b>R</b>egion multiplications with multiple buffer sizes, and the\r\n<b>B</b>est region multiplication. </p>\r\n<p>\r\n<b>gf_methods</b> enumerates combinations of flags, and calls <b>create_gf_from_argv()</b> to see if the combinations are\r\nsupported. Although it enumerates a large number of combinations, it doesn't enumerate all possible parameters for\r\n<b>\"SPLIT,\" \"GROUP\"</b> or <b>\"COMPOSITE.\"</b> </p>\r\n\r\n<p>Some examples of calling <b>gf_methods</b> are shown below in section 6.3.2. </p>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n6 &nbsp &nbsp  <em>  THE DEFAULTS     </em>   <span id=\"index_number\">21 </span> <br><br><br>\r\n\r\n\r\n<h3>6.3 Testing with <b>gf_unit </b>, <b>gf_time </b>, and time_tool.sh </h3>\r\n\r\n\r\n\r\n<b>gf_unit </b> and <b>gf_time </b> may be used to verify that a combination of arguments works correctly and efficiently on your\r\nplatform. If you plan to stray from the defaults, it is probably best to run both tools to ensure there are no issues with\r\nyour environment. <b>gf_unit </b> will run a set of unit tests based on the arguments provided to the tool, and <b>gf_time </b> will\r\ntime Galois Field methods based on the provided arguments.<br>\r\nThe usage of gf_ unit is:<br><br>\r\n<div id=\"number_spacing\">\r\n<b>gf_unit </b> w tests seed method<br><br> </div>\r\nThe usage of gf_ time is:<br><br>\r\n<div id=\"number_spacing\">\r\n<b>gf_time </b> w tests seed buffer-size iterations method<br><br>\r\n</div>\r\n\r\n\r\n\r\nThe seed is an integer- negative one uses the current time. The tests are specified by a listing of characters. The\r\nfollowing tests are supported (All are supported by <b>gf_time.</b> Only ', 'S' and 'R' are supported by <b>gf_unit</b>):<br><br>\r\n\r\n<ul>\r\n<li> <b>'M':</b> Single multiplications</li><br>\r\n<li> <b> 'D':</b> Single divisions</li><br>\r\n<li> <b> 'I':</b> Single inverses</li><br>\r\n<li> <b>'G': </b> Region multiplication of a buffer by a random constant</li><br>\r\n<li> <b>'0': </b> Region multiplication of a buffer by zero (does nothing and<b>bzero()</b>)</li><br>\r\n<li> <b>'1': </b> Region multiplication of a buffer by one (does <b>memcpy()</b> and <b>XOR</b>)</li><br>\r\n<li> <b>'2': </b> Region multiplication of a buffer by two – sometimes this is faster than general multiplication</li><br>\r\n<li> <b>'S':</b> All three single tests</li><br>\r\n<li> <b>'R':</b> All four region tests</li><br>\r\n<li> <b>'A':</b> All seven tests</li><br>\r\n</ul>\r\n\r\n\r\n\r\n\r\n\r\n<p>Here are some examples of calling <b>gf_unit</b> and <b>gf_time</b> to verify that <b>\"-m SPLIT 32 4 -r ALTMAP -\"</b> works\r\nin <em>GF(2<sup>32</sup>),</em> and to get a feel for its performance. First, we go to the test directory and call <b>gf_unit:</b> </p><br><br>\r\n\r\n\r\n<div id=\"number_spacing\">\r\nUNIX> cd test <br>\r\nUNIX> ./gf_unit 32 A -1 -m SPLIT 32 4 -r ALTMAP - <br>\r\nArgs: 32 A -1 -m SPLIT 32 4 -r ALTMAP - / size (bytes): 684 <br>\r\nUNIX> <br><br>\r\n</div>\r\n\r\n<b>gf_unit</b> reports on the arguments and how may bytes the <b>gf_t</b> consumes. If it discovers any problems or inconsistencies\r\nwith multiplication, division or region multiplication, it will report them. Here, there are no problems.\r\nNext, we move to the <b>tools</b> directory and run performance tests on a 10K buffer, with 10,000 iterations of each test:<br><br>\r\n\r\n\r\nUNIX> cd ../tools <br>\r\nUNIX> ./gf_time 32 A -1 10240 10000 -m SPLIT 32 4 -r ALTMAP -<br>\r\nSeed: 1388435794 <br>\r\n<div id=\"number_spacing\">\r\n<table cellpadding=\"0\" cellspacing=\"25\" style=\"font-size:19px,font-family: 'Roboto Condensed', sans-serif;\r\n\">\r\n\r\n<tr>\r\n\r\n<td>Multiply:</td> <td>4.090548 s</td> <td> Mops: </td> <td> 24.414 </td> <td>5.968 Mega-ops/s </td> </tr>\r\n<tr><td>Divide:</td> <td> 37.794962 s </td> <td>Mops: </td> <td> 24.414 </td> <td>0.646 Mega-ops/s </td> </tr>\r\n<tr><td>Inverse:</td> <td> 33.709875 s </td> <td> Mops: </td> <td> 24.414 </td> <td> 0.724 Mega-ops/s </td> </tr>\r\n<tr><td>Region-Random: XOR: 0 </td> <td> 0.035210 s </td> <td> MB:</td> <td> 97.656 </td> <td> 2773.527 MB/s </td></tr>\r\n<tr><td>Region-Random: XOR: 1 </td> <td> 0.036081 s</td> <td> MB:</td> <td> 97.656 </td> <td>2706.578 MB/s </td></tr>\r\n<tr><td>Region-By-Zero:XOR: 0 </td> <td> 0.003199 s </tD> <td> MB: </td> <td>97.656 </td> <td> 30523.884 MB/s </td> </tr>\r\n<tr><td>Region-By-Zero: XOR: 1 </td> <td> 0.000626 s  </td> <td>MB: </td> <td> 97.656 </td> <td> 156038.095 MB/s </td></tr>\r\n\r\n</table>\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n6 &nbsp &nbsp  <em>  THE DEFAULTS     </em>   <span id=\"index_number\">22 </span> <br><br><br>\r\n\r\n<div id=\"number_spacing\">\r\n<table cellpadding=\"0\" cellspacing=\"10\" style=\"font-family: 'Roboto Condensed', sans-serif;\r\n\">\r\n\r\n<tr>\r\n<td>Region-By-One: XOR: 0</td> <td> 0.003810 s</td> <td> MB:</td> <td> 97.656 </td> <td> 25628.832 MB/s </td>\r\n<tr><td>Region-By-One: XOR: 1 </td> <td> 0.008363 s </td> <td> MB:</td> <td> 97.656 </tD> <td>11677.500 MB/s </td></tr>\r\n\r\n<tr><td>Region-By-Two: XOR: 0 </td> <td>0.032942 s  </td> <td>MB: </td> <td> 97.656 </td> <td> 2964.486 MB/s </td> </tr>\r\n<tr><td>Region-By-Two: XOR: 1 </td> <td> 0.033488 s </td> <td> MB: </td> <td> 97.656 </td> <td> 2916.153 MB/s </td> </tr> </table>\r\n</div>\r\nUNIX><br><br>\r\n\r\n<p>The first column of output displays the name of the test performed. Region tests will test with and without the XOR\r\nflag being set (see Section 4.3 for an example). The second column displays the total time the test took to complete\r\nmeasured in seconds (s). The third column displays the size of the test measured in millions of operations (Mops) for\r\nsingle tests and in Megabytes (MB) for the region tests. The final column displays the speed of the tests calculated\r\nfrom the second and third columns, and is where you should look to get an idea of a method's performance.</p>\r\n<p>\r\nIf the output of <b>gf_unit</b> and <b>gf_time</b> are to your satisfaction, you can incorporate the method into application code\r\nusing create <b>gf_from_argv()</b> or <b>gf_init hard().</b></p>\r\n<p>\r\nThe performance of \"Region-By-Zero\" and \"Region-By-One\" will not change from test to test, as all methods make\r\nthe same calls for these. \"Region-By-Zero\" with \"XOR: 1\" does nothing except set up the tests. Therefore, you may\r\nuse it as a control.</p>\r\n\r\n<h3>6.3.1 &nbsp &nbsp &nbsp time tool.sh </h3> \r\n\r\nFinally, the shell script <b>time_tool.sh</b> makes a bunch of calls to <b>gf_time</b> to give a rough estimate of performance. It is\r\ncalled as follows:<br><br>\r\nusage sh time_tool.sh M|D|R|B w method<br><br>\r\n\r\n\r\n<p>The values for the first argument are <b>MDRB,</b> for <b>M</b>ultiplication, <b>D</b>ivision,<b>R</b>egion multiplications with multiple\r\nbuffer sizes, and the <b>B</b>est region multiplication. For the example above, let's call <b>time_tool.sh</b> to get a rough idea of\r\nperformance: </p><br><br>\r\n\r\n<div id=\"number_spacing\">\r\nUNIX> sh time_tool.sh M 32 -m SPLIT 32 4 -r ALTMAP - <br>\r\nM speed (MB/s): 6.03 W-Method: 32 -m SPLIT 32 4 -r ALTMAP - <br>\r\nUNIX> sh time_tool.sh D 32 -m SPLIT 32 4 -r ALTMAP - <br>\r\nD speed (MB/s): 0.65 W-Method: 32 -m SPLIT 32 4 -r ALTMAP - <br>\r\nUNIX> sh time_tool.sh R 32 -m SPLIT 32 4 -r ALTMAP - <br>\r\n\r\n<table cellpadding=\"0\" cellspacing=\"10\" style=\"font-family: 'Roboto Condensed', sans-serif;\r\n\">\r\n\r\n<tr>\r\n<td>Region Buffer-Size:</td> <td> 16K (MB/s):</td> <td>3082.91</td><td> W-Method: 32 </td> <td>-m SPLIT 32 4 </td> <td>-r ALTMAP -</td> </tr>\r\n<tr><td>Region Buffer-Size:</td> <td>32K (MB/s): </td> <td>3529.07 </td><td> W-Method: 32 </td> <td>-m SPLIT 32 4 </td> <td>-r ALTMAP -</td> </tr>\r\n<tr><td>Region Buffer-Size:</td> <td>64K (MB/s): </td> <td> 3749.94</td><td> W-Method: 32 </td> <td>-m SPLIT 32 4 </td> <td>-r ALTMAP -</td> </tr>\r\n<tr><td>Region Buffer-Size:</td> <td>128K (MB/s):</td> <td>3861.27 </td> <td>W-Method: 32 </td> <td>-m SPLIT 32 4 </td> <td>-r ALTMAP -</td> </tr>\r\n<tr><td>Region Buffer-Size:</td> <td>512K (MB/s):</td> <td>3820.82 </td><td> W-Method: 32 </td> <td>-m SPLIT 32 4 </td> <td>-r ALTMAP -</td> </tr>\r\n<tr><td>Region Buffer-Size:</td> <td>1M (MB/s):</td> <td>3737.41 </td><td> W-Method: 32 </td> <td>-m SPLIT 32 4 </td> <td>-r ALTMAP -</td>  </tr>\r\n<tr><td>Region Buffer-Size:</td> <td>2M (MB/s):</td> <td>3002.90 </td><td> W-Method: 32 </td> <td>-m SPLIT 32 4 </td> <td>-r ALTMAP -</td> </tr>\r\n<tr><td>Region Buffer-Size:</td> <td>4M (MB/s): </td><td>2760.77</td><td> W-Method: 32 </td> <td>-m SPLIT 32 4 </td> <td>-r ALTMAP -</td> </tr>\r\n<tr><td>Region Best (MB/s):</td><td> 3861.27</td><td> W-Method: 32 </td> <td>-m SPLIT 32 4 </td> <td>-r ALTMAP -</td> </tr>\r\n</table>\r\n\r\nUNIX> sh time_tool.sh B 32 -m SPLIT 32 4 -r ALTMAP - <br>\r\nRegion Best (MB/s): 3929.09  W-Method: 32  -m SPLIT 32 4 -r ALTMAP -</br>\r\nUNIX><br><br>\r\n</div>\r\n<p>\r\nWe say that <b>time_tool.sh </b>is \"rough\" because it tries to limit each test to 5 ms or less. Thus, the time granularity\r\nis fine, which means that the numbers may not be as precise as they could be were the time granularity to be course.\r\nWhen in doubt, you should make your own calls to <b>gf_time</b> with a lot of iterations, so that startup costs and roundoff\r\nerror may be minimized. </p>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n6 &nbsp &nbsp  <em>  THE DEFAULTS     </em>   <span id=\"index_number\">23 </span> <br><br><br>\r\n\r\n<h3>6.3.2 &nbsp &nbsp &nbsp An example of gf methods and time tool.sh </h3><br><br>\r\nLet's give an example of how some of these components fit together. Suppose we want to explore the basic techniques\r\nin <em>GF(2<sup>32</sup>).</em> First, let's take a look at what <b>gf_methods</b> suggests as \"basic\" methods: <br><br>\r\n<div id=\"number_spacing\">\r\nUNIX> gf_methods 32 -B -L <br>\r\nw=32: - <br>\r\nw=32: -m GROUP 4 8 - <br>\r\nw=32: -m SPLIT 32 4 - <br>\r\nw=32: -m SPLIT 32 4 -r ALTMAP - <br>\r\nw=32: -m SPLIT 32 8 - <br>\r\nw=32: -m SPLIT 8 8 - <br>\r\nw=32: -m COMPOSITE 2 - - <br>\r\nw=32: -m COMPOSITE 2 - -r ALTMAP - <br>\r\nUNIX> <br><br>\r\n</div>\r\n\r\n\r\n<p>\r\n\r\nYou'll note, this is on my old Macbook Pro, which doesn't support (PCLMUL), so <b>\"CARRY FREE\"</b> is not included\r\nas an option. Now, let's run the unit tester on these to make sure they work, and to see their memory consumption: </p><br><br>\r\n\r\n<div id=\"number_spacing\">\r\nUNIX> gf_methods 32 -B -U <br>\r\n../test/gf_unit 32 A -1 - <br>\r\n../test/gf_unit 32 A -1 -m GROUP 4 8 - <br>\r\n../test/gf_unit 32 A -1 -m SPLIT 32 4 - <br>\r\n../test/gf_unit 32 A -1 -m SPLIT 32 4 -r ALTMAP - <br>\r\n../test/gf_unit 32 A -1 -m SPLIT 32 8 - <br>\r\n../test/gf_unit 32 A -1 -m SPLIT 8 8 - <br>\r\n../test/gf_unit 32 A -1 -m COMPOSITE 2 - - <br>\r\n../test/gf_unit 32 A -1 -m COMPOSITE 2 - -r ALTMAP - <br>\r\nUNIX> gf_methods 32 -B -U | sh <br>\r\nArgs: 32 A -1 - / size (bytes): 684 <br>\r\nArgs: 32 A -1 -m GROUP 4 8 - / size (bytes): 1296 <br>\r\nArgs: 32 A -1 -m SPLIT 32 4 - / size (bytes): 684 <br>\r\nArgs: 32 A -1 -m SPLIT 32 4 -r ALTMAP - / size (bytes): 684 <br>\r\nArgs: 32 A -1 -m SPLIT 32 8 - / size (bytes): 4268 <br>\r\nArgs: 32 A -1 -m SPLIT 8 8 - / size (bytes): 1839276 <br>\r\nArgs: 32 A -1 -m COMPOSITE 2 - - / size (bytes): 524648 <br>\r\nArgs: 32 A -1 -m COMPOSITE 2 - -r ALTMAP - / size (bytes): 524648 <br>\r\nUNIX> <br> <br>\r\n</div>\r\n<p>\r\nAs anticipated, <b>\"SPLIT 8 8\"</b> consumes quite a bit of memory! Now, let's see how well they perform with both\r\nsingle multiplications and region multiplications: </p> <br><br>\r\n<div id=\"number_spacing\">\r\nUNIX> gf_methods 32 -B -M <br>\r\nsh time_tool.sh M 32 - <br>\r\nsh time_tool.sh M 32 -m GROUP 4 8  - <br>\r\nsh time_tool.sh M 32 -m SPLIT 32 4 - <br>\r\nsh time_tool.sh M 32 -m SPLIT 32 4 -r ALTMAP -<br>\r\nsh time_tool.sh M 32 -m SPLIT 32 8 - <br>\r\nsh time_tool.sh M 32 -m SPLIT 8 8 - <br>\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n6 &nbsp &nbsp  <em>  THE DEFAULTS     </em>   <span id=\"index_number\">24 </span> <br><br><br>\r\n\r\n<div id=\"number_spacing\">\r\nsh time_tool.sh M 32 -m COMPOSITE 2 - <br>\r\nsh time_tool.sh M 32 -m COMPOSITE 2 - -r ALTMAP <br>\r\nUNIX> gf_methods 32 -B -M | sh\r\nM speed (MB/s): 5.90 W-Method: 32 <br>\r\nM speed (MB/s): 14.09 W-Method: 32 -m GROUP 4 8 <br>\r\nM speed (MB/s): 5.60 W-Method: 32 -m SPLIT 32 4 <br>\r\nM speed (MB/s): 5.19 W-Method: 32 -m SPLIT 32 4 -r ALTMAP <br>\r\nM speed (MB/s): 5.98 W-Method: 32 -m SPLIT 32 8 <br>\r\nM speed (MB/s): 22.10 W-Method: 32 -m SPLIT 8 8 <br>\r\nM speed (MB/s): 34.98 W-Method: 32 -m COMPOSITE 2 - <br>\r\nM speed (MB/s): 34.16 W-Method: 32 -m COMPOSITE 2 - -r ALTMAP <br>\r\nUNIX> gf_methods 32 -B -B | sh\r\nRegion Best (MB/s): 2746.76 W-Method: 32 <br>\r\nRegion Best (MB/s): 177.06 W-Method: 32 -m GROUP 4 8 <br>\r\nRegion Best (MB/s): 2818.75 W-Method: 32 -m SPLIT 32 4 <br>\r\nRegion Best (MB/s): 3818.21 W-Method: 32 -m SPLIT 32 4 -r ALTMAP <br>\r\nRegion Best (MB/s): 728.68 W-Method: 32 -m SPLIT 32 8 <br>\r\nRegion Best (MB/s): 730.97 W-Method: 32 -m SPLIT 8 8 <br>\r\nRegion Best (MB/s): 190.20 W-Method: 32 -m COMPOSITE 2 - <br>\r\nRegion Best (MB/s): 1837.99 W-Method: 32 -m COMPOSITE 2 - -r ALTMAP <br>\r\nUNIX>\r\n</div>\r\n<p>\r\nThe default is quite a bit slower than the best performing methods for both single and region multiplication. So\r\nwhy are the defaults the way that they are? As detailed at the beginning of this chapter, we strive for lower memory\r\nconsumption, so we don't use <b>\"SPLIT 8 8,\"</b> which consumes 1.75MB.We don't implement alternate fields by default,\r\nwhich is why we don't use <b>\"COMPOSITE.\"</b> Finally, we don't implement alternate mappings of memory by default,\r\nwhich is why we don't use \"<b>-m SPLIT 32 4 -r ALTMAP -.</b>\"</p>\r\n\r\n<p>Of course, you may change these defaults if you please.</p>\r\n<p>\r\n<b>Test question:</b> Given the numbers above, it would appear that <b>\"COMPOSITE\"</b> yields the fastest performance of\r\nsingle multiplication, while \"SPLIT 32 4\" yields the fastest performance of region multiplication. Should I use two\r\ngf t's in my application – one for single multiplication that uses <b>\"COMPOSITE,\"</b> and one for region multiplication\r\nthat uses <b>\"SPLIT 32 4?\"</b></p>\r\n<p>\r\nThe answer to this is \"no.\" Why? Because composite fields are different from the \"standard\" fields, and if you mix\r\nthese two <b>gf_t</b>'s, then you are using different fields for single multiplication and region multiplication. Please read\r\nsection 7.2 for a little more information on this.</p>\r\n\r\n<h3>6.4 &nbsp &nbsp &nbspCalling gf_init_hard()</h3>\r\n\r\nWe recommend that you use <b>create_gf_from_argv()</b> instead of <b>gf_init_hard().</b> However, there are extra things that\r\nyou can do with <b>gf_init_hard().</b> Here's the prototype:<br><br>\r\n<div id=\"number_spacing\">\r\nint gf_init_hard(gf_t *gf<br>\r\n<div style=\"padding-left:100px\">\r\nint w<br>\r\nint mult_type<br>\r\nint region_type<br>\r\nint divide_type<br>\r\nuint64_t prim_poly<br>\r\nint arg1<br>\r\nint arg2<br>\r\n</div>\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n6 &nbsp &nbsp  <em>  THE DEFAULTS     </em>   <span id=\"index_number\">25 </span> <br><br><br>\r\n<div id=\"number_spacing\">\r\n<div style=\"padding-left:100px\">\r\nGFP base_gf, <br>\r\nvoid *scratch_memory); </div><br><br>\r\n\r\n\r\nThe arguments mult type, region type and divide type allow for the same specifications as above, except the\r\ntypes are integer constants defined in gf complete.h: <br><br>\r\ntypedef enum {GF_MULT_DEFAULT,<br>\r\n<div style=\"padding-left:124px\">\r\nGF_MULT_SHIFT<br>\r\nGF_MULT_CARRY_FREE<br>\r\nGF_MULT_GROUP<br>\r\nGF_MULT_BYTWO_p<br>\r\nGF_MULT_BYTWO_b<br>\r\nGF_MULT_TABLE<br>\r\nGF_MULT_LOG_TABLE<br>\r\nGF_MULT_LOG_ZERO<br>\r\nGF_MULT_LOG_ZERO_EXT<br>\r\nGF_MULT_SPLIT_TABLE<br>\r\nGF_MULT_COMPOSITE } gf_mult_type_t;<br><br>\r\n\r\n</div>\r\n\r\n#define GF_REGION_DEFAULT (0x0)<br>\r\n#define GF_REGION_DOUBLE_TABLE (0x1) <br>\r\n#define GF_REGION_QUAD_TABLE (0x2) <br>\r\n#define GF_REGION_LAZY (0x4) <br>\r\n#define GF_REGION_SSE (0x8) <br>\r\n#define GF_REGION_NOSSE (0x10) <br>\r\n#define GF_REGION_ALTMAP (0x20) <br>\r\n#define GF_REGION_CAUCHY (0x40) <br><br>\r\ntypedef enum { GF_DIVIDE_DEFAULT<br>\r\n<div style=\"padding-left:130px\">GF_DIVIDE_MATRIX<br>\r\nGF_DIVIDE_EUCLID } gf_division_type_t;<br><br>\r\n</div>\r\n</div>\r\n<p>\r\nYou can mix the region types with bitwise or. The arguments to <b>GF_MULT_GROUP,GF_MULT_SPLIT_TABLE</b>\r\nand <b>GF_MULT_COMPOSITE</b> are specified in arg1 and arg2. <b>GF_MULT_COMPOSITE</b> also takes a base field\r\nin <b>base_gf.</b> The base field is itself a <b>gf_t,</b> which should have been created previously with <b>create_gf_fro_argv(),</b>\r\n<b>gf_init_easy()</b> or <b>gf_init_hard().</b> Note that this <b>base_gf</b> has its own <b>base_gf</b> member and can be a composite field\r\nitself.</p>\r\n<p>\r\nYou can specify an alternate polynomial in <b>prim_poly.</b> For <em>w </em>&#8804 32, the leftmost one (the one in bit position <em>w</em>) is\r\noptional. If you omit it, it will be added for you. For <em>w </em> = 64, there's no room for that one, so you have to leave it off.\r\nFor <em>w </em>= 128, your polynomial can only use the bottom-most 64 bits. Fortunately, the standard polynomial only uses\r\nthose bits. If you set <b>prim_poly</b> to zero, the library selects the \"standard\" polynomial.\r\n</p>\r\n<p>\r\nFinally, <b>scratch_memory</b> is there in case you don't want <b>gf_init_hard()</b> to call <b>malloc()</b>. Youmay call <b>gf_scratch_size()</b>\r\nto find out how much extra memory each technique uses, and then you may pass it a pointer for it to use in <b>scratc_memory.</b>\r\nIf you set scratch memory to NULL, then the extra memory is allocated for you with <b>malloc().</b> If you use <b>gf_init_easy()</b>\r\nor <b>create_gf_from_argv(),</b> or you use <b>gf_init_hard()</b> and set <b>scratch_memory</b> to <b>NULL,</b> then you should call <b>gf_free()</b>\r\nto free memory. If you use <b>gf_init_hard()</b> and use your own <b>scratch_memory</b> you can still call <b>gf_free(),</b> and it will\r\nnot do anything.</p>\r\n<p>\r\nBoth <b>gf_init_hard()</b> and <b>gf_scratch_size()</b> return zero if the arguments don't specify a valid <b>gf_t.</b> When that happens,\r\nyou can call <b>gf_error()</b> to print why the call failed.</p>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n\r\n6 &nbsp &nbsp  <em>  FURTHER INFORMATION ON OPTIONS AND ALGORITHMS     </em>   <span id=\"index_number\">26  </span> <br><br><br>\r\n\r\n\r\n<p>We'll give you one example of calling <b>gf_ init_hard().</b> Suppose you want to make a <b>gf_ init_hard()</b> call to be\r\nequivalent to \"-m SPLIT 16 4 -r SSE -r ALTMAP -\" and you want to allocate the scratch space yourself. Then you'd\r\ndo the following:</p><br><br>\r\n\r\n<div id=\"number_spacing\">\r\ngf_t gf; <br>\r\nvoid *scratch; <br>\r\nint size; <br>\r\nsize = gf_scratch_size(16, GF_MULT_SPLIT_TABLE,<br>\r\nGF_REGION_SSE | GF_REGION_ALTMAP,<br>\r\nGF_DIVIDE_DEFAULT,<br>\r\n16, 4); <br>\r\nif (size == 0) { gf_error(); exit(1); } /* It failed. That shouldn’t happen */<br>\r\nscratch = (void *) malloc(size); <br>\r\nif (scratch == NULL) { perror(\"malloc\"); exit(1); } <br>\r\nif (!gf_init_hard(&gf, 16, GF_MULT_SPLIT_TABLE, <br>\r\nGF_REGION_SSE | GF_REGION_ALTMAP, <br>\r\nGF_DIVIDE_DEFAULT,<br>\r\n0, 16, 4, NULL, scratch)) { <br>\r\ngf_error(); <br>\r\nexit(1); <br>\r\n} <br>\r\n\r\n</div>\r\n\r\n\r\n<h3>6.5 &nbsp   &nbsp   gf_size() </h3>\r\n\r\nYou can call <b>gf_size(gf_t *gf)</b> to learn the memory consumption of the <b>gf_t.</b> It returns all memory consumed by the\r\n<b>gf_t,</b> including the <b>gf_t</b> itself, any scratch memory required by the gf_ t, and the memory consumed by the sub-field\r\nif the field is <b>\"COMPOSITE.\"</b> If you provided your own memory to <b>gf_init_hard(),</b> it does not report the size of\r\nthis memory, but what the size should be, as determined by <b>gf_scratch size(). gf_ unit() </b> prints out the return value of\r\n<b>gf_size()</b> on the given field.\r\n\r\n\r\n<h2>7 &nbsp Further Information on Options and Algorithms </h2>\r\n<h3>\r\n7.1 &nbsp Inlining Single Multiplication and Division for Speed </h3>\r\n\r\nObviously, procedure calls are more expensive than single instructions, and the mechanics of multiplication in <b>\"TABLE\"</b>\r\nand <b>\"LOG\"</b> are pretty simple. For that reason, we support inlining for <b>\"TABLE\"</b> when <em>w </em> = 4 and <em>w </em> = 8, and\r\nfor <b>\"LOG\"</b> when <em>w </em> = 16. We elaborate below.\r\n<p>\r\nWhen <em>w </em> = 4, you may inline multiplication and division as follows. The following procedures return pointers to\r\nthe multiplication and division tables respectively: </p> <br><br>\r\n\r\n<div id=\"number_spacing\">\r\nuint8_t *gf_w4_get_mult_table(gf_t * gf);<br>\r\nuint8_t *gf_w4_get_div_table(gf_t * gf);<br><br>\r\n</div>\r\n<p>The macro <b>Gf_W4_INLINE_MULTDIV </b>(<em>table, a, b</em>) then multiplies or divides <em>a </em> by <em>b</em> using the given table. This\r\nof course only works if the multiplication technique is <b>\"TABLE,\"</b> which is the default for <em>w </em> = 4. If the multiplication\r\ntechnique is not <b>\"TABLE,\"</b> then <b>gf_w4_get_mult_table()</b> will return <b>NULL.</b></p>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n\r\n6 &nbsp &nbsp  <em>  FURTHER INFORMATION ON OPTIONS AND ALGORITHMS     </em>   <span id=\"index_number\">27  </span> <br><br><br>\r\n\r\n\r\n\r\n\r\n<p>When <em>w </em> = 8, the procedures <b>gf_w8_et_mult_table()</b> and <b>gf_ w8_get_div_table(),</b> and the macro </p>\r\n\r\n<b>GF_W8_INLINE_MULTDIV </b>(<em>table, a, b</em>) work identically to the <em>w </em> = 4 case.\r\n\r\n<p>When <em>w </em> = 16, the following procedures return pointers to the logarithm table, and the two inverse logarithm tables\r\nrespectively: </p><br>\r\n\r\n<div id=\"number_spacing\">\r\nuint16_t *gf_w16_get_log_table(gf_t * gf); <br>\r\nuint16_t *gf_w16_get_mult_alog_table(gf_t * gf);<br>\r\nuint16_t *gf_w16_get_div_alog_table(gf_t * gf);<br>\r\n\r\n</div>\r\n<br>\r\n<p>\r\nThe first inverse logarithm table works for multiplication, and the second works for division. They actually point\r\nto the same table, but to different places in the table. You may then use the macro <b>GF_W16_INLINE_MULT</b>(<em>log,\r\nalog, a, b </em>) to multiply <em>a</em> and <em>b</em>, and the macro <b>GF_W16_INLINE_DIV </b>(<em>log, alog, a, b </em>) to divide a and b. Make\r\nsure you use the <em>alog</em> table returned by <b>gf_w16_get_mult_alog_table()</b> for multiplication and the one returned by\r\n<b>gf_w16_get_div_alog_table()</b> for division. Here are some timings: </p> <br><br>\r\n\r\n\r\nUNIX> gf_time 4 M 0 10240 10240 - <br>\r\nSeed: 0 <br>\r\nMultiply: 0.228860 s Mops: 100.000 436.949 Mega-ops/s <br>\r\nUNIX> gf_inline_time 4 0 10240 10240 <br>\r\nSeed: 0 <br>\r\nInline mult: 0.096859 s Mops: 100.000 1032.424 Mega-ops/s <br>\r\nUNIX> gf_time 8 M 0 10240 10240 - <br>\r\nSeed: 0 <br>\r\nMultiply: 0.228931 s Mops: 100.000 436.812 Mega-ops/s <br>\r\nUNIX> gf_inline_time 8 0 10240 10240 <br>\r\nSeed: 0 <br>\r\nInline mult: 0.114300 s Mops: 100.000 874.889 Mega-ops/s <br>\r\nUNIX> gf_time 16 M 0 10240 10240 - <br>\r\nSeed: 0 <br>\r\nMultiply: 0.193626 s Mops: 50.000 258.229 Mega-ops/s <br>\r\nUNIX> gf_inline_time 16 0 10240 10240 <br>\r\nSeed: 0 <br>\r\nInline mult: 0.310229 s Mops: 100.000 322.342 Mega-ops/s <br>\r\nUNIX> <br> <br>\r\n\r\n<h3>\r\n7.2 &nbsp &nbsp Using different techniques for single and region multiplication </h3>\r\n\r\n\r\nYou may want to \"mix and match\" the techniques. For example, suppose you'd like to use \"-m SPLIT 8 8\" for\r\n<b>multiply()</b> in <em>GF(2<sup>32</sup>),</em> because it's fast, and you don't mind consuming all of that space for tables. However, for\r\n<b>multiply_region(),</b> you'd like to use \"-m SPLIT 32 4 -r ALTMAP,\" because that's the fastest way to implement\r\n<b>multiply_region().</b> Unfortunately, There is no way to create a <b>gf_t</b> that does this combination. In this case, you should\r\nsimply create two <b>gf_t's,</b> and use one for <b>multiply()</b> and the other for <b>multiply_region().</b> All of the implementations\r\nmay be used interchangably with the following exceptions:\r\n\r\n<ul>\r\n<li>\r\n<b>\"COMPOSITE\"</b> implements a different Galois Field. </li><br>\r\n\r\n<li>If you change a field's polynomial, then the resulting Galois Field will be different. </li>\r\n\r\n</ul>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n\r\n6 &nbsp &nbsp  <em>  FURTHER INFORMATION ON OPTIONS AND ALGORITHMS     </em>   <span id=\"index_number\">28  </span> <br><br><br>\r\n\r\n<ul>\r\n<li>\r\n\r\nIf you are using <b>\"ALTMAP\"</b> to multiply regions, then the contents of the resulting regions of memory will\r\ndepend on the multiplication technique, the size of the region and its alignment. Please see section 7.9 for a\r\ndetailed explanation of this. </li>\r\n\r\n<li>If you are using <b>\"CAUCHY\"</b> to multiply regions, then like <b>\"ALTMAP,\"</b> the contents of the result regions of\r\nmemory the multiplication technique and the size of the region. You don't have to worry about alignment. </li>\r\n\r\n<h3>7.3 &nbsp &nbsp General <em>w </em>  </h3>\r\nThe library supports Galois Field arithmetic with 2 < <em>w </em> &#8804 32. Values of <em>w </em> which are not whole number powers of\r\n2 are handled by the functions in <b>gf_wgen.c</b> . For these values of <em>w </em>, the available multiplication types are <b>\"SHIFT,\"\r\n\"BYT<em>w </em>O p,\" \"BYT<em>w </em>O b,\" \"GROUP,\" \"TABLE\"</b> and <b>\"LOG.\" \"LOG\" </b> is only valid for <em>w </em> < 28 and <b>\"TABLE\"</b>\r\n\r\nis only valid for <em>w </em> < 15. The defaults for these values of <em>w </em> are <b>\"TABLE\"</b> for <em>w </em> < 8, <b>\"LOG\"</b> for <em>w </em> < 16, and\r\n<b>\"BYT<em>w </em>O p\"</b> for <em>w </em> < 32.<br><br>\r\n\r\n<h3>7.4 Arguments to \"SPLIT\" </h3>\r\n\r\nThe \"SPLIT\" technique is based on the distributive property of multiplication and addition: <br><br>\r\n<center>\r\na * (b + c) = (a * b) + (a * c). </center>\r\n<br>\r\nThis property allo<em>w </em>s us to, for example, split an eight bit <em>w </em>ord into t<em>w </em>o four-bit components and calculate the product\r\nby performing t<em>w </em>o table lookups in 16-element tables on each of the compoents, and adding the result. There is much\r\nmore information on <b>\"SPLIT\"</b> in The Paper. Here <em>w </em>e describe the version of <b>\"SPLIT\"</b> implemented in GF-Complete.\r\n\r\n<p>\r\n<b>\"SPLIT\"</b> takes t<em>w </em>o arguments, <em>w </em>hich are the number of bits in each component of a, <em>w </em>hich <em>w </em>e call <em>w </em><sub>a</sub>, and the\r\nnumber of bits in each component of b, <em>w </em>hich <em>w </em>e call <em>w </em><sub>b.</sub> If the t<em>w </em>o differ, it does not matter <em>w </em>hich is bigger - the\r\nlibrary recognizes this and performs the correct implementation. The legal values of <em>w </em><sub>a</sub> and <em>w </em><sub>b</sub> fall into five categories:\r\n</p><br>\r\n\r\n\r\n<ol>\r\n<li>\r\n <em>w </em><sub>a</sub> is equal to <em>w </em> and <em>w </em><sub>b</sub> is equal to four. In this case, b is broken up into <em>w </em>/4\r\nfour-bit <em>w </em>ords <em>w </em>hich are used\r\nin 16-element lookup tables. The tables are created on demand in <b>multiply_region()</b> and the SSSE3 instruction\r\n\r\n<b>mm_shuffle_epi8()</b> is leveraged to perform 16 lookups in parallel. Thus, these are very fast implementations.\r\n<em>w </em>hen <em>w </em> &#8805 16, you should combine this <em>w </em>ith <b>\"ALTMAP\"</b> to get the best performance (see The Paper\r\nor [PGM13b] for explanation). If you do this please see section 7.9 for information about <b>\"ALTMAP\"</b> and\r\nalignment.<br><br>\r\n\r\n\r\nIf you don't use <b>\"ALTMAP,\"</b> the implementations for <em>w </em> &#8712 {16, 32, 64} convert the standard representation into\r\n<b>\"ALTMAP,\"</b> perform the multiplication <em>w </em>ith <b>\"ALTMAP\"</b> and then convert back to the standard representation.\r\nThe performance difference using <b>\"ALTMAP\"</b> can be significant: <br><br><br>\r\n\r\n<div id=\"number_spacing\">\r\n<center>\r\n<div id=\"table_page28\">\r\n<table cellpadding=\"6\" cellspacing=\"0\" style=\"text-align:center;font-size:19px\">\r\n<tr>\r\n<td> gf time 16 G 0 1048576 100 -m SPLIT 16 4 -</td> <td>Speed = 8,389 MB/s </td> \r\n</tr>\r\n<tr>\r\n<td>gf time 16 G 0 1048576 100 -m SPLIT 16 4 -r ALTMAP - </td> <td>Speed = 8,389 MB/s </td> \r\n</tr>\r\n\r\n<tr>\r\n<td>gf time 32 G 0 1048576 100 -m SPLIT 32 4 -</td> <td> Speed = 5,304 MB/s</td> \r\n</tr>\r\n<tr>\r\n<td>gf time 32 G 0 1048576 100 -m SPLIT 32 4 -r ALTMAP -</td> <td> Speed = 7,146 MB/s</td> \r\n</tr>\r\n\r\n\r\n<tr>\r\n<td>gf time 64 G 0 1048576 100 -m SPLIT 64 4 - </td> <td>Speed = 2,595 MB/s </td> \r\n</tr>\r\n\r\n<tr>\r\n<td>gf time 64 G 0 1048576 100 -m SPLIT 64 4 -r ALTMAP - </td> <td>Speed = 3,436 MB/s </td> \r\n</tr>\r\n</div>\r\n\r\n\r\n\r\n</table>\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n\r\n6 &nbsp &nbsp  <em>  FURTHER INFORMATION ON OPTIONS AND ALGORITHMS     </em>   <span id=\"index_number\">29  </span> <br><br><br>\r\n\r\n<ol style=\"list-style-type:none\">\r\n\r\n\r\n<li>2. &nbsp w<sub>a</sub> is equal to <em>w </em> and w<sub>b</sub> is equal to eight. Now, b is broken into bytes, each of these is used in its own 256-element\r\nlookup table. This is typically the best w<sub>a</sub>y to perform <b>multiply_region()</b> without SSE.</li> \r\nBecause this is a region optimization, when you specify these options, you get a default <b>multiply()</b>  see\r\nTable 1 for a listing of the defaults. See section 7.2 for using a different <b>multiply()</b> than the defaults.<br><br>\r\n\r\n\r\n<li>\r\n3. &nbsp w<sub>a</sub> is equal to <em>w </em> and <em>w </em><sub>b</sub> is equal to 16. This is only valid for <em>w </em> = 32 and <em>w </em> = 64. No<em>w </em>, b is broken into shorts,\r\neach of these is used in its own 64K-element lookup table. This is typically slower than when <em>w </em><sub>b</suB> equals 8, and\r\nrequires more amortization (larger buffer sizes) to be effective. </li><br>\r\n\r\n\r\n<li>4. &nbsp <em>w </em><sub>a</sub> and <em>w </em><sub>b</sub> are both equal to eight. Now  both <em>a</em> and <em>b</em> are broken into bytes, \r\nand the products of the various bytes\r\nare looked up in multiple 256 &#215 256 tables. In <em>GF(2<sup>16</sup>),</em> there are three of these tables. In <em>GF(232),</em> there are\r\nseven, and in <em>GF(2<sup>64</sup>)</em> there are fifteen. Thus, this implementation can be a space hog. How ever, for <em>w </em> = 32,\r\nthis is the fastest way to perform <b>multiply()</b> on some machines.\r\nwhen this option is employed, <b>multiply_region()</b> is implemented in an identical fashion to when <em>w </em><sub>a</sub> = <em>w </em>\r\nand <em>w </em><sub>b</sub> = 8. </li><br>\r\n\r\n<li>5.&nbsp w<sub>a</sub> = 32 and w<sub>b</sub> = 2. (<em>w</em> = 32 only). I was playing with a different way to use <b>mm_shuffle_epi8().</b> It works,\r\nbut it's slower than when w<sub>b</sub> = 4.\r\n</li>\r\n\r\n</ul>\r\n\r\n\r\n\r\n<h2>7.5 &nbsp&nbsp Arguments to \"GROUP\" </h3>\r\n\r\nThe <b>\"GROUP\"</b> multiplication option takes t<em>w </em>o arguments, g<sub>s</sub> and g<sub>r</sub>. It implements multiplication in the same manner\r\nas <b>\"SHIFT,\"</b> except it uses a table of size 2<sup>gs</sup> to perform g<sup>s</sup> shifts at a time, and a table of size 2<sup>gr</sup> to perform g<sup>r</sup>\r\nreductions at at time. The program <b>gf_methods</b> only prints the options 4 4 and 4 8 as arguments for <b>\"GROUP.\"</b>\r\nHowever, other values of g<sub>s</sub> and g<sub>r</sub> are legal and sometimes desirable: <br><br>\r\n\r\n<ol>\r\n<li>\r\n For <em>w </em> &#8804 32 and <em>w </em> = 64, any values of g<sub>s</sub> and g<sub>r</sub> may be used, so long as they are less than or equal to <em>w </em> and so\r\nlong as the tables fit into memory. There are four exceptions to this, listed belo<em>w </em>. </li><br>\r\n<li> For <em>w </em> = 4, <b>\"GROUP\"</b> is not supported. </li><br>\r\n<li> For <em>w </em> = 8, <b>\"GROUP\"</b> is not supported. </li><br>\r\n<li> For <em>w </em> = 16, <b>\"GROUP\"</b> is only supported for gs = gr = 4. </li><br>\r\n<li> For <em>w </em> = 128 <b>\"GROUP\"</b> only supports <em>g<sub>s</sub></em> = 4 and <em> g<sub>r</b> </em> &#8712 {4, 8, 16}.</li><br>\r\n</ol>\r\n<p>\r\nThe way that gs and gr impact performance is as follows. The <b>\"SHIFT\"</b> implementation works by performing a\r\ncarry-free multiplication in <em>w </em> steps, and then performing reduction in <em>w </em> steps. In \"GROUP,\" the carry-free multiplication\r\nis reduced to  <em>w /</em>g<sub>s</sub>steps, and the reduction is reduced to <em>w /</em>g<sub>r</sub>\r\n\r\n. Both require tables. The table for the carry-free\r\nmultiplication must be created at the beginning of each <b>multiply()</b> or <b>multiply_region(),</b> while the table for reduction\r\nis created when the <b>gf_t</b> is initialized. For that reason, it makes sense for g<sub>r</sub> to be bigger than g<sub>s.</sub></p>\r\n\r\n<p>\r\nTo give a flavor for the impact of these arguments, Figure 3 show </em>s the performance of varying g<sub>s</sub> and g<sub>r</sub> for\r\nsingle multiplication and region multiplication respectively, in <em> GF(2<sup>32</sup>)</em> and <em>GF(2<sup>64</sup>).</em> As the graphs demonstrate,\r\n<b>multiply()</b> performs better <em>w </em>ith smaller values of gs, <em>w </em>hile multiply region() amortizes the creation of the shifting\r\ntable, and can tolerate larger values of g<sub>s.</sub> <em>w </em>hen g<sub>s</sub> equals g<sub>r,</sub> there are some optimizations that we hand-encode.\r\nThese can be seen clearly in the <b>multiply_region()</b> graphs.\r\n</p>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n7 &nbsp &nbsp  <em>   FURTHER INFORMATION ON OPTIONS AND ALGORITHMS     </em>   <span id=\"index_number\">30 </span> \r\n\r\n\r\n<div id=\"box_1\"> \r\n \r\n<div class=\"image-cell_3\"> </div>\r\n\r\n<div class=\"image-cell_4\"> </div>\r\n</div>\r\nFigure 3: The performance of <b>multiply()</b> and <b>multiply_region()</b> using <b>\"GROUP,\"</b> and varying the arguments <br> g<sub>s</sub>\r\nand g<sub>r.</sub> All graphs are heat maps with black equaling zero. The region size is 100KB.\r\n\r\n<h3>7.6 &nbspConsiderations with \"COMPOSITE\" </h3>\r\n\r\n\r\nAs mentioned above, using <b>\"ALTMAP\"</b> with <b>\"COMPOSITE\"</b> allows <b>multiply_region()</b> to recursively call <b>multiply_\r\nregion(),</b> rather than simply calling <b>multiply()</b> on every word in the region. The difference can be pronounced:<br><br>\r\n\r\n<div id=\"table_page28\"><center>\r\n\r\n<table cellpadding=\"6\" cellspacing=\"0\" style=\"text-align:center;font-size:19px\"><tr>\r\n<td>\r\ngf time 32 G 0 10240 10240 -m COMPOSITE 2 - -\r\nSpeed = 322 MB/s </td> </tr>\r\n<tr>\r\n<td>gf time 32 G 0 10240 10240 -m COMPOSITE 2 - -r ALTMAP -\r\nSpeed = 3,368 MB/s </td> </tr>\r\n\r\n<tr>\r\n<td>\r\ngf time 32 G 0 10240 10240 -m COMPOSITE 2 -m SPLIT 16 4 -r ALTMAP - -r ALTMAP -\r\nSpeed = 3,925 MB/s </td> </tr>\r\n</center>\r\n</table>\r\n</div>\r\n\r\n\r\n<br><br>\r\n<p>\r\nThere is support for performing <b>multiply()</b> inline for the <b>\"TABLE\"</b> implementations for w &#8712 {4, 8} and for the\r\n\"LOG\" implementation for <em>w</em> = 16 (see section 7.1). These are leveraged by <b>multiply()</b> in <b>\"COMPOSITE,\"</b> and\r\nby <b>multiply_region()</b> if you are not using <b>\"ALTMAP.\"</b> To demonstrate this, in the table below, you can see that the\r\nperformance of <b>multiply()</b> with <b>\"SPLIT 8 4\"</b> is 88 percent as fast than the default in <em>w</em> = 8 (which is <b>\"TABLE\"</b>).\r\nWhen you use each as a base field for <b>\"COMPOSITE\"</b> with <em>w</em> = 16, the one with <b>\"SPLIT 8 4\"</b> is now just 37 percent\r\nas fast. The difference is the inlining of multiplication in the base field when <b>\"TABLE\"</b> is employed:</p><br><br>\r\n\r\n<div id=\"table_page28\" border=\"0\"><center>\r\n\r\n    <table cellpadding=\"6\" cellspacing=\"0\" style=\"text-align:center;font-size:19px\">\r\n\r\n      <tr><td>gf time 8 M 0 1048576 100 - Speed = 501 Mega-ops/s</td> </tr>\r\n      <tr><td>gf time 8 M 0 1048576 100 -m SPLIT 8 4 - Speed = 439 Mega-ops/s </td> </tr>\r\n      <tr><td>gf time 8 M 0 1048576 100 -m COMPOSITE 2 - - Speed = 207 Mega-ops/s </td> </tr>\r\n      <tr><td>gf time 8 M 0 1048576 100 -m COMPOSITE 2 -m SPLIT 8 4 - - Speed = 77 Mega-ops/s </td> </tr>\r\n\r\n    </table> \r\n    </center>\r\n<br><br>\r\n</div>\r\n\r\nYou can keep making recursive definitions of composites field if you want. For example, this one's not too slow for\r\nregion operations (641 MB/s):\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n<br/>\r\n\r\n\r\n6 &nbsp &nbsp  <em>  FURTHER INFORMATION ON OPTIONS AND ALGORITHMS     </em>   <span id=\"index_number\">31  </span> <br><br><br>\r\n\r\n<div id=\"number_spacing\">\r\n<center>\r\ngf time 128 G 0 1048576 100 -m COMPOSITE 2 <span style=\"color:red\">-m COMPOSITE 2 </span> <span style=\"color:blue\">-m COMPOSITE 2 </span> <br>\r\n<span style=\"color:rgb(250, 149, 167)\">-m SPLIT 16 4 -r ALTMAP -</span> <span style=\"color:blue\">-r ALTMAP -</span> <span style=\"color:red\"> -r ALTMAP -</span> -r ALTMAP -\r\n</center>\r\n</div><br>\r\n\r\n<p>Please see section 7.8.1 for a discussion of polynomials in composite fields.</p>\r\n\r\n<h2>7.7 &nbsp &nbsp &nbsp \"CARRY FREE\" and the Primitive Polynomial </h2>\r\n\r\n\r\nIf your machine supports the PCLMUL instruction, then we leverage that in <b>\"CARRY FREE.\"</b> This implementation\r\nfirst performs a carry free multiplication of two <em>w</em>-bit numbers, which yields a 2<em>w</em>-bit number. It does this with\r\none PCLMUL instruction. To reduce the 2<em>w</em>-bit number back to a <em>w</em>-bit number requires some manipulation of the\r\npolynomial. As it turns out, if the polynomial has a lot of contiguous zeroes following its leftmost one, the number of\r\nreduction steps may be minimized. For example, with <em>w </em> = 32, we employ the polynomial 0x100400007, because that\r\nis what other libraries employ. This only has 9 contiguous zeros following the one, which means that the reduction\r\ntakes four steps. If we instead use 0x1000000c5, which has 24 contiguous zeros, the reduction takes just two steps.\r\nYou can see the difference in performance:\r\n<br><br>\r\n<center>\r\n<div id=\"table_page28\">\r\n\r\n<table cellpadding=\"6\" cellspacing=\"0\" style=\"text-align:center;font-size:19px\">\r\n<tr>\r\n\r\n<td>gf time 32 M 0 1048576 100 -m CARRY FREE - </td> <td> Speed = 48 Mega-ops/s</td> </tr>\r\n\r\n<tr><td>gf time 32 M 0 1048576 100 -m CARRY FREE -p 0xc5 -</td> <td> Speed = 81 Mega-ops/s </td> </tr>\r\n\r\n</table></center>\r\n</div>\r\n<br><br>\r\n\r\n<p>\r\nThis is relevant for <em>w </em> = 16 and <em>w </em> = 32, where the \"standard\" polynomials are sub-optimal with respect to\r\n<b>\"CARRY FREE.\"</b> For <em>w </em> = 16, the polynomial 0x1002d has the desired property. It’s less important, of course,\r\nwith <em>w </em> = 16, because <b>\"LOG\"</b> is so much faster than <b>CARRY FREE.</b> </p>\r\n\r\n<h2>7.8 &nbsp  More on Primitive Polynomials </h3>\r\n\r\n<h3>7.8.1 &nbsp Primitive Polynomials that are not Primitive </h4>\r\n\r\nThe library is willing to work with most polynomials, even if they are not primitive or irreducible. For example, the\r\npolynomial x<sup>4</sup> + x<sup>3</sup> +x<sup>2</sup> +x+1 is irreducible, and therefore generates a valid Galois Field for <em>GF(2<sup>4</sup>).</em> However, it\r\nis not primitive, because 2<sup>5</sup> = 1. For that reason, if you use this polynomial, you cannot use the <b>\"LOG\"</b> method. The\r\nother methods will work fine: <br><br>\r\n\r\n<div id=\"number_spacing\">\r\n\r\nUNIX> gf_mult 2 2 4 -p 0xf -  <br>\r\n4 <br>\r\nUNIX> gf_mult 4 2 4 -p 0xf - <br>\r\n8 <br>\r\nUNIX> gf_mult 8 2 4 -p 0xf - <br>\r\n15 <br>\r\nUNIX> gf_mult 15  2 4 -p 0xf - <br>\r\n1 <br>\r\nUNIX> gf_div 1 15 4 -p 0xf - <br>\r\n2 <br>\r\nUNIX> gf_div 1 15 4 -p 0xf -m LOG - <br>\r\nusage: gf_div a b w [method] - does division of a and b in GF(2&#710;w) <br>\r\nBad Method Specification: Cannot use Log tables because the polynomial is not primitive. <br>\r\nUNIX>  <br>\r\n</div>\r\n<p>\r\nIf a polynomial is reducible, then it does not define a Galois Field, but instead a ring. GF-Complete attempts to\r\nwork here where it can; however certain parts of the library will not work:\r\n</p>\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n\r\n6 &nbsp &nbsp  <em>  FURTHER INFORMATION ON OPTIONS AND ALGORITHMS     </em>   <span id=\"index_number\">32  </span> <br><br><br>\r\n<ol>\r\n<li>\r\nDivision is a best effort service. The problemis that often quotients are not unique. If <b>divide()</b> returns a non-zero\r\nnumber, then that number will be a valid quotient, but it may be one of many. If the multiplication technique is\r\n<b>\"TABLE,\"</b> then if a quotient exists, one is returned. Otherwise, zero is returned. Here are some examples - the\r\npolynomial x<sup>4</sup> + 1 is reducible, and therefore produces a ring. Below, we see that with this polynomal, 1*6 = 6\r\nand 14*6 = 6. Therefore, 6/6 has two valid quotients: 1 and 14. GF-Complete returns 14 as the quotient:</li><br>\r\n\r\n<div id=\"number_spacing\">\r\nUNIX> gf_mult 1 6 4 -p 0x1 -<br>\r\n6 <br>\r\nUNIX> gf_mult 14 6 4 -p 0x1 - <br>\r\n6 <br>\r\nUNIX> gf_div 6 6 4 -p 0x1 - <br>\r\n14 <br>\r\nUNIX> <br><br>\r\n</div>\r\n\r\n\r\n<li>When <b>\"EUCLID\"</b> is employed for division, it uses the extended Euclidean algorithm for GCD to find a number's\r\ninverse, and then it multiplies by the inverse. The problem is that not all numbers in a ring have inverses. For\r\nexample, in the above ring, there is no number <em>a</em> such that 6a = 1. Thus, 6 has no inverse. This means that even\r\nthough 6/6 has quotients in this ring, <b>\"EUCLID\"</b> will fail on it because it is unable to find the inverse of 6. It will\r\nreturn 0:\r\n</li><br>\r\n<div id=\"number_spacing\">\r\nUNIX> gf_div 6 6 4 -p 0x1 -m TABLE -d EUCLID -<br>\r\n0<br>\r\nUNIX><br>\r\n</div><br>\r\n\r\n<li> Inverses only work if a number has an inverse. Inverses may not be unique. </li><br>\r\n\r\n<li> <b>\"LOG\"</b> will not work. In cases where the default would be <b>\"LOG,\"</b> <b>\"SHIFT\"</b> is used instead. </li>\r\n</ol>\r\n\r\n<p>\r\nDue to problems with division, <b>gf_unit</b> may fail on a reducible polynomial. If you are determined to use such a\r\npolynomial, don't let this error discourage you.\r\n</p>\r\n\r\n<h3>7.8.2 Default Polynomials for Composite Fields </h3>\r\n\r\nGF-Complete will successfully select a default polynomial in the following composite fields:\r\n<ul>\r\n<li> <em>w </em> = 8 and the default polynomial (0x13) is employed for <em>GF(2<sup>4</sup>)</em></li><br>\r\n<li> w = 16 and the default polynomial (0x11d) is employed for <em>GF(2<sup>8</sup>)</em></li><br>\r\n<li> <em>w </em> = 32 and the default polynomial (0x1100b) is employed for <em>GF(2<sup>16</sup>) </em></li><br>\r\n<li> <em>w </em> = 32 and 0x1002d is employed for <em>GF(2<sup>16</sup>) </em></li><br>\r\n<li> <em>w </em> = 32 and the base field for <em>GF(w<em>16</em>) </em> is a composite field that uses a default polynomial</li><br>\r\n<li> <em>w </em> = 64 and the default polynomial (0x100400007) is employed for <em>GF(2<sup>32</sup>)</em></li><br>\r\n<li> <em>w </em> = 64 and 0x1000000c5 is employed for <em>GF(2<sup>32</sup>) </em></li><br>\r\n<li> <em>w </em> = 64 and the base field for <em>GF(w<sup>32</sup>) </em> is a composite field that uses a default polynomial</li><br>\r\n<li> <em>w </em> = 128 and the default polynomial (0x1b) is employed for <em>GF(2<sup>64</sup>) </em></li><br>\r\n<li> <em>w </em> = 128 and the base field for <em> GF(w<sup>64 </sup>) </em> is a composite field that uses a default polynomial</li><br>\r\n</ul>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n\r\n6 &nbsp &nbsp  <em>  FURTHER INFORMATION ON OPTIONS AND ALGORITHMS     </em>   <span id=\"index_number\">33  </span> <br><br><br>\r\n\r\n\r\n<h3>7.8.3 The Program gf poly for Verifying Irreducibility of Polynomials </h3>\r\n\r\nThe program <b>gf_poly</b> uses the Ben-Or algorithm[GP97] to determine whether a polynomial with coefficients in <em> GF(2<sup>w </sup>) </em>\r\nis reducible. Its syntax is:<br><br>\r\n<div id=\"number_spacing\">\r\ngf_poly w method power:coef power:coef ... \r\n</div>\r\n\r\n<br>\r\n<p>You can use it to test for irreducible polynomials with binary coefficients by specifying w = 1. For example, from\r\nthe discussion above, we know that x<sup>4</sup> +x+1 and x<sup>4</sup> +x<sup>3</sup> +x<sup>2</sup> +x+1 are both irreducible, but x<sup>4</sup> +1 is reducible.\r\n<b>gf_poly</b> confirms:<p><br>\r\n\r\n<div id=\"number_spacing\">\r\nUNIX> gf_poly 1 - 4:1 1:1 0:1 <br>\r\nPoly: x&#710;4 + x + 1 <br>\r\nIrreducible. <br>\r\nUNIX> gf_poly 1 - 4:1 3:1 2:1 1:1 0:1 <rb>\r\nPoly: x&#710;4 + x&#710;3 + x&#710;2 + x + 1 <br>\r\nIrreducible. <br>\r\nUNIX> gf_poly 1 - 4:1 0:1 r<br>\r\nPoly: x&#710;4 + 1 <br>\r\nReducible. <br>\r\nUNIX> <br>\r\n\r\n</div>\r\n\r\n\r\n<p>\r\nFor composite fields <em>GF((2<sup>l</sup>)<sup>2</sup>),</em> we are looking for a value s such that x<sup>2</sup> + sx + 1 is irreducible. That value\r\ndepends on the base field. For example, for the default field <em>GF(2<sup>32</sup>),</em> a value of <em>s</em> = 2 makes the polynomial\r\nirreducible. However, if the polynomial 0xc5 is used (so that PCLMUL is fast - see section 7.7), then <em>s</em> = 2 yields a\r\nreducible polynomial, but <em>s</em> = 3 yields an irreducible one. You can use <b>gf_poly</b> to help verify these things, and to help\r\ndefine s if you need to stray from the defaults:</p> <br>\r\n\r\n<div id=\"number_spacing\">\r\nUNIX> gf_poly 32 - 2:1 1:2 0:1<br>\r\nPoly: x&#710;2 + (0x2)x + 1 <br>\r\nIrreducible. <br>\r\nUNIX> gf_poly 32 -p 0xc5 - 2:1 1:2 0:1 <br>\r\nPoly: x&#710;2 + (0x2)x + 1 <br>\r\nReducible. <br>\r\nUNIX> gf_poly 32 -p 0xc5 - 2:1 1:3 0:1 <br>\r\nPoly: x&#710;2 + (0x3)x + 1 <br>\r\nIrreducible. <br>\r\nUNIX> <br>\r\n</div>\r\n\r\n<p>\r\n<b>gf_unit</b> does random sampling to test for problems. In particular, it chooses a random a and a random b, multiplies\r\nthem, and then tests the result by dividing it by a and b. When w is large, this sampling does not come close to\r\nproviding complete coverage to check for problems. In particular, if the polynomial is reducible, there is a good\r\nchance that <b>gf_unit</b> won't discover any problems. For example, the following <b>gf_unit</b> call does not flag any problems,\r\neven though the polynomial is reducible.</p>\r\n<br>\r\n<div id=\"number_spacing\">\r\nUNIX> gf_unit 64 A 0 -m COMPOSITE 2 -p 0xc5 - -p 2 -<br>\r\nUNIX>\r\n</div>\r\n\r\n<p>\r\nHow can we demonstrate that this particular field has a problem? Well, when the polynomial is 0xc5, we can factor\r\nx<sup>2</sup> + 2x + 1 as (x + 0x7f6f95f9)(x + 0x7f6f95fb). Thus, in the composite field, when we multiply 0x17f6f95f9 by\r\n0x17f6f95fb, we get zero. That's the problem:\r\n</p>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n\r\n6 &nbsp &nbsp  <em>  FURTHER INFORMATION ON OPTIONS AND ALGORITHMS     </em>   <span id=\"index_number\">34  </span> <br><br><br>\r\n\r\n<div id=\"number_spacing\">\r\n\r\nUNIX> gf_mult 7f6f95f9 7f6f95fb 32h -p 0xc5 - <br>\r\n1 <br>\r\nUNIX> gf_mult 17f6f95f9 17f6f95fb 64h -m COMPOSITE 2 -p 0xc5 - -p 2 - <br>\r\n0 <br>\r\nUNIX> <br>\r\n\r\n</div>\r\n\r\n<h2>7.9 \"ALTMAP\" considerations and extract_word() </h2>\r\n\r\nThere are two times when you may employ alternate memory mappings:\r\n<ol>\r\n<li> When using <b>\"SPLIT\"</b> and w<sub>b</sub> = 4. </li>\r\n<li> When using <b>\"COMPOSITE.\"</b> </li>\r\n</ol>\r\n\r\nAdditionally, by default, the <b>\"CAUCHY\"</b> region option also employs an alternate memory mapping.\r\n\r\n<p>When you use alternate memory mappings, the exact mapping of words in <em> GF(2<sup>w </sup>) </em> to memory depends on the\r\nsituation, the size of the region, and the alignment of the pointers. To help you figure things out, we have included the\r\nprocedures <b>extract_word.wxx()</b> as part of the <b>gf_t</b> struct. This procedure takes four parameters: </p>\r\n<ul>\r\n<li>A pointer to the <b>gf_t.</b> </li>\r\n<li> The beginning of the memory region. </li>\r\n<li>The number of bytes in the memory region. </li>\r\n<li>The desired word number: <em>n.</em> </li>\r\n</ul>\r\n\r\n<p>\r\nIt then returns the <em>n</em>-th word in memory. When the standard mapping is employed, this simply returns the <em>n</em>-\r\nth contiguous word in memory. With alternate mappings, each word may be split over several memory regions, so\r\n<b>extract_word()</b> grabs the relevant parts of each memory region to extract the word. Below, we go over each of the\r\nabove situations in detail. Please refer to Figure 2 in Section 5 for reference. </p>\r\n\r\n\r\n<h3>7.9.1 Alternate mappings with \"SPLIT\" </h3>\r\n\r\nThe alternate mapping with <b>\"SPLIT\"</b> is employed so that we can best leverage <b>mm_shuffle_epi8().</b> Please read [PGM13b]\r\nfor details as to why. Consider an example when <em>w</em> = 16. In the main region of memory (the middle region in Figure\r\n2), multiplication proceeds in units of 32 bytes, which are each broken into two 16-byte regions. The first region\r\nholds the high bytes of each word in <em>GF(2<sup>16</sup>),</em> and the second region holds the low bytes.\r\nLet's look at a very detailed example, from <b>gf_example_5.c.</b> This program makes the following call, where <b>gf</b> has\r\n\r\nbeen initialized for <em>w</em> = 16, using <b>\"SPLIT\"</b> and <b>\"ALTMAP:\"</b><br><br>\r\n<div id=\"number_spacing\">\r\ngf.multiply_region.w32(&gf, a, b, 0x1234, 30*2, 0);\r\n</div><br>\r\n\r\n\r\n<p>In other words, it is multiplying a region a of 60 bytes (30 words) by the constant 0x1234 in <em> GF(2<sup>16</sup>),</em> and placing\r\nthe result into <em>b.</em> The pointers <em>a</em> and <em>b</em> have been set up so that they are not multiples of 16. The first line of output\r\nprints <em>a</em> and <em>b:</em></p><br>\r\n\r\na: 0x10010008c b: 0x10010015c <br><br>\r\n\r\nAs described in Section 5, the regions of memory are split into three parts:\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n\r\n6 &nbsp &nbsp  <em>  FURTHER INFORMATION ON OPTIONS AND ALGORITHMS     </em>   <span id=\"index_number\">35  </span> <br><br><br>\r\n\r\n\r\n<ol>\r\n<li> 4 bytes starting at 0x1001008c / 0x10010015c. </li>\r\n<li> 32 bytes starting at 0x10010090 / 0x100100160. </li>\r\n<li> 24 bytes starting at 0x100100b0 / 0x100100180. </li>\r\n\r\n</ol>\r\n\r\n\r\n<p>In the first and third parts, the bytes are laid out according to the standard mapping. However, the second part is\r\nsplit into two 16-byte regions- one that holds the high bytes of each word and one that holds the low bytes. To help\r\nillustrate, the remainder of the output prints the 30 words of <em>a</em> and <em>b</em> as they appear in memory, and then the 30 return\r\nvalues of <b>extract_word.w32():</b> </p><br>\r\n\r\n<div id=\"number_spacing\">\r\n<table cellspacing=\"6\" style=\"text-align:right\">\r\n\r\n<tr>\r\n<td></td> <td> 1</td> <td> 2 </td> <td> 3 </td> <td> 4</td> <td> 5 </td> <td> 6 </td> <td> 7</td> <td> 8 </td> <td> 9</td> </tr>\r\n<tr>\r\n<td>a:</td><td> 640b</td> <td> 07e5</td> <td> 2fba </td> <td> ce5d </td> <td> f1f9</td> <td> 3ab8</td> <td> c518 </td> <td> 1d97</td> <td> 45a7</td>\r\n <td> 0160</td> </tr>\r\n \r\n<tr><td>b:</td> <td>1ba3</td><td> 644e</td> <td> 84f8</td> <td> be3c</td> <td> 4318</td> <td> 4905</td> <td> b2fb </td> <td> 46eb </td> <td> ef01 </td>\r\n <td>a503</td> \r\n</tr>\r\n</table> \r\n <br><br>\r\n<table cellspacing=\"6\" style=\"text-align:right\">\r\n\r\n<tr>\r\n<td> 10</td> <td> 11 </td> <td> 12</td> <td> 13</td> <td> 14 </td> <td> 15 </td> <td> 16</td> <td> 17</td> <td>18</td> <td> 19 </td></tr>\r\n<tr>\r\n<td>a:</td><td> 3759</td> <td> b107</td> <td> 9660 </td> <td> 3fde </td> <td> b3ea</td> <td> 8a53</td> <td> 75ff </td> <td> 46dc</td> <td> c504</td>\r\n <td> 72c2</td> </tr>\r\n \r\n<tr><td>b:</td> <td>da27</td><td> e166</td> <td> a0d2</td> <td> b3a2</td> <td> 1699</td> <td> 3a3e</td> <td> 47fb </td> <td> 39af </td> <td> 1314 </td>\r\n <td>8e76</td> \r\n</tr>\r\n</table> \r\n\r\n<table cellspacing=\"6\" style=\"text-align:right\">\r\n<br><br>\r\n<tr>\r\n<td> 20</td> <td> 21 </td> <td> 22</td> <td> 23</td> <td> 24 </td> <td> 25 </td> <td> 26</td> <td> 27</td> <td>28</td> <td> 29 </td></tr>\r\n<tr>\r\n<td>a:</td><td> b469</td> <td> 1b97</td> <td> e91d </td> <td> 1dbc </td> <td> 131e</td> <td> 47e0</td> <td> c11a </td> <td> 7f07</td> <td> 76e0</td>\r\n <td> fe86</td> </tr>\r\n \r\n<tr><td>b:</td> <td>937c</td><td> a5db</td> <td> 01b7</td> <td> 7f5f</td> <td> 8974</td> <td> 05e1</td> <td> cff3 </td> <td> a09c </td> <td> de3c </td>\r\n <td>4ac0</td> \r\n</tr>\r\n</table> \r\n<br><br>\r\n<table cellspacing=\"6\">\r\n\r\n\r\n<tr><td>Word</td><td> 0:</td> <td>0x640b </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x1ba3 Word 15:</td> <td>0x4575 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0xef47</td></tr>     \r\n<tr><td>Word</td> <td> 1:</td> <td>0x07e5 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x644e Word 16:</td> <td>0x60dc </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x03af</td></tr>\r\n<tr><td>Word</td> <td> 2:</td> <td>0xba59 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0xf827 Word 17:</td> <td>0x0146 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0xa539 </td> </tr>\r\n<tr><td>Word</td> <td>3:</td> <td>0x2f37 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x84da Word 18:</td> <td>0xc504 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x1314 </td> </tr>\r\n<tr><td>Word</td> <td>4:</td> <td>0x5d07 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x3c66 Word 19:</td> <td>0x72c2 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x8e76 </td> </tr>\r\n<tr><td>Word</td> <td>5:</td> <td>0xceb1 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0xbee1 Word 20:</td> <td>0xb469 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x937c </td> </tr>\r\n<tr><td>Word</td> <td>6:</td> <td>0xf960 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x18d2 Word 21:</td> <td>0x1b97 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0xa5db </td> </tr>\r\n<tr><td>Word</td> <td>7:</td> <td>0xf196 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x43a0 Word 22:</td> <td>0xe91d </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x01b7 </td> </tr>\r\n<tr><td>Word</td> <td>8:</td> <td>0xb8de </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x05a2 Word 23:</td> <td>0x1dbc </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x7f5f </td> </tr>\r\n<tr><td>Word</td> <td>9:</td> <td>0x3a3f </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x49b3 Word 24:</td> <td>0x131e </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x8974 </td> </tr>\r\n<tr><td>Word</td> <td>10:</td> <td>0x18ea </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0xfb99 Word 25:</td> <td>0x47e0 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x05e1 </td> </tr>\r\n<tr><td>Word</td> <td>11:</td> <td>0xc5b3 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0xb216 Word 26:</td> <td>0xc11a </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0xcff3  </td> </tr>\r\n<tr><td>Word</td> <td>12:</td> <td>0x9753 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0xeb3e Word 27:</td> <td>0x7f07 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0xa09c  </td> </tr>\r\n<tr><td>Word</td> <td>13:</td> <td>0x1d8a </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x463a Word 28:</td> <td>0x76e0 </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0xde3c  </td> </tr>\r\n<tr><td>Word</td> <td>14:</td> <td>0xa7ff </td><td>*</td> <td>0x1234</td> <td>=</td> <td>0x01fb Word 29:</td> <td>0xfe86 <td>*</td> <td>0x1234</td> <td>=</td> <td>0x4ac0 </td> </tr>\r\n\r\n</table>\r\n</div>\r\n<br>\r\nIn the first region are words 0 and 1, which are identical to how they appear in memory: 0x640b and 0x07e5. In\r\nthe second region are words 2 through 17. These words are split among the two sixteen-byte regions. For example,\r\nword 2, which <b>extract_word()</b> reports is 0xba59, is constructed from the low byte in word 2 (0xba) and the low byte\r\nin word 10 (0x59). Since 0xba59 * 0x1234 = 0xf827, we see that the low byte in word 2 of <em> b </em> is 0xf8, and the low byte\r\nin word 10 is 0x27.\r\n<p>When we reach word 22, we are in the third region of memory, and words are once again identical to how they\r\nappear in memory.</p>\r\n\r\n<p>While this is confusing, we stress that that so long as you call <b>multiply_region()</b> with pointers of the same alignment\r\nand regions of the same size, your results with <b>ALTMAP</b> will be consistent. If you call it with pointers of </p>\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n\r\n7 &nbsp &nbsp  <em>  FURTHER INFORMATION ON OPTIONS AND ALGORITHMS     </em>   <span id=\"index_number\">36  </span> <br><br><br>\r\n\r\ndifferent alignments, or with different region sizes, then the results will not be consistent. To reiterate, if you don't use\r\n<b>ALTMAP,</b> you don't have to worry about any of this - words will always be laid out contiguously in memory.\r\n<p>\r\nWhen <em>w</em> = 32, the middle region is a multiple of 64, and each word in the middle region is broken into bytes, each\r\nof which is in a different 16-byte region. When <em>w</em> = 64, the middle region is a multiple of 128, and each word is\r\nstored in eight 16-byte regions. And finally, when<em>w</em> = 128, the middle region is a multiple of 128, and each word is\r\nstored in 16 16-byte regions.</p><br>\r\n\r\n<h3>7.9.2 &nbsp Alternate mappings with \"COMPOSITE\" </h3>\r\n\r\nWith <b>\"COMPOSITE,\"</b> the alternate mapping divides the middle region in half. The lower half of each word is stored\r\nin the first half of the middle region, and the higher half is stored in the second half. To illustrate, gf example 6\r\nperforms the same example as gf example 5, except it is using <b>\"COMPOSITE\"</b> in GF((2<sup>16</sup>)<sup>2</sup>), and it is multiplying\r\na region of 120 bytes rather than 60. As before, the pointers are not aligned on 16-bit quantities, so the region is broken\r\ninto three regions of 4 bytes, 96 bytes, and 20 bytes. In the first and third region, each consecutive four byte word is a\r\nword in <em>GF(2<sup>32</sup>).</em> For example, word 0 is 0x562c640b, and word 25 is 0x46bc47e0. In the middle region, the low two\r\nbytes of each word come from the first half, and the high two bytes come from the second half. For example, word 1\r\nas reported by <b>extract_word()</b> is composed of the lower two bytes of word 1 of memory (0x07e5), and the lower two\r\nbytes of word 13 (0x3fde). The product of 0x3fde07e5 and 0x12345678 is 0x211c880d, which is stored in the lower\r\ntwo bytes of words 1 and 13 of <em>b.</em><br><br>\r\n\r\na: 0x10010011c b: 0x1001001ec\r\n\r\n<br><br>\r\n\r\n<div id=\"number_spacing\">\r\n<table cellspacing=\"6\" style=\"text-align:right\">\r\n\r\n<tr>\r\n<td></td> <td> 1</td> <td> 2 </td> <td> 3 </td> <td> 4</td> <td> 5 </td> <td> 6 </td> <td> 7</td> <td> 8 </td> <td> 9</td> </tr>\r\n<tr>\r\n<td>a:</td><td> 562c640b</td> <td> 959407e5</td> <td> 56592fba </td> <td> cbadce5d </td> <td> 1d1cf1f9</td> <td> 35d73ab8</td> <td> 6493c518 </td> <td> b37c1d97</td> \r\n<td> 8e4545a7</td>\r\n <td> c0d80160</td> </tr>\r\n \r\n<tr><td>b:</td> <td>f589f36c</td><td> f146880d</td> <td> 74f7b349</td> <td> 7ea7c5c6</td> <td> 34827c1a</td> <td> 93cc3746</td> <td> bfd9288b </td>\r\n <td> 763941d1 </td> \r\n<td> bcd33a5d </td>\r\n <td>da695e64</td> \r\n</tr>\r\n</table> \r\n\r\n\r\n<br><br>\r\n<table cellspacing=\"6\" style=\"text-align:right\">\r\n\r\n<tr>\r\n<td> 10</td> <td> 11 </td> <td> 12</td> <td> 13</td> <td> 14 </td> <td> 15 </td> <td> 16</td> <td> 17</td> <td>18</td> <td> 19 </td></tr>\r\n<tr>\r\n<td>a:</td><td> 965b3759</td> <td> cb3eb107</td> <td> 1b129660 </td> <td> 95a33fde </td> <td> 95a7b3ea</td> <td> d16c8a53</td> <td> 153375ff </td> \r\n<td> f74646dc</td> <td> 35aac504</td>\r\n <td> 98f972c2</td> </tr>\r\n \r\n<tr><td>b:</td> <td>fd70f125</td><td> 3274fa8f</td> <td> d9dd34ee</td> <td> c01a211c</td> <td> d4402403</td> <td> 8b55c08b</td> <td> da45f0ad </td> \r\n<td> 90992e18 </td> <td> b65e0902 </td>\r\n <td>d91069b5</td> \r\n</tr>\r\n</table> \r\n\r\n\r\n<table cellspacing=\"6\" style=\"text-align:right\">\r\n<br><br>\r\n<tr>\r\n<td> 20</td> <td> 21 </td> <td> 22</td> <td> 23</td> <td> 24 </td> <td> 25 </td> <td> 26</td> <td> 27</td> <td>28</td> <td> 29 </td></tr>\r\n<tr>\r\n<td>a:</td><td> 5509b469</td> <td> 7f8a1b97</td> <td> 3472e91d </td> <td> 9ee71dbc </td> <td> de4e131e</td> <td> 46bc47e0</td> <td> 5bc9c11a </td>\r\n <td> 931d7f07</td> <td> c85cfe86</td>\r\n <td> fe86</td> </tr>\r\n \r\n<tr><td>b:</td> <td>fc92b8f5</td><td> edd59668</td> <td> b4bc0d90</td> <td> a679e4ce</td> <td> 1a98f7d0</td> <td> 6038765f</td> <td> b2ff333f </td> <td> e7937e49 </td> \r\n<td> fa5a5867 </td>\r\n <td>79c00ea2</td> \r\n</tr>\r\n</table> \r\n<br><br>\r\n\r\n\r\n<table cellspacing=\"6\" style=\"text-align:right\">\r\n\r\n\r\n<tr><td>Word</td><td> 0:</td> <td>0x562c640b </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0xf589f36c Word 15:</td> <td>0xb46945a7 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0xb8f53a5d</td></tr>     \r\n<tr><td>Word</td> <td> 1:</td> <td>0x3fde07e5 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0x211c880d Word 16:</td> <td>0x55098e45 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0xfc92bcd3</td></tr>\r\n<tr><td>Word</td> <td> 2:</td> <td>0x95a39594 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0xc01af146 Word 17:</td> <td>0x1b970160 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0x96685e64 </td> </tr>\r\n<tr><td>Word</td> <td>3:</td> <td>0xb3ea2fba </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0x2403b349 Word 18:</td> <td>0x7f8ac0d8 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0xedd5da69 </td> </tr>\r\n<tr><td>Word</td> <td>4:</td> <td>0x95a75659 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0xd44074f7 Word 19:</td> <td>0xe91d3759 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0x0d90f125 </td> </tr>\r\n<tr><td>Word</td> <td>5:</td> <td>0x8a53ce5d </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0xc08bc5c6 Word 20:</td> <td>0x3472965b </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0xb4bcfd70 </td> </tr>\r\n<tr><td>Word</td> <td>6:</td> <td>0xd16ccbad </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0x8b557ea7 Word 21:</td> <td>0x1dbcb107 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0xe4cefa8f </td> </tr>\r\n<tr><td>Word</td> <td>7:</td> <td>0x75fff1f9 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0xf0ad7c1a Word 22:</td> <td>0x9ee7cb3e </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0xa6793274 </td> </tr>\r\n<tr><td>Word</td> <td>8:</td> <td>0x15331d1c </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0xda453482 Word 23:</td> <td>0x131e9660 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0xf7d034ee </td> </tr>\r\n<tr><td>Word</td> <td>9:</td> <td>0x46dc3ab8 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0x2e183746 Word 24:</td> <td>0xde4e1b12 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0x1a98d9dd </td> </tr>\r\n<tr><td>Word</td> <td>10:</td> <td>0xf74635d7 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0x909993cc Word 25:</td> <td>0x46bc47e0 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0x6038765f </td> </tr>\r\n<tr><td>Word</td> <td>11:</td> <td>0xc504c518 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0x0902288b Word 26:</td> <td>0x5bc9c11a </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0xb2ff333f  </td> </tr>\r\n<tr><td>Word</td> <td>12:</td> <td>0x35aa6493 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0xb65ebfd9 Word 27:</td> <td>0x931d7f07 </td><td>*</td> <td>0x12345678</td> <td>=</td> <td>0xe7937e49  </td> </tr>\r\n\r\n</table>\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n\r\n8 &nbsp &nbsp  <em>  THREAD SAFETY     </em>   <span id=\"index_number\">37  </span> <br><br><br>\r\n<div id=\"number_spacing\">\r\n<table cellpadding=\"6\" cellspacing=\"0\">\r\n<tr>\r\n<td>Word 13:</td> <td> 0x72c21d97</td> <td> *</td> <td> 0x12345678</td> <td> =</td> <td> 0x69b541d1</td> <td> Word 28:</tD>\r\n\r\n<td> 0xd40676e0 </td> <td> * </td> <td> 0x12345678 </td> <td> = </td> <td> 0xfa5a5867 </td> </tr>\r\n\r\n<tr><td>Word 14:</td> <td> 0x98f9b37c</td> <td> * </td> <td> 0x12345678 </td> <td> = </td> <td> 0xd9107639</td> <td> Word 29:</td>\r\n<td> 0xc85cfe86</td> <td>*</td> <td> 0x12345678</td> <td> =</td> <td> 0x79c00ea2</td></tr>\r\n\r\n</table>\r\n</div><br>\r\n\r\n\r\n<p>\r\nAs with <b>\"SPLIT,\"</b> using <b>multiply_region()</b> with <b>\"COMPOSITE\"</b> and <b>\"ALTMAP\"</b> will be consistent only if the\r\nalignment of pointers and region sizes are identical. </p>\r\n\r\n\r\n<h3>7.9.3 The mapping of \"CAUCHY\" </h3>\r\n\r\nWith <b>\"CAUCHY,\"</b> the region is partitioned into <em>w</em> subregions, and each word in the region is broken into <em>w</em> bits,\r\neach of which is stored in a different subregion. To illustrate, <b>gf_example_7</b> multiplies a region of three bytes by 5\r\nin <em>GF(2<sup>3</sup>)</em> using <b>\"CAUCHY:\"</b><br><br>\r\n\r\n<div id=\"number_spacing\">\r\n\r\nUNIX> gf_example_7 <br>\r\na: 0x100100190 b: 0x1001001a0 <br><br>\r\na: 0x0b 0xe5 0xba <br>\r\nb: 0xee 0xba 0x0b <br><br>\r\na bits: 00001011 11100101 10111010 <br>\r\nb bits: 11101110 10111010 00001011<br><br>\r\nWord 0: 3 * 5 = 4 <br>\r\nWord 1: 5 * 5 = 7 <br>\r\nWord 2: 2 * 5 = 1 <br>\r\nWord 3: 5 * 5 = 7 <br>  \r\nWord 4: 4 * 5 = 2 <br>\r\nWord 5: 6 * 5 = 3 <br>\r\nWord 6: 2 * 5 = 1 <br>\r\nWord 7: 6 * 5 = 3 <br>\r\nUNIX><br><br> </div>\r\n<p>\r\n\r\nThe program prints the three bytes of a and b in hexadecimal and in binary. To see how words are broken up,\r\nconsider word 0, which is the lowest bit of each of the three bytes of a (and b). These are the bits 1, 1 and 0 in a, and\r\n0, 0, and 1 in b. Accordingly, the word is 3 in a, and 3*5 = 4 in b. Similarly, word 7 is the high bit in each byte: 0, 1, 1\r\n(6) in a, and 1, 1, 0 (3) in b.</p>\r\n<p>With <b>\"CAUCHY,\" multiply_region()</b>may be implemented exclusively with XOR operations. Please see [BKK<sup>+</sup>95]\r\nfor more information on the motivation behind <b>\"CAUCHY.\"</b> </p>\r\n\r\n<h2>8 &nbsp Thread Safety </h2>\r\n\r\nOnce you initialize a <b>gf_t,</b> you may use it wontonly in multiple threads for all operations except for the ones below.\r\nWith the implementations listed below, the scratch space in the <b>gf_t</b> is used for temporary tables, and therefore you\r\ncannot call <b>region_multiply,</b> and in some cases <b>multiply</b> from multiple threads because they will overwrite each\r\nothers' tables. In these cases, if you want to call the procedures from multiple threads, you should allocate a separate\r\ngf_t for each thread:\r\n<ul>\r\n<li>\r\n All \"GROUP\" implementations are not thread safe for either <b>region_multiply()</b> or <b> multiply().</b> Other than\r\n<b>\"GROUP,\" multiply() </b> is always thread-safe.\r\n\r\n</li>\r\n</ul>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n\r\n9 &nbsp &nbsp  <em>  LISTING OF PROCEDURES     </em>   <span id=\"index_number\">38  </span> <br><br><br>\r\n<ul>\r\n<li>\r\n\r\nFor <em>w </em> = 4, <b>region_multiply.w32()</b> is unsafe in in \"-m TABLE -r QUAD -r LAZY.\" </li><br>\r\n<li> For <em>w </em> = 8, <b> region_multiply.w32()</b> is unsafe in in \"-m TABLE -r DOUBLE -r LAZY.\"</li><br>\r\n<li> For <em>w </em> = 16, <b>region_multiply.w32() </b> is unsafe in in \"-m TABLE.\"</li><br>\r\n<li> For <em>w </em> &#8712 {32, 64, 128}, all <b>\"SPLIT\"</b> implementations are unsafe for <b>region_multiply().</b> This means that if the\r\ndefault uses <b>\"SPLIT\"</b> (see Table 1 for when that occurs), then <b>region_multiply()</b> is not thread safe.</li><br>\r\n<li> The <b>\"COMPOSITE\"</b> operations are only safe if the implementations of the underlying fields are safe.</li>\r\n</ul>\r\n\r\n<h2>9 &nbspListing of Procedures </h2>\r\n\r\nThe following is an alphabetical listing of the procedures, data types and global variables for users to employ in\r\nGF-complete.<br>\r\n\r\n<ul>\r\n<li> <b>GF_W16_INLINE_DIV()</b> in <b>gf_complete.h:</b> This is a macro for inline division when <em>w </em> = 16. See section 7.1.</li><br>\r\n<li> <b>GF_W16_INLINE_MULT()</b> in <b>gf_complete.h:</b> This is a macro for inline multiplication when <em>w </em> = 16. See\r\nsection 7.1.</li><br>\r\n<li> <b>GF_W4_INLINE_MULTDIV()</b> in <b>gf_complete.h:</b> This is a macro for inline multiplication/division when <em>w </em> =\r\n4. See section 7.1.</li><br>\r\n\r\n<li> <b>GF_W8_INLINE_MULTDIV()</b> in <b>gf_complete.h:</b> This is a macro for inline multiplication/division when <em>w </em> =\r\n8. See section 7.1.</li><br>\r\n<li> <b>MOA_Fill_Random_Region()</b> in <b>gf_rand.h:</b> Fills a region with random numbers.</li><br>\r\n<li> <b>MOA_Random_128()</b> in <b>gf_rand.h:</b> Creates a random 128-bit number.</li><br>\r\n<li> <b>MOA_Random_32()</b> in <b>gf_rand.h:</b> Creates a random 32-bit number. </li><br>\r\n<li> <b>MOA_Random_64()</b> in <b>gf_rand.h:</b> Creates a random 64-bit number. </li><br>\r\n<li> <b>MOA_Random_W()</b> in <b>gf_rand.h:</b> Creates a random w-bit number, where <em>w </em> &#8804 32. </li><br>\r\n<li> <b>MOA_Seed()</b> in <b>gf_rand.h:</b> Sets the seed for the random number generator. </li><br>\r\n<li> <b>gf_errno</b> in <b>gf_complete.h:</b> This is to help figure out why an initialization call failed. See section 6.1.</li><br>\r\n<li> <b>gf_create_gf_from_argv()</b> in <b>gf method.h:</b> Creates a gf t using C style argc/argv. See section 6.1.1. </li><br>\r\n<li> <b>gf_division_type_t</b> in <b>gf_complete.h:</b> the different ways to specify division when using <b>gf_init_hard().</b> See \r\nsection 6.4. </li><br>\r\n<li> <b>gf_error()</b> in <b>gf_complete.h:</b> This prints out why an initialization call failed. See section 6.1. </li><br>\r\n\r\n<li> <b>gf_extract</b> in <b>gf_complete.h:</b> This is the data type of <b>extract_word()</b> in a gf t. See section 7.9 for an example\r\nof how to use extract word().</li>\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n\r\n9 &nbsp &nbsp  <em>  LISTING OF PROCEDURES     </em>   <span id=\"index_number\">39  </span> <br><br><br>\r\n<ul>\r\n<li>\r\n<b>gf_free()</b> in <b>gf_complete.h:</b> If <b>gf_init easy(), gf_init hard()</b> or <b>create_gf_from_argv()</b> allocated memory, this\r\nfrees it. See section 6.4. </li>\r\n\r\n<li> <b>gf_func_a_b</b> in <b>gf_complete.h:</b> This is the data type of <b>multiply()</b> and <b>divide()</b> in a gf_t. See section 4.2 for\r\nexamples of how to use <b>multiply()</b> and <b>divide()</b></li><br>\r\n\r\n<li> <b>gf_func_a_b</b> in <b>gf_complete.h:</b> This is the data type of <b>multiply()</b> and <b>divide()</b> in a <b>gf_t.</b> See section 4.2 for\r\nexamples of how to use <b>multiply()</b> and <b>divide()</b></li><br>\r\n\r\n<li> <b>gf_func_a</b> in <b>gf_complete.h:</b> This is the data type of <b>inverse()</b> in a <b>gf_t</b></li><br>\r\n\r\n<li> <b>gf_general_add()</b> in <b>gf_general.h:</b> This adds two <b>gf_general_t's </b></li><br>\r\n\r\n<li> <b>gf_general_divide()</b> in <b>gf_general.h:</b> This divides two <b>gf_general t's </b></li><br>\r\n\r\n<li> <b>gf_general_do_region_check() </b> in <b>gf_general.h:</b> This checks a region multiply of <b>gf_general_t's </b></li><br>\r\n\r\n<li> <b>gf_general_do_region_multiply() </b> in <b>gf_general.h:</b> This does a region multiply of <b>gf_general_t's </b></li><br>\r\n\r\n<li> <b>gf_general_do_single_timing_test()</b> in <b>gf_general.h:</b> Used in <b>gf_time.c </b></li><br>\r\n\r\n<li> <b>gf_general_inverse() </b> in <b>gf_general.h:</b> This takes the inverse of a <b>gf_general_t </b></li><br>\r\n\r\n<li> <b>gf_general_is_one() </b> in <b>gf_general.h:</b> This tests whether a <b>gf_general_t </b> is one</li><br>\r\n\r\n<li> <b>gf_general_is_two() </b> in <b>gf_general.h:</b> This tests whether a <b>gf_general_t  </b>is two</li><br>\r\n\r\n<li> <b>gf_general_is_zero() </b> in <b>gf_general.h:</b> This tests whether a <b>gf_general_t </b> is zero</li><br>\r\n\r\n<li> <b>gf_general_multiply() </b> in <b>gf_general.h:</b> This multiplies two <b>gf_general_t's.</b> See the implementation of gf_mult.c\r\n\r\nfor an example</li><br>\r\n<li> <b>gf_general_s_to_val() </b> in <b>gf_general.h:</b> This converts a string to a <b>gf_general t.</b> See the implementation of\r\ngf_mult.c for an example</li><br>\r\n<li> <b>gf_general_set_one() </b> in <b>gf_general.h:</b> This sets a <b>gf_general_t</b> to one</li><br>\r\n<li> <b>gf_general_set_random()</b> in <b>gf_general.h:</b> This sets a <b>gf_general_t </b> to a random number</li><br>\r\n<li> <b>gf_general_set_two() in </b><b>gf_general.h:</b> This sets a <b>gf_general_t </b> to two</li><br>\r\n<li> <b>gf_general_set_up_single_timing_test() </b> in <b>gf_general.h:</b> Used in <b>gf_time.c</b></li><br>\r\n<li> <b>gf_general_set_zero() in </b><b>gf_general.h:</b> This sets a <b>gf_general_t_to_zero</b></li><br>\r\n<li> <b>gf_general_t_in .</b><b>gf_general.h:</b> This is a general data type for all values of w. See the implementation of gf_mult.c\r\nfor examples of using these</li><br>\r\n<li> <b>gf_general_val_to_s()</b> in<b>gf_general.h:</b> This converts a <b>gf_general_t </b> to a string. See the implementation of\r\n<b>gf_mult.c</b> for an example</li><br>\r\n\r\n<li> <b>gf_init_easy()</b> in <b>gf_complete.h:</b> This is how you initialize a default <b>gf_t.</b> See 4.2 through 4.5 for examples of\r\ncalling <b>gf_init_easy()</b></li><br>\r\n</ul>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n\r\n\r\n9 &nbsp &nbsp  <em>  LISTING OF PROCEDURES     </em>   <span id=\"index_number\">40  </span> <br><br><br>\r\n\r\n<ul>\r\n\r\n<li><b>gf_init hard()</b> in <b>gf_complete.h: </b> This allows you to initialize a <b>gf_t</b> without using the defaults. See 6.4. We\r\nrecommend calling create <b>gf_from argv()</b> when you can, instead of <b>gf_ init_hard()</b></li><br>\r\n\r\n<li> <b>gf_ mult_type_t </b> in <b>gf_complete.h: </b> the different ways to specify multiplication when using <b>gf_init hard()</b>. See\r\nsection 6.4</li><br>\r\n\r\n<li> <b>gf_region_type_t</b> in <b>gf_complete.h: </b> the different ways to specify region multiplication when using <b>gf_init_hard()</b>.\r\nSee section 6.4</li><br>\r\n\r\n<li> <b>gf_region_in</b> <b>gf_complete.h: </b> This is the data type of <b>multiply_region()</b> in a <b>gf_t.</b> See section 4.3 for an example\r\nof how to use <b>multiply_region()</b></li><br>\r\n\r\n<li> <b>gf_scratch_size()</b> in <b>gf_complete.h: </b> This is how you calculate how much memory a <b>gf_t</b> needs. See section 6.4.</li><br>\r\n\r\n<li> <b>gf_size()</b> in <b>gf_complete.h: </b> Returns the memory consumption of a <b>gf_t.</b> See section 6.5.</li><br>\r\n\r\n<li> <b>gf_ val_128_t</b> in <b>gf_complete.h: </b> This is how you store a value where <em>w </em> &#8804 128. It is a pointer to two 64-bit\r\nunsigned integers. See section 4.4</li><br>\r\n\r\n\r\n<li> <b>gf_val_32_t</b> in <b>gf_ complete.h: </b> This is how you store a value where <em>w </em> &#8804 32. It is equivalent to a 32-bit unsigned\r\ninteger. See section 4.2</li><br>\r\n\r\n<li> <b>gf_ val_64_t</b> in <b>gf_complete.h: </b> This is how you store a value where <em>w </em> &#8804 64. It is equivalent to a 64-bit unsigned\r\ninteger. See section 4.5</li><br>\r\n\r\n<li> <b>gf_w16_get_div_alog_table()</b> in <b>gf_ complete.h: </b> This returns a pointer to an inverse logarithm table that can be\r\nused for inlining division when <em>w </em> = 16. See section 7.1</li><br>\r\n\r\n\r\n<li> <b>gf_w16_get_log_table()</b> in <b>gf_complete.h: </b> This returns a pointer to a logarithm table that can be used for inlining\r\nwhen <em>w </em> = 16. See section 7.1</li><br>\r\n\r\n\r\n<li> <b>gf_w16_get_mult_alog_table()</b> in <b>gf_complete.h: </b> This returns a pointer to an inverse logarithm table that can be\r\nused for inlining multiplication when <em>w </em> = 16. See section 7.1</li><br>\r\n\r\n\r\n<li> <b>gf_ w4 get div table()</b> in <b>gf_complete.h: </b> This returns a pointer to a division table that can be used for inlining\r\nwhen <em>w </em> = 4. See section 7.1</li><br>\r\n\r\n\r\n<li> <b>gf_w4_get_mult_table()</b> in <b>gf_complete.h: </b> This returns a pointer to a multiplication table that can be used for\r\ninlining when <em>w </em> = 4. See section 7.1</li><br>\r\n\r\n<li> <b>gf_w8_get_div_table()</b> in <b>gf_complete.h: </b> This returns a pointer to a division table that can be used for inlining\r\nwhen <em>w </em> = 8. See section 7.1</li><br>\r\n\r\n<li> <b>gf_w8_get_mult_table()</b> in <b>gf_complete.h: </b> This returns a pointer to a multiplication table that can be used for\r\ninlining when <em>w </em> = 8. See section 7.1</li><br>\r\n\r\n</ul>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n10 &nbsp &nbsp  <em>TROUBLESHOOTING </em>   <span id=\"index_number\">41  </span> <br><br><br>\r\n\r\n<ul>\r\n<li><b> SSE support.</b> Leveraging SSE instructions requires processor support as well as compiler support. For example,\r\nthe Mac OS 10.8.4 (and possibly earlier versions) default compile environment fails to properly compile\r\nPCLMUL instructions. This issue can be fixed by installing an alternative compiler; see Section 3 for details</li><br>\r\n\r\n<li> <b>Initialization segfaults.</b> You have to already have allocated your <b>gf_t</b> before you pass a pointer to it in\r\n<b>bgf_init_easy()</b>, <b>create_gf_ from_argv()</b>, or <b>bgf_ini_hard()</b></li><br>\r\n\r\n\r\n<li> <b>GF-Complete is slower than it should be.</b> Perhaps your machine has SSE, but you haven't specified the SSE\r\ncompilation flags. See section 3 for how to compile using the proper flags</li><br>\r\n\r\n\r\n<li> <b>Bad alignment.</b> If you get alignment errors, see Section 5</li><br>\r\n\r\n<li> <b>Mutually exclusive region types.</b> Some combinations of region types are invalid. All valid and implemented\r\ncombinations are printed by <b>bgf_methods.c </b></li><br>\r\n\r\n<li><b>Incompatible division types.</b> Some choices of multiplication type constrain choice of divide type. For example,\r\n<b>\"COMPOSITE\"</b> methods only allow the default division type, which divides by finding inverses (i.e.,\r\nneither <b>\"EUCLID\"</b> nor <b>\"MATRIX\"</b> are allowed). For each multiplication method printed by <b>gf_methods.c,</b> the\r\ncorresponding valid division types are also printed</li><br>\r\n\r\n\r\n<li><b> Arbitrary \"GROUP\" arguments.</b> The legal arguments to <b>\"GROUP\"</b> are specified in section 7.5</li><br>\r\n\r\n<li> <b> Arbitrary \"SPLIt\" arguments.</b> The legal arguments to <b>\"SPLIt\"</b> are specified in section 7.4</li><br>\r\n\r\n<li> <b>Threading problems.</b> For threading questions, see Section 8</li><br>\r\n\r\n<li> <b>No default polynomial.</b> If you change the polynomial in a base field using <b>\"COMPOSITE,\"</b> then unless it is\r\na special case for which GF-Complete finds a default polynomial, you'll need to specify the polynomial of the\r\ncomposite field too. See 7.8.2 for the fields where GF-Complete will support default polynomials</li><br>\r\n<li> Encoding/decoding with different fields. Certain fields are not compatible. Please see section 7.2 for an\r\nexplanation</li><br>\r\n\r\n\r\n<li> <b>\"ALTMAP\" is confusing.</b> We agree. Please see section 7.9 for more explanation.\r\n\r\n<li> <b>I used \"ALTMAP\" and it doesn't appear to be functioning correctly.</b> With 7.9, the size of the region and\r\nits alignment both matter in terms of how <b>\"ALTMAP\"</b> performs <b>multiply_region()</b>. Please see section 7.9 for\r\ndetailed explanation</li><br>\r\n\r\n<li><b>Where are the erasure codes?.</b> This library only implements Galois Field arithmetic, which is an underlying\r\ncomponent for erasure coding. Jerasure will eventually be ported to this library, so that you can have fast erasure\r\ncoding</li><br>\r\n</ul>\r\n<h2>11 &nbsp &nbsp Timings </h2>\r\n\r\nWe don't want to get too detailed with timing, because it is quite machine specific. However, here are the timings on\r\nan Intel Core i7-3770 CPU running at 3.40 GHz, with 4 &#215 256 KB L2 caches and an 8MB L3 cache. All timings are\r\nobtained with <b>gf_time</b> or <b>gf_inline_time,</b> in user mode with the machine dedicated solely to running these jobs.\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n10 &nbsp &nbsp  <em>TROUBLESHOOTING </em>   <span id=\"index_number\">41  </span> <br><br><br>\r\n\r\n<div class=\"image-cell_5\"> </div>\r\n<center>Figure 4: Speed of doing single multiplications for w &#8712 {4, 8, 16}. </center>\r\n<h2>11.1 &nbsp Multiply() </h2>\r\n\r\nThe performance of <b>multiply()</b> is displayed in Figures 4 for w &#8712 {4, 8, 16} and 5 for w &#8712 {32, 64, 128}. These\r\nnumbers were obtained by calling <b>gf_time</b> with the size and iterations both set to 10240. We plot the speed in megaops\r\nper second.\r\n\r\n<p>As would be anticipated, the inlined operations (see section 7.1) outperform the others. Additionally, in all\r\ncases with the exception of <em>w</em> = 32, the defaults are the fastest performing implementations. With w = 32,\r\n\"CARRY FREE\" is the fastest with an alternate polynomial (see section 7.7). Because we require the defaults to\r\nuse a \"standard\" polynomial, we cannot use this implementation as the default. </p>\r\n\r\n<h2>11.2 &nbsp Divide() </h2>\r\n\r\nFor the  <b>\"TABLE\"</b> and <b>\"LOG\"</b> implementations, the performance of division is the same as multiplication. This means\r\nthat for w &#8712 {4, 8, 16}, it is very fast indeed. For the other implementations, division is implemented with Euclid's\r\nmethod, and is several factors slower than multiplication.\r\nIn Figure 6, we plot the speed of a few implementations of the larger word sizes. Compared to the <b>\"TABLE\"</b> and\r\n<b>\"LOG\"</b> implemenations for the smaller word sizes, where the speeds are in the hundreds of mega-ops per second,\r\nthese are very slow. Of note is the <b>\"COMPOSITE\"</b> implementation for <em>w</em> = 32, which is much faster than the others\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n10 &nbsp &nbsp  <em>TROUBLESHOOTING </em>   <span id=\"index_number\">43  </span> <br><br><br>\r\n\r\n<div class=\"image-cell_6\"> </div>\r\n\r\n<center>Figure 5: Speed of doing single multiplications for w &#8712 {32, 64, 128}. </center><br>\r\n\r\nbecause it uses a special application of Euclid's method, which relies on division in <em>GF(2<sup>16</sup>),</em> which is very fast.<br><br>\r\n\r\n<h3>11.3 &nbsp Multiply_Region() </h2>\r\n\r\nTables 3 through 8 show the performance of the various region operations. It should be noted that for <em>GF(2<sup>16 </sup>) </em>\r\nthrough <em>GF(2<sup>128</sup>),</em> the default is not the fastest implementation of <b>multiply_region().</b> The reasons for this are outlined\r\nin section 6\r\n<p>\r\nFor these tables, we performed 1GB worth of <b>multiply_region()</b> calls for all regions of size 2i bytes for 10 &#8804 i &#8804\r\n30. In the table, we plot the fastest speed obtained.</p>\r\n<p>We note that the performance of <b>\"CAUCHY\"</b> can be improved with techniques from [LSXP13] and [PSR12].</p>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n<em>REFERENCES </em>   <span id=\"index_number\">44  </span> <br><br><br>\r\n\r\n<div class=\"image-cell_7\"> </div>\r\n\r\n<center>Figure 6: Speed of doing single divisions for w &#8712 {32, 64, 128}. </center><br>\r\n\r\n<center>\r\n<div id=\"data2\">\r\n<table cellpadding=\"2\" cellspacing=\"0\" style=\"text-align:center;font-size:19px\">\r\n\r\n<tr><th>Method</td> <th>Speed (MB/s)</td> </tr>\r\n\r\n<tr><td>-m TABLE (Default) -</td> <td>11879.909</td> </tr>\r\n<tr><td>-m TABLE -r CAUCHY -</td> <td>9079.712</td> </tr>\r\n<tr><td>-m BYTWO b -</td> <td>5242.400</td> </tr>\r\n<tr><td>-m BYTWO p -</td> <td>4078.431</td> </tr>\r\n<tr><td>-m BYTWO b -r NOSSE -</td> <td>3799.699</td> </tr>\r\n<tr><td>-m TABLE -r QUAD -</td> <td>3014.315</td> </tr>\r\n\r\n<tr><td>-m TABLE -r DOUBLE -</td> <td>2253.627</td> </tr>\r\n<tr><td>-m TABLE -r NOSSE -</td> <td>2021.237</td> </tr>\r\n<tr><td>-m TABLE -r NOSSE -</td> <td>1061.497</td> </tr>\r\n<tr><td>-m LOG -</td> <td>503.310</td> </tr>\r\n\r\n\r\n<tr><td>m SHIFT -</td> <td>157.749</td> </tr>\r\n<tr><td>-m CARRY FREE -</td> <td>86.202</td> </tr>\r\n</div>\r\n</table> <br><br>\r\n</div> </center>\r\n<center>Table 3: Speed of various calls to <b>multiply_region()</b> for <em>w</em> = 4. </center>\r\n\r\n<h3>References </h3>\r\n\r\n[Anv09] H. P. Anvin. The mathematics of RAID-6.<a href=\"\"> http://kernel.org/pub/linux/kernel/people/hpa/\r\nraid6.pdf,</a> 2009.<br><br>\r\n\r\n[BKK<sup>+</sup>95] J. Blomer, M. Kalfane, M. Karpinski, R. Karp, M. Luby, and D. Zuckerman. An XOR-based erasureresilient\r\ncoding scheme. Technical Report TR-95-048, International Computer Science Institute, August\r\n1995. <br><br>\r\n\r\n[GMS08] K. Greenan, E. Miller, and T. J. Schwartz. Optimizing Galois Field arithmetic for diverse processor\r\narchitectures and applications. In MASCOTS 2008: <em>16th IEEE Symposium on Modeling, Analysis and\r\nSimulation of Computer and Telecommunication Systems,</em> Baltimore, MD, September 2008.<br><br>\r\n\r\n\r\n[GP97] S. Gao and D. Panario. Tests and constructions of irreducible polynomials over finite fields. In <em> Foundations\r\nof Computational Mathematics,</em> pages 346–361. Springer Verlag, 1997.\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n<em>REFERENCES </em>   <span id=\"index_number\">45  </span> <br><br><br>\r\n\r\n\r\n<center>\r\n<div id=\"data2\">\r\n<table cellpadding=\"2\" cellspacing=\"0\" style=\"text-align:center;font-size:19px\">\r\n\r\n<tr><th>Method</td> <th>Speed (MB/s)</td> </tr>\r\n<tr><td>-m SPLIT 8 4 (Default)</td> <td>13279.146</td> </tr>\r\n<tr><td>-m COMPOSITE 2 - -r ALTMAP -</td> <td>5516.588</td> </tr>\r\n<tr><td>-m TABLE -r CAUCHY -</td> <td>4968.721</td> </tr>\r\n<tr><td>-m BYTWO b -</td> <td>2656.463</td> </tr>\r\n<tr><td>-m TABLE -r DOUBLE -</td> <td>2561.225</td> </tr>\r\n<tr><td>-m TABLE -</td> <td>1408.577</td> </tr>\r\n\r\n<tr><td>-m BYTWO b -r NOSSE -</td> <td>1382.409</td> </tr>\r\n<tr><td>-m BYTWO p -</td> <td>1376.661</td> </tr>\r\n<tr><td>-m LOG ZERO EXT -</td> <td>1175.739</td> </tr>\r\n<tr><td>-m LOG ZERO -</td> <td>1174.694</td> </tr>\r\n\r\n\r\n<tr><td>-m LOG -</td> <td>997.838</td> </tr>\r\n<tr><td>-m SPLIT 8 4 -r NOSSE -</td> <td>885.897</td> </tr>\r\n\r\n\r\n<tr><td>-m BYTWO p -r NOSSE -</td> <td>589.520</td> </tr>\r\n<tr><td>-m COMPOSITE 2 - -</td> <td>327.039</td> </tr>\r\n\r\n\r\n<tr><td>-m SHIFT -</td> <td>106.115</td> </tr>\r\n\r\n<tr><td>-m CARRY FREE -</td> <td>104.299</td> </tr>\r\n\r\n\r\n</div>\r\n</table> <br><br>\r\n</div> </center>\r\n<center>Table 4: Speed of various calls to multiply region() for <em>w</em> = 4. </center><br><br>\r\n\r\n[LBOX12] J. Luo, K. D. Bowers, A. Oprea, and L. Xu. Efficient software implementations of large finite fields\r\n<em>GF(2<sup>n</sup>) </em> for secure storage applications.<em> ACM Transactions on Storage, 8(2),</em> February 2012.<br><br>\r\n\r\n[LD00] J. Lopez and R. Dahab. High-speed software multiplication in f<sub>2<sup>m</sup></sub>. In <em>Annual International Conference\r\non Cryptology in India,</em> 2000.<br><br>\r\n\r\n[LHy08] H. Li and Q. Huan-yan. Parallelized network coding with SIMD instruction sets. In <em>International Symposium\r\non Computer Science and Computational Technology,</em> pages 364-369. IEEE, December 2008.<br><br>\r\n\r\n[LSXP13] J. Luo, M. Shrestha, L. Xu, and J. S. Plank. Efficient encoding schedules for XOR-based erasure codes.\r\n<em>IEEE Transactions on Computing,</em>May 2013.<br><br>\r\n\r\n[Mar94] G. Marsaglia. The mother of all random generators.<a href=\"\"> ftp://ftp.taygeta.com/pub/c/mother.\r\nc,</a> October 1994.<br>\r\n\r\n[PGM13a] J. S. Plank, K. M. Greenan, and E. L. Miller. A complete treatment of software implementations of\r\nfinite field arithmetic for erasure coding applications. Technical Report UT-CS-13-717, University of\r\nTennessee, September 2013.<br><br>\r\n\r\n[PGM13b] J. S. Plank, K. M. Greenan, and E. L. Miller. Screaming fast Galois Field arithmetic using Intel SIMD\r\ninstructions. In FAST-2013: <em>11th Usenix Conference on File and Storage Technologies,</em> San Jose, February\r\n2013.<br><br>\r\n\r\n[Pla97] J. S. Plank. A tutorial on Reed-Solomon coding for fault-tolerance in RAID-like systems.<em> Software -\r\nPractice & Experience,</em> 27(9):995-1012, September 1997.\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n<em>REFERENCES </em>   <span id=\"index_number\">46  </span> <br><br><br>\r\n\r\n\r\n<center>\r\n<div id=\"data2\">\r\n<table cellpadding=\"2\" cellspacing=\"0\" style=\"text-align:center;font-size:19px\">\r\n\r\n<tr><th>Method</td> <th>Speed (MB/s)</td> </tr>\r\n<tr><td>-m SPLIT 16 4 -r ALTMAP -</td> <td>10460.834</td> </tr>\r\n<tr><td>-m SPLIT 16 4 -r SSE (Default) - </td> <td>8473.793</td> </tr>\r\n<tr><td>-m COMPOSITE 2 - -r ALTMAP -</td> <td>5215.073</td> </tr>\r\n<tr><td>-m LOG -r CAUCHY -</td> <td>2428.824</td> </tr>\r\n<tr><td>-m TABLE -</td> <td>2319.129</td> </tr>\r\n<tr><td>-m SPLIT 16 8 -</td> <td>2164.111</td> </tr>\r\n\r\n<tr><td>-m SPLIT 8 8 -</td> <td>2163.993</td> </tr>\r\n<tr><td>-m SPLIT 16 4 -r NOSSE -</td> <td>1148.810</td> </tr>\r\n<tr><td>-m LOG -</td> <td>1019.896</td> </tr>\r\n<tr><td>-m LOG ZERO -</td> <td>1016.814</td> </tr>\r\n<tr><td>-m BYTWO b -</td> <td>738.879</td> </tr>\r\n<tr><td>-m COMPOSITE 2 - -</td> <td>596.819</td> </tr>\r\n<tr><td>-m BYTWO p -</td> <td>560.972</td> </tr>\r\n<tr><td>-m GROUP 4 4 -</td> <td>450.815</td> </tr>\r\n<tr><td>-m BYTWO b -r NOSSE -</td> <td>332.967</td> </tr>\r\n<tr><td>-m BYTWO p -r NOSSE -</td> <td>249.849</td> </tr>\r\n<tr><td>-m CARRY FREE -</td> <td>111.582</td> </tr>\r\n<tr><td>-m SHIFT -</td> <td>95.813</td> </tr>\r\n\r\n\r\n</div>\r\n</table> <br><br>\r\n</div> </center>\r\n<center>Table 5: Speed of various calls to multiply region()  for <em>w</em> = 4. </center><br><br>\r\n\r\n[PMG<sup>+</sup>13] J. S. Plank, E. L. Miller, K. M. Greenan, B. A. Arnold, J. A. Burnum, A. W. Disney, and A. C. McBride.\r\nGF-Complete: A comprehensive open source library for Galois Field arithmetic. version 1.0. Technical\r\nReport UT-CS-13-716, University of Tennessee, September 2013.<br><br>\r\n\r\n[PSR12] J. S. Plank, C. D. Schuman, and B. D. Robison. Heuristics for optimizing matrix-based erasure codes for\r\nfault-tolerant storage systems. In DSN-2012:<em> The International Conference on Dependable Systems and\r\nNetworks,</em> Boston, MA, June 2012. IEEE.<br><br>\r\n\r\n[Rab89] M. O. Rabin. Efficient dispersal of information for security, load balancing, and fault tolerance. <em>Journal\r\nof the Association for Computing Machinery,</em> 36(2):335-348, April 1989.\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n<em>REFERENCES </em>   <span id=\"index_number\">47  </span> <br><br><br>\r\n<center>\r\n<div id=\"data2\">\r\n<table cellpadding=\"2\" cellspacing=\"0\" style=\"text-align:center;font-size:19px\">\r\n<tr><th>Method</td> <th>Speed (MB/s)</td> </tr>\r\n<tr>\r\n \r\n<td>\r\n\r\n-m SPLIT 32 4 -r SSE -r ALTMAP - <br>\r\n-m SPLIT 32 4 (Default)  <br>\r\n-m COMPOSITE 2 -m SPLIT 16 4 -r ALTMAP - -r ALTMAP - <br>\r\n-m COMPOSITE 2 - -r ALTMAP -  <br>\r\n-m SPLIT 8 8 <br> \r\n-m SPLIT 32 8 <br> \r\n-m SPLIT 32 16 <br> \r\n-m SPLIT 8 8 -r CAUCHY <br> \r\n-m SPLIT 32 4 -r NOSSE <br> \r\n-m CARRY FREE -p 0xc5 <br> \r\n-m COMPOSITE 2 - <br> \r\n-m BYTWO b <br> \r\n-m BYTWO p <br> \r\n-m GROUP 4 8 <br> \r\n-m GROUP 4 4 <br> \r\n-m CARRY FREE <br> \r\n-m BYTWO b -r NOSSE <br> \r\n-m BYTWO p -r NOSSE <br>\r\n-m SHIFT <br> \r\n\r\n</td>\r\n\r\n<td>\r\n7185.440 <br>\r\n5063.966 <br>\r\n 4176.440 <br>\r\n3360.860 <br>\r\n1345.678 <br>\r\n1340.656 <br>\r\n1262.676 <br>\r\n1143.263  <br>\r\n 480.859 <br>\r\n393.185 <br>\r\n332.964 <br>\r\n309.971 <br>\r\n258.623 <br>\r\n242.076 <br>\r\n227.399 <br>\r\n226.785 <br>\r\n143.403 <br>\r\n111.956 <br>\r\n52.295 <br>\r\n</td>\r\n\r\n\r\n</tr>\r\n\r\n</div>\r\n</table> <br><br>\r\n</div> </center>\r\n<center>Table 6: Speed of various calls to multiply region() <em>w</em> = 4. </center><br><br>\r\n\r\n<center>\r\n<div id=\"data2\">\r\n<table cellpadding=\"2\" cellspacing=\"0\" style=\"text-align:center;font-size:19px\">\r\n<tr><th>Method</td> <th>Speed (MB/s)</td> </tr>\r\n<tr>\r\n \r\n<td>\r\n-m SPLIT 64 4 -r ALTMAP - <br>\r\n-m SPLIT 64 4 -r SSE (Default) - <br>\r\n-m COMPOSITE 2 -m SPLIT 32 4 -r ALTMAP - -r ALTMAP - <br>\r\n-m COMPOSITE 2 - -r ALTMAP -  <br>\r\n-m SPLIT 64 16 - <br>\r\n-m SPLIT 64 8 -  <br>\r\n-m CARRY FREE -  <br>\r\n-m SPLIT 64 4 -r NOSSE - <br>\r\n-m GROUP 4 4 -  <br>\r\n-m GROUP 4 8 -  <br>\r\n-m BYTWO b -  <br>\r\n-m BYTWO p -  <br>\r\n-m SPLIT 8 8 - <br>\r\n-m BYTWO p -r NOSSE - <br>\r\n-m COMPOSITE 2 - - <br>\r\n-m BYTWO b -r NOSSE - <br>\r\n-m SHIFT - <br>\r\n\r\n</td>\r\n\r\n<td>3522.798 <br>\r\n 2647.862 <br>\r\n2461.572 <br>\r\n1860.921 <br>\r\n1066.490 <br>\r\n998.461 <br>\r\n975.290 <br>\r\n545.479 <br>\r\n230.137 <br>\r\n153.947 <br>\r\n144.052 <br>\r\n124.538 <br>\r\n98.892 <br>\r\n77.912 <br>\r\n77.522 <br>\r\n36.391 <br>\r\n25.282 <br>\r\n</td>\r\n\r\n\r\n</tr>\r\n\r\n</div>\r\n</table> <br><br>\r\n</div> </center>\r\n<center>Table 7: Speed of various calls to multiply region() for  <em>w</em> = 4. </center><br><br>\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n<br/>\r\n<em>REFERENCES </em>   <span id=\"index_number\">48  </span> <br><br><br>\r\n\r\n<center>\r\n<div id=\"data2\">\r\n<table cellpadding=\"2\" cellspacing=\"0\" style=\"text-align:center;font-size:19px\">\r\n<tr><th>Method</td> <th>Speed (MB/s)</td> </tr>\r\n<tr>\r\n \r\n<td>\r\n\r\n-m SPLIT 128 4 -r ALTMAP- <br>\r\n-m COMPOSITE 2 -m SPLIT 64 4 -r ALTMAP - -r ALTMAP- <br> \r\n-m COMPOSITE 2 - -r ALTMAP- <br> \r\n-m SPLIT 128 8 (Default)- <br>\r\n-m CARRY FREE -<br> \r\n-m SPLIT 128 4 -<br> \r\n-m COMPOSITE 2 - <br>\r\n-m GROUP 4 8 -<br> \r\n-m GROUP 4 4 -<br> \r\n-m BYTWO p -<br> \r\n-m BYTWO b -<br> \r\n-m SHIFT -<br> \r\n</td>\r\n\r\n<td>\r\n1727.683 <br>\r\n1385.693 <br>\r\n1041.456 <br>\r\n872.619 <br>\r\n814.030 <br>\r\n500.133  <br>\r\n289.207 <br>\r\n133.583 <br>\r\n116.187 <br>\r\n25.162 <br>\r\n25.157 <br>\r\n14.183 <br>\r\n</td>\r\n\r\n\r\n</tr>\r\n\r\n</div>\r\n</table> <br><br>\r\n</div> </center>\r\n<center>Table 8: Speed of various calls to multiply region() for <em>w</em> = 4. </center><br><br>\r\n"
  },
  {
    "path": "fst/layout/gf-complete/manual/style.css",
    "content": "\r\nbody {\r\nmargin:147px 104px 147px 173px;\r\n\r\n\r\nfont-size:20px;\r\ntext-align:justify;\r\n\r\n\r\n\r\n}\r\n\r\n#index_number{\r\n\r\nfloat:right;\r\n\r\n}\r\n\r\na {\r\n\r\ntext-decoration:none;\r\nfont-size:19px;\r\ncolor:#19191F;\r\nletter-spacing:1.5px;\r\n\r\nfont-family: 'Roboto Condensed', sans-serif;\r\n\r\n\r\n\r\n\r\n\r\n}\r\n/*This is page1 css */\r\n\r\n#box {\r\n\r\ntext-align:center;\r\nfont-size:19px;\r\nmargin-top:166px;\r\n\r\n\r\n}\r\n\r\n#body_text{\r\n\r\nfont-family: 'Roboto Condensed', sans-serif;\r\nfont-size:18px;\r\n\r\n}\r\n\r\nh1{\r\nfont-weight:inherit;\r\n\r\n}\r\n\r\nh4{\r\nfont-size:22px;\r\n\r\nfont-weight:inherit;\r\n\r\n}\r\n\r\n#footer{\r\n\r\nmargin:1px 0px 1px 0px;\r\nfont-size:18px;\r\ntext-align:justify;\r\npadding-bottom:104px;\r\n\r\n\r\n\r\n}\r\n\r\np {\r\nmargin:0;\r\ntext-indent: 50px;\r\nfont-size:19px;\r\ntext-align:justify;\r\n\r\n\r\n}\r\n\r\n\r\n#footer_bar {\r\nborder-top:solid;\r\n\r\nborder-top-width:thin;\r\n\r\n}\r\n\r\n#pages_paragraphs {\r\nmargin:1px 115px 1px 57px;\r\n\r\n\r\n\r\n}\r\n\r\n#pages_paragraphs_2{\r\nmargin:1px 0px 1px 0px;\r\nfont-size:20px;\r\ntext-align:justify;\r\n}\r\n\r\n.code{\r\nfont-size:22px;\r\n\r\n}\r\n\r\n\r\n\r\n\r\n/* This is page3 css */\r\n\r\n.index{\r\nfont-weight:bold;\r\ntext-align:justify;\r\n\r\n}\r\n\r\n.sub_indices {\r\n\r\npadding-left:52px;\r\ntext-align:justify;\r\n}\r\n\r\n\r\n\r\n.aligning_numbers{\r\n\r\npadding-left:27px;\r\n\r\n\r\n}\r\n\r\n.aligning_page_number{\r\n\r\n\r\nfloat:right;\r\n\r\n\r\n}\r\n\r\n/* This page 6 css  */\r\n.box {\r\n\r\nheight:223px;\r\n}\r\n\r\n\r\n\r\n.image-cell_1 {\r\n  background: url(image1.png) no-repeat; \r\n  width:716px;\r\n  height:300px;\r\n\r\n   float:left;\r\n   margin-left:180px;\r\n   margin-right:134px;\r\n   margin-bottom:1px;\r\n   margin-bottom:31px;\r\n   \r\n}\r\n\r\n\r\n/* This page 9 and 10 css */\r\n\r\n\r\n\r\n#number_spacing{\r\n\r\nletter-spacing:1px;\r\nfont-size:17px;\r\n\r\n\r\n}\r\n\r\n\r\n#number_spacing_1{\r\n\r\nletter-spacing:1px;\r\nfont-size:19px;\r\nmargin-left:10px;\r\n\r\n\r\n}\r\n\r\n/* this page 13 css */\r\n\r\n\r\n.image-cell_2 {\r\n  background: url(image2.png) no-repeat; \r\n  width:939px;\r\n  height:419px;\r\n\r\n   float:left;\r\n   margin-left:68px;\r\n   margin-right:134px;\r\n   margin-bottom:1px;\r\n   margin-bottom:31px;\r\n   \r\n}\r\n\r\n/* This is page 14 */\r\n#data1 table{\r\nborder-top-style:solid;\r\nborder-left-style:solid;\r\n\r\nborder-bottom-style:solid;\r\nfont-family: 'Roboto Condensed', sans-serif;\r\n\r\n}\r\n\r\n#data1 th{\r\nborder-bottom-style:solid;\r\nborder-right-style:solid;\r\nborder-right-style:thin;\r\nfont-family: 'Roboto Condensed', sans-serif;\r\n\r\n\r\n}\r\n\r\n#data1 td {\r\nborder-right-style:solid;\r\n\r\nfont-family: 'Roboto Condensed', sans-serif;\r\n\r\n}\r\n\r\n\r\n/* This is page 28 */\r\n#table_page28 table{\r\nborder-top-style:solid;\r\nborder-left-style:solid;\r\n\r\nborder-bottom-style:solid;\r\nborder-top-width:thin;\r\nborder-left-width:thin;\r\nborder-bottom-width:thin;\r\nfont-family: 'Roboto Condensed', sans-serif;\r\n\r\n}\r\n\r\n#table_page28 th{\r\nborder-bottom-style:solid;\r\nborder-right-style:solid;\r\nborder-right-width:thin;\r\nborder-bottom-width:thin;\r\nfont-family: 'Roboto Condensed', sans-serif;\r\n\r\n\r\n}\r\n\r\n#table_page28 td {\r\nborder-right-style:solid;\r\nborder-bottom-style:solid;\r\nborder-bottom-width:thin;\r\nborder-right-width:thin;\r\nfont-family: 'Roboto Condensed', sans-serif;\r\n\r\n}\r\n\r\n\r\n/* This is page 30 */\r\n#table_page30 table{\r\nborder-top-style:solid;\r\nborder-left-style:solid;\r\n\r\nborder-bottom-style:solid;\r\n\r\n}\r\n\r\n#table_page30 th{\r\nborder-bottom-style:solid;\r\nborder-right-style:solid;\r\n\r\n\r\n}\r\n\r\n#table_page30 td {\r\nborder-right-style:solid;\r\nborder-bottom-style:solid;\r\n\r\n\r\n}\r\n#box_1 {\r\n\r\nheight:485px;\r\nmargin-top:44px;\r\nmargin-bottom:-61px;\r\n\r\n}\r\n.image-cell_3 {\r\n  background: url(image3.png) no-repeat; \r\n  width:583px;\r\n  height:393px;\r\n\r\n   float:left;\r\n   \r\n}\r\n\r\n.image-cell_4 {\r\n  background: url(image4.png) no-repeat; \r\n  width:487px;\r\n  height:390px;\r\n\r\n   float:right;\r\n   \r\n\r\n   \r\n}\r\n\r\n/* This is page 42 Css */\r\n\r\n\r\n.image-cell_5 {\r\n  background: url(image5.png) no-repeat; \r\n  width:907px;\r\n  height:592px;\r\n\r\n   float:left;\r\n   margin-right:134px;\r\n   margin-bottom:1px;\r\n   margin-bottom:31px;\r\n   \r\n}\r\n\r\n\r\n/* This is page 43 Css */\r\n\r\n\r\n.image-cell_6 {\r\n  background: url(image6.png) no-repeat; \r\n  width:851px;\r\n  height:532px;\r\n\r\n   margin-right:134px;\r\n   margin-bottom:1px;\r\n   margin-bottom:31px;\r\n   \r\n}\r\n\r\n/* This is page 44 Css */\r\n\r\n\r\n.image-cell_7{\r\n  background: url(image7.png) no-repeat; \r\n  width:945px;\r\n  height:321px;\r\n\r\n   margin-right:134px;\r\n   margin-bottom:1px;\r\n   margin-bottom:31px;\r\n   \r\n}\r\n\r\n/* This is page 45 */\r\n#data2 table{\r\nborder-top-style:solid;\r\nborder-left-style:solid;\r\n\r\nborder-bottom-style:solid;\r\nborder-top-width:2px;\r\nborder-left-width:2px;\r\nborder-bottom-width:2px;\r\nborder-color:black;\r\nfont-family: 'Roboto Condensed', sans-serif;\r\n\r\n}\r\n\r\n#data2 th{\r\nborder-bottom-style:solid;\r\nborder-right-style:solid;\r\nborder-bottom-width:2px;\r\nborder-right-width:2px;\r\nfont-family: 'Roboto Condensed', sans-serif;\r\n\r\n\r\n}\r\n #data2 td {\r\nborder-right-style:solid;\r\nborder-right-width:2px;\r\nfont-family: 'Roboto Condensed', sans-serif;\r\n\r\n}\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"
  },
  {
    "path": "fst/layout/gf-complete/src/Makefile.am",
    "content": "# GF-Complete 'core' AM file\n# Creates the library\n\nAUTOMAKE_OPTIONS = subdir-objects\n\nAM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include\nAM_CFLAGS = -O3 $(SIMD_FLAGS) -fPIC -Wsign-compare\n\nlib_LTLIBRARIES = libgf_complete.la\nlibgf_complete_la_SOURCES = gf.c gf_method.c gf_wgen.c gf_w4.c gf_w8.c gf_w16.c gf_w32.c \\\n          gf_w64.c gf_w128.c gf_rand.c gf_general.c\n\nif HAVE_NEON\nlibgf_complete_la_SOURCES += neon/gf_w4_neon.c  \\\n                             neon/gf_w8_neon.c  \\\n                             neon/gf_w16_neon.c \\\n                             neon/gf_w32_neon.c \\\n                             neon/gf_w64_neon.c\nendif\n\nlibgf_complete_la_LDFLAGS = -version-info 1:0:0\n\n"
  },
  {
    "path": "fst/layout/gf-complete/src/gf.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf.c\n *\n * Generic routines for Galois fields\n */\n\n#include \"gf_int.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <assert.h>\n\nint _gf_errno = GF_E_DEFAULT;\n\nvoid gf_error()\n{\n  char *s;\n\n  switch(_gf_errno) {\n    case GF_E_DEFAULT: s = \"No Error.\"; break;\n    case GF_E_TWOMULT: s = \"Cannot specify two -m's.\"; break;\n    case GF_E_TWO_DIV: s = \"Cannot specify two -d's.\"; break;\n    case GF_E_POLYSPC: s = \"-p needs to be followed by a number in hex (0x optional).\"; break;\n    case GF_E_GROUPAR: s = \"Ran out of arguments in -m GROUP.\"; break;\n    case GF_E_GROUPNU: s = \"In -m GROUP g_s g_r -- g_s and g_r need to be numbers.\"; break;\n    case GF_E_SPLITAR: s = \"Ran out of arguments in -m SPLIT.\"; break;\n    case GF_E_SPLITNU: s = \"In -m SPLIT w_a w_b -- w_a and w_b need to be numbers.\"; break;\n    case GF_E_FEWARGS: s = \"Not enough arguments (Perhaps end with '-'?)\"; break;\n    case GF_E_CFM___W: s = \"-m CARRY_FREE, w must be 4, 8, 16, 32, 64 or 128.\"; break;\n    case GF_E_COMPXPP: s = \"-m COMPOSITE, No poly specified, and we don't have a default for the given sub-field.\"; break;\n    case GF_E_BASE__W: s = \"-m COMPOSITE and the base field is not for w/2.\"; break;\n    case GF_E_CFM4POL: s = \"-m CARRY_FREE, w=4. (Prim-poly & 0xc) must equal 0.\"; break;\n    case GF_E_CFM8POL: s = \"-m CARRY_FREE, w=8. (Prim-poly & 0x80) must equal 0.\"; break;\n    case GF_E_CF16POL: s = \"-m CARRY_FREE, w=16. (Prim-poly & 0xe000) must equal 0.\"; break;\n    case GF_E_CF32POL: s = \"-m CARRY_FREE, w=32. (Prim-poly & 0xfe000000) must equal 0.\"; break;\n    case GF_E_CF64POL: s = \"-m CARRY_FREE, w=64. (Prim-poly & 0xfffe000000000000ULL) must equal 0.\"; break;\n    case GF_E_MDEFDIV: s = \"If multiplication method == default, can't change division.\"; break;\n    case GF_E_MDEFREG: s = \"If multiplication method == default, can't change region.\"; break;\n    case GF_E_MDEFARG: s = \"If multiplication method == default, can't use arg1/arg2.\"; break;\n    case GF_E_DIVCOMP: s = \"Cannot change the division technique with -m COMPOSITE.\"; break;\n    case GF_E_DOUQUAD: s = \"Cannot specify -r DOUBLE and -r QUAD.\"; break;\n    case GF_E_SIMD_NO: s = \"Cannot specify -r SIMD and -r NOSIMD.\"; break;\n    case GF_E_CAUCHYB: s = \"Cannot specify -r CAUCHY and any other -r.\"; break;\n    case GF_E_CAUCOMP: s = \"Cannot specify -m COMPOSITE and -r CAUCHY.\"; break;\n    case GF_E_CAUGT32: s = \"Cannot specify -r CAUCHY with w > 32.\"; break;\n    case GF_E_ARG1SET: s = \"Only use arg1 with SPLIT, GROUP or COMPOSITE.\"; break;\n    case GF_E_ARG2SET: s = \"Only use arg2 with SPLIT or GROUP.\"; break;\n    case GF_E_MATRIXW: s = \"Cannot specify -d MATRIX with w > 32.\"; break;\n    case GF_E_BAD___W: s = \"W must be 1-32, 64 or 128.\"; break;\n    case GF_E_DOUBLET: s = \"Can only specify -r DOUBLE with -m TABLE.\"; break;\n    case GF_E_DOUBLEW: s = \"Can only specify -r DOUBLE w = 4 or w = 8.\"; break;\n    case GF_E_DOUBLEJ: s = \"Cannot specify -r DOUBLE with -r ALTMAP|SIMD|NOSIMD.\"; break;\n    case GF_E_DOUBLEL: s = \"Can only specify -r DOUBLE -r LAZY with w = 8\"; break;\n    case GF_E_QUAD__T: s = \"Can only specify -r QUAD with -m TABLE.\"; break;\n    case GF_E_QUAD__W: s = \"Can only specify -r QUAD w = 4.\"; break;\n    case GF_E_QUAD__J: s = \"Cannot specify -r QUAD with -r ALTMAP|SIMD|NOSIMD.\"; break;\n    case GF_E_BADPOLY: s = \"Bad primitive polynomial (high bits set).\"; break;\n    case GF_E_COMP_PP: s = \"Bad primitive polynomial -- bigger than sub-field.\"; break;\n    case GF_E_LAZY__X: s = \"If -r LAZY, then -r must be DOUBLE or QUAD.\"; break;\n    case GF_E_ALTSHIF: s = \"Cannot specify -m SHIFT and -r ALTMAP.\"; break;\n    case GF_E_SSESHIF: s = \"Cannot specify -m SHIFT and -r SIMD|NOSIMD.\"; break;\n    case GF_E_ALT_CFM: s = \"Cannot specify -m CARRY_FREE and -r ALTMAP.\"; break;\n    case GF_E_SSE_CFM: s = \"Cannot specify -m CARRY_FREE and -r SIMD|NOSIMD.\"; break;\n    case GF_E_PCLMULX: s = \"Specified -m CARRY_FREE, but PCLMUL is not supported.\"; break;\n    case GF_E_ALT_BY2: s = \"Cannot specify -m BYTWO_x and -r ALTMAP.\"; break;\n    case GF_E_BY2_SSE: s = \"Specified -m BYTWO_x -r SIMD, but SSE2 is not supported.\"; break;\n    case GF_E_LOGBADW: s = \"With Log Tables, w must be <= 27.\"; break;\n    case GF_E_LOG___J: s = \"Cannot use Log tables with -r ALTMAP|SIMD|NOSIMD.\"; break;\n    case GF_E_LOGPOLY: s = \"Cannot use Log tables because the polynomial is not primitive.\"; break;\n    case GF_E_ZERBADW: s = \"With -m LOG_ZERO, w must be 8 or 16.\"; break;\n    case GF_E_ZEXBADW: s = \"With -m LOG_ZERO_EXT, w must be 8.\"; break;\n    case GF_E_GR_ARGX: s = \"With -m GROUP, arg1 and arg2 must be >= 0.\"; break;\n    case GF_E_GR_W_48: s = \"With -m GROUP, w cannot be 4 or 8.\"; break;\n    case GF_E_GR_W_16: s = \"With -m GROUP, w == 16, arg1 and arg2 must be 4.\"; break;\n    case GF_E_GR_128A: s = \"With -m GROUP, w == 128, arg1 must be 4, and arg2 in { 4,8,16 }.\"; break;\n    case GF_E_GR_A_27: s = \"With -m GROUP, arg1 and arg2 must be <= 27.\"; break;\n    case GF_E_GR_AR_W: s = \"With -m GROUP, arg1 and arg2 must be <= w.\"; break;\n    case GF_E_GR____J: s = \"Cannot use GROUP with -r ALTMAP|SIMD|NOSIMD.\"; break;\n    case GF_E_TABLE_W: s = \"With -m TABLE, w must be < 15, or == 16.\"; break;\n    case GF_E_TAB_SSE: s = \"With -m TABLE, SIMD|NOSIMD only applies to w=4.\"; break;\n    case GF_E_TABSSE3: s = \"With -m TABLE, -r SIMD, you need SSSE3 supported.\"; break;\n    case GF_E_TAB_ALT: s = \"With -m TABLE, you cannot use ALTMAP.\"; break;\n    case GF_E_SP128AR: s = \"With -m SPLIT, w=128, bad arg1/arg2.\"; break;\n    case GF_E_SP128AL: s = \"With -m SPLIT, w=128, -r SIMD requires -r ALTMAP.\"; break;\n    case GF_E_SP128AS: s = \"With -m SPLIT, w=128, ALTMAP needs SSSE3 supported.\"; break;\n    case GF_E_SP128_A: s = \"With -m SPLIT, w=128, -r ALTMAP only with arg1/arg2 = 4/128.\"; break;\n    case GF_E_SP128_S: s = \"With -m SPLIT, w=128, -r SIMD|NOSIMD only with arg1/arg2 = 4/128.\"; break;\n    case GF_E_SPLIT_W: s = \"With -m SPLIT, w must be in {8, 16, 32, 64, 128}.\"; break;\n    case GF_E_SP_16AR: s = \"With -m SPLIT, w=16, Bad arg1/arg2.\"; break;\n    case GF_E_SP_16_A: s = \"With -m SPLIT, w=16, -r ALTMAP only with arg1/arg2 = 4/16.\"; break;\n    case GF_E_SP_16_S: s = \"With -m SPLIT, w=16, -r SIMD|NOSIMD only with arg1/arg2 = 4/16.\"; break;\n    case GF_E_SP_32AR: s = \"With -m SPLIT, w=32, Bad arg1/arg2.\"; break;\n    case GF_E_SP_32AS: s = \"With -m SPLIT, w=32, -r ALTMAP needs SSSE3 supported.\"; break;\n    case GF_E_SP_32_A: s = \"With -m SPLIT, w=32, -r ALTMAP only with arg1/arg2 = 4/32.\"; break;\n    case GF_E_SP_32_S: s = \"With -m SPLIT, w=32, -r SIMD|NOSIMD only with arg1/arg2 = 4/32.\"; break;\n    case GF_E_SP_64AR: s = \"With -m SPLIT, w=64, Bad arg1/arg2.\"; break;\n    case GF_E_SP_64AS: s = \"With -m SPLIT, w=64, -r ALTMAP needs SSSE3 supported.\"; break;\n    case GF_E_SP_64_A: s = \"With -m SPLIT, w=64, -r ALTMAP only with arg1/arg2 = 4/64.\"; break;\n    case GF_E_SP_64_S: s = \"With -m SPLIT, w=64, -r SIMD|NOSIMD only with arg1/arg2 = 4/64.\"; break;\n    case GF_E_SP_8_AR: s = \"With -m SPLIT, w=8, Bad arg1/arg2.\"; break;\n    case GF_E_SP_8__A: s = \"With -m SPLIT, w=8, Can't have -r ALTMAP.\"; break;\n    case GF_E_SP_SSE3: s = \"With -m SPLIT, Need SSSE3 support for SIMD.\"; break;\n    case GF_E_COMP_A2: s = \"With -m COMPOSITE, arg1 must equal 2.\"; break;\n    case GF_E_COMP_SS: s = \"With -m COMPOSITE, -r SIMD and -r NOSIMD do not apply.\"; break;\n    case GF_E_COMP__W: s = \"With -m COMPOSITE, w must be 8, 16, 32, 64 or 128.\"; break;\n    case GF_E_UNKFLAG: s = \"Unknown method flag - should be -m, -d, -r or -p.\"; break;\n    case GF_E_UNKNOWN: s = \"Unknown multiplication type.\"; break;\n    case GF_E_UNK_REG: s = \"Unknown region type.\"; break;\n    case GF_E_UNK_DIV: s = \"Unknown division type.\"; break;\n    default: s = \"Undefined error.\";\n  }\n\n  fprintf(stderr, \"%s\\n\", s);\n}\n\nuint64_t gf_composite_get_default_poly(gf_t *base) \n{\n  gf_internal_t *h;\n  uint64_t rv;\n\n  h = (gf_internal_t *) base->scratch;\n  if (h->w == 4) {\n    if (h->mult_type == GF_MULT_COMPOSITE) return 0;\n    if (h->prim_poly == 0x13) return 2;\n    return 0;\n  } \n  if (h->w == 8) {\n    if (h->mult_type == GF_MULT_COMPOSITE) return 0;\n    if (h->prim_poly == 0x11d) return 3;\n    return 0;\n  }\n  if (h->w == 16) {\n    if (h->mult_type == GF_MULT_COMPOSITE) {\n      rv = gf_composite_get_default_poly(h->base_gf);\n      if (rv != h->prim_poly) return 0;\n      if (rv == 3) return 0x105;\n      return 0;\n    } else {\n      if (h->prim_poly == 0x1100b) return 2;\n      if (h->prim_poly == 0x1002d) return 7;\n      return 0;\n    }\n  }\n  if (h->w == 32) {\n    if (h->mult_type == GF_MULT_COMPOSITE) {\n      rv = gf_composite_get_default_poly(h->base_gf);\n      if (rv != h->prim_poly) return 0;\n      if (rv == 2) return 0x10005;\n      if (rv == 7) return 0x10008;\n      if (rv == 0x105) return 0x10002;\n      return 0;\n    } else {\n      if (h->prim_poly == 0x400007) return 2;\n      if (h->prim_poly == 0xc5) return 3;\n      return 0;\n    }\n  }\n  if (h->w == 64) {\n    if (h->mult_type == GF_MULT_COMPOSITE) {\n      rv = gf_composite_get_default_poly(h->base_gf);\n      if (rv != h->prim_poly) return 0;\n      if (rv == 3) return 0x100000009ULL;\n      if (rv == 2) return 0x100000004ULL;\n      if (rv == 0x10005) return 0x100000003ULL;\n      if (rv == 0x10002) return 0x100000005ULL;\n      if (rv == 0x10008) return 0x100000006ULL;  /* JSP: (0x0x100000003 works too, \n                                                    but I want to differentiate cases). */\n      return 0;\n    } else {\n      if (h->prim_poly == 0x1bULL) return 2;\n      return 0;\n    }\n  }\n  return 0;\n}\n\nint gf_error_check(int w, int mult_type, int region_type, int divide_type,\n                   int arg1, int arg2, uint64_t poly, gf_t *base)\n{\n  int sse3 = 0;\n  int sse2 = 0;\n  int pclmul = 0;\n  int rdouble, rquad, rlazy, rsimd, rnosimd, raltmap, rcauchy, tmp;\n  gf_internal_t *sub;\n\n  rdouble = (region_type & GF_REGION_DOUBLE_TABLE);\n  rquad   = (region_type & GF_REGION_QUAD_TABLE);\n  rlazy   = (region_type & GF_REGION_LAZY);\n  rsimd   = (region_type & GF_REGION_SIMD);\n  rnosimd = (region_type & GF_REGION_NOSIMD);\n  raltmap = (region_type & GF_REGION_ALTMAP);\n  rcauchy = (region_type & GF_REGION_CAUCHY);\n\n  if (divide_type != GF_DIVIDE_DEFAULT &&\n      divide_type != GF_DIVIDE_MATRIX && \n      divide_type != GF_DIVIDE_EUCLID) {\n    _gf_errno = GF_E_UNK_DIV;\n    return 0;\n  }\n\n  tmp = ( GF_REGION_DOUBLE_TABLE | GF_REGION_QUAD_TABLE | GF_REGION_LAZY |\n          GF_REGION_SIMD | GF_REGION_NOSIMD | GF_REGION_ALTMAP |\n          GF_REGION_CAUCHY );\n  if (region_type & (~tmp)) { _gf_errno = GF_E_UNK_REG; return 0; }\n\n#ifdef INTEL_SSE2\n  sse2 = 1;\n#endif\n\n#ifdef INTEL_SSSE3\n  sse3 = 1;\n#endif\n\n#ifdef INTEL_SSE4_PCLMUL\n  pclmul = 1;\n#endif\n\n#ifdef ARM_NEON\n  pclmul = 1;\n  sse3 = 1;\n#endif\n\n\n  if (w < 1 || (w > 32 && w != 64 && w != 128)) { _gf_errno = GF_E_BAD___W; return 0; }\n    \n  if (mult_type != GF_MULT_COMPOSITE && w < 64) {\n    if ((poly >> (w+1)) != 0)                   { _gf_errno = GF_E_BADPOLY; return 0; }\n  }\n\n  if (mult_type == GF_MULT_DEFAULT) {\n    if (divide_type != GF_DIVIDE_DEFAULT) { _gf_errno = GF_E_MDEFDIV; return 0; }\n    if (region_type != GF_REGION_DEFAULT) { _gf_errno = GF_E_MDEFREG; return 0; }\n    if (arg1 != 0 || arg2 != 0)           { _gf_errno = GF_E_MDEFARG; return 0; }\n    return 1;\n  }\n  \n  if (rsimd && rnosimd)                              { _gf_errno = GF_E_SIMD_NO; return 0; }\n  if (rcauchy && w > 32)                             { _gf_errno = GF_E_CAUGT32; return 0; }\n  if (rcauchy && region_type != GF_REGION_CAUCHY)    { _gf_errno = GF_E_CAUCHYB; return 0; }\n  if (rcauchy && mult_type == GF_MULT_COMPOSITE)     { _gf_errno = GF_E_CAUCOMP; return 0; }\n\n  if (arg1 != 0 && mult_type != GF_MULT_COMPOSITE && \n      mult_type != GF_MULT_SPLIT_TABLE && mult_type != GF_MULT_GROUP) {\n    _gf_errno = GF_E_ARG1SET;\n    return 0;\n  }\n\n  if (arg2 != 0 && mult_type != GF_MULT_SPLIT_TABLE && mult_type != GF_MULT_GROUP) {\n    _gf_errno = GF_E_ARG2SET;\n    return 0;\n  }\n\n  if (divide_type == GF_DIVIDE_MATRIX && w > 32) { _gf_errno = GF_E_MATRIXW; return 0; }\n\n  if (rdouble) {\n    if (rquad)                      { _gf_errno = GF_E_DOUQUAD; return 0; }\n    if (mult_type != GF_MULT_TABLE) { _gf_errno = GF_E_DOUBLET; return 0; }\n    if (w != 4 && w != 8)           { _gf_errno = GF_E_DOUBLEW; return 0; }\n    if (rsimd || rnosimd || raltmap) { _gf_errno = GF_E_DOUBLEJ; return 0; }\n    if (rlazy && w == 4)            { _gf_errno = GF_E_DOUBLEL; return 0; }\n    return 1;\n  }\n\n  if (rquad) {\n    if (mult_type != GF_MULT_TABLE) { _gf_errno = GF_E_QUAD__T; return 0; }\n    if (w != 4)                     { _gf_errno = GF_E_QUAD__W; return 0; }\n    if (rsimd || rnosimd || raltmap) { _gf_errno = GF_E_QUAD__J; return 0; }\n    return 1;\n  }\n\n  if (rlazy)                        { _gf_errno = GF_E_LAZY__X; return 0; }\n\n  if (mult_type == GF_MULT_SHIFT) {\n    if (raltmap)                    { _gf_errno = GF_E_ALTSHIF; return 0; }\n    if (rsimd || rnosimd)           { _gf_errno = GF_E_SSESHIF; return 0; }\n    return 1;\n  }\n\n  if (mult_type == GF_MULT_CARRY_FREE) {\n    if (w != 4 && w != 8 && w != 16 &&\n        w != 32 && w != 64 && w != 128)            { _gf_errno = GF_E_CFM___W; return 0; }\n    if (w == 4 && (poly & 0xc))                    { _gf_errno = GF_E_CFM4POL; return 0; }\n    if (w == 8 && (poly & 0x80))                   { _gf_errno = GF_E_CFM8POL; return 0; }\n    if (w == 16 && (poly & 0xe000))                { _gf_errno = GF_E_CF16POL; return 0; }\n    if (w == 32 && (poly & 0xfe000000))            { _gf_errno = GF_E_CF32POL; return 0; }\n    if (w == 64 && (poly & 0xfffe000000000000ULL)) { _gf_errno = GF_E_CF64POL; return 0; }\n    if (raltmap)                                   { _gf_errno = GF_E_ALT_CFM; return 0; }\n    if (rsimd || rnosimd)                          { _gf_errno = GF_E_SSE_CFM; return 0; }\n    if (!pclmul)                                   { _gf_errno = GF_E_PCLMULX; return 0; }\n    return 1;\n  }\n\n  if (mult_type == GF_MULT_CARRY_FREE_GK) {\n    if (w != 4 && w != 8 && w != 16 &&\n        w != 32 && w != 64 && w != 128)            { _gf_errno = GF_E_CFM___W; return 0; }\n    if (raltmap)                                   { _gf_errno = GF_E_ALT_CFM; return 0; }\n    if (rsimd || rnosimd)                          { _gf_errno = GF_E_SSE_CFM; return 0; }\n    if (!pclmul)                                   { _gf_errno = GF_E_PCLMULX; return 0; }\n    return 1;\n  }\n\n  if (mult_type == GF_MULT_BYTWO_p || mult_type == GF_MULT_BYTWO_b) {\n    if (raltmap)                    { _gf_errno = GF_E_ALT_BY2; return 0; }\n    if (rsimd && !sse2)              { _gf_errno = GF_E_BY2_SSE; return 0; }\n    return 1;\n  }\n\n  if (mult_type == GF_MULT_LOG_TABLE || mult_type == GF_MULT_LOG_ZERO\n                                     || mult_type == GF_MULT_LOG_ZERO_EXT ) {\n    if (w > 27)                     { _gf_errno = GF_E_LOGBADW; return 0; }\n    if (raltmap || rsimd || rnosimd) { _gf_errno = GF_E_LOG___J; return 0; }\n\n    if (mult_type == GF_MULT_LOG_TABLE) return 1;\n\n    if (w != 8 && w != 16)          { _gf_errno = GF_E_ZERBADW; return 0; }\n\n    if (mult_type == GF_MULT_LOG_ZERO) return 1;\n\n    if (w != 8)                     { _gf_errno = GF_E_ZEXBADW; return 0; }\n    return 1;\n  }\n\n  if (mult_type == GF_MULT_GROUP) {\n    if (arg1 <= 0 || arg2 <= 0)                 { _gf_errno = GF_E_GR_ARGX; return 0; }\n    if (w == 4 || w == 8)                       { _gf_errno = GF_E_GR_W_48; return 0; }\n    if (w == 16 && (arg1 != 4 || arg2 != 4))     { _gf_errno = GF_E_GR_W_16; return 0; }\n    if (w == 128 && (arg1 != 4 || \n       (arg2 != 4 && arg2 != 8 && arg2 != 16))) { _gf_errno = GF_E_GR_128A; return 0; }\n    if (arg1 > 27 || arg2 > 27)                 { _gf_errno = GF_E_GR_A_27; return 0; }\n    if (arg1 > w || arg2 > w)                   { _gf_errno = GF_E_GR_AR_W; return 0; }\n    if (raltmap || rsimd || rnosimd)            { _gf_errno = GF_E_GR____J; return 0; }\n    return 1;\n  }\n  \n  if (mult_type == GF_MULT_TABLE) {\n    if (w != 16 && w >= 15)                     { _gf_errno = GF_E_TABLE_W; return 0; }\n    if (w != 4 && (rsimd || rnosimd))           { _gf_errno = GF_E_TAB_SSE; return 0; }\n    if (rsimd && !sse3)                         { _gf_errno = GF_E_TABSSE3; return 0; }\n    if (raltmap)                                { _gf_errno = GF_E_TAB_ALT; return 0; }\n    return 1;\n  }\n\n  if (mult_type == GF_MULT_SPLIT_TABLE) {\n    if (arg1 > arg2) {\n      tmp = arg1;\n      arg1 = arg2;\n      arg2 = tmp;\n    }\n    if (w == 8) {\n      if (arg1 != 4 || arg2 != 8)               { _gf_errno = GF_E_SP_8_AR; return 0; }\n      if (rsimd && !sse3)                       { _gf_errno = GF_E_SP_SSE3; return 0; }\n      if (raltmap)                              { _gf_errno = GF_E_SP_8__A; return 0; }\n    } else if (w == 16) {\n      if ((arg1 == 8 && arg2 == 8) ||\n          (arg1 == 8 && arg2 == 16)) {\n        if (rsimd || rnosimd)                   { _gf_errno = GF_E_SP_16_S; return 0; }\n        if (raltmap)                            { _gf_errno = GF_E_SP_16_A; return 0; }\n      } else if (arg1 == 4 && arg2 == 16) {\n        if (rsimd && !sse3)                     { _gf_errno = GF_E_SP_SSE3; return 0; }\n      } else                                    { _gf_errno = GF_E_SP_16AR; return 0; }\n    } else if (w == 32) {\n      if ((arg1 == 8 && arg2 == 8) ||\n          (arg1 == 8 && arg2 == 32) ||\n          (arg1 == 16 && arg2 == 32)) {\n        if (rsimd || rnosimd)                   { _gf_errno = GF_E_SP_32_S; return 0; }\n        if (raltmap)                            { _gf_errno = GF_E_SP_32_A; return 0; }\n      } else if (arg1 == 4 && arg2 == 32) {\n        if (rsimd && !sse3)                     { _gf_errno = GF_E_SP_SSE3; return 0; }\n        if (raltmap && !sse3)                   { _gf_errno = GF_E_SP_32AS; return 0; }\n        if (raltmap && rnosimd)                 { _gf_errno = GF_E_SP_32AS; return 0; }\n      } else                                    { _gf_errno = GF_E_SP_32AR; return 0; }\n    } else if (w == 64) {\n      if ((arg1 == 8 && arg2 == 8) ||\n          (arg1 == 8 && arg2 == 64) ||\n          (arg1 == 16 && arg2 == 64)) {\n        if (rsimd || rnosimd)                   { _gf_errno = GF_E_SP_64_S; return 0; }\n        if (raltmap)                            { _gf_errno = GF_E_SP_64_A; return 0; }\n      } else if (arg1 == 4 && arg2 == 64) {\n        if (rsimd && !sse3)                     { _gf_errno = GF_E_SP_SSE3; return 0; }\n        if (raltmap && !sse3)                   { _gf_errno = GF_E_SP_64AS; return 0; }\n        if (raltmap && rnosimd)                 { _gf_errno = GF_E_SP_64AS; return 0; }\n      } else                                    { _gf_errno = GF_E_SP_64AR; return 0; }\n    } else if (w == 128) {\n      if (arg1 == 8 && arg2 == 128) {\n        if (rsimd || rnosimd)                   { _gf_errno = GF_E_SP128_S; return 0; }\n        if (raltmap)                            { _gf_errno = GF_E_SP128_A; return 0; }\n      } else if (arg1 == 4 && arg2 == 128) {\n        if (rsimd && !sse3)                     { _gf_errno = GF_E_SP_SSE3; return 0; }\n        if (raltmap && !sse3)                   { _gf_errno = GF_E_SP128AS; return 0; }\n        if (raltmap && rnosimd)                 { _gf_errno = GF_E_SP128AS; return 0; }\n      } else                                    { _gf_errno = GF_E_SP128AR; return 0; }\n    } else                                      { _gf_errno = GF_E_SPLIT_W; return 0; }\n    return 1;\n  }\n\n  if (mult_type == GF_MULT_COMPOSITE) {\n    if (w != 8 && w != 16 && w != 32 \n               && w != 64 && w != 128)          { _gf_errno = GF_E_COMP__W; return 0; }\n    if (w < 128 && (poly >> (w/2)) != 0)                   { _gf_errno = GF_E_COMP_PP; return 0; }\n    if (divide_type != GF_DIVIDE_DEFAULT)       { _gf_errno = GF_E_DIVCOMP; return 0; }\n    if (arg1 != 2)                              { _gf_errno = GF_E_COMP_A2; return 0; }\n    if (rsimd || rnosimd)                       { _gf_errno = GF_E_COMP_SS; return 0; }\n    if (base != NULL) {\n      sub = (gf_internal_t *) base->scratch;\n      if (sub->w != w/2)                      { _gf_errno = GF_E_BASE__W; return 0; }\n      if (poly == 0) {\n        if (gf_composite_get_default_poly(base) == 0) { _gf_errno = GF_E_COMPXPP; return 0; }\n      }\n    }\n    return 1;\n  }\n\n  _gf_errno = GF_E_UNKNOWN; \n  return 0;\n}\n\nint gf_scratch_size(int w, \n                    int mult_type, \n                    int region_type, \n                    int divide_type, \n                    int arg1, \n                    int arg2)\n{\n  if (gf_error_check(w, mult_type, region_type, divide_type, arg1, arg2, 0, NULL) == 0) return 0;\n\n  switch(w) {\n    case 4: return gf_w4_scratch_size(mult_type, region_type, divide_type, arg1, arg2);\n    case 8: return gf_w8_scratch_size(mult_type, region_type, divide_type, arg1, arg2);\n    case 16: return gf_w16_scratch_size(mult_type, region_type, divide_type, arg1, arg2);\n    case 32: return gf_w32_scratch_size(mult_type, region_type, divide_type, arg1, arg2);\n    case 64: return gf_w64_scratch_size(mult_type, region_type, divide_type, arg1, arg2);\n    case 128: return gf_w128_scratch_size(mult_type, region_type, divide_type, arg1, arg2);\n    default: return gf_wgen_scratch_size(w, mult_type, region_type, divide_type, arg1, arg2);\n  }\n}\n\nextern int gf_size(gf_t *gf)\n{\n  gf_internal_t *h;\n  int s;\n\n  s = sizeof(gf_t);\n  h = (gf_internal_t *) gf->scratch;\n  s += gf_scratch_size(h->w, h->mult_type, h->region_type, h->divide_type, h->arg1, h->arg2);\n  if (h->mult_type == GF_MULT_COMPOSITE) s += gf_size(h->base_gf);\n  return s;\n}\n\n\nint gf_init_easy(gf_t *gf, int w)\n{\n  return gf_init_hard(gf, w, GF_MULT_DEFAULT, GF_REGION_DEFAULT, GF_DIVIDE_DEFAULT, \n                      0, 0, 0, NULL, NULL);\n}\n\n/* Allen: What's going on here is this function is putting info into the\n       scratch mem of gf, and then calling the relevant REAL init\n       func for the word size.  Probably done this way to consolidate\n       those aspects of initialization that don't rely on word size,\n       and then take care of word-size-specific stuff. */\n\nint gf_init_hard(gf_t *gf, int w, int mult_type, \n                        int region_type,\n                        int divide_type,\n                        uint64_t prim_poly,\n                        int arg1, int arg2,\n                        gf_t *base_gf,\n                        void *scratch_memory) \n{\n  int sz;\n  gf_internal_t *h;\n \n  if (gf_error_check(w, mult_type, region_type, divide_type, \n                     arg1, arg2, prim_poly, base_gf) == 0) return 0;\n\n  sz = gf_scratch_size(w, mult_type, region_type, divide_type, arg1, arg2);\n  if (sz <= 0) return 0;  /* This shouldn't happen, as all errors should get caught\n                             in gf_error_check() */\n  \n  if (scratch_memory == NULL) {\n    h = (gf_internal_t *) malloc(sz);\n    h->free_me = 1;\n  } else {\n    h = scratch_memory;\n    h->free_me = 0;\n  }\n  gf->scratch = (void *) h;\n  h->mult_type = mult_type;\n  h->region_type = region_type;\n  h->divide_type = divide_type;\n  h->w = w;\n  h->prim_poly = prim_poly;\n  h->arg1 = arg1;\n  h->arg2 = arg2;\n  h->base_gf = base_gf;\n  h->private = (void *) gf->scratch;\n  h->private = (uint8_t *)h->private + (sizeof(gf_internal_t));\n  gf->extract_word.w32 = NULL;\n\n  switch(w) {\n    case 4: return gf_w4_init(gf);\n    case 8: return gf_w8_init(gf);\n    case 16: return gf_w16_init(gf);\n    case 32: return gf_w32_init(gf);\n    case 64: return gf_w64_init(gf);\n    case 128: return gf_w128_init(gf);\n    default: return gf_wgen_init(gf);\n  }\n}\n\nint gf_free(gf_t *gf, int recursive)\n{\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  if (recursive && h->base_gf != NULL) {\n    gf_free(h->base_gf, 1);\n    free(h->base_gf);\n  }\n  if (h->free_me) free(h);\n  return 0; /* Making compiler happy */\n}\n\nvoid gf_alignment_error(char *s, int a)\n{\n  fprintf(stderr, \"Alignment error in %s:\\n\", s);\n  fprintf(stderr, \"   The source and destination buffers must be aligned to each other,\\n\");\n  fprintf(stderr, \"   and they must be aligned to a %d-byte address.\\n\", a);\n  assert(0);\n}\n\nstatic \nvoid gf_invert_binary_matrix(uint32_t *mat, uint32_t *inv, int rows) {\n  int cols, i, j;\n  uint32_t tmp;\n\n  cols = rows;\n\n  for (i = 0; i < rows; i++) inv[i] = (1 << i);\n\n  /* First -- convert into upper triangular */\n\n  for (i = 0; i < cols; i++) {\n\n    /* Swap rows if we ave a zero i,i element.  If we can't swap, then the\n       matrix was not invertible */\n\n    if ((mat[i] & (1 << i)) == 0) {\n      for (j = i+1; j < rows && (mat[j] & (1 << i)) == 0; j++) ;\n      if (j == rows) {\n        fprintf(stderr, \"galois_invert_matrix: Matrix not invertible!!\\n\");\n        assert(0);\n      }\n      tmp = mat[i]; mat[i] = mat[j]; mat[j] = tmp;\n      tmp = inv[i]; inv[i] = inv[j]; inv[j] = tmp;\n    }\n\n    /* Now for each j>i, add A_ji*Ai to Aj */\n    for (j = i+1; j != rows; j++) {\n      if ((mat[j] & (1 << i)) != 0) {\n        mat[j] ^= mat[i];\n        inv[j] ^= inv[i];\n      }\n    }\n  }\n\n  /* Now the matrix is upper triangular.  Start at the top and multiply down */\n\n  for (i = rows-1; i >= 0; i--) {\n    for (j = 0; j < i; j++) {\n      if (mat[j] & (1 << i)) {\n        /*  mat[j] ^= mat[i]; */\n        inv[j] ^= inv[i];\n      }\n    }\n  }\n}\n\nuint32_t gf_bitmatrix_inverse(uint32_t y, int w, uint32_t pp) \n{\n  uint32_t mat[32], inv[32], mask;\n  int i;\n\n  mask = (w == 32) ? 0xffffffff : ((uint32_t)1 << w) - 1;\n  for (i = 0; i < w; i++) {\n    mat[i] = y;\n\n    if (y & (1 << (w-1))) {\n      y = y << 1;\n      y = ((y ^ pp) & mask);\n    } else {\n      y = y << 1;\n    }\n  }\n\n  gf_invert_binary_matrix(mat, inv, w);\n  return inv[0];\n}\n\nvoid gf_two_byte_region_table_multiply(gf_region_data *rd, uint16_t *base)\n{\n  uint64_t a, prod;\n  int xor;\n  uint64_t *s64, *d64, *top;\n\n  s64 = rd->s_start;\n  d64 = rd->d_start;\n  top = rd->d_top;\n  xor = rd->xor;\n  \n  if (xor) {\n    while (d64 != top) {\n      a = *s64;\n      prod = base[a >> 48];\n      a <<= 16;\n      prod <<= 16;\n      prod ^= base[a >> 48];\n      a <<= 16;\n      prod <<= 16;\n      prod ^= base[a >> 48];\n      a <<= 16;\n      prod <<= 16;\n      prod ^= base[a >> 48];\n      prod ^= *d64;\n      *d64 = prod;\n      s64++;\n      d64++;\n    }\n  } else {\n    while (d64 != top) {\n      a = *s64;\n      prod = base[a >> 48];\n      a <<= 16;\n      prod <<= 16;\n      prod ^= base[a >> 48];\n      a <<= 16;\n      prod <<= 16;\n      prod ^= base[a >> 48];\n      a <<= 16;\n      prod <<= 16;\n      prod ^= base[a >> 48];\n      *d64 = prod;\n      s64++;\n      d64++;\n    }\n  }\n}\n\nstatic void gf_slow_multiply_region(gf_region_data *rd, void *src, void *dest, void *s_top)\n{\n  uint8_t *s8, *d8;\n  uint16_t *s16, *d16;\n  uint32_t *s32, *d32;\n  uint64_t *s64, *d64;\n  gf_internal_t *h;\n  int wb;\n  uint32_t p, a;\n\n  h = rd->gf->scratch;\n  wb = (h->w)/8;\n  if (wb == 0) wb = 1;\n  \n  while (src < s_top) {\n    switch (h->w) {\n    case 8:\n      s8 = (uint8_t *) src;\n      d8 = (uint8_t *) dest;\n      *d8 = (rd->xor) ? (*d8 ^ rd->gf->multiply.w32(rd->gf, rd->val, *s8)) : \n                      rd->gf->multiply.w32(rd->gf, rd->val, *s8);\n      break;\n    case 4:\n      s8 = (uint8_t *) src;\n      d8 = (uint8_t *) dest;\n      a = *s8;\n      p = rd->gf->multiply.w32(rd->gf, rd->val, a&0xf);\n      p |= (rd->gf->multiply.w32(rd->gf, rd->val, a >> 4) << 4);\n      if (rd->xor) p ^= *d8;\n      *d8 = p;\n      break;\n    case 16:\n      s16 = (uint16_t *) src;\n      d16 = (uint16_t *) dest;\n      *d16 = (rd->xor) ? (*d16 ^ rd->gf->multiply.w32(rd->gf, rd->val, *s16)) : \n                      rd->gf->multiply.w32(rd->gf, rd->val, *s16);\n      break;\n    case 32:\n      s32 = (uint32_t *) src;\n      d32 = (uint32_t *) dest;\n      *d32 = (rd->xor) ? (*d32 ^ rd->gf->multiply.w32(rd->gf, rd->val, *s32)) : \n                      rd->gf->multiply.w32(rd->gf, rd->val, *s32);\n      break;\n    case 64:\n      s64 = (uint64_t *) src;\n      d64 = (uint64_t *) dest;\n      *d64 = (rd->xor) ? (*d64 ^ rd->gf->multiply.w64(rd->gf, rd->val, *s64)) : \n                      rd->gf->multiply.w64(rd->gf, rd->val, *s64);\n      break;\n    default:\n      fprintf(stderr, \"Error: gf_slow_multiply_region: w=%d not implemented.\\n\", h->w);\n      exit(1);\n    }\n    src = (uint8_t *)src + wb;\n    dest = (uint8_t *)dest + wb;\n  }\n}\n\n/* JSP - The purpose of this procedure is to error check alignment,\n   and to set up the region operation so that it can best leverage\n   large words.\n\n   It stores its information in rd.\n\n   Assuming you're not doing Cauchy coding, (see below for that),\n   then w will be 4, 8, 16, 32 or 64. It can't be 128 (probably\n   should change that).\n\n   src and dest must then be aligned on ceil(w/8)-byte boundaries.\n   Moreover, bytes must be a multiple of ceil(w/8).  If the variable\n   align is equal to ceil(w/8), then we will set s_start = src,\n   d_start = dest, s_top to (src+bytes) and d_top to (dest+bytes).\n   And we return -- the implementation will go ahead and do the\n   multiplication on individual words (e.g. using discrete logs).\n\n   If align is greater than ceil(w/8), then the implementation needs\n   to work on groups of \"align\" bytes.  For example, suppose you are\n   implementing BYTWO, without SSE. Then you will be doing the region\n   multiplication in units of 8 bytes, so align = 8. Or, suppose you\n   are doing a Quad table in GF(2^4). You will be doing the region\n   multiplication in units of 2 bytes, so align = 2. Or, suppose you\n   are doing split multiplication with SSE operations in GF(2^8).\n   Then align = 16. Worse yet, suppose you are doing split\n   multiplication with SSE operations in GF(2^16), with or without\n   ALTMAP. Then, you will be doing the multiplication on 256 bits at\n   a time.  So align = 32.\n\n   When align does not equal ceil(w/8), we split the region\n   multiplication into three parts.  We are going to make s_start be\n   the first address greater than or equal to src that is a multiple\n   of align.  s_top is going to be the largest address >= src+bytes\n   such that (s_top - s_start) is a multiple of align.  We do the\n   same with d_start and d_top.  When we say that \"src and dest must\n   be aligned with respect to each other, we mean that s_start-src\n   must equal d_start-dest.\n\n   Now, the region multiplication is done in three parts -- the part\n   between src and s_start must be done using single words.\n   Similarly, the part between s_top and src+bytes must also be done\n   using single words.  The part between s_start and s_top will be\n   done in chunks of \"align\" bytes.\n\n   One final thing -- if align > 16, then s_start and d_start will be\n   aligned on a 16 byte boundary.  Perhaps we should have two\n   variables: align and chunksize.  Then we'd have s_start & d_start\n   aligned to \"align\", and have s_top-s_start be a multiple of\n   chunksize.  That may be less confusing, but it would be a big\n   change.\n\n   Finally, if align = -1, then we are doing Cauchy multiplication,\n   using only XOR's.  In this case, we're not going to care about\n   alignment because we are just doing XOR's.  Instead, the only\n   thing we care about is that bytes must be a multiple of w.\n\n   This is not to say that alignment doesn't matter in performance\n   with XOR's.  See that discussion in gf_multby_one().\n\n   After you call gf_set_region_data(), the procedure\n   gf_do_initial_region_alignment() calls gf->multiply.w32() on\n   everything between src and s_start.  The procedure\n   gf_do_final_region_alignment() calls gf->multiply.w32() on\n   everything between s_top and src+bytes.\n   */\n\nvoid gf_set_region_data(gf_region_data *rd,\n  gf_t *gf,\n  void *src,\n  void *dest,\n  int bytes,\n  uint64_t val,\n  int xor,\n  int align)\n{\n  gf_internal_t *h = NULL;\n  int wb;\n  uint32_t a;\n  unsigned long uls, uld;\n\n  if (gf == NULL) {  /* JSP - Can be NULL if you're just doing XOR's */\n    wb = 1;\n  } else {\n    h = gf->scratch;\n    wb = (h->w)/8;\n    if (wb == 0) wb = 1;\n  }\n  \n  rd->gf = gf;\n  rd->src = src;\n  rd->dest = dest;\n  rd->bytes = bytes;\n  rd->val = val;\n  rd->xor = xor;\n  rd->align = align;\n\n  uls = (unsigned long) src;\n  uld = (unsigned long) dest;\n\n  a = (align <= 16) ? align : 16;\n\n  if (align == -1) { /* JSP: This is cauchy.  Error check bytes, then set up the pointers\n                        so that there are no alignment regions. */\n    if (h != NULL && bytes % h->w != 0) {\n      fprintf(stderr, \"Error in region multiply operation.\\n\");\n      fprintf(stderr, \"The size must be a multiple of %d bytes.\\n\", h->w);\n      assert(0);\n    }\n  \n    rd->s_start = src;\n    rd->d_start = dest;\n    rd->s_top = (uint8_t *)src + bytes;\n    rd->d_top = (uint8_t *)src + bytes;\n    return;\n  }\n\n  if (uls % a != uld % a) {\n    fprintf(stderr, \"Error in region multiply operation.\\n\");\n    fprintf(stderr, \"The source & destination pointers must be aligned with respect\\n\");\n    fprintf(stderr, \"to each other along a %d byte boundary.\\n\", a);\n    fprintf(stderr, \"Src = 0x%lx.  Dest = 0x%lx\\n\", (unsigned long) src,\n            (unsigned long) dest);\n    assert(0);\n  }\n\n  if (uls % wb != 0) {\n    fprintf(stderr, \"Error in region multiply operation.\\n\");\n    fprintf(stderr, \"The pointers must be aligned along a %d byte boundary.\\n\", wb);\n    fprintf(stderr, \"Src = 0x%lx.  Dest = 0x%lx\\n\", (unsigned long) src,\n            (unsigned long) dest);\n    assert(0);\n  }\n\n  if (bytes % wb != 0) {\n    fprintf(stderr, \"Error in region multiply operation.\\n\");\n    fprintf(stderr, \"The size must be a multiple of %d bytes.\\n\", wb);\n    assert(0);\n  }\n\n  uls %= a;\n  if (uls != 0) uls = (a-uls);\n  rd->s_start = (uint8_t *)rd->src + uls;\n  rd->d_start = (uint8_t *)rd->dest + uls;\n  bytes -= uls;\n  bytes -= (bytes % align);\n  rd->s_top = (uint8_t *)rd->s_start + bytes;\n  rd->d_top = (uint8_t *)rd->d_start + bytes;\n\n}\n\nvoid gf_do_initial_region_alignment(gf_region_data *rd)\n{\n  gf_slow_multiply_region(rd, rd->src, rd->dest, rd->s_start);\n}\n\nvoid gf_do_final_region_alignment(gf_region_data *rd)\n{\n  gf_slow_multiply_region(rd, rd->s_top, rd->d_top, (uint8_t *)rd->src+rd->bytes);\n}\n\nvoid gf_multby_zero(void *dest, int bytes, int xor) \n{\n  if (xor) return;\n  bzero(dest, bytes);\n  return;\n}\n\n/* JSP - gf_multby_one tries to do this in the most efficient way\n   possible.  If xor = 0, then simply call memcpy() since that\n   should be optimized by the system.  Otherwise, try to do the xor\n   in the following order:\n\n   If src and dest are aligned with respect to each other on 16-byte\n   boundaries and you have SSE instructions, then use aligned SSE\n   instructions.\n\n   If they aren't but you still have SSE instructions, use unaligned\n   SSE instructions.\n\n   If there are no SSE instructions, but they are aligned with\n   respect to each other on 8-byte boundaries, then do them with\n   uint64_t's.\n\n   Otherwise, call gf_unaligned_xor(), which does the following:\n   align a destination pointer along an 8-byte boundary, and then\n   memcpy 32 bytes at a time from the src pointer to an array of\n   doubles.  I'm not sure if that's the best -- probably needs\n   testing, but this seems like it could be a black hole.\n */\n\nstatic void gf_unaligned_xor(void *src, void *dest, int bytes);\n\nvoid gf_multby_one(void *src, void *dest, int bytes, int xor) \n{\n#ifdef   INTEL_SSE2\n  __m128i ms, md;\n#endif\n  unsigned long uls, uld;\n  uint8_t *s8, *d8;\n  uint64_t *s64, *d64, *dtop64;\n  gf_region_data rd;\n\n  if (!xor) {\n    memcpy(dest, src, bytes);\n    return;\n  }\n  uls = (unsigned long) src;\n  uld = (unsigned long) dest;\n\n#ifdef   INTEL_SSE2\n  int abytes;\n  s8 = (uint8_t *) src;\n  d8 = (uint8_t *) dest;\n  if (uls % 16 == uld % 16) {\n    gf_set_region_data(&rd, NULL, src, dest, bytes, 1, xor, 16);\n    while (s8 != rd.s_start) {\n      *d8 ^= *s8;\n      d8++;\n      s8++;\n    }\n    while (s8 < (uint8_t *) rd.s_top) {\n      ms = _mm_load_si128 ((__m128i *)(s8));\n      md = _mm_load_si128 ((__m128i *)(d8));\n      md = _mm_xor_si128(md, ms);\n      _mm_store_si128((__m128i *)(d8), md);\n      s8 += 16;\n      d8 += 16;\n    }\n    while (s8 != (uint8_t *) src + bytes) {\n      *d8 ^= *s8;\n      d8++;\n      s8++;\n    }\n    return;\n  }\n\n  abytes = (bytes & 0xfffffff0);\n\n  while (d8 < (uint8_t *) dest + abytes) {\n    ms = _mm_loadu_si128 ((__m128i *)(s8));\n    md = _mm_loadu_si128 ((__m128i *)(d8));\n    md = _mm_xor_si128(md, ms);\n    _mm_storeu_si128((__m128i *)(d8), md);\n    s8 += 16;\n    d8 += 16;\n  }\n  while (d8 != (uint8_t *) dest+bytes) {\n    *d8 ^= *s8;\n    d8++;\n    s8++;\n  }\n  return;\n#endif\n#if defined(ARM_NEON)\n  s8 = (uint8_t *) src;\n  d8 = (uint8_t *) dest;\n\n  if (uls % 16 == uld % 16) {\n    gf_set_region_data(&rd, NULL, src, dest, bytes, 1, xor, 16);\n    while (s8 != rd.s_start) {\n      *d8 ^= *s8;\n      s8++;\n      d8++;\n    }\n    while (s8 < (uint8_t *) rd.s_top) {\n      uint8x16_t vs = vld1q_u8 (s8);\n      uint8x16_t vd = vld1q_u8 (d8);\n      uint8x16_t vr = veorq_u8 (vs, vd);\n      vst1q_u8 (d8, vr);\n      s8 += 16;\n      d8 += 16;\n    }\n  } else {\n    while (s8 + 15 < (uint8_t *) src + bytes) {\n      uint8x16_t vs = vld1q_u8 (s8);\n      uint8x16_t vd = vld1q_u8 (d8);\n      uint8x16_t vr = veorq_u8 (vs, vd);\n      vst1q_u8 (d8, vr);\n      s8 += 16;\n      d8 += 16;\n    }\n  }\n  while (s8 < (uint8_t *) src + bytes) {\n    *d8 ^= *s8;\n    s8++;\n    d8++;\n  }\n  return;\n#endif\n  if (uls % 8 != uld % 8) {\n    gf_unaligned_xor(src, dest, bytes);\n    return;\n  }\n  \n  gf_set_region_data(&rd, NULL, src, dest, bytes, 1, xor, 8);\n  s8 = (uint8_t *) src;\n  d8 = (uint8_t *) dest;\n  while (d8 != rd.d_start) {\n    *d8 ^= *s8;\n    d8++;\n    s8++;\n  }\n  dtop64 = (uint64_t *) rd.d_top;\n\n  d64 = (uint64_t *) rd.d_start;\n  s64 = (uint64_t *) rd.s_start;\n\n  while (d64 < dtop64) {\n    *d64 ^= *s64;\n    d64++;\n    s64++;\n  }\n\n  s8 = (uint8_t *) rd.s_top;\n  d8 = (uint8_t *) rd.d_top;\n\n  while (d8 != (uint8_t *) dest+bytes) {\n    *d8 ^= *s8;\n    d8++;\n    s8++;\n  }\n  return;\n}\n\n#define UNALIGNED_BUFSIZE (8)\n\nstatic void gf_unaligned_xor(void *src, void *dest, int bytes)\n{\n  uint64_t scopy[UNALIGNED_BUFSIZE], *d64;\n  int i;\n  gf_region_data rd;\n  uint8_t *s8, *d8;\n\n  /* JSP - call gf_set_region_data(), but use dest in both places.  This is\n     because I only want to set up dest.  If I used src, gf_set_region_data()\n     would fail because src and dest are not aligned to each other wrt \n     8-byte pointers.  I know this will actually align d_start to 16 bytes.\n     If I change gf_set_region_data() to split alignment & chunksize, then \n     I could do this correctly. */\n\n  gf_set_region_data(&rd, NULL, dest, dest, bytes, 1, 1, 8*UNALIGNED_BUFSIZE);\n  s8 = (uint8_t *) src;\n  d8 = (uint8_t *) dest;\n\n  while (d8 < (uint8_t *) rd.d_start) {\n    *d8 ^= *s8;\n    d8++;\n    s8++;\n  }\n  \n  d64 = (uint64_t *) d8;\n  while (d64 < (uint64_t *) rd.d_top) {\n    memcpy(scopy, s8, 8*UNALIGNED_BUFSIZE);\n    s8 += 8*UNALIGNED_BUFSIZE;\n    for (i = 0; i < UNALIGNED_BUFSIZE; i++) {\n      *d64 ^= scopy[i];\n      d64++;\n    }\n  }\n  \n  d8 = (uint8_t *) d64;\n  while (d8 < (uint8_t *) ((uint8_t *)dest+bytes)) {\n    *d8 ^= *s8;\n    d8++;\n    s8++;\n  }\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/src/gf_general.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_general.c\n *\n * This file has helper routines for doing basic GF operations with any\n * legal value of w.  The problem is that w <= 32, w=64 and w=128 all have\n * different data types, which is a pain.  The procedures in this file try\n * to alleviate that pain.  They are used in gf_unit and gf_time.\n */\n\n#include <stdio.h>\n#include <getopt.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n#include <assert.h>\n\n#include \"gf_complete.h\"\n#include \"gf_int.h\"\n#include \"gf_method.h\"\n#include \"gf_rand.h\"\n#include \"gf_general.h\"\n\nvoid gf_general_set_zero(gf_general_t *v, int w)\n{\n  if (w <= 32) {\n    v->w32 = 0;\n  } else if (w <= 64) {\n    v->w64 = 0;\n  } else {\n    v->w128[0] = 0;\n    v->w128[1] = 0;\n  }\n}\n\nvoid gf_general_set_one(gf_general_t *v, int w)\n{\n  if (w <= 32) {\n    v->w32 = 1;\n  } else if (w <= 64) {\n    v->w64 = 1;\n  } else {\n    v->w128[0] = 0;\n    v->w128[1] = 1;\n  }\n}\n\nvoid gf_general_set_two(gf_general_t *v, int w)\n{\n  if (w <= 32) {\n    v->w32 = 2;\n  } else if (w <= 64) {\n    v->w64 = 2;\n  } else {\n    v->w128[0] = 0;\n    v->w128[1] = 2;\n  }\n}\n\nint gf_general_is_zero(gf_general_t *v, int w) \n{\n  if (w <= 32) {\n    return (v->w32 == 0);\n  } else if (w <= 64) {\n    return (v->w64 == 0);\n  } else {\n    return (v->w128[0] == 0 && v->w128[1] == 0);\n  }\n}\n\nint gf_general_is_one(gf_general_t *v, int w) \n{\n  if (w <= 32) {\n    return (v->w32 == 1);\n  } else if (w <= 64) {\n    return (v->w64 == 1);\n  } else {\n    return (v->w128[0] == 0 && v->w128[1] == 1);\n  }\n}\n\nvoid gf_general_set_random(gf_general_t *v, int w, int zero_ok) \n{\n  if (w <= 32) {\n      v->w32 = MOA_Random_W(w, zero_ok);\n  } else if (w <= 64) {\n    while (1) {\n      v->w64 = MOA_Random_64();\n      if (v->w64 != 0 || zero_ok) return;\n    }\n  } else {\n    while (1) {\n      MOA_Random_128(v->w128);\n      if (v->w128[0] != 0 || v->w128[1] != 0 || zero_ok) return;\n    }\n  }\n}\n\nvoid gf_general_val_to_s(gf_general_t *v, int w, char *s, int hex)\n{\n  if (w <= 32) {\n    if (hex) {\n      sprintf(s, \"%x\", v->w32);\n    } else {\n      sprintf(s, \"%u\", v->w32);\n    }\n  } else if (w <= 64) {\n    if (hex) {\n      sprintf(s, \"%llx\", (long long unsigned int) v->w64);\n    } else {\n      sprintf(s, \"%lld\", (long long unsigned int) v->w64);\n    }\n  } else {\n    if (v->w128[0] == 0) {\n      sprintf(s, \"%llx\", (long long unsigned int) v->w128[1]);\n    } else {\n      sprintf(s, \"%llx%016llx\", (long long unsigned int) v->w128[0], \n                                (long long unsigned int) v->w128[1]);\n    }\n  }\n}\n\nint gf_general_s_to_val(gf_general_t *v, int w, char *s, int hex)\n{\n  int l;\n  int save;\n\n  if (w <= 32) {\n    if (hex) {\n      if (sscanf(s, \"%x\", &(v->w32)) == 0) return 0;\n    } else {\n      if (sscanf(s, \"%u\", &(v->w32)) == 0) return 0;\n    }\n    if (w == 32) return 1;\n    if (w == 31) {\n      if (v->w32 & (1 << 31)) return 0;\n      return 1;\n    } \n    if (v->w32 & ~((1 << w)-1)) return 0;\n    return 1;\n  } else if (w <= 64) {\n    if (hex) return (sscanf(s, \"%llx\", (long long unsigned int *) (&(v->w64))) == 1);\n    return (sscanf(s, \"%lld\", (long long int *) (&(v->w64))) == 1);\n  } else {\n    if (!hex) return 0;\n    l = strlen(s);\n    if (l <= 16) {\n      v->w128[0] = 0;\n      return (sscanf(s, \"%llx\", (long long unsigned int *) (&(v->w128[1]))) == 1);\n    } else {\n      if (l > 32) return 0;\n      save = s[l-16];\n      s[l-16] = '\\0';\n      if (sscanf(s, \"%llx\", (long long unsigned int *) (&(v->w128[0]))) == 0) {\n        s[l-16] = save;\n        return 0;\n      }\n      return (sscanf(s+(l-16), \"%llx\", (long long unsigned int *) (&(v->w128[1]))) == 1);\n    }\n  }\n}\n    \nvoid gf_general_add(gf_t *gf, gf_general_t *a, gf_general_t *b, gf_general_t *c)\n{\n  gf_internal_t *h;\n  int w;\n\n  h = (gf_internal_t *) gf->scratch;\n  w = h->w;\n\n  if (w <= 32) {\n    c->w32 = a->w32 ^ b->w32;\n  } else if (w <= 64) {\n    c->w64 = a->w64 ^ b->w64;\n  } else {\n    c->w128[0] = a->w128[0] ^ b->w128[0];\n    c->w128[1] = a->w128[1] ^ b->w128[1];\n  }\n}\n  \nvoid gf_general_multiply(gf_t *gf, gf_general_t *a, gf_general_t *b, gf_general_t *c)\n{\n  gf_internal_t *h;\n  int w;\n\n  h = (gf_internal_t *) gf->scratch;\n  w = h->w;\n\n  if (w <= 32) {\n    c->w32 = gf->multiply.w32(gf, a->w32, b->w32);\n  } else if (w <= 64) {\n    c->w64 = gf->multiply.w64(gf, a->w64, b->w64);\n  } else {\n    gf->multiply.w128(gf, a->w128, b->w128, c->w128);\n  }\n}\n  \nvoid gf_general_divide(gf_t *gf, gf_general_t *a, gf_general_t *b, gf_general_t *c)\n{\n  gf_internal_t *h;\n  int w;\n\n  h = (gf_internal_t *) gf->scratch;\n  w = h->w;\n\n  if (w <= 32) {\n    c->w32 = gf->divide.w32(gf, a->w32, b->w32);\n  } else if (w <= 64) {\n    c->w64 = gf->divide.w64(gf, a->w64, b->w64);\n  } else {\n    gf->divide.w128(gf, a->w128, b->w128, c->w128);\n  }\n}\n  \nvoid gf_general_inverse(gf_t *gf, gf_general_t *a, gf_general_t *b)\n{\n  gf_internal_t *h;\n  int w;\n\n  h = (gf_internal_t *) gf->scratch;\n  w = h->w;\n\n  if (w <= 32) {\n    b->w32 = gf->inverse.w32(gf, a->w32);\n  } else if (w <= 64) {\n    b->w64 = gf->inverse.w64(gf, a->w64);\n  } else {\n    gf->inverse.w128(gf, a->w128, b->w128);\n  }\n}\n  \nint gf_general_are_equal(gf_general_t *v1, gf_general_t *v2, int w)\n{\n  if (w <= 32) {\n    return (v1->w32 == v2->w32);\n  } else if (w <= 64) {\n    return (v1->w64 == v2->w64);\n  } else {\n    return (v1->w128[0] == v2->w128[0] &&\n            v1->w128[1] == v2->w128[1]);\n  }\n}\n\nvoid gf_general_do_region_multiply(gf_t *gf, gf_general_t *a, void *ra, void *rb, int bytes, int xor)\n{\n  gf_internal_t *h;\n  int w;\n\n  h = (gf_internal_t *) gf->scratch;\n  w = h->w;\n\n  if (w <= 32) {\n    gf->multiply_region.w32(gf, ra, rb, a->w32, bytes, xor);\n  } else if (w <= 64) {\n    gf->multiply_region.w64(gf, ra, rb, a->w64, bytes, xor);\n  } else {\n    gf->multiply_region.w128(gf, ra, rb, a->w128, bytes, xor);\n  }\n}\n\nvoid gf_general_do_region_check(gf_t *gf, gf_general_t *a, void *orig_a, void *orig_target, void *final_target, int bytes, int xor)\n{\n  gf_internal_t *h;\n  int w, words, i;\n  gf_general_t oa, ot, ft, sb;\n  char sa[50], soa[50], sot[50], sft[50], ssb[50];\n\n  h = (gf_internal_t *) gf->scratch;\n  w = h->w;\n\n  words = (bytes * 8) / w;\n  for (i = 0; i < words; i++) {\n    if (w <= 32) {\n      oa.w32 = gf->extract_word.w32(gf, orig_a, bytes, i);\n      ot.w32 = gf->extract_word.w32(gf, orig_target, bytes, i);\n      ft.w32 = gf->extract_word.w32(gf, final_target, bytes, i);\n      sb.w32 = gf->multiply.w32(gf, a->w32, oa.w32);\n      if (xor) sb.w32 ^= ot.w32;\n    } else if (w <= 64) {\n      oa.w64 = gf->extract_word.w64(gf, orig_a, bytes, i);\n      ot.w64 = gf->extract_word.w64(gf, orig_target, bytes, i);\n      ft.w64 = gf->extract_word.w64(gf, final_target, bytes, i);\n      sb.w64 = gf->multiply.w64(gf, a->w64, oa.w64);\n      if (xor) sb.w64 ^= ot.w64;\n    } else {\n      gf->extract_word.w128(gf, orig_a, bytes, i, oa.w128);\n      gf->extract_word.w128(gf, orig_target, bytes, i, ot.w128);\n      gf->extract_word.w128(gf, final_target, bytes, i, ft.w128);\n      gf->multiply.w128(gf, a->w128, oa.w128, sb.w128);\n      if (xor) {\n        sb.w128[0] ^= ot.w128[0];\n        sb.w128[1] ^= ot.w128[1];\n      }\n    }\n\n    if (!gf_general_are_equal(&ft, &sb, w)) {\n      \n      fprintf(stderr,\"Problem with region multiply (all values in hex):\\n\");\n      fprintf(stderr,\"   Target address base: 0x%lx.  Word 0x%x of 0x%x.  Xor: %d\\n\", \n                 (unsigned long) final_target, i, words, xor);\n      gf_general_val_to_s(a, w, sa, 1);\n      gf_general_val_to_s(&oa, w, soa, 1);\n      gf_general_val_to_s(&ot, w, sot, 1);\n      gf_general_val_to_s(&ft, w, sft, 1);\n      gf_general_val_to_s(&sb, w, ssb, 1);\n      fprintf(stderr,\"   Value: %s\\n\", sa);\n      fprintf(stderr,\"   Original source word: %s\\n\", soa);\n      if (xor) fprintf(stderr,\"   XOR with target word: %s\\n\", sot);\n      fprintf(stderr,\"   Product word: %s\\n\", sft);\n      fprintf(stderr,\"   It should be: %s\\n\", ssb);\n      assert(0);\n    }\n  }\n}\n\nvoid gf_general_set_up_single_timing_test(int w, void *ra, void *rb, int size)\n{\n  void *top;\n  gf_general_t g;\n  uint8_t *r8, *r8a;\n  uint16_t *r16;\n  uint32_t *r32;\n  uint64_t *r64;\n  int i;\n\n  top = (uint8_t *)rb+size;\n\n  /* If w is 8, 16, 32, 64 or 128, fill the regions with random bytes.\n     However, don't allow for zeros in rb, because that will screw up\n     division.\n     \n     When w is 4, you fill the regions with random 4-bit words in each byte.\n\n     Otherwise, treat every four bytes as an uint32_t\n     and fill it with a random value mod (1 << w).\n   */\n\n  if (w == 8 || w == 16 || w == 32 || w == 64 || w == 128) {\n    MOA_Fill_Random_Region (ra, size);\n    while (rb < top) {\n      gf_general_set_random(&g, w, 0);\n      switch (w) {\n        case 8: \n          r8 = (uint8_t *) rb;\n          *r8 = g.w32;\n          break;\n        case 16: \n          r16 = (uint16_t *) rb;\n          *r16 = g.w32;\n          break;\n        case 32: \n          r32 = (uint32_t *) rb;\n          *r32 = g.w32;\n          break;\n        case 64:\n          r64 = (uint64_t *) rb;\n          *r64 = g.w64;\n          break;\n        case 128: \n          r64 = (uint64_t *) rb;\n          r64[0] = g.w128[0];\n          r64[1] = g.w128[1];\n          break;\n      }\n      rb = (uint8_t *)rb + (w/8);\n    }\n  } else if (w == 4) {\n    r8a = (uint8_t *) ra;\n    r8 = (uint8_t *) rb;\n    while (r8 < (uint8_t *) top) {\n      gf_general_set_random(&g, w, 1);\n      *r8a = g.w32;\n      gf_general_set_random(&g, w, 0);\n      *r8 = g.w32;\n      r8a++;\n      r8++;\n    }\n  } else {\n    r32 = (uint32_t *) ra;\n    for (i = 0; i < size/4; i++) r32[i] = MOA_Random_W(w, 1);\n    r32 = (uint32_t *) rb;\n    for (i = 0; i < size/4; i++) r32[i] = MOA_Random_W(w, 0);\n  }\n}\n\n/* This sucks, but in order to time, you really need to avoid putting ifs in \n   the inner loops.  So, I'm doing a separate timing test for each w: \n   (4 & 8), 16, 32, 64, 128 and everything else.  Fortunately, the \"everything else\"\n   tests can be equivalent to w=32.\n\n   I'm also putting the results back into ra, because otherwise, the optimizer might\n   figure out that we're not really doing anything in the inner loops and it \n   will chuck that. */\n\nint gf_general_do_single_timing_test(gf_t *gf, void *ra, void *rb, int size, char test)\n{\n  gf_internal_t *h;\n  void *top;\n  uint8_t *r8a, *r8b, *top8;\n  uint16_t *r16a, *r16b, *top16;\n  uint32_t *r32a, *r32b, *top32;\n  uint64_t *r64a, *r64b, *top64, *r64c;\n  int w, rv;\n\n  h = (gf_internal_t *) gf->scratch;\n  w = h->w;\n  top = (uint8_t *)ra + size;\n\n  if (w == 8 || w == 4) {\n    r8a = (uint8_t *) ra; \n    r8b = (uint8_t *) rb; \n    top8 = (uint8_t *) top;\n    if (test == 'M') {\n      while (r8a < top8) {\n        *r8a = gf->multiply.w32(gf, *r8a, *r8b);\n        r8a++;\n        r8b++;\n      }\n    } else if (test == 'D') {\n      while (r8a < top8) {\n        *r8a = gf->divide.w32(gf, *r8a, *r8b);\n        r8a++;\n        r8b++;\n      }\n    } else if (test == 'I') {\n      while (r8a < top8) {\n        *r8a = gf->inverse.w32(gf, *r8a);\n        r8a++;\n      }\n    }\n    return (top8 - (uint8_t *) ra);\n  }\n\n  if (w == 16) {\n    r16a = (uint16_t *) ra; \n    r16b = (uint16_t *) rb; \n    top16 = (uint16_t *) top;\n    if (test == 'M') {\n      while (r16a < top16) {\n        *r16a = gf->multiply.w32(gf, *r16a, *r16b);\n        r16a++;\n        r16b++;\n      }\n    } else if (test == 'D') {\n      while (r16a < top16) {\n        *r16a = gf->divide.w32(gf, *r16a, *r16b);\n        r16a++;\n        r16b++;\n      }\n    } else if (test == 'I') {\n      while (r16a < top16) {\n        *r16a = gf->inverse.w32(gf, *r16a);\n        r16a++;\n      }\n    }\n    return (top16 - (uint16_t *) ra);\n  }\n  if (w <= 32) {\n    r32a = (uint32_t *) ra; \n    r32b = (uint32_t *) rb; \n    top32 = (uint32_t *) ra + (size/4); /* This is for the \"everything elses\" */\n    \n    if (test == 'M') {\n      while (r32a < top32) {\n        *r32a = gf->multiply.w32(gf, *r32a, *r32b);\n        r32a++;\n        r32b++;\n      }\n    } else if (test == 'D') {\n      while (r32a < top32) {\n        *r32a = gf->divide.w32(gf, *r32a, *r32b);\n        r32a++;\n        r32b++;\n      }\n    } else if (test == 'I') {\n      while (r32a < top32) {\n        *r32a = gf->inverse.w32(gf, *r32a);\n        r32a++;\n      }\n    }\n    return (top32 - (uint32_t *) ra);\n  }\n  if (w == 64) {\n    r64a = (uint64_t *) ra; \n    r64b = (uint64_t *) rb; \n    top64 = (uint64_t *) top;\n    if (test == 'M') {\n      while (r64a < top64) {\n        *r64a = gf->multiply.w64(gf, *r64a, *r64b);\n        r64a++;\n        r64b++;\n      }\n    } else if (test == 'D') {\n      while (r64a < top64) {\n        *r64a = gf->divide.w64(gf, *r64a, *r64b);\n        r64a++;\n        r64b++;\n      }\n    } else if (test == 'I') {\n      while (r64a < top64) {\n        *r64a = gf->inverse.w64(gf, *r64a);\n        r64a++;\n      }\n    }\n    return (top64 - (uint64_t *) ra);\n  }\n  if (w == 128) {\n    r64a = (uint64_t *) ra; \n    r64c = r64a;\n    r64a += 2;\n    r64b = (uint64_t *) rb; \n    top64 = (uint64_t *) top;\n    rv = (top64 - r64a)/2;\n    if (test == 'M') {\n      while (r64a < top64) {\n        gf->multiply.w128(gf, r64a, r64b, r64c);\n        r64a += 2;\n        r64b += 2;\n      }\n    } else if (test == 'D') {\n      while (r64a < top64) {\n        gf->divide.w128(gf, r64a, r64b, r64c);\n        r64a += 2;\n        r64b += 2;\n      }\n    } else if (test == 'I') {\n      while (r64a < top64) {\n        gf->inverse.w128(gf, r64a, r64c);\n        r64a += 2;\n      }\n    }\n    return rv;\n  }\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/src/gf_method.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_method.c\n *\n * Parses argv to figure out the mult_type and arguments.  Returns the gf.\n */\n\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n\n#include \"gf_complete.h\"\n#include \"gf_int.h\"\n#include \"gf_method.h\"\n\nint create_gf_from_argv(gf_t *gf, int w, int argc, char **argv, int starting)\n{\n  int mult_type, divide_type, region_type;\n  int arg1, arg2;\n  uint64_t prim_poly;\n  gf_t *base;\n\n  mult_type = GF_MULT_DEFAULT;\n  region_type = GF_REGION_DEFAULT;\n  divide_type = GF_DIVIDE_DEFAULT;\n  prim_poly = 0;\n  base = NULL;\n  arg1 = 0;\n  arg2 = 0;\n  while (1) {\n    if (argc > starting) {\n      if (strcmp(argv[starting], \"-m\") == 0) {\n        starting++;\n        if (mult_type != GF_MULT_DEFAULT) {\n          if (base != NULL) gf_free(base, 1);\n          _gf_errno = GF_E_TWOMULT;\n          return 0;\n        }\n        if (strcmp(argv[starting], \"SHIFT\") == 0) {\n          mult_type = GF_MULT_SHIFT;\n          starting++;\n        } else if (strcmp(argv[starting], \"CARRY_FREE\") == 0) {\n          mult_type = GF_MULT_CARRY_FREE;\n          starting++;\n        } else if (strcmp(argv[starting], \"CARRY_FREE_GK\") == 0) {\n          mult_type = GF_MULT_CARRY_FREE_GK;\n          starting++;\n        } else if (strcmp(argv[starting], \"GROUP\") == 0) {\n          mult_type = GF_MULT_GROUP;\n          if (argc < starting + 3) {\n            _gf_errno = GF_E_GROUPAR;\n            return 0;\n          }\n          if (sscanf(argv[starting+1], \"%d\", &arg1) == 0 ||\n              sscanf(argv[starting+2], \"%d\", &arg2) == 0) {\n            _gf_errno = GF_E_GROUPNU;\n            return 0;\n          }\n          starting += 3;\n        } else if (strcmp(argv[starting], \"BYTWO_p\") == 0) {\n          mult_type = GF_MULT_BYTWO_p;\n          starting++;\n        } else if (strcmp(argv[starting], \"BYTWO_b\") == 0) {\n          mult_type = GF_MULT_BYTWO_b;\n          starting++;\n        } else if (strcmp(argv[starting], \"TABLE\") == 0) {\n          mult_type = GF_MULT_TABLE;\n          starting++;\n        } else if (strcmp(argv[starting], \"LOG\") == 0) {\n          mult_type = GF_MULT_LOG_TABLE;\n          starting++;\n        } else if (strcmp(argv[starting], \"LOG_ZERO\") == 0) {\n          mult_type = GF_MULT_LOG_ZERO;\n          starting++;\n        } else if (strcmp(argv[starting], \"LOG_ZERO_EXT\") == 0) {\n          mult_type = GF_MULT_LOG_ZERO_EXT;\n          starting++;\n        } else if (strcmp(argv[starting], \"SPLIT\") == 0) {\n          mult_type = GF_MULT_SPLIT_TABLE;\n          if (argc < starting + 3) {\n            _gf_errno = GF_E_SPLITAR;\n            return 0;\n          }\n          if (sscanf(argv[starting+1], \"%d\", &arg1) == 0 ||\n              sscanf(argv[starting+2], \"%d\", &arg2) == 0) {\n            _gf_errno = GF_E_SPLITNU;\n            return 0;\n          }\n          starting += 3;\n        } else if (strcmp(argv[starting], \"COMPOSITE\") == 0) {\n          mult_type = GF_MULT_COMPOSITE;\n          if (argc < starting + 2) { _gf_errno = GF_E_FEWARGS; return 0; }\n          if (sscanf(argv[starting+1], \"%d\", &arg1) == 0) {\n            _gf_errno = GF_E_COMP_A2;\n            return 0;\n          }\n          starting += 2;\n          base = (gf_t *) malloc(sizeof(gf_t));\n          starting = create_gf_from_argv(base, w/arg1, argc, argv, starting);\n          if (starting == 0) {\n            free(base);\n            return 0;\n          }\n        } else {\n          _gf_errno = GF_E_UNKNOWN;\n          return 0;\n        }\n      } else if (strcmp(argv[starting], \"-r\") == 0) {\n        starting++;\n        if (strcmp(argv[starting], \"DOUBLE\") == 0) {\n          region_type |= GF_REGION_DOUBLE_TABLE;\n          starting++;\n        } else if (strcmp(argv[starting], \"QUAD\") == 0) {\n          region_type |= GF_REGION_QUAD_TABLE;\n          starting++;\n        } else if (strcmp(argv[starting], \"LAZY\") == 0) {\n          region_type |= GF_REGION_LAZY;\n          starting++;\n        } else if (strcmp(argv[starting], \"SIMD\") == 0) {\n          region_type |= GF_REGION_SIMD;\n          starting++;\n        } else if (strcmp(argv[starting], \"NOSIMD\") == 0) {\n          region_type |= GF_REGION_NOSIMD;\n          starting++;\n        } else if (strcmp(argv[starting], \"SSE\") == 0) {\n          region_type |= GF_REGION_SIMD;\n          starting++;\n        } else if (strcmp(argv[starting], \"NOSSE\") == 0) {\n          region_type |= GF_REGION_NOSIMD;\n          starting++;\n        } else if (strcmp(argv[starting], \"CAUCHY\") == 0) {\n          region_type |= GF_REGION_CAUCHY;\n          starting++;\n        } else if (strcmp(argv[starting], \"ALTMAP\") == 0) {\n          region_type |= GF_REGION_ALTMAP;\n          starting++;\n        } else {\n          if (base != NULL) gf_free(base, 1);\n          _gf_errno = GF_E_UNK_REG;\n          return 0;\n        }\n      } else if (strcmp(argv[starting], \"-p\") == 0) {\n        starting++;\n        if (sscanf(argv[starting], \"%llx\", (long long unsigned int *)(&prim_poly)) == 0) {\n          if (base != NULL) gf_free(base, 1);\n          _gf_errno = GF_E_POLYSPC;\n          return 0;\n        }\n        starting++;\n      } else if (strcmp(argv[starting], \"-d\") == 0) {\n        starting++;\n        if (divide_type != GF_DIVIDE_DEFAULT) {\n          if (base != NULL) gf_free(base, 1);\n          _gf_errno = GF_E_TWO_DIV;\n          return 0;\n        } else if (strcmp(argv[starting], \"EUCLID\") == 0) {\n          divide_type = GF_DIVIDE_EUCLID;\n          starting++;\n        } else if (strcmp(argv[starting], \"MATRIX\") == 0) {\n          divide_type = GF_DIVIDE_MATRIX;\n          starting++;\n        } else {\n          _gf_errno = GF_E_UNK_DIV;\n          return 0;\n        }\n      } else if (strcmp(argv[starting], \"-\") == 0) {\n         /*\n         printf(\"Scratch size: %d\\n\", gf_scratch_size(w, \n                                      mult_type, region_type, divide_type, arg1, arg2));\n         */\n        if (gf_init_hard(gf, w, mult_type, region_type, divide_type, \n                         prim_poly, arg1, arg2, base, NULL) == 0) {\n          if (base != NULL) gf_free(base, 1);\n          return 0;\n        } else\n          return starting + 1;\n      } else {\n        if (base != NULL) gf_free(base, 1);\n        _gf_errno = GF_E_UNKFLAG;\n        return 0;\n      }\n    } else {\n      if (base != NULL) gf_free(base, 1);\n      _gf_errno = GF_E_FEWARGS;\n      return 0;\n    }\n  }\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/src/gf_rand.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_rand.c -- Random number generator.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include \"gf_rand.h\"\n\n/* Lifted the \"Mother of All\" random number generator from http://www.agner.org/random/ */\n\nstatic uint32_t MOA_X[5];\n\nuint32_t MOA_Random_32() {\n  uint64_t sum;\n  sum = (uint64_t)2111111111UL * (uint64_t)MOA_X[3] +\n     (uint64_t)1492 * (uint64_t)(MOA_X[2]) +\n     (uint64_t)1776 * (uint64_t)(MOA_X[1]) +\n     (uint64_t)5115 * (uint64_t)(MOA_X[0]) +\n     (uint64_t)MOA_X[4];\n  MOA_X[3] = MOA_X[2];  MOA_X[2] = MOA_X[1];  MOA_X[1] = MOA_X[0];\n  MOA_X[4] = (uint32_t)(sum >> 32);\n  MOA_X[0] = (uint32_t)sum;\n  return MOA_X[0];\n}\n\nuint64_t MOA_Random_64() {\n  uint64_t sum;\n\n  sum = MOA_Random_32();\n  sum <<= 32;\n  sum |= MOA_Random_32();\n  return sum;\n}\n\nvoid MOA_Random_128(uint64_t *x) {\n  x[0] = MOA_Random_64();\n  x[1] = MOA_Random_64();\n  return;\n}\n\nuint32_t MOA_Random_W(int w, int zero_ok)\n{\n  uint32_t b;\n\n  do {\n    b = MOA_Random_32();\n    if (w == 31) b &= 0x7fffffff;\n    if (w < 31)  b %= (1 << w);\n  } while (!zero_ok && b == 0);\n  return b;\n}\n\nvoid MOA_Seed(uint32_t seed) {\n  int i;\n  uint32_t s = seed;\n  for (i = 0; i < 5; i++) {\n    s = s * 29943829 - 1;\n    MOA_X[i] = s;\n  }\n  for (i=0; i<19; i++) MOA_Random_32();\n}\n\n\nvoid MOA_Fill_Random_Region (void *reg, int size)\n{\n  uint32_t *r32;\n  uint8_t *r8;\n  int i;\n\n  r32 = (uint32_t *) reg;\n  r8 = (uint8_t *) reg;\n  for (i = 0; i < size/4; i++) r32[i] = MOA_Random_32();\n  for (i *= 4; i < size; i++) r8[i] = MOA_Random_W(8, 1);\n}\n\n"
  },
  {
    "path": "fst/layout/gf-complete/src/gf_w128.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_w128.c\n *\n * Routines for 128-bit Galois fields\n */\n\n#include \"gf_int.h\"\n#include <stdio.h>\n#include <stdlib.h>\n\n#define GF_FIELD_WIDTH (128)\n\n#define two_x(a) {\\\n  a[0] <<= 1; \\\n  if (a[1] & 1ULL << 63) a[0] ^= 1; \\\n  a[1] <<= 1; }\n  \n#define a_get_b(a, i, b, j) {\\\n  a[i] = b[j]; \\\n  a[i + 1] = b[j + 1];}\n\n#define set_zero(a, i) {\\\n  a[i] = 0; \\\n  a[i + 1] = 0;}\n\nstruct gf_w128_split_4_128_data {\n  uint64_t last_value[2];\n  uint64_t tables[2][32][16];\n};\n\nstruct gf_w128_split_8_128_data {\n  uint64_t last_value[2];\n  uint64_t tables[2][16][256];\n};\n\ntypedef struct gf_group_tables_s {\n  gf_val_128_t m_table;\n  gf_val_128_t r_table;\n} gf_group_tables_t;\n\n#define MM_PRINT8(s, r) { uint8_t blah[16], ii; printf(\"%-12s\", s); _mm_storeu_si128((__m128i *)blah, r); for (ii = 0; ii < 16; ii += 1) printf(\"%s%02x\", (ii%4==0) ? \"   \" : \" \", blah[15-ii]); printf(\"\\n\"); }\n\nstatic\nvoid\ngf_w128_multiply_region_from_single(gf_t *gf, void *src, void *dest, gf_val_128_t val, int bytes,\nint xor)\n{\n    uint32_t i;\n    gf_val_128_t s128;\n    gf_val_128_t d128;\n    uint64_t c128[2];\n    gf_region_data rd;\n\n    /* We only do this to check on alignment. */\n    gf_set_region_data(&rd, gf, src, dest, bytes, 0, xor, 8);\n\n    if (val[0] == 0) {\n      if (val[1] == 0) { gf_multby_zero(dest, bytes, xor); return; }\n      if (val[1] == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n    }\n\n    set_zero(c128, 0);\n\n    s128 = (gf_val_128_t) src;\n    d128 = (gf_val_128_t) dest;\n\n    if (xor) {\n      for (i = 0; i < bytes/sizeof(gf_val_64_t); i += 2) {\n        gf->multiply.w128(gf, &s128[i], val, c128);\n        d128[i] ^= c128[0];\n        d128[i+1] ^= c128[1];\n      }\n    } else {\n      for (i = 0; i < bytes/sizeof(gf_val_64_t); i += 2) {\n        gf->multiply.w128(gf, &s128[i], val, &d128[i]);\n      }\n    }\n}\n\n#if defined(INTEL_SSE4_PCLMUL)\nstatic\nvoid\ngf_w128_clm_multiply_region_from_single(gf_t *gf, void *src, void *dest, gf_val_128_t val, int bytes,\nint xor)\n{\n    uint32_t i;\n    gf_val_128_t s128;\n    gf_val_128_t d128;\n    gf_region_data rd;\n    __m128i     a,b;\n    __m128i     result0,result1;\n    __m128i     prim_poly;\n    __m128i     c,d,e,f;\n    gf_internal_t * h = gf->scratch;\n    prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)h->prim_poly);\n    /* We only do this to check on alignment. */\n    gf_set_region_data(&rd, gf, src, dest, bytes, 0, xor, 8);\n\n    if (val[0] == 0) {\n      if (val[1] == 0) { gf_multby_zero(dest, bytes, xor); return; }\n      if (val[1] == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n    }\n\n    s128 = (gf_val_128_t) src;\n    d128 = (gf_val_128_t) dest;\n\n    if (xor) {\n      for (i = 0; i < bytes/sizeof(gf_val_64_t); i += 2) {\n        a = _mm_insert_epi64 (_mm_setzero_si128(), s128[i+1], 0);\n        b = _mm_insert_epi64 (a, val[1], 0);\n        a = _mm_insert_epi64 (a, s128[i], 1);\n        b = _mm_insert_epi64 (b, val[0], 1);\n    \n        c = _mm_clmulepi64_si128 (a, b, 0x00); /*low-low*/\n        f = _mm_clmulepi64_si128 (a, b, 0x01); /*high-low*/\n        e = _mm_clmulepi64_si128 (a, b, 0x10); /*low-high*/\n        d = _mm_clmulepi64_si128 (a, b, 0x11); /*high-high*/\n\n        /* now reusing a and b as temporary variables*/\n        result0 = _mm_setzero_si128();\n        result1 = result0;\n\n        result0 = _mm_xor_si128 (result0, _mm_insert_epi64 (d, 0, 0));\n        a = _mm_xor_si128 (_mm_srli_si128 (e, 8), _mm_insert_epi64 (d, 0, 1));\n        result0 = _mm_xor_si128 (result0, _mm_xor_si128 (_mm_srli_si128 (f, 8), a));\n\n        a = _mm_xor_si128 (_mm_slli_si128 (e, 8), _mm_insert_epi64 (c, 0, 0));\n        result1 = _mm_xor_si128 (result1, _mm_xor_si128 (_mm_slli_si128 (f, 8), a));\n        result1 = _mm_xor_si128 (result1, _mm_insert_epi64 (c, 0, 1));\n        /* now we have constructed our 'result' with result0 being the carry bits, and we have to reduce. */\n\n        a = _mm_srli_si128 (result0, 8);\n        b = _mm_clmulepi64_si128 (a, prim_poly, 0x00);\n        result0 = _mm_xor_si128 (result0, _mm_srli_si128 (b, 8));\n        result1 = _mm_xor_si128 (result1, _mm_slli_si128 (b, 8));\n\n        a = _mm_insert_epi64 (result0, 0, 1);\n        b = _mm_clmulepi64_si128 (a, prim_poly, 0x00);\n        result1 = _mm_xor_si128 (result1, b); \n        d128[i] ^= (uint64_t)_mm_extract_epi64(result1,1);\n        d128[i+1] ^= (uint64_t)_mm_extract_epi64(result1,0);\n      }\n    } else {\n      for (i = 0; i < bytes/sizeof(gf_val_64_t); i += 2) {\n        a = _mm_insert_epi64 (_mm_setzero_si128(), s128[i+1], 0);\n        b = _mm_insert_epi64 (a, val[1], 0);\n        a = _mm_insert_epi64 (a, s128[i], 1);\n        b = _mm_insert_epi64 (b, val[0], 1);\n\n        c = _mm_clmulepi64_si128 (a, b, 0x00); /*low-low*/\n        f = _mm_clmulepi64_si128 (a, b, 0x01); /*high-low*/\n        e = _mm_clmulepi64_si128 (a, b, 0x10); /*low-high*/ \n        d = _mm_clmulepi64_si128 (a, b, 0x11); /*high-high*/ \n\n        /* now reusing a and b as temporary variables*/\n        result0 = _mm_setzero_si128();\n        result1 = result0;\n\n        result0 = _mm_xor_si128 (result0, _mm_insert_epi64 (d, 0, 0));\n        a = _mm_xor_si128 (_mm_srli_si128 (e, 8), _mm_insert_epi64 (d, 0, 1));\n        result0 = _mm_xor_si128 (result0, _mm_xor_si128 (_mm_srli_si128 (f, 8), a));\n\n        a = _mm_xor_si128 (_mm_slli_si128 (e, 8), _mm_insert_epi64 (c, 0, 0));\n        result1 = _mm_xor_si128 (result1, _mm_xor_si128 (_mm_slli_si128 (f, 8), a));\n        result1 = _mm_xor_si128 (result1, _mm_insert_epi64 (c, 0, 1));\n        /* now we have constructed our 'result' with result0 being the carry bits, and we have to reduce.*/\n\n        a = _mm_srli_si128 (result0, 8);\n        b = _mm_clmulepi64_si128 (a, prim_poly, 0x00);\n        result0 = _mm_xor_si128 (result0, _mm_srli_si128 (b, 8));\n        result1 = _mm_xor_si128 (result1, _mm_slli_si128 (b, 8));\n\n        a = _mm_insert_epi64 (result0, 0, 1);\n        b = _mm_clmulepi64_si128 (a, prim_poly, 0x00);\n        result1 = _mm_xor_si128 (result1, b);\n        d128[i] = (uint64_t)_mm_extract_epi64(result1,1);\n        d128[i+1] = (uint64_t)_mm_extract_epi64(result1,0);\n      }\n    }\n}\n#endif\n\n/*\n * Some w128 notes:\n * --Big Endian\n * --return values allocated beforehand\n */\n\n#define GF_W128_IS_ZERO(val) (val[0] == 0 && val[1] == 0)\n\nvoid\ngf_w128_shift_multiply(gf_t *gf, gf_val_128_t a128, gf_val_128_t b128, gf_val_128_t c128)\n{\n  /* ordered highest bit to lowest l[0] l[1] r[0] r[1] */\n  uint64_t pl[2], pr[2], ppl[2], ppr[2], i, a[2], bl[2], br[2], one, lbit;\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n\n  if (GF_W128_IS_ZERO(a128) || GF_W128_IS_ZERO(b128)) {\n    set_zero(c128, 0);\n    return;\n  }\n\n  a_get_b(a, 0, a128, 0);\n  a_get_b(br, 0, b128, 0);\n  set_zero(bl, 0);\n\n  one = 1;\n  lbit = (one << 63);\n\n  set_zero(pl, 0);\n  set_zero(pr, 0);\n\n  /* Allen: a*b for right half of a */\n  for (i = 0; i < GF_FIELD_WIDTH/2; i++) {\n    if (a[1] & (one << i)) {\n      pl[1] ^= bl[1];\n      pr[0] ^= br[0];\n      pr[1] ^= br[1];\n    }\n    bl[1] <<= 1;\n    if (br[0] & lbit) bl[1] ^= 1;\n    br[0] <<= 1;\n    if (br[1] & lbit) br[0] ^= 1;\n    br[1] <<= 1;\n  }\n\n  /* Allen: a*b for left half of a */\n  for (i = 0; i < GF_FIELD_WIDTH/2; i++) {\n    if (a[0] & (one << i)) {\n      pl[0] ^= bl[0];\n      pl[1] ^= bl[1];\n      pr[0] ^= br[0];\n    }\n    bl[0] <<= 1;\n    if (bl[1] & lbit) bl[0] ^= 1;\n    bl[1] <<= 1;\n    if (br[0] & lbit) bl[1] ^= 1;\n    br[0] <<= 1;\n  }\n\n  /* Allen: do first half of reduction (based on left quarter of initial product) */\n  one = lbit >> 1;\n  ppl[0] = one; /* Allen: introduce leading one of primitive polynomial */\n  ppl[1] = h->prim_poly >> 2;\n  ppr[0] = h->prim_poly << (GF_FIELD_WIDTH/2-2);\n  ppr[1] = 0;\n  while (one != 0) {\n    if (pl[0] & one) {\n      pl[0] ^= ppl[0];\n      pl[1] ^= ppl[1];\n      pr[0] ^= ppr[0];\n      pr[1] ^= ppr[1];\n    }\n    one >>= 1;\n    ppr[1] >>= 1;\n    if (ppr[0] & 1) ppr[1] ^= lbit;\n    ppr[0] >>= 1;\n    if (ppl[1] & 1) ppr[0] ^= lbit;\n    ppl[1] >>= 1;\n    if (ppl[0] & 1) ppl[1] ^= lbit;\n    ppl[0] >>= 1;\n  }\n\n  /* Allen: final half of reduction */\n  one = lbit;\n  while (one != 0) {\n    if (pl[1] & one) {\n      pl[1] ^= ppl[1];\n      pr[0] ^= ppr[0];\n      pr[1] ^= ppr[1];\n    }\n    one >>= 1;\n    ppr[1] >>= 1;\n    if (ppr[0] & 1) ppr[1] ^= lbit;\n    ppr[0] >>= 1;\n    if (ppl[1] & 1) ppr[0] ^= lbit;\n    ppl[1] >>= 1;\n  }\n\n  /* Allen: if we really want to optimize this we can just be using c128 instead of pr all along */\n  c128[0] = pr[0];\n  c128[1] = pr[1];\n\n  return;\n}\n\nvoid\ngf_w128_clm_multiply(gf_t *gf, gf_val_128_t a128, gf_val_128_t b128, gf_val_128_t c128)\n{\n#if defined(INTEL_SSE4_PCLMUL)\n\n    __m128i     a,b;\n    __m128i     result0,result1;\n    __m128i     prim_poly;\n    __m128i     c,d,e,f;\n    gf_internal_t * h = gf->scratch;\n    \n    a = _mm_insert_epi64 (_mm_setzero_si128(), a128[1], 0);\n    b = _mm_insert_epi64 (a, b128[1], 0);\n    a = _mm_insert_epi64 (a, a128[0], 1);\n    b = _mm_insert_epi64 (b, b128[0], 1);\n\n    prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)h->prim_poly);\n\n    /* we need to test algorithm 2 later*/\n    c = _mm_clmulepi64_si128 (a, b, 0x00); /*low-low*/\n    f = _mm_clmulepi64_si128 (a, b, 0x01); /*high-low*/\n    e = _mm_clmulepi64_si128 (a, b, 0x10); /*low-high*/\n    d = _mm_clmulepi64_si128 (a, b, 0x11); /*high-high*/\n    \n    /* now reusing a and b as temporary variables*/\n    result0 = _mm_setzero_si128();\n    result1 = result0;\n\n    result0 = _mm_xor_si128 (result0, _mm_insert_epi64 (d, 0, 0));\n    a = _mm_xor_si128 (_mm_srli_si128 (e, 8), _mm_insert_epi64 (d, 0, 1));\n    result0 = _mm_xor_si128 (result0, _mm_xor_si128 (_mm_srli_si128 (f, 8), a));\n\n    a = _mm_xor_si128 (_mm_slli_si128 (e, 8), _mm_insert_epi64 (c, 0, 0));\n    result1 = _mm_xor_si128 (result1, _mm_xor_si128 (_mm_slli_si128 (f, 8), a));\n    result1 = _mm_xor_si128 (result1, _mm_insert_epi64 (c, 0, 1));\n    /* now we have constructed our 'result' with result0 being the carry bits, and we have to reduce.*/\n    \n    a = _mm_srli_si128 (result0, 8);\n    b = _mm_clmulepi64_si128 (a, prim_poly, 0x00);\n    result0 = _mm_xor_si128 (result0, _mm_srli_si128 (b, 8));\n    result1 = _mm_xor_si128 (result1, _mm_slli_si128 (b, 8));\n    \n    a = _mm_insert_epi64 (result0, 0, 1);\n    b = _mm_clmulepi64_si128 (a, prim_poly, 0x00);\n    result1 = _mm_xor_si128 (result1, b);\n\n    c128[0] = (uint64_t)_mm_extract_epi64(result1,1);\n    c128[1] = (uint64_t)_mm_extract_epi64(result1,0);\n#endif\nreturn;\n}\n\nvoid\ngf_w128_bytwo_p_multiply(gf_t *gf, gf_val_128_t a128, gf_val_128_t b128, gf_val_128_t c128)\n{\n  uint64_t amask[2], pmask, pp, prod[2]; /*John: pmask is always the highest bit set, and the rest zeros. amask changes, it's a countdown.*/\n  uint64_t topbit; /* this is used as a boolean value */\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n  prod[0] = 0;\n  prod[1] = 0;\n  pmask = 0x8000000000000000ULL;\n  amask[0] = 0x8000000000000000ULL;\n  amask[1] = 0;\n\n  while (amask[1] != 0 || amask[0] != 0) {\n    topbit = (prod[0] & pmask);\n    prod[0] <<= 1;\n    if (prod[1] & pmask) prod[0] ^= 1;\n    prod[1] <<= 1;\n    if (topbit) prod[1] ^= pp;\n    if ((a128[0] & amask[0]) || (a128[1] & amask[1])) {\n      prod[0] ^= b128[0];\n      prod[1] ^= b128[1];\n    }\n    amask[1] >>= 1;\n    if (amask[0] & 1) amask[1] ^= pmask;\n    amask[0] >>= 1;\n  }\n  c128[0] = prod [0];\n  c128[1] = prod [1];\n  return;\n}\n\nvoid\ngf_w128_sse_bytwo_p_multiply(gf_t *gf, gf_val_128_t a128, gf_val_128_t b128, gf_val_128_t c128)\n{\n#if defined(INTEL_SSE4)\n  int i;\n  __m128i a, b, pp, prod, amask, u_middle_one; \n  /*John: pmask is always the highest bit set, and the rest zeros. amask changes, it's a countdown.*/\n  uint32_t topbit, middlebit, pmask; /* this is used as a boolean value */\n  gf_internal_t *h;\n\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = _mm_set_epi32(0, 0, 0, (uint32_t)h->prim_poly);\n  prod = _mm_setzero_si128();\n  a = _mm_insert_epi64(prod, a128[1], 0x0);\n  a = _mm_insert_epi64(a, a128[0], 0x1);\n  b = _mm_insert_epi64(prod, b128[1], 0x0);\n  b = _mm_insert_epi64(b, b128[0], 0x1);\n  pmask = 0x80000000;\n  amask = _mm_insert_epi32(prod, 0x80000000, 0x3);\n  u_middle_one = _mm_insert_epi32(prod, 1, 0x2);\n  \n  for (i = 0; i < 64; i++) {\n    topbit = (_mm_extract_epi32(prod, 0x3) & pmask);\n    middlebit = (_mm_extract_epi32(prod, 0x1) & pmask);\n    prod = _mm_slli_epi64(prod, 1); /* this instruction loses the middle bit */\n    if (middlebit) {\n      prod = _mm_xor_si128(prod, u_middle_one);\n    }\n    if (topbit) {\n      prod = _mm_xor_si128(prod, pp);\n    }\n    if (((uint64_t)_mm_extract_epi64(_mm_and_si128(a, amask), 1))) {\n      prod = _mm_xor_si128(prod, b);\n    }\n    amask = _mm_srli_epi64(amask, 1); /*so does this one, but we can just replace after loop*/\n  }\n  amask = _mm_insert_epi32(amask, 1 << 31, 0x1);\n  for (i = 64; i < 128; i++) {\n    topbit = (_mm_extract_epi32(prod, 0x3) & pmask);\n    middlebit = (_mm_extract_epi32(prod, 0x1) & pmask);\n    prod = _mm_slli_epi64(prod, 1);\n    if (middlebit) prod = _mm_xor_si128(prod, u_middle_one);\n    if (topbit) prod = _mm_xor_si128(prod, pp);\n    if (((uint64_t)_mm_extract_epi64(_mm_and_si128(a, amask), 0))) {\n      prod = _mm_xor_si128(prod, b);\n    }\n    amask = _mm_srli_epi64(amask, 1);\n  }\n  c128[0] = (uint64_t)_mm_extract_epi64(prod, 1);\n  c128[1] = (uint64_t)_mm_extract_epi64(prod, 0);\n#endif\n  return;\n}\n\n\n/* Ben: This slow function implements sse instrutions for bytwo_b because why not */\nvoid\ngf_w128_sse_bytwo_b_multiply(gf_t *gf, gf_val_128_t a128, gf_val_128_t b128, gf_val_128_t c128)\n{\n#if defined(INTEL_SSE4)\n  __m128i a, b, lmask, hmask, pp, c, middle_one;\n  gf_internal_t *h;\n  uint64_t topbit, middlebit;\n\n  h = (gf_internal_t *) gf->scratch;\n  \n  c = _mm_setzero_si128();\n  lmask = _mm_insert_epi64(c, 1ULL << 63, 0);\n  hmask = _mm_insert_epi64(c, 1ULL << 63, 1);\n  b = _mm_insert_epi64(c, a128[0], 1);\n  b = _mm_insert_epi64(b, a128[1], 0);\n  a = _mm_insert_epi64(c, b128[0], 1);\n  a = _mm_insert_epi64(a, b128[1], 0);\n  pp = _mm_insert_epi64(c, h->prim_poly, 0);\n  middle_one = _mm_insert_epi64(c, 1, 0x1);\n\n  while (1) {\n    if (_mm_extract_epi32(a, 0x0) & 1) {\n      c = _mm_xor_si128(c, b);\n    }\n    middlebit = (_mm_extract_epi32(a, 0x2) & 1);\n    a = _mm_srli_epi64(a, 1);\n    if (middlebit) a = _mm_xor_si128(a, lmask);\n    if ((_mm_extract_epi64(a, 0x1) == 0ULL) && (_mm_extract_epi64(a, 0x0) == 0ULL)){\n      c128[0] = _mm_extract_epi64(c, 0x1);\n      c128[1] = _mm_extract_epi64(c, 0x0);\n      return;\n    }\n    topbit = (_mm_extract_epi64(_mm_and_si128(b, hmask), 1));\n    middlebit = (_mm_extract_epi64(_mm_and_si128(b, lmask), 0));\n    b = _mm_slli_epi64(b, 1);\n    if (middlebit) b = _mm_xor_si128(b, middle_one);\n    if (topbit) b = _mm_xor_si128(b, pp);\n  }\n#endif\n}\n\nvoid\ngf_w128_bytwo_b_multiply(gf_t *gf, gf_val_128_t a128, gf_val_128_t b128, gf_val_128_t c128)\n{\n  uint64_t bmask, pp;\n  gf_internal_t *h;\n  uint64_t a[2], b[2], c[2];\n\n  h = (gf_internal_t *) gf->scratch;\n\n  bmask = (1ULL << 63);\n  set_zero(c, 0);\n  b[0] = a128[0];\n  b[1] = a128[1];\n  a[0] = b128[0];\n  a[1] = b128[1];\n  \n  while (1) {\n    if (a[1] & 1) {\n      c[0] ^= b[0];\n      c[1] ^= b[1];\n    }\n    a[1] >>= 1;\n    if (a[0] & 1) a[1] ^= bmask;\n    a[0] >>= 1;\n    if (a[1] == 0 && a[0] == 0) {\n      c128[0] = c[0];\n      c128[1] = c[1];\n      return;\n    }\n    pp = (b[0] & bmask);\n    b[0] <<= 1;\n    if (b[1] & bmask) b[0] ^= 1;\n    b[1] <<= 1;\n    if (pp) b[1] ^= h->prim_poly;\n  }\n}\n\nstatic\nvoid\ngf_w128_split_4_128_multiply_region(gf_t *gf, void *src, void *dest, gf_val_128_t val, int bytes, int xor)\n{\n  int i, j, k;\n  uint64_t pp;\n  gf_internal_t *h;\n  uint64_t *s64, *d64, *top;\n  gf_region_data rd;\n  uint64_t v[2], s;\n  struct gf_w128_split_4_128_data *ld;\n\n  /* We only do this to check on alignment. */\n  gf_set_region_data(&rd, gf, src, dest, bytes, 0, xor, 8);\n\n  if (val[0] == 0) {\n    if (val[1] == 0) { gf_multby_zero(dest, bytes, xor); return; }\n    if (val[1] == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n  }\n    \n  h = (gf_internal_t *) gf->scratch;\n  ld = (struct gf_w128_split_4_128_data *) h->private;\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  top = (uint64_t *) rd.d_top;\n\n  if (val[0] != ld->last_value[0] || val[1] != ld->last_value[1]) {\n    v[0] = val[0];\n    v[1] = val[1];\n    for (i = 0; i < 32; i++) {\n      ld->tables[0][i][0] = 0;\n      ld->tables[1][i][0] = 0;\n      for (j = 1; j < 16; j <<= 1) {\n        for (k = 0; k < j; k++) {\n          ld->tables[0][i][k^j] = (v[0] ^ ld->tables[0][i][k]);\n          ld->tables[1][i][k^j] = (v[1] ^ ld->tables[1][i][k]);\n        }\n        pp = (v[0] & (1ULL << 63));\n        v[0] <<= 1;\n        if (v[1] & (1ULL << 63)) v[0] ^= 1;\n        v[1] <<= 1;\n        if (pp) v[1] ^= h->prim_poly;\n      }\n    }\n  }\n  ld->last_value[0] = val[0];\n  ld->last_value[1] = val[1];\n\n/*\n  for (i = 0; i < 32; i++) {\n    for (j = 0; j < 16; j++) {\n      printf(\"%2d %2d %016llx %016llx\\n\", i, j, ld->tables[0][i][j], ld->tables[1][i][j]);\n    }\n    printf(\"\\n\");\n  }\n */\n  while (d64 < top) {\n    v[0] = (xor) ? d64[0] : 0;\n    v[1] = (xor) ? d64[1] : 0;\n    s = s64[1];\n    i = 0;\n    while (s != 0) {\n      v[0] ^= ld->tables[0][i][s&0xf];\n      v[1] ^= ld->tables[1][i][s&0xf];\n      s >>= 4;\n      i++;\n    }\n    s = s64[0];\n    i = 16;\n    while (s != 0) {\n      v[0] ^= ld->tables[0][i][s&0xf];\n      v[1] ^= ld->tables[1][i][s&0xf];\n      s >>= 4;\n      i++;\n    }\n    d64[0] = v[0];\n    d64[1] = v[1];\n    s64 += 2;\n    d64 += 2;\n  }\n}\n\n#if defined(INTEL_SSSE3) && defined(INTEL_SSE4)\nstatic\nvoid\ngf_w128_split_4_128_sse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_128_t val, int bytes, int xor)\n{\n  gf_internal_t *h;\n  int i, j, k;\n  uint64_t pp, v[2], s, *s64, *d64, *top;\n  __m128i p, tables[32][16];\n  struct gf_w128_split_4_128_data *ld;\n  gf_region_data rd;\n\n  if (val[0] == 0) {\n    if (val[1] == 0) { gf_multby_zero(dest, bytes, xor); return; }\n    if (val[1] == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n  }\n\n  h = (gf_internal_t *) gf->scratch;\n  \n  /* We only do this to check on alignment. */\n  gf_set_region_data(&rd, gf, src, dest, bytes, 0, xor, 16);\n\n  /* Doing this instead of gf_do_initial_region_alignment() because that doesn't hold 128-bit vals */\n\n  gf_w128_multiply_region_from_single(gf, src, dest, val, ((uint8_t *)rd.s_start-(uint8_t *)src), xor);\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  top = (uint64_t *) rd.d_top;\n \n  ld = (struct gf_w128_split_4_128_data *) h->private;\n\n  if (val[0] != ld->last_value[0] || val[1] != ld->last_value[1]) {\n    v[0] = val[0];\n    v[1] = val[1];\n    for (i = 0; i < 32; i++) {\n      ld->tables[0][i][0] = 0;\n      ld->tables[1][i][0] = 0;\n      for (j = 1; j < 16; j <<= 1) {\n        for (k = 0; k < j; k++) {\n          ld->tables[0][i][k^j] = (v[0] ^ ld->tables[0][i][k]);\n          ld->tables[1][i][k^j] = (v[1] ^ ld->tables[1][i][k]);\n        }\n        pp = (v[0] & (1ULL << 63));\n        v[0] <<= 1;\n        if (v[1] & (1ULL << 63)) v[0] ^= 1;\n        v[1] <<= 1;\n        if (pp) v[1] ^= h->prim_poly;\n      }\n    }\n  }\n\n  ld->last_value[0] = val[0];\n  ld->last_value[1] = val[1];\n\n  for (i = 0; i < 32; i++) {\n    for (j = 0; j < 16; j++) {\n      v[0] = ld->tables[0][i][j];\n      v[1] = ld->tables[1][i][j];\n      tables[i][j] = _mm_loadu_si128((__m128i *) v);\n\n/*\n      printf(\"%2d %2d: \", i, j);\n      MM_PRINT8(\"\", tables[i][j]); */\n    }\n  }\n\n  while (d64 != top) {\n\n    if (xor) {\n      p = _mm_load_si128 ((__m128i *) d64);\n    } else {\n      p = _mm_setzero_si128();\n    }\n    s = *s64;\n    s64++;\n    for (i = 0; i < 16; i++) {\n      j = (s&0xf);\n      s >>= 4;\n      p = _mm_xor_si128(p, tables[16+i][j]);\n    }\n    s = *s64;\n    s64++;\n    for (i = 0; i < 16; i++) {\n      j = (s&0xf);\n      s >>= 4;\n      p = _mm_xor_si128(p, tables[i][j]);\n    }\n    _mm_store_si128((__m128i *) d64, p);\n    d64 += 2;\n  }\n\n  /* Doing this instead of gf_do_final_region_alignment() because that doesn't hold 128-bit vals */\n\n  gf_w128_multiply_region_from_single(gf, rd.s_top, rd.d_top, val, ((uint8_t *)src+bytes)-(uint8_t *)rd.s_top, xor);\n}\n#endif\n\n#if defined(INTEL_SSSE3) && defined(INTEL_SSE4)\nstatic\nvoid\ngf_w128_split_4_128_sse_altmap_multiply_region(gf_t *gf, void *src, void *dest, gf_val_128_t val, int bytes, int xor)\n{\n  gf_internal_t *h;\n  int i, j, k;\n  uint64_t pp, v[2], *s64, *d64, *top;\n  __m128i si, tables[32][16], p[16], v0, mask1;\n  struct gf_w128_split_4_128_data *ld;\n  uint8_t btable[16];\n  gf_region_data rd;\n\n  if (val[0] == 0) {\n    if (val[1] == 0) { gf_multby_zero(dest, bytes, xor); return; }\n    if (val[1] == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n  }\n\n  h = (gf_internal_t *) gf->scratch;\n  \n  /* We only do this to check on alignment. */\n  gf_set_region_data(&rd, gf, src, dest, bytes, 0, xor, 256);\n\n  /* Doing this instead of gf_do_initial_region_alignment() because that doesn't hold 128-bit vals */\n\n  gf_w128_multiply_region_from_single(gf, src, dest, val, ((uint8_t *)rd.s_start-(uint8_t *)src), xor);\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  top = (uint64_t *) rd.d_top;\n \n  ld = (struct gf_w128_split_4_128_data *) h->private;\n\n  if (val[0] != ld->last_value[0] || val[1] != ld->last_value[1]) {\n    v[0] = val[0];\n    v[1] = val[1];\n    for (i = 0; i < 32; i++) {\n      ld->tables[0][i][0] = 0;\n      ld->tables[1][i][0] = 0;\n      for (j = 1; j < 16; j <<= 1) {\n        for (k = 0; k < j; k++) {\n          ld->tables[0][i][k^j] = (v[0] ^ ld->tables[0][i][k]);\n          ld->tables[1][i][k^j] = (v[1] ^ ld->tables[1][i][k]);\n        }\n        pp = (v[0] & (1ULL << 63));\n        v[0] <<= 1;\n        if (v[1] & (1ULL << 63)) v[0] ^= 1;\n        v[1] <<= 1;\n        if (pp) v[1] ^= h->prim_poly;\n      }\n    }\n  }\n\n  ld->last_value[0] = val[0];\n  ld->last_value[1] = val[1];\n\n  for (i = 0; i < 32; i++) {\n    for (j = 0; j < 16; j++) {\n      for (k = 0; k < 16; k++) {\n        btable[k] = (uint8_t) ld->tables[1-(j/8)][i][k];\n        ld->tables[1-(j/8)][i][k] >>= 8;\n      }\n      tables[i][j] = _mm_loadu_si128((__m128i *) btable);\n/*\n      printf(\"%2d %2d: \", i, j);\n      MM_PRINT8(\"\", tables[i][j]);\n */\n    }\n  }\n\n\n  mask1 = _mm_set1_epi8(0xf);\n\n  while (d64 != top) {\n\n    if (xor) {\n      for (i = 0; i < 16; i++) p[i] = _mm_load_si128 ((__m128i *) (d64+i*2));\n    } else {\n      for (i = 0; i < 16; i++) p[i] = _mm_setzero_si128();\n    }\n    i = 0;\n    for (k = 0; k < 16; k++) {\n      v0 = _mm_load_si128((__m128i *) s64); \n      s64 += 2;\n      \n      si = _mm_and_si128(v0, mask1);\n  \n      for (j = 0; j < 16; j++) {\n        p[j] = _mm_xor_si128(p[j], _mm_shuffle_epi8(tables[i][j], si));\n      }\n      i++;\n      v0 = _mm_srli_epi32(v0, 4);\n      si = _mm_and_si128(v0, mask1);\n      for (j = 0; j < 16; j++) {\n        p[j] = _mm_xor_si128(p[j], _mm_shuffle_epi8(tables[i][j], si));\n      }\n      i++;\n    }\n    for (i = 0; i < 16; i++) {\n      _mm_store_si128((__m128i *) d64, p[i]);\n      d64 += 2;\n    }\n  }\n  /* Doing this instead of gf_do_final_region_alignment() because that doesn't hold 128-bit vals */\n\n  gf_w128_multiply_region_from_single(gf, rd.s_top, rd.d_top, val, ((uint8_t *)src+bytes)-(uint8_t *)rd.s_top, xor);\n}\n#endif\n\nstatic\nvoid\ngf_w128_split_8_128_multiply_region(gf_t *gf, void *src, void *dest, gf_val_128_t val, int bytes, int xor)\n{\n  int i, j, k;\n  uint64_t pp;\n  gf_internal_t *h;\n  uint64_t *s64, *d64, *top;\n  gf_region_data rd;\n  uint64_t v[2], s;\n  struct gf_w128_split_8_128_data *ld;\n\n  /* Check on alignment. Ignore it otherwise. */\n  gf_set_region_data(&rd, gf, src, dest, bytes, 0, xor, 8);\n\n  if (val[0] == 0) {\n    if (val[1] == 0) { gf_multby_zero(dest, bytes, xor); return; }\n    if (val[1] == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n  }\n    \n  h = (gf_internal_t *) gf->scratch;\n  ld = (struct gf_w128_split_8_128_data *) h->private;\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  top = (uint64_t *) rd.d_top;\n\n  if (val[0] != ld->last_value[0] || val[1] != ld->last_value[1]) {\n    v[0] = val[0];\n    v[1] = val[1];\n    for (i = 0; i < 16; i++) {\n      ld->tables[0][i][0] = 0;\n      ld->tables[1][i][0] = 0;\n      for (j = 1; j < (1 << 8); j <<= 1) {\n        for (k = 0; k < j; k++) {\n          ld->tables[0][i][k^j] = (v[0] ^ ld->tables[0][i][k]);\n          ld->tables[1][i][k^j] = (v[1] ^ ld->tables[1][i][k]);\n        }\n        pp = (v[0] & (1ULL << 63));\n        v[0] <<= 1;\n        if (v[1] & (1ULL << 63)) v[0] ^= 1;\n        v[1] <<= 1;\n        if (pp) v[1] ^= h->prim_poly;\n      }\n    }\n  }\n  ld->last_value[0] = val[0];\n  ld->last_value[1] = val[1];\n\n  while (d64 < top) {\n    v[0] = (xor) ? d64[0] : 0;\n    v[1] = (xor) ? d64[1] : 0;\n    s = s64[1];\n    i = 0;\n    while (s != 0) {\n      v[0] ^= ld->tables[0][i][s&0xff];\n      v[1] ^= ld->tables[1][i][s&0xff];\n      s >>= 8;\n      i++;\n    }\n    s = s64[0];\n    i = 8;\n    while (s != 0) {\n      v[0] ^= ld->tables[0][i][s&0xff];\n      v[1] ^= ld->tables[1][i][s&0xff];\n      s >>= 8;\n      i++;\n    }\n    d64[0] = v[0];\n    d64[1] = v[1];\n    s64 += 2;\n    d64 += 2;\n  }\n}\n\nvoid\ngf_w128_bytwo_b_multiply_region(gf_t *gf, void *src, void *dest, gf_val_128_t val, int bytes, int xor)\n{\n  uint64_t bmask, pp;\n  gf_internal_t *h;\n  uint64_t a[2], c[2], b[2], *s64, *d64, *top;\n  gf_region_data rd;\n\n  /* We only do this to check on alignment. */\n  gf_set_region_data(&rd, gf, src, dest, bytes, 0, xor, 8);\n\n  if (val[0] == 0) {\n    if (val[1] == 0) { gf_multby_zero(dest, bytes, xor); return; }\n    if (val[1] == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n  }\n    \n  h = (gf_internal_t *) gf->scratch;\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  top = (uint64_t *) rd.d_top;\n  bmask = (1ULL << 63);\n\n  while (d64 < top) {\n    set_zero(c, 0);\n    b[0] = s64[0];\n    b[1] = s64[1];\n    a[0] = val[0];\n    a[1] = val[1];\n\n    while (a[0] != 0) {\n      if (a[1] & 1) {\n        c[0] ^= b[0];\n        c[1] ^= b[1];\n      }\n      a[1] >>= 1;\n      if (a[0] & 1) a[1] ^= bmask;\n      a[0] >>= 1;\n      pp = (b[0] & bmask);\n      b[0] <<= 1;\n      if (b[1] & bmask) b[0] ^= 1;    \n      b[1] <<= 1;\n      if (pp) b[1] ^= h->prim_poly;\n    }\n    while (1) {\n      if (a[1] & 1) {\n        c[0] ^= b[0];\n        c[1] ^= b[1];\n      }\n      a[1] >>= 1;\n      if (a[1] == 0) break;\n      pp = (b[0] & bmask);\n      b[0] <<= 1;\n      if (b[1] & bmask) b[0] ^= 1;    \n      b[1] <<= 1;\n      if (pp) b[1] ^= h->prim_poly;\n    }\n    if (xor) {\n      d64[0] ^= c[0];\n      d64[1] ^= c[1];\n    } else {\n      d64[0] = c[0];\n      d64[1] = c[1];\n    }\n    s64 += 2;\n    d64 += 2;\n  }\n}\n\nstatic\nvoid gf_w128_group_m_init(gf_t *gf, gf_val_128_t b128)\n{\n  int i, j;\n  int g_m;\n  uint64_t prim_poly, lbit;\n  gf_internal_t *scratch;\n  gf_group_tables_t *gt;\n  uint64_t a128[2];\n  scratch = (gf_internal_t *) gf->scratch;\n  gt = scratch->private;\n  g_m = scratch->arg1;\n  prim_poly = scratch->prim_poly;\n\n\n  set_zero(gt->m_table, 0);\n  a_get_b(gt->m_table, 2, b128, 0);\n  lbit = 1;\n  lbit <<= 63;\n\n  for (i = 2; i < (1 << g_m); i <<= 1) {\n    a_get_b(a128, 0, gt->m_table, 2 * (i >> 1));\n    two_x(a128);\n    a_get_b(gt->m_table, 2 * i, a128, 0);\n    if (gt->m_table[2 * (i >> 1)] & lbit) gt->m_table[(2 * i) + 1] ^= prim_poly;\n    for (j = 0; j < i; j++) {\n      gt->m_table[(2 * i) + (2 * j)] = gt->m_table[(2 * i)] ^ gt->m_table[(2 * j)];\n      gt->m_table[(2 * i) + (2 * j) + 1] = gt->m_table[(2 * i) + 1] ^ gt->m_table[(2 * j) + 1];\n    }\n  }\n  return;\n}\n\nvoid\ngf_w128_group_multiply(GFP gf, gf_val_128_t a128, gf_val_128_t b128, gf_val_128_t c128)\n{\n  int i;\n  /* index_r, index_m, total_m (if g_r > g_m) */\n  int i_r, i_m, t_m;\n  int mask_m, mask_r;\n  int g_m, g_r;\n  uint64_t p_i[2], a[2];\n  gf_internal_t *scratch;\n  gf_group_tables_t *gt;\n\n  scratch = (gf_internal_t *) gf->scratch;\n  gt = scratch->private;\n  g_m = scratch->arg1;\n  g_r = scratch->arg2;\n\n  mask_m = (1 << g_m) - 1;\n  mask_r = (1 << g_r) - 1;\n\n  if (b128[0] != gt->m_table[2] || b128[1] != gt->m_table[3]) {\n    gf_w128_group_m_init(gf, b128);\n  }\n  \n  p_i[0] = 0;\n  p_i[1] = 0;\n  a[0] = a128[0];\n  a[1] = a128[1];\n\n  t_m = 0;\n  i_r = 0;\n\n  /* Top 64 bits */\n  for (i = ((GF_FIELD_WIDTH / 2) / g_m) - 1; i >= 0; i--) {\n    i_m = (a[0] >> (i * g_m)) & mask_m;\n    i_r ^= (p_i[0] >> (64 - g_m)) & mask_r;\n    p_i[0] <<= g_m;\n    p_i[0] ^= (p_i[1] >> (64-g_m));\n    p_i[1] <<= g_m;\n    p_i[0] ^= gt->m_table[2 * i_m];\n    p_i[1] ^= gt->m_table[(2 * i_m) + 1];\n    t_m += g_m;\n    if (t_m == g_r) {\n      p_i[1] ^= gt->r_table[i_r];\n      t_m = 0;\n      i_r = 0;\n    } else {\n      i_r <<= g_m;\n    }\n  }\n\n  for (i = ((GF_FIELD_WIDTH / 2) / g_m) - 1; i >= 0; i--) {\n    i_m = (a[1] >> (i * g_m)) & mask_m;\n    i_r ^= (p_i[0] >> (64 - g_m)) & mask_r;\n    p_i[0] <<= g_m;\n    p_i[0] ^= (p_i[1] >> (64-g_m));\n    p_i[1] <<= g_m;\n    p_i[0] ^= gt->m_table[2 * i_m];\n    p_i[1] ^= gt->m_table[(2 * i_m) + 1];\n    t_m += g_m;\n    if (t_m == g_r) {\n      p_i[1] ^= gt->r_table[i_r];\n      t_m = 0;\n      i_r = 0;\n    } else {\n      i_r <<= g_m;\n    }\n  }\n  c128[0] = p_i[0];\n  c128[1] = p_i[1];\n}\n\nstatic\nvoid\ngf_w128_group_multiply_region(gf_t *gf, void *src, void *dest, gf_val_128_t val, int bytes, int xor)\n{\n  int i;\n  int i_r, i_m, t_m;\n  int mask_m, mask_r;\n  int g_m, g_r;\n  uint64_t p_i[2], a[2];\n  gf_internal_t *scratch;\n  gf_group_tables_t *gt;\n  gf_region_data rd;\n  uint64_t *a128, *c128, *top;\n\n  /* We only do this to check on alignment. */\n  gf_set_region_data(&rd, gf, src, dest, bytes, 0, xor, 8);\n      \n  if (val[0] == 0) {\n    if (val[1] == 0) { gf_multby_zero(dest, bytes, xor); return; }\n    if (val[1] == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n  }\n    \n  scratch = (gf_internal_t *) gf->scratch;\n  gt = scratch->private;\n  g_m = scratch->arg1;\n  g_r = scratch->arg2;\n\n  mask_m = (1 << g_m) - 1;\n  mask_r = (1 << g_r) - 1;\n\n  if (val[0] != gt->m_table[2] || val[1] != gt->m_table[3]) {\n    gf_w128_group_m_init(gf, val);\n  }\n\n  a128 = (uint64_t *) src;\n  c128 = (uint64_t *) dest;\n  top = (uint64_t *) rd.d_top;\n\n  while (c128 < top) {\n    p_i[0] = 0;\n    p_i[1] = 0;\n    a[0] = a128[0];\n    a[1] = a128[1];\n  \n    t_m = 0;\n    i_r = 0;\n  \n    /* Top 64 bits */\n    for (i = ((GF_FIELD_WIDTH / 2) / g_m) - 1; i >= 0; i--) {\n      i_m = (a[0] >> (i * g_m)) & mask_m;\n      i_r ^= (p_i[0] >> (64 - g_m)) & mask_r;\n      p_i[0] <<= g_m;\n      p_i[0] ^= (p_i[1] >> (64-g_m));\n      p_i[1] <<= g_m;\n      \n      p_i[0] ^= gt->m_table[2 * i_m];\n      p_i[1] ^= gt->m_table[(2 * i_m) + 1];\n      t_m += g_m;\n      if (t_m == g_r) {\n        p_i[1] ^= gt->r_table[i_r];\n        t_m = 0;\n        i_r = 0;\n      } else {\n        i_r <<= g_m;\n      }\n    }\n    for (i = ((GF_FIELD_WIDTH / 2) / g_m) - 1; i >= 0; i--) {\n      i_m = (a[1] >> (i * g_m)) & mask_m;\n      i_r ^= (p_i[0] >> (64 - g_m)) & mask_r;\n      p_i[0] <<= g_m;\n      p_i[0] ^= (p_i[1] >> (64-g_m));\n      p_i[1] <<= g_m;\n      p_i[0] ^= gt->m_table[2 * i_m];\n      p_i[1] ^= gt->m_table[(2 * i_m) + 1];\n      t_m += g_m;\n      if (t_m == g_r) {\n        p_i[1] ^= gt->r_table[i_r];\n        t_m = 0;\n        i_r = 0;\n      } else {\n        i_r <<= g_m;\n      }\n    }\n  \n    if (xor) {\n      c128[0] ^= p_i[0];\n      c128[1] ^= p_i[1];\n    } else {\n      c128[0] = p_i[0];\n      c128[1] = p_i[1];\n    }\n    a128 += 2;\n    c128 += 2;\n  }\n}\n\n/* a^-1 -> b */\n  void\ngf_w128_euclid(GFP gf, gf_val_128_t a128, gf_val_128_t b128)\n{\n  uint64_t e_i[2], e_im1[2], e_ip1[2];\n  uint64_t d_i, d_im1, d_ip1;\n  uint64_t y_i[2], y_im1[2], y_ip1[2];\n  uint64_t c_i[2];\n  uint64_t *b;\n  uint64_t one = 1;\n\n  /* This needs to return some sort of error (in b128?) */\n  if (a128[0] == 0 && a128[1] == 0) return;\n\n  b = (uint64_t *) b128;\n\n  e_im1[0] = 0;\n  e_im1[1] = ((gf_internal_t *) (gf->scratch))->prim_poly;\n  e_i[0] = a128[0];\n  e_i[1] = a128[1];\n  d_im1 = 128;\n\n  //Allen: I think d_i starts at 63 here, and checks each bit of a, starting at MSB, looking for the first nonzero bit\n  //so d_i should be 0 if this half of a is all 0s, otherwise it should be the position from right of the first-from-left zero bit of this half of a.\n  //BUT if d_i is 0 at end we won't know yet if the rightmost bit of this half is 1 or not\n\n  for (d_i = (d_im1-1) % 64; ((one << d_i) & e_i[0]) == 0 && d_i > 0; d_i--) ;\n\n  //Allen: this is testing just the first half of the stop condition above, so if it holds we know we did not find a nonzero bit yet\n\n  if (!((one << d_i) & e_i[0])) {\n\n    //Allen: this is doing the same thing on the other half of a. In other words, we're still searching for a nonzero bit of a.\n    // but not bothering to test if d_i hits zero, which is fine because we've already tested for a=0.\n\n    for (d_i = (d_im1-1) % 64; ((one << d_i) & e_i[1]) == 0; d_i--) ;\n\n  } else {\n\n    //Allen: if a 1 was found in more-significant half of a, make d_i the ACTUAL index of the first nonzero bit in the entire a.\n\n    d_i += 64;\n  }\n  y_i[0] = 0;\n  y_i[1] = 1;\n  y_im1[0] = 0;\n  y_im1[1] = 0;\n\n  while (!(e_i[0] == 0 && e_i[1] == 1)) {\n\n    e_ip1[0] = e_im1[0];\n    e_ip1[1] = e_im1[1];\n    d_ip1 = d_im1;\n    c_i[0] = 0;\n    c_i[1] = 0;\n\n    while (d_ip1 >= d_i) {\n      if ((d_ip1 - d_i) >= 64) {\n        c_i[0] ^= (one << ((d_ip1 - d_i) - 64));\n        e_ip1[0] ^= (e_i[1] << ((d_ip1 - d_i) - 64));\n      } else {\n        c_i[1] ^= (one << (d_ip1 - d_i));\n        e_ip1[0] ^= (e_i[0] << (d_ip1 - d_i));\n        if (d_ip1 - d_i > 0) e_ip1[0] ^= (e_i[1] >> (64 - (d_ip1 - d_i)));\n        e_ip1[1] ^= (e_i[1] << (d_ip1 - d_i));\n      }\n      d_ip1--;\n      if (e_ip1[0] == 0 && e_ip1[1] == 0) { b[0] = 0; b[1] = 0; return; }\n      while (d_ip1 >= 64 && (e_ip1[0] & (one << (d_ip1 - 64))) == 0) d_ip1--;\n      while (d_ip1 <  64 && (e_ip1[1] & (one << d_ip1)) == 0) d_ip1--;\n    }\n    gf->multiply.w128(gf, c_i, y_i, y_ip1);\n    y_ip1[0] ^= y_im1[0];\n    y_ip1[1] ^= y_im1[1];\n\n    y_im1[0] = y_i[0];\n    y_im1[1] = y_i[1];\n\n    y_i[0] = y_ip1[0];\n    y_i[1] = y_ip1[1];\n\n    e_im1[0] = e_i[0];\n    e_im1[1] = e_i[1];\n    d_im1 = d_i;\n    e_i[0] = e_ip1[0];\n    e_i[1] = e_ip1[1];\n    d_i = d_ip1;\n  }\n\n  b[0] = y_i[0];\n  b[1] = y_i[1];\n  return;\n}\n\n  void\ngf_w128_divide_from_inverse(GFP gf, gf_val_128_t a128, gf_val_128_t b128, gf_val_128_t c128)\n{\n  uint64_t d[2];\n  gf->inverse.w128(gf, b128, d);\n  gf->multiply.w128(gf, a128, d, c128);\n  return;\n}\n\n  void\ngf_w128_inverse_from_divide(GFP gf, gf_val_128_t a128, gf_val_128_t b128)\n{\n  uint64_t one128[2];\n  one128[0] = 0;\n  one128[1] = 1;\n  gf->divide.w128(gf, one128, a128, b128);\n  return;\n}\n\n\nstatic\n  void\ngf_w128_composite_inverse(gf_t *gf, gf_val_128_t a, gf_val_128_t inv)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  gf_t *base_gf = h->base_gf;\n  uint64_t a0 = a[1];\n  uint64_t a1 = a[0];\n  uint64_t c0, c1, d, tmp;\n  uint64_t a0inv, a1inv;\n\n  if (a0 == 0) {\n    a1inv = base_gf->inverse.w64(base_gf, a1);\n    c0 = base_gf->multiply.w64(base_gf, a1inv, h->prim_poly);\n    c1 = a1inv;\n  } else if (a1 == 0) {\n    c0 = base_gf->inverse.w64(base_gf, a0);\n    c1 = 0;\n  } else {\n    a1inv = base_gf->inverse.w64(base_gf, a1);\n    a0inv = base_gf->inverse.w64(base_gf, a0);\n\n    d = base_gf->multiply.w64(base_gf, a1, a0inv);\n\n    tmp = (base_gf->multiply.w64(base_gf, a1, a0inv) ^ base_gf->multiply.w64(base_gf, a0, a1inv) ^ h->prim_poly);\n    tmp = base_gf->inverse.w64(base_gf, tmp);\n\n    d = base_gf->multiply.w64(base_gf, d, tmp);\n\n    c0 = base_gf->multiply.w64(base_gf, (d^1), a0inv);\n    c1 = base_gf->multiply.w64(base_gf, d, a1inv);\n  }\n  inv[0] = c1;\n  inv[1] = c0;\n}\n\nstatic\n  void\ngf_w128_composite_multiply(gf_t *gf, gf_val_128_t a, gf_val_128_t b, gf_val_128_t rv)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  gf_t *base_gf = h->base_gf;\n  uint64_t b0 = b[1];\n  uint64_t b1 = b[0];\n  uint64_t a0 = a[1];\n  uint64_t a1 = a[0];\n  uint64_t a1b1;\n\n  a1b1 = base_gf->multiply.w64(base_gf, a1, b1);\n\n  rv[1] = (base_gf->multiply.w64(base_gf, a0, b0) ^ a1b1);\n  rv[0] = base_gf->multiply.w64(base_gf, a1, b0) ^ \n    base_gf->multiply.w64(base_gf, a0, b1) ^ \n    base_gf->multiply.w64(base_gf, a1b1, h->prim_poly);\n}\n\nstatic\n  void\ngf_w128_composite_multiply_region(gf_t *gf, void *src, void *dest, gf_val_128_t val, int bytes, int xor)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  gf_t *base_gf = h->base_gf;\n  uint64_t b0 = val[1];\n  uint64_t b1 = val[0];\n  uint64_t *s64, *d64;\n  uint64_t *top;\n  uint64_t a0, a1, a1b1;\n  gf_region_data rd;\n\n  if (val[0] == 0 && val[1] == 0) { gf_multby_zero(dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, 0, xor, 8);\n\n  s64 = rd.s_start;\n  d64 = rd.d_start;\n  top = rd.d_top;\n\n  if (xor) {\n    while (d64 < top) {\n      a1 = s64[0];\n      a0 = s64[1];\n      a1b1 = base_gf->multiply.w64(base_gf, a1, b1);\n\n      d64[1] ^= (base_gf->multiply.w64(base_gf, a0, b0) ^ a1b1);\n      d64[0] ^= (base_gf->multiply.w64(base_gf, a1, b0) ^ \n          base_gf->multiply.w64(base_gf, a0, b1) ^ \n          base_gf->multiply.w64(base_gf, a1b1, h->prim_poly));\n      s64 += 2;\n      d64 += 2;\n    }\n  } else {\n    while (d64 < top) {\n      a1 = s64[0];\n      a0 = s64[1];\n      a1b1 = base_gf->multiply.w64(base_gf, a1, b1);\n\n      d64[1] = (base_gf->multiply.w64(base_gf, a0, b0) ^ a1b1);\n      d64[0] = (base_gf->multiply.w64(base_gf, a1, b0) ^ \n          base_gf->multiply.w64(base_gf, a0, b1) ^ \n          base_gf->multiply.w64(base_gf, a1b1, h->prim_poly));\n      s64 += 2;\n      d64 += 2;\n    }\n  }\n}\n\nstatic\nvoid\ngf_w128_composite_multiply_region_alt(gf_t *gf, void *src, void *dest, gf_val_128_t val, int bytes, int \n    xor)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;  gf_t *base_gf = h->base_gf;\n  gf_val_64_t val0 = val[1];\n  gf_val_64_t val1 = val[0];\n  uint8_t *slow, *shigh;\n  uint8_t *dlow, *dhigh, *top;\n  int sub_reg_size;\n  gf_region_data rd;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, 0, xor, 64);\n  gf_w128_multiply_region_from_single(gf, src, dest, val, ((uint8_t *)rd.s_start-(uint8_t *)src), xor);\n\n  slow = (uint8_t *) rd.s_start;\n  dlow = (uint8_t *) rd.d_start;\n  top = (uint8_t*) rd.d_top;\n  sub_reg_size = (top - dlow)/2;\n  shigh = slow + sub_reg_size;\n  dhigh = dlow + sub_reg_size;\n\n  base_gf->multiply_region.w64(base_gf, slow, dlow, val0, sub_reg_size, xor);\n  base_gf->multiply_region.w64(base_gf, shigh, dlow, val1, sub_reg_size, 1);\n  base_gf->multiply_region.w64(base_gf, slow, dhigh, val1, sub_reg_size, xor);\n  base_gf->multiply_region.w64(base_gf, shigh, dhigh, val0, sub_reg_size, 1);\n  base_gf->multiply_region.w64(base_gf, shigh, dhigh, base_gf->multiply.w64(base_gf, h->prim_poly, val1\n        ), sub_reg_size, 1);\n\n  gf_w128_multiply_region_from_single(gf, rd.s_top, rd.d_top, val, ((uint8_t *)src+bytes)-(uint8_t *)rd.s_top, xor);\n}\n\n\n  static\nint gf_w128_composite_init(gf_t *gf)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n\n  if (h->region_type & GF_REGION_ALTMAP) {\n    gf->multiply_region.w128 = gf_w128_composite_multiply_region_alt;   \n  } else {\n    gf->multiply_region.w128 = gf_w128_composite_multiply_region;\n  }\n\n  gf->multiply.w128 = gf_w128_composite_multiply;\n  gf->divide.w128 = gf_w128_divide_from_inverse;\n  gf->inverse.w128 = gf_w128_composite_inverse;\n\n  return 1;\n}\n\nstatic\nint gf_w128_cfm_init(gf_t *gf)\n{\n#if defined(INTEL_SSE4_PCLMUL)\n  gf->inverse.w128 = gf_w128_euclid;\n  gf->multiply.w128 = gf_w128_clm_multiply;\n  gf->multiply_region.w128 = gf_w128_clm_multiply_region_from_single;\n  return 1;\n#endif\n\n  return 0;\n}\n\nstatic\nint gf_w128_shift_init(gf_t *gf)\n{\n  gf->multiply.w128 = gf_w128_shift_multiply;\n  gf->inverse.w128 = gf_w128_euclid;\n  gf->multiply_region.w128 = gf_w128_multiply_region_from_single;\n  return 1;\n}\n\n  static\nint gf_w128_bytwo_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  h = (gf_internal_t *) gf->scratch;\n\n  if (h->mult_type == GF_MULT_BYTWO_p) {\n    gf->multiply.w128 = gf_w128_bytwo_p_multiply;\n    /*gf->multiply.w128 = gf_w128_sse_bytwo_p_multiply;*/\n    /* John: the sse function is slower.*/\n  } else {\n    gf->multiply.w128 = gf_w128_bytwo_b_multiply;\n    /*gf->multiply.w128 = gf_w128_sse_bytwo_b_multiply;\nBen: This sse function is also slower. */\n  }\n  gf->inverse.w128 = gf_w128_euclid;\n  gf->multiply_region.w128 = gf_w128_bytwo_b_multiply_region;\n  return 1;\n}\n\n/*\n * Because the prim poly is only 8 bits and we are limiting g_r to 16, I do not need the high 64\n * bits in all of these numbers.\n */\n  static\nvoid gf_w128_group_r_init(gf_t *gf)\n{\n  int i, j;\n  int g_r;\n  uint64_t pp;\n  gf_internal_t *scratch;\n  gf_group_tables_t *gt;\n  scratch = (gf_internal_t *) gf->scratch;\n  gt = scratch->private;\n  g_r = scratch->arg2;\n  pp = scratch->prim_poly;\n\n  gt->r_table[0] = 0;\n  for (i = 1; i < (1 << g_r); i++) {\n    gt->r_table[i] = 0;\n    for (j = 0; j < g_r; j++) {\n      if (i & (1 << j)) {\n        gt->r_table[i] ^= (pp << j);\n      }\n    }\n  }\n  return;\n}\n\n#if 0 // defined(INTEL_SSE4)\n  static\nvoid gf_w128_group_r_sse_init(gf_t *gf)\n{\n  int i, j;\n  int g_r;\n  uint64_t pp;\n  gf_internal_t *scratch;\n  gf_group_tables_t *gt;\n  scratch = (gf_internal_t *) gf->scratch;\n  gt = scratch->private;\n  __m128i zero = _mm_setzero_si128();\n  __m128i *table = (__m128i *)(gt->r_table);\n  g_r = scratch->arg2;\n  pp = scratch->prim_poly;\n  table[0] = zero;\n  for (i = 1; i < (1 << g_r); i++) {\n    table[i] = zero;\n    for (j = 0; j < g_r; j++) {\n      if (i & (1 << j)) {\n        table[i] = _mm_xor_si128(table[i], _mm_insert_epi64(zero, pp << j, 0));\n      }\n    }\n  }\n  return;\n}\n#endif\n\n  static \nint gf_w128_split_init(gf_t *gf)\n{\n  struct gf_w128_split_4_128_data *sd4;\n  struct gf_w128_split_8_128_data *sd8;\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n\n  gf->multiply.w128 = gf_w128_bytwo_p_multiply;\n#if defined(INTEL_SSE4_PCLMUL)\n  if (!(h->region_type & GF_REGION_NOSIMD)){\n    gf->multiply.w128 = gf_w128_clm_multiply;\n  }\n#endif\n\n  gf->inverse.w128 = gf_w128_euclid;\n\n  if ((h->arg1 != 4 && h->arg2 != 4) || h->mult_type == GF_MULT_DEFAULT) {\n    sd8 = (struct gf_w128_split_8_128_data *) h->private;\n    sd8->last_value[0] = 0;\n    sd8->last_value[1] = 0;\n    gf->multiply_region.w128 = gf_w128_split_8_128_multiply_region;\n  } else {\n    sd4 = (struct gf_w128_split_4_128_data *) h->private;\n    sd4->last_value[0] = 0;\n    sd4->last_value[1] = 0;\n    if((h->region_type & GF_REGION_ALTMAP))\n    {\n      #ifdef INTEL_SSE4\n        if(!(h->region_type & GF_REGION_NOSIMD))\n          gf->multiply_region.w128 = gf_w128_split_4_128_sse_altmap_multiply_region;\n        else\n          return 0;\n      #else\n        return 0;\n      #endif\n    }\n    else {\n      #ifdef INTEL_SSE4\n        if(!(h->region_type & GF_REGION_NOSIMD))\n          gf->multiply_region.w128 = gf_w128_split_4_128_sse_multiply_region;\n        else\n          gf->multiply_region.w128 = gf_w128_split_4_128_multiply_region;\n      #else\n      gf->multiply_region.w128 = gf_w128_split_4_128_multiply_region;\n      #endif\n    }\n  }\n  return 1;\n}\n\n\nstatic\nint gf_w128_group_init(gf_t *gf)\n{\n  gf_internal_t *scratch;\n  gf_group_tables_t *gt;\n  int g_r, size_r;\n\n  scratch = (gf_internal_t *) gf->scratch;\n  gt = scratch->private;\n  g_r = scratch->arg2;\n  size_r = (1 << g_r);\n\n  gt->r_table = (gf_val_128_t)((uint8_t *)scratch->private + (2 * sizeof(uint64_t *)));\n  gt->m_table = gt->r_table + size_r;\n  gt->m_table[2] = 0;\n  gt->m_table[3] = 0;\n\n  gf->multiply.w128 = gf_w128_group_multiply;\n  gf->inverse.w128 = gf_w128_euclid;\n  gf->multiply_region.w128 = gf_w128_group_multiply_region;\n\n  gf_w128_group_r_init(gf);\n\n  return 1;\n}\n\nvoid gf_w128_extract_word(gf_t *gf, void *start, int bytes, int index, gf_val_128_t rv)\n{\n  gf_val_128_t s;\n\n  s = (gf_val_128_t) start;\n  s += (index * 2); \n  memcpy(rv, s, 16);\n}\n\nstatic void gf_w128_split_extract_word(gf_t *gf, void *start, int bytes, int index, gf_val_128_t rv)\n{\n  int i, blocks;\n  uint64_t *r64, tmp;\n  uint8_t *r8;\n  gf_region_data rd;\n\n  gf_set_region_data(&rd, gf, start, start, bytes, 0, 0, 256);\n  r64 = (uint64_t *) start;\n  if ((r64 + index*2 < (uint64_t *) rd.d_start) ||\n      (r64 + index*2 >= (uint64_t *) rd.d_top)) {\n    memcpy(rv, r64+(index*2), 16);\n    return;\n  }\n\n  index -= (((uint64_t *) rd.d_start) - r64)/2;\n  r64 = (uint64_t *) rd.d_start;\n\n  blocks = index/16;\n  r64 += (blocks*32);\n  index %= 16;\n  r8 = (uint8_t *) r64;\n  r8 += index;\n  rv[0] = 0;\n  rv[1] = 0;\n\n  for (i = 0; i < 8; i++) {\n    tmp = *r8;\n    rv[1] |= (tmp << (i*8));\n    r8 += 16;\n  }\n\n  for (i = 0; i < 8; i++) {\n    tmp = *r8;\n    rv[0] |= (tmp << (i*8));\n    r8 += 16;\n  }\n  return;\n}\n\n  static\nvoid gf_w128_composite_extract_word(gf_t *gf, void *start, int bytes, int index, gf_val_128_t rv)\n{\n  int sub_size;\n  gf_internal_t *h;\n  uint8_t *r8, *top;\n  uint64_t *r64;\n  gf_region_data rd;\n\n  h = (gf_internal_t *) gf->scratch;\n  gf_set_region_data(&rd, gf, start, start, bytes, 0, 0, 64);\n  r64 = (uint64_t *) start;\n  if ((r64 + index*2 < (uint64_t *) rd.d_start) ||\n      (r64 + index*2 >= (uint64_t *) rd.d_top)) {\n    memcpy(rv, r64+(index*2), 16);\n    return;\n  }\n  index -= (((uint64_t *) rd.d_start) - r64)/2;\n  r8 = (uint8_t *) rd.d_start;\n  top = (uint8_t *) rd.d_top;\n  sub_size = (top-r8)/2;\n\n  rv[1] = h->base_gf->extract_word.w64(h->base_gf, r8, sub_size, index);\n  rv[0] = h->base_gf->extract_word.w64(h->base_gf, r8+sub_size, sub_size, index);\n  \n  return;\n}\n\nint gf_w128_scratch_size(int mult_type, int region_type, int divide_type, int arg1, int arg2)\n{\n  int size_m, size_r;\n  if (divide_type==GF_DIVIDE_MATRIX) return 0;\n\n  switch(mult_type)\n  {\n    case GF_MULT_CARRY_FREE:\n      return sizeof(gf_internal_t);\n      break;\n    case GF_MULT_SHIFT:\n      return sizeof(gf_internal_t);\n      break;\n    case GF_MULT_BYTWO_p:\n    case GF_MULT_BYTWO_b:\n      return sizeof(gf_internal_t);\n      break;\n    case GF_MULT_DEFAULT: \n    case GF_MULT_SPLIT_TABLE:\n      if ((arg1 == 4 && arg2 == 128) || (arg1 == 128 && arg2 == 4)) {\n        return sizeof(gf_internal_t) + sizeof(struct gf_w128_split_4_128_data) + 64;\n      } else if ((arg1 == 8 && arg2 == 128) || (arg1 == 128 && arg2 == 8) || mult_type == GF_MULT_DEFAULT) {\n        return sizeof(gf_internal_t) + sizeof(struct gf_w128_split_8_128_data) + 64;\n      }\n      return 0;\n      break;\n    case GF_MULT_GROUP:\n      /* JSP We've already error checked the arguments. */\n      size_m = (1 << arg1) * 2 * sizeof(uint64_t);\n      size_r = (1 << arg2) * 2 * sizeof(uint64_t);\n      /* \n       * two pointers prepend the table data for structure\n       * because the tables are of dynamic size\n       */\n      return sizeof(gf_internal_t) + size_m + size_r + 4 * sizeof(uint64_t *);\n      break;\n    case GF_MULT_COMPOSITE:\n      if (arg1 == 2) {\n        return sizeof(gf_internal_t) + 4;\n      } else {\n        return 0;\n      }\n      break;\n\n    default:\n      return 0;\n   }\n}\n\nint gf_w128_init(gf_t *gf)\n{\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  \n  /* Allen: set default primitive polynomial / irreducible polynomial if needed */\n\n  if (h->prim_poly == 0) {\n    if (h->mult_type == GF_MULT_COMPOSITE) {\n      h->prim_poly = gf_composite_get_default_poly(h->base_gf);\n      if (h->prim_poly == 0) return 0; /* This shouldn't happen */\n    } else {\n      h->prim_poly = 0x87; /* Omitting the leftmost 1 as in w=32 */\n    }\n  }\n\n  gf->multiply.w128 = NULL;\n  gf->divide.w128 = NULL;\n  gf->inverse.w128 = NULL;\n  gf->multiply_region.w128 = NULL;\n  switch(h->mult_type) {\n    case GF_MULT_BYTWO_p:\n    case GF_MULT_BYTWO_b:      if (gf_w128_bytwo_init(gf) == 0) return 0; break;\n    case GF_MULT_CARRY_FREE:   if (gf_w128_cfm_init(gf) == 0) return 0; break;\n    case GF_MULT_SHIFT:        if (gf_w128_shift_init(gf) == 0) return 0; break;\n    case GF_MULT_GROUP:        if (gf_w128_group_init(gf) == 0) return 0; break;\n    case GF_MULT_DEFAULT: \n    case GF_MULT_SPLIT_TABLE:  if (gf_w128_split_init(gf) == 0) return 0; break;\n    case GF_MULT_COMPOSITE:    if (gf_w128_composite_init(gf) == 0) return 0; break;\n    default: return 0;\n  }\n\n  /* Ben: Used to be h->region_type == GF_REGION_ALTMAP, but failed since there\n     are multiple flags in h->region_type */\n  if (h->mult_type == GF_MULT_SPLIT_TABLE && (h->region_type & GF_REGION_ALTMAP)) {\n    gf->extract_word.w128 = gf_w128_split_extract_word;\n  } else if (h->mult_type == GF_MULT_COMPOSITE && h->region_type == GF_REGION_ALTMAP) {\n    gf->extract_word.w128 = gf_w128_composite_extract_word;\n  } else {\n    gf->extract_word.w128 = gf_w128_extract_word;\n  }\n\n  if (h->divide_type == GF_DIVIDE_EUCLID) {\n    gf->divide.w128 = gf_w128_divide_from_inverse;\n  } \n\n  if (gf->inverse.w128 != NULL && gf->divide.w128 == NULL) {\n    gf->divide.w128 = gf_w128_divide_from_inverse;\n  }\n  if (gf->inverse.w128 == NULL && gf->divide.w128 != NULL) {\n    gf->inverse.w128 = gf_w128_inverse_from_divide;\n  }\n  return 1;\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/src/gf_w16.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_w16.c\n *\n * Routines for 16-bit Galois fields\n */\n\n#include \"gf_int.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include \"gf_w16.h\"\n\n#define AB2(ip, am1 ,am2, b, t1, t2) {\\\n  t1 = (b << 1) & am1;\\\n  t2 = b & am2; \\\n  t2 = ((t2 << 1) - (t2 >> (GF_FIELD_WIDTH-1))); \\\n  b = (t1 ^ (t2 & ip));}\n\n#define SSE_AB2(pp, m1 ,m2, va, t1, t2) {\\\n          t1 = _mm_and_si128(_mm_slli_epi64(va, 1), m1); \\\n          t2 = _mm_and_si128(va, m2); \\\n          t2 = _mm_sub_epi64 (_mm_slli_epi64(t2, 1), _mm_srli_epi64(t2, (GF_FIELD_WIDTH-1))); \\\n          va = _mm_xor_si128(t1, _mm_and_si128(t2, pp)); }\n\n#define MM_PRINT(s, r) { uint8_t blah[16], ii; printf(\"%-12s\", s); _mm_storeu_si128((__m128i *)blah, r); for (ii = 0; ii < 16; ii += 2) printf(\"  %02x %02x\", blah[15-ii], blah[14-ii]); printf(\"\\n\"); }\n\n#define GF_FIRST_BIT (1 << 15)\n#define GF_MULTBY_TWO(p) (((p) & GF_FIRST_BIT) ? (((p) << 1) ^ h->prim_poly) : (p) << 1)\n\nstatic\ninline\ngf_val_32_t gf_w16_inverse_from_divide (gf_t *gf, gf_val_32_t a)\n{\n  return gf->divide.w32(gf, 1, a);\n}\n\nstatic\ninline\ngf_val_32_t gf_w16_divide_from_inverse (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  b = gf->inverse.w32(gf, b);\n  return gf->multiply.w32(gf, a, b);\n}\n\nstatic\nvoid\ngf_w16_multiply_region_from_single(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  gf_region_data rd;\n  uint16_t *s16;\n  uint16_t *d16;\n  \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 2);\n  gf_do_initial_region_alignment(&rd);\n\n  s16 = (uint16_t *) rd.s_start;\n  d16 = (uint16_t *) rd.d_start;\n\n  if (xor) {\n    while (d16 < ((uint16_t *) rd.d_top)) {\n      *d16 ^= gf->multiply.w32(gf, val, *s16);\n      d16++;\n      s16++;\n    } \n  } else {\n    while (d16 < ((uint16_t *) rd.d_top)) {\n      *d16 = gf->multiply.w32(gf, val, *s16);\n      d16++;\n      s16++;\n    } \n  }\n  gf_do_final_region_alignment(&rd);\n}\n\n#if defined(INTEL_SSE4_PCLMUL)\nstatic\nvoid\ngf_w16_clm_multiply_region_from_single_2(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  gf_region_data rd;\n  uint16_t *s16;\n  uint16_t *d16;\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t * h = gf->scratch;\n  prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0x1ffffULL));\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 2);\n  gf_do_initial_region_alignment(&rd);\n\n  a = _mm_insert_epi32 (_mm_setzero_si128(), val, 0);\n  \n  s16 = (uint16_t *) rd.s_start;\n  d16 = (uint16_t *) rd.d_start;\n\n  if (xor) {\n    while (d16 < ((uint16_t *) rd.d_top)) {\n\n      /* see gf_w16_clm_multiply() to see explanation of method */\n      \n      b = _mm_insert_epi32 (a, (gf_val_32_t)(*s16), 0);\n      result = _mm_clmulepi64_si128 (a, b, 0);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n\n      *d16 ^= ((gf_val_32_t)_mm_extract_epi32(result, 0));\n      d16++;\n      s16++;\n    } \n  } else {\n    while (d16 < ((uint16_t *) rd.d_top)) {\n      \n      /* see gf_w16_clm_multiply() to see explanation of method */\n      \n      b = _mm_insert_epi32 (a, (gf_val_32_t)(*s16), 0);\n      result = _mm_clmulepi64_si128 (a, b, 0);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n      \n      *d16 = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n      d16++;\n      s16++;\n    } \n  }\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\n#if defined(INTEL_SSE4_PCLMUL)\nstatic\nvoid\ngf_w16_clm_multiply_region_from_single_3(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  gf_region_data rd;\n  uint16_t *s16;\n  uint16_t *d16;\n\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t * h = gf->scratch;\n  prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0x1ffffULL));\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  a = _mm_insert_epi32 (_mm_setzero_si128(), val, 0);\n  \n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 2);\n  gf_do_initial_region_alignment(&rd);\n\n  s16 = (uint16_t *) rd.s_start;\n  d16 = (uint16_t *) rd.d_start;\n\n  if (xor) {\n    while (d16 < ((uint16_t *) rd.d_top)) {\n      \n      /* see gf_w16_clm_multiply() to see explanation of method */\n      \n      b = _mm_insert_epi32 (a, (gf_val_32_t)(*s16), 0);\n      result = _mm_clmulepi64_si128 (a, b, 0);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n\n      *d16 ^= ((gf_val_32_t)_mm_extract_epi32(result, 0));\n      d16++;\n      s16++;\n    } \n  } else {\n    while (d16 < ((uint16_t *) rd.d_top)) {\n      \n      /* see gf_w16_clm_multiply() to see explanation of method */\n      \n      b = _mm_insert_epi32 (a, (gf_val_32_t)(*s16), 0);\n      result = _mm_clmulepi64_si128 (a, b, 0);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n      \n      *d16 = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n      d16++;\n      s16++;\n    } \n  }\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\n#if defined(INTEL_SSE4_PCLMUL)\nstatic\nvoid\ngf_w16_clm_multiply_region_from_single_4(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  gf_region_data rd;\n  uint16_t *s16;\n  uint16_t *d16;\n\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t * h = gf->scratch;\n  prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0x1ffffULL));\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 2);\n  gf_do_initial_region_alignment(&rd);\n\n  a = _mm_insert_epi32 (_mm_setzero_si128(), val, 0);\n  \n  s16 = (uint16_t *) rd.s_start;\n  d16 = (uint16_t *) rd.d_start;\n\n  if (xor) {\n    while (d16 < ((uint16_t *) rd.d_top)) {\n      \n      /* see gf_w16_clm_multiply() to see explanation of method */\n      \n      b = _mm_insert_epi32 (a, (gf_val_32_t)(*s16), 0);\n      result = _mm_clmulepi64_si128 (a, b, 0);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n\n      *d16 ^= ((gf_val_32_t)_mm_extract_epi32(result, 0));\n      d16++;\n      s16++;\n    } \n  } else {\n    while (d16 < ((uint16_t *) rd.d_top)) {\n      \n      /* see gf_w16_clm_multiply() to see explanation of method */\n      \n      b = _mm_insert_epi32 (a, (gf_val_32_t)(*s16), 0);\n      result = _mm_clmulepi64_si128 (a, b, 0);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n      result = _mm_xor_si128 (result, w);\n      \n      *d16 = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n      d16++;\n      s16++;\n    } \n  }\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\nstatic\ninline\ngf_val_32_t gf_w16_euclid (gf_t *gf, gf_val_32_t b)\n{\n  gf_val_32_t e_i, e_im1, e_ip1;\n  gf_val_32_t d_i, d_im1, d_ip1;\n  gf_val_32_t y_i, y_im1, y_ip1;\n  gf_val_32_t c_i;\n\n  if (b == 0) return -1;\n  e_im1 = ((gf_internal_t *) (gf->scratch))->prim_poly;\n  e_i = b;\n  d_im1 = 16;\n  for (d_i = d_im1; ((1 << d_i) & e_i) == 0; d_i--) ;\n  y_i = 1;\n  y_im1 = 0;\n\n  while (e_i != 1) {\n\n    e_ip1 = e_im1;\n    d_ip1 = d_im1;\n    c_i = 0;\n\n    while (d_ip1 >= d_i) {\n      c_i ^= (1 << (d_ip1 - d_i));\n      e_ip1 ^= (e_i << (d_ip1 - d_i));\n      if (e_ip1 == 0) return 0;\n      while ((e_ip1 & (1 << d_ip1)) == 0) d_ip1--;\n    }\n\n    y_ip1 = y_im1 ^ gf->multiply.w32(gf, c_i, y_i);\n    y_im1 = y_i;\n    y_i = y_ip1;\n\n    e_im1 = e_i;\n    d_im1 = d_i;\n    e_i = e_ip1;\n    d_i = d_ip1;\n  }\n\n  return y_i;\n}\n\nstatic\ngf_val_32_t gf_w16_extract_word(gf_t *gf, void *start, int bytes, int index)\n{\n  uint16_t *r16, rv;\n\n  r16 = (uint16_t *) start;\n  rv = r16[index];\n  return rv;\n}\n\nstatic\ngf_val_32_t gf_w16_composite_extract_word(gf_t *gf, void *start, int bytes, int index)\n{\n  int sub_size;\n  gf_internal_t *h;\n  uint8_t *r8, *top;\n  uint16_t a, b, *r16;\n  gf_region_data rd;\n\n  h = (gf_internal_t *) gf->scratch;\n  gf_set_region_data(&rd, gf, start, start, bytes, 0, 0, 32);\n  r16 = (uint16_t *) start;\n  if (r16 + index < (uint16_t *) rd.d_start) return r16[index];\n  if (r16 + index >= (uint16_t *) rd.d_top) return r16[index];\n  index -= (((uint16_t *) rd.d_start) - r16);\n  r8 = (uint8_t *) rd.d_start;\n  top = (uint8_t *) rd.d_top;\n  sub_size = (top-r8)/2;\n\n  a = h->base_gf->extract_word.w32(h->base_gf, r8, sub_size, index);\n  b = h->base_gf->extract_word.w32(h->base_gf, r8+sub_size, sub_size, index);\n  return (a | (b << 8));\n}\n\nstatic\ngf_val_32_t gf_w16_split_extract_word(gf_t *gf, void *start, int bytes, int index)\n{\n  uint16_t *r16, rv;\n  uint8_t *r8;\n  gf_region_data rd;\n\n  gf_set_region_data(&rd, gf, start, start, bytes, 0, 0, 32);\n  r16 = (uint16_t *) start;\n  if (r16 + index < (uint16_t *) rd.d_start) return r16[index];\n  if (r16 + index >= (uint16_t *) rd.d_top) return r16[index];\n  index -= (((uint16_t *) rd.d_start) - r16);\n  r8 = (uint8_t *) rd.d_start;\n  r8 += ((index & 0xfffffff0)*2);\n  r8 += (index & 0xf);\n  rv = (*r8 << 8);\n  r8 += 16;\n  rv |= *r8;\n  return rv;\n}\n\nstatic\ninline\ngf_val_32_t gf_w16_matrix (gf_t *gf, gf_val_32_t b)\n{\n  return gf_bitmatrix_inverse(b, 16, ((gf_internal_t *) (gf->scratch))->prim_poly);\n}\n\n/* JSP: GF_MULT_SHIFT: The world's dumbest multiplication algorithm.  I only\n   include it for completeness.  It does have the feature that it requires no\n   extra memory.  \n */\n\nstatic\ninline\ngf_val_32_t\ngf_w16_clm_multiply_2 (gf_t *gf, gf_val_32_t a16, gf_val_32_t b16)\n{\n  gf_val_32_t rv = 0;\n\n#if defined(INTEL_SSE4_PCLMUL)\n\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t * h = gf->scratch;\n\n  a = _mm_insert_epi32 (_mm_setzero_si128(), a16, 0);\n  b = _mm_insert_epi32 (a, b16, 0);\n\n  prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0x1ffffULL));\n\n  /* Do the initial multiply */\n  \n  result = _mm_clmulepi64_si128 (a, b, 0);\n\n  /* Ben: Do prim_poly reduction twice. We are guaranteed that we will only\n     have to do the reduction at most twice, because (w-2)/z == 2. Where\n     z is equal to the number of zeros after the leading 1\n\n     _mm_clmulepi64_si128 is the carryless multiply operation. Here\n     _mm_srli_si128 shifts the result to the right by 2 bytes. This allows\n     us to multiply the prim_poly by the leading bits of the result. We\n     then xor the result of that operation back with the result.*/\n\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n  result = _mm_xor_si128 (result, w);\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n  result = _mm_xor_si128 (result, w);\n\n  /* Extracts 32 bit value from result. */\n  \n  rv = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n\n\n#endif\n  return rv;\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w16_clm_multiply_3 (gf_t *gf, gf_val_32_t a16, gf_val_32_t b16)\n{\n  gf_val_32_t rv = 0;\n\n#if defined(INTEL_SSE4_PCLMUL)\n\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t * h = gf->scratch;\n\n  a = _mm_insert_epi32 (_mm_setzero_si128(), a16, 0);\n  b = _mm_insert_epi32 (a, b16, 0);\n\n  prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0x1ffffULL));\n\n  /* Do the initial multiply */\n  \n  result = _mm_clmulepi64_si128 (a, b, 0);\n\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n  result = _mm_xor_si128 (result, w);\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n  result = _mm_xor_si128 (result, w);\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n  result = _mm_xor_si128 (result, w);\n\n  /* Extracts 32 bit value from result. */\n  \n  rv = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n\n\n#endif\n  return rv;\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w16_clm_multiply_4 (gf_t *gf, gf_val_32_t a16, gf_val_32_t b16)\n{\n  gf_val_32_t rv = 0;\n\n#if defined(INTEL_SSE4_PCLMUL)\n\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t * h = gf->scratch;\n\n  a = _mm_insert_epi32 (_mm_setzero_si128(), a16, 0);\n  b = _mm_insert_epi32 (a, b16, 0);\n\n  prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0x1ffffULL));\n\n  /* Do the initial multiply */\n  \n  result = _mm_clmulepi64_si128 (a, b, 0);\n\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n  result = _mm_xor_si128 (result, w);\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n  result = _mm_xor_si128 (result, w);\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n  result = _mm_xor_si128 (result, w);\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 2), 0);\n  result = _mm_xor_si128 (result, w);\n\n  /* Extracts 32 bit value from result. */\n  \n  rv = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n\n\n#endif\n  return rv;\n}\n\n\nstatic\ninline\n gf_val_32_t\ngf_w16_shift_multiply (gf_t *gf, gf_val_32_t a16, gf_val_32_t b16)\n{\n  gf_val_32_t product, i, pp, a, b;\n  gf_internal_t *h;\n\n  a = a16;\n  b = b16;\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  product = 0;\n\n  for (i = 0; i < GF_FIELD_WIDTH; i++) { \n    if (a & (1 << i)) product ^= (b << i);\n  }\n  for (i = (GF_FIELD_WIDTH*2-2); i >= GF_FIELD_WIDTH; i--) {\n    if (product & (1 << i)) product ^= (pp << (i-GF_FIELD_WIDTH)); \n  }\n  return product;\n}\n\nstatic \nint gf_w16_shift_init(gf_t *gf)\n{\n  gf->multiply.w32 = gf_w16_shift_multiply;\n  return 1;\n}\n\nstatic \nint gf_w16_cfm_init(gf_t *gf)\n{\n#if defined(INTEL_SSE4_PCLMUL)\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  \n  /*Ben: Determining how many reductions to do */\n  \n  if ((0xfe00 & h->prim_poly) == 0) {\n    gf->multiply.w32 = gf_w16_clm_multiply_2;\n    gf->multiply_region.w32 = gf_w16_clm_multiply_region_from_single_2;\n  } else if((0xf000 & h->prim_poly) == 0) {\n    gf->multiply.w32 = gf_w16_clm_multiply_3;\n    gf->multiply_region.w32 = gf_w16_clm_multiply_region_from_single_3;\n  } else if ((0xe000 & h->prim_poly) == 0) {\n    gf->multiply.w32 = gf_w16_clm_multiply_4;\n    gf->multiply_region.w32 = gf_w16_clm_multiply_region_from_single_4;\n  } else {\n    return 0;\n  } \n  return 1;\n#endif\n\n  return 0;\n}\n\n/* KMG: GF_MULT_LOGTABLE: */\n\nstatic\nvoid\ngf_w16_log_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint16_t *s16, *d16;\n  int lv;\n  struct gf_w16_logtable_data *ltd;\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 2);\n  gf_do_initial_region_alignment(&rd);\n\n  ltd = (struct gf_w16_logtable_data *) ((gf_internal_t *) gf->scratch)->private;\n  s16 = (uint16_t *) rd.s_start;\n  d16 = (uint16_t *) rd.d_start;\n\n  lv = ltd->log_tbl[val];\n\n  if (xor) {\n    while (d16 < (uint16_t *) rd.d_top) {\n      *d16 ^= (*s16 == 0 ? 0 : ltd->antilog_tbl[lv + ltd->log_tbl[*s16]]);\n      d16++;\n      s16++;\n    }\n  } else {\n    while (d16 < (uint16_t *) rd.d_top) {\n      *d16 = (*s16 == 0 ? 0 : ltd->antilog_tbl[lv + ltd->log_tbl[*s16]]);\n      d16++;\n      s16++;\n    }\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w16_log_multiply(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_w16_logtable_data *ltd;\n\n  ltd = (struct gf_w16_logtable_data *) ((gf_internal_t *) gf->scratch)->private;\n  return (a == 0 || b == 0) ? 0 : ltd->antilog_tbl[(int) ltd->log_tbl[a] + (int) ltd->log_tbl[b]];\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w16_log_divide(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  int log_sum = 0;\n  struct gf_w16_logtable_data *ltd;\n\n  if (a == 0 || b == 0) return 0;\n  ltd = (struct gf_w16_logtable_data *) ((gf_internal_t *) gf->scratch)->private;\n\n  log_sum = (int) ltd->log_tbl[a] - (int) ltd->log_tbl[b];\n  return (ltd->d_antilog[log_sum]);\n}\n\nstatic\ngf_val_32_t\ngf_w16_log_inverse(gf_t *gf, gf_val_32_t a)\n{\n  struct gf_w16_logtable_data *ltd;\n\n  ltd = (struct gf_w16_logtable_data *) ((gf_internal_t *) gf->scratch)->private;\n  return (ltd->inv_tbl[a]);\n}\n\nstatic\nint gf_w16_log_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  struct gf_w16_logtable_data *ltd;\n  int i, b;\n  int check = 0;\n\n  h = (gf_internal_t *) gf->scratch;\n  ltd = h->private;\n  \n  for (i = 0; i < GF_MULT_GROUP_SIZE+1; i++)\n    ltd->log_tbl[i] = 0;\n  ltd->d_antilog = ltd->antilog_tbl + GF_MULT_GROUP_SIZE;\n\n  b = 1;\n  for (i = 0; i < GF_MULT_GROUP_SIZE; i++) {\n      if (ltd->log_tbl[b] != 0) check = 1;\n      ltd->log_tbl[b] = i;\n      ltd->antilog_tbl[i] = b;\n      ltd->antilog_tbl[i+GF_MULT_GROUP_SIZE] = b;\n      b <<= 1;\n      if (b & GF_FIELD_SIZE) {\n          b = b ^ h->prim_poly;\n      }\n  }\n\n  /* If you can't construct the log table, there's a problem.  This code is used for\n     some other implementations (e.g. in SPLIT), so if the log table doesn't work in \n     that instance, use CARRY_FREE / SHIFT instead. */\n\n  if (check) {\n    if (h->mult_type != GF_MULT_LOG_TABLE) {\n\n#if defined(INTEL_SSE4_PCLMUL)\n      return gf_w16_cfm_init(gf);\n#endif\n      return gf_w16_shift_init(gf);\n    } else {\n      _gf_errno = GF_E_LOGPOLY;\n      return 0;\n    }\n  }\n\n  ltd->inv_tbl[0] = 0;  /* Not really, but we need to fill it with something  */\n  ltd->inv_tbl[1] = 1;\n  for (i = 2; i < GF_FIELD_SIZE; i++) {\n    ltd->inv_tbl[i] = ltd->antilog_tbl[GF_MULT_GROUP_SIZE-ltd->log_tbl[i]];\n  }\n\n  gf->inverse.w32 = gf_w16_log_inverse;\n  gf->divide.w32 = gf_w16_log_divide;\n  gf->multiply.w32 = gf_w16_log_multiply;\n  gf->multiply_region.w32 = gf_w16_log_multiply_region;\n\n  return 1;\n}\n\n/* JSP: GF_MULT_SPLIT_TABLE: Using 8 multiplication tables to leverage SSE instructions.\n*/\n\n\n/* Ben: Does alternate mapping multiplication using a split table in the\n lazy method without sse instructions*/\n\nstatic \nvoid\ngf_w16_split_4_16_lazy_nosse_altmap_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint64_t i, j, c, prod;\n  uint8_t *s8, *d8, *top;\n  uint16_t table[4][16];\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 32);\n  gf_do_initial_region_alignment(&rd);    \n\n  /*Ben: Constructs lazy multiplication table*/\n\n  for (j = 0; j < 16; j++) {\n    for (i = 0; i < 4; i++) {\n      c = (j << (i*4));\n      table[i][j] = gf->multiply.w32(gf, c, val);\n    }\n  }\n\n  /*Ben: s8 is the start of source, d8 is the start of dest, top is end of dest region. */\n  \n  s8 = (uint8_t *) rd.s_start;\n  d8 = (uint8_t *) rd.d_start;\n  top = (uint8_t *) rd.d_top;\n\n\n  while (d8 < top) {\n    \n    /*Ben: Multiplies across 16 two byte quantities using alternate mapping \n       high bits are on the left, low bits are on the right. */\n  \n    for (j=0;j<16;j++) {\n    \n      /*Ben: If the xor flag is set, the product should include what is in dest */\n      prod = (xor) ? ((uint16_t)(*d8)<<8) ^ *(d8+16) : 0;\n\n      /*Ben: xors all 4 table lookups into the product variable*/\n      \n      prod ^= ((table[0][*(s8+16)&0xf]) ^\n          (table[1][(*(s8+16)&0xf0)>>4]) ^\n          (table[2][*(s8)&0xf]) ^\n          (table[3][(*(s8)&0xf0)>>4]));\n\n      /*Ben: Stores product in the destination and moves on*/\n      \n      *d8 = (uint8_t)(prod >> 8);\n      *(d8+16) = (uint8_t)(prod & 0x00ff);\n      s8++;\n      d8++;\n    }\n    s8+=16;\n    d8+=16;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\n  void\ngf_w16_split_4_16_lazy_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint64_t i, j, a, c, prod;\n  uint16_t *s16, *d16, *top;\n  uint16_t table[4][16];\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 2);\n  gf_do_initial_region_alignment(&rd);    \n\n  for (j = 0; j < 16; j++) {\n    for (i = 0; i < 4; i++) {\n      c = (j << (i*4));\n      table[i][j] = gf->multiply.w32(gf, c, val);\n    }\n  }\n\n  s16 = (uint16_t *) rd.s_start;\n  d16 = (uint16_t *) rd.d_start;\n  top = (uint16_t *) rd.d_top;\n\n  while (d16 < top) {\n    a = *s16;\n    prod = (xor) ? *d16 : 0;\n    for (i = 0; i < 4; i++) {\n      prod ^= table[i][a&0xf];\n      a >>= 4;\n    }\n    *d16 = prod;\n    s16++;\n    d16++;\n  }\n}\n\nstatic\nvoid\ngf_w16_split_8_16_lazy_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint64_t j, k, v, a, prod, *s64, *d64, *top64;\n  gf_internal_t *h;\n  uint64_t htable[256], ltable[256];\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 8);\n  gf_do_initial_region_alignment(&rd);\n  \n  h = (gf_internal_t *) gf->scratch;\n\n  v = val;\n  ltable[0] = 0;\n  for (j = 1; j < 256; j <<= 1) {\n    for (k = 0; k < j; k++) ltable[k^j] = (v ^ ltable[k]);\n    v = GF_MULTBY_TWO(v);\n  }\n  htable[0] = 0;\n  for (j = 1; j < 256; j <<= 1) {\n    for (k = 0; k < j; k++) htable[k^j] = (v ^ htable[k]);\n    v = GF_MULTBY_TWO(v);\n  }\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  top64 = (uint64_t *) rd.d_top;\n  \n/* Does Unrolling Matter?  -- Doesn't seem to.\n  while (d64 != top64) {\n    a = *s64;\n\n    prod = htable[a >> 56];\n    a <<= 8;\n    prod ^= ltable[a >> 56];\n    a <<= 8;\n    prod <<= 16;\n\n    prod ^= htable[a >> 56];\n    a <<= 8;\n    prod ^= ltable[a >> 56];\n    a <<= 8;\n    prod <<= 16;\n\n    prod ^= htable[a >> 56];\n    a <<= 8;\n    prod ^= ltable[a >> 56];\n    a <<= 8;\n    prod <<= 16;\n\n    prod ^= htable[a >> 56];\n    a <<= 8;\n    prod ^= ltable[a >> 56];\n    prod ^= ((xor) ? *d64 : 0); \n    *d64 = prod;\n    s64++;\n    d64++;\n  }\n*/\n  \n  while (d64 != top64) {\n    a = *s64;\n\n    prod = 0;\n    for (j = 0; j < 4; j++) {\n      prod <<= 16;\n      prod ^= htable[a >> 56];\n      a <<= 8;\n      prod ^= ltable[a >> 56];\n      a <<= 8;\n    }\n\n    //JSP: We can move the conditional outside the while loop, but we need to fully test it to understand which is better.\n   \n    prod ^= ((xor) ? *d64 : 0); \n    *d64 = prod;\n    s64++;\n    d64++;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic void\ngf_w16_table_lazy_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint64_t c;\n  gf_internal_t *h;\n  struct gf_w16_lazytable_data *ltd;\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 8);\n  gf_do_initial_region_alignment(&rd);\n\n  h = (gf_internal_t *) gf->scratch;\n  ltd = (struct gf_w16_lazytable_data *) h->private;\n\n  ltd->lazytable[0] = 0;\n\n  /*\n  a = val;\n  c = 1;\n  pp = h->prim_poly;\n\n  do {\n    ltd->lazytable[c] = a;\n    c <<= 1;\n    if (c & (1 << GF_FIELD_WIDTH)) c ^= pp;\n    a <<= 1;\n    if (a & (1 << GF_FIELD_WIDTH)) a ^= pp;\n  } while (c != 1);\n  */\n\n  for (c = 1; c < GF_FIELD_SIZE; c++) {\n    ltd->lazytable[c] = gf_w16_shift_multiply(gf, c, val);\n  }\n   \n  gf_two_byte_region_table_multiply(&rd, ltd->lazytable);\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\nvoid\ngf_w16_split_4_16_lazy_sse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n#ifdef INTEL_SSSE3\n  uint64_t i, j, *s64, *d64, *top64;;\n  uint64_t c, prod;\n  uint8_t low[4][16];\n  uint8_t high[4][16];\n  gf_region_data rd;\n\n  __m128i  mask, ta, tb, ti, tpl, tph, tlow[4], thigh[4], tta, ttb, lmask;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 32);\n  gf_do_initial_region_alignment(&rd);\n\n  for (j = 0; j < 16; j++) {\n    for (i = 0; i < 4; i++) {\n      c = (j << (i*4));\n      prod = gf->multiply.w32(gf, c, val);\n      low[i][j] = (prod & 0xff);\n      high[i][j] = (prod >> 8);\n    }\n  }\n\n  for (i = 0; i < 4; i++) {\n    tlow[i] = _mm_loadu_si128((__m128i *)low[i]);\n    thigh[i] = _mm_loadu_si128((__m128i *)high[i]);\n  }\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  top64 = (uint64_t *) rd.d_top;\n\n  mask = _mm_set1_epi8 (0x0f);\n  lmask = _mm_set1_epi16 (0xff);\n\n  if (xor) {\n    while (d64 != top64) {\n      \n      ta = _mm_load_si128((__m128i *) s64);\n      tb = _mm_load_si128((__m128i *) (s64+2));\n\n      tta = _mm_srli_epi16(ta, 8);\n      ttb = _mm_srli_epi16(tb, 8);\n      tpl = _mm_and_si128(tb, lmask);\n      tph = _mm_and_si128(ta, lmask);\n\n      tb = _mm_packus_epi16(tpl, tph);\n      ta = _mm_packus_epi16(ttb, tta);\n\n      ti = _mm_and_si128 (mask, tb);\n      tph = _mm_shuffle_epi8 (thigh[0], ti);\n      tpl = _mm_shuffle_epi8 (tlow[0], ti);\n  \n      tb = _mm_srli_epi16(tb, 4);\n      ti = _mm_and_si128 (mask, tb);\n      tpl = _mm_xor_si128(_mm_shuffle_epi8 (tlow[1], ti), tpl);\n      tph = _mm_xor_si128(_mm_shuffle_epi8 (thigh[1], ti), tph);\n\n      ti = _mm_and_si128 (mask, ta);\n      tpl = _mm_xor_si128(_mm_shuffle_epi8 (tlow[2], ti), tpl);\n      tph = _mm_xor_si128(_mm_shuffle_epi8 (thigh[2], ti), tph);\n  \n      ta = _mm_srli_epi16(ta, 4);\n      ti = _mm_and_si128 (mask, ta);\n      tpl = _mm_xor_si128(_mm_shuffle_epi8 (tlow[3], ti), tpl);\n      tph = _mm_xor_si128(_mm_shuffle_epi8 (thigh[3], ti), tph);\n\n      ta = _mm_unpackhi_epi8(tpl, tph);\n      tb = _mm_unpacklo_epi8(tpl, tph);\n\n      tta = _mm_load_si128((__m128i *) d64);\n      ta = _mm_xor_si128(ta, tta);\n      ttb = _mm_load_si128((__m128i *) (d64+2));\n      tb = _mm_xor_si128(tb, ttb); \n      _mm_store_si128 ((__m128i *)d64, ta);\n      _mm_store_si128 ((__m128i *)(d64+2), tb);\n\n      d64 += 4;\n      s64 += 4;\n      \n    }\n  } else {\n    while (d64 != top64) {\n      \n      ta = _mm_load_si128((__m128i *) s64);\n      tb = _mm_load_si128((__m128i *) (s64+2));\n\n      tta = _mm_srli_epi16(ta, 8);\n      ttb = _mm_srli_epi16(tb, 8);\n      tpl = _mm_and_si128(tb, lmask);\n      tph = _mm_and_si128(ta, lmask);\n\n      tb = _mm_packus_epi16(tpl, tph);\n      ta = _mm_packus_epi16(ttb, tta);\n\n      ti = _mm_and_si128 (mask, tb);\n      tph = _mm_shuffle_epi8 (thigh[0], ti);\n      tpl = _mm_shuffle_epi8 (tlow[0], ti);\n  \n      tb = _mm_srli_epi16(tb, 4);\n      ti = _mm_and_si128 (mask, tb);\n      tpl = _mm_xor_si128(_mm_shuffle_epi8 (tlow[1], ti), tpl);\n      tph = _mm_xor_si128(_mm_shuffle_epi8 (thigh[1], ti), tph);\n\n      ti = _mm_and_si128 (mask, ta);\n      tpl = _mm_xor_si128(_mm_shuffle_epi8 (tlow[2], ti), tpl);\n      tph = _mm_xor_si128(_mm_shuffle_epi8 (thigh[2], ti), tph);\n  \n      ta = _mm_srli_epi16(ta, 4);\n      ti = _mm_and_si128 (mask, ta);\n      tpl = _mm_xor_si128(_mm_shuffle_epi8 (tlow[3], ti), tpl);\n      tph = _mm_xor_si128(_mm_shuffle_epi8 (thigh[3], ti), tph);\n\n      ta = _mm_unpackhi_epi8(tpl, tph);\n      tb = _mm_unpacklo_epi8(tpl, tph);\n\n      _mm_store_si128 ((__m128i *)d64, ta);\n      _mm_store_si128 ((__m128i *)(d64+2), tb);\n\n      d64 += 4;\n      s64 += 4;\n    }\n  }\n\n  gf_do_final_region_alignment(&rd);\n#endif\n}\n\nstatic\nvoid\ngf_w16_split_4_16_lazy_sse_altmap_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n#ifdef INTEL_SSSE3\n  uint64_t i, j, *s64, *d64, *top64;;\n  uint64_t c, prod;\n  uint8_t low[4][16];\n  uint8_t high[4][16];\n  gf_region_data rd;\n  __m128i  mask, ta, tb, ti, tpl, tph, tlow[4], thigh[4];\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 32);\n  gf_do_initial_region_alignment(&rd);\n\n  for (j = 0; j < 16; j++) {\n    for (i = 0; i < 4; i++) {\n      c = (j << (i*4));\n      prod = gf->multiply.w32(gf, c, val);\n      low[i][j] = (prod & 0xff);\n      high[i][j] = (prod >> 8);\n    }\n  }\n\n  for (i = 0; i < 4; i++) {\n    tlow[i] = _mm_loadu_si128((__m128i *)low[i]);\n    thigh[i] = _mm_loadu_si128((__m128i *)high[i]);\n  }\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  top64 = (uint64_t *) rd.d_top;\n\n  mask = _mm_set1_epi8 (0x0f);\n\n  if (xor) {\n    while (d64 != top64) {\n\n      ta = _mm_load_si128((__m128i *) s64);\n      tb = _mm_load_si128((__m128i *) (s64+2));\n\n      ti = _mm_and_si128 (mask, tb);\n      tph = _mm_shuffle_epi8 (thigh[0], ti);\n      tpl = _mm_shuffle_epi8 (tlow[0], ti);\n  \n      tb = _mm_srli_epi16(tb, 4);\n      ti = _mm_and_si128 (mask, tb);\n      tpl = _mm_xor_si128(_mm_shuffle_epi8 (tlow[1], ti), tpl);\n      tph = _mm_xor_si128(_mm_shuffle_epi8 (thigh[1], ti), tph);\n\n      ti = _mm_and_si128 (mask, ta);\n      tpl = _mm_xor_si128(_mm_shuffle_epi8 (tlow[2], ti), tpl);\n      tph = _mm_xor_si128(_mm_shuffle_epi8 (thigh[2], ti), tph);\n  \n      ta = _mm_srli_epi16(ta, 4);\n      ti = _mm_and_si128 (mask, ta);\n      tpl = _mm_xor_si128(_mm_shuffle_epi8 (tlow[3], ti), tpl);\n      tph = _mm_xor_si128(_mm_shuffle_epi8 (thigh[3], ti), tph);\n\n      ta = _mm_load_si128((__m128i *) d64);\n      tph = _mm_xor_si128(tph, ta);\n      _mm_store_si128 ((__m128i *)d64, tph);\n      tb = _mm_load_si128((__m128i *) (d64+2));\n      tpl = _mm_xor_si128(tpl, tb);\n      _mm_store_si128 ((__m128i *)(d64+2), tpl);\n\n      d64 += 4;\n      s64 += 4;\n    }\n  } else {\n    while (d64 != top64) {\n\n      ta = _mm_load_si128((__m128i *) s64);\n      tb = _mm_load_si128((__m128i *) (s64+2));\n\n      ti = _mm_and_si128 (mask, tb);\n      tph = _mm_shuffle_epi8 (thigh[0], ti);\n      tpl = _mm_shuffle_epi8 (tlow[0], ti);\n  \n      tb = _mm_srli_epi16(tb, 4);\n      ti = _mm_and_si128 (mask, tb);\n      tpl = _mm_xor_si128(_mm_shuffle_epi8 (tlow[1], ti), tpl);\n      tph = _mm_xor_si128(_mm_shuffle_epi8 (thigh[1], ti), tph);\n\n      ti = _mm_and_si128 (mask, ta);\n      tpl = _mm_xor_si128(_mm_shuffle_epi8 (tlow[2], ti), tpl);\n      tph = _mm_xor_si128(_mm_shuffle_epi8 (thigh[2], ti), tph);\n  \n      ta = _mm_srli_epi16(ta, 4);\n      ti = _mm_and_si128 (mask, ta);\n      tpl = _mm_xor_si128(_mm_shuffle_epi8 (tlow[3], ti), tpl);\n      tph = _mm_xor_si128(_mm_shuffle_epi8 (thigh[3], ti), tph);\n\n      _mm_store_si128 ((__m128i *)d64, tph);\n      _mm_store_si128 ((__m128i *)(d64+2), tpl);\n\n      d64 += 4;\n      s64 += 4;\n      \n    }\n  }\n  gf_do_final_region_alignment(&rd);\n\n#endif\n}\n\nuint32_t \ngf_w16_split_8_8_multiply(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  uint32_t alow, blow;\n  struct gf_w16_split_8_8_data *d8;\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  d8 = (struct gf_w16_split_8_8_data *) h->private;\n\n  alow = a & 0xff;\n  blow = b & 0xff;\n  a >>= 8;\n  b >>= 8;\n\n  return d8->tables[0][alow][blow] ^\n         d8->tables[1][alow][b] ^\n         d8->tables[1][a][blow] ^\n         d8->tables[2][a][b];\n}\n\nstatic \nint gf_w16_split_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  struct gf_w16_split_8_8_data *d8;\n  int i, j, exp, issse3;\n  int isneon = 0;\n  uint32_t p, basep, tmp;\n\n  h = (gf_internal_t *) gf->scratch;\n\n#ifdef INTEL_SSSE3\n  issse3 = 1;\n#else\n  issse3 = 0;\n#endif\n#ifdef ARM_NEON\n  isneon = 1;\n#endif\n\n  if (h->arg1 == 8 && h->arg2 == 8) {\n    d8 = (struct gf_w16_split_8_8_data *) h->private;\n    basep = 1;\n    for (exp = 0; exp < 3; exp++) {\n      for (j = 0; j < 256; j++) d8->tables[exp][0][j] = 0;\n      for (i = 0; i < 256; i++) d8->tables[exp][i][0] = 0;\n      d8->tables[exp][1][1] = basep;\n      for (i = 2; i < 256; i++) {\n        if (i&1) {\n          p = d8->tables[exp][i^1][1];\n          d8->tables[exp][i][1] = p ^ basep;\n        } else {\n          p = d8->tables[exp][i>>1][1];\n          d8->tables[exp][i][1] = GF_MULTBY_TWO(p);\n        }\n      }\n      for (i = 1; i < 256; i++) {\n        p = d8->tables[exp][i][1];\n        for (j = 1; j < 256; j++) {\n          if (j&1) {\n            d8->tables[exp][i][j] = d8->tables[exp][i][j^1] ^ p;\n          } else {\n            tmp = d8->tables[exp][i][j>>1];\n            d8->tables[exp][i][j] = GF_MULTBY_TWO(tmp);\n          }\n        }\n      }\n      for (i = 0; i < 8; i++) basep = GF_MULTBY_TWO(basep);\n    }\n    gf->multiply.w32 = gf_w16_split_8_8_multiply;\n    gf->multiply_region.w32 = gf_w16_split_8_16_lazy_multiply_region;\n    return 1;\n\n  }\n\n  /* We'll be using LOG for multiplication, unless the pp isn't primitive.\n     In that case, we'll be using SHIFT. */\n\n  gf_w16_log_init(gf);\n\n  /* Defaults */\n\n  if (issse3) {\n    gf->multiply_region.w32 = gf_w16_split_4_16_lazy_sse_multiply_region;\n  } else if (isneon) {\n#ifdef ARM_NEON\n    gf_w16_neon_split_init(gf);\n#endif\n  } else {\n    gf->multiply_region.w32 = gf_w16_split_8_16_lazy_multiply_region;\n  }\n\n\n  if ((h->arg1 == 8 && h->arg2 == 16) || (h->arg2 == 8 && h->arg1 == 16)) {\n    gf->multiply_region.w32 = gf_w16_split_8_16_lazy_multiply_region;\n\n  } else if ((h->arg1 == 4 && h->arg2 == 16) || (h->arg2 == 4 && h->arg1 == 16)) {\n    if (issse3 || isneon) {\n      if(h->region_type & GF_REGION_ALTMAP && h->region_type & GF_REGION_NOSIMD)\n        gf->multiply_region.w32 = gf_w16_split_4_16_lazy_nosse_altmap_multiply_region;\n      else if(h->region_type & GF_REGION_NOSIMD)\n        gf->multiply_region.w32 = gf_w16_split_4_16_lazy_multiply_region;\n      else if(h->region_type & GF_REGION_ALTMAP && issse3)\n        gf->multiply_region.w32 = gf_w16_split_4_16_lazy_sse_altmap_multiply_region;\n    } else {\n      if(h->region_type & GF_REGION_SIMD)\n        return 0;\n      else if(h->region_type & GF_REGION_ALTMAP)\n        gf->multiply_region.w32 = gf_w16_split_4_16_lazy_nosse_altmap_multiply_region;\n      else\n        gf->multiply_region.w32 = gf_w16_split_4_16_lazy_multiply_region;\n    }\n  }\n\n  return 1;\n}\n\nstatic \nint gf_w16_table_init(gf_t *gf)\n{\n  gf_w16_log_init(gf);\n\n  gf->multiply_region.w32 = gf_w16_table_lazy_multiply_region; \n  return 1;\n}\n\nstatic\nvoid\ngf_w16_log_zero_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint16_t lv;\n  int i;\n  uint16_t *s16, *d16, *top16;\n  struct gf_w16_zero_logtable_data *ltd;\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 2);\n  gf_do_initial_region_alignment(&rd);\n\n  ltd = (struct gf_w16_zero_logtable_data*) ((gf_internal_t *) gf->scratch)->private;\n  s16 = (uint16_t *) rd.s_start;\n  d16 = (uint16_t *) rd.d_start;\n  top16 = (uint16_t *) rd.d_top;\n  bytes = top16 - d16;\n\n  lv = ltd->log_tbl[val];\n\n  if (xor) {\n    for (i = 0; i < bytes; i++) {\n      d16[i] ^= (ltd->antilog_tbl[lv + ltd->log_tbl[s16[i]]]);\n    }\n  } else {\n    for (i = 0; i < bytes; i++) {\n      d16[i] = (ltd->antilog_tbl[lv + ltd->log_tbl[s16[i]]]);\n    }\n  }\n\n  /* This isn't necessary. */\n  \n  gf_do_final_region_alignment(&rd);\n}\n\n/* Here -- double-check Kevin */\n\nstatic\ninline\ngf_val_32_t\ngf_w16_log_zero_multiply (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_w16_zero_logtable_data *ltd;\n\n  ltd = (struct gf_w16_zero_logtable_data *) ((gf_internal_t *) gf->scratch)->private;\n  return ltd->antilog_tbl[ltd->log_tbl[a] + ltd->log_tbl[b]];\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w16_log_zero_divide (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  int log_sum = 0;\n  struct gf_w16_zero_logtable_data *ltd;\n\n  if (a == 0 || b == 0) return 0;\n  ltd = (struct gf_w16_zero_logtable_data *) ((gf_internal_t *) gf->scratch)->private;\n\n  log_sum = ltd->log_tbl[a] - ltd->log_tbl[b] + (GF_MULT_GROUP_SIZE);\n  return (ltd->antilog_tbl[log_sum]);\n}\n\nstatic\ngf_val_32_t\ngf_w16_log_zero_inverse (gf_t *gf, gf_val_32_t a)\n{\n  struct gf_w16_zero_logtable_data *ltd;\n\n  ltd = (struct gf_w16_zero_logtable_data *) ((gf_internal_t *) gf->scratch)->private;\n  return (ltd->inv_tbl[a]);\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w16_bytwo_p_multiply (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  uint32_t prod, pp, pmask, amask;\n  gf_internal_t *h;\n  \n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  \n  prod = 0;\n  pmask = 0x8000;\n  amask = 0x8000;\n\n  while (amask != 0) {\n    if (prod & pmask) {\n      prod = ((prod << 1) ^ pp);\n    } else {\n      prod <<= 1;\n    }\n    if (a & amask) prod ^= b;\n    amask >>= 1;\n  }\n  return prod;\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w16_bytwo_b_multiply (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  uint32_t prod, pp, bmask;\n  gf_internal_t *h;\n  \n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  prod = 0;\n  bmask = 0x8000;\n\n  while (1) {\n    if (a & 1) prod ^= b;\n    a >>= 1;\n    if (a == 0) return prod;\n    if (b & bmask) {\n      b = ((b << 1) ^ pp);\n    } else {\n      b <<= 1;\n    }\n  }\n}\n\nstatic\nvoid \ngf_w16_bytwo_p_nosse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint64_t *s64, *d64, t1, t2, ta, prod, amask;\n  gf_region_data rd;\n  struct gf_w16_bytwo_data *btd;\n    \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  btd = (struct gf_w16_bytwo_data *) ((gf_internal_t *) (gf->scratch))->private;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 8);\n  gf_do_initial_region_alignment(&rd);\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n\n  if (xor) {\n    while (s64 < (uint64_t *) rd.s_top) {\n      prod = 0;\n      amask = 0x8000;\n      ta = *s64;\n      while (amask != 0) {\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, prod, t1, t2);\n        if (val & amask) prod ^= ta;\n        amask >>= 1;\n      }\n      *d64 ^= prod;\n      d64++;\n      s64++;\n    }\n  } else { \n    while (s64 < (uint64_t *) rd.s_top) {\n      prod = 0;\n      amask = 0x8000;\n      ta = *s64;\n      while (amask != 0) {\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, prod, t1, t2);\n        if (val & amask) prod ^= ta;\n        amask >>= 1;\n      }\n      *d64 = prod;\n      d64++;\n      s64++;\n    }\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\n#define BYTWO_P_ONESTEP {\\\n      SSE_AB2(pp, m1 ,m2, prod, t1, t2); \\\n      t1 = _mm_and_si128(v, one); \\\n      t1 = _mm_sub_epi16(t1, one); \\\n      t1 = _mm_and_si128(t1, ta); \\\n      prod = _mm_xor_si128(prod, t1); \\\n      v = _mm_srli_epi64(v, 1); }\n\n#ifdef INTEL_SSE2\nstatic\nvoid \ngf_w16_bytwo_p_sse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  int i;\n  uint8_t *s8, *d8;\n  uint32_t vrev;\n  __m128i pp, m1, m2, ta, prod, t1, t2, tp, one, v;\n  struct gf_w16_bytwo_data *btd;\n  gf_region_data rd;\n    \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  btd = (struct gf_w16_bytwo_data *) ((gf_internal_t *) (gf->scratch))->private;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n\n  vrev = 0;\n  for (i = 0; i < 16; i++) {\n    vrev <<= 1;\n    if (!(val & (1 << i))) vrev |= 1;\n  }\n\n  s8 = (uint8_t *) rd.s_start;\n  d8 = (uint8_t *) rd.d_start;\n\n  pp = _mm_set1_epi16(btd->prim_poly&0xffff);\n  m1 = _mm_set1_epi16((btd->mask1)&0xffff);\n  m2 = _mm_set1_epi16((btd->mask2)&0xffff);\n  one = _mm_set1_epi16(1);\n\n  while (d8 < (uint8_t *) rd.d_top) {\n    prod = _mm_setzero_si128();\n    v = _mm_set1_epi16(vrev);\n    ta = _mm_load_si128((__m128i *) s8);\n    tp = (!xor) ? _mm_setzero_si128() : _mm_load_si128((__m128i *) d8);\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    _mm_store_si128((__m128i *) d8, _mm_xor_si128(prod, tp));\n    d8 += 16;\n    s8 += 16;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic\nvoid\ngf_w16_bytwo_b_sse_region_2_noxor(gf_region_data *rd, struct gf_w16_bytwo_data *btd)\n{\n  uint8_t *d8, *s8;\n  __m128i pp, m1, m2, t1, t2, va;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  pp = _mm_set1_epi16(btd->prim_poly&0xffff);\n  m1 = _mm_set1_epi16((btd->mask1)&0xffff);\n  m2 = _mm_set1_epi16((btd->mask2)&0xffff);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    SSE_AB2(pp, m1, m2, va, t1, t2);\n    _mm_store_si128((__m128i *)d8, va);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic\nvoid\ngf_w16_bytwo_b_sse_region_2_xor(gf_region_data *rd, struct gf_w16_bytwo_data *btd)\n{\n  uint8_t *d8, *s8;\n  __m128i pp, m1, m2, t1, t2, va, vb;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  pp = _mm_set1_epi16(btd->prim_poly&0xffff);\n  m1 = _mm_set1_epi16((btd->mask1)&0xffff);\n  m2 = _mm_set1_epi16((btd->mask2)&0xffff);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    SSE_AB2(pp, m1, m2, va, t1, t2);\n    vb = _mm_load_si128 ((__m128i *)(d8));\n    vb = _mm_xor_si128(vb, va);\n    _mm_store_si128((__m128i *)d8, vb);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n\n#ifdef INTEL_SSE2\nstatic\nvoid \ngf_w16_bytwo_b_sse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  int itb;\n  uint8_t *d8, *s8;\n  __m128i pp, m1, m2, t1, t2, va, vb;\n  struct gf_w16_bytwo_data *btd;\n  gf_region_data rd;\n    \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n\n  btd = (struct gf_w16_bytwo_data *) ((gf_internal_t *) (gf->scratch))->private;\n\n  if (val == 2) {\n    if (xor) {\n      gf_w16_bytwo_b_sse_region_2_xor(&rd, btd);\n    } else {\n      gf_w16_bytwo_b_sse_region_2_noxor(&rd, btd);\n    }\n    gf_do_final_region_alignment(&rd);\n    return;\n  }\n\n  s8 = (uint8_t *) rd.s_start;\n  d8 = (uint8_t *) rd.d_start;\n\n  pp = _mm_set1_epi16(btd->prim_poly&0xffff);\n  m1 = _mm_set1_epi16((btd->mask1)&0xffff);\n  m2 = _mm_set1_epi16((btd->mask2)&0xffff);\n\n  while (d8 < (uint8_t *) rd.d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    vb = (!xor) ? _mm_setzero_si128() : _mm_load_si128 ((__m128i *)(d8));\n    itb = val;\n    while (1) {\n      if (itb & 1) vb = _mm_xor_si128(vb, va);\n      itb >>= 1;\n      if (itb == 0) break;\n      SSE_AB2(pp, m1, m2, va, t1, t2);\n    }\n    _mm_store_si128((__m128i *)d8, vb);\n    d8 += 16;\n    s8 += 16;\n  }\n\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\nstatic\nvoid \ngf_w16_bytwo_b_nosse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint64_t *s64, *d64, t1, t2, ta, tb, prod;\n  struct gf_w16_bytwo_data *btd;\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n\n  btd = (struct gf_w16_bytwo_data *) ((gf_internal_t *) (gf->scratch))->private;\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n\n  switch (val) {\n  case 2:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= ta;\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = ta;\n        d64++;\n        s64++;\n      }\n    }\n    break; \n  case 3:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    }\n    break; \n  case 4:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= ta;\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = ta;\n        d64++;\n        s64++;\n      }\n    }\n    break; \n  case 5:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = ta ^ prod;\n        d64++;\n        s64++;\n      }\n    }\n    break;\n  default:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        prod = *d64 ;\n        ta = *s64;\n        tb = val;\n        while (1) {\n          if (tb & 1) prod ^= ta;\n          tb >>= 1;\n          if (tb == 0) break;\n          AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        }\n        *d64 = prod;\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        prod = 0 ;\n        ta = *s64;\n        tb = val;\n        while (1) {\n          if (tb & 1) prod ^= ta;\n          tb >>= 1;\n          if (tb == 0) break;\n          AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        }\n        *d64 = prod;\n        d64++;\n        s64++;\n      }\n    }\n    break;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\nint gf_w16_bytwo_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  uint64_t ip, m1, m2;\n  struct gf_w16_bytwo_data *btd;\n\n  h = (gf_internal_t *) gf->scratch;\n  btd = (struct gf_w16_bytwo_data *) (h->private);\n  ip = h->prim_poly & 0xffff;\n  m1 = 0xfffe;\n  m2 = 0x8000;\n  btd->prim_poly = 0;\n  btd->mask1 = 0;\n  btd->mask2 = 0;\n\n  while (ip != 0) {\n    btd->prim_poly |= ip;\n    btd->mask1 |= m1;\n    btd->mask2 |= m2;\n    ip <<= GF_FIELD_WIDTH;\n    m1 <<= GF_FIELD_WIDTH;\n    m2 <<= GF_FIELD_WIDTH;\n  }\n\n  if (h->mult_type == GF_MULT_BYTWO_p) {\n    gf->multiply.w32 = gf_w16_bytwo_p_multiply;\n    #ifdef INTEL_SSE2\n      if (h->region_type & GF_REGION_NOSIMD)\n        gf->multiply_region.w32 = gf_w16_bytwo_p_nosse_multiply_region;\n      else\n        gf->multiply_region.w32 = gf_w16_bytwo_p_sse_multiply_region;\n    #else\n      gf->multiply_region.w32 = gf_w16_bytwo_p_nosse_multiply_region;\n      if(h->region_type & GF_REGION_SIMD)\n        return 0;\n    #endif\n  } else {\n    gf->multiply.w32 = gf_w16_bytwo_b_multiply;\n    #ifdef INTEL_SSE2\n      if (h->region_type & GF_REGION_NOSIMD)\n        gf->multiply_region.w32 = gf_w16_bytwo_b_nosse_multiply_region;\n      else\n        gf->multiply_region.w32 = gf_w16_bytwo_b_sse_multiply_region;\n    #else\n      gf->multiply_region.w32 = gf_w16_bytwo_b_nosse_multiply_region;\n      if(h->region_type & GF_REGION_SIMD)\n        return 0;\n    #endif\n  }\n\n  return 1;\n}\n\nstatic\nint gf_w16_log_zero_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  struct gf_w16_zero_logtable_data *ltd;\n  int i, b;\n\n  h = (gf_internal_t *) gf->scratch;\n  ltd = h->private;\n\n  ltd->log_tbl[0] = (-GF_MULT_GROUP_SIZE) + 1;\n\n  bzero(&(ltd->_antilog_tbl[0]), sizeof(ltd->_antilog_tbl));\n\n  ltd->antilog_tbl = &(ltd->_antilog_tbl[GF_FIELD_SIZE * 2]);\n\n  b = 1;\n  for (i = 0; i < GF_MULT_GROUP_SIZE; i++) {\n      ltd->log_tbl[b] = (uint16_t)i;\n      ltd->antilog_tbl[i] = (uint16_t)b;\n      ltd->antilog_tbl[i+GF_MULT_GROUP_SIZE] = (uint16_t)b;\n      b <<= 1;\n      if (b & GF_FIELD_SIZE) {\n          b = b ^ h->prim_poly;\n      }\n  }\n  ltd->inv_tbl[0] = 0;  /* Not really, but we need to fill it with something  */\n  ltd->inv_tbl[1] = 1;\n  for (i = 2; i < GF_FIELD_SIZE; i++) {\n    ltd->inv_tbl[i] = ltd->antilog_tbl[GF_MULT_GROUP_SIZE-ltd->log_tbl[i]];\n  }\n\n  gf->inverse.w32 = gf_w16_log_zero_inverse;\n  gf->divide.w32 = gf_w16_log_zero_divide;\n  gf->multiply.w32 = gf_w16_log_zero_multiply;\n  gf->multiply_region.w32 = gf_w16_log_zero_multiply_region;\n  return 1;\n}\n\nstatic\ngf_val_32_t\ngf_w16_composite_multiply_recursive(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  gf_t *base_gf = h->base_gf;\n  uint8_t b0 = b & 0x00ff;\n  uint8_t b1 = (b & 0xff00) >> 8;\n  uint8_t a0 = a & 0x00ff;\n  uint8_t a1 = (a & 0xff00) >> 8;\n  uint8_t a1b1;\n  uint16_t rv;\n\n  a1b1 = base_gf->multiply.w32(base_gf, a1, b1);\n\n  rv = ((base_gf->multiply.w32(base_gf, a0, b0) ^ a1b1) | ((base_gf->multiply.w32(base_gf, a1, b0) ^ base_gf->multiply.w32(base_gf, a0, b1) ^ base_gf->multiply.w32(base_gf, a1b1, h->prim_poly)) << 8));\n  return rv;\n}\n\nstatic\ngf_val_32_t\ngf_w16_composite_multiply_inline(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  uint8_t b0 = b & 0x00ff;\n  uint8_t b1 = (b & 0xff00) >> 8;\n  uint8_t a0 = a & 0x00ff;\n  uint8_t a1 = (a & 0xff00) >> 8;\n  uint8_t a1b1, *mt;\n  uint16_t rv;\n  struct gf_w16_composite_data *cd;\n\n  cd = (struct gf_w16_composite_data *) h->private;\n  mt = cd->mult_table;\n\n  a1b1 = GF_W8_INLINE_MULTDIV(mt, a1, b1);\n\n  rv = ((GF_W8_INLINE_MULTDIV(mt, a0, b0) ^ a1b1) | ((GF_W8_INLINE_MULTDIV(mt, a1, b0) ^ GF_W8_INLINE_MULTDIV(mt, a0, b1) ^ GF_W8_INLINE_MULTDIV(mt, a1b1, h->prim_poly)) << 8));\n  return rv;\n}\n\n/*\n * Composite field division trick (explained in 2007 tech report)\n *\n * Compute a / b = a*b^-1, where p(x) = x^2 + sx + 1\n *\n * let c = b^-1\n *\n * c*b = (s*b1c1+b1c0+b0c1)x+(b1c1+b0c0)\n *\n * want (s*b1c1+b1c0+b0c1) = 0 and (b1c1+b0c0) = 1\n *\n * let d = b1c1 and d+1 = b0c0\n *\n * solve s*b1c1+b1c0+b0c1 = 0\n *\n * solution: d = (b1b0^-1)(b1b0^-1+b0b1^-1+s)^-1\n *\n * c0 = (d+1)b0^-1\n * c1 = d*b1^-1\n *\n * a / b = a * c\n */\n\nstatic\ngf_val_32_t\ngf_w16_composite_inverse(gf_t *gf, gf_val_32_t a)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  gf_t *base_gf = h->base_gf;\n  uint8_t a0 = a & 0x00ff;\n  uint8_t a1 = (a & 0xff00) >> 8;\n  uint8_t c0, c1, d, tmp;\n  uint16_t c;\n  uint8_t a0inv, a1inv;\n\n  if (a0 == 0) {\n    a1inv = base_gf->inverse.w32(base_gf, a1);\n    c0 = base_gf->multiply.w32(base_gf, a1inv, h->prim_poly);\n    c1 = a1inv;\n  } else if (a1 == 0) {\n    c0 = base_gf->inverse.w32(base_gf, a0);\n    c1 = 0;\n  } else {\n    a1inv = base_gf->inverse.w32(base_gf, a1);\n    a0inv = base_gf->inverse.w32(base_gf, a0);\n\n    d = base_gf->multiply.w32(base_gf, a1, a0inv);\n\n    tmp = (base_gf->multiply.w32(base_gf, a1, a0inv) ^ base_gf->multiply.w32(base_gf, a0, a1inv) ^ h->prim_poly);\n    tmp = base_gf->inverse.w32(base_gf, tmp);\n\n    d = base_gf->multiply.w32(base_gf, d, tmp);\n\n    c0 = base_gf->multiply.w32(base_gf, (d^1), a0inv);\n    c1 = base_gf->multiply.w32(base_gf, d, a1inv);\n  }\n\n  c = c0 | (c1 << 8);\n\n  return c;\n}\n\nstatic\nvoid\ngf_w16_composite_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  gf_t *base_gf = h->base_gf;\n  uint8_t b0 = val & 0x00ff;\n  uint8_t b1 = (val & 0xff00) >> 8;\n  uint16_t *s16, *d16, *top;\n  uint8_t a0, a1, a1b1, *mt;\n  gf_region_data rd;\n  struct gf_w16_composite_data *cd;\n\n  cd = (struct gf_w16_composite_data *) h->private;\n  mt = cd->mult_table;\n  \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 2);\n\n  s16 = rd.s_start;\n  d16 = rd.d_start;\n  top = rd.d_top;\n\n  if (mt == NULL) {\n    if (xor) {\n      while (d16 < top) {\n        a0 = (*s16) & 0x00ff;\n        a1 = ((*s16) & 0xff00) >> 8;\n        a1b1 = base_gf->multiply.w32(base_gf, a1, b1);\n  \n        (*d16) ^= ((base_gf->multiply.w32(base_gf, a0, b0) ^ a1b1) |\n                  ((base_gf->multiply.w32(base_gf, a1, b0) ^ \n                    base_gf->multiply.w32(base_gf, a0, b1) ^ \n                    base_gf->multiply.w32(base_gf, a1b1, h->prim_poly)) << 8));\n        s16++;\n        d16++;\n      }\n    } else {\n      while (d16 < top) {\n        a0 = (*s16) & 0x00ff;\n        a1 = ((*s16) & 0xff00) >> 8;\n        a1b1 = base_gf->multiply.w32(base_gf, a1, b1);\n  \n        (*d16) = ((base_gf->multiply.w32(base_gf, a0, b0) ^ a1b1) |\n                  ((base_gf->multiply.w32(base_gf, a1, b0) ^ \n                    base_gf->multiply.w32(base_gf, a0, b1) ^ \n                    base_gf->multiply.w32(base_gf, a1b1, h->prim_poly)) << 8));\n        s16++;\n        d16++;\n      }\n    }\n  } else {\n    if (xor) {\n      while (d16 < top) {\n        a0 = (*s16) & 0x00ff;\n        a1 = ((*s16) & 0xff00) >> 8;\n        a1b1 = GF_W8_INLINE_MULTDIV(mt, a1, b1);\n  \n        (*d16) ^= ((GF_W8_INLINE_MULTDIV(mt, a0, b0) ^ a1b1) |\n                  ((GF_W8_INLINE_MULTDIV(mt, a1, b0) ^ \n                    GF_W8_INLINE_MULTDIV(mt, a0, b1) ^ \n                    GF_W8_INLINE_MULTDIV(mt, a1b1, h->prim_poly)) << 8));\n        s16++;\n        d16++;\n      }\n    } else {\n      while (d16 < top) {\n        a0 = (*s16) & 0x00ff;\n        a1 = ((*s16) & 0xff00) >> 8;\n        a1b1 = GF_W8_INLINE_MULTDIV(mt, a1, b1);\n  \n        (*d16) = ((GF_W8_INLINE_MULTDIV(mt, a0, b0) ^ a1b1) |\n                  ((GF_W8_INLINE_MULTDIV(mt, a1, b0) ^ \n                    GF_W8_INLINE_MULTDIV(mt, a0, b1) ^ \n                    GF_W8_INLINE_MULTDIV(mt, a1b1, h->prim_poly)) << 8));\n        s16++;\n        d16++;\n      }\n    }\n  }\n}\n\nstatic\nvoid\ngf_w16_composite_multiply_region_alt(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  gf_t *base_gf = h->base_gf;\n  uint8_t val0 = val & 0x00ff;\n  uint8_t val1 = (val & 0xff00) >> 8;\n  gf_region_data rd;\n  int sub_reg_size;\n  uint8_t *slow, *shigh;\n  uint8_t *dlow, *dhigh, *top;;\n\n  /* JSP: I want the two pointers aligned wrt each other on 16 byte \n     boundaries.  So I'm going to make sure that the area on \n     which the two operate is a multiple of 32. Of course, that \n     junks up the mapping, but so be it -- that's why we have extract_word.... */\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 32);\n  gf_do_initial_region_alignment(&rd);\n\n  slow = (uint8_t *) rd.s_start;\n  dlow = (uint8_t *) rd.d_start;\n  top = (uint8_t *)  rd.d_top;\n  sub_reg_size = (top - dlow)/2;\n  shigh = slow + sub_reg_size;\n  dhigh = dlow + sub_reg_size;\n\n  base_gf->multiply_region.w32(base_gf, slow, dlow, val0, sub_reg_size, xor);\n  base_gf->multiply_region.w32(base_gf, shigh, dlow, val1, sub_reg_size, 1);\n  base_gf->multiply_region.w32(base_gf, slow, dhigh, val1, sub_reg_size, xor);\n  base_gf->multiply_region.w32(base_gf, shigh, dhigh, val0, sub_reg_size, 1);\n  base_gf->multiply_region.w32(base_gf, shigh, dhigh, base_gf->multiply.w32(base_gf, h->prim_poly, val1), sub_reg_size, 1);\n\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\nint gf_w16_composite_init(gf_t *gf)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  struct gf_w16_composite_data *cd;\n\n  if (h->base_gf == NULL) return 0;\n\n  cd = (struct gf_w16_composite_data *) h->private;\n  cd->mult_table = gf_w8_get_mult_table(h->base_gf);\n\n  if (h->region_type & GF_REGION_ALTMAP) {\n    gf->multiply_region.w32 = gf_w16_composite_multiply_region_alt;\n  } else {\n    gf->multiply_region.w32 = gf_w16_composite_multiply_region;\n  }\n\n  if (cd->mult_table == NULL) {\n    gf->multiply.w32 = gf_w16_composite_multiply_recursive;\n  } else {\n    gf->multiply.w32 = gf_w16_composite_multiply_inline;\n  }\n  gf->divide.w32 = NULL;\n  gf->inverse.w32 = gf_w16_composite_inverse;\n\n  return 1;\n}\n\nstatic\nvoid\ngf_w16_group_4_set_shift_tables(uint16_t *shift, uint16_t val, gf_internal_t *h)\n{\n  int i, j;\n\n  shift[0] = 0;\n  for (i = 0; i < 16; i += 2) {\n    j = (shift[i>>1] << 1);\n    if (j & (1 << 16)) j ^= h->prim_poly;\n    shift[i] = j;\n    shift[i^1] = j^val;\n  }\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w16_group_4_4_multiply(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  uint16_t p, l, ind, r, a16;\n\n  struct gf_w16_group_4_4_data *d44;\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n\n  d44 = (struct gf_w16_group_4_4_data *) h->private;\n  gf_w16_group_4_set_shift_tables(d44->shift, b, h);\n\n  a16 = a;\n  ind = a16 >> 12;\n  a16 <<= 4;\n  p = d44->shift[ind];\n  r = p & 0xfff;\n  l = p >> 12;\n  ind = a16 >> 12;\n  a16 <<= 4;\n  p = (d44->shift[ind] ^ d44->reduce[l] ^ (r << 4));\n  r = p & 0xfff;\n  l = p >> 12;\n  ind = a16 >> 12;\n  a16 <<= 4;\n  p = (d44->shift[ind] ^ d44->reduce[l] ^ (r << 4));\n  r = p & 0xfff;\n  l = p >> 12;\n  ind = a16 >> 12;\n  p = (d44->shift[ind] ^ d44->reduce[l] ^ (r << 4));\n  return p;\n}\n\nstatic\nvoid gf_w16_group_4_4_region_multiply(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint16_t p, l, ind, r, a16, p16;\n  struct gf_w16_group_4_4_data *d44;\n  gf_region_data rd;\n  uint16_t *s16, *d16, *top;\n  \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  d44 = (struct gf_w16_group_4_4_data *) h->private;\n  gf_w16_group_4_set_shift_tables(d44->shift, val, h);\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 2);\n  gf_do_initial_region_alignment(&rd);\n\n  s16 = (uint16_t *) rd.s_start;\n  d16 = (uint16_t *) rd.d_start;\n  top = (uint16_t *) rd.d_top;\n\n  while (d16 < top) {\n    a16 = *s16;\n    p16 = (xor) ? *d16 : 0;\n    ind = a16 >> 12;\n    a16 <<= 4;\n    p = d44->shift[ind];\n    r = p & 0xfff;\n    l = p >> 12;\n    ind = a16 >> 12;\n    a16 <<= 4;\n    p = (d44->shift[ind] ^ d44->reduce[l] ^ (r << 4));\n    r = p & 0xfff;\n    l = p >> 12;\n    ind = a16 >> 12;\n    a16 <<= 4;\n    p = (d44->shift[ind] ^ d44->reduce[l] ^ (r << 4));\n    r = p & 0xfff;\n    l = p >> 12;\n    ind = a16 >> 12;\n    p = (d44->shift[ind] ^ d44->reduce[l] ^ (r << 4));\n    p ^= p16;\n    *d16 = p;\n    d16++;\n    s16++;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\nint gf_w16_group_init(gf_t *gf)\n{\n  int i, j, p;\n  struct gf_w16_group_4_4_data *d44;\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n\n  d44 = (struct gf_w16_group_4_4_data *) h->private;\n  d44->reduce[0] = 0;\n  for (i = 0; i < 16; i++) {\n    p = 0;\n    for (j = 0; j < 4; j++) {\n      if (i & (1 << j)) p ^= (h->prim_poly << j);\n    }\n    d44->reduce[p>>16] = (p&0xffff);\n  }\n\n  gf->multiply.w32 = gf_w16_group_4_4_multiply;\n  gf->divide.w32 = NULL;\n  gf->inverse.w32 = NULL;\n  gf->multiply_region.w32 = gf_w16_group_4_4_region_multiply;\n\n  return 1;\n}\n\nint gf_w16_scratch_size(int mult_type, int region_type, int divide_type, int arg1, int arg2)\n{\n  switch(mult_type)\n  {\n    case GF_MULT_TABLE:\n      return sizeof(gf_internal_t) + sizeof(struct gf_w16_lazytable_data) + 64;\n      break;\n    case GF_MULT_BYTWO_p:\n    case GF_MULT_BYTWO_b:\n      return sizeof(gf_internal_t) + sizeof(struct gf_w16_bytwo_data);\n      break;\n    case GF_MULT_LOG_ZERO:\n      return sizeof(gf_internal_t) + sizeof(struct gf_w16_zero_logtable_data) + 64;\n      break;\n    case GF_MULT_LOG_TABLE:\n      return sizeof(gf_internal_t) + sizeof(struct gf_w16_logtable_data) + 64;\n      break;\n    case GF_MULT_DEFAULT:\n    case GF_MULT_SPLIT_TABLE: \n      if (arg1 == 8 && arg2 == 8) {\n        return sizeof(gf_internal_t) + sizeof(struct gf_w16_split_8_8_data) + 64;\n      } else if ((arg1 == 8 && arg2 == 16) || (arg2 == 8 && arg1 == 16)) {\n        return sizeof(gf_internal_t) + sizeof(struct gf_w16_logtable_data) + 64;\n      } else if (mult_type == GF_MULT_DEFAULT || \n                 (arg1 == 4 && arg2 == 16) || (arg2 == 4 && arg1 == 16)) {\n        return sizeof(gf_internal_t) + sizeof(struct gf_w16_logtable_data) + 64;\n      }\n      return 0;\n      break;\n    case GF_MULT_GROUP:     \n      return sizeof(gf_internal_t) + sizeof(struct gf_w16_group_4_4_data) + 64;\n      break;\n    case GF_MULT_CARRY_FREE:\n      return sizeof(gf_internal_t);\n      break;\n    case GF_MULT_SHIFT:\n      return sizeof(gf_internal_t);\n      break;\n    case GF_MULT_COMPOSITE:\n      return sizeof(gf_internal_t) + sizeof(struct gf_w16_composite_data) + 64;\n      break;\n\n    default:\n      return 0;\n   }\n   return 0;\n}\n\nint gf_w16_init(gf_t *gf)\n{\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n\n  /* Allen: set default primitive polynomial / irreducible polynomial if needed */\n\n  if (h->prim_poly == 0) {\n    if (h->mult_type == GF_MULT_COMPOSITE) {\n      h->prim_poly = gf_composite_get_default_poly(h->base_gf);\n      if (h->prim_poly == 0) return 0;\n    } else { \n\n     /* Allen: use the following primitive polynomial to make \n               carryless multiply work more efficiently for GF(2^16).\n\n        h->prim_poly = 0x1002d;\n\n        The following is the traditional primitive polynomial for GF(2^16) */\n\n      h->prim_poly = 0x1100b;\n    } \n  }\n\n  if (h->mult_type != GF_MULT_COMPOSITE) h->prim_poly |= (1 << 16);\n\n  gf->multiply.w32 = NULL;\n  gf->divide.w32 = NULL;\n  gf->inverse.w32 = NULL;\n  gf->multiply_region.w32 = NULL;\n\n  switch(h->mult_type) {\n    case GF_MULT_LOG_ZERO:    if (gf_w16_log_zero_init(gf) == 0) return 0; break;\n    case GF_MULT_LOG_TABLE:   if (gf_w16_log_init(gf) == 0) return 0; break;\n    case GF_MULT_DEFAULT: \n    case GF_MULT_SPLIT_TABLE: if (gf_w16_split_init(gf) == 0) return 0; break;\n    case GF_MULT_TABLE:       if (gf_w16_table_init(gf) == 0) return 0; break;\n    case GF_MULT_CARRY_FREE:  if (gf_w16_cfm_init(gf) == 0) return 0; break;\n    case GF_MULT_SHIFT:       if (gf_w16_shift_init(gf) == 0) return 0; break;\n    case GF_MULT_COMPOSITE:   if (gf_w16_composite_init(gf) == 0) return 0; break;\n    case GF_MULT_BYTWO_p: \n    case GF_MULT_BYTWO_b:     if (gf_w16_bytwo_init(gf) == 0) return 0; break;\n    case GF_MULT_GROUP:       if (gf_w16_group_init(gf) == 0) return 0; break;\n    default: return 0;\n  }\n  if (h->divide_type == GF_DIVIDE_EUCLID) {\n    gf->divide.w32 = gf_w16_divide_from_inverse;\n    gf->inverse.w32 = gf_w16_euclid;\n  } else if (h->divide_type == GF_DIVIDE_MATRIX) {\n    gf->divide.w32 = gf_w16_divide_from_inverse;\n    gf->inverse.w32 = gf_w16_matrix;\n  }\n\n  if (gf->divide.w32 == NULL) {\n    gf->divide.w32 = gf_w16_divide_from_inverse;\n    if (gf->inverse.w32 == NULL) gf->inverse.w32 = gf_w16_euclid;\n  }\n\n  if (gf->inverse.w32 == NULL)  gf->inverse.w32 = gf_w16_inverse_from_divide;\n\n  if (h->region_type & GF_REGION_ALTMAP) {\n    if (h->mult_type == GF_MULT_COMPOSITE) {\n      gf->extract_word.w32 = gf_w16_composite_extract_word;\n    } else {\n      gf->extract_word.w32 = gf_w16_split_extract_word;\n    }\n  } else if (h->region_type == GF_REGION_CAUCHY) {\n    gf->multiply_region.w32 = gf_wgen_cauchy_region;\n    gf->extract_word.w32 = gf_wgen_extract_word;\n  } else {\n    gf->extract_word.w32 = gf_w16_extract_word;\n  }\n  if (gf->multiply_region.w32 == NULL) {\n    gf->multiply_region.w32 = gf_w16_multiply_region_from_single;\n  }\n  return 1;\n}\n\n/* Inline setup functions */\n\nuint16_t *gf_w16_get_log_table(gf_t *gf)\n{\n  struct gf_w16_logtable_data *ltd;\n\n  if (gf->multiply.w32 == gf_w16_log_multiply) {\n    ltd = (struct gf_w16_logtable_data *) ((gf_internal_t *) gf->scratch)->private;\n    return (uint16_t *) ltd->log_tbl;\n  }\n  return NULL;\n}\n\nuint16_t *gf_w16_get_mult_alog_table(gf_t *gf)\n{\n  gf_internal_t *h;\n  struct gf_w16_logtable_data *ltd;\n\n  h = (gf_internal_t *) gf->scratch;\n  if (gf->multiply.w32 == gf_w16_log_multiply) {\n    ltd = (struct gf_w16_logtable_data *) h->private;\n    return (uint16_t *) ltd->antilog_tbl;\n  }\n  return NULL;\n}\n\nuint16_t *gf_w16_get_div_alog_table(gf_t *gf)\n{\n  gf_internal_t *h;\n  struct gf_w16_logtable_data *ltd;\n\n  h = (gf_internal_t *) gf->scratch;\n  if (gf->multiply.w32 == gf_w16_log_multiply) {\n    ltd = (struct gf_w16_logtable_data *) h->private;\n    return (uint16_t *) ltd->d_antilog;\n  }\n  return NULL;\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/src/gf_w32.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_w32.c\n *\n * Routines for 32-bit Galois fields\n */\n\n\n#include \"gf_int.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include \"gf_w32.h\"\n\n#define MM_PRINT32(s, r) { uint8_t blah[16], ii; printf(\"%-12s\", s); _mm_storeu_si128((__m128i *)blah, r); for (ii = 0; ii < 16; ii += 4) printf(\" %02x%02x%02x%02x\", blah[15-ii], blah[14-ii], blah[13-ii], blah[12-ii]); printf(\"\\n\"); }\n\n#define MM_PRINT8(s, r) { uint8_t blah[16], ii; printf(\"%-12s\", s); _mm_storeu_si128((__m128i *)blah, r); for (ii = 0; ii < 16; ii += 1) printf(\"%s%02x\", (ii%4==0) ? \"   \" : \" \", blah[15-ii]); printf(\"\\n\"); }\n\n#define AB2(ip, am1 ,am2, b, t1, t2) {\\\n  t1 = (b << 1) & am1;\\\n  t2 = b & am2; \\\n  t2 = ((t2 << 1) - (t2 >> (GF_FIELD_WIDTH-1))); \\\n  b = (t1 ^ (t2 & ip));}\n\n#define SSE_AB2(pp, m1 ,m2, va, t1, t2) {\\\n          t1 = _mm_and_si128(_mm_slli_epi64(va, 1), m1); \\\n          t2 = _mm_and_si128(va, m2); \\\n          t2 = _mm_sub_epi64 (_mm_slli_epi64(t2, 1), _mm_srli_epi64(t2, (GF_FIELD_WIDTH-1))); \\\n          va = _mm_xor_si128(t1, _mm_and_si128(t2, pp)); }\n\nstatic\ninline\nuint32_t gf_w32_inverse_from_divide (gf_t *gf, uint32_t a)\n{\n  return gf->divide.w32(gf, 1, a);\n}\n\nstatic\ninline\nuint32_t gf_w32_divide_from_inverse (gf_t *gf, uint32_t a, uint32_t b)\n{\n  b = gf->inverse.w32(gf, b);\n  return gf->multiply.w32(gf, a, b);\n}\n\nstatic\nvoid\ngf_w32_multiply_region_from_single(gf_t *gf, void *src, void *dest, uint32_t val, int bytes, int \nxor)\n{\n  uint32_t i;\n  uint32_t *s32;\n  uint32_t *d32;\n   \n  s32 = (uint32_t *) src;\n  d32 = (uint32_t *) dest; \n \n  if (xor) {\n    for (i = 0; i < bytes/sizeof(uint32_t); i++) {\n      d32[i] ^= gf->multiply.w32(gf, val, s32[i]);\n    } \n  } else {\n    for (i = 0; i < bytes/sizeof(uint32_t); i++) {\n      d32[i] = gf->multiply.w32(gf, val, s32[i]);\n    } \n  }\n}\n\n#if defined(INTEL_SSE4_PCLMUL)\n\nstatic \nvoid\ngf_w32_clm_multiply_region_from_single_2(gf_t *gf, void *src, void *dest, uint32_t val, int bytes, int xor)\n{\n\n  uint32_t i;\n  uint32_t *s32;\n  uint32_t *d32;\n  \n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t * h = gf->scratch;\n  \n  prim_poly = _mm_set_epi32(0, 0, 1, (uint32_t)(h->prim_poly & 0xffffffffULL));\n   \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  a = _mm_insert_epi32 (_mm_setzero_si128(), val, 0);\n  s32 = (uint32_t *) src;\n  d32 = (uint32_t *) dest; \n \n  if (xor) {\n    for (i = 0; i < bytes/sizeof(uint32_t); i++) {\n      b = _mm_insert_epi32 (a, s32[i], 0);\n      result = _mm_clmulepi64_si128 (a, b, 0);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      d32[i] ^= ((gf_val_32_t)_mm_extract_epi32(result, 0));\n    } \n  } else {\n    for (i = 0; i < bytes/sizeof(uint32_t); i++) {\n      b = _mm_insert_epi32 (a, s32[i], 0);\n      result = _mm_clmulepi64_si128 (a, b, 0);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      d32[i] = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n    } \n  }\n}\n#endif\n\n#if defined(INTEL_SSE4_PCLMUL) \n\nstatic \nvoid\ngf_w32_clm_multiply_region_from_single_3(gf_t *gf, void *src, void *dest, uint32_t val, int bytes, int xor)\n{\n\n  uint32_t i;\n  uint32_t *s32;\n  uint32_t *d32;\n  \n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t * h = gf->scratch;\n  \n  prim_poly = _mm_set_epi32(0, 0, 1, (uint32_t)(h->prim_poly & 0xffffffffULL));\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n  \n  a = _mm_insert_epi32 (_mm_setzero_si128(), val, 0);\n  \n  s32 = (uint32_t *) src;\n  d32 = (uint32_t *) dest; \n \n  if (xor) {\n    for (i = 0; i < bytes/sizeof(uint32_t); i++) {\n      b = _mm_insert_epi32 (a, s32[i], 0);\n      result = _mm_clmulepi64_si128 (a, b, 0);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      d32[i] ^= ((gf_val_32_t)_mm_extract_epi32(result, 0));\n    } \n  } else {\n    for (i = 0; i < bytes/sizeof(uint32_t); i++) {\n      b = _mm_insert_epi32 (a, s32[i], 0);\n      result = _mm_clmulepi64_si128 (a, b, 0);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      d32[i] = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n    } \n  }\n}\n#endif\n\n#if defined(INTEL_SSE4_PCLMUL)\nstatic \nvoid\ngf_w32_clm_multiply_region_from_single_4(gf_t *gf, void *src, void *dest, uint32_t val, int bytes, int xor)\n{\n  uint32_t i;\n  uint32_t *s32;\n  uint32_t *d32;\n  \n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t * h = gf->scratch;\n  \n  prim_poly = _mm_set_epi32(0, 0, 1, (uint32_t)(h->prim_poly & 0xffffffffULL));\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n  \n  a = _mm_insert_epi32 (_mm_setzero_si128(), val, 0);\n  \n  s32 = (uint32_t *) src;\n  d32 = (uint32_t *) dest; \n \n  if (xor) {\n    for (i = 0; i < bytes/sizeof(uint32_t); i++) {\n      b = _mm_insert_epi32 (a, s32[i], 0);\n      result = _mm_clmulepi64_si128 (a, b, 0);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      d32[i] ^= ((gf_val_32_t)_mm_extract_epi32(result, 0));\n    } \n  } else {\n    for (i = 0; i < bytes/sizeof(uint32_t); i++) {\n      b = _mm_insert_epi32 (a, s32[i], 0);\n      result = _mm_clmulepi64_si128 (a, b, 0);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      d32[i] = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n    } \n  }\n}\n#endif\n\nstatic\ninline\nuint32_t gf_w32_euclid (gf_t *gf, uint32_t b)\n{\n  uint32_t e_i, e_im1, e_ip1;\n  uint32_t d_i, d_im1, d_ip1;\n  uint32_t y_i, y_im1, y_ip1;\n  uint32_t c_i;\n\n  if (b == 0) return -1;\n  e_im1 = ((gf_internal_t *) (gf->scratch))->prim_poly; \n  e_i = b;\n  d_im1 = 32;\n  for (d_i = d_im1-1; ((1 << d_i) & e_i) == 0; d_i--) ;\n  y_i = 1;\n  y_im1 = 0;\n\n  while (e_i != 1) {\n\n    e_ip1 = e_im1;\n    d_ip1 = d_im1;\n    c_i = 0;\n\n    while (d_ip1 >= d_i) {\n      c_i ^= (1 << (d_ip1 - d_i));\n      e_ip1 ^= (e_i << (d_ip1 - d_i));\n      d_ip1--;\n      if (e_ip1 == 0) return 0;\n      while ((e_ip1 & (1 << d_ip1)) == 0) d_ip1--;\n    }\n\n    y_ip1 = y_im1 ^ gf->multiply.w32(gf, c_i, y_i);\n    y_im1 = y_i;\n    y_i = y_ip1;\n\n    e_im1 = e_i;\n    d_im1 = d_i;\n    e_i = e_ip1;\n    d_i = d_ip1;\n  }\n\n  return y_i;\n}\n\nstatic\ngf_val_32_t gf_w32_extract_word(gf_t *gf, void *start, int bytes, int index)\n{\n  uint32_t *r32, rv;\n\n  r32 = (uint32_t *) start;\n  rv = r32[index];\n  return rv;\n}\n\nstatic\ngf_val_32_t gf_w32_composite_extract_word(gf_t *gf, void *start, int bytes, int index)\n{\n  int sub_size;\n  gf_internal_t *h;\n  uint8_t *r8, *top;\n  uint32_t a, b, *r32;\n  gf_region_data rd;\n\n  h = (gf_internal_t *) gf->scratch;\n  gf_set_region_data(&rd, gf, start, start, bytes, 0, 0, 32);\n  r32 = (uint32_t *) start;\n  if (r32 + index < (uint32_t *) rd.d_start) return r32[index];\n  if (r32 + index >= (uint32_t *) rd.d_top) return r32[index];\n  index -= (((uint32_t *) rd.d_start) - r32);\n  r8 = (uint8_t *) rd.d_start;\n  top = (uint8_t *) rd.d_top;\n  sub_size = (top-r8)/2;\n\n  a = h->base_gf->extract_word.w32(h->base_gf, r8, sub_size, index);\n  b = h->base_gf->extract_word.w32(h->base_gf, r8+sub_size, sub_size, index);\n  return (a | (b << 16));\n}\n\nstatic\ngf_val_32_t gf_w32_split_extract_word(gf_t *gf, void *start, int bytes, int index)\n{\n  int i;\n  uint32_t *r32, rv;\n  uint8_t *r8;\n  gf_region_data rd;\n\n  gf_set_region_data(&rd, gf, start, start, bytes, 0, 0, 64);\n  r32 = (uint32_t *) start;\n  if (r32 + index < (uint32_t *) rd.d_start) return r32[index];\n  if (r32 + index >= (uint32_t *) rd.d_top) return r32[index];\n  index -= (((uint32_t *) rd.d_start) - r32);\n  r8 = (uint8_t *) rd.d_start;\n  r8 += ((index & 0xfffffff0)*4);\n  r8 += (index & 0xf);\n  r8 += 48;\n  rv =0;\n  for (i = 0; i < 4; i++) {\n    rv <<= 8;\n    rv |= *r8;\n    r8 -= 16;\n  }\n  return rv;\n}\n\n\nstatic\ninline\nuint32_t gf_w32_matrix (gf_t *gf, uint32_t b)\n{\n  return gf_bitmatrix_inverse(b, 32, ((gf_internal_t *) (gf->scratch))->prim_poly);\n}\n\n/* JSP: GF_MULT_SHIFT: The world's dumbest multiplication algorithm.  I only\n   include it for completeness.  It does have the feature that it requires no\n   extra memory.  \n*/\n\nstatic\ninline\ngf_val_32_t\ngf_w32_cfmgk_multiply (gf_t *gf, gf_val_32_t a32, gf_val_32_t b32)\n{\n  gf_val_32_t rv = 0;\n\n#if defined(INTEL_SSE4_PCLMUL)\n\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         w;\n  __m128i         g, q;\n  gf_internal_t * h = gf->scratch;\n  uint64_t        g_star, q_plus;\n\n  q_plus = *(uint64_t *) h->private;\n  g_star = *((uint64_t *) h->private + 1);\n\n  a = _mm_insert_epi32 (_mm_setzero_si128(), a32, 0);\n  b = _mm_insert_epi32 (a, b32, 0);\n  g = _mm_insert_epi64 (a, g_star, 0);\n  q = _mm_insert_epi64 (a, q_plus, 0);\n  \n  result = _mm_clmulepi64_si128 (a, b, 0);\n  w = _mm_clmulepi64_si128 (q, _mm_srli_si128 (result, 4), 0);\n  w = _mm_clmulepi64_si128 (g, _mm_srli_si128 (w, 4), 0);\n  result = _mm_xor_si128 (result, w);\n\n  /* Extracts 32 bit value from result. */\n  rv = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n#endif\n  return rv;\n}\n\n#if defined(INTEL_SSE4_PCLMUL)\n\nstatic \nvoid\ngf_w32_cfmgk_multiply_region_from_single(gf_t *gf, void *src, void *dest, uint32_t val, int bytes, int xor)\n{\n\n  uint32_t i;\n  uint32_t *s32;\n  uint32_t *d32;\n  \n  __m128i         a, b;\n  __m128i         result;\n  __m128i         w;\n  __m128i         g, q;\n  gf_internal_t * h = gf->scratch;\n  uint64_t        g_star, q_plus;\n  \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  q_plus = *(uint64_t *) h->private;\n  g_star = *((uint64_t *) h->private + 1);\n\n  g = _mm_insert_epi64 (a, g_star, 0);\n  q = _mm_insert_epi64 (a, q_plus, 0);\n  a = _mm_insert_epi32 (_mm_setzero_si128(), val, 0);\n  s32 = (uint32_t *) src;\n  d32 = (uint32_t *) dest; \n \n  if (xor) {\n    for (i = 0; i < bytes/sizeof(uint32_t); i++) {\n      b = _mm_insert_epi32 (a, s32[i], 0);\n      result = _mm_clmulepi64_si128 (a, b, 0);\n      w = _mm_clmulepi64_si128 (q, _mm_srli_si128 (result, 4), 0);\n      w = _mm_clmulepi64_si128 (g, _mm_srli_si128 (w, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      d32[i] ^= ((gf_val_32_t)_mm_extract_epi32(result, 0));\n    } \n  } else {\n    for (i = 0; i < bytes/sizeof(uint32_t); i++) {\n      b = _mm_insert_epi32 (a, s32[i], 0);\n      result = _mm_clmulepi64_si128 (a, b, 0);\n      w = _mm_clmulepi64_si128 (q, _mm_srli_si128 (result, 4), 0);\n      w = _mm_clmulepi64_si128 (g, _mm_srli_si128 (w, 4), 0);\n      result = _mm_xor_si128 (result, w);\n      d32[i] = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n    } \n  }\n}\n#endif\n\n\nstatic\ninline\ngf_val_32_t\ngf_w32_clm_multiply_2 (gf_t *gf, gf_val_32_t a32, gf_val_32_t b32)\n{\n  gf_val_32_t rv = 0;\n\n#if defined(INTEL_SSE4_PCLMUL)\n\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t * h = gf->scratch;\n\n\n  a = _mm_insert_epi32 (_mm_setzero_si128(), a32, 0);\n  b = _mm_insert_epi32 (a, b32, 0);\n  \n  prim_poly = _mm_set_epi32(0, 0, 1, (uint32_t)(h->prim_poly & 0xffffffffULL));\n  \n  /* Do the initial multiply */\n\n  result = _mm_clmulepi64_si128 (a, b, 0);\n\n  /* Ben: Do prim_poly reduction twice. We are guaranteed that we will only\n     have to do the reduction at most twice, because (w-2)/z == 2. Where\n     z is equal to the number of zeros after the leading 1 \n\n   _mm_clmulepi64_si128 is the carryless multiply operation. Here\n   _mm_srli_si128 shifts the result to the right by 4 bytes. This allows\n   us to multiply the prim_poly by the leading bits of the result. We\n   then xor the result of that operation back with the result.*/\n\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n  result = _mm_xor_si128 (result, w);\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n  result = _mm_xor_si128 (result, w);\n\n  /* Extracts 32 bit value from result. */\n  rv = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n#endif\n  return rv;\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w32_clm_multiply_3 (gf_t *gf, gf_val_32_t a32, gf_val_32_t b32)\n{\n  gf_val_32_t rv = 0;\n\n#if defined(INTEL_SSE4_PCLMUL)\n\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t * h = gf->scratch;\n\n\n  a = _mm_insert_epi32 (_mm_setzero_si128(), a32, 0);\n  b = _mm_insert_epi32 (a, b32, 0);\n\n  prim_poly = _mm_set_epi32(0, 0, 1, (uint32_t)(h->prim_poly & 0xffffffffULL));\n\n  /* Do the initial multiply */\n  \n  result = _mm_clmulepi64_si128 (a, b, 0);\n\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n  result = _mm_xor_si128 (result, w);\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n  result = _mm_xor_si128 (result, w);\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n  result = _mm_xor_si128 (result, w);\n\n  /* Extracts 32 bit value from result. */\n  \n  rv = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n#endif\n  return rv;\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w32_clm_multiply_4 (gf_t *gf, gf_val_32_t a32, gf_val_32_t b32)\n{\n  gf_val_32_t rv = 0;\n\n#if defined(INTEL_SSE4_PCLMUL)\n\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t * h = gf->scratch;\n\n\n  a = _mm_insert_epi32 (_mm_setzero_si128(), a32, 0);\n  b = _mm_insert_epi32 (a, b32, 0);\n\n  prim_poly = _mm_set_epi32(0, 0, 1, (uint32_t)(h->prim_poly & 0xffffffffULL));\n\n  /* Do the initial multiply */\n  \n  result = _mm_clmulepi64_si128 (a, b, 0);\n\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n  result = _mm_xor_si128 (result, w);\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n  result = _mm_xor_si128 (result, w);\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n  result = _mm_xor_si128 (result, w);\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_si128 (result, 4), 0);\n  result = _mm_xor_si128 (result, w);\n\n  /* Extracts 32 bit value from result. */\n  \n  rv = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n#endif\n  return rv;\n}\n\n\nstatic\ninline\nuint32_t\ngf_w32_shift_multiply (gf_t *gf, uint32_t a32, uint32_t b32)\n{\n  uint64_t product, i, pp, a, b, one;\n  gf_internal_t *h;\n\n  a = a32;\n  b = b32;\n  h = (gf_internal_t *) gf->scratch;\n  one = 1;\n  pp = h->prim_poly | (one << 32);\n\n  product = 0;\n\n  for (i = 0; i < GF_FIELD_WIDTH; i++) { \n    if (a & (one << i)) product ^= (b << i);\n  }\n  for (i = (GF_FIELD_WIDTH*2-2); i >= GF_FIELD_WIDTH; i--) {\n    if (product & (one << i)) product ^= (pp << (i-GF_FIELD_WIDTH)); \n  }\n  return product;\n}\n\n  static \nint gf_w32_cfmgk_init(gf_t *gf)\n{\n  gf->inverse.w32 = gf_w32_euclid;\n  gf->multiply_region.w32 = gf_w32_multiply_region_from_single;\n  \n#if defined(INTEL_SSE4_PCLMUL)\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  gf->multiply.w32 = gf_w32_cfmgk_multiply;\n  gf->multiply_region.w32 = gf_w32_cfmgk_multiply_region_from_single;\n\n  uint64_t *q_plus = (uint64_t *) h->private;\n  uint64_t *g_star = (uint64_t *) h->private + 1;\n\n  uint64_t tmp = h->prim_poly << 32;\n  *q_plus = 1ULL << 32;\n\n  int i;\n  for(i = 63; i >= 32; i--)\n    if((1ULL << i) & tmp)\n    {\n      *q_plus |= 1ULL << (i-32);\n      tmp ^= h->prim_poly << (i-32);\n    }\n\n  *g_star = h->prim_poly & ((1ULL << 32) - 1);\n\n  return 1;\n#endif\n\n  return 0;\n}\n\n  static \nint gf_w32_cfm_init(gf_t *gf)\n{\n  gf->inverse.w32 = gf_w32_euclid;\n  gf->multiply_region.w32 = gf_w32_multiply_region_from_single;\n  \n  /*Ben: We also check to see if the prim poly will work for pclmul */\n  /*Ben: Check to see how many reduction steps it will take*/\n\n#if defined(INTEL_SSE4_PCLMUL)\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n\n  if ((0xfffe0000 & h->prim_poly) == 0){ \n    gf->multiply.w32 = gf_w32_clm_multiply_2;\n    gf->multiply_region.w32 = gf_w32_clm_multiply_region_from_single_2;\n  }else if ((0xffc00000 & h->prim_poly) == 0){\n    gf->multiply.w32 = gf_w32_clm_multiply_3;\n    gf->multiply_region.w32 = gf_w32_clm_multiply_region_from_single_3;\n  }else if ((0xfe000000 & h->prim_poly) == 0){\n    gf->multiply.w32 = gf_w32_clm_multiply_4;\n    gf->multiply_region.w32 = gf_w32_clm_multiply_region_from_single_4;\n  } else {\n    return 0;\n  }\n  return 1;\n  #endif\n\n  return 0;\n}\n\n  static \nint gf_w32_shift_init(gf_t *gf)\n{\n  gf->inverse.w32 = gf_w32_euclid;\n  gf->multiply_region.w32 = gf_w32_multiply_region_from_single;\n  gf->multiply.w32 = gf_w32_shift_multiply;\n  return 1;\n}\n\nstatic\n  void\ngf_w32_group_set_shift_tables(uint32_t *shift, uint32_t val, gf_internal_t *h)\n{\n  uint32_t i;\n  uint32_t j;\n\n  shift[0] = 0;\n\n  for (i = 1; i < ((uint32_t)1 << h->arg1); i <<= 1) {\n    for (j = 0; j < i; j++) shift[i|j] = shift[j]^val;\n    if (val & GF_FIRST_BIT) {\n      val <<= 1;\n      val ^= h->prim_poly;\n    } else {\n      val <<= 1;\n    }\n  }\n}\n\n  static\nvoid gf_w32_group_s_equals_r_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  int leftover, rs;\n  uint32_t p, l, ind, a32;\n  int bits_left;\n  int g_s;\n  gf_region_data rd;\n  uint32_t *s32, *d32, *top;\n  struct gf_w32_group_data *gd;\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gd = (struct gf_w32_group_data *) h->private;\n  g_s = h->arg1;\n  gf_w32_group_set_shift_tables(gd->shift, val, h);\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 4);\n  gf_do_initial_region_alignment(&rd);\n\n  s32 = (uint32_t *) rd.s_start;\n  d32 = (uint32_t *) rd.d_start;\n  top = (uint32_t *) rd.d_top;\n\n  leftover = 32 % g_s;\n  if (leftover == 0) leftover = g_s;\n\n  while (d32 < top) {\n    rs = 32 - leftover;\n    a32 = *s32;\n    ind = a32 >> rs;\n    a32 <<= leftover;\n    p = gd->shift[ind];\n\n    bits_left = rs;\n    rs = 32 - g_s;\n\n    while (bits_left > 0) {\n      bits_left -= g_s;\n      ind = a32 >> rs;\n      a32 <<= g_s;\n      l = p >> rs;\n      p = (gd->shift[ind] ^ gd->reduce[l] ^ (p << g_s));\n    }\n    if (xor) p ^= *d32;\n    *d32 = p;\n    d32++;\n    s32++;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\n  static\nvoid gf_w32_group_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint32_t *s32, *d32, *top;\n  int i;\n  int leftover;\n  uint64_t p, l, r;\n  uint32_t a32, ind;\n  int g_s, g_r;\n  struct gf_w32_group_data *gd;\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  g_s = h->arg1;\n  g_r = h->arg2;\n  gd = (struct gf_w32_group_data *) h->private;\n  gf_w32_group_set_shift_tables(gd->shift, val, h);\n\n  leftover = GF_FIELD_WIDTH % g_s;\n  if (leftover == 0) leftover = g_s;\n\n  gd = (struct gf_w32_group_data *) h->private;\n  gf_w32_group_set_shift_tables(gd->shift, val, h);\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 4);\n  gf_do_initial_region_alignment(&rd);\n\n  s32 = (uint32_t *) rd.s_start;\n  d32 = (uint32_t *) rd.d_start;\n  top = (uint32_t *) rd.d_top;\n\n  while (d32 < top) {\n    a32 = *s32;\n    ind = a32 >> (GF_FIELD_WIDTH - leftover);\n    p = gd->shift[ind];\n    p <<= g_s;\n    a32 <<= leftover;\n  \n    i = (GF_FIELD_WIDTH - leftover);\n    while (i > g_s) {\n      ind = a32 >> (GF_FIELD_WIDTH-g_s);\n      p ^= gd->shift[ind];\n      a32 <<= g_s;\n      p <<= g_s;\n      i -= g_s;\n    }\n  \n    ind = a32 >> (GF_FIELD_WIDTH-g_s);\n    p ^= gd->shift[ind];\n  \n    for (i = gd->tshift ; i >= 0; i -= g_r) {\n      l = p & (gd->rmask << i);\n      r = gd->reduce[l >> (i+32)];\n      r <<= (i);\n      p ^= r;\n    }\n\n    if (xor) p ^= *d32;\n    *d32 = p;\n    d32++;\n    s32++;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w32_group_s_equals_r_multiply(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  int leftover, rs;\n  uint32_t p, l, ind, a32;\n  int bits_left;\n  int g_s;\n\n  struct gf_w32_group_data *gd;\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  g_s = h->arg1;\n\n  gd = (struct gf_w32_group_data *) h->private;\n  gf_w32_group_set_shift_tables(gd->shift, b, h);\n\n  leftover = 32 % g_s;\n  if (leftover == 0) leftover = g_s;\n\n  rs = 32 - leftover;\n  a32 = a;\n  ind = a32 >> rs;\n  a32 <<= leftover;\n  p = gd->shift[ind];\n\n  bits_left = rs;\n  rs = 32 - g_s;\n\n  while (bits_left > 0) {\n    bits_left -= g_s;\n    ind = a32 >> rs;\n    a32 <<= g_s;\n    l = p >> rs;\n    p = (gd->shift[ind] ^ gd->reduce[l] ^ (p << g_s));\n  }\n  return p;\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w32_group_4_4_multiply(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  uint32_t p, l, ind, a32;\n\n  struct gf_w32_group_data *d44;\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n\n  d44 = (struct gf_w32_group_data *) h->private;\n  gf_w32_group_set_shift_tables(d44->shift, b, h);\n\n  a32 = a;\n  ind = a32 >> 28;\n  a32 <<= 4;\n  p = d44->shift[ind];\n  ind = a32 >> 28;\n  a32 <<= 4;\n  l = p >> 28;\n  p = (d44->shift[ind] ^ d44->reduce[l] ^ (p << 4));\n  ind = a32 >> 28;\n  a32 <<= 4;\n  l = p >> 28;\n  p = (d44->shift[ind] ^ d44->reduce[l] ^ (p << 4));\n  ind = a32 >> 28;\n  a32 <<= 4;\n  l = p >> 28;\n  p = (d44->shift[ind] ^ d44->reduce[l] ^ (p << 4));\n  ind = a32 >> 28;\n  a32 <<= 4;\n  l = p >> 28;\n  p = (d44->shift[ind] ^ d44->reduce[l] ^ (p << 4));\n  ind = a32 >> 28;\n  a32 <<= 4;\n  l = p >> 28;\n  p = (d44->shift[ind] ^ d44->reduce[l] ^ (p << 4));\n  ind = a32 >> 28;\n  a32 <<= 4;\n  l = p >> 28;\n  p = (d44->shift[ind] ^ d44->reduce[l] ^ (p << 4));\n  ind = a32 >> 28;\n  l = p >> 28;\n  p = (d44->shift[ind] ^ d44->reduce[l] ^ (p << 4));\n  return p;\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w32_group_multiply(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  int i;\n  int leftover;\n  uint64_t p, l, r;\n  uint32_t a32, ind;\n  int g_s, g_r;\n  struct gf_w32_group_data *gd;\n\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  g_s = h->arg1;\n  g_r = h->arg2;\n  gd = (struct gf_w32_group_data *) h->private;\n  gf_w32_group_set_shift_tables(gd->shift, b, h);\n\n  leftover = GF_FIELD_WIDTH % g_s;\n  if (leftover == 0) leftover = g_s;\n\n  a32 = a;\n  ind = a32 >> (GF_FIELD_WIDTH - leftover);\n  p = gd->shift[ind];\n  p <<= g_s;\n  a32 <<= leftover;\n\n  i = (GF_FIELD_WIDTH - leftover);\n  while (i > g_s) {\n    ind = a32 >> (GF_FIELD_WIDTH-g_s);\n    p ^= gd->shift[ind];\n    a32 <<= g_s;\n    p <<= g_s;\n    i -= g_s;\n  }\n\n  ind = a32 >> (GF_FIELD_WIDTH-g_s);\n  p ^= gd->shift[ind];\n\n  for (i = gd->tshift ; i >= 0; i -= g_r) {\n    l = p & (gd->rmask << i);\n    r = gd->reduce[l >> (i+32)];\n    r <<= (i);\n    p ^= r;\n  }\n  return p;\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w32_bytwo_b_multiply (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  uint32_t prod, pp, bmask;\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  prod = 0;\n  bmask = 0x80000000;\n\n  while (1) {\n    if (a & 1) prod ^= b;\n    a >>= 1;\n    if (a == 0) return prod;\n    if (b & bmask) {\n      b = ((b << 1) ^ pp);\n    } else {\n      b <<= 1;\n    }\n  }\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w32_bytwo_p_multiply (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  uint32_t prod, pp, pmask, amask;\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n\n  prod = 0;\n  pmask = 0x80000000;\n  amask = 0x80000000;\n\n  while (amask != 0) {\n    if (prod & pmask) {\n      prod = ((prod << 1) ^ pp);\n    } else {\n      prod <<= 1;\n    }\n    if (a & amask) prod ^= b;\n    amask >>= 1;\n  }\n  return prod;\n}\n\nstatic\nvoid\ngf_w32_bytwo_p_nosse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint64_t *s64, *d64, t1, t2, ta, prod, amask;\n  gf_region_data rd;\n  struct gf_w32_bytwo_data *btd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  btd = (struct gf_w32_bytwo_data *) ((gf_internal_t *) (gf->scratch))->private;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 8);\n  gf_do_initial_region_alignment(&rd);\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n\n  if (xor) {\n    while (s64 < (uint64_t *) rd.s_top) {\n      prod = 0;\n      amask = 0x80000000;\n      ta = *s64;\n      while (amask != 0) {\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, prod, t1, t2);\n        if (val & amask) prod ^= ta;\n        amask >>= 1;\n      }\n      *d64 ^= prod;\n      d64++;\n      s64++;\n    }\n  } else {\n    while (s64 < (uint64_t *) rd.s_top) {\n      prod = 0;\n      amask = 0x80000000;\n      ta = *s64;\n      while (amask != 0) {\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, prod, t1, t2);\n        if (val & amask) prod ^= ta;\n        amask >>= 1;\n      }\n      *d64 = prod;\n      d64++;\n      s64++;\n    }\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\n#define BYTWO_P_ONESTEP {\\\n      SSE_AB2(pp, m1 ,m2, prod, t1, t2); \\\n      t1 = _mm_and_si128(v, one); \\\n      t1 = _mm_sub_epi32(t1, one); \\\n      t1 = _mm_and_si128(t1, ta); \\\n      prod = _mm_xor_si128(prod, t1); \\\n      v = _mm_srli_epi64(v, 1); }\n\n#ifdef INTEL_SSE2\nstatic\nvoid\ngf_w32_bytwo_p_sse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  int i;\n  uint8_t *s8, *d8;\n  uint32_t vrev;\n  __m128i pp, m1, m2, ta, prod, t1, t2, tp, one, v;\n  struct gf_w32_bytwo_data *btd;\n  gf_region_data rd;\n   \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  btd = (struct gf_w32_bytwo_data *) ((gf_internal_t *) (gf->scratch))->private;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n\n  vrev = 0;\n  for (i = 0; i < 32; i++) {\n    vrev <<= 1;\n    if (!(val & (1 << i))) vrev |= 1;\n  }\n\n  s8 = (uint8_t *) rd.s_start;\n  d8 = (uint8_t *) rd.d_start;\n\n  pp = _mm_set1_epi32(btd->prim_poly&0xffffffff);\n  m1 = _mm_set1_epi32((btd->mask1)&0xffffffff);\n  m2 = _mm_set1_epi32((btd->mask2)&0xffffffff);\n  one = _mm_set1_epi32(1);\n\n  while (d8 < (uint8_t *) rd.d_top) {\n    prod = _mm_setzero_si128();\n    v = _mm_set1_epi32(vrev);\n    ta = _mm_load_si128((__m128i *) s8);\n    tp = (!xor) ? _mm_setzero_si128() : _mm_load_si128((__m128i *) d8);\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    _mm_store_si128((__m128i *) d8, _mm_xor_si128(prod, tp));\n    d8 += 16;\n    s8 += 16;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\nstatic\nvoid\ngf_w32_bytwo_b_nosse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint64_t *s64, *d64, t1, t2, ta, tb, prod;\n  struct gf_w32_bytwo_data *btd;\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 32);\n  gf_do_initial_region_alignment(&rd);\n\n  btd = (struct gf_w32_bytwo_data *) ((gf_internal_t *) (gf->scratch))->private;\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n\n  switch (val) {\n  case 2:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= ta;\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = ta;\n        d64++;\n        s64++;\n      }\n    }\n    break;\n  case 3:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    }\n    break;\n  case 4:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= ta;\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = ta;\n        d64++;\n        s64++;\n      }\n    }\n    break;\n  case 5:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = ta ^ prod;\n        d64++;\n        s64++;\n      }\n    }\n    break;\n  default:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        prod = *d64 ;\n        ta = *s64;\n        tb = val;\n        while (1) {\n          if (tb & 1) prod ^= ta;\n          tb >>= 1;\n          if (tb == 0) break;\n          AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        }\n        *d64 = prod;\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        prod = 0 ;\n        ta = *s64;\n        tb = val;\n        while (1) {\n          if (tb & 1) prod ^= ta;\n          tb >>= 1;\n          if (tb == 0) break;\n          AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        }\n        *d64 = prod;\n        d64++;\n        s64++;\n      }\n    }\n    break;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\n#ifdef INTEL_SSE2\nstatic\nvoid\ngf_w32_bytwo_b_sse_region_2_noxor(gf_region_data *rd, struct gf_w32_bytwo_data *btd)\n{\n  uint8_t *d8, *s8;\n  __m128i pp, m1, m2, t1, t2, va;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  pp = _mm_set1_epi32(btd->prim_poly&0xffffffff);\n  m1 = _mm_set1_epi32((btd->mask1)&0xffffffff);\n  m2 = _mm_set1_epi32((btd->mask2)&0xffffffff);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    SSE_AB2(pp, m1, m2, va, t1, t2);\n    _mm_store_si128((__m128i *)d8, va);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic\nvoid\ngf_w32_bytwo_b_sse_region_2_xor(gf_region_data *rd, struct gf_w32_bytwo_data *btd)\n{\n  uint8_t *d8, *s8;\n  __m128i pp, m1, m2, t1, t2, va, vb;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  pp = _mm_set1_epi32(btd->prim_poly&0xffffffff);\n  m1 = _mm_set1_epi32((btd->mask1)&0xffffffff);\n  m2 = _mm_set1_epi32((btd->mask2)&0xffffffff);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    SSE_AB2(pp, m1, m2, va, t1, t2);\n    vb = _mm_load_si128 ((__m128i *)(d8));\n    vb = _mm_xor_si128(vb, va);\n    _mm_store_si128((__m128i *)d8, vb);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n\n#ifdef INTEL_SSE2\nstatic\nvoid \ngf_w32_bytwo_b_sse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint32_t itb;\n  uint8_t *d8, *s8;\n  __m128i pp, m1, m2, t1, t2, va, vb;\n  struct gf_w32_bytwo_data *btd;\n  gf_region_data rd;\n    \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n\n  btd = (struct gf_w32_bytwo_data *) ((gf_internal_t *) (gf->scratch))->private;\n\n  if (val == 2) {\n    if (xor) {\n      gf_w32_bytwo_b_sse_region_2_xor(&rd, btd);\n    } else {\n      gf_w32_bytwo_b_sse_region_2_noxor(&rd, btd);\n    }\n    gf_do_final_region_alignment(&rd);\n    return;\n  }\n\n  s8 = (uint8_t *) rd.s_start;\n  d8 = (uint8_t *) rd.d_start;\n\n  pp = _mm_set1_epi32(btd->prim_poly&0xffffffff);\n  m1 = _mm_set1_epi32((btd->mask1)&0xffffffff);\n  m2 = _mm_set1_epi32((btd->mask2)&0xffffffff);\n\n  while (d8 < (uint8_t *) rd.d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    vb = (!xor) ? _mm_setzero_si128() : _mm_load_si128 ((__m128i *)(d8));\n    itb = val;\n    while (1) {\n      if (itb & 1) vb = _mm_xor_si128(vb, va);\n      itb >>= 1;\n      if (itb == 0) break;\n      SSE_AB2(pp, m1, m2, va, t1, t2);\n    }\n    _mm_store_si128((__m128i *)d8, vb);\n    d8 += 16;\n    s8 += 16;\n  }\n\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\nstatic\nint gf_w32_bytwo_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  uint64_t ip, m1, m2;\n  struct gf_w32_bytwo_data *btd;\n\n  h = (gf_internal_t *) gf->scratch;\n  btd = (struct gf_w32_bytwo_data *) (h->private);\n  ip = h->prim_poly & 0xffffffff;\n  m1 = 0xfffffffe;\n  m2 = 0x80000000;\n  btd->prim_poly = 0;\n  btd->mask1 = 0;\n  btd->mask2 = 0;\n\n  while (ip != 0) {\n    btd->prim_poly |= ip;\n    btd->mask1 |= m1;\n    btd->mask2 |= m2;\n    ip <<= GF_FIELD_WIDTH;\n    m1 <<= GF_FIELD_WIDTH;\n    m2 <<= GF_FIELD_WIDTH;\n  }\n\n  if (h->mult_type == GF_MULT_BYTWO_p) {\n    gf->multiply.w32 = gf_w32_bytwo_p_multiply;\n    #ifdef INTEL_SSE2\n      if (h->region_type & GF_REGION_NOSIMD)\n        gf->multiply_region.w32 = gf_w32_bytwo_p_nosse_multiply_region; \n      else\n        gf->multiply_region.w32 = gf_w32_bytwo_p_sse_multiply_region; \n    #else\n      gf->multiply_region.w32 = gf_w32_bytwo_p_nosse_multiply_region; \n      if(h->region_type & GF_REGION_SIMD)\n        return 0;\n    #endif\n  } else {\n    gf->multiply.w32 = gf_w32_bytwo_b_multiply; \n    #ifdef INTEL_SSE2\n      if (h->region_type & GF_REGION_NOSIMD)\n        gf->multiply_region.w32 = gf_w32_bytwo_b_nosse_multiply_region; \n      else\n        gf->multiply_region.w32 = gf_w32_bytwo_b_sse_multiply_region; \n    #else\n      gf->multiply_region.w32 = gf_w32_bytwo_b_nosse_multiply_region; \n      if(h->region_type & GF_REGION_SIMD)\n        return 0;\n    #endif\n  }\n\n  gf->inverse.w32 = gf_w32_euclid;\n  return 1;\n}\n\nstatic\ninline\nuint32_t\ngf_w32_split_8_8_multiply (gf_t *gf, uint32_t a32, uint32_t b32)\n{\n  uint32_t product, i, j, mask, tb;\n  gf_internal_t *h;\n  struct gf_w32_split_8_8_data *d8;\n  \n  h = (gf_internal_t *) gf->scratch;\n  d8 = (struct gf_w32_split_8_8_data *) h->private;\n  product = 0;\n  mask = 0xff;\n\n  for (i = 0; i < 4; i++) {\n    tb = b32;\n    for (j = 0; j < 4; j++) {\n      product ^= d8->tables[i+j][a32&mask][tb&mask];\n      tb >>= 8;\n    }\n    a32 >>= 8;\n  }\n  return product;\n}\n\nstatic\ninline\nvoid\ngf_w32_split_8_32_lazy_multiply_region(gf_t *gf, void *src, void *dest, uint32_t val, int bytes, int xor)\n{\n  gf_internal_t *h;\n  uint32_t *s32, *d32, *top, p, a, v;\n  struct gf_split_8_32_lazy_data *d8;\n  struct gf_w32_split_8_8_data *d88;\n  uint32_t *t[4];\n  int i, j, k, change;\n  uint32_t pp;\n  gf_region_data rd;\n  \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  h = (gf_internal_t *) gf->scratch;\n  if (h->arg1 == 32 || h->arg2 == 32 || h->mult_type == GF_MULT_DEFAULT) {\n    d8 = (struct gf_split_8_32_lazy_data *) h->private;\n    for (i = 0; i < 4; i++) t[i] = d8->tables[i];\n    change = (val != d8->last_value);\n    if (change) d8->last_value = val;\n  } else {\n    d88 = (struct gf_w32_split_8_8_data *) h->private;\n    for (i = 0; i < 4; i++) t[i] = d88->region_tables[i];\n    change = (val != d88->last_value);\n    if (change) d88->last_value = val;\n  }\n  pp = h->prim_poly;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 4);\n  gf_do_initial_region_alignment(&rd);\n\n  s32 = (uint32_t *) rd.s_start;\n  d32 = (uint32_t *) rd.d_start;\n  top = (uint32_t *) rd.d_top;\n  \n  if (change) {\n    v = val;\n    for (i = 0; i < 4; i++) {\n      t[i][0] = 0;\n      for (j = 1; j < 256; j <<= 1) {\n        for (k = 0; k < j; k++) {\n          t[i][k^j] = (v ^ t[i][k]);\n        }\n        v = (v & GF_FIRST_BIT) ? ((v << 1) ^ pp) : (v << 1);\n      }\n    }\n  } \n\n  while (d32 < top) {\n    p = (xor) ? *d32 : 0;\n    a = *s32;\n    i = 0;\n    while (a != 0) {\n      v = (a & 0xff);\n      p ^= t[i][v];\n      a >>= 8;\n      i++;\n    }\n    *d32 = p;\n    d32++;\n    s32++;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\ninline\nvoid\ngf_w32_split_16_32_lazy_multiply_region(gf_t *gf, void *src, void *dest, uint32_t val, int bytes, int xor)\n{\n  gf_internal_t *h;\n  uint32_t *s32, *d32, *top, p, a, v;\n  struct gf_split_16_32_lazy_data *d16;\n  uint32_t *t[2];\n  int i, j, k, change;\n  uint32_t pp;\n  gf_region_data rd;\n  \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  h = (gf_internal_t *) gf->scratch;\n  d16 = (struct gf_split_16_32_lazy_data *) h->private;\n  for (i = 0; i < 2; i++) t[i] = d16->tables[i];\n  change = (val != d16->last_value);\n  if (change) d16->last_value = val;\n\n  pp = h->prim_poly;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 4);\n  gf_do_initial_region_alignment(&rd);\n\n  s32 = (uint32_t *) rd.s_start;\n  d32 = (uint32_t *) rd.d_start;\n  top = (uint32_t *) rd.d_top;\n  \n  if (change) {\n    v = val;\n    for (i = 0; i < 2; i++) {\n      t[i][0] = 0;\n      for (j = 1; j < (1 << 16); j <<= 1) {\n        for (k = 0; k < j; k++) {\n          t[i][k^j] = (v ^ t[i][k]);\n        }\n        v = (v & GF_FIRST_BIT) ? ((v << 1) ^ pp) : (v << 1);\n      }\n    }\n  } \n\n  while (d32 < top) {\n    p = (xor) ? *d32 : 0;\n    a = *s32;\n    i = 0;\n    while (a != 0 && i < 2) {\n      v = (a & 0xffff);\n      p ^= t[i][v];\n      a >>= 16;\n      i++;\n    }\n    *d32 = p;\n    d32++;\n    s32++;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\nvoid\ngf_w32_split_2_32_lazy_multiply_region(gf_t *gf, void *src, void *dest, uint32_t val, int bytes, int xor)\n{\n  gf_internal_t *h;\n  struct gf_split_2_32_lazy_data *ld;\n  int i;\n  uint32_t pp, v, v2, s, *s32, *d32, *top;\n  gf_region_data rd;\n \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 4);\n  gf_do_initial_region_alignment(&rd);\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  ld = (struct gf_split_2_32_lazy_data *) h->private;\n  \n  if (ld->last_value != val) {\n    v = val;\n    for (i = 0; i < 16; i++) {\n      v2 = (v << 1);\n      if (v & GF_FIRST_BIT) v2 ^= pp;\n      ld->tables[i][0] = 0;\n      ld->tables[i][1] = v;\n      ld->tables[i][2] = v2;\n      ld->tables[i][3] = (v2 ^ v);\n      v = (v2 << 1);\n      if (v2 & GF_FIRST_BIT) v ^= pp;\n    }\n  }\n  ld->last_value = val;\n\n  s32 = (uint32_t *) rd.s_start;\n  d32 = (uint32_t *) rd.d_start;\n  top = (uint32_t *) rd.d_top;\n\n  while (d32 != top) {\n    v = (xor) ? *d32 : 0;\n    s = *s32;\n    i = 0;\n    while (s != 0) {\n      v ^= ld->tables[i][s&3];\n      s >>= 2;\n      i++;\n    }\n    *d32 = v;\n    d32++;\n    s32++;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\n#ifdef INTEL_SSSE3\nstatic\nvoid\ngf_w32_split_2_32_lazy_sse_multiply_region(gf_t *gf, void *src, void *dest, uint32_t val, int bytes, int xor)\n{\n  gf_internal_t *h;\n  int i, tindex;\n  uint32_t pp, v, v2, *s32, *d32, *top;\n  __m128i vi, si, pi, shuffler, tables[16], adder, xi, mask1, mask2;\n  gf_region_data rd;\n \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 32);\n  gf_do_initial_region_alignment(&rd);\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n  \n  s32 = (uint32_t *) rd.s_start;\n  d32 = (uint32_t *) rd.d_start;\n  top = (uint32_t *) rd.d_top;\n  \n  v = val;\n  for (i = 0; i < 16; i++) {\n    v2 = (v << 1);\n    if (v & GF_FIRST_BIT) v2 ^= pp;\n    tables[i] = _mm_set_epi32(v2 ^ v, v2, v, 0);\n    v = (v2 << 1);\n    if (v2 & GF_FIRST_BIT) v ^= pp;\n  }\n\n  shuffler = _mm_set_epi8(0xc, 0xc, 0xc, 0xc, 8, 8, 8, 8, 4, 4, 4, 4, 0, 0, 0, 0);\n  adder = _mm_set_epi8(3, 2, 1, 0, 3, 2, 1, 0, 3, 2, 1, 0, 3, 2, 1, 0);\n  mask1 = _mm_set1_epi8(0x3);\n  mask2 = _mm_set1_epi8(0xc);\n\n  while (d32 != top) {\n    pi = (xor) ? _mm_load_si128 ((__m128i *) d32) : _mm_setzero_si128();\n    vi = _mm_load_si128((__m128i *) s32);\n \n    tindex = 0;\n    for (i = 0; i < 4; i++) {\n      si = _mm_shuffle_epi8(vi, shuffler);\n\n      xi = _mm_and_si128(si, mask1);\n      xi = _mm_slli_epi16(xi, 2);\n      xi = _mm_xor_si128(xi, adder);\n      pi = _mm_xor_si128(pi, _mm_shuffle_epi8(tables[tindex], xi));\n      tindex++;\n\n      xi = _mm_and_si128(si, mask2);\n      xi = _mm_xor_si128(xi, adder);\n      pi = _mm_xor_si128(pi, _mm_shuffle_epi8(tables[tindex], xi));\n      si = _mm_srli_epi16(si, 2);\n      tindex++;\n\n      xi = _mm_and_si128(si, mask2);\n      xi = _mm_xor_si128(xi, adder);\n      pi = _mm_xor_si128(pi, _mm_shuffle_epi8(tables[tindex], xi));\n      si = _mm_srli_epi16(si, 2);\n      tindex++;\n\n      xi = _mm_and_si128(si, mask2);\n      xi = _mm_xor_si128(xi, adder);\n      pi = _mm_xor_si128(pi, _mm_shuffle_epi8(tables[tindex], xi));\n      tindex++;\n      \n      vi = _mm_srli_epi32(vi, 8);\n    }\n    _mm_store_si128((__m128i *) d32, pi);\n    d32 += 4;\n    s32 += 4;\n  }\n\n  gf_do_final_region_alignment(&rd);\n\n}\n#endif\n\nstatic\nvoid\ngf_w32_split_4_32_lazy_multiply_region(gf_t *gf, void *src, void *dest, uint32_t val, int bytes, int xor)\n{\n  gf_internal_t *h;\n  struct gf_split_4_32_lazy_data *ld;\n  int i, j, k;\n  uint32_t pp, v, s, *s32, *d32, *top;\n  gf_region_data rd;\n \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  ld = (struct gf_split_4_32_lazy_data *) h->private;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 4);\n  gf_do_initial_region_alignment(&rd);\n  \n  if (ld->last_value != val) {\n    v = val;\n    for (i = 0; i < 8; i++) {\n      ld->tables[i][0] = 0;\n      for (j = 1; j < 16; j <<= 1) {\n        for (k = 0; k < j; k++) {\n          ld->tables[i][k^j] = (v ^ ld->tables[i][k]);\n        }\n        v = (v & GF_FIRST_BIT) ? ((v << 1) ^ pp) : (v << 1);\n      }\n    }\n  }\n  ld->last_value = val;\n\n  s32 = (uint32_t *) rd.s_start;\n  d32 = (uint32_t *) rd.d_start;\n  top = (uint32_t *) rd.d_top;\n\n  while (d32 != top) {\n    v = (xor) ? *d32 : 0;\n    s = *s32;\n    i = 0;\n    while (s != 0) {\n      v ^= ld->tables[i][s&0xf];\n      s >>= 4;\n      i++;\n    }\n    *d32 = v;\n    d32++;\n    s32++;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\nvoid\ngf_w32_split_4_32_lazy_sse_altmap_multiply_region(gf_t *gf, void *src, void *dest, uint32_t val, int bytes, int xor)\n{\n#ifdef INTEL_SSSE3\n  gf_internal_t *h;\n  int i, j, k;\n  uint32_t pp, v, *s32, *d32, *top;\n  __m128i si, tables[8][4], p0, p1, p2, p3, mask1, v0, v1, v2, v3;\n  struct gf_split_4_32_lazy_data *ld;\n  uint8_t btable[16];\n  gf_region_data rd;\n \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n  \n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 64);\n  gf_do_initial_region_alignment(&rd);\n\n  s32 = (uint32_t *) rd.s_start;\n  d32 = (uint32_t *) rd.d_start;\n  top = (uint32_t *) rd.d_top;\n  \n  ld = (struct gf_split_4_32_lazy_data *) h->private;\n \n  v = val;\n  for (i = 0; i < 8; i++) {\n    ld->tables[i][0] = 0;\n    for (j = 1; j < 16; j <<= 1) {\n      for (k = 0; k < j; k++) {\n        ld->tables[i][k^j] = (v ^ ld->tables[i][k]);\n      }\n      v = (v & GF_FIRST_BIT) ? ((v << 1) ^ pp) : (v << 1);\n    }\n    for (j = 0; j < 4; j++) {\n      for (k = 0; k < 16; k++) {\n        btable[k] = (uint8_t) ld->tables[i][k];\n        ld->tables[i][k] >>= 8;\n      }\n      tables[i][j] = _mm_loadu_si128((__m128i *) btable);\n    }\n  }\n\n  mask1 = _mm_set1_epi8(0xf);\n\n  if (xor) {\n    while (d32 != top) {\n      p0 = _mm_load_si128 ((__m128i *) d32);\n      p1 = _mm_load_si128 ((__m128i *) (d32+4));\n      p2 = _mm_load_si128 ((__m128i *) (d32+8));\n      p3 = _mm_load_si128 ((__m128i *) (d32+12));\n  \n      v0 = _mm_load_si128((__m128i *) s32); s32 += 4;\n      v1 = _mm_load_si128((__m128i *) s32); s32 += 4;\n      v2 = _mm_load_si128((__m128i *) s32); s32 += 4;\n      v3 = _mm_load_si128((__m128i *) s32); s32 += 4;\n  \n      si = _mm_and_si128(v0, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[0][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[0][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[0][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[0][3], si));\n      \n      v0 = _mm_srli_epi32(v0, 4);\n      si = _mm_and_si128(v0, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[1][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[1][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[1][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[1][3], si));\n  \n      si = _mm_and_si128(v1, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[2][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[2][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[2][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[2][3], si));\n      \n      v1 = _mm_srli_epi32(v1, 4);\n      si = _mm_and_si128(v1, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[3][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[3][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[3][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[3][3], si));\n  \n      si = _mm_and_si128(v2, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[4][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[4][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[4][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[4][3], si));\n      \n      v2 = _mm_srli_epi32(v2, 4);\n      si = _mm_and_si128(v2, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[5][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[5][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[5][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[5][3], si));\n  \n      si = _mm_and_si128(v3, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[6][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[6][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[6][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[6][3], si));\n      \n      v3 = _mm_srli_epi32(v3, 4);\n      si = _mm_and_si128(v3, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[7][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[7][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[7][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[7][3], si));\n  \n      _mm_store_si128((__m128i *) d32, p0);\n      _mm_store_si128((__m128i *) (d32+4), p1);\n      _mm_store_si128((__m128i *) (d32+8), p2);\n      _mm_store_si128((__m128i *) (d32+12), p3);\n      d32 += 16;\n    } \n  } else {\n    while (d32 != top) {\n  \n      v0 = _mm_load_si128((__m128i *) s32); s32 += 4;\n      v1 = _mm_load_si128((__m128i *) s32); s32 += 4;\n      v2 = _mm_load_si128((__m128i *) s32); s32 += 4;\n      v3 = _mm_load_si128((__m128i *) s32); s32 += 4;\n\n      si = _mm_and_si128(v0, mask1);\n      p0 = _mm_shuffle_epi8(tables[0][0], si);\n      p1 = _mm_shuffle_epi8(tables[0][1], si);\n      p2 = _mm_shuffle_epi8(tables[0][2], si);\n      p3 = _mm_shuffle_epi8(tables[0][3], si);\n      \n      v0 = _mm_srli_epi32(v0, 4);\n      si = _mm_and_si128(v0, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[1][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[1][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[1][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[1][3], si));\n  \n      si = _mm_and_si128(v1, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[2][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[2][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[2][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[2][3], si));\n      \n      v1 = _mm_srli_epi32(v1, 4);\n      si = _mm_and_si128(v1, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[3][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[3][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[3][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[3][3], si));\n  \n      si = _mm_and_si128(v2, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[4][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[4][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[4][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[4][3], si));\n      \n      v2 = _mm_srli_epi32(v2, 4);\n      si = _mm_and_si128(v2, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[5][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[5][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[5][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[5][3], si));\n  \n      si = _mm_and_si128(v3, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[6][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[6][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[6][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[6][3], si));\n      \n      v3 = _mm_srli_epi32(v3, 4);\n      si = _mm_and_si128(v3, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[7][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[7][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[7][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[7][3], si));\n  \n      _mm_store_si128((__m128i *) d32, p0);\n      _mm_store_si128((__m128i *) (d32+4), p1);\n      _mm_store_si128((__m128i *) (d32+8), p2);\n      _mm_store_si128((__m128i *) (d32+12), p3);\n      d32 += 16;\n    } \n  }\n\n  gf_do_final_region_alignment(&rd);\n\n#endif\n}\n\n\nstatic\nvoid\ngf_w32_split_4_32_lazy_sse_multiply_region(gf_t *gf, void *src, void *dest, uint32_t val, int bytes, int xor)\n{\n#ifdef INTEL_SSSE3\n  gf_internal_t *h;\n  int i, j, k;\n  uint32_t pp, v, *s32, *d32, *top, tmp_table[16];\n  __m128i si, tables[8][4], p0, p1, p2, p3, mask1, v0, v1, v2, v3, mask8;\n  __m128i tv1, tv2, tv3, tv0;\n  uint8_t btable[16];\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n  \n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 64);\n  gf_do_initial_region_alignment(&rd);\n\n  s32 = (uint32_t *) rd.s_start;\n  d32 = (uint32_t *) rd.d_start;\n  top = (uint32_t *) rd.d_top;\n\n  v = val;\n  for (i = 0; i < 8; i++) {\n    tmp_table[0] = 0;\n    for (j = 1; j < 16; j <<= 1) {\n      for (k = 0; k < j; k++) {\n        tmp_table[k^j] = (v ^ tmp_table[k]);\n      }\n      v = (v & GF_FIRST_BIT) ? ((v << 1) ^ pp) : (v << 1);\n    }\n    for (j = 0; j < 4; j++) {\n      for (k = 0; k < 16; k++) {\n        btable[k] = (uint8_t) tmp_table[k];\n        tmp_table[k] >>= 8;\n      }\n      tables[i][j] = _mm_loadu_si128((__m128i *) btable);\n    }\n  }\n\n  mask1 = _mm_set1_epi8(0xf);\n  mask8 = _mm_set1_epi16(0xff);\n\n  if (xor) {\n    while (d32 != top) {\n      v0 = _mm_load_si128((__m128i *) s32); s32 += 4;\n      v1 = _mm_load_si128((__m128i *) s32); s32 += 4;\n      v2 = _mm_load_si128((__m128i *) s32); s32 += 4;\n      v3 = _mm_load_si128((__m128i *) s32); s32 += 4;\n  \n      p0 = _mm_srli_epi16(v0, 8);\n      p1 = _mm_srli_epi16(v1, 8);\n      p2 = _mm_srli_epi16(v2, 8);\n      p3 = _mm_srli_epi16(v3, 8);\n\n      tv0 = _mm_and_si128(v0, mask8);\n      tv1 = _mm_and_si128(v1, mask8);\n      tv2 = _mm_and_si128(v2, mask8);\n      tv3 = _mm_and_si128(v3, mask8);\n\n      v0 = _mm_packus_epi16(p1, p0);\n      v1 = _mm_packus_epi16(tv1, tv0);\n      v2 = _mm_packus_epi16(p3, p2);\n      v3 = _mm_packus_epi16(tv3, tv2);\n\n      p0 = _mm_srli_epi16(v0, 8);\n      p1 = _mm_srli_epi16(v1, 8);\n      p2 = _mm_srli_epi16(v2, 8);\n      p3 = _mm_srli_epi16(v3, 8);\n\n      tv0 = _mm_and_si128(v0, mask8);\n      tv1 = _mm_and_si128(v1, mask8);\n      tv2 = _mm_and_si128(v2, mask8);\n      tv3 = _mm_and_si128(v3, mask8);\n\n      v0 = _mm_packus_epi16(p2, p0);\n      v1 = _mm_packus_epi16(p3, p1);\n      v2 = _mm_packus_epi16(tv2, tv0);\n      v3 = _mm_packus_epi16(tv3, tv1);\n\n      si = _mm_and_si128(v0, mask1);\n      p0 = _mm_shuffle_epi8(tables[6][0], si);\n      p1 = _mm_shuffle_epi8(tables[6][1], si);\n      p2 = _mm_shuffle_epi8(tables[6][2], si);\n      p3 = _mm_shuffle_epi8(tables[6][3], si);\n      \n      v0 = _mm_srli_epi32(v0, 4);\n      si = _mm_and_si128(v0, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[7][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[7][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[7][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[7][3], si));\n  \n      si = _mm_and_si128(v1, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[4][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[4][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[4][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[4][3], si));\n      \n      v1 = _mm_srli_epi32(v1, 4);\n      si = _mm_and_si128(v1, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[5][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[5][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[5][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[5][3], si));\n  \n      si = _mm_and_si128(v2, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[2][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[2][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[2][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[2][3], si));\n      \n      v2 = _mm_srli_epi32(v2, 4);\n      si = _mm_and_si128(v2, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[3][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[3][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[3][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[3][3], si));\n  \n      si = _mm_and_si128(v3, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[0][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[0][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[0][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[0][3], si));\n      \n      v3 = _mm_srli_epi32(v3, 4);\n      si = _mm_and_si128(v3, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[1][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[1][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[1][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[1][3], si));\n  \n      tv0 = _mm_unpackhi_epi8(p1, p3);\n      tv1 = _mm_unpackhi_epi8(p0, p2);\n      tv2 = _mm_unpacklo_epi8(p1, p3);\n      tv3 = _mm_unpacklo_epi8(p0, p2);\n\n      p0 = _mm_unpackhi_epi8(tv1, tv0);\n      p1 = _mm_unpacklo_epi8(tv1, tv0);\n      p2 = _mm_unpackhi_epi8(tv3, tv2);\n      p3 = _mm_unpacklo_epi8(tv3, tv2);\n\n      v0 = _mm_load_si128 ((__m128i *) d32);\n      v1 = _mm_load_si128 ((__m128i *) (d32+4));\n      v2 = _mm_load_si128 ((__m128i *) (d32+8));\n      v3 = _mm_load_si128 ((__m128i *) (d32+12));\n  \n      p0 = _mm_xor_si128(p0, v0);\n      p1 = _mm_xor_si128(p1, v1);\n      p2 = _mm_xor_si128(p2, v2);\n      p3 = _mm_xor_si128(p3, v3);\n\n      _mm_store_si128((__m128i *) d32, p0);\n      _mm_store_si128((__m128i *) (d32+4), p1);\n      _mm_store_si128((__m128i *) (d32+8), p2);\n      _mm_store_si128((__m128i *) (d32+12), p3);\n      d32 += 16;\n    } \n  } else {\n    while (d32 != top) {\n      v0 = _mm_load_si128((__m128i *) s32); s32 += 4;\n      v1 = _mm_load_si128((__m128i *) s32); s32 += 4;\n      v2 = _mm_load_si128((__m128i *) s32); s32 += 4;\n      v3 = _mm_load_si128((__m128i *) s32); s32 += 4;\n \n      p0 = _mm_srli_epi16(v0, 8);\n      p1 = _mm_srli_epi16(v1, 8);\n      p2 = _mm_srli_epi16(v2, 8);\n      p3 = _mm_srli_epi16(v3, 8);\n      \n      tv0 = _mm_and_si128(v0, mask8);\n      tv1 = _mm_and_si128(v1, mask8);\n      tv2 = _mm_and_si128(v2, mask8);\n      tv3 = _mm_and_si128(v3, mask8);\n      \n      v0 = _mm_packus_epi16(p1, p0);\n      v1 = _mm_packus_epi16(tv1, tv0);\n      v2 = _mm_packus_epi16(p3, p2);\n      v3 = _mm_packus_epi16(tv3, tv2);\n      \n      p0 = _mm_srli_epi16(v0, 8);\n      p1 = _mm_srli_epi16(v1, 8);\n      p2 = _mm_srli_epi16(v2, 8);\n      p3 = _mm_srli_epi16(v3, 8);\n     \n      tv0 = _mm_and_si128(v0, mask8);\n      tv1 = _mm_and_si128(v1, mask8);\n      tv2 = _mm_and_si128(v2, mask8);\n      tv3 = _mm_and_si128(v3, mask8);\n      \n      v0 = _mm_packus_epi16(p2, p0);\n      v1 = _mm_packus_epi16(p3, p1);\n      v2 = _mm_packus_epi16(tv2, tv0);\n      v3 = _mm_packus_epi16(tv3, tv1);\n      \n      si = _mm_and_si128(v0, mask1);\n      p0 = _mm_shuffle_epi8(tables[6][0], si);\n      p1 = _mm_shuffle_epi8(tables[6][1], si);\n      p2 = _mm_shuffle_epi8(tables[6][2], si);\n      p3 = _mm_shuffle_epi8(tables[6][3], si);\n      \n      v0 = _mm_srli_epi32(v0, 4);\n      si = _mm_and_si128(v0, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[7][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[7][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[7][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[7][3], si));\n  \n      si = _mm_and_si128(v1, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[4][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[4][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[4][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[4][3], si));\n      \n      v1 = _mm_srli_epi32(v1, 4);\n      si = _mm_and_si128(v1, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[5][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[5][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[5][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[5][3], si));\n  \n      si = _mm_and_si128(v2, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[2][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[2][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[2][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[2][3], si));\n      \n      v2 = _mm_srli_epi32(v2, 4);\n      si = _mm_and_si128(v2, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[3][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[3][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[3][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[3][3], si));\n  \n      si = _mm_and_si128(v3, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[0][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[0][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[0][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[0][3], si));\n      \n      v3 = _mm_srli_epi32(v3, 4);\n      si = _mm_and_si128(v3, mask1);\n      p0 = _mm_xor_si128(p0, _mm_shuffle_epi8(tables[1][0], si));\n      p1 = _mm_xor_si128(p1, _mm_shuffle_epi8(tables[1][1], si));\n      p2 = _mm_xor_si128(p2, _mm_shuffle_epi8(tables[1][2], si));\n      p3 = _mm_xor_si128(p3, _mm_shuffle_epi8(tables[1][3], si)); \n  \n      tv0 = _mm_unpackhi_epi8(p1, p3);\n      tv1 = _mm_unpackhi_epi8(p0, p2);\n      tv2 = _mm_unpacklo_epi8(p1, p3);\n      tv3 = _mm_unpacklo_epi8(p0, p2);\n      \n      p0 = _mm_unpackhi_epi8(tv1, tv0);\n      p1 = _mm_unpacklo_epi8(tv1, tv0);\n      p2 = _mm_unpackhi_epi8(tv3, tv2);\n      p3 = _mm_unpacklo_epi8(tv3, tv2);\n      \n      _mm_store_si128((__m128i *) d32, p0);\n      _mm_store_si128((__m128i *) (d32+4), p1);\n      _mm_store_si128((__m128i *) (d32+8), p2);\n      _mm_store_si128((__m128i *) (d32+12), p3);\n      d32 += 16;\n    } \n  }\n  gf_do_final_region_alignment(&rd);\n\n#endif\n}\n\nstatic \nint gf_w32_split_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  struct gf_split_2_32_lazy_data *ld2;\n  struct gf_split_4_32_lazy_data *ld4;\n  struct gf_w32_split_8_8_data *d8;\n  struct gf_split_8_32_lazy_data *d32;\n  struct gf_split_16_32_lazy_data *d16;\n  uint32_t p, basep;\n  int i, j, exp, ispclmul, issse3;\n  int isneon = 0;\n\n#if defined(INTEL_SSE4_PCLMUL)\n  ispclmul = 1;\n#else\n  ispclmul = 0;\n#endif\n\n#ifdef INTEL_SSSE3\n  issse3 = 1;\n#else\n  issse3 = 0;\n#endif\n#ifdef ARM_NEON\n  isneon = 1;\n#endif\n\n  h = (gf_internal_t *) gf->scratch;\n\n  /* Defaults */\n  \n  gf->inverse.w32 = gf_w32_euclid;\n\n  /* JSP: First handle single multiplication:  \n     If args == 8, then we're doing split 8 8.  \n     Otherwise, if PCLMUL, we use that.\n     Otherwise, we use bytwo_p.\n   */\n\n  if (h->arg1 == 8 && h->arg2 == 8) {\n    gf->multiply.w32 = gf_w32_split_8_8_multiply;\n  } else if (ispclmul) {\n    if ((0xfffe0000 & h->prim_poly) == 0){\n      gf->multiply.w32 = gf_w32_clm_multiply_2;\n    } else if ((0xffc00000 & h->prim_poly) == 0){\n      gf->multiply.w32 = gf_w32_clm_multiply_3;\n    } else if ((0xfe000000 & h->prim_poly) == 0){\n     gf->multiply.w32 = gf_w32_clm_multiply_4;\n    }\n  } else {\n    gf->multiply.w32 = gf_w32_bytwo_p_multiply;\n  }\n\n  /* Easy cases: 16/32 and 2/32 */\n\n  if ((h->arg1 == 16 && h->arg2 == 32) || (h->arg1 == 32 && h->arg2 == 16)) {\n    d16 = (struct gf_split_16_32_lazy_data *) h->private;\n    d16->last_value = 0;\n    gf->multiply_region.w32 = gf_w32_split_16_32_lazy_multiply_region;\n    return 1;\n  }\n\n  if ((h->arg1 == 2 && h->arg2 == 32) || (h->arg1 == 32 && h->arg2 == 2)) {\n    ld2 = (struct gf_split_2_32_lazy_data *) h->private;\n    ld2->last_value = 0;\n    #ifdef INTEL_SSSE3\n      if (!(h->region_type & GF_REGION_NOSIMD))\n        gf->multiply_region.w32 = gf_w32_split_2_32_lazy_sse_multiply_region;\n      else\n        gf->multiply_region.w32 = gf_w32_split_2_32_lazy_multiply_region;\n    #else\n      gf->multiply_region.w32 = gf_w32_split_2_32_lazy_multiply_region;\n      if(h->region_type & GF_REGION_SIMD) return 0;\n    #endif\n    return 1;\n  } \n\n  /* 4/32 or Default + SSE - There is no ALTMAP/NOSSE. */\n\n  if ((h->arg1 == 4 && h->arg2 == 32) || (h->arg1 == 32 && h->arg2 == 4) ||\n      ((issse3 || isneon) && h->mult_type == GF_REGION_DEFAULT)) {\n    ld4 = (struct gf_split_4_32_lazy_data *) h->private;\n    ld4->last_value = 0;\n    if ((h->region_type & GF_REGION_NOSIMD) || !(issse3 || isneon)) {\n      gf->multiply_region.w32 = gf_w32_split_4_32_lazy_multiply_region;\n    } else if (isneon) {\n#ifdef ARM_NEON\n      gf_w32_neon_split_init(gf);\n#endif\n    } else if (h->region_type & GF_REGION_ALTMAP) {\n      gf->multiply_region.w32 = gf_w32_split_4_32_lazy_sse_altmap_multiply_region;\n    } else {\n      gf->multiply_region.w32 = gf_w32_split_4_32_lazy_sse_multiply_region;\n    }\n    return 1;\n  } \n\n  /* 8/32 or Default + no SSE */\n\n  if ((h->arg1 == 8 && h->arg2 == 32) || (h->arg1 == 32 && h->arg2 == 8) || \n       h->mult_type == GF_MULT_DEFAULT) {\n    d32 = (struct gf_split_8_32_lazy_data *) h->private;\n    d32->last_value = 0;\n    gf->multiply_region.w32 = gf_w32_split_8_32_lazy_multiply_region;\n    return 1;\n  }\n\n  /* Finally, if args == 8, then we have to set up the tables here. */\n\n  if (h->arg1 == 8 && h->arg2 == 8) {\n    d8 = (struct gf_w32_split_8_8_data *) h->private;\n    d8->last_value = 0;\n    gf->multiply.w32 = gf_w32_split_8_8_multiply;\n    gf->multiply_region.w32 = gf_w32_split_8_32_lazy_multiply_region;\n    basep = 1;\n    for (exp = 0; exp < 7; exp++) {\n      for (j = 0; j < 256; j++) d8->tables[exp][0][j] = 0;\n      for (i = 0; i < 256; i++) d8->tables[exp][i][0] = 0;\n      d8->tables[exp][1][1] = basep;\n      for (i = 2; i < 256; i++) {\n        if (i&1) {\n          p = d8->tables[exp][i^1][1];\n          d8->tables[exp][i][1] = p ^ basep;\n        } else {\n          p = d8->tables[exp][i>>1][1];\n          d8->tables[exp][i][1] = GF_MULTBY_TWO(p);\n        }\n      }\n      for (i = 1; i < 256; i++) {\n        p = d8->tables[exp][i][1];\n        for (j = 1; j < 256; j++) {\n          if (j&1) {\n            d8->tables[exp][i][j] = d8->tables[exp][i][j^1] ^ p;\n          } else {\n            d8->tables[exp][i][j] = GF_MULTBY_TWO(d8->tables[exp][i][j>>1]);\n          }\n        }\n      }\n      for (i = 0; i < 8; i++) basep = GF_MULTBY_TWO(basep);\n    }\n    return 1;\n  }\n\n  /* If we get here, then the arguments were bad. */\n\n  return 0;\n}\n\nstatic\nint gf_w32_group_init(gf_t *gf)\n{\n  uint32_t i, j, p, index;\n  struct gf_w32_group_data *gd;\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  uint32_t g_r, g_s;\n\n  g_s = h->arg1;\n  g_r = h->arg2;\n\n  gd = (struct gf_w32_group_data *) h->private;\n  gd->shift = (uint32_t *) (&(gd->memory));\n  gd->reduce = gd->shift + (1 << g_s);\n\n  gd->rmask = (1 << g_r) - 1;\n  gd->rmask <<= 32;\n\n  gd->tshift = 32 % g_s;\n  if (gd->tshift == 0) gd->tshift = g_s;\n  gd->tshift = (32 - gd->tshift);\n  gd->tshift = ((gd->tshift-1)/g_r) * g_r;\n\n  gd->reduce[0] = 0;\n  for (i = 0; i < ((uint32_t)1 << g_r); i++) {\n    p = 0;\n    index = 0;\n    for (j = 0; j < g_r; j++) {\n      if (i & (1 << j)) {\n        p ^= (h->prim_poly << j);\n        index ^= (1 << j);\n        index ^= (h->prim_poly >> (32-j));\n      }\n    }\n    gd->reduce[index] = p;\n  }\n\n  if (g_s == g_r) {\n    gf->multiply.w32 = gf_w32_group_s_equals_r_multiply;\n    gf->multiply_region.w32 = gf_w32_group_s_equals_r_multiply_region; \n  } else {\n    gf->multiply.w32 = gf_w32_group_multiply;\n    gf->multiply_region.w32 = gf_w32_group_multiply_region;\n  }\n  gf->divide.w32 = NULL;\n  gf->inverse.w32 = gf_w32_euclid;\n\n  return 1;\n}\n\n\nstatic\nuint32_t\ngf_w32_composite_multiply_recursive(gf_t *gf, uint32_t a, uint32_t b)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  gf_t *base_gf = h->base_gf;\n  uint32_t b0 = b & 0x0000ffff;\n  uint32_t b1 = (b & 0xffff0000) >> 16;\n  uint32_t a0 = a & 0x0000ffff;\n  uint32_t a1 = (a & 0xffff0000) >> 16;\n  uint32_t a1b1;\n  uint32_t rv;\n  a1b1 = base_gf->multiply.w32(base_gf, a1, b1);\n\n  rv = ((base_gf->multiply.w32(base_gf, a1, b0) ^ base_gf->multiply.w32(base_gf, a0, b1) ^ base_gf->multiply.w32(base_gf, a1b1, h->prim_poly)) << 16) | (base_gf->multiply.w32(base_gf, a0, b0) ^ a1b1);\n  return rv;\n}\n\n/* JSP: This could be made faster. Someday, when I'm bored. */\n\nstatic\nuint32_t\ngf_w32_composite_multiply_inline(gf_t *gf, uint32_t a, uint32_t b)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  uint32_t b0 = b & 0x0000ffff;\n  uint32_t b1 = b >> 16;\n  uint32_t a0 = a & 0x0000ffff;\n  uint32_t a1 = a >> 16;\n  uint32_t a1b1, prod;\n  uint16_t *log, *alog;\n  struct gf_w32_composite_data *cd;\n\n  cd = (struct gf_w32_composite_data *) h->private;\n  log = cd->log;\n  alog = cd->alog;\n\n  a1b1 = GF_W16_INLINE_MULT(log, alog, a1, b1);\n  prod = GF_W16_INLINE_MULT(log, alog, a1, b0);\n  prod ^= GF_W16_INLINE_MULT(log, alog, a0, b1);\n  prod ^= GF_W16_INLINE_MULT(log, alog, a1b1, h->prim_poly);\n  prod <<= 16;\n  prod ^= GF_W16_INLINE_MULT(log, alog, a0, b0);\n  prod ^= a1b1;\n  return prod;\n}\n\n/*\n * Composite field division trick (explained in 2007 tech report)\n *\n * Compute a / b = a*b^-1, where p(x) = x^2 + sx + 1\n *\n * let c = b^-1\n *\n * c*b = (s*b1c1+b1c0+b0c1)x+(b1c1+b0c0)\n *\n * want (s*b1c1+b1c0+b0c1) = 0 and (b1c1+b0c0) = 1\n *\n * let d = b1c1 and d+1 = b0c0\n *\n * solve s*b1c1+b1c0+b0c1 = 0\n *\n * solution: d = (b1b0^-1)(b1b0^-1+b0b1^-1+s)^-1\n *\n * c0 = (d+1)b0^-1\n * c1 = d*b1^-1\n *\n * a / b = a * c\n */\n\nstatic\nuint32_t\ngf_w32_composite_inverse(gf_t *gf, uint32_t a)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  gf_t *base_gf = h->base_gf;\n  uint16_t a0 = a & 0x0000ffff;\n  uint16_t a1 = (a & 0xffff0000) >> 16;\n  uint16_t c0, c1, d, tmp;\n  uint32_t c;\n  uint16_t a0inv, a1inv;\n\n  if (a0 == 0) {\n    a1inv = base_gf->inverse.w32(base_gf, a1);\n    c0 = base_gf->multiply.w32(base_gf, a1inv, h->prim_poly);\n    c1 = a1inv;\n  } else if (a1 == 0) {\n    c0 = base_gf->inverse.w32(base_gf, a0);\n    c1 = 0;\n  } else {\n    a1inv = base_gf->inverse.w32(base_gf, a1);\n    a0inv = base_gf->inverse.w32(base_gf, a0);\n\n    d = base_gf->multiply.w32(base_gf, a1, a0inv);\n\n    tmp = (base_gf->multiply.w32(base_gf, a1, a0inv) ^ base_gf->multiply.w32(base_gf, a0, a1inv) ^ h->prim_poly);\n    tmp = base_gf->inverse.w32(base_gf, tmp);\n\n    d = base_gf->multiply.w32(base_gf, d, tmp);\n\n    c0 = base_gf->multiply.w32(base_gf, (d^1), a0inv);\n    c1 = base_gf->multiply.w32(base_gf, d, a1inv);\n  }\n\n  c = c0 | (c1 << 16);\n\n  return c;\n}\n\nstatic\nvoid\ngf_w32_composite_multiply_region(gf_t *gf, void *src, void *dest, uint32_t val, int bytes, int xor)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  gf_t *base_gf = h->base_gf;\n  uint32_t b0 = val & 0x0000ffff;\n  uint32_t b1 = (val & 0xffff0000) >> 16;\n  uint32_t *s32, *d32, *top;\n  uint16_t a0, a1, a1b1, *log, *alog;\n  uint32_t prod;\n  gf_region_data rd;\n  struct gf_w32_composite_data *cd;\n\n  cd = (struct gf_w32_composite_data *) h->private;\n  log = cd->log;\n  alog = cd->alog;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 4);\n  \n  s32 = rd.s_start;\n  d32 = rd.d_start;\n  top = rd.d_top;\n\n  if (log == NULL) {\n    if (xor) {\n      while (d32 < top) {\n        a0 = *s32 & 0x0000ffff;\n        a1 = (*s32 & 0xffff0000) >> 16;\n        a1b1 = base_gf->multiply.w32(base_gf, a1, b1);\n  \n        *d32 ^= ((base_gf->multiply.w32(base_gf, a0, b0) ^ a1b1) |\n                  ((base_gf->multiply.w32(base_gf, a1, b0) ^ base_gf->multiply.w32(base_gf, a0, b1) ^ base_gf->multiply.w32(base_gf, a1b1, h->prim_poly)) << 16)); \n        s32++;\n        d32++;\n      }\n    } else {\n      while (d32 < top) {\n        a0 = *s32 & 0x0000ffff;\n        a1 = (*s32 & 0xffff0000) >> 16;\n        a1b1 = base_gf->multiply.w32(base_gf, a1, b1);\n  \n        *d32 = ((base_gf->multiply.w32(base_gf, a0, b0) ^ a1b1) |\n                  ((base_gf->multiply.w32(base_gf, a1, b0) ^ base_gf->multiply.w32(base_gf, a0, b1) ^ base_gf->multiply.w32(base_gf, a1b1, h->prim_poly)) << 16)); \n        s32++;\n        d32++;\n      }\n    }\n  } else {\n    if (xor) {\n      while (d32 < top) {\n        a0 = *s32 & 0x0000ffff;\n        a1 = (*s32 & 0xffff0000) >> 16;\n        a1b1 = GF_W16_INLINE_MULT(log, alog, a1, b1);\n\n        prod = GF_W16_INLINE_MULT(log, alog, a1, b0);\n        prod ^= GF_W16_INLINE_MULT(log, alog, a0, b1);\n        prod ^= GF_W16_INLINE_MULT(log, alog, a1b1, h->prim_poly);\n        prod <<= 16;\n        prod ^= GF_W16_INLINE_MULT(log, alog, a0, b0);\n        prod ^= a1b1;\n        *d32 ^= prod;\n        s32++;\n        d32++;\n      }\n    } else {\n      while (d32 < top) {\n        a0 = *s32 & 0x0000ffff;\n        a1 = (*s32 & 0xffff0000) >> 16;\n        a1b1 = GF_W16_INLINE_MULT(log, alog, a1, b1);\n  \n        prod = GF_W16_INLINE_MULT(log, alog, a1, b0);\n        prod ^= GF_W16_INLINE_MULT(log, alog, a0, b1);\n        prod ^= GF_W16_INLINE_MULT(log, alog, a1b1, h->prim_poly);\n        prod <<= 16;\n        prod ^= GF_W16_INLINE_MULT(log, alog, a0, b0);\n        prod ^= a1b1;\n        \n        *d32 = prod;\n        s32++;\n        d32++;\n      }\n    }\n  }\n}\n\nstatic\nvoid\ngf_w32_composite_multiply_region_alt(gf_t *gf, void *src, void *dest, uint32_t val, int bytes, int xor)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  gf_t *base_gf = h->base_gf;\n  uint16_t    val0 = val & 0x0000ffff;\n  uint16_t    val1 = (val & 0xffff0000) >> 16;\n  gf_region_data rd;\n  int sub_reg_size;\n  uint8_t *slow, *shigh;\n  uint8_t *dlow, *dhigh, *top;\n\n  /* JSP: I want the two pointers aligned wrt each other on 16 byte\n     boundaries.  So I'm going to make sure that the area on\n     which the two operate is a multiple of 32. Of course, that\n     junks up the mapping, but so be it -- that's why we have extract_word.... */\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 32);\n  gf_do_initial_region_alignment(&rd);\n\n  slow = (uint8_t *) rd.s_start;\n  dlow = (uint8_t *) rd.d_start;\n  top = (uint8_t *)  rd.d_top;\n  sub_reg_size = (top - dlow)/2;\n  shigh = slow + sub_reg_size;\n  dhigh = dlow + sub_reg_size;\n  \n  base_gf->multiply_region.w32(base_gf, slow, dlow, val0, sub_reg_size, xor);\n  base_gf->multiply_region.w32(base_gf, shigh, dlow, val1, sub_reg_size, 1);\n  base_gf->multiply_region.w32(base_gf, slow, dhigh, val1, sub_reg_size, xor);\n  base_gf->multiply_region.w32(base_gf, shigh, dhigh, val0, sub_reg_size, 1);\n  base_gf->multiply_region.w32(base_gf, shigh, dhigh, base_gf->multiply.w32(base_gf, h->prim_poly, val1), sub_reg_size, 1);\n\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\nint gf_w32_composite_init(gf_t *gf)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  struct gf_w32_composite_data *cd;\n\n  if (h->base_gf == NULL) return 0;\n\n  cd = (struct gf_w32_composite_data *) h->private;\n  cd->log = gf_w16_get_log_table(h->base_gf);\n  cd->alog = gf_w16_get_mult_alog_table(h->base_gf);\n\n  if (h->region_type & GF_REGION_ALTMAP) {\n    gf->multiply_region.w32 = gf_w32_composite_multiply_region_alt;\n  } else {\n    gf->multiply_region.w32 = gf_w32_composite_multiply_region;\n  }\n\n  if (cd->log == NULL) {\n    gf->multiply.w32 = gf_w32_composite_multiply_recursive;\n  } else {\n    gf->multiply.w32 = gf_w32_composite_multiply_inline; \n  }\n  gf->divide.w32 = NULL;\n  gf->inverse.w32 = gf_w32_composite_inverse;\n\n  return 1;\n}\n\n\n\nint gf_w32_scratch_size(int mult_type, int region_type, int divide_type, int arg1, int arg2)\n{\n  int issse3 = 0;\n  int isneon = 0;\n\n#ifdef INTEL_SSSE3\n  issse3 = 1;\n#endif\n#ifdef ARM_NEON\n  isneon = 1;\n#endif\n\n  switch(mult_type)\n  {\n    case GF_MULT_BYTWO_p:\n    case GF_MULT_BYTWO_b:\n      return sizeof(gf_internal_t) + sizeof(struct gf_w32_bytwo_data) + 64;\n      break;\n    case GF_MULT_GROUP: \n      return sizeof(gf_internal_t) + sizeof(struct gf_w32_group_data) +\n               sizeof(uint32_t) * (1 << arg1) +\n               sizeof(uint32_t) * (1 << arg2) + 64;\n      break;\n    case GF_MULT_DEFAULT:\n\n    case GF_MULT_SPLIT_TABLE: \n        if (arg1 == 8 && arg2 == 8){\n          return sizeof(gf_internal_t) + sizeof(struct gf_w32_split_8_8_data) + 64;\n        }\n        if ((arg1 == 16 && arg2 == 32) || (arg2 == 16 && arg1 == 32)) {\n          return sizeof(gf_internal_t) + sizeof(struct gf_split_16_32_lazy_data) + 64;\n        }\n        if ((arg1 == 2 && arg2 == 32) || (arg2 == 2 && arg1 == 32)) {\n          return sizeof(gf_internal_t) + sizeof(struct gf_split_2_32_lazy_data) + 64;\n        }\n        if ((arg1 == 8 && arg2 == 32) || (arg2 == 8 && arg1 == 32) || \n             (mult_type == GF_MULT_DEFAULT && !(issse3 || isneon))) {\n          return sizeof(gf_internal_t) + sizeof(struct gf_split_8_32_lazy_data) + 64;\n        }\n        if ((arg1 == 4 && arg2 == 32) || \n            (arg2 == 4 && arg1 == 32) ||\n            mult_type == GF_MULT_DEFAULT) {\n          return sizeof(gf_internal_t) + sizeof(struct gf_split_4_32_lazy_data) + 64;\n        }\n        return 0;\n    case GF_MULT_CARRY_FREE:\n      return sizeof(gf_internal_t);\n      break;\n    case GF_MULT_CARRY_FREE_GK:\n      return sizeof(gf_internal_t) + sizeof(uint64_t)*2;\n      break;\n    case GF_MULT_SHIFT:\n      return sizeof(gf_internal_t);\n      break;\n    case GF_MULT_COMPOSITE:\n      return sizeof(gf_internal_t) + sizeof(struct gf_w32_composite_data) + 64;\n      break;\n\n    default:\n      return 0;\n   }\n   return 0;\n}\n\nint gf_w32_init(gf_t *gf)\n{\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  \n  /* Allen: set default primitive polynomial / irreducible polynomial if needed */\n\n  if (h->prim_poly == 0) {\n    if (h->mult_type == GF_MULT_COMPOSITE) { \n      h->prim_poly = gf_composite_get_default_poly(h->base_gf);\n      if (h->prim_poly == 0) return 0; /* This shouldn't happen */\n    } else { \n\n      /* Allen: use the following primitive polynomial to make carryless multiply work more efficiently for GF(2^32).*/\n\n      /* h->prim_poly = 0xc5; */\n\n      /* Allen: The following is the traditional primitive polynomial for GF(2^32) */\n\n      h->prim_poly = 0x400007;\n    } \n  }\n\n  /* No leading one */\n\n  if(h->mult_type != GF_MULT_COMPOSITE) h->prim_poly &= 0xffffffff;\n    \n  gf->multiply.w32 = NULL;\n  gf->divide.w32 = NULL;\n  gf->inverse.w32 = NULL;\n  gf->multiply_region.w32 = NULL;\n\n  switch(h->mult_type) {\n    case GF_MULT_CARRY_FREE:    if (gf_w32_cfm_init(gf) == 0) return 0; break;\n    case GF_MULT_CARRY_FREE_GK: if (gf_w32_cfmgk_init(gf) == 0) return 0; break;\n    case GF_MULT_SHIFT:         if (gf_w32_shift_init(gf) == 0) return 0; break;\n    case GF_MULT_COMPOSITE:     if (gf_w32_composite_init(gf) == 0) return 0; break;\n    case GF_MULT_DEFAULT: \n    case GF_MULT_SPLIT_TABLE:   if (gf_w32_split_init(gf) == 0) return 0; break;\n    case GF_MULT_GROUP:         if (gf_w32_group_init(gf) == 0) return 0; break;\n    case GF_MULT_BYTWO_p:   \n    case GF_MULT_BYTWO_b:       if (gf_w32_bytwo_init(gf) == 0) return 0; break;\n    default: return 0;\n  }\n  if (h->divide_type == GF_DIVIDE_EUCLID) {\n    gf->divide.w32 = gf_w32_divide_from_inverse;\n    gf->inverse.w32 = gf_w32_euclid;\n  } else if (h->divide_type == GF_DIVIDE_MATRIX) {\n    gf->divide.w32 = gf_w32_divide_from_inverse;\n    gf->inverse.w32 = gf_w32_matrix;\n  }\n\n  if (gf->inverse.w32 != NULL && gf->divide.w32 == NULL) {\n    gf->divide.w32 = gf_w32_divide_from_inverse;\n  }\n  if (gf->inverse.w32 == NULL && gf->divide.w32 != NULL) {\n    gf->inverse.w32 = gf_w32_inverse_from_divide;\n  }\n  if (h->region_type == GF_REGION_CAUCHY) {\n    gf->extract_word.w32 = gf_wgen_extract_word;\n    gf->multiply_region.w32 = gf_wgen_cauchy_region;\n  } else if (h->region_type & GF_REGION_ALTMAP) {\n    if (h->mult_type == GF_MULT_COMPOSITE) {\n      gf->extract_word.w32 = gf_w32_composite_extract_word;\n    } else {\n      gf->extract_word.w32 = gf_w32_split_extract_word;\n    }\n  } else {\n    gf->extract_word.w32 = gf_w32_extract_word;\n  }\n  return 1;\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/src/gf_w4.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_w4.c\n *\n * Routines for 4-bit Galois fields\n */\n\n#include \"gf_int.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include \"gf_w4.h\"\n\n#define AB2(ip, am1 ,am2, b, t1, t2) {\\\n  t1 = (b << 1) & am1;\\\n  t2 = b & am2; \\\n  t2 = ((t2 << 1) - (t2 >> (GF_FIELD_WIDTH-1))); \\\n  b = (t1 ^ (t2 & ip));}\n\n// ToDo(KMG/JSP): Why is 0x88 hard-coded?\n#define SSE_AB2(pp, m1, va, t1, t2) {\\\n          t1 = _mm_and_si128(_mm_slli_epi64(va, 1), m1); \\\n          t2 = _mm_and_si128(va, _mm_set1_epi8(0x88)); \\\n          t2 = _mm_sub_epi64 (_mm_slli_epi64(t2, 1), _mm_srli_epi64(t2, (GF_FIELD_WIDTH-1))); \\\n          va = _mm_xor_si128(t1, _mm_and_si128(t2, pp)); }\n\n/* ------------------------------------------------------------\n   JSP: These are basic and work from multiple implementations.\n */\n\nstatic\ninline\ngf_val_32_t gf_w4_inverse_from_divide (gf_t *gf, gf_val_32_t a)\n{\n  return gf->divide.w32(gf, 1, a);\n}\n\nstatic\ninline\ngf_val_32_t gf_w4_divide_from_inverse (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  b = gf->inverse.w32(gf, b);\n  return gf->multiply.w32(gf, a, b);\n}\n\nstatic\ninline\ngf_val_32_t gf_w4_euclid (gf_t *gf, gf_val_32_t b)\n{\n  gf_val_32_t e_i, e_im1, e_ip1;\n  gf_val_32_t d_i, d_im1, d_ip1;\n  gf_val_32_t y_i, y_im1, y_ip1;\n  gf_val_32_t c_i;\n\n  if (b == 0) return -1;\n  e_im1 = ((gf_internal_t *) (gf->scratch))->prim_poly;\n  e_i = b;\n  d_im1 = 4;\n  for (d_i = d_im1; ((1 << d_i) & e_i) == 0; d_i--) ;\n  y_i = 1;\n  y_im1 = 0;\n\n  while (e_i != 1) {\n    e_ip1 = e_im1;\n    d_ip1 = d_im1;\n    c_i = 0;\n\n    while (d_ip1 >= d_i) {\n      c_i ^= (1 << (d_ip1 - d_i));\n      e_ip1 ^= (e_i << (d_ip1 - d_i));\n      if (e_ip1 == 0) return 0;\n      while ((e_ip1 & (1 << d_ip1)) == 0) d_ip1--;\n    }\n\n    y_ip1 = y_im1 ^ gf->multiply.w32(gf, c_i, y_i);\n    y_im1 = y_i;\n    y_i = y_ip1;\n\n    e_im1 = e_i;\n    d_im1 = d_i;\n    e_i = e_ip1;\n    d_i = d_ip1;\n  }\n\n  return y_i;\n}\n\nstatic \ngf_val_32_t gf_w4_extract_word(gf_t *gf, void *start, int bytes, int index)\n{\n  uint8_t *r8, v;\n\n  r8 = (uint8_t *) start;\n  v = r8[index/2];\n  if (index%2) {\n    return v >> 4;\n  } else {\n    return v&0xf;\n  }\n}\n\n\nstatic\ninline\ngf_val_32_t gf_w4_matrix (gf_t *gf, gf_val_32_t b)\n{\n  return gf_bitmatrix_inverse(b, 4, ((gf_internal_t *) (gf->scratch))->prim_poly);\n}\n\n\nstatic\ninline\ngf_val_32_t\ngf_w4_shift_multiply (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  uint8_t product, i, pp;\n  gf_internal_t *h;\n  \n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  product = 0;\n\n  for (i = 0; i < GF_FIELD_WIDTH; i++) { \n    if (a & (1 << i)) product ^= (b << i);\n  }\n  for (i = (GF_FIELD_WIDTH*2-2); i >= GF_FIELD_WIDTH; i--) {\n    if (product & (1 << i)) product ^= (pp << (i-GF_FIELD_WIDTH)); \n  }\n  return product;\n}\n\n/* Ben: This function works, but it is 33% slower than the normal shift mult */\n\nstatic\ninline\ngf_val_32_t\ngf_w4_clm_multiply (gf_t *gf, gf_val_32_t a4, gf_val_32_t b4)\n{\n  gf_val_32_t rv = 0;\n\n#if defined(INTEL_SSE4_PCLMUL)\n\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t * h = gf->scratch;\n\n  a = _mm_insert_epi32 (_mm_setzero_si128(), a4, 0);\n  b = _mm_insert_epi32 (a, b4, 0);\n\n  prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0x1fULL));\n\n  /* Do the initial multiply */\n\n  result = _mm_clmulepi64_si128 (a, b, 0);\n\n  /* Ben/JSP: Do prim_poly reduction once. We are guaranteed that we will only\n     have to do the reduction only once, because (w-2)/z == 1. Where\n     z is equal to the number of zeros after the leading 1.\n\n     _mm_clmulepi64_si128 is the carryless multiply operation. Here\n     _mm_srli_epi64 shifts the result to the right by 4 bits. This allows\n     us to multiply the prim_poly by the leading bits of the result. We\n     then xor the result of that operation back with the result. */\n\n  w = _mm_clmulepi64_si128 (prim_poly, _mm_srli_epi64 (result, 4), 0);\n  result = _mm_xor_si128 (result, w);\n\n  /* Extracts 32 bit value from result. */\n\n  rv = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n#endif\n  return rv;\n}\n\nstatic\nvoid\ngf_w4_multiply_region_from_single(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int \n    xor)\n{\n  gf_region_data rd;\n  uint8_t *s8;\n  uint8_t *d8;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 1);\n  gf_do_initial_region_alignment(&rd);\n\n  s8 = (uint8_t *) rd.s_start;\n  d8 = (uint8_t *) rd.d_start;\n\n  if (xor) {\n    while (d8 < ((uint8_t *) rd.d_top)) {\n      *d8 ^= (gf->multiply.w32(gf, val, (*s8 & 0xf)) | \n             ((gf->multiply.w32(gf, val, (*s8 >> 4))) << 4));\n      d8++;\n      s8++;\n    }\n  } else {\n    while (d8 < ((uint8_t *) rd.d_top)) {\n      *d8 = (gf->multiply.w32(gf, val, (*s8 & 0xf)) | \n             ((gf->multiply.w32(gf, val, (*s8 >> 4))) << 4));\n      d8++;\n      s8++;\n    }\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\n/* ------------------------------------------------------------\n  IMPLEMENTATION: LOG_TABLE: \n\n  JSP: This is a basic log-antilog implementation.  \n       I'm not going to spend any time optimizing it because the\n       other techniques are faster for both single and region\n       operations. \n */\n\nstatic\ninline\ngf_val_32_t\ngf_w4_log_multiply (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_logtable_data *ltd;\n    \n  ltd = (struct gf_logtable_data *) ((gf_internal_t *) (gf->scratch))->private;\n  return (a == 0 || b == 0) ? 0 : ltd->antilog_tbl[(unsigned)(ltd->log_tbl[a] + ltd->log_tbl[b])];\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w4_log_divide (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  int log_sum = 0;\n  struct gf_logtable_data *ltd;\n    \n  if (a == 0 || b == 0) return 0;\n  ltd = (struct gf_logtable_data *) ((gf_internal_t *) (gf->scratch))->private;\n\n  log_sum = ltd->log_tbl[a] - ltd->log_tbl[b];\n  return (ltd->antilog_tbl_div[log_sum]);\n}\n\nstatic\nvoid \ngf_w4_log_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  int i;\n  uint8_t lv, b, c;\n  uint8_t *s8, *d8;\n  \n  struct gf_logtable_data *ltd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  ltd = (struct gf_logtable_data *) ((gf_internal_t *) (gf->scratch))->private;\n  s8 = (uint8_t *) src;\n  d8 = (uint8_t *) dest;\n\n  lv = ltd->log_tbl[val];\n\n  for (i = 0; i < bytes; i++) {\n    c = (xor) ? d8[i] : 0;\n    b = (s8[i] >> GF_FIELD_WIDTH);\n    c ^= (b == 0) ? 0 : (ltd->antilog_tbl[lv + ltd->log_tbl[b]] << GF_FIELD_WIDTH);\n    b = (s8[i] & 0xf);\n    c ^= (b == 0) ? 0 : ltd->antilog_tbl[lv + ltd->log_tbl[b]];\n    d8[i] = c;\n  }\n}\n\nstatic \nint gf_w4_log_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  struct gf_logtable_data *ltd;\n  int i, b;\n\n  h = (gf_internal_t *) gf->scratch;\n  ltd = h->private;\n\n  for (i = 0; i < GF_FIELD_SIZE; i++)\n    ltd->log_tbl[i]=0;\n\n  ltd->antilog_tbl_div = ltd->antilog_tbl + (GF_FIELD_SIZE-1);\n  b = 1;\n  i = 0;\n  do {\n    if (ltd->log_tbl[b] != 0 && i != 0) {\n      fprintf(stderr, \"Cannot construct log table: Polynomial is not primitive.\\n\\n\");\n      return 0;\n    }\n    ltd->log_tbl[b] = i;\n    ltd->antilog_tbl[i] = b;\n    ltd->antilog_tbl[i+GF_FIELD_SIZE-1] = b;\n    b <<= 1;\n    i++;\n    if (b & GF_FIELD_SIZE) b = b ^ h->prim_poly;\n  } while (b != 1);\n\n  if (i != GF_FIELD_SIZE - 1) {\n    _gf_errno = GF_E_LOGPOLY;\n    return 0;\n  }\n    \n  gf->inverse.w32 = gf_w4_inverse_from_divide;\n  gf->divide.w32 = gf_w4_log_divide;\n  gf->multiply.w32 = gf_w4_log_multiply;\n  gf->multiply_region.w32 = gf_w4_log_multiply_region;\n  return 1;\n}\n\n/* ------------------------------------------------------------\n  IMPLEMENTATION: SINGLE TABLE: JSP. \n */\n\nstatic\ninline\ngf_val_32_t\ngf_w4_single_table_multiply (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_single_table_data *std;\n    \n  std = (struct gf_single_table_data *) ((gf_internal_t *) (gf->scratch))->private;\n  return std->mult[a][b];\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w4_single_table_divide (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_single_table_data *std;\n    \n  std = (struct gf_single_table_data *) ((gf_internal_t *) (gf->scratch))->private;\n  return std->div[a][b];\n}\n\nstatic\nvoid \ngf_w4_single_table_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  int i;\n  uint8_t b, c;\n  uint8_t *s8, *d8;\n  \n  struct gf_single_table_data *std;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  std = (struct gf_single_table_data *) ((gf_internal_t *) (gf->scratch))->private;\n  s8 = (uint8_t *) src;\n  d8 = (uint8_t *) dest;\n\n  for (i = 0; i < bytes; i++) {\n    c = (xor) ? d8[i] : 0;\n    b = (s8[i] >> GF_FIELD_WIDTH);\n    c ^= (std->mult[val][b] << GF_FIELD_WIDTH);\n    b = (s8[i] & 0xf);\n    c ^= (std->mult[val][b]);\n    d8[i] = c;\n  }\n}\n\n#define MM_PRINT(s, r) { uint8_t blah[16]; printf(\"%-12s\", s); _mm_storeu_si128((__m128i *)blah, r); for (i = 0; i < 16; i++) printf(\" %02x\", blah[i]); printf(\"\\n\"); }\n\n#ifdef INTEL_SSSE3\nstatic\nvoid \ngf_w4_single_table_sse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  gf_region_data rd;\n  uint8_t *base, *sptr, *dptr, *top;\n  __m128i  tl, loset, r, va, th;\n  \n  struct gf_single_table_data *std;\n    \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n\n  std = (struct gf_single_table_data *) ((gf_internal_t *) (gf->scratch))->private;\n  base = (uint8_t *) std->mult;\n  base += (val << GF_FIELD_WIDTH);\n\n  gf_do_initial_region_alignment(&rd);\n\n  tl = _mm_loadu_si128((__m128i *)base);\n  th = _mm_slli_epi64(tl, 4);\n  loset = _mm_set1_epi8 (0x0f);\n\n  sptr = rd.s_start;\n  dptr = rd.d_start;\n  top = rd.s_top;\n\n  while (sptr < (uint8_t *) top) {\n    va = _mm_load_si128 ((__m128i *)(sptr));\n    r = _mm_and_si128 (loset, va);\n    r = _mm_shuffle_epi8 (tl, r);\n    va = _mm_srli_epi64 (va, 4);\n    va = _mm_and_si128 (loset, va);\n    va = _mm_shuffle_epi8 (th, va);\n    r = _mm_xor_si128 (r, va);\n    va = (xor) ? _mm_load_si128 ((__m128i *)(dptr)) : _mm_setzero_si128(); \n    r = _mm_xor_si128 (r, va);\n    _mm_store_si128 ((__m128i *)(dptr), r);\n    dptr += 16;\n    sptr += 16;\n  }\n  gf_do_final_region_alignment(&rd);\n\n}\n#endif\n\nstatic \nint gf_w4_single_table_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  struct gf_single_table_data *std;\n  int a, b, prod;\n\n\n  h = (gf_internal_t *) gf->scratch;\n  std = (struct gf_single_table_data *)h->private;\n\n  bzero(std->mult, sizeof(uint8_t) * GF_FIELD_SIZE * GF_FIELD_SIZE);\n  bzero(std->div, sizeof(uint8_t) * GF_FIELD_SIZE * GF_FIELD_SIZE);\n\n  for (a = 1; a < GF_FIELD_SIZE; a++) {\n    for (b = 1; b < GF_FIELD_SIZE; b++) {\n      prod = gf_w4_shift_multiply(gf, a, b);\n      std->mult[a][b] = prod;\n      std->div[prod][b] = a;\n    }\n  }\n\n  gf->inverse.w32 = NULL;\n  gf->divide.w32 = gf_w4_single_table_divide;\n  gf->multiply.w32 = gf_w4_single_table_multiply;\n  #if defined(INTEL_SSSE3) || defined(ARM_NEON)\n    if(h->region_type & (GF_REGION_NOSIMD | GF_REGION_CAUCHY))\n      gf->multiply_region.w32 = gf_w4_single_table_multiply_region;\n    else\n    #if defined(INTEL_SSSE3)\n      gf->multiply_region.w32 = gf_w4_single_table_sse_multiply_region;\n    #elif defined(ARM_NEON)\n      gf_w4_neon_single_table_init(gf);\n    #endif\n  #else\n    gf->multiply_region.w32 = gf_w4_single_table_multiply_region;\n    if (h->region_type & GF_REGION_SIMD) return 0;\n  #endif\n\n  return 1;\n}\n\n/* ------------------------------------------------------------\n  IMPLEMENTATION: DOUBLE TABLE: JSP. \n */\n\nstatic\ninline\ngf_val_32_t\ngf_w4_double_table_multiply (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_double_table_data *std;\n    \n  std = (struct gf_double_table_data *) ((gf_internal_t *) (gf->scratch))->private;\n  return std->mult[a][b];\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w4_double_table_divide (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_double_table_data *std;\n    \n  std = (struct gf_double_table_data *) ((gf_internal_t *) (gf->scratch))->private;\n  return std->div[a][b];\n}\n\nstatic\nvoid \ngf_w4_double_table_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  int i;\n  uint8_t *s8, *d8, *base;\n  gf_region_data rd;\n  struct gf_double_table_data *std;\n    \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 8);\n\n  std = (struct gf_double_table_data *) ((gf_internal_t *) (gf->scratch))->private;\n  s8 = (uint8_t *) src;\n  d8 = (uint8_t *) dest;\n  base = (uint8_t *) std->mult;\n  base += (val << GF_DOUBLE_WIDTH);\n\n  if (xor) {\n    for (i = 0; i < bytes; i++) d8[i] ^= base[s8[i]];\n  } else {\n    for (i = 0; i < bytes; i++) d8[i] = base[s8[i]];\n  }\n}\n\nstatic \nint gf_w4_double_table_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  struct gf_double_table_data *std;\n  int a, b, c, prod, ab;\n  uint8_t mult[GF_FIELD_SIZE][GF_FIELD_SIZE];\n\n  h = (gf_internal_t *) gf->scratch;\n  std = (struct gf_double_table_data *)h->private;\n\n  bzero(mult, sizeof(uint8_t) * GF_FIELD_SIZE * GF_FIELD_SIZE);\n  bzero(std->div, sizeof(uint8_t) * GF_FIELD_SIZE * GF_FIELD_SIZE);\n\n  for (a = 1; a < GF_FIELD_SIZE; a++) {\n    for (b = 1; b < GF_FIELD_SIZE; b++) {\n      prod = gf_w4_shift_multiply(gf, a, b);\n      mult[a][b] = prod;\n      std->div[prod][b] = a;\n    }\n  }\n  bzero(std->mult, sizeof(uint8_t) * GF_FIELD_SIZE * GF_FIELD_SIZE * GF_FIELD_SIZE);\n  for (a = 0; a < GF_FIELD_SIZE; a++) {\n    for (b = 0; b < GF_FIELD_SIZE; b++) {\n      ab = mult[a][b];\n      for (c = 0; c < GF_FIELD_SIZE; c++) {\n        std->mult[a][(b << 4) | c] = ((ab << 4) | mult[a][c]);\n      }\n    }\n  }\n\n  gf->inverse.w32 = NULL;\n  gf->divide.w32 = gf_w4_double_table_divide;\n  gf->multiply.w32 = gf_w4_double_table_multiply;\n  gf->multiply_region.w32 = gf_w4_double_table_multiply_region;\n  return 1;\n}\n\n\nstatic\ninline\ngf_val_32_t\ngf_w4_quad_table_lazy_divide (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_quad_table_lazy_data *std;\n    \n  std = (struct gf_quad_table_lazy_data *) ((gf_internal_t *) (gf->scratch))->private;\n  return std->div[a][b];\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w4_quad_table_lazy_multiply (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_quad_table_lazy_data *std;\n    \n  std = (struct gf_quad_table_lazy_data *) ((gf_internal_t *) (gf->scratch))->private;\n  return std->smult[a][b];\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w4_quad_table_divide (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_quad_table_data *std;\n    \n  std = (struct gf_quad_table_data *) ((gf_internal_t *) (gf->scratch))->private;\n  return std->div[a][b];\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w4_quad_table_multiply (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_quad_table_data *std;\n  uint16_t v;\n    \n  std = (struct gf_quad_table_data *) ((gf_internal_t *) (gf->scratch))->private;\n  v = std->mult[a][b];\n  return v;\n}\n\nstatic\nvoid \ngf_w4_quad_table_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint16_t *base;\n  gf_region_data rd;\n  struct gf_quad_table_data *std;\n  struct gf_quad_table_lazy_data *ltd;\n  gf_internal_t *h;\n  int a, b, c, d, va, vb, vc, vd;\n    \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  h = (gf_internal_t *) (gf->scratch);\n  if (h->region_type & GF_REGION_LAZY) {\n    ltd = (struct gf_quad_table_lazy_data *) ((gf_internal_t *) (gf->scratch))->private;\n    base = ltd->mult;\n    for (a = 0; a < 16; a++) {\n      va = (ltd->smult[val][a] << 12);\n      for (b = 0; b < 16; b++) {\n        vb = (ltd->smult[val][b] << 8);\n        for (c = 0; c < 16; c++) {\n          vc = (ltd->smult[val][c] << 4);\n          for (d = 0; d < 16; d++) {\n            vd = ltd->smult[val][d];\n            base[(a << 12) | (b << 8) | (c << 4) | d ] = (va | vb | vc | vd);\n          }\n        }\n      }\n    }\n  } else {\n    std = (struct gf_quad_table_data *) ((gf_internal_t *) (gf->scratch))->private;\n    base = &(std->mult[val][0]);\n  }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 8);\n  gf_do_initial_region_alignment(&rd);\n  gf_two_byte_region_table_multiply(&rd, base);\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic \nint gf_w4_quad_table_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  struct gf_quad_table_data *std;\n  int prod, val, a, b, c, d, va, vb, vc, vd;\n  uint8_t mult[GF_FIELD_SIZE][GF_FIELD_SIZE];\n\n  h = (gf_internal_t *) gf->scratch;\n  std = (struct gf_quad_table_data *)h->private;\n\n  bzero(mult, sizeof(uint8_t) * GF_FIELD_SIZE * GF_FIELD_SIZE);\n  bzero(std->div, sizeof(uint8_t) * GF_FIELD_SIZE * GF_FIELD_SIZE);\n\n  for (a = 1; a < GF_FIELD_SIZE; a++) {\n    for (b = 1; b < GF_FIELD_SIZE; b++) {\n      prod = gf_w4_shift_multiply(gf, a, b);\n      mult[a][b] = prod;\n      std->div[prod][b] = a;\n    }\n  }\n\n  for (val = 0; val < 16; val++) {\n    for (a = 0; a < 16; a++) {\n      va = (mult[val][a] << 12);\n      for (b = 0; b < 16; b++) {\n        vb = (mult[val][b] << 8);\n        for (c = 0; c < 16; c++) {\n          vc = (mult[val][c] << 4);\n          for (d = 0; d < 16; d++) {\n            vd = mult[val][d];\n            std->mult[val][(a << 12) | (b << 8) | (c << 4) | d ] = (va | vb | vc | vd);\n          }\n        }\n      }\n    }\n  }\n\n  gf->inverse.w32 = NULL;\n  gf->divide.w32 = gf_w4_quad_table_divide;\n  gf->multiply.w32 = gf_w4_quad_table_multiply;\n  gf->multiply_region.w32 = gf_w4_quad_table_multiply_region;\n  return 1;\n}\nstatic \nint gf_w4_quad_table_lazy_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  struct gf_quad_table_lazy_data *std;\n  int a, b, prod, loga, logb;\n  uint8_t log_tbl[GF_FIELD_SIZE];\n  uint8_t antilog_tbl[GF_FIELD_SIZE*2];\n\n  h = (gf_internal_t *) gf->scratch;\n  std = (struct gf_quad_table_lazy_data *)h->private;\n\n  b = 1;\n  for (a = 0; a < GF_MULT_GROUP_SIZE; a++) {\n      log_tbl[b] = a;\n      antilog_tbl[a] = b;\n      antilog_tbl[a+GF_MULT_GROUP_SIZE] = b;\n      b <<= 1;\n      if (b & GF_FIELD_SIZE) {\n          b = b ^ h->prim_poly;\n      }\n  }\n\n  bzero(std->smult, sizeof(uint8_t) * GF_FIELD_SIZE * GF_FIELD_SIZE);\n  bzero(std->div, sizeof(uint8_t) * GF_FIELD_SIZE * GF_FIELD_SIZE);\n\n  for (a = 1; a < GF_FIELD_SIZE; a++) {\n    loga = log_tbl[a];\n    for (b = 1; b < GF_FIELD_SIZE; b++) {\n      logb = log_tbl[b];\n      prod = antilog_tbl[loga+logb];\n      std->smult[a][b] = prod;\n      std->div[prod][b] = a;\n    }\n  }\n\n  gf->inverse.w32 = NULL;\n  gf->divide.w32 = gf_w4_quad_table_lazy_divide;\n  gf->multiply.w32 = gf_w4_quad_table_lazy_multiply;\n  gf->multiply_region.w32 = gf_w4_quad_table_multiply_region;\n  return 1;\n}\n\nstatic \nint gf_w4_table_init(gf_t *gf)\n{\n  int rt;\n  gf_internal_t *h;\n  int simd = 0;\n\n#if defined(INTEL_SSSE3) || defined(ARM_NEON)\n  simd = 1;\n#endif\n\n  h = (gf_internal_t *) gf->scratch;\n  rt = (h->region_type);\n\n  if (h->mult_type == GF_MULT_DEFAULT && !simd) rt |= GF_REGION_DOUBLE_TABLE;\n\n  if (rt & GF_REGION_DOUBLE_TABLE) {\n    return gf_w4_double_table_init(gf);\n  } else if (rt & GF_REGION_QUAD_TABLE) {\n    if (rt & GF_REGION_LAZY) {\n      return gf_w4_quad_table_lazy_init(gf);\n    } else {\n      return gf_w4_quad_table_init(gf);\n    }\n  } else {\n    return gf_w4_single_table_init(gf);\n  }\n  return 0;\n}\n\n/* ------------------------------------------------------------\n   JSP: GF_MULT_BYTWO_p and _b: See the paper.\n*/\n\nstatic\ninline\ngf_val_32_t\ngf_w4_bytwo_p_multiply (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  uint32_t prod, pp, pmask, amask;\n  gf_internal_t *h;\n  \n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  \n  prod = 0;\n  pmask = 0x8;\n  amask = 0x8;\n\n  while (amask != 0) {\n    if (prod & pmask) {\n      prod = ((prod << 1) ^ pp);\n    } else {\n      prod <<= 1;\n    }\n    if (a & amask) prod ^= b;\n    amask >>= 1;\n  }\n  return prod;\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w4_bytwo_b_multiply (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  uint32_t prod, pp, bmask;\n  gf_internal_t *h;\n  \n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  prod = 0;\n  bmask = 0x8;\n\n  while (1) {\n    if (a & 1) prod ^= b;\n    a >>= 1;\n    if (a == 0) return prod;\n    if (b & bmask) {\n      b = ((b << 1) ^ pp);\n    } else {\n      b <<= 1;\n    }\n  }\n}\n\nstatic\nvoid \ngf_w4_bytwo_p_nosse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint64_t *s64, *d64, t1, t2, ta, prod, amask;\n  gf_region_data rd;\n  struct gf_bytwo_data *btd;\n    \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  btd = (struct gf_bytwo_data *) ((gf_internal_t *) (gf->scratch))->private;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 8);\n  gf_do_initial_region_alignment(&rd);\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n\n  if (xor) {\n    while (s64 < (uint64_t *) rd.s_top) {\n      prod = 0;\n      amask = 0x8;\n      ta = *s64;\n      while (amask != 0) {\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, prod, t1, t2);\n        if (val & amask) prod ^= ta;\n        amask >>= 1;\n      }\n      *d64 ^= prod;\n      d64++;\n      s64++;\n    }\n  } else { \n    while (s64 < (uint64_t *) rd.s_top) {\n      prod = 0;\n      amask = 0x8;\n      ta = *s64;\n      while (amask != 0) {\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, prod, t1, t2);\n        if (val & amask) prod ^= ta;\n        amask >>= 1;\n      }\n      *d64 = prod;\n      d64++;\n      s64++;\n    }\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\n#define BYTWO_P_ONESTEP {\\\n      SSE_AB2(pp, m1, prod, t1, t2); \\\n      t1 = _mm_and_si128(v, one); \\\n      t1 = _mm_sub_epi8(t1, one); \\\n      t1 = _mm_and_si128(t1, ta); \\\n      prod = _mm_xor_si128(prod, t1); \\\n      v = _mm_srli_epi64(v, 1); }\n\n#ifdef INTEL_SSE2\nstatic\nvoid\ngf_w4_bytwo_p_sse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  int i;\n  uint8_t *s8, *d8;\n  uint8_t vrev;\n  __m128i pp, m1, ta, prod, t1, t2, tp, one, v;\n  struct gf_bytwo_data *btd;\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  btd = (struct gf_bytwo_data *) ((gf_internal_t *) (gf->scratch))->private;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n\n  vrev = 0;\n  for (i = 0; i < 4; i++) {\n    vrev <<= 1;\n    if (!(val & (1 << i))) vrev |= 1;\n  }\n\n  s8 = (uint8_t *) rd.s_start;\n  d8 = (uint8_t *) rd.d_start;\n\n  pp = _mm_set1_epi8(btd->prim_poly&0xff);\n  m1 = _mm_set1_epi8((btd->mask1)&0xff);\n  one = _mm_set1_epi8(1);\n\n  while (d8 < (uint8_t *) rd.d_top) {\n    prod = _mm_setzero_si128();\n    v = _mm_set1_epi8(vrev);\n    ta = _mm_load_si128((__m128i *) s8);\n    tp = (!xor) ? _mm_setzero_si128() : _mm_load_si128((__m128i *) d8);\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    _mm_store_si128((__m128i *) d8, _mm_xor_si128(prod, tp));\n    d8 += 16;\n    s8 += 16;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\n/*\nstatic\nvoid \ngf_w4_bytwo_b_sse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n#ifdef INTEL_SSE2\n  uint8_t *d8, *s8, tb;\n  __m128i pp, m1, m2, t1, t2, va, vb;\n  struct gf_bytwo_data *btd;\n  gf_region_data rd;\n    \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n\n  s8 = (uint8_t *) rd.s_start;\n  d8 = (uint8_t *) rd.d_start;\n\n  btd = (struct gf_bytwo_data *) ((gf_internal_t *) (gf->scratch))->private;\n\n  pp = _mm_set1_epi8(btd->prim_poly&0xff);\n  m1 = _mm_set1_epi8((btd->mask1)&0xff);\n  m2 = _mm_set1_epi8((btd->mask2)&0xff);\n\n  if (xor) {\n    while (d8 < (uint8_t *) rd.d_top) {\n      va = _mm_load_si128 ((__m128i *)(s8));\n      vb = _mm_load_si128 ((__m128i *)(d8));\n      tb = val;\n      while (1) {\n        if (tb & 1) vb = _mm_xor_si128(vb, va);\n        tb >>= 1;\n        if (tb == 0) break;\n        SSE_AB2(pp, m1, m2, va, t1, t2);\n      }\n      _mm_store_si128((__m128i *)d8, vb);\n      d8 += 16;\n      s8 += 16;\n    }\n  } else {\n    while (d8 < (uint8_t *) rd.d_top) {\n      va = _mm_load_si128 ((__m128i *)(s8));\n      vb = _mm_setzero_si128 ();\n      tb = val;\n      while (1) {\n        if (tb & 1) vb = _mm_xor_si128(vb, va);\n        tb >>= 1;\n        if (tb == 0) break;\n        t1 = _mm_and_si128(_mm_slli_epi64(va, 1), m1);\n        t2 = _mm_and_si128(va, m2);\n        t2 = _mm_sub_epi64 (\n          _mm_slli_epi64(t2, 1), _mm_srli_epi64(t2, (GF_FIELD_WIDTH-1)));\n        va = _mm_xor_si128(t1, _mm_and_si128(t2, pp));\n      }\n      _mm_store_si128((__m128i *)d8, vb);\n      d8 += 16;\n      s8 += 16;\n    }\n  }\n  gf_do_final_region_alignment(&rd);\n#endif\n}\n*/\n\n#ifdef INTEL_SSE2\nstatic \nvoid\ngf_w4_bytwo_b_sse_region_2_noxor(gf_region_data *rd, struct gf_bytwo_data *btd)\n{\n  uint8_t *d8, *s8;\n  __m128i pp, m1, t1, t2, va;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  pp = _mm_set1_epi8(btd->prim_poly&0xff);\n  m1 = _mm_set1_epi8((btd->mask1)&0xff);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    SSE_AB2(pp, m1, va, t1, t2);\n    _mm_store_si128((__m128i *)d8, va);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic \nvoid\ngf_w4_bytwo_b_sse_region_2_xor(gf_region_data *rd, struct gf_bytwo_data *btd)\n{\n  uint8_t *d8, *s8;\n  __m128i pp, m1, t1, t2, va, vb;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  pp = _mm_set1_epi8(btd->prim_poly&0xff);\n  m1 = _mm_set1_epi8((btd->mask1)&0xff);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    SSE_AB2(pp, m1, va, t1, t2);\n    vb = _mm_load_si128 ((__m128i *)(d8));\n    vb = _mm_xor_si128(vb, va);\n    _mm_store_si128((__m128i *)d8, vb);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic \nvoid\ngf_w4_bytwo_b_sse_region_4_noxor(gf_region_data *rd, struct gf_bytwo_data *btd)\n{\n  uint8_t *d8, *s8;\n  __m128i pp, m1, t1, t2, va;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  pp = _mm_set1_epi8(btd->prim_poly&0xff);\n  m1 = _mm_set1_epi8((btd->mask1)&0xff);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    SSE_AB2(pp, m1, va, t1, t2);\n    SSE_AB2(pp, m1, va, t1, t2);\n    _mm_store_si128((__m128i *)d8, va);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic \nvoid\ngf_w4_bytwo_b_sse_region_4_xor(gf_region_data *rd, struct gf_bytwo_data *btd)\n{\n  uint8_t *d8, *s8;\n  __m128i pp, m1, t1, t2, va, vb;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  pp = _mm_set1_epi8(btd->prim_poly&0xff);\n  m1 = _mm_set1_epi8((btd->mask1)&0xff);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    SSE_AB2(pp, m1, va, t1, t2);\n    SSE_AB2(pp, m1, va, t1, t2);\n    vb = _mm_load_si128 ((__m128i *)(d8));\n    vb = _mm_xor_si128(vb, va);\n    _mm_store_si128((__m128i *)d8, vb);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n\n#ifdef INTEL_SSE2\nstatic \nvoid\ngf_w4_bytwo_b_sse_region_3_noxor(gf_region_data *rd, struct gf_bytwo_data *btd)\n{\n  uint8_t *d8, *s8;\n  __m128i pp, m1, t1, t2, va, vb;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  pp = _mm_set1_epi8(btd->prim_poly&0xff);\n  m1 = _mm_set1_epi8((btd->mask1)&0xff);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    vb = va;\n    SSE_AB2(pp, m1, va, t1, t2);\n    va = _mm_xor_si128(va, vb);\n    _mm_store_si128((__m128i *)d8, va);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic \nvoid\ngf_w4_bytwo_b_sse_region_3_xor(gf_region_data *rd, struct gf_bytwo_data *btd)\n{\n  uint8_t *d8, *s8;\n  __m128i pp, m1, t1, t2, va, vb;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  pp = _mm_set1_epi8(btd->prim_poly&0xff);\n  m1 = _mm_set1_epi8((btd->mask1)&0xff);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    vb = _mm_xor_si128(_mm_load_si128 ((__m128i *)(d8)), va);\n    SSE_AB2(pp, m1, va, t1, t2);\n    vb = _mm_xor_si128(vb, va);\n    _mm_store_si128((__m128i *)d8, vb);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic \nvoid\ngf_w4_bytwo_b_sse_region_5_noxor(gf_region_data *rd, struct gf_bytwo_data *btd)\n{\n  uint8_t *d8, *s8;\n  __m128i pp, m1, t1, t2, va, vb;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  pp = _mm_set1_epi8(btd->prim_poly&0xff);\n  m1 = _mm_set1_epi8((btd->mask1)&0xff);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    vb = va;\n    SSE_AB2(pp, m1, va, t1, t2);\n    SSE_AB2(pp, m1, va, t1, t2);\n    va = _mm_xor_si128(va, vb);\n    _mm_store_si128((__m128i *)d8, va);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic \nvoid\ngf_w4_bytwo_b_sse_region_5_xor(gf_region_data *rd, struct gf_bytwo_data *btd)\n{\n  uint8_t *d8, *s8;\n  __m128i pp, m1, t1, t2, va, vb;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  pp = _mm_set1_epi8(btd->prim_poly&0xff);\n  m1 = _mm_set1_epi8((btd->mask1)&0xff);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    vb = _mm_xor_si128(_mm_load_si128 ((__m128i *)(d8)), va);\n    SSE_AB2(pp, m1, va, t1, t2);\n    SSE_AB2(pp, m1, va, t1, t2);\n    vb = _mm_xor_si128(vb, va);\n    _mm_store_si128((__m128i *)d8, vb);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic \nvoid\ngf_w4_bytwo_b_sse_region_7_noxor(gf_region_data *rd, struct gf_bytwo_data *btd)\n{\n  uint8_t *d8, *s8;\n  __m128i pp, m1, t1, t2, va, vb;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  pp = _mm_set1_epi8(btd->prim_poly&0xff);\n  m1 = _mm_set1_epi8((btd->mask1)&0xff);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    vb = va;\n    SSE_AB2(pp, m1, va, t1, t2);\n    vb = _mm_xor_si128(va, vb);\n    SSE_AB2(pp, m1, va, t1, t2);\n    va = _mm_xor_si128(va, vb);\n    _mm_store_si128((__m128i *)d8, va);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic \nvoid\ngf_w4_bytwo_b_sse_region_7_xor(gf_region_data *rd, struct gf_bytwo_data *btd)\n{\n  uint8_t *d8, *s8;\n  __m128i pp, m1, t1, t2, va, vb;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  pp = _mm_set1_epi8(btd->prim_poly&0xff);\n  m1 = _mm_set1_epi8((btd->mask1)&0xff);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    vb = _mm_xor_si128(_mm_load_si128 ((__m128i *)(d8)), va);\n    SSE_AB2(pp, m1, va, t1, t2);\n    vb = _mm_xor_si128(vb, va);\n    SSE_AB2(pp, m1, va, t1, t2);\n    vb = _mm_xor_si128(vb, va);\n    _mm_store_si128((__m128i *)d8, vb);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic \nvoid\ngf_w4_bytwo_b_sse_region_6_noxor(gf_region_data *rd, struct gf_bytwo_data *btd)\n{\n  uint8_t *d8, *s8;\n  __m128i pp, m1, t1, t2, va, vb;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  pp = _mm_set1_epi8(btd->prim_poly&0xff);\n  m1 = _mm_set1_epi8((btd->mask1)&0xff);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    SSE_AB2(pp, m1, va, t1, t2);\n    vb = va;\n    SSE_AB2(pp, m1, va, t1, t2);\n    va = _mm_xor_si128(va, vb);\n    _mm_store_si128((__m128i *)d8, va);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic \nvoid\ngf_w4_bytwo_b_sse_region_6_xor(gf_region_data *rd, struct gf_bytwo_data *btd)\n{\n  uint8_t *d8, *s8;\n  __m128i pp, m1, t1, t2, va, vb;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  pp = _mm_set1_epi8(btd->prim_poly&0xff);\n  m1 = _mm_set1_epi8((btd->mask1)&0xff);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    SSE_AB2(pp, m1, va, t1, t2);\n    vb = _mm_xor_si128(_mm_load_si128 ((__m128i *)(d8)), va);\n    SSE_AB2(pp, m1, va, t1, t2);\n    vb = _mm_xor_si128(vb, va);\n    _mm_store_si128((__m128i *)d8, vb);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic\nvoid \ngf_w4_bytwo_b_sse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint8_t *d8, *s8, tb;\n  __m128i pp, m1, m2, t1, t2, va, vb;\n  struct gf_bytwo_data *btd;\n  gf_region_data rd;\n    \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n\n  s8 = (uint8_t *) rd.s_start;\n  d8 = (uint8_t *) rd.d_start;\n\n  btd = (struct gf_bytwo_data *) ((gf_internal_t *) (gf->scratch))->private;\n\n  switch (val) {\n    case 2:\n      if (!xor) {\n        gf_w4_bytwo_b_sse_region_2_noxor(&rd, btd);\n      } else {\n        gf_w4_bytwo_b_sse_region_2_xor(&rd, btd);\n      }\n      gf_do_final_region_alignment(&rd);\n      return;\n    case 3:\n      if (!xor) {\n        gf_w4_bytwo_b_sse_region_3_noxor(&rd, btd);\n      } else {\n        gf_w4_bytwo_b_sse_region_3_xor(&rd, btd);\n      }\n      gf_do_final_region_alignment(&rd);\n      return;\n    case 4:\n      if (!xor) {\n        gf_w4_bytwo_b_sse_region_4_noxor(&rd, btd);\n      } else {\n        gf_w4_bytwo_b_sse_region_4_xor(&rd, btd);\n      }\n      gf_do_final_region_alignment(&rd);\n      return;\n    case 5:\n      if (!xor) {\n        gf_w4_bytwo_b_sse_region_5_noxor(&rd, btd);\n      } else {\n        gf_w4_bytwo_b_sse_region_5_xor(&rd, btd);\n      }\n      gf_do_final_region_alignment(&rd);\n      return;\n    case 6:\n      if (!xor) {\n        gf_w4_bytwo_b_sse_region_6_noxor(&rd, btd);\n      } else {\n        gf_w4_bytwo_b_sse_region_6_xor(&rd, btd);\n      }\n      gf_do_final_region_alignment(&rd);\n      return;\n    case 7:\n      if (!xor) {\n        gf_w4_bytwo_b_sse_region_7_noxor(&rd, btd);\n      } else {\n        gf_w4_bytwo_b_sse_region_7_xor(&rd, btd);\n      }\n      gf_do_final_region_alignment(&rd);\n      return;\n  }\n\n  pp = _mm_set1_epi8(btd->prim_poly&0xff);\n  m1 = _mm_set1_epi8((btd->mask1)&0xff);\n  m2 = _mm_set1_epi8((btd->mask2)&0xff);\n\n  if (xor) {\n    while (d8 < (uint8_t *) rd.d_top) {\n      va = _mm_load_si128 ((__m128i *)(s8));\n      vb = _mm_load_si128 ((__m128i *)(d8));\n      tb = val;\n      while (1) {\n        if (tb & 1) vb = _mm_xor_si128(vb, va);\n        tb >>= 1;\n        if (tb == 0) break;\n        SSE_AB2(pp, m1, va, t1, t2);\n      }\n      _mm_store_si128((__m128i *)d8, vb);\n      d8 += 16;\n      s8 += 16;\n    }\n  } else {\n    while (d8 < (uint8_t *) rd.d_top) {\n      va = _mm_load_si128 ((__m128i *)(s8));\n      vb = _mm_setzero_si128 ();\n      tb = val;\n      while (1) {\n        if (tb & 1) vb = _mm_xor_si128(vb, va);\n        tb >>= 1;\n        if (tb == 0) break;\n        t1 = _mm_and_si128(_mm_slli_epi64(va, 1), m1);\n        t2 = _mm_and_si128(va, m2);\n        t2 = _mm_sub_epi64 (\n          _mm_slli_epi64(t2, 1), _mm_srli_epi64(t2, (GF_FIELD_WIDTH-1)));\n        va = _mm_xor_si128(t1, _mm_and_si128(t2, pp));\n      }\n      _mm_store_si128((__m128i *)d8, vb);\n      d8 += 16;\n      s8 += 16;\n    }\n  }\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\nstatic\nvoid \ngf_w4_bytwo_b_nosse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint64_t *s64, *d64, t1, t2, ta, tb, prod;\n  struct gf_bytwo_data *btd;\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n\n  btd = (struct gf_bytwo_data *) ((gf_internal_t *) (gf->scratch))->private;\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n\n  switch (val) {\n  case 1:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        *d64 ^= *s64;\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        *d64 = *s64;\n        d64++;\n        s64++;\n      }\n    }\n    break;\n  case 2:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= ta;\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = ta;\n        d64++;\n        s64++;\n      }\n    }\n    break; \n  case 3:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    }\n    break; \n  case 4:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= ta;\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = ta;\n        d64++;\n        s64++;\n      }\n    }\n    break; \n  case 5:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = ta ^ prod;\n        d64++;\n        s64++;\n      }\n    }\n    break;\n  case 6:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = ta ^ prod;\n        d64++;\n        s64++;\n      }\n    }\n    break;\n  case 7:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod ^= ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod ^= ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = ta ^ prod;\n        d64++;\n        s64++;\n      }\n    }\n    break; \n  case 8:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= ta;\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = ta;\n        d64++;\n        s64++;\n      }\n    }\n    break; \n  case 9:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    }\n    break; \n  case 10:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    }\n    break; \n  case 11:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod ^= ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod ^= ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    }\n    break; \n  case 12:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    }\n    break; \n  case 13:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod ^= ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod ^= ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    }\n    break; \n  case 14:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod ^= ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod ^= ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    }\n    break; \n  case 15:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod ^= ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod ^= ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod ^= ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod ^= ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    }\n    break; \n  default:\n    if (xor) {\n      while (d64 < (uint64_t *) rd.d_top) {\n        prod = *d64 ;\n        ta = *s64;\n        tb = val;\n        while (1) {\n          if (tb & 1) prod ^= ta;\n          tb >>= 1;\n          if (tb == 0) break;\n          AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        }\n        *d64 = prod;\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t *) rd.d_top) {\n        prod = 0 ;\n        ta = *s64;\n        tb = val;\n        while (1) {\n          if (tb & 1) prod ^= ta;\n          tb >>= 1;\n          if (tb == 0) break;\n          AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        }\n        *d64 = prod;\n        d64++;\n        s64++;\n      }\n    }\n    break;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic \nint gf_w4_bytwo_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  uint64_t ip, m1, m2;\n  struct gf_bytwo_data *btd;\n\n  h = (gf_internal_t *) gf->scratch;\n  btd = (struct gf_bytwo_data *) (h->private);\n  ip = h->prim_poly & 0xf;\n  m1 = 0xe;\n  m2 = 0x8;\n  btd->prim_poly = 0;\n  btd->mask1 = 0;\n  btd->mask2 = 0;\n\n  while (ip != 0) {\n    btd->prim_poly |= ip;\n    btd->mask1 |= m1;\n    btd->mask2 |= m2;\n    ip <<= GF_FIELD_WIDTH;\n    m1 <<= GF_FIELD_WIDTH;\n    m2 <<= GF_FIELD_WIDTH;\n  }\n\n  if (h->mult_type == GF_MULT_BYTWO_p) {\n    gf->multiply.w32 = gf_w4_bytwo_p_multiply;\n    #ifdef INTEL_SSE2\n      if (h->region_type & GF_REGION_NOSIMD)\n        gf->multiply_region.w32 = gf_w4_bytwo_p_nosse_multiply_region;\n      else\n        gf->multiply_region.w32 = gf_w4_bytwo_p_sse_multiply_region;\n    #else\n      gf->multiply_region.w32 = gf_w4_bytwo_p_nosse_multiply_region;\n      if (h->region_type & GF_REGION_SIMD)\n        return 0;\n    #endif\n  } else {\n    gf->multiply.w32 = gf_w4_bytwo_b_multiply;\n    #ifdef INTEL_SSE2\n      if (h->region_type & GF_REGION_NOSIMD)\n        gf->multiply_region.w32 = gf_w4_bytwo_b_nosse_multiply_region;\n      else\n        gf->multiply_region.w32 = gf_w4_bytwo_b_sse_multiply_region;\n    #else\n      gf->multiply_region.w32 = gf_w4_bytwo_b_nosse_multiply_region;\n      if (h->region_type & GF_REGION_SIMD)\n        return 0;\n    #endif\n  }\n  return 1;\n}\n\n\nstatic \nint gf_w4_cfm_init(gf_t *gf)\n{\n#if defined(INTEL_SSE4_PCLMUL)\n  gf->multiply.w32 = gf_w4_clm_multiply;\n  return 1;\n#elif defined(ARM_NEON)\n  return gf_w4_neon_cfm_init(gf);\n#endif\n  return 0;\n}\n\nstatic \nint gf_w4_shift_init(gf_t *gf)\n{\n  gf->multiply.w32 = gf_w4_shift_multiply;\n  return 1;\n}\n\n/* JSP: I'm putting all error-checking into gf_error_check(), so you don't \n   have to do error checking in scratch_size or in init */\n\nint gf_w4_scratch_size(int mult_type, int region_type, int divide_type, int arg1, int arg2)\n{\n  int issse3 = 0, isneon = 0;\n\n#ifdef INTEL_SSSE3\n  issse3 = 1;\n#endif\n#ifdef ARM_NEON\n  isneon = 1;\n#endif\n\n  switch(mult_type)\n  {\n    case GF_MULT_BYTWO_p:\n    case GF_MULT_BYTWO_b:\n      return sizeof(gf_internal_t) + sizeof(struct gf_bytwo_data);\n      break;\n    case GF_MULT_DEFAULT:\n    case GF_MULT_TABLE:\n      if (region_type == GF_REGION_CAUCHY) {\n        return sizeof(gf_internal_t) + sizeof(struct gf_single_table_data) + 64;\n      }\n\n      if (mult_type == GF_MULT_DEFAULT && !(issse3 || isneon))\n          region_type = GF_REGION_DOUBLE_TABLE;\n\n      if (region_type & GF_REGION_DOUBLE_TABLE) {\n        return sizeof(gf_internal_t) + sizeof(struct gf_double_table_data) + 64;\n      } else if (region_type & GF_REGION_QUAD_TABLE) {\n        if ((region_type & GF_REGION_LAZY) == 0) {\n          return sizeof(gf_internal_t) + sizeof(struct gf_quad_table_data) + 64;\n        } else {\n          return sizeof(gf_internal_t) + sizeof(struct gf_quad_table_lazy_data) + 64;\n        }\n      } else {\n        return sizeof(gf_internal_t) + sizeof(struct gf_single_table_data) + 64;\n      }\n      break;\n\n    case GF_MULT_LOG_TABLE:\n      return sizeof(gf_internal_t) + sizeof(struct gf_logtable_data) + 64;\n      break;\n    case GF_MULT_CARRY_FREE:\n      return sizeof(gf_internal_t);\n      break;\n    case GF_MULT_SHIFT:\n      return sizeof(gf_internal_t);\n      break;\n    default:\n      return 0;\n   }\n  return 0;\n}\n\nint\ngf_w4_init (gf_t *gf)\n{\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  if (h->prim_poly == 0) h->prim_poly = 0x13;\n  h->prim_poly |= 0x10;\n  gf->multiply.w32 = NULL;\n  gf->divide.w32 = NULL;\n  gf->inverse.w32 = NULL;\n  gf->multiply_region.w32 = NULL;\n  gf->extract_word.w32 = gf_w4_extract_word;\n\n  switch(h->mult_type) {\n    case GF_MULT_CARRY_FREE: if (gf_w4_cfm_init(gf) == 0) return 0; break;\n    case GF_MULT_SHIFT:      if (gf_w4_shift_init(gf) == 0) return 0; break;\n    case GF_MULT_BYTWO_p:   \n    case GF_MULT_BYTWO_b:    if (gf_w4_bytwo_init(gf) == 0) return 0; break;\n    case GF_MULT_LOG_TABLE:  if (gf_w4_log_init(gf) == 0) return 0; break;\n    case GF_MULT_DEFAULT:   \n    case GF_MULT_TABLE:      if (gf_w4_table_init(gf) == 0) return 0; break;\n    default: return 0;\n  }\n\n  if (h->divide_type == GF_DIVIDE_EUCLID) {\n    gf->divide.w32 = gf_w4_divide_from_inverse;\n    gf->inverse.w32 = gf_w4_euclid;\n  } else if (h->divide_type == GF_DIVIDE_MATRIX) {\n    gf->divide.w32 = gf_w4_divide_from_inverse;\n    gf->inverse.w32 = gf_w4_matrix;\n  }\n\n  if (gf->divide.w32 == NULL) {\n    gf->divide.w32 = gf_w4_divide_from_inverse;\n    if (gf->inverse.w32 == NULL) gf->inverse.w32 = gf_w4_euclid;\n  }\n\n  if (gf->inverse.w32 == NULL)  gf->inverse.w32 = gf_w4_inverse_from_divide;\n\n  if (h->region_type == GF_REGION_CAUCHY) {\n    gf->multiply_region.w32 = gf_wgen_cauchy_region;\n    gf->extract_word.w32 = gf_wgen_extract_word;\n  }\n\n  if (gf->multiply_region.w32 == NULL) {\n    gf->multiply_region.w32 = gf_w4_multiply_region_from_single;\n  }\n\n  return 1;\n}\n\n/* Inline setup functions */\n\nuint8_t *gf_w4_get_mult_table(gf_t *gf)\n{\n  gf_internal_t *h;\n  struct gf_single_table_data *std;\n  \n  h = (gf_internal_t *) gf->scratch;\n  if (gf->multiply.w32 == gf_w4_single_table_multiply) {\n    std = (struct gf_single_table_data *) h->private;\n    return (uint8_t *) std->mult;\n  } \n  return NULL;\n}\n    \nuint8_t *gf_w4_get_div_table(gf_t *gf)\n{\n  gf_internal_t *h;\n  struct gf_single_table_data *std;\n  \n  h = (gf_internal_t *) gf->scratch;\n  if (gf->multiply.w32 == gf_w4_single_table_multiply) {\n    std = (struct gf_single_table_data *) h->private;\n    return (uint8_t *) std->div;\n  } \n  return NULL;\n}\n\n"
  },
  {
    "path": "fst/layout/gf-complete/src/gf_w64.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_w64.c\n *\n * Routines for 64-bit Galois fields\n */\n\n#include \"gf_int.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include \"gf_w64.h\"\n\nstatic\ninline\ngf_val_64_t gf_w64_inverse_from_divide (gf_t *gf, gf_val_64_t a)\n{\n  return gf->divide.w64(gf, 1, a);\n}\n\n#define MM_PRINT8(s, r) { uint8_t blah[16], ii; printf(\"%-12s\", s); _mm_storeu_si128((__m128i *)blah, r); for (ii = 0; ii < 16; ii += 1) printf(\"%s%02x\", (ii%4==0) ? \"   \" : \" \", blah[15-ii]); printf(\"\\n\"); }\n\nstatic\ninline\ngf_val_64_t gf_w64_divide_from_inverse (gf_t *gf, gf_val_64_t a, gf_val_64_t b)\n{\n  b = gf->inverse.w64(gf, b);\n  return gf->multiply.w64(gf, a, b);\n}\n\nstatic\nvoid\ngf_w64_multiply_region_from_single(gf_t *gf, void *src, void *dest, gf_val_64_t val, int bytes, int\nxor)\n{\n  uint32_t i;\n  gf_val_64_t *s64;\n  gf_val_64_t *d64;\n\n  s64 = (gf_val_64_t *) src;\n  d64 = (gf_val_64_t *) dest;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  if (xor) {\n    for (i = 0; i < bytes/sizeof(gf_val_64_t); i++) {\n      d64[i] ^= gf->multiply.w64(gf, val, s64[i]);\n    }\n  } else {\n    for (i = 0; i < bytes/sizeof(gf_val_64_t); i++) {\n      d64[i] = gf->multiply.w64(gf, val, s64[i]);\n    }\n  }\n}\n\n#if defined(INTEL_SSE4_PCLMUL) \nstatic\nvoid\ngf_w64_clm_multiply_region_from_single_2(gf_t *gf, void *src, void *dest, gf_val_64_t val, int bytes, int\nxor)\n{\n  gf_val_64_t *s64, *d64, *top;\n  gf_region_data rd;\n\n  __m128i         a, b;\n  __m128i         result, r1;\n  __m128i         prim_poly;\n  __m128i         w;\n  __m128i         m1, m3, m4;\n  gf_internal_t * h = gf->scratch;\n  \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n  \n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n\n  prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0xffffffffULL));\n  b = _mm_insert_epi64 (_mm_setzero_si128(), val, 0);\n  m1 = _mm_set_epi32(0, 0, 0, (uint32_t)0xffffffff);\n  m3 = _mm_slli_si128(m1, 8);\n  m4 = _mm_slli_si128(m3, 4);\n\n  s64 = (gf_val_64_t *) rd.s_start;\n  d64 = (gf_val_64_t *) rd.d_start;\n  top = (gf_val_64_t *) rd.d_top;\n\n  if (xor) {\n    while (d64 != top) {\n      a = _mm_load_si128((__m128i *) s64);  \n      result = _mm_clmulepi64_si128 (a, b, 1);\n\n      w = _mm_clmulepi64_si128 (_mm_and_si128(result, m4), prim_poly, 1);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (_mm_and_si128(result, m3), prim_poly, 1);\n      r1 = _mm_xor_si128 (result, w);\n\n      result = _mm_clmulepi64_si128 (a, b, 0);\n\n      w = _mm_clmulepi64_si128 (_mm_and_si128(result, m4), prim_poly, 1);\n      result = _mm_xor_si128 (result, w);\n\n      w = _mm_clmulepi64_si128 (_mm_and_si128(result, m3), prim_poly, 1);\n      result = _mm_xor_si128 (result, w);\n\n      result = _mm_unpacklo_epi64(result, r1);\n      \n      r1 = _mm_load_si128((__m128i *) d64);\n      result = _mm_xor_si128(r1, result);\n      _mm_store_si128((__m128i *) d64, result);\n      d64 += 2;\n      s64 += 2;\n    }\n  } else {\n    while (d64 != top) {\n      \n      a = _mm_load_si128((__m128i *) s64);  \n      result = _mm_clmulepi64_si128 (a, b, 1);\n\n      w = _mm_clmulepi64_si128 (_mm_and_si128(result, m4), prim_poly, 1);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (_mm_and_si128(result, m3), prim_poly, 1);\n      r1 = _mm_xor_si128 (result, w);\n\n      result = _mm_clmulepi64_si128 (a, b, 0);\n\n      w = _mm_clmulepi64_si128 (_mm_and_si128(result, m4), prim_poly, 1);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (_mm_and_si128(result, m3), prim_poly, 1);\n      result = _mm_xor_si128 (result, w);\n      \n      result = _mm_unpacklo_epi64(result, r1);\n\n      _mm_store_si128((__m128i *) d64, result);\n      d64 += 2;\n      s64 += 2;\n    }\n  }\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\n#if defined(INTEL_SSE4_PCLMUL)\nstatic\nvoid\ngf_w64_clm_multiply_region_from_single_4(gf_t *gf, void *src, void *dest, gf_val_64_t val, int bytes, int\nxor)\n{\n  gf_val_64_t *s64, *d64, *top;\n  gf_region_data rd;\n\n  __m128i         a, b;\n  __m128i         result, r1;\n  __m128i         prim_poly;\n  __m128i         w;\n  __m128i         m1, m3, m4;\n  gf_internal_t * h = gf->scratch;\n  \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n  \n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n  \n  prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0xffffffffULL));\n  b = _mm_insert_epi64 (_mm_setzero_si128(), val, 0);\n  m1 = _mm_set_epi32(0, 0, 0, (uint32_t)0xffffffff);\n  m3 = _mm_slli_si128(m1, 8);\n  m4 = _mm_slli_si128(m3, 4);\n\n  s64 = (gf_val_64_t *) rd.s_start;\n  d64 = (gf_val_64_t *) rd.d_start;\n  top = (gf_val_64_t *) rd.d_top;\n\n  if (xor) {\n    while (d64 != top) {\n      a = _mm_load_si128((__m128i *) s64);\n      result = _mm_clmulepi64_si128 (a, b, 1);\n\n      w = _mm_clmulepi64_si128 (_mm_and_si128(result, m4), prim_poly, 1);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (_mm_and_si128(result, m3), prim_poly, 1);\n      r1 = _mm_xor_si128 (result, w);\n\n      result = _mm_clmulepi64_si128 (a, b, 0);\n\n      w = _mm_clmulepi64_si128 (_mm_and_si128(result, m4), prim_poly, 1);\n      result = _mm_xor_si128 (result, w);\n\n      w = _mm_clmulepi64_si128 (_mm_and_si128(result, m3), prim_poly, 1);\n      result = _mm_xor_si128 (result, w);\n\n      result = _mm_unpacklo_epi64(result, r1);\n\n      r1 = _mm_load_si128((__m128i *) d64);\n      result = _mm_xor_si128(r1, result);\n      _mm_store_si128((__m128i *) d64, result);\n      d64 += 2;\n      s64 += 2;\n    }\n  } else {\n    while (d64 != top) {\n      a = _mm_load_si128((__m128i *) s64);\n      result = _mm_clmulepi64_si128 (a, b, 1);\n\n      w = _mm_clmulepi64_si128 (_mm_and_si128(result, m4), prim_poly, 1);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (_mm_and_si128(result, m3), prim_poly, 1);\n      r1 = _mm_xor_si128 (result, w);\n\n      result = _mm_clmulepi64_si128 (a, b, 0);\n\n      w = _mm_clmulepi64_si128 (_mm_and_si128(result, m4), prim_poly, 1);\n      result = _mm_xor_si128 (result, w);\n      w = _mm_clmulepi64_si128 (_mm_and_si128(result, m3), prim_poly, 1);\n      result = _mm_xor_si128 (result, w);\n\n      result = _mm_unpacklo_epi64(result, r1);\n\n      _mm_store_si128((__m128i *) d64, result);\n      d64 += 2;\n      s64 += 2; \n    }\n  }\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\nstatic\n  inline\ngf_val_64_t gf_w64_euclid (gf_t *gf, gf_val_64_t b)\n{\n  gf_val_64_t e_i, e_im1, e_ip1;\n  gf_val_64_t d_i, d_im1, d_ip1;\n  gf_val_64_t y_i, y_im1, y_ip1;\n  gf_val_64_t c_i;\n  gf_val_64_t one = 1;\n\n  if (b == 0) return -1;\n  e_im1 = ((gf_internal_t *) (gf->scratch))->prim_poly;\n  e_i = b;\n  d_im1 = 64;\n  for (d_i = d_im1-1; ((one << d_i) & e_i) == 0; d_i--) ;\n  y_i = 1;\n  y_im1 = 0;\n\n  while (e_i != 1) {\n\n    e_ip1 = e_im1;\n    d_ip1 = d_im1;\n    c_i = 0;\n\n    while (d_ip1 >= d_i) {\n      c_i ^= (one << (d_ip1 - d_i));\n      e_ip1 ^= (e_i << (d_ip1 - d_i));\n      d_ip1--;\n      if (e_ip1 == 0) return 0;\n      while ((e_ip1 & (one << d_ip1)) == 0) d_ip1--;\n    }\n\n    y_ip1 = y_im1 ^ gf->multiply.w64(gf, c_i, y_i);\n    y_im1 = y_i;\n    y_i = y_ip1;\n\n    e_im1 = e_i;\n    d_im1 = d_i;\n    e_i = e_ip1;\n    d_i = d_ip1;\n  }\n\n  return y_i;\n}\n\n/* JSP: GF_MULT_SHIFT: The world's dumbest multiplication algorithm.  I only\n   include it for completeness.  It does have the feature that it requires no\n   extra memory.  \n*/\n\nstatic\ninline\ngf_val_64_t\ngf_w64_shift_multiply (gf_t *gf, gf_val_64_t a64, gf_val_64_t b64)\n{\n  uint64_t pl, pr, ppl, ppr, i, a, bl, br, one, lbit;\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  \n  /* Allen: set leading one of primitive polynomial */\n  \n  a = a64;\n  bl = 0;\n  br = b64;\n  one = 1;\n  lbit = (one << 63);\n\n  pl = 0; /* Allen: left side of product */\n  pr = 0; /* Allen: right side of product */\n\n  /* Allen: unlike the corresponding functions for smaller word sizes,\n   * this loop carries out the initial carryless multiply by\n   * shifting b itself rather than simply looking at successively\n   * higher shifts of b */\n  \n  for (i = 0; i < GF_FIELD_WIDTH; i++) {\n    if (a & (one << i)) {\n      pl ^= bl;\n      pr ^= br;\n    }\n\n    bl <<= 1;\n    if (br & lbit) bl ^= 1;\n    br <<= 1;\n  }\n\n  /* Allen: the name of the variable \"one\" is no longer descriptive at this point */\n  \n  one = lbit >> 1;\n  ppl = (h->prim_poly >> 2) | one;\n  ppr = (h->prim_poly << (GF_FIELD_WIDTH-2));\n  while (one != 0) {\n    if (pl & one) {\n      pl ^= ppl;\n      pr ^= ppr;\n    }\n    one >>= 1;\n    ppr >>= 1;\n    if (ppl & 1) ppr ^= lbit;\n    ppl >>= 1;\n  }\n  return pr;\n}\n\n/*\n * ELM: Use the Intel carryless multiply instruction to do very fast 64x64 multiply.\n */\n\nstatic\ninline\ngf_val_64_t\ngf_w64_clm_multiply_2 (gf_t *gf, gf_val_64_t a64, gf_val_64_t b64)\n{\n       gf_val_64_t rv = 0;\n\n#if defined(INTEL_SSE4_PCLMUL) \n\n        __m128i         a, b;\n        __m128i         result;\n        __m128i         prim_poly;\n        __m128i         v, w;\n        gf_internal_t * h = gf->scratch;\n\n        a = _mm_insert_epi64 (_mm_setzero_si128(), a64, 0);\n        b = _mm_insert_epi64 (a, b64, 0); \n        prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0xffffffffULL));\n        /* Do the initial multiply */\n   \n        result = _mm_clmulepi64_si128 (a, b, 0);\n        \n        /* Mask off the high order 32 bits using subtraction of the polynomial.\n         * NOTE: this part requires that the polynomial have at least 32 leading 0 bits.\n         */\n\n        /* Adam: We cant include the leading one in the 64 bit pclmul,\n         so we need to split up the high 8 bytes of the result into two \n         parts before we multiply them with the prim_poly.*/\n\n        v = _mm_insert_epi32 (_mm_srli_si128 (result, 8), 0, 0);\n        w = _mm_clmulepi64_si128 (prim_poly, v, 0);\n        result = _mm_xor_si128 (result, w);\n        v = _mm_insert_epi32 (_mm_srli_si128 (result, 8), 0, 1);\n        w = _mm_clmulepi64_si128 (prim_poly, v, 0);\n        result = _mm_xor_si128 (result, w);\n\n        rv = ((gf_val_64_t)_mm_extract_epi64(result, 0));\n#endif\n        return rv;\n}\n \nstatic\ninline\ngf_val_64_t\ngf_w64_clm_multiply_4 (gf_t *gf, gf_val_64_t a64, gf_val_64_t b64)\n{\n  gf_val_64_t rv = 0;\n\n#if defined(INTEL_SSE4_PCLMUL) \n\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         v, w;\n  gf_internal_t * h = gf->scratch;\n\n  a = _mm_insert_epi64 (_mm_setzero_si128(), a64, 0);\n  b = _mm_insert_epi64 (a, b64, 0);\n  prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0xffffffffULL));\n \n  /* Do the initial multiply */\n  \n  result = _mm_clmulepi64_si128 (a, b, 0);\n\n  v = _mm_insert_epi32 (_mm_srli_si128 (result, 8), 0, 0);\n  w = _mm_clmulepi64_si128 (prim_poly, v, 0);\n  result = _mm_xor_si128 (result, w);\n  v = _mm_insert_epi32 (_mm_srli_si128 (result, 8), 0, 1);\n  w = _mm_clmulepi64_si128 (prim_poly, v, 0);\n  result = _mm_xor_si128 (result, w);\n  \n  v = _mm_insert_epi32 (_mm_srli_si128 (result, 8), 0, 0);\n  w = _mm_clmulepi64_si128 (prim_poly, v, 0);\n  result = _mm_xor_si128 (result, w);\n  v = _mm_insert_epi32 (_mm_srli_si128 (result, 8), 0, 1);\n  w = _mm_clmulepi64_si128 (prim_poly, v, 0);\n  result = _mm_xor_si128 (result, w);\n\n  rv = ((gf_val_64_t)_mm_extract_epi64(result, 0));\n#endif\n  return rv;\n}\n\n\n  void\ngf_w64_clm_multiply_region(gf_t *gf, void *src, void *dest, uint64_t val, int bytes, int xor)\n{\n#if defined(INTEL_SSE4_PCLMUL) \n  gf_internal_t *h;\n  uint8_t *s8, *d8, *dtop;\n  gf_region_data rd;\n  __m128i  v, b, m, prim_poly, c, fr, w, result;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  h = (gf_internal_t *) gf->scratch;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n\n  s8 = (uint8_t *) rd.s_start;\n  d8 = (uint8_t *) rd.d_start;\n  dtop = (uint8_t *) rd.d_top;\n\n  v = _mm_insert_epi64(_mm_setzero_si128(), val, 0);\n  m = _mm_set_epi32(0, 0, 0xffffffff, 0xffffffff);\n  prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0xffffffffULL));\n\n  if (xor) {\n    while (d8 != dtop) {\n      b = _mm_load_si128((__m128i *) s8);\n      result = _mm_clmulepi64_si128 (b, v, 0);\n      c = _mm_insert_epi32 (_mm_srli_si128 (result, 8), 0, 0);\n      w = _mm_clmulepi64_si128 (prim_poly, c, 0);\n      result = _mm_xor_si128 (result, w);\n      c = _mm_insert_epi32 (_mm_srli_si128 (result, 8), 0, 1);\n      w = _mm_clmulepi64_si128 (prim_poly, c, 0);\n      fr = _mm_xor_si128 (result, w);\n      fr = _mm_and_si128 (fr, m);\n\n      result = _mm_clmulepi64_si128 (b, v, 1);\n      c = _mm_insert_epi32 (_mm_srli_si128 (result, 8), 0, 0);\n      w = _mm_clmulepi64_si128 (prim_poly, c, 0);\n      result = _mm_xor_si128 (result, w);\n      c = _mm_insert_epi32 (_mm_srli_si128 (result, 8), 0, 1);\n      w = _mm_clmulepi64_si128 (prim_poly, c, 0);\n      result = _mm_xor_si128 (result, w);\n      result = _mm_slli_si128 (result, 8);\n      fr = _mm_xor_si128 (result, fr);\n      result = _mm_load_si128((__m128i *) d8);\n      fr = _mm_xor_si128 (result, fr);\n\n      _mm_store_si128((__m128i *) d8, fr);\n      d8 += 16;\n      s8 += 16;\n    }\n  } else {\n    while (d8 < dtop) {\n      b = _mm_load_si128((__m128i *) s8);\n      result = _mm_clmulepi64_si128 (b, v, 0);\n      c = _mm_insert_epi32 (_mm_srli_si128 (result, 8), 0, 0);\n      w = _mm_clmulepi64_si128 (prim_poly, c, 0);\n      result = _mm_xor_si128 (result, w);\n      c = _mm_insert_epi32 (_mm_srli_si128 (result, 8), 0, 1);\n      w = _mm_clmulepi64_si128 (prim_poly, c, 0);\n      fr = _mm_xor_si128 (result, w);\n      fr = _mm_and_si128 (fr, m);\n  \n      result = _mm_clmulepi64_si128 (b, v, 1);\n      c = _mm_insert_epi32 (_mm_srli_si128 (result, 8), 0, 0);\n      w = _mm_clmulepi64_si128 (prim_poly, c, 0);\n      result = _mm_xor_si128 (result, w);\n      c = _mm_insert_epi32 (_mm_srli_si128 (result, 8), 0, 1);\n      w = _mm_clmulepi64_si128 (prim_poly, c, 0);\n      result = _mm_xor_si128 (result, w);\n      result = _mm_slli_si128 (result, 8);\n      fr = _mm_xor_si128 (result, fr);\n  \n      _mm_store_si128((__m128i *) d8, fr);\n      d8 += 16;\n      s8 += 16;\n    }\n  }\n  gf_do_final_region_alignment(&rd);\n#endif\n}\n\nvoid\ngf_w64_split_4_64_lazy_multiply_region(gf_t *gf, void *src, void *dest, uint64_t val, int bytes, int xor)\n{\n  gf_internal_t *h;\n  struct gf_split_4_64_lazy_data *ld;\n  int i, j, k;\n  uint64_t pp, v, s, *s64, *d64, *top;\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  ld = (struct gf_split_4_64_lazy_data *) h->private;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 8);\n  gf_do_initial_region_alignment(&rd);\n\n  if (ld->last_value != val) {\n    v = val;\n    for (i = 0; i < 16; i++) {\n      ld->tables[i][0] = 0;\n      for (j = 1; j < 16; j <<= 1) {\n        for (k = 0; k < j; k++) {\n          ld->tables[i][k^j] = (v ^ ld->tables[i][k]);\n        }\n        v = (v & GF_FIRST_BIT) ? ((v << 1) ^ pp) : (v << 1);\n      }\n    }\n  }\n  ld->last_value = val;\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  top = (uint64_t *) rd.d_top;\n\n  while (d64 != top) {\n    v = (xor) ? *d64 : 0;\n    s = *s64;\n    i = 0;\n    while (s != 0) {\n      v ^= ld->tables[i][s&0xf];\n      s >>= 4;\n      i++;\n    }\n    *d64 = v;\n    d64++;\n    s64++;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\ninline\nuint64_t\ngf_w64_split_8_8_multiply (gf_t *gf, uint64_t a64, uint64_t b64)\n{\n  uint64_t product, i, j, mask, tb;\n  gf_internal_t *h;\n  struct gf_split_8_8_data *d8;\n \n  h = (gf_internal_t *) gf->scratch;\n  d8 = (struct gf_split_8_8_data *) h->private;\n  product = 0;\n  mask = 0xff;\n\n  for (i = 0; a64 != 0; i++) {\n    tb = b64;\n    for (j = 0; tb != 0; j++) {\n      product ^= d8->tables[i+j][a64&mask][tb&mask];\n      tb >>= 8;\n    }\n    a64 >>= 8;\n  }\n  return product;\n}\n\nvoid\ngf_w64_split_8_64_lazy_multiply_region(gf_t *gf, void *src, void *dest, uint64_t val, int bytes, int xor)\n{\n  gf_internal_t *h;\n  struct gf_split_8_64_lazy_data *ld;\n  int i, j, k;\n  uint64_t pp, v, s, *s64, *d64, *top;\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  ld = (struct gf_split_8_64_lazy_data *) h->private;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 4);\n  gf_do_initial_region_alignment(&rd);\n\n  if (ld->last_value != val) {\n    v = val;\n    for (i = 0; i < 8; i++) {\n      ld->tables[i][0] = 0;\n      for (j = 1; j < 256; j <<= 1) {\n        for (k = 0; k < j; k++) {\n          ld->tables[i][k^j] = (v ^ ld->tables[i][k]);\n        }\n        v = (v & GF_FIRST_BIT) ? ((v << 1) ^ pp) : (v << 1);\n      }\n    }\n  }\n  ld->last_value = val;\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  top = (uint64_t *) rd.d_top;\n\n  while (d64 != top) {\n    v = (xor) ? *d64 : 0;\n    s = *s64;\n    i = 0;\n    while (s != 0) {\n      v ^= ld->tables[i][s&0xff];\n      s >>= 8;\n      i++;\n    }\n    *d64 = v;\n    d64++;\n    s64++;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\nvoid\ngf_w64_split_16_64_lazy_multiply_region(gf_t *gf, void *src, void *dest, uint64_t val, int bytes, int xor)\n{\n  gf_internal_t *h;\n  struct gf_split_16_64_lazy_data *ld;\n  int i, j, k;\n  uint64_t pp, v, s, *s64, *d64, *top;\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  ld = (struct gf_split_16_64_lazy_data *) h->private;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 4);\n  gf_do_initial_region_alignment(&rd);\n\n  if (ld->last_value != val) {\n    v = val;\n    for (i = 0; i < 4; i++) {\n      ld->tables[i][0] = 0;\n      for (j = 1; j < (1<<16); j <<= 1) {\n        for (k = 0; k < j; k++) {\n          ld->tables[i][k^j] = (v ^ ld->tables[i][k]);\n        }\n        v = (v & GF_FIRST_BIT) ? ((v << 1) ^ pp) : (v << 1);\n      }\n    }\n  }\n  ld->last_value = val;\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  top = (uint64_t *) rd.d_top;\n\n  while (d64 != top) {\n    v = (xor) ? *d64 : 0;\n    s = *s64;\n    i = 0;\n    while (s != 0) {\n      v ^= ld->tables[i][s&0xffff];\n      s >>= 16;\n      i++;\n    }\n    *d64 = v;\n    d64++;\n    s64++;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic \nint gf_w64_shift_init(gf_t *gf)\n{\n  gf->multiply.w64 = gf_w64_shift_multiply;\n  gf->inverse.w64 = gf_w64_euclid;\n  gf->multiply_region.w64 = gf_w64_multiply_region_from_single;\n  return 1;\n}\n\nstatic \nint gf_w64_cfm_init(gf_t *gf)\n{\n  gf->inverse.w64 = gf_w64_euclid;\n  gf->multiply_region.w64 = gf_w64_multiply_region_from_single;\n\n#if defined(INTEL_SSE4_PCLMUL) \n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n\n  if ((0xfffffffe00000000ULL & h->prim_poly) == 0){ \n    gf->multiply.w64 = gf_w64_clm_multiply_2;\n    gf->multiply_region.w64 = gf_w64_clm_multiply_region_from_single_2; \n  }else if((0xfffe000000000000ULL & h->prim_poly) == 0){\n    gf->multiply.w64 = gf_w64_clm_multiply_4;\n    gf->multiply_region.w64 = gf_w64_clm_multiply_region_from_single_4;\n  } else {\n    return 0;\n  }\n  return 1;\n#endif\n\n  return 0;\n}\n\nstatic\nvoid\ngf_w64_group_set_shift_tables(uint64_t *shift, uint64_t val, gf_internal_t *h)\n{\n  uint64_t i;\n  uint64_t j;\n  uint64_t one = 1;\n  int g_s;\n\n  g_s = h->arg1;\n  shift[0] = 0;\n \n  for (i = 1; i < ((uint64_t)1 << g_s); i <<= 1) {\n    for (j = 0; j < i; j++) shift[i|j] = shift[j]^val;\n    if (val & (one << 63)) {\n      val <<= 1;\n      val ^= h->prim_poly;\n    } else {\n      val <<= 1;\n    }\n  }\n}\n\nstatic\ninline\ngf_val_64_t\ngf_w64_group_multiply(gf_t *gf, gf_val_64_t a, gf_val_64_t b)\n{\n  uint64_t top, bot, mask, tp;\n  int g_s, g_r, lshift, rshift;\n  struct gf_w64_group_data *gd;\n\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  g_s = h->arg1;\n  g_r = h->arg2;\n  gd = (struct gf_w64_group_data *) h->private;\n  gf_w64_group_set_shift_tables(gd->shift, b, h);\n\n  mask = (((uint64_t)1 << g_s) - 1);\n  top = 0;\n  bot = gd->shift[a&mask];\n  a >>= g_s; \n\n  if (a == 0) return bot;\n  lshift = 0;\n  rshift = 64;\n\n  do {              /* Shifting out is straightfoward */\n    lshift += g_s;\n    rshift -= g_s;\n    tp = gd->shift[a&mask];\n    top ^= (tp >> rshift);\n    bot ^= (tp << lshift);\n    a >>= g_s; \n  } while (a != 0);\n\n  /* Reducing is a bit gross, because I don't zero out the index bits of top.\n     The reason is that we throw top away.  Even better, that last (tp >> rshift)\n     is going to be ignored, so it doesn't matter how (tp >> 64) is implemented. */\n     \n  lshift = ((lshift-1) / g_r) * g_r;\n  rshift = 64 - lshift;\n  mask = ((uint64_t)1 << g_r) - 1;\n  while (lshift >= 0) {\n    tp = gd->reduce[(top >> lshift) & mask];\n    top ^= (tp >> rshift);\n    bot ^= (tp << lshift);\n    lshift -= g_r;\n    rshift += g_r;\n  }\n    \n  return bot;\n}\n\nstatic\nvoid gf_w64_group_multiply_region(gf_t *gf, void *src, void *dest, gf_val_64_t val, int bytes, int xor)\n{\n  int i, fzb;\n  uint64_t a64, smask, rmask, top, bot, tp;\n  int lshift, rshift, g_s, g_r;\n  gf_region_data rd;\n  uint64_t *s64, *d64, *dtop;\n  struct gf_w64_group_data *gd;\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gd = (struct gf_w64_group_data *) h->private;\n  g_s = h->arg1;\n  g_r = h->arg2;\n  gf_w64_group_set_shift_tables(gd->shift, val, h);\n\n  for (i = 63; !(val & (1ULL << i)); i--) ;\n  i += g_s;\n  \n  /* i is the bit position of the first zero bit in any element of\n                           gd->shift[] */\n  \n  if (i > 64) i = 64;   \n  \n  fzb = i;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 4);\n  \n  gf_do_initial_region_alignment(&rd);\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  dtop = (uint64_t *) rd.d_top;\n\n  smask = ((uint64_t)1 << g_s) - 1;\n  rmask = ((uint64_t)1 << g_r) - 1;\n\n  while (d64 < dtop) {\n    a64 = *s64;\n    \n    top = 0;\n    bot = gd->shift[a64&smask];\n    a64 >>= g_s;\n    i = fzb;\n\n    if (a64 != 0) {\n      lshift = 0;\n      rshift = 64;\n  \n      do {  \n        lshift += g_s;\n        rshift -= g_s;\n        tp = gd->shift[a64&smask];\n        top ^= (tp >> rshift);\n        bot ^= (tp << lshift);\n        a64 >>= g_s;\n      } while (a64 != 0);\n      i += lshift;\n  \n      lshift = ((i-64-1) / g_r) * g_r;\n      rshift = 64 - lshift;\n      while (lshift >= 0) {\n        tp = gd->reduce[(top >> lshift) & rmask];\n        top ^= (tp >> rshift);    \n        bot ^= (tp << lshift);\n        lshift -= g_r;\n        rshift += g_r;\n      }\n    }\n\n    if (xor) bot ^= *d64;\n    *d64 = bot;\n    d64++;\n    s64++;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\ninline\ngf_val_64_t\ngf_w64_group_s_equals_r_multiply(gf_t *gf, gf_val_64_t a, gf_val_64_t b)\n{\n  int leftover, rs;\n  uint64_t p, l, ind, a64;\n  int bits_left;\n  int g_s;\n\n  struct gf_w64_group_data *gd;\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  g_s = h->arg1;\n\n  gd = (struct gf_w64_group_data *) h->private;\n  gf_w64_group_set_shift_tables(gd->shift, b, h);\n\n  leftover = 64 % g_s;\n  if (leftover == 0) leftover = g_s;\n\n  rs = 64 - leftover;\n  a64 = a;\n  ind = a64 >> rs;\n  a64 <<= leftover;\n  p = gd->shift[ind];\n\n  bits_left = rs;\n  rs = 64 - g_s;\n\n  while (bits_left > 0) {\n    bits_left -= g_s;\n    ind = a64 >> rs;\n    a64 <<= g_s;\n    l = p >> rs;\n    p = (gd->shift[ind] ^ gd->reduce[l] ^ (p << g_s));\n  }\n  return p;\n}\n\nstatic\nvoid gf_w64_group_s_equals_r_multiply_region(gf_t *gf, void *src, void *dest, gf_val_64_t val, int bytes, int xor)\n{\n  int leftover, rs;\n  uint64_t p, l, ind, a64;\n  int bits_left;\n  int g_s;\n  gf_region_data rd;\n  uint64_t *s64, *d64, *top;\n  struct gf_w64_group_data *gd;\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gd = (struct gf_w64_group_data *) h->private;\n  g_s = h->arg1;\n  gf_w64_group_set_shift_tables(gd->shift, val, h);\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 4);\n  gf_do_initial_region_alignment(&rd);\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  top = (uint64_t *) rd.d_top;\n\n  leftover = 64 % g_s;\n  if (leftover == 0) leftover = g_s;\n\n  while (d64 < top) {\n    rs = 64 - leftover;\n    a64 = *s64;\n    ind = a64 >> rs;\n    a64 <<= leftover;\n    p = gd->shift[ind];\n\n    bits_left = rs;\n    rs = 64 - g_s;\n\n    while (bits_left > 0) {\n      bits_left -= g_s;\n      ind = a64 >> rs;\n      a64 <<= g_s;\n      l = p >> rs;\n      p = (gd->shift[ind] ^ gd->reduce[l] ^ (p << g_s));\n    }\n    if (xor) p ^= *d64;\n    *d64 = p;\n    d64++;\n    s64++;\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\n\nstatic\nint gf_w64_group_init(gf_t *gf)\n{\n  uint64_t i, j, p, index;\n  struct gf_w64_group_data *gd;\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  uint64_t g_r, g_s;\n\n  g_s = h->arg1;\n  g_r = h->arg2;\n\n  gd = (struct gf_w64_group_data *) h->private;\n  gd->shift = (uint64_t *) (&(gd->memory));\n  gd->reduce = gd->shift + (1 << g_s);\n\n  gd->reduce[0] = 0;\n  for (i = 0; i < ((uint64_t)1 << g_r); i++) {\n    p = 0;\n    index = 0;\n    for (j = 0; j < g_r; j++) {\n      if (i & (1 << j)) {\n        p ^= (h->prim_poly << j);\n        index ^= (1 << j);\n        if (j > 0) index ^= (h->prim_poly >> (64-j)); \n      }\n    }\n    gd->reduce[index] = p;\n  }\n\n  if (g_s == g_r) {\n    gf->multiply.w64 = gf_w64_group_s_equals_r_multiply;\n    gf->multiply_region.w64 = gf_w64_group_s_equals_r_multiply_region; \n  } else {\n    gf->multiply.w64 = gf_w64_group_multiply;\n    gf->multiply_region.w64 = gf_w64_group_multiply_region; \n  }\n  gf->divide.w64 = NULL;\n  gf->inverse.w64 = gf_w64_euclid;\n\n  return 1;\n}\n\nstatic\ngf_val_64_t gf_w64_extract_word(gf_t *gf, void *start, int bytes, int index)\n{\n  uint64_t *r64, rv;\n\n  r64 = (uint64_t *) start;\n  rv = r64[index];\n  return rv;\n}\n\nstatic\ngf_val_64_t gf_w64_composite_extract_word(gf_t *gf, void *start, int bytes, int index)\n{\n  int sub_size;\n  gf_internal_t *h;\n  uint8_t *r8, *top;\n  uint64_t a, b, *r64;\n  gf_region_data rd;\n\n  h = (gf_internal_t *) gf->scratch;\n  gf_set_region_data(&rd, gf, start, start, bytes, 0, 0, 32);\n  r64 = (uint64_t *) start;\n  if (r64 + index < (uint64_t *) rd.d_start) return r64[index];\n  if (r64 + index >= (uint64_t *) rd.d_top) return r64[index];\n  index -= (((uint64_t *) rd.d_start) - r64);\n  r8 = (uint8_t *) rd.d_start;\n  top = (uint8_t *) rd.d_top;\n  sub_size = (top-r8)/2;\n\n  a = h->base_gf->extract_word.w32(h->base_gf, r8, sub_size, index);\n  b = h->base_gf->extract_word.w32(h->base_gf, r8+sub_size, sub_size, index);\n  return (a | ((uint64_t)b << 32));\n}\n\nstatic\ngf_val_64_t gf_w64_split_extract_word(gf_t *gf, void *start, int bytes, int index)\n{\n  int i;\n  uint64_t *r64, rv;\n  uint8_t *r8;\n  gf_region_data rd;\n\n  gf_set_region_data(&rd, gf, start, start, bytes, 0, 0, 128);\n  r64 = (uint64_t *) start;\n  if (r64 + index < (uint64_t *) rd.d_start) return r64[index];\n  if (r64 + index >= (uint64_t *) rd.d_top) return r64[index];\n  index -= (((uint64_t *) rd.d_start) - r64);\n  r8 = (uint8_t *) rd.d_start;\n  r8 += ((index & 0xfffffff0)*8);\n  r8 += (index & 0xf);\n  r8 += 112;\n  rv =0;\n  for (i = 0; i < 8; i++) {\n    rv <<= 8;\n    rv |= *r8;\n    r8 -= 16;\n  }\n  return rv;\n}\n\nstatic\ninline\ngf_val_64_t\ngf_w64_bytwo_b_multiply (gf_t *gf, gf_val_64_t a, gf_val_64_t b)\n{\n  uint64_t prod, pp, bmask;\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  prod = 0;\n  bmask = 0x8000000000000000ULL;\n\n  while (1) {\n    if (a & 1) prod ^= b;\n    a >>= 1;\n    if (a == 0) return prod;\n    if (b & bmask) {\n      b = ((b << 1) ^ pp);\n    } else {\n      b <<= 1;\n    }\n  }\n}\n\nstatic\ninline\ngf_val_64_t\ngf_w64_bytwo_p_multiply (gf_t *gf, gf_val_64_t a, gf_val_64_t b)\n{\n  uint64_t prod, pp, pmask, amask;\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  prod = 0;\n  \n  /* changed from declare then shift to just declare.*/\n  \n  pmask = 0x8000000000000000ULL;\n  amask = 0x8000000000000000ULL;\n\n  while (amask != 0) {\n    if (prod & pmask) {\n      prod = ((prod << 1) ^ pp);\n    } else {\n      prod <<= 1;\n    }\n    if (a & amask) prod ^= b;\n    amask >>= 1;\n  }\n  return prod;\n}\n\nstatic\nvoid\ngf_w64_bytwo_p_nosse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_64_t val, int bytes, int xor)\n{\n  uint64_t *s64, *d64, ta, prod, amask, pmask, pp;\n  gf_region_data rd;\n  gf_internal_t *h;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 8);\n  gf_do_initial_region_alignment(&rd);\n\n  h = (gf_internal_t *) gf->scratch;\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  pmask = 0x80000000;\n  pmask <<= 32;\n  pp = h->prim_poly;\n\n  if (xor) {\n    while (s64 < (uint64_t *) rd.s_top) {\n      prod = 0;\n      amask = pmask;\n      ta = *s64;\n      while (amask != 0) {\n        prod = (prod & pmask) ? ((prod << 1) ^ pp) : (prod << 1);\n        if (val & amask) prod ^= ta;\n        amask >>= 1;\n      }\n      *d64 ^= prod;\n      d64++;\n      s64++;\n    }\n  } else {\n    while (s64 < (uint64_t *) rd.s_top) {\n      prod = 0;\n      amask = pmask;\n      ta = *s64;\n      while (amask != 0) {\n        prod = (prod & pmask) ? ((prod << 1) ^ pp) : (prod << 1);\n        if (val & amask) prod ^= ta;\n        amask >>= 1;\n      }\n      *d64 = prod;\n      d64++;\n      s64++;\n    }\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\nvoid\ngf_w64_bytwo_b_nosse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_64_t val, int bytes, int xor)\n{\n  uint64_t *s64, *d64, ta, tb, prod, bmask, pp;\n  gf_region_data rd;\n  gf_internal_t *h;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 8);\n  gf_do_initial_region_alignment(&rd);\n\n  h = (gf_internal_t *) gf->scratch;\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  bmask = 0x80000000;\n  bmask <<= 32;\n  pp = h->prim_poly;\n\n  if (xor) {\n    while (s64 < (uint64_t *) rd.s_top) {\n      prod = 0;\n      tb = val;\n      ta = *s64;\n      while (1) {\n        if (tb & 1) prod ^= ta;\n        tb >>= 1;\n        if (tb == 0) break;\n        ta = (ta & bmask) ? ((ta << 1) ^ pp) : (ta << 1);\n      }\n      *d64 ^= prod;\n      d64++;\n      s64++;\n    }\n  } else {\n    while (s64 < (uint64_t *) rd.s_top) {\n      prod = 0;\n      tb = val;\n      ta = *s64;\n      while (1) {\n        if (tb & 1) prod ^= ta;\n        tb >>= 1;\n        if (tb == 0) break;\n        ta = (ta & bmask) ? ((ta << 1) ^ pp) : (ta << 1);\n      }\n      *d64 = prod;\n      d64++;\n      s64++;\n    }\n  }\n  gf_do_final_region_alignment(&rd);\n}\n\n#define SSE_AB2(pp, m1 ,m2, va, t1, t2) {\\\n          t1 = _mm_and_si128(_mm_slli_epi64(va, 1), m1); \\\n          t2 = _mm_and_si128(va, m2); \\\n          t2 = _mm_sub_epi64 (_mm_slli_epi64(t2, 1), _mm_srli_epi64(t2, (GF_FIELD_WIDTH-1))); \\\n          va = _mm_xor_si128(t1, _mm_and_si128(t2, pp)); }\n\n#define BYTWO_P_ONESTEP {\\\n      SSE_AB2(pp, m1 ,m2, prod, t1, t2); \\\n      t1 = _mm_and_si128(v, one); \\\n      t1 = _mm_sub_epi64(t1, one); \\\n      t1 = _mm_and_si128(t1, ta); \\\n      prod = _mm_xor_si128(prod, t1); \\\n      v = _mm_srli_epi64(v, 1); }\n\n\nvoid gf_w64_bytwo_p_sse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_64_t val, int bytes, int xor)\n{\n#ifdef INTEL_SSE2\n  int i;\n  uint8_t *s8, *d8;\n  uint64_t vrev, one64;\n  uint64_t amask;\n  __m128i pp, m1, m2, ta, prod, t1, t2, tp, one, v;\n  gf_region_data rd;\n  gf_internal_t *h;\n  \n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n\n  h = (gf_internal_t *) gf->scratch;\n  one64 = 1;\n  vrev = 0;\n  for (i = 0; i < 64; i++) {\n    vrev <<= 1;\n    if (!(val & (one64 << i))) vrev |= 1;\n  }\n\n  s8 = (uint8_t *) rd.s_start;\n  d8 = (uint8_t *) rd.d_start;\n\n  amask = -1;\n  amask ^= 1;\n  pp = _mm_set1_epi64x(h->prim_poly);\n  m1 = _mm_set1_epi64x(amask);\n  m2 = _mm_set1_epi64x(one64 << 63);\n  one = _mm_set1_epi64x(1);\n\n  while (d8 < (uint8_t *) rd.d_top) {\n    prod = _mm_setzero_si128();\n    v = _mm_set1_epi64x(vrev);\n    ta = _mm_load_si128((__m128i *) s8);\n    tp = (!xor) ? _mm_setzero_si128() : _mm_load_si128((__m128i *) d8);\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP; BYTWO_P_ONESTEP;\n    _mm_store_si128((__m128i *) d8, _mm_xor_si128(prod, tp));\n    d8 += 16;\n    s8 += 16;\n  }\n  gf_do_final_region_alignment(&rd);\n#endif\n}\n\n#ifdef INTEL_SSE2\nstatic\nvoid\ngf_w64_bytwo_b_sse_region_2_xor(gf_region_data *rd)\n{\n  uint64_t one64, amask;\n  uint8_t *d8, *s8;\n  __m128i pp, m1, m2, t1, t2, va, vb;\n  gf_internal_t *h;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  h = (gf_internal_t *) rd->gf->scratch;\n  one64 = 1;\n  amask = -1;\n  amask ^= 1;\n  pp = _mm_set1_epi64x(h->prim_poly);\n  m1 = _mm_set1_epi64x(amask);\n  m2 = _mm_set1_epi64x(one64 << 63);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    SSE_AB2(pp, m1, m2, va, t1, t2);\n    vb = _mm_load_si128 ((__m128i *)(d8));\n    vb = _mm_xor_si128(vb, va);\n    _mm_store_si128((__m128i *)d8, vb);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic\nvoid\ngf_w64_bytwo_b_sse_region_2_noxor(gf_region_data *rd)\n{\n  uint64_t one64, amask;\n  uint8_t *d8, *s8;\n  __m128i pp, m1, m2, t1, t2, va;\n  gf_internal_t *h;\n\n  s8 = (uint8_t *) rd->s_start;\n  d8 = (uint8_t *) rd->d_start;\n\n  h = (gf_internal_t *) rd->gf->scratch;\n  one64 = 1;\n  amask = -1;\n  amask ^= 1;\n  pp = _mm_set1_epi64x(h->prim_poly);\n  m1 = _mm_set1_epi64x(amask);\n  m2 = _mm_set1_epi64x(one64 << 63);\n\n  while (d8 < (uint8_t *) rd->d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    SSE_AB2(pp, m1, m2, va, t1, t2);\n    _mm_store_si128((__m128i *)d8, va);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic\nvoid\ngf_w64_bytwo_b_sse_multiply_region(gf_t *gf, void *src, void *dest, gf_val_64_t val, int bytes, int xor)\n{\n  uint64_t itb, amask, one64;\n  uint8_t *d8, *s8;\n  __m128i pp, m1, m2, t1, t2, va, vb;\n  gf_region_data rd;\n  gf_internal_t *h;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n\n  if (val == 2) {\n    if (xor) {\n      gf_w64_bytwo_b_sse_region_2_xor(&rd);\n    } else {\n      gf_w64_bytwo_b_sse_region_2_noxor(&rd);\n    }\n    gf_do_final_region_alignment(&rd);\n    return;\n  }\n\n  s8 = (uint8_t *) rd.s_start;\n  d8 = (uint8_t *) rd.d_start;\n  h = (gf_internal_t *) gf->scratch;\n\n  one64 = 1;\n  amask = -1;\n  amask ^= 1;\n  pp = _mm_set1_epi64x(h->prim_poly);\n  m1 = _mm_set1_epi64x(amask);\n  m2 = _mm_set1_epi64x(one64 << 63);\n\n  while (d8 < (uint8_t *) rd.d_top) {\n    va = _mm_load_si128 ((__m128i *)(s8));\n    vb = (!xor) ? _mm_setzero_si128() : _mm_load_si128 ((__m128i *)(d8));\n    itb = val;\n    while (1) {\n      if (itb & 1) vb = _mm_xor_si128(vb, va);\n      itb >>= 1;\n      if (itb == 0) break;\n      SSE_AB2(pp, m1, m2, va, t1, t2);\n    }\n    _mm_store_si128((__m128i *)d8, vb);\n    d8 += 16;\n    s8 += 16;\n  }\n\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\n\nstatic\nint gf_w64_bytwo_init(gf_t *gf)\n{\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n\n  if (h->mult_type == GF_MULT_BYTWO_p) {\n    gf->multiply.w64 = gf_w64_bytwo_p_multiply;\n    #ifdef INTEL_SSE2 \n      if (h->region_type & GF_REGION_NOSIMD)\n        gf->multiply_region.w64 = gf_w64_bytwo_p_nosse_multiply_region; \n      else\n        gf->multiply_region.w64 = gf_w64_bytwo_p_sse_multiply_region; \n    #else\n      gf->multiply_region.w64 = gf_w64_bytwo_p_nosse_multiply_region; \n      if(h->region_type & GF_REGION_SIMD)\n        return 0;\n    #endif\n  } else {\n    gf->multiply.w64 = gf_w64_bytwo_b_multiply;\n    #ifdef INTEL_SSE2 \n      if (h->region_type & GF_REGION_NOSIMD)\n        gf->multiply_region.w64 = gf_w64_bytwo_b_nosse_multiply_region; \n      else\n        gf->multiply_region.w64 = gf_w64_bytwo_b_sse_multiply_region; \n    #else\n      gf->multiply_region.w64 = gf_w64_bytwo_b_nosse_multiply_region; \n      if(h->region_type & GF_REGION_SIMD)\n        return 0;\n    #endif\n  }\n  gf->inverse.w64 = gf_w64_euclid;\n  return 1;\n}\n\n\nstatic\ngf_val_64_t\ngf_w64_composite_multiply(gf_t *gf, gf_val_64_t a, gf_val_64_t b)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  gf_t *base_gf = h->base_gf;\n  uint32_t b0 = b & 0x00000000ffffffff;\n  uint32_t b1 = (b & 0xffffffff00000000) >> 32;\n  uint32_t a0 = a & 0x00000000ffffffff;\n  uint32_t a1 = (a & 0xffffffff00000000) >> 32;\n  uint32_t a1b1;\n\n  a1b1 = base_gf->multiply.w32(base_gf, a1, b1);\n\n  return ((uint64_t)(base_gf->multiply.w32(base_gf, a0, b0) ^ a1b1) | \n         ((uint64_t)(base_gf->multiply.w32(base_gf, a1, b0) ^ base_gf->multiply.w32(base_gf, a0, b1) ^ base_gf->multiply.w32(base_gf, a1b1, h->prim_poly)) << 32));\n}\n\n/*\n * Composite field division trick (explained in 2007 tech report)\n *\n * Compute a / b = a*b^-1, where p(x) = x^2 + sx + 1\n *\n * let c = b^-1\n *\n * c*b = (s*b1c1+b1c0+b0c1)x+(b1c1+b0c0)\n *\n * want (s*b1c1+b1c0+b0c1) = 0 and (b1c1+b0c0) = 1\n *\n * let d = b1c1 and d+1 = b0c0\n *\n * solve s*b1c1+b1c0+b0c1 = 0\n *\n * solution: d = (b1b0^-1)(b1b0^-1+b0b1^-1+s)^-1\n *\n * c0 = (d+1)b0^-1\n * c1 = d*b1^-1\n *\n * a / b = a * c\n */\n\nstatic\ngf_val_64_t\ngf_w64_composite_inverse(gf_t *gf, gf_val_64_t a)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  gf_t *base_gf = h->base_gf;\n  uint32_t a0 = a & 0x00000000ffffffff;\n  uint32_t a1 = (a & 0xffffffff00000000) >> 32;\n  uint32_t c0, c1, d, tmp;\n  uint64_t c;\n  uint32_t a0inv, a1inv;\n\n  if (a0 == 0) {\n    a1inv = base_gf->inverse.w32(base_gf, a1);\n    c0 = base_gf->multiply.w32(base_gf, a1inv, h->prim_poly);\n    c1 = a1inv;\n  } else if (a1 == 0) {\n    c0 = base_gf->inverse.w32(base_gf, a0);\n    c1 = 0;\n  } else {\n    a1inv = base_gf->inverse.w32(base_gf, a1);\n    a0inv = base_gf->inverse.w32(base_gf, a0);\n\n    d = base_gf->multiply.w32(base_gf, a1, a0inv);\n\n    tmp = (base_gf->multiply.w32(base_gf, a1, a0inv) ^ base_gf->multiply.w32(base_gf, a0, a1inv) ^ h->prim_poly);\n    tmp = base_gf->inverse.w32(base_gf, tmp);\n\n    d = base_gf->multiply.w32(base_gf, d, tmp);\n\n    c0 = base_gf->multiply.w32(base_gf, (d^1), a0inv);\n    c1 = base_gf->multiply.w32(base_gf, d, a1inv);\n  }\n\n  c = c0 | ((uint64_t)c1 << 32);\n\n  return c;\n}\n\nstatic\nvoid\ngf_w64_composite_multiply_region(gf_t *gf, void *src, void *dest, gf_val_64_t val, int bytes, int xor)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  gf_t *base_gf = h->base_gf;\n  uint32_t b0 = val & 0x00000000ffffffff;\n  uint32_t b1 = (val & 0xffffffff00000000) >> 32;\n  uint64_t *s64, *d64;\n  uint64_t *top;\n  uint64_t a0, a1, a1b1;\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 8);\n\n  s64 = rd.s_start;\n  d64 = rd.d_start;\n  top = rd.d_top;\n  \n  if (xor) {\n    while (d64 < top) {\n      a0 = *s64 & 0x00000000ffffffff;\n      a1 = (*s64 & 0xffffffff00000000) >> 32;\n      a1b1 = base_gf->multiply.w32(base_gf, a1, b1);\n\n      *d64 ^= ((uint64_t)(base_gf->multiply.w32(base_gf, a0, b0) ^ a1b1) |\n                ((uint64_t)(base_gf->multiply.w32(base_gf, a1, b0) ^ base_gf->multiply.w32(base_gf, a0, b1) ^ base_gf->multiply.w32(base_gf, a1b1, h->prim_poly)) << 32));\n      s64++;\n      d64++;\n    }\n  } else {\n    while (d64 < top) {\n      a0 = *s64 & 0x00000000ffffffff;\n      a1 = (*s64 & 0xffffffff00000000) >> 32;\n      a1b1 = base_gf->multiply.w32(base_gf, a1, b1);\n\n      *d64 = ((base_gf->multiply.w32(base_gf, a0, b0) ^ a1b1) |\n                ((uint64_t)(base_gf->multiply.w32(base_gf, a1, b0) ^ base_gf->multiply.w32(base_gf, a0, b1) ^ base_gf->multiply.w32(base_gf, a1b1, h->prim_poly)) << 32));\n      s64++;\n      d64++;\n    }\n  }\n}\n\nstatic\nvoid\ngf_w64_composite_multiply_region_alt(gf_t *gf, void *src, void *dest, gf_val_64_t val, int bytes, int xor)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  gf_t *base_gf = h->base_gf;\n  gf_val_32_t val0 = val & 0x00000000ffffffff;\n  gf_val_32_t val1 = (val & 0xffffffff00000000) >> 32;\n  uint8_t *slow, *shigh;\n  uint8_t *dlow, *dhigh, *top;\n  int sub_reg_size;\n  gf_region_data rd;\n\n  if (!xor) {\n    memset(dest, 0, bytes);\n  }\n  \n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 32);\n  gf_do_initial_region_alignment(&rd);\n\n  slow = (uint8_t *) rd.s_start;\n  dlow = (uint8_t *) rd.d_start;\n  top = (uint8_t*) rd.d_top;\n  sub_reg_size = (top - dlow)/2;\n  shigh = slow + sub_reg_size;\n  dhigh = dlow + sub_reg_size;\n\n  base_gf->multiply_region.w32(base_gf, slow, dlow, val0, sub_reg_size, xor);\n  base_gf->multiply_region.w32(base_gf, shigh, dlow, val1, sub_reg_size, 1);\n  base_gf->multiply_region.w32(base_gf, slow, dhigh, val1, sub_reg_size, xor);\n  base_gf->multiply_region.w32(base_gf, shigh, dhigh, val0, sub_reg_size, 1);\n  base_gf->multiply_region.w32(base_gf, shigh, dhigh, base_gf->multiply.w32(base_gf, h->prim_poly, val1), sub_reg_size, 1);\n\n  gf_do_final_region_alignment(&rd);\n}\n\n\n\nstatic\nint gf_w64_composite_init(gf_t *gf)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n\n  if (h->region_type & GF_REGION_ALTMAP) {\n    gf->multiply_region.w64 = gf_w64_composite_multiply_region_alt;\n  } else {\n    gf->multiply_region.w64 = gf_w64_composite_multiply_region;\n  }\n\n  gf->multiply.w64 = gf_w64_composite_multiply;\n  gf->divide.w64 = NULL;\n  gf->inverse.w64 = gf_w64_composite_inverse;\n\n  return 1;\n}\n\n#ifdef INTEL_SSSE3\nstatic\n  void\ngf_w64_split_4_64_lazy_sse_altmap_multiply_region(gf_t *gf, void *src, void *dest, uint64_t val, int bytes, int xor)\n{\n  gf_internal_t *h;\n  int i, j, k;\n  uint64_t pp, v, *s64, *d64, *top;\n  __m128i si, tables[16][8], p[8], v0, mask1;\n  struct gf_split_4_64_lazy_data *ld;\n  uint8_t btable[16];\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 128);\n  gf_do_initial_region_alignment(&rd);\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  top = (uint64_t *) rd.d_top;\n \n  ld = (struct gf_split_4_64_lazy_data *) h->private;\n\n  v = val;\n  for (i = 0; i < 16; i++) {\n    ld->tables[i][0] = 0;\n    for (j = 1; j < 16; j <<= 1) {\n      for (k = 0; k < j; k++) {\n        ld->tables[i][k^j] = (v ^ ld->tables[i][k]);\n      }\n      v = (v & GF_FIRST_BIT) ? ((v << 1) ^ pp) : (v << 1);\n    }\n    for (j = 0; j < 8; j++) {\n      for (k = 0; k < 16; k++) {\n        btable[k] = (uint8_t) ld->tables[i][k];\n        ld->tables[i][k] >>= 8;\n      }\n      tables[i][j] = _mm_loadu_si128((__m128i *) btable);\n    }\n  }\n\n  mask1 = _mm_set1_epi8(0xf);\n\n  while (d64 != top) {\n\n    if (xor) {\n      for (i = 0; i < 8; i++) p[i] = _mm_load_si128 ((__m128i *) (d64+i*2));\n    } else {\n      for (i = 0; i < 8; i++) p[i] = _mm_setzero_si128();\n    }\n    i = 0;\n    for (k = 0; k < 8; k++) {\n      v0 = _mm_load_si128((__m128i *) s64); \n      /* MM_PRINT8(\"v\", v0); */\n      s64 += 2;\n      \n      si = _mm_and_si128(v0, mask1);\n  \n      for (j = 0; j < 8; j++) {\n        p[j] = _mm_xor_si128(p[j], _mm_shuffle_epi8(tables[i][j], si));\n      }\n      i++;\n      v0 = _mm_srli_epi32(v0, 4);\n      si = _mm_and_si128(v0, mask1);\n      for (j = 0; j < 8; j++) {\n        p[j] = _mm_xor_si128(p[j], _mm_shuffle_epi8(tables[i][j], si));\n      }\n      i++;\n    }\n    for (i = 0; i < 8; i++) {\n      /* MM_PRINT8(\"v\", p[i]); */\n      _mm_store_si128((__m128i *) d64, p[i]);\n      d64 += 2;\n    }\n  }\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\n#ifdef INTEL_SSE4\nstatic\n  void\ngf_w64_split_4_64_lazy_sse_multiply_region(gf_t *gf, void *src, void *dest, uint64_t val, int bytes, int xor)\n{\n  gf_internal_t *h;\n  int i, j, k;\n  uint64_t pp, v, *s64, *d64, *top;\n  __m128i si, tables[16][8], p[8], st[8], mask1, mask8, mask16, t1;\n  struct gf_split_4_64_lazy_data *ld;\n  uint8_t btable[16];\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 128);\n  gf_do_initial_region_alignment(&rd);\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  top = (uint64_t *) rd.d_top;\n \n  ld = (struct gf_split_4_64_lazy_data *) h->private;\n\n  v = val;\n  for (i = 0; i < 16; i++) {\n    ld->tables[i][0] = 0;\n    for (j = 1; j < 16; j <<= 1) {\n      for (k = 0; k < j; k++) {\n        ld->tables[i][k^j] = (v ^ ld->tables[i][k]);\n      }\n      v = (v & GF_FIRST_BIT) ? ((v << 1) ^ pp) : (v << 1);\n    }\n    for (j = 0; j < 8; j++) {\n      for (k = 0; k < 16; k++) {\n        btable[k] = (uint8_t) ld->tables[i][k];\n        ld->tables[i][k] >>= 8;\n      }\n      tables[i][j] = _mm_loadu_si128((__m128i *) btable);\n    }\n  }\n\n  mask1 = _mm_set1_epi8(0xf);\n  mask8 = _mm_set1_epi16(0xff);\n  mask16 = _mm_set1_epi32(0xffff);\n\n  while (d64 != top) {\n\n    for (i = 0; i < 8; i++) p[i] = _mm_setzero_si128();\n\n    for (k = 0; k < 8; k++) {\n      st[k]  = _mm_load_si128((__m128i *) s64); \n      s64 += 2;\n    }\n\n    for (k = 0; k < 4; k ++) {\n      st[k] = _mm_shuffle_epi32(st[k], _MM_SHUFFLE(3,1,2,0));\n      st[k+4] = _mm_shuffle_epi32(st[k+4], _MM_SHUFFLE(2,0,3,1));\n      t1 = _mm_blend_epi16(st[k], st[k+4], 0xf0);\n      st[k] = _mm_srli_si128(st[k], 8);\n      st[k+4] = _mm_slli_si128(st[k+4], 8);\n      st[k+4] = _mm_blend_epi16(st[k], st[k+4], 0xf0);\n      st[k] = t1;\n    }\n\n/*\n    printf(\"After pack pass 1\\n\");\n    for (k = 0; k < 8; k++) {\n      MM_PRINT8(\"v\", st[k]);\n    }\n    printf(\"\\n\");\n */\n    \n    t1 = _mm_packus_epi32(_mm_and_si128(st[0], mask16), _mm_and_si128(st[2], mask16));\n    st[2] = _mm_packus_epi32(_mm_srli_epi32(st[0], 16), _mm_srli_epi32(st[2], 16));\n    st[0] = t1;\n    t1 = _mm_packus_epi32(_mm_and_si128(st[1], mask16), _mm_and_si128(st[3], mask16));\n    st[3] = _mm_packus_epi32(_mm_srli_epi32(st[1], 16), _mm_srli_epi32(st[3], 16));\n    st[1] = t1;\n    t1 = _mm_packus_epi32(_mm_and_si128(st[4], mask16), _mm_and_si128(st[6], mask16));\n    st[6] = _mm_packus_epi32(_mm_srli_epi32(st[4], 16), _mm_srli_epi32(st[6], 16));\n    st[4] = t1;\n    t1 = _mm_packus_epi32(_mm_and_si128(st[5], mask16), _mm_and_si128(st[7], mask16));\n    st[7] = _mm_packus_epi32(_mm_srli_epi32(st[5], 16), _mm_srli_epi32(st[7], 16));\n    st[5] = t1;\n\n/*\n    printf(\"After pack pass 2\\n\");\n    for (k = 0; k < 8; k++) {\n      MM_PRINT8(\"v\", st[k]);\n    }\n    printf(\"\\n\");\n */\n    t1 = _mm_packus_epi16(_mm_and_si128(st[0], mask8), _mm_and_si128(st[1], mask8));\n    st[1] = _mm_packus_epi16(_mm_srli_epi16(st[0], 8), _mm_srli_epi16(st[1], 8));\n    st[0] = t1;\n    t1 = _mm_packus_epi16(_mm_and_si128(st[2], mask8), _mm_and_si128(st[3], mask8));\n    st[3] = _mm_packus_epi16(_mm_srli_epi16(st[2], 8), _mm_srli_epi16(st[3], 8));\n    st[2] = t1;\n    t1 = _mm_packus_epi16(_mm_and_si128(st[4], mask8), _mm_and_si128(st[5], mask8));\n    st[5] = _mm_packus_epi16(_mm_srli_epi16(st[4], 8), _mm_srli_epi16(st[5], 8));\n    st[4] = t1;\n    t1 = _mm_packus_epi16(_mm_and_si128(st[6], mask8), _mm_and_si128(st[7], mask8));\n    st[7] = _mm_packus_epi16(_mm_srli_epi16(st[6], 8), _mm_srli_epi16(st[7], 8));\n    st[6] = t1;\n\n/*\n    printf(\"After final pack pass 2\\n\");\n    for (k = 0; k < 8; k++) {\n      MM_PRINT8(\"v\", st[k]);\n    }\n */\n    i = 0;\n    for (k = 0; k < 8; k++) {\n      si = _mm_and_si128(st[k], mask1);\n  \n      for (j = 0; j < 8; j++) {\n        p[j] = _mm_xor_si128(p[j], _mm_shuffle_epi8(tables[i][j], si));\n      }\n      i++;\n      st[k] = _mm_srli_epi32(st[k], 4);\n      si = _mm_and_si128(st[k], mask1);\n      for (j = 0; j < 8; j++) {\n        p[j] = _mm_xor_si128(p[j], _mm_shuffle_epi8(tables[i][j], si));\n      }\n      i++;\n    }\n\n    t1 = _mm_unpacklo_epi8(p[0], p[1]);\n    p[1] = _mm_unpackhi_epi8(p[0], p[1]);\n    p[0] = t1;\n    t1 = _mm_unpacklo_epi8(p[2], p[3]);\n    p[3] = _mm_unpackhi_epi8(p[2], p[3]);\n    p[2] = t1;\n    t1 = _mm_unpacklo_epi8(p[4], p[5]);\n    p[5] = _mm_unpackhi_epi8(p[4], p[5]);\n    p[4] = t1;\n    t1 = _mm_unpacklo_epi8(p[6], p[7]);\n    p[7] = _mm_unpackhi_epi8(p[6], p[7]);\n    p[6] = t1;\n\n/*\n    printf(\"After unpack pass 1:\\n\");\n    for (i = 0; i < 8; i++) {\n      MM_PRINT8(\"v\", p[i]);\n    }\n */\n\n    t1 = _mm_unpacklo_epi16(p[0], p[2]);\n    p[2] = _mm_unpackhi_epi16(p[0], p[2]);\n    p[0] = t1;\n    t1 = _mm_unpacklo_epi16(p[1], p[3]);\n    p[3] = _mm_unpackhi_epi16(p[1], p[3]);\n    p[1] = t1;\n    t1 = _mm_unpacklo_epi16(p[4], p[6]);\n    p[6] = _mm_unpackhi_epi16(p[4], p[6]);\n    p[4] = t1;\n    t1 = _mm_unpacklo_epi16(p[5], p[7]);\n    p[7] = _mm_unpackhi_epi16(p[5], p[7]);\n    p[5] = t1;\n\n/*\n    printf(\"After unpack pass 2:\\n\");\n    for (i = 0; i < 8; i++) {\n      MM_PRINT8(\"v\", p[i]);\n    }\n */\n\n    t1 = _mm_unpacklo_epi32(p[0], p[4]);\n    p[4] = _mm_unpackhi_epi32(p[0], p[4]);\n    p[0] = t1;\n    t1 = _mm_unpacklo_epi32(p[1], p[5]);\n    p[5] = _mm_unpackhi_epi32(p[1], p[5]);\n    p[1] = t1;\n    t1 = _mm_unpacklo_epi32(p[2], p[6]);\n    p[6] = _mm_unpackhi_epi32(p[2], p[6]);\n    p[2] = t1;\n    t1 = _mm_unpacklo_epi32(p[3], p[7]);\n    p[7] = _mm_unpackhi_epi32(p[3], p[7]);\n    p[3] = t1;\n\n    if (xor) {\n      for (i = 0; i < 8; i++) {\n        t1 = _mm_load_si128((__m128i *) d64);\n        _mm_store_si128((__m128i *) d64, _mm_xor_si128(p[i], t1));\n        d64 += 2;\n      }\n    } else {\n      for (i = 0; i < 8; i++) {\n        _mm_store_si128((__m128i *) d64, p[i]);\n        d64 += 2;\n      }\n    }\n\n  }\n\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\n#define GF_MULTBY_TWO(p) (((p) & GF_FIRST_BIT) ? (((p) << 1) ^ h->prim_poly) : (p) << 1);\n\nstatic\nint gf_w64_split_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  struct gf_split_4_64_lazy_data *d4;\n  struct gf_split_8_64_lazy_data *d8;\n  struct gf_split_8_8_data *d88;\n  struct gf_split_16_64_lazy_data *d16;\n  uint64_t p, basep;\n  int exp, i, j;\n\n  h = (gf_internal_t *) gf->scratch;\n\n  /* Defaults */\n\n  gf->multiply_region.w64 = gf_w64_multiply_region_from_single;\n\n  gf->multiply.w64 = gf_w64_bytwo_p_multiply; \n\n#if defined(INTEL_SSE4_PCLMUL) \n  if ((!(h->region_type & GF_REGION_NOSIMD) &&\n     (h->arg1 == 64 || h->arg2 == 64)) ||\n     h->mult_type == GF_MULT_DEFAULT){\n   \n    if ((0xfffffffe00000000ULL & h->prim_poly) == 0){ \n      gf->multiply.w64 = gf_w64_clm_multiply_2;\n      gf->multiply_region.w64 = gf_w64_clm_multiply_region_from_single_2; \n    }else if((0xfffe000000000000ULL & h->prim_poly) == 0){\n      gf->multiply.w64 = gf_w64_clm_multiply_4;\n      gf->multiply_region.w64 = gf_w64_clm_multiply_region_from_single_4; \n    }else{\n      return 0;\n    }\n  }\n#endif\n\n  gf->inverse.w64 = gf_w64_euclid;\n\n  /* Allen: set region pointers for default mult type. Single pointers are\n   * taken care of above (explicitly for sse, implicitly for no sse). */\n\n#if defined(INTEL_SSE4) || defined(ARCH_AARCH64)\n  if (h->mult_type == GF_MULT_DEFAULT) {\n    d4 = (struct gf_split_4_64_lazy_data *) h->private;\n    d4->last_value = 0;\n#if defined(INTEL_SSE4)\n    gf->multiply_region.w64 = gf_w64_split_4_64_lazy_sse_multiply_region; \n#elif defined(ARCH_AARCH64)\n    gf_w64_neon_split_init(gf);\n#endif\n  }\n#else\n  if (h->mult_type == GF_MULT_DEFAULT) {\n    d8 = (struct gf_split_8_64_lazy_data *) h->private;\n    d8->last_value = 0;\n    gf->multiply_region.w64 = gf_w64_split_8_64_lazy_multiply_region;\n  }\n#endif\n\n  if ((h->arg1 == 4 && h->arg2 == 64) || (h->arg1 == 64 && h->arg2 == 4)) {\n    d4 = (struct gf_split_4_64_lazy_data *) h->private;\n    d4->last_value = 0;\n\n    if((h->region_type & GF_REGION_ALTMAP) && (h->region_type & GF_REGION_NOSIMD)) return 0;\n    if(h->region_type & GF_REGION_ALTMAP)\n    {\n      #ifdef INTEL_SSSE3\n        gf->multiply_region.w64 = gf_w64_split_4_64_lazy_sse_altmap_multiply_region; \n      #elif defined(ARCH_AARCH64)\n        gf_w64_neon_split_init(gf);\n      #else\n        return 0;\n      #endif\n    }\n    else //no altmap\n    {\n      #if defined(INTEL_SSE4) || defined(ARCH_AARCH64)\n        if(h->region_type & GF_REGION_NOSIMD)\n          gf->multiply_region.w64 = gf_w64_split_4_64_lazy_multiply_region;\n        else\n        #if defined(INTEL_SSE4)\n          gf->multiply_region.w64 = gf_w64_split_4_64_lazy_sse_multiply_region;\n        #elif defined(ARCH_AARCH64)\n          gf_w64_neon_split_init(gf);\n        #endif\n      #else\n        gf->multiply_region.w64 = gf_w64_split_4_64_lazy_multiply_region;\n        if(h->region_type & GF_REGION_SIMD)\n          return 0;\n      #endif\n    }\n  }\n  if ((h->arg1 == 8 && h->arg2 == 64) || (h->arg1 == 64 && h->arg2 == 8)) {\n    d8 = (struct gf_split_8_64_lazy_data *) h->private;\n    d8->last_value = 0;\n    gf->multiply_region.w64 = gf_w64_split_8_64_lazy_multiply_region;\n  }\n  if ((h->arg1 == 16 && h->arg2 == 64) || (h->arg1 == 64 && h->arg2 == 16)) {\n    d16 = (struct gf_split_16_64_lazy_data *) h->private;\n    d16->last_value = 0;\n    gf->multiply_region.w64 = gf_w64_split_16_64_lazy_multiply_region;\n  }\n  if ((h->arg1 == 8 && h->arg2 == 8)) {\n    d88 = (struct gf_split_8_8_data *) h->private;\n    gf->multiply.w64 = gf_w64_split_8_8_multiply;\n\n    /* The performance of this guy sucks, so don't bother with a region op */\n    \n    basep = 1;\n    for (exp = 0; exp < 15; exp++) {\n      for (j = 0; j < 256; j++) d88->tables[exp][0][j] = 0;\n      for (i = 0; i < 256; i++) d88->tables[exp][i][0] = 0;\n      d88->tables[exp][1][1] = basep;\n      for (i = 2; i < 256; i++) {\n        if (i&1) {\n          p = d88->tables[exp][i^1][1];\n          d88->tables[exp][i][1] = p ^ basep;\n        } else {\n          p = d88->tables[exp][i>>1][1];\n          d88->tables[exp][i][1] = GF_MULTBY_TWO(p);\n        }\n      }\n      for (i = 1; i < 256; i++) {\n        p = d88->tables[exp][i][1];\n        for (j = 1; j < 256; j++) {\n          if (j&1) {\n            d88->tables[exp][i][j] = d88->tables[exp][i][j^1] ^ p;\n          } else {\n            d88->tables[exp][i][j] = GF_MULTBY_TWO(d88->tables[exp][i][j>>1]);\n          }\n        }\n      }\n      for (i = 0; i < 8; i++) basep = GF_MULTBY_TWO(basep);\n    }\n  }\n  return 1;\n}\n\nint gf_w64_scratch_size(int mult_type, int region_type, int divide_type, int arg1, int arg2)\n{\n  switch(mult_type)\n  {\n    case GF_MULT_SHIFT:\n      return sizeof(gf_internal_t);\n      break;\n    case GF_MULT_CARRY_FREE:\n      return sizeof(gf_internal_t);\n      break;\n    case GF_MULT_BYTWO_p:\n    case GF_MULT_BYTWO_b:\n      return sizeof(gf_internal_t);\n      break;\n\n    case GF_MULT_DEFAULT:\n\n      /* Allen: set the *local* arg1 and arg2, just for scratch size purposes,\n       * then fall through to split table scratch size code. */\n\n#if defined(INTEL_SSE4) || defined(ARCH_AARCH64)\n      arg1 = 64;\n      arg2 = 4;\n#else\n      arg1 = 64;\n      arg2 = 8;\n#endif\n      /* fallthrough */\n\n    case GF_MULT_SPLIT_TABLE:\n        if (arg1 == 8 && arg2 == 8) {\n          return sizeof(gf_internal_t) + sizeof(struct gf_split_8_8_data) + 64;\n        }\n        if ((arg1 == 16 && arg2 == 64) || (arg2 == 16 && arg1 == 64)) {\n          return sizeof(gf_internal_t) + sizeof(struct gf_split_16_64_lazy_data) + 64;\n        }\n        if ((arg1 == 8 && arg2 == 64) || (arg2 == 8 && arg1 == 64)) {\n          return sizeof(gf_internal_t) + sizeof(struct gf_split_8_64_lazy_data) + 64;\n        }\n\n        if ((arg1 == 64 && arg2 == 4) || (arg1 == 4 && arg2 == 64)) {\n          return sizeof(gf_internal_t) + sizeof(struct gf_split_4_64_lazy_data) + 64;\n        }\n        return 0;\n    case GF_MULT_GROUP:\n      return sizeof(gf_internal_t) + sizeof(struct gf_w64_group_data) +\n               sizeof(uint64_t) * (1 << arg1) +\n               sizeof(uint64_t) * (1 << arg2) + 64;\n      break;\n    case GF_MULT_COMPOSITE:\n      if (arg1 == 2) return sizeof(gf_internal_t) + 64;\n      return 0;\n      break;\n    default:\n      return 0;\n   }\n}\n\nint gf_w64_init(gf_t *gf)\n{\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  \n  /* Allen: set default primitive polynomial / irreducible polynomial if needed */\n\n  /* Omitting the leftmost 1 as in w=32 */\n\n  if (h->prim_poly == 0) {\n    if (h->mult_type == GF_MULT_COMPOSITE) {\n      h->prim_poly = gf_composite_get_default_poly(h->base_gf);\n      if (h->prim_poly == 0) return 0; /* This shouldn't happen */\n    } else {\n      h->prim_poly = 0x1b;\n    } \n  }\n\n  gf->multiply.w64 = NULL;\n  gf->divide.w64 = NULL;\n  gf->inverse.w64 = NULL;\n  gf->multiply_region.w64 = NULL;\n\n  switch(h->mult_type) {\n    case GF_MULT_CARRY_FREE:  if (gf_w64_cfm_init(gf) == 0) return 0; break;\n    case GF_MULT_SHIFT:       if (gf_w64_shift_init(gf) == 0) return 0; break;\n    case GF_MULT_COMPOSITE:   if (gf_w64_composite_init(gf) == 0) return 0; break;\n    case GF_MULT_DEFAULT:\n    case GF_MULT_SPLIT_TABLE: if (gf_w64_split_init(gf) == 0) return 0; break; \n    case GF_MULT_GROUP:       if (gf_w64_group_init(gf) == 0) return 0; break; \n    case GF_MULT_BYTWO_p:\n    case GF_MULT_BYTWO_b:     if (gf_w64_bytwo_init(gf) == 0) return 0; break;\n    default: return 0;\n  }\n  if (h->divide_type == GF_DIVIDE_EUCLID) {\n    gf->divide.w64 = gf_w64_divide_from_inverse;\n    gf->inverse.w64 = gf_w64_euclid;\n  } \n\n  if (gf->inverse.w64 != NULL && gf->divide.w64 == NULL) {\n    gf->divide.w64 = gf_w64_divide_from_inverse;\n  }\n  if (gf->inverse.w64 == NULL && gf->divide.w64 != NULL) {\n    gf->inverse.w64 = gf_w64_inverse_from_divide;\n  }\n\n  if (h->region_type == GF_REGION_CAUCHY) return 0;\n\n  if (h->region_type & GF_REGION_ALTMAP) {\n    if (h->mult_type == GF_MULT_COMPOSITE) {\n      gf->extract_word.w64 = gf_w64_composite_extract_word;\n    } else if (h->mult_type == GF_MULT_SPLIT_TABLE) {\n      gf->extract_word.w64 = gf_w64_split_extract_word;\n    }\n  } else {\n    gf->extract_word.w64 = gf_w64_extract_word;\n  }\n\n  return 1;\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/src/gf_w8.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_w8.c\n *\n * Routines for 8-bit Galois fields\n */\n\n#include \"gf_int.h\"\n#include \"gf_w8.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <assert.h>\n\n#define AB2(ip, am1 ,am2, b, t1, t2) {\\\n  t1 = (b << 1) & am1;\\\n  t2 = b & am2; \\\n  t2 = ((t2 << 1) - (t2 >> (GF_FIELD_WIDTH-1))); \\\n  b = (t1 ^ (t2 & ip));}\n\n#define SSE_AB2(pp, m1 ,m2, va, t1, t2) {\\\n          t1 = _mm_and_si128(_mm_slli_epi64(va, 1), m1); \\\n          t2 = _mm_and_si128(va, m2); \\\n          t2 = _mm_sub_epi64 (_mm_slli_epi64(t2, 1), _mm_srli_epi64(t2, (GF_FIELD_WIDTH-1))); \\\n          va = _mm_xor_si128(t1, _mm_and_si128(t2, pp)); }\n\n#define MM_PRINT(s, r) { uint8_t blah[16], ii; printf(\"%-12s\", s); _mm_storeu_si128((__m128i *)blah, r); for (ii = 0; ii < 16; ii += 2) printf(\"  %02x %02x\", blah[15-ii], blah[14-ii]); printf(\"\\n\"); }\n\nstatic\ninline\nuint32_t gf_w8_inverse_from_divide(gf_t* gf, uint32_t a)\n{\n  return gf->divide.w32(gf, 1, a);\n}\n\nstatic\ninline\nuint32_t gf_w8_divide_from_inverse(gf_t* gf, uint32_t a, uint32_t b)\n{\n  b = gf->inverse.w32(gf, b);\n  return gf->multiply.w32(gf, a, b);\n}\n\nstatic\ninline\nuint32_t gf_w8_euclid(gf_t* gf, uint32_t b)\n{\n  uint32_t e_i, e_im1, e_ip1;\n  uint32_t d_i, d_im1, d_ip1;\n  uint32_t y_i, y_im1, y_ip1;\n  uint32_t c_i;\n\n  if (b == 0) {\n    return -1;\n  }\n\n  e_im1 = ((gf_internal_t*)(gf->scratch))->prim_poly;\n  e_i = b;\n  d_im1 = 8;\n\n  for (d_i = d_im1; ((1 << d_i) & e_i) == 0; d_i--) ;\n\n  y_i = 1;\n  y_im1 = 0;\n\n  while (e_i != 1) {\n    e_ip1 = e_im1;\n    d_ip1 = d_im1;\n    c_i = 0;\n\n    while (d_ip1 >= d_i) {\n      c_i ^= (1 << (d_ip1 - d_i));\n      e_ip1 ^= (e_i << (d_ip1 - d_i));\n\n      if (e_ip1 == 0) {\n        return 0;\n      }\n\n      while ((e_ip1 & (1 << d_ip1)) == 0) {\n        d_ip1--;\n      }\n    }\n\n    y_ip1 = y_im1 ^ gf->multiply.w32(gf, c_i, y_i);\n    y_im1 = y_i;\n    y_i = y_ip1;\n    e_im1 = e_i;\n    d_im1 = d_i;\n    e_i = e_ip1;\n    d_i = d_ip1;\n  }\n\n  return y_i;\n}\n\nstatic\ngf_val_32_t gf_w8_extract_word(gf_t* gf, void* start, int bytes, int index)\n{\n  uint8_t* r8;\n  r8 = (uint8_t*) start;\n  return r8[index];\n}\n\nstatic\ngf_val_32_t gf_w8_composite_extract_word(gf_t* gf, void* start, int bytes,\n    int index)\n{\n  int sub_size;\n  gf_internal_t* h;\n  uint8_t* r8, *top;\n  uint8_t a, b;\n  gf_region_data rd;\n  h = (gf_internal_t*) gf->scratch;\n  gf_set_region_data(&rd, gf, start, start, bytes, 0, 0, 32);\n  r8 = (uint8_t*) start;\n\n  if (r8 + index < (uint8_t*) rd.d_start) {\n    return r8[index];\n  }\n\n  if (r8 + index >= (uint8_t*) rd.d_top) {\n    return r8[index];\n  }\n\n  index -= (((uint8_t*) rd.d_start) - r8);\n  r8 = (uint8_t*) rd.d_start;\n  top = (uint8_t*) rd.d_top;\n  sub_size = (top - r8) / 2;\n  a = h->base_gf->extract_word.w32(h->base_gf, r8, sub_size, index);\n  b = h->base_gf->extract_word.w32(h->base_gf, r8 + sub_size, sub_size, index);\n  return (a | (b << 4));\n}\n\nstatic\ninline\nuint32_t gf_w8_matrix(gf_t* gf, uint32_t b)\n{\n  return gf_bitmatrix_inverse(b, 8, ((gf_internal_t*)(gf->scratch))->prim_poly);\n}\n\n\nstatic\ninline\ngf_val_32_t\ngf_w8_clm_multiply_2(gf_t* gf, gf_val_32_t a8, gf_val_32_t b8)\n{\n  gf_val_32_t rv = 0;\n#if defined(INTEL_SSE4_PCLMUL)\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t* h = gf->scratch;\n  a = _mm_insert_epi32(_mm_setzero_si128(), a8, 0);\n  b = _mm_insert_epi32(a, b8, 0);\n  prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0x1ffULL));\n  /* Do the initial multiply */\n  result = _mm_clmulepi64_si128(a, b, 0);\n  /* Ben: Do prim_poly reduction twice. We are guaranteed that we will only\n     have to do the reduction at most twice, because (w-2)/z == 2. Where\n     z is equal to the number of zeros after the leading 1\n\n     _mm_clmulepi64_si128 is the carryless multiply operation. Here\n     _mm_srli_si128 shifts the result to the right by 1 byte. This allows\n     us to multiply the prim_poly by the leading bits of the result. We\n     then xor the result of that operation back with the result.*/\n  w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n  result = _mm_xor_si128(result, w);\n  w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n  result = _mm_xor_si128(result, w);\n  /* Extracts 32 bit value from result. */\n  rv = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n#endif\n  return rv;\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w8_clm_multiply_3(gf_t* gf, gf_val_32_t a8, gf_val_32_t b8)\n{\n  gf_val_32_t rv = 0;\n#if defined(INTEL_SSE4_PCLMUL)\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t* h = gf->scratch;\n  a = _mm_insert_epi32(_mm_setzero_si128(), a8, 0);\n  b = _mm_insert_epi32(a, b8, 0);\n  prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0x1ffULL));\n  /* Do the initial multiply */\n  result = _mm_clmulepi64_si128(a, b, 0);\n  w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n  result = _mm_xor_si128(result, w);\n  w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n  result = _mm_xor_si128(result, w);\n  w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n  result = _mm_xor_si128(result, w);\n  /* Extracts 32 bit value from result. */\n  rv = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n#endif\n  return rv;\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w8_clm_multiply_4(gf_t* gf, gf_val_32_t a8, gf_val_32_t b8)\n{\n  gf_val_32_t rv = 0;\n#if defined(INTEL_SSE4_PCLMUL)\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t* h = gf->scratch;\n  a = _mm_insert_epi32(_mm_setzero_si128(), a8, 0);\n  b = _mm_insert_epi32(a, b8, 0);\n  prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0x1ffULL));\n  /* Do the initial multiply */\n  result = _mm_clmulepi64_si128(a, b, 0);\n  w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n  result = _mm_xor_si128(result, w);\n  w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n  result = _mm_xor_si128(result, w);\n  w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n  result = _mm_xor_si128(result, w);\n  w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n  result = _mm_xor_si128(result, w);\n  /* Extracts 32 bit value from result. */\n  rv = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n#endif\n  return rv;\n}\n\n\nstatic\nvoid\ngf_w8_multiply_region_from_single(gf_t* gf, void* src, void* dest,\n                                  gf_val_32_t val, int bytes, int\n                                  xor)\n{\n  gf_region_data rd;\n  uint8_t* s8;\n  uint8_t* d8;\n\n  if (val == 0) {\n    gf_multby_zero(dest, bytes, xor);\n    return;\n  }\n\n  if (val == 1) {\n    gf_multby_one(src, dest, bytes, xor);\n    return;\n  }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 1);\n  gf_do_initial_region_alignment(&rd);\n  s8 = (uint8_t*) rd.s_start;\n  d8 = (uint8_t*) rd.d_start;\n\n  if (xor) {\n    while (d8 < ((uint8_t*) rd.d_top)) {\n      *d8 ^= gf->multiply.w32(gf, val, *s8);\n      d8++;\n      s8++;\n    }\n  } else {\n    while (d8 < ((uint8_t*) rd.d_top)) {\n      *d8 = gf->multiply.w32(gf, val, *s8);\n      d8++;\n      s8++;\n    }\n  }\n\n  gf_do_final_region_alignment(&rd);\n}\n\n#if defined(INTEL_SSE4_PCLMUL)\nstatic\nvoid\ngf_w8_clm_multiply_region_from_single_2(gf_t* gf, void* src, void* dest,\n                                        gf_val_32_t val, int bytes, int\n                                        xor)\n{\n  gf_region_data rd;\n  uint8_t* s8;\n  uint8_t* d8;\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t* h = gf->scratch;\n  prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0x1ffULL));\n\n  if (val == 0) {\n    gf_multby_zero(dest, bytes, xor);\n    return;\n  }\n\n  if (val == 1) {\n    gf_multby_one(src, dest, bytes, xor);\n    return;\n  }\n\n  a = _mm_insert_epi32(_mm_setzero_si128(), val, 0);\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 1);\n  gf_do_initial_region_alignment(&rd);\n  s8 = (uint8_t*) rd.s_start;\n  d8 = (uint8_t*) rd.d_start;\n\n  if (xor) {\n    while (d8 < ((uint8_t*) rd.d_top)) {\n      b = _mm_insert_epi32(a, (gf_val_32_t)(*s8), 0);\n      result = _mm_clmulepi64_si128(a, b, 0);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      *d8 ^= ((gf_val_32_t)_mm_extract_epi32(result, 0));\n      d8++;\n      s8++;\n    }\n  } else {\n    while (d8 < ((uint8_t*) rd.d_top)) {\n      b = _mm_insert_epi32(a, (gf_val_32_t)(*s8), 0);\n      result = _mm_clmulepi64_si128(a, b, 0);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      *d8 = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n      d8++;\n      s8++;\n    }\n  }\n\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\n#if defined(INTEL_SSE4_PCLMUL)\nstatic\nvoid\ngf_w8_clm_multiply_region_from_single_3(gf_t* gf, void* src, void* dest,\n                                        gf_val_32_t val, int bytes, int\n                                        xor)\n{\n  gf_region_data rd;\n  uint8_t* s8;\n  uint8_t* d8;\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t* h = gf->scratch;\n  prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0x1ffULL));\n\n  if (val == 0) {\n    gf_multby_zero(dest, bytes, xor);\n    return;\n  }\n\n  if (val == 1) {\n    gf_multby_one(src, dest, bytes, xor);\n    return;\n  }\n\n  a = _mm_insert_epi32(_mm_setzero_si128(), val, 0);\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 1);\n  gf_do_initial_region_alignment(&rd);\n  s8 = (uint8_t*) rd.s_start;\n  d8 = (uint8_t*) rd.d_start;\n\n  if (xor) {\n    while (d8 < ((uint8_t*) rd.d_top)) {\n      b = _mm_insert_epi32(a, (gf_val_32_t)(*s8), 0);\n      result = _mm_clmulepi64_si128(a, b, 0);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      *d8 ^= ((gf_val_32_t)_mm_extract_epi32(result, 0));\n      d8++;\n      s8++;\n    }\n  } else {\n    while (d8 < ((uint8_t*) rd.d_top)) {\n      b = _mm_insert_epi32(a, (gf_val_32_t)(*s8), 0);\n      result = _mm_clmulepi64_si128(a, b, 0);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      *d8 = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n      d8++;\n      s8++;\n    }\n  }\n\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\n#if defined(INTEL_SSE4_PCLMUL)\nstatic\nvoid\ngf_w8_clm_multiply_region_from_single_4(gf_t* gf, void* src, void* dest,\n                                        gf_val_32_t val, int bytes, int\n                                        xor)\n{\n  gf_region_data rd;\n  uint8_t* s8;\n  uint8_t* d8;\n  __m128i         a, b;\n  __m128i         result;\n  __m128i         prim_poly;\n  __m128i         w;\n  gf_internal_t* h = gf->scratch;\n  prim_poly = _mm_set_epi32(0, 0, 0, (uint32_t)(h->prim_poly & 0x1ffULL));\n\n  if (val == 0) {\n    gf_multby_zero(dest, bytes, xor);\n    return;\n  }\n\n  if (val == 1) {\n    gf_multby_one(src, dest, bytes, xor);\n    return;\n  }\n\n  a = _mm_insert_epi32(_mm_setzero_si128(), val, 0);\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 1);\n  gf_do_initial_region_alignment(&rd);\n  s8 = (uint8_t*) rd.s_start;\n  d8 = (uint8_t*) rd.d_start;\n\n  if (xor) {\n    while (d8 < ((uint8_t*) rd.d_top)) {\n      b = _mm_insert_epi32(a, (gf_val_32_t)(*s8), 0);\n      result = _mm_clmulepi64_si128(a, b, 0);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      *d8 ^= ((gf_val_32_t)_mm_extract_epi32(result, 0));\n      d8++;\n      s8++;\n    }\n  } else {\n    while (d8 < ((uint8_t*) rd.d_top)) {\n      b = _mm_insert_epi32(a, (gf_val_32_t)(*s8), 0);\n      result = _mm_clmulepi64_si128(a, b, 0);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      w = _mm_clmulepi64_si128(prim_poly, _mm_srli_si128(result, 1), 0);\n      result = _mm_xor_si128(result, w);\n      *d8 = ((gf_val_32_t)_mm_extract_epi32(result, 0));\n      d8++;\n      s8++;\n    }\n  }\n\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\n/* ------------------------------------------------------------\nIMPLEMENTATION: SHIFT:\n\nJSP: The world's dumbest multiplication algorithm.  I only\ninclude it for completeness.  It does have the feature that it requires no\nextra memory.\n */\n\nstatic\ninline\nuint32_t\ngf_w8_shift_multiply(gf_t* gf, uint32_t a8, uint32_t b8)\n{\n  uint16_t product, i, pp, a, b;\n  gf_internal_t* h;\n  a = a8;\n  b = b8;\n  h = (gf_internal_t*) gf->scratch;\n  pp = h->prim_poly;\n  product = 0;\n\n  for (i = 0; i < GF_FIELD_WIDTH; i++) {\n    if (a & (1 << i)) {\n      product ^= (b << i);\n    }\n  }\n\n  for (i = (GF_FIELD_WIDTH * 2 - 2); i >= GF_FIELD_WIDTH; i--) {\n    if (product & (1 << i)) {\n      product ^= (pp << (i - GF_FIELD_WIDTH));\n    }\n  }\n\n  return product;\n}\n\nstatic\nint gf_w8_cfm_init(gf_t* gf)\n{\n#if defined(INTEL_SSE4_PCLMUL)\n  gf_internal_t* h;\n  h = (gf_internal_t*) gf->scratch;\n\n  if ((0xe0 & h->prim_poly) == 0) {\n    gf->multiply.w32 = gf_w8_clm_multiply_2;\n    gf->multiply_region.w32 = gf_w8_clm_multiply_region_from_single_2;\n  } else if ((0xc0 & h->prim_poly) == 0) {\n    gf->multiply.w32 = gf_w8_clm_multiply_3;\n    gf->multiply_region.w32 = gf_w8_clm_multiply_region_from_single_3;\n  } else if ((0x80 & h->prim_poly) == 0) {\n    gf->multiply.w32 = gf_w8_clm_multiply_4;\n    gf->multiply_region.w32 = gf_w8_clm_multiply_region_from_single_4;\n  } else {\n    return 0;\n  }\n\n  return 1;\n#elif defined(ARM_NEON)\n  return gf_w8_neon_cfm_init(gf);\n#endif\n  return 0;\n}\n\nstatic\nint gf_w8_shift_init(gf_t* gf)\n{\n  gf->multiply.w32 =\n    gf_w8_shift_multiply;  /* The others will be set automatically */\n  return 1;\n}\n\n/* ------------------------------------------------------------\nIMPLEMENTATION: LOG_TABLE:\n\nJSP: Kevin wrote this, and I'm converting it to my structure.\n*/\n\nstatic\ninline\nuint32_t\ngf_w8_logzero_multiply(gf_t* gf, uint32_t a, uint32_t b)\n{\n  struct gf_w8_logzero_table_data* ltd;\n  ltd = (struct gf_w8_logzero_table_data*)((gf_internal_t*) gf->scratch)->private;\n  return ltd->antilog_tbl[ltd->log_tbl[a] + ltd->log_tbl[b]];\n}\n\nstatic\ninline\nuint32_t\ngf_w8_logzero_divide(gf_t* gf, uint32_t a, uint32_t b)\n{\n  struct gf_w8_logzero_table_data* ltd;\n  ltd = (struct gf_w8_logzero_table_data*)((gf_internal_t*) gf->scratch)->private;\n  return ltd->div_tbl[ltd->log_tbl[a] - ltd->log_tbl[b]];\n}\n\nstatic\ninline\nuint32_t\ngf_w8_logzero_small_multiply(gf_t* gf, uint32_t a, uint32_t b)\n{\n  struct gf_w8_logzero_small_table_data* std;\n  std = (struct gf_w8_logzero_small_table_data*)((gf_internal_t*)\n        gf->scratch)->private;\n\n  if (b == 0) {\n    return 0;\n  }\n\n  return std->antilog_tbl[std->log_tbl[a] + std->log_tbl[b]];\n}\n\nstatic\ninline\nuint32_t\ngf_w8_logzero_small_divide(gf_t* gf, uint32_t a, uint32_t b)\n{\n  struct gf_w8_logzero_small_table_data* std;\n  std = (struct gf_w8_logzero_small_table_data*)((gf_internal_t*)\n        gf->scratch)->private;\n  return std->div_tbl[std->log_tbl[a] - std->log_tbl[b]];\n}\n\nstatic\ninline\nuint32_t\ngf_w8_log_multiply(gf_t* gf, uint32_t a, uint32_t b)\n{\n  struct gf_w8_logtable_data* ltd;\n  ltd = (struct gf_w8_logtable_data*)((gf_internal_t*) gf->scratch)->private;\n  return (a == 0 ||\n          b == 0) ? 0 : ltd->antilog_tbl[(unsigned)(ltd->log_tbl[a] + ltd->log_tbl[b])];\n}\n\nstatic\ninline\nuint32_t\ngf_w8_log_divide(gf_t* gf, uint32_t a, uint32_t b)\n{\n  int log_sum = 0;\n  struct gf_w8_logtable_data* ltd;\n\n  if (a == 0 || b == 0) {\n    return 0;\n  }\n\n  ltd = (struct gf_w8_logtable_data*)((gf_internal_t*) gf->scratch)->private;\n  log_sum = ltd->log_tbl[a] - ltd->log_tbl[b] + (GF_MULT_GROUP_SIZE);\n  return (ltd->antilog_tbl[log_sum]);\n}\n\nstatic\nuint32_t\ngf_w8_log_inverse(gf_t* gf, uint32_t a)\n{\n  struct gf_w8_logtable_data* ltd;\n  ltd = (struct gf_w8_logtable_data*)((gf_internal_t*) gf->scratch)->private;\n  return (ltd->inv_tbl[a]);\n}\n\nstatic\nuint32_t\ngf_w8_logzero_inverse(gf_t* gf, uint32_t a)\n{\n  struct gf_w8_logzero_table_data* ltd;\n  ltd = (struct gf_w8_logzero_table_data*)((gf_internal_t*) gf->scratch)->private;\n  return (ltd->inv_tbl[a]);\n}\n\nstatic\nuint32_t\ngf_w8_logzero_small_inverse(gf_t* gf, uint32_t a)\n{\n  struct gf_w8_logzero_small_table_data* std;\n  std = (struct gf_w8_logzero_small_table_data*)((gf_internal_t*)\n        gf->scratch)->private;\n  return (std->inv_tbl[a]);\n}\n\nstatic\nvoid\ngf_w8_log_multiply_region(gf_t* gf, void* src, void* dest, uint32_t val,\n                          int bytes, int xor)\n{\n  int i;\n  uint8_t lv;\n  uint8_t* s8, *d8;\n  struct gf_w8_logtable_data* ltd;\n\n  if (val == 0) {\n    gf_multby_zero(dest, bytes, xor);\n    return;\n  }\n\n  if (val == 1) {\n    gf_multby_one(src, dest, bytes, xor);\n    return;\n  }\n\n  ltd = (struct gf_w8_logtable_data*)((gf_internal_t*) gf->scratch)->private;\n  s8 = (uint8_t*) src;\n  d8 = (uint8_t*) dest;\n  lv = ltd->log_tbl[val];\n\n  if (xor) {\n    for (i = 0; i < bytes; i++) {\n      d8[i] ^= (s8[i] == 0 ? 0 : ltd->antilog_tbl[lv + ltd->log_tbl[s8[i]]]);\n    }\n  } else {\n    for (i = 0; i < bytes; i++) {\n      d8[i] = (s8[i] == 0 ? 0 : ltd->antilog_tbl[lv + ltd->log_tbl[s8[i]]]);\n    }\n  }\n}\n\nstatic\nvoid\ngf_w8_logzero_multiply_region(gf_t* gf, void* src, void* dest, uint32_t val,\n                              int bytes, int xor)\n{\n  int i;\n  uint8_t lv;\n  uint8_t* s8, *d8;\n  struct gf_w8_logzero_table_data* ltd;\n  struct gf_w8_logzero_small_table_data* std;\n  short* log;\n  uint8_t* alt;\n  gf_internal_t* h;\n\n  if (val == 0) {\n    gf_multby_zero(dest, bytes, xor);\n    return;\n  }\n\n  if (val == 1) {\n    gf_multby_one(src, dest, bytes, xor);\n    return;\n  }\n\n  h = (gf_internal_t*) gf->scratch;\n\n  if (h->arg1 == 1) {\n    std = (struct gf_w8_logzero_small_table_data*) h->private;\n    log = std->log_tbl;\n    alt = std->antilog_tbl;\n  } else {\n    ltd = (struct gf_w8_logzero_table_data*) h->private;\n    log = ltd->log_tbl;\n    alt = ltd->antilog_tbl;\n  }\n\n  s8 = (uint8_t*) src;\n  d8 = (uint8_t*) dest;\n  lv = log[val];\n\n  if (xor) {\n    for (i = 0; i < bytes; i++) {\n      d8[i] ^= (alt[lv + log[s8[i]]]);\n    }\n  } else {\n    for (i = 0; i < bytes; i++) {\n      d8[i] = (alt[lv + log[s8[i]]]);\n    }\n  }\n}\n\nstatic\nint gf_w8_log_init(gf_t* gf)\n{\n  gf_internal_t* h;\n  struct gf_w8_logtable_data* ltd = NULL;\n  struct gf_w8_logzero_table_data* ztd = NULL;\n  struct gf_w8_logzero_small_table_data* std = NULL;\n  uint8_t* alt;\n  uint8_t* inv;\n  int i, b;\n  int check = 0;\n  h = (gf_internal_t*) gf->scratch;\n\n  if (h->mult_type == GF_MULT_LOG_TABLE) {\n    ltd = h->private;\n    alt = ltd->antilog_tbl;\n    inv = ltd->inv_tbl;\n  } else if (h->mult_type == GF_MULT_LOG_ZERO) {\n    std = h->private;\n    alt = std->antilog_tbl;\n    std->div_tbl = (alt + 255);\n    inv = std->inv_tbl;\n  } else {\n    ztd = h->private;\n    alt = ztd->antilog_tbl;\n    ztd->inv_tbl = (alt + 512 + 256);\n    ztd->div_tbl = (alt + 255);\n    inv = ztd->inv_tbl;\n  }\n\n  for (i = 0; i < GF_MULT_GROUP_SIZE + 1; i++) {\n    if (h->mult_type == GF_MULT_LOG_TABLE) {\n      ltd->log_tbl[i] = 0;\n    } else if (h->mult_type == GF_MULT_LOG_ZERO) {\n      std->log_tbl[i] = 0;\n    } else {\n      ztd->log_tbl[i] = 0;\n    }\n  }\n\n  if (h->mult_type == GF_MULT_LOG_TABLE) {\n    ltd->log_tbl[0] = 0;\n  } else if (h->mult_type == GF_MULT_LOG_ZERO) {\n    std->log_tbl[0] = 510;\n  } else {\n    ztd->log_tbl[0] = 512;\n  }\n\n  b = 1;\n\n  for (i = 0; i < GF_MULT_GROUP_SIZE; i++) {\n    if (h->mult_type == GF_MULT_LOG_TABLE) {\n      if (ltd->log_tbl[b] != 0) {\n        check = 1;\n      }\n\n      ltd->log_tbl[b] = i;\n    } else if (h->mult_type == GF_MULT_LOG_ZERO) {\n      if (std->log_tbl[b] != 0) {\n        check = 1;\n      }\n\n      std->log_tbl[b] = i;\n    } else {\n      if (ztd->log_tbl[b] != 0) {\n        check = 1;\n      }\n\n      ztd->log_tbl[b] = i;\n    }\n\n    alt[i] = b;\n    alt[i + GF_MULT_GROUP_SIZE] = b;\n    b <<= 1;\n\n    if (b & GF_FIELD_SIZE) {\n      b = b ^ h->prim_poly;\n    }\n  }\n\n  if (check) {\n    _gf_errno = GF_E_LOGPOLY;\n    return 0;\n  }\n\n  if (h->mult_type == GF_MULT_LOG_ZERO) {\n    bzero(alt + 510, 255);\n  }\n\n  if (h->mult_type == GF_MULT_LOG_ZERO_EXT) {\n    bzero(alt + 512, 255);\n    alt[512 + 512] = 0;\n  }\n\n  inv[0] = 0;  /* Not really, but we need to fill it with something  */\n  i = 1;\n  b = GF_MULT_GROUP_SIZE;\n\n  do {\n    inv[i] = alt[b];\n    i <<= 1;\n\n    if (i & (1 << 8)) {\n      i ^= h->prim_poly;\n    }\n\n    b--;\n  } while (i != 1);\n\n  if (h->mult_type == GF_MULT_LOG_TABLE) {\n    gf->inverse.w32 = gf_w8_log_inverse;\n    gf->divide.w32 = gf_w8_log_divide;\n    gf->multiply.w32 = gf_w8_log_multiply;\n    gf->multiply_region.w32 = gf_w8_log_multiply_region;\n  } else if (h->mult_type == GF_MULT_LOG_ZERO) {\n    gf->inverse.w32 = gf_w8_logzero_small_inverse;\n    gf->divide.w32 = gf_w8_logzero_small_divide;\n    gf->multiply.w32 = gf_w8_logzero_small_multiply;\n    gf->multiply_region.w32 = gf_w8_logzero_multiply_region;\n  } else {\n    gf->inverse.w32 = gf_w8_logzero_inverse;\n    gf->divide.w32 = gf_w8_logzero_divide;\n    gf->multiply.w32 = gf_w8_logzero_multiply;\n    gf->multiply_region.w32 = gf_w8_logzero_multiply_region;\n  }\n\n  return 1;\n}\n\n/* ------------------------------------------------------------\nIMPLEMENTATION: FULL_TABLE:\n\nJSP: Kevin wrote this, and I'm converting it to my structure.\n */\n\nstatic\ngf_val_32_t\ngf_w8_table_multiply(gf_t* gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_w8_single_table_data* ftd;\n  ftd = (struct gf_w8_single_table_data*)((gf_internal_t*) gf->scratch)->private;\n  return (ftd->multtable[a][b]);\n}\n\nstatic\ngf_val_32_t\ngf_w8_table_divide(gf_t* gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_w8_single_table_data* ftd;\n  ftd = (struct gf_w8_single_table_data*)((gf_internal_t*) gf->scratch)->private;\n  return (ftd->divtable[a][b]);\n}\n\nstatic\ngf_val_32_t\ngf_w8_default_multiply(gf_t* gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_w8_default_data* ftd;\n  ftd = (struct gf_w8_default_data*)((gf_internal_t*) gf->scratch)->private;\n  return (ftd->multtable[a][b]);\n}\n\n#if defined(INTEL_SSSE3) || defined(ARM_NEON)\nstatic\ngf_val_32_t\ngf_w8_default_divide(gf_t* gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_w8_default_data* ftd;\n  ftd = (struct gf_w8_default_data*)((gf_internal_t*) gf->scratch)->private;\n  return (ftd->divtable[a][b]);\n}\n#endif\n\nstatic\ngf_val_32_t\ngf_w8_double_table_multiply(gf_t* gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_w8_double_table_data* ftd;\n  ftd = (struct gf_w8_double_table_data*)((gf_internal_t*) gf->scratch)->private;\n  return (ftd->mult[a][b]);\n}\n\nstatic\ngf_val_32_t\ngf_w8_double_table_divide(gf_t* gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_w8_double_table_data* ftd;\n  ftd = (struct gf_w8_double_table_data*)((gf_internal_t*) gf->scratch)->private;\n  return (ftd->div[a][b]);\n}\n\nstatic\nvoid\ngf_w8_double_table_multiply_region(gf_t* gf, void* src, void* dest,\n                                   gf_val_32_t val, int bytes, int xor)\n{\n  uint16_t* base;\n  uint32_t b, c, vc, vb;\n  gf_internal_t* h;\n  struct gf_w8_double_table_data*  dtd;\n  struct gf_w8_double_table_lazy_data*  ltd;\n  gf_region_data rd;\n\n  if (val == 0) {\n    gf_multby_zero(dest, bytes, xor);\n    return;\n  }\n\n  if (val == 1) {\n    gf_multby_one(src, dest, bytes, xor);\n    return;\n  }\n\n  h = (gf_internal_t*)(gf->scratch);\n\n  if (h->region_type & GF_REGION_LAZY) {\n    ltd = (struct gf_w8_double_table_lazy_data*) h->private;\n    base = ltd->mult;\n\n    for (b = 0; b < GF_FIELD_SIZE; b++) {\n      vb = (ltd->smult[val][b] << 8);\n\n      for (c = 0; c < GF_FIELD_SIZE; c++) {\n        vc = ltd->smult[val][c];\n        base[(b << 8) | c] = (vb | vc);\n      }\n    }\n  } else {\n    dtd = (struct gf_w8_double_table_data*) h->private;\n    base = &(dtd->mult[val][0]);\n  }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 8);\n  gf_do_initial_region_alignment(&rd);\n  gf_two_byte_region_table_multiply(&rd, base);\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\ngf_val_32_t\ngf_w8_double_table_lazy_multiply(gf_t* gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_w8_double_table_lazy_data* ftd;\n  ftd = (struct gf_w8_double_table_lazy_data*)((gf_internal_t*)\n        gf->scratch)->private;\n  return (ftd->smult[a][b]);\n}\n\nstatic\ngf_val_32_t\ngf_w8_double_table_lazy_divide(gf_t* gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_w8_double_table_lazy_data* ftd;\n  ftd = (struct gf_w8_double_table_lazy_data*)((gf_internal_t*)\n        gf->scratch)->private;\n  return (ftd->div[a][b]);\n}\n\nstatic\nvoid\ngf_w8_table_multiply_region(gf_t* gf, void* src, void* dest, gf_val_32_t val,\n                            int bytes, int xor)\n{\n  int i;\n  uint8_t* s8, *d8;\n  struct gf_w8_single_table_data* ftd;\n\n  if (val == 0) {\n    gf_multby_zero(dest, bytes, xor);\n    return;\n  }\n\n  if (val == 1) {\n    gf_multby_one(src, dest, bytes, xor);\n    return;\n  }\n\n  ftd = (struct gf_w8_single_table_data*)((gf_internal_t*) gf->scratch)->private;\n  s8 = (uint8_t*) src;\n  d8 = (uint8_t*) dest;\n\n  if (xor) {\n    for (i = 0; i < bytes; i++) {\n      d8[i] ^= ftd->multtable[s8[i]][val];\n    }\n  } else {\n    for (i = 0; i < bytes; i++) {\n      d8[i] = ftd->multtable[s8[i]][val];\n    }\n  }\n}\n\n#ifdef INTEL_SSSE3\nstatic\nvoid\ngf_w8_split_multiply_region_sse(gf_t* gf, void* src, void* dest,\n                                gf_val_32_t val, int bytes, int xor)\n{\n  uint8_t* bh, *bl, *sptr, *dptr;\n  __m128i  loset, t1, r, va, mth, mtl;\n  struct gf_w8_half_table_data* htd;\n  gf_region_data rd;\n\n  if (val == 0) {\n    gf_multby_zero(dest, bytes, xor);\n    return;\n  }\n\n  if (val == 1) {\n    gf_multby_one(src, dest, bytes, xor);\n    return;\n  }\n\n  htd = (struct gf_w8_half_table_data*)((gf_internal_t*)(gf->scratch))->private;\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n  bh = (uint8_t*) htd->high;\n  bh += (val << 4);\n  bl = (uint8_t*) htd->low;\n  bl += (val << 4);\n  sptr = rd.s_start;\n  dptr = rd.d_start;\n  mth = _mm_loadu_si128((__m128i*)(bh));\n  mtl = _mm_loadu_si128((__m128i*)(bl));\n  loset = _mm_set1_epi8(0x0f);\n\n  if (xor) {\n    while (sptr < (uint8_t*) rd.s_top) {\n      va = _mm_load_si128((__m128i*)(sptr));\n      t1 = _mm_and_si128(loset, va);\n      r = _mm_shuffle_epi8(mtl, t1);\n      va = _mm_srli_epi64(va, 4);\n      t1 = _mm_and_si128(loset, va);\n      r = _mm_xor_si128(r, _mm_shuffle_epi8(mth, t1));\n      va = _mm_load_si128((__m128i*)(dptr));\n      r = _mm_xor_si128(r, va);\n      _mm_store_si128((__m128i*)(dptr), r);\n      dptr += 16;\n      sptr += 16;\n    }\n  } else {\n    while (sptr < (uint8_t*) rd.s_top) {\n      va = _mm_load_si128((__m128i*)(sptr));\n      t1 = _mm_and_si128(loset, va);\n      r = _mm_shuffle_epi8(mtl, t1);\n      va = _mm_srli_epi64(va, 4);\n      t1 = _mm_and_si128(loset, va);\n      r = _mm_xor_si128(r, _mm_shuffle_epi8(mth, t1));\n      _mm_store_si128((__m128i*)(dptr), r);\n      dptr += 16;\n      sptr += 16;\n    }\n  }\n\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\n\n/* ------------------------------------------------------------\nIMPLEMENTATION: FULL_TABLE:\n */\n\nstatic\ngf_val_32_t\ngf_w8_split_multiply(gf_t* gf, gf_val_32_t a, gf_val_32_t b)\n{\n  struct gf_w8_half_table_data* htd;\n  htd = (struct gf_w8_half_table_data*)((gf_internal_t*) gf->scratch)->private;\n  return htd->high[b][a >> 4] ^ htd->low[b][a & 0xf];\n}\n\nstatic\nvoid\ngf_w8_split_multiply_region(gf_t* gf, void* src, void* dest, gf_val_32_t val,\n                            int bytes, int xor)\n{\n  int i;\n  uint8_t* s8, *d8;\n  struct gf_w8_half_table_data* htd;\n\n  if (val == 0) {\n    gf_multby_zero(dest, bytes, xor);\n    return;\n  }\n\n  if (val == 1) {\n    gf_multby_one(src, dest, bytes, xor);\n    return;\n  }\n\n  htd = (struct gf_w8_half_table_data*)((gf_internal_t*) gf->scratch)->private;\n  s8 = (uint8_t*) src;\n  d8 = (uint8_t*) dest;\n\n  if (xor) {\n    for (i = 0; i < bytes; i++) {\n      d8[i] ^= (htd->high[val][s8[i] >> 4] ^ htd->low[val][s8[i] & 0xf]);\n    }\n  } else {\n    for (i = 0; i < bytes; i++) {\n      d8[i] = (htd->high[val][s8[i] >> 4] ^ htd->low[val][s8[i] & 0xf]);\n    }\n  }\n}\n\n\nstatic\nint gf_w8_split_init(gf_t* gf)\n{\n  gf_internal_t* h;\n  struct gf_w8_half_table_data* htd;\n  int a, b;\n  h = (gf_internal_t*) gf->scratch;\n  htd = (struct gf_w8_half_table_data*)h->private;\n  bzero(htd->high, sizeof(uint8_t)*GF_FIELD_SIZE * GF_HALF_SIZE);\n  bzero(htd->low, sizeof(uint8_t)*GF_FIELD_SIZE * GF_HALF_SIZE);\n\n  for (a = 1; a < GF_FIELD_SIZE; a++) {\n    for (b = 1; b < GF_HALF_SIZE; b++) {\n      htd->low[a][b] = gf_w8_shift_multiply(gf, a, b);\n      htd->high[a][b] = gf_w8_shift_multiply(gf, a, b << 4);\n    }\n  }\n\n  gf->multiply.w32 = gf_w8_split_multiply;\n#if defined(INTEL_SSSE3) || defined(ARM_NEON)\n\n  if (h->region_type & GF_REGION_NOSIMD) {\n    gf->multiply_region.w32 = gf_w8_split_multiply_region;\n  } else\n#if defined(INTEL_SSSE3)\n    gf->multiply_region.w32 = gf_w8_split_multiply_region_sse;\n\n#elif defined(ARM_NEON)\n    gf_w8_neon_split_init(gf);\n#endif\n#else\n  gf->multiply_region.w32 = gf_w8_split_multiply_region;\n\n  if (h->region_type & GF_REGION_SIMD) {\n    return 0;\n  }\n\n#endif\n  return 1;\n}\n\n/* JSP: This is disgusting, but it is what it is.  If there is no SSE,\n   then the default is equivalent to single table.  If there is SSE, then\n   we use the \"gf_w8_default_data\" which is a hybrid of SPLIT & TABLE. */\n\nstatic\nint gf_w8_table_init(gf_t* gf)\n{\n  gf_internal_t* h;\n  struct gf_w8_single_table_data* ftd = NULL;\n  struct gf_w8_double_table_data* dtd = NULL;\n  struct gf_w8_double_table_lazy_data* ltd = NULL;\n  struct gf_w8_default_data* dd = NULL;\n  // scase will be overwritten later, but make the compiler happy\n  int a, b, c, prod, use_simd, scase = 0;\n  h = (gf_internal_t*) gf->scratch;\n#if defined(INTEL_SSSE3) || defined(ARM_NEON)\n  use_simd = 1;\n#else\n  use_simd = 0;\n#endif\n\n  if (h->mult_type == GF_MULT_DEFAULT && use_simd) {\n    dd = (struct gf_w8_default_data*)h->private;\n    scase = 3;\n    bzero(dd->high, sizeof(uint8_t) * GF_FIELD_SIZE * GF_HALF_SIZE);\n    bzero(dd->low, sizeof(uint8_t) * GF_FIELD_SIZE * GF_HALF_SIZE);\n    bzero(dd->divtable, sizeof(uint8_t) * GF_FIELD_SIZE * GF_FIELD_SIZE);\n    bzero(dd->multtable, sizeof(uint8_t) * GF_FIELD_SIZE * GF_FIELD_SIZE);\n  } else if (h->mult_type == GF_MULT_DEFAULT ||\n             h->region_type == 0 || (h->region_type & GF_REGION_CAUCHY)) {\n    ftd = (struct gf_w8_single_table_data*)h->private;\n    bzero(ftd->divtable, sizeof(uint8_t) * GF_FIELD_SIZE * GF_FIELD_SIZE);\n    bzero(ftd->multtable, sizeof(uint8_t) * GF_FIELD_SIZE * GF_FIELD_SIZE);\n    scase = 0;\n  } else if (h->region_type == GF_REGION_DOUBLE_TABLE) {\n    dtd = (struct gf_w8_double_table_data*)h->private;\n    bzero(dtd->div, sizeof(uint8_t) * GF_FIELD_SIZE * GF_FIELD_SIZE);\n    bzero(dtd->mult, sizeof(uint16_t) * GF_FIELD_SIZE * GF_FIELD_SIZE *\n          GF_FIELD_SIZE);\n    scase = 1;\n  } else if (h->region_type == (GF_REGION_DOUBLE_TABLE | GF_REGION_LAZY)) {\n    ltd = (struct gf_w8_double_table_lazy_data*)h->private;\n    bzero(ltd->div, sizeof(uint8_t) * GF_FIELD_SIZE * GF_FIELD_SIZE);\n    bzero(ltd->smult, sizeof(uint8_t) * GF_FIELD_SIZE * GF_FIELD_SIZE);\n    scase = 2;\n  } else {\n    fprintf(stderr, \"Internal error in gf_w8_table_init\\n\");\n    assert(0);\n  }\n\n  for (a = 1; a < GF_FIELD_SIZE; a++) {\n    for (b = 1; b < GF_FIELD_SIZE; b++) {\n      prod = gf_w8_shift_multiply(gf, a, b);\n\n      switch (scase) {\n      case 0:\n        ftd->multtable[a][b] = prod;\n        ftd->divtable[prod][b] = a;\n        break;\n\n      case 1:\n        dtd->div[prod][b] = a;\n\n        for (c = 0; c < GF_FIELD_SIZE; c++) {\n          dtd->mult[a][(c << 8) | b] |= prod;\n          dtd->mult[a][(b << 8) | c] |= (prod << 8);\n        }\n\n        break;\n\n      case 2:\n        ltd->div[prod][b] = a;\n        ltd->smult[a][b] = prod;\n        break;\n\n      case 3:\n        dd->multtable[a][b] = prod;\n        dd->divtable[prod][b] = a;\n\n        if ((b & 0xf) == b) {\n          dd->low[a][b] = prod;\n        }\n\n        if ((b & 0xf0) == b) {\n          dd->high[a][b >> 4] = prod;\n        }\n\n        break;\n      }\n    }\n  }\n\n  gf->inverse.w32 = NULL; /* Will set from divide */\n\n  switch (scase) {\n  case 0:\n    gf->divide.w32 = gf_w8_table_divide;\n    gf->multiply.w32 = gf_w8_table_multiply;\n    gf->multiply_region.w32 = gf_w8_table_multiply_region;\n    break;\n\n  case 1:\n    gf->divide.w32 = gf_w8_double_table_divide;\n    gf->multiply.w32 = gf_w8_double_table_multiply;\n    gf->multiply_region.w32 = gf_w8_double_table_multiply_region;\n    break;\n\n  case 2:\n    gf->divide.w32 = gf_w8_double_table_lazy_divide;\n    gf->multiply.w32 = gf_w8_double_table_lazy_multiply;\n    gf->multiply_region.w32 = gf_w8_double_table_multiply_region;\n    break;\n\n  case 3:\n#if defined(INTEL_SSSE3) || defined(ARM_NEON)\n    gf->divide.w32 = gf_w8_default_divide;\n    gf->multiply.w32 = gf_w8_default_multiply;\n#if defined(INTEL_SSSE3)\n    gf->multiply_region.w32 = gf_w8_split_multiply_region_sse;\n#elif defined(ARM_NEON)\n    gf_w8_neon_split_init(gf);\n#endif\n#endif\n    break;\n  }\n\n  return 1;\n}\n\nstatic\nvoid\ngf_w8_composite_multiply_region_alt(gf_t* gf, void* src, void* dest,\n                                    gf_val_32_t val, int bytes, int xor)\n{\n  gf_internal_t* h = (gf_internal_t*) gf->scratch;\n  gf_t* base_gf = h->base_gf;\n  uint8_t val0 = val & 0x0f;\n  uint8_t val1 = (val & 0xf0) >> 4;\n  gf_region_data rd;\n  int sub_reg_size;\n\n  if (val == 0) {\n    if (xor) {\n      return;\n    }\n\n    bzero(dest, bytes);\n    return;\n  }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 32);\n  gf_do_initial_region_alignment(&rd);\n  sub_reg_size = ((uint8_t*)rd.d_top - (uint8_t*)rd.d_start) / 2;\n  base_gf->multiply_region.w32(base_gf, rd.s_start, rd.d_start, val0,\n                               sub_reg_size, xor);\n  base_gf->multiply_region.w32(base_gf, (uint8_t*)rd.s_start + sub_reg_size,\n                               rd.d_start, val1, sub_reg_size, 1);\n  base_gf->multiply_region.w32(base_gf, rd.s_start,\n                               (uint8_t*)rd.d_start + sub_reg_size, val1, sub_reg_size, xor);\n  base_gf->multiply_region.w32(base_gf, (uint8_t*)rd.s_start + sub_reg_size,\n                               (uint8_t*)rd.d_start + sub_reg_size, val0, sub_reg_size, 1);\n  base_gf->multiply_region.w32(base_gf, (uint8_t*)rd.s_start + sub_reg_size,\n                               (uint8_t*)rd.d_start + sub_reg_size, base_gf->multiply.w32(base_gf,\n                                   h->prim_poly, val1), sub_reg_size, 1);\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\ngf_val_32_t\ngf_w8_composite_multiply_recursive(gf_t* gf, gf_val_32_t a, gf_val_32_t b)\n{\n  gf_internal_t* h = (gf_internal_t*) gf->scratch;\n  gf_t* base_gf = h->base_gf;\n  uint8_t b0 = b & 0x0f;\n  uint8_t b1 = (b & 0xf0) >> 4;\n  uint8_t a0 = a & 0x0f;\n  uint8_t a1 = (a & 0xf0) >> 4;\n  uint8_t a1b1;\n  a1b1 = base_gf->multiply.w32(base_gf, a1, b1);\n  return ((base_gf->multiply.w32(base_gf, a0, b0) ^ a1b1) |\n          ((base_gf->multiply.w32(base_gf, a1, b0) ^\n            base_gf->multiply.w32(base_gf, a0, b1) ^\n            base_gf->multiply.w32(base_gf, a1b1, h->prim_poly)) << 4));\n}\n\nstatic\ngf_val_32_t\ngf_w8_composite_multiply_inline(gf_t* gf, gf_val_32_t a, gf_val_32_t b)\n{\n  gf_internal_t* h = (gf_internal_t*) gf->scratch;\n  uint8_t b0 = b & 0x0f;\n  uint8_t b1 = (b & 0xf0) >> 4;\n  uint8_t a0 = a & 0x0f;\n  uint8_t a1 = (a & 0xf0) >> 4;\n  uint8_t a1b1, *mt;\n  struct gf_w8_composite_data* cd;\n  cd = (struct gf_w8_composite_data*) h->private;\n  mt = cd->mult_table;\n  a1b1 = GF_W4_INLINE_MULTDIV(mt, a1, b1);\n  return ((GF_W4_INLINE_MULTDIV(mt, a0, b0) ^ a1b1) |\n          ((GF_W4_INLINE_MULTDIV(mt, a1, b0) ^\n            GF_W4_INLINE_MULTDIV(mt, a0, b1) ^\n            GF_W4_INLINE_MULTDIV(mt, a1b1, h->prim_poly)) << 4));\n}\n\n/*\n * Composite field division trick (explained in 2007 tech report)\n *\n * Compute a / b = a*b^-1, where p(x) = x^2 + sx + 1\n *\n * let c = b^-1\n *\n * c*b = (s*b1c1+b1c0+b0c1)x+(b1c1+b0c0)\n *\n * want (s*b1c1+b1c0+b0c1) = 0 and (b1c1+b0c0) = 1\n *\n * let d = b1c1 and d+1 = b0c0\n *\n * solve s*b1c1+b1c0+b0c1 = 0\n *\n * solution: d = (b1b0^-1)(b1b0^-1+b0b1^-1+s)^-1\n *\n * c0 = (d+1)b0^-1\n * c1 = d*b1^-1\n *\n * a / b = a * c\n */\n\nstatic\ngf_val_32_t\ngf_w8_composite_inverse(gf_t* gf, gf_val_32_t a)\n{\n  gf_internal_t* h = (gf_internal_t*) gf->scratch;\n  gf_t* base_gf = h->base_gf;\n  uint8_t a0 = a & 0x0f;\n  uint8_t a1 = (a & 0xf0) >> 4;\n  uint8_t c0, c1, c, d, tmp;\n  uint8_t a0inv, a1inv;\n\n  if (a0 == 0) {\n    a1inv = base_gf->inverse.w32(base_gf, a1) & 0xf;\n    c0 = base_gf->multiply.w32(base_gf, a1inv, h->prim_poly);\n    c1 = a1inv;\n  } else if (a1 == 0) {\n    c0 = base_gf->inverse.w32(base_gf, a0);\n    c1 = 0;\n  } else {\n    a1inv = base_gf->inverse.w32(base_gf, a1) & 0xf;\n    a0inv = base_gf->inverse.w32(base_gf, a0) & 0xf;\n    d = base_gf->multiply.w32(base_gf, a1, a0inv) & 0xf;\n    tmp = (base_gf->multiply.w32(base_gf, a1,\n                                 a0inv) ^ base_gf->multiply.w32(base_gf, a0, a1inv) ^ h->prim_poly) & 0xf;\n    tmp = base_gf->inverse.w32(base_gf, tmp) & 0xf;\n    d = base_gf->multiply.w32(base_gf, d, tmp) & 0xf;\n    c0 = base_gf->multiply.w32(base_gf, (d ^ 1), a0inv) & 0xf;\n    c1 = base_gf->multiply.w32(base_gf, d, a1inv) & 0xf;\n  }\n\n  c = c0 | (c1 << 4);\n  return c;\n}\n\nstatic\nvoid\ngf_w8_composite_multiply_region(gf_t* gf, void* src, void* dest,\n                                gf_val_32_t val, int bytes, int xor)\n{\n  gf_region_data rd;\n  gf_internal_t* h = (gf_internal_t*) gf->scratch;\n  gf_t* base_gf = h->base_gf;\n  uint8_t b0 = val & 0x0f;\n  uint8_t b1 = (val & 0xf0) >> 4;\n  uint8_t* s8;\n  uint8_t* d8;\n  uint8_t* mt;\n  uint8_t a0, a1, a1b1;\n  struct gf_w8_composite_data* cd;\n  cd = (struct gf_w8_composite_data*) h->private;\n\n  if (val == 0) {\n    if (xor) {\n      return;\n    }\n\n    bzero(dest, bytes);\n    return;\n  }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 1);\n  gf_do_initial_region_alignment(&rd);\n  s8 = (uint8_t*) rd.s_start;\n  d8 = (uint8_t*) rd.d_start;\n  mt = cd->mult_table;\n\n  if (mt == NULL) {\n    if (xor) {\n      while (d8 < (uint8_t*) rd.d_top) {\n        a0 = *s8 & 0x0f;\n        a1 = (*s8 & 0xf0) >> 4;\n        a1b1 = base_gf->multiply.w32(base_gf, a1, b1);\n        *d8 ^= ((base_gf->multiply.w32(base_gf, a0, b0) ^ a1b1) |\n                ((base_gf->multiply.w32(base_gf, a1, b0) ^\n                  base_gf->multiply.w32(base_gf, a0, b1) ^\n                  base_gf->multiply.w32(base_gf, a1b1, h->prim_poly)) << 4));\n        s8++;\n        d8++;\n      }\n    } else {\n      while (d8 < (uint8_t*) rd.d_top) {\n        a0 = *s8 & 0x0f;\n        a1 = (*s8 & 0xf0) >> 4;\n        a1b1 = base_gf->multiply.w32(base_gf, a1, b1);\n        *d8 = ((base_gf->multiply.w32(base_gf, a0, b0) ^ a1b1) |\n               ((base_gf->multiply.w32(base_gf, a1, b0) ^\n                 base_gf->multiply.w32(base_gf, a0, b1) ^\n                 base_gf->multiply.w32(base_gf, a1b1, h->prim_poly)) << 4));\n        s8++;\n        d8++;\n      }\n    }\n  } else {\n    if (xor) {\n      while (d8 < (uint8_t*) rd.d_top) {\n        a0 = *s8 & 0x0f;\n        a1 = (*s8 & 0xf0) >> 4;\n        a1b1 = GF_W4_INLINE_MULTDIV(mt, a1, b1);\n        *d8 ^= ((GF_W4_INLINE_MULTDIV(mt, a0, b0) ^ a1b1) |\n                ((GF_W4_INLINE_MULTDIV(mt, a1, b0) ^\n                  GF_W4_INLINE_MULTDIV(mt, a0, b1) ^\n                  GF_W4_INLINE_MULTDIV(mt, a1b1, h->prim_poly)) << 4));\n        s8++;\n        d8++;\n      }\n    } else {\n      while (d8 < (uint8_t*) rd.d_top) {\n        a0 = *s8 & 0x0f;\n        a1 = (*s8 & 0xf0) >> 4;\n        a1b1 = GF_W4_INLINE_MULTDIV(mt, a1, b1);\n        *d8 = ((GF_W4_INLINE_MULTDIV(mt, a0, b0) ^ a1b1) |\n               ((GF_W4_INLINE_MULTDIV(mt, a1, b0) ^\n                 GF_W4_INLINE_MULTDIV(mt, a0, b1) ^\n                 GF_W4_INLINE_MULTDIV(mt, a1b1, h->prim_poly)) << 4));\n        s8++;\n        d8++;\n      }\n    }\n  }\n\n  gf_do_final_region_alignment(&rd);\n  return;\n}\n\nstatic\nint gf_w8_composite_init(gf_t* gf)\n{\n  gf_internal_t* h = (gf_internal_t*) gf->scratch;\n  struct gf_w8_composite_data* cd;\n\n  if (h->base_gf == NULL) {\n    return 0;\n  }\n\n  cd = (struct gf_w8_composite_data*) h->private;\n  cd->mult_table = gf_w4_get_mult_table(h->base_gf);\n\n  if (h->region_type & GF_REGION_ALTMAP) {\n    gf->multiply_region.w32 = gf_w8_composite_multiply_region_alt;\n  } else {\n    gf->multiply_region.w32 = gf_w8_composite_multiply_region;\n  }\n\n  if (cd->mult_table == NULL) {\n    gf->multiply.w32 = gf_w8_composite_multiply_recursive;\n  } else {\n    gf->multiply.w32 = gf_w8_composite_multiply_inline;\n  }\n\n  gf->divide.w32 = NULL;\n  gf->inverse.w32 = gf_w8_composite_inverse;\n  return 1;\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w8_bytwo_p_multiply(gf_t* gf, gf_val_32_t a, gf_val_32_t b)\n{\n  uint32_t prod, pp, pmask, amask;\n  gf_internal_t* h;\n  h = (gf_internal_t*) gf->scratch;\n  pp = h->prim_poly;\n  prod = 0;\n  pmask = 0x80;\n  amask = 0x80;\n\n  while (amask != 0) {\n    if (prod & pmask) {\n      prod = ((prod << 1) ^ pp);\n    } else {\n      prod <<= 1;\n    }\n\n    if (a & amask) {\n      prod ^= b;\n    }\n\n    amask >>= 1;\n  }\n\n  return prod;\n}\n\nstatic\ninline\ngf_val_32_t\ngf_w8_bytwo_b_multiply(gf_t* gf, gf_val_32_t a, gf_val_32_t b)\n{\n  uint32_t prod, pp, bmask;\n  gf_internal_t* h;\n  h = (gf_internal_t*) gf->scratch;\n  pp = h->prim_poly;\n  prod = 0;\n  bmask = 0x80;\n\n  while (1) {\n    if (a & 1) {\n      prod ^= b;\n    }\n\n    a >>= 1;\n\n    if (a == 0) {\n      return prod;\n    }\n\n    if (b & bmask) {\n      b = ((b << 1) ^ pp);\n    } else {\n      b <<= 1;\n    }\n  }\n}\n\nstatic\nvoid\ngf_w8_bytwo_p_nosse_multiply_region(gf_t* gf, void* src, void* dest,\n                                    gf_val_32_t val, int bytes, int xor)\n{\n  uint64_t* s64, *d64, t1, t2, ta, prod, amask;\n  gf_region_data rd;\n  struct gf_w8_bytwo_data* btd;\n\n  if (val == 0) {\n    gf_multby_zero(dest, bytes, xor);\n    return;\n  }\n\n  if (val == 1) {\n    gf_multby_one(src, dest, bytes, xor);\n    return;\n  }\n\n  btd = (struct gf_w8_bytwo_data*)((gf_internal_t*)(gf->scratch))->private;\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 8);\n  gf_do_initial_region_alignment(&rd);\n  s64 = (uint64_t*) rd.s_start;\n  d64 = (uint64_t*) rd.d_start;\n\n  if (xor) {\n    while (s64 < (uint64_t*) rd.s_top) {\n      prod = 0;\n      amask = 0x80;\n      ta = *s64;\n\n      while (amask != 0) {\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, prod, t1, t2);\n\n        if (val & amask) {\n          prod ^= ta;\n        }\n\n        amask >>= 1;\n      }\n\n      *d64 ^= prod;\n      d64++;\n      s64++;\n    }\n  } else {\n    while (s64 < (uint64_t*) rd.s_top) {\n      prod = 0;\n      amask = 0x80;\n      ta = *s64;\n\n      while (amask != 0) {\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, prod, t1, t2);\n\n        if (val & amask) {\n          prod ^= ta;\n        }\n\n        amask >>= 1;\n      }\n\n      *d64 = prod;\n      d64++;\n      s64++;\n    }\n  }\n\n  gf_do_final_region_alignment(&rd);\n}\n\n#define BYTWO_P_ONESTEP {\\\n  SSE_AB2(pp, m1 ,m2, prod, t1, t2); \\\n  t1 = _mm_and_si128(v, one); \\\n  t1 = _mm_sub_epi8(t1, one); \\\n  t1 = _mm_and_si128(t1, ta); \\\n  prod = _mm_xor_si128(prod, t1); \\\n  v = _mm_srli_epi64(v, 1); }\n\n#ifdef INTEL_SSE2\nstatic\nvoid\ngf_w8_bytwo_p_sse_multiply_region(gf_t* gf, void* src, void* dest,\n                                  gf_val_32_t val, int bytes, int xor)\n{\n  int i;\n  uint8_t* s8, *d8;\n  uint8_t vrev;\n  __m128i pp, m1, m2, ta, prod, t1, t2, tp, one, v;\n  struct gf_w8_bytwo_data* btd;\n  gf_region_data rd;\n\n  if (val == 0) {\n    gf_multby_zero(dest, bytes, xor);\n    return;\n  }\n\n  if (val == 1) {\n    gf_multby_one(src, dest, bytes, xor);\n    return;\n  }\n\n  btd = (struct gf_w8_bytwo_data*)((gf_internal_t*)(gf->scratch))->private;\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n  vrev = 0;\n\n  for (i = 0; i < 8; i++) {\n    vrev <<= 1;\n\n    if (!(val & (1 << i))) {\n      vrev |= 1;\n    }\n  }\n\n  s8 = (uint8_t*) rd.s_start;\n  d8 = (uint8_t*) rd.d_start;\n  pp = _mm_set1_epi8(btd->prim_poly & 0xff);\n  m1 = _mm_set1_epi8((btd->mask1) & 0xff);\n  m2 = _mm_set1_epi8((btd->mask2) & 0xff);\n  one = _mm_set1_epi8(1);\n\n  while (d8 < (uint8_t*) rd.d_top) {\n    prod = _mm_setzero_si128();\n    v = _mm_set1_epi8(vrev);\n    ta = _mm_load_si128((__m128i*) s8);\n    tp = (!xor) ? _mm_setzero_si128() : _mm_load_si128((__m128i*) d8);\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    BYTWO_P_ONESTEP;\n    _mm_store_si128((__m128i*) d8, _mm_xor_si128(prod, tp));\n    d8 += 16;\n    s8 += 16;\n  }\n\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic\nvoid\ngf_w8_bytwo_b_sse_region_2_noxor(gf_region_data* rd,\n                                 struct gf_w8_bytwo_data* btd)\n{\n  uint8_t* d8, *s8;\n  __m128i pp, m1, m2, t1, t2, va;\n  s8 = (uint8_t*) rd->s_start;\n  d8 = (uint8_t*) rd->d_start;\n  pp = _mm_set1_epi8(btd->prim_poly & 0xff);\n  m1 = _mm_set1_epi8((btd->mask1) & 0xff);\n  m2 = _mm_set1_epi8((btd->mask2) & 0xff);\n\n  while (d8 < (uint8_t*) rd->d_top) {\n    va = _mm_load_si128((__m128i*)(s8));\n    SSE_AB2(pp, m1, m2, va, t1, t2);\n    _mm_store_si128((__m128i*)d8, va);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n#ifdef INTEL_SSE2\nstatic\nvoid\ngf_w8_bytwo_b_sse_region_2_xor(gf_region_data* rd, struct gf_w8_bytwo_data* btd)\n{\n  uint8_t* d8, *s8;\n  __m128i pp, m1, m2, t1, t2, va, vb;\n  s8 = (uint8_t*) rd->s_start;\n  d8 = (uint8_t*) rd->d_start;\n  pp = _mm_set1_epi8(btd->prim_poly & 0xff);\n  m1 = _mm_set1_epi8((btd->mask1) & 0xff);\n  m2 = _mm_set1_epi8((btd->mask2) & 0xff);\n\n  while (d8 < (uint8_t*) rd->d_top) {\n    va = _mm_load_si128((__m128i*)(s8));\n    SSE_AB2(pp, m1, m2, va, t1, t2);\n    vb = _mm_load_si128((__m128i*)(d8));\n    vb = _mm_xor_si128(vb, va);\n    _mm_store_si128((__m128i*)d8, vb);\n    d8 += 16;\n    s8 += 16;\n  }\n}\n#endif\n\n\n#ifdef INTEL_SSE2\nstatic\nvoid\ngf_w8_bytwo_b_sse_multiply_region(gf_t* gf, void* src, void* dest,\n                                  gf_val_32_t val, int bytes, int xor)\n{\n  int itb;\n  uint8_t* d8, *s8;\n  __m128i pp, m1, m2, t1, t2, va, vb;\n  struct gf_w8_bytwo_data* btd;\n  gf_region_data rd;\n\n  if (val == 0) {\n    gf_multby_zero(dest, bytes, xor);\n    return;\n  }\n\n  if (val == 1) {\n    gf_multby_one(src, dest, bytes, xor);\n    return;\n  }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n  btd = (struct gf_w8_bytwo_data*)((gf_internal_t*)(gf->scratch))->private;\n\n  if (val == 2) {\n    if (xor) {\n      gf_w8_bytwo_b_sse_region_2_xor(&rd, btd);\n    } else {\n      gf_w8_bytwo_b_sse_region_2_noxor(&rd, btd);\n    }\n\n    gf_do_final_region_alignment(&rd);\n    return;\n  }\n\n  s8 = (uint8_t*) rd.s_start;\n  d8 = (uint8_t*) rd.d_start;\n  pp = _mm_set1_epi8(btd->prim_poly & 0xff);\n  m1 = _mm_set1_epi8((btd->mask1) & 0xff);\n  m2 = _mm_set1_epi8((btd->mask2) & 0xff);\n\n  while (d8 < (uint8_t*) rd.d_top) {\n    va = _mm_load_si128((__m128i*)(s8));\n    vb = (!xor) ? _mm_setzero_si128() : _mm_load_si128((__m128i*)(d8));\n    itb = val;\n\n    while (1) {\n      if (itb & 1) {\n        vb = _mm_xor_si128(vb, va);\n      }\n\n      itb >>= 1;\n\n      if (itb == 0) {\n        break;\n      }\n\n      SSE_AB2(pp, m1, m2, va, t1, t2);\n    }\n\n    _mm_store_si128((__m128i*)d8, vb);\n    d8 += 16;\n    s8 += 16;\n  }\n\n  gf_do_final_region_alignment(&rd);\n}\n#endif\n\nstatic\nvoid\ngf_w8_bytwo_b_nosse_multiply_region(gf_t* gf, void* src, void* dest,\n                                    gf_val_32_t val, int bytes, int xor)\n{\n  uint64_t* s64, *d64, t1, t2, ta, tb, prod;\n  struct gf_w8_bytwo_data* btd;\n  gf_region_data rd;\n\n  if (val == 0) {\n    gf_multby_zero(dest, bytes, xor);\n    return;\n  }\n\n  if (val == 1) {\n    gf_multby_one(src, dest, bytes, xor);\n    return;\n  }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n  btd = (struct gf_w8_bytwo_data*)((gf_internal_t*)(gf->scratch))->private;\n  s64 = (uint64_t*) rd.s_start;\n  d64 = (uint64_t*) rd.d_start;\n\n  switch (val) {\n  case 2:\n    if (xor) {\n      while (d64 < (uint64_t*) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= ta;\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t*) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = ta;\n        d64++;\n        s64++;\n      }\n    }\n\n    break;\n\n  case 3:\n    if (xor) {\n      while (d64 < (uint64_t*) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t*) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    }\n\n    break;\n\n  case 4:\n    if (xor) {\n      while (d64 < (uint64_t*) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= ta;\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t*) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = ta;\n        d64++;\n        s64++;\n      }\n    }\n\n    break;\n\n  case 5:\n    if (xor) {\n      while (d64 < (uint64_t*) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t*) rd.d_top) {\n        ta = *s64;\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = ta ^ prod;\n        d64++;\n        s64++;\n      }\n    }\n\n    break;\n\n  case 6:\n    if (xor) {\n      while (d64 < (uint64_t*) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= (ta ^ prod);\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t*) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        prod = ta;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = ta ^ prod;\n        d64++;\n        s64++;\n      }\n    }\n\n    break;\n\n  /*\n     case 7:\n     if (xor) {\n     while (d64 < (uint64_t *) rd.d_top) {\n     ta = *s64;\n     prod = ta;\n     AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n     prod ^= ta;\n     AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n   *d64 ^= (ta ^ prod);\n   d64++;\n   s64++;\n   }\n   } else {\n   while (d64 < (uint64_t *) rd.d_top) {\n   ta = *s64;\n   prod = ta;\n   AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n   prod ^= ta;\n   AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n   *d64 = ta ^ prod;\n   d64++;\n   s64++;\n   }\n   }\n   break;\n   */\n  case 8:\n    if (xor) {\n      while (d64 < (uint64_t*) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 ^= ta;\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t*) rd.d_top) {\n        ta = *s64;\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        *d64 = ta;\n        d64++;\n        s64++;\n      }\n    }\n\n    break;\n\n  /*\n     case 9:\n     if (xor) {\n     while (d64 < (uint64_t *) rd.d_top) {\n     ta = *s64;\n     prod = ta;\n     AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n     AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n     AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n   *d64 ^= (ta ^ prod);\n   d64++;\n   s64++;\n   }\n   } else {\n   while (d64 < (uint64_t *) rd.d_top) {\n   ta = *s64;\n   prod = ta;\n   AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n   AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n   AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n   *d64 = (ta ^ prod);\n   d64++;\n   s64++;\n   }\n   }\n   break;\n   case 10:\n   if (xor) {\n   while (d64 < (uint64_t *) rd.d_top) {\n   ta = *s64;\n   AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n   prod = ta;\n   AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n   AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n   *d64 ^= (ta ^ prod);\n   d64++;\n   s64++;\n   }\n   } else {\n   while (d64 < (uint64_t *) rd.d_top) {\n   ta = *s64;\n   AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n   prod = ta;\n   AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n   AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n   *d64 = (ta ^ prod);\n   d64++;\n   s64++;\n   }\n   }\n   break;\n   case 11:\n   if (xor) {\n   while (d64 < (uint64_t *) rd.d_top) {\n   ta = *s64;\n   prod = ta;\n   AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n   prod ^= ta;\n   AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n   AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n   *d64 ^= (ta ^ prod);\n   d64++;\n   s64++;\n   }\n   } else {\n   while (d64 < (uint64_t *) rd.d_top) {\n   ta = *s64;\n   prod = ta;\n   AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n   prod ^= ta;\n   AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  *d64 = (ta ^ prod);\n  d64++;\n  s64++;\n  }\n  }\n  break;\n  case 12:\n  if (xor) {\n  while (d64 < (uint64_t *) rd.d_top) {\n  ta = *s64;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  prod = ta;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  *d64 ^= (ta ^ prod);\n  d64++;\n  s64++;\n  }\n  } else {\n  while (d64 < (uint64_t *) rd.d_top) {\n  ta = *s64;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  prod = ta;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  *d64 = (ta ^ prod);\n  d64++;\n  s64++;\n  }\n  }\n  break;\n  case 13:\n  if (xor) {\n  while (d64 < (uint64_t *) rd.d_top) {\n  ta = *s64;\n  prod = ta;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  prod ^= ta;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  *d64 ^= (ta ^ prod);\n  d64++;\n  s64++;\n  }\n  } else {\n  while (d64 < (uint64_t *) rd.d_top) {\n  ta = *s64;\n  prod = ta;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  prod ^= ta;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  *d64 = (ta ^ prod);\n  d64++;\n  s64++;\n  }\n  }\n  break;\n  case 14:\n  if (xor) {\n  while (d64 < (uint64_t *) rd.d_top) {\n  ta = *s64;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  prod = ta;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  prod ^= ta;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  *d64 ^= (ta ^ prod);\n  d64++;\n  s64++;\n  }\n  } else {\n  while (d64 < (uint64_t *) rd.d_top) {\n  ta = *s64;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  prod = ta;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  prod ^= ta;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  *d64 = (ta ^ prod);\n  d64++;\n  s64++;\n  }\n  }\n  break;\n  case 15:\n  if (xor) {\n  while (d64 < (uint64_t *) rd.d_top) {\n  ta = *s64;\n  prod = ta;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  prod ^= ta;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  prod ^= ta;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  *d64 ^= (ta ^ prod);\n  d64++;\n  s64++;\n  }\n  } else {\n  while (d64 < (uint64_t *) rd.d_top) {\n  ta = *s64;\n  prod = ta;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  prod ^= ta;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  prod ^= ta;\n  AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n  *d64 = (ta ^ prod);\n  d64++;\n  s64++;\n  }\n  }\n  break;\n  */\n  default:\n    if (xor) {\n      while (d64 < (uint64_t*) rd.d_top) {\n        prod = *d64 ;\n        ta = *s64;\n        tb = val;\n\n        while (1) {\n          if (tb & 1) {\n            prod ^= ta;\n          }\n\n          tb >>= 1;\n\n          if (tb == 0) {\n            break;\n          }\n\n          AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        }\n\n        *d64 = prod;\n        d64++;\n        s64++;\n      }\n    } else {\n      while (d64 < (uint64_t*) rd.d_top) {\n        prod = 0 ;\n        ta = *s64;\n        tb = val;\n\n        while (1) {\n          if (tb & 1) {\n            prod ^= ta;\n          }\n\n          tb >>= 1;\n\n          if (tb == 0) {\n            break;\n          }\n\n          AB2(btd->prim_poly, btd->mask1, btd->mask2, ta, t1, t2);\n        }\n\n        *d64 = prod;\n        d64++;\n        s64++;\n      }\n    }\n\n    break;\n  }\n\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\nint gf_w8_bytwo_init(gf_t* gf)\n{\n  gf_internal_t* h;\n  uint64_t ip, m1, m2;\n  struct gf_w8_bytwo_data* btd;\n  h = (gf_internal_t*) gf->scratch;\n  btd = (struct gf_w8_bytwo_data*)(h->private);\n  ip = h->prim_poly & 0xff;\n  m1 = 0xfe;\n  m2 = 0x80;\n  btd->prim_poly = 0;\n  btd->mask1 = 0;\n  btd->mask2 = 0;\n\n  while (ip != 0) {\n    btd->prim_poly |= ip;\n    btd->mask1 |= m1;\n    btd->mask2 |= m2;\n    ip <<= GF_FIELD_WIDTH;\n    m1 <<= GF_FIELD_WIDTH;\n    m2 <<= GF_FIELD_WIDTH;\n  }\n\n  if (h->mult_type == GF_MULT_BYTWO_p) {\n    gf->multiply.w32 = gf_w8_bytwo_p_multiply;\n#ifdef INTEL_SSE2\n\n    if (h->region_type & GF_REGION_NOSIMD) {\n      gf->multiply_region.w32 = gf_w8_bytwo_p_nosse_multiply_region;\n    } else {\n      gf->multiply_region.w32 = gf_w8_bytwo_p_sse_multiply_region;\n    }\n\n#else\n    gf->multiply_region.w32 = gf_w8_bytwo_p_nosse_multiply_region;\n\n    if (h->region_type & GF_REGION_SIMD) {\n      return 0;\n    }\n\n#endif\n  } else {\n    gf->multiply.w32 = gf_w8_bytwo_b_multiply;\n#ifdef INTEL_SSE2\n\n    if (h->region_type & GF_REGION_NOSIMD) {\n      gf->multiply_region.w32 = gf_w8_bytwo_b_nosse_multiply_region;\n    } else {\n      gf->multiply_region.w32 = gf_w8_bytwo_b_sse_multiply_region;\n    }\n\n#else\n    gf->multiply_region.w32 = gf_w8_bytwo_b_nosse_multiply_region;\n\n    if (h->region_type & GF_REGION_SIMD) {\n      return 0;\n    }\n\n#endif\n  }\n\n  return 1;\n}\n\n\n/* ------------------------------------------------------------\n   General procedures.\n   You don't need to error check here on in init, because it's done\n   for you in gf_error_check().\n */\n\nint gf_w8_scratch_size(int mult_type, int region_type, int divide_type,\n                       int arg1, int arg2)\n{\n  switch (mult_type) {\n  case GF_MULT_DEFAULT:\n#if defined(INTEL_SSSE3) || defined(ARM_NEON)\n    return sizeof(gf_internal_t) + sizeof(struct gf_w8_default_data) + 64;\n#endif\n    return sizeof(gf_internal_t) + sizeof(struct gf_w8_single_table_data) + 64;\n\n  case GF_MULT_TABLE:\n    if (region_type == GF_REGION_CAUCHY) {\n      return sizeof(gf_internal_t) + sizeof(struct gf_w8_single_table_data) + 64;\n    }\n\n    if (region_type == GF_REGION_DEFAULT) {\n      return sizeof(gf_internal_t) + sizeof(struct gf_w8_single_table_data) + 64;\n    }\n\n    if (region_type & GF_REGION_DOUBLE_TABLE) {\n      if (region_type == GF_REGION_DOUBLE_TABLE) {\n        return sizeof(gf_internal_t) + sizeof(struct gf_w8_double_table_data) + 64;\n      } else if (region_type == (GF_REGION_DOUBLE_TABLE | GF_REGION_LAZY)) {\n        return sizeof(gf_internal_t) + sizeof(struct gf_w8_double_table_lazy_data) + 64;\n      } else {\n        return 0;\n      }\n    }\n\n    return 0;\n    break;\n\n  case GF_MULT_BYTWO_p:\n  case GF_MULT_BYTWO_b:\n    return sizeof(gf_internal_t) + sizeof(struct gf_w8_bytwo_data);\n    break;\n\n  case GF_MULT_SPLIT_TABLE:\n    if ((arg1 == 4 && arg2 == 8) || (arg1 == 8 && arg2 == 4)) {\n      return sizeof(gf_internal_t) + sizeof(struct gf_w8_half_table_data) + 64;\n    }\n\n    break;\n\n  case GF_MULT_LOG_TABLE:\n    return sizeof(gf_internal_t) + sizeof(struct gf_w8_logtable_data) + 64;\n    break;\n\n  case GF_MULT_LOG_ZERO:\n    return sizeof(gf_internal_t) + sizeof(struct gf_w8_logzero_small_table_data) +\n           64;\n    break;\n\n  case GF_MULT_LOG_ZERO_EXT:\n    return sizeof(gf_internal_t) + sizeof(struct gf_w8_logzero_table_data) + 64;\n    break;\n\n  case GF_MULT_CARRY_FREE:\n    return sizeof(gf_internal_t);\n    break;\n\n  case GF_MULT_SHIFT:\n    return sizeof(gf_internal_t);\n    break;\n\n  case GF_MULT_COMPOSITE:\n    return sizeof(gf_internal_t) + sizeof(struct gf_w8_composite_data) + 64;\n\n  default:\n    return 0;\n  }\n\n  return 0;\n}\n\nint gf_w8_init(gf_t* gf)\n{\n  gf_internal_t* h;\n  h = (gf_internal_t*) gf->scratch;\n\n  /* Allen: set default primitive polynomial / irreducible polynomial if needed */\n\n  if (h->prim_poly == 0) {\n    if (h->mult_type == GF_MULT_COMPOSITE) {\n      h->prim_poly = gf_composite_get_default_poly(h->base_gf);\n\n      if (h->prim_poly == 0) {\n        return 0;  /* JSP: This shouldn't happen, but just in case. */\n      }\n    } else {\n      h->prim_poly = 0x11d;\n    }\n  }\n\n  if (h->mult_type != GF_MULT_COMPOSITE) {\n    h->prim_poly |= 0x100;\n  }\n\n  gf->multiply.w32 = NULL;\n  gf->divide.w32 = NULL;\n  gf->inverse.w32 = NULL;\n  gf->multiply_region.w32 = NULL;\n  gf->extract_word.w32 = gf_w8_extract_word;\n\n  switch (h->mult_type) {\n  case GF_MULT_DEFAULT:\n  case GF_MULT_TABLE:\n    if (gf_w8_table_init(gf) == 0) {\n      return 0;\n    }\n\n    break;\n\n  case GF_MULT_BYTWO_p:\n  case GF_MULT_BYTWO_b:\n    if (gf_w8_bytwo_init(gf) == 0) {\n      return 0;\n    }\n\n    break;\n\n  case GF_MULT_LOG_ZERO:\n  case GF_MULT_LOG_ZERO_EXT:\n  case GF_MULT_LOG_TABLE:\n    if (gf_w8_log_init(gf) == 0) {\n      return 0;\n    }\n\n    break;\n\n  case GF_MULT_CARRY_FREE:\n    if (gf_w8_cfm_init(gf) == 0) {\n      return 0;\n    }\n\n    break;\n\n  case GF_MULT_SHIFT:\n    if (gf_w8_shift_init(gf) == 0) {\n      return 0;\n    }\n\n    break;\n\n  case GF_MULT_SPLIT_TABLE:\n    if (gf_w8_split_init(gf) == 0) {\n      return 0;\n    }\n\n    break;\n\n  case GF_MULT_COMPOSITE:\n    if (gf_w8_composite_init(gf) == 0) {\n      return 0;\n    }\n\n    break;\n\n  default:\n    return 0;\n  }\n\n  if (h->divide_type == GF_DIVIDE_EUCLID) {\n    gf->divide.w32 = gf_w8_divide_from_inverse;\n    gf->inverse.w32 = gf_w8_euclid;\n  } else if (h->divide_type == GF_DIVIDE_MATRIX) {\n    gf->divide.w32 = gf_w8_divide_from_inverse;\n    gf->inverse.w32 = gf_w8_matrix;\n  }\n\n  if (gf->divide.w32 == NULL) {\n    gf->divide.w32 = gf_w8_divide_from_inverse;\n\n    if (gf->inverse.w32 == NULL) {\n      gf->inverse.w32 = gf_w8_euclid;\n    }\n  }\n\n  if (gf->inverse.w32 == NULL) {\n    gf->inverse.w32 = gf_w8_inverse_from_divide;\n  }\n\n  if (h->mult_type == GF_MULT_COMPOSITE && (h->region_type & GF_REGION_ALTMAP)) {\n    gf->extract_word.w32 = gf_w8_composite_extract_word;\n  }\n\n  if (h->region_type == GF_REGION_CAUCHY) {\n    gf->multiply_region.w32 = gf_wgen_cauchy_region;\n    gf->extract_word.w32 = gf_wgen_extract_word;\n  }\n\n  if (gf->multiply_region.w32 == NULL) {\n    gf->multiply_region.w32 = gf_w8_multiply_region_from_single;\n  }\n\n  return 1;\n}\n\n\n/* Inline setup functions */\n\nuint8_t* gf_w8_get_mult_table(gf_t* gf)\n{\n  gf_internal_t* h;\n  struct gf_w8_default_data* ftd;\n  struct gf_w8_single_table_data* std;\n  h = (gf_internal_t*) gf->scratch;\n\n  if (gf->multiply.w32 == gf_w8_default_multiply) {\n    ftd = (struct gf_w8_default_data*) h->private;\n    return (uint8_t*) ftd->multtable;\n  } else if (gf->multiply.w32 == gf_w8_table_multiply) {\n    std = (struct gf_w8_single_table_data*) h->private;\n    return (uint8_t*) std->multtable;\n  }\n\n  return NULL;\n}\n\nuint8_t* gf_w8_get_div_table(gf_t* gf)\n{\n  struct gf_w8_default_data* ftd;\n  struct gf_w8_single_table_data* std;\n\n  if (gf->multiply.w32 == gf_w8_default_multiply) {\n    ftd = (struct gf_w8_default_data*)((gf_internal_t*) gf->scratch)->private;\n    return (uint8_t*) ftd->divtable;\n  } else if (gf->multiply.w32 == gf_w8_table_multiply) {\n    std = (struct gf_w8_single_table_data*)((gf_internal_t*) gf->scratch)->private;\n    return (uint8_t*) std->divtable;\n  }\n\n  return NULL;\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/src/gf_wgen.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_wgen.c\n *\n * Routines for Galois fields for general w < 32.  For specific w, \n   like 4, 8, 16, 32, 64 and 128, see the other files.\n */\n\n#include \"gf_int.h\"\n#include <stdio.h>\n#include <stdlib.h>\n\nstruct gf_wgen_table_w8_data {\n  uint8_t *mult;\n  uint8_t *div;\n  uint8_t base;\n};\n\nstruct gf_wgen_table_w16_data {\n  uint16_t *mult;\n  uint16_t *div;\n  uint16_t base;\n};\n\nstruct gf_wgen_log_w8_data {\n  uint8_t *log;\n  uint8_t *anti;\n  uint8_t *danti;\n  uint8_t base;\n};\n\nstruct gf_wgen_log_w16_data {\n  uint16_t *log;\n  uint16_t *anti;\n  uint16_t *danti;\n  uint16_t base;\n};\n\nstruct gf_wgen_log_w32_data {\n  uint32_t *log;\n  uint32_t *anti;\n  uint32_t *danti;\n  uint32_t base;\n};\n\nstruct gf_wgen_group_data {\n    uint32_t *reduce;\n    uint32_t *shift;\n    uint32_t mask;\n    uint64_t rmask;\n    int tshift;\n    uint32_t memory;\n};\n\nstatic\ninline\ngf_val_32_t gf_wgen_inverse_from_divide (gf_t *gf, gf_val_32_t a)\n{\n  return gf->divide.w32(gf, 1, a);\n}\n\nstatic\ninline\ngf_val_32_t gf_wgen_divide_from_inverse (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  b = gf->inverse.w32(gf, b);\n  return gf->multiply.w32(gf, a, b);\n}\n\nstatic\ninline\ngf_val_32_t gf_wgen_euclid (gf_t *gf, gf_val_32_t b)\n{\n  \n  gf_val_32_t e_i, e_im1, e_ip1;\n  gf_val_32_t d_i, d_im1, d_ip1;\n  gf_val_32_t y_i, y_im1, y_ip1;\n  gf_val_32_t c_i;\n\n  if (b == 0) return -1;\n  e_im1 = ((gf_internal_t *) (gf->scratch))->prim_poly;\n  e_i = b;\n  d_im1 = ((gf_internal_t *) (gf->scratch))->w;\n  for (d_i = d_im1; ((1 << d_i) & e_i) == 0; d_i--) ;\n  y_i = 1;\n  y_im1 = 0;\n\n  while (e_i != 1) {\n\n    e_ip1 = e_im1;\n    d_ip1 = d_im1;\n    c_i = 0;\n\n    while (d_ip1 >= d_i) {\n      c_i ^= (1 << (d_ip1 - d_i));\n      e_ip1 ^= (e_i << (d_ip1 - d_i));\n      if (e_ip1 == 0) return 0;\n      while ((e_ip1 & (1 << d_ip1)) == 0) d_ip1--;\n    }\n\n    y_ip1 = y_im1 ^ gf->multiply.w32(gf, c_i, y_i);\n    y_im1 = y_i;\n    y_i = y_ip1;\n\n    e_im1 = e_i;\n    d_im1 = d_i;\n    e_i = e_ip1;\n    d_i = d_ip1;\n  }\n\n  return y_i;\n}\n\ngf_val_32_t gf_wgen_extract_word(gf_t *gf, void *start, int bytes, int index)\n{\n  uint8_t *ptr;\n  uint32_t rv;\n  int rs;\n  int byte, bit, i;\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  rs = bytes / h->w;\n  byte = index/8;\n  bit = index%8;\n\n  ptr = (uint8_t *) start;\n  ptr += bytes;\n  ptr -= rs;\n  ptr += byte;\n\n  rv = 0;\n  for (i = 0; i < h->w; i++) {\n    rv <<= 1;\n    if ((*ptr) & (1 << bit)) rv |= 1;\n    ptr -= rs;\n  }\n  \n  return rv;\n}\n\nstatic\ninline\ngf_val_32_t gf_wgen_matrix (gf_t *gf, gf_val_32_t b)\n{\n  return gf_bitmatrix_inverse(b, ((gf_internal_t *) (gf->scratch))->w, \n              ((gf_internal_t *) (gf->scratch))->prim_poly);\n}\n\nstatic\ninline\nuint32_t\ngf_wgen_shift_multiply (gf_t *gf, uint32_t a32, uint32_t b32)\n{\n  uint64_t product, i, pp, a, b, one;\n  gf_internal_t *h;\n \n  a = a32;\n  b = b32;\n  h = (gf_internal_t *) gf->scratch;\n  one = 1;\n  pp = h->prim_poly | (one << h->w);\n\n  product = 0;\n\n  for (i = 0; i < (uint64_t)h->w; i++) {\n    if (a & (one << i)) product ^= (b << i);\n  }\n  for (i = h->w*2-1; i >= (uint64_t)h->w; i--) {\n    if (product & (one << i)) product ^= (pp << (i-h->w));\n  }\n  return product;\n}\n\nstatic \nint gf_wgen_shift_init(gf_t *gf)\n{\n  gf->multiply.w32 = gf_wgen_shift_multiply;\n  gf->inverse.w32 = gf_wgen_euclid;\n  return 1;\n}\n\nstatic\ngf_val_32_t\ngf_wgen_bytwo_b_multiply (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  uint32_t prod, pp, bmask;\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  prod = 0;\n  bmask = (1 << (h->w-1));\n\n  while (1) {\n    if (a & 1) prod ^= b;\n    a >>= 1;\n    if (a == 0) return prod;\n    if (b & bmask) {\n      b = ((b << 1) ^ pp);\n    } else {\n      b <<= 1;\n    }\n  }\n}\n\nstatic \nint gf_wgen_bytwo_b_init(gf_t *gf)\n{\n  gf->multiply.w32 = gf_wgen_bytwo_b_multiply;\n  gf->inverse.w32 = gf_wgen_euclid;\n  return 1;\n}\n\nstatic\ninline\ngf_val_32_t\ngf_wgen_bytwo_p_multiply (gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  uint32_t prod, pp, pmask, amask;\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  prod = 0;\n  pmask = (1 << ((h->w)-1)); /*Ben: Had an operator precedence warning here*/\n  amask = pmask;\n\n  while (amask != 0) {\n    if (prod & pmask) {\n      prod = ((prod << 1) ^ pp);\n    } else {\n      prod <<= 1;\n    }\n    if (a & amask) prod ^= b;\n    amask >>= 1;\n  }\n  return prod;\n}\n\n\nstatic \nint gf_wgen_bytwo_p_init(gf_t *gf)\n{\n  gf->multiply.w32 = gf_wgen_bytwo_p_multiply;\n  gf->inverse.w32 = gf_wgen_euclid;\n  return 1;\n}\n\nstatic\nvoid\ngf_wgen_group_set_shift_tables(uint32_t *shift, uint32_t val, gf_internal_t *h)\n{\n  uint32_t i;\n  uint32_t j;\n  int g_s;\n\n  if (h->mult_type == GF_MULT_DEFAULT) {\n    g_s = 2;\n  } else {\n    g_s = h->arg1;\n  }\n\n  shift[0] = 0;\n\n  for (i = 1; i < ((uint32_t)1 << g_s); i <<= 1) {\n    for (j = 0; j < i; j++) shift[i|j] = shift[j]^val;\n    if (val & (1 << (h->w-1))) {\n      val <<= 1;\n      val ^= h->prim_poly;\n    } else {\n      val <<= 1;\n    }\n  }\n}\n\nstatic\ninline\ngf_val_32_t\ngf_wgen_group_s_equals_r_multiply(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  int leftover, rs;\n  uint32_t p, l, ind, a32;\n  int bits_left;\n  int g_s;\n  int w;\n\n  struct gf_wgen_group_data *gd;\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  g_s = h->arg1;\n  w = h->w;\n\n  gd = (struct gf_wgen_group_data *) h->private;\n  gf_wgen_group_set_shift_tables(gd->shift, b, h);\n\n  leftover = w % g_s;\n  if (leftover == 0) leftover = g_s;\n\n  rs = w - leftover;\n  a32 = a;\n  ind = a32 >> rs;\n  a32 <<= leftover;\n  a32 &= gd->mask;\n  p = gd->shift[ind];\n\n  bits_left = rs;\n  rs = w - g_s;\n\n  while (bits_left > 0) {\n    bits_left -= g_s;\n    ind = a32 >> rs;\n    a32 <<= g_s;\n    a32 &= gd->mask;\n    l = p >> rs;\n    p = (gd->shift[ind] ^ gd->reduce[l] ^ (p << g_s)) & gd->mask;\n  }\n  return p;\n}\n\nchar *bits(uint32_t v)\n{\n  char *rv;\n  int i, j;\n\n  rv = malloc(30);\n  j = 0;\n  for (i = 27; i >= 0; i--) {\n    rv[j] = '0' + ((v & (1 << i)) ? 1 : 0);\n    j++;\n  }\n  rv[j] = '\\0';\n  return rv;\n}\nchar *bits_56(uint64_t v)\n{\n  char *rv;\n  int i, j;\n  uint64_t one;\n\n  one = 1;\n\n  rv = malloc(60);\n  j = 0;\n  for (i = 55; i >= 0; i--) {\n    rv[j] = '0' + ((v & (one << i)) ? 1 : 0);\n    j++;\n  }\n  rv[j] = '\\0';\n  return rv;\n}\n\nstatic\ninline\ngf_val_32_t\ngf_wgen_group_multiply(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  int i;\n  int leftover;\n  uint64_t p, l, r;\n  uint32_t a32, ind;\n  int g_s, g_r;\n  struct gf_wgen_group_data *gd;\n  int w;\n\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  if (h->mult_type == GF_MULT_DEFAULT) {\n    g_s = 2;\n    g_r = 8;\n  } else {\n    g_s = h->arg1;\n    g_r = h->arg2;\n  }\n  w = h->w;\n  gd = (struct gf_wgen_group_data *) h->private;\n  gf_wgen_group_set_shift_tables(gd->shift, b, h);\n\n  leftover = w % g_s;\n  if (leftover == 0) leftover = g_s;\n\n  a32 = a;\n  ind = a32 >> (w - leftover);\n  p = gd->shift[ind];\n  p <<= g_s;\n  a32 <<= leftover;\n  a32 &= gd->mask;\n\n  i = (w - leftover);\n  while (i > g_s) {\n    ind = a32 >> (w-g_s);\n    p ^= gd->shift[ind];\n    a32 <<= g_s;\n    a32 &= gd->mask;\n    p <<= g_s;\n    i -= g_s;\n  }\n\n  ind = a32 >> (h->w-g_s);\n  p ^= gd->shift[ind];\n\n  for (i = gd->tshift ; i >= 0; i -= g_r) {\n    l = p & (gd->rmask << i);\n    r = gd->reduce[l >> (i+w)];\n    r <<= (i);\n    p ^= r;\n  }\n  return p & gd->mask;\n}\n\nstatic\nint gf_wgen_group_init(gf_t *gf)\n{\n  uint32_t i, j, p, index;\n  struct gf_wgen_group_data *gd;\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  uint32_t g_s, g_r;\n\n  if (h->mult_type == GF_MULT_DEFAULT) {\n    g_s = 2;\n    g_r = 8;\n  } else {\n    g_s = h->arg1;\n    g_r = h->arg2;\n  }\n  gd = (struct gf_wgen_group_data *) h->private;\n  gd->shift = &(gd->memory);\n  gd->reduce = gd->shift + (1 << g_s);\n  gd->mask = (h->w != 31) ? ((1 << h->w)-1) : 0x7fffffff;\n\n  gd->rmask = (1 << g_r) - 1;\n  gd->rmask <<= h->w;\n\n  gd->tshift = h->w % g_s;\n  if (gd->tshift == 0) gd->tshift = g_s;\n  gd->tshift = (h->w - gd->tshift);\n  gd->tshift = ((gd->tshift-1)/g_r) * g_r;\n\n  gd->reduce[0] = 0;\n  for (i = 0; i < ((uint32_t)1 << g_r); i++) {\n    p = 0;\n    index = 0;\n    for (j = 0; j < g_r; j++) {\n      if (i & (1 << j)) {\n        p ^= (h->prim_poly << j);\n        index ^= (h->prim_poly >> (h->w-j));\n      }\n    }\n    gd->reduce[index] = (p & gd->mask);\n  }\n\n  if (g_s == g_r) {\n    gf->multiply.w32 = gf_wgen_group_s_equals_r_multiply;\n  } else {\n    gf->multiply.w32 = gf_wgen_group_multiply; \n  }\n  gf->divide.w32 = NULL;\n  gf->divide.w32 = NULL;\n  return 1;\n}\n\n\nstatic\ngf_val_32_t\ngf_wgen_table_8_multiply(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  gf_internal_t *h;\n  struct gf_wgen_table_w8_data *std;\n  \n  h = (gf_internal_t *) gf->scratch;\n  std = (struct gf_wgen_table_w8_data *) h->private;\n\n  return (std->mult[(a<<h->w)+b]);\n}\n\nstatic\ngf_val_32_t\ngf_wgen_table_8_divide(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  gf_internal_t *h;\n  struct gf_wgen_table_w8_data *std;\n  \n  h = (gf_internal_t *) gf->scratch;\n  std = (struct gf_wgen_table_w8_data *) h->private;\n\n  return (std->div[(a<<h->w)+b]);\n}\n\nstatic \nint gf_wgen_table_8_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  int w;\n  struct gf_wgen_table_w8_data *std;\n  uint32_t a, b, p;\n  \n  h = (gf_internal_t *) gf->scratch;\n  w = h->w;\n  std = (struct gf_wgen_table_w8_data *) h->private;\n  \n  std->mult = &(std->base);\n  std->div = std->mult + ((1<<h->w)*(1<<h->w));\n  \n  for (a = 0; a < ((uint32_t)1 << w); a++) {\n    std->mult[a] = 0;\n    std->mult[a<<w] = 0;\n    std->div[a] = 0;\n    std->div[a<<w] = 0;\n  }\n    \n  for (a = 1; a < ((uint32_t)1 << w); a++) {\n    for (b = 1; b < ((uint32_t)1 << w); b++) {\n      p = gf_wgen_shift_multiply(gf, a, b);\n      std->mult[(a<<w)|b] = p;\n      std->div[(p<<w)|a] = b;\n    }\n  }\n\n  gf->multiply.w32 = gf_wgen_table_8_multiply;\n  gf->divide.w32 = gf_wgen_table_8_divide;\n  return 1;\n}\n\nstatic\ngf_val_32_t\ngf_wgen_table_16_multiply(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  gf_internal_t *h;\n  struct gf_wgen_table_w16_data *std;\n  \n  h = (gf_internal_t *) gf->scratch;\n  std = (struct gf_wgen_table_w16_data *) h->private;\n\n  return (std->mult[(a<<h->w)+b]);\n}\n\nstatic\ngf_val_32_t\ngf_wgen_table_16_divide(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  gf_internal_t *h;\n  struct gf_wgen_table_w16_data *std;\n  \n  h = (gf_internal_t *) gf->scratch;\n  std = (struct gf_wgen_table_w16_data *) h->private;\n\n  return (std->div[(a<<h->w)+b]);\n}\n\nstatic \nint gf_wgen_table_16_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  int w;\n  struct gf_wgen_table_w16_data *std;\n  uint32_t a, b, p;\n  \n  h = (gf_internal_t *) gf->scratch;\n  w = h->w;\n  std = (struct gf_wgen_table_w16_data *) h->private;\n  \n  std->mult = &(std->base);\n  std->div = std->mult + ((1<<h->w)*(1<<h->w));\n  \n  for (a = 0; a < ((uint32_t)1 << w); a++) {\n    std->mult[a] = 0;\n    std->mult[a<<w] = 0;\n    std->div[a] = 0;\n    std->div[a<<w] = 0;\n  }\n  \n  for (a = 1; a < ((uint32_t)1 << w); a++) {\n    for (b = 1; b < ((uint32_t)1 << w); b++) {\n      p = gf_wgen_shift_multiply(gf, a, b);\n      std->mult[(a<<w)|b] = p;\n      std->div[(p<<w)|a] = b;\n    }\n  }\n\n  gf->multiply.w32 = gf_wgen_table_16_multiply;\n  gf->divide.w32 = gf_wgen_table_16_divide;\n  return 1;\n}\n\nstatic \nint gf_wgen_table_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  \n  h = (gf_internal_t *) gf->scratch;\n  if (h->w <= 8) return gf_wgen_table_8_init(gf);\n  if (h->w <= 14) return gf_wgen_table_16_init(gf);\n\n  /* Returning zero to make the compiler happy, but this won't get \n     executed, because it is tested in _scratch_space. */\n\n  return 0;\n}\n\nstatic\ngf_val_32_t\ngf_wgen_log_8_multiply(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  gf_internal_t *h;\n  struct gf_wgen_log_w8_data *std;\n  \n  h = (gf_internal_t *) gf->scratch;\n  std = (struct gf_wgen_log_w8_data *) h->private;\n\n  if (a == 0 || b == 0) return 0;\n  return (std->anti[std->log[a]+std->log[b]]);\n}\n\nstatic\ngf_val_32_t\ngf_wgen_log_8_divide(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  gf_internal_t *h;\n  struct gf_wgen_log_w8_data *std;\n  int index;\n  \n  h = (gf_internal_t *) gf->scratch;\n  std = (struct gf_wgen_log_w8_data *) h->private;\n\n  if (a == 0 || b == 0) return 0;\n  index = std->log[a];\n  index -= std->log[b];\n\n  return (std->danti[index]);\n}\n\nstatic \nint gf_wgen_log_8_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  struct gf_wgen_log_w8_data *std;\n  int w;\n  uint32_t a, i;\n  int check = 0;\n  \n  h = (gf_internal_t *) gf->scratch;\n  w = h->w;\n  std = (struct gf_wgen_log_w8_data *) h->private;\n  \n  std->log = &(std->base);\n  std->anti = std->log + (1<<h->w);\n  std->danti = std->anti + (1<<h->w)-1;\n  \n  for (i = 0; i < ((uint32_t)1 << w); i++)\n    std->log[i] = 0;\n\n  a = 1;\n  for(i=0; i < ((uint32_t)1<<w)-1; i++)\n  {\n    if (std->log[a] != 0) check = 1;\n    std->log[a] = i;\n    std->anti[i] = a;\n    std->danti[i] = a;\n    a <<= 1;\n    if(a & (1<<w))\n      a ^= h->prim_poly;\n    //a &= ((1 << w)-1);\n  }\n\n  if (check != 0) {\n    _gf_errno = GF_E_LOGPOLY;\n    return 0;\n  }\n\n  gf->multiply.w32 = gf_wgen_log_8_multiply;\n  gf->divide.w32 = gf_wgen_log_8_divide;\n  return 1;\n}\n\nstatic\ngf_val_32_t\ngf_wgen_log_16_multiply(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  gf_internal_t *h;\n  struct gf_wgen_log_w16_data *std;\n  \n  h = (gf_internal_t *) gf->scratch;\n  std = (struct gf_wgen_log_w16_data *) h->private;\n\n  if (a == 0 || b == 0) return 0;\n  return (std->anti[std->log[a]+std->log[b]]);\n}\n\nstatic\ngf_val_32_t\ngf_wgen_log_16_divide(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  gf_internal_t *h;\n  struct gf_wgen_log_w16_data *std;\n  int index;\n  \n  h = (gf_internal_t *) gf->scratch;\n  std = (struct gf_wgen_log_w16_data *) h->private;\n\n  if (a == 0 || b == 0) return 0;\n  index = std->log[a];\n  index -= std->log[b];\n\n  return (std->danti[index]);\n}\n\nstatic \nint gf_wgen_log_16_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  struct gf_wgen_log_w16_data *std;\n  int w;\n  uint32_t a, i;\n  int check = 0;\n  \n  h = (gf_internal_t *) gf->scratch;\n  w = h->w;\n  std = (struct gf_wgen_log_w16_data *) h->private;\n  \n  std->log = &(std->base);\n  std->anti = std->log + (1<<h->w);\n  std->danti = std->anti + (1<<h->w)-1;\n \n  for (i = 0; i < ((uint32_t)1 << w); i++)\n    std->log[i] = 0;\n\n  a = 1;\n  for(i=0; i < ((uint32_t)1<<w)-1; i++)\n  {\n    if (std->log[a] != 0) check = 1;\n    std->log[a] = i;\n    std->anti[i] = a;\n    std->danti[i] = a;\n    a <<= 1;\n    if(a & (1<<w))\n      a ^= h->prim_poly;\n    //a &= ((1 << w)-1);\n  }\n\n  if (check) {\n    if (h->mult_type != GF_MULT_LOG_TABLE) return gf_wgen_shift_init(gf);\n    _gf_errno = GF_E_LOGPOLY;\n    return 0;\n  }\n  \n  gf->multiply.w32 = gf_wgen_log_16_multiply;\n  gf->divide.w32 = gf_wgen_log_16_divide;\n  return 1;\n}\n\nstatic\ngf_val_32_t\ngf_wgen_log_32_multiply(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  gf_internal_t *h;\n  struct gf_wgen_log_w32_data *std;\n  \n  h = (gf_internal_t *) gf->scratch;\n  std = (struct gf_wgen_log_w32_data *) h->private;\n\n  if (a == 0 || b == 0) return 0;\n  return (std->anti[std->log[a]+std->log[b]]);\n}\n\nstatic\ngf_val_32_t\ngf_wgen_log_32_divide(gf_t *gf, gf_val_32_t a, gf_val_32_t b)\n{\n  gf_internal_t *h;\n  struct gf_wgen_log_w32_data *std;\n  int index;\n  \n  h = (gf_internal_t *) gf->scratch;\n  std = (struct gf_wgen_log_w32_data *) h->private;\n\n  if (a == 0 || b == 0) return 0;\n  index = std->log[a];\n  index -= std->log[b];\n\n  return (std->danti[index]);\n}\n\nstatic \nint gf_wgen_log_32_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  struct gf_wgen_log_w32_data *std;\n  int w;\n  uint32_t a, i;\n  int check = 0;\n\n  h = (gf_internal_t *) gf->scratch;\n  w = h->w;\n  std = (struct gf_wgen_log_w32_data *) h->private;\n  \n  std->log = &(std->base);\n  std->anti = std->log + (1<<h->w);\n  std->danti = std->anti + (1<<h->w)-1;\n  \n  for (i = 0; i < ((uint32_t)1 << w); i++)\n    std->log[i] = 0;\n\n  a = 1;\n  for(i=0; i < ((uint32_t)1<<w)-1; i++)\n  {\n    if (std->log[a] != 0) check = 1;\n    std->log[a] = i;\n    std->anti[i] = a;\n    std->danti[i] = a;\n    a <<= 1;\n    if(a & (1<<w))\n      a ^= h->prim_poly;\n    //a &= ((1 << w)-1);\n  }\n\n  if (check != 0) {\n    _gf_errno = GF_E_LOGPOLY;\n    return 0;\n  }\n\n  gf->multiply.w32 = gf_wgen_log_32_multiply;\n  gf->divide.w32 = gf_wgen_log_32_divide;\n  return 1;\n}\n\nstatic \nint gf_wgen_log_init(gf_t *gf)\n{\n  gf_internal_t *h;\n  \n  h = (gf_internal_t *) gf->scratch;\n  if (h->w <= 8) return gf_wgen_log_8_init(gf);\n  if (h->w <= 16) return gf_wgen_log_16_init(gf);\n  if (h->w <= 32) return gf_wgen_log_32_init(gf); \n\n  /* Returning zero to make the compiler happy, but this won't get \n     executed, because it is tested in _scratch_space. */\n\n  return 0;\n}\n\nint gf_wgen_scratch_size(int w, int mult_type, int region_type, int divide_type, int arg1, int arg2)\n{\n\n  switch(mult_type)\n  {\n    case GF_MULT_DEFAULT: \n      if (w <= 8) {\n          return sizeof(gf_internal_t) + sizeof(struct gf_wgen_table_w8_data) +\n               sizeof(uint8_t)*(1 << w)*(1<<w)*2 + 64;\n      } else if (w <= 16) {\n        return sizeof(gf_internal_t) + sizeof(struct gf_wgen_log_w16_data) +\n               sizeof(uint16_t)*(1 << w)*3;\n      } else {\n        return sizeof(gf_internal_t) + sizeof(struct gf_wgen_group_data) +\n               sizeof(uint32_t) * (1 << 2) +\n               sizeof(uint32_t) * (1 << 8) + 64;\n      }\n    case GF_MULT_SHIFT:\n    case GF_MULT_BYTWO_b:\n    case GF_MULT_BYTWO_p:\n      return sizeof(gf_internal_t);\n      break;\n    case GF_MULT_GROUP:\n      return sizeof(gf_internal_t) + sizeof(struct gf_wgen_group_data) +\n               sizeof(uint32_t) * (1 << arg1) +\n               sizeof(uint32_t) * (1 << arg2) + 64;\n      break;\n\n    case GF_MULT_TABLE: \n      if (w <= 8) {\n        return sizeof(gf_internal_t) + sizeof(struct gf_wgen_table_w8_data) +\n               sizeof(uint8_t)*(1 << w)*(1<<w)*2 + 64;\n      } else if (w < 15) {\n        return sizeof(gf_internal_t) + sizeof(struct gf_wgen_table_w16_data) +\n               sizeof(uint16_t)*(1 << w)*(1<<w)*2 + 64;\n      } \n      return 0;\n    case GF_MULT_LOG_TABLE: \n      if (w <= 8) {\n        return sizeof(gf_internal_t) + sizeof(struct gf_wgen_log_w8_data) +\n               sizeof(uint8_t)*(1 << w)*3;\n      } else if (w <= 16) {\n        return sizeof(gf_internal_t) + sizeof(struct gf_wgen_log_w16_data) +\n               sizeof(uint16_t)*(1 << w)*3;\n      } else if (w <= 27) {\n        return sizeof(gf_internal_t) + sizeof(struct gf_wgen_log_w32_data) +\n               sizeof(uint32_t)*(1 << w)*3;\n      } else \n      return 0;\n    default:\n      return 0;\n   }\n}\n\nvoid\ngf_wgen_cauchy_region(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  gf_internal_t *h;\n  gf_region_data rd;\n  int written;    \n  int rs, i, j;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, -1);\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  h = (gf_internal_t *) gf->scratch;\n  rs = bytes / (h->w);\n  \n  written = (xor) ? 0xffffffff : 0;\n  for (i = 0; i < h->w; i++) {\n    for (j = 0; j < h->w; j++) {\n      if (val & (1 << j)) {\n        gf_multby_one(src, ((uint8_t *)dest) + j*rs, rs, (written & (1 << j)));\n        written |= (1 << j);\n      }\n    }\n    src = (uint8_t *)src + rs;\n    val = gf->multiply.w32(gf, val, 2);\n  }\n}\n\nint gf_wgen_init(gf_t *gf)\n{\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n  if (h->prim_poly == 0) {\n    switch (h->w) {\n      case 1: h->prim_poly = 1; break;\n      case 2: h->prim_poly = 7; break;\n      case 3: h->prim_poly = 013; break;\n      case 4: h->prim_poly = 023; break;\n      case 5: h->prim_poly = 045; break;\n      case 6: h->prim_poly = 0103; break;\n      case 7: h->prim_poly = 0211; break;\n      case 8: h->prim_poly = 0435; break;\n      case 9: h->prim_poly = 01021; break;\n      case 10: h->prim_poly = 02011; break;\n      case 11: h->prim_poly = 04005; break;\n      case 12: h->prim_poly = 010123; break;\n      case 13: h->prim_poly = 020033; break;\n      case 14: h->prim_poly = 042103; break;\n      case 15: h->prim_poly = 0100003; break;\n      case 16: h->prim_poly = 0210013; break;\n      case 17: h->prim_poly = 0400011; break;\n      case 18: h->prim_poly = 01000201; break;\n      case 19: h->prim_poly = 02000047; break;\n      case 20: h->prim_poly = 04000011; break;\n      case 21: h->prim_poly = 010000005; break;\n      case 22: h->prim_poly = 020000003; break;\n      case 23: h->prim_poly = 040000041; break;\n      case 24: h->prim_poly = 0100000207; break;\n      case 25: h->prim_poly = 0200000011; break;\n      case 26: h->prim_poly = 0400000107; break;\n      case 27: h->prim_poly = 01000000047; break;\n      case 28: h->prim_poly = 02000000011; break;\n      case 29: h->prim_poly = 04000000005; break;\n      case 30: h->prim_poly = 010040000007; break;\n      case 31: h->prim_poly = 020000000011; break;\n      case 32: h->prim_poly = 00020000007; break;\n      default: fprintf(stderr, \"gf_wgen_init: w not defined yet\\n\"); exit(1);\n    }\n  } else {\n    if (h->w == 32) {\n      h->prim_poly &= 0xffffffff;\n    } else {\n      h->prim_poly |= (1 << h->w);\n      if (h->prim_poly & ~((1ULL<<(h->w+1))-1)) return 0;\n    }\n  }\n\n  gf->multiply.w32 = NULL;\n  gf->divide.w32 = NULL;\n  gf->inverse.w32 = NULL;\n  gf->multiply_region.w32 = gf_wgen_cauchy_region;\n  gf->extract_word.w32 = gf_wgen_extract_word;\n\n  switch(h->mult_type) {\n    case GF_MULT_DEFAULT:\n      if (h->w <= 8) {\n        if (gf_wgen_table_init(gf) == 0) return 0; \n      } else if (h->w <= 16) {\n        if (gf_wgen_log_init(gf) == 0) return 0; \n      } else {\n        if (gf_wgen_bytwo_p_init(gf) == 0) return 0; \n      }\n      break;\n    case GF_MULT_SHIFT:     if (gf_wgen_shift_init(gf) == 0) return 0; break;\n    case GF_MULT_BYTWO_b:     if (gf_wgen_bytwo_b_init(gf) == 0) return 0; break;\n    case GF_MULT_BYTWO_p:     if (gf_wgen_bytwo_p_init(gf) == 0) return 0; break;\n    case GF_MULT_GROUP:     if (gf_wgen_group_init(gf) == 0) return 0; break;\n    case GF_MULT_TABLE:     if (gf_wgen_table_init(gf) == 0) return 0; break;\n    case GF_MULT_LOG_TABLE: if (gf_wgen_log_init(gf) == 0) return 0; break;\n    default: return 0;\n  }\n  if (h->divide_type == GF_DIVIDE_EUCLID) {\n    gf->divide.w32 = gf_wgen_divide_from_inverse;\n    gf->inverse.w32 = gf_wgen_euclid;\n  } else if (h->divide_type == GF_DIVIDE_MATRIX) {\n    gf->divide.w32 = gf_wgen_divide_from_inverse;\n    gf->inverse.w32 = gf_wgen_matrix;\n  }\n\n  if (gf->inverse.w32== NULL && gf->divide.w32 == NULL) gf->inverse.w32 = gf_wgen_euclid;\n\n  if (gf->inverse.w32 != NULL && gf->divide.w32 == NULL) {\n    gf->divide.w32 = gf_wgen_divide_from_inverse;\n  }\n  if (gf->inverse.w32 == NULL && gf->divide.w32 != NULL) {\n    gf->inverse.w32 = gf_wgen_inverse_from_divide;\n  }\n  return 1;\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/src/neon/gf_w16_neon.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * Copyright (c) 2014: Janne Grunau <j@jannau.net>\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *     notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n *\n *\n * gf_w16_neon.c\n *\n * Neon routines for 16-bit Galois fields\n *\n */\n\n#include \"gf_int.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include \"gf_w16.h\"\n\n#ifdef ARCH_AARCH64\nstatic\ninline\nvoid\nneon_w16_split_4_multiply_region(gf_t *gf, uint16_t *src, uint16_t *dst,\n                                 uint16_t *d_end, uint8_t *tbl,\n                                 gf_val_32_t val, int xor)\n{\n  unsigned i;\n  uint8_t *high = tbl + 4 * 16;\n  uint16x8_t va0, va1, r0, r1;\n  uint8x16_t loset, rl, rh;\n  uint8x16x2_t va;\n\n  uint8x16_t tbl_h[4], tbl_l[4];\n  for (i = 0; i < 4; i++) {\n      tbl_l[i] = vld1q_u8(tbl + i*16);\n      tbl_h[i] = vld1q_u8(high + i*16);\n  }\n\n  loset = vdupq_n_u8(0xf);\n\n  while (dst < d_end) {\n      va0 = vld1q_u16(src);\n      va1 = vld1q_u16(src + 8);\n\n      va = vtrnq_u8(vreinterpretq_u8_u16(va0), vreinterpretq_u8_u16(va1));\n\n      rl = vqtbl1q_u8(tbl_l[0], vandq_u8(va.val[0], loset));\n      rh = vqtbl1q_u8(tbl_h[0], vandq_u8(va.val[0], loset));\n      rl = veorq_u8(rl, vqtbl1q_u8(tbl_l[2], vandq_u8(va.val[1], loset)));\n      rh = veorq_u8(rh, vqtbl1q_u8(tbl_h[2], vandq_u8(va.val[1], loset)));\n\n      va.val[0] = vshrq_n_u8(va.val[0], 4);\n      va.val[1] = vshrq_n_u8(va.val[1], 4);\n\n      rl = veorq_u8(rl, vqtbl1q_u8(tbl_l[1], va.val[0]));\n      rh = veorq_u8(rh, vqtbl1q_u8(tbl_h[1], va.val[0]));\n      rl = veorq_u8(rl, vqtbl1q_u8(tbl_l[3], va.val[1]));\n      rh = veorq_u8(rh, vqtbl1q_u8(tbl_h[3], va.val[1]));\n\n      va = vtrnq_u8(rl, rh);\n      r0 = vreinterpretq_u16_u8(va.val[0]);\n      r1 = vreinterpretq_u16_u8(va.val[1]);\n\n      if (xor) {\n          va0 = vld1q_u16(dst);\n          va1 = vld1q_u16(dst + 8);\n          r0 = veorq_u16(r0, va0);\n          r1 = veorq_u16(r1, va1);\n      }\n      vst1q_u16(dst, r0);\n      vst1q_u16(dst + 8, r1);\n\n      src += 16;\n      dst += 16;\n  }\n}\n\nstatic\ninline\nvoid\nneon_w16_split_4_altmap_multiply_region(gf_t *gf, uint8_t *src,\n                                        uint8_t *dst, uint8_t *d_end,\n                                        uint8_t *tbl, gf_val_32_t val,\n                                        int xor)\n{\n  unsigned i;\n  uint8_t *high = tbl + 4 * 16;\n  uint8x16_t vh, vl, rh, rl;\n  uint8x16_t loset;\n\n  uint8x16_t tbl_h[4], tbl_l[4];\n  for (i = 0; i < 4; i++) {\n      tbl_l[i] = vld1q_u8(tbl + i*16);\n      tbl_h[i] = vld1q_u8(high + i*16);\n  }\n\n  loset = vdupq_n_u8(0xf);\n\n  while (dst < d_end) {\n      vh = vld1q_u8(src);\n      vl = vld1q_u8(src + 16);\n\n      rl = vqtbl1q_u8(tbl_l[0], vandq_u8(vl, loset));\n      rh = vqtbl1q_u8(tbl_h[0], vandq_u8(vl, loset));\n      rl = veorq_u8(rl, vqtbl1q_u8(tbl_l[2], vandq_u8(vh, loset)));\n      rh = veorq_u8(rh, vqtbl1q_u8(tbl_h[2], vandq_u8(vh, loset)));\n\n      vl = vshrq_n_u8(vl, 4);\n      vh = vshrq_n_u8(vh, 4);\n\n      rl = veorq_u8(rl, vqtbl1q_u8(tbl_l[1], vl));\n      rh = veorq_u8(rh, vqtbl1q_u8(tbl_h[1], vl));\n      rl = veorq_u8(rl, vqtbl1q_u8(tbl_l[3], vh));\n      rh = veorq_u8(rh, vqtbl1q_u8(tbl_h[3], vh));\n\n      if (xor) {\n          vh = vld1q_u8(dst);\n          vl = vld1q_u8(dst + 16);\n          rh = veorq_u8(rh, vh);\n          rl = veorq_u8(rl, vl);\n      }\n      vst1q_u8(dst, rh);\n      vst1q_u8(dst + 16, rl);\n\n      src += 32;\n      dst += 32;\n  }\n}\n\n#else /* ARCH_AARCH64 */\n\nstatic\ninline\nvoid\nneon_w16_split_4_multiply_region(gf_t *gf, uint16_t *src, uint16_t *dst,\n                                 uint16_t *d_end, uint8_t *tbl,\n                                 gf_val_32_t val, int xor)\n{\n  unsigned i;\n  uint8_t *high = tbl + 4 * 16;\n  uint16x8_t va, r;\n  uint8x8_t loset, vb, vc, rl, rh;\n\n  uint8x8x2_t tbl_h[4], tbl_l[4];\n  for (i = 0; i < 4; i++) {\n      tbl_l[i].val[0] = vld1_u8(tbl + i*16);\n      tbl_l[i].val[1] = vld1_u8(tbl + i*16 + 8);\n      tbl_h[i].val[0] = vld1_u8(high + i*16);\n      tbl_h[i].val[1] = vld1_u8(high + i*16 + 8);\n  }\n\n  loset = vdup_n_u8(0xf);\n\n  while (dst < d_end) {\n      va = vld1q_u16(src);\n\n      vb = vmovn_u16(va);\n      vc = vshrn_n_u16(va, 8);\n\n      rl = vtbl2_u8(tbl_l[0], vand_u8(vb, loset));\n      rh = vtbl2_u8(tbl_h[0], vand_u8(vb, loset));\n      vb = vshr_n_u8(vb, 4);\n      rl = veor_u8(rl, vtbl2_u8(tbl_l[2], vand_u8(vc, loset)));\n      rh = veor_u8(rh, vtbl2_u8(tbl_h[2], vand_u8(vc, loset)));\n      vc = vshr_n_u8(vc, 4);\n      rl = veor_u8(rl, vtbl2_u8(tbl_l[1], vb));\n      rh = veor_u8(rh, vtbl2_u8(tbl_h[1], vb));\n      rl = veor_u8(rl, vtbl2_u8(tbl_l[3], vc));\n      rh = veor_u8(rh, vtbl2_u8(tbl_h[3], vc));\n\n      r  = vmovl_u8(rl);\n      r  = vorrq_u16(r, vshll_n_u8(rh, 8));\n\n      if (xor) {\n          va = vld1q_u16(dst);\n          r = veorq_u16(r, va);\n      }\n      vst1q_u16(dst, r);\n\n      src += 8;\n      dst += 8;\n  }\n}\n\nstatic\ninline\nvoid\nneon_w16_split_4_altmap_multiply_region(gf_t *gf, uint8_t *src,\n                                        uint8_t *dst, uint8_t *d_end,\n                                        uint8_t *tbl, gf_val_32_t val,\n                                        int xor)\n{\n  unsigned i;\n  uint8_t *high = tbl + 4 * 16;\n  uint8x8_t vh0, vh1, vl0, vl1, r0, r1, r2, r3;\n  uint8x8_t loset;\n\n  uint8x8x2_t tbl_h[4], tbl_l[4];\n  for (i = 0; i < 4; i++) {\n      tbl_l[i].val[0] = vld1_u8(tbl + i*16);\n      tbl_l[i].val[1] = vld1_u8(tbl + i*16 + 8);\n      tbl_h[i].val[0] = vld1_u8(high + i*16);\n      tbl_h[i].val[1] = vld1_u8(high + i*16 + 8);\n  }\n\n  loset = vdup_n_u8(0xf);\n\n  while (dst < d_end) {\n      vh0 = vld1_u8(src);\n      vh1 = vld1_u8(src + 8);\n      vl0 = vld1_u8(src + 16);\n      vl1 = vld1_u8(src + 24);\n\n      r0 = vtbl2_u8(tbl_l[0], vand_u8(vh0, loset));\n      r1 = vtbl2_u8(tbl_h[0], vand_u8(vh1, loset));\n      r2 = vtbl2_u8(tbl_l[2], vand_u8(vl0, loset));\n      r3 = vtbl2_u8(tbl_h[2], vand_u8(vl1, loset));\n\n      vh0 = vshr_n_u8(vh0, 4);\n      vh1 = vshr_n_u8(vh1, 4);\n      vl0 = vshr_n_u8(vl0, 4);\n      vl1 = vshr_n_u8(vl1, 4);\n\n      r0 = veor_u8(r0, vtbl2_u8(tbl_l[1], vh0));\n      r1 = veor_u8(r1, vtbl2_u8(tbl_h[1], vh1));\n      r2 = veor_u8(r2, vtbl2_u8(tbl_l[3], vl0));\n      r3 = veor_u8(r3, vtbl2_u8(tbl_h[3], vl1));\n\n      if (xor) {\n          vh0 = vld1_u8(dst);\n          vh1 = vld1_u8(dst + 8);\n          vl0 = vld1_u8(dst + 16);\n          vl1 = vld1_u8(dst + 24);\n          r0  = veor_u8(r0, vh0);\n          r1  = veor_u8(r1, vh1);\n          r2  = veor_u8(r2, vl0);\n          r3  = veor_u8(r3, vl1);\n      }\n      vst1_u8(dst,      r0);\n      vst1_u8(dst +  8, r1);\n      vst1_u8(dst + 16, r2);\n      vst1_u8(dst + 24, r3);\n\n      src += 32;\n      dst += 32;\n  }\n}\n#endif /* ARCH_AARCH64 */\n\nstatic\ninline\nvoid\nneon_w16_split_4_16_lazy_multiply_region(gf_t *gf, void *src, void *dest,\n                                         gf_val_32_t val, int bytes, int xor,\n                                         int altmap)\n{\n  gf_region_data rd;\n  unsigned i, j;\n  uint64_t c, prod;\n  uint8_t tbl[2 * 4 * 16];\n  uint8_t *high = tbl + 4 * 16;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  for (i = 0; i < 4; i++) {\n    for (j = 0; j < 16; j++) {\n      c = (j << (i*4));\n      prod = gf->multiply.w32(gf, c, val);\n      tbl[i*16 + j]  = prod & 0xff;\n      high[i*16 + j] = prod >> 8;\n    }\n  }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 32);\n  gf_do_initial_region_alignment(&rd);\n\n  if (altmap) {\n    uint8_t *s8   = rd.s_start;\n    uint8_t *d8   = rd.d_start;\n    uint8_t *end8 = rd.d_top;\n    if (xor)\n      neon_w16_split_4_altmap_multiply_region(gf, s8, d8, end8, tbl, val, 1);\n    else\n      neon_w16_split_4_altmap_multiply_region(gf, s8, d8, end8, tbl, val, 0);\n  } else {\n    uint16_t *s16   = rd.s_start;\n    uint16_t *d16   = rd.d_start;\n    uint16_t *end16 = rd.d_top;\n    if (xor)\n      neon_w16_split_4_multiply_region(gf, s16, d16, end16, tbl, val, 1);\n    else\n      neon_w16_split_4_multiply_region(gf, s16, d16, end16, tbl, val, 0);\n  }\n\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\nvoid\ngf_w16_split_4_16_lazy_multiply_region_neon(gf_t *gf, void *src, void *dest,\n                                            gf_val_32_t val, int bytes, int xor)\n{\n  neon_w16_split_4_16_lazy_multiply_region(gf, src, dest, val, bytes, xor, 0);\n}\n\nstatic\nvoid\ngf_w16_split_4_16_lazy_altmap_multiply_region_neon(gf_t *gf, void *src,\n                                                   void *dest,\n                                                   gf_val_32_t val, int bytes,\n                                                   int xor)\n{\n  neon_w16_split_4_16_lazy_multiply_region(gf, src, dest, val, bytes, xor, 1);\n}\n\n\nvoid gf_w16_neon_split_init(gf_t *gf)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n\n  if (h->region_type & GF_REGION_ALTMAP)\n    gf->multiply_region.w32 = gf_w16_split_4_16_lazy_altmap_multiply_region_neon;\n  else\n    gf->multiply_region.w32 = gf_w16_split_4_16_lazy_multiply_region_neon;\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/src/neon/gf_w32_neon.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * Copyright (c) 2014: Janne Grunau <j@jannau.net>\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *     notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n *\n * gf_w32_neon.c\n *\n * Neon routines for 32-bit Galois fields\n *\n */\n\n\n#include \"gf_int.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include \"gf_w32.h\"\n\n#ifndef ARCH_AARCH64\n#define vqtbl1q_u8(tbl, v) vcombine_u8(vtbl2_u8(tbl, vget_low_u8(v)),   \\\n                                       vtbl2_u8(tbl, vget_high_u8(v)))\n#endif\n\nstatic\nvoid\nneon_w32_split_4_32_multiply_region(gf_t *gf, uint32_t *src, uint32_t *dst,\n                                    uint32_t *d_end, uint8_t btable[8][4][16],\n                                    uint32_t val, int xor, int altmap)\n{\n  int i, j;\n#ifdef ARCH_AARCH64\n  uint8x16_t tables[8][4];\n#else\n  uint8x8x2_t tables[8][4];\n#endif\n  uint32x4_t v0, v1, v2, v3, s0, s1, s2, s3;\n  uint8x16_t p0, p1, p2, p3, si, mask1;\n  uint16x8x2_t r0, r1;\n  uint8x16x2_t q0, q1;\n\n  for (i = 0; i < 8; i++) {\n    for (j = 0; j < 4; j++) {\n#ifdef ARCH_AARCH64\n      tables[i][j] = vld1q_u8(btable[i][j]);\n#else\n      tables[i][j].val[0] = vld1_u8(btable[i][j]);\n      tables[i][j].val[1] = vld1_u8(btable[i][j] + 8);\n#endif\n    }\n  }\n\n  mask1 = vdupq_n_u8(0xf);\n\n  while (dst < d_end) {\n\n      v0 = vld1q_u32(src); src += 4;\n      v1 = vld1q_u32(src); src += 4;\n      v2 = vld1q_u32(src); src += 4;\n      v3 = vld1q_u32(src); src += 4;\n\n      if (altmap) {\n          q0.val[0] = vreinterpretq_u8_u32(v0);\n          q0.val[1] = vreinterpretq_u8_u32(v1);\n          q1.val[0] = vreinterpretq_u8_u32(v2);\n          q1.val[1] = vreinterpretq_u8_u32(v3);\n      } else {\n          r0 = vtrnq_u16(vreinterpretq_u16_u32(v0), vreinterpretq_u16_u32(v2));\n          r1 = vtrnq_u16(vreinterpretq_u16_u32(v1), vreinterpretq_u16_u32(v3));\n\n          q0 = vtrnq_u8(vreinterpretq_u8_u16(r0.val[0]),\n                        vreinterpretq_u8_u16(r1.val[0]));\n          q1 = vtrnq_u8(vreinterpretq_u8_u16(r0.val[1]),\n                        vreinterpretq_u8_u16(r1.val[1]));\n      }\n\n      si = vandq_u8(q0.val[0], mask1);\n      p0 = vqtbl1q_u8(tables[0][0], si);\n      p1 = vqtbl1q_u8(tables[0][1], si);\n      p2 = vqtbl1q_u8(tables[0][2], si);\n      p3 = vqtbl1q_u8(tables[0][3], si);\n\n      si = vshrq_n_u8(q0.val[0], 4);\n      p0 = veorq_u8(p0, vqtbl1q_u8(tables[1][0], si));\n      p1 = veorq_u8(p1, vqtbl1q_u8(tables[1][1], si));\n      p2 = veorq_u8(p2, vqtbl1q_u8(tables[1][2], si));\n      p3 = veorq_u8(p3, vqtbl1q_u8(tables[1][3], si));\n\n      si = vandq_u8(q0.val[1], mask1);\n      p0 = veorq_u8(p0, vqtbl1q_u8(tables[2][0], si));\n      p1 = veorq_u8(p1, vqtbl1q_u8(tables[2][1], si));\n      p2 = veorq_u8(p2, vqtbl1q_u8(tables[2][2], si));\n      p3 = veorq_u8(p3, vqtbl1q_u8(tables[2][3], si));\n\n      si = vshrq_n_u8(q0.val[1], 4);\n      p0 = veorq_u8(p0, vqtbl1q_u8(tables[3][0], si));\n      p1 = veorq_u8(p1, vqtbl1q_u8(tables[3][1], si));\n      p2 = veorq_u8(p2, vqtbl1q_u8(tables[3][2], si));\n      p3 = veorq_u8(p3, vqtbl1q_u8(tables[3][3], si));\n\n      si = vandq_u8(q1.val[0], mask1);\n      p0 = veorq_u8(p0, vqtbl1q_u8(tables[4][0], si));\n      p1 = veorq_u8(p1, vqtbl1q_u8(tables[4][1], si));\n      p2 = veorq_u8(p2, vqtbl1q_u8(tables[4][2], si));\n      p3 = veorq_u8(p3, vqtbl1q_u8(tables[4][3], si));\n\n      si = vshrq_n_u8(q1.val[0], 4);\n      p0 = veorq_u8(p0, vqtbl1q_u8(tables[5][0], si));\n      p1 = veorq_u8(p1, vqtbl1q_u8(tables[5][1], si));\n      p2 = veorq_u8(p2, vqtbl1q_u8(tables[5][2], si));\n      p3 = veorq_u8(p3, vqtbl1q_u8(tables[5][3], si));\n\n      si = vandq_u8(q1.val[1], mask1);\n      p0 = veorq_u8(p0, vqtbl1q_u8(tables[6][0], si));\n      p1 = veorq_u8(p1, vqtbl1q_u8(tables[6][1], si));\n      p2 = veorq_u8(p2, vqtbl1q_u8(tables[6][2], si));\n      p3 = veorq_u8(p3, vqtbl1q_u8(tables[6][3], si));\n\n      si = vshrq_n_u8(q1.val[1], 4);\n      p0 = veorq_u8(p0, vqtbl1q_u8(tables[7][0], si));\n      p1 = veorq_u8(p1, vqtbl1q_u8(tables[7][1], si));\n      p2 = veorq_u8(p2, vqtbl1q_u8(tables[7][2], si));\n      p3 = veorq_u8(p3, vqtbl1q_u8(tables[7][3], si));\n\n      if (altmap) {\n          s0 = vreinterpretq_u32_u8(p0);\n          s1 = vreinterpretq_u32_u8(p1);\n          s2 = vreinterpretq_u32_u8(p2);\n          s3 = vreinterpretq_u32_u8(p3);\n      } else {\n          q0 = vtrnq_u8(p0, p1);\n          q1 = vtrnq_u8(p2, p3);\n\n          r0 = vtrnq_u16(vreinterpretq_u16_u8(q0.val[0]),\n                         vreinterpretq_u16_u8(q1.val[0]));\n          r1 = vtrnq_u16(vreinterpretq_u16_u8(q0.val[1]),\n                         vreinterpretq_u16_u8(q1.val[1]));\n\n          s0 = vreinterpretq_u32_u16(r0.val[0]);\n          s1 = vreinterpretq_u32_u16(r1.val[0]);\n          s2 = vreinterpretq_u32_u16(r0.val[1]);\n          s3 = vreinterpretq_u32_u16(r1.val[1]);\n      }\n\n      if (xor) {\n          v0 = vld1q_u32(dst);\n          v1 = vld1q_u32(dst + 4);\n          v2 = vld1q_u32(dst + 8);\n          v3 = vld1q_u32(dst + 12);\n          s0 = veorq_u32(s0, v0);\n          s1 = veorq_u32(s1, v1);\n          s2 = veorq_u32(s2, v2);\n          s3 = veorq_u32(s3, v3);\n      }\n\n      vst1q_u32(dst,      s0);\n      vst1q_u32(dst + 4,  s1);\n      vst1q_u32(dst + 8,  s2);\n      vst1q_u32(dst + 12, s3);\n\n      dst += 16;\n  }\n}\n\nstatic\ninline\nvoid\nneon_w32_split_4_32_lazy_multiply_region(gf_t *gf, void *src, void *dest, uint32_t val, int bytes, int xor, int altmap)\n{\n  gf_internal_t *h;\n  int i, j, k;\n  uint32_t pp, v, *s32, *d32, *top, tmp_table[16];\n  uint8_t btable[8][4][16];\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 64);\n  gf_do_initial_region_alignment(&rd);\n\n  s32 = (uint32_t *) rd.s_start;\n  d32 = (uint32_t *) rd.d_start;\n  top = (uint32_t *) rd.d_top;\n\n  v = val;\n  for (i = 0; i < 8; i++) {\n    tmp_table[0] = 0;\n    for (j = 1; j < 16; j <<= 1) {\n      for (k = 0; k < j; k++) {\n        tmp_table[k^j] = (v ^ tmp_table[k]);\n      }\n      v = (v & GF_FIRST_BIT) ? ((v << 1) ^ pp) : (v << 1);\n    }\n    for (j = 0; j < 4; j++) {\n      for (k = 0; k < 16; k++) {\n        btable[i][j][k] = (uint8_t) tmp_table[k];\n        tmp_table[k] >>= 8;\n      }\n    }\n  }\n\n  if (xor)\n    neon_w32_split_4_32_multiply_region(gf, s32, d32, top, btable, val, 1, altmap);\n  else\n    neon_w32_split_4_32_multiply_region(gf, s32, d32, top, btable, val, 0, altmap);\n\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\nvoid\ngf_w32_split_4_32_lazy_multiply_region_neon(gf_t *gf, void *src, void *dest,\n                                            gf_val_32_t val, int bytes, int xor)\n{\n  neon_w32_split_4_32_lazy_multiply_region(gf, src, dest, val, bytes, xor, 0);\n}\n\nstatic\nvoid\ngf_w32_split_4_32_lazy_altmap_multiply_region_neon(gf_t *gf, void *src,\n                                                   void *dest, gf_val_32_t val,\n                                                   int bytes, int xor)\n{\n  neon_w32_split_4_32_lazy_multiply_region(gf, src, dest, val, bytes, xor, 1);\n}\n\nvoid gf_w32_neon_split_init(gf_t *gf)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n\n  if (h->region_type & GF_REGION_ALTMAP)\n      gf->multiply_region.w32 = gf_w32_split_4_32_lazy_altmap_multiply_region_neon;\n  else\n      gf->multiply_region.w32 = gf_w32_split_4_32_lazy_multiply_region_neon;\n\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/src/neon/gf_w4_neon.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * Copyright (c) 2014: Janne Grunau <j@jannau.net>\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *     notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n *\n * gf_w4_neon.c\n *\n * Neon routines for 4-bit Galois fields\n *\n */\n\n#include \"gf_int.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include \"gf_w4.h\"\n\nstatic\ngf_val_32_t\ngf_w4_neon_clm_multiply (gf_t *gf, gf_val_32_t a4, gf_val_32_t b4)\n{\n  gf_val_32_t rv = 0;\n  poly8x8_t       result, prim_poly;\n  poly8x8_t       a, b, w;\n  uint8x8_t       v;\n  gf_internal_t * h = gf->scratch;\n\n  a =  vdup_n_p8 (a4);\n  b =  vdup_n_p8 (b4);\n\n  prim_poly = vdup_n_p8 ((uint32_t)(h->prim_poly & 0x1fULL));\n\n  /* Do the initial multiply */\n  result = vmul_p8 (a, b);\n  v = vshr_n_u8 (vreinterpret_u8_p8(result), 4);\n  w = vmul_p8 (prim_poly, vreinterpret_p8_u8(v));\n  result = vreinterpret_p8_u8 (veor_u8 (vreinterpret_u8_p8(result), vreinterpret_u8_p8(w)));\n\n  /* Extracts 32 bit value from result. */\n  rv = (gf_val_32_t)vget_lane_u8 (vreinterpret_u8_p8 (result), 0);\n\n  return rv;\n}\n\nstatic inline void\nneon_clm_multiply_region_from_single (gf_t *gf, uint8_t *s8, uint8_t *d8,\n                                      gf_val_32_t val, uint8_t *d_end, int xor)\n{\n  gf_internal_t * h = gf->scratch;\n  poly8x8_t       prim_poly;\n  poly8x8_t       a, w, even, odd;\n  uint8x8_t       b, c, v, mask;\n\n  a         = vdup_n_p8 (val);\n  mask      = vdup_n_u8 (0xf);\n  prim_poly = vdup_n_p8 ((uint8_t)(h->prim_poly & 0x1fULL));\n\n  while (d8 < d_end) {\n    b = vld1_u8 (s8);\n\n    even = vreinterpret_p8_u8 (vand_u8 (b, mask));\n    odd  = vreinterpret_p8_u8 (vshr_n_u8 (b, 4));\n\n    if (xor)\n        c = vld1_u8 (d8);\n\n    even = vmul_p8 (a, even);\n    odd  = vmul_p8 (a, odd);\n\n    v = vshr_n_u8 (vreinterpret_u8_p8(even), 4);\n    w = vmul_p8 (prim_poly, vreinterpret_p8_u8(v));\n    even = vreinterpret_p8_u8 (veor_u8 (vreinterpret_u8_p8(even), vreinterpret_u8_p8(w)));\n\n    v = vshr_n_u8 (vreinterpret_u8_p8(odd), 4);\n    w = vmul_p8 (prim_poly, vreinterpret_p8_u8(v));\n    odd = vreinterpret_p8_u8 (veor_u8 (vreinterpret_u8_p8(odd), vreinterpret_u8_p8(w)));\n\n    v = veor_u8 (vreinterpret_u8_p8 (even), vshl_n_u8 (vreinterpret_u8_p8 (odd), 4));\n\n    if (xor)\n      v = veor_u8 (c, v);\n\n    vst1_u8 (d8, v);\n\n    d8 += 8;\n    s8 += 8;\n  }\n}\n\n\nstatic void\ngf_w4_neon_clm_multiply_region_from_single (gf_t *gf, void *src, void *dest,\n                                            gf_val_32_t val, int bytes, int xor)\n{\n  gf_region_data rd;\n  uint8_t *s8;\n  uint8_t *d8;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n\n  s8 = (uint8_t *) rd.s_start;\n  d8 = (uint8_t *) rd.d_start;\n\n  if (xor)\n    neon_clm_multiply_region_from_single (gf, s8, d8, val, rd.d_top, 1);\n  else\n    neon_clm_multiply_region_from_single (gf, s8, d8, val, rd.d_top, 0);\n\n  gf_do_final_region_alignment(&rd);\n}\n\n#ifndef ARCH_AARCH64\n#define vqtbl1q_u8(tbl, v) vcombine_u8(vtbl2_u8(tbl, vget_low_u8(v)),   \\\n                                       vtbl2_u8(tbl, vget_high_u8(v)))\n#endif\n\nstatic\ninline\nvoid\nw4_single_table_multiply_region_neon(gf_t *gf, uint8_t *src, uint8_t *dst,\n                                     uint8_t * d_end, gf_val_32_t val, int xor)\n{\n  struct gf_single_table_data *std;\n  uint8_t *base;\n  uint8x16_t r, va, vh, vl, loset;\n\n#ifdef ARCH_AARCH64\n  uint8x16_t th, tl;\n#else\n  uint8x8x2_t th, tl;\n#endif\n\n  std = (struct gf_single_table_data *) ((gf_internal_t *) (gf->scratch))->private;\n  base = (uint8_t *) std->mult;\n  base += (val << GF_FIELD_WIDTH);\n\n#ifdef ARCH_AARCH64\n  tl = vld1q_u8 (base);\n  th = vshlq_n_u8 (tl, 4);\n#else\n  tl.val[0] = vld1_u8 (base);\n  tl.val[1] = vld1_u8 (base + 8);\n  th.val[0] =  vshl_n_u8 (tl.val[0], 4);\n  th.val[1] =  vshl_n_u8 (tl.val[1], 4);\n#endif\n\n  loset = vdupq_n_u8(0xf);\n\n  while (dst < d_end) {\n      va = vld1q_u8 (src);\n\n      vh = vshrq_n_u8 (va, 4);\n      vl = vandq_u8 (va, loset);\n\n      if (xor)\n        va = vld1q_u8 (dst);\n\n      vh = vqtbl1q_u8 (th, vh);\n      vl = vqtbl1q_u8 (tl, vl);\n\n      r = veorq_u8 (vh, vl);\n\n      if (xor)\n        r = veorq_u8 (va, r);\n\n      vst1q_u8 (dst, r);\n\n    dst += 16;\n    src += 16;\n  }\n}\n\nstatic\nvoid\ngf_w4_single_table_multiply_region_neon(gf_t *gf, void *src, void *dest,\n                                        gf_val_32_t val, int bytes, int xor)\n{\n  gf_region_data rd;\n  uint8_t *sptr, *dptr, *top;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n\n  sptr = rd.s_start;\n  dptr = rd.d_start;\n  top  = rd.d_top;\n\n  if (xor)\n      w4_single_table_multiply_region_neon(gf, sptr, dptr, top, val, 1);\n  else\n      w4_single_table_multiply_region_neon(gf, sptr, dptr, top, val, 0);\n\n  gf_do_final_region_alignment(&rd);\n\n}\n\n\nint gf_w4_neon_cfm_init(gf_t *gf)\n{\n  // single clm multiplication probably pointless\n  gf->multiply.w32 = gf_w4_neon_clm_multiply;\n  gf->multiply_region.w32 = gf_w4_neon_clm_multiply_region_from_single;\n\n  return 1;\n}\n\nvoid gf_w4_neon_single_table_init(gf_t *gf)\n{\n  gf->multiply_region.w32 = gf_w4_single_table_multiply_region_neon;\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/src/neon/gf_w64_neon.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * Copyright (c) 2014: Janne Grunau <j@jannau.net>\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *     notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n *\n * gf_w64_neon.c\n *\n * Neon routines for 64-bit Galois fields\n *\n */\n\n#include \"gf_int.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include \"gf_w64.h\"\n\n\n#ifndef ARCH_AARCH64\n#define vqtbl1q_u8(tbl, v) vcombine_u8(vtbl2_u8(tbl, vget_low_u8(v)),   \\\n                                       vtbl2_u8(tbl, vget_high_u8(v)))\n#endif\n\nstatic\ninline\nvoid\nneon_w64_split_4_lazy_altmap_multiply_region(gf_t *gf, uint64_t *src,\n                                             uint64_t *dst, uint64_t *d_end,\n                                             uint64_t val, int xor)\n{\n  unsigned i, j, k;\n  uint8_t btable[16];\n#ifdef ARCH_AARCH64\n  uint8x16_t tables[16][8];\n#else\n  uint8x8x2_t tables[16][8];\n#endif\n  uint8x16_t p[8], mask1, si;\n\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  struct gf_split_4_64_lazy_data *ld = (struct gf_split_4_64_lazy_data *) h->private;\n\n  for (i = 0; i < 16; i++) {\n    for (j = 0; j < 8; j++) {\n      for (k = 0; k < 16; k++) {\n        btable[k] = (uint8_t) ld->tables[i][k];\n        ld->tables[i][k] >>= 8;\n      }\n#ifdef ARCH_AARCH64\n      tables[i][j] = vld1q_u8(btable);\n#else\n      tables[i][j].val[0] = vld1_u8(btable);\n      tables[i][j].val[1] = vld1_u8(btable + 8);\n#endif\n    }\n  }\n\n  mask1 = vdupq_n_u8(0xf);\n\n  while (dst < d_end) {\n\n    if (xor) {\n      for (i = 0; i < 8; i++)\n        p[i] = vld1q_u8((uint8_t *) (dst + i * 2));\n    } else {\n      for (i = 0; i < 8; i++)\n        p[i] = vdupq_n_u8(0);\n    }\n\n    i = 0;\n    for (k = 0; k < 8; k++) {\n      uint8x16_t v0 = vld1q_u8((uint8_t *) src);\n      src += 2;\n\n      si = vandq_u8(v0, mask1);\n      for (j = 0; j < 8; j++) {\n        p[j] = veorq_u8(p[j], vqtbl1q_u8(tables[i][j], si));\n      }\n      i++;\n      si = vshrq_n_u8(v0, 4);\n      for (j = 0; j < 8; j++) {\n        p[j] = veorq_u8(p[j], vqtbl1q_u8(tables[i][j], si));\n      }\n      i++;\n\n    }\n    for (i = 0; i < 8; i++) {\n      vst1q_u8((uint8_t *) dst, p[i]);\n      dst += 2;\n    }\n  }\n}\n\nstatic\ninline\nvoid\nneon_w64_split_4_lazy_multiply_region(gf_t *gf, uint64_t *src, uint64_t *dst,\n                                      uint64_t *d_end, uint64_t val, int xor)\n{\n  unsigned i, j, k;\n  uint8_t btable[16];\n#ifdef ARCH_AARCH64\n  uint8x16_t tables[16][8];\n#else\n  uint8x8x2_t tables[16][8];\n#endif\n  uint8x16_t p[8], mask1, si;\n  uint64x2_t st[8];\n  uint32x4x2_t s32[4];\n  uint16x8x2_t s16[4];\n  uint8x16x2_t s8[4];\n\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n  struct gf_split_4_64_lazy_data *ld = (struct gf_split_4_64_lazy_data *) h->private;\n\n  for (i = 0; i < 16; i++) {\n    for (j = 0; j < 8; j++) {\n      for (k = 0; k < 16; k++) {\n        btable[k] = (uint8_t) ld->tables[i][k];\n        ld->tables[i][k] >>= 8;\n      }\n#ifdef ARCH_AARCH64\n      tables[i][j] = vld1q_u8(btable);\n#else\n      tables[i][j].val[0] = vld1_u8(btable);\n      tables[i][j].val[1] = vld1_u8(btable + 8);\n#endif\n    }\n  }\n\n  mask1 = vdupq_n_u8(0xf);\n\n  while (dst < d_end) {\n\n    for (k = 0; k < 8; k++) {\n      st[k]  = vld1q_u64(src);\n      src += 2;\n      p[k] = vdupq_n_u8(0);\n    }\n\n    s32[0] = vuzpq_u32(vreinterpretq_u32_u64(st[0]),\n                       vreinterpretq_u32_u64(st[1]));\n    s32[1] = vuzpq_u32(vreinterpretq_u32_u64(st[2]),\n                       vreinterpretq_u32_u64(st[3]));\n    s32[2] = vuzpq_u32(vreinterpretq_u32_u64(st[4]),\n                       vreinterpretq_u32_u64(st[5]));\n    s32[3] = vuzpq_u32(vreinterpretq_u32_u64(st[6]),\n                       vreinterpretq_u32_u64(st[7]));\n\n    s16[0] = vuzpq_u16(vreinterpretq_u16_u32(s32[0].val[0]),\n                       vreinterpretq_u16_u32(s32[1].val[0]));\n    s16[1] = vuzpq_u16(vreinterpretq_u16_u32(s32[2].val[0]),\n                       vreinterpretq_u16_u32(s32[3].val[0]));\n    s16[2] = vuzpq_u16(vreinterpretq_u16_u32(s32[0].val[1]),\n                       vreinterpretq_u16_u32(s32[1].val[1]));\n    s16[3] = vuzpq_u16(vreinterpretq_u16_u32(s32[2].val[1]),\n                       vreinterpretq_u16_u32(s32[3].val[1]));\n\n    s8[0]  = vuzpq_u8(vreinterpretq_u8_u16(s16[0].val[0]),\n                      vreinterpretq_u8_u16(s16[1].val[0]));\n    s8[1]  = vuzpq_u8(vreinterpretq_u8_u16(s16[0].val[1]),\n                      vreinterpretq_u8_u16(s16[1].val[1]));\n    s8[2]  = vuzpq_u8(vreinterpretq_u8_u16(s16[2].val[0]),\n                      vreinterpretq_u8_u16(s16[3].val[0]));\n    s8[3]  = vuzpq_u8(vreinterpretq_u8_u16(s16[2].val[1]),\n                      vreinterpretq_u8_u16(s16[3].val[1]));\n\n    i = 0;\n    for (k = 0; k < 8; k++) {\n      si = vandq_u8(s8[k >> 1].val[k & 1], mask1);\n      for (j = 0; j < 8; j++) {\n        p[j] = veorq_u8(p[j], vqtbl1q_u8(tables[i][j], si));\n      }\n      i++;\n      si = vshrq_n_u8(s8[k >> 1].val[k & 1], 4);\n      for (j = 0; j < 8; j++) {\n        p[j] = veorq_u8(p[j], vqtbl1q_u8(tables[i][j], si));\n      }\n      i++;\n    }\n\n    s8[0]  = vzipq_u8(p[0], p[1]);\n    s8[1]  = vzipq_u8(p[2], p[3]);\n    s8[2]  = vzipq_u8(p[4], p[5]);\n    s8[3]  = vzipq_u8(p[6], p[7]);\n\n    s16[0] = vzipq_u16(vreinterpretq_u16_u8(s8[0].val[0]),\n                       vreinterpretq_u16_u8(s8[1].val[0]));\n    s16[1] = vzipq_u16(vreinterpretq_u16_u8(s8[2].val[0]),\n                       vreinterpretq_u16_u8(s8[3].val[0]));\n    s16[2] = vzipq_u16(vreinterpretq_u16_u8(s8[0].val[1]),\n                       vreinterpretq_u16_u8(s8[1].val[1]));\n    s16[3] = vzipq_u16(vreinterpretq_u16_u8(s8[2].val[1]),\n                       vreinterpretq_u16_u8(s8[3].val[1]));\n\n    s32[0] = vzipq_u32(vreinterpretq_u32_u16(s16[0].val[0]),\n                       vreinterpretq_u32_u16(s16[1].val[0]));\n    s32[1] = vzipq_u32(vreinterpretq_u32_u16(s16[0].val[1]),\n                       vreinterpretq_u32_u16(s16[1].val[1]));\n    s32[2] = vzipq_u32(vreinterpretq_u32_u16(s16[2].val[0]),\n                       vreinterpretq_u32_u16(s16[3].val[0]));\n    s32[3] = vzipq_u32(vreinterpretq_u32_u16(s16[2].val[1]),\n                       vreinterpretq_u32_u16(s16[3].val[1]));\n\n    for (k = 0; k < 8; k ++) {\n        st[k] = vreinterpretq_u64_u32(s32[k >> 1].val[k & 1]);\n    }\n\n    if (xor) {\n      for (i = 0; i < 8; i++) {\n        uint64x2_t t1 = vld1q_u64(dst);\n        vst1q_u64(dst, veorq_u64(st[i], t1));\n        dst += 2;\n      }\n    } else {\n      for (i = 0; i < 8; i++) {\n        vst1q_u64(dst, st[i]);\n        dst += 2;\n      }\n    }\n\n  }\n}\n\nstatic\nvoid\ngf_w64_neon_split_4_lazy_multiply_region(gf_t *gf, void *src, void *dest,\n                                         uint64_t val, int bytes, int xor,\n                                         int altmap)\n{\n  gf_internal_t *h;\n  int i, j, k;\n  uint64_t pp, v, *s64, *d64, *top;\n  struct gf_split_4_64_lazy_data *ld;\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 128);\n  gf_do_initial_region_alignment(&rd);\n\n  s64 = (uint64_t *) rd.s_start;\n  d64 = (uint64_t *) rd.d_start;\n  top = (uint64_t *) rd.d_top;\n\n  h = (gf_internal_t *) gf->scratch;\n  pp = h->prim_poly;\n  ld = (struct gf_split_4_64_lazy_data *) h->private;\n\n  v = val;\n  for (i = 0; i < 16; i++) {\n    ld->tables[i][0] = 0;\n    for (j = 1; j < 16; j <<= 1) {\n      for (k = 0; k < j; k++) {\n        ld->tables[i][k^j] = (v ^ ld->tables[i][k]);\n      }\n      v = (v & GF_FIRST_BIT) ? ((v << 1) ^ pp) : (v << 1);\n    }\n  }\n\n  if (altmap) {\n    if (xor)\n      neon_w64_split_4_lazy_altmap_multiply_region(gf, s64, d64, top, val, 1);\n    else\n      neon_w64_split_4_lazy_altmap_multiply_region(gf, s64, d64, top, val, 0);\n  } else {\n    if (xor)\n      neon_w64_split_4_lazy_multiply_region(gf, s64, d64, top, val, 1);\n    else\n      neon_w64_split_4_lazy_multiply_region(gf, s64, d64, top, val, 0);\n  }\n\n  gf_do_final_region_alignment(&rd);\n}\n\nstatic\nvoid\ngf_w64_split_4_64_lazy_multiply_region_neon(gf_t *gf, void *src, void *dest,\n                                            uint64_t val, int bytes, int xor)\n{\n  gf_w64_neon_split_4_lazy_multiply_region(gf, src, dest, val, bytes, xor, 0);\n}\n\nstatic\nvoid\ngf_w64_split_4_64_lazy_altmap_multiply_region_neon(gf_t *gf, void *src,\n                                                   void *dest, uint64_t val,\n                                                   int bytes, int xor)\n{\n  gf_w64_neon_split_4_lazy_multiply_region(gf, src, dest, val, bytes, xor, 1);\n}\n\nvoid gf_w64_neon_split_init(gf_t *gf)\n{\n  gf_internal_t *h = (gf_internal_t *) gf->scratch;\n\n  if (h->region_type & GF_REGION_ALTMAP)\n      gf->multiply_region.w64 = gf_w64_split_4_64_lazy_altmap_multiply_region_neon;\n  else\n      gf->multiply_region.w64 = gf_w64_split_4_64_lazy_multiply_region_neon;\n\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/src/neon/gf_w8_neon.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * Copyright (c) 2014: Janne Grunau <j@jannau.net>\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *     notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n *\n * gf_w8_neon.c\n *\n * Neon optimized routines for 8-bit Galois fields\n *\n */\n\n#include \"gf_int.h\"\n#include \"gf_w8.h\"\n#include <stdio.h>\n#include <stdlib.h>\n\n/* ARM NEON reducing macro for the carry free multiplication\n *   vmull_p8 is the carryless multiply operation. Here vshrn_n_u16 shifts\n *   the result to the right by 1 byte. This allows us to multiply\n *   the prim_poly by the leading bits of the result. We then xor the result\n *   of that operation back with the result. */\n#define NEON_CFM_REDUCE(v, w, result, prim_poly, initial)               \\\n  do {\t\t\t\t\t\t\t\t        \\\n    if (initial)                                                        \\\n      v = vshrn_n_u16 (vreinterpretq_u16_p16(result), 8);               \\\n    else                                                                \\\n      v = veor_u8 (v, vshrn_n_u16 (vreinterpretq_u16_p16(result), 8));  \\\n    w = vmull_p8 (prim_poly, vreinterpret_p8_u8(v));                    \\\n    result = vreinterpretq_p16_u16 (veorq_u16 (vreinterpretq_u16_p16(result), vreinterpretq_u16_p16(w))); \\\n  } while (0)\n\nstatic\ninline\ngf_val_32_t\ngf_w8_neon_clm_multiply_x (gf_t *gf, gf_val_32_t a8, gf_val_32_t b8, int x)\n{\n  gf_val_32_t rv = 0;\n  poly8x8_t       a, b;\n  uint8x8_t       v;\n  poly16x8_t      result;\n  poly8x8_t       prim_poly;\n  poly16x8_t      w;\n  gf_internal_t * h = gf->scratch;\n\n  a =  vdup_n_p8 (a8);\n  b =  vdup_n_p8 (b8);\n\n  prim_poly = vdup_n_p8 ((uint32_t)(h->prim_poly & 0x1ffULL));\n\n  /* Do the initial multiply */\n  result = vmull_p8 (a, b);\n\n  /* Ben: Do prim_poly reduction twice. We are guaranteed that we will only\n     have to do the reduction at most twice, because (w-2)/z == 2. Where\n     z is equal to the number of zeros after the leading 1 */\n  NEON_CFM_REDUCE (v, w, result, prim_poly, 1);\n  NEON_CFM_REDUCE (v, w, result, prim_poly, 0);\n  if (x >= 3) {\n    NEON_CFM_REDUCE (v, w, result, prim_poly, 0);\n  }\n  if (x >= 4) {\n    NEON_CFM_REDUCE (v, w, result, prim_poly, 0);\n  }\n  /* Extracts 32 bit value from result. */\n  rv = (gf_val_32_t)vget_lane_u8 (vmovn_u16 (vreinterpretq_u16_p16 (result)), 0);\n\n  return rv;\n}\n\n#define CLM_MULTIPLY(x) \\\nstatic gf_val_32_t gf_w8_neon_clm_multiply_ ## x (gf_t *gf, gf_val_32_t a8, gf_val_32_t b8) \\\n{\\\n    return gf_w8_neon_clm_multiply_x (gf, a8, b8, x);\\\n}\n\nCLM_MULTIPLY(2)\nCLM_MULTIPLY(3)\nCLM_MULTIPLY(4)\n\nstatic inline void\nneon_clm_multiply_region_from_single_x(gf_t *gf, uint8_t *s8, uint8_t *d8,\n                                       gf_val_32_t val, uint8_t *d_end,\n                                       int xor, int x)\n{\n  gf_internal_t * h = gf->scratch;\n  poly8x8_t       a, b;\n  uint8x8_t       c, v;\n  poly16x8_t      result;\n  poly8x8_t       prim_poly;\n  poly16x8_t      w;\n\n  a         = vdup_n_p8 (val);\n  prim_poly = vdup_n_p8 ((uint8_t)(h->prim_poly & 0xffULL));\n\n  while (d8 < d_end) {\n    b = vld1_p8 ((poly8_t *) s8);\n\n    if (xor)\n        c = vld1_u8 (d8);\n\n    result = vmull_p8 (a, b);\n\n    NEON_CFM_REDUCE(v, w, result, prim_poly, 1);\n    NEON_CFM_REDUCE (v, w, result, prim_poly, 0);\n    if (x >= 3) {\n      NEON_CFM_REDUCE (v, w, result, prim_poly, 0);\n    }\n    if (x >= 4) {\n      NEON_CFM_REDUCE (v, w, result, prim_poly, 0);\n    }\n    v = vmovn_u16 (vreinterpretq_u16_p16 (result));\n    if (xor)\n      v = veor_u8 (c, v);\n\n    vst1_u8 (d8, v);\n\n    d8 += 8;\n    s8 += 8;\n  }\n}\n\n#define CLM_MULT_REGION(x)                                              \\\nstatic void                                                             \\\ngf_w8_neon_clm_multiply_region_from_single_ ## x (gf_t *gf, void *src,  \\\n                                                  void *dest,           \\\n                                                  gf_val_32_t val, int bytes, \\\n                                                  int xor)              \\\n{                                                                       \\\n  gf_region_data rd;                                                    \\\n  uint8_t *s8;                                                          \\\n  uint8_t *d8;                                                          \\\n                                                                        \\\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }           \\\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }       \\\n                                                                        \\\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);          \\\n  gf_do_initial_region_alignment(&rd);                                  \\\n  s8 = (uint8_t *) rd.s_start;                                          \\\n  d8 = (uint8_t *) rd.d_start;                                          \\\n                                                                        \\\n  if (xor)                                                              \\\n    neon_clm_multiply_region_from_single_x (gf, s8, d8, val, rd.d_top, 1, x); \\\n  else                                                                  \\\n    neon_clm_multiply_region_from_single_x (gf, s8, d8, val, rd.d_top, 0, x);\\\n  gf_do_final_region_alignment(&rd);                                    \\\n}\n\nCLM_MULT_REGION(2)\nCLM_MULT_REGION(3)\nCLM_MULT_REGION(4)\n\n\nint gf_w8_neon_cfm_init(gf_t *gf)\n{\n  gf_internal_t *h;\n\n  h = (gf_internal_t *) gf->scratch;\n\n  if ((0xe0 & h->prim_poly) == 0){\n    gf->multiply.w32 = gf_w8_neon_clm_multiply_2;\n    gf->multiply_region.w32 = gf_w8_neon_clm_multiply_region_from_single_2;\n  }else if ((0xc0 & h->prim_poly) == 0){\n    gf->multiply.w32 = gf_w8_neon_clm_multiply_3;\n    gf->multiply_region.w32 = gf_w8_neon_clm_multiply_region_from_single_3;\n  }else if ((0x80 & h->prim_poly) == 0){\n    gf->multiply.w32 = gf_w8_neon_clm_multiply_4;\n    gf->multiply_region.w32 = gf_w8_neon_clm_multiply_region_from_single_4;\n  }else{\n    return 0;\n  }\n  return 1;\n}\n\n#ifndef ARCH_AARCH64\n#define vqtbl1q_u8(tbl, v) vcombine_u8(vtbl2_u8(tbl, vget_low_u8(v)),   \\\n                                       vtbl2_u8(tbl, vget_high_u8(v)))\n#endif\n\nstatic\nvoid\ngf_w8_split_multiply_region_neon(gf_t *gf, void *src, void *dest, gf_val_32_t val, int bytes, int xor)\n{\n  uint8_t *bh, *bl, *sptr, *dptr;\n  uint8x16_t r, va, vh, vl, loset;\n#ifdef ARCH_AARCH64\n  uint8x16_t mth, mtl;\n#else\n  uint8x8x2_t mth, mtl;\n#endif\n  struct gf_w8_half_table_data *htd;\n  gf_region_data rd;\n\n  if (val == 0) { gf_multby_zero(dest, bytes, xor); return; }\n  if (val == 1) { gf_multby_one(src, dest, bytes, xor); return; }\n\n  htd = (struct gf_w8_half_table_data *) ((gf_internal_t *) (gf->scratch))->private;\n\n  gf_set_region_data(&rd, gf, src, dest, bytes, val, xor, 16);\n  gf_do_initial_region_alignment(&rd);\n\n  bh = (uint8_t *) htd->high;\n  bh += (val << 4);\n  bl = (uint8_t *) htd->low;\n  bl += (val << 4);\n\n  sptr = rd.s_start;\n  dptr = rd.d_start;\n\n#ifdef ARCH_AARCH64\n  mth = vld1q_u8 (bh);\n  mtl = vld1q_u8 (bl);\n#else\n  mth.val[0] = vld1_u8 (bh);\n  mtl.val[0] = vld1_u8 (bl);\n  mth.val[1] = vld1_u8 (bh + 8);\n  mtl.val[1] = vld1_u8 (bl + 8);\n#endif\n\n  loset = vdupq_n_u8(0xf);\n\n  if (xor) {\n    while (sptr < (uint8_t *) rd.s_top) {\n      va = vld1q_u8 (sptr);\n\n      vh = vshrq_n_u8 (va, 4);\n      vl = vandq_u8 (va, loset);\n      va = vld1q_u8 (dptr);\n\n      vh = vqtbl1q_u8 (mth, vh);\n      vl = vqtbl1q_u8 (mtl, vl);\n\n      r = veorq_u8 (vh, vl);\n\n      vst1q_u8 (dptr, veorq_u8 (va, r));\n\n      dptr += 16;\n      sptr += 16;\n    }\n  } else {\n    while (sptr < (uint8_t *) rd.s_top) {\n      va = vld1q_u8 (sptr);\n\n      vh = vshrq_n_u8 (va, 4);\n      vl = vandq_u8 (va, loset);\n#ifdef ARCH_AARCH64\n      vh = vqtbl1q_u8 (mth, vh);\n      vl = vqtbl1q_u8 (mtl, vl);\n#else\n      vh = vcombine_u8 (vtbl2_u8 (mth, vget_low_u8 (vh)),\n\t\t\tvtbl2_u8 (mth, vget_high_u8 (vh)));\n      vl = vcombine_u8 (vtbl2_u8 (mtl, vget_low_u8 (vl)),\n\t\t\tvtbl2_u8 (mtl, vget_high_u8 (vl)));\n#endif\n\n      r = veorq_u8 (vh, vl);\n\n      vst1q_u8(dptr, r);\n\n      dptr += 16;\n      sptr += 16;\n    }\n  }\n\n  gf_do_final_region_alignment(&rd);\n}\n\n\nvoid gf_w8_neon_split_init(gf_t *gf)\n{\n  gf->multiply_region.w32 = gf_w8_split_multiply_region_neon;\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/test/Makefile.am",
    "content": "# GF-Complete 'test' AM file\n\nAM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include\nAM_CFLAGS = -O3 $(SIMD_FLAGS) -fPIC\n\nbin_PROGRAMS = gf_unit \n\ngf_unit_SOURCES = gf_unit.c\n#gf_unit_LDFLAGS = -lgf_complete\ngf_unit_LDADD = ../src/libgf_complete.la\n\n"
  },
  {
    "path": "fst/layout/gf-complete/test/gf_unit.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_unit.c\n *\n * Performs unit testing for gf arithmetic\n */\n\n#include \"config.h\"\n\n#ifdef HAVE_POSIX_MEMALIGN\n#ifndef _XOPEN_SOURCE\n#define _XOPEN_SOURCE 600\n#endif\n#endif\n\n#include <stdio.h>\n#include <getopt.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n#include <signal.h>\n\n#include \"gf_complete.h\"\n#include \"gf_int.h\"\n#include \"gf_method.h\"\n#include \"gf_rand.h\"\n#include \"gf_general.h\"\n\n#define REGION_SIZE (16384)\n#define RMASK (0x00000000ffffffffLL)\n#define LMASK (0xffffffff00000000LL)\n\nvoid problem(char *s)\n{\n  fprintf(stderr, \"Unit test failed.\\n\");\n  fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\nchar *BM = \"Bad Method: \";\n\nvoid usage(char *s)\n{\n  fprintf(stderr, \"usage: gf_unit w tests seed [method] - does unit testing in GF(2^w)\\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"Legal w are: 1 - 32, 64 and 128\\n\");\n  fprintf(stderr, \"           128 is hex only (i.e. '128' will be an error - do '128h')\\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"Tests may be any combination of:\\n\");\n  fprintf(stderr, \"       A: All\\n\");\n  fprintf(stderr, \"       S: Single operations (multiplication/division)\\n\");\n  fprintf(stderr, \"       R: Region operations\\n\");\n  fprintf(stderr, \"       V: Verbose Output\\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"Use -1 for time(0) as a seed.\\n\");\n  fprintf(stderr, \"\\n\");\n  if (s == BM) {\n    fprintf(stderr, \"%s\", BM);\n    gf_error();\n  } else if (s != NULL) {\n    fprintf(stderr, \"%s\\n\", s);\n  }\n  exit(1);\n}\n\nvoid SigHandler(int v)\n{\n  fprintf(stderr, \"Problem: SegFault!\\n\");\n  fflush(stdout);\n  exit(2);\n}\n\nint main(int argc, char **argv)\n{\n  signal(SIGSEGV, SigHandler);\n\n  int w, i, verbose, single, region, top;\n  int s_start, d_start, bytes, xor, alignment_test;\n  gf_t   gf, gf_def;\n  time_t t0;\n  gf_internal_t *h;\n  gf_general_t *a, *b, *c, *d;\n  uint8_t a8, b8, c8, *mult4 = NULL, *mult8 = NULL;\n  uint16_t a16, b16, c16, *log16 = NULL, *alog16 = NULL;\n  char as[50], bs[50], cs[50], ds[50];\n  uint32_t mask = 0;\n  char *ra, *rb, *rc, *rd, *target;\n  int align;\n#ifndef HAVE_POSIX_MEMALIGN\n  char *malloc_ra, *malloc_rb, *malloc_rc, *malloc_rd;\n#endif\n\n\n  if (argc < 4) usage(NULL);\n\n  if (sscanf(argv[1], \"%d\", &w) == 0){\n    usage(\"Bad w\\n\");\n  }\n\n  if (sscanf(argv[3], \"%ld\", &t0) == 0) usage(\"Bad seed\\n\");\n  if (t0 == -1) t0 = time(0);\n  MOA_Seed(t0);\n\n  if (w > 32 && w != 64 && w != 128) usage(\"Bad w\");\n\n  if (create_gf_from_argv(&gf, w, argc, argv, 4) == 0) {\n    usage(BM);\n  }\n\n  printf(\"Args: \");\n  for (i = 1; i < argc; i++) {\n    printf (\"%s \", argv[i]);\n  }\n  printf(\"/ size (bytes): %d\\n\", gf_size(&gf));\n\n  for (i = 0; i < strlen(argv[2]); i++) {\n    if (strchr(\"ASRV\", argv[2][i]) == NULL) usage(\"Bad test\\n\");\n  }\n\n  h = (gf_internal_t *) gf.scratch;\n  a = (gf_general_t *) malloc(sizeof(gf_general_t));\n  b = (gf_general_t *) malloc(sizeof(gf_general_t));\n  c = (gf_general_t *) malloc(sizeof(gf_general_t));\n  d = (gf_general_t *) malloc(sizeof(gf_general_t));\n\n#if HAVE_POSIX_MEMALIGN\n  if (posix_memalign((void **) &ra, 16, sizeof(char)*REGION_SIZE))\n    ra = NULL;\n  if (posix_memalign((void **) &rb, 16, sizeof(char)*REGION_SIZE))\n    rb = NULL;\n  if (posix_memalign((void **) &rc, 16, sizeof(char)*REGION_SIZE))\n    rc = NULL;\n  if (posix_memalign((void **) &rd, 16, sizeof(char)*REGION_SIZE))\n    rd = NULL;\n#else\n  //15 bytes extra to make sure it's 16byte aligned\n  malloc_ra = (char *) malloc(sizeof(char)*REGION_SIZE+15);\n  malloc_rb = (char *) malloc(sizeof(char)*REGION_SIZE+15);\n  malloc_rc = (char *) malloc(sizeof(char)*REGION_SIZE+15);\n  malloc_rd = (char *) malloc(sizeof(char)*REGION_SIZE+15);\n  ra = (uint8_t *) (((uintptr_t) malloc_ra + 15) & ~((uintptr_t) 0xf));\n  rb = (uint8_t *) (((uintptr_t) malloc_rb + 15) & ~((uintptr_t) 0xf));\n  rc = (uint8_t *) (((uintptr_t) malloc_rc + 15) & ~((uintptr_t) 0xf));\n  rd = (uint8_t *) (((uintptr_t) malloc_rd + 15) & ~((uintptr_t) 0xf));\n#endif\n\n  if (w <= 32) {\n    mask = 0;\n    for (i = 0; i < w; i++) mask |= (1 << i);\n  }\n\n  verbose = (strchr(argv[2], 'V') != NULL);\n  single = (strchr(argv[2], 'S') != NULL || strchr(argv[2], 'A') != NULL);\n  region = (strchr(argv[2], 'R') != NULL || strchr(argv[2], 'A') != NULL);\n\n  if (!gf_init_hard(&gf_def, w, GF_MULT_DEFAULT, GF_REGION_DEFAULT, GF_DIVIDE_DEFAULT,\n      (h->mult_type != GF_MULT_COMPOSITE) ? h->prim_poly : 0, 0, 0, NULL, NULL))\n    problem(\"No default for this value of w\");\n\n  if (w == 4) {\n    mult4 = gf_w4_get_mult_table(&gf);\n  } else if (w == 8) {\n    mult8 = gf_w8_get_mult_table(&gf);\n  } else if (w == 16) {\n    log16 = gf_w16_get_log_table(&gf);\n    alog16 = gf_w16_get_mult_alog_table(&gf);\n  }\n\n  if (verbose) printf(\"Seed: %ld\\n\", t0);\n\n  if (single) {\n    \n    if (gf.multiply.w32 == NULL) problem(\"No multiplication operation defined.\");\n    if (verbose) { printf(\"Testing single multiplications/divisions.\\n\"); fflush(stdout); }\n    if (w <= 10) {\n      top = (1 << w)*(1 << w);\n    } else {\n      top = 1024*1024;\n    }\n    for (i = 0; i < top; i++) {\n      if (w <= 10) {\n        a->w32 = i % (1 << w);\n        b->w32 = (i >> w);\n\n      //Allen: the following conditions were being run 10 times each. That didn't seem like nearly enough to\n      //me for these special cases, so I converted to doing this mod stuff to easily make the number of times\n      //run both larger and proportional to the total size of the run.\n      } else {\n        switch (i % 32)\n        {\n          case 0: \n            gf_general_set_zero(a, w);\n            gf_general_set_random(b, w, 1);\n            break;\n          case 1:\n            gf_general_set_random(a, w, 1);\n            gf_general_set_zero(b, w);\n            break;\n          case 2:\n            gf_general_set_one(a, w);\n            gf_general_set_random(b, w, 1);\n            break;\n          case 3:\n            gf_general_set_random(a, w, 1);\n            gf_general_set_one(b, w);\n            break;\n          default:\n            gf_general_set_random(a, w, 1);\n            gf_general_set_random(b, w, 1);\n        }\n      }\n\n      //Allen: the following special cases for w=64 are based on the code below for w=128.\n      //These w=64 cases are based on Dr. Plank's suggestion because some of the methods for w=64\n      //involve splitting it in two. I think they're less likely to give errors than the 128-bit case\n      //though, because the 128 bit case is always split in two.\n      //As with w=128, I'm arbitrarily deciding to do this sort of thing with a quarter of the cases\n      if (w == 64) {\n        switch (i % 32)\n        {\n          case 0: if (!gf_general_is_one(a, w)) a->w64 &= RMASK; break;\n          case 1: if (!gf_general_is_one(a, w)) a->w64 &= LMASK; break;\n          case 2: if (!gf_general_is_one(a, w)) a->w64 &= RMASK; if (!gf_general_is_one(b, w)) b->w64 &= RMASK; break;\n          case 3: if (!gf_general_is_one(a, w)) a->w64 &= RMASK; if (!gf_general_is_one(b, w)) b->w64 &= LMASK; break;\n          case 4: if (!gf_general_is_one(a, w)) a->w64 &= LMASK; if (!gf_general_is_one(b, w)) b->w64 &= RMASK; break;\n          case 5: if (!gf_general_is_one(a, w)) a->w64 &= LMASK; if (!gf_general_is_one(b, w)) b->w64 &= LMASK; break;\n          case 6: if (!gf_general_is_one(b, w)) b->w64 &= RMASK; break;\n          case 7: if (!gf_general_is_one(b, w)) b->w64 &= LMASK; break;\n        }\n      }\n\n      //Allen: for w=128, we have important special cases where one half or the other of the number is all\n      //zeros. The probability of hitting such a number randomly is 1^-64, so if we don't force these cases\n      //we'll probably never hit them. This could be implemented more efficiently by changing the set-random\n      //function for w=128, but I think this is easier to follow.\n      //I'm arbitrarily deciding to do this sort of thing with a quarter of the cases\n      if (w == 128) {\n        switch (i % 32)\n        {\n          case 0: if (!gf_general_is_one(a, w)) a->w128[0] = 0; break;\n          case 1: if (!gf_general_is_one(a, w)) a->w128[1] = 0; break;\n          case 2: if (!gf_general_is_one(a, w)) a->w128[0] = 0; if (!gf_general_is_one(b, w)) b->w128[0] = 0; break;\n          case 3: if (!gf_general_is_one(a, w)) a->w128[0] = 0; if (!gf_general_is_one(b, w)) b->w128[1] = 0; break;\n          case 4: if (!gf_general_is_one(a, w)) a->w128[1] = 0; if (!gf_general_is_one(b, w)) b->w128[0] = 0; break;\n          case 5: if (!gf_general_is_one(a, w)) a->w128[1] = 0; if (!gf_general_is_one(b, w)) b->w128[1] = 0; break;\n          case 6: if (!gf_general_is_one(b, w)) b->w128[0] = 0; break;\n          case 7: if (!gf_general_is_one(b, w)) b->w128[1] = 0; break;\n        }\n      }\n\n      gf_general_multiply(&gf, a, b, c);\n      \n      /* If w is 4, 8 or 16, then there are inline multiplication/division methods.  \n         Test them here. */\n\n      if (w == 4 && mult4 != NULL) {\n        a8 = a->w32;\n        b8 = b->w32;\n        c8 = GF_W4_INLINE_MULTDIV(mult4, a8, b8);\n        if (c8 != c->w32) {\n          printf(\"Error in inline multiplication. %d * %d.  Inline = %d.  Default = %d.\\n\",\n             a8, b8, c8, c->w32);\n          exit(1);\n        }\n      }\n\n      if (w == 8 && mult8 != NULL) {\n        a8 = a->w32;\n        b8 = b->w32;\n        c8 = GF_W8_INLINE_MULTDIV(mult8, a8, b8);\n        if (c8 != c->w32) {\n          printf(\"Error in inline multiplication. %d * %d.  Inline = %d.  Default = %d.\\n\",\n             a8, b8, c8, c->w32);\n          exit(1);\n        }\n      }\n\n      if (w == 16 && log16 != NULL) {\n        a16 = a->w32;\n        b16 = b->w32;\n        c16 = GF_W16_INLINE_MULT(log16, alog16, a16, b16);\n        if (c16 != c->w32) {\n          printf(\"Error in inline multiplication. %d * %d.  Inline = %d.  Default = %d.\\n\",\n             a16, b16, c16, c->w32);\n          printf(\"%d %d\\n\", log16[a16], log16[b16]);\n          top = log16[a16] + log16[b16];\n          printf(\"%d %d\\n\", top, alog16[top]);\n          exit(1);\n        }\n      }\n\n      /* If this is not composite, then first test against the default: */\n\n      if (h->mult_type != GF_MULT_COMPOSITE) {\n        gf_general_multiply(&gf_def, a, b, d);\n\n        if (!gf_general_are_equal(c, d, w)) {\n          gf_general_val_to_s(a, w, as, 1);\n          gf_general_val_to_s(b, w, bs, 1);\n          gf_general_val_to_s(c, w, cs, 1);\n          gf_general_val_to_s(d, w, ds, 1);\n          printf(\"Error in single multiplication (all numbers in hex):\\n\\n\");\n          printf(\"  gf.multiply(gf, %s, %s) = %s\\n\", as, bs, cs);\n          printf(\"  The default gf multiplier returned %s\\n\", ds);\n          exit(1);\n        }\n      }\n\n      /* Now, we also need to double-check by other means, in case the default is wanky, \n         and when we're performing composite operations. Start with 0 and 1, where we know\n         what the result should be. */\n\n      if (gf_general_is_zero(a, w) || gf_general_is_zero(b, w) || \n          gf_general_is_one(a, w)  || gf_general_is_one(b, w)) {\n        if (((gf_general_is_zero(a, w) || gf_general_is_zero(b, w)) && !gf_general_is_zero(c, w)) ||\n            (gf_general_is_one(a, w) && !gf_general_are_equal(b, c, w)) ||\n            (gf_general_is_one(b, w) && !gf_general_are_equal(a, c, w))) {\n          gf_general_val_to_s(a, w, as, 1);\n          gf_general_val_to_s(b, w, bs, 1);\n          gf_general_val_to_s(c, w, cs, 1);\n          printf(\"Error in single multiplication (all numbers in hex):\\n\\n\");\n          printf(\"  gf.multiply(gf, %s, %s) = %s, which is clearly wrong.\\n\", as, bs, cs);\n          exit(1);\n        }\n      }\n\n      /* Dumb check to make sure that it's not returning numbers that are too big: */\n\n      if (w < 32 && (c->w32 & mask) != c->w32) {\n        gf_general_val_to_s(a, w, as, 1);\n        gf_general_val_to_s(b, w, bs, 1);\n        gf_general_val_to_s(c, w, cs, 1);\n        printf(\"Error in single multiplication (all numbers in hex):\\n\\n\");\n        printf(\"  gf.multiply.w32(gf, %s, %s) = %s, which is too big.\\n\", as, bs, cs);\n        exit(1);\n      }\n\n      /* Finally, let's check to see that multiplication and division work together */\n\n      if (!gf_general_is_zero(a, w)) {\n        gf_general_divide(&gf, c, a, d);\n        if (!gf_general_are_equal(b, d, w)) {\n          gf_general_val_to_s(a, w, as, 1);\n          gf_general_val_to_s(b, w, bs, 1);\n          gf_general_val_to_s(c, w, cs, 1);\n          gf_general_val_to_s(d, w, ds, 1);\n          printf(\"Error in single multiplication/division (all numbers in hex):\\n\\n\");\n          printf(\"  gf.multiply(gf, %s, %s) = %s, but gf.divide(gf, %s, %s) = %s\\n\", as, bs, cs, cs, as, ds);\n          exit(1);\n        }\n      }\n\n    }\n  }\n\n  if (region) {\n    if (verbose) { printf(\"Testing region multiplications\\n\"); fflush(stdout); }\n    for (i = 0; i < 1024; i++) {\n      //Allen: changing to a switch thing as with the single ops to make things proportional\n      switch (i % 32)\n      {\n        case 0:\n          gf_general_set_zero(a, w);\n          break;\n        case 1:\n          gf_general_set_one(a, w);\n          break;\n        case 2:\n          gf_general_set_two(a, w);\n          break;\n        default:\n          gf_general_set_random(a, w, 1);\n      }\n      MOA_Fill_Random_Region(ra, REGION_SIZE);\n      MOA_Fill_Random_Region(rb, REGION_SIZE);\n      xor = (i/32)%2;\n      align = w/8;\n      if (align == 0) align = 1;\n      if (align > 16) align = 16;\n\n      /* JSP - Cauchy test.  When w < 32 & it doesn't equal 4, 8 or 16, the default is\n         equal to GF_REGION_CAUCHY, even if GF_REGION_CAUCHY is not set. We are testing\n         three alignments here:\n\n         1. Anything goes -- no alignment guaranteed.\n         2. Perfect alignment.  Here src and dest must be aligned wrt each other,\n            and bytes must be a multiple of 16*w.  \n         3. Imperfect alignment.  Here we'll have src and dest be aligned wrt each \n            other, but bytes is simply a multiple of w.  That means some XOR's will\n            be aligned, and some won't.\n       */\n\n      if ((h->region_type & GF_REGION_CAUCHY) || (w < 32 && w != 4 && w != 8 && w != 16)) {\n        alignment_test = (i%3);\n        \n        s_start = MOA_Random_W(5, 1);\n        if (alignment_test == 0) {\n          d_start = MOA_Random_W(5, 1);\n        } else {\n          d_start = s_start;\n        }\n\n        bytes = (d_start > s_start) ? REGION_SIZE - d_start : REGION_SIZE - s_start;\n        bytes -= MOA_Random_W(5, 1);\n        if (alignment_test == 1) {\n          bytes -= (bytes % (w*16));\n        } else {\n          bytes -= (bytes % w);\n        }\n\n        target = rb;\n \n      /* JSP - Otherwise, we're testing a non-cauchy test, and alignment\n        must be more strict.  We have to make sure that the regions are\n        aligned wrt each other on 16-byte pointers.  */\n\n      } else {\n        s_start = MOA_Random_W(5, 1) * align;\n        d_start = s_start;\n        bytes = REGION_SIZE - s_start - MOA_Random_W(5, 1);\n        bytes -= (bytes % align);\n\n        if (h->mult_type == GF_MULT_COMPOSITE && (h->region_type & GF_REGION_ALTMAP)) {\n          target = rb ;\n        } else {\n          target = (i/64)%2 ? rb : ra;\n        }\n      }\n\n      memcpy(rc, ra, REGION_SIZE);\n      memcpy(rd, target, REGION_SIZE);\n      gf_general_do_region_multiply(&gf, a, ra+s_start, target+d_start, bytes, xor);\n      gf_general_do_region_check(&gf, a, rc+s_start, rd+d_start, target+d_start, bytes, xor);\n    }\n  }\n\n  free(a);\n  free(b);\n  free(c);\n  free(d);\n#ifdef HAVE_POSIX_MEMALIGN\n  free(ra);\n  free(rb);\n  free(rc);\n  free(rd);\n#else\n  free(malloc_ra);\n  free(malloc_rb);\n  free(malloc_rc);\n  free(malloc_rd);\n#endif\n  \n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/tools/Makefile.am",
    "content": "# GF-Complete 'tools' AM file\n\nAM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include\nAM_CFLAGS = -O3 $(SIMD_FLAGS) -fPIC\n\nbin_PROGRAMS = gf_mult gf_div gf_add gf_time gf_methods gf_poly gf_inline_time\n\ngf_mult_SOURCES = gf_mult.c\n#gf_mult_LDFLAGS = -lgf_complete\ngf_mult_LDADD = ../src/libgf_complete.la\n\ngf_div_SOURCES = gf_div.c\n#gf_div_LDFLAGS = -lgf_complete\ngf_div_LDADD = ../src/libgf_complete.la\n\ngf_add_SOURCES = gf_add.c\n#gf_add_LDFLAGS = -lgf_complete\ngf_add_LDADD = ../src/libgf_complete.la\n\ngf_time_SOURCES = gf_time.c\n#gf_time_LDFLAGS = -lgf_complete\ngf_time_LDADD = ../src/libgf_complete.la\n\ngf_methods_SOURCES = gf_methods.c\n#gf_methods_LDFLAGS = -lgf_complete\ngf_methods_LDADD = ../src/libgf_complete.la\n\ngf_poly_SOURCES = gf_poly.c\n#gf_poly_LDFLAGS = -lgf_complete\ngf_poly_LDADD = ../src/libgf_complete.la\n\ngf_inline_time_SOURCES = gf_inline_time.c\n#gf_inline_time_LDFLAGS = -lgf_complete\ngf_inline_time_LDADD = ../src/libgf_complete.la\n\n# gf_unit tests as generated by gf_methods\ngf_unit_w%.sh: gf_methods\n\t./$^ $(@:gf_unit_w%.sh=%) -A -U > $@ || rm $@\n\nTESTS = gf_unit_w128.sh \\\n        gf_unit_w64.sh  \\\n        gf_unit_w32.sh  \\\n        gf_unit_w16.sh  \\\n        gf_unit_w8.sh   \\\n        gf_unit_w4.sh\n\nTEST_EXTENSIONS = .sh\nSH_LOG_COMPILER = $(SHELL)\nAM_SH_LOG_FLAGS = -e\n\nCLEANFILES = $(TESTS)\n"
  },
  {
    "path": "fst/layout/gf-complete/tools/gf_add.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_add.c\n *\n * Adds two numbers in gf_2^w\n */\n\n#include <stdio.h>\n#include <getopt.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n\nvoid usage(char *s)\n{\n  fprintf(stderr, \"usage: gf_add a b w - does addition of a and b in GF(2^w)\\n\");\n  fprintf(stderr, \"       If w has an h on the end, treat a, b and the sum as hexadecimal (no 0x required)\\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"       legal w are: 1-32, 64 and 128\\n\");\n  fprintf(stderr, \"       128 is hex only (i.e. '128' will be an error - do '128h')\\n\");\n\n  if (s != NULL) fprintf(stderr, \"%s\", s);\n  exit(1);\n}\n\nint read_128(char *s, uint64_t *v)\n{\n  int l, t;\n  char save;\n\n  l = strlen(s);\n  if (l > 32) return 0;\n\n  if (l > 16) {\n    if (sscanf(s + (l-16), \"%llx\", (long long unsigned int *) &(v[1])) == 0) return 0;\n    save = s[l-16];\n    s[l-16] = '\\0';\n    t = sscanf(s, \"%llx\", (long long unsigned int *) &(v[0]));\n    s[l-16] = save;\n    return t;\n  } else {\n    v[0] = 0;\n    return sscanf(s, \"%llx\", (long long unsigned int *)&(v[1]));\n  }\n  return 1;\n}\n\nvoid print_128(uint64_t *v) \n{\n  if (v[0] > 0) {\n    printf(\"%llx\", (long long unsigned int) v[0]);\n    printf(\"%016llx\", (long long unsigned int) v[1]);\n  } else {\n    printf(\"%llx\", (long long unsigned int) v[1]);\n  }\n  printf(\"\\n\");\n}\n\n\nint main(int argc, char **argv)\n{\n  int hex, w;\n  uint32_t a, b, c, top;\n  uint64_t a64, b64, c64;\n  uint64_t a128[2], b128[2], c128[2];\n  char *format;\n\n  if (argc != 4) usage(NULL);\n  if (sscanf(argv[3], \"%d\", &w) == 0) usage(\"Bad w\\n\");\n\n  if (w <= 0 || (w > 32 && w != 64 && w != 128)) usage(\"Bad w\");\n\n  hex = (strchr(argv[3], 'h') != NULL);\n\n  if (!hex && w == 128) usage(NULL);\n \n  if (w <= 32) {\n    format = (hex) ? \"%x\" : \"%u\";\n    if (sscanf(argv[1], format, &a) == 0) usage(\"Bad a\\n\");\n    if (sscanf(argv[2], format, &b) == 0) usage(\"Bad b\\n\");\n\n    if (w < 32) {\n      top = (w == 31) ? 0x80000000 : (1 << w);\n      if (w != 32 && a >= top) usage(\"a is too large\\n\");\n      if (w != 32 && b >= top) usage(\"b is too large\\n\");\n    }\n  \n    c = a ^ b;\n    printf(format, c);\n    printf(\"\\n\");\n\n  } else if (w == 64) {\n    format = (hex) ? \"%llx\" : \"%llu\";\n    if (sscanf(argv[1], format, &a64) == 0) usage(\"Bad a\\n\");\n    if (sscanf(argv[2], format, &b64) == 0) usage(\"Bad b\\n\");\n    c64 = a64 ^ b64;\n\n    printf(format, c64);\n    printf(\"\\n\");\n\n  } else if (w == 128) {\n\n    if (read_128(argv[1], a128) == 0) usage(\"Bad a\\n\");\n    if (read_128(argv[2], b128) == 0) usage(\"Bad b\\n\");\n    c128[0] = a128[0] ^ b128[0];\n    c128[1] = a128[1] ^ b128[1];\n\n    print_128(c128);\n  }\n  exit(0);\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/tools/gf_div.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_div.c\n *\n * Multiplies two numbers in gf_2^w\n */\n\n#include <stdio.h>\n#include <getopt.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n\n#include \"gf_complete.h\"\n#include \"gf_method.h\"\n#include \"gf_general.h\"\n\nvoid usage(int why)\n{\n  fprintf(stderr, \"usage: gf_div a b w [method] - does division of a and b in GF(2^w)\\n\");\n  if (why == 'W') {\n    fprintf(stderr, \"Bad w.\\n\");\n    fprintf(stderr, \"Legal w are: 1 - 32, 64 and 128.\\n\");\n    fprintf(stderr, \"Append 'h' to w to treat a, b and the quotient as hexadecimal.\\n\");\n    fprintf(stderr, \"w=128 is hex only (i.e. '128' will be an error - do '128h')\\n\");\n  }\n  if (why == 'A') fprintf(stderr, \"Bad a\\n\");\n  if (why == 'B') fprintf(stderr, \"Bad b\\n\");\n  if (why == 'M') {\n    fprintf(stderr, \"Bad Method Specification: \");\n    gf_error();\n  }\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  int hex, w;\n  gf_t gf;\n  gf_general_t a, b, c;\n  char output[50];\n\n  if (argc < 4) usage(' ');\n\n  if (sscanf(argv[3], \"%d\", &w) == 0) usage('W');\n  if (w <= 0 || (w > 32 && w != 64 && w != 128)) usage('W');\n\n  hex = (strchr(argv[3], 'h') != NULL);\n  if (!hex && w == 128) usage('W');\n\n  if (argc == 4) {\n    if (gf_init_easy(&gf, w) == 0) usage('M');\n  } else {\n    if (create_gf_from_argv(&gf, w, argc, argv, 4) == 0) usage('M');\n  }\n \n  if (!gf_general_s_to_val(&a, w, argv[1], hex)) usage('A');\n  if (!gf_general_s_to_val(&b, w, argv[2], hex)) usage('B');\n\n  gf_general_divide(&gf, &a, &b, &c);\n  gf_general_val_to_s(&c, w, output, hex);\n  \n  printf(\"%s\\n\", output);\n  exit(0);\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/tools/gf_inline_time.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_inline_time.c\n *\n * Times inline single multiplication when w = 4, 8 or 16\n */\n\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n#include <sys/time.h>\n\n#include \"gf_complete.h\"\n#include \"gf_rand.h\"\n\nvoid\ntimer_start (double *t)\n{\n    struct timeval  tv;\n\n    gettimeofday (&tv, NULL);\n    *t = (double)tv.tv_sec + (double)tv.tv_usec * 1e-6;\n}\n\ndouble\ntimer_split (const double *t)\n{\n    struct timeval  tv;\n    double  cur_t;\n\n    gettimeofday (&tv, NULL);\n    cur_t = (double)tv.tv_sec + (double)tv.tv_usec * 1e-6;\n    return (cur_t - *t);\n}\n\nvoid problem(char *s)\n{\n  fprintf(stderr, \"Timing test failed.\\n\");\n  fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\nvoid usage(char *s)\n{\n  fprintf(stderr, \"usage: gf_inline_time w seed #elts iterations - does timing of single multiplies\\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"Legal w are: 4, 8 or 16\\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"Use -1 for time(0) as a seed.\\n\");\n  fprintf(stderr, \"\\n\");\n  if (s != NULL) fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  int w, j, i, size, iterations;\n  gf_t      gf;\n  double timer, elapsed, dnum, num;\n  uint8_t *ra = NULL, *rb = NULL, *mult4, *mult8;\n  uint16_t *ra16 = NULL, *rb16 = NULL, *log16, *alog16;\n  time_t t0;\n  \n  if (argc != 5) usage(NULL);\n  if (sscanf(argv[1], \"%d\", &w) == 0) usage(\"Bad w\\n\");\n  if (w != 4 && w != 8 && w != 16) usage(\"Bad w\\n\");\n  if (sscanf(argv[2], \"%ld\", &t0) == 0) usage(\"Bad seed\\n\");\n  if (sscanf(argv[3], \"%d\", &size) == 0) usage(\"Bad #elts\\n\");\n  if (sscanf(argv[4], \"%d\", &iterations) == 0) usage(\"Bad iterations\\n\");\n  if (t0 == -1) t0 = time(0);\n  MOA_Seed(t0);\n\n  num = size;\n\n  gf_init_easy(&gf, w);\n  \n  printf(\"Seed: %ld\\n\", t0);\n\n  if (w == 4 || w == 8) {\n    ra = (uint8_t *) malloc(size);\n    rb = (uint8_t *) malloc(size);\n\n    if (ra == NULL || rb == NULL) { perror(\"malloc\"); exit(1); }\n  } else if (w == 16) {\n    ra16 = (uint16_t *) malloc(size*2);\n    rb16 = (uint16_t *) malloc(size*2);\n\n    if (ra16 == NULL || rb16 == NULL) { perror(\"malloc\"); exit(1); }\n  }\n\n  if (w == 4) {\n    mult4 = gf_w4_get_mult_table(&gf);\n    if (mult4 == NULL) {\n      printf(\"Couldn't get inline multiplication table.\\n\");\n      exit(1);\n    }\n    elapsed = 0;\n    dnum = 0;\n    for (i = 0; i < iterations; i++) {\n      for (j = 0; j < size; j++) {\n        ra[j] = MOA_Random_W(w, 1);\n        rb[j] = MOA_Random_W(w, 1);\n      }\n      timer_start(&timer);\n      for (j = 0; j < size; j++) {\n        ra[j] = GF_W4_INLINE_MULTDIV(mult4, ra[j], rb[j]);\n      }\n      dnum += num;\n      elapsed += timer_split(&timer);\n    }\n    printf(\"Inline mult:   %10.6lf s   Mops: %10.3lf    %10.3lf Mega-ops/s\\n\",\n           elapsed, dnum/1024.0/1024.0, dnum/1024.0/1024.0/elapsed);\n\n  } else if (w == 8) {\n    mult8 = gf_w8_get_mult_table(&gf);\n    if (mult8 == NULL) {\n      printf(\"Couldn't get inline multiplication table.\\n\");\n      exit(1);\n    }\n    elapsed = 0;\n    dnum = 0;\n    for (i = 0; i < iterations; i++) {\n      for (j = 0; j < size; j++) {\n        ra[j] = MOA_Random_W(w, 1);\n        rb[j] = MOA_Random_W(w, 1);\n      }\n      timer_start(&timer);\n      for (j = 0; j < size; j++) {\n        ra[j] = GF_W8_INLINE_MULTDIV(mult8, ra[j], rb[j]);\n      }\n      dnum += num;\n      elapsed += timer_split(&timer);\n    }\n    printf(\"Inline mult:   %10.6lf s   Mops: %10.3lf    %10.3lf Mega-ops/s\\n\",\n           elapsed, dnum/1024.0/1024.0, dnum/1024.0/1024.0/elapsed);\n  } else if (w == 16) {\n    log16 = gf_w16_get_log_table(&gf);\n    alog16 = gf_w16_get_mult_alog_table(&gf);\n    if (log16 == NULL) {\n      printf(\"Couldn't get inline multiplication table.\\n\");\n      exit(1);\n    }\n    elapsed = 0;\n    dnum = 0;\n    for (i = 0; i < iterations; i++) {\n      for (j = 0; j < size; j++) {\n        ra16[j] = MOA_Random_W(w, 1);\n        rb16[j] = MOA_Random_W(w, 1);\n      }\n      timer_start(&timer);\n      for (j = 0; j < size; j++) {\n        ra16[j] = GF_W16_INLINE_MULT(log16, alog16, ra16[j], rb16[j]);\n      }\n      dnum += num;\n      elapsed += timer_split(&timer);\n    }\n    printf(\"Inline mult:   %10.6lf s   Mops: %10.3lf    %10.3lf Mega-ops/s\\n\",\n           elapsed, dnum/1024.0/1024.0, dnum/1024.0/1024.0/elapsed);\n  }\n  free (ra);\n  free (rb);\n  free (ra16);\n  free (rb16);\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/tools/gf_methods.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_methods.c\n *\n * Lists supported methods (incomplete w.r.t. GROUP and COMPOSITE)\n */\n\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n\n#include \"gf_complete.h\"\n#include \"gf_method.h\"\n#include \"gf_int.h\"\n\n#define BNMULTS (8)\nstatic char *BMULTS[BNMULTS] = { \"CARRY_FREE\", \"GROUP48\", \n                               \"TABLE\", \"LOG\", \"SPLIT4\", \"SPLIT8\", \"SPLIT88\", \"COMPOSITE\" };\n#define NMULTS (17)\nstatic char *MULTS[NMULTS] = { \"SHIFT\", \"CARRY_FREE\", \"CARRY_FREE_GK\", \"GROUP44\", \"GROUP48\", \"BYTWO_p\", \"BYTWO_b\",\n                               \"TABLE\", \"LOG\", \"LOG_ZERO\", \"LOG_ZERO_EXT\", \"SPLIT2\",\n                               \"SPLIT4\", \"SPLIT8\", \"SPLIT16\", \"SPLIT88\", \"COMPOSITE\" };\n\n/* Make sure CAUCHY is last */\n\n#define NREGIONS (7) \nstatic char *REGIONS[NREGIONS] = { \"DOUBLE\", \"QUAD\", \"LAZY\", \"SIMD\", \"NOSIMD\",\n                                   \"ALTMAP\", \"CAUCHY\" };\n\n#define BNREGIONS (4) \nstatic char *BREGIONS[BNREGIONS] = { \"DOUBLE\", \"QUAD\", \"ALTMAP\", \"CAUCHY\" };\n\n#define NDIVS (2)\nstatic char *divides[NDIVS] = { \"MATRIX\", \"EUCLID\" }; \n\nvoid usage(char *s)\n{\n   fprintf(stderr, \"usage: gf_methods w -BADC -LUMDRB\\n\");\n   fprintf(stderr, \"\\n\");\n   fprintf(stderr, \"       w can be 1-32, 64, 128\\n\");\n   fprintf(stderr, \"\\n\");\n   fprintf(stderr, \"       -B lists basic methods that are useful\\n\");\n   fprintf(stderr, \"       -A does a nearly exhaustive listing\\n\");\n   fprintf(stderr, \"       -D adds EUCLID and MATRIX division\\n\");\n   fprintf(stderr, \"       -C adds CAUCHY when possible\\n\");\n   fprintf(stderr, \"       Combinations are fine.\\n\");\n   fprintf(stderr, \"\\n\");\n   fprintf(stderr, \"       -L Simply lists methods\\n\");\n   fprintf(stderr, \"       -U Produces calls to gf_unit\\n\");\n   fprintf(stderr, \"       -M Produces calls to time_tool.sh for single multiplications\\n\");\n   fprintf(stderr, \"       -D Produces calls to time_tool.sh for single divisions\\n\");\n   fprintf(stderr, \"       -R Produces calls to time_tool.sh for region multiplications\\n\");\n   fprintf(stderr, \"       -B Produces calls to time_tool.sh for the fastest region multiplications\\n\");\n   fprintf(stderr, \"       Cannot combine L, U, T.\\n\");\n   if (s != NULL) {\n     fprintf(stderr, \"\\n\");\n     fprintf(stderr, \"%s\\n\", s);\n   }\n   exit(1);\n}\n\nint main(int argc, char *argv[])\n{\n  int m, r, d, w, i, sa, j, k, reset, ok;\n  int nregions;\n  int nmults;\n  char **regions;\n  char **mults;\n  int exhaustive = 0;\n  int divide = 0;\n  int cauchy = 0;\n  int listing;\n  char *gf_argv[50], *x;\n  gf_t gf;\n  char ls[10];\n  char * w_str;\n\n  if (argc != 4) usage(NULL);\n  w = atoi(argv[1]);\n  ok = (w >= 1 && w <= 32);\n  if (w == 64) ok = 1;\n  if (w == 128) ok = 1;\n  if (!ok) usage(\"Bad w\");\n  \n  if (argv[2][0] != '-' || argv[3][0] != '-' || strlen(argv[2]) == 1 || strlen(argv[3]) != 2) {\n    usage(NULL);\n  }\n  for (i = 1; argv[2][i] != '\\0'; i++) {\n    switch(argv[2][i]) {\n      case 'B': exhaustive = 0; break;\n      case 'A': exhaustive = 1; break;\n      case 'D': divide = 1; break;\n      case 'C': cauchy = 1; break;\n      default: usage(\"Bad -BADC\");\n    }\n  }\n\n  if (strchr(\"LUMDRB\", argv[3][1]) == NULL) { usage(\"Bad -LUMDRB\"); }\n  listing = argv[3][1];\n\n  if (listing == 'U') {\n    w_str = \"../test/gf_unit %d A -1\";\n  } else if (listing == 'L') {\n    w_str = \"w=%d:\";\n  } else {\n    w_str = strdup(\"sh time_tool.sh X %d\");\n    x = strchr(w_str, 'X');\n    *x = listing;\n  }\n\n  gf_argv[0] = \"-\";\n  if (create_gf_from_argv(&gf, w, 1, gf_argv, 0) > 0) {\n    printf(w_str, w);\n    printf(\" - \\n\");\n    gf_free(&gf, 1);\n  } else if (_gf_errno == GF_E_DEFAULT) {\n    fprintf(stderr, \"Unlabeled failed method: w=%d: -\\n\", 2);\n    exit(1);\n  }\n\n  nregions = (exhaustive) ? NREGIONS : BNREGIONS;\n  if (!cauchy) nregions--;\n  regions = (exhaustive) ? REGIONS : BREGIONS;\n  mults = (exhaustive) ? MULTS : BMULTS;\n  nmults = (exhaustive) ? NMULTS : BNMULTS;\n\n\n  for (m = 0; m < nmults; m++) {\n    sa = 0;\n    gf_argv[sa++] = \"-m\";\n    if (strcmp(mults[m], \"GROUP44\") == 0) {\n      gf_argv[sa++] = \"GROUP\";\n      gf_argv[sa++] = \"4\";\n      gf_argv[sa++] = \"4\";\n    } else if (strcmp(mults[m], \"GROUP48\") == 0) {\n      gf_argv[sa++] = \"GROUP\";\n      gf_argv[sa++] = \"4\";\n      gf_argv[sa++] = \"8\";\n    } else if (strcmp(mults[m], \"SPLIT2\") == 0) {\n      gf_argv[sa++] = \"SPLIT\";\n      sprintf(ls, \"%d\", w);\n      gf_argv[sa++] = ls;\n      gf_argv[sa++] = \"2\";\n    } else if (strcmp(mults[m], \"SPLIT4\") == 0) {\n      gf_argv[sa++] = \"SPLIT\";\n      sprintf(ls, \"%d\", w);\n      gf_argv[sa++] = ls;\n      gf_argv[sa++] = \"4\";\n    } else if (strcmp(mults[m], \"SPLIT8\") == 0) {\n      gf_argv[sa++] = \"SPLIT\";\n      sprintf(ls, \"%d\", w);\n      gf_argv[sa++] = ls;\n      gf_argv[sa++] = \"8\";\n    } else if (strcmp(mults[m], \"SPLIT16\") == 0) {\n      gf_argv[sa++] = \"SPLIT\";\n      sprintf(ls, \"%d\", w);\n      gf_argv[sa++] = ls;\n      gf_argv[sa++] = \"16\";\n    } else if (strcmp(mults[m], \"SPLIT88\") == 0) {\n      gf_argv[sa++] = \"SPLIT\";\n      gf_argv[sa++] = \"8\";\n      gf_argv[sa++] = \"8\";\n    } else if (strcmp(mults[m], \"COMPOSITE\") == 0) {\n      gf_argv[sa++] = \"COMPOSITE\";\n      gf_argv[sa++] = \"2\";\n      gf_argv[sa++] = \"-\";\n    } else {\n      gf_argv[sa++] = mults[m];\n    }\n    reset = sa;\n\n\n    for (r = 0; r < (1 << nregions); r++) {\n      sa = reset;\n      for (k = 0; k < nregions; k++) {\n        if (r & (1 << k)) {\n          gf_argv[sa++] = \"-r\";\n          gf_argv[sa++] = regions[k];\n        }\n      }\n      gf_argv[sa++] = \"-\";\n\n      /* printf(\"Hmmmm. %s\", gf_argv[0]);\n      for (j = 0; j < sa; j++) printf(\" %s\", gf_argv[j]);\n      printf(\"\\n\");  */\n  \n      if (create_gf_from_argv(&gf, w, sa, gf_argv, 0) > 0) {\n        printf(w_str, w);\n        for (j = 0; j < sa; j++) printf(\" %s\", gf_argv[j]);\n        printf(\"\\n\");\n        gf_free(&gf, 1);\n      } else if (_gf_errno == GF_E_DEFAULT) {\n        fprintf(stderr, \"Unlabeled failed method: w=%d:\", w);\n        for (j = 0; j < sa; j++) fprintf(stderr, \" %s\", gf_argv[j]);\n        fprintf(stderr, \"\\n\");\n        exit(1);\n      }\n      sa--;\n      if (divide) {\n        for (d = 0; d < NDIVS; d++) {\n          gf_argv[sa++] = \"-d\";\n          gf_argv[sa++] = divides[d];\n          /*          printf(\"w=%d:\", w);\n                      for (j = 0; j < sa; j++) printf(\" %s\", gf_argv[j]);\n                      printf(\"\\n\"); */\n          gf_argv[sa++] = \"-\";\n          if (create_gf_from_argv(&gf, w, sa, gf_argv, 0) > 0) {\n            printf(w_str, w);\n            for (j = 0; j < sa; j++) printf(\" %s\", gf_argv[j]);\n            printf(\"\\n\");\n            gf_free(&gf, 1);\n          } else if (_gf_errno == GF_E_DEFAULT) {\n            fprintf(stderr, \"Unlabeled failed method: w=%d:\", w);\n            for (j = 0; j < sa; j++) fprintf(stderr, \" %s\", gf_argv[j]);\n            fprintf(stderr, \"\\n\");\n            exit(1);\n          } \n          sa-=3;\n        }\n      }\n    }\n  }\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/tools/gf_mult.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_mult.c\n *\n * Multiplies two numbers in gf_2^w\n */\n\n#include <stdio.h>\n#include <getopt.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n\n#include \"gf_complete.h\"\n#include \"gf_method.h\"\n#include \"gf_general.h\"\n\nvoid usage(int why)\n{\n  fprintf(stderr, \"usage: gf_mult a b w [method] - does multiplication of a and b in GF(2^w)\\n\");\n  if (why == 'W') {\n    fprintf(stderr, \"Bad w.\\n\");\n    fprintf(stderr, \"Legal w are: 1 - 32, 64 and 128.\\n\");\n    fprintf(stderr, \"Append 'h' to w to treat a, b and the product as hexadecimal.\\n\");\n    fprintf(stderr, \"w=128 is hex only (i.e. '128' will be an error - do '128h')\\n\");\n  }\n  if (why == 'A') fprintf(stderr, \"Bad a\\n\");\n  if (why == 'B') fprintf(stderr, \"Bad b\\n\");\n  if (why == 'M') {\n    fprintf(stderr, \"Bad Method Specification: \");\n    gf_error();\n  }\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  int hex, w;\n  gf_t gf;\n  gf_general_t a, b, c;\n  char output[50];\n\n  if (argc < 4) usage(' ');\n\n  if (sscanf(argv[3], \"%d\", &w) == 0) usage('W');\n  if (w <= 0 || (w > 32 && w != 64 && w != 128)) usage('W');\n\n  hex = (strchr(argv[3], 'h') != NULL);\n  if (!hex && w == 128) usage('W');\n\n  if (argc == 4) {\n    if (gf_init_easy(&gf, w) == 0) usage('M');\n  } else {\n    if (create_gf_from_argv(&gf, w, argc, argv, 4) == 0) usage('M');\n  }\n \n  if (!gf_general_s_to_val(&a, w, argv[1], hex)) usage('A');\n  if (!gf_general_s_to_val(&b, w, argv[2], hex)) usage('B');\n\n  gf_general_multiply(&gf, &a, &b, &c);\n  gf_general_val_to_s(&c, w, output, hex);\n  \n  printf(\"%s\\n\", output);\n  exit(0);\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/tools/gf_poly.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_poly.c - program to help find irreducible polynomials in composite fields,\n * using the Ben-Or algorithm.  \n * \n * (This one was written by Jim) \n * \n * Please see the following paper for a description of the Ben-Or algorithm:\n * \n * author    S. Gao and D. Panario\n * title     Tests and Constructions of Irreducible Polynomials over Finite Fields\n * booktitle Foundations of Computational Mathematics\n * year      1997\n * publisher Springer Verlag\n * pages     346-361\n * \n * The basic technique is this.  You have a polynomial f(x) whose coefficients are\n * in a base field GF(2^w).  The polynomial is of degree n.  You need to do the \n * following for all i from 1 to n/2:\n * \n * Construct x^(2^w)^i modulo f.  That will be a polynomial of maximum degree n-1\n * with coefficients in GF(2^w).  You construct that polynomial by starting with x\n * and doubling it w times, each time taking the result modulo f.  Then you \n * multiply that by itself i times, again each time taking the result modulo f.\n * \n * When you're done, you need to \"subtract\" x -- since addition = subtraction = \n * XOR, that means XOR x.  \n * \n * Now, find the GCD of that last polynomial and f, using Euclid's algorithm.  If\n * the GCD is not one, then f is reducible.  If it is not reducible for each of\n * those i, then it is irreducible.\n * \n * In this code, I am using a gf_general_t to represent elements of GF(2^w).  This\n * is so that I can use base fields that are GF(2^64) or GF(2^128). \n * \n * I have two main procedures.  The first is x_to_q_to_i_minus_x, which calculates\n * x^(2^w)^i - x, putting the result into a gf_general_t * called retval.\n * \n * The second is gcd_one, which takes a polynomial of degree n and a second one\n * of degree n-1, and uses Euclid's algorithm to decide if their GCD == 1.\n * \n * These can be made faster (e.g. calculate x^(2^w) once and store it).\n */\n\n#include \"gf_complete.h\"\n#include \"gf_method.h\"\n#include \"gf_general.h\"\n#include \"gf_int.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <assert.h>\n\nchar *BM = \"Bad Method: \";\n\nvoid usage(char *s)\n{\n  fprintf(stderr, \"usage: gf_poly w(base-field) method power:coef [ power:coef .. ]\\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"       use - for the default method.\\n\");\n  fprintf(stderr, \"       use 0x in front of the coefficient if it's in hex\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"       For example, to test whether x^2 + 2x + 1 is irreducible\\n\");\n  fprintf(stderr, \"       in GF(2^16), the call is:\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"       gf_poly 16 - 2:1 1:2 0:1\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"       See the user's manual for more information.\\n\");\n  if (s != NULL) {\n    fprintf(stderr, \"\\n\");\n    if (s == BM) {\n      fprintf(stderr, \"%s\", s);\n      gf_error();\n    } else {\n      fprintf(stderr, \"%s\\n\", s);\n    }\n  }\n  exit(1);\n}\n\nint gcd_one(gf_t *gf, int w, int n, gf_general_t *poly, gf_general_t *prod)\n{\n  gf_general_t *a, *b, zero, factor, p;\n  int i, j, da, db;\n\n  gf_general_set_zero(&zero, w);\n\n  a = (gf_general_t *) malloc(sizeof(gf_general_t) * n+1);\n  b = (gf_general_t *) malloc(sizeof(gf_general_t) * n);\n  for (i = 0; i <= n; i++) gf_general_add(gf, &zero, poly+i, a+i);\n  for (i = 0; i < n; i++) gf_general_add(gf, &zero, prod+i, b+i);\n\n  da = n;\n  while (1) {\n    for (db = n-1; db >= 0 && gf_general_is_zero(b+db, w); db--) ;\n    if (db < 0) return 0;\n    if (db == 0) return 1;\n    for (j = da; j >= db; j--) {\n      if (!gf_general_is_zero(a+j, w)) {\n        gf_general_divide(gf, a+j, b+db, &factor);\n        for (i = 0; i <= db; i++) {\n          gf_general_multiply(gf, b+i, &factor, &p); \n          gf_general_add(gf, &p, a+(i+j-db), a+(i+j-db));\n        }\n      }\n    }\n    for (i = 0; i < n; i++) {\n      gf_general_add(gf, a+i, &zero, &p);\n      gf_general_add(gf, b+i, &zero, a+i);\n      gf_general_add(gf, &p, &zero, b+i);\n    }\n  }\n\n}\n\nvoid x_to_q_to_i_minus_x(gf_t *gf, int w, int n, gf_general_t *poly, int logq, int i, gf_general_t *retval)\n{\n  gf_general_t x;\n  gf_general_t *x_to_q;\n  gf_general_t *product;\n  gf_general_t p, zero, factor;\n  int j, k, lq;\n\n  gf_general_set_zero(&zero, w);\n  product = (gf_general_t *) malloc(sizeof(gf_general_t) * n*2);\n  x_to_q = (gf_general_t *) malloc(sizeof(gf_general_t) * n);\n  for (j = 0; j < n; j++) gf_general_set_zero(x_to_q+j, w);\n  gf_general_set_one(x_to_q+1, w);\n\n  for (lq = 0; lq < logq; lq++) {\n    for (j = 0; j < n*2; j++) gf_general_set_zero(product+j, w);\n    for (j = 0; j < n; j++) {\n      for (k = 0; k < n; k++) {\n        gf_general_multiply(gf, x_to_q+j, x_to_q+k, &p);\n        gf_general_add(gf, product+(j+k), &p, product+(j+k));\n      }\n    }\n    for (j = n*2-1; j >= n; j--) {\n      if (!gf_general_is_zero(product+j, w)) {\n        gf_general_add(gf, product+j, &zero, &factor);\n        for (k = 0; k <= n; k++) {\n          gf_general_multiply(gf, poly+k, &factor, &p);\n          gf_general_add(gf, product+(j-n+k), &p, product+(j-n+k));\n        }\n      }\n    }\n    for (j = 0; j < n; j++) gf_general_add(gf, product+j, &zero, x_to_q+j);\n  }\n  for (j = 0; j < n; j++) gf_general_set_zero(retval+j, w);\n  gf_general_set_one(retval, w);\n\n  while (i > 0) {\n    for (j = 0; j < n*2; j++) gf_general_set_zero(product+j, w);\n    for (j = 0; j < n; j++) {\n      for (k = 0; k < n; k++) {\n        gf_general_multiply(gf, x_to_q+j, retval+k, &p);\n        gf_general_add(gf, product+(j+k), &p, product+(j+k));\n      }\n    }\n    for (j = n*2-1; j >= n; j--) {\n      if (!gf_general_is_zero(product+j, w)) {\n        gf_general_add(gf, product+j, &zero, &factor);\n        for (k = 0; k <= n; k++) {\n          gf_general_multiply(gf, poly+k, &factor, &p);\n          gf_general_add(gf, product+(j-n+k), &p, product+(j-n+k));\n        }\n      }\n    }\n    for (j = 0; j < n; j++) gf_general_add(gf, product+j, &zero, retval+j);\n    i--;\n  }\n\n  gf_general_set_one(&x, w);\n  gf_general_add(gf, &x, retval+1, retval+1);\n\n  free(product);\n  free(x_to_q);\n}\n\nint main(int argc, char **argv)\n{\n  int w, i, power, n, ap, success;\n  gf_t gf;\n  gf_general_t *poly, *prod;\n  char *string, *ptr;\n  char buf[100];\n\n  if (argc < 4) usage(NULL);\n\n  if (sscanf(argv[1], \"%d\", &w) != 1 || w <= 0) usage(\"Bad w.\");\n  ap = create_gf_from_argv(&gf, w, argc, argv, 2);\n\n  if (ap == 0) usage(BM);\n\n  if (ap == argc) usage(\"No powers/coefficients given.\");\n\n  n = -1;\n  for (i = ap; i < argc; i++) {\n    if (strchr(argv[i], ':') == NULL || sscanf(argv[i], \"%d:\", &power) != 1) {\n      string = (char *) malloc(sizeof(char)*(strlen(argv[i]+100)));\n      sprintf(string, \"Argument '%s' not in proper format of power:coefficient\\n\", argv[i]);\n      usage(string);\n    }\n    if (power < 0) {\n      usage(\"Can't have negative powers\\n\");\n    } else {\n      n = power;\n    }\n  }\n  // in case the for-loop header fails\n  assert (n >= 0);\n\n  poly = (gf_general_t *) malloc(sizeof(gf_general_t)*(n+1));\n  for (i = 0; i <= n; i++) gf_general_set_zero(poly+i, w);\n  prod = (gf_general_t *) malloc(sizeof(gf_general_t)*n);\n\n  for (i = ap; i < argc; i++) {\n    sscanf(argv[i], \"%d:\", &power);\n    ptr = strchr(argv[i], ':');\n    ptr++;\n    if (strncmp(ptr, \"0x\", 2) == 0) {\n      success = gf_general_s_to_val(poly+power, w, ptr+2, 1);\n    } else {\n      success = gf_general_s_to_val(poly+power, w, ptr, 0);\n    }\n    if (success == 0) {\n      string = (char *) malloc(sizeof(char)*(strlen(argv[i]+100)));\n      sprintf(string, \"Argument '%s' not in proper format of power:coefficient\\n\", argv[i]);\n      usage(string);\n    }\n  }\n\n  printf(\"Poly:\");\n  for (power = n; power >= 0; power--) {\n    if (!gf_general_is_zero(poly+power, w)) {\n      printf(\"%s\", (power == n) ? \" \" : \" + \");\n      if (!gf_general_is_one(poly+power, w)) {\n        gf_general_val_to_s(poly+power, w, buf, 1);\n        if (n > 0) {\n          printf(\"(0x%s)\", buf);\n        } else {\n          printf(\"0x%s\", buf);\n        }\n      }\n      if (power == 0) {\n        if (gf_general_is_one(poly+power, w)) printf(\"1\");\n      } else if (power == 1) {\n        printf(\"x\");\n      } else {\n        printf(\"x^%d\", power);\n      }\n    }\n  }\n  printf(\"\\n\");\n\n  if (!gf_general_is_one(poly+n, w)) {\n    printf(\"\\n\");\n    printf(\"Can't do Ben-Or, because the polynomial is not monic.\\n\");\n    exit(0);\n  }\n\n  for (i = 1; i <= n/2; i++) {\n    x_to_q_to_i_minus_x(&gf, w, n, poly, w, i, prod); \n    if (!gcd_one(&gf, w, n, poly, prod)) {\n      printf(\"Reducible.\\n\");\n      exit(0);\n    }\n  }\n  \n  printf(\"Irreducible.\\n\");\n  exit(0);\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/tools/gf_time.c",
    "content": "/*\n * GF-Complete: A Comprehensive Open Source Library for Galois Field Arithmetic\n * James S. Plank, Ethan L. Miller, Kevin M. Greenan,\n * Benjamin A. Arnold, John A. Burnum, Adam W. Disney, Allen C. McBride.\n *\n * gf_time.c\n *\n * Performs timing for gf arithmetic\n */\n\n#include \"config.h\"\n\n#ifdef HAVE_POSIX_MEMALIGN\n#ifndef _XOPEN_SOURCE\n#define _XOPEN_SOURCE 600\n#endif\n#endif\n\n#include <stdio.h>\n#include <getopt.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <sys/time.h>\n\n#include \"gf_complete.h\"\n#include \"gf_method.h\"\n#include \"gf_rand.h\"\n#include \"gf_general.h\"\n\nvoid\ntimer_start (double *t)\n{\n    struct timeval  tv;\n\n    gettimeofday (&tv, NULL);\n    *t = (double)tv.tv_sec + (double)tv.tv_usec * 1e-6;\n}\n\ndouble\ntimer_split (const double *t)\n{\n    struct timeval  tv;\n    double  cur_t;\n\n    gettimeofday (&tv, NULL);\n    cur_t = (double)tv.tv_sec + (double)tv.tv_usec * 1e-6;\n    return (cur_t - *t);\n}\n\nvoid problem(char *s)\n{\n  fprintf(stderr, \"Timing test failed.\\n\");\n  fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\nchar *BM = \"Bad Method: \";\n\nvoid usage(char *s)\n{\n  fprintf(stderr, \"usage: gf_time w tests seed size(bytes) iterations [method [params]] - does timing\\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"does unit testing in GF(2^w)\\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"Legal w are: 1 - 32, 64 and 128\\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"Tests may be any combination of:\\n\");\n  fprintf(stderr, \"       A: All\\n\");\n  fprintf(stderr, \"       S: All Single Operations\\n\");\n  fprintf(stderr, \"       R: All Region Operations\\n\");\n  fprintf(stderr, \"       M: Single: Multiplications\\n\");\n  fprintf(stderr, \"       D: Single: Divisions\\n\");\n  fprintf(stderr, \"       I: Single: Inverses\\n\");\n  fprintf(stderr, \"       G: Region: Buffer-Constant Multiplication\\n\");\n  fprintf(stderr, \"       0: Region: Doing nothing, and bzero()\\n\");\n  fprintf(stderr, \"       1: Region: Memcpy() and XOR\\n\");\n  fprintf(stderr, \"       2: Region: Multiplying by two\\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"Use -1 for time(0) as a seed.\\n\");\n  fprintf(stderr, \"\\n\");\n  if (s == BM) {\n    fprintf(stderr, \"%s\", BM);\n    gf_error();\n  } else if (s != NULL) {\n    fprintf(stderr, \"%s\\n\", s);\n  }\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  int w, it, i, size, iterations, xor;\n  char tests[100];\n  char test;\n  char *single_tests = \"MDI\";\n  char *region_tests = \"G012\";\n  char *tstrings[256];\n  void *tmethods[256];\n  gf_t      gf;\n  double timer, elapsed, ds, di, dnum;\n  int num;\n  time_t t0;\n  uint8_t *ra, *rb;\n  gf_general_t a;\n#ifndef HAVE_POSIX_MEMALIGN\n  uint8_t *malloc_ra, *malloc_rb;\n#endif\n\n  \n  if (argc < 6) usage(NULL);\n  \n  if (sscanf(argv[1], \"%d\", &w) == 0){\n    usage(\"Bad w[-pp]\\n\");\n  }\n\n  \n  if (sscanf(argv[3], \"%ld\", &t0) == 0) usage(\"Bad seed\\n\");\n  if (sscanf(argv[4], \"%d\", &size) == 0) usage(\"Bad size\\n\");\n  if (sscanf(argv[5], \"%d\", &iterations) == 0) usage(\"Bad iterations\\n\");\n  if (t0 == -1) t0 = time(0);\n  MOA_Seed(t0);\n\n  ds = size;\n  di = iterations;\n\n  if ((w > 32 && w != 64 && w != 128) || w < 0) usage(\"Bad w\");\n  if ((size * 8) % w != 0) usage (\"Bad size -- must be a multiple of w*8\\n\");\n  \n  if (!create_gf_from_argv(&gf, w, argc, argv, 6)) usage(BM);\n\n  strcpy(tests, \"\");\n  for (i = 0; argv[2][i] != '\\0'; i++) {\n    switch(argv[2][i]) {\n      case 'A': strcat(tests, single_tests); \n                strcat(tests, region_tests); \n                break;\n      case 'S': strcat(tests, single_tests); break;\n      case 'R': strcat(tests, region_tests); break;\n      case 'G': strcat(tests, \"G\"); break;\n      case '0': strcat(tests, \"0\"); break;\n      case '1': strcat(tests, \"1\"); break;\n      case '2': strcat(tests, \"2\"); break;\n      case 'M': strcat(tests, \"M\"); break;\n      case 'D': strcat(tests, \"D\"); break;\n      case 'I': strcat(tests, \"I\"); break;\n      default: usage(\"Bad tests\");\n    }\n  }\n\n  tstrings['M'] = \"Multiply\";\n  tstrings['D'] = \"Divide\";\n  tstrings['I'] = \"Inverse\";\n  tstrings['G'] = \"Region-Random\";\n  tstrings['0'] = \"Region-By-Zero\";\n  tstrings['1'] = \"Region-By-One\";\n  tstrings['2'] = \"Region-By-Two\";\n\n  tmethods['M'] = (void *) gf.multiply.w32;\n  tmethods['D'] = (void *) gf.divide.w32;\n  tmethods['I'] = (void *) gf.inverse.w32;\n  tmethods['G'] = (void *) gf.multiply_region.w32;\n  tmethods['0'] = (void *) gf.multiply_region.w32;\n  tmethods['1'] = (void *) gf.multiply_region.w32;\n  tmethods['2'] = (void *) gf.multiply_region.w32;\n\n  printf(\"Seed: %ld\\n\", t0);\n\n#ifdef HAVE_POSIX_MEMALIGN\n  if (posix_memalign((void **) &ra, 16, size))\n    ra = NULL;\n  if (posix_memalign((void **) &rb, 16, size))\n    rb = NULL;\n#else\n  malloc_ra = (uint8_t *) malloc(size + 15);\n  malloc_rb = (uint8_t *) malloc(size + 15);\n  ra = (uint8_t *) (((uintptr_t) malloc_ra + 15) & ~((uintptr_t) 0xf));\n  rb = (uint8_t *) (((uintptr_t) malloc_rb + 15) & ~((uintptr_t) 0xf));\n#endif\n\n  if (ra == NULL || rb == NULL) { perror(\"malloc\"); exit(1); }\n\n  for (i = 0; i < 3; i++) {\n    test = single_tests[i];\n    if (strchr(tests, test) != NULL) {\n      if (tmethods[(int)test] == NULL) {\n        printf(\"No %s method.\\n\", tstrings[(int)test]);\n      } else {\n        elapsed = 0;\n        dnum = 0;\n        for (it = 0; it < iterations; it++) {\n          gf_general_set_up_single_timing_test(w, ra, rb, size);\n          timer_start(&timer);\n          num = gf_general_do_single_timing_test(&gf, ra, rb, size, test);\n          dnum += num;\n          elapsed += timer_split(&timer);\n        }\n        printf(\"%14s:           %10.6lf s   Mops: %10.3lf    %10.3lf Mega-ops/s\\n\", \n               tstrings[(int)test], elapsed, \n               dnum/1024.0/1024.0, dnum/1024.0/1024.0/elapsed);\n      }\n    }\n  }\n\n  for (i = 0; i < 4; i++) {\n    test = region_tests[i];\n    if (strchr(tests, test) != NULL) {\n      if (tmethods[(int)test] == NULL) {\n        printf(\"No %s method.\\n\", tstrings[(int)test]);\n      } else {\n        if (test == '0') gf_general_set_zero(&a, w);\n        if (test == '1') gf_general_set_one(&a, w);\n        if (test == '2') gf_general_set_two(&a, w);\n\n        for (xor = 0; xor < 2; xor++) {\n          elapsed = 0;\n          for (it = 0; it < iterations; it++) {\n            if (test == 'G') gf_general_set_random(&a, w, 1);\n            gf_general_set_up_single_timing_test(8, ra, rb, size);\n            timer_start(&timer);\n            gf_general_do_region_multiply(&gf, &a, ra, rb, size, xor);\n            elapsed += timer_split(&timer);\n          }\n          printf(\"%14s: XOR: %d    %10.6lf s     MB: %10.3lf    %10.3lf MB/s\\n\", \n               tstrings[(int)test], xor, elapsed, \n               ds*di/1024.0/1024.0, ds*di/1024.0/1024.0/elapsed);\n        }\n      }\n    }\n  }\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/gf-complete/tools/time_tool.sh",
    "content": "# time_tool.sh - Shell script to test various timings.  \n# This is a rough tester -- its job is to work quickly rather than precisely.\n# (Jim Plank)\n\n#!/bin/sh\n\nif [ $# -lt 3 ]; then\n  echo 'usage sh time_tool.sh M|D|R|B w method' >&2\n  exit 1\nfi\n\nop=$1\nw=$2\n\nshift ; shift\n\nmethod=\"$*\"\n\nif [ $op != M -a $op != D -a $op != R -a $op != B ]; then\n  echo 'usage sh time_tool.sh M|D|R|B w method' >&2\n  echo 'You have to specify a test: ' >&2 \n  echo '  M=Multiplication' >&2 \n  echo '  D=Division' >&2 \n  echo '  R=Regions' >&2 \n  echo '  B=Best-Region' >&2 \n  exit 1\nfi\n\n# First, use a 16K buffer to test the performance of single multiplies.\n\nfac=`echo $w | awk '{ n = $1; while (n != 0 && n%2==0) n /= 2; print n }'`\nif [ $fac -eq 0 ]; then\n  echo 'usage sh time_tool.sh M|D|R|B w method' >&2\n  echo 'Bad w' >&2\n  exit 1\nfi\n\nbsize=16384\nbsize=`echo $bsize $fac | awk '{ print $1 * $2 }'`\n\nif [ `./gf_time $w M -1 $bsize 1 $method 2>&1 | wc | awk '{ print $1 }'` -gt 2 ]; then\n  echo 'usage sh time_tool.sh w method' >&2\n  echo \"Bad method\"\n  exit 1\nfi\n\nif [ $op = M -o $op = D ]; then\n  iter=1\n  c1=`./gf_time $w $op -1 $bsize $iter $method`\n  t=`echo $c1 | awk '{ printf \"%d\\n\", $4*100 }'`\n  s=`echo $c1 | awk '{ print $8 }'`\n  bs=$s\n  \n  while [ $t -lt 1 ]; do\n    bs=$s\n    iter=`echo $iter | awk '{ print $1*2 }'`\n    c1=`./gf_time $w $op -1 $bsize $iter $method`\n    t=`echo $c1 | awk '{ printf \"%d\\n\", $4*100 }'`\n    s=`echo $c1 | awk '{ print $8 }'`\n  done\n  \n  echo $op $bs | awk '{ printf \"%s speed (MB/s): %8.2lf   W-Method: \", $1, $2 }'\n  echo $w $method \n  exit 0\nfi\n  \nbsize=16384\nbsize=`echo $bsize $fac | awk '{ print $1 * $2 }'`\n\nbest=0\nwhile [ $bsize -le 4194304 ]; do\n  iter=1\n  c1=`./gf_time $w G -1 $bsize $iter $method`\n  t=`echo $c1 | awk '{ printf \"%d\\n\", $6*500 }'`\n  s=`echo $c1 | awk '{ print $10 }'`\n  bs=$s\n\n  while [ $t -lt 1 ]; do\n    bs=$s\n    iter=`echo $iter | awk '{ print $1*2 }'`\n    c1=`./gf_time $w G -1 $bsize $iter $method`\n    t=`echo $c1 | awk '{ printf \"%d\\n\", $6*500 }'`\n    s=`echo $c1 | awk '{ print $10 }'`\n  done\n  if [ $bsize -lt 1048576 ]; then\n    str=`echo $bsize | awk '{ printf \"%3dK\\n\", $1/1024 }'`\n  else \n    str=`echo $bsize | awk '{ printf \"%3dM\\n\", $1/1024/1024 }'`\n  fi\n  if [ $op = R ]; then\n    echo $str $bs | awk '{ printf \"Region Buffer-Size: %4s (MB/s): %8.2lf   W-Method: \", $1, $2 }'\n    echo $w $method \n  fi\n  best=`echo $best $bs | awk '{ print ($1 > $2) ? $1 : $2 }'`\n  bsize=`echo $bsize | awk '{ print $1 * 2 }'`\ndone\necho $best | awk '{ printf \"Region Best (MB/s): %8.2lf   W-Method: \", $1 }'\necho $w $method \n"
  },
  {
    "path": "fst/layout/jerasure/.gitattributes",
    "content": "*.ac eol=lf\n*.am eol=lf\n*.sh eol=lf\n"
  },
  {
    "path": "fst/layout/jerasure/.gitignore",
    "content": "/INSTALL\nMakefile\nMakefile.in\n/aclocal.m4\n/autom4te.cache/\n/autoscan.log\n/build-aux/\n/config.log\n/config.status\n/configure\n/configure.scan\n.deps/\n/include/config.h\n/include/config.h.in\n/include/stamp-h1\n.libs/\n/libtool\n/m4/libtool.m4\n/m4/ltoptions.m4\n/m4/ltsugar.m4\n/m4/ltversion.m4\n/m4/lt~obsolete.m4\n*.l[ao]\n*.[ao]\n*~\n.dirstamp\n*.log\n*.trs\n"
  },
  {
    "path": "fst/layout/jerasure/AUTHORS",
    "content": ""
  },
  {
    "path": "fst/layout/jerasure/COPYING",
    "content": "\nCopyright (c) 2013, James S. Plank and Kevin Greenan\nAll rights reserved.\n\nJerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure Coding Techniques\n\nRevision 2.0: Galois Field backend now links to GF-Complete\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n - Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n\n - Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in\n   the documentation and/or other materials provided with the\n   distribution.\n\n - Neither the name of the University of Tennessee nor the names of its\n   contributors may be used to endorse or promote products derived\n   from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\nINCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\nBUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\nOF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\nAND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\nWAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE.\n\n"
  },
  {
    "path": "fst/layout/jerasure/ChangeLog",
    "content": ""
  },
  {
    "path": "fst/layout/jerasure/Examples/.gitignore",
    "content": "/cauchy_[0-9][0-9]\n/decoder\n/encoder\n/jerasure_[0-9][0-9]\n/liberation_[0-9][0-9]\n/reed_sol_[0-9][0-9]\n/reed_sol_test_gf\n/reed_sol_time_gf\n/test_galois\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/Makefile.am",
    "content": "# Jerasure AM file\n\nAM_CPPFLAGS = -I$(top_srcdir)/include\nAM_CFLAGS = $(SIMD_FLAGS)\n\nbin_PROGRAMS = jerasure_01 \\\n               jerasure_02 \\\n               jerasure_03 \\\n               jerasure_04 \\\n               jerasure_05 \\\n               jerasure_06 \\\n               jerasure_07 \\\n               jerasure_08 \\\n               reed_sol_01 \\\n               reed_sol_02 \\\n               reed_sol_03 \\\n               reed_sol_04 \\\n               reed_sol_test_gf \\\n               reed_sol_time_gf \\\n               cauchy_01 \\\n               cauchy_02 \\\n               cauchy_03 \\\n               cauchy_04 \\\n               liberation_01 \\\n               encoder \\\n               decoder\n\ncheck_PROGRAMS = \n\nTESTS=test_all_gfs.sh encode_decode.sh $(check_PROGRAMS)\n\ndist_noinst_SCRIPTS = test_all_gfs.sh time_all_gfs_argv_init.sh\n\ntest_galois_SOURCES = test_galois.c\ncheck_PROGRAMS += test_galois\n\njerasure_01_SOURCES = jerasure_01.c\njerasure_02_SOURCES = jerasure_02.c\njerasure_03_SOURCES = jerasure_03.c\njerasure_04_SOURCES = jerasure_04.c\njerasure_05_SOURCES = jerasure_05.c\njerasure_06_SOURCES = jerasure_06.c\njerasure_07_SOURCES = jerasure_07.c\njerasure_08_SOURCES = jerasure_08.c\n\nreed_sol_01_SOURCES = reed_sol_01.c\nreed_sol_02_SOURCES = reed_sol_02.c\nreed_sol_03_SOURCES = reed_sol_03.c\nreed_sol_04_SOURCES = reed_sol_04.c\n\nreed_sol_test_gf_SOURCES = reed_sol_test_gf.c\nreed_sol_time_gf_SOURCES = reed_sol_time_gf.c\n\ncauchy_01_SOURCES = cauchy_01.c\ncauchy_02_SOURCES = cauchy_02.c\ncauchy_03_SOURCES = cauchy_03.c\ncauchy_04_SOURCES = cauchy_04.c\n\nliberation_01_SOURCES = liberation_01.c\n\ndecoder_SOURCES = decoder.c\nencoder_SOURCES = encoder.c\n\nLDADD = ../src/libJerasure.la\ndecoder_LDADD = $(LDADD) ../src/libtiming.a\nencoder_LDADD = $(LDADD) ../src/libtiming.a\nreed_sol_time_gf_LDADD = $(LDADD) ../src/libtiming.a\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/cauchy_01.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n \n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank\n */\n\n#include <stdio.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"cauchy.h\"\n#include \"jerasure.h\"\n#include \"reed_sol.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: cauchy_01 n w - Converts the value n to a bitmatrix using GF(2^w).\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"       It prints the bitmatrix, and reports on the numberof ones.\\n\");\n  fprintf(stderr, \"       Use 0x to input n in hexadecimal.\\n\");\n  fprintf(stderr, \"       W must be <= 32.\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"This demonstrates: cauchy_n_ones()\\n\");\n  fprintf(stderr, \"                   jerasure_matrix_to_bitmatrix()\\n\");\n  fprintf(stderr, \"                   jerasure_print_bitmatrix()\\n\");\n  if (s != NULL) fprintf(stderr, \"\\n%s\\n\", s);\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  int n;\n  int i, no, w;\n  int *bitmatrix;\n  \n  if (argc != 3) usage(NULL);\n  if (sscanf(argv[1], \"0x%x\", &n) == 0) {\n    if (sscanf(argv[1], \"%d\", &n) == 0) usage(\"Bad n\");\n  }\n  if (sscanf(argv[2], \"%d\", &w) == 0 || w <= 0 || w > 32) usage(\"Bad w\");\n  if (w == 31) {\n    if (n & 0x80000000L) usage(\"Bad n/w combination (n not between 0 and 2^w-1)\\n\");\n  } else if (w < 31) {\n    if (n >= (1 << w)) usage(\"Bad n/w combination (n not between 0 and 2^w-1)\\n\");\n  }\n\n  bitmatrix = jerasure_matrix_to_bitmatrix(1, 1, w, &n);\n  printf(\"<HTML><title>cauchy_01 %u %d</title>\\n\", w, n);\n  printf(\"<HTML><h3>cauchy_01 %u %d</h3>\\n\", w, n);\n  printf(\"<pre>\\n\");\n  if (w == 32) {\n    printf(\"Converted the value 0x%x to the following bitmatrix:\\n\\n\", n);\n  } else {\n    printf(\"Converted the value %d (0x%x) to the following bitmatrix:\\n\\n\", n, n);\n  }\n  jerasure_print_bitmatrix(bitmatrix, w, w, w);\n  printf(\"\\n\");\n\n  no = 0;\n  for (i = 0; i < w*w; i++) no += bitmatrix[i];\n  if (no != cauchy_n_ones(n, w)) { \n    fprintf(stderr, \"Jerasure error: # ones in the bitmatrix (%d) doesn't match cauchy_n_ones() (%d).\\n\",\n       no, cauchy_n_ones(n, w));\n    exit(1);\n  }\n\n  printf(\"# Ones: %d\\n\", cauchy_n_ones(n, w));\n\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/cauchy_02.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n \n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <string.h>\n#include <gf_rand.h>\n#include \"jerasure.h\"\n#include \"cauchy.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: cauchy_02 k m w seed - CRS coding example using Bloemer's original matrix.\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"k+m must be <= 2^w\\n\");\n  fprintf(stderr, \"This sets up a generator matrix (G^T) in GF(2^w) whose last m rows are\\n\");\n  fprintf(stderr, \"created from a Cauchy matrix, using the original definition from [Bloemer95].\\n\");\n  fprintf(stderr, \"It converts this matrix to a bitmatrix, and then it encodes w packets from\\n\");\n  fprintf(stderr, \"each of k disks (simulated) onto w packets on each of m disks.  Packets are \\n\");\n  fprintf(stderr, \"simply longs.  Then, it deletes m random disks, and decodes.  \\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"The encoding and decoding are done twice, first, with jerasure_bitmatrix_encode()\\n\");\n  fprintf(stderr, \"and jerasure_bitmatrix_decode(), and second using 'smart' scheduling with\\n\");\n  fprintf(stderr, \"jerasure_schedule_encode() and jerasure_schedule_decode_lazy().\\n\");\n\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"This demonstrates: cauchy_original_coding_matrix()\\n\");\n  fprintf(stderr, \"                   jerasure_bitmatrix_encode()\\n\");\n  fprintf(stderr, \"                   jerasure_bitmatrix_decode()\\n\");\n  fprintf(stderr, \"                   cauchy_n_ones()\\n\");\n  fprintf(stderr, \"                   jerasure_smart_bitmatrix_to_schedule()\\n\");\n  fprintf(stderr, \"                   jerasure_schedule_encode()\\n\");\n  fprintf(stderr, \"                   jerasure_schedule_decode_lazy()\\n\");\n  fprintf(stderr, \"                   jerasure_print_matrix()\\n\");\n  fprintf(stderr, \"                   jerasure_print_bitmatrix()\\n\");\n  fprintf(stderr, \"                   jerasure_get_stats()\\n\");\n  if (s != NULL) fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\nstatic void print_array(char **ptrs, int ndevices, int size, int packetsize, char *label)\n{\n  int i, j, x;\n  unsigned char *up;\n\n  printf(\"<center><table border=3 cellpadding=3><tr><td></td>\\n\");\n  \n  for (i = 0; i < ndevices; i++) printf(\"<td align=center>%s%x</td>\\n\", label, i);\n  printf(\"</tr>\\n\");\n  printf(\"<td align=right><pre>\");\n  for (j = 0; j < size/packetsize; j++) printf(\"Packet %d\\n\", j);\n  printf(\"</pre></td>\\n\");\n  for (i = 0; i < ndevices; i++) {\n    printf(\"<td><pre>\");\n    up = (unsigned char *) ptrs[i];\n    for (j = 0; j < size/packetsize; j++) {\n      for (x = 0; x < packetsize; x++) {\n        if (x > 0 && x%4 == 0) printf(\" \");\n        printf(\"%02x\", up[j*packetsize+x]);\n      }\n      printf(\"\\n\");\n    }\n    printf(\"</td>\\n\");\n  }\n  printf(\"</tr></table></center>\\n\");\n}\n\nint main(int argc, char **argv)\n{\n  int k, w, i, m;\n  int *matrix, *bitmatrix, **schedule;\n  char **data, **coding, **dcopy, **ccopy;\n  int no;\n  int *erasures, *erased;\n  double mstats[3], sstats[3];\n  uint32_t seed;\n  \n  if (argc != 5) usage(NULL);\n  if (sscanf(argv[1], \"%d\", &k) == 0 || k <= 0) usage(\"Bad k\");\n  if (sscanf(argv[2], \"%d\", &m) == 0 || m <= 0) usage(\"Bad m\");\n  if (sscanf(argv[3], \"%d\", &w) == 0 || w <= 0 || w > 32) usage(\"Bad w\");\n  if (sscanf(argv[4], \"%d\", &seed) == 0) usage(\"Bad seed\");\n  if (w < 30 && (k+m) > (1 << w)) usage(\"k + m is too big\");\n\n  matrix = cauchy_original_coding_matrix(k, m, w);\n  if (matrix == NULL) {\n    usage(\"couldn't make coding matrix\");\n  }\n\n  /* Print out header information to the output file. */\n  printf(\"<HTML>\\n\");\n  printf(\"<TITLE>Jerasure Example Output: cauchy_02 %d %d %d %d</TITLE>\\n\", k, m, w, seed);\n  printf(\"<h2>Jerasure Example Output: cauchy_02 %d %d %d %d</h3>\\n\", k, m, w, seed);\n\n  printf(\"<hr>\\n\");\n  printf(\"Parameters:\\n\");\n  printf(\"<UL><LI> Number of data disks <i>(k)</i>: %d\\n\", k);\n  printf(\"<LI> Number of coding disks <i>(m)</i>: %d\\n\", m);\n  printf(\"<LI> Word size of the Galois Field: <i>(w)</i>: %d\\n\", w);\n  printf(\"<LI> Seed for the random number generator: %d\\n\", seed);\n  printf(\"<LI> Number of bytes stored per disk: %ld\\n\", sizeof(long)*w);\n  printf(\"<LI> Number of packets stored per disk: %d\\n\", w);\n  printf(\"<LI> Number of bytes per packet: %ld\\n\", sizeof(long));\n  printf(\"</UL>\\n\");\n\n  /* Print out the matrix and the bitmatrix */\n  printf(\"<hr>\\n\");\n  printf(\"Here is the matrix, which was created with <b>cauchy_original_coding_matrix()</b>.\\n\");\n  printf(\"This is not the best matrix to use, but we include it to show an example\\n\");\n  printf(\"of <b>cauchy_original_coding_matrix()</b>.  For the best matrix and encoding/decoding\\n\");\n  printf(\"methodology, see <b>cauchy_04.</b><p><pre>\\n\");\n\n  jerasure_print_matrix(matrix, m, k, w);\n  printf(\"</pre>\\n\");\n\n  bitmatrix = jerasure_matrix_to_bitmatrix(k, m, w, matrix);\n\n  no = 0;\n  for (i = 0; i < k*m; i++) {\n    no += cauchy_n_ones(matrix[i], w);\n  }\n\n  printf(\"The bitmatrix, which has %d one%s:<p><pre>\\n\", no, (no == 1) ? \"\" : \"s\");\n  jerasure_print_bitmatrix(bitmatrix, m*w, k*w, w);\n  printf(\"</pre>\\n\");\n  printf(\"<hr>\\n\");\n  MOA_Seed(seed);\n\n  data = talloc(char *, k);\n  dcopy = talloc(char *, k);\n  for (i = 0; i < k; i++) {\n    data[i] = talloc(char, sizeof(long)*w);\n    dcopy[i] = talloc(char, sizeof(long)*w);\n    MOA_Fill_Random_Region(data[i], sizeof(long)*w);\n    memcpy(dcopy[i], data[i], sizeof(long)*w);\n  }\n\n  printf(\"Here are the packets on the data disks:<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n\n  coding = talloc(char *, m);\n  ccopy = talloc(char *, m);\n  for (i = 0; i < m; i++) {\n    coding[i] = talloc(char, sizeof(long)*w);\n    ccopy[i] = talloc(char, sizeof(long)*w);\n  }\n\n  jerasure_bitmatrix_encode(k, m, w, bitmatrix, data, coding, w*sizeof(long), sizeof(long));\n  jerasure_get_stats(mstats);\n\n  schedule = jerasure_smart_bitmatrix_to_schedule(k, m, w, bitmatrix);\n  jerasure_schedule_encode(k, m, w, schedule, data, ccopy, w*sizeof(long), sizeof(long));\n  jerasure_get_stats(sstats);\n\n  printf(\"<p>Encoding with jerasure_bitmatrix_encode() - Bytes XOR'd: %.0lf.<br>\\n\", mstats[0]);\n  printf(\"Encoding with jerasure_schedule_encode() - Bytes XOR'd: %.0lf.<br>\\n\", sstats[0]);\n\n  for (i = 0; i < m; i++) {\n    if (memcmp(coding[i], ccopy[i], sizeof(long)*w) != 0) {\n      printf(\"Problem: the two encodings don't match on disk C%x\\n\", i);\n      exit(0);\n    }\n  }\n\n  printf(\"Here are the packets on the coding disks.<br>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n\n  erasures = talloc(int, (m+1));\n  erased = talloc(int, (k+m));\n  for (i = 0; i < m+k; i++) erased[i] = 0;\n  for (i = 0; i < m; ) {\n    erasures[i] = MOA_Random_W(31, 1)%(k+m);\n    if (erased[erasures[i]] == 0) {\n      erased[erasures[i]] = 1;\n      bzero((erasures[i] < k) ? data[erasures[i]] : coding[erasures[i]-k], sizeof(long)*w);\n      i++;\n    }\n  }\n  erasures[i] = -1;\n  printf(\"Erasures on the following devices:\");\n  for (i = 0; erasures[i] != -1; i++) {\n    printf(\" %c%x\", ((erasures[i] < k) ? 'D' : 'C'), (erasures[i] < k ? erasures[i] : erasures[i]-k));\n  }\n  printf(\"<br>\\nHere is the state of the system:\\n<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n\n  jerasure_bitmatrix_decode(k, m, w, bitmatrix, 0, erasures, data, coding, w*sizeof(long), sizeof(long));\n  jerasure_get_stats(mstats);\n\n  printf(\"<p>Decoded with jerasure_bitmatrix_decode - Bytes XOR'd: %.0lf.<br>\\n\", mstats[0]);\n\n  for (i = 0; i < k; i++) if (memcmp(data[i], dcopy[i], sizeof(long)*w) != 0) {\n    printf(\"ERROR: D%x after decoding does not match its state before decoding!<br>\\n\", i);\n  }\n  for (i = 0; i < m; i++) if (memcmp(coding[i], ccopy[i], sizeof(long)*w) != 0) {\n    printf(\"ERROR: C%x after decoding does not match its state before decoding!<br>\\n\", i);\n  }\n\n  for (i = 0; erasures[i] != -1; i++) {\n    bzero((erasures[i] < k) ? data[erasures[i]] : coding[erasures[i]-k], sizeof(long)*w);\n  }\n\n  jerasure_schedule_decode_lazy(k, m, w, bitmatrix, erasures, data, coding, w*sizeof(long), sizeof(long), 1);\n  jerasure_get_stats(sstats);\n\n  printf(\"jerasure_schedule_decode_lazy - Bytes XOR'd: %.0lf.<br>\\n\", sstats[0]);\n\n  for (i = 0; i < k; i++) if (memcmp(data[i], dcopy[i], sizeof(long)*w) != 0) {\n    printf(\"ERROR: D%x after decoding does not match its state before decoding!<br>\\n\", i);\n  }\n  for (i = 0; i < m; i++) if (memcmp(coding[i], ccopy[i], sizeof(long)*w) != 0) {\n    printf(\"ERROR: C%x after decoding does not match its state before decoding!<br>\\n\", i);\n  }\n\n  printf(\"Here is the state of the system:\\n<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/cauchy_03.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n \n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <string.h>\n#include <gf_rand.h>\n#include \"jerasure.h\"\n#include \"cauchy.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: cauchy_03 k m w seed - CRS coding example improving the matrix.\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"k+m must be <= 2^w\\n\");\n  fprintf(stderr, \"This sets up a generator matrix (G^T) in GF(2^w) whose last m rows are\\n\");\n  fprintf(stderr, \"created from a Cauchy matrix, using the original definition from [Bloemer95].\\n\");\n  fprintf(stderr, \"This is done with cauchy_xy_coding_matrix().\\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"It then it improves the matrix, which yields a different bitmatrix that is\\n\");\n  fprintf(stderr, \"MDS like the original bitmatrix, but it will yield a bitmatrix with fewer ones.\\n\");\n  fprintf(stderr, \"Then, it encodes w packets from each of k disks (simulated) onto w packets on\\n\");\n  fprintf(stderr, \"on each of m disks.  Packets are longs.  Then, it deletes m random disks, and decodes.\\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"The encoding and decoding are done twice, first, with jerasure_bitmatrix_encode()\\n\");\n  fprintf(stderr, \"and jerasure_bitmatrix_decode(), and second using 'smart' scheduling with\\n\");\n  fprintf(stderr, \"jerasure_schedule_encode() and jerasure_schedule_decode_lazy().\\n\");\n\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"This demonstrates: cauchy_xy_coding_matrix()\\n\");\n  fprintf(stderr, \"                   cauchy_improve_coding_matrix()\\n\");\n  fprintf(stderr, \"                   jerasure_bitmatrix_encode()\\n\");\n  fprintf(stderr, \"                   jerasure_bitmatrix_decode()\\n\");\n  fprintf(stderr, \"                   cauchy_n_ones()\\n\");\n  fprintf(stderr, \"                   jerasure_smart_bitmatrix_to_schedule()\\n\");\n  fprintf(stderr, \"                   jerasure_schedule_encode()\\n\");\n  fprintf(stderr, \"                   jerasure_schedule_decode_lazy()\\n\");\n  fprintf(stderr, \"                   jerasure_print_matrix()\\n\");\n  fprintf(stderr, \"                   jerasure_print_bitmatrix()\\n\");\n  fprintf(stderr, \"                   jerasure_get_stats()\\n\");\n  if (s != NULL) fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\nstatic void print_array(char **ptrs, int ndevices, int size, int packetsize, char *label)\n{\n  int i, j, x;\n  unsigned char *up;\n\n  printf(\"<center><table border=3 cellpadding=3><tr><td></td>\\n\");\n  \n  for (i = 0; i < ndevices; i++) printf(\"<td align=center>%s%x</td>\\n\", label, i);\n  printf(\"</tr>\\n\");\n  printf(\"<td align=right><pre>\");\n  for (j = 0; j < size/packetsize; j++) printf(\"Packet %d\\n\", j);\n  printf(\"</pre></td>\\n\");\n  for (i = 0; i < ndevices; i++) {\n    printf(\"<td><pre>\");\n    up = (unsigned char *) ptrs[i];\n    for (j = 0; j < size/packetsize; j++) {\n      for (x = 0; x < packetsize; x++) {\n        if (x > 0 && x%4 == 0) printf(\" \");\n        printf(\"%02x\", up[j*packetsize+x]);\n      }\n      printf(\"\\n\");\n    }\n    printf(\"</td>\\n\");\n  }\n  printf(\"</tr></table></center>\\n\");\n}\n\nint main(int argc, char **argv)\n{\n  int k, w, i, m;\n  int *matrix, *bitmatrix, **schedule;\n  char **data, **coding, **dcopy, **ccopy;\n  int no;\n  int *erasures, *erased;\n  double mstats[3], sstats[3];\n  uint32_t seed;\n  int *X, *Y;\n  \n  if (argc != 5) usage(NULL);\n  if (sscanf(argv[1], \"%d\", &k) == 0 || k <= 0) usage(\"Bad k\");\n  if (sscanf(argv[2], \"%d\", &m) == 0 || m <= 0) usage(\"Bad m\");\n  if (sscanf(argv[3], \"%d\", &w) == 0 || w <= 0 || w > 32) usage(\"Bad w\");\n  if (sscanf(argv[4], \"%d\", &seed) == 0) usage(\"Bad seed\");\n  if (w < 30 && (k+m) > (1 << w)) usage(\"k + m is too big\");\n\n  X = talloc(int, m);\n  Y = talloc(int, k);\n  for (i = 0; i < m; i++) X[i] = i;\n  for (i = 0; i < k; i++) Y[i] = m+i;\n\n  matrix = cauchy_xy_coding_matrix(k, m, w, X, Y);\n  if (matrix == NULL) {\n    usage(\"couldn't make coding matrix\");\n  }\n\n  /* Print out header information to the output file. */\n  printf(\"<HTML>\\n\");\n  printf(\"<TITLE>Jerasure Example Output: cauchy_03 %d %d %d %d</TITLE>\\n\", k, m, w, seed);\n  printf(\"<h2>Jerasure Example Output: cauchy_03 %d %d %d %d</h3>\\n\", k, m, w, seed);\n\n  printf(\"<hr>\\n\");\n  printf(\"Parameters:\\n\");\n  printf(\"<UL><LI> Number of data disks <i>(k)</i>: %d\\n\", k);\n  printf(\"<LI> Number of coding disks <i>(m)</i>: %d\\n\", m);\n  printf(\"<LI> Word size of the Galois Field: <i>(w)</i>: %d\\n\", w);\n  printf(\"<LI> Seed for the random number generator: %d\\n\", seed);\n  printf(\"<LI> Number of bytes stored per disk: %ld\\n\", sizeof(long)*w);\n  printf(\"<LI> Number of packets stored per disk: %d\\n\", w);\n  printf(\"<LI> Number of bytes per packet: %ld\\n\", sizeof(long));\n  printf(\"</UL>\\n\");\n\n  /* Print out the matrix and the bitmatrix */\n  printf(\"<hr>\\n\");\n  printf(\"Here is the matrix, which was created with <b>cauchy_xy_coding_matrix()</b>.\\n\");\n\n  printf(\"<pre>\\n\");\n  jerasure_print_matrix(matrix, m, k, w);\n  printf(\"</pre>\\n\");\n\n  cauchy_improve_coding_matrix(k, m, w, matrix);\n\n  printf(\"<hr>\\n\");\n  printf(\"Here is the matrix improved with <b>cauchy_improve_coding_matrix()</b>.\\n\");\n\n  printf(\"<pre>\\n\");\n  jerasure_print_matrix(matrix, m, k, w);\n  printf(\"</pre>\\n\");\n\n  bitmatrix = jerasure_matrix_to_bitmatrix(k, m, w, matrix);\n\n  no = 0;\n  for (i = 0; i < k*m; i++) {\n    no += cauchy_n_ones(matrix[i], w);\n  }\n\n  printf(\"The bitmatrix, which has %d one%s:<p><pre>\\n\", no, (no == 1) ? \"\" : \"s\");\n  jerasure_print_bitmatrix(bitmatrix, m*w, k*w, w);\n  printf(\"</pre>\\n\");\n  printf(\"<hr>\\n\");\n\n  MOA_Seed(seed);\n\n  data = talloc(char *, k);\n  dcopy = talloc(char *, k);\n  for (i = 0; i < k; i++) {\n    data[i] = talloc(char, sizeof(long)*w);\n    dcopy[i] = talloc(char, sizeof(long)*w);\n    MOA_Fill_Random_Region(data[i], sizeof(long)*w);\n    memcpy(dcopy[i], data[i], sizeof(long)*w);\n  }\n\n  printf(\"Here are the packets on the data disks:<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n\n  coding = talloc(char *, m);\n  ccopy = talloc(char *, m);\n  for (i = 0; i < m; i++) {\n    coding[i] = talloc(char, sizeof(long)*w);\n    ccopy[i] = talloc(char, sizeof(long)*w);\n  }\n\n  jerasure_bitmatrix_encode(k, m, w, bitmatrix, data, coding, w*sizeof(long), sizeof(long));\n  jerasure_get_stats(mstats);\n\n  schedule = jerasure_smart_bitmatrix_to_schedule(k, m, w, bitmatrix);\n  jerasure_schedule_encode(k, m, w, schedule, data, ccopy, w*sizeof(long), sizeof(long));\n  jerasure_get_stats(sstats);\n\n  printf(\"<p>Encoding with jerasure_bitmatrix_encode() - Bytes XOR'd: %.0lf.<br>\\n\", mstats[0]);\n  printf(\"Encoding with jerasure_schedule_encode() - Bytes XOR'd: %.0lf.<br>\\n\", sstats[0]);\n\n  for (i = 0; i < m; i++) {\n    if (memcmp(coding[i], ccopy[i], sizeof(long)*w) != 0) {\n      printf(\"Problem: the two encodings don't match on disk C%x\\n\", i);\n      exit(0);\n    }\n  }\n\n  printf(\"Here are the packets on the coding disks.<br>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n\n  erasures = talloc(int, (m+1));\n  erased = talloc(int, (k+m));\n  for (i = 0; i < m+k; i++) erased[i] = 0;\n  for (i = 0; i < m; ) {\n    erasures[i] = MOA_Random_W(31, 1)%(k+m);\n    if (erased[erasures[i]] == 0) {\n      erased[erasures[i]] = 1;\n      bzero((erasures[i] < k) ? data[erasures[i]] : coding[erasures[i]-k], sizeof(long)*w);\n      i++;\n    }\n  }\n  erasures[i] = -1;\n  printf(\"Erasures on the following devices:\");\n  for (i = 0; erasures[i] != -1; i++) {\n    printf(\" %c%x\", ((erasures[i] < k) ? 'D' : 'C'), (erasures[i] < k ? erasures[i] : erasures[i]-k));\n  }\n  printf(\"<br>\\nHere is the state of the system:\\n<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n\n  jerasure_bitmatrix_decode(k, m, w, bitmatrix, 0, erasures, data, coding, w*sizeof(long), sizeof(long));\n  jerasure_get_stats(mstats);\n\n  printf(\"<p>Decoded with jerasure_bitmatrix_decode - Bytes XOR'd: %.0lf.<br>\\n\", mstats[0]);\n\n  for (i = 0; i < k; i++) if (memcmp(data[i], dcopy[i], sizeof(long)*w) != 0) {\n    printf(\"ERROR: D%x after decoding does not match its state before decoding!<br>\\n\", i);\n  }\n  for (i = 0; i < m; i++) if (memcmp(coding[i], ccopy[i], sizeof(long)*w) != 0) {\n    printf(\"ERROR: C%x after decoding does not match its state before decoding!<br>\\n\", i);\n  }\n\n  for (i = 0; erasures[i] != -1; i++) {\n    bzero((erasures[i] < k) ? data[erasures[i]] : coding[erasures[i]-k], sizeof(long)*w);\n  }\n\n  jerasure_schedule_decode_lazy(k, m, w, bitmatrix, erasures, data, coding, w*sizeof(long), sizeof(long), 1);\n  jerasure_get_stats(sstats);\n\n  printf(\"jerasure_schedule_decode_lazy - Bytes XOR'd: %.0lf.<br>\\n\", sstats[0]);\n\n  for (i = 0; i < k; i++) if (memcmp(data[i], dcopy[i], sizeof(long)*w) != 0) {\n    printf(\"ERROR: D%x after decoding does not match its state before decoding!<br>\\n\", i);\n  }\n  for (i = 0; i < m; i++) if (memcmp(coding[i], ccopy[i], sizeof(long)*w) != 0) {\n    printf(\"ERROR: C%x after decoding does not match its state before decoding!<br>\\n\", i);\n  }\n\n  printf(\"Here is the state of the system:\\n<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/cauchy_04.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n \n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <string.h>\n#include <gf_rand.h>\n#include \"jerasure.h\"\n#include \"cauchy.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: cauchy_04 k m w seed - CRS coding example improving the matrix.\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"k+m must be <= 2^w\\n\");\n  fprintf(stderr, \"This sets up a generator matrix (G^T) in GF(2^w) whose last m rows are\\n\");\n  fprintf(stderr, \"a 'good' matrix, created with cauchy_good_general_coding_matrix().\\n\");\n  fprintf(stderr, \"It converts this matrix to a bitmatrix.\\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"Then, it encodes w packets from each of k disks (simulated) onto w packets on\\n\");\n  fprintf(stderr, \"on each of m disks.  Packets are longs.  Then, it deletes m random disks, and decodes.\\n\");\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"The encoding and decoding are done twice, first, with jerasure_bitmatrix_encode()\\n\");\n  fprintf(stderr, \"and jerasure_bitmatrix_decode(), and second using 'smart' scheduling with\\n\");\n  fprintf(stderr, \"jerasure_schedule_encode() and jerasure_schedule_decode_lazy().\\n\");\n\n  fprintf(stderr, \"\\n\");\n  fprintf(stderr, \"This demonstrates: cauchy_good_general_coding_matrix()\\n\");\n  fprintf(stderr, \"                   jerasure_bitmatrix_encode()\\n\");\n  fprintf(stderr, \"                   jerasure_bitmatrix_decode()\\n\");\n  fprintf(stderr, \"                   cauchy_n_ones()\\n\");\n  fprintf(stderr, \"                   jerasure_smart_bitmatrix_to_schedule()\\n\");\n  fprintf(stderr, \"                   jerasure_schedule_encode()\\n\");\n  fprintf(stderr, \"                   jerasure_schedule_decode_lazy()\\n\");\n  fprintf(stderr, \"                   jerasure_print_matrix()\\n\");\n  fprintf(stderr, \"                   jerasure_print_bitmatrix()\\n\");\n  fprintf(stderr, \"                   jerasure_get_stats()\\n\");\n  if (s != NULL) fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\nstatic void print_array(char **ptrs, int ndevices, int size, int packetsize, char *label)\n{\n  int i, j, x;\n  unsigned char *up;\n\n  printf(\"<center><table border=3 cellpadding=3><tr><td></td>\\n\");\n  \n  for (i = 0; i < ndevices; i++) printf(\"<td align=center>%s%x</td>\\n\", label, i);\n  printf(\"</tr>\\n\");\n  printf(\"<td align=right><pre>\");\n  for (j = 0; j < size/packetsize; j++) printf(\"Packet %d\\n\", j);\n  printf(\"</pre></td>\\n\");\n  for (i = 0; i < ndevices; i++) {\n    printf(\"<td><pre>\");\n    up = (unsigned char *) ptrs[i];\n    for (j = 0; j < size/packetsize; j++) {\n      for (x = 0; x < packetsize; x++) {\n        if (x > 0 && x%4 == 0) printf(\" \");\n        printf(\"%02x\", up[j*packetsize+x]);\n      }\n      printf(\"\\n\");\n    }\n    printf(\"</td>\\n\");\n  }\n  printf(\"</tr></table></center>\\n\");\n}\n\nint main(int argc, char **argv)\n{\n  int k, w, i, m;\n  int *matrix, *bitmatrix, **schedule;\n  char **data, **coding, **dcopy, **ccopy;\n  int no;\n  int *erasures, *erased;\n  double mstats[3], sstats[3];\n  uint32_t seed;\n  \n  if (argc != 5) usage(NULL);\n  if (sscanf(argv[1], \"%d\", &k) == 0 || k <= 0) usage(\"Bad k\");\n  if (sscanf(argv[2], \"%d\", &m) == 0 || m <= 0) usage(\"Bad m\");\n  if (sscanf(argv[3], \"%d\", &w) == 0 || w <= 0 || w > 32) usage(\"Bad w\");\n  if (sscanf(argv[4], \"%d\", &seed) == 0) usage(\"Bad seed\");\n  if (w < 30 && (k+m) > (1 << w)) usage(\"k + m is too big\");\n\n  matrix = cauchy_good_general_coding_matrix(k, m, w);\n  if (matrix == NULL) {\n    usage(\"couldn't make coding matrix\");\n  }\n\n  /* Print out header information to the output file. */\n  printf(\"<HTML>\\n\");\n  printf(\"<TITLE>Jerasure Example Output: cauchy_04 %d %d %d %d</TITLE>\\n\", k, m, w, seed);\n  printf(\"<h2>Jerasure Example Output: cauchy_04 %d %d %d %d</h3>\\n\", k, m, w, seed);\n\n  printf(\"<hr>\\n\");\n  printf(\"Parameters:\\n\");\n  printf(\"<UL><LI> Number of data disks <i>(k)</i>: %d\\n\", k);\n  printf(\"<LI> Number of coding disks <i>(m)</i>: %d\\n\", m);\n  printf(\"<LI> Word size of the Galois Field: <i>(w)</i>: %d\\n\", w);\n  printf(\"<LI> Seed for the random number generator: %d\\n\", seed);\n  printf(\"<LI> Number of bytes stored per disk: %ld\\n\", sizeof(long)*w);\n  printf(\"<LI> Number of packets stored per disk: %d\\n\", w);\n  printf(\"<LI> Number of bytes per packet: %ld\\n\", sizeof(long));\n  printf(\"</UL>\\n\");\n\n  /* Print out the matrix and the bitmatrix */\n  printf(\"<hr>\\n\");\n  printf(\"Here is the matrix, which was created with <b>cauchy_good_general_coding_matrix()</b>.\\n\");\n\n  printf(\"<pre>\\n\");\n  jerasure_print_matrix(matrix, m, k, w);\n  printf(\"</pre>\\n\");\n\n  bitmatrix = jerasure_matrix_to_bitmatrix(k, m, w, matrix);\n\n  no = 0;\n  for (i = 0; i < k*m; i++) {\n    no += cauchy_n_ones(matrix[i], w);\n  }\n\n  printf(\"The bitmatrix, which has %d one%s:<p><pre>\\n\", no, (no == 1) ? \"\" : \"s\");\n  jerasure_print_bitmatrix(bitmatrix, m*w, k*w, w);\n  printf(\"</pre>\\n\");\n  printf(\"<hr>\\n\");\n\n  MOA_Seed(seed);\n\n  data = talloc(char *, k);\n  dcopy = talloc(char *, k);\n  for (i = 0; i < k; i++) {\n    data[i] = talloc(char, sizeof(long)*w);\n    dcopy[i] = talloc(char, sizeof(long)*w);\n    MOA_Fill_Random_Region(data[i], sizeof(long)*w);\n    memcpy(dcopy[i], data[i], sizeof(long)*w);\n  }\n\n  printf(\"Here are the packets on the data disks:<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n\n  coding = talloc(char *, m);\n  ccopy = talloc(char *, m);\n  for (i = 0; i < m; i++) {\n    coding[i] = talloc(char, sizeof(long)*w);\n    ccopy[i] = talloc(char, sizeof(long)*w);\n  }\n\n  jerasure_bitmatrix_encode(k, m, w, bitmatrix, data, coding, w*sizeof(long), sizeof(long));\n  jerasure_get_stats(mstats);\n\n  schedule = jerasure_smart_bitmatrix_to_schedule(k, m, w, bitmatrix);\n  jerasure_schedule_encode(k, m, w, schedule, data, ccopy, w*sizeof(long), sizeof(long));\n  jerasure_get_stats(sstats);\n\n  printf(\"<p>Encoding with jerasure_bitmatrix_encode() - Bytes XOR'd: %.0lf.<br>\\n\", mstats[0]);\n  printf(\"Encoding with jerasure_schedule_encode() - Bytes XOR'd: %.0lf.<br>\\n\", sstats[0]);\n\n  for (i = 0; i < m; i++) {\n    if (memcmp(coding[i], ccopy[i], sizeof(long)*w) != 0) {\n      printf(\"Problem: the two encodings don't match on disk C%x\\n\", i);\n      exit(0);\n    }\n  }\n\n  printf(\"Here are the packets on the coding disks.<br>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n\n  erasures = talloc(int, (m+1));\n  erased = talloc(int, (k+m));\n  for (i = 0; i < m+k; i++) erased[i] = 0;\n  for (i = 0; i < m; ) {\n    erasures[i] = MOA_Random_W(31, 1)%(k+m);\n    if (erased[erasures[i]] == 0) {\n      erased[erasures[i]] = 1;\n      bzero((erasures[i] < k) ? data[erasures[i]] : coding[erasures[i]-k], sizeof(long)*w);\n      i++;\n    }\n  }\n  erasures[i] = -1;\n  printf(\"Erasures on the following devices:\");\n  for (i = 0; erasures[i] != -1; i++) {\n    printf(\" %c%x\", ((erasures[i] < k) ? 'D' : 'C'), (erasures[i] < k ? erasures[i] : erasures[i]-k));\n  }\n  printf(\"<br>\\nHere is the state of the system:\\n<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n\n  jerasure_bitmatrix_decode(k, m, w, bitmatrix, 0, erasures, data, coding, w*sizeof(long), sizeof(long));\n  jerasure_get_stats(mstats);\n\n  printf(\"<p>Decoded with jerasure_bitmatrix_decode - Bytes XOR'd: %.0lf.<br>\\n\", mstats[0]);\n\n  for (i = 0; i < k; i++) if (memcmp(data[i], dcopy[i], sizeof(long)*w) != 0) {\n    printf(\"ERROR: D%x after decoding does not match its state before decoding!<br>\\n\", i);\n  }\n  for (i = 0; i < m; i++) if (memcmp(coding[i], ccopy[i], sizeof(long)*w) != 0) {\n    printf(\"ERROR: C%x after decoding does not match its state before decoding!<br>\\n\", i);\n  }\n\n  for (i = 0; erasures[i] != -1; i++) {\n    bzero((erasures[i] < k) ? data[erasures[i]] : coding[erasures[i]-k], sizeof(long)*w);\n  }\n\n  jerasure_schedule_decode_lazy(k, m, w, bitmatrix, erasures, data, coding, w*sizeof(long), sizeof(long), 1);\n  jerasure_get_stats(sstats);\n\n  printf(\"jerasure_schedule_decode_lazy - Bytes XOR'd: %.0lf.<br>\\n\", sstats[0]);\n\n  for (i = 0; i < k; i++) if (memcmp(data[i], dcopy[i], sizeof(long)*w) != 0) {\n    printf(\"ERROR: D%x after decoding does not match its state before decoding!<br>\\n\", i);\n  }\n  for (i = 0; i < m; i++) if (memcmp(coding[i], ccopy[i], sizeof(long)*w) != 0) {\n    printf(\"ERROR: C%x after decoding does not match its state before decoding!<br>\\n\", i);\n  }\n\n  printf(\"Here is the state of the system:\\n<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/decoder.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n/* \nThis program takes as input an inputfile, k, m, a coding\ntechnique, w, and packetsize.  It is the companion program\nof encoder.c, which creates k+m files.  This program assumes \nthat up to m erasures have occurred in the k+m files.  It\nreads in the k+m files or marks the file as erased. It then\nrecreates the original file and creates a new file with the\nsuffix \"decoded\" with the decoded contents of the file.\n\nThis program does not error check command line arguments because \nit is assumed that encoder.c has been called previously with the\nsame arguments, and encoder.c does error check.\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <time.h>\n#include <assert.h>\n#include <unistd.h>\n#include <sys/time.h>\n#include <sys/stat.h>\n#include <signal.h>\n#include <unistd.h>\n#include \"jerasure.h\"\n#include \"reed_sol.h\"\n#include \"galois.h\"\n#include \"cauchy.h\"\n#include \"liberation.h\"\n#include \"timing.h\"\n\n#define N 10\n\nenum Coding_Technique {Reed_Sol_Van, Reed_Sol_R6_Op, Cauchy_Orig, Cauchy_Good, Liberation, Blaum_Roth, Liber8tion, RDP, EVENODD, No_Coding};\n\nchar *Methods[N] = {\"reed_sol_van\", \"reed_sol_r6_op\", \"cauchy_orig\", \"cauchy_good\", \"liberation\", \"blaum_roth\", \"liber8tion\", \"rdp\", \"evenodd\", \"no_coding\"};\n\n/* Global variables for signal handler */\nenum Coding_Technique method;\nint readins, n;\n\n/* Function prototype */\nvoid ctrl_bs_handler(int dummy);\n\nint main (int argc, char **argv) {\n\tFILE *fp;\t\t\t\t// File pointer\n\n\t/* Jerasure arguments */\n\tchar **data;\n\tchar **coding;\n\tint *erasures;\n\tint *erased;\n\tint *matrix;\n\tint *bitmatrix;\n\t\n\t/* Parameters */\n\tint k, m, w, packetsize, buffersize;\n\tint tech;\n\tchar *c_tech;\n\t\n\tint i, j;\t\t\t\t// loop control variable, s\n\tint blocksize = 0;\t\t\t// size of individual files\n\tint origsize;\t\t\t// size of file before padding\n\tint total;\t\t\t\t// used to write data, not padding to file\n\tstruct stat status;\t\t// used to find size of individual files\n\tint numerased;\t\t\t// number of erased files\n\t\t\n\t/* Used to recreate file names */\n\tchar *temp;\n\tchar *cs1, *cs2, *extension;\n\tchar *fname;\n\tint md;\n\tchar *curdir;\n\n\t/* Used to time decoding */\n\tstruct timing t1, t2, t3, t4;\n\tdouble tsec;\n\tdouble totalsec;\n\n\t\n\tsignal(SIGQUIT, ctrl_bs_handler);\n\n\tmatrix = NULL;\n\tbitmatrix = NULL;\n\ttotalsec = 0.0;\n\t\n\t/* Start timing */\n\ttiming_set(&t1);\n\n\t/* Error checking parameters */\n\tif (argc != 2) {\n\t\tfprintf(stderr, \"usage: inputfile\\n\");\n\t\texit(0);\n\t}\n\tcurdir = (char *)malloc(sizeof(char)*1000);\n\tassert(curdir == getcwd(curdir, 1000));\n\t\n\t/* Begin recreation of file names */\n\tcs1 = (char*)malloc(sizeof(char)*strlen(argv[1]));\n\tcs2 = strrchr(argv[1], '/');\n\tif (cs2 != NULL) {\n\t\tcs2++;\n\t\tstrcpy(cs1, cs2);\n\t}\n\telse {\n\t\tstrcpy(cs1, argv[1]);\n\t}\n\tcs2 = strchr(cs1, '.');\n\tif (cs2 != NULL) {\n                extension = strdup(cs2);\n\t\t*cs2 = '\\0';\n\t} else {\n           extension = strdup(\"\");\n        }\t\n\tfname = (char *)malloc(sizeof(char*)*(100+strlen(argv[1])+20));\n\n\t/* Read in parameters from metadata file */\n\tsprintf(fname, \"%s/Coding/%s_meta.txt\", curdir, cs1);\n\n\tfp = fopen(fname, \"rb\");\n        if (fp == NULL) {\n          fprintf(stderr, \"Error: no metadata file %s\\n\", fname);\n          exit(1);\n        }\n\ttemp = (char *)malloc(sizeof(char)*(strlen(argv[1])+20));\n\tif (fscanf(fp, \"%s\", temp) != 1) {\n\t\tfprintf(stderr, \"Metadata file - bad format\\n\");\n\t\texit(0);\n\t}\n\t\n\tif (fscanf(fp, \"%d\", &origsize) != 1) {\n\t\tfprintf(stderr, \"Original size is not valid\\n\");\n\t\texit(0);\n\t}\n\tif (fscanf(fp, \"%d %d %d %d %d\", &k, &m, &w, &packetsize, &buffersize) != 5) {\n\t\tfprintf(stderr, \"Parameters are not correct\\n\");\n\t\texit(0);\n\t}\n\tc_tech = (char *)malloc(sizeof(char)*(strlen(argv[1])+20));\n\tif (fscanf(fp, \"%s\", c_tech) != 1) {\n\t\tfprintf(stderr, \"Metadata file - bad format\\n\");\n\t\texit(0);\n\t}\n\tif (fscanf(fp, \"%d\", &tech) != 1) {\n\t\tfprintf(stderr, \"Metadata file - bad format\\n\");\n\t\texit(0);\n\t}\n\tmethod = tech;\n\tif (fscanf(fp, \"%d\", &readins) != 1) {\n\t\tfprintf(stderr, \"Metadata file - bad format\\n\");\n\t\texit(0);\n\t}\n\tfclose(fp);\t\n\n\t/* Allocate memory */\n\terased = (int *)malloc(sizeof(int)*(k+m));\n\tfor (i = 0; i < k+m; i++)\n\t\terased[i] = 0;\n\terasures = (int *)malloc(sizeof(int)*(k+m));\n\n\tdata = (char **)malloc(sizeof(char *)*k);\n\tcoding = (char **)malloc(sizeof(char *)*m);\n\tif (buffersize != origsize) {\n\t\tfor (i = 0; i < k; i++) {\n\t\t\tdata[i] = (char *)malloc(sizeof(char)*(buffersize/k));\n\t\t}\n\t\tfor (i = 0; i < m; i++) {\n\t\t\tcoding[i] = (char *)malloc(sizeof(char)*(buffersize/k));\n\t\t}\n\t\tblocksize = buffersize/k;\n\t}\n\n\tsprintf(temp, \"%d\", k);\n\tmd = strlen(temp);\n\ttiming_set(&t3);\n\n\t/* Create coding matrix or bitmatrix */\n\tswitch(tech) {\n\t\tcase No_Coding:\n\t\t\tbreak;\n\t\tcase Reed_Sol_Van:\n\t\t\tmatrix = reed_sol_vandermonde_coding_matrix(k, m, w);\n\t\t\tbreak;\n\t\tcase Reed_Sol_R6_Op:\n\t\t\tmatrix = reed_sol_r6_coding_matrix(k, w);\n\t\t\tbreak;\n\t\tcase Cauchy_Orig:\n\t\t\tmatrix = cauchy_original_coding_matrix(k, m, w);\n\t\t\tbitmatrix = jerasure_matrix_to_bitmatrix(k, m, w, matrix);\n\t\t\tbreak;\n\t\tcase Cauchy_Good:\n\t\t\tmatrix = cauchy_good_general_coding_matrix(k, m, w);\n\t\t\tbitmatrix = jerasure_matrix_to_bitmatrix(k, m, w, matrix);\n\t\t\tbreak;\n\t\tcase Liberation:\n\t\t\tbitmatrix = liberation_coding_bitmatrix(k, w);\n\t\t\tbreak;\n\t\tcase Blaum_Roth:\n\t\t\tbitmatrix = blaum_roth_coding_bitmatrix(k, w);\n\t\t\tbreak;\n\t\tcase Liber8tion:\n\t\t\tbitmatrix = liber8tion_coding_bitmatrix(k);\n\t}\n\ttiming_set(&t4);\n\ttotalsec += timing_delta(&t3, &t4);\n\t\n\t/* Begin decoding process */\n\ttotal = 0;\n\tn = 1;\t\n\twhile (n <= readins) {\n\t\tnumerased = 0;\n\t\t/* Open files, check for erasures, read in data/coding */\t\n\t\tfor (i = 1; i <= k; i++) {\n\t\t\tsprintf(fname, \"%s/Coding/%s_k%0*d%s\", curdir, cs1, md, i, extension);\n\t\t\tfp = fopen(fname, \"rb\");\n\t\t\tif (fp == NULL) {\n\t\t\t\terased[i-1] = 1;\n\t\t\t\terasures[numerased] = i-1;\n\t\t\t\tnumerased++;\n\t\t\t\t//printf(\"%s failed\\n\", fname);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (buffersize == origsize) {\n\t\t\t\t\tstat(fname, &status);\n\t\t\t\t\tblocksize = status.st_size;\n\t\t\t\t\tdata[i-1] = (char *)malloc(sizeof(char)*blocksize);\n\t\t\t\t\tassert(blocksize == fread(data[i-1], sizeof(char), blocksize, fp));\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfseek(fp, blocksize*(n-1), SEEK_SET); \n\t\t\t\t\tassert(buffersize/k == fread(data[i-1], sizeof(char), buffersize/k, fp));\n\t\t\t\t}\n\t\t\t\tfclose(fp);\n\t\t\t}\n\t\t}\n\t\tfor (i = 1; i <= m; i++) {\n\t\t\tsprintf(fname, \"%s/Coding/%s_m%0*d%s\", curdir, cs1, md, i, extension);\n\t\t\t\tfp = fopen(fname, \"rb\");\n\t\t\tif (fp == NULL) {\n\t\t\t\terased[k+(i-1)] = 1;\n\t\t\t\terasures[numerased] = k+i-1;\n\t\t\t\tnumerased++;\n\t\t\t\t//printf(\"%s failed\\n\", fname);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (buffersize == origsize) {\n\t\t\t\t\tstat(fname, &status);\n\t\t\t\t\tblocksize = status.st_size;\n\t\t\t\t\tcoding[i-1] = (char *)malloc(sizeof(char)*blocksize);\n\t\t\t\t\tassert(blocksize == fread(coding[i-1], sizeof(char), blocksize, fp));\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfseek(fp, blocksize*(n-1), SEEK_SET);\n\t\t\t\t\tassert(blocksize == fread(coding[i-1], sizeof(char), blocksize, fp));\n\t\t\t\t}\t\n\t\t\t\tfclose(fp);\n\t\t\t}\n\t\t}\n\t\t/* Finish allocating data/coding if needed */\n\t\tif (n == 1) {\n\t\t\tfor (i = 0; i < numerased; i++) {\n\t\t\t\tif (erasures[i] < k) {\n\t\t\t\t\tdata[erasures[i]] = (char *)malloc(sizeof(char)*blocksize);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tcoding[erasures[i]-k] = (char *)malloc(sizeof(char)*blocksize);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\terasures[numerased] = -1;\n\t\ttiming_set(&t3);\n\t\n\t\t/* Choose proper decoding method */\n\t\tif (tech == Reed_Sol_Van || tech == Reed_Sol_R6_Op) {\n\t\t\ti = jerasure_matrix_decode(k, m, w, matrix, 1, erasures, data, coding, blocksize);\n\t\t}\n\t\telse if (tech == Cauchy_Orig || tech == Cauchy_Good || tech == Liberation || tech == Blaum_Roth || tech == Liber8tion) {\n\t\t\ti = jerasure_schedule_decode_lazy(k, m, w, bitmatrix, erasures, data, coding, blocksize, packetsize, 1);\n\t\t}\n\t\telse {\n\t\t\tfprintf(stderr, \"Not a valid coding technique.\\n\");\n\t\t\texit(0);\n\t\t}\n\t\ttiming_set(&t4);\n\t\n\t\t/* Exit if decoding was unsuccessful */\n\t\tif (i == -1) {\n\t\t\tfprintf(stderr, \"Unsuccessful!\\n\");\n\t\t\texit(0);\n\t\t}\n\t\n\t\t/* Create decoded file */\n\t\tsprintf(fname, \"%s/Coding/%s_decoded%s\", curdir, cs1, extension);\n\t\tif (n == 1) {\n\t\t\tfp = fopen(fname, \"wb\");\n\t\t}\n\t\telse {\n\t\t\tfp = fopen(fname, \"ab\");\n\t\t}\n\t\tfor (i = 0; i < k; i++) {\n\t\t\tif (total+blocksize <= origsize) {\n\t\t\t\tfwrite(data[i], sizeof(char), blocksize, fp);\n\t\t\t\ttotal+= blocksize;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfor (j = 0; j < blocksize; j++) {\n\t\t\t\t\tif (total < origsize) {\n\t\t\t\t\t\tfprintf(fp, \"%c\", data[i][j]);\n\t\t\t\t\t\ttotal++;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tn++;\n\t\tfclose(fp);\n\t\ttotalsec += timing_delta(&t3, &t4);\n\t}\n\t\n\t/* Free allocated memory */\n\tfree(cs1);\n\tfree(extension);\n\tfree(fname);\n\tfree(data);\n\tfree(coding);\n\tfree(erasures);\n\tfree(erased);\n\t\n\t/* Stop timing and print time */\n\ttiming_set(&t2);\n\ttsec = timing_delta(&t1, &t2);\n\tprintf(\"Decoding (MB/sec): %0.10f\\n\", (((double) origsize)/1024.0/1024.0)/totalsec);\n\tprintf(\"De_Total (MB/sec): %0.10f\\n\\n\", (((double) origsize)/1024.0/1024.0)/tsec);\n\n\treturn 0;\n}\t\n\nvoid ctrl_bs_handler(int dummy) {\n\ttime_t mytime;\n\tmytime = time(0);\n\tfprintf(stderr, \"\\n%s\\n\", ctime(&mytime));\n\tfprintf(stderr, \"You just typed ctrl-\\\\ in decoder.c\\n\");\n\tfprintf(stderr, \"Total number of read ins = %d\\n\", readins);\n\tfprintf(stderr, \"Current read in: %d\\n\", n);\n\tfprintf(stderr, \"Method: %s\\n\\n\", Methods[method]);\n\tsignal(SIGQUIT, ctrl_bs_handler);\n}\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/encode_decode.sh",
    "content": "#!/bin/bash -e\n#\n# Copyright (C) 2014 Red Hat <contact@redhat.com>\n#\n# Author: Loic Dachary <loic@dachary.org>\n#\n# This program is free software; you can redistribute it and/or modify\n# it under the terms of the GNU Library Public License as published by\n# the Free Software Foundation; either version 2, or (at your option)\n# any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU Library Public License for more details.\n#\ntrap \"rm -fr T Coding\"  EXIT\n\ndd if=/dev/urandom of=T bs=4096 count=1\n./encoder T 3 2 reed_sol_van 8 0  0\n./decoder T\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/encoder.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n/* \n\nThis program takes as input an inputfile, k, m, a coding \ntechnique, w, and packetsize.  It creates k+m files from \nthe original file so that k of these files are parts of \nthe original file and m of the files are encoded based on \nthe given coding technique. The format of the created files \nis the file name with \"_k#\" or \"_m#\" and then the extension.  \n(For example, inputfile test.txt would yield file \"test_k1.txt\".)\n*/\n\n#include <assert.h>\n#include <time.h>\n#include <sys/time.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <string.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <errno.h>\n#include <signal.h>\n#include <gf_rand.h>\n#include <unistd.h>\n#include \"jerasure.h\"\n#include \"reed_sol.h\"\n#include \"cauchy.h\"\n#include \"liberation.h\"\n#include \"timing.h\"\n\n#define N 10\n\nenum Coding_Technique {Reed_Sol_Van, Reed_Sol_R6_Op, Cauchy_Orig, Cauchy_Good, Liberation, Blaum_Roth, Liber8tion, RDP, EVENODD, No_Coding};\n\nchar *Methods[N] = {\"reed_sol_van\", \"reed_sol_r6_op\", \"cauchy_orig\", \"cauchy_good\", \"liberation\", \"blaum_roth\", \"liber8tion\", \"no_coding\"};\n\n/* Global variables for signal handler */\nint readins, n;\nenum Coding_Technique method;\n\n/* Function prototypes */\nint is_prime(int w);\nvoid ctrl_bs_handler(int dummy);\n\nint jfread(void *ptr, int size, int nmembers, FILE *stream)\n{\n  if (stream != NULL) return fread(ptr, size, nmembers, stream);\n\n  MOA_Fill_Random_Region(ptr, size);\n  return size;\n}\n\n\nint main (int argc, char **argv) {\n\tFILE *fp, *fp2;\t\t\t\t// file pointers\n\tchar *block;\t\t\t\t// padding file\n\tint size, newsize;\t\t\t// size of file and temp size \n\tstruct stat status;\t\t\t// finding file size\n\n\t\n\tenum Coding_Technique tech;\t\t// coding technique (parameter)\n\tint k, m, w, packetsize;\t\t// parameters\n\tint buffersize;\t\t\t\t\t// paramter\n\tint i;\t\t\t\t\t\t// loop control variables\n\tint blocksize;\t\t\t\t\t// size of k+m files\n\tint total;\n\tint extra;\n\t\n\t/* Jerasure Arguments */\n\tchar **data;\t\t\t\t\n\tchar **coding;\n\tint *matrix;\n\tint *bitmatrix;\n\tint **schedule;\n\t\n\t/* Creation of file name variables */\n\tchar temp[5];\n\tchar *s1, *s2, *extension;\n\tchar *fname;\n\tint md;\n\tchar *curdir;\n\t\n\t/* Timing variables */\n\tstruct timing t1, t2, t3, t4;\n\tdouble tsec;\n\tdouble totalsec;\n\tstruct timing start;\n\n\t/* Find buffersize */\n\tint up, down;\n\n\n\tsignal(SIGQUIT, ctrl_bs_handler);\n\n\t/* Start timing */\n\ttiming_set(&t1);\n\ttotalsec = 0.0;\n\tmatrix = NULL;\n\tbitmatrix = NULL;\n\tschedule = NULL;\n\t\n\t/* Error check Arguments*/\n\tif (argc != 8) {\n\t\tfprintf(stderr,  \"usage: inputfile k m coding_technique w packetsize buffersize\\n\");\n\t\tfprintf(stderr,  \"\\nChoose one of the following coding techniques: \\nreed_sol_van, \\nreed_sol_r6_op, \\ncauchy_orig, \\ncauchy_good, \\nliberation, \\nblaum_roth, \\nliber8tion\");\n\t\tfprintf(stderr,  \"\\n\\nPacketsize is ignored for the reed_sol's\");\n\t\tfprintf(stderr,  \"\\nBuffersize of 0 means the buffersize is chosen automatically.\\n\");\n\t\tfprintf(stderr,  \"\\nIf you just want to test speed, use an inputfile of \\\"-number\\\" where number is the size of the fake file you want to test.\\n\\n\");\n\t\texit(0);\n\t}\n\t/* Conversion of parameters and error checking */\t\n\tif (sscanf(argv[2], \"%d\", &k) == 0 || k <= 0) {\n\t\tfprintf(stderr,  \"Invalid value for k\\n\");\n\t\texit(0);\n\t}\n\tif (sscanf(argv[3], \"%d\", &m) == 0 || m < 0) {\n\t\tfprintf(stderr,  \"Invalid value for m\\n\");\n\t\texit(0);\n\t}\n\tif (sscanf(argv[5],\"%d\", &w) == 0 || w <= 0) {\n\t\tfprintf(stderr,  \"Invalid value for w.\\n\");\n\t\texit(0);\n\t}\n\tif (argc == 6) {\n\t\tpacketsize = 0;\n\t}\n\telse {\n\t\tif (sscanf(argv[6], \"%d\", &packetsize) == 0 || packetsize < 0) {\n\t\t\tfprintf(stderr,  \"Invalid value for packetsize.\\n\");\n\t\t\texit(0);\n\t\t}\n\t}\n\tif (argc != 8) {\n\t\tbuffersize = 0;\n\t}\n\telse {\n\t\tif (sscanf(argv[7], \"%d\", &buffersize) == 0 || buffersize < 0) {\n\t\t\tfprintf(stderr, \"Invalid value for buffersize\\n\");\n\t\t\texit(0);\n\t\t}\n\t\t\n\t}\n\n\t/* Determine proper buffersize by finding the closest valid buffersize to the input value  */\n\tif (buffersize != 0) {\n\t\tif (packetsize != 0 && buffersize%(sizeof(long)*w*k*packetsize) != 0) { \n\t\t\tup = buffersize;\n\t\t\tdown = buffersize;\n\t\t\twhile (up%(sizeof(long)*w*k*packetsize) != 0 && (down%(sizeof(long)*w*k*packetsize) != 0)) {\n\t\t\t\tup++;\n\t\t\t\tif (down == 0) {\n\t\t\t\t\tdown--;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (up%(sizeof(long)*w*k*packetsize) == 0) {\n\t\t\t\tbuffersize = up;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (down != 0) {\n\t\t\t\t\tbuffersize = down;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (packetsize == 0 && buffersize%(sizeof(long)*w*k) != 0) {\n\t\t\tup = buffersize;\n\t\t\tdown = buffersize;\n\t\t\twhile (up%(sizeof(long)*w*k) != 0 && down%(sizeof(long)*w*k) != 0) {\n\t\t\t\tup++;\n\t\t\t\tdown--;\n\t\t\t}\n\t\t\tif (up%(sizeof(long)*w*k) == 0) {\n\t\t\t\tbuffersize = up;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tbuffersize = down;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Setting of coding technique and error checking */\n\t\n\tif (strcmp(argv[4], \"no_coding\") == 0) {\n\t\ttech = No_Coding;\n\t}\n\telse if (strcmp(argv[4], \"reed_sol_van\") == 0) {\n\t\ttech = Reed_Sol_Van;\n\t\tif (w != 8 && w != 16 && w != 32) {\n\t\t\tfprintf(stderr,  \"w must be one of {8, 16, 32}\\n\");\n\t\t\texit(0);\n\t\t}\n\t}\n\telse if (strcmp(argv[4], \"reed_sol_r6_op\") == 0) {\n\t\tif (m != 2) {\n\t\t\tfprintf(stderr,  \"m must be equal to 2\\n\");\n\t\t\texit(0);\n\t\t}\n\t\tif (w != 8 && w != 16 && w != 32) {\n\t\t\tfprintf(stderr,  \"w must be one of {8, 16, 32}\\n\");\n\t\t\texit(0);\n\t\t}\n\t\ttech = Reed_Sol_R6_Op;\n\t}\n\telse if (strcmp(argv[4], \"cauchy_orig\") == 0) {\n\t\ttech = Cauchy_Orig;\n\t\tif (packetsize == 0) {\n\t\t\tfprintf(stderr, \"Must include packetsize.\\n\");\n\t\t\texit(0);\n\t\t}\n\t}\n\telse if (strcmp(argv[4], \"cauchy_good\") == 0) {\n\t\ttech = Cauchy_Good;\n\t\tif (packetsize == 0) {\n\t\t\tfprintf(stderr, \"Must include packetsize.\\n\");\n\t\t\texit(0);\n\t\t}\n\t}\n\telse if (strcmp(argv[4], \"liberation\") == 0) {\n\t\tif (k > w) {\n\t\t\tfprintf(stderr,  \"k must be less than or equal to w\\n\");\n\t\t\texit(0);\n\t\t}\n\t\tif (w <= 2 || !(w%2) || !is_prime(w)) {\n\t\t\tfprintf(stderr,  \"w must be greater than two and w must be prime\\n\");\n\t\t\texit(0);\n\t\t}\n\t\tif (packetsize == 0) {\n\t\t\tfprintf(stderr, \"Must include packetsize.\\n\");\n\t\t\texit(0);\n\t\t}\n\t\tif ((packetsize%(sizeof(long))) != 0) {\n\t\t\tfprintf(stderr,  \"packetsize must be a multiple of sizeof(long)\\n\");\n\t\t\texit(0);\n\t\t}\n\t\ttech = Liberation;\n\t}\n\telse if (strcmp(argv[4], \"blaum_roth\") == 0) {\n\t\tif (k > w) {\n\t\t\tfprintf(stderr,  \"k must be less than or equal to w\\n\");\n\t\t\texit(0);\n\t\t}\n\t\tif (w <= 2 || !((w+1)%2) || !is_prime(w+1)) {\n\t\t\tfprintf(stderr,  \"w must be greater than two and w+1 must be prime\\n\");\n\t\t\texit(0);\n\t\t}\n\t\tif (packetsize == 0) {\n\t\t\tfprintf(stderr, \"Must include packetsize.\\n\");\n\t\t\texit(0);\n\t\t}\n\t\tif ((packetsize%(sizeof(long))) != 0) {\n\t\t\tfprintf(stderr,  \"packetsize must be a multiple of sizeof(long)\\n\");\n\t\t\texit(0);\n\t\t}\n\t\ttech = Blaum_Roth;\n\t}\n\telse if (strcmp(argv[4], \"liber8tion\") == 0) {\n\t\tif (packetsize == 0) {\n\t\t\tfprintf(stderr, \"Must include packetsize\\n\");\n\t\t\texit(0);\n\t\t}\n\t\tif (w != 8) {\n\t\t\tfprintf(stderr, \"w must equal 8\\n\");\n\t\t\texit(0);\n\t\t}\n\t\tif (m != 2) {\n\t\t\tfprintf(stderr, \"m must equal 2\\n\");\n\t\t\texit(0);\n\t\t}\n\t\tif (k > w) {\n\t\t\tfprintf(stderr, \"k must be less than or equal to w\\n\");\n\t\t\texit(0);\n\t\t}\n\t\ttech = Liber8tion;\n\t}\n\telse {\n\t\tfprintf(stderr,  \"Not a valid coding technique. Choose one of the following: reed_sol_van, reed_sol_r6_op, cauchy_orig, cauchy_good, liberation, blaum_roth, liber8tion, no_coding\\n\");\n\t\texit(0);\n\t}\n\n\t/* Set global variable method for signal handler */\n\tmethod = tech;\n\n\t/* Get current working directory for construction of file names */\n\tcurdir = (char*)malloc(sizeof(char)*1000);\t\n\tassert(curdir == getcwd(curdir, 1000));\n\n        if (argv[1][0] != '-') {\n\n\t\t/* Open file and error check */\n\t\tfp = fopen(argv[1], \"rb\");\n\t\tif (fp == NULL) {\n\t\t\tfprintf(stderr,  \"Unable to open file.\\n\");\n\t\t\texit(0);\n\t\t}\n\t\n\t\t/* Create Coding directory */\n\t\ti = mkdir(\"Coding\", S_IRWXU);\n\t\tif (i == -1 && errno != EEXIST) {\n\t\t\tfprintf(stderr, \"Unable to create Coding directory.\\n\");\n\t\t\texit(0);\n\t\t}\n\t\n\t\t/* Determine original size of file */\n\t\tstat(argv[1], &status);\t\n\t\tsize = status.st_size;\n        } else {\n        \tif (sscanf(argv[1]+1, \"%d\", &size) != 1 || size <= 0) {\n                \tfprintf(stderr, \"Files starting with '-' should be sizes for randomly created input\\n\");\n\t\t\texit(1);\n\t\t}\n        \tfp = NULL;\n\t\tMOA_Seed(time(0));\n        }\n\n\tnewsize = size;\n\t\n\t/* Find new size by determining next closest multiple */\n\tif (packetsize != 0) {\n\t\tif (size%(k*w*packetsize*sizeof(long)) != 0) {\n\t\t\twhile (newsize%(k*w*packetsize*sizeof(long)) != 0) \n\t\t\t\tnewsize++;\n\t\t}\n\t}\n\telse {\n\t\tif (size%(k*w*sizeof(long)) != 0) {\n\t\t\twhile (newsize%(k*w*sizeof(long)) != 0) \n\t\t\t\tnewsize++;\n\t\t}\n\t}\n\t\n\tif (buffersize != 0) {\n\t\twhile (newsize%buffersize != 0) {\n\t\t\tnewsize++;\n\t\t}\n\t}\n\n\n\t/* Determine size of k+m files */\n\tblocksize = newsize/k;\n\n\t/* Allow for buffersize and determine number of read-ins */\n\tif (size > buffersize && buffersize != 0) {\n\t\tif (newsize%buffersize != 0) {\n\t\t\treadins = newsize/buffersize;\n\t\t}\n\t\telse {\n\t\t\treadins = newsize/buffersize;\n\t\t}\n\t\tblock = (char *)malloc(sizeof(char)*buffersize);\n\t\tblocksize = buffersize/k;\n\t}\n\telse {\n\t\treadins = 1;\n\t\tbuffersize = size;\n\t\tblock = (char *)malloc(sizeof(char)*newsize);\n\t}\n\t\n\t/* Break inputfile name into the filename and extension */\t\n\ts1 = (char*)malloc(sizeof(char)*(strlen(argv[1])+20));\n\ts2 = strrchr(argv[1], '/');\n\tif (s2 != NULL) {\n\t\ts2++;\n\t\tstrcpy(s1, s2);\n\t}\n\telse {\n\t\tstrcpy(s1, argv[1]);\n\t}\n\ts2 = strchr(s1, '.');\n\tif (s2 != NULL) {\n          extension = strdup(s2);\n          *s2 = '\\0';\n\t} else {\n          extension = strdup(\"\");\n        }\n\t\n\t/* Allocate for full file name */\n\tfname = (char*)malloc(sizeof(char)*(strlen(argv[1])+strlen(curdir)+20));\n\tsprintf(temp, \"%d\", k);\n\tmd = strlen(temp);\n\t\n\t/* Allocate data and coding */\n\tdata = (char **)malloc(sizeof(char*)*k);\n\tcoding = (char **)malloc(sizeof(char*)*m);\n\tfor (i = 0; i < m; i++) {\n\t\tcoding[i] = (char *)malloc(sizeof(char)*blocksize);\n                if (coding[i] == NULL) { perror(\"malloc\"); exit(1); }\n\t}\n\n\t\n\n\t/* Create coding matrix or bitmatrix and schedule */\n\ttiming_set(&t3);\n\tswitch(tech) {\n\t\tcase No_Coding:\n\t\t\tbreak;\n\t\tcase Reed_Sol_Van:\n\t\t\tmatrix = reed_sol_vandermonde_coding_matrix(k, m, w);\n\t\t\tbreak;\n\t\tcase Reed_Sol_R6_Op:\n\t\t\tbreak;\n\t\tcase Cauchy_Orig:\n\t\t\tmatrix = cauchy_original_coding_matrix(k, m, w);\n\t\t\tbitmatrix = jerasure_matrix_to_bitmatrix(k, m, w, matrix);\n\t\t\tschedule = jerasure_smart_bitmatrix_to_schedule(k, m, w, bitmatrix);\n\t\t\tbreak;\n\t\tcase Cauchy_Good:\n\t\t\tmatrix = cauchy_good_general_coding_matrix(k, m, w);\n\t\t\tbitmatrix = jerasure_matrix_to_bitmatrix(k, m, w, matrix);\n\t\t\tschedule = jerasure_smart_bitmatrix_to_schedule(k, m, w, bitmatrix);\n\t\t\tbreak;\t\n\t\tcase Liberation:\n\t\t\tbitmatrix = liberation_coding_bitmatrix(k, w);\n\t\t\tschedule = jerasure_smart_bitmatrix_to_schedule(k, m, w, bitmatrix);\n\t\t\tbreak;\n\t\tcase Blaum_Roth:\n\t\t\tbitmatrix = blaum_roth_coding_bitmatrix(k, w);\n\t\t\tschedule = jerasure_smart_bitmatrix_to_schedule(k, m, w, bitmatrix);\n\t\t\tbreak;\n\t\tcase Liber8tion:\n\t\t\tbitmatrix = liber8tion_coding_bitmatrix(k);\n\t\t\tschedule = jerasure_smart_bitmatrix_to_schedule(k, m, w, bitmatrix);\n\t\t\tbreak;\n\t\tcase RDP:\n\t\tcase EVENODD:\n\t\t\tassert(0);\n\t}\n\ttiming_set(&start);\n\ttiming_set(&t4);\n\ttotalsec += timing_delta(&t3, &t4);\n\n\t\n\n\t/* Read in data until finished */\n\tn = 1;\n\ttotal = 0;\n\n\twhile (n <= readins) {\n\t\t/* Check if padding is needed, if so, add appropriate \n\t\t   number of zeros */\n\t\tif (total < size && total+buffersize <= size) {\n\t\t\ttotal += jfread(block, sizeof(char), buffersize, fp);\n\t\t}\n\t\telse if (total < size && total+buffersize > size) {\n\t\t\textra = jfread(block, sizeof(char), buffersize, fp);\n\t\t\tfor (i = extra; i < buffersize; i++) {\n\t\t\t\tblock[i] = '0';\n\t\t\t}\n\t\t}\n\t\telse if (total == size) {\n\t\t\tfor (i = 0; i < buffersize; i++) {\n\t\t\t\tblock[i] = '0';\n\t\t\t}\n\t\t}\n\t\n\t\t/* Set pointers to point to file data */\n\t\tfor (i = 0; i < k; i++) {\n\t\t\tdata[i] = block+(i*blocksize);\n\t\t}\n\n\t\ttiming_set(&t3);\n\t\t/* Encode according to coding method */\n\t\tswitch(tech) {\t\n\t\t\tcase No_Coding:\n\t\t\t\tbreak;\n\t\t\tcase Reed_Sol_Van:\n\t\t\t\tjerasure_matrix_encode(k, m, w, matrix, data, coding, blocksize);\n\t\t\t\tbreak;\n\t\t\tcase Reed_Sol_R6_Op:\n\t\t\t\treed_sol_r6_encode(k, w, data, coding, blocksize);\n\t\t\t\tbreak;\n\t\t\tcase Cauchy_Orig:\n\t\t\t\tjerasure_schedule_encode(k, m, w, schedule, data, coding, blocksize, packetsize);\n\t\t\t\tbreak;\n\t\t\tcase Cauchy_Good:\n\t\t\t\tjerasure_schedule_encode(k, m, w, schedule, data, coding, blocksize, packetsize);\n\t\t\t\tbreak;\n\t\t\tcase Liberation:\n\t\t\t\tjerasure_schedule_encode(k, m, w, schedule, data, coding, blocksize, packetsize);\n\t\t\t\tbreak;\n\t\t\tcase Blaum_Roth:\n\t\t\t\tjerasure_schedule_encode(k, m, w, schedule, data, coding, blocksize, packetsize);\n\t\t\t\tbreak;\n\t\t\tcase Liber8tion:\n\t\t\t\tjerasure_schedule_encode(k, m, w, schedule, data, coding, blocksize, packetsize);\n\t\t\t\tbreak;\n\t\t\tcase RDP:\n\t\t\tcase EVENODD:\n\t\t\t\tassert(0);\n\t\t}\n\t\ttiming_set(&t4);\n\t\n\t\t/* Write data and encoded data to k+m files */\n\t\tfor\t(i = 1; i <= k; i++) {\n\t\t\tif (fp == NULL) {\n\t\t\t\tbzero(data[i-1], blocksize);\n \t\t\t} else {\n\t\t\t\tsprintf(fname, \"%s/Coding/%s_k%0*d%s\", curdir, s1, md, i, extension);\n\t\t\t\tif (n == 1) {\n\t\t\t\t\tfp2 = fopen(fname, \"wb\");\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfp2 = fopen(fname, \"ab\");\n\t\t\t\t}\n\t\t\t\tfwrite(data[i-1], sizeof(char), blocksize, fp2);\n\t\t\t\tfclose(fp2);\n\t\t\t}\n\t\t\t\n\t\t}\n\t\tfor\t(i = 1; i <= m; i++) {\n\t\t\tif (fp == NULL) {\n\t\t\t\tbzero(data[i-1], blocksize);\n \t\t\t} else {\n\t\t\t\tsprintf(fname, \"%s/Coding/%s_m%0*d%s\", curdir, s1, md, i, extension);\n\t\t\t\tif (n == 1) {\n\t\t\t\t\tfp2 = fopen(fname, \"wb\");\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfp2 = fopen(fname, \"ab\");\n\t\t\t\t}\n\t\t\t\tfwrite(coding[i-1], sizeof(char), blocksize, fp2);\n\t\t\t\tfclose(fp2);\n\t\t\t}\n\t\t}\n\t\tn++;\n\t\t/* Calculate encoding time */\n\t\ttotalsec += timing_delta(&t3, &t4);\n\t}\n\n\t/* Create metadata file */\n        if (fp != NULL) {\n\t\tsprintf(fname, \"%s/Coding/%s_meta.txt\", curdir, s1);\n\t\tfp2 = fopen(fname, \"wb\");\n\t\tfprintf(fp2, \"%s\\n\", argv[1]);\n\t\tfprintf(fp2, \"%d\\n\", size);\n\t\tfprintf(fp2, \"%d %d %d %d %d\\n\", k, m, w, packetsize, buffersize);\n\t\tfprintf(fp2, \"%s\\n\", argv[4]);\n\t\tfprintf(fp2, \"%d\\n\", tech);\n\t\tfprintf(fp2, \"%d\\n\", readins);\n\t\tfclose(fp2);\n\t}\n\n\n\t/* Free allocated memory */\n\tfree(s1);\n\tfree(fname);\n\tfree(block);\n\tfree(curdir);\n\t\n\t/* Calculate rate in MB/sec and print */\n\ttiming_set(&t2);\n\ttsec = timing_delta(&t1, &t2);\n\tprintf(\"Encoding (MB/sec): %0.10f\\n\", (((double) size)/1024.0/1024.0)/totalsec);\n\tprintf(\"En_Total (MB/sec): %0.10f\\n\", (((double) size)/1024.0/1024.0)/tsec);\n\n\treturn 0;\n}\n\n/* is_prime returns 1 if number if prime, 0 if not prime */\nint is_prime(int w) {\n\tint prime55[] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,\n\t    73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,\n\t\t    181,191,193,197,199,211,223,227,229,233,239,241,251,257};\n\tint i;\n\tfor (i = 0; i < 55; i++) {\n\t\tif (w%prime55[i] == 0) {\n\t\t\tif (w == prime55[i]) return 1;\n\t\t\telse { return 0; }\n\t\t}\n\t}\n\tassert(0);\n}\n\n/* Handles ctrl-\\ event */\nvoid ctrl_bs_handler(int dummy) {\n\ttime_t mytime;\n\tmytime = time(0);\n\tfprintf(stderr, \"\\n%s\\n\", ctime(&mytime));\n\tfprintf(stderr, \"You just typed ctrl-\\\\ in encoder.c.\\n\");\n\tfprintf(stderr, \"Total number of read ins = %d\\n\", readins);\n\tfprintf(stderr, \"Current read in: %d\\n\", n);\n\tfprintf(stderr, \"Method: %s\\n\\n\", Methods[method]);\t\n\tsignal(SIGQUIT, ctrl_bs_handler);\n}\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/jerasure_01.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include \"jerasure.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: jerasure_01 r c w - creates and prints out a matrix in GF(2^w).\\n\\n\");\n  fprintf(stderr, \"       Element i,j is equal to 2^(i*c+j)\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"This demonstrates jerasure_print_matrix().\\n\");\n  if (s != NULL) fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  int r, c, w, i, n;\n  int *matrix;\n\n  if (argc != 4) usage(NULL);\n  if (sscanf(argv[1], \"%d\", &r) == 0 || r <= 0) usage(\"Bad r\");\n  if (sscanf(argv[2], \"%d\", &c) == 0 || c <= 0) usage(\"Bad c\");\n  if (sscanf(argv[3], \"%d\", &w) == 0 || w <= 0) usage(\"Bad w\");\n\n  matrix = talloc(int, r*c);\n\n  n = 1;\n  for (i = 0; i < r*c; i++) {\n    matrix[i] = n;\n    n = galois_single_multiply(n, 2, w);\n  }\n\n  printf(\"<HTML><TITLE>jerasure_01\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</TITLE>\\n\");\n  printf(\"<h3>jerasure_01\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</h3>\\n\");\n  printf(\"<pre>\\n\");\n\n  jerasure_print_matrix(matrix, r, c, w);\n  return 0;\n}\n\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/jerasure_02.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include \"jerasure.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: jerasure_02 r c w - Converts the matrix of jerasure_01 to a bit matrix.\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"This demonstrates jerasure_print_bitmatrix() and jerasure_matrix_to_bitmatrix().\\n\");\n  if (s != NULL) fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  int r, c, w, i, n;\n  int *matrix;\n  int *bitmatrix;\n\n  if (argc != 4) usage(NULL);\n  if (sscanf(argv[1], \"%d\", &r) == 0 || r <= 0) usage(\"Bad r\");\n  if (sscanf(argv[2], \"%d\", &c) == 0 || c <= 0) usage(\"Bad c\");\n  if (sscanf(argv[3], \"%d\", &w) == 0 || w <= 0) usage(\"Bad w\");\n\n  matrix = talloc(int, r*c);\n\n  n = 1;\n  for (i = 0; i < r*c; i++) {\n    matrix[i] = n;\n    n = galois_single_multiply(n, 2, w);\n  }\n\n  bitmatrix = jerasure_matrix_to_bitmatrix(c, r, w, matrix);\n\n  printf(\"<HTML><TITLE>jerasure_02\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</TITLE>\\n\");\n  printf(\"<h3>jerasure_02\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</h3>\\n\");\n  printf(\"<pre>\\n\");\n\n  jerasure_print_bitmatrix(bitmatrix, r*w, c*w, w);\n  return 0;\n}\n\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/jerasure_03.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"jerasure.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: jerasure_03 k w - Creates a kxk Cauchy matrix in GF(2^w). \\n\\n\");\n  fprintf(stderr, \"       k must be < 2^w.  Element i,j is 1/(i+(2^w-j-1)).  (If that is\\n\");\n  fprintf(stderr, \"       If that is 1/0, then it sets it to zero).  \\n\");\n  fprintf(stderr, \"       It then tests whether that matrix is invertible.\\n\");\n  fprintf(stderr, \"       If it is invertible, then it prints out the inverse.\\n\");\n  fprintf(stderr, \"       Finally, it prints the product of the matrix and its inverse.\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"This demonstrates: jerasure_print_matrix()\\n\");\n  fprintf(stderr, \"                   jerasure_invertible_matrix()\\n\");\n  fprintf(stderr, \"                   jerasure_invert_matrix()\\n\");\n  fprintf(stderr, \"                   jerasure_matrix_multiply().\\n\");\n  if (s != NULL) fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  unsigned int k, w, i, j, n;\n  int *matrix;\n  int *matrix_copy;\n  int *inverse;\n  int *identity;\n\n  if (argc != 3) usage(NULL);\n  if (sscanf(argv[1], \"%d\", &k) == 0 || k <= 0) usage(\"Bad k\");\n  if (sscanf(argv[2], \"%d\", &w) == 0 || w <= 0 || w > 31) usage(\"Bad w\");\n  if (k >= (1 << w)) usage(\"K too big\");\n\n  matrix = talloc(int, k*k);\n  matrix_copy = talloc(int, k*k);\n  inverse = talloc(int, k*k);\n\n  for (i = 0; i < k; i++) {\n    for (j = 0; j < k; j++) {\n      n = i ^ ((1 << w)-1-j);\n      matrix[i*k+j] = (n == 0) ? 0 : galois_single_divide(1, n, w);\n    }\n  }\n\n  printf(\"<HTML><TITLE>jerasure_03\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</TITLE>\\n\");\n  printf(\"<h3>jerasure_03\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</h3>\\n\");\n  printf(\"<pre>\\n\");\n\n  printf(\"The Cauchy Matrix:\\n\");\n  jerasure_print_matrix(matrix, k, k, w);\n  memcpy(matrix_copy, matrix, sizeof(int)*k*k);\n  i = jerasure_invertible_matrix(matrix_copy, k, w);\n  printf(\"\\nInvertible: %s\\n\", (i == 1) ? \"Yes\" : \"No\");\n  if (i == 1) {\n    printf(\"\\nInverse:\\n\");\n    memcpy(matrix_copy, matrix, sizeof(int)*k*k);\n    i = jerasure_invert_matrix(matrix_copy, inverse, k, w);\n    jerasure_print_matrix(inverse, k, k, w);\n    identity = jerasure_matrix_multiply(inverse, matrix, k, k, k, k, w);\n    printf(\"\\nInverse times matrix (should be identity):\\n\");\n    jerasure_print_matrix(identity, k, k, w);\n  }\n  return 0;\n}\n\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/jerasure_04.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"jerasure.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: jerasure_04 k w - Performs the analogous bit-matrix operations to jerasure_03.\\n\\n\");\n  fprintf(stderr, \"       It converts the matrix to a kw*kw bit matrix and does the same operations.\\n\");\n  fprintf(stderr, \"       k must be < 2^w.\\n\");\n  fprintf(stderr, \"This demonstrates: jerasure_print_bitmatrix()\\n\");\n  fprintf(stderr, \"                   jerasure_matrix_to_bitmatrix()\\n\");\n  fprintf(stderr, \"                   jerasure_invertible_bitmatrix()\\n\");\n  fprintf(stderr, \"                   jerasure_invert_bitmatrix()\\n\");\n  fprintf(stderr, \"                   jerasure_matrix_multiply().\\n\");\n  if (s != NULL) fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  unsigned int k, w, i, j, n;\n  int *matrix;\n  int *bitmatrix;\n  int *bitmatrix_copy;\n  int *inverse;\n  int *identity;\n\n  if (argc != 3) usage(NULL);\n  if (sscanf(argv[1], \"%d\", &k) == 0 || k <= 0) usage(\"Bad k\");\n  if (sscanf(argv[2], \"%d\", &w) == 0 || w <= 0 || w > 31) usage(\"Bad w\");\n  if (k >= (1 << w)) usage(\"K too big\");\n\n  matrix = talloc(int, k*k);\n  bitmatrix_copy = talloc(int, k*w*k*w);\n  inverse = talloc(int, k*w*k*w);\n\n  for (i = 0; i < k; i++) {\n    for (j = 0; j < k; j++) {\n      n = i ^ ((1 << w)-1-j);\n      matrix[i*k+j] = (n == 0) ? 0 : galois_single_divide(1, n, w);\n    }\n  }\n  bitmatrix = jerasure_matrix_to_bitmatrix(k, k, w, matrix);\n\n  printf(\"<HTML><TITLE>jerasure_04\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</TITLE>\\n\");\n  printf(\"<h3>jerasure_04\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</h3>\\n\");\n  printf(\"<pre>\\n\");\n\n  printf(\"The Cauchy Bit-Matrix:\\n\");\n  jerasure_print_bitmatrix(bitmatrix, k*w, k*w, w);\n  memcpy(bitmatrix_copy, bitmatrix, sizeof(int)*k*w*k*w);\n  i = jerasure_invertible_bitmatrix(bitmatrix_copy, k*w);\n  printf(\"\\nInvertible: %s\\n\", (i == 1) ? \"Yes\" : \"No\");\n  if (i == 1) {\n    printf(\"\\nInverse:\\n\");\n    memcpy(bitmatrix_copy, bitmatrix, sizeof(int)*k*w*k*w);\n    i = jerasure_invert_bitmatrix(bitmatrix_copy, inverse, k*w);\n    jerasure_print_bitmatrix(inverse, k*w, k*w, w);\n    identity = jerasure_matrix_multiply(inverse, bitmatrix, k*w, k*w, k*w, k*w, 2);\n    printf(\"\\nInverse times matrix (should be identity):\\n\");\n    jerasure_print_bitmatrix(identity, k*w, k*w, w);\n  }\n  return 0;\n}\n\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/jerasure_05.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <gf_rand.h>\n#include \"jerasure.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: jerasure_05 k m w size seed - Does a simple Reed-Solomon coding example in GF(2^w).\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"       k+m must be <= 2^w.  w can be 8, 16 or 32.\\n\");\n  fprintf(stderr, \"       It sets up a Cauchy generator matrix and encodes\\n\");\n  fprintf(stderr, \"       k devices of size bytes with it.  Then it decodes.\\n\");\n  fprintf(stderr, \"       After that, it decodes device 0 by using jerasure_make_decoding_matrix()\\n\");\n  fprintf(stderr, \"       and jerasure_matrix_dotprod().\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"This demonstrates: jerasure_matrix_encode()\\n\");\n  fprintf(stderr, \"                   jerasure_matrix_decode()\\n\");\n  fprintf(stderr, \"                   jerasure_print_matrix()\\n\");\n  fprintf(stderr, \"                   jerasure_make_decoding_matrix()\\n\");\n  fprintf(stderr, \"                   jerasure_matrix_dotprod()\\n\");\n  if (s != NULL) fprintf(stderr, \"\\n%s\\n\\n\", s);\n  exit(1);\n}\n\nstatic void print_data_and_coding(int k, int m, int w, int size, \n\t\tchar **data, char **coding) \n{\n  int i, j, x;\n  int n, sp;\n\n  if(k > m) n = k;\n  else n = m;\n  sp = size * 2 + size/(w/8) + 8;\n\n  printf(\"%-*sCoding\\n\", sp, \"Data\");\n  for(i = 0; i < n; i++) {\n\t  if(i < k) {\n\t\t  printf(\"D%-2d:\", i);\n\t\t  for(j=0;j< size; j+=(w/8)) { \n\t\t\t  printf(\" \");\n\t\t\t  for(x=0;x < w/8;x++){\n\t\t\t\tprintf(\"%02x\", (unsigned char)data[i][j+x]);\n\t\t\t  }\n\t\t  }\n\t\t  printf(\"    \");\n\t  }\n\t  else printf(\"%*s\", sp, \"\");\n\t  if(i < m) {\n\t\t  printf(\"C%-2d:\", i);\n\t\t  for(j=0;j< size; j+=(w/8)) { \n\t\t\t  printf(\" \");\n\t\t\t  for(x=0;x < w/8;x++){\n\t\t\t\tprintf(\"%02x\", (unsigned char)coding[i][j+x]);\n\t\t\t  }\n\t\t  }\n\t  }\n\t  printf(\"\\n\");\n  }\n\tprintf(\"\\n\");\n}\n\nint main(int argc, char **argv)\n{\n  int k, m, w, size;\n  int i, j;\n  int *matrix;\n  char **data, **coding;\n  int *erasures, *erased;\n  int *decoding_matrix, *dm_ids;\n  uint32_t seed;\n  \n  if (argc != 6) usage(NULL);\n  if (sscanf(argv[1], \"%d\", &k) == 0 || k <= 0) usage(\"Bad k\");\n  if (sscanf(argv[2], \"%d\", &m) == 0 || m <= 0) usage(\"Bad m\");\n  if (sscanf(argv[3], \"%d\", &w) == 0 || (w != 8 && w != 16 && w != 32)) usage(\"Bad w\");\n  if (w < 32 && k + m > (1 << w)) usage(\"k + m must be <= 2 ^ w\");\n  if (sscanf(argv[4], \"%d\", &size) == 0 || size % sizeof(long) != 0) \n\t\tusage(\"size must be multiple of sizeof(long)\");\n  if (sscanf(argv[5], \"%d\", &seed) == 0) usage(\"Bad seed\");\n\n  matrix = talloc(int, m*k);\n  for (i = 0; i < m; i++) {\n    for (j = 0; j < k; j++) {\n      matrix[i*k+j] = galois_single_divide(1, i ^ (m + j), w);\n    }\n  }\n\n  printf(\"<HTML><TITLE>jerasure_05\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</TITLE>\\n\");\n  printf(\"<h3>jerasure_05\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</h3>\\n\");\n  printf(\"<pre>\\n\");\n\n  printf(\"The Coding Matrix (the last m rows of the Generator Matrix G^T):\\n\\n\");\n  jerasure_print_matrix(matrix, m, k, w);\n  printf(\"\\n\");\n\n  MOA_Seed(seed);\n  data = talloc(char *, k);\n  for (i = 0; i < k; i++) {\n    data[i] = talloc(char, size);\n    MOA_Fill_Random_Region(data[i], size);\n  }\n\n  coding = talloc(char *, m);\n  for (i = 0; i < m; i++) {\n    coding[i] = talloc(char, size);\n  }\n\n  jerasure_matrix_encode(k, m, w, matrix, data, coding, size);\n  \n  printf(\"Encoding Complete:\\n\\n\");\n  print_data_and_coding(k, m, w, size, data, coding);\n\n  erasures = talloc(int, (m+1));\n  erased = talloc(int, (k+m));\n  for (i = 0; i < m+k; i++) erased[i] = 0;\n  for (i = 0; i < m; ) {\n    erasures[i] = (MOA_Random_W(w, 1))%(k+m);\n    if (erased[erasures[i]] == 0) {\n      erased[erasures[i]] = 1;\n\t  \n      bzero((erasures[i] < k) ? data[erasures[i]] : coding[erasures[i]-k], size);\n      i++;\n    }\n  }\n  erasures[i] = -1;\n\n  printf(\"Erased %d random devices:\\n\\n\", m);\n  print_data_and_coding(k, m, w, size, data, coding);\n  \n  i = jerasure_matrix_decode(k, m, w, matrix, 0, erasures, data, coding, size);\n\n  printf(\"State of the system after decoding:\\n\\n\");\n  print_data_and_coding(k, m, w, size, data, coding);\n  \n  decoding_matrix = talloc(int, k*k);\n  dm_ids = talloc(int, k);\n\n  for (i = 0; i < m; i++) erased[i] = 1;\n  for (; i < k+m; i++) erased[i] = 0;\n\n  jerasure_make_decoding_matrix(k, m, w, matrix, erased, decoding_matrix, dm_ids);\n\n  printf(\"Suppose we erase the first %d devices.  Here is the decoding matrix:\\n\\n\", m);\n  jerasure_print_matrix(decoding_matrix, k, k, w);\n  printf(\"\\n\");\n  printf(\"And dm_ids:\\n\\n\");\n  jerasure_print_matrix(dm_ids, 1, k, w);\n\n  bzero(data[0], size);\n  jerasure_matrix_dotprod(k, w, decoding_matrix, dm_ids, 0, data, coding, size);\n\n  printf(\"\\nAfter calling jerasure_matrix_dotprod, we calculate the value of device #0 to be:\\n\\n\");\n  printf(\"D0 :\");\n  for(i=0;i< size; i+=(w/8)) {\n\t  printf(\" \");\n\t  for(j=0;j < w/8;j++){\n\t\tprintf(\"%02x\", (unsigned char)data[0][i+j]);\n\t  }\n  }\n  printf(\"\\n\\n\");\n\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/jerasure_06.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <string.h>\n#include <gf_rand.h>\n#include \"jerasure.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: jerasure_06 k m w packetsize seed\\n\");\n  fprintf(stderr, \"Does a simple Cauchy Reed-Solomon coding example in GF(2^w).\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"       k+m must be < 2^w.  Packetsize must be a multiple of sizeof(long)\\n\");\n  fprintf(stderr, \"       It sets up a Cauchy generator matrix and encodes k devices of w*packetsize bytes.\\n\");\n  fprintf(stderr, \"       After that, it decodes device 0 by using jerasure_make_decoding_bitmatrix()\\n\");\n  fprintf(stderr, \"       and jerasure_bitmatrix_dotprod().\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"This demonstrates: jerasure_bitmatrix_encode()\\n\");\n  fprintf(stderr, \"                   jerasure_bitmatrix_decode()\\n\");\n  fprintf(stderr, \"                   jerasure_print_bitmatrix()\\n\");\n  fprintf(stderr, \"                   jerasure_make_decoding_bitmatrix()\\n\");\n  fprintf(stderr, \"                   jerasure_bitmatrix_dotprod()\\n\");\n  if (s != NULL) fprintf(stderr, \"\\n%s\\n\\n\", s);\n  exit(1);\n}\n\nstatic void print_array(char **ptrs, int ndevices, int size, int packetsize, char *label)\n{\n  int i, j, x;\n  unsigned char *up;\n\n  printf(\"<center><table border=3 cellpadding=3><tr><td></td>\\n\");\n\n  for (i = 0; i < ndevices; i++) printf(\"<td align=center>%s%x</td>\\n\", label, i);\n  printf(\"</tr>\\n\");\n  printf(\"<td align=right><pre>\");\n  for (j = 0; j < size/packetsize; j++) printf(\"Packet %d\\n\", j);\n  printf(\"</pre></td>\\n\");\n  for (i = 0; i < ndevices; i++) {\n    printf(\"<td><pre>\");\n    up = (unsigned char *) ptrs[i];\n    for (j = 0; j < size/packetsize; j++) {\n      for (x = 0; x < packetsize; x++) {\n        if (x > 0 && x%4 == 0) printf(\" \");\n        printf(\"%02x\", up[j*packetsize+x]);\n      }\n      printf(\"\\n\");\n    }\n    printf(\"</td>\\n\");\n  }\n  printf(\"</tr></table></center>\\n\");\n}\n\nint main(int argc, char **argv)\n{\n  int k, w, i, j, m, psize, x;\n  int *matrix, *bitmatrix;\n  char **data, **coding;\n  int *erasures, *erased;\n  int *decoding_matrix, *dm_ids;\n  uint32_t seed;\n  \n  if (argc != 6) usage(NULL);\n  if (sscanf(argv[1], \"%d\", &k) == 0 || k <= 0) usage(\"Bad k\");\n  if (sscanf(argv[2], \"%d\", &m) == 0 || m <= 0) usage(\"Bad m\");\n  if (sscanf(argv[3], \"%d\", &w) == 0 || w <= 0 || w > 32) usage(\"Bad w\");\n  if (w < 30 && (k+m) > (1 << w)) usage(\"k + m is too big\");\n  if (sscanf(argv[4], \"%d\", &psize) == 0 || psize <= 0) usage(\"Bad packetsize\");\n  if(psize%sizeof(long) != 0) usage(\"Packetsize must be multiple of sizeof(long)\");\n  if (sscanf(argv[5], \"%d\", &seed) == 0) usage(\"Bad seed\");\n\n  MOA_Seed(seed);\n  matrix = talloc(int, m*k);\n  for (i = 0; i < m; i++) {\n    for (j = 0; j < k; j++) {\n      matrix[i*k+j] = galois_single_divide(1, i ^ (m + j), w);\n    }\n  }\n  bitmatrix = jerasure_matrix_to_bitmatrix(k, m, w, matrix);\n\n  printf(\"<HTML><TITLE>jerasure_06\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</TITLE>\\n\");\n  printf(\"<h3>jerasure_06\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</h3>\\n\");\n\n  printf(\"<hr>\\n\");\n  printf(\"Last (m * w) rows of the Generator Matrix: (G^T):\\n<pre>\\n\");\n  jerasure_print_bitmatrix(bitmatrix, w*m, w*k, w);\n  printf(\"</pre><hr>\\n\");\n\n  data = talloc(char *, k);\n  for (i = 0; i < k; i++) {\n    data[i] = talloc(char, psize*w);\n    MOA_Fill_Random_Region(data[i], psize*w);\n  }\n\n  coding = talloc(char *, m);\n  for (i = 0; i < m; i++) {\n    coding[i] = talloc(char, psize*w);\n  }\n\n  jerasure_bitmatrix_encode(k, m, w, bitmatrix, data, coding, w*psize, psize);\n  \n  printf(\"Encoding Complete - Here is the state of the system\\n\\n\");\n  printf(\"<p>\\n\");\n  print_array(data, k, psize*w, psize, \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, psize*w, psize, \"C\");\n  printf(\"<hr>\\n\");\n\n  erasures = talloc(int, (m+1));\n  erased = talloc(int, (k+m));\n  for (i = 0; i < m+k; i++) erased[i] = 0;\n  for (i = 0; i < m; ) {\n    erasures[i] = MOA_Random_W(w, 1)%(k+m);\n    if (erased[erasures[i]] == 0) {\n      erased[erasures[i]] = 1;\n      bzero((erasures[i] < k) ? data[erasures[i]] : coding[erasures[i]-k], psize*w);\n      i++;\n    }\n  }\n  erasures[i] = -1;\n\n  printf(\"Erased %d random devices:\", m);\n  for (i = 0; erasures[i] != -1; i++) {\n    printf(\" %c%x\", ((erasures[i] < k) ? 'D' : 'C'), (erasures[i] < k ? erasures[i] : erasures[i]-k));\n  }\n  printf(\". Here is the state of the system:\\n\");\n\n  printf(\"<p>\\n\");\n  print_array(data, k, psize*w, psize, \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, psize*w, psize, \"C\");\n  printf(\"<hr>\\n\");\n\n  i = jerasure_bitmatrix_decode(k, m, w, bitmatrix, 0, erasures, data, coding, \n\t\t  w*psize, psize);\n\n  printf(\"Here is the state of the system after decoding:\\n\\n\");\n  printf(\"<p>\\n\");\n  print_array(data, k, psize*w, psize, \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, psize*w, psize, \"C\");\n  printf(\"<hr>\\n\");\n\n  decoding_matrix = talloc(int, k*k*w*w);\n  dm_ids = talloc(int, k);\n\n  x = (m < k) ? m : k;\n\n  for (i = 0; i < x; i++) erased[i] = 1;\n  for (; i < k+m; i++) erased[i] = 0;\n\n  jerasure_make_decoding_bitmatrix(k, m, w, bitmatrix, erased, decoding_matrix, dm_ids);\n\n  printf(\"Suppose we erase the first %d devices.  Here is the decoding matrix:\\n<pre>\\n\", x);\n  jerasure_print_bitmatrix(decoding_matrix, k*w, k*w, w);\n  printf(\"</pre>\\n\");\n  printf(\"And dm_ids:\\n<pre>\\n\");\n  jerasure_print_matrix(dm_ids, 1, k, w);\n  printf(\"</pre><hr>\\n\");\n\n  for (i = 0; i < x; i++) bzero(data[i], w*psize);\n\n  printf(\"Here is the state of the system after the erasures:\\n\\n\");\n  printf(\"<p>\\n\");\n  print_array(data, k, psize*w, psize, \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, psize*w, psize, \"C\");\n  printf(\"<hr>\\n\");\n\n  for (i = 0; i < x; i++) {\n    jerasure_bitmatrix_dotprod(k, w, decoding_matrix+i*(k*w*w), dm_ids, i, data, coding, w*psize, psize);\n  }\n\n  printf(\"Here is the state of the system after calling <b>jerasure_bitmatrix_dotprod()</b> %d time%s with the decoding matrix:\\n\\n\", x, (x == 1) ? \"\" : \"s\");\n  printf(\"<p>\\n\");\n  print_array(data, k, psize*w, psize, \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, psize*w, psize, \"C\");\n  printf(\"<hr>\\n\");\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/jerasure_07.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <stdint.h>\n#include <gf_rand.h>\n#include \"jerasure.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: jerasure_07 k m w seed - Scheduled Cauchy Reed-Solomon coding example in GF(2^w).\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"       k+m must be <= 2^w.  It sets up a Cauchy generator matrix and encodes\\n\");\n  fprintf(stderr, \"       k sets of w*%ld bytes. It uses bit-matrix scheduling, both smart and dumb.\\n\", sizeof(long));\n  fprintf(stderr, \"       It decodes using bit-matrix scheduling, then shows an example of\\n\");\n  fprintf(stderr, \"       using jerasure_do_scheduled_operations().\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"This demonstrates: jerasure_dumb_bitmatrix_to_schedule()\\n\");\n  fprintf(stderr, \"                   jerasure_smart_bitmatrix_to_schedule()\\n\");\n  fprintf(stderr, \"                   jerasure_schedule_encode()\\n\");\n  fprintf(stderr, \"                   jerasure_schedule_decode_lazy()\\n\");\n  fprintf(stderr, \"                   jerasure_do_scheduled_operations()\\n\");\n  fprintf(stderr, \"                   jerasure_get_stats()\\n\");\n  if (s != NULL) fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\nstatic void print_array(char **ptrs, int ndevices, int size, int packetsize, char *label)\n{\n  int i, j, x;\n  unsigned char *up;\n\n  printf(\"<center><table border=3 cellpadding=3><tr><td></td>\\n\");\n\n  for (i = 0; i < ndevices; i++) printf(\"<td align=center>%s%x</td>\\n\", label, i);\n  printf(\"</tr>\\n\");\n  printf(\"<td align=right><pre>\");\n  for (j = 0; j < size/packetsize; j++) printf(\"Packet %d\\n\", j);\n  printf(\"</pre></td>\\n\");\n  for (i = 0; i < ndevices; i++) {\n    printf(\"<td><pre>\");\n    up = (unsigned char *) ptrs[i];\n    for (j = 0; j < size/packetsize; j++) {\n      for (x = 0; x < packetsize; x++) {\n        if (x > 0 && x%4 == 0) printf(\" \");\n        printf(\"%02x\", up[j*packetsize+x]);\n      }\n      printf(\"\\n\");\n    }\n    printf(\"</td>\\n\");\n  }\n  printf(\"</tr></table></center>\\n\");\n}\n\nint main(int argc, char **argv)\n{\n  int k, w, i, j, m;\n  int *matrix, *bitmatrix;\n  char **data, **coding, **ptrs;\n  int **smart, **dumb;\n  int *erasures, *erased;\n  double stats[3];\n  uint32_t seed;\n  \n  if (argc != 5) usage(\"Wrong number of arguments\");\n  if (sscanf(argv[1], \"%d\", &k) == 0 || k <= 0) usage(\"Bad k\");\n  if (sscanf(argv[2], \"%d\", &m) == 0 || m <= 0) usage(\"Bad m\");\n  if (sscanf(argv[3], \"%d\", &w) == 0 || w <= 0 || w > 32) usage(\"Bad w\");\n  if (sscanf(argv[4], \"%d\", &seed) == 0) usage(\"Bad seed\");\n  if (w < 30 && (k+m) > (1 << w)) usage(\"k + m is too big\");\n\n  matrix = talloc(int, m*k);\n  for (i = 0; i < m; i++) {\n    for (j = 0; j < k; j++) {\n      matrix[i*k+j] = galois_single_divide(1, i ^ (m + j), w);\n    }\n  }\n  bitmatrix = jerasure_matrix_to_bitmatrix(k, m, w, matrix);\n\n  printf(\"<HTML><TITLE>jerasure_07\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</TITLE>\\n\");\n  printf(\"<h3>jerasure_07\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</h3>\\n\");\n  printf(\"<hr>\\n\");\n\n  printf(\"Last m*w rows of the generator matrix (G^T):\\n<pre>\\n\");\n  jerasure_print_bitmatrix(bitmatrix, w*m, w*k, w);\n  printf(\"</pre><hr>\\n\");\n  \n  dumb = jerasure_dumb_bitmatrix_to_schedule(k, m, w, bitmatrix);\n  smart = jerasure_smart_bitmatrix_to_schedule(k, m, w, bitmatrix);\n\n  MOA_Seed(seed);\n  data = talloc(char *, k);\n  for (i = 0; i < k; i++) {\n    data[i] = talloc(char, sizeof(long)*w);\n    MOA_Fill_Random_Region(data[i], sizeof(long)*w);\n  }\n\n  coding = talloc(char *, m);\n  for (i = 0; i < m; i++) {\n    coding[i] = talloc(char, sizeof(long)*w);\n  }\n\n  jerasure_schedule_encode(k, m, w, dumb, data, coding, w*sizeof(long), sizeof(long));\n  jerasure_get_stats(stats);\n  printf(\"Dumb Encoding Complete: - %.0lf XOR'd bytes.  State of the system:\\n\\n\", stats[0]);\n  printf(\"<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n\n  jerasure_schedule_encode(k, m, w, smart, data, coding, w*sizeof(long), sizeof(long));\n  jerasure_get_stats(stats);\n  printf(\"Smart Encoding Complete: - %.0lf XOR'd bytes\\n\\n\", stats[0]);\n  printf(\"<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n\n  erasures = talloc(int, (m+1));\n  erased = talloc(int, (k+m));\n  for (i = 0; i < m+k; i++) erased[i] = 0;\n  for (i = 0; i < m; ) {\n    erasures[i] = MOA_Random_W(w, 1)%(k+m);\n    if (erased[erasures[i]] == 0) {\n      erased[erasures[i]] = 1;\n      bzero((erasures[i] < k) ? data[erasures[i]] : coding[erasures[i]-k], sizeof(long)*w);\n      i++;\n    }\n  }\n  erasures[i] = -1;\n\n  printf(\"Erased %d random devices:\\n\\n\", m);\n  printf(\"<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n\n  jerasure_schedule_decode_lazy(k, m, w, bitmatrix, erasures, data, coding, w*sizeof(long), sizeof(long), 1);\n  jerasure_get_stats(stats);\n\n  printf(\"State of the system after decoding: %.0lf XOR'd bytes\\n\\n\", stats[0]);\n  printf(\"<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n  \n  ptrs = talloc(char *, (k+m));\n  for (i = 0; i < k; i++) ptrs[i] = data[i];\n  for (i = 0; i < m; i++) ptrs[k+i] = coding[i];\n\n  for (j = 0; j < m; j++) bzero(coding[j], sizeof(long)*w);\n  printf(\"State of the system after erasing the coding devices:\\n\");\n  printf(\"<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n\n  jerasure_do_scheduled_operations(ptrs, smart, sizeof(long));\n  printf(\"And using <b>jerasure_do_scheduled_operations()</b>: %.0lf XOR'd bytes\\n\\n\", stats[0]);\n  printf(\"<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/jerasure_08.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <gf_rand.h>\n#include \"jerasure.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: jerasure_08 k w seed - Example schedule cache usage with RAID-6\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"       m=2.  k+m must be <= 2^w.  It sets up a RAID-6 generator matrix and encodes\\n\");\n  fprintf(stderr, \"       k sets of w*%ld bytes. It creates a schedule cache for decoding.\\n\", sizeof(long));\n  fprintf(stderr, \"       It demonstrates using the schedule cache for both encoding and decoding.\\n\");\n  fprintf(stderr, \"       Then it demonstrates using jerasure_do_parity() to re-encode the first.\\n\");\n  fprintf(stderr, \"       coding device\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"This demonstrates: jerasure_generate_schedule_cache()\\n\");\n  fprintf(stderr, \"                   jerasure_smart_bitmatrix_to_schedule()\\n\");\n  fprintf(stderr, \"                   jerasure_schedule_encode()\\n\");\n  fprintf(stderr, \"                   jerasure_schedule_decode_cache()\\n\");\n  fprintf(stderr, \"                   jerasure_free_schedule()\\n\");\n  fprintf(stderr, \"                   jerasure_free_schedule_cache()\\n\");\n  fprintf(stderr, \"                   jerasure_get_stats()\\n\");\n  fprintf(stderr, \"                   jerasure_do_parity()\\n\");\n  if (s != NULL) fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\nstatic void print_array(char **ptrs, int ndevices, int size, int packetsize, char *label)\n{\n  int i, j, x;\n  unsigned char *up;\n\n  printf(\"<center><table border=3 cellpadding=3><tr><td></td>\\n\");\n\n  for (i = 0; i < ndevices; i++) printf(\"<td align=center>%s%x</td>\\n\", label, i);\n  printf(\"</tr>\\n\");\n  printf(\"<td align=right><pre>\");\n  for (j = 0; j < size/packetsize; j++) printf(\"Packet %d\\n\", j);\n  printf(\"</pre></td>\\n\");\n  for (i = 0; i < ndevices; i++) {\n    printf(\"<td><pre>\");\n    up = (unsigned char *) ptrs[i];\n    for (j = 0; j < size/packetsize; j++) {\n      for (x = 0; x < packetsize; x++) {\n        if (x > 0 && x%4 == 0) printf(\" \");\n        printf(\"%02x\", up[j*packetsize+x]);\n      }\n      printf(\"\\n\");\n    }\n    printf(\"</td>\\n\");\n  }\n  printf(\"</tr></table></center>\\n\");\n}\n\nint main(int argc, char **argv)\n{\n  int k, w, i, j, m;\n  int *matrix, *bitmatrix;\n  char **data, **coding;\n  int **smart, ***cache;\n  int *erasures, *erased;\n  double stats[3];\n  uint32_t seed;\n  \n  if (argc != 4) usage(\"Wrong number of arguments\");\n  if (sscanf(argv[1], \"%d\", &k) == 0 || k <= 0) usage(\"Bad k\");\n  if (sscanf(argv[2], \"%d\", &w) == 0 || w <= 0 || w > 32) usage(\"Bad m\");\n  if (sscanf(argv[3], \"%d\", &seed) == 0) usage(\"Bad seed\");\n  m = 2;\n  if (w < 30 && (k+m) > (1 << w)) usage(\"k + m is too big\");\n\n  MOA_Seed(seed);\n\n  matrix = talloc(int, m*k);\n  for (j = 0; j < k; j++) matrix[j] = 1;\n  i = 1;\n  for (j = 0; j < k; j++) {\n    matrix[k+j] = i;\n    i = galois_single_multiply(i, 2, w);\n  }\n  bitmatrix = jerasure_matrix_to_bitmatrix(k, m, w, matrix);\n\n  smart = jerasure_smart_bitmatrix_to_schedule(k, m, w, bitmatrix);\n  cache = jerasure_generate_schedule_cache(k, m, w, bitmatrix, 1);\n\n  data = talloc(char *, k);\n  for (i = 0; i < k; i++) {\n    data[i] = talloc(char, sizeof(long)*w);\n    MOA_Fill_Random_Region(data[i], sizeof(long)*w);\n  }\n\n  coding = talloc(char *, m);\n  for (i = 0; i < m; i++) {\n    coding[i] = talloc(char, sizeof(long)*w);\n  }\n\n  jerasure_schedule_encode(k, m, w, smart, data, coding, w*sizeof(long), sizeof(long));\n  jerasure_get_stats(stats);\n\n  printf(\"<HTML><TITLE>jerasure_08\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</TITLE>\\n\");\n  printf(\"<h3>jerasure_08\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</h3>\\n\");\n  printf(\"<hr>\\n\");\n\n  printf(\"Encoding Complete: - %.0lf XOR'd bytes.  Here is the state of the system:\\n<p>\\n\", stats[0]);\n  printf(\"<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n\n  erasures = talloc(int, (m+1));\n  erasures[0] = k;\n  erasures[1] = k+1;\n  erasures[2] = -1;\n  for (j = 0; j < m; j++) bzero(coding[j], sizeof(long)*w);\n\n  jerasure_schedule_decode_cache(k, m, w, cache, erasures, data, coding, w*sizeof(long), sizeof(long));\n  jerasure_get_stats(stats);\n  printf(\"Encoding Using the Schedule Cache: - %.0lf XOR'd bytes\\n\\n\", stats[0]);\n  printf(\"<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n\n  erased = talloc(int, (k+m));\n  for (i = 0; i < m+k; i++) erased[i] = 0;\n  for (i = 0; i < m; ) {\n    erasures[i] = MOA_Random_W(w, 1)%(k+m);\n    if (erased[erasures[i]] == 0) {\n      erased[erasures[i]] = 1;\n      bzero((erasures[i] < k) ? data[erasures[i]] : coding[erasures[i]-k], sizeof(long)*w);\n      i++;\n    }\n  }\n  erasures[i] = -1;\n\n  printf(\"Erased %d random devices:\\n\\n\", m);\n  printf(\"<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n  \n  jerasure_schedule_decode_cache(k, m, w, cache, erasures, data, coding, w*sizeof(long), sizeof(long));\n  jerasure_get_stats(stats);\n\n  printf(\"State of the system after decoding: %.0lf XOR'd bytes\\n\\n\", stats[0]);\n  printf(\"<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n  \n  bzero(coding[0], sizeof(long)*w);\n  printf(\"Erased the first coding device:\\n\\n\");\n  printf(\"<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n  \n  jerasure_do_parity(k, data, coding[0], sizeof(long)*w);\n  printf(\"State of the system after using\\n\");\n  printf(\"<b>jerasure_do_parity()</b> to re-encode it:\\n\\n\");\n  printf(\"<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n  \n  jerasure_free_schedule(smart);\n  jerasure_free_schedule_cache(k, m, cache);\n  \n  printf(\"Smart schedule and cache freed.\\n\\n\");\n\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/liberation_01.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <stdlib.h>\n#include <gf_rand.h>\n#include \"jerasure.h\"\n#include \"liberation.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: liberation_01 k w seed - Liberation RAID-6 coding/decoding example in GF(2^w).\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"       w must be prime and k <= w.  It sets up a Liberation bit-matrix\\n\");\n  fprintf(stderr, \"       then it encodes k devices of w*%ld bytes using dumb bit-matrix scheduling.\\n\", sizeof(long));\n  fprintf(stderr, \"       It decodes using smart bit-matrix scheduling.\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"This demonstrates: liberation_coding_bitmatrix()\\n\");\n  fprintf(stderr, \"                   jerasure_smart_bitmatrix_to_schedule()\\n\");\n  fprintf(stderr, \"                   jerasure_dumb_bitmatrix_to_schedule()\\n\");\n  fprintf(stderr, \"                   jerasure_schedule_encode()\\n\");\n  fprintf(stderr, \"                   jerasure_schedule_decode_lazy()\\n\");\n  fprintf(stderr, \"                   jerasure_print_bitmatrix()\\n\");\n  fprintf(stderr, \"                   jerasure_get_stats()\\n\");\n  if (s != NULL) fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\nstatic void print_array(char **ptrs, int ndevices, int size, int packetsize, char *label)\n{\n  int i, j, x;\n  unsigned char *up;\n\n  printf(\"<center><table border=3 cellpadding=3><tr><td></td>\\n\");\n\n  for (i = 0; i < ndevices; i++) printf(\"<td align=center>%s%x</td>\\n\", label, i);\n  printf(\"</tr>\\n\");\n  printf(\"<td align=right><pre>\");\n  for (j = 0; j < size/packetsize; j++) printf(\"Packet %d\\n\", j);\n  printf(\"</pre></td>\\n\");\n  for (i = 0; i < ndevices; i++) {\n    printf(\"<td><pre>\");\n    up = (unsigned char *) ptrs[i];\n    for (j = 0; j < size/packetsize; j++) {\n      for (x = 0; x < packetsize; x++) {\n        if (x > 0 && x%4 == 0) printf(\" \");\n        printf(\"%02x\", up[j*packetsize+x]);\n      }\n      printf(\"\\n\");\n    }\n    printf(\"</td>\\n\");\n  }\n  printf(\"</tr></table></center>\\n\");\n}\n\nint main(int argc, char **argv)\n{\n  int k, w, i, m;\n  int *bitmatrix;\n  char **data, **coding;\n  int **dumb;\n  int *erasures, *erased;\n  double stats[3];\n  uint32_t seed;\n  \n  if (argc != 4) usage(\"Wrong number of arguments\");\n  if (sscanf(argv[1], \"%d\", &k) == 0 || k <= 0) usage(\"Bad k\");\n  if (sscanf(argv[2], \"%d\", &w) == 0 || w <= 0 || w > 32) usage(\"Bad w\");\n  if (sscanf(argv[3], \"%u\", &seed) == 0) usage(\"Bad seed\");\n  m = 2;\n  if (w < k) usage(\"k is too big\");\n  for (i = 2; i*i <= w; i++) if (w%i == 0) usage(\"w isn't prime\");\n\n  bitmatrix = liberation_coding_bitmatrix(k, w);\n  if (bitmatrix == NULL) {\n    usage(\"couldn't make coding matrix\");\n  }\n\n  printf(\"<HTML><TITLE>liberation_01\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</TITLE>\\n\");\n  printf(\"<h3>liberation_01\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</h3>\\n\");\n  printf(\"<hr>\\n\");\n\n  printf(\"Coding Bit-Matrix:\\n<pre>\\n\");\n  jerasure_print_bitmatrix(bitmatrix, w*m, w*k, w);\n  printf(\"</pre><hr>\\n\");\n\n  dumb = jerasure_dumb_bitmatrix_to_schedule(k, m, w, bitmatrix);\n\n  MOA_Seed(seed);\n  data = talloc(char *, k);\n  for (i = 0; i < k; i++) {\n    data[i] = talloc(char, sizeof(long)*w);\n    MOA_Fill_Random_Region(data[i], sizeof(long)*w);\n  }\n\n  coding = talloc(char *, m);\n  for (i = 0; i < m; i++) {\n    coding[i] = talloc(char, sizeof(long)*w);\n  }\n\n  jerasure_schedule_encode(k, m, w, dumb, data, coding, w*sizeof(long), sizeof(long));\n  jerasure_get_stats(stats);\n  printf(\"Smart Encoding Complete: - %.0lf XOR'd bytes.  State of the system:\\n\\n\", stats[0]);\n  printf(\"<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n\n  erasures = talloc(int, (m+1));\n  erased = talloc(int, (k+m));\n  for (i = 0; i < m+k; i++) erased[i] = 0;\n  for (i = 0; i < m; ) {\n    erasures[i] = MOA_Random_W(30,1)%(k+m);\n    if (erased[erasures[i]] == 0) {\n      erased[erasures[i]] = 1;\n      bzero((erasures[i] < k) ? data[erasures[i]] : coding[erasures[i]-k], sizeof(long)*w);\n      i++;\n    }\n  }\n  erasures[i] = -1;\n\n  printf(\"Erased %d random devices:\\n\\n\", m);\n  printf(\"<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n  \n  jerasure_schedule_decode_lazy(k, m, w, bitmatrix, erasures, data, coding, w*sizeof(long), sizeof(long), 1);\n  jerasure_get_stats(stats);\n\n  printf(\"State of the system after decoding: %.0lf XOR'd bytes\\n\\n\", stats[0]);\n  printf(\"<p>\\n\");\n  print_array(data, k, sizeof(long)*w, sizeof(long), \"D\");\n  printf(\"<p>\\n\");\n  print_array(coding, m, sizeof(long)*w, sizeof(long), \"C\");\n  printf(\"<hr>\\n\");\n  \n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/reed_sol_01.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <string.h>\n#include <gf_rand.h>\n#include \"jerasure.h\"\n#include \"reed_sol.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: reed_sol_01 k m w seed - Does a simple Reed-Solomon coding example in GF(2^w).\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"w must be 8, 16 or 32.  k+m must be <= 2^w.  It sets up a classic\\n\");\n  fprintf(stderr, \"Vandermonde-based generator matrix and encodes k devices of\\n\");\n  fprintf(stderr, \"%ld bytes each with it.  Then it decodes.\\n\", sizeof(long));\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"This demonstrates: jerasure_matrix_encode()\\n\");\n  fprintf(stderr, \"                   jerasure_matrix_decode()\\n\");\n  fprintf(stderr, \"                   jerasure_print_matrix()\\n\");\n  fprintf(stderr, \"                   reed_sol_vandermonde_coding_matrix()\\n\");\n  if (s != NULL) fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\nstatic void print_data_and_coding(int k, int m, int w, int size, \n\t\tchar **data, char **coding) \n{\n  int i, j, x;\n  int n, sp;\n\n  if(k > m) n = k;\n  else n = m;\n  sp = size * 2 + size/(w/8) + 8;\n\n  printf(\"%-*sCoding\\n\", sp, \"Data\");\n  for(i = 0; i < n; i++) {\n\t  if(i < k) {\n\t\t  printf(\"D%-2d:\", i);\n\t\t  for(j=0;j< size; j+=(w/8)) { \n\t\t\t  printf(\" \");\n\t\t\t  for(x=0;x < w/8;x++){\n\t\t\t\tprintf(\"%02x\", (unsigned char)data[i][j+x]);\n\t\t\t  }\n\t\t  }\n\t\t  printf(\"    \");\n\t  }\n\t  else printf(\"%*s\", sp, \"\");\n\t  if(i < m) {\n\t\t  printf(\"C%-2d:\", i);\n\t\t  for(j=0;j< size; j+=(w/8)) { \n\t\t\t  printf(\" \");\n\t\t\t  for(x=0;x < w/8;x++){\n\t\t\t\tprintf(\"%02x\", (unsigned char)coding[i][j+x]);\n\t\t\t  }\n\t\t  }\n\t  }\n\t  printf(\"\\n\");\n  }\n\tprintf(\"\\n\");\n}\n\nint main(int argc, char **argv)\n{\n  long l;\n  int k, w, i, j, m;\n  int *matrix;\n  char **data, **coding, **dcopy, **ccopy;\n  unsigned char uc;\n  int *erasures, *erased;\n  uint32_t seed;\n  \n  if (argc != 5) usage(NULL);\n  if (sscanf(argv[1], \"%d\", &k) == 0 || k <= 0) usage(\"Bad k\");\n  if (sscanf(argv[2], \"%d\", &m) == 0 || m <= 0) usage(\"Bad m\");\n  if (sscanf(argv[3], \"%d\", &w) == 0 || (w != 8 && w != 16 && w != 32)) usage(\"Bad w\");\n  if (sscanf(argv[4], \"%u\", &seed) == 0) usage(\"Bad seed\");\n  if (w <= 16 && k + m > (1 << w)) usage(\"k + m is too big\");\n\n  matrix = reed_sol_vandermonde_coding_matrix(k, m, w);\n\n  printf(\"<HTML><TITLE>reed_sol_01 %d %d %d %d</title>\\n\", k, m, w, seed);\n  printf(\"<h3>reed_sol_01 %d %d %d %d</h3>\\n\", k, m, w, seed);\n  printf(\"<pre>\\n\");\n  printf(\"Last m rows of the generator Matrix (G^T):\\n\\n\");\n  jerasure_print_matrix(matrix, m, k, w);\n  printf(\"\\n\");\n\n  MOA_Seed(seed);\n  data = talloc(char *, k);\n  dcopy = talloc(char *, k);\n  for (i = 0; i < k; i++) {\n    data[i] = talloc(char, sizeof(long));\n    dcopy[i] = talloc(char, sizeof(long));\n    for (j = 0; j < sizeof(long); j++) {\n      uc = MOA_Random_W(8, 1);\n      data[i][j] = (char) uc;\n    }\n    memcpy(dcopy[i], data[i], sizeof(long));\n  }\n\n  coding = talloc(char *, m);\n  ccopy = talloc(char *, m);\n  for (i = 0; i < m; i++) {\n    coding[i] = talloc(char, sizeof(long));\n    ccopy[i] = talloc(char, sizeof(long));\n  }\n\n  jerasure_matrix_encode(k, m, w, matrix, data, coding, sizeof(long));\n\n  for (i = 0; i < m; i++) {\n    memcpy(ccopy[i], coding[i], sizeof(long));\n  }\n  \n  printf(\"Encoding Complete:\\n\\n\");\n  print_data_and_coding(k, m, w, sizeof(long), data, coding);\n\n  erasures = talloc(int, (m+1));\n  erased = talloc(int, (k+m));\n  for (i = 0; i < m+k; i++) erased[i] = 0;\n  l = 0;\n  for (i = 0; i < m; ) {\n    erasures[i] = MOA_Random_W(31, 0)%(k+m);\n    if (erased[erasures[i]] == 0) {\n      erased[erasures[i]] = 1;\n      memcpy((erasures[i] < k) ? data[erasures[i]] : coding[erasures[i]-k], &l, sizeof(long));\n      i++;\n    }\n  }\n  erasures[i] = -1;\n\n  printf(\"Erased %d random devices:\\n\\n\", m);\n  print_data_and_coding(k, m, w, sizeof(long), data, coding);\n  \n  i = jerasure_matrix_decode(k, m, w, matrix, 1, erasures, data, coding, sizeof(long));\n\n  printf(\"State of the system after decoding:\\n\\n\");\n  print_data_and_coding(k, m, w, sizeof(long), data, coding);\n  \n  for (i = 0; i < k; i++) if (memcmp(data[i], dcopy[i], sizeof(long)) != 0) {\n    printf(\"ERROR: D%x after decoding does not match its state before decoding!<br>\\n\", i);\n  }\n  for (i = 0; i < m; i++) if (memcmp(coding[i], ccopy[i], sizeof(long)) != 0) {\n    printf(\"ERROR: C%x after decoding does not match its state before decoding!<br>\\n\", i);\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/reed_sol_02.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"jerasure.h\"\n#include \"reed_sol.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: reed_sol_02 k m w - Vandermonde matrices in GF(2^w).\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"       k+m must be <= 2^w.  This simply prints out the \\n\");\n  fprintf(stderr, \"       Vandermonde matrix in GF(2^w), and then the generator\\n\");\n  fprintf(stderr, \"       matrix that is constructed from it.  See [Plank-Ding-05] for\\n\");\n  fprintf(stderr, \"       information on how this construction proceeds\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"This demonstrates: reed_sol_extended_vandermonde_matrix()\\n\");\n  fprintf(stderr, \"                   reed_sol_big_vandermonde_coding_matrix()\\n\");\n  fprintf(stderr, \"                   reed_sol_vandermonde_coding_matrix()\\n\");\n  fprintf(stderr, \"                   jerasure_print_matrix()\\n\");\n  if (s != NULL) fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  int k, w, m;\n  int *matrix;\n  \n  if (argc != 4) usage(NULL);\n  if (sscanf(argv[1], \"%d\", &k) == 0 || k <= 0) usage(\"Bad k\");\n  if (sscanf(argv[2], \"%d\", &m) == 0 || m <= 0) usage(\"Bad m\");\n  if (sscanf(argv[3], \"%d\", &w) == 0 || w <= 0 || w > 32) usage(\"Bad w\");\n  if (w <= 30 && k + m > (1 << w)) usage(\"k + m is too big\");\n\n  matrix = reed_sol_extended_vandermonde_matrix(k+m, k, w);\n\n  printf(\"<HTML><TITLE>reed_sol_02 %d %d %d</title>\\n\", k, m, w);\n  printf(\"<h3>reed_sol_02 %d %d %d</h3>\\n\", k, m, w);\n  printf(\"<pre>\\n\");\n  printf(\"Extended Vandermonde Matrix:\\n\\n\");\n  jerasure_print_matrix(matrix, k+m, k, w);\n  printf(\"\\n\");\n\n  matrix = reed_sol_big_vandermonde_distribution_matrix(k+m, k, w);\n  printf(\"Vandermonde Generator Matrix (G^T):\\n\\n\");\n  jerasure_print_matrix(matrix, k+m, k, w);\n  printf(\"\\n\");\n\n  matrix = reed_sol_vandermonde_coding_matrix(k, m, w);\n  printf(\"Vandermonde Coding Matrix:\\n\\n\");\n  jerasure_print_matrix(matrix, m, k, w);\n  printf(\"\\n\");\n\n  \n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/reed_sol_03.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <gf_rand.h>\n#include \"jerasure.h\"\n#include \"reed_sol.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: reed_sol_03 k w seed - Does a simple RAID-6 coding example in GF(2^w).\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"       w must be 8, 16 or 32.  k+2 must be <= 2^w.  It sets up a classic\\n\");\n  fprintf(stderr, \"       RAID-6 coding matrix based on Anvin's optimization and encodes\\n\");\n  fprintf(stderr, \"       %ld-byte devices with it.  Then it decodes.\\n\", sizeof(long));\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"This demonstrates: reed_sol_r6_encode()\\n\");\n  fprintf(stderr, \"                   reed_sol_r6_coding_matrix()\\n\");\n  fprintf(stderr, \"                   jerasure_matrix_decode()\\n\");\n  fprintf(stderr, \"                   jerasure_print_matrix()\\n\");\n  if (s != NULL) fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\n\nstatic void print_data_and_coding(int k, int m, int w, int size, \n\t\tchar **data, char **coding) \n{\n  int i, j, x;\n  int n, sp;\n\n  if(k > m) n = k;\n  else n = m;\n  sp = size * 2 + size/(w/8) + 8;\n\n  printf(\"%-*sCoding\\n\", sp, \"Data\");\n  for(i = 0; i < n; i++) {\n\t  if(i < k) {\n\t\t  printf(\"D%-2d:\", i);\n\t\t  for(j=0;j< size; j+=(w/8)) { \n\t\t\t  printf(\" \");\n\t\t\t  for(x=0;x < w/8;x++){\n\t\t\t\tprintf(\"%02x\", (unsigned char)data[i][j+x]);\n\t\t\t  }\n\t\t  }\n\t\t  printf(\"    \");\n\t  }\n\t  else printf(\"%*s\", sp, \"\");\n\t  if(i < m) {\n\t\t  printf(\"C%-2d:\", i);\n\t\t  for(j=0;j< size; j+=(w/8)) { \n\t\t\t  printf(\" \");\n\t\t\t  for(x=0;x < w/8;x++){\n\t\t\t\tprintf(\"%02x\", (unsigned char)coding[i][j+x]);\n\t\t\t  }\n\t\t  }\n\t  }\n\t  printf(\"\\n\");\n  }\n\tprintf(\"\\n\");\n}\n\nint main(int argc, char **argv)\n{\n  long l;\n  unsigned char uc;\n  int k, w, i, j, m;\n  int *matrix;\n  char **data, **coding, **dcopy, **ccopy;\n  int *erasures, *erased;\n  uint32_t seed;\n  \n  if (argc != 4) usage(NULL);\n  if (sscanf(argv[1], \"%d\", &k) == 0 || k <= 0) usage(\"Bad k\");\n  if (sscanf(argv[2], \"%d\", &w) == 0 || (w != 8 && w != 16 && w != 32)) usage(\"Bad w\");\n  if (sscanf(argv[3], \"%d\", &seed) == 0) usage(\"Bad seed\");\n  m = 2;\n  if (w <= 16 && k + m > (1 << w)) usage(\"k + m is too big\");\n\n  MOA_Seed(seed);\n  matrix = reed_sol_r6_coding_matrix(k, w);\n\n  printf(\"<HTML><TITLE>reed_sol_03 %d %d %d</title>\\n\", k, w, seed);\n  printf(\"<h3>reed_sol_03 %d %d %d</h3>\\n\", k, w, seed);\n  printf(\"<pre>\\n\");\n\n  printf(\"Last 2 rows of the Generator Matrix:\\n\\n\");\n  jerasure_print_matrix(matrix, m, k, w);\n  printf(\"\\n\");\n\n  data = talloc(char *, k);\n  dcopy = talloc(char *, k);\n  for (i = 0; i < k; i++) {\n    data[i] = talloc(char, sizeof(long));\n    dcopy[i] = talloc(char, sizeof(long));\n    for (j = 0; j < sizeof(long); j++) {\n      uc = MOA_Random_W(8, 1) %256;\n      data[i][j] = (char) uc;   \n    }\n    memcpy(dcopy[i], data[i], sizeof(long));\n  }\n\n  coding = talloc(char *, m);\n  ccopy = talloc(char *, m);\n  for (i = 0; i < m; i++) {\n    coding[i] = talloc(char, sizeof(long));\n    ccopy[i] = talloc(char, sizeof(long));\n  }\n\n  reed_sol_r6_encode(k, w, data, coding, sizeof(long));\n  for (i = 0; i < m; i++) {\n    memcpy(ccopy[i], coding[i], sizeof(long));\n  }\n  \n  printf(\"Encoding Complete:\\n\\n\");\n  print_data_and_coding(k, m, w, sizeof(long), data, coding);\n\n  erasures = talloc(int, (m+1));\n  erased = talloc(int, (k+m));\n  for (i = 0; i < m+k; i++) erased[i] = 0;\n  l = 0;\n  for (i = 0; i < m; ) {\n    erasures[i] = ((unsigned int) MOA_Random_W(w, 1))%(k+m);\n    if (erased[erasures[i]] == 0) {\n      erased[erasures[i]] = 1;\n      memcpy((erasures[i] < k) ? data[erasures[i]] : coding[erasures[i]-k], &l, sizeof(long));\n      i++;\n    }\n  }\n  erasures[i] = -1;\n\n  printf(\"Erased %d random devices:\\n\\n\", m);\n  print_data_and_coding(k, m, w, sizeof(long), data, coding);\n  \n  i = jerasure_matrix_decode(k, m, w, matrix, 1, erasures, data, coding, sizeof(long));\n\n  printf(\"State of the system after decoding:\\n\\n\");\n  print_data_and_coding(k, m, w, sizeof(long), data, coding);\n  \n  for (i = 0; i < k; i++) if (memcmp(data[i], dcopy[i], sizeof(long)) != 0) {\n    printf(\"ERROR: D%x after decoding does not match its state before decoding!<br>\\n\", i);\n  }\n  for (i = 0; i < m; i++) if (memcmp(coding[i], ccopy[i], sizeof(long)) != 0) {\n    printf(\"ERROR: C%x after decoding does not match its state before decoding!<br>\\n\", i);\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/reed_sol_04.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <string.h>\n#include <gf_rand.h>\n#include \"jerasure.h\"\n#include \"reed_sol.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: reed_sol_04 w seed - Shows reed_sol_galois_wXX_region_multby_2\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"       w must be 8, 16 or 32.  Sets up an array of 4 random words in\\n\");\n  fprintf(stderr, \"       GF(2^w) and multiplies them by two.  \\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"This demonstrates: reed_sol_galois_w08_region_multby_2()\\n\");\n  fprintf(stderr, \"                   reed_sol_galois_w16_region_multby_2()\\n\");\n  fprintf(stderr, \"                   reed_sol_galois_w32_region_multby_2()\\n\");\n  if (s != NULL) fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\nint main(int argc, char **argv)\n{\n  unsigned char *x, *y;\n  unsigned short *xs, *ys;\n  unsigned int *xi, *yi;\n  uint32_t seed;\n  int *a32, *copy;\n  int i;\n  int w;\n  \n  if (argc != 3) usage(NULL);\n  if (sscanf(argv[1], \"%d\", &w) == 0 || (w != 8 && w != 16 && w != 32)) usage(\"Bad w\");\n  if (sscanf(argv[2], \"%d\", &seed) == 0) usage(\"Bad seed\");\n\n  printf(\"<HTML><TITLE>reed_sol_04 %d %d</title>\\n\", w, seed);\n  printf(\"<h3>reed_sol_04 %d %d</h3>\\n\", w, seed);\n  printf(\"<pre>\\n\");\n\n  MOA_Seed(seed);\n  a32 = talloc(int, 4);\n  copy = talloc(int, 4);\n  y = (unsigned char *) a32;\n  for (i = 0; i < 4*sizeof(int); i++) y[i] = MOA_Random_W(8, 1);\n  memcpy(copy, a32, sizeof(int)*4);\n\n  if (w == 8) {\n    x = (unsigned char *) copy;\n    y = (unsigned char *) a32;\n    reed_sol_galois_w08_region_multby_2((char *) a32, sizeof(int)*4);\n    for (i = 0; i < 4*sizeof(int)/sizeof(char); i++) {\n       printf(\"Char %2d: %3u *2 = %3u\\n\", i, x[i], y[i]);\n    }\n  } else if (w == 16) {\n    xs = (unsigned short *) copy;\n    ys = (unsigned short *) a32;\n    reed_sol_galois_w16_region_multby_2((char *) a32, sizeof(int)*4);\n    for (i = 0; i < 4*sizeof(int)/sizeof(short); i++) {\n       printf(\"Short %2d: %5u *2 = %5u\\n\", i, xs[i], ys[i]);\n    }\n  } else if (w == 32) {\n    xi = (unsigned int *) copy;\n    yi = (unsigned int *) a32;\n    reed_sol_galois_w16_region_multby_2((char *) a32, sizeof(int)*4);\n    for (i = 0; i < 4*sizeof(int)/sizeof(int); i++) {\n       printf(\"Int %2d: %10u *2 = %10u\\n\", i, xi[i], yi[i]);\n    }\n  } \n\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/reed_sol_test_gf.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <gf_complete.h>\n#include <gf_method.h>\n#include <gf_rand.h>\n#include <stdint.h>\n#include <sys/time.h>\n#include \"jerasure.h\"\n#include \"reed_sol.h\"\n\n#define BUFSIZE 4096\n\nstatic void *malloc16(int size) {\n    void *mem = malloc(size+16+sizeof(void*));\n    void **ptr = (void**)((long)(mem+16+sizeof(void*)) & ~(15));\n    ptr[-1] = mem;\n    return ptr;\n}\n\n#if 0\n// Unused for now.\nstatic void free16(void *ptr) {\n    free(((void**)ptr)[-1]);\n}\n#endif\n\n#define talloc(type, num) (type *) malloc16(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: reed_sol_test_gf k m w seed (additional GF args) - Tests Reed-Solomon in GF(2^w).\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"       w must be 8, 16 or 32.  k+m must be <= 2^w.\\n\");\n  fprintf(stderr, \"       See the README for information on the additional GF args.\\n\");\n  fprintf(stderr, \"       Set up a Vandermonde-based distribution matrix and encodes k devices of\\n\");\n  fprintf(stderr, \"       %d bytes each with it.  Then it decodes.\\n\", BUFSIZE);\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"This tests:        jerasure_matrix_encode()\\n\");\n  fprintf(stderr, \"                   jerasure_matrix_decode()\\n\");\n  fprintf(stderr, \"                   jerasure_print_matrix()\\n\");\n  fprintf(stderr, \"                   galois_change_technique()\\n\");\n  fprintf(stderr, \"                   reed_sol_vandermonde_coding_matrix()\\n\");\n  if (s != NULL) fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\ngf_t* get_gf(int w, int argc, char **argv, int starting)\n{\n  gf_t *gf = (gf_t*)malloc(sizeof(gf_t));\n  if (create_gf_from_argv(gf, w, argc, argv, starting) == 0) {\n    free(gf);\n    gf = NULL;\n  }\n  return gf;\n}\n\nint main(int argc, char **argv)\n{\n  int k, w, i, m;\n  int *matrix;\n  char **data, **coding, **old_values;\n  int *erasures, *erased;\n  gf_t *gf = NULL;\n  uint32_t seed;\n  \n  if (argc < 6) usage(\"Not enough command line arguments\");  \n  if (sscanf(argv[1], \"%d\", &k) == 0 || k <= 0) usage(\"Bad k\");\n  if (sscanf(argv[2], \"%d\", &m) == 0 || m <= 0) usage(\"Bad m\");\n  if (sscanf(argv[3], \"%d\", &w) == 0 || (w != 8 && w != 16 && w != 32)) usage(\"Bad w\");\n  if (sscanf(argv[4], \"%d\", &seed) == 0) usage(\"Bad seed\");\n  if (w <= 16 && k + m > (1 << w)) usage(\"k + m is too big\");\n\n  MOA_Seed(seed);\n\n  gf = get_gf(w, argc, argv, 5); \n\n  if (gf == NULL) {\n    usage(\"Invalid arguments given for GF!\\n\");\n  }\n\n  galois_change_technique(gf, w); \n\n  matrix = reed_sol_vandermonde_coding_matrix(k, m, w);\n\n  printf(\"<HTML><TITLE>reed_sol_test_gf\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</TITLE>\\n\");\n  printf(\"<h3>reed_sol_test_gf\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</h3>\\n\");\n  printf(\"<pre>\\n\");\n\n  printf(\"Last m rows of the generator matrix (G^T):\\n\\n\");\n  jerasure_print_matrix(matrix, m, k, w);\n  printf(\"\\n\");\n\n  data = talloc(char *, k);\n  for (i = 0; i < k; i++) {\n    data[i] = talloc(char, BUFSIZE);\n    MOA_Fill_Random_Region(data[i], BUFSIZE);\n  }\n\n  coding = talloc(char *, m);\n  old_values = talloc(char *, m);\n  for (i = 0; i < m; i++) {\n    coding[i] = talloc(char, BUFSIZE);\n    old_values[i] = talloc(char, BUFSIZE);\n  }\n\n  jerasure_matrix_encode(k, m, w, matrix, data, coding, BUFSIZE);\n  \n  erasures = talloc(int, (m+1));\n  erased = talloc(int, (k+m));\n  for (i = 0; i < m+k; i++) erased[i] = 0;\n  for (i = 0; i < m; ) {\n    erasures[i] = ((unsigned int)MOA_Random_W(w,1))%(k+m);\n    if (erased[erasures[i]] == 0) {\n      erased[erasures[i]] = 1;\n      memcpy(old_values[i], (erasures[i] < k) ? data[erasures[i]] : coding[erasures[i]-k], BUFSIZE);\n      bzero((erasures[i] < k) ? data[erasures[i]] : coding[erasures[i]-k], BUFSIZE);\n      i++;\n    }\n  }\n  erasures[i] = -1;\n\n  i = jerasure_matrix_decode(k, m, w, matrix, 1, erasures, data, coding, BUFSIZE);\n\n  for (i = 0; i < m; i++) {\n    if (erasures[i] < k) {\n      if (memcmp(data[erasures[i]], old_values[i], BUFSIZE)) {\n        fprintf(stderr, \"Decoding failed for %d!\\n\", erasures[i]);\n        exit(1);\n      }\n    } else {\n      if (memcmp(coding[erasures[i]-k], old_values[i], BUFSIZE)) {\n        fprintf(stderr, \"Decoding failed for %d!\\n\", erasures[i]);\n        exit(1);\n      }\n    }\n  }\n  \n  printf(\"Encoding and decoding were both successful.\\n\");\n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/reed_sol_time_gf.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan.\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank.\n */\n\n#include <sys/time.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <gf_complete.h>\n#include <gf_rand.h>\n#include <gf_method.h>\n#include <stdint.h>\n#include \"jerasure.h\"\n#include \"reed_sol.h\"\n#include \"timing.h\"\n\nstatic void *malloc16(int size) {\n    void *mem = malloc(size+16+sizeof(void*));\n    void **ptr = (void**)((long)(mem+16+sizeof(void*)) & ~(15));\n    ptr[-1] = mem;\n    return ptr;\n}\n\n#if 0\n// Unused for now.\nstatic void free16(void *ptr) {\n    free(((void**)ptr)[-1]);\n}\n#endif\n\n#define talloc(type, num) (type *) malloc16(sizeof(type)*(num))\n\nstatic void usage(char *s)\n{\n  fprintf(stderr, \"usage: reed_sol_time_gf k m w seed iterations bufsize (additional GF args) - Test and time Reed-Solomon in a particular GF(2^w).\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"       w must be 8, 16 or 32.  k+m must be <= 2^w.\\n\");\n  fprintf(stderr, \"       See the README for information on the additional GF args.\\n\");\n  fprintf(stderr, \"       Set up a Vandermonde-based distribution matrix and encodes k devices of\\n\");\n  fprintf(stderr, \"       bufsize bytes each with it.  Then it decodes.\\n\");\n  fprintf(stderr, \"       \\n\");\n  fprintf(stderr, \"This tests:        jerasure_matrix_encode()\\n\");\n  fprintf(stderr, \"                   jerasure_matrix_decode()\\n\");\n  fprintf(stderr, \"                   jerasure_print_matrix()\\n\");\n  fprintf(stderr, \"                   galois_change_technique()\\n\");\n  fprintf(stderr, \"                   reed_sol_vandermonde_coding_matrix()\\n\");\n  if (s != NULL) fprintf(stderr, \"%s\\n\", s);\n  exit(1);\n}\n\ngf_t* get_gf(int w, int argc, char **argv, int starting)\n{\n  gf_t *gf = (gf_t*)malloc(sizeof(gf_t));\n  if (create_gf_from_argv(gf, w, argc, argv, starting) == 0) {\n    free(gf);\n    gf = NULL;\n  }\n  return gf;\n}\n\nint main(int argc, char **argv)\n{\n  int k, w, i, m, iterations, bufsize;\n  int *matrix;\n  char **data, **coding, **old_values;\n  int *erasures, *erased;\n  uint32_t seed;\n  double t = 0, total_time = 0;\n  gf_t *gf = NULL;\n  \n  if (argc < 8) usage(NULL);  \n  if (sscanf(argv[1], \"%d\", &k) == 0 || k <= 0) usage(\"Bad k\");\n  if (sscanf(argv[2], \"%d\", &m) == 0 || m <= 0) usage(\"Bad m\");\n  if (sscanf(argv[3], \"%d\", &w) == 0 || (w != 8 && w != 16 && w != 32)) usage(\"Bad w\");\n  if (sscanf(argv[4], \"%d\", &seed) == 0) usage(\"Bad seed\");\n  if (sscanf(argv[5], \"%d\", &iterations) == 0) usage(\"Bad iterations\");\n  if (sscanf(argv[6], \"%d\", &bufsize) == 0) usage(\"Bad bufsize\");\n  if (w <= 16 && k + m > (1 << w)) usage(\"k + m is too big\");\n\n  MOA_Seed(seed);\n\n  gf = get_gf(w, argc, argv, 7); \n\n  if (gf == NULL) {\n    usage(\"Invalid arguments given for GF!\\n\");\n  }\n\n  galois_change_technique(gf, w); \n\n  matrix = reed_sol_vandermonde_coding_matrix(k, m, w);\n\n  printf(\"<HTML><TITLE>reed_sol_time_gf\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</TITLE>\\n\");\n  printf(\"<h3>reed_sol_time_gf\");\n  for (i = 1; i < argc; i++) printf(\" %s\", argv[i]);\n  printf(\"</h3>\\n\");\n  printf(\"<pre>\\n\");\n\n  printf(\"Last m rows of the generator matrix (G^T):\\n\\n\");\n  jerasure_print_matrix(matrix, m, k, w);\n  printf(\"\\n\");\n\n  data = talloc(char *, k);\n  for (i = 0; i < k; i++) {\n    data[i] = talloc(char, bufsize);\n    MOA_Fill_Random_Region(data[i], bufsize);\n  }\n\n  coding = talloc(char *, m);\n  old_values = talloc(char *, m);\n  for (i = 0; i < m; i++) {\n    coding[i] = talloc(char, bufsize);\n    old_values[i] = talloc(char, bufsize);\n  }\n\n  for (i = 0; i < iterations; i++) {\n    t = timing_now();\n    jerasure_matrix_encode(k, m, w, matrix, data, coding, bufsize);\n    total_time += timing_now() - t;\n  }\n\n  printf(\"Encode throughput for %d iterations: %.2f MB/s (%.2f sec)\\n\", iterations, (double)(k*iterations*bufsize/1024/1024) / total_time, total_time);\n  \n  erasures = talloc(int, (m+1));\n  erased = talloc(int, (k+m));\n  for (i = 0; i < m+k; i++) erased[i] = 0;\n  for (i = 0; i < m; ) {\n    erasures[i] = ((unsigned int)MOA_Random_W(w, 1))%(k+m);\n    if (erased[erasures[i]] == 0) {\n      erased[erasures[i]] = 1;\n      memcpy(old_values[i], (erasures[i] < k) ? data[erasures[i]] : coding[erasures[i]-k], bufsize);\n      bzero((erasures[i] < k) ? data[erasures[i]] : coding[erasures[i]-k], bufsize);\n      i++;\n    }\n  }\n  erasures[i] = -1;\n\n  for (i = 0; i < iterations; i++) {\n    t = timing_now();\n    jerasure_matrix_decode(k, m, w, matrix, 1, erasures, data, coding, bufsize);\n    total_time += timing_now() - t;\n  }\n  \n  printf(\"Decode throughput for %d iterations: %.2f MB/s (%.2f sec)\\n\", iterations, (double)(k*iterations*bufsize/1024/1024) / total_time, total_time);\n\n  for (i = 0; i < m; i++) {\n    if (erasures[i] < k) {\n      if (memcmp(data[erasures[i]], old_values[i], bufsize)) {\n        fprintf(stderr, \"Decoding failed for %d!\\n\", erasures[i]);\n        exit(1);\n      }\n    } else {\n      if (memcmp(coding[erasures[i]-k], old_values[i], bufsize)) {\n        fprintf(stderr, \"Decoding failed for %d!\\n\", erasures[i]);\n        exit(1);\n      }\n    }\n  }\n  \n  return 0;\n}\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/test_all_gfs.sh",
    "content": "#!/bin/bash\n#\n# Copyright (c) 2013, James S. Plank and Kevin Greenan\n# All rights reserved.\n#\n# Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n# Coding Techniques\n#\n# Revision 2.0: Galois Field backend now links to GF-Complete\n#\n# Redistribution and use in source and binary forms, with or without\n# modification, are permitted provided that the following conditions\n# are met:\n#\n#  - Redistributions of source code must retain the above copyright\n#    notice, this list of conditions and the following disclaimer.\n#\n#  - Redistributions in binary form must reproduce the above copyright\n#    notice, this list of conditions and the following disclaimer in\n#    the documentation and/or other materials provided with the\n#    distribution.\n#\n#  - Neither the name of the University of Tennessee nor the names of its\n#    contributors may be used to endorse or promote products derived\n#    from this software without specific prior written permission.\n#\n# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n# \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n# WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n# POSSIBILITY OF SUCH DAMAGE.\n#\nGF_METHODS=${GF_COMPLETE_DIR:-/usr/local/bin}/gf_methods\nk=12\nm=3\nseed=1370\n\nif ! test -x ${GF_METHODS} ; then\n    ${GF_METHODS}\n    exit 1\nfi\n\n# Test all w=8\n${GF_METHODS} 8 -B -L | awk -F: '{ if ($1 == \"w=8\") print $2; }' |\nwhile read method; do\n  echo \"Testing ${k} ${m} 8 $seed ${method}\"\n  $VALGRIND ./reed_sol_test_gf ${k} ${m} 8 $seed ${method} | tail -n 1\n  if [[ $? != \"0\" ]]; then\n    echo \"Failed test for ${k} ${m} 8 $seed ${method}\"\n    exit 1\n  fi\ndone\n\nif [[ $? == \"1\" ]]; then\n  exit 1\nfi\n\n\n# Test all w=16\n${GF_METHODS} 16 -B -L | awk -F: '{ if ($1 == \"w=16\") print $2; }' |\nwhile read method; do\n  echo \"Testing ${k} ${m} 16 $seed ${method}\"\n  $VALGRIND ./reed_sol_test_gf ${k} ${m} 16 $seed ${method} | tail -n 1\n  if [[ $? != \"0\" ]]; then\n    echo \"Failed test for ${k} ${m} 16 $seed ${method}\"\n    exit 1\n  fi\ndone\n\n\nif [[ $? == \"1\" ]]; then\n  exit 1\nfi\n\n# Test all w=32\n${GF_METHODS} 32 -B -L | awk -F: '{ if ($1 == \"w=32\") print $2; }' |\nwhile read method; do\n  echo \"Testing ${k} ${m} 32 $seed ${method}\"\n  $VALGRIND ./reed_sol_test_gf ${k} ${m} 32 $seed ${method} | tail -n 1\n  if [[ $? != \"0\" ]]; then\n    echo \"Failed test for ${k} ${m} 32 $seed ${method}\"\n    exit 1\n  fi\ndone\n\n\nif [[ $? == \"1\" ]]; then\n  exit 1\nfi\n\necho \"Passed all tests!\"\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/test_galois.c",
    "content": "#include <assert.h>\n#include \"galois.h\"\n\nint main(int argc, char **argv)\n{\n  assert(galois_init_default_field(4) == 0);\n  assert(galois_uninit_field(4) == 0);\n  assert(galois_init_default_field(4) == 0);\n  assert(galois_uninit_field(4) == 0);\n\n  assert(galois_init_default_field(8) == 0);\n  assert(galois_uninit_field(8) == 0);\n  assert(galois_init_default_field(8) == 0);\n  assert(galois_uninit_field(8) == 0);\n\n  return 0;\n}\n/*\n * Local Variables:\n * compile-command: \"make test_galois && \n *    libtool --mode=execute valgrind --tool=memcheck --leak-check=full ./test_galois\"\n * End:\n */\n"
  },
  {
    "path": "fst/layout/jerasure/Examples/time_all_gfs_argv_init.sh",
    "content": "#\n#\n# Copyright (c) 2013, James S. Plank and Kevin Greenan\n# All rights reserved.\n#\n# Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n# Coding Techniques\n#\n# Revision 2.0: Galois Field backend now links to GF-Complete\n#\n# Redistribution and use in source and binary forms, with or without\n# modification, are permitted provided that the following conditions\n# are met:\n#\n#  - Redistributions of source code must retain the above copyright\n#    notice, this list of conditions and the following disclaimer.\n#\n#  - Redistributions in binary form must reproduce the above copyright\n#    notice, this list of conditions and the following disclaimer in\n#    the documentation and/or other materials provided with the\n#    distribution.\n#\n#  - Neither the name of the University of Tennessee nor the names of its\n#    contributors may be used to endorse or promote products derived\n#    from this software without specific prior written permission.\n#\n# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n# \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n# WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n# POSSIBILITY OF SUCH DAMAGE.\n#\nGF_COMPLETE_DIR=/usr/local/bin\nGF_METHODS=${GF_COMPLETE_DIR}/gf_methods\nITERATIONS=128\nBUFSIZE=65536\nk=12\nm=3\nseed=1370\n\nfor w in 8 16 32 ; do \n  ${GF_METHODS} $w -B -L | awk -F: '{ if ($1 == \"w='$w'\") print $2; }' |\n  while read method; do\n    echo \"Testing ${k} ${m} ${w} ${seed} ${ITERATIONS} ${BUFSIZE} ${method}\"\n    ./reed_sol_time_gf ${k} ${m} ${w} ${seed} ${ITERATIONS} ${BUFSIZE} ${method} | tail -n 2\n    if [[ $? != \"0\" ]]; then\n      echo \"Failed test for ${k} ${m} ${w} ${seed} ${ITERATIONS} ${BUFSIZE} ${method}\"\n      exit 1\n    fi\n  done\n\n  if [[ $? == \"1\" ]]; then\n    exit 1\n  fi\ndone\n\necho \"Passed all tests!\"\n"
  },
  {
    "path": "fst/layout/jerasure/License.txt",
    "content": "\nCopyright (c) 2013, James S. Plank and Kevin Greenan\nAll rights reserved.\n\nJerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure Coding Techniques\n\nRevision 2.0: Galois Field backend now links to GF-Complete\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n - Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n\n - Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in\n   the documentation and/or other materials provided with the\n   distribution.\n\n - Neither the name of the University of Tennessee nor the names of its\n   contributors may be used to endorse or promote products derived\n   from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\nINCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\nBUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\nOF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\nAND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\nWAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE.\n\n"
  },
  {
    "path": "fst/layout/jerasure/Makefile.am",
    "content": "# Jerasure Automake file\n\nSUBDIRS = src Examples\n\nEXTRA_DIST = Manual.pdf PERF.txt\n"
  },
  {
    "path": "fst/layout/jerasure/NEWS",
    "content": ""
  },
  {
    "path": "fst/layout/jerasure/PERF.txt",
    "content": "This reflects time_all_gfs_argv_init.sh run on a MacBook Air with 4 GB of memory and a 1.7 GHz Intel Core i5\n\n#uname -a \n11.4.2 Darwin Kernel Version 11.4.2: Thu Aug 23 16:25:48 PDT 2012; root:xnu-1699.32.7~1/RELEASE_X86_64 x86_64\n\n<Arguments to reed_sol_time_gf> <Throughput MB/s>\n\n_12_3_8_128_65536_-m_SPLIT_8_4_-r_SSE_- 2813.34\n_12_3_8_128_65536_-m_COMPOSITE_2_-_-r_ALTMAP_- 2808.39\n_12_3_8_128_65536_- 2797.62\n_12_3_8_128_65536_-m_SPLIT_8_4_- 2793.14\n_12_3_8_128_65536_-m_SPLIT_8_4_-r_SSE_-d_EUCLID_- 2779.97\n_12_3_8_128_65536_-m_SPLIT_8_4_-d_EUCLID_- 2776.50\n_12_3_8_128_65536_-m_SPLIT_8_4_-d_MATRIX_- 2762.82\n_12_3_8_128_65536_-m_SPLIT_8_4_-r_SSE_-d_MATRIX_- 2711.49\n_12_3_16_128_65536_-m_COMPOSITE_2_-_-r_ALTMAP_- 2700.11\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_ALTMAP_- 2367.78\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_SSE_-r_ALTMAP_-d_MATRIX_- 2365.21\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_ALTMAP_-d_EUCLID_- 2364.95\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_SSE_-r_ALTMAP_- 2356.81\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_SSE_-r_ALTMAP_-d_EUCLID_- 2319.16\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_ALTMAP_-d_MATRIX_- 2307.02\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_SSE_-d_EUCLID_- 1879.46\n_12_3_16_128_65536_- 1877.06\n_12_3_16_128_65536_-m_SPLIT_16_4_-d_MATRIX_- 1868.61\n_12_3_16_128_65536_-m_SPLIT_16_4_- 1864.30\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_SSE_-d_MATRIX_- 1861.95\n_12_3_16_128_65536_-m_SPLIT_16_4_-d_EUCLID_- 1854.40\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_SSE_- 1850.96\n_12_3_32_128_65536_-m_COMPOSITE_2_-_-r_ALTMAP_- 1828.50\n_12_3_8_128_65536_-m_TABLE_-r_CAUCHY_- 1534.09\n_12_3_8_128_65536_-m_SPLIT_8_4_-r_CAUCHY_-d_MATRIX_- 1531.78\n_12_3_8_128_65536_-m_LOG_ZERO_EXT_-r_CAUCHY_-d_EUCLID_- 1526.97\n_12_3_8_128_65536_-m_TABLE_-r_CAUCHY_-d_EUCLID_- 1527.21\n_12_3_8_128_65536_-m_LOG_-r_CAUCHY_- 1524.65\n_12_3_8_128_65536_-m_SHIFT_-r_CAUCHY_-d_MATRIX_- 1525.47\n_12_3_8_128_65536_-m_BYTWO_b_-r_CAUCHY_-d_EUCLID_- 1525.53\n_12_3_8_128_65536_-m_LOG_ZERO_EXT_-r_CAUCHY_- 1522.80\n_12_3_8_128_65536_-m_LOG_ZERO_-r_CAUCHY_-d_EUCLID_- 1522.57\n_12_3_8_128_65536_-m_BYTWO_p_-r_CAUCHY_-d_EUCLID_- 1519.20\n_12_3_8_128_65536_-m_BYTWO_p_-r_CAUCHY_-d_MATRIX_- 1517.28\n_12_3_8_128_65536_-m_LOG_-r_CAUCHY_-d_MATRIX_- 1515.57\n_12_3_8_128_65536_-m_TABLE_-r_CAUCHY_-d_MATRIX_- 1516.30\n_12_3_8_128_65536_-m_LOG_ZERO_-r_CAUCHY_- 1516.35\n_12_3_8_128_65536_-m_LOG_ZERO_-r_CAUCHY_-d_MATRIX_- 1515.13\n_12_3_8_128_65536_-m_SHIFT_-r_CAUCHY_-d_EUCLID_- 1513.07\n_12_3_8_128_65536_-m_LOG_ZERO_EXT_-r_CAUCHY_-d_MATRIX_- 1512.42\n_12_3_8_128_65536_-m_LOG_-r_CAUCHY_-d_EUCLID_- 1510.87\n_12_3_8_128_65536_-m_BYTWO_p_-r_CAUCHY_- 1511.34\n_12_3_8_128_65536_-m_BYTWO_b_-r_CAUCHY_- 1508.70\n_12_3_8_128_65536_-m_SPLIT_8_4_-r_CAUCHY_- 1508.28\n_12_3_8_128_65536_-m_SHIFT_-r_CAUCHY_- 1504.25\n_12_3_8_128_65536_-m_SPLIT_8_4_-r_CAUCHY_-d_EUCLID_- 1499.62\n_12_3_8_128_65536_-m_BYTWO_b_-r_CAUCHY_-d_MATRIX_- 1488.90\n_12_3_32_128_65536_-m_SPLIT_32_4_-r_SSE_- 1337.74\n_12_3_32_128_65536_-m_SPLIT_32_4_-r_SSE_-d_EUCLID_- 1334.65\n_12_3_32_128_65536_-m_SPLIT_32_4_-r_SSE_-d_MATRIX_- 1326.72\n_12_3_32_128_65536_-m_SPLIT_32_4_-d_MATRIX_- 1325.45\n_12_3_32_128_65536_- 1325.80\n_12_3_32_128_65536_-m_SPLIT_32_4_-d_EUCLID_- 1325.00\n_12_3_32_128_65536_-m_SPLIT_32_4_- 1300.37\n_12_3_32_128_65536_-m_SPLIT_32_4_-r_ALTMAP_-d_MATRIX_- 1196.01\n_12_3_32_128_65536_-m_SPLIT_32_4_-r_ALTMAP_- 1196.97\n_12_3_32_128_65536_-m_SPLIT_32_4_-r_ALTMAP_-d_EUCLID_- 1193.25\n_12_3_32_128_65536_-m_SPLIT_32_4_-r_SSE_-r_ALTMAP_- 1191.37\n_12_3_32_128_65536_-m_SPLIT_32_4_-r_SSE_-r_ALTMAP_-d_EUCLID_- 1188.98\n_12_3_32_128_65536_-m_SPLIT_32_4_-r_SSE_-r_ALTMAP_-d_MATRIX_- 1187.58\n_12_3_8_128_65536_-m_TABLE_-r_DOUBLE_- 1015.70\n_12_3_8_128_65536_-m_TABLE_-r_DOUBLE_-d_EUCLID_- 999.25\n_12_3_8_128_65536_-m_TABLE_-r_DOUBLE_-d_MATRIX_- 996.35\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_CAUCHY_-d_EUCLID_- 971.19\n_12_3_16_128_65536_-m_LOG_-r_CAUCHY_-d_MATRIX_- 972.08\n_12_3_16_128_65536_-m_LOG_ZERO_-r_CAUCHY_- 967.87\n_12_3_16_128_65536_-m_LOG_ZERO_-r_CAUCHY_-d_EUCLID_- 965.51\n_12_3_16_128_65536_-m_SPLIT_16_8_-r_CAUCHY_-d_MATRIX_- 965.75\n_12_3_16_128_65536_-m_LOG_ZERO_-r_CAUCHY_-d_MATRIX_- 965.41\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_CAUCHY_- 966.20\n_12_3_16_128_65536_-m_SPLIT_16_8_-r_CAUCHY_-d_EUCLID_- 964.21\n_12_3_16_128_65536_-m_LOG_-r_CAUCHY_- 962.11\n_12_3_16_128_65536_-m_SPLIT_8_8_-r_CAUCHY_-d_MATRIX_- 959.53\n_12_3_16_128_65536_-m_LOG_-r_CAUCHY_-d_EUCLID_- 959.75\n_12_3_16_128_65536_-m_SPLIT_16_8_-r_CAUCHY_- 958.98\n_12_3_16_128_65536_-m_BYTWO_p_-r_CAUCHY_-d_MATRIX_- 957.03\n_12_3_16_128_65536_-m_BYTWO_b_-r_CAUCHY_-d_MATRIX_- 955.82\n_12_3_16_128_65536_-m_GROUP_4_4_-r_CAUCHY_-d_EUCLID_- 956.46\n_12_3_16_128_65536_-m_GROUP_4_4_-r_CAUCHY_- 955.41\n_12_3_16_128_65536_-m_GROUP_4_4_-r_CAUCHY_-d_MATRIX_- 955.45\n_12_3_16_128_65536_-m_SPLIT_8_8_-r_CAUCHY_-d_EUCLID_- 955.93\n_12_3_16_128_65536_-m_BYTWO_p_-r_CAUCHY_-d_EUCLID_- 952.43\n_12_3_16_128_65536_-m_BYTWO_b_-r_CAUCHY_-d_EUCLID_- 951.90\n_12_3_16_128_65536_-m_BYTWO_p_-r_CAUCHY_- 945.16\n_12_3_16_128_65536_-m_BYTWO_b_-r_CAUCHY_- 945.30\n_12_3_16_128_65536_-m_SHIFT_-r_CAUCHY_- 944.22\n_12_3_16_128_65536_-m_SHIFT_-r_CAUCHY_-d_MATRIX_- 939.66\n_12_3_16_128_65536_-m_SHIFT_-r_CAUCHY_-d_EUCLID_- 934.56\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_CAUCHY_-d_MATRIX_- 917.26\n_12_3_16_128_65536_-m_SPLIT_8_8_-r_CAUCHY_- 831.63\n_12_3_16_128_65536_-m_SPLIT_16_8_- 621.13\n_12_3_16_128_65536_-m_SPLIT_16_8_-d_MATRIX_- 619.98\n_12_3_16_128_65536_-m_SPLIT_8_8_- 614.67\n_12_3_16_128_65536_-m_SPLIT_16_8_-d_EUCLID_- 603.05\n_12_3_16_128_65536_-m_SPLIT_8_8_-d_MATRIX_- 553.45\n_12_3_16_128_65536_-m_SPLIT_8_8_-d_EUCLID_- 543.90\n_12_3_8_128_65536_-m_BYTWO_b_-d_EUCLID_- 522.34\n_12_3_8_128_65536_-m_BYTWO_b_-r_SSE_- 520.97\n_12_3_8_128_65536_-m_BYTWO_b_-r_SSE_-d_MATRIX_- 520.49\n_12_3_8_128_65536_-m_BYTWO_b_-d_MATRIX_- 518.74\n_12_3_8_128_65536_-m_BYTWO_b_-r_SSE_-d_EUCLID_- 518.95\n_12_3_8_128_65536_-m_BYTWO_b_- 519.19\n_12_3_16_128_65536_-m_LOG_-d_MATRIX_- 485.44\n_12_3_16_128_65536_-m_LOG_- 483.58\n_12_3_16_128_65536_-m_LOG_-d_EUCLID_- 480.18\n_12_3_16_128_65536_-m_LOG_ZERO_-d_EUCLID_- 441.75\n_12_3_16_128_65536_-m_LOG_ZERO_-d_MATRIX_- 427.39\n_12_3_16_128_65536_-m_LOG_ZERO_- 419.07\n_12_3_32_128_65536_-m_SPLIT_8_8_-d_EUCLID_- 415.80\n_12_3_32_128_65536_-m_SPLIT_8_8_- 415.31\n_12_3_32_128_65536_-m_SPLIT_32_8_-d_EUCLID_- 416.06\n_12_3_32_128_65536_-m_BYTWO_p_-r_CAUCHY_-d_EUCLID_- 415.84\n_12_3_8_128_65536_-m_BYTWO_p_-d_MATRIX_- 416.50\n_12_3_8_128_65536_-m_BYTWO_p_- 416.57\n_12_3_32_128_65536_-m_SPLIT_32_8_- 416.36\n_12_3_8_128_65536_-m_BYTWO_p_-r_SSE_- 414.77\n_12_3_8_128_65536_-m_BYTWO_p_-r_SSE_-d_EUCLID_- 414.64\n_12_3_32_128_65536_-m_BYTWO_b_-r_CAUCHY_-d_EUCLID_- 415.14\n_12_3_32_128_65536_-m_BYTWO_b_-r_CAUCHY_- 413.58\n_12_3_32_128_65536_-m_GROUP_4_8_-r_CAUCHY_-d_EUCLID_- 413.55\n_12_3_32_128_65536_-m_SPLIT_8_8_-r_CAUCHY_-d_EUCLID_- 413.76\n_12_3_32_128_65536_-m_SPLIT_32_8_-d_MATRIX_- 413.83\n_12_3_32_128_65536_-m_BYTWO_p_-r_CAUCHY_-d_MATRIX_- 412.09\n_12_3_32_128_65536_-m_SPLIT_32_16_-r_CAUCHY_- 413.06\n_12_3_32_128_65536_-m_GROUP_4_4_-r_CAUCHY_-d_EUCLID_- 413.62\n_12_3_32_128_65536_-m_SPLIT_8_8_-r_CAUCHY_-d_MATRIX_- 411.67\n_12_3_32_128_65536_-m_GROUP_4_4_-r_CAUCHY_- 412.35\n_12_3_32_128_65536_-m_SPLIT_32_4_-r_CAUCHY_-d_MATRIX_- 412.30\n_12_3_32_128_65536_-m_SPLIT_32_4_-r_CAUCHY_-d_EUCLID_- 411.08\n_12_3_32_128_65536_-m_BYTWO_p_-r_CAUCHY_- 411.89\n_12_3_32_128_65536_-m_SPLIT_8_8_-r_CAUCHY_- 412.23\n_12_3_8_128_65536_-m_BYTWO_p_-r_SSE_-d_MATRIX_- 413.10\n_12_3_32_128_65536_-m_SPLIT_32_4_-r_CAUCHY_- 411.24\n_12_3_32_128_65536_-m_SPLIT_32_16_-r_CAUCHY_-d_EUCLID_- 411.23\n_12_3_32_128_65536_-m_SPLIT_32_16_-r_CAUCHY_-d_MATRIX_- 411.49\n_12_3_32_128_65536_-m_GROUP_4_4_-r_CAUCHY_-d_MATRIX_- 410.80\n_12_3_32_128_65536_-m_SPLIT_32_8_-r_CAUCHY_-d_EUCLID_- 409.80\n_12_3_32_128_65536_-m_SPLIT_32_8_-r_CAUCHY_- 408.72\n_12_3_32_128_65536_-m_SPLIT_32_8_-r_CAUCHY_-d_MATRIX_- 409.51\n_12_3_32_128_65536_-m_GROUP_4_8_-r_CAUCHY_- 409.34\n_12_3_8_128_65536_-m_BYTWO_p_-d_EUCLID_- 405.75\n_12_3_32_128_65536_-m_BYTWO_b_-r_CAUCHY_-d_MATRIX_- 406.53\n_12_3_32_128_65536_-m_SPLIT_8_8_-d_MATRIX_- 405.91\n_12_3_32_128_65536_-m_SHIFT_-r_CAUCHY_- 403.98\n_12_3_32_128_65536_-m_SHIFT_-r_CAUCHY_-d_EUCLID_- 404.79\n_12_3_32_128_65536_-m_SHIFT_-r_CAUCHY_-d_MATRIX_- 401.29\n_12_3_8_128_65536_-m_TABLE_-r_DOUBLE_-r_LAZY_-d_EUCLID_- 384.38\n_12_3_8_128_65536_-m_TABLE_-r_DOUBLE_-r_LAZY_- 381.47\n_12_3_8_128_65536_-m_TABLE_-r_DOUBLE_-r_LAZY_-d_MATRIX_- 381.49\n_12_3_32_128_65536_-m_GROUP_4_8_-r_CAUCHY_-d_MATRIX_- 374.88\n_12_3_8_128_65536_-m_LOG_ZERO_-d_MATRIX_- 349.17\n_12_3_8_128_65536_-m_LOG_ZERO_- 349.34\n_12_3_8_128_65536_-m_LOG_ZERO_EXT_-d_MATRIX_- 349.43\n_12_3_8_128_65536_-m_LOG_ZERO_-d_EUCLID_- 349.61\n_12_3_8_128_65536_-m_LOG_ZERO_EXT_- 349.71\n_12_3_8_128_65536_-m_TABLE_-d_EUCLID_- 343.28\n_12_3_8_128_65536_-m_LOG_ZERO_EXT_-d_EUCLID_- 341.73\n_12_3_8_128_65536_-m_TABLE_-d_MATRIX_- 329.42\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_NOSSE_- 327.32\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_NOSSE_-d_EUCLID_- 318.34\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_NOSSE_-d_MATRIX_- 317.40\n_12_3_8_128_65536_-m_TABLE_- 316.84\n_12_3_32_128_65536_-m_COMPOSITE_2_-_- 301.69\n_12_3_8_128_65536_-m_LOG_-d_MATRIX_- 281.59\n_12_3_8_128_65536_-m_LOG_-d_EUCLID_- 281.28\n_12_3_8_128_65536_-m_LOG_- 279.75\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_NOSSE_-r_ALTMAP_- 275.37\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_NOSSE_-r_ALTMAP_-d_EUCLID_- 276.07\n_12_3_16_128_65536_-m_SPLIT_16_4_-r_NOSSE_-r_ALTMAP_-d_MATRIX_- 269.77\n_12_3_8_128_65536_-m_BYTWO_b_-r_NOSSE_-d_MATRIX_- 257.86\n_12_3_8_128_65536_-m_BYTWO_b_-r_NOSSE_-d_EUCLID_- 256.55\n_12_3_8_128_65536_-m_SPLIT_8_4_-r_NOSSE_-d_EUCLID_- 236.07\n_12_3_8_128_65536_-m_SPLIT_8_4_-r_NOSSE_-d_MATRIX_- 236.76\n_12_3_8_128_65536_-m_SPLIT_8_4_-r_NOSSE_- 236.71\n_12_3_16_128_65536_-m_BYTWO_b_-d_EUCLID_- 217.68\n_12_3_16_128_65536_-m_BYTWO_b_- 217.87\n_12_3_16_128_65536_-m_BYTWO_b_-r_SSE_- 217.10\n_12_3_16_128_65536_-m_BYTWO_b_-r_SSE_-d_MATRIX_- 216.91\n_12_3_16_128_65536_-m_BYTWO_b_-r_SSE_-d_EUCLID_- 217.27\n_12_3_16_128_65536_-m_BYTWO_b_-d_MATRIX_- 215.01\n_12_3_8_128_65536_-m_BYTWO_p_-r_NOSSE_- 206.00\n_12_3_8_128_65536_-m_BYTWO_p_-r_NOSSE_-d_MATRIX_- 205.66\n_12_3_8_128_65536_-m_BYTWO_p_-r_NOSSE_-d_EUCLID_- 204.44\n_12_3_32_128_65536_-m_SPLIT_32_4_-r_NOSSE_-d_MATRIX_- 199.64\n_12_3_32_128_65536_-m_SPLIT_32_4_-r_NOSSE_-d_EUCLID_- 198.20\n_12_3_16_128_65536_-m_COMPOSITE_2_-_- 182.98\n_12_3_16_128_65536_-m_BYTWO_p_-r_SSE_- 183.34\n_12_3_16_128_65536_-m_BYTWO_p_-r_SSE_-d_EUCLID_- 182.87\n_12_3_16_128_65536_-m_BYTWO_p_-d_EUCLID_- 183.57\n_12_3_16_128_65536_-m_BYTWO_p_-d_MATRIX_- 183.95\n_12_3_16_128_65536_-m_BYTWO_p_-r_SSE_-d_MATRIX_- 179.94\n_12_3_16_128_65536_-m_GROUP_4_4_- 166.90\n_12_3_32_128_65536_-m_SPLIT_32_16_- 167.55\n_12_3_32_128_65536_-m_SPLIT_32_16_-d_MATRIX_- 167.51\n_12_3_32_128_65536_-m_SPLIT_32_16_-d_EUCLID_- 167.70\n_12_3_16_128_65536_-m_GROUP_4_4_-d_EUCLID_- 166.56\n_12_3_16_128_65536_-m_GROUP_4_4_-d_MATRIX_- 167.07\n_12_3_16_128_65536_-m_BYTWO_b_-r_NOSSE_-d_EUCLID_- 110.64\n_12_3_16_128_65536_-m_BYTWO_b_-r_NOSSE_- 110.76\n_12_3_16_128_65536_-m_BYTWO_b_-r_NOSSE_-d_MATRIX_- 111.17\n_12_3_16_128_65536_-m_BYTWO_p_-r_NOSSE_- 100.39\n_12_3_16_128_65536_-m_BYTWO_p_-r_NOSSE_-d_EUCLID_- 100.27\n_12_3_16_128_65536_-m_BYTWO_p_-r_NOSSE_-d_MATRIX_- 100.18\n_12_3_32_128_65536_-m_BYTWO_b_-r_SSE_- 96.85\n_12_3_32_128_65536_-m_BYTWO_b_-d_MATRIX_- 97.76\n_12_3_32_128_65536_-m_BYTWO_b_-r_SSE_-d_EUCLID_- 97.69\n_12_3_32_128_65536_-m_BYTWO_b_-r_SSE_-d_MATRIX_- 97.48\n_12_3_32_128_65536_-m_BYTWO_b_-d_EUCLID_- 97.42\n_12_3_32_128_65536_-m_BYTWO_b_- 97.54\n_12_3_32_128_65536_-m_BYTWO_p_- 86.61\n_12_3_32_128_65536_-m_BYTWO_p_-d_MATRIX_- 86.81\n_12_3_32_128_65536_-m_BYTWO_p_-r_SSE_-d_MATRIX_- 86.31\n_12_3_32_128_65536_-m_BYTWO_p_-d_EUCLID_- 86.70\n_12_3_32_128_65536_-m_BYTWO_p_-r_SSE_- 86.69\n_12_3_32_128_65536_-m_BYTWO_p_-r_SSE_-d_EUCLID_- 86.80\n_12_3_8_128_65536_-m_COMPOSITE_2_-_- 76.58\n_12_3_32_128_65536_-m_GROUP_4_8_-d_EUCLID_- 57.06\n_12_3_32_128_65536_-m_GROUP_4_8_-d_MATRIX_- 57.08\n_12_3_32_128_65536_-m_GROUP_4_4_-d_EUCLID_- 56.59\n_12_3_32_128_65536_-m_GROUP_4_4_-d_MATRIX_- 56.91\n_12_3_32_128_65536_-m_GROUP_4_8_- 54.68\n_12_3_32_128_65536_-m_BYTWO_b_-r_NOSSE_-d_EUCLID_- 50.64\n_12_3_32_128_65536_-m_BYTWO_b_-r_NOSSE_-d_MATRIX_- 50.55\n_12_3_32_128_65536_-m_BYTWO_b_-r_NOSSE_- 50.75\n_12_3_32_128_65536_-m_BYTWO_p_-r_NOSSE_-d_EUCLID_- 49.50\n_12_3_32_128_65536_-m_BYTWO_p_-r_NOSSE_- 49.14\n_12_3_32_128_65536_-m_BYTWO_p_-r_NOSSE_-d_MATRIX_- 47.39\n_12_3_8_128_65536_-m_SHIFT_-d_EUCLID_- 14.08\n_12_3_8_128_65536_-m_SHIFT_-d_MATRIX_- 14.39\n_12_3_8_128_65536_-m_SHIFT_- 14.48\n_12_3_16_128_65536_-m_SHIFT_-d_EUCLID_- 12.75\n_12_3_16_128_65536_-m_SHIFT_-d_MATRIX_- 12.74\n_12_3_16_128_65536_-m_SHIFT_- 12.77\n_12_3_32_128_65536_-m_SHIFT_-d_MATRIX_- 12.51\n_12_3_32_128_65536_-m_SHIFT_-d_EUCLID_- 12.50\n_12_3_32_128_65536_-m_SHIFT_- 12.46\n"
  },
  {
    "path": "fst/layout/jerasure/README",
    "content": "This is revision 2.0 of Jerasure.  This is pretty much Jerasure 1.2 without the\noriginal Galois Field backend.  Version 2.0 links directly to GF-Complete, which\nis more flexible than the original, and *much* faster, because it leverages SIMD\ninstructions.\nAuthors: James S. Plank (University of Tennessee)\n         Kevin M. Greenan (Box)\n\n------------------------------------------------------------\n\nThe online home for jerasure is:\n\n  - http://jerasure.org/jerasure/jerasure\n\n------------------------------------------------------------\n\nExternal Documentation:\n\nSee the file Manual.pdf for the programmer's manual and tutorial.\n\nSee http://jerasure.org/jerasure/gf-complete for GF-Complete.\n\nNOTE: You must have GF-Complete installed (or compiled) in order to use Jerasure 2.0.\n\nThere are two directories of source code:\n\nThe src directory contains the jerasure code.\nThe Examples directory contains the example programs.\n\n------------------------------------------------------------\n\nIf you do not have Autoconf 2.65 or later installed, you can simply build\nfrom the tarball distribution:\n\nhttp://www.kaymgee.com/Kevin_Greenan/Software_files/jerasure.tar.gz\n\nInstalling if you are allowed to install GF-Complete on your machine:\n(You can skip the autoreconf step if you're using a tarball distribution.)\n\n1.) Install GF-Complete\n2.) autoreconf --force --install (*skip* if you are building from tarball)\n3.) ./configure\n4.) make\n5.) sudo make install\n\nThis will install the library into your machine's lib directory,\nthe headers into include, and the example programs into bin.\n\nThe configuration process assumes shared objects are searched for in\n/usr/local/lib. If this is not the case on your system, you can specify a\nsearch path at configuration time. For example:\n  ./configure LD_LIBRARY_PATH=/usr/local/lib\n\n------------------------------------------------------------\n\nInstalling if you can compile GF-Complete, but you cannot install it:\n\n1.) Install GF-Complete.  Let's suppose the full path to GF-Complete is\n    in the environment variable  GFP\n2A.) On Linux, set the environment variable LD_LIBRARY_PATH so that it\n     includes $GFP/src/.libs\n2B.) On a mac, set the environment variable DYLD_LIBRARY_PATH so that it\n     includes $GFP/src/.libs\n2.) ./configure LDFLAGS=-L$GFP/src/.libs/ CPPFLAGS=-I$GFP/include\n3.) make\n\nThe examples will be in the directory Examples.  The include files will\nbe in the directory include, and the library will be called libJerasure.a\nin the directory src/.libs.\n\n------------------------------------------------------------\n\nAs long as GF-Complete is installed, Jerasure 2.0 can be used just as previous\nversions.  There is no need to define custom Galois Fields.  Jerasure will\ndetermine the default field to use, if one is not specified.\n\nIf you would like to explore a using a different Galois Field implementation,\nplease see the manual.\n\n------------------------------------------------------------\n\nTesting GF-Complete\n\nIf the GF-Complete tools are installed in /usr/local/bin\n\n  make check\n\nIf the GF-Complete tools are installed elsewhere\n\n  make GF_COMPLETE_DIR=$(pwd)/../gf-complete/tools check\n\nTo run some tests with valgrind\n\n  make VALGRIND='valgrind --tool=memcheck --quiet' \\\n       GF_COMPLETE_DIR=$(pwd)/../gf-complete/tools \\\n       check\n"
  },
  {
    "path": "fst/layout/jerasure/configure.ac",
    "content": "# Jerasure autoconf template\n\nAC_PREREQ([2.65])\nAC_INIT([Jerasure], [2.0], [], [],\n        [https://jerasure.org/jerasure/jerasure])\nAC_CONFIG_SRCDIR([src/jerasure.c])\nAC_CONFIG_HEADERS([include/config.h])\n\nAC_CONFIG_AUX_DIR([build-aux])\nAC_CONFIG_MACRO_DIR([m4])\n\nAM_INIT_AUTOMAKE([1.13 -Wall -Wno-extra-portability])\n\n# Package default C compiler flags.\ndnl This must be before LT_INIT and AC_PROG_CC.\n: ${CFLAGS='-g -O3 -Wall'}\n\nLT_INIT([disable-static])\n\n# Checks for programs.\nAC_PROG_CC\n\n# Checks for libraries.\nAC_CHECK_LIB([gf_complete], [gf_init_easy], [],\n             [AC_MSG_FAILURE(\n               [You need to have gf_complete installed.\n                  gf_complete is available from http://jerasure.org/jerasure/gf-complete])\n             ])\n\n# Checks for header files.\nAC_CHECK_HEADERS([stddef.h stdint.h stdlib.h string.h sys/time.h unistd.h])\nAC_CHECK_HEADERS([gf_complete.h gf_general.h gf_method.h gf_rand.h])\n\n# Checks for typedefs, structures, and compiler characteristics.\nAC_TYPE_UINT32_T\nAC_TYPE_UINT64_T\nAX_EXT\n\nAC_ARG_ENABLE([sse],\n              AS_HELP_STRING([--disable-sse], [Build without SSE optimizations]),\n              [if   test \"x$enableval\" = \"xno\" ; then\n                SIMD_FLAGS=\"\"\n                echo \"DISABLED SSE!!!\"\n              fi]\n)\n\n# Checks for library functions.\nAC_FUNC_MALLOC\nAC_CHECK_FUNCS([bzero getcwd gettimeofday mkdir strchr strdup strrchr])\n\nAC_CONFIG_FILES([Examples/Makefile\n                 Makefile\n                 src/Makefile])\nAC_OUTPUT\n"
  },
  {
    "path": "fst/layout/jerasure/include/cauchy.h",
    "content": "/* *\n * Copyright (c) 2013, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#pragma once\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nextern int *cauchy_original_coding_matrix(int k, int m, int w);\nextern int *cauchy_xy_coding_matrix(int k, int m, int w, int *x, int *y);\nextern void cauchy_improve_coding_matrix(int k, int m, int w, int *matrix);\nextern int *cauchy_good_general_coding_matrix(int k, int m, int w);\nextern int cauchy_n_ones(int n, int w);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "fst/layout/jerasure/include/galois.h",
    "content": "/* *\n * Copyright (c) 2013, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#pragma once\n\n#include <stdint.h>\n#include <gf_complete.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nextern int galois_init_default_field(int w);\nextern int galois_uninit_field(int w);\nextern void galois_change_technique(gf_t *gf, int w);\n\nextern int galois_single_multiply(int a, int b, int w);\nextern int galois_single_divide(int a, int b, int w);\nextern int galois_inverse(int x, int w);\n\nvoid galois_region_xor(           char *src,         /* Source Region */\n                                  char *dest,        /* Dest Region (holds result) */\n                                  int nbytes);      /* Number of bytes in region */\n\n/* These multiply regions in w=8, w=16 and w=32.  They are much faster\n   than calling galois_single_multiply.  The regions must be long word aligned. */\n\nvoid galois_w08_region_multiply(char *region,       /* Region to multiply */\n                                  int multby,       /* Number to multiply by */\n                                  int nbytes,       /* Number of bytes in region */\n                                  char *r2,         /* If r2 != NULL, products go here.  \n                                                       Otherwise region is overwritten */\n                                  int add);         /* If (r2 != NULL && add) the produce is XOR'd with r2 */\n\nvoid galois_w16_region_multiply(char *region,       /* Region to multiply */\n                                  int multby,       /* Number to multiply by */\n                                  int nbytes,       /* Number of bytes in region */\n                                  char *r2,         /* If r2 != NULL, products go here.  \n                                                       Otherwise region is overwritten */\n                                  int add);         /* If (r2 != NULL && add) the produce is XOR'd with r2 */\n\nvoid galois_w32_region_multiply(char *region,       /* Region to multiply */\n                                  int multby,       /* Number to multiply by */\n                                  int nbytes,       /* Number of bytes in region */\n                                  char *r2,         /* If r2 != NULL, products go here.  \n                                                       Otherwise region is overwritten */\n                                  int add);         /* If (r2 != NULL && add) the produce is XOR'd with r2 */\n\ngf_t* galois_init_field(int w,\n                             int mult_type,\n                             int region_type,\n                             int divide_type,\n                             uint64_t prim_poly,\n                             int arg1,\n                             int arg2);\n\ngf_t* galois_init_composite_field(int w,\n                                int region_type,\n                                int divide_type,\n                                int degree,\n                                gf_t* base_gf);\n\ngf_t * galois_get_field_ptr(int w);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "fst/layout/jerasure/include/jerasure.h",
    "content": "/* *\n * Copyright (c) 2013, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#pragma once\n\n#ifndef _JERASURE_H\n#define _JERASURE_H\n\n/* This uses procedures from the Galois Field arithmetic library */\n\n#include \"galois.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* ------------------------------------------------------------ */\n/* In all of the routines below:\n\n   k = Number of data devices\n   m = Number of coding devices\n   w = Word size\n\n   data_ptrs = An array of k pointers to data which is size bytes.  \n               Size must be a multiple of sizeof(long).\n               Pointers must also be longword aligned.\n \n   coding_ptrs = An array of m pointers to coding data which is size bytes.\n\n   packetsize = The size of a coding block with bitmatrix coding. \n                When you code with a bitmatrix, you will use w packets\n                of size packetsize.\n\n   matrix = an array of k*m integers.  \n            It represents an m by k matrix.\n            Element i,j is in matrix[i*k+j];\n\n   bitmatrix = an array of k*m*w*w integers.\n            It represents an mw by kw matrix.\n            Element i,j is in matrix[i*k*w+j];\n\n   erasures = an array of id's of erased devices. \n              Id's are integers between 0 and k+m-1.\n              Id's 0 to k-1 are id's of data devices.\n              Id's k to k+m-1 are id's of coding devices: \n                  Coding device id = id-k.\n              If there are e erasures, erasures[e] = -1.\n\n   schedule = an array of schedule operations.  \n\n              If there are m operations, then schedule[m][0] = -1.\n\n   operation = an array of 5 integers:\n\n          0 = operation: 0 for copy, 1 for xor (-1 for end)\n          1 = source device (0 - k+m-1)\n          2 = source packet (0 - w-1)\n          3 = destination device (0 - k+m-1)\n          4 = destination packet (0 - w-1)\n */\n\n/* ---------------------------------------------------------------  */\n/* Bitmatrices / schedules ---------------------------------------- */\n/*\n - jerasure_matrix_to_bitmatrix turns a m X k matrix in GF(2^w) into a\n                              wm X wk bitmatrix (in GF(2)).  This is\n                              explained in the Cauchy Reed-Solomon coding\n                              paper.\n\n - jerasure_dumb_bitmatrix_to_schedule turns a bitmatrix into a schedule \n                              using the straightforward algorithm -- just\n                              schedule the dot products defined by each\n                              row of the matrix.\n\n - jerasure_smart_bitmatrix_to_schedule turns a bitmatrix into a schedule,\n                              but tries to use previous dot products to\n                              calculate new ones.  This is the optimization\n                              explained in the original Liberation code paper.\n\n - jerasure_generate_schedule_cache precalcalculate all the schedule for the\n                              given distribution bitmatrix.  M must equal 2.\n \n - jerasure_free_schedule frees a schedule that was allocated with \n                              jerasure_XXX_bitmatrix_to_schedule.\n \n - jerasure_free_schedule_cache frees a schedule cache that was created with \n                              jerasure_generate_schedule_cache.\n */\n\nint *jerasure_matrix_to_bitmatrix(int k, int m, int w, int *matrix);\nint **jerasure_dumb_bitmatrix_to_schedule(int k, int m, int w, int *bitmatrix);\nint **jerasure_smart_bitmatrix_to_schedule(int k, int m, int w, int *bitmatrix);\nint ***jerasure_generate_schedule_cache(int k, int m, int w, int *bitmatrix, int smart);\n\nvoid jerasure_free_schedule(int **schedule);\nvoid jerasure_free_schedule_cache(int k, int m, int ***cache);\n\n\n/* ------------------------------------------------------------ */\n/* Encoding - these are all straightforward.  jerasure_matrix_encode only \n   works with w = 8|16|32.  */\n\nvoid jerasure_do_parity(int k, char **data_ptrs, char *parity_ptr, int size);\n\nvoid jerasure_matrix_encode(int k, int m, int w, int *matrix,\n                          char **data_ptrs, char **coding_ptrs, int size);\n\nvoid jerasure_bitmatrix_encode(int k, int m, int w, int *bitmatrix,\n                            char **data_ptrs, char **coding_ptrs, int size, int packetsize);\n\nvoid jerasure_schedule_encode(int k, int m, int w, int **schedule,\n                                  char **data_ptrs, char **coding_ptrs, int size, int packetsize);\n\n/* ------------------------------------------------------------ */\n/* Decoding. -------------------------------------------------- */\n\n/* These return integers, because the matrix may not be invertible. \n   \n   The parameter row_k_ones should be set to 1 if row k of the matrix\n   (or rows kw to (k+1)w+1) of th distribution matrix are all ones\n   (or all identity matrices).  Then you can improve the performance\n   of decoding when there is more than one failure, and the parity\n   device didn't fail.  You do it by decoding all but one of the data\n   devices, and then decoding the last data device from the data devices\n   and the parity device.\n\n   jerasure_schedule_decode_lazy generates the schedule on the fly.\n\n   jerasure_matrix_decode only works when w = 8|16|32.\n\n   jerasure_make_decoding_matrix/bitmatrix make the k*k decoding matrix\n         (or wk*wk bitmatrix) by taking the rows corresponding to k\n         non-erased devices of the distribution matrix, and then\n         inverting that matrix.\n\n         You should already have allocated the decoding matrix and\n         dm_ids, which is a vector of k integers.  These will be\n         filled in appropriately.  dm_ids[i] is the id of element\n         i of the survivors vector.  I.e. row i of the decoding matrix\n         times dm_ids equals data drive i.\n\n         Both of these routines take \"erased\" instead of \"erasures\".\n         Erased is a vector with k+m elements, which has 0 or 1 for \n         each device's id, according to whether the device is erased.\n \n   jerasure_erasures_to_erased allocates and returns erased from erasures.\n    \n */\n\nint jerasure_matrix_decode(int k, int m, int w, \n                          int *matrix, int row_k_ones, int *erasures,\n                          char **data_ptrs, char **coding_ptrs, int size);\n                          \nint jerasure_bitmatrix_decode(int k, int m, int w, \n                            int *bitmatrix, int row_k_ones, int *erasures,\n                            char **data_ptrs, char **coding_ptrs, int size, int packetsize);\n\nint jerasure_schedule_decode_lazy(int k, int m, int w, int *bitmatrix, int *erasures,\n                            char **data_ptrs, char **coding_ptrs, int size, int packetsize,\n                            int smart);\n\nint jerasure_schedule_decode_cache(int k, int m, int w, int ***scache, int *erasures,\n                            char **data_ptrs, char **coding_ptrs, int size, int packetsize);\n\nint jerasure_make_decoding_matrix(int k, int m, int w, int *matrix, int *erased, \n                                  int *decoding_matrix, int *dm_ids);\n\nint jerasure_make_decoding_bitmatrix(int k, int m, int w, int *matrix, int *erased, \n                                  int *decoding_matrix, int *dm_ids);\n\nint *jerasure_erasures_to_erased(int k, int m, int *erasures);\n\n/* ------------------------------------------------------------ */\n/* These perform dot products and schedules. -------------------*/\n/*\n   src_ids is a matrix of k id's (0 - k-1 for data devices, k - k+m-1\n   for coding devices) that identify the source devices.  Dest_id is\n   the id of the destination device.\n\n   jerasure_matrix_dotprod only works when w = 8|16|32.\n\n   jerasure_do_scheduled_operations executes the schedule on w*packetsize worth of\n   bytes from each device.  ptrs is an array of pointers which should have as many\n   elements as the highest referenced device in the schedule.\n\n */\n \nvoid jerasure_matrix_dotprod(int k, int w, int *matrix_row,\n                          int *src_ids, int dest_id,\n                          char **data_ptrs, char **coding_ptrs, int size);\n\nvoid jerasure_bitmatrix_dotprod(int k, int w, int *bitmatrix_row,\n                             int *src_ids, int dest_id,\n                             char **data_ptrs, char **coding_ptrs, int size, int packetsize);\n\nvoid jerasure_do_scheduled_operations(char **ptrs, int **schedule, int packetsize);\n\n/* ------------------------------------------------------------ */\n/* Matrix Inversion ------------------------------------------- */\n/*\n   The two matrix inversion functions work on rows*rows matrices of\n   ints.  If a bitmatrix, then each int will just be zero or one.\n   Otherwise, they will be elements of gf(2^w).  Obviously, you can\n   do bit matrices with crs_invert_matrix() and set w = 1, but\n   crs_invert_bitmatrix will be more efficient.\n\n   The two invertible functions return whether a matrix is invertible.\n   They are more efficient than the inverstion functions.\n\n   Mat will be destroyed when the matrix inversion or invertible\n   testing is done.  Sorry.\n\n   Inv must be allocated by the caller.\n\n   The two invert_matrix functions return 0 on success, and -1 if the\n   matrix is uninvertible.\n\n   The two invertible function simply return whether the matrix is\n   invertible.  (0 or 1). Mat will be destroyed.\n */\n\nint jerasure_invert_matrix(int *mat, int *inv, int rows, int w);\nint jerasure_invert_bitmatrix(int *mat, int *inv, int rows);\nint jerasure_invertible_matrix(int *mat, int rows, int w);\nint jerasure_invertible_bitmatrix(int *mat, int rows);\n\n/* ------------------------------------------------------------ */\n/* Basic matrix operations -------------------------------------*/\n/*\n   Each of the print_matrix routines require a w.  In jerasure_print_matrix,\n   this is to calculate the field width.  In jerasure_print_bitmatrix, it is\n   to put spaces between the bits.\n\n   jerasure_matrix_multiply is a simple matrix multiplier in GF(2^w).  It returns a r1*c2\n   matrix, which is the product of the two input matrices.  It allocates\n   the product.  Obviously, c1 should equal r2.  However, this is not\n   validated by the procedure.  \n*/\n\nvoid jerasure_print_matrix(int *matrix, int rows, int cols, int w);\nvoid jerasure_print_bitmatrix(int *matrix, int rows, int cols, int w);\n\n\nint *jerasure_matrix_multiply(int *m1, int *m2, int r1, int c1, int r2, int c2, int w);\n\n/* ------------------------------------------------------------ */\n/* Stats ------------------------------------------------------ */\n/*\n  jerasure_get_stats fills in a vector of three doubles:\n\n      fill_in[0] is the number of bytes that have been XOR'd\n      fill_in[1] is the number of bytes that have been copied\n      fill_in[2] is the number of bytes that have been multiplied\n                 by a constant in GF(2^w)\n\n  When jerasure_get_stats() is called, it resets its values.\n */\n\nvoid jerasure_get_stats(double *fill_in);\n\nint jerasure_autoconf_test();\n\n#ifdef __cplusplus\n}\n#endif\n#endif\n"
  },
  {
    "path": "fst/layout/jerasure/include/liberation.h",
    "content": "/* *\n * Copyright (c) 2013, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#pragma once\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nextern int *liberation_coding_bitmatrix(int k, int w);\nextern int *liber8tion_coding_bitmatrix(int k);\nextern int *blaum_roth_coding_bitmatrix(int k, int w);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "fst/layout/jerasure/include/reed_sol.h",
    "content": "/* *\n * Copyright (c) 2013, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n#pragma once\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nextern int *reed_sol_vandermonde_coding_matrix(int k, int m, int w);\nextern int *reed_sol_extended_vandermonde_matrix(int rows, int cols, int w);\nextern int *reed_sol_big_vandermonde_distribution_matrix(int rows, int cols, int w);\n\nextern int reed_sol_r6_encode(int k, int w, char **data_ptrs, char **coding_ptrs, int size);\nextern int *reed_sol_r6_coding_matrix(int k, int w);\n\nextern void reed_sol_galois_w08_region_multby_2(char *region, int nbytes);\nextern void reed_sol_galois_w16_region_multby_2(char *region, int nbytes);\nextern void reed_sol_galois_w32_region_multby_2(char *region, int nbytes);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "fst/layout/jerasure/include/timing.h",
    "content": "// Timing measurement utilities.\n\n#ifndef JERASURE_INCLUDED__TIMING_H\n#define JERASURE_INCLUDED__TIMING_H\n\n// Define USE_CLOCK to use clock(). Otherwise use gettimeofday().\n#define USE_CLOCK\n\n#ifdef USE_CLOCK\n#include <time.h>\n#else\n#include <sys/time.h>\n#endif\n\nstruct timing {\n#ifdef USE_CLOCK\n  clock_t clock;\n#else\n  struct timeval tv;\n#endif\n};\n\n// Get the current time as a double in seconds.\ndouble\ntiming_now(\n  void);\n\n// Set *t to the current time.\nvoid\ntiming_set(\n  struct timing * t);\n\n// Get *t as a double in seconds.\ndouble\ntiming_get(\n  struct timing * t);\n\n// Return *t2 - *t1 as a double in seconds.\ndouble\ntiming_delta(\n  struct timing * t1,\n  struct timing * t2);\n#endif\n"
  },
  {
    "path": "fst/layout/jerasure/m4/ax_check_compile_flag.m4",
    "content": "# ===========================================================================\n#   http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])\n#\n# DESCRIPTION\n#\n#   Check whether the given FLAG works with the current language's compiler\n#   or gives an error.  (Warnings, however, are ignored)\n#\n#   ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on\n#   success/failure.\n#\n#   If EXTRA-FLAGS is defined, it is added to the current language's default\n#   flags (e.g. CFLAGS) when the check is done.  The check is thus made with\n#   the flags: \"CFLAGS EXTRA-FLAGS FLAG\".  This can for example be used to\n#   force the compiler to issue an error when a bad flag is given.\n#\n#   INPUT gives an alternative input source to AC_COMPILE_IFELSE.\n#\n#   NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this\n#   macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.\n#\n# LICENSE\n#\n#   Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>\n#   Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>\n#\n#   This program is free software: you can redistribute it and/or modify it\n#   under the terms of the GNU General Public License as published by the\n#   Free Software Foundation, either version 3 of the License, or (at your\n#   option) any later version.\n#\n#   This program is distributed in the hope that it will be useful, but\n#   WITHOUT ANY WARRANTY; without even the implied warranty of\n#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\n#   Public License for more details.\n#\n#   You should have received a copy of the GNU General Public License along\n#   with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n#   As a special exception, the respective Autoconf Macro's copyright owner\n#   gives unlimited permission to copy, distribute and modify the configure\n#   scripts that are the output of Autoconf when processing the Macro. You\n#   need not follow the terms of the GNU General Public License when using\n#   or distributing such scripts, even though portions of the text of the\n#   Macro appear in them. The GNU General Public License (GPL) does govern\n#   all other use of the material that constitutes the Autoconf Macro.\n#\n#   This special exception to the GPL applies to versions of the Autoconf\n#   Macro released by the Autoconf Archive. When you make and distribute a\n#   modified version of the Autoconf Macro, you may extend this special\n#   exception to the GPL to apply to your modified version as well.\n\n#serial 3\n\nAC_DEFUN([AX_CHECK_COMPILE_FLAG],\n[AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX\nAS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl\nAC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [\n  ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS\n  _AC_LANG_PREFIX[]FLAGS=\"$[]_AC_LANG_PREFIX[]FLAGS $4 $1\"\n  AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],\n    [AS_VAR_SET(CACHEVAR,[yes])],\n    [AS_VAR_SET(CACHEVAR,[no])])\n  _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])\nAS_IF([test x\"AS_VAR_GET(CACHEVAR)\" = xyes],\n  [m4_default([$2], :)],\n  [m4_default([$3], :)])\nAS_VAR_POPDEF([CACHEVAR])dnl\n])dnl AX_CHECK_COMPILE_FLAGS\n"
  },
  {
    "path": "fst/layout/jerasure/m4/ax_ext.m4",
    "content": "#\n# Modified from autoconf-archive to replace AC_REQUIRE([AX_GCC_X86_*]) with\n# AX_REQUIRE_DEFINED(...).\n#\n# ===========================================================================\n#          http://www.gnu.org/software/autoconf-archive/ax_ext.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_EXT\n#\n# DESCRIPTION\n#\n#   Find supported SIMD extensions by requesting cpuid. When an SIMD\n#   extension is found, the -m\"simdextensionname\" is added to SIMD_FLAGS if\n#   compiler supports it. For example, if \"sse2\" is available, then \"-msse2\"\n#   is added to SIMD_FLAGS.\n#\n#   This macro calls:\n#\n#     AC_SUBST(SIMD_FLAGS)\n#\n#   And defines:\n#\n#     HAVE_MMX / HAVE_SSE / HAVE_SSE2 / HAVE_SSE3 / HAVE_SSSE3 / HAVE_SSE4.1 / HAVE_SSE4.2 / HAVE_AVX\n#\n# LICENSE\n#\n#   Copyright (c) 2007 Christophe Tournayre <turn3r@users.sourceforge.net>\n#   Copyright (c) 2013 Michael Petch <mpetch@capp-sysware.com>\n#\n#   Copying and distribution of this file, with or without modification, are\n#   permitted in any medium without royalty provided the copyright notice\n#   and this notice are preserved. This file is offered as-is, without any\n#   warranty.\n\n#serial 13.1\n\nAC_DEFUN([AX_EXT],\n[\n  AC_REQUIRE([AC_CANONICAL_HOST])\n\n  case $host_cpu in\n    powerpc*)\n      AC_CACHE_CHECK([whether altivec is supported], [ax_cv_have_altivec_ext],\n          [\n            if test `/usr/sbin/sysctl -a 2>/dev/null| grep -c hw.optional.altivec` != 0; then\n                if test `/usr/sbin/sysctl -n hw.optional.altivec` = 1; then\n                  ax_cv_have_altivec_ext=yes\n                fi\n            fi\n          ])\n\n          if test \"$ax_cv_have_altivec_ext\" = yes; then\n            AC_DEFINE(HAVE_ALTIVEC,,[Support Altivec instructions])\n            AX_CHECK_COMPILE_FLAG(-faltivec, SIMD_FLAGS=\"$SIMD_FLAGS -faltivec\", [])\n          fi\n    ;;\n\n\n    i[[3456]]86*|x86_64*|amd64*)\n\n      AX_REQUIRE_DEFINED([AX_GCC_X86_CPUID])\n      AX_REQUIRE_DEFINED([AX_GCC_X86_AVX_XGETBV])\n\n      AX_GCC_X86_CPUID(0x00000001)\n      ecx=0\n      edx=0\n      if test \"$ax_cv_gcc_x86_cpuid_0x00000001\" != \"unknown\";\n      then\n        ecx=`echo $ax_cv_gcc_x86_cpuid_0x00000001 | cut -d \":\" -f 3`\n        edx=`echo $ax_cv_gcc_x86_cpuid_0x00000001 | cut -d \":\" -f 4`\n      fi\n\n      AC_CACHE_CHECK([whether mmx is supported], [ax_cv_have_mmx_ext],\n      [\n        ax_cv_have_mmx_ext=no\n        if test \"$((0x$edx>>23&0x01))\" = 1; then\n          ax_cv_have_mmx_ext=yes\n        fi\n      ])\n\n      AC_CACHE_CHECK([whether sse is supported], [ax_cv_have_sse_ext],\n      [\n        ax_cv_have_sse_ext=no\n        if test \"$((0x$edx>>25&0x01))\" = 1; then\n          ax_cv_have_sse_ext=yes\n        fi\n      ])\n\n      AC_CACHE_CHECK([whether sse2 is supported], [ax_cv_have_sse2_ext],\n      [\n        ax_cv_have_sse2_ext=no\n        if test \"$((0x$edx>>26&0x01))\" = 1; then\n          ax_cv_have_sse2_ext=yes\n        fi\n      ])\n\n      AC_CACHE_CHECK([whether sse3 is supported], [ax_cv_have_sse3_ext],\n      [\n        ax_cv_have_sse3_ext=no\n        if test \"$((0x$ecx&0x01))\" = 1; then\n          ax_cv_have_sse3_ext=yes\n        fi\n      ])\n\n      AC_CACHE_CHECK([whether ssse3 is supported], [ax_cv_have_ssse3_ext],\n      [\n        ax_cv_have_ssse3_ext=no\n        if test \"$((0x$ecx>>9&0x01))\" = 1; then\n          ax_cv_have_ssse3_ext=yes\n        fi\n      ])\n\n      AC_CACHE_CHECK([whether sse4.1 is supported], [ax_cv_have_sse41_ext],\n      [\n        ax_cv_have_sse41_ext=no\n        if test \"$((0x$ecx>>19&0x01))\" = 1; then\n          ax_cv_have_sse41_ext=yes\n        fi\n      ])\n\n      AC_CACHE_CHECK([whether sse4.2 is supported], [ax_cv_have_sse42_ext],\n      [\n        ax_cv_have_sse42_ext=no\n        if test \"$((0x$ecx>>20&0x01))\" = 1; then\n          ax_cv_have_sse42_ext=yes\n        fi\n      ])\n\n      AC_CACHE_CHECK([whether avx is supported by processor], [ax_cv_have_avx_cpu_ext],\n      [\n        ax_cv_have_avx_cpu_ext=no\n        if test \"$((0x$ecx>>28&0x01))\" = 1; then\n          ax_cv_have_avx_cpu_ext=yes\n        fi\n      ])\n\n      if test x\"$ax_cv_have_avx_cpu_ext\" = x\"yes\"; then\n        AX_GCC_X86_AVX_XGETBV(0x00000000)\n\n        xgetbv_eax=\"0\"\n        if test x\"$ax_cv_gcc_x86_avx_xgetbv_0x00000000\" != x\"unknown\"; then\n          xgetbv_eax=`echo $ax_cv_gcc_x86_avx_xgetbv_0x00000000 | cut -d \":\" -f 1`\n        fi\n\n        AC_CACHE_CHECK([whether avx is supported by operating system], [ax_cv_have_avx_ext],\n        [\n          ax_cv_have_avx_ext=no\n\n          if test \"$((0x$ecx>>27&0x01))\" = 1; then\n            if test \"$((0x$xgetbv_eax&0x6))\" = 6; then\n              ax_cv_have_avx_ext=yes\n            fi\n          fi\n        ])\n        if test x\"$ax_cv_have_avx_ext\" = x\"no\"; then\n          AC_MSG_WARN([Your processor supports AVX, but your operating system doesn't])\n        fi\n      fi\n\n      if test \"$ax_cv_have_mmx_ext\" = yes; then\n        AX_CHECK_COMPILE_FLAG(-mmmx, ax_cv_support_mmx_ext=yes, [])\n        if test x\"$ax_cv_support_mmx_ext\" = x\"yes\"; then\n          SIMD_FLAGS=\"$SIMD_FLAGS -mmmx\"\n          AC_DEFINE(HAVE_MMX,,[Support mmx instructions])\n        else\n          AC_MSG_WARN([Your processor supports mmx instructions but not your compiler, can you try another compiler?])\n        fi\n      fi\n\n      if test \"$ax_cv_have_sse_ext\" = yes; then\n        AX_CHECK_COMPILE_FLAG(-msse, ax_cv_support_sse_ext=yes, [])\n        if test x\"$ax_cv_support_sse_ext\" = x\"yes\"; then\n          SIMD_FLAGS=\"$SIMD_FLAGS -msse\"\n          AC_DEFINE(HAVE_SSE,,[Support SSE (Streaming SIMD Extensions) instructions])\n        else\n          AC_MSG_WARN([Your processor supports sse instructions but not your compiler, can you try another compiler?])\n        fi\n      fi\n\n      if test \"$ax_cv_have_sse2_ext\" = yes; then\n        AX_CHECK_COMPILE_FLAG(-msse2, ax_cv_support_sse2_ext=yes, [])\n        if test x\"$ax_cv_support_sse2_ext\" = x\"yes\"; then\n          SIMD_FLAGS=\"$SIMD_FLAGS -msse2\"\n          AC_DEFINE(HAVE_SSE2,,[Support SSE2 (Streaming SIMD Extensions 2) instructions])\n        else\n          AC_MSG_WARN([Your processor supports sse2 instructions but not your compiler, can you try another compiler?])\n        fi\n      fi\n\n      if test \"$ax_cv_have_sse3_ext\" = yes; then\n        AX_CHECK_COMPILE_FLAG(-msse3, ax_cv_support_sse3_ext=yes, [])\n        if test x\"$ax_cv_support_sse3_ext\" = x\"yes\"; then\n          SIMD_FLAGS=\"$SIMD_FLAGS -msse3\"\n          AC_DEFINE(HAVE_SSE3,,[Support SSE3 (Streaming SIMD Extensions 3) instructions])\n        else\n          AC_MSG_WARN([Your processor supports sse3 instructions but not your compiler, can you try another compiler?])\n        fi\n      fi\n\n      if test \"$ax_cv_have_ssse3_ext\" = yes; then\n        AX_CHECK_COMPILE_FLAG(-mssse3, ax_cv_support_ssse3_ext=yes, [])\n        if test x\"$ax_cv_support_ssse3_ext\" = x\"yes\"; then\n          SIMD_FLAGS=\"$SIMD_FLAGS -mssse3\"\n          AC_DEFINE(HAVE_SSSE3,,[Support SSSE3 (Supplemental Streaming SIMD Extensions 3) instructions])\n        else\n          AC_MSG_WARN([Your processor supports ssse3 instructions but not your compiler, can you try another compiler?])\n        fi\n      fi\n\n      if test \"$ax_cv_have_sse41_ext\" = yes; then\n        AX_CHECK_COMPILE_FLAG(-msse4.1, ax_cv_support_sse41_ext=yes, [])\n        if test x\"$ax_cv_support_sse41_ext\" = x\"yes\"; then\n          SIMD_FLAGS=\"$SIMD_FLAGS -msse4.1\"\n          AC_DEFINE(HAVE_SSE4_1,,[Support SSSE4.1 (Streaming SIMD Extensions 4.1) instructions])\n        else\n          AC_MSG_WARN([Your processor supports sse4.1 instructions but not your compiler, can you try another compiler?])\n        fi\n      fi\n\n      if test \"$ax_cv_have_sse42_ext\" = yes; then\n        AX_CHECK_COMPILE_FLAG(-msse4.2, ax_cv_support_sse42_ext=yes, [])\n        if test x\"$ax_cv_support_sse42_ext\" = x\"yes\"; then\n          SIMD_FLAGS=\"$SIMD_FLAGS -msse4.2\"\n          AC_DEFINE(HAVE_SSE4_2,,[Support SSSE4.2 (Streaming SIMD Extensions 4.2) instructions])\n        else\n          AC_MSG_WARN([Your processor supports sse4.2 instructions but not your compiler, can you try another compiler?])\n        fi\n      fi\n\n      if test \"$ax_cv_have_avx_ext\" = yes; then\n        AX_CHECK_COMPILE_FLAG(-mavx, ax_cv_support_avx_ext=yes, [])\n        if test x\"$ax_cv_support_avx_ext\" = x\"yes\"; then\n          SIMD_FLAGS=\"$SIMD_FLAGS -mavx\"\n          AC_DEFINE(HAVE_AVX,,[Support AVX (Advanced Vector Extensions) instructions])\n        else\n          AC_MSG_WARN([Your processor supports avx instructions but not your compiler, can you try another compiler?])\n        fi\n      fi\n\n  ;;\n  esac\n\n  AC_SUBST(SIMD_FLAGS)\n])\n"
  },
  {
    "path": "fst/layout/jerasure/m4/ax_gcc_x86_avx_xgetbv.m4",
    "content": "# ===========================================================================\n#   http://www.gnu.org/software/autoconf-archive/ax_gcc_x86_avx_xgetbv.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_GCC_X86_AVX_XGETBV\n#\n# DESCRIPTION\n#\n#   On later x86 processors with AVX SIMD support, with gcc or a compiler\n#   that has a compatible syntax for inline assembly instructions, run a\n#   small program that executes the xgetbv instruction with input OP. This\n#   can be used to detect if the OS supports AVX instruction usage.\n#\n#   On output, the values of the eax and edx registers are stored as\n#   hexadecimal strings as \"eax:edx\" in the cache variable\n#   ax_cv_gcc_x86_avx_xgetbv.\n#\n#   If the xgetbv instruction fails (because you are running a\n#   cross-compiler, or because you are not using gcc, or because you are on\n#   a processor that doesn't have this instruction),\n#   ax_cv_gcc_x86_avx_xgetbv_OP is set to the string \"unknown\".\n#\n#   This macro mainly exists to be used in AX_EXT.\n#\n# LICENSE\n#\n#   Copyright (c) 2013 Michael Petch <mpetch@capp-sysware.com>\n#\n#   This program is free software: you can redistribute it and/or modify it\n#   under the terms of the GNU General Public License as published by the\n#   Free Software Foundation, either version 3 of the License, or (at your\n#   option) any later version.\n#\n#   This program is distributed in the hope that it will be useful, but\n#   WITHOUT ANY WARRANTY; without even the implied warranty of\n#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\n#   Public License for more details.\n#\n#   You should have received a copy of the GNU General Public License along\n#   with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n#   As a special exception, the respective Autoconf Macro's copyright owner\n#   gives unlimited permission to copy, distribute and modify the configure\n#   scripts that are the output of Autoconf when processing the Macro. You\n#   need not follow the terms of the GNU General Public License when using\n#   or distributing such scripts, even though portions of the text of the\n#   Macro appear in them. The GNU General Public License (GPL) does govern\n#   all other use of the material that constitutes the Autoconf Macro.\n#\n#   This special exception to the GPL applies to versions of the Autoconf\n#   Macro released by the Autoconf Archive. When you make and distribute a\n#   modified version of the Autoconf Macro, you may extend this special\n#   exception to the GPL to apply to your modified version as well.\n\n#serial 1\n\nAC_DEFUN([AX_GCC_X86_AVX_XGETBV],\n[AC_REQUIRE([AC_PROG_CC])\nAC_LANG_PUSH([C])\nAC_CACHE_CHECK(for x86-AVX xgetbv $1 output, ax_cv_gcc_x86_avx_xgetbv_$1,\n [AC_RUN_IFELSE([AC_LANG_PROGRAM([#include <stdio.h>], [\n     int op = $1, eax, edx;\n     FILE *f;\n      /* Opcodes for xgetbv */\n      __asm__(\".byte 0x0f, 0x01, 0xd0\"\n        : \"=a\" (eax), \"=d\" (edx)\n        : \"c\" (op));\n     f = fopen(\"conftest_xgetbv\", \"w\"); if (!f) return 1;\n     fprintf(f, \"%x:%x\\n\", eax, edx);\n     fclose(f);\n     return 0;\n])],\n     [ax_cv_gcc_x86_avx_xgetbv_$1=`cat conftest_xgetbv`; rm -f conftest_xgetbv],\n     [ax_cv_gcc_x86_avx_xgetbv_$1=unknown; rm -f conftest_xgetbv],\n     [ax_cv_gcc_x86_avx_xgetbv_$1=unknown])])\nAC_LANG_POP([C])\n])\n"
  },
  {
    "path": "fst/layout/jerasure/m4/ax_gcc_x86_cpuid.m4",
    "content": "# ===========================================================================\n#     http://www.gnu.org/software/autoconf-archive/ax_gcc_x86_cpuid.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_GCC_X86_CPUID(OP)\n#\n# DESCRIPTION\n#\n#   On Pentium and later x86 processors, with gcc or a compiler that has a\n#   compatible syntax for inline assembly instructions, run a small program\n#   that executes the cpuid instruction with input OP. This can be used to\n#   detect the CPU type.\n#\n#   On output, the values of the eax, ebx, ecx, and edx registers are stored\n#   as hexadecimal strings as \"eax:ebx:ecx:edx\" in the cache variable\n#   ax_cv_gcc_x86_cpuid_OP.\n#\n#   If the cpuid instruction fails (because you are running a\n#   cross-compiler, or because you are not using gcc, or because you are on\n#   a processor that doesn't have this instruction), ax_cv_gcc_x86_cpuid_OP\n#   is set to the string \"unknown\".\n#\n#   This macro mainly exists to be used in AX_GCC_ARCHFLAG.\n#\n# LICENSE\n#\n#   Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>\n#   Copyright (c) 2008 Matteo Frigo\n#\n#   This program is free software: you can redistribute it and/or modify it\n#   under the terms of the GNU General Public License as published by the\n#   Free Software Foundation, either version 3 of the License, or (at your\n#   option) any later version.\n#\n#   This program is distributed in the hope that it will be useful, but\n#   WITHOUT ANY WARRANTY; without even the implied warranty of\n#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\n#   Public License for more details.\n#\n#   You should have received a copy of the GNU General Public License along\n#   with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n#   As a special exception, the respective Autoconf Macro's copyright owner\n#   gives unlimited permission to copy, distribute and modify the configure\n#   scripts that are the output of Autoconf when processing the Macro. You\n#   need not follow the terms of the GNU General Public License when using\n#   or distributing such scripts, even though portions of the text of the\n#   Macro appear in them. The GNU General Public License (GPL) does govern\n#   all other use of the material that constitutes the Autoconf Macro.\n#\n#   This special exception to the GPL applies to versions of the Autoconf\n#   Macro released by the Autoconf Archive. When you make and distribute a\n#   modified version of the Autoconf Macro, you may extend this special\n#   exception to the GPL to apply to your modified version as well.\n\n#serial 7\n\nAC_DEFUN([AX_GCC_X86_CPUID],\n[AC_REQUIRE([AC_PROG_CC])\nAC_LANG_PUSH([C])\nAC_CACHE_CHECK(for x86 cpuid $1 output, ax_cv_gcc_x86_cpuid_$1,\n [AC_RUN_IFELSE([AC_LANG_PROGRAM([#include <stdio.h>], [\n     int op = $1, eax, ebx, ecx, edx;\n     FILE *f;\n      __asm__(\"cpuid\"\n        : \"=a\" (eax), \"=b\" (ebx), \"=c\" (ecx), \"=d\" (edx)\n        : \"a\" (op));\n     f = fopen(\"conftest_cpuid\", \"w\"); if (!f) return 1;\n     fprintf(f, \"%x:%x:%x:%x\\n\", eax, ebx, ecx, edx);\n     fclose(f);\n     return 0;\n])],\n     [ax_cv_gcc_x86_cpuid_$1=`cat conftest_cpuid`; rm -f conftest_cpuid],\n     [ax_cv_gcc_x86_cpuid_$1=unknown; rm -f conftest_cpuid],\n     [ax_cv_gcc_x86_cpuid_$1=unknown])])\nAC_LANG_POP([C])\n])\n"
  },
  {
    "path": "fst/layout/jerasure/m4/ax_require_defined.m4",
    "content": "# ===========================================================================\n#    http://www.gnu.org/software/autoconf-archive/ax_require_defined.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_REQUIRE_DEFINED(MACRO)\n#\n# DESCRIPTION\n#\n#   AX_REQUIRE_DEFINED is a simple helper for making sure other macros have\n#   been defined and thus are available for use.  This avoids random issues\n#   where a macro isn't expanded.  Instead the configure script emits a\n#   non-fatal:\n#\n#     ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found\n#\n#   It's like AC_REQUIRE except it doesn't expand the required macro.\n#\n#   Here's an example:\n#\n#     AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG])\n#\n# LICENSE\n#\n#   Copyright (c) 2014 Mike Frysinger <vapier@gentoo.org>\n#\n#   Copying and distribution of this file, with or without modification, are\n#   permitted in any medium without royalty provided the copyright notice\n#   and this notice are preserved. This file is offered as-is, without any\n#   warranty.\n\n#serial 1\n\nAC_DEFUN([AX_REQUIRE_DEFINED], [dnl\n  m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])])\n])dnl AX_REQUIRE_DEFINED\n"
  },
  {
    "path": "fst/layout/jerasure/src/Makefile.am",
    "content": "# Jerasure AM file\n\nAM_CPPFLAGS = -I$(top_srcdir)/include\nAM_CFLAGS = $(SIMD_FLAGS)\n\nlib_LTLIBRARIES = libJerasure.la\nlibJerasure_la_SOURCES = galois.c jerasure.c reed_sol.c cauchy.c liberation.c\nlibJerasure_la_LDFLAGS = -version-info 2:0:0\nlibJerasure_la_LIBADD = -lgf_complete\ninclude_HEADERS = ../include/jerasure.h\n\n# Install additional Jerasure header files in their own directory.\njerasureincludedir = $(includedir)/jerasure\njerasureinclude_HEADERS = \\\n  ../include/cauchy.h \\\n  ../include/galois.h \\\n  ../include/liberation.h \\\n  ../include/reed_sol.h\n\nnoinst_HEADERS = ../include/timing.h\nnoinst_LIBRARIES = libtiming.a\nlibtiming_a_SOURCES = timing.c\n"
  },
  {
    "path": "fst/layout/jerasure/src/cauchy.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"galois.h\"\n#include \"jerasure.h\"\n#include \"cauchy.h\"\n\nstatic int PPs[33] = { -1, -1, -1, -1, -1, -1, -1, -1,\n                       -1, -1, -1, -1, -1, -1, -1, -1,\n                       -1, -1, -1, -1, -1, -1, -1, -1,\n                       -1, -1, -1, -1, -1, -1, -1, -1, -1 };\nstatic int NOs[33];\nstatic int ONEs[33][33];\n\nstatic int *cbest_0;\nstatic int *cbest_1;\nstatic int cbest_2[3];\nstatic int cbest_3[7];\nstatic int cbest_4[15];\nstatic int cbest_5[31];\nstatic int cbest_6[63];\nstatic int cbest_7[127];\nstatic int cbest_8[255];\nstatic int cbest_9[511];\nstatic int cbest_10[1023];\nstatic int cbest_11[1023];\nstatic int *cbest_12, *cbest_13, *cbest_14, *cbest_15, *cbest_16, *cbest_17, *cbest_18, *cbest_19, *cbest_20,\n           *cbest_21, *cbest_22, *cbest_23, *cbest_24, *cbest_25, *cbest_26, *cbest_27, *cbest_28, *cbest_29, *cbest_30,\n           *cbest_31, *cbest_32;\n\nstatic int cbest_max_k[33] = { -1, -1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 1023, -1,\n     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n     -1, -1, -1, -1 };\n\nstatic int cbest_init = 0;\n\nstatic int *cbest_all[33];\n\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nint cauchy_n_ones(int n, int w)\n{\n  int no;\n  int cno;\n  int nones;\n  int i, j;\n  int highbit;\n\n  highbit = (1 << (w-1));\n\n  if (PPs[w] == -1) {\n    nones = 0;\n    PPs[w] = galois_single_multiply(highbit, 2, w);\n    for (i = 0; i < w; i++) {\n      if (PPs[w] & (1 << i)) {\n        ONEs[w][nones] = (1 << i);\n        nones++;\n      }\n    }\n    NOs[w] = nones;\n  }\n\n  no = 0;\n  for (i = 0; i < w; i++) if (n & (1 << i)) no++;\n  cno = no;\n  for (i = 1; i < w; i++) {\n    if (n & highbit) {\n      n ^= highbit;\n      n <<= 1;\n      n ^= PPs[w];\n      cno--;\n      for (j = 0; j < NOs[w]; j++) {\n        cno += (n & ONEs[w][j]) ? 1 : -1;\n      }\n    } else {\n      n <<= 1;\n    } \n    no += cno;\n  }\n  return no;\n}\n  \nint *cauchy_original_coding_matrix(int k, int m, int w)\n{\n  int *matrix;\n  int i, j, index;\n\n  if (w < 31 && (k+m) > (1 << w)) return NULL;\n  matrix = talloc(int, k*m);\n  if (matrix == NULL) return NULL;\n  index = 0;\n  for (i = 0; i < m; i++) {\n    for (j = 0; j < k; j++) {\n      matrix[index] = galois_single_divide(1, (i ^ (m+j)), w);\n      index++;\n    }\n  }\n  return matrix;\n}\n\nint *cauchy_xy_coding_matrix(int k, int m, int w, int *X, int *Y)\n{\n  int index, i, j;\n  int *matrix;\n\n  matrix = talloc(int, k*m);\n  if (matrix == NULL) { return NULL; }\n  index = 0;\n  for (i = 0; i < m; i++) {\n    for (j = 0; j < k; j++) {\n      matrix[index] = galois_single_divide(1, (X[i] ^ Y[j]), w);\n      index++;\n    }\n  }\n  return matrix;\n}\n\nvoid cauchy_improve_coding_matrix(int k, int m, int w, int *matrix)\n{\n  int index, i, j, x;\n  int tmp;\n  int bno, tno, bno_index;\n\n  for (j = 0; j < k; j++) {\n    if (matrix[j] != 1) {\n      tmp = galois_single_divide(1, matrix[j], w);\n      index = j;\n      for (i = 0; i < m; i++) {\n        matrix[index] = galois_single_multiply(matrix[index], tmp, w);\n        index += k;\n      }\n    }\n  }\n  for (i = 1; i < m; i++) {\n    bno = 0;\n    index = i*k;\n    for (j = 0; j < k; j++) bno += cauchy_n_ones(matrix[index+j], w);\n    bno_index = -1;\n    for (j = 0; j < k; j++) {\n      if (matrix[index+j] != 1) {\n        tmp = galois_single_divide(1, matrix[index+j], w);\n        tno = 0;\n        for (x = 0; x < k; x++) {\n          tno += cauchy_n_ones(galois_single_multiply(matrix[index+x], tmp, w), w);\n        }\n        if (tno < bno) {\n          bno = tno;\n          bno_index = j;\n        }\n      }\n    }\n    if (bno_index != -1) {\n      tmp = galois_single_divide(1, matrix[index+bno_index], w);\n      for (j = 0; j < k; j++) {\n        matrix[index+j] = galois_single_multiply(matrix[index+j], tmp, w);\n      }\n    }\n  }\n}\n\nint *cauchy_good_general_coding_matrix(int k, int m, int w)\n{\n  int *matrix, i;\n\n  if (m == 2 && k <= cbest_max_k[w]) {\n    matrix = talloc(int, k*m);\n    if (matrix == NULL) return NULL;\n    if (!cbest_init) {\n      cbest_init = 1;\n      cbest_all[0] = cbest_0; cbest_all[1] = cbest_1; cbest_all[2] = cbest_2; cbest_all[3] = cbest_3; cbest_all[4] =\n      cbest_4; cbest_all[5] = cbest_5; cbest_all[6] = cbest_6; cbest_all[7] = cbest_7; cbest_all[8] = cbest_8;\n      cbest_all[9] = cbest_9; cbest_all[10] = cbest_10; cbest_all[11] = cbest_11; cbest_all[12] = cbest_12;\n      cbest_all[13] = cbest_13; cbest_all[14] = cbest_14; cbest_all[15] = cbest_15; cbest_all[16] = cbest_16;\n      cbest_all[17] = cbest_17; cbest_all[18] = cbest_18; cbest_all[19] = cbest_19; cbest_all[20] = cbest_20;\n      cbest_all[21] = cbest_21; cbest_all[22] = cbest_22; cbest_all[23] = cbest_23; cbest_all[24] = cbest_24;\n      cbest_all[25] = cbest_25; cbest_all[26] = cbest_26; cbest_all[27] = cbest_27; cbest_all[28] = cbest_28;\n      cbest_all[29] = cbest_29; cbest_all[30] = cbest_30; cbest_all[31] = cbest_31; cbest_all[32] = (int *) cbest_32;\n    }\n    for (i = 0; i < k; i++) {\n      matrix[i] = 1;\n      matrix[i+k] = cbest_all[w][i];\n    }\n    return matrix;\n  } else {\n    matrix = cauchy_original_coding_matrix(k, m, w);\n    if (matrix == NULL) return NULL;\n    cauchy_improve_coding_matrix(k, m, w, matrix);\n    return matrix;\n  }\n}\n\nstatic int cbest_2[3] = { 1, 2, 3 };\nstatic int cbest_3[7] = { 1, 2, 5, 4, 7, 3, 6 };\n\nstatic int cbest_4[15] = { 1, 2, 9, 4, 8, 13, 3, 6, 12, 5, 11, 15, 10, 14, 7 };\n\nstatic int cbest_5[31] = { 1, 2, 18, 4, 9, 8, 22, 16, 3, 11, 19, 5, 10, 6, 20, 27, 13, 23, 26, 12,\n    17, 25, 24, 31, 30, 7, 15, 21, 29, 14, 28 };\n\nstatic int cbest_6[63] = { 1, 2, 33, 4, 8, 49, 16, 32, 57, 3, 6, 12, 24, 48, 5, 35, 9, 37, 10, 17,\n    41, 51, 56, 61, 18, 28, 53, 14, 20, 34, 7, 13, 25, 36, 59, 26, 39, 40, 45, 50, 60, 52, 63,\n    11, 30, 55, 19, 22, 29, 43, 58, 15, 21, 38, 44, 47, 62, 27, 54, 42, 31, 23, 46 };\n\nstatic int cbest_7[127] = { 1, 2, 68, 4, 34, 8, 17, 16, 76, 32, 38, 3, 64, 69, 5, 19, 35, 70, 6, 9,\n    18, 102, 10, 36, 85, 12, 21, 42, 51, 72, 77, 84, 20, 25, 33, 50, 78, 98, 24, 39, 49, 100, 110\n   , 48, 65, 93, 40, 66, 71, 92, 7, 46, 55, 87, 96, 103, 106, 11, 23, 37, 54, 81, 86, 108, 13,\n    22, 27, 43, 53, 73, 80, 14, 26, 52, 74, 79, 99, 119, 44, 95, 101, 104, 111, 118, 29, 59, 89,\n    94, 117, 28, 41, 58, 67, 88, 115, 116, 47, 57, 83, 97, 107, 114, 127, 56, 82, 109, 113, 126,\n    112, 125, 15, 63, 75, 123, 124, 31, 45, 62, 91, 105, 122, 30, 61, 90, 121, 60, 120 };\n\nstatic int cbest_8[255] = { 1, 2, 142, 4, 71, 8, 70, 173, 3, 35, 143, 16, 17, 67, 134, 140, 172, 6, 34\n   , 69, 201, 216, 5, 33, 86, 12, 65, 138, 158, 159, 175, 10, 32, 43, 66, 108, 130, 193, 234, 9,\n    24, 25, 50, 68, 79, 100, 132, 174, 200, 217, 20, 21, 42, 48, 87, 169, 41, 54, 64, 84, 96, 117\n   , 154, 155, 165, 226, 77, 82, 135, 136, 141, 168, 192, 218, 238, 7, 18, 19, 39, 40, 78, 113,\n    116, 128, 164, 180, 195, 205, 220, 232, 14, 26, 27, 58, 109, 156, 157, 203, 235, 13, 28, 29, 38\n   , 51, 56, 75, 85, 90, 101, 110, 112, 139, 171, 11, 37, 49, 52, 76, 83, 102, 119, 131, 150, 151\n   , 167, 182, 184, 188, 197, 219, 224, 45, 55, 80, 94, 97, 133, 170, 194, 204, 221, 227, 236, 36,\n    47, 73, 92, 98, 104, 118, 152, 153, 166, 202, 207, 239, 251, 22, 23, 44, 74, 91, 148, 149, 161\n   , 181, 190, 233, 46, 59, 88, 137, 146, 147, 163, 196, 208, 212, 222, 250, 57, 81, 95, 106, 111,\n    129, 160, 176, 199, 243, 249, 15, 53, 72, 93, 103, 115, 125, 162, 183, 185, 189, 206, 225, 255,\n    186, 210, 230, 237, 242, 248, 30, 31, 62, 89, 99, 105, 114, 121, 124, 178, 209, 213, 223, 228,\n    241, 254, 60, 191, 198, 247, 120, 240, 107, 127, 144, 145, 177, 211, 214, 246, 245, 123, 126,\n    187, 231, 253, 63, 179, 229, 244, 61, 122, 215, 252 };\n\nstatic int cbest_9[511] = { 1, 2, 264, 4, 132, 8, 66, 16, 33, 32, 280, 64, 140, 128, 3, 70, 265, 5,\n    133, 256, 266, 6, 9, 35, 67, 134, 268, 396, 10, 17, 34, 330, 12, 18, 68, 198, 297, 20, 37, 74\n   , 136, 148, 165, 281, 296, 24, 36, 41, 65, 82, 99, 164, 272, 282, 388, 40, 49, 98, 141, 194,\n    284, 328, 412, 48, 97, 129, 142, 196, 346, 71, 72, 96, 130, 313, 392, 80, 206, 257, 267, 312,\n    334, 7, 135, 156, 173, 192, 258, 269, 397, 404, 11, 78, 144, 161, 172, 260, 270, 299, 331, 344,\n    398, 13, 19, 39, 69, 86, 103, 160, 167, 199, 202, 298, 322, 384, 14, 21, 38, 43, 75, 102, 137,\n    149, 166, 204, 289, 332, 408, 462, 22, 25, 42, 51, 83, 101, 138, 150, 273, 283, 288, 301, 350,\n    389, 429, 26, 50, 76, 100, 195, 274, 285, 300, 329, 363, 390, 413, 428, 28, 45, 84, 143, 197,\n    200, 214, 231, 276, 286, 315, 320, 347, 362, 414, 458, 44, 53, 73, 90, 107, 131, 152, 169, 181,\n    230, 314, 338, 361, 393, 400, 454, 460, 52, 57, 81, 106, 115, 168, 175, 180, 207, 229, 305, 335\n   , 348, 360, 394, 421, 478, 56, 105, 114, 157, 163, 174, 193, 210, 227, 228, 259, 304, 317, 326,\n    405, 420, 445, 79, 104, 113, 145, 158, 162, 212, 226, 261, 271, 316, 345, 379, 399, 406, 444,\n    450, 456, 87, 88, 112, 146, 203, 225, 262, 291, 323, 336, 378, 385, 425, 452, 474, 15, 205, 222\n   , 224, 239, 290, 303, 333, 367, 377, 386, 409, 424, 431, 463, 470, 476, 23, 139, 151, 189, 208,\n    238, 302, 324, 351, 366, 376, 410, 430, 437, 27, 47, 77, 94, 111, 177, 188, 237, 275, 293, 342,\n    365, 391, 436, 448, 29, 46, 55, 85, 110, 119, 171, 176, 183, 201, 215, 218, 235, 236, 277, 287,\n    292, 321, 355, 364, 415, 417, 459, 466, 472, 30, 54, 59, 91, 109, 118, 153, 170, 182, 220, 234,\n    278, 307, 339, 354, 401, 416, 423, 441, 455, 461, 468, 495, 58, 108, 117, 154, 233, 306, 319,\n    349, 353, 383, 395, 402, 422, 440, 447, 479, 494, 92, 116, 211, 232, 318, 327, 340, 352, 382,\n    446, 493, 61, 159, 213, 216, 247, 309, 381, 407, 427, 451, 457, 464, 491, 492, 60, 89, 123, 147\n   , 185, 246, 263, 308, 337, 371, 380, 426, 433, 453, 475, 487, 490, 122, 184, 191, 223, 245, 370,\n    387, 432, 439, 471, 477, 486, 489, 511, 121, 179, 190, 209, 243, 244, 295, 325, 359, 369, 411,\n    438, 485, 488, 510, 95, 120, 178, 242, 294, 343, 358, 368, 419, 449, 483, 484, 509, 219, 241,\n    357, 418, 443, 467, 473, 482, 507, 508, 31, 221, 240, 255, 279, 356, 442, 469, 481, 503, 506,\n    155, 254, 403, 480, 502, 505, 63, 93, 127, 253, 311, 341, 375, 501, 504, 62, 126, 187, 217, 251\n   , 252, 310, 374, 435, 465, 499, 500, 125, 186, 250, 373, 434, 498, 124, 249, 372, 497, 248, 496\n    };\n\nstatic int cbest_10[1023] = { 1, 2, 516, 4, 258, 8, 129, 16, 32, 580, 64, 128, 290, 145, 256, 3, 512,\n    517, 5, 259, 518, 588, 6, 9, 18, 36, 72, 144, 774, 10, 17, 131, 262, 288, 524, 645, 12, 33,\n    133, 266, 294, 387, 532, 576, 581, 20, 34, 65, 137, 274, 548, 582, 24, 66, 291, 838, 40, 68,\n    130, 147, 161, 322, 644, 709, 806, 48, 132, 193, 257, 386, 596, 80, 136, 298, 419, 612, 661, 772\n   , 96, 149, 260, 272, 306, 403, 513, 146, 153, 160, 264, 292, 385, 514, 519, 544, 584, 589, 708,\n    870, 7, 19, 37, 73, 192, 354, 590, 770, 775, 11, 38, 74, 177, 263, 289, 418, 520, 525, 534, 641\n   , 660, 725, 802, 836, 846, 13, 22, 76, 148, 209, 267, 295, 320, 330, 402, 526, 528, 533, 577,\n    647, 717, 804, 14, 21, 26, 35, 44, 135, 152, 165, 201, 275, 304, 384, 401, 435, 549, 578, 583,\n    604, 608, 782, 903, 25, 52, 67, 88, 139, 270, 296, 391, 417, 550, 620, 653, 790, 834, 839, 41,\n    50, 69, 104, 141, 176, 278, 302, 323, 395, 423, 540, 598, 640, 705, 724, 807, 866, 28, 42, 49,\n    70, 82, 100, 163, 208, 282, 310, 556, 592, 597, 646, 663, 677, 711, 716, 868, 878, 81, 134, 151\n   , 164, 195, 200, 299, 326, 352, 362, 400, 434, 564, 613, 657, 768, 773, 902, 967, 97, 138, 155,\n    169, 197, 261, 273, 307, 358, 390, 416, 433, 451, 614, 652, 733, 800, 814, 844, 854, 935, 56, 84\n   , 98, 140, 181, 217, 265, 293, 328, 338, 394, 422, 515, 545, 585, 704, 788, 822, 871, 919, 162,\n    179, 276, 355, 407, 427, 546, 586, 591, 616, 662, 669, 676, 710, 727, 741, 771, 780, 901, 39, 75\n   , 150, 157, 194, 211, 225, 268, 280, 308, 314, 389, 411, 439, 521, 530, 535, 628, 656, 721, 803,\n    832, 837, 842, 847, 966, 23, 77, 112, 154, 168, 196, 300, 321, 331, 393, 421, 432, 450, 522, 527\n   , 529, 552, 606, 643, 673, 693, 713, 732, 805, 864, 874, 934, 999, 15, 27, 45, 54, 78, 90, 108,\n    180, 216, 305, 483, 560, 579, 600, 605, 609, 719, 778, 783, 852, 876, 886, 899, 918, 983, 46, 53\n   , 89, 167, 178, 185, 203, 213, 271, 297, 324, 334, 336, 360, 370, 406, 426, 467, 542, 551, 610,\n    621, 649, 668, 726, 740, 786, 791, 810, 820, 835, 900, 917, 931, 951, 965, 975, 30, 51, 105, 156\n   , 205, 210, 224, 279, 303, 356, 366, 388, 405, 410, 438, 449, 459, 536, 541, 594, 599, 622, 655,\n    720, 812, 818, 862, 867, 933, 29, 43, 71, 83, 92, 101, 106, 143, 173, 283, 311, 312, 346, 392,\n    409, 420, 437, 443, 557, 566, 593, 642, 659, 672, 692, 707, 712, 737, 757, 869, 879, 911, 998,\n    60, 102, 241, 327, 353, 363, 399, 425, 482, 558, 565, 624, 679, 718, 735, 749, 769, 798, 898,\n    963, 982, 58, 86, 166, 183, 184, 202, 212, 219, 233, 286, 359, 431, 466, 615, 636, 648, 689, 729\n   , 801, 815, 840, 845, 850, 855, 884, 916, 930, 950, 964, 974, 981, 995, 1015, 57, 85, 99, 120,\n    171, 199, 204, 229, 318, 329, 339, 368, 404, 448, 458, 465, 499, 654, 671, 685, 784, 789, 823,\n    872, 882, 915, 932, 949, 997, 1007, 116, 142, 159, 172, 277, 408, 436, 442, 455, 481, 491, 547,\n    572, 587, 617, 630, 658, 665, 706, 723, 736, 756, 776, 781, 816, 860, 894, 897, 910, 947, 991,\n    114, 221, 240, 269, 281, 309, 315, 332, 342, 344, 378, 398, 424, 441, 475, 487, 531, 618, 629,\n    678, 695, 734, 743, 748, 808, 833, 843, 929, 943, 962, 973, 113, 182, 189, 218, 227, 232, 301,\n    364, 374, 430, 457, 523, 553, 562, 602, 607, 688, 728, 753, 796, 830, 865, 875, 927, 980, 994,\n    1014, 55, 79, 91, 109, 170, 187, 198, 215, 228, 284, 415, 464, 498, 554, 561, 601, 670, 675, 684\n   , 715, 745, 765, 779, 848, 853, 877, 887, 909, 914, 948, 979, 996, 1006, 1013, 47, 110, 158, 249\n   , 316, 325, 335, 337, 361, 371, 397, 447, 454, 480, 490, 497, 538, 543, 611, 632, 664, 722, 787,\n    811, 821, 880, 896, 913, 946, 961, 971, 990, 1011, 31, 94, 220, 245, 357, 367, 429, 440, 474,\n    486, 537, 595, 623, 651, 681, 694, 701, 742, 759, 813, 819, 858, 863, 892, 928, 942, 945, 972,\n    989, 993, 1003, 1023, 62, 93, 107, 188, 207, 226, 237, 243, 313, 340, 347, 376, 456, 471, 473,\n    507, 567, 568, 626, 752, 890, 907, 926, 1005, 61, 103, 124, 175, 186, 214, 372, 414, 453, 463,\n    489, 503, 559, 625, 638, 674, 691, 714, 731, 739, 744, 764, 794, 799, 828, 908, 925, 939, 959,\n    978, 1012, 59, 87, 122, 248, 287, 350, 396, 413, 446, 485, 495, 496, 637, 751, 826, 841, 851,\n    885, 912, 941, 960, 970, 977, 1010, 118, 121, 235, 244, 319, 369, 382, 428, 445, 574, 650, 667,\n    680, 700, 758, 761, 785, 873, 883, 944, 988, 992, 1002, 1009, 1022, 117, 206, 223, 231, 236, 242\n   , 470, 472, 506, 573, 631, 687, 777, 817, 856, 861, 895, 906, 987, 1004, 1021, 115, 174, 191, 333\n   , 343, 345, 379, 452, 462, 469, 488, 502, 505, 619, 690, 697, 730, 738, 755, 809, 888, 924, 938,\n    958, 969, 1019, 253, 365, 375, 412, 484, 494, 501, 563, 603, 750, 767, 792, 797, 831, 923, 940,\n    957, 976, 1001, 234, 251, 285, 348, 444, 479, 555, 634, 666, 760, 824, 849, 905, 955, 1008, 111,\n    222, 230, 247, 317, 380, 461, 511, 539, 633, 686, 703, 747, 881, 937, 986, 1020, 95, 190, 468,\n    493, 504, 570, 696, 754, 859, 893, 968, 985, 1018, 63, 126, 252, 341, 377, 500, 569, 627, 683,\n    766, 891, 922, 956, 1000, 1017, 125, 239, 250, 373, 478, 639, 795, 829, 904, 921, 954, 123, 246,\n    351, 460, 477, 510, 702, 746, 763, 827, 936, 953, 119, 383, 492, 509, 575, 984, 682, 699, 857,\n    1016, 238, 255, 889, 920, 476, 762, 793, 952, 349, 508, 635, 825, 381, 698, 254, 571, 127 };\n\nstatic int cbest_11[1023] = { 1,\n    2, 1026, 4, 513, 8, 16, 1282, 32, 64, 641, 128, 256, 512, 1346, 1024, 3, 673, 1027, 5, 10, 20, 40, 80, 160, 320,\n    640, 6, 9, 515, 1030, 1280, 1539, 17, 517, 1034, 1283, 12, 18, 33, 521, 1042, 1362, 34, 65, 529, 1058, 1286, 1795,\n    24, 36, 66, 129, 545, 643, 1090, 1290, 1667, 68, 130, 257, 577, 645, 672, 1154, 1298, 1344, 48, 72, 132, 258, 336,\n    649, 681, 1314, 1347, 136, 168, 260, 514, 657, 769, 1538, 1923, 84, 96, 144, 264, 516, 1025, 1350, 1410, 1859, 42,\n    272, 520, 705, 1032, 1354, 11, 21, 41, 81, 161, 192, 288, 321, 528, 675, 1028, 1537, 1699, 1794, 7, 22, 82, 162,\n    322, 544, 642, 677, 897, 1031, 1046, 1066, 1106, 1186, 1281, 1366, 1378, 1666, 14, 44, 164, 324, 384, 523, 533,\n    553, 576, 593, 644, 833, 1035, 1040, 1288, 1360, 1987, 13, 19, 28, 88, 328, 519, 648, 680, 689, 1043, 1056, 1284,\n    1363, 1474, 1543, 1793, 1955, 26, 35, 56, 176, 656, 768, 1038, 1059, 1088, 1287, 1302, 1322, 1442, 1547, 1665,\n    1922, 25, 37, 52, 67, 112, 340, 352, 525, 531, 737, 1091, 1152, 1291, 1296, 1555, 1858, 1875, 38, 69, 74, 104, 131,\n    224, 547, 651, 661, 683, 704, 721, 961, 1050, 1062, 1155, 1299, 1312, 1345, 1370, 1571, 1799, 49, 70, 73, 133, 138,\n    148, 170, 208, 259, 337, 448, 537, 549, 579, 647, 674, 929, 1094, 1294, 1315, 1352, 1536, 1603, 1671, 1698, 1803,\n    1921, 50, 134, 137, 169, 261, 266, 276, 296, 338, 416, 581, 676, 896, 1074, 1098, 1158, 1348, 1394, 1408, 1675,\n    1707, 1811, 1857, 2019, 76, 85, 97, 145, 262, 265, 522, 532, 552, 561, 585, 592, 653, 659, 685, 771, 832, 849,\n    1064, 1162, 1194, 1306, 1318, 1351, 1386, 1411, 1506, 1683, 1827, 1986, 2003, 43, 86, 98, 140, 146, 172, 273, 344,\n    518, 688, 773, 1033, 1110, 1122, 1170, 1355, 1490, 1542, 1697, 1792, 1927, 1954, 100, 193, 268, 274, 289, 597, 609,\n    665, 697, 707, 777, 1029, 1044, 1104, 1184, 1330, 1364, 1376, 1414, 1546, 1664, 1731, 1863, 1931, 1963, 23, 46, 83,\n    92, 152, 163, 184, 194, 290, 323, 368, 524, 530, 555, 693, 709, 736, 753, 785, 993, 1036, 1047, 1067, 1107, 1187,\n    1218, 1320, 1358, 1367, 1379, 1418, 1450, 1545, 1554, 1867, 1874, 1939, 1985, 15, 30, 45, 60, 90, 120, 165, 180,\n    196, 240, 280, 292, 325, 330, 360, 385, 480, 546, 650, 660, 679, 682, 713, 720, 745, 801, 899, 960, 977, 1041,\n    1289, 1361, 1426, 1472, 1541, 1570, 1703, 1798, 1953, 29, 58, 89, 116, 166, 200, 232, 326, 329, 386, 464, 535, 536,\n    548, 578, 595, 646, 835, 901, 928, 1048, 1057, 1070, 1190, 1285, 1300, 1368, 1382, 1440, 1475, 1559, 1579, 1602,\n    1619, 1670, 1802, 1879, 1891, 1920, 27, 57, 177, 304, 388, 527, 557, 580, 691, 725, 837, 905, 937, 1039, 1054,\n    1089, 1114, 1292, 1303, 1323, 1374, 1443, 1553, 1674, 1706, 1715, 1801, 1810, 1856, 1873, 1991, 2018, 2035, 53,\n    106, 113, 178, 212, 332, 341, 353, 392, 424, 541, 560, 584, 601, 652, 658, 684, 770, 841, 848, 913, 1060, 1082,\n    1096, 1153, 1202, 1297, 1402, 1478, 1522, 1569, 1673, 1682, 1705, 1797, 1826, 1959, 1995, 2002, 2027, 39, 54, 75,\n    105, 114, 225, 342, 354, 400, 539, 569, 739, 772, 1051, 1063, 1078, 1092, 1138, 1160, 1192, 1304, 1313, 1326, 1371,\n    1384, 1398, 1446, 1482, 1514, 1551, 1601, 1669, 1696, 1763, 1815, 1835, 1926, 71, 139, 149, 171, 209, 226, 298,\n    356, 449, 565, 596, 608, 625, 663, 664, 696, 706, 723, 741, 776, 853, 865, 963, 1072, 1095, 1130, 1156, 1250, 1295,\n    1310, 1353, 1392, 1687, 1730, 1747, 1809, 1862, 1930, 1962, 1971, 2007, 2017, 51, 78, 108, 135, 150, 210, 228, 267,\n    277, 297, 339, 348, 417, 450, 551, 554, 587, 617, 655, 687, 692, 708, 752, 784, 931, 965, 992, 1009, 1075, 1099,\n    1159, 1174, 1234, 1316, 1338, 1349, 1395, 1409, 1458, 1494, 1504, 1544, 1563, 1575, 1681, 1825, 1866, 1883, 1929,\n    1938, 1961, 1984, 2001, 77, 142, 174, 263, 278, 346, 376, 418, 452, 496, 583, 669, 678, 701, 712, 729, 744, 761,\n    800, 898, 933, 969, 976, 1001, 1065, 1108, 1120, 1163, 1168, 1195, 1307, 1319, 1334, 1356, 1387, 1416, 1448, 1488,\n    1507, 1540, 1607, 1702, 1807, 1865, 1925, 1952, 87, 99, 141, 147, 156, 173, 188, 216, 248, 270, 300, 345, 372, 420,\n    456, 488, 534, 563, 594, 667, 699, 757, 779, 789, 809, 834, 851, 900, 1102, 1111, 1123, 1171, 1328, 1412, 1491,\n    1558, 1578, 1587, 1611, 1618, 1679, 1711, 1729, 1861, 1878, 1890, 1907, 1943, 2023, 94, 101, 124, 154, 186, 244,\n    269, 275, 284, 526, 556, 589, 690, 724, 775, 836, 904, 936, 945, 981, 1045, 1068, 1105, 1166, 1185, 1198, 1216,\n    1331, 1365, 1377, 1390, 1415, 1430, 1510, 1552, 1577, 1714, 1800, 1819, 1831, 1872, 1899, 1937, 1990, 2034, 47, 62,\n    93, 102, 122, 153, 185, 195, 282, 291, 312, 362, 369, 432, 468, 540, 599, 600, 611, 715, 747, 840, 857, 912, 1037,\n    1052, 1112, 1126, 1219, 1321, 1359, 1372, 1419, 1424, 1451, 1568, 1623, 1635, 1672, 1691, 1701, 1704, 1723, 1796,\n    1958, 1994, 2011, 2026, 2043, 31, 61, 91, 121, 181, 197, 202, 234, 241, 281, 293, 308, 331, 361, 370, 481, 538,\n    568, 613, 695, 711, 738, 755, 781, 787, 995, 1080, 1118, 1178, 1188, 1210, 1380, 1400, 1427, 1473, 1498, 1530,\n    1550, 1557, 1600, 1617, 1668, 1719, 1735, 1762, 1779, 1814, 1834, 1843, 1877, 1889, 1935, 1967, 1993, 2025, 2039,\n    59, 117, 167, 182, 198, 201, 233, 242, 294, 327, 387, 465, 482, 559, 564, 605, 624, 662, 722, 740, 803, 852, 864,\n    881, 907, 917, 939, 962, 979, 997, 1049, 1071, 1086, 1146, 1191, 1206, 1222, 1266, 1301, 1324, 1369, 1383, 1406,\n    1422, 1441, 1454, 1480, 1512, 1526, 1549, 1686, 1713, 1739, 1746, 1771, 1808, 1833, 1871, 1970, 1989, 2006, 2016,\n    2033, 118, 305, 334, 364, 389, 394, 404, 426, 466, 484, 543, 550, 573, 586, 603, 616, 633, 654, 686, 717, 749, 793,\n    805, 843, 873, 903, 930, 964, 1008, 1055, 1115, 1128, 1142, 1200, 1226, 1258, 1293, 1308, 1375, 1476, 1520, 1562,\n    1574, 1680, 1824 };\n\n"
  },
  {
    "path": "fst/layout/jerasure/src/cauchy_best_r6.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"galois.h\"\n#include \"jerasure.h\"\n#include \"cauchy.h\"\n\nstatic int PPs[33] = { -1, -1, -1, -1, -1, -1, -1, -1,\n                       -1, -1, -1, -1, -1, -1, -1, -1,\n                       -1, -1, -1, -1, -1, -1, -1, -1,\n                       -1, -1, -1, -1, -1, -1, -1, -1, -1 };\nstatic int NOs[33];\nstatic int ONEs[33][33];\n\nstatic int *cbest_0;\nstatic int *cbest_1;\nstatic int cbest_2[3];\nstatic int cbest_3[7];\nstatic int cbest_4[15];\nstatic int cbest_5[31];\nstatic int cbest_6[63];\nstatic int cbest_7[127];\nstatic int cbest_8[255];\nstatic int cbest_9[511];\nstatic int cbest_10[1023];\nstatic int cbest_11[1023]; \nstatic int cbest_12[1023], cbest_13[1023], cbest_14[1023], cbest_15[1023], cbest_16[1023], cbest_17[1023], cbest_18[1023],\n           cbest_19[1023], cbest_20[1023], cbest_21[1023], cbest_22[1023], cbest_23[1023], cbest_24[1023], cbest_25[1023],\n           cbest_26[1023], cbest_27[1023], cbest_28[1023], cbest_29[1023], cbest_30[1023], cbest_31[1023];\nstatic unsigned int cbest_32[1023];\n\nstatic int cbest_max_k[33] = { -1, -1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 1023, \n     1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023,\n     1023, 1023, 1023, 1023 };\n\nstatic int cbest_init = 0;\n\nstatic int *cbest_all[33];\n\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nint cauchy_n_ones(int n, int w)\n{\n  int no;\n  int cno;\n  int nones;\n  int i, j;\n  int highbit;\n\n  highbit = (1 << (w-1));\n\n  if (PPs[w] == -1) {\n    nones = 0;\n    PPs[w] = galois_single_multiply(highbit, 2, w);\n    for (i = 0; i < w; i++) {\n      if (PPs[w] & (1 << i)) {\n        ONEs[w][nones] = (1 << i);\n        nones++;\n      }\n    }\n    NOs[w] = nones;\n  }\n\n  no = 0;\n  for (i = 0; i < w; i++) if (n & (1 << i)) no++;\n  cno = no;\n  for (i = 1; i < w; i++) {\n    if (n & highbit) {\n      n ^= highbit;\n      n <<= 1;\n      n ^= PPs[w];\n      cno--;\n      for (j = 0; j < NOs[w]; j++) {\n        cno += (n & ONEs[w][j]) ? 1 : -1;\n      }\n    } else {\n      n <<= 1;\n    } \n    no += cno;\n  }\n  return no;\n}\n  \nint *cauchy_original_coding_matrix(int k, int m, int w)\n{\n  int *matrix;\n  int i, j, index;\n\n  if (w < 31 && (k+m) > (1 << w)) return NULL;\n  matrix = talloc(int, k*m);\n  if (matrix == NULL) return NULL;\n  index = 0;\n  for (i = 0; i < m; i++) {\n    for (j = 0; j < k; j++) {\n      matrix[index] = galois_single_divide(1, (i ^ (m+j)), w);\n      index++;\n    }\n  }\n  return matrix;\n}\n\nint *cauchy_xy_coding_matrix(int k, int m, int w, int *X, int *Y)\n{\n  int index, i, j;\n  int *matrix;\n\n  matrix = talloc(int, k*m);\n  if (matrix == NULL) { return NULL; }\n  index = 0;\n  for (i = 0; i < m; i++) {\n    for (j = 0; j < k; j++) {\n      matrix[index] = galois_single_divide(1, (X[i] ^ Y[j]), w);\n      index++;\n    }\n  }\n  return matrix;\n}\n\nvoid cauchy_improve_coding_matrix(int k, int m, int w, int *matrix)\n{\n  int index, i, j, x;\n  int tmp;\n  int bno, tno, bno_index;\n\n  for (j = 0; j < k; j++) {\n    if (matrix[j] != 1) {\n      tmp = galois_single_divide(1, matrix[j], w);\n      index = j;\n      for (i = 0; i < m; i++) {\n        matrix[index] = galois_single_multiply(matrix[index], tmp, w);\n        index += k;\n      }\n    }\n  }\n  for (i = 1; i < m; i++) {\n    bno = 0;\n    index = i*k;\n    for (j = 0; j < k; j++) bno += cauchy_n_ones(matrix[index+j], w);\n    bno_index = -1;\n    for (j = 0; j < k; j++) {\n      if (matrix[index+j] != 1) {\n        tmp = galois_single_divide(1, matrix[index+j], w);\n        tno = 0;\n        for (x = 0; x < k; x++) {\n          tno += cauchy_n_ones(galois_single_multiply(matrix[index+x], tmp, w), w);\n        }\n        if (tno < bno) {\n          bno = tno;\n          bno_index = j;\n        }\n      }\n    }\n    if (bno_index != -1) {\n      tmp = galois_single_divide(1, matrix[index+bno_index], w);\n      for (j = 0; j < k; j++) {\n        matrix[index+j] = galois_single_multiply(matrix[index+j], tmp, w);\n      }\n    }\n  }\n}\n\nint *cauchy_good_general_coding_matrix(int k, int m, int w)\n{\n  int *matrix, i;\n\n  if (m == 2 && k <= cbest_max_k[w]) {\n    matrix = talloc(int, k*m);\n    if (matrix == NULL) return NULL;\n    if (!cbest_init) {\n      cbest_init = 1;\n      cbest_all[0] = cbest_0; cbest_all[1] = cbest_1; cbest_all[2] = cbest_2; cbest_all[3] = cbest_3; cbest_all[4] =\n      cbest_4; cbest_all[5] = cbest_5; cbest_all[6] = cbest_6; cbest_all[7] = cbest_7; cbest_all[8] = cbest_8;\n      cbest_all[9] = cbest_9; cbest_all[10] = cbest_10; cbest_all[11] = cbest_11; cbest_all[12] = cbest_12;\n      cbest_all[13] = cbest_13; cbest_all[14] = cbest_14; cbest_all[15] = cbest_15; cbest_all[16] = cbest_16;\n      cbest_all[17] = cbest_17; cbest_all[18] = cbest_18; cbest_all[19] = cbest_19; cbest_all[20] = cbest_20;\n      cbest_all[21] = cbest_21; cbest_all[22] = cbest_22; cbest_all[23] = cbest_23; cbest_all[24] = cbest_24;\n      cbest_all[25] = cbest_25; cbest_all[26] = cbest_26; cbest_all[27] = cbest_27; cbest_all[28] = cbest_28;\n      cbest_all[29] = cbest_29; cbest_all[30] = cbest_30; cbest_all[31] = cbest_31; cbest_all[32] = (int *) cbest_32;\n    }\n    for (i = 0; i < k; i++) {\n      matrix[i] = 1;\n      matrix[i+k] = cbest_all[w][i];\n    }\n    return matrix;\n  } else {\n    matrix = cauchy_original_coding_matrix(k, m, w);\n    if (matrix == NULL) return NULL;\n    cauchy_improve_coding_matrix(k, m, w, matrix);\n    return matrix;\n  }\n}\n\nstatic int cbest_2[3] = { 1, 2, 3 };\nstatic int cbest_3[7] = { 1, 2, 5, 4, 7, 3, 6 };\n\nstatic int cbest_4[15] = { 1, 2, 9, 4, 8, 13, 3, 6, 12, 5, 11, 15, 10, 14, 7 };\n\nstatic int cbest_5[31] = { 1, 2, 18, 4, 9, 8, 22, 16, 3, 11, 19, 5, 10, 6, 20, 27, 13, 23, 26, 12,\n    17, 25, 24, 31, 30, 7, 15, 21, 29, 14, 28 };\n\nstatic int cbest_6[63] = { 1, 2, 33, 4, 8, 49, 16, 32, 57, 3, 6, 12, 24, 48, 5, 35, 9, 37, 10, 17,\n    41, 51, 56, 61, 18, 28, 53, 14, 20, 34, 7, 13, 25, 36, 59, 26, 39, 40, 45, 50, 60, 52, 63,\n    11, 30, 55, 19, 22, 29, 43, 58, 15, 21, 38, 44, 47, 62, 27, 54, 42, 31, 23, 46 };\n\nstatic int cbest_7[127] = { 1, 2, 68, 4, 34, 8, 17, 16, 76, 32, 38, 3, 64, 69, 5, 19, 35, 70, 6, 9,\n    18, 102, 10, 36, 85, 12, 21, 42, 51, 72, 77, 84, 20, 25, 33, 50, 78, 98, 24, 39, 49, 100, 110\n   , 48, 65, 93, 40, 66, 71, 92, 7, 46, 55, 87, 96, 103, 106, 11, 23, 37, 54, 81, 86, 108, 13,\n    22, 27, 43, 53, 73, 80, 14, 26, 52, 74, 79, 99, 119, 44, 95, 101, 104, 111, 118, 29, 59, 89,\n    94, 117, 28, 41, 58, 67, 88, 115, 116, 47, 57, 83, 97, 107, 114, 127, 56, 82, 109, 113, 126,\n    112, 125, 15, 63, 75, 123, 124, 31, 45, 62, 91, 105, 122, 30, 61, 90, 121, 60, 120 };\n\nstatic int cbest_8[255] = { 1, 2, 142, 4, 71, 8, 70, 173, 3, 35, 143, 16, 17, 67, 134, 140, 172, 6, 34\n   , 69, 201, 216, 5, 33, 86, 12, 65, 138, 158, 159, 175, 10, 32, 43, 66, 108, 130, 193, 234, 9,\n    24, 25, 50, 68, 79, 100, 132, 174, 200, 217, 20, 21, 42, 48, 87, 169, 41, 54, 64, 84, 96, 117\n   , 154, 155, 165, 226, 77, 82, 135, 136, 141, 168, 192, 218, 238, 7, 18, 19, 39, 40, 78, 113,\n    116, 128, 164, 180, 195, 205, 220, 232, 14, 26, 27, 58, 109, 156, 157, 203, 235, 13, 28, 29, 38\n   , 51, 56, 75, 85, 90, 101, 110, 112, 139, 171, 11, 37, 49, 52, 76, 83, 102, 119, 131, 150, 151\n   , 167, 182, 184, 188, 197, 219, 224, 45, 55, 80, 94, 97, 133, 170, 194, 204, 221, 227, 236, 36,\n    47, 73, 92, 98, 104, 118, 152, 153, 166, 202, 207, 239, 251, 22, 23, 44, 74, 91, 148, 149, 161\n   , 181, 190, 233, 46, 59, 88, 137, 146, 147, 163, 196, 208, 212, 222, 250, 57, 81, 95, 106, 111,\n    129, 160, 176, 199, 243, 249, 15, 53, 72, 93, 103, 115, 125, 162, 183, 185, 189, 206, 225, 255,\n    186, 210, 230, 237, 242, 248, 30, 31, 62, 89, 99, 105, 114, 121, 124, 178, 209, 213, 223, 228,\n    241, 254, 60, 191, 198, 247, 120, 240, 107, 127, 144, 145, 177, 211, 214, 246, 245, 123, 126,\n    187, 231, 253, 63, 179, 229, 244, 61, 122, 215, 252 };\n\nstatic int cbest_9[511] = { 1, 2, 264, 4, 132, 8, 66, 16, 33, 32, 280, 64, 140, 128, 3, 70, 265, 5,\n    133, 256, 266, 6, 9, 35, 67, 134, 268, 396, 10, 17, 34, 330, 12, 18, 68, 198, 297, 20, 37, 74\n   , 136, 148, 165, 281, 296, 24, 36, 41, 65, 82, 99, 164, 272, 282, 388, 40, 49, 98, 141, 194,\n    284, 328, 412, 48, 97, 129, 142, 196, 346, 71, 72, 96, 130, 313, 392, 80, 206, 257, 267, 312,\n    334, 7, 135, 156, 173, 192, 258, 269, 397, 404, 11, 78, 144, 161, 172, 260, 270, 299, 331, 344,\n    398, 13, 19, 39, 69, 86, 103, 160, 167, 199, 202, 298, 322, 384, 14, 21, 38, 43, 75, 102, 137,\n    149, 166, 204, 289, 332, 408, 462, 22, 25, 42, 51, 83, 101, 138, 150, 273, 283, 288, 301, 350,\n    389, 429, 26, 50, 76, 100, 195, 274, 285, 300, 329, 363, 390, 413, 428, 28, 45, 84, 143, 197,\n    200, 214, 231, 276, 286, 315, 320, 347, 362, 414, 458, 44, 53, 73, 90, 107, 131, 152, 169, 181,\n    230, 314, 338, 361, 393, 400, 454, 460, 52, 57, 81, 106, 115, 168, 175, 180, 207, 229, 305, 335\n   , 348, 360, 394, 421, 478, 56, 105, 114, 157, 163, 174, 193, 210, 227, 228, 259, 304, 317, 326,\n    405, 420, 445, 79, 104, 113, 145, 158, 162, 212, 226, 261, 271, 316, 345, 379, 399, 406, 444,\n    450, 456, 87, 88, 112, 146, 203, 225, 262, 291, 323, 336, 378, 385, 425, 452, 474, 15, 205, 222\n   , 224, 239, 290, 303, 333, 367, 377, 386, 409, 424, 431, 463, 470, 476, 23, 139, 151, 189, 208,\n    238, 302, 324, 351, 366, 376, 410, 430, 437, 27, 47, 77, 94, 111, 177, 188, 237, 275, 293, 342,\n    365, 391, 436, 448, 29, 46, 55, 85, 110, 119, 171, 176, 183, 201, 215, 218, 235, 236, 277, 287,\n    292, 321, 355, 364, 415, 417, 459, 466, 472, 30, 54, 59, 91, 109, 118, 153, 170, 182, 220, 234,\n    278, 307, 339, 354, 401, 416, 423, 441, 455, 461, 468, 495, 58, 108, 117, 154, 233, 306, 319,\n    349, 353, 383, 395, 402, 422, 440, 447, 479, 494, 92, 116, 211, 232, 318, 327, 340, 352, 382,\n    446, 493, 61, 159, 213, 216, 247, 309, 381, 407, 427, 451, 457, 464, 491, 492, 60, 89, 123, 147\n   , 185, 246, 263, 308, 337, 371, 380, 426, 433, 453, 475, 487, 490, 122, 184, 191, 223, 245, 370,\n    387, 432, 439, 471, 477, 486, 489, 511, 121, 179, 190, 209, 243, 244, 295, 325, 359, 369, 411,\n    438, 485, 488, 510, 95, 120, 178, 242, 294, 343, 358, 368, 419, 449, 483, 484, 509, 219, 241,\n    357, 418, 443, 467, 473, 482, 507, 508, 31, 221, 240, 255, 279, 356, 442, 469, 481, 503, 506,\n    155, 254, 403, 480, 502, 505, 63, 93, 127, 253, 311, 341, 375, 501, 504, 62, 126, 187, 217, 251\n   , 252, 310, 374, 435, 465, 499, 500, 125, 186, 250, 373, 434, 498, 124, 249, 372, 497, 248, 496\n    };\n\nstatic int cbest_10[1023] = { 1, 2, 516, 4, 258, 8, 129, 16, 32, 580, 64, 128, 290, 145, 256, 3, 512,\n    517, 5, 259, 518, 588, 6, 9, 18, 36, 72, 144, 774, 10, 17, 131, 262, 288, 524, 645, 12, 33,\n    133, 266, 294, 387, 532, 576, 581, 20, 34, 65, 137, 274, 548, 582, 24, 66, 291, 838, 40, 68,\n    130, 147, 161, 322, 644, 709, 806, 48, 132, 193, 257, 386, 596, 80, 136, 298, 419, 612, 661, 772\n   , 96, 149, 260, 272, 306, 403, 513, 146, 153, 160, 264, 292, 385, 514, 519, 544, 584, 589, 708,\n    870, 7, 19, 37, 73, 192, 354, 590, 770, 775, 11, 38, 74, 177, 263, 289, 418, 520, 525, 534, 641\n   , 660, 725, 802, 836, 846, 13, 22, 76, 148, 209, 267, 295, 320, 330, 402, 526, 528, 533, 577,\n    647, 717, 804, 14, 21, 26, 35, 44, 135, 152, 165, 201, 275, 304, 384, 401, 435, 549, 578, 583,\n    604, 608, 782, 903, 25, 52, 67, 88, 139, 270, 296, 391, 417, 550, 620, 653, 790, 834, 839, 41,\n    50, 69, 104, 141, 176, 278, 302, 323, 395, 423, 540, 598, 640, 705, 724, 807, 866, 28, 42, 49,\n    70, 82, 100, 163, 208, 282, 310, 556, 592, 597, 646, 663, 677, 711, 716, 868, 878, 81, 134, 151\n   , 164, 195, 200, 299, 326, 352, 362, 400, 434, 564, 613, 657, 768, 773, 902, 967, 97, 138, 155,\n    169, 197, 261, 273, 307, 358, 390, 416, 433, 451, 614, 652, 733, 800, 814, 844, 854, 935, 56, 84\n   , 98, 140, 181, 217, 265, 293, 328, 338, 394, 422, 515, 545, 585, 704, 788, 822, 871, 919, 162,\n    179, 276, 355, 407, 427, 546, 586, 591, 616, 662, 669, 676, 710, 727, 741, 771, 780, 901, 39, 75\n   , 150, 157, 194, 211, 225, 268, 280, 308, 314, 389, 411, 439, 521, 530, 535, 628, 656, 721, 803,\n    832, 837, 842, 847, 966, 23, 77, 112, 154, 168, 196, 300, 321, 331, 393, 421, 432, 450, 522, 527\n   , 529, 552, 606, 643, 673, 693, 713, 732, 805, 864, 874, 934, 999, 15, 27, 45, 54, 78, 90, 108,\n    180, 216, 305, 483, 560, 579, 600, 605, 609, 719, 778, 783, 852, 876, 886, 899, 918, 983, 46, 53\n   , 89, 167, 178, 185, 203, 213, 271, 297, 324, 334, 336, 360, 370, 406, 426, 467, 542, 551, 610,\n    621, 649, 668, 726, 740, 786, 791, 810, 820, 835, 900, 917, 931, 951, 965, 975, 30, 51, 105, 156\n   , 205, 210, 224, 279, 303, 356, 366, 388, 405, 410, 438, 449, 459, 536, 541, 594, 599, 622, 655,\n    720, 812, 818, 862, 867, 933, 29, 43, 71, 83, 92, 101, 106, 143, 173, 283, 311, 312, 346, 392,\n    409, 420, 437, 443, 557, 566, 593, 642, 659, 672, 692, 707, 712, 737, 757, 869, 879, 911, 998,\n    60, 102, 241, 327, 353, 363, 399, 425, 482, 558, 565, 624, 679, 718, 735, 749, 769, 798, 898,\n    963, 982, 58, 86, 166, 183, 184, 202, 212, 219, 233, 286, 359, 431, 466, 615, 636, 648, 689, 729\n   , 801, 815, 840, 845, 850, 855, 884, 916, 930, 950, 964, 974, 981, 995, 1015, 57, 85, 99, 120,\n    171, 199, 204, 229, 318, 329, 339, 368, 404, 448, 458, 465, 499, 654, 671, 685, 784, 789, 823,\n    872, 882, 915, 932, 949, 997, 1007, 116, 142, 159, 172, 277, 408, 436, 442, 455, 481, 491, 547,\n    572, 587, 617, 630, 658, 665, 706, 723, 736, 756, 776, 781, 816, 860, 894, 897, 910, 947, 991,\n    114, 221, 240, 269, 281, 309, 315, 332, 342, 344, 378, 398, 424, 441, 475, 487, 531, 618, 629,\n    678, 695, 734, 743, 748, 808, 833, 843, 929, 943, 962, 973, 113, 182, 189, 218, 227, 232, 301,\n    364, 374, 430, 457, 523, 553, 562, 602, 607, 688, 728, 753, 796, 830, 865, 875, 927, 980, 994,\n    1014, 55, 79, 91, 109, 170, 187, 198, 215, 228, 284, 415, 464, 498, 554, 561, 601, 670, 675, 684\n   , 715, 745, 765, 779, 848, 853, 877, 887, 909, 914, 948, 979, 996, 1006, 1013, 47, 110, 158, 249\n   , 316, 325, 335, 337, 361, 371, 397, 447, 454, 480, 490, 497, 538, 543, 611, 632, 664, 722, 787,\n    811, 821, 880, 896, 913, 946, 961, 971, 990, 1011, 31, 94, 220, 245, 357, 367, 429, 440, 474,\n    486, 537, 595, 623, 651, 681, 694, 701, 742, 759, 813, 819, 858, 863, 892, 928, 942, 945, 972,\n    989, 993, 1003, 1023, 62, 93, 107, 188, 207, 226, 237, 243, 313, 340, 347, 376, 456, 471, 473,\n    507, 567, 568, 626, 752, 890, 907, 926, 1005, 61, 103, 124, 175, 186, 214, 372, 414, 453, 463,\n    489, 503, 559, 625, 638, 674, 691, 714, 731, 739, 744, 764, 794, 799, 828, 908, 925, 939, 959,\n    978, 1012, 59, 87, 122, 248, 287, 350, 396, 413, 446, 485, 495, 496, 637, 751, 826, 841, 851,\n    885, 912, 941, 960, 970, 977, 1010, 118, 121, 235, 244, 319, 369, 382, 428, 445, 574, 650, 667,\n    680, 700, 758, 761, 785, 873, 883, 944, 988, 992, 1002, 1009, 1022, 117, 206, 223, 231, 236, 242\n   , 470, 472, 506, 573, 631, 687, 777, 817, 856, 861, 895, 906, 987, 1004, 1021, 115, 174, 191, 333\n   , 343, 345, 379, 452, 462, 469, 488, 502, 505, 619, 690, 697, 730, 738, 755, 809, 888, 924, 938,\n    958, 969, 1019, 253, 365, 375, 412, 484, 494, 501, 563, 603, 750, 767, 792, 797, 831, 923, 940,\n    957, 976, 1001, 234, 251, 285, 348, 444, 479, 555, 634, 666, 760, 824, 849, 905, 955, 1008, 111,\n    222, 230, 247, 317, 380, 461, 511, 539, 633, 686, 703, 747, 881, 937, 986, 1020, 95, 190, 468,\n    493, 504, 570, 696, 754, 859, 893, 968, 985, 1018, 63, 126, 252, 341, 377, 500, 569, 627, 683,\n    766, 891, 922, 956, 1000, 1017, 125, 239, 250, 373, 478, 639, 795, 829, 904, 921, 954, 123, 246,\n    351, 460, 477, 510, 702, 746, 763, 827, 936, 953, 119, 383, 492, 509, 575, 984, 682, 699, 857,\n    1016, 238, 255, 889, 920, 476, 762, 793, 952, 349, 508, 635, 825, 381, 698, 254, 571, 127 };\n\nstatic int cbest_11[1023] = { 1,\n    2, 1026, 4, 513, 8, 16, 1282, 32, 64, 641, 128, 256, 512, 1346, 1024, 3, 673, 1027, 5, 10, 20, 40, 80, 160, 320,\n    640, 6, 9, 515, 1030, 1280, 1539, 17, 517, 1034, 1283, 12, 18, 33, 521, 1042, 1362, 34, 65, 529, 1058, 1286, 1795,\n    24, 36, 66, 129, 545, 643, 1090, 1290, 1667, 68, 130, 257, 577, 645, 672, 1154, 1298, 1344, 48, 72, 132, 258, 336,\n    649, 681, 1314, 1347, 136, 168, 260, 514, 657, 769, 1538, 1923, 84, 96, 144, 264, 516, 1025, 1350, 1410, 1859, 42,\n    272, 520, 705, 1032, 1354, 11, 21, 41, 81, 161, 192, 288, 321, 528, 675, 1028, 1537, 1699, 1794, 7, 22, 82, 162,\n    322, 544, 642, 677, 897, 1031, 1046, 1066, 1106, 1186, 1281, 1366, 1378, 1666, 14, 44, 164, 324, 384, 523, 533,\n    553, 576, 593, 644, 833, 1035, 1040, 1288, 1360, 1987, 13, 19, 28, 88, 328, 519, 648, 680, 689, 1043, 1056, 1284,\n    1363, 1474, 1543, 1793, 1955, 26, 35, 56, 176, 656, 768, 1038, 1059, 1088, 1287, 1302, 1322, 1442, 1547, 1665,\n    1922, 25, 37, 52, 67, 112, 340, 352, 525, 531, 737, 1091, 1152, 1291, 1296, 1555, 1858, 1875, 38, 69, 74, 104, 131,\n    224, 547, 651, 661, 683, 704, 721, 961, 1050, 1062, 1155, 1299, 1312, 1345, 1370, 1571, 1799, 49, 70, 73, 133, 138,\n    148, 170, 208, 259, 337, 448, 537, 549, 579, 647, 674, 929, 1094, 1294, 1315, 1352, 1536, 1603, 1671, 1698, 1803,\n    1921, 50, 134, 137, 169, 261, 266, 276, 296, 338, 416, 581, 676, 896, 1074, 1098, 1158, 1348, 1394, 1408, 1675,\n    1707, 1811, 1857, 2019, 76, 85, 97, 145, 262, 265, 522, 532, 552, 561, 585, 592, 653, 659, 685, 771, 832, 849,\n    1064, 1162, 1194, 1306, 1318, 1351, 1386, 1411, 1506, 1683, 1827, 1986, 2003, 43, 86, 98, 140, 146, 172, 273, 344,\n    518, 688, 773, 1033, 1110, 1122, 1170, 1355, 1490, 1542, 1697, 1792, 1927, 1954, 100, 193, 268, 274, 289, 597, 609,\n    665, 697, 707, 777, 1029, 1044, 1104, 1184, 1330, 1364, 1376, 1414, 1546, 1664, 1731, 1863, 1931, 1963, 23, 46, 83,\n    92, 152, 163, 184, 194, 290, 323, 368, 524, 530, 555, 693, 709, 736, 753, 785, 993, 1036, 1047, 1067, 1107, 1187,\n    1218, 1320, 1358, 1367, 1379, 1418, 1450, 1545, 1554, 1867, 1874, 1939, 1985, 15, 30, 45, 60, 90, 120, 165, 180,\n    196, 240, 280, 292, 325, 330, 360, 385, 480, 546, 650, 660, 679, 682, 713, 720, 745, 801, 899, 960, 977, 1041,\n    1289, 1361, 1426, 1472, 1541, 1570, 1703, 1798, 1953, 29, 58, 89, 116, 166, 200, 232, 326, 329, 386, 464, 535, 536,\n    548, 578, 595, 646, 835, 901, 928, 1048, 1057, 1070, 1190, 1285, 1300, 1368, 1382, 1440, 1475, 1559, 1579, 1602,\n    1619, 1670, 1802, 1879, 1891, 1920, 27, 57, 177, 304, 388, 527, 557, 580, 691, 725, 837, 905, 937, 1039, 1054,\n    1089, 1114, 1292, 1303, 1323, 1374, 1443, 1553, 1674, 1706, 1715, 1801, 1810, 1856, 1873, 1991, 2018, 2035, 53,\n    106, 113, 178, 212, 332, 341, 353, 392, 424, 541, 560, 584, 601, 652, 658, 684, 770, 841, 848, 913, 1060, 1082,\n    1096, 1153, 1202, 1297, 1402, 1478, 1522, 1569, 1673, 1682, 1705, 1797, 1826, 1959, 1995, 2002, 2027, 39, 54, 75,\n    105, 114, 225, 342, 354, 400, 539, 569, 739, 772, 1051, 1063, 1078, 1092, 1138, 1160, 1192, 1304, 1313, 1326, 1371,\n    1384, 1398, 1446, 1482, 1514, 1551, 1601, 1669, 1696, 1763, 1815, 1835, 1926, 71, 139, 149, 171, 209, 226, 298,\n    356, 449, 565, 596, 608, 625, 663, 664, 696, 706, 723, 741, 776, 853, 865, 963, 1072, 1095, 1130, 1156, 1250, 1295,\n    1310, 1353, 1392, 1687, 1730, 1747, 1809, 1862, 1930, 1962, 1971, 2007, 2017, 51, 78, 108, 135, 150, 210, 228, 267,\n    277, 297, 339, 348, 417, 450, 551, 554, 587, 617, 655, 687, 692, 708, 752, 784, 931, 965, 992, 1009, 1075, 1099,\n    1159, 1174, 1234, 1316, 1338, 1349, 1395, 1409, 1458, 1494, 1504, 1544, 1563, 1575, 1681, 1825, 1866, 1883, 1929,\n    1938, 1961, 1984, 2001, 77, 142, 174, 263, 278, 346, 376, 418, 452, 496, 583, 669, 678, 701, 712, 729, 744, 761,\n    800, 898, 933, 969, 976, 1001, 1065, 1108, 1120, 1163, 1168, 1195, 1307, 1319, 1334, 1356, 1387, 1416, 1448, 1488,\n    1507, 1540, 1607, 1702, 1807, 1865, 1925, 1952, 87, 99, 141, 147, 156, 173, 188, 216, 248, 270, 300, 345, 372, 420,\n    456, 488, 534, 563, 594, 667, 699, 757, 779, 789, 809, 834, 851, 900, 1102, 1111, 1123, 1171, 1328, 1412, 1491,\n    1558, 1578, 1587, 1611, 1618, 1679, 1711, 1729, 1861, 1878, 1890, 1907, 1943, 2023, 94, 101, 124, 154, 186, 244,\n    269, 275, 284, 526, 556, 589, 690, 724, 775, 836, 904, 936, 945, 981, 1045, 1068, 1105, 1166, 1185, 1198, 1216,\n    1331, 1365, 1377, 1390, 1415, 1430, 1510, 1552, 1577, 1714, 1800, 1819, 1831, 1872, 1899, 1937, 1990, 2034, 47, 62,\n    93, 102, 122, 153, 185, 195, 282, 291, 312, 362, 369, 432, 468, 540, 599, 600, 611, 715, 747, 840, 857, 912, 1037,\n    1052, 1112, 1126, 1219, 1321, 1359, 1372, 1419, 1424, 1451, 1568, 1623, 1635, 1672, 1691, 1701, 1704, 1723, 1796,\n    1958, 1994, 2011, 2026, 2043, 31, 61, 91, 121, 181, 197, 202, 234, 241, 281, 293, 308, 331, 361, 370, 481, 538,\n    568, 613, 695, 711, 738, 755, 781, 787, 995, 1080, 1118, 1178, 1188, 1210, 1380, 1400, 1427, 1473, 1498, 1530,\n    1550, 1557, 1600, 1617, 1668, 1719, 1735, 1762, 1779, 1814, 1834, 1843, 1877, 1889, 1935, 1967, 1993, 2025, 2039,\n    59, 117, 167, 182, 198, 201, 233, 242, 294, 327, 387, 465, 482, 559, 564, 605, 624, 662, 722, 740, 803, 852, 864,\n    881, 907, 917, 939, 962, 979, 997, 1049, 1071, 1086, 1146, 1191, 1206, 1222, 1266, 1301, 1324, 1369, 1383, 1406,\n    1422, 1441, 1454, 1480, 1512, 1526, 1549, 1686, 1713, 1739, 1746, 1771, 1808, 1833, 1871, 1970, 1989, 2006, 2016,\n    2033, 118, 305, 334, 364, 389, 394, 404, 426, 466, 484, 543, 550, 573, 586, 603, 616, 633, 654, 686, 717, 749, 793,\n    805, 843, 873, 903, 930, 964, 1008, 1055, 1115, 1128, 1142, 1200, 1226, 1258, 1293, 1308, 1375, 1476, 1520, 1562,\n    1574, 1680, 1824 };\n\nstatic int cbest_12[1023] = {\n    1, 2, 2089, 4, 8, 3133, 16, 2088, 1044, 3, 32, 522, 261, 3639, 5, 64, 65, 2057, 2091, 6, 130, 3132, 260,\n    2219, 9, 2093, 3117, 10, 128, 257, 2081, 3890, 12, 1566, 2217, 3129, 17, 514, 520, 3135, 18, 3196, 3637, 20,\n    256, 1028, 1045, 3638, 24, 33, 523, 783, 2105, 3197, 3647, 34, 1040, 1945, 2056, 2090, 2595, 3125, 3891, 36,\n    1046, 2348, 40, 1598, 2218, 3635, 7, 48, 66, 67, 131, 263, 269, 512, 538, 782, 1076, 1174, 1819, 2049, 2059,\n    2092, 2152, 3113, 3116, 3384, 3607, 3888, 68, 69, 277, 554, 1108, 1109, 1558, 1564, 1944, 2061, 2080, 2478,\n    3894, 72, 73, 134, 265, 293, 391, 526, 586, 587, 779, 2153, 2211, 2216, 2563, 3128, 4016, 11, 14, 80, 81,\n    129, 132, 138, 530, 1172, 1692, 2073, 2095, 2223, 2316, 3101, 3109, 3119, 3134, 3645, 3874, 13, 96, 97, 146,\n    259, 262, 268, 289, 799, 1052, 1060, 1158, 1567, 1818, 2083, 2221, 2344, 2349, 2476, 2980, 3045, 3192, 3368,\n    3636, 144, 162, 276, 389, 515, 521, 578, 579, 781, 972, 1024, 1156, 2085, 2120, 2235, 2282, 2312, 3131, 3623,\n    19, 22, 28, 160, 194, 195, 264, 292, 390, 778, 846, 1238, 1239, 1562, 1596, 1684, 1937, 1947, 2104, 2233,\n    2593, 3193, 3385, 3633, 3643, 3646, 3889, 21, 26, 324, 518, 536, 909, 1029, 1297, 2008, 2121, 2209, 2283,\n    2479, 2594, 2854, 3085, 3121, 3124, 3188, 3198, 3605, 3895, 25, 136, 258, 288, 528, 552, 798, 842, 1030,\n    1140, 1141, 1490, 1556, 2470, 2603, 2626, 2721, 3892, 3898, 4017, 35, 38, 44, 56, 320, 388, 423, 486, 524,\n    570, 584, 585, 618, 619, 648, 777, 780, 1041, 1594, 1817, 2097, 2107, 2317, 2561, 3105, 3164, 3189, 3199,\n    3263, 3634, 3875, 37, 42, 52, 285, 309, 421, 1036, 1042, 1047, 1056, 1072, 1170, 1313, 1427, 1823, 1936,\n    1946, 2048, 2053, 2058, 2065, 2109, 2280, 2345, 2477, 2579, 2627, 2981, 3041, 3044, 3112, 3127, 3369, 3388,\n    3603, 3606, 3631, 3765, 3872, 41, 192, 193, 273, 399, 775, 797, 908, 968, 1104, 1105, 1168, 1236, 1237, 1281,\n    1296, 1554, 1582, 1599, 1949, 2051, 2060, 2313, 2332, 2340, 2474, 3115, 3165, 3336, 3360, 3641, 4018, 49, 70,\n    71, 76, 77, 88, 89, 112, 113, 387, 454, 484, 513, 516, 539, 640, 744, 745, 1004, 1077, 1175, 1234, 1235,\n    1550, 1668, 1680, 1694, 1803, 2063, 2112, 2210, 2215, 2227, 2281, 2472, 2562, 2784, 3261, 3449, 3547, 3591,\n    3621, 84, 85, 104, 105, 142, 154, 178, 226, 227, 242, 243, 267, 271, 385, 422, 534, 555, 771, 776, 791, 834,\n    840, 844, 973, 1068, 1078, 1166, 1425, 1488, 1559, 1560, 1565, 1688, 1816, 2072, 2077, 2094, 2136, 2144,\n    2154, 2222, 2298, 2336, 2350, 2468, 2543, 2611, 2624, 2658, 2729, 2850, 2855, 2982, 3081, 3097, 3100, 3108,\n    3118, 3184, 3247, 3326, 3376, 3644, 3702, 4024, 82, 83, 135, 164, 170, 210, 211, 279, 284, 308, 328, 356,\n    372, 417, 420, 527, 542, 576, 577, 616, 617, 656, 795, 847, 1048, 1110, 1111, 1312, 1329, 1392, 1426, 1590,\n    1592, 1822, 2082, 2113, 2128, 2156, 2187, 2220, 2364, 2466, 2471, 2785, 2976, 3077, 3111, 3180, 3245, 3882,\n    3893, 3899, 3902, 4081, 15, 30, 50, 60, 98, 99, 120, 121, 133, 139, 166, 272, 291, 295, 325, 340, 398, 419,\n    531, 558, 582, 583, 712, 713, 774, 796, 838, 1064, 1084, 1173, 1182, 1232, 1233, 1280, 1522, 1542, 1676,\n    1693, 1811, 1948, 2009, 2084, 2137, 2145, 2155, 2185, 2213, 2225, 2234, 2286, 2299, 2599, 2601, 2625, 2659,\n    2745, 2870, 2988, 3047, 3130, 3185, 3194, 3327, 3352, 3386, 3545, 3601, 3619, 3622, 3629, 3703, 3733, 3773,\n    3896, 3955, 140, 147, 152, 176, 186, 198, 199, 224, 225, 281, 305, 332, 386, 397, 532, 546, 590, 591, 680,\n    696, 843, 901, 911, 974, 1026, 1032, 1053, 1061, 1116, 1117, 1159, 1164, 1491, 1552, 1802, 1941, 2069, 2087,\n    2122, 2129, 2157, 2168, 2232, 2296, 2314, 2318, 2446, 2567, 2592, 2737, 2852, 3040, 3073, 3093, 3103, 3156,\n    3181, 3389, 3627, 3632, 3642, 3858, 3873, 4020, 74, 75, 145, 163, 168, 208, 209, 266, 270, 321, 384, 395,\n    452, 487, 502, 664, 770, 790, 905, 1025, 1054, 1148, 1149, 1152, 1157, 1301, 1360, 1424, 1578, 1686, 1821,\n    1851, 1929, 1939, 2012, 2208, 2287, 2328, 2333, 2341, 2346, 2381, 2397, 2444, 2464, 2475, 2723, 2752, 3084,\n    3120, 3195, 3337, 3361, 3370, 3417, 3514, 3579, 3604, 3615, 3780, 3878, 3984, 4019, 23, 29, 46, 92, 93, 161,\n    240, 241, 278, 348, 416, 450, 562, 568, 610, 611, 650, 769, 773, 789, 794, 1154, 1222, 1223, 1328, 1376,\n    1494, 1520, 1563, 1597, 1685, 1951, 2010, 2075, 2123, 2169, 2284, 2297, 2473, 2511, 2541, 2602, 2619, 2666,\n    2720, 3107, 3123, 3157, 3294, 3324, 3372, 3448, 3453, 3546, 3587, 3670, 3710, 27, 196, 197, 290, 294, 418,\n    482, 519, 537, 540, 574, 688, 787, 793, 969, 1062, 1142, 1143, 1435, 1548, 1588, 1662, 1663, 1801, 1810,\n    1992, 2055, 2096, 2101, 2106, 2337, 2351, 2469, 2539, 2542, 2560, 2571, 2577, 2634, 2753, 2851, 2919, 2983,\n    3013, 3043, 3104, 3259, 3262, 3322, 3377, 3380, 3441, 3625, 3717, 3767, 3781, 3880, 3900, 3939, 4025, 4049,\n    137, 174, 280, 304, 322, 326, 344, 352, 393, 396, 455, 480, 485, 529, 553, 556, 760, 761, 832, 900, 910, 964,\n    970, 1005, 1031, 1124, 1125, 1180, 1289, 1317, 1333, 1540, 1557, 1574, 1580, 1586, 1690, 1724, 1921, 1940,\n    2052, 2064, 2099, 2108, 2250, 2266, 2285, 2304, 2365, 2467, 2578, 2583, 2609, 2656, 2667, 2848, 2977, 2996,\n    3126, 3166, 3176, 3215, 3239, 3257, 3295, 3325, 3512, 3602, 3617, 3630, 3671, 3700, 3711, 3761, 3764, 3842,\n    3883, 3903, 4080, 39, 45, 57, 148, 172, 184, 287, 297, 336, 394, 448, 525, 544, 571, 588, 589, 649, 746, 747,\n    835, 841, 845, 904, 996, 1006, 1038, 1074, 1080, 1092, 1093, 1178, 1220, 1221, 1300, 1489, 1492, 1538, 1595,\n    1670, 1682, 1756, 1757, 1795, 1815, 1820, 1835, 1850, 1928, 1938, 2050, 2111, 2179, 2356, 2440, 2509, 2565,\n    2587, 2635, 2871, 2915, 2978, 2984, 2989, 3046, 3053, 3114, 3148, 3172, 3207, 3292, 3323, 3340, 3353, 3356,\n    3364, 3387, 3401, 3445, 3544, 3589, 3640, 3729, 3757, 3769, 3796, 3897, 3954, 3959, 251, 373, 581, 1057,\n    1107, 1137, 1299, 1630, 1882, 2214, 2308, 2315, 2319, 2597, 2853, 3167, 3260, 3482, 3590, 3862 };\n\nstatic int cbest_13[1023] = {\n    1, 2, 4109, 4, 6155, 8, 7176, 16, 3588, 4108, 32, 2054, 3, 1027, 1794, 6, 64, 4620, 6154, 5, 897, 4111, 6153,\n    7177, 12, 2310, 3077, 4105, 128, 9, 24, 513, 1155, 10, 1026, 4101, 7689, 18, 48, 256, 896, 3589, 4365, 6159,\n    7178, 17, 448, 4557, 4621, 4684, 5647, 36, 96, 224, 1025, 1792, 20, 112, 512, 769, 1538, 1795, 2052, 3076,\n    3590, 6147, 6283, 7180, 33, 56, 72, 192, 2055, 2342, 3584, 4125, 6411, 7945, 28, 34, 577, 1154, 6922, 7240,\n    14, 144, 384, 2050, 4110, 4493, 5134, 5903, 6152, 7168, 7304, 7, 40, 65, 1171, 3620, 4104, 4141, 5004, 288,\n    768, 1153, 2308, 2567, 3652, 4097, 4107, 4397, 4685, 6157, 6171, 6379, 7688, 13, 68, 1024, 1810, 2311, 3461,\n    4676, 7179, 66, 129, 576, 1826, 3079, 3844, 4100, 4173, 4622, 5646, 6347, 8073, 25, 80, 899, 905, 1031, 1091,\n    1798, 2182, 2306, 2338, 2502, 3073, 3596, 4364, 4616, 5390, 5645, 6158, 6187, 7050, 7192, 11, 26, 913, 1922,\n    4117, 4556, 4652, 5262, 6151, 6299, 6443, 6923, 7181, 19, 49, 130, 136, 257, 1169, 1283, 1536, 1793, 2062,\n    2326, 2566, 2823, 4103, 4237, 6145, 7182, 7232, 7272, 7691, 7944, 50, 449, 515, 901, 961, 1035, 1163, 1170,\n    1539, 1802, 2048, 2340, 2695, 3586, 3591, 3604, 3616, 4553, 4612, 4680, 4748, 5005, 6146, 6219, 6282, 7208,\n    7241, 7288, 22, 37, 52, 97, 160, 225, 585, 1152, 1251, 1808, 2374, 2631, 3141, 3585, 4124, 4549, 4876, 5135,\n    5902, 6281, 6410, 7169, 7172, 7305, 7320, 7560, 21, 38, 98, 113, 132, 226, 258, 452, 545, 898, 904, 1187,\n    2053, 2070, 2278, 2438, 3205, 3460, 3525, 3636, 3972, 4127, 4137, 4157, 4367, 4677, 5132, 5839, 5901, 6163,\n    6169, 6409, 6666, 7693, 57, 73, 100, 114, 193, 228, 272, 450, 456, 517, 912, 1029, 1043, 1159, 1219, 1796,\n    2343, 3078, 3333, 4113, 4121, 4361, 4381, 4393, 4492, 4559, 4623, 4686, 5518, 6275, 6920, 7721, 8137, 29, 35,\n    44, 74, 104, 146, 641, 1030, 1090, 1730, 1824, 2246, 2318, 2822, 3072, 3085, 3592, 3621, 3660, 3780, 4099,\n    4140, 4149, 4165, 4205, 4333, 4617, 4700, 5454, 5643, 6149, 6377, 7051, 7170, 7244, 7753, 7947, 15, 30, 42,\n    60, 76, 120, 145, 196, 240, 260, 292, 320, 385, 480, 514, 771, 900, 960, 1123, 1411, 1818, 1986, 2051, 2086,\n    2304, 2694, 2951, 3622, 3648, 3653, 4096, 4106, 4221, 4396, 4429, 4589, 4636, 4653, 4716, 5422, 5679, 6156,\n    6170, 6378, 6603, 7184, 7242, 7681, 8072, 41, 58, 194, 200, 521, 581, 584, 785, 865, 1059, 1139, 1168, 1282,\n    1347, 1811, 2058, 2198, 2350, 2565, 2630, 3644, 4301, 4357, 4485, 4732, 5391, 5644, 5711, 6191, 6203, 6287,\n    6297, 7193, 7296, 7306, 70, 88, 148, 208, 289, 544, 579, 801, 1034, 1089, 1099, 1162, 1315, 1570, 1827, 1830,\n    1890, 2309, 2324, 2336, 2358, 2759, 3075, 3093, 3845, 4133, 4143, 4172, 4613, 4648, 4668, 4681, 4749, 4812,\n    4996, 5263, 5278, 5388, 5639, 5775, 6167, 6175, 6185, 6315, 6346, 6383, 6415, 6427, 6447, 7194, 7949, 7961,\n    69, 84, 116, 152, 264, 290, 386, 392, 516, 593, 773, 833, 909, 993, 1033, 1161, 1175, 1250, 1542, 1602, 1799,\n    1800, 1920, 2118, 2498, 2727, 3140, 3597, 3654, 4253, 4389, 4399, 4495, 4672, 4780, 4877, 5260, 6186, 6195,\n    6211, 6251, 6345, 6441, 6794, 6926, 6938, 7018, 7183, 7233, 7256, 7273, 7624, 7690, 7977, 67, 82, 400, 529,\n    609, 640, 929, 1041, 1157, 1179, 1186, 1249, 1281, 1666, 1858, 1923, 2060, 2180, 2334, 2406, 2500, 2563,\n    2711, 3173, 3204, 3463, 3524, 3598, 3628, 3716, 4036, 4116, 4169, 4489, 4509, 4608, 4618, 4660, 4678, 4764,\n    5006, 5130, 5899, 5919, 6150, 6267, 6298, 6351, 6442, 6475, 6667, 6730, 6954, 7048, 7174, 7209, 7212, 7224,\n    7289, 7308, 7400, 7432, 81, 140, 176, 232, 296, 416, 625, 770, 915, 945, 1028, 1042, 1158, 1185, 1218, 1537,\n    1814, 2068, 2183, 2307, 2322, 2339, 2390, 2503, 2950, 3109, 3189, 3332, 3397, 3457, 3606, 3612, 3846, 4102,\n    4236, 4525, 4687, 5000, 5020, 5582, 5641, 5871, 5935, 6144, 6339, 6403, 6539, 6921, 6986, 7173, 7188, 7196,\n    7321, 7336, 7561, 7705, 7937, 8065, 8075, 27, 54, 108, 168, 216, 304, 388, 432, 520, 561, 580, 705, 777, 784,\n    864, 907, 1039, 1167, 1203, 1546, 1803, 1806, 2314, 2366, 2382, 2564, 2639, 2821, 3081, 3084, 3149, 3221,\n    3365, 3469, 3587, 3600, 3605, 3617, 3668, 3840, 4373, 4413, 4541, 4552, 4701, 5133, 5198, 5838, 5900, 6179,\n    6218, 6303, 6395, 6914, 7042, 7216, 7234, 7274, 7322, 7368, 7692, 8169, 131, 137, 164, 464, 578, 673, 800,\n    903, 1095, 1122, 1195, 1410, 1475, 1762, 1809, 1822, 1834, 2056, 2063, 2078, 2082, 2178, 2276, 2327, 2330,\n    2370, 2599, 2627, 2693, 2758, 3137, 3477, 3594, 3618, 3684, 3812, 4119, 4175, 4548, 4555, 4573, 4637, 4644,\n    4654, 4682, 4692, 4717, 5068, 5126, 5254, 5446, 5486, 5519, 5663, 5837, 5895, 6031, 6161, 6217, 6279, 6280,\n    6291, 6435, 7210, 7236, 7276, 7290, 7695, 7720, 7725, 7737, 7745, 8136, 51, 138, 280, 352, 549, 592, 657,\n    772, 832, 908, 917, 992, 1051, 1058, 1121, 1138, 1191, 1217, 1346, 1816, 1842, 2018, 2049, 2066, 2190, 2274,\n    2341, 2346, 2372, 2510, 2629, 2726, 3493, 3521, 3608, 3632, 3637, 3700, 3860, 3973, 4126, 4136, 4156, 4189,\n    4229, 4366, 4405, 4421, 4461, 4628, 4696, 4733, 5276, 5420, 5455, 5470, 5516, 5642, 5677, 6162, 6168, 6223,\n    6273, 6363, 6408, 7171, 7245, 7264, 7752, 7946, 8077, 8089, 23, 53, 134, 161, 336, 454, 496, 528, 608, 921,\n    928, 1057, 1088, 1098, 1107, 1137, 1173, 1314, 1379, 1540, 1554, 1728, 1797, 1930, 2102, 2196, 2316, 2348,\n    2375, 2436, 2562, 2710, 2819, 3074, 3092, 3509, 3638, 3876, 4112, 4120, 4360, 4380, 4392, 4445, 4545, 4558,\n    4565, 4605, 4614, 4649, 4669, 4740, 4813, 4997, 5386, 5406, 5418, 5423, 5452, 5675, 5678, 5695, 5703, 6274,\n    6285, 6371, 6664, 7054, 7066, 7185, 7200, 7243, 7246, 7280, 7324, 7680, 7685, 7729, 7881, 39, 78, 133, 156,\n    162, 227, 274, 312, 328, 453, 519, 569, 624, 914, 965, 1032, 1037, 1075, 1097, 1165, 1313, 1363, 1409, 1825,\n    1828, 1938, 2074, 2084, 2214, 2242, 2279, 2439, 2534, 2691, 3143, 3593, 3650, 3656, 3661, 3781, 4123, 4148,\n    4153, 4239, 4353, 4363, 4481, 4551, 4673, 4781, 5128, 5258, 5358, 5637, 5710, 5807, 5835, 5897, 5917, 6173,\n    6355, 6413, 6419, 6425, 6459, 6795, 6858, 6927, 7019, 7297, 7300, 7307, 7312, 7562, 7723, 7953 };\n\nstatic int cbest_14[1023] = {\n    1, 2, 8737, 4, 8, 13105, 16, 8736, 32, 4368, 3, 2184, 17, 1092, 15289, 34, 546, 5, 64, 68, 273, 8739, 8745,\n    6, 136, 13104, 272, 9, 8741, 128, 8873, 12, 6552, 13109, 10, 544, 13107, 256, 18, 24, 3276, 8753, 33, 1088,\n    4369, 15288, 16381, 2185, 8705, 13089, 13173, 20, 512, 1093, 2186, 4372, 8195, 8738, 8744, 13113, 35, 66,\n    547, 1094, 1638, 2188, 4370, 4376, 8752, 8865, 12832, 15291, 36, 48, 65, 69, 257, 2176, 6416, 8707, 10921, 7,\n    19, 25, 70, 137, 3208, 8740, 9008, 9829, 13088, 13169, 15281, 96, 132, 138, 277, 554, 1024, 1108, 1604, 6544,\n    8749, 8872, 50, 140, 275, 281, 562, 802, 819, 3272, 4504, 7644, 8747, 8801, 8805, 12833, 13073, 13108, 21,\n    100, 129, 192, 276, 401, 1636, 4352, 4436, 8743, 12563, 13106, 15257, 15293, 13, 38, 40, 72, 130, 200, 264,\n    274, 280, 514, 550, 818, 2216, 2252, 6553, 8193, 8875, 9009, 15259, 11, 14, 49, 384, 400, 545, 1124, 2048,\n    2218, 6554, 8761, 8877, 13075, 13111, 15273, 409, 1100, 1126, 14197, 15153, 15839, 42, 528, 768, 1109, 4097,\n    4432, 8937, 9144, 15016, 16377, 16380, 51, 76, 98, 102, 204, 408, 552, 563, 803, 1090, 2200, 2248, 3277,\n    8203, 8704, 10785, 13172, 144, 260, 548, 560, 800, 817, 1028, 1089, 1536, 3822, 4096, 4353, 8194, 8760,\n    13112, 13117, 13241, 15272, 16365, 28, 258, 265, 1606, 1634, 4380, 6556, 8864, 8869, 8993, 9136, 10913,\n    12561, 12836, 15290, 26, 80, 84, 196, 385, 513, 816, 1056, 2180, 2187, 2190, 3072, 3268, 3278, 4373, 4400,\n    4496, 6145, 7508, 8706, 8755, 8941, 9016, 9145, 9287, 10920, 12013, 12290, 13175, 15017, 22, 67, 152, 1095,\n    1639, 2189, 3212, 3292, 4371, 4374, 4377, 4568, 4572, 6418, 6536, 6584, 8225, 8929, 9825, 9828, 12849, 13093,\n    13141, 13168, 13233, 14129, 14747, 15280, 16373, 16383, 37, 529, 570, 769, 1104, 1600, 1632, 2177, 5460,\n    6144, 6417, 6586, 7636, 8466, 8713, 8748, 8867, 13091, 13097, 13171, 15837, 16313, 16364, 71, 134, 168, 285,\n    392, 520, 530, 770, 1026, 1058, 1096, 2056, 2178, 2201, 2284, 3209, 4233, 4360, 4402, 4508, 4914, 6424, 7640,\n    8709, 8721, 8746, 8757, 8800, 8804, 8992, 9121, 9137, 10923, 11809, 12837, 13072, 13115, 13137, 14193, 15566,\n    97, 133, 139, 142, 145, 261, 288, 305, 337, 555, 561, 785, 801, 1025, 1120, 1140, 1605, 1646, 2112, 2116,\n    2730, 3210, 3264, 3754, 4378, 6420, 6545, 8201, 8211, 8451, 8742, 8754, 8803, 8809, 8841, 8889, 9001, 9017,\n    9831, 10925, 12291, 12562, 12834, 12840, 15161, 15256, 15283, 15292, 29, 56, 141, 259, 268, 279, 284, 674,\n    1110, 1142, 1538, 1570, 1911, 2280, 2457, 3273, 3820, 4232, 4505, 6281, 6546, 7628, 7645, 8192, 8715, 8751,\n    8769, 8874, 9012, 9285, 9317, 12848, 12960, 13057, 13077, 13092, 14880, 15258, 15265, 15285, 15295, 27, 52,\n    74, 81, 85, 101, 160, 193, 304, 336, 516, 558, 784, 1060, 1348, 1365, 1540, 1637, 2052, 2254, 2286, 3140,\n    3200, 3274, 3818, 4225, 4437, 4506, 4560, 6480, 6528, 6548, 7440, 8190, 8199, 8467, 8723, 8807, 8876, 9010,\n    9129, 9837, 11945, 12307, 12547, 13074, 13090, 13096, 13110, 13143, 13655, 14745, 14777, 15241, 15353, 15357,\n    15831, 23, 39, 41, 44, 73, 131, 153, 194, 201, 278, 290, 307, 403, 515, 536, 551, 610, 614, 806, 823, 1116,\n    1122, 1228, 1612, 1910, 2050, 2217, 2220, 2253, 2456, 2696, 3224, 3240, 3293, 3720, 4105, 4438, 6280, 6537,\n    8331, 8720, 8756, 8773, 8879, 8881, 9120, 9761, 9845, 10849, 10853, 12288, 12593, 13157, 13297, 13617, 14196,\n    15008, 15152, 15243, 15567, 15835, 15838, 16245, 15, 58, 112, 170, 202, 283, 341, 405, 571, 682, 810, 1040,\n    1105, 1125, 1364, 1568, 1620, 1860, 1877, 2049, 2192, 2219, 2250, 3076, 4101, 4112, 4224, 4356, 4500, 5392,\n    6448, 6555, 7504, 7646, 8210, 8227, 8450, 8711, 8813, 8888, 8936, 9000, 10889, 12009, 12567, 12835, 12841,\n    12968, 13059, 13177, 13181, 13305, 13309, 14888, 15155, 16108, 16376, 104, 306, 386, 393, 402, 531, 568, 771,\n    822, 930, 1030, 1101, 1127, 1602, 1644, 2120, 2728, 3080, 3288, 3752, 3814, 4104, 4354, 4361, 4434, 4440,\n    4444, 6153, 7576, 8202, 8219, 8464, 8602, 8843, 8933, 9013, 9797, 10784, 10789, 10891, 11877, 12571, 12896,\n    12900, 12961, 13056, 13237, 14097, 14181, 14474, 14881, 15249, 15264, 15275, 16357, 16369, 43, 116, 148, 224,\n    266, 282, 289, 340, 388, 404, 411, 465, 522, 576, 580, 608, 672, 1072, 1102, 1141, 1220, 1876, 1909, 2208,\n    2222, 2240, 2244, 2282, 3136, 4100, 4433, 4468, 4564, 4912, 5456, 6273, 6400, 6484, 7444, 7620, 7632, 8323,\n    8722, 8765, 9011, 9128, 9799, 9965, 10787, 10989, 12306, 12546, 13079, 13116, 13139, 13240, 13245, 14199,\n    15033, 15240, 15251, 15261, 15564, 15869, 16317, 16361, 54, 57, 77, 99, 103, 162, 205, 232, 269, 518, 553,\n    556, 566, 787, 1062, 1091, 1111, 1143, 1542, 2060, 2210, 2232, 2249, 3788, 3816, 6152, 6450, 7629, 7783,\n    8182, 8197, 8209, 8241, 8449, 8481, 8587, 8763, 8868, 8880, 8939, 9146, 9295, 9844, 10912, 10917, 11471,\n    11813, 12005, 12289, 12560, 13153, 13156, 13301, 13653, 13685, 15009, 15018, 15157, 15242, 15562, 16379, 46,\n    53, 78, 208, 320, 410, 448, 464, 549, 612, 772, 804, 821, 938, 955, 1029, 1349, 1537, 1642, 1908, 2202, 2234,\n    3242, 3284, 3722, 3823, 4240, 4464, 6160, 6272, 6529, 6558, 6576, 7098, 7237, 8233, 8329, 8771, 8775, 8871,\n    8905, 8940, 9020, 9283, 9286, 10793, 12012, 12294, 12844, 12857, 12969, 13058, 13174, 13587, 14099, 14521,\n    14739, 14761, 14889, 15020, 15321, 15430, 15833, 16109, 16349, 82, 88, 146, 262, 291, 537, 578, 680, 776,\n    786, 807, 1032, 1074, 1117, 1160, 1574, 1607, 1635, 1654, 1894, 1907, 2124, 2182, 2204, 2440, 3084, 3225,\n    3308, 3810, 4099, 4120, 4301, 4381, 4488, 4643, 6147, 6557, 6578, 6616, 6620, 7782, 8188, 8207, 8218, 8224,\n    8465, 8603, 8833, 8928, 9249, 9824, 11873, 12305, 12337, 12545, 12565, 12577, 12853, 12897, 12901, 12977,\n    13119, 13140, 13232, 13243, 14128, 14133, 14180, 14475, 14746, 15121, 15217, 15263, 15274, 15771, 15829,\n    16177, 16356, 16372, 16382, 30, 59, 113, 197, 206, 469, 811, 820, 827, 896, 928, 954, 1041, 1057, 1136, 1216,\n    1344, 1361, 1572, 1621, 1861, 2080, 2144, 2181, 2193, 2321, 3073, 3204, 3214, 3269, 3279, 4113, 4357, 4384,\n    4404, 4420, 4497, 4922, 7236, 7492, 7509, 8579, 8600, 8712, 8729, 8866, 9138, 9319, 9827, 9833, 10915, 10985,\n    11267, 12595, 12631, 12838, 13101, 13170, 13382, 13619, 13651, 14131, 14472, 14504, 15032, 15325, 15565,\n    15827, 16125, 16312 };\n\nstatic int cbest_15[1023] = {\n    1, 2, 16385, 4, 8, 24577, 16, 32, 64, 28673, 128, 256, 512, 1024, 30721, 2048, 4096, 8192, 16384, 31745, 3,\n    6, 12, 24, 48, 96, 192, 384, 768, 1536, 3072, 6144, 12288, 24576, 5, 16387, 9, 16389, 10, 17, 16393, 24579,\n    28672, 18, 33, 14336, 16401, 24581, 20, 34, 65, 7168, 16417, 24585, 32257, 36, 66, 129, 3584, 16449, 24593,\n    28675, 40, 68, 130, 257, 1792, 16513, 24609, 28677, 30720, 72, 132, 258, 513, 896, 16641, 24641, 28681, 80,\n    136, 260, 448, 514, 1025, 15360, 16897, 24705, 28689, 144, 224, 264, 516, 1026, 2049, 17409, 24833, 28705,\n    30723, 112, 160, 272, 520, 1028, 2050, 4097, 7680, 18433, 25089, 28737, 30725, 56, 288, 528, 1032, 2052,\n    4098, 8193, 20481, 25601, 28801, 30729, 31744, 32513, 28, 320, 544, 1040, 2056, 3840, 4100, 8194, 26625,\n    28929, 30737, 14, 576, 1056, 2064, 4104, 8196, 16386, 29185, 30753, 7, 13, 25, 49, 97, 193, 385, 640, 769,\n    1088, 1537, 1920, 2080, 3073, 4112, 6145, 8200, 12289, 15872, 16388, 29697, 30785, 31747, 26, 50, 98, 194,\n    386, 770, 1152, 1538, 2112, 3074, 4128, 6146, 8208, 12290, 16391, 16392, 16397, 16409, 16433, 16481, 16577,\n    16769, 17153, 17921, 19457, 22529, 24578, 30849, 31749, 52, 100, 196, 388, 772, 960, 1280, 1540, 2176, 3076,\n    4160, 6148, 8224, 12292, 16400, 24580, 30977, 31753, 11, 104, 200, 392, 776, 1544, 2304, 3080, 4224, 6152,\n    7936, 8256, 12296, 16416, 24583, 24584, 24589, 24601, 24625, 24673, 24769, 24961, 25345, 26113, 27649, 31233,\n    31761, 32256, 19, 22, 208, 400, 480, 784, 1552, 2560, 3088, 4352, 6160, 8320, 12304, 14337, 16395, 16448,\n    24592, 28674, 31777, 21, 35, 38, 44, 416, 800, 1568, 3104, 4608, 6176, 7169, 8448, 12320, 14338, 16403,\n    16512, 24608, 28676, 31809, 32641, 37, 67, 70, 76, 88, 240, 832, 1600, 3136, 3585, 3968, 5120, 6208, 7170,\n    8704, 12352, 14340, 16405, 16419, 16640, 23553, 24587, 24640, 28679, 28680, 28685, 28697, 28721, 28769,\n    28865, 29057, 29441, 30209, 31873, 32259, 41, 69, 131, 134, 140, 152, 176, 1664, 1793, 3200, 3586, 6272,\n    7172, 9216, 12416, 14344, 16128, 16421, 16451, 16896, 19969, 24595, 24704, 28688, 32001, 32261, 42, 73, 120,\n    133, 259, 262, 268, 280, 304, 352, 897, 1794, 3328, 3588, 6400, 7176, 10240, 12544, 14352, 16425, 16453,\n    16515, 17408, 18177, 24597, 24611, 24832, 28704, 30722, 32265, 74, 81, 137, 261, 449, 515, 518, 524, 536,\n    560, 608, 704, 898, 1796, 1984, 3592, 6656, 7184, 12800, 14368, 15361, 16457, 16517, 16643, 17281, 18432,\n    24613, 24643, 25088, 28161, 28683, 28736, 30724, 32273, 60, 82, 138, 145, 225, 265, 450, 517, 900, 1027,\n    1030, 1036, 1048, 1072, 1120, 1216, 1408, 1800, 3600, 7200, 13312, 14400, 15362, 16465, 16521, 16645, 16833,\n    16899, 20480, 24617, 24645, 24707, 25600, 26369, 28691, 28800, 30727, 30728, 30733, 30745, 30769, 30817,\n    30913, 31105, 31489, 32289, 32512, 84, 113, 146, 161, 226, 266, 273, 452, 521, 904, 1029, 1808, 2051, 2054,\n    2060, 2072, 2096, 2144, 2240, 2432, 2816, 3616, 7232, 7681, 8064, 14464, 15364, 16529, 16609, 16649, 16901,\n    17411, 24649, 24709, 24835, 25473, 26624, 28693, 28707, 28928, 30736, 32321, 30, 57, 114, 148, 162, 228, 274,\n    289, 456, 522, 529, 912, 992, 1033, 1824, 2053, 3648, 4099, 4102, 4108, 4120, 4144, 4192, 4288, 4480, 4864,\n    5632, 7296, 7682, 14592, 15368, 16497, 16545, 16657, 16905, 17413, 18435, 24065, 24657, 24713, 24837, 25025,\n    25091, 28709, 28739, 29184, 30752, 32385, 29, 58, 116, 164, 232, 276, 290, 321, 464, 530, 545, 928, 1034,\n    1041, 1856, 2057, 3712, 3841, 4101, 7424, 7684, 8195, 8198, 8204, 8216, 8240, 8288, 8384, 8576, 8960, 9728,\n    11264, 14848, 15376, 16441, 16673, 16913, 17417, 18437, 20483, 24721, 24801, 24841, 25093, 25603, 28713,\n    28741, 28803, 29696, 30465, 30731, 30784, 31746, 32515, 15, 168, 292, 322, 532, 546, 577, 1042, 1057, 2058,\n    2065, 3842, 4105, 7688, 8197, 15392, 16390, 16396, 16408, 16413, 16432, 16480, 16576, 16705, 16768, 16929,\n    17152, 17425, 17920, 18441, 19456, 20225, 20485, 22528, 24689, 24737, 24849, 25097, 25605, 26627, 28745,\n    28805, 28931, 29569, 30739, 30848, 31748, 32517, 32705, 296, 324, 496, 548, 578, 641, 1044, 1058, 1089, 1921,\n    2066, 2081, 3844, 4032, 4106, 4113, 7696, 8201, 15424, 15873, 16256, 16399, 16961, 17441, 18449, 20489,\n    24633, 24865, 25105, 25609, 26629, 28753, 28809, 28933, 29121, 29187, 30741, 30755, 30976, 31751, 31752,\n    31757, 31769, 31793, 31841, 31937, 32129, 32521, 27, 51, 54, 99, 102, 108, 195, 198, 204, 216, 328, 387, 390,\n    396, 408, 432, 552, 580, 642, 771, 774, 780, 792, 816, 864, 1060, 1090, 1153, 1539, 1542, 1548, 1560, 1584,\n    1632, 1728, 1922, 2068, 2082, 2113, 3075, 3078, 3084, 3096, 3120, 3168, 3264, 3456, 3848, 4114, 4129, 6147,\n    6150, 6156, 6168, 6192, 6240, 6336, 6528, 6912, 7712, 8202, 8209, 12291, 12294, 12300, 12312, 12336, 12384,\n    12480, 12672, 13056, 13824, 15488, 15874, 17025, 17473, 18305, 18465, 20497, 24582, 24588, 24600, 24605,\n    24624, 24672, 24768, 24897, 24960, 25121, 25344, 25617, 26112, 26633, 27648, 28417, 28817, 28897, 28937,\n    29189, 29699, 30757, 30787, 31232, 31760, 32529, 53, 101, 197, 336, 389, 584, 644, 773, 961, 1064, 1092,\n    1154, 1281, 1541, 1924, 2084, 2114, 2177, 3077, 3856, 4116, 4130, 4161, 6149, 7744, 8210, 8225, 12293, 15616,\n    15876, 16394, 16411, 16435, 16483, 16579, 16771, 17155, 17537, 17923, 18497, 19459, 20513, 22531, 24591,\n    25153, 25633, 26641, 28785, 28833, 28945, 29193, 29701, 30761, 30789, 30851, 31776, 32545, 105, 201, 248,\n    393, 592, 648, 777, 962, 1096, 1156, 1282, 1545, 1928, 2088, 2116, 2178, 2305, 3081, 3872, 4132, 4162, 4225,\n    6153, 7808, 7937, 8212, 8226, 8257, 12297, 15880, 16402, 16437, 16485, 16581, 16773, 17157, 17345, 17665,\n    17925, 18561, 19461, 20545, 22533, 25217, 25665, 26497, 26657, 28729, 28961, 29201, 29705, 30793, 30853,\n    30979, 31617, 31755, 31808, 32577, 32640, 23, 46, 92, 106, 184, 202, 209, 368, 394, 401, 481, 656, 736, 778,\n    785, 964, 1104, 1160, 1284, 1472, 1546, 1553, 1936, 2016, 2120, 2180, 2306, 2561, 2944, 3082, 3089, 3904,\n    4136, 4164, 4226, 4353, 5888, 6154, 6161, 7938, 8228, 8258, 8321, 11776, 12298, 12305, 15888, 16404, 16418,\n    16489, 16585, 16777, 17161, 17929, 18689, 19465, 20609, 22537, 23552, 24321, 24586, 24603, 24627, 24675,\n    24771, 24963, 25347, 25729, 26115, 26689, 27651, 28678, 28684, 28696, 28701, 28720, 28768, 28864, 28993,\n    29056, 29217, 29440, 29713, 30208, 30801, 30857, 30981, 31169, 31235, 31763, 31872, 32258, 45, 78, 156, 312,\n    624, 672, 801, 1554, 2562, 14384, 16593, 16785, 17937, 24677, 31237, 31765 };\n\nstatic int cbest_16[1023] = {\n    1, 2, 34821, 4, 8, 52231, 16, 60934, 34820, 32, 17410, 3, 8705, 17, 34, 5, 68, 34823, 34829, 6, 136, 30467,\n    34817, 39173, 52230, 64, 272, 52229, 9, 544, 52227, 10, 1088, 12, 2176, 26115, 4352, 50311, 60935, 18, 8704,\n    45956, 60932, 20, 128, 24, 34837, 47876, 17408, 30466, 33, 36, 17411, 52239, 59974, 40, 22978, 48, 256, 8707,\n    17414, 34822, 34828, 43524, 60930, 65286, 19, 35, 8709, 17418, 23938, 26114, 34816, 34836, 34853, 39172,\n    52247, 21, 69, 15233, 21762, 30465, 34825, 52225, 52228, 7, 25, 38, 72, 137, 13057, 19586, 34831, 34881,\n    52226, 42, 65, 80, 273, 10881, 26113, 29987, 34819, 34855, 39175, 50, 66, 70, 76, 545, 9793, 11489, 45957,\n    60933, 60942, 11, 14, 84, 96, 138, 1089, 11969, 34957, 37253, 50310, 52261, 13, 152, 274, 1090, 2177, 34885,\n    34949, 35093, 39169, 50309, 52246, 52263, 49, 144, 257, 546, 4353, 8713, 32643, 32901, 34845, 35365, 38341,\n    47877, 60950, 22, 28, 140, 512, 2180, 4354, 25155, 30471, 35909, 40261, 45716, 52291, 52295, 100, 129, 160,\n    276, 2178, 8706, 17426, 26123, 39717, 49351, 52235, 59975, 44, 98, 130, 168, 548, 4360, 8708, 8721, 34833,\n    51271, 52237, 60928, 81, 132, 192, 304, 514, 1092, 15232, 30464, 30475, 34839, 34861, 39181, 43525, 45958,\n    47872, 50307, 52238, 52367, 53703, 60931, 60951, 60966, 65287, 51, 88, 102, 204, 280, 408, 816, 1632, 3264,\n    6528, 8720, 13056, 17409, 17412, 26119, 45952, 47396, 52243, 52503, 59972, 60964, 26, 37, 85, 170, 196, 288,\n    340, 552, 680, 1360, 2720, 4356, 5440, 10880, 17416, 22858, 22976, 23936, 26112, 29986, 34844, 40565, 40805,\n    52245, 52775, 54663, 41, 56, 153, 162, 200, 306, 612, 1096, 1224, 2448, 4896, 7616, 8737, 9792, 11488, 17422,\n    17440, 21760, 22979, 30483, 34849, 34852, 39189, 47044, 47878, 49895, 50855, 59494, 60454, 65284, 145, 320,\n    336, 1028, 2184, 8711, 11968, 17415, 17442, 34824, 34889, 34893, 43520, 50583, 52224, 60940, 60994, 23, 29,\n    258, 392, 560, 608, 17419, 23698, 23939, 30482, 34827, 34830, 34832, 34863, 34880, 43526, 50319, 52259,\n    56583, 60943, 64806, 46, 58, 74, 176, 260, 324, 384, 1024, 1104, 3808, 8225, 8712, 14993, 17427, 17474,\n    19584, 21763, 32642, 34818, 34838, 34854, 39174, 45964, 52257, 57574, 59970, 39, 73, 82, 92, 264, 290, 2192,\n    5744, 5984, 8769, 11429, 19587, 25154, 26131, 29985, 30227, 30470, 34869, 35077, 38933, 39205, 41604, 43532,\n    52242, 52359, 58054, 60948, 61070, 63366, 65294, 43, 52, 112, 116, 400, 2056, 4368, 8739, 12577, 16450,\n    17478, 20802, 21766, 26122, 29747, 30497, 34841, 34851, 34956, 37252, 42212, 50327, 52244, 52255, 52260,\n    60998, 61206, 65282, 67, 71, 77, 89, 178, 184, 576, 672, 1904, 8773, 10401, 10883, 11425, 11849, 13061,\n    15235, 15241, 17546, 18626, 22850, 22982, 26121, 26130, 30499, 34884, 34948, 34965, 35092, 39168, 39188,\n    42692, 45700, 45717, 45972, 47884, 50191, 50308, 52233, 52262, 59014, 60938, 15, 27, 78, 97, 139, 148, 164,\n    289, 352, 356, 1120, 1216, 2992, 8717, 8841, 9313, 13059, 13065, 17538, 17682, 19594, 23522, 30469, 30474,\n    32900, 34857, 34883, 35364, 37013, 37249, 38340, 39171, 44612, 45836, 52293, 59906, 60246, 57, 86, 232, 275,\n    640, 784, 1091, 2872, 8715, 8977, 9797, 17424, 17434, 17954, 23942, 26118, 26147, 29953, 32641, 34897, 34901,\n    34945, 34981, 35908, 37255, 38221, 39233, 40260, 49349, 51127, 52290, 52294, 59766, 59982, 60110, 60929,\n    60949, 154, 224, 321, 368, 547, 648, 712, 952, 4112, 4384, 9795, 11491, 15237, 16321, 17430, 22986, 25153,\n    26117, 26145, 28787, 29027, 29955, 32403, 32897, 34871, 34887, 34951, 38337, 39207, 39716, 40533, 45596,\n    45959, 47873, 47892, 49350, 51007, 51269, 52234, 52241, 52271, 52289, 52303, 60946, 60967, 104, 118, 141,\n    156, 259, 328, 393, 513, 516, 561, 580, 704, 768, 800, 1496, 2181, 2208, 4355, 8725, 8736, 11393, 11457,\n    11971, 19590, 21106, 21346, 22306, 22918, 29507, 30473, 30535, 34868, 34917, 34959, 35009, 35013, 35076,\n    40021, 45701, 45953, 45973, 47397, 49231, 50305, 50341, 51270, 52236, 52269, 52279, 52311, 52487, 59973,\n    60965, 30, 59, 101, 142, 161, 177, 186, 261, 277, 325, 385, 464, 520, 578, 786, 1105, 1344, 1424, 2179, 2182,\n    4362, 8710, 8723, 10885, 17420, 19170, 20130, 23946, 24675, 25635, 29991, 30603, 31683, 32647, 34835, 34840,\n    34860, 34909, 34953, 34973, 35095, 35101, 35911, 39180, 39309, 39477, 40517, 40549, 40737, 50306, 50326,\n    50343, 52254, 52277, 52366, 53702, 59910, 59990, 60962, 61062, 45, 54, 83, 93, 99, 114, 131, 134, 146, 169,\n    172, 194, 236, 265, 278, 291, 358, 476, 528, 549, 650, 736, 1094, 1122, 1436, 1572, 2048, 2193, 2240, 2432,\n    2856, 4361, 8724, 8741, 11493, 17450, 17482, 19858, 21770, 22786, 22914, 22994, 30531, 32903, 32909, 34847,\n    34913, 34964, 35089, 35367, 35373, 38343, 39183, 39237, 39719, 40535, 45988, 47045, 47879, 49827, 51267,\n    52327, 52365, 52502, 53583, 53701, 54661, 59426, 59495, 60455, 60958, 65285, 53, 113, 117, 133, 193, 234,\n    305, 308, 372, 448, 468, 515, 550, 582, 642, 716, 748, 936, 1093, 1152, 1300, 1408, 1428, 2210, 2848, 3144,\n    4369, 8224, 8722, 11153, 11459, 11841, 11973, 14992, 17446, 17448, 22798, 25163, 26127, 30481, 34877, 34983,\n    35333, 35361, 35917, 39177, 39685, 40263, 40564, 40804, 43521, 43540, 45236, 45476, 45580, 45718, 47636,\n    47893, 49893, 50183, 50371, 50567, 50991, 52251, 52299, 52325, 52363, 52501, 52774, 53575, 54662, 60452,\n    60941, 60947, 60995, 65302, 103, 166, 179, 185, 205, 281, 296, 312, 354, 370, 409, 472, 714, 740, 817, 928,\n    1164, 1432, 1480, 1568, 1633, 1872, 2244, 2600, 3265, 4358, 4364, 4386, 5712, 6288, 6529, 8768, 8833, 9729,\n    10065, 10673, 10889, 11397, 11428, 11761, 17413, 17444, 22790, 22856, 23682, 23954, 26183, 29713, 29984,\n    29995, 30123, 30226, 34848, 34859, 34896, 34900, 35905, 40257, 43527, 45116, 45572, 45712, 45828, 46016,\n    47156, 47392, 47874, 49894, 50854, 50983, 52307, 52499, 52773, 60960, 60974, 60992, 64807, 90, 149, 171, 197,\n    208, 238, 282, 341, 353, 357, 518, 553, 681, 744, 770, 1098, 1156, 1280, 1296, 1361, 1472, 1634, 2188, 2328,\n    2721, 2864, 3266, 3744, 4357, 4420, 5200, 5441, 6530, 7496, 8729, 8738, 9585, 9801, 9929, 10553, 11395,\n    11497, 12576, 14977, 15249, 17417, 21778, 22794, 22859, 22977, 23696, 23818, 23937, 26129, 26251, 29715,\n    29746, 30055, 30479, 30480, 30496, 33989, 34870, 34888, 34892, 34905, 34915, 34919, 34989, 35017, 38213,\n    39191, 39713, 40741, 45954, 45965, 45990, 46924, 47908, 49347, 50317, 50582, 50735, 50853, 52240, 52267,\n    52771, 54423, 56581, 57506, 57510, 57575, 59492, 59971, 59991, 60102, 60936, 60982, 198, 374, 1097, 1225,\n    7488, 15113, 17441, 17472, 26387, 29883, 30211, 34879, 35205, 50463, 50999, 52258, 61071 };\n\n\nstatic int cbest_17[1023] = {\n    1, 2, 65540, 4, 32770, 8, 16385, 16, 32, 73732, 64, 128, 36866, 256, 512, 18433, 1024, 2048, 4096, 74756,\n    8192, 16384, 37378, 32768, 3, 18689, 65536, 65541, 5, 32771, 65542, 6, 9, 18, 36, 72, 144, 288, 576, 1152,\n    2304, 4608, 9216, 18432, 98310, 10, 17, 16387, 32774, 36864, 65548, 81925, 12, 33, 16389, 32778, 49155,\n    65556, 73728, 73733, 74884, 20, 34, 65, 16393, 32786, 65572, 73734, 24, 66, 129, 16401, 32802, 36867, 65604,\n    106502, 40, 68, 130, 257, 16417, 32834, 65668, 73740, 90117, 102406, 48, 132, 258, 513, 16449, 32898, 36870,\n    37442, 65796, 73748, 80, 136, 260, 514, 1025, 16513, 18435, 33026, 36874, 53251, 66052, 73764, 83973, 96,\n    264, 516, 1026, 2049, 16641, 18437, 33282, 36882, 51203, 66564, 73796, 160, 272, 520, 1028, 2050, 4097,\n    16897, 18441, 18688, 33794, 36898, 37376, 67588, 73860, 74752, 74757, 110598, 192, 528, 1032, 2052, 4098,\n    8193, 9344, 17409, 18449, 18721, 34818, 36930, 69636, 73988, 74758, 320, 544, 1040, 2056, 4100, 4672, 8194,\n    18465, 36994, 74244, 92165, 107526, 384, 1056, 2064, 2336, 4104, 8196, 16386, 18497, 20481, 37122, 37379,\n    40962, 74764, 81924, 91141, 640, 1088, 1168, 2080, 4112, 8200, 16388, 18561, 24577, 32769, 49154, 55299,\n    74772, 75780, 102918, 584, 768, 2112, 4128, 8208, 16392, 37382, 37890, 74788, 77828, 98308, 292, 1280, 2176,\n    4160, 8224, 16400, 18945, 32772, 32784, 37386, 38914, 53763, 65537, 74820, 74900, 146, 1536, 4224, 8256,\n    16416, 18691, 19457, 32776, 37394, 49153, 65538, 65543, 65568, 84229, 90116, 111622, 7, 19, 37, 73, 145, 289,\n    577, 1153, 2305, 2560, 4352, 4609, 8320, 9217, 16448, 18693, 37410, 45058, 51459, 75012, 98306, 98311,\n    111110, 11, 38, 74, 290, 578, 1154, 2306, 3072, 4610, 8448, 9218, 16512, 18434, 18697, 22529, 32775, 32800,\n    36865, 53250, 65544, 65549, 65558, 65612, 65684, 65828, 66116, 66692, 67844, 70148, 75268, 81921, 83972,\n    93189, 106500, 13, 22, 76, 148, 580, 1156, 2308, 4612, 5120, 8704, 9220, 16640, 18436, 18705, 26625, 32779,\n    32806, 32832, 32842, 32914, 33058, 33346, 33922, 35074, 37506, 41986, 51202, 65550, 65552, 65557, 73729,\n    74880, 74885, 81927, 102404, 14, 21, 26, 35, 44, 152, 296, 1160, 2312, 4616, 6144, 9224, 16391, 16403, 16421,\n    16457, 16529, 16673, 16896, 16961, 17537, 18440, 20993, 25601, 32787, 32896, 36868, 36880, 37440, 37450,\n    37634, 65573, 73730, 73735, 73760, 74886, 76804, 92421, 98318, 114695, 25, 52, 67, 88, 304, 592, 2320, 4624,\n    9232, 10240, 16395, 17408, 18448, 18720, 18753, 32782, 32803, 33024, 36872, 49159, 53249, 55811, 65574,\n    65600, 65605, 78852, 81933, 98326, 106498, 106503, 107654, 41, 50, 69, 104, 131, 176, 608, 1184, 4640, 9248,\n    12288, 16397, 18464, 18817, 32790, 32835, 33280, 38402, 49163, 51201, 55555, 65564, 65606, 65664, 65669,\n    73736, 73741, 73750, 73804, 73876, 74020, 74308, 74892, 76036, 78340, 81941, 90113, 91269, 92164, 98342,\n    102402, 102407, 28, 42, 49, 70, 82, 100, 133, 208, 259, 352, 1216, 2368, 9280, 9360, 16405, 16419, 18496,\n    20480, 32794, 32899, 33792, 36871, 36896, 37443, 39426, 49171, 65580, 65670, 65792, 65797, 73742, 73744,\n    73749, 81957, 90119, 91140, 98374, 110596, 81, 134, 137, 164, 200, 261, 416, 515, 704, 2432, 4736, 16409,\n    16451, 18560, 19201, 24576, 32810, 32838, 33027, 34816, 36875, 36902, 36928, 36938, 37010, 37154, 38018,\n    39170, 46082, 49187, 55298, 65588, 65798, 66048, 66053, 73765, 74916, 81989, 83969, 98438, 102982, 106510,\n    112134, 122887, 97, 138, 262, 265, 274, 328, 400, 517, 832, 1027, 1408, 4680, 4864, 9472, 16425, 16453,\n    16515, 18725, 19713, 32818, 32902, 33283, 36883, 36992, 37446, 45570, 49219, 65620, 65676, 66054, 66560,\n    66565, 73766, 73792, 73797, 74948, 82053, 83975, 90125, 98566, 102414, 106518, 107524, 118791, 56, 84, 98,\n    140, 161, 266, 273, 518, 521, 530, 548, 656, 800, 1029, 1664, 2051, 2816, 9728, 16433, 16517, 16643, 18439,\n    18451, 18469, 18505, 18577, 18944, 19009, 19585, 23041, 27649, 32850, 32906, 33030, 33795, 36878, 36899,\n    37120, 37377, 40960, 49283, 53255, 53762, 53827, 65636, 65804, 66566, 67584, 67589, 73756, 73798, 73856,\n    73861, 74753, 82181, 90133, 98822, 102422, 106534, 110594, 110599, 111750, 116743, 162, 193, 268, 522, 529,\n    1030, 1033, 1042, 1060, 1096, 1312, 1600, 2053, 2340, 3328, 4099, 5632, 9345, 16465, 16521, 16645, 16899,\n    18443, 18690, 19456, 22785, 32866, 33034, 33286, 34819, 36886, 36931, 37458, 49152, 49411, 51207, 53259,\n    55297, 65700, 65812, 66060, 67590, 69632, 69637, 73772, 73862, 73984, 73989, 74754, 74759, 74784, 75140,\n    82437, 83981, 84228, 90149, 93445, 99334, 102438, 102916, 106566, 194, 276, 321, 524, 545, 1034, 1041, 2054,\n    2057, 2066, 2084, 2120, 2192, 2624, 3200, 4101, 4673, 6656, 8195, 9346, 11264, 16481, 16649, 16901, 17411,\n    18445, 18692, 18723, 26881, 32930, 33042, 33290, 33798, 36890, 36995, 37380, 37392, 37474, 37888, 49667,\n    51211, 51458, 53267, 65732, 66068, 66572, 69638, 73780, 73990, 74240, 74245, 75396, 82949, 83989, 84261,\n    90181, 92161, 93317, 100358, 102470, 106630, 107522, 107527, 111174, 112, 168, 196, 280, 322, 385, 532, 546,\n    1036, 1057, 1170, 2058, 2065, 2337, 4102, 4105, 4114, 4132, 4168, 4240, 4384, 4674, 5248, 6400, 8197, 9348,\n    13312, 16545, 16657, 16905, 17413, 18453, 18467, 18696, 22528, 32962, 33298, 33802, 34822, 36906, 36934,\n    37123, 37384, 38912, 40963, 42114, 50179, 51219, 51491, 53283, 53761, 65860, 66084, 66580, 67596, 70212,\n    73812, 73868, 74246, 74760, 74765, 74774, 74828, 75044, 75332, 77060, 79364, 81920, 84005, 90245, 91137,\n    92167, 93188, 102534, 106758, 110606, 126983, 324, 386, 536, 641, 1044, 1058, 1089, 1169, 2060, 2081, 2338,\n    4106, 4113, 4676, 8198, 8201, 8210, 8228, 8264, 8336, 8480, 8768, 9352, 10496, 12800, 16577, 16913, 17417,\n    18457, 18499, 18704, 18729, 20483, 25729, 26624, 33090, 33314, 33810, 34826, 36914, 36998, 37570, 51235,\n    53315, 56067, 65924, 66596, 67604, 67876, 69644, 73828, 73996, 74766, 74768, 74773, 75776, 75781, 76932,\n    81926, 84037, 86021, 90373, 91143, 102662, 102914, 102919, 107014, 110614, 111620, 124935, 388, 552, 585,\n    642, 769, 1048, 1090, 2068, 2082, 2113, 4108, 4129, 8202, 8209, 16390, 16402, 16420, 16456, 16528, 16672,\n    16705, 16929, 16960, 17425, 17536, 18473, 18501, 18563, 18737, 20485, 20992, 21057, 24579, 25600, 32912,\n    33154, 33826, 34834, 35106, 36946, 37002, 37126, 37383, 37408, 37698, 37891, 40966, 45056, 51267, 51457,\n    53379, 65824, 66180, 66628, 66708, 67620, 69652, 73892, 74004, 74252, 74789, 74902, 75782, 77824, 77829,\n    78980, 84101, 90629, 92173, 92420, 98304, 98309, 107534, 110630, 111108, 114694, 120839, 123911, 392, 644,\n    770, 1281, 2072, 2114, 2177, 4116, 4130, 4161, 8204, 8225, 16769, 17441, 18565, 24581, 33410, 33938, 34850,\n    36962, 37130, 38530, 38915, 40970, 46594, 49158, 53248, 55303, 55810, 69668, 73924, 74260, 74790, 74821,\n    74896, 74901, 77830, 91149, 92181, 92453, 107542, 110662 };\n\nstatic int cbest_18[1023] = {\n    1, 2, 131136, 4, 65568, 8, 32784, 16, 16392, 32, 8196, 64, 4098, 128, 2049, 256, 512, 132160, 1024, 2048,\n    66080, 4096, 33040, 8192, 16520, 8260, 16384, 3, 4130, 32768, 131137, 5, 65569, 131138, 6, 9, 2065, 32785,\n    65536, 65570, 131140, 196704, 10, 17, 16393, 32786, 65572, 131144, 163920, 12, 18, 33, 8197, 16394, 32788,\n    65576, 98352, 131072, 131152, 147528, 20, 34, 65, 4099, 8198, 16396, 32792, 65584, 81960, 131168, 132168,\n    139332, 24, 36, 66, 129, 258, 516, 1032, 2064, 49176, 73764, 135234, 40, 68, 130, 257, 2051, 4102, 4128,\n    8204, 16408, 32816, 40980, 65632, 69666, 131264, 133185, 48, 72, 132, 513, 2053, 4106, 8212, 8256, 16424,\n    24588, 32848, 36882, 65696, 66084, 67617, 131392, 132161, 80, 136, 260, 514, 1025, 2057, 4114, 8228, 16456,\n    16512, 20490, 32912, 34833, 65824, 131648, 132162, 96, 144, 264, 1026, 12294, 18441, 33024, 66081, 132164,\n    197728, 160, 272, 520, 1028, 2050, 2081, 4162, 8324, 10245, 16648, 33042, 33296, 66048, 66082, 66592, 133184,\n    164944, 197216, 192, 288, 528, 2052, 2113, 4097, 4226, 6147, 8452, 16904, 33041, 33808, 67616, 132096,\n    132176, 148552, 320, 544, 1040, 2056, 2177, 4354, 8708, 17416, 34832, 66088, 98864, 132192, 135232, 140356,\n    164176, 384, 576, 1056, 2305, 4100, 4610, 8193, 9220, 16521, 18440, 33044, 66096, 69664, 82472, 98608,\n    136258, 640, 1088, 2080, 2561, 4104, 5122, 8194, 10244, 16522, 33048, 36880, 74276, 132288, 134209, 139268,\n    139328, 147656, 768, 1152, 2112, 3073, 4112, 6146, 8261, 16385, 16524, 20488, 49432, 66144, 69634, 70178,\n    73760, 82088, 132416, 1280, 2176, 8200, 8262, 12292, 16386, 33072, 34817, 40976, 41236, 49304, 66208, 68129,\n    132672, 147520, 1536, 2304, 4131, 4160, 4352, 8208, 16388, 16536, 24584, 32769, 33104, 37138, 66336, 73828,\n    81952, 2560, 4224, 6145, 8224, 8268, 8704, 16552, 24716, 32770, 33168, 35089, 41044, 49168, 131139, 134208,\n    135266, 148544, 163904, 198240, 7, 3072, 4134, 8276, 12290, 16400, 16584, 17408, 20618, 24652, 32772, 65537,\n    65571, 67104, 98336, 131141, 196705, 11, 2067, 4138, 4608, 8292, 8320, 10241, 16416, 18569, 24580, 32776,\n    32787, 33552, 34816, 36914, 65538, 65573, 68128, 74272, 131142, 131145, 133201, 136256, 163921, 165200,\n    196672, 196706, 13, 19, 2069, 4146, 5120, 8448, 12358, 16395, 16448, 16776, 20482, 20522, 32789, 34064,\n    49160, 65540, 65574, 65577, 67633, 98353, 131073, 131146, 131153, 147529, 163922, 196708, 14, 21, 35, 2073,\n    6144, 8199, 8388, 10309, 12326, 16397, 17032, 18433, 32790, 32793, 32800, 35088, 37136, 40964, 65544, 65578,\n    65585, 69632, 70176, 81961, 98320, 98354, 99120, 131074, 131148, 131154, 131169, 132169, 139333, 140292,\n    140352, 147530, 148680, 163924, 196712, 229488, 22, 25, 37, 67, 259, 517, 1033, 4194, 8516, 9216, 16398,\n    16640, 17544, 18457, 32794, 32832, 36866, 49177, 65552, 65580, 65586, 73765, 81928, 81962, 98356, 131076,\n    131156, 131170, 132170, 135235, 139334, 147532, 163928, 196640, 196720, 213096, 26, 38, 41, 69, 131, 518,\n    1034, 2066, 2097, 4103, 4129, 4258, 6179, 8205, 8772, 10240, 10261, 16409, 16896, 18568, 32796, 32817, 32896,\n    40981, 49178, 65588, 65633, 69667, 70146, 73732, 73766, 81964, 82600, 98360, 131080, 131160, 131172, 131265,\n    131394, 131652, 132172, 133200, 139264, 163856, 180312, 197736, 204900, 28, 42, 49, 70, 73, 133, 262, 1036,\n    2068, 2129, 4107, 4386, 6163, 8206, 8213, 8257, 9284, 16410, 16425, 24589, 32818, 32849, 36883, 40982, 49180,\n    65592, 65600, 65634, 65697, 65826, 66085, 66600, 67632, 114744, 131088, 131176, 131266, 131393, 133187,\n    135238, 135264, 139340, 147464, 147544, 163952, 164952, 172116, 200802, 44, 50, 74, 81, 134, 137, 261, 266,\n    515, 524, 2055, 2072, 2193, 4115, 4132, 4642, 8214, 8229, 8258, 10308, 12288, 16412, 16426, 16457, 16513,\n    18432, 20491, 20616, 24590, 32820, 32850, 32913, 33026, 33280, 33300, 33816, 35073, 41232, 49560, 65636,\n    65664, 65698, 65825, 66052, 66086, 67585, 67619, 69670, 73772, 74340, 81976, 82464, 106548, 131104, 131184,\n    131268, 131649, 132104, 132163, 132184, 133189, 135242, 136290, 139348, 147560, 155724, 164928, 168018,\n    196832, 197220, 198753, 52, 76, 82, 97, 138, 145, 265, 274, 532, 1027, 1048, 2059, 2321, 4110, 4136, 5154,\n    8230, 12295, 16428, 16458, 16514, 16650, 16908, 18456, 32824, 32852, 32914, 33025, 33792, 34835, 36886,\n    36912, 40988, 65640, 65700, 65792, 67621, 69674, 73780, 73824, 90156, 98416, 102450, 131272, 131396, 131650,\n    132165, 132200, 133193, 135170, 135250, 139364, 140364, 147648, 151626, 164048, 165969, 196960, 197729, 56,\n    84, 98, 140, 146, 161, 273, 290, 521, 548, 1029, 1064, 2061, 2096, 2577, 4118, 4144, 4163, 6178, 8220, 8264,\n    8325, 8454, 9228, 10260, 12356, 16460, 16516, 16649, 18443, 20480, 20494, 20520, 24712, 32856, 32916, 33043,\n    33297, 34837, 36890, 41040, 41300, 49208, 49424, 57372, 65648, 65704, 65828, 66049, 66083, 66092, 66593,\n    67625, 69682, 82024, 82080, 86058, 98480, 98848, 98868, 100401, 131200, 131280, 131400, 132166, 133121,\n    134225, 136266, 143430, 149577, 164160, 164945, 197217, 197696, 197730, 88, 100, 148, 162, 193, 268, 289,\n    322, 522, 529, 580, 1030, 1096, 2083, 2128, 3089, 4122, 4227, 4614, 5130, 6162, 8236, 8272, 8326, 8453,\n    10247, 12324, 16440, 16905, 18445, 24604, 24648, 32920, 33028, 33298, 33809, 34841, 41012, 49240, 49296,\n    53274, 65712, 65832, 66050, 66100, 66560, 66594, 77862, 82476, 84009, 98592, 131296, 131328, 131408, 131656,\n    132097, 132177, 132296, 133186, 133217, 134217, 139460, 141381, 147784, 148553, 148672, 164178, 164432,\n    164946, 197184, 197218, 197732, 104, 152, 164, 194, 276, 321, 386, 530, 545, 644, 1041, 1160, 2054, 2085,\n    2115, 2192, 2307, 2565, 3081, 4166, 4192, 4355, 4384, 8244, 8288, 8709, 8768, 12302, 16472, 16528, 16652,\n    16906, 17417, 17536, 20506, 24576, 24620, 24780, 32880, 33032, 33046, 33810, 35072, 36864, 37170, 45078,\n    51225, 65840, 66089, 66596, 67584, 67618, 68145, 69730, 73892, 75813, 82216, 98610, 98865, 131424, 131584,\n    131664, 132098, 132178, 132193, 132424, 133188, 135233, 135362, 137283, 139588, 140357, 148040, 148554,\n    164177, 164948, 198752, 230512, 112, 168, 196, 280, 292, 385, 546, 577, 772, 1042, 1057, 1288, 2058, 2089,\n    2117, 2179, 2320, 4101, 4170, 4230, 4256, 4611, 6151, 6177, 8332, 8710, 9221, 10253, 12310, 12354, 16488,\n    16544, 17418, 24708, 28686, 32944, 33045, 33050, 33304, 33812, 34834, 34865, 36946, 41108, 43029, 49416,\n    65760, 66056, 66090, 66097, 66148, 67620, 67681, 69665, 69794, 70144, 70182, 71715, 74020, 82473, 98609,\n    98832, 98866, 99376, 131680, 132100, 132180, 132194, 132680, 133192, 133313, 135490, 136259, 139844, 140358,\n    148556, 165968, 197224, 197664, 197744, 214120, 230000, 176, 200, 296, 324, 536, 641, 770, 1044, 1058, 1089,\n    1544, 2060, 2121, 2181, 4105, 4178, 4234, 4358, 8460, 9222, 10305, 16576, 17420, 18442, 18473, 20554, 20610,\n    32976, 33049, 33056, 34836, 34897, 38931, 41220, 65888, 66098, 66608, 67624, 67745, 68133, 74277, 74336,\n    82440, 82474, 131520, 132196, 132418, 133120, 133441, 134224, 135746, 164880, 181336, 197232, 205924, 229744 };\n\n\nstatic int cbest_19[1023] = {\n    1, 2, 262163, 4, 393242, 8, 196621, 16, 32, 360469, 64, 262162, 128, 131081, 3, 442393, 262161, 6, 256,\n    393243, 5, 327703, 12, 196620, 512, 393240, 9, 24, 98310, 262167, 426008, 10, 458782, 48, 1024, 49155,\n    196617, 17, 213004, 262171, 393234, 483359, 18, 96, 262147, 360471, 393246, 20, 2048, 16385, 196623, 286738,\n    360468, 442392, 33, 192, 32770, 106502, 229391, 34, 65540, 196613, 270355, 360465, 36, 384, 4096, 131080,\n    143369, 180234, 393226, 40, 65, 53251, 221196, 262195, 66, 768, 376852, 397338, 442395, 68, 8192, 24577,\n    49154, 90117, 503836, 72, 129, 1536, 262160, 262227, 80, 110598, 198669, 288786, 393274, 132, 3072, 16384,\n    98308, 188426, 196637, 274451, 327702, 333847, 483358, 7, 130, 136, 257, 131083, 262291, 307217, 144, 6144,\n    49153, 163851, 213005, 262165, 286739, 327699, 360477, 393241, 393306, 426010, 13, 160, 55299, 131073,\n    144393, 196616, 196653, 262166, 327701, 360453, 361493, 426009, 14, 26, 258, 264, 513, 12288, 53250, 94213,\n    131085, 196609, 262419, 399386, 442385, 458783, 25, 52, 272, 26625, 98306, 98311, 251918, 344086, 360467,\n    393370, 429080, 442397, 11, 104, 288, 24576, 32768, 90116, 106500, 196619, 196622, 196685, 229389, 262155,\n    262170, 262175, 311312, 368661, 393218, 393235, 393238, 393244, 405530, 458778, 22, 28, 49, 208, 260, 320,\n    514, 1025, 45058, 155656, 180235, 229390, 241679, 262146, 262169, 262675, 360470, 376853, 393232, 393247,\n    442394, 458780, 44, 416, 528, 22529, 77828, 172043, 180232, 196612, 199693, 213000, 221197, 262145, 262179,\n    289810, 360501, 393498, 415771, 442905, 483355, 491548, 19, 88, 97, 544, 832, 38914, 53249, 143368, 196749,\n    275475, 309265, 442377, 475167, 483357, 21, 38, 50, 56, 176, 516, 576, 1026, 1664, 2049, 19457, 71684,\n    114695, 131097, 135177, 202765, 214540, 263187, 270354, 288787, 334359, 360464, 426000, 76, 193, 352, 640,\n    3328, 32771, 35842, 49152, 106498, 106503, 139273, 213006, 229387, 262151, 278546, 286736, 327711, 352278,\n    360533, 393227, 393754, 426012, 446489, 458774, 35, 98, 152, 194, 704, 1056, 6656, 17921, 55298, 65536,\n    65541, 196629, 196877, 245774, 262194, 262259, 273427, 294929, 319504, 327687, 348182, 376854, 393224,\n    393258, 450585, 37, 42, 70, 112, 304, 385, 520, 1028, 1088, 1408, 2050, 4097, 13312, 16387, 49159, 94212,\n    110596, 125959, 131113, 144905, 188427, 196615, 212996, 262193, 264211, 270353, 315408, 329751, 360473,\n    362005, 372757, 382996, 397339, 483351, 503838, 41, 140, 608, 1152, 2816, 26624, 27649, 98318, 159752,\n    188424, 198668, 221192, 262355, 271891, 331799, 360597, 394266, 399898, 442425, 499740, 503837, 67, 100, 280,\n    388, 769, 1216, 1280, 5632, 47106, 65542, 98304, 99334, 107270, 157704, 176139, 196633, 196636, 197133,\n    262226, 363541, 376848, 393230, 406554, 409627, 425992, 427032, 442384, 69, 74, 84, 224, 386, 560, 1032,\n    2052, 2112, 2432, 4098, 8193, 11264, 16389, 49163, 49667, 79876, 90113, 122887, 131082, 131145, 143371,\n    163849, 174091, 229383, 262199, 262225, 262547, 266259, 271123, 286742, 344087, 360479, 393275, 393338,\n    397336, 428056, 429336, 442396, 458766, 470046, 483615, 503832, 73, 196, 1120, 1537, 2176, 4864, 22528,\n    23553, 32774, 55297, 78852, 81925, 98326, 163850, 213516, 221198, 262203, 274450, 307219, 311313, 327735,\n    333846, 360455, 360725, 393266, 393272, 395290, 398874, 416795, 442399, 442457, 460830, 81, 134, 770, 776,\n    2240, 2304, 9728, 39938, 53248, 110594, 110599, 131072, 137225, 143361, 144392, 180226, 196649, 196652,\n    196669, 197645, 198665, 214028, 251919, 262290, 262931, 270359, 286994, 303121, 307216, 327697, 356374,\n    360449, 393434, 434200, 442387, 483343, 133, 148, 168, 448, 1040, 2056, 2560, 3073, 4100, 4480, 8194, 16393,\n    16897, 19456, 24579, 39426, 49171, 90119, 98309, 106758, 114694, 131084, 131209, 143373, 180238, 191498,\n    196608, 199949, 221188, 249870, 251916, 262164, 262211, 262231, 275987, 286746, 288784, 289811, 323600,\n    327698, 333843, 360461, 360476, 361495, 393307, 398106, 415770, 426011, 442904, 443161, 448537, 466974,\n    483354, 485407, 131, 137, 200, 1538, 4224, 8960, 17409, 19969, 32778, 53255, 53635, 57347, 72196, 98342,\n    106510, 107014, 131077, 143497, 153608, 155657, 196639, 196717, 203277, 213020, 217100, 262235, 263699,\n    270611, 274449, 286722, 325136, 327700, 327767, 333845, 350230, 360452, 360981, 361492, 393278, 393298,\n    393304, 393626, 397330, 426040, 442376, 442521, 475166, 483356, 487455, 82, 138, 145, 268, 772, 1552, 4352,\n    6145, 17920, 18433, 19713, 33794, 45056, 53379, 65548, 106496, 166923, 180233, 196618, 196645, 196681,\n    196684, 198671, 213001, 229385, 229388, 230415, 241677, 262289, 262418, 270363, 270867, 317456, 334615,\n    349206, 360485, 397342, 397722, 399387, 417819, 429082, 442369, 442389, 443929, 458814, 503828, 146, 161,\n    296, 336, 392, 896, 2064, 3074, 4104, 4608, 8196, 16386, 16401, 20481, 24581, 34818, 36098, 49158, 49187,\n    53507, 76804, 108550, 125958, 131087, 131089, 131337, 161800, 172041, 180746, 196813, 199437, 214668, 221452,\n    241678, 262153, 262275, 262295, 265235, 270339, 271379, 273939, 278547, 286737, 290834, 311314, 327707,\n    330775, 352279, 360466, 360503, 361489, 368663, 393216, 393371, 394010, 397466, 413723, 429081, 446488,\n    458770, 458776, 15, 27, 54, 108, 162, 216, 259, 265, 432, 864, 1540, 1728, 3456, 5120, 6912, 12289, 13824,\n    27648, 32786, 36866, 38912, 49157, 53259, 67588, 94209, 98374, 106518, 131075, 172042, 178187, 196655,\n    198661, 199692, 213036, 214541, 229407, 233487, 262154, 262174, 262299, 272403, 319505, 327831, 348183,\n    360497, 368660, 376855, 393219, 393239, 393245, 393290, 393310, 393362, 397594, 401434, 405531, 426014,\n    426072, 442379, 442649, 450584, 458779, 483391, 503964, 514077, 53, 266, 273, 536, 3104, 6146, 8448, 18049,\n    24833, 38402, 40962, 54275, 65556, 69636, 86021, 90373, 98307, 114693, 124935, 145417, 158728, 188418,\n    196611, 196677, 196745, 196748, 197005, 198733, 199053, 200717, 207885, 212992, 213007, 235023, 237583,\n    251914, 262168, 262173, 262417, 262674, 268307, 288790, 288850, 315409, 339991, 344082, 376860, 377364,\n    382997, 393233, 393236, 394778, 397850, 399384, 426002, 442907, 458781, 458846, 483347, 483350, 483353,\n    491550, 30, 105, 262, 274, 289, 400, 592, 672, 1792, 2080, 3076, 4112, 8200, 8704, 16388, 16417, 24585,\n    32769, 35840, 49162, 49219, 49666, 73732, 80900, 90112, 90125, 95749, 98314, 99846, 106501, 110726, 122886,\n    131096, 131593, 135176, 143385, 144395, 175115, 180250, 184330, 188430, 198797, 202764, 223244, 245775,\n    262144, 262149, 262178, 262183, 262323, 262403, 272147, 274455, 286770, 288914, 309267, 333911, 344084,\n    360500, 360535, 360565, 362133, 368657, 376836, 388116, 393368, 393499, 397322, 398362, 400154, 407066,\n    427544, 438296, 442424, 491549, 23, 29, 46, 92, 106, 184, 209, 261, 276, 290, 321, 368, 515, 736, 1472, 1544,\n    2944, 5888, 9216, 11776, 12290, 19201, 23552, 32802, 45059, 53267, 55296, 81924, 94215, 98316, 98438, 106534,\n    139272, 163843, 174603, 196687, 197389, 198861, 198925, 212997, 213002, 213068, 221212, 225292, 229386,\n    229423, 241675, 245772, 262427, 272019, 274579, 275474, 288978, 305169, 307221, 309264, 327959, 345622,\n    360529, 362517, 363797, 376849, 378900, 393354, 393374, 393490, 396314, 405528, 409626, 426136, 436248,\n    461854, 470558, 471070, 483423, 503820, 504860, 417, 529, 1072, 6148, 10240, 92165, 101382, 125957, 144425,\n    147464, 196741, 204813, 262159, 262339, 262673, 263186, 286743, 288794, 307345, 327683, 425984, 426001,\n    432152, 442409, 458910, 505884 };\n\nstatic int cbest_20[1023] = {\n    1, 2, 524292, 4, 262146, 8, 131073, 16, 32, 589828, 64, 128, 294914, 256, 512, 147457, 1024, 2048, 4096,\n    598020, 8192, 16384, 32768, 299010, 65536, 131072, 149505, 262144, 3, 524288, 524293, 5, 262147, 524294,\n    599044, 6, 9, 18, 36, 72, 144, 288, 576, 1152, 2304, 4608, 9216, 18432, 36864, 73728, 147456, 786438, 10, 17,\n    131075, 262150, 294912, 524300, 655365, 12, 33, 131077, 262154, 393219, 524308, 589824, 589829, 20, 34, 65,\n    131081, 262162, 299522, 524324, 589830, 24, 66, 129, 131089, 262178, 294915, 524356, 851974, 40, 68, 130,\n    257, 131105, 262210, 524420, 589836, 720901, 819206, 48, 132, 258, 513, 131137, 262274, 294918, 524548,\n    589844, 80, 136, 260, 514, 1025, 131201, 147459, 149761, 262402, 294922, 425987, 524804, 589860, 671749, 96,\n    264, 516, 1026, 2049, 131329, 147461, 262658, 294930, 409603, 525316, 589892, 160, 272, 520, 1028, 2050,\n    4097, 131585, 147465, 149504, 263170, 294946, 299008, 526340, 589956, 598016, 598021, 884742, 192, 528, 1032,\n    2052, 4098, 8193, 74752, 132097, 147473, 264194, 294978, 528388, 590084, 598022, 320, 544, 1040, 2056, 4100,\n    8194, 16385, 37376, 133121, 147489, 266242, 295042, 532484, 590340, 737285, 860166, 384, 1056, 2064, 4104,\n    8196, 16386, 18688, 32769, 135169, 147521, 270338, 295170, 299011, 540676, 590852, 598028, 599172, 729093,\n    640, 1088, 2080, 4112, 8200, 9344, 16388, 32770, 65537, 139265, 147585, 278530, 295426, 442371, 557060,\n    591876, 598036, 823302, 768, 2112, 4128, 4672, 8208, 16392, 32772, 65538, 147713, 295938, 299014, 593924,\n    598052, 1280, 2176, 2336, 4160, 8224, 16400, 32776, 65540, 131074, 147969, 163841, 296962, 299018, 327682,\n    430083, 598084, 655364, 1168, 1536, 4224, 8256, 16416, 32784, 65544, 131076, 148481, 149507, 196609, 262145,\n    299026, 393218, 598148, 606212, 673797, 892934, 584, 2560, 4352, 8320, 16448, 32800, 65552, 131080, 149509,\n    299042, 299586, 303106, 411651, 598276, 622596, 786436, 888838, 292, 3072, 8448, 16512, 32832, 65568, 131088,\n    149513, 151553, 262148, 262160, 299074, 311298, 524289, 598532, 745477, 146, 5120, 8704, 16640, 32896, 65600,\n    131104, 149521, 155649, 262152, 299138, 393217, 524290, 524295, 524320, 599040, 599045, 720900, 7, 19, 37,\n    73, 145, 289, 577, 1153, 2305, 4609, 6144, 9217, 16896, 18433, 33024, 36865, 65664, 73729, 131136, 149537,\n    299266, 299520, 360450, 599046, 600068, 739333, 786434, 786439, 11, 38, 74, 290, 578, 1154, 2306, 4610, 9218,\n    10240, 17408, 18434, 33280, 36866, 65792, 73730, 131200, 147458, 149569, 149760, 180225, 262151, 262176,\n    294913, 425986, 446467, 524296, 524301, 524310, 524364, 524436, 524580, 524868, 525444, 526596, 528900,\n    533508, 542724, 561156, 602116, 655361, 671748, 851972, 861190, 13, 22, 76, 148, 580, 1156, 2308, 4612, 9220,\n    12288, 18436, 33792, 36868, 66048, 73732, 131328, 147460, 149633, 149793, 212993, 262155, 262182, 262208,\n    262218, 262290, 262434, 262722, 263298, 264450, 266754, 271362, 280578, 300034, 335874, 409602, 444419,\n    524302, 524304, 524309, 589825, 599052, 655367, 730117, 819204, 14, 21, 26, 35, 44, 152, 296, 1160, 2312,\n    4616, 9224, 18440, 20480, 34816, 36872, 66560, 73736, 74880, 131079, 131091, 131109, 131145, 131217, 131361,\n    131584, 131649, 132225, 133377, 135681, 140289, 147464, 167937, 204801, 262163, 262272, 294916, 294928,\n    299523, 301058, 524325, 589826, 589831, 589856, 599060, 614404, 786446, 917511, 25, 52, 67, 88, 304, 592,\n    2320, 4624, 9232, 18448, 24576, 36880, 67584, 73744, 131083, 132096, 147472, 150017, 262158, 262179, 262400,\n    294920, 393223, 425985, 524326, 524352, 524357, 599076, 630788, 655373, 786454, 823814, 851970, 851975,\n    897030, 41, 50, 69, 104, 131, 176, 608, 1184, 4640, 9248, 18464, 36896, 37440, 40960, 69632, 73760, 131085,\n    133120, 147488, 150529, 262166, 262211, 262656, 299526, 307202, 393227, 409601, 524316, 524358, 524416,\n    524421, 589832, 589837, 589846, 589900, 589972, 590116, 590404, 590980, 592132, 594436, 599108, 608260,\n    626692, 655381, 720897, 737284, 786470, 819202, 819207, 28, 42, 49, 70, 82, 100, 133, 208, 259, 352, 1216,\n    2368, 9280, 18496, 36928, 49152, 73792, 131093, 131107, 135168, 147520, 262170, 262275, 263168, 294919,\n    294944, 299530, 315394, 393235, 430595, 524332, 524422, 524544, 524549, 589838, 589840, 589845, 655397,\n    720903, 729092, 786502, 884740, 893958, 81, 134, 137, 164, 200, 261, 416, 515, 704, 2432, 4736, 18560, 18720,\n    36992, 73856, 81920, 131097, 131139, 139264, 147584, 153601, 262186, 262214, 262403, 264192, 294923, 294950,\n    294976, 294986, 295058, 295202, 295490, 296066, 297218, 299538, 304130, 313346, 368642, 393251, 442370,\n    524340, 524550, 524800, 524805, 589861, 599300, 655429, 671745, 747525, 786566, 851982, 983047, 97, 138, 262,\n    265, 274, 328, 400, 517, 832, 1027, 1408, 4864, 9472, 37120, 73984, 98304, 131113, 131141, 131203, 147712,\n    149763, 157697, 262194, 262278, 262659, 266240, 294931, 295040, 299554, 364546, 393283, 524372, 524428,\n    524806, 525312, 525317, 589862, 589888, 589893, 599188, 599556, 655493, 671751, 674053, 720909, 746501,\n    786694, 819214, 851990, 860164, 889350, 950279, 56, 84, 98, 140, 161, 266, 273, 518, 521, 530, 548, 656, 800,\n    1029, 1664, 2051, 2816, 9360, 9728, 18944, 74240, 131121, 131205, 131331, 147463, 147475, 147493, 147529,\n    147601, 147745, 147968, 148033, 148609, 149765, 152065, 156673, 163840, 184321, 221185, 262226, 262282,\n    262406, 263171, 270336, 294926, 294947, 295168, 299009, 393347, 411907, 425991, 430082, 524388, 524556,\n    525318, 526336, 526341, 589852, 589894, 589952, 589957, 598017, 655621, 720917, 786950, 819222, 852006,\n    884738, 884743, 933895, 162, 193, 268, 522, 529, 1030, 1033, 1042, 1060, 1096, 1312, 1600, 2053, 3328, 4099,\n    5632, 19456, 37888, 74753, 131153, 131209, 131333, 131587, 147467, 148480, 149506, 149769, 182273, 196608,\n    262242, 262410, 262662, 264195, 278528, 294934, 294979, 295424, 299650, 393475, 409607, 425995, 442369,\n    448515, 524452, 524564, 524812, 526342, 528384, 528389, 589868, 589958, 590080, 590085, 598018, 598023,\n    598048, 601092, 655877, 671757, 673796, 720933, 787462, 819238, 823300, 852038, 194, 276, 321, 524, 545,\n    1034, 1041, 2054, 2057, 2066, 2084, 2120, 2192, 2624, 3200, 4101, 4680, 6656, 8195, 11264, 37377, 38912,\n    74754, 75776, 131169, 131337, 131589, 132099, 147469, 149508, 149777, 215041, 262306, 262418, 262666, 263174,\n    266243, 294938, 295043, 295936, 299012, 299024, 299778, 393731, 409611, 411650, 426003, 524484, 524820,\n    525324, 528390, 532480, 532485, 589876, 590086, 590336, 590341, 603140, 656389, 671765, 720965, 737281,\n    788486, 819270, 852102, 860162, 860167, 112, 168, 196, 280, 322, 385, 532, 546, 1036, 1057, 2058, 2065, 4102,\n    4105, 4114, 4132, 4168, 4240, 4384, 5248, 6400, 8197, 13312, 16387, 18689, 22528, 37378, 74756, 77824,\n    131233, 131345, 131593, 132101, 133123, 147477, 147491, 149512, 151552, 262338, 262674, 263178, 264198,\n    270339, 294954, 294982, 295171, 296960, 299016, 327680, 336898, 394243, 409619, 426019, 430081, 446979,\n    524612, 524836, 525332, 526348, 532486, 540672, 540677, 561668, 589908, 589964, 590342, 590848, 590853,\n    598024, 598029, 598038, 598092, 598164, 598308, 598596, 599168, 599173, 600324, 602628, 616452, 634884,\n    657413, 671781, 721029, 729089, 737287, 739589, 745476, 790534, 819334, 852230, 884750, 1015815, 386, 1089,\n    4106, 8228, 8264, 8336, 10496, 131601, 147481, 147523, 149825, 155648, 262466, 263186, 295046, 300546,\n    393216, 426051, 590092, 599174, 615428, 729095, 823303 };\n\nstatic int cbest_21[1023] = {\n    1, 2, 1048578, 4, 524289, 8, 16, 1310722, 32, 64, 655361, 128, 256, 512, 1376258, 1024, 2048, 4096, 688129,\n    8192, 16384, 32768, 65536, 1392642, 131072, 262144, 524288, 696321, 1048576, 3, 1048579, 5, 10, 20, 40, 80,\n    160, 320, 640, 1280, 2560, 5120, 10240, 20480, 40960, 81920, 163840, 327680, 655360, 6, 9, 524291, 1048582,\n    1310720, 1572867, 17, 524293, 1048586, 1310723, 1396738, 12, 18, 33, 524297, 1048594, 34, 65, 524305,\n    1048610, 1310726, 1835011, 24, 36, 66, 129, 524321, 655363, 1048642, 1310730, 1703939, 68, 130, 257, 524353,\n    655365, 688128, 1048706, 1310738, 1376256, 48, 72, 132, 258, 513, 344064, 524417, 655369, 698369, 1048834,\n    1310754, 1376259, 136, 260, 514, 1025, 172032, 524545, 655377, 1049090, 1310786, 1966083, 96, 144, 264, 516,\n    1026, 2049, 86016, 524801, 655393, 1049602, 1310850, 1376262, 1900547, 272, 520, 1028, 2050, 4097, 43008,\n    525313, 655425, 1050626, 1310978, 1376266, 192, 288, 528, 1032, 2052, 4098, 8193, 21504, 526337, 655489,\n    688131, 1052674, 1311234, 1376274, 1736707, 544, 1040, 2056, 4100, 8194, 10752, 16385, 528385, 655617,\n    688133, 1056770, 1311746, 1376290, 384, 576, 1056, 2064, 4104, 5376, 8196, 16386, 32769, 532481, 655873,\n    688137, 1064962, 1312770, 1376322, 1392640, 1397762, 2031619, 1088, 2080, 2688, 4112, 8200, 16388, 32770,\n    65537, 540673, 656385, 688145, 696320, 1081346, 1314818, 1376386, 1392643, 1998851, 768, 1152, 1344, 2112,\n    4128, 8208, 16392, 32772, 65538, 131073, 557057, 657409, 688161, 1114114, 1318914, 1376514, 672, 2176, 4160,\n    8224, 16400, 32776, 65540, 131074, 262145, 348160, 589825, 659457, 688193, 1179650, 1327106, 1376770,\n    1392646, 1916931, 336, 1536, 2304, 4224, 8256, 16416, 32784, 65544, 131076, 262146, 663553, 688257, 1343490,\n    1377282, 1392650, 168, 4352, 8320, 16448, 32800, 65552, 131080, 174080, 262148, 524290, 671745, 688385,\n    786433, 1378306, 1392658, 1572866, 84, 3072, 4608, 8448, 16512, 32832, 65568, 131088, 262152, 524292, 688641,\n    696323, 698881, 1048577, 1380354, 1392674, 1441794, 1744899, 2064387, 42, 8704, 16640, 32896, 65600, 87040,\n    131104, 262160, 524296, 689153, 696325, 720897, 1048584, 1384450, 1392706, 2048003, 11, 21, 41, 81, 161, 321,\n    641, 1281, 2561, 5121, 6144, 9216, 10241, 16896, 20481, 33024, 40961, 65664, 81921, 131136, 163841, 262176,\n    327681, 524304, 690177, 696329, 1048580, 1392770, 1572865, 1835010, 7, 22, 82, 162, 322, 642, 1282, 2562,\n    5122, 10242, 17408, 20482, 33280, 40962, 43520, 65792, 81922, 131200, 163842, 262208, 327682, 524320, 655362,\n    692225, 696337, 917505, 1048583, 1048598, 1048618, 1048658, 1048738, 1048898, 1049218, 1049858, 1051138,\n    1053698, 1058818, 1069058, 1089538, 1130498, 1212418, 1310721, 1392898, 1396736, 1409026, 1703938, 2007043,\n    14, 44, 164, 324, 644, 1284, 2564, 5124, 10244, 12288, 18432, 20484, 33792, 40964, 66048, 81924, 131328,\n    163844, 262272, 327684, 524299, 524309, 524329, 524352, 524369, 524449, 524609, 524929, 525569, 526849,\n    529409, 534529, 544769, 565249, 606209, 655364, 696353, 851969, 1048587, 1048592, 1310728, 1393154, 1396739,\n    13, 19, 28, 88, 328, 648, 1288, 2568, 5128, 10248, 20488, 21760, 34816, 40968, 66560, 81928, 131584, 163848,\n    262400, 327688, 524295, 524416, 655368, 696385, 698368, 704513, 1048595, 1048608, 1310724, 1393666, 1507330,\n    1572871, 1835009, 26, 35, 56, 176, 656, 1296, 2576, 5136, 10256, 20496, 24576, 36864, 40976, 67584, 81936,\n    132096, 163856, 262656, 327696, 524544, 655376, 696449, 1048590, 1048611, 1048640, 1310727, 1310742, 1310762,\n    1310802, 1310882, 1311042, 1311362, 1312002, 1313282, 1315842, 1320962, 1331202, 1351682, 1394690, 1396742,\n    1474562, 1572875, 1703937, 1921027, 1966082, 25, 37, 52, 67, 112, 352, 1312, 2592, 5152, 10272, 10880, 20512,\n    40992, 69632, 81952, 133120, 163872, 263168, 327712, 524301, 524307, 524800, 655392, 696577, 753665, 1048643,\n    1048704, 1310731, 1310736, 1396746, 1398018, 1572883, 1900546, 2080771, 38, 69, 74, 104, 131, 224, 704, 2624,\n    5184, 10304, 20544, 41024, 49152, 73728, 81984, 135168, 163904, 264192, 327744, 349184, 524323, 525312,\n    655371, 655381, 655401, 655424, 655441, 655521, 655681, 656001, 656641, 657921, 660481, 665601, 675841,\n    696833, 737281, 983041, 1048602, 1048614, 1048707, 1048832, 1310739, 1310752, 1376257, 1396754, 1400834,\n    1572899, 1835015, 2072579, 49, 70, 73, 133, 138, 148, 208, 259, 448, 1408, 5248, 5440, 10368, 20608, 41088,\n    82048, 139264, 163968, 266240, 327808, 344065, 524313, 524325, 524355, 526336, 655367, 655488, 688130,\n    697345, 950273, 1048646, 1048835, 1049088, 1310734, 1310755, 1310784, 1376264, 1396770, 1572931, 1703943,\n    1736706, 1835019, 1966081, 50, 134, 137, 261, 266, 276, 296, 416, 515, 896, 2816, 10496, 20736, 41216, 82176,\n    98304, 147456, 164096, 172033, 270336, 327936, 344066, 524357, 524419, 528384, 655616, 688132, 698371,\n    1048626, 1048650, 1048710, 1049091, 1049600, 1310787, 1310848, 1376260, 1396802, 1425410, 1572995, 1703947,\n    1746947, 1835027, 1900545, 2052099, 76, 97, 145, 262, 265, 517, 522, 532, 552, 592, 832, 1027, 1792, 2720,\n    5632, 20992, 41472, 82432, 86017, 164352, 172034, 174592, 278528, 328192, 344068, 524337, 524361, 524421,\n    524547, 532480, 655373, 655379, 655872, 688136, 698373, 700417, 868353, 1048714, 1048838, 1049603, 1050624,\n    1220610, 1310746, 1310758, 1310851, 1310976, 1376263, 1376278, 1376298, 1376338, 1376418, 1376578, 1376898,\n    1377538, 1378818, 1381378, 1386498, 1396866, 1417218, 1540098, 1573123, 1703955, 1835043, 2031618, 98, 140,\n    146, 273, 518, 521, 1029, 1034, 1044, 1064, 1104, 1184, 1664, 2051, 3584, 11264, 41984, 43009, 82944, 86018,\n    164864, 172036, 196608, 294912, 328704, 344072, 524425, 524549, 524803, 540672, 655395, 656384, 688144,\n    698377, 1048674, 1048722, 1048842, 1049094, 1050627, 1052672, 1134594, 1310790, 1310979, 1311232, 1376267,\n    1376272, 1396994, 1523714, 1573379, 1703971, 1736705, 1835075, 1966087, 1998850, 100, 193, 268, 274, 289,\n    529, 1030, 1033, 1360, 2053, 2058, 2068, 2088, 2128, 2208, 2368, 3328, 4099, 7168, 21505, 22528, 43010,\n    83968, 86020, 165888, 172040, 329728, 344080, 524385, 524433, 524553, 524805, 525315, 557056, 610305, 655385,\n    655397, 655427, 657408, 688160, 698385, 712705, 1048850, 1049098, 1049606, 1052675, 1056768, 1091586,\n    1310770, 1310794, 1310854, 1311235, 1311744, 1376275, 1376288, 1397250, 1573891, 1704003, 1835139, 1900551,\n    1966091, 2009091, 152, 194, 290, 524, 530, 545, 1041, 2054, 2057, 4101, 4106, 4116, 4136, 4176, 4256, 4416,\n    4736, 6656, 8195, 10753, 14336, 21506, 43012, 45056, 86024, 87296, 167936, 172048, 331776, 344096, 393216,\n    524561, 524809, 525317, 526339, 567297, 589824, 655429, 655491, 659456, 688139, 688149, 688169, 688192,\n    688209, 688289, 688449, 688769, 689409, 690689, 693249, 698401, 699009, 708609, 770049, 1015809, 1048770,\n    1048866, 1049106, 1049610, 1050630, 1056771, 1064960, 1070082, 1310858, 1310982, 1311747, 1312768, 1376270,\n    1376291, 1376320, 1397760, 1482754, 1574915, 1704067, 1835267, 1900555, 1916930, 1966099, 2031617, 196, 280,\n    292, 385, 546, 577, 680, 1036, 1042, 1057, 2065, 4102, 4105, 5377, 8197, 8202, 8212, 8232, 8272, 8352, 8512,\n    8832, 9472, 10754, 13312, 16387, 21508, 28672, 43016, 86032, 90112, 172064, 335872, 344128, 524481, 524577,\n    524817, 525321, 526341, 528387, 545793, 655409, 655433, 655493, 655619, 663552, 688135, 688256, 698433,\n    761857, 999425, 1049122, 1049618, 1050634, 1052678, 1059330, 1064963, 1081344, 1310818, 1310866, 1310986,\n    1311238, 1312771, 1314816, 1376323, 1376384, 1392641, 1397763, 1398786, 1576963, 1704195, 1736711, 1835523,\n    1900563, 1966115, 1998849, 2088963, 200, 386, 548, 1058, 16394, 16424, 16544, 17664, 18944, 57344, 180224,\n    535041, 671744, 698497, 1052682, 1056774, 1353730, 1392648, 1836035 };\n\nstatic int cbest_22[1023] = {\n    1, 2, 2097153, 4, 8, 3145729, 16, 32, 64, 3670017, 128, 256, 512, 1024, 3932161, 2048, 4096, 8192, 16384,\n    32768, 4063233, 65536, 131072, 262144, 524288, 1048576, 2097152, 4128769, 3, 6, 12, 24, 48, 96, 192, 384,\n    768, 1536, 3072, 6144, 12288, 24576, 49152, 98304, 196608, 393216, 786432, 1572864, 3145728, 5, 2097155, 9,\n    2097157, 10, 17, 2097161, 3145731, 3670016, 18, 33, 1835008, 2097169, 3145733, 20, 34, 65, 917504, 2097185,\n    3145737, 4161537, 36, 66, 129, 458752, 2097217, 3145745, 3670019, 40, 68, 130, 257, 229376, 2097281, 3145761,\n    3670021, 3932160, 72, 132, 258, 513, 114688, 2097409, 3145793, 3670025, 80, 136, 260, 514, 1025, 57344,\n    1966080, 2097665, 3145857, 3670033, 144, 264, 516, 1026, 2049, 28672, 2098177, 3145985, 3670049, 3932163,\n    160, 272, 520, 1028, 2050, 4097, 14336, 983040, 2099201, 3146241, 3670081, 3932165, 288, 528, 1032, 2052,\n    4098, 7168, 8193, 2101249, 3146753, 3670145, 3932169, 4063232, 320, 544, 1040, 2056, 3584, 4100, 8194, 16385,\n    491520, 2105345, 3147777, 3670273, 3932177, 4177921, 576, 1056, 1792, 2064, 4104, 8196, 16386, 32769,\n    2113537, 3149825, 3670529, 3932193, 640, 896, 1088, 2080, 4112, 8200, 16388, 32770, 65537, 245760, 2031616,\n    2129921, 3153921, 3671041, 3932225, 4063235, 448, 1152, 2112, 4128, 8208, 16392, 32772, 65538, 131073,\n    2162689, 3162113, 3672065, 3932289, 4063237, 224, 1280, 2176, 4160, 8224, 16400, 32776, 65540, 122880,\n    131074, 262145, 2228225, 3178497, 3674113, 3932417, 4063241, 112, 2304, 4224, 8256, 16416, 32784, 65544,\n    131076, 262146, 524289, 1015808, 2359297, 3211265, 3678209, 3932673, 4063249, 4128768, 56, 2560, 4352, 8320,\n    16448, 32800, 61440, 65552, 131080, 262148, 524290, 1048577, 2621441, 3276801, 3686401, 3933185, 4063265, 28,\n    4608, 8448, 16512, 32832, 65568, 131088, 262152, 524292, 1048578, 3407873, 3702785, 3934209, 4063297, 14,\n    5120, 8704, 16640, 30720, 32896, 65600, 131104, 262160, 507904, 524296, 1048580, 2097154, 3735553, 3936257,\n    4063361, 4128771, 7, 13, 25, 49, 97, 193, 385, 769, 1537, 3073, 6145, 9216, 12289, 16896, 24577, 33024,\n    49153, 65664, 98305, 131136, 196609, 262176, 393217, 524304, 786433, 1048584, 1572865, 2064384, 2097156,\n    3801089, 3940353, 4063489, 4128773, 4186113, 26, 50, 98, 194, 386, 770, 1538, 3074, 6146, 10240, 12290,\n    15360, 17408, 24578, 33280, 49154, 65792, 98306, 131200, 196610, 262208, 393218, 524320, 786434, 1048592,\n    1572866, 2097159, 2097160, 2097165, 2097177, 2097201, 2097249, 2097345, 2097537, 2097921, 2098689, 2100225,\n    2103297, 2109441, 2121729, 2146305, 2195457, 2293761, 2490369, 2883585, 3145730, 3948545, 4063745, 4128777,\n    52, 100, 196, 388, 772, 1540, 3076, 6148, 12292, 18432, 24580, 33792, 49156, 66048, 98308, 131328, 196612,\n    253952, 262272, 393220, 524352, 786436, 1048608, 1572868, 2097168, 3145732, 3964929, 4064257, 4128785, 11,\n    104, 200, 392, 776, 1544, 3080, 6152, 7680, 12296, 20480, 24584, 34816, 49160, 66560, 98312, 131584, 196616,\n    262400, 393224, 524416, 786440, 1048640, 1572872, 2097184, 3145735, 3145736, 3145741, 3145753, 3145777,\n    3145825, 3145921, 3146113, 3146497, 3147265, 3148801, 3151873, 3158017, 3170305, 3194881, 3244033, 3342337,\n    3538945, 3997697, 4065281, 4128801, 4161536, 19, 22, 208, 400, 784, 1552, 3088, 6160, 12304, 24592, 36864,\n    49168, 67584, 98320, 132096, 196624, 262656, 393232, 524544, 786448, 1032192, 1048704, 1572880, 1835009,\n    2097163, 2097216, 3145744, 3670018, 4067329, 4128833, 21, 35, 38, 44, 416, 800, 1568, 3104, 3840, 6176,\n    12320, 24608, 40960, 49184, 69632, 98336, 126976, 133120, 196640, 263168, 393248, 524800, 786464, 917505,\n    1048832, 1572896, 1835010, 2097171, 2097280, 3145760, 3670020, 4071425, 4128897, 37, 67, 70, 76, 88, 832,\n    1600, 3136, 6208, 12352, 24640, 49216, 73728, 98368, 135168, 196672, 264192, 393280, 458753, 525312, 786496,\n    917506, 1049088, 1572928, 1835012, 2097173, 2097187, 2097408, 3014657, 3145739, 3145792, 3670023, 3670024,\n    3670029, 3670041, 3670065, 3670113, 3670209, 3670401, 3670785, 3671553, 3673089, 3676161, 3682305, 3694593,\n    3719169, 3768321, 3866625, 4079617, 4129025, 4161539, 41, 69, 131, 134, 140, 152, 176, 1664, 1920, 3200,\n    6272, 12416, 24704, 49280, 81920, 98432, 139264, 196736, 229377, 266240, 393344, 458754, 526336, 786560,\n    917508, 1049600, 1572992, 1835016, 2097189, 2097219, 2097664, 2555905, 3145747, 3145856, 3670032, 4096001,\n    4129281, 4161541, 42, 73, 133, 259, 262, 268, 280, 304, 352, 3328, 6400, 12544, 24832, 49408, 63488, 98560,\n    114689, 147456, 196864, 229378, 270336, 393472, 458756, 516096, 528384, 786688, 917512, 1050624, 1573120,\n    1835024, 2080768, 2097193, 2097221, 2097283, 2098176, 2326529, 3145749, 3145763, 3145984, 3670048, 3932162,\n    4129793, 4161545, 74, 81, 137, 261, 515, 518, 524, 536, 560, 608, 704, 960, 6656, 12800, 25088, 49664, 57345,\n    98816, 114690, 163840, 197120, 229380, 278528, 393728, 458760, 532480, 786944, 917520, 1052672, 1573376,\n    1835040, 1966081, 2097225, 2097285, 2097411, 2099200, 2211841, 3145765, 3145795, 3146240, 3604481, 3670027,\n    3670080, 3932164, 4130817, 4161553, 82, 138, 145, 265, 517, 1027, 1030, 1036, 1048, 1072, 1120, 1216, 1408,\n    13312, 25600, 28673, 50176, 57346, 99328, 114692, 197632, 229384, 294912, 394240, 458768, 540672, 787456,\n    917536, 1056768, 1573888, 1835072, 1966082, 2097233, 2097289, 2097413, 2097667, 2101248, 2154497, 3145769,\n    3145797, 3145859, 3146752, 3375105, 3670035, 3670144, 3932167, 3932168, 3932173, 3932185, 3932209, 3932257,\n    3932353, 3932545, 3932929, 3933697, 3935233, 3938305, 3944449, 3956737, 3981313, 4030465, 4132865, 4161569,\n    4190209, 84, 146, 161, 266, 273, 480, 521, 1029, 2051, 2054, 2060, 2072, 2096, 2144, 2240, 2432, 2816, 14337,\n    26624, 28674, 31744, 51200, 57348, 100352, 114696, 198656, 229392, 327680, 395264, 458784, 557056, 788480,\n    917568, 983041, 1064960, 1574912, 1835136, 1966084, 2097297, 2097417, 2097669, 2098179, 2105344, 2125825,\n    3145801, 3145861, 3145987, 3147776, 3260417, 3670037, 3670051, 3670272, 3932176, 4136961, 4161601, 4177920,\n    148, 162, 274, 289, 522, 529, 1033, 2053, 4099, 4102, 4108, 4120, 4144, 4192, 4288, 4480, 4864, 5632, 7169,\n    14338, 28676, 53248, 57352, 102400, 114704, 200704, 229408, 258048, 397312, 458816, 589824, 790528, 917632,\n    983042, 1081344, 1576960, 1835264, 1966088, 2097313, 2097425, 2097673, 2098181, 2099203, 2111489, 2113536,\n    3080193, 3145809, 3145865, 3145989, 3146243, 3149824, 3203073, 3670053, 3670083, 3670528, 3932192, 4145153,\n    4161665, 164, 240, 276, 290, 321, 530, 545, 1034, 1041, 2057, 3585, 4101, 7170, 8195, 8198, 8204, 8216, 8240,\n    8288, 8384, 8576, 8960, 9728, 11264, 14340, 28680, 57360, 106496, 114720, 204800, 229440, 401408, 458880,\n    491521, 655360, 794624, 917760, 983044, 1040384, 1114112, 1581056, 1835520, 1966096, 2097441, 2097681,\n    2098185, 2099205, 2101251, 2104321, 2129920, 3145873, 3145993, 3146245, 3146755, 3153920, 3174401, 3670057,\n    3670085, 3670147, 3671040, 3899393, 3932171, 3932224, 4063234, 4161793, 168, 292, 322, 532, 546, 577, 1042,\n    1057, 1793, 2058, 2065, 3586, 4105, 7172, 8197, 14344, 15872, 16387, 16390, 16396, 16408, 16432, 16480,\n    16576, 16768, 17152, 17920, 19456, 22528, 28688, 57376, 114752, 212992, 229504, 409600, 459008, 491522,\n    802816, 918016, 983048, 1179648, 1589248, 1836032, 1966112, 2097473, 2097697, 2098193, 2099209, 2100737,\n    2101253, 2105347, 2162688, 2588673, 3145889, 3146001, 3146249, 3146757, 3147779, 3160065, 3162112, 3670089,\n    3670149, 3670275, 3672064, 3784705, 3932179, 3932288, 4063236, 4162049, 4177923, 120, 296, 897, 1058, 1089,\n    2081, 4106, 4113, 8201, 32774, 32780, 33536, 34304, 45056, 491524, 983056, 1605632, 1966144, 2031617,\n    2098209, 2098945, 2099217, 2228224, 3146257, 3146761, 3178496, 3670153, 3670277, 3670531, 3932181, 3932416,\n    4063329, 4063425, 4069377, 4075521 };\n\nstatic int cbest_23[1023] = {\n    1, 2, 4194320, 4, 2097160, 8, 1048580, 16, 524290, 32, 262145, 64, 128, 4325392, 256, 512, 2162696, 1024,\n    2048, 1081348, 4096, 8192, 540674, 16384, 32768, 270337, 65536, 131072, 262144, 4329488, 524288, 2164744,\n    1048576, 3, 1082372, 2097152, 4194321, 5, 2097161, 4194322, 6, 9, 1048581, 2097162, 4194304, 4194324,\n    6291480, 10, 17, 524291, 541186, 1048582, 2097164, 4194328, 5242900, 12, 18, 33, 66, 132, 264, 528, 1056,\n    2112, 4224, 8448, 16896, 33792, 67584, 135168, 270336, 3145740, 4718610, 20, 34, 65, 262147, 524294, 540672,\n    1048588, 2097176, 2621450, 4194352, 4456465, 24, 36, 129, 262149, 270593, 524298, 1048596, 1081344, 1572870,\n    2097192, 2359305, 4194384, 4325393, 40, 68, 130, 257, 262153, 524306, 1048612, 1310725, 2097224, 2162688,\n    4194448, 4325394, 48, 72, 258, 513, 262161, 524322, 786435, 1048644, 2097288, 2162697, 4194576, 4325376,\n    4325396, 6422552, 80, 136, 260, 514, 1025, 262177, 524354, 1048708, 2097416, 2162698, 4194832, 4325400,\n    5373972, 6357016, 96, 144, 516, 1026, 2049, 262209, 524418, 1048836, 1081349, 2097672, 2162700, 4195344,\n    4329616, 4849682, 160, 272, 520, 1028, 2050, 4097, 262273, 524546, 1049092, 1081350, 2098184, 3211276,\n    4196368, 4325424, 4587537, 5275668, 192, 288, 1032, 2052, 4098, 8193, 262401, 524802, 540675, 1049604,\n    2099208, 2162712, 2686986, 3178508, 4198416, 4325456, 320, 544, 1040, 2056, 4100, 8194, 16385, 262657,\n    525314, 1050628, 1081356, 2101256, 2162728, 2424841, 4202512, 4325520, 4734994, 384, 576, 2064, 4104, 8196,\n    16386, 32769, 263169, 526338, 540678, 1052676, 1081364, 1605638, 2105352, 2162760, 2164808, 2637834, 4210704,\n    4325648, 640, 1088, 2080, 4112, 8200, 16388, 32770, 65537, 264193, 270339, 528386, 540682, 1056772, 1081380,\n    1343493, 1589254, 2113544, 2162824, 4227088, 4325904, 4464657, 6488088, 768, 1152, 4128, 8208, 16392, 32772,\n    65538, 131073, 266241, 270341, 532482, 540690, 1064964, 1081412, 2129928, 2162952, 2367497, 4259856, 4326416,\n    1280, 2176, 4160, 8224, 16400, 32776, 65540, 131074, 270345, 540706, 802819, 1081476, 1318917, 2163208,\n    4327440, 4329489, 5406740, 1536, 2304, 8256, 16416, 32784, 65544, 131076, 262146, 270353, 278529, 540738,\n    557058, 794627, 1081604, 1082404, 1114116, 2163720, 2228232, 4329490, 4456464, 2560, 4352, 8320, 16448,\n    32800, 65552, 131080, 262148, 270369, 270592, 294913, 524289, 540802, 541184, 589826, 1081860, 1082368,\n    1179652, 2164736, 2359304, 3244044, 4329472, 4329492, 4333584, 4866066, 6426648, 3072, 4608, 16512, 32832,\n    65568, 131088, 135296, 262152, 270401, 327681, 540930, 655362, 1310724, 2164745, 2166792, 4329496, 4341776,\n    4718608, 5378068, 5120, 8704, 16640, 32896, 65600, 67648, 131104, 262160, 270465, 393217, 524292, 786434,\n    1048577, 1083396, 2164746, 2170888, 2621448, 2703370, 4358160, 4595729, 4853778, 6359064, 6144, 9216, 33024,\n    33824, 65664, 131136, 262176, 524296, 541202, 541698, 1048578, 1085444, 1572868, 2164748, 2179080, 4329520,\n    4390928, 4591633, 5242896, 10240, 16912, 17408, 33280, 65792, 131200, 262208, 270849, 524304, 524352, 542722,\n    1082373, 1089540, 1622022, 2097153, 2195464, 2433033, 3145736, 3213324, 4329552, 8456, 12288, 18432, 66048,\n    131328, 262272, 271361, 524320, 544770, 786433, 1048584, 1048704, 1082374, 1097732, 2097154, 2164760,\n    2689034, 4194323, 4587536, 5276692, 6291472, 7, 4228, 20480, 34816, 66560, 131584, 262400, 272385, 548866,\n    1048592, 1351685, 1572866, 2097156, 2097163, 2097408, 2164776, 2293768, 2426889, 3179532, 4194305, 4194325,\n    4329744, 6291481, 11, 2114, 24576, 36864, 132096, 262656, 270601, 274433, 524416, 541187, 1048583, 1048608,\n    1082380, 1146884, 1310721, 2097165, 2424840, 3145732, 4194306, 4194326, 4194329, 4194816, 4330000, 4849680,\n    5242901, 6291482, 6492184, 13, 19, 67, 133, 265, 529, 1057, 2113, 4225, 8449, 16897, 33793, 40960, 67585,\n    69632, 133120, 135169, 263168, 524544, 573442, 811011, 1048640, 1082388, 1212420, 1606662, 2097166, 2097168,\n    2164872, 2621442, 3145741, 4194308, 4194330, 4330512, 4718611, 4735506, 5242902, 6291464, 6291484, 6490136,\n    14, 21, 35, 134, 266, 530, 1058, 4226, 8450, 16898, 33794, 49152, 67586, 73728, 135170, 264192, 270338,\n    286721, 524295, 524800, 540673, 541190, 606210, 1048589, 1343492, 1344517, 2097177, 2097184, 2165000,\n    2359297, 2621451, 2638346, 2686984, 3145742, 4194312, 4194332, 4194353, 4194386, 4194452, 4194584, 4195376,\n    4196432, 4198544, 4202768, 4211216, 4228112, 4261904, 4331536, 4464656, 5242884, 5373968, 5410836, 7340060,\n    22, 25, 37, 70, 268, 532, 1060, 2116, 8452, 16900, 33796, 67588, 81920, 135172, 139264, 266240, 270340,\n    303105, 524299, 525312, 541194, 671746, 1048590, 1048597, 1048832, 1081345, 1082436, 1572871, 1589766,\n    2097178, 2097193, 2097216, 2097226, 2097292, 2097688, 2098216, 2099272, 2101384, 2105608, 2114056, 2130952,\n    2165256, 2232328, 2367496, 4194354, 4194385, 4456467, 4718594, 4718614, 4734992, 5242908, 6815770, 26, 38,\n    41, 69, 74, 131, 140, 536, 1064, 2120, 4232, 16904, 33800, 67592, 98304, 135176, 147456, 262151, 270344,\n    270595, 335873, 524307, 526336, 540676, 802818, 1048598, 1048613, 1048646, 1048844, 1049088, 1049108,\n    1049636, 1050692, 1052804, 1057028, 1065476, 1081346, 1082500, 1116164, 1183748, 1318916, 1605636, 2097180,\n    2097194, 2097225, 2097280, 2162689, 2165768, 2359307, 2621454, 2637832, 3211272, 4194336, 4194356, 4194449,\n    4325395, 4337680, 4456449, 4456469, 4464913, 4718618, 4870162, 5275664, 5407764, 5767190, 6291512, 6422544,\n    6553625, 28, 42, 49, 73, 82, 148, 259, 280, 1072, 2128, 4240, 8464, 33808, 67600, 135184, 163840, 262155,\n    270352, 270597, 278528, 401409, 524302, 524323, 524422, 524554, 524818, 525346, 526402, 528384, 528514,\n    532738, 540680, 541218, 558082, 591874, 659458, 794626, 803331, 1048614, 1048645, 1049600, 1082628, 1310727,\n    1589252, 2097196, 2097289, 2162690, 2359309, 2367753, 3145756, 3178504, 3246092, 3670030, 4194360, 4194368,\n    4194388, 4194450, 4194577, 4325377, 4325397, 4329620, 4345872, 4456473, 5242932, 5505045, 6291544, 6357008,\n    6422553, 44, 50, 81, 98, 137, 164, 261, 296, 515, 560, 2144, 4256, 8480, 16928, 67616, 135200, 196608,\n    262157, 262163, 262211, 262277, 262409, 262673, 263201, 264257, 266369, 270368, 279041, 294912, 295937,\n    329729, 397313, 524310, 524355, 532480, 540688, 540736, 541250, 1048604, 1048709, 1050624, 1081352, 1081472,\n    1082884, 1319173, 1572878, 2097228, 2097290, 2097417, 2097664, 2162692, 2162699, 2162944, 2168840, 2621466,\n    3145772, 3245068, 3407885, 4194392, 4194432, 4194578, 4194833, 4325378, 4325398, 4325401, 4325888, 4362256,\n    4599825, 4718642, 4980755, 5242964, 5373973, 6291608, 6357017, 6422554, 52, 76, 97, 138, 145, 196, 262, 328,\n    517, 592, 1027, 1120, 4288, 8512, 16960, 33856, 135232, 262165, 262179, 270400, 270609, 327680, 524314,\n    524326, 524419, 540704, 541314, 786439, 794883, 802817, 1048620, 1048710, 1048837, 1052672, 1081360, 1310733,\n    1572886, 1605634, 2097208, 2097418, 2097673, 2098176, 2162701, 2172936, 2359321, 2621482, 2705418, 2883595,\n    3145804, 3211268, 4194456, 4194560, 4194580, 4194834, 4195345, 4325380, 4325402, 4329617, 4395024, 4456497,\n    4718674, 4849683, 4866578, 5243028, 5373974, 6291736, 6357018, 6422536, 6422556, 56, 84, 146, 161, 194, 273,\n    392, 518, 521, 656, 1029, 1184, 2051, 2240, 8576, 17024, 33920, 67712, 262169, 262181, 270464, 270625,\n    393216, 524330, 524358, 524547, 541442, 557056, 786443, 794625, 1048628, 1048652, 1048838, 1049093, 1056768,\n    1081351, 1081376, 1084420, 1310741, 1343489, 1572902, 1589250, 1835015, 2097240, 2097420, 2097674, 2098185,\n    2099200, 2162702, 2162704, 2181128, 2359337, 2621514, 2686978, 3145868, 3178500, 3211277, 4194416, 4194836,\n    4195346, 4196369, 4325384, 4325404, 4325425, 4325458, 4325524, 4325656, 4326448, 4327504, 4329618, 4333840,\n    4342288, 4359184, 4392976, 4456529, 4595728, 4718738, 5243156, 5275669, 5373956, 6291992, 6357000, 6357020,\n    7471132, 162, 193, 289, 1030, 1312, 2053, 4099, 34048, 262185, 262213, 524338, 524362, 524803, 786451,\n    1048716, 1049605, 1064960, 1086468, 1310757, 2097256, 2097304, 2097676, 2098186, 2099209, 2101248, 2162713,\n    2424833, 2621578, 2703882, 3145996, 3178509, 4194480, 4198417, 4325426, 4456593, 4587539, 4718866, 4849666,\n    6292504, 6426776, 7405596 };\n\nstatic int cbest_24[1023] = {\n    1, 2, 8388675, 4, 12583010, 8, 6291505, 16, 32, 64, 11534427, 128, 256, 8388674, 4194337, 14155886, 3, 512,\n    8388673, 6, 12583011, 5, 1024, 10485843, 12, 6291504, 12583008, 9, 24, 2048, 3145752, 8388679, 10, 7077943,\n    14680178, 48, 1572876, 13631594, 17, 4096, 8388683, 96, 786438, 12583014, 20, 6291507, 18, 33, 192, 8192,\n    393219, 7340089, 8388691, 6815797, 11534426, 12583018, 384, 131073, 6291489, 6291509, 11927640, 34, 40, 65,\n    16384, 262146, 8388707, 8585282, 12582978, 66, 768, 524292, 8388611, 8454211, 11534419, 11534425, 12583026,\n    1048584, 6291513, 36, 129, 1536, 32768, 2097168, 4292641, 5767213, 80, 130, 4194336, 6291473, 11796569,\n    12615778, 14155882, 132, 3072, 12058719, 12582946, 68, 257, 65536, 5963820, 8388803, 11534411, 11534431,\n    14155887, 258, 6144, 196609, 393218, 6307889, 7077941, 160, 260, 8388672, 72, 264, 513, 12288, 131072,\n    8388931, 10534995, 11272277, 14155878, 14155884, 786436, 8486979, 10485842, 12583138, 7, 516, 24576, 4194339,\n    6291569, 11927641, 136, 320, 514, 520, 1025, 393217, 5242921, 7077939, 7077942, 8388677, 8389187, 8585283,\n    12583009, 14418028, 13, 528, 49152, 1572872, 2981910, 8388678, 10485841, 11542619, 12583266, 14286959,\n    15466612, 14, 4194341, 6291633, 12632162, 14680179, 25, 144, 1026, 1032, 2049, 98304, 262144, 786434,\n    3145753, 8389699, 11534459, 13631595, 11, 26, 640, 1040, 3145744, 6291506, 8388682, 8388687, 11534363,\n    11927642, 12583012, 12583522, 12681314, 13656170, 14024809, 28, 49, 1056, 196608, 1572877, 4194345, 6291761,\n    7340088, 8388681, 11010135, 12583015, 14680176, 272, 1028, 2050, 4097, 1572868, 3145754, 3538971, 6291457,\n    6316081, 6815793, 6815796, 8388699, 8390723, 10485827, 10485847, 13631586, 13631592, 22, 52, 97, 2064,\n    786439, 4194305, 6291488, 6291508, 7209014, 8388690, 11534403, 12584034, 14155854, 21, 50, 56, 98, 1280,\n    2080, 1490955, 1572878, 3670044, 4194353, 5963821, 6291497, 6292017, 6340657, 7733306, 8388643, 8388723,\n    8781888, 9175109, 9961551, 12582914, 12582994, 12583019, 12583022, 14155902, 14159982, 15728762, 19, 193,\n    288, 2052, 2112, 4098, 8193, 524288, 3145736, 3145756, 3407898, 4390944, 8392771, 10485851, 11534555,\n    11796571, 12583016, 13893736, 14155874, 14352495, 15204470, 15532148, 44, 104, 393216, 2195472, 6291511,\n    6291512, 7077927, 7340081, 8388689, 8388706, 8388739, 12582979, 12583034, 12585058, 14680162, 14680182, 112,\n    196, 385, 4128, 1097736, 1835022, 4227105, 4292640, 5767209, 5767212, 6292529, 6828085, 7077937, 8388610,\n    8388695, 8454210, 11534418, 11534423, 11534424, 12583027, 13631598, 35, 41, 194, 544, 2056, 2560, 4100, 4160,\n    8194, 16385, 262147, 548868, 1703949, 2146320, 4259873, 6291472, 6291517, 7340091, 8388609, 8396867, 8519746,\n    11534417, 11534683, 11927632, 12976225, 13369444, 14155822, 42, 67, 88, 100, 208, 769, 4224, 274434, 524293,\n    1073160, 6291493, 6815799, 6946868, 7077951, 8388705, 8585280, 8650817, 10158158, 11546715, 12058715,\n    12582976, 12587106, 14155880, 14286958, 14680186, 38, 134, 224, 386, 392, 770, 131075, 137217, 536580,\n    786432, 917511, 1048585, 2883606, 3604507, 4194401, 5963816, 6291491, 6291537, 6293553, 7864381, 8389059,\n    8454209, 10502227, 11272279, 11796568, 12583024, 12583074, 12615779, 14155883, 15401079, 37, 70, 268, 576,\n    1537, 4104, 8196, 8256, 16386, 32769, 268290, 393223, 1048576, 2097169, 3866653, 6291515, 6307888, 7077911,\n    7077940, 7079991, 7340073, 7340093, 7602235, 8388715, 8405059, 10485875, 10518611, 11534429, 11534939,\n    11559003, 12058718, 12582947, 12582986, 12583030, 14680146, 81, 131, 176, 416, 536, 5120, 8320, 134145,\n    524294, 786446, 2981908, 3153944, 3538970, 5767205, 6684722, 6815781, 8388802, 8389443, 8847424, 8912967,\n    9134150, 9240645, 10485779, 11370581, 11534410, 11534430, 11927643, 12582944, 12591202, 12714083, 13631562,\n    14024808, 82, 133, 262, 388, 448, 784, 1072, 1540, 3073, 8448, 131077, 1048586, 1441803, 1572892, 1576972,\n    3473434, 4194465, 6295601, 7766074, 8388627, 8388801, 9437259, 11272273, 11534443, 11796561, 11927624,\n    11927644, 12615776, 12845152, 13631610, 14156014, 14418030, 69, 84, 140, 200, 524, 1088, 1538, 2144, 4112,\n    8200, 16388, 32770, 65537, 262150, 393227, 788486, 1490954, 1572864, 2097170, 3145784, 4292643, 4423712,\n    5767215, 5898284, 6422576, 8388711, 8390211, 8421443, 8457283, 8585286, 11535451, 12058711, 12058717,\n    12582982, 12583394, 13639786, 14696562, 15466614, 15564916, 76, 259, 352, 772, 832, 1048, 4288, 6145, 16512,\n    394243, 786454, 3211288, 4194338, 5963812, 6291475, 6291481, 6291568, 6815805, 8388615, 8388930, 8454215,\n    10534994, 11272276, 11534409, 11802713, 12599394, 13647978, 14155879, 14155885, 14876787, 74, 161, 261, 266,\n    896, 1568, 2096, 3074, 3080, 8576, 10240, 16640, 131081, 655365, 1048588, 1310730, 1572908, 1605644, 1769485,\n    2211856, 2621460, 3342361, 4194593, 5079079, 5242920, 6291697, 6299697, 7077938, 7143479, 7340057, 8388807,\n    8388929, 8391747, 8455747, 8486978, 8781889, 11534415, 12583139, 12583778, 13107302, 14155876, 14156142,\n    14418024, 14680114, 14745714, 73, 162, 265, 280, 1152, 4192, 8208, 12289, 16392, 16896, 17152, 32772, 65538,\n    132097, 262154, 393235, 802822, 1736717, 2097152, 2097172, 2981906, 3145816, 3538969, 4243489, 4292645,\n    5963822, 6029359, 6291553, 6815765, 7012404, 7209015, 8585290, 8585794, 8716353, 9109574, 10485971, 11536475,\n    11928664, 12320861, 12582962, 12583136, 13008993, 13631530, 14024811, 14155894, 14162030, 14286955, 14352494,\n    15466608, 164, 518, 704, 776, 1664, 6146, 8384, 34304, 133121, 196611, 401411, 524300, 786437, 786470,\n    1105928, 1703948, 2949142, 3145728, 4194340, 6291477, 6291632, 6291889, 6307891, 6819893, 7078007, 7209012,\n    7348281, 7733307, 8388619, 8388676, 8389186, 8394819, 8454219, 8454723, 10190926, 10534993, 11534451,\n    11796553, 11796573, 12189790, 12584546, 12615782, 14418029, 138, 168, 400, 517, 532, 1792, 3076, 3136, 6160,\n    16768, 24577, 33024, 68608, 131089, 135169, 264194, 745477, 851974, 1490953, 1572940, 4194849, 4292897,\n    4390945, 5267497, 5636138, 6553651, 6823989, 7733304, 8388835, 8388935, 8455235, 8486977, 10321997, 10485840,\n    11534395, 11542618, 12058703, 12582950, 12583106, 12583267, 12617314, 14156398, 14168174, 15466613, 16089208,\n    137, 152, 321, 515, 521, 560, 1544, 2176, 8224, 12290, 16400, 20480, 32776, 33280, 33536, 65540, 131074,\n    137216, 262162, 266242, 393251, 425987, 552964, 917510, 1572874, 2097176, 3145880, 3407896, 3604506, 4292649,\n    4567075, 6291571, 6292273, 7372857, 8388939, 8400963, 8456259, 8519747, 8585298, 8589378, 10486099, 11538523,\n    11927633, 12583142, 12583264, 12586082, 12616034, 12632163, 12779619, 12976224, 14155898, 148, 522, 529,\n    1036, 1408, 3328, 6148, 33792, 49153, 67072, 196613, 270338, 393222, 458755, 524308, 528388, 786502, 1572873,\n    2981911, 3866652, 3883037, 4194343, 4194344, 4554787, 5242913, 6291760, 6307873, 6307893, 7077925, 7077947,\n    7078071, 8388867, 8389185, 8389698, 8454227, 8585281, 8781890, 9175111, 9273413, 10158159, 10510419,\n    11010131, 11534458, 11534491, 12615786, 12616290, 12616546, 12648546, 12746851, 14073961, 14418020, 14680306,\n    15, 324, 1064, 3584, 6272, 12320, 24578, 131105, 134144, 163841, 276482, 393221, 532484, 1048600, 1474571,\n    1573004, 1835020, 3145748, 3506202, 4195361, 4292609, 4294689, 5767181, 6291456, 6293041, 6308017, 6316080,\n    6324273, 6488112, 6815792, 7077923, 7077949, 8388686, 8389191, 8413251, 8601666, 9257029, 10928208, 11403348,\n    11534347, 11534362, 11542611, 11542617, 11927672, 11960408, 12582954, 12583013, 12583523, 12589154, 12616802,\n    12681315, 13631722, 13656171, 13893738, 14155846, 14156910, 14286951, 14286957, 15401078, 15532150, 145, 322,\n    328, 1027, 1033, 1120, 1552, 2304, 12292, 16416, 32784, 65544, 66048, 98305, 131076, 262145, 262178, 393283,\n    540676, 786435, 1056776, 1441802, 2818069, 3146008, 4194304, 4194321, 4194349, 4292657, 5242923, 5767229,\n    5771309, 5964332, 6291601, 6291635, 6308145, 6946869, 7209010, 7340153, 7782458, 8388680, 8388685, 8388811,\n    8389195, 8470595, 8585314, 8618050, 10059855, 10485835, 10485845, 10486355, 10534979, 10534999, 10584147,\n    11010134, 11272261, 11534361, 11665498, 11798617, 11927576, 11927634, 11993176, 12583270, 12615746, 12632160,\n    14155790, 14155850, 14155870, 14680177, 15827066, 146, 276, 336, 641, 800, 1034, 2816, 6656, 49154, 66560,\n    196617, 197121, 524324, 720901, 786442, 786566, 3158040, 3670040, 5963817, 6291637, 6292016, 6294577,\n    6307897, 6308657, 6340656, 6815861, 7078199, 8389203, 8390722, 8454243, 8486983, 8585218, 8847425, 9134151,\n    10485846, 10535123, 12583274, 12615794, 13631587, 14159978 };\n\nstatic int cbest_25[1023] = {\n    1, 2, 16777220, 4, 8388610, 8, 4194305, 16, 32, 18874372, 64, 128, 9437186, 256, 512, 4718593, 1024, 2048,\n    4096, 19136516, 8192, 16384, 32768, 9568258, 65536, 131072, 262144, 4784129, 524288, 1048576, 2097152,\n    4194304, 19169284, 8388608, 3, 9584642, 16777216, 16777221, 5, 8388611, 16777222, 6, 9, 18, 36, 72, 144, 288,\n    576, 1152, 2304, 4608, 9216, 18432, 36864, 73728, 147456, 294912, 589824, 1179648, 2359296, 4718592,\n    25165830, 10, 17, 4194307, 8388614, 9437184, 16777228, 20971525, 12, 33, 4194309, 4792321, 8388618, 12582915,\n    16777236, 18874368, 18874373, 20, 34, 65, 4194313, 8388626, 16777252, 18874374, 24, 66, 129, 4194321,\n    8388642, 9437187, 16777284, 27262982, 40, 68, 130, 257, 4194337, 8388674, 16777348, 18874380, 23068677,\n    26214406, 48, 132, 258, 513, 4194369, 8388738, 9437190, 16777476, 18874388, 80, 136, 260, 514, 1025, 4194433,\n    4718595, 8388866, 9437194, 13631491, 16777732, 18874404, 19173380, 21495813, 96, 264, 516, 1026, 2049,\n    4194561, 4718597, 8389122, 9437202, 13107203, 16778244, 18874436, 160, 272, 520, 1028, 2050, 4097, 4194817,\n    4718601, 4784128, 8389634, 9437218, 9568256, 16779268, 18874500, 19136512, 19136517, 28311558, 192, 528,\n    1032, 2052, 4098, 8193, 2392064, 4195329, 4718609, 8390658, 9437250, 16781316, 18874628, 19136518, 320, 544,\n    1040, 2056, 4100, 8194, 16385, 1196032, 4196353, 4718625, 8392706, 9437314, 16785412, 18874884, 23592965,\n    27525126, 384, 1056, 2064, 4104, 8196, 16386, 32769, 598016, 4198401, 4718657, 8396802, 9437442, 9568259,\n    9586690, 16793604, 18875396, 19136524, 23330821, 640, 1088, 2080, 4112, 8200, 16388, 32770, 65537, 299008,\n    4202497, 4718721, 8404994, 9437698, 14155779, 16809988, 18876420, 19136532, 26345478, 768, 2112, 4128, 8208,\n    16392, 32772, 65538, 131073, 149504, 4210689, 4718849, 8421378, 9438210, 9568262, 16842756, 18878468,\n    19136548, 1280, 2176, 4160, 8224, 16400, 32776, 65540, 74752, 131074, 262145, 4227073, 4719105, 8454146,\n    9439234, 9568266, 13762563, 16908292, 18882564, 19136580, 1536, 4224, 8256, 16416, 32784, 37376, 65544,\n    131076, 262146, 524289, 4259841, 4719617, 4784131, 8519682, 9441282, 9568274, 17039364, 18890756, 19136644,\n    21561349, 28573702, 2560, 4352, 8320, 16448, 18688, 32800, 65552, 131080, 262148, 524290, 1048577, 4325377,\n    4720641, 4784133, 4793345, 8650754, 9445378, 9568290, 13172739, 17301508, 18907140, 19136772, 28442630, 3072,\n    8448, 9344, 16512, 32832, 65568, 131088, 262152, 524292, 1048578, 2097153, 4456449, 4722689, 4784137,\n    8912898, 9453570, 9568322, 17825796, 18939908, 19137028, 23855109, 4672, 5120, 8704, 16640, 32896, 65600,\n    131104, 262160, 524296, 1048580, 2097154, 4726785, 4784145, 9469954, 9568386, 19005444, 19137540, 19169280,\n    19169285, 2336, 6144, 16896, 33024, 65664, 131136, 262176, 524304, 1048584, 2097156, 4194306, 4734977,\n    4784161, 5242881, 9502722, 9568514, 9584640, 10485762, 19138564, 19169286, 20971524, 23658501, 1168, 10240,\n    17408, 33280, 65792, 131200, 262208, 524320, 1048592, 2097160, 4194308, 4751361, 4784193, 4792320, 6291457,\n    8388609, 9568770, 12582914, 14286851, 19140612, 19398660, 27557894, 584, 12288, 33792, 66048, 131328, 262272,\n    524352, 1048608, 2097168, 4194312, 4784257, 9569282, 9699330, 14221315, 19144708, 19169292, 19922948,\n    23363589, 25165828, 292, 20480, 34816, 66560, 131584, 262400, 524416, 1048640, 2097184, 2396160, 4194320,\n    4784385, 4849665, 8388612, 8388624, 9570306, 9584643, 9961474, 16777217, 19152900, 19169300, 19173892, 146,\n    24576, 67584, 132096, 262656, 524544, 1048704, 2097216, 4194336, 4784641, 4980737, 8388616, 9572354,\n    12582913, 16777218, 16777223, 16777248, 19169316, 23068676, 26361862, 28704774, 7, 19, 37, 73, 145, 289, 577,\n    1153, 2305, 4609, 9217, 18433, 36865, 40960, 69632, 73729, 133120, 147457, 263168, 294913, 524800, 589825,\n    1048832, 1179649, 1198080, 2097280, 2359297, 4194368, 4785153, 9576450, 9584646, 11534338, 19169348,\n    19202052, 25165826, 25165831, 11, 38, 74, 290, 578, 1154, 2306, 4610, 9218, 18434, 36866, 49152, 73730,\n    135168, 147458, 264192, 294914, 525312, 589826, 1049088, 1179650, 2097408, 2359298, 4194432, 4718594,\n    4786177, 5767169, 8388615, 8388640, 9437185, 9584650, 13631490, 13778947, 16777224, 16777229, 16777238,\n    16777292, 16777364, 16777508, 16777796, 16778372, 16779524, 16781828, 16786436, 16795652, 16814084, 16850948,\n    16924676, 17072132, 17367044, 17956868, 19169412, 19267588, 20971521, 21495812, 27262980, 28606470, 13, 22,\n    76, 148, 580, 1156, 2308, 4612, 9220, 18436, 36868, 73732, 81920, 139264, 147460, 266240, 294916, 526336,\n    589828, 599040, 1049600, 1179652, 2097664, 2359300, 4194560, 4718596, 4788225, 6815745, 8388619, 8388646,\n    8388672, 8388682, 8388754, 8388898, 8389186, 8389762, 8390914, 8393218, 8397826, 8407042, 8425474, 8462338,\n    8536066, 8683522, 8978434, 9584658, 9601026, 10747906, 13107202, 16777230, 16777232, 16777237, 18874369,\n    19169540, 20971527, 23920645, 26214404, 14, 21, 26, 35, 44, 152, 296, 1160, 2312, 4616, 9224, 18440, 36872,\n    73736, 98304, 147464, 270336, 294920, 528384, 589832, 1050624, 1179656, 2098176, 2359304, 4194311, 4194323,\n    4194341, 4194377, 4194449, 4194593, 4194816, 4194881, 4195457, 4196609, 4198913, 4203521, 4212737, 4231169,\n    4268033, 4341761, 4489217, 4718600, 4792323, 5373953, 6553601, 8388627, 8388736, 9437188, 9437200, 9584674,\n    9633794, 16777253, 18874370, 18874375, 18874400, 19169796, 19660804, 21569541, 23887877, 25165838, 28459014,\n    29360135, 25, 52, 67, 88, 304, 592, 2320, 4624, 9232, 18448, 36880, 73744, 147472, 163840, 278528, 294928,\n    299520, 532480, 589840, 1052672, 1179664, 2099200, 2359312, 4194315, 4195328, 4718608, 4792325, 4800513,\n    8388622, 8388643, 8388864, 9437192, 9584706, 9586946, 12582919, 13180931, 13631489, 16777254, 16777280,\n    16777285, 19170308, 20185092, 20971533, 25165846, 27262978, 27262983, 41, 50, 69, 104, 131, 176, 608, 1184,\n    4640, 9248, 18464, 36896, 73760, 147488, 196608, 294944, 540672, 589856, 1056768, 1179680, 2101248, 2359328,\n    4194317, 4196352, 4718624, 4792329, 4816897, 8388630, 8388675, 8389120, 9584770, 9830402, 12582923, 13107201,\n    14352387, 16777244, 16777286, 16777344, 16777349, 18874376, 18874381, 18874390, 18874444, 18874516, 18874660,\n    18874948, 18875524, 18876676, 18878980, 18883588, 18892804, 18911236, 18948100, 19021828, 19171332, 19464196,\n    20054020, 20971541, 23068673, 23592964, 25165862, 26214402, 26214407, 28, 42, 49, 70, 82, 100, 133, 208, 259,\n    352, 1216, 2368, 9280, 18496, 36928, 73792, 147520, 149760, 294976, 327680, 557056, 589888, 1064960, 1179712,\n    2105344, 2359360, 4194325, 4194339, 4198400, 4718656, 4792337, 8388634, 8388739, 8389632, 9437191, 9437216,\n    9584898, 10092546, 12582931, 16777260, 16777350, 16777472, 16777477, 18874382, 18874384, 18874389, 20971557,\n    23068679, 23330820, 25165894, 28311556, 81, 134, 137, 164, 200, 261, 416, 515, 704, 2432, 4736, 18560, 36992,\n    73856, 147584, 295040, 393216, 589952, 1081344, 1179776, 2113536, 2359424, 4194329, 4194371, 4202496,\n    4718720, 4792353, 4915201, 8388650, 8388678, 8388867, 8390656, 9437195, 9437222, 9437248, 9437258, 9437330,\n    9437474, 9437762, 9438338, 9439490, 9441794, 9446402, 9455618, 9474050, 9510914, 9585154, 9732098, 10027010,\n    11796482, 12582947, 14155778, 14303235, 16777268, 16777478, 16777728, 16777733, 18874405, 19173376, 19173381,\n    19177476, 20971589, 21495809, 23666693, 25165958, 27262990, 31457287, 97, 138, 262, 265, 274, 328, 400, 517,\n    832, 1027, 1408, 4864, 9472, 37120, 73984, 74880, 147712, 295168, 590080, 655360, 1114112, 1179904, 2129920,\n    2359552, 4194345, 4194373, 4194435, 4210688, 4718848, 4792385, 5046273, 8388658, 8388742, 8389123, 8392704,\n    9437203, 9437312, 9585666, 11665410, 12582979, 16777300, 16777356, 16777734, 16778240, 16778245, 18874406,\n    18874432, 18874437, 19173382, 19185668, 20971653, 21495815, 23068685, 25166086, 26214414, 27262998, 27525124,\n    30408711, 56, 84, 98, 140, 161, 266, 518, 521, 530, 656, 800, 1029, 1664, 2051, 9728, 18944, 74240, 147968,\n    295424, 590336, 786432, 1180160, 2162688, 2359808, 4194353, 4194437, 4194563, 4227072, 4718599, 4718611,\n    4718629, 4718665, 4718737, 4718881, 4719104, 4719169, 4720897, 4723201, 4727809, 4737025, 4755457, 4792449,\n    4866049, 5013505, 5898241, 7077889, 8388690, 8388870, 8389635, 8396800, 9437198, 9437219, 9437440, 9568257,\n    12583043, 13631495, 13762562, 14229507, 16777316, 16777484, 16778246, 16779264, 16779269, 18874396, 18874438,\n    18874496, 18874501, 19136513, 20971781, 23068693, 25166342, 26214422, 27263014, 27561990, 28311554, 28311559,\n    28737542, 29884423 };\n\nstatic int cbest_26[1023] = {\n    1, 2, 33554467, 4, 50331698, 8, 25165849, 16, 32, 64, 46137391, 128, 256, 56623156, 512, 33554466, 16777233,\n    3, 1024, 28311578, 33554465, 6, 50331699, 5, 41943083, 12, 2048, 25165848, 50331696, 9, 24, 12582924,\n    33554471, 10, 4096, 14155789, 58720314, 48, 6291462, 54526006, 17, 33554475, 96, 8192, 3145731, 50331702, 20,\n    25165841, 25165851, 18, 33, 192, 29360157, 33554483, 50331682, 34, 16384, 27263003, 33554435, 35127330,\n    46137387, 46137390, 50331706, 384, 25165853, 40632357, 40, 65, 66, 768, 32768, 17563665, 25165833, 46137389,\n    56623158, 68, 1048577, 50331666, 36, 129, 1536, 2097154, 23068695, 33554531, 46137383, 48234541, 80, 130,\n    65536, 4194308, 28311579, 34078755, 47185966, 56623152, 56623157, 132, 3072, 8388616, 28311576, 136, 257,\n    14155788, 16777232, 33554595, 6144, 131072, 42336299, 45088808, 50331762, 50593842, 53870641, 160, 260,\n    25165881, 72, 258, 264, 513, 12288, 1572865, 3145730, 33554723, 272, 262144, 7077894, 23592983, 25296921,\n    33554464, 50331826, 57671733, 24576, 22544404, 25165913, 47710254, 61866041, 320, 514, 520, 1025, 6291460,\n    33554979, 34340899, 41943082, 46137407, 7, 528, 49152, 524288, 16777235, 46137359, 50331954, 544, 3145729,\n    3538947, 20971541, 25165977, 33554469, 35127331, 50331697, 54722614, 56623140, 13, 144, 516, 1026, 2049,\n    98304, 11272202, 12582920, 28311570, 33554470, 33555491, 41943081, 45350952, 56623164, 14, 640, 1040,\n    1048576, 14155785, 16777237, 46202927, 50332210, 50724914, 58720315, 25, 1056, 196608, 6291458, 12582925,\n    25166105, 27263001, 41943075, 44040233, 54526002, 54526007, 11, 26, 1028, 1088, 2050, 4097, 16777217,\n    23855127, 25165825, 25165840, 25165850, 28311582, 33554474, 33554479, 33556515, 46137455, 50331700, 51118130,\n    60489787, 62390329, 28, 49, 393216, 5636101, 6291463, 16777241, 29360156, 33554473, 35323938, 46137379,\n    50331703, 50332722, 56623124, 58720312, 50, 1280, 2080, 12582916, 12582926, 22675476, 25165845, 25166361,\n    25362457, 27263002, 28311562, 33554451, 33554491, 36700192, 39845925, 40632359, 41943087, 47185967, 50331650,\n    50331690, 54526004, 22, 52, 97, 288, 1032, 2052, 2112, 4098, 8193, 786432, 14155781, 14155791, 18350096,\n    25165852, 27361307, 29360153, 33554482, 33558563, 46137385, 46137519, 50331683, 56623154, 56655924, 58720306,\n    60817464, 64487487, 21, 56, 2176, 2097152, 9175048, 14680078, 23068693, 25559065, 33554434, 33554499,\n    46137386, 50331707, 50331710, 50333746, 62914622, 19, 100, 193, 1572864, 4587524, 13631501, 17563664,\n    25165832, 25165843, 25166873, 28311577, 33554433, 40632353, 40632356, 45088809, 50331704, 53870640, 55574583,\n    35, 44, 98, 104, 2056, 2560, 4100, 4160, 8194, 16385, 2293762, 8781832, 17661969, 25165855, 33554481,\n    33562659, 36372513, 45482024, 46137647, 48234543, 50331680, 53477425, 56623220, 58720318, 42, 70, 112, 385,\n    4224, 1146881, 4390916, 7340039, 11337738, 16777265, 23068694, 30408732, 33554487, 35127328, 46137388,\n    50331686, 50335794, 56623159, 57147444, 38, 41, 140, 194, 200, 386, 4352, 2195458, 25165865, 25167897,\n    28327962, 29360149, 29360159, 33554659, 41943099, 50331667, 50331730, 57671732, 58720298, 67, 88, 208, 280,\n    576, 769, 2064, 4104, 8196, 16386, 32769, 1097729, 3145735, 23068691, 27262995, 28311610, 33554443, 33554530,\n    33570851, 37224480, 40370213, 41943051, 46137382, 46137903, 46235695, 47710255, 48234537, 48234540, 50331664,\n    54525990, 56623284, 61866040, 69, 84, 134, 224, 560, 5120, 8320, 3145728, 3538946, 4194304, 6291470,\n    11534347, 16777297, 17039377, 20316178, 31457311, 33554529, 33554851, 34078754, 45088810, 46137381, 47185962,\n    50339890, 53870643, 54526014, 56623153, 61341752, 37, 76, 196, 268, 400, 772, 1120, 1537, 8448, 2097155,\n    7077892, 12582940, 15204366, 17301521, 18612240, 22544405, 23592981, 25165837, 25169945, 26935320, 34127907,\n    34603042, 46137399, 46333999, 47235118, 64749631, 81, 131, 176, 416, 536, 770, 2240, 4112, 8200, 8704, 16388,\n    32770, 65537, 1769473, 3145739, 4194309, 5668869, 14155784, 14155805, 17563667, 22741012, 25165835, 25165880,\n    27263007, 28311642, 28573722, 33554439, 33554594, 33555235, 33587235, 35127334, 35651617, 46138415, 50331890,\n    56098871, 56623412, 63799358, 65798204, 82, 133, 138, 168, 388, 448, 1072, 3073, 4480, 1048579, 6291478,\n    8388617, 9306120, 14163981, 16777361, 24117270, 25165873, 28835866, 29360141, 33554535, 33554593, 34078753,\n    34103331, 42074155, 42336298, 45350953, 47185964, 50331674, 50331763, 50348082, 50593843, 56623160, 57671735,\n    58720282, 60293179, 137, 152, 800, 1538, 1544, 2144, 8960, 10240, 16640, 6291456, 12582956, 23592982,\n    25165945, 25174041, 25296920, 27262987, 28311568, 30933020, 33556003, 39321638, 41943147, 42205227, 42385451,\n    46137403, 48234533, 50331760, 50332082, 51740723, 53739569, 54525974, 61866043, 262, 352, 832, 1152, 4128,\n    4288, 6145, 8208, 16392, 16896, 17920, 32772, 65538, 131073, 3145747, 3538945, 4194310, 4653060, 5636100,\n    7602183, 10158089, 12648460, 14155821, 17563669, 25165912, 28311571, 28311580, 28311706, 30670876, 33554547,\n    33554722, 33620003, 35127338, 37748775, 46139439, 47185958, 49283116, 50331746, 51380275, 56623668, 56672308,\n    57409588, 74, 161, 261, 276, 336, 392, 896, 3074, 8576, 17408, 35840, 1048581, 6291494, 6324230, 7077890,\n    8388608, 8388618, 11272200, 16777489, 23592979, 25166041, 33554599, 33557539, 40632365, 42336297, 44826665,\n    50331670, 50331827, 50332466, 50364466, 50593840, 50618418, 52428848, 56623136, 56623166, 57671729, 60489786,\n    62390328, 73, 259, 265, 304, 1540, 1600, 3088, 12289, 17152, 71680, 2097158, 2326530, 2818050, 3162115,\n    12058635, 12582988, 13467660, 14155780, 14155790, 14286861, 16777234, 17563649, 21168149, 22544400, 25165883,\n    25182233, 26214424, 33554563, 33554603, 34439203, 35323939, 41943211, 46137375, 47710250, 50331766, 50331824,\n    54591542, 56623142, 56721460, 58851386, 164, 266, 273, 524, 704, 776, 1664, 6146, 8224, 16400, 20480, 32776,\n    33280, 34304, 65540, 131074, 143360, 262145, 3145763, 5242885, 7077895, 10485770, 11370506, 11796491,\n    12582912, 13107212, 14155853, 14417933, 17563673, 19660819, 20971540, 25165976, 25166233, 28311583, 28311834,\n    33554721, 33554978, 33560611, 33685539, 34078759, 34340898, 35127346, 36388897, 36700193, 37355552, 42336291,\n    45088800, 45350954, 46137351, 46137406, 46141487, 50333234, 50606130, 54657078, 56624180, 58720378, 61603896,\n    162, 552, 672, 1792, 3076, 24577, 33792, 68608, 286720, 1048585, 1163265, 1409025, 6291526, 6553606, 8388620,\n    13631500, 14155777, 15466510, 16777745, 17170449, 23068679, 23617559, 23855125, 25165897, 25165969, 28311554,\n    33554727, 35127298, 35135522, 36175905, 40632373, 40636453, 46137355, 46137358, 46137423, 50331955, 50397234,\n    54526070, 56623108, 56623148, 59244602, 64487486, 321, 515, 521, 608, 3200, 6176, 12290, 34816, 137216,\n    573440, 1572867, 2097162, 3276803, 6291461, 6815750, 12583052, 15335438, 16777236, 20971537, 22675477,\n    23068703, 24641558, 25165915, 25166617, 25198617, 28311563, 29360189, 33554468, 33554539, 33554731, 33566755,\n    40632325, 41943339, 44040235, 46137357, 46137451, 47710252, 50331830, 50334770, 54722610, 54722615, 56623126,\n    56623141, 148, 274, 322, 522, 529, 784, 1048, 1408, 2304, 3328, 6148, 8256, 16416, 32784, 49153, 65544,\n    131076, 262146, 274432, 524289, 1146880, 3145795, 3407875, 4194316, 7340038, 14155917, 17567761, 18350097,\n    18677776, 25165917, 25166104, 25296913, 25296923, 25309209, 27263000, 27263035, 27295771, 28311560, 28311574,\n    28312090, 28336154, 28704794, 29425693, 32899102, 33554739, 33554977, 33555490, 33816611, 34078763, 34340897,\n    37093408, 41943080, 42336303, 45088812, 46137405, 46145583, 50331794, 50331834, 50331938, 50593846, 53870645,\n    55574582, 56229943, 56623165, 56625204, 58720442, 62390331, 63701054, 65929276, 518, 545, 1104, 1344, 3080,\n    3584, 24578, 40960, 66560, 548864, 1048593, 3670019, 6291590, 12582922, 16777216, 16778257, 22544406,\n    23855126, 25165824, 25165885, 25165905, 25166097, 25167385, 27328539, 27361305, 31195164, 33579043, 35192866,\n    39583782, 40632355, 44138537, 45482025, 46202923, 46202926, 47710246, 50331952, 50332211, 50337842, 50462770,\n    50724915, 51904563, 53477424, 54526000, 54526134, 60424251, 61866033, 145, 328, 517, 532, 546, 1027, 1216,\n    1552, 6400, 12292, 12352, 67584, 98305, 1097728, 1572869, 2097170, 3145734, 6733830, 11272203, 12582921,\n    12583180, 14155787, 14680076, 16777225, 16777239, 16777240, 25165979, 25231385, 25303065, 28360730, 29360221,\n    29622301, 33554787, 33554947, 33554987, 35127329, 35258402, 36700194, 38633511, 39452710, 39845927, 40697893,\n    41943074, 41943079, 41943595, 42139691, 43384874, 44040232, 46137515, 46137583, 48234557, 50331958, 50593826,\n    50774066, 51216434, 51642419, 53870625, 54526003, 54722612, 54747190, 59424826, 15, 641, 6152, 6656, 49154,\n    69632, 131080, 4194324, 11534346, 14156045, 17596433, 25168921, 25296925, 25362456, 27263067, 33556514,\n    35389474, 41943073, 50331701, 50331770, 50332194, 50343986, 50593850, 60817466 };\n\nstatic int cbest_27[1023] = {\n    1, 2, 67108883, 4, 100663322, 8, 50331661, 16, 32, 92274709, 64, 128, 113246233, 256, 512, 67108882,\n    33554441, 3, 1024, 67108881, 123731999, 6, 100663323, 5, 2048, 83886103, 12, 50331660, 100663320, 9, 24,\n    4096, 25165830, 67108887, 109051928, 10, 117440542, 48, 12582915, 50331657, 17, 8192, 54525964, 67108891,\n    100663314, 128974876, 18, 96, 67108867, 92274711, 100663326, 20, 50331663, 73400338, 92274708, 113246232, 33,\n    192, 16384, 27262982, 58720271, 34, 50331653, 92274705, 36, 384, 36700169, 46137354, 100663306, 40, 65,\n    32768, 13631491, 56623116, 67108915, 66, 768, 96469012, 113246235, 68, 23068677, 64487438, 72, 129, 1536,\n    65536, 67108947, 80, 4194305, 28311558, 73924626, 100663354, 132, 3072, 8388610, 48234506, 50331677,\n    85458967, 123731998, 130, 136, 257, 131072, 16777220, 67109011, 69206035, 78643217, 144, 6144, 33554440,\n    92274717, 100663386, 160, 14155779, 36962313, 50331693, 92274693, 258, 264, 513, 12288, 262144, 24117253,\n    67109139, 101711898, 113246225, 272, 6291457, 12582914, 32243719, 100663450, 109838360, 113246237, 288,\n    24576, 50331725, 67108880, 260, 320, 514, 1025, 524288, 50855949, 61865999, 67109395, 528, 49152, 25165828,\n    70254611, 74186770, 83886102, 92274741, 100663578, 106430491, 123731995, 7, 544, 33554443, 50331789,\n    79167505, 113246217, 123731997, 516, 576, 1026, 2049, 98304, 1048576, 12582913, 41943051, 54525965, 54919180,\n    67108885, 67109907, 73400339, 83886099, 85590039, 100663321, 109051930, 13, 640, 33554433, 50331656,\n    67108886, 83886101, 92274773, 92536853, 100663834, 109051929, 14, 26, 1056, 196608, 13631490, 33554445,\n    50331649, 50331917, 83230736, 102236186, 117440543, 25, 52, 520, 1028, 1088, 2050, 4097, 2097152, 6815745,\n    25165826, 25165831, 37093385, 67110931, 88080406, 92274707, 98041876, 123731991, 128974878, 11, 104, 1152,\n    393216, 23068676, 27262980, 50331659, 50331662, 58720269, 67108875, 67108890, 67108895, 79691792, 92274837,\n    94371861, 100663298, 100663315, 100663318, 100663324, 100664346, 103809050, 113246265, 117440538, 128974877,\n    22, 28, 49, 208, 1280, 11534338, 27459590, 39845896, 46137355, 50332173, 58720270, 67108866, 67108889,\n    92274710, 96469013, 100663312, 100663327, 113246234, 117440540, 44, 416, 1032, 2052, 2112, 4098, 8193,\n    786432, 4194304, 5767169, 19922948, 44040203, 46137352, 50331652, 51118093, 54525960, 56623117, 67108865,\n    67108899, 67112979, 109903896, 113377305, 120324126, 125829148, 128974872, 19, 88, 97, 832, 2176, 9961474,\n    13631489, 36700168, 70516755, 92274965, 100665370, 106692635, 113246297, 121634847, 21, 38, 50, 56, 176,\n    1664, 2304, 1572864, 4980737, 18350084, 29360135, 33554457, 50332685, 51904525, 64487439, 73924627, 92274704,\n    109051920, 123731983, 76, 193, 352, 1040, 2056, 2560, 3328, 4100, 8194, 16385, 9175042, 27262978, 27262983,\n    41615368, 49020938, 54525966, 58720267, 64487436, 67108871, 67117075, 73400336, 83886111, 90177558,\n    100663307, 109051932, 114294809, 117440534, 35, 98, 152, 194, 704, 4224, 6656, 3145728, 4587521, 13729795,\n    14155778, 50331669, 62914574, 67108914, 67108979, 69992467, 81788944, 83886087, 89128982, 92275221, 96469014,\n    100663304, 100663338, 100667418, 113246361, 115343385, 37, 42, 70, 112, 304, 385, 1408, 4352, 13312,\n    12582919, 24117252, 28311556, 33554473, 48234507, 50331655, 50333709, 54525956, 67108913, 80740368, 85655575,\n    92274713, 92667925, 95420437, 128974868, 41, 140, 608, 2064, 2816, 4104, 4608, 8196, 16386, 26624, 32769,\n    6291456, 7077889, 8388608, 25165838, 32243718, 40894472, 48234504, 54951948, 56623112, 67109075, 67125267,\n    69599251, 102367258, 127926300, 67, 100, 280, 388, 769, 1216, 5120, 5632, 53248, 12058626, 40370184,\n    45088779, 50331673, 50331676, 67108946, 92275733, 93061141, 96469008, 100663310, 100671514, 104071194,\n    109051912, 113246224, 113246489, 123732031, 131596317, 69, 74, 84, 224, 386, 560, 2432, 8448, 11264, 106496,\n    12582923, 20447236, 23068673, 31457287, 33554505, 36700171, 44564491, 50335757, 58720263, 60162063, 64487434,\n    67108919, 67108945, 67109267, 69402643, 73400342, 92274719, 100663355, 100663418, 113246236, 117440526,\n    123797535, 73, 196, 1120, 1537, 2080, 4112, 4864, 8200, 8704, 16388, 22528, 32770, 65537, 212992, 6029313,\n    14155777, 20185092, 24510469, 25165846, 56623118, 67108923, 67141651, 78643219, 83886135, 85458966, 92274695,\n    99352596, 100663346, 100663352, 102105114, 113246239, 81, 134, 770, 776, 2240, 9216, 9728, 45056, 425984,\n    10223618, 20807684, 28311554, 28311559, 34603017, 36700161, 36962312, 46137346, 50331689, 50331692, 50331709,\n    67109010, 67109651, 69206034, 73973778, 78643216, 91226134, 92274689, 92276757, 100663514, 100679706,\n    113246227, 113246745, 120455198, 123732063, 128974860, 133, 148, 168, 448, 3073, 4480, 10240, 19456, 90112,\n    851968, 8388611, 10092546, 12582912, 12582931, 23068679, 32243717, 33554569, 35651593, 36700173, 46137358,\n    50339853, 51183629, 56623108, 63963150, 67108931, 67108951, 70647827, 71303186, 73400346, 73924624, 74186771,\n    82837520, 85458963, 92274701, 92274716, 100663387, 101908506, 106430490, 113442841, 114819097, 123731994,\n    124256287, 131, 137, 200, 1538, 4128, 8208, 8960, 16392, 16896, 32772, 38912, 65538, 131073, 180224, 1703936,\n    5111809, 13631495, 16121859, 16777216, 16777221, 18481156, 25165862, 27262990, 27475974, 39321608, 50331679,\n    50331757, 52035597, 54525980, 67108955, 67110419, 67174419, 73400322, 75497489, 83886167, 85458965, 89653270,\n    92274692, 100663358, 100663378, 100663384, 100663706, 109051960, 109936664, 113246216, 123731996, 124780575,\n    82, 138, 145, 268, 772, 1552, 6145, 17408, 17920, 77824, 360448, 3407872, 4194307, 5046273, 42729483,\n    50331685, 50331721, 50331724, 61865997, 64487430, 67109009, 67109138, 69206033, 81264656, 84410391, 89391126,\n    92274725, 92278805, 100696090, 101711899, 101810202, 109838362, 113246209, 113246229, 113247257, 113639449,\n    117440574, 123732127, 146, 161, 296, 336, 392, 896, 3074, 18432, 35840, 155648, 720896, 6815744, 9240578,\n    12582947, 19660804, 33554697, 41418760, 50331853, 50348045, 50855948, 51052557, 61865998, 67108995, 67109015,\n    67111955, 70123539, 84934679, 92274743, 100663451, 100664090, 109838361, 162, 259, 265, 1540, 4160, 8224,\n    12289, 16400, 20480, 32776, 65540, 71680, 131074, 262145, 311296, 1441792, 13631499, 16777222, 24117249,\n    25165824, 25165894, 25427974, 27262998, 45613067, 50331695, 54525996, 54919181, 58720287, 67109019, 67239955,\n    83230737, 83886231, 92274737, 97189908, 100663370, 100663390, 100663442, 104857627, 109051992, 109314072,\n    113246219, 128974908, 129007644, 266, 273, 536, 3104, 6146, 33792, 143360, 622592, 2883584, 4194309, 4620289,\n    9830402, 12713987, 31981575, 32243715, 33554442, 36986889, 40632328, 41943049, 48234498, 49676298, 50331717,\n    50331785, 50331788, 50332045, 50954253, 53215245, 67109137, 67109394, 67115027, 73924630, 79364113, 88080407,\n    92282901, 96469020, 98041877, 100664858, 100728858, 101711896, 109576216, 113248281, 117440606, 123731987,\n    123731990, 123731993, 123732255, 262, 274, 289, 400, 592, 672, 1792, 3076, 24577, 34816, 286720, 1245184,\n    5767168, 8388614, 10403842, 12582979, 20709380, 20971525, 23068685, 33554953, 36700185, 36962315, 41943050,\n    44826635, 46137370, 48234510, 50364429, 54657036, 67109043, 67109123, 69664787, 70254610, 73400370, 79167507,\n    79691793, 92274740, 92274775, 92274805, 92700693, 96468996, 100663448, 100663579, 102432794, 104202266,\n    113246264, 117964830, 132907037, 261, 276, 290, 321, 515, 1544, 8256, 12290, 16416, 32784, 36864, 65544,\n    131076, 262146, 524289, 573440, 2490368, 4915201, 13631488, 13631507, 13737987, 24117255, 25165958, 27263014,\n    33554432, 35127305, 44695563, 50331727, 50332429, 50855945, 50905101, 54526028, 54788108, 56623132, 58720303,\n    61865995, 67109147, 67121171, 67371027, 69206039, 69632019, 73465874, 77594641, 78643221, 79167504, 83886097,\n    83886359, 88473622, 92274769, 93126677, 100663434, 100663454, 100663570, 100666394, 109052056, 111149080,\n    128974940, 129237020, 292, 322, 529, 784, 1072, 6148, 6208, 40960, 49153, 1146880, 4194313, 4980736, 6291459,\n    20316164, 25165829, 27328518, 28311566, 29360134, 30932999, 33554444, 36962305, 50331648, 50331781, 50331916,\n    54968332, 60227599, 67108884, 67109393, 67109906, 73924634, 75169810, 83886098, 85590038, 92274901, 92291093,\n    92536855, 100794394, 109051931, 113246249, 113250329, 113377304, 117440670, 119537694, 123732511, 129499164,\n    164, 324, 518, 530, 545, 1184, 1344, 3080, 3584, 24578, 67584, 2293760, 8388618, 10354690, 12583043,\n    14155783, 14680067, 23068693, 27394054, 33554437, 33555465, 36700201, 36732937, 36962317, 37093384, 39845897,\n    46137386, 50333197, 50397197, 55574540, 64487454, 67109171, 67109379, 67133459, 70254609, 73400402, 73924610,\n    74186768, 83099664, 83230738, 83886100, 85458975, 92274772, 92536852, 92635157, 100663576, 100663835,\n    100669466, 101711890, 102170650, 106430489, 106692634, 113246267, 113246296, 121634846, 123830303, 124518431,\n    128974874, 517, 532, 16448, 69632, 524290, 1048577, 4587520, 27262976, 46137353, 50331658, 50331741,\n    54525961, 56623148, 58720268, 58982415, 67109143, 67109203, 67633171, 78643225, 100663482, 102236187,\n    106954779, 123731982 };\n\nstatic int cbest_28[1023] = {\n    1, 2, 134217732, 4, 67108866, 8, 33554433, 16, 32, 150994948, 64, 128, 75497474, 256, 512, 37748737, 1024,\n    2048, 4096, 153092100, 8192, 16384, 32768, 76546050, 65536, 131072, 262144, 38273025, 524288, 1048576,\n    2097152, 4194304, 153354244, 8388608, 16777216, 33554432, 76677122, 67108864, 3, 134217728, 134217733, 5,\n    38338561, 67108867, 134217734, 6, 9, 18, 36, 72, 144, 288, 576, 1152, 2304, 4608, 9216, 18432, 36864, 73728,\n    147456, 294912, 589824, 1179648, 2359296, 4718592, 9437184, 18874368, 37748736, 201326598, 10, 17, 33554435,\n    67108870, 75497472, 134217740, 167772165, 12, 33, 33554437, 67108874, 100663299, 134217748, 150994944,\n    150994949, 20, 34, 65, 33554441, 67108882, 134217764, 150994950, 24, 66, 129, 33554449, 67108898, 75497475,\n    134217796, 153387012, 218103814, 40, 68, 130, 257, 33554465, 67108930, 134217860, 150994956, 184549381,\n    209715206, 48, 132, 258, 513, 33554497, 67108994, 75497478, 134217988, 150994964, 80, 136, 260, 514, 1025,\n    33554561, 37748739, 67109122, 75497482, 109051907, 134218244, 150994980, 171966469, 96, 264, 516, 1026, 2049,\n    33554689, 37748741, 67109378, 75497490, 104857603, 134218756, 150995012, 160, 272, 520, 1028, 2050, 4097,\n    33554945, 37748745, 38273024, 67109890, 75497506, 76546048, 76693506, 134219780, 150995076, 153092096,\n    153092101, 226492422, 192, 528, 1032, 2052, 4098, 8193, 19136512, 33555457, 37748753, 67110914, 75497538,\n    134221828, 150995204, 153092102, 320, 544, 1040, 2056, 4100, 8194, 16385, 9568256, 33556481, 37748769,\n    67112962, 75497602, 134225924, 150995460, 188743685, 220200966, 384, 1056, 2064, 4104, 8196, 16386, 32769,\n    4784128, 33558529, 37748801, 67117058, 75497730, 76546051, 134234116, 150995972, 153092108, 186646533, 640,\n    1088, 2080, 4112, 8200, 16388, 32770, 65537, 2392064, 33562625, 37748865, 67125250, 75497986, 113246211,\n    134250500, 150996996, 153092116, 210763782, 768, 2112, 4128, 8208, 16392, 32772, 65538, 131073, 1196032,\n    33570817, 37748993, 38346753, 67141634, 75498498, 76546054, 134283268, 150999044, 153092132, 1280, 2176,\n    4160, 8224, 16400, 32776, 65540, 131074, 262145, 598016, 33587201, 37749249, 67174402, 75499522, 76546058,\n    110100483, 134348804, 151003140, 153092164, 1536, 4224, 8256, 16416, 32784, 65544, 131076, 262146, 299008,\n    524289, 33619969, 37749761, 38273027, 67239938, 75501570, 76546066, 134479876, 151011332, 153092228,\n    172490757, 228589574, 2560, 4352, 8320, 16448, 32800, 65552, 131080, 149504, 262148, 524290, 1048577,\n    33685505, 37750785, 38273029, 67371010, 75505666, 76546082, 105381891, 134742020, 151027716, 153092356,\n    227540998, 3072, 8448, 16512, 32832, 65568, 74752, 131088, 262152, 524292, 1048578, 2097153, 33816577,\n    37752833, 38273033, 67633154, 75513858, 76546114, 135266308, 151060484, 153092612, 190840837, 5120, 8704,\n    16640, 32896, 37376, 65600, 131104, 262160, 524296, 1048580, 2097154, 4194305, 34078721, 37756929, 38273041,\n    68157442, 75530242, 76546178, 136314884, 151126020, 153093124, 153354240, 153354245, 6144, 16896, 18688,\n    33024, 65664, 131136, 262176, 524304, 1048584, 2097156, 4194306, 8388609, 34603009, 37765121, 38273057,\n    69206018, 75563010, 76546306, 76677120, 138412036, 151257092, 153094148, 153354246, 153391108, 189267973,\n    9344, 10240, 17408, 33280, 65792, 131200, 262208, 524320, 1048592, 2097160, 4194308, 8388610, 16777217,\n    35651585, 37781505, 38273089, 38338560, 71303170, 75628546, 76546562, 114294787, 142606340, 151519236,\n    153096196, 220463110, 4672, 12288, 33792, 66048, 131328, 262272, 524352, 1048608, 2097168, 4194312, 8388612,\n    16777218, 37814273, 38273153, 75759618, 76547074, 113770499, 152043524, 153100292, 153354252, 186908677,\n    2336, 20480, 34816, 66560, 131584, 262400, 524416, 1048640, 2097184, 4194320, 8388616, 16777220, 19169280,\n    33554434, 37879809, 38273281, 41943041, 76021762, 76548098, 76677123, 83886082, 153108484, 153354260,\n    167772164, 1168, 24576, 67584, 132096, 262656, 524544, 1048704, 2097216, 4194336, 8388624, 16777224,\n    33554436, 38010881, 38273537, 50331649, 67108865, 76550146, 100663298, 153124868, 153354276, 155189252,\n    210894854, 229638150, 584, 40960, 69632, 133120, 263168, 524800, 1048832, 2097280, 4194368, 8388640, 9584640,\n    16777232, 33554440, 38274049, 76554242, 76677126, 77594626, 153157636, 153354308, 159383556, 201326596, 292,\n    49152, 135168, 264192, 525312, 1049088, 2097408, 4194432, 8388672, 16777248, 33554448, 38275073, 38797313,\n    67108868, 67108880, 76562434, 76677130, 76695554, 79691778, 110231555, 134217729, 153223172, 153354372,\n    228851718, 146, 81920, 139264, 266240, 526336, 1049600, 2097664, 4194560, 4792320, 8388736, 16777280,\n    33554464, 38277121, 39845889, 67108872, 76578818, 76677138, 100663297, 134217730, 134217735, 134217760,\n    153354500, 184549380, 191365125, 7, 19, 37, 73, 145, 289, 577, 1153, 2305, 4609, 9217, 18433, 36865, 73729,\n    98304, 147457, 270336, 294913, 528384, 589825, 1050624, 1179649, 2098176, 2359297, 4194816, 4718593, 8388864,\n    9437185, 16777344, 18874369, 33554496, 38281217, 38338563, 76611586, 76677154, 92274690, 153354756,\n    153616388, 172556293, 191102981, 201326594, 201326599, 227672070, 11, 38, 74, 290, 578, 1154, 2306, 4610,\n    9218, 18434, 36866, 73730, 147458, 163840, 278528, 294914, 532480, 589826, 1052672, 1179650, 2099200,\n    2359298, 2396160, 4195328, 4718594, 8389120, 9437186, 16777472, 18874370, 33554560, 37748738, 38289409,\n    38338565, 46137345, 67108871, 67108896, 75497473, 76677186, 105447427, 109051906, 134217736, 134217741,\n    134217750, 134217804, 134217876, 134218020, 134218308, 134218884, 134220036, 134222340, 134226948, 134236164,\n    134254596, 134291460, 134365188, 134512644, 134807556, 135397380, 136577028, 138936324, 143654916, 153355268,\n    154140676, 167772161, 171966468, 218103812, 13, 22, 76, 148, 580, 1156, 2308, 4612, 9220, 18436, 36868,\n    73732, 147460, 196608, 294916, 540672, 589828, 1056768, 1179652, 2101248, 2359300, 4196352, 4718596, 8389632,\n    9437188, 16777728, 18874372, 33554688, 37748740, 38305793, 38338569, 54525953, 67108875, 67108902, 67108928,\n    67108938, 67109010, 67109154, 67109442, 67110018, 67111170, 67113474, 67118082, 67127298, 67145730, 67182594,\n    67256322, 67403778, 67698690, 68288514, 69468162, 71827458, 76677250, 76808194, 85983234, 104857602,\n    114819075, 134217742, 134217744, 134217749, 150994945, 153356292, 167772167, 209715204, 14, 21, 26, 35, 44,\n    152, 296, 1160, 2312, 4616, 9224, 18440, 36872, 73736, 147464, 294920, 327680, 557056, 589832, 1064960,\n    1179656, 1198080, 2105344, 2359304, 4198400, 4718600, 8390656, 9437192, 16778240, 18874376, 33554439,\n    33554451, 33554469, 33554505, 33554577, 33554721, 33554944, 33555009, 33555585, 33556737, 33559041, 33563649,\n    33572865, 33591297, 33628161, 33701889, 33849345, 34144257, 34734081, 35913729, 37748744, 38338577, 42991617,\n    52428801, 67108883, 67108992, 75497476, 75497488, 76677378, 77070338, 134217765, 150994946, 150994951,\n    150994976, 153358340, 157286404, 201326606, 234881031, 25, 52, 67, 88, 304, 592, 2320, 4624, 9232, 18448,\n    36880, 73744, 147472, 294928, 393216, 589840, 1081344, 1179664, 2113536, 2359312, 4202496, 4718608, 8392704,\n    9437200, 16779264, 18874384, 33554443, 33555456, 37748752, 38338593, 38347777, 38404097, 67108878, 67108899,\n    67109120, 75497480, 76677634, 100663303, 109051905, 114425859, 134217766, 134217792, 134217797, 153362436,\n    153387008, 153387013, 161480708, 167772173, 189333509, 201326614, 218103810, 218103815, 41, 50, 69, 104, 131,\n    176, 608, 1184, 4640, 9248, 18464, 36896, 73760, 147488, 294944, 589856, 599040, 655360, 1114112, 1179680,\n    2129920, 2359328, 4210688, 4718624, 8396800, 9437216, 16781312, 18874400, 33554445, 33556480, 37748768,\n    38338625, 38535169, 67108886, 67108931, 67109376, 76678146, 78643202, 100663307, 104857601, 134217756,\n    134217798, 134217856, 134217861, 150994952, 150994957, 150994966, 150995020, 150995092, 150995236, 150995524,\n    150996100, 150997252, 150999556, 151004164, 151013380, 151031812, 151068676, 151142404, 151289860, 151584772,\n    152174596, 153370628, 153387014, 155713540, 160432132, 167772181, 184549377, 188743684, 201326630, 209715202,\n    209715207, 28, 42, 49, 70, 82, 100, 133, 208, 259, 352, 1216, 2368, 9280, 18496, 36928, 73792, 147520,\n    294976, 589888, 786432, 1179712, 2162688, 2359360, 4227072, 4718656, 8404992, 9437248, 16785408, 18874432,\n    33554453, 33554467, 33558528, 37748800, 38338689, 67108890, 67108995, 67109888, 75497479, 75497504, 76679170,\n    76693504, 80740354, 100663315, 113836035, 134217772, 134217862, 134217984, 134217989, 150994958, 150994960,\n    150994965, 167772197, 184549383, 186646532, 201326662, 220495878, 226492420, 229900294, 81, 134, 137, 164,\n    200, 261, 416, 515, 704, 4736, 36992, 73856, 147584, 295040, 299520, 1179776, 2228224, 2359424, 8421376,\n    9437312, 18874496, 33554457, 33554499, 33562624, 37748864, 67108906, 67108934, 67109123, 67110912, 75497483,\n    75497510, 75497536, 75497546, 75497762, 75498050, 75499778, 75502082, 75506690, 75515906, 75534338, 75571202,\n    75644930, 75792386, 76087298, 76681218, 77856770, 80216066, 94371842, 100663331, 113246210, 134217780,\n    134217990, 134218240, 150994981, 153419780, 167772229, 171966465, 201326726, 218103822, 229769222, 251658247 };\n\nstatic int cbest_29[1023] = {\n    1, 2, 268435458, 4, 134217729, 8, 16, 335544322, 32, 64, 167772161, 128, 256, 512, 352321538, 1024, 2048,\n    4096, 176160769, 8192, 16384, 32768, 65536, 356515842, 131072, 262144, 524288, 1048576, 178257921, 2097152,\n    4194304, 8388608, 16777216, 33554432, 357564418, 67108864, 134217728, 268435456, 3, 178782209, 268435459, 5,\n    10, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 10240, 20480, 40960, 81920, 163840, 327680, 655360, 1310720,\n    2621440, 5242880, 10485760, 20971520, 41943040, 83886080, 167772160, 6, 9, 134217731, 268435462, 335544320,\n    402653187, 17, 134217733, 268435466, 335544323, 12, 18, 33, 134217737, 268435474, 34, 65, 134217745,\n    268435490, 335544326, 469762051, 24, 36, 66, 129, 134217761, 167772163, 268435522, 335544330, 357826562,\n    436207619, 68, 130, 257, 134217793, 167772165, 176160768, 268435586, 335544338, 352321536, 48, 72, 132, 258,\n    513, 88080384, 134217857, 167772169, 268435714, 335544354, 352321539, 136, 260, 514, 1025, 44040192,\n    134217985, 167772177, 268435970, 335544386, 503316483, 96, 144, 264, 516, 1026, 2049, 22020096, 134218241,\n    167772193, 268436482, 335544450, 352321542, 486539267, 272, 520, 1028, 2050, 4097, 11010048, 134218753,\n    167772225, 268437506, 335544578, 352321546, 192, 288, 528, 1032, 2052, 4098, 8193, 5505024, 134219777,\n    167772289, 176160771, 178913281, 268439554, 335544834, 352321554, 444596227, 544, 1040, 2056, 4100, 8194,\n    16385, 2752512, 134221825, 167772417, 176160773, 268443650, 335545346, 352321570, 384, 576, 1056, 2064, 4104,\n    8196, 16386, 32769, 1376256, 134225921, 167772673, 176160777, 268451842, 335546370, 352321602, 356515840,\n    520093699, 1088, 2080, 4112, 8200, 16388, 32770, 65537, 688128, 134234113, 167773185, 176160785, 178257920,\n    268468226, 335548418, 352321666, 356515843, 511705091, 768, 1152, 2112, 4128, 8208, 16392, 32772, 65538,\n    131073, 344064, 134250497, 167774209, 176160801, 268500994, 335552514, 352321794, 2176, 4160, 8224, 16400,\n    32776, 65540, 131074, 172032, 262145, 89128960, 134283265, 167776257, 176160833, 268566530, 335560706,\n    352322050, 356515846, 490733571, 1536, 2304, 4224, 8256, 16416, 32784, 65544, 86016, 131076, 262146, 524289,\n    134348801, 167780353, 176160897, 268697602, 335577090, 352322562, 356515850, 4352, 8320, 16448, 32800, 43008,\n    65552, 131080, 262148, 524290, 1048577, 44564480, 134479873, 167788545, 176161025, 268959746, 335609858,\n    352323586, 356515858, 357892098, 3072, 4608, 8448, 16512, 21504, 32832, 65568, 131088, 262152, 524292,\n    1048578, 2097153, 134742017, 167804929, 176161281, 178257923, 269484034, 335675394, 352325634, 356515874,\n    446693379, 528482307, 8704, 10752, 16640, 32896, 65600, 131104, 262160, 524296, 1048580, 2097154, 4194305,\n    22282240, 135266305, 167837697, 176161793, 178257925, 270532610, 335806466, 352329730, 356515906, 524288003,\n    5376, 6144, 9216, 16896, 33024, 65664, 131136, 262176, 524304, 1048584, 2097156, 4194306, 8388609, 136314881,\n    167903233, 176162817, 178257929, 272629762, 336068610, 352337922, 356515970, 2688, 17408, 33280, 65792,\n    131200, 262208, 524320, 1048592, 2097160, 4194308, 8388610, 11141120, 16777217, 138412033, 168034305,\n    176164865, 178257937, 276824066, 336592898, 352354306, 356516098, 357564416, 513802243, 1344, 12288, 18432,\n    33792, 66048, 131328, 262272, 524352, 1048608, 2097168, 4194312, 8388612, 16777218, 33554433, 142606337,\n    168296449, 176168961, 178257953, 285212674, 337641474, 352387074, 356516354, 357564419, 672, 34816, 66560,\n    131584, 262400, 524416, 1048640, 2097184, 4194320, 5570560, 8388616, 16777220, 33554434, 67108865, 150994945,\n    168820737, 176177153, 178257985, 178782208, 301989890, 339738626, 352452610, 356516866, 336, 24576, 36864,\n    67584, 132096, 262656, 524544, 1048704, 2097216, 4194336, 8388624, 16777224, 33554436, 67108866, 169869313,\n    176193537, 178258049, 178946049, 343932930, 352583682, 356517890, 357564422, 491782147, 168, 69632, 133120,\n    263168, 524800, 1048832, 2097280, 2785280, 4194368, 8388640, 16777232, 33554440, 67108868, 134217730,\n    171966465, 176226305, 178258177, 201326593, 352845826, 356519938, 357564426, 402653186, 532676611, 84, 49152,\n    73728, 135168, 264192, 525312, 1049088, 2097408, 4194432, 8388672, 16777248, 33554448, 67108872, 89391104,\n    134217732, 176291841, 178258433, 268435457, 353370114, 356524034, 357564434, 369098754, 530579459, 42,\n    139264, 266240, 526336, 1049600, 1392640, 2097664, 4194560, 8388736, 16777280, 33554464, 67108880, 134217736,\n    176422913, 178258945, 184549377, 268435464, 354418690, 356532226, 357564450, 11, 21, 41, 81, 161, 321, 641,\n    1281, 2561, 5121, 10241, 20481, 40961, 81921, 98304, 147456, 163841, 270336, 327681, 528384, 655361, 1050624,\n    1310721, 2098176, 2621441, 4194816, 5242881, 8388864, 10485761, 16777344, 20971521, 33554496, 41943041,\n    67108896, 83886081, 134217744, 176685057, 178259969, 178782211, 268435460, 356548610, 357564482, 402653185,\n    447217667, 469762050, 525336579, 7, 22, 82, 162, 322, 642, 1282, 2562, 5122, 10242, 20482, 40962, 81922,\n    163842, 278528, 327682, 532480, 655362, 696320, 1052672, 1310722, 2099200, 2621442, 4195328, 5242882,\n    8389120, 10485762, 16777472, 20971522, 33554560, 41943042, 44695552, 67108928, 83886082, 134217760,\n    167772162, 177209345, 178262017, 178782213, 234881025, 268435463, 268435478, 268435498, 268435538, 268435618,\n    268435778, 268436098, 268436738, 268438018, 268440578, 268445698, 268455938, 268476418, 268517378, 268599298,\n    268763138, 269090818, 269746178, 271056898, 273678338, 278921218, 289406978, 310378498, 335544321, 356581378,\n    357564546, 360710146, 436207618, 14, 44, 164, 324, 644, 1284, 2564, 5124, 10244, 20484, 40964, 81924, 163844,\n    196608, 294912, 327684, 540672, 655364, 1056768, 1310724, 2101248, 2621444, 4196352, 5242884, 8389632,\n    10485764, 16777728, 20971524, 33554688, 41943044, 67108992, 83886084, 134217739, 134217749, 134217769,\n    134217792, 134217809, 134217889, 134218049, 134218369, 134219009, 134220289, 134222849, 134227969, 134238209,\n    134258689, 134299649, 134381569, 134545409, 134873089, 135528449, 136839169, 139460609, 144703489, 155189249,\n    167772164, 178266113, 178782217, 218103809, 268435467, 268435472, 335544328, 356646914, 357564674, 13, 19,\n    28, 88, 328, 648, 1288, 2568, 5128, 10248, 20488, 40968, 81928, 163848, 327688, 348160, 557056, 655368,\n    1064960, 1310728, 2105344, 2621448, 4198400, 5242888, 8390656, 10485768, 16778240, 20971528, 33554944,\n    41943048, 67109120, 83886088, 134217735, 134217856, 167772168, 178274305, 178782225, 180355073, 268435475,\n    268435488, 335544324, 356777986, 357564930, 385875970, 402653191, 469762049, 514326531, 26, 35, 56, 176, 656,\n    1296, 2576, 5136, 10256, 20496, 40976, 81936, 163856, 327696, 393216, 589824, 655376, 1081344, 1310736,\n    2113536, 2621456, 4202496, 5242896, 8392704, 10485776, 16779264, 20971536, 22347776, 33555456, 41943056,\n    67109376, 83886096, 134217984, 167772176, 178290689, 178782241, 268435470, 268435491, 268435520, 335544327,\n    335544342, 335544362, 335544402, 335544482, 335544642, 335544962, 335545602, 335546882, 335549442, 335554562,\n    335564802, 335585282, 335626242, 335708162, 335872002, 336199682, 336855042, 338165762, 340787202, 346030082,\n    357040130, 357565442, 357826560, 357908482, 377487362, 402653195, 436207617, 503316482, 25, 37, 52, 67, 112,\n    352, 1312, 2592, 5152, 10272, 20512, 40992, 81952, 163872, 174080, 327712, 655392, 1114112, 1310752, 2129920,\n    2621472, 4210688, 5242912, 8396800, 10485792, 16781312, 20971552, 33556480, 41943072, 67109888, 83886112,\n    134217741, 134217747, 134218240, 167772192, 178323457, 178782273, 192937985, 268435523, 268435584, 335544331,\n    335544336, 357566466, 357826563, 402653203, 486539266, 534773763, 38, 69, 74, 104, 131, 224, 704, 2624, 5184,\n    10304, 20544, 41024, 81984, 163904, 327744, 655424, 786432, 1179648, 1310784, 2162688, 2621504, 4227072,\n    5242944, 8404992, 10485824, 16785408, 20971584, 33558528, 41943104, 67110912, 83886144, 134217763, 134218752,\n    167772171, 167772181, 167772201, 167772224, 167772241, 167772321, 167772481, 167772801, 167773441, 167774721,\n    167777281, 167782401, 167792641, 167813121, 167854081, 167936001, 168099841, 168427521, 169082881, 170393601,\n    173015041, 178388993, 178782337, 188743681, 251658241, 268435482, 268435494, 268435587, 268435712, 335544339,\n    335544352, 352321537, 357568514, 358612994, 402653219, 469762055, 533725187, 49, 70, 73, 133, 138, 148, 208,\n    259, 448, 1408, 5248, 10368, 20608, 41088, 82048, 87040, 163968, 327808, 655488, 1310848, 2228224, 2621568,\n    4259840, 5243008, 8421376, 10485888, 11173888, 16793600, 20971648, 33562624, 41943168, 67112960, 83886208,\n    88080385, 134217753, 134217765, 134217795, 134219776, 167772167, 167772288, 176160770, 178520065, 178782465,\n    178913280, 243269633, 268435526, 268435715, 268435968, 335544334, 335544355, 335544384, 352321544, 357572610,\n    357826566, 402653251, 436207623, 444596226, 469762059, 492044291, 503316481, 50, 134, 137, 261, 266, 296,\n    416, 515, 896, 2816, 20736, 41216, 82176, 164096, 327936, 655616, 1310976, 1572864, 2359296, 2621696,\n    8454144, 10486016, 16809984, 20971776, 33570816, 44040193, 67117056, 83886336, 134217797, 167772416,\n    178782721, 268435506, 268435530, 268435590, 268435971, 268436480, 335544387, 335544448, 352321540, 357826570,\n    364904450, 402653315, 436207627, 469762067, 486539265 };\n\nstatic int cbest_30[1023] = {\n    1, 2, 541065219, 4, 811597826, 8, 405798913, 16, 32, 64, 743964675, 128, 913047554, 256, 541065218,\n    270532609, 3, 512, 456523777, 541065217, 6, 811597827, 5, 676331523, 12, 129, 405798912, 258, 811597824, 9,\n    24, 516, 1024, 202899456, 541065223, 541065283, 10, 1032, 946864130, 48, 2064, 101449728, 879230978, 17,\n    4128, 541065227, 811597858, 96, 8256, 50724864, 811597830, 20, 16512, 405798915, 18, 33, 192, 2048, 33024,\n    25362432, 405798929, 473432065, 541065235, 769327107, 66048, 439615489, 743964674, 811597834, 132096,\n    12681216, 405798917, 34, 40, 65, 264192, 541065251, 528384, 6340608, 743964673, 811597842, 384, 1056768,\n    405798921, 36, 66, 4096, 2113536, 3170304, 371982337, 743964683, 80, 4227072, 1585152, 8454144, 777781251,\n    68, 130, 768, 16908288, 541065347, 743964679, 760872963, 913047555, 792576, 33816576, 811597890, 917340162,\n    160, 67633152, 405798945, 72, 132, 257, 8192, 396288, 135266304, 913047552, 1536, 270532608, 811597954,\n    913047558, 260, 198144, 405798977, 727056387, 136, 456523776, 541065475, 743964691, 193, 386, 99072, 514,\n    3072, 405799041, 541065216, 929955842, 144, 320, 513, 16384, 49536, 743964707, 921501698, 997588994, 264,\n    520, 772, 541065315, 676331522, 811598082, 7, 24768, 270532611, 456523779, 458670081, 913047562, 385, 6144,\n    228261888, 338165761, 541065221, 541065411, 541065731, 743964739, 811597825, 13, 1028, 1544, 12384,\n    270532641, 541065222, 541065282, 676331521, 14, 259, 270532613, 270532673, 405799169, 541065346, 811597874,\n    904593410, 913047570, 946864131, 25, 272, 517, 770, 1025, 1040, 6192, 202899457, 541065473, 743964803,\n    879230979, 11, 26, 131, 640, 1026, 1033, 3088, 12288, 405798914, 456523781, 541065226, 541065231, 541065281,\n    676331539, 811597828, 811597859, 811597922, 811598338, 28, 49, 528, 2065, 3096, 101449729, 270532617,\n    405798928, 464977921, 473432064, 541065225, 676331555, 710148099, 811597831, 913047586, 946864128, 161, 288,\n    518, 1540, 2056, 4129, 32768, 114130944, 202899458, 202899464, 405798937, 439615488, 460750849, 498794497,\n    541065243, 541065735, 541066243, 676331527, 811597955, 879230976, 22, 52, 97, 133, 1034, 1548, 6176, 8257,\n    50724865, 101449732, 405798916, 456523785, 541065234, 541065299, 541066251, 769327106, 811597856, 811598080,\n    21, 50, 56, 262, 322, 2066, 2080, 16513, 24576, 50724866, 101449730, 236716032, 270532625, 405798961,\n    405799040, 405799425, 541065267, 541067283, 642514947, 811597835, 811597838, 879230986, 913047618, 946864146,\n    1014497282, 19, 774, 2049, 3080, 4130, 33025, 25362433, 202899460, 202899520, 219807744, 541065287,\n    541069347, 676331531, 743964931, 811597832, 811597866, 879230994, 980680706, 44, 98, 104, 137, 644, 1036,\n    8258, 12352, 66049, 101449760, 405798919, 405798920, 456523793, 541065233, 541065250, 541073475, 591790083,\n    769327105, 811597850, 811598342, 811598850, 946864134, 946864194, 112, 266, 387, 2052, 2068, 4112, 16514,\n    132097, 12681217, 50724880, 118358016, 371982336, 405798933, 452296705, 541065239, 541081731, 743964672,\n    811597843, 811598858, 879230982, 896139266, 913047682, 35, 41, 194, 524, 544, 1056, 1280, 2050, 4132, 6160,\n    33026, 264193, 25362434, 25362440, 57065472, 109903872, 405798925, 405798931, 405800961, 439615493,\n    473432067, 473432073, 541065291, 541067267, 541073411, 541098243, 545357827, 566427651, 770400259, 811597862,\n    811599890, 811601922, 42, 88, 100, 145, 208, 4160, 8260, 16385, 24704, 66050, 528385, 6340609, 12681220,\n    50724868, 405799171, 439615491, 439615497, 456523809, 541065249, 541131267, 743964682, 743964687, 811601954,\n    946864138, 38, 224, 274, 2072, 16516, 132098, 1056769, 6340610, 12681218, 59179008, 101449736, 185991168,\n    405799429, 405799937, 473432097, 507248641, 539017219, 541065410, 541197315, 743964681, 805355522, 811597840,\n    811606082, 862322690, 37, 67, 196, 532, 576, 1288, 4097, 4136, 12320, 33028, 49152, 65536, 264194, 2113537,\n    3170305, 25362436, 54951936, 202899472, 405798923, 405799945, 473432069, 490340353, 541065259, 541329411,\n    743964677, 743964699, 743965187, 743965699, 765165571, 769327111, 777781250, 811597846, 811614338, 81, 176,\n    388, 1048, 8224, 8264, 49408, 66052, 528386, 4227073, 50724872, 384663553, 402677761, 405798944, 405800977,\n    456523841, 541593603, 743964678, 760872962, 782073859, 811598018, 811599874, 811630850, 815890434, 836960258,\n    70, 290, 16520, 32770, 132100, 1056770, 1585153, 4227074, 8454145, 29589504, 92995584, 101449744, 270532705,\n    270532737, 371982341, 405803041, 448069633, 536903683, 541065603, 542121987, 743964802, 760872961, 811597891,\n    811597986, 811663874, 913047810, 917340163, 69, 84, 200, 548, 769, 4098, 4104, 4144, 8320, 24640, 33032,\n    264196, 2113538, 3170306, 8454146, 16908289, 27475968, 28532736, 202899488, 371982339, 405799009, 405807169,\n    541065255, 541065539, 541069315, 543178755, 544235523, 676331587, 743964929, 769327115, 771440643, 777781249,\n    809517058, 810573826, 811729922, 76, 82, 416, 1064, 2112, 8272, 66056, 528388, 792577, 6340612, 8454148,\n    16908290, 33816577, 404758529, 405798976, 405798993, 405815425, 456523905, 541065345, 777781255, 811862018,\n    913047553, 913047556, 913048066, 946864162, 134, 448, 641, 2096, 2560, 2576, 16528, 132104, 1056772, 1585154,\n    12681224, 14794752, 16908292, 46497792, 67633153, 405799105, 405831937, 407945217, 431161345, 542650371,\n    549519363, 760872967, 811597888, 812126210, 879231010, 913047559, 917340160, 73, 392, 580, 776, 1088, 4100,\n    8193, 16448, 33040, 65540, 98304, 264200, 396289, 2113540, 3170308, 13737984, 16908296, 25362448, 33816580,\n    135266305, 371982401, 388890625, 405864961, 473432081, 541065987, 557973507, 676331651, 727056386, 742404099,\n    743965191, 758824963, 769327123, 777781267, 807436290, 811598210, 812654594, 74, 140, 352, 1096, 1537, 8288,\n    66064, 98816, 528392, 792578, 4227076, 6340616, 33816578, 33816584, 50724896, 380436481, 403718145,\n    405286913, 405931009, 418480129, 439615505, 541065474, 541857795, 574881795, 727056385, 743964690, 743965707,\n    162, 168, 261, 1538, 2128, 16544, 132112, 198145, 1056776, 1585156, 7397376, 12681232, 23248896, 33816592,\n    67633154, 67633160, 101449792, 270532865, 405798947, 405798953, 406063105, 456524033, 541065379, 608698371,\n    676331571, 743444483, 743964723, 743966739, 794689539, 811597906, 811597952, 879231042, 913047566, 152, 832,\n    1152, 4192, 8194, 24577, 33056, 49280, 131072, 264208, 396290, 2113544, 3170312, 6868992, 14266368, 25362464,\n    67633168, 135266306, 202899584, 371982345, 406327297, 541065351, 541461507, 743964715, 743968803, 756711427,\n    769327139, 786235395, 813182978, 820051970, 915259394, 917876738, 929955843, 138, 896, 1160, 1282, 5152,\n    8208, 66080, 99073, 131080, 528400, 792580, 4227080, 6340624, 50724928, 67633184, 135266320, 270532610,\n    405799297, 439615521, 456523778, 458670080, 541066755, 541077507, 743964689, 743964706, 743964771, 743972931,\n    811597894, 811598594, 828506114, 913047683, 917340166, 921501699, 923713538, 929955840, 946864258, 997588995,\n    164, 268, 400, 515, 645, 784, 1290, 1552, 2192, 2580, 3073, 4224, 5160, 10320, 16576, 16640, 20640, 41280,\n    49154, 82560, 132128, 165120, 198146, 330240, 660480, 1056784, 1320960, 1585160, 2641920, 3698688, 5283840,\n    8454152, 10567680, 11624448, 12681248, 21135360, 42270720, 67633156, 84541440, 101449856, 135266336,\n    169082880, 338165760, 405798949, 405798979, 405803009, 541065314, 541065479, 541065601, 541263363, 725008387,\n    742924291, 743964695, 743981187, 777781259, 811598083, 812390402, 845414402, 879231106, 913047808, 921501696,\n    991346690, 997588992, 321, 3074, 4256, 8196, 33088, 49537, 196608, 264224, 396292, 2113552, 3170320, 3434496,\n    25362496, 135266308, 135266368, 270532640, 270532657, 363528193, 371982353, 410025985, 473432129, 541065355,\n    541065537, 541065729, 676331779, 743964867, 743966723, 743997699, 745037827, 748257283, 760872971, 769327171,\n    811603970, 912267266, 913047563, 195, 265, 280, 521, 704, 773, 3076, 5120, 8384, 66112, 99074, 197632,\n    528416, 792584, 4227088, 6340640, 50724992, 270532612, 270532672, 405799168, 414253057, 439615553, 541065220,\n    541065730, 541164291, 727056419, 743964705, 743964738, 744030723, 811597898, 811597958, 811606018, 811994114,\n    912787458, 913047560, 917340170, 148, 209, 324, 522, 1664, 24769, 32896, 132160, 198148, 262160, 1056800,\n    1585168, 1849344, 5812224, 8454160, 12681280, 270532736, 270532801, 270533121, 405798981, 405799043,\n    405799681, 405801985, 422707201, 541065313, 541068291, 541089795, 676331520, 722894851, 727056391, 741916675,\n    743708675, 744096771, 811599362, 913047578, 913048070, 913048578, 918396930, 929955850, 146, 225, 418, 1546,\n    2176, 2564, 6145, 8200, 10304, 16386, 33152, 49538, 98560, 264256, 396296, 1717248, 2113568, 3170336,\n    7133184, 16908304, 25362560, 135266312, 202899712, 228261889, 371982369, 405799057, 406591489, 456523780,\n    473432193, 541065363, 541081603, 541114755, 735510531, 744228867, 760872979, 769327235, 811597875, 811598019,\n    811598086, 811598208, 811795970, 879231002, 904593411, 913047571, 913047574, 913048586, 197, 336, 1029, 1545,\n    1568, 1792, 12385, 49153, 66176, 98308, 99076, 792592, 4227104, 270532615, 270532616, 456524289, 464977920,\n    541065409, 541065472, 541065602, 541065991, 676331547, 743964737, 811597962, 811597987, 811598336, 811610114,\n    912527362, 913049618, 917340178, 946864386 };\n\nstatic int cbest_31[1023] = {\n    1, 2, 1073741828, 4, 536870914, 8, 268435457, 16, 32, 1207959556, 64, 128, 603979778, 256, 512, 301989889,\n    1024, 2048, 4096, 1224736772, 8192, 16384, 32768, 612368386, 65536, 131072, 262144, 306184193, 524288,\n    1048576, 2097152, 4194304, 1226833924, 8388608, 16777216, 33554432, 67108864, 613416962, 134217728,\n    268435456, 306708481, 536870912, 3, 1073741824, 1073741829, 5, 536870915, 1073741830, 6, 9, 18, 36, 72, 144,\n    288, 576, 1152, 2304, 4608, 9216, 18432, 36864, 73728, 147456, 294912, 589824, 1179648, 2359296, 4718592,\n    9437184, 18874368, 37748736, 75497472, 150994944, 301989888, 1610612742, 10, 17, 268435459, 536870918,\n    603979776, 1073741836, 1227096068, 1342177285, 12, 33, 268435461, 536870922, 805306371, 1073741844,\n    1207959552, 1207959557, 20, 34, 65, 268435465, 536870930, 1073741860, 1207959558, 24, 66, 129, 268435473,\n    536870946, 603979779, 1073741892, 1744830470, 40, 68, 130, 257, 268435489, 536870978, 1073741956, 1207959564,\n    1476395013, 1677721606, 48, 132, 258, 513, 268435521, 536871042, 603979782, 613548034, 1073742084,\n    1207959572, 80, 136, 260, 514, 1025, 268435585, 301989891, 536871170, 603979786, 872415235, 1073742340,\n    1207959588, 1375731717, 96, 264, 516, 1026, 2049, 268435713, 301989893, 536871426, 603979794, 838860803,\n    1073742852, 1207959620, 160, 272, 520, 1028, 2050, 4097, 268435969, 301989897, 306184192, 536871938,\n    603979810, 612368384, 1073743876, 1207959684, 1224736768, 1224736773, 1811939334, 192, 528, 1032, 2052, 4098,\n    8193, 153092096, 268436481, 301989905, 536872962, 603979842, 1073745924, 1207959812, 1224736774, 320, 544,\n    1040, 2056, 4100, 8194, 16385, 76546048, 268437505, 301989921, 306774017, 536875010, 603979906, 1073750020,\n    1207960068, 1509949445, 1761607686, 384, 1056, 2064, 4104, 8196, 16386, 32769, 38273024, 268439553,\n    301989953, 536879106, 603980034, 612368387, 1073758212, 1207960580, 1224736780, 1493172229, 640, 1088, 2080,\n    4112, 8200, 16388, 32770, 65537, 19136512, 268443649, 301990017, 536887298, 603980290, 905969667, 1073774596,\n    1207961604, 1224736788, 1686110214, 768, 2112, 4128, 8208, 16392, 32772, 65538, 131073, 9568256, 268451841,\n    301990145, 536903682, 603980802, 612368390, 1073807364, 1207963652, 1224736804, 1280, 2176, 4160, 8224,\n    16400, 32776, 65540, 131074, 262145, 4784128, 268468225, 301990401, 536936450, 603981826, 612368394,\n    880803843, 1073872900, 1207967748, 1224736836, 1536, 4224, 8256, 16416, 32784, 65544, 131076, 262146, 524289,\n    2392064, 268500993, 301990913, 306184195, 537001986, 603983874, 612368402, 1074003972, 1207975940,\n    1224736900, 1379926021, 1828716550, 2560, 4352, 8320, 16448, 32800, 65552, 131080, 262148, 524290, 1048577,\n    1196032, 268566529, 301991937, 306184197, 537133058, 603987970, 612368418, 843055107, 1074266116, 1207992324,\n    1224737028, 1227128836, 1820327942, 3072, 8448, 16512, 32832, 65568, 131088, 262152, 524292, 598016, 1048578,\n    2097153, 268697601, 301993985, 306184201, 537395202, 603996162, 612368450, 1074790404, 1208025092,\n    1224737284, 1526726661, 5120, 8704, 16640, 32896, 65600, 131104, 262160, 299008, 524296, 1048580, 2097154,\n    4194305, 268959745, 301998081, 306184209, 537919490, 604012546, 612368514, 1075838980, 1208090628,\n    1224737796, 1226833920, 1226833925, 6144, 16896, 33024, 65664, 131136, 149504, 262176, 524304, 1048584,\n    2097156, 4194306, 8388609, 269484033, 302006273, 306184225, 538968066, 604045314, 612368642, 613416960,\n    1077936132, 1208221700, 1224738820, 1226833926, 1514143749, 10240, 17408, 33280, 65792, 74752, 131200,\n    262208, 524320, 1048592, 2097160, 4194308, 8388610, 16777217, 270532609, 302022657, 306184257, 306708480,\n    541065218, 604110850, 612368898, 914358275, 1082130436, 1208483844, 1224740868, 1763704838, 12288, 33792,\n    37376, 66048, 131328, 262272, 524352, 1048608, 2097168, 4194312, 8388612, 16777218, 33554433, 272629761,\n    302055425, 306184321, 545259522, 604241922, 612369410, 910163971, 1090519044, 1209008132, 1224744964,\n    1226833932, 1495269381, 18688, 20480, 34816, 66560, 131584, 262400, 524416, 1048640, 2097184, 4194320,\n    8388616, 16777220, 33554434, 67108865, 153354240, 276824065, 302120961, 306184449, 553648130, 604504066,\n    612370434, 613416963, 613564418, 1107296260, 1210056708, 1224753156, 1226833940, 9344, 24576, 67584, 132096,\n    262656, 524544, 1048704, 2097216, 4194336, 8388624, 16777224, 33554436, 67108866, 134217729, 285212673,\n    302252033, 306184705, 570425346, 605028354, 612372482, 1140850692, 1212153860, 1224769540, 1226833956,\n    1687158790, 1837105158, 4672, 40960, 69632, 133120, 263168, 524800, 1048832, 2097280, 4194368, 8388640,\n    16777232, 33554440, 67108868, 76677120, 134217730, 302514177, 306185217, 606076930, 612376578, 613416966,\n    1216348164, 1224802308, 1226833988, 2336, 49152, 135168, 264192, 525312, 1049088, 2097408, 4194432, 8388672,\n    16777248, 33554448, 67108872, 134217732, 268435458, 303038465, 306186241, 335544321, 608174082, 612384770,\n    613416970, 671088642, 881852419, 1224867844, 1226834052, 1342177284, 1830813702, 1168, 81920, 139264, 266240,\n    526336, 1049600, 2097664, 4194560, 8388736, 16777280, 33554464, 38338560, 67108880, 134217736, 268435460,\n    304087041, 306188289, 402653185, 536870913, 612401154, 613416978, 805306370, 1224998916, 1226834180,\n    1241513988, 1530920965, 584, 98304, 270336, 528384, 1050624, 2098176, 4194816, 8388864, 16777344, 33554496,\n    67108896, 134217744, 268435464, 306192385, 306708483, 612433922, 613416994, 620756994, 1225261060,\n    1226834436, 1275068420, 1380450309, 1528823813, 1610612740, 1821376518, 292, 163840, 278528, 532480, 1052672,\n    2099200, 4195328, 8389120, 16777472, 19169280, 33554560, 67108928, 134217760, 268435472, 306200577,\n    306708485, 306782209, 310378497, 536870916, 536870928, 612499458, 613417026, 637534210, 843579395,\n    1073741825, 1225785348, 1226834948, 146, 196608, 540672, 1056768, 2101248, 4196352, 8389632, 16777728,\n    33554688, 67108992, 134217792, 268435488, 306216961, 306708489, 318767105, 536870920, 612630530, 613417090,\n    805306369, 918552579, 1073741826, 1073741831, 1073741856, 1226835972, 1476395012, 7, 19, 37, 73, 145, 289,\n    577, 1153, 2305, 4609, 9217, 18433, 36865, 73729, 147457, 294913, 327680, 557056, 589825, 1064960, 1179649,\n    2105344, 2359297, 4198400, 4718593, 8390656, 9437185, 9584640, 16778240, 18874369, 33554944, 37748737,\n    67109120, 75497473, 134217856, 150994945, 268435520, 306249729, 306708497, 612892674, 613417218, 738197506,\n    1226838020, 1228931076, 1610612738, 1610612743, 11, 38, 74, 290, 578, 1154, 2306, 4610, 9218, 18434, 36866,\n    73730, 147458, 294914, 393216, 589826, 1081344, 1179650, 2113536, 2359298, 4202496, 4718594, 8392704,\n    9437186, 16779264, 18874370, 33555456, 37748738, 67109376, 75497474, 134217984, 150994946, 268435584,\n    301989890, 306315265, 306708513, 369098753, 536870919, 536870944, 603979777, 613417474, 872415234, 915406851,\n    1073741832, 1073741837, 1073741846, 1073741900, 1073741972, 1073742116, 1073742404, 1073742980, 1073744132,\n    1073746436, 1073751044, 1073760260, 1073778692, 1073815556, 1073889284, 1074036740, 1074331652, 1074921476,\n    1076101124, 1078460420, 1083179012, 1092616196, 1111490564, 1149239300, 1226842116, 1227096064, 1227096069,\n    1233125380, 1342177281, 1375731716, 1514668037, 1744830468, 13, 22, 76, 148, 580, 1156, 2308, 4612, 9220,\n    18436, 36868, 73732, 147460, 294916, 589828, 655360, 1114112, 1179652, 2129920, 2359300, 4210688, 4718596,\n    4792320, 8396800, 9437188, 16781312, 18874372, 33556480, 37748740, 67109888, 75497476, 134218240, 150994948,\n    268435712, 301989892, 306446337, 306708545, 436207617, 536870923, 536870950, 536870976, 536870986, 536871058,\n    536871202, 536871490, 536872066, 536873218, 536875522, 536880130, 536889346, 536907778, 536944642, 537018370,\n    537165826, 537460738, 538050562, 539230210, 541589506, 546308098, 555745282, 574619650, 613417986, 614465538,\n    687865858, 838860802, 1073741838, 1073741840, 1073741845, 1207959553, 1226850308, 1227096070, 1342177287,\n    1677721604, 14, 21, 26, 35, 44, 152, 296, 1160, 2312, 4616, 9224, 18440, 36872, 73736, 147464, 294920,\n    589832, 786432, 1179656, 2162688, 2359304, 4227072, 4718600, 8404992, 9437192, 16785408, 18874376, 33558528,\n    37748744, 67110912, 75497480, 134218752, 150994952, 268435463, 268435475, 268435493, 268435529, 268435601,\n    268435745, 268435968, 268436033, 268436609, 268437761, 268440065, 268444673, 268453889, 268472321, 268509185,\n    268582913, 268730369, 269025281, 269615105, 270794753, 273154049, 277872641, 287309825, 301989896, 306708609,\n    343932929, 419430401, 536870931, 536871040, 603979780, 603979792, 613419010, 613548032, 616562690, 910688259,\n    1073741861, 1207959554, 1207959559, 1207959584, 1226866692, 1258291204, 1610612750, 1763966982, 1839202310,\n    1879048199, 25, 52, 67, 88, 304, 592, 2320, 4624, 9232, 18448, 36880, 73744, 147472, 294928, 589840, 1179664,\n    1310720, 2228224, 2359312, 2396160, 4259840, 4718608, 8421376, 9437200, 16793600, 18874384, 33562624,\n    37748752, 67112960, 75497488, 134219776, 150994960, 268435467, 268436480, 301989904, 306708737, 307232769,\n    536870926, 536870947, 536871168, 603979784, 613421058, 805306375, 872415233, 1073741862, 1073741888,\n    1073741893, 1226899460, 1227096076, 1291845636, 1342177293, 1495531525, 1610612758, 1744830466, 1744830471,\n    1838153734, 41, 50, 69, 104, 131, 176, 608, 18464, 73760, 147488, 589856, 1179680, 1572864, 2359328, 4325376,\n    4718624, 8454144, 9437216, 16809984, 18874400, 33570816, 67117056, 75497504, 134221824, 301989920, 306708993,\n    306774016, 308281345, 536870934, 536870979, 629145602, 805306379, 1073741894, 1073741952, 1207959560,\n    1207959700, 1207959844, 1207960708, 1207961860, 1207964164, 1207968772, 1207977988, 1207996420, 1208033284,\n    1208107012, 1208549380, 1209139204, 1210318852, 1212678148, 1217396740, 1226964996, 1283457028, 1342177301,\n    1476395009, 1509949444, 1610612774, 1677721607 };\n\nstatic unsigned int cbest_32[1023] = {\n    1, 2149580803, 2, 4, 3224371202, 8, 1612185601, 16, 32, 2955673603, 64, 128, 256, 3627417602, 512, 1024,\n    2149580802, 1074790401, 1813708801, 3, 2149580801, 2048, 3224371203, 6, 2686976003, 5, 12, 1612185600,\n    3224371200, 2149580807, 9, 24, 806092800, 3761766402, 10, 1025, 4096, 3493068802, 48, 2050, 403046400,\n    2149580811, 2149581315, 17, 4100, 3056435203, 3224371206, 96, 8200, 201523200, 20, 16400, 1612185603,\n    2149580819, 3224371458, 18, 33, 192, 32800, 100761600, 1880883201, 2955673602, 3224371210, 8192, 65600,\n    1746534401, 384, 131200, 50380800, 1612185605, 2149580835, 34, 40, 65, 262400, 1612185729, 2955673601,\n    3224371218, 768, 524800, 25190400, 1049600, 1612185609, 2149580867, 36, 66, 129, 1536, 2099200, 12595200,\n    1477836801, 3224371234, 80, 16384, 4198400, 3090022403, 6297600, 8396800, 1612185617, 2149580931, 2955673607,\n    2955673667, 3022848003, 3627417603, 68, 130, 257, 16793600, 3224371266, 3677798402, 3148800, 33587200, 160,\n    3072, 67174400, 1612185633, 2149581059, 2955673611, 3627417600, 72, 132, 258, 513, 1574400, 134348800,\n    3224371330, 32768, 268697600, 2888499203, 787200, 537395200, 1612185665, 2955673619, 136, 260, 320, 514,\n    6144, 1074790400, 1813708800, 3627417634, 393600, 3627417606, 3694592002, 2149581827, 2955673635, 3661004802,\n    3963289602, 144, 264, 516, 1026, 1537, 3074, 196800, 2149580800, 3224371714, 640, 12288, 65536, 3627417610,\n    98400, 1612185857, 2149581571, 2686976002, 272, 520, 1028, 2049, 6148, 906854400, 3224372226, 7, 49200,\n    1074790403, 1813708803, 1838899201, 2149580805, 2149582339, 3224371201, 3593830402, 3627417618, 1280, 2052,\n    3073, 24576, 1343488001, 1612186113, 1813708817, 2149580806, 2149582851, 2686976001, 2955673731, 13, 288,\n    528, 1032, 12296, 24600, 3224371586, 3761766403, 14, 131072, 1074790405, 1813708805, 3493068803, 25, 4098,\n    6146, 12300, 806092801, 1612186625, 1847296001, 2149580810, 2149580815, 2149581314, 2955673859, 3224371204,\n    3224371970, 11, 26, 544, 1040, 4097, 24592, 49152, 453427200, 1074790657, 1612185602, 1830502401, 1981644801,\n    2149580809, 2149581826, 2821324803, 3056435202, 3224371207, 3224373250, 3761766400, 28, 49, 2051, 2056, 4104,\n    6150, 403046401, 1074790409, 1074790913, 1813708809, 1880883200, 2149580827, 2149582849, 2686976007,\n    3493068800, 3627417666, 4101, 12292, 806092802, 1612185793, 1746534400, 2149580818, 2149581313, 2149584899,\n    2686976131, 2955674115, 3224371459, 22, 52, 97, 576, 1027, 1056, 2560, 3075, 8201, 49184, 201523201,\n    1612185604, 2149580851, 2552627203, 2686976259, 3056435201, 3224371211, 3224371214, 4030464002, 21, 50, 56,\n    8196, 16401, 98304, 262144, 403046402, 940441600, 1074790417, 1612185728, 1612185985, 2149584903, 2686976011,\n    3224371208, 3224372227, 3627417730, 3896115202, 19, 193, 4102, 24584, 32801, 100761601, 806092804, 806092864,\n    873267200, 1612187649, 1796915201, 2149580817, 2149580834, 2149580899, 2149582338, 2149589003, 2351104003,\n    2955674627, 3056435211, 3224371226, 3224371456, 3224373248, 3761766406, 44, 98, 104, 1029, 1088, 2064, 8193,\n    8202, 8208, 65601, 98368, 201523202, 226713600, 403046432, 1612185607, 1612185608, 2149580823, 2149597203,\n    2955673600, 3224371219, 3224375298, 3493068806, 3493068866, 3560243202, 3761766530, 112, 385, 2054, 8194,\n    16402, 131201, 50380801, 201523216, 403046404, 470220800, 1074790433, 1477836800, 1612186624, 1813708833,\n    2149580995, 2149581319, 2149613603, 2250342403, 2686976019, 3224371250, 3493068930, 3627417858, 35, 41, 194,\n    1281, 4112, 32802, 49168, 196608, 262401, 100761602, 100761608, 436633600, 806092808, 806093312, 1612185613,\n    1880883203, 2149580833, 2149580866, 2149581443, 2149588995, 2149646403, 2955673699, 3056435207, 3224375302,\n    3761766410, 3761766914, 42, 88, 100, 208, 769, 1033, 1152, 2080, 8204, 16392, 65602, 196736, 524801,\n    25190401, 50380804, 201523204, 403046656, 1074791169, 1612185616, 1746534403, 2149581187, 2149712003,\n    2199961603, 3069030403, 3224371216, 3224371235, 3224371298, 3224379402, 3425894402, 3493068810, 38, 224, 386,\n    2058, 2562, 5120, 16404, 131202, 524288, 1049601, 25190402, 50380802, 201523328, 235110400, 403046408,\n    738918400, 1074790465, 1528217601, 1612185625, 1813708865, 2015232001, 2149580843, 2149581323, 2149843203,\n    2686976035, 2955673605, 2955673795, 3073228803, 3090022402, 3224371222, 3224371462, 3224371522, 3224387602,\n    3627418114, 37, 67, 196, 4108, 16416, 32804, 98336, 262402, 2099201, 12595201, 100761604, 100761664,\n    218316800, 806092816, 1612185611, 1612185731, 1612189697, 1746534433, 1880883205, 1880883265, 1948057601,\n    2147485699, 2149580865, 2149580930, 2150105603, 2174771203, 2955673606, 2955673666, 2955675651, 3022848002,\n    3064832003, 3140403203, 3224371394, 3224404002, 3325132802, 3761766418, 81, 176, 416, 770, 1041, 2112, 5124,\n    16385, 65604, 393216, 393472, 524802, 2099202, 4198401, 50380832, 113356800, 201523208, 1612185632,\n    1612185649, 1612187651, 1746534405, 1746534465, 2150630403, 3223323650, 3224371232, 3224371267, 3224379394,\n    3224436802, 3493068818, 3677798403, 70, 388, 448, 2066, 16408, 131204, 1049602, 4198402, 6297601, 8396801,\n    25190416, 117555200, 369459200, 403046416, 1074790529, 1612185761, 1612189701, 1780121601, 1813708929,\n    1880883457, 2149580839, 2149581331, 2151684099, 2162176003, 2686976067, 3090022401, 3224371466, 3224502402,\n    3274752002, 3627418626, 69, 84, 131, 200, 1538, 4116, 16388, 32784, 32808, 196672, 262404, 4198404, 8396802,\n    12595202, 12595208, 16793601, 109158400, 806092832, 1477836803, 1611661825, 1612185697, 1612185733,\n    1612193801, 1880883209, 2149580883, 2149580929, 2149581058, 2149597187, 2153779203, 2686976387, 2955673610,\n    2955673615, 2955673665, 3022848001, 3056435219, 3224371242, 3224633602, 3627417601, 3761766434, 76, 82, 352,\n    772, 832, 1057, 2176, 4128, 8216, 8224, 16386, 65608, 524804, 786944, 3148801, 6297604, 8396804, 25190404,\n    33587201, 1612185621, 1612185664, 1612202001, 1746534409, 2148535299, 2157977603, 2955673609, 2955674626,\n    3221228546, 3222276098, 3224371264, 3224371331, 3224896002, 3249561602, 3493068834, 3677798400, 134, 161,\n    392, 896, 2082, 32832, 131208, 786432, 1048576, 1049604, 3148802, 6297602, 8396808, 16793604, 50380808,\n    58777600, 67174401, 184729600, 1477836833, 1612185619, 1612218401, 1712947201, 1813709057, 2149580871,\n    2149581347, 2166374403, 2888499202, 2955673627, 2955675649, 3048038403, 3224371474, 3627417650, 3677798406,\n    73, 133, 259, 1540, 4132, 10248, 32816, 262408, 393344, 1574401, 2099204, 12595204, 16793602, 16793608,\n    54579200, 100761616, 134348801, 1477836805, 1545011201, 1611138049, 1612185737, 1612193793, 1612251201,\n    1880883217, 2149581057, 2183168003, 2955411715, 2955673618, 2955673683, 2955677699, 3022848019, 3056435235,\n    3090022435, 3224371238, 3224372738, 3226474498, 3236966402, 3761766466, 74, 140, 704, 776, 1089, 1664, 2304,\n    3076, 4160, 8232, 10240, 32769, 65616, 524808, 1573888, 16793616, 25190408, 33587202, 33587208, 56678400,\n    201523232, 268697601, 1511424001, 1610614273, 1612316801, 1662566401, 1746534417, 2149580963, 2149583875,\n    2216755203, 2955673651, 3022848035, 3090022407, 3157196803, 3224371282, 3224371328, 3224372482, 3224387586,\n    3228569602, 3627417604, 3627417635, 3627417698, 162, 168, 262, 400, 1792, 2114, 16432, 65568, 131216, 787201,\n    1049608, 29388800, 33587216, 50380816, 67174402, 92364800, 403046464, 537395201, 1612185635, 1612185641,\n    1612448001, 1813709313, 2149580875, 2149580935, 2149581379, 2149583363, 2151155203, 2283929603, 2888499201,\n    2955673671, 2955677703, 3022848007, 3090022531, 3123609603, 3223848450, 3224371490, 3232768002, 3627417607,\n    3627419650, 3694592003, 137, 152, 261, 321, 515, 1544, 4164, 6145, 262416, 786688, 1572864, 1574402, 2099208,\n    27289600, 33587232, 67174416, 100761632, 134348802, 806092928, 1477836809, 1477837313, 1612185745,\n    1612186369, 1637376001, 1880883233, 2149613571, 2418278403, 2955149827, 2955673617, 2955673634, 2955681803,\n    3056435267, 3224371270, 3241164802, 3661004803, 3963289603, 138, 268, 784, 1153, 1408, 4224, 8264, 32770,\n    32776, 65632, 65664, 393601, 524816, 3147776, 3148804, 4198408, 33587204, 67174432, 201523264, 268697602,\n    1612185856, 1612186241, 2150368003, 2913689603, 2955673623, 2955690003, 3039641603, 3090022411, 3224371715,\n    3257958402, 3493068994, 3627417632, 164, 518, 2178, 5121, 16448, 16464, 20496, 131232, 787202, 1049616,\n    6297608, 14694400, 46182400, 67174404, 67174464, 134348832, 403046528, 537395202, 1074791425, 1612185637,\n    1612185667, 1612187137, 1613237249, 1614284801, 1624780801, 1813709825, 2149580939, 2149581063, 2149581123,\n    2149586947, 2686976515, 2888499219, 2954887939, 2955673675, 2955706403, 3022848011, 3224371362, 3224374274,\n    3291545602, 3627286658, 3627417611, 3627417614, 3694592000, 145, 265, 280, 322, 517, 1552, 3080, 4228, 6152,\n    32864, 196801, 262432, 1573376, 1574404, 2097152, 2099216, 12595216, 13644800, 134348804, 134348864,\n    806093056, 1074790402, 1444249601, 1477836817, 1612185681, 1612201985, 1616384001, 1813708802, 1838899200,\n    2149581570, 2149581825, 2149583873, 2149974403, 2955673633, 2955681795, 2955739203, 3056435331, 3224371274,\n    3224371334, 3358720002, 3627417608, 3627417642, 3627418627, 3661004800, 3677798410, 3679901698, 3686195202,\n    3761766658, 3963289600, 266, 336, 524, 641, 800, 3328, 4352, 5125, 8256, 8328, 10250, 12289, 20500, 32772,\n    41000, 65537, 82000, 131136, 164000, 328000, 393602, 524832, 656000, 1312000, 2624000, 3148808, 4198416,\n    5248000, 10496000, 20992000, 25190432, 28339200, 41984000, 83968000, 134348928, 167936000, 268697604,\n    268697664, 335872000, 671744000, 1343488000, 1611924225, 1612186112, 1618483201, 1620582401, 1813708816,\n    2149583361, 2955673987, 2955804803, 3006054403, 3090022419, 3224371712, 3224403970, 3493069058, 3627417626,\n    3627419648, 3681996802, 3719782402, 148, 304, 2306, 3584, 12290, 16528, 98401, 131264, 787204, 1049632,\n    3145728, 6297616, 8396816, 23091200, 50380864, 67174408, 268697728, 1074790785, 1612185669, 1813708825 };\n"
  },
  {
    "path": "fst/layout/jerasure/src/galois.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <errno.h>\n#include <assert.h>\n\n#include \"galois.h\"\n\n#define MAX_GF_INSTANCES 64\ngf_t *gfp_array[MAX_GF_INSTANCES] = { 0 };\nint  gfp_is_composite[MAX_GF_INSTANCES] = { 0 };\n\ngf_t *galois_get_field_ptr(int w)\n{\n  if (gfp_array[w] != NULL) {\n    return gfp_array[w];\n  }\n\n  return NULL;\n}\n\ngf_t* galois_init_field(int w,\n                        int mult_type,\n                        int region_type,\n                        int divide_type,\n                        uint64_t prim_poly,\n                        int arg1,\n                        int arg2)\n{\n  int scratch_size;\n  void *scratch_memory;\n  gf_t *gfp;\n\n  if (w <= 0 || w > 32) {\n    fprintf(stderr, \"ERROR -- cannot init default Galois field for w=%d\\n\", w);\n    assert(0);\n  }\n\n  gfp = (gf_t *) malloc(sizeof(gf_t));\n  if (!gfp) {\n    fprintf(stderr, \"ERROR -- cannot allocate memory for Galois field w=%d\\n\", w);\n    assert(0);\n  }\n\n  scratch_size = gf_scratch_size(w, mult_type, region_type, divide_type, arg1, arg2);\n  if (!scratch_size) {\n    fprintf(stderr, \"ERROR -- cannot get scratch size for base field w=%d\\n\", w);\n    assert(0);\n  }\n\n  scratch_memory = malloc(scratch_size);\n  if (!scratch_memory) {\n    fprintf(stderr, \"ERROR -- cannot get scratch memory for base field w=%d\\n\", w);\n    assert(0);\n  }\n\n  if(!gf_init_hard(gfp,\n                   w, \n                   mult_type, \n                   region_type, \n                   divide_type, \n                   prim_poly, \n                   arg1, \n                   arg2, \n                   NULL, \n                   scratch_memory))\n  {\n    fprintf(stderr, \"ERROR -- cannot init default Galois field for w=%d\\n\", w);\n    assert(0);\n  }\n\n  gfp_is_composite[w] = 0;\n  return gfp;\n}\n\ngf_t* galois_init_composite_field(int w,\n                                int region_type,\n                                int divide_type,\n                                int degree,\n                                gf_t* base_gf)\n{\n  int scratch_size;\n  void *scratch_memory;\n  gf_t *gfp;\n  \n  if (w <= 0 || w > 32) {\n    fprintf(stderr, \"ERROR -- cannot init composite field for w=%d\\n\", w);\n    assert(0);\n  }\n  \n  gfp = (gf_t *) malloc(sizeof(gf_t));\n  if (!gfp) {\n    fprintf(stderr, \"ERROR -- cannot allocate memory for Galois field w=%d\\n\", w);\n    assert(0);\n  }\n\n  scratch_size = gf_scratch_size(w, GF_MULT_COMPOSITE, region_type, divide_type, degree, 0);\n  if (!scratch_size) {\n    fprintf(stderr, \"ERROR -- cannot get scratch size for composite field w=%d\\n\", w);\n    assert(0);\n  }\n\n  scratch_memory = malloc(scratch_size);\n  if (!scratch_memory) {\n    fprintf(stderr, \"ERROR -- cannot get scratch memory for composite field w=%d\\n\", w);\n    assert(0);\n  }\n\n  if(!gf_init_hard(gfp,\n                   w,\n                   GF_MULT_COMPOSITE,\n                   region_type,\n                   divide_type,\n                   0, \n                   degree, \n                   0, \n                   base_gf,\n                   scratch_memory))\n  {\n    fprintf(stderr, \"ERROR -- cannot init default composite field for w=%d\\n\", w);\n    assert(0);\n  }\n  gfp_is_composite[w] = 1;\n  return gfp;\n}\n\nint galois_init_default_field(int w)\n{\n  if (gfp_array[w] == NULL) {\n    gfp_array[w] = (gf_t*)malloc(sizeof(gf_t));\n    if(gfp_array[w] == NULL)\n      return ENOMEM;\n    if (!gf_init_easy(gfp_array[w], w))\n      return EINVAL;\n  }\n  return 0;\n}\n\nint galois_uninit_field(int w)\n{\n  int ret = 0;\n  if (gfp_array[w] != NULL) {\n    int recursive = 1;\n    ret = gf_free(gfp_array[w], recursive);\n    free(gfp_array[w]);\n    gfp_array[w] = NULL;\n  }\n  return ret;\n}\n\nstatic void galois_init(int w)\n{\n  if (w <= 0 || w > 32) {\n    fprintf(stderr, \"ERROR -- cannot init default Galois field for w=%d\\n\", w);\n    assert(0);\n  }\n\n  switch (galois_init_default_field(w)) {\n  case ENOMEM:\n    fprintf(stderr, \"ERROR -- cannot allocate memory for Galois field w=%d\\n\", w);\n    assert(0);\n    break;\n  case EINVAL:\n    fprintf(stderr, \"ERROR -- cannot init default Galois field for w=%d\\n\", w);\n    assert(0);\n    break;\n  }\n}\n\n\nstatic int is_valid_gf(gf_t *gf, int w)\n{\n  // TODO: I assume we may eventually\n  // want to do w=64 and 128, so w\n  // will be needed to perform this check\n  (void)w;\n\n  if (gf == NULL) {\n    return 0;\n  }\n  if (gf->multiply.w32 == NULL) {\n    return 0;\n  }\n  if (gf->multiply_region.w32 == NULL) {\n    return 0;\n  }\n  if (gf->divide.w32 == NULL) {\n    return 0;\n  }\n  if (gf->inverse.w32 == NULL) {\n    return 0;\n  }\n  if (gf->extract_word.w32 == NULL) {\n    return 0;\n  }\n\n  return 1;\n}\n\nvoid galois_change_technique(gf_t *gf, int w)\n{\n  if (w <= 0 || w > 32) {\n    fprintf(stderr, \"ERROR -- cannot support Galois field for w=%d\\n\", w);\n    assert(0);\n  }\n\n  if (!is_valid_gf(gf, w)) {\n    fprintf(stderr, \"ERROR -- overriding with invalid Galois field for w=%d\\n\", w);\n    assert(0);\n  }\n\n  if (gfp_array[w] != NULL) {\n    gf_free(gfp_array[w], gfp_is_composite[w]);\n  }\n\n  gfp_array[w] = gf;\n}\n\nint galois_single_multiply(int x, int y, int w)\n{\n  if (x == 0 || y == 0) return 0;\n  \n  if (gfp_array[w] == NULL) {\n    galois_init(w);\n  }\n\n  if (w <= 32) {\n    return gfp_array[w]->multiply.w32(gfp_array[w], x, y);\n  } else {\n    fprintf(stderr, \"ERROR -- Galois field not implemented for w=%d\\n\", w);\n    return 0;\n  }\n}\n\nint galois_single_divide(int x, int y, int w)\n{\n  if (x == 0) return 0;\n  if (y == 0) return -1;\n\n  if (gfp_array[w] == NULL) {\n    galois_init(w);\n  }\n\n  if (w <= 32) {\n    return gfp_array[w]->divide.w32(gfp_array[w], x, y);\n  } else {\n    fprintf(stderr, \"ERROR -- Galois field not implemented for w=%d\\n\", w);\n    return 0;\n  }\n}\n\nvoid galois_w08_region_multiply(char *region,      /* Region to multiply */\n                                  int multby,       /* Number to multiply by */\n                                  int nbytes,        /* Number of bytes in region */\n                                  char *r2,          /* If r2 != NULL, products go here */\n                                  int add)\n{\n  if (gfp_array[8] == NULL) {\n    galois_init(8);\n  }\n  gfp_array[8]->multiply_region.w32(gfp_array[8], region, r2, multby, nbytes, add);\n}\n\nvoid galois_w16_region_multiply(char *region,      /* Region to multiply */\n                                  int multby,       /* Number to multiply by */\n                                  int nbytes,        /* Number of bytes in region */\n                                  char *r2,          /* If r2 != NULL, products go here */\n                                  int add)\n{\n  if (gfp_array[16] == NULL) {\n    galois_init(16);\n  }\n  gfp_array[16]->multiply_region.w32(gfp_array[16], region, r2, multby, nbytes, add);\n}\n\n\nvoid galois_w32_region_multiply(char *region,      /* Region to multiply */\n                                  int multby,       /* Number to multiply by */\n                                  int nbytes,        /* Number of bytes in region */\n                                  char *r2,          /* If r2 != NULL, products go here */\n                                  int add)\n{\n  if (gfp_array[32] == NULL) {\n    galois_init(32);\n  }\n  gfp_array[32]->multiply_region.w32(gfp_array[32], region, r2, multby, nbytes, add);\n}\n\nvoid galois_w8_region_xor(void *src, void *dest, int nbytes)\n{\n  if (gfp_array[8] == NULL) {\n    galois_init(8);\n  }\n  gfp_array[8]->multiply_region.w32(gfp_array[32], src, dest, 1, nbytes, 1);\n}\n\nvoid galois_w16_region_xor(void *src, void *dest, int nbytes)\n{\n  if (gfp_array[16] == NULL) {\n    galois_init(16);\n  }\n  gfp_array[16]->multiply_region.w32(gfp_array[16], src, dest, 1, nbytes, 1);\n}\n\nvoid galois_w32_region_xor(void *src, void *dest, int nbytes)\n{\n  if (gfp_array[32] == NULL) {\n    galois_init(32);\n  }\n  gfp_array[32]->multiply_region.w32(gfp_array[32], src, dest, 1, nbytes, 1);\n}\n\nvoid galois_region_xor(char *src, char *dest, int nbytes)\n{\n  if (nbytes >= 16) {\n    galois_w32_region_xor(src, dest, nbytes);\n  } else {\n    int i = 0;\n    for (i = 0; i < nbytes; i++) {\n      *dest ^= *src;\n      dest++;\n      src++;\n    } \n  }\n}\n\nint galois_inverse(int y, int w)\n{\n  if (y == 0) return -1;\n  return galois_single_divide(1, y, w);\n}\n"
  },
  {
    "path": "fst/layout/jerasure/src/jerasure.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <assert.h>\n\n#include \"galois.h\"\n#include \"jerasure.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nstatic double jerasure_total_xor_bytes = 0;\nstatic double jerasure_total_gf_bytes = 0;\nstatic double jerasure_total_memcpy_bytes = 0;\n\nvoid jerasure_print_matrix(int* m, int rows, int cols, int w)\n{\n  int i, j;\n  int fw;\n  char s[30];\n  unsigned int w2;\n\n  if (w == 32) {\n    fw = 10;\n  } else {\n    w2 = (1 << w);\n    sprintf(s, \"%u\", w2 - 1);\n    fw = strlen(s);\n  }\n\n  for (i = 0; i < rows; i++) {\n    for (j = 0; j < cols; j++) {\n      if (j != 0) {\n        printf(\" \");\n      }\n\n      printf(\"%*u\", fw, m[i * cols + j]);\n    }\n\n    printf(\"\\n\");\n  }\n}\n\nvoid jerasure_print_bitmatrix(int* m, int rows, int cols, int w)\n{\n  int i, j;\n\n  for (i = 0; i < rows; i++) {\n    if (i != 0 && i % w == 0) {\n      printf(\"\\n\");\n    }\n\n    for (j = 0; j < cols; j++) {\n      if (j != 0 && j % w == 0) {\n        printf(\" \");\n      }\n\n      printf(\"%d\", m[i * cols + j]);\n    }\n\n    printf(\"\\n\");\n  }\n}\n\nint jerasure_make_decoding_matrix(int k, int m, int w, int* matrix, int* erased,\n                                  int* decoding_matrix, int* dm_ids)\n{\n  int i, j, *tmpmat;\n  j = 0;\n\n  for (i = 0; j < k; i++) {\n    if (erased[i] == 0) {\n      dm_ids[j] = i;\n      j++;\n    }\n  }\n\n  tmpmat = talloc(int, k * k);\n\n  if (tmpmat == NULL) {\n    return -1;\n  }\n\n  for (i = 0; i < k; i++) {\n    if (dm_ids[i] < k) {\n      for (j = 0; j < k; j++) {\n        tmpmat[i * k + j] = 0;\n      }\n\n      tmpmat[i * k + dm_ids[i]] = 1;\n    } else {\n      for (j = 0; j < k; j++) {\n        tmpmat[i * k + j] = matrix[(dm_ids[i] - k) * k + j];\n      }\n    }\n  }\n\n  i = jerasure_invert_matrix(tmpmat, decoding_matrix, k, w);\n  free(tmpmat);\n  return i;\n}\n\n/* Internal Routine */\nint jerasure_make_decoding_bitmatrix(int k, int m, int w, int* matrix,\n                                     int* erased, int* decoding_matrix, int* dm_ids)\n{\n  int i, j, *tmpmat;\n  int index, mindex;\n  j = 0;\n\n  for (i = 0; j < k; i++) {\n    if (erased[i] == 0) {\n      dm_ids[j] = i;\n      j++;\n    }\n  }\n\n  tmpmat = talloc(int, k * k * w * w);\n\n  if (tmpmat == NULL) {\n    return -1;\n  }\n\n  for (i = 0; i < k; i++) {\n    if (dm_ids[i] < k) {\n      index = i * k * w * w;\n\n      for (j = 0; j < k * w * w; j++) {\n        tmpmat[index + j] = 0;\n      }\n\n      index = i * k * w * w + dm_ids[i] * w;\n\n      for (j = 0; j < w; j++) {\n        tmpmat[index] = 1;\n        index += (k * w + 1);\n      }\n    } else {\n      index = i * k * w * w;\n      mindex = (dm_ids[i] - k) * k * w * w;\n\n      for (j = 0; j < k * w * w; j++) {\n        tmpmat[index + j] = matrix[mindex + j];\n      }\n    }\n  }\n\n  i = jerasure_invert_bitmatrix(tmpmat, decoding_matrix, k * w);\n  free(tmpmat);\n  return i;\n}\n\nint jerasure_matrix_decode(int k, int m, int w, int* matrix, int row_k_ones,\n                           int* erasures,\n                           char** data_ptrs, char** coding_ptrs, int size)\n{\n  int i, edd, lastdrive;\n  int* tmpids;\n  int* erased, *decoding_matrix, *dm_ids;\n\n  if (w != 8 && w != 16 && w != 32) {\n    return -1;\n  }\n\n  erased = jerasure_erasures_to_erased(k, m, erasures);\n\n  if (erased == NULL) {\n    return -1;\n  }\n\n  /* Find the number of data drives failed */\n  lastdrive = k;\n  edd = 0;\n\n  for (i = 0; i < k; i++) {\n    if (erased[i]) {\n      edd++;\n      lastdrive = i;\n    }\n  }\n\n  /* You only need to create the decoding matrix in the following cases:\n\n      1. edd > 0 and row_k_ones is false.\n      2. edd > 0 and row_k_ones is true and coding device 0 has been erased.\n      3. edd > 1\n\n      We're going to use lastdrive to denote when to stop decoding data.\n      At this point in the code, it is equal to the last erased data device.\n      However, if we can't use the parity row to decode it (i.e. row_k_ones=0\n         or erased[k] = 1, we're going to set it to k so that the decoding\n         pass will decode all data.\n   */\n\n  if (!row_k_ones || erased[k]) {\n    lastdrive = k;\n  }\n\n  dm_ids = NULL;\n  decoding_matrix = NULL;\n\n  if (edd > 1 || (edd > 0 && (!row_k_ones || erased[k]))) {\n    dm_ids = talloc(int, k);\n\n    if (dm_ids == NULL) {\n      free(erased);\n      return -1;\n    }\n\n    decoding_matrix = talloc(int, k * k);\n\n    if (decoding_matrix == NULL) {\n      free(erased);\n      free(dm_ids);\n      return -1;\n    }\n\n    if (jerasure_make_decoding_matrix(k, m, w, matrix, erased, decoding_matrix,\n                                      dm_ids) < 0) {\n      free(erased);\n      free(dm_ids);\n      free(decoding_matrix);\n      return -1;\n    }\n  }\n\n  /* Decode the data drives.\n     If row_k_ones is true and coding device 0 is intact, then only decode edd-1 drives.\n     This is done by stopping at lastdrive.\n     We test whether edd > 0 so that we can exit the loop early if we're done.\n   */\n\n  for (i = 0; edd > 0 && i < lastdrive; i++) {\n    if (erased[i]) {\n      jerasure_matrix_dotprod(k, w, decoding_matrix + (i * k), dm_ids, i, data_ptrs,\n                              coding_ptrs, size);\n      edd--;\n    }\n  }\n\n  /* Then if necessary, decode drive lastdrive */\n\n  if (edd > 0) {\n    tmpids = talloc(int, k);\n\n    if (!tmpids) {\n      free(erased);\n      free(dm_ids);\n      free(decoding_matrix);\n      return -1;\n    }\n\n    for (i = 0; i < k; i++) {\n      tmpids[i] = (i < lastdrive) ? i : i + 1;\n    }\n\n    jerasure_matrix_dotprod(k, w, matrix, tmpids, lastdrive, data_ptrs, coding_ptrs,\n                            size);\n    free(tmpids);\n  }\n\n  /* Finally, re-encode any erased coding devices */\n\n  for (i = 0; i < m; i++) {\n    if (erased[k + i]) {\n      jerasure_matrix_dotprod(k, w, matrix + (i * k), NULL, i + k, data_ptrs,\n                              coding_ptrs, size);\n    }\n  }\n\n  free(erased);\n\n  if (dm_ids != NULL) {\n    free(dm_ids);\n  }\n\n  if (decoding_matrix != NULL) {\n    free(decoding_matrix);\n  }\n\n  return 0;\n}\n\n\nint* jerasure_matrix_to_bitmatrix(int k, int m, int w, int* matrix)\n{\n  int* bitmatrix;\n  int rowelts, rowindex, colindex, elt, i, j, l, x;\n\n  if (matrix == NULL) {\n    return NULL;\n  }\n\n  bitmatrix = talloc(int, k * m * w * w);\n\n  if (!bitmatrix) {\n    return NULL;\n  }\n\n  rowelts = k * w;\n  rowindex = 0;\n\n  for (i = 0; i < m; i++) {\n    colindex = rowindex;\n\n    for (j = 0; j < k; j++) {\n      elt = matrix[i * k + j];\n\n      for (x = 0; x < w; x++) {\n        for (l = 0; l < w; l++) {\n          bitmatrix[colindex + x + l * rowelts] = ((elt & (1 << l)) ? 1 : 0);\n        }\n\n        elt = galois_single_multiply(elt, 2, w);\n      }\n\n      colindex += w;\n    }\n\n    rowindex += rowelts * w;\n  }\n\n  return bitmatrix;\n}\n\nvoid jerasure_matrix_encode(int k, int m, int w, int* matrix,\n                            char** data_ptrs, char** coding_ptrs, int size)\n{\n  int i;\n\n  if (w != 8 && w != 16 && w != 32) {\n    fprintf(stderr, \"ERROR: jerasure_matrix_encode() and w is not 8, 16 or 32\\n\");\n    assert(0);\n  }\n\n  for (i = 0; i < m; i++) {\n    jerasure_matrix_dotprod(k, w, matrix + (i * k), NULL, k + i, data_ptrs,\n                            coding_ptrs, size);\n  }\n}\n\nvoid jerasure_bitmatrix_dotprod(int k, int w, int* bitmatrix_row,\n                                int* src_ids, int dest_id,\n                                char** data_ptrs, char** coding_ptrs, int size, int packetsize)\n{\n  int j, sindex, pstarted, index, x, y;\n  char* dptr, *pptr, *bdptr, *bpptr;\n\n  if (size % (w * packetsize) != 0) {\n    fprintf(stderr, \"jerasure_bitmatrix_dotprod - size%c(w*packetsize)) must = 0\\n\",\n            '%');\n    assert(0);\n  }\n\n  bpptr = (dest_id < k) ? data_ptrs[dest_id] : coding_ptrs[dest_id - k];\n\n  for (sindex = 0; sindex < size; sindex += (packetsize * w)) {\n    index = 0;\n\n    for (j = 0; j < w; j++) {\n      pstarted = 0;\n      pptr = bpptr + sindex + j * packetsize;\n\n      for (x = 0; x < k; x++) {\n        if (src_ids == NULL) {\n          bdptr = data_ptrs[x];\n        } else if (src_ids[x] < k) {\n          bdptr = data_ptrs[src_ids[x]];\n        } else {\n          bdptr = coding_ptrs[src_ids[x] - k];\n        }\n\n        for (y = 0; y < w; y++) {\n          if (bitmatrix_row[index]) {\n            dptr = bdptr + sindex + y * packetsize;\n\n            if (!pstarted) {\n              memcpy(pptr, dptr, packetsize);\n              jerasure_total_memcpy_bytes += packetsize;\n              pstarted = 1;\n            } else {\n              galois_region_xor(dptr, pptr, packetsize);\n              jerasure_total_xor_bytes += packetsize;\n            }\n          }\n\n          index++;\n        }\n      }\n    }\n  }\n}\n\nvoid jerasure_do_parity(int k, char** data_ptrs, char* parity_ptr, int size)\n{\n  int i;\n  memcpy(parity_ptr, data_ptrs[0], size);\n  jerasure_total_memcpy_bytes += size;\n\n  for (i = 1; i < k; i++) {\n    galois_region_xor(data_ptrs[i], parity_ptr, size);\n    jerasure_total_xor_bytes += size;\n  }\n}\n\nint jerasure_invert_matrix(int* mat, int* inv, int rows, int w)\n{\n  int cols, i, j, k, x, rs2;\n  int row_start, tmp, inverse;\n  cols = rows;\n  k = 0;\n\n  for (i = 0; i < rows; i++) {\n    for (j = 0; j < cols; j++) {\n      inv[k] = (i == j) ? 1 : 0;\n      k++;\n    }\n  }\n\n  /* First -- convert into upper triangular  */\n  for (i = 0; i < cols; i++) {\n    row_start = cols * i;\n\n    /* Swap rows if we ave a zero i,i element.  If we can't swap, then the\n       matrix was not invertible  */\n\n    if (mat[row_start + i] == 0) {\n      for (j = i + 1; j < rows && mat[cols * j + i] == 0; j++) ;\n\n      if (j == rows) {\n        return -1;\n      }\n\n      rs2 = j * cols;\n\n      for (k = 0; k < cols; k++) {\n        tmp = mat[row_start + k];\n        mat[row_start + k] = mat[rs2 + k];\n        mat[rs2 + k] = tmp;\n        tmp = inv[row_start + k];\n        inv[row_start + k] = inv[rs2 + k];\n        inv[rs2 + k] = tmp;\n      }\n    }\n\n    /* Multiply the row by 1/element i,i  */\n    tmp = mat[row_start + i];\n\n    if (tmp != 1) {\n      inverse = galois_single_divide(1, tmp, w);\n\n      for (j = 0; j < cols; j++) {\n        mat[row_start + j] = galois_single_multiply(mat[row_start + j], inverse, w);\n        inv[row_start + j] = galois_single_multiply(inv[row_start + j], inverse, w);\n      }\n    }\n\n    /* Now for each j>i, add A_ji*Ai to Aj  */\n    k = row_start + i;\n\n    for (j = i + 1; j != cols; j++) {\n      k += cols;\n\n      if (mat[k] != 0) {\n        if (mat[k] == 1) {\n          rs2 = cols * j;\n\n          for (x = 0; x < cols; x++) {\n            mat[rs2 + x] ^= mat[row_start + x];\n            inv[rs2 + x] ^= inv[row_start + x];\n          }\n        } else {\n          tmp = mat[k];\n          rs2 = cols * j;\n\n          for (x = 0; x < cols; x++) {\n            mat[rs2 + x] ^= galois_single_multiply(tmp, mat[row_start + x], w);\n            inv[rs2 + x] ^= galois_single_multiply(tmp, inv[row_start + x], w);\n          }\n        }\n      }\n    }\n  }\n\n  /* Now the matrix is upper triangular.  Start at the top and multiply down  */\n\n  for (i = rows - 1; i >= 0; i--) {\n    row_start = i * cols;\n\n    for (j = 0; j < i; j++) {\n      rs2 = j * cols;\n\n      if (mat[rs2 + i] != 0) {\n        tmp = mat[rs2 + i];\n        mat[rs2 + i] = 0;\n\n        for (k = 0; k < cols; k++) {\n          inv[rs2 + k] ^= galois_single_multiply(tmp, inv[row_start + k], w);\n        }\n      }\n    }\n  }\n\n  return 0;\n}\n\nint jerasure_invertible_matrix(int* mat, int rows, int w)\n{\n  int cols, i, j, k, x, rs2;\n  int row_start, tmp, inverse;\n  cols = rows;\n\n  /* First -- convert into upper triangular  */\n  for (i = 0; i < cols; i++) {\n    row_start = cols * i;\n\n    /* Swap rows if we ave a zero i,i element.  If we can't swap, then the\n       matrix was not invertible  */\n\n    if (mat[row_start + i] == 0) {\n      for (j = i + 1; j < rows && mat[cols * j + i] == 0; j++) ;\n\n      if (j == rows) {\n        return 0;\n      }\n\n      rs2 = j * cols;\n\n      for (k = 0; k < cols; k++) {\n        tmp = mat[row_start + k];\n        mat[row_start + k] = mat[rs2 + k];\n        mat[rs2 + k] = tmp;\n      }\n    }\n\n    /* Multiply the row by 1/element i,i  */\n    tmp = mat[row_start + i];\n\n    if (tmp != 1) {\n      inverse = galois_single_divide(1, tmp, w);\n\n      for (j = 0; j < cols; j++) {\n        mat[row_start + j] = galois_single_multiply(mat[row_start + j], inverse, w);\n      }\n    }\n\n    /* Now for each j>i, add A_ji*Ai to Aj  */\n    k = row_start + i;\n\n    for (j = i + 1; j != cols; j++) {\n      k += cols;\n\n      if (mat[k] != 0) {\n        if (mat[k] == 1) {\n          rs2 = cols * j;\n\n          for (x = 0; x < cols; x++) {\n            mat[rs2 + x] ^= mat[row_start + x];\n          }\n        } else {\n          tmp = mat[k];\n          rs2 = cols * j;\n\n          for (x = 0; x < cols; x++) {\n            mat[rs2 + x] ^= galois_single_multiply(tmp, mat[row_start + x], w);\n          }\n        }\n      }\n    }\n  }\n\n  return 1;\n}\n\n/* Converts a list-style version of the erasures into an array of k+m elements\n   where the element = 1 if the index has been erased, and zero otherwise */\n\nint* jerasure_erasures_to_erased(int k, int m, int* erasures)\n{\n  int td;\n  int t_non_erased;\n  int* erased;\n  int i;\n  td = k + m;\n  erased = talloc(int, td);\n\n  if (erased == NULL) {\n    return NULL;\n  }\n\n  t_non_erased = td;\n\n  for (i = 0; i < td; i++) {\n    erased[i] = 0;\n  }\n\n  for (i = 0; erasures[i] != -1; i++) {\n    if (erased[erasures[i]] == 0) {\n      erased[erasures[i]] = 1;\n      t_non_erased--;\n\n      if (t_non_erased < k) {\n        free(erased);\n        return NULL;\n      }\n    }\n  }\n\n  return erased;\n}\n\nvoid jerasure_free_schedule(int** schedule)\n{\n  int i;\n\n  for (i = 0; schedule[i][0] >= 0; i++) {\n    free(schedule[i]);\n  }\n\n  free(schedule[i]);\n  free(schedule);\n}\n\nvoid jerasure_free_schedule_cache(int k, int m, int** *cache)\n{\n  int e1, e2;\n\n  if (m != 2) {\n    fprintf(stderr, \"jerasure_free_schedule_cache(): m must equal 2\\n\");\n    assert(0);\n  }\n\n  for (e1 = 0; e1 < k + m; e1++) {\n    for (e2 = 0; e2 < e1; e2++) {\n      jerasure_free_schedule(cache[e1 * (k + m) + e2]);\n    }\n\n    jerasure_free_schedule(cache[e1 * (k + m) + e1]);\n  }\n\n  free(cache);\n}\n\nvoid jerasure_matrix_dotprod(int k, int w, int* matrix_row,\n                             int* src_ids, int dest_id,\n                             char** data_ptrs, char** coding_ptrs, int size)\n{\n  int init;\n  char* dptr, *sptr;\n  int i;\n\n  if (w != 1 && w != 8 && w != 16 && w != 32) {\n    fprintf(stderr,\n            \"ERROR: jerasure_matrix_dotprod() called and w is not 1, 8, 16 or 32\\n\");\n    assert(0);\n  }\n\n  init = 0;\n  dptr = (dest_id < k) ? data_ptrs[dest_id] : coding_ptrs[dest_id - k];\n\n  /* First copy or xor any data that does not need to be multiplied by a factor */\n\n  for (i = 0; i < k; i++) {\n    if (matrix_row[i] == 1) {\n      if (src_ids == NULL) {\n        sptr = data_ptrs[i];\n      } else if (src_ids[i] < k) {\n        sptr = data_ptrs[src_ids[i]];\n      } else {\n        sptr = coding_ptrs[src_ids[i] - k];\n      }\n\n      if (init == 0) {\n        memcpy(dptr, sptr, size);\n        jerasure_total_memcpy_bytes += size;\n        init = 1;\n      } else {\n        galois_region_xor(sptr, dptr, size);\n        jerasure_total_xor_bytes += size;\n      }\n    }\n  }\n\n  /* Now do the data that needs to be multiplied by a factor */\n\n  for (i = 0; i < k; i++) {\n    if (matrix_row[i] != 0 && matrix_row[i] != 1) {\n      if (src_ids == NULL) {\n        sptr = data_ptrs[i];\n      } else if (src_ids[i] < k) {\n        sptr = data_ptrs[src_ids[i]];\n      } else {\n        sptr = coding_ptrs[src_ids[i] - k];\n      }\n\n      switch (w) {\n      case 8:\n        galois_w08_region_multiply(sptr, matrix_row[i], size, dptr, init);\n        break;\n\n      case 16:\n        galois_w16_region_multiply(sptr, matrix_row[i], size, dptr, init);\n        break;\n\n      case 32:\n        galois_w32_region_multiply(sptr, matrix_row[i], size, dptr, init);\n        break;\n      }\n\n      jerasure_total_gf_bytes += size;\n      init = 1;\n    }\n  }\n}\n\n\nint jerasure_bitmatrix_decode(int k, int m, int w, int* bitmatrix,\n                              int row_k_ones, int* erasures,\n                              char** data_ptrs, char** coding_ptrs, int size, int packetsize)\n{\n  int i;\n  int* erased;\n  int* decoding_matrix;\n  int* dm_ids;\n  int edd, *tmpids, lastdrive;\n  erased = jerasure_erasures_to_erased(k, m, erasures);\n\n  if (erased == NULL) {\n    return -1;\n  }\n\n  /* See jerasure_matrix_decode for the logic of this routine.  This one works just like\n     it, but calls the bitmatrix ops instead */\n  lastdrive = k;\n  edd = 0;\n\n  for (i = 0; i < k; i++) {\n    if (erased[i]) {\n      edd++;\n      lastdrive = i;\n    }\n  }\n\n  if (row_k_ones != 1 || erased[k]) {\n    lastdrive = k;\n  }\n\n  dm_ids = NULL;\n  decoding_matrix = NULL;\n\n  if (edd > 1 || (edd > 0 && (row_k_ones != 1 || erased[k]))) {\n    dm_ids = talloc(int, k);\n\n    if (dm_ids == NULL) {\n      free(erased);\n      return -1;\n    }\n\n    decoding_matrix = talloc(int, k * k * w * w);\n\n    if (decoding_matrix == NULL) {\n      free(erased);\n      free(dm_ids);\n      return -1;\n    }\n\n    if (jerasure_make_decoding_bitmatrix(k, m, w, bitmatrix, erased,\n                                         decoding_matrix, dm_ids) < 0) {\n      free(erased);\n      free(dm_ids);\n      free(decoding_matrix);\n      return -1;\n    }\n  }\n\n  for (i = 0; edd > 0 && i < lastdrive; i++) {\n    if (erased[i]) {\n      jerasure_bitmatrix_dotprod(k, w, decoding_matrix + i * k * w * w, dm_ids, i,\n                                 data_ptrs, coding_ptrs, size, packetsize);\n      edd--;\n    }\n  }\n\n  if (edd > 0) {\n    tmpids = talloc(int, k);\n\n    if (!tmpids) {\n      free(erased);\n      free(dm_ids);\n      free(decoding_matrix);\n      return -1;\n    }\n\n    for (i = 0; i < k; i++) {\n      tmpids[i] = (i < lastdrive) ? i : i + 1;\n    }\n\n    jerasure_bitmatrix_dotprod(k, w, bitmatrix, tmpids, lastdrive, data_ptrs,\n                               coding_ptrs, size, packetsize);\n    free(tmpids);\n  }\n\n  for (i = 0; i < m; i++) {\n    if (erased[k + i]) {\n      jerasure_bitmatrix_dotprod(k, w, bitmatrix + i * k * w * w, NULL, k + i,\n                                 data_ptrs, coding_ptrs, size, packetsize);\n    }\n  }\n\n  free(erased);\n\n  if (dm_ids != NULL) {\n    free(dm_ids);\n  }\n\n  if (decoding_matrix != NULL) {\n    free(decoding_matrix);\n  }\n\n  return 0;\n}\n\nstatic char** set_up_ptrs_for_scheduled_decoding(int k, int m, int* erasures,\n    char** data_ptrs, char** coding_ptrs)\n{\n  int ddf, cdf;\n  int* erased;\n  char** ptrs;\n  int i, j, x;\n  ddf = 0;\n  cdf = 0;\n\n  for (i = 0; erasures[i] != -1; i++) {\n    if (erasures[i] < k) {\n      ddf++;\n    } else {\n      cdf++;\n    }\n  }\n\n  erased = jerasure_erasures_to_erased(k, m, erasures);\n\n  if (erased == NULL) {\n    return NULL;\n  }\n\n  /* Set up ptrs.  It will be as follows:\n\n       - If data drive i has not failed, then ptrs[i] = data_ptrs[i].\n       - If data drive i has failed, then ptrs[i] = coding_ptrs[j], where j is the\n            lowest unused non-failed coding drive.\n       - Elements k to k+ddf-1 are data_ptrs[] of the failed data drives.\n       - Elements k+ddf to k+ddf+cdf-1 are coding_ptrs[] of the failed data drives.\n\n       The array row_ids contains the ids of ptrs.\n       The array ind_to_row_ids contains the row_id of drive i.\n\n       However, we're going to set row_ids and ind_to_row in a different procedure.\n   */\n  ptrs = talloc(char*, k + m);\n\n  if (!ptrs) {\n    free(erased);\n    return NULL;\n  }\n\n  j = k;\n  x = k;\n\n  for (i = 0; i < k; i++) {\n    if (erased[i] == 0) {\n      ptrs[i] = data_ptrs[i];\n    } else {\n      while (erased[j]) {\n        j++;\n      }\n\n      ptrs[i] = coding_ptrs[j - k];\n      j++;\n      ptrs[x] = data_ptrs[i];\n      x++;\n    }\n  }\n\n  for (i = k; i < k + m; i++) {\n    if (erased[i]) {\n      ptrs[x] = coding_ptrs[i - k];\n      x++;\n    }\n  }\n\n  free(erased);\n  return ptrs;\n}\n\nstatic int set_up_ids_for_scheduled_decoding(int k, int m, int* erasures,\n    int* row_ids, int* ind_to_row)\n{\n  int ddf, cdf;\n  int* erased;\n  int i, j, x;\n  ddf = 0;\n  cdf = 0;\n\n  for (i = 0; erasures[i] != -1; i++) {\n    if (erasures[i] < k) {\n      ddf++;\n    } else {\n      cdf++;\n    }\n  }\n\n  erased = jerasure_erasures_to_erased(k, m, erasures);\n\n  if (erased == NULL) {\n    return -1;\n  }\n\n  /* See set_up_ptrs_for_scheduled_decoding for how these are set */\n  j = k;\n  x = k;\n\n  for (i = 0; i < k; i++) {\n    if (erased[i] == 0) {\n      row_ids[i] = i;\n      ind_to_row[i] = i;\n    } else {\n      while (erased[j]) {\n        j++;\n      }\n\n      row_ids[i] = j;\n      ind_to_row[j] = i;\n      j++;\n      row_ids[x] = i;\n      ind_to_row[i] = x;\n      x++;\n    }\n  }\n\n  for (i = k; i < k + m; i++) {\n    if (erased[i]) {\n      row_ids[x] = i;\n      ind_to_row[i] = x;\n      x++;\n    }\n  }\n\n  free(erased);\n  return 0;\n}\n\nstatic int** jerasure_generate_decoding_schedule(int k, int m, int w,\n    int* bitmatrix, int* erasures, int smart)\n{\n  int i, j, x, drive, y, index, z;\n  int* decoding_matrix, *inverse, *real_decoding_matrix;\n  int* ptr;\n  int* row_ids;\n  int* ind_to_row;\n  int ddf, cdf;\n  int** schedule;\n  int* b1, *b2;\n  /* First, figure out the number of data drives that have failed, and the\n     number of coding drives that have failed: ddf and cdf */\n  ddf = 0;\n  cdf = 0;\n\n  for (i = 0; erasures[i] != -1; i++) {\n    if (erasures[i] < k) {\n      ddf++;\n    } else {\n      cdf++;\n    }\n  }\n\n  row_ids = talloc(int, k + m);\n\n  if (!row_ids) {\n    return NULL;\n  }\n\n  ind_to_row = talloc(int, k + m);\n\n  if (!ind_to_row) {\n    free(row_ids);\n    return NULL;\n  }\n\n  if (set_up_ids_for_scheduled_decoding(k, m, erasures, row_ids,\n                                        ind_to_row) < 0) {\n    free(row_ids);\n    free(ind_to_row);\n    return NULL;\n  }\n\n  /* Now, we're going to create one decoding matrix which is going to\n     decode everything with one call.  The hope is that the scheduler\n     will do a good job.    This matrix has w*e rows, where e is the\n     number of erasures (ddf+cdf) */\n  real_decoding_matrix = talloc(int, k * w * (cdf + ddf) * w);\n\n  if (!real_decoding_matrix) {\n    free(row_ids);\n    free(ind_to_row);\n    return NULL;\n  }\n\n  /* First, if any data drives have failed, then initialize the first\n     ddf*w rows of the decoding matrix from the standard decoding\n     matrix inversion */\n\n  if (ddf > 0) {\n    decoding_matrix = talloc(int, k * k * w * w);\n\n    if (!decoding_matrix) {\n      free(row_ids);\n      free(ind_to_row);\n      free(real_decoding_matrix);\n      return NULL;\n    }\n\n    ptr = decoding_matrix;\n\n    for (i = 0; i < k; i++) {\n      if (row_ids[i] == i) {\n        bzero(ptr, k * w * w * sizeof(int));\n\n        for (x = 0; x < w; x++) {\n          ptr[x + i * w + x * k * w] = 1;\n        }\n      } else {\n        memcpy(ptr, bitmatrix + k * w * w * (row_ids[i] - k), k * w * w * sizeof(int));\n      }\n\n      ptr += (k * w * w);\n    }\n\n    inverse = talloc(int, k * k * w * w);\n\n    if (!inverse) {\n      free(row_ids);\n      free(ind_to_row);\n      free(real_decoding_matrix);\n      free(decoding_matrix);\n      return NULL;\n    }\n\n    jerasure_invert_bitmatrix(decoding_matrix, inverse, k * w);\n    /*    printf(\"\\nMatrix to invert\\n\");\n        jerasure_print_bitmatrix(decoding_matrix, k*w, k*w, w);\n        printf(\"\\n\");\n        printf(\"\\nInverse\\n\");\n        jerasure_print_bitmatrix(inverse, k*w, k*w, w);\n        printf(\"\\n\"); */\n    free(decoding_matrix);\n    ptr = real_decoding_matrix;\n\n    for (i = 0; i < ddf; i++) {\n      memcpy(ptr, inverse + k * w * w * row_ids[k + i], sizeof(int)*k * w * w);\n      ptr += (k * w * w);\n    }\n\n    free(inverse);\n  }\n\n  /* Next, here comes the hard part.  For each coding node that needs\n     to be decoded, you start by putting its rows of the distribution\n     matrix into the decoding matrix.  If there were no failed data\n     nodes, then you're done.  However, if there have been failed\n     data nodes, then you need to modify the columns that correspond\n     to the data nodes.  You do that by first zeroing them.  Then\n     whereever there is a one in the distribution matrix, you XOR\n     in the corresponding row from the failed data node's entry in\n     the decoding matrix.  The whole process kind of makes my head\n     spin, but it works.\n   */\n\n  for (x = 0; x < cdf; x++) {\n    drive = row_ids[x + ddf + k] - k;\n    ptr = real_decoding_matrix + k * w * w * (ddf + x);\n    memcpy(ptr, bitmatrix + drive * k * w * w, sizeof(int)*k * w * w);\n\n    for (i = 0; i < k; i++) {\n      if (row_ids[i] != i) {\n        for (j = 0; j < w; j++) {\n          bzero(ptr + j * k * w + i * w, sizeof(int)*w);\n        }\n      }\n    }\n\n    /* There's the yucky part */\n    index = drive * k * w * w;\n\n    for (i = 0; i < k; i++) {\n      if (row_ids[i] != i) {\n        b1 = real_decoding_matrix + (ind_to_row[i] - k) * k * w * w;\n\n        for (j = 0; j < w; j++) {\n          b2 = ptr + j * k * w;\n\n          for (y = 0; y < w; y++) {\n            if (bitmatrix[index + j * k * w + i * w + y]) {\n              for (z = 0; z < k * w; z++) {\n                b2[z] = b2[z] ^ b1[z + y * k * w];\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  /*\n    printf(\"\\n\\nReal Decoding Matrix\\n\\n\");\n    jerasure_print_bitmatrix(real_decoding_matrix, (ddf+cdf)*w, k*w, w);\n    printf(\"\\n\"); */\n  if (smart) {\n    schedule = jerasure_smart_bitmatrix_to_schedule(k, ddf + cdf, w,\n               real_decoding_matrix);\n  } else {\n    schedule = jerasure_dumb_bitmatrix_to_schedule(k, ddf + cdf, w,\n               real_decoding_matrix);\n  }\n\n  free(row_ids);\n  free(ind_to_row);\n  free(real_decoding_matrix);\n  return schedule;\n}\n\nint jerasure_schedule_decode_lazy(int k, int m, int w, int* bitmatrix,\n                                  int* erasures,\n                                  char** data_ptrs, char** coding_ptrs, int size, int packetsize,\n                                  int smart)\n{\n  int i, tdone;\n  char** ptrs;\n  int** schedule;\n  ptrs = set_up_ptrs_for_scheduled_decoding(k, m, erasures, data_ptrs,\n         coding_ptrs);\n\n  if (ptrs == NULL) {\n    return -1;\n  }\n\n  schedule = jerasure_generate_decoding_schedule(k, m, w, bitmatrix, erasures,\n             smart);\n\n  if (schedule == NULL) {\n    free(ptrs);\n    return -1;\n  }\n\n  for (tdone = 0; tdone < size; tdone += packetsize * w) {\n    jerasure_do_scheduled_operations(ptrs, schedule, packetsize);\n\n    for (i = 0; i < k + m; i++) {\n      ptrs[i] += (packetsize * w);\n    }\n  }\n\n  jerasure_free_schedule(schedule);\n  free(ptrs);\n  return 0;\n}\n\nint jerasure_schedule_decode_cache(int k, int m, int w, int** *scache,\n                                   int* erasures,\n                                   char** data_ptrs, char** coding_ptrs, int size, int packetsize)\n{\n  int i, tdone;\n  char** ptrs;\n  int** schedule;\n  int index;\n\n  if (erasures[1] == -1) {\n    index = erasures[0] * (k + m) + erasures[0];\n  } else if (erasures[2] == -1) {\n    index = erasures[0] * (k + m) + erasures[1];\n  } else {\n    return -1;\n  }\n\n  schedule = scache[index];\n  ptrs = set_up_ptrs_for_scheduled_decoding(k, m, erasures, data_ptrs,\n         coding_ptrs);\n\n  if (ptrs == NULL) {\n    return -1;\n  }\n\n  for (tdone = 0; tdone < size; tdone += packetsize * w) {\n    jerasure_do_scheduled_operations(ptrs, schedule, packetsize);\n\n    for (i = 0; i < k + m; i++) {\n      ptrs[i] += (packetsize * w);\n    }\n  }\n\n  free(ptrs);\n  return 0;\n}\n\n/* This only works when m = 2 */\n\nint*** jerasure_generate_schedule_cache(int k, int m, int w, int* bitmatrix,\n                                        int smart)\n{\n  int** *scache;\n  int erasures[3];\n  int e1, e2;\n\n  /* Ok -- this is yucky, but it's how I'm doing it.  You will make an index out\n     of erasures, which will be  e1*(k+m)+(e2).  If there is no e2, then e2 = e1.\n     Isn't that clever and confusing.  Sorry.\n\n     We're not going to worry about ordering -- in other words, the schedule for\n     e1,e2 will be the same as e2,e1.  They will have the same pointer -- the\n     schedule will not be duplicated. */\n\n  if (m != 2) {\n    return NULL;\n  }\n\n  scache = talloc(int**, (k + m) * (k + m + 1));\n\n  if (scache == NULL) {\n    return NULL;\n  }\n\n  for (e1 = 0; e1 < k + m; e1++) {\n    erasures[0] = e1;\n\n    for (e2 = 0; e2 < e1; e2++) {\n      erasures[1] = e2;\n      erasures[2] = -1;\n      scache[e1 * (k + m) + e2] = jerasure_generate_decoding_schedule(k, m, w,\n                                  bitmatrix, erasures, smart);\n      scache[e2 * (k + m) + e1] = scache[e1 * (k + m) + e2];\n    }\n\n    erasures[1] = -1;\n    scache[e1 * (k + m) + e1] = jerasure_generate_decoding_schedule(k, m, w,\n                                bitmatrix, erasures, smart);\n  }\n\n  return scache;\n}\n\nint jerasure_invert_bitmatrix(int* mat, int* inv, int rows)\n{\n  int cols, i, j, k;\n  int tmp;\n  cols = rows;\n  k = 0;\n\n  for (i = 0; i < rows; i++) {\n    for (j = 0; j < cols; j++) {\n      inv[k] = (i == j) ? 1 : 0;\n      k++;\n    }\n  }\n\n  /* First -- convert into upper triangular */\n\n  for (i = 0; i < cols; i++) {\n    /* Swap rows if we have a zero i,i element.  If we can't swap, then the\n       matrix was not invertible */\n    if ((mat[i * cols + i]) == 0) {\n      for (j = i + 1; j < rows && (mat[j * cols + i]) == 0; j++) ;\n\n      if (j == rows) {\n        return -1;\n      }\n\n      for (k = 0; k < cols; k++) {\n        tmp = mat[i * cols + k];\n        mat[i * cols + k] = mat[j * cols + k];\n        mat[j * cols + k] = tmp;\n        tmp = inv[i * cols + k];\n        inv[i * cols + k] = inv[j * cols + k];\n        inv[j * cols + k] = tmp;\n      }\n    }\n\n    /* Now for each j>i, add A_ji*Ai to Aj */\n    for (j = i + 1; j != rows; j++) {\n      if (mat[j * cols + i] != 0) {\n        for (k = 0; k < cols; k++) {\n          mat[j * cols + k] ^= mat[i * cols + k];\n          inv[j * cols + k] ^= inv[i * cols + k];\n        }\n      }\n    }\n  }\n\n  /* Now the matrix is upper triangular.  Start at the top and multiply down */\n\n  for (i = rows - 1; i >= 0; i--) {\n    for (j = 0; j < i; j++) {\n      if (mat[j * cols + i]) {\n        for (k = 0; k < cols; k++) {\n          mat[j * cols + k] ^= mat[i * cols + k];\n          inv[j * cols + k] ^= inv[i * cols + k];\n        }\n      }\n    }\n  }\n\n  return 0;\n}\n\nint jerasure_invertible_bitmatrix(int* mat, int rows)\n{\n  int cols, i, j, k;\n  int tmp;\n  cols = rows;\n\n  /* First -- convert into upper triangular */\n\n  for (i = 0; i < cols; i++) {\n    /* Swap rows if we have a zero i,i element.  If we can't swap, then the\n       matrix was not invertible */\n    if ((mat[i * cols + i]) == 0) {\n      for (j = i + 1; j < rows && (mat[j * cols + i]) == 0; j++) ;\n\n      if (j == rows) {\n        return 0;\n      }\n\n      for (k = 0; k < cols; k++) {\n        tmp = mat[i * cols + k];\n        mat[i * cols + k] = mat[j * cols + k];\n        mat[j * cols + k] = tmp;\n      }\n    }\n\n    /* Now for each j>i, add A_ji*Ai to Aj */\n    for (j = i + 1; j != rows; j++) {\n      if (mat[j * cols + i] != 0) {\n        for (k = 0; k < cols; k++) {\n          mat[j * cols + k] ^= mat[i * cols + k];\n        }\n      }\n    }\n  }\n\n  return 1;\n}\n\n\nint* jerasure_matrix_multiply(int* m1, int* m2, int r1, int c1, int r2, int c2,\n                              int w)\n{\n  int* product, i, j, k;\n  product = (int*) malloc(sizeof(int) * r1 * c2);\n\n  for (i = 0; i < r1 * c2; i++) {\n    product[i] = 0;\n  }\n\n  for (i = 0; i < r1; i++) {\n    for (j = 0; j < c2; j++) {\n      for (k = 0; k < r2; k++) {\n        product[i * c2 + j] ^= galois_single_multiply(m1[i * c1 + k], m2[k * c2 + j],\n                               w);\n      }\n    }\n  }\n\n  return product;\n}\n\nvoid jerasure_get_stats(double* fill_in)\n{\n  fill_in[0] = jerasure_total_xor_bytes;\n  fill_in[1] = jerasure_total_gf_bytes;\n  fill_in[2] = jerasure_total_memcpy_bytes;\n  jerasure_total_xor_bytes = 0;\n  jerasure_total_gf_bytes = 0;\n  jerasure_total_memcpy_bytes = 0;\n}\n\nvoid jerasure_do_scheduled_operations(char** ptrs, int** operations,\n                                      int packetsize)\n{\n  char* sptr;\n  char* dptr;\n  int op;\n\n  for (op = 0; operations[op][0] >= 0; op++) {\n    sptr = ptrs[operations[op][0]] + operations[op][1] * packetsize;\n    dptr = ptrs[operations[op][2]] + operations[op][3] * packetsize;\n\n    if (operations[op][4]) {\n      /*      printf(\"%d,%d %d,%d\\n\", operations[op][0],\n            operations[op][1],\n            operations[op][2],\n            operations[op][3]);\n            printf(\"xor(0x%x, 0x%x -> 0x%x, %d)\\n\", sptr, dptr, dptr, packetsize); */\n      galois_region_xor(sptr, dptr, packetsize);\n      jerasure_total_xor_bytes += packetsize;\n    } else {\n      /*      printf(\"memcpy(0x%x <- 0x%x)\\n\", dptr, sptr); */\n      memcpy(dptr, sptr, packetsize);\n      jerasure_total_memcpy_bytes += packetsize;\n    }\n  }\n}\n\nvoid jerasure_schedule_encode(int k, int m, int w, int** schedule,\n                              char** data_ptrs, char** coding_ptrs, int size, int packetsize)\n{\n  char** ptr_copy;\n  int i, tdone;\n  ptr_copy = talloc(char*, (k + m));\n\n  for (i = 0; i < k; i++) {\n    ptr_copy[i] = data_ptrs[i];\n  }\n\n  for (i = 0; i < m; i++) {\n    ptr_copy[i + k] = coding_ptrs[i];\n  }\n\n  for (tdone = 0; tdone < size; tdone += packetsize * w) {\n    jerasure_do_scheduled_operations(ptr_copy, schedule, packetsize);\n\n    for (i = 0; i < k + m; i++) {\n      ptr_copy[i] += (packetsize * w);\n    }\n  }\n\n  free(ptr_copy);\n}\n\nint** jerasure_dumb_bitmatrix_to_schedule(int k, int m, int w, int* bitmatrix)\n{\n  int** operations;\n  int op;\n  int index, optodo, i, j;\n  operations = talloc(int*, k * m * w * w + 1);\n\n  if (!operations) {\n    return NULL;\n  }\n\n  op = 0;\n  index = 0;\n\n  for (i = 0; i < m * w; i++) {\n    optodo = 0;\n\n    for (j = 0; j < k * w; j++) {\n      if (bitmatrix[index]) {\n        operations[op] = talloc(int, 5);\n\n        if (!operations[op]) {\n          // -ENOMEM\n          goto error;\n        }\n\n        operations[op][4] = optodo;\n        operations[op][0] = j / w;\n        operations[op][1] = j % w;\n        operations[op][2] = k + i / w;\n        operations[op][3] = i % w;\n        optodo = 1;\n        op++;\n      }\n\n      index++;\n    }\n  }\n\n  operations[op] = talloc(int, 5);\n\n  if (!operations[op]) {\n    // -ENOMEM\n    goto error;\n  }\n\n  operations[op][0] = -1;\n  return operations;\nerror:\n\n  for (i = 0; i <= op; i++) {\n    free(operations[op]);\n  }\n\n  free(operations);\n  return NULL;\n}\n\nint** jerasure_smart_bitmatrix_to_schedule(int k, int m, int w, int* bitmatrix)\n{\n  int** operations;\n  int op;\n  int i, j;\n  int* diff, *from, *b1, *flink, *blink;\n  int* ptr, no, row;\n  int optodo;\n  int bestrow = 0, bestdiff, top;\n  /*   printf(\"Scheduling:\\n\\n\");\n    jerasure_print_bitmatrix(bitmatrix, m*w, k*w, w); */\n  operations = talloc(int*, k * m * w * w + 1);\n\n  if (!operations) {\n    return NULL;\n  }\n\n  op = 0;\n  diff = talloc(int, m * w);\n\n  if (!diff) {\n    free(operations);\n    return NULL;\n  }\n\n  from = talloc(int, m * w);\n\n  if (!from) {\n    free(operations);\n    free(diff);\n    return NULL;\n  }\n\n  flink = talloc(int, m * w);\n\n  if (!flink) {\n    free(operations);\n    free(diff);\n    free(from);\n    return NULL;\n  }\n\n  blink = talloc(int, m * w);\n\n  if (!blink) {\n    free(operations);\n    free(diff);\n    free(from);\n    free(flink);\n    return NULL;\n  }\n\n  ptr = bitmatrix;\n  bestdiff = k * w + 1;\n  top = 0;\n\n  for (i = 0; i < m * w; i++) {\n    no = 0;\n\n    for (j = 0; j < k * w; j++) {\n      no += *ptr;\n      ptr++;\n    }\n\n    diff[i] = no;\n    from[i] = -1;\n    flink[i] = i + 1;\n    blink[i] = i - 1;\n\n    if (no < bestdiff) {\n      bestdiff = no;\n      bestrow = i;\n    }\n  }\n\n  flink[m * w - 1] = -1;\n\n  while (top != -1) {\n    row = bestrow;\n    /* printf(\"Doing row %d - %d from %d\\n\", row, diff[row], from[row]);  */\n\n    if (blink[row] == -1) {\n      top = flink[row];\n\n      if (top != -1) {\n        blink[top] = -1;\n      }\n    } else {\n      flink[blink[row]] = flink[row];\n\n      if (flink[row] != -1) {\n        blink[flink[row]] = blink[row];\n      }\n    }\n\n    ptr = bitmatrix + row * k * w;\n\n    if (from[row] == -1) {\n      optodo = 0;\n\n      for (j = 0; j < k * w; j++) {\n        if (ptr[j]) {\n          operations[op] = talloc(int, 5);\n\n          if (!operations[op]) {\n            goto error;\n          }\n\n          operations[op][4] = optodo;\n          operations[op][0] = j / w;\n          operations[op][1] = j % w;\n          operations[op][2] = k + row / w;\n          operations[op][3] = row % w;\n          optodo = 1;\n          op++;\n        }\n      }\n    } else {\n      operations[op] = talloc(int, 5);\n\n      if (!operations[op]) {\n        goto error;\n      }\n\n      operations[op][4] = 0;\n      operations[op][0] = k + from[row] / w;\n      operations[op][1] = from[row] % w;\n      operations[op][2] = k + row / w;\n      operations[op][3] = row % w;\n      op++;\n      b1 = bitmatrix + from[row] * k * w;\n\n      for (j = 0; j < k * w; j++) {\n        if (ptr[j] ^ b1[j]) {\n          operations[op] = talloc(int, 5);\n\n          if (!operations[op]) {\n            goto error;\n          }\n\n          operations[op][4] = 1;\n          operations[op][0] = j / w;\n          operations[op][1] = j % w;\n          operations[op][2] = k + row / w;\n          operations[op][3] = row % w;\n          optodo = 1;\n          op++;\n        }\n      }\n    }\n\n    bestdiff = k * w + 1;\n\n    for (i = top; i != -1; i = flink[i]) {\n      no = 1;\n      b1 = bitmatrix + i * k * w;\n\n      for (j = 0; j < k * w; j++) {\n        no += (ptr[j] ^ b1[j]);\n      }\n\n      if (no < diff[i]) {\n        from[i] = row;\n        diff[i] = no;\n      }\n\n      if (diff[i] < bestdiff) {\n        bestdiff = diff[i];\n        bestrow = i;\n      }\n    }\n  }\n\n  operations[op] = talloc(int, 5);\n\n  if (!operations[op]) {\n    goto error;\n  }\n\n  operations[op][0] = -1;\n  free(from);\n  free(diff);\n  free(blink);\n  free(flink);\n  return operations;\nerror:\n\n  for (i = 0; i <= op; i++) {\n    free(operations[op]);\n  }\n\n  free(operations);\n  free(from);\n  free(diff);\n  free(blink);\n  free(flink);\n  return NULL;\n}\n\nvoid jerasure_bitmatrix_encode(int k, int m, int w, int* bitmatrix,\n                               char** data_ptrs, char** coding_ptrs, int size, int packetsize)\n{\n  int i;\n\n  if (packetsize % sizeof(long) != 0) {\n    fprintf(stderr,\n            \"jerasure_bitmatrix_encode - packetsize(%d) %c sizeof(long) != 0\\n\", packetsize,\n            '%');\n    assert(0);\n  }\n\n  if (size % (packetsize * w) != 0) {\n    fprintf(stderr,\n            \"jerasure_bitmatrix_encode - size(%d) %c (packetsize(%d)*w(%d))) != 0\\n\",\n            size, '%', packetsize, w);\n    assert(0);\n  }\n\n  for (i = 0; i < m; i++) {\n    jerasure_bitmatrix_dotprod(k, w, bitmatrix + i * k * w * w, NULL, k + i,\n                               data_ptrs, coding_ptrs, size, packetsize);\n  }\n}\n\n/*\n * Exported function for use by autoconf to perform quick\n * spot-check.\n */\nint jerasure_autoconf_test()\n{\n  int x = galois_single_multiply(1, 2, 8);\n\n  if (x != 2) {\n    return -1;\n  }\n\n  return 0;\n}\n\n"
  },
  {
    "path": "fst/layout/jerasure/src/liberation.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"galois.h\"\n#include \"jerasure.h\"\n#include \"liberation.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nint *liberation_coding_bitmatrix(int k, int w)\n{\n  int *matrix, i, j, index;\n\n  if (k > w) return NULL;\n  matrix = talloc(int, 2*k*w*w);\n  if (matrix == NULL) return NULL;\n  bzero(matrix, sizeof(int)*2*k*w*w);\n  \n  /* Set up identity matrices */\n\n  for(i = 0; i < w; i++) {\n    index = i*k*w+i;\n    for (j = 0; j < k; j++) {\n      matrix[index] = 1;\n      index += w;\n    }\n  }\n\n  /* Set up liberation matrices */\n\n  for (j = 0; j < k; j++) {\n    index = k*w*w+j*w;\n    for (i = 0; i < w; i++) {\n      matrix[index+(j+i)%w] = 1;\n      index += (k*w);\n    }\n    if (j > 0) {\n      i = (j*((w-1)/2))%w;\n      matrix[k*w*w+j*w+i*k*w+(i+j-1)%w] = 1;\n    }\n  }\n  return matrix;\n}\n  \n\nint *liber8tion_coding_bitmatrix(int k)\n{\n  int *matrix, i, j, index;\n  int w;\n\n  w = 8;\n  if (k > w) return NULL;\n  matrix = talloc(int, 2*k*w*w);\n  if (matrix == NULL) return NULL;\n  bzero(matrix, sizeof(int)*2*k*w*w);\n  \n  /* Set up identity matrices */\n\n  for(i = 0; i < w; i++) {\n    index = i*k*w+i;\n    for (j = 0; j < k; j++) {\n      matrix[index] = 1;\n      index += w;\n    }\n  }\n\n  /* Set up liber8tion matrices */\n\n  index = k*w*w;\n\n  if (k == 0) return matrix;\n  matrix[index+0*k*w+0*w+0] = 1;\n  matrix[index+1*k*w+0*w+1] = 1;\n  matrix[index+2*k*w+0*w+2] = 1;\n  matrix[index+3*k*w+0*w+3] = 1;\n  matrix[index+4*k*w+0*w+4] = 1;\n  matrix[index+5*k*w+0*w+5] = 1;\n  matrix[index+6*k*w+0*w+6] = 1;\n  matrix[index+7*k*w+0*w+7] = 1;\n\n  if (k == 1) return matrix;\n  matrix[index+0*k*w+1*w+7] = 1;\n  matrix[index+1*k*w+1*w+3] = 1;\n  matrix[index+2*k*w+1*w+0] = 1;\n  matrix[index+3*k*w+1*w+2] = 1;\n  matrix[index+4*k*w+1*w+6] = 1;\n  matrix[index+5*k*w+1*w+1] = 1;\n  matrix[index+6*k*w+1*w+5] = 1;\n  matrix[index+7*k*w+1*w+4] = 1;\n  matrix[index+4*k*w+1*w+7] = 1;\n\n  if (k == 2) return matrix;\n  matrix[index+0*k*w+2*w+6] = 1;\n  matrix[index+1*k*w+2*w+2] = 1;\n  matrix[index+2*k*w+2*w+4] = 1;\n  matrix[index+3*k*w+2*w+0] = 1;\n  matrix[index+4*k*w+2*w+7] = 1;\n  matrix[index+5*k*w+2*w+3] = 1;\n  matrix[index+6*k*w+2*w+1] = 1;\n  matrix[index+7*k*w+2*w+5] = 1;\n  matrix[index+1*k*w+2*w+3] = 1;\n\n  if (k == 3) return matrix;\n  matrix[index+0*k*w+3*w+2] = 1;\n  matrix[index+1*k*w+3*w+5] = 1;\n  matrix[index+2*k*w+3*w+7] = 1;\n  matrix[index+3*k*w+3*w+6] = 1;\n  matrix[index+4*k*w+3*w+0] = 1;\n  matrix[index+5*k*w+3*w+3] = 1;\n  matrix[index+6*k*w+3*w+4] = 1;\n  matrix[index+7*k*w+3*w+1] = 1;\n  matrix[index+5*k*w+3*w+4] = 1;\n\n  if (k == 4) return matrix;\n  matrix[index+0*k*w+4*w+5] = 1;\n  matrix[index+1*k*w+4*w+6] = 1;\n  matrix[index+2*k*w+4*w+1] = 1;\n  matrix[index+3*k*w+4*w+7] = 1;\n  matrix[index+4*k*w+4*w+2] = 1;\n  matrix[index+5*k*w+4*w+4] = 1;\n  matrix[index+6*k*w+4*w+3] = 1;\n  matrix[index+7*k*w+4*w+0] = 1;\n  matrix[index+2*k*w+4*w+0] = 1;\n\n  if (k == 5) return matrix;\n  matrix[index+0*k*w+5*w+1] = 1;\n  matrix[index+1*k*w+5*w+2] = 1;\n  matrix[index+2*k*w+5*w+3] = 1;\n  matrix[index+3*k*w+5*w+4] = 1;\n  matrix[index+4*k*w+5*w+5] = 1;\n  matrix[index+5*k*w+5*w+6] = 1;\n  matrix[index+6*k*w+5*w+7] = 1;\n  matrix[index+7*k*w+5*w+0] = 1;\n  matrix[index+7*k*w+5*w+2] = 1;\n\n  if (k == 6) return matrix;\n  matrix[index+0*k*w+6*w+3] = 1;\n  matrix[index+1*k*w+6*w+0] = 1;\n  matrix[index+2*k*w+6*w+6] = 1;\n  matrix[index+3*k*w+6*w+5] = 1;\n  matrix[index+4*k*w+6*w+1] = 1;\n  matrix[index+5*k*w+6*w+7] = 1;\n  matrix[index+6*k*w+6*w+4] = 1;\n  matrix[index+7*k*w+6*w+2] = 1;\n  matrix[index+6*k*w+6*w+5] = 1;\n\n  if (k == 7) return matrix;\n  matrix[index+0*k*w+7*w+4] = 1;\n  matrix[index+1*k*w+7*w+7] = 1;\n  matrix[index+2*k*w+7*w+1] = 1;\n  matrix[index+3*k*w+7*w+5] = 1;\n  matrix[index+4*k*w+7*w+3] = 1;\n  matrix[index+5*k*w+7*w+2] = 1;\n  matrix[index+6*k*w+7*w+0] = 1;\n  matrix[index+7*k*w+7*w+6] = 1;\n  matrix[index+3*k*w+7*w+1] = 1;\n\n  return matrix;\n}\n  \nint *blaum_roth_coding_bitmatrix(int k, int w)\n{\n  int *matrix, i, j, index, l, m, p;\n\n  if (k > w) return NULL ;\n\n  matrix = talloc(int, 2*k*w*w);\n  if (matrix == NULL) return NULL;\n  bzero(matrix, sizeof(int)*2*k*w*w);\n  \n  /* Set up identity matrices */\n\n  for(i = 0; i < w; i++) {\n    index = i*k*w+i;\n    for (j = 0; j < k; j++) {\n      matrix[index] = 1;\n      index += w;\n    }\n  }\n\n  /* Set up blaum_roth matrices -- Ignore identity */\n\n  p = w+1;\n  for (j = 0; j < k; j++) {\n    index = k*w*w+j*w;\n    if (j == 0) {\n      for (l = 0; l < w; l++) {\n        matrix[index+l] = 1;\n        index += k*w;\n      }\n    } else {\n      i = j;\n      for (l = 1; l <= w; l++) {\n        if (l != p-i) {\n          m = l+i;\n          if (m >= p) m -= p;\n          m--;\n          matrix[index+m] = 1;\n        } else {\n          matrix[index+i-1] = 1;\n          if (i%2 == 0) {\n            m = i/2;\n          } else {\n            m = (p/2) + 1 + (i/2);\n          }\n          m--;\n          matrix[index+m] = 1;\n        }\n        index += k*w;\n      }\n    }\n  }\n\n  return matrix;\n}\n"
  },
  {
    "path": "fst/layout/jerasure/src/reed_sol.c",
    "content": "/* *\n * Copyright (c) 2014, James S. Plank and Kevin Greenan\n * All rights reserved.\n *\n * Jerasure - A C/C++ Library for a Variety of Reed-Solomon and RAID-6 Erasure\n * Coding Techniques\n *\n * Revision 2.0: Galois Field backend now links to GF-Complete\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n *  - Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *\n *  - Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n *  - Neither the name of the University of Tennessee nor the names of its\n *    contributors may be used to endorse or promote products derived\n *    from this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY\n * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n\n/* Jerasure's authors:\n\n   Revision 2.x - 2014: James S. Plank and Kevin M. Greenan\n   Revision 1.2 - 2008: James S. Plank, Scott Simmerman and Catherine D. Schuman.\n   Revision 1.0 - 2007: James S. Plank\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <assert.h>\n\n#include <gf_complete.h>\n#include \"galois.h\"\n#include \"jerasure.h\"\n#include \"reed_sol.h\"\n\n#define talloc(type, num) (type *) malloc(sizeof(type)*(num))\n\nint *reed_sol_r6_coding_matrix(int k, int w)\n{\n  int *matrix;\n  int i, tmp;\n\n  if (w != 8 && w != 16 && w != 32) return NULL;\n\n  matrix = talloc(int, 2*k);\n  if (matrix == NULL) return NULL;\n\n  for (i = 0; i < k; i++) matrix[i] = 1;\n  matrix[k] = 1;\n  tmp = 1;\n  for (i = 1; i < k; i++) {\n    tmp = galois_single_multiply(tmp, 2, w);\n    matrix[k+i] = tmp;\n  }\n  return matrix;\n}\n\nint *reed_sol_vandermonde_coding_matrix(int k, int m, int w)\n{\n  int i, j;\n  int *vdm, *dist;\n\n  vdm = reed_sol_big_vandermonde_distribution_matrix(k+m, k, w);\n  if (vdm == NULL) return NULL;\n  dist = talloc(int, m*k);\n  if (dist == NULL) {\n    free(vdm);\n    return NULL;\n  }\n\n  i = k*k;\n  for (j = 0; j < m*k; j++) {\n    dist[j] = vdm[i];\n    i++;\n  }\n  free(vdm);\n  return dist;\n}\n\nstatic int prim08 = -1;\nstatic gf_t GF08;\n\nvoid reed_sol_galois_w08_region_multby_2(char *region, int nbytes)\n{\n  if (prim08 == -1) {\n    prim08 = galois_single_multiply((1 << 7), 2, 8);\n    if (!gf_init_hard(&GF08, 8, GF_MULT_BYTWO_b, GF_REGION_DEFAULT, GF_DIVIDE_DEFAULT,\n                      prim08, 0, 0, NULL, NULL)) {\n      fprintf(stderr, \"Error: Can't initialize the GF for reed_sol_galois_w08_region_multby_2\\n\");\n      assert(0);\n    }\n  }\n  GF08.multiply_region.w32(&GF08, region, region, 2, nbytes, 0);\n}\n\nstatic int prim16 = -1;\nstatic gf_t GF16;\n\nvoid reed_sol_galois_w16_region_multby_2(char *region, int nbytes)\n{\n  if (prim16 == -1) {\n    prim16 = galois_single_multiply((1 << 15), 2, 16);\n    if (!gf_init_hard(&GF16, 16, GF_MULT_BYTWO_b, GF_REGION_DEFAULT, GF_DIVIDE_DEFAULT,\n                      prim16, 0, 0, NULL, NULL)) {\n      fprintf(stderr, \"Error: Can't initialize the GF for reed_sol_galois_w16_region_multby_2\\n\");\n      assert(0);\n    }\n  }\n  GF16.multiply_region.w32(&GF16, region, region, 2, nbytes, 0);\n}\n\nstatic int prim32 = -1;\nstatic gf_t GF32;\n\nvoid reed_sol_galois_w32_region_multby_2(char *region, int nbytes)\n{\n  if (prim32 == -1) {\n    prim32 = galois_single_multiply((1 << 31), 2, 32);\n    if (!gf_init_hard(&GF32, 32, GF_MULT_BYTWO_b, GF_REGION_DEFAULT, GF_DIVIDE_DEFAULT,\n                      prim32, 0, 0, NULL, NULL)) {\n      fprintf(stderr, \"Error: Can't initialize the GF for reed_sol_galois_w32_region_multby_2\\n\");\n      assert(0);\n    }\n  }\n  GF32.multiply_region.w32(&GF32, region, region, 2, nbytes, 0);\n}\n\nint reed_sol_r6_encode(int k, int w, char **data_ptrs, char **coding_ptrs, int size)\n{\n  int i;\n\n  /* First, put the XOR into coding region 0 */\n\n  memcpy(coding_ptrs[0], data_ptrs[0], size);\n\n  for (i = 1; i < k; i++) galois_region_xor(data_ptrs[i], coding_ptrs[0], size);\n\n  /* Next, put the sum of (2^j)*Dj into coding region 1 */\n\n  memcpy(coding_ptrs[1], data_ptrs[k-1], size);\n\n  for (i = k-2; i >= 0; i--) {\n    switch (w) {\n      case 8:  reed_sol_galois_w08_region_multby_2(coding_ptrs[1], size); break;\n      case 16: reed_sol_galois_w16_region_multby_2(coding_ptrs[1], size); break;\n      case 32: reed_sol_galois_w32_region_multby_2(coding_ptrs[1], size); break;\n      default: return 0;\n    }\n\n    galois_region_xor(data_ptrs[i], coding_ptrs[1], size);\n  }\n  return 1;\n}\n\nint *reed_sol_extended_vandermonde_matrix(int rows, int cols, int w)\n{\n  int *vdm;\n  int i, j, k;\n\n  if (w < 30 && (1 << w) < rows) return NULL;\n  if (w < 30 && (1 << w) < cols) return NULL;\n\n  vdm = talloc(int, rows*cols);\n  if (vdm == NULL) { return NULL; }\n  \n  vdm[0] = 1;\n  for (j = 1; j < cols; j++) vdm[j] = 0;\n  if (rows == 1) return vdm;\n\n  i=(rows-1)*cols;\n  for (j = 0; j < cols-1; j++) vdm[i+j] = 0;\n  vdm[i+j] = 1;\n  if (rows == 2) return vdm;\n\n  for (i = 1; i < rows-1; i++) {\n    k = 1;\n    for (j = 0; j < cols; j++) {\n      vdm[i*cols+j] = k;\n      k = galois_single_multiply(k, i, w);\n    }\n  }\n  return vdm;\n}\n\nint *reed_sol_big_vandermonde_distribution_matrix(int rows, int cols, int w)\n{\n  int *dist;\n  int i, j, k;\n  int sindex, srindex, siindex, tmp;\n\n  if (cols >= rows) return NULL;\n  \n  dist = reed_sol_extended_vandermonde_matrix(rows, cols, w);\n  if (dist == NULL) return NULL;\n\n  sindex = 0;\n  for (i = 1; i < cols; i++) {\n    sindex += cols;\n\n    /* Find an appropriate row -- where i,i != 0 */\n    srindex = sindex+i;\n    for (j = i; j < rows && dist[srindex] == 0; j++) srindex += cols;\n    if (j >= rows) {   /* This should never happen if rows/w are correct */\n      fprintf(stderr, \"reed_sol_big_vandermonde_distribution_matrix(%d,%d,%d) - couldn't make matrix\\n\", \n             rows, cols, w);\n      assert(0);\n    }\n \n    /* If necessary, swap rows */\n    if (j != i) {\n      srindex -= i;\n      for (k = 0; k < cols; k++) {\n        tmp = dist[srindex+k];\n        dist[srindex+k] = dist[sindex+k];\n        dist[sindex+k] = tmp;\n      }\n    }\n  \n    /* If Element i,i is not equal to 1, multiply the column by 1/i */\n\n    if (dist[sindex+i] != 1) {\n      tmp = galois_single_divide(1, dist[sindex+i], w);\n      srindex = i;\n      for (j = 0; j < rows; j++) {\n        dist[srindex] = galois_single_multiply(tmp, dist[srindex], w);\n        srindex += cols;\n      }\n    }\n \n    /* Now, for each element in row i that is not in column 1, you need\n       to make it zero.  Suppose that this is column j, and the element\n       at i,j = e.  Then you want to replace all of column j with \n       (col-j + col-i*e).   Note, that in row i, col-i = 1 and col-j = e.\n       So (e + 1e) = 0, which is indeed what we want. */\n\n    for (j = 0; j < cols; j++) {\n      tmp = dist[sindex+j];\n      if (j != i && tmp != 0) {\n        srindex = j;\n        siindex = i;\n        for (k = 0; k < rows; k++) {\n          dist[srindex] = dist[srindex] ^ galois_single_multiply(tmp, dist[siindex], w);\n          srindex += cols;\n          siindex += cols;\n        }\n      }\n    }\n  }\n  /* We desire to have row k be all ones.  To do that, multiply\n     the entire column j by 1/dist[k,j].  Then row j by 1/dist[j,j]. */\n\n  sindex = cols*cols;\n  for (j = 0; j < cols; j++) {\n    tmp = dist[sindex];\n    if (tmp != 1) { \n      tmp = galois_single_divide(1, tmp, w);\n      srindex = sindex;\n      for (i = cols; i < rows; i++) {\n        dist[srindex] = galois_single_multiply(tmp, dist[srindex], w);\n        srindex += cols;\n      }\n    }\n    sindex++;\n  }\n\n  /* Finally, we'd like the first column of each row to be all ones.  To\n     do that, we multiply the row by the inverse of the first element. */\n\n  sindex = cols*(cols+1);\n  for (i = cols+1; i < rows; i++) {\n    tmp = dist[sindex];\n    if (tmp != 1) { \n      tmp = galois_single_divide(1, tmp, w);\n      for (j = 0; j < cols; j++) dist[sindex+j] = galois_single_multiply(dist[sindex+j], tmp, w);\n    }\n    sindex += cols;\n  }\n\n  return dist;\n}\n\n"
  },
  {
    "path": "fst/layout/jerasure/src/timing.c",
    "content": "// Timing measurement utilities implementation.\n\n#include \"timing.h\"\n#include <stddef.h>\n\nvoid\ntiming_set(\n  struct timing * t)\n{\n#ifdef USE_CLOCK\n  t->clock = clock();\n#else\n  gettimeofday(&t->tv, NULL);\n#endif\n}\n\ndouble\ntiming_get(\n  struct timing * t)\n{\n#ifdef USE_CLOCK\n  // The clock_t type is an \"arithmetic type\", which could be\n  // integral, double, long double, or others.\n  //\n  // Add 0.0 to make it a double or long double, then divide (in\n  // double or long double), then convert to double for our purposes.\n  return (double) ((t->clock + 0.0) / CLOCKS_PER_SEC);\n#else\n  return (double) t->tv.tv_sec + ((double) t->tv.tv_usec) / 1000000.0;\n#endif\n}\n\ndouble\ntiming_now()\n{\n#ifdef USE_CLOCK\n  return (double) ((clock() + 0.0) / CLOCKS_PER_SEC);\n#else\n  struct timeval tv;\n  gettimeofday(&tv, NULL);\n  return (double) tv.tv_sec + ((double) tv.tv_usec) / 1000000.0;\n#endif\n}\n\ndouble\ntiming_delta(\n  struct timing * t1,\n  struct timing * t2)\n{\n#ifdef USE_CLOCK\n  // The clock_t type is an \"arithmetic type\", which could be\n  // integral, double, long double, or others.\n  //\n  // Subtract first, resulting in another clock_t, then add 0.0 to\n  // make it a double or long double, then divide (in double or long\n  // double), then convert to double for our purposes.\n  return (double) (((t2->clock - t1->clock) + 0.0) / CLOCKS_PER_SEC);\n#else\n  double const d2 = (double) t2->tv.tv_sec + ((double) t2->tv.tv_usec) / 1000000.0;\n  double const d1 = (double) t1->tv.tv_sec + ((double) t1->tv.tv_usec) / 1000000.0;\n  return d2 - d1;\n#endif\n}\n"
  },
  {
    "path": "fst/storage/Communicator.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Communicator.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Constants.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/SymKeys.hh\"\n#include \"fst/Config.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/storage/FileSystem.hh\"\n#include \"fst/storage/Storage.hh\"\n#include \"mq/SharedHashWrapper.hh\"\n#include \"qclient/shared/SharedHashSubscription.hh\"\n#include \"qclient/structures/QScanner.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n// Set of keys updates to be tracked at the node level\nstd::set<std::string> Storage::sNodeUpdateKeys{\n    \"stat.refresh_fs\",\n    \"manager\",\n    \"symkey\",\n    \"publish.interval\",\n    \"debug.level\",\n    \"error.simulation\",\n    \"stripexs\",\n    common::FST_TRAFFIC_SHAPING_IO_LIMITS,\n    common::FST_TRAFFIC_SHAPING_ENABLE_TOGGLE,\n    common::FST_TRAFFIC_SHAPING_STATS_THREAD_PERIOD,\n};\n\n//------------------------------------------------------------------------------\n// Get configuration value from global FST config\n//------------------------------------------------------------------------------\nbool Storage::GetFstConfigValue(const std::string& key, std::string& value) const {\n  common::SharedHashLocator locator = gConfig.getNodeHashLocator(\"getConfigValue\", false);\n\n  if (locator.empty()) { return false; }\n\n  mq::SharedHashWrapper hash(gOFS.mMessagingRealm.get(), locator, true, false);\n  return hash.get(key, value);\n}\n\n//------------------------------------------------------------------------------\n// Get configuration value from global FST config\n//------------------------------------------------------------------------------\nbool Storage::GetFstConfigValue(const std::string& key, unsigned long long& value) const {\n  std::string strVal;\n\n  if (!GetFstConfigValue(key, strVal)) { return false; }\n\n  value = atoi(strVal.c_str());\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Unregister file system given a queue path\n//------------------------------------------------------------------------------\nvoid Storage::UnregisterFileSystem(const std::string& queuepath) {\n  while (mFsMutex.TryLockWrite() != 0) {\n    std::this_thread::sleep_for(std::chrono::milliseconds(10));\n  }\n\n  auto it =\n      std::find_if(mFsVect.begin(), mFsVect.end(), [&](FileSystem* fs) { return (fs->GetQueuePath() == queuepath); });\n\n  if (it == mFsVect.end()) {\n    eos_static_warning(\"msg=\\\"file system is already removed\\\" qpath=%s\", queuepath.c_str());\n    mFsMutex.UnLockWrite();\n    return;\n  }\n\n  auto fs = *it;\n  mFsVect.erase(it);\n  auto it_map = std::find_if(mFsMap.begin(), mFsMap.end(),\n                             [&](const auto& pair) { return (pair.second->GetQueuePath() == queuepath); });\n\n  if (it_map == mFsMap.end()) {\n    eos_static_warning(\"msg=\\\"file system missing from map\\\" qpath=%s\", queuepath.c_str());\n  } else {\n    mFsMap.erase(it_map);\n  }\n\n  mFsMutex.UnLockWrite();\n  eos_static_info(\"msg=\\\"deleting file system\\\" qpath=%s\", fs->GetQueuePath().c_str());\n  delete fs;\n}\n\n//------------------------------------------------------------------------------\n// Register file system\n//------------------------------------------------------------------------------\nStorage::FsRegisterStatus Storage::RegisterFileSystem(const std::string& queuepath) {\n  while (mFsMutex.TryLockWrite() != 0) {\n    std::this_thread::sleep_for(std::chrono::milliseconds(10));\n  }\n\n  auto it =\n      std::find_if(mFsVect.begin(), mFsVect.end(), [&](FileSystem* fs) { return (fs->GetQueuePath() == queuepath); });\n\n  if (it != mFsVect.end()) {\n    eos_static_warning(\"msg=\\\"file system is already registered\\\" qpath=%s\", queuepath.c_str());\n    mFsMutex.UnLockWrite();\n    return FsRegisterStatus::kNoAction;\n  }\n\n  common::FileSystemLocator locator;\n\n  if (!common::FileSystemLocator::fromQueuePath(queuepath, locator)) {\n    eos_static_crit(\"msg=\\\"failed to parse locator\\\" qpath=%s\", queuepath.c_str());\n    mFsMutex.UnLockWrite();\n    return FsRegisterStatus::kNoAction;\n  }\n\n  fst::FileSystem* fs = new fst::FileSystem(locator, gOFS.mMessagingRealm.get());\n  fs->SetLocalId();\n  fs->SetLocalUuid();\n  mFsVect.push_back(fs);\n  eos_static_info(\"msg=\\\"attempt file system registration\\\" qpath=\\\"%s\\\" \"\n                  \"fsid=%u uuid=\\\"%s\\\"\",\n                  queuepath.c_str(), fs->GetLocalId(), fs->GetLocalUuid().c_str());\n\n  if ((fs->GetLocalId() == 0ul) || fs->GetLocalUuid().empty()) {\n    eos_static_info(\"msg=\\\"partially register file system\\\" qpath=\\\"%s\\\"\", queuepath.c_str());\n    mFsMutex.UnLockWrite();\n    return FsRegisterStatus::kPartial;\n  }\n\n  if (mFsMap.find(fs->GetLocalId()) != mFsMap.end()) {\n    eos_static_crit(\"msg=\\\"trying to register an already existing file system\\\" \"\n                    \"fsid=%u uuid=\\\"%s\\\"\",\n                    fs->GetLocalId(), fs->GetLocalUuid().c_str());\n    std::abort();\n  }\n\n  mFsMap[fs->GetLocalId()] = fs;\n\n  if (gConfig.autoBoot && (fs->GetConfigStatus() > eos::common::ConfigStatus::kOff)) { RunBootThread(fs, \"\"); }\n\n  mFsMutex.UnLockWrite();\n  return FsRegisterStatus::kRegistered;\n}\n\nvoid\nProcessFstIoLimitsCommand(const std::string& data)\n{\n  std::string serialized;\n\n  if (!eos::common::SymKey::DeBase64(data, serialized)) {\n    eos_static_err(\"msg=\\\"Failed to base64-decode FST IO limits config\\\"\");\n    return;\n  }\n\n  eos::traffic_shaping::TrafficShapingFstIoDelayConfig fst_io_delay_config;\n\n  if (!fst_io_delay_config.ParseFromString(serialized)) {\n    eos_static_err(\"msg=\\\"Failed to parse FST IO limits config\\\"\");\n    return;\n  }\n  eos_static_debug(\n      \"msg=\\\"Traffic Shaping FST IO limits config change received\\\" new_config=\\\"%s\\\"\",\n      fst_io_delay_config.DebugString().c_str());\n\n  gOFS.mIoDelayConfig.UpdateConfig(std::move(fst_io_delay_config));\n}\n\nvoid\nProcessTrafficShapingToggle(bool enable)\n{\n  Storage* storage = gOFS.Storage;\n  if (!storage) {\n    eos_static_err(\"msg=\\\"Storage instance not available\\\"\");\n    return;\n  }\n  eos_static_debug(\"msg=\\\"Traffic Shaping enable toggle\\\" new_value=%s\",\n                   enable ? \"enabled\" : \"disabled\");\n\n  if (enable) {\n    storage->StartTrafficShapingThread();\n  } else {\n    storage->StopTrafficShapingThread();\n  }\n}\n\nvoid\nProcessFstIoStatsReportingThreadPeriod(const std::string& period_millis_as_str)\n{\n  try {\n    unsigned long long period_millis = std::stoull(period_millis_as_str);\n    traffic_shaping::IoStatsCollector::fst_io_stats_reporting_thread_period_milliseconds =\n        period_millis;\n    eos_static_debug(\"msg=\\\"Traffic Shaping FST IO stats reporting thread period \"\n                     \"changed\\\" new_period_ms=%llu\",\n                     period_millis);\n  } catch (const std::exception& e) {\n    eos_static_err(\"msg=\\\"invalid FST IO stats reporting thread period value\\\" \"\n                   \"value=\\\"%s\\\" error=\\\"%s\\\"\",\n                   period_millis_as_str.c_str(), e.what());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Process incoming configuration change\n//------------------------------------------------------------------------------\nvoid Storage::ProcessFstConfigChange(const std::string& key, const std::string& value) {\n  static std::string last_refresh_ts;\n  eos_static_debug(\"msg=\\\"FST node configuration change\\\" key=\\\"%s\\\" \"\n                   \"value=\\\"%s\\\"\",\n                   key.c_str(), value.c_str());\n\n  // if key not in list, warning and return\n  if (sNodeUpdateKeys.find(key) == sNodeUpdateKeys.end()) {\n    eos_static_warning(\"msg=\\\"unhandled FST node configuration change due to invalid key\\\" \"\n                       \"key=\\\"%s\\\" value=\\\"%s\\\"\",\n                       key.c_str(), value.c_str());\n    return;\n  }\n\n  if (key == \"stat.refresh_fs\") {\n    // Refresh the list of filesystems registered from QDB shared hashes\n    if (last_refresh_ts != value) {\n      eos_static_info(\"msg=\\\"refreshing file system list\\\" \"\n                      \"last_refresh_ts=\\\"%s\\\" new_refresh_ts=\\\"%s\\\"\",\n                      last_refresh_ts.c_str(), value.c_str());\n      last_refresh_ts = value;\n      SignalRegisterThread();\n    }\n  } else if (key == \"manager\") {\n    eos_static_info(\"msg=\\\"manager changed\\\" new_manager=\\\"%s\\\"\", value.c_str());\n    XrdSysMutexHelper lock(gConfig.Mutex);\n    gConfig.Manager = value.c_str();\n  } else if (key == \"symkey\") {\n    eos_static_info(\"msg=\\\"symkey changed\\\"\");\n    eos::common::gSymKeyStore.SetKey64(value.c_str(), 0);\n  } else if (key == \"publish.interval\") {\n    eos_static_info(\"msg=\\\"publish interval changed\\\" new_interval=\\\"%s\\\"\", value.c_str());\n    XrdSysMutexHelper lock(gConfig.Mutex);\n    try {\n      gConfig.PublishInterval = std::stoi(value);\n    } catch (const std::exception& e) {\n      eos_static_warning(\"msg=\\\"invalid PublishInterval value\\\" value=\\\"%s\\\" error=\\\"%s\\\"\", value.c_str(), e.what());\n    }\n  } else if (key == eos::common::FST_TRAFFIC_SHAPING_IO_LIMITS) {\n    ProcessFstIoLimitsCommand(value);\n  } else if (key == eos::common::FST_TRAFFIC_SHAPING_ENABLE_TOGGLE) {\n    ProcessTrafficShapingToggle(value == \"on\" || value == \"true\" || value == \"1\");\n  } else if (key == eos::common::FST_TRAFFIC_SHAPING_STATS_THREAD_PERIOD) {\n    ProcessFstIoStatsReportingThreadPeriod(value);\n  } else if (key == \"debug.level\") {\n    const std::string& debugLevel = value;\n    eos_static_info(\"msg=\\\"debug level changed\\\" new_level=\\\"%s\\\"\", debugLevel.c_str());\n    eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n    if (const int debugValue = g_logging.GetPriorityByString(debugLevel.c_str()); debugValue < 0) {\n      eos_static_err(\"msg=\\\"unknown debug level\\\" level=\\\"%s\\\"\", debugLevel.c_str());\n    } else {\n      g_logging.SetLogPriority(debugValue);\n    }\n  } else if (key == \"error.simulation\") {\n    eos_static_info(\"msg=\\\"error simulation changed\\\" new_value=\\\"%s\\\"\", value.c_str());\n    gOFS.SetSimulationError(value);\n  } else if (key == \"stripexs\") {\n    // value can be \"on\" or \"off\"\n    mComputeStripeChecksum = (value == \"on\");\n    eos_static_info(\"msg=\\\"stripe checksum calculation changed\\\" new_value=\\\"%s\\\" mComputeStripeChecksum=%s\",\n                    value.c_str(), mComputeStripeChecksum ? \"enabled\" : \"disabled\");\n  } else {\n    eos_static_err(\"msg=\\\"unhandled FST node configuration change because \"\n                   \"of missing implementation\\\" key=\\\"%s\\\" value=\\\"%s\\\". \"\n                   \"This should never happen!\",\n                   key.c_str(), value.c_str());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Process incoming filesystem-level configuration change\n//------------------------------------------------------------------------------\nvoid Storage::ProcessFsConfigChange(fst::FileSystem* fs, const std::string& key, const std::string& value) {\n  if ((key == \"id\") || (key == \"uuid\") || (key == \"bootsenttime\")) {\n    RunBootThread(fs, key);\n  } else {\n    mFsUpdQueue.emplace(fs->GetLocalId(), key, value);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Process incoming filesystem-level configuration change\n//------------------------------------------------------------------------------\nvoid Storage::ProcessFsConfigChange(const std::string& queuepath, const std::string& key) {\n  eos::common::RWMutexReadLock fs_rd_lock(mFsMutex);\n  auto it = std::find_if(mFsMap.begin(), mFsMap.end(),\n                         [&](const auto& pair) { return (pair.second->GetQueuePath() == queuepath); });\n\n  if (it == mFsMap.end()) {\n    // If file system does not exist in the map and this an \"id\" info then\n    // it could be that we have a partially registered file system\n    if ((key == \"id\") || (key == \"uuid\")) {\n      // Switch to a write lock as we might add the new fs to the map\n      fs_rd_lock.Release();\n\n      // Mutex write lock non-blocking\n      while (mFsMutex.TryLockWrite() != 0) {\n        std::this_thread::sleep_for(std::chrono::milliseconds(10));\n      }\n\n      auto itv = std::find_if(mFsVect.begin(), mFsVect.end(),\n                              [&](fst::FileSystem* fs) { return (fs->GetQueuePath() == queuepath); });\n\n      if (itv == mFsVect.end()) {\n        eos_static_err(\"msg=\\\"no file system for id modification\\\" \"\n                       \"qpath=\\\"%s\\\" key=\\\"%s\\\"\",\n                       queuepath.c_str(), key.c_str());\n        mFsMutex.UnLockWrite();\n        return;\n      }\n\n      fst::FileSystem* fs = *itv;\n      fs->SetLocalId();\n      fs->SetLocalUuid();\n      eos_static_info(\"msg=\\\"attempt file system registration\\\" qpath=\\\"%s\\\" \"\n                      \"fsid=%u uuid=\\\"%s\\\"\",\n                      queuepath.c_str(), fs->GetLocalId(), fs->GetLocalUuid().c_str());\n\n      if ((fs->GetLocalId() == 0ul) || fs->GetLocalUuid().empty()) {\n        eos_static_info(\"msg=\\\"defer file system registration\\\" qpath=\\\"%s\\\"\", queuepath.c_str());\n        mFsMutex.UnLockWrite();\n        return;\n      }\n\n      eos::common::FileSystem::fsid_t fsid = fs->GetLocalId();\n      it = mFsMap.emplace(fsid, fs).first;\n      eos_static_info(\"msg=\\\"fully register file system\\\" qpath=%s fsid=%u \"\n                      \"uuid=\\\"%s\\\"\",\n                      queuepath.c_str(), fs->GetLocalId(), fs->GetLocalUuid().c_str());\n      // Switch back to read lock and update the iterator\n      mFsMutex.UnLockWrite();\n      fs_rd_lock.Grab(mFsMutex);\n      it = mFsMap.find(fsid);\n    } else {\n      eos_static_err(\"msg=\\\"no file system for modification\\\" qpath=\\\"%s\\\" \"\n                     \"key=\\\"%s\\\"\",\n                     queuepath.c_str(), key.c_str());\n      return;\n    }\n  }\n\n  eos_static_info(\"msg=\\\"process modification\\\" qpath=\\\"%s\\\" key=\\\"%s\\\"\", queuepath.c_str(), key.c_str());\n  fst::FileSystem* fs = it->second;\n  mq::SharedHashWrapper hash(gOFS.mMessagingRealm.get(), fs->getHashLocator());\n  std::string value;\n\n  if (!hash.get(key, value)) {\n    eos_static_err(\"msg=\\\"no such key in hash\\\" qpath=\\\"%s\\\" key=\\\"%s\\\"\", queuepath.c_str(), key.c_str());\n    return;\n  }\n\n  return ProcessFsConfigChange(fs, key, value);\n}\n\n//------------------------------------------------------------------------------\n// Extract filesystem path from QDB hash key - helper function\n//------------------------------------------------------------------------------\nstatic std::string ExtractFsPath(const std::string& key) {\n  std::vector<std::string> parts = common::StringTokenizer::split<std::vector<std::string>>(key, '|');\n  return parts[parts.size() - 1];\n}\n\n//------------------------------------------------------------------------------\n// Handle FS configuration updates in a separate thread to avoid deadlocks\n// in the QClient callback mechanism.\n//------------------------------------------------------------------------------\nvoid Storage::FsConfigUpdate(ThreadAssistant& assistant) noexcept {\n  eos_static_info(\"%s\", \"msg=\\\"starting fs config update thread\\\"\");\n  FsCfgUpdate upd;\n\n  while (!assistant.terminationRequested()) {\n    mFsUpdQueue.wait_pop(upd);\n\n    // If sentinel object then exit\n    if ((upd.fsid == 0) && (upd.key == \"ACTION\") && (upd.value == \"EXIT\")) {\n      eos_static_notice(\"%s\", \"msg=\\\"fs config update thread got a \"\n                              \"sentinel object exiting\\\"\");\n      break;\n    }\n\n    if ((upd.key == eos::common::SCAN_IO_RATE_NAME) || (upd.key == eos::common::SCAN_ENTRY_INTERVAL_NAME) ||\n        (upd.key == eos::common::SCAN_RAIN_ENTRY_INTERVAL_NAME) || (upd.key == eos::common::SCAN_DISK_INTERVAL_NAME) ||\n        (upd.key == eos::common::SCAN_NS_INTERVAL_NAME) || (upd.key == eos::common::SCAN_NS_RATE_NAME) ||\n        (upd.key == eos::common::SCAN_ALTXS_INTERVAL_NAME) || (upd.key == eos::common::ALTXS_SYNC) ||\n        (upd.key == eos::common::ALTXS_SYNC_INTERVAL)) {\n      try {\n        long long val = std::stoll(upd.value);\n\n        if (val >= 0) {\n          eos::common::RWMutexReadLock fs_rd_lock(mFsMutex);\n          auto it = mFsMap.find(upd.fsid);\n\n          if (it != mFsMap.end()) { it->second->ConfigScanner(&mFstLoad, upd.key.c_str(), val); }\n        }\n      } catch (...) {\n        eos_static_err(\"msg=\\\"failed to convert value\\\" key=\\\"%s\\\" val=\\\"%s\\\"\", upd.key.c_str(), upd.value.c_str());\n      }\n    }\n  }\n\n  eos_static_info(\"%s\", \"msg=\\\"stopped fs config update thread\\\"\");\n}\n\n//------------------------------------------------------------------------------\n// Update file system list given the QDB shared hash configuration -this\n// update is done in a separate thread handling the trigger event otherwise\n// we deadlock in the QClient code.\n//------------------------------------------------------------------------------\nvoid Storage::UpdateRegisteredFs(ThreadAssistant& assistant) noexcept {\n  eos_static_info(\"%s\", \"msg=\\\"starting register file system thread\\\"\");\n\n  while (!assistant.terminationRequested()) {\n    {\n      // Reduce scope of mutex to avoid coupling the the SignalRegisterThread\n      // which is called from the QClient event loop with other QClient requests\n      // like the QScanner listing below - this will lead to a deadlock!!!\n      std::unique_lock lock(mMutexRegisterFs);\n      mCvRegisterFs.wait(lock, [&] { return mTriggerRegisterFs; });\n      eos_static_info(\"%s\", \"msg=\\\"update registered file systems\\\"\");\n      mTriggerRegisterFs = false;\n    }\n    qclient::QScanner scanner(*gOFS.mMessagingRealm->getQSom()->getQClient(),\n                              SSTR(\"eos-hash||fs||\" << gConfig.FstHostPort << \"||*\"));\n    std::set<std::string> new_filesystems;\n\n    for (; scanner.valid(); scanner.next()) {\n      std::string queuePath = SSTR(\"/eos/\" << gConfig.FstHostPort << \"/fst\" << ExtractFsPath(scanner.getValue()));\n      new_filesystems.insert(queuePath);\n    }\n\n    // Filesystems added?\n    std::set<std::string> partial_filesystems;\n\n    for (auto it = new_filesystems.begin(); it != new_filesystems.end(); ++it) {\n      if (mLastRoundFilesystems.find(*it) == mLastRoundFilesystems.end()) {\n        if (RegisterFileSystem(*it) == FsRegisterStatus::kPartial) { partial_filesystems.insert(*it); }\n      }\n    }\n\n    // Filesystems removed?\n    for (auto it = mLastRoundFilesystems.begin(); it != mLastRoundFilesystems.end(); ++it) {\n      if (new_filesystems.find(*it) == new_filesystems.end()) {\n        eos_static_info(\"msg=\\\"unregister file system\\\" queuepath=\\\"%s\\\"\", it->c_str());\n        UnregisterFileSystem(*it);\n      }\n    }\n\n    if (!partial_filesystems.empty()) {\n      // Reset register trigger flag and remove partial file systems so\n      // that we properly register them in them next loop.\n      {\n        std::unique_lock lock(mMutexRegisterFs);\n        mTriggerRegisterFs = true;\n      }\n\n      for (const auto& elem : partial_filesystems) {\n        UnregisterFileSystem(elem);\n        auto it_del = new_filesystems.find(elem);\n        new_filesystems.erase(it_del);\n      }\n\n      eos_static_info(\"%s\", \"msg=\\\"re-trigger file system registration \"\n                            \"in 5 seconds\\\"\");\n      assistant.wait_for(std::chrono::seconds(5));\n    }\n\n    mLastRoundFilesystems = std::move(new_filesystems);\n  }\n\n  eos_static_info(\"%s\", \"msg=\\\"stopped register file system thread\\\"\");\n}\n\n//------------------------------------------------------------------------------\n// FST node update callback - this is triggered whenever the underlying\n// qclient::SharedHash corresponding to the node is modified.\n//------------------------------------------------------------------------------\nvoid Storage::NodeUpdateCb(qclient::SharedHashUpdate&& upd) {\n  if (sNodeUpdateKeys.find(upd.key) != sNodeUpdateKeys.end()) { ProcessFstConfigChange(upd.key, upd.value); }\n}\n\n//------------------------------------------------------------------------------\n// QdbCommunicator\n//------------------------------------------------------------------------------\nvoid Storage::QdbCommunicator(ThreadAssistant& assistant) noexcept {\n  using namespace std::placeholders;\n  eos_static_info(\"%s\", \"msg=\\\"starting QDB communicator thread\\\"\");\n  // Process initial FST configuration ... discover instance name\n  std::string instance_name;\n\n  for (size_t i = 0; i < 10; i++) {\n    if (gOFS.mMessagingRealm->getInstanceName(instance_name)) { break; }\n\n    std::this_thread::sleep_for(std::chrono::seconds(1));\n  }\n\n  if (instance_name.empty()) {\n    eos_static_crit(\"%s\", \"msg=\\\"unable to obtain instance name from QDB\\\"\");\n    exit(1);\n  }\n\n  std::string cfg_queue = SSTR(\"/config/\" << instance_name << \"/node/\" << gConfig.FstHostPort);\n  gConfig.setFstNodeConfigQueue(cfg_queue);\n  // Discover node-specific configuration\n  mq::SharedHashWrapper node_hash(gOFS.mMessagingRealm.get(), gConfig.getNodeHashLocator(), false, false);\n  // Discover MGM name\n  std::string mgm_host;\n\n  for (size_t i = 0; i < 10; i++) {\n    if (node_hash.get(\"manager\", mgm_host)) { break; }\n\n    std::this_thread::sleep_for(std::chrono::seconds(5));\n  }\n\n  if (mgm_host.empty()) {\n    eos_static_crit(\"%s\", \"msg=\\\"unable to obtain manager info for node\\\"\");\n    exit(1);\n  }\n\n  ProcessFstConfigChange(\"manager\", mgm_host);\n\n  // Discover the rest of the FST node configuration options\n  for (const auto& node_key : sNodeUpdateKeys) {\n    std::string value;\n\n    if (node_hash.get(node_key, value)) { ProcessFstConfigChange(node_key, value); }\n  }\n\n  // One-off collect all configured file systems for this node\n  SignalRegisterThread();\n  // Attach callback for node configuration updates\n  std::unique_ptr<qclient::SharedHashSubscription> node_subscription = node_hash.subscribe();\n  node_subscription->attachCallback(std::bind(&Storage::NodeUpdateCb, this, _1));\n\n  // Broadcast FST node hearbeat\n  while (!assistant.terminationRequested()) {\n    node_hash.set(eos::common::FST_HEARTBEAT_KEY, std::to_string(time(0)));\n    assistant.wait_for(std::chrono::seconds(1));\n  }\n\n  node_subscription->detachCallback();\n  node_subscription.reset(nullptr);\n  mq::SharedHashWrapper::deleteHash(gOFS.mMessagingRealm.get(), gConfig.getNodeHashLocator(), false);\n  eos_static_info(\"%s\", \"msg=\\\"stopped QDB communicator thread\\\"\");\n}\n\n//------------------------------------------------------------------------------\n// Signal the thread responsible with registered file systems\n//------------------------------------------------------------------------------\nvoid Storage::SignalRegisterThread() {\n  {\n    std::unique_lock lock(mMutexRegisterFs);\n    mTriggerRegisterFs = true;\n  }\n  mCvRegisterFs.notify_one();\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/storage/ErrorReport.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ErrorReport.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/storage/Storage.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/Config.hh\"\n#include \"mq/MessagingRealm.hh\"\n#include <deque>\n#include <string>\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Method sending error reports\n//------------------------------------------------------------------------------\nvoid\nStorage::ErrorReport(ThreadAssistant& assistant) noexcept\n{\n  bool failure = false;\n  XrdOucString errorReceiver = gConfig.FstDefaultReceiverQueue;\n  errorReceiver.replace(\"*/mgm\", \"*/errorreport\");\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n  eos::common::Logging::LogCircularIndex localCircularIndex;\n  localCircularIndex.resize(LOG_DEBUG + 1);\n  std::deque<XrdOucString> ErrorReportQueue;\n  eos_static_info(\"%s\", \"msg=\\\"starting error report thread\\\"\");\n\n  // initialize with the current positions of the circular index\n  for (size_t i = LOG_EMERG; i <= LOG_DEBUG; i++) {\n    localCircularIndex[i] = g_logging.gLogCircularIndex[i];\n  }\n\n  while (!assistant.terminationRequested()) {\n    failure = false;\n\n    // push messages from the circular buffers to the error queue\n    for (size_t i = LOG_EMERG; i <= LOG_ERR; i++) {\n      g_logging.gMutex.Lock();\n      size_t endpos = g_logging.gLogCircularIndex[i];\n      g_logging.gMutex.UnLock();\n\n      if (endpos > localCircularIndex[i]) {\n        // we have to follow the messages and add them to the queue\n        for (unsigned long j = localCircularIndex[i]; j < endpos; j++) {\n          // copy the messages to the queue\n          g_logging.gMutex.Lock();\n          ErrorReportQueue.push_back(g_logging.gLogMemory[i][j %\n                                     g_logging.gCircularIndexSize]);\n          g_logging.gMutex.UnLock();\n        }\n\n        localCircularIndex[i] = endpos;\n      }\n    }\n\n    while (ErrorReportQueue.size() > 0) {\n      // send all reports away and dump them into the log\n      XrdOucString report = ErrorReportQueue.front().c_str();\n      std::string truncationmessage;\n      eos_debug(\"broadcasting errorreport message: %s\", report.c_str());\n\n      if (ErrorReportQueue.size() > 5) {\n        // don't keep long error queues, send a suppression marker and clean the queue, the errors are anyway in the local log files\n        truncationmessage = \" ... [ ErrorReport ] suppressing \" + std::to_string(\n                              ErrorReportQueue.size() - 1) + \" error messages!\";\n        ErrorReportQueue.clear();\n      }\n\n      // evt. exclude some messages from upstream reporting if the contain [NB]\n      if (report.find(\"[NB]\") == STR_NPOS) {\n        report += truncationmessage.c_str();\n        mq::MessagingRealm::Response response =\n          gOFS.mMessagingRealm->sendMessage(\"errorreport\", report.c_str(),\n                                            errorReceiver.c_str(), true);\n\n        if (!response.ok()) {\n          // display communication error\n          eos_err(\"%s\", \"msg=\\\"cannot send errorreport broadcast\\\"\");\n          failure = true;\n          break;\n        }\n      }\n\n      if (ErrorReportQueue.size()) {\n        ErrorReportQueue.pop_front();\n      }\n    }\n\n    if (failure) {\n      assistant.wait_for(std::chrono::seconds(10));\n    } else {\n      assistant.wait_for(std::chrono::seconds(1));\n    }\n  }\n\n  eos_static_info(\"%s\", \"msg=\\\"stopped error report thread\\\"\");\n}\n\nEOSFSTNAMESPACE_END\n\n\n"
  },
  {
    "path": "fst/storage/FileSystem.cc",
    "content": "// ----------------------------------------------------------------------\n// File: FileSystem.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/storage/FileSystem.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/ScanDir.hh\"\n#include \"fst/Config.hh\"\n#include \"fst/utils/DiskMeasurements.hh\"\n#include \"common/Constants.hh\"\n#include \"qclient/shared/SharedHashSubscription.hh\"\n#include <sys/syscall.h>\n#include <unistd.h>\n\n#ifdef __APPLE__\n#define O_DIRECT 0\n#endif\n\nnamespace\n{\n//----------------------------------------------------------------------------\n//! Struct SwitchToRootEuid takes care of switching the current effective UID\n//! of the **current thread** to root (uid=0) and then revert to the original\n//! setup once this object is destroyed. We need this to be able to access\n//! the block devices on the machine during start up to measure the IO\n//! performance.\n//----------------------------------------------------------------------------\nstruct SwitchToRootEuid {\n  //--------------------------------------------------------------------------\n  //! Constructor\n  //--------------------------------------------------------------------------\n  SwitchToRootEuid()\n  {\n    if (syscall(SYS_getresuid, &mRuid, &mEuid, &mSuid) == -1) {\n      eos_static_err(\"%s\", \"msg=\\\"failed to get uids\\\"\");\n      mFailed = true;\n      return;\n    }\n\n    eos_static_info(\"msg=\\\"initial user identity\\\" ruid=%i euid=%i suid=%i\",\n                    mRuid, mEuid, mSuid);\n\n    // Switch to target user\n    if (syscall(SYS_setresuid, sRootUid, sRootUid, sRootUid) == -1) {\n      eos_static_err(\"msg=\\\"insuffcient priviledges to switch to root\\\" \"\n                     \"euid=%i\", mEuid);\n      mFailed = true;\n      return;\n    }\n  }\n\n  //--------------------------------------------------------------------------\n  //! Destructor\n  //--------------------------------------------------------------------------\n  ~SwitchToRootEuid()\n  {\n    // Put back the original effective UID\n    if (syscall(SYS_setresuid, mRuid, mEuid, mSuid) == -1) {\n      eos_static_err(\"msg=\\\"insuffcient priviledges to switch to user\\\" \"\n                     \"euid=%i\", mEuid);\n    }\n\n    if (syscall(SYS_getresuid, &mRuid, &mEuid, &mSuid) == -1) {\n      eos_static_err(\"%s\", \"msg=\\\"failed to get uids\\\"\");\n    }\n\n    eos_static_info(\"msg=\\\"reverted user identity\\\" ruid=%i euid=%i suid=%i\",\n                    mRuid, mEuid, mSuid);\n  }\n\n  static constexpr uid_t sRootUid = 0;\n  //! Real, effective and saved UID\n  uid_t mRuid, mEuid, mSuid;\n  //! Flag to mark any failures\n  bool mFailed {false};\n};\n}\n\nEOSFSTNAMESPACE_BEGIN\n\n// Set of key updates to be tracked at the file system level\nstd::set<std::string> FileSystem::sFsUpdateKeys {\n  \"id\", \"uuid\", \"bootsenttime\",\n  eos::common::SCAN_IO_RATE_NAME,\n  eos::common::SCAN_ENTRY_INTERVAL_NAME,\n  eos::common::SCAN_RAIN_ENTRY_INTERVAL_NAME,\n  eos::common::SCAN_DISK_INTERVAL_NAME,\n  eos::common::SCAN_NS_INTERVAL_NAME,\n  eos::common::SCAN_NS_RATE_NAME,\n  eos::common::SCAN_ALTXS_INTERVAL_NAME,\n  eos::common::ALTXS_SYNC,\n  eos::common::ALTXS_SYNC_INTERVAL };\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFileSystem::FileSystem(const common::FileSystemLocator& locator,\n                       mq::MessagingRealm* realm) :\n  eos::common::FileSystem(locator, realm, true),\n  mLocalId(0ul), mLocalUuid(\"\"),  mScanDir(nullptr), mFileIO(nullptr)\n{\n  last_blocks_free = 0;\n  last_status_broadcast = 0;\n  seqBandwidth = 0;\n  IOPS = 0;\n  mLocalBootStatus = eos::common::BootStatus::kDown;\n  mRecoverable = false;\n  mFileIO.reset(FileIoPlugin::GetIoObject(mLocator.getStoragePath()));\n  // Subscribe to the underlying SharedHash object to get updates\n  mSubscription = mq::SharedHashWrapper(mRealm, mHashLocator).subscribe();\n\n  if (mSubscription) {\n    using namespace std::placeholders;\n    mSubscription->attachCallback(std::bind(&FileSystem::ProcessUpdateCb,\n                                            this, _1));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nFileSystem::~FileSystem()\n{\n  if (mSubscription) {\n    mSubscription->detachCallback();\n  }\n\n  mScanDir.release();\n  mFileIO.release();\n  // Notify the MGM this file system is down\n  SetStatus(eos::common::BootStatus::kDown);\n  // Delete the local SharedHash object attached to it without touching the\n  // shared object in QDB\n  DeleteSharedHash(false);\n}\n\n//------------------------------------------------------------------------------\n// Process shared hash update\n//------------------------------------------------------------------------------\nvoid\nFileSystem::ProcessUpdateCb(qclient::SharedHashUpdate&& upd)\n{\n  if (sFsUpdateKeys.find(upd.key) != sFsUpdateKeys.end()) {\n    eos_static_info(\"msg=\\\"process update callback\\\" key=%s value=%s\",\n                    upd.key.c_str(), upd.value.c_str());\n\n    if (upd.key == \"id\") {\n      try {\n        mLocalId = std::stoul(upd.value);\n      } catch (...) {}\n    } else if (upd.key == \"uuid\") {\n      mLocalUuid = upd.value;\n    }\n\n    // @note handle here the updates but make sure not to access or set any\n    // shared hash values as this will trigger a deadlock. We are now called\n    // from the shared hash itself that digest the updates and also pushes them\n    // through a subscriber to us. Digesting these update is done in an\n    // exclusive lock region that protects the contents of the shared hash -\n    // therefore we risk ending up in a deadlock situation\n    gOFS.Storage->ProcessFsConfigChange(this, upd.key, upd.value);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Broadcast given error message\n//------------------------------------------------------------------------------\nvoid\nFileSystem::BroadcastError(const char* msg)\n{\n  if (!gOFS.sShutdown) {\n    SetStatus(eos::common::BootStatus::kOpsError);\n    SetError(errno ? errno : EIO, msg);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Broadcast given error code and message\n//------------------------------------------------------------------------------\nvoid\nFileSystem::BroadcastError(int errc, const char* errmsg)\n{\n  if (!gOFS.sShutdown) {\n    SetStatus(eos::common::BootStatus::kOpsError);\n    SetError(errno ? errno : EIO, errmsg);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Set given error code and message\n//------------------------------------------------------------------------------\nvoid\nFileSystem::SetError(int errc, const char* errmsg)\n{\n  if (errc) {\n    eos_static_err(\"setting errc=%d errmsg=%s\", errc, errmsg ? errmsg : \"\");\n  }\n\n  if (!SetLongLong(\"stat.errc\", errc)) {\n    eos_static_err(\"cannot set errcode for filesystem %s\", GetQueuePath().c_str());\n  }\n\n  if (errmsg && strlen(errmsg) && !SetString(\"stat.errmsg\", errmsg)) {\n    eos_static_err(\"cannot set errmsg for filesystem %s\", GetQueuePath().c_str());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get statfs info about mountpoint\n//------------------------------------------------------------------------------\nstd::unique_ptr<eos::common::Statfs>\nFileSystem::GetStatfs()\n{\n  if (!GetPath().length()) {\n    return nullptr;\n  }\n\n  std::unique_ptr<eos::common::Statfs> statFs;\n\n  if (mFileIO) {\n    statFs = mFileIO->GetStatfs();\n  }\n\n  if ((!statFs) && GetPath().length()) {\n    eos_err(\"msg=\\\"cannot statfs\\\" path=\\\"%s\\\"\", GetPath().c_str());\n    BroadcastError(\"cannot statfs\");\n    return nullptr;\n  } else {\n    eos_static_debug(\"ec=%d error=%s recover=%d\", GetStatus(),\n                     GetString(\"stat.errmsg\").c_str(), mRecoverable);\n\n    if ((GetStatus() == eos::common::BootStatus::kOpsError) && mRecoverable) {\n      if (GetString(\"stat.errmsg\") == \"cannot statfs\") {\n        // reset the statfs error\n        SetStatus(eos::common::BootStatus::kBooted);\n        SetError(0, \"\");\n      }\n    }\n  }\n\n  return statFs;\n}\n\n//------------------------------------------------------------------------------\n// Configure scanner thread - possibly start the scanner\n//------------------------------------------------------------------------------\nvoid\nFileSystem::ConfigScanner(Load* fst_load, const std::string& key,\n                          long long value)\n{\n  // Don't scan filesystems which are 'remote'\n  if (GetPath()[0] != '/') {\n    return;\n  }\n\n  // If not running then create scanner thread with default parameters\n  if (mLocalId && !mLocalUuid.empty()) {\n    if (mScanDir == nullptr) {\n      mScanDir.reset(new ScanDir(GetPath().c_str(), mLocalId, fst_load, true));\n      eos_info(\"msg=\\\"started ScanDir thread with default parameters\\\" fsid=%d\",\n               mLocalId);\n    }\n\n    mScanDir->SetConfig(key, value);\n  } else {\n    eos_static_notice(\"msg=\\\"skip scanner config for partial file system\\\" \"\n                      \"queue=\\\"%s\\\"\", GetQueuePath().c_str());\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Set file system boot status\n//------------------------------------------------------------------------------\nvoid\nFileSystem::SetStatus(eos::common::BootStatus status)\n{\n  eos::common::FileSystem::SetStatus(status);\n\n  if (mLocalBootStatus == status) {\n    return;\n  }\n\n  eos_debug(\"before=%d after=%d\", mLocalBootStatus.load(), status);\n\n  if ((mLocalBootStatus == eos::common::BootStatus::kBooted) &&\n      (status == eos::common::BootStatus::kOpsError)) {\n    mRecoverable = true;\n  } else {\n    mRecoverable = false;\n  }\n\n  mLocalBootStatus = status;\n}\n\n//------------------------------------------------------------------------------\n// Get file system disk performance metrics eg. IOPS/seq bandwidth\n//------------------------------------------------------------------------------\nvoid\nFileSystem::IoPing()\n{\n  IOPS = 0;\n  seqBandwidth = 0;\n\n  // Exclude 'remote' disks\n  if (GetPath()[0] != '/') {\n    eos_static_notice(\"msg=\\\"skip disk measurements for \\'remote\\' disk\\\" \"\n                      \"path=%s\", GetPath().c_str());\n    return;\n  }\n\n  std::string device_path = eos::fst::GetDevicePath(GetPath());\n\n  if (device_path.empty()) {\n    eos_static_err(\"msg=\\\"failed to resolve block device\\\" path=%s\",\n                   GetPath().c_str());\n    return;\n  }\n\n  // Switch to root euid will be reverted upon destruction\n  SwitchToRootEuid root_euid;\n\n  if (root_euid.mFailed) {\n    eos_static_err(\"%s\", \"msg=\\\"failed to switch euid for IO perfmance \"\n                   \"measurements\\\"\");\n    return;\n  }\n\n  // Open the file for direct access\n  int fd = open(device_path.c_str(), O_RDONLY | O_DIRECT);\n  using namespace std::chrono;\n  auto start_iops = high_resolution_clock::now();\n  IOPS = eos::fst::ComputeIops(fd);\n  auto end_iops = high_resolution_clock::now();\n  uint64_t rd_buf_size = 4 * (1 << 20); // 4MB\n  auto start_bw = high_resolution_clock::now();\n  seqBandwidth = eos::fst::ComputeBandwidth(fd, rd_buf_size);\n  auto end_bw = high_resolution_clock::now();\n  (void) close(fd);\n  eos_info(\"bw=%lld iops=%d iops_time=%llums bw_time=%llums\", seqBandwidth, IOPS,\n           duration_cast<milliseconds>(end_iops - start_iops).count(),\n           duration_cast<milliseconds>(end_bw - start_bw).count());\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Get IO statistics from the `sys.iostats` xattr\n//------------------------------------------------------------------------------\nbool\nFileSystem::GetFileIOStats(std::map<std::string, std::string>& map)\n{\n  if (!mFileIO) {\n    return false;\n  }\n\n  // Avoid querying IO stats attributes for certain storage types\n  if (mFileIO->GetIoType() == \"DavixIo\" ||\n      mFileIO->GetIoType() == \"NfsIo\" ||\n      mFileIO->GetIoType() == \"XrdIo\") {\n    return false;\n  }\n\n  std::string iostats;\n  mFileIO->attrGet(\"sys.iostats\", iostats);\n  return eos::common::StringConversion::GetKeyValueMap(iostats.c_str(),\n         map, \"=\", \",\");\n}\n\n//------------------------------------------------------------------------------\n// Get health information from the `sys.health` xattr\n//------------------------------------------------------------------------------\nbool\nFileSystem::GetHealthInfo(std::map<std::string, std::string>& map)\n{\n  if (!mFileIO) {\n    return false;\n  }\n\n  // Avoid querying Health attributes for certain storage types\n  if (mFileIO->GetIoType() == \"DavixIo\" ||\n      mFileIO->GetIoType() == \"NfsIo\" ||\n      mFileIO->GetIoType() == \"XrdIo\") {\n    return false;\n  }\n\n  // Avoid querying Health attributes for certain storage types\n  if (mFileIO->GetIoType() == \"DavixIo\" ||\n      mFileIO->GetIoType() == \"NfsIo\" ||\n      mFileIO->GetIoType() == \"XrdIo\") {\n    return false;\n  }\n\n  std::string health;\n  mFileIO->attrGet(\"sys.health\", health);\n  return eos::common::StringConversion::GetKeyValueMap(health.c_str(),\n         map, \"=\", \",\");\n}\n\n//----------------------------------------------------------------------------\n// Decide if we should run the boot procedure for current file system\n//----------------------------------------------------------------------------\nbool\nFileSystem::ShouldBoot(const std::string& trigger)\n{\n  if ((trigger == \"id\") || (trigger == \"uuid\")) {\n    // Check if we are auto-booting\n    if (gConfig.autoBoot &&\n        (GetStatus() <= eos::common::BootStatus::kDown) &&\n        (GetConfigStatus() > eos::common::ConfigStatus::kOff)) {\n      return true;\n    }\n  }\n\n  if (trigger == \"bootsenttime\") {\n    uint64_t bootcheck_val = GetLongLong(\"bootcheck\");\n\n    if (GetInternalBootStatus() == eos::common::BootStatus::kBooted) {\n      if (bootcheck_val) {\n        eos_static_info(\"msg=\\\"boot enforced\\\" queue=%s status=%d check=%lld\",\n                        GetQueuePath().c_str(), GetStatus(), bootcheck_val);\n        return true;\n      } else {\n        eos_static_info(\"msg=\\\"skip boot, already booted\\\" queue=%s \"\n                        \"status=%d check=%lld\", GetQueuePath().c_str(),\n                        GetStatus(), bootcheck_val);\n        SetStatus(eos::common::BootStatus::kBooted);\n        return false;\n      }\n    } else {\n      eos_static_info(\"msg=\\\"do boot as we're not yet booted\\\" queue=%s \"\n                      \"status=%d check=%lld\", GetQueuePath().c_str(),\n                      GetStatus(), bootcheck_val);\n      return true;\n    }\n  }\n\n  if (trigger.empty()) {\n    return true;\n  }\n\n  return false;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/storage/FileSystem.hh",
    "content": "// ----------------------------------------------------------------------\n// File: FileSystem.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_FILESYSTEM_HH__\n#define __EOSFST_FILESYSTEM_HH__\n\n#include \"fst/Namespace.hh\"\n#include \"fst/io/FileIoPlugin.hh\"\n#include \"fst/storage/FileSystem.hh\"\n#include \"fst/io/FileIo.hh\"\n#include \"common/Logging.hh\"\n#include \"common/FileSystem.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/FileId.hh\"\n#include <vector>\n#include <list>\n#include <queue>\n#include <map>\n\nnamespace eos::mq\n{\nclass MessagingRealm;\n}\n\nnamespace eos::common\n{\nclass Statfs;\n}\n\nnamespace qclient\n{\nstruct SharedHashUpdate;\nclass SharedHashSubscription;\n}\n\nEOSFSTNAMESPACE_BEGIN\n\nclass ScanDir;\nclass Load;\n\n//-------------------------------------------------------------------------------\n//! Class FileSystem\n//-------------------------------------------------------------------------------\nclass FileSystem : public eos::common::FileSystem, eos::common::LogId\n{\npublic:\n  //! Set of key updates to be tracked at the file system level\n  static std::set<std::string> sFsUpdateKeys;\n\n  //-----------------------------------------------------------------------------\n  //! Constructor\n  //-----------------------------------------------------------------------------\n  FileSystem(const common::FileSystemLocator& locator, mq::MessagingRealm* realm);\n\n  //-----------------------------------------------------------------------------\n  //! Destructor\n  //-----------------------------------------------------------------------------\n  ~FileSystem();\n\n  //-----------------------------------------------------------------------------\n  //! Set local id as it was published by the MGM the first time, this won't\n  //! change throughout the lifetime of this object.\n  //-----------------------------------------------------------------------------\n  inline void SetLocalId()\n  {\n    mLocalId = GetId();\n  }\n\n  //-----------------------------------------------------------------------------\n  //! Get local id value\n  //-----------------------------------------------------------------------------\n  inline eos::common::FileSystem::fsid_t GetLocalId() const\n  {\n    return mLocalId;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set local uuid as it was published by the MGM the first time, this won't\n  //! change throughout the lifetime of this object.\n  //----------------------------------------------------------------------------\n  inline void SetLocalUuid()\n  {\n    mLocalUuid = GetString(\"uuid\");\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get local id value\n  //----------------------------------------------------------------------------\n  inline std::string GetLocalUuid() const\n  {\n    return mLocalUuid;;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Configure scanner thread - possibly start the scanner\n  //!\n  //! @param fst_load file system IO load monitoring object\n  //! @param key configuration key\n  //! @param value configuration value\n  //----------------------------------------------------------------------------\n  void ConfigScanner(Load* fst_load, const std::string& key, long long value);\n\n  //----------------------------------------------------------------------------\n  //! Set file system boot status\n  //!\n  //! @param status new value to set\n  //----------------------------------------------------------------------------\n  void SetStatus(eos::common::BootStatus status);\n\n  //----------------------------------------------------------------------------\n  //! Get local boot status\n  //----------------------------------------------------------------------------\n  eos::common::BootStatus\n  GetStatus() const\n  {\n    // we patch this function because we don't want to see the shared information\n    // but the 'true' information created locally\n    return mLocalBootStatus;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Broadcast given error message\n  //!\n  //! @param msg message to be sent\n  //----------------------------------------------------------------------------\n  void BroadcastError(const char* msg);\n\n  //----------------------------------------------------------------------------\n  //! Broadcast given error code and message\n  //!\n  //! @param errc error code to be sent\n  //! @param msg message to be sent\n  //----------------------------------------------------------------------------\n  void BroadcastError(int errc, const char* errmsg);\n\n  //----------------------------------------------------------------------------\n  //! Set given error code and message\n  //----------------------------------------------------------------------------\n  void SetError(int errc, const char* errmsg);\n\n  //----------------------------------------------------------------------------\n  //! Get statfs info about mountpoint\n  //----------------------------------------------------------------------------\n  std::unique_ptr<eos::common::Statfs> GetStatfs();\n\n  //----------------------------------------------------------------------------\n  //! Get file system disk performance metrics eg. IOPS/seq bandwidth\n  //----------------------------------------------------------------------------\n  void IoPing();\n\n  //----------------------------------------------------------------------------\n  //! Get sequential bandwidth\n  //----------------------------------------------------------------------------\n  inline long long getSeqBandwidth()\n  {\n    return seqBandwidth;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get IOPS\n  //----------------------------------------------------------------------------\n  inline int getIOPS()\n  {\n    return IOPS;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Do a reload of the underlying file IO object\n  //----------------------------------------------------------------------------\n  // bool condReloadFileIo(std::string iotype)\n  // {\n  //   if (!mFileIO || mFileIO->GetIoType() != iotype) {\n  //     return false;\n  //   }\n\n  //   mFileIO.reset(FileIoPlugin::GetIoObject(GetPath().c_str()));\n  //   return true;\n  // }\n\n  //----------------------------------------------------------------------------\n  //! Get IO statistics from the `sys.iostats` xattr\n  //!\n  //! @param map map containing returned information\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool GetFileIOStats(std::map<std::string, std::string>& map);\n\n  //----------------------------------------------------------------------------\n  //! Get health information from the `sys.health` xattr\n  //!\n  //! @param map map containing health information\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool GetHealthInfo(std::map<std::string, std::string>& map);\n\n  //----------------------------------------------------------------------------\n  //! Decide if we should run the boot procedure for current file system\n  //!\n  //! @param trigger update key that trigger the current check\n  //!\n  //! @return true if boot should run, otherwise false\n  //----------------------------------------------------------------------------\n  bool ShouldBoot(const std::string& trigger);\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Process shared hash update\n  //!\n  //! @param upd shared hash update\n  //----------------------------------------------------------------------------\n  void ProcessUpdateCb(qclient::SharedHashUpdate&& upd);\n\n  //! Subscription to underlying shared hash notifications\n  std::unique_ptr<qclient::SharedHashSubscription> mSubscription;\n  //! Local file system id irrespective of the shared hash status, populated\n  //! the first time the id is broadcasted from the mgm\n  eos::common::FileSystem::fsid_t mLocalId {0ull};\n  //! Local file system uuid irrespective of the shared hash status, populated\n  //! the first time the *id* is broadcasted from the mgm\n  std::string mLocalUuid;\n  std::unique_ptr<eos::fst::ScanDir> mScanDir; ///< Filesystem scanner\n  std::unique_ptr<FileIo> mFileIO; ///< File used for statfs calls\n  unsigned long last_blocks_free;\n  time_t last_status_broadcast;\n  //! Internal boot state not stored in the shared hash\n  std::atomic<eos::common::BootStatus> mLocalBootStatus;\n  long long seqBandwidth; // measurement of sequential bandwidth\n  int IOPS; // measurement of IOPS\n  bool mRecoverable; // true if a filesystem was booted and then set to ops error\n};\n\nEOSFSTNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "fst/storage/MgmSyncer.cc",
    "content": "// ----------------------------------------------------------------------\n// File: MgmSyncer.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/storage/Storage.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/Config.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\nvoid\nStorage::MgmSyncer()\n{\n  // this thread checks the synchronization between the local MD after a file\n  // modification/write against the MGM server\n  bool knowmanager = false;\n\n  while (1) {\n    XrdOucString manager = \"\";\n    size_t cnt = 0;\n\n    // get the currently active manager\n    do {\n      cnt++;\n      {\n        XrdSysMutexHelper lock(gConfig.Mutex);\n        manager = gConfig.Manager.c_str();\n      }\n\n      if (manager != \"\") {\n        if (!knowmanager) {\n          eos_info(\"msg=\\\"manager known\\\" manager=\\\"%s\\\"\", manager.c_str());\n          knowmanager = true;\n        }\n\n        break;\n      }\n\n      std::this_thread::sleep_for(std::chrono::seconds(5));\n      eos_info(\"msg=\\\"waiting to know manager\\\"\");\n\n      if (cnt > 20) {\n        eos_static_alert(\"didn't receive manager name, aborting\");\n        std::this_thread::sleep_for(std::chrono::seconds(10));\n        XrdFstOfs::xrdfstofs_shutdown(1);\n      }\n    } while (1);\n\n    bool failure = false;\n    gOFS.WrittenFilesQueueMutex.lock();\n\n    while (gOFS.WrittenFilesQueue.size() > 0) {\n      // we enter this loop with the WrittenFilesQueueMutex locked\n      time_t now = time(NULL);\n      eos::common::FmdHelper fmd = gOFS.WrittenFilesQueue.front();\n      gOFS.WrittenFilesQueue.pop();\n      gOFS.WrittenFilesQueueMutex.unlock();\n      // Guarantee that we delay the check by at least 60 seconds to wait\n      // for the commit of all replicas\n      time_t delay = fmd.mProtoFmd.mtime() + 60 - now;\n\n      if ((delay > 0) && (delay <= 60)) {\n        // only values less than a minute should be taken into account here\n        eos_static_debug(\"msg=\\\"postpone mgm sync\\\" delay=%d\",\n                         delay);\n        std::this_thread::sleep_for(std::chrono::seconds(delay));\n        gOFS.WrittenFilesQueueMutex.lock();\n        gOFS.WrittenFilesQueue.push(fmd);\n        continue;\n      }\n\n      eos_static_info(\"fxid=%08llx mtime=%llu\", fmd.mProtoFmd.fid(),\n                      fmd.mProtoFmd.mtime());\n      bool isopenforwrite = gOFS.openedForWriting.isOpen(fmd.mProtoFmd.fsid(),\n                            fmd.mProtoFmd.fid());\n\n      if (!isopenforwrite) {\n        // now do the consistency check\n        if (gOFS.mFmdHandler->ResyncMgm(fmd.mProtoFmd.fsid(),\n                                        fmd.mProtoFmd.fid(), nullptr)) {\n          eos_static_debug(\"msg=\\\"resync ok\\\" fsid=%lu fxid=%08llx\",\n                           (unsigned long) fmd.mProtoFmd.fsid(),\n                           fmd.mProtoFmd.fid());\n          gOFS.WrittenFilesQueueMutex.lock();\n        } else {\n          eos_static_err(\"msg=\\\"resync failed\\\" fsid=%lu fxid=%08llx\",\n                         (unsigned long) fmd.mProtoFmd.fsid(),\n                         fmd.mProtoFmd.fid());\n          failure = true;\n          gOFS.WrittenFilesQueueMutex.lock(); // put back the lock and the entry\n          gOFS.WrittenFilesQueue.push(fmd);\n          break;\n        }\n      } else {\n        // if there was still a reference, we can just discard this check\n        // since the other write open will trigger a new entry in the queue\n        gOFS.WrittenFilesQueueMutex.lock(); // put back the lock\n      }\n    }\n\n    gOFS.WrittenFilesQueueMutex.unlock();\n\n    if (failure) {\n      // the last synchronization to the MGM failed, we wait longer\n      std::this_thread::sleep_for(std::chrono::seconds(10));\n    } else {\n      // the queue was empty\n      std::this_thread::sleep_for(std::chrono::seconds(1));\n    }\n  }\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/storage/MonitorVarPartition.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file MonitorVarPartition.hh\n//! @author Stefan Isidorovic <stefan.isidorovic@comtrade.com>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_STORAGE_MONITORVARPARTITION__HH__\n#define __EOSFST_STORAGE_MONITORVARPARTITION__HH__\n\n#include \"common/Logging.hh\"\n#include \"common/FileSystem.hh\"\n#include \"common/RWMutex.hh\"\n#include \"fst/Namespace.hh\"\n#include \"fst/storage/FileSystem.hh\"\n#include <unistd.h>\n#include <sys/statvfs.h>\n#include <string>\n#include <errno.h>\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class MonitorVarPartition\n//!\n//! @description The var partition monitoring thread is responsible with\n//!  switching the FST on a particular machine in read-only mode if the space\n//!  available on the /var/ partition drops under a specified threshold value.\n//------------------------------------------------------------------------------\ntemplate<class FSs>\nclass MonitorVarPartition : public eos::common::LogId\n{\n  double mSpaceThreshold; ///< Free space threshold when FST state is changed\n  int mIntervalMicroSec; ///< Check time interval\n  std::string mPath; ///< Monitored path\n  bool mRunning; ///< State of monitoring thread\n\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param threshold threshold value when FSTs are switched to RO mode\n  //! @param time thread interval check in seconds\n  //! @param path path being monitored\n  //----------------------------------------------------------------------------\n  MonitorVarPartition(double threshold, int time, std::string path) :\n    mSpaceThreshold(threshold), mIntervalMicroSec(time * 1000 * 1000),\n    mPath(path), mRunning(true)\n  {\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~MonitorVarPartition() = default;\n\n  //----------------------------------------------------------------------------\n  //! Actual monitoring implementation\n  //!\n  //! @param fss list of FSTs that needs to be updated\n  //! @param mtx FST status mutex\n  //----------------------------------------------------------------------------\n  void Monitor(FSs& fss, eos::common::RWMutex& mtx)\n  {\n    eos_info(\"%s\", \"msg=\\\"fst partition monitor activated\\\"\");\n    struct statvfs buf;\n    char buffer[256];\n\n    while (mRunning) {\n      // Get info about filesystem where mPath is located\n      if (statvfs(mPath.c_str(), &buf) == -1) {\n        char* errorMessage = strerror_r(errno, buffer, 256);\n        eos_err(\"msg=\\\"statvfs failed\\\" error=\\\"%s\\\" \", errorMessage);\n        continue;\n      }\n\n      // Calculating precentage of free space left while ignoring fragment\n      // size as doesn't matter in calculating percentage\n      double free_percentage = ((buf.f_bfree * 1.) / buf.f_blocks) * 100.;\n\n      if (free_percentage < mSpaceThreshold) {\n        eos_crit(\"msg=\\\"partition holding %s is almost full, FSTs set to \"\n                 \"read-only mode - please take action\\\"\", mPath.c_str());\n        eos::common::RWMutexReadLock fs_rd_lock(mtx);\n\n        for (auto fs = fss.begin(); fs != fss.end(); ++fs) {\n          if ((*fs)->GetConfigStatus() != eos::common::ConfigStatus::kRO) {\n            (*fs)->SetString(\"configstatus\", \"ro\");\n          }\n        }\n      }\n\n      usleep(mIntervalMicroSec);\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Switch off monitoring\n  //----------------------------------------------------------------------------\n  void StopMonitoring()\n  {\n    mRunning = false;\n  }\n};\n\nEOSFSTNAMESPACE_END\n#endif // __EOSFST_STORAGE_MONITORVARPARTITION__HH__\n"
  },
  {
    "path": "fst/storage/Publish.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Publish.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"Constants.hh\"\n#include \"common/IntervalStopwatch.hh\"\n#include \"common/LinuxStat.hh\"\n#include \"common/ShellCmd.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/Timing.hh\"\n#include \"common/Utils.hh\"\n#include \"fst/Config.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/storage/FileSystem.hh\"\n#include \"fst/storage/Storage.hh\"\n#include \"fst/storage/TrafficShaping.hh\"\n#include \"mgm/shaping/TrafficShaping.hh\"\n#include \"qclient/Formatting.hh\"\n\n#include <XrdVersion.hh>\n#include <google/protobuf/util/json_util.h>\n#include <optional>\n#include <sys/sysinfo.h>\n\n#ifdef PROCPS3\n#include <proc/readproc.h>\n#else\n#include <libproc2/pids.h>\n#endif\n\nXrdVERSIONINFOREF(XrdgetProtocol);\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Serialize hot files vector into std::string\n// Return \" \" if given an empty vector, instead of \"\".\n//\n// This is to keep the entry in the hash, even if no opened files exist.\n//------------------------------------------------------------------------------\nstatic std::string HotFilesToString(const std::vector<eos::fst::OpenFileTracker::HotEntry>& entries) {\n  if (entries.size() == 0u) { return \" \"; }\n\n  std::ostringstream ss;\n\n  for (size_t i = 0; i < entries.size(); i++) {\n    ss << entries[i].uses;\n    ss << \":\";\n    ss << eos::common::FileId::Fid2Hex(entries[i].fid);\n    ss << \" \";\n  }\n\n  return ss.str();\n}\n\n//------------------------------------------------------------------------------\n// Get uptime information in a more pretty format\n//------------------------------------------------------------------------------\nstatic void GetUptime(std::map<std::string, std::string>& output) {\n  static float f_load = 1.f / (1 << SI_LOAD_SHIFT);\n  static auto GetUptimePretty = [](const struct sysinfo& input) -> std::string {\n    std::stringstream oss;\n    std::time_t now_t = eos::common::Timing::GetNowInSec();\n    struct tm now_tm_parts;\n    (void)localtime_r(&now_t, &now_tm_parts);\n    char str[9];\n\n    if (strftime(str, 9, \"%H:%M:%S\", &now_tm_parts)) {\n      oss << str << \" up \";\n      int t_mins = (input.uptime % 3600) / 60;\n      int t_hours = (input.uptime % 86400) / 3600;\n      int t_days = input.uptime / 86400;\n\n      if (t_days) { oss << t_days << \" days, \"; }\n\n      oss << t_hours << \":\" << t_mins << \", load average: \";\n      oss.setf(std::ios::fixed);\n      oss.precision(2);\n      oss << input.loads[0] * f_load << \", \" << input.loads[1] * f_load << \", \" << input.loads[2] * f_load;\n      return oss.str();\n    }\n\n    return \"N/A\";\n  };\n  struct sysinfo info;\n\n  if (sysinfo(&info) == 0) {\n    try {\n      output[\"stat.sys.uptime_sec\"] = std::to_string(info.uptime);\n      output[\"stat.sys.load_avg_1m\"] = std::to_string(info.loads[0] * f_load);\n      output[\"stat.sys.load_avg_5m\"] = std::to_string(info.loads[1] * f_load);\n      output[\"stat.sys.load_avg_15m\"] = std::to_string(info.loads[2] * f_load);\n      output[\"stat.sys.uptime\"] = GetUptimePretty(info);\n      return;\n    } catch (...) {\n      // any error will populate the data with N/A entries\n    }\n  }\n\n  output[\"stat.sys.uptime_sec\"] = \"N/A\";\n  output[\"stat.sys.load_avg_1m\"] = \"N/A\";\n  output[\"stat.sys.load_avg_5m\"] = \"N/A\";\n  output[\"stat.sys.load_avg_15m\"] = \"N/A\";\n  output[\"stat.sys.uptime\"] = \"N/A\";\n}\n\n//------------------------------------------------------------------------------\n// Retrieve xrootd version\n//------------------------------------------------------------------------------\nstatic std::string GetXrootdVersion() {\n  static std::string s_xrootd_version = \"\";\n\n  if (!s_xrootd_version.empty()) { return s_xrootd_version; }\n\n  XrdOucString v = XrdVERSIONINFOVAR(XrdgetProtocol).vStr;\n  int pos = v.find(\" \");\n\n  if (pos != STR_NPOS) { v.erasefromstart(pos + 1); }\n\n  s_xrootd_version = v.c_str();\n  return s_xrootd_version;\n}\n\n//------------------------------------------------------------------------------\n// Retrieve eos version\n//------------------------------------------------------------------------------\nstatic std::string GetEosVersion() {\n  static std::string s_eos_version = SSTR(VERSION << \"-\" << RELEASE).c_str();\n  return s_eos_version;\n}\n\n//------------------------------------------------------------------------------\n// Retrieve FST network interface\n//------------------------------------------------------------------------------\nstatic std::string GetNetworkInterface() {\n  static std::string s_net_interface = \"\";\n\n  if (!s_net_interface.empty()) { return s_net_interface; }\n\n  const char* ptr = getenv(\"EOS_FST_NETWORK_INTERFACE\");\n\n  if (ptr) {\n    // Use value set in the environment\n    s_net_interface = ptr;\n  } else {\n    // Use blindly the default\n    s_net_interface = \"eth0\";\n  }\n\n  return s_net_interface;\n}\n\n//------------------------------------------------------------------------------\n// Get network transfer RX/TX errors and dropped packet counters\n//------------------------------------------------------------------------------\nstatic void GetNetworkCounters(std::map<std::string, std::string>& output) {\n  static const std::set<std::string> set_keys{\"rx_errors\", \"rx_dropped\", \"tx_errors\", \"tx_dropped\"};\n  static std::map<std::string, std::string> map_key_paths;\n\n  // Build set of files to query for the above counters depending on the\n  // network interface name\n  if (map_key_paths.empty()) {\n    struct stat info;\n\n    for (const auto& key : set_keys) {\n      std::string fn_path = SSTR(\"/sys/class/net/\" << GetNetworkInterface() << \"/statistics/\" << key);\n\n      if (::stat(fn_path.c_str(), &info)) {\n        map_key_paths[key] = \"\";\n      } else {\n        map_key_paths[key] = fn_path;\n      }\n    }\n  }\n\n  static const int max_read = 32;\n  static char data[32] = \"\";\n  std::map<std::string, std::string> map_counters;\n\n  for (const auto& pair : map_key_paths) {\n    map_counters[pair.first] = \"N/A\";\n    FILE* fnetcounters = fopen(pair.second.c_str(), \"r\");\n\n    if (fnetcounters) {\n      data[0] = '\\0';\n      int nbytes = fread((void*)data, sizeof(char), max_read, fnetcounters);\n\n      if (nbytes > 1) {\n        data[nbytes - 1] = '\\0';\n        map_counters[pair.first] = data;\n      }\n\n      (void)fclose(fnetcounters);\n    }\n  }\n\n  for (const auto& pair : map_counters) {\n    const std::string key = \"stat.net.\" + pair.first;\n    output[key] = pair.second;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Retrieve network interface speed as bytes/second\n//------------------------------------------------------------------------------\nstatic uint64_t GetNetSpeed() {\n  static uint64_t s_net_speed = 0ull;\n\n  if (s_net_speed) { return s_net_speed; }\n\n  const char* ptr = getenv(\"EOS_FST_NETWORK_SPEED\");\n\n  if (ptr) {\n    const std::string sval = ptr;\n\n    try {\n      s_net_speed = std::stoull(sval);\n\n      if (s_net_speed) { return s_net_speed; }\n    } catch (...) {\n      eos_static_err(\"msg=\\\"EOS_FST_NETWORK_SPEED not a numeric value\\\" \"\n                     \"val=\\\"%s\\\"\",\n                     sval.c_str());\n    }\n  }\n\n  // Default value set to 1Gb/s\n  s_net_speed = 1000000000;\n  // Read network speed from the sys interface\n  const std::string net_interface = GetNetworkInterface();\n  const std::string fn_path = SSTR(\"/sys/class/net/\" << net_interface << \"/speed\");\n  FILE* fnetspeed = fopen(fn_path.c_str(), \"r\");\n\n  if (fnetspeed) {\n    const int max_read = 32;\n    char data[32] = \"\";\n    int nbytes = fread((void*)data, sizeof(char), max_read, fnetspeed);\n\n    if (nbytes > 1) {\n      data[nbytes] = '\\0';\n\n      try {\n        const std::string sval = data;\n        s_net_speed = std::stoull(sval);\n        // We get Mb/s as number, convert to bytes/s\n        s_net_speed *= 1000000;\n      } catch (...) { eos_static_err(\"msg=\\\"network speed not a numeric value\\\" fn=\\\"%s\\\"\", fn_path.c_str()); }\n    }\n\n    (void)fclose(fnetspeed);\n  }\n\n  eos_static_info(\"msg=\\\"network speed\\\" interface=\\\"%s\\\" speed=%.02f GB/s\", net_interface.c_str(),\n                  1.0 * s_net_speed / 1000000000.0);\n  return s_net_speed;\n}\n\n//------------------------------------------------------------------------------\n// Retrieve number of TCP sockets in the system\n//------------------------------------------------------------------------------\nstatic std::string GetNumOfTcpSockets() {\n  static auto ReadTpcSocketsInUse = [](const std::string& fn, const std::string& search_tag) -> uint64_t {\n    uint64_t num_sockets = 0ull;\n    std::ifstream file(fn.c_str());\n\n    if (file.is_open()) {\n      std::string line;\n\n      while (std::getline(file, line)) {\n        if (line.find(search_tag) == 0) {\n          line.erase(0, search_tag.length());\n\n          try {\n            num_sockets = std::stoull(line.substr(0, line.find(' ')));\n          } catch (...) {\n            // if any error we report 0\n          }\n\n          break;\n        }\n      }\n    }\n\n    return num_sockets;\n  };\n  static const std::string tcp4_fn = \"/proc/net/sockstat\";\n  static const std::string tcp6_fn = \"/proc/net/sockstat6\";\n  static const std::string tcp4_tag = \"TCP: inuse \";\n  static const std::string tcp6_tag = \"TCP6: inuse \";\n  uint64_t num_tcp4_sockets = ReadTpcSocketsInUse(tcp4_fn, tcp4_tag);\n  uint64_t num_tcp6_sockets = ReadTpcSocketsInUse(tcp6_fn, tcp6_tag);\n  return std::to_string(num_tcp4_sockets + num_tcp6_sockets);\n}\n\n//------------------------------------------------------------------------------\n// Get size of subtree by using the system \"du -sb\" command\n//------------------------------------------------------------------------------\nstatic std::string GetSubtreeSize(const std::string& path) {\n  std::string fn_pattern = \"/tmp/fst.subtree.XXXXXX\";\n  const std::string tmp_name = eos::common::MakeTemporaryFile(fn_pattern);\n  const std::string command = SSTR(\"du -sb \" << path << \" | cut -f1 > \" << tmp_name);\n  eos::common::ShellCmd cmd(command.c_str());\n  eos::common::cmd_status rc = cmd.wait(5);\n\n  if (rc.exit_code) { eos_static_err(\"msg=\\\"failed to compute subtree size\\\" path=%s\", path.c_str()); }\n\n  std::string retval;\n  eos::common::StringConversion::LoadFileIntoString(tmp_name.c_str(), retval);\n  (void)unlink(tmp_name.c_str());\n  return retval;\n}\n\n//------------------------------------------------------------------------------\n// Get number of kworker processes on the machine - a high number might indicate\n// a problem with the machine and might require a reboot.\n//------------------------------------------------------------------------------\nstatic uint32_t GetNumOfKworkerProcs() {\n  uint32_t count = 0u;\n#ifdef PROCPS3\n\n  if (proc_t** procs = readproctab(PROC_FILLSTAT)) {\n    for (int i = 0; procs[i]; ++i) {\n      if (procs[i]->cmd) {\n        eos_static_debug(\"msg=\\\"process cmd line\\\" cmd=\\\"%s\\\"\", procs[i]->cmd);\n\n        if (strstr(procs[i]->cmd, \"kworker\") == procs[i]->cmd) { ++count; }\n      }\n\n      freeproc(procs[i]);\n    }\n\n    free(procs);\n  }\n\n#else\n  struct pids_info* info = nullptr;\n  enum pids_item items[] = {PIDS_CMD};\n  procps_pids_new(&info, items, 1);\n\n  while (struct pids_stack* stack = procps_pids_get(info, PIDS_FETCH_TASKS_ONLY)) {\n    char* cmd = PIDS_VAL(0, str, stack, info);\n\n    if (strstr(cmd, \"kworker\") == cmd) { ++count; }\n  }\n\n  procps_pids_unref(&info);\n#endif\n  eos_static_debug(\"msg=\\\"current number of kworker processes\\\" count=%i\", count);\n  return count;\n}\n\n//------------------------------------------------------------------------------\n// Overwrite statfs statistics for testing environment\n//------------------------------------------------------------------------------\nstatic void OverwriteTestingStatfs(const std::string& path, std::map<std::string, std::string>& output) {\n  static std::optional<bool> do_overwrite;\n\n  if (!do_overwrite.has_value()) {\n    const char* ptr = getenv(\"EOS_FST_TESTING\");\n\n    if (ptr == nullptr) {\n      do_overwrite.emplace(false);\n    } else {\n      do_overwrite.emplace(true);\n    }\n  }\n\n  if (!do_overwrite.value()) { return; }\n\n  eos_static_info(\"msg=\\\"overwrite statfs values\\\" path=%s\", path.c_str());\n  static uint64_t subtree_max_size = 0ull;\n\n  if (subtree_max_size == 0ull) {\n    subtree_max_size = 10ull * 1024 * 1024 * 1024; // 10GB\n    const char* ptr = getenv(\"EOS_FST_SUBTREE_MAX_SIZE\");\n\n    if (ptr) {\n      if (!eos::common::StringToNumeric(std::string(ptr), subtree_max_size, subtree_max_size)) {\n        eos_static_err(\"msg=\\\"failed convertion\\\" data=\\\"%s\\\"\", ptr);\n      }\n    }\n  }\n\n  uint64_t bsize = 4096;\n  (void)eos::common::StringToNumeric(output[\"stat.statfs.bsize\"], bsize, bsize);\n  uint64_t used_bytes{0ull};\n  const std::string sused_bytes = GetSubtreeSize(path);\n  (void)eos::common::StringToNumeric(sused_bytes, used_bytes);\n  double filled = 100.0 - ((double)100.0 * (subtree_max_size - used_bytes) / subtree_max_size);\n  output[\"stat.statfs.filled\"] = std::to_string(filled);\n  output[\"stat.statfs.usedbytes\"] = std::to_string(used_bytes);\n  output[\"stat.statfs.freebytes\"] = std::to_string(subtree_max_size - used_bytes);\n  output[\"stat.statfs.capacity\"] = std::to_string(subtree_max_size);\n}\n\n//------------------------------------------------------------------------------\n// Get statistics about this FST, used for publishing\n//------------------------------------------------------------------------------\nstd::map<std::string, std::string> Storage::GetFstStatistics(const std::string& tmpfile, unsigned long long netspeed) {\n  eos::common::LinuxStat::linux_stat_t osstat;\n\n  if (!eos::common::LinuxStat::GetStat(osstat)) { eos_crit(\"failed to get the memory usage information\"); }\n\n  std::map<std::string, std::string> output;\n  // Kernel version\n  output[\"stat.sys.kernel\"] = gConfig.KernelVersion.c_str();\n  // Virtual memory size\n  output[\"stat.sys.vsize\"] = SSTR(osstat.vsize);\n  // rss usage\n  output[\"stat.sys.rss\"] = SSTR(osstat.rss);\n  // number of active threads on this machine\n  output[\"stat.sys.threads\"] = SSTR(osstat.threads);\n  // eos version\n  output[\"stat.sys.eos.version\"] = GetEosVersion();\n  // xrootd version\n  output[\"stat.sys.xrootd.version\"] = GetXrootdVersion();\n  // adler32 of keytab\n  output[\"stat.sys.keytab\"] = gConfig.KeyTabAdler.c_str();\n  // machine uptime\n  GetUptime(output);\n  // active TCP sockets\n  output[\"stat.sys.sockets\"] = GetNumOfTcpSockets();\n  // number of kworker processes\n  output[\"stat.sys.kworkers\"] = std::to_string(GetNumOfKworkerProcs());\n  // Collect network RX/TX errors and dropped packets\n  GetNetworkCounters(output);\n  // startup time of the FST daemon\n  output[\"stat.sys.eos.start\"] = gConfig.StartDate.c_str();\n  // FST geotag\n  output[\"stat.geotag\"] = gOFS.GetGeoTag();\n  // http port\n  output[\"http.port\"] = SSTR(gOFS.mHttpdPort);\n  // debug level\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n  output[\"debug.state\"] =\n      eos::common::StringConversion::ToLower(g_logging.GetPriorityString(g_logging.gPriorityLevel)).c_str();\n  // net info\n  output[\"stat.net.ethratemib\"] = SSTR(netspeed / (8 * 1024 * 1024));\n  output[\"stat.net.inratemib\"] = SSTR(mFstLoad.GetNetRate(GetNetworkInterface().c_str(), \"rxbytes\") / 1024.0 / 1024.0);\n  output[\"stat.net.outratemib\"] = SSTR(mFstLoad.GetNetRate(GetNetworkInterface().c_str(), \"txbytes\") / 1024.0 / 1024.0);\n  // publish timestamp\n  output[\"stat.publishtimestamp\"] = SSTR(eos::common::getEpochInMilliseconds().count());\n\n  return output;\n}\n\n//------------------------------------------------------------------------------\n// Insert statfs info into the map\n//------------------------------------------------------------------------------\nstatic void InsertStatfs(struct statfs* statfs, std::map<std::string, std::string>& output) {\n  output[\"stat.statfs.type\"] = std::to_string(statfs->f_type);\n  output[\"stat.statfs.bsize\"] = std::to_string(statfs->f_bsize);\n  output[\"stat.statfs.blocks\"] = std::to_string(statfs->f_blocks);\n  output[\"stat.statfs.bfree\"] = std::to_string(statfs->f_bfree);\n  output[\"stat.statfs.bavail\"] = std::to_string(statfs->f_bavail);\n  output[\"stat.statfs.files\"] = std::to_string(statfs->f_files);\n  output[\"stat.statfs.ffree\"] = std::to_string(statfs->f_ffree);\n#ifdef __APPLE__\n  output[\"stat.statfs.namelen\"] = std::to_string(MNAMELEN);\n#else\n  output[\"stat.statfs.namelen\"] = std::to_string(statfs->f_namelen);\n#endif\n  output[\"stat.statfs.freebytes\"] = std::to_string(statfs->f_bfree * statfs->f_bsize);\n  output[\"stat.statfs.usedbytes\"] = std::to_string((statfs->f_blocks - statfs->f_bfree) * statfs->f_bsize);\n  output[\"stat.statfs.filled\"] =\n      std::to_string((double)100.0 * ((double)(statfs->f_blocks - statfs->f_bfree) / (double)(1 + statfs->f_blocks)));\n  output[\"stat.statfs.capacity\"] = std::to_string(statfs->f_blocks * statfs->f_bsize);\n  output[\"stat.statfs.fused\"] = std::to_string(statfs->f_files - statfs->f_ffree);\n}\n\n//------------------------------------------------------------------------------\n// Get statistics about this FileSystem, used for publishing\n//------------------------------------------------------------------------------\nstd::map<std::string, std::string> Storage::GetFsStatistics(FileSystem* fs) {\n  if (!fs) {\n    eos_static_crit(\"asked to publish statistics for a null filesystem\");\n    return {};\n  }\n\n  eos::common::FileSystem::fsid_t fsid = fs->GetLocalId();\n\n  if (!fsid) {\n    // during the boot phase we can find a filesystem without ID\n    eos_static_warning(\"asked to publish statistics for filesystem with fsid=0\");\n    return {};\n  }\n\n  std::map<std::string, std::string> output;\n  // Publish statfs\n  std::unique_ptr<eos::common::Statfs> statfs = fs->GetStatfs();\n\n  if (statfs) {\n    InsertStatfs(statfs->GetStatfs(), output);\n    OverwriteTestingStatfs(fs->GetPath(), output);\n  }\n\n  // Publish stat.disk.*\n  double readratemb;\n  double writeratemb;\n  double diskload;\n  std::map<std::string, std::string> iostats;\n\n  if (fs->GetFileIOStats(iostats)) {\n    readratemb = strtod(iostats[\"read-mb-second\"].c_str(), 0);\n    writeratemb = strtod(iostats[\"write-mb-second\"].c_str(), 0);\n    diskload = strtod(iostats[\"load\"].c_str(), 0);\n  } else {\n    readratemb = mFstLoad.GetDiskRate(fs->GetPath().c_str(), \"readSectors\") * 512.0 / 1000000.0;\n    writeratemb = mFstLoad.GetDiskRate(fs->GetPath().c_str(), \"writeSectors\") * 512.0 / 1000000.0;\n    diskload = mFstLoad.GetDiskRate(fs->GetPath().c_str(), \"millisIO\") / 1000.0;\n  }\n\n  output[\"stat.disk.readratemb\"] = std::to_string(readratemb);\n  output[\"stat.disk.writeratemb\"] = std::to_string(writeratemb);\n  output[\"stat.disk.load\"] = std::to_string(diskload);\n  // Publish stat.health.*\n  std::map<std::string, std::string> health;\n\n  if (!fs->GetHealthInfo(health)) {\n    health = mFstHealth.getDiskHealth(fs->GetPath());\n\n    // If SMART status is FAILING then mark the file system for auto drain\n    if (mDrainOnSmartErr && health.count(\"summary\") && health[\"summary\"] == \"FAILING\") {\n      fs->BroadcastError(EIO, \"S.M.A.R.T. errors detected\");\n    }\n  }\n\n  output[\"stat.health\"] = (health.count(\"summary\") ? health[\"summary\"].c_str() : \"N/A\");\n  // set some reasonable defaults if information is not available\n  output[\"stat.health.indicator\"] = (health.count(\"indicator\") ? health[\"indicator\"] : \"N/A\");\n  output[\"stat.health.drives_total\"] = (health.count(\"drives_total\") ? health[\"drives_total\"] : \"1\");\n  output[\"stat.health.drives_failed\"] = (health.count(\"drives_failed\") ? health[\"drives_failed\"] : \"0\");\n  output[\"stat.health.redundancy_factor\"] = (health.count(\"redundancy_factor\") ? health[\"redundancy_factor\"] : \"1\");\n  {\n    // don't publish smart info too often, it is few kb per filesystem!\n    time_t now = time(NULL);\n    static std::map<FileSystem*, time_t> smartPublishing;\n    static XrdSysMutex smartPublishingMutex;\n    bool publish = false;\n    {\n      XrdSysMutexHelper scope_lock(smartPublishingMutex);\n\n      if (!smartPublishing[fs] || (smartPublishing[fs] < now)) {\n        smartPublishing[fs] = now + 3600;\n        publish = true;\n      }\n    }\n\n    if (publish) {\n      // compress the json smart info\n      eos::common::SymKey::ZBase64(health[\"attributes\"], output[\"stat.health.z64smart\"]);\n    }\n  }\n  // Publish generic statistics, related to free space and current load\n  long long r_open = (long long)gOFS.openedForReading.getOpenOnFilesystem(fsid);\n  long long w_open = (long long)gOFS.openedForWriting.getOpenOnFilesystem(fsid);\n  output[\"stat.ropen\"] = std::to_string(r_open);\n  output[\"stat.wopen\"] = std::to_string(w_open);\n\n  if (auto kv = output.find(\"stat.statfs.fused\"); kv != output.end()) {\n    // FIXME: Actually subtract the statfs of the .eosorphans, also count\n    // checksums & scrub files!\n    output[\"stat.usedfiles\"] = kv->second;\n  }\n\n  output[\"stat.boot\"] = fs->GetStatusAsString(fs->GetStatus());\n  output[\"stat.geotag\"] = gOFS.GetGeoTag();\n  output[\"stat.publishtimestamp\"] = std::to_string(eos::common::getEpochInMilliseconds().count());\n  output[\"stat.disk.iops\"] = std::to_string(fs->getIOPS());\n  output[\"stat.disk.bw\"] = std::to_string(fs->getSeqBandwidth()); // in MB\n  output[\"stat.http.port\"] = std::to_string(gOFS.mHttpdPort);\n\n  // FST alias\n  if (gConfig.HostAlias.length()) { output[\"stat.alias.host\"] = gConfig.HostAlias.c_str(); }\n\n  // FST port alias\n  if (gConfig.PortAlias.length()) { output[\"stat.alias.port\"] = gConfig.PortAlias.c_str(); }\n\n  // debug level\n  output[\"stat.ropen.hotfiles\"] = HotFilesToString(gOFS.openedForReading.getHotFiles(fsid, 10));\n  output[\"stat.wopen.hotfiles\"] = HotFilesToString(gOFS.openedForWriting.getHotFiles(fsid, 10));\n  return output;\n}\n\n//------------------------------------------------------------------------------\n// Publish statistics about the given filesystem\n//------------------------------------------------------------------------------\nbool Storage::PublishFsStatistics(eos::common::FileSystem::fsid_t fsid) {\n  CheckFilesystemFullness(fsid);\n  eos::common::RWMutexReadLock fs_rd_lock(mFsMutex);\n  auto it = mFsMap.find(fsid);\n\n  if (it == mFsMap.end()) {\n    eos_static_crit(\"msg=\\\"asked to publish statistics for unknown fs\\\" \"\n                    \"fsid=%lu\",\n                    fsid);\n    return false;\n  }\n\n  common::FileSystemUpdateBatch batch;\n  FileSystem* fs = it->second;\n  std::map<std::string, std::string> fsStats = GetFsStatistics(fs);\n\n  for (auto it = fsStats.begin(); it != fsStats.end(); it++) {\n    batch.setStringTransient(it->first, it->second);\n  }\n\n  return fs->applyBatch(batch);\n}\n\n//------------------------------------------------------------------------------\n// Publish\n//------------------------------------------------------------------------------\nvoid Storage::Publish(ThreadAssistant& assistant) noexcept {\n  eos_static_info(\"%s\", \"msg=\\\"start file system publishing thread\\\"\");\n  std::string fn_pattern = \"/tmp/fst.publish.XXXXXX\";\n  const std::string tmp_name = eos::common::MakeTemporaryFile(fn_pattern);\n\n  if (tmp_name.empty()) { return; }\n\n  // The following line acts as a barrier that prevents progress\n  // until the config queue becomes known\n  gConfig.getFstNodeConfigQueue(\"Publish\");\n\n  while (!assistant.terminationRequested()) {\n    std::chrono::milliseconds randomizedReportInterval = gConfig.getRandomizedPublishInterval();\n    common::IntervalStopwatch stopwatch(randomizedReportInterval);\n    std::set<eos::common::FileSystem::fsid_t> set_fsids;\n    {\n      // Reduce lock scope and avoid recursive locks of mFsMutex\n      // which is also taken inside PublishFsStatistics\n      eos::common::RWMutexReadLock fs_rd_lock(mFsMutex);\n\n      for (const auto& elem : mFsMap) {\n        if (elem.first) { set_fsids.insert(elem.first); }\n      }\n    }\n    std::map<eos::common::FileSystem::fsid_t, std::future<bool>> map_futures;\n\n    // Copy out statfs info in parallel to speed-up things\n    for (const auto& fsid : set_fsids) {\n      try {\n        map_futures.emplace(fsid, std::async(std::launch::async, &Storage::PublishFsStatistics, this, fsid));\n      } catch (const std::system_error& e) {\n        eos_static_err(\"msg=\\\"exception while collecting fs statistics\\\" \"\n                       \"fsid=%lu msg=\\\"%s\\\"\",\n                       fsid, e.what());\n      }\n    }\n\n    for (auto& elem : map_futures) {\n      if (elem.second.get() == false) { eos_static_err(\"msg=\\\"failed to publish fs stats\\\" fsid=%lu\", elem.first); }\n    }\n\n    // Collect and publish node status info\n    auto fst_stats = GetFstStatistics(tmp_name, GetNetSpeed());\n    common::SharedHashLocator locator = gConfig.getNodeHashLocator(\"Publish\");\n\n    if (!locator.empty()) {\n      mq::SharedHashWrapper::Batch batch;\n\n      for (auto it = fst_stats.begin(); it != fst_stats.end(); ++it) {\n        batch.SetTransient(it->first, it->second);\n      }\n\n      mq::SharedHashWrapper hash(gOFS.mMessagingRealm.get(), locator, true, false);\n      hash.set(batch);\n    }\n\n    if (std::chrono::milliseconds sleepTime = stopwatch.timeRemainingInCycle();\n        sleepTime == std::chrono::milliseconds(0)) {\n      eos_static_warning(\"msg=\\\"publisher cycle exceeded %d millisec - took %d \"\n                         \"millisec\",\n                         randomizedReportInterval.count(), stopwatch.timeIntoCycle());\n    } else {\n      assistant.wait_for(sleepTime);\n    }\n  }\n\n  (void)unlink(tmp_name.c_str());\n}\n\nvoid\nStorage::SendTrafficShapingStats(ThreadAssistant& assistant) noexcept\n{\n  eos_static_info(\"%s\", \"msg=\\\"Starting Traffic Shaping stats publishing thread\\\"\");\n\n  const std::string configQueue = \"TrafficShapingStats\";\n  gConfig.getFstNodeConfigQueue(configQueue);\n\n  std::unordered_map<eos::fst::traffic_shaping::IoStatsKey, std::pair<uint64_t, uint64_t>,\n                     eos::fst::traffic_shaping::IoStatsKeyHash>\n      last_sent_cache;\n\n  int loop_counter = 0;\n\n  while (!assistant.terminationRequested()) {\n    std::chrono::milliseconds reportInterval =\n        std::chrono::milliseconds(traffic_shaping::IoStatsCollector::\n                                      fst_io_stats_reporting_thread_period_milliseconds);\n    common::IntervalStopwatch stopwatch(reportInterval);\n\n    eos::traffic_shaping::FstIoReport report;\n\n    const auto mNodeId = gConfig.FstHostPort.c_str();\n    report.set_node_id(mNodeId);\n\n    const int64_t now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(\n                               std::chrono::system_clock::now().time_since_epoch())\n                               .count();\n    report.set_timestamp_ms(now_ms);\n\n    struct PendingUpdate {\n      eos::fst::traffic_shaping::IoStatsKey key;\n      uint64_t new_iops;\n      uint64_t new_generation;\n    };\n    std::vector<PendingUpdate> pending_updates;\n    std::vector<eos::fst::traffic_shaping::IoStatsKey> active_keys;\n\n    gOFS.mIoStatsCollector.VisitEntries(\n        [&](const eos::fst::traffic_shaping::IoStatsKey& key,\n            const eos::fst::traffic_shaping::IoStatsEntry& entry) {\n          active_keys.push_back(key);\n\n          uint64_t cur_r_ops = entry.read_iops.load(std::memory_order_relaxed);\n          uint64_t cur_w_ops = entry.write_iops.load(std::memory_order_relaxed);\n          uint64_t cur_total_iops = cur_r_ops + cur_w_ops;\n          uint64_t cur_gen = entry.generation_id;\n\n          // Check against persistent cache\n          auto& last_state = last_sent_cache[key];\n\n          // If IOPS changed OR Generation changed (Restart)\n          if (cur_total_iops != last_state.first || cur_gen != last_state.second) {\n            auto* proto = report.add_entries();\n            proto->set_app_name(key.app);\n            proto->set_uid(key.uid);\n            proto->set_gid(key.gid);\n            proto->set_generation_id(cur_gen);\n\n            proto->set_total_read_ops(cur_r_ops);\n            proto->set_total_write_ops(cur_w_ops);\n            proto->set_total_bytes_read(entry.bytes_read.load(std::memory_order_relaxed));\n            proto->set_total_bytes_written(\n                entry.bytes_written.load(std::memory_order_relaxed));\n\n            // Queue update (don't commit yet)\n            pending_updates.push_back({key, cur_total_iops, cur_gen});\n          }\n        });\n\n    if (report.entries_size() > 0) {\n      for (const auto& update : pending_updates) {\n        auto& [iops, generation] = last_sent_cache[update.key];\n        iops = update.new_iops;\n        generation = update.new_generation;\n      }\n    }\n\n    // Run roughly every 60 seconds\n    loop_counter++;\n    if (loop_counter >= (60000 / reportInterval.count())) {\n      eos_static_info(\"msg=\\\"FST Traffic Shaping Stats - Garbage Collection loop \"\n                      \"checkpoint\\\" active_streams=%zu\",\n                      active_keys.size());\n      loop_counter = 0;\n\n      // Remove idle streams\n      size_t pruned_count = gOFS.mIoStatsCollector.PruneStaleEntries(90);\n\n      // Cleanup our local cache to prevent unbounded growth\n      if (last_sent_cache.size() > active_keys.size()) {\n        std::unordered_set<eos::fst::traffic_shaping::IoStatsKey,\n                           eos::fst::traffic_shaping::IoStatsKeyHash>\n            active_set(active_keys.begin(), active_keys.end());\n        for (auto it = last_sent_cache.begin(); it != last_sent_cache.end();) {\n          if (active_set.find(it->first) == active_set.end()) {\n            it = last_sent_cache.erase(it);\n          } else {\n            ++it;\n          }\n        }\n      }\n\n      if (pruned_count > 0) {\n        eos_static_info(\n            \"msg=\\\"FST Traffic Shaping Garbage Collection completed\\\" pruned_streams=%zu\",\n            pruned_count);\n      }\n    }\n\n    if (common::SharedHashLocator locator = gConfig.getNodeHashLocator(configQueue);\n        !locator.empty()) {\n      mq::SharedHashWrapper::Batch batch;\n      std::string serialized_report = report.SerializeAsString();\n      std::string encoded_report;\n\n      if (!eos::common::SymKey::Base64(serialized_report, encoded_report)) {\n        eos_static_warning(\"%s\", \"msg=\\\"failed to base64-encode FST IO report\\\"\");\n      } else {\n        batch.SetTransient(eos::common::FST_TRAFFIC_SHAPING_IO_REPORT, encoded_report);\n\n        mq::SharedHashWrapper hash(gOFS.mMessagingRealm.get(), locator, true, false);\n        hash.set(batch);\n      }\n    } else {\n      eos_static_warning(\n          \"msg=\\\"no locator for Traffic Shaping stats publishing - skipping\\\" \"\n          \"config_queue=\\\"%s\\\"\",\n          configQueue.c_str());\n    }\n\n    if (std::chrono::milliseconds sleepTime = stopwatch.timeRemainingInCycle();\n        sleepTime == std::chrono::milliseconds(0)) {\n      eos_static_warning(\n          \"msg=\\\"send Traffic Shaping stats cycle exceeded %d ms - took %d \"\n          \"ms\",\n          reportInterval.count(), stopwatch.timeIntoCycle());\n    } else {\n      assistant.wait_for(sleepTime);\n    }\n  }\n\n  eos_static_info(\"%s\", \"msg=\\\"Traffic Shaping stats publishing thread terminated\\\"\");\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/storage/Remover.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Remover.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/SymKeys.hh\"\n#include \"fst/storage/Storage.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/Deletion.hh\"\n#include \"fst/Config.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Thead requesting deletions from the MGM and unlinking the physical files\n//------------------------------------------------------------------------------\nvoid\nStorage::Remover()\n{\n  using namespace std::chrono;\n  static auto last_request_ts = system_clock::now();\n  static std::chrono::seconds request_interval(300);\n  // Used as barrier for FSTs proper config\n  (void)gConfig.getFstNodeConfigQueue(\"Remover\").c_str();\n  (void)gConfig.WaitManager();\n  uint64_t num_deleted = 0ull;\n  const char* ptr = getenv(\"EOS_FST_DELETE_QUERY_INTERVAL\");\n\n  if (ptr) {\n    try {\n      request_interval = std::chrono::seconds(std::stoi(std::string(ptr)));\n      eos_static_info(\"msg=\\\"update deletions request interval\\\" val=%llu\",\n                      request_interval.count());\n    } catch (...) {}\n  }\n\n  // Check for deletions when starting\n  (void) gOFS.Query2Delete();\n\n  // Thread that unlinks stored files\n  while (true) {\n    num_deleted = 0ull;\n    std::unique_ptr<Deletion> to_del;\n\n    while ((to_del = GetDeletion())) {\n      for (unsigned int i = 0; i < to_del->mFidVect.size(); ++i) {\n        eos_static_debug(\"msg=\\\"delete file\\\" fxid=%08llx fsid=%u\",\n                         to_del->mFidVect[i], to_del->mFsid);\n        ++num_deleted;\n        const std::string hex_fid = eos::common::FileId::Fid2Hex(to_del->mFidVect[i]);\n        XrdOucErrInfo error;\n        XrdOucString capOpaqueString = \"/?mgm.pcmd=drop\";\n        XrdOucString OpaqueString = \"\";\n        OpaqueString += \"&mgm.fsid=\";\n        OpaqueString += (int) to_del->mFsid;\n        OpaqueString += \"&mgm.fid=\";\n        OpaqueString += hex_fid.c_str();\n        XrdOucEnv Opaque(OpaqueString.c_str());\n        capOpaqueString += OpaqueString;\n        // Delete local file\n        std::string deletionreport;\n        std::string deletionreport64;\n\n        if ((gOFS._rem(\"/DELETION\", error, (const XrdSecEntity*) 0, &Opaque,\n                       0, 0, 0, true, &deletionreport) != SFS_OK)) {\n          eos_static_warning(\"msg=\\\"unable to remove local file\\\" fxid=%s \"\n                             \"fsid=%lu\", hex_fid.c_str(), to_del->mFsid);\n        } else {\n          // Encode the deletion report only if deletion is successful\n          eos::common::SymKey::ZBase64(deletionreport, deletionreport64);\n          capOpaqueString += \"&mgm.report=\";\n          capOpaqueString += deletionreport64.c_str();\n        }\n\n        // Update the manager\n        if (gOFS.CallManager(&error, 0, 0, capOpaqueString)) {\n          eos_static_err(\"msg=\\\"unable to drop file\\\" fxid=\\\"%s\\\" fsid=\\\"%u\\\"\",\n                         hex_fid.c_str(), to_del->mFsid);\n        }\n      }\n    }\n\n    auto now_ts = std::chrono::system_clock::now();\n    bool request_del = (std::chrono::duration_cast<std::chrono::seconds>\n                        (now_ts - last_request_ts) > request_interval);\n\n    // Ask for more deletions if deleted something in last round or request\n    // interval expired\n    if (num_deleted || request_del) {\n      eos_static_debug(\"%s\", \"msg=\\\"query manager for deletions\\\"\");\n      last_request_ts = now_ts;\n      (void) gOFS.Query2Delete();\n    } else {\n      std::this_thread::sleep_for(std::chrono::seconds(10));\n    }\n  }\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/storage/Report.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Report.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/storage/Storage.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/Config.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\nvoid\nStorage::Report()\n{\n  // this thread send's report messages from the report queue\n  bool failure;\n  XrdOucString monitorReceiver = gConfig.FstDefaultReceiverQueue;\n  monitorReceiver.replace(\"*/mgm\", \"*/report\");\n\n  while (1) {\n    failure = false;\n    gOFS.ReportQueueMutex.Lock();\n\n    while (gOFS.ReportQueue.size() > 0) {\n      gOFS.ReportQueueMutex.UnLock();\n      gOFS.ReportQueueMutex.Lock();\n      // send all reports away and dump them into the log\n      XrdOucString report = gOFS.ReportQueue.front();\n      gOFS.ReportQueueMutex.UnLock();\n      // this type of messages can have no receiver\n      mq::MessagingRealm::Response response =\n        gOFS.mMessagingRealm->sendMessage(\"report\", report.c_str(),\n                                          monitorReceiver.c_str(), true);\n\n      if (!response.ok()) {\n        // display communication error\n        eos_err(\"%s\", \"msg=\\\"cannot send report broadcast\\\"\");\n        failure = true;\n        gOFS.ReportQueueMutex.Lock();\n        break;\n      }\n\n      gOFS.ReportQueueMutex.Lock();\n      gOFS.ReportQueue.pop();\n    }\n\n    gOFS.ReportQueueMutex.UnLock();\n\n    if (failure) {\n      std::this_thread::sleep_for(std::chrono::seconds(10));\n    } else {\n      std::this_thread::sleep_for(std::chrono::seconds(1));\n    }\n  }\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/storage/Scrub.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Scrub.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/storage/Storage.hh\"\n#include \"fst/storage/FileSystem.hh\"\n#include \"common/utils/RandUtils.hh\"\n#include <fcntl.h>\n\n#ifdef __APPLE__\n#define O_DIRECT 0\n#endif\n\nEOSFSTNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\nvoid\nStorage::Scrub()\n{\n  // create a 1M pattern\n  eos_info(\"%s\", \"msg=\\\"create scrubbing pattern ...\\\"\");\n\n  for (int i = 0; i < 1024 * 1024 / 8; i += 2) {\n    mScrubPattern[0][i] = 0xaaaa5555aaaa5555ULL;\n    mScrubPattern[0][i + 1] = 0x5555aaaa5555aaaaULL;\n    mScrubPattern[1][i] = 0x5555aaaa5555aaaaULL;\n    mScrubPattern[1][i + 1] = 0xaaaa5555aaaa5555ULL;\n  }\n\n  eos_info(\"%s\", \"msg=\\\"start scrubbing\\\"\");\n\n  // this thread reads the oldest files and checks their integrity\n  while (true) {\n    time_t start = time(0);\n    std::set<eos::common::FileSystem::fsid_t> fsids;\n    // Collect all file system ids registered\n    {\n      eos::common::RWMutexReadLock fs_rd_lock(mFsMutex);\n\n      for (const auto& elem : mFsMap) {\n        fsids.insert(elem.first);\n      }\n    }\n    eos_debug(\"msg=\\\"running on %lu file systems\\\"\", fsids.size());\n    std::string path {\"\"};\n    uint64_t free {0ull};\n    uint64_t blocks {0ull};\n    bool direct_io = false;\n    eos::common::BootStatus boot_st;\n    eos::common::ConfigStatus config_st;\n\n    for (auto fsid : fsids) {\n      {\n        eos::common::RWMutexReadLock fs_rd_lock(mFsMutex);\n        auto it = mFsMap.find(fsid);\n\n        if (it == mFsMap.end()) {\n          eos_warning(\"msg=\\\"skip removed file system\\\" fsid=%lu\", fsid);\n          continue;\n        }\n\n        auto fs = it->second;\n        path = fs->GetPath();\n\n        if (fs->GetStatfs() == nullptr) {\n          eos_notice(\"msg=\\\"statfs failed on file system\\\" fsid=%lu path=\\\"%s\\\"\",\n                     fsid, path.c_str());\n          continue;\n        }\n\n        free = fs->GetStatfs()->GetStatfs()->f_bfree;\n        blocks = fs->GetStatfs()->GetStatfs()->f_blocks;\n        // Disable direct IO for ZFS\n        direct_io = (fs->GetStatfs()->GetStatfs()->f_type != 0x2fc12fc1);\n        boot_st = fs->GetStatus();\n        config_st = fs->GetConfigStatus();\n      }\n\n      // Skip scrubbing file systems for which either of the following\n      // conditions hold:\n      // - not a local file system (i.e. remote)\n      // - not in writable mode\n      // - not booted\n      if (path.empty() || (path[0] != '/') ||\n          (config_st < eos::common::ConfigStatus::kWO) ||\n          (boot_st != eos::common::BootStatus::kBooted)) {\n        continue;\n      }\n\n      struct stat buf;\n\n      std::string no_scrub = path + \"/\" + \".eosnoscrub\";\n\n      if (!::stat(no_scrub.c_str(), &buf)) {\n        eos_debug(\"msg=\\\"scrub is disabled, remove %s to activate\\\"\",\n                  no_scrub.c_str());\n        continue;\n      }\n\n      if (ScrubFs(path.c_str(), free, blocks, fsid, direct_io)) {\n        // Filesystem has errors\n        eos::common::RWMutexReadLock fs_rd_lock(mFsMutex);\n        auto it = mFsMap.find(fsid);\n\n        if (it == mFsMap.end()) {\n          eos_warning(\"msg=\\\"skip removed file system\\\" fsid=%lu\", fsid);\n          continue;\n        }\n\n        it->second->BroadcastError(EIO, \"filesystem probe error detected\");\n      }\n    }\n\n    time_t stop = time(0);\n    int nsleep = ((300) - (stop - start));\n\n    if (nsleep > 0) {\n      eos_debug(\"msg=\\\"scrubber will pause for %u seconds\\\"\", nsleep);\n      std::this_thread::sleep_for(std::chrono::seconds(nsleep));\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Scrub filesystem\n//------------------------------------------------------------------------------\nint\nStorage::ScrubFs(const char* path, unsigned long long free,\n                 unsigned long long blocks, unsigned long id, bool direct_io)\n{\n  int MB = 1; // the test files have 1 MB\n  int index = 10 - (int)(10.0 * free / blocks);\n  eos_static_debug(\"Running Scrubber on filesystem path=%s id=%u free=%llu blocks=%llu index=%d\",\n                   path, id, free, blocks, index);\n  int fserrors = 0;\n\n  for (int fs = 1; fs <= index; fs++) {\n    // check if test file exists, if not, write it\n    XrdOucString scrubfile[2];\n    scrubfile[0] = path;\n    scrubfile[1] = path;\n    scrubfile[0] += \"/scrub.write-once.\";\n    scrubfile[0] += fs;\n    scrubfile[1] += \"/scrub.re-write.\";\n    scrubfile[1] += fs;\n    struct stat buf;\n    int dflags = 0;\n\n    if (direct_io) {\n      dflags = O_DIRECT;\n    }\n\n    for (int k = 0; k < 2; k++) {\n      eos_static_debug(\"Scrubbing file %s\", scrubfile[k].c_str());\n\n      if (((k == 0) && stat(scrubfile[k].c_str(), &buf)) || ((k == 0) &&\n          (buf.st_size != (MB * 1024 * 1024))) || ((k == 1))) {\n        // ok, create this file once\n        int ff = 0;\n\n        if (k == 0) {\n          ff = open(scrubfile[k].c_str(), O_CREAT | O_TRUNC | O_WRONLY | dflags,\n                    S_IRWXU);\n        } else {\n          ff = open(scrubfile[k].c_str(), O_CREAT | O_WRONLY | dflags, S_IRWXU);\n        }\n\n        if (ff < 0) {\n\t  if (errno == EMFILE) {\n\t    eos_static_warning(\"Unable to create/wopen scrubfile %s errno=%d\",\n\t\t\t    scrubfile[k].c_str(), errno);\n\t    // this is not fatal, since it might be a temporary problem\n\t    return 0;\n\t  }\n          eos_static_crit(\"Unable to create/wopen scrubfile %s errno=%d\",\n                          scrubfile[k].c_str(), errno);\n          fserrors = 1;\n          break;\n        }\n\n        // select the pattern randomly\n        int rshift = (int)((1.0 * eos::common::getRandom() / RAND_MAX) + 0.5);\n        eos_static_debug(\"rshift is %d\", rshift);\n\n        for (int i = 0; i < MB; i++) {\n          int nwrite = write(ff, mScrubPattern[rshift], 1024 * 1024);\n\n          if (nwrite != (1024 * 1024)) {\n            eos_static_crit(\"Unable to write all needed bytes for scrubfile %s errno=%d\",\n                            scrubfile[k].c_str(), errno);\n            fserrors = 1;\n            break;\n          }\n\n          if (k != 0) {\n            std::this_thread::sleep_for(std::chrono::milliseconds(100));\n          }\n        }\n\n        close(ff);\n      }\n\n      // do a read verify\n      int ff = open(scrubfile[k].c_str(), dflags | O_RDONLY);\n\n      if (ff < 0) {\n\tif (errno == EMFILE) {\n\t  eos_static_warning(\"Unable to create/wopen scrubfile %s errno=%d\",\n\t\t\t     scrubfile[k].c_str(), errno);\n\t  // this is not fatal, since it might be a temporary problem\n\t  return 0;\n\t} else {\n\t  eos_static_crit(\"Unable to open static scrubfile %s, errno=%d\",\n\t\t\t  scrubfile[k].c_str(), errno);\n\t  return 1;\n\t}\n      }\n\n      int eberrors = 0;\n\n      for (int i = 0; i < MB; i++) {\n        int nread = read(ff, mScrubPatternVerify, 1024 * 1024);\n\n        if (nread != (1024 * 1024)) {\n          eos_static_crit(\"Unable to read all needed bytes from scrubfile %s errno=%d\",\n                          scrubfile[k].c_str(), errno);\n          fserrors = 1;\n          break;\n        }\n\n        unsigned long long* ref = (unsigned long long*) mScrubPattern[0];\n        unsigned long long* cmp = (unsigned long long*) mScrubPatternVerify;\n\n        // do a quick check\n        for (int b = 0; b < MB * 1024 / 8; b++) {\n          if ((*ref != *cmp)) {\n            ref = (unsigned long long*) mScrubPattern[1];\n\n            if (*(ref) == *cmp) {\n              // ok - pattern shifted\n            } else {\n              // this is real fatal error\n              eberrors++;\n            }\n          }\n        }\n\n        std::this_thread::sleep_for(std::chrono::milliseconds(100));\n      }\n\n      if (eberrors) {\n        eos_static_alert(\"%d block errors on filesystem %lu scrubfile %s\", eberrors, id,\n                         scrubfile[k].c_str());\n        fserrors++;\n      }\n\n      close(ff);\n    }\n  }\n\n  if (fserrors) {\n    return 1;\n  }\n\n  return 0;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/storage/Storage.cc",
    "content": "//------------------------------------------------------------------------------\n// File: Storage.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/Config.hh\"\n#include \"fst/storage/Storage.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/Verify.hh\"\n#include \"fst/Deletion.hh\"\n#include \"fst/io/FileIoPluginCommon.hh\"\n#include \"fst/XrdFstOss.hh\"\n#include \"common/Fmd.hh\"\n#include \"common/FileId.hh\"\n#include \"common/FileSystem.hh\"\n#include \"common/Constants.hh\"\n#include \"common/Path.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/LinuxStat.hh\"\n#include \"common/ShellCmd.hh\"\n#include \"common/StringUtils.hh\"\n#include \"fst/utils/FTSWalkTree.hh\"\n#include \"MonitorVarPartition.hh\"\n#include \"qclient/structures/QSet.hh\"\n#include <google/dense_hash_map>\n#include <math.h>\n// @note (esindril)use this when Clang (>= 6.0.0) supports it\n//#include <filesystem>\n\nextern eos::fst::XrdFstOss* XrdOfsOss;\n\nnamespace\n{\n//------------------------------------------------------------------------------\n//! Get minimum free space threshold after which a file system is considered\n//! full\n//------------------------------------------------------------------------------\nstatic long long\nGetFullFsThresholdBytes()\n{\n  static std::string s_full_env(\"EOS_FS_FULL_SIZE_IN_GB\");\n  static long long s_full_threshold =\n    (std::stoll(getenv(s_full_env.c_str()) ?\n                getenv(s_full_env.c_str()) : \"5\")\n     * 1024ll * 1024ll * 1024ll);\n  return s_full_threshold;\n}\n\n//------------------------------------------------------------------------------\n//! Check a few files randomly to make sure they have the FMD xattr converted\n//!\n//! @param fst_path file system mount point\n//!\n//! @return true if conversion was done, otherwise false\n//------------------------------------------------------------------------------\nbool RandomCheckFsXattrConverted(const std::string& fs_path)\n{\n  std::set<uint64_t> dir_hash {0, 1, 100, 1000, 2000, 10000, 20000, 50000, 100000};\n  std::set<std::string> existing_dirs;\n  struct stat info;\n  char full_path[16384];\n  // Lambda function to check for existence of FMD xattr\n  auto check_fmd_xattr = [](std::string_view abs_path) -> bool {\n    static const std::string xattr_key = \"user.eos.fmd\";\n    eos::fst::FsIo local_io {abs_path.data()};\n    std::string xattr_val;\n    return (local_io.attrGet(xattr_key, xattr_val) == 0);\n  };\n\n  // Check which hash directories actually exist on this mountpoint\n  for (unsigned long long hash : dir_hash) {\n    sprintf(full_path, \"%s/%08llx/\", fs_path.c_str(), hash);\n\n    if (stat(full_path, &info) == 0) {\n      existing_dirs.insert(full_path);\n    }\n  }\n\n  int checked_files = 0;\n  int correct_files = 0;\n  constexpr int max_index = 100;\n  std::set<int> file_index {1, 10, 50, max_index};\n  char* fts_argv[2];\n  fts_argv[1] = nullptr;\n\n  // Check the above index files in the existing hash directories for the xattr\n  for (const auto& dir : existing_dirs) {\n    fts_argv[0] = (char*)dir.c_str();\n    FTS* tree = fts_open(fts_argv, FTS_NOCHDIR | FTS_NOSTAT, 0);\n\n    if (tree == nullptr) {\n      eos_static_notice(\"msg=\\\"fts_open failed\\\" path=\\\"%s\\\"\", dir.c_str());\n      continue;\n    }\n\n    int count = 0;\n    FTSENT* node;\n\n    while ((node = fts_read(tree))) {\n      if (node->fts_level > 0 && node->fts_name[0] == '.') {\n        fts_set(tree, node, FTS_SKIP);\n      } else {\n        if (node->fts_info == FTS_F) {\n          ++count;\n\n          if (file_index.find(count) != file_index.end()) {\n            // Skip check for block checksum files\n            if (strstr(node->fts_path, eos::fst::XSMAP_SUFFIX.data()) != nullptr) {\n              continue;\n            }\n\n            ++checked_files;\n\n            if (!check_fmd_xattr(node->fts_path)) {\n              eos_static_err(\"msg=\\\"no xattr for file\\\" path=\\\"%s\\\"\",\n                             node->fts_path);\n              return false;\n            } else {\n              ++correct_files;\n            }\n          }\n\n          // Don't list more then max index entries\n          if (count > max_index) {\n            break;\n          }\n        }\n      }\n    }\n\n    if (fts_close(tree)) {\n      eos_static_err(\"msg=\\\"fts_close failed\\\" path=\\\"%s\\\" errno=%d\",\n                     dir.c_str(), errno);\n    }\n  }\n\n  eos_static_notice(\"msg=\\\"%i out of %i checked files with converted xattrs\\\"\",\n                    correct_files, checked_files);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n//! Check that the given file system has been converted to xattr Fmd. This\n//! method only checks for the existence of the \".eosattrconverted\" special file\n//!\n//! @param path file system mount-point\n//!\n//! @return true if conversion was done, otherwise false\n//------------------------------------------------------------------------------\nbool\nCheckFsXattrConverted(std::string fs_path)\n{\n  // Skip xattr conversion check for non-local filesystems (with protocol prefixes)\n  if (fs_path.find(\"://\") != std::string::npos || fs_path[0] != '/') {\n    eos_static_info(\"msg=\\\"skipping xattr conversion check for non-local filesystem\\\" \"\n                    \"path=\\\"%s\\\"\", fs_path.c_str());\n    return true;\n  }\n\n  const std::string xattr_conv_marker = \".eosattrconverted\";\n  const std::string xattr_path = fs_path + \"/\" + xattr_conv_marker;\n  struct stat info;\n\n  if (stat(xattr_path.c_str(), &info) != 0) {\n    // Check if this is a new file system and add by default\n    // the converted file marker\n    DIR* dir = opendir(fs_path.c_str());\n\n    if (dir == nullptr) {\n      eos_static_err(\"msg=\\\"failed to open file system root directory\\\" \"\n                     \"path=\\\"%s\\\"\",  fs_path.c_str());\n      return false;\n    }\n\n    struct dirent* dent {\n      nullptr\n    };\n\n    while ((dent = readdir(dir))) {\n      if ((dent->d_type == DT_DIR) &&\n          ((strncmp(dent->d_name, \".\", strlen(dent->d_name)) != 0) &&\n           (strncmp(dent->d_name, \"..\", strlen(dent->d_name)) != 0))) {\n        // No xattr marker, check some random files for user.eos.fmd xattr\n        if (!RandomCheckFsXattrConverted(fs_path)) {\n          return false;\n        } else {\n          // Exit loop and mark file system as converted\n          break;\n        }\n      }\n    }\n\n    (void) closedir(dir);\n    // This is a new fs, add the converted marker\n    std::ofstream file;\n    file.open(xattr_path, std::ios::out);\n  }\n\n  return true;\n}\n}\n\nEOSFSTNAMESPACE_BEGIN\n\n//-------------------------------------------------------------------------------\n// Determine if check for file system running on the root partition is disabled\n//------------------------------------------------------------------------------\nbool\nStorage::IsRootFsCheckDisabled()\n{\n  if (getenv(\"EOS_FST_DISABLE_ROOT_PARTITION_CHECK\")) {\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Create new Storage object\n//------------------------------------------------------------------------------\nStorage*\nStorage::Create(const char* meta_dir)\n{\n  Storage* storage = new Storage(meta_dir);\n\n  if (storage->IsZombie()) {\n    delete storage;\n    return 0;\n  }\n\n  return storage;\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nStorage::Storage(const char* meta_dir)\n{\n  // Check if automatic drain on SMART errors is requested\n  const char* ptr = getenv(\"EOS_FST_DRAIN_ON_SMART_ERROR\");\n\n  if (ptr && strncmp(ptr, \"1\", 1) == 0) {\n    mDrainOnSmartErr = true;\n  }\n\n  SetLogId(\"FstOfsStorage\", \"<service>\");\n  XrdOucString mkmetalogdir = \"mkdir -p \";\n  mkmetalogdir += meta_dir;\n  mkmetalogdir += \" >& /dev/null\";\n  int rc = system(mkmetalogdir.c_str());\n\n  if (rc) {\n    rc = 0;\n  }\n\n  mkmetalogdir = \"chown -R daemon.daemon \";\n  mkmetalogdir += meta_dir;\n  mkmetalogdir += \" >& /dev/null\";\n  rc = system(mkmetalogdir.c_str());\n\n  if (rc) {\n    rc = 0;\n  }\n\n  mMetaDir = meta_dir;\n\n  // Check if the meta directory is accessible\n  if (access(meta_dir, R_OK | W_OK | X_OK)) {\n    eos_crit(\"cannot access meta data directory %s\", meta_dir);\n    mZombie = true;\n  }\n\n  mZombie = false;\n  pthread_t tid;\n  // We need page aligned addresses for direct IO\n  long pageval = sysconf(_SC_PAGESIZE);\n\n  if (pageval < 0) {\n    eos_crit(\"cannot get page size\");\n    exit(-1);\n  }\n\n  if (posix_memalign((void**) &mScrubPattern[0], pageval, 1024 * 1024) ||\n      posix_memalign((void**) &mScrubPattern[1], pageval, 1024 * 1024) ||\n      posix_memalign((void**) &mScrubPatternVerify, pageval, 1024 * 1024)) {\n    eos_crit(\"cannot allocate memory aligned scrub buffer\");\n    exit(-1);\n  }\n\n  eos_info(\"starting scrubbing thread\");\n\n  if ((rc = XrdSysThread::Run(&tid, Storage::StartFsScrub,\n                              static_cast<void*>(this),\n                              0, \"Scrubber\"))) {\n    eos_crit(\"cannot start scrubber thread\");\n    mZombie = true;\n  }\n\n  mFsConfigThread.reset(&Storage::FsConfigUpdate, this);\n  mFsConfigThread.setName(\"FsConfigUpdate Thread\");\n  mRegisterFsThread.reset(&Storage::UpdateRegisteredFs, this);\n  mRegisterFsThread.setName(\"RegisterFS Thread\");\n  mQdbCommunicatorThread.reset(&Storage::QdbCommunicator, this);\n  mQdbCommunicatorThread.setName(\"QDB Communicator Thread\");\n  XrdSysMutexHelper tsLock(mThreadsMutex);\n  mThreadSet.insert(tid);\n  eos_info(\"starting deletion thread\");\n\n  if ((rc = XrdSysThread::Run(&tid, Storage::StartFsRemover,\n                              static_cast<void*>(this),\n                              0, \"Data Store Remover\"))) {\n    eos_crit(\"cannot start deletion theread\");\n    mZombie = true;\n  }\n\n  mThreadSet.insert(tid);\n  eos_info(\"starting report thread\");\n\n  if ((rc = XrdSysThread::Run(&tid, Storage::StartFsReport,\n                              static_cast<void*>(this),\n                              0, \"Report Thread\"))) {\n    eos_crit(\"cannot start report thread\");\n    mZombie = true;\n  }\n\n  mThreadSet.insert(tid);\n  eos_info(\"starting verification thread\");\n\n  if ((rc = XrdSysThread::Run(&tid, Storage::StartFsVerify,\n                              static_cast<void*>(this),\n                              0, \"Verify Thread\"))) {\n    eos_crit(\"cannot start verify thread\");\n    mZombie = true;\n  }\n\n  mThreadSet.insert(tid);\n  eos_static_info(\"%s\", \"msg=\\\"starting daemon supervisor thread\\\"\");\n\n  if ((rc = XrdSysThread::Run(&tid, Storage::StartDaemonSupervisor,\n                              static_cast<void*>(this),\n                              0, \"Supervisor Thread\"))) {\n    eos_static_crit(\"%s\", \"msg=\\\"cannot start supervisor thread\\\"\");\n    mZombie = true;\n  }\n\n  mThreadSet.insert(tid);\n\n  mErrorReportThread.reset(&Storage::ErrorReport, this);\n  mErrorReportThread.setName(\"Error Report Thread\");\n\n  mPublisherThread.reset(&Storage::Publish, this);\n  mPublisherThread.setName(\"Publisher Thread\");\n\n  eos_info(\"starting mgm synchronization thread\");\n\n  if ((rc = XrdSysThread::Run(&tid, Storage::StartMgmSyncer,\n                              static_cast<void*>(this),\n                              0, \"MgmSyncer Thread\"))) {\n    eos_static_crit(\"%s\", \"msg=\\\"cannot start mgm syncer thread\\\"\");\n    mZombie = true;\n  }\n\n  mThreadSet.insert(tid);\n  // Starting FstPartitionMonitor\n  eos_info(\"%s\", \"msg=\\\"starting /var/ partition monitor thread ...\\\"\");\n\n  if ((rc = XrdSysThread::Run(&tid, Storage::StartVarPartitionMonitor,\n                              static_cast<void*>(this),\n                              0, \"Var Partition Monitor\"))) {\n    eos_crit(\"%s\", \"msg=\\\"annot start /var partition monitor thread\\\"\");\n    mZombie = true;\n  }\n\n  mThreadSet.insert(tid);\n  eos_info(\"%s\", \"msg=\\\"enabling net/io load monitor\\\"\");\n  mFstLoad.Monitor();\n  eos_info(\"%s\", \"msg=\\\"enabling local disk S.M.A.R.T attribute monitor\\\"\");\n  mFstHealth.Monitor();\n}\n\n//------------------------------------------------------------------------------\n// General shutdown including stopping the helper threads and also\n// cleaning up the registered file systems\n//------------------------------------------------------------------------------\nvoid\nStorage::Shutdown()\n{\n  ShutdownThreads();\n  // Collect all the file systems to be deleted and then trigger the actual\n  // deletion outside the mFsMutex to avoid any deadlocks\n  std::set<eos::fst::FileSystem*> set_fs;\n  {\n    while (mFsMutex.TryLockWrite() != 0) {\n      std::this_thread::sleep_for(std::chrono::milliseconds(10));\n    }\n\n    for (auto* ptr_fs : mFsVect) {\n      set_fs.insert(ptr_fs);\n    }\n\n    for (auto& elem : mFsMap) {\n      set_fs.insert(elem.second);\n    }\n\n    mFsVect.clear();\n    mFsMap.clear();\n    mFsMutex.UnLockWrite();\n  }\n\n  for (auto& ptr_fs : set_fs) {\n    eos_static_warning(\"msg=\\\"deleting file system\\\" fsid=%lu\",\n                       ptr_fs->GetLocalId());\n    delete ptr_fs;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Shutdown all helper threads\n//------------------------------------------------------------------------------\nvoid\nStorage::ShutdownThreads()\n{\n  mQdbCommunicatorThread.join();\n  mPublisherThread.join();\n  mErrorReportThread.join();\n  mTrafficShapingThread.join();\n  mFsUpdQueue.emplace(0, \"ACTION\", \"EXIT\");\n  mFsConfigThread.join();\n  XrdSysMutexHelper scope_lock(mThreadsMutex);\n\n  for (auto it = mThreadSet.begin(); it != mThreadSet.end(); it++) {\n    eos_warning(\"op=shutdown thread_id=%llx\", (unsigned long long) *it);\n    XrdSysThread::Cancel(*it);\n  }\n}\n\nvoid\nStorage::StartTrafficShapingThread()\n{\n  if (mTrafficShapingThreadRunning) {\n    return;\n  }\n\n  mTrafficShapingThreadRunning = true;\n  gOFS.mIoStatsCollector.SetEnabled(true);\n  gOFS.mIoDelayConfig.SetEnabled(true);\n\n  mTrafficShapingThread.reset(&Storage::SendTrafficShapingStats, this);\n  mTrafficShapingThread.setName(\"Traffic Shaping Thread\");\n}\n\nvoid\nStorage::StopTrafficShapingThread()\n{\n  if (!mTrafficShapingThreadRunning) {\n    return;\n  }\n\n  mTrafficShapingThread.join();\n\n  mTrafficShapingThreadRunning = false;\n  gOFS.mIoStatsCollector.SetEnabled(false);\n  gOFS.mIoDelayConfig.SetEnabled(false);\n}\n\n//------------------------------------------------------------------------------\n// Push new verification job to the queue if the maximum number of pending\n// verifications is not exceeded.\n//------------------------------------------------------------------------------\nvoid\nStorage::PushVerification(eos::fst::Verify* entry)\n{\n  XrdSysMutexHelper scope_lock(mVerifyMutex);\n\n  if (mVerifications.size() < 1000000) {\n    mVerifications.push(entry);\n    entry->Show();\n  } else {\n    eos_err(\"%s\", \"msg=\\\"verify list has already 1 Mio. entries - discarding \"\n            \"verify message\\\"\");\n  }\n}\n\n//------------------------------------------------------------------------------\n// Start boot thread\n//------------------------------------------------------------------------------\nvoid*\nStorage::StartBoot(void* pp)\n{\n  if (pp) {\n    BootThreadInfo* info = (BootThreadInfo*) pp;\n\n    if (info->filesystem->ShouldBoot(info->mTriggerKey)) {\n      info->storage->Boot(info->filesystem);\n    } else {\n      eos_static_info(\"msg=\\\"skip booting\\\" fsid=%lu trigger=\\\"%s\\\"\",\n                      info->filesystem->GetId(), info->mTriggerKey.c_str());\n    }\n\n    // Remove from the set containing the ids of booting filesystems\n    XrdSysMutexHelper bootLock(info->storage->mBootingMutex);\n    info->storage->mBootingSet.erase(info->filesystem->GetLocalId());\n    XrdSysMutexHelper tsLock(info->storage->mThreadsMutex);\n    info->storage->mThreadSet.erase(XrdSysThread::ID());\n    delete info;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Boot file system\n//------------------------------------------------------------------------------\nvoid\nStorage::Boot(FileSystem* fs)\n{\n  static bool is_root_check_disabled = IsRootFsCheckDisabled();\n\n  if (!fs) {\n    eos_static_warning(\"%s\", \"msg=\\\"skip booting of NULL file system\\\"\");\n    return;\n  }\n\n  fs->SetStatus(eos::common::BootStatus::kBooting);\n  fs->SetError(0, \"\");\n  // Wait to know who is our manager\n  std::string manager = \"\";\n  size_t cnt = 0;\n\n  do {\n    cnt++;\n    {\n      XrdSysMutexHelper lock(gConfig.Mutex);\n      manager = gConfig.Manager.c_str();\n    }\n\n    if (manager != \"\") {\n      break;\n    }\n\n    std::this_thread::sleep_for(std::chrono::seconds(5));\n    eos_static_info(\"msg=\\\"waiting to know manager\\\" fsid=%lu\",\n                    fs->GetLocalId());\n\n    if (cnt > 20) {\n      eos_static_alert(\"%s\", \"msg=\\\"didn't receive manager name, aborting\\\"\");\n      std::this_thread::sleep_for(std::chrono::seconds(5));\n      XrdFstOfs::xrdfstofs_shutdown(1);\n    }\n  } while (true);\n\n  eos_static_info(\"msg=\\\"manager known\\\" manager=\\\"%s\\\"\", manager.c_str());\n  eos::common::FileSystem::fsid_t fsid = fs->GetLocalId();\n  std::string uuid = fs->GetLocalUuid();\n  eos_static_info(\"msg=\\\"booting filesystem\\\" qpath=%s fsid=%lu uuid=%s\",\n                  fs->GetQueuePath().c_str(), fsid, uuid.c_str());\n\n  if (!fsid) {\n    return;\n  }\n\n  // Try to statfs the filesystem\n  std::unique_ptr<eos::common::Statfs> statfs = fs->GetStatfs();\n\n  if (!statfs) {\n    fs->SetStatus(eos::common::BootStatus::kBootFailure);\n    fs->SetError(errno ? errno : EIO, \"cannot statfs filesystem\");\n    return;\n  }\n\n  // Exclude remote disks\n  if (fs->GetPath()[0] == '/') {\n    // Test if we have rw access\n    struct stat buf;\n    const std::string path = fs->GetPath();\n    int stat_rc = ::stat(path.c_str(), &buf);\n\n    if (stat_rc || (buf.st_uid != DAEMONUID) ||\n        ((buf.st_mode & S_IRWXU) != S_IRWXU)) {\n      eos_static_err(\"msg=\\\"potential failed stat\\\" errno=%d path=\\\"%s\\\"\",\n                     errno, path.c_str());\n\n      if (buf.st_uid != DAEMONUID) {\n        errno = ENOTCONN;\n      }\n\n      if ((buf.st_mode & S_IRWXU) != S_IRWXU) {\n        errno = EPERM;\n      }\n\n      fs->SetStatus(eos::common::BootStatus::kBootFailure);\n      fs->SetError(errno ? errno : EIO, \"cannot have <rw> access\");\n      return;\n    }\n\n    // Test if we are on the root partition\n    struct stat root_buf;\n\n    if (::stat(\"/\", &root_buf)) {\n      fs->SetStatus(eos::common::BootStatus::kBootFailure);\n      fs->SetError(errno ? errno : EIO, \"cannot stat root partition\");\n      return;\n    }\n\n    if (!is_root_check_disabled && (root_buf.st_dev == buf.st_dev)) {\n      // This filesystem is on the ROOT partition\n      if (!CheckLabel(fs->GetPath(), fsid, uuid, false, true)) {\n        fs->SetStatus(eos::common::BootStatus::kBootFailure);\n        fs->SetError(EIO, \"filesystem is on the root partition without or \"\n                     \"wrong <uuid> label file .eosfsuuid\");\n        return;\n      }\n    }\n  }\n\n  // Check if there is a label on the disk and that the configuration\n  // shows the same fsid + uuid\n  if (!CheckLabel(fs->GetPath(), fsid, uuid)) {\n    fs->SetStatus(eos::common::BootStatus::kBootFailure);\n    fs->SetError(EFAULT, SSTR(\"filesystem has a different label (fsid=\"\n                              << fsid << \", uuid=\" << uuid << \") than \"\n                              << \"the configuration\").c_str());\n    return;\n  }\n\n  if (!FsLabel(fs->GetPath(), fsid, uuid)) {\n    fs->SetStatus(eos::common::BootStatus::kBootFailure);\n    fs->SetError(EFAULT, \"cannot write the filesystem label (fsid+uuid) - \"\n                 \"please check filesystem state/permissions\");\n    return;\n  }\n\n\n  // Make sure the Fmd info was moved to xattrs\n  if (!CheckFsXattrConverted(fs->GetPath())) {\n    eos_static_crit(\"msg=\\\"files don't have Fmd info in xattr\\\" \"\n                    \"fs_path=\\\"%s\\\"\", fs->GetPath().c_str());\n    eos_static_crit(\"%s\", \"msg=\\\"process will abort now, please convert \"\n                    \"your file systems to drop LeveDB and use xattrs\\\"\");\n    std::abort();\n  } else {\n    eos_static_info(\"msg=\\\"check for Fmd xattr conversion successful\\\" \"\n                    \"fs_path=%s\", fs->GetPath().c_str());\n  }\n\n  {\n    XrdSysMutexHelper scope_lock(gOFS.OpenFidMutex);\n    gOFS.WNoDeleteOnCloseFid[fsid].clear_deleted_key();\n    gOFS.WNoDeleteOnCloseFid[fsid].set_deleted_key(0);\n  }\n\n  bool resyncmgm = (fs->GetLongLong(\"bootcheck\") ==\n                    eos::common::FileSystem::kBootMgm);\n  bool resyncdisk = (fs->GetLongLong(\"bootcheck\") >=\n                     eos::common::FileSystem::kBootDisk);\n  // If we see the bootcheck kBootMgm for the filesystem, we resync with\n  // the mgm. Remove the bootcheck flag.\n  fs->SetLongLong(\"bootcheck\", 0);\n  eos_info(\"msg=\\\"booting\\\" fsid=%u resync_mgm=%d resync_disk=%d\", fsid,\n           resyncmgm, resyncdisk);\n\n  // Sync only local disks\n  if (resyncdisk && (fs->GetPath()[0] == '/')) {\n    eos_info(\"msg=\\\"start disk synchronisation\\\" fsid=%u\", fsid);\n\n    if (!gOFS.mFmdHandler->ResyncAllDisk(fs->GetPath().c_str(), fsid, resyncmgm)) {\n      fs->SetStatus(eos::common::BootStatus::kBootFailure);\n      fs->SetError(EFAULT, \"cannot resync the DB from local disk\");\n      return;\n    }\n\n    eos_info(\"msg=\\\"finished disk synchronisation\\\" fsid=%u\", fsid);\n  } else {\n    eos_info(\"msg=\\\"skipped disk synchronisization\\\" fsid=%u\", fsid);\n  }\n\n  if (resyncmgm) {\n    eos_info(\"msg=\\\"start mgm synchronisation\\\" fsid=%u\", fsid);\n\n    if (!gOFS.mQdbContactDetails.empty()) {\n      // Resync meta data connecting directly to QuarkDB\n      eos_info(\"msg=\\\"synchronizing from QuarkDB backend\\\" fsid=%u\", fsid);\n\n      if (!gOFS.mFmdHandler->ResyncAllFromQdb(gOFS.mQdbContactDetails, fsid)) {\n        fs->SetStatus(eos::common::BootStatus::kBootFailure);\n        fs->SetError(EFAULT, \"cannot resync meta data from QuarkDB\");\n        return;\n      }\n    } else {\n      eos_info(\"msg=\\\"only mgm synchronization via QDB supported but missing \"\n               \"QDB connection details\\\" fsid=%u\", fsid);\n    }\n\n    eos_info(\"msg=\\\"finished mgm synchronization\\\" fsid=%u\", fsid);\n  } else {\n    eos_info(\"msg=\\\"skip mgm resynchronization\\\" fsid=%u\", fsid);\n  }\n\n  fs->SetLongLong(\"stat.bootdonetime\", (unsigned long long) time(NULL));\n  fs->IoPing();\n  fs->SetStatus(eos::common::BootStatus::kBooted);\n  fs->SetError(0, \"\");\n  // Create FS orphans and deletions directories\n  std::string orphans_dir = fs->GetPath();\n  std::string deletions_dir = fs->GetPath();\n\n  if (fs->GetPath()[0] != '/') {\n    orphans_dir = mMetaDir.c_str();\n    orphans_dir += \"/.eosorphans\";\n    orphans_dir += \"-\";\n    orphans_dir += (int) fs->GetLocalId();\n    deletions_dir = mMetaDir.c_str();\n    deletions_dir += \"/.eosdeletions\";\n    deletions_dir += \"-\";\n    deletions_dir += (int) fs->GetLocalId();\n  } else {\n    orphans_dir += \"/.eosorphans\";\n    deletions_dir += \"/.eosdeletions\";\n  }\n\n  const std::list<std::string> lst_dirs = {orphans_dir, deletions_dir};\n\n  for (const auto& dir : lst_dirs) {\n    if (mkdir(dir.c_str(),\n              S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) {\n      if (errno != EEXIST) {\n        fs->SetStatus(eos::common::BootStatus::kBootFailure);\n        fs->SetError(errno ? errno : EIO, \"cannot create orphans/deletions \"\n                     \" directories\");\n        return;\n      }\n    }\n\n    if (chown(dir.c_str(), 2, 2)) {\n      fs->SetStatus(eos::common::BootStatus::kBootFailure);\n      fs->SetError(errno ? errno : EIO, \"cannot change ownership of \"\n                   \" orphans/deletions directories\");\n      return;\n    }\n  }\n\n  // Apply scanner configuration after booting is done\n  const std::list<std::string> scan_keys {\n    eos::common::SCAN_IO_RATE_NAME, eos::common::SCAN_ENTRY_INTERVAL_NAME,\n    eos::common::SCAN_RAIN_ENTRY_INTERVAL_NAME, eos::common::SCAN_DISK_INTERVAL_NAME,\n    eos::common::SCAN_NS_INTERVAL_NAME, eos::common::SCAN_NS_RATE_NAME,\n    eos::common::SCAN_ALTXS_INTERVAL_NAME, eos::common::ALTXS_SYNC,\n    eos::common::ALTXS_SYNC_INTERVAL};\n\n  for (const auto& key : scan_keys) {\n    const std::string sval = fs->GetString(key.c_str());\n\n    if (sval.size() == 0) {\n      continue;\n    }\n\n    try {\n      long long val = std::stoll(sval);\n\n      if (val >= 0) {\n        fs->ConfigScanner(&mFstLoad, key.c_str(), val);\n      }\n    } catch (...) {\n      eos_static_err(\"msg=\\\"failed to convert value\\\" key=\\\"%s\\\" val=\\\"%s\\\"\",\n                     key.c_str(), sval.c_str());\n    }\n  }\n\n  eos_info(\"msg=\\\"finished boot procedure\\\" fsid=%lu\", (unsigned long) fsid);\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Start scurbber thread\n//------------------------------------------------------------------------------\nvoid*\nStorage::StartFsScrub(void* pp)\n{\n  Storage* storage = (Storage*) pp;\n  storage->Scrub();\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Start remover thread\n//------------------------------------------------------------------------------\nvoid*\nStorage::StartFsRemover(void* pp)\n{\n  Storage* storage = (Storage*) pp;\n  storage->Remover();\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Start reporter thread\n//------------------------------------------------------------------------------\nvoid*\nStorage::StartFsReport(void* pp)\n{\n  Storage* storage = (Storage*) pp;\n  storage->Report();\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Start verification thread\n//------------------------------------------------------------------------------\nvoid*\nStorage::StartFsVerify(void* pp)\n{\n  Storage* storage = (Storage*) pp;\n  storage->Verify();\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Start supervisor thread doing automatic restart if needed\n//------------------------------------------------------------------------------\nvoid*\nStorage::StartDaemonSupervisor(void* pp)\n{\n  Storage* storage = (Storage*) pp;\n  storage->Supervisor();\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Start mgm syncer thread\n//------------------------------------------------------------------------------\nvoid*\nStorage::StartMgmSyncer(void* pp)\n{\n  Storage* storage = (Storage*) pp;\n  storage->MgmSyncer();\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Start /var/ monitoring thread\n//------------------------------------------------------------------------------\nvoid* Storage::StartVarPartitionMonitor(void* pp)\n{\n  Storage* storage = (Storage*) pp;\n  MonitorVarPartition<std::vector<FileSystem*>> mon(5., 30, \"/var/\");\n  mon.Monitor(storage->mFsVect, storage->mFsMutex);\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Run boot thread for specified filesystem\n//------------------------------------------------------------------------------\nbool\nStorage::RunBootThread(FileSystem* fs, const std::string& trigger_key)\n{\n  bool retc = false;\n\n  if (fs) {\n    if (fs->GetLocalId() == 0) {\n      eos_warning(\"msg=\\\"defer booting for fsid 0\\\" fs_ptr=%x\", fs);\n      return retc;\n    }\n\n    XrdSysMutexHelper boot_lock(mBootingMutex);\n\n    // Check if this filesystem is currently already booting\n    if (mBootingSet.count(fs->GetLocalId())) {\n      eos_warning(\"msg=\\\"discard boot request: filesytem fsid=%lu is currently booting\",\n                  (unsigned long) fs->GetLocalId());\n      return retc;\n    } else {\n      // Insert into the set of booting filesystems\n      mBootingSet.insert(fs->GetLocalId());\n    }\n\n    BootThreadInfo* info = new BootThreadInfo();\n    info->storage = this;\n    info->filesystem = fs;\n    info->mTriggerKey = trigger_key;\n    pthread_t tid;\n\n    if ((XrdSysThread::Run(&tid, Storage::StartBoot, static_cast<void*>(info),\n                           0, \"Booter\"))) {\n      eos_crit(\"msg=\\\"failed to start boot thread\\\" fsid=%lu\", fs->GetLocalId());\n      mBootingSet.erase(fs->GetLocalId());\n    } else {\n      retc = true;\n      eos_notice(\"msg=\\\"started boot thread\\\" fsid=%lu\", fs->GetLocalId());\n      XrdSysMutexHelper ls_lock(mThreadsMutex);\n      mThreadSet.insert(tid);\n    }\n  }\n\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Add deletion to the list of pending ones\n//------------------------------------------------------------------------------\nvoid\nStorage::AddDeletion(std::unique_ptr<Deletion> del)\n{\n  XrdSysMutexHelper scope_lock(mDeletionsMutex);\n  mListDeletions.push_front(std::move(del));\n}\n\n//----------------------------------------------------------------------------\n// Delete file by moving it to a special directory on the file system root\n// mount location in the .eosdeletions directory\n//----------------------------------------------------------------------------\nvoid\nStorage::DeleteByMove(std::unique_ptr<Deletion> del)\n{\n  using eos::common::FileId;\n  static const std::string del_dir = \".eosdeletions\";\n  const std::string sfxid = FileId::Fid2Hex(del->mFidVect[0]);\n  const std::string local_prefix = gOFS.Storage->GetStoragePath(del->mFsid);\n  const std::string fpath = FileId::FidPrefix2FullPath(sfxid.c_str(),\n                            local_prefix.c_str());\n  eos::common::Path cpath(fpath.c_str());\n  size_t cpath_sz = cpath.GetSubPathSize();\n\n  if (cpath_sz <= 2) {\n    eos_static_err(\"msg=\\\"failed to extract FST mount/fid hex\\\" path=%s\",\n                   fpath.c_str());\n    return;\n  }\n\n  std::ostringstream oss;\n  oss << cpath.GetSubPath(cpath_sz - 2) << \".eosdeletions/\" << sfxid;\n  std::string fdeletion = oss.str();\n  // Store the original path name as an extended attribute in case ...\n  std::unique_ptr<FileIo> io(FileIoPluginHelper::GetIoObject(fpath));\n  io->attrSet(\"user.eos.deletion\", fpath.c_str());\n\n  // Move it into the deletions directory\n  if (!rename(fpath.c_str(), fdeletion.c_str())) {\n    eos_static_warning(\"msg=\\\"deletion quarantined\\\" path=%s del-path=%s\",\n                       fpath.c_str(), fdeletion.c_str());\n  } else {\n    eos_static_err(\"msg=\\\"failed to quarantine deletion\\\" path=%s del-path=%s\",\n                   fpath.c_str(), fdeletion.c_str());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get deletion object removing it from the list\n//------------------------------------------------------------------------------\nstd::unique_ptr<Deletion>\nStorage::GetDeletion()\n{\n  std::unique_ptr<Deletion> del;\n  XrdSysMutexHelper scope_lock(mDeletionsMutex);\n\n  if (mListDeletions.size()) {\n    del.swap(mListDeletions.back());\n    mListDeletions.pop_back();\n  }\n\n  return del;\n}\n\n//------------------------------------------------------------------------------\n// Get number of pending deletions\n//------------------------------------------------------------------------------\nsize_t\nStorage::GetNumDeletions()\n{\n  size_t total = 0;\n  XrdSysMutexHelper scope_lock(mDeletionsMutex);\n\n  for (auto it = mListDeletions.cbegin(); it != mListDeletions.cend(); ++it) {\n    total += (*it)->mFidVect.size();\n  }\n\n  return total;\n}\n\n//------------------------------------------------------------------------------\n// Get the filesystem associated with the given filesystem id\n//------------------------------------------------------------------------------\nFileSystem*\nStorage::GetFileSystemById(eos::common::FileSystem::fsid_t fsid) const\n{\n  auto it = mFsMap.find(fsid);\n\n  if (it != mFsMap.end()) {\n    return it->second;\n  }\n\n  return nullptr;\n}\n\n//------------------------------------------------------------------------------\n// Get configuration associated with the given file system id\n//------------------------------------------------------------------------------\nstd::string\nStorage::GetFileSystemConfig(eos::common::FileSystem::fsid_t fsid,\n                             const std::string& key) const\n{\n  std::string value;\n  eos::common::RWMutexReadLock fs_rd_lock(mFsMutex);\n  FileSystem* fs = GetFileSystemById(fsid);\n\n  if (fs) {\n    value = fs->GetString(key.c_str());\n  }\n\n  return value;\n}\n\n//------------------------------------------------------------------------------\n// Check if file system is in operational state i.e. config status < kDrain\n//------------------------------------------------------------------------------\nbool\nStorage::IsFsOperational(eos::common::FileSystem::fsid_t fsid) const\n{\n  eos::common::RWMutexReadLock fs_rd_lock(mFsMutex);\n  FileSystem* fs = GetFileSystemById(fsid);\n\n  if (!fs) {\n    return false;\n  }\n\n  if (fs->GetConfigStatus() < eos::common::ConfigStatus::kDrain) {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Writes file system label files .eosfsid .eosuuid according to config (if\n// they didn't exist!)\n//------------------------------------------------------------------------------\nbool\nStorage::FsLabel(std::string path, eos::common::FileSystem::fsid_t fsid,\n                 std::string uuid)\n{\n  // exclude remote disks\n  if (path[0] != '/') {\n    return true;\n  }\n\n  XrdOucString fsidfile = path.c_str();\n  fsidfile += \"/.eosfsid\";\n  struct stat buf;\n\n  if (stat(fsidfile.c_str(), &buf)) {\n    int fd = open(fsidfile.c_str(),\n                  O_TRUNC | O_CREAT | O_RDWR, S_IRWXU | S_IRWXG | S_IRWXO);\n\n    if (fd < 0) {\n      return false;\n    } else {\n      char ssfid[32];\n      snprintf(ssfid, 32, \"%u\", fsid);\n\n      if ((write(fd, ssfid, strlen(ssfid))) != (int) strlen(ssfid)) {\n        close(fd);\n        return false;\n      }\n    }\n\n    close(fd);\n  }\n\n  std::string uuidfile = path;\n  uuidfile += \"/.eosfsuuid\";\n\n  if (stat(uuidfile.c_str(), &buf)) {\n    int fd = open(uuidfile.c_str(),\n                  O_TRUNC | O_CREAT | O_RDWR, S_IRWXU | S_IRWXG | S_IRWXO);\n\n    if (fd < 0) {\n      return false;\n    } else {\n      if ((write(fd, uuid.c_str(), strlen(uuid.c_str()) + 1))\n          != (int)(strlen(uuid.c_str()) + 1)) {\n        close(fd);\n        return false;\n      }\n    }\n\n    close(fd);\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Checks that the label on the filesystem matches the one in the config\n//------------------------------------------------------------------------------\nbool\nStorage::CheckLabel(std::string path,\n                    eos::common::FileSystem::fsid_t fsid,\n                    std::string uuid, bool fail_noid, bool fail_nouuid)\n{\n  // exclude remote disks\n  if (path[0] != '/') {\n    return true;\n  }\n\n  XrdOucString fsidfile = path.c_str();\n  fsidfile += \"/.eosfsid\";\n  struct stat buf;\n  std::string ckuuid = uuid;\n  eos::common::FileSystem::fsid_t ckfsid = fsid;\n\n  if (!stat(fsidfile.c_str(), &buf)) {\n    int fd = open(fsidfile.c_str(), O_RDONLY);\n\n    if (fd == -1) {\n      return false;\n    } else {\n      ssize_t len = 32;\n      char ssfid[len];\n      memset(ssfid, 0, sizeof(ssfid));\n      ssize_t nread = read(fd, ssfid, sizeof(ssfid) - 1);\n\n      if (nread == -1) {\n        close(fd);\n        return false;\n      }\n\n      close(fd);\n      ssfid[std::min(nread, len - 1)] = '\\0';\n\n      if (ssfid[strlen(ssfid) - 1] == '\\n') {\n        ssfid[strlen(ssfid) - 1] = '\\0';\n      }\n\n      ckfsid = atoi(ssfid);\n    }\n  } else {\n    if (fail_noid) {\n      return false;\n    }\n  }\n\n  // read FS uuid file\n  std::string uuidfile = path;\n  uuidfile += \"/.eosfsuuid\";\n\n  if (!stat(uuidfile.c_str(), &buf)) {\n    int fd = open(uuidfile.c_str(), O_RDONLY);\n\n    if (fd < 0) {\n      return false;\n    } else {\n      ssize_t sz = 4096;\n      char suuid[sz];\n      (void)memset(suuid, 0, sz);\n      ssize_t nread = read(fd, suuid, sz);\n\n      if (nread == -1) {\n        close(fd);\n        return false;\n      }\n\n      close(fd);\n      suuid[std::min(nread, sz - 1)] = '\\0';\n\n      if (suuid[strlen(suuid) - 1] == '\\n') {\n        suuid[strlen(suuid) - 1] = '\\0';\n      }\n\n      ckuuid = suuid;\n    }\n  } else {\n    if (fail_nouuid) {\n      return false;\n    }\n  }\n\n  if ((fsid != ckfsid) || (ckuuid != uuid)) {\n    return false;\n  }\n\n  return true;\n}\n\n//----------------------------------------------------------------------------\n// Check if the selected FST needs to be registered as \"full\" or \"warning\"\n//----------------------------------------------------------------------------\nvoid\nStorage::CheckFilesystemFullness(eos::common::FileSystem::fsid_t fsid)\n{\n  long long headroom = 0ll;\n  long long freebytes = 0ll;\n  {\n    // Collect headroom and free bytes values for the given file system\n    eos::common::RWMutexReadLock fs_rd_lock(mFsMutex);\n    auto it = mFsMap.find(fsid);\n\n    if (it == mFsMap.end()) {\n      return;\n    }\n\n    FileSystem* fs = it->second;\n    headroom = fs->GetLongLong(\"headroom\");\n    freebytes = fs->GetLongLong(\"stat.statfs.freebytes\");\n\n    // Watch out for stat.statfs.freebytes not yet set\n    if (freebytes == 0 && fs->GetString(\"stat.statfs.freebytes\").length() == 0) {\n      eos_static_info(\"msg=\\\"stat.statfs.freebytes has not yet been \"\n                      \"defined, not setting file system fill status\\\" \"\n                      \"fsid=%lu\", fsid);\n      return;\n    }\n  }\n  XrdSysMutexHelper lock(mFsFullMapMutex);\n\n  if (freebytes < GetFullFsThresholdBytes())  {\n    mFsFullMap[fsid] = true;\n  } else {\n    mFsFullMap[fsid] = false;\n  }\n\n  if ((freebytes < 1024ll * 1024ll * 1024ll) || (freebytes <= headroom)) {\n    mFsFullWarnMap[fsid] = true;\n  } else {\n    mFsFullWarnMap[fsid] = false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get storage path for a particular file system id\n//------------------------------------------------------------------------------\nstd::string\nStorage::GetStoragePath(eos::common::FileSystem::fsid_t fsid) const\n{\n  std::string path;\n  eos::common::RWMutexReadLock rd_lock(mFsMutex);\n  auto it = mFsMap.find(fsid);\n\n  if (it != mFsMap.end()) {\n    path = it->second->GetPath();\n  }\n\n  return path;\n}\n\n//------------------------------------------------------------------------------\n// Cleanup orphans\n//------------------------------------------------------------------------------\nbool\nStorage::CleanupOrphans(eos::common::FileSystem::fsid_t fsid,\n                        std::ostringstream& err_msg)\n{\n  bool success = true;\n  std::map<eos::common::FileSystem::fsid_t, std::string> map;\n  {\n    eos::common::RWMutexReadLock rd_lock(mFsMutex);\n\n    for (const auto& elem : mFsMap) {\n      if (fsid == 0ul) {\n        if (elem.second->GetStatus() != eos::common::BootStatus::kBooted) {\n          eos_static_warning(\"msg=\\\"skip orphans clean up for not-booted file \"\n                             \"system, best-effort\\\" fsid=%lu\", elem.first);\n          continue;\n        }\n\n        map.emplace(elem.first, elem.second->GetPath());\n      } else {\n        if (fsid == elem.first) {\n          if (elem.second->GetStatus() != eos::common::BootStatus::kBooted) {\n            err_msg << \"skip orphans clean up for not-booted file system fsid=\"\n                    << elem.first << std::endl;\n            eos_static_warning(\"msg=\\\"skip orphans clean up for not-booted file \"\n                               \"system\\\" fsid=%lu\", elem.first);\n            success = false;\n            break;\n          }\n\n          map.emplace(elem.first, elem.second->GetPath());\n          break;\n        }\n      }\n    }\n  }\n\n  // Perform the actual cleanup for the selected file systems\n  for (const auto& elem : map) {\n    std::set<uint64_t> fids;\n\n    if (!CleanupOrphansDisk(elem.second, fids)) {\n      err_msg << \"error: failed orphans cleanup on disk fsid=\"\n              << elem.first << std::endl;\n      eos_static_err(\"msg=\\\"failed orphans cleanup on disk\\\" fsid=%lu\",\n                     elem.first);\n      success = false;\n    }\n\n    if (!CleanupOrphansQdb(elem.first, fids)) {\n      err_msg << \"error: failed orphans cleanup in QDB fsid=\"\n              << elem.first << std::endl;\n      eos_static_err(\"msg=\\\"failed orphans cleanup in QDB\\\" fsid=%lu\",\n                     elem.first);\n      success = false;\n    }\n  }\n\n  return success;\n}\n\n//------------------------------------------------------------------------------\n// Cleanup orphans on disk\n//------------------------------------------------------------------------------\nbool\nStorage::CleanupOrphansDisk(const std::string& mount,\n                            std::set<uint64_t>& fids)\n{\n  bool success = true;\n  eos_static_info(\"msg=\\\"doing orphans cleanup on disk\\\" path=\\\"%s\\\"\",\n                  mount.c_str());\n  std::string path_orphans = mount + \"/.eosorphans/\";\n  DIR* dir {nullptr};\n  struct dirent* entry {\n    nullptr\n  };\n  std::string fn_path;\n\n  if (!(dir = opendir(path_orphans.c_str()))) {\n    eos_static_err(\"msg=\\\"failed to open dir\\\" errno=%d path=%s\", errno,\n                   path_orphans.c_str());\n    return success;\n  }\n\n  while ((entry = readdir(dir)) != nullptr) {\n    eos_debug(\"msg=\\\"dir contents\\\" name=%s type=%i\", entry->d_name, entry->d_type);\n\n    // Fallback to stat if readdir does not provide the d_type for the entries\n    if (entry && entry->d_type == DT_UNKNOWN) {\n      struct stat buf;\n      fn_path = path_orphans + entry->d_name;\n\n      if (stat(fn_path.c_str(), &buf)) {\n        entry = nullptr;\n      } else {\n        entry->d_type = S_ISDIR(buf.st_mode) ? DT_DIR : DT_REG;\n      }\n    }\n\n    if (entry && (entry->d_type == DT_REG)) {\n      fn_path = path_orphans + entry->d_name;\n      eos_static_info(\"msg=\\\"delete orphan entry\\\" path=\\\"%s\\\"\",\n                      fn_path.c_str());\n\n      try {\n        fids.insert(std::stoull(entry->d_name, nullptr, 16));\n      } catch (...) {\n        eos_static_info(\"msg=\\\"failed to convert orphan entry\\\" \"\n                        \"path=\\\"%s\\\"\", fn_path.c_str());\n      }\n\n      if (unlink(fn_path.c_str())) {\n        eos_static_err(\"msg=\\\"delete failed\\\" path=\\\"%s\\\"\", fn_path.c_str());\n        success = false;\n      }\n    }\n  }\n\n  closedir(dir);\n  /* @note (esindril) Use this once clang (>= 6.0.0) supports std::filesystem\n  for (auto& entry : std::filesystem::directory_iterator(path_orphans)) {\n    if (std::filesystem::is_regular_file(entry.status())) {\n      eos_static_info(\"msg=\\\"delete orphan entry\\\" path=\\\"%s\\\"\",\n                      entry.path().c_str());\n\n      if (!std::filesystem::remove(entry.path())) {\n        eos_static_info(\"msg=\\\"delete failed\\\" path=\\\"%s\\\"\",\n                        entry.path().c_str());\n        success = false;\n      }\n    }\n  }\n  */\n  return success;\n}\n\n//------------------------------------------------------------------------------\n// Cleanup orphans from QDB\n//------------------------------------------------------------------------------\nbool\nStorage::CleanupOrphansQdb(eos::common::FileSystem::fsid_t fsid,\n                           const std::set<uint64_t>& fids)\n{\n  static const uint32_t s_max_batch_size = 10000;\n  eos_static_info(\"msg=\\\"doing orphans cleanup in QDB\\\" fsid=%lu\", fsid);\n\n  if (fids.empty()) {\n    return true;\n  }\n\n  std::list<std::string> to_delete;\n  qclient::QSet qset(*gOFS.mQcl.get(),\n                     SSTR(\"fsck:\" << eos::common::FSCK_ORPHANS_N));\n\n  for (const auto& fid : fids) {\n    to_delete.push_back(SSTR(fid << \":\" << fsid));\n\n    if (to_delete.size() >= s_max_batch_size) {\n      try {\n        (void) qset.srem(to_delete);\n      } catch (const std::runtime_error& e) {\n        eos_static_err(\"msg=\\\"failed clean orphans in QDB\\\" msg=\\\"%s\\\"\",\n                       e.what());\n        return false;\n      }\n\n      to_delete.clear();\n    }\n  }\n\n  if (!to_delete.empty()) {\n    try {\n      (void) qset.srem(to_delete);\n    } catch (const std::runtime_error& e) {\n      eos_static_err(\"msg=\\\"failed clean orphans in QDB\\\" msg=\\\"%s\\\"\",\n                     e.what());\n      return false;\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Get number of file systems\n//------------------------------------------------------------------------------\nsize_t\nStorage::GetFSCount() const\n{\n  eos::common::RWMutexReadLock rd_lock(mFsMutex);\n  return mFsMap.size();\n}\n\n//------------------------------------------------------------------------------\n// Push collected errors to quarkdb\n//------------------------------------------------------------------------------\nbool\nStorage::PushToQdb(eos::common::FileSystem::fsid_t fsid,\n                   const eos::common::FsckErrsPerFsMap& errs_map)\n{\n#ifndef _NOOFS\n  static const uint32_t s_max_batch_size = 10000;\n\n  if (gOFS.mQcl == nullptr) {\n    eos_notice(\"%s\", \"msg=\\\"no qclient present, push to QDB failed\\\"\");\n    return false;\n  }\n\n  qclient::AsyncHandler ah;\n  qclient::QSet fsck_set(*gOFS.mQcl, \"\");\n\n  for (const auto& elem : errs_map) {\n    std::list<std::string> values; // contains fid:fsid entries\n\n    for (auto& errfsid : elem.second) {\n      for (auto& fid : errfsid.second) {\n        if (values.size() <= s_max_batch_size) {\n          values.push_back(SSTR(fid << \":\" << errfsid.first));\n        } else {\n          fsck_set.setKey(SSTR(\"fsck:\" << elem.first).c_str());\n          fsck_set.sadd_async(values, &ah);\n          values.clear();\n        }\n      }\n    }\n\n    if (!values.empty()) {\n      fsck_set.setKey(SSTR(\"fsck:\" << elem.first).c_str());\n      fsck_set.sadd_async(values, &ah);\n    }\n  }\n\n  if (!ah.Wait()) {\n    eos_err(\"msg=\\\"some qset async requests failed\\\" fsid=%lu\", fsid);\n    return false;\n  }\n\n#endif\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Publish a paricular fsck error to QDB\n//------------------------------------------------------------------------------\nvoid\nStorage::PublishFsckError(eos::common::FileId::fileid_t fid,\n                          eos::common::FileSystem::fsid_t fsid,\n                          eos::common::FsckErr err_type)\n{\n  eos::common::FsckErrsPerFsMap errs_map;\n  errs_map[eos::common::FsckErrToString(err_type)][fsid].insert(fid);\n\n  if (!PushToQdb(fsid, errs_map)) {\n    eos_static_err(\"msg=\\\"failed to push fsck error to QDB\\\" fxid=%08llx \"\n                   \"fsid=%lu err=%s\", fid, fsid,\n                   eos::common::FsckErrToString(err_type).c_str());\n  }\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/storage/Storage.hh",
    "content": "//------------------------------------------------------------------------------\n// File: Storage.hh\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include \"common/AssistedThread.hh\"\n#include \"common/ConcurrentQueue.hh\"\n#include \"common/FileSystem.hh\"\n#include \"common/Fmd.hh\"\n#include \"common/Logging.hh\"\n#include \"common/RWMutex.hh\"\n#include \"fst/Health.hh\"\n#include \"fst/Load.hh\"\n#include \"fst/Namespace.hh\"\n#include \"fst/filemd/FmdHandler.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n\n#include <atomic>\n#include <list>\n#include <map>\n#include <queue>\n#include <vector>\n\nnamespace eos {\nnamespace common {\nclass ExecutorMgr;\n}\n} // namespace eos\n\nnamespace qclient {\nstruct SharedHashUpdate;\n}\n\nEOSFSTNAMESPACE_BEGIN\n\nclass Verify;\nclass Deletion;\nclass FileSystem;\n\n//------------------------------------------------------------------------------\n//! Class Storage\n//------------------------------------------------------------------------------\nclass Storage : public eos::common::LogId {\n  friend class XrdFstOfsFile;\n  friend class XrdFstOfs;\n\npublic:\n  //----------------------------------------------------------------------------\n  //! Create Storage object\n  //!\n  //! @param metadirectory path to meta dir\n  //!\n  //! @return pointer to newly created storage object\n  //----------------------------------------------------------------------------\n  static Storage* Create(const char* metadirectory);\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  Storage(const char* metadirectory);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~Storage() = default;\n\n  //----------------------------------------------------------------------------\n  //! General shutdown including stopping the helper threads and also\n  //! cleaning up the registered file systems\n  //----------------------------------------------------------------------------\n  void Shutdown();\n\n  //----------------------------------------------------------------------------\n  //! Add deletion object to the list of pending ones\n  //!\n  //! @param del deletion object\n  //----------------------------------------------------------------------------\n  void AddDeletion(std::unique_ptr<Deletion> del);\n\n  //----------------------------------------------------------------------------\n  //! Delete file by moving it to a special directory on the file system root\n  //! mount location in the .eosdeletions directory\n  //!\n  //! @param del deletion object\n  //----------------------------------------------------------------------------\n  void DeleteByMove(std::unique_ptr<Deletion> del);\n\n  //----------------------------------------------------------------------------\n  //! Get deletion object removing it from the list\n  //!\n  //! @return get deletion object\n  //----------------------------------------------------------------------------\n  std::unique_ptr<Deletion> GetDeletion();\n\n  //----------------------------------------------------------------------------\n  //! Get number of pending deletions\n  //!\n  //! @return number of pending deletions\n  //----------------------------------------------------------------------------\n  size_t GetNumDeletions();\n\n  //----------------------------------------------------------------------------\n  //! Push new verification job to the queue if the maximum number of pending\n  //! verifications is not exceeded.\n  //!\n  //! @param entry verification information about a file\n  //----------------------------------------------------------------------------\n  void PushVerification(eos::fst::Verify* entry);\n\n  //----------------------------------------------------------------------------\n  //! Check that file system exists i.e properly registered in the internal\n  //! maps with an id and uuid\n  //!\n  //! @param fsid file system id\n  //!\n  //! @return true if it exists, otherwise false\n  //----------------------------------------------------------------------------\n  inline bool ExistsFs(eos::common::FileSystem::fsid_t fsid) const {\n    return (GetFileSystemById(fsid) != nullptr);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if files system is booting\n  //!\n  //! @param fsid file system id\n  //!\n  //! @return true if booting, otherwise false\n  //----------------------------------------------------------------------------\n  inline bool IsFsBooting(eos::common::FileSystem::fsid_t fsid) const {\n    XrdSysMutexHelper lock(mBootingMutex);\n    return (mBootingSet.find(fsid) != mBootingSet.end());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if file system is in operational state i.e. config status < kDrain\n  //!\n  //! @param fsid file system identifier\n  //!\n  //! @return true if operations, otherwise false\n  //----------------------------------------------------------------------------\n  bool IsFsOperational(eos::common::FileSystem::fsid_t fsid) const;\n\n  //----------------------------------------------------------------------------\n  //! Get storage path for a particular file system id\n  //!\n  //! @param fsid file system id\n  //!\n  //! @return stoage path or empty string if unkown file system id\n  //----------------------------------------------------------------------------\n  std::string GetStoragePath(eos::common::FileSystem::fsid_t fsid) const;\n\n  //----------------------------------------------------------------------------\n  //! Get configuration associated with the given file system id\n  //!\n  //! @param fsid file system id\n  //! @param key configuration key\n  //!\n  //! @return associated configuration value or empty string if nothing found\n  //----------------------------------------------------------------------------\n  std::string GetFileSystemConfig(eos::common::FileSystem::fsid_t fsid, const std::string& key) const;\n\n  //----------------------------------------------------------------------------\n  //! Cleanup orphans\n  //!\n  //! @param fsid file system id or 0 if cleanup is to be performed for all\n  //!             file systems\n  //! @param err_msg error message\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool CleanupOrphans(eos::common::FileSystem::fsid_t fsid, std::ostringstream& err_msg);\n\n  //----------------------------------------------------------------------------\n  //! Cleanup orphans on disk\n  //!\n  //! @param mount file system mount path\n  //! @param fids set of fids cleaned up\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool CleanupOrphansDisk(const std::string& mount, std::set<uint64_t>& fids);\n\n  //----------------------------------------------------------------------------\n  //! Cleanup orphans from QDB\n  //!\n  //! @param fsid file system id\n  //! @param fids set of fids to clean up\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool CleanupOrphansQdb(eos::common::FileSystem::fsid_t fsid, const std::set<uint64_t>& fids);\n\n  //----------------------------------------------------------------------------\n  //! Get Total FSes tracked in storage, ie. size of the FsMap\n  //!\n  //! @return total count of FSes\n  //----------------------------------------------------------------------------\n  size_t GetFSCount() const;\n\n  //----------------------------------------------------------------------------\n  //! Publish fsck error to QDB\n  //!\n  //! @param fid file identifier\n  //! @param fsid file system identifier\n  //! @param err_type fsck error type\n  //----------------------------------------------------------------------------\n  void PublishFsckError(eos::common::FileId::fileid_t fid, eos::common::FileSystem::fsid_t fsid,\n                        eos::common::FsckErr err_type);\n\n  //----------------------------------------------------------------------------\n  //! Push collected fsck errors to QDB\n  //!\n  //! @param fsid file system identifier\n  //! @param errs_map map of error types to set of fids which are affected\n  //!\n  //! @return true if push was successful, othewise false\n  //----------------------------------------------------------------------------\n  bool PushToQdb(eos::common::FileSystem::fsid_t fsid, const eos::common::FsckErrsPerFsMap& errs_map);\n\n  //----------------------------------------------------------------------------\n  //! Process file system configuration change\n  //!\n  //! @param fs target file system object\n  //! @param key configuration key\n  //! @param value configuration value\n  //!\n  //! @note This requires the mFsMutex to be write locked\n  //----------------------------------------------------------------------------\n  void ProcessFsConfigChange(fst::FileSystem* fs, const std::string& key, const std::string& value);\n\n  void StartTrafficShapingThread();\n\n  void StopTrafficShapingThread();\n\nprotected:\n  mutable eos::common::RWMutex mFsMutex; ///< Mutex protecting the fs map\n  std::vector<fst::FileSystem*> mFsVect; ///< Vector of filesystems\n  //! Map of filesystem id to filesystem object\n  std::map<eos::common::FileSystem::fsid_t, fst::FileSystem*> mFsMap;\n\nprivate:\n  //! Set of key updates to be tracked at the node level\n  static std::set<std::string> sNodeUpdateKeys;\n  //! Set of key updates to be tracked at the file system level\n  static std::set<std::string> sFsUpdateKeys;\n\n  bool mZombie;                                    ///< State of the node\n  XrdOucString mMetaDir;                           ///< Path to meta directory\n  std::atomic<bool> mComputeStripeChecksum{false}; ///< If to compute stripe checksum synchronously when file is written\n  unsigned long long* mScrubPattern[2];\n  unsigned long long* mScrubPatternVerify;\n  mutable XrdSysMutex mBootingMutex; // Mutex protecting the boot set\n  //! Set containing the filesystems currently booting\n  std::set<eos::common::FileSystem::fsid_t> mBootingSet;\n  eos::fst::Verify* mRunningVerify; ///< Currently running verification job\n  XrdSysMutex mThreadsMutex;        ///< Mutex protecting access to the set of threads\n  std::set<pthread_t> mThreadSet;   ///< Set of running helper threads\n  XrdSysMutex mFsFullMapMutex;      ///< Mutex protecting access to the fs full map\n  //! Map indicating if a filesystem has less than  5 GB free\n  std::map<eos::common::FileSystem::fsid_t, bool> mFsFullMap;\n  //! Map indicating if a filesystem has less than (headroom) space free, which\n  //! disables draining and balancing\n  std::map<eos::common::FileSystem::fsid_t, bool> mFsFullWarnMap;\n  XrdSysMutex mVerifyMutex; ///< Mutex protecting access to the verifications\n  //! Queue of verification jobs pending\n  std::queue<eos::fst::Verify*> mVerifications;\n  XrdSysMutex mDeletionsMutex;                         ///< Mutex protecting the list of deletions\n  std::list<std::unique_ptr<Deletion>> mListDeletions; ///< List of deletions\n  Load mFstLoad;                                       ///< Net/IO load monitor\n  Health mFstHealth;                                   ///< Local disk S.M.A.R.T monitor\n  AssistedThread mQdbCommunicatorThread;\n  std::set<std::string> mLastRoundFilesystems;\n  AssistedThread mPublisherThread;   ///< Thread publishing FST/FS info\n  AssistedThread mErrorReportThread; ///< Thread sending error reports\n  AssistedThread mRegisterFsThread;  ///< Thread updating list of FS registered\n  AssistedThread mTrafficShapingThread; ///< Thread sending traffic shaping stats\n  std::atomic<bool> mTrafficShapingThreadRunning{false};\n  //! CV and mutex used for notifying the register thread\n  std::condition_variable mCvRegisterFs;\n  std::mutex mMutexRegisterFs;\n  bool mTriggerRegisterFs{false};\n  AssistedThread mFsConfigThread; ///< Thread applying FS config updates\n  //! Trigger automatic drain if S.M.A.R.T. errors detected\n  bool mDrainOnSmartErr{false};\n\n  //----------------------------------------------------------------------------\n  //! Struct modelling a file system configuration update\n  //----------------------------------------------------------------------------\n  struct FsCfgUpdate {\n    eos::common::FileSystem::fsid_t fsid;\n    std::string key;\n    std::string value;\n    //----------------------------------------------------------------------------\n    //! Default constructor\n    //----------------------------------------------------------------------------\n    FsCfgUpdate() : fsid(0ul), key(\"\"), value(\"\") {}\n\n    //----------------------------------------------------------------------------\n    //! Constructor with parameters\n    //----------------------------------------------------------------------------\n    FsCfgUpdate(eos::common::FileSystem::fsid_t id, const std::string& k, const std::string& v)\n        : fsid(id)\n        , key(k)\n        , value(v) {}\n  };\n\n  ///< Queue of file system config updates\n  eos::common::ConcurrentQueue<FsCfgUpdate> mFsUpdQueue;\n\n  enum class FsRegisterStatus { kNoAction, kPartial, kRegistered };\n\n  //! Struct BootThreadInfo\n  struct BootThreadInfo {\n    Storage* storage;\n    fst::FileSystem* filesystem;\n    std::string mTriggerKey;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Helper methods used for starting worker threads\n  //----------------------------------------------------------------------------\n  static void* StartVarPartitionMonitor(void* pp);\n  static void* StartDaemonSupervisor(void* pp);\n  static void* StartFsScrub(void* pp);\n  static void* StartFsRemover(void* pp);\n  static void* StartFsReport(void* pp);\n  static void* StartFsVerify(void* pp);\n  static void* StartMgmSyncer(void* pp);\n  static void* StartBoot(void* pp);\n\n  //----------------------------------------------------------------------------\n  //! Get statistics about given file system used for publishing\n  //!\n  //! @param fs file system object\n  //!\n  //! @return map of statistics to be published\n  //----------------------------------------------------------------------------\n  std::map<std::string, std::string> GetFsStatistics(fst::FileSystem* fs);\n\n  //----------------------------------------------------------------------------\n  //! Get statistics about this FST node used for publishing\n  //----------------------------------------------------------------------------\n  std::map<std::string, std::string> GetFstStatistics(const std::string& tmpfile, unsigned long long netspeed);\n\n  //----------------------------------------------------------------------------\n  //! Publish statistics about the given file system\n  //!\n  //! @param fsid file system identifier\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool PublishFsStatistics(eos::common::FileSystem::fsid_t fsid);\n\n  //----------------------------------------------------------------------------\n  //! Register file system for which we know we have file fsid info available\n  //!\n  //! @param queuepath file system queuepath identifier\n  //----------------------------------------------------------------------------\n  FsRegisterStatus RegisterFileSystem(const std::string& queuepath);\n\n  //----------------------------------------------------------------------------\n  //! Unregister file system given a queue path\n  //!\n  //! @param queuepath file system queuepath identifier\n  //----------------------------------------------------------------------------\n  void UnregisterFileSystem(const std::string& queuepath);\n\n  //----------------------------------------------------------------------------\n  //! Worker threads implementation\n  //----------------------------------------------------------------------------\n  void Supervisor();\n\n  //----------------------------------------------------------------------------\n  //! Communicator used for processing updates coming through QDB\n  //----------------------------------------------------------------------------\n  void QdbCommunicator(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Update file system list given the QDB shared hash configuration i.e. scan\n  //! QDB for file systems belonging to the current node and update the\n  //! internal list.\n  //----------------------------------------------------------------------------\n  void UpdateRegisteredFs(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Handle FS configuration updates in a separate thread to avoid deadlocks\n  //! in the QClient callback mechanism.\n  //----------------------------------------------------------------------------\n  void FsConfigUpdate(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Method sending error reports\n  //----------------------------------------------------------------------------\n  void ErrorReport(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Get configuration value from global FST config\n  //!\n  //! @param key configuration key\n  //! @param value output configuration value as string\n  //!\n  //! @return true if config key found, otherwise false\n  //----------------------------------------------------------------------------\n  bool GetFstConfigValue(const std::string& key, std::string& value) const;\n\n  //----------------------------------------------------------------------------\n  //! Get configuration value from global FST config\n  //!\n  //! @param key configuration key\n  //! @param value output configuration value as ull\n  //!\n  //! @return true if config key found, otherwise false\n  //----------------------------------------------------------------------------\n  bool GetFstConfigValue(const std::string& key, unsigned long long& value) const;\n\n  //----------------------------------------------------------------------------\n  //! Process FST node configuration change\n  //!\n  //! @param key configuration key\n  //! @param value configuration value\n  //----------------------------------------------------------------------------\n  void ProcessFstConfigChange(const std::string& key, const std::string& value);\n\n  //----------------------------------------------------------------------------\n  //! Process file system configuration change\n  //!\n  //! @param queue file system queue\n  //! @param key configuration key\n  //----------------------------------------------------------------------------\n  void ProcessFsConfigChange(const std::string& queue, const std::string& key);\n\n  void Scrub();\n  void Remover();\n  void Report();\n  void Verify();\n  void Publish(ThreadAssistant& assistant) noexcept;\n  void SendTrafficShapingStats(ThreadAssistant& assistant) noexcept;\n  void MgmSyncer();\n  void Boot(fst::FileSystem* fs);\n\n  //----------------------------------------------------------------------------\n  //! Scrub filesystem\n  //----------------------------------------------------------------------------\n  int ScrubFs(const char* path, unsigned long long free, unsigned long long lbocks, unsigned long id, bool direct_io);\n\n  //----------------------------------------------------------------------------\n  //! Check if node is in zombie state i.e. true if any of the helper threads\n  //! was not properly started.\n  //----------------------------------------------------------------------------\n  inline bool IsZombie() {\n    return mZombie;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Run boot thread for specified filesystem\n  //!\n  //! @param fs filesystem object\n  //! @param trigger_key update key which is triggering this boot request\n  //!\n  //! @return true if boot thread started successfully, otherwise false\n  //----------------------------------------------------------------------------\n  bool RunBootThread(fst::FileSystem* fs, const std::string& trigger_key);\n\n  //----------------------------------------------------------------------------\n  //! Write file system label files (.eosid and .eosuuid) according to the\n  //! configuration if they don't exist already.\n  //!\n  //! @param path mount point of the file system\n  //! @param fsid file system id\n  //! @param uuid file system uuid\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool FsLabel(std::string path, eos::common::FileSystem::fsid_t fsid, std::string uuid);\n\n  //----------------------------------------------------------------------------\n  //! Check that the label on the file system matches the one in the\n  //! configuration.\n  //!\n  //! @param path mount point of the file system\n  //! @param fsid file system id\n  //! @param uuid file system uuid\n  //! @param fail_noid when true fail if there is no .eosfsid file present\n  //! @param fail_nouuid when true fail if there is no .eosfsuuid file present\n  //!\n  //! @return true if labels match, otherwise false\n  //----------------------------------------------------------------------------\n  bool CheckLabel(std::string path, eos::common::FileSystem::fsid_t fsid, std::string uuid, bool fail_noid = false,\n                  bool fail_nouuid = false);\n\n  //----------------------------------------------------------------------------\n  //! Check if the selected file system needs to be registered as \"full\" or\n  //! \"warning\".\n  //!\n  //! @param fsid file system identifier\n  //----------------------------------------------------------------------------\n  void CheckFilesystemFullness(eos::common::FileSystem::fsid_t fsid);\n\n  //----------------------------------------------------------------------------\n  //! Get the filesystem associated with the given filesystem id\n  //! or NULL if none could be found.\n  //! @note  Needs to be called with at least a read lock on the mFsMutex.\n  //!\n  //! @param fsid filesystem id\n  //!\n  //! @return associated filesystem object or NULL\n  //----------------------------------------------------------------------------\n  fst::FileSystem* GetFileSystemById(eos::common::FileSystem::fsid_t fsid) const;\n\n  //----------------------------------------------------------------------------\n  //! Shutdown all helper threads\n  //----------------------------------------------------------------------------\n  void ShutdownThreads();\n\n  //----------------------------------------------------------------------------\n  //! FST node update callback - this is triggered whenever the underlying\n  //! qclient::SharedHash corresponding to the node is modified.\n  //!\n  //! @param upd SharedHashUpdate object\n  //----------------------------------------------------------------------------\n  void NodeUpdateCb(qclient::SharedHashUpdate&& upd);\n\n  //----------------------------------------------------------------------------\n  //! Signal the thread responsible with registered file systems\n  //----------------------------------------------------------------------------\n  void SignalRegisterThread();\n\n  //----------------------------------------------------------------------------\n  //! Determine if check for file system running on the root partition\n  //! is disabled\n  //!\n  //! @return true if check disabled, otherwise false\n  //----------------------------------------------------------------------------\n  static bool IsRootFsCheckDisabled();\n\n  //----------------------------------------------------------------------------\n  /// Parse the scaler for the IoAggregateMap and regule the bandwidth\n  ///\n  /// @param cmd The new scaler data\n  //----------------------------------------------------------------------------\n  void ScalerCmd(const std::string&);\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/storage/Supervisor.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Supervisor.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/storage/Storage.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/storage/FileSystem.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Supervisor thread\n//------------------------------------------------------------------------------\nvoid\nStorage::Supervisor()\n{\n  // this thread does an automatic self-restart if this storage node has\n  // filesystems configured but they don't boot - this can happen by a\n  //timing issue during the autoboot phase\n  eos_static_info(\"Supervisor activated ...\");\n\n  while (true) {\n    size_t ndown = 0;\n    size_t nfs = 0;\n    {\n      eos::common::RWMutexReadLock fs_rd_lock(mFsMutex);\n\n      for (const auto& elem : mFsMap) {\n        auto fs = elem.second;\n\n        if (!fs) {\n          eos_warning(\"msg=\\\"skip file system id without object in map\\\" \"\n                      \"fsid=%lu\", elem.first);\n          continue;\n        }\n\n        eos::common::BootStatus bootstatus = fs->GetStatus();\n        eos::common::ConfigStatus configstatus = fs->GetConfigStatus();\n\n        if ((bootstatus == eos::common::BootStatus::kDown) &&\n            (configstatus > eos::common::ConfigStatus::kDrain)) {\n          ++ndown;\n        }\n      }\n    }\n\n    if (ndown) {\n      // We give one more minute to get things going\n      std::this_thread::sleep_for(std::chrono::seconds(60));\n      ndown = 0;\n      {\n        eos::common::RWMutexReadLock fs_rd_lock(mFsMutex);\n        nfs = mFsMap.size();\n\n        for (const auto& elem : mFsMap) {\n          auto fs = elem.second;\n\n          if (!fs) {\n            eos_warning(\"msg=\\\"skip file system id without object in map\\\" \"\n                        \"fsid=%lu\", elem.first);\n            continue;\n          }\n\n          eos::common::BootStatus bootstatus = fs->GetStatus();\n          eos::common::ConfigStatus configstatus = fs->GetConfigStatus();\n\n          if ((bootstatus == eos::common::BootStatus::kDown) &&\n              (configstatus > eos::common::ConfigStatus::kDrain)) {\n            ++ndown;\n          }\n        }\n      }\n\n      if (ndown == nfs) {\n        // shutdown this daemon\n        eos_static_alert(\"found %d/%d filesystems in <down> status - committing suicide !\",\n                         ndown, nfs);\n        std::this_thread::sleep_for(std::chrono::seconds(10));\n        kill(getpid(), SIGQUIT);\n      }\n    }\n\n    std::this_thread::sleep_for(std::chrono::seconds(60));\n  }\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/storage/TrafficShaping.cc",
    "content": "#include \"fst/storage/TrafficShaping.hh\"\n#include <mutex>\n\nnamespace eos::fst::traffic_shaping {\nIoStatsEntry::IoStatsEntry()\n{\n  const auto now = std::chrono::system_clock::now().time_since_epoch();\n  generation_id = std::chrono::duration_cast<std::chrono::milliseconds>(now).count();\n\n  // Set initial activity timestamp\n  last_activity_s = std::chrono::duration_cast<std::chrono::seconds>(\n                        std::chrono::steady_clock::now().time_since_epoch())\n                        .count();\n}\n\nstd::shared_ptr<IoStatsEntry>\nIoStatsCollector::GetEntry(const std::string& app, uint32_t uid, uint32_t gid)\n{\n  const IoStatsKey key{app, uid, gid};\n  {\n    std::shared_lock lock(mutex_);\n    if (const auto it = stats_map_.find(key); it != stats_map_.end()) {\n      return it->second;\n    }\n  }\n  //\n  {\n    std::unique_lock lock(mutex_);\n    // Double-check in case another thread created it while we waited for lock\n    if (const auto it = stats_map_.find(key); it != stats_map_.end()) {\n      return it->second;\n    }\n\n    auto entry = std::make_shared<IoStatsEntry>();\n    stats_map_[key] = entry;\n    return entry;\n  }\n}\n\nvoid\nIoStatsCollector::RecordRead(const std::string& app, const uint32_t uid,\n                             const uint32_t gid, const size_t bytes)\n{\n  if (!mIsEnabled.load(std::memory_order_relaxed)) {\n    return;\n  }\n\n  const auto entry = GetEntry(app, uid, gid);\n\n  // Atomic updates - thread safe and fast\n  entry->bytes_read.fetch_add(bytes, std::memory_order_relaxed);\n  entry->read_iops.fetch_add(1, std::memory_order_relaxed);\n\n  // Update timestamp for cleanup\n  const auto now = std::chrono::steady_clock::now().time_since_epoch();\n  entry->last_activity_s.store(\n      std::chrono::duration_cast<std::chrono::seconds>(now).count(),\n      std::memory_order_relaxed);\n}\n\nvoid\nIoStatsCollector::RecordWrite(const std::string& app, const uint32_t uid,\n                              const uint32_t gid, const size_t bytes)\n{\n  if (!mIsEnabled.load(std::memory_order_relaxed)) {\n    return;\n  }\n\n  const auto entry = GetEntry(app, uid, gid);\n\n  entry->bytes_written.fetch_add(bytes, std::memory_order_relaxed);\n  entry->write_iops.fetch_add(1, std::memory_order_relaxed);\n\n  const auto now = std::chrono::steady_clock::now().time_since_epoch();\n  entry->last_activity_s.store(\n      std::chrono::duration_cast<std::chrono::seconds>(now).count(),\n      std::memory_order_relaxed);\n}\n\nsize_t\nIoStatsCollector::PruneStaleEntries(const int64_t max_idle_seconds)\n{\n  std::unique_lock lock(mutex_); // Exclusive lock required to erase\n\n  const auto now = std::chrono::steady_clock::now().time_since_epoch();\n  const int64_t now_s = std::chrono::duration_cast<std::chrono::seconds>(now).count();\n\n  size_t removed = 0;\n  for (auto it = stats_map_.begin(); it != stats_map_.end();) {\n    if (const int64_t idle_time = now_s - it->second->last_activity_s.load();\n        idle_time > max_idle_seconds) {\n      // Delete entry. The shared_ptr ensures that if a thread\n      // is currently holding this entry in Record(), it won't crash.\n      it = stats_map_.erase(it);\n      removed++;\n    } else {\n      ++it;\n    }\n  }\n  return removed;\n}\n} // namespace eos::fst::traffic_shaping\n"
  },
  {
    "path": "fst/storage/TrafficShaping.hh",
    "content": "#pragma once\n\n#include \"common/Logging.hh\"\n#include \"common/shaping/IoStatsKey.hh\"\n\n#include \"proto/TrafficShaping.pb.h\"\n\n#include <atomic>\n#include <memory>\n#include <mutex>\n#include <shared_mutex>\n#include <string>\n#include <unordered_map>\n\nnamespace eos::fst::traffic_shaping {\n\nusing IoStatsKey = eos::common::traffic_shaping::IoStatsKey;\nusing IoStatsKeyHash = eos::common::traffic_shaping::IoStatsKeyHash;\n\n// \"alignas(64)\" prevents False Sharing (cache line bouncing) between threads.\nstruct alignas(64) IoStatsEntry {\n  std::atomic<uint64_t> bytes_read{0};\n  std::atomic<uint64_t> bytes_written{0};\n  std::atomic<uint64_t> read_iops{0};\n  std::atomic<uint64_t> write_iops{0};\n\n  // Lifecycle management\n  uint64_t generation_id;                 // Random ID assigned on creation\n  std::atomic<int64_t> last_activity_s{}; // Timestamp for cleanup\n\n  IoStatsEntry(); // Constructor generates the random ID\n};\n\nclass IoStatsCollector {\npublic:\n  IoStatsCollector() = default;\n\n  void RecordRead(const std::string& app, uint32_t uid, uint32_t gid, size_t bytes);\n\n  void RecordWrite(const std::string& app, uint32_t uid, uint32_t gid, size_t bytes);\n\n  inline static std::atomic<uint32_t> fst_io_stats_reporting_thread_period_milliseconds{\n      1000};\n\n  // Returns number of entries removed\n  size_t PruneStaleEntries(int64_t max_idle_seconds = 3600);\n\n  // Data Export (for the Protobuf later)\n  // We pass a lambda/function to visit all entries without copying the whole map\n  template <typename Visitor>\n  void\n  VisitEntries(Visitor visitor)\n  {\n    std::shared_lock lock(mutex_); // Read Lock\n    for (const auto& [key, entry] : stats_map_) {\n      visitor(key, *entry);\n    }\n  }\n\n  void\n  Clear()\n  {\n    std::unique_lock lock(mutex_);\n    stats_map_.clear();\n  }\n\n  void\n  SetEnabled(const bool enabled)\n  {\n    mIsEnabled.store(enabled, std::memory_order_relaxed);\n    if (!enabled) {\n      Clear();\n    }\n  }\n\n  bool\n  IsEnabled() const\n  {\n    return mIsEnabled.load(std::memory_order_relaxed);\n  }\n\nprivate:\n  std::shared_ptr<IoStatsEntry> GetEntry(const std::string& app, uint32_t uid,\n                                         uint32_t gid);\n\n  mutable std::shared_mutex mutex_;\n  std::unordered_map<IoStatsKey, std::shared_ptr<IoStatsEntry>, IoStatsKeyHash>\n      stats_map_;\n\n  std::atomic<bool> mIsEnabled{false};\n};\n\nclass IoDelayConfig {\npublic:\n  IoDelayConfig()\n  {\n    const auto initial_config =\n        std::make_shared<const eos::traffic_shaping::TrafficShapingFstIoDelayConfig>();\n    std::atomic_store(&mFstIoDelayConfigPtr, initial_config);\n  }\n\n  void\n  UpdateConfig(eos::traffic_shaping::TrafficShapingFstIoDelayConfig new_config)\n  {\n    const auto new_ptr =\n        std::make_shared<const eos::traffic_shaping::TrafficShapingFstIoDelayConfig>(\n            std::move(new_config));\n    std::atomic_store_explicit(&mFstIoDelayConfigPtr, new_ptr, std::memory_order_release);\n  }\n\n  uint64_t\n  GetReadDelayForAppUidGid(const eos::common::VirtualIdentity& vid) const\n  {\n    return GetDelayForAppUidGid(vid, /*is_write=*/false);\n  }\n\n  uint64_t\n  GetWriteDelayForAppUidGid(const eos::common::VirtualIdentity& vid) const\n  {\n    return GetDelayForAppUidGid(vid, /*is_write=*/true);\n  }\n\n  void\n  Clear()\n  {\n    UpdateConfig({});\n  }\n\n  void\n  SetEnabled(const bool enabled)\n  {\n    mIsEnabled.store(enabled, std::memory_order_relaxed);\n    if (!enabled) {\n      Clear();\n    }\n  }\n\n  bool\n  IsEnabled() const\n  {\n    return mIsEnabled.load(std::memory_order_relaxed);\n  }\n\nprivate:\n  uint64_t\n  GetDelayForAppUidGid(const eos::common::VirtualIdentity& vid, bool is_write) const\n  {\n    if (!IsEnabled()) {\n      return 0;\n    }\n\n    const std::shared_ptr<const eos::traffic_shaping::TrafficShapingFstIoDelayConfig>\n        cfg = std::atomic_load_explicit(&mFstIoDelayConfigPtr, std::memory_order_acquire);\n\n    uint64_t max_delay = 0;\n\n    auto check = [&](const auto& map, const auto& key) {\n      if (const auto it = map.find(key); it != map.end()) {\n        max_delay = std::max(max_delay, it->second);\n      }\n    };\n\n    if (is_write) {\n      check(cfg->app_write_delay(), vid.app);\n      check(cfg->uid_write_delay(), vid.uid);\n      check(cfg->gid_write_delay(), vid.gid);\n    } else {\n      check(cfg->app_read_delay(), vid.app);\n      check(cfg->uid_read_delay(), vid.uid);\n      check(cfg->gid_read_delay(), vid.gid);\n    }\n\n    return max_delay;\n  }\n\n  std::shared_ptr<const eos::traffic_shaping::TrafficShapingFstIoDelayConfig>\n      mFstIoDelayConfigPtr;\n\n  std::atomic<bool> mIsEnabled{false};\n};\n} // namespace eos::fst::traffic_shaping\n"
  },
  {
    "path": "fst/storage/Verify.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Verify.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#define __STDC_FORMAT_MACROS\n#include <inttypes.h>\n\n#include \"fst/storage/Storage.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/XrdFstOss.hh\"\n#include \"fst/Verify.hh\"\n#include \"fst/checksum/ChecksumPlugins.hh\"\n#include \"fst/checksum/ChecksumGroup.hh\"\n#include \"fst/io/FileIoPluginCommon.hh\"\n#include \"common/Path.hh\"\n\nextern eos::fst::XrdFstOss* XrdOfsOss;\n\nEOSFSTNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\nvoid\nStorage::Verify()\n{\n  using eos::common::FileId;\n  std::map<uint64_t, time_t> open_w_out;\n\n  // Thread that verifies stored files\n  while (1) {\n    mVerifyMutex.Lock();\n\n    if (!mVerifications.size()) {\n      mVerifyMutex.UnLock();\n      sleep(1);\n      continue;\n    }\n\n    eos::fst::Verify* verifyfile = mVerifications.front();\n\n    if (verifyfile) {\n      eos_static_debug(\"got %llu\\n\", (unsigned long long) verifyfile);\n      mVerifications.pop();\n      mRunningVerify = verifyfile;\n\n      if (gOFS.openedForWriting.isOpen(verifyfile->fsId, verifyfile->fId)) {\n        time_t now = time(NULL);\n\n        if (open_w_out[verifyfile->fId] < now) {\n          eos_static_warning(\"file is currently opened for writing id=%x on \"\n                             \"fs=%u - skipping verification\", verifyfile->fId,\n                             verifyfile->fsId);\n          // Spit this message out only once pre minute\n          open_w_out[verifyfile->fId] = now + 60;\n        }\n\n        mVerifications.push(verifyfile);\n        mVerifyMutex.UnLock();\n        continue;\n      }\n    } else {\n      eos_static_debug(\"got nothing\");\n      mVerifyMutex.UnLock();\n      mRunningVerify = 0;\n      continue;\n    }\n\n    mVerifyMutex.UnLock();\n    eos_static_debug(\"msg=\\\"verifying file\\\" fxid=%08llx fsid=%u\", verifyfile->fId,\n                     verifyfile->fsId);\n    // verify the file\n    const std::string hex_fid = FileId::Fid2Hex(verifyfile->fId);\n    const std::string local_prefix = gOFS.Storage->GetStoragePath(verifyfile->fsId);\n    const std::string fstPath = FileId::FidPrefix2FullPath(hex_fid.c_str(),\n                                local_prefix.c_str());\n    {\n      auto fMd = gOFS.mFmdHandler->LocalGetFmd(verifyfile->fId,\n                 verifyfile->fsId, true);\n\n      if (fMd) {\n        // force a resync of meta data from the MGM\n        // e.g. store in the WrittenFilesQueue to have it done asynchronous\n        std::unique_lock<std::mutex> lock(gOFS.WrittenFilesQueueMutex);\n        gOFS.WrittenFilesQueue.push(*fMd.get());\n      }\n    }\n    FileIo* io = eos::fst::FileIoPluginHelper::GetIoObject(fstPath.c_str());\n    // get current size on disk\n    struct stat statinfo;\n    int open_rc = -1;\n\n    if (!io || (open_rc = io->fileOpen(0, 0)) || io->fileStat(&statinfo)) {\n      eos_static_err(\"unable to verify file id=%x on fs=%u path=%s - stat on \"\n                     \"local disk failed\", verifyfile->fId, verifyfile->fsId,\n                     fstPath.c_str());\n      // If there is no file, we should not commit anything to the MGM\n      verifyfile->commitSize = 0;\n      verifyfile->commitChecksum = 0;\n      statinfo.st_size = 0; // indicates the missing file - not perfect though\n    }\n\n    // even if the stat failed, we run this code to tag the file as is ...\n    // attach meta data\n    bool localUpdate = false;\n    auto fMd = gOFS.mFmdHandler->LocalGetFmd(verifyfile->fId, verifyfile->fsId,\n               true, verifyfile->commitFmd);\n\n    if (!fMd) {\n      eos_static_err(\"unable to verify id=%x on fs=%u path=%s - no local MD stored\",\n                     verifyfile->fId, verifyfile->fsId, fstPath.c_str());\n    } else {\n      if (fMd->mProtoFmd.disksize() != (unsigned long long) statinfo.st_size) {\n        eos_static_err(\"msg=\\\"updating disk size\\\" path=\\\"%s\\\" fxid=%s \"\n                       \"stat_sz=%llu disk_sz=%llu\", verifyfile->path.c_str(),\n                       hex_fid.c_str(), statinfo.st_size,\n                       fMd->mProtoFmd.disksize());\n        fMd->mProtoFmd.set_disksize(statinfo.st_size);\n        localUpdate = true;\n      }\n\n      if (fMd->mProtoFmd.lid() != verifyfile->lId) {\n        eos_static_err(\"msg=\\\"updating layout id\\\" path=\\\"%s\\\" fxid=%s \"\n                       \"central value %u - changelog value %u\",\n                       verifyfile->path.c_str(), hex_fid.c_str(),\n                       verifyfile->lId, fMd->mProtoFmd.lid());\n        localUpdate = true;\n      }\n\n      if (fMd->mProtoFmd.cid() != verifyfile->cId) {\n        eos_static_err(\"msg=\\\"updating container id\\\" path=\\\"%s\\\" fxid=%s \"\n                       \"central value %llu - changelog value %llu\",\n                       verifyfile->path.c_str(), hex_fid.c_str(),\n                       verifyfile->cId, fMd->mProtoFmd.cid());\n        localUpdate = true;\n      }\n\n      // Update reference size\n      if (eos::common::LayoutId::IsRain(fMd->mProtoFmd.lid())) {\n        // This is the best he have, no easy way to know the logical size\n        // for a RAIN file\n        if (fMd->mProtoFmd.size() != fMd->mProtoFmd.mgmsize()) {\n          fMd->mProtoFmd.set_size(fMd->mProtoFmd.mgmsize());\n          localUpdate = true;\n        }\n      } else {\n        if (fMd->mProtoFmd.size() != (unsigned long long)statinfo.st_size) {\n          fMd->mProtoFmd.set_size(statinfo.st_size);\n          localUpdate = true;\n        }\n      }\n\n      fMd->mProtoFmd.set_lid(verifyfile->lId);\n      fMd->mProtoFmd.set_cid(verifyfile->cId);\n      std::unique_ptr<ChecksumGroup> checksummer{ new ChecksumGroup };\n\n      if (verifyfile->computeChecksum) {\n        checksummer->SetDefault(std::move(ChecksumPlugins::GetChecksumObject(\n                                            fMd->mProtoFmd.lid())), static_cast<eos::common::LayoutId::eChecksum>\n                                (eos::common::LayoutId::GetChecksum(fMd->mProtoFmd.lid())));\n\n        for (const auto type : verifyfile->altchecksums) {\n          checksummer->AddAlternative(std::move(ChecksumPlugins::GetXsObj(type)), type);\n        }\n      }\n\n      // std::unique_ptr<CheckSum> checksummer =\n      //   ChecksumPlugins::GetChecksumObject(fMd->mProtoFmd.lid());\n      unsigned long long scansize = 0;\n      std::chrono::milliseconds scantime {0};\n      eos::fst::CheckSum::ReadCallBack::callback_data_t cbd;\n      cbd.caller = (void*) io;\n      eos::fst::CheckSum::ReadCallBack cb(eos::fst::XrdFstOfsFile::FileIoReadCB, cbd);\n\n      if ((checksummer) && verifyfile->computeChecksum &&\n          (!checksummer->ScanFile(cb, scansize, scantime, verifyfile->verifyRate))) {\n        eos_static_crit(\"cannot scan file to recalculate the checksum id=%llu on fs=%u path=%s\",\n                        verifyfile->fId, verifyfile->fsId, fstPath.c_str());\n      } else {\n        XrdOucString sizestring;\n\n        if (checksummer && verifyfile->computeChecksum) {\n          eos_static_info(\"rescanned checksum - size=%s time=%.02fms rate=%.02f \"\n                          \"MB/s limit=%d MB/s\", eos::common::StringConversion::GetReadableSizeString(\n                            sizestring, scansize, \"B\"),\n                          scantime.count(), 1.0 * scansize / 1000 /\n                          (scantime.count() ? scantime.count() : 99999999999999LL),\n                          verifyfile->verifyRate);\n        }\n\n        if (checksummer && verifyfile->computeChecksum) {\n          int checksumlen = 0;\n          bool cxError = false;\n          std::string computedchecksum = checksummer->GetDefault()->GetHexChecksum();\n\n          if (fMd->mProtoFmd.checksum() != computedchecksum) {\n            cxError = true;\n          }\n\n          // commit the disk checksum in case of differences between the in-memory value\n          if (fMd->mProtoFmd.diskchecksum() != computedchecksum) {\n            cxError = true;\n          }\n\n          // check local checksum alternatives\n          const auto altchecksums = checksummer->GetAlternatives();\n\n          if (cxError) {\n            eos_static_err(\"checksum invalid   : path=%s fxid=%s checksum=%s stored-checksum=%s\",\n                           verifyfile->path.c_str(), hex_fid.c_str(), computedchecksum.c_str(),\n                           fMd->mProtoFmd.checksum().c_str());\n            fMd->mProtoFmd.set_checksum(computedchecksum);\n            fMd->mProtoFmd.set_diskchecksum(computedchecksum);\n            fMd->mProtoFmd.set_disksize(fMd->mProtoFmd.size());\n\n            if (verifyfile->commitSize) {\n              fMd->mProtoFmd.set_mgmsize(fMd->mProtoFmd.size());\n            }\n\n            if (verifyfile->commitChecksum) {\n              fMd->mProtoFmd.set_mgmchecksum(computedchecksum);\n              fMd->mProtoFmd.set_blockcxerror(0);\n              fMd->mProtoFmd.set_filecxerror(0);\n            }\n\n            localUpdate = true;\n          } else {\n            eos_static_info(\"checksum OK        : path=%s fxid=%s checksum=%s\",\n                            verifyfile->path.c_str(), hex_fid.c_str(),\n                            computedchecksum.c_str());\n\n            // Reset error flags if needed\n            if (fMd->mProtoFmd.blockcxerror() || fMd->mProtoFmd.filecxerror()) {\n              fMd->mProtoFmd.set_blockcxerror(0);\n              fMd->mProtoFmd.set_filecxerror(0);\n              localUpdate = true;\n            }\n          }\n\n          // Update the extended attributes\n          if (io) {\n            const char* ptr = checksummer->GetDefault()->GetBinChecksum(checksumlen);\n            (void)io->attrSet(\"user.eos.checksum\", ptr, checksumlen);\n            (void)io->attrSet(\"user.eos.checksumtype\", checksummer->GetDefault()->GetName(),\n                              strlen(checksummer->GetDefault()->GetName()));\n            (void)io->attrSet(\"user.eos.filecxerror\", \"0\", 1);\n            (void)io->attrSet(\"user.eos.blockcxerror\", \"0\");\n          }\n        }\n\n        eos::common::Path cPath(verifyfile->path.c_str());\n\n        // commit local\n        if (localUpdate && (!gOFS.mFmdHandler->Commit(fMd.get()))) {\n          eos_static_err(\"unable to verify file id=%llu on fs=%u path=%s - commit \"\n                         \"to local MD storage failed\", verifyfile->fId,\n                         verifyfile->fsId, fstPath.c_str());\n        } else {\n          if (localUpdate) {\n            eos_static_info(\"committed verified meta data locally id=%llu on fs=%u path=%s\",\n                            verifyfile->fId, verifyfile->fsId, fstPath.c_str());\n          }\n\n          // commit to central mgm cache, only if commitSize or commitChecksum is set\n          XrdOucString capOpaqueFile = \"\";\n          XrdOucString mTimeString = \"\";\n          capOpaqueFile += \"/?\";\n          capOpaqueFile += \"&mgm.pcmd=commit\";\n          capOpaqueFile += \"&mgm.verify.checksum=1\";\n          capOpaqueFile += \"&mgm.size=\";\n          char filesize[1024];\n          sprintf(filesize, \"%\" PRIu64 \"\", fMd->mProtoFmd.size());\n          capOpaqueFile += filesize;\n          capOpaqueFile += \"&mgm.fid=\";\n          capOpaqueFile += hex_fid.c_str();\n          capOpaqueFile += \"&mgm.path=\";\n          capOpaqueFile += verifyfile->path.c_str();\n\n          if (checksummer && verifyfile->computeChecksum) {\n            capOpaqueFile += \"&mgm.checksum=\";\n            capOpaqueFile += checksummer->GetDefault()->GetHexChecksum();\n\n            if (verifyfile->commitChecksum) {\n              capOpaqueFile += \"&mgm.commit.checksum=1\";\n              const auto altchecksums = checksummer->GetAlternatives();\n\n              if (!altchecksums.empty()) {\n                std::vector<std::string> alt;\n\n                for (auto const [type, xs] : altchecksums) {\n                  alt.emplace_back(std::string(eos::common::LayoutId::GetChecksumString(\n                                                 type)) + \":\" + xs->GetHexChecksum());\n                }\n\n                capOpaqueFile += \"&mgm.altxs=\";\n                capOpaqueFile += eos::common::StringConversion::Join(alt, \",\").c_str();\n              }\n            }\n          }\n\n          if (verifyfile->commitSize) {\n            capOpaqueFile += \"&mgm.commit.size=1\";\n          }\n\n          capOpaqueFile += \"&mgm.commit.verify=1\";\n          capOpaqueFile += \"&mgm.mtime=\";\n          capOpaqueFile += eos::common::StringConversion::GetSizeString(mTimeString,\n                           (unsigned long long) fMd->mProtoFmd.mtime());\n          capOpaqueFile += \"&mgm.mtime_ns=\";\n          capOpaqueFile += eos::common::StringConversion::GetSizeString(mTimeString,\n                           (unsigned long long) fMd->mProtoFmd.mtime_ns());\n          capOpaqueFile += \"&mgm.add.fsid=\";\n          capOpaqueFile += (int) fMd->mProtoFmd.fsid();\n\n          if (verifyfile->commitSize || verifyfile->commitChecksum) {\n            if (localUpdate) {\n              eos_static_info(\"committed verified meta data centrally id=%llu on fs=%u path=%s\",\n                              verifyfile->fId, verifyfile->fsId, fstPath.c_str());\n            }\n\n            XrdOucErrInfo lerror;\n            int rc = gOFS.CallManager(&lerror, verifyfile->path.c_str(), 0, capOpaqueFile);\n\n            if (rc) {\n              eos_static_err(\"unable to verify file id=%s fs=%u at manager %s\",\n                             hex_fid.c_str(), verifyfile->fsId, verifyfile->managerId.c_str());\n            }\n          }\n        }\n      }\n    }\n\n    if (!open_rc) {\n      io->fileClose();\n    }\n\n    mRunningVerify = 0;\n\n    if (verifyfile) {\n      delete verifyfile;\n    }\n  }\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/tools/Adler32.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Adler32.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include \"fst/checksum/ChecksumPlugins.hh\"\n#include \"common/CLI11.hpp\"\n\nint\nmain(int argc, char* argv[])\n{\n  size_t offset{0};\n  std::string path{\"\"};\n  CLI::App app(\"Compute adler32 checksum on a file\");\n  app.add_option(\"--offset\", offset, \"Offset\");\n  app.add_option(\"path\", path, \"Path\")->required();\n\n  try {\n    app.parse(argc, argv);\n  } catch (const CLI::ParseError& e) {\n    return app.exit(e);\n  }\n\n  std::unique_ptr<eos::fst::CheckSum> normalXS =\n    eos::fst::ChecksumPlugins::GetChecksumObject(eos::common::LayoutId::kAdler);\n\n  if (normalXS) {\n    unsigned long long scansize;\n    std::chrono::milliseconds scantime;\n\n    if (!normalXS->ScanFile(path.c_str(), scansize, scantime, 0, offset)) {\n      fprintf(stderr, \"error: unable to scan file path=%s\\n\", path.c_str());\n      exit(-1);\n    } else {\n      fprintf(stdout, \"path=%s size=%llu time=%li adler32=%s\\n\", path.c_str(),\n              scansize, scantime.count(), normalXS->GetHexChecksum());\n      exit(0);\n    }\n  }\n\n  fprintf(stderr, \"error: failed to get checksum object\\n\");\n  exit(-1);\n}\n"
  },
  {
    "path": "fst/tools/CheckBlockXS.cc",
    "content": "// ----------------------------------------------------------------------\n// File: CheckBlockXS.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/checksum/ChecksumPlugins.hh\"\n#include \"common/LayoutId.hh\"\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <fst/io/FileIoPluginCommon.hh>\n\nint\nmain(int argc, const char* argv[])\n{\n  if (argc != 2) {\n    fprintf(stderr, \"usage: eos-check-blockxs <path> \\n\");\n    exit(-1);\n  }\n\n  XrdOucString path = argv[1];\n  XrdOucString pathXS = path;\n  pathXS += \".xsmap\";\n  int fd = open(path.c_str(), O_RDONLY);\n\n  if (fd < 0) {\n    fprintf(stderr, \"error: cannot open path %s\\n\", path.c_str());\n    exit(-1);\n  }\n\n  int fdxs = open(pathXS.c_str(), O_RDONLY);\n\n  if (fdxs < 0) {\n    fprintf(stderr, \"error: cannot open block checksum file for path %s\\n\",\n            pathXS.c_str());\n    exit(-1);\n  }\n\n  int ngood = 0;\n  int nerr = 0;\n  std::string checksumtype;\n  std::string blocksize;\n  std::unique_ptr<eos::fst::FileIo> io(eos::fst::FileIoPluginHelper::GetIoObject(\n                                         pathXS.c_str()));\n\n  if (io->attrGet(\"user.eos.blockchecksum\", checksumtype) ||\n      io->attrGet(\"user.eos.blocksize\", blocksize)) {\n    fprintf(stderr, \"error: the extended attributes are missing on the block \"\n            \"checksum file!\\n\");\n    exit(-1);\n  }\n\n  XrdOucString envstring = \"eos.layout.blockchecksum=\";\n  envstring += checksumtype.c_str();\n  XrdOucEnv env(envstring.c_str());\n  unsigned long checksumType =\n    eos::common::LayoutId::GetBlockChecksumFromEnv(env);\n  int blockSize = atoi(blocksize.c_str());\n  int blockSizeSymbol = eos::common::LayoutId::BlockSizeEnum(blockSize);\n  unsigned int layoutid =\n    eos::common::LayoutId::GetId(eos::common::LayoutId::kPlain,\n                                 eos::common::LayoutId::kNone, 0,\n                                 blockSizeSymbol, checksumType);\n  std::unique_ptr<eos::fst::CheckSum> checksum =\n    eos::fst::ChecksumPlugins::GetChecksumObject(layoutid, true);\n\n  if (!checksum) {\n    fprintf(stderr, \"error: failed to get checksum object for file %s\",\n            path.c_str());\n    exit(-1);\n  }\n\n  struct stat info;\n\n  if (fstat(fd, &info)) {\n    fprintf(stderr, \"error: failed to stat file %s\\n\", path.c_str());\n    exit(-1);\n  }\n\n  off_t maxfilesize = info.st_size;\n  off_t offset = 0;\n  char* buffer = (char*) malloc(blockSize);\n\n  if (!buffer) {\n    fprintf(stderr, \"error: cannot allocate blockmemory of size %u\\n\",\n            (unsigned int) blockSize);\n    exit(-1);\n  }\n\n  if (checksum->OpenMap(pathXS.c_str(), maxfilesize, blockSize, false)) {\n    do {\n      int nread = read(fd, buffer, blockSize);\n\n      if (nread < 0) {\n        fprintf(stderr, \"error: failed to read block at offset %llu\\n\",\n                (unsigned long long) offset);\n        free(buffer);\n        exit(-1);\n      }\n\n      if (nread < blockSize) {\n        // 0 rest of the block\n        memset(buffer + nread, 0, blockSize - nread);\n      }\n\n      checksum->Reset();\n\n      //        fprintf(stderr,\"checking %lld %lld\\n\", offset, blockSize);\n      if (!checksum->CheckBlockSum(offset, buffer, blockSize)) {\n        fprintf(stderr, \"block-XS error => offset %llu\\n\", (unsigned long long) offset);\n        nerr++;\n      } else {\n        //          fprintf(stderr,\"block-XS ok    => offset %llu\\n\", (unsigned long long)offset);\n        ngood++;\n      }\n\n      if (nread < blockSize) {\n        break;\n      }\n\n      offset += nread;\n    } while (1);\n  } else {\n    fprintf(stderr, \"error: unable to open block checksum map\\n\");\n  }\n\n  checksum->CloseMap();\n  free(buffer);\n  close(fd);\n  close(fdxs);\n  fprintf(stderr, \"%s : tot: %i ok: %i error: %i\\n\", path.c_str(), ngood + nerr,\n          ngood, nerr);\n\n  if (nerr) {\n    exit(-1);\n  }\n\n  exit(0);\n}\n\n"
  },
  {
    "path": "fst/tools/CheckSum.cc",
    "content": "// ----------------------------------------------------------------------\n// File: CRC32C.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include \"fst/checksum/ChecksumPlugins.hh\"\n#include \"common/LayoutId.hh\"\n\n\nvoid usage()\n{\n  fprintf(stderr,\n          \"usage: eos-checksum adler|blake7|crc32|crc32c|crc64|md5|sha|sha256|xxhash64|hwh64 <path>|/dev/stdin\\n\");\n}\n\nint\nmain(int argc, char* argv[])\n{\n  if (argc != 3) {\n    usage();\n    exit(-1);\n  }\n\n  std::string requested_checksum = argv[1];\n  int checksum_type = 0;\n\n  if ((checksum_type = eos::common::LayoutId::GetChecksumFromString(\n                         requested_checksum) == eos::common::LayoutId::kNone)) {\n    fprintf(stderr, \"error: checksum <%s> is not supported\\n\", argv[1]);\n    exit(-EINVAL);\n  }\n\n  std::unique_ptr<eos::fst::CheckSum> normalXS =\n    std::unique_ptr<eos::fst::CheckSum> (eos::fst::ChecksumPlugins::GetXsObj(\n        eos::common::LayoutId::GetChecksumFromString(requested_checksum)));\n\n  if (normalXS) {\n    XrdOucString path = (argv[2]) ? argv[2] : \"\";\n    unsigned long long scansize;\n    std::chrono::milliseconds scantime;\n\n    if (!normalXS->ScanFile(path.c_str(), scansize, scantime)) {\n      fprintf(stderr, \"error: unable to scan file path=%s errno=%d\\n\", argv[2],\n              errno);\n      exit(-1);\n    } else {\n      fprintf(stdout, \"path=%s size=%llu time=%lims %s=%s\\n\", argv[2], scansize,\n              scantime.count(), argv[1], normalXS->GetHexChecksum());\n      exit(0);\n    }\n  }\n\n  fprintf(stderr, \"error: failed to get checksum object\\n\");\n  exit(-1);\n}\n"
  },
  {
    "path": "fst/tools/ComputeBlockXS.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ComputeBlockXS.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/checksum/ChecksumPlugins.hh\"\n#include \"fst/io/FileIoPluginCommon.hh\"\n#include \"common/LayoutId.hh\"\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n\n\nint\nmain(int argc, const char* argv[])\n{\n  if (argc != 2) {\n    fprintf(stderr, \"usage: eos-check-blockxs <path> \\n\");\n    exit(-1);\n  }\n\n  XrdOucString path = argv[1];\n  XrdOucString pathXS = path;\n  pathXS += \".xsmap\";\n  int fd = open(path.c_str(), O_RDONLY);\n\n  if (fd < 0) {\n    fprintf(stderr, \"error: cannot open path %s\\n\", path.c_str());\n    exit(-1);\n  }\n\n  int fdxs = open(pathXS.c_str(), O_RDONLY);\n\n  if (fdxs < 0) {\n    fprintf(stderr, \"error: cannot open block checksum file for path %s\\n\",\n            pathXS.c_str());\n    exit(-1);\n  }\n\n  int ngood = 0;\n  int nerr = 0;\n  std::unique_ptr<eos::fst::FileIo> io(eos::fst::FileIoPluginHelper::GetIoObject(\n                                         pathXS.c_str()));\n  std::string checksumtype;\n  std::string blocksize;\n\n  if (io->attrGet(\"user.eos.blockchecksum\", checksumtype) ||\n      io->attrGet(\"user.eos.blocksize\", blocksize)) {\n    fprintf(stderr,\n            \"error: the extended attributes are missing on the block checksum file!\\n\");\n    exit(-1);\n  }\n\n  XrdOucString envstring = \"eos.layout.blockchecksum=\";\n  envstring += checksumtype.c_str();\n  XrdOucEnv env(envstring.c_str());\n  unsigned long checksumType = eos::common::LayoutId::GetBlockChecksumFromEnv(\n                                 env);\n  int blockSize = atoi(blocksize.c_str());\n  int blockSizeSymbol = eos::common::LayoutId::BlockSizeEnum(blockSize);\n  unsigned int layoutid =\n    eos::common::LayoutId::GetId(eos::common::LayoutId::kPlain,\n                                 eos::common::LayoutId::kNone, 0,\n                                 blockSizeSymbol, checksumType);\n  std::unique_ptr<eos::fst::CheckSum> checksum =\n    eos::fst::ChecksumPlugins::GetChecksumObject(layoutid, true);\n\n  if (!checksum) {\n    fprintf(stderr, \"error: failed to get checksum object for file %s\",\n            path.c_str());\n    exit(-1);\n  }\n\n  struct stat info;\n\n  if (fstat(fd, &info)) {\n    fprintf(stderr, \"error: failed to stat file %s\\n\", path.c_str());\n    exit(-1);\n  }\n\n  off_t maxfilesize = info.st_size;\n  off_t offset = 0;\n  char* buffer = (char*) malloc(blockSize);\n\n  if (!buffer) {\n    fprintf(stderr, \"error: cannot allocate blockmemory of size %u\\n\",\n            (unsigned int) blockSize);\n    exit(-1);\n  }\n\n  if (checksum->OpenMap(pathXS.c_str(), maxfilesize, blockSize, true)) {\n    do {\n      int nread = read(fd, buffer, blockSize);\n\n      if (nread < 0) {\n        fprintf(stderr, \"error: failed to read block at offset %llu\\n\",\n                (unsigned long long) offset);\n        free(buffer);\n        exit(-1);\n      }\n\n      if (nread < blockSize) {\n        // 0 rest of the block\n        memset(buffer + nread, 0, blockSize - nread);\n      }\n\n      checksum->Reset();\n\n      if (!checksum->AddBlockSum(offset, buffer, blockSize)) {\n        fprintf(stderr, \"block-XS error => offset %llu\\n\", (unsigned long long) offset);\n        nerr++;\n      } else {\n        // fprintf(stderr,\"block-XS ok    => offset %llu\\n\", (unsigned long long)offset);\n        ngood++;\n      }\n\n      if (nread < blockSize) {\n        break;\n      }\n\n      offset += nread;\n    } while (1);\n  } else {\n    fprintf(stderr, \"error: unable to open block checksum map\\n\");\n  }\n\n  checksum->CloseMap();\n  free(buffer);\n  close(fd);\n  close(fdxs);\n  fprintf(stderr, \"%s : tot: %i ok: %i error: %i\\n\", path.c_str(), ngood + nerr,\n          ngood, nerr);\n\n  if (nerr) {\n    exit(-1);\n  }\n\n  exit(0);\n}\n\n"
  },
  {
    "path": "fst/tools/ConvertFileMD.cc",
    "content": "// /************************************************************************\n//  * EOS - the CERN Disk Storage System                                   *\n//  * Copyright (C) 2022 CERN/Switzerland                                  *\n//  *                                                                      *\n//  * This program is free software: you can redistribute it and/or modify *\n//  * it under the terms of the GNU General Public License as published by *\n//  * the Free Software Foundation, either version 3 of the License, or    *\n//  * (at your option) any later version.                                  *\n//  *                                                                      *\n//  * This program is distributed in the hope that it will be useful,      *\n//  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n//  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n//  * GNU General Public License for more details.                         *\n//  *                                                                      *\n//  * You should have received a copy of the GNU General Public License    *\n//  * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n//  ************************************************************************\n\n#include \"fst/filemd/FmdAttr.hh\"\n#include \"fst/utils/FSPathHandler.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/Logging.hh\"\n#include \"common/CLI11.hpp\"\n#include <iostream>\n#include <memory>\n\nbool configureLogger(FILE* fp)\n{\n  if (fp == nullptr) {\n    return false;\n  }\n\n  // Redirect stdout and stderr to the log file\n  int ret = dup2(fileno(fp), fileno(stdout));\n\n  if (ret != -1) {\n    ret = dup2(fileno(fp), fileno(stderr));\n  }\n\n  return ret != -1;\n}\n\nint\nmain(int argc, char* argv[])\n{\n  std::string log_file {\"\"};\n  std::string log_level {\"err\"}; // accepts info, debug, err, crit, warning etc.\n  CLI::App app(\"Tool to inspect filemd metadata\");\n  app.add_option(\"--log-level\", log_level, \"Logging level\", true);\n  app.require_subcommand();\n  std::string file_path;\n  auto inspect_subcmd = app.add_subcommand(\"inspect\",\n                        \"inspect filemd attributes\");\n  inspect_subcmd->add_option(\"--path\", file_path, \"full path to file\")\n  ->required();\n  inspect_subcmd->add_option(\"--log-file\", log_file,\n                             \"Log file for operations\", true);\n\n  try {\n    app.parse(argc, argv);\n  } catch (const CLI::ParseError& e) {\n    return app.exit(e);\n  }\n\n  auto& g_logger = eos::common::Logging::GetInstance();\n  g_logger.SetLogPriority(g_logger.GetPriorityByString(log_level.c_str()));\n  g_logger.SetUnit(\"EOSFileMD\");\n  std::unique_ptr<FILE, int(*)(FILE*)> fptr {fopen(log_file.c_str(), \"a+\"), &fclose};\n\n  if (fptr.get() && !configureLogger(fptr.get())) {\n    std::cerr << \"error: failed to setup logging using log_file: \" << log_file\n              << std::endl;\n    return -1;\n  }\n\n  auto attr_handler = std::make_unique<eos::fst::FmdAttrHandler>(\n                        eos::fst::makeFSPathHandler(\"\"));\n\n  if (app.got_subcommand(\"inspect\")) {\n    auto [status, fmd] = attr_handler->LocalRetrieveFmd(file_path);\n\n    if (!status) {\n      std::cerr << \"error: failed to retreive filemd for path=\"\n                << file_path << std::endl;\n    }\n\n    std::cout << fmd.mProtoFmd.DebugString() << std::endl;\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "fst/tools/IoPing.c",
    "content": "/*\n *  ioping  -- simple I/0 latency measuring tool\n *\n *  Copyright (C) 2011-2015 Konstantin Khlebnikov <koct9i@gmail.com>\n *\n *  This program is free software: you can redistribute it and/or modify\n *  it under the terms of the GNU General Public License as published by\n *  the Free Software Foundation, either version 3 of the License, or\n *  (at your option) any later version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program.  If not, see <http://www.gnu.org/licenses/>.\n *\n */\n\n#ifndef VERSION\n# define VERSION \"1.0\"\n#endif\n\n#ifndef EXTRA_VERSION\n# define EXTRA_VERSION \"\"\n#endif\n\n#define _GNU_SOURCE\n#define _FILE_OFFSET_BITS 64\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <getopt.h>\n#include <string.h>\n#include <unistd.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <time.h>\n#include <math.h>\n#include <limits.h>\n#include <signal.h>\n#include <sys/types.h>\n#include <sys/time.h>\n#include <sys/stat.h>\n\n#ifdef __LINUX__\n# include <sys/ioctl.h>\n# include <sys/mount.h>\n# include <sys/sysmacros.h>\n# define HAVE_CLOCK_GETTIME\n# define HAVE_POSIX_FADVICE\n# define HAVE_POSIX_MEMALIGN\n# define HAVE_MKOSTEMP\n# define HAVE_DIRECT_IO\n# define HAVE_LINUX_ASYNC_IO\n# define HAVE_ERR_INCLUDE\n# define MAX_RW_COUNT   0x7ffff000 /* 2G - 4K */\n#endif\n\n#ifdef __gnu_hurd__\n# include <sys/ioctl.h>\n# define HAVE_CLOCK_GETTIME\n# define HAVE_POSIX_MEMALIGN\n# define HAVE_MKOSTEMP\n# define HAVE_ERR_INCLUDE\n#endif\n\n#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)\n# include <sys/ioctl.h>\n# include <sys/mount.h>\n# include <sys/disk.h>\n# define HAVE_CLOCK_GETTIME\n# define HAVE_MKOSTEMP\n# define HAVE_DIRECT_IO\n# define HAVE_ERR_INCLUDE\n#endif\n\n#ifdef __DragonFly__\n# include <sys/diskslice.h>\n# define HAVE_CLOCK_GETTIME\n# define HAVE_MKOSTEMP\n# define HAVE_ERR_INCLUDE\n#endif\n\n#ifdef __OpenBSD__\n# include <sys/ioctl.h>\n# include <sys/disklabel.h>\n# include <sys/dkio.h>\n# include <sys/param.h>\n# include <sys/mount.h>\n# define HAVE_CLOCK_GETTIME\n# define HAVE_POSIX_MEMALIGN\n# define HAVE_MKOSTEMP\n# define HAVE_ERR_INCLUDE\n#endif\n\n#ifdef __APPLE__ /* OS X */\n# include <sys/ioctl.h>\n# include <sys/mount.h>\n# include <sys/disk.h>\n# include <sys/uio.h>\n# define HAVE_NOCACHE_IO\n# define HAVE_ERR_INCLUDE\n#endif\n\n#ifdef __sun  /* Solaris */\n# include <sys/dkio.h>\n# include <sys/vtoc.h>\n# define HAVE_CLOCK_GETTIME\n# define HAVE_DIRECT_IO\n# define O_DIRECT O_DSYNC\n# define HAVE_ERR_INCLUDE\n#endif\n\n#ifdef __MINGW32__ /* Windows */\n# include <io.h>\n# include <stdarg.h>\n# include <windows.h>\n# define HAVE_DIRECT_IO\n# define HAVE_MKOSTEMP /* not required */\n#endif\n\n#if defined(_POSIX_SYNCHRONIZED_IO) && _POSIX_SYNCHRONIZED_IO > 0\n# define HAVE_POSIX_FDATASYNC\n#endif\n\n#ifdef HAVE_ERR_INCLUDE\n# include <err.h>\n#else\n\n#define ERR_PREFIX \"ioping: \"\n\nvoid err(int eval, const char* fmt, ...)\n{\n  va_list ap;\n  va_start(ap, fmt);\n  fprintf(stderr, ERR_PREFIX);\n  vfprintf(stderr, fmt, ap);\n  fprintf(stderr, \": %s\\n\", strerror(errno));\n  va_end(ap);\n  exit(eval);\n}\n\nvoid errx(int eval, const char* fmt, ...)\n{\n  va_list ap;\n  va_start(ap, fmt);\n  fprintf(stderr, ERR_PREFIX);\n  vfprintf(stderr, fmt, ap);\n  fprintf(stderr, \"\\n\");\n  va_end(ap);\n  exit(eval);\n}\n\nvoid warnx(const char* fmt, ...)\n{\n  va_list ap;\n  va_start(ap, fmt);\n  fprintf(stderr, ERR_PREFIX);\n  vfprintf(stderr, fmt, ap);\n  fprintf(stderr, \"\\n\");\n  va_end(ap);\n}\n\n#endif /* HAVE_ERR_INCLUDE */\n\n#define NSEC_PER_SEC  1000000000ll\n\n#ifdef HAVE_CLOCK_GETTIME\n\nstatic inline long long now(void)\n{\n  struct timespec ts;\n\n  if (clock_gettime(CLOCK_MONOTONIC, &ts)) {\n    err(3, \"clock_gettime failed\");\n  }\n\n  return ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec;\n}\n\n#else\n\nstatic inline long long now(void)\n{\n  struct timeval tv;\n\n  if (gettimeofday(&tv, NULL)) {\n    err(3, \"gettimeofday failed\");\n  }\n\n  return tv.tv_sec * NSEC_PER_SEC + tv.tv_usec * 1000ll;\n}\n\n#endif /* HAVE_CLOCK_GETTIME */\n\n#ifndef HAVE_MKOSTEMP\nint mkostemp(char* template, int flags)\n{\n  int fd;\n  fd = mkstemp(template);\n\n  if (!flags || fd < 0) {\n    return fd;\n  }\n\n  close(fd);\n  return open(template, O_RDWR | flags);\n}\n#endif\n\nint async = 0;\n\n#ifdef __MINGW32__\n\nssize_t pread(int fd, void* buf, size_t count, off_t offset)\n{\n  HANDLE h = (HANDLE)_get_osfhandle(fd);\n  DWORD r;\n  OVERLAPPED o;\n  memset(&o, 0, sizeof(o));\n  o.Offset = offset;\n  o.OffsetHigh = offset >> 32;\n\n  if (ReadFile(h, buf, count, &r, &o)) {\n    return r;\n  }\n\n  if (async && GetLastError() == ERROR_IO_PENDING &&\n      GetOverlappedResult(h, &o, &r, TRUE)) {\n    return r;\n  }\n\n  return -1;\n}\n\nssize_t pwrite(int fd, void* buf, size_t count, off_t offset)\n{\n  HANDLE h = (HANDLE)_get_osfhandle(fd);\n  DWORD r;\n  OVERLAPPED o;\n  memset(&o, 0, sizeof(o));\n  o.Offset = offset;\n  o.OffsetHigh = offset >> 32;\n\n  if (WriteFile(h, buf, count, &r, &o)) {\n    return r;\n  }\n\n  if (async && GetLastError() == ERROR_IO_PENDING &&\n      GetOverlappedResult(h, &o, &r, TRUE)) {\n    return r;\n  }\n\n  return -1;\n}\n\nint fsync(int fd)\n{\n  HANDLE h = (HANDLE)_get_osfhandle(fd);\n  return FlushFileBuffers(h) ? 0 : -1;\n}\n\nvoid srandom(unsigned int seed)\n{\n  srand(seed);\n}\n\nlong int random(void)\n{\n  return rand() * (RAND_MAX + 1) + rand();\n}\n\nint nanosleep(const struct timespec* req, struct timespec* rem)\n{\n  (void)rem;\n  Sleep(req->tv_sec * 1000 + req->tv_nsec / 1000000);\n  return 0;\n}\n\n#endif /* __MINGW32__ */\n\n#ifndef HAVE_POSIX_MEMALIGN\n/* don't free it */\nint posix_memalign(void** memptr, size_t alignment, size_t size)\n{\n  char* ptr;\n  ptr = malloc(size + alignment);\n\n  if (!ptr) {\n    return -ENOMEM;\n  }\n\n  *memptr = ptr + alignment - (size_t)ptr % alignment;\n  return 0;\n}\n#endif\n\n#ifndef HAVE_POSIX_FDATASYNC\nint fdatasync(int fd)\n{\n  return fsync(fd);\n}\n#endif\n\nvoid usage(void)\n{\n  fprintf(stderr,\n          \" Usage: ioping [-ABCDRLWYykq] [-c count] [-i interval] [-s size] [-S wsize]\\n\"\n          \"               [-o offset] [-w deadline] [-pP period] directory|file|device\\n\"\n          \"        ioping -h | -v\\n\"\n          \"\\n\"\n          \"      -c <count>      stop after <count> requests\\n\"\n          \"      -i <interval>   interval between requests (1s)\\n\"\n          \"      -l <speed>      speed limit in bytes per second\\n\"\n          \"      -t <time>       minimal valid request time (0us)\\n\"\n          \"      -T <time>       maximum valid request time\\n\"\n          \"      -s <size>       request size (4k)\\n\"\n          \"      -S <wsize>      working set size (1m)\\n\"\n          \"      -o <offset>     working set offset (0)\\n\"\n          \"      -w <deadline>   stop after <deadline> time passed\\n\"\n          \"      -p <period>     print raw statistics for every <period> requests\\n\"\n          \"      -P <period>     print raw statistics for every <period> in time\\n\"\n          \"      -A              use asynchronous I/O\\n\"\n          \"      -C              use cached I/O (no cache flush/drop)\\n\"\n          \"      -B              print final statistics in raw format\\n\"\n          \"      -D              use direct I/O (O_DIRECT)\\n\"\n          \"      -R              seek rate test\\n\"\n          \"      -L              use sequential operations\\n\"\n          \"      -W              use write I/O (please read manpage)\\n\"\n          \"      -G              read-write ping-pong mode\\n\"\n          \"      -Y              use sync I/O (O_SYNC)\\n\"\n          \"      -y              use data sync I/O (O_DSYNC)\\n\"\n          \"      -k              keep and reuse temporary file (ioping.tmp)\\n\"\n          \"      -q              suppress human-readable output\\n\"\n          \"      -h              display this message and exit\\n\"\n          \"      -v              display version and exit\\n\"\n          \"\\n\"\n         );\n}\n\nvoid version(void)\n{\n  fprintf(stderr, \"ioping %s\\n\", VERSION EXTRA_VERSION);\n}\n\nstruct suffix {\n  const char*  txt;\n  long long mul;\n};\n\nstatic struct suffix int_suffix[] = {\n  { \"T\",    1000000000000ll },\n  { \"G\",    1000000000ll },\n  { \"M\",    1000000ll },\n  { \"k\",    1000ll },\n  { \"\",   1ll },\n  { \"da\",   10ll },\n  { \"P\",    1000000000000000ll },\n  { \"E\",    1000000000000000000ll },\n  { NULL,   0ll },\n};\n\nstatic struct suffix size_suffix[] = {\n  /* These are first match for printing */\n  { \"PiB\",  1ll << 50 },\n  { \"TiB\",  1ll << 40 },\n  { \"GiB\",  1ll << 30 },\n  { \"MiB\",  1ll << 20 },\n  { \"KiB\",  1ll << 10 },\n  { \"B\",    1 },\n  { \"\",   1 },\n  /* Should be decimal, keep binary for compatibility */\n  { \"k\",    1ll << 10 },\n  { \"kb\",   1ll << 10 },\n  { \"m\",    1ll << 20 },\n  { \"mb\",   1ll << 20 },\n  { \"g\",    1ll << 30 },\n  { \"gb\",   1ll << 30 },\n  { \"t\",    1ll << 40 },\n  { \"tb\",   1ll << 40 },\n  { \"pb\",   1ll << 50 },\n  { \"eb\",   1ll << 60 },\n  { \"sector\", 512 },\n  { \"page\", 4096 },\n  { NULL,   0ll },\n};\n\nstatic struct suffix time_suffix[] = {\n  { \"hour\", NSEC_PER_SEC * 60 * 60 },\n  { \"min\",  NSEC_PER_SEC * 60 },\n  { \"s\",    NSEC_PER_SEC },\n  { \"ms\",   1000000ll },\n  { \"us\",   1000ll },\n  { \"ns\",   1ll },\n  { \"nsec\", 1ll },\n  { \"usec\", 1000ll },\n  { \"msec\", 1000000ll },\n  { \"\",   NSEC_PER_SEC },\n  { \"sec\",  NSEC_PER_SEC },\n  { \"m\",    NSEC_PER_SEC * 60 },\n  { \"h\",    NSEC_PER_SEC * 60 * 60 },\n  { NULL,   0ll },\n};\n\nlong long parse_suffix(const char* str, struct suffix* sfx,\n                       long long min, long long max)\n{\n  char* end;\n  double val, den;\n  val = strtod(str, &end);\n\n  if (*end == '/') {\n    if (end == str) {\n      val = 1;\n    }\n\n    den = strtod(end + 1, &end);\n\n    if (!den) {\n      errx(1, \"division by zero in parsing argument: %s\", str);\n    }\n\n    val /= den;\n  }\n\n  for (; sfx->txt ; sfx++) {\n    if (strcasecmp(end, sfx->txt)) {\n      continue;\n    }\n\n    val *= sfx->mul;\n\n    if (val < min || val > max) {\n      errx(1, \"integer overflow at parsing argument: %s\", str);\n    }\n\n    return val;\n  }\n\n  errx(1, \"invalid suffix: \\\"%s\\\"\", end);\n  return 0;\n}\n\nint parse_int(const char* str)\n{\n  return parse_suffix(str, int_suffix, 0, INT_MAX);\n}\n\nssize_t parse_size(const char* str)\n{\n  return parse_suffix(str, size_suffix, 0, LONG_MAX);\n}\n\noff_t parse_offset(const char* str)\n{\n  return parse_suffix(str, size_suffix, 0, LLONG_MAX);\n}\n\nlong long parse_time(const char* str)\n{\n  return parse_suffix(str, time_suffix, 0, LLONG_MAX);\n}\n\nvoid print_suffix(long long val, struct suffix* sfx)\n{\n  int precision;\n\n  while (val < sfx->mul && sfx->mul > 1) {\n    sfx++;\n  }\n\n  if (val % sfx->mul == 0) {\n    precision = 0;\n  } else if (val >= sfx->mul * 10) {\n    precision = 1;\n  } else {\n    precision = 2;\n  }\n\n  printf(\"%.*f\", precision, val * 1.0 / sfx->mul);\n\n  if (*sfx->txt) {\n    printf(\" %s\", sfx->txt);\n  }\n}\n\nvoid print_int(long long val)\n{\n  print_suffix(val, int_suffix);\n}\n\nvoid print_size(long long val)\n{\n  print_suffix(val, size_suffix);\n}\n\nvoid print_time(long long val)\n{\n  print_suffix(val, time_suffix);\n}\n\nchar* path = NULL;\nchar* fstype = \"\";\nchar* device = \"\";\noff_t device_size = 0;\n\nint fd;\nvoid* buf;\n\nint quiet = 0;\nint batch_mode = 0;\nint direct = 0;\nint cached = 0;\nint syncio = 0;\nint data_syncio = 0;\nint randomize = 1;\nint write_test = 0;\nint write_read_test = 0;\n\nlong long period_request = 0;\nlong long period_time = 0;\n\nint custom_interval, custom_deadline;\nlong long interval = NSEC_PER_SEC;\nstruct timespec interval_ts;\nlong long deadline = 0;\nlong long speed_limit = 0;\n\nlong long min_valid_time = 0;\nlong long max_valid_time = LLONG_MAX;\n\nssize_t default_size = 1 << 12;\nssize_t size = 0;\noff_t wsize = 0;\noff_t temp_wsize = 1 << 20;\n\nint keep_file = 0;\n\noff_t offset = 0;\noff_t woffset = 0;\n\nlong long stop_at_request = 0;\n\nint exiting = 0;\n\nvoid parse_options(int argc, char** argv)\n{\n  int opt;\n\n  if (argc < 2) {\n    usage();\n    exit(1);\n  }\n\n  while ((opt = getopt(argc, argv,\n                       \"hvkALRDCWGYBqyi:t:T:w:s:S:c:o:p:P:l:\")) != -1) {\n    switch (opt) {\n    case 'h':\n      usage();\n      exit(0);\n\n    case 'v':\n      version();\n      exit(0);\n\n    case 'L':\n      randomize = 0;\n      default_size = 1 << 18;\n      break;\n\n    case 'l':\n      speed_limit = parse_size(optarg);\n      break;\n\n    case 'R':\n      if (!custom_interval) {\n        interval = 0;\n      }\n\n      if (!custom_deadline) {\n        deadline = 3 * NSEC_PER_SEC;\n      }\n\n      temp_wsize = 1 << 26;\n      quiet = 1;\n      break;\n\n    case 'D':\n      direct = 1;\n      break;\n\n    case 'C':\n      cached = 1;\n      break;\n\n    case 'A':\n      async = 1;\n      break;\n\n    case 'W':\n      write_test++;\n      break;\n\n    case 'G':\n      write_test++;\n      write_read_test = 1;\n      break;\n\n    case 'Y':\n      syncio = 1;\n      break;\n\n    case 'y':\n      data_syncio = 1;\n      break;\n\n    case 'i':\n      interval = parse_time(optarg);\n      custom_interval = 1;\n      break;\n\n    case 't':\n      min_valid_time = parse_time(optarg);\n      break;\n\n    case 'T':\n      max_valid_time = parse_time(optarg);\n      break;\n\n    case 'w':\n      deadline = parse_time(optarg);\n      custom_deadline = 1;\n      break;\n\n    case 's':\n      size = parse_size(optarg);\n      break;\n\n    case 'S':\n      wsize = parse_offset(optarg);\n      break;\n\n    case 'o':\n      offset = parse_offset(optarg);\n      break;\n\n    case 'p':\n      period_request = parse_int(optarg);\n      break;\n\n    case 'P':\n      period_time = parse_time(optarg);\n      break;\n\n    case 'q':\n      quiet = 1;\n      break;\n\n    case 'B':\n      quiet = 1;\n      batch_mode = 1;\n      break;\n\n    case 'c':\n      stop_at_request = parse_int(optarg);\n      break;\n\n    case 'k':\n      keep_file = 1;\n      break;\n\n    case '?':\n      usage();\n      exit(1);\n    }\n  }\n\n  if (optind > argc - 1) {\n    errx(1, \"no destination specified\");\n  }\n\n  if (optind < argc - 1) {\n    errx(1, \"more than one destination specified\");\n  }\n\n  path = argv[optind];\n}\n\n#ifdef __LINUX__\n\nvoid parse_device(dev_t dev)\n{\n  char* buf = NULL, *ptr;\n  unsigned major, minor;\n  struct stat st;\n  size_t len;\n  FILE* file;\n  char* real;\n  /* since v2.6.26 */\n  file = fopen(\"/proc/self/mountinfo\", \"r\");\n\n  if (!file) {\n    goto old;\n  }\n\n  while (getline(&buf, &len, file) > 0) {\n    sscanf(buf, \"%*d %*d %u:%u\", &major, &minor);\n\n    if (makedev(major, minor) != dev) {\n      continue;\n    }\n\n    ptr = strstr(buf, \" - \") + 3;\n    fstype = strdup(strsep(&ptr, \" \"));\n    device = strdup(strsep(&ptr, \" \"));\n    goto out;\n  }\n\nold:\n  /* for older versions */\n  file = fopen(\"/proc/mounts\", \"r\");\n\n  if (!file) {\n    return;\n  }\n\n  while (getline(&buf, &len, file) > 0) {\n    ptr = buf;\n    strsep(&ptr, \" \");\n\n    if (*buf != '/' || stat(buf, &st) || st.st_rdev != dev) {\n      continue;\n    }\n\n    strsep(&ptr, \" \");\n    fstype = strdup(strsep(&ptr, \" \"));\n    device = strdup(buf);\n    goto out;\n  }\n\nout:\n  free(buf);\n  fclose(file);\n  real = realpath(device, NULL);\n\n  if (real) {\n    free(device);\n    device = real;\n  }\n}\n\n#elif defined(__APPLE__) || defined(__OpenBSD__) \\\n  || defined(__FreeBSD__) || defined(__FreeBSD_kernel__)\n\nvoid parse_device(dev_t dev)\n{\n  struct statfs fs;\n  (void)dev;\n\n  if (statfs(path, &fs)) {\n    return;\n  }\n\n  fstype = strdup(fs.f_fstypename);\n  device = strdup(fs.f_mntfromname);\n}\n\n#else\n\nvoid parse_device(dev_t dev)\n{\n  (void)dev;\n}\n\n#endif\n\nint get_device_size(int fd, struct stat* st)\n{\n  unsigned long long blksize = 0;\n  int ret = 0;\n#if defined(BLKGETSIZE64)\n  /* linux */\n  ret = ioctl(fd, BLKGETSIZE64, &blksize);\n#elif defined(DIOCGMEDIASIZE)\n  /* freebsd */\n  ret = ioctl(fd, DIOCGMEDIASIZE, &blksize);\n#elif defined(DKIOCGETBLOCKCOUNT)\n  /* macos */\n  ret = ioctl(fd, DKIOCGETBLOCKCOUNT, &blksize);\n  blksize <<= 9;\n#elif defined(__gnu_hurd__)\n  /* hurd */\n  blksize = st->st_size;\n#elif defined(__MINGW32__)\n  blksize = 0;\n#elif defined(__DragonFly__)\n  struct partinfo pinfo;\n  ret = ioctl(fd, DIOCGPART, &pinfo);\n  blksize = pinfo.media_size;\n#elif defined(__OpenBSD__)\n  struct disklabel label;\n  struct partition part;\n  ret = ioctl(fd, DIOCGDINFO, &label);\n  part = label.d_partitions[DISKPART(st->st_rdev)];\n  blksize = DL_GETPSIZE(&part) * label.d_secsize;\n#elif defined(__sun)\n  struct dk_minfo dkmp;\n  ret = ioctl(fd, DKIOCGMEDIAINFO, &dkmp);\n  blksize =  dkmp.dki_capacity * dkmp.dki_lbsize;\n#else\n# warning no get disk size method\n  ret = -1;\n  errno = ENOSYS;\n  blksize = 0;\n#endif\n  (void)fd;\n  st->st_size = blksize;\n  return ret;\n}\n\nssize_t do_pwrite(int fd, void* buf, size_t nbytes, off_t offset)\n{\n  ssize_t ret;\n  ret = pwrite(fd, buf, nbytes, offset);\n\n  if (ret < 0) {\n    return ret;\n  }\n\n  if (!cached && fdatasync(fd) < 0) {\n    err(3, \"fdatasync failed, please retry with option -C\");\n  }\n\n  return ret;\n}\n\nssize_t (*make_pread)(int fd, void* buf, size_t nbytes, off_t offset) = pread;\nssize_t (*make_pwrite)(int fd, void* buf, size_t nbytes,\n                       off_t offset) = do_pwrite;\nssize_t (*make_request)(int fd, void* buf, size_t nbytes, off_t offset) = pread;\n\n#ifdef HAVE_LINUX_ASYNC_IO\n\n#include <sys/syscall.h>\n#define timespec linux_timespec\n#define timeval linux_timeval\n#define itimerval linux_itimerval\n#define itimerspec linux_itimerspc\n#define timezone linux_timezone_\n#define sigset_t linux_sigset_t\n#define sigaction linux_sigaction\n#define stack_t linux_stack_t\n#define sigval_t linux_sigval_t\n#define siginfo_t linux_siginfo_t\n#define sigevent_t linux_sigevent_t\n#define sigevent linux_sigevent\n#define sigval linux_sigval\n#include <linux/aio_abi.h>\n#undef sigval\n#undef sigevent\n#undef sigevent_t\n#undef siginfo_t\n#undef sigval_t\n#undef stack_t\n#undef sigaction\n#undef sigset_t\n#undef timezone\n#undef itimerspec\n#undef itimerval\n#undef timeval\n#undef timespec\n\n\nstatic long io_setup(unsigned nr_reqs, aio_context_t* ctx)\n{\n  return syscall(__NR_io_setup, nr_reqs, ctx);\n}\n\nstatic long io_submit(aio_context_t ctx, long n, struct iocb** paiocb)\n{\n  return syscall(__NR_io_submit, ctx, n, paiocb);\n}\n\nstatic long io_getevents(aio_context_t ctx, long min_nr, long nr,\n                         struct io_event* events, struct timespec* tmo)\n{\n  return syscall(__NR_io_getevents, ctx, min_nr, nr, events, tmo);\n}\n\n#if 0\nstatic long io_cancel(aio_context_t ctx, struct iocb* aiocb,\n                      struct io_event* res)\n{\n  return syscall(__NR_io_cancel, ctx, aiocb, res);\n}\n\nstatic long io_destroy(aio_context_t ctx)\n{\n  return syscall(__NR_io_destroy, ctx);\n}\n#endif\n\naio_context_t aio_ctx;\nstruct iocb aio_cb;\nstruct iocb* aio_cbp = &aio_cb;\nstruct io_event aio_ev;\n\nstatic ssize_t aio_pread(int fd, void* buf, size_t nbytes, off_t offset)\n{\n  aio_cb.aio_lio_opcode = IOCB_CMD_PREAD;\n  aio_cb.aio_fildes = fd;\n  aio_cb.aio_buf = (unsigned long) buf;\n  aio_cb.aio_nbytes = nbytes;\n  aio_cb.aio_offset = offset;\n\n  if (io_submit(aio_ctx, 1, &aio_cbp) != 1) {\n    err(1, \"aio submit failed\");\n  }\n\n  if (io_getevents(aio_ctx, 1, 1, &aio_ev, NULL) != 1) {\n    err(1, \"aio getevents failed\");\n  }\n\n  if (aio_ev.res < 0) {\n    errno = -aio_ev.res;\n    return -1;\n  }\n\n  return aio_ev.res;\n}\n\nstatic ssize_t aio_pwrite(int fd, void* buf, size_t nbytes, off_t offset)\n{\n  aio_cb.aio_lio_opcode = IOCB_CMD_PWRITE;\n  aio_cb.aio_fildes = fd;\n  aio_cb.aio_buf = (unsigned long) buf;\n  aio_cb.aio_nbytes = nbytes;\n  aio_cb.aio_offset = offset;\n\n  if (io_submit(aio_ctx, 1, &aio_cbp) != 1) {\n    err(1, \"aio submit failed\");\n  }\n\n  if (io_getevents(aio_ctx, 1, 1, &aio_ev, NULL) != 1) {\n    err(1, \"aio getevents failed\");\n  }\n\n  if (aio_ev.res < 0) {\n    errno = -aio_ev.res;\n    return -1;\n  }\n\n  if (!cached && fdatasync(fd) < 0) {\n    err(3, \"fdatasync failed, please retry with option -C\");\n  }\n\n  return aio_ev.res;\n#if 0\n  aio_cb.aio_lio_opcode = IOCB_CMD_FDSYNC;\n\n  if (io_submit(aio_ctx, 1, &aio_cbp) != 1) {\n    err(1, \"aio fdsync submit failed\");\n  }\n\n  if (io_getevents(aio_ctx, 1, 1, &aio_ev, NULL) != 1) {\n    err(1, \"aio getevents failed\");\n  }\n\n  if (aio_ev.res < 0) {\n    return aio_ev.res;\n  }\n\n#endif\n}\n\nstatic void aio_setup(void)\n{\n  memset(&aio_ctx, 0, sizeof aio_ctx);\n  memset(&aio_cb, 0, sizeof aio_cb);\n\n  if (io_setup(1, &aio_ctx)) {\n    err(2, \"aio setup failed\");\n  }\n\n  make_pread = aio_pread;\n  make_pwrite = aio_pwrite;\n}\n\n#else\n\nstatic void aio_setup(void)\n{\n#ifndef __MINGW32__\n  errx(1, \"asynchronous I/O not supported by this platform\");\n#endif\n}\n\n#endif\n\n#ifdef __MINGW32__\n\nint open_file(const char* path, const char* temp)\n{\n  char* file_path = (char*)path;\n  DWORD action = OPEN_ALWAYS;\n  DWORD attr = 0;\n  HANDLE h;\n\n  if (temp) {\n    int length = strlen(path) + strlen(temp) + 9;\n    file_path = malloc(length);\n\n    if (!file_path) {\n      err(2, NULL);\n    }\n\n    snprintf(file_path, length, \"%s\\\\%s\", path, temp);\n\n    if (!keep_file) {\n      strcat(file_path, \".XXXXXX\");\n      mktemp(file_path);\n      action = CREATE_NEW;\n      attr |= FILE_ATTRIBUTE_HIDDEN | FILE_FLAG_DELETE_ON_CLOSE;\n    }\n  }\n\n  if (direct) {\n    attr |= FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH;\n  }\n\n  if (randomize) {\n    attr |= FILE_FLAG_RANDOM_ACCESS;\n  } else {\n    attr |= FILE_FLAG_SEQUENTIAL_SCAN;\n  }\n\n  if (async) {\n    attr |= FILE_FLAG_OVERLAPPED;\n  }\n\n  h = CreateFile(file_path, GENERIC_READ | GENERIC_WRITE,\n                 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                 NULL, action, attr, NULL);\n\n  if (file_path != path) {\n    free(file_path);\n  }\n\n  if (h == INVALID_HANDLE_VALUE) {\n    return -1;\n  }\n\n  return _open_osfhandle((long)h, 0);\n}\n\nBOOL WINAPI sig_exit(DWORD type)\n{\n  switch (type) {\n  case CTRL_C_EVENT:\n    if (exiting) {\n      exit(4);\n    }\n\n    exiting = 1;\n    return TRUE;\n\n  default:\n    return FALSE;\n  }\n}\n\nvoid set_signal(void)\n{\n  SetConsoleCtrlHandler(sig_exit, TRUE);\n}\n\n#else /* __MINGW32__ */\n\nint open_file(const char* path, const char* temp)\n{\n  char* file_path = NULL;\n  int length, fd;\n  int flags = 0;\n#ifdef O_SYNC\n\n  if (syncio) {\n    flags |= O_SYNC;\n  }\n\n#endif\n#ifdef O_DSYNC\n\n  if (data_syncio) {\n    flags |= O_DSYNC;\n  }\n\n#endif\n\n  if (!temp) {\n    fd = open(path, (write_test ? O_RDWR : O_RDONLY) | flags);\n\n    if (fd < 0) {\n      goto out;\n    }\n\n    goto done;\n  }\n\n  length = strlen(path) + strlen(temp) + 9;\n  file_path = malloc(length);\n\n  if (!file_path) {\n    err(2, NULL);\n  }\n\n  snprintf(file_path, length, \"%s/%s\", path, temp);\n\n  if (keep_file) {\n    fd = open(file_path, O_RDWR | O_CREAT | flags, 0600);\n\n    if (fd < 0) {\n      goto out;\n    }\n\n    goto done;\n  }\n\n#ifdef O_TMPFILE\n  fd = open(path, O_RDWR | O_TMPFILE | flags, 0600);\n\n  if (fd >= 0) {\n    goto done;\n  }\n\n#endif\n  strcat(file_path, \".XXXXXX\");\n  fd = mkostemp(file_path, flags);\n\n  if (fd < 0) {\n    goto out;\n  }\n\n  if (unlink(file_path)) {\n    err(2, \"unlink \\\"%s\\\" failed\", file_path);\n  }\n\ndone:\n#ifdef HAVE_DIRECT_IO\n\n  if (direct && fcntl(fd, F_SETFL, O_DIRECT))\n    err(2, \"fcntl(O_DIRECT) failed, \"\n        \"please retry without option -D\");\n\n#endif\nout:\n\n  if (file_path != path) {\n    free(file_path);\n  }\n\n  return fd;\n}\n\nvoid sig_exit(int signo)\n{\n  (void)signo;\n\n  if (exiting) {\n    exit(4);\n  }\n\n  exiting = 1;\n}\n\nvoid set_signal(void)\n{\n  struct sigaction sa;\n  memset(&sa, 0, sizeof(sa));\n  sa.sa_handler = sig_exit;\n  sigaction(SIGINT, &sa, NULL);\n}\n\n#endif /* __MINGW32__ */\n\nstatic unsigned long long random_state[2];\n\n/* xorshift128+ */\nstatic inline unsigned long long random64(void)\n{\n  unsigned long long s1 = random_state[0];\n  const unsigned long long s0 = random_state[1];\n  random_state[0] = s0;\n  s1 ^= s1 << 23; // a\n  random_state[1] = s1 ^ s0 ^ (s1 >> 17) ^ (s0 >> 26); // b, c\n  return random_state[1] + s0;\n}\n\nstatic void random_init(void)\n{\n  srandom(now());\n  random_state[0] = random();\n  random_state[1] = random();\n  (void)random64();\n  (void)random64();\n}\n\nstatic void random_memory(void* buf, size_t len)\n{\n  unsigned long long* ptr = buf;\n  size_t words = len >> 3;\n\n  while (words--) {\n    *ptr++ = random64();\n  }\n\n  if (len & 7) {\n    unsigned long long last = random64();\n    memcpy(ptr, &last, len & 7);\n  }\n}\n\nstruct statistics {\n  long long start, finish, load_time;\n  long long count, valid, too_slow, too_fast;\n  long long min, max;\n  double sum, sum2, avg, mdev;\n  double speed, iops, load_speed, load_iops;\n  long long size, load_size;\n};\n\nstatic void start_statistics(struct statistics* s, unsigned long long start)\n{\n  memset(s, 0, sizeof(*s));\n  s->min = LLONG_MAX;\n  s->max = LLONG_MIN;\n  s->start = start;\n}\n\nstatic void add_statistics(struct statistics* s, long long val)\n{\n  s->count++;\n\n  if (val < min_valid_time) {\n    s->too_fast++;\n  } else if (val > max_valid_time) {\n    s->too_slow++;\n  } else {\n    s->valid++;\n    s->sum += val;\n    s->sum2 += (double)val * val;\n\n    if (val < s->min) {\n      s->min = val;\n    }\n\n    if (val > s->max) {\n      s->max = val;\n    }\n  }\n}\n\nstatic void merge_statistics(struct statistics* s, struct statistics* o)\n{\n  s->count += o->count;\n  s->too_fast += o->too_fast;\n  s->too_slow += o->too_slow;\n\n  if (o->valid) {\n    s->valid += o->valid;\n    s->sum += o->sum;\n    s->sum2 += o->sum2;\n\n    if (o->min < s->min) {\n      s->min = o->min;\n    }\n\n    if (o->max > s->max) {\n      s->max = o->max;\n    }\n  }\n}\n\nstatic void finish_statistics(struct statistics* s, unsigned long long finish)\n{\n  s->finish = finish;\n  s->load_time = finish - s->start;\n\n  if (s->valid) {\n    s->avg = s->sum / s->valid;\n    s->mdev = sqrt(s->sum2 / s->valid - s->avg * s->avg);\n  } else {\n    s->min = 0;\n    s->max = 0;\n  }\n\n  if (s->sum) {\n    s->iops = (double)NSEC_PER_SEC * s->valid / s->sum;\n  }\n\n  if (s->load_time) {\n    s->load_iops = (double)NSEC_PER_SEC * s->count / s->load_time;\n  }\n\n  s->speed = s->iops * size;\n  s->load_speed = s->load_iops * size;\n  s->size = s->valid * size;\n  s->load_size = s->count * size;\n}\n\nstatic void dump_statistics(struct statistics* s)\n{\n  printf(\"%lu %.0f %.0f %.0f %lu %.0f %lu %.0f %lu %lu\\n\",\n         (unsigned long)s->valid, s->sum,\n         s->iops, s->speed,\n         (unsigned long)s->min, s->avg,\n         (unsigned long)s->max, s->mdev,\n         (unsigned long)s->count,\n         (unsigned long)s->load_time);\n}\n\nint main(int argc, char** argv)\n{\n  ssize_t ret_size;\n  struct stat st;\n  int ret;\n  struct statistics part, total;\n  long long request, this_time;\n  long long time_now, time_next, period_deadline;\n  setvbuf(stdout, NULL, _IOLBF, 0);\n  parse_options(argc, argv);\n\n  if (!size) {\n    size = default_size;\n  }\n\n  if (size <= 0) {\n    errx(1, \"request size must be greater than zero\");\n  }\n\n  if (custom_interval && speed_limit) {\n    errx(1, \"speed limit and interval cannot be set simultaneously\");\n  }\n\n  if (speed_limit) {\n    interval = size * NSEC_PER_SEC / speed_limit;\n  }\n\n#ifdef MAX_RW_COUNT\n\n  if (size > MAX_RW_COUNT)\n    warnx(\"this platform supports requests %u bytes at most\",\n          MAX_RW_COUNT);\n\n#endif\n\n  if (wsize) {\n    temp_wsize = wsize;\n  } else if (size > temp_wsize) {\n    temp_wsize = size;\n  }\n\n#if !defined(HAVE_POSIX_FADVICE) && !defined(HAVE_NOCACHE_IO)\n# if defined(HAVE_DIRECT_IO)\n\n  if (!direct && !cached) {\n    warnx(\"non-cached I/O not supported, will use direct I/O\");\n    direct = cached = 1;\n  }\n\n# else\n\n  if (!cached && !write_test) {\n    warnx(\"non-cached I/O not supported by this platform\");\n    warnx(\"you can use write I/O to get reliable results\");\n    cached = 1;\n  }\n\n# endif\n#endif\n\n  if (async) {\n    aio_setup();\n  }\n\n  make_request = write_test ? make_pwrite : make_pread;\n#ifndef HAVE_DIRECT_IO\n\n  if (direct) {\n    errx(1, \"direct I/O not supported by this platform\");\n  }\n\n#endif\n#ifndef O_SYNC\n\n  if (syncio) {\n    errx(1, \"sync I/O not supported by this platform\");\n  }\n\n#endif\n#ifndef O_DSYNC\n\n  if (data_syncio) {\n    errx(1, \"data sync I/O not supported by this platform\");\n  }\n\n#endif\n\n  if (stat(path, &st)) {\n    err(2, \"stat \\\"%s\\\" failed\", path);\n  }\n\n  if (!S_ISDIR(st.st_mode) && write_test && write_test < 3) {\n    errx(2, \"think twice, then use -WWW to shred this target\");\n  }\n\n  if (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode)) {\n    if (S_ISDIR(st.st_mode)) {\n      st.st_size = offset + temp_wsize;\n    }\n\n    parse_device(st.st_dev);\n  } else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)) {\n    fd = open_file(path, NULL);\n\n    if (fd < 0) {\n      err(2, \"failed to open \\\"%s\\\"\", path);\n    }\n\n    if (get_device_size(fd, &st)) {\n      if (!S_ISCHR(st.st_mode)) {\n        err(2, \"block get size ioctl failed\");\n      }\n\n      st.st_size = offset + temp_wsize;\n      fstype = \"character\";\n      device = \"device\";\n    } else {\n      device_size = st.st_size;\n      fstype = \"block\";\n      device = \"device \";\n    }\n  } else {\n    errx(2, \"unsupported destination: \\\"%s\\\"\", path);\n  }\n\n  if (wsize > st.st_size || offset > st.st_size - wsize) {\n    errx(2, \"target is too small for this\");\n  }\n\n  if (!wsize) {\n    wsize = st.st_size - offset;\n  }\n\n  if (size > wsize) {\n    errx(2, \"request size is too big for this target\");\n  }\n\n  ret = posix_memalign(&buf, 0x1000, size);\n\n  if (ret) {\n    errx(2, \"buffer allocation failed\");\n  }\n\n  random_init();\n  random_memory(buf, size);\n\n  if (S_ISDIR(st.st_mode)) {\n    fd = open_file(path, \"ioping.tmp\");\n\n    if (fd < 0) {\n      err(2, \"failed to create temporary file at \\\"%s\\\"\", path);\n    }\n\n    if (keep_file) {\n      if (fstat(fd, &st)) {\n        err(2, \"fstat at \\\"%s\\\" failed\", path);\n      }\n\n      if (st.st_size >= offset + wsize)\n#ifndef __MINGW32__\n        if (st.st_blocks >= (st.st_size + 511) / 512)\n#endif\n          goto skip_preparation;\n    }\n\n    for (woffset = 0 ; woffset < wsize ; woffset += ret_size) {\n      ret_size = size;\n\n      if (woffset + ret_size > wsize) {\n        ret_size = wsize - woffset;\n      }\n\n      if (woffset) {\n        random_memory(buf, ret_size);\n      }\n\n      ret_size = pwrite(fd, buf, ret_size, offset + woffset);\n\n      if (ret_size <= 0) {\n        err(2, \"preparation write failed\");\n      }\n    }\n\nskip_preparation:\n\n    if (fsync(fd)) {\n      err(2, \"fsync failed\");\n    }\n  } else if (S_ISREG(st.st_mode)) {\n    fd = open_file(path, NULL);\n\n    if (fd < 0) {\n      err(2, \"failed to open \\\"%s\\\"\", path);\n    }\n  }\n\n  /* No readahead for non-cached I/O, we'll invalidate it anyway */\n  if (randomize || !cached) {\n#ifdef HAVE_POSIX_FADVICE\n    ret = posix_fadvise(fd, offset, wsize, POSIX_FADV_RANDOM);\n\n    if (ret)\n      warn(\"fadvise(RANDOM) failed, \"\n           \"operations might perform unneeded readahead\");\n\n#endif\n  }\n\n  if (!cached) {\n#ifdef HAVE_NOCACHE_IO\n    ret = fcntl(fd, F_NOCACHE, 1);\n\n    if (ret)\n      err(2, \"fcntl(F_NOCACHE) failed, \"\n          \"please retry with option -C\");\n\n#endif\n  }\n\n  set_signal();\n  request = 0;\n  woffset = 0;\n  time_now = now();\n  start_statistics(&part, time_now);\n  start_statistics(&total, time_now);\n\n  if (deadline) {\n    deadline += time_now;\n  }\n\n  period_deadline = time_now + period_time;\n  time_next = time_now;\n\n  while (!exiting) {\n    request++;\n\n    if (randomize) {\n      woffset = random64() % (wsize / size) * size;\n    }\n\n#ifdef HAVE_POSIX_FADVICE\n\n    if (!cached) {\n      ret = posix_fadvise(fd, offset + woffset, size,\n                          POSIX_FADV_DONTNEED);\n\n      if (ret)\n        err(3, \"fadvise(DONTNEED) failed, \"\n            \"please retry with option -C\");\n    }\n\n#endif\n\n    if (write_read_test) {\n      write_test = request & 1;\n      make_request = write_test ? make_pwrite : make_pread;\n    }\n\n    if (write_test) {\n      random_memory(buf, size);\n    }\n\n    this_time = now();\n    ret_size = make_request(fd, buf, size, offset + woffset);\n\n    if (ret_size < 0) {\n      if (errno != EINTR) {\n        err(3, \"request failed\");\n      }\n    } else if (ret_size < size) {\n      warnx(\"request returned less than expected: %zu\", ret_size);\n    } else if (ret_size > size) {\n      errx(3, \"request returned more than expected: %zu\", ret_size);\n    }\n\n    time_now = now();\n    time_next += interval;\n\n    if ((time_now - time_next) > 0) {\n      time_next = time_now;\n    }\n\n    this_time = time_now - this_time;\n\n    if (request == 1) {\n      /* warmup */\n      part.count++;\n    } else {\n      add_statistics(&part, this_time);\n    }\n\n    if (!quiet) {\n      print_size(ret_size);\n      printf(\" %s %s (%s %s\", write_test ? \">>>\" : \"<<<\",\n             path, fstype, device);\n\n      if (device_size) {\n        print_size(device_size);\n      }\n\n      printf(\"): request=%lu time=\", (long unsigned)request);\n      print_time(this_time);\n\n      if (request == 1) {\n        printf(\" (warmup)\");\n      } else if (this_time < min_valid_time) {\n        printf(\" (too fast)\");\n      } else if (this_time > max_valid_time) {\n        printf(\" (too slow)\");\n      } else if (part.valid > 5 && part.min < part.max) {\n        int percent = (this_time - part.min) * 100 /\n                      (part.max - part.min);\n\n        if (percent < 5) {\n          printf(\" (fast)\");\n        } else if (percent > 95) {\n          printf(\" (slow)\");\n        }\n      }\n\n      printf(\"\\n\");\n    }\n\n    if ((period_request && (part.count >= period_request)) ||\n        (period_time && (time_next >= period_deadline))) {\n      finish_statistics(&part, time_now);\n      dump_statistics(&part);\n      merge_statistics(&total, &part);\n      start_statistics(&part, time_now);\n      period_deadline = time_now + period_time;\n    }\n\n    if (!randomize) {\n      woffset += size;\n\n      if (woffset + size > wsize) {\n        woffset = 0;\n      }\n    }\n\n    if (exiting) {\n      break;\n    }\n\n    if (stop_at_request && request >= stop_at_request) {\n      break;\n    }\n\n    if (deadline && time_next >= deadline) {\n      break;\n    }\n\n    if ((time_next - time_now) > 0) {\n      long long delta = time_next - time_now;\n      interval_ts.tv_sec = delta / NSEC_PER_SEC;\n      interval_ts.tv_nsec = delta % NSEC_PER_SEC;\n      nanosleep(&interval_ts, NULL);\n    }\n  }\n\n  time_now = now();\n  finish_statistics(&part, time_now);\n  merge_statistics(&total, &part);\n  finish_statistics(&total, time_now);\n\n  if (batch_mode) {\n    dump_statistics(&total);\n    return 0;\n  }\n\n  if (quiet && (period_time || period_request)) {\n    return 0;\n  }\n\n  printf(\"\\n--- %s (%s %s\", path, fstype, device);\n\n  if (device_size) {\n    print_size(device_size);\n  }\n\n  printf(\") ioping statistics ---\\n\");\n  print_int(total.valid);\n  printf(\" requests completed in \");\n  print_time(total.sum);\n  printf(\", \");\n  print_size(total.size);\n  printf(\"%s, \", write_read_test ? \"\" :\n         write_test ? \" written\" : \" read\");\n  print_int(total.iops);\n  printf(\" iops, \");\n  print_size(total.speed);\n  printf(\"/s\\n\");\n\n  if (total.too_fast) {\n    print_int(total.too_fast);\n    printf(\" too fast, \");\n  }\n\n  if (total.too_slow) {\n    print_int(total.too_slow);\n    printf(\" too slow, \");\n  }\n\n  printf(\"generated \");\n  print_int(total.count);\n  printf(\" requests in \");\n  print_time(total.load_time);\n  printf(\", \");\n  print_size(total.load_size);\n  printf(\", \");\n  print_int(total.load_iops);\n  printf(\" iops, \");\n  print_size(total.load_speed);\n  printf(\"/s\\n\");\n  printf(\"min/avg/max/mdev = \");\n  print_time(total.min);\n  printf(\" / \");\n  print_time(total.avg);\n  printf(\" / \");\n  print_time(total.max);\n  printf(\" / \");\n  print_time(total.mdev);\n  printf(\"\\n\");\n  return 0;\n}\n"
  },
  {
    "path": "fst/tools/RainCheck.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <XrdCl/XrdClFile.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include \"common/StringSplit.hh\"\n#include \"fst/checksum/ChecksumPlugins.hh\"\n#include \"fst/layout/HeaderCRC.hh\"\n#include \"fst/layout/RaidDpLayout.hh\"\n#include \"fst/layout/ReedSLayout.hh\"\n#include <string>\n\n#define DEFAULTBUFFERSIZE (4 * 1024 * 1024)\n\nstd::pair<XrdCl::URL, std::string>\nparseLocation(std::string& location)\n{\n  size_t spos = location.rfind(\"//\");\n  std::string address = location.substr(0, spos + 1);\n  XrdCl::URL url(address);\n\n  if (!url.IsValid()) {\n    fprintf(stderr, \"URL is invalid: %s\", address.c_str());\n    exit(-1);\n  }\n\n  std::string path = location.substr(spos + 1, std::string::npos);\n  return std::make_pair(url, path);\n}\n\nstd::string\nopenOpaque(XrdCl::URL& url, std::string& filePath)\n{\n  XrdCl::FileSystem fs(url);\n  std::string request = filePath;\n\n  if (request.find('?') == std::string::npos) {\n    request += \"?mgm.pcmd=open\";\n  } else {\n    request += \"&mgm.pcmd=open\";\n  }\n\n  XrdCl::Buffer arg;\n  arg.FromString(request);\n  XrdCl::Buffer* response = nullptr;\n  XrdCl::XRootDStatus status =\n    fs.Query(XrdCl::QueryCode::OpaqueFile, arg, response);\n\n  if (!status.IsOK()) {\n    fprintf(stderr, \"Could not open file %s: %s\", filePath.c_str(),\n            status.GetErrorMessage().c_str());\n    exit(-1);\n  }\n\n  std::string res(response->GetBuffer(), response->GetSize());\n  delete response;\n  return res;\n}\n\nstd::string\ngetCheckSum(XrdCl::URL& url, std::string& filePath)\n{\n  XrdCl::FileSystem fs(url);\n  XrdCl::Buffer arg;\n  arg.FromString(filePath);\n  XrdCl::Buffer* response = nullptr;\n  XrdCl::XRootDStatus status =\n    fs.Query(XrdCl::QueryCode::Checksum, arg, response);\n\n  if (!status.IsOK()) {\n    fprintf(stderr, \"Could not open file %s: %s\", filePath.c_str(),\n            status.GetErrorMessage().c_str());\n    exit(-1);\n  }\n\n  std::string checkSumResponse = response->GetBuffer();\n  delete response;\n  auto checksum = eos::common::StringSplit(checkSumResponse, \" \");\n\n  if (checksum.size() != 2) {\n    fprintf(stderr, \"Could not get checksum of file\\n\");\n    exit(-1);\n  }\n\n  return std::string(checksum[1]);\n}\n\nbool\nisValidStripeCombination(const std::vector<std::string>& stripes,\n                         const std::string& XS, eos::fst::CheckSum* xsObj,\n                         eos::common::LayoutId::layoutid_t layout,\n                         const std::string& opaqueInfo, char* buffer)\n{\n  eos::fst::RainMetaLayout* redundancyObj = nullptr;\n\n  if (eos::common::LayoutId::GetLayoutType(layout) ==\n      eos::common::LayoutId::kRaidDP) {\n    redundancyObj = new eos::fst::RaidDpLayout(\n      nullptr, layout, nullptr, nullptr, stripes.front().c_str(), 0, false);\n  } else {\n    try {\n      redundancyObj = new eos::fst::ReedSLayout(nullptr, layout, nullptr, nullptr,\n                                                stripes.front().c_str(), 0, false);\n    } catch (const std::runtime_error& e) {\n      redundancyObj = nullptr;\n    }\n  }\n\n  if (!redundancyObj) {\n    fprintf(stderr, \"error: failed to create RAID object\\n\");\n    return false;\n  }\n\n  if (redundancyObj->OpenPio(stripes, 0, 0, opaqueInfo.c_str())) {\n    redundancyObj->Close();\n    delete redundancyObj;\n    return false;\n  }\n\n  off_t offsetXrd = 0;\n  xsObj->Reset();\n\n  while (true) {\n    int64_t nread = redundancyObj->Read(offsetXrd, buffer, DEFAULTBUFFERSIZE);\n\n    if (nread == 0) {\n      break;\n    }\n\n    if (nread == -1) {\n      fprintf(stderr, \"error: could not read from local stripes\\n\");\n      redundancyObj->Close();\n      delete redundancyObj;\n      return false;\n    }\n\n    xsObj->Add(buffer, nread, offsetXrd);\n    offsetXrd += nread;\n  }\n\n  redundancyObj->Close();\n  delete redundancyObj;\n  xsObj->Finalize();\n  return !strcmp(xsObj->GetHexChecksum(), XS.c_str());\n}\n\nvoid\ncleanup(int code, const std::vector<std::string>& stripePaths)\n{\n  for (const auto& path : stripePaths) {\n    if (std::remove(path.c_str()) != 0) {\n      fprintf(stderr, \"Could not cleanup file: %s\\n\", path.c_str());\n    }\n  }\n\n  exit(code);\n}\n\nint\ngetStripeId(const std::string& path)\n{\n  auto file{eos::fst::FileIoPlugin::GetIoObject(path)};\n\n  if (file->fileOpen(0, 0)) {\n    fprintf(stderr, \"Could not open file %s\\n\", path.c_str());\n    return -1;\n  }\n\n  auto* hd = new eos::fst::HeaderCRC(0, 0);\n  hd->ReadFromFile(file, 0);\n  int const id = hd->GetIdStripe();\n  delete hd;\n  return id;\n}\n\n\n//------------------------------------------------------------------------------\n// Main\n//------------------------------------------------------------------------------\nint\nmain(int argc, char* argv[])\n{\n  if (argc != 2) {\n    return -1;\n  }\n\n  std::string location = argv[1];\n  auto [url, filePath] = parseLocation(location);\n  std::string opaqueResponse = openOpaque(url, filePath);\n  auto* opaqueEnv = new XrdOucEnv(opaqueResponse.c_str());\n  std::string opaqueInfo = strstr(opaqueResponse.c_str(), \"&mgm.logid\");\n  eos::common::LayoutId::layoutid_t layout = opaqueEnv->GetInt(\"mgm.lid\");\n\n  if (!eos::common::LayoutId::IsRain(layout)) {\n    fprintf(stderr, \"Layout is not rain\\n\");\n    exit(-1);\n  }\n\n  int const nStripes = (int)eos::common::LayoutId::GetStripeNumber(layout) + 1;\n  int const nParityStripes =\n    (int)eos::common::LayoutId::GetRedundancyStripeNumber(layout);\n  int const nDataStripes = nStripes - nParityStripes;\n  fprintf(stdout, \"Found file with %d stripes (%d data, %d parity)\\n\", nStripes,\n          nDataStripes, nParityStripes);\n  int qpos = filePath.rfind('?');\n\n  if (qpos != STR_NPOS) {\n    opaqueInfo += \"&\";\n    opaqueInfo += filePath.substr(qpos + 1);\n    filePath.erase(qpos);\n  }\n\n  std::string pio;\n  std::string tag;\n  std::vector<std::string> stripeUrls;\n  stripeUrls.reserve(nStripes);\n\n  for (int i = 0; i < nStripes; i++) {\n    tag = SSTR(\"pio.\" << i);\n\n    if (!opaqueEnv->Get(tag.c_str())) {\n      fprintf(stderr, \"msg=\\\"empty pio url in mgm response\\\"\");\n      exit(-1);\n    }\n\n    pio = opaqueEnv->Get(tag.c_str());\n    stripeUrls.emplace_back(SSTR(\"root://\" << pio << \"/\" << filePath));\n  }\n\n  delete opaqueEnv;\n  std::string XS = getCheckSum(url, filePath);\n  eos::fst::RainMetaLayout* redundancyObj{nullptr};\n\n  try {\n    redundancyObj = new eos::fst::ReedSLayout(nullptr, layout, nullptr, nullptr,\n                                              stripeUrls.front().c_str(), 0, false);\n  } catch (const std::runtime_error& e) {\n    redundancyObj = nullptr;\n  }\n\n  if (!redundancyObj) {\n    fprintf(stderr, \"error: failed to create RAID object for read/write\\n\");\n    exit(-EINVAL);\n  }\n\n  if (redundancyObj->OpenPio(stripeUrls, 0, 0, opaqueInfo.c_str())) {\n    fprintf(stderr, \"error: can not open RAID object for read/write\\n\");\n    exit(-EIO);\n  }\n\n  char* buffer = new char[DEFAULTBUFFERSIZE];\n  int pos = filePath.rfind('/');\n  std::string fileName = filePath.substr(pos + 1);\n  std::vector<std::string> stripePaths;\n  stripePaths.reserve(nStripes);\n\n  for (int i = 0; i < nStripes; ++i) {\n    std::string dstPath =\n      SSTR(\"/var/tmp/eos-rain-check.\" << fileName << '.' << std::to_string(i));\n    int dst = open(dstPath.c_str(), O_RDWR | O_TRUNC | O_CREAT | O_CLOEXEC,\n                   S_IRUSR | S_IWUSR);\n\n    if (dst == -1) {\n      fprintf(stderr, \"Could not create destination file: %s\\n\",\n              dstPath.c_str());\n      cleanup(-1, stripePaths);\n    }\n\n    stripePaths.emplace_back(dstPath);\n    off_t offsetXrd = 0;\n\n    while (true) {\n      int64_t nread =\n        redundancyObj->ReadStripe(offsetXrd, buffer, DEFAULTBUFFERSIZE, i);\n      offsetXrd += nread;\n\n      if (nread == 0) {\n        close(dst);\n        break;\n      }\n\n      if (nread == -1) {\n        fprintf(stderr, \"stripe %d located %s has invalid data\\n\",\n                getStripeId(stripePaths[i]), stripeUrls[i].c_str());\n        close(dst);\n        break;\n      }\n\n      if (write(dst, buffer, nread) != nread) {\n        fprintf(stderr, \"Could not write to file: %s\\n\", dstPath.c_str());\n        cleanup(-1, stripePaths);\n      }\n    }\n  }\n\n  redundancyObj->Close();\n  delete redundancyObj;\n  std::vector<bool> combinations(nStripes, false);\n  std::fill(combinations.begin(), combinations.begin() + nDataStripes, true);\n  std::vector<std::string> stripeCombination(nStripes, std::string());\n  std::set<int> validStripes;\n  std::set<int> unknownStripes;\n  std::set<int> invalidStripes;\n  auto* xsObj = eos::fst::ChecksumPlugins::GetXsObj(\n                  eos::common::LayoutId::GetChecksum(layout));\n\n  if (!xsObj) {\n    fprintf(stderr, \"invalid xs_type\\n\");\n    cleanup(-1, stripePaths);\n  }\n\n  // Try to find a valid stripe combination\n  do {\n    for (int i = 0; i < nStripes; i++) {\n      if (combinations[i]) {\n        stripeCombination[i] = stripePaths[i];\n      } else {\n        stripeCombination[i] = \"\";\n      }\n    }\n\n    if (isValidStripeCombination(stripeCombination, XS, xsObj, layout,\n                                 opaqueInfo, buffer)) {\n      bool markInvalid = true;\n\n      for (int i = 0; i < nStripes; i++) {\n        if (combinations[i]) {\n          markInvalid = false;\n          validStripes.insert(i);\n        } else {\n          if (markInvalid) {\n            // All combinations involving this stripe have already been checked\n            invalidStripes.insert(i);\n          } else {\n            unknownStripes.insert(i);\n          }\n        }\n      }\n\n      break;\n    }\n  } while (std::prev_permutation(combinations.begin(), combinations.end()));\n\n  if (validStripes.empty()) {\n    fprintf(stderr,\n            \"could not find enough valid stripes to reconstruct the file\");\n    cleanup(-1, stripePaths);\n  }\n\n  // Found a valid combination, check the rest of the stripes\n  for (auto stripeId : unknownStripes) {\n    combinations.assign(nStripes, false);\n    // Try combinations with 1 unknown stripe and `nDataStripes - 1` valid\n    // stripes\n    combinations[stripeId] = true;\n    auto vsid = validStripes.begin();\n\n    for (int i = 0; i < nDataStripes - 1; i++, vsid++) {\n      combinations[*vsid] = true;\n    }\n\n    for (int i = 0; i < nStripes; i++) {\n      // Paths need to keep the same idx in `stripesPath` and `pathsCombination`\n      if (combinations[i]) {\n        stripeCombination[i] = stripePaths[i];\n      } else {\n        stripeCombination[i] = \"\";\n      }\n    }\n\n    if (isValidStripeCombination(stripeCombination, XS, xsObj, layout,\n                                 opaqueInfo, buffer)) {\n      validStripes.insert(stripeId);\n    } else {\n      invalidStripes.insert(stripeId);\n    }\n  }\n\n  for (auto i : invalidStripes) {\n    fprintf(stderr, \"stripe %d with path %s is invalid\\n\",\n            getStripeId(stripePaths[i]), stripeUrls[i].c_str());\n  }\n\n  cleanup(0, stripePaths);\n}\n"
  },
  {
    "path": "fst/tools/RainHdrDump.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file RainHdrDump.cc\n//! @author Elvin-Alin Sindrilaru - CERN\n//! @brief Tool to dump the header information of a RAIN stripe file\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/layout/HeaderCRC.hh\"\n#include \"fst/io/local/FsIo.hh\"\n#include <iostream>\n\nint main(int argc, char* argv[])\n{\n  if (argc != 2) {\n    std::cerr << \"Usage: \" << argv[0] << \" <rain_stripe_file>\" << std::endl;\n    return -1;\n  }\n\n  std::string stripe_path = argv[1];\n  struct stat st;\n\n  if (stat(stripe_path.c_str(), &st)) {\n    std::cerr << \"ERROR: No such file \" << stripe_path << std::endl;\n    return -1;\n  }\n\n  eos::fst::FsIo f(stripe_path);\n\n  if (f.fileOpen(SFS_O_RDONLY)) {\n    std::cerr << \"ERROR: Failed to open file \" << stripe_path << std::endl;\n    return -1;\n  }\n\n  eos::fst::HeaderCRC hd(0, 0);\n\n  if (hd.ReadFromFile(&f, 0)) {\n    std::cout << \"RAIN header info:\" << std::endl\n              << hd.DumpInfo() << std::endl;\n  } else {\n    std::cout << \"ERROR: Failed to read header information!\" << std::endl;\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "fst/tools/RecoverRaidDP.cc",
    "content": "//------------------------------------------------------------------------------\n// File: RecoverRaidDP.cc\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <cstdio>\n#include <cstdlib>\n#include <fst/RaidDPScan.hh>\n#include <XrdOuc/XrdOucString.hh>\n\nint\nmain(int argc, char* argv[])\n{\n  if (argc != 2) {\n    fprintf(stderr, \"usage: eos-raiddp-scan <file_name>\\n\");\n    exit(-1);\n  }\n\n  XrdOucString fileName = argv[1];\n  eos::fst::RaidDPScan* rds = new eos::fst::RaidDPScan(fileName.c_str(), false);\n\n  if (rds) {\n    eos::fst::RaidDPScan::StaticThreadProc((void*) rds);\n    delete rds;\n  }\n}\n"
  },
  {
    "path": "fst/tools/ScanXS.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ScanXS.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/ScanDir.hh\"\n#include \"fst/checksum/ChecksumPlugins.hh\"\n#include \"fst/Config.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/AssistedThread.hh\"\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n\nint\nmain(int argc, char* argv[])\n{\n  bool setxs = false;\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n  g_logging.SetLogPriority(LOG_INFO);\n  g_logging.SetUnit(\"Scandir\");\n\n  if ((argc < 2) || (argc > 3)) {\n    fprintf(stderr, \"usage: eos-scan-fs <directory> [--setxs]\\n\");\n    exit(-1);\n  }\n\n  if (argc == 3) {\n    XrdOucString set = argv[2];\n\n    if (set != \"--setxs\") {\n      fprintf(stderr, \"usage: eos-scan-fs <directory> [--setxs]\\n\");\n      exit(-1);\n    }\n\n    setxs = true;\n  }\n\n  srand((unsigned int) time(NULL));\n  eos::fst::Load fstLoad(1);\n  fstLoad.Monitor();\n  usleep(100000);\n  XrdOucString dirName = argv[1];\n  eos::fst::ScanDir* sd =\n    new eos::fst::ScanDir(dirName.c_str(), 0, &fstLoad, false, 10, 100, setxs);\n  AssistedThread thread;\n  thread.reset(&eos::fst::ScanDir::RunDiskScan, sd);\n  thread.blockUntilThreadJoins();\n  delete sd;\n}\n"
  },
  {
    "path": "fst/tools/eosfstinfo",
    "content": "#!/usr/bin/perl\nuse POSIX \":sys_wait_h\";\nuse Time::HiRes qw( usleep );\nuse File::Basename;\n\nmy $eosowner = \"daemon\";\n\nprint \"###########################\\n\";\nprint \"# <eosfstinfo> v1.0.0\\n\";\nprint \"###########################\\n\";\n\nsub usage() {\n    printf STDERR  \"usage: eosfstinfo [<host>[:<port>]] <mount-point>\\n\";\n}\n\nmy $hostname = `hostname -f`;\nchomp $hostname;\nmy $ignoreerrors=0;\nmy $allowroot=0;\n\nfor ( my $arg = 0; $arg < $#ARGV+1; $arg++) {\n    if ( ($ARGV[$arg] =~ /^-h/ ) ||\n\t ($ARGV[$arg] =~ /^--h/ )) {\n\tusage();\n\texit(-1);\n    }\n}\n       \n\nmy $mgmurl    = shift @ARGV;\nmy $mountprefix=\"\";\n\nif ( $mgmurl =~/^\\// ) {\n    my $redirector=`test -r /etc/sysconfig/eos && . /etc/sysconfig/eos && echo \\$EOS_MGM_URL`;\n    chomp $redirector;\n    if ($redirector eq \"\") {\n        $redirector=`test -r /etc/quattor_install_info && . /etc/quattor_install_info && echo \\$CDB_CLUSTER`;\n        chomp $redirector;\n        if ($redirector ne \"\") {\n            $redirector = \"root://\" . $redirector;\n        } else {\n            printf STDERR \"error: cannot automatically determine to which MGM I should connect - set it via EOS_MGM_URL in /etc/sysconfig/eos or CDB_CLUSTER variable in /etc/quattor_install_info!\\n\";\n        }\n    }\n\n    #automatic host configuration\n    $mountprefix = $mgmurl;\n    $mgmurl = $redirector;\n} else {\n    $mgmurl = \"root://\" . $mgmurl;\n    $mountprefix = shift @ARGV;\n}\n\nchomp $mgmurl;\nchomp $mountprefix;\n\n# determine the filesystem id\nmy $fsid=`cat $mountprefix/.eosfsid`;\nchomp $fsid;\n\nif ( $fsid eq \"\" ) {\n    printf STDERR \"error: cannot get the filesystem id from $mountprefix\\n\";\n    usage();\n    exit(-1);\n}\n\nif ( $mgmurl eq \"\") {\n    printf STDERR \"error: you have to provide a manager name <host>[:<port>]\\n\";\n    usage();\n    exit(-1);\n}\n\nif ( $mountprefix eq \"\") {\n    printf STDERR \"error: you have to provide a mountprefix as the first argument!\\n\";\n    usage();\n    exit(-1);\n}\n\nsystem(\"unset EOS_MGM_URL; env XrdSecPROTOCOL=sss eos -b $mgmurl fs status $fsid\");\n    \n\n\n"
  },
  {
    "path": "fst/tools/eosfstregister",
    "content": "#!/usr/bin/perl\n\nuse POSIX \":sys_wait_h\";\nuse Time::HiRes qw(usleep);\nuse File::Basename;\n\nmy $eosowner = \"daemon\";\n\nprint \"###########################\\n\";\nprint \"# <eosfstregister> v1.0.0\\n\";\nprint \"###########################\\n\";\n\nsub usage() {\n    printf STDERR \"usage: eosfstregister [-i] [-r] [--force] [--port <port>] [<host[:port]>] <mount-prefix> [<space1>:<nfilesystems1>] [<space2>:<nfilesystems2>] [-h|--help]\\n\";\n    printf STDERR \"                       -r : allows to register file systems mounted on the root partition\\n\";\n    printf STDERR \"                       -i : ignore if one of the filesystems has already a filesystem id stored and continue\\n\";\n    printf STDERR \"                  --force : delete old filesystem files and force registration of the new filesystem\\n\";\n    printf STDERR \"                   --port : the FST port\\n\";\n    printf STDERR \" hint: if <mount-prefix> ends with '/' subdirectories are scanned in this directory, \\n\";\n    printf STDERR \"       if <mount-prefix> does not end with '/' directories starting with <mount-prefix> are scanned ( e.g. /data* )\\n\";\n    printf STDERR \"       if <space>='spare' all filesystems will be registered without scheduling group and you can move them in to production via 'fs mv spare <prod-space>'\\n\";\n}\n\n#my $fst=`service eos status fst >& /dev/null`;\n#\n#if (!$fst) {\n#    printf STDERR \"warning: stopping your fst service first ...\\n\";\n#    my $fststop=`service eos stop fst>& /dev/null`;\n#}\n\nmy $hostname = `hostname -f`;\nchomp $hostname;\n\n#################################################\n# Process optional flag arguments\n#################################################\n\nmy $ignoreerrors = 0;\nmy $allowroot = 0;\nmy $force = 0;\nmy $fstport = 1095;\n\nfor (my $arg = 0; $arg < $#ARGV + 1; $arg++) {\n    if (($ARGV[$arg] =~ /^-h/) ||\n        ($ARGV[$arg] =~ /^--h/)) {\n        usage();\n        exit(-1);\n    }\n\n    if (($ARGV[$arg] =~ /--port/)) {\n        $fstport = $ARGV[$arg + 1];\n\n        if (!($fstport =~ /^\\d+$/)) {\n            printf STDERR \"error: provided port=$fstport is not valid\\n\";\n            exit(-1);\n        }\n        splice(@ARGV, $arg, 2);\n    }\n\n    if (($ARGV[$arg] =~ /-i/)) {\n        $ignoreerrors = 1;\n        splice(@ARGV, $arg, 1);\n    }\n\n    if (($ARGV[$arg] =~ /--force/)) {\n        $force = 1;\n        splice(@ARGV, $arg, 1);\n    }\n\n    if (($ARGV[$arg] =~ /-r/)) {\n        $allowroot = 1;\n        splice(@ARGV, $arg, 1);\n    }\n\n    #print \"$ARGV[$arg]\\n\";\n}\n\n#################################################\n# Process MGM URL and mount point arguments\n#################################################\n\nmy $mgmurl = shift @ARGV;\nmy $mountprefix;\n\nif ($mgmurl =~ /^\\//) {\n    my $redirector = `test -r /etc/sysconfig/eos && . /etc/sysconfig/eos && echo \\$EOS_MGM_URL`;\n    chomp $redirector;\n\n    if ($redirector eq \"\") {\n        $redirector = `test -r /etc/sysconfig/eos_env && source /etc/sysconfig/eos_env && echo \\$EOS_MGM_URL`;\n        chomp $redirector;\n\n        if ($redirector ne \"\") {\n            if ($redirector !~ /^root:\\/\\/*/) {\n                $redirector = \"root://\" . $redirector;\n            }\n        }\n        else {\n            printf STDERR \"error: cannot automatically determine to which MGM I should connect - set it via EOS_MGM_URL in /etc/sysconfig/eos or CDB_CLUSTER variable in /etc/quattor_install_info!\\n\";\n        }\n    }\n\n    # automatic host configuration\n    $mountprefix = $mgmurl;\n    $mgmurl = $redirector;\n}\nelse {\n    $mgmurl = \"root://\" . $mgmurl;\n    $mountprefix = shift @ARGV;\n}\n\nchomp $mgmurl;\nchomp $mountprefix;\n\nif ($mgmurl eq \"\") {\n    printf STDERR \"error: you have to provide a manager name <host>[:<port>]\\n\";\n    usage();\n    exit(-1);\n}\n\nif ($mountprefix eq \"\") {\n    printf STDERR \"error: you have to provide a mountprefix as the first argument!\\n\";\n    usage();\n    exit(-1);\n}\n\n#print \"Listing mount prefix <$mountprefix> ...\\n\";\n\n#####################################################\n# Validate space:nfilesystems pairs\n# and count number of filesystems to assign\n#####################################################\n\nmy $policy;\nmy $npfsdefined = 0;\nmy @spacearray;\n\ndo {\n    $policy = (shift @ARGV or \"\");\n    $policy =~ /(\\w*):(\\d*)/;\n    my $space = $1;\n    my $nspace = $2;\n\n    if (($policy ne \"\") && (($space eq \"\") || ($nspace eq \"\"))) {\n        printf STDERR \"error: policy definition seems illegal!\\n\";\n        usage();\n        exit(-3);\n    }\n\n    $npfsdefined += int($nspace);\n    for (my $i = 0; $i < $nspace; $i++) {\n        if ($space eq \"spare\") {\n            push @spacearray, \"$space\";\n        }\n        else {\n            push @spacearray, \"$space.$i\";\n        }\n    }\n} while ($policy ne \"\");\n\n\n#####################################################\n# Grep all entries matching mount-prefix.\n#\n# Scenarios:\n#  1. mountprefix ends with '/': E.g. /data01/\n#     - mountprefix stays as it is\n#\n#  2. mountprefix points to a pattern: E.g. /data01\n#     - establish basedir: /\n#     - establish search pattern: data01\n#     - add a filesystem for each directory\n#       which matches <basedir>/<pattern>\n#       E.g.: /data01 /data011 /data012\n#####################################################\n\nmy @filesystems;\nmy $pattern = \"\";\n\nif (!($mountprefix =~ /\\/$/)) {\n    $pattern = basename($mountprefix);\n    $mountprefix = dirname($mountprefix);\n}\n\nif (($mountprefix ne \"/\") && ($mountprefix =~ /\\/$/)) {\n    chop $mountprefix;\n}\n\nopen IN, \"ls -1 $mountprefix| \";\n\nwhile (<IN>) {\n    chomp $_;\n\n    if (\"$_\" eq \"lost+found\") {\n        next;\n    }\n\n    if (!-d \"$mountprefix/$_\") {\n        next;\n    }\n\n    if ($_ =~ /^\\./) {\n        next;\n    }\n\n    if (($pattern ne \"\") && (!($_ =~ /^$pattern/))) {\n        next;\n    }\n\n    #print \"Registering: $mountprefix/$_\\n\";\n    if ($mountprefix eq \"/\") {\n        push @filesystems, \"/$_\";\n    }\n    else {\n        push @filesystems, \"$mountprefix/$_\";\n    }\n}\n\nif ($#filesystems < 0) {\n    printf STDERR \"error: I didn't see any directory inside your mount prefix [ $#filesystems ]!\\n\";\n    exit(-2);\n}\n\n# Number of filesystems must equal policy definitions\n# (the sum of nfilesystems from space:nfilesystems pairs)\n\nmy $nfilesystems = $#filesystems + 1;\n\nif (\"$nfilesystems\" ne \"$npfsdefined\") {\n    printf STDERR \"error: Your policy definitions don't match the number of file systems I have found [ #filesystem = $nfilesystems #fspolicies = $npfsdefined ]\\n\";\n    usage();\n    exit(-1);\n}\n\n#####################################################\n# Register filesystems which matched the mount-prefix\n#####################################################\n\nmy $cnt = 0;\nforeach (@filesystems) {\n\n    my ($root_dev, $root_ino, $root_mode, $root_nlink, $root_uid, $root_gid, $root_rdev, $root_size, $root_atime, $root_mtime, $root_ctime, $root_blksize, $root_blocks) = stat(\"/\");\n    my ($reg_dev, $reg_ino, $reg_mode, $reg_nlink, $reg_uid, $reg_gid, $reg_rdev, $reg_size, $reg_atime, $reg_mtime, $reg_ctime, $reg_blksize, $reg_blocks) = stat(\"$_\");\n    if (!$allowroot) {\n        if (\"$root_dev\" eq \"$reg_dev\") {\n            printf STDERR \"error: filesystem $_ is on the root partition ... use '-r' if you really want to register it\\n\";\n            next;\n        }\n    }\n\n    my $uuid = \"\";\n    my $fsid = 0;\n    print $_;\n\n    if ($force) {\n        unlink \"$_/.eosfsuuid\";\n        unlink \"$_/.eosfsid\";\n    }\n\n    if (!-e \"$_/.eosfsuuid\") {\n        $uuid = `uuidgen`;\n        chomp $uuid;\n        system(\"echo $uuid > $_/.eosfsuuid; chown $eosowner.$eosowner $_/.eosfsuuid\");\n    }\n    else {\n        $uuid = `cat $_/.eosfsuuid`;\n        chomp $uuid;\n    }\n\n    if (!-e \"$_/.eosfsid\") {\n        $fsid = \"undef\";\n    }\n    else {\n        $fsid = `cat $_/.eosfsid`;\n        chomp $fsid;\n    }\n\n    if ($fsid ne \"undef\") {\n        printf STDERR \"error: filesystem $_ is already labeled with fsid=$fsid - remove the file $_/.eosfsid if you want to register with new fsid\\n\";\n        if (!$ignoreerrors) {\n            exit(-4);\n        }\n    }\n    printf \" : uuid=$uuid fsid=$fsid\\n\";\n\n    my $cmd = 0;\n    if (!($cmd = fork())) {\n        system(\"unset EOS_MGM_URL; env XrdSecPROTOCOL=sss eos -b $mgmurl fs add $uuid $hostname:$fstport $_ $spacearray[$cnt] rw\");\n        system(\"unset EOS_MGM_URL; env XrdSecPROTOCOL=sss eos -b $mgmurl fs boot $uuid\");\n        exit(0);\n    }\n\n    for (my $i = 0; $i < 40; $i++) {\n        waitpid(-1, WNOHANG);\n        if (kill 0, $cmd) {\n            usleep(250000);\n        }\n    }\n\n    $cnt++;\n}\n"
  },
  {
    "path": "fst/utils/CheckFileReadWithPattern.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n  * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/CLI11.hpp\"\n#include \"common/utils/RandUtils.hh\"\n#include <XrdCl/XrdClFile.hh>\n#include <fstream>\n#include <map>\n#include <sstream>\n\n//------------------------------------------------------------------------------\n//! Generate a map of offset and length that represent individual read requests\n//!\n//! @param max_size file maximum size\n//! @param block_sz size of a read block - corresponds to the RAIN stripe size\n//! @param num_chunks number of entries to generate < max_size / 4\n//!\n//! @return map of offset and length of the individual read requests\n//------------------------------------------------------------------------------\nstd::map<uint64_t, uint32_t>\nGenerateReadRequests(uint64_t max_size, uint32_t block_sz, uint32_t num_chunks)\n{\n  if (num_chunks >= max_size / 4) {\n    std::cout << \"error: number of chunks to be genrated needs to be \"\n              << \"smaller than file size / 4\";\n    std::exit(EINVAL);\n  }\n\n  uint64_t offset, next_offset;\n  uint32_t length;\n  std::map<uint64_t, uint32_t> chunks;\n  float mean = 1.0 * block_sz;\n  float stddev = 0.5 * block_sz;\n  bool found = false;\n\n  for (uint32_t i = 0; i < num_chunks; ++i) {\n    do {\n      found = false;\n      offset = eos::common::getRandom(0ul, max_size);\n      auto it = chunks.lower_bound(offset);\n\n      if (it != chunks.end()) {\n        next_offset = it->first;\n\n        if (it->first == offset) {\n          found = true;\n        } else {\n          if (it != chunks.begin()) {\n            --it;\n\n            if ((it->first <= offset) && (it->first + it->second >= offset)) {\n              found = true;\n            }\n          }\n        }\n      }\n    } while (found);\n\n    do {\n      length = std::round(eos::common::getRandomNormal(mean, stddev));\n      auto it = chunks.lower_bound(offset);\n\n      if (it != chunks.end()) {\n        next_offset = it->first;\n        // Make sure we don't overlap with the next chunk\n        found = (offset + length >= next_offset);\n      } else {\n        found = false;\n      }\n\n      // Make sure we don't go beyond the end of file\n      if (!found) {\n        found = (offset + length >= max_size);\n      }\n    } while (found);\n\n    chunks[offset] = length;\n  }\n\n  return chunks;\n}\n\n\n//------------------------------------------------------------------------------\n//! Write the read requests in \"offset length\" format to the given file\n//!\n//! @param fpattern pattern file where to write the map contents\n//------------------------------------------------------------------------------\nvoid\nWritePatternToFile(const std::string& fpattern,\n                   const std::map<uint64_t, uint32_t> chunks)\n{\n  std::ofstream file(fpattern);\n\n  for (const auto& chunk : chunks) {\n    file << chunk.first << \" \" << chunk.second << std::endl;\n  }\n}\n\n\n//------------------------------------------------------------------------------\n//! Read the individual chunk requests from the given file. Data is organized\n//! in two colums, the first one represents the offset and the second the length\n//! of the individual read requests.\n//!\n//! @param fpattern pattern file path\n//!\n//! @return map of offset and length of the individual read requests\n//------------------------------------------------------------------------------\nstd::map<uint64_t, uint32_t>\nGetReadRequestsFromFile(const std::string fpattern)\n{\n  std::string line;\n  std::map<uint64_t, uint32_t> chunks;\n  std::ifstream file(fpattern);\n  std::string soff, slen;\n  uint64_t offset;\n  uint32_t length;\n\n  if (file.is_open()) {\n    while (std::getline(file, line)) {\n      std::istringstream iss(line);\n      iss >> soff;\n      iss >> slen;\n\n      try {\n        offset = std::stoull(soff);\n        length = std::stoul(slen);\n      } catch (...) {\n        std::cerr << \"error: failed to parse offset/length from -\" << line\n                  << std::endl;\n        std::exit(EINVAL);\n      }\n\n      chunks[offset] = length;\n    }\n  }\n\n  return chunks;\n}\n\n//------------------------------------------------------------------------------\n//! Check match between the reference file and the checked file\n//!\n//! @param fref path to the reference file\n//! @param fcehck path to the file to be checked\n//! @param chunks map of (offset, length) pairs that will make the readv req.\n//!\n//! @return true if successful, otherwise false\n//------------------------------------------------------------------------------\nbool\nCheckMatch(const std::string& fref, const std::string fcheck,\n           const std::map<uint64_t, uint32_t> chunks, uint32_t block_sz)\n{\n  size_t readv_sz = 5;\n  std::string ref_buff;\n  std::string check_buff;\n  ref_buff.reserve(2 * block_sz * readv_sz);\n  check_buff.reserve(2 * block_sz * readv_sz);\n  XrdCl::URL ref_url(fref);\n  XrdCl::URL check_url(fcheck);\n\n  if (!ref_url.IsValid() || !check_url.IsValid()) {\n    std::cerr << \"error: invalid reference or check URL - \"\n              << fref << \" / \" << fcheck << std::endl;\n    return false;\n  }\n\n  XrdCl::File ref_file, check_file;\n  XrdCl::XRootDStatus st = ref_file.Open(fref, XrdCl::OpenFlags::Read);\n\n  if (!st.IsOK()) {\n    std::cerr << \"error: failed to open reference file - \" << fref << std::endl;\n    return false;\n  }\n\n  st = check_file.Open(fcheck, XrdCl::OpenFlags::Read);\n\n  if (!st.IsOK()) {\n    std::cerr << \"error: failed to open check file - \" << fcheck << std::endl;\n    return false;\n  }\n\n  std::map<uint64_t, uint32_t> readv_req;\n\n  for (const auto& chunk : chunks) {\n    if (readv_req.size() < readv_sz) {\n      readv_req.insert(chunk);\n    } else {\n      uint64_t total_sz = 0ull;\n      XrdCl::ChunkList xrd_chunks;\n\n      for (const auto& elem : readv_req) {\n        //std::cout << \"off: \" << elem.first << \" len: \" << elem.second << std::endl;\n        xrd_chunks.emplace_back(elem.first, elem.second);\n        total_sz += elem.second;\n      }\n\n      //std::cout << \"--------------------------\" << std::endl;\n      readv_req.clear();\n      XrdCl::VectorReadInfo* vinfo_raw = new XrdCl::VectorReadInfo();\n      std::unique_ptr<XrdCl::VectorReadInfo> vinfo;\n      vinfo.reset(vinfo_raw);\n      st = ref_file.VectorRead(xrd_chunks, ref_buff.data(), vinfo_raw);\n\n      if (!st.IsOK()) {\n        std::cerr << \"error: failed readv from reference file\" << std::endl\n                  << \"err_msg: \" << st.ToStr() << std::endl;\n        return false;\n      }\n\n      st = check_file.VectorRead(xrd_chunks, check_buff.data(), vinfo_raw);\n\n      if (!st.IsOK()) {\n        std::cerr << \"error: failed readv from checked file\" << std::endl;\n        return false;\n      }\n\n      if (strncmp(ref_buff.data(), check_buff.data(), total_sz) != 0) {\n        std::cerr << \"error: mismatch in reference vs. checked buffer \" << std::endl;\n        return false;\n      }\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n//! Get XRootD file size\n//!\n//! @param fref path to XRootD file\n//!\n//! @return file size or 0 if failure\n//------------------------------------------------------------------------------\nuint64_t\nGetXrdFileSize(const std::string& fref)\n{\n  uint64_t size = 0ull;\n  XrdCl::URL url(fref);\n\n  if (!url.IsValid()) {\n    std::cerr << \"error: invalid XRootD URL - \" << fref << std::endl;\n    return size;\n  }\n\n  XrdCl::File file;\n  auto st = file.Open(fref, XrdCl::OpenFlags::Read);\n\n  if (!st.IsOK()) {\n    std::cerr << \"error: failed to open file - \" << fref << std::endl;\n    return size;\n  }\n\n  XrdCl::StatInfo* info_raw = new XrdCl::StatInfo();\n  std::unique_ptr<XrdCl::StatInfo> info;\n  info.reset(info_raw);\n  st = file.Stat(true, info_raw);\n\n  if (!st.IsOK()) {\n    std::cerr << \"error: failed to stat file - \" << fref << std::endl;\n    return size;\n  }\n\n  size = info_raw->GetSize();\n  return size;\n}\n\n\n//------------------------------------------------------------------------------\n//! Main function\n//------------------------------------------------------------------------------\nint main(int argc, char* argv[])\n{\n  CLI::App app(\"Tool to do vector reads and check \");\n  uint32_t num_chunks = 100;\n  uint32_t sz_chunk = 128 * 1024;\n  std::string fref, fcheck, fpattern;\n  bool output_pattern = false;\n  app.add_option(\"-r,--reference_file\", fref,\n                 \"File path used as reference\")->required();\n  app.add_option(\"-c,--check_file\", fcheck,\n                 \"File path used for testing\")->required();\n  app.add_option(\"-n,--num_chunks\", num_chunks,\n                 \"Number of generated chunks [default 100]\");\n  app.add_option(\"-s,--size_chunk\", sz_chunk, \"Average size of the chunks\");\n  app.add_option(\"-p,--pattern_file\", fpattern,\n                 \"File holding the read pattern (offset -> length)\");\n  app.add_flag(\"-o,--output_pattern\", output_pattern,\n               \"Write generated pattern to file\");\n\n  // Parse the inputs\n  try {\n    app.parse(argc, argv);\n  } catch (const CLI::ParseError& e) {\n    return app.exit(e);\n  }\n\n  uint64_t file_size = GetXrdFileSize(fref);\n\n  if (file_size == 0) {\n    std::cerr << \"error: failed to stat reference file - \" << fref << std::endl;\n    std::exit(EIO);\n  }\n\n  std::map<uint64_t, uint32_t> chunks;\n\n  if (output_pattern) {\n    if (fpattern.empty()) {\n      std::cerr << \"error: no output pattern file specified\" << std::endl;\n      std::exit(EINVAL);\n    }\n\n    chunks = GenerateReadRequests(file_size, sz_chunk, num_chunks);\n    std::cout << \"Write pattern to file: \" << fpattern << std::endl;\n    WritePatternToFile(fpattern, chunks);\n    return 0;\n  }\n\n  if (fpattern.empty()) {\n    chunks = GenerateReadRequests(file_size, sz_chunk, num_chunks);\n  } else {\n    chunks = GetReadRequestsFromFile(fpattern);\n  }\n\n  if (CheckMatch(fref, fcheck, chunks, sz_chunk)) {\n    std::cout << \"info: readv requests matched!\" << std::endl;\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "fst/utils/CreateFileWithPattern.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n  * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/CLI11.hpp\"\n#include <fstream>\n#include <stdlib.h>\n#include <stdio.h>\n#include <unistd.h>\n\n\nstd::string GetFilePath(const std::string& fname)\n{\n  std::string fpath;\n\n  // Absolute path with filename specified\n  if (!fname.empty() && (*fname.begin() == '/') && (*fname.rbegin() != '/')) {\n    return fname;\n  }\n\n  // Absolute path without filename\n  if (!fname.empty() && (*fname.begin() == '/') && (*fname.rbegin() == '/')) {\n    fpath = fname;\n    char tmpfile[1024];\n    snprintf(tmpfile, sizeof(tmpfile), \"%seosfp.XXXXXX\", fpath.c_str());\n    int tmp_fd = mkstemp(tmpfile);\n\n    if (tmp_fd == -1) {\n      std::cerr << \"error: failed to create file\" << std::endl;\n      std::exit(EIO);\n    }\n\n    (void) close(tmp_fd);\n    fpath = tmpfile;\n    return fpath;\n  }\n\n  // Just a filename specified, we put it in /tmp/\n  if (!fname.empty() && (*fname.begin() != '/')) {\n    fpath = \"/tmp/\";\n    fpath += fname;\n    return fpath;\n  }\n\n  // If nothing specified create a path in /tmp/\n  char tmpfile[] = \"/tmp/eosfp.XXXXXX\";\n  int tmp_fd = mkstemp(tmpfile);\n\n  if (tmp_fd == -1) {\n    std::cerr << \"error: failed to create file\" << std::endl;\n    std::exit(EIO);\n  }\n\n  (void) close(tmp_fd);\n  fpath = tmpfile;\n  return fpath;\n}\n\nint CreateFileWithPattern(const std::string& fpath, const std::string& pattern,\n                          uint64_t size)\n{\n  std::cout << \"info: writing to file \" << fpath << std::endl;\n  uint64_t sz_pattern = pattern.length();\n  uint64_t sz_file = 0ull;\n  std::ofstream file(fpath);\n\n  while (sz_file < size) {\n    file << pattern;\n    sz_file += sz_pattern;\n\n    if ((size > sz_file) && (size - sz_file < sz_pattern)) {\n      file.write(pattern.c_str(), size - sz_file);\n      break;\n    }\n  }\n\n  return 0;\n}\n\n\nint main(int argc, char* argv[])\n{\n  CLI::App app(\"Tool to create a file with a certain pattern\");\n  std::string fname;\n  std::string pattern;\n  uint64_t size = 0ull;\n  app.add_option(\"-s,--size\", size, \"File size\")->required();\n  app.add_option(\"-p,--pattern\", pattern, \"Data pattern\")->required();\n  app.add_option(\"-f,--filename\", fname, \"File pathname\");\n\n  // Parse the inputs\n  try {\n    app.parse(argc, argv);\n  } catch (const CLI::ParseError& e) {\n    return app.exit(e);\n  }\n\n  fname = GetFilePath(fname);\n  return CreateFileWithPattern(fname, pattern, size);\n}\n"
  },
  {
    "path": "fst/utils/DiskMeasurements.cc",
    "content": "//------------------------------------------------------------------------------\n// File: DiskMeasurements.cc\n// Author: Elvin Sindrilaru - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/utils/DiskMeasurements.hh\"\n#include \"common/BufferManager.hh\"\n#include \"common/Logging.hh\"\n#include \"common/utils/RandUtils.hh\"\n#include <chrono>\n#include <cstdint>\n#include <cstring>\n#include <fcntl.h>\n#include <iostream>\n#include <linux/fs.h>\n#include <sys/ioctl.h>\n#include <sys/stat.h>\n#include <sys/sysmacros.h>\n#include <unistd.h>\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Generate random data\n//------------------------------------------------------------------------------\nvoid\nGenerateRandomData(char* data, size_t length)\n{\n  std::ifstream urandom(\"/dev/urandom\", std::ios::in | std::ios::binary);\n  urandom.read(data, length);\n  urandom.close();\n}\n\n//------------------------------------------------------------------------------\n// Create file path with given size\n//------------------------------------------------------------------------------\nbool FillFileGivenSize(int fd, size_t length)\n{\n  using namespace eos::common;\n  int retc = 0, nwrite = 0;\n  const size_t sz {4 * 1024 * 1024};\n  auto buffer = GetAlignedBuffer(sz);\n  GenerateRandomData(buffer.get(), sz);\n\n  while (length > 0) {\n    nwrite = (length < sz) ? length : sz;\n    retc = write(fd, buffer.get(), nwrite);\n\n    if (retc != nwrite) {\n      return false;\n    }\n\n    length -= nwrite;\n  }\n\n  fsync(fd);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Create random temporary file in given location\n//------------------------------------------------------------------------------\nstd::string MakeTemporaryFile(std::string base_path)\n{\n  // Absolute base path  specified\n  if (base_path.empty() || (*base_path.begin() != '/')) {\n    eos_static_err(\"msg=\\\"base path needs to a an absolute path\\\" base_path=%s\",\n                   base_path.c_str());\n    return \"\";\n  }\n\n  // Make sure path is / terminated\n  if (*base_path.rbegin() != '/') {\n    base_path += '/';\n  }\n\n  char tmp_path[1024];\n  snprintf(tmp_path, sizeof(tmp_path), \"%sfst.ioping.XXXXXX\", base_path.c_str());\n  int tmp_fd = mkstemp(tmp_path);\n\n  if (tmp_fd == -1) {\n    eos_static_crit(\"%s\", \"msg=\\\"failed to create temporary file!\\\"\");\n    return \"\";\n  }\n\n  (void) close(tmp_fd);\n  return tmp_path;\n}\n\n//------------------------------------------------------------------------------\n// Get block device for a given path\n//------------------------------------------------------------------------------\nstd::string GetDevicePath(const std::string& path)\n{\n  struct stat st;\n\n  if (stat(path.c_str(), &st)) {\n    return \"\";\n  }\n\n  dev_t dev = st.st_dev;\n  FILE* file = fopen(\"/proc/self/mountinfo\", \"r\");\n\n  if (!file) {\n    file = fopen(\"/proc/mounts\", \"r\");\n  }\n\n  if (!file) {\n    return \"\";\n  }\n\n  char* line = NULL;\n  size_t len = 0;\n  unsigned int major, minor;\n  std::string device_path;\n\n  while (getline(&line, &len, file) != -1) {\n    // Try parsing mountinfo format first: \"id parent major:minor ...\"\n    // If not, it might be /proc/mounts format, but /proc/self/mountinfo is standard on modern linux\n    // Scan for major:minor\n    int num_scanned = sscanf(line, \"%*d %*d %u:%u\", &major, &minor);\n\n    if (num_scanned == 2) {\n      if (makedev(major, minor) == dev) {\n        // Found it in mountinfo format\n        // The device is usually the field after \" - \"\n        // Format: ... - <fstype> <device> <options>\n        char* sep = strstr(line, \" - \");\n\n        if (sep) {\n          char* fstype = strtok(sep + 3, \" \");\n          char* dev_str = strtok(NULL, \" \");\n          (void) fstype;\n\n          if (dev_str) {\n            device_path = dev_str;\n            break;\n          }\n        }\n      }\n    } else {\n      // Fallback for simple /proc/mounts format: <device> <mountpoint> ...\n      // We need to stat the mountpoint to see if it matches our dev\n      char dev_str[1024];\n      char mount_str[1024];\n\n      if (sscanf(line, \"%1023s %1023s\", dev_str, mount_str) == 2) {\n        struct stat mp_st;\n\n        if (stat(mount_str, &mp_st) == 0) {\n          if (mp_st.st_dev == dev) {\n            // This mountpoint corresponds to our device\n            // But wait, st_dev of a file IS the device ID of the filesystem it is on.\n            // So if st_dev matches mp_st.st_dev, then dev_str is likely our device.\n            device_path = dev_str;\n            break;\n          }\n        }\n      }\n    }\n  }\n\n  free(line);\n  fclose(file);\n  return device_path;\n}\n\n//------------------------------------------------------------------------------\n// Get file/device size\n//------------------------------------------------------------------------------\nuint64_t GetBlkSize(int fd)\n{\n  struct stat st;\n\n  if (fstat(fd, &st)) {\n    return 0;\n  }\n\n  if (S_ISBLK(st.st_mode)) {\n    uint64_t size = 0;\n\n    if (ioctl(fd, BLKGETSIZE64, &size) == 0) {\n      return size;\n    }\n  }\n\n  return st.st_size;\n}\n\n//------------------------------------------------------------------------------\n// Get IOPS measurement for the given path\n//------------------------------------------------------------------------------\nint ComputeIops(int fd, uint64_t rd_buf_size, std::chrono::seconds timeout)\n{\n  using namespace eos::common;\n  using namespace std::chrono;\n  int IOPS = -1;\n  uint64_t fn_size = GetBlkSize(fd);\n\n  if (fn_size == 0) {\n    std::cerr << \"err: failed to get file size fd=\" << fd << std::endl;\n    eos_static_err(\"msg=\\\"failed to get file size\\\" fd=%i\", fd);\n    return IOPS;\n  }\n\n  auto buf = GetAlignedBuffer(rd_buf_size);\n  // Get a uniform int distribution for offset generation\n  int iterations = 10000;\n  int actual_iter = 0;\n  uint64_t offset = 0ull;\n  microseconds duration {0};\n  time_point<high_resolution_clock> start, end;\n\n  for (; actual_iter < iterations; ++actual_iter) {\n    // Generate offset 4kB aligned inside the given file size\n    offset = (((fn_size * eos::common::getRandom(0, 1024)) >> 10) >> 12) << 12;\n    start = high_resolution_clock::now();\n\n    if (pread(fd, buf.get(), rd_buf_size, offset) == -1) {\n      std::cerr << \"error: failed to read at offset=\" << offset << std::endl;\n      eos_static_err(\"msg=\\\"failed read\\\" offset=%llu\", offset);\n      return IOPS;\n    }\n\n    end = high_resolution_clock::now();\n    duration += duration_cast<microseconds>(end - start);\n\n    if (actual_iter % 10 == 0) {\n      if (duration.count() > timeout.count() * 1000000) {\n        break;\n      }\n    }\n  }\n\n  IOPS = (actual_iter * 1000000.0) / duration.count();\n  return IOPS;\n}\n\n//------------------------------------------------------------------------------\n// Get disk bandwidth for the given path\n//------------------------------------------------------------------------------\nint ComputeBandwidth(int fd, uint64_t rd_buf_size, std::chrono::seconds timeout)\n{\n  using namespace eos::common;\n  using namespace std::chrono;\n  int bandwidth = -1;\n  uint64_t fn_size = GetBlkSize(fd);\n\n  if (fn_size == 0) {\n    std::cerr << \"err: failed to get file size fd=\" << fd << std::endl;\n    eos_static_err(\"msg=\\\"failed to get file size\\\" fd=%i\", fd);\n    return bandwidth;\n  }\n\n  auto buf = GetAlignedBuffer(rd_buf_size);\n  uint64_t max_read = 1 << 28; // 256 MB\n  // Randomize start offset if file is large enough\n  uint64_t offset = 0;\n\n  if (fn_size > max_read) {\n    ;\n    // Align to rd_buf_size (4MB)\n    uint64_t max_blocks = (fn_size - max_read) / rd_buf_size;\n    offset = eos::common::getRandom(0ul, max_blocks) * rd_buf_size;\n  }\n\n  uint64_t start_offset = offset;\n  uint64_t end_offset = offset + max_read;\n\n  if (end_offset > fn_size) {\n    end_offset = fn_size;\n  }\n\n  time_point<high_resolution_clock> start, end;\n  start = high_resolution_clock::now();\n\n  while (offset < end_offset) {\n    if (pread(fd, buf.get(), rd_buf_size, offset) == -1) {\n      std::cerr << \"error: failed to read at offset=\" << offset << std::endl;\n      eos_static_err(\"msg=\\\"failed read\\\" offset=%llu\", offset);\n      return bandwidth;\n    }\n\n    offset += rd_buf_size;\n\n    if ((offset & (eos::common::MB - 1)) == 0) {\n      if (duration_cast<seconds>(high_resolution_clock::now() - start) > timeout) {\n        break;\n      }\n    }\n  }\n\n  end = high_resolution_clock::now();\n  auto duration = duration_cast<microseconds> (end - start).count();\n  bandwidth = (((offset - start_offset) >> 20) * 1000000.0) / duration;\n  return bandwidth;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/utils/DiskMeasurements.hh",
    "content": "//------------------------------------------------------------------------------\n// File: DiskMeasurements.hh\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/Namespace.hh\"\n#include <string>\n#include <cstdint>\n#include <chrono>\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Generate random data\n//!\n//! @param data buffer to store data\n//! @param length length of the data generated\n//------------------------------------------------------------------------------\nvoid GenerateRandomData(char* data, uint64_t length);\n\n//------------------------------------------------------------------------------\n//! Create file path with given size\n//!\n//! @param fd file descriptor with flags O_RDRW | O_TRUNC | O_DIRECT | O_SYNC\n//! @param lenght lenght of the generated file\n//!\n//! @return true if successful, otherwise false\n//------------------------------------------------------------------------------\nbool FillFileGivenSize(int fd, uint64_t length);\n\n//------------------------------------------------------------------------------\n//! Create random temporary file in given location\n//!\n//! @param base_path base path for new file\n//!\n//! @return string file path if successful, otherwise empty string\n//------------------------------------------------------------------------------\nstd::string MakeTemporaryFile(std::string base_path);\n\n//------------------------------------------------------------------------------\n//! Get block device for a given path\n//!\n//! @param path input path\n//!\n//! @return device path if found, otherwise empty string\n//------------------------------------------------------------------------------\nstd::string GetDevicePath(const std::string& path);\n\n//------------------------------------------------------------------------------\n//! Get file/device size\n//!\n//! @param fd file descriptor\n//!\n//! @return size of the file or device\n//------------------------------------------------------------------------------\nuint64_t GetBlkSize(int fd);\n\n//------------------------------------------------------------------------------\n//! Get IOPS measurement using the given file descriptor\n//!\n//! @param fd file descriptor with flags O_RDRW | O_TRUNC | O_DIRECT | O_SYNC\n//! @param rd_buf_size size of buffer used for read operations [default 4096]\n//! @param timeout max time this computation can run for\n//!\n//! @return IOPS measurement\n//------------------------------------------------------------------------------\nint ComputeIops(int fd, uint64_t rd_buf_size = 4096,\n                std::chrono::seconds timeout = std::chrono::seconds(5));\n\n//------------------------------------------------------------------------------\n//! Get disk bandwidth using the given file descriptor\n//!\n//! @param fd file descriptor with flags O_RDRW | O_TRUNC | O_DIRECT | O_SYNC\n//! @param rd_buf_size size of buffer used for read operations [default 4096]\n//! @param timeout max time this computation can run for\n//!\n//! @return disk bandwidth measurement\n//------------------------------------------------------------------------------\nint ComputeBandwidth(int fd, uint64_t rd_buf_size = 4096,\n                     std::chrono::seconds timeout = std::chrono::seconds(5));\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/utils/DiskMeasurementsMain.cc",
    "content": "//------------------------------------------------------------------------------\n// File: DiskMeasurements.cc\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/utils/DiskMeasurements.hh\"\n#include \"common/Logging.hh\"\n#include <chrono>\n#include <iostream>\n#include <fcntl.h>\n#include <sys/types.h>\n#include <unistd.h>\n\n//------------------------------------------------------------------------------\n// Main\n//------------------------------------------------------------------------------\nint main(int argc, char** argv)\n{\n  if (argc < 2) {\n    std::cerr << \"error: path argument required\" << std::endl;\n    return -1;\n  }\n\n  std::string input_path = argv[1];\n  std::string device_path = eos::fst::GetDevicePath(input_path);\n  std::string measure_path = device_path.empty() ? input_path : device_path;\n\n  if (device_path.empty()) {\n    std::cerr << \"warning: could not resolve block device for \" << input_path <<\n              \", using path as is.\" << std::endl;\n  } else {\n    std::cout << \"info: resolved \" << input_path << \" to device \" << device_path <<\n              std::endl;\n  }\n\n  // Open the file for direct access\n  int fd = open(measure_path.c_str(), O_RDONLY | O_DIRECT);\n\n  if (fd == -1) {\n    std::cerr << \"err: failed to open file/device \" << measure_path << std::endl;\n    eos_static_err(\"msg=\\\"failed to open file/device\\\" path=%s\",\n                   measure_path.c_str());\n    return -1;\n  }\n\n  uint64_t rd_buf_size = 4 * (1 << 20); // 4MB\n  std::cout << \"Path=\" << measure_path << std::endl\n            << \"IOPS=\" << eos::fst::ComputeIops(fd) << std::endl\n            << \"BW=\" << eos::fst::ComputeBandwidth(fd, rd_buf_size) << \" MB/s\"\n            << std::endl;\n  (void) close(fd);\n  return 0;\n}\n"
  },
  {
    "path": "fst/utils/FSPathHandler.cc",
    "content": "// /************************************************************************\n//  * EOS - the CERN Disk Storage System                                   *\n//  * Copyright (C) 2022 CERN/Switzerland                           *\n//  *                                                                      *\n//  * This program is free software: you can redistribute it and/or modify *\n//  * it under the terms of the GNU General Public License as published by *\n//  * the Free Software Foundation, either version 3 of the License, or    *\n//  * (at your option) any later version.                                  *\n//  *                                                                      *\n//  * This program is distributed in the hope that it will be useful,      *\n//  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n//  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n//  * GNU General Public License for more details.                         *\n//  *                                                                      *\n//  * You should have received a copy of the GNU General Public License    *\n//  * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n//  ************************************************************************\n//\n\n#include \"fst/utils/FSPathHandler.hh\"\n#include \"common/Logging.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/StringSplit.hh\"\n#include \"common/StringUtils.hh\"\n\nnamespace eos::fst\n{\n\neos::common::FileSystem::fsid_t\nFSPathHandler::GetFsid(std::string_view path, bool at_root)\n{\n  eos::common::FileSystem::fsid_t fsid;\n  std::string err_msg;\n  std::string fsidpath = at_root ? eos::common::GetRootPath(path) : std::string(\n                           path);\n  fsidpath += \"/.eosfsid\";\n  std::string sfsid;\n  eos::common::StringConversion::LoadFileIntoString(fsidpath.c_str(), sfsid);\n\n  if (!eos::common::StringToNumeric(sfsid, fsid, (uint32_t)0, &err_msg)) {\n    eos_static_crit(\"msg=\\\"Unable to obtain FSID from path=\\\"%s\", path.data());\n    // TODO: this is exceptional, throw an error!\n  }\n\n  return fsid;\n}\n\nstd::string\nFSPathHandler::GetPath(eos::common::FileId::fileid_t fid,\n                       eos::common::FileSystem::fsid_t fsid)\n{\n  return eos::common::FileId::FidPrefix2FullPath(eos::common::FileId::Fid2Hex(\n           fid).c_str(),\n         GetFSPath(fsid).c_str());\n}\n\nstd::string\nFixedFSPathHandler::GetFSPath(eos::common::FileSystem::fsid_t fsid)\n{\n  return fs_path;\n}\n\n} // namespace eos::fst\n"
  },
  {
    "path": "fst/utils/FSPathHandler.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************\n */\n\n#pragma once\n#include \"common/FileId.hh\"\n#include \"common/FileSystem.hh\"\n#include <string_view>\n#include <string>\n\nnamespace eos::fst\n{\n\n\nstruct FSPathHandler {\n  virtual std::string GetFSPath(eos::common::FileSystem::fsid_t fsid) = 0;\n  virtual std::string GetPath(eos::common::FileId::fileid_t fid,\n                              eos::common::FileSystem::fsid_t fsid);\n  static eos::common::FileSystem::fsid_t GetFsid(std::string_view path,\n      bool at_root = false);\n  virtual ~FSPathHandler() = default;\n};\n\nclass FixedFSPathHandler : public FSPathHandler\n{\npublic:\n  FixedFSPathHandler(std::string_view _fs_path)\n    : FSPathHandler(), fs_path(_fs_path) {}\n\n  std::string GetFSPath(eos::common::FileSystem::fsid_t fsid) override;\n\n\nprivate:\n  std::string fs_path;\n};\n\ninline std::unique_ptr<FSPathHandler>\nmakeFSPathHandler(std::string_view path)\n{\n  return std::make_unique<FixedFSPathHandler>(path);\n}\n\n} // namespace eos::fst\n"
  },
  {
    "path": "fst/utils/FTSWalkTree.hh",
    "content": "// ----------------------------------------------------------------------\n// File: WalkDirTree\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#pragma once\n\n#include \"fst/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include \"common/StringUtils.hh\"\n#include <fts.h>\n#include <utility>\n#include <type_traits>\n#include <system_error>\n#include <vector>\n\nEOSFSTNAMESPACE_BEGIN\n\nstatic constexpr std::string_view XSMAP_SUFFIX = \".xsmap\";\nstatic constexpr std::string_view SCRUB_PREFIX = \"/scrub.\";\n\ninline bool exclude_xs_and_scrub(std::string_view filename)\n{\n  return (common::endsWith(filename, XSMAP_SUFFIX) ||\n          (filename.find(SCRUB_PREFIX) != std::string::npos));\n}\n\n// A function to walk the dir tree and apply a function with arguments\n// It is necessary that the function's first argument is a const char* path\n// This function uses FTS to walk through directory entries and\n// doesn't follow symlinks and only operates on regular files atm\ntemplate <typename ExcludeFn, typename PathOp>\nuint64_t\nWalkDirTree(std::vector<char*>&& paths, ExcludeFn exclude_fn, PathOp path_op,\n            std::error_code& ec)\n{\n  FTS* tree = fts_open(paths.data(), FTS_NOCHDIR, 0);\n\n  if (!tree) {\n    eos_static_err(\"msg=\\\"fts_open failed\\\" errno=%d\", errno);\n    ec = std::make_error_code(static_cast<std::errc>(errno));\n    return 0;\n  }\n\n  uint64_t cnt {0};\n  FTSENT* node;\n\n  while ((node = fts_read(tree))) {\n    if (node->fts_level > 0 && node->fts_name[0] == '.') {\n      fts_set(tree, node, FTS_SKIP);\n    } else {\n      if (node->fts_info == FTS_F) {\n        if (!exclude_fn(node->fts_accpath)) {\n          path_op(node->fts_path);\n          ++cnt;\n        }\n      }\n    }\n  }\n\n  if (fts_close(tree)) {\n    eos_static_err(\"msg=\\\"fts_close failed\\\" errno=%d\", errno);\n    ec = std::make_error_code(static_cast<std::errc>(errno));\n  }\n\n  return cnt;\n}\n\n//------------------------------------------------------------------------------\n//! A function useful for walking FST trees, where xsmap and scrub files are\n//! usually excluded. This variant expects a member function to be applied\n//! across the tree.\n//------------------------------------------------------------------------------\ntemplate <typename UnaryOp>\nuint64_t\nWalkFSTree(std::string path, UnaryOp&& op, std::error_code& ec)\n{\n  return WalkDirTree({path.data(), nullptr},\n                     exclude_xs_and_scrub,\n                     std::forward<UnaryOp>(op),\n                     ec);\n}\n\n//------------------------------------------------------------------------------\n//! Method to travers the subtree and check the file if they satisfy a certain\n//! condition. The files are counted and only the ones with the index matching\n//! the given ones are checked.\n//!\n//! @param path path of the sub-tree to check\n//! @param check_fn operation to be applied to individual files\n//! @param exclude_fn operator that should skip check the file if it returns\n//!                   true\n//! @param match_indexes set of indexes to check inside the subtree\n//------------------------------------------------------------------------------\ntemplate <typename CheckFn, typename ExcludeFn>\nbool\nWalkFsTreeCheckCond(std::vector<char*>&& paths,\n                    CheckFn check_fn,\n                    ExcludeFn exclude_fn,\n                    const std::set<uint64_t>& match_indexes)\n{\n  FTS* tree = fts_open(paths.data(), FTS_NOCHDIR, 0);\n\n  if (!tree) {\n    eos_static_err(\"msg=\\\"fts_open failed\\\" path=\\\"%s\\\" errno=%d\",\n                   paths.data(), errno);\n    return false;\n  }\n\n  std::set<uint64_t> checked_indexes;\n  uint64_t cnt {0};\n  FTSENT* node {nullptr};\n\n  while ((node = fts_read(tree))) {\n    if (node->fts_level > 0 && node->fts_name[0] == '.') {\n      fts_set(tree, node, FTS_SKIP);\n    } else {\n      if (node->fts_info == FTS_F) {\n        if (!exclude_fn(node->fts_name)) {\n          ++cnt;\n\n          if (match_indexes.find(cnt) != match_indexes.end()) {\n            if (!check_fn(node->fts_path)) {\n              eos_static_crit(\"msg=\\\"file not matching condition\\\" fn=\\\"%s\\\" \"\n                              \"index=%llu\", node->fts_path, cnt);\n              (void) fts_close(tree);\n              return false;\n            } else {\n              checked_indexes.insert(cnt);\n\n              if (checked_indexes.size() == match_indexes.size()) {\n                break;\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  if (fts_close(tree)) {\n    eos_static_err(\"msg=\\\"fts_close failed\\\" errno=%d\", errno);\n    return false;\n  }\n\n  return true;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/utils/IoPriority.cc",
    "content": "// ----------------------------------------------------------------------\n//! @file IoPriority.cc\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/utils/IoPriority.hh\"\n#include <unistd.h>\n#include <fcntl.h>\n#ifndef __APPLE__\n#include <sys/syscall.h>\n#include <asm/unistd.h>\n#include <sys/capability.h>\n#endif\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Set IO priority\n//------------------------------------------------------------------------------\nint ioprio_set(int which, int ioprio)\n{\n#ifdef __APPLE__\n  return 0;\n#else\n  return syscall(SYS_ioprio_set, which, 0, ioprio);\n#endif\n}\n\n//------------------------------------------------------------------------------\n// Get IO priority\n//------------------------------------------------------------------------------\nint ioprio_get(int which)\n{\n#ifdef __APPLE__\n  return 0;\n#else\n  return syscall(SYS_ioprio_get, which, 0);\n#endif\n}\n\n//------------------------------------------------------------------------------\n// Convert string to IO priority class\n//------------------------------------------------------------------------------\nint ioprio_class(const std::string& c)\n{\n  if (c == \"idle\") {\n    return IOPRIO_CLASS_IDLE;\n  } else if (c == \"be\") {\n    return IOPRIO_CLASS_BE;\n  } else if (c == \"rt\") {\n    return IOPRIO_CLASS_RT;\n  } else {\n    return IOPRIO_CLASS_NONE;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Convert string to IO priority level (0..7)\n//------------------------------------------------------------------------------\nint ioprio_value(const std::string& v)\n{\n  if (v.length()) {\n    int level = std::atoi(v.c_str());\n\n    if ((level < 0) || (level > 7)) {\n      return 0;\n    } else {\n      return level;\n    }\n  } else {\n    return 0;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check if requested IO priority requires sysadm rights\n//------------------------------------------------------------------------------\nint ioprio_needs_sysadm(int iopriority)\n{\n  if ((IOPRIO_PRIO_CLASS(iopriority) == IOPRIO_CLASS_RT) ||\n      (IOPRIO_PRIO_CLASS(iopriority) == IOPRIO_CLASS_IDLE)) {\n    return true;\n  } else {\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Change IO priority\n//------------------------------------------------------------------------------\nint ioprio_begin(int which, int iopriority, int local_iopriority)\n{\n  int rc = 0;\n\n  if (local_iopriority == iopriority) {\n    return 0;\n  }\n\n#ifndef __APPLE__\n\n  if (ioprio_needs_sysadm(iopriority)) {\n    struct __user_cap_header_struct cap_header;\n    struct __user_cap_data_struct cap_data;\n    cap_header.pid = 0;\n    cap_header.version = _LINUX_CAPABILITY_VERSION_1;\n    cap_data.effective = cap_data.permitted = ~0u;\n    cap_data.inheritable = 0;\n    rc |= capset(&cap_header, &cap_data);\n  }\n\n#endif\n  rc |= ioprio_set(which, iopriority);\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Change back to default IO priority to BE 4\n//------------------------------------------------------------------------------\nint ioprio_end(int which, int iopriority)\n{\n#ifndef __APPLE__\n\n  if (ioprio_needs_sysadm(iopriority)) {\n    struct __user_cap_header_struct cap_header;\n    struct __user_cap_data_struct cap_data;\n    cap_header.pid = 0;\n    cap_header.version = _LINUX_CAPABILITY_VERSION_1;\n    cap_data.permitted = ~0u;\n    cap_data.effective = 0;\n    cap_data.inheritable = 0;\n    capset(&cap_header, &cap_data);\n  }\n\n#endif\n  ioprio_set(which, IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 4));\n  int current_iopriority = ioprio_get(which);\n  return current_iopriority;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/utils/IoPriority.hh",
    "content": "// ----------------------------------------------------------------------\n//! @file: IoPriority.hh\n//! @author: Andreas Joachim Peters <andreas.joachim.peters@cern.ch>\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"fst/Namespace.hh\"\n#include <string>\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! 8 best effort priority levels are supported\n//------------------------------------------------------------------------------\n#define IOPRIO_BE_NR (8)\n\n//------------------------------------------------------------------------------\n//! Gives us 8 prio classes with 13-bits of data for each class\n//------------------------------------------------------------------------------\n#define IOPRIO_BITS             (16)\n#define IOPRIO_CLASS_SHIFT      (13)\n#define IOPRIO_PRIO_MASK        ((1UL << IOPRIO_CLASS_SHIFT) - 1)\n#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT)\n#define IOPRIO_PRIO_DATA(mask)  ((mask) & IOPRIO_PRIO_MASK)\n#define IOPRIO_PRIO_VALUE(class, data)  (((class) << IOPRIO_CLASS_SHIFT) | data)\n\n#define ioprio_valid(mask)      (IOPRIO_PRIO_CLASS((mask)) != IOPRIO_CLASS_NONE)\n\n//------------------------------------------------------------------------------\n//! These are the io priority groups as implemented by CFQ. RT is the realtime\n//! class, it always gets premium service. BE is the best-effort scheduling\n//! class, the default for any process. IDLE is the idle scheduling class, it\n//! is only served when no one else is using the disk.\n//------------------------------------------------------------------------------\nenum {\n  IOPRIO_CLASS_NONE,\n  IOPRIO_CLASS_RT,\n  IOPRIO_CLASS_BE,\n  IOPRIO_CLASS_IDLE,\n};\n\nenum {\n  IOPRIO_WHO_PROCESS = 1,\n  IOPRIO_WHO_PGRP,\n  IOPRIO_WHO_USER,\n};\n\n//------------------------------------------------------------------------------\n//! Set IO priority\n//!\n//! @return 0 if successful, otherwise -1\n//------------------------------------------------------------------------------\nint ioprio_set(int which, int ioprio);\n\n//------------------------------------------------------------------------------\n//! Get IO priority\n//------------------------------------------------------------------------------\nint ioprio_get(int which);\n\n//------------------------------------------------------------------------------\n//! Convert string to IO priority class\n//!\n//! @param c string representation of IO priority class\n//!\n//! @return IO priority class numeric\n//------------------------------------------------------------------------------\nint ioprio_class(const std::string& c);\n\n//------------------------------------------------------------------------------\n//! Convert string to IO priority level (0..7)\n//!\n//! @param v string representation of IO priority level\n//!\n//! @return IO priority level numeric\n//------------------------------------------------------------------------------\nint ioprio_value(const std::string& v);\n\n//------------------------------------------------------------------------------\n//! Check if requested IO priority class requires sysadm rights\n//!\n//! @param iopriority IO priority class\n//!\n//! @return 0 if false, 1 if true\n//------------------------------------------------------------------------------\nint ioprio_needs_sysadm(int iopriority);\n\n//------------------------------------------------------------------------------\n//! Change IO priority\n//!\n//! @return 0 if successful, otherwise non-zero\n//------------------------------------------------------------------------------\nint ioprio_begin(int which, int iopriority, int local_iopriority);\n\n//------------------------------------------------------------------------------\n//! Change back to default IO priority BE 4\n//------------------------------------------------------------------------------\nint ioprio_end(int which, int iopriority);\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/utils/OpenFileTracker.cc",
    "content": "// ----------------------------------------------------------------------\n// File: OpenFileTracker.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/utils/OpenFileTracker.hh\"\n#include \"common/Assert.hh\"\n#include \"common/Logging.hh\"\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nOpenFileTracker::OpenFileTracker()\n{\n  mMutex.SetBlocking(true);\n}\n\n//------------------------------------------------------------------------------\n// Mark that the given file ID, on the given filesystem ID, was just opened\n//------------------------------------------------------------------------------\nvoid OpenFileTracker::up(eos::common::FileSystem::fsid_t fsid, uint64_t fid)\n{\n  eos::common::RWMutexWriteLock wr_lock(mMutex);\n  mContents[fsid][fid]++;\n\n  if (mContents[fsid][fid] > 1) {\n    mMultiOpen[fsid][fid] = true;\n  }\n}\n\n//------------------------------------------------------------------------------\n//! Wait for an excl open of a file and count up\n//------------------------------------------------------------------------------\nvoid OpenFileTracker::waitExclOpen(eos::common::FileSystem::fsid_t fsid,\n                                   uint64_t fid)\n{\n  do {\n    bool busy = false;\n    {\n      eos::common::RWMutexWriteLock wr_lock(mMutex);\n      busy = (mContents[fsid].find(fid) != mContents[fsid].end());\n\n      if (!busy) {\n        mContents[fsid][fid]++;\n        break;\n      }\n    }\n    // this is not starvation free, but considering the use-case it won't happen\n    std::this_thread::sleep_for(std::chrono::milliseconds(25));\n  } while (1);\n}\n\n//------------------------------------------------------------------------------\n// Mark that the given file ID, on the given filesystem ID, was just closed\n//\n// Prints warning in the logs if the value was about to go negative - it will\n// never go negative.\n//------------------------------------------------------------------------------\nvoid OpenFileTracker::down(eos::common::FileSystem::fsid_t fsid, uint64_t fid)\n{\n  eos::common::RWMutexWriteLock wr_lock(mMutex);\n  auto fsit = mContents.find(fsid);\n\n  if (fsit == mContents.end()) {\n    // Can happen if OpenFileTracker is misused\n    eos_static_crit(\"Could not find fsid=%lu when calling OpenFileTracker::down \"\n                    \"for fxid=%08llx\", fsid, fid);\n    return;\n  }\n\n  auto fidit = fsit->second.find(fid);\n\n  if (fidit == fsit->second.end()) {\n    // Can happen if OpenFileTracker is misused\n    eos_static_crit(\"Could not find fxid=%08llx when calling OpenFileTracker::down \"\n                    \"for fsid=%lu\", fid, fsid);\n    return;\n  }\n\n  if (fidit->second == 1) {\n    // Last use, remove from map\n    fsit->second.erase(fidit);\n    // Last use, remove from map\n    mMultiOpen[fsid].erase(fid);\n\n    // Also remove fs from top-level map?\n    if (fsit->second.empty()) {\n      mContents.erase(fsit);\n    }\n\n    // Also remove fs from top-level map?\n    if (mMultiOpen[fsid].empty()) {\n      mMultiOpen.erase(fsid);\n    }\n\n    return;\n  }\n\n  if (fidit->second < 1) {\n    eos_static_crit(\"Should never happen - encountered bogus value in \"\n                    \"OpenFileTracker::down for fsid=%lu, fxid=%08llx - dropping\",\n                    fsid, fid);\n    fsit->second.erase(fidit);\n    mMultiOpen.erase(fid);\n    return;\n  }\n\n  // Simply decrement\n  fidit->second--;\n}\n\n//------------------------------------------------------------------------------\n// Checks if the given file ID, on the given filesystem ID, is currently open\n//------------------------------------------------------------------------------\nbool OpenFileTracker::isOpen(eos::common::FileSystem::fsid_t fsid,\n                             uint64_t fid) const\n{\n  return getUseCount(fsid, fid) > 0;\n}\n\n//------------------------------------------------------------------------------\n// Checks if the given file ID, on the given filesystem ID, had multiple opens\n//------------------------------------------------------------------------------\nbool OpenFileTracker::hadMultiOpen(eos::common::FileSystem::fsid_t fsid,\n                                   uint64_t fid) const\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  auto fsit = mMultiOpen.find(fsid);\n\n  if (fsit == mMultiOpen.end()) {\n    return 0;\n  }\n\n  auto fidit = fsit->second.find(fid);\n\n  if (fidit == fsit->second.end()) {\n    return 0;\n  }\n\n  return fidit->second;\n}\n\n//------------------------------------------------------------------------------\n// Checks if the given file ID, on the given filesystem ID, is currently open\n//------------------------------------------------------------------------------\nint32_t OpenFileTracker::getUseCount(eos::common::FileSystem::fsid_t fsid,\n                                     uint64_t fid) const\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  auto fsit = mContents.find(fsid);\n\n  if (fsit == mContents.end()) {\n    return 0;\n  }\n\n  auto fidit = fsit->second.find(fid);\n\n  if (fidit == fsit->second.end()) {\n    return 0;\n  }\n\n  return fidit->second;\n}\n\n//------------------------------------------------------------------------------\n// Checks if there's _any_ operation currently in progress\n//------------------------------------------------------------------------------\nbool OpenFileTracker::isAnyOpen() const\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  return ! mContents.empty();\n}\n\n//------------------------------------------------------------------------------\n// Get open file IDs of a filesystem, sorted by usecount\n//------------------------------------------------------------------------------\nstd::map<size_t, std::set<uint64_t>> OpenFileTracker::getSortedByUsecount(\n                                    eos::common::FileSystem::fsid_t fsid) const\n{\n  std::map<size_t, std::set<uint64_t>> contentsSortedByUsecount;\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  auto fsit = mContents.find(fsid);\n\n  if (fsit == mContents.end()) {\n    // Filesystem has no open files\n    return {};\n  }\n\n  for (auto it = fsit->second.begin(); it != fsit->second.end(); it++) {\n    contentsSortedByUsecount[it->second].insert(it->first);\n  }\n\n  return contentsSortedByUsecount;\n}\n\n//------------------------------------------------------------------------------\n// Get number of distinct open files by filesystem\n//------------------------------------------------------------------------------\nint32_t OpenFileTracker::getOpenOnFilesystem(eos::common::FileSystem::fsid_t\n    fsid) const\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  auto fsit = mContents.find(fsid);\n\n  if (fsit == mContents.end()) {\n    return 0;\n  }\n\n  return fsit->second.size();\n}\n\n//------------------------------------------------------------------------------\n// Get round-robin scheduling mutex per fsid/app\n//------------------------------------------------------------------------------\nstd::mutex*\nOpenFileTracker::scheduleRR(eos::common::FileSystem::fsid_t fsid,\n                            const std::string app)\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  return &mApp[fsid][app];\n}\n\n//------------------------------------------------------------------------------\n// Get top hot files on current filesystem\n//------------------------------------------------------------------------------\nstd::vector<OpenFileTracker::HotEntry> OpenFileTracker::getHotFiles(\n  eos::common::FileSystem::fsid_t fsid, size_t maxEntries) const\n{\n  auto sorted = getSortedByUsecount(fsid);\n  std::vector<HotEntry> results;\n\n  for (auto it = sorted.rbegin(); it != sorted.rend(); it++) {\n    for (auto it2 =  it->second.begin(); it2 != it->second.end(); it2++) {\n      if (results.size() >= maxEntries) {\n        goto done;\n      }\n\n      results.emplace_back(fsid, *it2, it->first);\n    }\n  }\n\ndone:\n  return results;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/utils/OpenFileTracker.hh",
    "content": "// ----------------------------------------------------------------------\n// File: OpenFileTracker.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_FST_UTILS_OPENFILETRACKER_H\n#define EOS_FST_UTILS_OPENFILETRACKER_H\n\n#include \"fst/Namespace.hh\"\n#include \"common/FileSystem.hh\"\n#include \"common/RWMutex.hh\"\n#include <mutex>\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class to track which files are open at any given moment, on a\n//! filesystem-basis.\n//!\n//! Thread-safe. To track both \"open-for-read\" and \"open-for-write\" files,\n//! use two different objects.\n//------------------------------------------------------------------------------\nclass OpenFileTracker\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  OpenFileTracker();\n\n  //----------------------------------------------------------------------------\n  //! Mark that the given file ID, on the given filesystem ID, was just opened\n  //----------------------------------------------------------------------------\n  void up(eos::common::FileSystem::fsid_t fsid, uint64_t fid);\n\n  //----------------------------------------------------------------------------\n  //! Wait for an excl open of a file and count up\n  //----------------------------------------------------------------------------\n  void waitExclOpen(eos::common::FileSystem::fsid_t fsid, uint64_t fid);\n\n  //----------------------------------------------------------------------------\n  //! Mark that the given file ID, on the given filesystem ID, was just closed\n  //!\n  //! Prints warning in the logs if the value was about to go negative - it will\n  //! never go negative.\n  //----------------------------------------------------------------------------\n  void down(eos::common::FileSystem::fsid_t fsid, uint64_t fid);\n\n  //----------------------------------------------------------------------------\n  //! Checks if the given file ID, on the given filesystem ID, is currently open\n  //----------------------------------------------------------------------------\n  bool isOpen(eos::common::FileSystem::fsid_t fsid, uint64_t fid) const;\n\n  //----------------------------------------------------------------------------\n  //! Checks if there's _any_ operation currently in progress\n  //----------------------------------------------------------------------------\n  bool isAnyOpen() const;\n\n  //----------------------------------------------------------------------------\n  //! Checks if there was ever more than one writer on that file\n  //----------------------------------------------------------------------------\n  bool hadMultiOpen(eos::common::FileSystem::fsid_t fsid, uint64_t fid) const;\n\n  //----------------------------------------------------------------------------\n  //! Checks if the given file ID, on the given filesystem ID, is currently open\n  //----------------------------------------------------------------------------\n  int32_t getUseCount(eos::common::FileSystem::fsid_t fsid, uint64_t fid) const;\n\n  //----------------------------------------------------------------------------\n  //! Get number of distinct open files by filesystem\n  //----------------------------------------------------------------------------\n  int32_t getOpenOnFilesystem(eos::common::FileSystem::fsid_t fsid) const;\n\n  //----------------------------------------------------------------------------\n  //! Get open file IDs of a filesystem, sorted by usecount\n  //----------------------------------------------------------------------------\n  std::map<size_t, std::set<uint64_t>> getSortedByUsecount(\n                                      eos::common::FileSystem::fsid_t fsid) const;\n\n  //----------------------------------------------------------------------------\n  //! Get the RR scheduling object per filesystem/app\n  //----------------------------------------------------------------------------\n  std::mutex* scheduleRR(eos::common::FileSystem::fsid_t fsid,\n                         const std::string app);\n  //----------------------------------------------------------------------------\n\n  //----------------------------------------------------------------------------\n  //! Get top hot files on current filesystem\n  //----------------------------------------------------------------------------\n  struct HotEntry {\n    HotEntry(eos::common::FileSystem::fsid_t fs, uint64_t f, size_t u)\n      : fsid(fs), fid(f), uses(u) {}\n\n    HotEntry() {}\n\n    bool operator==(const HotEntry& other) const\n    {\n      return fsid == other.fsid && fid == other.fid && uses == other.uses;\n    }\n\n    bool operator!=(const HotEntry& other) const\n    {\n      return !(*this == other);\n    }\n\n    eos::common::FileSystem::fsid_t fsid;\n    uint64_t fid;\n    size_t uses;\n  };\n\n  std::vector<HotEntry> getHotFiles(eos::common::FileSystem::fsid_t fsid,\n                                    size_t maxEntries) const;\n\n  //----------------------------------------------------------------------------\n  //! Class acting as a barrier to avoid concurrent file creation interference\n  //----------------------------------------------------------------------------\n  class CreationBarrier\n  {\n  public:\n    CreationBarrier(OpenFileTracker& tracker,\n                    eos::common::FileSystem::fsid_t fsid,\n                    uint64_t fid) : mTracker(tracker), mFsid(fsid), mFid(fid) , mReleased(false)\n    {\n      mTracker.waitExclOpen(fsid, fid);\n    };\n\n    ~CreationBarrier()\n    {\n      Release();\n    }\n\n    void Release()\n    {\n      if (!mReleased) {\n        mTracker.down(mFsid, mFid);\n      }\n\n      mReleased = true;\n    }\n  private:\n    OpenFileTracker& mTracker;\n    eos::common::FileSystem::fsid_t mFsid;\n    uint64_t mFid;\n    bool mReleased;\n  };\n\nprivate:\n  mutable eos::common::RWMutex mMutex;\n  std::map<eos::common::FileSystem::fsid_t, std::map<uint64_t, int32_t>>\n      mContents;\n\n  std::map<eos::common::FileSystem::fsid_t, std::map<uint64_t, bool>>\n      mMultiOpen;\n\n  std::map<eos::common::FileSystem::fsid_t, std::map<uint64_t, bool>>\n      mClosing;\n\n  std::map<eos::common::FileSystem::fsid_t, std::map<std::string, std::mutex>>\n      mApp;\n};\n\nEOSFSTNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "fst/utils/ScanRate.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ScanRate.cc\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2026 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fst/utils/ScanRate.hh\"\n#include \"fst/Load.hh\"\n#include <thread>\n\nEOSFSTNAMESPACE_BEGIN\n\nnamespace utils\n{\n\n//------------------------------------------------------------------------------\n// Enforce and adjust scan rate logic\n//------------------------------------------------------------------------------\nvoid EnforceAndAdjustScanRate(const off_t offset,\n                              const std::chrono::time_point\n                              <std::chrono::system_clock> open_ts,\n                              int& scan_rate,\n                              Load* fst_load,\n                              const char* dir_path,\n                              const int max_rate)\n{\n  using namespace std::chrono;\n\n  if (scan_rate) {\n    const auto now_ts = std::chrono::system_clock::now();\n    uint64_t scan_duration_msec =\n      duration_cast<milliseconds>(now_ts - open_ts).count();\n    uint64_t expect_duration_msec =\n      (uint64_t)((1000.0 * offset) / (scan_rate * 1024 * 1024));\n\n    if (expect_duration_msec > scan_duration_msec) {\n      std::this_thread::sleep_for(milliseconds(expect_duration_msec -\n                                  scan_duration_msec));\n    }\n\n    if (fst_load && dir_path) {\n      // Adjust the rate according to the load information\n      double load = fst_load->GetDiskRate(dir_path, \"millisIO\") / 1000.0;\n\n      if (load > 0.7) {\n        // Adjust the scan_rate which is in MB/s but no lower then 5 MB/s\n        if (scan_rate > 5) {\n          scan_rate = 0.9 * scan_rate;\n        }\n      } else if (max_rate) {\n        scan_rate = max_rate;\n      }\n    }\n  }\n}\n\n} // namespace utils\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/utils/ScanRate.hh",
    "content": "//------------------------------------------------------------------------------\n// File: ScanRate.hh\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2026 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"fst/Namespace.hh\"\n#include <chrono>\n#include <sys/types.h>\n\nEOSFSTNAMESPACE_BEGIN\n\nclass Load;\n\nnamespace utils\n{\n\n//------------------------------------------------------------------------------\n//! Enforce the scan rate by throttling the current thread and also adjust it\n//! depending on the IO load on the mountpoint\n//!\n//! @param offset current offset in file\n//! @param open_ts time point when file was opened\n//! @param scan_rate current scan rate, if 0 then then rate limiting is\n//!        disabled\n//! @param fst_load load object\n//! @param dir_path path to the directory being scanned\n//! @param max_rate maximum allowed scan rate\n//------------------------------------------------------------------------------\nvoid EnforceAndAdjustScanRate(const off_t offset,\n                              const std::chrono::time_point\n                              <std::chrono::system_clock> open_ts,\n                              int& scan_rate,\n                              Load* fst_load = nullptr,\n                              const char* dir_path = nullptr,\n                              int max_rate = 0);\n\n} // namespace utils\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/utils/StdFSWalkTree.hh",
    "content": "// ----------------------------------------------------------------------\n// File: StdFSWalkDirTree\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#pragma once\n\n#include <string_view>\n\n#if defined(__clang__) && __clang_major__ < 6\n#include <experimental/filesystem>\n#else\n#include <filesystem>\n#endif\n\n#include \"common/Logging.hh\"\n\n// A std::filesystem version of Filesystem like functions for eos\n// Clang support is in experimental namespace until clang 6\nnamespace eos::fst::stdfs\n{\n\n#if defined(__clang__) && __clang_major__ < 6\nnamespace fs = std::experimental::filesystem::v1;\n#else\nnamespace fs = std::filesystem;\n#endif\n\n\n// A walk directory tree function using the recursive directory iterator.\n// We deliberately template PathOp fn, which takes a path and count,\n// while he first argument to be fs::path, however this is convertible to a std::string,\n// so we can reuse functionalities from FTS/C versions of the functions as well.\n// Hidden files or directories are not visited, and symlinks not followed\n// This version throws exceptions.\ntemplate <typename FilterFn, typename PathOp>\nuint64_t WalkDirTree(std::string_view path, FilterFn&& filter, PathOp&& path_op)\n{\n  uint64_t count {0};\n\n  for (auto p = fs::recursive_directory_iterator(path,\n                fs::directory_options::skip_permission_denied);\n       p != fs::recursive_directory_iterator();\n       ++p) {\n    if (p->path().filename().c_str()[0] == '.') {\n      p.disable_recursion_pending();\n      continue;\n    }\n\n    if (filter(p)) {\n      path_op(p->path());\n      ++count;\n    }\n  }\n\n  return count;\n}\n\n// A non throwing version of WalkFSTree; if allocator throws this is of course raised,\n// but then that would anyway warrant a critical failure\ntemplate <typename FilterFn, typename PathOp>\nuint64_t WalkDirTree(std::string_view path, FilterFn&& filter, PathOp&& path_op,\n                     std::error_code& ec) noexcept\n{\n  uint64_t count {0};\n  eos_static_debug(\"msg=\\\"walking directory\\\" path=\\\"%s\\\"\", path.data());\n\n  for (auto p = fs::recursive_directory_iterator(path,\n                fs::directory_options::skip_permission_denied,\n                ec);\n       p != fs::recursive_directory_iterator();\n       p.increment(ec)) {\n    if (ec) {\n      eos_static_crit(\"msg=\\\"error while walking directory tree\\\" ec=\\\"%s\\\"\",\n                      ec.message().c_str());\n      return count;\n    }\n\n    eos_static_debug(\"msg=\\\"processing path\\\" path=\\\"%s\\\"\", p->path().c_str());\n\n    if (p->path().filename().c_str()[0] == '.') {\n      p.disable_recursion_pending();\n      continue;\n    }\n\n    if (filter(p)) {\n      eos_static_debug(\"msg=\\\"processing after filter\\\" path=\\\"%s\\\"\",\n                       p->path().c_str());\n      path_op(p->path());\n      ++count;\n    }\n  }\n\n  return count;\n}\n\n// This works with multiple directory iterator types hence the template\ntemplate <typename It>\nbool IsRegularFile(It it)\n{\n#if defined(__clang__) && __clang_major__ < 6\n  return fs::is_regular_file(it->path());\n#else\n  return it->is_regular_file();\n#endif\n}\n\n\n\ntemplate <typename UnaryOp>\nuint64_t\nWalkFSTree(std::string_view path, UnaryOp&& op, std::error_code& ec)\n{\n  auto exclude_xs_map = [](const auto & p)  {\n    return IsRegularFile(p) && p->path().extension() != \".xsmap\";\n  };\n  return WalkDirTree(path,\n                     exclude_xs_map,\n                     std::forward<UnaryOp>(op),\n                     ec);\n}\n\n} // namespace eos::fst::stdfs\n"
  },
  {
    "path": "fst/utils/TpcInfo.hh",
    "content": "// ----------------------------------------------------------------------\n// File: TpcInfo.hh\n// Author: Mihai Patrascoiu - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include \"fst/Namespace.hh\"\n#include <string>\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! TPC data structure to hold useful TPC information.\n//!\n//! Note: The structure has been extracted to a header file\n//!       as it is being used by the XrdFstOfs and XrdFstOfsFile objects\n//------------------------------------------------------------------------------\nstruct TpcInfo {\n  std::string path; ///< file path to read/write\n  std::string opaque; ///< opaque info\n  std::string capability; ///< EOS capability\n  std::string key; ///< Transfer key\n  std::string src; ///< Source hostname\n  std::string dst; ///< Destination hostname\n  std::string org; ///< Origin client\n  std::string lfn; ///< File name at source\n  time_t expires; ///< Expiry timestamp\n};\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/utils/TransformAttr.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file TransformAttr.hh\n//! @author Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n  * EOS - the CERN Disk Storage System                                   *\n  * Copyright (C) 2022 CERN/Switzerland                           *\n  *                                                                      *\n  * This program is free software: you can redistribute it and/or modify *\n  * it under the terms of the GNU General Public License as published by *\n  * the Free Software Foundation, either version 3 of the License, or    *\n  * (at your option) any later version.                                  *\n  *                                                                      *\n  * This program is distributed in the hope that it will be useful,      *\n  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n  * GNU General Public License for more details.                         *\n  *                                                                      *\n  * You should have received a copy of the GNU General Public License    *\n  * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n  ************************************************************************/\n\n\n#pragma once\n\n#include \"common/Logging.hh\"\n#include \"fst/io/local/FsIo.hh\"\n\nnamespace eos::fst\n{\n\ntemplate <typename transformFn>\nbool TransformAttr(const std::string& path,\n                   const std::string& attrName,\n                   transformFn f)\n{\n  FsIo fsio{path};\n  std::string attrval;\n  int rc = fsio.attrGet(gFmdAttrName, attrval);\n\n  if (rc != 0) {\n    eos_static_err(\"msg=\\\"Failed to retrieve filemd attr\\\" path=%s\",\n                   path.c_str());\n    return false;\n  }\n\n  rc = fsio.attrSet(gFmdAttrName, f(attrval));\n\n  if (rc != 0) {\n    eos_static_err(\"msg=\\\"Failed to set filemd attr\\\" path=%s\",\n                   path.c_str());\n    return false;\n  }\n\n  return true;\n}\n\n} // namespace eos::fst\n"
  },
  {
    "path": "fst/utils/XrdOfsPathHandler.cc",
    "content": "// /************************************************************************\n//  * EOS - the CERN Disk Storage System                                   *\n//  * Copyright (C) 2022 CERN/Switzerland                           *\n//  *                                                                      *\n//  * This program is free software: you can redistribute it and/or modify *\n//  * it under the terms of the GNU General Public License as published by *\n//  * the Free Software Foundation, either version 3 of the License, or    *\n//  * (at your option) any later version.                                  *\n//  *                                                                      *\n//  * This program is distributed in the hope that it will be useful,      *\n//  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n//  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n//  * GNU General Public License for more details.                         *\n//  *                                                                      *\n//  * You should have received a copy of the GNU General Public License    *\n//  * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n//  ************************************************************************\n//\n\n#include \"fst/utils/XrdOfsPathHandler.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#include \"fst/storage/Storage.hh\"\n\nnamespace eos::fst\n{\n\nstd::string\nXrdOfsPathHandler::GetFSPath(eos::common::FileSystem::fsid_t fsid)\n{\n  return mOFS->Storage->GetStoragePath(fsid);\n}\n\n\n} // namespace eos::fst\n"
  },
  {
    "path": "fst/utils/XrdOfsPathHandler.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************\n */\n\n#pragma once\n\n#include \"fst/utils/FSPathHandler.hh\"\n\nnamespace eos::fst\n{\n\nclass XrdFstOfs;\n\nclass XrdOfsPathHandler final: public FSPathHandler\n{\npublic:\n  XrdOfsPathHandler(XrdFstOfs const* pOFS):\n    FSPathHandler(), mOFS(pOFS)\n  {}\n\n  std::string GetFSPath(eos::common::FileSystem::fsid_t fsid) override;\n\nprivate:\n  XrdFstOfs const* mOFS;\n};\n\n\ninline std::unique_ptr<FSPathHandler>\nmakeFSPathHandler(XrdFstOfs* pOFS)\n{\n  return std::make_unique<XrdOfsPathHandler>(pOFS);\n}\n\n} // namespace eos::fst\n"
  },
  {
    "path": "fst/xrdcl_plugins/CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch> CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2014 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nadd_library(EosRainClient MODULE\n  RainPlugin.cc RainPlugin.hh\n  RainFile.cc   RainFile.hh)\n\ntarget_link_libraries(EosRainClient PUBLIC EosFstIo)\n\nset_target_properties(EosRainClient PROPERTIES\n  VERSION ${VERSION}\n  SOVERSION ${VERSION_MAJOR}\n  MACOSX_RPATH TRUE)\n\ninstall(TARGETS EosRainClient\n  LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n  ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\n\n"
  },
  {
    "path": "fst/xrdcl_plugins/RainFile.cc",
    "content": "//------------------------------------------------------------------------------\n// File RainFile.cc\n// Author Elvin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2014 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"RainFile.hh\"\n#include \"fst/layout/RainMetaLayout.hh\"\n#include \"fst/layout/RaidDpLayout.hh\"\n#include \"fst/layout/ReedSLayout.hh\"\n/*----------------------------------------------------------------------------*/\n\nusing namespace eos::common;\nusing namespace eos::fst;\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nRainFile::RainFile():\n  mIsOpen(false),\n  pFile(0),\n  pRainFile(0)\n{\n  eos_debug(\"calling constructor\");\n}\n\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nRainFile::~RainFile()\n{\n  eos_debug(\"calling destructor\");\n\n  if (pFile) {\n    delete pFile;\n  }\n\n  if (pRainFile) {\n    delete pRainFile;\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Open\n//------------------------------------------------------------------------------\nXRootDStatus\nRainFile::Open(const std::string& url,\n               OpenFlags::Flags flags,\n               Access::Mode mode,\n               ResponseHandler* handler,\n               uint16_t timeout)\n{\n  eos_debug(\"url=%s\", url.c_str());\n  XRootDStatus st;\n\n  if (mIsOpen) {\n    st = XRootDStatus(stError, errInvalidOp);\n    return st;\n  }\n\n  // For reading try PIO mode\n  if ((flags & OpenFlags::Flags::Read) == OpenFlags::Flags::Read) {\n    XrdCl::Buffer arg;\n    XrdCl::Buffer* response = 0;\n    std::string fpath = url;\n    size_t spos = fpath.rfind(\"//\");\n\n    if (spos != std::string::npos) {\n      fpath.erase(0, spos + 1);\n    }\n\n    std::string request = fpath;\n    request += \"?mgm.pcmd=open\";\n    arg.FromString(request);\n    std::string endpoint = url;\n    endpoint.erase(spos + 1);\n    URL Url(endpoint);\n    XrdCl::FileSystem fs(Url);\n    st = fs.Query(QueryCode::OpaqueFile, arg, response);\n\n    if (st.IsOK()) {\n      // Parse output\n      XrdOucString tag;\n      XrdOucString stripePath;\n      std::vector<std::string> stripeUrls;\n      XrdOucString origResponse = response->GetBuffer();\n      XrdOucString stringOpaque = response->GetBuffer();\n      // Add the eos.app=rainplugin tag to all future PIO open requests\n      origResponse += \"&eos.app=rainplugin\";\n\n      while (stringOpaque.replace(\"?\", \"&\")) { }\n\n      while (stringOpaque.replace(\"&&\", \"&\")) { }\n\n      XrdOucEnv* openOpaque = new XrdOucEnv(stringOpaque.c_str());\n      char* opaqueInfo = (char*) strstr(origResponse.c_str(), \"&mgm.logid\");\n\n      if (opaqueInfo) {\n        opaqueInfo += 1;\n        LayoutId::layoutid_t layout = openOpaque->GetInt(\"mgm.lid\");\n\n        for (unsigned int i = 0; i <= eos::common::LayoutId::GetStripeNumber(layout);\n             i++) {\n          tag = \"pio.\";\n          tag += static_cast<int>(i);\n          stripePath = \"root://\";\n          stripePath += openOpaque->Get(tag.c_str());\n          stripePath += \"/\";\n          stripePath += fpath.c_str();\n          stripeUrls.push_back(stripePath.c_str());\n        }\n\n        if (LayoutId::GetLayoutType(layout) == LayoutId::kRaidDP) {\n          pRainFile = new RaidDpLayout(NULL, layout, NULL, NULL, \"\", NULL);\n        } else if ((LayoutId::IsRain(layout))) {\n          try {\n            pRainFile = new ReedSLayout(NULL, layout, NULL, NULL, \"\", NULL);\n          } catch (const std::runtime_error& e) {\n            pRainFile = nullptr;\n          }\n        } else {\n          eos_warning(\"unsupported PIO layout\");\n          return XRootDStatus(stError, errNotSupported, 0, \"unsupported PIO layout\");\n        }\n\n        if (pRainFile) {\n          if (pRainFile->OpenPio(stripeUrls, SFS_O_RDONLY, mode, opaqueInfo)) {\n            eos_err(\"failed PIO open for path=%s\", url.c_str());\n            delete pRainFile;\n            st = XRootDStatus(stError, errInvalidOp, 0, \"failed PIO open\");\n          }\n        } else {\n          eos_err(\"%s\", \"msg=\\\"failed to create RAIN file object\\\")\");\n          st = XRootDStatus(stError, errInternal, 0, \"no RAIN file allocated\");\n        }\n      } else {\n        eos_err(\"no opaque info\");\n        st = XRootDStatus(stError, errDataError, 0, \"no opaque info\");\n      }\n    } else {\n      eos_err(\"error while doing PIO read request\");\n      st = XRootDStatus(stError, errNotImplemented, 0, \"error PIO read request\");\n    }\n\n    if (st.IsOK()) {\n      mIsOpen = true;\n      XRootDStatus* ret_st = new XRootDStatus(st);\n      handler->HandleResponse(ret_st, 0);\n    }\n  } else {\n    // Normal XrdCl file access\n    pFile = new XrdCl::File(false);\n    st = pFile->Open(url, flags, mode, handler, timeout);\n\n    if (st.IsOK()) {\n      mIsOpen = true;\n    }\n  }\n\n  return st;\n}\n\n\n//------------------------------------------------------------------------------\n// Close\n//------------------------------------------------------------------------------\nXRootDStatus\nRainFile::Close(ResponseHandler* handler,\n                uint16_t timeout)\n{\n  eos_debug(\"calling close\");\n  XRootDStatus st;\n\n  if (mIsOpen) {\n    mIsOpen = false;\n\n    if (pFile) {\n      st = pFile->Close(handler, timeout);\n    } else {\n      int retc = pRainFile->Close();\n\n      if (retc) {\n        st = XRootDStatus(stError, errUnknown);\n      } else {\n        XRootDStatus* ret_st = new XRootDStatus(st);\n        handler->HandleResponse(ret_st, 0);\n      }\n    }\n  } else {\n    // File already closed\n    st = XRootDStatus(stError, errInvalidOp);\n    XRootDStatus* ret_st = new XRootDStatus(st);\n    handler->HandleResponse(ret_st, 0);\n  }\n\n  return st;\n}\n\n\n//------------------------------------------------------------------------------\n// Stat\n//------------------------------------------------------------------------------\nXRootDStatus\nRainFile::Stat(bool force,\n               ResponseHandler* handler,\n               uint16_t timeout)\n{\n  eos_debug(\"calling stat\");\n  XRootDStatus st;\n\n  if (pFile) {\n    st = pFile->Stat(force, handler, timeout);\n  } else {\n    struct stat buf;\n    int retc = pRainFile->Stat(&buf);\n\n    if (retc) {\n      eos_err(\"RAIN stat failed retc=%i\", retc);\n      st = XRootDStatus(stError, errUnknown);\n    } else {\n      StatInfo* sinfo = new StatInfo();\n      std::ostringstream data;\n      data << buf.st_dev << \" \" << buf.st_size << \" \"\n           << buf.st_mode << \" \" << buf.st_mtime;\n\n      if (!sinfo->ParseServerResponse(data.str().c_str())) {\n        eos_err(\"error parsing stat info\");\n        delete sinfo;\n        st = XRootDStatus(stError, errDataError);\n      } else {\n        eos_debug(\"stat parsing is ok:%i\", st.IsOK());\n        XRootDStatus* ret_st = new XRootDStatus(st);\n        AnyObject* obj = new AnyObject();\n        obj->Set(sinfo);\n        handler->HandleResponse(ret_st, obj);\n      }\n    }\n  }\n\n  return st;\n}\n\n\n//------------------------------------------------------------------------------\n// Read\n//------------------------------------------------------------------------------\nXRootDStatus\nRainFile::Read(uint64_t offset,\n               uint32_t size,\n               void* buffer,\n               ResponseHandler* handler,\n               uint16_t timeout)\n{\n  eos_debug(\"offset=%ju, size=%ju\", offset, size);\n  XRootDStatus st;\n\n  if (pFile) {\n    st = pFile->Read(offset, size, buffer, handler, timeout);\n  } else {\n    int64_t retc = pRainFile->Read(offset, (char*)buffer, size);\n\n    if (retc == -1) {\n      st = XRootDStatus(stError, errUnknown);\n    } else {\n      XRootDStatus* ret_st = new XRootDStatus(st);\n      ChunkInfo* chunkInfo = new ChunkInfo(offset, retc, buffer);\n      AnyObject* obj = new AnyObject();\n      obj->Set(chunkInfo);\n      handler->HandleResponse(ret_st, obj);\n    }\n  }\n\n  return st;\n}\n\n\n//------------------------------------------------------------------------------\n// Write\n//------------------------------------------------------------------------------\nXRootDStatus\nRainFile::Write(uint64_t offset,\n                uint32_t size,\n                const void* buffer,\n                ResponseHandler* handler,\n                uint16_t timeout)\n{\n  eos_debug(\"offset=%ju, size=%ju\", offset, size);\n  XRootDStatus st;\n\n  if (pFile) {\n    st = pFile->Write(offset, size, buffer, handler, timeout);\n  } else {\n    st = XRootDStatus(stError, errNotImplemented, 0, \"RAIN write not implemented\");\n  }\n\n  return st;\n}\n\n\n//------------------------------------------------------------------------------\n// Sync\n//------------------------------------------------------------------------------\nXRootDStatus\nRainFile::Sync(ResponseHandler* handler,\n               uint16_t timeout)\n{\n  eos_debug(\"callnig sync\");\n  XRootDStatus st;\n\n  if (pFile) {\n    st = pFile->Sync(handler, timeout);\n  } else {\n    int retc = pRainFile->Sync();\n\n    if (retc) {\n      st = XRootDStatus(stError, errUnknown);\n    } else {\n      XRootDStatus* ret_st = new XRootDStatus(st);\n      handler->HandleResponse(ret_st, 0);\n    }\n  }\n\n  return st;\n}\n\n\n//------------------------------------------------------------------------------\n// Truncate\n//------------------------------------------------------------------------------\nXRootDStatus\nRainFile::Truncate(uint64_t size,\n                   ResponseHandler* handler,\n                   uint16_t timeout)\n{\n  eos_debug(\"offset=%ju\", size);\n  XRootDStatus st;\n\n  if (pFile) {\n    st = pFile->Truncate(size, handler, timeout);\n  } else {\n    st = XRootDStatus(stError, errNotImplemented, 0,\n                      \"RAIN truncate not implemented\");\n  }\n\n  return st;\n}\n\n\n//------------------------------------------------------------------------------\n// VectorRead\n//------------------------------------------------------------------------------\nXRootDStatus\nRainFile::VectorRead(const ChunkList& chunks,\n                     void* buffer,\n                     ResponseHandler* handler,\n                     uint16_t timeout)\n{\n  eos_debug(\"calling vread\");\n  XRootDStatus st;\n\n  if (pFile) {\n    st = pFile->VectorRead(chunks, buffer, handler, timeout);\n  } else {\n    // Compute total length of readv request\n    uint32_t len = 0;\n\n    for (auto it = chunks.begin(); it != chunks.end(); ++it) {\n      len += it->length;\n    }\n\n    int64_t retc = pRainFile->ReadV(const_cast<ChunkList&>(chunks), len);\n\n    if (retc == (int64_t)len) {\n      XRootDStatus* ret_st = new XRootDStatus(st);\n      AnyObject* obj = new AnyObject();\n      VectorReadInfo* vReadInfo = new VectorReadInfo();\n      vReadInfo->SetSize(len);\n      ChunkList& vResp = vReadInfo->GetChunks();\n      vResp = chunks;\n      obj->Set(vReadInfo);\n      handler->HandleResponse(ret_st, obj);\n    } else {\n      st = XRootDStatus(stError, errUnknown);\n    }\n  }\n\n  return st;\n}\n\n\n//------------------------------------------------------------------------------\n// Fcntl\n//------------------------------------------------------------------------------\nXRootDStatus\nRainFile::Fcntl(const XrdCl::Buffer& arg,\n                ResponseHandler* handler,\n                uint16_t timeout)\n{\n  eos_debug(\"calling fcntl\");\n  XRootDStatus st;\n\n  if (pFile) {\n    st = pFile->Fcntl(arg, handler, timeout);\n  } else {\n    st = XRootDStatus(stError, errNotImplemented, 0, \"RAIN fcntl not implemented\");\n  }\n\n  return st;\n}\n\n\n//------------------------------------------------------------------------------\n// Visa\n//------------------------------------------------------------------------------\nXRootDStatus\nRainFile::Visa(ResponseHandler* handler,\n               uint16_t timeout)\n{\n  eos_debug(\"calling visa\");\n  XRootDStatus st;\n\n  if (pFile) {\n    st = pFile->Visa(handler, timeout);\n  } else {\n    st = XRootDStatus(stError, errNotImplemented, 0, \"RAIN visa not implemented\");\n  }\n\n  return st;\n}\n\n\n//------------------------------------------------------------------------------\n// IsOpen\n//------------------------------------------------------------------------------\nbool\nRainFile::IsOpen() const\n{\n  return mIsOpen;\n}\n\n\n//------------------------------------------------------------------------------\n// @see XrdCl::File::SetProperty\n//------------------------------------------------------------------------------\nbool\nRainFile::SetProperty(const std::string& name,\n                      const std::string& value)\n{\n  eos_debug(\"name=%s, value=%s\", name.c_str(), value.c_str());\n\n  if (pFile) {\n    return pFile->SetProperty(name, value);\n  } else {\n    eos_err(\"op. not implemented for RAIN files\");\n    return false;\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// @see XrdCl::File::GetProperty\n//------------------------------------------------------------------------------\nbool\nRainFile::GetProperty(const std::string& name,\n                      std::string& value) const\n{\n  eos_debug(\"name=%s\", name.c_str());\n\n  if (pFile) {\n    return pFile->GetProperty(name, value);\n  } else {\n    eos_err(\"op. not implemented for RAIN files\");\n    return false;\n  }\n}\n\n\n//------------------------------------------------------------------------------\n//! @see XrdCl::File::GetDataServer\n//------------------------------------------------------------------------------\nstd::string\nRainFile::GetDataServer() const\n{\n  eos_debug(\"get data server\");\n  return std::string(\"\");\n}\n\n\n//------------------------------------------------------------------------------\n//! @see XrdCl::File::GetLastURL\n//------------------------------------------------------------------------------\nURL\nRainFile::GetLastURL() const\n{\n  eos_debug(\"get last URL\");\n  return std::string(\"\");\n}\n\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/xrdcl_plugins/RainFile.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file RainFilePlugin.hh\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2014 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_XRDCLPLUGINS_RAINFILEPLUGIN_HH__\n#define __EOSFST_XRDCLPLUGINS_RAINFILEPLUGIN_HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"fst/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include <XrdCl/XrdClPlugInInterface.hh>\n/*----------------------------------------------------------------------------*/\n\nusing namespace XrdCl;\n\n// Forward declaration\nnamespace eos\n{\nnamespace fst\n{\nclass RainMetaLayout;\n}\n}\n\nEOSFSTNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n//! RAIN file plugin\n//----------------------------------------------------------------------------\nclass RainFile: public XrdCl::FilePlugIn, eos::common::LogId\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  RainFile();\n\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~RainFile();\n\n\n  //----------------------------------------------------------------------------\n  //! Open\n  //----------------------------------------------------------------------------\n  virtual XRootDStatus Open(const std::string& url,\n                            OpenFlags::Flags flags,\n                            Access::Mode mode,\n                            ResponseHandler* handler,\n                            uint16_t timeout);\n\n\n  //----------------------------------------------------------------------------\n  //! Close\n  //----------------------------------------------------------------------------\n  virtual XRootDStatus Close(ResponseHandler* handler,\n                             uint16_t timeout);\n\n\n  //----------------------------------------------------------------------------\n  //! Stat\n  //----------------------------------------------------------------------------\n  virtual XRootDStatus Stat(bool force,\n                            ResponseHandler* handler,\n                            uint16_t timeout);\n\n\n  //----------------------------------------------------------------------------\n  //! Read\n  //----------------------------------------------------------------------------\n  virtual XRootDStatus Read(uint64_t offset,\n                            uint32_t size,\n                            void* buffer,\n                            ResponseHandler* handler,\n                            uint16_t timeout);\n\n\n  //----------------------------------------------------------------------------\n  //! Write\n  //----------------------------------------------------------------------------\n  virtual XRootDStatus Write(uint64_t offset,\n                             uint32_t size,\n                             const void* buffer,\n                             ResponseHandler* handler,\n                             uint16_t timeout);\n\n\n  //----------------------------------------------------------------------------\n  //! Sync\n  //----------------------------------------------------------------------------\n  virtual XRootDStatus Sync(ResponseHandler* handler,\n                            uint16_t timeout);\n\n\n  //----------------------------------------------------------------------------\n  //! Truncate\n  //----------------------------------------------------------------------------\n  virtual XRootDStatus Truncate(uint64_t size,\n                                ResponseHandler* handler,\n                                uint16_t timeout);\n\n\n  //----------------------------------------------------------------------------\n  //! VectorRead\n  //----------------------------------------------------------------------------\n  virtual XRootDStatus VectorRead(const ChunkList& chunks,\n                                  void* buffer,\n                                  ResponseHandler* handler,\n                                  uint16_t timeout);\n\n\n  //------------------------------------------------------------------------\n  //! Fcntl\n  //------------------------------------------------------------------------\n  virtual XRootDStatus Fcntl(const Buffer& arg,\n                             ResponseHandler* handler,\n                             uint16_t timeout);\n\n\n  //----------------------------------------------------------------------------\n  //! Visa\n  //----------------------------------------------------------------------------\n  virtual XRootDStatus Visa(ResponseHandler* handler,\n                            uint16_t timeout);\n\n\n  //----------------------------------------------------------------------------\n  //! IsOpen\n  //----------------------------------------------------------------------------\n  virtual bool IsOpen() const;\n\n\n  //----------------------------------------------------------------------------\n  //! @see XrdCl::File::SetProperty\n  //----------------------------------------------------------------------------\n  virtual bool SetProperty(const std::string& name,\n                           const std::string& value);\n\n\n  //----------------------------------------------------------------------------\n  //! @see XrdCl::File::GetProperty\n  //----------------------------------------------------------------------------\n  virtual bool GetProperty(const std::string& name,\n                           std::string& value) const;\n\n\n  //----------------------------------------------------------------------------\n  //! @see XrdCl::File::GetDataServer\n  //----------------------------------------------------------------------------\n  virtual std::string GetDataServer() const;\n\n\n  //----------------------------------------------------------------------------\n  //! @see XrdCl::File::GetLastURL\n  //----------------------------------------------------------------------------\n  virtual URL GetLastURL() const;\n\nprivate:\n\n  bool mIsOpen;\n  XrdCl::File* pFile;\n  eos::fst::RainMetaLayout* pRainFile;\n\n};\n\nEOSFSTNAMESPACE_END\n\n#endif // __EOSFST_XRDCLPLUGINS_RAINFILEPLUGIN_HH__\n"
  },
  {
    "path": "fst/xrdcl_plugins/RainPlugin.cc",
    "content": "//------------------------------------------------------------------------------\n// File RainPlugin.cc\n// Author Elvin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2014 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include <stdlib.h>\n/*----------------------------------------------------------------------------*/\n#include <XrdVersion.hh>\n#include \"RainPlugin.hh\"\n#include \"RainFile.hh\"\n#include <XrdNet/XrdNetUtils.hh>\n/*----------------------------------------------------------------------------*/\n\nXrdVERSIONINFO(XrdClGetPlugIn, XrdClGetPlugIn)\n\nextern \"C\"\n{\n  void* XrdClGetPlugIn(const void* arg)\n  {\n    return static_cast<void*>(new eos::fst::RainFactory());\n  }\n}\n\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Construtor\n//------------------------------------------------------------------------------\nRainFactory::RainFactory()\n{\n  eos_debug(\"RainFactory constructor\");\n}\n\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nRainFactory::~RainFactory()\n{\n  // empty\n}\n\n//------------------------------------------------------------------------------\n// Create a file plug-in for the given URL\n//------------------------------------------------------------------------------\nXrdCl::FilePlugIn*\nRainFactory::CreateFile(const std::string& url)\n{\n  eos_debug(\"url=%s\", url.c_str());\n  return static_cast<XrdCl::FilePlugIn*>(new RainFile());\n}\n\n\n//------------------------------------------------------------------------------\n// Create a file system plug-in for the given URL\n//------------------------------------------------------------------------------\nXrdCl::FileSystemPlugIn*\nRainFactory::CreateFileSystem(const std::string& url)\n{\n  eos_debug(\"url=%s\", url.c_str());\n  return static_cast<XrdCl::FileSystemPlugIn*>(0);\n}\n\n\n//------------------------------------------------------------------------------\n// Finalizer\n//------------------------------------------------------------------------------\nnamespace\n{\nstatic struct EnvInitializer {\n  //--------------------------------------------------------------------------\n  // Initializer\n  //--------------------------------------------------------------------------\n  EnvInitializer()\n  {\n    char* myhost = XrdNetUtils::MyHostName();\n    std::string host_name = myhost;\n    free(myhost);\n    std::string unit = \"rain@\";\n    unit += host_name;\n    // Get log level from env variable XRD_LOGLEVEL\n    int log_level = 6; // by default use LOG_INFO\n    char* c = nullptr;\n    eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n\n    if ((c = getenv(\"XRD_LOGLEVEL\"))) {\n      if ((*c >= '0') && (*c <= '7')) {\n        log_level = atoi(c);\n      } else {\n        log_level = g_logging.GetPriorityByString(c);\n      }\n    }\n\n    g_logging.SetLogPriority(log_level);\n    g_logging.SetUnit(unit.c_str());\n    // Create log file for RAIN transfers\n    std::string log_file = \"/tmp/rain/xrdcp_rain.log\";\n    std::string log_dir = \"/tmp/rain\";\n    std::ostringstream oss;\n    oss << \"mkdir -p \" << log_dir;\n\n    if (system(oss.str().c_str())) {\n      eos_static_err(\"failed to create log directory:%s\", log_dir.c_str());\n      exit(1);\n    }\n\n    if (::access(log_dir.c_str(), R_OK | W_OK | X_OK)) {\n      eos_static_err(\"can not access log directory:%s\", log_dir.c_str());\n      exit(1);\n    }\n\n    // Create/Open the log file\n    mFp = fopen(log_file.c_str(), \"a+\");\n\n    if (!mFp) {\n      eos_static_err(\"error opening log file:%s\", log_file.c_str());\n      exit(1);\n    } else {\n      eos_static_debug(\"set up log file:%s\", log_file.c_str());\n      // Redirect stdout and stderr to log file\n      fflush(stdout);\n      fflush(stderr);\n      mOldStdout = dup(fileno(stdout));\n      mOldStderr = dup(fileno(stderr));\n      dup2(fileno(mFp), fileno(stdout));\n      dup2(fileno(mFp), fileno(stderr));\n    }\n  }\n\n  //--------------------------------------------------------------------------\n  // Finalizer\n  //--------------------------------------------------------------------------\n  ~EnvInitializer()\n  {\n    // Restore stdout and stderr\n    fflush(stdout);\n    fflush(stderr);\n    dup2(mOldStdout, fileno(stdout));\n    dup2(mOldStderr, fileno(stderr));\n    close(mOldStdout);\n    close(mOldStderr);\n\n    if (fclose(mFp)) {\n      fprintf(stderr, \"[Error] failed to close log file\\n\");\n    }\n\n    //else\n    //fprintf(stderr, \"[Info] log file closed successfully\\n\");\n  }\n\n  FILE* mFp;\n  int mOldStdout;\n  int mOldStderr;\n\n} initializer;\n}\n\nEOSFSTNAMESPACE_END\n"
  },
  {
    "path": "fst/xrdcl_plugins/RainPlugin.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file RainPlugin.hh\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2014 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFST_XRDCLPLUGINS_RAINPLUGIN_HH__\n#define __EOSFST_XRDCLPLUGINS_RAINPLUGIN_HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"fst/Namespace.hh\"\n#include \"common/Logging.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdCl/XrdClPlugInInterface.hh>\n/*----------------------------------------------------------------------------*/\n\nEOSFSTNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! RAIN plugin factory\n//------------------------------------------------------------------------------\nclass RainFactory: public XrdCl::PlugInFactory, eos::common::LogId\n{\n public:\n  \n  //----------------------------------------------------------------------------\n  //! Construtor\n  //----------------------------------------------------------------------------\n  RainFactory();\n\n  \n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~RainFactory();\n\n  \n  //----------------------------------------------------------------------------\n  //! Create a file plug-in for the given URL\n  //----------------------------------------------------------------------------\n  virtual XrdCl::FilePlugIn* CreateFile( const std::string &url );\n\n  \n  //----------------------------------------------------------------------------\n  //! Create a file system plug-in for the given URL\n  //----------------------------------------------------------------------------\n  virtual XrdCl::FileSystemPlugIn* CreateFileSystem( const std::string &url );\n  \n};\n\nEOSFSTNAMESPACE_END;\n\n#endif // __EOSFST_XRDCLPLUGINS_RAINPLUGIN_HH__\n\n"
  },
  {
    "path": "fusex/CMakeLists.txt",
    "content": "# ------------------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Andreas-Joachim Peters - CERN\n# ------------------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ninclude_directories(\n  ${CMAKE_BINARY_DIR}\n  ${CMAKE_CURRENT_SOURCE_DIR}\n  ${CMAKE_CURRENT_SOURCE_DIR}/eosxd/ ${PROTOBUF3_INCLUDE_DIR} )\n\nif (Linux)\n  add_subdirectory(benchmark)\nendif (Linux)\n\n#-------------------------------------------------------------------------------\n# Compile flags\n#-------------------------------------------------------------------------------\nset(EOSXD_COMPILE_FLAGS\n  \"-DDAEMONUID=${DAEMONUID} -DDAEMONGID=${DAEMONGID} -DFUSE_MOUNT_VERSION=${FUSE_MOUNT_VERSION}0 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -DVERSION=\\\\\\\"${VERSION}\\\\\\\"\"\n)\nset(EOSXD3_COMPILE_FLAGS\n  \"-DDAEMONUID=${DAEMONUID} -DDAEMONGID=${DAEMONGID} -DFUSE_MOUNT_VERSION=${FUSE_MOUNT_VERSION}0 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -DVERSION=\\\\\\\"${VERSION}\\\\\\\"\"\n)\n\n#-------------------------------------------------------------------------------\n# Add tests directory\n#-------------------------------------------------------------------------------\nadd_subdirectory(tests)\n\n#-------------------------------------------------------------------------------\n# Add auth subsystem\n#-------------------------------------------------------------------------------\nadd_subdirectory(auth)\n\n#-------------------------------------------------------------------------------\n# Generate protocol buffer files\n#-------------------------------------------------------------------------------\nPROTOBUF_GENERATE_CPP(PROTO_SRC PROTO_HEADER fusex.proto)\n\nset_source_files_properties(\n  ${PROTO_SRC} ${PROTO_HEADER}\n  PROPERTIES GENERATED 1)\n\nset(PROTO_SRC ${PROTO_SRC} PARENT_SCOPE)\nset(PROTO_HEADER ${PROTO_HEADER} PARENT_SCOPE)\n\n#-------------------------------------------------------------------------------\n# Link libraries\n#-------------------------------------------------------------------------------\nset(EOSXD_COMMON_LINK_LIBRARIES\n  EosFuseAuth\n  EosCommon\n  XROOTD::CL\n  XROOTD::UTILS\n  BZ2::BZ2\n  LIBBFD::LIBBFD)\n\n#-------------------------------------------------------------------------------\n# eosxd common objects, shared between tests and main code.\n# An object library prevents having to compile them twice.\n#-------------------------------------------------------------------------------\nadd_library(eosxd-objects OBJECT\n  eosxd/eosfuse.cc eosxd/eosfuse.hh\n  stat/Stat.cc stat/Stat.hh\n  md/md.cc md/md.hh\n  cap/cap.cc cap/cap.hh\n  data/data.cc data/data.hh\n  kv/RocksKV.cc kv/RocksKV.hh\n  kv/NoKV.cc    kv/NoKV.hh\n  kv/kv.hh\n  misc/longstring.cc misc/longstring.hh\n  misc/fusexrdlogin.cc misc/fusexrdlogin.hh\n  misc/RunningPidScanner.cc misc/RunningPidScanner.hh\n  misc/ConcurrentMount.cc misc/ConcurrentMount.hh\n  data/cache.cc data/cache.hh data/bufferll.hh\n  data/diskcache.cc data/diskcache.hh\n  data/memorycache.cc data/memorycache.hh\n  data/journalcache.cc data/journalcache.hh\n  data/cachesyncer.cc data/cachesyncer.hh\n  data/xrdclproxy.cc data/xrdclproxy.hh\n  data/dircleaner.cc data/dircleaner.hh\n  backend/backend.cc backend/backend.hh\n  ${CMAKE_SOURCE_DIR}/common/ShellCmd.cc\n  ${CMAKE_SOURCE_DIR}/common/ShellExecutor.cc\n  submount/SubMount.cc submount/SubMount.hh\n  ${PROTO_SRC} ${PROTO_HEADER})\n\ntarget_link_libraries(eosxd-objects PUBLIC\n  ROCKSDB::ROCKSDB\n  LIBEVENT::LIBEVENT\n  OpenSSL::SSL\n  PROTOBUF::PROTOBUF\n  JSONCPP::JSONCPP\n  XROOTD::PRIVATE\n  XROOTD::UTILS\n  GOOGLE::SPARSEHASH\n  ZMQ::ZMQ\n  fmt::fmt-header-only)\n\nif (FUSE3_FOUND)\n#-------------------------------------------------------------------------------\n# eosxd common objects, shared between tests and main code.\n# An object library prevents having to compile them twice.\n#-------------------------------------------------------------------------------\nadd_library(eosxd3-objects OBJECT\n  eosxd/eosfuse.cc eosxd/eosfuse.hh\n  stat/Stat.cc stat/Stat.hh\n  md/md.cc md/md.hh\n  cap/cap.cc cap/cap.hh\n  data/data.cc data/data.hh\n  kv/RocksKV.cc kv/RocksKV.hh\n  kv/NoKV.cc    kv/NoKV.hh\n  kv/kv.hh\n  misc/longstring.cc misc/longstring.hh\n  misc/fusexrdlogin.cc misc/fusexrdlogin.hh\n  misc/RunningPidScanner.cc misc/RunningPidScanner.hh\n  misc/ConcurrentMount.cc misc/ConcurrentMount.hh\n  data/cache.cc data/cache.hh data/bufferll.hh\n  data/diskcache.cc data/diskcache.hh\n  data/memorycache.cc data/memorycache.hh\n  data/journalcache.cc data/journalcache.hh\n  data/cachesyncer.cc data/cachesyncer.hh\n  data/xrdclproxy.cc data/xrdclproxy.hh\n  data/dircleaner.cc data/dircleaner.hh\n  backend/backend.cc backend/backend.hh\n  ${CMAKE_SOURCE_DIR}/common/ShellCmd.cc\n  ${CMAKE_SOURCE_DIR}/common/ShellExecutor.cc\n  submount/SubMount.cc submount/SubMount.hh\n  ${PROTO_SRC} ${PROTO_HEADER})\n\ntarget_link_libraries(eosxd3-objects PUBLIC\n  ROCKSDB::ROCKSDB\n  LIBEVENT::LIBEVENT\n  OpenSSL::SSL\n  PROTOBUF::PROTOBUF\n  JSONCPP::JSONCPP\n  XROOTD::PRIVATE\n  XROOTD::UTILS\n  GOOGLE::SPARSEHASH\n  ZMQ::ZMQ\n  fmt::fmt-header-only)\nendif()\n\nif (FUSE3_FOUND)\n  target_link_libraries(eosxd3-objects PUBLIC FUSE3::FUSE3)\n  set_target_properties(eosxd3-objects\n    PROPERTIES COMPILE_FLAGS ${EOSXD3_COMPILE_FLAGS})\nendif()\n\ntarget_link_libraries(eosxd-objects PUBLIC FUSE::FUSE)\n\nset_target_properties(eosxd-objects\n  PROPERTIES COMPILE_FLAGS ${EOSXD_COMPILE_FLAGS})\n\n#-------------------------------------------------------------------------------\n# eosxd executables\n#-------------------------------------------------------------------------------\nadd_executable(eosxd eosxd/main.cc)\n\ntarget_link_libraries(eosxd PRIVATE\n  eosxd-objects\n  ${EOSXD_COMMON_LINK_LIBRARIES})\n\nif(Linux)\n  target_link_libraries(eosxd PRIVATE JEMALLOC::JEMALLOC)\nendif()\n\nset_target_properties(eosxd\n  PROPERTIES COMPILE_FLAGS ${EOSXD_COMPILE_FLAGS})\n\ninstall(TARGETS eosxd\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR})\n\ninstall(FILES eosfusebind\n  DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n  PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ\n  GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ )\n\n#-------------------------------------------------------------------------------\n# eoscfsd executables\n#-------------------------------------------------------------------------------\n\nif (FUSE3_FOUND)\n  set(EOSCFSD_COMPILE_FLAGS\n    \"-DFUSE_MOUNT_VERSION=${FUSE_MOUNT_VERSION}0 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -DVERSION=\\\\\\\"${VERSION}\\\\\\\"\")\n\n  set(EOSCFSD_COMMON_LINK_LIBRARIES\n    EosFuseAuth\n    EosCommon\n    XROOTD::CL\n    XROOTD::UTILS\n    PROTOBUF::PROTOBUF\n    JSONCPP::JSONCPP\n    LIBBFD::LIBBFD)\n\n  add_library(eoscfsd-objects OBJECT\n    eoscfsd/cfslogin.cc eoscfsd/cfslogin.hh\n    eoscfsd/cfsrecycle.cc eoscfsd/cfsrecycle.hh\n    stat/Stat.cc stat/Stat.hh\n    ../common/ShellCmd.cc ../common/ShellCmd.hh\n    ../common/ShellExecutor.cc ../common/ShellExecutor.hh)\n\n  set_target_properties(eoscfsd-objects\n    PROPERTIES COMPILE_FLAGS ${EOSCFSD_COMPILE_FLAGS})\n\n  target_link_libraries(eoscfsd-objects PUBLIC\n    FUSE3::FUSE3\n    XROOTD::PRIVATE\n    XROOTD::UTILS\n    JSONCPP::JSONCPP\n    fmt::fmt-header-only )\n\n  add_executable(eoscfsd eoscfsd/eoscfsd.cc)\n  add_executable(eosxd3 eosxd/main.cc)\n\n  target_link_libraries(eoscfsd PUBLIC\n    eoscfsd-objects\n    JEMALLOC::JEMALLOC\n    ${EOSCFSD_COMMON_LINK_LIBRARIES})\n\n  set_target_properties(eoscfsd\n    PROPERTIES COMPILE_FLAGS ${EOSCFSD_COMPILE_FLAGS})\n\n  install(TARGETS eoscfsd\n    RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR})\n\n  target_link_libraries(eosxd3 PUBLIC\n    eosxd3-objects\n    JEMALLOC::JEMALLOC\n    ${EOSXD_COMMON_LINK_LIBRARIES})\n\n  set_target_properties(eosxd3\n    PROPERTIES COMPILE_FLAGS ${EOSXD3_COMPILE_FLAGS})\n\n  install(TARGETS eosxd3\n    RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR})\nendif()\n"
  },
  {
    "path": "fusex/README.md",
    "content": "eosxd\n=====\n\nWarning\n-------\n\nTo have *eosxd* working properly with many writers you have to modify the MGM configuration file `/etc/xrd.cf.mgm` with the nolock option: `all.export / nolock`\n\nConfiguration File\n------------------\nThe configuration file name for an unnamed instance is `/etc/eos/fuse.conf`.\nThe configuration file for a named instance is `/etc/eos/fuse.<name>.conf`.\n\nYou can select a named instance adding `'-ofsname=<name>'` to the argument list.\n\nThis\n\n```json\n{\n  \"name\" : \"\",\n  \"hostport\" : \"localhost:1094\",\n  \"remotemountdir\" : \"/eos/\",\n  \"localmountdir\" : \"/eos/\",\n  \"statisticfile\" : \"stats\",\n  \"mdcachedir\" : \"/var/cache/eos/fusex/md\",\n  \"mdzmqtarget\" : \"tcp://localhost:1100\",\n  \"mdzmqidentity\" : \"eosxd\",\n  \"appname\" : \"\",\n  \"encryptionkey\" : \"\",\n  \"options\" : {\n    \"debug\" : 1,\n    \"debuglevel\" : 4,\n    \"jsonstats\" : 1,\n    \"backtrace\" : 1,\n    \"libfusethreads\" : 0,\n    \"hide-versions\" : 1,\n    \"protect-directory-symlink-loops\" : 0,\n    \"md-kernelcache\" : 1,\n    \"md-kernelcache.enoent.timeout\" : 0,\n    \"md-backend.timeout\" : 86400,\n    \"md-backend.put.timeout\" : 120,\n    \"data-kernelcache\" : 1,\n    \"rename-is-sync\" : 1,\n    \"rmdir-is-sync\" : 0,\n    \"global-flush\" : 0,\n    \"flush-wait-open\" : 1, // 1 = flush waits for open when updating - 2 = flush waits for open when creating - 0 flush never waits\n    \"flush-wait-open-size\" : 262144 , // file size for which we force to wait that files are opened on FSTs\n    \"flush-wait-umount\" : 120, // seconds to wait for write-back data to be flushed out before terminating the mount - 0 disables waiting for flush\n    \"flush-nowait-executables\" : [ \"/tar\", \"/touch\" ],\n    \"global-locking\" : 1,\n    \"fd-limit\" : 524288,\n    \"no-fsync\" : [ \".db\", \".db-journal\", \".sqlite\", \".sqlite-journal\", \".db3\", \".db3-journal\", \"*.o\" ],\n    \"overlay-mode\" : \"000\",\n    \"rm-rf-protect-levels\" : 0,\n    \"rm-rf-bulk\" : 0,\n    \"show-tree-size\" : 0,\n    \"cpu-core-affinity\" : 1,\n    \"no-xattr\" : 1,\n    \"no-eos-xattr-listing\" : 0,\n    \"no-link\" : 0,\n    \"nocache-graceperiod\" : 5,\n    \"leasetime\" : 300,\n    \"write-size-flush-interval\" : 10,\n    \"submounts\" : 0,\n    \"inmemory-inodes\" : 16384,\n    \"tmp-fake-rename\" : false,\n  },\n  \"auth\" : {\n    \"shared-mount\" : 1,\n    \"krb5\" : 1,\n    \"gsi-first\" : 0,\n    \"sss\" : 1,\n    \"ssskeytab\" : \"/etc/eos/fuse.sss.keytab\",\n    \"sssEndorsement\" : \"\",\n    \"oauth2\" : 1,\n    \"ztn\" : 1,    \t     \n    \"unix\" : 0,\n    \"unix-root\" : 0,    \t  \n    \"environ-deadlock-timeout\" : 100,\n    \"forknoexec-heuristic\" : 1\n  },\n  \"inline\" : {\n    \"max-size\" : 0,\n    \"default-compressor\" : \"none\"\n  },\n  \"fuzzing\" : {\n    \"open-async-submit\" : 0,\n    \"open-async-return\" : 0,\n    \"open-async-submit-fatal\" : 0,\n    \"open-async-return-fatal\" : 0,\n    \"read-async-submit\" : 0\n  }\n}\n```\n\nYou also need to define a local cache directory (location) where small files are cached and an optional journal directory to improve the write speed (journal).\n\n```json\n  \"cache\" : {\n    \"type\" : \"disk\",\n    \"size-mb\" : 512,\n    \"size-ino\" : 65536,\n    \"journal-mb\" : 2048,\n    \"journal-ino\" : 65536,\n    \"clean-threshold\" : 85.0,\n    \"location\" : \"/var/cache/eos/fusex/cache/\",\n    \"journal\" : \"/var/cache/eos/fusex/journal/\",\n    \"read-ahead-strategy\" : \"dynamic\",\n    \"read-ahead-bytes-nominal\" : 262144,\n    \"read-ahead-bytes-max\" : 1048576,\n    \"read-ahead-blocks-max\" : 16,\n    \"read-ahead-sparse-ratio\" : 0.0,\n    \"max-read-ahead-buffer\" : 134217728,\n    \"max-write-buffer\" : 134217728,\n    \"rescue-cache-files\" : 0,\n  }\n\n```\n\nThe available read-ahead strategies are 'dynamic', 'static' or 'none'. Dynamic read-ahead doubles the read-ahead window from nominal to max if the strategy provides cache hits. The default is a dynamic read-ahead starting with 512kb and using 2,4,8,16 blocks resizing blocks up to 2M.\n\nThe daemon automatically appends a directory to the mdcachedir, location and journal path and automatically creates these directory private to root (mode=700).\n\nYou can modify some of the XrdCl variables, however it is recommended not to change these:\n\n```json\n  \"xrdcl\" : {\n    \"TimeoutResolution\" : 1,\n    \"ConnectionWindow\": 10,\n    \"ConnectionRetry\" : 0,\n    \"StreamErrorWindow\" : 120,\n    \"RequestTimeout\" : 60,\n    \"StreamTimeout\" : 120,\n    \"RedirectLimit\" : 3,\n    \"LogLevel\" : \"None\"\n  },\n\n```\n\nThe recovery settings are defined in the following section:\n\n```json\n   \"recovery\" : {\n     \"read-open\" : 1,\n     \"read-open-noserver\" : 1,\n     \"read-open-noserver-retrywindow\" : 15,\n     \"write-open\" : 1,\n     \"write-open-noserver\" : 1,\n     \"write-open-noserver-retrywindow\" : 15\n   }\n```\n\nIt is possible to overwrite the settings of any standard config files using a second configuration file:\n`/etc/eos/fuse.local.conf` or `/etc/eos/fuse.<name>.local.conf`.\nThis is usefule to ship a standard configuration via a package and give users the opportunity to change individual parameters.\n\n\nConfiguration default values and avoiding configuration files\n-------------------------------------------------------------\n\nEvery configuration value has a corresponding default value .\nAs explained the configuration file name is taken from the fsname option given on the command line:\n\n```shell\nroot> eosxd -ofsname=foo loads /etc/eos/fuse.foo.conf\nroot> eosxd              loads /etc/eos/fuse.conf\n\nuser> eosxd -ofsname=foo loads $HOME/.eos/fuse.foo.conf\n```\n\nOne can avoid to use configuration files if the defaults are fine providing the remote host and remote mount directory via the fsname:\n\n```shell\nroot> eosxd -ofsname=eos.cern.ch:/eos/ $HOME/eos # mounts the /eos/ directory from eos.cern.ch shared under $HOME/eos/\n\nuser> eosxd -ofsname=user@eos.cern.ch:/eos/user/u/user/ $home/eos # mounts /eos/user/u/user from eos.cern.ch private under $HOME/eos/\n```\n\nIf this is a user-private mount the syntax 'foo@cern.ch' should be used to distinguish private \\\nmounts of individual users in the 'df' output\n\nPlease note, that root mounts are by default shared mounts with kerberos configuration, user mounts are private mounts with kerberos configuration\n\nStatistics File\n---------------\n\nThe *stat* file contains rate and execution average time counting, and other metrics regarding the execution of the eos mount.\nBy enabling 'options.jsonstats 'in the mount configuration file one can find `/var/log/eos/fusex/fuse.<name>.stats.json` with the metric in a JSON format, useful to serve as a monitoring input. Checkout the JSON output [here](fusex/fuse.example.stats.json)\n```JSON\n{\n  \"name\" : \"\",\n  \"hostport\" : \"localhost:1094\",\n  \"remotemountdir\" : \"/eos/\",\n  \"localmountdir\" : \"/eos/\",\n  \"options\" : {\n    \"jsonstats\" : 1\n  }\n}\n```\n\n```shell\nbash> cat /var/log/eos/fusex/fuse.stats\nALL      Execution Time                   0.00 +- 0.00\n# -----------------------------------------------------------------------------------------------------------\nwho      command                          sum             5s     1min     5min       1h exec(ms) +- sigma(ms)\n# -----------------------------------------------------------------------------------------------------------\nALL        :sum                                        0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        access                                      0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        create                                      0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        flush                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        forget                                      0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        fsync                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        getattr                                     0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        getxattr                                    0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        listxattr                                   0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        lookup                                      0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        mkdir                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        mknod                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        open                                        0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        opendir                                     0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        read                                        0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        readdir                                     0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        readlink                                    0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        release                                     0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        releasedir                                  0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        removexattr                                 0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        rename                                      0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        rm                                          0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        rmdir                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        setattr                                     0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        setattr:chmod                               0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        setattr:chown                               0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        setattr:truncate                            0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        setattr:utimes                              0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        setxattr                                    0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        statfs                                      0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        symlink                                     0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        unlink                                      0     0.00     0.00     0.00     0.00     -NA- +- -NA-\nALL        write                                       0     0.00     0.00     0.00     0.00     -NA- +- -NA-\n# -----------------------------------------------------------------------------------------------------------\nALL        inodes              := 1\nALL        inodes-todelete     := 0\nALL        inodes-backlog      := 0\nALL        inodes-ever         := 1\nALL        inodes-ever-deleted := 0\nALL        inodes-open         := 0\nALL        inodes-vmap         := 1\nALL        inodes-caps         := 0\n# -----------------------------------------------------------------------------------------------------------\nALL        threads             := 17\nALL        visze               := 336.41 Mb\nAll        rss                 := 53.10 Mb\nAll        wr-buf-inflight     := 0 b\nAll        wr-buf-queued       := 0 b\nAll        ra-buf-inflight     := 0 b\nAll        ra-buf-queued       := 0 b\nAll        rd-buf-inflight     := 0 b\nAll        rd-buf-queued       := 0 b\nAll        version             := 4.2.11\nALl        fuseversion         := 28\nAll        starttime           := 1517583072\nAll        uptime              := 1\nAll        instance-url        := apeters.cern.ch\n# -----------------------------------------------------------------------------------------------------------\n```\n\nMounting with configuration files\n---------------------------------\n\n```shell\n# mount on /eos/\nmount -t fuse eosxd /eos/\n\n# umount /eos/\numount -f /eos/\n\n# run the default mount in foreground mode\neosxd -f\n\n# run the default mount to a different mount directory from the JSON configuration\neosxd -f /other/\n\n# run the default mount in background mode\neosxd\n\n# mount without configuration files and default values\nmount -t fuse -ofsname=eos.cern.ch:/eos/scratch /eos/scratch\n\n# run without configuration files in foreground\neosxd -ofsname=eos.cern.ch:/eos/scratch /eos/scratch\n\n# run a usermount without configuration in background\neosxd -ofsname=me@eos.cern.ch:/eos/user/m/me/ $HOME/eos/\n\n```\n\nMounting with sss credentials\n-----------------------------\n\nUse these authentication directives in the config file:\n```json\n  \"auth\" : {\n    \"shared-mount\" : 1,\n    \"krb5\" : 0,\n    \"gsi-first\" : 1,\n    \"sss\" : 1\n  }\n```\nThe mount daemon uses /etc/fuse/fuse.sss.keytab as default keytab when running as a shared mount. The user mount default is $HOME/.eos/fuse.sss.keytab. Unlike Kerberos it is not possible in XRootD to use different keytabs for individual users. If you want to create a 'trusted' mount mapping local users to their local username, you have to create an sss keytab entry for user **anybody** and group **anygroup**. Otherwise you can create an sss keytab for a given application user.\nThe mount also supports to forward sss endorsements, which are forwarded to the server. These endorsement can be used server-side to define an ACL entry by key e.g. sys.acl=\"k:9c2bd333-5331-4095-8fcd-28726404742f:rwx\". This would provide access to all sss clients having this key in their environment even if the mapped sss user/group wouldn't have access. Another type of endorsement can be EosToken, which are read from XrdSecsssENDORSEMENT environemnt variables. You can also configure a mount with a static endorsement using \"auth\":\"sssEndorsement\" : zteos:... which will be used by all users on this mount using sss authentication.\n\n\nMounting for UNIX gateways\n--------------------------\n\n\nUse these authentication directives in the config files:\n```json\n  \"auth\" : {\n    \"shared-mount\" : 1,\n    \"unix\" : 1,\n    \"unix-root\" : 0,\n    \"sss\" : 1\n  }\n```\nIf you enable UNIX, it will be used for everbody but root (uid=0). In this example root will fall back to sss authentication. If you don't specify any other, root will also use UNIX and will be mapped to nobody server side.\n\nIf you define \"unix-root\":1 it will also be used for the root user!\n\n\n\nAUTOFS Configuration\n--------------------\n\nMake sure you have in `/etc/autofs.conf` :\n```\nbrowse_mode = yes\n```\nAdd this line to `/etc/auto.master` to configure automount for the directory `/eos/` :\n```\n/eos/  /etc/auto.eos\n```\nCreate the directory `/eos` (should be empty).\n\nCreate the file `/etc/auto.eos` to mount f.e. from instance `eos.cern.ch` the remote path `/eos/user/` under local `/eos/scratch` :\n```\nscratch -fstype=eosx,fsname=eos.cern.ch:/eos/user/ :eosxd\n```\n\n\nWeb/NFS/Samba Gateway Configuration\n-----------------------------------\n\nTo run eosxd for gateways you can specify the gateway type. eosxd will optimize internal settings for the referenced gateway type.\n\n```shell\n# WebServer, NFS etc...\neosxd -ofsname=gw@eos.cern.ch:/eos/user/ /eos/user/\n\n# CIFS(Samba)\neosxd -ofsname=smb@eos.cern.ch:/eos/user/ /eos/user/\n```\n\n\nClient Interaction with a FUSE mount\n------------------------------------\n\neosxd provides a command line interface to interact with mounts (see eosxd -h):\n\n```shell\n# eosxd -h \nusage CLI   : eosxd get <key> [<path>]\n\n                     eos.btime <path>                   : show inode birth time\n                     eos.ttime <path>                   : show lastest mtime in tree\n                     eos.tsize <path>                   : show size of directory tree\n                     eos.dsize <path>                   : show total size of files inside a directory\n\t\t     eos.checksum <path>                : show path checksum if defined\n                     eos.name <path>                    : show EOS instance name for given path\n                     eos.md_ino <path>                  : show inode number valid on MGM \n                     eos.hostport <path>                : show MGM connection host + port for given path\n                     eos.mgmurl <path>                  : show MGM URL for a given path\n                     eos.stats <path>                   : show mount statistics\n                     eos.stacktrace <path>              : test thread stack trace functionality\n                     eos.quota <path>                   : show user quota information for a given path\n                     eos.reconnect <mount>              : reconnect and dump the connection credentials\n                     eos.reconnectparent <mount>        : reconnect parent process and dump the connection credentials\n                     eos.identity <mount>               : show credential assignment of the calling process\n                     eos.identityparent <mount>         : show credential assignment of the executing shell\n\n as root             system.eos.md  <path>              : dump meta data for given path\n                     system.eos.cap <path>              : dump cap for given path\n                     system.eos.caps <mount>            : dump all caps\n                     system.eos.vmap <mount>            : dump virtual inode translation table\n\nusage CLI   : eosxd set <key> <value> [<path>]\n\n as root             system.eos.debug <level> <mount>   : set debug level with <level>=crit|warn|err|notice|info|debug|trace\n                     system.eos.dropcap - <mount>       : drop capability of the given path\n                     system.eos.dropcaps - <mount>      : drop call capabilities for given mount\n                     system.eos.resetstat - <mount>     : reset the statistic counters\n                     system.eos.log <mode> <mount>      : make log file public or private with <mode>=public|private\n\nusage FS    : eosxd -ofsname=<host><remote-path> <mnt-path>\n                     eosxd -ofsname=<config-name> <mnt-path>\n                        with configuration file /etc/eos/fuse.<config-name>.conf\n                     mount -t fuse eosxd -ofsname=<host><remote-path> <mnt-path>\n                     mount -t fuse eosxd -ofsname=<config-name> <mnt-path>\n\nusage HELP  : eosxd [-h|--help|help]                    : get help\n\n```\n\nThe CLI uses the following extended attribute interfaces internally:\n\nTo change the log configuration do as root:\n```shell\n# setfattr -n system.eos.debug -v info <path>\n# setfattr -n system.eos.debug -v debug <path>\n# setfattr -n system.eos.debug -v notice <path>\n# enable low-level file tracing on flush, disable by setting anything but debug\n# setfattr -n system.eos.debug -v trace <path>\n```\n\nTo display the local meta data record do as root\n```shell\n# getfattr --only-values -n system.eos.md <path>\n```\n\nTo display a capability on a path do as root\n```shell\n# getfattr --only-values -n system.eos.cap <path>\n```\n\nTo display a list of all capabilities on a path do as root\n```shell\n# getfattr --only-values -n system.eos.caps <any-path>\n```\n\nTo display a list of local to remote inode translations\n```shell\n# getfattr --only-values -n system.eos.vmap <any-path>\n```\n\nTo drop a capability on a path do as root\n```shell\n# setfattr -n system.eos.dropcap <path>\n```\n\nTo drop all capabilities on a mount do as root\n```shell\n# setfattr -n system.eos.dropallcap <any-path>\n```\n\nShow all hidden system attributes on a given path\n```shell\n$ getfattr -d -m - <path>\n```\n\nInspect which set of credentials have been assigned to the calling process:\n```shell\n$ getfattr --only-values -n eos.identity <any-path>\n```\n\nInspect which set of credentials have been assigned to the parent of the calling process,\nin this case this would be the shell:\n```shell\n$ getfattr --only-values -n eos.identityparent <any-path>\n```\n\nInvalidate current set of credentials assigned to the calling process, and reconnect\nwhile printing a detailed log of what's happening:\n```shell\n$ getfattr --only-values -n eos.reconnect <any-path>\n```\n\nInvalidate current set of credentials assigned to the parent of the calling process\n(in this case, the shell), and reconnect while printing a detailed log of what's happening.\n```shell\n$ getfattr --only-values -n eos.reconnectparent <any-path>\n```\n\nReset the statis counters on a mount as root\n```shell\n# setfattr -n system.eos.resetstat <any-path>\n```\n\nVirtual extended attributes on a FUSE mount\n-------------------------------------------\n\nDisplay instance name\n```shell\n# getfattr --only-values -n eos.name /eos/\n```\n\nDisplay MGM hostname+port\n```shell\n# getfattr --only-values -n eos.hostport /eos/\n```\n\nDisplay MGM url\n```shell\n# getfattr --only-values -n eos.mgmurl /eos/\n```\n\nDisplay Quota Information for a given path\n```shell\n# getfattr --only-values -n eos.quota <path>\n```\n\nDisplay Checksum value for a given path\n```shell\n# getfattr --only-values -n eos.checksum <path>\n```\n\nServer Interaction with a FUSE mount\n------------------------------------\n\n```shell\nEOS Console [root://localhost] |/eos/dev/fusetest/workspace/senf/> fusex\nusage: fusex ls [-c] [-n] [-z] [-a] [-m] [-s]                        :  print statistics about eosxd fuse clients\n                -c                                                   -  break down by client host\n                -a                                                   -  print all\n                -s                                                   -  print summary for clients\n                -m                                                   -  print in monitoring format <key>=<value>\n\n       fuxex evict <uuid> [<reason>]                                 :  evict a fuse client\n                                                              <uuid> -  uuid of the client to evict\n                                                            <reason> -  optional text shown to the client why he has been evicted\n\n       fusex dropcaps <uuid>                                         :  advice a client to drop all caps\n\n       fusex droplocks <uuid>                                        :  advice a client to drop all locks\n\n       fusex caps [-t | -i | -p [<regexp>] ]                         :  print caps\n                -t                                                   -  sort by expiration time\n                -i                                                   -  sort by inode\n                -p                                                   -  display by path\n                -t|i|p <regexp>>                                     -  display entries matching <regexp> for the used filter type\nexamples:\n           fusex caps -i ^0000abcd$                                  :  show caps for inode 0000abcd\n           fusex caps -p ^/eos/$                                     :  show caps for path /eos\n           fusex caps -p ^/eos/caps/                                 :  show all caps in subtree /eos/caps\n\n```\n\n\nFUSE clients interaction with the EOS quota subsystem\n------------------------------------------------------\n\nUsers doing transfers via the FUSE client need to be aware of some restrictions that apply when the EOS instances they interact with has quota enabled. Checking if quota is enabled on the MGM is done with the following command:\n\n```\neos space status default | grep quota\nquota  := on\n```\n\nBy default for any new write transfer coming from the FUSE client, EOS will try to pre-book at least 5GB of disk space. Therefore, the minimum logical quota that a user needs, to be able to write to the instance is 5GB. This booking value can be modifed by setting the following environment variable for the MGM in `/etc/sysconfig/eos_env`:\n\n```\n# Default 5GB\nEOS_MGM_FUSE_BOOKING_SIZE=5368709120\n```\n\nAllow traversing of directories without 'x' mode\n-------------------------------------------------\n\nTo allow on a mount to cd into directories without 'x' mode bit for the user you can define 'overlay-mode' to allow the access & stat function to work for directories without 'x' bit.\nThis is necessary e.g. on a Samba mount to reach a subfolder, which you made accessibla to another person without granting 'x' mode on all parent folders. It is enough to use \"1\" as overlay mode.\n\n\nFile Obfuscation\n----------------\n\nIt is possible to obfuscate files inside the mount client using random per file 256-bit key. The original contents of the files is only\nvisible using the mount client, all other protocols see obfuscated contents. Obfuscation is enabled per directory defining an extended attribute:\n\n```shell\n$ attr set sys.file.obfuscate=\"1\" <mydir>\n```\n\nThe obfuscation key is stored on each file as an extended user argument, which cannot be displayed:\n```\nuser.obfuscate.key=\n```\n\n\nFile Encryption\n---------------\n\nIt is possible to combine obfuscation with encryption. To encrypt files using a 256-bit key you just do:\n\n```shell\n# set the encryption key in your shell environment\nexport EOS_FUSE_SECRET=e117c22d-a844-4f53-89d3-43a21e9cdaea\n\n# define a directory to be obfuscated\nattr set sys.file.obfuscate=\"1\" <mydir>\n\n# copy a file into an obfuscating directory\ncp <myfile> <mydir>\n# the file contents is now obfuscate using an hmac mechanism\n\n# the file can be read not obfuscated if the correct secret is set in the process environment\ncp <mydir>/<myfile> /tmp/\n```\n\nEncrypted files are flagged with an extended attribute:\n\n```\nuser.encrypted=1\n```\n\nIf files have been encrypted through eosxd, an additional attribute\n```\nuser.encrypted.fp=int16\n```\nstores a low resolution key fingerprint which is checked during open. If the key does not match ENOKEY is returned as errno. File can be encrypted with an absent fingerprint attribute (e.g. using eoscp). In this case no key correctness check is done client side.\nIf you have read such a file and change the key to re-read the file, you might need to drop the buffer cache contents to force a re-read.\n\nIf you lose EOS_FUSE_SECRET for a given file, there is no way to decrypt the contents since the key is stored nowhereelse.\n\n```shell\n# eos fileinfo myfile\n\nEOS Console [root://localhost] |/eos/dev/encryption/> fileinfo enc.32\n  File: '/eos/dev/encryption/enc.1'  Flags: 0644\n  Size: 2195\nModify: Mon Jan 24 15:55:29 2022 Timestamp: 1643036129.045484252\nChange: Mon Jan 24 15:56:28 2022 Timestamp: 1643036188.645107946\n Birth: Mon Jan 24 15:54:58 2022 Timestamp: 1643036098.235424071\n  CUid: 99 CGid: 99 Fxid: 001b7800 Fid: 1800192 Pid: 384815 Pxid: 0005df2f\nXStype: adler    XS: 55 91 03 04    ETAGs: \"483235360407552:55910304\"\nLayout: plain Stripes: 1 Blocksize: 4k LayoutId: 00100002 Redundancy: d1::t0\n  #Rep: 1\n Crypt: encrypted\n```\nshows in a comprehensive way after the \"Crypt:\" tag, if a file is either encrypted or obfuscated.\n\nIf encryption is to be applied for all users in the same way sharing the same key, it is possible to define the encryption key in the FUSE configuration file.  The configuration file has to have mode 0400 in this case and has to be owned by root:root for shared mounts, or the mounting user/group id for private mounts. The configuration parameter is called \"encryptionkey\" in the main section of the JSON configuration file. By default it is empty as shown above.\n"
  },
  {
    "path": "fusex/auth/AuthenticationGroup.cc",
    "content": "//------------------------------------------------------------------------------\n// File: AuthenticationGroup.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"CredentialValidator.hh\"\n#include \"AuthenticationGroup.hh\"\n#include \"ProcessCache.hh\"\n#include \"ProcessInfo.hh\"\n#include \"UserCredentialFactory.hh\"\n#include \"CredentialFinder.hh\"\n#include \"CredentialValidator.hh\"\n#include \"UuidStore.hh\"\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nAuthenticationGroup::AuthenticationGroup(const CredentialConfig& config_)\n  : config(config_)\n{\n  processCache(); // need to call the constructors in the init phase\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nAuthenticationGroup::~AuthenticationGroup() {}\n\n//------------------------------------------------------------------------------\n// Retrieve process cache, lazy initialize\n//------------------------------------------------------------------------------\nProcessCache* AuthenticationGroup::processCache()\n{\n  if (!processCachePtr) {\n    processCachePtr.reset(new ProcessCache(config, *boundIdentityProvider(),\n                                           *processInfoProvider(), *jailResolver()));\n  }\n\n  return processCachePtr.get();\n}\n\n//------------------------------------------------------------------------------\n// Retrieve bound identity provider, lazy initialize\n//------------------------------------------------------------------------------\nBoundIdentityProvider* AuthenticationGroup::boundIdentityProvider()\n{\n  if (!boundIdentityProviderPtr) {\n    boundIdentityProviderPtr.reset(new BoundIdentityProvider(\n                                     *securityChecker(), *environmentReader(), *credentialValidator()));\n    boundIdentityProviderPtr->setCredentialConfig(config);\n  }\n\n  return boundIdentityProviderPtr.get();\n}\n\n//------------------------------------------------------------------------------\n// Retrieve process info provider, lazy initialize\n//------------------------------------------------------------------------------\nProcessInfoProvider* AuthenticationGroup::processInfoProvider()\n{\n  if (!processInfoProviderPtr) {\n    processInfoProviderPtr.reset(new ProcessInfoProvider());\n  }\n\n  return processInfoProviderPtr.get();\n}\n\n//------------------------------------------------------------------------------\n// Retrieve jail resolver, lazy initialize\n//------------------------------------------------------------------------------\nJailResolver* AuthenticationGroup::jailResolver()\n{\n  if (!jailResolverPtr) {\n    jailResolverPtr.reset(new JailResolver());\n  }\n\n  return jailResolverPtr.get();\n}\n\n//------------------------------------------------------------------------------\n// Retrieve security checker, lazy initialize\n//------------------------------------------------------------------------------\nSecurityChecker* AuthenticationGroup::securityChecker()\n{\n  if (!securityCheckerPtr) {\n    securityCheckerPtr.reset(new SecurityChecker(\n                               config.ignore_containerization));\n  }\n\n  return securityCheckerPtr.get();\n}\n\n//------------------------------------------------------------------------------\n// Retrieve environment reader, lazy initialize\n//------------------------------------------------------------------------------\nEnvironmentReader* AuthenticationGroup::environmentReader()\n{\n  if (!environmentReaderPtr) {\n    environmentReaderPtr.reset(new EnvironmentReader(10));\n  }\n\n  return environmentReaderPtr.get();\n}\n\n//------------------------------------------------------------------------------\n// Retrieve credential validator, lazy initialize\n//------------------------------------------------------------------------------\nCredentialValidator* AuthenticationGroup::credentialValidator()\n{\n  if (!credentialValidatorPtr) {\n    credentialValidatorPtr.reset(new CredentialValidator(*securityChecker(),\n                                 *uuidStore()));\n  }\n\n  return credentialValidatorPtr.get();\n}\n\n//------------------------------------------------------------------------------\n// Retrieve uuid store, lazy initialize\n//------------------------------------------------------------------------------\nUuidStore* AuthenticationGroup::uuidStore()\n{\n  if (!uuidStorePtr) {\n    uuidStorePtr.reset(new UuidStore(config.credentialStore));\n  }\n\n  return uuidStorePtr.get();\n}\n\n//------------------------------------------------------------------------------\n// Retrieve user credential factory, lazy initialize\n//------------------------------------------------------------------------------\nUserCredentialFactory* AuthenticationGroup::userCredentialFactory()\n{\n  if (!userCredentialFactoryPtr) {\n    userCredentialFactoryPtr.reset(new UserCredentialFactory(config));\n  }\n\n  return userCredentialFactoryPtr.get();\n}\n\n"
  },
  {
    "path": "fusex/auth/AuthenticationGroup.hh",
    "content": "// ----------------------------------------------------------------------\n// File: AuthenticationGroup.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_FUSEX_AUTHENTICATION_GROUP_HH\n#define EOS_FUSEX_AUTHENTICATION_GROUP_HH\n\n#include \"CredentialFinder.hh\"\n\nclass ProcessCache;\nclass BoundIdentityProvider;\nclass ProcessInfoProvider;\nclass JailResolver;\nclass SecurityChecker;\nclass EnvironmentReader;\nclass CredentialValidator;\nclass UuidStore;\nclass UserCredentialFactory;\n\n//------------------------------------------------------------------------------\n// Utility class to manage ownership of all classes involved in the\n// authentication party. Handles correct construction and deletion, and\n// lazy-initialization of objects, as-requested.\n//------------------------------------------------------------------------------\nclass AuthenticationGroup\n{\npublic:\n  //----------------------------------------------------------------------------\n  // Constructor\n  //----------------------------------------------------------------------------\n  AuthenticationGroup(const CredentialConfig& config);\n\n  //----------------------------------------------------------------------------\n  // Destructor\n  //----------------------------------------------------------------------------\n  ~AuthenticationGroup();\n\n  //----------------------------------------------------------------------------\n  // Retrieve process cache, lazy initialize\n  //----------------------------------------------------------------------------\n  ProcessCache* processCache();\n\n  //----------------------------------------------------------------------------\n  // Retrieve bound identity provider, lazy initialize\n  //----------------------------------------------------------------------------\n  BoundIdentityProvider* boundIdentityProvider();\n\n  //----------------------------------------------------------------------------\n  // Retrieve process info provider, lazy initialize\n  //----------------------------------------------------------------------------\n  ProcessInfoProvider* processInfoProvider();\n\n  //----------------------------------------------------------------------------\n  // Retrieve jail resolver, lazy initialize\n  //----------------------------------------------------------------------------\n  JailResolver* jailResolver();\n\n  //----------------------------------------------------------------------------\n  // Retrieve security checker, lazy initialize\n  //----------------------------------------------------------------------------\n  SecurityChecker* securityChecker();\n\n  //----------------------------------------------------------------------------\n  // Retrieve environment reader, lazy initialize\n  //----------------------------------------------------------------------------\n  EnvironmentReader* environmentReader();\n\n  //----------------------------------------------------------------------------\n  // Retrieve credential validator, lazy initialize\n  //----------------------------------------------------------------------------\n  CredentialValidator* credentialValidator();\n\n  //----------------------------------------------------------------------------\n  // Retrieve uuid store, lazy initialize\n  //----------------------------------------------------------------------------\n  UuidStore* uuidStore();\n\n  //----------------------------------------------------------------------------\n  // Retrieve user credential factory, lazy initialize\n  //----------------------------------------------------------------------------\n  UserCredentialFactory* userCredentialFactory();\n\nprivate:\n  CredentialConfig config;\n\n  std::unique_ptr<EnvironmentReader> environmentReaderPtr;\n  std::unique_ptr<SecurityChecker> securityCheckerPtr;\n  std::unique_ptr<JailResolver> jailResolverPtr;\n  std::unique_ptr<ProcessInfoProvider> processInfoProviderPtr;\n  std::unique_ptr<CredentialValidator> credentialValidatorPtr;\n  std::unique_ptr<UuidStore> uuidStorePtr;\n  std::unique_ptr<BoundIdentityProvider> boundIdentityProviderPtr;\n  std::unique_ptr<ProcessCache> processCachePtr;\n  std::unique_ptr<UserCredentialFactory> userCredentialFactoryPtr;\n};\n\n\n\n#endif\n"
  },
  {
    "path": "fusex/auth/BoundIdentityProvider.cc",
    "content": "//------------------------------------------------------------------------------\n// File: BoundIdentityProvider.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"Utils.hh\"\n#include \"ScopedEUidSetter.hh\"\n#include \"BoundIdentityProvider.hh\"\n#include \"EnvironmentReader.hh\"\n#include \"CredentialValidator.hh\"\n#include \"Logbook.hh\"\n#include <sys/stat.h>\n\nextern \"C\" {\n#include \"krb5.h\"\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nBoundIdentityProvider::BoundIdentityProvider(SecurityChecker& checker,\n    EnvironmentReader& reader, CredentialValidator& valid)\n  : environmentReader(reader), validator(valid)\n{\n}\n\n//------------------------------------------------------------------------------\n// Attempt to produce a BoundIdentity object out of KRB5 environment\n// variables. NO fallback to default paths. If not possible, return nullptr.\n//------------------------------------------------------------------------------\nstd::shared_ptr<const BoundIdentity>\nBoundIdentityProvider::krb5EnvToBoundIdentity(const JailInformation& jail,\n    const Environment& env, uid_t uid, gid_t gid, bool reconnect,\n    LogbookScope& scope)\n{\n  std::string path = env.get(\"KRB5CCNAME\");\n  std::string key = env.get(\"EOS_FUSE_SECRET\");\n\n  if (key.empty() && credConfig.encryptionKey.length()) {\n    key = credConfig.encryptionKey;\n  }\n\n  //----------------------------------------------------------------------------\n  // Kerberos keyring?\n  //----------------------------------------------------------------------------\n  if (startsWith(path, \"KEYRING\")) {\n    LOGBOOK_INSERT(scope, \"Found kerberos keyring: \" << path <<\n                   \", need to validate\");\n    return userCredsToBoundIdentity(jail,\n                                    UserCredentials::MakeKrk5(path, uid, gid, key), reconnect, scope);\n  }\n\n  //----------------------------------------------------------------------------\n  // Kerberos KCM?\n  //----------------------------------------------------------------------------\n  if (startsWith(path, \"KCM\")) {\n    LOGBOOK_INSERT(scope, \"Found kerberos kcm: \" << path << \", need to validate\");\n    return userCredsToBoundIdentity(jail,\n                                    UserCredentials::MakeKcm(path, uid, gid, key), reconnect, scope);\n  }\n\n  //----------------------------------------------------------------------------\n  // Drop FILE:, if exists\n  //----------------------------------------------------------------------------\n  const std::string prefix = \"FILE:\";\n\n  if (startsWith(path, prefix)) {\n    path = path.substr(prefix.size());\n  }\n\n  if (path.empty()) {\n    //--------------------------------------------------------------------------\n    // Early exit, no need to go through the trouble\n    // of userCredsToBoundIdentity.\n    //--------------------------------------------------------------------------\n    LOGBOOK_INSERT(scope, \"Invalid KRB5CCNAME (size: \" << path.size() << \")\");\n    return {};\n  }\n\n  LOGBOOK_INSERT(scope, \"Found KRB5CCNAME: \" << path << \", need to validate\");\n  return userCredsToBoundIdentity(jail,\n                                  UserCredentials::MakeKrb5(jail.id, path, uid, gid, key), reconnect,\n                                  scope);\n}\n\n//------------------------------------------------------------------------------\n// Attempt to produce a BoundIdentity object out of OAUTH2 environment\n// variables. NO fallback to default paths. If not possible, return nullptr.\n//------------------------------------------------------------------------------\nstd::shared_ptr<const BoundIdentity>\nBoundIdentityProvider::oauth2EnvToBoundIdentity(const JailInformation& jail,\n    const Environment& env, uid_t uid, gid_t gid, bool reconnect,\n    LogbookScope& scope)\n{\n  std::string path = env.get(\"OAUTH2_TOKEN\");\n  std::string key = env.get(\"EOS_FUSE_SECRET\");\n\n  if (key.empty() && credConfig.encryptionKey.length()) {\n    key = credConfig.encryptionKey;\n  }\n\n  //----------------------------------------------------------------------------\n  // Drop FILE:, if exists\n  //----------------------------------------------------------------------------\n  const std::string prefix = \"FILE:\";\n\n  if (startsWith(path, prefix)) {\n    path = path.substr(prefix.size());\n  }\n\n  if (path.empty()) {\n    //--------------------------------------------------------------------------\n    // Early exit, no need to go through the trouble\n    // of userCredsToBoundIdentity.\n    //--------------------------------------------------------------------------\n    LOGBOOK_INSERT(scope, \"Invalid OAUTH2_TOKEN (size: \" << path.size() << \")\");\n    return {};\n  }\n\n  LOGBOOK_INSERT(scope, \"Found OAUTH2_TOKEN: \" << path << \", need to validate\");\n  return userCredsToBoundIdentity(jail,\n                                  UserCredentials::MakeOAUTH2(jail.id, path, uid, gid, key), reconnect,\n                                  scope);\n}\n\n//------------------------------------------------------------------------------\n// Attempt to produce a BoundIdentity object out of X509 environment\n// variables. NO fallback to default paths. If not possible, return nullptr.\n//------------------------------------------------------------------------------\nstd::shared_ptr<const BoundIdentity>\nBoundIdentityProvider::x509EnvToBoundIdentity(const JailInformation& jail,\n    const Environment& env, uid_t uid, gid_t gid, bool reconnect,\n    LogbookScope& scope)\n{\n  std::string path = env.get(\"X509_USER_PROXY\");\n  std::string key = env.get(\"EOS_FUSE_SECRET\");\n\n  if (key.empty() && credConfig.encryptionKey.length()) {\n    key = credConfig.encryptionKey;\n  }\n\n  if (path.empty()) {\n    //--------------------------------------------------------------------------\n    // Early exit, no need to go through the trouble\n    // of userCredsToBoundIdentity.\n    //--------------------------------------------------------------------------\n    LOGBOOK_INSERT(scope, \"Invalid X509_USER_PROXY (size: \" << path.size() << \")\");\n    return {};\n  }\n\n  LOGBOOK_INSERT(scope, \"Found X509_USER_PROXY: \" << path <<\n                 \", need to validate\");\n  return userCredsToBoundIdentity(jail,\n                                  UserCredentials::MakeX509(jail.id, path, uid, gid, key), reconnect,\n                                  scope);\n}\n\n//------------------------------------------------------------------------------\n// Attempt to produce a BoundIdentity object out of ZTN environment\n// variables. NO fallback to default paths. If not possible, return nullptr.\n//------------------------------------------------------------------------------\nstd::shared_ptr<const BoundIdentity>\nBoundIdentityProvider::ztnEnvToBoundIdentity(const JailInformation& jail,\n    const Environment& env, uid_t uid, gid_t gid, bool reconnect,\n    LogbookScope& scope)\n{\n  std::string key = env.get(\"EOS_FUSE_SECRET\");\n  std::string btf = env.get(\"BEARER_TOKEN_FILE\");\n  std::string btfd = env.get(\"XDG_RUNTIME_DIR\");\n  std::string path;\n\n  if (btf.length()) {\n    path = btf;\n  } else if (btfd.length()) {\n    path = btfd + std::string(\"/bt_u\") + std::to_string(uid);\n  }\n\n  if (path.empty()) {\n    //--------------------------------------------------------------------------\n    // Early exit, no need to go through the trouble\n    // of userCredsToBoundIdentity.\n    //--------------------------------------------------------------------------\n    LOGBOOK_INSERT(scope, \"No bearer token file specified (size: \" << path.size() <<\n                   \")\");\n    return {};\n  }\n\n  LOGBOOK_INSERT(scope, \"Found token: \" << path << \", need to validate\");\n  return userCredsToBoundIdentity(jail,\n                                  UserCredentials::MakeZTN(jail.id, path, uid, gid, key), reconnect,\n                                  scope);\n}\n\n//------------------------------------------------------------------------------\n// Attempt to produce a BoundIdentity object out of SSS environment\n// variables. If not possible, return nullptr.\n//------------------------------------------------------------------------------\nstd::shared_ptr<const BoundIdentity>\nBoundIdentityProvider::sssEnvToBoundIdentity(const JailInformation& jail,\n    const Environment& env, uid_t uid, gid_t gid, bool reconnect,\n    LogbookScope& scope)\n{\n  std::string endorsement = env.get(\"XrdSecsssENDORSEMENT\");\n  std::string key = env.get(\"EOS_FUSE_SECRET\");\n\n  if (credConfig.sssEndorsement.length()) {\n    endorsement = credConfig.sssEndorsement;\n  }\n\n  if (key.empty() && credConfig.encryptionKey.length()) {\n    key = credConfig.encryptionKey;\n  }\n\n  LOGBOOK_INSERT(scope, \"Found SSS endorsement of size \" << endorsement.size());\n  return userCredsToBoundIdentity(jail,\n                                  UserCredentials::MakeSSS(endorsement, uid, gid, key), reconnect,\n                                  scope);\n}\n\n//------------------------------------------------------------------------------\n// Attempt to produce a BoundIdentity object out of given environment\n// variables. If not possible, return nullptr.\n//------------------------------------------------------------------------------\nstd::shared_ptr<const BoundIdentity>\nBoundIdentityProvider::environmentToBoundIdentity(const JailInformation& jail,\n    const Environment& env, uid_t uid, gid_t gid, bool reconnect,\n    LogbookScope& scope, bool skip_sss)\n{\n  std::shared_ptr<const BoundIdentity> output;\n\n  //----------------------------------------------------------------------------\n  // No SSS.. should we try KRB5 first, or second?\n  //----------------------------------------------------------------------------\n  if (credConfig.tryKrb5First) {\n    if (credConfig.use_user_krb5cc) {\n      output = krb5EnvToBoundIdentity(jail, env, uid, gid, reconnect, scope);\n\n      if (output) {\n        return output;\n      }\n    }\n\n    //--------------------------------------------------------------------------\n    // No krb5.. what about x509..\n    //--------------------------------------------------------------------------\n    if (credConfig.use_user_gsiproxy) {\n      output = x509EnvToBoundIdentity(jail, env, uid, gid, reconnect, scope);\n\n      if (output) {\n        return output;\n      }\n    }\n\n    //--------------------------------------------------------------------------\n    // No x509.. what about ztn..\n    //--------------------------------------------------------------------------\n    if (credConfig.use_user_ztn) {\n      output = ztnEnvToBoundIdentity(jail, env, uid, gid, reconnect, scope);\n\n      if (output) {\n        return output;\n      }\n    }\n\n    //----------------------------------------------------------------------------\n    // Try to use OAUTH2 if available.\n    //----------------------------------------------------------------------------\n    if (credConfig.use_user_oauth2) {\n      output = oauth2EnvToBoundIdentity(jail, env, uid, gid, reconnect, scope);\n\n      if (output) {\n        return output;\n      }\n    }\n    //----------------------------------------------------------------------------\n    // Try to use SSS if available.\n    //----------------------------------------------------------------------------\n    if (credConfig.use_user_sss && ((!skip_sss) ||\n                                    (!env.get(\"XrdSecsssENDORSEMENT\").empty() || !credConfig.sssEndorsement.empty()))) {\n      output = sssEnvToBoundIdentity(jail, env, uid, gid, reconnect, scope);\n\n      if (output) {\n        return output;\n      }\n    }\n\n    //--------------------------------------------------------------------------\n    // Nothing, bail out\n    //--------------------------------------------------------------------------\n    return {};\n  }\n\n  //----------------------------------------------------------------------------\n  // We should try krb5 second.\n  //----------------------------------------------------------------------------\n  if (credConfig.use_user_gsiproxy) {\n    output = x509EnvToBoundIdentity(jail, env, uid, gid, reconnect, scope);\n\n    if (output) {\n      return output;\n    }\n  }\n\n  //--------------------------------------------------------------------------\n  // No x509.. what about krb5..\n  //--------------------------------------------------------------------------\n  if (credConfig.use_user_krb5cc) {\n    output = krb5EnvToBoundIdentity(jail, env, uid, gid, reconnect, scope);\n\n    if (output) {\n      return output;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  // Try to us OAUTH2 if available.\n  //----------------------------------------------------------------------------\n  if (credConfig.use_user_oauth2) {\n    output = oauth2EnvToBoundIdentity(jail, env, uid, gid, reconnect, scope);\n\n    if (output) {\n      return output;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  // Try to use SSS if available.\n  //----------------------------------------------------------------------------\n  if (credConfig.use_user_sss && (!skip_sss ||\n                                  (!env.get(\"XrdSecsssENDORSEMENT\").empty() || !credConfig.sssEndorsement.empty()))) {\n    output = sssEnvToBoundIdentity(jail, env, uid, gid, reconnect, scope);\n\n    if (output) {\n      return output;\n    }\n  }\n\n  //--------------------------------------------------------------------------\n  // Nothing, bail out\n  //--------------------------------------------------------------------------\n  return {};\n}\n\n//------------------------------------------------------------------------------\n// Register SSS credentials\n//------------------------------------------------------------------------------\nvoid BoundIdentityProvider::registerSSS(const BoundIdentity& bdi)\n{\n  const UserCredentials uc = bdi.getCreds()->getUC();\n\n  if ((uc.type == CredentialType::SSS) ||\n      (uc.type == CredentialType::OAUTH2)) {\n    // by default we request the uid/gid name of the calling process\n    // the xrootd server rejects to map these if the sss key is not issued for anyuser/anygroup\n    XrdSecEntity* newEntity = new XrdSecEntity(\"sss\");\n    int errc_uid = 0;\n    std::string username = eos::common::Mapping::UidToUserName(uc.uid, errc_uid);\n    int errc_gid = 0;\n    std::string groupname = eos::common::Mapping::GidToGroupName(uc.gid, errc_gid);\n\n    if (errc_uid) {\n      newEntity->name = strdup(\"nobody\");\n    } else {\n      newEntity->name = strdup(username.c_str());\n    }\n\n    if (errc_gid) {\n      newEntity->grps = strdup(\"nogroup\");\n    } else {\n      newEntity->grps = strdup(groupname.c_str());\n    }\n\n    // store the endorsement from the environment\n    if (!uc.endorsement.empty()) {\n      newEntity->endorsements = strdup(uc.endorsement.c_str());\n    }\n\n    // register new ID\n    XrdSecsssIDInstance().Register(bdi.getLogin().getStringID().c_str(), newEntity);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Given a set of user-provided, non-trusted UserCredentials, attempt to\n// translate them into a BoundIdentity object. (either by allocating a new\n// connection, or re-using a cached one)\n//\n// If such a thing is not possible, return false.\n//------------------------------------------------------------------------------\nstd::shared_ptr<const BoundIdentity>\nBoundIdentityProvider::userCredsToBoundIdentity(const JailInformation& jail,\n    const UserCredentials& creds, bool reconnect, LogbookScope& scope)\n{\n  //----------------------------------------------------------------------------\n  // Make a proper LogbookScope, and pretty-print UserCredentials\n  //----------------------------------------------------------------------------\n  LogbookScope subscope(\n    scope.makeScope(\"Attempt to translate UserCredentials -> BoundIdentity\"));\n  //----------------------------------------------------------------------------\n  // First check: Is the item in the cache?\n  //----------------------------------------------------------------------------\n  std::shared_ptr<const BoundIdentity> cached = credentialCache.retrieve(creds);\n\n  //----------------------------------------------------------------------------\n  // Invalidate result if asked to reconnect\n  //----------------------------------------------------------------------------\n  if (cached && reconnect) {\n    LOGBOOK_INSERT(subscope,\n                   \"Cache entry UserCredentials -> BoundIdentity already exists (\" <<\n                   cached->getLogin().describe() << \") - invalidating\");\n    credentialCache.invalidate(creds);\n    cached->getCreds()->invalidate();\n    cached = {};\n  }\n\n  if (cached) {\n    //--------------------------------------------------------------------------\n    // Item is in the cache, and reconnection was not requested. Still valid?\n    //--------------------------------------------------------------------------\n    if (validator.checkValidity(jail, *cached->getCreds())) {\n      return cached;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  // Alright, we have a cache miss. Can we promote UserCredentials into\n  // TrustedCredentials?\n  //----------------------------------------------------------------------------\n  std::unique_ptr<BoundIdentity> bdi(new BoundIdentity());\n\n  if (!validator.validate(jail, creds, *bdi->getCreds(), subscope)) {\n    //--------------------------------------------------------------------------\n    // Nope, these UserCredentials are unusable.\n    //--------------------------------------------------------------------------\n    return {};\n  }\n\n  //----------------------------------------------------------------------------\n  // We made it, the crowd goes wild, allocate a new connection\n  //----------------------------------------------------------------------------\n  bdi->getLogin() = LoginIdentifier(connectionCounter++);\n  LOGBOOK_INSERT(subscope,\n                 \"UserCredentials registerSSS (\" << bdi->getLogin().getStringID() << \")\");\n  LOGBOOK_INSERT(subscope,\n                 \"Endorsement (\" << bdi->getCreds()->getUC().endorsement.c_str() << \")\");\n  registerSSS(*bdi);\n  //----------------------------------------------------------------------------\n  // Store into the cache\n  //----------------------------------------------------------------------------\n  credentialCache.store(creds, std::move(bdi), cached);\n  return cached;\n}\n\n//------------------------------------------------------------------------------\n// Fallback to unix authentication. Guaranteed to always return a valid\n// BoundIdentity object. (whether this is accepted by the server is another\n// matter)\n//------------------------------------------------------------------------------\nstd::shared_ptr<const BoundIdentity>\nBoundIdentityProvider::unixAuth(pid_t pid, uid_t uid, gid_t gid,\n                                bool reconnect, LogbookScope& scope, const Environment& pidEnv)\n{\n  LOGBOOK_INSERT(scope, \"Producing UNIX identity out of pid=\" << pid <<\n                 \", uid=\" << uid << \", gid=\" << gid);\n  return unixAuthenticator.createIdentity(pid, uid, gid, reconnect,\n                                          pidEnv.get(\"EOS_FUSE_SECRET\"));\n}\n\n//------------------------------------------------------------------------------\n// Attempt to produce a BoundIdentity object out of default paths, such\n// as /tmp/krb5cc_<uid>.\n// If not possible, return nullptr.\n//------------------------------------------------------------------------------\nstd::shared_ptr<const BoundIdentity>\nBoundIdentityProvider::defaultPathsToBoundIdentity(const JailInformation& jail,\n    uid_t uid, gid_t gid, bool reconnect, LogbookScope& scope,\n    const Environment& pidEnv)\n{\n  // Pretend as if the environment of the process simply contained the default values,\n  // and follow the usual code path.\n  Environment defaultEnv;\n  {\n\n#ifdef __linux__\n    ScopedEUidSetter uidSetter(uid, gid);\n\n    if (!uidSetter.IsOk()) {\n      eos_static_crit(\"Could not set fsuid,fsgid to %d, %d\", uid, gid);\n    }\n\n#endif\n\n    // get the default cache from KRB5\n    krb5_context krb_ctx;\n    krb5_error_code ret = krb5_init_context(&krb_ctx);\n\n    if (ret == 0) {\n      std::string default_name = krb5_cc_default_name(krb_ctx);\n      if ((default_name.empty())) {\n        defaultEnv.push_back(\"KRB5CCNAME=FILE:/tmp/krb5cc_\" + std::to_string(uid));\n      } else if (default_name.substr(0, 18) == \"KEYRING:persistent\") {\n        defaultEnv.push_back(\"KRB5CCNAME=KEYRING:persistent:\" + std::to_string(uid));\n      } else {\n#ifdef __linux__\n        defaultEnv.push_back(\"KRB5CCNAME=\" + default_name);\n#endif\n      }\n      \n      krb5_free_context(krb_ctx);\n    }\n  }\n  defaultEnv.push_back(\"X509_USER_PROXY=/tmp/x509up_u\" + std::to_string(uid));\n  defaultEnv.push_back(\"OAUTH2_TOKEN=FILE:/tmp/oauthtk_\" + std::to_string(uid));\n  defaultEnv.push_back(\"BEARER_TOKEN_FILE=/tmp/bt_u\" + std::to_string(uid));\n  LogbookScope subscope(scope.makeScope(\n                          SSTR(\"Attempting to produce BoundIdentity out of default paths for uid=\"\n                               << uid)));\n  // attach secret key and endorsement\n  defaultEnv.push_back(\"EOS_FUSE_SECRET=\" + pidEnv.get(\"EOS_FUSE_SECRET\"));\n\n  if (credConfig.sssEndorsement.length()) {\n    defaultEnv.push_back(\"XrdSecsssENDROSEMENT=\" + credConfig.sssEndorsement);\n  } else {\n    defaultEnv.push_back(\"XrdSecsssENDORSEMENT=\" +\n\t\t\t pidEnv.get(\"XrdSecsssENDORSEMENT\"));\n  }\n\n  return environmentToBoundIdentity(jail, defaultEnv, uid, gid, reconnect,\n                                    subscope, false);\n}\n\n//------------------------------------------------------------------------------\n// Attempt to produce a BoundIdentity object out of the global eosfusebind\n// binding. If not possible, return nullptr.\n//------------------------------------------------------------------------------\nstd::shared_ptr<const BoundIdentity>\nBoundIdentityProvider::globalBindingToBoundIdentity(const JailInformation& jail,\n    uid_t uid, gid_t gid, bool reconnect, LogbookScope& scope,\n    const Environment& pidEnv)\n{\n  // Pretend as if the environment of the process simply contained the eosfusebind\n  // global bindings, and follow the usual code path.\n  Environment defaultEnv;\n  defaultEnv.push_back(SSTR(\"KRB5CCNAME=FILE:/var/run/eos/credentials/uid\" << uid\n                            << \".krb5\"));\n  defaultEnv.push_back(SSTR(\"X509_USER_PROXY=/var/run/eos/credentials/uid\" << uid\n                            << \".x509\"));\n  LogbookScope subscope(scope.makeScope(\n                          SSTR(\"Attempting to produce BoundIdentity out of eosfusebind \" <<\n                               \"global binding for uid=\" << uid)));\n  defaultEnv.push_back(\"EOS_FUSE_SECRET=\" + pidEnv.get(\"EOS_FUSE_SECRET\"));\n  if (credConfig.sssEndorsement.length()) {\n    defaultEnv.push_back(\"XrdSecsssENDORSEMENT=\" + credConfig.sssEndorsement);\n  } else {\n    defaultEnv.push_back(\"XrdSecsssENDORSEMENT=\" +\n\t\t\t pidEnv.get(\"XrdSecsssENDORSEMENT\"));\n  }\n  return environmentToBoundIdentity(jail, defaultEnv, uid, gid, reconnect,\n                                    subscope, true);\n}\n\n//------------------------------------------------------------------------------\n// Attempt to produce a BoundIdentity object out of environment variables\n// of the given PID. If not possible, return nullptr.\n//------------------------------------------------------------------------------\nstd::shared_ptr<const BoundIdentity>\nBoundIdentityProvider::pidEnvironmentToBoundIdentity(\n  const JailInformation& jail, pid_t pid, uid_t uid, gid_t gid,\n  bool reconnect, LogbookScope& scope, Environment& env)\n{\n  LogbookScope subscope(scope.makeScope(\n                          SSTR(\"Attempting to produce BoundIdentity out of process environment, pid=\" <<\n                               pid)));\n  // First, let's read the environment to build up a UserCredentials object.\n  FutureEnvironment response = environmentReader.stageRequest(pid, uid);\n\n  if (!response.waitUntilDeadline(\n        std::chrono::milliseconds(credConfig.environ_deadlock_timeout))) {\n    eos_static_info(\"Timeout when retrieving environment for pid %d (uid %d) - we're doing an execve!\",\n                    pid, uid);\n    LOGBOOK_INSERT(subscope,\n                   \"FAILED in retrieving environment variables for pid=\" << pid <<\n                   \": TIMEOUT after \" << credConfig.environ_deadlock_timeout << \" ms\");\n    return {};\n  }\n\n  LOGBOOK_INSERT(subscope, \"Succeeded in retrieving environment \"\n                 \"variables for pid=\" << pid);\n  // store environment\n  env = response.get();\n  return environmentToBoundIdentity(jail, env, uid, gid,\n                                    reconnect, subscope, true);\n}\n\n//------------------------------------------------------------------------------\n// Check if the given BoundIdentity object is still valid.\n//------------------------------------------------------------------------------\nbool BoundIdentityProvider::checkValidity(const JailInformation& jail,\n    const BoundIdentity& identity)\n{\n  if (!identity.getCreds()) {\n    return false;\n  }\n\n  if (identity.getAge() > std::chrono::hours(24)) {\n    return false;\n  }\n\n  return validator.checkValidity(jail, *identity.getCreds());\n}\n\n//------------------------------------------------------------------------------\n// Remove the BoundIdentity corresponding to creds & connId from the cache\n//------------------------------------------------------------------------------\nbool BoundIdentityProvider::remove(const UserCredentials& creds, uint64_t connId)\n{\n  std::shared_ptr<const BoundIdentity> cached = credentialCache.retrieve(creds);\n  if (!cached) return false;\n  if (cached->getLogin().getConnectionID() != connId) return false;\n  return credentialCache.invalidate(creds);\n}\n"
  },
  {
    "path": "fusex/auth/BoundIdentityProvider.hh",
    "content": "// ----------------------------------------------------------------------\n// File: BoundIdentityProvider.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __BOUND_IDENTITY_PROVIDER__HH__\n#define __BOUND_IDENTITY_PROVIDER__HH__\n\n#include \"JailIdentifier.hh\"\n#include \"UnixAuthenticator.hh\"\n#include \"CredentialCache.hh\"\n#include \"CredentialFinder.hh\"\n#include \"ProcessInfo.hh\"\n#include \"SecurityChecker.hh\"\n#include \"EnvironmentReader.hh\"\n#include <XrdSec/XrdSecEntity.hh>\n#include <private/XrdSecsss/XrdSecsssID.hh>\n#include <atomic>\n\nclass SecurityChecker;\nclass EnvironmentReader;\nclass CredentialValidator;\nclass LogbookScope;\n\nclass BoundIdentityProvider\n{\npublic:\n  //----------------------------------------------------------------------------\n  // Constructor.\n  //----------------------------------------------------------------------------\n  BoundIdentityProvider(SecurityChecker& checker, EnvironmentReader& reader,\n                        CredentialValidator& validator);\n\n  //----------------------------------------------------------------------------\n  // Destructor.\n  //----------------------------------------------------------------------------\n  ~BoundIdentityProvider()\n  {\n  }\n\n  //----------------------------------------------------------------------------\n  // Attempt to produce a BoundIdentity object out of given environment\n  // variables. If not possible, return nullptr.\n  //----------------------------------------------------------------------------\n  std::shared_ptr<const BoundIdentity> environmentToBoundIdentity(\n    const JailInformation& jail, const Environment& env, uid_t uid,\n    gid_t gid, bool reconnect, LogbookScope& scope, bool skip_sss = false);\n\n  //----------------------------------------------------------------------------\n  // Attempt to produce a BoundIdentity object out of environment variables\n  // of the given PID. If not possible, return nullptr.\n  //----------------------------------------------------------------------------\n  std::shared_ptr<const BoundIdentity> pidEnvironmentToBoundIdentity(\n    const JailInformation& jail, pid_t pid, uid_t uid, gid_t gid,\n    bool reconnect, LogbookScope& logbook, Environment& env);\n\n  //----------------------------------------------------------------------------\n  // Attempt to produce a BoundIdentity object out of default paths, such\n  // as /tmp/krb5cc_<uid>.\n  // If not possible, return nullptr.\n  //----------------------------------------------------------------------------\n  std::shared_ptr<const BoundIdentity>\n  defaultPathsToBoundIdentity(const JailInformation& jail, uid_t uid,\n                              gid_t gid, bool reconnect, LogbookScope& scope, const Environment& env);\n\n  //----------------------------------------------------------------------------\n  // Attempt to produce a BoundIdentity object out of the global eosfusebind\n  // binding. If not possible, return nullptr.\n  //----------------------------------------------------------------------------\n  std::shared_ptr<const BoundIdentity>\n  globalBindingToBoundIdentity(const JailInformation& jail, uid_t uid,\n                               gid_t gid, bool reconnect, LogbookScope& scope, const Environment& env);\n\n  void setCredentialConfig(const CredentialConfig& conf)\n  {\n    credConfig = conf;\n  }\n\n  //----------------------------------------------------------------------------\n  // Check if the given BoundIdentity object is still valid.\n  //----------------------------------------------------------------------------\n  bool checkValidity(const JailInformation& jail,\n                     const BoundIdentity& identity);\n\n  //----------------------------------------------------------------------------\n  // Fallback to unix authentication. Guaranteed to always return a valid\n  // BoundIdentity object. (whether this is accepted by the server is another\n  // matter)\n  //----------------------------------------------------------------------------\n  std::shared_ptr<const BoundIdentity> unixAuth(pid_t pid, uid_t uid, gid_t gid,\n      bool reconnect, LogbookScope& scope, const Environment& env);\n\n  //----------------------------------------------------------------------------\n  // Remove the BoundIdentity corresponding to creds & connId from the cache\n  //----------------------------------------------------------------------------\n  bool remove(const UserCredentials& creds, uint64_t connId);\n\nprivate:\n  EnvironmentReader& environmentReader;\n  CredentialValidator& validator;\n\n  UnixAuthenticator unixAuthenticator;\n  CredentialConfig credConfig;\n  CredentialCache credentialCache;\n\n  static XrdSecsssID& XrdSecsssIDInstance()\n  {\n    static XrdSecsssID* sssRegistry = new XrdSecsssID(XrdSecsssID::idDynamic);\n    return *sssRegistry;\n  }\n\n  //----------------------------------------------------------------------------\n  // Attempt to produce a BoundIdentity object out of KRB5 environment\n  // variables. NO fallback to default paths. If not possible, return nullptr.\n  //----------------------------------------------------------------------------\n  std::shared_ptr<const BoundIdentity> krb5EnvToBoundIdentity(\n    const JailInformation& jail, const Environment& env, uid_t uid, gid_t gid,\n    bool reconnect, LogbookScope& scope);\n\n  //----------------------------------------------------------------------------\n  // Attempt to produce a BoundIdentity object out of X509 environment\n  // variables. NO fallback to default paths. If not possible, return nullptr.\n  //----------------------------------------------------------------------------\n  std::shared_ptr<const BoundIdentity> x509EnvToBoundIdentity(\n    const JailInformation& jail, const Environment& env, uid_t uid, gid_t gid,\n    bool reconnect, LogbookScope& scope);\n\n  //----------------------------------------------------------------------------\n  // Attempt to produce a BoundIdentity object out of ZTN environment\n  // variables. NO fallback to default paths. If not possible, return nullptr.\n  //----------------------------------------------------------------------------\n  std::shared_ptr<const BoundIdentity> ztnEnvToBoundIdentity(\n    const JailInformation& jail, const Environment& env, uid_t uid, gid_t gid,\n    bool reconnect, LogbookScope& scope);\n\n  //----------------------------------------------------------------------------\n  // Attempt to produce a BoundIdentity object out of SSS environment\n  // variables. If not possible, return nullptr.\n  //----------------------------------------------------------------------------\n  std::shared_ptr<const BoundIdentity> sssEnvToBoundIdentity(\n    const JailInformation& jail, const Environment& env, uid_t uid, gid_t gid,\n    bool reconnect, LogbookScope& scope);\n\n  //----------------------------------------------------------------------------\n  // Attempt to produce a BoundIdentity object out of OAUTH2 environment\n  // variables. If not possible, return nullptr.\n  //----------------------------------------------------------------------------\n  std::shared_ptr<const BoundIdentity> oauth2EnvToBoundIdentity(\n    const JailInformation& jail, const Environment& env, uid_t uid, gid_t gid,\n    bool reconnect, LogbookScope& scope);\n\n  //----------------------------------------------------------------------------\n  // Given a set of user-provided, non-trusted UserCredentials, attempt to\n  // translate them into a BoundIdentity object. (either by allocating a new\n  // connection, or re-using a cached one)\n  //\n  // If such a thing is not possible, return nullptr.\n  //----------------------------------------------------------------------------\n  std::shared_ptr<const BoundIdentity> userCredsToBoundIdentity(\n    const JailInformation& jail, const UserCredentials& creds, bool reconnect,\n    LogbookScope& scope);\n\n  //----------------------------------------------------------------------------\n  // Register SSS credentials\n  //----------------------------------------------------------------------------\n  void registerSSS(const BoundIdentity& bdi);\n\n  std::atomic<uint64_t> connectionCounter{1};\n};\n\n#endif\n"
  },
  {
    "path": "fusex/auth/CMakeLists.txt",
    "content": "# ------------------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Georgios Bitzes - CERN\n# ------------------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nadd_library(\n  EosFuseAuth OBJECT\n  AuthenticationGroup.cc           AuthenticationGroup.hh\n  BoundIdentityProvider.cc         BoundIdentityProvider.hh\n                                   CredentialCache.hh\n  CredentialFinder.cc              CredentialFinder.hh\n  CredentialValidator.cc           CredentialValidator.hh\n  DirectoryIterator.cc             DirectoryIterator.hh\n  EnvironmentReader.cc             EnvironmentReader.hh\n                                   FileDescriptor.hh\n  JailIdentifier.cc                JailIdentifier.hh\n  Logbook.cc                       Logbook.hh\n  LoginIdentifier.cc               LoginIdentifier.hh\n  ProcessCache.cc                  ProcessCache.hh\n  ProcessInfo.cc                   ProcessInfo.hh\n  RmInfo.cc                        RmInfo.hh\n                                   ScopedFsUidSetter.hh\n  SecurityChecker.cc               SecurityChecker.hh\n  UnixAuthenticator.cc             UnixAuthenticator.hh\n  UserCredentialFactory.cc         UserCredentialFactory.hh\n                                   UserCredentials.hh\n  Utils.cc                         Utils.hh\n  UuidStore.cc                     UuidStore.hh)\n\ntarget_link_libraries(EosFuseAuth PUBLIC\n  XROOTD::UTILS\n  XROOTD::PRIVATE\n  KRB5::KRB5\n  OpenSSL::SSL\n  PROTOBUF::PROTOBUF\n  GOOGLE::SPARSEHASH)\n"
  },
  {
    "path": "fusex/auth/CredentialCache.hh",
    "content": "//------------------------------------------------------------------------------\n// File: CredentialCache.hh\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_FUSEX_CREDENTIAL_CACHE_HH\n#define EOS_FUSEX_CREDENTIAL_CACHE_HH\n\n#include \"UserCredentials.hh\"\n#include \"CredentialFinder.hh\"\n#include \"common/ShardedCache.hh\"\n#include \"common/Murmur3.hh\"\n\n//------------------------------------------------------------------------------\n// Hasher class for UserCredentials.\n//------------------------------------------------------------------------------\nstruct UserCredentialsHasher {\n\n  static uint64_t hash(const UserCredentials& key)\n  {\n    uint64_t result = std::uint32_t(key.type);\n    result += key.jail.hash();\n    result += Murmur3::MurmurHasher<std::string> {} (key.fname);\n    result += Murmur3::MurmurHasher<std::string> {} (key.endorsement);\n    return result;\n  }\n};\n\n//------------------------------------------------------------------------------\n// Maps UserCredentials to a BoundIdentity.\n//------------------------------------------------------------------------------\nclass CredentialCache\n{\npublic:\n\n  CredentialCache() : cache(16 /* 2^16 shards */,\n                              1000 * 60 * 60 * 12 /* 12 hours */) { }\n\n  std::shared_ptr<const BoundIdentity> retrieve(const UserCredentials& credInfo)\n  {\n    return cache.retrieve(credInfo);\n  }\n\n  // replace by default\n\n  bool store(const UserCredentials& credInfo,\n             std::unique_ptr<BoundIdentity> boundIdentity,\n             std::shared_ptr<const BoundIdentity>& retval)\n  {\n    return cache.store(credInfo, std::move(boundIdentity), retval, true);\n  }\n\n  bool invalidate(const UserCredentials& credInfo)\n  {\n    return cache.invalidate(credInfo);\n  }\n\nprivate:\n  // shards: 2^16 = 65536\n  ShardedCache<UserCredentials, BoundIdentity, UserCredentialsHasher, false>\n  cache;\n};\n\n#endif\n"
  },
  {
    "path": "fusex/auth/CredentialFinder.cc",
    "content": "//------------------------------------------------------------------------------\n// File: CredentialFinder.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <iostream>\n#include <sstream>\n#include \"CredentialFinder.hh\"\n#include \"Utils.hh\"\n\nvoid Environment::fromFile(const std::string& path)\n{\n  std::string contents;\n\n  if (readFile(path, contents)) {\n    fromString(contents);\n  }\n}\n\nvoid Environment::fromString(const std::string& str)\n{\n  contents = split_on_nullbyte(str);\n}\n\nvoid Environment::fromVector(const std::vector<std::string>& vec)\n{\n  contents = vec;\n}\n\nstd::string Environment::get(const std::string& key) const\n{\n  std::string keyWithEquals = key + \"=\";\n\n  for (size_t i = 0; i < contents.size(); i++) {\n    if (startsWith(contents[i], keyWithEquals)) {\n      return contents[i].substr(keyWithEquals.size());\n    }\n  }\n\n  return \"\";\n}\n\nstd::vector<std::string> Environment::getAll() const\n{\n  return contents;\n}\n"
  },
  {
    "path": "fusex/auth/CredentialFinder.hh",
    "content": "//------------------------------------------------------------------------------\n// File: CredentialFinder.hh\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSEX_CREDENTIAL_FINDER_HH\n#define FUSEX_CREDENTIAL_FINDER_HH\n\n#include \"UserCredentials.hh\"\n#include \"SecurityChecker.hh\"\n#include \"LoginIdentifier.hh\"\n#include \"Utils.hh\"\n#include \"common/Logging.hh\"\n#include <XrdCl/XrdClURL.hh>\n#include <string>\n#include <map>\n#include <vector>\n#include <sstream>\n#include <atomic>\n#include <time.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\nclass CredentialConfig\n{\npublic:\n\n  CredentialConfig() : use_user_krb5cc(false), use_user_gsiproxy(false),\n    use_user_sss(false), use_user_oauth2(false), use_user_ztn(false),\n    tryKrb5First(false),\n    use_user_unix(false),\n    use_root_unix(false),\n    fuse_shared(false),\n    environ_deadlock_timeout(500), forknoexec_heuristic(true),\n    ignore_containerization(false) { }\n\n  //! Indicates if user krb5cc file should be used for authentication\n  bool use_user_krb5cc;\n  //! Indicates if user gsi proxy should be used for authentication\n  bool use_user_gsiproxy;\n  //! Indicates if user sss file should be used for authentication\n  bool use_user_sss;\n  //! Indicates if user oauth2 file should be used for authentication\n  bool use_user_oauth2;\n  //! Indicates if user ztn token authentication should be used for authentication\n  bool use_user_ztn;\n  //! Indicates if Krb5 should be tried before Gsi\n  bool tryKrb5First;\n  //! Indicates if unix authentication is to be used for authentication for all but uid=0\n  bool use_user_unix;\n  //! Indicates if unix authentication can be used for the roo tuser)\n  bool use_root_unix;\n  //! Indicates if this is a shared fuse mount\n  bool fuse_shared;\n  //! How long to wait before declaring a kernel deadlock when reading /proc/environ\n  unsigned environ_deadlock_timeout;\n  //! Use PF_FORKNOEXEC as a heuristic to decide if the process is doing an execve.\n  bool forknoexec_heuristic;\n  //! Credential store\n  std::string credentialStore;\n  //! Ignore containerization\n  bool ignore_containerization;\n  //! Encryption Key\n  std::string encryptionKey;\n  //! Static token\n  std::string sssEndorsement;\n};\n\n//------------------------------------------------------------------------------\n// TrustedCredentials = UserCredentials with a stamp of approval. We need\n// this object to generate the parameters in the xrootd URL.\n//------------------------------------------------------------------------------\nclass TrustedCredentials\n{\npublic:\n  //----------------------------------------------------------------------------\n  // Constructor\n  //----------------------------------------------------------------------------\n  TrustedCredentials(const UserCredentials& uc_, struct timespec mtime_,\n                     const std::string& intercepted, const std::string& username)\n  {\n    initialize(uc_, mtime_, intercepted, username);\n  }\n\n  //----------------------------------------------------------------------------\n  // Empty constructor.\n  //----------------------------------------------------------------------------\n  TrustedCredentials()\n  {\n    clear();\n  }\n\n  //----------------------------------------------------------------------------\n  // Destructor\n  //----------------------------------------------------------------------------\n  ~TrustedCredentials()\n  {\n    if (!interceptedPath.empty()) {\n      if (unlink(interceptedPath.c_str()) != 0) {\n        int myerrno = errno;\n        eos_static_crit(\"Unable to unlink intercepted-path: %s, errno: %d\",\n                        interceptedPath.c_str(), myerrno);\n      }\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  // Clear contents.\n  //----------------------------------------------------------------------------\n  void clear()\n  {\n    uc = UserCredentials::MakeNobody();\n    initialized = false;\n    invalidated = false;\n    mtime = {0, 0};\n    interceptedPath.clear();\n  }\n\n  //----------------------------------------------------------------------------\n  // Re-initialize contents.\n  //----------------------------------------------------------------------------\n  void initialize(const UserCredentials& uc_, struct timespec mtime_,\n                  const std::string& intercepted,\n                  const std::string& user)\n  {\n    uc = uc_;\n    initialized = true;\n    invalidated = false;\n    mtime = mtime_;\n    interceptedPath = intercepted;\n    username = user;\n\n    if (uc.type == CredentialType::OAUTH2) {\n      eos::common::StringConversion::LoadFileIntoString(getFinalPath().c_str(),\n          uc.endorsement);\n\n      if (!uc.endorsement.empty()) {\n        eos_static_warning(\"loaded OAUTH2 token file '%s'\", getFinalPath().c_str());\n      }\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  // Get credential path, maybe intercepted.\n  //----------------------------------------------------------------------------\n  std::string getFinalPath() const\n  {\n    if (!interceptedPath.empty()) {\n      return interceptedPath;\n    }\n\n    return uc.fname;\n  }\n\n  //----------------------------------------------------------------------------\n  // Get the key secret retrieved from the environment\n  //----------------------------------------------------------------------------\n  std::string getKey() const\n  {\n    return uc.secretkey;\n  }\n\n  //----------------------------------------------------------------------------\n  // Generate parameters for this TrustedCredential as ParamsMap\n  //----------------------------------------------------------------------------\n  void toXrdParams(XrdCl::URL::ParamsMap& paramsMap) const\n  {\n    if (uc.hasUnsafeCharacters()) {\n      eos_static_err(\"rejecting credential for using forbidden characters in the path: %s\",\n                     uc.fname.c_str());\n      paramsMap[\"xrd.wantprot\"] = \"unix\";\n      return;\n    }\n\n    if (uc.type == CredentialType::NOBODY) {\n      paramsMap[\"xrd.wantprot\"] = \"unix\";\n      return;\n    }\n\n    if (uc.type == CredentialType::SSS) {\n      paramsMap[\"xrd.wantprot\"] = \"sss,unix\";\n      return;\n    }\n\n    if ((uc.type != CredentialType::OAUTH2) &&\n        (uc.type != CredentialType::SSS)) {\n      if (interceptedPath.empty()) {\n        paramsMap[\"xrdcl.secuid\"] = std::to_string(uc.uid);\n        paramsMap[\"xrdcl.secgid\"] = std::to_string(uc.gid);\n      }\n    }\n\n    if (uc.type == CredentialType::KRB5) {\n      paramsMap[\"xrd.wantprot\"] = \"krb5,unix\";\n      paramsMap[\"xrd.k5ccname\"] = getFinalPath();\n    } else if (uc.type == CredentialType::KRK5) {\n      paramsMap[\"xrd.wantprot\"] = \"krb5,unix\";\n      paramsMap[\"xrd.k5ccname\"] = uc.keyring;\n    } else if (uc.type == CredentialType::KCM) {\n      paramsMap[\"xrd.wantprot\"] = \"krb5,unix\";\n      paramsMap[\"xrd.k5ccname\"] = uc.kcm;\n    } else if (uc.type == CredentialType::X509) {\n      paramsMap[\"xrd.wantprot\"] = \"gsi,unix\";\n      paramsMap[\"xrd.gsiusrpxy\"] = getFinalPath();\n    } else if (uc.type == CredentialType::OAUTH2) {\n      paramsMap[\"xrd.wantprot\"] = \"sss,unix\";\n    } else if (uc.type == CredentialType::ZTN) {\n      paramsMap[\"xrd.wantprot\"] = \"ztn,unix\";\n      paramsMap[\"xrd.ztn\"] = getFinalPath();\n    } else {\n      THROW(\"should never reach here\");\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  // Generate username for this TrustedCredential\n  //----------------------------------------------------------------------------\n  std::string toUserName() const\n  {\n    if (uc.hasUnsafeCharacters()) {\n      eos_static_err(\"rejecting credential for using forbidden characters in the path: %s\",\n                     uc.fname.c_str());\n      return \"nobody\";\n    }\n\n    if (uc.type == CredentialType::NOBODY) {\n      return \"nobody\";\n    }\n\n    if (uc.type == CredentialType::OAUTH2) {\n      return \"nobody\";\n    }\n\n    if (uc.type == CredentialType::KRB5) {\n      return username;\n    } else if (uc.type == CredentialType::KRK5) {\n      return username;\n    } else if (uc.type == CredentialType::KCM) {\n      return username;\n    } else if (uc.type == CredentialType::X509) {\n      return \"nobody\";\n    } else if (uc.type == CredentialType::OAUTH2) {\n      return \"nobody\";\n    } else {\n      THROW(\"should never reach here\");\n    }\n  }\n\n\n  //----------------------------------------------------------------------------\n  // Generate parameters for this TrustedCredential as std::string\n  //----------------------------------------------------------------------------\n  std::string toXrdParams() const\n  {\n    XrdCl::URL::ParamsMap paramsMap;\n    this->toXrdParams(paramsMap);\n    std::stringstream ss;\n\n    for (auto it = paramsMap.begin(); it != paramsMap.end(); it++) {\n      if (it != paramsMap.begin()) {\n        ss << \"&\";\n      }\n\n      ss << it->first << \"=\" << it->second;\n    }\n\n    return ss.str();\n  }\n\n  void invalidate() const\n  {\n    invalidated = true;\n  }\n\n  bool valid() const\n  {\n    return ! invalidated;\n  }\n\n  //----------------------------------------------------------------------------\n  // Accessor for underlying UserCredentials\n  //----------------------------------------------------------------------------\n  UserCredentials& getUC()\n  {\n    return uc;\n  }\n\n  //----------------------------------------------------------------------------\n  // Const accessor for underlying UserCredentials\n  //----------------------------------------------------------------------------\n  const UserCredentials& getUC() const\n  {\n    return uc;\n  }\n\n  //----------------------------------------------------------------------------\n  // Accessor for intercepted path\n  //----------------------------------------------------------------------------\n  std::string getIntercepted() const\n  {\n    return interceptedPath;\n  }\n\n  //----------------------------------------------------------------------------\n  // Accessor for mtime\n  //----------------------------------------------------------------------------\n  struct timespec getMTime() const {\n    return mtime;\n  }\n\n  bool empty() const\n  {\n    return !initialized;\n  }\n\n  //----------------------------------------------------------------------------\n  // Describe object as string\n  //----------------------------------------------------------------------------\n  std::string describe() const\n  {\n    std::stringstream ss;\n    ss << uc.describe() << std::endl;\n    ss << \"mtime: \" << mtime.tv_sec << \".\" << mtime.tv_nsec << std::endl;\n    ss << \"intercepted path: \" << interceptedPath << std::endl;\n    ss << \"username: \" << username << std::endl;\n    return ss.str();\n  }\n\nprivate:\n  UserCredentials uc;\n\n  bool initialized;\n  mutable std::atomic<bool> invalidated;\n  struct timespec mtime;\n  std::string interceptedPath;\n  std::string username;\n};\n\n// TrustedCredentials bound to a LoginIdentifier. We need this to talk to the MGM.\n\nclass BoundIdentity\n{\npublic:\n\n  BoundIdentity()\n  {\n    creationTime = std::chrono::steady_clock::now();\n  }\n\n  LoginIdentifier& getLogin()\n  {\n    return login;\n  }\n\n  const LoginIdentifier& getLogin() const\n  {\n    return login;\n  }\n\n  TrustedCredentials* getCreds()\n  {\n    return &creds;\n  }\n\n  const TrustedCredentials* getCreds() const\n  {\n    return &creds;\n  }\n\n  std::chrono::seconds getAge() const\n  {\n    return std::chrono::duration_cast<std::chrono::seconds>(\n             std::chrono::steady_clock::now() - creationTime\n           );\n  }\n\n  //----------------------------------------------------------------------------\n  // Describe object as string\n  //----------------------------------------------------------------------------\n  std::string describe() const\n  {\n    std::stringstream ss;\n    ss << \"Login identifier: \" << login.describe() << std::endl;\n    ss << creds.describe();\n    return ss.str();\n  }\n\nprivate:\n  LoginIdentifier login;\n  TrustedCredentials creds;\n  std::chrono::steady_clock::time_point creationTime;\n};\n\n// A class to read and parse environment values\n\nclass Environment\n{\npublic:\n  void fromFile(const std::string& path);\n  void fromString(const std::string& str);\n  void fromVector(const std::vector<std::string>& vec);\n\n  std::string get(const std::string& key) const;\n  std::vector<std::string> getAll() const;\n\n  void push_back(const std::string& str)\n  {\n    contents.emplace_back(str);\n  }\n\n  bool operator==(const Environment& other) const\n  {\n    return contents == other.contents;\n  }\nprivate:\n  std::vector<std::string> contents;\n};\n\n#endif\n"
  },
  {
    "path": "fusex/auth/CredentialValidator.cc",
    "content": "// ----------------------------------------------------------------------\n// File: CredentialValidator.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"CredentialValidator.hh\"\n#include \"CredentialFinder.hh\"\n#include \"UuidStore.hh\"\n#include \"Logbook.hh\"\n#include \"ScopedFsUidSetter.hh\"\n\nextern \"C\" {\n#include \"krb5.h\"\n}\n\n//----------------------------------------------------------------------------\n// Constructor - dependency injection of SecurityChecker\n//----------------------------------------------------------------------------\nCredentialValidator::CredentialValidator(SecurityChecker& chk,\n    UuidStore& store)\n  : checker(chk), credentialStore(store) { }\n\n//----------------------------------------------------------------------------\n// Should the given keyring be usable by this uid?\n//----------------------------------------------------------------------------\nbool CredentialValidator::checkKeyringUID(const std::string& keyring,\n    uid_t uid)\n{\n  std::string nameless = SSTR(\"KEYRING:persistent:\" << uid);\n\n  if (nameless == keyring) {\n    return true;\n  }\n\n  std::string prefix = SSTR(\"KEYRING:persistent:\" << uid << \":\");\n  return startsWith(keyring, prefix);\n}\n\n//----------------------------------------------------------------------------\n// Should the given kcm be usable by this uid?\n//----------------------------------------------------------------------------\nbool CredentialValidator::checkKcmUID(const std::string& kcm,\n                                      uid_t uid)\n{\n  std::string uidless = SSTR(\"KCM:\");\n\n  if (uidless == kcm) {\n    return true;\n  }\n\n  std::string nameless = SSTR(\"KCM:\" << uid);\n\n  if (nameless == kcm) {\n    return true;\n  }\n\n  std::string prefix = SSTR(\"KCM:\" << uid << \":\");\n  return startsWith(kcm, prefix);\n}\n\n//----------------------------------------------------------------------------\n// Some data comparison and conversion functions.\n//----------------------------------------------------------------------------\nstatic int data_eq(krb5_data d1, krb5_data d2)\n{\n  return (d1.length == d2.length && (d1.length == 0 ||\n                                     !memcmp(d1.data, d2.data, d1.length)));\n}\n\nstatic inline int data_eq_string(krb5_data d, const char* s)\n{\n  return (d.length == strlen(s) && (d.length == 0 ||\n                                    !memcmp(d.data, s, d.length)));\n}\n\n//----------------------------------------------------------------------------\n// Return true if princ is the local krbtgt principal for local_realm -\n// method exported from klist\n//----------------------------------------------------------------------------\nstatic krb5_boolean is_local_tgt(krb5_principal princ, krb5_data* realm)\n{\n  return princ->length == 2 && data_eq(princ->realm, *realm) &&\n         data_eq_string(princ->data[0], KRB5_TGS_NAME) &&\n         data_eq(princ->data[1], *realm);\n}\n\n//----------------------------------------------------------------------------\n// Return true if princ is the local krbtgt principal for local_realm -\n// method exported from klist\n//----------------------------------------------------------------------------\nstatic inline krb5_boolean ts_after(krb5_timestamp a, krb5_timestamp b)\n{\n  return (uint32_t)a > (uint32_t)b;\n}\n\n//----------------------------------------------------------------------------\n// Check if ccache is OK - method exported from klist, with minor changes\n//----------------------------------------------------------------------------\nstatic int check_ccache(krb5_context& context, krb5_ccache cache,\n                        krb5_timestamp now,\n\t\t\tstd::string& principal)\n{\n  /* clients/klist/klist.c - List contents of credential cache or keytab */\n  /*\n   * Copyright 1990 by the Massachusetts Institute of Technology.\n   * All Rights Reserved.\n   *\n   * Export of this software from the United States of America may\n   *   require a specific license from the United States Government.\n   *   It is the responsibility of any person or organization contemplating\n   *   export to obtain such a license before exporting.\n   *\n   * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and\n   * distribute this software and its documentation for any purpose and\n   * without fee is hereby granted, provided that the above copyright\n   * notice appear in all copies and that both that copyright notice and\n   * this permission notice appear in supporting documentation, and that\n   * the name of M.I.T. not be used in advertising or publicity pertaining\n   * to distribution of the software without specific, written prior\n   * permission.  Furthermore if you modify this software you must label\n   * your software as modified software and not distribute it in such a\n   * fashion that it might be confused with the original M.I.T. software.\n   * M.I.T. makes no representations about the suitability of\n   * this software for any purpose.  It is provided \"as is\" without express\n   * or implied warranty.\n   */\n  krb5_error_code ret;\n  krb5_cc_cursor cur;\n  krb5_creds creds;\n  krb5_principal princ;\n  krb5_boolean found_tgt, found_current_tgt, found_current_cred;\n\n  principal = \"\";\n\n  if (krb5_cc_get_principal(context, cache, &princ) != 0) {\n    return 1;\n  }\n\n  if (krb5_cc_start_seq_get(context, cache, &cur) != 0) {\n    return 1;\n  }\n\n  found_tgt = found_current_tgt = found_current_cred = FALSE;\n\n  while ((ret = krb5_cc_next_cred(context, cache, &cur, &creds)) == 0) {\n    if (is_local_tgt(creds.server, &princ->realm)) {\n      found_tgt = TRUE;\n\n      if (ts_after(creds.times.endtime, now)) {\n        found_current_tgt = TRUE;\n      }\n    } else if (!krb5_is_config_principal(context, creds.server) &&\n               ts_after(creds.times.endtime, now)) {\n      found_current_cred = TRUE;\n    }\n\n    krb5_free_cred_contents(context, &creds);\n  }\n\n  // extract name@REALM\n  char* princstring=0;\n  if ( krb5_unparse_name(\n\t\t\t context,\n\t\t\t princ,\n\t\t\t &princstring) == 0) {\n    principal = princstring;\n    krb5_free_string(context, princstring);\n  }\n\n  krb5_free_principal(context, princ);\n\n  if (ret != KRB5_CC_END) {\n    return 1;\n  }\n\n  if (krb5_cc_end_seq_get(context, cache, &cur) != 0) {\n    return 1;\n  }\n\n  /* If the cache contains at least one local TGT, require that it be\n   * current.  Otherwise accept any current cred. */\n  if (found_tgt) {\n    return found_current_tgt ? 0 : 1;\n  }\n\n  return found_current_cred ? 0 : 1;\n}\n\n//------------------------------------------------------------------------------\n// Validate the given set of UserCredentials, promote into TrustedCredentials,\n// if possible. Return true if promotion succeeded.\n//------------------------------------------------------------------------------\nbool CredentialValidator::validate(const JailInformation& jail,\n                                   const UserCredentials& uc, TrustedCredentials& out, LogbookScope& scope)\n{\n  if (uc.type == CredentialType::INVALID) {\n    THROW(\"invalid credentials provided to CredentialValidator\");\n  }\n\n  std::string principal;\n\n  //----------------------------------------------------------------------------\n  // Take care of the easy cases first\n  // TODO: Maybe need to add checks here later? eg check SSS endorsement,\n  // or something.\n  //----------------------------------------------------------------------------\n  if (uc.type == CredentialType::SSS || uc.type == CredentialType::NOBODY) {\n    LOGBOOK_INSERT(scope, \"Credential type does not need validation - accepting\");\n    out.initialize(uc, {0, 0}, \"\", \"\");\n    return true;\n  }\n\n  //----------------------------------------------------------------------------\n  // KRK5: Block everything other than persistent keyrings, ensure uid matches\n  //----------------------------------------------------------------------------\n  if (uc.type == CredentialType::KRK5) {\n    if (!checkKeyringUID(uc.keyring, uc.uid)) {\n      eos_static_alert(\"Refusing to use keyring %s by uid %d\", uc.keyring.c_str(),\n                       uc.uid);\n      LOGBOOK_INSERT(scope, \"Refusing to use \" << uc.keyring << \" from uid \" << uc.uid\n                     << \". Only persistent keyrings set to the proper uid owner can be used.\");\n      return false;\n    }\n\n#ifdef __linux__\n    ScopedFsUidSetter uidSetter(uc.uid, uc.gid);\n\n    if (!uidSetter.IsOk()) {\n      eos_static_crit(\"Could not set fsuid,fsgid to %d, %d\", uc.uid, uc.gid);\n      LOGBOOK_INSERT(scope, \"Could not set fsuid, fsgid to \" << uc.uid << \", \" <<\n                     uc.gid);\n      return false;\n    }\n\n#endif\n    //--------------------------------------------------------------------------\n    // Looks good. Does the keyring cache actually exist?\n    //--------------------------------------------------------------------------\n    krb5_context krb_ctx;\n    krb5_error_code ret = krb5_init_context(&krb_ctx);\n\n    if (ret != 0) {\n      eos_static_crit(\"Could not allocate krb5_init_context\");\n      LOGBOOK_INSERT(scope, \"Could not allocate krb5_init_context\");\n      return false;\n    }\n\n    krb5_ccache ccache;\n\n    if (krb5_cc_resolve(krb_ctx, uc.keyring.c_str(), &ccache) != 0) {\n      LOGBOOK_INSERT(scope, \"Could not resolve \" << uc.keyring);\n      krb5_free_context(krb_ctx);\n      return false;\n    }\n\n    //--------------------------------------------------------------------------\n    // Go through whatever klist does to check ccache validity.\n    //--------------------------------------------------------------------------\n    if (!checker.useInjected()) {\n      if (check_ccache(krb_ctx, ccache, time(0), principal) != 0) {\n\tkrb5_cc_close(krb_ctx, ccache);\n\tkrb5_free_context(krb_ctx);\n\tLOGBOOK_INSERT(scope, \"provided ccache appears invalid: \" << uc.keyring);\n\treturn false;\n      }\n    }\n\n    krb5_cc_close(krb_ctx, ccache);\n    krb5_free_context(krb_ctx);\n    out.initialize(uc, {0, 0}, \"\", principal);\n    return true;\n  }\n\n  //----------------------------------------------------------------------------\n  // KCM: Make sure the possible uid reference is for the calling uid\n  //----------------------------------------------------------------------------\n  if (uc.type == CredentialType::KCM) {\n    if (!checkKcmUID(uc.kcm, uc.uid)) {\n      eos_static_alert(\"Refusing to use kcm %s by uid %d\", uc.kcm.c_str(), uc.uid);\n      LOGBOOK_INSERT(scope, \"Refusing to use \" << uc.kcm << \" from uid \" << uc.uid <<\n                     \". Only KCM set to the proper uid are allowed.\");\n      return false;\n    }\n\n#ifdef __linux__\n    ScopedFsUidSetter uidSetter(uc.uid, uc.gid);\n\n    if (!uidSetter.IsOk()) {\n      eos_static_crit(\"Could not set fsuid,fsgid to %d, %d\", uc.uid, uc.gid);\n      LOGBOOK_INSERT(scope, \"Could not set fsuid, fsgid to \" << uc.uid << \", \" <<\n                     uc.gid);\n      return false;\n    }\n\n#endif\n    //--------------------------------------------------------------------------\n    // Looks good. Does the KCM cache actually exist?\n    //--------------------------------------------------------------------------\n    krb5_context krb_ctx;\n    krb5_error_code ret = krb5_init_context(&krb_ctx);\n\n    if (ret != 0) {\n      eos_static_crit(\"Could not allocate krb5_init_context\");\n      LOGBOOK_INSERT(scope, \"Could not allocate krb5_init_context\");\n      return false;\n    }\n\n    krb5_ccache ccache;\n\n    if (krb5_cc_resolve(krb_ctx, uc.kcm.c_str(), &ccache) != 0) {\n      eos_static_crit(\"Could not resolve\");\n      LOGBOOK_INSERT(scope, \"Could not resolve \" << uc.kcm);\n      krb5_free_context(krb_ctx);\n      return false;\n    }\n\n    //--------------------------------------------------------------------------\n    // Go through whatever klist does to check ccache validity.\n    //--------------------------------------------------------------------------\n    if (!checker.useInjected()) {\n      if (check_ccache(krb_ctx, ccache, time(0), principal) != 0) {\n\teos_static_crit(\"Could not check_ccache\");\n\tkrb5_cc_close(krb_ctx, ccache);\n\tkrb5_free_context(krb_ctx);\n\tLOGBOOK_INSERT(scope, \"provided ccache appears invalid: \" << uc.kcm);\n\treturn false;\n      }\n    }\n\n    krb5_cc_close(krb_ctx, ccache);\n    krb5_free_context(krb_ctx);\n    out.initialize(uc, {0, 0}, \"\", principal);\n    return true;\n  }\n\n  //----------------------------------------------------------------------------\n  // Only KRB5, X509, OAUTH2 remaining. Test credential file permissions.\n  //----------------------------------------------------------------------------\n  SecurityChecker::Info info = checker.lookup(jail, uc.fname, uc.uid, uc.gid);\n\n  //----------------------------------------------------------------------------\n  // Three cases:\n  //----------------------------------------------------------------------------\n  switch (info.state) {\n  case CredentialState::kCannotStat:\n    //------------------------------------------------------------------------\n    // Credential file cannot be stat'ed\n    //------------------------------------------------------------------------\n    LOGBOOK_INSERT(scope, \"Credential file unable to stat\");\n    return false;\n\n  case CredentialState::kBadPermissions: {\n    //------------------------------------------------------------------------\n    // Credential file cannot be used.\n    //------------------------------------------------------------------------\n    LOGBOOK_INSERT(scope, \"Credential file has bad permissions\");\n    return false;\n  }\n\n  case CredentialState::kOk: {\n    //----------------------------------------------------------------------------\n    // KRB5:\n    //----------------------------------------------------------------------------\n    if (uc.type == CredentialType::KRB5) {\n#ifdef __linux__\n      ScopedFsUidSetter uidSetter(uc.uid, uc.gid);\n\n      if (!uidSetter.IsOk()) {\n\teos_static_crit(\"Could not set fsuid,fsgid to %d, %d\", uc.uid, uc.gid);\n\tLOGBOOK_INSERT(scope, \"Could not set fsuid, fsgid to \" << uc.uid << \", \" <<\n\t\t       uc.gid);\n\treturn false;\n      }\n#endif\n      //--------------------------------------------------------------------------\n      krb5_context krb_ctx;\n      krb5_error_code ret = krb5_init_context(&krb_ctx);\n\n      if (ret != 0) {\n\teos_static_crit(\"Could not allocate krb5_init_context\");\n\tLOGBOOK_INSERT(scope, \"Could not allocate krb5_init_context\");\n\treturn false;\n      }\n\n      krb5_ccache ccache;\n\n      if (krb5_cc_resolve(krb_ctx, uc.fname.c_str(), &ccache) != 0) {\n\teos_static_crit(\"Could not resolve %s\\n\", uc.fname.c_str());\n\tLOGBOOK_INSERT(scope, \"Could not resolve \" << uc.fname);\n\tkrb5_free_context(krb_ctx);\n\treturn false;\n      }\n\n      //--------------------------------------------------------------------------\n      // Go through whatever klist does to check ccache validity.\n      //--------------------------------------------------------------------------\n      if (!checker.useInjected()) {\n\tif (check_ccache(krb_ctx, ccache, time(0), principal) != 0) {\n\t  krb5_cc_close(krb_ctx, ccache);\n\t  krb5_free_context(krb_ctx);\n\t  LOGBOOK_INSERT(scope, \"provided ccache appears invalid: \" << uc.fname);\n\t  return false;\n\t}\n      }\n\n      krb5_cc_close(krb_ctx, ccache);\n      krb5_free_context(krb_ctx);\n    }\n\n    //------------------------------------------------------------------------\n    // Credential file is OK, and the SecurityChecker determined the path\n    // can be used as-is - no need for copying.\n    //------------------------------------------------------------------------\n    LOGBOOK_INSERT(scope, \"Credential file is OK - using as-is\");\n    out.initialize(uc, info.mtime, \"\", principal);\n    return true;\n  }\n\n  case CredentialState::kOkWithContents: {\n    //------------------------------------------------------------------------\n    // Credential file is OK, but is not safe to pass onto XrdCl. We should\n    // copy it onto our own credential store, and use that when building\n    // XrdCl params.\n    //------------------------------------------------------------------------\n    std::string casPath = credentialStore.put(info.contents);\n    LOGBOOK_INSERT(scope, \"Credential file must be copied - path: \" << casPath);\n    //----------------------------------------------------------------------------\n    // KRB5:\n    //----------------------------------------------------------------------------\n    if (uc.type == CredentialType::KRB5) {\n      //--------------------------------------------------------------------------\n      // the copied credential is owned by root!\n      //--------------------------------------------------------------------------      \n      krb5_context krb_ctx;\n      krb5_error_code ret = krb5_init_context(&krb_ctx);\n\n      if (ret != 0) {\n\teos_static_crit(\"Could not allocate krb5_init_context\");\n\tLOGBOOK_INSERT(scope, \"Could not allocate krb5_init_context\");\n\treturn false;\n      }\n\n      krb5_ccache ccache;\n\n      if (krb5_cc_resolve(krb_ctx, casPath.c_str(), &ccache) != 0) {\n\teos_static_crit(\"Could not resolve %s\\n\", casPath.c_str());\n\tLOGBOOK_INSERT(scope, \"Could not resolve \" << casPath.c_str());\n\tkrb5_free_context(krb_ctx);\n\treturn false;\n      }\n\n      //--------------------------------------------------------------------------\n      // Go through whatever klist does to check ccache validity.\n      //--------------------------------------------------------------------------\n      if (!checker.useInjected()) {\n\tif (check_ccache(krb_ctx, ccache, time(0), principal) != 0) {\n\t  krb5_cc_close(krb_ctx, ccache);\n\t  krb5_free_context(krb_ctx);\n\t  LOGBOOK_INSERT(scope, \"provided ccache appears invalid: \" << casPath.c_str());\n\t  return false;\n\t}\n      }\n      LOGBOOK_INSERT(scope, \"provided ccache appears valid: \" << casPath.c_str());\n      krb5_cc_close(krb_ctx, ccache);\n      krb5_free_context(krb_ctx);\n    }\n\n    out.initialize(uc, info.mtime, casPath, principal);\n    return true;\n  }\n  }\n\n  THROW(\"should never reach here\");\n}\n\n//------------------------------------------------------------------------------\n// Check two given timespecs for equality\n//------------------------------------------------------------------------------\nstatic bool checkTimespecEquality(const struct timespec& t1,\n                                  const struct timespec& t2)\n{\n  return t1.tv_sec == t2.tv_sec && t1.tv_nsec == t2.tv_nsec;\n}\n\n//------------------------------------------------------------------------------\n// Is the given TrustedCredentials object still valid? Reasons for\n// invalidation:\n//\n// - The underlying credential file on disk has changed.\n// - Reconnection\n//------------------------------------------------------------------------------\nbool CredentialValidator::checkValidity(const JailInformation& jail,\n                                        const TrustedCredentials& tc)\n{\n  if (!tc.valid()) {\n    return false;\n  }\n\n  const UserCredentials& uc = tc.getUC();\n\n  //----------------------------------------------------------------------------\n  // KRK5, SSS, and nobody don't expire.\n  //----------------------------------------------------------------------------\n  if (uc.type == CredentialType::KRK5 || uc.type == CredentialType::SSS ||\n      uc.type == CredentialType::KCM ||\n      uc.type == CredentialType::NOBODY) {\n    return true;\n  }\n\n  //----------------------------------------------------------------------------\n  // KRB5, X509, OAUTH2: Check underlying file, ensure contents have not changed.\n  //----------------------------------------------------------------------------\n  SecurityChecker::Info info = checker.lookup(jail, uc.fname, uc.uid, uc.gid);\n\n  if (info.state != CredentialState::kOk &&\n      info.state != CredentialState::kOkWithContents) {\n    //--------------------------------------------------------------------------\n    // File has disappeared on us, or permissions changed.\n    //--------------------------------------------------------------------------\n    tc.invalidate();\n    return false;\n  }\n\n  if (!checkTimespecEquality(info.mtime, tc.getMTime())) {\n    //--------------------------------------------------------------------------\n    // File was modified\n    //--------------------------------------------------------------------------\n    tc.invalidate();\n    return false;\n  }\n\n  //----------------------------------------------------------------------------\n  // All clear\n  //----------------------------------------------------------------------------\n  return true;\n}\n"
  },
  {
    "path": "fusex/auth/CredentialValidator.hh",
    "content": "// ----------------------------------------------------------------------\n// File: CredentialValidator.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSEX_CREDENTIAL_VALIDATOR_HH\n#define FUSEX_CREDENTIAL_VALIDATOR_HH\n\n#include <string>\n\nclass TrustedCredentials;\nclass SecurityChecker;\nstruct UserCredentials;\nstruct JailInformation;\nclass UuidStore;\nclass LogbookScope;\n\n//------------------------------------------------------------------------------\n// This class validates UserCredentials objects, and promotes those that\n// pass the test into TrustedCredentials.\n//\n// UserCredentials is built from user-provided data, and thus cannot be\n// trusted before validation checks.\n//------------------------------------------------------------------------------\nclass CredentialValidator\n{\npublic:\n  //----------------------------------------------------------------------------\n  // Constructor\n  //----------------------------------------------------------------------------\n  CredentialValidator(SecurityChecker& chk, UuidStore& credentialStore);\n\n  //----------------------------------------------------------------------------\n  // Validate the given set of UserCredentials, promote into TrustedCredentials,\n  // if possible. Return true if promotion succeeded.\n  //----------------------------------------------------------------------------\n  bool validate(const JailInformation& jail,\n                const UserCredentials& uc, TrustedCredentials& out,\n                LogbookScope& scope);\n\n  //----------------------------------------------------------------------------\n  // Is the given TrustedCredentials object still valid? Reasons for\n  // invalidation:\n  //\n  // - The underlying credential file on disk has changed.\n  // - Reconnection\n  //----------------------------------------------------------------------------\n  bool checkValidity(const JailInformation& jail,\n                     const TrustedCredentials& out);\n\n  //----------------------------------------------------------------------------\n  // Should the given keyring be usable by this uid?\n  //----------------------------------------------------------------------------\n  bool checkKeyringUID(const std::string& keyring, uid_t uid);\n\n  //----------------------------------------------------------------------------\n  // Should the given KCM user be usable by this uid?\n  //----------------------------------------------------------------------------\n  bool checkKcmUID(const std::string& kcm, uid_t uid);\n\nprivate:\n  SecurityChecker& checker;\n  UuidStore& credentialStore;\n};\n\n#endif\n"
  },
  {
    "path": "fusex/auth/DirectoryIterator.cc",
    "content": "// ----------------------------------------------------------------------\n// File: DirectoryIterator.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"DirectoryIterator.hh\"\n#include \"Utils.hh\"\n\n#include \"common/Logging.hh\"\n\n#include <string.h>\n\n//------------------------------------------------------------------------------\n// Construct iterator object on the given path - must be a directory.\n//------------------------------------------------------------------------------\nDirectoryIterator::DirectoryIterator(const std::string& mypath)\n  : path(mypath), reachedEnd(false), dir(nullptr), nextEntry(nullptr)\n{\n  dir = opendir(path.c_str());\n\n  if (!dir) {\n    error = SSTR(\"Unable to opendir: \" << path);\n    return;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nDirectoryIterator::~DirectoryIterator()\n{\n  if (dir) {\n    if (closedir(dir) != 0) {\n      eos_static_crit(\"Unable to close DIR* for %s\", path.c_str());\n    }\n\n    dir = nullptr;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Retrieve next directory entry.\n// This object retains ownership on the given pointer, never call free on it.\n//\n// If the iterator is in an error state, next() will only ever return nullptr.\n//------------------------------------------------------------------------------\nstruct dirent* DirectoryIterator::next()\n{\n  if (!ok()) {\n    return nullptr;\n  }\n\n  if (reachedEnd) {\n    return nullptr;\n  }\n\n  errno = 0;\n  nextEntry = readdir(dir);\n\n  if (!nextEntry && errno == 0) {\n    reachedEnd = true;\n  } else if (!nextEntry) {\n    error = SSTR(\"Error when calling readdir: \" << strerror(errno));\n  }\n\n  return nextEntry;\n}\n\n//------------------------------------------------------------------------------\n// Checks if the iterator is in an error state. EOF is not an error state!\n//------------------------------------------------------------------------------\nbool DirectoryIterator::ok() const\n{\n  return error.empty();\n}\n\n//------------------------------------------------------------------------------\n// Checks whether we have reached the end.\n//------------------------------------------------------------------------------\nbool DirectoryIterator::eof() const\n{\n  return reachedEnd;\n}\n\n//------------------------------------------------------------------------------\n// Retrieve the error message if the iterator object is in an error state.\n// If no error state, returns an empty string.\n//------------------------------------------------------------------------------\nstd::string DirectoryIterator::err() const\n{\n  return error;\n}\n"
  },
  {
    "path": "fusex/auth/DirectoryIterator.hh",
    "content": "// ----------------------------------------------------------------------\n// File: DirectoryIterator.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSEX_DIRECTORY_ITERATOR_HH\n#define FUSEX_DIRECTORY_ITERATOR_HH\n\n#include <dirent.h>\n#include <string>\n\nclass DirectoryIterator\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  // Construct iterator object on the given path - must be a directory.\n  //----------------------------------------------------------------------------\n  DirectoryIterator(const std::string& path);\n\n  //----------------------------------------------------------------------------\n  // Destructor\n  //----------------------------------------------------------------------------\n  ~DirectoryIterator();\n\n  //----------------------------------------------------------------------------\n  // Checks if the iterator is in an error state. EOF is not an error state!\n  //----------------------------------------------------------------------------\n  bool ok() const;\n\n  //----------------------------------------------------------------------------\n  // Retrieve the error message if the iterator object is in an error state.\n  // If no error state, returns an empty string.\n  //----------------------------------------------------------------------------\n  std::string err() const;\n\n  //----------------------------------------------------------------------------\n  // Checks whether we have reached the end.\n  //----------------------------------------------------------------------------\n  bool eof() const;\n\n  //----------------------------------------------------------------------------\n  // Retrieve next directory entry.\n  // This object retains ownership on the given pointer, never call free on it.\n  //\n  // If the iterator is in an error state, next() will only ever return nullptr.\n  //----------------------------------------------------------------------------\n  struct dirent* next();\n\nprivate:\n  std::string error;\n  std::string path;\n  bool reachedEnd;\n\n  DIR* dir;\n  struct dirent* nextEntry;\n};\n\n#endif\n"
  },
  {
    "path": "fusex/auth/EnvironmentReader.cc",
    "content": "//------------------------------------------------------------------------------\n// File: EnvironmentReader.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"EnvironmentReader.hh\"\n\n//------------------------------------------------------------------------------\n//! Constructor - launch a thread pool with the specified number of threads\n//------------------------------------------------------------------------------\nEnvironmentReader::EnvironmentReader(size_t nthreads)\n{\n  //----------------------------------------------------------------------------\n  // Start up our thread pool.\n  //----------------------------------------------------------------------------\n  for (size_t i = 0; i < nthreads; i++) {\n    threads.emplace_back(&EnvironmentReader::worker, this);\n  }\n\n  //----------------------------------------------------------------------------\n  // Wait until all threads have been properly spawned - this allows us to\n  // assume all threads are active in the destructor.\n  //----------------------------------------------------------------------------\n  while (threadsAlive != nthreads);\n}\n\nvoid EnvironmentReader::inject(pid_t pid, const Environment& env,\n                               std::chrono::milliseconds artificialDelay)\n{\n  SimulatedResponse simulated;\n  simulated.env = env;\n  simulated.artificialDelay = artificialDelay;\n  std::lock_guard<std::mutex> lock(injectionMtx);\n  injections[pid] = simulated;\n}\n\nvoid EnvironmentReader::removeInjection(pid_t pid)\n{\n  std::lock_guard<std::mutex> lock(injectionMtx);\n  injections.erase(pid);\n}\n\nvoid EnvironmentReader::fillFromInjection(pid_t pid, Environment& env)\n{\n  SimulatedResponse response;\n  {\n    std::lock_guard<std::mutex> lock(injectionMtx);\n    auto it = injections.find(pid);\n\n    if (it == injections.end()) {\n      return;\n    }\n\n    response = it->second;\n  }\n  std::this_thread::sleep_for(response.artificialDelay);\n  env = response.env;\n}\n\nEnvironmentReader::~EnvironmentReader()\n{\n  //----------------------------------------------------------------------------\n  // Spin until all threads are done, and join.\n  //----------------------------------------------------------------------------\n  shutdown = true;\n\n  while (threadsAlive != 0) {\n    queueCV.notify_all();\n  }\n\n  for (size_t i = 0; i < threads.size(); i++) {\n    threads[i].join();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Each worker loops on the queue, waiting for pending requests to fulfill.\n//------------------------------------------------------------------------------\nvoid EnvironmentReader::worker()\n{\n  threadsAlive++;\n  std::unique_lock<std::mutex> lock(mtx);\n\n  while (!shutdown) {\n    //------------------------------------------------------------------------\n    // Is there an item for me to process?\n    //------------------------------------------------------------------------\n    if (!requestQueue.empty()) {\n      QueuedRequest request = std::move(requestQueue.front());\n      requestQueue.pop();\n      lock.unlock();\n      //------------------------------------------------------------------------\n      // Yes, I have work to do. Start timing how long it takes to receive\n      // a response from the kernel.\n      //------------------------------------------------------------------------\n      std::chrono::high_resolution_clock::time_point startTime =\n        std::chrono::high_resolution_clock::now();\n      Environment env;\n\n      //------------------------------------------------------------------------\n      // Provide simulated or real response?\n      //------------------------------------------------------------------------\n      if (injections.empty()) {\n        //----------------------------------------------------------------------\n        // Real response, read environment. If a (temporary) kernel deadlock\n        // occurs, it will be at this point. Provide simulated or real response?\n        //----------------------------------------------------------------------\n        env.fromFile(SSTR(\"/proc/\" << request.pid << \"/environ\"));\n      } else {\n        //----------------------------------------------------------------------\n        // Simulation\n        //----------------------------------------------------------------------\n        fillFromInjection(request.pid, env);\n      }\n\n      //----------------------------------------------------------------------\n      // Measure how long it took, issue warning if too high.\n      //----------------------------------------------------------------------\n      std::chrono::high_resolution_clock::time_point endTime =\n        std::chrono::high_resolution_clock::now();\n      std::chrono::milliseconds duration =\n        std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);\n\n      if (duration.count() > 5) {\n        eos_static_notice(\"Reading /proc/%d/environ took %dms (uid=%d)\", request.pid,\n                          duration.count(), request.uid);\n      }\n\n      //------------------------------------------------------------------------\n      // It's over, it's done. Give back result.\n      //------------------------------------------------------------------------\n      lock.lock();\n      auto it = pendingRequests.find(request.pid);\n\n      if (it == pendingRequests.end()) {\n        eos_static_crit(\"EnvironmentReader queue corruption, unable to find entry for pid %d\",\n                        request.pid);\n      } else {\n        pendingRequests.erase(it);\n      }\n\n      request.promise.set_value(env);\n      //------------------------------------------------------------------------\n      // Process next item in the queue, no waiting.\n      //------------------------------------------------------------------------\n    } else {\n      //------------------------------------------------------------------------\n      // No work to do, sleep.\n      //------------------------------------------------------------------------\n      queueCV.wait_for(lock, std::chrono::seconds(1));\n    }\n  }\n\n  threadsAlive--;\n}\n\n//------------------------------------------------------------------------------\n// Request to retrieve the environmnet variables for the given pid.\n//\n// Returns a FutureEnvironment object, which _might_ be kernel-deadlocked,\n// and must be waited-for with a timeout.\n//------------------------------------------------------------------------------\nFutureEnvironment EnvironmentReader::stageRequest(pid_t pid, uid_t uid)\n{\n  std::unique_lock<std::mutex> lock(mtx);\n  eos_static_debug(\"Staging request to read environment of pid %d for %d\", pid,\n                   uid);\n  //----------------------------------------------------------------------------\n  //! Check: Is this request already pending? If so, give back the same\n  //! response, connected to the same promise object.\n  //----------------------------------------------------------------------------\n  auto it = pendingRequests.find(pid);\n\n  if (it != pendingRequests.end()) {\n    eos_static_debug(\"Request to read environment for pid %d already staged\", pid);\n    return it->second;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Nope, stage it\n  //----------------------------------------------------------------------------\n  QueuedRequest request;\n  FutureEnvironment response;\n  request.pid = pid;\n  request.uid = uid;\n  response.contents = request.promise.get_future();\n  response.queuedSince = std::chrono::high_resolution_clock::now();\n  pendingRequests[pid] = response;\n  requestQueue.push(std::move(request));\n  eos_static_debug(\"Queueing request to read environment for pid %d, notifying workers\",\n                   pid);\n  queueCV.notify_all();\n  return response;\n}\n"
  },
  {
    "path": "fusex/auth/EnvironmentReader.hh",
    "content": "//------------------------------------------------------------------------------\n// File: AsynchronousFileReader.hh\n// Author: Georgios Bitzes, CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __ENVIRONMENT_READER_HH__\n#define __ENVIRONMENT_READER_HH__\n\n#include <vector>\n#include <future>\n#include <chrono>\n#include <queue>\n#include \"CredentialFinder.hh\"\n\nstruct FutureEnvironment {\n  std::shared_future<Environment> contents;\n  std::chrono::high_resolution_clock::time_point queuedSince;\n\n  //----------------------------------------------------------------------------\n  //! Returns the Environemnt object.\n  //!\n  //! Always call wait first with a timeout! This could block indefinitely\n  //! causing a kernel deadlock.\n  //----------------------------------------------------------------------------\n  Environment get()\n  {\n    return contents.get();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Wait until the deadline, which is, t milliseconds after queuedSince. If\n  //! this time has elapsed since submitting the request, give up and unblock.\n  //!\n  //! Returns whether the result is available or not. If false, it means the\n  //! deadline has certainly passed.\n  //----------------------------------------------------------------------------\n  bool waitUntilDeadline(std::chrono::milliseconds t)\n  {\n    std::chrono::high_resolution_clock::time_point deadline =\n      queuedSince + t;\n    return contents.wait_until(deadline) == std::future_status::ready;\n  }\n};\n\n//------------------------------------------------------------------------------\n//! This contraption is used to safely read /proc/pid/environ in a separate\n//! thread, without risk of deadlocking.\n//!\n//!\n//! We return a future to all requests. Never block on it, always wait with a\n//! timeout.\n//!\n//! If we receive a request for the same file again, and the other is still\n//! pending, we should tell the caller for how long the other request has been\n//! pending for.\n//!\n//! This is because a single execve() will typically issue many requests to\n//! fuse - we only want to pay the wait penalty once.\n//------------------------------------------------------------------------------\nclass EnvironmentReader\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor - launch a thread pool with the specified number of threads\n  //----------------------------------------------------------------------------\n  EnvironmentReader(size_t threads);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~EnvironmentReader();\n\n  //----------------------------------------------------------------------------\n  //! Request to retrieve the environmnet variables for the given pid.\n  //!\n  //! Returns a FutureEnvironment object, which _might_ be kernel-deadlocked,\n  //! and must be waited-for with a timeout.\n  //----------------------------------------------------------------------------\n  FutureEnvironment stageRequest(pid_t pid, uid_t uid = -1);\n\n  //----------------------------------------------------------------------------\n  //! Inject fake data into this class. _All_ responses will be faked if there's\n  //! at least one injection active. Used in testing.\n  //----------------------------------------------------------------------------\n  void inject(pid_t pid, const Environment& env,\n              std::chrono::milliseconds artificialDelay = std::chrono::milliseconds(0));\n\n  //----------------------------------------------------------------------------\n  //! Remove fake data injection for given pid.\n  //----------------------------------------------------------------------------\n  void removeInjection(pid_t pid);\nprivate:\n  //----------------------------------------------------------------------------\n  //! Fill fake data for a request.\n  //----------------------------------------------------------------------------\n  void fillFromInjection(pid_t pid, Environment& env);\n\n  //----------------------------------------------------------------------------\n  //! Stores a simulated response, served from fake data.\n  //----------------------------------------------------------------------------\n  struct SimulatedResponse {\n    Environment env;\n    std::chrono::milliseconds artificialDelay;\n  };\n\n  //----------------------------------------------------------------------------\n  //! For each pending, still-unfulfilled request we keep a QueuedRequest\n  //! object with the corresponding promise.\n  //----------------------------------------------------------------------------\n  struct QueuedRequest {\n    pid_t pid;\n    uid_t uid;\n    std::promise<Environment> promise;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Each worker loops on the queue, waiting for pending requests to fulfill.\n  //----------------------------------------------------------------------------\n  void worker();\n\n  //----------------------------------------------------------------------------\n  //! Thread synchronization, request queue\n  //----------------------------------------------------------------------------\n  std::atomic<bool> shutdown{false};\n  std::atomic<size_t> threadsAlive{0};\n  std::vector<std::thread> threads;\n\n  std::mutex mtx;\n  std::condition_variable queueCV;\n  std::queue<QueuedRequest> requestQueue;\n  std::map<pid_t, FutureEnvironment> pendingRequests;\n\n  std::mutex injectionMtx;\n  std::map<pid_t, SimulatedResponse> injections;\n};\n\n#endif\n"
  },
  {
    "path": "fusex/auth/FileDescriptor.hh",
    "content": "//----------------------------------------------------------------------\n// File: ScopedFileDescriptor.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_FUSEX_FILE_DESCRIPTOR_HH\n#define EOS_FUSEX_FILE_DESCRIPTOR_HH\n\n#include <unistd.h>\n\nclass FileDescriptor\n{\npublic:\n  FileDescriptor(int fd_) : fd(fd_)\n  {\n    if (fd < 0) {\n      // We assume that EventDescriptor immediatelly wraps a call which\n      // returns a file descriptor, so errno still contains the error we're\n      // interested in.\n      localerrno = errno;\n    }\n  }\n\n  FileDescriptor() {}\n\n  ~FileDescriptor()\n  {\n    close();\n  }\n\n  // No assignment, no copying\n  FileDescriptor& operator=(const FileDescriptor&) = delete;\n  FileDescriptor(const FileDescriptor&) = delete;\n\n  // Moving is acceptable.\n  FileDescriptor(FileDescriptor&& other)\n  {\n    close();\n    localerrno = other.localerrno;\n    fd = other.fd;\n    other.localerrno = 0;\n    other.fd = -1;\n  }\n\n  FileDescriptor& operator=(FileDescriptor&& other)\n  {\n    close();\n    localerrno = other.localerrno;\n    fd = other.fd;\n    other.localerrno = 0;\n    other.fd = -1;\n    return *this;\n  }\n\n  void close()\n  {\n    if (fd >= 0) {\n      ::close(fd);\n      fd = -1;\n    }\n  }\n\n  bool ok()\n  {\n    return fd >= 0 && localerrno == 0;\n  }\n\n  std::string err()\n  {\n    return strerror(localerrno);\n  }\n\n  int getFD()\n  {\n    return fd;\n  }\n\nprivate:\n  int localerrno = 0;\n  int fd = -1;\n\n};\n\n#endif\n\n"
  },
  {
    "path": "fusex/auth/JailIdentifier.cc",
    "content": "//------------------------------------------------------------------------------\n// File: JailIdentifier.cc\n// Author: Georgios Bitzes, CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"JailIdentifier.hh\"\n#include \"Utils.hh\"\n#include \"common/Logging.hh\"\n#include <sys/stat.h>\n#include <string.h>\n#include <unistd.h>\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nJailResolver::JailResolver()\n{\n  myJail = resolve(getpid());\n}\n\n//------------------------------------------------------------------------------\n// Resolve a given pid_t\n//------------------------------------------------------------------------------\nJailIdentifier JailResolver::resolveIdentifier(pid_t pid)\n{\n  std::string path = SSTR(\"/proc/\" << pid << \"/root\");\n  struct stat buf;\n\n  if (stat(path.c_str(), &buf) != 0) {\n    int myerrno = errno;\n    return JailIdentifier::MakeError(myerrno,\n                                     SSTR(\"Could not resolve jail of \" << pid << \": \" << strerror(myerrno)));\n  }\n\n  return JailIdentifier::Make(buf.st_dev, buf.st_ino);\n}\n\n//------------------------------------------------------------------------------\n// Describe this object\n//------------------------------------------------------------------------------\nstd::string JailIdentifier::describe() const\n{\n  if (!ok()) {\n    return SSTR(\"Jail resolution failed: errno=\" << errc << \", \" << error);\n  }\n\n  return SSTR(\"jail identifier: st_dev=\" << st_dev << \", ino=\" << st_ino);\n}\n\n//------------------------------------------------------------------------------\n// Simple hash for this jail\n//------------------------------------------------------------------------------\nuint64_t JailIdentifier::hash() const\n{\n  return ((st_dev<<32) + st_ino);\n}\n\n//------------------------------------------------------------------------------\n// Describe a JailInformation object\n//------------------------------------------------------------------------------\nstd::string JailInformation::describe() const\n{\n  std::string idDescr = id.describe();\n\n  if (sameJailAsThisPid) {\n    return SSTR(idDescr << \" -- same jail as eosxd\");\n  }\n\n  return SSTR(idDescr << \" -- DIFFERENT jail than eosxd!\");\n}\n\n//----------------------------------------------------------------------------\n// Check if the object contains an error\n//----------------------------------------------------------------------------\nbool JailIdentifier::ok() const\n{\n  return error.empty();\n}\n\n//------------------------------------------------------------------------------\n// Equality operator\n//------------------------------------------------------------------------------\nbool JailIdentifier::operator==(const JailIdentifier& other) const\n{\n  if (errc != other.errc) {\n    return false;\n  }\n\n  if (error != other.error) {\n    return false;\n  }\n\n  if (st_dev != other.st_dev) {\n    return false;\n  }\n\n  if (st_ino != other.st_ino) {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Resolve a given pid_t to JailInformation\n//------------------------------------------------------------------------------\nJailInformation JailResolver::resolve(pid_t pid)\n{\n  JailIdentifier id = resolveIdentifier(pid);\n  return { id, pid, (id == myJail.id) };\n}\n\n//------------------------------------------------------------------------------\n// Resolve a given pid_t to JailInformation - if an error is encountered,\n// return _my_ jail.\n//------------------------------------------------------------------------------\nJailInformation JailResolver::resolveOrReturnMyJail(pid_t pid)\n{\n  JailInformation jailInfo = resolve(pid);\n\n  if (!jailInfo.id.ok()) {\n    //--------------------------------------------------------------------------\n    // Couldn't retrieve jail of this pid.. bad. Assume our jail.\n    //--------------------------------------------------------------------------\n    eos_static_notice(\"Could not retrieve jail information for pid=%d\", pid);\n    return myJail;\n  }\n\n  return jailInfo;\n}\n"
  },
  {
    "path": "fusex/auth/JailIdentifier.hh",
    "content": "//------------------------------------------------------------------------------\n// File: JailIdentifier.hh\n// Author: Georgios Bitzes, CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_FUSE_JAIL_IDENTIFIER_HH\n#define EOS_FUSE_JAIL_IDENTIFIER_HH\n\n#include <string>\n#include <cstdint>\n\n#ifdef __APPLE__\ntypedef uint64_t ino_t;\n#endif\n\n//------------------------------------------------------------------------------\n// Uniquely identifies a jail - also contains room for an error message, in\n// case jail resolution was not successful.\n//------------------------------------------------------------------------------\nclass JailIdentifier\n{\npublic:\n  //----------------------------------------------------------------------------\n  // Constructor: Empty object.\n  //----------------------------------------------------------------------------\n  JailIdentifier() : errc(0), st_dev(0), st_ino(0) {}\n\n  //----------------------------------------------------------------------------\n  // Constructor: Indicate an error message - jail resolution failed.\n  //----------------------------------------------------------------------------\n  static JailIdentifier MakeError(int errc, const std::string& msg)\n  {\n    JailIdentifier id;\n    id.errc = errc;\n    id.error = msg;\n    return id;\n  }\n\n  //----------------------------------------------------------------------------\n  // Constructor: Identification succeeded.\n  //----------------------------------------------------------------------------\n  static JailIdentifier Make(dev_t dev, ino_t ino)\n  {\n    JailIdentifier id;\n    id.st_dev = dev;\n    id.st_ino = ino;\n    return id;\n  }\n\n  //----------------------------------------------------------------------------\n  // Describe this object.\n  //----------------------------------------------------------------------------\n  std::string describe() const;\n\n  //----------------------------------------------------------------------------\n  // Simple hash for this jail\n  //----------------------------------------------------------------------------\n  uint64_t hash() const;\n\n  //----------------------------------------------------------------------------\n  // Check if the object contains an error\n  //----------------------------------------------------------------------------\n  bool ok() const;\n\n  //----------------------------------------------------------------------------\n  // Equality operator\n  //----------------------------------------------------------------------------\n  bool operator==(const JailIdentifier& other) const;\n\nprivate:\n  //----------------------------------------------------------------------------\n  // error filled only to indicate jail resolution failed.\n  //----------------------------------------------------------------------------\n  int errc;\n  std::string error;\n\n  //----------------------------------------------------------------------------\n  // Identification\n  //----------------------------------------------------------------------------\n  dev_t st_dev;\n  ino_t st_ino;\n};\n\n//------------------------------------------------------------------------------\n// JailInformation: JailIdentifier + pid_t\n//\n// We can't store pid in JailIdentifier, it's used as a cache key. Many pids\n// will resolve to the same JailIdentifier, adding pid there breaks caching.\n//\n// But we need the pid to actually do path lookups in such jail, st_dev and\n// st_ino can't be used in such case... Hence the distinction between\n// JailIdentifier and JailInformation.\n//------------------------------------------------------------------------------\nstruct JailInformation {\n  JailIdentifier id;\n  pid_t pid;\n  bool sameJailAsThisPid;\n\n  std::string describe() const;\n};\n\n//------------------------------------------------------------------------------\n// Use this class to uniquely resolve jails.\n//------------------------------------------------------------------------------\nclass JailResolver\n{\npublic:\n  //----------------------------------------------------------------------------\n  // Constructor\n  //----------------------------------------------------------------------------\n  JailResolver();\n\n  //----------------------------------------------------------------------------\n  // Resolve a given pid_t to JailIdentifier\n  //----------------------------------------------------------------------------\n  JailIdentifier resolveIdentifier(pid_t pid);\n\n  //----------------------------------------------------------------------------\n  // Resolve a given pid_t to JailInformation\n  //----------------------------------------------------------------------------\n  JailInformation resolve(pid_t pid);\n\n  //----------------------------------------------------------------------------\n  // Resolve a given pid_t to JailInformation - if an error is encountered,\n  // return _my_ jail.\n  //----------------------------------------------------------------------------\n  JailInformation resolveOrReturnMyJail(pid_t pid);\n\nprivate:\n  JailInformation myJail;\n};\n\n#endif\n"
  },
  {
    "path": "fusex/auth/Logbook.cc",
    "content": "//------------------------------------------------------------------------------\n// File: Logbook.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"Logbook.hh\"\n#include <sstream>\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nLogbook::Logbook(bool active_) : activated(active_) {}\n\n//------------------------------------------------------------------------------\n// Record message into the log\n//------------------------------------------------------------------------------\nvoid Logbook::insert(const std::string& msg)\n{\n  if (activated) {\n    messages.emplace_back(msg);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get a new scope\n//------------------------------------------------------------------------------\nLogbookScope Logbook::makeScope(const std::string& header)\n{\n  return LogbookScope(this, header, 0);\n}\n\n//----------------------------------------------------------------------------\n// Check if activated\n//----------------------------------------------------------------------------\nbool Logbook::active() const\n{\n  return activated;\n}\n\n//------------------------------------------------------------------------------\n// Build a string out of all messages\n//------------------------------------------------------------------------------\nstd::string Logbook::toString() const\n{\n  std::stringstream ss;\n\n  for (size_t i = 0; i < messages.size(); i++) {\n    ss << messages[i] << std::endl;\n  }\n\n  return ss.str();\n}\n\n//------------------------------------------------------------------------------\n// Empty constructor\n//------------------------------------------------------------------------------\nLogbookScope::LogbookScope() : logbook(nullptr), indentationLevel(false)\n{\n}\n\n\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nLogbookScope::LogbookScope(Logbook* lb, const std::string& header,\n                           size_t indent) : logbook(lb), indentationLevel(indent)\n{\n  std::stringstream ss;\n\n  for (size_t i = 0; i < indentationLevel; i++) {\n    ss << \" \";\n  }\n\n  ss << \"-- \" << header;\n  logbook->insert(ss.str());\n}\n\n//----------------------------------------------------------------------------\n// Get a new sub-scope\n//----------------------------------------------------------------------------\nLogbookScope LogbookScope::makeScope(const std::string& header)\n{\n  if (!logbook) {\n    return LogbookScope();\n  }\n\n  return LogbookScope(logbook, header, indentationLevel + 2);\n}\n\n//----------------------------------------------------------------------------\n// Record message into the log, under the given scope\n//----------------------------------------------------------------------------\nvoid LogbookScope::insert(const std::string& msg)\n{\n  if (logbook) {\n    std::stringstream ss;\n\n    for (size_t i = 0; i < indentationLevel + 2; i++) {\n      ss << \" \";\n    }\n\n    ss << msg;\n    logbook->insert(ss.str());\n  }\n}\n\n//----------------------------------------------------------------------------\n// Check if activated\n//----------------------------------------------------------------------------\nbool LogbookScope::active() const\n{\n  if (!logbook) {\n    return false;\n  }\n\n  return logbook->active();\n}\n"
  },
  {
    "path": "fusex/auth/Logbook.hh",
    "content": "//------------------------------------------------------------------------------\n// File: Logbook.hh\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_FUSEX_LOGBOOK_HH\n#define EOS_FUSEX_LOGBOOK_HH\n\n#include <vector>\n#include <string>\n\nclass Logbook;\n\n//------------------------------------------------------------------------------\n// Helper class to record messages regarding a specific logbook scope. A scope\n// indents any messages that appear inside it and inserts a special header\n// message at the beginning of the scope.\n//------------------------------------------------------------------------------\nclass LogbookScope\n{\npublic:\n  //----------------------------------------------------------------------------\n  // Empty constructor\n  //----------------------------------------------------------------------------\n  LogbookScope();\n\n  //----------------------------------------------------------------------------\n  // Constructor\n  //----------------------------------------------------------------------------\n  LogbookScope(Logbook* logbook, const std::string& header, size_t indent);\n\n  //----------------------------------------------------------------------------\n  // Get a new sub-scope\n  //----------------------------------------------------------------------------\n  LogbookScope makeScope(const std::string& header);\n\n  //----------------------------------------------------------------------------\n  // Record message into the log, under the given scope\n  //----------------------------------------------------------------------------\n  void insert(const std::string& msg);\n\n  //----------------------------------------------------------------------------\n  // Check if activated\n  //----------------------------------------------------------------------------\n  bool active() const;\n\nprivate:\n  Logbook* logbook;\n  size_t indentationLevel = 0;\n};\n\n//------------------------------------------------------------------------------\n// Use this class to keep a log for a stream of events.\n//------------------------------------------------------------------------------\nclass Logbook\n{\npublic:\n  //----------------------------------------------------------------------------\n  // Constructor\n  //----------------------------------------------------------------------------\n  Logbook(bool active);\n\n  //----------------------------------------------------------------------------\n  // Record message into the log\n  //----------------------------------------------------------------------------\n  void insert(const std::string& msg);\n\n  //----------------------------------------------------------------------------\n  // Get a new scope\n  //----------------------------------------------------------------------------\n  LogbookScope makeScope(const std::string& header);\n\n  //----------------------------------------------------------------------------\n  // Check if activated\n  //----------------------------------------------------------------------------\n  bool active() const;\n\n  //----------------------------------------------------------------------------\n  // Build a string out of all messages\n  //----------------------------------------------------------------------------\n  std::string toString() const;\n\nprivate:\n  bool activated;\n  std::vector<std::string> messages;\n};\n\n//------------------------------------------------------------------------------\n// Macro to avoid building the string if logbook is inactive\n// Usage:\n//   Logbook logbook( .. );\n//   LOGBOOK_INSERT(logbook, \"my\" << \"string here\" << someVariable);\n//\n// The same macro works with LogbookScope as well.\n//------------------------------------------------------------------------------\n#define LOGBOOK_INSERT(logger, msg) if(logger.active()) { logger.insert(static_cast<std::ostringstream&>(std::ostringstream().flush() << msg).str()); }\n\n#endif\n"
  },
  {
    "path": "fusex/auth/LoginIdentifier.cc",
    "content": "//------------------------------------------------------------------------------\n// File: LoginIdentifier.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"LoginIdentifier.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/Macros.hh\"\n#include \"common/Logging.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucString.hh>\n/*----------------------------------------------------------------------------*/\n#include <arpa/inet.h>\n\n// Logic extracted from the old AuthIdManager::mapUser.\n\nLoginIdentifier::LoginIdentifier(uid_t uid, gid_t gid, pid_t pid,\n                                 uint64_t connId_)\n  : connId(connId_)\n{\n  if (uid == 0) {\n    uid = gid = 99;\n  }\n\n  bool map_only_user = false;\n\n  if (uid > 0x3ffff) {\n    eos_static_info(\"msg=\\\"unable to map uid+gid - out of range - mapping only user\");\n    map_only_user = true;\n  }\n\n  if (gid > 0xffff) {\n    eos_static_info(\"msg=\\\"unable to map uid+gid - out of range - mapping only user\");\n    map_only_user = true;\n  }\n\n  // this mechanism can only transport uid's over UNIX < 1024*1024 !\n  if (uid >= (1024*1024)) {\n    eos_static_info(\"msg=\\\"unable to map uid+gid - out of range - requesting 99/99\");\n    uid = 99;\n    gid = 99;\n  }\n\n  uint64_t bituser = 0;\n\n  if (map_only_user) {\n    bituser = (uid & 0xfffffffff);\n    bituser <<= 6;\n  } else {\n    bituser = (uid & 0xfffff);\n    bituser <<= 16;\n    bituser |= (gid & 0xffff);\n    bituser <<= 6;\n  }\n\n  // if using the gateway node, the purpose of the reamining 6 bits is just a connection counter to be able to reconnect\n  if (connId) {\n    bituser |= (connId & 0x3f);\n  }\n\n  if (map_only_user) {\n    stringId = encode('~', bituser);\n  } else {\n    stringId = encode('*', bituser);\n  }\n}\n\nLoginIdentifier::LoginIdentifier(uint64_t connId_) : connId(connId_)\n{\n  stringId = encode('A', connId);\n}\n\n//----------------------------------------------------------------------------\n// Describe object as string - different than getStringID, as we also print\n// the connectionID, if any\n//----------------------------------------------------------------------------\nstd::string LoginIdentifier::describe() const\n{\n  if (connId == 0) {\n    return stringId;\n  }\n\n  return SSTR(stringId << \" - \" << connId);\n}\n\n// Extracted from the old AuthIdManager::mapUser function\n// TODO(gbitzes): Truncating the output from base64 encode seems.. bad? review\n\nstd::string LoginIdentifier::encode(char prefix, uint64_t bituser)\n{\n  XrdOucString sb64;\n  bituser = h_tonll(bituser);\n  // WARNING: we support only one endianess flavour by doing this\n  eos::common::SymKey::Base64Encode((char*) &bituser, 8, sb64);\n  size_t len = sb64.length();\n\n  // Remove the non-informative '=' in the end\n  if (len > 2) {\n    sb64.erase(len - 1);\n    len--;\n  }\n\n  // Reduce to 7 b64 letters\n  if (len > 7) {\n    sb64.erase(0, len - 7);\n  }\n\n  XrdOucString sid = prefix + sb64;\n  // Encode '/' -> '_', '+' -> '-' to ensure the validity of the XRootD URL\n  // if necessary.\n  sid.replace('/', '_');\n  sid.replace('+', '-');\n  return sid.c_str();\n}\n"
  },
  {
    "path": "fusex/auth/LoginIdentifier.hh",
    "content": "//------------------------------------------------------------------------------\n// File: LoginIdentifier.hh\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __LOGIN_IDENTIFIER__HH__\n#define __LOGIN_IDENTIFIER__HH__\n\n#include <cstdint>\n#include <string>\n#include <sys/types.h>\n\n// We have to juggle many different xrootd logins.\n// This class identifies them with a unique ID, which is provided in\n// the user part of an xrootd URL: root://user@host/path\n// We're only limited to 8 chars..\n// Each object is immutable after construction, no need for locking.\n\nclass LoginIdentifier\n{\npublic:\n\n  LoginIdentifier()\n  {\n    connId = 0;\n    stringId = \"nobody\";\n  }\n\n  LoginIdentifier(uint64_t connId);\n  LoginIdentifier(uid_t uid, gid_t gid, pid_t pid, uint64_t connId);\n\n  std::string getStringID() const\n  {\n    return stringId;\n  }\n\n  uint64_t getConnectionID() const\n  {\n    return connId;\n  }\n\n  bool operator==(const LoginIdentifier& other) const\n  {\n    return stringId == other.stringId;\n  }\n\n  //----------------------------------------------------------------------------\n  // Describe object as string - different than getStringID, as we also print\n  // the connectionID, if any\n  //----------------------------------------------------------------------------\n  std::string describe() const;\n\nprivate:\n  uint64_t connId;\n  std::string stringId;\n\n  static std::string encode(char prefix, uint64_t bituser);\n};\n\n\n#endif\n"
  },
  {
    "path": "fusex/auth/ProcessCache.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ProcessCache.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"ProcessCache.hh\"\n#include \"Logbook.hh\"\n\nthread_local bool execveAlarm {\n  false\n};\n\nExecveAlert::ExecveAlert(bool val)\n{\n  execveAlarm = val;\n}\n\nExecveAlert::~ExecveAlert()\n{\n  execveAlarm = false;\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nProcessCache::ProcessCache(const CredentialConfig& conf,\n                           BoundIdentityProvider& bip, ProcessInfoProvider& pip, JailResolver& jr)\n  : credConfig(conf),\n    cache(16 /* 2^16 shards */, 1000 * 60 /* 1 minute inactivity TTL */),\n    boundIdentityProvider(bip),\n    processInfoProvider(pip),\n    jailResolver(jr)\n{\n  myJail = jailResolver.resolve(getpid());\n}\n\n//------------------------------------------------------------------------------\n// Discover some bound identity to use matching the given arguments.\n//------------------------------------------------------------------------------\nstd::shared_ptr<const BoundIdentity>\nProcessCache::discoverBoundIdentity(const JailInformation& jail,\n                                    const ProcessInfo& processInfo, uid_t uid, gid_t gid, bool reconnect,\n                                    Logbook& logbook)\n{\n  std::shared_ptr<const BoundIdentity> output;\n\n  //----------------------------------------------------------------------------\n  // Shortcut: If the situation implies that only unix is available\n  //----------------------------------------------------------------------------\n  if (onlyUnix(uid)) {\n    LogbookScope scope;\n\n    if (credConfig.use_user_unix && (uid || credConfig.use_root_unix)) {\n      scope = logbook.makeScope(\"unix enabled - \"\n                                \"using UNIX\");\n    } else {\n      scope = logbook.makeScope(\"krb5, x509, OAUTH2 and SSS disabled - \"\n                                \"falling back to UNIX\");\n    }\n\n    Environment env;\n    // in such a case encryptio does not work\n    return boundIdentityProvider.unixAuth(processInfo.getPid(), uid, gid,\n                                          reconnect, scope, env);\n  }\n\n  //----------------------------------------------------------------------------\n  // First thing to consider: Should we check the credentials of the process\n  // itself first, or that of the parent?\n  //----------------------------------------------------------------------------\n#define PF_FORKNOEXEC 0x00000040 /* Forked but didn't exec */\n  bool checkParentFirst = false;\n\n  if (execveAlarm) {\n    //--------------------------------------------------------------------------\n    // Nope, we're certainly in execve, don't check the process itself at all.\n    //--------------------------------------------------------------------------\n    checkParentFirst = true;\n  }\n\n  if (credConfig.forknoexec_heuristic &&\n      (processInfo.getFlags() & PF_FORKNOEXEC)) {\n    //--------------------------------------------------------------------------\n    // Process is in FORKNOEXEC.. suspicious. The vast majority of processes\n    // doing an execve are in PF_FORKNOEXEC state, such as processes spawned\n    // by shells.\n    //\n    // First check the parent - this radically decreases the number of times\n    // we have to pay the deadlock timeout penalty.\n    //--------------------------------------------------------------------------\n    checkParentFirst = true;\n  }\n\n  LOGBOOK_INSERT(logbook, \"execveAlarm = \" << execveAlarm <<\n                 \", PF_FORKNOEXEC = \" << (processInfo.getFlags() & PF_FORKNOEXEC) <<\n                 \", checkParentFirst = \" << checkParentFirst);\n  LogbookScope scope = logbook.makeScope(\"Attempting to discover bound identity \"\n                                         \"based on environment variables\");\n  //----------------------------------------------------------------------------\n  // Check parent?\n  //----------------------------------------------------------------------------\n  Environment pidEnv;\n\n  if (checkParentFirst && processInfo.getParentId() != 1) {\n    output = boundIdentityProvider.pidEnvironmentToBoundIdentity(jail,\n             processInfo.getParentId(), uid, gid, reconnect, scope, pidEnv);\n\n    if (output) {\n      return output;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  // Check process itself?\n  //\n  // Don't even attempt to read /proc/pid/environ if we _know_ we're doing an\n  // execve. If execveAlarm is off, there's still the possibility we're doing\n  // an execve due to uncached lookups sent by the kernel before the actual\n  // open! In that case, we'll simply have to pay the deadlock timeout penalty,\n  // but we'll still recover.\n  //----------------------------------------------------------------------------\n  if (!execveAlarm) {\n    output = boundIdentityProvider.pidEnvironmentToBoundIdentity(jail,\n             processInfo.getPid(), uid, gid, reconnect, scope, pidEnv);\n\n    if (output) {\n      return output;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  // Check parent, if we didn't already\n  //----------------------------------------------------------------------------\n  if (!checkParentFirst && processInfo.getParentId() != 1) {\n    output = boundIdentityProvider.pidEnvironmentToBoundIdentity(jail,\n             processInfo.getParentId(), uid, gid, reconnect, scope, pidEnv);\n\n    if (output) {\n      return output;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  // Nothing yet.. try global binding from eosfusebind...\n  //----------------------------------------------------------------------------\n  output = boundIdentityProvider.globalBindingToBoundIdentity(jail, uid, gid,\n           reconnect, scope, pidEnv);\n\n  if (output) {\n    return output;\n  }\n\n  //----------------------------------------------------------------------------\n  // What about default paths, ie /tmp/krb5cc_<uid>?\n  //----------------------------------------------------------------------------\n  output = boundIdentityProvider.defaultPathsToBoundIdentity(jail, uid, gid,\n           reconnect, scope, pidEnv);\n\n  if (output) {\n    return output;\n  }\n\n  //----------------------------------------------------------------------------\n  // No credentials found at all.. fallback to unix authentication.\n  //----------------------------------------------------------------------------\n  return boundIdentityProvider.unixAuth(processInfo.getPid(), uid, gid,\n                                        reconnect, scope, pidEnv);\n}\n\n//------------------------------------------------------------------------------\n// Major retrieve function, called by the rest of eosxd.\n//------------------------------------------------------------------------------\nProcessSnapshot ProcessCache::retrieve(pid_t pid, uid_t uid, gid_t gid,\n                                       bool reconnect)\n{\n  Logbook enabled(EOS_LOGS_DEBUG); // do logbook tracking only in debug level\n  return retrieve(pid, uid, gid, reconnect, enabled);\n}\n\n//----------------------------------------------------------------------------\n// Major retrieve function, called by the rest of eosxd - using\n// custom logbook.\n//----------------------------------------------------------------------------\nProcessSnapshot ProcessCache::retrieve(pid_t pid, uid_t uid, gid_t gid,\n                                       bool reconnect, Logbook& logbook)\n{\n  LOGBOOK_INSERT(logbook, \"===== Retrieve process snapshot for pid=\" << pid <<\n                 \", uid=\" << uid\n                 << \", gid=\" << gid << \", reconnect=\" << reconnect << \" =====\");\n  LogbookScope scope(logbook.makeScope(SSTR(\"/proc/\" << pid << \"/root lookup\")));\n\n  //----------------------------------------------------------------------------\n  // Warn if pid <= 0, something is wrong\n  //----------------------------------------------------------------------------\n  if (pid <= 0) {\n    std::ostringstream ss;\n    ss << \"Received invalid pid: \" << pid <<\n       \" - eosxd running in different pid namespace?\";\n    eos_static_notice(ss.str().c_str());\n    LOGBOOK_INSERT(scope, ss.str());\n  }\n\n  //----------------------------------------------------------------------------\n  // Possibly retrieve information about the jail in which this pid lives.\n  // The result is used for reading of credential files. However resolving the\n  // jail can cause deadlock, e.g. if the jail itself is the eos filesystem we\n  // are providing. If we are sure we'll only be needed unix auth skip resolve.\n  //----------------------------------------------------------------------------\n  JailInformation jailInfo = myJail;\n  if (onlyUnix(uid)) {\n    LOGBOOK_INSERT(scope, \"Not attempting to retrieve jail information for pid=\"\n                   << pid << \", as we will only need unix auth, subsituting \"\n                   \"with my jail\");\n  } else {\n    jailInfo = jailResolver.resolve(pid);\n  }\n\n  if (!jailInfo.id.ok()) {\n    //--------------------------------------------------------------------------\n    // Couldn't retrieve jail of this pid.. bad. Assume our jail.\n    //--------------------------------------------------------------------------\n    eos_static_notice(\"Could not retrieve jail information for pid=%d: %s\", pid,\n                      jailInfo.id.describe().c_str());\n    jailInfo = myJail;\n    LOGBOOK_INSERT(scope, \"WARNING: Could not retrieve jail information for pid=\" <<\n                   pid << \", subsituting with my jail\");\n  }\n\n  LOGBOOK_INSERT(scope, jailInfo.describe());\n  //----------------------------------------------------------------------------\n  // First, let's check the cache. Major retrieve function, called by the rest\n  // of eosxd.\n  //----------------------------------------------------------------------------\n  ProcessCacheKey cacheKey(pid, uid, gid);\n  ProcessSnapshot entry = cache.retrieve(cacheKey);\n\n  if (entry && reconnect) {\n    LOGBOOK_INSERT(logbook, \"Found cached entry in ProcessCache (\" <<\n                   entry->getBoundIdentity()->getLogin().describe() <<\n                   \"), but reconnecting as requested\");\n  }\n\n  if (entry && !reconnect) {\n    //--------------------------------------------------------------------------\n    // We have a cache hit, but it could refer to different processes, even if\n    // PID is the same. The kernel could have re-used the same PID, verify.\n    //--------------------------------------------------------------------------\n    ProcessInfo processInfo;\n\n    if (!processInfoProvider.retrieveBasic(pid, processInfo)) {\n      //--------------------------------------------------------------------------\n      // Dead PIDs issue no syscalls... or do they?!\n      //\n      // Release fuse request can be issued even after a process has died - in\n      // this strange case, let's just return the cache info.\n      //--------------------------------------------------------------------------\n      return entry;\n    }\n\n    if (processInfo.isSameProcess(entry->getProcessInfo())) {\n      //------------------------------------------------------------------------\n      // Yep, that's a cache hit.. but credentials could have been invalidated\n      // in the meantime, check.\n      //------------------------------------------------------------------------\n      if (boundIdentityProvider.checkValidity(jailInfo,\n                                              *entry->getBoundIdentity())) {\n        return entry;\n      }\n    }\n\n    //--------------------------------------------------------------------------\n    // Process has changed, or credentials invalidated - cache miss.\n    //--------------------------------------------------------------------------\n  }\n\n  //----------------------------------------------------------------------------\n  // Retrieve full information about this process, including its jail\n  //----------------------------------------------------------------------------\n  ProcessInfo processInfo;\n\n  if (!processInfoProvider.retrieveFull(pid, processInfo)) {\n    return {};\n  }\n\n  //----------------------------------------------------------------------------\n  // Discover which bound identity to attach to this process, and store into\n  // the cache for future requests.\n  //----------------------------------------------------------------------------\n  std::shared_ptr<const BoundIdentity> bdi = discoverBoundIdentity(jailInfo,\n      processInfo, uid, gid, reconnect, logbook);\n  LOGBOOK_INSERT(logbook, \"\");\n  LOGBOOK_INSERT(logbook, \"===== BOUND IDENTITY: =====\");\n  LOGBOOK_INSERT(logbook, bdi->describe());\n  ProcessSnapshot result;\n  cache.store(cacheKey,\n              std::unique_ptr<ProcessCacheEntry>(new ProcessCacheEntry(processInfo,\n                  jailInfo, bdi)),\n              result);\n\n  if (EOS_LOGS_NOTICE && logbook.toString().size()) {\n    eos_static_notice(\"Auth:{\\n%s\\n}\", logbook.toString().c_str());\n  }\n  //----------------------------------------------------------------------------\n  // All done\n  //----------------------------------------------------------------------------\n  return result;\n}\n\n//------------------------------------------------------------------------------\n// Remove identified user credential from cache. Also removes from unerlying\n// BoundIdentity provider cache if necessary.\n//------------------------------------------------------------------------------\nbool ProcessCache::remove(uid_t uid, gid_t gid, const UserCredentials &uc, uint64_t connId)\n{\n  bool found = false;\n\n  // unless it's a unix style usercred, there will also be an entry in\n  // the BoundIdeneity provider so remove from there\n  if (uc.type != CredentialType::INVALID) {\n    found = boundIdentityProvider.remove(uc, connId);\n  }\n\n  // remove entry from our cache if it matches\n  const size_t ns = cache.num_content_shards();\n  for(size_t i=0;i<ns;i++) {\n    auto &&m = cache.get_shard(i);\n    for(auto it=m.begin(); it!= m.end(); ++it) {\n      const auto &val = it->second;\n      const auto &key = it->first;\n      if (key.uid == uid && key.gid == gid &&\n          val.getBoundIdentity()->getCreds()->getUC() == uc &&\n          val.getBoundIdentity()->getLogin().getConnectionID() == connId) {\n        if (cache.invalidate(key)) {\n          found = true;\n        }\n      }\n    }\n  }\n  return found;\n}\n"
  },
  {
    "path": "fusex/auth/ProcessCache.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ProcessCache.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_FUSEX_PROCESS_CACHE_HH\n#define EOS_FUSEX_PROCESS_CACHE_HH\n\n#include \"JailIdentifier.hh\"\n#include \"CredentialFinder.hh\"\n#include \"ProcessInfo.hh\"\n#include \"BoundIdentityProvider.hh\"\n#include \"common/ShardedCache.hh\"\n\nclass Logbook;\n\nclass ProcessCacheEntry\n{\npublic:\n\n  ProcessCacheEntry(const ProcessInfo& pinfo, const JailInformation& jinfo,\n                    std::shared_ptr<const BoundIdentity> boundid)\n    : processInfo(pinfo), jailInfo(jinfo), boundIdentity(boundid) { }\n\n  const ProcessInfo& getProcessInfo() const\n  {\n    return processInfo;\n  }\n\n  const BoundIdentity* getBoundIdentity() const\n  {\n    return boundIdentity.get();\n  }\n\n  std::string getXrdLogin() const\n  {\n    return boundIdentity->getLogin().getStringID();\n  }\n\n  std::string getXrdCreds() const\n  {\n    return boundIdentity->getCreds()->toXrdParams();\n  }\n\n  std::string getUserName() const\n  {\n    return boundIdentity->getCreds()->toUserName();\n  }\n\n  Jiffies getStartTime() const\n  {\n    return processInfo.getStartTime();\n  }\n\n  std::string getCmdStr() const\n  {\n    return processInfo.getCmdStr();\n  }\n\n  const std::vector<std::string>& getCmdVec() const\n  {\n    return processInfo.getCmd();\n  }\n\n  bool filledCredentials() const\n  {\n    return boundIdentity->getCreds() && (!boundIdentity->getCreds()->empty());\n  }\n\n  std::string getExe() const\n  {\n    return processInfo.getExe();\n  }\n\nprivate:\n  ProcessInfo processInfo;\n  JailInformation jailInfo;\n  std::shared_ptr<const BoundIdentity> boundIdentity;\n};\n\nusing ProcessSnapshot = std::shared_ptr<const ProcessCacheEntry>;\n\nclass ExecveAlert\n{\npublic:\n  ExecveAlert(bool value);\n  ~ExecveAlert();\n};\n\nclass ProcessCache\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  // Constructor\n  //----------------------------------------------------------------------------\n  ProcessCache(const CredentialConfig& conf, BoundIdentityProvider& bip,\n               ProcessInfoProvider& pip, JailResolver& jr);\n\n  //----------------------------------------------------------------------------\n  // Major retrieve function, called by the rest of eosxd - using\n  // custom logbook.\n  //----------------------------------------------------------------------------\n  ProcessSnapshot retrieve(pid_t pid, uid_t uid, gid_t gid, bool reconnect,\n                           Logbook& logbook);\n\n  //----------------------------------------------------------------------------\n  // Major retrieve function, called by the rest of eosxd.\n  //----------------------------------------------------------------------------\n  ProcessSnapshot retrieve(pid_t pid, uid_t uid, gid_t gid, bool reconnect);\n\n  //----------------------------------------------------------------------------\n  // Remove identified user credential from cache.\n  //----------------------------------------------------------------------------\n  bool remove(uid_t uid, gid_t gid, const UserCredentials &uc, uint64_t connId);\n\nprivate:\n  //----------------------------------------------------------------------------\n  // Discover some bound identity to use matching the given arguments.\n  //----------------------------------------------------------------------------\n  std::shared_ptr<const BoundIdentity>\n  discoverBoundIdentity(const JailInformation& jail, const ProcessInfo&\n                        processInfo, uid_t uid, gid_t gid, bool reconnect, Logbook& logbook);\n\n  //----------------------------------------------------------------------------\n  // Return true if we will only use Unix:\n  // If all authentication methods are disabled, or unix is enabled and uid!=0\n  // we can just use Unix and this may enable certain shortcuts.\n  //----------------------------------------------------------------------------\n  bool onlyUnix(uid_t uid) const {\n    if ((!credConfig.use_user_krb5cc && !credConfig.use_user_gsiproxy &&\n         !credConfig.use_user_sss && !credConfig.use_user_oauth2 &&\n         !credConfig.use_user_ztn) ||\n        (credConfig.use_user_unix && (uid || credConfig.use_root_unix))) {\n      return true;\n    }\n    return false;\n  }\n\n  CredentialConfig credConfig;\n\n  struct ProcessCacheKey {\n    pid_t pid;\n    uid_t uid;\n    gid_t gid;\n\n    ProcessCacheKey(pid_t p, uid_t u, gid_t g) : pid(p), uid(u), gid(g) { }\n\n    bool operator<(const ProcessCacheKey& other) const\n    {\n      if (pid != other.pid) {\n        return pid < other.pid;\n      }\n\n      if (uid != other.uid) {\n        return uid < other.uid;\n      }\n\n      return gid < other.gid;\n    }\n  };\n\n  struct KeyHasher {\n\n    static uint64_t hash(const ProcessCacheKey& key)\n    {\n      return key.pid;\n    }\n  };\n\n  ShardedCache<ProcessCacheKey, ProcessCacheEntry, KeyHasher, false> cache;\n  BoundIdentityProvider& boundIdentityProvider;\n  ProcessInfoProvider& processInfoProvider;\n  JailResolver& jailResolver;\n\n  JailInformation myJail;\n};\n\n#endif\n"
  },
  {
    "path": "fusex/auth/ProcessInfo.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ProcessInfo.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#define __STDC_FORMAT_MACROS\n#include <inttypes.h>\n#include <unistd.h>\n#include \"ProcessInfo.hh\"\n#include \"common/Logging.hh\"\n\nbool ProcessInfoProvider::fromString(const std::string& procstat,\n                                     const std::string& cmdline, ProcessInfo& ret)\n{\n  if (!ret.isEmpty()) {\n    THROW(\"The ProcessInfo object must be empty for this function to fill!\");\n  }\n\n  if (!parseStat(procstat, ret)) {\n    return false;\n  }\n\n  parseCmdline(cmdline, ret);\n  return true;\n}\n\n// Reference:\n// Table 1-4: Contents of the stat files (as of 2.6.30-rc7)\n// ..............................................................................\n//  Field          Content\n//   pid           process id\n//   tcomm         filename of the executable\n//   state         state (R is running, S is sleeping, D is sleeping in an\n//                 uninterruptible wait, Z is zombie, T is traced or stopped)\n//   ppid          process id of the parent process\n//   pgrp          pgrp of the process\n//   sid           session id\n//   tty_nr        tty the process uses\n//   tty_pgrp      pgrp of the tty\n//   flags         task flags\n//   min_flt       number of minor faults\n//   cmin_flt      number of minor faults with child's\n//   maj_flt       number of major faults\n//   cmaj_flt      number of major faults with child's\n//   utime         user mode jiffies\n//   stime         kernel mode jiffies\n//   cutime        user mode jiffies with child's\n//   cstime        kernel mode jiffies with child's\n//   priority      priority level\n//   nice          nice level\n//   num_threads   number of threads\n//   it_real_value  (obsolete, always 0)\n//   start_time    time the process started after system boot\n//   vsize         virtual memory size\n//   rss           resident set memory size\n//   rsslim        current limit in bytes on the rss\n//   start_code    address above which program text can run\n//   end_code      address below which program text can run\n//   start_stack   address of the start of the main process stack\n//   esp           current value of ESP\n//   eip           current value of EIP\n//   pending       bitmap of pending signals\n//   blocked       bitmap of blocked signals\n//   sigign        bitmap of ignored signals\n//   sigcatch      bitmap of caught signals\n//   0             (place holder, used to be the wchan address, use /proc/PID/wchan instead)\n//   0             (place holder)\n//   0             (place holder)\n//   exit_signal   signal to send to parent thread on exit\n//   task_cpu      which CPU the task is scheduled on\n//   rt_priority   realtime priority\n//   policy        scheduling policy (man sched_setscheduler)\n//   blkio_ticks   time spent waiting for block IO\n//   gtime         guest time of the task in jiffies\n//   cgtime        guest time of the task children in jiffies\n//   start_data    address above which program data+bss is placed\n//   end_data      address below which program data+bss is placed\n//   start_brk     address above which program heap can be expanded with brk()\n//   arg_start     address above which program command line is placed\n//   arg_end       address below which program command line is placed\n//   env_start     address above which program environment is placed\n//   env_end       address below which program environment is placed\n//   exit_code     the thread's exit_code in the form reported by the waitpid system call\n// ..............................................................................\n\nbool ProcessInfoProvider::parseStat(const std::string& procstat,\n                                    ProcessInfo& ret)\n{\n  if (!ret.isEmpty()) {\n    THROW(\"The ProcessInfo object must be empty for this function to fill!\");\n  }\n\n  // variables to assist with parsing\n  bool inParenth = false;\n  size_t tokenCount = 0;\n  bool success = false;\n  // variables in which to store results\n  pid_t pid;\n  pid_t ppid;\n  pid_t pgrp;\n  pid_t sid;\n  Jiffies startTime;\n  unsigned flags;\n\n  // let's parse\n  for (size_t i = 0; i < procstat.size(); i++) {\n    // be careful, process names can have all kinds of combinations of () in it !\n    // we will fail parsing if a process ends with a name like ') <X> ' where <X> = R|S|Z|D|T and there is a space of <X>\n    if (procstat[i] == '(') {\n      inParenth = true;\n      continue;\n    }\n\n    if (procstat[i] == ')' &&\n        procstat[i + 1] == ' ' &&\n        procstat[i + 3] == ' ' &&\n        ((procstat[i + 2] == 'R') ||\n         (procstat[i + 2] == 'S') ||\n         (procstat[i + 2] == 'Z') ||\n         (procstat[i + 2] == 'D') ||\n         (procstat[i + 2] == 'T'))) {\n      if (!inParenth) {\n        return false; // parse error\n      }\n\n      inParenth = false;\n    }\n\n    // start of a token, use scanf if we're interested in it\n    if (!inParenth && (procstat[i] == ' ' || i == 0)) {\n      switch (tokenCount) {\n      case 0: {\n        if (!sscanf(procstat.c_str() + i, \"%u\", &pid)) {\n          return false;\n        }\n\n        break;\n      }\n\n      case 3: {\n        if (!sscanf(procstat.c_str() + i, \"%u\", &ppid)) {\n          return false;\n        }\n\n        break;\n      }\n\n      case 4: {\n        if (!sscanf(procstat.c_str() + i, \"%u\", &pgrp)) {\n          return false;\n        }\n\n        break;\n      }\n\n      case 5: {\n        if (!sscanf(procstat.c_str() + i, \"%u\", &sid)) {\n          return false;\n        }\n\n        break;\n      }\n\n      case 8: {\n        if (!sscanf(procstat.c_str() + i, \"%u\", &flags)) {\n          return false;\n        }\n\n        break;\n      }\n\n      case 21: {\n        if (!sscanf(procstat.c_str() + i, \"%\" PRId64, &startTime)) {\n          return false;\n        }\n\n        success = true;\n        break;\n      }\n      }\n\n      tokenCount++;\n    }\n  }\n\n  if (!success) {\n    return false;\n  }\n\n  ret.fillStat(pid, ppid, pgrp, sid, startTime, flags);\n  return true;\n}\n\nvoid ProcessInfoProvider::parseCmdline(const std::string& cmdline,\n                                       ProcessInfo& ret)\n{\n  if (cmdline.empty()) {\n    return;\n  }\n\n  ret.fillCmdline(split_on_nullbyte(cmdline));\n}\n\nvoid ProcessInfoProvider::inject(pid_t pid, const ProcessInfo& info)\n{\n  std::lock_guard<std::mutex> lock(mtx);\n  useInjectedData = true;\n  injections[pid] = info;\n}\n\nbool ProcessInfoProvider::retrieveBasic(pid_t pid, ProcessInfo& ret)\n{\n  if (useInjectedData) {\n    std::lock_guard<std::mutex> lock(mtx);\n    auto it = injections.find(pid);\n\n    if (it == injections.end()) {\n      return false;\n    }\n\n    ret = it->second;\n    // Keep the same behavior as when reading from /proc, don't give out the\n    // cmdline even if the injection contains it\n    ret.fillCmdline({});\n    return true;\n  }\n\n  std::string procstat;\n\n  if (!readFile(SSTR(\"/proc/\" << pid << \"/stat\"), procstat)) {\n    return false;\n  }\n\n  if (!parseStat(procstat, ret)) {\n    return false;\n  }\n\n  if (pid != ret.getPid()) {\n    eos_static_crit(\"Hell has frozen over, /proc/%d/stat contained information for a different pid: %d\",\n                    pid, ret.getPid());\n    return false;\n  }\n\n  return true;\n}\n\nbool ProcessInfoProvider::retrieveFull(pid_t pid, ProcessInfo& ret)\n{\n  if (useInjectedData) {\n    std::lock_guard<std::mutex> lock(mtx);\n    auto it = injections.find(pid);\n\n    if (it == injections.end()) {\n      return false;\n    }\n\n    ret = it->second;\n    ret.fillRmInfo();\n    return true;\n  }\n\n  if (!retrieveBasic(pid, ret)) {\n    return false;\n  }\n\n  std::string cmdline;\n\n  if (!readFile(SSTR(\"/proc/\" << pid << \"/cmdline\"), cmdline)) {\n    // This is a valid case, if for example, the calling PID is actually\n    // a kernel thread.\n    return true;\n  }\n\n  parseCmdline(cmdline, ret);\n  parseExec(pid, ret);\n  ret.fillRmInfo();\n  // Read path of /proc/<pid>/exe\n  std::string exePath = SSTR(\"/proc/\" << pid << \"/exe\");\n  char buffer[1024];\n  ssize_t outcome = readlink(exePath.c_str(), buffer, 1024);\n\n  if (outcome > 0) {\n    ret.exe = std::string(buffer, outcome);\n  }\n\n  return true;\n}\n\nbool ProcessInfoProvider::parseExec(pid_t pid, ProcessInfo& ret)\n{\n  const size_t BUFF_SIZE = 8096;\n  char buffer[BUFF_SIZE];\n  ssize_t len = readlink(SSTR(\"/proc/\" << pid << \"/exe\").c_str(), buffer,\n                         BUFF_SIZE - 2);\n\n  if (len == -1) {\n    return false;\n  }\n\n  ret.fillExecutablePath(std::string(buffer, len));\n  return true;\n}\n"
  },
  {
    "path": "fusex/auth/ProcessInfo.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ProcessInfo.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __PROCESS_INFO_HH__\n#define __PROCESS_INFO_HH__\n\n#include <mutex>\n#include <map>\n#include <atomic>\n#include \"Utils.hh\"\n#include \"auth/RmInfo.hh\"\n\ntypedef int64_t Jiffies;\n\n// Holds information about a specific process.\n// Stat information (pid, ppid, sid, starttime) must be present for such an\n// object to be considered non-empty.\n\nclass ProcessInfo\n{\npublic:\n\n  ProcessInfo() : empty(true), pid(0), ppid(0), pgrp(0), sid(0), startTime(-1),\n    flags(0) { }\n\n  // Fill stat information as obtained from /proc/<pid>/stat\n\n  void fillStat(pid_t pid, pid_t ppid, pid_t pgrp, pid_t sid, Jiffies startTime,\n                unsigned int flags)\n  {\n    if (!empty) {\n      THROW(\"ProcessInfo stat information can only be filled once\");\n    }\n\n    empty = false;\n    this->pid = pid;\n    this->ppid = ppid;\n    this->pgrp = pgrp;\n    this->sid = sid;\n    this->startTime = startTime;\n    this->flags = flags;\n  }\n\n  bool isSameProcess(const ProcessInfo& other)\n  {\n    if (pid != other.pid || startTime != other.startTime) {\n      return false;\n    }\n\n    return true;\n  }\n\n  // Fill cmdline information as obtained from /proc/<pid>/cmdline\n  void fillCmdline(const std::vector<std::string>& contents)\n  {\n    cmd = contents;\n    cmdStr = join(cmd, \" \");\n  }\n\n  void fillExecutablePath(const std::string& path)\n  {\n    executablePath = path;\n  }\n\n  void fillRmInfo()\n  {\n    rmInfo = RmInfo(executablePath, cmd);\n  }\n\n  bool isEmpty() const\n  {\n    return empty;\n  }\n\n  pid_t getPid() const\n  {\n    return pid;\n  }\n\n  pid_t getParentId() const\n  {\n    return ppid;\n  }\n\n  pid_t getGroupLeader() const\n  {\n    return pgrp;\n  }\n\n  pid_t getSid() const\n  {\n    return sid;\n  }\n\n  Jiffies getStartTime() const\n  {\n    return startTime;\n  }\n\n  const std::vector<std::string>& getCmd() const\n  {\n    return cmd;\n  }\n\n  std::string getCmdStr() const\n  {\n    return cmdStr;\n  }\n\n  unsigned int getFlags() const\n  {\n    return flags;\n  }\n\n  std::string getExecPath() const\n  {\n    return executablePath;\n  }\n\n  const RmInfo& getRmInfo() const\n  {\n    return rmInfo;\n  }\n\n  std::string getExe() const\n  {\n    return exe;\n  }\n\nprivate:\n  bool empty;\n  RmInfo rmInfo;\n\n  // TODO(gbitzes): Make these private once ProcessInfoProvider is implemented\npublic:\n  // from /proc/<pid>/stat\n  pid_t pid;\n  pid_t ppid;\n  pid_t pgrp;\n  pid_t sid;\n  Jiffies startTime;\n  unsigned int flags;\n\n  // from /proc/<pid>/cmdline\n  std::vector<std::string> cmd;\n  std::string cmdStr; // TODO(gbitzes): remove this eventually?\n  std::string executablePath;\n  std::string exe; // derived from /proc/<pid>/exe\n};\n\n// Parses the contents of /proc/<pid>/stat, converting it to a ProcessInfo\n\nclass ProcessInfoProvider\n{\npublic:\n  void inject(pid_t pid, const ProcessInfo& info);\n\n  // retrieves information about a process from the kernel.\n  // Does not fill cmdline, thus only reading a single file.\n  bool retrieveBasic(pid_t pid, ProcessInfo& ret);\n\n  // retrieves information about a process from the kernel, including cmdline.\n  bool retrieveFull(pid_t pid, ProcessInfo& ret);\n  static bool fromString(const std::string& stat, const std::string& cmd,\n                         ProcessInfo& ret);\nprivate:\n  std::mutex mtx;\n  std::map<pid_t, ProcessInfo> injections;\n  std::atomic<bool> useInjectedData{false};\n  static bool parseStat(const std::string& stat, ProcessInfo& ret);\n  static void parseCmdline(const std::string& cmdline, ProcessInfo& ret);\n  static bool parseExec(pid_t pid, ProcessInfo& ret);\n};\n\n#endif\n"
  },
  {
    "path": "fusex/auth/README.md",
    "content": ""
  },
  {
    "path": "fusex/auth/RmInfo.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file RmInfo.hh\n//! @author Georgios Bitzes CERN\n//! @brief Utility class to prevent \"rm -rf\" or equivalent to top-level\n//!        directories of the FUSE mount. A bit hacky, we do string\n//!        comparisons with cmdline.\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"auth/ProcessInfo.hh\"\n#include \"RmInfo.hh\"\n#include \"common/Logging.hh\"\n\nRmInfo::RmInfo(const std::string& executablePath,\n               const std::vector<std::string>& cmdline)\n{\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"path: %s\", executablePath.c_str());\n  }\n\n  if (executablePath != \"/bin/rm\" &&\n      executablePath != \"/usr/bin/rm\" &&\n      executablePath != \"/usr/local/bin/rm\") {\n    return;\n  }\n\n  rm = true;\n\n  for (auto it = cmdline.begin(); it != cmdline.end(); it++) {\n    const std::string& arg = *it;\n\n    if (arg == \"--recursive\") {\n      recursive = true;\n    } else if (arg == \"--force\") {\n      force = true;\n    } else if (arg == \"--verbose\") {\n      verbose = true;\n    }\n\n    if (arg.size() >= 2 && arg[0] == '-' && arg[1] != '-') {\n      for (size_t i = 1; i < arg.size(); i++) {\n        if (arg[i] == 'r' || arg[i] == 'R') {\n          recursive = true;\n        } else if (arg[i] == 'f') {\n          force = true;\n        } else if (arg[i] == 'v') {\n          verbose = true;\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "fusex/auth/RmInfo.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file RmInfo.hh\n//! @author Georgios Bitzes CERN\n//! @brief Utility class to prevent \"rm -rf\" or equivalent to top-level\n//!        directories of the FUSE mount. A bit hacky, we do string\n//!        comparisons with cmdline.\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_RM_INFO_HH_\n#define FUSE_RM_INFO_HH_\n\n#include <vector>\n#include <string>\n\n//------------------------------------------------------------------------------\n// This thing is really hacky.. Tries to determine if the process contacting\n// us is an rm, and extract a few details about what it's trying to do, based\n// on its command line arguments.\n//\n// We should try not to have false positives! A process which is not rm should\n// never be misidentified as rm..\n//------------------------------------------------------------------------------\n\nclass RmInfo\n{\npublic:\n\n  RmInfo() { }\n  RmInfo(const std::string& executablePath,\n         const std::vector<std::string>& cmdline);\n\n  bool isRm() const\n  {\n    return rm;\n  }\n\n  bool isRecursive() const\n  {\n    return recursive;\n  }\n\n  bool isForce() const\n  {\n    return force;\n  }\n\n  bool isVerbose() const\n  {\n    return verbose;\n  }\n\nprivate:\n  bool rm = false;\n  bool recursive = false;\n  bool force = false;\n  bool verbose = false;\n};\n\n#endif\n"
  },
  {
    "path": "fusex/auth/ScopedEUidSetter.hh",
    "content": "//----------------------------------------------------------------------\n// File: ScopedEUidSetter.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_FUSEX_SCOPED_EUID_SETTER_HH\n#define EOS_FUSEX_SCOPED_EUID_SETTER_HH\n\n#ifdef __linux__\n\n#include \"common/Logging.hh\"\n#include <sys/syscall.h>\n\n//------------------------------------------------------------------------------\n//! Scoped euid and egid setter, restoring original values on destruction\n//------------------------------------------------------------------------------\nclass ScopedEUidSetter\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ScopedEUidSetter(uid_t euid_, gid_t egid_)\n    : euid(euid_), egid(egid_)\n  {\n    ok = true;\n    prevEuid = -1;\n    prevEgid = -1;\n\n    //--------------------------------------------------------------------------\n    //! Set euid\n    //--------------------------------------------------------------------------\n    if (euid >= 0) {\n      prevEuid = getuid();\n      if (syscall(SYS_setresuid, euid, -1, -1) != 0) {\n        eos_static_crit(\"Unable to set euid to %d!\", euid);\n        ok = false;\n        return;\n      }\n    }\n\n    //--------------------------------------------------------------------------\n    //! Set egid\n    //--------------------------------------------------------------------------\n    if (egid >= 0) {\n      prevEgid = getgid();\n\n      if (syscall(SYS_setresgid, egid, -1, -1) != 0) {\n        eos_static_crit(\"Unable to set euid to %d!\", egid);\n        ok = false;\n        return;\n      }\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~ScopedEUidSetter()\n  {\n    if (prevEuid >= 0) {\n      int retcode = syscall(SYS_setresuid, prevEuid, -1, -1);\n      eos_static_debug(\"Restored euid from %d to %d [%d/%d]\", euid, prevEuid, retcode, getuid());\n    }\n\n    if (prevEgid >= 0) {\n      int retcode = syscall(SYS_setresgid, prevEgid, -1, -1);\n      eos_static_debug(\"Restored fsgid from %d to %d [%d/%d]\", egid, prevEgid, retcode, getgid());\n    }\n  }\n\n  bool IsOk() const\n  {\n    return ok;\n  }\n\nprivate:\n  int euid;\n  int egid;\n\n  int prevEuid;\n  int prevEgid;\n\n  bool ok;\n};\n\n#endif\n\n#endif\n"
  },
  {
    "path": "fusex/auth/ScopedFsUidSetter.hh",
    "content": "//----------------------------------------------------------------------\n// File: ScopedfsUidSetter.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_FUSEX_SCOPED_FS_UID_SETTER_HH\n#define EOS_FUSEX_SCOPED_FS_UID_SETTER_HH\n\n#ifdef __linux__\n\n#include \"common/Logging.hh\"\n#include <sys/fsuid.h>\n\n//------------------------------------------------------------------------------\n//! Scoped fsuid and fsgid setter, restoring original values on destruction\n//------------------------------------------------------------------------------\nclass ScopedFsUidSetter\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ScopedFsUidSetter(uid_t fsuid_, gid_t fsgid_)\n    : fsuid(fsuid_), fsgid(fsgid_)\n  {\n    ok = true;\n    prevFsuid = -1;\n    prevFsgid = -1;\n\n    //--------------------------------------------------------------------------\n    //! Set fsuid\n    //--------------------------------------------------------------------------\n    if (fsuid >= 0) {\n      prevFsuid = setfsuid(fsuid);\n\n      if (setfsuid(fsuid) != fsuid) {\n        eos_static_crit(\"Unable to set fsuid to %d!\", fsuid);\n        ok = false;\n        return;\n      }\n    }\n\n    //--------------------------------------------------------------------------\n    //! Set fsgid\n    //--------------------------------------------------------------------------\n    if (fsgid >= 0) {\n      prevFsgid = setfsgid(fsgid);\n\n      if (setfsgid(fsgid) != fsgid) {\n        eos_static_crit(\"Unable to set fsuid to %d!\", fsgid);\n        ok = false;\n        return;\n      }\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~ScopedFsUidSetter()\n  {\n    if (prevFsuid >= 0) {\n      int retcode = setfsuid(prevFsuid);\n      eos_static_debug(\"Restored fsuid from %d to %d\", retcode, prevFsuid);\n    }\n\n    if (prevFsgid >= 0) {\n      int retcode = setfsgid(prevFsgid);\n      eos_static_debug(\"Restored fsgid from %d to %d\", retcode, prevFsgid);\n    }\n  }\n\n  bool IsOk() const\n  {\n    return ok;\n  }\n\nprivate:\n  int fsuid;\n  int fsgid;\n\n  int prevFsuid;\n  int prevFsgid;\n\n  bool ok;\n};\n\n#endif\n\n#endif"
  },
  {
    "path": "fusex/auth/SecurityChecker.cc",
    "content": "// ----------------------------------------------------------------------\n// File: SecurityChecker.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"SecurityChecker.hh\"\n#include \"ScopedFsUidSetter.hh\"\n#include \"FileDescriptor.hh\"\n#include \"Utils.hh\"\n#include \"common/Logging.hh\"\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <iostream>\n\n//------------------------------------------------------------------------------\n// Portability helper: Extract timespec from stat struct\n//------------------------------------------------------------------------------\nstruct timespec extractTimespec(const struct stat& st)\n{\n#ifdef __APPLE__\n  return st.st_mtimespec;\n#else\n  return st.st_mtim;\n#endif\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nSecurityChecker::SecurityChecker(bool ij) : ignoreJails(ij) {}\n\n//------------------------------------------------------------------------------\n// Inject the given fake data. Once an injection is active, _all_ returned\n// data is faked.\n//------------------------------------------------------------------------------\nvoid SecurityChecker::inject(const JailIdentifier& jail,\n                             const std::string& path, uid_t uid, mode_t mode, struct timespec mtime)\n{\n  std::lock_guard<std::mutex> lock(mtx);\n  useInjectedData = true;\n  injections[path] = InjectedData(uid, mode, mtime);\n}\n\n//------------------------------------------------------------------------------\n// Same as lookup, but only serve simulated data.\n//------------------------------------------------------------------------------\nSecurityChecker::Info SecurityChecker::lookupInjected(\n  const JailIdentifier& jail, const std::string& path, uid_t uid)\n{\n  std::lock_guard<std::mutex> lock(mtx);\n  auto it = injections.find(path);\n\n  if (it == injections.end()) return {};\n\n  if (!checkPermissions(it->second.uid, it->second.mode, uid)) {\n    return Info(CredentialState::kBadPermissions, {0, 0});\n  }\n\n  return Info(CredentialState::kOk, it->second.mtime);\n}\n\n//------------------------------------------------------------------------------\n// We have a file with the given uid and mode, and we're \"expectedUid\".\n// Should we be able to read it? Enforce strict permissions on mode, as it's\n// a credential file - only _we_ should be able to read it and no-one else.\n//------------------------------------------------------------------------------\nbool SecurityChecker::checkPermissions(uid_t uid, mode_t mode,\n                                       uid_t expectedUid)\n{\n  if (uid != expectedUid) {\n    return false;\n  }\n\n  if ((mode & 0077) != 0) {\n    // No access to other users/groups\n    return false;\n  }\n\n  if ((mode & 0400) == 0) {\n    // Read should be allowed for the user\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Lookup given path in the context of our local jail.\n//------------------------------------------------------------------------------\nSecurityChecker::Info SecurityChecker::lookupLocalJail(const std::string& path,\n    uid_t uid)\n{\n  std::string resolvedPath;\n  // is \"path\" a symlink?\n  char buffer[1024];\n  const ssize_t retsize = readlink(path.c_str(), buffer, 1023);\n\n  if (retsize != -1) {\n    resolvedPath = std::string(buffer, retsize);\n  } else {\n    resolvedPath = path;\n  }\n\n  struct stat filestat;\n\n  if (::stat(resolvedPath.c_str(), &filestat) != 0) {\n    // cannot stat\n    return {};\n  }\n\n  if (!checkPermissions(filestat.st_uid, filestat.st_mode, uid)) {\n    eos_static_alert(\"Uid %d is asking to use credentials '%s', but file \"\n                     \"belongs to uid %d! Refusing.\", uid, path.c_str(), filestat.st_uid);\n    return Info(CredentialState::kBadPermissions, {0, 0});\n  }\n\n  return Info(CredentialState::kOk, extractTimespec(filestat));\n}\n\n//------------------------------------------------------------------------------\n// Things have gotten serious - interpret given path in the context of a\n// different jail, and return entire contents.\n//------------------------------------------------------------------------------\nSecurityChecker::Info SecurityChecker::lookupNonLocalJail(\n  const JailInformation& jail, const std::string& path, uid_t uid, gid_t gid)\n{\n  //----------------------------------------------------------------------------\n  // First, let's open the jail as root.\n  //----------------------------------------------------------------------------\n  std::string jailPath = SSTR(\"/proc/\" << jail.pid << \"/root\");\n  FileDescriptor jailfd(open(jailPath.c_str(), O_DIRECTORY | O_RDONLY));\n\n  if (!jailfd.ok()) {\n    eos_static_alert(\"Opening jail '%s' failed\", jailPath.c_str());\n    return Info(CredentialState::kCannotStat, {0, 0});\n  }\n\n  //----------------------------------------------------------------------------\n  // Reset my fsuid, fsgid to user-provided ones.\n  //----------------------------------------------------------------------------\n#ifdef __linux__\n  ScopedFsUidSetter uidSetter(uid, gid);\n\n  if (!uidSetter.IsOk()) {\n    eos_static_alert(\"Setting uid,gid to %d,%d failed\", uid, gid);\n    return Info(CredentialState::kCannotStat, {0, 0});\n  }\n\n#endif\n\n  //----------------------------------------------------------------------------\n  // User-space lookup of path - this could be avoided if the linux kernel\n  // supported openat with AT_THIS_ROOT ...\n  //----------------------------------------------------------------------------\n\n  if (!eos::common::startsWith(path, \"/\")) {\n    //--------------------------------------------------------------------------\n    // User is attempting to open a relative path ?! No.\n    //--------------------------------------------------------------------------\n    eos_static_alert(\"Forbidden relative path '%s' \", path.c_str());\n    return Info(CredentialState::kCannotStat, {0, 0});\n  }\n\n  FileDescriptor current = std::move(jailfd);\n  auto splitPath = eos::common::SplitPath(path);\n\n  for (size_t i = 0; i < splitPath.size() - 1; i++) {\n    //--------------------------------------------------------------------------\n    // \"..\" in path? Disallow for now.\n    //--------------------------------------------------------------------------\n    if (splitPath[i] == \"..\") {\n      eos_static_alert(\".. in sub-path\");\n      return Info(CredentialState::kCannotStat, {0, 0});\n    }\n\n    FileDescriptor next(openat(current.getFD(), splitPath[i].c_str(),\n                               O_DIRECTORY | O_NOFOLLOW | O_RDONLY));\n\n    if (!next.ok()) {\n      eos_static_alert(\"Failed to openat next child\");\n      return Info(CredentialState::kCannotStat, {0, 0});\n    }\n\n    current = std::move(next);\n  }\n\n  //----------------------------------------------------------------------------\n  // We survived, up to the last chunk. Now try to read file contents.\n  //----------------------------------------------------------------------------\n  FileDescriptor fileFd(openat(current.getFD(), splitPath.back().c_str(),\n                               O_NOFOLLOW | O_RDONLY));\n\n  if (!fileFd.ok()) {\n    eos_static_alert(\"Failed to openat file\");\n    return Info(CredentialState::kCannotStat, {0, 0});\n  }\n\n  //----------------------------------------------------------------------------\n  // First stat the fd, make sure file permissions are OK.\n  //----------------------------------------------------------------------------\n  struct stat filestat;\n\n  if (::fstat(fileFd.getFD(), &filestat) != 0) {\n    eos_static_alert(\"failed to stat by fd\");\n    return Info(CredentialState::kCannotStat, {0, 0});\n  }\n\n  if (!checkPermissions(filestat.st_uid, filestat.st_mode, uid)) {\n    eos_static_alert(\"bad file permission\");\n    return Info(CredentialState::kBadPermissions, {0, 0});\n  }\n\n  //----------------------------------------------------------------------------\n  // All is good, try to read contents.\n  //----------------------------------------------------------------------------\n  std::string contents;\n\n  if (!readFile(fileFd.getFD(), contents)) {\n    eos_static_alert(\"failed to read file\");\n    return Info::CannotStat();\n  }\n\n  //----------------------------------------------------------------------------\n  // We have the contents, return.\n  //----------------------------------------------------------------------------\n  return Info::WithContents(extractTimespec(filestat), contents);\n}\n\n//------------------------------------------------------------------------------\n// Lookup given path.\n//------------------------------------------------------------------------------\nSecurityChecker::Info SecurityChecker::lookup(const JailInformation& jail,\n    const std::string& path, uid_t uid, gid_t gid)\n{\n  //----------------------------------------------------------------------------\n  // Simulation?\n  //----------------------------------------------------------------------------\n  if (useInjectedData) {\n    return lookupInjected(jail.id, path, uid);\n  }\n\n  //----------------------------------------------------------------------------\n  // Nope, real thing.\n  //----------------------------------------------------------------------------\n  if (path.empty()) {\n    return {};\n  }\n\n  //----------------------------------------------------------------------------\n  // Is the request towards our local jail? If so, use fast path, no need to\n  // go through heavyweight remote-jail lookup.\n  //\n  // Also, if ignoreJails is set to true we ignore containerization completely,\n  // and treat all paths relative to the host.\n  //----------------------------------------------------------------------------\n  if (jail.sameJailAsThisPid || ignoreJails) {\n    return lookupLocalJail(path, uid);\n  }\n\n  return lookupNonLocalJail(jail, path, uid, gid);\n}\n"
  },
  {
    "path": "fusex/auth/SecurityChecker.hh",
    "content": "//----------------------------------------------------------------------\n// File: SecurityChecker.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __SECURITY_CHECKER__HH__\n#define __SECURITY_CHECKER__HH__\n\n#include \"JailIdentifier.hh\"\n#include <mutex>\n#include <atomic>\n#include <map>\n#ifdef __APPLE__\n#include <sys/types.h>\n#endif\n\n//------------------------------------------------------------------------------\n// A class which provides a preliminary check that a credentials file can be\n// safely used by a particular uid.\n//\n// The strong check will be provided by XrdCl, which changes its fsuid on the\n// thread that reads the credentials.\n//\n// There's a window of opportunity between this check and the time XrdCl reads\n// the credentials that the underlying file can change, but as long as XrdCl\n// does the fsuid trick, there's no possibility for a malicious user to trick\n// us into using a credential file he does not have access to.\n//\n// You can also inject simulated data into this class, for use under test.\n// If there's at least one injection, we completely ignore the filesystem\n// and only serve injected data.\n//\n// NOTE: The SecurityChecker will return the entire file contents if it cannot\n// guarantee containment within the given jail by XrdCl. You are supposed to\n// copy the file contents into a separate file store, and use that in such\n// case.\n//------------------------------------------------------------------------------\n\nenum class CredentialState {\n  kCannotStat = 0,\n  kBadPermissions = 1,\n  kOk = 2,\n  kOkWithContents = 3\n};\n\nclass SecurityChecker\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  SecurityChecker(bool ignoreJails);\n\n  struct Info {\n    CredentialState state;\n    struct timespec mtime;\n    std::string contents;\n\n    static Info Ok(struct timespec mtime)\n    {\n      Info ret;\n      ret.state = CredentialState::kOk;\n      ret.mtime = mtime;\n      return ret;\n    }\n\n    static Info BadPermissions()\n    {\n      Info ret;\n      ret.state = CredentialState::kBadPermissions;\n      ret.mtime = {0, 0};\n      return ret;\n    }\n\n    static Info CannotStat()\n    {\n      Info ret;\n      ret.state = CredentialState::kCannotStat;\n      ret.mtime = {0, 0};\n      return ret;\n    }\n\n    static Info WithContents(struct timespec mtime, const std::string& contents)\n    {\n      Info ret;\n      ret.state = CredentialState::kOkWithContents;\n      ret.mtime = mtime;\n      ret.contents = contents;\n      return ret;\n    }\n\n    Info() : state(CredentialState::kCannotStat), mtime{0, 0} { }\n\n    Info(CredentialState st, struct timespec mt) : state(st), mtime(mt) { }\n\n    bool operator==(const Info& other) const\n    {\n      return state          ==  other.state           &&\n             mtime.tv_sec   ==  other.mtime. tv_sec   &&\n             mtime.tv_nsec  ==  other.mtime.tv_nsec   &&\n             contents       ==  other.contents;\n    }\n  };\n\n  //----------------------------------------------------------------------------\n  // Inject the given fake data. Once an injection is active, _all_ returned\n  // data is faked.\n  //----------------------------------------------------------------------------\n  void inject(const JailIdentifier& jail, const std::string& path, uid_t uid,\n              mode_t mode, struct timespec mtime);\n\n  //----------------------------------------------------------------------------\n  // Lookup given path, interpreted in the context of the given jail.\n  //----------------------------------------------------------------------------\n  Info lookup(const JailInformation& jail, const std::string& path, uid_t uid,\n              gid_t gid);\n\n  //----------------------------------------------------------------------------\n  // Check if data has been injected\n  //----------------------------------------------------------------------------\n  bool useInjected() {\n    return useInjectedData.load();\n  }\n\nprivate:\n  //----------------------------------------------------------------------------\n  // We have a file with the given uid and mode, and we're \"expectedUid\".\n  // Should we be able to read it? Enforce strict permissions on mode, as it's\n  // a credential file - only _we_ should be able to read it and no-one else.\n  //----------------------------------------------------------------------------\n  static bool checkPermissions(uid_t uid, mode_t mode, uid_t expectedUid);\n\n  //----------------------------------------------------------------------------\n  // Same as lookup, but only serve simulated data.\n  //----------------------------------------------------------------------------\n  Info lookupInjected(const JailIdentifier& jail, const std::string& path,\n                      uid_t uid);\n\n  //----------------------------------------------------------------------------\n  // Lookup given path in the context of our local jail.\n  //----------------------------------------------------------------------------\n  Info lookupLocalJail(const std::string& path, uid_t uid);\n\n  //----------------------------------------------------------------------------\n  // Things have gotten serious - interpret given path in the context of a\n  // different jail.\n  //----------------------------------------------------------------------------\n  Info lookupNonLocalJail(const JailInformation& jail, const std::string& path,\n                          uid_t uid, gid_t gid);\n\n  std::mutex mtx;\n  std::atomic<bool> useInjectedData{false};\n\n  struct InjectedData {\n    uid_t uid;\n    mode_t mode;\n    struct timespec mtime;\n\n    InjectedData() { }\n\n    InjectedData(uid_t u, mode_t md, struct timespec mt) : uid(u), mode(md), mtime(mt) { }\n  };\n\n  struct InjectedRequest {\n    JailIdentifier jail;\n    std::string path;\n  };\n\n  std::map<std::string, InjectedData> injections;\n  bool ignoreJails;\n};\n\n#endif\n"
  },
  {
    "path": "fusex/auth/UnixAuthenticator.cc",
    "content": "// ----------------------------------------------------------------------\n// File: UnixAuthenticator.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"UnixAuthenticator.hh\"\n\n//------------------------------------------------------------------------------\n// Create an identity based on unix-authentication. The uid and gid are\n// encoded in the LoginIdentifier in a way the MGM understands.\n//\n// It has to be that the MGM trusts the machine from which this request\n// originates, as this mechanism can be used to impersonate anyone.\n//------------------------------------------------------------------------------\nstd::shared_ptr<const BoundIdentity> UnixAuthenticator::createIdentity(\n  pid_t pid, uid_t uid, gid_t gid, bool reconnect, std::string key)\n{\n  std::shared_ptr<BoundIdentity> bdi(new BoundIdentity());\n  bdi->getLogin() = LoginIdentifier(uid, gid, pid,\n                                    getUnixConnectionCounter(uid, gid, reconnect));\n  bdi->getCreds()->getUC().secretkey = key;\n  return bdi;\n}\n\n//------------------------------------------------------------------------------\n// Get the current connection counter for the given uid, gid.\n//------------------------------------------------------------------------------\nuint64_t UnixAuthenticator::getUnixConnectionCounter(uid_t uid, gid_t gid,\n    bool reconnect)\n{\n  std::lock_guard<std::mutex> lock(mtx);\n\n  if (reconnect) {\n    connectionCounter[std::make_pair(uid, gid)]++;\n  }\n\n  return connectionCounter[std::make_pair(uid, gid)];\n}\n\n"
  },
  {
    "path": "fusex/auth/UnixAuthenticator.hh",
    "content": "// ----------------------------------------------------------------------\n// File: UnixAuthenticator.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_FUSEX_UNIX_AUTHENTICATOR_HH\n#define EOS_FUSEX_UNIX_AUTHENTICATOR_HH\n\n#include \"CredentialFinder.hh\"\n\nclass UnixAuthenticator\n{\npublic:\n  //----------------------------------------------------------------------------\n  // Create an identity based on unix-authentication. The uid and gid are\n  // encoded in the LoginIdentifier in a way the MGM understands.\n  //\n  // It has to be that the MGM trusts the machine from which this request\n  // originates, as this mechanism can be used to impersonate anyone.\n  //----------------------------------------------------------------------------\n  std::shared_ptr<const BoundIdentity> createIdentity(pid_t pid, uid_t uid,\n      gid_t gid, bool reconnect, std::string key);\n\nprivate:\n  std::mutex mtx;\n  std::map<std::pair<uid_t, gid_t>, uint64_t> connectionCounter;\n\n  //----------------------------------------------------------------------------\n  // Get the current connection counter for the given uid, gid.\n  //----------------------------------------------------------------------------\n  uint64_t getUnixConnectionCounter(uid_t uid, gid_t gid, bool reconnect);\n};\n\n#endif\n"
  },
  {
    "path": "fusex/auth/UserCredentialFactory.cc",
    "content": "// ----------------------------------------------------------------------\n// File: UserCredentialFactory.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"UserCredentialFactory.hh\"\n#include \"Logbook.hh\"\n#include \"Utils.hh\"\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nUserCredentialFactory::UserCredentialFactory(const CredentialConfig& conf) :\n  config(conf) {}\n\n//------------------------------------------------------------------------------\n// Generate SearchOrder from environment variables, while taking into account\n// EOS_FUSE_CREDS.\n//------------------------------------------------------------------------------\nSearchOrder UserCredentialFactory::parse(LogbookScope& scope,\n    const JailIdentifier& id, const Environment& env, uid_t uid, gid_t gid)\n{\n  SearchOrder retval;\n  std::string credString = env.get(\"EOS_FUSE_CREDS\");\n\n  if (credString.empty()) {\n    // Use defaults.\n    parseSingle(scope, \"defaults\", id, env, uid, gid, retval);\n    return retval;\n  }\n\n  std::vector<std::string> parts =\n    eos::common::StringSplit<std::vector<std::string>>(credString, \",\");\n\n  for (auto it = parts.begin(); it != parts.end(); it++) {\n    parseSingle(scope, *it, id, env, uid, gid, retval);\n  }\n\n  return retval;\n}\n\n//------------------------------------------------------------------------------\n// Append krb5 UserCredentials built from KRB5CCNAME-equivalent string.\n//------------------------------------------------------------------------------\nvoid UserCredentialFactory::addKrb5(const JailIdentifier& id, std::string path,\n                                    uid_t uid, gid_t gid, SearchOrder& out, const std::string& key)\n{\n  if (!config.use_user_krb5cc || path.empty()) {\n    return;\n  }\n\n  //----------------------------------------------------------------------------\n  // Kerberos keyring?\n  //----------------------------------------------------------------------------\n  if (startsWith(path, \"KEYRING\")) {\n    out.emplace_back(UserCredentials::MakeKrk5(path, uid, gid, key));\n    return;\n  }\n\n  //----------------------------------------------------------------------------\n  // Kerberos kcm?\n  //----------------------------------------------------------------------------\n  if (startsWith(path, \"KCM\")) {\n    out.emplace_back(UserCredentials::MakeKcm(path, uid, gid, key));\n    return;\n  }\n\n  //----------------------------------------------------------------------------\n  // Drop FILE:, if exists\n  //----------------------------------------------------------------------------\n  const std::string prefix = \"FILE:\";\n\n  if (startsWith(path, prefix)) {\n    path = path.substr(prefix.size());\n  }\n\n  if (path.empty()) {\n    //--------------------------------------------------------------------------\n    // Early exit, nothing to add to search order.\n    //--------------------------------------------------------------------------\n    return;\n  }\n\n  out.emplace_back(UserCredentials::MakeKrb5(id, path, uid, gid, key));\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Append OAUTH2 UserCredentials built from KRB5CCNAME-equivalent string.\n//------------------------------------------------------------------------------\nvoid UserCredentialFactory::addOAUTH2(const JailIdentifier& id,\n                                      std::string path,\n                                      uid_t uid, gid_t gid, SearchOrder& out, const std::string& key)\n{\n  if (!config.use_user_oauth2 || path.empty()) {\n    return;\n  }\n\n  //----------------------------------------------------------------------------\n  // Drop FILE:, if exists\n  //----------------------------------------------------------------------------\n  const std::string prefix = \"FILE:\";\n\n  if (startsWith(path, prefix)) {\n    path = path.substr(prefix.size());\n  }\n\n  if (path.empty()) {\n    //--------------------------------------------------------------------------\n    // Early exit, nothing to add to search order.\n    //--------------------------------------------------------------------------\n    return;\n  }\n\n  out.emplace_back(UserCredentials::MakeOAUTH2(id, path, uid, gid, key));\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Append ZTN UserCredentials built from KRB5CCNAME-equivalent string.\n//------------------------------------------------------------------------------\nvoid UserCredentialFactory::addZTN(const JailIdentifier& id, std::string path,\n                                   uid_t uid, gid_t gid, SearchOrder& out, const std::string& key)\n{\n  if (!config.use_user_ztn || path.empty()) {\n    return;\n  }\n\n  //----------------------------------------------------------------------------\n  // Drop FILE:, if exists\n  //----------------------------------------------------------------------------\n  const std::string prefix = \"FILE:\";\n\n  if (startsWith(path, prefix)) {\n    path = path.substr(prefix.size());\n  }\n\n  if (path.empty()) {\n    //--------------------------------------------------------------------------\n    // Early exit, nothing to add to search order.\n    //--------------------------------------------------------------------------\n    return;\n  }\n\n  out.emplace_back(UserCredentials::MakeZTN(id, path, uid, gid, key));\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Append krb5 UserCredentials built from Environment, if KRB5CCNAME\n// is defined.\n//------------------------------------------------------------------------------\nvoid UserCredentialFactory::addKrb5FromEnv(const JailIdentifier& id,\n    const Environment& env, uid_t uid, gid_t gid, SearchOrder& out)\n{\n  std::string key = env.get(\"EOS_FUSE_SECRET\");\n\n  if (key.empty() && config.encryptionKey.length()) {\n    key = config.encryptionKey;\n  }\n\n  return addKrb5(id, env.get(\"KRB5CCNAME\"), uid, gid, out, key);\n}\n\n//------------------------------------------------------------------------------\n// Append OAUTH2 UserCredentials built from Environment, if OAUHT2_TOKEN\n// is defined.\n//------------------------------------------------------------------------------\nvoid UserCredentialFactory::addOAUTH2FromEnv(const JailIdentifier& id,\n    const Environment& env, uid_t uid, gid_t gid, SearchOrder& out)\n{\n  std::string key = env.get(\"EOS_FUSE_SECRET\");\n\n  if (key.empty() && config.encryptionKey.length()) {\n    key = config.encryptionKey;\n  }\n\n  return addOAUTH2(id, env.get(\"OAUTH2_TOKEN\"), uid, gid, out, key);\n}\n\n//------------------------------------------------------------------------------\n// Append ztn UserCredentials built from Environment, if\n// is defined.\n//------------------------------------------------------------------------------\nvoid UserCredentialFactory::addZTNFromEnv(const JailIdentifier& id,\n    const Environment& env, uid_t uid, gid_t gid, SearchOrder& out)\n{\n  std::string key  = env.get(\"EOS_FUSE_SECRET\");\n  std::string btf  = env.get(\"BEARER_TOKEN_FILE\");\n  std::string btfd = env.get(\"XDG_RUNTIME_DIR\");\n  std::string path;\n\n  if (btf.length()) {\n    path = btf;\n  } else if (btfd.length()) {\n    path = btfd + std::string(\"/bt_u\") + std::to_string(uid);\n  }\n\n  return addZTN(id, path, uid, gid, out, key);\n}\n\n\n//------------------------------------------------------------------------------\n// Append krb5 UserCredentials built from X509_USER_PROXY-equivalent string.\n//------------------------------------------------------------------------------\nvoid UserCredentialFactory::addx509(const JailIdentifier& id, const\n                                    std::string& path, uid_t uid, gid_t gid, SearchOrder& out,\n                                    const std::string& key)\n{\n  if (!config.use_user_gsiproxy || path.empty()) {\n    return;\n  }\n\n  out.emplace_back(UserCredentials::MakeX509(id, path, uid, gid, key));\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Append UserCredentials object built from X509_USER_PROXY\n//------------------------------------------------------------------------------\nvoid UserCredentialFactory::addx509FromEnv(const JailIdentifier& id,\n    const Environment& env, uid_t uid, gid_t gid, SearchOrder& out)\n{\n  std::string key = env.get(\"EOS_FUSE_SECRET\");\n\n  if (key.empty() && config.encryptionKey.length()) {\n    key = config.encryptionKey;\n  }\n\n  return addx509(id, env.get(\"X509_USER_PROXY\"), uid, gid, out, key);\n}\n\n//------------------------------------------------------------------------------\n// Populate SearchOrder with entries given in environment variables.\n//------------------------------------------------------------------------------\nvoid UserCredentialFactory::addDefaultsFromEnv(const JailIdentifier& id,\n    const Environment& env, uid_t uid, gid_t gid, SearchOrder& searchOrder)\n{\n  std::string key = env.get(\"EOS_FUSE_SECRET\");\n\n  if (key.empty() && config.encryptionKey.length()) {\n    key = config.encryptionKey;\n  }\n\n  //----------------------------------------------------------------------------\n  // Using SSS? If so, add first.\n  //----------------------------------------------------------------------------\n  if (config.use_user_sss) {\n    std::string endorsement = env.get(\"XrdSecsssENDORSEMENT\");\n    searchOrder.emplace_back(\n      UserCredentials::MakeSSS(endorsement, uid, gid, key));\n  }\n\n  //----------------------------------------------------------------------------\n  // Add krb5, x509 derived from environment variables\n  //----------------------------------------------------------------------------\n  addKrb5AndX509FromEnv(id, env, uid, gid, searchOrder);\n\n  //----------------------------------------------------------------------------\n  // Add oauth2 derived from environment variables\n  if (config.use_user_oauth2) {\n    addOAUTH2FromEnv(id, env, uid, gid, searchOrder);\n  }\n}\n\n//------------------------------------------------------------------------------\n//! Append UserCredentials object built from krb5, and x509 env variables\n//------------------------------------------------------------------------------\nvoid UserCredentialFactory::addKrb5AndX509FromEnv(const JailIdentifier& id,\n    const Environment& env, uid_t uid, gid_t gid, SearchOrder& out)\n{\n  if (config.tryKrb5First) {\n    addKrb5FromEnv(id, env, uid, gid, out);\n    addx509FromEnv(id, env, uid, gid, out);\n  } else {\n    addx509FromEnv(id, env, uid, gid, out);\n    addKrb5FromEnv(id, env, uid, gid, out);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Given a single entry of the search path, append any entries\n// into the given SearchOrder object\n//------------------------------------------------------------------------------\nbool UserCredentialFactory::parseSingle(LogbookScope& scope,\n                                        const std::string& str, const JailIdentifier& id, const Environment& env,\n                                        uid_t uid, gid_t gid, SearchOrder& out)\n{\n  std::string key = env.get(\"EOS_FUSE_SECRET\");\n\n  if (key.empty() && config.encryptionKey.length()) {\n    key = config.encryptionKey;\n  }\n\n  //----------------------------------------------------------------------------\n  // Defaults?\n  //----------------------------------------------------------------------------\n  if (str == \"defaults\") {\n    addDefaultsFromEnv(id, env, uid, gid, out);\n    return true;\n  }\n\n  //----------------------------------------------------------------------------\n  // KRB?\n  //----------------------------------------------------------------------------\n  const std::string krbPrefix = \"krb:\";\n\n  if (startsWith(str, krbPrefix)) {\n    addKrb5(id, str.substr(krbPrefix.size()), uid, gid, out, key);\n    return true;\n  }\n\n  //----------------------------------------------------------------------------\n  // X509?\n  //----------------------------------------------------------------------------\n  const std::string x509Prefix = \"x509:\";\n\n  if (startsWith(str, x509Prefix)) {\n    addx509(id, str.substr(x509Prefix.size()), uid, gid, out, key);\n    return true;\n  }\n\n  //----------------------------------------------------------------------------\n  // ZTN?\n  //----------------------------------------------------------------------------\n  const std::string ztnPrefix = \"ztn:\";\n\n  if (startsWith(str, ztnPrefix)) {\n    addZTN(id, str.substr(ztnPrefix.size()), uid, gid, out, key);\n    return true;\n  }\n\n  //----------------------------------------------------------------------------\n  // Cannot parse given string\n  //----------------------------------------------------------------------------\n  LOGBOOK_INSERT(scope,\n                 \"Cannot understand this part of EOS_FUSE_CREDS, skipping: \" << str);\n  return false;\n}\n"
  },
  {
    "path": "fusex/auth/UserCredentialFactory.hh",
    "content": "// ----------------------------------------------------------------------\n// File: UserCredentialFactory.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_FUSEX_USER_CREDENTIAL_FACTORY_HH\n#define EOS_FUSEX_USER_CREDENTIAL_FACTORY_HH\n\n#include \"CredentialFinder.hh\"\n#include <vector>\n\nstruct UserCredentials;\nclass LogbookScope;\n\n//------------------------------------------------------------------------------\n//! SearchOrder is simply a vector of UserCredentials.\n//------------------------------------------------------------------------------\nusing SearchOrder = std::vector<UserCredentials>;\n\n//------------------------------------------------------------------------------\n//! This class knows how to translate credential strings into SearchOrder.\n//! (ie krb:/tmp/my-path,defaults) -> SearchOrder object\n//------------------------------------------------------------------------------\nclass UserCredentialFactory\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  UserCredentialFactory(const CredentialConfig& config);\n\n  //----------------------------------------------------------------------------\n  //! Parse a string, convert into SearchOrder\n  //----------------------------------------------------------------------------\n  SearchOrder parse(LogbookScope& scope, const JailIdentifier& id,\n                    const Environment& env, uid_t uid, gid_t gid);\n\n  //----------------------------------------------------------------------------\n  //! Given a single entry of the search path, try to parse and fill out a\n  //! single UserCredentials object\n  //----------------------------------------------------------------------------\n  bool parseSingle(LogbookScope& scope, const std::string& str,\n                   const JailIdentifier& id, const Environment& env, uid_t uid, gid_t gid,\n                   SearchOrder& out);\n\n  //----------------------------------------------------------------------------\n  //! Append defaults into given SearchOrder\n  //----------------------------------------------------------------------------\n  void addDefaultsFromEnv(const JailIdentifier& id, const Environment& env,\n                          uid_t uid, gid_t gid, SearchOrder& out);\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Append krb5 UserCredentials built from X509_USER_PROXY-equivalent string.\n  //----------------------------------------------------------------------------\n  void addx509(const JailIdentifier& id, const std::string& path, uid_t uid,\n               gid_t gid, SearchOrder& out, const std::string& key);\n\n  //----------------------------------------------------------------------------\n  //! Append krb5 UserCredentials built from KRB5CCNAME-equivalent string.\n  //----------------------------------------------------------------------------\n  void addKrb5(const JailIdentifier& id, std::string path, uid_t uid,\n               gid_t gid, SearchOrder& out, const std::string& key);\n\n  //----------------------------------------------------------------------------\n  //! Append oauth2 UserCredentials built from OAUTH2_TOKEN_FILE-equivalent string.\n  //----------------------------------------------------------------------------\n  void addOAUTH2(const JailIdentifier& id, std::string path, uid_t uid,\n                 gid_t gid, SearchOrder& out, const std::string& key);\n\n  //----------------------------------------------------------------------------\n  //! Append ZTN UserCredentials built from bearer token file.\n  //----------------------------------------------------------------------------\n  void addZTN(const JailIdentifier& id, std::string path, uid_t uid,\n              gid_t gid, SearchOrder& out, const std::string& key);\n\n  //----------------------------------------------------------------------------\n  //! Append UserCredentials object built from KRB5CCNAME\n  //----------------------------------------------------------------------------\n  void addKrb5FromEnv(const JailIdentifier& id, const Environment& env,\n                      uid_t uid, gid_t gid, SearchOrder& out);\n\n  //----------------------------------------------------------------------------\n  //! Append UserCredentials object built from KRB5CCNAME\n  //----------------------------------------------------------------------------\n  void addZTNFromEnv(const JailIdentifier& id, const Environment& env,\n                     uid_t uid, gid_t gid, SearchOrder& out);\n\n  //----------------------------------------------------------------------------\n  //! Append UserCredentials object built from OAUTH2_TOKEN_FILE\n  //----------------------------------------------------------------------------\n  void addOAUTH2FromEnv(const JailIdentifier& id, const Environment& env,\n                        uid_t uid, gid_t gid, SearchOrder& out);\n\n  //----------------------------------------------------------------------------\n  //! Append UserCredentials object built from X509_USER_PROXY\n  //----------------------------------------------------------------------------\n  void addx509FromEnv(const JailIdentifier& id, const Environment& env,\n                      uid_t uid, gid_t gid, SearchOrder& out);\n\n  //----------------------------------------------------------------------------\n  //! Append UserCredentials object built from krb5, and x509 env variables\n  //----------------------------------------------------------------------------\n  void addKrb5AndX509FromEnv(const JailIdentifier& id, const Environment& env,\n                             uid_t uid, gid_t gid, SearchOrder& out);\n\n  CredentialConfig config;\n};\n\n\n#endif\n"
  },
  {
    "path": "fusex/auth/UserCredentials.hh",
    "content": "// ----------------------------------------------------------------------\n// File: UserCredentials.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_FUSEX_USER_CREDENTIALS_HH\n#define EOS_FUSEX_USER_CREDENTIALS_HH\n\n#include \"JailIdentifier.hh\"\n#include \"Utils.hh\"\n#include <sstream>\n#include <sys/types.h>\n#include \"common/StringConversion.hh\"\n//------------------------------------------------------------------------------\n// Designates what kind of user credentials we're dealing with:\n// - KRB5: Kerberos file-based ticket cache\n// - KRK5: Kerberos kernel-keyring-based ticket cache\n// - KCM:  Kerberos KCM daemon ticket cache\n// - X509: GSI user certificates\n// - SSS: SSS ticket delegation\n// - NOBODY: Identify as nobody, no user credentails whatsoever\n// - OAUTH2: oauth2 over sss\n// --ZTN:  Token authentication\n//------------------------------------------------------------------------------\nenum class CredentialType : std::uint32_t {\n  KRB5 = 0,\n  KRK5,\n  KCM,\n  X509,\n  SSS,\n  NOBODY,\n  OAUTH2,\n  ZTN,\n  INVALID\n};\n\n//------------------------------------------------------------------------------\n// Convert CredentialType to string\n//------------------------------------------------------------------------------\ninline std::string credentialTypeAsString(CredentialType type)\n{\n  switch (type) {\n  case CredentialType::KRB5: {\n    return \"krb5\";\n  }\n\n  case CredentialType::KRK5: {\n    return \"krk5\";\n  }\n\n  case CredentialType::KCM: {\n    return \"kcm\";\n  }\n\n  case CredentialType::X509: {\n    return \"x509\";\n  }\n\n  case CredentialType::SSS: {\n    return \"sss\";\n  }\n\n  case CredentialType::OAUTH2: {\n    return \"oauth2\";\n  }\n\n  case CredentialType::ZTN: {\n    return \"ztn\";\n  }\n\n  case CredentialType::NOBODY: {\n    return \"nobody\";\n  }\n\n  case CredentialType::INVALID: {\n    return \"invadid\";\n  }\n  }\n\n  THROW(\"should never reach here\");\n}\n\n//------------------------------------------------------------------------------\n// This class stores information about an instance of user credentials. The\n// information contained within _must be sufficient_ to create an instance\n// of TrustedCredentials, after validation.\n//\n// UserCredentials could be all kinds of wrong, as it's derived directly\n// by user-provided data: Maybe credential files don't exist, or they have\n// wrong permissions, etc, so we cannot use it yet.\n//\n// TrustedCredentials = validated UserCredentials with a stamp of approval, but\n// not yet bound to a connection.\n//\n// BoundIdentity = TrustedCredentials bound to a LoginIdentifier.\n//------------------------------------------------------------------------------\nstruct UserCredentials {\n\n  //----------------------------------------------------------------------------\n  // Private constructor: Use the methods above to create such an object.\n  //----------------------------------------------------------------------------\n  UserCredentials()\n  {\n    type = CredentialType::INVALID;\n    // fname, keyring, endorsement default-initialized to empty\n    uid = 0;\n    gid = 0;\n  }\n\n  //----------------------------------------------------------------------------\n  // Constructor: Make a KRB5 object.\n  // We only need two pieces of information: The path at which the ticket cache\n  // resides in, and the uid to validate file permissions.\n  //----------------------------------------------------------------------------\n  static UserCredentials MakeKrb5(const JailIdentifier& jail,\n                                  const std::string& path, uid_t uid, gid_t gid, const std::string& key)\n  {\n    UserCredentials retval;\n    retval.type = CredentialType::KRB5;\n    retval.jail = jail;\n    retval.fname = path;\n    retval.uid = uid;\n    retval.gid = gid;\n    retval.secretkey = key;\n    return retval;\n  }\n\n  //----------------------------------------------------------------------------\n  // Constructor: Make a KRK5 object.\n  // TODO(gbitzes): Actually test this...\n  //----------------------------------------------------------------------------\n  static UserCredentials MakeKrk5(const std::string& keyring, uid_t uid,\n                                  gid_t gid, const std::string& key)\n  {\n    UserCredentials retval;\n    retval.type = CredentialType::KRK5;\n    retval.keyring = keyring;\n    retval.uid = uid;\n    retval.gid = gid;\n    retval.secretkey = key;\n    return retval;\n  }\n\n\n  //----------------------------------------------------------------------------\n  // Constructor: Make a KCM object.\n  // TODO(gbitzes): Actually test this...\n  //----------------------------------------------------------------------------\n  static UserCredentials MakeKcm(const std::string& kcm, uid_t uid, gid_t gid,\n                                 const std::string& key)\n  {\n    UserCredentials retval;\n    retval.type = CredentialType::KCM;\n    retval.kcm = kcm;\n    retval.uid = uid;\n    retval.gid = gid;\n    retval.secretkey = key;\n    return retval;\n  }\n\n  //----------------------------------------------------------------------------\n  // Constructor: Make a OAUTH2 object.\n  // TODO(gbitzes): Actually test this...\n  //----------------------------------------------------------------------------\n  static UserCredentials MakeOAUTH2(const JailIdentifier& jail,\n                                    const std::string& path, uid_t uid, gid_t gid, const std::string& key)\n  {\n    UserCredentials retval;\n    retval.type = CredentialType::OAUTH2;\n    retval.jail = jail;\n    retval.fname = path;\n    retval.uid = uid;\n    retval.gid = gid;\n    retval.secretkey = key;\n    std::string out;\n    return retval;\n  }\n\n  //----------------------------------------------------------------------------\n  // Constructor: Make an X509 object.\n  // We only need two pieces of information: The path at which the certificate\n  // resides in, and the uid to validate file permissions.\n  //----------------------------------------------------------------------------\n  static UserCredentials MakeX509(const JailIdentifier& jail,\n                                  const std::string& path, uid_t uid, gid_t gid, const std::string& key)\n  {\n    UserCredentials retval;\n    retval.type = CredentialType::X509;\n    retval.jail = jail;\n    retval.fname = path;\n    retval.uid = uid;\n    retval.gid = gid;\n    retval.secretkey = key;\n    return retval;\n  }\n\n  //----------------------------------------------------------------------------\n  // Constructor: Make a \"nobody\" object.\n  //----------------------------------------------------------------------------\n  static UserCredentials MakeNobody()\n  {\n    UserCredentials retval;\n    retval.type = CredentialType::NOBODY;\n    return retval;\n  }\n\n  //----------------------------------------------------------------------------\n  // Constructor: Make an SSS object.\n  // Three things required: The endorsement derived through environment\n  // variables, as well as uid and gid.\n  //\n  // TODO: If the global SSS key is not mapped to anyuser / anygroup,\n  // persisting uid/gid here is pointless.\n  //----------------------------------------------------------------------------\n  static UserCredentials MakeSSS(const std::string& endorsement, uid_t uid,\n                                 gid_t gid, const std::string& key)\n  {\n    UserCredentials retval;\n    retval.type = CredentialType::SSS;\n    retval.endorsement = endorsement;\n    retval.uid = uid;\n    retval.gid = gid;\n    retval.secretkey = key;\n    return retval;\n  }\n\n  //----------------------------------------------------------------------------\n  // Constructor: Make a ZTN object.\n  // We only need two pieces of information: The path at which the ticket cache\n  // resides in, and the uid to validate file permissions.\n  //----------------------------------------------------------------------------\n  static UserCredentials MakeZTN(const JailIdentifier& jail,\n                                 const std::string& path, uid_t uid, gid_t gid, const std::string& key)\n  {\n    UserCredentials retval;\n    retval.type = CredentialType::ZTN;\n    retval.jail = jail;\n    retval.fname = path;\n    retval.uid = uid;\n    retval.gid = gid;\n    retval.secretkey = key;\n    return retval;\n  }\n\n\n  //----------------------------------------------------------------------------\n  // Check if path contains unsafe characters: '&' or '='\n  //----------------------------------------------------------------------------\n  bool hasUnsafeCharacters() const\n  {\n    for (size_t i = 0; i < fname.size(); i++) {\n      if (fname[i] == '&' || fname[i] == '=') {\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  //----------------------------------------------------------------------------\n  // The subset of fields actually containing a value depends on the\n  // CredentialType.\n  //----------------------------------------------------------------------------\n  CredentialType type;\n  JailIdentifier jail;     // jail identifier for krb5, x509\n  std::string fname;       // credential filename for krb5, x509\n  std::string keyring;     // kernel keyring for krk5\n  std::string kcm;         // kcm for kcm\n  std::string endorsement; // endorsement for sss\n  std::string secretkey;   // secret key for encryptions\n  uid_t uid;               // uid for krb5, x509, sss, unix\n  gid_t gid;               // gid for krb5, x509, sss, unix\n  std::string username;    // username derived from a credential\n\n  //----------------------------------------------------------------------------\n  // Comparator for storing such objects in maps.\n  //----------------------------------------------------------------------------\n  bool operator<(const UserCredentials& src) const\n  {\n    if (type != src.type) {\n      return type < src.type;\n    }\n\n    if (jail.hash() != src.jail.hash()) {\n      return jail.hash() < src.jail.hash();\n    }\n\n    if (fname != src.fname) {\n      return fname < src.fname;\n    }\n\n    if (keyring != src.keyring) {\n      return keyring < src.keyring;\n    }\n\n    if (secretkey != src.secretkey) {\n      return secretkey < src.secretkey;\n    }\n\n    if (endorsement != src.endorsement) {\n      return endorsement < src.endorsement;\n    }\n\n    if (uid != src.uid) {\n      return uid < src.uid;\n    }\n\n    return gid < src.gid;\n  }\n\n  //----------------------------------------------------------------------------\n  // Equality operator\n  //----------------------------------------------------------------------------\n  bool operator==(const UserCredentials& src) const\n  {\n    return type        ==   src.type        &&\n           jail        ==   src.jail        &&\n           fname       ==   src.fname       &&\n           keyring     ==   src.keyring     &&\n           secretkey   ==   src.secretkey   &&\n           endorsement ==   src.endorsement &&\n           uid         ==   src.uid         &&\n           gid         ==   src.gid;\n  }\n\n  //----------------------------------------------------------------------------\n  // Describe contents\n  //----------------------------------------------------------------------------\n  std::string describe() const\n  {\n    std::stringstream ss;\n    ss << credentialTypeAsString(type);;\n\n    switch (type) {\n    case CredentialType::KRB5:\n    case CredentialType::OAUTH2:\n    case CredentialType::ZTN:\n    case CredentialType::X509: {\n      ss << \": \" << fname << \" for uid=\" << uid << \", gid=\" << gid << \", secret=\" <<\n         secretkey <<\n         \", under \" << jail.describe();\n      break;\n    }\n\n    case CredentialType::KRK5: {\n      ss << \": \" << keyring << \" for uid=\" << uid << \", gid=\" << gid << \", secret=\" <<\n         secretkey;\n      break;\n    }\n\n    case CredentialType::KCM: {\n      ss << \": \" << kcm << \" for uid=\" << uid << \", gid=\" << gid << \", secret=\" <<\n         secretkey;\n      break;\n    }\n\n    case CredentialType::SSS: {\n      ss << \" with endorsement of size \" << endorsement.size() <<\n         \", for uid=\" << uid << \", gid=\" << gid << \", secret=\" << secretkey;\n      break;\n    }\n\n    case CredentialType::NOBODY:\n    case CredentialType::INVALID: {\n      ss << \",secret=\" << secretkey;\n      break;\n      // null\n    }\n    }\n\n    return ss.str();\n  }\n\nprivate:\n};\n\n#endif\n"
  },
  {
    "path": "fusex/auth/Utils.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Utils.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <sys/stat.h>\n#include <unistd.h>\n#include \"Utils.hh\"\n\nbool readFile(int fd, std::string& contents)\n{\n  bool retvalue = true;\n  std::ostringstream ss;\n  const int BUFFER_SIZE = 1024;\n  char buffer[BUFFER_SIZE];\n\n  while (true) {\n    ssize_t bytesRead = ::read(fd, buffer, BUFFER_SIZE);\n\n    if (bytesRead > 0) {\n      ss.write(buffer, bytesRead);\n    }\n\n    if (bytesRead < 0) {\n      retvalue = false;\n      break;\n    }\n\n    if (bytesRead != BUFFER_SIZE) {\n      break;\n    }\n  }\n\n  contents = ss.str();\n  return retvalue;\n}\n\nbool readFile(const std::string& path, std::string& contents)\n{\n  bool retvalue = true;\n  std::ostringstream ss;\n  const int BUFFER_SIZE = 1024;\n  char buffer[BUFFER_SIZE];\n  FILE* in = fopen(path.c_str(), \"rb\");\n\n  if (!in) {\n    return false;\n  }\n\n  while (true) {\n    size_t bytesRead = fread(buffer, 1, BUFFER_SIZE, in);\n\n    if (bytesRead > 0) {\n      ss.write(buffer, bytesRead);\n    }\n\n    if (bytesRead == 0) {\n      if (!feof(in)) {\n        retvalue = false;\n      }\n\n      break;\n    }\n\n    if (bytesRead != BUFFER_SIZE) {\n      break;\n    }\n  }\n\n  fclose(in);\n  contents = ss.str();\n  return retvalue;\n}\n\nbool writeFile600(const std::string& path, const std::string& contents)\n{\n  FILE* out = fopen(path.c_str(), \"wb\");\n\n  if (!out) {\n    return false;\n  }\n\n  if (fchmod(fileno(out), S_IRUSR | S_IWUSR) != 0) {\n    fclose(out);\n    return false;\n  }\n\n  if (fwrite(contents.c_str(), sizeof(char), contents.size(),\n             out) != contents.size()) {\n    fclose(out);\n    return false;\n  }\n\n  fclose(out);\n  return true;\n}\n\nbool checkCredSecurity(const struct stat& filestat, uid_t uid)\n{\n  if (filestat.st_uid == uid\n      && (filestat.st_mode & 0077) == 0 // no access to other users/groups\n      && (filestat.st_mode & 0400) != 0 // read allowed for the user\n     ) {\n    return true;\n  }\n\n  return false;\n}\n\nstd::string chopTrailingSlashes(const std::string& path)\n{\n  std::string value = path;\n\n  while (value.size() > 1 && value[value.size() - 1] == '/') {\n    value.pop_back();\n  }\n\n  return value;\n}\n"
  },
  {
    "path": "fusex/auth/Utils.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Utils.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSFUSE_UTILS_HH__\n#define __EOSFUSE_UTILS_HH__\n\n#include <exception>\n#include <string>\n#include <vector>\n#include <sstream>\n#include <iostream>\n#include \"common/StringUtils.hh\"\n#include \"common/StringSplit.hh\"\n\nclass FatalException : public std::exception\n{\npublic:\n\n  FatalException(const std::string& m) : msg(m) { }\n\n  virtual ~FatalException() { }\n\n  virtual const char* what() const noexcept\n  {\n    return msg.c_str();\n  }\n\nprivate:\n  std::string msg;\n};\n\n#define DBG(message) std::cerr << __FILE__ << \":\" << __LINE__ << \" -- \" << #message << \" = \" << message << std::endl\n#define SSTR(message) static_cast<std::ostringstream&>(std::ostringstream().flush() << message).str()\n#define THROW(message) throw FatalException(SSTR(message))\n\ninline std::vector<std::string> split_on_nullbyte(std::string_view data)\n{\n  std::vector<std::string> result;\n  auto segments = eos::common::CharSplitIt(data, '\\0');\n\n  for (std::string_view segment : segments) {\n    result.emplace_back(segment);\n  }\n\n  return result;\n}\n\ninline std::string join(const std::vector<std::string>& contents,\n                        const std::string& delimiter)\n{\n  std::stringstream ss;\n\n  for (size_t i = 0; i < contents.size(); i++) {\n    ss << contents[i];\n\n    if (i != contents.size() - 1) {\n      ss << \" \";\n    }\n  }\n\n  return ss.str();\n}\n\nusing eos::common::startsWith;\nbool readFile(int fd, std::string& ret);\nbool readFile(const std::string& path, std::string& ret);\nbool writeFile600(const std::string& path, const std::string& contents);\n\nbool checkCredSecurity(const struct stat& filestat, uid_t uid);\nstd::string chopTrailingSlashes(const std::string& path);\n\n#endif\n"
  },
  {
    "path": "fusex/auth/UuidStore.cc",
    "content": "//------------------------------------------------------------------------------\n// File: UuidStore.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"UuidStore.hh\"\n#include \"DirectoryIterator.hh\"\n#include \"Utils.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/Logging.hh\"\n#include <uuid/uuid.h>\n#include <sys/stat.h>\n\n\n//----------------------------------------------------------------------------\n//! Constructor. Provide the repository, that is the directory to use\n//! on the physical filesystem.\n//----------------------------------------------------------------------------\nUuidStore::UuidStore(const std::string& repository_)\n  : repository(chopTrailingSlashes(repository_))\n{\n  struct stat repostat;\n\n  if (::stat(repository.c_str(), &repostat) != 0) {\n    THROW(\"Cannot stat uuid-store repository: \" << repository);\n  }\n\n  if (!S_ISDIR(repostat.st_mode)) {\n    THROW(\"Repository path is not a directory: \" << repository);\n  }\n\n  initialCleanup();\n}\n\n//------------------------------------------------------------------------------\n//! Unlink leftover credential files from previous runs - if eosxd crashes,\n//! this can happen. Only unlink files matching our prefix, so that in case\n//! of misconfiguration we don't wipe out important files.\n//------------------------------------------------------------------------------\nvoid UuidStore::initialCleanup()\n{\n  DirectoryIterator iterator(repository);\n  struct dirent* current = nullptr;\n\n  while ((current = iterator.next())) {\n    std::string f = current->d_name;\n\n    if ((f == \".\") || (f == \"..\")) {\n      continue;\n    }\n\n    if (startsWith(current->d_name, \"eos-fusex-uuid-store-\")) {\n      if (unlink(SSTR(repository << \"/\" << current->d_name).c_str()) != 0) {\n        eos_static_crit(\"UuidStore:: Could not delete %s during initial cleanup, errno %d\",\n                        current->d_name, errno);\n      }\n    } else {\n      eos_static_crit(\"Found file in credential store with suspicious filename, should not be there: '%s' Not unlinking.\",\n                      current->d_name);\n    }\n  }\n\n  if (!iterator.ok()) {\n    eos_static_crit(\"UuidStore:: Cleanup thread encountered an error while iterating over the repository: %s\",\n                    iterator.err().c_str());\n  }\n}\n\n//------------------------------------------------------------------------------\n//! Store the given contents inside the store. Returns the full filesystem\n//! path on which the contents were stored.\n//------------------------------------------------------------------------------\nstd::string UuidStore::put(const std::string& contents)\n{\n  std::string path = SSTR(repository << \"/\" << \"eos-fusex-uuid-store-\"\n                          << generateUuid());\n\n  if (!writeFile600(path, contents)) {\n    eos_static_crit(\"UuidStore: Could not write path: %s\", path.c_str());\n    return \"\";\n  }\n\n  return path;\n}\n\n//------------------------------------------------------------------------------\n//! Make uuid\n//------------------------------------------------------------------------------\nstd::string UuidStore::generateUuid()\n{\n  char buffer[64];\n  uuid_t uuid;\n  uuid_generate_random(uuid);\n  uuid_unparse(uuid, buffer);\n  return std::string(buffer);\n}\n"
  },
  {
    "path": "fusex/auth/UuidStore.hh",
    "content": "//------------------------------------------------------------------------------\n// File: UuidStore.hh\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSEX_UUID_STORE_HH\n#define FUSEX_UUID_STORE_HH\n\n#include \"common/AssistedThread.hh\"\n\n#include <string>\n#include <chrono>\n\n//------------------------------------------------------------------------------\n//! A filesystem-backed store - every write is assigned to a specific UUID.\n//! Contents are not meant to persist after process restart, and in fact will\n//! be cleared out explicitly.\n//------------------------------------------------------------------------------\nclass UuidStore\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor. Provide the repository, that is the directory to use\n  //! on the physical filesystem.\n  //----------------------------------------------------------------------------\n  UuidStore(const std::string& repository);\n\n  //----------------------------------------------------------------------------\n  //! Store the given contents inside the store. Returns the full filesystem\n  //! path on which the contents were stored.\n  //----------------------------------------------------------------------------\n  std::string put(const std::string& contents);\n\n  //----------------------------------------------------------------------------\n  //! Unlink leftover credential files from previous runs - if eosxd crashes,\n  //! this can happen. Only unlink files matching our prefix, so that in case\n  //! of misconfiguration we don't wipe out important files.\n  //----------------------------------------------------------------------------\n  void initialCleanup();\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Make uuid\n  //----------------------------------------------------------------------------\n  static std::string generateUuid();\n\n  std::string repository;\n};\n\n#endif\n\n"
  },
  {
    "path": "fusex/backend/backend.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file backend.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief backend IO handling class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"backend/backend.hh\"\n#include \"cap/cap.hh\"\n#include \"misc/fusexrdlogin.hh\"\n#include \"eosfuse.hh\"\n#include \"common/Logging.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/SymKeys.hh\"\n#include <XrdCl/XrdClFile.hh>\n#include <XrdCl/XrdClURL.hh>\n\n/* -------------------------------------------------------------------------- */\nbackend::backend()\n/* -------------------------------------------------------------------------- */\n{\n  timeout = 0;\n  put_timeout = 0;\n}\n\n/* -------------------------------------------------------------------------- */\nbackend::~backend()\n/* -------------------------------------------------------------------------- */\n{\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nbackend::init(std::string& _hostport, std::string& _remotemountdir,\n              double& _timeout, double& _put_timeout)\n/* -------------------------------------------------------------------------- */\n{\n  hostport = _hostport;\n  mount = _remotemountdir;\n  timeout = _timeout;\n  put_timeout = _put_timeout;\n\n  if ((mount.length() && (mount.at(mount.length() - 1) == '/'))) {\n    mount.erase(mount.length() - 1);\n  }\n\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nbackend::mapErrCode(int retc)\n{\n  if (!retc) {\n    return retc;\n  }\n\n  return XProtocol::toErrno(retc);\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nbackend::getMD(fuse_req_t req,\n               const std::string& path,\n               std::vector<eos::fusex::container>& contv,\n               bool listing,\n               std::string authid\n              )\n/* -------------------------------------------------------------------------- */\n{\n  // return's the inode of path in inode and rc=0 for success, otherwise errno\n  std::string requestURL = getURL(req, path, \"fuseX\", \"getfusex\",\n                                  listing ? \"LS\" : \"GET\", authid, listing ? true : false);\n\n  if (listing || !use_mdquery()) {\n    return fetchResponse(req, 0, requestURL, contv);\n  } else {\n    return fetchQueryResponse(0, requestURL, contv);\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nbackend::getMD(fuse_req_t req,\n               uint64_t inode,\n               const std::string& name,\n               std::vector<eos::fusex::container>& contv,\n               bool listing,\n               std::string authid\n              )\n{\n  EosFuse::instance().Tracker().SetOrigin(req, inode, \"md::get\");\n  std::string requestURL = getURL(req, inode, name, \"fuseX\", \"getfusex\",\n                                  listing ? \"LS\" : \"GET\",\n                                  authid, listing ? true : false);\n\n  if (listing || !use_mdquery()) {\n    return fetchResponse(req, 0, requestURL, contv);\n  } else {\n    return fetchQueryResponse(0, requestURL, contv);\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nbackend::getMD(fuse_req_t req,\n               uint64_t inode,\n               uint64_t myclock,\n               std::vector<eos::fusex::container>& contv,\n               bool listing,\n               std::string authid\n              )\n/* -------------------------------------------------------------------------- */\n{\n  EosFuse::instance().Tracker().SetOrigin(req, inode, \"md::get\");\n  std::string requestURL = getURL(req, inode, myclock, \"fuseX\", \"getfusex\",\n                                  listing ? \"LS\" : \"GET\",\n                                  authid, listing ? true : false);\n\n  if (listing || !use_mdquery()) {\n    return fetchResponse(req, inode, requestURL, contv);\n  } else {\n    return fetchQueryResponse(inode, requestURL, contv);\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nint\nbackend::getCAP(fuse_req_t req,\n                uint64_t inode,\n                std::vector<eos::fusex::container>& contv\n               )\n/* -------------------------------------------------------------------------- */\n{\n  double total_exec_time_sec = 0;\n\n  do {\n    struct timespec ts;\n    eos::common::Timing::GetTimeSpec(ts, true);\n\n    EosFuse::instance().Tracker().SetOrigin(req, inode, \"cap::get\");\n    const uint64_t myclock = (uint64_t) time(NULL) +\n                             5; // allow for drifts of up to 5s (+2 on server side)\n    std::string requestURL = getURL(req, inode, myclock, \"fuseX\", \"getfusex\",\n                                    \"GETCAP\", \"\", true);\n    const int rc = fetchResponse(req, inode, requestURL, contv, true);\n    if (rc != EL2NSYNC) return rc;\n\n    // MGM reported a clock error, but try to determine if it is due to a\n    // time synchronization problem or a long round-trip time.\n    const double exec_time_sec = 1.0 * eos::common::Timing::GetCoarseAgeInNs(&ts,\n                                 0) / 1000000000.0;\n\n    if (exec_time_sec < 5.0) {\n      // this is a time synchronization error\n      eos_static_err(\"%s\", \"msg=\\\"GETCAP finished within 5 seconds \"\n                           \"round-trip time, the clock seems to be out of sync \"\n                           \"with the MGM\\\"\");\n      errno = EL2NSYNC;\n      return EL2NSYNC;\n    }\n\n    total_exec_time_sec += exec_time_sec;\n    if (timeout && ((total_exec_time_sec + 5) > timeout)) {\n      eos_static_err(\"giving up getcap after sum-fetch-exec-s=%.02f backend-timeout-s=%.02f\",\n                     total_exec_time_sec, timeout);\n      errno = EL2NSYNC;\n      return EL2NSYNC;\n    }\n\n    eos_static_err(\"%s\", \"msg=\\\"GETCAP took more than 5 seconds and we got a clock sync error\"\n                         \"with the MGM - retrying\\\"\");\n    std::this_thread::sleep_for(std::chrono::seconds(5));\n    total_exec_time_sec += 5.0;\n  } while(1);\n\n  // unreached\n  return ENXIO;\n}\n\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nbackend::fetchQueryResponse(uint64_t inode,\n                            std::string& requestURL,\n                            std::vector<eos::fusex::container>& contv\n                           )\n/* -------------------------------------------------------------------------- */\n{\n  XrdCl::URL url(requestURL);\n  eos_static_debug(\"request='%s'\", requestURL.c_str());\n  std::string sarg = url.GetPathWithParams();\n  XrdCl::Buffer arg;\n  arg.FromString(sarg);\n  XrdCl::Buffer* bresponse = 0;\n  XrdCl::XRootDStatus status = Query(url, XrdCl::QueryCode::OpaqueFile, arg,\n                                     bresponse, 30, false);\n  std::unique_ptr<XrdCl::Buffer> rsp(bresponse);\n\n  if (status.IsOK()) {\n    eos_static_debug(\"%x\", bresponse);\n    eos_static_debug(\"response-size=%d\",\n                     bresponse ? bresponse->GetSize() : 0);\n\n    if (bresponse && bresponse->GetBuffer()) {\n      off_t offset = 0;\n      eos::fusex::container cont;\n      std::string response(bresponse->GetBuffer(), bresponse->GetSize());\n\n      if (EOS_LOGS_DEBUG)\n        eos_static_debug(\"result-dump=%s\",\n                         eos::common::StringConversion::string_to_hex(response).c_str());\n\n      do {\n        cont.Clear();\n\n        if ((response.size() - offset) > 10) {\n          std::string slen = response.substr(1 + offset, 8);\n          size_t len = strtoll(slen.c_str(), 0, 16);\n          eos_static_debug(\"len=%llu offset=%llu\", len, offset);\n\n          if (!len) {\n            eos_static_debug(\"response had illegal length\");\n            return EINVAL;\n          }\n\n          if (response.size() < offset + 10 + len) {\n            eos_static_debug(\"response had short length\");\n            return EINVAL;\n          }\n\n          std::string item;\n          item.assign(response.c_str() + offset + 10, len);\n          offset += (10 + len);\n\n          if (cont.ParseFromString(item)) {\n            eos_static_debug(\"response parsing OK\");\n\n            if ((cont.type() != cont.MD) &&\n                (cont.type() != cont.MDMAP) &&\n                (cont.type() != cont.CAP)) {\n              eos_static_debug(\"wrong response type\");\n              return EINVAL;\n            }\n\n            contv.push_back(cont);\n            eos_static_debug(\"parsed %ld/%ld\", offset, response.size());\n\n            if (offset == (off_t) response.size()) {\n              break;\n            }\n          } else {\n            eos_static_debug(\"response parsing FAILED\");\n            return EIO;\n          }\n        } else {\n          eos_static_err(\"fatal protocol parsing error\");\n          return EINVAL;\n        };\n      } while (1);\n\n      return 0;\n    }\n\n    eos_static_debug(\"\");\n  } else {\n    if (status.errNo == XErrorCode::kXR_NotFound) {\n      // this is just no such file or directory\n      eos_static_debug(\"error=status is NOT ok : %s\", status.ToString().c_str());\n      errno = ENOENT;\n      return ENOENT;\n    }\n\n    if (status.code == XrdCl::errAuthFailed) {\n      eos_static_debug(\"\");\n      // this is an authentication error which results in permission denied\n      errno = EPERM;\n      return EPERM;\n    }\n\n    // all the other errors are reported back\n    if (status.errNo) {\n      errno = XrdCl::Proxy::status2errno(status);\n      eos_static_err(\"error=status is not ok : errno=%d\", errno);\n\n      // xrootd does not transport E2BIG ... sigh\n      if (errno == ENAMETOOLONG) {\n        errno = E2BIG;\n      }\n\n      return errno;\n    }\n\n    eos_static_debug(\"\");\n  }\n\n  return EIO;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nbackend::fetchResponse(fuse_req_t req,\n                       uint64_t inode,\n                       std::string& requestURL,\n                       std::vector<eos::fusex::container>& contv,\n                       bool cap\n                      )\n/* -------------------------------------------------------------------------- */\n{\n  eos_static_debug(\"request='%s'\", requestURL.c_str());\n  double total_exec_time_sec = 0;\n  XrdCl::XRootDStatus status;\n  std::unique_ptr <XrdCl::File> file(new XrdCl::File());\n  std::string response;\n  off_t offset = 0;\n  const int kPAGE = 512 * 1024;\n  std::vector<char> rbuff(kPAGE);\n  uint32_t bytesread = 0;\n\n  do {\n    struct timespec ts;\n    eos::common::Timing::GetTimeSpec(ts, true);\n\n    // the MD get operation is implemented via a stream: open/read/close\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"opening %s\", requestURL.c_str());\n    }\n\n    status = file->Open(requestURL.c_str(),\n                        XrdCl::OpenFlags::Flags::Read);\n    double exec_time_sec = 1.0 * eos::common::Timing::GetCoarseAgeInNs(&ts,\n                           0) / 1000000000.0;\n    total_exec_time_sec += exec_time_sec;\n    std::string lasturl;\n    file->GetProperty(\"LastURL\", lasturl);\n\n    if (lasturl.length()) {\n      EosFuse::Instance().TrackMgm(lasturl);\n    }\n\n    if (!status.IsOK()) {\n      // check if we got an inlined response in an error object\n      std::string b64response = status.GetErrorMessage();\n\n      if (b64response.substr(0, 6) == \"base64\") {\n        eos::common::SymKey::DeBase64(b64response, response);\n        goto has_response;\n      }\n\n      // in case of any failure\n      if (status.errNo == XErrorCode::kXR_NotFound) {\n        // this is just no such file or directory\n        eos_static_debug(\"error=status is NOT ok : %s\", status.ToString().c_str());\n        errno = ENOENT;\n        EosFuse::instance().Tracker().SetOrigin(req, inode, \"fs\");\n        return ENOENT;\n      }\n\n      if (status.IsFatal() || EOS_LOGS_DEBUG || (status.errNo != kXR_NotAuthorized)) {\n        eos_static_err(\"fetch-exec-ms=%.02f sum-query-exec-ms=%.02f ok=%d err=%d fatal=%d status-code=%d err-no=%d\",\n                       exec_time_sec * 1000.0, total_exec_time_sec * 1000.0, status.IsOK(),\n                       status.IsError(), status.IsFatal(), status.code, status.errNo);\n        eos_static_err(\"error=status is NOT ok : %s %d %d\", status.ToString().c_str(),\n                       status.code, status.errNo);\n      }\n\n      if (status.code == XrdCl::errAuthFailed) {\n        // this is an authentication error which results in permission denied\n        errno = EPERM;\n        EosFuse::instance().Tracker().SetOrigin(req, inode, \"fs\");\n        return EPERM;\n      }\n\n      std::string xrootderr = status.GetErrorMessage();\n\n      // the xrootd mapping of errno to everything unknown to EIO is really unfortunate\n      if (xrootderr.find(\"get-cap-clock-out-of-sync\") != std::string::npos) {\n        // MGM not happy with the consistency of our timestamp: possibly long\n        // round-trip or a clock sync issue. Let getCAP determine which.\n        errno = EL2NSYNC;\n        EosFuse::instance().Tracker().SetOrigin(req, inode, \"fs\");\n        return EL2NSYNC;\n      }\n\n      if (\n        (status.code == XrdCl::errConnectionError) ||\n        (status.code == XrdCl::errSocketTimeout) ||\n        (status.code == XrdCl::errOperationExpired) ||\n        (status.code == XrdCl::errSocketDisconnected)\n      ) {\n        // if there is a timeout, we might retry according to the backend timeout setting\n        if (timeout &&\n            ((total_exec_time_sec + 5) >\n             timeout)) {\n          // it took longer than our backend timeout allows\n          eos_static_err(\"giving up fetch after sum-fetch-exec-s=%.02f backend-timeout-s=%.02f\",\n                         total_exec_time_sec, timeout);\n        } else {\n          if (status.code == XrdCl::errConnectionError) {\n            if (cap) {\n              EosFuse::instance().Tracker().SetOrigin(req, inode, \"cap::conn::err\");\n            } else {\n              EosFuse::instance().Tracker().SetOrigin(req, inode, \"fetch::conn::err\");\n            }\n          }\n\n          if (status.code == XrdCl::errSocketTimeout) {\n            if (cap) {\n              EosFuse::instance().Tracker().SetOrigin(req, inode, \"cap::sock::tout\");\n            } else {\n              EosFuse::instance().Tracker().SetOrigin(req, inode, \"fetch::sock::tout\");\n            }\n          }\n\n          if (status.code == XrdCl::errOperationExpired) {\n            if (cap) {\n              EosFuse::instance().Tracker().SetOrigin(req, inode, \"cap::oper::exp\");\n            } else {\n              EosFuse::instance().Tracker().SetOrigin(req, inode, \"fetch::oper::exp\");\n            }\n          }\n\n          if (status.code == XrdCl::errSocketDisconnected) {\n            if (cap) {\n              EosFuse::instance().Tracker().SetOrigin(req, inode, \"cap::sock::disc\");\n            } else {\n              EosFuse::instance().Tracker().SetOrigin(req, inode, \"fetch::sock::disc\");\n            }\n          }\n\n          // retry\n          std::this_thread::sleep_for(std::chrono::seconds(5));\n          total_exec_time_sec += 5;\n          file.reset(new XrdCl::File());\n          continue;\n        }\n      }\n\n      // all the other errors are reported back\n      if (status.errNo) {\n        errno = XrdCl::Proxy::status2errno(status);\n\n        if ((status.errNo != EPERM)) {\n          eos_static_err(\"error=status is not ok : errno=%d\", errno);\n        }\n\n        // xrootd does not transport E2BIG ... sigh\n        if (errno == ENAMETOOLONG) {\n          errno = E2BIG;\n        }\n\n        return errno;\n      }\n\n      if (status.code) {\n        errno = EIO;\n        eos_static_err(\"error=status is not ok : code=%d\", errno);\n        EosFuse::instance().Tracker().SetOrigin(req, inode, \"fs\");\n        return errno;\n      }\n    } else {\n      eos_static_debug(\"fetch-exec-ms=%.02f sum-fetch-exec-ms=%.02f ok=%d err=%d fatal=%d status-code=%d err-no=%d\",\n                       exec_time_sec * 1000.0, total_exec_time_sec * 1000.0, status.IsOK(),\n                       status.IsError(), status.IsFatal(), status.code, status.errNo);\n      break;\n    }\n  } while (1);\n\n  // Start to read\n\n  do {\n    status = file->Read(offset, kPAGE, (char*) & rbuff[0], bytesread);\n\n    if (status.IsOK()) {\n      offset += bytesread;\n      response.append(&rbuff[0], bytesread);\n      eos_static_debug(\"+response=%s size=%u rsize=%u\",\n                       response.c_str(),\n                       response.size(),\n                       rbuff.size());\n    } else {\n      // failure\n      bytesread = 0;\n      if (status.errNo || status.code) {\n        int err = EIO;\n        if (status.errNo) err = XrdCl::Proxy::status2errno(status);\n        eos_static_err(\"error=status is not ok during read : code=%d\", err);\n        EosFuse::instance().Tracker().SetOrigin(req, inode, \"fs\");\n        errno = err;\n        return errno;\n      }\n    }\n\n    eos_static_debug(\"rbytes=%lu offset=%llu\", bytesread, offset);\n  } while (bytesread);\n\nhas_response:\n  EosFuse::instance().Tracker().SetOrigin(req, inode, \"fs\");\n  eos_static_debug(\"response-size=%u response=%s\",\n                   response.size(), response.c_str());\n  //eos_static_debug(\"response-dump=%s\", eos::common::StringConversion::string_to_hex(response).c_str());\n  offset = 0;\n  eos::fusex::container cont;\n\n  do {\n    contv.clear();\n    cont.Clear();\n\n    if ((response.size() - offset) > 10) {\n      std::string slen = response.substr(1 + offset, 8);\n      size_t len = strtoll(slen.c_str(), 0, 16);\n      eos_static_debug(\"len=%llu offset=%llu\", len, offset);\n\n      if (!len) {\n        eos_static_debug(\"response had illegal length\");\n        EosFuse::instance().Tracker().SetOrigin(req, inode, \"fs\");\n        return EINVAL;\n      }\n\n      if (response.size() < offset + 10 + len) {\n        eos_static_debug(\"response had short length\");\n        EosFuse::instance().Tracker().SetOrigin(req, inode, \"fs\");\n        return EINVAL;\n      }\n\n      std::string item;\n      item.assign(response.c_str() + offset + 10, len);\n      offset += (10 + len);\n\n      if (cont.ParseFromString(item)) {\n        eos_static_debug(\"response parsing OK\");\n\n        if ((cont.type() != cont.MD) &&\n            (cont.type() != cont.MDMAP) &&\n            (cont.type() != cont.CAP)) {\n          eos_static_debug(\"wrong response type\");\n          EosFuse::instance().Tracker().SetOrigin(req, inode, \"fs\");\n          return EINVAL;\n        }\n\n        contv.push_back(cont);\n        eos_static_debug(\"parsed %ld/%ld\", offset, response.size());\n\n        if (offset == (off_t) response.size()) {\n          break;\n        }\n      } else {\n        eos_static_debug(\"response parsing FAILED\");\n        EosFuse::instance().Tracker().SetOrigin(req, inode, \"fs\");\n        return EIO;\n      }\n    } else {\n      eos_static_err(\"fatal protocol parsing error\");\n      EosFuse::instance().Tracker().SetOrigin(req, inode, \"fs\");\n      return EINVAL;\n    };\n  } while (1);\n\n  EosFuse::instance().Tracker().SetOrigin(req, inode, \"fs\");\n  return 0;\n}\n\nint\n/* -------------------------------------------------------------------------- */\nbackend::rmRf(fuse_req_t req, eos::fusex::md* md)\n/* -------------------------------------------------------------------------- */\n{\n  fuse_id id(req);\n  XrdCl::URL url(\"root://\" + hostport);\n  url.SetPath(\"/proc/user/\");\n  XrdCl::URL::ParamsMap query;\n  query[\"mgm.cmd\"] = \"rm\";\n  query[\"mgm.option\"] = \"r\";\n  query[\"mgm.container.id\"] = std::to_string(md->md_ino());\n  query[\"mgm.uuid\"] = clientuuid;\n  query[\"mgm.retc\"] = \"1\";\n\n  if (req) {\n    query[\"mgm.cid\"] = cap::capx::getclientid(req);\n  }\n\n  query[\"eos.app\"] = get_appname();\n  query[\"fuse.v\"] = std::to_string(FUSEPROTOCOLVERSION);\n\n  if (req) {\n    fusexrdlogin::loginurl(url, query, req, 0);\n  }\n\n  url.SetParams(query);\n  std::unique_ptr <XrdCl::File> file(new XrdCl::File());\n  XrdCl::XRootDStatus status = file->Open(url.GetURL().c_str(),\n                                          XrdCl::OpenFlags::Flags::Read);\n\n  if (status.IsOK()) {\n    return 0;\n  } else {\n    int retc = EREMOTEIO;\n\n    if (status.code == XrdCl::errErrorResponse) {\n      return mapErrCode(status.errNo);\n    } else {\n      return retc;\n    }\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nbackend::putMD(fuse_req_t req, eos::fusex::md* md, std::string authid,\n               XrdSysMutex* locker)\n{\n  fuse_id id(req);\n  return putMD(id, md, authid, locker);\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nbackend::putMD(fuse_id& id, eos::fusex::md* md, std::string authid,\n               XrdSysMutex* locker)\n{\n  XrdCl::URL url;\n  XrdCl::URL::ParamsMap query;\n  bool was_bound = false;\n\n  if (!(id.getid())) {\n    id.bind();\n  } else {\n    was_bound = true;\n  }\n\n  {\n    // update host + port NOW\n    XrdCl::URL lurl(\"root://\" + hostport);\n    id.getid()->url.SetHostPort(lurl.GetHostName(), lurl.GetPort());\n  }\n\n  id.getid()->query[\"eos.app\"] = get_appname();\n  id.getid()->query[\"fuse.v\"] = std::to_string(FUSEPROTOCOLVERSION);\n  id.getid()->url.SetParams(id.getid()->query);\n  eos_static_debug(\"identity bound url=%s was-bound=%d\",\n                   id.getid()->url.GetURL().c_str(), was_bound);\n  // temporary add the authid to be used for that request\n  md->set_authid(authid);\n  md->set_clientuuid(clientuuid);\n  std::string mdstream;\n  eos_static_info(\"proto-serialize\");\n\n  eos::fusex::md tmpmd;\n  // save traffic, don't need to send our list of children to the server\n  tmpmd.mutable_children()->swap(*md->mutable_children());\n  const bool serOk = md->SerializeToString(&mdstream);\n  md->mutable_children()->swap(*tmpmd.mutable_children());\n  if (!serOk) {\n    md->clear_authid();\n    md->clear_clientuuid();\n    md->clear_implied_authid();\n    eos_static_err(\"fatal serialization error\");\n    return EFAULT;\n  }\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"MD:\\n%s\", EosFuse::Instance().mds.dump_md(*md).c_str());\n  }\n\n  md->clear_authid();\n  md->clear_clientuuid();\n  md->clear_implied_authid();\n  locker->UnLock();\n  eos_static_info(\"proto-serialize unlock\");\n  XrdCl::Buffer arg;\n  XrdCl::Buffer* response = 0;\n  std::string prefix = \"/?fusex:\";\n  arg.Append(prefix.c_str(), prefix.length());\n  arg.Append(mdstream.c_str(), mdstream.length());\n  eos_static_debug(\"query: url=%s path=%s length=%d\",\n                   id.getid()->url.GetURL().c_str(),\n                   prefix.c_str(), mdstream.length());\n  XrdCl::XRootDStatus status = Query(id.getid()->url,\n                                     XrdCl::QueryCode::OpaqueFile, arg,\n                                     response, put_timeout);\n  std::unique_ptr<XrdCl::Buffer> rsp(response);\n  eos_static_info(\"sync-response\");\n  eos_static_debug(\"response-size=%d\",\n                   response ? response->GetSize() : 0);\n\n  if (status.IsOK()) {\n    if (response && response->GetBuffer()) {\n      std::string responseprefix;\n\n      if (response->GetSize() > 6) {\n        responseprefix.assign(response->GetBuffer(), 6);\n        // retrieve response\n      } else {\n        eos_static_err(\"protocol error - to short response received\");\n        locker->Lock();\n        return EIO;\n      }\n\n      if (responseprefix != \"Fusex:\") {\n        eos_static_err(\"protocol error - fusex: prefix missing in response\");\n        locker->Lock();\n        return EIO;\n      }\n\n      std::string sresponse;\n      std::string b64response;\n      b64response.assign(response->GetBuffer() + 6, response->GetSize() - 6);\n      eos::common::SymKey::DeBase64(b64response, sresponse);\n      eos::fusex::response resp;\n\n      if (!resp.ParseFromString(sresponse) ||\n          ((resp.type() != resp.ACK) && (resp.type() != resp.NONE))) {\n        eos_static_err(\"parsing error/wrong response type received\");\n        locker->Lock();\n        return EIO;\n      }\n\n      if (resp.type() == resp.ACK) {\n        if (resp.ack_().code() == resp.ack_().OK) {\n          eos_static_info(\"relock do\");\n          locker->Lock();\n\n          if (resp.ack_().md_ino()) {\n            md->set_md_ino(resp.ack_().md_ino());\n          }\n\n          eos_static_debug(\"directory inode %lx => %lx/%lx tid=%lx error='%s'\", md->id(),\n                           md->md_ino(),\n                           resp.ack_().md_ino(), resp.ack_().transactionid(),\n                           resp.ack_().err_msg().c_str());\n          eos_static_info(\"relock done\");\n          return 0;\n        }\n\n        eos_static_err(\"failed query command for ino=%lx error='%s'\", md->id(),\n                       resp.ack_().err_msg().c_str());\n\n        if (EOS_LOGS_DEBUG) {\n          eos_static_err(\"MD:\\n%s\", EosFuse::Instance().mds.dump_md(*md).c_str());\n        }\n\n        locker->Lock();\n        return EIO;\n      }\n\n      if (resp.type() == resp.NONE) {\n        locker->Lock();\n        return 0;\n      }\n    } else {\n      eos_static_err(\"no response retrieved response=%lu response-buffer=%lu\",\n                     response, response ? response->GetBuffer() : 0);\n      locker->Lock();\n      return EIO;\n    }\n\n    locker->Lock();\n    return 0;\n  } else {\n    eos_static_err(\"query resulted in error for ino=%lx url=%s\", md->id(),\n                   id.getid()->url.GetURL().c_str());\n    locker->Lock();\n\n    if (status.code == XrdCl::errErrorResponse) {\n      eos_static_err(\"errno=%i\", status.errNo);\n      return mapErrCode(status.errNo);\n    } else {\n      return EIO;\n    }\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nbackend::doLock(fuse_req_t req,\n                eos::fusex::md& md,\n                XrdSysMutex* locker)\n/* -------------------------------------------------------------------------- */\n{\n  XrdCl::URL url(\"root://\" + hostport);\n  url.SetPath(\"/dummy\");\n  XrdCl::URL::ParamsMap query;\n  fusexrdlogin::loginurl(url, query, req, 0);\n  query[\"fuse.v\"] = std::to_string(FUSEPROTOCOLVERSION);\n  url.SetParams(query);\n  md.set_clientuuid(clientuuid);\n  std::string mdstream;\n  eos_static_info(\"proto-serialize\");\n\n  eos::fusex::md tmpmd;\n  // save traffic, don't need to send our list of children to the server\n  tmpmd.mutable_children()->swap(*md.mutable_children());\n  const bool serOk = md.SerializeToString(&mdstream);\n  md.mutable_children()->swap(*tmpmd.mutable_children());\n  if (!serOk) {\n    md.clear_clientuuid();\n    md.clear_flock();\n    eos_static_err(\"fatal serialization error\");\n    return EFAULT;\n  }\n\n  md.clear_clientuuid();\n  md.clear_flock();\n  locker->UnLock();\n  eos_static_info(\"proto-serialize unlock\");\n  XrdCl::Buffer arg;\n  XrdCl::Buffer* response = 0;\n  std::string prefix = \"/?fusex:\";\n  arg.Append(prefix.c_str(), prefix.length());\n  arg.Append(mdstream.c_str(), mdstream.length());\n  eos_static_debug(\"query: url=%s path=%s length=%d\", url.GetURL().c_str(),\n                   prefix.c_str(), mdstream.length());\n  XrdCl::XRootDStatus status = Query(url, XrdCl::QueryCode::OpaqueFile, arg,\n                                     response);\n  std::unique_ptr<XrdCl::Buffer> rsp(response);\n  eos_static_info(\"sync-response\");\n\n  if (status.IsOK()) {\n    eos_static_debug(\"response=%d response-size=%d\",\n                     response ? true : false, response ? response->GetSize() : 0);\n\n    if (response && response->GetBuffer()) {\n      std::string responseprefix;\n\n      if (response->GetSize() > 6) {\n        responseprefix.assign(response->GetBuffer(), 6);\n        // retrieve response\n      } else {\n        eos_static_err(\"protocol error - to short response received\");\n        locker->Lock();\n        return EIO;\n      }\n\n      if (responseprefix != \"Fusex:\") {\n        eos_static_err(\"protocol error - fusex: prefix missing in response\");\n        locker->Lock();\n        return EIO;\n      }\n\n      std::string sresponse;\n      std::string b64response;\n      b64response.assign(response->GetBuffer() + 6, response->GetSize() - 6);\n      eos::common::SymKey::DeBase64(b64response, sresponse);\n      eos::fusex::response resp;\n\n      if (!resp.ParseFromString(sresponse) || (resp.type() != resp.LOCK)) {\n        eos_static_err(\"parsing error/wrong response type received\");\n        locker->Lock();\n        return EIO;\n      }\n\n      if (resp.ack_().code() == resp.ack_().OK) {\n        eos_static_info(\"relock do\");\n        locker->Lock();\n        (*(md.mutable_flock())) = (resp.lock_());\n        eos_static_debug(\"directory inode %lx => %lx/%lx tid=%lx error='%s'\", md.id(),\n                         md.md_ino(),\n                         resp.ack_().md_ino(), resp.ack_().transactionid(),\n                         resp.ack_().err_msg().c_str());\n        eos_static_info(\"relock done\");\n        return 0;\n      }\n\n      eos_static_err(\"failed query command for ino=%lx error='%s'\", md.id(),\n                     resp.ack_().err_msg().c_str());\n\n      if (EOS_LOGS_DEBUG) {\n        eos_static_err(\"MD:\\n%s\", EosFuse::Instance().mds.dump_md(md).c_str());\n      }\n\n      locker->Lock();\n      return EIO;\n    } else {\n      eos_static_err(\"no response retrieved response=%lu response-buffer=%lu\",\n                     response, response ? response->GetBuffer() : 0);\n      locker->Lock();\n      return EIO;\n    }\n  } else {\n    eos_static_err(\"query resulted in error url=%s\", url.GetURL().c_str());\n  }\n\n  locker->Lock();\n  return EIO;\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\n/* -------------------------------------------------------------------------- */\nbackend::getURL(fuse_req_t req, const std::string& path, std::string cmd,\n                std::string pcmd, std::string op,\n                std::string authid,\n                bool setinline)\n/* -------------------------------------------------------------------------- */\n{\n  XrdCl::URL url(\"root://\" + hostport);\n  url.SetPath(\"/proc/user/\");\n  XrdCl::URL::ParamsMap query;\n  query[\"mgm.cmd\"] = cmd;\n  query[\"mgm.pcmd\"] = pcmd;\n  query[\"mgm.clock\"] = \"0\";\n  query[\"mgm.path\"] = eos::common::StringConversion::curl_escaped(mount + path);\n  query[\"mgm.op\"] = op;\n  query[\"mgm.uuid\"] = clientuuid;\n\n  if (setinline) {\n    query[\"mgm.inline\"] = \"1\";\n  }\n\n  if (req) {\n    query[\"mgm.cid\"] = cap::capx::getclientid(req);\n  }\n\n  query[\"eos.app\"] = get_appname();\n\n  if (authid.length()) {\n    query[\"mgm.authid\"] = authid;\n  }\n\n  query[\"fuse.v\"] = std::to_string(FUSEPROTOCOLVERSION);\n\n  if (req) {\n    fusexrdlogin::loginurl(url, query, req, 0);\n  }\n\n  url.SetParams(query);\n  return url.GetURL();\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\n/* -------------------------------------------------------------------------- */\nbackend::getURL(fuse_req_t req, uint64_t inode, const std::string& name,\n                std::string cmd,\n                std::string pcmd, std::string op, std::string authid, bool setinline)\n{\n  XrdCl::URL url(\"root://\" + hostport);\n  url.SetPath(\"/proc/user/\");\n  XrdCl::URL::ParamsMap query;\n  query[\"mgm.cmd\"] = cmd;\n  query[\"mgm.pcmd\"] = pcmd;\n  query[\"mgm.clock\"] = \"0\";\n  query[\"mgm.child\"] = eos::common::StringConversion::curl_escaped(name);\n  char hexinode[32];\n  snprintf(hexinode, sizeof(hexinode), \"%08lx\", (unsigned long) inode);\n  query[\"mgm.inode\"] =\n    hexinode;\n  query[\"mgm.op\"] = op;\n  query[\"mgm.uuid\"] = clientuuid;\n  query[\"eos.app\"] = get_appname();\n\n  if (authid.length()) {\n    query[\"mgm.authid\"] = authid;\n  }\n\n  query[\"mgm.cid\"] = cap::capx::getclientid(req);\n\n  if (setinline) {\n    query[\"mgm.inline\"] = \"1\";\n  }\n\n  query[\"fuse.v\"] = std::to_string(FUSEPROTOCOLVERSION);\n  fusexrdlogin::loginurl(url, query, req, inode);\n  url.SetParams(query);\n  return url.GetURL();\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\n/* -------------------------------------------------------------------------- */\nbackend::getURL(fuse_req_t req, uint64_t inode, uint64_t clock, std::string cmd,\n                std::string pcmd, std::string op,\n                std::string authid, bool setinline)\n/* -------------------------------------------------------------------------- */\n{\n  XrdCl::URL url(\"root://\" + hostport);\n  url.SetPath(\"/proc/user/\");\n  XrdCl::URL::ParamsMap query;\n  std::string sclock;\n  query[\"mgm.cmd\"] = cmd;\n  query[\"mgm.pcmd\"] = pcmd;\n  query[\"mgm.clock\"] =\n    eos::common::StringConversion::GetSizeString(sclock,\n        (unsigned long long) clock);\n  char hexinode[32];\n  snprintf(hexinode, sizeof(hexinode), \"%08lx\", (unsigned long) inode);\n  query[\"mgm.inode\"] =\n    hexinode;\n  query[\"mgm.op\"] = op;\n  query[\"mgm.uuid\"] = clientuuid;\n  query[\"eos.app\"] = get_appname();\n\n  if (authid.length()) {\n    query[\"mgm.authid\"] = authid;\n  }\n\n  query[\"mgm.cid\"] = cap::capx::getclientid(req);\n\n  if (setinline) {\n    query[\"mgm.inline\"] = \"1\";\n  }\n\n  query[\"fuse.v\"] = std::to_string(FUSEPROTOCOLVERSION);\n  fusexrdlogin::loginurl(url, query, req, inode);\n  url.SetParams(query);\n  return url.GetURL();\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nbackend::statvfs(fuse_req_t req,\n                 struct statvfs* stbuf\n                )\n/* -------------------------------------------------------------------------- */\n{\n  XrdCl::URL url(\"root://\" + hostport);\n  url.SetPath(\"/\");\n  XrdCl::URL::ParamsMap query;\n  std::string sclock;\n  query[\"mgm.pcmd\"] = \"statvfs\";\n  query[\"eos.app\"] = get_appname();\n  query[\"path\"] = \"/\";\n  query[\"fuse.v\"] = std::to_string(FUSEPROTOCOLVERSION);\n  fusexrdlogin::loginurl(url, query, req, 0);\n  url.SetParams(query);\n  std::string sarg = url.GetPathWithParams();\n  static unsigned long long a1 = 0;\n  static unsigned long long a2 = 0;\n  static unsigned long long a3 = 0;\n  static unsigned long long a4 = 0;\n  // ---------------------------------------------------------------------------\n  // statfs caching around 10s\n  // ---------------------------------------------------------------------------\n  static XrdSysMutex statmutex;\n  static time_t laststat = 0;\n  errno = 0;\n  {\n    XrdSysMutexHelper sLock(statmutex);\n\n    if ((time(NULL) - laststat) < (15 + eos::common::getRandom<int>(0, 4))) {\n      stbuf->f_bsize = 4096;\n      stbuf->f_frsize = 4096;\n      stbuf->f_blocks = a3 / 4096;\n      stbuf->f_bfree = a1 / 4096;\n      stbuf->f_bavail = a1 / 4096;\n      stbuf->f_files = a4;\n      stbuf->f_ffree = a2;\n      stbuf->f_fsid = 0xcafe;\n      stbuf->f_namemax = 1024;\n      eos_static_info(\"not calling %s\\n\", url.GetURL().c_str());\n      return errno;\n    }\n  }\n  XrdCl::Buffer arg;\n  arg.FromString(sarg);\n  XrdCl::Buffer* response = 0;\n  XrdCl::XRootDStatus status = Query(url, XrdCl::QueryCode::OpaqueFile, arg,\n                                     response, 2, true);\n  eos_static_info(\"calling %s\\n\", url.GetURL().c_str());\n  std::unique_ptr<XrdCl::Buffer> rsp(response);\n\n  if (status.IsOK() && response && response->GetBuffer()) {\n    int retc;\n    char tag[1024];\n\n    if (!response->GetBuffer()) {\n      eos_static_err(\"no response received to statvfs call\");\n      errno = EACCES;\n      return errno;\n    }\n\n    XrdSysMutexHelper sLock(statmutex);\n    int items = sscanf(response->GetBuffer(),\n                       \"%s retc=%d f_avail_bytes=%llu f_avail_files=%llu \"\n                       \"f_max_bytes=%llu f_max_files=%llu\",\n                       tag, &retc, &a1, &a2, &a3, &a4);\n\n    if ((items != 6) || (strcmp(tag, \"statvfs:\"))) {\n      eos_static_err(\"malformed response received to statvfs call\");\n      errno = EACCES;\n      return errno;\n    }\n\n    errno = retc;\n    laststat = time(NULL);\n    stbuf->f_bsize = 4096;\n    stbuf->f_frsize = 4096;\n    stbuf->f_blocks = a3 / 4096;\n    stbuf->f_bfree = a1 / 4096;\n    stbuf->f_bavail = a1 / 4096;\n    stbuf->f_files = a4;\n    stbuf->f_ffree = a2;\n    stbuf->f_namemax = 1024;\n    eos_static_debug(\"vol=%lu ino=%lu\", a1, a4);\n  } else {\n    errno = EACCES;\n    ;\n  }\n\n  return errno;\n}\n\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nbackend::getChecksum(fuse_req_t req,\n                     uint64_t inode,\n                     std::string& checksum_return)\n/* -------------------------------------------------------------------------- */\n{\n  fuse_id id(req);\n  XrdCl::URL url(\"root://\" + hostport);\n  std::string path = \"ino:\";\n  char sino[64];\n  snprintf(sino, sizeof(sino), \"%lx\", inode);\n  path += sino;\n  url.SetPath(\"/\");\n  XrdCl::URL::ParamsMap query;\n  fusexrdlogin::loginurl(url, query, id.uid, id.gid, id.pid, 0);\n  query[\"eos.app\"] = get_appname();\n  query[\"mgm.pcmd\"] = \"checksum\";\n  query[\"eos.lfn\"] = path;\n  query[\"mgm.option\"] = \"fuse\";\n  url.SetParams(query);\n  std::string sarg = url.GetPathWithParams();\n  XrdCl::Buffer arg;\n  arg.FromString(sarg);\n  XrdCl::Buffer* response = 0;\n  eos_static_debug(\"query: url=%s\", url.GetURL().c_str());\n  XrdCl::XRootDStatus status = Query(url, XrdCl::QueryCode::OpaqueFile, arg,\n                                     response, put_timeout);\n  std::unique_ptr<XrdCl::Buffer> rsp(response);\n  eos_static_info(\"sync-response\");\n  eos_static_debug(\"response-size=%d\",\n                   response ? response->GetSize() : 0);\n\n  if (status.IsOK()) {\n    if (response && response->GetBuffer()) {\n      std::string checksum_response;\n      checksum_response.assign(response->GetBuffer(), response->GetSize());\n      eos_static_debug(\"response=%s\", checksum_response.c_str());\n      char checksum[1024]; // there should be no checksum with length 1024 bytes... unless you have corruption\n      int retc = 0;\n      size_t items = sscanf(checksum_response.c_str(), \"checksum: %1023s retc=%i\",\n                            checksum, &retc);\n\n      if (items != 2) {\n        size_t items = sscanf(checksum_response.c_str(), \"checksum:  retc=%i\", &retc);\n\n        if (items == 1) {\n          if (retc == ENOENT) {\n            // an old server might not be able to call getChecksum by file id, we return an empty one in that case\n            checksum_return = \"unknown\";\n          } else {\n            return retc;\n          }\n        } else {\n          return ENODATA;\n        }\n      } else {\n        if (retc) {\n          if (retc == ENOENT) {\n            checksum_return = \"unknown\";\n          } else {\n            return ENODATA;\n          }\n        } else {\n          checksum_return = checksum;\n        }\n      }\n    }\n\n    return 0;\n  } else {\n    eos_static_err(\"query resulted in error for ino=%lx url=%s rc=%d\", inode,\n                   url.GetURL().c_str(),\n                   (status.code == XrdCl::errErrorResponse) ? mapErrCode(status.errNo) : EIO);\n\n    if (status.code == XrdCl::errErrorResponse) {\n      return mapErrCode(status.errNo);\n    } else {\n      return EIO;\n    }\n  }\n}\n\n\n\n/* -------------------------------------------------------------------------- */\nXrdCl::XRootDStatus\n/* -------------------------------------------------------------------------- */\nbackend::Query(XrdCl::URL& url, XrdCl::QueryCode::Code query_code,\n               XrdCl::Buffer& arg, XrdCl::Buffer*& response, uint16_t rtimeout,\n               bool noretry)\n/* -------------------------------------------------------------------------- */\n{\n  // this function retries queries until the given timeout period has been reached\n  // it does not proceed if there is an authentication failure\n  double total_exec_time_sec = 0;\n  std::unique_ptr <XrdCl::FileSystem> fs(new XrdCl::FileSystem(url));\n\n  do {\n    struct timespec ts;\n    eos::common::Timing::GetTimeSpec(ts, true);\n    XrdCl::XRootDStatus status;\n    status = fs->Query(XrdCl::QueryCode::OpaqueFile, arg, response, rtimeout);\n\n    // we can't do anything if we cannot authenticate\n    if (status.code == XrdCl::errAuthFailed) {\n      return status;\n    }\n\n    // we want to report all errors which are not timeout related\n    if (\n      (status.code != XrdCl::errConnectionError) &&\n      (status.code != XrdCl::errSocketTimeout) &&\n      (status.code != XrdCl::errOperationExpired) &&\n      (status.code != XrdCl::errSocketDisconnected)\n    ) {\n      return status;\n    }\n\n    double exec_time_sec = 1.0 * eos::common::Timing::GetCoarseAgeInNs(&ts,\n                           0) / 1000000000.0;\n    total_exec_time_sec += exec_time_sec;\n    eos_static_err(\"query-exec-ms=%.02f sum-query-exec-ms=%.02f ok=%d err=%d fatal=%d status-code=%d err-no=%d\",\n                   exec_time_sec * 1000.0, total_exec_time_sec * 1000.0, status.IsOK(),\n                   status.IsError(), status.IsFatal(), status.code, status.errNo);\n\n    if ((noretry) || (timeout &&\n                      ((total_exec_time_sec + 5) >\n                       timeout))) {\n      std::string sarg = url.GetPathWithParams();\n      eos_static_err(\"giving up query after sum-query-exec-s=%.02f backend-timeout-s=%.02f no-retry=%d url=%s\",\n                     total_exec_time_sec, timeout, noretry, sarg.c_str());\n      return status;\n    }\n\n    std::this_thread::sleep_for(std::chrono::seconds(5));\n    total_exec_time_sec += 5;\n    fs.reset(new XrdCl::FileSystem(url));\n  } while (1);\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\n/* -------------------------------------------------------------------------- */\nbackend::get_appname()\n{\n  if (EosFuse::Instance().mds.supports_appname()) {\n    return EosFuse::Instance().Config().appname;\n  } else {\n    return \"fuse\";\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nbool\n/* -------------------------------------------------------------------------- */\nbackend::use_mdquery()\n{\n  return EosFuse::Instance().mds.supports_mdquery();\n}\n"
  },
  {
    "path": "fusex/backend/backend.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file backend.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief backend IO handling class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_BACKEND_HH_\n#define FUSE_BACKEND_HH_\n\n#include \"common/Logging.hh\"\n#include \"fusex/fusex.pb.h\"\n#include \"misc/FuseId.hh\"\n#include \"llfusexx.hh\"\n#include <XrdCl/XrdClStatus.hh>\n#include <XrdCl/XrdClFile.hh>\n#include <XrdCl/XrdClURL.hh>\n\n#include <sys/statvfs.h>\n\nclass backend\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  backend();\n  virtual ~backend();\n\n  int init(std::string& hostport, std::string& remotemountdir, double& timeout,\n           double& put_timeout);\n\n  int getMD(fuse_req_t req,\n            const std::string& path,\n            std::vector<eos::fusex::container>& cont,\n            bool listing,\n            std::string authid = \"\"\n           );\n\n  int getMD(fuse_req_t req,\n            uint64_t inode,\n            const std::string& name,\n            std::vector<eos::fusex::container>& cont,\n            bool listing,\n            std::string authid = \"\"\n           );\n\n  int getMD(fuse_req_t req,\n            uint64_t inode,\n            uint64_t myclock,\n            std::vector<eos::fusex::container>& cont,\n            bool listing,\n            std::string authid = \"\"\n           );\n\n  int doLock(fuse_req_t req,\n             eos::fusex::md& md,\n             XrdSysMutex* locker);\n\n\n  int fetchResponse(fuse_req_t req,\n                    uint64_t inode,\n                    std::string& url,\n                    std::vector<eos::fusex::container>& cont,\n                    bool cap = false\n                   );\n\n\n  int fetchQueryResponse(uint64_t inode,\n                         std::string& url,\n                         std::vector<eos::fusex::container>& cont\n                        );\n\n  int rmRf(fuse_req_t req, eos::fusex::md* md);\n\n  int putMD(fuse_req_t req, eos::fusex::md* md, std::string authid,\n            XrdSysMutex* locker);\n  int putMD(fuse_id& id, eos::fusex::md* md, std::string authid,\n            XrdSysMutex* locker);\n\n  int getCAP(fuse_req_t req,\n             uint64_t inode,\n             std::vector<eos::fusex::container>& cont\n            );\n\n  int getChecksum(fuse_req_t req,\n                  uint64_t inode,\n                  std::string& checksum);\n\n  void set_clientuuid(std::string& s)\n  {\n    clientuuid = s;\n  }\n\n  int statvfs(fuse_req_t req, struct statvfs* stbuf);\nprivate:\n\n  std::string getURL(fuse_req_t req, const std::string& path,\n                     std::string cmd = \"fuseX\",\n                     std::string pcmd = \"getfusex\",\n                     std::string op = \"GET\", std::string authid = \"\", bool setinline = false);\n  std::string getURL(fuse_req_t req, uint64_t inode, const std::string& name,\n                     std::string cmd = \"fuseX\",\n                     std::string pcmd = \"getfusex\",\n                     std::string op = \"GET\", std::string authid = \"\", bool setinline = false);\n  std::string getURL(fuse_req_t req, uint64_t inode, uint64_t clock,\n                     std::string cmd = \"fuseX\",\n                     std::string pcmd = \"getfusex\",\n                     std::string op = \"GET\", std::string authid = \"\", bool setinline = false);\n\n  std::string hostport;\n  std::string mount;\n  std::string clientuuid;\n  double timeout;\n  double put_timeout;\n\n  int mapErrCode(int retc);\n\n  XrdCl::XRootDStatus Query(XrdCl::URL& url,\n                            XrdCl::QueryCode::Code query_code, XrdCl::Buffer& arg,\n                            XrdCl::Buffer*& repsonse,\n                            uint16_t timeout = 0,\n                            bool noretry = false);\n\n  std::string get_appname();\n  bool use_mdquery();\n\n};\n#endif /* FUSE_BACKEND_HH_ */\n"
  },
  {
    "path": "fusex/benchmark/CMakeLists.txt",
    "content": "# ------------------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Jozsef Makai <jmakai@cern.ch> CERN\n# ------------------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2017 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ninclude_directories(\n  ${CMAKE_SOURCE_DIR})\n\nadd_executable(fusex-benchmark\n  fusex-benchmark.cc\n  ${CMAKE_SOURCE_DIR}/common/ShellExecutor.cc\n  ${CMAKE_SOURCE_DIR}/common/ShellCmd.cc)\n\ntarget_link_libraries(fusex-benchmark\n  EosCommon\n  ${CMAKE_THREAD_LIBS_INIT})\n\ninstall(\n  TARGETS fusex-benchmark\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR})\n\ninstall(\n  PROGRAMS eos-fusex-certify\n  DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR}\n  PERMISSIONS OWNER_READ OWNER_EXECUTE\n              GROUP_READ GROUP_EXECUTE\n              WORLD_READ WORLD_EXECUTE)\n"
  },
  {
    "path": "fusex/benchmark/eos-fusex-certify",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: eos-fusex-certify\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2017 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n\nwdir=\"certify.$RANDOM\";\nmkdir $wdir\ncd $wdir\necho \"====================================================================\"\necho \"--- ... working-dir = $PWD\"\n##################################################\n\nif [ \"$1\" = \"1\" ] || [ \"$1\" = \"\" ]; then\ntime (\nif [ -e \"/usr/sbin/fusex-benchmark\" ]; then\necho \"====================================================================\"\necho \"001 ... fusex-benchmark\"\nmkdir 001-fusex-benchmark\ncd 001-fusex-benchmark\n/usr/sbin/fusex-benchmark >&/tmp/001-fusex-benchmark.log ||  exit 1\ncd ../\nfi\n) || exit 1\nfi\n##################################################\nif [ \"$1\" = \"2a\" ] || [ \"$1\" = \"\" ]; then\ntime (\necho \"====================================================================\"\necho \"002a ... rename-test\"\nmkdir 002-rename\ncd 002-rename\nfor name in `seq 1 1000`; do unlink b 2>/dev/null && echo damn b exists; touch a; mv a b ; stat b>/dev/null; touch a; mv -f a b ; unlink b; ls | grep b && echo failed && exit 2 ; done;  exit 0;\n) || exit 2\nfi\n##################################################\nif [ \"$1\" = \"2b\" ] || [ \"$1\" = \"\" ]; then\ntime (\necho \"====================================================================\"\necho \"002b ... mkdir-rmdir-test\"\nmkdir 002-mkdir-rmdir\ncd 002-mkdir-rmdir\nfor name in `seq 1 1000`; do  mkdir asdf || exit 2; rmdir asdf; done;  exit 0;\n) || exit 2\nfi\n##################################################\nif [ \"$1\" = \"2c\" ] || [ \"$1\" = \"\" ]; then\ntime (\necho \"====================================================================\"\necho \"002c ... rename-overwrite-test\"\n(\n    mkdir 002-rename-overwrite\n    cd 002-rename-overwrite\n    for name in `seq 1 1000`; do\n\techo $name\n\tmkdir a  || exit 1\n\tmkdir a/a\n\tmkdir b\n\tmkdir b/b\n\tmv -T a/a b/b\n\tls -la a/\n\tls -la b/\n\tfind b\n\trm -rf a || exit 9\n\trm -rf b || exit 10\n    done\n) >& /tmp/002c-rename-overwrite-test.log\n) || exit 2\nfi\n##################################################\nif [ \"$1\" = \"3\" ] || [ \"$1\" = \"\" ]; then\ntime (\necho \"====================================================================\"\necho \"003 ... git-clone-test\"\nmkdir 003-git\ncd 003-git\ngit clone https://github.com/gbitzes/quarkdb quarkdb-2 >& /tmp/003-git-clone-test.log || exit 3\ncd quarkdb-2\ngit submodule update --recursive --init >> /tmp/003-git-clone-test.log 2>&1 || exit 3\ncd ../../\n) || exit 3\nfi\n##################################################\nif [ \"$1\" = \"4\" ] || [ \"$1\" = \"\" ]; then\ntime (\necho \"====================================================================\"\necho \"004 ... xrootd-compilation\"\nmkdir 004-xrootd\ncd 004-xrootd\ntar xvzf /var/eos/test/fuse/untar/xrootd.tgz >& /tmp/004-xrootd-compilation.log\ncd xrootd\nmkdir build\ncd build\ncmake3 ../ >> /tmp/004-xrootd-compilation.log 2>&1 || exit 4\ntime make -j 4  >> /tmp/004-xrootd-compilation.log 2>&1 || exit 4\ncd ../../../\n) || exit 4\nfi\n##################################################\nif [ \"$1\" = \"5\" ] || [ \"$1\" = \"\" ]; then\ntime (\necho \"====================================================================\"\necho \"005 ... client-tests\"\nmkdir 005-eosclient-tests\ncd 005-eosclient-tests\ngit clone https://gitlab.cern.ch/dss/eosclient-tests.git >& /tmp/005-eos-clienttests.log\ncd eosclient-tests/\necho \"005a... micro-tests\"\npython run.py --workdir=$PWD microbench >& /tmp/005-eos-clienttests.log 2>&1 || exit 5\ncd functional_tests/\necho \"005b... zlib-compile\"\n./zlib-compile-selftest.sh $PWD >& /tmp/005-eos-clienttests.log 2>&1 || exit 5\necho \"005c... git-clone\"\n./git-clone.sh $PWD  >& /tmp/005-eos-clienttests.log 2>&1 || exit 5\necho \"005d... rsync\"\n./rsync.sh $PWD  >& /tmp/005-eos-clienttests.log 2>&1 || exit 5\necho \"005d... sqlite\"\n./test_sqlite.py $PWD >& /tmp/005-eos-clienttests.log 2>&1|| exit 5\n) || exit 5\nfi\n##################################################\nif [ \"$1\" = \"6\" ] || [ \"$1\" = \"\" ]; then\ntime (\necho \"====================================================================\"\necho \"006 ... eos-rpm-test\"\nmkdir 006-eos-rpm-test\ncd 006-eos-rpm-test\necho \"006 ... clone eos\"\ngit clone https://:@gitlab.cern.ch:8443/dss/eos.git >& /tmp/006-eos-rpm-test.log 2>&1 || exit 6\ncd eos\necho \"006 ... submodule update\"\ngit submodule update --init --recursive >& /tmp/006-eos-rpm-test.log 2>&1 || exit 6\necho \"006 ... cmake\"\nmkdir build\ncd build\nuname -a | grep \".el6.\" >& /dev/null\nif [ $? -eq 0 ] ; then\n    scl enable devtoolset-8 \"cmake3 ../ -DPACKAGEONLY=1\" >& /tmp/006-eos-rpm-test.log 2>&1 || exit 6\nelse\n    scl enable devtoolset-8 \"cmake3 ../ -DPACKAGEONLY=1\" >& /tmp/006-eos-rpm-test.log 2>&1 || exit 6\nfi\n\nmake srpm >& /tmp/006-eos-rpm-test.log 2>&1 || exit 6\nrpmbuild --rebuild --define \"_topdir $(pwd)/build\" --define \"debug_package %{nil}\" SRPMS/eos-*.src.rpm >& /tmp/006-eos-rpm-test.log 2>&1 || exit 6\nfor name in `seq 1 15`; do echo $name / 15 ; sleep 10; done\ncd ../../\nrm -rf eos\n) || exit 6\nfi\n##################################################\nif [ \"$1\" = \"7\" ] || [ \"$1\" = \"\" ]; then\ntime (\necho \"====================================================================\"\necho \"007 ... eos-write-recovery-test\"\nmkdir 007-eos-write-recovery-test\ncd 007-eos-write-recovery-test\necho \"007 ... recovery-on-flush\"\ndd if=/dev/zero of=512M.1\\#err_sim_flush\\# bs=1M count=512 || exit 7\ncat 512M.1\\#err_sim_flush\\# > /dev/null || exit 7\necho \"007 ... recovery-on-flusher\"\ndd if=/dev/zero of=512M.2\\#err_sim_flusher\\# bs=1M count=512 || exit 7\ncat 512M.2\\#err_sim_flusher\\# > /dev/null || exit 7\nrm -rf 512M.1\\#err_sim_flush\\#\nrm -rf 512M.2\\#err_sim_flusher\\#\n) || exit 7\nfi\n##################################################\nif [ \"$1\" = \"8\" ] || [ \"$1\" = \"\" ]; then\ntime (\necho \"====================================================================\"\necho \"008 ... eos-hardlink-tests\"\nmkdir 008-eos-hardlink-tests\ncd 008-eos-hardlink-tests\necho \"008 ... create hardlinks\"\ntouch /tmp/008-eos-hardlink-tests\ncp /etc/passwd .\nfor name in `seq 2 128`; do\n  ln passwd hardlink.$name >> /tmp/008-eos-hardlink-tests\n  link_source=`stat -c %h passwd`;\n  link_target=`stat -c %h hardlink.$name`;\n  ino_source=`stat -c %i passwd`;\n  ino_target=`stat -c %i hardlink.$name`;\n  test \"$name\" == \"$link_source\" || exit 8\n  test \"$name\" == \"$link_target\" || exit 8\n  test \"$ino_source\" == \"$ino_target\" || exit 8\n  echo $name $link_source $link_target >> /tmp/008-eos-hardlink-tests\ndone\necho \"008 ... remove hardlinks\"\nfor name in `seq 128 -1 2`; do\n  link_source=`stat -c %h passwd`;\n  link_target=`stat -c %h hardlink.$name`;\n  echo $name $link_source $link_target >> /tmp/008-eos-hardlink-tests\n  test \"$name\" == \"$link_source\" || exit 8\n  test \"$name\" == \"$link_target\" || exit 8\n  unlink hardlink.$name >> /tmp/008-eos-hardlink-tests\ndone\n\nrm passwd\n) || exit 8\nfi\n\n\nif [ \"$1\" = \"9\" ] ; then\ntime (\necho \"====================================================================\"\necho \"009 ... inlined-files test\"\necho \"    ... this test works only with uncompressed inlined files and max-size= <4096\"\nmkdir 009-eos-inlined-files-tests\ncd 009-eos-inlined-files-tests\necho \"009 creating inlined file\"\ntouch inlined\n# create an inlined file\ndate +%s >> inlined;\n# we should see the inline buffer\ngetfattr --only-values -d -n system.eos.md inlined | grep sys.file.buf || exit 9\necho \". 1\"\n\n# truncate of the limit\ntruncate -s 4097 inlined;\n# file should not be inlined anymore\ngetfattr --only-values -d -n system.eos.md inlined | grep sys.file.buf && exit 9\necho \". 2\"\n\ntruncate -s 0 inlined;\n# file should have an empty inline buffer\ngetfattr --only-values -d -n system.eos.md inlined | grep sys.file.buf || exit 9;\necho \". 3\"\n\n# create an inlined file\ndate +%s >> inlined;\n# we should see the inline buffer\ngetfattr --only-values -d -n system.eos.md inlined | grep sys.file.buf || exit 9\necho \". 4\"\n\nfor name in `seq 1 400`; do\n    date +%s >> inlined ;\ndone\ngetfattr --only-values -d -n system.eos.md inlined | grep sys.file.buf && exit 9\necho \". 6\"\n\n) || exit 9\nfi\n\n\nif [ \"$1\" = \"10\" ] ; then\ntime (\necho \"====================================================================\"\necho \"010 ... non UTF-8 files test\"\nmkdir 010-eos-non-utf8-test\ncd 010-eos-non-utf8-test\necho \"010 creating funny filenames\"\ntouch $'\\007' || exit 10\n\ntouch $'\\xa9' || exit 10\n\necho \"010 stat'ing funny filenames\"\nstat $'\\007' || exit 10\nstat $'\\xa9' || exit 10\n\necho \"010 listinging funny filenames\"\nls | grep $'\\007' || exit 10\nls | grep $'\\xa9' || exit 10\n\necho \"010 deleting funny filenames\"\nrm $'\\007' || exit 10\nrm $'\\xa9' || exit 10\necho \"010 all done\"\n) || exit 10\nfi\n\nif [ \"$1\" = \"11\" ] ; then\ntime (\necho \"====================================================================\"\necho \"011 ... chmod test\"\nmkdir 011-eos-chmod-test\ncd 011-eos-chmod-test\nmkdir newdir || exit 11\nchmod a-w newdir || exit 11\nmkdir newdir/forbidden >& /dev/null\nif [ $? -ne 0 ] ; then echo OK > /dev/null; else exit 11; fi\nchmod a+w newdir || exit 11\nmkdir newdir/forbidden >& /dev/null\nif [ $? -ne 0 ] ; then exit 11; else echo OK > /dev/null; fi\nrmdir newdir/forbidden || exit 11;\nrmdir newdir || exit 11;\n) || exit 11\nfi\n\nif [ \"$1\" = \"12\" ]; then\ntime (\necho \"====================================================================\"\necho \"012 ... rm-rf eternal loop test\"\nmkdir 012-eos-rmrf-test\ncd 012-eos-rmrf-test\nfor name in `seq 1 100`; do mkdir name; rm -rf name; stat name 2>/dev/null  && exit 12;  done; exit 0;\n) || exit 12\nfi\n\nif [ \"$1\" = \"13\" ] ; then\ntime (\necho \"====================================================================\"\necho \"013 ... IO verify 512 files\"\nmkdir 013-eos-ioverify-test\ncd 013-eos-ioverify-test\n\neos-fusex-ioverify $PWD 512 0 >& /tmp/013-eos-clienttests.log\nif [ $? -eq 0 ] ; then echo OK > /dev/null; else exit 13; fi\necho \"3\" > /proc/sys/vm/drop_caches\neos-fusex-ioverify $PWD 512 1000 >> /tmp/013-eos-clienttests.log 2>&1\nif [ $? -eq 0 ] ; then echo OK > /dev/null; else exit 13; fi\nrm -rf pattern.* >> /tmp/032-eos-clienttests.log 2>&1\nif [ $? -eq 0 ] ; then echo OK > /dev/null; else exit 13; fi\n) || exit 13\nfi\n\n\nif [ \"$1\" = \"14\" ] ; then\ntime (\necho \"====================================================================\"\necho \"014 ... flock test\"\nmkdir 014-flock-test\ncd 014-flock-test\n\nFLOCK=$PWD/flocktest\nflock --verbose \"$FLOCK\" -c '/bin/sleep 5' > /dev/null &\nsleep 1;\n# the lock should not work now\nflock --verbose --nonblock \"$FLOCK\" -c '/bin/sleep 5' > /dev/null 2>&1\nif [ $? -ne 0 ]; then echo OK > /dev/null; else exit 14; fi\nsleep 5;\n# the lock should work now\nflock --verbose --nonblock \"$FLOCK\" -c '/bin/sleep 5' > /dev/null\nif [ $? -eq 0 ]; then echo OK > /dev/null; else exit 14; fi\nexit 0;\n) || exit 14;\nfi\n\necho \"====================================================================\"\necho \"... :-) passed !\"\necho \"====================================================================\"\n"
  },
  {
    "path": "fusex/benchmark/fusex-benchmark.cc",
    "content": "#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <dirent.h>\n#include <errno.h>\n#include <sys/types.h>\n#include <vector>\n#include <string>\n#include <set>\n\n#include \"common/ShellCmd.hh\"\n#include \"common/Timing.hh\"\n#include \"common/utils/RandUtils.hh\"\n\n#define LOOP_1 100\n#define LOOP_2 100\n#define LOOP_4 100\n#define LOOP_6 3\n#define LOOP_7 100\n#define LOOP_8 100\n#define LOOP_9 1000\n#define LOOP_10 10000\n#define LOOP_11 100\n#define LOOP_12 10\n#define LOOP_13 10\n#define LOOP_14 100\n#define LOOP_15 100\n#define LOOP_16 100\n#define LOOP_17 1234\n#define LOOP_18 100\n#define LOOP_19 100\n#define LOOP_20 100\n#define LOOP_21 10000\n#define LOOP_22 5\n\nint main(int argc, char* argv[])\n{\n  eos::common::Timing tm(\"Test\");\n  char name[1024];\n  size_t ino = 0;\n  int testno = 0;\n  int test_start = (argc > 1) ? atoi(argv[1]) : 0;\n  int test_stop = (argc > 2) ? atoi(argv[2]) : 999999;\n  // ------------------------------------------------------------------------ //\n  testno = 1;\n  COMMONTIMING(\"test-start\", &tm);\n  struct stat buf;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n\n    for (size_t i = 0; i < LOOP_1; i++) {\n      snprintf(name, sizeof(name), \"test-same\");\n      int fd = creat(name, S_IRWXU);\n\n      if (fd > 0) {\n        close(fd);\n      } else {\n        fprintf(stderr, \"[test=%03d] creat failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n\n      if (stat(name, &buf)) {\n        fprintf(stderr, \"[test=%03d] creation failed i=%lu\\n\", testno, i);\n        exit(testno);\n      } else {\n        if (ino) {\n          if (buf.st_ino == ino) {\n            fprintf(stderr, \"[test=%03d] inode sequence violation i=%lu\\n\", testno, i);\n            exit(testno);\n          }\n        } else {\n          ino = buf.st_ino;\n        }\n      }\n\n      if (unlink(name)) {\n        fprintf(stderr, \"[test=%03d] unlink failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n    }\n\n    COMMONTIMING(\"create-delete-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 2;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n\n    for (size_t i = 0; i < LOOP_2; i++) {\n      snprintf(name, sizeof(name), \"test-mkdir-%04lu\", i);\n\n      if (mkdir(name, S_IRWXU)) {\n        fprintf(stderr, \"[test=%03d] mkdir failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n    }\n\n    COMMONTIMING(\"mkdir-flat-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 3;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n\n    for (size_t i = 0; i < LOOP_2; i++) {\n      snprintf(name, sizeof(name), \"test-mkdir-%04lu\", i);\n\n      if (rmdir(name)) {\n        fprintf(stderr, \"[test=%03d] rmdir failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n    }\n\n    COMMONTIMING(\"rmdir-flat-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 4;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n    ino = 0;\n\n    for (size_t i = 0; i < LOOP_4; i++) {\n      snprintf(name, sizeof(name), \"test-file-%lu\", i);\n      int fd = creat(name, S_IRWXU);\n\n      if (fd < 0) {\n        fprintf(stderr, \"[test=%03d] creat failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n\n      char buffer[4];\n      memcpy(buffer, &i, sizeof(int));\n\n      if (stat(name, &buf)) {\n        fprintf(stderr, \"[test=%03d] creation failed i=%lu\\n\", testno, i);\n        exit(testno);\n      } else {\n        if (ino) {\n          if (buf.st_ino == ino) {\n            fprintf(stderr, \"[test=%03d] inode sequence violation i=%lu\\n\", testno, i);\n            exit(testno);\n          }\n        } else {\n          ino = buf.st_ino;\n        }\n      }\n\n      ssize_t nwrite = pwrite(fd, buffer, 4, i);\n\n      if (nwrite != 4) {\n        fprintf(stderr, \"[test=%03d] pwrite failed %ld i=%lu\\n\", testno, nwrite, i);\n        exit(testno);\n      }\n\n      close(fd);\n    }\n\n    COMMONTIMING(\"create-pwrite-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 5;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n\n    for (size_t i = 0; i < LOOP_4; i++) {\n      snprintf(name, sizeof(name), \"test-file-%lu\", i);\n\n      if (unlink(name)) {\n        fprintf(stderr, \"[test=%03d] unlink failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n    }\n\n    COMMONTIMING(\"delete-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 6;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n\n    for (size_t i = 0; i < LOOP_6; i++) {\n      eos::common::ShellCmd\n      makethedir(\"mkdir -p a/b/c/d/e/f/g/h/i/j/k/1/2/3/4/5/6/7/8/9/0\");\n      eos::common::cmd_status rc = makethedir.wait(5);\n\n      if (rc.exit_code) {\n        fprintf(stderr, \"[test=%03d] mkdir -p failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n\n      eos::common::ShellCmd removethedir(\"rm -rf a/\");\n      rc = removethedir.wait(5);\n\n      if (rc.exit_code) {\n        fprintf(stderr, \"[test=%03d] rm -rf failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n    }\n\n    COMMONTIMING(\"mkdir-p-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 7;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n\n    for (size_t i = 0; i < LOOP_7; i++) {\n      char execline[1024];\n      snprintf(execline, sizeof(execline),\n               \"for name in `seq 1 100`; do echo %lu.$name >> append.%d; done\", i, LOOP_7);\n      eos::common::ShellCmd appendfile(execline);\n      eos::common::cmd_status rc = appendfile.wait(5);\n\n      if (rc.exit_code) {\n        fprintf(stderr, \"[test=%03d] echo >> failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n    }\n\n    char execline[1024];\n    snprintf(execline, sizeof(execline), \"rm -rf append.%d\", LOOP_7);\n    eos::common::ShellCmd removethefile(execline);\n    eos::common::cmd_status rc = removethefile.wait(5);\n\n    if (rc.exit_code) {\n      fprintf(stderr, \"[test=%03d] rm -rf failed \\n\", testno);\n      exit(testno);\n    }\n\n    COMMONTIMING(\"echo-append-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 8;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n    char execline[1024];\n    snprintf(execline, sizeof(execline),\n             \"cp /etc/passwd pwd1 && mv pwd1 pwd2 && stat pwd1 || stat pwd2 && mv pwd2 pwd1 && stat pwd2 || stat pwd1 && rm -rf pwd1\");\n\n    for (size_t i = 0; i < LOOP_8; i++) {\n      eos::common::ShellCmd renames(execline);\n      eos::common::cmd_status rc = renames.wait(5);\n\n      if (rc.exit_code) {\n        fprintf(stderr, \"[test=%03d] circular-rename failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n    }\n\n    COMMONTIMING(\"rename-circular-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 9;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n    snprintf(name, sizeof(name), \"ftruncate\");\n    unlink(name);\n    int fd = open(name, O_CREAT | O_RDWR, S_IRWXU);\n\n    if (fd > 0) {\n      for (size_t i = 0; i < LOOP_9; i++) {\n        int rc = ftruncate(fd, i);\n\n        if (rc) {\n          fprintf(stderr,\n                  \"[test=%03d] failed ftruncate linear truncate i=%lu rc=%d errno=%d\\n\", testno,\n                  i, rc, errno);\n          exit(testno);\n        }\n\n        struct stat buf;\n\n        if (fstat(fd, &buf)) {\n          fprintf(stderr, \"[test=%03d] failed stat linear truncate i=%lu\\n\", testno, i);\n          exit(testno);\n        } else {\n          if ((size_t) buf.st_size != i) {\n            fprintf(stderr, \"[test=%03d] failed size linear truncate i=%lu size=%lu\\n\",\n                    testno, i, buf.st_size);\n            exit(testno);\n          }\n        }\n      }\n\n      close(fd);\n\n      if (unlink(name)) {\n        fprintf(stderr, \"[test=%03d] failed unlink linear truncate \\n\", testno);\n        exit(testno);\n      }\n    }\n\n    COMMONTIMING(\"truncate-expand-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 10;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n    snprintf(name, sizeof(name), \"fjournal\");\n    unlink(name);\n    int fd = open(name, O_CREAT | O_RDWR, S_IRWXU);\n\n    if (fd < 0) {\n      fprintf(stderr, \"[test=%03d] creat failed\\n\", testno);\n      exit(testno);\n    }\n\n    for (int i = 0; i < LOOP_10; i += 2) {\n      ssize_t nwrite = pwrite(fd, &i, 4, (i * 4) + (2 * 1024 * 1024));\n\n      if (nwrite != 4) {\n        fprintf(stderr, \"[test=%03d] failed linear(1) write i=%d\\n\", testno, i);\n        exit(testno);\n      }\n    }\n\n    for (int i = 0; i < LOOP_10; i += 2) {\n      int v;\n      ssize_t nread = pread(fd, &v, 4, (i * 4) + (2 * 1024 * 1024));\n\n      if (nread != 4) {\n        fprintf(stderr, \"[test=%03d] failed linear read i=%d nread=%ld \\n\", testno, i,\n                nread);\n        exit(testno);\n      }\n\n      if (v != i) {\n        fprintf(stderr, \"[test=%03d] inconsistent(1) read i=%d != v=%d\\n\", testno, i,\n                v);\n        exit(testno);\n      }\n    }\n\n    fdatasync(fd);\n\n    for (int i = 1; i < LOOP_10; i += 2) {\n      ssize_t nwrite = pwrite(fd, &i, 4, i * 4 + 2 * 1024 * 1024);\n\n      if (nwrite != 4) {\n        fprintf(stderr, \"[test=%03d] failed linear(2) write i=%d\\n\", testno, i);\n        exit(testno);\n      }\n    }\n\n    for (int i = 0; i < LOOP_10; i += 1) {\n      int v;\n      ssize_t nread = pread(fd, &v, 4, i * 4 + 2 * 1024 * 1024);\n\n      if (nread != 4) {\n        fprintf(stderr, \"[test=%03d] failed linear read i=%d\\n\", testno, i);\n        exit(testno);\n      }\n\n      if (v != i) {\n        fprintf(stderr, \"[test=%03d] inconsistent(2) read i=%d != v=%d\\n\", testno, i,\n                v);\n        exit(testno);\n      }\n    }\n\n    fdatasync(fd);\n    close(fd);\n    unlink(name);\n    COMMONTIMING(\"journal-cache-timing\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 11;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n    eos::common::ShellCmd\n    makethedir(\"dd if=/dev/urandom of=/var/tmp/random bs=1k count=16\");\n    eos::common::cmd_status rc = makethedir.wait(60);\n\n    if (rc.exit_code) {\n      fprintf(stderr, \"[test=%03d] creation of random contents file failed\\n\",\n              testno);\n      exit(testno);\n    }\n\n    for (size_t i = 0; i < LOOP_11; i++) {\n      eos::common::ShellCmd\n      ddcompare(\"dd if=/var/tmp/random of=random bs=1k count=16; diff /var/tmp/random random\");\n      eos::common::cmd_status rc = ddcompare.wait(10);\n\n      if (rc.exit_code) {\n        fprintf(stderr, \"[test=%03d] dd & compare failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n    }\n\n    eos::common::ShellCmd removethefiles(\"rm -rf random /var/tmp/random\");\n    rc = removethefiles.wait(5);\n\n    if (rc.exit_code) {\n      fprintf(stderr, \"[test=%03d] rm -rf failed\\n\", testno);\n      exit(testno);\n    }\n\n    COMMONTIMING(\"dd-diff-16k-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 12;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n    eos::common::ShellCmd\n    makethedir(\"dd if=/dev/urandom of=/var/tmp/random bs=1M count=16\");\n    eos::common::cmd_status rc = makethedir.wait(60);\n\n    if (rc.exit_code) {\n      fprintf(stderr, \"[test=%03d] creation of random contents file failed\\n\",\n              testno);\n      exit(testno);\n    }\n\n    for (size_t i = 0; i < LOOP_12; i++) {\n      eos::common::ShellCmd\n      ddcompare(\"dd if=/var/tmp/random of=random bs=1M count=16; diff /var/tmp/random random\");\n      eos::common::cmd_status rc = ddcompare.wait(10);\n\n      if (rc.exit_code) {\n        fprintf(stderr, \"[test=%03d] dd & compare failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n    }\n\n    eos::common::ShellCmd removethefiles(\"rm -rf random /var/tmp/random\");\n    rc = removethefiles.wait(5);\n\n    if (rc.exit_code) {\n      fprintf(stderr, \"[test=%03d] rm -rf failed\\n\", testno);\n      exit(testno);\n    }\n\n    COMMONTIMING(\"dd-diff-16M-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 13;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    char buffer[1024];\n\n    for (size_t i = 0; i < 1024; ++i) {\n      buffer [i] = (char)(i % 256);\n    }\n\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n\n    for (size_t i = 0; i < LOOP_13; i++) {\n      snprintf(name, sizeof(name), \"test-unlink\");\n      int fd = open(name, O_CREAT | O_RDWR | O_TRUNC, S_IRWXU);\n\n      if (fd < 0) {\n        fprintf(stderr, \"[test=%03d] creat failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n\n      if (stat(name, &buf)) {\n        fprintf(stderr, \"[test=%03d] creation failed i=%lu\\n\", testno, i);\n        exit(testno);\n      } else {\n        // unlinking the file\n        if (unlink(name)) {\n          fprintf(stderr, \"[test=%03d] unlink failed i=%lu\\n\", testno, i);\n          exit(testno);\n        }\n\n        if (!stat(name, &buf)) {\n          fprintf(stderr, \"[test=%03d] file visible after ulink i=%lu\\n\", testno, i);\n          exit(testno);\n        }\n\n        for (size_t i = 0; i < 4000; ++i) {\n          ssize_t nwrite = write(fd, buffer, sizeof(buffer));\n\n          if (nwrite != sizeof(buffer)) {\n            fprintf(stderr, \"[test=%3d] write after unlink failed errno=%d i=%lu\\n\", testno,\n                    errno, i);\n            exit(testno);\n          }\n\n          fstat(fd, &buf);\n\n          if ((errno != 2) || (buf.st_size != (off_t)((i + 1) * sizeof(buffer)))) {\n            fprintf(stderr,\n                    \"[test=%3d] stat after write gives wrong size errno=%d size=%ld i=%lu\\n\",\n                    testno, errno, buf.st_size, i);\n            exit(testno);\n          }\n        }\n\n        for (size_t i = 0; i < 4000; ++i) {\n          memset(buffer, 0, sizeof(buffer));\n          ssize_t nread = pread(fd, buffer, sizeof(buffer), i * sizeof(buffer));\n\n          if (nread != sizeof(buffer)) {\n            fprintf(stderr, \"[test=%3d] read after unlink failed errno=%d i=%lu\\n\", testno,\n                    errno, i);\n            exit(testno);\n          }\n\n          for (size_t l = 0; l < sizeof(buffer); ++l) {\n            if (buffer[l] != ((char)(((size_t) l) % 256))) {\n              fprintf(stderr,\n                      \"[test=%3d] wrong contents for read after unlink i=%lu l=%lu b=%x\\n\", testno, i,\n                      l, buffer[l]);\n              exit(testno);\n            }\n          }\n        }\n\n        memset(&buf, 0, sizeof(struct stat));\n        fstat(fd, &buf);\n\n        if ((errno != 2) || (buf.st_size != 4000 * sizeof(buffer))) {\n          fprintf(stderr,\n                  \"[test=%3d] stat after read gives wrong size errno=%d size=%ld\\n\", testno,\n                  errno, buf.st_size);\n          exit(testno);\n        }\n      }\n\n      close(fd);\n    }\n\n    COMMONTIMING(\"write-unlinked-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 14;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n\n    for (size_t i = 0; i < LOOP_14; i++) {\n      int fd = creat(\"lockme\", S_IRWXU);\n      int lock_rc = lockf(fd, F_LOCK, 0);\n      int tlock_rc = lockf(fd, F_TLOCK, 0);\n      int ulock_rc = lockf(fd, F_ULOCK, 0);\n      int lockagain_rc = lockf(fd, F_LOCK, 0);\n      close(fd);\n      unlink(\"lockme\");\n\n      if (lock_rc | tlock_rc | ulock_rc | lockagain_rc) {\n        fprintf(stderr, \"[test=%3d] %d %d %d %d\\n\", testno, lock_rc, tlock_rc, ulock_rc,\n                lockagain_rc);\n        exit(testno);\n      }\n    }\n\n    COMMONTIMING(\"create-lockf-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 15;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n\n    for (size_t i = 0; i < LOOP_15; i++) {\n      snprintf(name, sizeof(name), \"test-same\");\n      int fd = creat(name, S_IRWXU);\n\n      if (fd > 0) {\n        close(fd);\n      } else {\n        fprintf(stderr, \"[test=%03d] creat failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n\n      if (unlink(name)) {\n        fprintf(stderr, \"[test=%03d] unlink failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n\n      if (symlink(\"../test\", name)) {\n        fprintf(stderr, \"[test=%03d] symlink failed i=%lu errno=%d\\n\", testno, i,\n                errno);\n        exit(testno);\n      }\n\n      if (unlink(name)) {\n        fprintf(stderr, \"[test=%03d] unlink failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n    }\n\n    COMMONTIMING(\"create-symlink-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 16;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n\n    for (size_t i = 0; i < LOOP_16; i++) {\n      snprintf(name, sizeof(name), \"test-same\");\n\n      if (mkdir(name, S_IRWXU)) {\n        fprintf(stderr, \"[test=%03d] mkdir failed i=%lu errno=%d\\n\", testno, i, errno);\n        exit(testno);\n      }\n\n      if (rmdir(name)) {\n        fprintf(stderr, \"[test=%03d] unlink failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n\n      if (symlink(\"../test\", name)) {\n        fprintf(stderr, \"[test=%03d] symlink failed i=%lu errno=%d\\n\", testno, i,\n                errno);\n        exit(testno);\n      }\n\n      if (unlink(name)) {\n        fprintf(stderr, \"[test=%03d] unlink failed i=%lu\\n\", testno, i);\n        exit(testno);\n      }\n    }\n\n    COMMONTIMING(\"mkdir-symlink-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 17;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n    std::set<std::string> names;\n    std::set<std::string> found;\n    names.insert(\".\");\n    names.insert(\"..\");\n\n    for (size_t i = 0; i < LOOP_17; i++) {\n      snprintf(name, sizeof(name), \"test-readdir-%lu\", i);\n\n      if (mkdir(name, S_IRWXU)) {\n        fprintf(stderr, \"[test=%03d] mkdir failed i=%lu errno=%d\\n\", testno, i, errno);\n        exit(testno);\n      }\n\n      names.insert(name);\n    }\n\n    DIR* dir = opendir(\".\");\n    struct dirent* rdir = 0;\n    size_t cnt = 0;\n    std::vector<std::string> position;\n    position.resize(LOOP_17 + 2);\n\n    do {\n      //      fprintf(stdout, \"pos=%ld\\n\", telldir(dir));\n      rdir = readdir(dir);\n\n      if (rdir) {\n        //fprintf(stdout, \"pos=%ld name=%s\\n\", telldir(dir), rdir->d_name);\n      } else {\n        //fprintf(stdout, \"EOF pos=%ld\\n\", telldir(dir));\n      }\n\n      off_t offset = telldir(dir);\n      seekdir(dir, offset);\n\n      if (rdir) {\n        position[offset - 1] = rdir->d_name;\n\n        if (found.count(rdir->d_name)) {\n          fprintf(stderr, \"[test=%03d] readdir failed duplicated item got=%s\\n\", testno,\n                  rdir->d_name);\n          exit(testno);\n        }\n\n        if (!names.count(rdir->d_name)) {\n          fprintf(stderr, \"[test=%03d] readdir failed missing item got=%s\\n\", testno,\n                  rdir->d_name);\n          exit(testno);\n        }\n\n        found.insert(rdir->d_name);\n      }\n\n      cnt++;\n    } while (rdir);\n\n    for (size_t i = 0; i < 10 * LOOP_17; i++) {\n      size_t idx = eos::common::getRandom<unsigned long>(0, LOOP_17 - 1);\n      seekdir(dir, idx);\n      rdir = readdir(dir);\n\n      if (rdir) {\n        if (position[idx] != std::string(rdir->d_name)) {\n          fprintf(stderr,\n                  \"[test=%03d] readdir failed inconsistent entry got=%s for index=%lu\\n\", testno,\n                  rdir->d_name, idx);\n          exit(testno);\n        }\n      }\n    }\n\n    // create one more directory\n    mkdir(\"onemore\", S_IRWXU);\n\n    // check the original positions\n    for (size_t i = 0; i < LOOP_17; i++) {\n      size_t idx = LOOP_17;\n      seekdir(dir, idx);\n      rdir = readdir(dir);\n\n      if (rdir) {\n        if (position[idx] != std::string(rdir->d_name)) {\n          fprintf(stderr,\n                  \"[test=%03d] readdir failed inconsistent entry got=%s for index=%lu\\n\", testno,\n                  rdir->d_name, idx);\n          exit(testno);\n        }\n      }\n    }\n\n    seekdir(dir, LOOP_17 + 2);\n    rdir = readdir(dir);\n\n    if (rdir) {\n      if (std::string(\"onemore\") != std::string(rdir->d_name)) {\n        fprintf(stderr,\n                \"[test=%03d] readdir failed to get one new directory in correct position\\n\",\n                testno);\n        exit(testno);\n      }\n\n      // fprintf(stdout, \"got on new position %s\\n\", rdir->d_name);\n    }\n\n    // remove one directory\n    rmdir(position[2].c_str());\n\n    for (size_t i = 0; i < LOOP_17 - 3; i++) {\n      seekdir(dir, i + 3);\n      rdir = readdir(dir);\n\n      if (!rdir) {\n        fprintf(stderr,\n                \"[test=%03d] readdir failed - errno %d\\n\", testno, errno);\n        exit(testno);\n      }\n\n      if (position[2] == std::string(rdir->d_name)) {\n        fprintf(stderr,\n                \"[test=%03d] readdir failed to have correct position after deletion\\n\", testno);\n        exit(testno);\n      }\n    }\n\n    if (dir) {\n      closedir(dir);\n    }\n\n    for (size_t i = 0 ; i < position.size(); ++i) {\n      rmdir(position[i].c_str());\n    }\n\n    rmdir(\"onemore\");\n    COMMONTIMING(\"readdir-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 18;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n    int fd = creat(\"lockme\", S_IRWXU);\n\n    if (ftruncate(fd, 1000)) {\n      fprintf(stderr, \"[test=%3d] errno=%d\\n\", testno, errno);\n      exit(testno);\n    }\n\n    close(fd);\n    fd = open(\"lockme\", 0, 0);\n    struct flock fl;\n    memset(&fl, 0, sizeof(fl));\n    fl.l_type = F_RDLCK;\n    fl.l_whence = SEEK_SET;\n    fl.l_start = 100;\n    fl.l_len = 100;\n    fl.l_pid = 0;\n\n    for (size_t i = 0; i < LOOP_18; i++) {\n      int do_shared_lock  = fcntl(fd, F_SETLKW, &fl);\n\n      if (do_shared_lock == -1) {\n        fprintf(stderr, \"[test=%3d] shared lock failed errno=%d\\n\", testno, errno);\n        exit(testno);\n      }\n    }\n\n    close(fd);\n    unlink(\"lockme\");\n    COMMONTIMING(\"shared-lock-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 19;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n    int fd = creat(\"lockme\", S_IRWXU);\n\n    if (ftruncate(fd, 1000)) {\n      fprintf(stderr, \"[test=%3d] errno=%d\\n\", testno, errno);\n      exit(testno);\n    }\n\n    struct flock fl;\n\n    memset(&fl, 0, sizeof(fl));\n\n    fl.l_type = F_WRLCK;\n\n    fl.l_whence = SEEK_SET;\n\n    fl.l_start = 100;\n\n    fl.l_len = 100;\n\n    fl.l_pid = 0;\n\n    for (size_t i = 0; i < LOOP_19; i++) {\n      int do_shared_lock  = fcntl(fd, F_SETLKW, &fl);\n\n      if (do_shared_lock == -1) {\n        fprintf(stderr, \"[test=%3d] exclusive lock failed errno=%d\\n\", testno, errno);\n        exit(testno);\n      }\n    }\n\n    close(fd);\n    unlink(\"lockme\");\n    COMMONTIMING(\"exclusive-lock-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 20;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n    char buffer[1024];\n    char rbuffer[1024];\n    sprintf(buffer, \"https://git.test.cern.ch\");\n\n    for (size_t i = 0; i < LOOP_20; i++) {\n      int fd = creat(\"config.lock\", S_IRWXU);\n\n      if (fd < 0) {\n        fprintf(stderr, \"[test=%3d] file creation failed errno=%d\\n\", testno, errno);\n        exit(testno);\n      }\n\n      size_t nwrite = (size_t)write(fd, buffer, strlen(buffer) + 1);\n\n      if (nwrite != (strlen(buffer) + 1)) {\n        fprintf(stderr, \"[test=%3d] file write failed - wrote %lu/%lu - errno=%d\\n\",\n                testno, nwrite, strlen(buffer) + 1, errno);\n        fprintf(stderr, \"[test=%3d] iteration=%lu\\n\", testno, i);\n        exit(testno);\n      }\n\n      close(fd);\n\n      if (rename(\"config.lock\", \"config\")) {\n        fprintf(stderr, \"[test=%3d] file rename failed - errno=%d\\n\", testno, errno);\n        fprintf(stderr, \"[test=%3d] iteration=%lu\\n\", testno, i);\n        exit(testno);\n      }\n\n      fd = open(\"config\", 0);\n\n      if (fd < 0) {\n        fprintf(stderr, \"[test=%3d] file open for read failed - errno=%d\\n\", testno,\n                errno);\n        fprintf(stderr, \"[test=%3d] iteration=%lu\\n\", testno, i);\n        exit(testno);\n      }\n\n      memset(rbuffer, 0, 1024);\n      size_t nread = (size_t) read(fd, rbuffer, 1024);\n\n      if (nread != (strlen(buffer) + 1)) {\n        fprintf(stderr, \"[test=%3d] file read failed - read %lu/%lu - errno=%d\\n\",\n                testno, nread, strlen(buffer) + 1, errno);\n        fprintf(stderr, \"[test=%3d] iteration=%lu\\n\", testno, i);\n        exit(testno);\n      }\n\n      if (strncmp(buffer, rbuffer, strlen(buffer) + 1)) {\n        fprintf(stderr, \"[test=%3d] file read wrong contents - read %lu/%lu\", testno,\n                nread, strlen(buffer) + 1);\n        fprintf(stderr, \"[test=%3d] iteration=%lu\\n\", testno, i);\n        exit(testno);\n      }\n\n      close(fd);\n    }\n\n    COMMONTIMING(\"version-rename-loop\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 21;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n    struct stat buf;\n\n    for (size_t i = 0 ; i < LOOP_21; ++i) {\n      ::stat(\"_does_not_exist\", &buf);\n    }\n\n    COMMONTIMING(\"repeat-enoent\", &tm);\n  }\n\n  // ------------------------------------------------------------------------ //\n  testno = 22;\n\n  if ((testno >= test_start) && (testno <= test_stop)) {\n    fprintf(stderr, \">>> test %04d\\n\", testno);\n\n    eos::common::cmd_status rc;\n    eos::common::ShellCmd mktoplev(\"mkdir test22\");\n    rc = mktoplev.wait(5);\n\n    if (rc.exit_code) {\n      fprintf(stderr, \"[test=%03d] toplevel mkdir failed\\n\", testno);\n      exit(testno);\n    }\n\n    for (size_t i1 = 0; i1 < LOOP_22; i1++) {\n      snprintf(name, sizeof(name), \"test22/test-level1-%04lu\", i1);\n      if (mkdir(name, S_IRWXU)) {\n        fprintf(stderr, \"[test=%03d] mkdir level1 failed %s\\n\", testno, name);\n        exit(testno);\n      }\n      for (size_t i2 = 0; i2 < LOOP_22; i2++) {\n        snprintf(name, sizeof(name), \"test22/test-level1-%04lu/test-level2-%04lu\", i1,i2);\n        if (mkdir(name, S_IRWXU)) {\n          fprintf(stderr, \"[test=%03d] mkdir level2 failed %s\\n\", testno, name);\n          exit(testno);\n        }\n        for (size_t i3 = 0; i3 < LOOP_22; i3++) {\n          snprintf(name, sizeof(name), \"test22/test-level1-%04lu/test-level2-%04lu/test-level3-%04lu\", i1,i2,i3);\n          if (mkdir(name, S_IRWXU)) {\n            fprintf(stderr, \"[test=%03d] mkdir level3 failed %s\\n\", testno, name);\n            exit(testno);\n          }\n        }\n      }\n    }\n\n    std::vector<std::unique_ptr<eos::common::ShellCmd>> vcmd;\n    for(size_t i=0;i<5;i++) {\n      vcmd.emplace_back(new eos::common::ShellCmd(\"ls -alR test22 > /dev/null 2>&1\"));\n    }\n    for(auto &c: vcmd) {\n      rc = c->wait(30);\n      if (rc.exit_code) {\n        fprintf(stderr, \"[test=%03d] ls -alR failed\\n\", testno);\n        exit(testno);\n      }\n    }\n\n    eos::common::ShellCmd removedir(\"rm -r test22\");\n    rc = removedir.wait(5);\n    if (rc.exit_code) {\n      fprintf(stderr, \"[test=%03d] rm -r test22 dir failed\\n\", testno);\n      exit(testno);\n    }\n\n    COMMONTIMING(\"concurrent-list-recursive\", &tm);\n  }\n\n  tm.Print();\n  fprintf(stdout, \"realtime = %.02f\\n\", tm.RealTime());\n}\n"
  },
  {
    "path": "fusex/cap/cap.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file cap.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief cap data handling class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"cap/cap.hh\"\n#include \"eosfuse.hh\"\n#include \"md/kernelcache.hh\"\n#include \"misc/MacOSXHelper.hh\"\n#include \"misc/fusexrdlogin.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Timing.hh\"\n#include <google/protobuf/util/json_util.h>\n\n/* -------------------------------------------------------------------------- */\ncap::cap()\n/* -------------------------------------------------------------------------- */\n{\n  mdbackend = 0;\n  mds = 0;\n}\n\n/* -------------------------------------------------------------------------- */\ncap::~cap()\n/* -------------------------------------------------------------------------- */\n{\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\ncap::init(backend* _mdbackend, metad* _metad)\n/* -------------------------------------------------------------------------- */\n{\n  mdbackend = _mdbackend;\n  mds = _metad;\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\n/* -------------------------------------------------------------------------- */\ncap::capx::dump(bool dense)\n/* -------------------------------------------------------------------------- */\n{\n  char sout[16384];\n\n  if (dense) {\n    snprintf(sout, sizeof(sout), \"i=%08lx m=%x c=%s\",\n             (*this)()->id(), (*this)()->mode(), (*this)()->clientid().c_str());\n  } else {\n    snprintf(sout, sizeof(sout),\n             \"id=%#lx mode=%#x vtime=%lu.%lu u=%u g=%u cid=%s auth-id=%s errc=%d maxs=%lu q-node=%16lx ino=%lu vol=%lu\",\n             (*this)()->id(), (*this)()->mode(), (*this)()->vtime(),\n             (*this)()->vtime_ns(), (*this)()->uid(), (*this)()->gid(),\n             (*this)()->clientid().c_str(),\n             (*this)()->authid().c_str(),\n             (*this)()->errc(),\n             (*this)()->max_file_size(),\n             (*this)()->_quota().quota_inode(),\n             (*this)()->_quota().inode_quota(),\n             (*this)()->_quota().volume_quota());\n  }\n\n  return sout;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\ncap::reset()\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper mLock(capmap);\n  XrdSysMutexHelper rLock(revocationLock);\n\n  for (auto it : capmap) {\n    revocationset.insert((*it.second)()->authid());\n  }\n\n  capmap.clear();\n  for (auto &p: capcnt) {\n    mds->set_cap_count(p.first, 0);\n  }\n  capcnt.clear();\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\n/* -------------------------------------------------------------------------- */\ncap::capx::capid(fuse_req_t req, fuse_ino_t ino)\n/* -------------------------------------------------------------------------- */\n{\n  char sid[256];\n  std::string login = fusexrdlogin::xrd_login(req);\n  snprintf(sid, sizeof(sid),\n           \"%lx:%u:%u:%s@%s:%s\",\n           ino,\n           fuse_req_ctx(req)->uid,\n           fuse_req_ctx(req)->gid,\n           login.c_str(),\n           EosFuse::Instance().Config().clienthost.c_str(),\n           EosFuse::Instance().Config().name.c_str()\n          );\n  return sid;\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\n/* -------------------------------------------------------------------------- */\ncap::capx::capid(fuse_ino_t ino, std::string clientid)\n/* -------------------------------------------------------------------------- */\n{\n  char sid[256];\n  snprintf(sid, sizeof(sid),\n           \"%lx:%s\",\n           ino,\n           clientid.c_str()\n          );\n  return sid;\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\n/* -------------------------------------------------------------------------- */\ncap::capx::getclientid(fuse_req_t req)\n/* -------------------------------------------------------------------------- */\n{\n  char sid[256];\n  std::string login = fusexrdlogin::xrd_login(req);\n  snprintf(sid, sizeof(sid),\n           \"%u:%u:%s@%s:%s\",\n           fuse_req_ctx(req)->uid,\n           fuse_req_ctx(req)->gid,\n           login.c_str(),\n           EosFuse::Instance().Config().clienthost.c_str(),\n           EosFuse::Instance().Config().name.c_str()\n          );\n  return sid;\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\n/* -------------------------------------------------------------------------- */\ncap::ls()\n/* -------------------------------------------------------------------------- */\n{\n  std::string listing;\n  XrdSysMutexHelper mLock(capmap);\n\n  for (auto it = capmap.begin(); it != capmap.end(); ++it) {\n    listing += it->second->dump(false);\n    listing += \"\\n\";\n  }\n\n  if (listing.size() > (64 * 1000)) {\n    listing.resize((64 * 1000));\n    listing += \"\\n... (truncated) ...\\n\";\n  }\n\n  char csize[32];\n  snprintf(csize, sizeof(csize), \"# [ %lu caps ]\\n\", capmap.size());\n  listing += csize;\n  return listing;\n}\n\n/* -------------------------------------------------------------------------- */\n/* ----------------------------------------------------------- -------------- */\ncap::shared_cap\ncap::get(fuse_req_t req,\n         fuse_ino_t ino,\n         bool lock)\n{\n  std::string cid = cap::capx::capid(req, ino);\n  std::string clientid = cap::capx::getclientid(req);\n  eos_static_debug(\"inode=%08lx cap-id=%s\", ino, cid.c_str());\n  XrdSysMutexHelper mLock(capmap);\n\n  if (capmap.count(cid)) {\n    shared_cap cap = capmap[cid];\n    return cap;\n  } else {\n    shared_cap cap = std::make_shared<capx>();\n    (*cap)()->set_clientid(clientid);\n    (*cap)()->set_authid(\"\");\n    (*cap)()->set_clientuuid(mds->get_clientuuid());\n    (*cap)()->set_id(ino);\n    (*cap)()->set_uid(fuse_req_ctx(req)->uid);\n    (*cap)()->set_gid(fuse_req_ctx(req)->gid);\n    (*cap)()->set_vtime(0);\n    (*cap)()->set_vtime_ns(0);\n    capmap[cid] = cap;\n    return cap;\n  }\n}\n\n/* -------------------------------------------------------------------------- */\n/* -------------------------------------------------------------------------- */\ncap::shared_cap\ncap::get(fuse_ino_t ino, std::string clientid)\n{\n  std::string cid = cap::capx::capid(ino, clientid);\n  eos_static_debug(\"inode=%08lx cap-id=%s\", ino, cid.c_str());\n  XrdSysMutexHelper mLock(capmap);\n\n  if (capmap.count(cid)) {\n    shared_cap cap = capmap[cid];\n    return cap;\n  } else {\n    shared_cap cap = std::make_shared<capx>();\n    (*cap)()->set_id(0);\n    return cap;\n  }\n}\n\n/* -------------------------------------------------------------------------- */\n/* -------------------------------------------------------------------------- */\nvoid\ncap::store(fuse_req_t req,\n           eos::fusex::cap icap)\n{\n  std::string clientid = cap::capx::getclientid(req);\n  uint64_t id = mds->vmaps().forward(icap.id());\n  std::string cid = cap::capx::capid(req, id); // cid uses the local inode\n  XrdSysMutexHelper mLock(capmap);\n  shared_cap cap = std::make_shared<capx>();\n  (*cap)()->set_clientid(clientid);\n  *cap = icap;\n  (*cap)()->set_id(id);\n  if (!capmap.count(cid)) {\n    capcnt[id]++;\n  }\n  capmap[cid] = cap;\n  eos_static_debug(\"store inode=[r:%lx l:%lx] capid=%s cap: %s\", icap.id(),\n                   id, cid.c_str(), capmap[cid]->dump().c_str());\n}\n\n/* -------------------------------------------------------------------------- */\n/* -------------------------------------------------------------------------- */\nfuse_ino_t\ncap::forget(const std::string& cid)\n{\n  fuse_ino_t inode = 0;\n  {\n    XrdSysMutexHelper mLock(capmap);\n\n    if (capmap.count(cid)) {\n      eos_static_debug(\"forget capid=%s cap: %s\", cid.c_str(),\n                       capmap[cid]->dump().c_str());\n      shared_cap cap = capmap[cid];\n      inode = (*cap)()->id();\n      capmap.erase(cid);\n      if (capcnt.count(inode)) {\n        if (capcnt[inode]<=1) {\n          capcnt.erase(inode);\n        } else {\n          capcnt[inode]--;\n        }\n      }\n      XrdSysMutexHelper rLock(revocationLock);\n      revocationset.insert((*cap)()->authid());\n    } else {\n      eos_static_debug(\"forget capid=%s cap: ENOENT\", cid.c_str());\n    }\n  }\n\n  if (inode) {\n    if (EosFuse::Instance().Config().options.md_kernelcache) {\n      kernelcache::inval_inode(inode, false);\n    }\n  }\n\n  return inode;\n}\n\n/* -------------------------------------------------------------------------- */\n/* -------------------------------------------------------------------------- */\nstd::string\ncap::imply(shared_cap cap,\n           std::string imply_authid,\n           mode_t mode,\n           fuse_ino_t ino)\n{\n  shared_cap implied_cap = std::make_shared<capx>();\n  // assignment below copies eos::fusex::cap protobuf, not the whole capx\n  *implied_cap = *(*cap)();\n  (*implied_cap)()->set_authid(imply_authid);\n  (*implied_cap)()->set_id(ino);\n  (*implied_cap)()->set_vtime((*cap)()->vtime() +\n                              EosFuse::Instance().Config().options.leasetime);\n  std::string clientid = (*cap)()->clientid();\n  std::string cid = capx::capid(ino, clientid);\n  XrdSysMutexHelper mLock(capmap);\n  // TODO: deal with the influence of mode to the cap itself\n  capmap[cid] = implied_cap;\n  return cid;\n}\n\n/* -------------------------------------------------------------------------- */\n/* -------------------------------------------------------------------------- */\ncap::shared_cap\ncap::acquire(fuse_req_t req, fuse_ino_t ino, mode_t mode, bool lock)\n{\n  // the parent of 1 might be 0\n  if (!ino) {\n    ino = 1;\n  }\n\n  std::string cid = cap::capx::capid(req, ino);\n  eos_static_debug(\"inode=%08lx cap-id=%s mode=%x\", ino, cid.c_str(), mode);\n  shared_cap cap = get(req, ino);\n  // avoid we create the same cap concurrently\n  XrdSysMutexHelper cLock(cap->Locker());\n  bool valid = cap->valid();\n\n  int nref = 0;\n  while (!valid && nref<3) {\n    shared_cap cap2 = get(req, ino);\n    if (cap2 != cap) {\n      cLock.UnLock();\n      cap = cap2;\n      cLock.Lock(&cap->Locker());\n      valid = cap->valid();\n      continue;\n    }\n\n    if (refresh(req, cap)) {\n      (*cap)()->set_errc(errno ? errno : EIO);\n      return cap;\n    }\n    nref++;\n\n    cLock.UnLock();\n    cap = get(req, ino);\n    cLock.Lock(&cap->Locker());\n    valid = cap->valid();\n  }\n\n  if (nref > 1) {\n    eos_static_debug(\"inode=%08lx cap-id=%s cap refresh made %d attempts\",\n                     ino, cid.c_str(), nref);\n  }\n\n  if (!cap->satisfy(mode) || !valid) {\n    if (!valid) {\n      eos_static_err(\"msg=\\\"did not succeed in refreshing cap with MGM\\\"\"\n                     \" inode=%08lx cap-id=%s now_time=%lu cap_time=%lu cap_errc=%d\",\n                     ino, cid.c_str(), time(nullptr),\n                     (*cap)()->vtime(), (*cap)()->errc());\n    }\n\n    (*cap)()->set_errc(EPERM);\n  } else {\n    (*cap)()->set_errc(0);\n  }\n\n  eos_static_debug(\"%s\", cap->dump().c_str());\n\n  // stamp latest time of use\n  cap->use();\n  return cap;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ncap::refresh(fuse_req_t req, shared_cap cap)\n/* -------------------------------------------------------------------------- */\n{\n  eos_static_debug(\"inode=%08lx cap-id=%s\", (*cap)()->id(),\n                   (*cap)()->clientid().c_str());\n  // retrieve cap from upstream\n  std::vector<eos::fusex::container> contv;\n  int rc = 0;\n  uint64_t remote_ino = mds->vmaps().backward((*cap)()->id());\n\n  rc = mdbackend->getCAP(req, remote_ino, contv);\n\n  if (!rc) {\n    // decode the cap\n    for (auto it = contv.begin(); it != contv.end(); ++it) {\n      switch (it->type()) {\n      case eos::fusex::container::CAP: {\n        uint64_t id = mds->vmaps().forward(it->cap_().id());\n\n        //XrdSysMutexHelper mLock(cap->Locker());\n        // check if the cap received matches what we think about local mapping\n        if ((*cap)()->id() == id) {\n          store(req, it->cap_());\n          eos_static_debug(\"correct cap received for inode=%#lx\", (*cap)()->id());\n        } else {\n          eos_static_debug(\"wrong cap received for inode=%#lx\", (*cap)()->id());\n          // that is a fatal logical error\n          rc = ENXIO;\n        }\n\n        break;\n      }\n\n      default:\n        eos_static_err(\"msg=\\\"wrong content type received\\\" type=%d\",\n                       it->type());\n      }\n    }\n\n    return rc;\n  }\n\n  if (errno != EPERM) {\n    fuse_id id(req);\n    eos_static_err(\"GETCAP failed with errno=%d for inode=%16x uid=%lu gid=%lu pid=%lu\",\n                   errno, (*cap)()->id(), id.uid, id.gid, id.pid);\n  }\n\n  return rc;\n}\n\n/* -------------------------------------------------------------------------- */\n/* -------------------------------------------------------------------------- */\nbool\ncap::capx::satisfy(mode_t mode)\n{\n  //XrdSysMutexHelper mLock(Locker());\n  if (((mode & (*this)()->mode())) == mode) {\n    eos_static_debug(\"inode=%08lx client-id=%s mode=%x test-mode=%x satisfy=true\",\n                     (*this)()->id(), (*this)()->clientid().c_str(),\n                     (*this)()->mode(), mode);\n    return true;\n  }\n\n  eos_static_debug(\"inode=%08lx client-id=%s mode=%x test-mode=%x satisfy=false\",\n                   (*this)()->id(), (*this)()->clientid().c_str(),\n                   (*this)()->mode(), mode);\n  return false;\n}\n\n/* -------------------------------------------------------------------------- */\n/* -------------------------------------------------------------------------- */\nbool\ncap::capx::valid(bool debug)\n{\n  struct timespec ts;\n  ts.tv_sec = (*this)()->vtime();\n  ts.tv_nsec = (*this)()->vtime_ns();\n\n  if (eos::common::Timing::GetCoarseAgeInNs(&ts, 0) < 0) {\n    if (debug)\n      eos_static_debug(\"inode=%08lx client-id=%s now=%lu vtime=%lu valid=true\",\n                       (*this)()->id(), (*this)()->clientid().c_str(),\n                       time(NULL), (*this)()->vtime());\n\n    return true;\n  } else {\n    if (debug)\n      eos_static_debug(\"inode=%08lx client-id=%s now=%lu vtime=%lu valid=false\",\n                       (*this)()->id(), (*this)()->clientid().c_str(),\n                       time(NULL), (*this)()->vtime());\n\n    return false;\n  }\n}\n\n/* -------------------------------------------------------------------------- */\n/* -------------------------------------------------------------------------- */\ndouble\ncap::capx::lifetime()\n{\n  // call this with this cap locked\n  struct timespec ts;\n  ts.tv_sec = (*this)()->vtime();\n  ts.tv_nsec = (*this)()->vtime_ns();\n  double lifetime = -1.0 * (eos::common::Timing::GetCoarseAgeInNs(&ts,\n                            0)) / 1000000000.0;\n  eos_static_debug(\"inode=%08lx client-id=%s lifetime=%.02f\",\n                   (*this)()->id(), (*this)()->clientid().c_str(), lifetime);\n\n  if (lifetime < 0) {\n    lifetime = 0.000000001;\n  }\n\n  return lifetime;\n}\n\n/* -------------------------------------------------------------------------- */\n/* -------------------------------------------------------------------------- */\nvoid\ncap::capx::invalidate()\n{\n  XrdSysMutexHelper cLock(Locker());\n  (*this)()->set_vtime(0);\n}\n\n/* -------------------------------------------------------------------------- */\n/* -------------------------------------------------------------------------- */\nvoid\ncap::capflush(ThreadAssistant& assistant)\n{\n  while (!assistant.terminationRequested()) {\n    {\n      std::map<std::string, fuse_ino_t> capdelmap;\n      cinodes capdelinodes;\n      cmap flushcaps;\n      // avoid locking a cap while holding capmap lock\n      {\n        XrdSysMutexHelper capLock(capmap);\n        flushcaps = capmap;\n      }\n\n      for (auto it = flushcaps.begin(); it != flushcaps.end(); ++it) {\n        XrdSysMutexHelper cLock(it->second->Locker());\n\n        // make a list of caps to timeout\n        if (!it->second->valid(false)) {\n          capdelmap[it->first] = (*it->second)()->id();\n\n          if (EOS_LOGS_DEBUG) {\n            eos_static_debug(\"expire %s\", it->second->dump().c_str());\n          }\n        }\n      }\n\n      {\n        XrdSysMutexHelper capLock(capmap);\n\n        for (auto it = capdelmap.begin(); it != capdelmap.end(); ++it) {\n          auto cmit = capmap.find(it->first);\n          // requirement on cap reference count is to avoid removing\n          // an invalid entry while it may be being refresh(); our\n          // capcnt would remain correct but we only notify mds on removal\n          if (cmit != capmap.end() && cmit->second.use_count() == 2) {\n            // remove the expired or invalidated by delete caps\n            capmap.erase(cmit);\n            const fuse_ino_t ino = it->second;\n            uint64_t cnt=0;\n            if (capcnt.count(ino)) {\n              if (capcnt[ino]<=1) {\n                capcnt.erase(ino);\n              } else {\n                cnt = --capcnt[ino];\n              }\n            }\n            mds->set_cap_count(it->second, cnt);\n            capdelinodes.insert(it->second);\n          }\n        }\n      }\n\n      for (auto it = capdelinodes.begin(); it != capdelinodes.end(); ++it) {\n        kernelcache::inval_inode(*it, false);\n        // retrieve the md object and if there is no cap reference remove all child files\n        EosFuse::Instance().cleanup(*it);\n      }\n\n      assistant.wait_for(std::chrono::seconds(5));\n    }\n  }\n}\n\n/* -------------------------------------------------------------------------- */\ncap::shared_quota\n/* -------------------------------------------------------------------------- */\ncap::qmap::get(shared_cap cap)\n{\n  XrdSysMutexHelper mLock(this);\n  uint64_t ino = (*cap)()->_quota().quota_inode();\n  char sqid[128];\n  snprintf(sqid, sizeof(sqid), \"%u:%u:%16lx\", (*cap)()->uid(),\n           (*cap)()->gid(), ino);\n  std::string qid = sqid;\n\n  // quota information is shared per uid/gid/quota_inode triple\n  if (this->count(qid)) {\n    shared_quota quota = (*this)[qid];\n\n    // check if we have a newer quota value\n    if ((cap->vtime() > quota->get_vtime()) ||\n        ((cap->vtime() == quota->get_vtime()) &&\n         (cap->vtime_ns() > quota->get_vtime_ns()))) {\n      eos_static_notice(\"updating qnode=%s volume=%lu inodes=%lu\",\n                        sqid, (*quota)()->volume_quota(),\n                        (*quota)()->inode_quota());\n      {\n        XrdSysMutexHelper qLock(quota->Locker());\n        // if there is no open file on that quota node, we can refresh from remote\n        *quota = (*cap)()->_quota();\n      }\n      // store latest vtime\n      quota->set_vtime(cap->vtime(), cap->vtime_ns());\n      // zero local accounting\n      quota->local_reset();\n    }\n\n    (*this)[qid] = quota;\n    return quota;\n  } else {\n    shared_quota quota = std::make_shared<quotax>();\n    *quota = (*cap)()->_quota();\n    quota->set_vtime(cap->vtime(), cap->vtime_ns());\n    (*this)[qid] = quota;\n    return quota;\n  }\n}\n\nstd::string\ncap::quotax::dump()\n{\n  google::protobuf::util::JsonPrintOptions options;\n  options.add_whitespace = true;\n#if GOOGLE_PROTOBUF_VERSION >= 5027000\n  options.always_print_fields_with_no_presence = true;\n#else\n  options.always_print_primitive_fields = true;\n#endif\n  std::string jsonstring;\n  {\n    XrdSysMutexHelper qLock(Locker());\n    (void) google::protobuf::util::MessageToJsonString(*((eos::fusex::quota*)((\n          *this)())),\n        &jsonstring, options);\n  }\n  jsonstring.pop_back();\n  jsonstring += \",\\n{\\n  timestamp : \";\n  jsonstring += std::to_string(timestamp());\n  jsonstring += \",\\n\";\n  jsonstring += \"  local-volume : \";\n  jsonstring += std::to_string(local_volume);\n  jsonstring += \",\\n\";\n  jsonstring += \"  local-inodes : \";\n  jsonstring += std::to_string(local_inode);\n  jsonstring += \"\\n}\\n\";\n  return jsonstring;\n}\n"
  },
  {
    "path": "fusex/cap/cap.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file cap.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief cap handling class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_CAP_HH_\n#define FUSE_CAP_HH_\n\n#include \"llfusexx.hh\"\n#include \"md/md.hh\"\n#include \"backend/backend.hh\"\n#include \"fusex/fusex.pb.h\"\n#include \"common/Definitions.hh\"\n#include <XrdSys/XrdSysPthread.hh>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <memory>\n#include <map>\n\nclass cap\n{\npublic:\n  //----------------------------------------------------------------------------\n  // Class quotax\n  //----------------------------------------------------------------------------\n  class quotax\n  {\n  public:\n    quotax() = default;\n\n    // make sure nobody copies us as we some members that will\n    // likely lead to problems if they are copied, e.g. XrdSysMutex\n    quotax(const quotax& other) = delete;\n    quotax& operator=(const quotax& other) = delete;\n\n    virtual ~quotax() = default;\n\n    eos::fusex::quota* operator()()\n    {\n      return &mQuotaProto;\n    }\n\n    XrdSysMutex& Locker()\n    {\n      return mLock;\n    }\n\n    quotax& operator=(eos::fusex::quota other)\n    {\n      mQuotaProto = other;\n      updated();\n      return *this;\n    }\n\n    int writer()\n    {\n      return writer_cnt;\n    }\n    void updated()\n    {\n      last_update = time(NULL);\n    }\n    time_t timestamp()\n    {\n      return last_update;\n    }\n\n    std::string dump();\n    void inc_writer()\n    {\n      writer_cnt++;\n    }\n    void dec_writer()\n    {\n      writer_cnt--;\n    }\n    void inc_inode()\n    {\n      local_inode++;\n    }\n    void dec_inode()\n    {\n      local_inode--;\n    }\n    void inc_volume(uint64_t size)\n    {\n      local_volume += size;\n    }\n    void dec_volume(uint64_t size)\n    {\n      local_volume -= size;\n    }\n    void local_reset()\n    {\n      local_inode = 0;\n      local_volume = 0;\n    }\n    void local_inode_reset()\n    {\n      local_inode = 0;\n    }\n\n    void set_vtime(uint64_t _vt, uint64_t _vt_ns)\n    {\n      vtime = _vt;\n      vtime_ns = _vt_ns;\n    }\n    uint64_t get_vtime() const\n    {\n      return vtime;\n    }\n    uint64_t get_vtime_ns() const\n    {\n      return vtime_ns;\n    }\n    int64_t get_local_inode() const\n    {\n      return local_inode;\n    }\n    int64_t get_local_volume() const\n    {\n      return local_volume;\n    }\n  private:\n    XrdSysMutex mLock;\n    std::atomic<uint64_t> vtime;\n    std::atomic<uint64_t> vtime_ns;\n    std::atomic<int> writer_cnt;\n    std::atomic<int64_t> local_volume;\n    std::atomic<int64_t> local_inode;\n    std::atomic<time_t> last_update;\n    eos::fusex::quota mQuotaProto;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Class capx\n  //----------------------------------------------------------------------------\n  class capx\n  {\n  public:\n    static std::string capid(fuse_req_t req, fuse_ino_t ino);\n    static std::string capid(fuse_ino_t ino, std::string clientid);\n    static std::string getclientid(fuse_req_t req);\n\n    capx() : lastusage(0) { }\n\n    capx(fuse_req_t req, fuse_ino_t ino)\n    {\n      mCapProto.set_id(ino);\n      std::string cid = getclientid(req);\n      mCapProto.set_clientid(cid);\n      mCapProto.set_authid(\"\");\n    }\n\n    // make sure nobody copies us as we some members that will\n    // likely lead to problems if they are copied, e.g. XrdSysMutex\n    capx(const capx& other) = delete;\n    capx& operator=(const capx& other) = delete;\n\n    virtual ~capx() = default;\n\n    eos::fusex::cap* operator()()\n    {\n      return &mCapProto;\n    }\n\n    capx& operator=(eos::fusex::cap other)\n    {\n      mCapProto = other;\n      return *this;\n    }\n\n    XrdSysMutex& Locker()\n    {\n      return mLock;\n    }\n\n    std::string dump(bool dense = false);\n\n    bool satisfy(mode_t mode);\n\n    bool valid(bool debug = true);\n\n    double lifetime();\n\n    void invalidate();\n\n    void use()\n    {\n      lastusage = time(NULL);\n    }\n\n    time_t used() const\n    {\n      return lastusage;\n    }\n\n    uint64_t vtimeTS()\n    {\n      XrdSysMutexHelper cLock(Locker());\n      return mCapProto.vtime();\n    }\n\n    uint64_t vtime_nsTS()\n    {\n      XrdSysMutexHelper cLock(Locker());\n      return mCapProto.vtime_ns();\n    }\n\n    uint64_t vtime()\n    {\n      return mCapProto.vtime();\n    }\n\n    uint64_t vtime_ns()\n    {\n      return mCapProto.vtime_ns();\n    }\n\n  private:\n    XrdSysMutex mLock;\n    time_t lastusage;\n    eos::fusex::cap mCapProto;\n  };\n\n  typedef std::shared_ptr<capx> shared_cap;\n  typedef std::shared_ptr<quotax> shared_quota;\n\n  typedef std::set<fuse_ino_t> cinodes;\n  typedef std::map<std::string, shared_quota> qmap_t;\n\n  //----------------------------------------------------------------------------\n\n  class qmap : public qmap_t, public XrdSysMutex\n  {\n    // map from quota inode to quota information\n  public:\n\n    qmap() { }\n\n    virtual ~qmap() { }\n\n    shared_quota get(shared_cap cap);\n  };\n\n  class cmap : public std::map<std::string, shared_cap>, public XrdSysMutex\n  //----------------------------------------------------------------------------\n  {\n  public:\n\n    cmap() { }\n\n    virtual ~cmap() { }\n  };\n\n  //----------------------------------------------------------------------------\n  cap();\n\n  virtual ~cap();\n\n  shared_cap get(fuse_req_t req,\n                 fuse_ino_t ino,\n                 bool lock = false);\n\n  shared_cap get(fuse_ino_t ino,\n                 std::string clientid\n                );\n\n  shared_cap acquire(fuse_req_t req,\n                     fuse_ino_t ino,\n                     mode_t mode,\n                     bool lock = false\n                    );\n\n  bool share_quotanode(shared_cap cap1, shared_cap cap2)\n  {\n    return ((*cap1)()->_quota().quota_inode() == (*cap2)()->_quota().quota_inode());\n  }\n\n\n  void open_writer_inode(shared_cap cap)\n  {\n    shared_quota q = quotamap.get(cap);\n    q->inc_writer();\n  }\n\n  void close_writer_inode(shared_cap cap)\n  {\n    shared_quota q = quotamap.get(cap);\n    q->dec_writer();\n  }\n\n  void book_inode(shared_cap cap)\n  {\n    shared_quota q = quotamap.get(cap);\n    q->inc_inode();\n    eos_static_debug(\"%s\", q->dump().c_str());\n  }\n\n  void free_inode(shared_cap cap)\n  {\n    shared_quota q = quotamap.get(cap);\n    q->dec_inode();\n    eos_static_debug(\"%s\", q->dump().c_str());\n  }\n\n  void book_volume(shared_cap cap, uint64_t size)\n  {\n    shared_quota q = quotamap.get(cap);\n    q->inc_volume(size);\n    eos_static_debug(\"%s\", q->dump().c_str());\n  }\n\n  void free_volume(shared_cap cap, uint64_t size)\n  {\n    shared_quota q = quotamap.get(cap);\n    q->dec_volume(size);\n    eos_static_debug(\"%s\", q->dump().c_str());\n  }\n\n  uint64_t has_quota(shared_cap cap, uint64_t size)\n  {\n    shared_quota q = quotamap.get(cap);\n    {\n      XrdSysMutexHelper qLock(q->Locker());\n      ssize_t volume = (*q)()->volume_quota() - q->get_local_volume();\n      ssize_t inodes = (*q)()->inode_quota()  - q->get_local_inode();\n\n      if (((volume > 0) && (volume > (ssize_t)size)) &&\n          ((inodes > 0) || (!size))) {\n        return volume;\n      }\n    }\n    // no quota, let's manifest this in the log file, but don't lock the\n    // quota node\n    eos_static_warning(\"no-quota: i=%08lx\\n%s,cap = {%s}\\n\", (*cap)()->id(),\n                       q->dump().c_str(), cap->dump().c_str());\n    return 0;\n  }\n\n  void set_volume_edquota(shared_cap cap)\n  {\n    shared_quota q = quotamap.get(cap);\n    XrdSysMutexHelper qLock(q->Locker());\n\n    if (q) {\n      (*q)()->set_volume_quota(0);\n    }\n  }\n\n  void update_quota(shared_cap cap, const eos::fusex::quota& new_quota)\n  {\n    shared_quota q = quotamap.get(cap);\n    XrdSysMutexHelper qLock(q->Locker());\n    *q = new_quota;\n    q->set_vtime((*cap)()->vtime(), (*cap)()->vtime_ns());\n  }\n\n  shared_quota quota(shared_cap cap)\n  {\n    return quotamap.get(cap);\n  }\n\n  std::string imply(shared_cap cap, std::string imply_authid, mode_t mode,\n                    fuse_ino_t inode);\n\n  fuse_ino_t forget(const std::string& capid);\n\n  void store(fuse_req_t req,\n             eos::fusex::cap cap);\n\n  int refresh(fuse_req_t req, shared_cap cap);\n\n  void init(backend* _mdbackend, metad* _metad);\n\n  void reset();\n\n  void clear()\n  {\n    capmap.clear();\n    capextionsmap.clear();\n    quotamap.clear();\n  }\n\n  std::string ls();\n\n  void capflush(ThreadAssistant& assistant); // thread removing capabilities\n\n  XrdSysMutex& get_revocationLock()\n  {\n    return revocationLock;\n  }\n\n  typedef std::set<std::string> revocation_set_t;;\n\n  size_t size()\n  {\n    XrdSysMutexHelper mLock(capmap);\n    return capmap.size();\n  }\n\n  revocation_set_t& get_revocationmap()\n  {\n    return revocationset;\n  }\n\nprivate:\n\n  std::map<fuse_ino_t, uint64_t> capcnt;\n  cmap capmap;\n  cmap capextionsmap;\n  qmap quotamap;\n\n  backend* mdbackend;\n  metad* mds;\n\n  XrdSysMutex revocationLock;\n  revocation_set_t revocationset; // set containing all authids to revoke\n\n};\n#endif /* FUSE_CAP_HH_ */\n"
  },
  {
    "path": "fusex/data/bufferll.hh",
    "content": "//------------------------------------------------------------------------------\n// File bufferll.hh\n// Author: Andreas Peters <Andreas.Joachim.Peters@cern.ch> CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOS_FUSE_BUFFERLL_HH__\n#define __EOS_FUSE_BUFFERLL_HH__\n\n#include \"common/RWMutex.hh\"\n#include <XrdSys/XrdSysPthread.hh>\n#include <memory>\n#include <vector>\n#include <string.h>\n#include <queue>\n\nusing namespace eos::common;\n\nclass bufferll : public std::vector<char>\n{\npublic:\n\n  bufferll(unsigned size = 0, unsigned capacity = 0)\n  {\n    if (size) {\n      resize(size);\n    }\n\n    if (capacity) {\n      reserve(capacity);\n    }\n\n    mMutex.SetBlockedStackTracing(false);\n    mMutex.SetBlocking(true);\n  }\n\n  virtual\n  ~bufferll() { }\n\n  //------------------------------------------------------------------------\n  //! Add data\n  //------------------------------------------------------------------------\n\n  size_t\n  putData(const void* ptr, size_t dataSize)\n  {\n    RWMutexWriteLock dLock(mMutex);\n    size_t currSize = size();\n    resize(currSize + dataSize);\n    memcpy(&operator[](currSize), ptr, dataSize);\n    return dataSize;\n  }\n\n  off_t\n  writeData(const void* ptr, off_t offset, size_t dataSize)\n  {\n    RWMutexWriteLock dLock(mMutex);\n    size_t currSize = size();\n\n    if ((offset + dataSize) > currSize) {\n      currSize = offset + dataSize;\n      resize(currSize);\n\n      if (currSize > capacity()) {\n        reserve(capacity() * 2);\n      }\n    }\n\n    memcpy(&operator[](offset), ptr, dataSize);\n    return currSize;\n  }\n\n  //------------------------------------------------------------------------\n  //! Retrieve data\n  //------------------------------------------------------------------------\n\n  size_t\n  readData(void* ptr, off_t offset, size_t dataSize)\n  {\n    RWMutexReadLock dLock(mMutex);\n\n    if (offset + dataSize > size()) {\n      if ((size_t) offset > size()) {\n        return 0;\n      }\n\n      dataSize = size() - offset;\n    }\n\n    memcpy(ptr, &operator[](offset), dataSize);\n    return dataSize;\n  }\n\n  //------------------------------------------------------------------------\n  //! peek data ( one has to call release claim aftewards )\n  //------------------------------------------------------------------------\n\n  size_t\n  peekData(char*& ptr, off_t offset, size_t dataSize)\n  {\n    mMutex.LockRead();\n    ptr = &(operator[](0)) + offset;\n    int avail = size() - offset;\n\n    if (((int) dataSize > avail)) {\n      if (avail > 0) {\n        return avail;\n      } else {\n        return 0;\n      }\n    }\n\n    return dataSize;\n  }\n\n  //------------------------------------------------------------------------\n  //! release a lock related to peekData\n  //------------------------------------------------------------------------\n\n  void\n  releasePeek()\n  {\n    mMutex.UnLockRead();\n  }\n\n  //------------------------------------------------------------------------\n  //! truncate a buffer\n  //------------------------------------------------------------------------\n\n  void\n  truncateData(off_t offset)\n  {\n    RWMutexWriteLock dLock(mMutex);\n    resize(offset);\n    reserve(offset);\n  }\n\n  off_t getSize()\n  {\n    RWMutexReadLock dLock(mMutex);\n    return size();\n  }\n\n  void zero()\n  {\n    RWMutexReadLock dLock(mMutex);\n    memset(ptr(), 0, size());\n  }\n\n  //------------------------------------------------------------------------\n  //! low-level pointer to the memory - better know what you do with that\n  //------------------------------------------------------------------------\n\n  char* ptr()\n  {\n    return &(operator[](0));\n  }\n\nprivate:\n  eos::common::RWMutex mMutex;\n};\n\nclass bufferllmanager : public XrdSysMutex\n{\npublic:\n\n  bufferllmanager(size_t _max = 128, size_t _default_size = 128 * 1024)\n  {\n    max = _max;\n    buffersize = _default_size;\n    queued_size = 0;\n    inflight_size = 0;\n  }\n\n  virtual ~bufferllmanager() { }\n\n  typedef std::shared_ptr<bufferll> shared_buffer;\n\n  void configure(size_t _max, size_t _size)\n  {\n    max = _max;\n    buffersize = _size;\n  }\n\n  shared_buffer get_buffer(size_t size)\n  {\n    XrdSysMutexHelper lLock(this);\n    size_t cap_size = (size > buffersize) ? size : buffersize;\n\n    if (!queue.size()) {\n      inflight_size += cap_size;\n      return std::make_shared<bufferll>(cap_size, 0);\n    } else {\n      shared_buffer buffer = queue.front();\n      queued_size -= buffer->capacity();\n      buffer->resize(cap_size);\n      buffer->reserve(cap_size);\n      inflight_size += buffer->capacity();\n      queue.pop();\n      return buffer;\n    }\n  }\n\n  void put_buffer(shared_buffer buffer)\n  {\n    XrdSysMutexHelper lLock(this);\n\n    if (inflight_size >= buffer->capacity()) {\n      inflight_size -= buffer->capacity();\n    } else {\n      inflight_size = 0;\n    }\n\n    if (queue.size() == max) {\n      return;\n    } else {\n      queue.push(buffer);\n      buffer->resize(buffersize);\n      buffer->reserve(buffersize);\n      buffer->shrink_to_fit();\n      buffer->zero();\n      queued_size += buffersize;\n      return;\n    }\n  }\n\n  size_t queued()\n  {\n    XrdSysMutexHelper lLock(this);\n    return queued_size;\n  }\n\n  size_t inflight()\n  {\n    XrdSysMutexHelper lLock(this);\n    return inflight_size;\n  }\n\nprivate:\n  std::queue<shared_buffer> queue;\n  size_t max;\n  size_t buffersize;\n  size_t queued_size;\n  size_t inflight_size;\n};\n#endif\n\n"
  },
  {
    "path": "fusex/data/cache.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file cache.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief cache handler implementation\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"cache.hh\"\n#include \"diskcache.hh\"\n#include \"memorycache.hh\"\n#include \"journalcache.hh\"\n#include \"cachehandler.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Path.hh\"\n#include \"common/StringConversion.hh\"\n#include <unistd.h>\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ncachehandler::init(cacheconfig& _config)\n/* -------------------------------------------------------------------------- */\n{\n  config = _config;\n\n  if ((config.type != cache_t::DISK) &&\n      (config.type != cache_t::MEMORY)) {\n    return EINVAL;\n  }\n\n  if (config.type == cache_t::DISK) {\n    if (diskcache::init(config)) {\n      fprintf(stderr,\n              \"error: cache directory %s or %s cannot be initialized - check existence/permissions!\\n\",\n              config.location.c_str(), config.journal.c_str());\n      return EPERM;\n    }\n  }\n\n  if (config.journal.length()) {\n    if (journalcache::init(config)) {\n      fprintf(stderr,\n              \"error: journal directory %s or %s cannot be initialized - check existence/permissions!\\n\",\n              config.location.c_str(), config.journal.c_str());\n      return EPERM;\n    }\n  }\n\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ncachehandler::init_daemonized()\n/* -------------------------------------------------------------------------- */\n{\n  int rc = 0;\n\n  if (config.type == cache_t::INVALID) {\n    rc = EINVAL;\n    return rc;\n  }\n\n  if (config.type == cache_t::DISK) {\n    rc = diskcache::init_daemonized(config);\n\n    if (rc) {\n      return rc;\n    }\n  }\n\n  if (config.journal.length()) {\n    rc = journalcache::init_daemonized(config);\n\n    if (rc) {\n      return rc;\n    }\n  }\n\n  return rc;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\ncachehandler::logconfig()\n{\n  eos_static_warning(\"data-cache-type        := %s\",\n                     (config.type == cache_t::MEMORY) ? \"memory\" :\n                     \"disk\");\n\n  if (config.type == cache_t::DISK) {\n    eos_static_warning(\"data-cache-location  := %s\",\n                       config.location.c_str());\n    std::string s;\n\n    if (config.total_file_cache_size == 0) {\n      eos_static_warning(\"data-cache-size      := unlimited\");\n    } else {\n      eos_static_warning(\"data-cache-size      := %s\",\n                         eos::common::StringConversion::GetReadableSizeString(s,\n                             config.total_file_cache_size, \"B\"));\n    }\n\n    if (config.per_file_cache_max_size == 0) {\n      eos_static_warning(\"cache-file-size      := unlimited\");\n    } else {\n      eos_static_warning(\"cache-file-max-size  := %s\",\n                         eos::common::StringConversion::GetReadableSizeString(s,\n                             config.per_file_cache_max_size, \"B\"));\n    }\n\n    if (config.journal.length()) {\n      eos_static_warning(\"journal-location     := %s\",\n                         config.journal.c_str());\n\n      if (config.total_file_journal_size == 0) {\n        eos_static_warning(\"journal-cache-size   := unlimited\");\n      } else {\n        eos_static_warning(\"journal-cache-size   := %s\",\n                           eos::common::StringConversion::GetReadableSizeString(s,\n                               config.total_file_journal_size, \"B\"));\n      }\n\n      if (config.per_file_journal_max_size == 0) {\n        eos_static_warning(\"file-journal-max-size:= unlimited\");\n      } else {\n        eos_static_warning(\"file-journal-max-size:= %s\",\n                           eos::common::StringConversion::GetReadableSizeString(s,\n                               config.per_file_journal_max_size, \"B\"));\n      }\n    } else {\n      eos_static_warning(\"journal-location     := disabled\");\n    }\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nshared_io\n/* -------------------------------------------------------------------------- */\ncachehandler::get(fuse_ino_t ino)\n/* -------------------------------------------------------------------------- */\n{\n  std::lock_guard<std::mutex> lock(mtx);\n  auto it = contents.find(ino);\n\n  if (it != contents.end()) {\n    return it->second;\n  }\n\n  shared_io entry = std::make_shared<io>(ino);\n\n  if (inmemory()) {\n    entry->set_file(new memorycache(ino));\n  } else {\n    entry->set_file(new diskcache(ino));\n  }\n\n  if (journaled()) {\n    entry->set_journal(new journalcache(ino));\n  }\n\n  contents[ino] = entry;\n  return entry;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ncachehandler::rm(fuse_ino_t ino)\n/* -------------------------------------------------------------------------- */\n{\n  std::lock_guard<std::mutex> lock(mtx);\n\n  if (contents.count(ino)) {\n    contents.erase(ino);\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "fusex/data/cache.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file cache.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief data cache handling base class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_CACHE_HH_\n#define FUSE_CACHE_HH_\n\n#include <sys/stat.h>\n#include <sys/types.h>\n#include \"llfusexx.hh\"\n#include \"bufferll.hh\"\n#include <XrdSys/XrdSysPthread.hh>\n#include <XrdCl/XrdClFile.hh>\n#include \"xrdclproxy.hh\"\n#include <map>\n#include <string>\n\nclass cache\n{\npublic:\n\n  virtual ~cache() { }\n\n  // base class interface\n  virtual int attach(fuse_req_t req, std::string& cookie, int flags) = 0;\n  virtual int detach(std::string& cookie) = 0;\n  virtual int unlink() = 0;\n\n  virtual ssize_t pread(void* buf, size_t count, off_t offset) = 0;\n  virtual ssize_t pwrite(const void* buf, size_t count, off_t offset) = 0;\n\n  virtual int truncate(off_t) = 0;\n  virtual int sync() = 0;\n\n  virtual size_t size() = 0;\n\n  virtual off_t prefetch_size()\n  {\n    return 0;\n  }\n\n  virtual int set_attr(const std::string& key, const std::string& value) = 0;\n  virtual int attr(const std::string& key, std::string& value) = 0;\n\n  virtual int set_cookie(const std::string& cookie)\n  {\n    return set_attr(\"user.eos.cache.cookie\", cookie);\n  }\n\n  virtual int cookie(std::string& acookie)\n  {\n    return attr(\"user.eos.cache.cookie\", acookie);\n  }\n\n  virtual int rescue(std::string& location)\n  {\n    return 0;\n  }\n\n  virtual int reset()\n  {\n    return 0;\n  }\n\n  virtual int recovery_location(std::string& location)\n  {\n    return 0;\n  }\n};\n\n\n#endif /* FUSE_CACHE_HH_ */\n"
  },
  {
    "path": "fusex/data/cacheconfig.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file cacheconfig.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief cacheconfig class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_CACHECONFIG_HH_\n#define FUSE_CACHECONFIG_HH_\n\nenum cache_t {\n  INVALID, MEMORY, DISK\n};\n\nstruct cacheconfig {\n  cacheconfig()\n  {\n    type = INVALID;\n    total_file_cache_size = total_file_cache_inodes = per_file_cache_max_size =\n                              total_file_journal_size = total_file_journal_inodes = per_file_journal_max_size\n                                  = default_read_ahead_size = max_inflight_read_ahead_buffer_size =\n                                        max_inflight_write_buffer_size = max_read_ahead_size = 0 ;\n    max_read_ahead_blocks = 0;\n    read_ahead_sparse_ratio = 0;\n    clean_threshold = 0;\n    clean_on_startup = false;\n    rescuecache = false;\n  }\n\n  cache_t type;\n  std::string location;\n  uint64_t total_file_cache_size; // total size of the file cache\n  uint64_t total_file_cache_inodes; // max number of inodes in the file cache\n  uint64_t per_file_cache_max_size; // per file maximum file cache size\n  uint64_t total_file_journal_size; // total size of the journal cache\n  uint64_t total_file_journal_inodes; // max number of inodes in the journal cache\n  uint64_t per_file_journal_max_size; // per file maximum journal cache size\n  uint64_t default_read_ahead_size; // default start value for read-ahead\n  uint64_t max_inflight_read_ahead_buffer_size; // max size of read-ahead-buffers\n  uint64_t max_inflight_write_buffer_size; // max size of write buffers\n  uint64_t max_read_ahead_size; // max value for read-ahead block size\n  size_t max_read_ahead_blocks; // max  number of read-ahead blocks\n  float clean_threshold; // filling percentage of the cache disk when we start to delete\n  std::string read_ahead_strategy; // string values 'none', 'static', 'dynamic'\n  float    read_ahead_sparse_ratio; // ratio of sparseness when to disable permanently read-ahead\n  bool  rescuecache; // indicates if journals/cache files are kept with .rescue extension in case of failures\n  std::string journal;\n  bool clean_on_startup; // indicate that the cache is not reusable after restart\n};\n\n#endif\n"
  },
  {
    "path": "fusex/data/cachehandler.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file cachehandler.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief cachehandler class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_CACHEHANDLER_HH_\n#define FUSE_CACHEHANDLER_HH_\n\n#include \"cache.hh\"\n#include \"io.hh\"\n#include \"cacheconfig.hh\"\n#include <mutex>\n\nclass cachehandler\n{\npublic:\n\n  cachehandler() { }\n\n  virtual ~cachehandler() { }\n\n  // static member functions\n\n  static cachehandler&\n  instance()\n  {\n    static cachehandler i;\n    return i;\n  }\n\n  shared_io get(fuse_ino_t ino);\n  int rm(fuse_ino_t ino);\n\n  int init(cacheconfig& config); // called before becoming a daemon\n  int init_daemonized(); // called after becoming a daemon\n  void logconfig();\n\n  bool inmemory()\n  {\n    return (config.type == cache_t::MEMORY);\n  }\n\n  bool journaled()\n  {\n    return (config.journal.length());\n  }\n\n  cacheconfig& get_config()\n  {\n    return config;\n  }\n\nprivate:\n  std::map<fuse_ino_t, shared_io> contents;\n  std::mutex mtx;\n  cacheconfig config;\n};\n\n#endif\n"
  },
  {
    "path": "fusex/data/cachelock.hh",
    "content": "/*\n * cachelock.hh\n *\n *  Created on: May 10, 2017\n *      Author: simonm\n */\n\n#ifndef FUSEX_CACHELOCK_HH_\n#define FUSEX_CACHELOCK_HH_\n\n#include <pthread.h>\n#include <errno.h>\n\n#include <exception>\n\nclass cachelock_error\n{\npublic:\n\n  cachelock_error(int errcode) : errcode(errcode) { }\n\n  cachelock_error(const cachelock_error& err) : errcode(err.errcode) { }\n\n  cachelock_error& operator=(const cachelock_error& err)\n  {\n    errcode = err.errcode;\n    return *this;\n  }\n\n  virtual ~cachelock_error() { }\n\n  virtual const char* what() const throw()\n  {\n    return strerror(errcode);\n  }\n\nprivate:\n\n  int errcode;\n};\n\nclass cachelock\n{\npublic:\n\n  cachelock() : readers(0)\n  {\n    int rc = 0;\n    rc = pthread_cond_init(&cvar, NULL);\n\n    if (rc) {\n      throw cachelock_error(rc);\n    }\n\n    rc = pthread_cond_init(&rwvar, NULL);\n\n    if (rc) {\n      throw cachelock_error(rc);\n    }\n\n    rc = pthread_mutex_init(&mtx, NULL);\n\n    if (rc) {\n      throw cachelock_error(rc);\n    }\n  }\n\n  ~cachelock()\n  {\n    pthread_cond_destroy(&cvar);\n    pthread_cond_destroy(&rwvar);\n    pthread_mutex_destroy(&mtx);\n  }\n\n  void read_lock()\n  {\n    lock(mtx);\n    ++readers;\n    unlock(mtx);\n  }\n\n  void read_unlock()\n  {\n    lock(mtx);\n    --readers;\n\n    if (readers == 0) {\n      broadcast(rwvar, mtx);\n    }\n\n    unlock(mtx);\n  }\n\n  void read_wait()\n  {\n    lock(mtx);\n    --readers;\n    wait(cvar, mtx);\n    ++readers;\n    unlock(mtx);\n  }\n\n  void write_lock()\n  {\n    lock(mtx);\n\n    while (readers > 0) {\n      wait(rwvar, mtx);\n    }\n  }\n\n  void write_unlock()\n  {\n    unlock(mtx);\n  }\n\n  void write_wait()\n  {\n    wait(cvar, mtx);\n\n    while (readers > 0) {\n      wait(rwvar, mtx);\n    }\n  }\n\n  void broadcast()\n  {\n    broadcast(cvar, mtx);\n  }\n\nprivate:\n\n  static void lock(pthread_mutex_t& mtx)\n  {\n    int rc = pthread_mutex_lock(&mtx);\n\n    if (rc) {\n      throw cachelock_error(rc);\n    }\n  }\n\n  static void unlock(pthread_mutex_t& mtx)\n  {\n    int rc = pthread_mutex_unlock(&mtx);\n\n    if (rc) {\n      throw cachelock_error(rc);\n    }\n  }\n\n  static void wait(pthread_cond_t& var, pthread_mutex_t& mtx)\n  {\n    int rc = pthread_cond_wait(&var, &mtx);\n\n    if (rc) {\n      pthread_mutex_unlock(&mtx);\n      throw cachelock_error(rc);\n    }\n  }\n\n  static void broadcast(pthread_cond_t& var, pthread_mutex_t& mtx)\n  {\n    int rc = pthread_cond_broadcast(&var);\n\n    if (rc) {\n      pthread_mutex_unlock(&mtx);\n      throw cachelock_error(rc);\n    }\n  }\n\n  size_t readers;\n  pthread_cond_t cvar;\n  pthread_cond_t rwvar;\n  pthread_mutex_t mtx;\n};\n\nclass read_lock\n{\npublic:\n\n  read_lock(cachelock& lck) : lck(lck)\n  {\n    lck.read_lock();\n  }\n\n  ~read_lock()\n  {\n    try {\n      lck.read_unlock();\n    } catch (const cachelock_error& ex) {\n    }\n  }\n\nprivate:\n\n  cachelock& lck;\n};\n\nclass write_lock\n{\npublic:\n\n  write_lock(cachelock& lck) : lck(lck)\n  {\n    lck.write_lock();\n  }\n\n  ~write_lock()\n  {\n    try {\n      lck.write_unlock();\n    } catch (const cachelock_error& ex) {\n    }\n  }\n\nprivate:\n\n  cachelock& lck;\n};\n\n#endif /* FUSEX_CACHELOCK_HH_ */\n"
  },
  {
    "path": "fusex/data/cachesyncer.cc",
    "content": "/*\n * cachesyncer.cc\n *\n *  Created on: May 10, 2017\n *      Author: simonm\n */\n\n#include \"cachesyncer.hh\"\n#include \"bufferll.hh\"\n#include <unistd.h>\n\n#include <XrdCl/XrdClXRootDResponses.hh>\n#include <XrdSys/XrdSysPthread.hh>\n\n#include <algorithm>\n#include <vector>\n\nclass CollectiveHandler : public XrdCl::ResponseHandler\n{\npublic:\n\n  CollectiveHandler(size_t count) : count(count), sem(0), result(true)\n  {\n  }\n\n  virtual void HandleResponse(XrdCl::XRootDStatus* status,\n                              XrdCl::AnyObject* response)\n  {\n    Report(status);\n  }\n\n  void Wait()\n  {\n    sem.Wait();\n  }\n\n  void Report(XrdCl::XRootDStatus* status)\n  {\n    XrdSysMutexHelper scope(mtx);\n    result &= status->IsOK();\n    delete status;\n    --count;\n\n    if (count == 0) {\n      scope.UnLock();\n      sem.Post();\n    }\n  }\n\n  bool WasSuccessful()\n  {\n    return result;\n  }\n\nprivate:\n\n  size_t count;\n  XrdSysMutex mtx;\n  XrdSysSemaphore sem;\n  bool result;\n};\n\nint cachesyncer::sync(int fd, interval_tree<uint64_t,\n                      uint64_t>& journal,\n                      size_t offshift,\n                      off_t truncatesize)\n{\n  if (!journal.size() && (truncatesize == -1)) {\n    return 0;\n  }\n\n  const size_t ntot = journal.size();\n  const size_t nbatch = 256;\n  size_t nsub = 0;\n  auto itr = journal.begin();\n  bool needtrunc = (truncatesize != -1);\n\n  while(nsub<ntot || needtrunc) {\n    const size_t n = std::min(ntot - nsub, nbatch);\n    const bool dotrunc = (n < nbatch && needtrunc);\n    CollectiveHandler handler(n + (dotrunc ? 1 : 0));\n\n    std::map<size_t, bufferll> bufferm;\n    size_t i = 0;\n    while(i<n) {\n      off_t cacheoff = itr->value + offshift;\n      size_t size = itr->high - itr->low;\n      bufferm[i].resize(size);\n      int bytesRead = pread(fd, bufferm[i].ptr(), size, cacheoff);\n\n      if (bytesRead < 0) {\n        // TODO handle error\n        return -1;\n      }\n\n      if (bytesRead < (int) size) {\n        // TODO handle error\n      }\n\n      // do async write\n      XrdCl::XRootDStatus st = file.Write(itr->low, size, bufferm[i].ptr(), &handler);\n      ++nsub;\n\n      if (!st.IsOK()) {\n        handler.Report(new XrdCl::XRootDStatus(st));\n      }\n\n      ++i;\n      ++itr;\n    }\n\n    // there might be a truncate call after the writes to be applied\n    if (dotrunc) {\n      XrdCl::XRootDStatus st = file.Truncate(truncatesize);\n      handler.Report(new XrdCl::XRootDStatus(st));\n      needtrunc = false;\n    }\n\n    handler.Wait();\n    int rc = handler.WasSuccessful() ? 0 : -1;\n    if (rc) return rc;\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "fusex/data/cachesyncer.hh",
    "content": "/*\n * cachesyncer.hh\n *\n *  Created on: May 10, 2017\n *      Author: simonm\n */\n\n#ifndef FUSEX_CACHESYNCER_HH_\n#define FUSEX_CACHESYNCER_HH_\n\n#include \"interval_tree.hh\"\n\n#include <XrdCl/XrdClFile.hh>\n\nclass cachesyncer\n{\npublic:\n\n  /**\n   * We expect a file that has been already opened\n   */\n  cachesyncer(XrdCl::File& file) : file(file) { }\n\n  virtual ~cachesyncer() { }\n\n  int sync(int fd, interval_tree<uint64_t, uint64_t>& journal,\n           size_t offshift,\n           off_t truncatesize = 0);\n\nprivate:\n\n  XrdCl::File& file;\n};\n\n#endif /* FUSEX_CACHESYNCER_HH_ */\n"
  },
  {
    "path": "fusex/data/data.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file data.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief data handling class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"data/data.hh\"\n#include \"kv/kv.hh\"\n#include \"eosfuse.hh\"\n#include \"data/cachesyncer.hh\"\n#include \"data/journalcache.hh\"\n#include \"data/xrdclproxy.hh\"\n#include \"misc/MacOSXHelper.hh\"\n#include \"misc/fusexrdlogin.hh\"\n#include \"misc/filename.hh\"\n#include \"common/Logging.hh\"\n#include \"common/SymKeys.hh\"\n#include <iostream>\n#include <sstream>\n\n\nbufferllmanager data::datax::sBufferManager;\nstd::string data::datax::kInlineAttribute = \"sys.file.buffer\";\nstd::string data::datax::kInlineMaxSize = \"sys.file.inline.maxsize\";\nstd::string data::datax::kInlineCompressor = \"sys.file.inline.compressor\";\n\n/* -------------------------------------------------------------------------- */\ndata::data()\n/* -------------------------------------------------------------------------- */\n{\n}\n\n/* -------------------------------------------------------------------------- */\ndata::~data()\n/* -------------------------------------------------------------------------- */\n{\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\ndata::init()\n/* -------------------------------------------------------------------------- */\n{\n  // configure the ra,rd,wr buffer sizes\n  XrdCl::Proxy::sRaBufferManager.configure(16,\n      cachehandler::instance().get_config().default_read_ahead_size,\n      cachehandler::instance().get_config().max_inflight_read_ahead_buffer_size);\n  XrdCl::Proxy::sWrBufferManager.configure(128,\n      128 * 1024,\n      cachehandler::instance().get_config().max_inflight_write_buffer_size);\n  datamap.run();\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\ndata::terminate(uint64_t seconds)\n/* -------------------------------------------------------------------------- */\n{\n  if (datamap.waitflush(seconds)) {\n    datamap.join();\n  }\n}\n\n\n/* -------------------------------------------------------------------------- */\ndata::shared_data\n/* -------------------------------------------------------------------------- */\ndata::get(fuse_req_t req,\n          fuse_ino_t ino,\n          metad::shared_md md)\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper mLock(datamap);\n\n  if (datamap.count(ino)) {\n    shared_data io = datamap[ino];\n    io->attach(); // client ref counting\n    return io;\n  } else {\n    // protect against running out of file descriptors\n    size_t openfiles = datamap.size();\n    size_t openlimit = (EosFuse::Instance().Config().options.fdlimit - 128) / 2;\n\n    while ((openfiles = datamap.size()) > openlimit) {\n      datamap.UnLock();\n      eos_static_warning(\"open-files=%lu limit=%lu - waiting for release of file descriptors\",\n                         openfiles, openlimit);\n      std::this_thread::sleep_for(std::chrono::milliseconds(1000));\n      datamap.Lock();\n    }\n\n    if (datamap.count(ino)) {\n      // might have been created in the meanwhile\n      shared_data io = datamap[ino];\n      io->attach(); // client ref counting\n      return io;\n    } else {\n      shared_data io = std::make_shared<datax>(md);\n      io->set_id(ino, req);\n      datamap[(fuse_ino_t) io->id()] = io;\n      io->attach();\n      return io;\n    }\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nbool\n/* -------------------------------------------------------------------------- */\ndata::has(fuse_ino_t ino, bool checkwriteopen)\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper mLock(datamap);\n\n  if (datamap.count(ino)) {\n    if (checkwriteopen) {\n      if (datamap[ino]->flags() & (O_RDWR | O_WRONLY)) {\n        return true;\n      } else {\n        return false;\n      }\n    } else {\n      return true;\n    }\n  } else {\n    return false;\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\n/* -------------------------------------------------------------------------- */\ndata::url(fuse_ino_t ino)\n/* -------------------------------------------------------------------------- */\n{\n  std::string p;\n  XrdSysMutexHelper mLock(datamap);\n\n  if (datamap.count(ino)) {\n    p = datamap[ino]->fullpath();\n\n    while (p.find(\"//\") != std::string::npos) {\n      p.replace(p.find(\"//\"), p.size(), \"/\");\n    }\n\n    p += \" [\";\n    p += datamap[ino]->url(true);\n    p += \" ]\";\n  }\n\n  return p;\n}\n\n/* -------------------------------------------------------------------------- */\nmetad::shared_md\ndata::retrieve_wr_md(fuse_ino_t ino)\n/* -------------------------------------------------------------------------- */\n{\n  // return the shared_md  boject if this is a writer\n  XrdSysMutexHelper mLock(datamap);\n\n  if (datamap.count(ino)) {\n    if (datamap[ino]->flags() & (O_RDWR | O_WRONLY)) {\n      return datamap[ino]->md();\n    }\n  }\n\n  return nullptr;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\ndata::release(fuse_req_t req,\n              fuse_ino_t ino, shared_data io)\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper mLock(datamap);\n\n  if (datamap.count(ino) || datamap.count(ino + 0xffffffff)) {\n    io->detach();\n    // the object is cleaned by the flush thread\n  }\n}\n\nvoid\n/* -------------------------------------------------------------------------- */\ndata::update_cookie(uint64_t ino, std::string& cookie)\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper mLock(datamap);\n\n  if (datamap.count(ino)) {\n    shared_data io = datamap[ino];\n    io->attach(); // client ref counting\n    io->store_cookie(cookie);\n    io->detach();\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\ndata::invalidate_cache(fuse_ino_t ino)\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper mLock(datamap);\n\n  if (datamap.count(ino)) {\n    shared_data io = datamap[ino];\n    io->attach(); // client ref counting\n    io->cache_invalidate();\n    io->detach();\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nbool\n/* -------------------------------------------------------------------------- */\ndata::unlink(fuse_req_t req, fuse_ino_t ino)\n/* -------------------------------------------------------------------------- */\n{\n  bool has_data = false;\n  bool is_rw = false;\n  shared_data datap;\n  {\n    XrdSysMutexHelper mLock(datamap);\n    has_data = datamap.count(ino);\n\n    if (has_data) {\n      datap = datamap[ino];\n    }\n  }\n\n  if (has_data) {\n    {\n      XrdSysMutexHelper helper(datap->Locker());\n      // wait for open in flight to be done\n      datap->WaitOpen();\n      datap->unlink(req);\n    }\n    // put the unlinked inode in a high bucket, will be removed by the flush thread\n    {\n      XrdSysMutexHelper mLock(datamap);\n\n      if (datamap.count(ino)) {\n        if (datamap[ino]->flags() & (O_RDWR | O_WRONLY)) {\n          is_rw = true;\n        }\n\n        datamap[ino + 0xffffffff] = datamap[ino];\n        datamap.erase(ino);\n        eos_static_info(\"datacache::unlink size=%lu\", datamap.size());\n      }\n    }\n    return is_rw;\n  } else {\n    shared_data io = std::make_shared<datax>();\n    io->set_id(ino, req);\n    io->unlink(req);\n    return false;\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndata::datax::flush(fuse_req_t req)\n/* -------------------------------------------------------------------------- */\n{\n  eos_info(\"\");\n  XrdSysMutexHelper lLock(mLock);\n  bool flush_wait_open = false;\n\n  if (mFlags & O_CREAT) {\n    flush_wait_open = (EosFuse::Instance().Config().options.flush_wait_open ==\n                       EosFuse::Instance().Config().options.kWAIT_FLUSH_ON_CREATE) ? true : false;\n\n    if ((!flush_wait_open) &&\n        ((*mMd)()->size() >=\n         EosFuse::Instance().Config().options.flush_wait_open_size)) {\n      flush_wait_open = true;\n    }\n\n    if (EosFuse::Instance().Config().options.nowait_flush_executables.size()) {\n      if (!filename::matches_suffix(fusexrdlogin::executable(req),\n                                    EosFuse::Instance().Config().options.nowait_flush_executables)) {\n        eos_notice(\"flush-wait-open: forced for exec=%s\",\n                   fusexrdlogin::executable(req).c_str());\n        flush_wait_open = true;\n      }\n    }\n  } else {\n    flush_wait_open = (EosFuse::Instance().Config().options.flush_wait_open !=\n                       EosFuse::Instance().Config().options.kWAIT_FLUSH_NEVER) ? true : false;\n  }\n\n  if (EOS_LOGS_DEBUG) {\n    eos_notice(\"flush-wait-open: %d size=%lu exec=%s\\n\", flush_wait_open,\n               (*mMd)()->size(), fusexrdlogin::executable(req).c_str());\n  }\n\n  return flush_nolock(req, flush_wait_open, false);\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndata::datax::flush_nolock(fuse_req_t req, bool wait_open, bool wait_writes)\n/* -------------------------------------------------------------------------- */\n{\n  eos_info(\"\");\n  set_shared_url();\n  bool journal_recovery = false;\n  errno = 0;\n\n  if (mFile->journal() && mFile->has_xrdiorw(req)) {\n    eos_info(\"flushing journal\");\n    ssize_t truncate_size = mFile->journal()->get_truncatesize();\n\n    if (wait_open) {\n      // wait at least that we could open that file\n      mFile->xrdiorw(req)->WaitOpen();\n      // set again the shared url now, since we know where we are\n      set_shared_url();\n    }\n\n    if ((truncate_size != -1)\n        || (wait_writes && mFile->journal()->size())) {\n      // if there is a truncate to be done, we have to wait for the writes and truncate\n      // if we are asked to wait for writes (when pwrite sees a journal full) we free the journal\n      for (auto it = mFile->get_xrdiorw().begin();\n           it != mFile->get_xrdiorw().end(); ++it) {\n        XrdCl::XRootDStatus status = it->second->WaitOpen();\n\n        if (!status.IsOK()) {\n          if (status.errNo == kXR_overQuota) {\n            eos_crit(\"flush error errno=%d\", XrdCl::Proxy::status2errno(status));\n            return XrdCl::Proxy::status2errno(status);\n          }\n\n          journal_recovery = true;\n          eos_err(\"file not open\");\n        }\n\n        status = it->second->WaitWrite();\n\n        if (!status.IsOK()) {\n          mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                           \"status='%s' hint='will TryRecovery'\",\n                                           status.ToString().c_str()));\n          journal_recovery = true;\n          eos_err(\"write error error=%s\", status.ToStr().c_str());\n        }\n      }\n\n      ssize_t truncate_size = mFile->journal()->get_truncatesize();\n\n      if (!journal_recovery && (truncate_size != -1)) {\n        // the journal might have a truncation size indicated, so we need to run a sync truncate in the end\n        XrdCl::XRootDStatus status = mFile->xrdiorw(req)->Truncate(truncate_size);\n\n        if (!status.IsOK()) {\n          mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                           \"status='%s' hint='will TryRecovery'\",\n                                           status.ToString().c_str()));\n          journal_recovery = true;\n          eos_err(\"truncation failed\");\n        }\n      }\n\n      if (simulate_write_error_in_flush()) {\n        // force a 'fake' repair now for testing purposes\n        journal_recovery = true;\n      }\n\n      if (journal_recovery) {\n        eos_debug(\"try recovery\");\n        int rc = 0;\n\n        if ((rc = TryRecovery(req, true))) {\n          mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                           \"errno='%d' hint='failed TryRecovery'\",\n                                           rc));\n          eos_err(\"journal-flushing recovery failed rc=%d\", rc);\n          return rc;\n        } else {\n          mRecoveryStack.push_back(eos_log(LOG_SILENT, \"hint='success TryRecovery'\"));\n\n          if ((rc = journalflush(req))) {\n            mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                             \"errno='%d' hint='failed journalflush'\",\n                                             rc));\n            eos_err(\"journal-flushing failed rc=%d\", rc);\n            return rc;\n          } else {\n            mRecoveryStack.push_back(eos_log(LOG_SILENT, \"hint='success journalflush'\"));\n          }\n        }\n      }\n\n      // truncate the journal\n      if (mFile->journal()->reset()) {\n        char msg[1024];\n        snprintf(msg, sizeof(msg), \"journal reset failed - ino=%#lx errno=%d %s\", id(),\n                 errno, mFile->journal()->dump().c_str());\n        eos_crit(\"%s\", msg);\n        throw std::runtime_error(msg);\n      }\n\n      mFile->journal()->done_flush();\n    }\n  }\n\n  // check if the open failed\n  XrdCl::shared_proxy proxy = mFile->has_xrdiorw(req) ? mFile->xrdiorw(\n                                req) : nullptr;\n\n  if (proxy) {\n    if (proxy->state() == XrdCl::Proxy::FAILED) {\n      int rc = 0;\n      eos_debug(\"try recovery\");\n      mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                       \"status='XrdCl::Proxy::FAILED' hint='will TryRecovery'\"));\n\n      if ((rc = TryRecovery(req, true))) {\n        mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                         \"errno='%d' hint='failed TryRecovery'\",\n                                         rc));\n        eos_err(\"remote open failed - returning %d\", rc);\n        return rc;\n      } else {\n        mRecoveryStack.push_back(eos_log(LOG_SILENT, \"hint='success TryRecovery'\"));\n\n        if ((rc = journalflush(req))) {\n          mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                           \"errno='%d' hint='failed journalflush'\",\n                                           rc));\n          eos_err(\"journal-flushing failed\");\n          return rc;\n        } else {\n          mRecoveryStack.push_back(eos_log(LOG_SILENT, \"hint='success journalflush'\"));\n\n          // truncate the journal\n          if (mFile->journal()->reset()) {\n            char msg[1024];\n            snprintf(msg, sizeof(msg), \"journal reset failed - ino=%#lx errno=%d %s\", id(),\n                     errno, mFile->journal()->dump().c_str());\n            eos_crit(\"%s\", msg);\n            throw std::runtime_error(msg);\n          }\n\n          mFile->journal()->done_flush();\n        }\n      }\n    }\n  }\n\n  eos_info(\"retc=0\");\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndata::datax::journalflush(fuse_req_t req)\n/* -------------------------------------------------------------------------- */\n{\n  // call this with a mLock locked\n  eos_info(\"\");\n  XrdCl::XRootDStatus status = mFile->xrdiorw(req)->WaitOpen();\n\n  // we have to push the journal now\n  if (!status.IsOK()) {\n    eos_err(\"async journal-cache-wait-open failed - ino=%#lx\", id());\n    errno = XrdCl::Proxy::status2errno(status);\n    return errno;\n  }\n\n  eos_info(\"syncing cache\");\n  cachesyncer cachesync(*((XrdCl::File*)mFile->xrdiorw(req).get()));\n\n  if (!mFile->journal()) {\n    // no journal this has to fail\n    errno = EOPNOTSUPP;\n    return errno;\n  }\n\n  if ((mFile->journal())->remote_sync(cachesync)) {\n    eos_err(\"async journal-cache-sync failed - ino=%#lx\", id());\n    return EREMOTEIO;\n  }\n\n  eos_info(\"retc=0\");\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndata::datax::journalflush(std::string cid)\n/* -------------------------------------------------------------------------- */\n{\n  // call this with a mLock locked\n  eos_info(\"\");\n  XrdCl::XRootDStatus status = mFile->xrdiorw(cid)->WaitOpen();\n\n  // we have to push the journal now\n  if (!status.IsOK()) {\n    eos_err(\"async journal-cache-wait-open failed - ino=%#lx\", id());\n    errno = XrdCl::Proxy::status2errno(status);\n    return errno;\n  }\n\n  if (mFile->journal()) {\n    eos_info(\"syncing cache\");\n    cachesyncer cachesync(*((XrdCl::File*)mFile->xrdiorw(cid).get()));\n\n    if ((mFile->journal())->remote_sync(cachesync)) {\n      eos_err(\"async journal-cache-sync failed - ino=%#lx\", id());\n      return EREMOTEIO;\n    }\n  }\n\n  eos_info(\"retc=0\");\n  return 0;\n}\n\n\n/* -------------------------------------------------------------------------- */\nbool\n/* -------------------------------------------------------------------------- */\ndata::datax::is_wopen(fuse_req_t req)\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper lLock(mLock);\n  return mFile->xrdiorw(req)->IsOpen();\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndata::datax::journalflush_async(std::string cid)\n/* -------------------------------------------------------------------------- */\n{\n  // call this with a mLock locked\n  eos_info(\"\");\n\n  // we have to push the journal now\n  if (!mFile->xrdiorw(cid)->WaitOpen().IsOK()) {\n    eos_err(\"async journal-cache-wait-open failed - ino=%#lx\", id());\n    return -1;\n  }\n\n  if (mFile->journal()) {\n    eos_info(\"syncing cache asynchronously\");\n\n    if ((mFile->journal())->remote_sync_async(mFile->xrdiorw(cid))) {\n      eos_err(\"async journal-cache-sync-async failed - ino=%#lx\", id());\n      return -1;\n    }\n  }\n\n  eos_info(\"retc=0\");\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\ndata::datax::set_id(uint64_t ino, fuse_req_t req)\n/* -------------------------------------------------------------------------- */\n{\n  mIno = ino;\n  mReq = req;\n  mFile = cachehandler::instance().get(ino);\n  char lid[64];\n  snprintf(lid, sizeof(lid), \"logid:ino:%016lx\", ino);\n  SetLogId(lid);\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndata::datax::attach(fuse_req_t freq, std::string& cookie, int flags)\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper lLock(mLock);\n  bool isRW = false;\n  bool add_O_SYNC = false;\n  bool add_O_CREAT = false;\n\n  if (mFlags & O_SYNC) {\n    // preserve the sync flag\n    add_O_SYNC = true;\n  }\n\n  if (mFlags & O_CREAT) {\n    // preserve the creat flag\n    add_O_CREAT = true;\n  }\n\n  mFlags = flags;\n\n  if (add_O_SYNC) {\n    mFlags |= O_SYNC;\n  }\n\n  if (add_O_CREAT) {\n    mFlags |= O_CREAT;\n  }\n\n  if (mFlags & O_CREAT) {\n    mFlags |= O_RDWR;\n  }\n\n  // check for file inlining only for the first attach call\n  if ((!inline_buffer) && (EosFuse::Instance().Config().inliner.max_size ||\n                           mMd->inlinesize())) {\n    if (mMd->inlinesize()) {\n      mInlineMaxSize = mMd->inlinesize();\n    } else {\n      mInlineMaxSize = EosFuse::Instance().Config().inliner.max_size;\n    }\n\n    auto attrMap = (*mMd)()->attr();\n\n    if (attrMap.count(kInlineMaxSize)) {\n      mInlineMaxSize = strtoull(attrMap[kInlineMaxSize].c_str(), 0, 10);\n    }\n\n    if (attrMap.count(kInlineCompressor)) {\n      mInlineCompressor = attrMap[kInlineCompressor];\n    } else {\n      mInlineCompressor = EosFuse::Instance().Config().inliner.default_compressor;\n    }\n\n    eos_debug(\"inline-size=%llu inline-compressor=%s\", mInlineMaxSize,\n              mInlineCompressor.c_str());\n    // reserve buffer for inlining\n    inline_buffer = std::make_shared<bufferll>(mInlineMaxSize,\n                    mInlineMaxSize);\n    mIsInlined = true;\n\n    if (attrMap.count(kInlineAttribute)) {\n      std::string base64_string(attrMap[kInlineAttribute].c_str(),\n                                attrMap[kInlineAttribute].size());\n      std::string raw_string;\n      bool decoding = false;\n\n      if (base64_string.substr(0, 8) == \"zbase64:\") {\n        SymKey::ZDeBase64(base64_string, raw_string);\n        decoding = true;\n      } else if (base64_string.substr(0, 7) == \"base64:\") {\n        SymKey::DeBase64(base64_string, raw_string);\n        decoding = true;\n      }\n\n      if (decoding) {\n        // decode attribute to buffer\n        inline_buffer->writeData(raw_string.c_str(), 0, raw_string.size());\n\n        // in case there is any inconsistency between size and attribute buffer, just ignore this one\n        if (raw_string.size() != (*mMd)()->size()) {\n          inline_buffer = 0;\n          // delete the inline buffer\n          ((*mMd)()->mutable_attr())->erase(kInlineAttribute);\n          mIsInlined = false;\n        }\n      } else {\n        mIsInlined = false;\n      }\n    } else {\n      if ((*mMd)()->size()) {\n        mIsInlined = false;\n      }\n    }\n  }\n\n  if (flags & (O_CREAT | O_RDWR | O_WRONLY)) {\n    isRW = true;\n  }\n\n  eos_info(\"cookie=%s flags=%o isrw=%d md-size=%d %s\", cookie.c_str(), flags,\n           isRW, (*mMd)()->size(),\n           isRW ? mRemoteUrlRW.c_str() : mRemoteUrlRO.c_str());\n  // store the currently known size here\n  mSize = (*mMd)()->size();\n\n  // set write error simulation flags\n  if ((*mMd)()->name().find(\"#err_sim_flush#\") != std::string::npos) {\n    eos_crit(\"enabling error simulation on flush\");\n    mSimulateWriteErrorInFlush = true;\n  } else if ((*mMd)()->name().find(\"#err_sim_flusher#\") != std::string::npos) {\n    eos_crit(\"enabling error simulation on flusher\");\n    mSimulateWriteErrorInFlusher = true;\n  }\n\n  if ((flags & O_SYNC) ||\n      ((time(NULL) - (*mMd)()->bc_time()) <\n       EosFuse::Instance().Config().options.nocache_graceperiod)) {\n    mFile->disable_caches();\n  }\n\n  int bcache = mFile->file() ? mFile->file()->attach(freq, cookie, isRW) : 0;\n  int jcache = mFile->journal() ? ((isRW ||\n                                    (mFlags & O_CACHE)) ? mFile->journal()->attach(freq, cookie,\n                                        flags) : 0) : 0;\n\n  if (bcache < 0) {\n    char msg[1024];\n    snprintf(msg, sizeof(msg), \"attach to cache failed - ino=%#lx errno=%d\", id(),\n             errno);\n    eos_crit(\"%s\", msg);\n    throw std::runtime_error(msg);\n  }\n\n  if (jcache < 0) {\n    char msg[1024];\n    snprintf(msg, sizeof(msg), \"attach to journal failed - ino=%#lx errno=%d\", id(),\n             errno);\n    eos_crit(\"%s\", msg);\n    throw std::runtime_error(msg);\n  }\n\n  if (isRW) {\n    if (!mFile->has_xrdiorw(freq) || mFile->xrdiorw(freq)->IsClosing() ||\n        mFile->xrdiorw(freq)->IsClosed()) {\n      if (mFile->has_xrdiorw(freq) && (mFile->xrdiorw(freq)->IsClosing() ||\n                                       mFile->xrdiorw(freq)->IsClosed())) {\n        mFile->xrdiorw(freq)->WaitClose();\n        mFile->xrdiorw(freq)->attach();\n      } else {\n        // attach an rw io object\n        mFile->set_xrdiorw(freq, XrdCl::Proxy::Factory());\n        mFile->xrdiorw(freq)->attach();\n        mFile->xrdiorw(freq)->set_id(id(), req());\n      }\n\n      XrdCl::OpenFlags::Flags targetFlags = XrdCl::OpenFlags::Update;\n      XrdCl::Access::Mode mode = XrdCl::Access::UR | XrdCl::Access::UW |\n                                 XrdCl::Access::UX;\n      mFile->xrdiorw(freq)->OpenAsync(mFile->xrdiorw(freq), mRemoteUrlRW.c_str(),\n                                      targetFlags, mode, 0);\n    } else {\n      if (mFile->xrdiorw(freq)->IsWaitWrite()) {\n        // re-open the file in the state machine\n        mFile->xrdiorw(freq)->set_state_TS(XrdCl::Proxy::OPENED);\n      }\n\n      mFile->xrdiorw(freq)->attach();\n    }\n\n    // when someone attaches a writer, we have to drop all the read-ahead buffers because we might get stale information in readers\n    for (auto fit = mFile->get_xrdioro().begin();\n         fit != mFile->get_xrdioro().end(); ++fit) {\n      if (fit->second->IsOpen()) {\n        fit->second->DropReadAhead();\n      }\n    }\n  } else {\n    if (!mFile->has_xrdioro(freq) || mFile->xrdioro(freq)->IsClosing() ||\n        mFile->xrdioro(freq)->IsClosed()) {\n      if (mFile->has_xrdioro(freq) && (mFile->xrdioro(freq)->IsClosing() ||\n                                       mFile->xrdioro(freq)->IsClosed())) {\n        mFile->xrdioro(freq)->WaitClose();\n        mFile->xrdioro(freq)->attach();\n      } else {\n        mFile->set_xrdioro(freq, XrdCl::Proxy::Factory());\n        mFile->xrdioro(freq)->attach();\n        mFile->xrdioro(freq)->set_id(id(), req());\n\n        if (!(flags & O_SYNC)) {\n          if (EOS_LOGS_DEBUG)\n            eos_debug(\"readhead: strategy=%s nom:%lu max:%lu sparse-ratio:%.01f\",\n                      cachehandler::instance().get_config().read_ahead_strategy.c_str(),\n                      cachehandler::instance().get_config().default_read_ahead_size,\n                      cachehandler::instance().get_config().max_read_ahead_size,\n                      cachehandler::instance().get_config().read_ahead_sparse_ratio);\n\n          mFile->xrdioro(freq)->set_readahead_strategy(\n            XrdCl::Proxy::readahead_strategy_from_string(\n              cachehandler::instance().get_config().read_ahead_strategy),\n            4096,\n            cachehandler::instance().get_config().default_read_ahead_size,\n            cachehandler::instance().get_config().max_read_ahead_size,\n            cachehandler::instance().get_config().max_read_ahead_blocks,\n            cachehandler::instance().get_config().read_ahead_sparse_ratio\n          );\n          mFile->xrdioro(freq)->set_readahead_maximum_position(mSize);\n        }\n      }\n\n      XrdCl::OpenFlags::Flags targetFlags = XrdCl::OpenFlags::Read;\n      XrdCl::Access::Mode mode = XrdCl::Access::UR | XrdCl::Access::UX;\n      // we might need to wait for a creation to go through\n      WaitOpen();\n      mFile->xrdioro(freq)->OpenAsync(mFile->xrdioro(freq), mRemoteUrlRO.c_str(),\n                                      targetFlags, mode, 0);\n    } else {\n      if (mFile->has_xrdiorw(freq)) {\n        // we have to drop all existing read-ahead buffers to avoid reading outdated buffers\n        mFile->xrdioro(freq)->DropReadAhead();\n      }\n\n      mFile->xrdioro(freq)->attach();\n    }\n  }\n\n  return bcache | jcache;\n}\n\n/* -------------------------------------------------------------------------- */\nbool\n/* -------------------------------------------------------------------------- */\ndata::datax::inline_file(ssize_t size)\n{\n  XrdSysMutexHelper lLock(mLock);\n\n  if (size == -1) {\n    size = (*mMd)()->size();\n  }\n\n  if (inlined() && inline_buffer) {\n    if ((size_t) size <= mInlineMaxSize) {\n      // rewrite the extended attribute\n      std::string raw_string(inline_buffer->ptr(), size);\n      std::string base64_string;\n\n      if (mInlineCompressor == \"zlib\") {\n        SymKey::ZBase64(raw_string, base64_string);\n      } else {\n        SymKey::Base64(raw_string, base64_string);\n      }\n\n      (*((*mMd)()->mutable_attr()))[kInlineAttribute] = base64_string;\n      (*((*mMd)()->mutable_attr()))[kInlineMaxSize] = std::to_string(mInlineMaxSize);\n      (*((*mMd)()->mutable_attr()))[kInlineCompressor] = mInlineCompressor;\n      return true;\n    } else {\n      // remove the extended attribute\n      ((*mMd)()->mutable_attr())->erase(kInlineAttribute);\n      mIsInlined = false;\n      return false;\n    }\n  }\n\n  return false;\n}\n\n/* -------------------------------------------------------------------------- */\nbool\n/* -------------------------------------------------------------------------- */\ndata::datax::prefetch(fuse_req_t req, bool lock)\n/* -------------------------------------------------------------------------- */\n{\n  size_t file_size = (*mMd)()->size();\n  eos_info(\"handler=%d file=%lx size=%lu md-size=%lu\", mPrefetchHandler ? 1 : 0,\n           mFile ? mFile->file() : 0,\n           mFile ? mFile->file() ? mFile->file()->size() : 0 : 0,\n           file_size);\n\n  if (mFile && mFile->has_xrdiorw(req)) {\n    // never prefetch on a wr open file\n    return true;\n  }\n\n  if (inlined()) {\n    // never prefetch an inlined file\n    return true;\n  }\n\n  if (lock) {\n    mLock.Lock();\n  }\n\n  if (!mPrefetchHandler && mFile->file() && !mFile->file()->size() && file_size) {\n    XrdCl::shared_proxy proxy = mFile->has_xrdioro(req) ? mFile->xrdioro(\n                                  req) : mFile->xrdiorw(req);\n\n    if (proxy) {\n      XrdCl::XRootDStatus status;\n      size_t prefetch_size = std::min((size_t) file_size,\n                                      (size_t) mFile->file()->prefetch_size());\n      // try to send an async read request\n      mPrefetchHandler = proxy->ReadAsyncPrepare(proxy, 0, prefetch_size, false);\n\n      if (!mPrefetchHandler) {\n        // already have read issued for this offset\n        return false;\n      }\n\n      bool nobuffer = false;\n      if (mPrefetchHandler->valid()) {\n        status = proxy->PreReadAsync(0, prefetch_size, mPrefetchHandler, 0);\n      } else {\n        // no free IO buffer\n        XrdCl::XRootDStatus newstatus(XrdCl::stFatal,\n                                      0,\n                                      XrdCl::errOSError,\n                                      \"no free read-ahead buffer\"\n                                     );\n        status = newstatus;\n        nobuffer = true;\n      }\n\n      if (!status.IsOK()) {\n        if (!nobuffer) {\n          eos_err(\"pre-fetch failed error=%s\", status.ToStr().c_str());\n        }\n\n        mPrefetchHandler = 0;\n      } else {\n        // instruct the read-ahead handler where to start\n        proxy->set_readahead_position(prefetch_size);\n      }\n    }\n  }\n\n  if (lock) {\n    mLock.UnLock();\n  }\n\n  return mPrefetchHandler ? true : false;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\ndata::datax::WaitPrefetch(fuse_req_t req, bool lock)\n/* -------------------------------------------------------------------------- */\n{\n  eos_info(\"\");\n\n  if (lock) {\n    mLock.Lock();\n  }\n\n  size_t file_size = (*mMd)()->size();\n\n  if (mPrefetchHandler && mFile->file()) {\n    XrdCl::shared_proxy proxy = mFile->has_xrdioro(req) ? mFile->xrdioro(\n                                  req) : mFile->xrdiorw(req);\n\n    if (mPrefetchHandler && proxy) {\n      XrdCl::XRootDStatus status = proxy->WaitRead(mPrefetchHandler);\n\n      if (status.IsOK()) {\n        eos_info(\"pre-read done with size=%lu md-size=%lu\",\n                 mPrefetchHandler->vbuffer().size(), file_size);\n\n        if ((mPrefetchHandler->vbuffer().size() == file_size) && mFile->file()) {\n          ssize_t nwrite = mFile->file()->pwrite(mPrefetchHandler->buffer(),\n                                                 mPrefetchHandler->vbuffer().size(), 0);\n          eos_debug(\"nwb=%lu to local cache\", nwrite);\n        }\n      } else {\n        eos_err(\"pre-read failed error=%s\", status.ToStr().c_str());\n      }\n    }\n  }\n\n  if (lock) {\n    mLock.UnLock();\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\ndata::datax::WaitOpen()\n{\n  // make sure there is no pending remote open.\n  // this has to be done to avoid opening a file for read, which is not yet\n  // created.\n  for (auto fit = mFile->get_xrdiorw().begin();\n       fit != mFile->get_xrdiorw().end(); ++fit) {\n    if (fit->second->IsOpening()) {\n      eos_info(\"status=pending url=%s\", fit->second->url().c_str());\n      fit->second->WaitOpen();\n      eos_info(\"status=final url=%s\", fit->second->url().c_str());\n    } else {\n      eos_info(\"status=final url=%s\", fit->second->url().c_str());\n    }\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\ndata::datax::FlagDeleted()\n{\n  for (auto fit = mFile->get_xrdiorw().begin();\n       fit != mFile->get_xrdiorw().end(); ++fit) {\n    fit->second->setDeleted();\n  }\n\n  for (auto fit = mFile->get_xrdioro().begin();\n       fit != mFile->get_xrdioro().end(); ++fit) {\n    fit->second->setDeleted();\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndata::datax::TryRecovery(fuse_req_t req, bool iswrite)\n{\n  eos_debug(\"\");\n\n  if (req && fuse_req_interrupted(req)) {\n    eos_warning(\"request interrupted\");\n\n    // clean-up pending in-memory requests\n    if (iswrite) {\n      XrdCl::shared_proxy proxy = mFile->xrdiorw(req);\n\n      if (proxy) {\n        proxy->CleanWriteQueue();\n      }\n    }\n\n    return EINTR;\n  }\n\n  {\n    XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:n\"]++;\n  }\n\n  if (mReadErrorStack.size() > 128) {\n    std::string stack_dump;\n\n    // we give up recovery if to many recoveries took place\n    for (auto it = mReadErrorStack.begin(); it != mReadErrorStack.end(); ++it) {\n      stack_dump += \"\\n\";\n      stack_dump += *it;\n    }\n\n    {\n      XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:read:exceeded\"]++;\n    }\n\n    eos_err(\"giving up recovery - error-stack:%s\", stack_dump.c_str());\n    return EREMOTEIO;\n  }\n\n  if (iswrite) {\n    {\n      XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:disabled\"]++;\n    }\n\n    // recover write failures\n    if (!EosFuse::Instance().Config().recovery.write) { // might be disabled\n      eos_warning(\"write recovery disabled\");\n      return EREMOTEIO;\n    }\n\n    if (!mFile->has_xrdiorw(req)) {\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:noproxy\"]++;\n      }\n      eos_crit(\"no proxy object\");\n      return EFAULT;\n    }\n\n    XrdCl::shared_proxy proxy = mFile->xrdiorw(req);\n\n    if (proxy->opening_state().IsError() &&\n        ! proxy->opening_state_should_retry()) {\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:unrecoverable\"]++;\n      }\n      eos_err(\"unrecoverable error - code=%d errNo=%d\",\n              proxy->opening_state().code,\n              proxy->opening_state().errNo);\n      proxy->CleanWriteQueue();\n      return XrdCl::Proxy::status2errno(proxy->opening_state());\n    }\n\n    switch (proxy->state()) {\n    case XrdCl::Proxy::FAILED:\n    case XrdCl::Proxy::OPENED:\n    case XrdCl::Proxy::WAITWRITE:\n    default: {\n      XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:n\"]++;\n    }\n\n    eos_crit(\"triggering write recovery state = %d\", proxy->state());\n    return recover_write(req);\n    eos_crit(\"default action\");\n    }\n  } else {\n    {\n      XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:read:disabled\"]++;\n    }\n\n    // recover read failures\n    if (!EosFuse::Instance().Config().recovery.read) { // might be disabled\n      return EREMOTEIO;\n    }\n\n    // no way to recover\n    if (!mFile->has_xrdioro(req)) {\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:read:noproxy\"]++;\n      }\n      eos_crit(\"no proxy object\");\n      return EFAULT;\n    }\n\n    XrdCl::shared_proxy proxy = mFile->xrdioro(req);\n\n    if (proxy->opening_state().IsError() &&\n        ! proxy->opening_state_should_retry()) {\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:read:unrecoverable\"]++;\n      }\n      eos_err(\"unrecoverable error - code=%d errNo=%d\",\n              proxy->opening_state().code,\n              proxy->opening_state().errNo);\n      proxy->CleanWriteQueue();\n      return XrdCl::Proxy::status2errno(proxy->opening_state());\n    }\n\n    switch (proxy->state()) {\n    case XrdCl::Proxy::FAILED: {\n      XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:read:reopen:n\"]++;\n    }\n\n    mReadErrorStack.push_back(\"open-failed\");\n    return recover_ropen(req);\n\n    case XrdCl::Proxy::OPENED: {\n      XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:read:reread:n\"]++;\n    }\n\n    mReadErrorStack.push_back(\"read-failed\");\n    return recover_read(req);\n\n    default:\n      break;\n    }\n  }\n\n  return EREMOTEIO;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndata::datax::recover_ropen(fuse_req_t req)\n/* -------------------------------------------------------------------------- */\n{\n  XrdCl::shared_proxy proxy = 0;\n  struct timespec ts;\n  eos::common::Timing::GetTimeSpec(ts, true);\n\n  while (1) {\n    proxy = mFile->xrdioro(req);\n    mRecoveryStack.push_back(eos_log(LOG_SILENT, \"hint='recover read-open'\"));\n    eos_warning(\"recover read-open [%d]\",\n                EosFuse::Instance().Config().recovery.read_open);\n\n    if (!EosFuse::Instance().Config().recovery.read_open) { // might be disabled\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:read:reopen:disabled\"]++;\n      }\n      break;\n    }\n\n    XrdCl::XRootDStatus status = proxy->opening_state();\n\n    if (status.errNo == kXR_noserver) {\n      eos_crit(\"recover read-open-noserver [%d]\",\n               EosFuse::Instance().Config().recovery.read_open_noserver);\n\n      // there is no server to read that file\n      if (!EosFuse::Instance().Config().recovery.read_open_noserver) { // might be disabled\n        {\n          XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:read:reopen:noserver:disabled\"]++;\n        }\n        return ENETUNREACH;\n      }\n    }\n\n    if (status.IsFatal()) {\n      // error useless to retry\n      eos_crit(\"recover-ropen failed errno=%d\", XrdCl::Proxy::status2errno(status));\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:read:reopen:failed\"]++;\n      }\n      return XrdCl::Proxy::status2errno(status);\n    }\n\n    eos_warning(\"recover reopening file for read\");\n    XrdCl::OpenFlags::Flags targetFlags = XrdCl::OpenFlags::Read;\n    XrdCl::Access::Mode mode = XrdCl::Access::UR | XrdCl::Access::UX;\n    // retrieve the 'tried' information to apply this for the file-reopening to exclude already known 'bad' locations\n    std::string slasturl;\n    proxy->GetProperty(\"LastURL\", slasturl);\n    XrdCl::URL lasturl(slasturl);\n    XrdCl::URL newurl(mRemoteUrlRO);\n    XrdCl::URL::ParamsMap last_cgi = lasturl.GetParams();\n    XrdCl::URL::ParamsMap new_cgi = newurl.GetParams();\n    std::string last_host = lasturl.GetHostName();\n\n    if ((lasturl.GetHostName() != newurl.GetHostName()) &&\n        (lasturl.GetPort() != newurl.GetPort())) {\n      eos_warning(\"applying exclusion list: tried=%s,%s\", last_host.c_str(),\n                  new_cgi[\"tried\"].c_str());\n      new_cgi[\"tried\"] = last_host.c_str() + std::string(\",\") + last_cgi[\"tried\"];\n      new_cgi[\"triedrc\"] = \"EIO\";\n      new_cgi[\"eos.repairread\"] = \"1\";\n      newurl.SetParams(new_cgi);\n      mRemoteUrlRO = newurl.GetURL();\n    } else {\n      new_cgi.erase(\"tried\");\n      new_cgi.erase(\"triedrc\");\n      new_cgi[\"eos.repairread\"] = \"1\";\n      newurl.SetParams(new_cgi);\n      mRemoteUrlRO = newurl.GetURL();\n    }\n\n    XrdCl::shared_proxy newproxy = XrdCl::Proxy::Factory(proxy);\n    newproxy->OpenAsync(newproxy, mRemoteUrlRO.c_str(), targetFlags, mode, 0);\n    // wait this time for completion\n\n    if ((req && fuse_req_interrupted(req)) || (newproxy->WaitOpen(req) == EINTR)) {\n      eos_warning(\"request interrupted\");\n      return EINTR;\n    }\n\n    newproxy->inherit_attached(proxy);\n    newproxy->inherit_protocol(proxy);\n    // replace the proxy object\n    mFile->set_xrdioro(req, newproxy);\n    proxy->detach();\n    // save the error status of the previous proxy object\n    status = proxy->opening_state();\n\n    if (newproxy->state() == XrdCl::Proxy::OPENED) { // that worked !\n      eos_warning(\"recover reopened file successfully\");\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:read:reopen:success\"]++;\n      }\n      return 0;\n    }\n\n    // that failed again ...\n\n    if (status.errNo == kXR_noserver) {\n      double retry_time_sec = 1.0 * eos::common::Timing::GetCoarseAgeInNs(&ts,\n                              0) / 1000000000.0;\n      eos_warning(\"recover no server retry window [ %.02f/%lu ]\",\n                  retry_time_sec,\n                  EosFuse::Instance().Config().recovery.read_open_noserver_retrywindow\n                 );\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:read:reopen:noserver:retry\"]++;\n      }\n\n      // check how long we are supposed to retry\n      if (retry_time_sec <\n          EosFuse::Instance().Config().recovery.read_open_noserver_retrywindow) {\n        eos_warning(\"recover no server retry in 5 seconds\");\n\n        for (auto i = 0; i < 50; ++i) {\n          // sleep for 5s and then try again\n          std::this_thread::sleep_for(std::chrono::milliseconds(100));\n\n          if (req && fuse_req_interrupted(req)) {\n            eos_warning(\"request interrupted\");\n            return EINTR;\n          }\n        }\n\n        // empty the tried= CGI tag\n        new_cgi.erase(\"tried\");\n        new_cgi.erase(\"triedrc\");\n        newurl.SetParams(new_cgi);\n        mRemoteUrlRO = newurl.GetURL();\n        continue;\n      }\n\n      break;\n    }\n\n    if (status.IsFatal()) {\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:read:reopen:noserver:fatal\"]++;\n      }\n      // error useless to retry\n      eos_crit(\"recover-ropen failed errno=%d\", XrdCl::Proxy::status2errno(status));\n      return XrdCl::Proxy::status2errno(status);\n    }\n\n    break;\n  }\n\n  return EREMOTEIO;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndata::datax::try_ropen(fuse_req_t req, XrdCl::shared_proxy &proxy,\n                       std::string open_url)\n{\n  mRecoveryStack.push_back(eos_log(LOG_SILENT, \"hint='try read-open'\"));\n  struct timespec ts;\n  eos::common::Timing::GetTimeSpec(ts, true);\n  XrdCl::OpenFlags::Flags targetFlags = XrdCl::OpenFlags::Read;\n  XrdCl::Access::Mode mode = XrdCl::Access::UR | XrdCl::Access::UX;\n  proxy->OpenAsync(proxy, open_url, targetFlags, mode, 0);\n\n  // wait this time for completion\n  if ((req && fuse_req_interrupted(req)) || (proxy->WaitOpen(req) == EINTR)) {\n    eos_warning(\"request interrupted\");\n    return EINTR;\n  }\n\n  if (proxy->state() == XrdCl::Proxy::OPENED) { // that worked !\n    eos_warning(\"recover read-open succesfull\");\n    return 0;\n  }\n\n  while (1) {\n    eos_warning(\"recover read-open [%d]\",\n                EosFuse::Instance().Config().recovery.read_open);\n\n    if (!EosFuse::Instance().Config().recovery.read_open) { // might be disabled\n      break;\n    }\n\n    XrdCl::XRootDStatus status = proxy->opening_state();\n\n    if (status.errNo == kXR_noserver) {\n      eos_crit(\"recover read-open-noserver [%d]\",\n               EosFuse::Instance().Config().recovery.read_open_noserver);\n\n      // there is no server to read that file\n      if (!EosFuse::Instance().Config().recovery.read_open_noserver) { // might be disabled\n        return ENETUNREACH;\n      }\n    }\n\n    if (status.IsFatal()) {\n      // error useless to retry\n      eos_crit(\"recover read-open errno=%d\", XrdCl::Proxy::status2errno(status));\n      return XrdCl::Proxy::status2errno(status);\n    }\n\n    if ((status.errNo == kXR_overQuota) ||\n        (status.errNo == kXR_NoSpace)) {\n      // error useless to retry - this can happen if the open tries to reattach a file without locations and the user is out of quota\n      eos_crit(\"recover read-open errno=%d\", XrdCl::Proxy::status2errno(status));\n      return XrdCl::Proxy::status2errno(status);\n    }\n\n    eos_warning(\"recover reopening file for read\");\n    // retrieve the 'tried' information to apply this for the file-reopening to exclude already knowns 'bad' locations\n    std::string slasturl;\n    proxy->GetProperty(\"LastURL\", slasturl);\n    XrdCl::URL lasturl(slasturl);\n    XrdCl::URL newurl(open_url);\n    XrdCl::URL::ParamsMap last_cgi = lasturl.GetParams();\n    XrdCl::URL::ParamsMap new_cgi = newurl.GetParams();\n    std::string last_host = lasturl.GetHostName();\n\n    if ((lasturl.GetHostName() != newurl.GetHostName()) ||\n        (lasturl.GetPort() != newurl.GetPort())) {\n      eos_warning(\"applying exclusion list: tried=%s,%s\", last_host.c_str(),\n                  new_cgi[\"tried\"].c_str());\n      new_cgi[\"tried\"] = last_host.c_str() + std::string(\",\") + last_cgi[\"tried\"];\n      new_cgi[\"triedrc\"] = \"EIO\" + std::string(\",\") + last_cgi[\"triedrc\"];\n      newurl.SetParams(new_cgi);\n      open_url = newurl.GetURL();\n    } else {\n      new_cgi.erase(\"tried\");\n      new_cgi.erase(\"triedrc\");\n      newurl.SetParams(new_cgi);\n      open_url = newurl.GetURL();\n    }\n\n    XrdCl::shared_proxy newproxy = XrdCl::Proxy::Factory(proxy);\n    newproxy->OpenAsync(newproxy, open_url.c_str(), targetFlags, mode, 0);\n    // wait this time for completion\n\n    if ((req && fuse_req_interrupted(req)) || (newproxy->WaitOpen(req) == EINTR)) {\n      eos_warning(\"request interrupted\");\n      return EINTR;\n    }\n\n    newproxy->inherit_attached(proxy);\n    newproxy->inherit_protocol(proxy);\n    proxy->detach();\n    // replace the proxy object\n    proxy = newproxy;\n\n    if (newproxy->state() == XrdCl::Proxy::OPENED) { // that worked !\n      eos_warning(\"recover reopened file successfully\");\n      return 0;\n    }\n\n    // that failed again ...\n    status = proxy->opening_state();\n\n    if (status.errNo == kXR_noserver) {\n      double retry_time_sec = 1.0 * eos::common::Timing::GetCoarseAgeInNs(&ts,\n                              0) / 1000000000.0;\n      eos_warning(\"recover no server retry window [ %.02f/%lu ]\",\n                  retry_time_sec,\n                  EosFuse::Instance().Config().recovery.read_open_noserver_retrywindow\n                 );\n\n      // check how long we are supposed to retry\n      if (retry_time_sec <\n          EosFuse::Instance().Config().recovery.read_open_noserver_retrywindow) {\n        eos_warning(\"recover no server retry in 5 seconds\");\n\n        for (auto i = 0; i < 50; ++i) {\n          // sleep for 5s and then try again\n          std::this_thread::sleep_for(std::chrono::milliseconds(100));\n\n          if (req && fuse_req_interrupted(req)) {\n            eos_warning(\"request interrupted\");\n            return EINTR;\n          }\n        }\n\n        // empty the tried= CGI tag\n        new_cgi.erase(\"tried\");\n        newurl.SetParams(new_cgi);\n        open_url = newurl.GetURL();\n        continue;\n      }\n\n      break;\n    }\n\n    break;\n  }\n\n  eos_warning(\"recover failed try_ropen\");\n  return EREMOTEIO;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndata::datax::try_wopen(fuse_req_t req, XrdCl::shared_proxy &proxy,\n                       std::string open_url)\n{\n  mRecoveryStack.push_back(eos_log(LOG_SILENT, \"hint='try write-open'\"));\n  struct timespec ts;\n  {\n    XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:reopen:n\"]++;\n  }\n  eos::common::Timing::GetTimeSpec(ts, true);\n  // try to open this file for writing\n  XrdCl::OpenFlags::Flags targetFlags = XrdCl::OpenFlags::Update;\n  XrdCl::Access::Mode mode = XrdCl::Access::UR | XrdCl::Access::UW |\n                             XrdCl::Access::UX;\n  // try to open\n  XrdCl::XRootDStatus status = proxy->OpenAsync(proxy, open_url.c_str(),\n                               targetFlags,\n                               mode, 0);\n\n  if (proxy->WaitOpen(req) == EINTR) {\n    eos_warning(\"request interrupted\");\n    proxy->CleanWriteQueue();\n    return EINTR;\n  }\n\n  // if that worked we are already fine, otherwise we enter a timebased logic for retries\n  if (proxy->state() == XrdCl::Proxy::OPENED) { // that worked !\n    {\n      XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:reopen:success\"]++;\n    }\n    eos_warning(\"re-opening for write succeeded\");\n    return 0;\n  }\n\n  while (1) {\n    eos_warning(\"recover write-open [%d]\",\n                EosFuse::Instance().Config().recovery.write_open);\n\n    if (!EosFuse::Instance().Config().recovery.write_open) { // might be disabled\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:reopen:disabled\"]++;\n      }\n      break;\n    }\n\n    XrdCl::XRootDStatus status = proxy->opening_state();\n\n    if (status.errNo == kXR_noserver) {\n      eos_crit(\"recover write-open-noserver [%d]\",\n               EosFuse::Instance().Config().recovery.write_open_noserver);\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:reopen:noserver::retry\"]++;\n      }\n\n      // there is no server to read that file\n      if (!EosFuse::Instance().Config().recovery.write_open_noserver) { // might be disable\n        {\n          XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:reopen:noserver::disabled\"]++;\n        }\n        return ENETUNREACH;\n      }\n    }\n\n    if (status.IsFatal()) {\n      // error useless to retry\n      eos_crit(\"recover write-open-fatal queue=%d errno=%d\",\n               proxy->WriteQueue().size(), XrdCl::Proxy::status2errno(status));\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:reopen:unrecoverable\"]++;\n      }\n      return XrdCl::Proxy::status2errno(status);\n    }\n\n    if (status.errNo == kXR_overQuota) {\n      // error useless to retry - no quota anymore\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:reopen:overquota\"]++;\n      }\n      eos_crit(\"recover write-open errno=%d\", XrdCl::Proxy::status2errno(status));\n      return XrdCl::Proxy::status2errno(status);\n    }\n\n    eos_warning(\"recover reopening file for writing\");\n    XrdCl::shared_proxy newproxy = XrdCl::Proxy::Factory(proxy);\n    newproxy->OpenAsync(newproxy, open_url.c_str(), targetFlags, mode, 0);\n    // wait this time for completion\n\n    if ((req && fuse_req_interrupted(req)) || (newproxy->WaitOpen(req) == EINTR)) {\n      eos_warning(\"request interrupted\");\n      proxy->CleanWriteQueue();\n      return EINTR;\n    }\n\n    newproxy->inherit_attached(proxy);\n    newproxy->inherit_protocol(proxy);\n    newproxy->inherit_writequeue(newproxy, proxy);\n    // replace the proxy object\n    proxy = newproxy;\n\n    if (newproxy->state() == XrdCl::Proxy::OPENED) { // that worked !\n      eos_warning(\"recover reopened file successfully\");\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:reopen:success\"]++;\n      }\n      return 0;\n    }\n\n    // that failed again ...\n    status = proxy->opening_state();\n\n    if (status.errNo == kXR_noserver) {\n      double retry_time_sec = 1.0 * eos::common::Timing::GetCoarseAgeInNs(&ts,\n                              0) / 1000000000.0;\n      eos_warning(\"recover no server retry window [ %.02f/%.02f ]\",\n                  retry_time_sec,\n                  EosFuse::Instance().Config().recovery.read_open_noserver_retrywindow\n                 );\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:reopen:nosever\"]++;\n      }\n\n      // check how long we are supposed to retry\n      if (retry_time_sec <\n          EosFuse::Instance().Config().recovery.read_open_noserver_retrywindow) {\n        eos_warning(\"recover no server retry in 5 seconds\");\n\n        for (auto i = 0; i < 50; ++i) {\n          // sleep for 5s and then try again\n          std::this_thread::sleep_for(std::chrono::milliseconds(100));\n\n          if (req && fuse_req_interrupted(req)) {\n            eos_warning(\"request interrupted\");\n            proxy->CleanWriteQueue();\n            return EINTR;\n          }\n        }\n\n        continue;\n      }\n\n      break;\n    }\n\n    if (status.IsFatal()) {\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:reopen:noserver:failed\"]++;\n      }\n      // error useless to retry\n      eos_crit(\"recover write-open-fatal errno=%d\",\n               XrdCl::Proxy::status2errno(status));\n      return XrdCl::Proxy::status2errno(status);\n    }\n\n    break;\n  }\n\n  return EREMOTEIO;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndata::datax::recover_read(fuse_req_t req)\n/* -------------------------------------------------------------------------- */\n{\n  mRecoveryStack.push_back(eos_log(LOG_SILENT, \"hint='recover read'\"));\n  XrdCl::shared_proxy proxy = mFile->xrdioro(req);\n  // recover a pread error\n  XrdCl::XRootDStatus status = proxy->read_state();\n  {\n    XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:read:n\"]++;\n  }\n\n  if (req && fuse_req_interrupted(req)) {\n    eos_warning(\"request interrupted\");\n    return EINTR;\n  }\n\n  // re-open the file\n  int reopen = recover_ropen(req);\n\n  if (!reopen) {\n    {\n      XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:read:success\"]++;\n    }\n    eos_warning(\"recover reopened file successfully to re-read\");\n    // let's try again\n    return 0;\n  }\n\n  {\n    XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:read:failed\"]++;\n  }\n\n  return reopen;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndata::datax::recover_write(fuse_req_t req)\n/* -------------------------------------------------------------------------- */\n{\n  mRecoveryStack.push_back(eos_log(LOG_SILENT, \"hint='recover write'\"));\n  eos_debug(\"\");\n  XrdCl::shared_proxy proxy = mFile->xrdiorw(req);\n  // check if we have a problem with the open\n  XrdCl::XRootDStatus status = proxy->WaitOpen();\n  {\n    XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:n\"]++;\n  }\n\n  if (status.IsFatal() ||\n      (proxy->opening_state().IsError() &&\n       ! proxy->opening_state_should_retry())\n     ) {\n    // error useless to retry\n    {\n      XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:unrecoverable\"]++;\n    }\n    proxy->CleanWriteQueue();\n    proxy->ChunkMap().clear();\n    eos_crit(\"recover write-open-fatal queue=%d errno=%d\",\n             proxy->WriteQueue().size(), XrdCl::Proxy::status2errno(status));\n    return XrdCl::Proxy::status2errno(status);\n  }\n\n  // try to open this file for reading\n  bool recover_from_file_cache = false;\n  bool recover_truncate = false;\n\n  // check if the file has been created here and is still complete in the local caches\n  if ((mFlags & O_CREAT) && mFile->file() &&\n      (((mSize <= mFile->file()->prefetch_size()) &&\n        (mSize == (ssize_t) mFile->file()->size())) ||\n       (mFile->journal() &&\n        mFile->journal()->first_flush()))) { // if the journal was not flushed yet and it is a creation, we have still all the data in the journal\n    eos_debug(\"recover from file cache\");\n    // this file can be recovered from the file start cache\n    recover_from_file_cache = true;\n    mRecoveryStack.push_back(eos_log(LOG_SILENT, \"hint='recover from file cache'\"));\n    {\n      XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:fromcache\"]++;\n    }\n  } else {\n    // we have to recover this from remote\n    eos_debug(\"recover from remote file\");\n    recover_from_file_cache = false;\n    ssize_t truncate_size = mFile->journal() ? mFile->journal()->get_truncatesize()\n                            : -1;\n\n    if (truncate_size == 0) {\n      recover_truncate = true;\n    }\n\n    {\n      XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:fromremote\"]++;\n    }\n\n    mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                     \"hint='recover from remote file'\"));\n  }\n\n  XrdCl::shared_proxy newproxy = XrdCl::Proxy::Factory(proxy);\n\n  if (!recover_from_file_cache && !recover_truncate) {\n    // we need to open this file because it is not complete locally\n    int rc = try_ropen(req, newproxy,\n                       mRemoteUrlRW + \"&eos.checksum=ignore&eos.repairread=1\");\n\n    if (rc) {\n      mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                       \"hint='read-open failed with rc=%d'\", rc));\n      proxy->CleanWriteQueue();\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:fromcache:failed\"]++;\n      }\n      mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                       \"hint='read-open failed with rc=%d'\", rc));\n      return rc;\n    }\n  }\n\n  if (mFile->file() || recover_truncate) {\n    void* buf = 0;\n    std::string stagefile;\n    int fd = -1;\n    off_t off = 0;\n    uint32_t size = 1 * 1024 * 1024;\n    bufferllmanager::shared_buffer buffer;\n    struct fd_guard {\n      fd_guard(int& fd) : fd_(fd) { }\n      ~fd_guard()\n      {\n        if (fd_ >= 0) {\n          ::close(fd_);\n          fd_ = -1;\n        }\n      }\n      int& fd_;\n    } fdg(fd);\n\n    if (!recover_truncate) {\n      mFile->file()->recovery_location(stagefile);\n      buffer = sBufferManager.get_buffer(size);\n      buf = (void*) buffer->ptr();\n      // open local stagefile\n      fd = ::open(stagefile.c_str(), O_CREAT | O_RDWR, S_IRWXU);\n      ::unlink(stagefile.c_str());\n\n      if (fd < 0) {\n        sBufferManager.put_buffer(buffer);\n        eos_crit(\"failed to open local stagefile %s\", stagefile.c_str());\n        proxy->CleanWriteQueue();\n        {\n          XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:fromremote:local:failed\"]++;\n        }\n        mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                         \"hint='failed to open local stagefile'\"));\n        return EREMOTEIO;\n      }\n    }\n\n    if (req && begin_flush(req)) {\n      eos_warning(\"failed to signal begin-flush\");\n    }\n\n    if (recover_from_file_cache) {\n      eos_debug(\"recovering from local start cache into stage file %s\",\n                stagefile.c_str());\n      // make sure the buffer size fits\n      buffer->resize(mFile->file()->size());\n      buffer->reserve(mFile->file()->size());\n      buf = (void*) buffer->ptr();\n\n      // recover file from the local start cache\n      if (mFile->file()->pread(buf, mFile->file()->size(), 0) < 0) {\n        if (!recover_truncate) {\n          sBufferManager.put_buffer(buffer);\n        }\n\n        eos_crit(\"unable to read file for recovery from local file cache\");\n\n        if (req && end_flush(req)) {\n          eos_warning(\"failed to signal end-flush\");\n        }\n\n        proxy->CleanWriteQueue();\n        {\n          XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:fromcache:read:failed\"]++;\n        }\n        mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                         \"hint='unable to read file for recovery from local cache file'\"));\n        return EIO;\n      }\n    } else {\n      eos_debug(\"recovering from remote file into stage file %s\", stagefile.c_str());\n\n      if (!recover_truncate) {\n        // download all into local stagefile, we don't need to do this, if there is a truncate request\n        uint32_t bytesRead = 0;\n        // get size of the file to download\n        XrdCl::StatInfo* statInfo;\n        status = newproxy->Stat(false, statInfo);\n\n        if (!status.IsOK() || !statInfo) {\n          // bail out\n          return EREMOTEIO;\n        }\n\n        auto sourcesize = statInfo->GetSize();\n        delete statInfo;\n        // try to pre-allocate the size locally\n        int rc = 0;\n#ifdef Linux\n\trc = posix_fallocate(fd, 0, sourcesize);\n#endif\n\n        if (rc) {\n          return ENOSPC;\n        }\n\n        do {\n          status = newproxy->Read(newproxy, off, size, buf, bytesRead);\n          eos_debug(\"off=%lu bytesread=%u\", off, bytesRead);\n\n          if (!status.IsOK()) {\n            sBufferManager.put_buffer(buffer);\n            eos_warning(\"failed to read remote file for recovery msg='%s'\",\n                        status.ToString().c_str());\n            mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                             \"status='%s' hint='failed to read remote file for recovery'\",\n                                             status.ToString().c_str()));\n\n            if (req && end_flush(req)) {\n              eos_warning(\"failed to signal end-flush\");\n            }\n\n            proxy->CleanWriteQueue();\n            {\n              XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:fromremote:read:failed\"]++;\n            }\n            return EREMOTEIO;\n          } else {\n            off += bytesRead;\n          }\n\n          ssize_t wr = ::write(fd, buf, bytesRead);\n\n          if (wr != bytesRead) {\n            sBufferManager.put_buffer(buffer);\n            eos_crit(\"failed to write to local stage file %s\", stagefile.c_str());\n\n            if (req && end_flush(req)) {\n              eos_warning(\"failed to signal end-flush\");\n            }\n\n            proxy->CleanWriteQueue();\n            {\n              XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:fromremote:localwrite:failed\"]++;\n            }\n            mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                             \"hint='failed to write to local stage file'\"));\n            return EREMOTEIO;\n          }\n        } while (bytesRead > 0);\n      }\n    }\n\n    // upload into identical inode using the drop & replace option (repair flag)\n    XrdCl::shared_proxy uploadproxy = XrdCl::Proxy::Factory(newproxy);\n    uploadproxy->inherit_attached(proxy);\n    uploadproxy->inherit_writequeue(uploadproxy, proxy);\n\n    // we have to remove the flush otherwise we cannot open this file even ourselfs\n    if (req && end_flush(req)) {\n      eos_warning(\"failed to signal begin-flush\");\n    }\n\n    std::string tmpUrl = mRemoteUrlRW;\n    // add the repair flag to drop existing locations and select new ones\n    tmpUrl += \"&eos.repair=1\";\n    // request enough space for this recovery upload\n    tmpUrl += \"&eos.bookingsize=0\";\n    eos_warning(\"re-opening with repair flag for recovery %s\",\n                tmpUrl.c_str());\n    int rc = try_wopen(req, uploadproxy, tmpUrl);\n\n    // put back the flush indicator\n    if (req && begin_flush(req)) {\n      eos_warning(\"failed to signal begin-flush\");\n    }\n\n    if (rc) {\n      if (!recover_truncate) {\n        sBufferManager.put_buffer(buffer);\n      }\n\n      if (req && end_flush(req)) {\n        eos_warning(\"failed to signal end-flush\");\n      }\n\n      proxy->CleanWriteQueue();\n      {\n        XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:fromremote:beginflush:failed\"]++;\n      }\n      mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                       \"hint='failed to signal endflush rc=%d'\", rc));\n      return rc;\n    }\n\n    off_t upload_offset = 0;\n\n    if (!recover_truncate) {\n      ssize_t nr = 0;\n\n      do {\n        nr = ::pread(fd, buf, size, upload_offset);\n\n        if (nr < 0) {\n          sBufferManager.put_buffer(buffer);\n          eos_crit(\"failed to read from local stagefile\");\n\n          if (req && end_flush(req)) {\n            eos_warning(\"failed to signal end-flush\");\n          }\n\n          sBufferManager.put_buffer(buffer);\n          {\n            uploadproxy->WaitWrite(req);\n          }\n\n          if (req && end_flush(req)) {\n            eos_warning(\"failed to signal end-flush\");\n          }\n\n          proxy->CleanWriteQueue();\n          {\n            XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:fromremote:endflush:failed\"]++;\n          }\n          mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                           \"hint='failed to read from local stagefile errno=%d'\", errno));\n          return EREMOTEIO;\n        }\n\n        if (nr) {\n          // send asynchronous upstream writes\n          XrdCl::Proxy::write_handler handler = uploadproxy->WriteAsyncPrepare(\n                                                  uploadproxy, nr,\n                                                  upload_offset, 60);\n          uploadproxy->ScheduleWriteAsync(buf, handler);\n          upload_offset += nr;\n        }\n      } while (nr > 0);\n\n      // collect the writes to verify everything is alright now\n      uploadproxy->WaitWrite(req);\n\n      if (!uploadproxy->write_state().IsOK()) {\n        sBufferManager.put_buffer(buffer);\n        eos_crit(\"got failure when collecting outstanding writes from the upload proxy\");\n\n        if (req && end_flush(req)) {\n          eos_warning(\"failed to signal end-flush\");\n        }\n\n        proxy->CleanWriteQueue();\n        {\n          XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:fromremote:write:failed\"]++;\n        }\n        return EREMOTEIO;\n      }\n\n      mRecoveryStack.push_back(eos_log(LOG_SILENT, \"uploaded-bytes=%lu\",\n                                       upload_offset));\n      sBufferManager.put_buffer(buffer);\n    }\n\n    eos_notice(\"finished write recovery successfully\");\n    // replace the proxy object\n    mFile->set_xrdiorw(req, uploadproxy);\n    proxy->CleanWriteQueue();\n    proxy->detach();\n\n    // replay the journal\n    if (mFile->journal()) {\n      if ((rc = journalflush(req))) {\n        mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                         \"errno='%d' hint='failed journalflush'\",\n                                         rc));\n        eos_err(\"journal-flushing failed rc=%d\", rc);\n        {\n          XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:journalflush:failed\"]++;\n        }\n        mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                         \"hint='journal-flushing failed rc=%d'\", rc));\n        return rc;\n      } else {\n        mRecoveryStack.push_back(eos_log(LOG_SILENT, \"hint='success journalflush'\"));\n        {\n          XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:journalflush:success\"]++;\n        }\n      }\n    }\n\n    // re-open the file centrally for access\n    if (req && end_flush(req)) {\n      eos_warning(\"failed to signal end-flush\");\n    }\n  } else {\n    eos_crit(\"no local cache data for recovery\");\n    proxy->CleanWriteQueue();\n    {\n      XrdCl::Proxy::ProxyStatHandle::Get()->Stats()[\"recover:write:nocache:failed\"]++;\n    }\n    mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                     \"hint='no local cache data for recovery'\"));\n    return EREMOTEIO;\n  }\n\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\ndata::datax::begin_flush(fuse_req_t req)\n/* -------------------------------------------------------------------------- */\n{\n  return EosFuse::Instance().mds.begin_flush(req, mMd,\n         std::string(\"repair\")); // flag an ongoing flush centrally\n}\n\n/* -------------------------------------------------------------------------- */\nint\ndata::datax::end_flush(fuse_req_t req)\n/* -------------------------------------------------------------------------- */\n{\n  return EosFuse::Instance().mds.end_flush(req, mMd,\n         std::string(\"repair\")); // unflag an ongoing flush centrally\n}\n\n/* -------------------------------------------------------------------------- */\nint\ndata::datax::detach(fuse_req_t req, std::string& cookie, int flags)\n/* -------------------------------------------------------------------------- */\n{\n  bool isRW = false;\n\n  if (flags & (O_RDWR | O_WRONLY)) {\n    isRW = true;\n  }\n\n  eos_info(\"cookie=%s flags=%o isrw=%d\", cookie.c_str(), flags, isRW);\n  XrdSysMutexHelper lLock(mLock);\n  int bcache = mFile->file() ? mFile->file()->detach(cookie) : 0;\n  int jcache = mFile->journal() ? ((isRW ||\n                                    (mFlags & O_CACHE)) ? mFile->journal()->detach(\n                                     cookie) : 0) : 0;\n  int xio = 0;\n\n  if (isRW) {\n    if (mFile->has_xrdiorw(req)) {\n      mFile->xrdiorw(req)->detach();\n    }\n  } else {\n    if (mFile->has_xrdioro(req)) {\n      mFile->xrdioro(req)->detach();\n    }\n  }\n\n  return bcache | jcache | xio;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndata::datax::store_cookie(std::string& cookie)\n/* -------------------------------------------------------------------------- */\n{\n  eos_info(\"cookie=%s\", cookie.c_str());\n  int bc = mFile->file() ? mFile->file()->set_cookie(cookie) : 0;\n  int jc = mFile->journal() ? mFile->journal()->set_cookie(cookie) : 0;\n  return bc | jc;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndata::datax::unlink(fuse_req_t req)\n/* -------------------------------------------------------------------------- */\n{\n  eos_info(\"\");\n  cachehandler::instance().rm(mIno);\n  int bcache = mFile->file() ? mFile->file()->unlink() : 0;\n  int jcache = mFile->journal() ? mFile->journal()->unlink() : 0;\n  mIsUnlinked = true;\n  FlagDeleted();\n  return bcache | jcache;\n}\n// IO bridge interface\n\n/* -------------------------------------------------------------------------- */\nssize_t\n/* -------------------------------------------------------------------------- */\ndata::datax::pread(fuse_req_t req, void* buf, size_t count, off_t offset)\n/* -------------------------------------------------------------------------- */\n{\n  eos_info(\"offset=%llu count=%lu\", offset, count);\n  mLock.Lock();\n\n  if (mFile->journal()) {\n    ssize_t jts = ((mFile->journal()))->get_truncatesize();\n\n    if (jts >= 0) {\n      // reduce reads in case of truncation stored in the journal\n      if ((ssize_t) offset > jts) {\n        offset = 0;\n        count = 0;\n      } else {\n        if ((ssize_t)(offset + count) > jts) {\n          count = jts - offset;\n        }\n      }\n    }\n  }\n\n  if (inline_buffer && inlined() &&\n      (count + offset) < mInlineMaxSize) {\n    // possibly return data from an inlined buffer\n    ssize_t avail_bytes = 0;\n\n    if (((size_t) offset < (*mMd)()->size())) {\n      if ((offset + count) > (*mMd)()->size()) {\n        avail_bytes = (*mMd)()->size() - offset;\n      } else {\n        avail_bytes = count;\n      }\n    } else {\n      avail_bytes = 0;\n    }\n\n    memcpy(buf, inline_buffer->ptr() + offset, avail_bytes);\n    mLock.UnLock();\n    return avail_bytes;\n  }\n\n  ssize_t br = 0;\n\n  if (mFile->file()) {\n    // read from file start cache\n    br = mFile->file()->pread(buf, count, offset);\n  }\n\n  if (br < 0) {\n    mLock.UnLock();\n    return br;\n  }\n\n  if (br == (ssize_t) count) {\n    mLock.UnLock();\n    return br;\n  }\n\n  if (mFile->file() && (offset < mFile->file()->prefetch_size())) {\n    mLock.UnLock();\n\n    if (prefetch(req)) {\n      WaitPrefetch(req);\n      mLock.Lock();\n      ssize_t br = mFile->file()->pread(buf, count, offset);\n\n      if (br < 0) {\n        mLock.UnLock();\n        return br;\n      }\n\n      if (br == (ssize_t) count) {\n        mLock.UnLock();\n        return br;\n      }\n    } else {\n      mLock.Lock();\n    }\n  }\n\n  ssize_t jr = 0;\n\n  if (mFile->journal()) {\n    // read from journal cache\n    jr = mFile->journal() ? mFile->journal()->pread((char*) buf + br, count - br,\n         offset + br) : 0;\n  }\n\n  if (jr < 0) {\n    mLock.UnLock();\n    return jr;\n  }\n\n  if ((br + jr) == (ssize_t) count) {\n    mLock.UnLock();\n    return (br + jr);\n  }\n\n  // read the missing part remote\n  XrdCl::shared_proxy proxy = mFile->has_xrdioro(req) ? mFile->xrdioro(\n                                req) : mFile->xrdiorw(req);\n  XrdCl::XRootDStatus status;\n\n  if (proxy) {\n    if (proxy->IsOpening()) {\n      status = proxy->WaitOpen();\n    }\n\n    if (!mFile->is_caching()) {\n      // if caching is disabled, we wait for outstanding writes\n      status = proxy->WaitWrite();\n      // it is not obvious what we should do if there was a write error,\n      // we just proceed\n    }\n\n    uint32_t bytesRead = 0;\n\n    if (proxy->Read(proxy,\n                    offset + br,\n                    count - br,\n                    (char*) buf + br,\n                    bytesRead).IsOK()) {\n      mLock.UnLock();\n      std::vector<journalcache::chunk_t> chunks;\n\n      if (mFile->journal()) {\n        // retrieve all journal chunks matching our range\n        chunks = ((mFile->journal()))->get_chunks(offset + br, count - br);\n\n        for (auto it = chunks.begin(); it != chunks.end(); ++it) {\n          eos_err(\"offset=%ld count=%lu overlay-chunk offset=%ld size=%lu\\n\", offset,\n                  count, it->offset, it->size);\n          // overlay journal contents again over the remote contents\n          ssize_t ljr = mFile->journal()->pread((char*) buf + br +\n                                                (it->offset - offset - br), it->size, it->offset);\n\n          if (ljr >= 0) {\n            // check if the journal contents extends the remote read\n            ssize_t chunkread = it->offset + it->size - offset - br;\n\n            if (chunkread > bytesRead) {\n              bytesRead = chunkread;\n            }\n          }\n        }\n\n        if (mFile->journal()) {\n          eos_info(\"offset=%ld count=%lu journal-max=%ld\\n\", offset, count,\n                   mFile->journal()->get_max_offset());\n\n          // check if there is a chunk in the journal which extends the file size,\n          // so we have to extend the read\n          if (mFile->journal()->get_max_offset() > (off_t)(offset + br + bytesRead)) {\n            if (mFile->journal()->get_max_offset() > (off_t)(offset  + count)) {\n              // the last journal entry extends over the requested range, we got all bytes\n              bytesRead = count;\n            } else {\n              //  this should not be required, because logically we cannot get here\n              eos_err(\"consistency error : max-journal=%ld offset=%ld count=%lu br=%lu bytesread=%lu\",\n                      mFile->journal()->get_max_offset(), offset, count, br, bytesRead);\n              // we don't set bytesread here!\n            }\n          }\n        }\n      }\n\n      eos_info(\"count=%lu read-bytes=%lu\", count, br + bytesRead);\n\n      if ((size_t)(br + bytesRead)  > count) {\n        return count;\n      } else {\n        return (br + bytesRead);\n      }\n\n      return (br + bytesRead);\n    } else {\n      mLock.UnLock();\n      errno = EREMOTEIO;\n      // IO error\n      return -1;\n    }\n  }\n\n  mLock.UnLock();\n  errno = EFAULT;\n  return -1;\n}\n\n/* -------------------------------------------------------------------------- */\nssize_t\n/* -------------------------------------------------------------------------- */\ndata::datax::pwrite(fuse_req_t req, const void* buf, size_t count, off_t offset)\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper lLock(mLock);\n  eos_info(\"offset=%llu count=%lu\", offset, count);\n  ssize_t dw = 0;\n\n  // inlined-files\n  if (inline_buffer) {\n    if ((count + offset) < mInlineMaxSize) {\n      // copy into file inline buffer\n      inline_buffer->writeData(buf, offset, count);\n    }\n  }\n\n  if (mFile->file()) {\n    if (mFile->file()->size() || (mFlags & O_CREAT)) {\n      // don't write into the file start cache, if it is currently empty and it is not a newly created file\n      dw = mFile->file()->pwrite(buf, count, offset);\n    }\n  }\n\n  if (dw < 0) {\n    return dw;\n  } else {\n    if (mFile->journal()) {\n      if (!mFile->journal()->fits(count)) {\n        int rc = flush_nolock(req, true, true);\n\n        if (rc) {\n          eos_warning(\"flush failed with errno=%d\", rc);\n          errno = rc;\n          return -1;\n        }\n      }\n\n      // now there is space to write for us\n      ssize_t jw = mFile->journal()->pwrite(buf, count, offset);\n\n      if (jw < 0) {\n        return jw;\n      }\n\n      dw = jw;\n    }\n\n    {\n      // stop sending more writes in case of unrecoverable errors\n      XrdCl::shared_proxy proxy = mFile->xrdiorw(req);\n\n      // block writes on read-only fds\n      if (!proxy) {\n        errno = EROFS;\n        return -1;\n      }\n\n      if (proxy->opening_state().IsError() &&\n          ! proxy->opening_state_should_retry()) {\n        eos_err(\"unrecoverable error - code=%d errNo=%d\",\n                proxy->opening_state().code,\n                proxy->opening_state().errNo);\n        proxy->CleanWriteQueue();\n        errno = XrdCl::Proxy::status2errno(proxy->opening_state());\n        return -1;\n      }\n    }\n\n    // send an asynchronous upstream write, which does not wait for the file open to be done\n    XrdCl::Proxy::write_handler handler =\n      mFile->xrdiorw(req)->WriteAsyncPrepare(mFile->xrdiorw(req), count, offset, 60);\n    XrdCl::XRootDStatus status =\n      mFile->xrdiorw(req)->ScheduleWriteAsync(buf, handler);\n    // test if we switch to xoff mode, where we only write into the journal\n    size_t cnt = 0;\n\n    while (mFile->xrdiorw(req)->HasTooManyWritesInFlight()) {\n      if (!(cnt % 1000)) {\n        eos_debug(\"doing XOFF\");\n      }\n\n      EosFuse::instance().datas.set_xoff();\n      mXoff = true;\n      std::string msg;\n\n      if (mFile->xrdiorw(req)->HadFailures(msg)) {\n        eos_err(\"file state failure during xoff - switching to sync mode msg='%s'\",\n                msg.c_str());\n        // if we had failures we change into synchronous mode to be able to trigger appropriate recovery\n        mFlags |= O_SYNC;\n        break;\n      }\n\n      std::this_thread::sleep_for(std::chrono::milliseconds(5));\n      cnt++;\n    }\n\n    mXoff = false;\n\n    if ((!status.IsOK()) && (!EosFuse::Instance().Config().recovery.write)) {\n      errno = XrdCl::Proxy::status2errno(status);\n      eos_err(\"async remote-io failed msg=\\\"%s\\\"\", status.ToString().c_str());\n      return -1;\n    }\n\n    if (mFlags & O_SYNC) {\n      eos_debug(\"O_SYNC\");\n      // make sure the file gets opened\n      XrdCl::XRootDStatus status = mFile->xrdiorw(req)->WaitOpen();\n\n      if (!status.IsOK()) {\n        mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                         \"status='%s' hint='will TryRecovery'\",\n                                         status.ToString().c_str()));\n        int tret = 0;\n\n        if ((tret = TryRecovery(req, true))) {\n          errno = XrdCl::Proxy::status2errno(status);\n          eos_err(\"pseudo-sync remote-io failed msg=\\\"%s\\\"\", status.ToString().c_str());\n          mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                           \"status='%s' errno='%d' hint='failed TryRecovery'\",\n                                           status.ToString().c_str(), tret));\n          return -1;\n        } else {\n          mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                           \"triggering-status='%s' hint='success TryRecovery'\",\n                                           status.ToString().c_str()));\n          // re-send the write again\n          XrdCl::Proxy::write_handler handler =\n            mFile->xrdiorw(req)->WriteAsyncPrepare(mFile->xrdiorw(req), count, offset, 60);\n          XrdCl::XRootDStatus status =\n            mFile->xrdiorw(req)->ScheduleWriteAsync(buf, handler);\n        }\n      }\n\n      // make sure all writes were successful\n      status = mFile->xrdiorw(req)->WaitWrite();\n\n      if (!status.IsOK()) {\n        mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                         \"status='%s' hint='will TryRecovery'\",\n                                         status.ToString().c_str()));\n        int tret = 0;\n\n        if ((tret = TryRecovery(req, true))) {\n          errno = XrdCl::Proxy::status2errno(status);\n          eos_err(\"pseudo-sync remote-io failed msg=\\\"%s\\\"\", status.ToString().c_str());\n          mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                           \"status='%s' errno='%d' hint='failed TryRecovery'\",\n                                           status.ToString().c_str(), tret));\n          return -1;\n        } else {\n          mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                           \"triggering-status='%s' hint='success TryRecovery'\",\n                                           status.ToString().c_str()));\n          // re-send the write again\n          XrdCl::Proxy::write_handler handler =\n            mFile->xrdiorw(req)->WriteAsyncPrepare(mFile->xrdiorw(req), count, offset, 60);\n          XrdCl::XRootDStatus status =\n            mFile->xrdiorw(req)->ScheduleWriteAsync(buf, handler);\n          status = mFile->xrdiorw(req)->WaitWrite();\n\n          if (!status.IsOK()) {\n            errno = XrdCl::Proxy::status2errno(status);\n            eos_err(\"pseudo-sync remote-io failed msg=\\\"%s\\\"\", status.ToString().c_str());\n            mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                             \"status='%s' hint='failed resending writes after successful recovery'\",\n                                             status.ToString().c_str()));\n            return -1;\n          }\n        }\n      }\n    }\n  }\n\n  if ((off_t)(offset + count) > mSize) {\n    mSize = count + offset;\n  }\n\n  eos_info(\"offset=%llu count=%lu result=%d\", offset, count, dw);\n  return dw;\n}\n\n/* -------------------------------------------------------------------------- */\nssize_t\n/* -------------------------------------------------------------------------- */\ndata::datax::peek_pread(fuse_req_t req, char*& buf, size_t count, off_t offset)\n/* -------------------------------------------------------------------------- */\n{\n  size_t md_size = 0;\n  {\n    XrdSysMutexHelper lLock(mMd->Locker());\n    md_size = (*mMd)()->size();\n  }\n  mLock.Lock();\n  eos_info(\"offset=%llu count=%lu size=%lu\", offset, count, md_size);\n\n  if (mFile->journal()) {\n    ssize_t jts = ((mFile->journal()))->get_truncatesize();\n\n    if (jts >= 0) {\n      // reduce reads in case of truncation stored in the journal\n      if ((ssize_t) offset > jts) {\n        offset = 0;\n        count = 0;\n      } else {\n        if ((ssize_t)(offset + count) > jts) {\n          count = jts - offset;\n        }\n      }\n    }\n  }\n\n  buffer = sBufferManager.get_buffer(count);\n  buf = buffer->ptr();\n\n  if (inline_buffer && inlined()) {\n    // possibly return data from an inlined buffer\n    ssize_t avail_bytes = 0;\n\n    if (((size_t) offset < md_size)) {\n      if ((offset + count) > md_size) {\n        avail_bytes = md_size - offset;\n      } else {\n        avail_bytes = count;\n      }\n    } else {\n      avail_bytes = 0;\n    }\n\n    if (md_size <= (unsigned long long) inline_buffer->getSize()) {\n      memcpy(buf, inline_buffer->ptr() + offset, avail_bytes);\n      eos_debug(\"inline-read byte=%lld inline-buffer-size=%lld\", avail_bytes,\n                inline_buffer->getSize());\n      return avail_bytes;\n    }\n  }\n\n  ssize_t br = 0;\n\n  if (mFile->file()) {\n    br = mFile->file()->pread(buf, count, offset);\n\n    if (EOS_LOGS_DEBUG) {\n      eos_debug(\"disk-read:%ld\", br);\n    }\n\n    if (br < 0) {\n      return br;\n    }\n\n    if ((br == (ssize_t) count) || (br == (ssize_t)md_size)) {\n      return br;\n    }\n  }\n\n  if (mFile->file() && (offset < mFile->file()->prefetch_size())) {\n    if (prefetch(req, false)) {\n      WaitPrefetch(req, false);\n      ssize_t br = mFile->file()->pread(buf, count, offset);\n\n      if (br < 0) {\n        return br;\n      }\n\n      if (br == (ssize_t) count) {\n        if (mFile->journal() && (mFlags & O_CACHE)) {\n          // optionally populate the read journal cache\n          mFile->journal()->pwrite(buf, count, offset);\n        }\n\n        return br;\n      }\n    }\n  }\n\n  ssize_t jr = 0;\n\n  if (mFile->journal()) {\n    jr = mFile->journal() ? mFile->journal()->pread(buf + br, count - br,\n         offset + br) : 0;\n\n    if (jr < 0) {\n      return jr;\n    }\n\n    if ((br + jr) == (ssize_t) count) {\n      return (br + jr);\n    }\n  }\n\n  // read the missing part remote\n  XrdCl::shared_proxy proxy = mFile->has_xrdioro(req) ? mFile->xrdioro(\n                                req) : mFile->xrdiorw(req);\n  XrdCl::XRootDStatus status;\n  eos_debug(\"ro=%d offset=%llu count=%lu br=%lu jr=%lu\", mFile->has_xrdioro(req),\n            offset, count, br, jr);\n\n  if (proxy) {\n    if (proxy->IsOpening()) {\n      status = proxy->WaitOpen();\n    }\n\n    if (mFile->has_xrdiorw(req)) {\n      if (!status.IsOK()) {\n        mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                         \"status='%s' hint='will TryRecovery'\",\n                                         status.ToString().c_str()));\n        int tret = 0;\n\n        // call recovery for an open\n        if ((tret = TryRecovery(req, true))) {\n          mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                           \"status='%s' errno='%d' hint='failed TryRecovery'\",\n                                           status.ToString().c_str(), tret));\n          errno = XrdCl::Proxy::status2errno(status);\n          eos_err(\"sync remote-io failed msg=\\\"%s\\\"\", status.ToString().c_str());\n          return -1;\n        } else {\n          mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                           \"triggering-status='%s' hint='success TryRecovery'\",\n                                           status.ToString().c_str()));\n          // get the new proxy object, the recovery might exchange the file object\n          proxy = mFile->has_xrdioro(req) ? mFile->xrdioro(req) : mFile->xrdiorw(\n                    req); // recovery might change the proxy object\n        }\n      }\n    }\n\n    if (mFile->has_xrdiorw(req)) {\n      XrdCl::shared_proxy wproxy = mFile->xrdiorw(req);\n\n      if (wproxy->OutstandingWrites()) {\n        status = wproxy->WaitWrite();\n      }\n\n      if (!status.IsOK()) {\n        errno = XrdCl::Proxy::status2errno(status);\n        eos_err(\"sync remote-io failed msg=\\\"%s\\\"\", status.ToString().c_str());\n        return -1;\n      }\n    }\n\n    uint32_t bytesRead = 0;\n    int recovery = 0;\n\n    while (1) {\n      // if the recovery failed already once, we continue to silently return this error\n      if (!can_recover_read()) {\n        errno = XrdCl::Proxy::status2errno(status);\n\n        if (EOS_LOGS_DEBUG) {\n          eos_debug(\"sync remote-io failed msg=\\\"%s\\\" previously - recovery disabled\",\n                    status.ToString().c_str());\n        }\n\n        return -1;\n      }\n\n      proxy = mFile->has_xrdioro(req) ? mFile->xrdioro(req) : mFile->xrdiorw(\n                req); // recovery might change the proxy object\n\n      if (proxy->IsOpening()) {\n        status = proxy->WaitOpen();\n      }\n\n      status = proxy->Read(proxy,\n                           offset + br + jr,\n                           count - br - jr,\n                           (char*) buf + br + jr,\n                           bytesRead);\n\n      if (!status.IsOK()) {\n        // read failed\n        mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                         \"status='%s' hint='will TryRecovery'\",\n                                         status.ToString().c_str()));\n        recovery = TryRecovery(req, mFile->has_xrdioro(req) ? false : true);\n\n        if (recovery) {\n          // recovery failed\n          mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                           \"status='%s' errno='%d' hint='failed TryRecovery'\",\n                                           status.ToString().c_str(), recovery));\n          break;\n        } else {\n          // recovery succeeded\n          mRecoveryStack.push_back(eos_log(LOG_SILENT,\n                                           \"triggering-status='%s' hint='success TryRecovery'\",\n                                           status.ToString().c_str()));\n        }\n      } else {\n        // read succeeded\n        break;\n      }\n    }\n\n    if (recovery) {\n      errno = recovery;\n      disable_read_recovery();\n      eos_err(\"sync remote-io recovery failed errno=%d\", errno);\n      return -1;\n    }\n\n    if (status. IsOK()) {\n      std::vector<journalcache::chunk_t> chunks;\n\n      if (mFile->journal()) {\n        // retrieve all journal chunks matching our range\n        chunks = ((mFile->journal()))->get_chunks(offset + br, count - br);\n\n        for (auto it = chunks.begin(); it != chunks.end(); ++it) {\n          eos_info(\"offset=%ld count=%lu overlay-chunk offset=%ld size=%lu\", offset,\n                   count, it->offset, it->size);\n          // overlay journal contents again over the remote contents\n          ssize_t ljr = mFile->journal()->pread((char*) buf + br +\n                                                (it->offset - offset - br), it->size, it->offset);\n\n          if (ljr >= 0) {\n            // check if the journal contents extends the remote read\n            ssize_t chunkread = it->offset + it->size - offset - br;\n\n            if (chunkread > bytesRead) {\n              bytesRead = chunkread;\n            }\n          }\n        }\n\n        eos_info(\"offset=%ld count=%lu bytes-read=%lu journal-max=%ld\\n\", offset, count,\n                 bytesRead, mFile->journal()->get_max_offset());\n\n        // check if there is a chunk in the journal which extends the file size,\n        // so we have to extend the read\n        if (mFile->journal()->get_max_offset() > (off_t)(offset + br + bytesRead)) {\n          if (mFile->journal()->get_max_offset() > (off_t)(offset  + count)) {\n            // the last journal entry extends over the requested range, we got all bytes\n            bytesRead = count;\n          } else {\n            //  this should not be required, because logically we cannot get here\n            bytesRead = mFile->journal()->get_max_offset() - offset;\n          }\n        }\n      }\n\n      eos_info(\"count=%lu read-bytes=%lu\", count, br + bytesRead);\n\n      if (mFile->journal() && (mFlags & O_CACHE)) {\n        // optionally populate the read journal cache\n        mFile->journal()->pwrite(buf, br + bytesRead, offset);\n      }\n\n      if ((size_t)(br + bytesRead)  > count) {\n        return count;\n      } else {\n        return (br + bytesRead);\n      }\n    } else {\n      errno = XrdCl::Proxy::status2errno(status);\n      eos_err(\"sync remote-io failed msg=\\\"%s\\\"\", status.ToString().c_str());\n      return -1;\n    }\n  }\n\n  return -1;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\ndata::datax::release_pread()\n/* -------------------------------------------------------------------------- */\n{\n  eos_info(\"\");\n  sBufferManager.put_buffer(buffer);\n  buffer.reset();\n  mLock.UnLock();\n  return;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndata::datax::truncate(fuse_req_t req, off_t offset)\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper lLock(mLock);\n  eos_info(\"offset=%llu size=%llu\", offset, mSize);\n  int dt = 0;\n\n  if (inline_buffer) {\n    if (inlined()) {\n      if (((size_t) offset) < mInlineMaxSize) {\n        // truncate file inline buffer\n        inline_buffer->truncateData(offset);\n      }\n    } else {\n      if (offset == 0) {\n        // we can re-enable the inlining for such a file\n        inline_buffer->truncateData(0);\n        mIsInlined = true;\n      }\n    }\n  }\n\n  if (mFile->file()) {\n    if (offset <= mFile->file()->prefetch_size()) {\n      // if the truncate falls into the file cache size, we have disable it because\n      // subsequent writes can stamp a whole inside the file cache\n      dt = mFile->file()->truncate(0);\n      remove_file_cache();\n    }\n  }\n\n  // if we have a journal it tracks the truncation size\n  int jt = 0;\n\n  if (mFile->journal()) {\n    jt = mFile->journal() ? mFile->journal()->truncate(offset) : 0;\n  }\n\n  eos_info(\"dt=%d jt=%d\", dt, jt);\n\n  if (!mFile->journal()) {\n    if (mFile->has_xrdiorw(req)) {\n      if (mFile->xrdiorw(req)->IsOpening()) {\n        mFile->xrdiorw(req)->WaitOpen();\n      }\n\n      mFile->xrdiorw(req)->WaitWrite();\n      // the journal keeps track of truncation, otherwise or for O_SYNC we do it here\n      XrdCl::XRootDStatus status = mFile->xrdiorw(req)->Truncate(offset);\n      errno = XrdCl::Proxy::status2errno(status);\n\n      if (!status.IsOK()) {\n        return -1;\n      }\n    } else {\n      errno = EFAULT;\n      return -1;\n    }\n  }\n\n  if (!(dt | jt)) {\n    mSize = offset;\n  }\n\n  return dt | jt;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndata::datax::sync()\n/* -------------------------------------------------------------------------- */\n{\n  eos_info(\"\");\n  int ds = 0;\n\n  if (mFile->file()) {\n    ds = mFile->file()->sync();\n  }\n\n  int js = 0;\n\n  if (mFile->journal()) {\n    js = mFile->journal() ? mFile->journal()->sync() : 0;\n  }\n\n  bool journal_recovery = false;\n\n  for (auto it = mFile->get_xrdiorw().begin();\n       it != mFile->get_xrdiorw().end(); ++it) {\n    if (it->second->IsOpening()) {\n      it->second->WaitOpen();\n    }\n\n    XrdCl::XRootDStatus status = it->second->WaitWrite();\n\n    if (!status.IsOK()) {\n      errno = XrdCl::Proxy::status2errno(status);\n      journal_recovery = true;\n    } else {\n      status = it->second->Sync();\n\n      if (!status.IsOK()) {\n        errno = XrdCl::Proxy::status2errno(status);\n        journal_recovery = true;\n      }\n    }\n  }\n\n  if (journal_recovery) {\n    eos_err(\"syncing failed\");\n    return -1;\n  }\n\n  return ds | js;\n}\n\n/* -------------------------------------------------------------------------- */\nsize_t\n/* -------------------------------------------------------------------------- */\ndata::datax::size()\n/* -------------------------------------------------------------------------- */\n{\n  eos_info(\"\");\n  off_t dsize = mFile->file() ? mFile->file()->size() : 0;\n\n  if (mSize > dsize) {\n    return mSize;\n  }\n\n  return dsize;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndata::datax::cache_invalidate()\n/* -------------------------------------------------------------------------- */\n{\n  eos_info(\"\");\n  XrdSysMutexHelper lLock(mLock);\n  // truncate the block cache\n  int dt = mFile->file() ? mFile->file()->truncate(0) : 0;\n  int jt = mFile->journal() ? mFile->journal()->truncate(0, true) : 0;\n  inline_buffer = nullptr;\n\n  for (auto fit = mFile->get_xrdioro().begin();\n       fit != mFile->get_xrdioro().end(); ++fit) {\n    if (fit->second->IsOpen()) {\n      fit->second->DropReadAhead();\n    }\n  }\n\n  return dt | jt;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\ndata::datax::set_remote(const std::string& hostport,\n                        const std::string& basename,\n                        const uint64_t md_ino,\n                        const uint64_t md_pino,\n                        fuse_req_t req,\n                        bool isRW)\n/* -------------------------------------------------------------------------- */\n{\n  eos_info(\"\");\n  std::string remoteurl;\n  remoteurl = \"root://\";\n  remoteurl += hostport;\n  remoteurl += \"//fusex-open\";\n  remoteurl += \"?eos.lfn=\";\n\n  if (md_ino) {\n    remoteurl += \"ino:\";\n    char sino[128];\n    snprintf(sino, sizeof(sino), \"%lx\", md_ino);\n    remoteurl += sino;\n  } else {\n    remoteurl += \"pino:\";\n    char pino[128];\n    snprintf(pino, sizeof(pino), \"%lx\", md_pino);\n    remoteurl += pino;\n    remoteurl += \"/\";\n    remoteurl += basename;\n  }\n\n  std::string appname;\n\n  if (EosFuse::Instance().mds.supports_appname()) {\n    appname =  EosFuse::Instance().Config().appname;\n  } else {\n    appname = \"fuse\";\n  }\n\n  remoteurl += \"&eos.app=\";\n  remoteurl += appname;\n  remoteurl += \"&mgm.mtime=0&mgm.fusex=1&eos.bookingsize=0\";\n\n  if (!isRW) {\n    // we don't check checksums in read, because we might read a file which is open and it does not have\n    // a final checksum when we read over the end\n    remoteurl += \"&eos.checksum=ignore\";\n  }\n\n  XrdCl::URL url(remoteurl);\n  XrdCl::URL::ParamsMap query = url.GetParams();\n  fusexrdlogin::loginurl(url, query, req, md_ino);\n  url.SetParams(query);\n  remoteurl = url.GetURL();\n\n  if (isRW) {\n    mRemoteUrlRW = remoteurl;\n  } else {\n    mRemoteUrlRO = remoteurl;\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\n/* -------------------------------------------------------------------------- */\ndata::datax::get_remote(bool isRW)\n/* -------------------------------------------------------------------------- */\n{\n  if (isRW) {\n    return mRemoteUrlRW;\n  }\n  return mRemoteUrlRO;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\ndata::datax::dump_recovery_stack()\n/* -------------------------------------------------------------------------- */\n{\n  size_t i = 0;\n  char n[8];\n\n  if (mRecoveryStack.size()) {\n    std::stringstream sdump;\n    sdump << \"#      -------------------\" << std::endl;\n    sdump << \"#      - recovery record -\" << std::endl;\n    sdump << \"#      -------------------\" << std::endl;\n    sdump << \"#        path := '\" << fullpath() << \"'\" << std::endl;\n    sdump << \"#        fid  := \" << fid() << std::endl;\n\n    for (auto it : mRecoveryStack) {\n      snprintf(n, sizeof(n), \"%03lu\", i);\n      sdump << \"#        -[ \" << n << \" ] \" << it << std::endl;\n      ++i;\n    }\n\n    fprintf(stderr, \"%s\\n\", sdump.str().c_str());\n    fflush(stderr);\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nconst char*\ndata::datax::Dump(std::string& out)\n/* -------------------------------------------------------------------------- */\n{\n  for (auto fit = mFile->get_xrdioro().begin();\n       fit != mFile->get_xrdioro().end(); ++fit) {\n    fit->second->Dump(out);\n  }\n\n  for (auto fit = mFile->get_xrdiorw().begin();\n       fit != mFile->get_xrdiorw().end(); ++fit) {\n    fit->second->Dump(out);\n  }\n\n  return out.c_str();\n}\n\n\nvoid\ndata::datax::set_shared_url()\n{\n  // this call comes from already locked datax environments\n  std::string p;\n\n  if (mFile->has_xrdiorw(mReq)) {\n    p = mFile->xrdiorw(mReq)->getLastUrl();\n\n    if (p.empty()) {\n      p = mRemoteUrlRW;\n    }\n  } else {\n    if (mFile->has_xrdioro(mReq)) {\n      p = mFile->xrdioro(mReq)->getLastUrl();\n\n      if (p.empty()) {\n        p = mRemoteUrlRO;\n      }\n    }\n  }\n\n  mUrl = std::make_shared<std::string>(p);\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\ndata::datax::url(bool nonblocking)\n/* -------------------------------------------------------------------------- */\n{\n  if (mUrl) {\n    return *mUrl;\n  }\n\n  std::string p;\n  {\n    if (nonblocking) {\n      if (!mLock.CondLock()) {\n        return \"url:unresolved\";\n      }\n    } else {\n      mLock.Lock();\n    }\n\n    if (mFile->has_xrdiorw(mReq)) {\n      p = mFile->xrdiorw(mReq)->getLastUrl();\n    } else {\n      if (mFile->has_xrdioro(mReq)) {\n        p = mFile->xrdioro(mReq)->getLastUrl();\n      }\n    }\n\n    mLock.UnLock();\n  }\n  size_t f1 = p.find(\"/fusex-open\");\n  size_t f2 = p.find(\"eos.app\");\n\n  if (f1 != std::string::npos) {\n    if (f2 != std::string::npos) {\n      p.erase(f1, f2 - f1);\n    } else {\n      p.erase(f1);\n    }\n\n    p.insert(f1, \" : \");\n    std::replace(p.begin(), p.end(), '&', ' ');\n  }\n\n  return p;\n}\n\n/* -------------------------------------------------------------------------- */\nbool\n/* -------------------------------------------------------------------------- */\ndata::dmap::waitflush(uint64_t seconds)\n{\n  // wait that all pending data is flushed for 'seconds'\n  // if all is flushed, it returns true, otherwise false\n  for (uint64_t i = 0; i < seconds; ++i) {\n    size_t nattached = 0;\n    {\n      XrdSysMutexHelper mLock(this);\n      nattached = this->size();\n    }\n\n    if (nattached) {\n      eos_static_warning(\"[ waiting data to be flushed for %03d io objects] [ %d of %d seconds ]\",\n                         nattached, i, seconds);\n      std::this_thread::sleep_for(std::chrono::milliseconds(1000));\n    } else {\n      eos_static_warning(\"[ all data flushed ]\");\n      return true;\n    }\n  }\n\n  eos_static_warning(\"[ data flush timed out after %d seconds ]\", seconds);\n  return false;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\ndata::dmap::ioflush(ThreadAssistant& assistant)\n/* -------------------------------------------------------------------------- */\n{\n  ThreadAssistant::setSelfThreadName(\"data::ioflush\");\n\n  while (!assistant.terminationRequested()) {\n    {\n      //eos_static_debug(\"\");\n      std::vector<shared_data> data;\n      {\n        // avoid mutex contention\n        XrdSysMutexHelper mLock(this);\n\n        for (auto it = this->begin(); it != this->end(); ++it) {\n          if (it->second) {\n            data.push_back(it->second);\n          }\n        }\n      }\n\n      for (auto it = data.begin(); it != data.end(); ++it) {\n        XrdSysMutexHelper lLock((*it)->Locker());\n        eos_static_info(\"dbmap-in %#lx => %lx\", (*it)->id(), &(*it));\n      }\n\n      for (auto it = data.begin(); it != data.end(); ++it) {\n        {\n          XrdSysMutexHelper lLock((*it)->Locker());\n          eos_static_info(\"dbmap-in => ino:%16lx %lx attached=%d\", (*it)->id(), &(*it),\n                          (*it)->attached_nolock());\n\n          if (!(*it)->attached_nolock()) {\n            // files which are detached might need an upstream sync\n            bool repeat = true;\n\n            while (repeat) {\n              // close all readers in async fashion\n              std::map<std::string, XrdCl::shared_proxy>& rmap = (*it)->file()->get_xrdioro();\n\n              for (auto fit = rmap.begin();\n                   fit != rmap.end();) {\n                if (!fit->second) {\n                  fit++;\n                  continue;\n                }\n\n                if (fit->second->IsOpening() || fit->second->IsClosing()) {\n                  eos_static_info(\"skipping xrdclproxyrw state=%d %d\", fit->second->state(),\n                                  fit->second->IsClosed());\n                  // skip files which are opening or closing\n                  fit++;\n                  continue;\n                }\n\n                if (fit->second->IsOpen()) {\n                  // close read-only file if longer than 1s open\n                  if ((fit->second->state_age() > 1.0)) {\n                    if (fit->second->HasReadsInFlight()) {\n                      // don't close files if there is still something in flight from read-ahead\n                      // TODO: in EOS5 (Xrootd5) we can use SetProperty( \"BundledClose\", \"true\" ) and end the close with outstanding reads\n                      eos_static_info(\"still have reads in flight ino:%16lx\", (*it)->id());\n                      fit++;\n                      continue;\n                    }\n\n                    // closing read-only file\n                    fit->second->CloseAsync(fit->second);\n                    eos_static_info(\"closing reader\");\n                    fit++;\n                    continue;\n                  } else {\n                    eos_static_info(\"age still too young ino:%16lx\", (*it)->id());\n                  }\n                }\n\n                if (fit->second->IsOpening() || fit->second->IsClosing()) {\n                  // skip if its neither opened nor closed\n                  fit++;\n                  continue;\n                }\n\n                if (fit->second->IsClosed()) {\n                  if (fit->second->DoneReadAhead()) {\n                    fit = (*it)->file()->get_xrdioro().erase(fit);\n                    eos_static_info(\"deleting reader\");\n                    continue;\n                  }\n                }\n\n                fit++;\n              }\n\n              std::map<std::string, XrdCl::shared_proxy>& map = (*it)->file()->get_xrdiorw();\n\n              for (auto fit = map.begin();\n                   fit != map.end(); ++fit) {\n                if (!fit->second) {\n                  continue;\n                }\n\n                if (fit->second->IsOpening() || fit->second->IsClosing()) {\n                  eos_static_info(\"skipping xrdclproxyrw state=%d %d\", fit->second->state(),\n                                  fit->second->IsClosed());\n                  // skip files which are opening or closing\n                  break;\n                }\n\n                if (fit->second->IsOpen()) {\n                  eos_static_info(\"skip flushing journal for req=%s id=%#lx\", fit->first.c_str(),\n                                  (*it)->id());\n                  // flush the journal using an asynchronous thread pool\n                  // skipped: (*it)->journalflush_async(fit->first);\n                  fit->second->set_state_TS(XrdCl::Proxy::WAITWRITE);\n                  eos_static_info(\"changing to wait write state\");\n                }\n\n                if (fit->second->IsWaitWrite()) {\n                  if (!fit->second->OutstandingWrites()) {\n                    if ((fit->second->state_age() > 1.0) &&\n                        !EosFuse::Instance().mds.has_flush((*it)->id())) {\n                      std::string msg;\n\n                      // check if we need to run a recovery action\n                      if ((fit->second->HadFailures(msg) ||\n                           ((*it)->simulate_write_error_in_flusher()))) {\n                        (*it)->recoverystack().push_back\n                        (eos_static_log(LOG_SILENT, \"status='%s' hint='will TryRecovery'\",\n                                        msg.c_str()));\n                        int tret = 0;\n\n                        if (!(tret = (*it)->TryRecovery(0, true))) {\n                          (*it)->recoverystack().push_back\n                          (eos_static_log(LOG_SILENT, \"hint='success TryRecovery'\"));\n                          int jret = 0;\n\n                          if ((jret = (*it)->journalflush(fit->first))) {\n                            eos_static_err(\"ino:%16lx recovery failed\", (*it)->id());\n                            (*it)->recoverystack().push_back\n                            (eos_static_log(LOG_SILENT, \"errno='%d' hint='failed journalflush'\", jret));\n                          } else {\n                            (*it)->recoverystack().push_back\n                            (eos_static_log(LOG_SILENT, \"hint='success journalflush'\"));\n                          }\n                        } else {\n                          (*it)->recoverystack().push_back\n                          (eos_static_log(LOG_SILENT, \"errno='%d' hint='failed TryRecovery\", tret));\n                        }\n                      }\n\n                      eos_static_info(\"changing to close async state - age = %f ino:%16lx has-flush=%s\",\n                                      fit->second->state_age(), (*it)->id(),\n                                      EosFuse::Instance().mds.has_flush((*it)->id()) ? \"true\" : \"false\");\n                      fit->second->CloseAsync(fit->second);\n                      break;\n                    } else {\n                      if (fit->second->state_age() < 1.0) {\n                        eos_static_info(\"waiting for right age before async close - age = %f ino:%16lx has-flush=%s\",\n                                        fit->second->state_age(), (*it)->id(),\n                                        EosFuse::Instance().mds.has_flush((*it)->id()) ? \"true\" : \"false\");\n                      } else {\n                        eos_static_info(\"waiting for flush before async close - age = %f ino:%16lx has-flush=%s\",\n                                        fit->second->state_age(), (*it)->id(),\n                                        EosFuse::Instance().mds.has_flush((*it)->id()) ? \"true\" : \"false\");\n                      }\n\n                      break;\n                    }\n                  }\n                }\n\n                if (!fit->second->IsClosed()) {\n                  break;\n                }\n\n                {\n                  std::string msg;\n\n                  if ((!(*it)->unlinked()) && fit->second->HadFailures(msg)) {\n                    // let's see if the initial OpenAsync got a timeout, this we should retry always\n                    XrdCl::XRootDStatus status = fit->second->opening_state();\n                    bool rescue = true;\n                    bool canreissue = true;\n                    const std::string opname =\n                      (fit->second->state() == XrdCl::Proxy::CLOSEFAILED) ? \"CloseAsync\" : \"OpenAsync\";\n\n                    if (fit->second->state() == XrdCl::Proxy::CLOSEFAILED &&\n                        fit->second->opening_state().code == XrdCl::errOperationExpired) {\n                      // CloseAsyncHandler should already have requested new id from processCache\n                      canreissue = false;\n                    }\n\n                    if (canreissue && (\n                      (status.code == XrdCl::errConnectionError) ||\n                      (status.code == XrdCl::errSocketTimeout) ||\n                      (status.code == XrdCl::errOperationExpired) ||\n                      (status.code == XrdCl::errSocketDisconnected))){\n                      eos_static_warning(\"re-issuing %s request after timeout - ino:%16lx err-code:%d\",\n                                         opname.c_str(),(*it)->id(), status.code);\n                      // Recover by reissue, using a new proxy. If available it will using the latest\n                      // connection identity associated with our fuseid. Specific errors such as close\n                      // timeout will have attempted to associate a new idenenity with the fuseid.\n                      // (If the process associated with the fuseid is already gone we generally\n                      // cannot generate a new connection id for the old process, but new processes\n                      // of the user using the same authentication type will get a new identity).\n                      XrdCl::shared_proxy newproxy = XrdCl::Proxy::Factory(fit->second);\n                      // For the open use the url from the ioctx (datax) object rather than\n                      // the previous proxy. The previous proxy may have used url options\n                      // we don't want here, such as eos.repair.\n                      newproxy->OpenAsync(newproxy, (*it)->get_remote(true),\n                                          fit->second->flags(), fit->second->mode(), 0);\n                      newproxy->inherit_attached(fit->second);\n                      newproxy->inherit_protocol(fit->second);\n                      map[fit->first] = newproxy;\n                      continue;\n                    } else {\n                      if (status.errNo == kXR_noserver) {\n                        int tret = 0;\n\n                        eos_static_warning(\"%s failed - trying recovery - ino:%16lx err-code:%d\",\n                                           opname.c_str(),(*it)->id(), status.code);\n\n                        if (!(tret = (*it)->TryRecovery(0, true))) {\n                          (*it)->recoverystack().push_back\n                          (eos_static_log(LOG_SILENT, \"hint='success TryRecovery'\"));\n                          int jret = 0;\n\n                          if ((jret = (*it)->journalflush(fit->first))) {\n                            eos_static_err(\"ino:%16lx recovery failed\", (*it)->id());\n                            (*it)->recoverystack().push_back\n                            (eos_static_log(LOG_SILENT, \"errno='%d' hint='failed journalflush'\", jret));\n                          } else {\n                            (*it)->recoverystack().push_back\n                            (eos_static_log(LOG_SILENT, \"hint='success journalflush'\"));\n                            continue;\n                          }\n                        } else {\n                          (*it)->recoverystack().push_back\n                          (eos_static_log(LOG_SILENT, \"errno='%d' hint='failed TryRecovery\", tret));\n                        }\n                      }\n\n                      eos_static_warning(\"giving up %s request - ino:%16lx err-code:%d\",\n                                         opname.c_str(),(*it)->id(), status.code);\n\n                      if (status.errNo == kXR_overQuota) {\n                        // don't preserve these files, they got an application error beforehand\n                        rescue = false;\n                      }\n                    }\n\n                    if (!cachehandler::instance().get_config().rescuecache) {\n                      // forced disablign of rescue files\n                      rescue = false;\n                    }\n\n                    // ---------------------------------------------------------\n                    // we really have to avoid this to happen, but\n                    // we can put everything we have cached in a save place for\n                    // manual recovery and tag the error message\n                    // ---------------------------------------------------------\n\n                    if (rescue) {\n                      std::string file_rescue_location;\n                      std::string journal_rescue_location;\n                      int dt = (*it)->file()->file() ? (*it)->file()->file()->rescue(\n                                 file_rescue_location) : 0;\n                      int jt = (*it)->file()->journal() ? (*it)->file()->journal()->rescue(\n                                 journal_rescue_location) : 0;\n\n                      if (!dt || !jt) {\n                        const char* cmsg =\n                          eos_static_log(LOG_CRIT,\n                                         \"ino:%16lx msg=%s file-recovery=%s journal-recovery=%s\",\n                                         (*it)->id(),\n                                         msg.c_str(),\n                                         (!dt) ? file_rescue_location.c_str() : \"<none>\",\n                                         (!jt) ? journal_rescue_location.c_str() : \"<none>\");\n                        (*it)->recoverystack().push_back(cmsg);\n                      }\n                    }\n                  }\n\n                  eos_static_info(\"deleting xrdclproxyrw state=%d %d\", fit->second->state(),\n                                  fit->second->IsClosed());\n                  (*it)->file()->get_xrdiorw().erase(fit);\n                  break;\n                }\n              }\n\n              repeat = false;\n            }\n          }\n        }\n        XrdSysMutexHelper mLock(this);\n        XrdSysMutexHelper lLock((*it)->Locker());\n\n        // re-check that nobody is attached\n        if (!(*it)->attached_nolock() && !(*it)->file()->get_xrdiorw().size() &&\n            !(*it)->file()->get_xrdioro().size()) {\n          eos_static_info(\"dropping one\");\n          // here we make the data object unreachable for new clients\n          (*it)->detach_nolock();\n          cachehandler::instance().rm((*it)->id());\n          auto it2 = this->find((*it)->id());\n          if (it2 != this->end() && it2->second == *it) {\n            this->erase((*it)->id());\n          }\n          it2 = this->find((*it)->id() + 0xffffffff);\n          if (it2 != this->end() && it2->second == *it) {\n            this->erase((*it)->id() + 0xffffffff);\n          }\n        }\n      }\n\n      assistant.wait_for(std::chrono::milliseconds(128));\n    }\n  }\n}\n"
  },
  {
    "path": "fusex/data/data.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file data.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief data handling class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_DATA_HH_\n#define FUSE_DATA_HH_\n\n#include <sys/stat.h>\n#include <sys/types.h>\n#include \"data/cache.hh\"\n#include \"data/io.hh\"\n#include \"data/cachehandler.hh\"\n#include \"misc/FuseId.hh\"\n#include \"md/md.hh\"\n#include \"cap/cap.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/FileId.hh\"\n#include \"common/SymKeys.hh\"\n#include \"bufferll.hh\"\n#include \"llfusexx.hh\"\n#include \"fusex/fusex.pb.h\"\n#include \"common/Logging.hh\"\n#include <XrdCl/XrdClFile.hh>\n#include <XrdSys/XrdSysPthread.hh>\n#include <memory>\n#include <map>\n#include <set>\n#include <atomic>\n#include <deque>\n#include <exception>\n#include <stdexcept>\n#include <thread>\n\nclass data\n{\npublic:\n\n  //----------------------------------------------------------------------------\n\n  class datax : public LogId\n  //----------------------------------------------------------------------------\n  {\n  public:\n\n    datax() : mIno(0), mReq(0), mFile(0), mSize(0), mAttached(0), mMd(0),\n      mPrefetchHandler(0),\n      mSimulateWriteErrorInFlush(false),\n      mSimulateWriteErrorInFlusher(false),\n      mFlags(0), mXoff(false), mIsInlined(false), mInlineMaxSize(0),\n      mInlineCompressor(\"none\"), mIsUnlinked(false),\n      mCanRecoverRead(true)\n\n    {\n      inline_buffer = nullptr;\n    }\n\n    datax(metad::shared_md md) : mIno(0), mReq(0), mFile(0), mSize(0),\n      mAttached(0), mMd(md), mPrefetchHandler(0),\n      mSimulateWriteErrorInFlush(false),\n      mSimulateWriteErrorInFlusher(false),\n      mFlags(0), mXoff(false),\n      mIsInlined(false), mInlineMaxSize(0), mInlineCompressor(\"none\"),\n      mIsUnlinked(false), mCanRecoverRead(true) { }\n\n    virtual ~datax()\n    {\n      dump_recovery_stack();\n    }\n\n    XrdSysMutex& Locker()\n    {\n      return mLock;\n    }\n\n    void set_id(uint64_t ino, fuse_req_t req);\n\n    uint64_t id() const\n    {\n      return mIno;\n    }\n\n    fuse_req_t req() const\n    {\n      return mReq;\n    }\n\n    shared_io file()\n    {\n      return mFile;\n    }\n\n    void remove_file_cache()\n    {\n      mFile->disable_file_cache();\n    }\n\n    int flags()\n    {\n      return mFlags;\n    }\n\n    int flush(fuse_req_t req);\n    int flush_nolock(fuse_req_t req, bool wait_open = true,\n                     bool wait_writes = false);\n    bool is_wopen(fuse_req_t req);\n    int journalflush(fuse_req_t req);\n    int journalflush(std::string cid);\n    int journalflush_async(std::string cid);\n    int attach(fuse_req_t req, std::string& cookie, int flags);\n    bool inline_file(ssize_t size = -1);\n    int detach(fuse_req_t req, std::string& cookie, int flags);\n    int store_cookie(std::string& cookie);\n    int unlink(fuse_req_t req);\n\n    void set_remote(const std::string& hostport,\n                    const std::string& basename,\n                    const uint64_t md_ino,\n                    const uint64_t md_pino,\n                    fuse_req_t req,\n                    bool isRW);\n\n    std::string get_remote(bool isRW);\n\n    // IO bridge interface\n    ssize_t pread(fuse_req_t req, void* buf, size_t count, off_t offset);\n    ssize_t pwrite(fuse_req_t req, const void* buf, size_t count, off_t offset);\n    ssize_t peek_pread(fuse_req_t req, char*& buf, size_t count, off_t offset);\n    void release_pread();\n    int truncate(fuse_req_t req, off_t offset);\n    int sync();\n    size_t size();\n    int cache_invalidate();\n    bool prefetch(fuse_req_t req, bool lock = true);\n    void WaitPrefetch(fuse_req_t req, bool lock = true);\n    void WaitOpen();\n    void FlagDeleted();\n\n    // IO recovery functions\n    int TryRecovery(fuse_req_t req, bool is_write);\n\n    int recover_ropen(fuse_req_t req);\n    int try_ropen(fuse_req_t req, XrdCl::shared_proxy &proxy, std::string open_url);\n    int try_wopen(fuse_req_t req, XrdCl::shared_proxy &proxy, std::string open_url);\n    int recover_read(fuse_req_t req);\n    int recover_write(fuse_req_t req);\n\n    int begin_flush(fuse_req_t req);\n    int end_flush(fuse_req_t req);\n\n\n    bool can_recover_read()\n    {\n      return mCanRecoverRead;\n    }\n\n    void disable_read_recovery()\n    {\n      mCanRecoverRead = false;\n    }\n\n    // ref counting for this object\n\n    void attach()\n    {\n      XrdSysMutexHelper lLock(mLock);\n      mAttached++;\n    }\n\n    bool detach()\n    {\n      XrdSysMutexHelper lLock(mLock);\n      return (--mAttached);\n    }\n\n    bool detach_nolock()\n    {\n      return (--mAttached);\n    }\n\n    bool attached()\n    {\n      XrdSysMutexHelper lLock(mLock);\n      return mAttached ? true : false;\n    }\n\n    bool attached_nolock()\n    {\n      return (mAttached) ? true : false;\n    }\n\n    bool attached_once_nolock()\n    {\n      return (mAttached == 1) ? true : false;\n    }\n\n    bool unlinked()\n    {\n      // caller has to have this object locked\n      return mIsUnlinked;\n    }\n\n    static bufferllmanager sBufferManager;\n\n    bool simulate_write_error_in_flusher()\n    {\n      return mSimulateWriteErrorInFlusher;\n    }\n\n    bool simulate_write_error_in_flush()\n    {\n      return mSimulateWriteErrorInFlush;\n    }\n\n    bool inlined()\n    {\n      return mIsInlined;\n    }\n\n    static std::string kInlineAttribute;\n    static std::string kInlineMaxSize;\n    static std::string kInlineCompressor;\n\n\n    metad::shared_md md()\n    {\n      return mMd;\n    }\n\n    std::string fullpath()\n    {\n      // TODO: we don't take md lock here; introduce a more robust approach.\n      // Currently return via the c-string to reduce disturbance of the source str\n      return (*md())()->fullpath().c_str();\n    }\n\n    std::string fid()\n    {\n      return std::to_string(eos::common::FileId::InodeToFid((*mMd)()->md_ino()));\n    }\n\n    std::string obfuscate()\n    {\n      auto attrMap = (*mMd)()->attr();\n\n      if (attrMap.count(\"sys.obfuscate.key\")) {\n        return attrMap[\"sys.obfuscate.key\"];\n      } else {\n        return \"\";\n      }\n    }\n\n    std::deque<std::string> recoverystack()\n    {\n      return mRecoveryStack;\n    }\n\n    void dump_recovery_stack();\n\n    const char* Dump(std::string& out);\n\n    std::string url(bool nonblocking = false);\n    typedef std::shared_ptr<std::string> shared_url;\n    void set_shared_url();\n\n  private:\n    XrdSysMutex mLock;\n    uint64_t mIno;\n    fuse_req_t mReq;\n    shared_io mFile;\n    off_t mSize;\n    std::string mRemoteUrlRW;\n    std::string mRemoteUrlRO;\n    std::string mBaseName;\n    shared_url mUrl;\n    size_t mAttached;\n    metad::shared_md mMd;\n    XrdCl::Proxy::read_handler mPrefetchHandler;\n    std::deque<std::string> mReadErrorStack;\n    std::deque<std::string> mRecoveryStack;\n\n    bufferllmanager::shared_buffer buffer;\n    bool mSimulateWriteErrorInFlush;\n    bool mSimulateWriteErrorInFlusher;\n    int mFlags;\n\n    bool mXoff;\n    bool mIsInlined;\n    uint64_t mInlineMaxSize;\n    std::string mInlineCompressor;\n    bufferllmanager::shared_buffer inline_buffer;\n    bool mIsUnlinked;\n    bool mCanRecoverRead;\n  };\n\n  typedef std::shared_ptr<datax> shared_data;\n\n  typedef struct _data_fh {\n    shared_data data;\n    cap::shared_cap cap_;\n    metad::shared_md md;\n    fuse_id id;\n    bool rw;\n    std::atomic<bool> flocked;\n    std::atomic<bool> edquota;\n    std::string _authid;\n    std::atomic<bool> update_mtime_on_flush;\n    std::atomic<time_t> next_size_flush;\n    uint64_t _maxfilesize; // maximum allowed file size\n    uint64_t _opensize; // size at the moment of opening the file\n    eos::common::SymKey::hmac_t hmac; /// obfuscation/encryption cipher\n\n    _data_fh(shared_data _data, metad::shared_md _md, bool _rw, fuse_id _id)\n    {\n      data = _data;\n      md = _md;\n      rw = _rw;\n      id = _id;\n      update_mtime_on_flush.store(false, std::memory_order_seq_cst);\n      flocked.store(false, std::memory_order_seq_cst);\n      edquota.store(false, std::memory_order_seq_cst);\n      next_size_flush.store(0, std::memory_order_seq_cst);\n      _maxfilesize = 0;\n      _opensize = (*md)()->size();\n    }\n\n    ~_data_fh() { }\n\n    static struct _data_fh* Instance(shared_data io, metad::shared_md md, bool rw,\n                                     fuse_id id)\n    {\n      return new struct _data_fh(io, md, rw, id);\n    }\n\n    shared_data ioctx()\n    {\n      return data;\n    }\n\n    metad::shared_md mdctx()\n    {\n      return md;\n    }\n\n    fuse_id fuseid()\n    {\n      return id;\n    }\n\n    std::string authid() const\n    {\n      return _authid;\n    }\n\n    uint64_t maxfilesize() const\n    {\n      return _maxfilesize;\n    }\n\n    uint64_t opensize() const\n    {\n      return _opensize;\n    }\n\n    void set_authid(const std::string& authid)\n    {\n      _authid = authid;\n    }\n\n    void set_update()\n    {\n      update_mtime_on_flush.store(true, std::memory_order_seq_cst);\n    }\n\n    void set_flocked(bool val)\n    {\n      flocked.store(val, std::memory_order_seq_cst);\n    }\n\n    void set_edquota()\n    {\n      edquota.store(true, std::memory_order_seq_cst);\n    }\n\n    bool has_update()\n    {\n      if (update_mtime_on_flush.load()) {\n        update_mtime_on_flush.store(false, std::memory_order_seq_cst);\n        return true;\n      }\n\n      return false;\n    }\n\n\n    void set_maxfilesize(uint64_t size)\n    {\n      _maxfilesize = size;\n    }\n\n  } data_fh;\n\n  //----------------------------------------------------------------------------\n\n  class dmap : public std::map<fuse_ino_t, shared_data>, public XrdSysMutex\n  //----------------------------------------------------------------------------\n  {\n  public:\n\n    dmap() { }\n\n    virtual ~dmap() { }\n\n    void run()\n    {\n      tIOFlush.reset(&dmap::ioflush, this);\n    }\n\n    void ioflush(ThreadAssistant&\n                 assistant); // thread for delayed asynchronous close\n\n    bool waitflush(uint64_t seconds);\n    void join()\n    {\n      tIOFlush.join();\n    }\n\n  private:\n    AssistedThread tIOFlush;\n  };\n\n  data();\n\n  virtual ~data();\n\n  void init();\n  void terminate(uint64_t seconds);\n\n  shared_data get(fuse_req_t req,\n                  fuse_ino_t ino,\n                  metad::shared_md m);\n\n  bool has(fuse_ino_t ino, bool checkwriteopen = false);\n  metad::shared_md retrieve_wr_md(fuse_ino_t ino);\n\n\n  std::string url(fuse_ino_t ino);\n\n  void release(fuse_req_t req,\n               fuse_ino_t ino, shared_data io);\n\n  uint64_t commit(fuse_req_t req,\n                  shared_data io);\n\n  bool unlink(fuse_req_t req, fuse_ino_t ino);\n\n  void update_cookie(uint64_t ino, std::string& cookie);\n\n  void invalidate_cache(fuse_ino_t ino);\n\n  size_t size()\n  {\n    XrdSysMutexHelper mLock(datamap);\n    return datamap.size();\n  }\n\n  void set_xoff()\n  {\n    xoffCounter.fetch_add(1, std::memory_order_seq_cst);\n  }\n\n  uint64_t get_xoff()\n  {\n    return xoffCounter.load();\n  }\n\n\n\nprivate:\n  dmap datamap;\n  std::atomic<uint64_t>  xoffCounter;\n};\n\n#endif /* FUSE_DATA_HH_ */\n"
  },
  {
    "path": "fusex/data/dircleaner.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file data.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief class keeping dir contents at a given level\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"data/dircleaner.hh\"\n#undef __USE_FILE_OFFSET64\n#include <fts.h>\n#define __USE_FILE_OFFSET64\n#include <sys/statvfs.h>\n\n/* -------------------------------------------------------------------------- */\ndircleaner::dircleaner(const std::string _path,\n                       const std::string _name,\n                       int64_t _maxsize,\n                       int64_t _maxfiles,\n                       float _clean_threshold) :\n  path(_path),\n  max_files(_maxfiles),\n  max_size(_maxsize),\n  clean_threshold(_clean_threshold),\n  name(_name)\n{\n  if (max_files | max_size) {\n    tLeveler.reset(&dircleaner::leveler, this);\n  }\n}\n\n/* -------------------------------------------------------------------------- */\ndircleaner::~dircleaner()\n/* -------------------------------------------------------------------------- */\n{\n}\n\n/* -------------------------------------------------------------------------- */\nbool\n/* -------------------------------------------------------------------------- */\ndircleaner::has_suffix(const std::string& str, const std::string& suffix)\n/* -------------------------------------------------------------------------- */\n{\n  return str.size() >= suffix.size() &&\n         str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndircleaner::cleanall(std::string matchsuffix)\n/* -------------------------------------------------------------------------- */\n{\n  std::lock_guard<std::recursive_mutex> mLock(cleaningMutex);\n\n  if (!scanall(matchsuffix)) {\n    std::string tout;\n    treeinfo.Print(tout);\n    eos_static_info(\"[ %s ] purging %s\", name.c_str(), tout.c_str());\n\n    for (auto it = treeinfo.treemap.begin(); it != treeinfo.treemap.end(); ++it) {\n      if (matchsuffix.length() && (!has_suffix(it->second.path, matchsuffix))) {\n        continue;\n      }\n\n      if (unlink(it->second.path.c_str()) && errno != ENOENT) {\n        eos_static_err(\"[ %s ] unlink: path=%s errno=%d\", name.c_str(),\n                       it->second.path.c_str(), errno);\n      }\n    }\n  }\n\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\ndircleaner::tree_info::Print(std::string& out)\n/* -------------------------------------------------------------------------- */\n{\n  char line[1024];\n  snprintf(line, sizeof(line), \"path=%s n-files=%lu tree-size=%lu\",\n           path.c_str(),\n           totalfiles,\n           totalsize);\n  out += line;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndircleaner::scanall(std::string matchsuffix)\n/* -------------------------------------------------------------------------- */\n{\n  int retc = 0;\n  char* paths[2];\n  paths[0] = (char*) path.c_str();\n  paths[1] = 0;\n  FTS* tree = fts_open(paths, FTS_NOCHDIR, 0);\n\n  if (!tree) {\n    return -1; // see errno\n  }\n\n  FTSENT* node;\n  treeinfo.path = path;\n  treeinfo.totalfiles = 0;\n  treeinfo.totalsize = 0;\n  treeinfo.treemap.clear();\n  externaltreeinfo.reset();\n\n  while ((node = fts_read(tree))) {\n    // skip any hidden file, we might want that\n    if (node->fts_level > 0 && node->fts_name[0] == '.') {\n      fts_set(tree, node, FTS_SKIP);\n    } else {\n      if (node->fts_info == FTS_F) {\n        std::string filepath = node->fts_accpath;\n\n        if (matchsuffix.length() && !has_suffix(filepath, matchsuffix)) {\n          continue;\n        }\n\n        struct stat buf;\n\n        if (::stat(filepath.c_str(), &buf)) {\n          if (errno == ENOENT) {\n            // this can happen when something get's cleaned during scanning\n            eos_static_info(\"[ %s ] stat: path=%s errno=%d\", name.c_str(), filepath.c_str(),\n                            errno);\n          } else {\n            eos_static_err(\"[ %s ] stat: path=%s errno=%d\", name.c_str(), filepath.c_str(),\n                           errno);\n            retc = -1;\n          }\n        } else {\n          treeinfo.totalfiles++;\n          treeinfo.totalsize += buf.st_size;\n          file_info_t finfo;\n          finfo.path = filepath;\n          finfo.mtime = buf.st_mtime;\n          finfo.size = buf.st_size;\n          treeinfo.treemap.insert(std::pair<time_t, file_info_t>(finfo.mtime, finfo));\n          eos_static_debug(\"[ %s ] adding path=%s mtime=%lu size=%lu\", name.c_str(),\n                           filepath.c_str(),\n                           buf.st_mtime,\n                           buf.st_size);\n        }\n      } else if (node->fts_info == FTS_D) {\n        std::string dirpath = node->fts_accpath;\n        struct stat buf;\n\n        if (node->fts_level > 0) {\n          if (EOS_LOGS_DEBUG) {\n            eos_static_debug(\"checking directory %s\", dirpath.c_str());\n          }\n\n          if (!::stat(dirpath.c_str(), &buf)) {\n            time_t now = time(NULL);\n\n            if (now > (buf.st_ctime - 60)) {\n              // avoid the unforunate race when the diskcache/or journal needs to create a parent directory for a file\n              // try to delete - will work if it is empty\n              if (!::rmdir(dirpath.c_str())) {\n                eos_static_notice(\"[ %s ] rmdir: empty inode directory\", dirpath.c_str());\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  if (fts_close(tree)) {\n    eos_static_err(\"[ %s ] fts_close: errno=%d\", name.c_str(), errno);\n    return -1;\n  }\n\n  return retc;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndircleaner::trim(bool force)\n{\n  if (!force) {\n    // avoid full scans\n    tree_info_t& externaltree = get_external_tree();\n    bool size_ok = true;\n    bool files_ok = true;\n    // an external cache can give change hints via the externaltree\n    int64_t tree_size = treeinfo.get_size() + externaltree.get_size();\n    int64_t tree_files = treeinfo.get_files() + externaltree.get_files();\n    eos_static_info(\"[ %s ] max-size=%ld is-size=%ld max-files=%lld is-files=%ld force=%d\",\n                    name.c_str(),\n                    max_size, tree_size,\n                    max_files, tree_files,\n                    force);\n\n    if (max_size && (tree_size > max_size)) {\n      size_ok = false;\n    }\n\n    if (max_files && (tree_files > max_files)) {\n      files_ok = false;\n    }\n\n    // nothing to do\n    if (size_ok && files_ok) {\n      return 0;\n    }\n  }\n\n  scanall(trim_suffix);\n\n  for (auto it = treeinfo.treemap.begin(); it != treeinfo.treemap.end(); ++it) {\n    eos_static_debug(\"[ %s ] is-size %lld max-size %lld\", name.c_str(),\n                     treeinfo.get_size(), max_size);\n    bool size_ok = true;\n    bool files_ok = true;\n\n    if (max_size && (treeinfo.get_size() > max_size)) {\n      size_ok = false;\n    }\n\n    if (max_files && (treeinfo.get_files() > max_files)) {\n      files_ok = false;\n    }\n\n    // nothing to do\n    if (size_ok && files_ok) {\n      return 0;\n    }\n\n    eos_static_info(\"[ %s ] erasing %s %ld => %ld\", name.c_str(),\n                    it->second.path.c_str(),\n                    treeinfo.get_size(), it->second.size);\n\n    if (::unlink(it->second.path.c_str())) {\n      eos_static_err(\"[ %s ] failed to unlink file %s errno=%d\", name.c_str(),\n                     it->second.path.c_str(),\n                     errno);\n    } else {\n      treeinfo.change(-it->second.size, -1);\n    }\n  }\n\n  return 0;\n}\n/* -------------------------------------------------------------------------- */\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\ndircleaner::leveler(ThreadAssistant& assistant)\n/* -------------------------------------------------------------------------- */\n{\n  size_t n = 0;\n\n  while (1) {\n    assistant.wait_for(std::chrono::seconds(15));\n\n    if (assistant.terminationRequested()) {\n      return;\n    }\n\n    // check the partition status\n    struct statvfs svfs;\n    int rsfs = statvfs(path.c_str(), &svfs);\n\n    if (!rsfs) {\n      uint64_t free_partition_bytes = svfs.f_bavail * svfs.f_bsize;\n      uint64_t total_partition_bytes = svfs.f_blocks * svfs.f_frsize;\n      double freep = 100.0 * free_partition_bytes / total_partition_bytes;\n      double filled = 100.0 - freep;\n      eos_static_info(\"[ %s ] diskspace on partition path %s free-bytes=%lu total-bytes=%lu filled=%.02f %%\",\n                      name.c_str(), path.c_str(), free_partition_bytes, total_partition_bytes,\n                      filled);\n\n      if (filled > clean_threshold) {\n        // we force a complete cleanup of the cache if disk space runs low\n        eos_static_warning(\"[ %s ] diskspace on partition path %s less than 5%% free : free-bytes=%lu total-bytes=%lu filled=%.02f %% - cleaning cache\",\n                           name.c_str(), path.c_str(), free_partition_bytes, total_partition_bytes,\n                           filled);\n        cleanall(trim_suffix);\n      }\n    } else {\n      eos_static_err(\"[ %s ] statvfs on path=%s failed with retc=%d errno=%d\",\n                     name.c_str(), path.c_str(),\n                     rsfs, errno);\n    }\n\n    std::lock_guard<std::recursive_mutex> mLock(cleaningMutex);\n    trim(!(n % (1 * 60 * 4)));  // forced trim every hour\n    n++;\n  }\n}\n"
  },
  {
    "path": "fusex/data/dircleaner.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file dircleaner.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief class keeping dir contents at a given level\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_DIRCLEANER_HH_\n#define FUSE_DIRCLEANER_HH_\n\n#include <sys/stat.h>\n#include <sys/types.h>\n#include \"common/AssistedThread.hh\"\n#include \"common/Logging.hh\"\n#include <memory>\n#include <map>\n#include <set>\n#include <atomic>\n#include <exception>\n#include <stdexcept>\n#include <thread>\n#include <mutex>\n\nclass dircleaner\n{\npublic:\n\n  typedef struct fileinfo {\n    std::string path;\n    time_t mtime;\n    size_t size;\n  } file_info_t;\n\n  typedef std::multimap<time_t, file_info_t> tree_map_t;\n\n  typedef struct tree_info {\n\n    tree_info()\n    {\n      totalsize = 0;\n      totalfiles = 0;\n    }\n    tree_map_t treemap;\n    int64_t totalsize;\n    int64_t totalfiles;\n    std::string path;\n\n    void Print(std::string& out);\n    XrdSysMutex Locker;\n\n    // thread safe change size, files\n\n    void change(int64_t size, int64_t files)\n    {\n      eos_static_info(\"size=%ld files=%ld\", size, files);\n      XrdSysMutexHelper mLock(Locker);\n      totalsize += size;\n      totalfiles += files;\n\n      if (totalsize < 0) {\n        totalsize = 0;\n      }\n\n      if (totalfiles < 0) {\n        totalfiles = 0;\n      }\n    }\n\n    // safe reset function\n\n    void reset()\n    {\n      XrdSysMutexHelper mLock(Locker);\n      totalsize = 0;\n      totalfiles = 0;\n    }\n    // thread safe get size\n\n    int64_t get_size()\n    {\n      XrdSysMutexHelper mLock(Locker);\n      return totalsize;\n    }\n\n    // thread safe get files\n\n    int64_t get_files()\n    {\n      XrdSysMutexHelper mLock(Locker);\n      return totalfiles;\n    }\n\n  } tree_info_t;\n\n  dircleaner(const std::string _path = \"/tmp/\", const std::string _name = \"none\",\n             int64_t _maxsize = 0,\n             int64_t _maxfiles = 0, float _clean_threshold = 85.0);\n  virtual ~dircleaner();\n\n  bool has_suffix(const std::string& str, const std::string& suffix);\n\n  tree_info_t& get_external_tree()\n  {\n    return externaltreeinfo;\n  }\n\n  void set_trim_suffix(const std::string& sfx)\n  {\n    trim_suffix = sfx;\n  }\n\n  int cleanall(std::string matchsuffix = \"\");\n  int scanall(std::string matchsuffix = \"\");\n  int trim(bool force);\n  void leveler(ThreadAssistant& assistant);\n\nprivate:\n  std::recursive_mutex cleaningMutex;\n  std::string path;\n\n  int64_t max_files;\n  int64_t max_size;\n  float clean_threshold;\n\n  tree_info_t treeinfo;\n  tree_info_t externaltreeinfo;\n\n  AssistedThread tLeveler;\n  std::string trim_suffix;\n  std::string name;\n};\n#endif\n"
  },
  {
    "path": "fusex/data/diskcache.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file diskcache.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief data cache disk implementation\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"diskcache.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Path.hh\"\n#include <unistd.h>\n#include <sys/types.h>\n#include <errno.h>\n#ifdef __APPLE__\n#define EKEYEXPIRED 127\n#include <XrdSys/XrdSysPlatform.hh>\n#endif\n#include \"common/XattrCompat.hh\"\n\nstd::string diskcache::sLocation;\nbufferllmanager diskcache::sBufferManager;\noff_t diskcache::sMaxSize = 2 * 1024 * 1024ll;\nfloat diskcache::sCleanThreshold = 85.0;\n\nstd::shared_ptr<dircleaner> diskcache::sDirCleaner;\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndiskcache::init(const cacheconfig& config)\n/* -------------------------------------------------------------------------- */\n{\n  if (::access(config.location.c_str(), W_OK)) {\n    return errno;\n  }\n\n  sLocation = config.location;\n\n  if (config.per_file_cache_max_size) {\n    diskcache::sMaxSize = config.per_file_cache_max_size;\n  }\n\n  if (config.clean_threshold) {\n    diskcache::sCleanThreshold = config.clean_threshold;\n  }\n\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndiskcache::init_daemonized(const cacheconfig& config)\n/* -------------------------------------------------------------------------- */\n{\n  if (config.per_file_cache_max_size) {\n    diskcache::sMaxSize = config.per_file_cache_max_size;\n  }\n\n  sDirCleaner = std::make_shared<dircleaner>(config.location,\n                \"dc\",\n                config.total_file_cache_size,\n                config.total_file_cache_inodes,\n                config.clean_threshold\n                                            );\n  sDirCleaner->set_trim_suffix(\".dc\");\n\n  if (config.clean_on_startup) {\n    eos_static_info(\"cleaning cache path=%s\", config.location.c_str());\n\n    if (sDirCleaner->cleanall(\".dc\")) {\n      eos_static_err(\"cache cleanup failed\");\n      return -1;\n    }\n  }\n\n  // start the disk cache leveling thread;\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\ndiskcache::diskcache(fuse_ino_t _ino) : ino(_ino), nattached(0), fd(-1)\n  /* -------------------------------------------------------------------------- */\n{\n  memset(&attachstat, 0, sizeof(attachstat));\n  memset(&detachstat, 0, sizeof(detachstat));\n  return;\n}\n\n/* -------------------------------------------------------------------------- */\ndiskcache::~diskcache()\n/* -------------------------------------------------------------------------- */\n{\n  if (!nattached) {\n    return;\n  }\n\n  // we may be destroyed while still being attached, e.g. during truncation\n  // via datax::truncate -> datax::remove_file_cache -> io::disable_file_cache\n  eos_static_debug(\"diskcache::~diskcache nattached=%lu fd=%d\\n\", nattached, fd);\n\n  if (fd < 0) {\n    return;\n  }\n\n  if (fstat(fd, &detachstat)) {\n    return;\n  }\n\n  sDirCleaner->get_external_tree().change(detachstat.st_size - attachstat.st_size,\n                                          0);\n  (void) close(fd);\n}\n\n/* -------------------------------------------------------------------------- */\nint\ndiskcache::location(std::string& path, bool mkpath)\n/* -------------------------------------------------------------------------- */\n{\n  char cache_path[1024 + 20];\n  snprintf(cache_path, sizeof(cache_path), \"%s/%03lX/%08lX.dc\",\n           sLocation.c_str(),\n           (ino > 0x0fffffff) ? (ino >> 28) % 4096 : ino % 4096, ino);\n\n  if (mkpath) {\n    eos::common::Path cPath(cache_path);\n\n    if (!cPath.MakeParentPath(S_IRWXU)) {\n      return -errno;\n    }\n  }\n\n  path = cache_path;\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndiskcache::attach(fuse_req_t req, std::string& acookie, int flag)\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper lLock(mMutex);\n  int rc = 0;\n\n  if (nattached == 0) {\n    std::string path;\n    rc = location(path);\n\n    if (rc) {\n      return rc;\n    }\n\n    if (stat(path.c_str(), &attachstat)) {\n      // a new file\n      sDirCleaner->get_external_tree().change(0, 1);\n    }\n\n    // need to open the file\n    size_t tries = 0;\n\n    do {\n      fd = open(path.c_str(), O_CREAT | O_RDWR, S_IRWXU);\n\n      if (fd < 0) {\n        if (errno == ENOENT) {\n          tries++;\n          // re-create the directory structure\n          rc = location(path);\n\n          if (rc) {\n            return rc;\n          }\n\n          if (tries < 10) {\n            continue;\n          } else {\n            return -errno;\n          }\n        }\n\n        return -errno;\n      }\n\n      break;\n    } while (1);\n  }\n\n  std::string ccookie;\n\n  if ((!nattached) && ((!cookie(ccookie) || (ccookie != \"\")))) {\n    if (fstat(fd, &attachstat)) {\n      return errno;\n    }\n\n    // compare if the cookies are identical, otherwise we truncate to 0\n    if (ccookie != acookie) {\n      eos_static_debug(\"diskcache::attach truncating for cookie: %s <=> %s\\n\",\n                       ccookie.c_str(), acookie.c_str());\n\n      if (truncate(0)) {\n        char msg[1024];\n        snprintf(msg, sizeof(msg),\n                 \"failed to truncate to invalidate cache file - ino=%08lx\", ino);\n        throw std::runtime_error(msg);\n      }\n\n      set_cookie(acookie);\n      rc = EKEYEXPIRED;\n    }\n  } else {\n    set_cookie(acookie);\n  }\n\n  nattached++;\n  return rc;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndiskcache::detach(std::string& cookie)\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper lLock(mMutex);\n  nattached--;\n\n  if (!nattached) {\n    if (fstat(fd, &detachstat)) {\n      return errno;\n    }\n\n    sDirCleaner->get_external_tree().change(detachstat.st_size - attachstat.st_size,\n                                            0);\n    int rc = close(fd);\n    fd = -1;\n\n    if (rc) {\n      return errno;\n    }\n  }\n\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndiskcache::unlink()\n/* -------------------------------------------------------------------------- */\n{\n  std::string path;\n  int rc = location(path);\n\n  if (!rc) {\n    struct stat buf;\n    rc = stat(path.c_str(), &buf);\n\n    if (!rc) {\n      rc = ::unlink(path.c_str());\n\n      if (!rc) {\n        // a deleted file\n        sDirCleaner->get_external_tree().change(-buf.st_size, -1);\n      }\n    }\n  }\n\n  return rc;\n}\n\n/* -------------------------------------------------------------------------- */\nssize_t\n/* -------------------------------------------------------------------------- */\ndiskcache::pread(void* buf, size_t count, off_t offset)\n/* -------------------------------------------------------------------------- */\n{\n  eos_static_debug(\"diskcache::pread %lu %lu\\n\", count, offset);\n\n  // restrict to our local max size cache size\n  if (offset >= sMaxSize) {\n    return 0;\n  }\n\n  if ((off_t)(offset + count) > sMaxSize) {\n    count = sMaxSize - offset;\n  }\n\n  return ::pread(fd, buf, count, offset);\n}\n\n/* -------------------------------------------------------------------------- */\nssize_t\ndiskcache::pwrite(const void* buf, size_t count, off_t offset)\n/* -------------------------------------------------------------------------- */\n{\n  eos_static_debug(\"diskcache::pwrite %lu %lu\\n\", count, offset);\n\n  if ((off_t) offset >= sMaxSize) {\n    return 0;\n  }\n\n  if ((off_t)(offset + count) > sMaxSize) {\n    count = sMaxSize - offset;\n  }\n\n  return ::pwrite(fd, buf, count, offset);\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndiskcache::truncate(off_t offset)\n/* -------------------------------------------------------------------------- */\n{\n  eos_static_debug(\"diskcache::truncate %lu\\n\", offset);\n\n  if (fstat(fd, &detachstat)) {\n    return -1;\n  }\n\n  int rc = 0;\n\n  if (offset >= sMaxSize) {\n    offset = sMaxSize;\n  }\n\n  rc = ::ftruncate(fd, offset);\n\n  if (!rc) {\n    sDirCleaner->get_external_tree().change(detachstat.st_size - attachstat.st_size,\n                                            0);\n    attachstat.st_size = offset;\n  }\n\n  return rc;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndiskcache::sync()\n/* -------------------------------------------------------------------------- */\n{\n  return ::fdatasync(fd);\n}\n\n/* -------------------------------------------------------------------------- */\nsize_t\n/* -------------------------------------------------------------------------- */\ndiskcache::size()\n/* -------------------------------------------------------------------------- */\n{\n  struct stat buf;\n  buf.st_size = 0;\n\n  if (fd > 0) {\n    if (::fstat(fd, &buf)) {\n      throw std::runtime_error(\"diskcache stat failure\");\n    }\n\n    return buf.st_size;\n  } else {\n    return 0;\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndiskcache::set_attr(const std::string& key, const std::string& value)\n/* -------------------------------------------------------------------------- */\n{\n  int rc = 0;\n\n  if (fd > 0) {\n#ifdef __APPLE__\n    rc = fsetxattr(fd, key.c_str(), value.c_str(), value.size(), 0, 0);\n#else\n    rc = fsetxattr(fd, key.c_str(), value.c_str(), value.size(), 0);\n#endif\n\n    if (rc && errno == ENOTSUP) {\n      throw std::runtime_error(\"diskcache has no xattr support\");\n    }\n  } else {\n    // set attribute by path since the diskcache could be unattached\n    std::string path;\n    rc = location(path);\n#ifdef __APPLE__\n    rc = setxattr(path.c_str(), key.c_str(), value.c_str(), value.size(), 0, 0);\n#else\n    rc = setxattr(path.c_str(), key.c_str(), value.c_str(), value.size(), 0);\n#endif\n  }\n\n  eos_static_debug(\"set_attr key=%s val=%s fd=%d rc=%d\\n\", key.c_str(),\n                   value.c_str(),\n                   fd, rc);\n  return -1;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndiskcache::attr(const std::string& key, std::string& value)\n/* -------------------------------------------------------------------------- */\n{\n  if (fd > 0) {\n    value.resize(4096);\n    ssize_t n = 0;\n#ifdef __APPLE__\n    n = fgetxattr(fd, key.c_str(), (void*) value.c_str(), value.size(), 0, 0);\n#else\n    n = fgetxattr(fd, key.c_str(), (void*) value.c_str(), value.size());\n#endif\n\n    if (n >= 0) {\n      value.resize(n);\n      return 0;\n    } else {\n      value.resize(0);\n      return -1;\n    }\n  }\n\n  return -1;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndiskcache::rescue(std::string& rescue_location)\n/* -------------------------------------------------------------------------- */\n{\n  std::string path;\n  int rc = location(path);\n\n  if (!rescue_location.length()) {\n    rescue_location = path;\n    rescue_location += \".recover\";\n  }\n\n  if (!rc) {\n    return ::rename(path.c_str(), rescue_location.c_str());\n  } else {\n    return rc;\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\ndiskcache::recovery_location(std::string& recovery_location)\n/* -------------------------------------------------------------------------- */\n{\n  std::string path;\n  int rc = location(path);\n  recovery_location = path;\n  recovery_location += \".download\";\n  return rc;\n}\n"
  },
  {
    "path": "fusex/data/diskcache.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file cache.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief data cache handling base class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_DISKCACHE_HH_\n#define FUSE_DISKCACHE_HH_\n\n#include <sys/stat.h>\n#include <sys/types.h>\n#include \"llfusexx.hh\"\n#include \"bufferll.hh\"\n#include \"data/cache.hh\"\n#include \"data/dircleaner.hh\"\n#include \"data/cacheconfig.hh\"\n#include <XrdSys/XrdSysPthread.hh>\n#include <map>\n#include <string>\n\nclass diskcache : public cache\n{\npublic:\n  diskcache(fuse_ino_t _ino);\n  virtual ~diskcache();\n\n  // base class interface\n  virtual int attach(fuse_req_t req, std::string& cookie, int flags) override;\n  virtual int detach(std::string& cookie) override;\n  virtual int unlink() override;\n\n  virtual ssize_t pread(void* buf, size_t count, off_t offset) override;\n  virtual ssize_t pwrite(const void* buf, size_t count, off_t offset) override;\n\n  virtual int truncate(off_t) override;\n  virtual int sync() override;\n\n  virtual size_t size() override;\n\n  virtual int set_attr(const std::string& key, const std::string& value) override;\n  virtual int attr(const std::string& key, std::string& value) override;\n\n  static int init(const cacheconfig& config);\n  static int init_daemonized(const cacheconfig& config);\n\n  virtual int rescue(std::string& location) override;\n\n  virtual int recovery_location(std::string& location) override;\n\n  virtual off_t prefetch_size() override\n  {\n    return sMaxSize;\n  }\n\nprivate:\n  XrdSysMutex mMutex;\n  int location(std::string& path, bool mkpath = true);\n  static off_t sMaxSize;\n  static float sCleanThreshold;\n\n  fuse_ino_t ino;\n  size_t nattached;\n  int fd;\n  struct stat attachstat;\n  struct stat detachstat;\n\n  bufferllmanager::shared_buffer buffer;\n\n  static std::string sLocation;\n\n  static bufferllmanager sBufferManager;\n\n  static std::shared_ptr<dircleaner> sDirCleaner;\n\n};\n\n#endif /* FUSE_JOURNALCACHE_HH_ */\n"
  },
  {
    "path": "fusex/data/interval_tree.hh",
    "content": "/*\n * interval_tree.hh\n *\n *  Created on: Mar 6, 2017\n *      Author: Michal Simon\n *\n ************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef INTERVALTREE_HH_\n#define INTERVALTREE_HH_\n\n#include \"rbtree.hh\"\n\n#include <set>\n#include <memory>\n#include <vector>\n#include <exception>\n#include <algorithm>\n#include <cstdlib>\n\ntemplate<typename I, typename V>\nclass interval_node_t\n{\npublic:\n\n  template<typename, typename> friend class interval_tree;\n  template<typename, typename, typename> friend class rbtree;\n  friend class IntervalTreeTest;\n\n\npublic:\n\n  interval_node_t(I low, I high, const V& value) :\n    low(low), high(high), value(value), key(this->low), max(high), colour(RED),\n    parent(nullptr) { }\n\n  const I low;\n  const I high;\n  V value;\n\nprivate:\n  const I& key;\n  I max;\n  colour_t colour;\n  interval_node_t* parent;\n\n  std::unique_ptr<interval_node_t> left;\n  std::unique_ptr<interval_node_t> right;\n};\n\ntemplate<typename I, typename V>\nclass interval_tree : public rbtree< I, V, interval_node_t<I, V> >\n{\nprivate:\n\n  typedef interval_node_t<I, V> N;\n\n  typedef typename rbtree<I, V, N>::leaf_node_t leaf_node_t;\n\n  std::unique_ptr<N> make_node(I low, I high, const V& value)\n  {\n    return std::unique_ptr<N>(new N(low, high, value));\n  }\n\npublic:\n\n  typedef typename rbtree<I, V, N>::iterator iterator;\n\n  struct less {\n\n    bool operator()(const iterator& x, const iterator& y) const\n    {\n      return x->low < y->low;\n    }\n  };\n\n  virtual ~interval_tree() { }\n\n  void insert(I low, I high, const V& value)\n  {\n    insert_into(low, high, value, this->tree_root);\n  }\n\n  void erase(I low, I high)\n  {\n    std::unique_ptr<N>& node = this->find_in(low, this->tree_root);\n\n    if (!node || node->low != low || node->high != high) {\n      return;\n    }\n\n    this->erase_node(node);\n  }\n\n  std::set<iterator, less> query(I low, I high)\n  {\n    std::set<iterator, less> result;\n    query(low, high, this->tree_root, result);\n    return result;\n  }\n\nprivate:\n\n  using rbtree<I, V, N>::insert;\n  using rbtree<I, V, N>::erase;\n  using rbtree<I, V, N>::find;\n\n  static bool overlaps(I low, I high, const N* node)\n  {\n    int64_t s1 = low + high;\n    int64_t d1 = high - low;\n    int64_t s2 = node->low + node->high;\n    int64_t d2 = node->high - node->low;\n    return std::abs(s2 - s1) < d1 + d2;\n  }\n\n  static void query(I low, I high, std::unique_ptr<N>& node,\n                    std::set<iterator, less>& result)\n  {\n    // base case\n    if (!node) {\n      return;\n    }\n\n    // the interval is to the right of the rightmost point of any interval\n    if (low > node->max) {\n      return;\n    }\n\n    // check if the interval overlaps fully with current node\n    if (overlaps(low, high, node.get())) {\n      result.insert(iterator(node.get()));\n    }\n\n    // check the left subtree\n    query(low, high, node->left, result);\n\n    // Do we need to check the right subtree?\n    if (high > node->low) {\n      query(low, high, node->right, result);\n    }\n  }\n\n  void insert_into(I low, I high, const V& value, std::unique_ptr<N>& node,\n                   N* parent = nullptr)\n  {\n    if (!node) {\n      node = make_node(low, high, value);\n      node->parent = parent;\n      ++this->tree_size;\n      update_max(node->parent, node->max);\n      this->rb_insert_case1(node.get());\n      return;\n    }\n\n    if (low == node->low) {\n      return;\n    }\n\n    if (low < node->low) {\n      insert_into(low, high, value, node->left, node.get());\n    } else {\n      insert_into(low, high, value, node->right, node.get());\n    }\n  }\n\n  void erase_node(std::unique_ptr<N>& node)\n  {\n    if (!node) {\n      return;\n    }\n\n    if (this->has_two(node.get())) {\n      // in this case:\n      // 1. look for the in-order successor\n      // 2. replace the node with the in-order successor\n      // 3. erase the in-order successor\n      N* n = node.get();\n      std::unique_ptr<N>& successor = this->find_successor(node);\n      this->swap_successor(node, successor);\n\n      // we don't update max since in erase_node we\n      // will do it after removing respective node\n      // we swapped the node with successor and the\n      // 'successor' unique pointer holds now the node\n      if (successor.get() == n) {\n        erase_node(successor);\n      }// otherwise the successor was the right child of node,\n      // hence node should be now the right child of 'node'\n      // unoique pointer\n      else if (node->right.get() == n) {\n        erase_node(node->right);\n      }// there are no other cases so anything else is wrong\n      else {\n        throw std::logic_error(\"Bad rbtree swap.\");\n      }\n\n      return;\n    }\n\n    // node has at most one child\n    // in this case simply replace the node with the\n    // single child or null if there are no children\n    N* parent = node->parent;\n    std::unique_ptr<N>& child = node->left ? node->left : node->right;\n    colour_t old_colour = node->colour;\n\n    if (child) {\n      child->parent = node->parent;\n    }\n\n    node.reset(child.release());\n    update_max(parent);\n    --this->tree_size;\n\n    if (old_colour == BLACK) {\n      if (node && node->colour == RED) {\n        node->colour = BLACK;\n      } else {\n        // if we are here the node is null because a BLACK\n        // node that has at most one non-leaf child must\n        // have two null children (null children are BLACK)\n        if (node) {\n          throw rb_invariant_error();\n        }\n\n        this->rb_erase_case1(leaf_node_t(parent));\n      }\n    } else if (node)\n      // if the node was red it has to have two BLACK children\n      // and since at most one of those children is a non-leaf\n      // child actually both have to be leafs (null) in order\n      // to satisfy the red-black tree invariant\n    {\n      throw rb_invariant_error();\n    }\n  }\n\n  void update_max(N* node, I new_high)\n  {\n    while (node) {\n      if (new_high > node->max) {\n        node->max = new_high;\n        node = node->parent;\n      } else {\n        break;\n      }\n    }\n  }\n\n  void update_max(N* node)\n  {\n    while (node) {\n      set_max(node);\n      node = node->parent;\n    }\n  }\n\n  void set_max(N* node)\n  {\n    if (!node->left && !node->right) {\n      node->max = node->high;\n      return;\n    }\n\n    if (!node->left || !node->right) {\n      node->max = std::max(node->high,\n                           (node->left ? node->left->max : node->right->max));\n      return;\n    }\n\n    node->max = std::max(node->high, std::max(node->left->max, node->right->max));\n  }\n\n  virtual void right_rotation(N* node)\n  {\n    N* pivot = node->left.get();\n    rbtree<I, V, N>::right_rotation(node);\n    set_max(node); // set first max for node since now it's lower in the tree\n    set_max(pivot);\n  }\n\n  virtual void left_rotation(N* node)\n  {\n    N* pivot = node->right.get();\n    rbtree<I, V, N>::left_rotation(node);\n    set_max(node); // set first max for node since now it's lower in the tree\n    set_max(pivot);\n  }\n};\n\n#endif /* INTERVALTREE_HH_ */\n"
  },
  {
    "path": "fusex/data/io.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file io.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief io class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_IO_HH_\n#define FUSE_IO_HH_\n\n#include \"data/cache.hh\"\n#include \"data/journalcache.hh\"\n\n#define O_CACHE 040000000\n\nnamespace XrdCl\n{\nclass Proxy;\ntypedef std::shared_ptr<XrdCl::Proxy> shared_proxy;\n}\n\nclass io\n{\npublic:\n\n  io()\n  {\n    _file = 0;\n    _journal = 0;\n    ino = 0;\n    caching = true;\n  }\n\n  io(fuse_ino_t _ino)\n  {\n    _file = 0;\n    _journal = 0;\n    ino = _ino;\n    caching = true;\n  }\n\n  ~io()\n  {\n    delete _file;\n    delete _journal;\n  }\n\n  void disable_caches()\n  {\n    delete _file;\n    delete _journal;\n    _file = 0;\n    _journal = 0;\n    caching = false;\n  }\n\n  void disable_file_cache()\n  {\n    delete _file;\n    _file = 0;\n  }\n\n  bool is_caching()\n  {\n    return caching;\n  }\n\n  void set_file(cache* file)\n  {\n    _file = file;\n  }\n\n  void set_journal(journalcache* journal)\n  {\n    _journal = journal;\n  }\n  void set_xrdioro(fuse_req_t req, XrdCl::shared_proxy _cl)\n  {\n    _xrdioro[\"default\"] = _cl;\n  }\n\n  void set_xrdiorw(fuse_req_t req, XrdCl::shared_proxy _cl)\n  {\n    _xrdiorw[\"default\"] = _cl;\n  }\n\n  cache* file()\n  {\n    return _file;\n  }\n\n  journalcache* journal()\n  {\n    return _journal;\n  }\n\n  XrdCl::shared_proxy xrdioro(fuse_req_t req)\n  {\n    // avoid creating a null entry if none exists,\n    // as this may be confusing for the caller\n    if (auto it = _xrdioro.find(\"default\"); it != _xrdioro.end())\n      return it->second;\n    return XrdCl::shared_proxy();\n  }\n\n  XrdCl::shared_proxy xrdiorw(fuse_req_t req)\n  {\n    // avoid creating a null entry if none exists,\n    // as this may be confusing for the caller\n    if (auto it = _xrdiorw.find(\"default\"); it != _xrdiorw.end())\n      return it->second;\n    return XrdCl::shared_proxy();\n  }\n\n  bool has_xrdioro(fuse_req_t req)\n  {\n    return _xrdioro.count(\"default\");\n  }\n\n  bool has_xrdiorw(fuse_req_t req)\n  {\n    return _xrdiorw.count(\"default\");\n  }\n\n  XrdCl::shared_proxy xrdioro(std::string& id)\n  {\n    // avoid creating a null entry if none exists,\n    // as this may be confusing for the caller\n    if (auto it = _xrdioro.find(id); it != _xrdioro.end())\n      return it->second;\n    return XrdCl::shared_proxy();\n  }\n\n  XrdCl::shared_proxy xrdiorw(std::string& id)\n  {\n    // avoid creating a null entry if none exists,\n    // as this may be confusing for the caller\n    if (auto it = _xrdiorw.find(id); it != _xrdiorw.end())\n      return it->second;\n    return XrdCl::shared_proxy();\n  }\n\n  std::map<std::string, XrdCl::shared_proxy>& get_xrdiorw()\n  {\n    return _xrdiorw;\n  }\n\n  std::map<std::string, XrdCl::shared_proxy>& get_xrdioro()\n  {\n    return _xrdioro;\n  }\n\n  bool erase_xrdioro(fuse_req_t req)\n  {\n    _xrdioro.erase(\"default\");\n    return true;\n  }\n\n  bool erase_xrdioro(const std::string& id)\n  {\n    _xrdioro.erase(id);\n    return true;\n  }\n\nprivate:\n  cache* _file;\n  journalcache* _journal;\n  std::map<std::string, XrdCl::shared_proxy> _xrdioro;\n  std::map<std::string, XrdCl::shared_proxy> _xrdiorw;\n  fuse_ino_t ino;\n  bool caching;\n};\n\ntypedef std::shared_ptr<io> shared_io;\n\n#endif\n"
  },
  {
    "path": "fusex/data/journalcache.cc",
    "content": "/*\n * journalcache.cc\n *\n *  Created on: Mar 15, 2017\n *      Author: Michal Simon\n *\n ************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"journalcache.hh\"\n#include \"dircleaner.hh\"\n#include \"io.hh\"\n#include \"common/Path.hh\"\n#include \"common/Logging.hh\"\n#ifdef __APPLE__\n#include <XrdSys/XrdSysPlatform.hh>\n#endif\n#include <algorithm>\n#include <iostream>\n\nconstexpr size_t journalcache::sDefaultMaxSize;\n\nstd::string journalcache::sLocation;\nsize_t journalcache::sMaxSize = journalcache::sDefaultMaxSize;\n\nstd::shared_ptr<dircleaner> journalcache::jDirCleaner;\n\njournalcache::journalcache(fuse_ino_t ino) : ino(ino), cachesize(0),\n  truncatesize(-1), max_offset(0), fd(-1), nbAttached(0), nbFlushed(0)\n{\n  memset(&attachstat, 0, sizeof(attachstat));\n  memset(&detachstat, 0, sizeof(detachstat));\n}\n\n\njournalcache::~journalcache()\n{\n  if (fd > 0) {\n    eos_static_debug(\"closing fd=%d\\n\", fd);\n    detachstat.st_size = 0 ;\n    fstat(fd, &detachstat);\n    int rc = close(fd);\n\n    if (rc) {\n      eos_static_crit(\"%s\", \"msg=\\\"journalcache::~journalcache fd close failed\\\"\");\n      std::abort();\n    }\n\n    if (jDirCleaner) {\n      jDirCleaner->get_external_tree().change(detachstat.st_size - attachstat.st_size,\n                                              0);\n    }\n\n    if (!(flags & O_CACHE)) {\n      // only clean write caches\n      journal.clear();\n      unlink();\n    }\n\n    fd = -1;\n  }\n}\n\nint journalcache::location(std::string& path, bool mkpath)\n{\n  char cache_path[1024 + 20];\n  snprintf(cache_path, sizeof(cache_path), \"%s/%03lX/%08lX.jc\",\n           sLocation.c_str(),\n           (ino > 0x0fffffff) ? (ino >> 28) % 4096 : ino % 4096, ino);\n\n  if (mkpath) {\n    eos::common::Path cPath(cache_path);\n\n    if (!cPath.MakeParentPath(S_IRWXU)) {\n      return -errno;\n    }\n  }\n\n  path = cache_path;\n  return 0;\n}\n\nint journalcache::read_journal()\n{\n  journal.clear();\n  const size_t bufsize = 1024;\n  char buffer[bufsize];\n  ssize_t bytesRead = 0, totalBytesRead = 0;\n  int64_t pos = 0;\n  ssize_t entrySize = 0;\n\n  while (true) {\n    bytesRead = ::pread(fd, buffer, bufsize, totalBytesRead);\n\n    if (bytesRead <= 0) {\n      break;\n    }\n\n    pos = 0;\n\n    do {\n      if (entrySize == 0) {\n        if ((pos + sizeof(header_t)) > (long unsigned int)bytesRead) {\n          // no complete header is left, we have re-align the next read to get a full header\n          bytesRead = pos;\n          break;\n        }\n\n        header_t* header = reinterpret_cast<header_t*>(buffer + pos);\n        journal.insert(header->offset, header->offset + header->size,\n                       totalBytesRead + pos);\n        entrySize = header->size;\n        pos += sizeof(header_t);\n      }\n\n      size_t shift = entrySize > bytesRead - pos ? bytesRead - pos : entrySize;\n      pos += shift;\n      entrySize -= shift;\n    } while (pos < bytesRead);\n\n    totalBytesRead += bytesRead;\n  }\n\n  if (bytesRead < 0) {\n    return errno;\n  }\n\n  return totalBytesRead;\n}\n\nint journalcache::attach(fuse_req_t req, std::string& cookie, int _flags)\n{\n  XrdSysMutexHelper lck(mtx);\n  flags = _flags;\n\n  if ((nbAttached == 0) && (fd == -1)) {\n    std::string path;\n    int rc = location(path);\n\n    if (rc) {\n      return rc;\n    }\n\n    if (stat(path.c_str(), &attachstat)) {\n      // a new file\n      if (jDirCleaner) {\n        jDirCleaner->get_external_tree().change(0, 1);\n      }\n    }\n\n    // need to open the file\n    size_t tries = 0;\n\n    do {\n      fd = open(path.c_str(), O_CREAT | O_RDWR, S_IRWXU);\n\n      if (fd < 0) {\n        if (errno == ENOENT) {\n          tries++;\n          // re-create the directory structure\n          rc = location(path);\n\n          if (rc) {\n            return rc;\n          }\n\n          if (tries < 10) {\n            continue;\n          } else {\n            return -errno;\n          }\n        }\n\n        return -errno;\n      }\n\n      break;\n    } while (1);\n\n    cachesize = read_journal();\n  }\n\n  nbAttached++;\n  return 0;\n}\n\nint journalcache::detach(std::string& cookie)\n{\n  XrdSysMutexHelper lck(mtx);\n  nbAttached--;\n  return 0;\n}\n\nint journalcache::unlink()\n{\n  std::string path;\n  int rc = location(path);\n\n  if (!rc) {\n    struct stat buf;\n    rc = stat(path.c_str(), &buf);\n\n    if (!rc) {\n      rc = ::unlink(path.c_str());\n\n      if (!rc) {\n        // a deleted file\n        if (jDirCleaner) {\n          jDirCleaner->get_external_tree().change(-buf.st_size, -1);\n        }\n      }\n    }\n  }\n\n  return rc;\n}\n\nint journalcache::rescue(std::string& rescue_location)\n{\n  std::string path;\n  int rc = location(path);\n\n  if (!rescue_location.length()) {\n    rescue_location = path;\n    rescue_location += \".recover\";\n  }\n\n  if (!rc) {\n    return ::rename(path.c_str(), rescue_location.c_str());\n  } else {\n    return rc;\n  }\n}\n\nssize_t journalcache::pread(void* buf, size_t count, off_t offset)\n{\n  read_lock lck(clck);\n  auto result = journal.query(offset, offset + count);\n\n  // there is not a single interval that overlaps\n  if (result.empty()) {\n    return 0;\n  }\n\n  char* buffer = reinterpret_cast<char*>(buf);\n  uint64_t off = offset;\n  uint64_t bytesRead = 0;\n\n  for (auto& itr : result) {\n    if (itr->low <= off && off < itr->high) {\n      // read from cache\n      uint64_t cacheoff = itr->value + sizeof(header_t) + (off - itr->low);\n      int64_t intervalsize = itr->high - off;\n      int64_t bytesLeft = count - bytesRead;\n      int64_t bufsize = intervalsize < bytesLeft ? intervalsize : bytesLeft;\n      ssize_t ret = ::pread(fd, buffer, bufsize, cacheoff);\n\n      if (ret < 0) {\n        return -1;\n      }\n\n      bytesRead += ret;\n      off += ret;\n      buffer += ret;\n\n      if (bytesRead >= count) {\n        break;\n      }\n    }\n  }\n\n  if ((truncatesize != -1) && ((ssize_t) offset >= truncatesize)) {\n    // offset after truncation mark\n    return 0;\n  }\n\n  if ((truncatesize != -1) && ((ssize_t)(offset + bytesRead) > truncatesize)) {\n    // read over truncation size\n    return (truncatesize - offset);\n  }\n\n  return bytesRead;\n}\n\nvoid journalcache::process_intersection(interval_tree<uint64_t, const void*>&\n                                        to_write, interval_tree<uint64_t, uint64_t>::iterator itr,\n                                        std::vector<chunk_t>& updates)\n{\n  auto result = to_write.query(itr->low, itr->high);\n\n  if (result.empty()) {\n    return;\n  }\n\n  if (result.size() > 1) {\n    throw std::logic_error(\"journalcache: overlapping journal entries\");\n  }\n\n  const interval_tree<uint64_t, const void*>::iterator to_wrt = *result.begin();\n  // the intersection\n  uint64_t low = std::max(to_wrt->low, itr->low);\n  uint64_t high = std::min(to_wrt->high, itr->high);\n  // update\n  chunk_t update;\n  update.offset = offset_for_update(itr->value, low - itr->low);\n  update.size = high - low;\n  update.buff = static_cast<const char*>(to_wrt->value) + (low - to_wrt->low);\n  updates.push_back(std::move(update));\n  // update the 'to write' intervals\n  uint64_t wrtlow = to_wrt->low;\n  uint64_t wrthigh = to_wrt->high;\n  const void* wrtbuff = to_wrt->value;\n  to_write.erase(wrtlow, wrthigh);\n\n  // the intersection overlaps with the given\n  // interval so there is nothing more to do\n  if (low == wrtlow && high == wrthigh) {\n    return;\n  }\n\n  if (high < wrthigh) {\n    // the remaining right-hand-side interval\n    const char* buff = static_cast<const char*>(wrtbuff) + (high - wrtlow);\n    to_write.insert(high, wrthigh, buff);\n  }\n\n  if (low > wrtlow) {\n    // the remaining left-hand-side interval\n    to_write.insert(wrtlow, low, wrtbuff);\n  }\n}\n\nint journalcache::update_cache(std::vector<chunk_t>& updates)\n{\n  // make sure we are updating the cache in ascending order\n  std::sort(updates.begin(), updates.end());\n  int rc = 0;\n\n  for (auto& u : updates) {\n    rc = ::pwrite(fd, u.buff, u.size,\n                  u.offset); // TODO is it safe to assume it will write it all\n\n    if (rc <= 0) {\n      return errno;\n    }\n  }\n\n  return 0;\n}\n\nssize_t journalcache::pwrite(const void* buf, size_t count, off_t offset)\n{\n  if (count <= 0) {\n    return 0;\n  }\n\n  write_lock lck(clck);\n\n  while (sMaxSize <= cachesize) {\n    clck.write_wait();\n  }\n\n  interval_tree<uint64_t, const void*> to_write;\n  std::vector<chunk_t> updates;\n  to_write.insert(offset, offset + count, buf);\n  auto res = journal.query(offset, offset + count);\n\n  for (auto itr : res) {\n    process_intersection(to_write, itr, updates);\n  }\n\n  int rc = update_cache(updates);\n\n  if (rc) {\n    return -1;\n  }\n\n  interval_tree<uint64_t, const void*>::iterator itr;\n\n  // TODO this could be replaced with a single pwritev\n  for (itr = to_write.begin(); itr != to_write.end(); ++itr) {\n    uint64_t size = itr->high - itr->low;\n    header_t header;\n    header.offset = itr->low;\n    header.size = size;\n    iovec iov[2];\n    iov[0].iov_base = &header;\n    iov[0].iov_len = sizeof(header_t);\n    iov[1].iov_base = const_cast<void*>(itr->value);\n    iov[1].iov_len = size;\n    // @todo: fix this properly for the mac if there is such support\n    rc = ::pwrite(fd, iov[0].iov_base, iov[0].iov_len, cachesize);\n    rc += ::pwrite(fd, iov[1].iov_base, iov[1].iov_len,\n                   cachesize + iov[0].iov_len);\n\n    // rc = ::pwritev( fd, iov, 2, cachesize ); // TODO is it safe to assume it will write it all\n    if (rc <= 0) {\n      return -1;\n    }\n\n    journal.insert(itr->low, itr->high, cachesize);\n    cachesize += sizeof(header_t) + size;\n  }\n\n  if ((truncatesize != -1) && ((ssize_t)(offset + count) > truncatesize)) {\n    // journal written after last truncation size\n    truncatesize = offset + count;\n  }\n\n  if ((ssize_t)(offset + count) >  max_offset) {\n    max_offset = offset + count;\n  }\n\n  return count;\n}\n\nint journalcache::truncate(off_t offset, bool invalidate)\n{\n  int rc = 0;\n  write_lock lck(clck);\n  fstat(fd, &detachstat);\n\n  if (offset) {\n    truncatesize = offset;\n    max_offset = offset;\n  } else {\n    // distinguish cache invalidation from 0 truncation\n    if (invalidate) {\n      truncatesize = -1;\n    } else {\n      truncatesize = 0;\n    }\n\n    max_offset = 0;\n    journal.clear();\n    cachesize = 0;\n\n    if (!::ftruncate(fd, 0)) {\n      if (jDirCleaner) {\n        jDirCleaner->get_external_tree().change(detachstat.st_size - attachstat.st_size,\n                                                0);\n      }\n\n      attachstat.st_size = offset;\n    }\n  }\n\n  return rc;\n}\n\nint journalcache::sync()\n{\n  return ::fdatasync(fd);\n}\n\nsize_t journalcache::size()\n{\n  return cachesize;\n}\n\noff_t journalcache::get_max_offset()\n{\n  read_lock lck(clck);\n  return max_offset;\n}\n\n\nint journalcache::init(const cacheconfig& config)\n{\n  if (::access(config.location.c_str(), W_OK)) {\n    return errno;\n  }\n\n  sLocation = config.journal;\n\n  if (config.per_file_journal_max_size) {\n    journalcache::sMaxSize = config.per_file_journal_max_size;\n  }\n\n  eos_static_info(\"journalcache location %s\", sLocation.c_str());\n  return 0;\n}\n\nint journalcache::init_daemonized(const cacheconfig& config)\n{\n  jDirCleaner = std::make_shared<dircleaner>(config.location,\n                \"jc\",\n                config.total_file_journal_size,\n                config.total_file_journal_inodes,\n                config.clean_threshold\n                                            );\n  jDirCleaner->set_trim_suffix(\".jc\");\n\n  if (config.clean_on_startup) {\n    eos_static_info(\"cleaning journal path=%s\", config.location.c_str());\n\n    if (jDirCleaner->cleanall(\".jc\")) {\n      eos_static_err(\"journal cleanup failed\");\n      return -1;\n    }\n  }\n\n  return 0;\n}\n\nint journalcache::remote_sync(cachesyncer& syncer)\n{\n  write_lock lck(clck);\n  int ret = syncer.sync(fd, journal, sizeof(header_t), truncatesize);\n\n  if (!ret) {\n    journal.clear();\n    eos_static_debug(\"ret=%d truncatesize=%ld\\n\", ret, truncatesize);\n    ret |= ::ftruncate(fd, 0);\n    eos_static_debug(\"ret=%d errno=%d\\n\", ret, errno);\n  }\n\n  clck.broadcast();\n  return ret;\n}\n\nint journalcache::remote_sync_async(XrdCl::shared_proxy proxy)\n{\n  // sends all the journal content as asynchronous write requests\n  int ret = 0;\n\n  if (!proxy) {\n    return -1;\n  }\n\n  off_t offshift = sizeof(header_t);\n  write_lock lck(clck);\n\n  for (auto itr = journal.begin(); itr != journal.end(); ++itr) {\n    off_t cacheoff = itr->value + offshift;\n    size_t size = itr->high - itr->low;\n    // prepare async buffer\n    XrdCl::Proxy::write_handler handler = proxy->WriteAsyncPrepare(proxy, size,\n                                          itr->low,\n                                          0);\n    int bytesRead = ::pread(fd, (void*) handler->buffer(), size, cacheoff);\n\n    if (bytesRead < 0) {\n      // TODO handle error\n      clck.broadcast();\n      return -1;\n    }\n\n    if (bytesRead < (int) size) {\n      // TODO handle error - still we continue\n    }\n\n    XrdCl::XRootDStatus st = proxy->ScheduleWriteAsync(0, handler);\n\n    if (!st.IsOK()) {\n      eos_static_err(\"failed to issue async-write\");\n      clck.broadcast();\n      return -1;\n    }\n  }\n\n  // there might be a truncate call after the writes to be applied\n  if (truncatesize != -1) {\n    XrdCl::XRootDStatus st = proxy->Truncate(truncatesize);\n\n    if (!st.IsOK()) {\n      eos_static_err(\"failed to truncate\");\n      clck.broadcast();\n      return -1;\n    }\n\n    truncatesize = -1;\n  }\n\n  journal.clear();\n  eos_static_debug(\"ret=%d truncatesize=%ld\\n\", ret, truncatesize);\n  errno = 0;\n  ret |= ::ftruncate(fd, 0);\n  eos_static_debug(\"ret=%d errno=%d\\n\", ret, errno);\n  clck.broadcast();\n  return ret;\n}\n\nint journalcache::reset()\n{\n  write_lock lck(clck);\n  journal.clear();\n  int retc = (fd > 0) ?::ftruncate(fd, 0) : 0;\n  cachesize = 0;\n  max_offset = 0;\n  truncatesize = -1;\n  clck.broadcast();\n  return retc;\n}\n\nstd::string journalcache::dump()\n{\n  std::string out;\n  out += \"fd=\";\n  out += std::to_string(fd);\n  out += \" truncatexize=\";\n  out += std::to_string(get_truncatesize());\n  out += \" maxoffset=\";\n  out += std::to_string(get_max_offset());\n  out += \" nbattached=\";\n  out += std::to_string(nbAttached);\n  out += \" nbflushed=\";\n  out += std::to_string(nbFlushed);\n  return out;\n}\n\nstd::vector<journalcache::chunk_t> journalcache::get_chunks(off_t offset,\n    size_t size)\n{\n  read_lock lck(clck);\n  auto result = journal.query(offset, offset + size);\n  std::vector<chunk_t> ret;\n\n  for (auto& itr : result) {\n    uint64_t off = (off_t) itr->low < (off_t) offset ? offset : itr->low;\n    uint64_t count = itr->high < offset + size ? itr->high - off : offset + size -\n                     off;\n    uint64_t cacheoff = itr->value + sizeof(header_t) + (off - itr->low);\n    std::unique_ptr<char[] > buffer(new char[count]);\n    ssize_t rc = ::pread(fd, buffer.get(), count, cacheoff);\n\n    if (rc < 0) {\n      return ret;\n    }\n\n    ret.push_back(chunk_t(off, count, std::move(buffer)));\n  }\n\n  return ret;\n}\n"
  },
  {
    "path": "fusex/data/journalcache.hh",
    "content": "/*\n * journalcache.hh\n *\n *  Created on: Mar 15, 2017\n *      Author: Michal Simon\n *\n ************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSEX_JOURNALCACHE_HH_\n#define FUSEX_JOURNALCACHE_HH_\n\n#include \"cache.hh\"\n#include \"cachelock.hh\"\n#include \"cachesyncer.hh\"\n#include \"cacheconfig.hh\"\n#include \"xrdclproxy.hh\"\n#include \"interval_tree.hh\"\n#include \"data/dircleaner.hh\"\n\n#include <stdint.h>\n\n#include <string>\n\nclass journalcache\n{\n\n  struct header_t {\n    uint64_t offset;\n    uint64_t size;\n  };\n\npublic:\n\n  struct chunk_t {\n\n    chunk_t() : offset(0), size(0), buff(0) { }\n\n    /* constructor - no ownership of underlying buffer */\n    chunk_t(off_t offset, size_t size, const void* buff) : offset(offset),\n      size(size), buff(buff) { }\n\n    /* constructor - with ownership of underlying buffer */\n    chunk_t(off_t offset, size_t size, std::unique_ptr<char[]> buff) :\n      offset(offset), size(size), buffOwnership(std::move(buff)),\n      buff((const void*) buffOwnership.get()) {}\n\n    off_t offset;\n    size_t size;\n    std::unique_ptr<char[]> buffOwnership;\n    const void* buff;\n\n    bool operator<(const chunk_t& u) const\n    {\n      return offset < u.offset;\n    }\n  };\n\n  // TODO Some dummy default\n  static constexpr size_t sDefaultMaxSize = 128 * 1024 * 1024ll;\n\n  journalcache(fuse_ino_t _ino);\n  virtual ~journalcache();\n\n  // base class interface\n  int attach(fuse_req_t req, std::string& cookie, int flags);\n  int detach(std::string& cookie);\n  int unlink();\n\n  ssize_t pread(void* buf, size_t count, off_t offset);\n  ssize_t pwrite(const void* buf, size_t count, off_t offset);\n\n  int truncate(off_t, bool invalidate = false);\n  int sync();\n\n  size_t size();\n\n  off_t get_max_offset();\n\n  ssize_t get_truncatesize()\n  {\n    XrdSysMutexHelper lck(mtx);\n    return truncatesize;\n  }\n\n  int set_attr(const std::string& key, const std::string& value)\n  {\n    return 0;\n  }\n\n  int attr(const std::string& key, std::string& value)\n  {\n    return 0;\n  }\n\n  int remote_sync(cachesyncer& syncer);\n\n  int remote_sync_async(XrdCl::shared_proxy proxy);\n\n  static int init(const cacheconfig& config);\n  static int init_daemonized(const cacheconfig& config);\n\n  bool fits(ssize_t count)\n  {\n    return (sMaxSize >= (cachesize + count));\n  }\n\n  int reset();\n\n  int rescue(std::string& location);\n\n  std::vector<chunk_t> get_chunks(off_t offset, size_t size);\n\n  int set_cookie(const std::string& cookie)\n  {\n    return set_attr(\"user.eos.cache.cookie\", cookie);\n  }\n\n  bool first_flush()\n  {\n    return (!nbFlushed) ? true : false;\n  }\n\n  void done_flush()\n  {\n    nbFlushed++;\n  }\n\n  std::string dump();\nprivate:\n\n  void process_intersection(interval_tree<uint64_t, const void*>& write,\n                            interval_tree<uint64_t, uint64_t>::iterator acr, std::vector<chunk_t>& updates);\n\n  int location(std::string& path, bool mkpath = true);\n\n  static uint64_t offset_for_update(uint64_t offset, uint64_t shift)\n  {\n    return offset + sizeof(header_t) + shift;\n  }\n\n  int update_cache(std::vector<chunk_t>& updates);\n\n  int read_journal();\n\n  fuse_ino_t ino;\n  size_t cachesize;\n  ssize_t truncatesize;\n  off_t max_offset;\n  int fd;\n  // the value is the offset in the cache file\n  interval_tree<uint64_t, uint64_t> journal;\n  size_t nbAttached;\n  size_t nbFlushed;\n  cachelock clck;\n  XrdSysMutex mtx;\n  int flags;\n  bufferllmanager::shared_buffer buffer;\n  static std::string sLocation;\n  static size_t sMaxSize;\n\n  struct stat attachstat;\n  struct stat detachstat;\n\n  static std::shared_ptr<dircleaner> jDirCleaner;\n};\n\n#endif /* FUSEX_JOURNALCACHE_HH_ */\n"
  },
  {
    "path": "fusex/data/memorycache.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file memorycache.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief data cache in-memory implementation\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"memorycache.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Path.hh\"\n#include <unistd.h>\n#include <sys/types.h>\n#include <errno.h>\n#include \"common/XattrCompat.hh\"\n\n/* -------------------------------------------------------------------------- */\nmemorycache::memorycache(fuse_ino_t _ino) : ino(_ino)\n  /* -------------------------------------------------------------------------- */\n{\n  (void) ino;\n  return;\n}\n\n/* -------------------------------------------------------------------------- */\nmemorycache::~memorycache()\n/* -------------------------------------------------------------------------- */\n{\n  return;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nmemorycache::attach(fuse_req_t req, std::string& cookie, int flags)\n/* -------------------------------------------------------------------------- */\n{\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nmemorycache::detach(std::string& cookie)\n/* -------------------------------------------------------------------------- */\n{\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nmemorycache::unlink()\n/* -------------------------------------------------------------------------- */\n{\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nssize_t\n/* -------------------------------------------------------------------------- */\nmemorycache::pread(void* buf, size_t count, off_t offset)\n{\n  return (ssize_t) buffer.readData(buf, offset, count);\n}\n\nssize_t\n/* -------------------------------------------------------------------------- */\nmemorycache::pwrite(const void* buf, size_t count, off_t offset)\n/* -------------------------------------------------------------------------- */\n{\n  return (ssize_t) buffer.writeData(buf, offset, count);\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nmemorycache::truncate(off_t offset)\n/* -------------------------------------------------------------------------- */\n{\n  buffer.truncateData(offset);\n  return 0;\n}\n\nint\n/* -------------------------------------------------------------------------- */\nmemorycache::sync()\n/* -------------------------------------------------------------------------- */\n{\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nsize_t\n/* -------------------------------------------------------------------------- */\nmemorycache::size()\n{\n  return buffer.getSize();\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nmemorycache::set_attr(const std::string& key, const std::string& value)\n{\n  XrdSysMutexHelper lLock(xattrmtx);\n  xattr[key] = value;\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nmemorycache::attr(const std::string& key, std::string& value)\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper lLock(xattrmtx);\n\n  if (xattr.count(key)) {\n    value = xattr[key];\n    return 0;\n  }\n\n  errno = ENOATTR;\n  return -1;\n}\n"
  },
  {
    "path": "fusex/data/memorycache.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file memorycache.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief data cache handling base class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_MEMORYCACHE_HH_\n#define FUSE_MEMORYCACHE_HH_\n\n#include <sys/stat.h>\n#include <sys/types.h>\n#include \"llfusexx.hh\"\n#include \"bufferll.hh\"\n#include \"cache.hh\"\n#include <XrdSys/XrdSysPthread.hh>\n#include <map>\n#include <string>\n\nclass memorycache : public cache\n{\npublic:\n  memorycache(fuse_ino_t _ino);\n  virtual ~memorycache();\n\n  // base class interface\n  virtual int attach(fuse_req_t req, std::string& cookie, int flags) override;\n  virtual int detach(std::string& cookie) override;\n  virtual int unlink() override;\n\n  virtual ssize_t pread(void* buf, size_t count, off_t offset) override;\n  virtual ssize_t pwrite(const void* buf, size_t count, off_t offset) override;\n\n  virtual int truncate(off_t) override;\n  virtual int sync() override;\n\n  virtual size_t size() override;\n\n  virtual int set_attr(const std::string& key, const std::string& value) override;\n  virtual int attr(const std::string& key, std::string& value) override;\n\nprivate:\n  bufferll buffer;\n  XrdSysMutex xattrmtx;\n  std::map<std::string, std::string> xattr;\n  fuse_ino_t ino;\n};\n\n\n\n\n#endif /* FUSE_MEMORYCACHE_HH_ */\n"
  },
  {
    "path": "fusex/data/rbtree.hh",
    "content": "/*\n * rbtree.hh\n *\n *  Created on: Mar 6, 2017\n *      Author: Michal Simon\n *\n ************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef RBTREE_HH_\n#define RBTREE_HH_\n\n#include <memory>\n#include <stdexcept>\n\nclass rb_invariant_error : public std::exception\n{\npublic:\n\n  rb_invariant_error() { }\n\n  virtual const char* what() const throw()\n  {\n    return \"Red-black tree invariant violation!\";\n  }\n\n};\n\nenum colour_t {\n  RED = true,\n  BLACK = false\n};\n\ntemplate<typename K, typename V>\nclass node_t\n{\n  template<typename, typename, typename> friend class rbtree;\n  friend class RBTreeTest;\n\npublic:\n\n  node_t(const K& key, const V& value) : key(key), value(value), colour(RED),\n    parent(nullptr) { }\n\n  const K key;\n  V value;\n\nprivate:\n  colour_t colour;\n  node_t* parent;\n\n  std::unique_ptr<node_t> left;\n  std::unique_ptr<node_t> right;\n};\n\ntemplate<typename K, typename V, typename N = node_t<K, V> >\nclass rbtree\n{\n  friend class RBTreeTest;\n  friend class IntervalTreeTest;\n\nprotected:\n\n  static std::unique_ptr<N> make_node(const K& key, const V& value)\n  {\n    return std::unique_ptr<N>(new N(key, value));\n  }\n\n  static void swap_right_child(std::unique_ptr<N>& node,\n                               std::unique_ptr<N>& successor)\n  {\n    std::swap(node->colour, successor->colour);\n    // first do the obvious\n    std::swap(node->left, successor->left);\n\n    if (node->left) {\n      node->left->parent = node.get();\n    }\n\n    if (successor->left) {\n      successor->left->parent = successor.get();\n    }\n\n    // now gather remaining pointers\n    N* p = node->parent;\n    N* n = node.release();\n    N* s = successor.release();\n    N* s_right = s->right.release();\n    // and finally reassign those pointers\n    s->parent = p;\n    node.reset(s);\n    s->right.reset(n);\n    n->parent = s;\n    n->right.reset(s_right);\n\n    if (s_right) {\n      s_right->parent = n;\n    }\n  }\n\n  static void swap_successor(std::unique_ptr<N>& node,\n                             std::unique_ptr<N>& successor)\n  {\n    // first check if successor is a direct child of node,\n    // since it is the in-order successor it can be only\n    // the right child\n    if (node->right.get() == successor.get()) {\n      // it is the right child\n      swap_right_child(node, successor);\n      return;\n    }\n\n    // swap colour\n    std::swap(node->colour, successor->colour);\n    // swap parents\n    std::swap(node, successor);\n    std::swap(node->parent, successor->parent);\n    // swap left\n    std::swap(node->left, successor->left);\n\n    if (node->left) {\n      node->left->parent = node.get();\n    }\n\n    if (successor->left) {\n      successor->left->parent = successor.get();\n    }\n\n    // swap right\n    std::swap(node->right, successor->right);\n\n    if (node->right) {\n      node->right->parent = node.get();\n    }\n\n    if (successor->right) {\n      successor->right->parent = successor.get();\n    }\n  }\n\n  static std::unique_ptr<N> null_node;\n\n  // this class is just used in rb_erase_case# methods as\n  // they need to accept a leaf (null) node as an argument\n  // that can return its parent and is BLACK\n\n  struct leaf_node_t {\n\n    leaf_node_t(N* parent) : colour(BLACK), parent(parent) { }\n\n    leaf_node_t(const leaf_node_t& leaf) : colour(leaf.colour),\n      parent(leaf.parent) { }\n\n    leaf_node_t& operator=(const leaf_node_t& leaf)\n    {\n      colour = leaf.colour;\n      parent = leaf.parent;\n      return *this;\n    }\n\n    leaf_node_t* operator->()\n    {\n      return this;\n    }\n\n    leaf_node_t& operator*()\n    {\n      return *this;\n    }\n\n    operator bool() const\n    {\n      return true;\n    }\n\n    bool operator==(N* node) const\n    {\n      return node == nullptr;\n    }\n\n    colour_t colour;\n    N* parent;\n  };\n\npublic:\n\n  class iterator\n  {\n  public:\n\n    iterator(N* node = 0) : node(node) { }\n\n    N* operator->()\n    {\n      return node;\n    }\n\n    N& operator*()\n    {\n      return *node;\n    }\n\n    const N* operator->() const\n    {\n      return node;\n    }\n\n    const N& operator*() const\n    {\n      return *node;\n    }\n\n    operator bool() const\n    {\n      return bool(node);\n    }\n\n    iterator& operator++()\n    {\n      if (!node) {\n        return *this;\n      }\n\n      if (node->right) {\n        node = node->right.get();\n\n        while (node->left) {\n          node = node->left.get();\n        }\n\n        return *this;\n      }\n\n      N* parent = node->parent;\n\n      while (parent && is_right(node)) {\n        node = parent;\n        parent = node->parent;\n      }\n\n      node = parent;\n      return *this;\n    }\n\n    bool operator!=(const iterator& itr)\n    {\n      return node != itr.node;\n    }\n\n  private:\n\n    N* node;\n  };\n\n  rbtree() : tree_size(0) { }\n\n  virtual ~rbtree() { }\n\n  void insert(const K& key, const V& value)\n  {\n    insert_into(key, value, tree_root);\n  }\n\n  void erase(const K& key)\n  {\n    std::unique_ptr<N>& node = find_in(key, tree_root);\n    erase_node(node);\n  }\n\n  void clear()\n  {\n    tree_root.reset();\n    tree_size = 0;\n  }\n\n  iterator find(const K& key)\n  {\n    const std::unique_ptr<N>& n = find_in(key, tree_root);\n    return iterator(n.get());\n  }\n\n  const iterator find(const K& key) const\n  {\n    const std::unique_ptr<N>& n = find_in(key, tree_root);\n    return iterator(n.get());\n  }\n\n  size_t size() const\n  {\n    return tree_size;\n  }\n\n  bool empty() const\n  {\n    return !tree_root;\n  }\n\n  iterator begin()\n  {\n    N* node = tree_root.get();\n\n    if (!node) {\n      return iterator();\n    }\n\n    while (node->left) {\n      node = node->left.get();\n    }\n\n    return iterator(node);\n  }\n\n  iterator end()\n  {\n    return iterator();\n  }\n\nprotected:\n\n  void insert_into(const K& key, const V& value, std::unique_ptr<N>& node,\n                   N* parent = nullptr)\n  {\n    if (!node) {\n      node = make_node(key, value);\n      node->parent = parent;\n      ++tree_size;\n      rb_insert_case1(node.get());\n      return;\n    }\n\n    if (key == node->key) {\n      return;\n    }\n\n    if (key < node->key) {\n      insert_into(key, value, node->left, node.get());\n    } else {\n      insert_into(key, value, node->right, node.get());\n    }\n  }\n\n  void erase_node(std::unique_ptr<N>& node)\n  {\n    if (!node) {\n      return;\n    }\n\n    if (has_two(node.get())) {\n      // in this case:\n      // 1. look for the in-order successor\n      // 2. replace the node with the in-order successor\n      // 3. erase the in-order successor\n      N* n = node.get();\n      std::unique_ptr<N>& successor = find_successor(node);\n      swap_successor(node, successor);\n\n      // we swapped the node with successor and the\n      // 'successor' unique pointer holds now the node\n      if (successor.get() == n) {\n        erase_node(successor);\n      }// otherwise the successor was the right child of node,\n      // hence node should be now the right child of 'node'\n      // unique pointer\n      else if (node->right.get() == n) {\n        erase_node(node->right);\n      }// there are no other cases so anything else is wrong\n      else {\n        throw std::logic_error(\"Bad rbtree swap.\");\n      }\n\n      return;\n    }\n\n    // node has at most one child\n    // in this case simply replace the node with the\n    // single child or null if there are no children\n    N* parent = node->parent;\n    std::unique_ptr<N>& child = node->left ? node->left : node->right;\n    colour_t old_colour = node->colour;\n\n    if (child) {\n      child->parent = node->parent;\n    }\n\n    node.reset(child.release());\n    --tree_size;\n\n    if (old_colour == BLACK) {\n      if (node && node->colour == RED) {\n        node->colour = BLACK;\n      } else {\n        // if we are here the node is null because a BLACK\n        // node that has at most one non-leaf child must\n        // have two null children (null children are BLACK)\n        if (node) {\n          throw rb_invariant_error();\n        }\n\n        rb_erase_case1(leaf_node_t(parent));\n      }\n    } else if (node)\n      // if the node was red it has to have two BLACK children\n      // and since at most one of those children is a non-leaf\n      // child actually both have to be leafs (null) in order\n      // to satisfy the red-black tree invariant\n    {\n      throw rb_invariant_error();\n    }\n  }\n\n  template<typename PTR> // make it a template so it works both for constant and mutable pointers\n  static PTR& find_in(const K& key, PTR& node)\n  {\n    if (!node) {\n      return null_node;\n    }\n\n    if (key == node->key) {\n      return node;\n    }\n\n    if (key < node->key) {\n      return find_in(key, node->left);\n    } else {\n      return find_in(key, node->right);\n    }\n  }\n\n  template<typename PTR> // make it a template so it works both for constant and mutable pointers\n  static PTR& find_min(PTR& node)\n  {\n    if (!node) {\n      return null_node;\n    }\n\n    if (node->left) {\n      return find_min(node->left);\n    }\n\n    return node;\n  }\n\n  template<typename PTR> // make it a template so it works both for constant and mutable pointers\n  static PTR& find_successor(PTR& node)\n  {\n    if (!node) {\n      return null_node;\n    }\n\n    return find_min(node->right);\n  }\n\n  static bool has_two(const N* node)\n  {\n    return node->left && node->right;\n  }\n\n  ////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n  static void replace(std::unique_ptr<N>& ptr, N* node)\n  {\n    ptr.release();\n    ptr.reset(node);\n  }\n\n  virtual void right_rotation(N* node)\n  {\n    if (!node) {\n      return;\n    }\n\n    N* parent = node->parent;\n    N* left_child = node->left.release();\n    bool is_left = (parent && parent->left.get() == node) ? true : false;\n    node->left.reset(left_child->right.release());\n\n    if (node->left) {\n      node->left->parent = node;\n    }\n\n    left_child->right.reset(node);\n\n    if (left_child->right) {\n      left_child->right->parent = left_child;\n    }\n\n    left_child->parent = parent;\n\n    if (!parent) {\n      replace(tree_root, left_child);\n    } else if (is_left) {\n      replace(parent->left, left_child);\n    } else {\n      replace(parent->right, left_child);\n    }\n  }\n\n  virtual void left_rotation(N* node)\n  {\n    if (!node) {\n      return;\n    }\n\n    N* parent = node->parent;\n    N* right_child = node->right.release();\n    bool is_left = (parent && parent->left.get() == node) ? true : false;\n    node->right.reset(right_child->left.release());\n\n    if (node->right) {\n      node->right->parent = node;\n    }\n\n    right_child->left.reset(node);\n\n    if (right_child->left) {\n      right_child->left->parent = right_child;\n    }\n\n    right_child->parent = parent;\n\n    if (!parent) {\n      replace(tree_root, right_child);\n    } else if (is_left) {\n      replace(parent->left, right_child);\n    } else {\n      replace(parent->right, right_child);\n    }\n  }\n\n  ////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n  N* get_grandparent(N* node)\n  {\n    if (!node || !node->parent) {\n      return nullptr;\n    }\n\n    return node->parent->parent;\n  }\n\n  N* get_uncle(N* node)\n  {\n    N* grandparent = get_grandparent(node);\n\n    if (!grandparent) {\n      return nullptr;\n    }\n\n    if (grandparent->left.get() == node->parent) {\n      return grandparent->right.get();\n    } else {\n      return grandparent->left.get();\n    }\n  }\n\n  void rb_insert_case1(N* node)\n  {\n    if (node->parent == nullptr) { // it is the root\n      node->colour = BLACK;\n    } else {\n      rb_insert_case2(node);\n    }\n  }\n\n  void rb_insert_case2(N* node)\n  {\n    if (node->parent->colour == BLACK) {\n      return; // the invariant is OK\n    } else {\n      rb_insert_case3(node);\n    }\n  }\n\n  void rb_insert_case3(N* node)\n  {\n    N* uncle = get_uncle(node);\n\n    if (uncle && uncle->colour == RED) {\n      node->parent->colour = BLACK;\n      uncle->colour = BLACK;\n      N* grandparent = get_grandparent(node);\n      grandparent->colour = RED;\n      rb_insert_case1(grandparent);\n    } else {\n      rb_insert_case4(node);\n    }\n  }\n\n  void rb_insert_case4(N* node)\n  {\n    N* grandparent = get_grandparent(node);\n\n    if ((node == node->parent->right.get()) &&\n        (node->parent == grandparent->left.get())) {\n      left_rotation(grandparent->left.get());\n      node = node->left.get();\n    } else if ((node == node->parent->left.get()) &&\n               (node->parent == grandparent->right.get())) {\n      right_rotation(grandparent->right.get());\n      node = node->right.get();\n    }\n\n    rb_insert_case5(node);\n  }\n\n  void rb_insert_case5(N* node)\n  {\n    N* grandparent = get_grandparent(node);\n    node->parent->colour = BLACK;\n    grandparent->colour = RED;\n\n    if (node == node->parent->left.get()) {\n      right_rotation(grandparent);\n    } else {\n      left_rotation(grandparent);\n    }\n  }\n\n  ////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n  template<typename NODE>\n  static bool is_left(NODE node)\n  {\n    return node == node->parent->left.get();\n  }\n\n  template<typename NODE>\n  static bool is_right(NODE node)\n  {\n    return node == node->parent->right.get();\n  }\n\n  template<typename NODE>\n  N* get_sibling(NODE node)\n  {\n    if (!node || !node->parent) {\n      return nullptr;\n    }\n\n    if (is_left(node)) {\n      return node->parent->right.get();\n    } else {\n      return node->parent->left.get();\n    }\n  }\n\n  template<typename NODE>\n  void rb_erase_case1(NODE node)\n  {\n    if (node->parent != nullptr) {\n      rb_erase_case2(node);\n    }\n  }\n\n  template<typename NODE>\n  void rb_erase_case2(NODE node)\n  {\n    N* sibling = get_sibling(node);\n\n    if (!sibling) {\n      throw rb_invariant_error();\n    }\n\n    if (sibling->colour == RED) {\n      node->parent->colour = RED;\n      sibling->colour = BLACK;\n\n      if (is_left(node)) {\n        left_rotation(node->parent);\n      } else {\n        right_rotation(node->parent);\n      }\n    }\n\n    rb_erase_case3(node);\n  }\n\n  template<typename NODE>\n  void rb_erase_case3(NODE node)\n  {\n    N* sibling = get_sibling(node);\n\n    if (!sibling) {\n      throw rb_invariant_error();\n    }\n\n    colour_t sibling_left_colour = sibling->left ? sibling->left->colour : BLACK;\n    colour_t sibling_right_colour = sibling->right ? sibling->right->colour : BLACK;\n\n    if (node->parent->colour == BLACK &&\n        sibling->colour == BLACK &&\n        sibling_left_colour == BLACK &&\n        sibling_right_colour == BLACK) {\n      sibling->colour = RED;\n      rb_erase_case1(node->parent);\n    } else {\n      rb_erase_case4(node);\n    }\n  }\n\n  template<typename NODE>\n  void rb_erase_case4(NODE node)\n  {\n    N* sibling = get_sibling(node);\n\n    if (!sibling) {\n      throw rb_invariant_error();\n    }\n\n    colour_t sibling_left_colour = sibling->left ? sibling->left->colour : BLACK;\n    colour_t sibling_right_colour = sibling->right ? sibling->right->colour : BLACK;\n\n    if (node->parent->colour == RED &&\n        sibling->colour == BLACK &&\n        sibling_left_colour == BLACK &&\n        sibling_right_colour == BLACK) {\n      sibling->colour = RED;\n      node->parent->colour = BLACK;\n    } else {\n      rb_erase_case5(node);\n    }\n  }\n\n  template<typename NODE>\n  void rb_erase_case5(NODE node)\n  {\n    N* sibling = get_sibling(node);\n\n    if (!sibling) {\n      throw rb_invariant_error();\n    }\n\n    colour_t sibling_left_colour = sibling->left ? sibling->left->colour : BLACK;\n    colour_t sibling_right_colour = sibling->right ? sibling->right->colour : BLACK;\n\n    if (sibling->colour == BLACK) {\n      if (is_left(node) &&\n          sibling_right_colour == BLACK &&\n          sibling_left_colour == RED) {\n        sibling->colour = RED;\n\n        if (sibling->left) {\n          sibling->left->colour = BLACK;\n        }\n\n        right_rotation(sibling);\n      } else if (is_right(node) &&\n                 sibling_left_colour == BLACK &&\n                 sibling_right_colour == RED) {\n        sibling->colour = RED;\n\n        if (sibling->right) {\n          sibling->right->colour = BLACK;\n        }\n\n        left_rotation(sibling);\n      }\n    }\n\n    rb_erase_case6(node);\n  }\n\n  template<typename NODE>\n  void rb_erase_case6(NODE node)\n  {\n    N* sibling = get_sibling(node);\n\n    if (!sibling) {\n      throw rb_invariant_error();\n    }\n\n    sibling->colour = node->parent->colour;\n    node->parent->colour = BLACK;\n\n    if (is_left(node)) {\n      if (sibling->right) {\n        sibling->right->colour = BLACK;\n      }\n\n      left_rotation(node->parent);\n    } else {\n      if (sibling->left) {\n        sibling->left->colour = BLACK;\n      }\n\n      right_rotation(node->parent);\n    }\n  }\n\n  std::unique_ptr<N> tree_root;\n  size_t tree_size;\n};\n\ntemplate<typename K, typename V, typename N>\nstd::unique_ptr<N> rbtree<K, V, N>::null_node;\n#endif /* RBTREE_HH_ */\n"
  },
  {
    "path": "fusex/data/xrdclproxy.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file xrdclproxy.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief XrdCl proxy class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"xrdclproxy.hh\"\n#include \"fusex/misc/fusexrdlogin.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Path.hh\"\n#include <XrdCl/XrdClXRootDResponses.hh>\n\nusing namespace XrdCl;\n\n\nssize_t XrdCl::Proxy::sChunkTimeout = 300;\n\nXrdCl::BufferManager XrdCl::Proxy::sWrBufferManager;\nXrdCl::BufferManager XrdCl::Proxy::sRaBufferManager;\n\nstd::atomic<int> XrdCl::Proxy::sProxy;\n\nstd::mutex XrdCl::Proxy::WriteAsyncHandler::gBuffReferenceMutex;\nstd::map<std::string, uint64_t>\nXrdCl::Proxy::WriteAsyncHandler::gBufferReference;\n\nstd::mutex XrdCl::Proxy::ReadAsyncHandler::gExpiredChunksMutex;\nstd::vector<XrdCl::Proxy::read_handler>\nXrdCl::Proxy::ReadAsyncHandler::gExpiredChunks;\n\n//------------------------------------------------------------------------------\n// Method to crate a new XrdCl::Proxy object with the option to request the\n// creation of a new TCP connection for the underlying XrdC::File object.\n//------------------------------------------------------------------------------\nXrdCl::shared_proxy\nXrdCl::Proxy::Factory(const shared_proxy proxy, bool reconnect)\n{\n  fuse_id fid;\n  std::string ruser;\n  UserCredentials uc;\n  uint64_t connId = 0;\n\n  // If a previous proxy is supplied in the arguments:\n  //\n  // If the current login url proposed by the processCache has already changed\n  // from that which the previous proxyed used, we use the newer login url.\n  // Otherwise if reconnect is true we request the processCache to change login\n  // url and use the new one.\n  if (proxy) {\n    fid = proxy->fuseid();\n    ruser = proxy->mReconUsername;\n    uc = proxy->mUc;\n    connId = proxy->mConnId;\n    if (ruser.empty()) {\n      XrdCl::URL url(proxy->url());\n      ruser = url.GetUserName();\n    }\n  }\n\n  if (fid.pid != 0) {\n    ProcessSnapshot snapshot = fusexrdlogin::processCache->retrieve(fid.pid, fid.uid, fid.gid, false);\n    bool skip = false;\n    if (snapshot) {\n      if (snapshot->getBoundIdentity()->getLogin().getStringID() != ruser) {\n        skip = true;\n      }\n      ruser = snapshot->getBoundIdentity()->getLogin().getStringID();\n      connId = snapshot->getBoundIdentity()->getLogin().getConnectionID();\n      uc = snapshot->getBoundIdentity()->getCreds()->getUC();\n    }\n    if (reconnect && !skip) {\n      snapshot = fusexrdlogin::processCache->retrieve(fid.pid, fid.uid, fid.gid, true);\n      if (snapshot) {\n        ruser = snapshot->getBoundIdentity()->getLogin().getStringID();\n        connId = snapshot->getBoundIdentity()->getLogin().getConnectionID();\n        uc = snapshot->getBoundIdentity()->getCreds()->getUC();\n      } else {\n        (void) fusexrdlogin::processCache->remove(fid.uid, fid.gid, uc, connId);\n      }\n    }\n  }\n\n  shared_proxy sp = std::make_shared<Proxy>();\n  if (proxy) {\n    sp->mIno = proxy->id();\n    sp->mReq = proxy->req();\n    char lid[64];\n    snprintf(lid, sizeof(lid), \"logid:ino:%016lx\", sp->mIno);\n    sp->SetLogId(lid);\n  }\n  sp->mId = fid;\n  sp->mReconUsername = ruser;\n  sp->mUc = uc;\n  sp->mConnId = connId;\n  return sp;\n}\n\nXrdCl::Proxy::ProxyStat XrdCl::Proxy::ProxyStatHandle::sProxyStats;\n\nstd::shared_ptr<XrdCl::Proxy::ProxyStatHandle>\nXrdCl::Proxy::ProxyStatHandle::Get()\n{\n  return std::make_shared<XrdCl::Proxy::ProxyStatHandle>();\n}\n\n/* -------------------------------------------------------------------------- */\nXRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::Write(uint64_t offset,\n                    uint32_t size,\n                    const void* buffer,\n                    ResponseHandler* handler,\n                    uint16_t timeout)\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"offset=%lu size=%u\", offset, size);\n  XRootDStatus status = WaitOpen();\n\n  if (!status.IsOK()) {\n    return status;\n  }\n\n  return File::Write(offset, size, buffer, handler, timeout);\n}\n\n/* -------------------------------------------------------------------------- */\nXRootDStatus\nXrdCl::Proxy::Read(XrdCl::shared_proxy proxy,\n                   uint64_t offset,\n                   uint32_t size,\n                   void* buffer,\n                   uint32_t& bytesRead,\n                   uint16_t timeout)\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"offset=%lu size=%u\", offset, size);\n  XRootDStatus status = WaitOpen();\n  bytesRead = 0;\n\n  if (!status.IsOK()) {\n    return status;\n  }\n\n  eos_debug(\"----: read: offset=%lu size=%u\", offset, size);\n  int readahead_window_hit = 0;\n  uint64_t current_offset = offset;\n  uint32_t current_size = size;\n  bool isEOF = false;\n  bool request_next = true;\n  std::set<uint64_t> delete_chunk;\n  std::set<uint64_t> expired_chunk;\n  void* pbuffer = buffer;\n\n  if ((off_t)offset == (off_t)mPosition) {\n    mSeqDistance += size;\n  } else {\n    if (!XReadAheadDisabled) {\n      // Offset if unsigned so abs might not work properly in case of underflow\n      off_t seek_distance = (offset >= (uint64_t)mPosition ? offset - mPosition :\n                             mPosition -\n                             offset);\n      double sparse_ratio = 1.0 * mSeqDistance / (seek_distance + mSeqDistance);\n\n      if (EOS_LOGS_DEBUG) {\n        eos_debug(\"sparse ratio:= %.02f seq-distance=%lu seek-distance=%lu\",\n                  sparse_ratio, (off_t)mSeqDistance, (off_t)seek_distance);\n      }\n\n      mSeqDistance = size;\n\n      if (sparse_ratio && (sparse_ratio < XReadAheadSparseRatio)) {\n        eos_notice(\"sparse ratio:= %.02f seq-distance=%lu seek-distance=%lu - disabling readahead permanently url:'%s'\",\n                   sparse_ratio, (off_t)mSeqDistance, (off_t)seek_distance,\n                   sparse_ratio, mUrl.c_str());\n        XReadAheadDisabled = true;\n      }\n    }\n  }\n\n  if ((XReadAheadStrategy != NONE)) {\n    ReadCondVar().Lock();\n    XReadAheadBlocksIs = 0;\n\n    if (ChunkRMap().size()) {\n      auto last_chunk_before_match = ChunkRMap().begin();\n\n      for (auto it = ChunkRMap().begin(); it != ChunkRMap().end(); ++it) {\n        // extract all possible data from the read-ahead map\n        off_t match_offset;\n        uint32_t match_size;\n        XrdSysCondVarHelper lLock(it->second->ReadCondVar());\n\n        if (EOS_LOGS_DEBUG) {\n          eos_debug(\"----: eval offset=%lu chunk-offset=%lu rah-position=%lu\", offset,\n                    it->second->offset(), mReadAheadPosition);\n        }\n\n        if (it->second->matches(current_offset, current_size, match_offset,\n                                match_size)) {\n          readahead_window_hit++;\n          size_t cnt = 0;\n\n          while (!it->second->done()) {\n            it->second->ReadCondVar().WaitMS(25);\n            cnt++;\n\n            if (!(cnt % 2400)) {\n              // every 60 seconds ...\n              if (it->second->expired()) {\n                eos_crit(\"read-ahead request expired after %u cycles - now: %lu ctime: %lu\",\n                         cnt, time(NULL), it->second->creationtime());\n                continue;\n              }\n            }\n          }\n\n          status = it->second->Status();\n\n          if (it->second->Status().IsOK()) {\n            // the match result can change after the read actually returned\n            if (!it->second->matches(current_offset, current_size, match_offset,\n                                     match_size)) {\n              continue;\n            }\n\n            if (EOS_LOGS_DEBUG) {\n              eos_debug(\"----: prefetched offset=%lu m-offset=%lu current-size=%u m-size=%u dim=%ld\",\n                        current_offset, match_offset, current_size, match_size,\n                        (char*) buffer - (char*) pbuffer);\n              eos_debug(\"----: out-buffer=%lx in-buffer=%lx in-buffer-size=%lu\",\n                        (long unsigned int) buffer, (long unsigned int) it->second->buffer(),\n                        it->second->vbuffer().size());\n            }\n\n            // just copy what we have\n            memcpy(buffer, it->second->buffer() + match_offset - it->second->offset(),\n                   match_size);\n            bytesRead += match_size;\n            mTotalReadAheadHitBytes += match_size;\n            buffer = (char*) buffer + match_size;\n            current_offset = match_offset + match_size;\n            current_size -= match_size;\n            isEOF = it->second->eof();\n\n            if (isEOF) {\n              eos_info(\"got EOF in matching chunk %lu (%lu)\", it->second->offset(),\n                       mPosition);\n              request_next = false;\n              XReadAheadNom = 0;\n              XReadAheadBlocksNom = XReadAheadBlocksMin;\n              mReadAheadPosition = 0;\n              XReadAheadReenableHits = 0;\n              break;\n            }\n          }\n        } else {\n          if (!readahead_window_hit) {\n            last_chunk_before_match = it;\n          } else {\n            XReadAheadBlocksIs++;\n          }\n\n          isEOF = it->second->eof();\n\n          if (isEOF) {\n            eos_info(\"got EOF in matching chunk %lu (%lu)\", it->second->offset(),\n                     mPosition);\n            request_next = false;\n            XReadAheadNom = 0;\n            XReadAheadBlocksNom = XReadAheadBlocksMin;\n            mReadAheadPosition = 0;\n            XReadAheadReenableHits = 0;\n          }\n        }\n      }\n\n      if (readahead_window_hit) {\n        // check if we can remove previous prefetched chunks, we keep one block before the current read position\n        for (auto it = ChunkRMap().begin(); it != last_chunk_before_match; ++it) {\n          XrdSysCondVarHelper lLock(it->second->ReadCondVar());\n\n          if (it->second->expired()) {\n            expired_chunk.insert(it->first);\n          } else {\n            if (it->second->done()) {\n              if (EOS_LOGS_DEBUG) {\n                eos_debug(\"----: dropping chunk offset=%lu chunk-offset=%lu\", offset,\n                          it->second->offset());\n              }\n\n              delete_chunk.insert(it->first);\n            }\n          }\n        }\n      } else {\n        // clean-up all chunks in the read-ahead map\n        for (auto it = ChunkRMap().begin(); it != ChunkRMap().end(); ++it) {\n          XrdSysCondVarHelper lLock(it->second->ReadCondVar());\n          size_t cnt = 0;\n\n          while (!it->second->done()) {\n            it->second->ReadCondVar().WaitMS(25);\n            cnt++;\n\n            if (!(cnt % 2400)) {\n              // every 60 seconds ...\n              if (it->second->expired()) {\n                eos_crit(\"read-ahead request expired after %u cycles - now: %lu ctime: %lu\",\n                         cnt, time(NULL), it->second->creationtime());\n                break;\n              }\n            }\n          }\n\n          if (it->second->expired()) {\n            expired_chunk.insert(it->first);\n          } else {\n            delete_chunk.insert(it->first);\n          }\n        }\n      }\n\n      for (auto it = delete_chunk.begin(); it != delete_chunk.end(); ++it) {\n        ChunkRMap().erase(*it);\n      }\n\n      for (auto it = expired_chunk.begin(); it != expired_chunk.end(); ++it) {\n        auto chunk = ChunkRMap()[*it];\n        {\n          // put on a garbage stack\n          std::lock_guard<std::mutex> lock(\n            XrdCl::Proxy::ReadAsyncHandler::gExpiredChunksMutex);\n          XrdCl::Proxy::ReadAsyncHandler::gExpiredChunks.push_back(chunk);\n        }\n        {\n          // delete form the read-ahead map\n          ChunkRMap().erase(*it);\n        }\n      }\n    } else {\n      if ((off_t) offset == mPosition) {\n        XReadAheadReenableHits++;\n\n        if ((!XReadAheadDisabled) && (XReadAheadReenableHits > 2)) {\n          eos_info(\"re-enabling read-ahead at %lu (%lu)\", offset, mPosition);\n          // re-enable read-ahead if sequential reading occurs\n          request_next = true;\n\n          if (!mReadAheadPosition) {\n            set_readahead_position(offset + size);\n\n            // tune the read-ahead size with the read-pattern\n            if (size > XReadAheadNom) {\n              XReadAheadNom = size;\n            }\n\n            if (XReadAheadNom > XReadAheadMax) {\n              XReadAheadNom = XReadAheadMax;\n            }\n          }\n        }\n      } else {\n        XReadAheadReenableHits = 0;\n        eos_info(\"disabling read-ahead at %lu (%lu)\", offset, mPosition);\n        request_next = false;\n        XReadAheadNom = 0;\n        ;\n        XReadAheadBlocksNom = XReadAheadBlocksMin;\n        set_readahead_position(0);\n      }\n    }\n\n    if (request_next) {\n      // dynamic window scaling\n      if (readahead_window_hit) {\n        if (XReadAheadStrategy == DYNAMIC) {\n          // increase the read-ahead window\n          XReadAheadNom *= 2;\n\n          if (XReadAheadNom > XReadAheadMax) {\n            XReadAheadNom = XReadAheadMax;\n          }\n\n          // increase the number of pre-fetched blocks\n          XReadAheadBlocksNom *= 2;\n\n          if (XReadAheadBlocksNom > XReadAheadBlocksMax) {\n            XReadAheadBlocksNom = XReadAheadBlocksMax;\n          }\n        }\n      }\n\n      if (EOS_LOGS_DEBUG) {\n        eos_debug(\"hit:%d chunks:%d pre-blocks:%d to-fetch:%d\", readahead_window_hit,\n                  ChunkRMap().size(), XReadAheadBlocksNom,\n                  XReadAheadBlocksNom - XReadAheadBlocksIs);\n      }\n\n      // pre-fetch missing read-ahead blocks, if there is a window !=0\n      size_t blocks_to_fetch = XReadAheadNom ?\n                               ((XReadAheadBlocksNom > XReadAheadBlocksIs) ? ((XReadAheadBlocksNom -\n                                   XReadAheadBlocksIs)) : 0)\n                               : 0;\n\n      for (size_t n_fetch = 0; n_fetch < blocks_to_fetch; n_fetch++) {\n        if (EOS_LOGS_DEBUG)\n          eos_debug(\"----: pre-fetch window=%lu pf-offset=%lu block(%d/%d)\",\n                    XReadAheadNom,\n                    (unsigned long) mReadAheadPosition,\n                    n_fetch, blocks_to_fetch\n                   );\n\n        if (mReadAheadPosition > get_readahead_maximum_position()) {\n          eos_debug(\"----: pre-fetch skipped max-readahead-position=%lu\",\n                    get_readahead_maximum_position());\n        }\n\n        if (!ChunkRMap().count(mReadAheadPosition)) {\n          ReadCondVar().UnLock();\n          XrdCl::Proxy::read_handler rahread = ReadAsyncPrepare(proxy, mReadAheadPosition,\n                                               XReadAheadNom, false);\n\n          if (!rahread) {\n            // already an entry in ChunkRMap for our offset by the\n            // time we tried to add it.\n            ReadCondVar().Lock();\n            continue;\n          }\n\n          if (!rahread->valid()) {\n            ReadCondVar().Lock();\n            // no buffer available\n            break;\n          }\n\n          XRootDStatus rstatus = PreReadAsync(mReadAheadPosition, XReadAheadNom,\n                                              rahread, timeout);\n\n          if (rstatus.IsOK()) {\n            mReadAheadPosition += XReadAheadNom;\n            mTotalReadAheadBytes += XReadAheadNom;\n          }\n\n          ReadCondVar().Lock();\n        }\n      }\n\n      ReadCondVar().UnLock();\n    } else {\n      ReadCondVar().UnLock();\n    }\n  }\n\n  if (current_size) {\n    // do a synchronous read for missing pieces\n    uint32_t rbytes_read = 0;\n    status = File::Read(current_offset,\n                        current_size,\n                        buffer, rbytes_read, timeout);\n\n    if (status.IsOK()) {\n      if (rbytes_read) {\n        if (EOS_LOGS_DEBUG) {\n          eos_debug(\"----: postfetched offset=%lu size=%u rbytes=%d\", current_offset,\n                    current_size, rbytes_read);\n        }\n      }\n\n      bytesRead += rbytes_read;\n    }\n  }\n\n  set_readstate(&status);\n\n  if (status.IsOK()) {\n    mPosition = offset + size;\n    mTotalBytes += bytesRead;\n  }\n\n  return status;\n}\n\n/* -------------------------------------------------------------------------- */\nXRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::OpenAsync(XrdCl::shared_proxy proxy,\n                        const std::string& url,\n                        OpenFlags::Flags flags,\n                        Access::Mode mode,\n                        uint16_t timeout)\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"url=%s flags=%x mode=%x\", url.c_str(), (int) flags, (int) mode);\n  XrdSysCondVarHelper lLock(OpenCondVar());\n  int in_state = state();\n  mUrl = url;\n  if (!mReconUsername.empty()) {\n    XrdCl::URL tmpurl(url);\n    tmpurl.SetUserName(mReconUsername);\n    mUrl = tmpurl.GetURL();\n  }\n  mFlags = flags;\n  mMode = mode;\n  mTimeout = timeout;\n\n  if ((state() == OPENING) ||\n      (state() == WAITWRITE)) {\n    XRootDStatus status(XrdCl::stError,\n                        suAlreadyDone,\n                        XrdCl::errInProgress,\n                        \"in progress\"\n                       );\n    return status;\n  }\n\n  if (state() == OPENED) {\n    XRootDStatus status(XrdCl::stOK,\n                        0,\n                        0,\n                        \"opened\"\n                       );\n    return status;\n  }\n\n  if (state() == FAILED) {\n    eos_err(\"url=%s flags=%x mode=%x state=failed\", mUrl.c_str(), (int) flags,\n            (int) mode);\n    return XOpenState;\n  }\n\n  // Disable recovery on read and write\n#if kXR_PROTOCOLVERSION == 0x00000297\n  ((XrdCl::File*)(this))->EnableReadRecovery(false);\n  ((XrdCl::File*)(this))->EnableWriteRecovery(false);\n#else\n  SetProperty(\"ReadRecovery\", \"false\");\n  SetProperty(\"WriteRecovery\", \"false\");\n#endif\n\n  if (EOS_LOGS_DEBUG) {\n    eos_debug(\"this=%x url=%s in-state %d state %d\\n\", this, mUrl.c_str(), in_state,\n              state());\n  }\n\n  XrdCl::XRootDStatus status;\n  status = fuzzing().OpenAsyncSubmitFuzz();\n\n  if (!status.IsOK()) {\n  } else {\n    XOpenAsyncHandler.SetProxy(proxy);\n    status = Open(mUrl.c_str(),\n                  flags,\n                  mode,\n                  &XOpenAsyncHandler,\n                  timeout);\n  }\n\n  if (status.IsOK()) {\n    // the status is cleared here, e.g. in case a closed proxy\n    // with a previous error is reopened ok.\n    set_state(OPENING, &status);\n  } else {\n    eos_err(\"url=%s flags=%x mode=%x state=failed errmsg=%s\", mUrl.c_str(),\n            (int) flags, (int) mode, status.ToString().c_str());\n    XOpenAsyncHandler.SetProxy(0);\n    set_state(FAILED, &status);\n  }\n\n  return XOpenState;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::OpenAsyncHandler::HandleResponseWithHosts(\n  XrdCl::XRootDStatus* status,\n  XrdCl::AnyObject* response,\n  XrdCl::HostList* hostList)\n/* -------------------------------------------------------------------------- */\n{\n  eos_static_debug(\"\");\n  {\n    XrdSysCondVarHelper openLock(proxy()->OpenCondVar());\n    XRootDStatus fuzzingstatus = proxy()->fuzzing().OpenAsyncResponseFuzz();\n\n    if (!fuzzingstatus.IsOK()) {\n      eos_static_debug(\"fuzzing open response\");\n      *status = fuzzingstatus;\n    }\n\n    if (status->IsOK()) {\n      proxy()->set_state(OPENED);\n      proxy()->set_lasturl();\n      openLock.UnLock();\n      XrdSysCondVarHelper writeLock(proxy()->WriteCondVar());\n\n      while (proxy()->WriteQueue().size()) {\n        write_handler handler = proxy()->WriteQueue().front();\n        XRootDStatus status;\n        eos_static_debug(\"sending scheduled write request: off=%ld size=%lu timeout=%hu\",\n                         handler->offset(),\n                         handler->vbuffer().size(),\n                         handler->timeout());\n        writeLock.UnLock();\n        status = proxy()->WriteAsync((uint64_t) handler->offset(),\n                                     (uint32_t)(handler->vbuffer().size()),\n                                     0,\n                                     handler,\n                                     handler->timeout()\n                                    );\n        writeLock.Lock(&proxy()->WriteCondVar());\n        proxy()->WriteQueue().pop_front();\n\n        if (!status.IsOK()) {\n          proxy()->set_writestate(&status);\n        }\n      }\n\n      writeLock.UnLock();\n      openLock.Lock(&proxy()->OpenCondVar());\n    } else {\n      eos_static_err(\"state=failed async open returned errmsg=%s\",\n                     status->ToString().c_str());\n      XrdSysCondVarHelper writeLock(proxy()->WriteCondVar());\n\n      if (proxy()->WriteQueue().size()) {\n        // if an open failes we clean the write queue\n        proxy()->CleanWriteQueue();\n      }\n\n      writeLock.UnLock();\n      proxy()->set_state(FAILED, status);\n    }\n\n    proxy()->OpenCondVar().Signal();\n    delete hostList;\n    delete status;\n\n    if (response) {\n      delete response;\n    }\n  }\n  mProxy = 0;\n}\n\n/* -------------------------------------------------------------------------- */\nXRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::ReOpenAsync(XrdCl::shared_proxy proxy)\n/* -------------------------------------------------------------------------- */\n{\n  if (mUrl.length()) {\n    set_state_TS(CLOSED);\n    return OpenAsync(proxy, mUrl, mFlags, mMode, mTimeout);\n  } else {\n    XRootDStatus status(XrdCl::stError,\n                        suRetry,\n                        XrdCl::errUninitialized,\n                        \"never opened before\"\n                       );\n    eos_err(\"state=failed reopenasync errmsg=%s\", status.ToString().c_str());\n    set_state_TS(FAILED, &status);\n    return status;\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nXRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::CloseAsync(XrdCl::shared_proxy proxy, uint16_t timeout)\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"\");\n\n  // don't close files attached by several clients\n  if (mAttached > 1) {\n    eos_debug(\"still attached\");\n    return XRootDStatus();\n  }\n\n  WaitOpen();\n  DropReadAhead();\n  XrdSysCondVarHelper lLock(OpenCondVar());\n\n  // only an opened file requires a close, otherwise we return the last known state\n  if ((state() == OPENED) ||\n      (state() == WAITWRITE)) {\n    XCloseAsyncHandler.SetProxy(proxy);\n    XrdCl::XRootDStatus status = XrdCl::File::Close(&XCloseAsyncHandler,\n                                 timeout);\n\n    if (!status.IsOK()) {\n      eos_err(\"state=failed closeasync errms=%s\", status.ToString().c_str());\n      set_state(FAILED, &status);\n      XCloseAsyncHandler.SetProxy(0);\n    } else {\n      set_state(CLOSING, &status);\n    }\n  } else {\n    eos_crit(\"%x closing an unopened file state=%d url=%s\\n\", this, state(),\n             mUrl.c_str());\n  }\n\n  return XOpenState;\n}\n\n/* -------------------------------------------------------------------------- */\nXRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::ScheduleCloseAsync(XrdCl::shared_proxy proxy, uint16_t timeout)\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"\");\n\n  if (mAttached > 1) {\n    eos_debug(\"still attached\");\n    return XRootDStatus();\n  }\n\n  {\n    bool no_chunks_left = true;\n\n    if ((state() == OPENING) ||\n        (state() == OPENED)) {\n      {\n        DropReadAhead();\n        XrdSysCondVarHelper lLock(WriteCondVar());\n\n        // either we have submitted chunks\n        if (ChunkMap().size()) {\n          no_chunks_left = false;\n        }\n\n        // or we have chunks still to be submitted\n        if (WriteQueue().size()) {\n          no_chunks_left = false;\n        }\n\n        if (!no_chunks_left) {\n          // indicate to close this file when the last write-callback arrived\n          eos_debug(\"indicating close-after-write\");\n          XCloseAfterWrite = true;\n          XCloseAfterWriteTimeout = timeout;\n        }\n      }\n\n      if (no_chunks_left) {\n        return CloseAsync(proxy, timeout);\n      } else {\n        return XOpenState;\n      }\n    }\n  }\n\n  XRootDStatus status(XrdCl::stError,\n                      suAlreadyDone,\n                      XrdCl::errInvalidOp,\n                      \"file not open\"\n                     );\n  return status;\n}\n\n/* -------------------------------------------------------------------------- */\nXRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::Close(uint16_t timeout)\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"\");\n\n  // don't close files attached by several clients\n  if (mAttached > 1) {\n    return XRootDStatus();\n  }\n\n  WaitOpen();\n\n  if (IsOpen()) {\n    Collect();\n  }\n\n  DropReadAhead();\n  XrdSysCondVarHelper lLock(OpenCondVar());\n  XrdCl::XRootDStatus status = XrdCl::File::Close(timeout);\n  set_state(CLOSED, &status);\n  return status;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::CloseAsyncHandler::HandleResponse(XrdCl::XRootDStatus* status,\n    XrdCl::AnyObject* response)\n/* -------------------------------------------------------------------------- */\n{\n  eos_static_debug(\"\");\n  {\n    XrdSysCondVarHelper lLock(mProxy->OpenCondVar());\n\n    if (!status->IsOK()) {\n      if (status->code == XrdCl::errOperationExpired) {\n        XrdCl::shared_proxy newproxy = XrdCl::Proxy::Factory(mProxy, true);\n      }\n      // if the open failed before, we leave the open failed state here\n      if (!mProxy->isDeleted()) {\n        if (mProxy->state() != XrdCl::Proxy::FAILED) {\n          eos_static_crit(\"%x current status = %d - setting CLOSEFAILED - msg=%s url=%s\\n\",\n                          mProxy.get(), mProxy->state(), status->ToString().c_str(),\n                          mProxy->url().c_str());\n          mProxy->set_state(XrdCl::Proxy::CLOSEFAILED, status);\n        }\n      } else {\n        eos_static_info(\"%x current status = %d - silencing CLOSEFAILED - msg=%s url=%s\\n\",\n                        mProxy.get(), mProxy->state(), status->ToString().c_str(),\n                        mProxy->url().c_str());\n        // an unlinked file can have a close failure response\n        XRootDStatus okstatus;\n        mProxy->set_state(XrdCl::Proxy::CLOSED, &okstatus);\n      }\n    } else {\n      mProxy->set_state(XrdCl::Proxy::CLOSED, status);\n    }\n\n    mProxy->OpenCondVar().Signal();\n    delete response;\n    delete status;\n  }\n  mProxy = 0;\n}\n\n/* -------------------------------------------------------------------------- */\nXRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::WaitClose()\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"\");\n\n  if (IsOpen()) {\n    Collect();\n  }\n\n  XrdSysCondVarHelper lLock(OpenCondVar());\n\n  while (state() == CLOSING) {\n    OpenCondVar().WaitMS(25);\n  }\n\n  return XOpenState;\n}\n\n/* -------------------------------------------------------------------------- */\nXRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::WaitOpen()\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"\");\n  XrdSysCondVarHelper lLock(OpenCondVar());\n\n  while (state() == OPENING) {\n    OpenCondVar().WaitMS(25);\n  }\n\n  return XOpenState;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::WaitOpen(fuse_req_t req)\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"\");\n  XrdSysCondVarHelper lLock(OpenCondVar());\n\n  while (state() == OPENING) {\n    if (req && fuse_req_interrupted(req)) {\n      return EINTR;\n    }\n\n    OpenCondVar().WaitMS(25);\n  }\n\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nbool\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::IsOpening()\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysCondVarHelper lLock(OpenCondVar());\n  eos_debug(\"state=%d\", state());\n  return (state() == OPENING) ? true : false;\n}\n\n/* -------------------------------------------------------------------------- */\nbool\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::IsClosing()\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysCondVarHelper lLock(OpenCondVar());\n  eos_debug(\"state=%d\", state());\n  return (state() == CLOSING) ? true : false;\n}\n\n/* -------------------------------------------------------------------------- */\nbool\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::IsOpen()\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysCondVarHelper lLock(OpenCondVar());\n  eos_debug(\"state=%d\", state());\n  return (state() == OPENED) ? true : false;\n}\n\n/* -------------------------------------------------------------------------- */\nbool\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::IsClosed()\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysCondVarHelper lLock(OpenCondVar());\n  eos_debug(\"state=%d\", state());\n  return ((state() == CLOSED) || (state() == CLOSEFAILED) ||\n          (state() == FAILED)) ? true : false;\n}\n\nbool\n\nXrdCl::Proxy::IsWaitWrite()\n{\n  XrdSysCondVarHelper lLock(OpenCondVar());\n  eos_debug(\"state=%d\", state());\n  return (state() == WAITWRITE) ? true : false;\n}\n\nbool\nXrdCl::Proxy::HadFailures(std::string& message)\n{\n  bool ok = true;\n  XrdSysCondVarHelper lLock(OpenCondVar());\n\n  if (state() == CLOSEFAILED) {\n    message = \"file close failed\";\n    ok = false;\n  }\n\n  if (state() == FAILED) {\n    message = \"file open failed\";\n    ok = false;\n  }\n\n  if (!write_state().IsOK()) {\n    message = \"file writing failed\";\n    ok = false;\n  }\n\n  eos_debug(\"state=%d had-failures=%d\", state(), !ok);\n  return !ok;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::WriteAsyncHandler::HandleResponse(XrdCl::XRootDStatus* status,\n    XrdCl::AnyObject* response)\n/* -------------------------------------------------------------------------- */\n{\n  eos_static_debug(\"ino=%llx\", mProxy->id());\n  bool no_chunks_left = true;\n  // for correct destruct order 'myself' must be declared before anything\n  // that uses member variables\n  write_handler myself;\n  shared_proxy proxy;\n  {\n    std::lock_guard<std::mutex> lock(mDisableProxyMutex);\n    proxy = mProxy;\n    myself = mDisableKeepalive;\n    mDisableKeepalive = 0;\n  }\n  if (!proxy) {\n    delete response;\n    delete status;\n    return;\n  }\n\n  {\n    XrdSysCondVarHelper lLock(proxy->WriteCondVar());\n    {\n      if (!status->IsOK()) {\n        proxy->set_writestate(status);\n        eos_static_crit(\"write error '%s'\", status->ToString().c_str());\n      }\n\n      proxy->WriteCondVar().Signal();\n      delete response;\n      delete status;\n\n      myself = proxy->ChunkMap()[(int64_t)this];\n      proxy->ChunkMap().erase((uint64_t)this);\n\n    }\n  }\n\n  std::lock_guard<std::mutex> lock(mDisableProxyMutex);\n  if (!mProxy) {\n    myself = mDisableKeepalive;\n    mDisableKeepalive = 0;\n    return;\n  }\n\n  if (no_chunks_left) {\n    if (mProxy->close_after_write()) {\n      eos_static_debug(\"sending close-after-write\");\n      // send an asynchronous close now\n      XrdCl::XRootDStatus status = mProxy->CloseAsync(mProxy,\n                                   mProxy->close_after_write_timeout());\n    }\n  }\n  mProxy = 0;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::WriteAsyncHandler::DumpReferences(std::string& out)\n{\n  std::lock_guard<std::mutex> lock(gBuffReferenceMutex);\n\n  for (auto it = gBufferReference.begin(); it != gBufferReference.end(); ++it) {\n    out += \"ref:\";\n    out += it->first;\n    out += \" := \";\n    out += std::to_string(it->second);\n    out += \"\\n\";\n  }\n\n  return;\n}\n\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::write_handler\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::WriteAsyncPrepare(XrdCl::shared_proxy proxy, uint32_t size,\n                                uint64_t offset,\n                                uint16_t timeout)\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"\");\n  write_handler dst = std::make_shared<WriteAsyncHandler>(proxy, size, offset,\n                      timeout);\n  XrdSysCondVarHelper lLock(WriteCondVar());\n  ChunkMap()[(uint64_t) dst.get()] = dst;\n  return dst;\n}\n\n/* -------------------------------------------------------------------------- */\nXRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::WriteAsync(uint64_t offset,\n                         uint32_t size,\n                         const void* buffer,\n                         XrdCl::Proxy::write_handler handler,\n                         uint16_t timeout)\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"\");\n\n  // a buffer indicates, the handler buffer is already filled\n  if (buffer) {\n    handler->copy(buffer, size);\n  }\n\n  XRootDStatus status = Write(static_cast<uint64_t>(offset),\n                              static_cast<uint32_t>(size),\n                              handler->buffer(), handler.get(), timeout);\n\n  if (!status.IsOK()) {\n    // remove failing requests\n    XrdSysCondVarHelper lLock(WriteCondVar());\n    ChunkMap().erase((uint64_t) handler.get());\n  }\n\n  return status;\n}\n\n/* -------------------------------------------------------------------------- */\nXRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::ScheduleWriteAsync(\n  const void* buffer,\n  write_handler handler\n)\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"\");\n\n  if (buffer) {\n    handler->copy(buffer, handler->vbuffer().size());\n  }\n\n  XrdSysCondVarHelper openLock(OpenCondVar());\n\n  if (state() == OPENED) {\n    openLock.UnLock();\n    eos_debug(\"direct\");\n    inc_write_queue_direct_submissions();\n    // we can send off the write request\n    return WriteAsync(handler->offset(),\n                      (size_t) handler->vbuffer().size(),\n                      0,\n                      handler,\n                      handler->timeout());\n  }\n\n  if (state() == OPENING) {\n    inc_write_queue_scheduled_submissions();\n    eos_debug(\"scheduled\");\n    // we add this write to the list to be submitted when the open call back arrives\n    XrdSysCondVarHelper lLock(WriteCondVar());\n    WriteQueue().push_back(handler);\n    // we can only say status OK in that case\n    XRootDStatus status(XrdCl::stOK,\n                        0,\n                        XrdCl::errInProgress,\n                        \"in progress\"\n                       );\n    return status;\n  } else {\n    // remove requests with failed open\n    XrdSysCondVarHelper lLock(WriteCondVar());\n    ChunkMap().erase((uint64_t) handler.get());\n  }\n\n  return XOpenState;\n}\n\n/* -------------------------------------------------------------------------- */\nXRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::WaitWrite()\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"\");\n  WaitOpen();\n\n  if (state() == WAITWRITE) {\n    XrdSysCondVarHelper openLock(OpenCondVar());\n    return XOpenState;\n  }\n\n  // check if the open failed\n  if (state() != OPENED) {\n    XrdSysCondVarHelper openLock(OpenCondVar());\n    return XOpenState;\n  }\n\n  {\n    time_t wait_start = time(NULL);\n    XrdSysCondVarHelper lLock(WriteCondVar());\n\n    while (ChunkMap().size()) {\n      eos_debug(\"     [..] map-size=%lu\", ChunkMap().size());\n      WriteCondVar().WaitMS(1000);\n      time_t wait_stop = time(NULL);\n\n      if (ChunkMap().size() && ((wait_stop - wait_start) > sChunkTimeout)) {\n        eos_err(\"discarding %d chunks  in-flight for writing\", ChunkMap().size());\n\n        for (auto it = ChunkMap().begin(); it != ChunkMap().end(); ++it) {\n          it->second->disable(it->second);\n        }\n\n        ChunkMap().clear();\n        return XRootDStatus(XrdCl::stFatal,\n                            suDone,\n                            XrdCl::errSocketTimeout,\n                            \"request timeout\"\n                           );\n      }\n    }\n\n    eos_debug(\" [..] map-size=%lu\", ChunkMap().size());\n  }\n\n  {\n    XrdSysCondVarHelper writeLock(WriteCondVar());\n    return XWriteState;\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::WaitWrite(fuse_req_t req)\n/* -------------------------------------------------------------------------- */\n{\n  // this waits for all writes to come back and checks for interrupts inbetween\n  // this assumes a file is in OPENED state\n  {\n    XrdSysCondVarHelper lLock(WriteCondVar());\n\n    while (ChunkMap().size()) {\n      if (req && fuse_req_interrupted(req)) {\n        return EINTR;\n      }\n\n      eos_debug(\"     [..] map-size=%lu\", ChunkMap().size());\n      WriteCondVar().WaitMS(1000);\n    }\n\n    eos_debug(\" [..] map-size=%lu\", ChunkMap().size());\n  }\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nXRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::CollectWrites()\n/* -------------------------------------------------------------------------- */\n{\n  // this waits for all writes to come back and checks for interrupts inbetween\n  // this assumes a file is in OPENED state\n  {\n    XrdSysCondVarHelper lLock(WriteCondVar());\n\n    while (ChunkMap().size()) {\n      eos_debug(\"     [..] map-size=%lu\", ChunkMap().size());\n      WriteCondVar().WaitMS(1000);\n    }\n\n    eos_debug(\" [..] map-size=%lu\", ChunkMap().size());\n  }\n  return XWriteState;\n}\n\n/* -------------------------------------------------------------------------- */\nbool\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::OutstandingWrites()\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"\");\n  XrdSysCondVarHelper lLock(WriteCondVar());\n  return ChunkMap().size() ? true : false;\n}\n\nvoid\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::ReadAsyncHandler::HandleResponse(XrdCl::XRootDStatus* status,\n    XrdCl::AnyObject* response)\n/* -------------------------------------------------------------------------- */\n{\n  eos_static_debug(\"\");\n  // for correct destruct order 'myself' must be declared before anything\n  // that uses member variables\n  read_handler myself;\n  shared_proxy proxy;\n  {\n    std::lock_guard<std::mutex> lock(mDisableProxyMutex);\n    proxy = mProxy;\n    myself = mDisableKeepalive;\n    mDisableKeepalive = 0;\n  }\n  if (!proxy) {\n    delete response;\n    delete status;\n    return;\n  }\n\n  {\n    XrdSysCondVarHelper lLock(ReadCondVar());\n    mStatus = *status;\n    bool fuzzing = proxy->fuzzing().ReadAsyncResponseFuzz();\n\n    if (!fuzzing && status->IsOK()) {\n      XrdCl::ChunkInfo* chunk = 0;\n\n      if (response) {\n        response->Get(chunk);\n\n        if (valid()) {\n          if (chunk->length < mBuffer->size()) {\n            if (EOS_LOGS_DEBUG) {\n              eos_static_debug(\"handler %x received %lu instead of %lu\\n\", this,\n                               chunk->length, mBuffer->size());\n            }\n\n            mBuffer->resize(chunk->length);\n          }\n        }\n\n        if (!chunk->length) {\n          mEOF = true;\n        }\n\n        delete response;\n      } else {\n        mBuffer->resize(0);\n      }\n    } else {\n      if (status->IsOK()) {\n        if (valid()) {\n          mBuffer->resize(0);\n        }\n\n        if (response) {\n          delete response;\n        }\n      }\n\n      // we free the buffer, so it get's back to the buffer handler;\n      release_buffer();\n    }\n\n    mDone = true;\n    delete status;\n\n    std::lock_guard<std::mutex> lock(mDisableProxyMutex);\n    if (!mProxy) {\n      myself = mDisableKeepalive;\n      mDisableKeepalive = 0;\n      return;\n    }\n    mProxy->dec_read_chunks_in_flight();\n    ReadCondVar().Signal();\n    mProxy = 0;\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::read_handler\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::ReadAsyncPrepare(XrdCl::shared_proxy proxy, off_t offset,\n                               uint32_t size, bool blocking)\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"\");\n  read_handler src = std::make_shared<ReadAsyncHandler>(proxy, offset, size,\n                     blocking);\n\n  if (!src->valid()) {\n    // check if an IO buffer was allocated\n    return src;\n  }\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"handler %x request %lu/%lu non-blocking\\n\", &(*src), offset,\n                     size);\n  }\n\n  XrdSysCondVarHelper lLock(ReadCondVar());\n\n  if (ChunkRMap().count(src->offset())) {\n    return XrdCl::Proxy::read_handler{};\n  }\n\n  inc_read_chunks_in_flight();\n\n  ChunkRMap()[(uint64_t) src->offset()] = src;\n  ReadCondVar().Signal();\n  return src;\n}\n\n/* -------------------------------------------------------------------------- */\nXRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::PreReadAsync(uint64_t offset,\n                           uint32_t size,\n                           read_handler handler,\n                           uint16_t timeout)\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"\");\n  XRootDStatus status = WaitOpen();\n\n  if (!status.IsOK()) {\n    // remove the allocated chunk buffer\n    XrdSysCondVarHelper lLock(ReadCondVar());\n    ChunkRMap().erase(offset);\n    dec_read_chunks_in_flight();\n    return status;\n  }\n\n  XRootDStatus rstatus = File::Read(static_cast<uint64_t>(offset),\n                                    static_cast<uint32_t>(size),\n                                    (void*) handler->buffer(), handler.get(), timeout);\n\n  if (!rstatus.IsOK()) {\n    // remove the allocated chunk buffer\n    XrdSysCondVarHelper lLock(ReadCondVar());\n    ChunkRMap().erase(offset);\n    dec_read_chunks_in_flight();\n  }\n\n  return rstatus;\n}\n\n/* -------------------------------------------------------------------------- */\nXRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::WaitRead(read_handler handler)\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"\");\n  XrdSysCondVarHelper lLock(handler->ReadCondVar());\n  time_t wait_start = time(NULL);\n\n  while (!handler->done()) {\n    handler->ReadCondVar().WaitMS(1000);\n    time_t wait_stop = time(NULL);\n\n    if (((wait_stop - wait_start) > sChunkTimeout)) {\n      eos_err(\"discarding %d chunks  in-flight for reading\", ChunkMap().size());\n\n      for (auto it = ChunkRMap().begin(); it != ChunkRMap().end(); ++it) {\n        it->second->disable(it->second);\n      }\n\n      clear_read_chunks_in_flight();\n      ChunkRMap().clear();\n      return XRootDStatus(XrdCl::stFatal,\n                          suDone,\n                          XrdCl::errSocketTimeout,\n                          \"request timeout\"\n                         );\n    }\n  }\n\n  if (handler->valid()) {\n    eos_debug(\" [..] read-size=%lu\", handler->vbuffer().size());\n  }\n\n  return handler->Status();\n}\n\n/* -------------------------------------------------------------------------- */\nXRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::ReadAsync(read_handler handler,\n                        uint32_t size,\n                        void* buffer,\n                        uint32_t& bytesRead)\n/* -------------------------------------------------------------------------- */\n{\n  eos_debug(\"\");\n  XRootDStatus status = WaitRead(handler);\n\n  if (!status.IsOK()) {\n    return status;\n  }\n\n  bytesRead = (size < handler->vbuffer().size()) ? size :\n              handler->vbuffer().size();\n  memcpy(buffer, handler->buffer(), bytesRead);\n  return status;\n}\n\n/* -------------------------------------------------------------------------- */\nbool\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::DoneAsync(read_handler handler)\n{\n  eos_debug(\"\");\n  XRootDStatus status = WaitRead(handler);\n  XrdSysCondVarHelper lLock(ReadCondVar());\n  ChunkRMap().erase(handler->offset());\n  return true;\n}\n\n/* -------------------------------------------------------------------------- */\nXRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::Sync(uint16_t timeout)\n{\n  eos_debug(\"\");\n  return File::Sync(timeout);\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::attach()\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper lLock(mAttachedMutex);\n  mAttached++;\n  eos_debug(\"attached=%u\", mAttached);\n  return;\n}\n\n/* -------------------------------------------------------------------------- */\nsize_t\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::detach()\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper lLock(mAttachedMutex);\n  mAttached--;\n  eos_debug(\"attached=%u\", mAttached);\n  return mAttached;\n}\n\n/* -------------------------------------------------------------------------- */\nbool\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::attached()\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper lLock(mAttachedMutex);\n  return mAttached ? true : false;\n}\n\n/* -------------------------------------------------------------------------- */\nsize_t\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::get_attached()\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper lLock(mAttachedMutex);\n  return mAttached;\n}\n\n\n/* -------------------------------------------------------------------------- */\nint XrdCl::Fuzzing::errors[22] = { 101, 102, 103, 104, 105, 106, 107, 108, 109,\n                                   201, 202, 203, 204, 205, 206, 207,\n                                   301, 302, 303, 304, 305, 306\n                                 };\n/* -------------------------------------------------------------------------- */\n\nsize_t XrdCl::Fuzzing::non_fatal_errors = 9;\n/* -------------------------------------------------------------------------- */\nsize_t XrdCl::Fuzzing::fatal_errors = 13;\n/* -------------------------------------------------------------------------- */\nsize_t XrdCl::Fuzzing::open_async_submit_scaler = 0;\nsize_t XrdCl::Fuzzing::open_async_submit_counter = 0;\nsize_t XrdCl::Fuzzing::open_async_return_scaler = 0;\nsize_t XrdCl::Fuzzing::open_async_return_counter = 0;\nsize_t XrdCl::Fuzzing::read_async_return_scaler = 0;\nsize_t XrdCl::Fuzzing::read_async_return_counter = 0;\nbool XrdCl::Fuzzing::open_async_submit_fatal = false;\nbool XrdCl::Fuzzing::open_async_return_fatal = false;\n\n/* -------------------------------------------------------------------------- */\nXrdCl::XRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Fuzzing::OpenAsyncSubmitFuzz()\n{\n  if (open_async_submit_scaler) {\n    if (!(open_async_submit_counter++ % open_async_submit_scaler)) {\n      size_t random_error = eos::common::getRandom<size_t>(\n          0, non_fatal_errors + (open_async_submit_fatal ? fatal_errors : 0) - 1);\n      eos_static_debug(\"fuzzing error %d\", errors[random_error]);\n\n      if (random_error < non_fatal_errors) {\n        XrdCl::XRootDStatus status(XrdCl::stError, errors[random_error], 0);\n        return status;\n      } else {\n        XrdCl::XRootDStatus status(XrdCl::stFatal, errors[random_error], 0);\n        return status;\n      }\n    }\n  }\n\n  //  size_t open_async_submit_counter;\n  XRootDStatus status(XrdCl::stOK,\n                      0,\n                      0,\n                      \"open submitted\"\n                     );\n  return status;\n}\n\n\n/* -------------------------------------------------------------------------- */\nXrdCl::XRootDStatus\n/* -------------------------------------------------------------------------- */\nXrdCl::Fuzzing::OpenAsyncResponseFuzz()\n{\n  if (open_async_return_scaler) {\n    if (!(open_async_return_counter++ % open_async_return_scaler)) {\n      size_t random_error = eos::common::getRandom<size_t>(\n          0, non_fatal_errors + (open_async_return_fatal ? fatal_errors : 0) - 1);\n      eos_static_debug(\"fuzzing error %d\", errors[random_error]);\n\n      if (random_error < non_fatal_errors) {\n        XrdCl::XRootDStatus status(XrdCl::stError, errors[random_error], 0);\n        return status;\n      } else {\n        XrdCl::XRootDStatus status(XrdCl::stFatal, errors[random_error], 0);\n        return status;\n      }\n    }\n  }\n\n  eos_static_debug(\"fuzzing OK\");\n  //  size_t open_async_return_counter\n  XRootDStatus status(XrdCl::stOK,\n                      0,\n                      0,\n                      \"open successful\"\n                     );\n  return status;\n}\n\n/* -------------------------------------------------------------------------- */\nbool\n/* -------------------------------------------------------------------------- */\nXrdCl::Fuzzing::ReadAsyncResponseFuzz()\n{\n  if (read_async_return_scaler) {\n    if (!(read_async_return_counter++ % read_async_return_scaler)) {\n      eos_static_debug(\"fuzzing error\");\n      return true;\n    }\n  }\n\n  eos_static_debug(\"fuzzing OK\");\n  return false;\n}\n\n\n/* -------------------------------------------------------------------------- */\nconst char*\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::Dump(std::string& out)\n{\n  mProtocol.Dump(out);\n  return out.c_str();\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::Protocol::Add(std::string s)\n{\n  XrdSysMutexHelper lock(mMutex);\n  mMessages.push_back(std::string(\"---- \" + s + \"\\n\"));\n}\n\n/* -------------------------------------------------------------------------- */\nconst char*\n/* -------------------------------------------------------------------------- */\nXrdCl::Proxy::Protocol::Dump(std::string& out)\n{\n  XrdSysMutexHelper lock(mMutex);\n\n  for (auto item = mMessages.begin(); item != mMessages.end(); ++item) {\n    out += *item;\n  }\n\n  return out.c_str();\n}\n"
  },
  {
    "path": "fusex/data/xrdclproxy.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file xrdclproxy.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief xrootd file proxy class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_XRDCLPROXY_HH_\n#define FUSE_XRDCLPROXY_HH_\n\n#include <XrdSys/XrdSysPthread.hh>\n#include <XrdCl/XrdClFile.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdCl/XrdClDefaultEnv.hh>\n#include \"llfusexx.hh\"\n#include \"misc/FuseId.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Timing.hh\"\n#include \"common/RWMutex.hh\"\n#include <memory>\n#include <map>\n#include <string>\n#include <deque>\n#include <queue>\n#include <thread>\n#include <atomic>\n#include <mutex>\n\nnamespace XrdCl\n{\n\n// ---------------------------------------------------------------------- //\nclass Fuzzing\n{\npublic:\n\n  Fuzzing()\n  {\n  }\n\n  static void Configure(size_t _open_async_submit_scaler,\n                        size_t _open_async_return_scaler,\n                        bool _open_async_submit_fatal,\n                        bool _open_async_return_fatal,\n                        size_t _read_async_return_scaler)\n  {\n    open_async_submit_scaler = _open_async_submit_scaler;\n    open_async_return_scaler = _open_async_return_scaler;\n    open_async_submit_fatal = _open_async_submit_fatal;\n    open_async_return_fatal = _open_async_return_fatal;\n    read_async_return_scaler = _read_async_return_scaler;\n  }\n\n  XRootDStatus OpenAsyncSubmitFuzz();\n  XRootDStatus OpenAsyncResponseFuzz();\n  bool ReadAsyncResponseFuzz();\n\n  static int errors[22];\n  static size_t non_fatal_errors;\n  static size_t fatal_errors;\n\nprivate:\n  static size_t open_async_submit_scaler;\n  static size_t open_async_submit_counter;\n  static size_t open_async_return_scaler;\n  static size_t open_async_return_counter;\n  static size_t read_async_return_scaler;\n  static size_t read_async_return_counter;\n  static bool open_async_submit_fatal;\n  static bool open_async_return_fatal;\n};\n\n// ---------------------------------------------------------------------- //\ntypedef std::shared_ptr<std::vector<char>> shared_buffer;\nclass Proxy;\ntypedef std::shared_ptr<Proxy> shared_proxy;\n\n// ---------------------------------------------------------------------- //\n\n// ---------------------------------------------------------------------- //\n\nclass BufferManager : public XrdSysMutex\n// ---------------------------------------------------------------------- //\n{\npublic:\n\n  BufferManager(size_t _max = 128, size_t _default_size = 128 * 1024,\n                size_t _max_inflight_size = 1 * 1024 * 1024 * 1024)\n  {\n    max = _max;\n    buffersize = _default_size;\n    queued_size = 0;\n    inflight_size = 0;\n    inflight_buffers = 0;\n    xoff_cnt = 0;\n    nobuf_cnt = 0;\n    max_inflight_size = _max_inflight_size;\n  }\n\n  virtual ~BufferManager() { }\n\n  void configure(size_t _max, size_t _size,\n                 size_t _max_inflight_size = 1 * 1024 * 1024 * 1024)\n  {\n    max = _max;\n    buffersize = _size;\n    max_inflight_size = _max_inflight_size;\n  }\n\n  void reset()\n  {\n    XrdSysMutexHelper lLock(this);\n    inflight_size = 0;\n    inflight_buffers = 0;\n  }\n\n  shared_buffer get_buffer(size_t size, bool blocking = true)\n  {\n    struct timespec ts;\n    eos::common::Timing::GetTimeSpec(ts, true);\n\n    // make sure, we don't have more buffers in flight than max_inflight_size\n    do {\n      size_t cnt = 0;\n      {\n        XrdSysMutexHelper lLock(this);\n        static time_t grace_buffer_time = 0;\n\n        if ((inflight_size < max_inflight_size) &&\n            (inflight_buffers < 16384)) { // avoid to trigger XRootD SID exhaustion\n          break;\n        }\n\n        // a grace buffer period allows to unstuck a getbuffer dead-lock where buffers are referenced by a failing fd\n        if (ts.tv_sec < grace_buffer_time) {\n          if ((inflight_size < (2 * max_inflight_size) &&\n               (inflight_buffers < 16384))) {\n            break;\n          }\n        }\n\n        if (!(cnt % 1000)) {\n          if (inflight_size >= max_inflight_size) {\n            eos_static_info(\"inflight-buffer exceeds maximum number of bytes [%ld/%ld]\",\n                            inflight_size, max_inflight_size);\n          }\n\n          if (inflight_buffers >= 16384) {\n            eos_static_info(\"inflight-buffer exceeds maximum number of buffers in flight [%ld/%ld]\",\n                            inflight_buffers, 16384);\n          }\n        }\n\n        if (!blocking) {\n          nobuf_cnt++;\n          // we don't wait for free buffers\n          return nullptr;\n        }\n\n        xoff_cnt++;\n        double exec_time_sec = 1.0 * eos::common::Timing::GetCoarseAgeInNs(&ts,\n                               0) / 1000000000.0;\n\n        if (exec_time_sec > 200) {\n          // temporarily increase the buffer size to unlock a buffer starvation dead-lock\n          grace_buffer_time = time(NULL) + 60;\n          // exceptionally grant more buffers to recover from a dead-lock situation\n          eos_static_warning(\"granting grace buffers now=%u until then=%u\",\n                             ts.tv_sec,\n                             grace_buffer_time);\n        }\n      }\n      cnt++;\n      // we wait that the situation relaxes\n      std::this_thread::sleep_for(std::chrono::milliseconds(100));\n    } while (1);\n\n    XrdSysMutexHelper lLock(this);\n    size_t cap_size = size;\n    inflight_buffers++;\n    shared_buffer buffer;\n\n    if (!queue.size() || (size < buffersize)) {\n      inflight_size += cap_size;\n      buffer = std::make_shared<std::vector<char>>(cap_size, 0);\n    } else {\n      buffer = queue.front();\n      queued_size -= buffer->capacity();\n      buffer->resize(cap_size);\n      buffer->reserve(cap_size);\n      inflight_size += buffer->capacity();\n      queue.pop();\n    }\n\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"get-buffer %lx size %lu\", (uint64_t)(&((*buffer)[0])),\n                       buffer->capacity());\n    }\n\n    return buffer;\n  }\n\n  void put_buffer(shared_buffer buffer)\n  {\n    XrdSysMutexHelper lLock(this);\n    inflight_size -= buffer->capacity();\n\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"put-buffer %lx size %lu\", (uint64_t)(&((*buffer)[0])),\n                       buffer->capacity());\n    }\n\n    inflight_buffers--;\n\n    if ((queue.size() == max) || (buffer->capacity() < buffersize)) {\n      return;\n    } else {\n      queue.push(buffer);\n      buffer->resize(buffersize);\n      buffer->reserve(buffersize);\n      buffer->shrink_to_fit();\n      queued_size += buffersize;\n      return;\n    }\n  }\n\n  size_t queued()\n  {\n    XrdSysMutexHelper lLock(this);\n    return queued_size;\n  }\n\n  size_t inflight()\n  {\n    XrdSysMutexHelper lLock(this);\n    return inflight_size;\n  }\n\n  size_t xoff()\n  {\n    XrdSysMutexHelper lLock(this);\n    return xoff_cnt;\n  }\n\n  size_t nobuf()\n  {\n    XrdSysMutexHelper lLock(this);\n    return nobuf_cnt;\n  }\n\nprivate:\n  std::queue<shared_buffer> queue;\n  size_t max;\n  size_t buffersize;\n  size_t queued_size;\n  size_t inflight_size;\n  size_t inflight_buffers;\n  size_t max_inflight_size;\n  size_t xoff_cnt;\n  size_t nobuf_cnt;\n};\n\n\n//------------------------------------------------------------------------------\n//! Class Proxy\n//------------------------------------------------------------------------------\nclass Proxy : public XrdCl::File, public eos::common::LogId\n\n{\npublic:\n\n  class ReadAsyncHandler;\n\n  static BufferManager sWrBufferManager; // write buffer manager\n  static BufferManager sRaBufferManager; // async read buffer manager\n\n  //----------------------------------------------------------------------------\n  //! Method to crate a new XrdCl::Proxy object with the option to request the\n  //! creation of a new TCP connection for the underlying XrdC::File object.\n  //!\n  //! @param proxy     a proxy from which to copy fuseid, ino and req\n  //!                  login url will be set to the current one proposed by\n  //!                  processCache for the id.\n  //! @param reconnect if true try to ensure the new proxy will use a different\n  //!                  login url to one used by \"proxy\", requesting a new one\n  //!                  from processCache is needed. If the process pid in\n  //!                  fuseid has already exited processCache will likely\n  //!                  not be able update the login url.\n  //!\n  //! @return new XrdCl::Proxy object\n  //----------------------------------------------------------------------------\n  static XrdCl::shared_proxy Factory(const shared_proxy proxy = {},\n                                     bool reconnect = false);\n\n\n  // ---------------------------------------------------------------------- //\n  XRootDStatus OpenAsync(XrdCl::shared_proxy proxy,\n                         const std::string& url,\n                         OpenFlags::Flags flags,\n                         Access::Mode mode,\n                         uint16_t timeout);\n\n  // ---------------------------------------------------------------------- //\n  XRootDStatus ReOpenAsync(XrdCl::shared_proxy proxy);\n\n  // ---------------------------------------------------------------------- //\n  XRootDStatus WaitOpen();\n\n  int WaitOpen(fuse_req_t); // waiting interrupts\n\n  // ---------------------------------------------------------------------- //\n  bool IsOpening();\n\n  // ---------------------------------------------------------------------- //\n  bool IsClosing();\n\n  // ---------------------------------------------------------------------- //\n  bool IsOpen();\n\n  // ---------------------------------------------------------------------- //\n  bool IsClosed();\n\n  // ---------------------------------------------------------------------- //\n  XRootDStatus WaitWrite();\n\n  // ---------------------------------------------------------------------- //\n  XRootDStatus CollectWrites();\n\n  // ---------------------------------------------------------------------- //\n  int WaitWrite(fuse_req_t); // waiting interrupts\n\n  // ---------------------------------------------------------------------- //\n  bool IsWaitWrite();\n\n  // ---------------------------------------------------------------------- //\n  bool OutstandingWrites();\n\n  // ---------------------------------------------------------------------- //\n  bool HadFailures(std::string& message);\n\n  // ---------------------------------------------------------------------- //\n  XRootDStatus Write(uint64_t offset,\n                     uint32_t size,\n                     const void* buffer,\n                     ResponseHandler* handler,\n                     uint16_t timeout = 0);\n\n  XRootDStatus Write(uint64_t offset,\n                     uint32_t size,\n                     const void* buffer,\n                     uint16_t timeout = 0)\n  {\n    return File::Write(offset, size, buffer, timeout);\n  }\n\n  // ---------------------------------------------------------------------- //\n  XRootDStatus Read(XrdCl::shared_proxy proxy,\n                    uint64_t offset,\n                    uint32_t size,\n                    void* buffer,\n                    uint32_t& bytesRead,\n                    uint16_t timeout = 0);\n\n  // ---------------------------------------------------------------------- //\n  XRootDStatus Sync(uint16_t timeout = 0);\n\n\n  // ---------------------------------------------------------------------- //\n\n  XRootDStatus CloseAsync(XrdCl::shared_proxy proxy, uint16_t timeout = 0);\n\n  XRootDStatus ScheduleCloseAsync(XrdCl::shared_proxy proxy,\n                                  uint16_t timeout = 0);\n\n\n  XRootDStatus WaitClose();\n\n  // ---------------------------------------------------------------------- //\n  XRootDStatus Close(uint16_t timeout = 0);\n\n  // ---------------------------------------------------------------------- //\n  bool HadFailures();\n\n  // ---------------------------------------------------------------------- //\n\n  void inherit_attached(shared_proxy proxy)\n  {\n    XrdSysMutex mAttachedMutex;\n    mAttached = proxy ? proxy->get_attached() : 1;\n  }\n\n  void inherit_writequeue(shared_proxy new_proxy, shared_proxy proxy)\n  {\n    XWriteQueue = proxy->WriteQueue();\n    proxy->WriteQueue().clear();\n\n    for (auto i : XWriteQueue) {\n      i->SetProxy(new_proxy);\n    }\n  }\n\n  void inherit_protocol(shared_proxy proxy)\n  {\n    mProtocol = proxy->getProtocol();\n  }\n\n  // ---------------------------------------------------------------------- //\n\n  static int status2errno(const XRootDStatus& status)\n  {\n    if (!status.errNo) {\n      if (status.IsOK()) {\n        return 0;\n      } else {\n        return EPROTO;\n      }\n    }\n\n    if (status.errNo < kXR_ArgInvalid) {\n      return status.errNo;\n    } else {\n      return XProtocol::toErrno(status.errNo);\n    }\n  }\n\n  enum OPEN_STATE {\n    CLOSED = 0,\n    OPENING = 1,\n    OPENED = 2,\n    WAITWRITE = 3,\n    CLOSING = 4,\n    FAILED = 5,\n    CLOSEFAILED = 6,\n  };\n\n  char const* state_string()\n  {\n    switch (open_state) {\n    case CLOSED:\n      return \"closed\";\n\n    case OPENING:\n      return \"opening\";\n\n    case OPENED:\n      return \"open\";\n\n    case WAITWRITE:\n      return \"waitwrite\";\n\n    case CLOSING:\n      return \"closing\";\n\n    case FAILED:\n      return \"failed\";\n\n    case CLOSEFAILED:\n      return \"closefailed\";\n\n    default:\n      return \"invalid\";\n    }\n  }\n\n  OPEN_STATE state()\n  {\n    // lock XOpenAsyncCond from outside\n    return open_state;\n  }\n\n  XRootDStatus read_state()\n  {\n    return XReadState;\n  }\n\n  XRootDStatus write_state()\n  {\n    return XWriteState;\n  }\n\n  XRootDStatus opening_state()\n  {\n    return XOpenState;\n  }\n\n  bool opening_state_should_retry()\n  {\n    if ((opening_state().code != XrdCl::errConnectionError) &&\n        (opening_state().code != XrdCl::errSocketTimeout) &&\n        (opening_state().code != XrdCl::errOperationExpired) &&\n        (opening_state().code != XrdCl::errSocketDisconnected) &&\n        (opening_state().errNo != kXR_noserver) &&\n        (opening_state().errNo != kXR_FSError) &&\n        (opening_state().errNo != kXR_IOError)) {\n      return false;\n    }\n\n    return true;\n  }\n\n  void set_state(OPEN_STATE newstate, XRootDStatus* xs = 0)\n  {\n    // lock XOpenAsyncCond from outside\n    open_state = newstate;\n    eos::common::Timing::GetTimeSpec(open_state_time);\n    mProtocol.Add(eos_static_log(LOG_SILENT, \"%s\", state_string()));\n\n    if (xs) {\n      XOpenState = *xs;\n    }\n  }\n\n\n  void set_lasturl()\n  {\n    this->GetProperty(\"LastURL\", mLastUrl);\n    XrdCl::URL newurl(mLastUrl);\n    XrdCl::URL::ParamsMap cgi = newurl.GetParams();\n    mProtocol.Add(eos_static_log(LOG_SILENT, \"host=%s:%d\",\n                                 newurl.GetHostName().c_str(), newurl.GetPort()));\n    mProtocol.Add(eos_static_log(LOG_SILENT, \"lfn='%s' app='%s'\",\n                                 cgi[\"eos.lfn\"].c_str(), cgi[\"eos.app\"].c_str()));\n    mProtocol.Add(eos_static_log(LOG_SILENT, \"logid=%s\", cgi[\"mgm.logid\"].c_str()));\n    mProtocol.Add(eos_static_log(LOG_SILENT, \"fuse=%s:%s:%s:%s:%s\",\n                                 cgi[\"fuse.exe\"].c_str(),\n                                 cgi[\"fuse.uid\"].c_str(),\n                                 cgi[\"fuse.gid\"].c_str(),\n                                 cgi[\"fuse.pid\"].c_str(),\n                                 cgi[\"fuse.ver\"].c_str()));\n    mProtocol.Add(eos_static_log(LOG_SILENT, \"xrd=%s:%s:%s:%s\",\n                                 cgi[\"xrdcl.requuid\"].c_str(),\n                                 cgi[\"xrdcl.secuid\"].c_str(),\n                                 cgi[\"xrdcl.sccgid\"].c_str(),\n                                 cgi[\"xrdcl.wantprot\"].c_str()));\n  }\n\n  // TS stands for \"thread-safe\"\n\n  void set_state_TS(OPEN_STATE newstate, XRootDStatus* xs = 0)\n  {\n    XrdSysCondVarHelper lLock(OpenCondVar());\n    set_state(newstate, xs);\n  }\n\n  double state_age()\n  {\n    return ((double) eos::common::Timing::GetAgeInNs(&open_state_time,\n            0) / 1000000000.0);\n  }\n\n  void set_readstate(XRootDStatus* xs)\n  {\n    XReadState = *xs;\n  }\n\n  void set_writestate(XRootDStatus* xs)\n  {\n    XWriteState = *xs;\n  }\n\n  enum READAHEAD_STRATEGY {\n    NONE = 0,\n    STATIC = 1,\n    DYNAMIC = 2\n  };\n\n  void set_readahead_maximum_position(off_t offset)\n  {\n    mReadAheadMaximumPosition = offset;\n  }\n\n  off_t get_readahead_maximum_position() const\n  {\n    return mReadAheadMaximumPosition;\n  }\n\n  void set_readahead_sparse_ratio(double r)\n  {\n    XReadAheadSparseRatio = r;\n  }\n\n  double get_readhead_sparse_ratio()\n  {\n    return XReadAheadSparseRatio;\n  }\n\n  static READAHEAD_STRATEGY readahead_strategy_from_string(\n    const std::string& strategy)\n  {\n    if (strategy == \"dynamic\") {\n      return DYNAMIC;\n    }\n\n    if (strategy == \"static\") {\n      return STATIC;\n    }\n\n    return NONE;\n  }\n\n  void set_readahead_strategy(READAHEAD_STRATEGY rhs,\n                              size_t min, size_t nom, size_t max, size_t rablocks, float sparse_ratio = 0.0)\n  {\n    XReadAheadStrategy = rhs;\n    XReadAheadMin = min;\n    XReadAheadNom = nom;\n    XReadAheadMax = max;\n    XReadAheadBlocksMax = rablocks;\n    XReadAheadBlocksNom = 1;\n    XReadAheadBlocksMin = 1;\n    XReadAheadReenableHits = 0;\n    XReadAheadSparseRatio = sparse_ratio;\n  }\n\n  float get_readahead_efficiency()\n  {\n    XrdSysCondVarHelper lLock(ReadCondVar());\n    return (mTotalBytes) ? (100.0 * mTotalReadAheadHitBytes / mTotalBytes) : 0.0;\n  }\n\n  float get_readahead_volume_efficiency()\n  {\n    XrdSysCondVarHelper lLock(ReadCondVar());\n    return (mTotalReadAheadBytes) ? (100.0 * mTotalReadAheadHitBytes /\n                                     mTotalReadAheadBytes)\n           : 0.0;\n  }\n\n  void set_id(uint64_t ino, fuse_req_t req)\n  {\n    ProcessSnapshot snapshot;\n    mIno = ino;\n    mReq = req;\n    if (req) {\n      mId.init(req);\n      snapshot = fusexrdlogin::processCache->retrieve(mId.pid, mId.uid, mId.gid, false);\n    }\n    if (snapshot) {\n      mUc = snapshot->getBoundIdentity()->getCreds()->getUC();\n      mConnId = snapshot->getBoundIdentity()->getLogin().getConnectionID();\n    }\n    char lid[64];\n    snprintf(lid, sizeof(lid), \"logid:ino:%016lx\", ino);\n    SetLogId(lid);\n  }\n\n  uint64_t id() const\n  {\n    return mIno;\n  }\n\n  fuse_req_t req() const\n  {\n    return mReq;\n  }\n\n  fuse_id fuseid() const\n  {\n    return mId;\n  }\n\n  // ---------------------------------------------------------------------- //\n\n  Proxy() :\n    XOpenAsyncCond(0),\n    XWriteAsyncCond(0),\n    XReadAsyncCond(0),\n    XWriteQueueDirectSubmission(0),\n    XWriteQueueScheduledSubmission(0),\n    XCloseAfterWrite(false),\n    XCloseAfterWriteTimeout(0),\n    mReq(0), mIno(0), mDeleted(false)\n  {\n    XrdSysCondVarHelper lLock(XOpenAsyncCond);\n    set_state(CLOSED);\n    XrdCl::Env* env = XrdCl::DefaultEnv::GetEnv();\n    env->PutInt(\"TimeoutResolution\", 1);\n    env->PutInt(\"MetalinkProcessing\", 0);\n    XReadAheadStrategy = NONE;\n    XReadAheadMin = 4 * 1024;\n    XReadAheadNom = 256 * 1204;\n    XReadAheadMax = 1024 * 1024;\n    XReadAheadBlocksMax = 16;\n    XReadAheadBlocksNom = 1;\n    XReadAheadBlocksMin = 1;\n    XReadAheadReenableHits = 0;\n    XReadAheadBlocksIs = 0;\n    XReadAheadDisabled = false;\n    XReadAheadSparseRatio = 0.0;\n    mSeqDistance = 0;\n    mPosition = 0;\n    mReadAheadPosition = 0;\n    mTotalBytes = 0;\n    mTotalReadAheadHitBytes = 0;\n    mTotalReadAheadBytes = 0;\n    mAttached = 0;\n    mTimeout = 0;\n    mRChunksInFlight.store(0, std::memory_order_seq_cst);\n    mDeleted = false;\n    mReadAheadMaximumPosition = 64 * 1024ll * 1024ll * 1024ll * 1024ll;\n    mConnId = 0;\n    sProxy++;\n  }\n\n  void Collect()\n  {\n    WaitWrite();\n    XrdSysCondVarHelper lLock(ReadCondVar());\n\n    for (auto it = ChunkRMap().begin(); it != ChunkRMap().end(); ++it) {\n      XrdSysCondVarHelper llLock(it->second->ReadCondVar());\n\n      while (!it->second->done()) {\n        it->second->ReadCondVar().WaitMS(25);\n      }\n    }\n  }\n\n  bool HasReadsInFlight()\n  {\n    return (read_chunks_in_flight()) ? true : false;\n  }\n\n  bool HasWritesInFlight()\n  {\n    // needs WriteCondVar locked\n    return ChunkMap().size() ? true : false;\n  }\n\n  bool HasTooManyWritesInFlight()\n  {\n    XrdSysCondVarHelper lLock(WriteCondVar());\n    return (ChunkMap().size() > 1024);\n  }\n\n  void DropReadAhead()\n  {\n    WaitWrite();\n    XrdSysCondVarHelper lLock(ReadCondVar());\n\n    for (auto it = ChunkRMap().begin(); it != ChunkRMap().end(); ++it) {\n      XrdSysCondVarHelper llLock(it->second->ReadCondVar());\n\n      while (!it->second->done()) {\n        it->second->ReadCondVar().WaitMS(25);\n      }\n    }\n\n    ChunkRMap().clear();\n  }\n\n  bool DoneReadAhead()\n  {\n    XrdSysCondVarHelper lLock(ReadCondVar());\n\n    for (auto it = ChunkRMap().begin(); it != ChunkRMap().end(); ++it) {\n      XrdSysCondVarHelper llLock(it->second->ReadCondVar());\n\n      if (!it->second->done()) {\n        return false;\n      }\n    }\n\n    ChunkRMap().clear();\n    return true;\n  }\n\n\n  // ---------------------------------------------------------------------- //\n\n  virtual ~Proxy()\n  {\n    sProxy--;\n    WaitOpen();\n\n    // collect all pending read requests\n    if (IsOpen()) {\n      Collect();\n    }\n\n    // collect all pending write requests\n    if (IsWaitWrite()) {\n      CollectWrites();\n    }\n\n    eos_notice(\"ra-efficiency=%f ra-vol-efficiency=%f tot-bytes=%lu ra-bytes=%lu ra-hit-bytes=%lu \",\n               get_readahead_efficiency(),\n               get_readahead_volume_efficiency(),\n               mTotalReadAheadBytes,\n               mTotalReadAheadHitBytes);\n  }\n\n  // ---------------------------------------------------------------------- //\n\n  class OpenAsyncHandler : public XrdCl::ResponseHandler\n  // ---------------------------------------------------------------------- //\n  {\n  public:\n\n    OpenAsyncHandler() { }\n\n    OpenAsyncHandler(shared_proxy file) :\n      mProxy(file) { }\n\n    void SetProxy(shared_proxy file)\n    {\n      mProxy = file;\n    }\n\n    virtual ~OpenAsyncHandler() { }\n\n    virtual void HandleResponseWithHosts(XrdCl::XRootDStatus* status,\n                                         XrdCl::AnyObject* response,\n                                         XrdCl::HostList* hostList);\n\n    shared_proxy proxy()\n    {\n      return mProxy;\n    }\n\n  private:\n    shared_proxy mProxy;\n  };\n\n  // ---------------------------------------------------------------------- //\n\n  class CloseAsyncHandler : public XrdCl::ResponseHandler\n  // ---------------------------------------------------------------------- //\n  {\n  public:\n\n    CloseAsyncHandler() { }\n\n    CloseAsyncHandler(shared_proxy file) :\n      mProxy(file) { }\n\n    void SetProxy(shared_proxy file)\n    {\n      mProxy = file;\n    }\n\n    virtual ~CloseAsyncHandler() { }\n\n    virtual void HandleResponse(XrdCl::XRootDStatus* pStatus,\n                                XrdCl::AnyObject* pResponse);\n\n    shared_proxy proxy()\n    {\n      return mProxy;\n    }\n\n  private:\n    shared_proxy mProxy;\n  };\n\n\n  // ---------------------------------------------------------------------- //\n\n  class WriteAsyncHandler : public XrdCl::ResponseHandler\n  // ---------------------------------------------------------------------- //\n  {\n  public:\n\n    WriteAsyncHandler() { }\n\n    WriteAsyncHandler(WriteAsyncHandler* other)\n    {\n      mProxy = other->proxy();\n      *mBuffer = other->vbuffer();\n      woffset = other->offset();\n      mTimeout = other->timeout();\n    }\n\n    WriteAsyncHandler(shared_proxy file, uint32_t size, off_t off = 0,\n                      uint16_t timeout = 0) : mProxy(file), woffset(off), mTimeout(timeout)\n    {\n      mBuffer = sWrBufferManager.get_buffer(size);\n      mBuffer->resize(size);\n\n      if (file) {\n        std::lock_guard<std::mutex> lock(gBuffReferenceMutex);\n        mId = std::to_string((uint64_t) file.get());\n        mId += \":open=\";\n        mId += std::to_string(file->state());\n        mId += \":\";\n        mId += file->url();\n        gBufferReference[mId] = size;\n      }\n\n      XrdSysCondVarHelper lLock(mProxy->WriteCondVar());\n      mProxy->WriteCondVar().Signal();\n    }\n\n    virtual ~WriteAsyncHandler()\n    {\n      {\n        sWrBufferManager.put_buffer(mBuffer);\n        std::lock_guard<std::mutex> lock(gBuffReferenceMutex);\n        gBufferReference.erase(mId);\n      }\n      mProxy = 0;\n    }\n\n    void SetProxy(shared_proxy proxy)\n    {\n      mProxy = proxy;\n    }\n\n    char* buffer()\n    {\n      return &((*mBuffer)[0]);\n    }\n\n    off_t offset()\n    {\n      return woffset;\n    }\n\n    uint16_t timeout()\n    {\n      return mTimeout;\n    }\n\n    std::vector<char>& vbuffer()\n    {\n      return *mBuffer;\n    }\n\n    shared_proxy proxy()\n    {\n      return mProxy;\n    }\n\n    void disable(std::shared_ptr<WriteAsyncHandler> self)\n    {\n      std::lock_guard<std::mutex> lock(mDisableProxyMutex);\n      if (mProxy) mDisableKeepalive = self;\n      mProxy = 0;\n    }\n\n    void copy(const void* cbuffer, size_t size)\n    {\n      mBuffer->resize(size);\n      memcpy(buffer(), cbuffer, size);\n    }\n\n    virtual void HandleResponse(XrdCl::XRootDStatus* pStatus,\n                                XrdCl::AnyObject* pResponse);\n\n    static std::mutex gBuffReferenceMutex;\n    static std::map<std::string, uint64_t> gBufferReference;\n\n    static void DumpReferences(std::string& out);\n\n  private:\n    shared_proxy mProxy;\n    shared_buffer mBuffer;\n    off_t woffset;\n    uint16_t mTimeout;\n    std::string mId;\n    // the disable members below are used for disable(), where\n    // we have been registered for callback but the associated proxy\n    // wants to disown the us (the handler).\n    std::mutex mDisableProxyMutex;\n    std::shared_ptr<WriteAsyncHandler> mDisableKeepalive;\n\n  };\n\n  // ---------------------------------------------------------------------- //\n\n  class ReadAsyncHandler : public XrdCl::ResponseHandler\n  // ---------------------------------------------------------------------- //\n  {\n  public:\n\n    ReadAsyncHandler() : mAsyncCond(0) { }\n\n    ReadAsyncHandler(ReadAsyncHandler* other) : mAsyncCond(0)\n    {\n      mProxy = other->proxy();\n\n      if (other->valid()) {\n        *mBuffer = other->vbuffer();\n      }\n\n      roffset = other->offset();\n      mDone = false;\n      mEOF = false;\n      mCreationTime = other->creationtime();\n    }\n\n    ReadAsyncHandler(shared_proxy file, off_t off, uint32_t size,\n                     bool blocking = true) : mProxy(file),\n      mAsyncCond(0)\n    {\n      mBuffer = sRaBufferManager.get_buffer(size, blocking);\n\n      if (valid()) {\n        mBuffer->resize(size);\n      }\n\n      roffset = off;\n      mDone = false;\n      mEOF = false;\n      mProxy = file;\n      mCreationTime = time(NULL);\n\n      if (valid()) {\n        eos_static_debug(\"----: creating chunk offset=%ld size=%u addr=%lx\", off, size,\n                         this);\n      }\n    }\n\n    virtual ~ReadAsyncHandler()\n    {\n      if (valid()) {\n        eos_static_debug(\"----: releasing chunk offset=%d size=%u addr=%lx\", roffset,\n                         mBuffer->size(), this);\n      }\n\n      release_buffer();\n    }\n\n    void release_buffer()\n    {\n      if (valid()) {\n        sRaBufferManager.put_buffer(mBuffer);\n        mBuffer = 0;\n      }\n    }\n\n    char* buffer()\n    {\n      return &((*mBuffer)[0]);\n    }\n\n    std::vector<char>& vbuffer()\n    {\n      return *mBuffer;\n    }\n\n    shared_proxy proxy()\n    {\n      return mProxy;\n    }\n\n    time_t creationtime()\n    {\n      return mCreationTime;\n    }\n\n    bool expired()\n    {\n      // a read should never take that long\n      if ((time(NULL) - creationtime()) > 300) {\n        return true;\n      } else {\n        return false;\n      }\n    }\n\n    off_t offset()\n    {\n      return roffset;\n    }\n\n    size_t size()\n    {\n      return mBuffer->size();\n    }\n\n    bool valid()\n    {\n      // check for an allocated buffer\n      return (mBuffer ? true : false);\n    }\n    bool matches(off_t off, uint32_t size,\n                 off_t& match_offset, uint32_t& match_size)\n    {\n      if (!mBuffer) {\n        return false;\n      }\n\n      if ((off >= roffset) &&\n          (off < ((off_t)(roffset + mBuffer->size())))) {\n        match_offset = off;\n\n        if ((off + size) <= (off_t)(roffset + mBuffer->size())) {\n          match_size = size;\n        } else {\n          match_size = (roffset + mBuffer->size() - off);\n        }\n\n        return true;\n      }\n\n      return false;\n    }\n\n    bool successor(off_t off, uint32_t size)\n    {\n      off_t match_offset;\n      uint32_t match_size;\n\n      if (matches((off_t) off, size, match_offset, match_size)) {\n        return true;\n      } else {\n        return false;\n      }\n    }\n\n    XrdSysCondVar& ReadCondVar()\n    {\n      return mAsyncCond;\n    }\n\n    XRootDStatus& Status()\n    {\n      return mStatus;\n    }\n\n    bool done()\n    {\n      return mDone;\n    }\n\n    bool eof()\n    {\n      return mEOF;\n    }\n\n    void disable(std::shared_ptr<ReadAsyncHandler> self)\n    {\n      std::lock_guard<std::mutex> lock(mDisableProxyMutex);\n      if (mProxy) mDisableKeepalive = self;\n      mProxy = 0;\n    }\n\n    virtual void HandleResponse(XrdCl::XRootDStatus* pStatus,\n\n                                XrdCl::AnyObject* pResponse);\n    static size_t nexpired()\n    {\n      std::lock_guard<std::mutex> lock(gExpiredChunksMutex);\n      return gExpiredChunks.size();\n    }\n\n    static std::mutex\n    gExpiredChunksMutex; // protecting expired global expired chunks vector\n    static std::vector<std::shared_ptr<ReadAsyncHandler>>\n        gExpiredChunks;     // global expired chunks vector\n\n  private:\n    bool mDone;\n    bool mEOF;\n    shared_proxy mProxy;\n    shared_buffer mBuffer;\n    off_t roffset;\n    XRootDStatus mStatus;\n    XrdSysCondVar mAsyncCond;\n    time_t mCreationTime;\n    // the disable members below are used for disable(), where\n    // we have been registered for callback but the associated proxy\n    // wants to disown the us (the handler).\n    std::mutex mDisableProxyMutex;\n    std::shared_ptr<ReadAsyncHandler> mDisableKeepalive;\n\n  };\n\n\n  // ---------------------------------------------------------------------- //\n  typedef std::shared_ptr<ReadAsyncHandler> read_handler;\n  typedef std::shared_ptr<WriteAsyncHandler> write_handler;\n\n  typedef std::map<uint64_t, write_handler> chunk_map;\n  typedef std::map<uint64_t, read_handler> chunk_rmap;\n\n  typedef std::vector<write_handler> chunk_vector;\n  typedef std::vector<read_handler> chunk_rvector;\n\n  // ---------------------------------------------------------------------- //\n  write_handler WriteAsyncPrepare(XrdCl::shared_proxy proxy, uint32_t size,\n                                  uint64_t offset = 0,\n                                  uint16_t timeout = 0);\n\n\n  // ---------------------------------------------------------------------- //\n  XRootDStatus WriteAsync(uint64_t offset,\n                          uint32_t size,\n                          const void* buffer,\n                          write_handler handler,\n                          uint16_t timeout);\n\n  // ---------------------------------------------------------------------- //\n  XRootDStatus ScheduleWriteAsync(\n    const void* buffer,\n    write_handler handler\n  );\n\n\n  // ---------------------------------------------------------------------- //\n\n  XrdSysCondVar& OpenCondVar()\n  {\n    return XOpenAsyncCond;\n  }\n\n  // ---------------------------------------------------------------------- //\n\n  XrdSysCondVar& WriteCondVar()\n  {\n    return XWriteAsyncCond;\n  }\n\n  // ---------------------------------------------------------------------- //\n\n  XrdSysCondVar& ReadCondVar()\n  {\n    return XReadAsyncCond;\n  }\n\n  // ---------------------------------------------------------------------- //\n\n  read_handler ReadAsyncPrepare(XrdCl::shared_proxy proxy,\n                                off_t offset, uint32_t size,\n                                bool blocking = true);\n\n\n  // ---------------------------------------------------------------------- //\n  XRootDStatus PreReadAsync(uint64_t offset,\n                            uint32_t size,\n                            read_handler handler,\n                            uint16_t timeout);\n\n  // ---------------------------------------------------------------------- //\n  XRootDStatus WaitRead(read_handler handler);\n\n\n  // ---------------------------------------------------------------------- //\n  XRootDStatus ReadAsync(read_handler handler,\n                         uint32_t size,\n                         void* buffer,\n                         uint32_t& bytesRead\n                        );\n\n  // ---------------------------------------------------------------------- //\n  bool DoneAsync(read_handler handler);\n\n\n  std::deque<write_handler>& WriteQueue()\n  {\n    return XWriteQueue;\n  }\n\n  void CleanWriteQueue()\n  {\n    XWriteQueueDirectSubmission = 0;\n    XWriteQueueScheduledSubmission = 0;\n\n    for (auto it = WriteQueue().begin(); it != WriteQueue().end(); ++it) {\n      ChunkMap().erase((uint64_t)it->get());\n    }\n\n    WriteQueue().clear();\n  }\n\n  void inc_write_queue_direct_submissions()\n  {\n    XWriteQueueDirectSubmission++;\n  }\n\n  void inc_write_queue_scheduled_submissions()\n  {\n    XWriteQueueScheduledSubmission++;\n  }\n\n  float get_scheduled_submission_fraction()\n  {\n    return (XWriteQueueScheduledSubmission + XWriteQueueDirectSubmission) ? 100.0 *\n           XWriteQueueScheduledSubmission / (XWriteQueueScheduledSubmission +\n               XWriteQueueDirectSubmission) : 0;\n  }\n\n  bool close_after_write()\n  {\n    return XCloseAfterWrite;\n  }\n\n  uint16_t close_after_write_timeout()\n  {\n    return XCloseAfterWriteTimeout;\n  }\n\n  chunk_map& ChunkMap()\n  {\n    return XWriteAsyncChunks;\n  }\n\n  chunk_rmap& ChunkRMap()\n  {\n    return XReadAsyncChunks;\n  }\n\n  size_t nominal_read_ahead()\n  {\n    return XReadAheadNom;\n  }\n\n  void set_readahead_position(off_t pos)\n  {\n    mReadAheadPosition = pos;\n  }\n\n  void set_readahead_nominal(size_t size)\n  {\n    XReadAheadNom = size;\n  }\n\n  off_t readahead_position()\n  {\n    return mReadAheadPosition;\n  }\n\n  // ref counting\n  void attach();\n  size_t detach();\n  bool attached();\n  size_t get_attached();\n\n  bool isDeleted()\n  {\n    return mDeleted;\n  }\n\n  void setDeleted()\n  {\n    mDeleted = true;\n  }\n\n  std::string url()\n  {\n    return mUrl;\n  }\n\n  OpenFlags::Flags flags()\n  {\n    return mFlags;\n  }\n\n\n  Access::Mode mode()\n  {\n    return mMode;\n  }\n\n  int read_chunks_in_flight()\n  {\n    return mRChunksInFlight.load();\n  }\n\n  void clear_read_chunks_in_flight()\n  {\n    mRChunksInFlight.store(0, std::memory_order_seq_cst);\n  }\n\n  void inc_read_chunks_in_flight()\n  {\n    mRChunksInFlight.fetch_add(1, std::memory_order_seq_cst);\n  }\n\n  void dec_read_chunks_in_flight()\n  {\n    mRChunksInFlight.fetch_sub(1, std::memory_order_seq_cst);\n  }\n\n  static ssize_t chunk_timeout(ssize_t to = 0)\n  {\n    if (to) {\n      sChunkTimeout = to;\n    }\n\n    return sChunkTimeout;\n  }\n  static ssize_t\n  sChunkTimeout; // time after we move an inflight chunk out of a proxy object into the static map\n\n  Fuzzing& fuzzing()\n  {\n    return mFuzzing;\n  }\n\n\n  char const* Dump(std::string& out);\n\n  class Protocol\n  {\n  public:\n    Protocol() {}\n    virtual ~Protocol() {}\n    void Add(std::string);\n    const char* Dump(std::string& out);\n  private:\n    XrdSysMutex mMutex;\n    std::deque<std::string> mMessages;\n  };\n\n  Protocol& getProtocol()\n  {\n    return mProtocol;\n  }\n\n  static std::atomic<int> sProxy;\n  static int Proxies()\n  {\n    return sProxy;\n  }\n\n  class ProxyStat : public XrdSysMutex, public std::map<std::string, uint64_t>\n  {\n  public:\n    ProxyStat()\n    {\n      (*this)[\"recover:n\"] = 0;\n      (*this)[\"recover:read:exceeded\"] = 0;\n      (*this)[\"recover:write:disabled\"] = 0;\n      (*this)[\"recover:write:noproxy\"] = 0;\n      (*this)[\"recover:write:unrecoverable\"] = 0;\n      (*this)[\"recover:write:n\"] = 0;\n      (*this)[\"recover:read:disabled\"] = 0;\n      (*this)[\"recover:read:noproxy\"] = 0;\n      (*this)[\"recover:read:unrecoverble\"] = 0;\n      (*this)[\"recover:read:reopen:n\"] = 0;\n      (*this)[\"recover:read:reread:n\"] = 0;\n      (*this)[\"recover:read:reopen:disabled\"] = 0;\n      (*this)[\"recover:read:reopen:noserver:disabled\"] = 0;\n      (*this)[\"recover:read:reopen:failed\"] = 0;\n      (*this)[\"recover:read:reopen:success\"] = 0;\n      (*this)[\"recover:read:reopen:noserver:retry\"] = 0;\n      (*this)[\"recover:read:reopen:noserver:fatal\"] = 0;\n      (*this)[\"recover:write:reopen:n\"] = 0;\n      (*this)[\"recover:write:reopen:success\"] = 0;\n      (*this)[\"recover:write:reopen:disabled\"] = 0;\n      (*this)[\"recover:write:reopen:noserver::retry\"] = 0;\n      (*this)[\"recover:write:reopen:noserver::disabled\"] = 0;\n      (*this)[\"recover:write:reopen:unrecoverable\"] = 0;\n      (*this)[\"recover:write:reopen:overquota\"] = 0;\n      (*this)[\"recover:write:reopen:success\"] = 0;\n      (*this)[\"recover:write:reopen:nosever\"] = 0;\n      (*this)[\"recover:write:reopen:noserver:failed\"] = 0;\n      (*this)[\"recover:read:n\"] = 0;\n      (*this)[\"recover:read:success\"] = 0;\n      (*this)[\"recover:read:failed\"] = 0;\n      (*this)[\"recover:write:n\"] = 0;\n      (*this)[\"recover:write:unrecoverable\"] = 0;\n      (*this)[\"recover:write:fromcache\"] = 0;\n      (*this)[\"recover:write:fromremote\"] = 0;\n      (*this)[\"recover:write:fromcache:failed\"] = 0;\n      (*this)[\"recover:write:fromremote:local:failed\"] = 0;\n      (*this)[\"recover:write:fromcache:read:failed\"] = 0;\n      (*this)[\"recover:write:fromremote:read:failed\"] = 0;\n      (*this)[\"recover:write:fromremote:localwrite:failed\"] = 0;\n      (*this)[\"recover:write:fromremote:beginflush:failed\"] = 0;\n      (*this)[\"recover:write:fromremote:endflush:failed\"] = 0;\n      (*this)[\"recover:write:fromremote:write:failed\"] = 0;\n      (*this)[\"recover:write:journalflush:failed\"] = 0;\n      (*this)[\"recover:write:journalflush:success\"] = 0;\n      (*this)[\"recover:write:nocache:failed\"] = 0;\n    }\n  };\n\n  class ProxyStatHandle\n  {\n  public:\n    static std::shared_ptr<ProxyStatHandle> Get();\n    ProxyStatHandle()\n    {\n      sProxyStats.Lock();\n    }\n\n    ~ProxyStatHandle()\n    {\n      sProxyStats.UnLock();\n    }\n    ProxyStat& Stats()\n    {\n      return sProxyStats;\n    }\n    static ProxyStat sProxyStats;\n  };\n\n  std::string getLastUrl()\n  {\n    return mLastUrl;\n  }\n\n\nprivate:\n  std::atomic<OPEN_STATE> open_state;\n  struct timespec open_state_time;\n  XRootDStatus XOpenState;\n  OpenAsyncHandler XOpenAsyncHandler;\n  CloseAsyncHandler XCloseAsyncHandler;\n  XrdSysCondVar XOpenAsyncCond;\n  XrdSysCondVar XWriteAsyncCond;\n  XrdSysCondVar XReadAsyncCond;\n  chunk_map XWriteAsyncChunks;\n  chunk_rmap XReadAsyncChunks;\n\n  Fuzzing mFuzzing;\n\n  // this static map will take over chunks where we don't see callbacks in a reasonable time\n  static chunk_vector sTimeoutWriteAsyncChunks;\n  static chunk_rvector sTimeoutReadAsyncChunks;\n  static XrdSysMutex sTimeoutAsyncChunksMutex;\n\n\n  XRootDStatus XReadState;\n  XRootDStatus XWriteState;\n\n  std::deque<write_handler> XWriteQueue;\n  size_t XWriteQueueDirectSubmission;\n  size_t XWriteQueueScheduledSubmission;\n\n  bool XCloseAfterWrite;\n  uint16_t XCloseAfterWriteTimeout;\n\n  READAHEAD_STRATEGY XReadAheadStrategy;\n  size_t XReadAheadMin; // minimum ra block size when re-enabling\n  size_t XReadAheadNom; // nominal ra block size when\n  size_t XReadAheadMax; // maximum pow2 scaled window\n  size_t XReadAheadBlocksMin; // minimum number of prefetch blocks\n  size_t XReadAheadBlocksNom; // nominal number of prefetch blocks\n  size_t XReadAheadBlocksMax; // maximum number of prefetch blocks\n  size_t XReadAheadBlocksIs; // current blocks in the read-ahead\n  size_t XReadAheadReenableHits; // sequential read hits in a row\n  bool   XReadAheadDisabled; // one-off disabling of read-ahead\n  double XReadAheadSparseRatio; // sparse ratio when we permanently disable read-ahead\n  off_t mPosition;\n  off_t mReadAheadPosition;\n  off_t mTotalBytes;\n  off_t mTotalReadAheadHitBytes;\n  off_t mTotalReadAheadBytes;\n  off_t mReadAheadMaximumPosition;\n  off_t mSeqDistance;\n  XrdSysMutex mAttachedMutex;\n  size_t mAttached;\n  fuse_req_t mReq;\n  uint64_t mIno;\n  fuse_id mId;\n\n  std::string mUrl;\n  std::string mLastUrl;\n  std::string mReconUsername;\n  UserCredentials mUc;\n  uint64_t mConnId;\n\n  OpenFlags::Flags mFlags;\n  Access::Mode mMode;\n  uint16_t mTimeout;\n\n  std::atomic<int> mRChunksInFlight;\n\n  Protocol mProtocol;\n  bool mDeleted;\n};\n\n}\n\n#endif /* FUSE_XRDCLPROXY_HH_ */\n"
  },
  {
    "path": "fusex/eoscfsd/README.md",
    "content": "eoscfsd\n========\n\neoscfsd is a high-performance pass-through implementation for POSIX filesystems. It adds CERN.CH kerberos authentication and remote configuration and mount key obfuscation. \n\neoscfsd fetches the mount instructions for a named mount from a configurable HTTPS server. \n\nConfiguration File\n------------------\nThe configuration file is located at `/etc/eos/cfsd/eoscfsd.conf`.\n\nAn example file looks like this:\n```json\n{\n  \"testmount\" : {\n     \"server\" : \"testmount-server.cern.ch\"\n  },\n  ...\n}\n````\n\nThe mount named `testmount` will fetch the mount instruction from HTTPS server `testmount-server.cern.ch`. The mount instruction has to be stored on this webserver on the top-level directory `.../html/testmount.key`. If you want to store the key in some subdirectory you can add the directory to the server name e.g. `testmount-server.cern.ch/cfsd-keys/`. \n\nSeveral named mounts can be configured pointing to identical or individual HTTPS server.\n\nFor each named mount (e.g. `testmount`) an unlock key for the mount instruction has to be installed on the client node under `/etc/eos/cfsd/[name].key` (e.g. `/etc/eos/cfsd/testmount.key).\n\nIf the HTTPS server is an empty string, eoscfsd will use a mount instruction defined at compile time. \n\nHow to create a mount instruction?\n----------------------------------\n\neoscfsd requires a mount instruction mounting any POSIX filesystem to /@eoscfsd.\n\nFirst we create an unlock key:\n`uuidgen > /etc/eos/cfsd/nfs.key ; chmod 400 /etc/eos/cfsd/nfs.key`\nE.g. if we want to user kerberos identities on a non-kerberized NFS mount, we can create a mount instruction like this:\n\n```\neos daemon seal \"sudo mount -t nfs -o vers=4 nfsserver.cern.ch:/nfsshare/ /@eoscfsd/\" `cat /etc/eos/cfsd/nfs.key` >& /tmp/nfs.key\n```\n\nWe upload `/tmp/nfs.key` to our key HTTPS server. \n\nNow we can run an eoscfsd mount using the syntax `eoscfsd <mount-point> <mount-name>`:\n```\nmkdir -p /cern/nfs\neoscfsd /cern/nfs nfs\n```\n\nWhen eoscfsd starts, it will have some verbose output about fetching the mount instruction from the configured HTTPS server. By default, eoscfsd always lists the top-level directory after running the mount instruction.\n\n```\n[root@node ] eoscfsd /test/ test\n# cleanup: old mounts\n# unsharing\n# re-mounting\n# mounting test\n*   Trying xxx.xxx.xxx.xxx:443...\n* Connected to xyz (xxx.xxx.xxx.xxx) port 443 (#0)\n* ALPN, offering h2\n* ALPN, offering http/1.1\n*  CAfile: /etc/pki/tls/certs/ca-bundle.crt\n* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384\n* ALPN, server accepted to use h2\n* Server certificate:\n*  subject: CN=xyz\n*  start date: Oct 21 14:31:18 2022 GMT\n*  expire date: Nov 24 14:31:18 2024 GMT\n*  subjectAltName: host \"xyz\" matched cert's \"xyz\"\n*  issuer: DC=ch; DC=cern; CN=CERN Certification Authority\n*  SSL certificate verify ok.\n* Using HTTP2, server supports multi-use\n* Connection state changed (HTTP/2 confirmed)\n* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0\n* Using Stream ID: 1 (easy handle 0x7f5e49c8d800)\n> GET /test.key HTTP/2\nHost: xyz\naccept: */*\n\n* old SSL session ID is stale, removing\n* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!\n< HTTP/2 200 \n< server: nginx/1.20.1\n< date: Mon, 29 Jan 2024 08:58:34 GMT\n< content-type: application/octet-stream\n< content-length: 346\n< last-modified: Tue, 23 Jan 2024 14:57:49 GMT\n< etag: \"65afd3ed-15a\"\n< accept-ranges: bytes\n< \n* Connection #0 to host xyz left intact\ninfo: ... mounting backends ...info: ... backends mounted ...total 10\ndrwxr-xr-x. 11 root       root   11 Jan 24 17:53 .\ndr-xr-xr-x. 37 root       root 4096 Jan 23 15:58 ..\ndrwxr-xr-x.  4 root       root    2 Jan 24 18:07 .proc\n\n```\n\nIt is possible to have a single key for all mounts. In this case don't deploy local keys in `/etc/eos/cfsd/name.key` but use the catch all default key `/etc/eos/cfsd/cfsd.key`. When the named key does not exists, it will automatically fall back to the default key.\n\nConfiguring Kerberos mapping in the backend filesystem\n------------------------------------------------------\n\nBy default every access to the eoscfsd mount is mapping to uid:gid=99/99. Every user mapping has to be explicitely configured in the configuration subtree inside the backend filesystem, which is stored in the root of the mount under `.cfsd/`.\n\nWe are adding the kerberos name `testprod` and want to map it to uid:gid=100123:1100\n\n```\nmkdir -p <mount>/.cfsd/mapping/name\ntouch <mount>/.cfsd/mapping/name/testprod\nchown 100123:1100 <mount>/.cfsd/mapping/name/testprod\n```\n\nEvery access using kerberos name `testprod` will now be mapped to 100123:1100 on the backend filesystem. It is also possible to map a kerberos name to uid:gid=0:0 aka `root` by setting the owner on the mapping name to 0:0!\n\nBe aware that mappings are cached for maximum 60 seconds and changes will be applied within a time window of maximum 60s.\n\nConfiguration Quota in the backend filesystem\n----------------------------------------------\nBy default even if mapping is enabled and the mapped user should have quota and permissions in the backend filesystem, eoscfsd does not allow write access unless quota is enabled for the given uid or gid. There is a simple `yes` or `no` configuration for a given user and or group. \n\n```\nmkdir -p <mount>/.cfsd/quota\nmkdir -p <mount>/.cfsd/quota/user/\nmkdir -p <mount>/.cfsd/quota/group/\n# quota for user 100123\ntouch <mount>/.cfsd/quota/user/100123\n# or quota for group 1100\ntouch <mount>/.cfsd/quota/group/1100\n```\n\nBe aware the quota settings are cached for a maximum of 60s and changes will be applied within a time window of maximum 60s. If you want to disallow writing for a given user/group, you remove the corresponding entry e.g.\n```\nunlink <mount>/.cfsd/quota/user/100123\n```\n\nVirtual attributes in eoscfsd mounts\n------------------------------------\n\nIf you get the virtual attribte `cfs.id`, you will get as a response the current mapping and quota status of the calling process e.g.\n\n```\ngetfattr -n cfs.id /test/\ngetfattr: Removing leading '/' from absolute path names\n# file: test/\ncfs.id=\"name: testprod uid:100123 gid:1100 quota:1\"\n```\n\nThis allows to debug quota/mapping problems.\n\neoscfsd recycle bin\n-------------------\n\neoscfsd supports deletion trhough a recycle bin. It is still in beta state and will be documented when the feature has evolved for production.\n\nautofs configuration\n--------------------\n\nBy default eoscfsd mounts are mounted unde /cern/. This can be chaned in /etc/auto.master.d/cfsd.autofs\nThe mount configration has to be defined in the map file /etc/auto.cfsd\n\nEntries look like e.g. mounting a cfsd filesystem named 'default':\ndefault -fstype=eoscfs :default\n\nThe mount will appear under /cern/default in this case.\n\n"
  },
  {
    "path": "fusex/eoscfsd/cfs.sh",
    "content": "#!/bin/bash\n\nCFSMOUNT=\"/cern/home/\"\nCFSPROC=\"/cern/home/.cfsd/\"\nalias rm=\"cfs_rm\"\n\nfunction cfs() {\n    if [ \"$1\" == \"recycle\" ]; then\n\tshift\n\tcfs_recycle $@\n\treturn \n    fi\n}\n\nfunction cfs_rm() {\n\n    if [ \"$1\" != \"-rf\" ]; then\n\teval '/bin/rm $@'\n\treturn\n    fi\n\n    rpath=`realpath $2`;\n    dpath=`dirname $rpath`;\n    bpath=`basename $2`;\n    if [ ! -e \"$rpath\" ]; then\n       eval '/bin/rm $@'\n       return\n    fi\n       \n    CFSMOUNTLEN=${#CFSMOUNT}\n    CFSPROCLEN=${#CFSPROC}\n    crpath=${rpath:0:$CFSMOUNTLEN}\n    prpath=${rpath:0:$CFSPROCLEN}\n    if [ \"$crpath\" != \"$CFSMOUNT\" ]; then\n\teval '/bin/rm $@'\n\treturn\n    fi\n\n    if [ \"$prpath\" == \"$CFSPROC\" ]; then\n\teval '/bin/rm $@'\n\treturn\n    fi\n    \n    cfs_checkcfs user_id\n\n    if [ \"${user_id}\" == \"\" ]; then\n\teval '/bin/rm $@'\n\treturn \n    fi\n    \n    uid=`echo $user_id | tr ' ' '\\n' | grep uid | cut -d \":\" -f2`;\n    if [ $uid == \"\" ]; then\n\treturn ;\n    fi\n    recyclepath=\"$CFSMOUNT/.cfsd/recycle/uid:$uid/\"\n\n    year=`date +\"%Y\"`\n    month=`date +\"%m\"`\n    day=`date +\"%d\"`\n\n    \n    if [ -e \"$recyclepath/$year/$month/$day\" ]; then\n\t# good\n\techo -n \"\"\n    else\n\tif [ -d \"$rpath\" ]; then\n\t    # trigger creation of recycle dirs\n\t    touch \"$rpath/.delete\";\n\t    rm \"$rpath/.delete\";\n\telse\n\t    # trigger creation of recycle dirs\n\t    touch \"$dpath/.delete\";\n\t    rm \"$dpath/.delete\";\n\tfi\n    fi\n\n    if [ -e \"$recyclepath/$year/$month/$day\" ]; then\n\tinode=`stat -c '%i' $dpath`;\n\ttargetdir=\"$recyclepath/$year/$month/$day/$inode.#_recycle_#/\"\n\ttargetpath=\"$recyclepath/$year/$month/$day/$inode.#_recycle_#/$bpath\"\n\tmkdir -p $targetdir\n\techo \"# Doing fast deletion into recycle bin: restorepath='$rpath'\"\n\tmv \"$rpath\" \"$targetpath\"\n    else\n\teval '/bin/rm $@'\n    fi\n}\n\nfunction cfs_checkcfs() {\n    userinfo=`getfattr -n cfs.id $CFSMOUNT 2> /dev/null | grep cfs.id | cut -d \"=\" -f 2 | sed s/\\\"//g`;\n    if [ $? -ne 0 ]; then\n\t$1=\"\";\n    fi\n    eval $1='$userinfo';\n}\n\nfunction cfs_byte_conversion() {\n    if [ $1 -ge 1000000000000 ]; then\n\techo -n \"$1\" | awk '{printf(\"%.02f TB\",$1/1000000000000);}'\n\treturn\n    fi\n\n    if [ $1 -ge 1000000000 ]; then\n\techo -n \"$1\" | awk '{printf(\"%.02f GB\",$1/1000000000);}'\n\treturn\n    fi\n\n    if [ $1 -ge 1000000 ]; then\n\techo -n \"$1\" | awk '{printf(\"%.02f MB\",$1/1000000);}'\n\treturn\n    fi\n    echo -n \"$1 Bytes\"\n}\n\nfunction cfs_recycle() {\n    cfs_checkcfs user_id\n    \n    if [ \"${user_id}\" == \"\" ]; then\n\treturn ;\n    fi\n    uid=`echo $user_id | tr ' ' '\\n' | grep uid | cut -d \":\" -f2`;\n    if [ $uid == \"\" ]; then\n\treturn ;\n    fi\n    recyclepath=\"$CFSMOUNT/.cfsd/recycle/uid:$uid/\"\n    \n    year=`date +\"%Y\"`\n    month=`date +\"%m\"`\n    day=`date +\"%d\"`\n\n    if [ \"$1\"  == \"purge\" ]; then\n\techo \"# Purging recycle bin for '$user_id' ...\";\n\trm -r $recyclepath 2> /dev/null\n\techo \"# done!\";\n\treturn\n    fi\n    \n    if [ \"$1\"  == \"info\" ]; then\n\tndirs=`getfattr -n ceph.dir.rsubdirs $recyclepath 2> /dev/null | grep ceph.dir.rsubdirs | cut -d \"=\" -f 2 | sed s/\\\"//g`\n\tif [ $? -ne 0 ]; then ndirs=0;fi \n\tnfiles=`getfattr -n ceph.dir.rfiles $recyclepath 2> /dev/null | grep ceph.dir.rfiles | cut -d \"=\" -f 2 | sed s/\\\"//g`\n\tif [ $? -ne 0 ]; then nfiles=0;fi  \n\tbytes=`getfattr -n ceph.dir.rbytes $recyclepath 2> /dev/null | grep ceph.dir.rbytes | cut -d \"=\" -f 2 | sed s/\\\"//g`\n\tif [ $? -ne 0 ]; then bytes=0; fi\n\techo \"# Recycle bin for '$user_id' : \"\n\techo \"#                              files       :    $nfiles\"\n\techo \"#                              directories :    $ndirs\"\n\techo -n \"#                              size        :    \"\n\tcfs_byte_conversion $bytes\n\techo \"\"\n\treturn\n    fi\n\n    if [ \"$1\"  == \"ls\" ]; then\n\tfind $recyclepath -type f\n\treturn\n    fi\n\n    if [ \"$1\" == \"restore\" ]; then\n\trestoreinput=$2;\n\trestoretarget=`dirname $restoreinput`;\n\trestoredir=`basename $restoreinput`;\n\tif [ ! -d \"$restoretarget\" ]; then\n\t    echo \"error: you have to provide the restore directory path as first argument!\";\n\t    return -1;\n\tfi\n\tinode=`stat -c '%i' $restoretarget`;\n\t   \n\ttargetpath=\"$recyclepath/$year/$month/$day/$inode.#_recycle_#/\"\n\tif [ -d \"$targetpath/$restoredir\" ]; then\n\t    echo \"# Restoring $restoredir to $restoreinput!\";\n\t    mv $targetpath $restoreinput\n\t    return\n\telse\n\t    echo \"error: I cannot find the directory you want to restore!\"\n\t    return -1\n\tfi\n    fi\n}\n\n"
  },
  {
    "path": "fusex/eoscfsd/cfskey.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file cfskey.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief Class providing the quota en-/disabling\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include <string>\n#include <curl/curl.h>\n#include <curl/easy.h>\n\nclass cfskey\n{\npublic:\n\n  static std::size_t\n  callback(const char* in, std::size_t size,\n           std::size_t num, std::string* out)\n  {\n    const std::size_t totalBytes(size * num);\n    out->append(in, totalBytes);\n    return totalBytes;\n  }\n\n\n  static std::string get(std::string resource)\n  {\n    std::string httpsresource = std::string(\"https://\") + resource;\n    auto curl = curl_easy_init();\n    curl_easy_setopt(curl, CURLOPT_URL, httpsresource.c_str());\n    long httpCode(0);\n    std::unique_ptr<std::string> httpData(new std::string());\n    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback);\n    curl_easy_setopt(curl, CURLOPT_WRITEDATA, httpData.get());\n    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);\n    curl_easy_perform(curl);\n    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);\n    curl_easy_cleanup(curl);\n\n    if (httpCode == 200) {\n      return *httpData;\n    } else {\n      return \"\";\n    }\n  }\nprivate:\n};\n"
  },
  {
    "path": "fusex/eoscfsd/cfslogin.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file cfslogin.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief Class providing username, executable from process/credentials\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"cfslogin.hh\"\n#include \"cfsmapping.hh\"\n#include \"auth/Logbook.hh\"\n#include \"common/Logging.hh\"\n#include \"common/RegexWrapper.hh\"\n#include <algorithm>\n\nstd::unique_ptr<AuthenticationGroup> cfslogin::authGroup;\nstd::unique_ptr<cfsmapping> cfslogin::cfsMap;\n\nProcessCache* cfslogin::processCache = nullptr;\nstd::string cfslogin::k5domain = \"@CERN.CH\";\n\nnamespace\n{\nstd::string sSafeRegex {\"[/\\\\w.]+\"};\n}\n\nvoid cfslogin::initializeProcessCache(const CredentialConfig& config)\n{\n  authGroup.reset(new AuthenticationGroup(config));\n  processCache = authGroup->processCache();\n  cfsMap.reset(new cfsmapping());\n}\n\nstd::string cfslogin::fillExeName(const std::string& execname)\n{\n  auto base_name = [](std::string const & path) {\n    return path.substr(path.find_last_of(\"/\\\\\") + 1);\n  };\n  std::string exe = execname;\n\n  if (execname.length() > 32) {\n    exe = base_name(execname);\n  }\n\n  if (eos::common::eos_regex_match(exe, sSafeRegex)) {\n    return exe;\n  } else {\n    std::string base64_string = \"base64\";\n    std::string base64in = exe;\n    eos::common::SymKey::Base64(base64in, base64_string);\n    return base64_string;\n  }\n}\n\n\nstd::string cfslogin::executable(fuse_req_t req)\n{\n  Logbook logbook(true);\n  ProcessSnapshot snapshot =\n    (fuse_req_ctx(req)->pid) ? processCache->retrieve(fuse_req_ctx(req)->pid,\n        fuse_req_ctx(req)->uid, fuse_req_ctx(req)->gid,\n        false, logbook) : 0;\n\n  if (snapshot) {\n    return fillExeName(snapshot->getExe());\n  } else {\n    return \"unknown\";\n  }\n}\n\nstd::string cfslogin::secret(fuse_req_t req)\n{\n  ProcessSnapshot snapshot = processCache->retrieve(fuse_req_ctx(req)->pid,\n                             fuse_req_ctx(req)->uid, fuse_req_ctx(req)->gid,\n                             false);\n\n  if (snapshot) {\n    return snapshot->getBoundIdentity()->getCreds()->getKey();\n  }\n\n  return \"\";\n}\n\nstd::string cfslogin::name(fuse_req_t req)\n{\n  ProcessSnapshot snapshot = processCache->retrieve(fuse_req_ctx(req)->pid,\n                             fuse_req_ctx(req)->uid, fuse_req_ctx(req)->gid,\n                             false);\n  std::string username = \"nobody\";\n\n  if (snapshot) {\n    username = snapshot->getBoundIdentity()->getCreds()->toUserName();;\n  }\n\n  size_t adpos = 0;\n\n  if ((adpos = username.find(\"@\")) != std::string::npos) {\n    if (username.find(cfslogin::k5domain) == std::string::npos) {\n      return \"nobody\";\n    } else {\n      username.erase(adpos);\n    }\n  }\n\n  return username;\n}\n\nstd::string cfslogin::translate(fuse_req_t req, uid_t& uid, gid_t& gid)\n{\n  std::string name = cfslogin::name(req);\n  cfsMap->translate(name, uid, gid);\n  return name;\n}\n\n\n"
  },
  {
    "path": "fusex/eoscfsd/cfslogin.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file cfslogin.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief Class providing the username, executable from process/credentials\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n#define FUSE_USE_VERSION 35\n\n#include <fuse3/fuse_lowlevel.h>\n#include \"auth/AuthenticationGroup.hh\"\n#include \"auth/ProcessCache.hh\"\n#include \"common/SymKeys.hh\"\n#include \"cfsmapping.hh\"\n#include <memory>\n\nclass cfslogin\n{\npublic:\n  static std::string fillExeName(const std::string& exename);\n\n  static std::string executable(fuse_req_t);\n\n  static std::string secret(fuse_req_t req);\n\n  static std::string name(fuse_req_t req);\n\n  static std::string translate(fuse_req_t req, uid_t& uid, gid_t& gid);\n\n  static void initializeProcessCache(const CredentialConfig& config);\n  static std::unique_ptr<AuthenticationGroup> authGroup;\n  static ProcessCache* processCache; // owned by authGroup\n  static std::unique_ptr<cfsmapping> cfsMap;\n  static std::string k5domain;\n};\n"
  },
  {
    "path": "fusex/eoscfsd/cfsmapping.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file cfsmapping.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief Class providing the username mapping to uid/gid with configurion on fs\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\nclass cfsmapping\n{\npublic:\n  cfsmapping(const char* path = \"/@eoscfsd/.cfsd/mapping/name/\") : namepath(\n      path) {};\n  void translate(std::string& name, uid_t& uid, gid_t& gid)\n  {\n    time_t now = time(NULL);\n    auto entry = namemap.find(name);\n\n    if ((entry != namemap.end()) && (now < entry->second.valid)) {\n      // serve from cache\n      uid = entry->second.uid;\n      gid = entry->second.gid;\n      return;\n    }\n\n    // refresh from fs\n    std::string lookup = namepath + std::string(\"/\") + name;\n    struct stat buf;\n\n    if (!::lstat(lookup.c_str(), &buf)) {\n      uid = buf.st_uid;\n      gid = buf.st_gid;\n    } else {\n      uid = 99;\n      gid = 99;\n    }\n\n    // store in cache for 60s\n    namemap[name].valid = now + 60;\n    namemap[name].uid = uid;\n    namemap[name].gid = gid;\n  }\n\n  class mapentry\n  {\n  public:\n    mapentry() {}\n    mapentry(time_t v, uid_t u, gid_t g) : valid(v), uid(u), gid(g) {}\n    time_t valid;\n    uid_t uid;\n    gid_t gid;\n  };\n\nprivate:\n  std::string namepath;\n  std::map<std::string, mapentry> namemap;\n};\n"
  },
  {
    "path": "fusex/eoscfsd/cfsquota.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file cfsquota.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief Class providing the quota en-/disabling\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\nclass cfsquota\n{\npublic:\n  cfsquota(const char* uqpath = \"/@eoscfsd/.cfsd/quota/user/\",\n           const char* gqpath = \"/@eoscfsd/.cfsd/quota/group/\") : userquotapath(uqpath),\n    groupquotapath(gqpath)  {};\n\n\n  bool hasquota(uid_t uid, gid_t gid)\n  {\n    time_t now = time(NULL);\n    // fprintf(stderr,\"lookup for %d %d\\n\", uid,gid);\n    // user quota\n    {\n      auto entry = userquotamap.find(uid);\n\n      if ((entry != userquotamap.end()) && (now < entry->second.valid)) {\n        // serve from cache\n        return true;\n      }\n\n      // refresh from fs\n      std::string lookup = userquotapath + std::string(\"/\") + std::to_string((\n                             int)uid);\n      struct stat buf;\n\n      if (!::lstat(lookup.c_str(), &buf)) {\n        userquotamap[uid].valid = now + 60;\n        return true;\n      } else {\n        userquotamap.erase(uid);\n      }\n    }\n    // group quota\n    {\n      auto entry = groupquotamap.find(gid);\n\n      if ((entry != groupquotamap.end()) && (now < entry->second.valid)) {\n        // serve from cache\n        return true;\n      }\n\n      // refresh from fs\n      std::string lookup = groupquotapath + std::string(\"/\") + std::to_string((\n                             int)gid);\n      struct stat buf;\n\n      if (!::lstat(lookup.c_str(), &buf)) {\n        groupquotamap[gid].valid = now + 60;\n        return true;\n      } else {\n        groupquotamap.erase(gid);\n      }\n    }\n    return false;\n  }\n\n  class quotaentry\n  {\n  public:\n    quotaentry() {}\n    quotaentry(time_t v) : valid(v) {}\n    time_t valid;\n  };\n\nprivate:\n  std::string userquotapath;\n  std::string groupquotapath;\n\n  std::map<uid_t, quotaentry> userquotamap;\n  std::map<gid_t, quotaentry> groupquotamap;\n};\n"
  },
  {
    "path": "fusex/eoscfsd/cfsrecycle.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file cfsrecycle.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief Class providing username, executable from process/credentials\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"cfsrecycle.hh\"\n#include <unistd.h>\n#include \"common/Path.hh\"\n#include \"eoscfsd.hh\"\n\nint\ncfsrecycle::provideBin(uid_t uid, ino_t ino)\n{\n  std::string binPath = recyclepath + std::string(\"/\") + std::to_string(uid);\n  {\n    FsID rootId(0, 0);\n    char srecycleuser[4096];\n    time_t now = time(NULL);\n    struct tm nowtm;\n    localtime_r(&now, &nowtm);\n    size_t index = ino;\n    snprintf(srecycleuser, sizeof(srecycleuser) - 1,\n             \"%s/uid:%u/%04u/%02u/%u/%lu.#_recycle_#/\",\n             recyclepath.c_str(),\n             uid,\n             1900 + nowtm.tm_year,\n             nowtm.tm_mon + 1,\n             nowtm.tm_mday,\n             index);\n    struct stat buf;\n    std::cerr << \"# recycle \" << srecycleuser << std::endl;\n\n    // if i_index is not -1, we just compute the path for the given index and return if it exists already\n    if (!::stat(srecycleuser, &buf)) {\n      // great that exists already\n    } else {\n      // create the path\n      std::string mpath = std::string(srecycleuser) + std::string(\"/dummy\");\n      eos::common::Path cpath(mpath.c_str());\n      eos::common::Path ppath(srecycleuser);\n\n      if (!cpath.MakeParentPath(S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) {\n        std::cerr << \"error: failed to create recycle bin path '\" << cpath.GetPath() <<\n                  \"'\\n\";\n        return -1;\n      }\n\n      if (chown(srecycleuser, uid, 0)) {\n        std::cerr << \"error: failed to chown reyccle bin path '\" << cpath.GetPath() <<\n                  \"'\\n\";\n        return -1;\n      }\n\n      if (chown(ppath.GetParentPath(), uid, 0)) {\n        std::cerr << \"error: failed to chown reyccle bin path '\" << cpath.GetPath() <<\n                  \"'\\n\";\n        return -1;\n      }\n\n      if (chmod(srecycleuser, S_IRWXU | S_IRGRP | S_IXGRP)) {\n        std::cerr << \"error: failed to chmod recycle bin path '\" << cpath.GetPath() <<\n                  \"'\\n\";\n        return -1;\n      }\n\n      if (chmod(ppath.GetParentPath(), S_IRWXU | S_IRGRP | S_IXGRP)) {\n        std::cerr << \"error: failed to chmod recycle bin path '\" << cpath.GetPath() <<\n                  \"'\\n\";\n        return -1;\n      }\n    }\n\n    return ::open(srecycleuser, O_PATH | O_NOFOLLOW);\n  }\n}\n\nint\ncfsrecycle::moveBin(uid_t uid, ino_t parent, int source_fd, const char* name)\n{\n  struct stat buf;\n\n  if (::fstat(source_fd, &buf)) {\n    return -1;\n  }\n\n  int target_fd = provideBin(uid, buf.st_ino);\n\n  if (target_fd >= 0) {\n    struct stat buf;\n    ::fstatat(source_fd, name, &buf, AT_SYMLINK_NOFOLLOW);\n    std::string newname = std::string(name) + std::string(\".\") + std::to_string((\n                            unsigned long) buf.st_ino) + std::string(\".#_recycle_#\");\n    int rc = ::renameat(source_fd, name, target_fd, newname.c_str());\n    rc |= close(target_fd);\n    return rc;\n  } else {\n    return -1;\n  }\n}\n\nbool\ncfsrecycle::shouldRecycle(uid_t uid, ino_t parent, int source_fd,\n                          const char* name)\n{\n  if (std::string(name).find(\".#_recycle_#\") == std::string::npos) {\n    return true;\n  } else {\n    return false;\n  }\n}\n"
  },
  {
    "path": "fusex/eoscfsd/cfsrecycle.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file cfsrecycle.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief Class providing the quota en-/disabling\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include <string>\n\nclass cfsrecycle\n{\npublic:\n  cfsrecycle(const char* rpath = \"/@eoscfsd/.cfsd/recycle/\") : recyclepath(\n      rpath) {};\n\n  int provideBin(uid_t uid, ino_t parent);\n  int moveBin(uid_t uid, ino_t parent, int source_fd, const char* name);\n  bool shouldRecycle(uid_t uid, ino_t parent, int source_fd, const char* name);\nprivate:\n  std::string recyclepath;\n};\n"
  },
  {
    "path": "fusex/eoscfsd/cfsutil.hh",
    "content": " //------------------------------------------------------------------------------\n//! @file cfsutil.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief Class providing the quota en-/disabling\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include <string>\n#include <iostream>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n#include <errno.h>\n#include <cstring>\n\nclass cfsutil\n{\npublic:\n  static bool checkAndCreateDirectory(const std::string& path) {\n    struct stat info;\n    \n    // Check if the directory exists\n    if (stat(path.c_str(), &info) != 0) {\n      if (errno == ENOENT) {\n\tstd::cerr << \"info: Directory does not exist. Creating directory: \" << path << std::endl;\n\t// Create the directory\n\tif (mkdir(path.c_str(), 0500) != 0) {\n\t  std::cerr << \"error: could not create directory: \" << strerror(errno) << std::endl;\n\t  return false;\n\t}\n\tstd::cerr << \"info: directory created and permissions set to 500.\" << std::endl;\n      } else {\n\tstd::cerr << \"error: could not check for directory ' \" << path << \"' : \" << strerror(errno) << std::endl;\n\treturn false;\n      }\n    } else {\n      if (S_ISDIR(info.st_mode)) {\n\t// Check the permissions\n\tif ((info.st_mode & 0777) == 0500) {\n\t} else {\n\t  std::cerr << \"info: directory exists but permissions are not 500. Setting permissions to 500 on directory '\" << path << \"'\" << std::endl;\n\t  // Set the permissions to 500\n\t  if (chmod(path.c_str(), 0500) != 0) {\n\t    std::cerr << \"error: failed to set permissions: \" << strerror(errno) << std::endl;\n\t    return false;\n\t  }\n\t  std::cerr << \"info: permissions set to 500 on directory '\" << path << \"'\" << std::endl;\n\t}\n      } else {\n\tstd::cerr << \"error: path exists but is not a directory '\" << path << \"'\"<< std::endl;\n\treturn false;\n      }\n    }\n    std::cerr << std::flush;\n    return true;\n  }\nprivate:\n};\n"
  },
  {
    "path": "fusex/eoscfsd/cfsvattr.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file cfsvattr.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief Class providing the quota en-/disabling\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include <string>\n\nclass cfsvattr\n{\npublic:\n  static std::string vattr(std::string key, std::string name, uid_t uid,\n                           gid_t gid, bool quota)\n  {\n    if (key == \"cfs.id\") {\n      std::string id = \"name: \";\n      id += name;\n      id += \" uid:\";\n      id += std::to_string(uid);\n      id += \" gid:\";\n      id += std::to_string(gid);\n      id += \" quota:\";\n      id += std::to_string(quota);\n      return id;\n    }\n\n    return \"\";\n  }\nprivate:\n};\n"
  },
  {
    "path": "fusex/eoscfsd/eoscfsd.cc",
    "content": "//! @file eoscfsd.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief EOS ClientFS C++ Fuse low-level implementation\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ***********************************************************************/\n\n#include \"eoscfsd.hh\"\n#include \"obfuscate.hh\"\n#include \"cfsutil.hh\"\n#include \"common/Untraceable.hh\"\n#include \"common/Path.hh\"\n#include \"common/StacktraceHere.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Timing.hh\"\n#include \"common/LinuxMemConsumption.hh\"\n#include \"common/LinuxTotalMem.hh\"\n#include \"common/LinuxStat.hh\"\n#include <stdio.h>\n#include <json/json.h>\n\nusing namespace std;\n\n\nstatic void maximize_fd_limit()\n{\n  struct rlimit lim {};\n  auto res = getrlimit(RLIMIT_NOFILE, &lim);\n\n  if (res != 0) {\n    warn(\"WARNING: getrlimit() failed with\");\n    return;\n  }\n\n  lim.rlim_cur = lim.rlim_max;\n  res = setrlimit(RLIMIT_NOFILE, &lim);\n\n  if (res != 0) {\n    warn(\"WARNING: setrlimit() failed with\");\n  }\n}\n\nstatic void maximize_priority()\n{\n  if (setpriority(PRIO_PROCESS, getpid(), -PRIO_MAX / 2) < 0) {\n    fprintf(stderr,\n            \"error: failed to renice this process '%u', to maximum priority '%d'\\n\",\n            getpid(), -PRIO_MAX / 2);\n  }\n}\n\n\nstatic void print_usage(char* prog_name)\n{\n  cout << \"Usage: \" << prog_name << \" --help\\n\"\n       << \"       \" << prog_name << \" [options] <mountpoint> [<name>]\\n\";\n  cout << \"options:\\n\";\n  cout << \"         -d    --debug       Enable filesystem debug messages\\n\";\n  cout << \"               --debug-fuse  Enable libfuse debug messages\\n\";\n  cout << \"         -h    --help        Print help\\n\";\n  cout << \"               --nosplice    Do not use splice(2) to transfer data\\n\";\n  cout << \"         -s    --single      Run single-threaded\\n\";\n  cout << \"         -f    --foreground  Run in foreground\\n\";\n  cout << \"         -r    --recycle     Run with recycling bin\\n\";\n  cout << \"         -e    --embedded    Use an embedded key\\n\";\n}\n\n\nstatic std::set<std::string> parse_options(int argc, char** argv)\n{\n  std::set<std::string> options;\n  std::string mountpath = \"\";\n  std::string mountname = \"\";\n\n  for (int i = 1 ; i < argc; ++i) {\n    std::string args = argv[i];\n\n    if (args == std::string(\"-o\")) {\n      i++;\n      continue;\n    } else {\n      if (args.substr(0, 2) == std::string(\"-o\")) {\n        continue;\n      }\n    }\n\n    if (args.substr(0, 1) == \"-\") {\n      if ((args == \"-h\") || (args == \"--help\")) {\n        print_usage(argv[0]);\n        exit(0);\n      }\n\n      if ((args == \"--debug\") || (args == \"-d\")) {\n        options.insert(\"debug\");\n      } else if ((args == \"--debug-fuse\")) {\n        options.insert(\"debug-fuse\");\n      } else if ((args == \"--nosplice\")) {\n        options.insert(\"nosplice\");\n      } else if ((args == \"--single\") || (args == \"-s\")) {\n        options.insert(\"single\");\n      } else if ((args == \"-f\") || (args == \"--foreground\")) {\n        options.insert(\"foreground\");\n      } else if ((args == \"-r\") || (args == \"--recycle\")) {\n        options.insert(\"recycle\");\n      } else if ((args == \"-e\") || (args == \"--embedded\")) {\n        options.insert(\"embedded\");\n      } else {\n        print_usage(argv[0]);\n        exit(0);\n      }\n    } else {\n      if (mountpath.empty()) {\n        mountpath = args;\n      } else {\n        if (mountname.empty()) {\n          mountname = args;\n        } else {\n          print_usage(argv[0]);\n          exit(-1);\n        }\n      }\n    }\n  }\n\n  if (mountpath.empty()) {\n    print_usage(argv[0]);\n    exit(-1);\n  }\n\n  fs.debug = options.count(\"debug\") != 0;\n  fs.nosplice = options.count(\"nosplice\") != 0;\n  fs.recyclebin = options.count(\"recycle\") != 0;\n  fs.mount = mountpath;\n  fs.name = mountname;\n  fs.foreground = options.count(\"foreground\") != 0;\n\n  if (options.count(\"embedded\")) {\n    fs.keyresource = \"\";\n  }\n\n  return options;\n}\n\n\nstatic Inode& get_inode(fuse_ino_t ino)\n{\n  if (ino == FUSE_ROOT_ID) {\n    return fs.root;\n  }\n\n  Inode* inode = reinterpret_cast<Inode*>(ino);\n\n  if (inode->fd == -1) {\n    cerr << \"INTERNAL ERROR: Unknown inode \" << ino << endl;\n    abort();\n  }\n\n  return *inode;\n}\n\n\nstatic int get_fs_fd(fuse_ino_t ino)\n{\n  int fd = get_inode(ino).fd;\n  return fd;\n}\n\n\nstatic void\n/* -------------------------------------------------------------------------- */\numount()\n/* -------------------------------------------------------------------------- */\n{\n  static char systemline[4096];\n  snprintf(systemline, sizeof(systemline),\n           \"umount -fl %s >& /dev/null; fusermount -u -z %s >& /dev/null\",\n           fs.source.c_str(),\n           fs.mount.c_str()\n          );\n  system(systemline);\n  fprintf(stderr, \"# cleanup: old mounts\\n\");\n}\n\nstatic void sfs_init(void* userdata, fuse_conn_info* conn)\n{\n  (void)userdata;\n\n  if (conn->capable & FUSE_CAP_EXPORT_SUPPORT) {\n    conn->want |= FUSE_CAP_EXPORT_SUPPORT;\n  }\n\n  if (fs.timeout && conn->capable & FUSE_CAP_WRITEBACK_CACHE) {\n    conn->want |= FUSE_CAP_WRITEBACK_CACHE;\n  }\n\n  if (conn->capable & FUSE_CAP_FLOCK_LOCKS) {\n    conn->want |= FUSE_CAP_FLOCK_LOCKS;\n  }\n\n  if (fs.nosplice) {\n    // FUSE_CAP_SPLICE_READ is enabled in libfuse3 by default,\n    // see do_init() in in fuse_lowlevel.c\n    // Just unset both, in case FUSE_CAP_SPLICE_WRITE would also get enabled\n    // by detault.\n    conn->want &= ~FUSE_CAP_SPLICE_READ;\n    conn->want &= ~FUSE_CAP_SPLICE_WRITE;\n  } else {\n    if (conn->capable & FUSE_CAP_SPLICE_WRITE) {\n      conn->want |= FUSE_CAP_SPLICE_WRITE;\n    }\n\n    if (conn->capable & FUSE_CAP_SPLICE_READ) {\n      conn->want |= FUSE_CAP_SPLICE_READ;\n    }\n  }\n}\n\n\nstatic void sfs_getattr(fuse_req_t req, fuse_ino_t ino, fuse_file_info* fi)\n{\n  eos::common::Timing timing(\"getattr\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"getattr\");\n  ADD_CFSD_STAT(\"getattr\", req);\n  //  FsID fsid(req);\n  (void)fi;\n  Inode& inode = get_inode(ino);\n  struct stat attr;\n  auto res = fstatat(inode.fd, \"\", &attr,\n                     AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);\n\n  if (res == -1) {\n    fuse_reply_err(req, errno);\n    return;\n  }\n\n  fuse_reply_attr(req, &attr, fs.timeout);\n  CFSD_TIMING_END(\"getattr\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void do_setattr(fuse_req_t req, fuse_ino_t ino, struct stat* attr,\n                       int valid, struct fuse_file_info* fi)\n{\n  FsID fsid(req);\n  Inode& inode = get_inode(ino);\n  int ifd = inode.fd;\n  int res;\n\n  if (valid & FUSE_SET_ATTR_MODE) {\n    if (fi) {\n      res = fchmod(fi->fh, attr->st_mode);\n    } else {\n      char procname[64];\n      sprintf(procname, \"/proc/self/fd/%i\", ifd);\n      res = chmod(procname, attr->st_mode);\n    }\n\n    if (res == -1) {\n      goto out_err;\n    }\n  }\n\n  if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {\n    uid_t uid = (valid & FUSE_SET_ATTR_UID) ? attr->st_uid : static_cast<uid_t>(-1);\n    gid_t gid = (valid & FUSE_SET_ATTR_GID) ? attr->st_gid : static_cast<gid_t>(-1);\n    res = fchownat(ifd, \"\", uid, gid, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);\n\n    if (res == -1) {\n      goto out_err;\n    }\n  }\n\n  if (valid & FUSE_SET_ATTR_SIZE) {\n    if (fi) {\n      res = ftruncate(fi->fh, attr->st_size);\n    } else {\n      char procname[64];\n      sprintf(procname, \"/proc/self/fd/%i\", ifd);\n      res = truncate(procname, attr->st_size);\n    }\n\n    if (res == -1) {\n      goto out_err;\n    }\n  }\n\n  if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {\n    struct timespec tv[2];\n    tv[0].tv_sec = 0;\n    tv[1].tv_sec = 0;\n    tv[0].tv_nsec = UTIME_OMIT;\n    tv[1].tv_nsec = UTIME_OMIT;\n\n    if (valid & FUSE_SET_ATTR_ATIME_NOW) {\n      tv[0].tv_nsec = UTIME_NOW;\n    } else if (valid & FUSE_SET_ATTR_ATIME) {\n      tv[0] = attr->st_atim;\n    }\n\n    if (valid & FUSE_SET_ATTR_MTIME_NOW) {\n      tv[1].tv_nsec = UTIME_NOW;\n    } else if (valid & FUSE_SET_ATTR_MTIME) {\n      tv[1] = attr->st_mtim;\n    }\n\n    if (fi) {\n      res = futimens(fi->fh, tv);\n    } else {\n      char procname[64];\n      sprintf(procname, \"/proc/self/fd/%i\", ifd);\n      res = utimensat(AT_FDCWD, procname, tv, 0);\n    }\n\n    if (res == -1) {\n      goto out_err;\n    }\n  }\n\n  return sfs_getattr(req, ino, fi);\nout_err:\n  fuse_reply_err(req, errno);\n}\n\n\nstatic void sfs_setattr(fuse_req_t req, fuse_ino_t ino, struct stat* attr,\n                        int valid, fuse_file_info* fi)\n{\n  eos::common::Timing timing(\"setattr\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"setattr\");\n  ADD_CFSD_STAT(\"setattr\", req);\n  (void) ino;\n  do_setattr(req, ino, attr, valid, fi);\n  CFSD_TIMING_END(\"setattr\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic int do_lookup(fuse_ino_t parent, const char* name,\n                     fuse_entry_param* e)\n{\n  if (fs.debug)\n    cerr << \"DEBUG: lookup(): name=\" << name\n         << \", parent=\" << parent << endl;\n\n  memset(e, 0, sizeof(*e));\n  e->attr_timeout = fs.timeout;\n  e->entry_timeout = fs.timeout;\n  auto newfd = openat(get_fs_fd(parent), name, O_PATH | O_NOFOLLOW);\n\n  if (newfd == -1) {\n    return errno;\n  }\n\n  auto res = fstatat(newfd, \"\", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);\n\n  if (res == -1) {\n    auto saveerr = errno;\n    close(newfd);\n\n    if (fs.debug) {\n      cerr << \"DEBUG: lookup(): fstatat failed\" << endl;\n    }\n\n    return saveerr;\n  }\n\n  if (0 && (e->attr.st_dev != fs.src_dev)) {\n    cerr << \"WARNING: Mountpoints in the source directory tree will be hidden.\" <<\n         endl;\n    return ENOTSUP;\n  } else if (e->attr.st_ino == FUSE_ROOT_ID) {\n    cerr << \"ERROR: Source directory tree must not include inode \"\n         << FUSE_ROOT_ID << endl;\n    return EIO;\n  }\n\n  SrcId id {e->attr.st_ino, e->attr.st_dev};\n  unique_lock<mutex> fs_lock {fs.mutex};\n  Inode* inode_p;\n\n  try {\n    inode_p = &fs.inodes[id];\n  } catch (std::bad_alloc&) {\n    return ENOMEM;\n  }\n\n  e->ino = reinterpret_cast<fuse_ino_t>(inode_p);\n  Inode& inode {*inode_p};\n  e->generation = inode.generation;\n\n  if (inode.fd == -ENOENT) { // found unlinked inode\n    if (fs.debug)\n      cerr << \"DEBUG: lookup(): inode \" << e->attr.st_ino\n           << \" recycled; generation=\" << inode.generation << endl;\n\n    /* fallthrough to new inode but keep existing inode.nlookup */\n  }\n\n  if (inode.fd > 0) { // found existing inode\n    fs_lock.unlock();\n\n    if (fs.debug)\n      cerr << \"DEBUG: lookup(): inode \" << e->attr.st_ino\n           << \" (userspace) already known; fd = \" << inode.fd << endl;\n\n    if (strcmp(name, \".\")) {\n      lock_guard<mutex> g {inode.m};\n      inode.nlookup++;\n\n      if (fs.debug)\n        cerr << \"DEBUG:\" << __func__ << \":\" << __LINE__ << \" \"\n             <<  \"inode \" << inode.src_ino\n             << \" count \" << inode.nlookup << endl;\n    }\n\n    close(newfd);\n  } else { // no existing inode\n    /* This is just here to make Helgrind happy. It violates the\n       lock ordering requirement (inode.m must be acquired before\n       fs.mutex), but this is of no consequence because at this\n       point no other thread has access to the inode mutex */\n    unique_lock<mutex> g {inode.m};\n    inode.src_ino = e->attr.st_ino;\n    inode.src_dev = e->attr.st_dev;\n    inode.nlookup++;\n\n    if (fs.debug)\n      cerr << \"DEBUG:\" << __func__ << \":\" << __LINE__ << \" \"\n           <<  \"inode \" << inode.src_ino\n           << \" count \" << inode.nlookup << endl;\n\n    inode.fd = newfd;\n\n    if ((S_ISDIR(e->attr.st_mode))) {\n      std::shared_ptr fe = std::make_shared<forgetentry_t>(parent, name);\n      fs.forgetq.push_back(fe);\n      fs.forgetq_size++;\n      fs_lock.unlock();\n    }\n\n    if (fs.debug)\n      cerr << \"DEBUG: lookup(): created userspace inode \" << e->attr.st_ino\n           << \"; fd = \" << inode.fd << endl;\n  }\n\n  return 0;\n}\n\n\nstatic void sfs_lookup(fuse_req_t req, fuse_ino_t parent, const char* name)\n{\n  eos::common::Timing timing(\"lookup\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"lookup\");\n  ADD_CFSD_STAT(\"lookup\", req);\n  fuse_entry_param e {};\n  auto err = do_lookup(parent, name, &e);\n\n  if (err == ENOENT) {\n    e.attr_timeout = fs.timeout;\n    e.entry_timeout = fs.timeout;\n    e.ino = e.attr.st_ino = 0;\n    fuse_reply_entry(req, &e);\n  } else if (err) {\n    if (err == ENFILE || err == EMFILE) {\n      cerr << \"ERROR: Reached maximum number of file descriptors.\" << endl;\n    }\n\n    fuse_reply_err(req, err);\n  } else {\n    fuse_reply_entry(req, &e);\n  }\n\n  CFSD_TIMING_END(\"lookup\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void mknod_symlink(fuse_req_t req, fuse_ino_t parent,\n                          const char* name, mode_t mode, dev_t rdev,\n                          const char* link)\n{\n  FsID fsid(req);\n  int res;\n  Inode& inode_p = get_inode(parent);\n  auto saverr = ENOMEM;\n\n  if (S_ISDIR(mode)) {\n    res = mkdirat(inode_p.fd, name, mode);\n  } else if (S_ISLNK(mode)) {\n    res = symlinkat(link, inode_p.fd, name);\n  } else {\n    res = mknodat(inode_p.fd, name, mode, rdev);\n  }\n\n  saverr = errno;\n\n  if (res == -1) {\n    goto out;\n  }\n\n  fuse_entry_param e;\n  saverr = do_lookup(parent, name, &e);\n\n  if (saverr) {\n    goto out;\n  }\n\n  fuse_reply_entry(req, &e);\n  return;\nout:\n\n  if (saverr == ENFILE || saverr == EMFILE) {\n    cerr << \"ERROR: Reached maximum number of file descriptors.\" << endl;\n  }\n\n  fuse_reply_err(req, saverr);\n}\n\n\nstatic void sfs_mknod(fuse_req_t req, fuse_ino_t parent, const char* name,\n                      mode_t mode, dev_t rdev)\n{\n  eos::common::Timing timing(\"mknod\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"mknod\");\n  ADD_CFSD_STAT(\"mknod\", req);\n  mknod_symlink(req, parent, name, mode, rdev, nullptr);\n  CFSD_TIMING_END(\"mknod\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_mkdir(fuse_req_t req, fuse_ino_t parent, const char* name,\n                      mode_t mode)\n{\n  eos::common::Timing timing(\"mkdir\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"mkdir\");\n  ADD_CFSD_STAT(\"mkdir\", req);\n  mknod_symlink(req, parent, name, S_IFDIR | mode, 0, nullptr);\n  CFSD_TIMING_END(\"mkdir\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_symlink(fuse_req_t req, const char* link, fuse_ino_t parent,\n                        const char* name)\n{\n  eos::common::Timing timing(\"symlink\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"symlink\");\n  ADD_CFSD_STAT(\"symlink\", req);\n  mknod_symlink(req, parent, name, S_IFLNK, 0, link);\n  CFSD_TIMING_END(\"symlink\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,\n                     const char* name)\n{\n  eos::common::Timing timing(\"link\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"link\");\n  ADD_CFSD_STAT(\"link\", req);\n  FsID fsid(req);\n  Inode& inode = get_inode(ino);\n  Inode& inode_p = get_inode(parent);\n  fuse_entry_param e {};\n  e.attr_timeout = fs.timeout;\n  e.entry_timeout = fs.timeout;\n  char procname[64];\n  sprintf(procname, \"/proc/self/fd/%i\", inode.fd);\n  auto res = linkat(AT_FDCWD, procname, inode_p.fd, name, AT_SYMLINK_FOLLOW);\n\n  if (res == -1) {\n    fuse_reply_err(req, errno);\n    return;\n  }\n\n  res = fstatat(inode.fd, \"\", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);\n\n  if (res == -1) {\n    fuse_reply_err(req, errno);\n    return;\n  }\n\n  e.ino = reinterpret_cast<fuse_ino_t>(&inode);\n  {\n    lock_guard<mutex> g {inode.m};\n    inode.nlookup++;\n\n    if (fs.debug)\n      cerr << \"DEBUG:\" << __func__ << \":\" << __LINE__ << \" \"\n           <<  \"inode \" << inode.src_ino\n           << \" count \" << inode.nlookup << endl;\n  }\n  fuse_reply_entry(req, &e);\n  CFSD_TIMING_END(\"link\");\n  COMMONTIMING(\"_stop_\", &timing);\n  return;\n}\n\n\nstatic void sfs_rmdir(fuse_req_t req, fuse_ino_t parent, const char* name)\n{\n  eos::common::Timing timing(\"rmdir\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"rmdir\");\n  ADD_CFSD_STAT(\"rmdir\", req);\n  FsID fsid(req);\n  Inode& inode_p = get_inode(parent);\n  lock_guard<mutex> g {inode_p.m};\n  auto res =\n    (fs.recyclebin &&\n     fs.recycle.shouldRecycle(fsid.getUid(), parent, inode_p.fd, name)) ?\n    fs.recycle.moveBin(fsid.getUid(), parent, inode_p.fd, name) :\n    unlinkat(inode_p.fd, name, AT_REMOVEDIR);\n  fuse_reply_err(req, res == -1 ? errno : 0);\n  CFSD_TIMING_END(\"rmdir\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_rename(fuse_req_t req, fuse_ino_t parent, const char* name,\n                       fuse_ino_t newparent, const char* newname,\n                       unsigned int flags)\n{\n  eos::common::Timing timing(\"rename\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"rename\");\n  ADD_CFSD_STAT(\"rename\", req);\n  FsID fsid(req);\n  Inode& inode_p = get_inode(parent);\n  Inode& inode_np = get_inode(newparent);\n\n  if (flags) {\n    fuse_reply_err(req, EINVAL);\n    return;\n  }\n\n  auto res = renameat(inode_p.fd, name, inode_np.fd, newname);\n  fuse_reply_err(req, res == -1 ? errno : 0);\n  CFSD_TIMING_END(\"rename\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void forget_one(fuse_ino_t ino, uint64_t n)\n{\n  Inode& inode = get_inode(ino);\n  unique_lock<mutex> l {inode.m};\n\n  if (n > inode.nlookup) {\n    cerr << \"INTERNAL ERROR: Negative lookup count for inode \"\n         << inode.src_ino << endl;\n    abort();\n  }\n\n  inode.nlookup -= n;\n\n  if (fs.debug)\n    cerr << \"DEBUG:\" << __func__ << \":\" << __LINE__ << \" \"\n         <<  \"inode \" << inode.src_ino\n         << \" count \" << inode.nlookup << endl;\n\n  if (!inode.nlookup) {\n    if (fs.debug) {\n      cerr << \"DEBUG: forget: cleaning up inode \" << inode.src_ino << endl;\n    }\n\n    {\n      lock_guard<mutex> g_fs {fs.mutex};\n      l.unlock();\n      fs.inodes.erase({inode.src_ino, inode.src_dev});\n    }\n  } else if (fs.debug)\n    cerr << \"DEBUG: forget: inode \" << inode.src_ino\n         << \" lookup count now \" << inode.nlookup << endl;\n}\n\n\nstatic void sfs_unlink(fuse_req_t req, fuse_ino_t parent, const char* name)\n{\n  eos::common::Timing timing(\"unlink\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"unlink\");\n  ADD_CFSD_STAT(\"unlink\", req);\n  FsID fsid(req);\n  Inode& inode_p = get_inode(parent);\n\n  // Release inode.fd before last unlink like nfsd EXPORT_OP_CLOSE_BEFORE_UNLINK\n  // to test reused inode numbers.\n  // Skip this when inode has an open file and when writeback cache is enabled.\n  if (!fs.timeout) {\n    fuse_entry_param e;\n    auto err = do_lookup(parent, name, &e);\n\n    if (err) {\n      fuse_reply_err(req, err);\n      return;\n    }\n\n    if (e.attr.st_nlink == 1) {\n      Inode& inode = get_inode(e.ino);\n      lock_guard<mutex> g {inode.m};\n\n      if (inode.fd > 0 && !inode.nopen) {\n        if (fs.debug)\n          cerr << \"DEBUG: unlink: release inode \" << e.attr.st_ino\n               << \"; fd=\" << inode.fd << endl;\n\n        {\n          lock_guard<mutex> g_fs {fs.mutex};\n          close(inode.fd);\n          inode.fd = -ENOENT;\n          inode.generation++;\n        }\n      }\n    }\n\n    // decrease the ref which lookup above had increased\n    forget_one(e.ino, 1);\n  }\n\n  auto res =\n    (fs.recyclebin &&\n     fs.recycle.shouldRecycle(fsid.getUid(), parent, inode_p.fd, name)) ?\n    fs.recycle.moveBin(fsid.getUid(), parent, inode_p.fd, name) :\n    unlinkat(inode_p.fd, name, 0);\n  fuse_reply_err(req, res == -1 ? errno : 0);\n  CFSD_TIMING_END(\"unlink\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)\n{\n  eos::common::Timing timing(\"forget\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"forget\");\n  ADD_CFSD_STAT(\"forget\", req);\n  forget_one(ino, nlookup);\n  fuse_reply_none(req);\n  CFSD_TIMING_END(\"forget\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_forget_multi(fuse_req_t req, size_t count,\n                             fuse_forget_data* forgets)\n{\n  eos::common::Timing timing(\"forgetmulti\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"forgetmulti\");\n  ADD_CFSD_STAT(\"forgetmulti\", req);\n\n  for (size_t i = 0; i < count; i++) {\n    forget_one(forgets[i].ino, forgets[i].nlookup);\n  }\n\n  fuse_reply_none(req);\n  CFSD_TIMING_END(\"forgetmulti\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_readlink(fuse_req_t req, fuse_ino_t ino)\n{\n  eos::common::Timing timing(\"readlink\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"readlink\");\n  ADD_CFSD_STAT(\"readlink\", req);\n  FsID fsid(req);\n  Inode& inode = get_inode(ino);\n  char buf[PATH_MAX + 1];\n  auto res = readlinkat(inode.fd, \"\", buf, sizeof(buf));\n\n  if (res == -1) {\n    fuse_reply_err(req, errno);\n  } else if (res == sizeof(buf)) {\n    fuse_reply_err(req, ENAMETOOLONG);\n  } else {\n    buf[res] = '\\0';\n    fuse_reply_readlink(req, buf);\n  }\n\n  CFSD_TIMING_END(\"readlink\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\nstatic DirHandle* get_dir_handle(fuse_file_info* fi)\n{\n  return reinterpret_cast<DirHandle*>(fi->fh);\n}\n\n\nstatic void sfs_opendir(fuse_req_t req, fuse_ino_t ino, fuse_file_info* fi)\n{\n  eos::common::Timing timing(\"opendir\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"opendir\");\n  ADD_CFSD_STAT(\"opendir\", req);\n  FsID fsid(req);\n  Inode& inode = get_inode(ino);\n  auto d = new(nothrow) DirHandle;\n\n  if (d == nullptr) {\n    fuse_reply_err(req, ENOMEM);\n    return;\n  }\n\n  // Make Helgrind happy - it can't know that there's an implicit\n  // synchronization due to the fact that other threads cannot\n  // access d until we've called fuse_reply_*.\n  lock_guard<mutex> g {inode.m};\n  auto fd = openat(inode.fd, \".\", O_RDONLY);\n\n  if (fd == -1) {\n    goto out_errno;\n  }\n\n  // On success, dir stream takes ownership of fd, so we\n  // do not have to close it.\n  d->dp = fdopendir(fd);\n\n  if (d->dp == nullptr) {\n    goto out_errno;\n  }\n\n  d->offset = 0;\n  fi->fh = reinterpret_cast<uint64_t>(d);\n\n  if (fs.timeout) {\n    fi->keep_cache = 1;\n    fi->cache_readdir = 1;\n  }\n\n  fuse_reply_open(req, fi);\n  return;\nout_errno:\n  auto error = errno;\n  delete d;\n\n  if (error == ENFILE || error == EMFILE) {\n    cerr << \"ERROR: Reached maximum number of file descriptors.\" << endl;\n  }\n\n  fuse_reply_err(req, error);\n  CFSD_TIMING_END(\"opendir\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic bool is_dot_or_dotdot(const char* name)\n{\n  return name[0] == '.' &&\n         (name[1] == '\\0' || (name[1] == '.' && name[2] == '\\0'));\n}\n\n\nstatic void do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,\n                       off_t offset, fuse_file_info* fi, int plus)\n{\n  FsID fsid(req);\n  auto d = get_dir_handle(fi);\n  Inode& inode = get_inode(ino);\n  lock_guard<mutex> g {inode.m};\n  char* p;\n  auto rem = size;\n  int err = 0, count = 0;\n\n  if (fs.debug)\n    cerr << \"DEBUG: readdir(): started with offset \"\n         << offset << endl;\n\n  auto buf = new(nothrow) char[size];\n\n  if (!buf) {\n    fuse_reply_err(req, ENOMEM);\n    return;\n  }\n\n  p = buf;\n\n  if (offset != d->offset) {\n    if (fs.debug) {\n      cerr << \"DEBUG: readdir(): seeking to \" << offset << endl;\n    }\n\n    seekdir(d->dp, offset);\n    d->offset = offset;\n  }\n\n  while (1) {\n    struct dirent* entry;\n    errno = 0;\n    entry = readdir(d->dp);\n\n    if (!entry) {\n      if (errno) {\n        err = errno;\n\n        if (fs.debug) {\n          warn(\"DEBUG: readdir(): readdir failed with\");\n        }\n\n        goto error;\n      }\n\n      break; // End of stream\n    }\n\n    d->offset = entry->d_off;\n    fuse_entry_param e{};\n    size_t entsize;\n\n    if (plus) {\n      err = do_lookup(ino, entry->d_name, &e);\n\n      if (err) {\n        goto error;\n      }\n\n      entsize = fuse_add_direntry_plus(req, p, rem, entry->d_name, &e, entry->d_off);\n\n      if (entsize > rem) {\n        if (fs.debug) {\n          cerr << \"DEBUG: readdir(): buffer full, returning data. \" << endl;\n        }\n\n        forget_one(e.ino, 1);\n        break;\n      }\n    } else {\n      e.attr.st_ino = entry->d_ino;\n      e.attr.st_mode = entry->d_type << 12;\n      entsize = fuse_add_direntry(req, p, rem, entry->d_name, &e.attr, entry->d_off);\n\n      if (!is_dot_or_dotdot(entry->d_name)) {\n        unique_lock<mutex> fs_lock {fs.mutex};\n        std::shared_ptr fe = std::make_shared<forgetentry_t>(ino, entry->d_name);\n        fs.forgetq.push_back(fe);\n        fs.forgetq_size++;\n      }\n\n      if (entsize > rem) {\n        if (fs.debug) {\n          cerr << \"DEBUG: readdir(): buffer full, returning data. \" << endl;\n        }\n\n        break;\n      }\n    }\n\n    p += entsize;\n    rem -= entsize;\n    count++;\n\n    if (fs.debug) {\n      cerr << \"DEBUG: readdir(): added to buffer: \" << entry->d_name\n           << \", ino \" << e.attr.st_ino << \", offset \" << entry->d_off << endl;\n    }\n  }\n\n  err = 0;\nerror:\n\n  // If there's an error, we can only signal it if we haven't stored\n  // any entries yet - otherwise we'd end up with wrong lookup\n  // counts for the entries that are already in the buffer. So we\n  // return what we've collected until that point.\n  if (err && rem == size) {\n    if (err == ENFILE || err == EMFILE) {\n      cerr << \"ERROR: Reached maximum number of file descriptors.\" << endl;\n    }\n\n    fuse_reply_err(req, err);\n  } else {\n    if (fs.debug)\n      cerr << \"DEBUG: readdir(): returning \" << count\n           << \" entries, curr offset \" << d->offset << endl;\n\n    fuse_reply_buf(req, buf, size - rem);\n  }\n\n  delete[] buf;\n  return;\n}\n\n\nstatic void sfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,\n                        off_t offset, fuse_file_info* fi)\n{\n  eos::common::Timing timing(\"readdir\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"readdir\");\n  ADD_CFSD_STAT(\"readdir\", req);\n  // operation logging is done in readdir to reduce code duplication\n  do_readdir(req, ino, size, offset, fi, 0);\n  CFSD_TIMING_END(\"readdir\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,\n                            off_t offset, fuse_file_info* fi)\n{\n  eos::common::Timing timing(\"readdirplus\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"readdirplus\");\n  ADD_CFSD_STAT(\"readdirplus\", req);\n  // operation logging is done in readdir to reduce code duplication\n  do_readdir(req, ino, size, offset, fi, 1);\n  CFSD_TIMING_END(\"readdirplus\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_releasedir(fuse_req_t req, fuse_ino_t ino, fuse_file_info* fi)\n{\n  eos::common::Timing timing(\"releasedir\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"releasedir\");\n  ADD_CFSD_STAT(\"releasedir\", req);\n  (void) ino;\n  auto d = get_dir_handle(fi);\n  delete d;\n  fuse_reply_err(req, 0);\n  CFSD_TIMING_END(\"releasedir\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_create(fuse_req_t req, fuse_ino_t parent, const char* name,\n                       mode_t mode, fuse_file_info* fi)\n{\n  eos::common::Timing timing(\"create\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"create\");\n  ADD_CFSD_STAT(\"create\", req);\n  FsID fsid(req);\n  Inode& inode_p = get_inode(parent);\n  auto fd = openat(inode_p.fd, name,\n                   (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);\n\n  if (fd == -1) {\n    auto err = errno;\n\n    if (err == ENFILE || err == EMFILE) {\n      cerr << \"ERROR: Reached maximum number of file descriptors.\" << endl;\n    }\n\n    fuse_reply_err(req, err);\n    return;\n  }\n\n  {\n    lock_guard<mutex> g {fs.openFdsMutex};\n    fs.openFds[fd].first  = fsid.getUid();\n    fs.openFds[fd].second = fsid.getGid();\n  }\n\n  fi->fh = fd;\n  fuse_entry_param e;\n  auto err = do_lookup(parent, name, &e);\n\n  if (err) {\n    if (err == ENFILE || err == EMFILE) {\n      cerr << \"ERROR: Reached maximum number of file descriptors.\" << endl;\n    }\n\n    fuse_reply_err(req, err);\n    return;\n  }\n\n  Inode& inode = get_inode(e.ino);\n  lock_guard<mutex> g {inode.m};\n  inode.nopen++;\n  fuse_reply_create(req, &e, fi);\n  CFSD_TIMING_END(\"create\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,\n                         fuse_file_info* fi)\n{\n  eos::common::Timing timing(\"fsyncdir\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"fsyncdir\");\n  ADD_CFSD_STAT(\"fsyncdir\", req);\n  (void) ino;\n  int res;\n  int fd = dirfd(get_dir_handle(fi)->dp);\n\n  if (datasync) {\n    res = fdatasync(fd);\n  } else {\n    res = fsync(fd);\n  }\n\n  fuse_reply_err(req, res == -1 ? errno : 0);\n  CFSD_TIMING_END(\"fsyncdir\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_open(fuse_req_t req, fuse_ino_t ino, fuse_file_info* fi)\n{\n  eos::common::Timing timing(\"open\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"open\");\n  ADD_CFSD_STAT(\"open\", req);\n  FsID fsid(req);\n  Inode& inode = get_inode(ino);\n\n  /* With writeback cache, kernel may send read requests even\n     when userspace opened write-only */\n  if (fs.timeout && (fi->flags & O_ACCMODE) == O_WRONLY) {\n    fi->flags &= ~O_ACCMODE;\n    fi->flags |= O_RDWR;\n  }\n\n  /* With writeback cache, O_APPEND is handled by the kernel.  This\n     breaks atomicity (since the file may change in the underlying\n     filesystem, so that the kernel's idea of the end of the file\n     isn't accurate anymore). However, no process should modify the\n     file in the underlying filesystem once it has been read, so\n     this is not a problem. */\n  if (fs.timeout && fi->flags & O_APPEND) {\n    fi->flags &= ~O_APPEND;\n  }\n\n  /* Unfortunately we cannot use inode.fd, because this was opened\n     with O_PATH (so it doesn't allow read/write access). */\n  char buf[64];\n  sprintf(buf, \"/proc/self/fd/%i\", inode.fd);\n  auto fd = open(buf, fi->flags & ~O_NOFOLLOW);\n\n  if (fd == -1) {\n    auto err = errno;\n\n    if (err == ENFILE || err == EMFILE) {\n      cerr << \"ERROR: Reached maximum number of file descriptors.\" << endl;\n    }\n\n    fuse_reply_err(req, err);\n    return;\n  }\n\n  {\n    lock_guard<mutex> g {fs.openFdsMutex};\n    fs.openFds[fd].first  = fsid.getUid();\n    fs.openFds[fd].second = fsid.getGid();\n  }\n\n  lock_guard<mutex> g {inode.m};\n  inode.nopen++;\n  fi->keep_cache = (fs.timeout != 0);\n#if ( FUSE_MINOR_VERSION > 10 )\n  fi->noflush = (fs.timeout == 0 && (fi->flags & O_ACCMODE) == O_RDONLY);\n#endif\n  fi->fh = fd;\n  fuse_reply_open(req, fi);\n  CFSD_TIMING_END(\"fsyncdir\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_release(fuse_req_t req, fuse_ino_t ino, fuse_file_info* fi)\n{\n  eos::common::Timing timing(\"release\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"release\");\n  ADD_CFSD_STAT(\"release\", req);\n  {\n    lock_guard<mutex> g {fs.openFdsMutex};\n    fs.openFds.erase(fi->fh);\n  }\n  Inode& inode = get_inode(ino);\n  lock_guard<mutex> g {inode.m};\n  inode.nopen--;\n  close(fi->fh);\n  fuse_reply_err(req, 0);\n  CFSD_TIMING_END(\"release\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_flush(fuse_req_t req, fuse_ino_t ino, fuse_file_info* fi)\n{\n  eos::common::Timing timing(\"flush\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"flush\");\n  ADD_CFSD_STAT(\"flush\", req);\n  FsID fsid(req);\n  (void) ino;\n  auto res = close(dup(fi->fh));\n  fuse_reply_err(req, res == -1 ? errno : 0);\n  CFSD_TIMING_END(\"flush\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,\n                      fuse_file_info* fi)\n{\n  eos::common::Timing timing(\"fsync\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"fsync\");\n  ADD_CFSD_STAT(\"fsync\", req);\n  FsID fsid(req);\n  (void) ino;\n  int res;\n\n  if (datasync) {\n    res = fdatasync(fi->fh);\n  } else {\n    res = fsync(fi->fh);\n  }\n\n  fuse_reply_err(req, res == -1 ? errno : 0);\n  CFSD_TIMING_END(\"fsync\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void do_read(fuse_req_t req, size_t size, off_t off, fuse_file_info* fi)\n{\n  fuse_bufvec buf = FUSE_BUFVEC_INIT(size);\n  buf.buf[0].flags = static_cast<fuse_buf_flags>(\n                       FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK);\n  buf.buf[0].fd = fi->fh;\n  buf.buf[0].pos = off;\n  fuse_reply_data(req, &buf, FUSE_BUF_COPY_FLAGS);\n  ADD_CFSD_IO_STAT(\"rbytes\", size);\n}\n\nstatic void sfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,\n                     fuse_file_info* fi)\n{\n  eos::common::Timing timing(\"read\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"read\");\n  ADD_CFSD_STAT(\"read\", req);\n  (void) ino;\n  do_read(req, size, off, fi);\n  CFSD_TIMING_END(\"read\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void do_write_buf(fuse_req_t req, size_t size, off_t off,\n                         fuse_bufvec* in_buf, fuse_file_info* fi)\n{\n  uid_t uid = 99;\n  gid_t gid = 99;\n  {\n    lock_guard<mutex> g {fs.openFdsMutex};\n    uid = fs.openFds[fi->fh].first;\n    gid = fs.openFds[fi->fh].second;\n  }\n\n  if (!fs.quota.hasquota(uid, gid)) {\n    fuse_reply_err(req, EDQUOT);\n    return;\n  }\n\n  fuse_bufvec out_buf = FUSE_BUFVEC_INIT(size);\n  out_buf.buf[0].flags = static_cast<fuse_buf_flags>(\n                           FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK);\n  out_buf.buf[0].fd = fi->fh;\n  out_buf.buf[0].pos = off;\n  auto res = fuse_buf_copy(&out_buf, in_buf, FUSE_BUF_COPY_FLAGS);\n\n  if (res < 0) {\n    fuse_reply_err(req, -res);\n  } else {\n    fuse_reply_write(req, (size_t)res);\n    ADD_CFSD_IO_STAT(\"wbytes\", res);\n  }\n}\n\n\nstatic void sfs_write_buf(fuse_req_t req, fuse_ino_t ino, fuse_bufvec* in_buf,\n                          off_t off, fuse_file_info* fi)\n{\n  eos::common::Timing timing(\"write\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"write\");\n  ADD_CFSD_STAT(\"write\", req);\n  (void) ino;\n  auto size {fuse_buf_size(in_buf)};\n  do_write_buf(req, size, off, in_buf, fi);\n  CFSD_TIMING_END(\"write\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_statfs(fuse_req_t req, fuse_ino_t ino)\n{\n  eos::common::Timing timing(\"statfs\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"statfs\");\n  ADD_CFSD_STAT(\"statfs\", req);\n  FsID fsid(req);\n  struct statvfs stbuf;\n  auto res = fstatvfs(get_fs_fd(ino), &stbuf);\n\n  if (res == -1) {\n    fuse_reply_err(req, errno);\n  } else {\n    fuse_reply_statfs(req, &stbuf);\n  }\n\n  CFSD_TIMING_END(\"statfs\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,\n                          off_t offset, off_t length, fuse_file_info* fi)\n{\n  eos::common::Timing timing(\"fallocate\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"fallocate\");\n  ADD_CFSD_STAT(\"fallocate\", req);\n  (void) ino;\n\n  if (mode) {\n    fuse_reply_err(req, EOPNOTSUPP);\n    return;\n  }\n\n  auto err = posix_fallocate(fi->fh, offset, length);\n  fuse_reply_err(req, err);\n  CFSD_TIMING_END(\"fallocate\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\nstatic void sfs_flock(fuse_req_t req, fuse_ino_t ino, fuse_file_info* fi,\n                      int op)\n{\n  eos::common::Timing timing(\"flock\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"flock\");\n  ADD_CFSD_STAT(\"flock\", req);\n  (void) ino;\n  auto res = flock(fi->fh, op);\n  fuse_reply_err(req, res == -1 ? errno : 0);\n  CFSD_TIMING_END(\"flock\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_getxattr(fuse_req_t req, fuse_ino_t ino, const char* name,\n                         size_t size)\n{\n  eos::common::Timing timing(\"getxattr\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"getxattr\");\n  ADD_CFSD_STAT(\"getxattr\", req);\n  FsID fsid(req);\n  std::string vattr = cfsvattr::vattr(name, fsid.getName(), fsid.getUid(),\n                                      fsid.getGid(), fs.quota.hasquota(fsid.getUid(), fsid.getGid()));\n\n  if (!vattr.empty()) {\n    if (size && vattr.size() > size) {\n      fuse_reply_err(req, ERANGE);\n    } else {\n      if (size == 0) {\n        fuse_reply_xattr(req, vattr.size());\n      } else {\n        fuse_reply_buf(req, vattr.c_str(), vattr.size());\n      }\n    }\n\n    return;\n  }\n\n  char* value = nullptr;\n  Inode& inode = get_inode(ino);\n  ssize_t ret;\n  int saverr;\n  char procname[64];\n  sprintf(procname, \"/proc/self/fd/%i\", inode.fd);\n\n  if (size) {\n    value = new(nothrow) char[size];\n\n    if (value == nullptr) {\n      saverr = ENOMEM;\n      goto out;\n    }\n\n    ret = getxattr(procname, name, value, size);\n\n    if (ret == -1) {\n      goto out_err;\n    }\n\n    saverr = 0;\n\n    if (ret == 0) {\n      goto out;\n    }\n\n    fuse_reply_buf(req, value, ret);\n  } else {\n    ret = getxattr(procname, name, nullptr, 0);\n\n    if (ret == -1) {\n      goto out_err;\n    }\n\n    fuse_reply_xattr(req, ret);\n  }\n\n  CFSD_TIMING_END(\"getxattr\");\n  COMMONTIMING(\"_stop_\", &timing);\nout_free:\n  delete[] value;\n  return;\nout_err:\n  saverr = errno;\nout:\n  fuse_reply_err(req, saverr);\n  goto out_free;\n}\n\n\nstatic void sfs_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)\n{\n  eos::common::Timing timing(\"listxattr\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"listxattr\");\n  ADD_CFSD_STAT(\"listxxattr\", req);\n  FsID fsid(req);\n  char* value = nullptr;\n  Inode& inode = get_inode(ino);\n  ssize_t ret;\n  int saverr;\n  char procname[64];\n  sprintf(procname, \"/proc/self/fd/%i\", inode.fd);\n\n  if (size) {\n    value = new(nothrow) char[size];\n\n    if (value == nullptr) {\n      saverr = ENOMEM;\n      goto out;\n    }\n\n    ret = listxattr(procname, value, size);\n\n    if (ret == -1) {\n      goto out_err;\n    }\n\n    saverr = 0;\n\n    if (ret == 0) {\n      goto out;\n    }\n\n    fuse_reply_buf(req, value, ret);\n  } else {\n    ret = listxattr(procname, nullptr, 0);\n\n    if (ret == -1) {\n      goto out_err;\n    }\n\n    fuse_reply_xattr(req, ret);\n  }\n\n  CFSD_TIMING_END(\"listxattr\");\n  COMMONTIMING(\"_stop_\", &timing);\nout_free:\n  delete[] value;\n  return;\nout_err:\n  saverr = errno;\nout:\n  fuse_reply_err(req, saverr);\n  goto out_free;\n}\n\n\nstatic void sfs_setxattr(fuse_req_t req, fuse_ino_t ino, const char* name,\n                         const char* value, size_t size, int flags)\n{\n  eos::common::Timing timing(\"setxattr\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"setxattr\");\n  ADD_CFSD_STAT(\"setxattr\", req);\n  FsID fsid(req);\n  Inode& inode = get_inode(ino);\n  ssize_t ret;\n  int saverr;\n  char procname[64];\n  sprintf(procname, \"/proc/self/fd/%i\", inode.fd);\n  ret = setxattr(procname, name, value, size, flags);\n  saverr = ret == -1 ? errno : 0;\n  fuse_reply_err(req, saverr);\n  CFSD_TIMING_END(\"setxattr\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n\nstatic void sfs_removexattr(fuse_req_t req, fuse_ino_t ino, const char* name)\n{\n  eos::common::Timing timing(\"removexattr\");\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  CFSD_TIMING_BEGIN(\"removexattr\");\n  ADD_CFSD_STAT(\"removexattr\", req);\n  FsID fsid(req);\n  char procname[64];\n  Inode& inode = get_inode(ino);\n  ssize_t ret;\n  int saverr;\n  sprintf(procname, \"/proc/self/fd/%i\", inode.fd);\n  ret = removexattr(procname, name);\n  saverr = ret == -1 ? errno : 0;\n  fuse_reply_err(req, saverr);\n  CFSD_TIMING_END(\"removexattr\");\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\nstatic void assign_operations(fuse_lowlevel_ops& sfs_oper)\n{\n  sfs_oper.init = sfs_init;\n  sfs_oper.lookup = sfs_lookup;\n  sfs_oper.mkdir = sfs_mkdir;\n  sfs_oper.mknod = sfs_mknod;\n  sfs_oper.symlink = sfs_symlink;\n  sfs_oper.link = sfs_link;\n  sfs_oper.unlink = sfs_unlink;\n  sfs_oper.rmdir = sfs_rmdir;\n  sfs_oper.rename = sfs_rename;\n  sfs_oper.forget = sfs_forget;\n  sfs_oper.forget_multi = sfs_forget_multi;\n  sfs_oper.getattr = sfs_getattr;\n  sfs_oper.setattr = sfs_setattr;\n  sfs_oper.readlink = sfs_readlink;\n  sfs_oper.opendir = sfs_opendir;\n  sfs_oper.readdir = sfs_readdir;\n  sfs_oper.readdirplus = sfs_readdirplus;\n  sfs_oper.releasedir = sfs_releasedir;\n  sfs_oper.fsyncdir = sfs_fsyncdir;\n  sfs_oper.create = sfs_create;\n  sfs_oper.open = sfs_open;\n  sfs_oper.release = sfs_release;\n  sfs_oper.flush = sfs_flush;\n  sfs_oper.fsync = sfs_fsync;\n  sfs_oper.read = sfs_read;\n  sfs_oper.write_buf = sfs_write_buf;\n  sfs_oper.statfs = sfs_statfs;\n  sfs_oper.fallocate = sfs_fallocate;\n  sfs_oper.flock = sfs_flock;\n  sfs_oper.setxattr = sfs_setxattr;\n  sfs_oper.getxattr = sfs_getxattr;\n  sfs_oper.listxattr = sfs_listxattr;\n  sfs_oper.removexattr = sfs_removexattr;\n}\n\n\nstd::string prepare(std::string input, std::string keylocation)\n{\n  std::string key;\n  eos::common::StringConversion::LoadFileIntoString(keylocation.c_str(), key);\n\n  // Make sure, there is no line-feed part of the key!\n  if (!key.empty() && key.back() == '\\n') {\n    key.pop_back();\n  }\n\n  struct stat buf;\n\n  if (::stat(keylocation.c_str(), &buf)) {\n    fprintf(stderr, \"error: %s not accessible!\\n\", keylocation.c_str());\n    exit(-1);\n  }\n\n#include \"keychange.hh\"\n\n  if (buf.st_uid || ((buf.st_mode & 0x1ff) != S_IRUSR)) {\n    fprintf(stderr,\n            \"error: %sdoes not have correct ownership (root) or 400 permission! [%d/%d/%x/%x]\\n\",\n            keylocation.c_str(), buf.st_uid, buf.st_mode, buf.st_mode, S_IRUSR);\n    exit(-1);\n  }\n\n  std::string shakey = eos::common::SymKey::HexSha256(key);\n  XrdOucString in = input.c_str();\n  XrdOucString out;\n  if (in.beginswith(\"enc:\")) {\n    in.erase(0,4);\n  }\n  eos::common::SymKey::SymmetricStringDecrypt(in, out, (char*)shakey.c_str());\n  if (out.length()) {\n    return out.c_str();\n  } else {\n    return \"\";\n  }\n}\n\nint execute(std::string& scmd)\n{\n  return system(scmd.c_str());\n}\n\nvoid\nFs::LevelFDs(ThreadAssistant& assistant)\n{\n  while (true) {\n    assistant.wait_for(std::chrono::milliseconds(1000));\n    {\n      ForgetQueue forget;\n      {\n        const std::lock_guard<std::mutex> g_fs(this->mutex);\n\n        if (fs.dropcache) {\n          if (fs.inodes.size() > (128 * 1024)) {\n            fprintf(stderr, \"# inodes:%lu\\n\", fs.inodes.size());\n            fprintf(stderr, \"# flushing DENTRY cache\\n\");\n            ofstream mdflush;\n            mdflush.open(\"/proc/sys/vm/drop_caches\");\n            mdflush << \"2\" << std::endl;\n            mdflush.close();\n          }\n        } else {\n          time_t now = time(NULL);\n\n          //    fprintf(stderr,\"%lu %lu %lu %lu\\n\", fs.forgetq_size.load(),\n          //      fs.forgetq_size.load()?fs.forgetq.front()->tst:0, fs.idletime, now);\n          while ((fs.forgetq_size > 4096) ||\n                 (fs.forgetq_size && ((time_t)(fs.forgetq.front()->tst + fs.idletime) < now))) {\n            forget.push_back(fs.forgetq.front());\n            //      fprintf(stderr,\"forgetting %x:%s\\n\", fs.forgetq.front()->parent, fs.forgetq.front()->name.c_str());\n            fs.forgetq.pop_front();\n            forgetq_size--;\n            //      fprintf(stderr,\"forgetq: %lu\\n\", forgetq_size.load());\n          }\n        }\n      }\n\n      for (auto it : forget) {\n        fuse_lowlevel_notify_inval_entry(fs.se,\n                                         it->parent, it->name.c_str(), it->name.length());\n        //fprintf(stderr,\"forgot %x:%s\\n\", it->parent, it->name.c_str());\n      }\n    }\n\n    if (assistant.terminationRequested()) {\n      break;\n    }\n  }\n}\n\nvoid\nFs::StatCirculate(ThreadAssistant& assistant)\n/* -------------------------------------------------------------------------- */\n{\n  eos_static_debug(\"started stat circulate thread\");\n  fusestat.Circulate(assistant);\n}\n\nvoid\nFs::DumpStatistic(ThreadAssistant& assistant)\n{\n  eos::common::LinuxTotalMem meminfo;\n\n  while (!assistant.terminationRequested()) {\n    Json::StreamWriterBuilder writer;\n    writer[\"indentation\"] = \"    \"; // You can set custom indentation here if needed\n    Json::Value jsonstats{};\n    meminfo.update();\n    eos::common::LinuxStat::linux_stat_t osstat;\n#ifndef __APPLE__\n    eos::common::LinuxMemConsumption::linux_mem_t mem;\n\n    if (!eos::common::LinuxMemConsumption::GetMemoryFootprint(mem)) {\n      eos_static_err(\"failed to get the MEM usage information\");\n    }\n\n    if (!eos::common::LinuxStat::GetStat(osstat)) {\n      eos_static_err(\"failed to get the OS usage information\");\n    }\n\n#endif\n    eos_static_debug(\"dumping statistics\");\n    {\n      //fuse counter\n      fusestat.PrintOutTotalJson(jsonstats);\n      {\n        unsigned long long rbytes, wbytes = 0;\n        unsigned long nops = 0;\n        float total_rbytes, total_wbytes = 0;\n        int sum = 0;\n        unsigned long totalram, freeram, loads0 = 0;\n        {\n          std::lock_guard<std::mutex> g{getFuseStat().Mutex};\n          rbytes = this->getFuseStat().GetTotal(\"rbytes\");\n          wbytes = this->getFuseStat().GetTotal(\"wbytes\");\n          nops = this->getFuseStat().GetOps();\n          total_rbytes = this->getFuseStat().GetTotalAvg5(\"rbytes\") / 1000.0 / 1000.0;\n          total_wbytes = this->getFuseStat().GetTotalAvg5(\"wbytes\") / 1000.0 / 1000.0;\n          sum = (int) this->getFuseStat().GetTotalAvg5(\":sum\");\n          {\n            std::lock_guard<std::mutex> lock(meminfo.mutex());\n            totalram = meminfo.getref().totalram;\n            freeram = meminfo.getref().freeram;\n            loads0 = meminfo.getref().loads[0];\n          }\n          {\n            //os stats\n            Json::Value stats {};\n            std::string s1;\n            std::string s2;\n            stats[\"threads\"]             = (Json::LargestUInt) osstat.threads;\n            stats[\"vsize\"]               =\n              eos::common::StringConversion::GetReadableSizeString(s1, osstat.vsize, \"b\");\n            stats[\"rss\"]                 =\n              eos::common::StringConversion::GetReadableSizeString(s2, osstat.rss, \"b\");\n            stats[\"pid\"]                 = (Json::UInt) getpid();\n            stats[\"version\"]             = VERSION;\n            stats[\"fuseversion\"]         = FUSE_USE_VERSION;\n            stats[\"starttime\"]           = (Json::LargestUInt) starttime;\n            stats[\"uptime\"]              = (Json::LargestUInt) time(NULL) - starttime;\n            stats[\"total-mem\"]           = (Json::LargestUInt) totalram;\n            stats[\"free-mem\"]            = (Json::LargestUInt) freeram;\n            stats[\"load\"]                = (Json::LargestUInt) loads0;\n            stats[\"total-rbytes\"]        = (Json::LargestUInt) rbytes;\n            stats[\"total-wbytes\"]        = (Json::LargestUInt) wbytes;\n            stats[\"total-io-ops\"]        = (Json::LargestUInt) nops;\n            stats[\"read-mb/s\"]           = (double) total_rbytes;\n            stats[\"write-mb/s\"]          = (double) total_wbytes;\n            stats[\"iops\"]                = sum;\n            stats[\"forgetq\"]             = (Json::LargestUInt) fs.forgetq_size.load();\n            {\n              std::unique_lock<std::mutex> fs_lock {fs.mutex};\n              stats[\"inodes\"]              = (Json::LargestUInt)fs.inodes.size();\n            }\n            jsonstats[\"stats\"] = stats;\n          }\n        }\n      }\n    }\n    std::string tmpjsonfile = fs.jsonpath + \"~\";\n    std::ofstream dumpjsonfile(tmpjsonfile);\n    {\n      // atomic re-write+replace\n      dumpjsonfile <<  Json::writeString(writer, jsonstats);\n      if (::chmod(tmpjsonfile.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) {\n        fprintf(stderr, \"error: failed to chmod <json> stats file to 644 '%s'\\n\",\n                tmpjsonfile.c_str());\n      }\n\n      if (::rename(tmpjsonfile.c_str(), fs.jsonpath.c_str())) {\n        fprintf(stderr,\n                \"error: failed to rename <json> stats file from temporary to final name '%s'=>'%s'\\n\",\n                tmpjsonfile.c_str(),\n                fs.jsonpath.c_str());\n      }\n    }\n    assistant.wait_for(std::chrono::seconds(1));\n  }\n}\n\nFs::Fs()\n{\n  dropcache = false;\n  idletime = 60;\n  fusestat.Add(\"getattr\", 0, 0, 0);\n  fusestat.Add(\"setattr\", 0, 0, 0);\n  fusestat.Add(\"setattr:chown\", 0, 0, 0);\n  fusestat.Add(\"setattr:chmod\", 0, 0, 0);\n  fusestat.Add(\"setattr:utimes\", 0, 0, 0);\n  fusestat.Add(\"setattr:truncate\", 0, 0, 0);\n  fusestat.Add(\"lookup\", 0, 0, 0);\n  fusestat.Add(\"opendir\", 0, 0, 0);\n  fusestat.Add(\"readdir\", 0, 0, 0);\n  fusestat.Add(\"readdirplus\", 0, 0, 0);\n  fusestat.Add(\"releasedir\", 0, 0, 0);\n  fusestat.Add(\"fsyncdir\", 0, 0, 0);\n  fusestat.Add(\"statfs\", 0, 0, 0);\n  fusestat.Add(\"mknod\", 0, 0, 0);\n  fusestat.Add(\"mkdir\", 0, 0, 0);\n  fusestat.Add(\"rm\", 0, 0, 0);\n  fusestat.Add(\"unlink\", 0, 0, 0);\n  fusestat.Add(\"rmdir\", 0, 0, 0);\n  fusestat.Add(\"rename\", 0, 0, 0);\n  fusestat.Add(\"access\", 0, 0, 0);\n  fusestat.Add(\"open\", 0, 0, 0);\n  fusestat.Add(\"create\", 0, 0, 0);\n  fusestat.Add(\"read\", 0, 0, 0);\n  fusestat.Add(\"write\", 0, 0, 0);\n  fusestat.Add(\"release\", 0, 0, 0);\n  fusestat.Add(\"fsync\", 0, 0, 0);\n  fusestat.Add(\"fallocate\", 0, 0, 0);\n  fusestat.Add(\"flock\", 0, 0, 0);\n  fusestat.Add(\"forget\", 0, 0, 0);\n  fusestat.Add(\"forgetmulti\", 0, 0, 0);\n  fusestat.Add(\"flush\", 0, 0, 0);\n  fusestat.Add(\"getxattr\", 0, 0, 0);\n  fusestat.Add(\"setxattr\", 0, 0, 0);\n  fusestat.Add(\"listxattr\", 0, 0, 0);\n  fusestat.Add(\"removexattr\", 0, 0, 0);\n  fusestat.Add(\"readlink\", 0, 0, 0);\n  fusestat.Add(\"symlink\", 0, 0, 0);\n  fusestat.Add(\"link\", 0, 0, 0);\n  fusestat.Add(__SUM__TOTAL__, 0, 0, 0);\n}\n\nvoid\nFs::Run()\n{\n  tFdLeveler.reset(&Fs::LevelFDs, this);\n  tDumpStatistic.reset(&Fs::DumpStatistic, this);\n  tStatCirculate.reset(&Fs::StatCirculate, this);\n}\n\nFs::~Fs()\n{\n  tFdLeveler.join();\n  tDumpStatistic.join();\n  tStatCirculate.join();\n}\n\nint main(int argc, char* argv[])\n{\n  // Parse command line options\n  auto options = parse_options(argc, argv);\n  // We need an fd for every dentry in our the filesystem that the\n  // kernel knows about. This is way more than most processes need,\n  // so try to get rid of any resource softlimit.\n  // Initialize filesystem root\n  fs.root.fd = -1;\n  fs.root.nlookup = 9999;\n  fs.timeout = 0;\n  fs.source = \"/@eoscfsd/\";\n\n  if (fs.name.empty()) {\n    fs.name = \"default\";\n  }\n\n  fs.keyresource = \"cernhome-server.cern.ch/\";\n  fs.keyresource += fs.name;\n  fs.keyresource += \".key\";\n  fs.keyfile = fs.name;\n  fs.keyfile += \".key\";\n\n  fs.starttime = time(NULL);\n  // cernhome mount\n  system(\"mkdir -p /@eoscfsd/\");\n  struct stat stat;\n  auto ret = lstat(fs.source.c_str(), &stat);\n\n  if (ret == -1) {\n    err(1, \"ERROR: failed to stat source (\\\"%s\\\")\", fs.source.c_str());\n  }\n\n  if (!S_ISDIR(stat.st_mode)) {\n    errx(1, \"ERROR: source is not a directory\");\n  }\n\n  fs.src_dev = stat.st_dev;\n  // Initialize fuse\n  fuse_args args = FUSE_ARGS_INIT(0, nullptr);\n  std::string nameopt = \"fsname=\";\n  nameopt += fs.name;\n  nameopt += \",allow_other,subtype=eoscfs\";\n\n  for (auto i : options) {\n    fprintf(stderr, \"options: %s\\n\", i.c_str());\n  }\n\n  if (fuse_opt_add_arg(&args, argv[0]) ||\n      fuse_opt_add_arg(&args, \"-o\") ||\n      fuse_opt_add_arg(&args, nameopt.c_str()) ||\n      (options.count(\"debug-fuse\") && fuse_opt_add_arg(&args, \"-odebug\"))) {\n    errx(3, \"ERROR: Out of memory\");\n  }\n\n  fuse_lowlevel_ops sfs_oper {};\n  assign_operations(sfs_oper);\n  // Setup credential cache\n  CredentialConfig cconfig;\n  cconfig.fuse_shared = true;\n  cconfig.use_user_krb5cc = true;\n  cconfig.use_user_oauth2 = false;\n  cconfig.use_user_unix = false;\n  cconfig.ignore_containerization = true;\n  cconfig.use_user_gsiproxy = false;\n  cconfig.use_user_sss = false;\n  cconfig.credentialStore = \"/var/cache/eos/cfsd/credential-store/\";\n  cconfig.tryKrb5First = true;\n  cconfig.environ_deadlock_timeout = 100;\n  cconfig.forknoexec_heuristic = true;\n  Json::Value root;\n\n  if (!cfsutil::checkAndCreateDirectory(cconfig.credentialStore)) {\n    fuse_opt_free_args(&args);\n    return 1;\n  }\n  try {\n    std::ifstream file(\"/etc/eos/cfsd/eoscfsd.conf\");\n    file >> root;\n  } catch (...) {\n    fprintf(stderr,\n            \"# warning: couldn't/didn't parse /etc/eos/cfsd/eoscfsd.conf\\n\");\n  }\n\n  std::string cmd;\n  std::string scmd;\n  fs.k5domain = \"CERN.CH\";\n\n  if (root.isMember(\"auth\") && root[\"auth\"].isMember(\"k5domain\")) {\n    fs.k5domain = root[\"auth\"][\"k5domain\"].asString();\n  }\n\n  fs.k5domain.insert(0, \"@\");\n  fprintf(stderr, \"info: kerberos domain is '%s'\\n\", fs.k5domain.c_str());\n\n  if (root.isMember(fs.name) && root[fs.name].isMember(\"server\")) {\n    if (root[fs.name][\"server\"].asString().empty()) {\n      // compiled in mount-script\n      fs.keyresource = \"\";\n      fs.keyfile = fs.name;\n      fs.keyfile += \".key\";\n    } else {\n      // fetch mount script\n      fs.keyresource = root[fs.name][\"server\"].asString();\n      fs.keyresource += \"/\";\n      fs.keyresource += fs.name;\n      fs.keyresource += \".key\";\n      fs.keyfile = fs.name;\n      fs.keyfile += \".key\";\n    }\n  }\n\n  std::string keyfile = \"/etc/eos/cfsd/\" + fs.keyfile;\n  struct stat buf;\n  if (::stat(keyfile.c_str(), &buf)) {\n    // if there is no dedicated key, fall back to the default key\n    keyfile = \"/etc/eos/cfsd/cfsd.key\";\n  }\n\n  //  fs.dcache.setMaxSize(4096);\n  fs.se = fuse_session_new(&args, &sfs_oper, sizeof(sfs_oper), &fs);\n\n  if (fs.se == nullptr) {\n    goto err_out1;\n  }\n\n  if (fuse_set_signal_handlers(fs.se) != 0) {\n    goto err_out2;\n  }\n\n  // umount us\n  umount();\n  // Don't apply umask, use modes exactly as specified\n  umask(0);\n  // Mount and run main loop\n  struct fuse_loop_config loop_config;\n  loop_config.clone_fd = 1;\n  loop_config.max_idle_threads = 10;\n\n  if (fuse_session_mount(fs.se, fs.mount.c_str()) != 0) {\n    goto err_out3;\n  }\n\n  fprintf(stderr, \"# unsharing\\n\");\n\n  // unshare mount namespace\n  if (unshare(CLONE_NEWNS)) {\n    fprintf(stderr, \"warning: failed to unshare mount namespace errno=%d\\n\", errno);\n  }\n\n  fprintf(stderr, \"# re-mounting\\n\");\n\n  if (mount(\"none\", \"/\", NULL, MS_REC | MS_PRIVATE, NULL)) {\n    fprintf(stderr, \"warning: failed none mount / - errno=%d\\n\", errno);\n  }\n\n  fprintf(stderr, \"# mounting %s\\n\", fs.name.c_str());\n#include \"overlay.hh\"\n\n  if (!fs.keyresource.empty()) {\n    // fetch mount instruction remote\n    cmd = cfskey::get(fs.keyresource);\n  } else {\n    // this will be provided by overlay.hh\n  }\n\n  fs.logpath = \"/var/log/eos/cfsd/\";\n  fs.logpath += fs.name;\n  fs.jsonpath = fs.logpath;\n  fs.logpath += \".log\";\n  fs.jsonpath += \".json\";\n  struct stat buf1;\n  struct stat buf2;\n  fprintf(stderr, \"# mounting backends ...\\n\");\n  ::stat(fs.source.c_str(), &buf1);\n  pid_t child;\n\n  if (!(child = fork())) {\n    eos::common::Untraceable();\n    scmd = prepare(cmd, keyfile);\n    if (scmd.empty()) {\n      fprintf(stderr, \"error: cannot decrypt the mount instruction - wrong key\\n\");\n      exit(-1);\n    }\n    execute(scmd);\n    exit(0);\n  } else {\n    time_t a_time = time(NULL);\n\n    do {\n      ::stat(fs.source.c_str(), &buf2);\n\n      if (buf1.st_ino != buf2.st_ino) {\n        kill(child, 9);\n        break;\n      }\n\n      time_t b_time = time(NULL);\n\n      if ((b_time - a_time) > 10) {\n        kill(child, 9);\n        fprintf(stderr, \"error: internal mount failed\\n\");\n        exit(-1);\n      }\n    } while (1);\n  }\n\n  fprintf(stderr, \"# backends mounted ...\");\n  system(\"ls -la /@eoscfsd/\");\n  fs.root.fd = open(fs.source.c_str(), O_PATH);\n\n  if (fs.root.fd == -1) {\n    err(1, \"ERROR: open(\\\"%s\\\", O_PATH)\", fs.source.c_str());\n  }\n\n  if (fuse_daemonize(fs.foreground) != -1) {\n    eos::common::Logging::GetInstance().SetUnit(\"FUSE@eoscfsd\");\n    eos::common::Logging::GetInstance().gShortFormat = true;\n    eos::common::Logging::GetInstance().SetIndexSize(512);\n\n    if (fs.debug) {\n      eos::common::Logging::GetInstance().SetLogPriority(LOG_DEBUG);\n    } else {\n      eos::common::Logging::GetInstance().SetLogPriority(LOG_WARNING);\n    }\n\n    // start threads now\n    fs.Run();\n\n    if (!fs.foreground) {\n      FILE* fstderr = 0;\n      eos::common::Path cPath(fs.logpath.c_str());\n      cPath.MakeParentPath(S_IRWXU | S_IRGRP | S_IROTH | S_IXGRP | S_IXOTH);\n\n      if (!(fstderr = freopen(cPath.GetPath(), \"a+\", stderr))) {\n        fprintf(stderr, \"error: cannot open log file %s\\n\", cPath.GetPath());\n        exit(-1);\n      } else if (::chmod(cPath.GetPath(), S_IRUSR | S_IWUSR)) {\n        fprintf(stderr, \"error: failed to chmod %s\\n\", cPath.GetPath());\n        exit(-1);\n      }\n    }\n\n    eos_static_warning(\"********************************************************************************\");\n    eos_static_warning(\"eoscfsd started version %s - FUSE protocol version %d\",\n                       VERSION, FUSE_USE_VERSION);\n    cfslogin::initializeProcessCache(cconfig);\n    maximize_fd_limit();\n    maximize_priority();\n\n    if (options.count(\"single\")) {\n      ret = fuse_session_loop(fs.se);\n    } else {\n      ret = fuse_session_loop_mt(fs.se, &loop_config);\n    }\n\n    eos_static_warning(\"eoscfsd stopped version %s - FUSE protocol version %d\",\n                       VERSION, FUSE_USE_VERSION);\n    eos_static_warning(\"********************************************************************************\");\n    fuse_session_unmount(fs.se);\nerr_out3:\n    fuse_remove_signal_handlers(fs.se);\nerr_out2:\n    fuse_session_destroy(fs.se);\nerr_out1:\n    fuse_opt_free_args(&args);\n    return ret ? 1 : 0;\n  } else {\n    fprintf(stderr, \"error: failed to daemonize\\n\");\n    exit(errno ? errno : -1);\n  }\n}\n\n\n"
  },
  {
    "path": "fusex/eoscfsd/eoscfsd.hh",
    "content": "//! @file eoscfsd.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief EOS ClientFS C++ Fuse low-level implementation\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#define FUSE_USE_VERSION 35\n#ifndef _GNU_SOURCE\n#define _GNU_SOURCE\n#endif\n\n// C includes\n#include <dirent.h>\n#include <err.h>\n#include <errno.h>\n#include <ftw.h>\n#include <fuse3/fuse_lowlevel.h>\n#include <inttypes.h>\n#include <string.h>\n#include <sys/file.h>\n#include <sys/wait.h>\n#include <sys/resource.h>\n#include <sys/xattr.h>\n#include <sys/mount.h>\n#include <sys/fsuid.h>\n#include <time.h>\n#include <unistd.h>\n#include <pthread.h>\n#include <limits.h>\n#include <sys/ptrace.h>\n// C++ includes\n#include <cstddef>\n#include <cstdio>\n#include <cstdlib>\n#include <list>\n#include <mutex>\n#include <fstream>\n#include <thread>\n#include <iomanip>\n#include <atomic>\n#include <deque>\n#include \"cfslogin.hh\"\n#include \"cfsquota.hh\"\n#include \"cfsrecycle.hh\"\n#include \"cfsvattr.hh\"\n#include \"cfskey.hh\"\n#include \"stat/Stat.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/LRU.hh\"\nusing namespace std;\n\n/*----------------------------------------------------------------------------*/\n\n/* We are re-using pointers to our `struct sfs_inode` and `struct\n   sfs_dirp` elements as inodes and file handles. This means that we\n   must be able to store pointer a pointer in both a fuse_ino_t\n   variable and a uint64_t variable (used for file handles). */\nstatic_assert(sizeof(fuse_ino_t) >= sizeof(void*),\n              \"void* must fit into fuse_ino_t\");\nstatic_assert(sizeof(fuse_ino_t) >= sizeof(uint64_t),\n              \"fuse_ino_t must be at least 64 bits\");\n\n\n/* Forward declarations */\nstruct Inode;\n\n// Uniquely identifies a file in the source directory tree. This could\n// be simplified to just ino_t since we require the source directory\n// not to contain any mountpoints. This hasn't been done yet in case\n// we need to reconsider this constraint (but relaxing this would have\n// the drawback that we can no longer re-use inode numbers, and thus\n// readdir() would need to do a full lookup() in order to report the\n// right inode number).\ntypedef std::pair<ino_t, dev_t> SrcId;\n\n// Define a hash function for SrcId\nnamespace std\n{\ntemplate<>\nstruct hash<SrcId> {\n  size_t operator()(const SrcId& id) const\n  {\n    return hash<ino_t> {}(id.first) ^ hash<dev_t> {}(id.second);\n  }\n};\n}\n\n// Maps files in the source directory tree to inodes\ntypedef std::unordered_map<SrcId, Inode> InodeMap;\n\ntypedef struct forgetentry {\n  forgetentry()\n  {\n    parent = 0;\n    tst = 0;\n  }\n  forgetentry(ino_t p, std::string n) : parent(p), name(n)\n  {\n    tst = time(NULL);\n  };\n  ino_t parent;\n  std::string name;\n  time_t tst;\n} forgetentry_t;\n\ntypedef std::deque<shared_ptr<forgetentry_t>> ForgetQueue;\ntypedef  std::pair<uid_t, gid_t> userid_t;\ntypedef std::map<int, userid_t> OpenFds;\n\n\nstruct Inode {\n  int fd { -1};\n  dev_t src_dev {0};\n  ino_t src_ino {0};\n  int generation {0};\n  uint64_t nopen {0};\n  uint64_t nlookup {0};\n  std::mutex m;\n\n  // Delete copy constructor and assignments. We could implement\n  // move if we need it.\n  Inode() = default;\n  Inode(const Inode&) = delete;\n  Inode(Inode&& inode) = delete;\n  Inode& operator=(Inode&& inode) = delete;\n  Inode& operator=(const Inode&) = delete;\n\n  ~Inode()\n  {\n    if (fd > 0) {\n      close(fd);\n    }\n  }\n};\n\nclass Fs\n{\npublic:\n  void LevelFDs(ThreadAssistant& assistant);\n\n  Fs();\n  virtual ~Fs();\n  void Run();\n  // Must be acquired *after* any Inode.m locks.\n  std::mutex mutex;\n  InodeMap inodes; // protected by mutex\n  ForgetQueue forgetq;\n  std::atomic<size_t> forgetq_size;\n\n  std::mutex openFdsMutex;\n  OpenFds openFds;\n\n  Inode root;\n  double timeout;\n  bool debug;\n  bool recyclebin;\n  std::string source;\n  std::string name;\n  std::string mount;\n  std::string logpath;\n  std::string jsonpath;\n  std::string keyresource;\n  std::string keyfile;\n  time_t starttime;\n  size_t blocksize;\n  dev_t src_dev;\n  bool nosplice;\n  bool nocache;\n  bool dropcache;\n  bool foreground;\n  size_t idletime;\n  cfsquota quota;\n  cfsrecycle recycle;\n  std::string k5domain;\n  FILE* fstderr;\n\n  Stat& getFuseStat()\n  {\n    return fusestat;\n  }\n\n  struct fuse_session* se;\nprivate:\n  AssistedThread tFdLeveler;\n  AssistedThread tDumpStatistic;\n  AssistedThread tStatCirculate;\n  std::string umount_system_line;\n  void DumpStatistic(ThreadAssistant& assistant);\n  void StatCirculate(ThreadAssistant& assistant);\n  Stat fusestat;\n};\n\nstatic Fs fs{};\n\n\n#define FUSE_BUF_COPY_FLAGS                      \\\n        (fs.nosplice ?                           \\\n            FUSE_BUF_NO_SPLICE :                 \\\n            static_cast<fuse_buf_copy_flags>(0))\n\n\nstruct DirHandle {\n  DIR* dp {nullptr};\n  off_t offset;\n\n  DirHandle() = default;\n  DirHandle(const DirHandle&) = delete;\n  DirHandle& operator=(const DirHandle&) = delete;\n\n  ~DirHandle()\n  {\n    if (dp) {\n      closedir(dp);\n    }\n  }\n};\n\n\nclass FsID\n{\npublic:\n  FsID(fuse_req_t req)\n  {\n    //fprintf(stderr,\"username=%s exec=%s\\n\", cfslogin::name(req).c_str(), cfslogin::executable(req).c_str());\n    uid_t myuid = 99;\n    gid_t mygid = 99;\n    name = cfslogin::translate(req, myuid, mygid);\n    setfsuid(myuid);\n    setfsgid(mygid);\n    uid = myuid;\n    gid = mygid;\n  }\n  FsID(uid_t u, gid_t g) : uid(u), gid(g)\n  {\n    setfsuid(uid);\n    setfsgid(gid);\n  }\n  virtual ~FsID()\n  {\n    setfsuid(0);\n    setfsuid(0);\n  }\n\n  uid_t getUid() const\n  {\n    return uid;\n  }\n  gid_t getGid() const\n  {\n    return gid;\n  }\n  std::string getName() const\n  {\n    return name;\n  }\n\nprivate:\n  uid_t uid;\n  gid_t gid;\n  std::string name;\n};\n\n"
  },
  {
    "path": "fusex/eoscfsd/keychange.hh",
    "content": "{\n  const char* mod = AY_OBFUSCATE(\"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\");\n\n  for (size_t i = 0; i < key.length(); ++i)\n  {\n    key[i] ^= mod[i];\n  }\n}\n\n\n\n"
  },
  {
    "path": "fusex/eoscfsd/obfuscate.hh",
    "content": "/* --------------------------------- ABOUT -------------------------------------\n\nOriginal Author: Adam Yaxley\nWebsite: https://github.com/adamyaxley\nLicense: See end of file\n\nObfuscate\nGuaranteed compile-time string literal obfuscation library for C++14\n\nUsage:\nPass string literals into the AY_OBFUSCATE macro to obfuscate them at compile\ntime. AY_OBFUSCATE returns a reference to an ay::obfuscated_data object with the\nfollowing traits:\n  - Guaranteed obfuscation of string\n  The passed string is encrypted with a simple XOR cipher at compile-time to\n  prevent it being viewable in the binary image\n  - Global lifetime\n  The actual instantiation of the ay::obfuscated_data takes place inside a\n  lambda as a function level static\n  - Implicitly convertable to a char*\n  This means that you can pass it directly into functions that would normally\n  take a char* or a const char*\n\nExample:\nconst char* obfuscated_string = AY_OBFUSCATE(\"Hello World\");\nstd::cout << obfuscated_string << std::endl;\n\n----------------------------------------------------------------------------- */\n\n// Workaround for __LINE__ not being constexpr when /ZI (Edit and Continue) is enabled in Visual Studio\n// See: https://developercommunity.visualstudio.com/t/-line-cannot-be-used-as-an-argument-for-constexpr/195665\n#ifdef _MSC_VER\n#define AY_CAT(X,Y) AY_CAT2(X,Y)\n#define AY_CAT2(X,Y) X##Y\n#define AY_LINE int(AY_CAT(__LINE__,U))\n#else\n#define AY_LINE __LINE__\n#endif\n\n#ifndef AY_OBFUSCATE_DEFAULT_KEY\n// The default 64 bit key to obfuscate strings with.\n// This can be user specified by defining AY_OBFUSCATE_DEFAULT_KEY before\n// including obfuscate.h\n#define AY_OBFUSCATE_DEFAULT_KEY ay::generate_key(AY_LINE)\n#endif\n\nnamespace ay\n{\nusing size_type = unsigned long long;\nusing key_type = unsigned long long;\n\n// Generate a pseudo-random key that spans all 8 bytes\nconstexpr key_type generate_key(key_type seed)\n{\n  // Use the MurmurHash3 64-bit finalizer to hash our seed\n  key_type key = seed;\n  key ^= (key >> 33);\n  key *= 0xff51afd7ed558ccd;\n  key ^= (key >> 33);\n  key *= 0xc4ceb9fe1a85ec53;\n  key ^= (key >> 33);\n  // Make sure that a bit in each byte is set\n  key |= 0x0101010101010101ull;\n  return key;\n}\n\n// Obfuscates or deobfuscates data with key\nconstexpr void cipher(char* data, size_type size, key_type key)\n{\n  // Obfuscate with a simple XOR cipher based on key\n  for (size_type i = 0; i < size; i++) {\n    data[i] ^= char(key >> ((i % 8) * 8));\n  }\n}\n\n// Obfuscates a string at compile time\ntemplate <size_type N, key_type KEY>\nclass obfuscator\n{\npublic:\n  // Obfuscates the string 'data' on construction\n  constexpr obfuscator(const char* data)\n  {\n    // Copy data\n    for (size_type i = 0; i < N; i++) {\n      m_data[i] = data[i];\n    }\n\n    // On construction each of the characters in the string is\n    // obfuscated with an XOR cipher based on key\n    cipher(m_data, N, KEY);\n  }\n\n  constexpr const char* data() const\n  {\n    return &m_data[0];\n  }\n\n  constexpr size_type size() const\n  {\n    return N;\n  }\n\n  constexpr key_type key() const\n  {\n    return KEY;\n  }\n\nprivate:\n\n  char m_data[N] {};\n};\n\n// Handles decryption and re-encryption of an encrypted string at runtime\ntemplate <size_type N, key_type KEY>\nclass obfuscated_data\n{\npublic:\n  obfuscated_data(const obfuscator<N, KEY>& obfuscator)\n  {\n    // Copy obfuscated data\n    for (size_type i = 0; i < N; i++) {\n      m_data[i] = obfuscator.data()[i];\n    }\n  }\n\n  ~obfuscated_data()\n  {\n    // Zero m_data to remove it from memory\n    for (size_type i = 0; i < N; i++) {\n      m_data[i] = 0;\n    }\n  }\n\n  // Returns a pointer to the plain text string, decrypting it if\n  // necessary\n  operator char* ()\n  {\n    decrypt();\n    return m_data;\n  }\n\n  // Manually decrypt the string\n  void decrypt()\n  {\n    if (m_encrypted) {\n      cipher(m_data, N, KEY);\n      m_encrypted = false;\n    }\n  }\n\n  // Manually re-encrypt the string\n  void encrypt()\n  {\n    if (!m_encrypted) {\n      cipher(m_data, N, KEY);\n      m_encrypted = true;\n    }\n  }\n\n  // Returns true if this string is currently encrypted, false otherwise.\n  bool is_encrypted() const\n  {\n    return m_encrypted;\n  }\n\nprivate:\n\n  // Local storage for the string. Call is_encrypted() to check whether or\n  // not the string is currently obfuscated.\n  char m_data[N];\n\n  // Whether data is currently encrypted\n  bool m_encrypted{ true };\n};\n\n// This function exists purely to extract the number of elements 'N' in the\n// array 'data'\ntemplate <size_type N, key_type KEY = AY_OBFUSCATE_DEFAULT_KEY>\nconstexpr auto make_obfuscator(const char(&data)[N])\n{\n  return obfuscator<N, KEY>(data);\n}\n}\n\n// Obfuscates the string 'data' at compile-time and returns a reference to a\n// ay::obfuscated_data object with global lifetime that has functions for\n// decrypting the string and is also implicitly convertable to a char*\n#define AY_OBFUSCATE(data) AY_OBFUSCATE_KEY(data, AY_OBFUSCATE_DEFAULT_KEY)\n\n// Obfuscates the string 'data' with 'key' at compile-time and returns a\n// reference to a ay::obfuscated_data object with global lifetime that has\n// functions for decrypting the string and is also implicitly convertable to a\n// char*\n#define AY_OBFUSCATE_KEY(data, key) \\\n  []() -> ay::obfuscated_data<sizeof(data)/sizeof(data[0]), key>& { \\\n    static_assert(sizeof(decltype(key)) == sizeof(ay::key_type), \"key must be a 64 bit unsigned integer\"); \\\n    static_assert((key) >= (1ull << 56), \"key must span all 8 bytes\"); \\\n    constexpr auto n = sizeof(data)/sizeof(data[0]); \\\n    constexpr auto obfuscator = ay::make_obfuscator<n, key>(data); \\\n    thread_local auto obfuscated_data = ay::obfuscated_data<n, key>(obfuscator); \\\n    return obfuscated_data; \\\n  }()\n\n/* -------------------------------- LICENSE ------------------------------------\n\nPublic Domain (http://www.unlicense.org)\n\nThis is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or distribute this\nsoftware, either in source code form or as a compiled binary, for any purpose,\ncommercial or non-commercial, and by any means.\n\nIn jurisdictions that recognize copyright laws, the author or authors of this\nsoftware dedicate any and all copyright interest in the software to the public\ndomain. We make this dedication for the benefit of the public at large and to\nthe detriment of our heirs and successors. We intend this dedication to be an\novert act of relinquishment in perpetuity of all present and future rights to\nthis software under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\nCONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n----------------------------------------------------------------------------- */\n"
  },
  {
    "path": "fusex/eoscfsd/overlay.hh",
    "content": "cmd = \"27T9szJ6Gsd9nC4hWvweqE4465eC1ARS\";\n"
  },
  {
    "path": "fusex/eosfusebind",
    "content": "#!/bin/bash\n#------------------------------------------------------------------------------\n# File eoslog\n# Futhor Geoffray Adde - CERN 2013\n#------------------------------------------------------------------------------\n\n#/************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2013 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************/\n\n#------------------------------------------------------------------------------\n# Description: Script used to bind credentials to the shared eos fuse mount\n#------------------------------------------------------------------------------\n\nfunction CheckCredDir\n{\n  if [[ `stat -c \"%a\" /var/run/eos/credentials` != \"1777\" ]]; then logger -s -t eosfusebind -p authpriv.err -- \"credential directory is not ready\"; exit 1; fi\n  if [[ `stat -c \"%a\" /var/run/eos/credentials/store` != \"1777\" ]]; then logger -s -t eosfusebind -p authpriv.err -- \"credential store directory is not ready\"; exit 2; fi\n}\n\nfunction ExitUsage\n{\n  local exename=`basename $0`\n  local nl=${#exename}\n  local f=\"%${nl}.${nl}s\"\n  echo\n  printf \"$f %s\\\\n\" $exename \"[-g|--global] [krb5|x509] [credfile]\"\n  printf \"$f %s\\\\n\\n\" \"\" \"bind the current session to given credentials\"\n  printf \"$f %s\\\\n\\n\" \"\" \"[-g] makes a global binding : bind the user instead of the session\"\n  printf \"$f %s\\\\n\" $exename \"-u|--unbind-session\"\n  printf \"$f %s\\\\n\\n\" \"\" \"unbind current session from any previsouly bound credentials\"\n  printf \"$f %s\\\\n\" $exename \"-U|--unbind-user\"\n  printf \"$f %s\\\\n\\n\" \"\" \"unbind all sessions of the current user from any previously bound credentials\"\n  printf \"$f %s\\\\n\" $exename \"-ug|--unbind-global\"\n  printf \"$f %s\\\\n\" $exename \"-s|--show-session\"\n  printf \"$f %s\\\\n\\n\" \"\" \"show current session binding\"\n  printf \"$f %s\\\\n\" $exename \"-S|--show-user\"\n  printf \"$f %s\\\\n\" \"\" \"show bindings of all sessions of current user\"\n  printf \"$f %s\\\\n\" $exename \"--pam\"\n  printf \"$f %s\\\\n\" \"\" \"PAM module mode using environment variable PAM_TYPE. Recognized values for this variable are open_session and close_session\"\n  echo\n  exit 3\n}\n\nfunction ParseArgs\n{\n  REQULNKGLB=0\n  REQULNKUSR=0\n  REQULNKSES=0\n  REQLSUSR=0\n  REQLSSES=0\n  REQGLOBBIND=0\n  AUTHTYPE=\"krb5\"\n  CREDTAG=\"\"\n  CREDISFILE=\"\"\n  if [[ $1 = \"--acron\" ]]; then\n\t# do some acron specfifcs\n\tshift 1\n  fi\n  if [[ $# = 1   &&  (  $1 = \"--pam\" ) ]]; then\n    if [[ \"$PAM_TYPE\" = \"close_session\" ]]; then\n      CURRENTSID=`ps -q $$ o sid=`\n      # we unbind the global binding if there is no other running session than the current one\n      if [[ \"`ps o sid= | grep -v $CURRENTSID | wc -l`\" = \"0\" ]]; then\n        REQULNKGLB=1; REQULNK=1;\n      fi\n      return\n    elif [[ \"$PAM_TYPE\" = \"open_session\" ]]; then\n      REQGLOBBIND=1;\n      return\n    else\n      logger -s -t eosfusebind -p authpriv.err -- \"PAM mode: Unknown PAM_TYPE='$PAM_TYPE'\"; exit 4\n    fi\n  fi\n  if [[ $# = 1   &&  (  $1 = \"-h\" || $1 = \"--help\" ) ]]; then ExitUsage; fi\n  if [[ $# = 1   &&  (  $1 = \"-u\" || $1 = \"--unbind-session\" ) ]]; then REQULNKSES=1; REQULNK=1; return; fi\n  if [[ $# = 1   &&  (  $1 = \"-U\" || $1 = \"--unbind-user\" ) ]]; then REQULNKUSR=1; REQULNK=1; return; fi\n  if [[ $# = 1   &&  (  $1 = \"-ug\" || $1 = \"--unbind-global\" ) ]]; then REQULNKGLB=1; REQULNK=1; return; fi\n  if [[ $# = 1   &&  (  $1 = \"-s\" || $1 = \"--show-session\" ) ]]; then REQLSSES=1; REQLS=1; return; fi\n  if [[ $# = 1   &&  (  $1 = \"-S\" || $1 = \"--show-user\" ) ]]; then REQLSUSR=1; REQLS=1; return; fi\n  if [[ $1 = \"-g\" || $1 = \"--global\" ]]; then REQGLOBBIND=1; shift; fi\n  if [[ $# -gt 1 ]]; then CREDTAG=$2; fi\n  if [[ $# -gt 0 ]]; then AUTHTYPE=$1; fi\n}\n\nfunction UpdateCredIsFile\n{\n  [[ \"$AUTHTYPE\" == \"x509\" ]] || [[ `echo $CREDTAG | grep -c ':'` = \"0\" || `echo $CREDTAG | cut -c1-5` = \"FILE:\" ]]\n  CREDISFILE=$?\n  # get rid of the 'FILE:' if it's specified\n  if [[ \"$CREDISFILE\" = \"0\" && `echo $CREDTAG | cut -c1-5` = \"FILE:\" ]]; then\n  CREDTAG=$(echo ${CREDTAG} | tail -c+6)\n  # if the mentioned file does not exist, discard its name\n  stat $CREDTAG  >/dev/null 2>&1\n  [[ \"$?\" == \"1\" ]] && CREDTAG=\"\";\n  fi;\n}\n\nfunction CheckArgs\n{\n  if [[ \"$EUID\" = \"0\" && \"$REQGLOBBIND\" = \"1\" ]]; then logger -s -t eosfusebind -p authpriv.err -- \"global user binding forbidden as root\"; exit 5; fi\n  if [[ \"$AUTHTYPE\" != \"krb5\" && \"$AUTHTYPE\" != \"x509\" ]]; then  logger -s -t eosfusebind -p authpriv.err -- \"invalid authentication type $AUTHTYPE\"; exit 6; fi\n  UpdateCredIsFile\n  if [[ \"$CREDISFILE\" = \"0\" ]]; then\n    if [[ \"X$CREDTAG\" != \"X\" && `stat -c \"%a\" $CREDTAG` != \"600\" ]]; then  logger -s -t eosfusebind -p authpriv.err -- \"credential file $CREDTAG does not exist or has bad permissions\"; exit 7; fi\n  fi\n}\n\nfunction GetKlistKrb5CredFile\n{\n  which klist >/dev/null 2>&1\n  if [[ \"$?\" = \"0\" ]]; then\n    CREDTAG=$(klist | head -1 | awk '{ print $3 }')\n  else\n    CREDTAG=$KRB5CCNAME\n  fi\n}\n\nfunction GetDefaultKrb5CredFile\n{\n  # if there is more than one credential, cannot chose a 'default'\n  klist -s\n  if [[ \"$?\" = \"1\" ]] ; then\n    return; # there is not ticket to grab there\n  fi\n  NCRED=$(klist -l | tail -n +3 | wc -l)\n  if [[ \"$NCRED\" != \"1\" ]]; then\n  return;\n  fi\n  CREDTAG=$(klist -l | tail -1 | awk '{print $2}')\n}\n\nfunction GetAutoKrb5CredFile\n{\n  GetKlistKrb5CredFile\n  UpdateCredIsFile\n  if [[ \"X$CREDTAG\" = \"X\" ]] || [[ \"${CREDISFILE}\" = \"0\" && `stat -c \"%a\" $CREDTAG` != \"600\" ]]; then  GetDefaultKrb5CredFile; fi\n  UpdateCredIsFile\n  if [[ \"X$CREDTAG\" = \"X\" ]] || [[ \"${CREDISFILE}\" = \"0\" && `stat -c \"%a\" $CREDTAG` != \"600\" ]]; then  logger -s -t eosfusebind -p authpriv.err -- \"could not find automatically a credential file\"; exit 8; fi\n  #echo \"using krb5 cache credential file $CREDTAG\"\n}\n\nfunction GetEnvX509CredFile\n{\n  CREDTAG=$X509_USER_PROXY\n}\n\nfunction GetDefaultX509CredFile\n{\n  CREDTAG=/tmp/x509up_u$UID\n}\n\nfunction GetAutoX509CredFile\n{\n  GetEnvX509CredFile\n  if [[ \"X$CREDTAG\" = \"X\" || `stat -c \"%a\" $CREDTAG` != \"600\" ]]; then GetDefaultX509CredFile; fi\n  if [[ \"X$CREDTAG\" = \"X\" || `stat -c \"%a\" $CREDTAG` != \"600\" ]]; then logger -s -t eosfusebind -p authpriv.err -- \"could not find automatically a credential file\"; exit 9; fi\n  #echo \"using x509 user proxy file $CREDTAG\"\n}\n\nfunction GetSymlinkBaseNameUser\n{\n  if test ! -n SymlinkBaseNameUser ; then return; fi\n  SymlinkBaseNameUser=\"/var/run/eos/credentials/uid${UID}\"\n}\n\nfunction GetSymlinkBaseNameSession\n{\n  if test ! -n SymlinkBaseNameSession ; then return; fi\n  GetSymlinkBaseNameUser\n  PID=$$\n  PROCPIDSTATCONTENT=$(cat /proc/$PID/stat 2> /dev/null)\n  PROCPIDSTATCONTENT=`echo $PROCPIDSTATCONTENT | sed 's/([^)]*)/execname/'`\n  SID=`echo $PROCPIDSTATCONTENT | awk '{ print $6 }'`\n  PROCPIDSTATCONTENT=$(cat /proc/$SID/stat 2> /dev/null)\n  PROCPIDSTATCONTENT=`echo $PROCPIDSTATCONTENT | sed 's/([^)]*)/execname/'`\n  SUT=`echo $PROCPIDSTATCONTENT | awk '{ print $22 }'`\n  let \"SUT/=`getconf CLK_TCK`\"\n  SymlinkBaseNameSession=\"${SymlinkBaseNameUser}_sid${SID}_sst${SUT}\"\n}\n\nfunction CheckSessionLeader\n{\n  if test ! -n SUT ; then\n    logger -s -t eosfusebind -p authpriv.err -- \"the session leader for the current session is not alive\";\n    exit 10;\n  fi\n}\n\nfunction ShowLink\n{\n  local LINK=\"$1\"\n  local SID=`ls $LINK | grep -oh \"sid[0-9]*\" | cut -c 4-`\n  local AUTHMET=\"${LINK##*.}\"\n  local FILE=`readlink -s $LINK`\n  printf \"%5d  %4s  %s\\\\n\" $SID $AUTHMET $FILE\n}\n\nfunction ShowGlob\n{\n  local LINK=\"$1\"\n  local AUTHMET=\"${LINK##*.}\"\n  local FILE=`readlink -s $LINK`\n  printf \"%4s  %s\\\\n\" $AUTHMET $FILE\n}\n\nfunction ShowHeader\n{\n  echo\n  echo \"current session id is $SID\"\n  echo \"   session credentials are:\"\n  printf \"%5s  %4s  %s\\\\n\" sid type credfile\n}\n\nfunction ShowHeaderGlob\n{\n  echo\n  echo \"current user is $USER\"\n  echo \"   user-global credentials are:\"\n  printf \"%4s  %s\\\\n\" type credfile\n}\n\nfunction GetActiveLinkedSessions\n{\n  if test ! -n ActiveLinkedSessions ; then return; fi\n  ActiveLinkedSessions=\"\"\n  UnactiveLinkedSessions=\"\"\n  local FILELIST=$(shopt -s nullglob; echo ${SymlinkBaseNameUser}_*)\n  if [[ \"X$FILELIST\" = \"X\" ]]; then return ; fi\n  for LINK in `echo $FILELIST` ;\n  do\n    local lSID=`ls $LINK | grep -oh \"sid[0-9]*\" | cut -c 4-`\n    local lSUT=`ls $LINK | grep -oh \"sst[0-9]*\" | cut -c 4-`\n    # check if the process is active\n    if test ! -d \"/proc/$lSID/\"; then UnactiveLinkedSessions=\"${UnactiveLinkedSessions} $LINK\"; continue; fi\n    local lPROCPIDSTATCONTENT=$(cat /proc/$lSID/stat 2> /dev/null)\n    local lPROCPIDSTATCONTENT=`echo $lPROCPIDSTATCONTENT | sed 's/([^)]*)/execname/'`\n    local lPROCSUT=`echo $lPROCPIDSTATCONTENT | awk '{ print $22 }'`\n    let \"lPROCSUT/=`getconf CLK_TCK`\"\n    # check the process has the same starting time\n    if [  $lPROCSUT != $lSUT  ]; then UnactiveLinkedSessions=\"${UnactiveLinkedSessions} $LINK\"; continue; fi\n    ActiveLinkedSessions=\"${ActiveLinkedSessions} $LINK\"\n  done\n}\n\nfunction ShowUserGlob\n{\n  GetSymlinkBaseNameUser\n  ShowHeaderGlob\n  local FILELIST=$(shopt -s nullglob; echo ${SymlinkBaseNameUser}.*)\n  #echo \"FILELIST=$FILELIST\"\n  local NOTHING=\"\"\n  for LINK in `echo $FILELIST` ;\n  do\n    # don't show lock files !\n    [[ $LINK =~ .*\\.lock ]] && continue\n    ShowGlob $LINK ;\n    NOTHING=\"FALSE\"\n  done\n  if [[ \"X$NOTHING\" = \"X\" ]]; then echo \"[none]\"; return ; fi\n  echo\n}\n\nfunction ShowSession\n{\n  GetSymlinkBaseNameSession\n  # if no session leader alive, nothing to show\n  if [[ \"X$SUT\" = \"X\" ]]; then return ; fi\n  local FILELIST=$(shopt -s nullglob; echo ${SymlinkBaseNameSession}*)\n  if [[ \"X$FILELIST\" != \"X\" ]]; then\n    ShowHeader\n    for LINK in `echo $FILELIST` ; do ShowLink $LINK ; done\n    echo\n    return\n  fi\n  GetSymlinkBaseNameUser\n  FILELIST=$(shopt -s nullglob; echo ${SymlinkBaseNameUser}.*)\n  if [[ \"X$FILELIST\" = \"X\" ]]; then\n    echo \"The current session is not bound to any credentials\"\n    return\n  fi\n  ShowUserGlob\n}\n\nfunction ShowUser\n{\n  GetSymlinkBaseNameSession\n  GetActiveLinkedSessions\n  ShowUserGlob\n  ShowHeader\n  local FILELIST=$(shopt -s nullglob; echo ${ActiveLinkedSessions})\n  if [[ \"X$FILELIST\" = \"X\" ]]; then echo \"[none]\"; return ; fi\n  for LINK in `echo $FILELIST` ; do ShowLink $LINK ; done\n  echo\n}\n\nfunction LinkSession\n{\n  GetSymlinkBaseNameSession\n  CheckSessionLeader\n  local EXTENSION=\"${AUTHTYPE}\"\n  if [[ \"$AUTHTYPE\" == \"krb5\" && \"${CREDISFILE}\" != \"0\" ]]; then\n  local EXTENSION=\"krk5\"\n  fi\n  if [[ \"X$CREDTAG\" = \"X\" ]] || [[ \"${CREDISFILE}\" = \"0\" && `stat -c \"%a\" $CREDTAG` != \"600\" ]]; then\n    logger -s -t eosfusebind -p authpriv.err -- \"could not find automatically a credential file\"\n    exit 11\n  fi\n  SymlinkName=\"${SymlinkBaseNameSession}.${EXTENSION}\"\n  #echo \"ln -sf $CREDTAG $SymlinkName\"\n  # create the symlink and then rename it so the overwrite is atomic\n  ln -sf $CREDTAG $SymlinkName.tmp\n  if [ $? -ne 0 ]; then\n    logger -s -t eosfusebind -p authpriv.err -- \"Error creating credentials symlink.\"\n  fi\n  mv -Tf $SymlinkName.tmp $SymlinkName\n  if [ $? -ne 0 ]; then\n    logger -s -t eosfusebind -p authpriv.err -- \"Error renaming temporary credentials symlink.\"\n  fi\n}\n\nfunction LinkUserGlob\n{\n  GetSymlinkBaseNameSession\n  local EXTENSION=\"${AUTHTYPE}\"\n  local CFILE=\"$CREDTAG\"\n  if [[ \"$AUTHTYPE\" == \"krb5\" && \"${CREDISFILE}\" != \"0\" ]]; then\n  local EXTENSION=\"krk5\"\n  fi\n  if [[ \"X$CREDTAG\" = \"X\" ]] || [[ \"${CREDISFILE}\" = \"0\" && `stat -c \"%a\" $CREDTAG` != \"600\" ]]; then\n    logger -s -t eosfusebind -p authpriv.err -- \"could not find automatically a credential file\"\n    exit 12\n  fi\n  SymlinkPathName=\"${SymlinkBaseNameUser}.${EXTENSION}\"\n  SymlinkName=$(basename $SymlinkPathName)\n  if [[ \"${CREDISFILE}\" = \"0\" ]]; then\n    # Create a temporary file\n    TEMP_FILE=$(mktemp /var/run/eos/credentials/store/temp-XXXXXXXXXXX)\n\n    if [ $? -ne 0 ]; then\n      logger -s -t eosfusebind -p authpriv.err -- \"Could not create temporary file inside /var/run/eos/credentials/store, something is wrong with the credential store.\"\n      exit 13\n    fi\n\n    # Ensure permissions are sane, should never fail\n    chmod 0600 \"${TEMP_FILE}\"\n    if [ $? -ne 0 ]; then\n      logger -s -t eosfusebind -p authpriv.err -- \"Could not chmod ${TEMP_FILE}, something is wrong in the credential store.\"\n      stat \"${TEMP_FILE}\" >&2\n      rm -f ${TEMP_FILE}\n      exit 14\n    fi\n\n    # Replace contents of temporary file, should never fail\n    cp -f \"${CREDTAG}\" \"${TEMP_FILE}\"\n    if [ $? -ne 0 ]; then\n      logger -s -t eosfusebind -p authpriv.err -- \"Could not replace contents of ${TEMP_FILE}, something is wrong in the credential store.\"\n      stat \"${TEMP_FILE}\" >&2\n      rm -f ${TEMP_FILE}\n      exit 15\n    fi\n\n    # Atomically replace CFILE with TEMP_FILE\n    CFILE=\"/var/run/eos/credentials/store/$SymlinkName\"\n    mv -f --no-target-directory \"${TEMP_FILE}\" \"${CFILE}\"\n\n    if [ $? -ne 0 ]; then\n      logger -s -t eosfusebind -p authpriv.err -- \"Could not replace ${CFILE} with ${TEMP_FILE}, something is wrong in the credential store.\"\n      stat \"${TEMP_FILE}\" >&2\n      stat \"${CFILE}\" >&2\n\n      rm -f ${TEMP_FILE}\n      exit 16\n    fi\n  fi\n  #echo \"ln -sf $CFILE $SymlinkPathName\"\n  # create the symlink and then rename it so the overwrite is atomic\n  ln -sf $CFILE $SymlinkPathName.tmp\n  if [ $? -ne 0 ]; then\n    logger -s -t eosfusebind -p authpriv.err -- \"Error creating credentials symlink.\"\n  fi\n  mv -Tf $SymlinkPathName.tmp $SymlinkPathName\n  if [ $? -ne 0 ]; then\n    logger -s -t eosfusebind -p authpriv.err -- \"Error renaming temporary credentials symlink.\"\n  fi\n  # cleanup the other credentials in the store as only one global credential is required at a time\n  find /var/run/eos/credentials/store -uid ${UID} -not -name $( basename ${CFILE} ) -exec rm -f \\{\\} \\; >/dev/null 2>&1\n}\n\nfunction UnlinkUserGlob\n{\n  GetSymlinkBaseNameUser\n  if [[ \"X$SymlinkBaseNameUser\" = \"X\" ]]; then \"error unbinding user\"; exit 17; fi\n  for symlink in ${SymlinkBaseNameUser}.*; do\n    # remove the file from the store\n    local file=$(readlink -f \"$symlink\")\n    rm -f \"$file\"\n    rm -f \"$symlink\"\n  done;\n  #echo rm -f ${SymlinkBaseNameUser}.*\n  rm -f ${SymlinkBaseNameUser}.*\n  # cleanup the other credentials in the store as no credentials is needed if there is no user global\n  find /var/run/eos/credentials/store -uid ${UID} -exec rm -f \\{\\} \\; >/dev/null 2>&1\n}\n\nfunction UnlinkUserSessions\n{\n  GetSymlinkBaseNameUser\n  CheckSessionLeader\n  #echo rm -f ${SymlinkBaseNameUser}_*\n  rm -f ${SymlinkBaseNameUser}_*\n}\n\nfunction UnlinkSession\n{\n  GetSymlinkBaseNameSession\n  if [[ \"X$SymlinkBaseNameSession\" = \"X\" ]]; then \"error unbinding session\"; exit 18; fi\n  #echo rm -f ${SymlinkBaseNameSession}*\n  rm -f ${SymlinkBaseNameSession}*\n}\n\nfunction UnlinkUnactiveSessions\n{\n  GetSymlinkBaseNameSession\n  GetActiveLinkedSessions\n  local FILELIST=$(shopt -s nullglob; echo ${UnactiveLinkedSessions})\n  if [[ \"X$FILELIST\" = \"X\" ]]; then return ; fi\n  #echo rm -f $FILELIST\n  rm -f $FILELIST\n}\n\n# if this a pam call, re-issue the command as the right user\nif [[ ( \"$#\" = 1 )   &&  (  \"$1\" = \"--pam\" ) && (  \"$EUID\" = \"0\" ) ]]; then\n  if [[ (  \"$PAM_USER\" != \"0\" ) && (  \"$PAM_USER\" != \"root\" ) ]]; then\n    export PATH=$PATH:/usr/sbin:/sbin\n    runuser -f $PAM_USER -c \"$0 $*\"\n    exit $?\n  else\n    logger -s -t eosfusebind -p authpriv.info -- \"note: eosfusebind not running for user root from pam.\"\n    exit 0\n  fi\nfi\n\n# re-iisue the call with a file lock to avoid race conditions\nif [[ \"A${EOSFUSEBIND_NOLOCK}\" != \"A1\" ]]; then\n  GetSymlinkBaseNameUser\n  env EOSFUSEBIND_NOLOCK=1 flock ${SymlinkBaseNameUser}.lock $0 $*\n  exit $?\nfi\n\nParseArgs $*\nCheckCredDir\nif [[ $REQULNK = 1 ]]; then\n  if [[ $REQULNKSES = 1 ]]; then UnlinkSession; fi\n  if [[ $REQULNKUSR = 1 ]]; then UnlinkUserSessions; fi\n  if [[ $REQULNKGLB = 1 ]]; then UnlinkUserGlob; fi\nelif [[ $REQLS = 1 ]]; then\n  if [[ $REQLSSES = 1 ]]; then ShowSession; fi\n  if [[ $REQLSUSR = 1 ]]; then ShowUser; fi\nelse\n  CheckArgs $*\n  UnlinkUnactiveSessions\n  if [[ \"$REQGLOBBIND\" = \"0\" ]]; then\n    UnlinkSession\n  fi\n  if [ \"X$CREDTAG\" = \"X\" ]; then\n    if [ \"$AUTHTYPE\" = \"krb5\" ]; then GetAutoKrb5CredFile; fi\n    if [ \"$AUTHTYPE\" = \"x509\" ]; then GetAutoX509CredFile; fi\n  fi\n  if [[ \"$REQGLOBBIND\" = \"1\" ]]; then\n    LinkUserGlob\n  else\n    LinkSession\n  fi\nfi\n"
  },
  {
    "path": "fusex/eosxd/eosfuse.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file eosfuse.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief EOS C++ Fuse low-level implementation (3rd generation)\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StacktraceHere.hh\"\n#ifndef __APPLE__\n#include \"common/ShellCmd.hh\"\n#endif\n#include \"kv/RocksKV.hh\"\n#include \"eosfuse.hh\"\n#include \"misc/fusexrdlogin.hh\"\n#include \"misc/filename.hh\"\n#include <string>\n#include <map>\n#include <set>\n#include <iostream>\n#include <sstream>\n#include <memory>\n#include <algorithm>\n#include <thread>\n#include <iterator>\n#ifndef __APPLE__\n#include <malloc.h>\n#endif\n#include <dirent.h>\n#include <stdint.h>\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <string.h>\n#include <sched.h>\n#include <json/json.h>\n\n#ifdef HAVE_RICHACL\nextern \"C\" { /* this 'extern \"C\"' brace will eventually end up in the .h file, then it can be removed */\n#include <sys/richacl.h>\n}\n#include \"misc/richacl.hh\"\n#endif\n\n#include <sys/resource.h>\n#include <sys/types.h>\n#include <sys/file.h>\n#include <sys/mount.h>\n\n#include \"common/XattrCompat.hh\"\n\n#ifdef __APPLE__\n#define O_DIRECT 0\n#define EKEYEXPIRED 127\n#define SI_LOAD_SHIFT 16\n#else\n#include <sys/resource.h>\n#endif\n\n#include \"common/Timing.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Path.hh\"\n#include \"common/LinuxMemConsumption.hh\"\n#include \"common/LinuxStat.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/SymKeys.hh\"\n#include \"auth/Logbook.hh\"\n#include \"md/md.hh\"\n#include \"md/kernelcache.hh\"\n#include \"kv/kv.hh\"\n#include \"data/cache.hh\"\n#include \"data/cachehandler.hh\"\n#include \"misc/ConcurrentMount.hh\"\n\n#define _FILE_OFFSET_BITS 64\n\nconst char* k_mdino = \"sys.eos.mdino\";\nconst char* k_nlink = \"sys.eos.nlink\";\nconst char* k_fifo = \"sys.eos.fifo\";\nEosFuse* EosFuse::sEosFuse = 0;\n\nnamespace\n{\nstd::string s_obfuscate_key = \"user.obfuscate.key\";\nstd::string s_encrypted_fp  = \"user.encrypted.fp\";\n}\n\n/* -------------------------------------------------------------------------- */\nstatic int\n/* -------------------------------------------------------------------------- */\nstartMount(ConcurrentMount& cmdet,\n           const std::string& mountpoint,\n           const std::string& fsname,\n           int& exitcode)\n/* -------------------------------------------------------------------------- */\n{\n  // use ConcurrentMount cmdet to detect existing eosxd and\n  // reattch by making a new mount if necessary.\n  //\n  // returns 0: continue with mount (enter fuse session loop)\n  // return -1: caller should exit with 'exitcode'.\n  //            a mount may have been reattached or\n  //            there may have been an error.\n  //\n  std::string source = fsname;\n\n  if (source.empty()) {\n    source = mountpoint;\n\n    if (size_t pos = source.rfind(\"/\"); pos != std::string::npos) {\n      source.erase(0, pos + 1);\n    }\n  }\n\n  int redo, retries = 3;\n  exitcode = 0;\n\n  do {\n    redo = 0;\n    int mntfd, rc;\n    rc = cmdet.StartMount(mntfd);\n\n    switch (rc) {\n    case 1: {\n      struct stat sb;\n      const int strc = lstat(mountpoint.c_str(), &sb);\n\n      if (strc < 0) {\n        fprintf(stderr, \"# detected concurrent eosxd, but error stating \"\n                \"mountpoint, exiting\\n\");\n        exitcode = 1;\n        return -1;\n      }\n\n      if (sb.st_ino == 1) {\n        fprintf(stderr, \"# detected concurrent eosxd, mount appears attached, \"\n                \"exiting\\n\");\n        exitcode = 0;\n        return -1;\n      }\n\n      if (mntfd < 0) {\n        fprintf(stderr, \"# detected concurrent eosxd, mount appears \"\n                \"not-attached but can not fetch fuse fd, exiting\\n\");\n        exitcode = 1;\n        return -1;\n      }\n\n      char mntopt[200], opt2[100];\n      *opt2 = '\\0';\n\n      if (getuid() == 0) {\n        strcpy(opt2, \",allow_other\");\n      }\n\n      snprintf(mntopt, sizeof(mntopt),\n               \"fd=%i,rootmode=%o,user_id=%d,group_id=%d%s\",\n               mntfd, sb.st_mode & S_IFMT, geteuid(), getegid(), opt2);\n      fprintf(stderr, \"# detected concurrent eosxd, \"\n              \"mounting using existing fuse descriptor\\n\");\n      const int retval = ::mount(source.c_str(), mountpoint.c_str(),\n#ifdef __LINUX__\n                                 \"fuse\", MS_NODEV | MS_NOSUID,\n\n#else\n                                 MNT_NODEV | MNT_NOSUID,\n#endif\n                                 mntopt);\n\n      if (retval) {\n        fprintf(stderr, \"# detected concurrent eosxd, but failed mount \"\n                \"with existing fuse descriptor%s\\n\",\n                retries ? \", retrying\" : \"\");\n\n        if (retries) {\n          retries--;\n          redo = 1;\n          std::this_thread::sleep_for(std::chrono::milliseconds(5000));\n        } else {\n          exitcode = 1;\n          return -1;\n        }\n      } else {\n        exitcode = 0;\n        return -1;\n      }\n    }\n    break;\n\n    case -1:\n      fprintf(stderr, \"# concurrent eosxd detection not available\\n\");\n      return 0;\n      break;\n\n    case 0:\n      fprintf(stderr, \"# concurrent eosxd detect enabled, lock prefix %s\\n\",\n              cmdet.lockpfx().c_str());\n      return 0;\n      break;\n\n    default:\n      fprintf(stderr, \"# unexpected condition during eosxd detection\\n\");\n      exitcode = 2;\n      return -1;\n      break;\n    }\n  } while (redo);\n\n  exitcode = 2;\n  return -1;\n}\n\n\n/* -------------------------------------------------------------------------- */\nEosFuse::EosFuse()\n{\n  sEosFuse = this;\n  fusesession = 0;\n#ifndef USE_FUSE3\n  fusechan = 0;\n#endif\n  SetTrace(false);\n}\n\n/* -------------------------------------------------------------------------- */\nEosFuse::~EosFuse()\n{\n}\n\n/* -------------------------------------------------------------------------- */\nstatic void\n/* -------------------------------------------------------------------------- */\nchmod_to_700_or_die(const std::string& path)\n/* -------------------------------------------------------------------------- */\n{\n  if (path.empty()) {\n    return;\n  }\n\n  if (chmod(path.c_str(), S_IRUSR | S_IWUSR | S_IXUSR) != 0) {\n    fprintf(stderr, \"error: failed to make path=%s RWX for root - errno=%d\",\n            path.c_str(), errno);\n    exit(-1);\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\n/* -------------------------------------------------------------------------- */\nEosFuse::UsageGet()\n{\n  std::string usage = \"usage CLI   : eosxd get <key> [<path>]\\n\";\n  usage += \"\\n\";\n  usage +=\n    \"                     eos.btime <path>                   : show inode birth time\\n\";\n  usage +=\n    \"                     eos.ttime <path>                   : show lastest mtime in tree\\n\";\n  usage +=\n    \"                     eos.tsize <path>                   : show size of directory tree\\n\";\n  usage +=\n    \"                     eos.dsize <path>                   : show total size of files inside a directory \\n\";\n  usage +=\n    \"                     eos.dcount <path>                  : show total number of directories inside a directory \\n\";\n  usage +=\n    \"                     eos.fcount <path>                  : show total number of files inside a directory\\n\";\n  usage +=\n    \"                     eos.name <path>                    : show EOS instance name for given path\\n\";\n  usage +=\n    \"                     eos.md_ino <path>                  : show inode number valid on MGM \\n\";\n  usage +=\n    \"                     eos.hostport <path>                : show MGM connection host + port for given path\\n\";\n  usage +=\n    \"                     eos.mgmurl <path>                  : show MGM URL for a given path\\n\";\n  usage +=\n    \"                     eos.stats <path>                   : show mount statistics\\n\";\n  usage +=\n    \"                     eos.stacktrace <path>              : test thread stack trace functionality\\n\";\n  usage +=\n    \"                     eos.quota <path>                   : show user quota information for a given path\\n\";\n  usage +=\n    \"                     eos.url.xroot                      : show the root:// protocol transport url for the given file\\n\";\n  usage +=\n    \"                     eos.reconnect <mount>              : reconnect and dump the connection credentials\\n\";\n  usage +=\n    \"                     eos.reconnectparent <mount>        : reconnect parent process and dump the connection credentials\\n\";\n  usage +=\n    \"                     eos.identity <mount>               : show credential assignment of the calling process\\n\";\n  usage +=\n    \"                     eos.identityparent <mount>         : show credential assignment of the executing shell\\n\";\n  usage += \"\\n\";\n  usage +=\n    \" as root             system.eos.md  <path>              : dump meta data for given path\\n\";\n  usage +=\n    \"                     system.eos.cap <path>              : dump cap for given path\\n\";\n  usage +=\n    \"                     system.eos.caps <mount>            : dump all caps\\n\";\n  usage +=\n    \"                     system.eos.vmap <mount>            : dump virtual inode translation table\\n\";\n  usage += \"\\n\";\n  return usage;\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\n/* -------------------------------------------------------------------------- */\nEosFuse::UsageSet()\n{\n  std::string usage = \"usage CLI   : eosxd set <key> <value> [<path>]\\n\";\n  usage += \"\\n\";\n  usage +=\n    \" as root             system.eos.debug <level> <mount>   : set debug level with <level>=crit|warn|err|notice|info|debug|trace\\n\";\n  usage +=\n    \"                     system.eos.dropcap - <mount>       : drop capability of the given path\\n\";\n  usage +=\n    \"                     system.eos.dropcaps - <mount>      : drop call capabilities for given mount\\n\";\n  usage +=\n    \"                     system.eos.resetstat - <mount>     : reset the statistic counters\\n\";\n  usage +=\n    \"                     system.eos.resetlru - <mount>      : reset the lru list and recompute it\\n\";\n  usage +=\n    \"                     system.eos.log <mode> <mount>      : make log file public or private with <mode>=public|private\\n\";\n  usage +=\n    \"                     system.eos.fuzz all|config <mount> : enabling fuzzing in all modes with scaler 1 (all) or switch back to the initial configuration (config)\\n\";\n  usage += \"\\n\";\n  return usage;\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\n/* -------------------------------------------------------------------------- */\nEosFuse::UsageMount()\n{\n  std::string usage =\n    \"usage FS    : eosxd -ofsname=<host><remote-path> <mnt-path>\\n\";\n  usage +=\n    \"                     eosxd -ofsname=<config-name> <mnt-path>\\n\";\n  usage +=\n    \"                        with configuration file /etc/eos/fuse.<config-name>.conf\\n\";\n  usage +=\n    \"                     mount -t fuse eosxd -ofsname=<host><remote-path> <mnt-path>\\n\";\n  usage +=\n    \"                     mount -t fuse eosxd -ofsname=<config-name> <mnt-path>\\n\";\n  usage += \"\\n\";\n  return usage;\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\n/* -------------------------------------------------------------------------- */\nEosFuse::UsageHelp()\n{\n  std::string usage =\n    \"usage HELP  : eosxd [-h|--help|help]                    : get help\\n\";\n  return usage;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nEosFuse::run(int argc, char* argv[], void* userdata)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Logging::GetInstance().LB->suspend();      /* no log thread yet */\n  eos_static_debug(\"\");\n  XrdCl::Env* env = XrdCl::DefaultEnv::GetEnv();\n  env->PutInt(\"RunForkHandler\", 1);\n  env->PutInt(\"WorkerThreads\", 10);\n  struct fuse_args args = FUSE_ARGS_INIT(argc, argv);\n  fuse_opt_parse(&args, NULL, NULL, NULL);\n#ifndef USE_FUSE3\n  char* local_mount_dir = 0;\n#else\n  struct fuse_cmdline_opts opts;\n#endif\n  int err = 0;\n  std::string no_fsync_list;\n  std::string nowait_flush_exec_list;\n  // check the fsname to choose the right JSON config file\n  std::string fsname = \"\";\n\n  if (argc == 1) {\n    fprintf(stderr, \"%s%s%s%s\", UsageGet().c_str(), UsageSet().c_str(),\n            UsageMount().c_str(), UsageHelp().c_str());\n    exit(0);\n  }\n\n  for (int i = 0; i < argc; i++) {\n    std::string option = argv[i];\n    size_t npos;\n    size_t epos;\n\n    if ((option == \"-h\") ||\n        (option == \"help\") ||\n        (option == \"--help\")) {\n      fprintf(stderr, \"%s%s%s%s\", UsageGet().c_str(), UsageSet().c_str(),\n              UsageMount().c_str(), UsageHelp().c_str());\n      exit(0);\n    }\n\n    if (option == \"get\") {\n      if ((i + 1) >= argc) {\n        fprintf(stderr, \"%s\\n\", UsageGet().c_str());\n        exit(-1);\n      }\n\n      std::string tag = argv[i + 1];\n#ifndef __APPLE__\n      std::string path = ((i + 2) >= argc) ? get_current_dir_name() : argv[i + 2];\n#else\n      std::string path = ((i + 2) >= argc) ? getenv(\"PWD\") : argv[i + 2];\n#endif\n      std::string systemline = \"getfattr --absolute-names --only-values -n \";\n      systemline += tag;\n      systemline += \" \";\n      systemline += path;\n      int rc = system(systemline.c_str());\n      exit(WEXITSTATUS(rc));\n    }\n\n    if (option == \"set\") {\n      if ((i + 2) >= argc) {\n        fprintf(stderr, \"%s\\n\", UsageSet().c_str());\n        exit(-1);\n      }\n\n      std::string tag = argv[i + 1];\n      std::string value = argv[i + 2];\n#ifndef __APPLE__\n      std::string path = ((i + 3) >= argc) ? get_current_dir_name() : argv[i + 3];\n#else\n      std::string path = ((i + 3) >= argc) ? getenv(\"PWD\") : argv[i + 3];\n#endif\n      std::string systemline = \"setfattr -n \";\n      systemline += tag;\n      systemline += \" -v \";\n      systemline += value;\n      systemline += \" \";\n      systemline += path;\n      int rc = system(systemline.c_str());\n      exit(WEXITSTATUS(rc));\n    }\n\n    if ((npos = option.find(\"fsname=\")) != std::string::npos) {\n      epos = option.find(\",\", npos);\n      fsname = option.substr(npos + std::string(\"fsname=\").length(),\n                             (epos != std::string::npos) ?\n                             epos - npos - std::string(\"fsname=\").length() : -1);\n      break;\n    }\n  }\n\n  fprintf(stderr, \"# fsname='%s'\\n\", fsname.c_str());\n\n  if (getuid() == 0) {\n    // the root mount always adds the 'allow_other' option\n    fuse_opt_add_arg(&args, \"-oallow_other\");\n#ifdef USE_FUSE3\n    fuse_opt_add_arg(&args, \"-oclone_fd\");\n#endif\n    fprintf(stderr, \"# -o allow_other enabled on shared mount\\n\");\n  }\n\n#ifndef USE_FUSE3\n  fprintf(stderr, \"# -o big_writes enabled\\n\");\n  fuse_opt_add_arg(&args, \"-obig_writes\");\n#endif\n  std::string jsonconfig = \"/etc/eos/fuse\";\n  std::string default_ssskeytab = \"/etc/eos/fuse.sss.keytab\";\n  std::string jsonconfiglocal;\n\n  if (geteuid()) {\n    if (getenv(\"HOME\")) {\n      jsonconfig = getenv(\"HOME\");\n    } else {\n      fprintf(stderr, \"# warning: HOME environment not defined\\n\");\n      jsonconfig = \".\";\n    }\n\n    jsonconfig += \"/.eos/fuse\";\n\n    if (getenv(\"HOME\")) {\n      default_ssskeytab = getenv(\"HOME\");\n    } else {\n      default_ssskeytab = \".\";\n    }\n\n    default_ssskeytab += \"/.eos/fuse.sss.keytab\";\n  }\n\n  if (fsname.length()) {\n    if (((fsname.find(\"@\") == std::string::npos)) &&\n        ((fsname.find(\":\") == std::string::npos))) {\n      jsonconfig += \".\";\n      jsonconfig += fsname;\n    }\n  }\n\n  jsonconfiglocal = jsonconfig;\n  jsonconfiglocal += \".local.conf\";\n  jsonconfig += \".conf\";\n#ifndef __APPLE__\n#ifdef USE_FUSE3\n\n  if (::access(\"/bin/fusermount3\", X_OK)) {\n    fprintf(stderr, \"error: /bin/fusermount3 is not executable for you!\\n\");\n    exit(-1);\n  }\n\n#else\n\n  if (::access(\"/bin/fusermount\", X_OK)) {\n    fprintf(stderr, \"error: /bin/fusermount is not executable for you!\\n\");\n    exit(-1);\n  }\n\n#endif\n#endif\n\n  if (getuid() == 0) {\n    unsetenv(\"KRB5CCNAME\");\n    unsetenv(\"X509_USER_PROXY\");\n  }\n\n  cacheconfig cconfig;\n  // ---------------------------------------------------------------------------------------------\n  // The logic of configuration works liks that:\n  // - every configuration value has a corresponding default value\n  // - the configuration file name is taken from the fsname option given on the command line\n  //   e.g. root> eosxd -ofsname=foo loads /etc/eos/fuse.foo.conf\n  //        root> eosxd              loads /etc/eos/fuse.conf\n  //        user> eosxd -ofsname=foo loads $HOME/.eos/fuse.foo.conf\n  // One can avoid to use configuration files if the defaults are fine providing the remote host and remote mount directory via the fsname\n  //   e.g. root> eosxd -ofsname=eos.cern.ch:/eos/ $HOME/eos mounts the /eos/ directory from eos.cern.ch shared under $HOME/eos/\n  //   e.g. user> eosxd -ofsname=user@eos.cern.ch:/eos/user/u/user/ $home/eos mounts /eos/user/u/user from eos.cern.ch private under $HOME/eos/\n  //   If this is a user-private mount the syntax 'foo@cern.ch' should be used to distinguish private mounts of individual users in the 'df' output\n  //\n  //   Please note, that root mounts are by default shared mounts with kerberos configuration,\n  //   user mounts are private mounts with kerberos configuration\n  // --------------------------------------------------------------------------------------------\n  // XrdCl::* options we read from our config file\n  std::vector<std::string> xrdcl_options;\n  xrdcl_options.push_back(\"TimeoutResolution\");\n  xrdcl_options.push_back(\"ConnectionWindow\");\n  xrdcl_options.push_back(\"ConnectionRetry\");\n  xrdcl_options.push_back(\"StreamErrorWindow\");\n  xrdcl_options.push_back(\"RequestTimeout\");\n  xrdcl_options.push_back(\"StreamTimeout\");\n  xrdcl_options.push_back(\"RedirectLimit\");\n  std::string mountpoint;\n  std::string store_directory;\n  config.options.foreground = 0;\n  config.options.automounted = 0;\n\n  for (int i = 1; i < argc; ++i) {\n    std::string opt = argv[i];\n    std::string opt0 = argv[i - 1];\n\n    if ((opt[0] != '-') && (opt0 != \"-o\")) {\n      mountpoint = opt;\n    }\n\n    if (opt == \"-f\") {\n      config.options.foreground = 1;\n    }\n  }\n\n  bool config_is_safe = false;\n\n  try {\n    // parse JSON configuration\n    Json::Value root;\n    std::string errs;\n    Json::CharReaderBuilder reader;\n    struct stat configstat;\n    bool has_config = false;\n\n    if (!::stat(jsonconfig.c_str(), &configstat)) {\n      std::ifstream configfile(jsonconfig, std::ifstream::binary);\n\n      if ((configstat.st_uid == geteuid()) &&\n          (configstat.st_gid == getegid())  &&\n          (configstat.st_mode == 0100400)) {\n        config_is_safe = true;\n      }\n\n      bool ok = parseFromStream(reader, configfile, &root, &errs);\n\n      if (ok) {\n        fprintf(stderr, \"# JSON parsing successful\\n\");\n        has_config = true;\n      } else {\n        fprintf(stderr, \"error: invalid configuration file %s - %s\\n\",\n                jsonconfig.c_str(), errs.c_str());\n        exit(EINVAL);\n      }\n    } else {\n      fprintf(stderr, \"# no config file - running on default values\\n\");\n    }\n\n    if (!::stat(jsonconfiglocal.c_str(), &configstat)) {\n      Json::Value localjson;\n      std::ifstream configfile(jsonconfiglocal, std::ifstream::binary);\n      bool ok = parseFromStream(reader, configfile, &localjson, &errs);\n\n      if (ok) {\n        fprintf(stderr, \"# JSON parsing successful\\n\");\n        has_config = true;\n      } else {\n        fprintf(stderr, \"error: invalid configuration file %s - %s\\n\",\n                jsonconfiglocal.c_str(), errs.c_str());\n        exit(EINVAL);\n      }\n\n      Merge(root, localjson);\n    } else {\n      fprintf(stderr, \"# no config file for local overwrites\\n\");\n    }\n\n    if (!root.isMember(\"hostport\")) {\n      if (has_config) {\n        fprintf(stderr,\n                \"error: please configure 'hostport' in your configuration file '%s'\\n\",\n                jsonconfig.c_str());\n        exit(EINVAL);\n      }\n\n      if (!fsname.length()) {\n        fprintf(stderr,\n                \"error: please configure the EOS endpoint via fsname=<user>@<host\\n\");\n        exit(EINVAL);\n      }\n\n      if ((fsname.find(\".\") == std::string::npos)) {\n        fprintf(stderr,\n                \"error: when running without a configuration file you need to configure the EOS endpoint via fsname=<host>.<domain> - the domain has to be added!\\n\");\n        exit(EINVAL);\n      }\n\n      size_t pos_add;\n\n      if ((pos_add = fsname.find(\"@\")) != std::string::npos) {\n        std::string fsuser = fsname;\n        fsname.erase(0, pos_add + 1);\n        fsuser.erase(pos_add);\n\n        if ((fsuser == \"gw\") || (fsuser == \"smb\")) {\n          root[\"auth\"][\"krb5\"] = 0;\n\n          if (fsuser == \"smb\") {\n            // enable overlay mode\n            if (!root[\"options\"].isMember(\"overlay-mode\")) {\n              root[\"options\"][\"overlay-mode\"] = \"0777\";\n              fprintf(stderr, \"# enabling overlay-mode 0777 for smb export\\n\");\n            }\n          }\n        }\n      }\n\n      size_t pos_colon;\n      std::string remotemount;\n\n      if ((pos_colon = fsname.rfind(\":\")) != std::string::npos) {\n        remotemount = fsname.substr(pos_colon + 1);\n        fsname.erase(pos_colon);\n        root[\"remotemountdir\"] = remotemount;\n        fprintf(stderr, \"# extracted remote mount dir from fsname is '%s'\\n\",\n                remotemount.c_str());\n      }\n\n      root[\"hostport\"] = fsname;\n      fprintf(stderr, \"# extracted connection host from fsname is '%s'\\n\",\n              fsname.c_str());\n    }\n\n    if (!root.isMember(\"mdcachedir\")) {\n      if (geteuid()) {\n        root[\"mdcachedir\"] = \"/var/tmp/eos/fusex/md-cache/\";\n      } else {\n        root[\"mdcachedir\"] = \"/var/cache/eos/fusex/md-cache/\";\n      }\n\n      fprintf(stderr, \"# enabling swapping inodes with md-cache in '%s'\\n\",\n              root[\"mdcachedir\"].asString().c_str());\n    }\n\n    // apply some default settings for undefined entries.\n    {\n      if (!root.isMember(\"name\")) {\n        XrdOucString id = mountpoint.c_str();\n\n        while (id.replace(\"/\", \"-\")) {\n        }\n\n        fsname += id.c_str();\n        root[\"name\"] = fsname;\n      }\n\n      if (!root.isMember(\"hostport\")) {\n        root[\"hostport\"] = \"localhost\";\n      }\n\n      if (!root.isMember(\"mdzmqidentity\")) {\n        if (geteuid()) {\n          root[\"mdzmqidentity\"] = \"userd\";\n        } else {\n          root[\"mdzmqidentity\"] = \"eosxd\";\n        }\n      }\n\n      if (!root.isMember(\"remotemountdir\")) {\n        root[\"remotemountdir\"] = \"/eos/\";\n      }\n\n      if (!root.isMember(\"localmountdir\")) {\n        root[\"localmountdir\"] = \"/eos/\";\n      }\n\n      if (!root[\"options\"].isMember(\"debuglevel\")) {\n        root[\"options\"][\"debuglevel\"] = 4;\n      }\n\n      if (!root[\"options\"].isMember(\"backtrace\")) {\n        root[\"options\"][\"backtrace\"] = 1;\n      }\n\n      if (!root[\"options\"].isMember(\"md-kernelcache\")) {\n        root[\"options\"][\"md-kernelcache\"] = 1;\n      }\n\n      if (!root[\"options\"].isMember(\"leasetime\")) {\n        root[\"options\"][\"leasetime\"] = 300;\n      }\n\n      if (!root[\"options\"].isMember(\"md-kernelcache.enoent.timeout\")) {\n        root[\"options\"][\"md-kernelcache.enoent.timeout\"] = 0;\n      }\n\n      if (!root[\"options\"].isMember(\"md-backend.timeout\")) {\n        root[\"options\"][\"md-backend.timeout\"] = 86400;\n      }\n\n      if (!root[\"options\"].isMember(\"md-backend.put.timeout\")) {\n        root[\"options\"][\"md-backend.put.timeout\"] = 120;\n      }\n\n      if (!root[\"options\"].isMember(\"data-kernelcache\")) {\n        root[\"options\"][\"data-kernelcache\"] = 1;\n      }\n\n      if (!root[\"options\"].isMember(\"rename-is-sync\")) {\n        root[\"options\"][\"rename-is-sync\"] = 1;\n      }\n\n      if (!root[\"options\"].isMember(\"rm-is-sync\")) {\n        root[\"options\"][\"rm-is-sync\"] = 0;\n      }\n\n      if (!root[\"options\"].isMember(\"global-flush\")) {\n        root[\"options\"][\"global-flush\"] = 0;\n      }\n\n      if (!root[\"options\"].isMember(\"global-locking\")) {\n        root[\"options\"][\"global-locking\"] = 1;\n      }\n\n      if (!root[\"options\"].isMember(\"flush-wait-open\")) {\n        root[\"options\"][\"flush-wait-open\"] = 1;\n      }\n\n      if (!root[\"options\"].isMember(\"flush-wait-open-size\")) {\n        root[\"options\"][\"flush-wait-open-size\"] = 262144;\n      }\n\n      if (!root[\"options\"].isMember(\"flush-wait-umount\")) {\n        root[\"options\"][\"flush-wait-umount\"] = 120;\n      }\n\n      if (!root[\"options\"].isMember(\"show-tree-size\")) {\n        root[\"options\"][\"show-tree-size\"] = 0;\n      }\n\n      if (!root[\"options\"].isMember(\"hide-versions\")) {\n        root[\"options\"][\"hide-versions\"] = 1;\n      }\n\n      if (!root[\"auth\"].isMember(\"krb5\")) {\n        root[\"auth\"][\"krb5\"] = 1;\n      }\n\n      if (!root[\"auth\"].isMember(\"sss\")) {\n        root[\"auth\"][\"sss\"] = 1;\n      }\n\n      if (!root[\"auth\"].isMember(\"oauth2\")) {\n        root[\"auth\"][\"oauth2\"] = 1;\n      }\n\n      if (!root[\"auth\"].isMember(\"ztn\")) {\n        root[\"auth\"][\"ztn\"] = 1;\n      }\n\n      if (!root[\"auth\"].isMember(\"unix\")) {\n        root[\"auth\"][\"unix\"] = 0;\n      }\n\n      if (!root[\"auth\"].isMember(\"unix-root\")) {\n        root[\"auth\"][\"unix-root\"] = 0;\n      }\n\n      if (!root[\"auth\"].isMember(\"ignore-containerization\")) {\n        root[\"auth\"][\"ignore-containerization\"] = 0;\n      }\n\n      if (!root[\"auth\"].isMember(\"credential-store\")) {\n        if (geteuid()) {\n          root[\"auth\"][\"credential-store\"] = \"/var/tmp/eos/fusex/credential-store/\";\n        } else {\n          root[\"auth\"][\"credential-store\"] = \"/var/cache/eos/fusex/credential-store/\";\n        }\n      }\n\n      if ((root[\"auth\"][\"sss\"] == 1) || (root[\"auth\"][\"oauth2\"] == 1)) {\n        if (!root[\"auth\"].isMember(\"ssskeytab\")) {\n          root[\"auth\"][\"ssskeytab\"] = default_ssskeytab;\n          config.ssskeytab = root[\"auth\"][\"ssskeytab\"].asString();\n          struct stat buf;\n\n          if (stat(config.ssskeytab.c_str(), &buf)) {\n            fprintf(stderr,\n                    \"warning: sss keytabfile '%s' does not exist - disabling sss/oauth2\\n\",\n                    config.ssskeytab.c_str());\n            root[\"auth\"][\"sss\"] = 0;\n            root[\"auth\"][\"oauth2\"] = 0;\n          }\n        } else {\n          config.ssskeytab = root[\"auth\"][\"ssskeytab\"].asString();\n        }\n      }\n\n      if (!root[\"inline\"].isMember(\"max-size\")) {\n        root[\"inline\"][\"max-size=\"] = 0;\n      }\n\n      if (!root[\"inline\"].isMember(\"default-compressor\")) {\n        root[\"inline\"][\"default-compressor\"] = \"none\";\n      }\n\n      if (!root[\"auth\"].isMember(\"shared-mount\")) {\n        if (geteuid()) {\n          root[\"auth\"][\"shared-mount\"] = 0;\n        } else {\n          root[\"auth\"][\"shared-mount\"] = 1;\n        }\n      }\n\n      if (!root[\"options\"].isMember(\"fd-limit\")) {\n        if (!geteuid()) {\n          root[\"options\"][\"fd-limit\"] = 524288;\n        } else {\n          root[\"options\"][\"fd-limit\"] = 4096;\n        }\n      }\n\n      if (!root[\"options\"].isMember(\"no-fsync\")) {\n        root[\"options\"][\"no-fsync\"].append(\".db\");\n        root[\"options\"][\"no-fsync\"].append(\".db-journal\");\n        root[\"options\"][\"no-fsync\"].append(\".sqlite\");\n        root[\"options\"][\"no-fsync\"].append(\".sqlite-journal\");\n        root[\"options\"][\"no-fsync\"].append(\".db3\");\n        root[\"options\"][\"no-fsync\"].append(\".db3-journal\");\n        root[\"options\"][\"no-fsync\"].append(\".o\");\n      }\n\n      if (!root[\"options\"].isMember(\"flush-nowait-executables\")) {\n        root[\"options\"][\"flush-nowait-executables\"].append(\"/tar\");\n        root[\"options\"][\"flush-nowait-executables\"].append(\"/touch\");\n      }\n    }\n\n    if (!root[\"options\"].isMember(\"cpu-core-affinity\")) {\n      root[\"options\"][\"cpu-core-affinity\"] = 0;\n    }\n\n    if (!root[\"options\"].isMember(\"no-xattr\")) {\n      root[\"options\"][\"no-xattr\"] = 0;\n    }\n\n    if (!root[\"options\"].isMember(\"no-link\")) {\n      root[\"options\"][\"no-link\"] = 0;\n    }\n\n    if (!root[\"options\"].isMember(\"nocache-graceperiod\")) {\n      root[\"options\"][\"nocache-graceperiod\"] = 5;\n    }\n\n    if (!root[\"auth\"].isMember(\"forknoexec-heuristic\")) {\n      root[\"auth\"][\"forknoexec-heuristic\"] = 1;\n    }\n\n    if (!root[\"options\"].isMember(\"rm-rf-protect-levels\")) {\n      root[\"options\"][\"rm-rf-protect-levels\"] = 0;\n    }\n\n    if (!root[\"options\"].isMember(\"rm-rf-bulk\")) {\n      root[\"options\"][\"rm-rf-bulk\"] = 0;\n    }\n\n    if (!root[\"options\"].isMember(\"write-size-flush-interval\")) {\n      root[\"options\"][\"write-size-flush-interval\"] = 10;\n    }\n\n    if (!root[\"options\"].isMember(\"submounts\")) {\n      root[\"options\"][\"submounts\"] = 0;\n    }\n\n    if (!root[\"options\"].isMember(\"inmemory-inodes\")) {\n      root[\"options\"][\"inmemory-inodes\"] = 16384;\n    }\n\n    // xrdcl default options\n    XrdCl::DefaultEnv::GetEnv()->PutInt(\"TimeoutResolution\", 1);\n    XrdCl::DefaultEnv::GetEnv()->PutInt(\"ConnectionWindow\", 10);\n    XrdCl::DefaultEnv::GetEnv()->PutInt(\"ConnectionRetry\", 0);\n    XrdCl::DefaultEnv::GetEnv()->PutInt(\"StreamErrorWindow\", 120);\n    XrdCl::DefaultEnv::GetEnv()->PutInt(\"RequestTimeout\", 60);\n    XrdCl::DefaultEnv::GetEnv()->PutInt(\"StreamTimeout\", 120);\n    XrdCl::DefaultEnv::GetEnv()->PutInt(\"RedirectLimit\", 2);\n\n    for (auto it = xrdcl_options.begin(); it != xrdcl_options.end(); ++it) {\n      if (root[\"xrdcl\"].isMember(*it)) {\n        XrdCl::DefaultEnv::GetEnv()->PutInt(it->c_str(),\n                                            root[\"xrdcl\"][it->c_str()].asInt());\n\n        if (*it == \"RequestTimeout\") {\n          int rtimeout = root[\"xrdcl\"][it->c_str()].asInt();\n\n          if (rtimeout > XrdCl::Proxy::chunk_timeout()) {\n            XrdCl::Proxy::chunk_timeout(rtimeout + 60);\n          }\n        }\n      }\n    }\n\n    if (root[\"xrdcl\"].isMember(\"LogLevel\")) {\n      XrdCl::DefaultEnv::GetEnv()->PutString(\"LogLevel\",\n                                             root[\"xrdcl\"][\"LogLevel\"].asString());\n      setenv((char*) \"XRD_LOGLEVEL\", root[\"xrdcl\"][\"LogLevel\"].asString().c_str(), 1);\n      XrdCl::DefaultEnv::ReInitializeLogging();\n    }\n\n    // recovery setting\n    if (!root[\"recovery\"].isMember(\"read\")) {\n      root[\"recovery\"][\"read\"] = 1;\n    }\n\n    if (!root[\"recovery\"].isMember(\"read-open\")) {\n      root[\"recovery\"][\"read-open\"] = 1;\n    }\n\n    if (!root[\"recovery\"].isMember(\"read-open-noserver\")) {\n      root[\"recovery\"][\"read-open-noserver\"] = 1;\n    }\n\n    if (!root[\"recovery\"].isMember(\"read-open-noserver-retrywindow\")) {\n      root[\"recovery\"][\"read-open-noserver-retrywindow\"] = 15;\n    }\n\n    if (!root[\"recovery\"].isMember(\"write\")) {\n      root[\"recovery\"][\"write\"] = 1;\n    }\n\n    if (!root[\"recovery\"].isMember(\"write-open\")) {\n      root[\"recovery\"][\"write-open\"] = 1;\n    }\n\n    if (!root[\"recovery\"].isMember(\"write-open-noserver\")) {\n      root[\"recovery\"][\"write-open-noserver\"] = 1;\n    }\n\n    if (!root[\"recovery\"].isMember(\"write-open-noserver-retrywindow\")) {\n      root[\"recovery\"][\"write-open-noserver-retrywindow\"] = 15;\n    }\n\n    // fuzzing settings\n    if (!root[\"fuzzing\"].isMember(\"open-async-submit\")) {\n      root[\"fuzzing\"][\"open-async-submit\"] = 0;\n    }\n\n    if (!root[\"fuzzing\"].isMember(\"open-async-return\")) {\n      root[\"fuzzing\"][\"open-async-return\"] = 0;\n    }\n\n    if (!root[\"fuzzing\"].isMember(\"open-async-submit-fatal\")) {\n      root[\"fuzzing\"][\"open-async-submit-fatal\"] = 0;\n    }\n\n    if (!root[\"fuzzing\"].isMember(\"open-async-return-fatal\")) {\n      root[\"fuzzing\"][\"open-async-return-fatal\"] = 0;\n    }\n\n    config.name = root[\"name\"].asString();\n    config.hostport = root[\"hostport\"].asString();\n    config.remotemountdir = root[\"remotemountdir\"].asString();\n    config.localmountdir = root[\"localmountdir\"].asString();\n    config.statfilesuffix = root[\"statfilesuffix\"].asString();\n    config.statfilepath = root[\"statfilepath\"].asString();\n    config.appname = \"fuse\";\n    config.encryptionkey = \"\";\n\n    if (root[\"appname\"].asString().length()) {\n      if (root[\"appname\"].asString().find(\"&\") == std::string::npos) {\n        config.appname += \"::\";\n        config.appname += root[\"appname\"].asString();\n      } else {\n        fprintf(stderr, \"error: appname cannot contain '&' character!\\n\");\n        exit(EINVAL);\n      }\n    }\n\n    if (root[\"encryptionkey\"].asString().length()) {\n      config.encryptionkey = root[\"encryptionkey\"].asString();\n\n      if (!config_is_safe) {\n        fprintf(stderr,\n                \"error: config file has to be owned by uid/gid:%u/%u and needs to have 400 mode set!\\n\",\n                geteuid(), getegid());\n        exit(EINVAL);\n      }\n    }\n\n    config.options.debug = root[\"options\"][\"debug\"].asInt();\n    config.options.debuglevel = root[\"options\"][\"debuglevel\"].asInt();\n    config.options.jsonstats = !root[\"options\"][\"jsonstats\"].isNull() &&\n                               root[\"options\"][\"jsonstats\"] !=\n                               0; // any value will make jsonstats to true but 0\n    config.options.enable_backtrace = root[\"options\"][\"backtrace\"].asInt();\n    config.options.libfusethreads = root[\"options\"][\"libfusethreads\"].asInt();\n    config.options.md_kernelcache = root[\"options\"][\"md-kernelcache\"].asInt();\n    config.options.md_kernelcache_enoent_timeout =\n      root[\"options\"][\"md-kernelcache.enoent.timeout\"].asDouble();\n    config.options.md_backend_timeout =\n      root[\"options\"][\"md-backend.timeout\"].asDouble();\n    config.options.md_backend_put_timeout =\n      root[\"options\"][\"md-backend.put.timeout\"].asDouble();\n    config.options.data_kernelcache = root[\"options\"][\"data-kernelcache\"].asInt();\n    config.options.rename_is_sync = root[\"options\"][\"rename-is-sync\"].asInt();\n    config.options.rmdir_is_sync = root[\"options\"][\"rmdir-is-sync\"].asInt();\n    config.options.global_flush = root[\"options\"][\"global-flush\"].asInt();\n    config.options.flush_wait_open = root[\"options\"][\"flush-wait-open\"].asInt();\n    config.options.flush_wait_open_size =\n      root[\"options\"][\"flush-wait-open-size\"].asInt();\n    config.options.flush_wait_umount = root[\"options\"][\"flush-wait-umount\"].asInt();\n    config.options.global_locking = root[\"options\"][\"global-locking\"].asInt();\n    config.options.overlay_mode = strtol(\n                                    root[\"options\"][\"overlay-mode\"].asString().c_str(), 0, 8);\n\n    if (config.options.overlay_mode & 1) {\n      config.options.x_ok = 0;\n    } else {\n      config.options.x_ok = X_OK;\n    }\n\n    config.options.fakerename = false;\n\n    if (root[\"options\"].isMember(\"tmp-fake-rename\")) {\n      if (root[\"options\"][\"tmp-fake-rename\"].asInt()) {\n        config.options.fakerename = true;\n      }\n    }\n\n    config.options.fdlimit = root[\"options\"][\"fd-limit\"].asInt();\n    config.options.rm_rf_protect_levels =\n      root[\"options\"][\"rm-rf-protect-levels\"].asInt();\n    config.options.rm_rf_bulk =\n      root[\"options\"][\"rm-rf-bulk\"].asInt();\n    config.options.show_tree_size = root[\"options\"][\"show-tree-size\"].asInt();\n    config.options.hide_versions = root[\"options\"][\"hide-versions\"].asInt();\n    config.options.protect_directory_symlink_loops =\n      root[\"options\"][\"protect-directory-symlink-loops\"].asInt();\n    config.options.cpu_core_affinity = root[\"options\"][\"cpu-core-affinity\"].asInt();\n    config.options.no_xattr = root[\"options\"][\"no-xattr\"].asInt();\n    config.options.no_eos_xattr_listing =\n      root[\"options\"][\"no-eos-xattr-listing\"].asInt();\n    config.options.no_hardlinks = root[\"options\"][\"no-link\"].asInt();\n    config.options.write_size_flush_interval =\n      root[\"options\"][\"write-size-flush-interval\"].asInt();\n    config.options.inmemory_inodes = root[\"options\"][\"inmemory-inodes\"].asInt();\n    config.options.flock = false;\n#ifdef FUSE_SUPPORTS_FLOCK\n    config.options.flock = true;\n#endif\n\n    if (config.options.no_xattr) {\n      disable_xattr();\n    }\n\n    if (config.options.no_hardlinks) {\n      disable_link();\n    }\n\n    config.options.nocache_graceperiod =\n      root[\"options\"][\"nocache-graceperiod\"].asInt();\n    config.options.leasetime = root[\"options\"][\"leasetime\"].asInt();\n    config.options.submounts = root[\"options\"][\"submounts\"].asInt();\n    config.recovery.read = root[\"recovery\"][\"read\"].asInt();\n    config.recovery.read_open = root[\"recovery\"][\"read-open\"].asInt();\n    config.recovery.read_open_noserver =\n      root[\"recovery\"][\"read-open-noserver\"].asInt();\n    config.recovery.read_open_noserver_retrywindow =\n      root[\"recovery\"][\"read-open-noserver-retrywindow\"].asInt();\n    config.recovery.write = root[\"recovery\"][\"write\"].asInt();\n    config.recovery.write_open = root[\"recovery\"][\"write-open\"].asInt();\n    config.recovery.write_open_noserver =\n      root[\"recovery\"][\"write-open-noserver\"].asInt();\n    config.recovery.write_open_noserver_retrywindow =\n      root[\"recovery\"][\"write-open-noserver-retrywindow\"].asInt();\n    config.fuzzing.open_async_submit = root[\"fuzzing\"][\"open-async-submit\"].asInt();\n    config.fuzzing.open_async_return = root[\"fuzzing\"][\"open-async-return\"].asInt();\n    config.fuzzing.read_async_return = root[\"fuzzing\"][\"read-async-return\"].asInt();\n    config.fuzzing.open_async_submit_fatal = (bool)\n        root[\"fuzzing\"][\"open-async-submit-fatal\"].asInt();\n    config.fuzzing.open_async_return_fatal = (bool)\n        root[\"fuzzing\"][\"open-async-return-fatal\"].asInt();\n    XrdCl::Fuzzing::Configure(config.fuzzing.open_async_submit,\n                              config.fuzzing.open_async_return,\n                              config.fuzzing.open_async_submit_fatal,\n                              config.fuzzing.open_async_return_fatal,\n                              config.fuzzing.read_async_return);\n    config.mdcachedir = root[\"mdcachedir\"].asString();\n    config.mqtargethost = root[\"mdzmqtarget\"].asString();\n    config.mqidentity = root[\"mdzmqidentity\"].asString();\n    config.mqname = config.mqidentity;\n    config.auth.fuse_shared = root[\"auth\"][\"shared-mount\"].asInt();\n    config.auth.use_user_krb5cc = root[\"auth\"][\"krb5\"].asInt();\n    config.auth.use_user_oauth2 = root[\"auth\"][\"oauth2\"].asInt();\n    config.auth.use_user_ztn = root[\"auth\"][\"ztn\"].asInt();\n    config.auth.use_user_unix = root[\"auth\"][\"unix\"].asInt();\n    config.auth.use_root_unix = root[\"auth\"][\"unix-root\"].asInt();\n    config.auth.ignore_containerization =\n      root[\"auth\"][\"ignore-containerization\"].asInt();\n    config.auth.use_user_gsiproxy = root[\"auth\"][\"gsi\"].asInt();\n    config.auth.use_user_sss = root[\"auth\"][\"sss\"].asInt();\n    config.auth.sssEndorsement = root[\"auth\"][\"sssEndorsement\"].asString();\n    config.auth.credentialStore = root[\"auth\"][\"credential-store\"].asString();\n    config.auth.encryptionKey = config.encryptionkey;\n\n    if (config.auth.use_user_sss || config.auth.use_user_oauth2) {\n      // store keytab location for this mount\n      setenv(\"XrdSecSSSKT\", root[\"auth\"][\"ssskeytab\"].asString().c_str(), 1);\n    }\n\n    config.auth.tryKrb5First = !((bool)root[\"auth\"][\"gsi-first\"].asInt());\n    config.auth.environ_deadlock_timeout =\n      root[\"auth\"][\"environ-deadlock-timeout\"].asInt();\n    config.auth.forknoexec_heuristic = root[\"auth\"][\"forknoexec-heuristic\"].asInt();\n\n    if (config.auth.environ_deadlock_timeout <= 0) {\n      config.auth.environ_deadlock_timeout = 500;\n    }\n\n    config.inliner.max_size = root[\"inline\"][\"max-size\"].asInt();\n    config.inliner.default_compressor =\n      root[\"inline\"][\"default-compressor\"].asString();\n\n    if ((config.inliner.default_compressor != \"none\") &&\n        (config.inliner.default_compressor != \"zlib\")) {\n      std::cerr <<\n                \"inline default compressor value can only be 'none' or 'zlib'.\"\n                << std::endl;\n      exit(EINVAL);\n    }\n\n    for (Json::Value::iterator it = root[\"options\"][\"no-fsync\"].begin();\n         it != root[\"options\"][\"no-fsync\"].end(); ++it) {\n      config.options.no_fsync_suffixes.push_back(it->asString());\n      no_fsync_list += it->asString();\n      no_fsync_list += \",\";\n    }\n\n    for (Json::Value::iterator it =\n           root[\"options\"][\"flush-nowait-executables\"].begin();\n         it != root[\"options\"][\"flush-nowait-executables\"].end(); ++it) {\n      config.options.nowait_flush_executables.push_back(it->asString());\n      nowait_flush_exec_list += it->asString();\n      nowait_flush_exec_list += \",\";\n    }\n\n    // reset mdcachedir if compiled without rocksdb support\n#ifndef HAVE_ROCKSDB\n\n    if (!config.mdcachedir.empty()) {\n      std::cerr <<\n                \"Options mdcachedir is unavailable, fusex was compiled without rocksdb support.\"\n                << std::endl;\n      config.mdcachedir = \"\";\n    }\n\n#endif // HAVE_ROCKSDB\n\n    if (config.mdcachedir.length()) {\n      // add the instance name to all cache directories\n      if (config.mdcachedir.rfind(\"/\") != (config.mdcachedir.size() - 1)) {\n        config.mdcachedir += \"/\";\n      }\n\n      config.mdcachedir += config.name.length() ? config.name : \"default\";\n    }\n\n    // the store directory is the tree before we append individual UUIDs for each mount\n    store_directory = config.mdcachedir;\n\n    // default settings\n    if (!config.statfilesuffix.length()) {\n      config.statfilesuffix = \"stats\";\n    }\n\n    if (!config.mqtargethost.length()) {\n      std::string h = config.hostport;\n\n      if (h.find(\":\") != std::string::npos) {\n        h.erase(h.find(\":\"));\n      }\n\n      config.mqtargethost = \"tcp://\" + h + \":1100\";\n    }\n\n    {\n      config.mqidentity.insert(0, \"fuse://\");\n      config.mqidentity += \"@\";\n      char hostname[4096];\n\n      if (gethostname(hostname, sizeof(hostname))) {\n        fprintf(stderr, \"error: failed to get hostname!\\n\");\n        exit(EINVAL);\n      }\n\n      config.clienthost = hostname;\n      config.mqidentity += hostname;\n      char suuid[40];\n      uuid_t uuid;\n      uuid_generate_time(uuid);\n      uuid_unparse(uuid, suuid);\n      config.clientuuid = suuid;\n      config.mqidentity += \"//\";\n      config.mqidentity += suuid;\n      config.mqidentity += \":\";\n      char spid[16];\n      snprintf(spid, sizeof(spid), \"%d\", getpid());\n      config.mqidentity += spid;\n\n      if (config.mdcachedir.length()) {\n        config.mdcachedir += \"/\";\n        config.mdcachedir += suuid;\n      }\n    }\n\n    if (config.options.fdlimit > 0) {\n      struct rlimit newrlimit;\n      newrlimit.rlim_cur = config.options.fdlimit;\n      newrlimit.rlim_max = config.options.fdlimit;\n\n      if ((setrlimit(RLIMIT_NOFILE, &newrlimit) != 0) && (!geteuid())) {\n        fprintf(stderr, \"warning: unable to set fd limit to %lu - errno %d\\n\",\n                config.options.fdlimit, errno);\n      }\n    }\n\n    struct rlimit nofilelimit;\n\n    if (getrlimit(RLIMIT_NOFILE, &nofilelimit) != 0) {\n      fprintf(stderr, \"error: unable to get fd limit - errno %d\\n\", errno);\n      exit(EINVAL);\n    }\n\n    fprintf(stderr, \"# File descriptor limit: %lu soft, %lu hard\\n\",\n            nofilelimit.rlim_cur, nofilelimit.rlim_max);\n    // store the current limit\n    config.options.fdlimit = nofilelimit.rlim_cur;\n    // data caching configuration\n    cconfig.type = cache_t::INVALID;\n    cconfig.clean_on_startup = true;\n\n    if (root[\"cache\"][\"type\"].asString() == \"disk\") {\n      cconfig.type = cache_t::DISK;\n    } else if (root[\"cache\"][\"type\"].asString() == \"memory\") {\n      cconfig.type = cache_t::MEMORY;\n    } else {\n      if (root[\"cache\"][\"type\"].asString().length()) {\n        fprintf(stderr, \"error: invalid cache type configuration\\n\");\n        exit(EINVAL);\n      } else {\n        cconfig.type = cache_t::DISK;\n      }\n    }\n\n    if (!root[\"cache\"].isMember(\"read-ahead-bytes-nominal\")) {\n      root[\"cache\"][\"read-ahead-bytes-nominal\"] = 256 * 1024;\n    }\n\n    if (!root[\"cache\"].isMember(\"read-ahead-bytes-max\")) {\n      root[\"cache\"][\"read-ahead-bytes-max\"] = 2 * 1024 * 1024;\n    }\n\n    if (!root[\"cache\"].isMember(\"read-ahead-blocks-max\")) {\n      root[\"cache\"][\"read-ahead-blocks-max\"] = 16;\n    }\n\n    if (!root[\"cache\"].isMember(\"read-ahead-strategy\")) {\n      root[\"cache\"][\"read-ahead-strategy\"] = \"dynamic\";\n    }\n\n    if (!root[\"cache\"].isMember(\"read-ahead-sparse-ratio\")) {\n      root[\"cache\"][\"read-ahead-sparse-ratio\"] = 0.0;\n    }\n\n    // auto-scale read-ahead and write-back buffer\n    uint64_t best_io_buffer_size = meminfo.get().totalram / 8;\n\n    if (best_io_buffer_size > 128 * 1024 * 1024) {\n      best_io_buffer_size = 128 * 1024 * 1024;\n    } else {\n      // we take 1/8 of the total available memory, if we don't have one GB available\n      best_io_buffer_size /= 8;\n    }\n\n    if (!root[\"cache\"].isMember(\"max-read-ahead-buffer\")) {\n      fprintf(stderr, \"# allowing max read-ahead buffers of %lu bytes\\n\",\n              best_io_buffer_size);\n      root[\"cache\"][\"max-read-ahead-buffer\"] = (Json::Value::UInt64)\n          best_io_buffer_size;\n    }\n\n    if (!root[\"cache\"].isMember(\"max-write-buffer\")) {\n      fprintf(stderr, \"# allowing max write-back buffers of %lu bytes\\n\",\n              best_io_buffer_size);\n      root[\"cache\"][\"max-write-buffer\"] = (Json::Value::UInt64)best_io_buffer_size;\n    }\n\n    cconfig.location = root[\"cache\"][\"location\"].asString();\n    cconfig.journal = root[\"cache\"][\"journal\"].asString();\n    cconfig.default_read_ahead_size =\n      root[\"cache\"][\"read-ahead-bytes-nominal\"].asInt();\n    cconfig.max_read_ahead_size = root[\"cache\"][\"read-ahead-bytes-max\"].asInt();\n    cconfig.max_read_ahead_blocks = root[\"cache\"][\"read-ahead-blocks-max\"].asInt();\n    cconfig.read_ahead_strategy = root[\"cache\"][\"read-ahead-strategy\"].asString();\n    cconfig.read_ahead_sparse_ratio =\n      root[\"cache\"][\"read-ahead-sparse-ratio\"].asFloat();\n\n    if ((cconfig.read_ahead_strategy != \"none\") &&\n        (cconfig.read_ahead_strategy != \"static\") &&\n        (cconfig.read_ahead_strategy != \"dynamic\")) {\n      fprintf(stderr,\n              \"error: invalid read-ahead-strategy specified - only 'none' 'static' 'dynamic' allowed\\n\");\n      exit(EINVAL);\n    }\n\n    cconfig.max_inflight_read_ahead_buffer_size =\n      root[\"cache\"][\"max-read-ahead-buffer\"].asInt();\n    cconfig.max_inflight_write_buffer_size =\n      root[\"cache\"][\"max-write-buffer\"].asInt();\n\n    // set defaults for journal and file-start cache\n    if (geteuid()) {\n      if (!cconfig.location.length()) {\n        cconfig.location = \"/var/tmp/eos/fusex/cache/\";\n\n        if (getenv(\"USER\")) {\n          cconfig.location += getenv(\"USER\");\n        } else {\n          cconfig.location += std::to_string(geteuid());\n        }\n\n        cconfig.location += \"/\";\n      }\n\n      if (!cconfig.journal.length()) {\n        cconfig.journal = \"/var/tmp/eos/fusex/cache/\";\n\n        if (getenv(\"USER\")) {\n          cconfig.journal += getenv(\"USER\");\n        } else {\n          cconfig.location += std::to_string(geteuid());\n        }\n\n        cconfig.journal += \"/\";\n      }\n\n      // default cache size 512 MB\n      if (!root[\"cache\"][\"size-mb\"].asString().length()) {\n        root[\"cache\"][\"size-mb\"] = 512;\n      }\n\n      // default cache size 64k inodes\n      if (!root[\"cache\"][\"size-ino\"].asString().length()) {\n        root[\"cache\"][\"size-ino\"] = 65536;\n      }\n\n      // default journal cache size 2 G\n      if (!root[\"cache\"][\"journal-mb\"].asString().length()) {\n        root[\"cache\"][\"journal-mb\"] = 2048;\n      }\n\n      // default journal size 64k inodes\n      if (!root[\"cache\"][\"journal-ino\"].asString().length()) {\n        root[\"cache\"][\"journal-ino\"] = 65536;\n      }\n\n      // default cleaning threshold\n      if (!root[\"cache\"][\"clean-threshold\"].asString().length()) {\n        root[\"cache\"][\"clean-threshold\"] = 85.0;\n      }\n\n      // default rescue cache files\n      if (!root[\"cache\"][\"rescue-cache-files\"].asInt()) {\n        root[\"cache\"][\"rescue-cache-files\"] = 0;\n      }\n\n      // default file cache max kb\n      if (!root[\"cache\"][\"file-cache-max-kb\"].asString().length()) {\n        root[\"cache\"][\"file-cache-max-kb\"] = 256;\n      }\n    } else {\n      if (!cconfig.location.length()) {\n        cconfig.location = \"/var/cache/eos/fusex/cache/\";\n      }\n\n      if (!cconfig.journal.length()) {\n        cconfig.journal = \"/var/cache/eos/fusex/cache/\";\n      }\n\n      // default cache size 1 GB\n      if (!root[\"cache\"][\"size-mb\"].asString().length()) {\n        root[\"cache\"][\"size-mb\"] = 1000;\n      }\n\n      // default cache size 64k indoes\n      if (!root[\"cache\"][\"size-ino\"].asString().length()) {\n        root[\"cache\"][\"size-ino\"] = 65536;\n      }\n\n      // default cleaning threshold\n      if (!root[\"cache\"][\"clean-threshold\"].asString().length()) {\n        root[\"cache\"][\"clean-threshold\"] = 85.0;\n      }\n\n      if (!root[\"cache\"][\"file-cache-max-kb\"].asString().length()) {\n        root[\"cache\"][\"file-cache-max-kb\"] = 256;\n      }\n    }\n\n    if (cconfig.location == \"OFF\") {\n      // disable file-start cache\n      cconfig.location = \"\";\n    }\n\n    if (cconfig.journal == \"OFF\") {\n      // disable journal\n      cconfig.journal = \"\";\n    }\n\n    if (cconfig.location.length()) {\n      if (cconfig.location.rfind(\"/\") != (cconfig.location.size() - 1)) {\n        cconfig.location += \"/\";\n      }\n\n      cconfig.location += config.name.length() ? config.name : \"default\";\n    }\n\n    if (cconfig.journal.length()) {\n      if (cconfig.journal.rfind(\"/\") != (cconfig.journal.size() - 1)) {\n        cconfig.journal += \"/\";\n      }\n\n      cconfig.journal += config.name.length() ? config.name : \"default\";\n    }\n\n    std::string lockpfx;\n\n    if (geteuid()) {\n      char ldir[1024];\n      snprintf(ldir, sizeof(ldir), \"/var/tmp/eos-%d/\", geteuid());\n      lockpfx = ldir;\n    } else {\n      lockpfx = \"/var/run/eos/\";\n    }\n\n    if (mountpoint[0] != '/') {\n      fprintf(stderr, \"# not using concurrent eosxd detection, mountpoint is \"\n              \"relative\\n\");\n      lockpfx.clear();\n    } else {\n      std::string mk_lockdir = \"mkdir -m 0755 -p \" + lockpfx;\n      (void) system(mk_lockdir.c_str());\n      lockpfx += \"fusex/\";\n      mk_lockdir = \"mkdir -m 0755 -p \" + lockpfx;\n      (void) system(mk_lockdir.c_str());\n      XrdOucString id = mountpoint.c_str();\n\n      while (id.replace(\"//\", \"/\"));\n\n      if (id.length() > 1 && id.endswith(\"/\")) {\n        id.erase(id.length() - 1);\n      }\n\n      id.replace(\"-\", \"--\");\n      id.replace(\"/\", \"-\");\n      lockpfx += std::string(\"mount.\") + id.c_str();\n    }\n\n    ConcurrentMount cmdet(lockpfx);\n\n    // starts the mount + does reattach if necessary\n    if (int exitcode; startMount(cmdet, mountpoint, fsname, exitcode) < 0) {\n      exit(exitcode);\n    }\n\n    config.auth.credentialStore += config.name.length() ? config.name : \"default\";\n    // apply some defaults for all existing options\n    // by default create all the specified cache paths\n    std::string mk_cachedir = \"mkdir -p \" + config.mdcachedir;\n    std::string mk_journaldir = \"mkdir -p \" + cconfig.journal;\n    std::string mk_locationdir = \"mkdir -p \" + cconfig.location;\n    std::string mk_credentialdir = \"mkdir -p \" + config.auth.credentialStore;\n\n    // These directories might still be used by execve spawned processes that don't have binded credentials\n    if (system(\"mkdir -m 1777 -p /var/run/eos/credentials/\") ||\n        system(\"mkdir -m 1777 -p /var/run/eos/credentials/store\")) {\n      fprintf(stderr,\n              \"# Unable to create /var/run/eos/credentials/ with mode 1777 \\n\");\n    }\n\n    if (config.mdcachedir.length()) {\n      (void) system(mk_cachedir.c_str());\n      size_t slashes = std::count(config.mdcachedir.begin(), config.mdcachedir.end(),\n                                  '/');\n\n      // just some paranoid safety to avoid wiping by accident something we didn't intend to wipe\n      if ((slashes > 2)  && config.mdcachedir.length() > 37 &&\n          config.mdcachedir[config.mdcachedir.length() - 37] == '/') {\n        config.mdcachedir_unlink = config.mdcachedir;\n      }\n    }\n\n    if (cconfig.journal.length()) {\n      (void) system(mk_journaldir.c_str());\n    }\n\n    if (cconfig.location.length()) {\n      (void) system(mk_locationdir.c_str());\n    }\n\n    if (config.auth.credentialStore.length()) {\n      (void) system(mk_credentialdir.c_str());\n    }\n\n    // make the cache directories private to root\n    chmod_to_700_or_die(config.mdcachedir);\n    chmod_to_700_or_die(cconfig.journal);\n    chmod_to_700_or_die(cconfig.location);\n    chmod_to_700_or_die(config.auth.credentialStore);\n    {\n      char list[64];\n#ifndef __APPLE__\n\n      if (::listxattr(cconfig.location.c_str(), list, sizeof(list))) {\n#else\n\n      if (::listxattr(cconfig.location.c_str(), list, sizeof(list), 0)) {\n#endif\n\n        if (errno == ENOTSUP) {\n          fprintf(stderr,\n                  \"error: eosxd requires XATTR support on partition %s errno=%d\\n\",\n                  cconfig.location.c_str(), errno);\n          exit(-1);\n        }\n      }\n\n      cconfig.total_file_cache_size = root[\"cache\"][\"size-mb\"].asUInt64() * 1024 *\n                                      1024;\n      cconfig.total_file_cache_inodes = root[\"cache\"][\"size-ino\"].asUInt64();\n      cconfig.total_file_journal_size = root[\"cache\"][\"journal-mb\"].asUInt64() *\n                                        1024 * 1024;\n      cconfig.total_file_journal_inodes = root[\"cache\"][\"journal-ino\"].asUInt64();\n      cconfig.per_file_cache_max_size = root[\"cache\"][\"file-cache-max-kb\"].asUInt64()\n                                        * 1024;\n      cconfig.per_file_journal_max_size =\n        root[\"cache\"][\"file-journal-max-kb\"].asUInt64() * 1024;\n      cconfig.clean_threshold = root[\"cache\"][\"clean-threshold\"].asDouble();\n      cconfig.rescuecache = root[\"cache\"][\"rescue-cache-files\"].asInt();\n      int rc = 0;\n\n      if ((rc = cachehandler::instance().init(cconfig))) {\n        exit(rc);\n      }\n    }\n\n    {\n      if (!mountpoint.length()) {\n        // we allow to take the mountpoint from the json file if it is not given on the command line\n        fuse_opt_add_arg(&args, config.localmountdir.c_str());\n        mountpoint = config.localmountdir.c_str();\n      } else {\n        config.localmountdir = mountpoint;\n      }\n\n      if (mountpoint.length()) {\n        DIR* d = 0;\n        struct stat d_stat;\n\n        // sanity check of the mount directory\n        if (!(d = ::opendir(mountpoint.c_str()))) {\n          // check for a broken mount\n          if ((errno == ENOTCONN) || (errno == ENOENT)) {\n            // force an 'umount -l '\n            std::string systemline = \"umount -l \";\n            systemline += mountpoint;\n            fprintf(stderr, \"# dead mount detected - forcing '%s'\\n\", systemline.c_str());\n            (void) system(systemline.c_str());\n          }\n\n          if (stat(mountpoint.c_str(), &d_stat)) {\n            if (errno == ENOENT) {\n              fprintf(stderr, \"error: mountpoint '%s' does not exist\\n\", mountpoint.c_str());\n              exit(-1);\n            } else {\n              fprintf(stderr, \"error: failed to stat '%s' - errno = %d\\n\", mountpoint.c_str(),\n                      errno);\n              exit(-1);\n            }\n          }\n        } else {\n          closedir(d);\n        }\n      }\n    }\n\n    std::string nodelay = getenv(\"XRD_NODELAY\") ? getenv(\"XRD_NODELAY\") : \"\";\n    umount_system_line = \"fusermount -u -z \";\n    umount_system_line += EosFuse::Instance().Config().localmountdir;\n\n    if (nodelay == \"1\") {\n      fprintf(stderr, \"# Running with XRD_NODELAY=1 (nagle algorithm is disabled)\\n\");\n    } else {\n      putenv((char*) \"XRD_NODELAY=1\");\n      fprintf(stderr, \"# Disabling nagle algorithm (XRD_NODELAY=1)\\n\");\n    }\n\n    if (!getenv(\"MALLOC_CONF\")) {\n      fprintf(stderr, \"# Setting MALLOC_CONF=dirty_decay_ms:0\\n\");\n      putenv((char*) \"MALLOC_CONF=dirty_decay_ms:0\");\n    } else {\n      fprintf(stderr, \"# MALLOC_CONF=%s\\n\", getenv(\"MALLOC_CONF\"));\n    }\n\n#ifndef USE_FUSE3\n    int debug;\n#endif\n    {\n      // C-style fuse configuration optionss\n      struct eosxd_options {\n        int autofs;\n      };\n      struct eosxd_options fuse_opts;\n      fuse_opts.autofs = 0;\n#define OPTION(t, p)    \\\n  { t, offsetof(struct eosxd_options, p), 1 }\n      static struct fuse_opt eosxd_options_spec[] = {\n        OPTION(\"autofs\", autofs),\n        FUSE_OPT_END\n      };\n#ifdef USE_FUSE3\n\n      if (fuse_parse_cmdline(&args, &opts) != 0) {\n        exit(errno ? errno : -1);\n      }\n\n#else\n\n      if (fuse_parse_cmdline(&args, &local_mount_dir, NULL, &debug) == -1) {\n        exit(errno ? errno : -1);\n      }\n\n#endif\n\n      if (fuse_opt_parse(&args, &fuse_opts, eosxd_options_spec, NULL) == -1) {\n        exit(errno ? errno : -1);\n      }\n\n      config.options.automounted = fuse_opts.autofs;\n    }\n#ifdef USE_FUSE3\n\n    if (opts.show_help) {\n      printf(\"usage: %s [options] <mountpoint>\\n\\n\", argv[0]);\n      fuse_cmdline_help();\n      fuse_lowlevel_help();\n      free(opts.mountpoint);\n      fuse_opt_free_args(&args);\n      exit(0);\n    } else if (opts.show_version) {\n      printf(\"FUSE library version %s\\n\", fuse_pkgversion());\n      fuse_lowlevel_version();\n      free(opts.mountpoint);\n      fuse_opt_free_args(&args);\n      exit(0);\n    }\n\n    if (opts.mountpoint == NULL) {\n      printf(\"usage: %s [options] <mountpoint>\\n\", argv[0]);\n      printf(\"       %s --help\\n\", argv[0]);\n      free(opts.mountpoint);\n      fuse_opt_free_args(&args);\n      exit(-1);\n    }\n\n    fusesession = fuse_session_new(&args,\n                                   &(get_operations()),\n                                   sizeof(operations), NULL);\n\n    if (fusesession == NULL) {\n      fprintf(stderr, \"error: fuse_session failed\\n\");\n      free(opts.mountpoint);\n      fuse_opt_free_args(&args);\n      exit(-1);\n    }\n\n    if (fuse_set_signal_handlers(fusesession) != 0) {\n      fprintf(stderr, \"error: failed to set signal handlers\\n\");\n      fuse_session_destroy(fusesession);\n      free(opts.mountpoint);\n      fuse_opt_free_args(&args);\n      exit(-1);\n    }\n\n    if (fuse_session_mount(fusesession, opts.mountpoint) != 0) {\n      fprintf(stderr, \"error: fuse_session_mount failed\\n\");\n      fuse_remove_signal_handlers(fusesession);\n      fuse_session_destroy(fusesession);\n      free(opts.mountpoint);\n      fuse_opt_free_args(&args);\n      exit(-1);\n    }\n\n#else\n\n    if ((fusechan = fuse_mount(local_mount_dir, &args)) == NULL) {\n      fprintf(stderr, \"error: fuse_mount failed\\n\");\n      exit(errno ? errno : -1);\n    }\n\n#endif\n\n    if (fuse_daemonize(config.options.foreground) != -1) {\n      // notify the locking object that fuse is aware of the mount.\n      // The locking object will start a thread (for fd passing),\n      // so this call has to be done after the daemonize step above.\n#if USE_FUSE3\n      cmdet.MountDone(fuse_session_fd(fusesession));\n#else\n      cmdet.MountDone(fuse_chan_fd(fusechan));\n#endif\n#ifndef __APPLE__\n      eos::common::ShellCmd cmd(\"echo eos::common::ShellCmd init 2>&1\");\n      eos::common::cmd_status st = cmd.wait(5);\n      int rc = st.exit_code;\n\n      if (rc) {\n        fprintf(stderr,\n                \"warning: failed to run shell command\\n\");\n      }\n\n      if (!geteuid()) {\n        // change the priority of this process to maximum\n        if (setpriority(PRIO_PROCESS, getpid(), -PRIO_MAX / 2) < 0) {\n          fprintf(stderr,\n                  \"error: failed to renice this process '%u', to maximum priority '%d'\\n\",\n                  getpid(), -PRIO_MAX / 2);\n        }\n\n        if (config.options.cpu_core_affinity > 0) {\n          cpu_set_t cpuset;\n          CPU_ZERO(&cpuset);\n          CPU_SET(config.options.cpu_core_affinity - 1, &cpuset);\n          sched_setaffinity(getpid(), sizeof(cpu_set_t), &cpuset);\n          fprintf(stderr, \"# Setting CPU core affinity to core %d\\n\",\n                  config.options.cpu_core_affinity - 1);\n        }\n      }\n\n#endif\n      fprintf(stderr, \"initialize process cache '%s'\\n\",\n              config.auth.credentialStore.c_str());\n      fusexrdlogin::initializeProcessCache(config.auth);\n\n      if (config.options.foreground) {\n        if (nodelay != \"1\") {\n          fprintf(stderr,\n                  \"# warning: nagle algorithm is still enabled (export XRD_NODELAY=1 before running in foreground)\\n\");\n        }\n      }\n\n      // Open log file\n      if (getuid()) {\n        char logfile[1024];\n\n        if (getenv(\"EOS_FUSE_LOGFILE\")) {\n          snprintf(logfile, sizeof(logfile) - 1, \"%s\",\n                   getenv(\"EOS_FUSE_LOGFILE\"));\n        } else {\n          snprintf(logfile, sizeof(logfile) - 1, \"/tmp/eos-fuse.%d.log\",\n                   getuid());\n        }\n\n        config.logfilepath = logfile;\n\n        if (!config.statfilepath.length()) {\n          config.statfilepath = logfile;\n          config.statfilepath += \".\";\n          config.statfilepath += config.statfilesuffix;\n        }\n\n        // Running as a user ... we log into /tmp/eos-fuse.$UID.log\n        if (!(fstderr = freopen(logfile, \"a+\", stderr))) {\n          fprintf(stdout, \"error: cannot open log file %s\\n\", logfile);\n        } else {\n          if (::chmod(logfile, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) {\n            fprintf(stderr, \"error: cannot change permission of log file %s\\n\", logfile);\n            exit(-1);\n          }\n        }\n      } else {\n        // Running as root ... we log into /var/log/eos/fuse\n        std::string log_path = \"/var/log/eos/fusex/fuse.\";\n\n        if (getenv(\"EOS_FUSE_LOG_PREFIX\") || fsname.length()) {\n          if (getenv(\"EOS_FUSE_LOG_PREFIX\")) {\n            log_path += getenv(\"EOS_FUSE_LOG_PREFIX\");\n          } else {\n            log_path += fsname;\n          }\n\n          if (!config.statfilepath.length()) config.statfilepath = log_path +\n                \".\" + config.statfilesuffix;\n\n          log_path += \".log\";\n        } else {\n          if (!config.statfilepath.length()) config.statfilepath = log_path +\n                config.statfilesuffix;\n\n          log_path += \"log\";\n        }\n\n        eos::common::Path cPath(log_path.c_str());\n        cPath.MakeParentPath(S_IRWXU | S_IRGRP | S_IROTH);\n        config.logfilepath = log_path;\n        ;\n\n        if (!(fstderr = freopen(cPath.GetPath(), \"a+\", stderr))) {\n          fprintf(stderr, \"error: cannot open log file %s\\n\", cPath.GetPath());\n        } else if (::chmod(cPath.GetPath(), S_IRUSR | S_IWUSR)) {\n          fprintf(stderr, \"error: failed to chmod %s\\n\", cPath.GetPath());\n        }\n      }\n\n      if (fstderr) {\n        setvbuf(fstderr, (char*) NULL, _IONBF, 0);\n      }\n\n      eos::common::Logging::GetInstance().SetUnit(\"FUSE@eosxd\");\n      eos::common::Logging::GetInstance().gShortFormat = true;\n      eos::common::Logging::GetInstance().SetFilter(\"DumpStatistic\");\n      eos::common::Logging::GetInstance().SetIndexSize(512);\n\n      if (config.options.debug) {\n        eos::common::Logging::GetInstance().SetLogPriority(LOG_DEBUG);\n      } else {\n        if (config.options.debuglevel) {\n          eos::common::Logging::GetInstance().SetLogPriority(config.options.debuglevel);\n        } else {\n          eos::common::Logging::GetInstance().SetLogPriority(LOG_INFO);\n        }\n      }\n\n      eos::common::Logging::GetInstance().SetIndexSize(512);\n      eos::common::Logging::GetInstance().EnableRateLimiter();\n      fprintf(stderr, \"Logging: suspended %d running %d in q %d\\n\",\n              eos::common::Logging::GetInstance().LB->log_suspended,\n              eos::common::Logging::GetInstance().LB->log_thread_started,\n              eos::common::Logging::GetInstance().LB->log_buffer_in_q);\n      eos::common::Logging::GetInstance().LB->resume();\n      eos_static_debug(\"\");\n      fprintf(stderr, \"Logging: suspended %d running %d in q %d\\n\",\n              eos::common::Logging::GetInstance().LB->log_suspended,\n              eos::common::Logging::GetInstance().LB->log_thread_started,\n              eos::common::Logging::GetInstance().LB->log_buffer_in_q);\n      // initialize mKV in case no cache is configured to act as no-op\n      mKV.reset(new NoKV());\n#ifdef HAVE_ROCKSDB\n\n      if (!config.mdcachedir.empty()) {\n        RocksKV* kv = new RocksKV();\n        // clean old stale DBs\n        kv->clean_stores(store_directory, config.clientuuid);\n\n        if (kv->connect(config.name, config.mdcachedir) != 0) {\n          fprintf(stderr, \"error: failed to open rocksdb KV cache - path=%s\",\n                  config.mdcachedir.c_str());\n          exit(EINVAL);\n        }\n\n        mKV.reset(kv);\n      }\n\n#endif // HAVE_ROCKSDB\n      mdbackend.init(config.hostport, config.remotemountdir,\n                     config.options.md_backend_timeout,\n                     config.options.md_backend_put_timeout);\n      mds.init(&mdbackend);\n      caps.init(&mdbackend, &mds);\n      datas.init();\n      eos::common::Mapping::Init();\n\n      if (config.mqtargethost.length()) {\n        if (mds.connect(config.mqtargethost, config.mqidentity, config.mqname,\n                        config.clienthost, config.clientuuid)) {\n          fprintf(stderr,\n                  \"error: failed to connect to mgm/zmq - connect-string=%s connect-identity=%s connect-name=%s\",\n                  config.mqtargethost.c_str(), config.mqidentity.c_str(), config.mqname.c_str());\n          exit(EINVAL);\n        }\n      }\n\n      if (cachehandler::instance().init_daemonized()) {\n        exit(errno);\n      }\n\n      fusestat.Add(\"getattr\", 0, 0, 0);\n      fusestat.Add(\"setattr\", 0, 0, 0);\n      fusestat.Add(\"setattr:chown\", 0, 0, 0);\n      fusestat.Add(\"setattr:chmod\", 0, 0, 0);\n      fusestat.Add(\"setattr:utimes\", 0, 0, 0);\n      fusestat.Add(\"setattr:truncate\", 0, 0, 0);\n      fusestat.Add(\"lookup\", 0, 0, 0);\n      fusestat.Add(\"opendir\", 0, 0, 0);\n      fusestat.Add(\"readdir\", 0, 0, 0);\n#ifdef USE_FUSE3\n      fusestat.Add(\"readdirplus\", 0, 0, 0);\n#endif\n      fusestat.Add(\"releasedir\", 0, 0, 0);\n      fusestat.Add(\"statfs\", 0, 0, 0);\n      fusestat.Add(\"mknod\", 0, 0, 0);\n      fusestat.Add(\"mkdir\", 0, 0, 0);\n      fusestat.Add(\"rm\", 0, 0, 0);\n      fusestat.Add(\"unlink\", 0, 0, 0);\n      fusestat.Add(\"rmdir\", 0, 0, 0);\n      fusestat.Add(\"rename\", 0, 0, 0);\n      fusestat.Add(\"access\", 0, 0, 0);\n      fusestat.Add(\"open\", 0, 0, 0);\n      fusestat.Add(\"create\", 0, 0, 0);\n      fusestat.Add(\"read\", 0, 0, 0);\n      fusestat.Add(\"write\", 0, 0, 0);\n      fusestat.Add(\"release\", 0, 0, 0);\n      fusestat.Add(\"fsync\", 0, 0, 0);\n      fusestat.Add(\"forget\", 0, 0, 0);\n#ifdef USE_FUSE3\n      fusestat.Add(\"forgetmulti\", 0, 0, 0);\n#endif\n      fusestat.Add(\"flush\", 0, 0, 0);\n      fusestat.Add(\"getxattr\", 0, 0, 0);\n      fusestat.Add(\"setxattr\", 0, 0, 0);\n      fusestat.Add(\"listxattr\", 0, 0, 0);\n      fusestat.Add(\"removexattr\", 0, 0, 0);\n      fusestat.Add(\"readlink\", 0, 0, 0);\n      fusestat.Add(\"symlink\", 0, 0, 0);\n      fusestat.Add(\"link\", 0, 0, 0);\n      fusestat.Add(__SUM__TOTAL__, 0, 0, 0);\n      tDumpStatistic.reset(&EosFuse::DumpStatistic, this);\n      tStatCirculate.reset(&EosFuse::StatCirculate, this);\n      tMetaCacheFlush.reset(&metad::mdcflush, &mds);\n      tMetaStackFree.reset(&metad::mdstackfree, &mds);\n      tMetaCommunicate.reset(&metad::mdcommunicate, &mds);\n      tMetaCallback.reset(&metad::mdcallback, &mds);\n      tCapFlush.reset(&cap::capflush, &caps);\n\n      // wait that we get our heartbeat sent ...\n      for (size_t i = 0; i < 50; ++i) {\n        if (mds.is_visible()) {\n          break;\n        } else {\n          eos_static_notice(\"waiting for established heart-beat : %u\", i);\n          std::this_thread::sleep_for(std::chrono::milliseconds(100));\n        }\n      }\n\n      eos_static_warning(\"********************************************************************************\");\n      eos_static_warning(\"eosxd started version %s - FUSE protocol version %d\",\n                         VERSION, FUSE_USE_VERSION);\n      eos_static_warning(\"eos-instance-url       := %s\", config.hostport.c_str());\n      eos_static_warning(\"thread-pool            := %s\",\n                         config.options.libfusethreads ? \"libfuse\" : \"custom\");\n      eos_static_warning(\"zmq-connection         := %s\", config.mqtargethost.c_str());\n      eos_static_warning(\"zmq-identity           := %s\", config.mqidentity.c_str());\n      eos_static_warning(\"fd-limit               := %lu\", config.options.fdlimit);\n\n      if (config.auth.use_user_sss) {\n        eos_static_warning(\"sss-keytabfile         := %s\", config.ssskeytab.c_str());\n      }\n\n      if (config.auth.use_user_ztn) {\n        eos_static_warning(\"ztn token              := enabled\");\n      }\n\n      eos_static_warning(\"options                := backtrace=%d md-cache:%d md-enoent:%.02f md-timeout:%.02f md-put-timeout:%.02f data-cache:%d rename-sync:%d rmdir-sync:%d flush:%d flush-w-open:%d flush-w-open-sz:%ld flush-w-umount:%d locking:%d no-fsync:%s flush-nowait-exec:%s ol-mode:%03o show-tree-size:%d hide-versions:%d protect-symlink-loops:%d core-affinity:%d no-xattr:%d no-eos-xattr-listing: %d no-link:%d nocache-graceperiod:%d rm-rf-protect-level=%d rm-rf-bulk=%d t(lease)=%d t(size-flush)=%d submounts=%d ino(in-mem)=%d flock:%d\",\n                         config.options.enable_backtrace,\n                         config.options.md_kernelcache,\n                         config.options.md_kernelcache_enoent_timeout,\n                         config.options.md_backend_timeout,\n                         config.options.md_backend_put_timeout,\n                         config.options.data_kernelcache,\n                         config.options.rename_is_sync,\n                         config.options.rmdir_is_sync,\n                         config.options.global_flush,\n                         config.options.flush_wait_open,\n                         config.options.flush_wait_open_size,\n                         config.options.flush_wait_umount,\n                         config.options.global_locking,\n                         no_fsync_list.c_str(),\n                         nowait_flush_exec_list.c_str(),\n                         config.options.overlay_mode,\n                         config.options.show_tree_size,\n                         config.options.hide_versions,\n                         config.options.protect_directory_symlink_loops,\n                         config.options.cpu_core_affinity,\n                         config.options.no_xattr,\n                         config.options.no_eos_xattr_listing,\n                         config.options.no_hardlinks,\n                         config.options.nocache_graceperiod,\n                         config.options.rm_rf_protect_levels,\n                         config.options.rm_rf_bulk,\n                         config.options.leasetime,\n                         config.options.write_size_flush_interval,\n                         config.options.submounts,\n                         config.options.inmemory_inodes,\n                         config.options.flock\n                        );\n      eos_static_warning(\"cache                  := rh-type:%s rh-nom:%d rh-max:%d rh-blocks:%d rh-sparse-ratio:%.01f max-rh-buffer=%lu max-wr-buffer=%lu tot-size=%ld tot-ino=%ld jc-size=%ld jc-ino=%ld dc-loc:%s jc-loc:%s clean-thrs:%02f%%%\",\n                         cconfig.read_ahead_strategy.c_str(),\n                         cconfig.default_read_ahead_size,\n                         cconfig.max_read_ahead_size,\n                         cconfig.max_read_ahead_blocks,\n                         cconfig.read_ahead_sparse_ratio,\n                         cconfig.max_inflight_read_ahead_buffer_size,\n                         cconfig.max_inflight_write_buffer_size,\n                         cconfig.total_file_cache_size,\n                         cconfig.total_file_cache_inodes,\n                         cconfig.total_file_journal_size,\n                         cconfig.total_file_journal_inodes,\n                         cconfig.location.c_str(),\n                         cconfig.journal.c_str(),\n                         cconfig.clean_threshold);\n      eos_static_warning(\"read-recovery          := enabled:%d ropen:%d ropen-noserv:%d ropen-noserv-window:%u\",\n                         config.recovery.read,\n                         config.recovery.read_open,\n                         config.recovery.read_open_noserver,\n                         config.recovery.read_open_noserver_retrywindow);\n      eos_static_warning(\"write-recovery         := enabled:%d wopen:%d wopen-noserv:%d wopen-noserv-window:%u\",\n                         config.recovery.write,\n                         config.recovery.write_open,\n                         config.recovery.write_open_noserver,\n                         config.recovery.write_open_noserver_retrywindow);\n      eos_static_warning(\"file-inlining          := emabled:%d max-size=%lu compressor=%s\",\n                         config.inliner.max_size ? 1 : 0,\n                         config.inliner.max_size,\n                         config.inliner.default_compressor.c_str());\n      eos_static_warning(\"fuzzing                := open-async-submit:%lu(fatal:%lu) open-async-return:%lu(fatal:%lu) read-async-return:%lu\",\n                         config.fuzzing.open_async_submit,\n                         config.fuzzing.open_async_submit_fatal,\n                         config.fuzzing.open_async_return,\n                         config.fuzzing.open_async_return_fatal,\n                         config.fuzzing.read_async_return);\n      std::string xrdcl_option_string;\n      std::string xrdcl_option_loglevel;\n\n      for (auto it = xrdcl_options.begin(); it != xrdcl_options.end(); ++it) {\n        xrdcl_option_string += *it;\n        xrdcl_option_string += \":\";\n        int value = 0;\n        std::string svalue;\n        XrdCl::DefaultEnv::GetEnv()->GetInt(it->c_str(), value);\n        xrdcl_option_string += eos::common::StringConversion::GetSizeString(svalue,\n                               (unsigned long long) value);\n        xrdcl_option_string += \" \";\n      }\n\n      XrdCl::DefaultEnv::GetEnv()->GetString(\"LogLevel\", xrdcl_option_loglevel);\n      eos_static_warning(\"xrdcl-options          := %s log-level='%s' fusex-chunk-timeout=%d\",\n                         xrdcl_option_string.c_str(), xrdcl_option_loglevel.c_str(),\n                         XrdCl::Proxy::sChunkTimeout);\n#ifndef USE_FUSE3\n      fusesession = fuse_lowlevel_new(&args,\n                                      &(get_operations()),\n                                      sizeof(operations), NULL);\n\n      if ((fusesession != NULL)) {\n        if (fuse_set_signal_handlers(fusesession) != -1) {\n          fuse_session_add_chan(fusesession, fusechan);\n\n          if (getenv(\"EOS_FUSE_NO_MT\") &&\n              (!strcmp(getenv(\"EOS_FUSE_NO_MT\"), \"1\"))) {\n            err = fuse_session_loop(fusesession);\n          } else {\n            err = fuse_session_loop_mt(fusesession);\n          }\n        }\n      }\n\n#else\n\n      if (getenv(\"EOS_FUSE_NO_MT\") &&\n          (!strcmp(getenv(\"EOS_FUSE_NO_MT\"), \"1\"))) {\n        err = fuse_session_loop(fusesession);\n      } else {\n#if FUSE_USE_VERSION < 32\n        err = fuse_session_loop_mt(fusesession, opts.clone_fd);\n#else\n        struct fuse_loop_config lconfig;\n        lconfig.clone_fd = opts.clone_fd;\n        lconfig.max_idle_threads = 10;\n        err = fuse_session_loop_mt(fusesession, &lconfig);\n#endif\n      }\n\n#endif\n      // notify the locking object that the fuse session loop has finished\n      cmdet.Unmounting();\n\n      if (config.options.flush_wait_umount) {\n        datas.terminate(config.options.flush_wait_umount);\n      }\n\n      eos_static_warning(\"eosxd stopped version %s - FUSE protocol version %d\",\n                         VERSION, FUSE_USE_VERSION);\n      eos_static_warning(\"********************************************************************************\");\n      // Avoid any chance we block excessively during these finalisations\n      alarm(90);\n      tDumpStatistic.join();\n      tStatCirculate.join();\n      tMetaCacheFlush.join();\n      tMetaStackFree.join();\n      tMetaCallback.join();\n      tMetaCommunicate.join();\n      tCapFlush.join();\n      {\n        // rename the stats file\n        std::string laststat = config.statfilepath;\n        laststat += \".last\";\n        ::rename(config.statfilepath.c_str(), laststat.c_str());\n\n        if (EosFuse::Instance().config.options.jsonstats) {\n          ::rename((config.statfilepath + \".json\").c_str(), (laststat + \".json\").c_str());\n        }\n      }\n\n      if (Instance().Config().options.submounts) {\n        Mounter().terminate();\n      }\n\n      // remove the session and channel object after all threads are joined\n      if (fusesession) {\n#ifdef USE_FUSE3\n        fuse_session_unmount(fusesession);\n#endif\n        fuse_remove_signal_handlers(fusesession);\n#ifndef USE_FUSE3\n\n        if (fusechan) {\n          fuse_session_remove_chan(fusechan);\n        }\n\n#endif\n        fuse_session_destroy(fusesession);\n      }\n\n#ifdef USE_FUSE3\n      free(opts.mountpoint);\n      fuse_opt_free_args(&args);\n#else\n      fuse_unmount(local_mount_dir, fusechan);\n#endif\n      // notify the locking object that the fuse mount has finished\n      cmdet.Unlock();\n      alarm(0);\n      mKV.reset();\n\n      if (config.mdcachedir_unlink.length()) {\n        // clean rocksdb directory\n        std::string rmline = \"rm -rf \";\n        rmline += config.mdcachedir_unlink.c_str();\n        (void) system(rmline.c_str());\n      }\n    } else {\n      fprintf(stderr, \"error: failed to daemonize\\n\");\n      exit(errno ? errno : -1);\n    }\n\n    return err ? 1 : 0;\n  } catch (Json::Exception const&) {\n    fprintf(stderr, \"error: catched json config exception\");\n    exit(-1);\n  }\n\n  eos::common::Logging::GetInstance().shutDown(true);\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::umounthandler(int sig, siginfo_t* si, void* ctx)\n/* -------------------------------------------------------------------------- */\n{\n  if (Instance().Config().options.submounts) {\n    Instance().Mounter().terminate();\n  }\n\n  eos::common::handleSignal(sig, si, ctx);\n  static char systemline[4096];\n#ifndef USE_FUSE3\n  sprintf(systemline, \"fusermount -u -z %s\",\n          EosFuse::Instance().Config().localmountdir.c_str());;\n#else\n  sprintf(systemline, \"fusermount3 -u -z %s\",\n          EosFuse::Instance().Config().localmountdir.c_str());;\n#endif\n  (void) system(systemline);\n  fprintf(stderr, \"# umounthandler: executing %s\\n\", systemline);\n  fprintf(stderr,\n          \"# umounthandler: sighandler received signal %d - emitting signal %d again\\n\",\n          sig, sig);\n  (void) system(systemline);\n  signal(SIGSEGV, SIG_DFL);\n  signal(SIGABRT, SIG_DFL);\n  signal(SIGTERM, SIG_DFL);\n  signal(SIGALRM, SIG_DFL);\n#ifndef __APPLE__\n  pthread_t thread = pthread_self();\n  pthread_kill(thread, sig);\n#else\n  kill(getpid(), sig);\n#endif\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::init(void* userdata, struct fuse_conn_info* conn)\n/* -------------------------------------------------------------------------- */\n{\n  eos_static_debug(\"\");\n\n  if (EosFuse::instance().config.options.enable_backtrace) {\n    struct sigaction sa;\n    sa.sa_flags = SA_SIGINFO;\n    sigemptyset(&sa.sa_mask);\n    sa.sa_sigaction = EosFuse::umounthandler;\n\n    if (sigaction(SIGSEGV, &sa, NULL) == -1) {\n      char msg[1024];\n      snprintf(msg, sizeof(msg), \"failed to install SEGV handler\");\n      throw std::runtime_error(msg);\n    }\n\n    if (sigaction(SIGABRT, &sa, NULL) == -1) {\n      char msg[1024];\n      snprintf(msg, sizeof(msg), \"failed to install SEGV handler\");\n      throw std::runtime_error(msg);\n    }\n\n    if (sigaction(SIGTERM, &sa, NULL) == -1) {\n      char msg[1024];\n      snprintf(msg, sizeof(msg), \"failed to install SEGV handler\");\n      throw std::runtime_error(msg);\n    }\n\n    if (sigaction(SIGALRM, &sa, NULL) == -1) {\n      char msg[1024];\n      snprintf(msg, sizeof(msg), \"failed to install ALRM handler\");\n      throw std::runtime_error(msg);\n    }\n\n    if (EosFuse::instance().config.options.enable_backtrace == 2) {\n      setenv(\"EOS_ENABLE_BACKWARD_STACKTRACE\", \"1\", 1);\n    }\n  }\n\n#ifdef USE_FUSE3\n  conn->want |= FUSE_CAP_EXPORT_SUPPORT | FUSE_CAP_POSIX_LOCKS;\n  // We don't honor TRUNC on open, so require fuse to still send the truncate separately\n  conn->want &= ~FUSE_CAP_ATOMIC_O_TRUNC;\n  // FUSE_CAP_WRITEBACK_CACHE => when we enable write back cache, inode invalidation does not work anymore, so don't enable it\n  Instance().Config().options.writebackcache = true;\n  //  conn->want |= FUSE_CAP_EXPORT_SUPPORT | FUSE_CAP_POSIX_LOCKS ; // | FUSE_CAP_CACHE_SYMLINKS;\n#else\n  Instance().Config().options.writebackcache = false;\n  conn->want |= FUSE_CAP_EXPORT_SUPPORT | FUSE_CAP_POSIX_LOCKS |\n                FUSE_CAP_BIG_WRITES;\n#endif\n  conn->max_background = 128;\n  conn->congestion_threshold = 96;\n}\n\nvoid\nEosFuse::destroy(void* userdata)\n{\n  eos_static_debug(\"\");\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::DumpStatistic(ThreadAssistant& assistant)\n/* -------------------------------------------------------------------------- */\n{\n  eos_static_debug(\"started statistic dump thread\");\n  char ino_stat[16384];\n  time_t start_time = time(NULL);\n\n  while (!assistant.terminationRequested()) {\n    Json::StreamWriterBuilder builder;\n    std::unique_ptr<Json::StreamWriter> jsonwriter(\n      builder.newStreamWriter());\n    Json::Value jsonstats{};\n    meminfo.update();\n    eos::common::LinuxStat::linux_stat_t osstat;\n#ifndef __APPLE__\n    eos::common::LinuxMemConsumption::linux_mem_t mem;\n\n    if (!eos::common::LinuxMemConsumption::GetMemoryFootprint(mem)) {\n      eos_static_err(\"failed to get the MEM usage information\");\n    }\n\n    if (!eos::common::LinuxStat::GetStat(osstat)) {\n      eos_static_err(\"failed to get the OS usage information\");\n    }\n\n#endif\n    eos_static_debug(\"dumping statistics\");\n\n    if (EosFuse::Instance().config.options.jsonstats) {\n      fusestat.PrintOutTotalJson(jsonstats); //creates activity object...\n      Json::Value inodes{};\n      inodes[\"number\"]      = (Json::UInt64) this->getMdStat().inodes();\n      inodes[\"stack\"]       = (Json::UInt64) this->getMdStat().inodes_stacked();\n      inodes[\"todelete\"]    = (Json::UInt64) this->getMdStat().inodes_deleted();\n      inodes[\"backlog\"]     = (Json::UInt64) this->getMdStat().inodes_backlog();\n      inodes[\"ever\"]        = (Json::UInt64) this->getMdStat().inodes_ever();\n      inodes[\"everdeleted\"] = (Json::UInt64) this->getMdStat().inodes_deleted_ever();\n      inodes[\"open\"]        = (Json::UInt64) this->datas.size();\n      inodes[\"vmap\"]        = (Json::UInt64) this->mds.vmaps().size();\n      inodes[\"caps\"]        = (Json::UInt64) this->caps.size();\n      inodes[\"tracker\"]     = (Json::UInt64) this->Tracker().size();\n      inodes[\"rhexpired\"]   = (Json::UInt64)\n                              XrdCl::Proxy::ReadAsyncHandler::nexpired();\n      inodes[\"proxies\"]     = (Json::UInt64) XrdCl::Proxy::Proxies();\n      inodes[\"lrureset\"]    = (Json::UInt64) this->getMdStat().lru_resets();\n      jsonstats[\"inodes\"] = inodes;\n    }\n\n    std::string sout;\n    fusestat.PrintOutTotal(sout);\n    time_t now = time(NULL);\n    snprintf(ino_stat, sizeof(ino_stat),\n             \"# -----------------------------------------------------------------------------------------------------------\\n\"\n             \"ALL        inodes              := %lu\\n\"\n             \"ALL        inodes stack        := %lu\\n\"\n             \"ALL        inodes-todelete     := %lu\\n\"\n             \"ALL        inodes-backlog      := %lu\\n\"\n             \"ALL        inodes-ever         := %lu\\n\"\n             \"ALL        inodes-ever-deleted := %lu\\n\"\n             \"ALL        inodes-open         := %lu\\n\"\n             \"ALL        inodes-vmap         := %lu\\n\"\n             \"ALL        inodes-caps         := %lu\\n\"\n             \"ALL        inodes-tracker      := %lu\\n\"\n             \"ALL        rh-expired          := %lu\\n\"\n             \"ALL        proxies             := %d\\n\"\n             \"ALL        lrureset            := %ld\\n\"\n             \"# -----------------------------------------------------------------------------------------------------------\\n\",\n             this->getMdStat().inodes(),\n             this->getMdStat().inodes_stacked(),\n             this->getMdStat().inodes_deleted(),\n             this->getMdStat().inodes_backlog(),\n             this->getMdStat().inodes_ever(),\n             this->getMdStat().inodes_deleted_ever(),\n             this->datas.size(),\n             this->mds.vmaps().size(),\n             this->caps.size(),\n             this->Tracker().size(),\n             XrdCl::Proxy::ReadAsyncHandler::nexpired(),\n             XrdCl::Proxy::Proxies(),\n             this->getMdStat().lru_resets()\n            );\n    sout += ino_stat;\n    {\n      auto recovery = XrdCl::Proxy::ProxyStatHandle::Get()->Stats();\n      int32_t recovery_ok = 0;\n      int32_t recovery_fail = 0;\n      Json::Value recoveries{};\n\n      for (auto it : recovery) {\n        if (it.first.find(\"success\") != std::string::npos) {\n          recovery_ok++;\n        }\n\n        if (it.first.find(\"failed\") != std::string::npos) {\n          recovery_fail++;\n        }\n\n        if (EosFuse::Instance().config.options.jsonstats) {\n          recoveries[it.first] = (Json::UInt64) it.second;\n          jsonstats[\"recoveries\"] = recoveries;\n        }\n\n        snprintf(ino_stat, sizeof(ino_stat),\n                 \"ALL        %-45s := %lu\\n\", it.first.c_str(), it.second);\n        sout += ino_stat;\n      }\n\n      if (!EosFuse::Instance().config.options.jsonstats) {\n        sout += \"# -----------------------------------------------------------------------------------------------------------\\n\";\n      }\n\n      Instance().aRecoveryOk = recovery_ok;\n      Instance().aRecoveryFail = recovery_fail;\n    }\n    std::string s1;\n    std::string s2;\n    std::string s3;\n    std::string s4;\n    std::string s5;\n    std::string s6;\n    std::string s7;\n    std::string s8;\n    std::string blocker;\n    std::string origin;\n    uint64_t    blocker_inode;\n    size_t      blocked_ops;\n    bool        root_blocked;\n    static std::string last_blocker = \"\";\n    static uint64_t last_blocker_inode = 0;\n    static double last_blocked_ms = 0;\n    static int last_heartbeat_age = 0;\n    static bool hb_warning = false;\n    {\n      unsigned long long rbytes, wbytes = 0;\n      unsigned long nops = 0;\n      float total_rbytes, total_wbytes = 0;\n      int sum = 0;\n      unsigned long totalram, freeram, loads0 = 0;\n      {\n        std::lock_guard g(getFuseStat().Mutex);\n        rbytes = this->getFuseStat().GetTotal(\"rbytes\");\n        wbytes = this->getFuseStat().GetTotal(\"wbytes\");\n        nops = this->getFuseStat().GetOps();\n        total_rbytes = this->getFuseStat().GetTotalAvg5(\"rbytes\") / 1000.0 / 1000.0;\n        total_wbytes = this->getFuseStat().GetTotalAvg5(\"wbytes\") / 1000.0 / 1000.0;\n        sum = (int) this->getFuseStat().GetTotalAvg5(\":sum\");\n      }\n      {\n        std::lock_guard<std::mutex> lock(meminfo.mutex());\n        totalram = meminfo.getref().totalram;\n        freeram = meminfo.getref().freeram;\n        loads0 = meminfo.getref().loads[0];\n      }\n      double blocked_ms = this->Tracker().blocked_ms(blocker, blocker_inode, origin,\n                          blocked_ops, root_blocked);\n      const time_t last_heartbeat = EosFuse::Instance().mds.last_heartbeat;\n      int heartbeat_age = 0;\n\n      if (last_heartbeat) {\n        heartbeat_age = time(NULL) - last_heartbeat;\n      }\n\n      if (EosFuse::Instance().config.options.jsonstats) {\n        Json::Value stats {};\n        stats[\"threads\"]             = (Json::LargestUInt) osstat.threads;\n        stats[\"vsize\"]               =\n          eos::common::StringConversion::GetReadableSizeString(s1, osstat.vsize, \"b\");\n        stats[\"rss\"]                 =\n          eos::common::StringConversion::GetReadableSizeString(s2, osstat.rss, \"b\");\n        stats[\"pid\"]                 = (Json::UInt) getpid();\n        stats[\"log-size\"]            = (Json::LargestUInt) this->sizeLogFile();\n        stats[\"wr-buf-inflight\"]     =\n          eos::common::StringConversion::GetReadableSizeString(s3,\n              XrdCl::Proxy::sWrBufferManager.inflight(), \"b\");\n        stats[\"wr-buf-queued\"]       =\n          eos::common::StringConversion::GetReadableSizeString(s4,\n              XrdCl::Proxy::sWrBufferManager.queued(), \"b\");\n        stats[\"wr-nobuff\"]           = (Json::LargestUInt)\n                                       XrdCl::Proxy::sWrBufferManager.nobuf();\n        stats[\"ra-buf-inflight\"]     =\n          eos::common::StringConversion::GetReadableSizeString(s5,\n              XrdCl::Proxy::sRaBufferManager.inflight(), \"b\");\n        stats[\"ra-buf-queued\"]       =\n          eos::common::StringConversion::GetReadableSizeString(s6,\n              XrdCl::Proxy::sRaBufferManager.queued(), \"b\");\n        stats[\"ra-xoff\"]             = (Json::LargestUInt)\n                                       XrdCl::Proxy::sRaBufferManager.xoff();\n        stats[\"ra-nobuff\"]           = (Json::LargestUInt)\n                                       XrdCl::Proxy::sRaBufferManager.nobuf();\n        stats[\"rd-buf-inflight\"]     =\n          eos::common::StringConversion::GetReadableSizeString(s7,\n              data::datax::sBufferManager.inflight(), \"b\");\n        stats[\"rd-buf-queued\"]       =\n          eos::common::StringConversion::GetReadableSizeString(s8,\n              data::datax::sBufferManager.queued(), \"b\");\n        stats[\"version\"]             = VERSION;\n        stats[\"fuseversion\"]         = FUSE_USE_VERSION;\n        stats[\"starttime\"]           = (Json::LargestUInt) start_time;\n        stats[\"uptime\"]              = (Json::LargestUInt) now - start_time;\n        stats[\"total-mem\"]           = (Json::LargestUInt) totalram;\n        stats[\"free-mem\"]            = (Json::LargestUInt) freeram;\n        stats[\"load\"]                = (Json::LargestUInt) loads0;\n        stats[\"total-rbytes\"]        = (Json::LargestUInt) rbytes;\n        stats[\"total-wbytes\"]        = (Json::LargestUInt) wbytes;\n        stats[\"total-io-ops\"]        = (Json::LargestUInt) nops;\n        stats[\"read-mb/s\"]          = (double) total_rbytes;\n        stats[\"write-mb/s\"]          = (double) total_wbytes;\n        stats[\"iops\"]                = sum;\n        stats[\"xoffs\"]               = (Json::LargestUInt) Instance().datas.get_xoff();\n        stats[\"instance-url\"]        = EosFuse::Instance().config.hostport.c_str();\n        stats[\"endpoint-url\"]        = lastMgmHostPort.get().c_str();\n        stats[\"client-uuid\"]         = EosFuse::Instance().config.clientuuid.c_str();\n        stats[\"server-version\"]      = EosFuse::Instance().mds.server_version().c_str();\n        stats[\"automounted\"]         = (Json::UInt)\n                                       EosFuse::Instance().Config().options.automounted;\n        stats[\"max-inode-lock-ms\"]   = blocked_ms;\n        stats[\"blocker\"]             = blocker.c_str();\n        stats[\"blocker-origin\"]      = origin.c_str();\n        stats[\"blocked-ops\"]         = (uint32_t) blocked_ops;\n        stats[\"blocked-root\"]        = root_blocked;\n        stats[\"last-heartbeat-secs\"] = (Json::UInt) heartbeat_age;\n        jsonstats[\"stats\"] = stats;\n      }\n\n      snprintf(ino_stat, sizeof(ino_stat),\n               \"ALL        threads             := %llu\\n\"\n               \"ALL        visze               := %s\\n\"\n               \"ALL        rss                 := %s\\n\"\n               \"ALL        pid                 := %d\\n\"\n               \"ALL        log-size            := %lu\\n\"\n               \"ALL        wr-buf-inflight     := %s\\n\"\n               \"ALL        wr-buf-queued       := %s\\n\"\n               \"ALL        wr-nobuff           := %lu\\n\"\n               \"ALL        ra-buf-inflight     := %s\\n\"\n               \"ALL        ra-buf-queued       := %s\\n\"\n               \"ALL        ra-xoff             := %lu\\n\"\n               \"ALL        ra-nobuff           := %lu\\n\"\n               \"ALL        rd-buf-inflight     := %s\\n\"\n               \"ALL        rd-buf-queued       := %s\\n\"\n               \"ALL        version             := %s\\n\"\n               \"ALL        fuseversion         := %d\\n\"\n               \"ALL        starttime           := %lu\\n\"\n               \"ALL        uptime              := %lu\\n\"\n               \"ALL        total-mem           := %lu\\n\"\n               \"ALL        free-mem            := %lu\\n\"\n               \"ALL        load                := %lu\\n\"\n               \"ALL        total-rbytes        := %llu\\n\"\n               \"ALL        total-wbytes        := %llu\\n\"\n               \"ALL        total-io-ops        := %lu\\n\"\n               \"ALL        read--mb/s          := %.02f\\n\"\n               \"ALL        write-mb/s          := %.02f\\n\"\n               \"ALL        iops                := %d\\n\"\n               \"ALL        xoffs               := %lu\\n\"\n               \"ALL        instance-url        := %s\\n\"\n               \"ALL        endpoint-url        := %s\\n\"\n               \"ALL        client-uuid         := %s\\n\"\n               \"ALL        server-version      := %s\\n\"\n               \"ALL        automounted         := %d\\n\"\n               \"ALL        max-inode-lock-ms   := %.02f [%s:%s] [n:%lu r:%d]\\n\"\n               \"ALL        last-heartbeat-secs := %d\\n\"\n               \"# -----------------------------------------------------------------------------------------------------------\\n\",\n               osstat.threads,\n               eos::common::StringConversion::GetReadableSizeString(s1, osstat.vsize, \"b\"),\n               eos::common::StringConversion::GetReadableSizeString(s2, osstat.rss, \"b\"),\n               getpid(),\n               this->sizeLogFile(),\n               eos::common::StringConversion::GetReadableSizeString(s3,\n                   XrdCl::Proxy::sWrBufferManager.inflight(), \"b\"),\n               eos::common::StringConversion::GetReadableSizeString(s4,\n                   XrdCl::Proxy::sWrBufferManager.queued(), \"b\"),\n               XrdCl::Proxy::sWrBufferManager.nobuf(),\n               eos::common::StringConversion::GetReadableSizeString(s5,\n                   XrdCl::Proxy::sRaBufferManager.inflight(), \"b\"),\n               eos::common::StringConversion::GetReadableSizeString(s6,\n                   XrdCl::Proxy::sRaBufferManager.queued(), \"b\"),\n               XrdCl::Proxy::sRaBufferManager.xoff(),\n               XrdCl::Proxy::sRaBufferManager.nobuf(),\n               eos::common::StringConversion::GetReadableSizeString(s7,\n                   data::datax::sBufferManager.inflight(), \"b\"),\n               eos::common::StringConversion::GetReadableSizeString(s8,\n                   data::datax::sBufferManager.queued(), \"b\"),\n               VERSION,\n               FUSE_USE_VERSION,\n               start_time,\n               now - start_time,\n               totalram,\n               freeram,\n               loads0,\n               rbytes,\n               wbytes,\n               nops,\n               total_rbytes,\n               total_wbytes,\n               sum,\n               Instance().datas.get_xoff(),\n               EosFuse::Instance().config.hostport.c_str(),\n               lastMgmHostPort.get().c_str(),\n               EosFuse::Instance().config.clientuuid.c_str(),\n               EosFuse::Instance().mds.server_version().c_str(),\n               EosFuse::Instance().Config().options.automounted,\n               blocked_ms,\n               blocker.c_str(),\n               origin.c_str(),\n               blocked_ops,\n               root_blocked,\n               heartbeat_age\n              );\n\n      if (blocker_inode != 1) {\n        if (blocker.length() && last_blocker.empty()) {\n          std::string url = Instance().datas.url(blocker_inode);\n\n          if (url.empty()) {\n            url = Instance().mds.getpath(blocker_inode);\n          }\n\n          eos_static_warning(\"IO blocked on ino=%#lx for op=%s since %.02f ms { %s }\",\n                             blocker_inode, blocker.c_str(), blocked_ms, url.c_str());\n        }\n\n        if (blocker.empty() && last_blocker.length()) {\n          std::string url = Instance().datas.url(last_blocker_inode).c_str();\n\n          if (url.empty()) {\n            url = Instance().mds.getpath(last_blocker_inode);\n          }\n\n          eos_static_warning(\"IO unblock on ino=%#lx for op=%s since %.02f ms { %s }\",\n                             last_blocker_inode, last_blocker.c_str(), last_blocked_ms, url.c_str());\n        }\n      }\n\n      if (!last_heartbeat) {\n        eos_static_warning(\"HB (heartbeat) has not started!\");\n        hb_warning = true;\n      } else {\n        if (hb_warning) {\n          eos_static_warning(\"HB (heartbeat) has started!\");\n          hb_warning = false;\n        }\n\n        if (heartbeat_age > 10) {\n          if ((heartbeat_age - last_heartbeat_age) > 15) {\n            eos_static_warning(\"HB (heartbeat) is stuck since %d seconds - we might get evicted\",\n                               heartbeat_age);\n            last_heartbeat_age = heartbeat_age;\n          }\n        } else {\n          if (last_heartbeat_age > 10) {\n            eos_static_warning(\"HB (heartbeat) is back\");\n          }\n\n          last_heartbeat_age = 0;\n        }\n      }\n\n      last_blocker_inode = blocker_inode;\n      last_blocker = blocker;\n      last_blocked_ms = blocked_ms;\n    }\n\n    if (EosFuse::Instance().config.options.jsonstats) {\n      std::ofstream dumpjsonfile(EosFuse::Instance().config.statfilepath + \".json\");\n      jsonwriter->write(jsonstats, &dumpjsonfile);\n    }\n\n    sout += ino_stat;\n    std::ofstream dumpfile(EosFuse::Instance().config.statfilepath);\n    dumpfile << sout;\n    this->statsout.set(sout);\n    shrinkLogFile();\n    assistant.wait_for(std::chrono::seconds(1));\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::StatCirculate(ThreadAssistant& assistant)\n/* -------------------------------------------------------------------------- */\n{\n  eos_static_debug(\"started stat circulate thread\");\n  fusestat.Circulate(assistant);\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  int rc = 0;\n  fuse_id id(req);\n  struct fuse_entry_param e;\n  metad::shared_md md = Instance().mds.getlocal(req, ino);\n\n  if (ino != 1) {\n    md->Locker().Lock();\n\n    if (!(*md)()->id() || (md->deleted() && !md->lookup_is())) {\n      rc = md->deleted() ? ENOENT : (*md)()->err();\n    } else {\n      fuse_ino_t cap_ino = S_ISDIR((*md)()->mode()) ? ino : (*md)()->pid();\n      // for consistentcy with EosFuse::lookup do not check for x-permission\n      cap::shared_cap pcap = Instance().caps.acquire(req, cap_ino ? cap_ino : 1,\n                             S_IFDIR);\n      double cap_lifetime = 0;\n      XrdSysMutexHelper capLock(pcap->Locker());\n\n      if ((*pcap)()->errc()) {\n        rc = (*pcap)()->errc();\n        capLock.UnLock();\n      } else {\n        cap_lifetime = pcap->lifetime();\n\n        if (md->needs_refresh()) {\n          md->Locker().UnLock();\n          std::string authid = (*pcap)()->authid();\n          capLock.UnLock();\n          md = Instance().mds.get(req, ino);\n          md->Locker().Lock();\n\n          if (!(*md)()->id() || (md->deleted() && !md->lookup_is())) {\n            rc = md->deleted() ? ENOENT : (*md)()->err();\n          }\n        } else {\n          capLock.UnLock();\n        }\n\n        if (!rc) {\n          md->convert(e, cap_lifetime);\n          eos_static_info(\"%s\", md->dump(e).c_str());\n        }\n      }\n    }\n\n    md->Locker().UnLock();\n  } else {\n    // mountpoint stat does not require a cap\n    XrdSysMutexHelper mLock(md->Locker());\n\n    if (!(*md)()->id()) {\n      rc = (*md)()->err();\n    } else {\n      md->convert(e);\n      eos_static_info(\"%s\", md->dump(e).c_str());\n    }\n  }\n\n  if (rc) {\n    fuse_reply_err(req, rc);\n  } else {\n    fuse_reply_attr(req, &e.attr, e.attr_timeout);\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, ino, fi, rc).c_str());\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::setattr(fuse_req_t req, fuse_ino_t ino, struct stat* attr, int op,\n                 struct fuse_file_info* fi)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"ino=%d\", ino);\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  int rc = 0;\n  fuse_id id(req);\n  cap::shared_cap pcap;\n  metad::shared_md md;\n  bool md_update_sync = false;     /* wait for MD update for return code */\n  md = Instance().mds.get(req, ino);\n  md->Locker().Lock();\n\n  if (op == 0) {\n    rc = EINVAL;\n  } else if (!(*md)()->id() || (md->deleted() && !md->lookup_is())) {\n    rc = md->deleted() ? ENOENT : (*md)()->err();\n  } else {\n    fuse_ino_t cap_ino = S_ISDIR((*md)()->mode()) ? ino : (*md)()->pid();\n\n    if (op & FUSE_SET_ATTR_MODE) {\n      // chmod permissions are derived from the parent in case of a directory or file\n      // otherwise we trap ourselfs when revoking W_OK\n      if (S_ISDIR((*md)()->mode())) {\n        cap_ino = (*md)()->pid();\n      }\n\n      // retrieve cap for mode setting\n      pcap = Instance().caps.acquire(req, cap_ino,\n                                     M_OK);\n    } else if ((op & FUSE_SET_ATTR_UID) || (op & FUSE_SET_ATTR_GID)) {\n      // retrieve cap for owner setting\n      pcap = Instance().caps.acquire(req, cap_ino,\n                                     C_OK);\n    } else if (op & FUSE_SET_ATTR_SIZE) {\n      // retrieve cap for write\n      pcap = Instance().caps.acquire(req, cap_ino,\n                                     W_OK);\n    } else if ((op & FUSE_SET_ATTR_ATIME)\n               || (op & FUSE_SET_ATTR_MTIME)\n#ifdef USE_FUSE3\n               || (op & FUSE_SET_ATTR_CTIME)\n#endif\n               || (op & FUSE_SET_ATTR_ATIME_NOW)\n               || (op & FUSE_SET_ATTR_MTIME_NOW)\n              ) {\n      // retrieve cap for write\n      pcap = Instance().caps.acquire(req, cap_ino,\n                                     W_OK);\n      pcap->Locker().Lock();\n\n      if ((*pcap)()->errc()) {\n        pcap->Locker().UnLock();\n        // retrieve cap for set utime\n        pcap = Instance().caps.acquire(req, cap_ino, SU_OK);\n      } else {\n        pcap->Locker().UnLock();\n      }\n    }\n\n    pcap->Locker().Lock();\n\n    if ((*pcap)()->errc()) {\n      pcap->Locker().UnLock();\n\n      // don't fail chown not changing the owner,\n      if ((op & FUSE_SET_ATTR_UID) && ((*md)()->uid() == (int) attr->st_uid)) {\n        rc = 0;\n      } else {\n        if ((op & FUSE_SET_ATTR_GID) && ((*md)()->gid() == (int) attr->st_gid)) {\n          rc = 0;\n        } else {\n          rc = (*pcap)()->errc();\n        }\n      }\n    } else {\n      pcap->Locker().UnLock();\n\n      if (op & FUSE_SET_ATTR_MODE) {\n        /*\n          EACCES Search permission is denied on a component of the path prefix.\n\n          EFAULT path points outside your accessible address space.\n\n          EIO    An I/O error occurred.\n\n          ELOOP  Too many symbolic links were encountered in resolving path.\n\n          ENAMETOOLONG\n           path is too long.\n\n          ENOENT The file does not exist.\n\n          ENOMEM Insufficient kernel memory was available.\n\n          ENOTDIR\n           A component of the path prefix is not a directory.\n\n          EPERM  The  effective  UID does not match the owner of the file,\n           and the process is not privileged (Linux: it does not\n\n           have the CAP_FOWNER capability).\n\n          EROFS  The named file resides on a read-only filesystem.\n\n          The general errors for fchmod() are listed below:\n\n          EBADF  The file descriptor fd is not valid.\n\n          EIO    See above.\n\n          EPERM  See above.\n\n          EROFS  See above.\n         */\n        ADD_FUSE_STAT(\"setattr:chmod\", req);\n        EXEC_TIMING_BEGIN(\"setattr:chmod\");\n        struct timespec tsnow;\n        eos::common::Timing::GetTimeSpec(tsnow);\n        (*md)()->set_ctime(tsnow.tv_sec);\n        (*md)()->set_ctime_ns(tsnow.tv_nsec);\n        (*md)()->set_mode(attr->st_mode);\n\n        if (S_ISDIR((*md)()->mode())) {\n          // if this is a directory we have to revoke a potential existing cap for that directory\n          cap::shared_cap cap = Instance().caps.get(req, (*md)()->id());\n          cap->invalidate();\n\n          if (Instance().mds.has_flush(ino)) {\n            // we have also to wait for the upstream flush\n            Instance().mds.wait_flush(req, md);\n          }\n        }\n\n        EXEC_TIMING_END(\"setattr:chmod\");\n      }\n\n      if ((op & FUSE_SET_ATTR_UID) || (op & FUSE_SET_ATTR_GID)) {\n        /*\n          EACCES Search permission is denied on a component of the path prefix.\n\n          EFAULT path points outside your accessible address space.\n\n          ELOOP  Too many symbolic links were encountered in resolving path.\n\n          ENAMETOOLONG\n           path is too long.\n\n          ENOENT The file does not exist.\n\n          ENOMEM Insufficient kernel memory was available.\n\n          ENOTDIR\n           A component of the path prefix is not a directory.\n\n          EPERM  The calling process did not have the required permissions\n           (see above) to change owner and/or group.\n\n          EROFS  The named file resides on a read-only filesystem.\n\n          The general errors for fchown() are listed below:\n\n          EBADF  The descriptor is not valid.\n\n          EIO    A low-level I/O error occurred while modifying the inode.\n\n          ENOENT See above.\n\n          EPERM  See above.\n\n          EROFS  See above.\n         */\n        ADD_FUSE_STAT(\"setattr:chown\", req);\n        EXEC_TIMING_BEGIN(\"setattr:chown\");\n\n        if (op & FUSE_SET_ATTR_UID) {\n          (*md)()->set_uid(attr->st_uid);\n        }\n\n        if (op & FUSE_SET_ATTR_GID) {\n          (*md)()->set_gid(attr->st_gid);\n        }\n\n        struct timespec tsnow;\n\n        eos::common::Timing::GetTimeSpec(tsnow);\n\n        (*md)()->set_ctime(tsnow.tv_sec);\n\n        (*md)()->set_ctime_ns(tsnow.tv_nsec);\n\n        if (S_ISDIR((*md)()->mode())) {\n          // if this is a directory we have to revoke a potential existing cap for that directory\n          cap::shared_cap cap = Instance().caps.get(req, (*md)()->id());\n          cap->invalidate();\n\n          if (Instance().mds.has_flush(ino)) {\n            // we have also to wait for the upstream flush\n            Instance().mds.wait_flush(req, md);\n          }\n        }\n\n        md_update_sync = true;\n        EXEC_TIMING_END(\"setattr:chown\");\n      }\n\n      if (\n        (op & FUSE_SET_ATTR_ATIME)\n        || (op & FUSE_SET_ATTR_MTIME)\n#ifdef USE_FUSE3\n        || (op & FUSE_SET_ATTR_CTIME)\n#endif\n        || (op & FUSE_SET_ATTR_ATIME_NOW)\n        || (op & FUSE_SET_ATTR_MTIME_NOW)\n      ) {\n        /*\n        EACCES Search permission is denied for one of the directories in\n        the  path  prefix  of  path\n\n        EACCES times  is  NULL,  the caller's effective user ID does not match\n        the owner of the file, the caller does not have\n        write access to the file, and the caller is not privileged\n        (Linux: does not have either the CAP_DAC_OVERRIDE or\n        the CAP_FOWNER capability).\n\n        ENOENT filename does not exist.\n\n        EPERM  times is not NULL, the caller's effective UID does not\n        match the owner of the file, and the caller is not priv‐\n        ileged (Linux: does not have the CAP_FOWNER capability).\n\n        EROFS  path resides on a read-only filesystem.\n         */\n        ADD_FUSE_STAT(\"setattr:utimes\", req);\n        EXEC_TIMING_BEGIN(\"setattr:utimes\");\n        eos_static_debug(\"setattr:utimes %d\", fi ? fi->fh : -1);\n        struct timespec tsnow;\n        eos::common::Timing::GetTimeSpec(tsnow);\n\n        if (op & FUSE_SET_ATTR_ATIME) {\n          (*md)()->set_atime(attr->ATIMESPEC.tv_sec);\n          (*md)()->set_atime_ns(attr->ATIMESPEC.tv_nsec);\n          (*md)()->set_ctime(tsnow.tv_sec);\n          (*md)()->set_ctime_ns(tsnow.tv_nsec);\n        }\n\n        if (op & FUSE_SET_ATTR_MTIME) {\n          (*md)()->set_mtime(attr->MTIMESPEC.tv_sec);\n          (*md)()->set_mtime_ns(attr->MTIMESPEC.tv_nsec);\n#ifndef USE_FUSE3\n          (*md)()->set_ctime(tsnow.tv_sec);\n          (*md)()->set_ctime_ns(tsnow.tv_nsec);\n#endif\n        }\n\n#ifdef USE_FUSE3\n\n        if (op & FUSE_SET_ATTR_CTIME) {\n          (*md)()->set_ctime(attr->CTIMESPEC.tv_sec);\n          (*md)()->set_ctime_ns(attr->CTIMESPEC.tv_nsec);\n        }\n\n#endif\n\n        if ((op & FUSE_SET_ATTR_ATIME_NOW) ||\n            (op & FUSE_SET_ATTR_MTIME_NOW)) {\n          if (op & FUSE_SET_ATTR_ATIME_NOW) {\n            (*md)()->set_atime(tsnow.tv_sec);\n            (*md)()->set_atime_ns(tsnow.tv_nsec);\n            (*md)()->set_ctime(tsnow.tv_sec);\n            (*md)()->set_ctime_ns(tsnow.tv_nsec);\n          }\n\n          if (op & FUSE_SET_ATTR_MTIME_NOW) {\n            (*md)()->set_mtime(tsnow.tv_sec);\n            (*md)()->set_mtime_ns(tsnow.tv_nsec);\n            (*md)()->set_ctime(tsnow.tv_sec);\n            (*md)()->set_ctime_ns(tsnow.tv_nsec);\n          }\n        }\n\n        std::string cookie = md->Cookie();\n        Instance().datas.update_cookie((*md)()->id(), cookie);\n        EXEC_TIMING_END(\"setattr:utimes\");\n      }\n\n      if (op & FUSE_SET_ATTR_SIZE) {\n        /*\n        EACCES Search  permission is denied for a component of the path\n        prefix, or the named file is not writable by the user.\n\n        EFAULT Path points outside the process's allocated address space.\n\n        EFBIG  The argument length is larger than the maximum file size.\n\n        EINTR  While blocked waiting to complete, the call was interrupted\n        by a signal handler; see fcntl(2) and signal(7).\n\n        EINVAL The argument length is negative or larger than the maximum\n        file size.\n\n        EIO    An I/O error occurred updating the inode.\n\n        EISDIR The named file is a directory.\n\n        ELOOP  Too many symbolic links were encountered in translating the\n        pathname.\n\n        ENAMETOOLONG\n        A component of a pathname exceeded 255 characters, or an\n        entire pathname exceeded 1023 characters.\n\n        ENOENT The named file does not exist.\n\n        ENOTDIR\n        A component of the path prefix is not a directory.\n\n        EPERM  The underlying filesystem does not support extending a file\n        beyond its current size.\n\n        EROFS  The named file resides on a read-only filesystem.\n\n        ETXTBSY\n        The file is a pure procedure (shared text) file that is\n        being executed.\n\n        For ftruncate() the same errors apply, but instead of things that\n        can be wrong with path, we now have things that  can\n        be wrong with the file descriptor, fd:\n\n        EBADF  fd is not a valid descriptor.\n\n        EBADF or EINVAL\n        fd is not open for writing.\n\n        EINVAL fd does not reference a regular file.\n         */\n        ADD_FUSE_STAT(\"setattr:truncate\", req);\n        EXEC_TIMING_BEGIN(\"setattr:truncate\");\n        int rc = 0;\n\n        if (!(*md)()->id() || (md->deleted() && !md->lookup_is())) {\n          rc = ENOENT;\n        } else {\n          if (((*md)()->mode() & S_IFDIR)) {\n            rc = EISDIR;\n          } else {\n            if (fi && fi->fh) {\n              // ftruncate\n              data::data_fh* io = (data::data_fh*) fi->fh;\n\n              if (io) {\n                if (!(*md)()->creator() || ((*md)()->creator() &&\n                                            ((off_t)(*md)()->size() != attr->st_size))) {\n                  // no need to truncate if we still have the creator key\n                  eos_static_debug(\"ftruncate size=%lu\", (size_t) attr->st_size);\n                  rc |= io->ioctx()->truncate(req, attr->st_size);\n                  io->ioctx()->inline_file(attr->st_size);\n                  struct timespec tsnow;\n                  eos::common::Timing::GetTimeSpec(tsnow);\n                  (*md)()->set_mtime(tsnow.tv_sec);\n                  (*md)()->set_mtime_ns(tsnow.tv_nsec);\n                  (*md)()->set_ctime(tsnow.tv_sec);\n                  (*md)()->set_ctime_ns(tsnow.tv_nsec);\n                  rc |= io->ioctx()->flush(req);\n                  rc = rc ? (errno ? errno : rc) : 0;\n                }\n              } else {\n                rc = EIO;\n              }\n            } else {\n              // truncate\n              eos_static_debug(\"truncate size=%lu\", (size_t) attr->st_size);\n              std::string cookie = md->Cookie();\n              data::shared_data io = Instance().datas.get(req, (*md)()->id(), md);\n\n              if (!(*md)()->creator() || ((*md)()->creator() &&\n                                          ((off_t)(*md)()->size() != attr->st_size))) {\n                rc = io->attach(req, cookie, true);\n                eos_static_debug(\"calling truncate\");\n                rc |= io->truncate(req, attr->st_size);\n                io->inline_file(attr->st_size);\n                rc |= io->flush(req);\n                rc |= io->detach(req, cookie, true);\n                rc = rc ? (errno ? errno : rc) : 0;\n                Instance().datas.release(req, (*md)()->id(), io);\n                struct timespec tsnow;\n                eos::common::Timing::GetTimeSpec(tsnow);\n                (*md)()->set_mtime(tsnow.tv_sec);\n                (*md)()->set_mtime_ns(tsnow.tv_nsec);\n                (*md)()->set_ctime(tsnow.tv_sec);\n                (*md)()->set_ctime_ns(tsnow.tv_nsec);\n              } else {\n                Instance().datas.release(req, (*md)()->id(), io);\n              }\n            }\n\n            if (!rc) {\n              ssize_t size_change = (int64_t)(attr->st_size) - (int64_t)(*md)()->size();\n\n              if (size_change > 0) {\n                Instance().caps.book_volume(pcap, size_change);\n              } else {\n                Instance().caps.free_volume(pcap, size_change);\n              }\n\n              (*md)()->set_size(attr->st_size);\n            }\n          }\n        }\n\n        EXEC_TIMING_END(\"setattr:truncate\");\n      }\n    }\n  }\n\n  if (md_update_sync && rc == 0) {\n    if (Instance().mds.has_flush((*md)()->id())) {\n      Instance().mds.wait_flush(req, md);\n    }\n\n    md->setop_update();\n    Instance().mds.update(req, md, (*pcap)()->authid());\n\n    if (Instance().mds.has_flush((*md)()->id())) {\n      Instance().mds.wait_flush(req, md);\n    }\n\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"id %ld err %d op %d del %d\", (*md)()->id(), (*md)()->err(),\n                       md->getop(),\n                       md->deleted());\n    }\n\n    rc = md->deleted() ? ENOENT : (*md)()->err();\n  }\n\n  if (rc) {\n    md->Locker().UnLock();\n    fuse_reply_err(req, rc);\n  } else {\n    struct fuse_entry_param e;\n    memset(&e, 0, sizeof(e));\n    md->convert(e, pcap->lifetime());\n    eos_static_info(\"%s\", md->dump(e).c_str());\n\n    if (!md_update_sync) {\n      Instance().mds.update(req, md, (*pcap)()->authid());\n    }\n\n    md->Locker().UnLock();\n    fuse_reply_attr(req, &e.attr, e.attr_timeout);\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f op=%x %s\", timing.RealTime(), op,\n                    dump(id, ino, fi, rc).c_str());\n}\n\nvoid\nEosFuse::lookup(fuse_req_t req, fuse_ino_t parent, const char* name)\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(name);\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  int rc = 0;\n  fuse_id id(req);\n  struct fuse_entry_param e;\n  memset(&e, 0, sizeof(e));\n  {\n    metad::shared_md md;\n    md = Instance().mds.lookup(req, parent, name);\n\n    if ((*md)()->id() && !md->deleted()) {\n      // lookup has traditionally not checked pcap errc, so\n      // we require no particular mode during acquire\n      cap::shared_cap pcap = Instance().caps.acquire(req, parent, 0);\n      XrdSysMutexHelper mLock(md->Locker());\n      (*md)()->set_pid(parent);\n      eos_static_info(\"%s\", md->dump(e).c_str());\n      {\n        auto attrMap = (*md)()->attr();\n\n        // fetch necessary hardlink target\n        if (attrMap.count(k_mdino)) {\n          uint64_t mdino = std::stoull(attrMap[k_mdino]);\n          uint64_t local_ino = EosFuse::Instance().mds.vmaps().forward(mdino);\n          metad::shared_md tmd = EosFuse::Instance().mds.get(req, local_ino, \"\");\n          // as we're a hard-link don't increase our lookup count as we\n          // return the target's inode to the fuse layer not our own.\n          tmd->lookup_inc();\n        } else {\n          md->lookup_inc();\n        }\n      }\n      md->convert(e, pcap->lifetime());\n    } else if (md->deleted() || (*md)()->err() == ENOENT || (*md)()->err() == 0) {\n      // negative cache entry\n      e.ino = 0;\n\n      if (Instance().Config().options.md_kernelcache_enoent_timeout) {\n        e.attr_timeout = Instance().Config().options.md_kernelcache_enoent_timeout;\n        e.entry_timeout = Instance().Config().options.md_kernelcache_enoent_timeout;\n      } else {\n        cap::shared_cap pcap = Instance().caps.acquire(req, parent, 0);\n        e.entry_timeout = pcap->lifetime();\n        metad::shared_md pmd = Instance().mds.getlocal(req, parent);\n\n        if (pmd && (*pmd)()->id()) {\n          // remember negative lookups\n          XrdSysMutexHelper mLock(pmd->Locker());\n          pmd->local_enoent().insert(name);\n        }\n      }\n\n      if (e.entry_timeout) {\n        rc = 0;\n        (*md)()->set_err(0);\n      } else {\n        rc = ENOENT;\n      }\n    }\n\n    if ((*md)()->err()) {\n      if (EOS_LOGS_DEBUG) {\n        eos_static_debug(\"returning errc=%d for ino=%#lx name=%s md-name=%s\\n\",\n                         (*md)()->err(), parent, name, (*md)()->name().c_str());\n      }\n\n      rc = (*md)()->err();\n    }\n  }\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n\n  if (e.ino) {\n    eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                      dump(id, parent, 0, rc, name).c_str());\n  } else {\n    eos_static_notice(\"t(ms)=%.03f ENOENT pino=%#lx name=%s lifetime=%.02f rc=%d\",\n                      timing.RealTime(), parent, name, e.entry_timeout, rc);\n  }\n\n  if (rc) {\n    fuse_reply_err(req, rc);\n  } else {\n    fuse_reply_entry(req, &e);\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nEosFuse::listdir(fuse_req_t req, fuse_ino_t ino, metad::shared_md& md,\n                 double& lifetime)\n/* -------------------------------------------------------------------------- */\n{\n  eos_static_debug(\"\");\n  int rc = 0;\n  fuse_id id(req);\n  // retrieve cap\n  cap::shared_cap pcap = Instance().caps.acquire(req, ino,\n                         S_IFDIR | R_OK, true);\n  XrdSysMutexHelper cLock(pcap->Locker());\n\n  if ((*pcap)()->errc()) {\n    rc = (*pcap)()->errc();\n  } else {\n    // retrieve md\n    std::string authid = (*pcap)()->authid();\n    cLock.UnLock();\n    md = Instance().mds.get(req, ino, authid, true);\n\n    if (!md) {\n      // this is weired, but instead of SEGV we throw an IO error\n      rc = EIO;\n    } else {\n      if (!(*md)()->pid() && ((*md)()->id() != 1)) {\n        if ((*md)()->err()) {\n          rc = (*md)()->err();\n        } else {\n          rc = ENOENT;\n        }\n      }\n    }\n  }\n\n  return rc;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  EXEC_TIMING_BEGIN(__func__);\n  ADD_FUSE_STAT(__func__, req);\n  fuse_ino_t pino = 0;\n  std::string name;\n  int rc = 0;\n  fuse_id id(req);\n  metad::shared_md md;\n  bool do_listdir = true;\n  double lifetime = 0;\n  {\n    Track::Monitor mon(\"opendir\", \"fs\", Instance().Tracker(), req, ino);\n\n    if (Instance().Config().options.rm_rf_protect_levels &&\n        Instance().Config().options.rm_rf_bulk &&\n        isRecursiveRm(req, true, true)) {\n      md = Instance().mds.get(req, ino);\n      XrdSysMutexHelper mLock(md->Locker());\n\n      if (md && (*md)()->attr().count(\"sys.recycle\")) {\n        do_listdir = false;\n        eos_static_warning(\"Running recursive rm (pid = %d)\", fuse_req_ctx(req)->pid);\n        // bulk rm only when a recycle bin is configured\n        {\n          name = (*md)()->name();\n\n          if (!(*md)()->id() || md->deleted()) {\n            rc = md->deleted() ? ENOENT : (*md)()->err();\n          } else {\n            if (!md->get_rmrf()) {\n              rc = Instance().mds.rmrf(req, md);\n            }\n          }\n\n          if (!rc) {\n            if (!md->get_rmrf()) {\n              if (EOS_LOGS_DEBUG) {\n                eos_static_warning(\"rm-rf marks for deletion\");\n              }\n\n              md->set_rmrf();\n            }\n          } else {\n            md->unset_rmrf();\n          }\n        }\n        mLock.UnLock();\n\n        if (EOS_LOGS_DEBUG) {\n          eos_static_debug(\"rm-rf gave retc=%d\", rc);\n        }\n\n        if (!rc) {\n          metad::shared_md pmd = Instance().mds.getlocal(req, (*md)()->pid());\n\n          if (pmd) {\n            XrdSysMutexHelper pLock(pmd->Locker());\n            pmd->local_children().erase(eos::common::StringConversion::EncodeInvalidUTF8(\n                                          name));\n            (*pmd)()->mutable_children()->erase(\n              eos::common::StringConversion::EncodeInvalidUTF8(\n                name));\n            pino = (*pmd)()->id();\n          }\n\n          rc = 0;\n\n          if (EOS_LOGS_DEBUG) {\n            eos_static_debug(\"rm-rf returns 0\");\n          }\n        }\n      }\n    }\n\n    if (do_listdir) {\n      rc = listdir(req, ino, md, lifetime);\n    }\n\n    if (!rc) {\n      XrdSysMutexHelper mLock(md->Locker());\n\n      if (!(*md)()->id() || md->deleted()) {\n        rc = md->deleted() ? ENOENT : (*md)()->err();\n      } else {\n        if (EOS_LOGS_DEBUG) {\n          eos_static_debug(\"%s\", md->dump().c_str());\n        }\n\n        if (Instance().Config().options.rm_rf_protect_levels &&\n            isRecursiveRm(req) &&\n            Instance().mds.calculateDepth(md) <=\n            Instance().Config().options.rm_rf_protect_levels) {\n          eos_static_warning(\"Blocking recursive rm (pid = %d)\", fuse_req_ctx(req)->pid);\n          rc = EPERM; // you shall not pass, muahahahahah\n        } else {\n          auto md_fh = new opendir_t;\n          md_fh->md = md;\n#ifdef USE_FUSE3\n          md_fh->lifetime = lifetime;\n#endif\n          md->opendir_inc();\n          // fh contains a dummy 0 pointer\n          eos_static_debug(\"adding ino=%08lx p-ino=%08lx\", (*md)()->id(), (*md)()->pid());\n          fi->fh = (unsigned long) md_fh;\n#ifdef USE_FUSE3\n          fi->keep_cache = 1;\n          fi->cache_readdir = 1;\n#endif\n        }\n      }\n    }\n  }\n\n  // rm-rf might need to tell the kernel cache  that this directory is gone\n  if (pino && EosFuse::Instance().Config().options.md_kernelcache) {\n    kernelcache::inval_entry(pino, name.c_str());\n  }\n\n  if (rc) {\n    fuse_reply_err(req, rc);\n  } else {\n    fuse_reply_open(req, fi);\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, ino, 0, rc).c_str());\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nEosFuse::readdir_filler(fuse_req_t req, EosFuse::opendir_t* md,\n                        mode_t& pmd_mode, uint64_t& pmd_id)\n/* -------------------------------------------------------------------------- */\n{\n  int rc = 0;\n  metad::shared_md pmd = md->md;\n  // avoid to have more than one md object locked at a time\n  XrdSysMutexHelper mLock(pmd->Locker());\n  pmd_id = (*pmd)()->id();\n  pmd_mode = (*pmd)()->mode();\n\n  // make sure, the meta-data object contains listing information\n  // it might have been invalidated by a callback)\n\n  do {\n    double lifetime = 0;\n\n    if ((*pmd)()->type() == (*pmd)()->MDLS) {\n      break;\n    }\n\n    mLock.UnLock();\n    // refresh the listing\n    eos_static_debug(\"refresh listing int=%#lx\", pmd_id);\n    rc = listdir(req, pmd_id, pmd, lifetime);\n    mLock.Lock(&pmd->Locker());\n  } while ((!rc) && ((*pmd)()->type() != (*pmd)()->MDLS));\n\n  if (md->pmd_children.size() != pmd->local_children().size() ||\n      ((md->pmd_mtime.tv_sec != (int64_t)(*pmd)()->mtime()) ||\n       (md->pmd_mtime.tv_nsec != (int64_t)(*pmd)()->mtime_ns()))) {\n    auto pmap = pmd->local_children();\n    auto it = pmap.begin();\n    // make a copy of the listing for subsequent readdir operations\n    eos_static_debug(\"copying children map [%lu]\", pmap.size());\n    md->pmd_children.clear();\n    bool fillchildset = false;\n\n    if (!md->readdir_items.size()) {\n      fillchildset = true;\n    }\n\n    std::set<std::string> listing_diff;\n\n    for (; it != pmap.end(); ++it) {\n      if (!fillchildset) {\n        listing_diff.insert(it->first);\n      }\n\n      std::string encname = eos::common::StringConversion::EncodeInvalidUTF8(\n                              it->first);\n      md->pmd_children[encname] = it->second;\n\n      if (fillchildset) {\n        md->readdir_items.push_back(encname);\n      }\n    }\n\n    if (!fillchildset) {\n      // compute difference to previous listing\n      for (size_t i = 0; i < md->readdir_items.size(); ++i) {\n        listing_diff.erase(md->readdir_items[i]);\n      }\n    }\n\n    // append all new items\n    for (auto i = listing_diff.begin(); i != listing_diff.end(); ++i) {\n      md->readdir_items.push_back(*i);\n    }\n\n    // store mtime for the current state\n    md->pmd_mtime.tv_sec = (*pmd)()->mtime();\n    md->pmd_mtime.tv_nsec = (*pmd)()->mtime_ns();\n  }\n\n  if (!md->pmd_children.size()) {\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"%s\", Instance().mds.dump_md(pmd, false).c_str());\n    }\n  }\n\n  return rc;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,\n                 struct fuse_file_info* fi)\n{\n  return EosFuse::readdir(req, ino, size, off, fi, false);\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,\n                 struct fuse_file_info* fi, bool plus)\n/* -------------------------------------------------------------------------- */\n/*\nEBADF  Invalid directory stream descriptor fi->fh\n */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  int rc = 0;\n  fuse_id id(req);\n\n  if (!fi->fh) {\n    fuse_reply_err(req, EBADF);\n    rc = EBADF;\n  } else {\n    // get the shared pointer from the open file descriptor\n    opendir_t* md = (opendir_t*) fi->fh;\n    // get the cache lifetime\n#ifdef USE_FUSE3\n    double lifetime = ((opendir_t*)(fi->fh))->lifetime;\n#endif\n    metad::shared_md pmd = md->md;\n    mode_t pmd_mode;\n    uint64_t pmd_id;\n    // refresh the current directory state\n    rc = readdir_filler(req, md, pmd_mode, pmd_id);\n    // only one readdir at a time\n    XrdSysMutexHelper lLock(md->items_lock);\n    eos_static_info(\"off=%lu size-%lu\", off, md->pmd_children.size());\n    fuse_ino_t cino = pmd_id;\n    struct stat stbuf;\n    memset(&stbuf, 0, sizeof(struct stat));\n    md->b.reset();\n    // ---------------------------------------------------------------------- //\n    // root directory has only . while all the other have . and ..\n    // ---------------------------------------------------------------------- //\n    size_t off_shift = (cino > 1) ? 2 : 1;\n\n    // ---------------------------------------------------------------------- //\n    // \".\"\n    // ---------------------------------------------------------------------- //\n    if (off == 0) {\n      // at offset=0 add the '.' directory\n      std::string bname = \".\";\n      eos_static_debug(\"list: %#lx %s\", cino, bname.c_str());\n      mode_t mode = pmd_mode;\n      stbuf.st_ino = cino;\n      stbuf.st_mode = mode;\n      size_t a_size = 0;\n#ifdef USE_FUSE3\n\n      if (plus) {\n        struct fuse_entry_param e;\n        {\n          XrdSysMutexHelper mLock(pmd->Locker());\n          pmd->convert(e, lifetime);\n        }\n        a_size = fuse_add_direntry_plus(req, md->b.ptr, size - md->b.size,\n                                        bname.c_str(), &e, ++off);\n      } else {\n        a_size = fuse_add_direntry(req, md->b.ptr, size - md->b.size,\n                                   bname.c_str(), &stbuf, ++off);\n        eos_static_info(\"name=%s ino=%08lx mode=%#lx bytes=%u/%u\",\n                        bname.c_str(), cino, mode, a_size, size - md->b.size);\n      }\n\n#else\n      a_size = fuse_add_direntry(req, md->b.ptr, size - md->b.size,\n                                 bname.c_str(), &stbuf, ++off);\n      eos_static_info(\"name=%s ino=%08lx mode=%#lx bytes=%u/%u\",\n                      bname.c_str(), cino, mode, a_size, size - md->b.size);\n#endif\n      md->b.ptr += a_size;\n      md->b.size += a_size;\n    }\n\n    // ---------------------------------------------------------------------- //\n    // \"..\"\n    // ---------------------------------------------------------------------- //\n    if (off == 1) {\n      // at offset=1 add the '..' directory\n      metad::shared_md ppmd = Instance().mds.get(req, (*pmd)()->pid(), \"\", false, 0,\n                              0,\n                              true);\n\n      // don't add a '..' at root\n      if ((cino > 1) && ppmd && ((*ppmd)()->id() == (*pmd)()->pid())) {\n        fuse_ino_t cino = 0;\n        mode_t mode = 0;\n        {\n          XrdSysMutexHelper ppLock(ppmd->Locker());\n          cino = (*pmd)()->id();\n          mode = (*pmd)()->mode();\n        }\n        std::string bname = \"..\";\n        eos_static_debug(\"list: %#lx %s\", cino, bname.c_str());\n        stbuf.st_ino = cino;\n        stbuf.st_mode = mode;\n        size_t a_size = 0;\n#ifdef USE_FUSE3\n\n        if (plus) {\n          struct fuse_entry_param e;\n          ppmd->convert(e, lifetime);\n          a_size = fuse_add_direntry_plus(req, md->b.ptr, size - md->b.size,\n                                          bname.c_str(), &e, ++off);\n        } else {\n          a_size = fuse_add_direntry(req, md->b.ptr, size - md->b.size,\n                                     bname.c_str(), &stbuf, ++off);\n          eos_static_info(\"name=%s ino=%08lx mode=%#lx bytes=%u/%u\",\n                          bname.c_str(), cino, mode, a_size, size - md->b.size);\n        }\n\n#else\n        a_size = fuse_add_direntry(req, md->b.ptr, size - md->b.size,\n                                   bname.c_str(), &stbuf, ++off);\n        eos_static_info(\"name=%s ino=%08lx mode=%#lx bytes=%u/%u\",\n                        bname.c_str(), cino, mode, a_size, size - md->b.size);\n#endif\n        md->b.ptr += a_size;\n        md->b.size += a_size;\n      }\n    }\n\n    memset(&stbuf, 0, sizeof(struct stat));\n\n    // ---------------------------------------------------------------------- //\n    // the 'rest' of a listing\n    // ---------------------------------------------------------------------- //\n\n    for (size_t i = off - off_shift; i < md->readdir_items.size(); ++i) {\n      std::string d_name = md->readdir_items[i];\n      std::string bname = eos::common::StringConversion::DecodeInvalidUTF8(d_name);\n      auto it = md->pmd_children.find(d_name);\n      fuse_ino_t cino = it->second;\n      metad::shared_md cmd = Instance().mds.get(req, cino, \"\", 0, 0, 0, true);\n\n      if (!cmd) {\n        continue;\n      }\n\n      eos_static_debug(\"list: %#lx %s (d=%d)\", cino, it->first.c_str(),\n                       cmd->deleted());\n\n      if (strncmp(d_name.c_str(), \"...eos.ino...\",\n                  13) == 0) { /* hard link deleted inodes */\n        off++;\n        continue;\n      }\n\n      mode_t mode;\n#ifdef USE_FUSE3\n      struct fuse_entry_param e;\n#endif\n      {\n        XrdSysMutexHelper cLock(cmd->Locker());\n        mode = (*cmd)()->mode();\n\n        // skip deleted entries or hidden entries\n        if (cmd->deleted()) {\n          continue;\n        }\n\n        stbuf.st_ino = cino;\n        auto attrMap = (*cmd)()->mutable_attr();\n\n        if (attrMap->count(k_mdino)) {\n          uint64_t mdino = std::stoull((*attrMap)[k_mdino]);\n          uint64_t local_ino = Instance().mds.vmaps().forward(mdino);\n\n          if (EOS_LOGS_DEBUG) {\n            eos_static_debug(\"hlnk %s id %#lx mdino '%s' (%lx) local_ino %#lx\",\n                             (*cmd)()->name().c_str(), (*cmd)()->id(), (*attrMap)[k_mdino].c_str(), mdino,\n                             local_ino);\n          }\n\n          cLock.UnLock();\n          stbuf.st_ino = local_ino;\n          metad::shared_md target = Instance().mds.get(req, local_ino, \"\", 0, 0, 0,\n                                    true);\n          mode = (*target)()->mode();\n        }\n      }\n      stbuf.st_mode = mode;\n      size_t a_size = 0;\n#ifdef USE_FUSE3\n\n      if (plus) {\n        e.attr.st_mode = mode;\n        e.attr.st_ino = cino;\n        a_size = fuse_add_direntry_plus(req, md->b.ptr, size - md->b.size,\n                                        bname.c_str(), &e, ++off);\n      } else {\n        a_size = fuse_add_direntry(req, md->b.ptr, size - md->b.size,\n                                   bname.c_str(), &stbuf, ++off);\n      }\n\n#else\n      a_size = fuse_add_direntry(req, md->b.ptr, size - md->b.size,\n                                 bname.c_str(), &stbuf, ++off);\n#endif\n\n      if (EOS_LOGS_DEBUG) {\n        eos_static_debug(\"name=%s id=%#lx ino=%#lx mode=%#o bytes=%u/%u \",\n                         bname.c_str(), cino, stbuf.st_ino, mode, a_size, size - md->b.size);\n      }\n\n      if (a_size > (size - md->b.size)) {\n        off--;\n        break;\n      }\n\n      md->b.ptr += a_size;\n      md->b.size += a_size;\n    }\n\n    if (md->b.size) {\n      fuse_reply_buf(req, md->b.buffer(), md->b.size);\n    } else {\n      fuse_reply_buf(req, md->b.buffer(), 0);\n    }\n\n    eos_static_info(\"size=%lu off=%llu reply-size=%lu \\n\",\n                    size, off, md->b.size);\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, ino, 0, rc).c_str());\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,\n                     struct fuse_file_info* fi)\n/* -------------------------------------------------------------------------- */\n/* calls readdir with 'plus' flag to fill stat information\n */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  EosFuse::readdir(req, ino, size, off, fi, true);\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  EXEC_TIMING_BEGIN(__func__);\n  ADD_FUSE_STAT(__func__, req);\n  int rc = 0;\n  fuse_id id(req);\n  opendir_t* md = (opendir_t*) fi->fh;\n\n  if (md) {\n    // The following two lines act as a barrier to ensure the last readdir() has\n    // released items_lock. From the point of view of the FUSE kernel module,\n    // once we call fuse_reply_buf inside readdir, that syscall is over, and it\n    // is free to call releasedir. This creates a race condition where we try to\n    // delete md while readdir still holds items_lock - the following two lines\n    // prevent this.\n    md->items_lock.Lock();\n    md->items_lock.UnLock();\n    md->md->opendir_dec(1);\n    delete md;\n    fi->fh = 0;\n  }\n\n  EXEC_TIMING_END(__func__);\n  fuse_reply_err(req, 0);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, ino, 0, rc).c_str());\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::statfs(fuse_req_t req, fuse_ino_t ino)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  int rc = 0;\n  fuse_id id(req);\n  struct statvfs svfs;\n  memset(&svfs, 0, sizeof(struct statvfs));\n  rc = Instance().mds.statvfs(req, &svfs);\n\n  if (rc) {\n    fuse_reply_err(req, rc);\n  } else {\n    fuse_reply_statfs(req, &svfs);\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, ino, 0, rc).c_str());\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::mkdir(fuse_req_t req, fuse_ino_t parent, const char* name, mode_t mode)\n/* -------------------------------------------------------------------------- */\n/*\nEACCES The parent directory does not allow write permission to the process,\nor one of the directories in pathname  did\n\nnot allow search permission.  (See also path_resolution(7).)\n\nEDQUOT The user's quota of disk blocks or inodes on the filesystem has been\nexhausted.\n\nEEXIST pathname  already exists (not necessarily as a directory).\nThis includes the case where pathname is a symbolic\nlink, dangling or not.\n\nEFAULT pathname points outside your accessible address space.\n\nELOOP  Too many symbolic links were encountered in resolving pathname.\n\nEMLINK The number of links to the parent directory would exceed LINK_MAX.\n\nENAMETOOLONG\npathname was too long.\n\nENOENT A directory component in pathname does not exist or is a dangling\nsymbolic link.\n\nENOMEM Insufficient kernel memory was available.\n\nENOSPC The device containing pathname has no room for the new directory.\n\nENOSPC The new directory cannot be created because the user's disk quota is\nexhausted.\n\nENOTDIR\nA component used as a directory in pathname is not, in fact, a directory.\n\nEPERM  The filesystem containing pathname does not support the creation of\ndirectories.\n\nEROFS  pathname refers to a file on a read-only filesystem.\n */\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(name);\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  Track::Monitor mon(\"mkdir\", \"fs\", Instance().Tracker(), req, parent, true);\n  int rc = 0;\n  fuse_id id(req);\n  struct fuse_entry_param e;\n  // do a parent check\n  cap::shared_cap pcap1 = Instance().caps.acquire(req, parent,\n                          S_IFDIR | X_OK, true);\n  cap::shared_cap pcap2 = Instance().caps.acquire(req, parent,\n                          S_IFDIR | X_OK | W_OK, true);\n\n  if ((*pcap1)()->errc()) {\n    rc = (*pcap1)()->errc();\n  } else {\n    metad::shared_md md;\n    metad::shared_md pmd;\n    uint64_t del_ino = 0;\n    md = Instance().mds.lookup(req, parent, name);\n    pmd = Instance().mds.get(req, parent, (*pcap2)()->authid());\n    {\n      std::string implied_cid;\n      {\n        // logic avoiding a mkdir/rmdir/mkdir sync/async race\n        {\n          XrdSysMutexHelper pLock(pmd->Locker());\n          auto it = pmd->get_todelete().find(\n                      eos::common::StringConversion::EncodeInvalidUTF8(name));\n\n          if ((it != pmd->get_todelete().end()) && it->second) {\n            del_ino = it->second;\n          }\n        }\n\n        if (del_ino) {\n          Instance().mds.wait_upstream(req, del_ino);\n        }\n      }\n      XrdSysMutexHelper mLock(md->Locker());\n\n      for (int n = 0; md->deleted() && n < 3; n++) {\n        // we need to wait that this entry is really gone\n        Instance().mds.wait_flush(req, md);\n        mLock.UnLock();\n        md = Instance().mds.lookup(req, parent, name);\n        mLock.Lock(&md->Locker());\n      }\n\n      if ((*md)()->id() || md->deleted()) {\n        rc = EEXIST;\n      } else {\n        if ((*pcap2)()->errc()) {\n          rc = (*pcap2)()->errc();\n        } else {\n          (*md)()->set_id(0);\n          (*md)()->set_md_ino(0);\n          (*md)()->set_err(0);\n          (*md)()->set_mode(mode | S_IFDIR);\n          struct timespec ts;\n          eos::common::Timing::GetTimeSpec(ts);\n          (*md)()->set_name(name);\n\t  std::string pfullpath = (*pmd)()->fullpath();\n\t  if (pfullpath.back() != '/') {\n\t    pfullpath += \"/\";\n\t  }\n\n\t  (*md)()->set_fullpath(pfullpath + name);\n          (*md)()->set_atime(ts.tv_sec);\n          (*md)()->set_atime_ns(ts.tv_nsec);\n          (*md)()->set_mtime(ts.tv_sec);\n          (*md)()->set_mtime_ns(ts.tv_nsec);\n          (*md)()->set_ctime(ts.tv_sec);\n          (*md)()->set_ctime_ns(ts.tv_nsec);\n          (*md)()->set_btime(ts.tv_sec);\n          (*md)()->set_btime_ns(ts.tv_nsec);\n          // need to update the parent mtime\n          (*md)()->set_pmtime(ts.tv_sec);\n          (*md)()->set_pmtime_ns(ts.tv_nsec);\n          pmd->Locker().Lock();\n          (*pmd)()->set_mtime(ts.tv_sec);\n          (*pmd)()->set_mtime_ns(ts.tv_nsec);\n          (*md)()->set_uid((*pcap2)()->uid());\n          (*md)()->set_gid((*pcap2)()->gid());\n          /* xattr inheritance */\n          auto attrMap = (*md)()->mutable_attr();\n          auto pattrMap = (*pmd)()->attr();\n\n          for (auto const& elem : pattrMap) {\n            eos_static_debug(\"adding xattr[%s]=%s\", elem.first.c_str(),\n                             elem.second.c_str());\n            (*attrMap)[elem.first] = elem.second;\n          }\n\n          pmd->Locker().UnLock();\n          (*md)()->set_nlink(2);\n          (*md)()->set_creator(true);\n          (*md)()->set_type((*md)()->EXCL);\n          std::string imply_authid = eos::common::StringConversion::random_uuidstring();\n          eos_static_info(\"generating implied authid %s => %s\",\n                          (*pcap2)()->authid().c_str(),\n                          imply_authid.c_str());\n          implied_cid = Instance().caps.imply(pcap2, imply_authid, mode,\n                                              (fuse_ino_t)(*md)()->id());\n          md->cap_inc();\n          (*md)()->set_implied_authid(imply_authid);\n          rc = Instance().mds.add_sync(req, pmd, md, (*pcap2)()->authid());\n          (*md)()->set_type((*md)()->MD);\n\n          if (!rc) {\n            Instance().mds.insert(md, (*pcap2)()->authid());\n            memset(&e, 0, sizeof(e));\n            md->convert(e, pcap2->lifetime());\n            md->lookup_inc();\n            eos_static_info(\"%s\", md->dump(e).c_str());\n            {\n              XrdSysMutexHelper pLock(pmd->Locker());\n              pmd->local_enoent().erase(name);\n            }\n          }\n        }\n      }\n    }\n  }\n\n  if (rc) {\n    fuse_reply_err(req, rc);\n  } else {\n    fuse_reply_entry(req, &e);\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, parent, 0, rc, name).c_str());\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::unlink(fuse_req_t req, fuse_ino_t parent, const char* name)\n/* -------------------------------------------------------------------------- */\n/*\nEACCES Write access to the directory containing pathname is not allowed for the process's effective UID, or one of the\ndirectories in pathname did not allow search permission.  (See also path_resolution(7).)\n\nEBUSY  The file pathname cannot be unlinked because it is being used by the system or another process; for example, it\nis a mount point or the NFS client software created it to represent an  active  but  otherwise  nameless  inode\n(\"NFS silly renamed\").\n\nEFAULT pathname points outside your accessible address space.\n\nEIO    An I/O error occurred.\n\nEISDIR pathname refers to a directory.  (This is the non-POSIX value returned by Linux since 2.1.132.)\n\nELOOP  Too many symbolic links were encountered in translating pathname.\n\nENAMETOOLONG\npathname was too long.\n\nENOENT A component in pathname does not exist or is a dangling symbolic link, or pathname is empty.\n\nENOMEM Insufficient kernel memory was available.\n\nENOTDIR\nA component used as a directory in pathname is not, in fact, a directory.\n\nEPERM  The  system  does  not allow unlinking of directories, or unlinking of directories requires privileges that the\ncalling process doesn't have.  (This is the POSIX prescribed error return; as noted above, Linux returns EISDIR\nfor this case.)\n\nEPERM (Linux only)\nThe filesystem does not allow unlinking of files.\n\nEPERM or EACCES\nThe  directory  containing pathname has the sticky bit (S_ISVTX) set and the process's effective UID is neither\nthe UID of the file to be deleted nor that of the directory containing it, and the process  is  not  privileged\n(Linux: does not have the CAP_FOWNER capability).\n\nEROFS  pathname refers to a file on a read-only filesystem.\n\n */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"parent=%#lx name=%s\", parent, name);\n  }\n\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  fuse_ino_t hardlink_target_ino = 0;\n  Track::Monitor pmon(\"unlink\", \"fs\",  Instance().Tracker(), req, parent, true);\n  int rc = 0;\n  fuse_id id(req);\n  // retrieve cap\n  cap::shared_cap pcap = Instance().caps.acquire(req, parent,\n                         S_IFDIR | X_OK | D_OK, true);\n\n  if ((*pcap)()->errc()) {\n    rc = (*pcap)()->errc();\n  } else {\n    metad::shared_md pmd = NULL /* Parent */, tmd = NULL /*Hard link target */;\n    std::string sname = name;\n    uint64_t freesize = 0;\n\n    if (sname == \".\") {\n      rc = EINVAL;\n    }\n\n    if (sname.length() > 1024) {\n      rc = ENAMETOOLONG;\n    }\n\n    fuse_ino_t del_ino = 0;\n\n    if (!rc) {\n      metad::shared_md md;\n      md = Instance().mds.lookup(req, parent, name);\n      XrdSysMutexHelper lLock(md->Locker());\n\n      if (!Instance().Config().options.rename_is_sync) {\n        if (Instance().mds.has_flush((*md)()->id())) {\n          Instance().mds.wait_flush(req, md);\n        }\n      }\n\n      if (!(*md)()->id() || md->deleted()) {\n        rc = ENOENT;\n      }\n\n      if ((!rc) && (((*md)()->mode() & S_IFDIR))) {\n        rc = EISDIR;\n      }\n\n      if (!rc) {\n        if (Instance().Config().options.rm_rf_protect_levels &&\n            isRecursiveRm(req) &&\n            (Instance().mds.calculateDepth(md) <=\n             Instance().Config().options.rm_rf_protect_levels)) {\n          eos_static_warning(\"Blocking recursive rm (pid = %d )\", fuse_req_ctx(req)->pid);\n          rc = EPERM; // you shall not pass, muahahahahah\n        } else {\n          del_ino = (*md)()->id();\n          int nlink =\n            0; /* nlink has 0-origin (0 = simple file, 1 = inode has two names) */\n          auto attrMap = (*md)()->attr();\n          pmd = Instance().mds.get(req, parent, (*pcap)()->authid());\n\n          if (((*pmd)()->mode() & S_ISVTX)) {\n            if ((*pcap)()->uid() != (*md)()->uid()) {\n              // vertex directory can only be deleted by owner\n              rc = EPERM;\n            }\n          }\n\n          if (!rc) {\n            if (attrMap.count(k_mdino)) { /* This is a hard link */\n              uint64_t mdino = std::stoull(attrMap[k_mdino]);\n              uint64_t local_ino = Instance().mds.vmaps().forward(mdino);\n              tmd = Instance().mds.get(req, local_ino,\n                                       (*pcap)()->authid()); /* the target of the link */\n              hardlink_target_ino = (*tmd)()->id();\n              {\n                // if a hardlink is deleted, we should remove the local shadow entry\n                char nameBuf[256];\n                snprintf(nameBuf, sizeof(nameBuf), \"...eos.ino...%lx\", hardlink_target_ino);\n                std::string newname = nameBuf;\n                XrdSysMutexHelper pLock(pmd->Locker());\n\n                if (pmd->local_children().count(\n                      eos::common::StringConversion::EncodeInvalidUTF8(newname))) {\n                  pmd->local_children().erase(eos::common::StringConversion::EncodeInvalidUTF8(\n                                                newname));\n                  (*pmd)()->set_nchildren((*pmd)()->nchildren() - 1);\n                }\n              }\n            }\n\n            freesize = (*md)()->size();\n\n            if (EOS_LOGS_DEBUG) {\n              eos_static_debug(\"hlnk unlink %s new nlink %d %s\", name, nlink,\n                               Instance().mds.dump_md(md, false).c_str());\n            }\n\n            bool is_open;\n\n            // we have to signal the unlink always to 'the' target inode of a hardlink\n            if (hardlink_target_ino) {\n              is_open = Instance().datas.unlink(req, hardlink_target_ino);\n            } else {\n              is_open = Instance().datas.unlink(req, (*md)()->id());\n            }\n\n            // we indicate not to put a file in a recycle bin if we delete it while it is open\n            Instance().mds.remove(req, pmd, md, (*pcap)()->authid(), true, is_open);\n\n            if (attrMap.count(k_nlink)) {\n              // this is a target for hardlinks and we want to invalidate in the kernel cache\n              hardlink_target_ino = (*md)()->id();\n              md->force_refresh();\n            }\n          }\n        }\n      }\n    }\n\n    if (!rc) {\n      if (hardlink_target_ino || Instance().Config().options.rmdir_is_sync) {\n        eos_static_warning(\"waiting for flush of ino=%#lx\", del_ino);\n\n        if (del_ino) {\n          Instance().mds.wait_upstream(req, del_ino);\n\n          if (hardlink_target_ino) {\n            // refetch a possible shadow inode and unmask the local deletion\n            metad::shared_md smd = EosFuse::Instance().mds.get(req, del_ino, \"\");\n            smd->setop_none();\n          }\n        }\n      }\n\n      XrdSysMutexHelper pLock(pcap->Locker());\n      Instance().caps.free_volume(pcap, freesize);\n      Instance().caps.free_inode(pcap);\n      eos_static_debug(\"freeing %llu bytes on cap \", freesize);\n    }\n  }\n\n  fuse_reply_err(req, rc);\n\n  // the link count has changed and we have to tell the kernel cache\n  if (hardlink_target_ino &&\n      EosFuse::Instance().Config().options.md_kernelcache) {\n    eos_static_warning(\"invalidating inode ino=%#lx\", hardlink_target_ino);\n    kernelcache::inval_inode(hardlink_target_ino, true);\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, parent, 0, rc, name).c_str());\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::rmdir(fuse_req_t req, fuse_ino_t parent, const char* name)\n/* -------------------------------------------------------------------------- */\n/*\nEACCES Write access to the directory containing pathname was not allowed,\nor one of the directories in the path prefix\nof pathname did not allow search permission.\n\nEBUSY  pathname is currently in use by the system or some process that\nprevents its  removal.   On  Linux  this  means\npathname is currently used as a mount point or is the root directory of\nthe calling process.\n\nEFAULT pathname points outside your accessible address space.\n\nEINVAL pathname has .  as last component.\n\nELOOP  Too many symbolic links were encountered in resolving pathname.\n\nENAMETOOLONG\npathname was too long.\n\nENOENT A directory component in pathname does not exist or is a dangling\nsymbolic link.\n\nENOMEM Insufficient kernel memory was available.\n\nENOTDIR\npathname, or a component used as a directory in pathname, is not,\nin fact, a directory.\n\nENOTEMPTY\npathname contains entries other than . and .. ; or, pathname has ..\nas its final component.  POSIX.1-2001 also\nallows EEXIST for this condition.\n\nEPERM  The directory containing pathname has the sticky bit (S_ISVTX) set and\nthe process's effective user ID is  nei‐\nther  the  user  ID  of  the file to be deleted nor that of the\ndirectory containing it, and the process is not\nprivileged (Linux: does not have the CAP_FOWNER capability).\n\nEPERM  The filesystem containing pathname does not support the removal of\ndirectories.\n\nEROFS  pathname refers to a directory on a read-only filesystem.\n\n */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  Track::Monitor mon(\"rmdir\", \"fs\", Instance().Tracker(), req, parent, true);\n  int rc = 0;\n  fuse_id id(req);\n  // retrieve cap\n  cap::shared_cap pcap = Instance().caps.acquire(req, parent,\n                         S_IFDIR | X_OK | D_OK, true);\n\n  if ((*pcap)()->errc()) {\n    rc = (*pcap)()->errc();\n  } else {\n    std::string sname = name;\n\n    if (sname == \".\") {\n      rc = EINVAL;\n    }\n\n    if (sname.length() > 1024) {\n      rc = ENAMETOOLONG;\n    }\n\n    fuse_ino_t del_ino = 0;\n\n    if (!rc) {\n      metad::shared_md md;\n      metad::shared_md pmd;\n      md = Instance().mds.lookup(req, parent, name);\n      Track::Monitor mon(\"rmdir\", \"fs\", Instance().Tracker(), req, (*md)()->id(),\n                         true);\n      md->Locker().Lock();\n\n      if (!(*md)()->id() || md->deleted()) {\n        rc = ENOENT;\n      }\n\n      if ((!rc) && (!((*md)()->mode() & S_IFDIR))) {\n        rc = ENOTDIR;\n      }\n\n      eos_static_info(\"link=%d\", (*md)()->nlink());\n\n      if ((!rc) && (md->local_children().size())) {\n        eos_static_warning(\"not empty local children\");\n        rc = ENOTEMPTY;\n      }\n\n      if ((!rc && (*md)()->nchildren())) {\n        // if we still see children, we wait that we have sent all our MD updates upstream and refetch it\n        md->Locker().UnLock();\n        Instance().mds.wait_upstream(req, (*md)()->id());\n        md->force_refresh();\n        // if we still see children, we wait that we have sent all our MD updates upstream and refetch it\n        md = Instance().mds.lookup(req, parent, name);\n        md->Locker().Lock();\n\n        if ((*md)()->nchildren()) {\n          eos_static_warning(\"not empty children after refresh\");\n          rc = ENOTEMPTY;\n        }\n      }\n\n      if (!rc) {\n        pmd = Instance().mds.get(req, parent, (*pcap)()->authid());\n\n        if (((*pmd)()->mode() & S_ISVTX)) {\n          if ((*pcap)()->uid() != (*md)()->uid()) {\n            // vertex directory can only be deleted by owner\n            rc = EPERM;\n          }\n        }\n\n        if (!rc) {\n          Instance().mds.remove(req, pmd, md, (*pcap)()->authid());\n          del_ino = (*md)()->id();\n        }\n      }\n\n      md->Locker().UnLock();\n    }\n\n    if (!rc) {\n      if (Instance().Config().options.rmdir_is_sync) {\n        Instance().mds.wait_upstream(req, del_ino);\n      }\n    }\n  }\n\n  fuse_reply_err(req, rc);\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, parent, 0, rc, name).c_str());\n}\n\n#ifdef USE_FUSE3\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::rename(fuse_req_t req, fuse_ino_t parent, const char* name,\n                fuse_ino_t newparent, const char* newname, unsigned int flags)\n/* -------------------------------------------------------------------------- */\n#else\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::rename(fuse_req_t req, fuse_ino_t parent, const char* name,\n                fuse_ino_t newparent, const char* newname)\n/* -------------------------------------------------------------------------- */\n#endif\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  // Need to pay attention to lock order here. This is the only (?) function where\n  // we have to lock more than two inodes at the same time.\n  //\n  // Two racing requests with inverted source/target directories,\n  // eg \"mv dir1/file1 dir2/file2\" and \"mv dir2/file3 dir1/file4\" can deadlock\n  // us if we simply lock in order of source -> target.\n  //\n  // Instead, lock in order of increasing inode - both racing requests will\n  // use the same locking order, and no deadlock can occur.\n  fuse_ino_t first = std::min(parent, newparent);\n  fuse_ino_t second = std::max(parent, newparent);\n  Track::Monitor monp(\"rename\", \"fs\", Instance().Tracker(), req, first, true);\n  Track::Monitor monn(\"rename\", \"fs\", Instance().Tracker(), req, second, true,\n                      first == second);\n  int rc = 0;\n  fuse_id id(req);\n  // do a parent check\n  cap::shared_cap p1cap = Instance().caps.acquire(req, parent,\n                          S_IFDIR | W_OK | X_OK, true);\n  cap::shared_cap p2cap = Instance().caps.acquire(req, newparent,\n                          S_IFDIR | W_OK | X_OK, true);\n\n  if ((*p1cap)()->errc()) {\n    rc = (*p1cap)()->errc();\n  }\n\n  if (!rc && (*p2cap)()->errc()) {\n    rc = (*p2cap)()->errc();\n  }\n\n  if (!Instance().caps.share_quotanode(p1cap, p2cap)) {\n    // cross-quota node move\n    rc = EXDEV;\n  }\n\n  if (!rc) {\n    metad::shared_md md;\n    metad::shared_md p1md;\n    metad::shared_md p2md;\n    md = Instance().mds.lookup(req, parent, name);\n    p1md = Instance().mds.get(req, parent, (*p1cap)()->authid());\n    p2md = Instance().mds.get(req, newparent, (*p2cap)()->authid());\n    uint64_t md_ino = 0;\n    uint64_t del_ino = 0;\n    {\n      // logic avoiding a delete/rename sync.async race\n      {\n        XrdSysMutexHelper pLock(p2md->Locker());\n        auto it = p2md->get_todelete().find(\n                    eos::common::StringConversion::EncodeInvalidUTF8(newname));\n\n        if ((it != p2md->get_todelete().end()) && it->second) {\n          del_ino = it->second;\n        }\n      }\n\n      if (del_ino) {\n        Instance().mds.wait_upstream(req, del_ino);\n      }\n\n      XrdSysMutexHelper mLock(md->Locker());\n\n      if (md->deleted()) {\n        // we need to wait that this entry is really gone\n        Instance().mds.wait_flush(req, md);\n      }\n\n      if (!(*md)()->id() || md->deleted()) {\n        rc = md->deleted() ? ENOENT : (*md)()->err();\n      } else {\n        md_ino = (*md)()->id();\n      }\n\n      // If this is a move between directories of a directory then make sure\n      // there is no destination directory with the same name that is not\n      // empty.\n      if (S_ISDIR((*md)()->mode()) && ((*p1md)()->id() != (*p2md)()->id())) {\n        metad::shared_md dst_same_name = Instance().mds.lookup(req, newparent, name);\n\n        if (dst_same_name) {\n          XrdSysMutexHelper dst_dir_lock(dst_same_name->Locker());\n\n          if (dst_same_name->local_children().size()) {\n            rc = ENOTEMPTY;\n          }\n        }\n      }\n    }\n\n    if (!rc) {\n      // fake rename logic for online editing if configured\n      if (Instance().Config().options.fakerename && (*p1md)()->tmptime()) {\n        auto ends_with = [](const std::string & str, const std::string & suffix) {\n          return str.size() >= suffix.size() &&\n                 str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;\n        };\n\n        // this applies only to M documents\n        if (ends_with(newname, \".tmp\")  &&\n            (ends_with(name, \".xlsx\") ||\n             ends_with(name, \".docx\") ||\n             ends_with(name, \".pptx\"))) {\n          auto now = time(NULL);\n\n          if ((now - (*p1md)()->tmptime()) < 10) {\n            (*p1md)()->set_tmptime(0);\n            fuse_reply_err(req, rc);\n            return ;\n          }\n        }\n      }\n\n      Track::Monitor mone(\"rename\", \"fs\", Instance().Tracker(), req, md_ino, true);\n      std::string new_name = newname;\n      Instance().mds.mv(req, p1md, p2md, md, newname, (*p1cap)()->authid(),\n                        (*p2cap)()->authid());\n\n      if (Instance().Config().options.rename_is_sync) {\n        XrdSysMutexHelper mLock(md->Locker());\n        Instance().mds.wait_flush(req, md);\n      }\n    }\n  }\n\n  EXEC_TIMING_END(__func__);\n  fuse_reply_err(req, rc);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s new-parent-ino=%#lx target-name=%s\",\n                    timing.RealTime(),\n                    dump(id, parent, 0, rc, name).c_str(), newparent, newname);\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::access(fuse_req_t req, fuse_ino_t ino, int mask)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  Track::Monitor mon(\"access\", \"fs\", Instance().Tracker(), req, ino);\n  int rc = 0;\n  fuse_id id(req);\n  metad::shared_md md = Instance().mds.getlocal(req, ino);\n  metad::shared_md pmd = md;\n  mode_t mode = 0;\n  mode_t pmode = mask;\n  bool is_deleted = false;\n  fuse_ino_t pino = 0;\n  {\n    XrdSysMutexHelper mLock(md->Locker());\n    pino = ((*md)()->id() == 1) ? (*md)()->id() : (*md)()->pid();\n    mode = (*md)()->mode();\n    is_deleted = md->deleted();\n  }\n  pmode &= ~F_OK;\n\n  if (!Instance().Config().options.x_ok) {\n    // if X_OK is maked, X_OK is set to 0\n    pmode &= ~X_OK;\n  }\n\n  if ((*md)()->id() == 0) {\n    rc = is_deleted ? ENOENT : EIO;\n  } else {\n    if (S_ISREG(mode)) {\n      pmd = Instance().mds.getlocal(req, pino);\n    }\n\n    if ((*pmd)()->id() == 0) {\n      rc = EIO;\n    } else {\n      // We need a fresh cap for pmd\n      cap::shared_cap pcap = Instance().caps.acquire(req, (*pmd)()->id(),\n                             S_IFDIR | pmode);\n      XrdSysMutexHelper mLock(pcap->Locker());\n\n      if ((*pcap)()->errc()) {\n        rc = (*pcap)()->errc();\n\n        if (rc == EPERM) {\n          rc = EACCES;\n        }\n      }\n\n      if (S_ISREG(mode)) {\n        // check the execution bits\n        if (mask & X_OK) {\n          bool allowed = false;\n\n          if ((*pcap)()->uid() == (*md)()->uid()) {\n            // check user X permission\n            if (mode & S_IXUSR) {\n              allowed = true;\n            }\n          }\n\n          if ((*pcap)()->gid() == (*md)()->gid()) {\n            // check group X permission\n            if (mode & S_IXGRP) {\n              allowed = true;\n            }\n          }\n\n          // check other X permision\n          if (mode & S_IXOTH) {\n            allowed = true;\n          }\n\n          if (!allowed) {\n            rc = EACCES;\n          }\n        }\n      }\n    }\n  }\n\n  fuse_reply_err(req, rc);\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, ino, 0, rc).c_str());\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"flags=%x sync=%d\", fi->flags, (fi->flags & O_SYNC) ? 1 : 0);\n  // FMODE_EXEC: \"secret\" internal flag which can be set only by the kernel when it's\n  // reading a file destined to be used as an image for an execve.\n#define FMODE_EXEC 0x20\n  ExecveAlert execve(fi->flags & FMODE_EXEC);\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  Track::Monitor mon(\"open\", \"fs\", Instance().Tracker(), req, ino, true);\n  int rc = 0;\n  fuse_id id(req);\n  int mode = R_OK;\n\n  if (fi->flags & (O_RDWR | O_WRONLY)) {\n    mode = U_OK;\n  }\n\n  {\n    metad::shared_md md;\n    md = Instance().mds.get(req, ino);\n    XrdSysMutexHelper mLock(md->Locker());\n\n    if (!(*md)()->id() || md->deleted()) {\n      rc = md->deleted() ? ENOENT : (*md)()->err();\n    } else {\n      fuse_ino_t cap_ino = (*md)()->pid();\n\n      if ((*md)()->attr().count(\"user.acl\")) { /* file with own ACL */\n        cap_ino = (*md)()->id();\n      } else {\n        // screen for sqash image access, they only retrieve X_OK on the parent directories\n        eos::common::Path cPath((*md)()->name());\n\n        if ((mode == R_OK) && (cPath.isSquashFile())) {\n          mode = X_OK;\n        }\n      }\n\n      cap::shared_cap pcap = Instance().caps.acquire(req, cap_ino, mode);\n      XrdSysMutexHelper capLock(pcap->Locker());\n\n      if (EOS_LOGS_DEBUG) {\n        eos_static_debug(\"id=%#lx cap-ino=%#lx mode=%#o\", (*md)()->id(), cap_ino, mode);\n\n        if ((!S_ISDIR((*md)()->mode())) && (*md)()->attr().count(\"user.acl\")) {\n          eos_static_debug(\"file cap %s\", pcap->dump().c_str());\n        }\n      }\n\n      if ((*pcap)()->errc()) {\n        rc = (*pcap)()->errc();\n      } else {\n        uint64_t pquota = 0;\n\n        if (mode == U_OK) {\n          if (!(pquota = Instance().caps.has_quota(pcap, 1024 * 1024))) {\n            rc = EDQUOT;\n            eos_static_err(\"quota-error: inode=%lld size=%lld - no update under 1M quota\",\n                           ino, (*md)()->size());\n          } else {\n            Instance().caps.open_writer_inode(pcap);\n          }\n        }\n\n        if (!rc) {\n          // check if we need an encryption key for this file and if it is a 'correct' one\n          std::string eoskey = fusexrdlogin::secret(req);\n          std::string fingerprint = md->keyprint16(eoskey, md->obfuscate_key());\n\n          if (md->encrypted() && (eoskey.empty() || md->wrong_key(fingerprint))) {\n            rc = ENOKEY;\n          } else {\n            int cache_flag = 0;\n            std::string md_name = (*md)()->name();\n            uint64_t md_ino = (*md)()->md_ino();\n            uint64_t md_pino = (*md)()->md_pino();\n            std::string cookie = md->Cookie();\n\n            if ((*md)()->attr().count(\"sys.file.cache\")) {\n              cache_flag |= O_CACHE;\n            }\n\n            capLock.UnLock();\n            struct fuse_entry_param e;\n            memset(&e, 0, sizeof(e));\n            md->convert(e, pcap->lifetime());\n            std::string obfuscation_key = md->obfuscate_key();\n            mLock.UnLock();\n            data::data_fh* io = data::data_fh::Instance(Instance().datas.get(req,\n                                (*md)()->id(),\n                                md), md, (mode == U_OK), id);\n            capLock.Lock(&pcap->Locker());\n            io->set_authid((*pcap)()->authid());\n\n            if (!obfuscation_key.empty()) {\n              io->hmac.set(obfuscation_key, eoskey);\n            }\n\n            if (pquota < (*pcap)()->max_file_size()) {\n              io->set_maxfilesize(pquota);\n            } else {\n              io->set_maxfilesize((*pcap)()->max_file_size());\n            }\n\n            io->cap_ = pcap;\n            capLock.UnLock();\n            // attach a datapool object\n            fi->fh = (uint64_t) io;\n            io->ioctx()->set_remote(Instance().Config().hostport,\n                                    md_name,\n                                    md_ino,\n                                    md_pino,\n                                    req,\n                                    (mode == U_OK));\n            bool outdated = (io->ioctx()->attach(req, cookie,\n                                                 fi->flags | cache_flag) == EKEYEXPIRED);\n            fi->keep_cache = outdated ? 0 : Instance().Config().options.data_kernelcache;\n\n            if ((*md)()->creator()) {\n              fi->keep_cache = Instance().Config().options.data_kernelcache;\n            }\n\n            // files which have been broadcasted from a remote update are not cached during the first default:5 seconds\n            if ((time(NULL) - (*md)()->bc_time()) <\n                EosFuse::Instance().Config().options.nocache_graceperiod) {\n              fi->keep_cache = false;\n            }\n\n            fi->direct_io = 0;\n            eos_static_info(\"%s data-cache=%d\", md->dump(e).c_str(), fi->keep_cache);\n          }\n        }\n      }\n    }\n  }\n\n  if (rc) {\n    fuse_reply_err(req, rc);\n  } else {\n    fuse_reply_open(req, fi);\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, ino, fi, rc).c_str());\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::mknod(fuse_req_t req, fuse_ino_t parent, const char* name,\n               mode_t mode, dev_t rdev)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  int rc = 0;\n  fuse_id id(req);\n\n  if (S_ISREG(mode) || S_ISFIFO(mode)) {\n    create(req, parent, name, mode, 0);\n  } else {\n    rc = ENOSYS;\n  }\n\n  if (rc) {\n    fuse_reply_err(req, rc);\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, parent, 0, rc, name).c_str());\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::create(fuse_req_t req, fuse_ino_t parent, const char* name,\n                mode_t mode, struct fuse_file_info* fi)\n/* -------------------------------------------------------------------------- */\n/*\nEACCES The  requested  access to the file is not allowed, or search permission is denied for one of the directories in\nthe path prefix of pathname, or the file did not exist yet and write access to  the  parent  directory  is  not\nallowed.  (See also path_resolution(7).)\n\nEDQUOT Where  O_CREAT  is  specified,  the  file  does not exist, and the user's quota of disk blocks or inodes on the\nfilesystem has been exhausted.\n\nEEXIST pathname already exists and O_CREAT and O_EXCL were used.\n\nEFAULT pathname points outside your accessible address space.\n\nEFBIG  See EOVERFLOW.\n\nEINTR  While blocked waiting to complete an open of a slow device (e.g., a FIFO; see fifo(7)),  the  call  was  inter‐\nrupted by a signal handler; see signal(7).\n\nEINVAL The filesystem does not support the O_DIRECT flag. See NOTES for more information.\n\nEISDIR pathname refers to a directory and the access requested involved writing (that is, O_WRONLY or O_RDWR is set).\n\nELOOP  Too  many symbolic links were encountered in resolving pathname, or O_NOFOLLOW was specified but pathname was a\nsymbolic link.\n\nEMFILE The process already has the maximum number of files open.\n\nENAMETOOLONG\npathname was too long.\n\nENFILE The system limit on the total number of open files has been reached.\n\nENODEV pathname refers to a device special file and no corresponding device exists.  (This is a Linux kernel  bug;  in\nthis situation ENXIO must be returned.)\n\nENOENT O_CREAT  is not set and the named file does not exist.  Or, a directory component in pathname does not exist or\nis a dangling symbolic link.\n\nENOMEM Insufficient kernel memory was available.\n\nENOSPC pathname was to be created but the device containing pathname has no room for the new file.\n\nENOTDIR\nA component used as a directory in pathname is not, in fact, a directory,  or  O_DIRECTORY  was  specified  and\npathname was not a directory.\n\nENXIO  O_NONBLOCK  |  O_WRONLY is set, the named file is a FIFO and no process has the file open for reading.  Or, the\nfile is a device special file and no corresponding device exists.\n\nEOVERFLOW\npathname refers to a regular file that is too large to be opened.  The usual scenario here is that an  applica‐\ntion  compiled  on  a  32-bit  platform  without -D_FILE_OFFSET_BITS=64 tried to open a file whose size exceeds\n(2<<31)-1 bits; see also O_LARGEFILE above.  This is the error specified by  POSIX.1-2001;  in  kernels  before\n2.6.24, Linux gave the error EFBIG for this case.\n\nEPERM  The  O_NOATIME  flag was specified, but the effective user ID of the caller did not match the owner of the file\nand the caller was not privileged (CAP_FOWNER).\n\nEROFS  pathname refers to a file on a read-only filesystem and write access was requested.\n\nETXTBSY\npathname refers to an executable image which is currently being executed and write access was requested.\n\nEWOULDBLOCK\nThe O_NONBLOCK flag was specified, and an incompatible lease was held on the file (see fcntl(2)).\n */\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  fuse_ino_t pino = 0;\n  {\n    COMMONTIMING(\"_start_\", &timing);\n    Track::Monitor mon(\"create\", \"fs\", Instance().Tracker(), req, parent, true);\n\n    if (fi) {\n      eos_static_debug(\"flags=%x\", fi->flags);\n    }\n\n    ADD_FUSE_STAT(__func__, req);\n    EXEC_TIMING_BEGIN(__func__);\n    int rc = 0;\n    fuse_id id(req);\n    // do a parent check\n    cap::shared_cap pcap = Instance().caps.acquire(req, parent,\n                           S_IFDIR | W_OK, true);\n    struct fuse_entry_param e;\n    XrdSysMutexHelper capLock(pcap->Locker());\n\n    if ((*pcap)()->errc()) {\n      rc = (*pcap)()->errc();\n    } else {\n      capLock.UnLock();\n      {\n        if (!Instance().caps.has_quota(pcap, 1024 * 1024)) {\n          rc = EDQUOT;\n          eos_static_err(\"quota-error: inode=%lld name='%s' - no creation under 1M quota\",\n                         parent, name);\n        }\n      }\n\n      if (!rc) {\n        metad::shared_md md;\n        metad::shared_md pmd;\n        bool obfuscate = false;\n        md = Instance().mds.lookup(req, parent, name);\n        pmd = Instance().mds.get(req, parent, (*pcap)()->authid());\n        std::string pfullpath;\n        {\n          uint64_t del_ino = 0;\n          // logic avoiding a create/unlink/create sync/async race\n          {\n            XrdSysMutexHelper pLock(pmd->Locker());\n            auto it = pmd->get_todelete().find(\n                        eos::common::StringConversion::EncodeInvalidUTF8(name));\n\n            if ((it != pmd->get_todelete().end()) && it->second) {\n              del_ino = it->second;\n            }\n\n            obfuscate = pmd->obfuscate();\n            pfullpath = (*pmd)()->fullpath();\n\t    if (pfullpath.back() != '/') {\n\t      pfullpath += \"/\";\n\t    }\n          }\n\n          if (del_ino) {\n            Instance().mds.wait_upstream(req, del_ino);\n          }\n        }\n        XrdSysMutexHelper mLock(md->Locker());\n\n        for (int n = 0; md->deleted() && n < 3; n++) {\n          // we need to wait that this entry is really gone\n          Instance().mds.wait_flush(req, md);\n          mLock.UnLock();\n          md = Instance().mds.lookup(req, parent, name);\n          mLock.Lock(&md->Locker());\n        }\n\n        if ((*md)()->id() || md->deleted()) {\n          rc = EEXIST;\n        } else {\n          (*md)()->set_id(0);\n          (*md)()->set_md_ino(0);\n          (*md)()->set_err(0);\n          (*md)()->set_mode(mode | (S_ISFIFO(mode) ? S_IFIFO : S_IFREG));\n          (*md)()->set_fullpath(pfullpath + name);\n\n          if (S_ISFIFO(mode)) {\n            (*(*md)()->mutable_attr())[k_fifo] = \"\";\n          }\n\n          struct timespec ts;\n\n          eos::common::Timing::GetTimeSpec(ts);\n\n          (*md)()->set_name(name);\n\n          (*md)()->set_atime(ts.tv_sec);\n\n          (*md)()->set_atime_ns(ts.tv_nsec);\n\n          (*md)()->set_mtime(ts.tv_sec);\n\n          (*md)()->set_mtime_ns(ts.tv_nsec);\n\n          (*md)()->set_ctime(ts.tv_sec);\n\n          (*md)()->set_ctime_ns(ts.tv_nsec);\n\n          (*md)()->set_btime(ts.tv_sec);\n\n          (*md)()->set_btime_ns(ts.tv_nsec);\n\n          // need to update the parent mtime\n          (*md)()->set_pmtime(ts.tv_sec);\n\n          (*md)()->set_pmtime_ns(ts.tv_nsec);\n\n          (*md)()->set_uid((*pcap)()->uid());\n\n          (*md)()->set_gid((*pcap)()->gid());\n\n          (*md)()->set_type((*md)()->EXCL);\n\n          std::string eoskey;\n\n          std::string obfuscation_key;\n\n          if (obfuscate) {\n            // extracte key from environment;\n            eoskey = fusexrdlogin::secret(req);\n            // create obfuscation key based on length of secret key\n            obfuscation_key = eos::common::SymKey::RandomCipher(eoskey);\n            // store obfuscation key\n            std::string fingerprint = md->keyprint16(eoskey, obfuscation_key);\n            md->set_obfuscate_key(obfuscation_key, eoskey.length(), fingerprint);\n          }\n\n          rc = Instance().mds.add_sync(req, pmd, md, (*pcap)()->authid());\n          (*md)()->set_type((*md)()->MD);\n\n          if (!rc) {\n            Instance().mds.insert(md, (*pcap)()->authid());\n            (*md)()->set_nlink(1);\n            (*md)()->set_creator(true);\n            // avoid lock-order violation\n            {\n              const std::string fn = (*md)()->name();\n              mLock.UnLock();\n              XrdSysMutexHelper mLockParent(pmd->Locker());\n              (*pmd)()->set_mtime(ts.tv_sec);\n              (*pmd)()->set_mtime_ns(ts.tv_nsec);\n              auto ends_with = [](const std::string & str, const std::string & suffix) {\n                return str.size() >= suffix.size() &&\n                       str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;\n              };\n\n              if (ends_with(fn, \".tmp\")) {\n                if (Instance().Config().options.fakerename) {\n                  // set rename creates version attribute on parent\n                  auto map = (*pmd)()->mutable_attr();\n                  (*map)[\"user.fusex.rename.version\"] = \"1\";\n                  // store last tmp file creation time\n                  (*pmd)()->set_tmptime(time(NULL));\n                }\n              }\n\n              // get file inline size from parent attribute\n              if ((*pmd)()->attr().count(\"sys.file.inline.maxsize\")) {\n                auto maxsize = (*(*pmd)()->mutable_attr())[\"sys.file.inline.maxsize\"];\n                md->set_inlinesize(strtoull(maxsize.c_str(), 0, 10));\n              }\n\n              mLockParent.UnLock();\n              mLock.Lock(&md->Locker());\n            }\n            memset(&e, 0, sizeof(e));\n            Instance().caps.book_inode(pcap);\n            Instance().caps.open_writer_inode(pcap);\n            md->convert(e, pcap->lifetime());\n            md->lookup_inc();\n\n            if (fi) {\n              // -----------------------------------------------------------------------\n              // FUSE caches the file for reads on the same filedescriptor in the buffer\n              // cache, but the pages are released once this filedescriptor is released.\n              fi->keep_cache = Instance().Config().options.data_kernelcache;\n\n              if ((fi->flags & O_DIRECT) ||\n                  (fi->flags & O_SYNC)) {\n                fi->direct_io = 1;\n              } else {\n                fi->direct_io = 0;\n              }\n\n              std::string md_name = (*md)()->name();\n              uint64_t md_ino = (*md)()->md_ino();\n              uint64_t md_pino = (*md)()->md_pino();\n              std::string cookie = md->Cookie();\n              mLock.UnLock();\n              data::data_fh* io = data::data_fh::Instance(Instance().datas.get(req,\n                                  (*md)()->id(),\n                                  md), md, true, id);\n              io->set_authid((*pcap)()->authid());\n              io->set_maxfilesize((*pcap)()->max_file_size());\n              io->cap_ = pcap;\n              // attach a datapool object\n              fi->fh = (uint64_t) io;\n              io->ioctx()->set_remote(Instance().Config().hostport,\n                                      md_name,\n                                      md_ino,\n                                      md_pino,\n                                      req,\n                                      true);\n              io->hmac.set(obfuscation_key, eoskey);\n              io->ioctx()->attach(req, cookie, fi->flags);\n            }\n\n            XrdSysMutexHelper pLock(pmd->Locker());\n            pmd->local_enoent().erase(name);\n            pino = (*pmd)()->id();\n          }\n\n          eos_static_info(\"%s\", md->dump(e).c_str());\n        }\n      }\n    }\n\n    const mode_t umask = fuse_req_ctx(req)->umask;\n\n    if (rc) {\n      fuse_reply_err(req, rc);\n    } else {\n      if (fi) { // create\n        fuse_reply_create(req, &e, fi);\n      } else {  // mknod\n        fuse_reply_entry(req, &e);\n      }\n    }\n\n    EXEC_TIMING_END(__func__);\n    COMMONTIMING(\"_stop_\", &timing);\n    eos_static_notice(\"t(ms)=%.03f mode=%#lx umask=%x %s\", timing.RealTime(), mode,\n                      umask, dump(id, parent, 0, rc).c_str());\n  }\n\n  // after creating a file we assign a new mtime to our parent directory\n  if (pino && EosFuse::Instance().Config().options.md_kernelcache) {\n    // now the mtime is wrong 'on-top' of use\n    kernelcache::inval_inode(pino, false);\n  }\n}\n\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,\n              struct fuse_file_info* fi)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  Track::Monitor mon(\"read\", \"io\", Instance().Tracker(), req, ino);\n  eos_static_debug(\"inode=%llu size=%li off=%llu\",\n                   (unsigned long long) ino, size, (unsigned long long) off);\n  eos_static_debug(\"\");\n  fuse_id id(req);\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  data::data_fh* io = (data::data_fh*) fi->fh;\n  ssize_t res = 0;\n  int rc = 0;\n\n  if (io) {\n    char* buf = 0;\n\n    if ((res = io->ioctx()->peek_pread(req, buf, size, off)) == -1) {\n      rc = errno ? errno : EIO;\n    } else {\n      eos_static_debug(\"reply res=%lu\", res);\n\n      if (!io->hmac.key.empty()) {\n        // un-obfuscate\n        eos::common::SymKey::UnobfuscateBuffer(buf, res, off, io->hmac);\n      }\n\n      fuse_reply_buf(req, buf, res);\n    }\n\n    io->ioctx()->release_pread();\n  } else {\n    rc = ENXIO;\n  }\n\n  if (rc) {\n    fuse_reply_err(req, rc);\n  } else {\n    ADD_IO_STAT(\"rbytes\", res);\n  }\n\n  eos_static_debug(\"t(ms)=%.03f %s\", timing.RealTime(),\n                   dump(id, ino, 0, rc).c_str());\n  EXEC_TIMING_END(__func__);\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::write(fuse_req_t req, fuse_ino_t ino, const char* buf, size_t size,\n               off_t off, struct fuse_file_info* fi)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  Track::Monitor mon(\"write\", \"io\", Instance().Tracker(), req, ino, true);\n  eos_static_debug(\"inode=%lld size=%lld off=%lld buf=%lld uid=%u gid=%u\",\n                   (long long) ino, (long long) size,\n                   (long long) off, (long long) buf,\n                   fuse_req_ctx(req)->uid,\n                   fuse_req_ctx(req)->gid);\n  eos_static_debug(\"\");\n  fuse_id id(req);\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  data::data_fh* io = (data::data_fh*) fi->fh;\n  int rc = 0;\n\n  if (io && !io->edquota.load()) {\n    if (!io->hmac.key.empty()) {\n      eos::common::SymKey::ObfuscateBuffer((char*)buf, (char*)buf, size, off,\n                                           io->hmac);\n    }\n\n    eos_static_debug(\"max-file-size=%llu\", io->maxfilesize());\n\n    if ((off + size) > io->maxfilesize()) {\n      eos_static_err(\"io-error: maximum file size exceeded inode=%lld size=%lld off=%lld buf=%lld max-size=%llu\",\n                     ino, size, off, buf, io->maxfilesize());\n      rc = EFBIG;\n    } else {\n      if (!EosFuse::instance().getCap().has_quota(io->cap_, size)) {\n        eos_static_err(\"quota-error: inode=%lld size=%lld off=%lld buf=%lld\", ino, size,\n                       off, buf);\n        io->set_edquota();\n        rc = EDQUOT;\n      } else {\n        if (io->ioctx()->pwrite(req, buf, size, off) == -1) {\n          eos_static_err(\"io-error: inode=%lld size=%lld off=%lld buf=%lld errno=%d\", ino,\n                         size,\n                         off, buf, errno);\n          rc = errno ? errno : EIO;\n\n          if (rc == EDQUOT) {\n            eos_static_err(\"quota-error: inode=%lld ran out of quota - setting cap to EDQUOT\",\n                           ino);\n            EosFuse::instance().getCap().set_volume_edquota(io->cap_);\n            io->set_edquota();\n          }\n        } else {\n          {\n            XrdSysMutexHelper mLock(io->mdctx()->Locker());\n            (*(io->mdctx()))()->set_size(io->ioctx()->size());\n            {\n              struct timespec tsnow;\n              eos::common::Timing::GetTimeSpec(tsnow);\n              (*(io->md))()->set_mtime(tsnow.tv_sec);\n              (*(io->md))()->set_mtime_ns(tsnow.tv_nsec);\n              (*(io->md))()->set_ctime(tsnow.tv_sec);\n              (*(io->md))()->set_ctime_ns(tsnow.tv_nsec);\n            }\n            io->set_update();\n            // flush size updates every 5 seconds\n            time_t now = time(NULL);\n\n            if (Instance().mds.should_flush_write_size()) {\n              if (Instance().Config().options.write_size_flush_interval) {\n                if (io->ioctx()->is_wopen(req)) {\n                  // only start updating the MGM size if the file could be opened on FSTs\n                  if (io->next_size_flush.load() && (io->next_size_flush.load() < now)) {\n                    // if (io->cap_->valid()) // we want updates also after cap expiration\n                    // use the identity used during the open call !\n                    Instance().mds.update(io->fuseid(), io->md, io->authid());\n                    io->next_size_flush.store(now +\n                                              Instance().Config().options.write_size_flush_interval,\n                                              std::memory_order_seq_cst);\n                  } else {\n                    if (!io->next_size_flush.load()) {\n                      io->next_size_flush.store(now +\n                                                Instance().Config().options.write_size_flush_interval,\n                                                std::memory_order_seq_cst);\n                    }\n                  }\n                }\n              }\n            }\n          }\n          fuse_reply_write(req, size);\n        }\n      }\n    }\n  } else {\n    if (io && io->edquota.load()) {\n      rc = EDQUOT;\n    } else {\n      rc = ENXIO;\n    }\n  }\n\n  if (rc) {\n    fuse_reply_err(req, rc);\n  } else {\n    ADD_IO_STAT(\"wbytes\", size);\n  }\n\n  eos_static_debug(\"t(ms)=%.03f %s\", timing.RealTime(),\n                   dump(id, ino, 0, rc).c_str());\n  EXEC_TIMING_END(__func__);\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  Track::Monitor mon(\"release\", \"io\", Instance().Tracker(), req, ino, true);\n  int rc = 0;\n  fuse_id id(req);\n\n  if (fi->fh) {\n    data::data_fh* io = (data::data_fh*) fi->fh;\n\n    if (io->flocked.load()) {\n      // unlock all locks for that owner\n      struct flock lock;\n      lock.l_type = F_UNLCK;\n      lock.l_start = 0;\n      lock.l_len = -1;\n      lock.l_pid = fuse_req_ctx(req)->pid;\n      rc |= Instance().mds.setlk(req, io->mdctx(), &lock, 0);\n\n      if (!rc) {\n        io->set_flocked(false);\n      }\n    }\n\n    std::string cookie = \"\";\n    io->ioctx()->detach(req, cookie, io->rw);\n    Instance().caps.close_writer_inode(io->cap_);\n    data::shared_data ioctx = io->ioctx();\n    delete io;\n    Instance().datas.release(req, ino, ioctx);\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  fuse_reply_err(req, rc);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, ino, 0, rc).c_str());\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::fsync(fuse_req_t req, fuse_ino_t ino, int datasync,\n               struct fuse_file_info* fi)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"datasync=%d\", datasync);\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  Track::Monitor mon(\"fsync\", \"io\", Instance().Tracker(), req, ino);\n  int rc = 0;\n  fuse_id id(req);\n  data::data_fh* io = (data::data_fh*) fi->fh;\n\n  if (io && !io->edquota.load()) {\n    {\n      std::string fname = \"\";\n      {\n        XrdSysMutexHelper mLock(io->md->Locker());\n        fname = (*(io->md))()->name();\n      }\n\n      if (filename::matches_suffix(fname,\n                                   Instance().Config().options.no_fsync_suffixes)) {\n        if (EOS_LOGS_DEBUG) {\n          eos_static_info(\"name=%s is in no-fsync list - suppressing fsync call\",\n                          fname.c_str());\n        }\n      } else {\n        if (Instance().Config().options.global_flush) {\n          XrdSysMutexHelper mLock(io->md->Locker());\n          Instance().mds.begin_flush(req, io->md,\n                                     io->authid()); // flag an ongoing flush centrally\n        }\n\n        struct timespec tsnow;\n\n        eos::common::Timing::GetTimeSpec(tsnow);\n\n        XrdSysMutexHelper mLock(io->md->Locker());\n\n        (*(io->md))()->set_mtime(tsnow.tv_sec);\n\n        if (!rc) {\n          // step 2 call sync - this currently flushed all open filedescriptors - should be ok\n          rc = io->ioctx()->sync(); // actually wait for writes to be acknowledged\n          rc = rc ? (errno ? errno : EIO) : 0;\n        } else {\n          rc = errno ? errno : EIO;\n        }\n\n        if (Instance().Config().options.global_flush) {\n          Instance().mds.end_flush(req, io->md,\n                                   io->authid()); // unflag an ongoing flush centrally\n        }\n      }\n    }\n  }\n\n  fuse_reply_err(req, rc);\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, ino, 0, rc).c_str());\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nEosFuse::_forget(fuse_req_t req, fuse_ino_t ino, unsigned long nlookup)\n/* -------------------------------------------------------------------------- */\n{\n  int rc = 0;\n  rc = Instance().mds.forget(req, ino, nlookup);\n\n  if (!rc) {\n    Instance().Tracker().forget(ino);\n  }\n\n  return rc;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::forget(fuse_req_t req, fuse_ino_t ino, unsigned long nlookup)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"ino=%#lx nlookup=%d\", ino, nlookup);\n  fuse_id id(req);\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  int rc = _forget(req, ino, nlookup);\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s nlookup=%d\", timing.RealTime(),\n                    dump(id, ino, 0, rc).c_str(), nlookup);\n  fuse_reply_none(req);\n}\n\n#ifdef USE_FUSE3\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::forget_multi(fuse_req_t req, size_t count,\n                      struct fuse_forget_data* forgets)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n\n  for (size_t i = 0; i < count; ++i) {\n    EosFuse::_forget(req, forgets[i].ino, forgets[i].nlookup);\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  fuse_reply_none(req);\n}\n#endif\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  int rc = 0;\n  fuse_id id(req);\n  data::data_fh* io = (data::data_fh*) fi->fh;\n  bool invalidate_inode = false;\n\n  if (io) {\n    if (io->rw) {\n      Track::Monitor mon(\"flush\", \"io\", Instance().Tracker(), req, ino, true);\n\n      if (io->has_update()) {\n        cap::shared_cap pcap;\n        {\n          XrdSysMutexHelper mLock(io->md->Locker());\n          auto map = (*(io->md))()->attr();\n\n          if (map.count(\"user.acl\") > 0) { /* file has it's own ACL */\n            mLock.UnLock();\n            cap::shared_cap ccap = Instance().caps.acquire(req, (*(io->md))()->id(), W_OK,\n                                   true);\n            rc = (*ccap)()->errc();\n\n            if (rc == 0) {\n              pcap = Instance().caps.acquire(req, (*(io->md))()->pid(), S_IFDIR | X_OK, true);\n            }\n          } else {\n            mLock.UnLock();\n            pcap = Instance().caps.acquire(req, (*(io->md))()->pid(), S_IFDIR | W_OK, true);\n          }\n        }\n        XrdSysMutexHelper capLock(pcap->Locker());\n\n        if (rc == 0 && (*pcap)()->errc() != 0) {\n          rc = (*pcap)()->errc();\n        }\n\n        if (rc == 0) {\n          {\n            ssize_t size_change = (int64_t)(*(io->md))()->size() - (int64_t) io->opensize();\n\n            if (size_change > 0) {\n              Instance().caps.book_volume(pcap, size_change);\n            } else {\n              Instance().caps.free_volume(pcap, size_change);\n            }\n\n            eos_static_debug(\"booking %ld bytes on cap \", size_change);\n          }\n          capLock.UnLock();\n          struct timespec tsnow;\n          eos::common::Timing::GetTimeSpec(tsnow);\n\n          // possibly inline the file in extended attribute before mds update\n          if (io->ioctx()->inline_file()) {\n            eos_static_debug(\"file is inlined\");\n          } else {\n            eos_static_debug(\"file is not inlined\");\n          }\n\n          XrdSysMutexHelper mLock(io->md->Locker());\n          auto map = (*(io->md))()->attr();\n\n          // actually do the flush\n          if ((rc = io->ioctx()->flush(req))) {\n            // if we have a flush error, we don't update the MD record\n            invalidate_inode = true;\n            (*(io->md))()->set_size(io->opensize());\n          } else {\n            Instance().mds.update(io->fuseid(), io->md, io->authid());\n          }\n\n          std::string cookie = io->md->Cookie();\n          io->ioctx()->store_cookie(cookie);\n          capLock.Lock(&pcap->Locker());\n        }\n      }\n\n      // unlock all locks for that owner\n      struct flock lock;\n      lock.l_type = F_UNLCK;\n      lock.l_start = 0;\n      lock.l_len = -1;\n      lock.l_pid = fi->lock_owner;\n\n      if (io->flocked.load()) {\n        lock.l_pid = fuse_req_ctx(req)->pid;\n      }\n\n      rc |= Instance().mds.setlk(req, io->mdctx(), &lock, 0);\n    } else {\n      if (io->flocked.load()) {\n        struct flock lock;\n        lock.l_type = F_UNLCK;\n        lock.l_start = 0;\n        lock.l_len = -1;\n        lock.l_pid = fuse_req_ctx(req)->pid;\n        rc |= Instance().mds.setlk(req, io->mdctx(), &lock, 0);\n      }\n    }\n  }\n\n  // report slow flush before we send a reply, otherwise we can get a segv because io can be deleted!\n  if (Instance().Trace() || (timing.RealTime() > 2000)) {\n    std::string path = Instance().mds.calculateLocalPath(io->md);\n    std::string s;\n    eos_static_warning(\"flush of '%s' took %.03fms\\n%s\",\n                       Instance().Prefix(path).c_str(),\n                       timing.RealTime(),\n                       io->ioctx()->Dump(s));\n  }\n\n  fuse_reply_err(req, rc);\n\n  if (invalidate_inode) {\n    eos_static_warning(\"invalidating ino=%#lx after flush error\", ino);\n    kernelcache::inval_inode(ino, true);\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, ino, 0, rc).c_str());\n}\n\n#ifdef __APPLE__\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::getxattr(fuse_req_t req, fuse_ino_t ino, const char* xattr_name,\n                  size_t size, uint32_t position)\n/* -------------------------------------------------------------------------- */\n#else\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::getxattr(fuse_req_t req, fuse_ino_t ino, const char* xattr_name,\n                  size_t size)\n/* -------------------------------------------------------------------------- */\n#endif\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  std::string key = xattr_name;\n  eos_static_debug(\"ino=%#x %s\", ino,\n                   key.c_str()); /* key in case xattr_name == NULL */\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  Track::Monitor mon(\"getxattr\", \"fs\", Instance().Tracker(), req, ino);\n  int rc = 0;\n  fuse_id id(req);\n  cap::shared_cap pcap;\n  std::string value;\n  bool local_getxattr = false;\n  // the root user has a bypass to be able to retrieve information in\n  // realtime\n  {\n    static std::string s_md = \"system.eos.md\";\n    static std::string s_refresh = \"system.eos.refreshls\";\n    static std::string s_cap = \"system.eos.cap\";\n    static std::string s_ls_caps = \"system.eos.caps\";\n    static std::string s_ls_vmap = \"system.eos.vmap\";\n\n    if (key.substr(0, s_md.length()) == s_md) {\n      local_getxattr = true;\n      metad::shared_md md;\n      pcap = Instance().caps.get(req, ino);\n      md = Instance().mds.get(req, ino, (*pcap)()->authid());\n      {\n        value = Instance().mds.dump_md(md);\n      }\n    }\n\n    if (key.substr(0, s_refresh.length()) == s_refresh) {\n      local_getxattr = true;\n      metad::shared_md md;\n      (*md)()->set_type((*md)()->MD);\n      {\n        value = \"info: force refresh for next listing\";\n      }\n    }\n\n    if (key.substr(0, s_cap.length()) == s_cap) {\n      local_getxattr = true;\n      pcap = Instance().caps.get(req, ino);\n      {\n        value = pcap->dump();\n      }\n    }\n\n    if (fuse_req_ctx(req)->uid == 0) {\n      if (key.substr(0, s_ls_caps.length()) == s_ls_caps) {\n        local_getxattr = true;\n        value = Instance().caps.ls();\n      }\n\n      if (key.substr(0, s_ls_vmap.length()) == s_ls_vmap) {\n        local_getxattr = true;\n        value = Instance().mds.vmaps().dump();\n      }\n    }\n\n    if ((size) && (value.size() > size)) {\n      value.erase(size - 4);\n      value += \"...\";\n    }\n  }\n\n  if (!local_getxattr) {\n    {\n      metad::shared_md md;\n      metad::shared_md pmd;\n      static std::string s_sec = \"security.\";\n      static std::string s_acl_a = \"system.posix_acl_access\";\n      static std::string s_acl_d = \"system.posix_acl_default\";\n      static std::string s_apple = \"com.apple\";\n      static std::string s_racl = \"system.richacl\";\n\n      // don't return any security attribute\n      if (key.substr(0, s_sec.length()) == s_sec) {\n        rc = ENOATTR;\n      } else {\n        // don't return any posix acl attribute\n        if ((key == s_acl_a) || (key == s_acl_d)) {\n          rc = ENOATTR;\n        }\n\n#ifdef __APPLE__\n        else\n\n          // don't return any finder attribute\n          if (key.substr(0, s_apple.length()) == s_apple) {\n            rc = ENOATTR;\n          }\n\n#endif\n      }\n\n      if (key == \"eos.name\") {\n        value = Instance().Config().name;\n      } else if (key == \"eos.hostport\") {\n        value = Instance().Config().hostport;\n      } else if (key == \"eos.stacktrace\") {\n        value = getStacktrace();\n      } else if (key == \"eos.mgmurl\") {\n        std::string mgmurl = \"root://\";\n        mgmurl += Instance().Config().hostport;\n        value = mgmurl;\n      } else if (key == \"eos.reconnect\") {\n        Logbook logbook(true);\n        const struct fuse_ctx* ctx = fuse_req_ctx(req);\n        ProcessSnapshot snapshot = fusexrdlogin::processCache->retrieve(ctx->pid,\n                                   ctx->uid, ctx->gid, true, logbook);\n        value = logbook.toString();\n\n        if (size == 0) {\n          // just make sure, the string does not get longer with the next call\n          value += value;\n        }\n      } else if (key == \"eos.reconnectparent\") {\n        const struct fuse_ctx* ctx = fuse_req_ctx(req);\n        ProcessSnapshot snapshot = fusexrdlogin::processCache->retrieve(ctx->pid,\n                                   ctx->uid, ctx->gid, false);\n        pid_t ppid = snapshot->getProcessInfo().getParentId();\n        Logbook logbook(true);\n        ProcessSnapshot snapshotParent =\n          fusexrdlogin::processCache->retrieve(ppid,\n                                               ctx->uid, ctx->gid, true, logbook);\n        value = logbook.toString();\n\n        if (size == 0) {\n          // just make sure, the string does not get longer with the next call\n          value += value;\n        }\n      } else if (key == \"eos.identity\") {\n        const struct fuse_ctx* ctx = fuse_req_ctx(req);\n        ProcessSnapshot snapshot = fusexrdlogin::processCache->retrieve(ctx->pid,\n                                   ctx->uid, ctx->gid, false);\n\n        if (snapshot) {\n          value = snapshot->getBoundIdentity()->describe();\n        }\n      } else if (key == \"eos.identityparent\") {\n        const struct fuse_ctx* ctx = fuse_req_ctx(req);\n        ProcessSnapshot snapshot = fusexrdlogin::processCache->retrieve(ctx->pid,\n                                   ctx->uid, ctx->gid, false);\n        pid_t ppid = snapshot->getProcessInfo().getParentId();\n        ProcessSnapshot snapshotParent =\n          fusexrdlogin::processCache->retrieve(\n            ppid, ctx->uid, ctx->gid, false);\n\n        if (snapshotParent) {\n          value = snapshotParent->getBoundIdentity()->describe();\n        }\n      } else if (!rc) {\n        md = Instance().mds.get(req, ino);\n        XrdSysMutexHelper mLock(md->Locker());\n\n        if (!(*md)()->id() || md->deleted()) {\n          rc = md->deleted() ? ENOENT : (*md)()->err();\n        } else {\n          auto map = (*md)()->attr();\n\n          if (key.substr(0, 8) == \"eos.sys.\") {\n            key.erase(0, 4);\n          }\n\n          if (key.substr(0, 4) == \"eos.\") {\n            if (key == \"eos.md_ino\") {\n              std::string md_ino;\n              value = eos::common::StringConversion::GetSizeString(md_ino,\n                      (unsigned long long)(*md)()->md_ino());\n            }\n\n            if (key == \"eos.btime\") {\n              char btime[256];\n              snprintf(btime, sizeof(btime), \"%lu.%lu\", (*md)()->btime(),\n                       (*md)()->btime_ns());\n              value = btime;\n            }\n\n            if (key == \"eos.ttime\") {\n              char ttime[256];\n\n              if (S_ISDIR((*md)()->mode())) {\n                snprintf(ttime, sizeof(ttime), \"%lu.%lu\", (*md)()->ttime(),\n                         (*md)()->ttime_ns());\n              } else {\n                snprintf(ttime, sizeof(ttime), \"%lu.%lu\", (*md)()->mtime(),\n                         (*md)()->mtime_ns());\n              }\n\n              value = ttime;\n            }\n\n            if (key == \"eos.tsize\") {\n              char tsize[256];\n              snprintf(tsize, sizeof(tsize), \"%lu\", (*md)()->size());\n              value = tsize;\n            }\n\n            if (key == \"eos.fcount\") {\n              uint64_t nfiles = 0;\n              double lifetime = 0;\n              mLock.UnLock();\n              rc = listdir(req, ino, md, lifetime);\n\n              if (!rc) {\n                for (auto it = md->local_children().begin(); it != md->local_children().end();\n                     ++it) {\n                  fuse_ino_t cino = it->second;\n                  metad::shared_md cmd = Instance().mds.get(req, cino, \"\", 0, 0, 0, true);\n                  XrdSysMutexHelper mLock(cmd->Locker());\n\n                  if ((*cmd)()->id()) {\n                    if (S_ISREG((*cmd)()->mode())) {\n                      nfiles++;\n                    }\n                  }\n                }\n              }\n\n              char fsize[256];\n              snprintf(fsize, sizeof(fsize), \"%lu\", nfiles);\n              value = fsize;\n            }\n\n            if (key == \"eos.dcount\") {\n              uint64_t ndirs = 0;\n              double lifetime = 0;\n              mLock.UnLock();\n              rc = listdir(req, ino, md, lifetime);\n\n              if (!rc) {\n                for (auto it = md->local_children().begin(); it != md->local_children().end();\n                     ++it) {\n                  fuse_ino_t cino = it->second;\n                  metad::shared_md cmd = Instance().mds.get(req, cino, \"\", 0, 0, 0, true);\n                  XrdSysMutexHelper mLock(cmd->Locker());\n\n                  if ((*cmd)()->id()) {\n                    if (S_ISDIR((*cmd)()->mode())) {\n                      ndirs++;\n                    }\n                  }\n                }\n              }\n\n              char dsize[256];\n              snprintf(dsize, sizeof(dsize), \"%lu\", ndirs);\n              value = dsize;\n            }\n\n            if (key == \"eos.dsize\") {\n              uint64_t sumsize = 0;\n              double lifetime = 0;\n              mLock.UnLock();\n              rc = listdir(req, ino, md, lifetime);\n\n              if (!rc) {\n                for (auto it = md->local_children().begin(); it != md->local_children().end();\n                     ++it) {\n                  fuse_ino_t cino = it->second;\n                  metad::shared_md cmd = Instance().mds.get(req, cino, \"\", 0, 0, 0, true);\n                  XrdSysMutexHelper mLock(cmd->Locker());\n\n                  if ((*cmd)()->id()) {\n                    if (S_ISREG((*cmd)()->mode())) {\n                      sumsize += (*cmd)()->size();\n                    }\n                  }\n                }\n              }\n\n              char dsize[256];\n              snprintf(dsize, sizeof(dsize), \"%lu\", sumsize);\n              value = dsize;\n            }\n\n            if (key == \"eos.checksum\") {\n              rc = Instance().mdbackend.getChecksum(req, (*md)()->md_ino(), value);\n            }\n\n            if (key == \"eos.stats\") {\n              value = Instance().statsout.get();\n            }\n\n            if (key == \"eos.url.xroot\") {\n              value = \"root://\";\n              value += Instance().Config().hostport;\n              value += \"/\";\n              value += (*md)()->fullpath().c_str();\n\t      if (S_ISDIR((*md)()->mode()) && ((*md)()->fullpath().back()!='/')) {\n\t\tvalue += \"/\";\n\t      }\n\t    }\n\n            if (key == \"eos.quota\") {\n              pcap = Instance().caps.acquire(req, ino,\n                                             R_OK);\n\n              if ((*pcap)()->errc()) {\n                rc = (*pcap)()->errc();\n              } else {\n                cap::shared_quota q = Instance().caps.quota(pcap);\n                XrdSysMutexHelper qLock(q->Locker());\n                char qline[4096];\n                snprintf(qline, sizeof(qline),\n                         \"%-32s %8s %8s %20s %20s %20s %32s %s\\n\"\n                         \"%-32s %8s %8s %20s %20s %20s %32s %s:%s:%s\\n\",\n                         \"instance\", \"uid\", \"gid\", \"vol-avail\", \"ino-avail\", \"max-fsize\", \"endpoint\",\n                         \"writer:lvol:lino\",\n                         Instance().Config().name.c_str(),\n                         std::to_string((*pcap)()->uid()).c_str(),\n                         std::to_string((*pcap)()->gid()).c_str(),\n                         std::to_string((*q)()->volume_quota() - q->get_local_volume()).c_str(),\n                         std::to_string((*q)()->inode_quota() - q->get_local_inode()).c_str(),\n                         std::to_string((*pcap)()->max_file_size()).c_str(),\n                         Instance().Config().hostport.c_str(),\n                         std::to_string(q->writer()).c_str(),\n                         std::to_string(q->get_local_volume()).c_str(),\n                         std::to_string(q->get_local_inode()).c_str()\n                        );\n                value = qline;\n              }\n            }\n          } else {\n            if (S_ISDIR((*md)()->mode())) {\n              // retrieve the appropriate cap of this inode\n              pcap = Instance().caps.acquire(req, ino,\n                                             R_OK);\n            } else {\n              // retrieve the appropriate cap of the parent inode\n              pcap = Instance().caps.acquire(req, (*md)()->pid(), R_OK);\n            }\n\n            if ((*pcap)()->errc()) {\n              rc = (*pcap)()->errc();\n            } else {\n#ifdef HAVE_RICHACL\n\n              if (key == s_racl) {\n                struct richacl* a = NULL;\n\n                if (map.count(\"user.acl\") > 0 && map[\"user.acl\"].length() > 0) {\n                  const char* eosacl = map[\"user.acl\"].c_str();\n                  eos_static_debug(\"eosacl '%s'\", eosacl);\n\n                  if (!S_ISDIR((*md)()->mode()) || map.count(\"sys.eval.useracl\") > 0) {\n                    a = eos2racl(eosacl, md);\n                  }\n                }\n\n                metad::shared_md pmd = Instance().mds.getlocal(req, (*md)()->pid());\n\n                if (pmd != NULL) {\n                  /* decode parent ACL for merge */\n                  auto pmap = (*pmd)()->attr();\n                  struct richacl* pa = NULL;\n\n                  if (pmap.count(\"sys.eval.useracl\") > 0 && pmap.count(\"user.acl\") > 0) {\n                    const char* peosacl = pmap[\"user.acl\"].c_str();\n                    pa = eos2racl(peosacl, pmd);\n                  }\n\n                  if (pa == NULL) {\n                    pa = richacl_from_mode((*md)()->mode()); /* Always returns an ACL */\n                  }\n\n                  a = richacl_merge_parent(a, md, pa, pmd);\n                  richacl_free(pa);\n\n                  if (a == NULL) {\n                    rc = ENOMEM;  /* a has been freed */\n                  }\n\n                  if (rc == 0) {\n                    size_t sz = richacl_xattr_size(a);\n                    value.assign(sz, '\\0'); /* allocate and clear result buffer */\n                    richacl_to_xattr(a, (void*) value.c_str());\n                    char* a_t = richacl_to_text(a, 0);\n                    eos_static_debug(\"eos2racl returned raw size %d, decoded: %s\", sz, a_t);\n                    free(a_t);\n                    richacl_free(a);\n                  }\n                } else { /* unsupported EOS Acl */\n                  size_t xx = 0;\n                  value.assign((char*) &xx, sizeof(xx)); /* Invalid xattr */\n                }\n\n                if (EOS_LOGS_DEBUG) {\n                  eos_static_debug(\"racl getxattr %d\", value.length());\n                }\n              } else\n#endif /*HAVE_RICHACL*/\n                if ((key == s_obfuscate_key) || (key == s_encrypted_fp) ||\n                    !map.count(key)) {\n                  rc = ENOATTR;\n                } else {\n                  value = map[key];\n                }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  if (!rc && (size != 0)) {\n    if (value.size() > size) {\n      rc = ERANGE;\n    }\n  }\n\n  if (rc) {\n    fuse_reply_err(req, rc);\n  } else {\n    if (size == 0) {\n      fuse_reply_xattr(req, value.size());\n    } else {\n      fuse_reply_buf(req, value.c_str(), value.size());\n    }\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, ino, 0, rc, xattr_name).c_str());\n}\n\n#ifdef __APPLE__\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::setxattr(fuse_req_t req, fuse_ino_t ino, const char* xattr_name,\n                  const char* xattr_value, size_t size, int flags,\n                  uint32_t position)\n/* -------------------------------------------------------------------------- */\n#else\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::setxattr(fuse_req_t req, fuse_ino_t ino, const char* xattr_name,\n                  const char* xattr_value, size_t size, int flags)\n/* -------------------------------------------------------------------------- */\n#endif\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  std::string key = xattr_name;\n  eos_static_debug(key.c_str()); /* key in case xattr_name == NULL */\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  Track::Monitor mon(\"setxattr\", \"fs\", Instance().Tracker(), req, ino, true);\n  int rc = 0;\n  fuse_id id(req);\n  cap::shared_cap pcap;\n  std::string value;\n  bool local_setxattr = false;\n  value.assign(xattr_value, size);\n#ifdef notdefHAVE_RICHACL\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"value: '%s' l=%d\", escape(value).c_str(), size);\n  }\n\n#endif /*HAVE_RICHACL*/\n  // the root user has a bypass to be able to change th fuse configuration in\n  // realtime\n  {\n    static std::string s_debug = \"system.eos.debug\";\n    static std::string s_dropcap = \"system.eos.dropcap\";\n    static std::string s_dropallcap = \"system.eos.dropallcap\";\n    static std::string s_resetstat = \"system.eos.resetstat\";\n    static std::string s_resetlru = \"system.eos.resetlru\";\n    static std::string s_log = \"system.eos.log\";\n    static std::string s_fuzz = \"system.eos.fuzz\";\n\n    if (key.substr(0, s_fuzz.length()) == s_fuzz) {\n      local_setxattr = true;\n\n      // only root can do this configuration changes\n      if (fuse_req_ctx(req)->uid == 0) {\n        rc = EINVAL;\n\n        if (value == \"all\") {\n          // set all scalers to fail all the time\n          XrdCl::Fuzzing::Configure(1,\n                                    1,\n                                    1,\n                                    1,\n                                    1);\n          rc = 0;\n        }\n\n        if (value == \"config\") {\n          // set all scalers as referenced in the startup configuration\n          XrdCl::Fuzzing::Configure(Instance().Config().fuzzing.open_async_submit,\n                                    Instance().Config().fuzzing.open_async_return,\n                                    Instance().Config().fuzzing.open_async_submit_fatal,\n                                    Instance().Config().fuzzing.open_async_return_fatal,\n                                    Instance().Config().fuzzing.read_async_return);\n          rc = 0;\n        }\n\n        if (value == \"none\") {\n          // disable all fuzzing\n          XrdCl::Fuzzing::Configure(0,\n                                    0,\n                                    0,\n                                    0,\n                                    0);\n          rc = 0;\n        }\n      } else {\n        rc = EPERM;\n      }\n    }\n\n    if (key.substr(0, s_debug.length()) == s_debug) {\n      local_setxattr = true;\n      // only root can do this configuration changes\n\n      if (fuse_req_ctx(req)->uid == 0) {\n        rc = EINVAL;\n\n        if (value == \"crit\") {\n          eos::common::Logging::GetInstance().SetLogPriority(LOG_CRIT);\n          Instance().SetTrace(false);\n          rc = 0;\n        }\n\n        if (value == \"warn\") {\n          eos::common::Logging::GetInstance().SetLogPriority(LOG_WARNING);\n          Instance().SetTrace(false);\n          rc = 0;\n        }\n\n        if (value == \"error\") {\n          eos::common::Logging::GetInstance().SetLogPriority(LOG_ERR);\n          Instance().SetTrace(false);\n          rc = 0;\n        }\n\n        if (value == \"notice\") {\n          eos::common::Logging::GetInstance().SetLogPriority(LOG_NOTICE);\n          Instance().SetTrace(false);\n          rc = 0;\n        }\n\n        if (value == \"info\") {\n          eos::common::Logging::GetInstance().SetLogPriority(LOG_INFO);\n          Instance().SetTrace(false);\n          rc = 0;\n        }\n\n        if (value == \"debug\") {\n          eos::common::Logging::GetInstance().SetLogPriority(LOG_DEBUG);\n          Instance().SetTrace(false);\n          rc = 0;\n        }\n\n        if (value == \"trace\") {\n          Instance().SetTrace(true);\n          rc = 0;\n        }\n      } else {\n        rc = EPERM;\n      }\n    }\n\n    if (key.substr(0, s_dropcap.length()) == s_dropcap) {\n      local_setxattr = true;\n      cap::shared_cap pcap = Instance().caps.get(req, ino);\n\n      if ((*pcap)()->id()) {\n        Instance().caps.forget(pcap->capid(req, ino));\n      }\n    }\n\n    if (key.substr(0, s_dropallcap.length()) == s_dropallcap) {\n      local_setxattr = true;\n\n      if (fuse_req_ctx(req)->uid == 0) {\n        Instance().caps.reset();\n      } else {\n        rc = EPERM;\n      }\n    }\n\n    if (fuse_req_ctx(req)->uid == 0) {\n      if (key.substr(0, s_resetlru.length()) == s_resetlru) {\n        local_setxattr = true;\n        Instance().mds.lrureset();\n        fuse_reply_err(req, 0);\n        // avoid to show this call in stats again\n        return ;\n      }\n    }\n\n    if (key.substr(0, s_log.length()) == s_log) {\n      local_setxattr = true;\n\n      if (value == \"public\") {\n        ::chmod(Instance().Config().logfilepath.c_str(),\n                S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);\n      }\n\n      if (value == \"private\") {\n        ::chmod(Instance().Config().logfilepath.c_str(), S_IRUSR | S_IWUSR);\n      }\n    }\n  }\n\n  if (!local_setxattr) {\n    metad::shared_md md;\n    md = Instance().mds.get(req, ino);\n    XrdSysMutexHelper mLock(md->Locker());\n\n    if (!(*md)()->id() || md->deleted()) {\n      rc = md->deleted() ? ENOENT : (*md)()->err();\n    } else {\n      // retrieve the appropriate cap\n      if (S_ISDIR((*md)()->mode())) {\n        pcap = Instance().caps.acquire(req, ino,\n                                       SA_OK);\n      } else {\n        pcap = Instance().caps.acquire(req, (*md)()->pid(),\n                                       SA_OK);\n      }\n\n      if ((*pcap)()->errc()) {\n        rc = (*pcap)()->errc();\n      } else {\n        static std::string s_sec = \"security.\";\n        static std::string s_acl = \"system.posix_acl_access\";\n        static std::string s_apple = \"com.apple\";\n        static std::string s_racl = \"system.richacl\";\n\n        if (key.substr(0, 4) == \"eos.\") {\n          // eos attributes are silently ignored\n          rc = 0;\n        }  else\n\n          // ignore silently any security attribute\n          if (key.substr(0, s_sec.length()) == s_sec) {\n            rc = 0;\n          } else\n\n            // return operation not supported\n            if (key == s_acl) {\n              rc = EOPNOTSUPP;\n            }\n\n#ifdef __APPLE__\n            else\n\n              // ignore silently any finder attribute\n              if (key.substr(0, s_apple.length()) == s_apple) {\n                rc = 0;\n              }\n\n#endif\n              else if (key == s_racl) {\n#ifdef HAVE_RICHACL\n                struct richacl* a = richacl_from_xattr(xattr_value, size);\n                richacl_compute_max_masks(a);\n\n                if (EOS_LOGS_DEBUG) {\n                  char* a_t = richacl_to_text(a, RICHACL_TEXT_SHOW_MASKS);\n                  eos_static_debug(\"acl a_t '%s' \", a_t);\n                  free(a_t);\n                }\n\n                int new_mode = richacl_masks_to_mode(a);\n                char eosAcl[512];\n                racl2eos(a, eosAcl, sizeof(eosAcl), md);\n                eos_static_debug(\"acl eosacl '%s'\", eosAcl);\n                auto map = (*md)()->mutable_attr();\n                rc = 0; /* assume green light */\n\n                // assert user acls are enabled\n                if (!map->count(\"sys.eval.useracl\")) {\n                  if (S_ISDIR((*md)()->mode())) {\n                    rc = EPERM;\n                  } else {\n                    metad::shared_md pmd = Instance().mds.getlocal(req, (*md)()->pid());\n                    auto pmap = (*pmd)()->mutable_attr();\n\n                    if (pmap->count(\"sys.eval.useracl\") == 0) {\n                      rc = EPERM;\n                    }\n                  }\n                }\n\n                if (rc == 0) {\n                  new_mode |= ((*md)()->mode() & ~0777);\n                  eos_static_debug(\"set new mode %#o\", new_mode);\n                  (*md)()->set_mode(new_mode);\n                  (*map)[\"user.acl\"] = std::string(eosAcl);\n                  Instance().mds.update(req, md, (*pcap)()->authid());\n                  pcap->invalidate();\n\n                  if (Instance().mds.has_flush(ino)) {\n                    Instance().mds.wait_flush(req, md); // wait for upstream flush\n                  }\n                }\n\n#else /*HAVE_RICHACL*/\n                rc = EINVAL;                          // fail loudly if not supported\n#endif /*HAVE_RICHACL*/\n              } else {\n                if ((key == s_obfuscate_key) || (key == s_encrypted_fp)) {\n                  eos_static_err(\"msg=\\\"not allowed to set obfuscate or \"\n                                 \"encrypted xattrs\\\" ino=%llu\", ino);\n                  rc = EPERM;\n                } else {\n                  auto map = (*md)()->mutable_attr();\n                  bool exists = false;\n\n                  if ((*map).count(key)) {\n                    exists = true;\n                  }\n\n                  if (exists && (flags == XATTR_CREATE)) {\n                    rc = EEXIST;\n                  } else if (!exists && (flags == XATTR_REPLACE)) {\n                    rc = ENOATTR;\n                  } else {\n                    (*map)[key] = value;\n                    Instance().mds.update(req, md, (*pcap)()->authid());\n                  }\n                }\n              }\n      }\n    }\n  }\n\n  fuse_reply_err(req, rc);\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, ino, 0, rc, xattr_name).c_str());\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  Track::Monitor mon(\"listxattr\", \"fs\", Instance().Tracker(), req, ino);\n  int rc = 0;\n  fuse_id id(req);\n  cap::shared_cap pcap;\n  std::string attrlist;\n  size_t attrlistsize = 0;\n  metad::shared_md md;\n  md = Instance().mds.get(req, ino);\n\n  // retrieve the appropriate cap\n  if (S_ISDIR((*md)()->mode())) {\n    pcap = Instance().caps.acquire(req, ino,\n                                   X_OK, true);\n  } else {\n    pcap = Instance().caps.acquire(req, (*md)()->pid(),\n                                   X_OK, true);\n  }\n\n  if ((*pcap)()->errc()) {\n    rc = (*pcap)()->errc();\n  } else {\n    XrdSysMutexHelper mLock(md->Locker());\n\n    if (!(*md)()->id() || md->deleted()) {\n      rc = md->deleted() ? ENOENT : (*md)()->err();\n    } else {\n      auto map = (*md)()->attr();\n      attrlist = \"\";\n\n      for (auto it = map.begin(); it != map.end(); ++it) {\n        // Never display the obfuscate or the encrypted fingerprint\n        if ((it->first == s_obfuscate_key) ||\n            (it->first == s_encrypted_fp)) {\n          continue;\n        }\n\n        if (it->first.substr(0, 4) == \"sys.\") {\n          if (Instance().Config().options.no_eos_xattr_listing) {\n            continue;\n          }\n\n          attrlist += \"eos.\";\n          attrlistsize += strlen(\"eos.\");\n        }\n\n        attrlistsize += it->first.length() + 1;\n        attrlist += it->first;\n        attrlist += '\\0';\n      }\n\n      if (!Instance().Config().options.no_eos_xattr_listing) {\n        // add 'eos.btime'\n        attrlist += \"eos.btime\";\n        attrlist += '\\0';\n        attrlistsize += strlen(\"eos.btime\") + 1;\n        // add 'eos.ttime'\n        attrlist += \"eos.ttime\";\n        attrlist += '\\0';\n        attrlistsize += strlen(\"eos.ttime\") + 1;\n        // add 'eos.tsize'\n        attrlist += \"eos.tsize\";\n        attrlist += '\\0';\n        attrlistsize += strlen(\"eos.tsize\") + 1;\n        // add \"eos.url.xroot\";\n        attrlist += \"eos.url.xroot\";\n        attrlist += '\\0';\n        attrlistsize += strlen(\"eos.url.xroot\") + 1;\n      }\n\n      if (!Instance().Config().options.no_eos_xattr_listing) {\n        // for files add 'eos.checksum'\n        if (S_ISREG((*md)()->mode())) {\n          attrlist += \"eos.checksum\";\n          attrlist += '\\0';\n          attrlistsize += strlen(\"eos.checksum\") + 1;\n          attrlist += \"eos.md_ino\";\n          attrlist += '\\0';\n          attrlistsize += strlen(\"eos.md_ino\") + 1;\n        }\n      }\n\n      if (size != 0) {\n        if (attrlistsize > size) {\n          rc = ERANGE;\n        }\n      }\n    }\n  }\n\n  if (rc) {\n    fuse_reply_err(req, rc);\n  } else {\n    if (size == 0) {\n      fuse_reply_xattr(req, attrlistsize);\n    } else {\n      fuse_reply_buf(req, attrlist.c_str(), attrlist.length());\n    }\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, ino, 0, rc).c_str());\n}\n\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::removexattr(fuse_req_t req, fuse_ino_t ino, const char* xattr_name)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  Track::Monitor mon(\"removexattr\", \"fs\", Instance().Tracker(), req, ino);\n  int rc = 0;\n  fuse_id id(req);\n  cap::shared_cap pcap;\n  metad::shared_md md;\n  md = Instance().mds.get(req, ino);\n\n  // retrieve the appropriate cap\n  if (S_ISDIR((*md)()->mode())) {\n    pcap = Instance().caps.acquire(req, ino,\n                                   SA_OK, true);\n  } else {\n    pcap = Instance().caps.acquire(req, (*md)()->pid(),\n                                   SA_OK, true);\n  }\n\n  if ((*pcap)()->errc()) {\n    rc = (*pcap)()->errc();\n  } else {\n    XrdSysMutexHelper mLock(md->Locker());\n\n    if (!(*md)()->id() || md->deleted()) {\n      rc = md->deleted() ? ENOENT : (*md)()->err();\n    } else {\n      std::string key = xattr_name;\n      static std::string s_sec = \"security.\";\n      static std::string s_acl = \"system.posix_acl\";\n      static std::string s_apple = \"com.apple\";\n      static std::string s_racl = \"system.richacl\";\n\n      if (key.substr(0, 4) == \"eos.\") {\n        // eos attributes are silently ignored\n        rc = 0;\n      }  else if (key.substr(0, s_sec.length()) == s_sec) {\n        // ignore silently any security attribute\n        rc = 0;\n      } else\n\n        // ignore silently any posix acl attribute\n        if (key == s_acl) {\n          rc = 0;\n        }\n\n#ifdef __APPLE__\n        else\n\n          // ignore silently any finder attribute\n          if (key.substr(0, s_apple.length()) == s_apple) {\n            rc = 0;\n          }\n\n#endif\n          else {\n#ifdef HAVE_RICHACL\n\n            if (key == s_racl) {\n              key = \"user.acl\";\n            }\n\n#endif\n            auto map = (*md)()->mutable_attr();\n            bool exists = false;\n\n            if ((*map).count(key)) {\n              exists = true;\n            }\n\n            if (!exists) {\n              rc = ENOATTR;\n            } else {\n              (*map).erase(key);\n              Instance().mds.update(req, md, (*pcap)()->authid());\n            }\n          }\n    }\n  }\n\n  fuse_reply_err(req, rc);\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, ino, 0, rc).c_str());\n}\n\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::readlink(fuse_req_t req, fuse_ino_t ino)\n/* -------------------------------------------------------------------------- */\n/*\n EACCES Search permission is denied for a component of the path prefix.  (See also path_resolution(7).)\n\n EFAULT buf extends outside the process’s allocated address space.\n\n EINVAL bufsiz is not positive.\n\n EINVAL The named file is not a symbolic link.\n\n EIO    An I/O error occurred while reading from the file system.\n\n ELOOP  Too many symbolic links were encountered in translating the pathname.\n\n ENAMETOOLONG\n  A pathname, or a component of a pathname, was too long.\n\n ENOENT The named file does not exist.\n\n ENOMEM Insufficient kernel memory was available.\n\n ENOTDIR\n  A component of the path prefix is not a directory.\n */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  Track::Monitor mon(\"readlink\", \"fs\", Instance().Tracker(), req, ino);\n  int rc = 0;\n  std::string target;\n  fuse_id id(req);\n  cap::shared_cap pcap;\n  metad::shared_md md;\n  md = Instance().mds.get(req, ino);\n\n  if (!(*md)()->id() || md->deleted()) {\n    rc = md->deleted() ? ENOENT : (*md)()->err();\n\n    if (rc == EPERM) {\n      rc = EACCES;\n    }\n  } else {\n    pcap = Instance().caps.acquire(req, (*md)()->pid(),\n                                   Instance().Config().options.x_ok, true);\n\n    if ((*pcap)()->errc()) {\n      rc = (*pcap)()->errc();\n    } else {\n      XrdSysMutexHelper mLock(md->Locker());\n\n      if (!(*md)()->id() || md->deleted()) {\n        rc = ENOENT;\n      } else {\n        if (!((*md)()->mode() & S_IFLNK)) {\n          // no a link\n          rc = EINVAL;\n        } else {\n          target = (*md)()->target();\n        }\n      }\n    }\n\n    if (Instance().Config().options.protect_directory_symlink_loops) {\n      std::string localpath = Instance().Prefix(Instance().mds.calculateLocalPath(\n                                md));\n\n      if (target.front() == '/') {\n        if (localpath.substr(0, target.size()) == target) {\n          target = \"/#_invalidated_link\";\n        }\n      } else {\n        std::string targetpath = localpath;\n        targetpath += \"/\";\n        targetpath += target;\n        eos::common::Path tPath(targetpath);\n        targetpath = tPath.GetPath();\n\n        if (localpath.substr(0, targetpath.size()) == targetpath) {\n          target = \"#_invalidated_link\";\n        }\n      }\n    }\n\n    if (Instance().Config().options.submounts) {\n      if (target.substr(0, 6) == \"mount:\") {\n        std::string env;\n\n        // if not shared, set the caller credentials\n        if (0) {\n          env = fusexrdlogin::environment(req);\n        }\n\n        std::string localpath = Instance().Prefix(Instance().mds.calculateLocalPath(\n                                  md));\n        rc = Instance().Mounter().mount(target, localpath, env);\n\n        if (rc < 0) {\n          rc = EINVAL;\n        }\n      }\n\n      if (target.substr(0, 11) == \"squashfuse:\") {\n        std::string env;\n        //    env = fusexrdlogin::environment(req);\n        std::string localpath = Instance().Prefix(Instance().mds.calculateLocalPath(\n                                  md));\n        rc = Instance().Mounter().squashfuse(target, localpath, env);\n\n        if (rc < 0) {\n          rc = EINVAL;\n        }\n      }\n    }\n  }\n\n  if (!rc) {\n    fuse_reply_readlink(req, target.c_str());\n  } else {\n    fuse_reply_err(req, rc);\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, ino, 0, rc).c_str());\n}\n\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::symlink(fuse_req_t req, const char* link, fuse_ino_t parent,\n                 const char* name)\n/* -------------------------------------------------------------------------- */\n/*\n EACCES Write access to the directory containing newpath is denied, or one of the directories in the path\n  prefix of newpath did not allow search permission.  (See also path_resolution(7).)\n\n EEXIST newpath already exists.\n\n EFAULT oldpath or newpath points outside your accessible address space.\n\n EIO    An I/O error occurred.\n\n ELOOP  Too many symbolic links were encountered in resolving newpath.\n\n ENAMETOOLONG\n  oldpath or newpath was too long.\n\n ENOENT A directory component in newpath does not exist or is a dangling symbolic link, or oldpath is the\n  empty string.\n\n ENOMEM Insufficient kernel memory was available.\n\n ENOSPC The device containing the file has no room for the new directory entry.\n\n ENOTDIR\n  A component used as a directory in newpath is not, in fact, a directory.\n\n EPERM  The file system containing newpath does not support the creation of symbolic links.\n\n EROFS  newpath is on a read-only file system.\n\n */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  Track::Monitor mon(\"symlink\", \"fs\", Instance().Tracker(), req, parent, true);\n  int rc = 0;\n  fuse_id id(req);\n  struct fuse_entry_param e;\n  // do a parent check\n  cap::shared_cap pcap = Instance().caps.acquire(req, parent,\n                         S_IFDIR | W_OK | X_OK, true);\n\n  if ((*pcap)()->errc()) {\n    rc = (*pcap)()->errc();\n  } else {\n    metad::shared_md md;\n    metad::shared_md pmd;\n    md = Instance().mds.lookup(req, parent, name);\n    pmd = Instance().mds.get(req, parent, (*pcap)()->authid());\n    {\n      uint64_t del_ino = 0;\n      // logic avoiding a create/unlink/create sync/async race\n      {\n        XrdSysMutexHelper pLock(pmd->Locker());\n        auto it = pmd->get_todelete().find(\n                    eos::common::StringConversion::EncodeInvalidUTF8(name));\n\n        if ((it != pmd->get_todelete().end()) && it->second) {\n          del_ino = it->second;\n        }\n      }\n\n      if (del_ino) {\n        Instance().mds.wait_upstream(req, del_ino);\n      }\n    }\n    XrdSysMutexHelper mLock(md->Locker());\n\n    for (int n = 0; md->deleted() && n < 3; n++) {\n      // we need to wait that this entry is really gone\n      Instance().mds.wait_flush(req, md);\n      mLock.UnLock();\n      md = Instance().mds.lookup(req, parent, name);\n      mLock.Lock(&md->Locker());\n    }\n\n    if ((*md)()->id() || md->deleted()) {\n      rc = EEXIST;\n    } else {\n      (*md)()->set_id(0);\n      (*md)()->set_md_ino(0);\n      (*md)()->set_nlink(1);\n      (*md)()->set_mode(S_IRWXU | S_IRWXG | S_IRWXO | S_IFLNK);\n      (*md)()->set_target(link);\n      (*md)()->set_err(0);\n      struct timespec ts;\n      eos::common::Timing::GetTimeSpec(ts);\n      (*md)()->set_name(name);\n      (*md)()->set_atime(ts.tv_sec);\n      (*md)()->set_atime_ns(ts.tv_nsec);\n      (*md)()->set_mtime(ts.tv_sec);\n      (*md)()->set_mtime_ns(ts.tv_nsec);\n      (*md)()->set_ctime(ts.tv_sec);\n      (*md)()->set_ctime_ns(ts.tv_nsec);\n      (*md)()->set_btime(ts.tv_sec);\n      (*md)()->set_btime_ns(ts.tv_nsec);\n      (*md)()->set_uid((*pcap)()->uid());\n      (*md)()->set_gid((*pcap)()->gid());\n      md->lookup_inc();\n      (*md)()->set_type((*md)()->EXCL);\n      rc = Instance().mds.add_sync(req, pmd, md, (*pcap)()->authid());\n      (*md)()->set_type((*md)()->MD);\n\n      if (!rc) {\n        Instance().mds.insert(md, (*pcap)()->authid());\n        XrdSysMutexHelper pLock(pmd->Locker());\n        pmd->local_enoent().erase(name);\n      }\n\n      memset(&e, 0, sizeof(e));\n      md->convert(e, pcap->lifetime());\n    }\n  }\n\n  if (rc) {\n    fuse_reply_err(req, rc);\n  } else {\n    fuse_reply_entry(req, &e);\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, parent, 0, rc).c_str());\n}\n\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,\n              const char* newname)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"hlnk newname=%s ino=%#lx parent=%#lx\", newname, ino, parent);\n  }\n\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  Track::Monitor mon(\"link\", \"fs\", Instance().Tracker(), req, parent, true);\n  int rc = 0;\n  fuse_id id(req);\n  struct fuse_entry_param e;\n  // do a parent check\n  cap::shared_cap pcap = Instance().caps.acquire(req, parent,\n                         S_IFDIR | X_OK | W_OK, true);\n\n  if ((*pcap)()->errc()) {\n    rc = (*pcap)()->errc();\n  } else {\n    metad::shared_md md; /* the new name */\n    metad::shared_md pmd; /* the parent directory for the new name */\n    metad::shared_md tmd; /* the link target */\n    md = Instance().mds.lookup(req, parent, newname);\n    pmd = Instance().mds.get(req, parent, (*pcap)()->authid());\n    {\n      uint64_t del_ino = 0;\n      // logic avoiding a create/unlink/create sync/async race\n      {\n        XrdSysMutexHelper pLock(pmd->Locker());\n        auto it = pmd->get_todelete().find(\n                    eos::common::StringConversion::EncodeInvalidUTF8(newname));\n\n        if ((it != pmd->get_todelete().end()) && it->second) {\n          del_ino = it->second;\n        }\n      }\n\n      if (del_ino) {\n        Instance().mds.wait_upstream(req, del_ino);\n      }\n    }\n    XrdSysMutexHelper mLock(md->Locker());\n\n    if ((*md)()->id() && !md->deleted()) {\n      rc = EEXIST;\n    } else {\n      if (md->deleted()) {\n        // we need to wait that this entry is really gone\n        Instance().mds.wait_flush(req, md);\n      }\n\n      tmd = Instance().mds.get(req, ino, (*pcap)()->authid()); /* link target */\n\n      if ((*tmd)()->id() == 0 || tmd->deleted()) {\n        rc = ENOENT;\n      } else if ((*tmd)()->pid() != parent) {\n        rc = EXDEV; /* only same parent supported */\n      } else {\n        XrdSysMutexHelper tmLock(tmd->Locker());\n\n        if (EOS_LOGS_DEBUG) {\n          eos_static_debug(\"hlnk tmd id=%ld %s\", (*tmd)()->id(),\n                           (*tmd)()->name().c_str());\n        }\n\n        (*md)()->set_id(0);\n        (*md)()->set_md_ino(0);\n        (*md)()->set_mode((*tmd)()->mode());\n        (*md)()->set_err(0);\n        struct timespec ts;\n        eos::common::Timing::GetTimeSpec(ts);\n        (*md)()->set_name(newname);\n        char tgtStr[64];\n        snprintf(tgtStr, sizeof(tgtStr), \"////hlnk%ld\",\n                 (*tmd)()->md_ino()); /* This triggers the hard link and specifies the target inode */\n        (*md)()->set_target(tgtStr);\n        (*md)()->set_atime((*tmd)()->atime());\n        (*md)()->set_atime_ns((*tmd)()->atime_ns());\n        (*md)()->set_mtime((*tmd)()->mtime());\n        (*md)()->set_mtime_ns((*tmd)()->mtime_ns());\n        (*md)()->set_ctime((*tmd)()->ctime());\n        (*md)()->set_ctime_ns((*tmd)()->ctime_ns());\n        (*md)()->set_btime((*tmd)()->btime());\n        (*md)()->set_btime_ns((*tmd)()->btime_ns());\n        (*md)()->set_uid((*tmd)()->uid());\n        (*md)()->set_gid((*tmd)()->gid());\n        (*md)()->set_size((*tmd)()->size());\n        // increase the link count of the target\n        auto attrMap = (*tmd)()->attr();\n        size_t nlink = 1;\n\n        if (attrMap.count(k_nlink)) {\n          nlink += std::stol(attrMap[k_nlink]);\n        }\n\n        auto wAttrMap = (*tmd)()->mutable_attr();\n        (*wAttrMap)[k_nlink] = std::to_string(nlink);\n        eos_static_debug(\"setting link count to %d\", nlink);\n        auto sAttrMap = (*md)()->mutable_attr();\n        (*sAttrMap)[k_mdino] = std::to_string((*tmd)()->md_ino());\n        (*tmd)()->set_nlink(nlink + 1);\n        tmLock.UnLock();\n        rc = Instance().mds.add_sync(req, pmd, md, (*pcap)()->authid());\n\n        if (!rc) {\n          Instance().mds.insert(md, (*pcap)()->authid());\n        }\n\n        (*md)()->set_target(\"\");\n        mLock.UnLock();\n\n        if (!rc) {\n          XrdSysMutexHelper tmLock(tmd->Locker());\n          memset(&e, 0, sizeof(e));\n          tmd->convert(e, pcap->lifetime());\n\n          if (EOS_LOGS_DEBUG) {\n            eos_static_debug(\"hlnk tmd %s %s\", (*tmd)()->name().c_str(),\n                             tmd->dump(e).c_str());\n          }\n\n          {\n            XrdSysMutexHelper pLock(pmd->Locker());\n            pmd->local_enoent().erase(newname);\n          }\n\n          // reply with the target entry\n          tmd->lookup_inc();\n          fuse_reply_entry(req, &e);\n        }\n      }\n    }\n  }\n\n  if (rc) {\n    fuse_reply_err(req, rc);\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n}\n\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::getlk(fuse_req_t req, fuse_ino_t ino,\n               struct fuse_file_info* fi, struct flock* lock)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  Track::Monitor mon(\"getlk\", \"fs\", Instance().Tracker(), req, ino);\n  fuse_id id(req);\n  int rc = 0;\n\n  if (!Instance().Config().options.global_locking) {\n    // use default local locking\n    rc = EOPNOTSUPP;\n  } else {\n    // use global locking\n    data::data_fh* io = (data::data_fh*) fi->fh;\n\n    if (io) {\n      rc = Instance().mds.getlk(req, io->mdctx(), lock);\n    } else {\n      rc = ENXIO;\n    }\n  }\n\n  eos_static_info(\"%u %u %u %lu %lu rc=%d\", lock->l_type,\n                  lock->l_whence,\n                  lock->l_pid,\n                  lock->l_start,\n                  lock->l_len,\n                  rc);\n\n  if (rc) {\n    fuse_reply_err(req, rc);\n  } else {\n    fuse_reply_lock(req, lock);\n  }\n\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, ino, 0, rc).c_str());\n}\n\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::setlk(fuse_req_t req, fuse_ino_t ino,\n               struct fuse_file_info* fi,\n               struct flock* lock, int sleep)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  fuse_id id(req);\n  int rc = 0;\n\n  if (!Instance().Config().options.global_locking) {\n    // use default local locking\n    rc = EOPNOTSUPP;\n  } else {\n    // use global locking\n    data::data_fh* io = (data::data_fh*) fi->fh;\n\n    if (io) {\n      size_t w_ms = 10;\n\n      do {\n        // we currently implement the polling lock on client side due to the\n        // thread-per-link model of XRootD\n        {\n          // take the exlusive lock only during the setlk call, then release\n          Track::Monitor mon(\"setlk\", \"fs\", Instance().Tracker(), req, ino, true);\n          rc = Instance().mds.setlk(req, io->mdctx(), lock, sleep);\n        }\n\n        if (rc && sleep) {\n          std::this_thread::sleep_for(std::chrono::milliseconds(w_ms));\n          // do exponential back-off with a hard limit at 1s\n          w_ms *= 2;\n\n          if (w_ms > 1000) {\n            w_ms = 1000;\n          }\n\n          continue;\n        }\n\n        break;\n      } while (rc);\n    } else {\n      rc = ENXIO;\n    }\n  }\n\n  fuse_reply_err(req, rc);\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f %s\", timing.RealTime(),\n                    dump(id, ino, 0, rc).c_str());\n}\n\n#ifdef FUSE_SUPPORTS_FLOCK\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::flock(fuse_req_t req, fuse_ino_t ino,\n               struct fuse_file_info* fi, int op)\n/* -------------------------------------------------------------------------- */\n{\n  eos::common::Timing timing(__func__);\n  COMMONTIMING(\"_start_\", &timing);\n  eos_static_debug(\"\");\n  ADD_FUSE_STAT(__func__, req);\n  EXEC_TIMING_BEGIN(__func__);\n  fuse_id id(req);\n  int rc = 0;\n\n  if (!Instance().Config().options.global_locking) {\n    // use default local locking\n    rc = EOPNOTSUPP;\n  } else {\n    if (op) {\n      // use global locking\n      data::data_fh* io = (data::data_fh*) fi->fh;\n\n      if (io) {\n        size_t w_ms = 10;\n        int sleep = 1;\n        struct flock lock;\n        lock.l_len = 0;\n        lock.l_start = 0;\n\n        if (op & LOCK_NB) {\n          sleep = 0;\n        }\n\n        if (op & LOCK_SH) {\n          lock.l_type = F_RDLCK;\n        } else if (op & LOCK_EX) {\n          lock.l_type = F_WRLCK;\n        } else if (op & LOCK_UN) {\n          lock.l_type = F_UNLCK;\n        } else if (op & LOCK_MAND) {\n          // mandatory locking used by samba\n          if (op & LOCK_READ) {\n            // 1st approximation\n            lock.l_type = F_RDLCK;\n          } else if (op & LOCK_WRITE) {\n            // 1st approximation\n            lock.l_type = F_RDLCK;\n          } else if (op & LOCK_RW) {\n            // 1st approximation\n            lock.l_type = F_RDLCK;\n          } else {\n            // 1st approximation\n            lock.l_type = F_WRLCK;\n          }\n        } else {\n          eos_static_notice(\"unsupported lock operation op:=%x\", op);\n          rc = EINVAL;\n        }\n\n        lock.l_pid = fuse_req_ctx(req)->pid;\n\n        if (!rc) {\n          do {\n            // we currently implement the polling lock on client side due to the\n            // thread-per-link model of XRootD\n            {\n              // take the exlusive lock only during the setlk call, then release.\n              // Otherwise we risk deadlock if we block other fuse queries coming\n              // from a user pid already holding an exclusive flock for this ino.\n              Track::Monitor mon(\"flock\", \"fs\", Instance().Tracker(), req, ino, true);\n              rc = Instance().mds.setlk(req, io->mdctx(), &lock, sleep);\n\n              if (!rc) {\n                io->set_flocked(true);\n              }\n            }\n\n            if (rc && sleep) {\n              std::this_thread::sleep_for(std::chrono::milliseconds(w_ms));\n              // do exponential back-off with a hard limit at 1s\n              w_ms *= 2;\n\n              if (w_ms > 1000) {\n                w_ms = 1000;\n              }\n\n              continue;\n            }\n\n            break;\n          } while (rc);\n        }\n      } else {\n        rc = ENXIO;\n      }\n    } else {\n      // consider a no-op\n      rc = 0;\n    }\n  }\n\n  fuse_reply_err(req, rc);\n  EXEC_TIMING_END(__func__);\n  COMMONTIMING(\"_stop_\", &timing);\n  eos_static_notice(\"t(ms)=%.03f op=%x %s\", timing.RealTime(), op,\n                    dump(id, ino, 0, rc).c_str());\n}\n#endif\n\n/* -------------------------------------------------------------------------- */\nvoid\nEosFuse::getHbStat(eos::fusex::statistics& hbs)\n/* -------------------------------------------------------------------------- */\n{\n  eos_static_debug(\"get statistics\");\n  eos::common::LinuxStat::linux_stat_t osstat;\n#ifndef __APPLE__\n  eos::common::LinuxMemConsumption::linux_mem_t mem;\n\n  if (!eos::common::LinuxMemConsumption::GetMemoryFootprint(mem)) {\n    eos_static_err(\"failed to get the MEM usage information\");\n  }\n\n  if (!eos::common::LinuxStat::GetStat(osstat)) {\n    eos_static_err(\"failed to get the OS usage information\");\n  }\n\n#endif\n  hbs.set_inodes(getMdStat().inodes());\n  hbs.set_inodes_todelete(getMdStat().inodes_deleted());\n  hbs.set_inodes_backlog(getMdStat().inodes_backlog());\n  hbs.set_inodes_ever(getMdStat().inodes_ever());\n  hbs.set_inodes_ever_deleted(getMdStat().inodes_deleted_ever());\n  hbs.set_threads(osstat.threads);\n  hbs.set_vsize_mb(osstat.vsize / 1000.0 / 1000.0);\n  hbs.set_rss_mb(osstat.rss / 1000.0 / 1000.0);\n  hbs.set_open_files(Instance().datas.size());\n  {\n    std::lock_guard<std::mutex> lock(meminfo.mutex());\n    hbs.set_free_ram_mb(meminfo.getref().freeram / 1000.0 / 1000.0);\n    hbs.set_total_ram_mb(meminfo.getref().totalram / 1000.0 / 1000.0);\n    hbs.set_load1(1.0 * meminfo.getref().loads[0] / (1 << SI_LOAD_SHIFT));\n  }\n  {\n    std::lock_guard g(getFuseStat().Mutex);\n    hbs.set_rbytes(getFuseStat().GetTotal(\"rbytes\"));\n    hbs.set_wbytes(getFuseStat().GetTotal(\"wbytes\"));\n    hbs.set_nio(getFuseStat().GetOps());\n    hbs.set_rd_rate_60_mb(getFuseStat().GetTotalAvg60(\"rbytes\") / 1000.0 / 1000.0);\n    hbs.set_wr_rate_60_mb(getFuseStat().GetTotalAvg60(\"wbytes\") / 1000.0 / 1000.0);\n    hbs.set_iops_60(getFuseStat().GetTotalAvg60(\":sum\"));\n  }\n  hbs.set_wr_buf_mb(XrdCl::Proxy::sWrBufferManager.inflight() / 1000.0 / 1000.0);\n  hbs.set_ra_buf_mb(XrdCl::Proxy::sRaBufferManager.inflight() / 1000.0 / 1000.0);\n  hbs.set_xoff(Instance().datas.get_xoff());\n  hbs.set_raxoff(XrdCl::Proxy::sRaBufferManager.xoff());\n  hbs.set_ranobuf(XrdCl::Proxy::sRaBufferManager.nobuf());\n  hbs.set_pid(getpid());\n  hbs.set_logfilesize(sizeLogFile());\n  hbs.set_wrnobuf(XrdCl::Proxy::sWrBufferManager.nobuf());\n  hbs.set_recovery_ok(Instance().aRecoveryOk); // computed by DumpStatistics\n  hbs.set_recovery_fail(Instance().aRecoveryFail); // computed by Dumpstatistics\n}\n\n/* -------------------------------------------------------------------------- */\nbool\nEosFuse::isRecursiveRm(fuse_req_t req, bool forced, bool notverbose)\n/* -------------------------------------------------------------------------- */\n{\n#ifndef __APPLE__\n  const struct fuse_ctx* ctx = fuse_req_ctx(req);\n  ProcessSnapshot snapshot = fusexrdlogin::processCache->retrieve(ctx->pid,\n                             ctx->uid, ctx->gid, false);\n\n  if (snapshot && snapshot->getProcessInfo().getRmInfo().isRm() &&\n      snapshot->getProcessInfo().getRmInfo().isRecursive()) {\n    bool result = true;\n\n    if (forced) {\n      // check if this is rm -rf style\n      result = snapshot->getProcessInfo().getRmInfo().isForce();\n    }\n\n    if (notverbose) {\n      result &= (!snapshot->getProcessInfo().getRmInfo().isVerbose());\n    }\n\n    return result;\n  }\n\n#endif\n  return false;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nEosFuse::TrackMgm(const std::string& lasturl)\n/* -------------------------------------------------------------------------- */\n{\n  static std::mutex lTrackMgmMutex;\n  std::lock_guard<std::mutex> sequenzerMutex(lTrackMgmMutex);\n  std::string currentmgm = lastMgmHostPort.get();\n  XrdCl::URL lastUrl(lasturl);\n  std::string newmgm = lastUrl.GetHostName();\n  std::string sport;\n  newmgm += \":\";\n  newmgm += eos::common::StringConversion::GetSizeString(sport,\n            (unsigned long long) lastUrl.GetPort());\n  eos_static_info(\"current-mgm:%s last-url:%s\", currentmgm.c_str(),\n                  newmgm.c_str());\n\n  if (currentmgm != newmgm) {\n    // for the first call currentmgm is an empty string, so we assume there is no failover needed\n    if (currentmgm.length()) {\n      // let's failover the ZMQ connection\n      size_t p_pos = config.mqtargethost.rfind(\":\");\n      std::string new_mqtargethost = config.mqtargethost;\n\n      if ((p_pos != std::string::npos) && (p_pos > 6)) {\n        new_mqtargethost.erase(6, p_pos - 6);\n      } else {\n        new_mqtargethost.erase(4);\n      }\n\n      lastMgmHostPort.set(newmgm);\n      newmgm.erase(newmgm.find(\":\"));\n      new_mqtargethost.insert(6, newmgm);\n      // instruct a new ZMQ connection\n      mds.connect(new_mqtargethost);\n      eos_static_warning(\"reconnecting mqtarget=%s => mqtarget=%s\",\n                         config.mqtargethost.c_str(), new_mqtargethost.c_str());\n    } else {\n      // just store the first time we see the connected endpoint url\n      lastMgmHostPort.set(newmgm);\n    }\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\nEosFuse::Prefix(std::string path)\n/* -------------------------------------------------------------------------- */\n{\n  std::string fullpath = Config().localmountdir;\n\n  if (fullpath.back() == '/') {\n    fullpath.pop_back();\n  }\n\n  return (fullpath + path);\n}\n"
  },
  {
    "path": "fusex/eosxd/eosfuse.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file eosfuse.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief EOS C++ Fuse low-level implementation\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_EOSFUSE_HH_\n#define FUSE_EOSFUSE_HH_\n\n#include \"misc/MacOSXHelper.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/LinuxTotalMem.hh\"\n#include \"common/Murmur3.hh\"\n\n#include \"stat/Stat.hh\"\n#include \"md/md.hh\"\n#include \"cap/cap.hh\"\n#include \"data/data.hh\"\n#include \"backend/backend.hh\"\n#include \"kv/kv.hh\"\n#include \"kv/NoKV.hh\"\n#include \"llfusexx.hh\"\n#include \"auth/CredentialFinder.hh\"\n#include \"misc/Track.hh\"\n#include \"misc/FuseId.hh\"\n#include \"misc/stringTS.hh\"\n#include \"submount/SubMount.hh\"\n#include <set>\n#include <signal.h>\n#include <string.h>\n#include <string>\n#include <thread>\n#include <json/json.h>\n#include <google/dense_hash_set>\n#include <google/dense_hash_map>\n\n// PROTOBUF protocol version announced via heartbeats and attached to URLs by the backend\n#define FUSEPROTOCOLVERSION eos::fusex::heartbeat::PROTOCOLV5\n\nclass EosFuse : public llfusexx::FuseBase<EosFuse>\n{\npublic:\n\n  static EosFuse&\n  instance()\n  {\n    static EosFuse i;\n    return i;\n  }\n\n  EosFuse();\n  virtual ~EosFuse();\n\n  std::string UsageGet();\n  std::string UsageSet();\n  std::string UsageMount();\n  std::string UsageHelp();\n\n  int run(int argc, char* argv[], void* userdata);\n\n  static void umounthandler(int sig, siginfo_t* si, void* unused);\n\n  static void init(void* userdata, struct fuse_conn_info* conn);\n\n  static void destroy(void* userdata);\n\n  static void getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);\n\n  static void setattr(fuse_req_t req, fuse_ino_t ino, struct stat* attr,\n                      int to_set, struct fuse_file_info* fi);\n\n  static void\n  lookup(fuse_req_t req, fuse_ino_t parent, const char* name);\n\n  static int listdir(fuse_req_t req, fuse_ino_t ino, metad::shared_md& md,\n                     double& lifetime);\n\n  static void opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);\n\n  static void readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,\n                      struct fuse_file_info* fi);\n\n  static void readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,\n                      struct fuse_file_info* fi, bool plus);\n\n  static void readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,\n                          struct fuse_file_info* fi);\n  static void releasedir(fuse_req_t req, fuse_ino_t ino,\n                         struct fuse_file_info* fi);\n\n  static void statfs(fuse_req_t req, fuse_ino_t ino);\n\n  static void mknod(fuse_req_t req, fuse_ino_t parent, const char* name,\n                    mode_t mode, dev_t rdev);\n\n  static void mkdir(fuse_req_t req, fuse_ino_t parent, const char* name,\n                    mode_t mode);\n\n  static void rm(fuse_req_t req, fuse_ino_t parent, const char* name);\n\n  static void unlink(fuse_req_t req, fuse_ino_t parent, const char* name);\n\n  static void rmdir(fuse_req_t req, fuse_ino_t parent, const char* name);\n\n#ifdef USE_FUSE3\n  static void rename(fuse_req_t req, fuse_ino_t parent, const char* name,\n                     fuse_ino_t newparent, const char* newname, unsigned int flags);\n#else\n  static void rename(fuse_req_t req, fuse_ino_t parent, const char* name,\n                     fuse_ino_t newparent, const char* newname);\n#endif\n\n  static void access(fuse_req_t req, fuse_ino_t ino, int mask);\n\n  static void open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);\n\n  static void create(fuse_req_t req, fuse_ino_t parent, const char* name,\n                     mode_t mode, struct fuse_file_info* fi);\n\n  static void read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,\n                   struct fuse_file_info* fi);\n\n  static void write(fuse_req_t req, fuse_ino_t ino, const char* buf,\n                    size_t size, off_t off, struct fuse_file_info* fi);\n\n  static void release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);\n\n  static void fsync(fuse_req_t req, fuse_ino_t ino, int datasync,\n                    struct fuse_file_info* fi);\n\n  static void forget(fuse_req_t req, fuse_ino_t ino, unsigned long nlookup);\n  static int _forget(fuse_req_t req, fuse_ino_t ino, unsigned long nlookup);\n\n#ifdef USE_FUSE3\n  static void forget_multi(fuse_req_t req, size_t count,\n                           struct fuse_forget_data* forgets);\n#endif\n\n  static void flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);\n\n#ifdef __APPLE__\n  static void getxattr(fuse_req_t req, fuse_ino_t ino, const char* name,\n                       size_t size, uint32_t position);\n#else\n  static void getxattr(fuse_req_t req, fuse_ino_t ino, const char* name,\n                       size_t size);\n#endif\n\n#ifdef __APPLE__\n  static void setxattr(fuse_req_t req, fuse_ino_t ino, const char* name,\n                       const char* value, size_t size, int flags, uint32_t position);\n#else\n  static void setxattr(fuse_req_t req, fuse_ino_t ino, const char* name,\n                       const char* value, size_t size, int flags);\n#endif\n\n  static void listxattr(fuse_req_t req, fuse_ino_t ino, size_t size);\n\n  static void removexattr(fuse_req_t req, fuse_ino_t ino, const char* name);\n\n  static void readlink(fuse_req_t req, fuse_ino_t ino);\n\n  static void\n  link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent, const char* newname);\n\n  static void\n  symlink(fuse_req_t req, const char* link, fuse_ino_t parent, const char* name);\n\n  static void getlk(fuse_req_t req, fuse_ino_t ino,\n                    struct fuse_file_info* fi, struct flock* lock);\n\n  static void setlk(fuse_req_t req, fuse_ino_t ino,\n                    struct fuse_file_info* fi,\n                    struct flock* lock, int sleep);\n\n#if ( FUSE_VERSION > 28 )\n  static void flock(fuse_req_t req, fuse_ino_t ino,\n                    struct fuse_file_info* fi, int op);\n#endif\n\n  metad mds;\n  ::data datas;\n  cap caps;\n  backend mdbackend;\n\n  static EosFuse& Instance()\n  {\n    return *sEosFuse;\n  }\n\n  fuse_session* Session()\n  {\n    return fusesession;\n  }\n\n#ifndef USE_FUSE3\n  fuse_chan* Channel()\n  {\n    return fusechan;\n  }\n#endif\n\n  std::string Prefix(std::string path);\n\n  typedef struct cfg {\n    std::string name;\n    std::string hostport;\n    std::string remotemountdir;\n    std::string localmountdir;\n    std::string statfilesuffix;\n    std::string statfilepath;\n    std::string logfilepath;\n    std::string mdcachedir;\n    std::string mdcachedir_unlink;\n    std::string mqtargethost;\n    std::string mqidentity;\n    std::string mqname;\n    std::string clienthost;\n    std::string clientuuid;\n    std::string ssskeytab;\n    std::string appname;\n    std::string encryptionkey;\n\n    typedef struct options {\n      int debug;\n      int debuglevel;\n      bool jsonstats;\n      int libfusethreads;\n      int foreground;\n      int automounted;\n      int md_kernelcache;\n      int enable_backtrace;\n      double md_kernelcache_enoent_timeout;\n      double md_backend_timeout;\n      double md_backend_put_timeout;\n      int data_kernelcache;\n      int rename_is_sync;\n      int rmdir_is_sync;\n      int global_flush;\n\n      int flush_wait_umount;\n      int flush_wait_open;\n      enum eFLUSH_WAIT_OPEN {\n        kWAIT_FLUSH_NEVER = 0, // if a file is updated/created - flush will not wait to open it\n        kWAIT_FLUSH_ON_UPDATE = 1, // if a file is updated - flush will wait to open it\n        kWAIT_FLUSH_ON_CREATE = 2 // if a file is created - flush will wait to open it\n      };\n\n      size_t flush_wait_open_size;\n\n      bool writebackcache;\n      int global_locking;\n      uint64_t fdlimit;\n      int rm_rf_protect_levels;\n      int rm_rf_bulk;\n      int show_tree_size;\n      int free_md_asap;\n      int cpu_core_affinity;\n      mode_t overlay_mode;\n      mode_t x_ok;\n      int no_xattr;\n      int no_eos_xattr_listing;\n      int no_hardlinks;\n      uint32_t nocache_graceperiod;\n      int leasetime;\n      int write_size_flush_interval;\n      int submounts;\n      int inmemory_inodes;\n      bool flock;\n      bool hide_versions;\n      std::vector<std::string> no_fsync_suffixes;\n      std::vector<std::string> nowait_flush_executables;\n      bool protect_directory_symlink_loops;\n      bool fakerename;\n    } options_t;\n\n    typedef struct recovery {\n      int read;\n      int write;\n      int read_open;\n      int write_open;\n      int read_open_noserver;\n      int write_open_noserver;\n      size_t read_open_noserver_retrywindow;\n      size_t write_open_noserver_retrywindow;\n    } recovery_t;\n\n    typedef struct fuzzing {\n      size_t open_async_submit;\n      size_t open_async_return;\n      size_t read_async_return;\n      bool open_async_submit_fatal;\n      bool open_async_return_fatal;\n    } fuzzing_t;\n\n    typedef struct inlining {\n      uint64_t max_size;\n      std::string default_compressor;\n    } inlining_t;\n\n    recovery_t recovery;\n    options_t options;\n    inlining_t inliner;\n    fuzzing_t fuzzing;\n\n    CredentialConfig auth;\n  } cfg_t;\n\n  cfg_t& Config()\n  {\n    return config;\n  }\n\n  Track& Tracker()\n  {\n    return tracker;\n  }\n\n  SubMount& Mounter()\n  {\n    return mounter;\n  }\n\n\n  const char* umountcall()\n  {\n    return umount_system_line.c_str();\n  }\n\n  static std::string dump(fuse_id id,\n                          fuse_ino_t ino,\n                          struct fuse_file_info* fi,\n                          int rc,\n                          std::string name = \"\")\n  {\n    char s[1024];\n    char ebuf[1024];\n    ebuf[0] = 0;\n\n    if (strerror_r(rc, ebuf, sizeof(ebuf))) {\n      snprintf(ebuf, sizeof(ebuf), \"???\");\n    }\n\n    snprintf(s, 1024,\n             \"rc=%02d uid=%05d gid=%05d pid=%05d ino=%#lx fh=%#lx name=%s\",\n             rc,\n             id.uid,\n             id.gid,\n             id.pid,\n             ino,\n             fi ? fi->fh : 0,\n             name.c_str());\n    return s;\n  }\n\n  typedef struct opendir_fh {\n    typedef std::vector<std::string> ChildSet;\n    typedef std::map <std::string, uint64_t> ChildMap;\n\n    opendir_fh()\n    {\n      pmd_mtime.tv_sec = pmd_mtime.tv_nsec = 0;\n    }\n\n    metad::shared_md md;\n\n    ChildSet readdir_items;\n    ChildMap pmd_children;\n\n    struct reply_buf {\n      char blob[65536];\n      char* ptr;\n      off_t size;\n\n      reply_buf()\n      {\n        reset();\n      }\n\n      void reset()\n      {\n        ptr = blob;\n        size = 0;\n      }\n\n      char* buffer()\n      {\n        return blob;\n      }\n    };\n\n    struct timespec pmd_mtime;\n    struct reply_buf b;\n\n    double lifetime;\n    struct timespec opendir_time;\n\n    XrdSysMutex items_lock;\n  } opendir_t;\n\n  static int readdir_filler(fuse_req_t req, opendir_t* md,\n                            mode_t& pmd_mode, uint64_t& pmd_id);\n\n  void getHbStat(eos::fusex::statistics&);\n\n  kv* getKV()\n  {\n    return mKV.get();\n  }\n\n  cap& getCap()\n  {\n    return caps;\n  }\n\n  void cleanup(fuse_ino_t ino)\n  {\n    return mds.cleanup(ino);\n  }\n\n  void TrackMgm(const std::string& lasturl);\n\n  stringTS statsout;\n\n  int truncateLogFile()\n  {\n    if (!fstderr) {\n      return 0;\n    }\n\n    fflush(fstderr);\n    return ftruncate(fileno(fstderr), (off_t) 0);\n  }\n\n  size_t sizeLogFile()\n  {\n    struct stat buf;\n\n    if (!fstderr) {\n      return 0;\n    }\n\n    if (!fstat(fileno(fstderr), &buf)) {\n      return buf.st_size;\n    } else {\n      return 0;\n    }\n  }\n\n  void shrinkLogFile()\n  {\n    if (!fstderr) {\n      return ;\n    }\n\n    const size_t maxsize = 4 * 1024ll * 1024ll * 1024ll; // 4G\n\n    if (sizeLogFile() > maxsize) {\n      ftruncate(fileno(fstderr), maxsize / 2);\n      eos_static_crit(\"logfile has been truncated back to %lu bytes - exceeded %lu bytes\",\n                      maxsize / 2, maxsize);\n    }\n  }\n\n  bool Trace()\n  {\n    return mTrace;\n  }\n  void SetTrace(bool t)\n  {\n    mTrace = t;\n  }\n\nprotected:\n\nprivate:\n\n  static bool isRecursiveRm(fuse_req_t req, bool forced = false,\n                            bool notverbose = false);\n\n  static void Merge(Json::Value& a, Json::Value& b)\n  {\n    if (!a.isObject() || !b.isObject()) {\n      return;\n    }\n\n    for (const auto& key : b.getMemberNames()) {\n      if (a[key].type() == Json::objectValue && b[key].type() == Json::objectValue) {\n        Merge(a[key], b[key]);\n      } else {\n        a[key] = b[key];\n      }\n    }\n  }\n\n  bool mTrace;\n\n  Track tracker;\n\n  SubMount mounter;\n\n  cfg_t config;\n\n  stringTS lastMgmHostPort;\n\n  std::unique_ptr<kv> mKV;\n  Stat fusestat;\n\n  FILE* fstderr;\n\n  Stat& getFuseStat()\n  {\n    return fusestat;\n  }\n\n  metad::mdstat& getMdStat()\n  {\n    return mds.stats();\n  }\n\n  eos::common::LinuxTotalMem meminfo;\n  XrdSysMutex statsoutmutex;\n\n  static EosFuse* sEosFuse;\n\n  struct fuse_session* fusesession;\n\n#ifndef USE_FUSE3\n  struct fuse_chan* fusechan;\n#endif\n\n  std::atomic<int32_t> aRecoveryOk;\n  std::atomic<int32_t> aRecoveryFail;\n\n  AssistedThread tDumpStatistic;\n  AssistedThread tStatCirculate;\n  AssistedThread tMetaCacheFlush;\n  AssistedThread tMetaStackFree;\n  AssistedThread tMetaCommunicate;\n  AssistedThread tMetaCallback;\n  AssistedThread tCapFlush;\n\n  std::string umount_system_line;\n\n  void DumpStatistic(ThreadAssistant& assistant);\n  void StatCirculate(ThreadAssistant& assistant);\n};\n\n#endif /* FUSE_EOSFUSE_HH_ */\n"
  },
  {
    "path": "fusex/eosxd/llfusexx.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file llfusexx.hh\n//! @author Justin Salmon, Andreas-Joachim Peters CERN\n//! @brief C++ template class for low-level FUSE calls\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __LLFUSEXX_H__\n#define __LLFUSEXX_H__\n\n#include <sys/stat.h>\n#include <sys/types.h>\n\n#if ( FUSE_MOUNT_VERSION == 290 )\n#define FUSE_SUPPORTS_FLOCK\n// Moved to cmake - #pragma message(\"FUSE_SUPPORTS_FLOCK\")\n#endif\n\n\n#ifndef FUSE_USE_VERSION\n#ifdef __APPLE__\n#define FUSE_USE_VERSION 27\n#pragma message(\"FUSE 27\")\n#define EL2NSYNC    45  /* Level 2 not synchronized */\n#else\n\n#ifdef USE_FUSE3\n#define FUSE_USE_VERSION 32\n#define FUSE_SUPPORTS_FLOCK\n// Moved to cmake - #pragma message(\"FUSE_SUPPORTS_FLOCK\")\n// Moved to cmake - #pragma message(\"FUSE 31\")\n#else\n#define FUSE_USE_VERSION 28\n// Moved to cmake - #pragma message(\"FUSE 28\")\n#endif\n\n\n#endif\n#endif\n\nextern \"C\" {\n#ifdef USE_FUSE3\n#include <fuse3/fuse_lowlevel.h>\n#else\n#include <fuse/fuse_lowlevel.h>\n#endif\n}\n\n#include <cstdlib>\n\n#ifndef FUSE_SET_ATTR_ATIME_NOW\n#define FUSE_SET_ATTR_ATIME_NOW (1 << 7)\n#endif\n#ifndef FUSE_SET_ATTR_MTIME_NOW\n#define FUSE_SET_ATTR_MTIME_NOW (1 << 8)\n#endif\n\n\nnamespace llfusexx\n{\n//----------------------------------------------------------------------------\n//! Interface to the low-level FUSE API\n//----------------------------------------------------------------------------\n\ntemplate <typename T>\nclass FuseBase\n{\nprotected:\n  //------------------------------------------------------------------------\n  //! Structure holding function pointers to the low-level \"operations\"\n  //! (function implementations)\n  //------------------------------------------------------------------------\n  struct fuse_lowlevel_ops operations;\n\n  //------------------------------------------------------------------------\n  //! @return const reference to the operations struct\n  //------------------------------------------------------------------------\n\n  const fuse_lowlevel_ops&\n  get_operations()\n  {\n    return operations;\n  }\n\n  void disable_xattr()\n  {\n    operations.getxattr = 0;\n    operations.setxattr = 0;\n    operations.listxattr = 0;\n    operations.removexattr = 0;\n  }\n\n  void disable_link()\n  {\n    operations.link = 0;\n  }\n\n  //------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! Install pointers to operation functions as implemented by the user\n  //! subclass\n  //------------------------------------------------------------------------\n\n  FuseBase()\n  {\n    operations.init = &T::init;\n    operations.destroy = &T::destroy;\n    operations.getattr = &T::getattr;\n    operations.lookup = &T::lookup;\n    operations.setattr = &T::setattr;\n    operations.opendir = &T::opendir;\n    operations.access = &T::access;\n    operations.readdir = &T::readdir;\n#ifdef USE_FUSE3\n    operations.readdirplus = &T::readdirplus;\n#endif\n    operations.mkdir = &T::mkdir;\n    operations.unlink = &T::unlink;\n    operations.rmdir = &T::rmdir;\n    operations.rename = &T::rename;\n    operations.open = &T::open;\n    operations.create = &T::create;\n    operations.mknod = &T::mknod;\n    operations.read = &T::read;\n    operations.write = &T::write;\n    operations.statfs = &T::statfs;\n    operations.release = &T::release;\n    operations.releasedir = &T::releasedir;\n    operations.fsync = &T::fsync;\n    operations.forget = &T::forget;\n#ifdef USE_FUSE3\n    operations.forget_multi = &T::forget_multi;\n#endif\n    operations.flush = &T::flush;\n    operations.setxattr = &T::setxattr;\n    operations.getxattr = &T::getxattr;\n    operations.listxattr = &T::listxattr;\n    operations.removexattr = &T::removexattr;\n    operations.readlink = &T::readlink;\n    operations.link = &T::link;\n    operations.symlink = &T::symlink;\n    operations.getlk = &T::getlk;\n    operations.setlk = &T::setlk;\n#ifdef FUSE_SUPPORTS_FLOCK\n    operations.flock = &T::flock;\n#endif\n  }\n\npublic:\n\n  int run(int argc, char* argv[], void* userdata);\n};\n}\n\n#endif /* __LLFUSEXX_H__ */\n"
  },
  {
    "path": "fusex/eosxd/main.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file main.cc\n//! @author Andreas-Joachim Peters\n//! @brief EOS C++ Fuse eosd executable\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#include \"eosfuse.hh\"\n\nint\nmain(int argc, char* argv[])\n{\n  EosFuse& eosfuse = EosFuse::instance();\n  return eosfuse.run(argc, argv, NULL);\n}\n"
  },
  {
    "path": "fusex/fuse.conf.example",
    "content": "\n{\n  \"name\" : \"\",\n  \"hostport\" : \"localhost:1094\",\n  \"remotemountdir\" : \"/eos/\",\n  \"localmountdir\" : \"/eos/\",\n  \"statisticfile\" : \"stats\",\n  \"mdcachedir\" : \"/var/cache/eos/fusex/md\",\n  \"mdzmqtarget\" : \"tcp://localhost:1100\",\n  \"mdzmqidentity\" : \"eosxd\",\n  \"appname\" : \"\",\n  \"options\" : {\n    \"debug\" : 1,\n    \"debuglevel\" : 4,\n    \"jsonstats\" : 1,\n    \"backtrace\" : 1,\n    \"libfusethreads\" : 0,\n    \"hide-versions\" : 1,\n    \"protect-directory-symlink-loops\" : 0,\n    \"md-kernelcache\" : 1,\n    \"md-kernelcache.enoent.timeout\" : 0,\n    \"md-backend.timeout\" : 86400,\n    \"md-backend.put.timeout\" : 120,\n    \"data-kernelcache\" : 1,\n    \"rename-is-sync\" : 1,\n    \"rmdir-is-sync\" : 0,\n    \"global-flush\" : 0,\n    \"flush-wait-open\" : 1,\n    \"flush-wait-open-size\" : 262144 ,\n    \"flush-wait-umount\" : 120,\n    \"flush-nowait-executables\" : [ \"/tar\", \"/touch\" ],\n    \"global-locking\" : 1,\n    \"fd-limit\" : 524288,\n    \"no-fsync\" : [ \".db\", \".db-journal\", \".sqlite\", \".sqlite-journal\", \".db3\", \".db3-journal\", \"*.o\" ],\n    \"overlay-mode\" : \"000\",\n    \"rm-rf-protect-levels\" : 0,\n    \"rm-rf-bulk\" : 0,\n    \"show-tree-size\" : 0,\n    \"cpu-core-affinity\" : 1,\n    \"no-xattr\" : 1,\n    \"no-eos-xattr-listing\" : 0,\n    \"no-link\" : 0,\n    \"nocache-graceperiod\" : 5,\n    \"leasetime\" : 300,\n    \"write-size-flush-interval\" : 10,\n    \"submounts\" : 0,\n    \"inmemory-inodes\" : 16384\n  },\n  \"auth\" : {\n    \"shared-mount\" : 1,\n    \"krb5\" : 1,\n    \"gsi-first\" : 0,\n    \"sss\" : 1,\n    \"ssskeytab\" : \"/etc/eos/fuse.sss.keytab\",\n    \"sssEndorsement\" : \"\",\n    \"oauth2\" : 1,\n    \"unix\" : 0,\n    \"environ-deadlock-timeout\" : 100,\n    \"forknoexec-heuristic\" : 1\n  },\n  \"inline\" : {\n    \"max-size\" : 0,\n    \"default-compressor\" : \"none\"\n  },\n  \"fuzzing\" : {\n    \"open-async-submit\" : 0,\n    \"open-async-return\" : 0,\n    \"open-async-submit-fatal\" : 0,\n    \"open-async-return-fatal\" : 0,\n    \"read-async-submit\" : 0\n  },\n  \"cache\" : {\n    \"type\" : \"disk\",\n    \"size-mb\" : 512,\n    \"size-ino\" : 65536,\n    \"journal-mb\" : 2048,\n    \"journal-ino\" : 65536,\n    \"clean-threshold\" : 85.0,\n    \"location\" : \"/var/cache/eos/fusex/cache/\",\n    \"journal\" : \"/var/cache/eos/fusex/journal/\",\n    \"read-ahead-strategy\" : \"static\",\n    \"read-ahead-bytes-nominal\" : 262144,\n    \"read-ahead-bytes-max\" : 2097152,\n    \"read-ahead-blocks-max\" : 16,\n    \"max-read-ahead-buffer\" : 134217728,\n    \"max-write-buffer\" : 134217728\n  },\n  \"xrdcl\" : {\n    \"TimeoutResolution\" : 1,\n    \"ConnectionWindow\": 10,\n    \"ConnectionRetry\" : 0,\n    \"StreamErrorWindow\" : 60,\n    \"RequestTimeout\" : 30,\n    \"StreamTimeout\" : 60,\n    \"RedirectLimit\" : 3,\n    \"LogLevel\" : \"None\"\n  },\n   \"recovery\" : {\n     \"read-open\" : 1,\n     \"read-open-noserver\" : 1,\n     \"read-open-noserver-retrywindow\" : 15,\n     \"write-open\" : 1,\n     \"write-open-noserver\" : 1,\n     \"write-open-noserver-retrywindow\" : 15\n   }\n}\n"
  },
  {
    "path": "fusex/fuse.example.stats.json",
    "content": "{\n   \"activity\" : [\n      {\n         \"1h\" : 0.070019449847179768,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \":sum\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 252\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"access\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"create\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"flush\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"forget\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"fsync\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0.0013892747985551543,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"getattr\",\n         \"cumul(s)\" : 0.00038600000552833078,\n         \"exec(ms)\" : 0.077200001105666161,\n         \"sigma(ms)\" : 0.044006363881297958,\n         \"sum\" : 5\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"getxattr\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"link\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"listxattr\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0.015282022784106696,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"lookup\",\n         \"cumul(s)\" : 0.0072429999858140944,\n         \"exec(ms)\" : 0.13169090883298354,\n         \"sigma(ms)\" : 0.012520344041837475,\n         \"sum\" : 55\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"mkdir\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"mknod\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"open\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0.01611558766323979,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"opendir\",\n         \"cumul(s)\" : 0.64249400457739825,\n         \"exec(ms)\" : 11.07748283754135,\n         \"sigma(ms)\" : 52.364150045145983,\n         \"sum\" : 58\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"read\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0.023895526535148651,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"readdir\",\n         \"cumul(s)\" : 0.0015640000132843849,\n         \"exec(ms)\" : 0.018186046666097501,\n         \"sigma(ms)\" : 0.014058442686889121,\n         \"sum\" : 86\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"readlink\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"release\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0.013059183106418449,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"releasedir\",\n         \"cumul(s)\" : 0.00048500000080093736,\n         \"exec(ms)\" : 0.010319148953211434,\n         \"sigma(ms)\" : 0.0075233151752647359,\n         \"sum\" : 47\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"removexattr\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"rename\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"rm\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"rmdir\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"setattr\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"setattr:chmod\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"setattr:chown\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"setattr:truncate\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"setattr:utimes\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"setxattr\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0.00027785495971103082,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"statfs\",\n         \"cumul(s)\" : 0.0025230000019073485,\n         \"exec(ms)\" : 2.5230000019073486,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 1\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"symlink\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"unlink\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      },\n      {\n         \"1h\" : 0,\n         \"1min\" : 0,\n         \"5min\" : 0,\n         \"5s\" : 0,\n         \"command\" : \"write\",\n         \"cumul(s)\" : 0,\n         \"exec(ms)\" : 0,\n         \"sigma(ms)\" : 0,\n         \"sum\" : 0\n      }\n   ],\n   \"inodes\" : {\n      \"backlog\" : 0,\n      \"caps\" : 0,\n      \"ever\" : 197,\n      \"everdeleted\" : 0,\n      \"number\" : 59,\n      \"open\" : 0,\n      \"proxies\" : 0,\n      \"rhexpired\" : 0,\n      \"stack\" : 0,\n      \"todelete\" : 0,\n      \"tracker\" : 44,\n      \"vmap\" : 1\n   },\n   \"recoveries\" : {\n      \"recover:n\" : 0,\n      \"recover:read:disabled\" : 0,\n      \"recover:read:exceeded\" : 0,\n      \"recover:read:failed\" : 0,\n      \"recover:read:n\" : 0,\n      \"recover:read:noproxy\" : 0,\n      \"recover:read:reopen:disabled\" : 0,\n      \"recover:read:reopen:failed\" : 0,\n      \"recover:read:reopen:n\" : 0,\n      \"recover:read:reopen:noserver:disabled\" : 0,\n      \"recover:read:reopen:noserver:fatal\" : 0,\n      \"recover:read:reopen:noserver:retry\" : 0,\n      \"recover:read:reopen:success\" : 0,\n      \"recover:read:reread:n\" : 0,\n      \"recover:read:success\" : 0,\n      \"recover:read:unrecoverble\" : 0,\n      \"recover:write:disabled\" : 0,\n      \"recover:write:fromcache\" : 0,\n      \"recover:write:fromcache:failed\" : 0,\n      \"recover:write:fromcache:read:failed\" : 0,\n      \"recover:write:fromremote\" : 0,\n      \"recover:write:fromremote:beginflush:failed\" : 0,\n      \"recover:write:fromremote:endflush:failed\" : 0,\n      \"recover:write:fromremote:local:failed\" : 0,\n      \"recover:write:fromremote:localwrite:failed\" : 0,\n      \"recover:write:fromremote:read:failed\" : 0,\n      \"recover:write:fromremote:write:failed\" : 0,\n      \"recover:write:journalflush:failed\" : 0,\n      \"recover:write:journalflush:success\" : 0,\n      \"recover:write:n\" : 0,\n      \"recover:write:nocache:failed\" : 0,\n      \"recover:write:noproxy\" : 0,\n      \"recover:write:reopen:disabled\" : 0,\n      \"recover:write:reopen:n\" : 0,\n      \"recover:write:reopen:noserver::disabled\" : 0,\n      \"recover:write:reopen:noserver::retry\" : 0,\n      \"recover:write:reopen:noserver:failed\" : 0,\n      \"recover:write:reopen:nosever\" : 0,\n      \"recover:write:reopen:overquota\" : 0,\n      \"recover:write:reopen:success\" : 0,\n      \"recover:write:reopen:unrecoverable\" : 0,\n      \"recover:write:unrecoverable\" : 0\n   },\n   \"stats\" : {\n      \"automounted\" : 0,\n      \"blocker\" : \"\",\n      \"client-uuid\" : \"a230572e-4302-11ed-ac74-fa163e8a0ad8\",\n      \"endpoint-url\" : \"localhost:1094\",\n      \"free-mem\" : 4995371008,\n      \"fuseversion\" : 28,\n      \"instance-url\" : \"localhost\",\n      \"iops\" : 0,\n      \"last-heartbeat-secs\" : 8,\n      \"load\" : 142912,\n      \"log-size\" : 586220,\n      \"max-inode-lock-ms\" : 0,\n      \"pid\" : 14065,\n      \"ra-buf-inflight\" : \"0 b\",\n      \"ra-buf-queued\" : \"0 b\",\n      \"ra-nobuff\" : 0,\n      \"ra-xoff\" : 0,\n      \"rd-buf-inflight\" : \"0 b\",\n      \"rd-buf-queued\" : \"0 b\",\n      \"read--mb/s\" : 0,\n      \"rss\" : \"27.93 Mb\",\n      \"server-version\" : \"5.1.1::20220927130729git0198b19\",\n      \"starttime\" : 1664791415,\n      \"threads\" : 45,\n      \"total-io-ops\" : 252,\n      \"total-mem\" : 30706642944,\n      \"total-rbytes\" : 0,\n      \"total-wbytes\" : 0,\n      \"uptime\" : 728,\n      \"version\" : \"5.1.1\",\n      \"visze\" : \"670.43 Mb\",\n      \"wr-buf-inflight\" : \"0 b\",\n      \"wr-buf-queued\" : \"0 b\",\n      \"wr-nobuff\" : 0,\n      \"write-mb/s\" : 0,\n      \"xoffs\" : 0\n   },\n   \"time\" : {\n      \"avg(ms)\" : 2.5979960499394181,\n      \"ops\" : 252,\n      \"sigma(ms)\" : 25.546408596193459,\n      \"total(s)\" : 0.65421000458393241\n   }\n}"
  },
  {
    "path": "fusex/fusex.proto",
    "content": "syntax = \"proto3\";\npackage eos.fusex;\n\nmessage md {\n  enum OP { GET = 0; SET = 1; DELETE = 2; GETCAP = 3; LS = 4; GETLK = 5; SETLK = 6; SETLKW = 7; BEGINFLUSH = 8; ENDFLUSH = 9;}\n  enum TYPE { MD = 0; MDLS = 1; EXCL = 2;}\n  enum FLAG { NONE = 0; DELETEVERSIONS = 1; NORECYCLEBIN = 2; DELETEVERSIONSNORECYCLEBIN = 3;}\n\n  fixed64 id = 1;        //< file/container id\n  fixed64 pid = 2;       //< parent id\n  fixed64 ctime = 3    ; //< change time\n  fixed64 ctime_ns = 4 ; //< ns of creation time\n  fixed64 mtime = 5    ; //< modification time | deletion time\n  fixed64 mtime_ns = 6 ; //< ns of modification time\n  fixed64 atime = 7    ; //< access time\n  fixed64 atime_ns = 8 ; //< ns of access time\n  fixed64 btime = 9    ; //< birth time\n  fixed64 btime_ns = 10; //< ns of birth time\n  fixed64 ttime = 11   ; //< tree modification time\n  fixed64 ttime_ns = 12; //< ns of tree modification time\n  fixed64 pmtime = 13   ; //< parent modification time\n  fixed64 pmtime_ns = 14; //< ns of parent modification time\n  fixed64 size = 15     ; //< size \n  sfixed32 uid = 16    ; //< user  id\n  sfixed32 gid = 17    ; //< group id\n  sfixed32 mode = 18   ; //< mode\n  sfixed32 nlink = 19  ; //< nlink\n  bytes name = 20     ; //< name\n  bytes target = 21   ; //< symlink target\n  string authid = 22   ; //< strong security auth id\n  string clientid = 23 ; //< string identifying a client\n  string clientuuid = 24; //< optional string of a temporary client id\n  fixed64 clock = 25    ; //< vector clock\n  fixed64 reqid = 26    ; //< request ID\n  fixed64 md_ino = 27  ; //< central namespace inode\n  fixed64 md_pino = 28 ; //< central namespace parent inode\n  OP operation = 29    ; //< indicates operations to execute\n  TYPE type = 30       ; //< indicates the content of this recored, just MD or MD+children\n  sfixed32 err = 31  ; //< errno concerning md record\n  map<string, bytes> attr = 32; //< xattribute map\n  map<string, fixed64> children = 33; //< children's name and id \n  // children id is a virtual inode on when stored FUSE client side\n  // children id is the physical inode when created on server side \n  cap capability = 34; //< optional response capability\n  string implied_authid = 35; //< implied auth id from a create call\n  lock flock = 36; //< file lock information\n  sfixed32 nchildren = 37; //< number of children\n  bytes fullpath = 38; //< current path to file/dir\n  fixed64 pt_mtime = 39 ; //< modification time for the parent directory\n  fixed64 pt_mtime_ns= 40 ; //< ns of modification time for the parent directory\n  bool creator = 41; //< indicates we are the creator of this md record\n  string mv_authid = 42; //< indicates the authid applying to the source directory of a mv\n  fixed64 bc_time = 43; //< indicates the reception time of a broadcasted md record\n  FLAG opflags = 44; //< indicates a flag for an operation\n  fixed64 tmptime = 45   ; //< last time a .tmp file was created\n};\n\nmessage md_state {\t\n  sfixed32 op = 1;\t\t\n  sfixed32 lookup_cnt = 2;\n  sfixed32 cap_cnt = 3;\n  sfixed32 opendir_cnt\t= 4;\n  bool lock_remote     = 5;\n  bool refresh =   6;\n  bool rmrf = 7; \n  fixed64 inline_size = 8;\n  map<string, fixed64> todelete = 9;\n  map<string, fixed64> children = 10;\n  map<string, fixed64> enoent = 11;\n};\n\nmessage md_map {\n  map<fixed64, md> md_map_ = 1;\n};\n\nmessage dir {\n  fixed64 id = 1; //< container id\n  repeated string linked = 2;\n  repeated string unlinked = 3;\n};\n\nmessage io {\n  fixed64 id = 1; //< file id\n  fixed64 offset = 2; //< offset\n  fixed64 length = 3; //< length\n  fixed64 clock = 4; //< vector clock\n};\n\nmessage quota {\n  fixed64 inode_quota = 1 ; //< free inode quota\n  fixed64 volume_quota = 2; //< free bytes quota\n  fixed64 quota_inode = 3; //< inode of the quota node\n};\n\nmessage cap {\n  fixed64 id = 1; //< file/container \n  fixed32 mode = 2; //< granted mode\n  fixed64 vtime = 3; //< valid until unix timestamp\n  fixed64 vtime_ns = 4; //< valid ns resolution\n  sfixed32 uid = 5    ; //< user  id\n  sfixed32 gid = 6    ; //< group id\n  string clientuuid = 7; //< client uuid\n  string clientid = 8; //< client id\n  string authid = 9; //< auth id\n  fixed32 errc = 10; //< error code\n  fixed64 clock = 11    ; //< vector clock of the file/container\n  fixed64 max_file_size = 12; //< maximum file size\n  quota _quota = 13; //< quota information for this cap\n};\n\n\n\nmessage cap_map {\n   map<fixed64, cap> cap_map_ = 1;\n};\n\nmessage heartbeat {\n  enum ProtVersion { PROTOCOLV1 = 0; PROTOCOLV2 = 1; PROTOCOLV3 = 2; PROTOCOLV4 = 3; PROTOCOLV5 = 4;}\n\n  string name = 1; //< client chosen ID\t\n  string host = 2; //< client host\n  string uuid = 3; //< client uuid\n  string version = 4; //< client version\n  fixed32 pid = 5; //< client pid\n  fixed64 starttime = 6; //< client startup time\n  fixed64 clock = 7; //< client clock ns when sending\n  fixed64 clock_ns = 8; //< client clock ns when sending\n  double delta = 9; //< client delta after arrival\n  ProtVersion protversion = 10;\n  map<string, fixed32> authextension = 11; // < map of authids to extend lifetime by seconds \n  map<string, fixed32> authrevocation = 12; // < map of authids to revoke - pair of authid/unused\n  fixed32 leasetime = 13; \n  bool shutdown = 14;\n  string mount = 15; //< client mount path\n  bool automounted = 16; //< indicate if autofs mount or not  \n  string log = 17; //< client logs\n  string trace = 18; //< client stacktrace\n  string appname = 19; // < client app name\n}    \n\nmessage statistics {\n  fixed64 inodes = 1; //< client inodes\n  fixed64 inodes_todelete = 2; //< client inodes to delete\n  fixed64 inodes_backlog = 3; //< client inodes backlog\n  fixed64 inodes_ever = 4; //< client inodes ever used\n  fixed64 inodes_ever_deleted = 5; //< client inodes ever deleted\n  fixed32 threads = 6; //< client threads in use\n  float vsize_mb = 7; //< client virtual memory usage in mb\n  float rss_mb = 8; //< client resident memory usage in mb\n  fixed32 open_files = 9; //< files currently open on a client\n  float total_ram_mb = 10;//< total usable ram size\n  float free_ram_mb = 11; //< total free ram size\n  float load1 = 12; //< load value 1\n  fixed64 rbytes = 13; //< number of bytes read since start\n  fixed64 wbytes = 14; //< number of bytes written since start\n  float rd_rate_60_mb = 15; //< read rate in 1m window in mb/s\n  float wr_rate_60_mb = 16; //< write rate in 1m window in mb/s\n  fixed64 nio = 17; //< total number of io's\n  float iops_60 = 18; //< average ios in 1m window\n  float wr_buf_mb = 19; //< write buffer in use\n  float ra_buf_mb = 20; //< read-ahead buffer in use\n  fixed64 xoff = 21; //< xoff conditions counter\n  fixed64 raxoff = 22; //< read-ahead buffer xoff\n  fixed64 ranobuf = 23; //< read-ahead buffer no buffer available\n  fixed32 pid = 24; //< process ID of eosxd\n  fixed64 logfilesize = 25; //< size of log file\n  fixed64 wrnobuf = 26; //< write buffer no buffer available\n  float blockedms = 27; //< max time in ms an inode tracking mutex is held in a lock\n  string blockedfunc = 28; //< function with is blocked for ms\n  fixed32 recovery_ok = 29; // < number of recoveries ok\n  fixed32 recovery_fail = 30; // < number of recoveries failed\n  fixed32 blockedops = 31; // < number of blocking operations\n  bool blockedroot = 32; // < indicate if operation on / is blocking\n}\n\nmessage container {\n  enum Type { HEARTBEAT = 0; STATISTICS = 1; MD = 2; DIR = 3; MDMAP = 4; CAP = 5; }\n\n  // Identifies which field is filled in.\n  Type type = 1;\n\n  // One of the following will be filled in.\n  heartbeat heartbeat_ = 2;\n  statistics statistics_ = 3;\n  md md_ = 4;\n  dir dir_ = 5;\n  md_map md_map_ = 6;\n  fixed64 ref_inode_ = 7;\n  cap cap_ = 8;\n  cap_map cap_map_ = 9;\n}\n\nmessage evict {\n  string reason = 1; //< reason given by server\n}\n\nmessage ack {\n  enum Code { OK = 0; TMP_FAILURE = 1; PERMANENT_FAILURE = 2; }\n\n  Code code = 1; //< ack code \n  fixed64 transactionid = 2; //< transaction id to ack\n  fixed32 err_no = 3; //< error number\n  fixed64 md_ino = 4; //< newly created inode number\n  string err_msg = 5; //< error message\n}\n\nmessage lease {\n  enum Type { RELEASECAP = 0; }\n\n  Type type = 1;\n  fixed64 md_ino = 2; //< inode number\n  string clientid = 3; //< clientid\n  string authid = 4; //< authid\n}\n\nmessage dentry {\n  enum Type { ADD = 0; REMOVE = 1;}\n\n  Type type = 1; \n  fixed64 md_ino = 2; //< inode number\n  bytes name = 3; //< entry name to delete\n  string clientid = 4; //< clientid\n  string authid = 5; //< authid\n  fixed64 pt_mtime = 6 ; //< modification time for the parent directory\n  fixed64 pt_mtime_ns= 7 ; //< ns of modification time for the parent directory\n}\n\nmessage refresh {\n  fixed64 md_ino = 1; //< inode number\n}\n\nmessage lock {\n  enum Type { RDLCK = 0; WRLCK = 1; UNLCK = 2; }\n  Type type = 1;   //< type of lock\n  fixed64 start = 2; //< offset of lock area\n  fixed64 len = 3; //< length of lock area\n  fixed64 pid = 4; //< owner of the lock\n  fixed32 err_no = 5; //< errno from locking call\n}\n\nmessage config {\n  fixed32 hbrate = 1; //< heartbeat interval for this client\n  bool dentrymessaging = 2; //< enables dentry invalidation by extra message\n  bool writesizeflush = 3; //< allows clients to use writesize flush interval ~= infinite\n  string serverversion = 4; //< software version of the server\n  bool appname = 5; //< supports extended app names like fuse::smaba not only fuse\n  bool mdquery = 6; //< supports fetchResponseQuery \n  bool hideversion = 7; //< supports clients hiding versions ( can delete version server side )\n}\n\nmessage response {\n  enum Type { EVICT = 0; ACK = 1; LEASE = 2; LOCK = 3; MD = 4; DROPCAPS = 5; CONFIG = 6; NONE = 7; CAP = 8; DENTRY = 9; REFRESH = 10; }\n\n  // Identifies which field is filled in.\n  Type type = 1;\n  // One of the following will be filled in.\n  evict evict_ = 2;\n  ack ack_ = 3;\n  lease lease_ = 4;\n  lock lock_ = 5;\n  md md_ = 6;\n  config config_ = 7;\n  cap cap_ = 8;\n  dentry dentry_ = 9;\n  refresh refresh_ = 10;\n}\n"
  },
  {
    "path": "fusex/kv/NoKV.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file NoKv.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief NokV persistency class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"kv/kv.hh\"\n#include \"eosfuse.hh\"\n#include \"misc/MacOSXHelper.hh\"\n#include \"common/Logging.hh\"\n#include <errno.h>\n\n/* -------------------------------------------------------------------------- */\nNoKV::NoKV()\n/* -------------------------------------------------------------------------- */\n{\n}\n\n/* -------------------------------------------------------------------------- */\nNoKV::~NoKV()\n/* -------------------------------------------------------------------------- */\n{\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nNoKV::connect(const std::string& prefix, const std::string& connectionstring,\n              int port)\n/* -------------------------------------------------------------------------- */\n{\n  return EOPNOTSUPP;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nNoKV::get(const std::string& key, std::string& value)\n/* -------------------------------------------------------------------------- */\n{\n  return EOPNOTSUPP;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nNoKV::inc(const std::string& key, uint64_t& value)\n/* -------------------------------------------------------------------------- */\n{\n  return EOPNOTSUPP;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nNoKV::put(const std::string& key, const std::string& value)\n/* -------------------------------------------------------------------------- */\n{\n  return EOPNOTSUPP;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nNoKV::erase(const std::string& key)\n/* -------------------------------------------------------------------------- */\n{\n  return EOPNOTSUPP;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nNoKV::get(uint64_t key, std::string& value, const std::string& name_space)\n/* -------------------------------------------------------------------------- */\n{\n  return EOPNOTSUPP;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nNoKV::get(uint64_t key, uint64_t& value, const std::string& name_space)\n/* -------------------------------------------------------------------------- */\n{\n  return EOPNOTSUPP;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nNoKV::get(const std::string& key, uint64_t& value)\n/* -------------------------------------------------------------------------- */\n{\n  return EOPNOTSUPP;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nNoKV::put(const std::string& key, uint64_t value)\n/* -------------------------------------------------------------------------- */\n{\n  return EOPNOTSUPP;\n}\n\nint\n/* -------------------------------------------------------------------------- */\nNoKV::put(uint64_t key, const std::string& value,\n          const std::string& name_space)\n/* -------------------------------------------------------------------------- */\n{\n  return EOPNOTSUPP;\n}\n\nint\n/* -------------------------------------------------------------------------- */\nNoKV::put(uint64_t key, uint64_t value, const std::string& name_space)\n/* -------------------------------------------------------------------------- */\n{\n  return EOPNOTSUPP;\n}\n\nint\n/* -------------------------------------------------------------------------- */\nNoKV::erase(uint64_t key, const std::string& name_space)\n/* -------------------------------------------------------------------------- */\n{\n  return EOPNOTSUPP;\n}\n"
  },
  {
    "path": "fusex/kv/NoKV.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file RedisKeyValueStore.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief kv persistency class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_NO_KV_HH_\n#define FUSE_NO_KV_HH_\n\n#include <sys/stat.h>\n#include <sys/types.h>\n#include \"llfusexx.hh\"\n#include \"fusex/fusex.pb.h\"\n#include \"kv/kv.hh\"\n#include <XrdSys/XrdSysPthread.hh>\n#include <memory>\n#include <map>\n#include <event.h>\n\n\n//------------------------------------------------------------------------------\n// Implementation of the key value store interface based on redis\n//------------------------------------------------------------------------------\n\nclass NoKV : public kv\n{\npublic:\n\n  //----------------------------------------------------------------------------\n\n  //----------------------------------------------------------------------------\n  NoKV();\n  virtual ~NoKV();\n\n  int connect(const std::string& prefix, const std::string& connectionstring,\n              int port);\n\n  int get(const std::string& key, std::string& value) override;\n  int get(const std::string& key, uint64_t& value) override;\n  int put(const std::string& key, const std::string& value) override;\n  int put(const std::string& key, uint64_t value) override;\n  int inc(const std::string& key, uint64_t& value) override;\n\n  int erase(const std::string& key) override;\n\n  int get(uint64_t key, std::string& value,\n          const std::string& name_space = \"i\") override;\n  int put(uint64_t key, const std::string& value,\n          const std::string& name_space = \"i\") override;\n\n  int get(uint64_t key, uint64_t& value,\n          const std::string& name_space = \"i\") override;\n  int put(uint64_t key, uint64_t value,\n          const std::string& name_space = \"i\") override;\n\n  int erase(uint64_t key, const std::string& name_space = \"i\") override;\n\n  int clean_stores(const std::string& storedir, const std::string& newdb) override\n  {\n    return 0;\n  }\n\n  std::string prefix(const std::string& key)\n  {\n    return key;\n  }\n\n  std::string statistics() override\n  {\n    return std::string(\"\");\n  }\n\nprivate:\n};\n#endif /* FUSE_KV_HH_ */\n"
  },
  {
    "path": "fusex/kv/RocksKV.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file RocksKV.cc\n//! @author Georgios Bitzes CERN\n//! @brief kv persistency class based on rocksdb\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifdef HAVE_ROCKSDB\n#include <rocksdb/filter_policy.h>\n#include <rocksdb/table.h>\n#include \"kv/kv.hh\"\n#include \"kv/RocksKV.hh\"\n#include \"eosfuse.hh\"\n#include \"misc/MacOSXHelper.hh\"\n#include \"common/Logging.hh\"\n#include \"misc/longstring.hh\"\n#include <sys/types.h>\n#include <dirent.h>\n#include <fstream>\n\n/* -------------------------------------------------------------------------- */\nstatic bool\n/* -------------------------------------------------------------------------- */\nsafe_strtoll(const std::string& str, uint64_t& ret)\n/* -------------------------------------------------------------------------- */\n{\n  char* endptr = NULL;\n  ret = strtoull(str.c_str(), &endptr, 10);\n\n  if (endptr != str.c_str() + str.size() || ret == ULLONG_MAX) {\n    return false;\n  }\n\n  return true;\n}\n\n/* -------------------------------------------------------------------------- */\nRocksKV::RocksKV()\n/* -------------------------------------------------------------------------- */\n{\n}\n\n/* -------------------------------------------------------------------------- */\nRocksKV::~RocksKV()\n/* -------------------------------------------------------------------------- */\n{\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nRocksKV::connect(const std::string& prefix, const std::string& path)\n/* -------------------------------------------------------------------------- */\n{\n  eos_static_info(\"Opening RocksKV store at local path %s\", path.c_str());\n  table_options.filter_policy.reset(rocksdb::NewBloomFilterPolicy(10, false));\n  table_options.block_size = 1024;\n  //  table_options.block_cache = rocksdb::NewLRUCache(4*1024);\n  //  table_options.cache_index_and_filter_blocks = false;\n  options.optimize_filters_for_hits = true;\n  options.statistics = rocksdb::CreateDBStatistics();\n  options.compression = rocksdb::kZSTD;\n  options.bottommost_compression = rocksdb::kZSTD;\n  options.table_factory.reset(rocksdb::NewBlockBasedTableFactory(table_options));\n  options.create_if_missing = true;\n  options.row_cache = rocksdb::NewLRUCache(4 * 1024 * 1024);\n  options.level_compaction_dynamic_level_bytes = true;\n  options.periodic_compaction_seconds = 30;\n  options.max_subcompactions = 4;\n  options.disable_auto_compactions = false;\n  options.write_buffer_size = 1 * 1024 * 1024;\n  options.max_log_file_size = 64 * 1024 * 1024;\n  options.info_log_level = rocksdb::HEADER_LEVEL;\n  rocksdb::TransactionDBOptions txopts;\n  txopts.transaction_lock_timeout = -1;\n  txopts.default_lock_timeout = -1;\n  rocksdb::TransactionDB* mydb;\n  rocksdb::Status st = rocksdb::TransactionDB::Open(options, txopts, path, &mydb);\n\n  if (!st.ok()) {\n    eos_static_crit(\"Could not open RocksKV store, error: %s\",\n                    st.ToString().c_str());\n    return -1;\n  }\n\n  mPrefix = prefix;\n  transactionDB.reset(mydb);\n  db = transactionDB->GetBaseDB();\n  return 0;\n}\n\nstatic int badStatus(const rocksdb::Status& st)\n{\n  eos_static_crit(\"Unexpected rocksdb status: %s\", st.ToString().c_str());\n  return -1;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nRocksKV::get(const std::string& key, std::string& value)\n/* -------------------------------------------------------------------------- */\n{\n  rocksdb::Status st = db->Get(rocksdb::ReadOptions(), prefix(key), &value);\n\n  if (st.IsNotFound()) {\n    return 1;\n  } else if (!st.ok()) {\n    return badStatus(st);\n  }\n\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nRocksKV::get(const std::string& key, uint64_t& value)\n/* -------------------------------------------------------------------------- */\n{\n  std::string tmp;\n  int ret = this->get(key, tmp);\n\n  if (ret != 0) {\n    return ret;\n  }\n\n  if (!safe_strtoll(tmp.c_str(), value)) {\n    eos_static_crit(\"Expected to find an integer on key %s, instead found %s\",\n                    key.c_str(), tmp.c_str());\n    return -1;\n  }\n\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nRocksKV::put(const std::string& key, const std::string& value)\n/* -------------------------------------------------------------------------- */\n{\n  rocksdb::Status st = db->Put(rocksdb::WriteOptions(), prefix(key), value);\n\n  if (!st.ok()) {\n    return badStatus(st);\n  }\n\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nRocksKV::put(const std::string& key, uint64_t value)\n/* -------------------------------------------------------------------------- */\n{\n  return this->put(key, std::to_string(value));\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nRocksKV::inc(const std::string& key, uint64_t& value)\n/* -------------------------------------------------------------------------- */\n{\n  TransactionPtr tx = startTransaction();\n  std::string tmp;\n  uint64_t initialValue = 0;\n  rocksdb::Status st = tx->GetForUpdate(rocksdb::ReadOptions(), prefix(key),\n                                        &tmp);\n\n  if (!st.ok() && !st.IsNotFound()) {\n    return badStatus(st);\n  }\n\n  if (!safe_strtoll(tmp, initialValue)) {\n    eos_static_crit(\"Attemted to increase a non-numeric value on key %s: %s\",\n                    key.c_str(), tmp.c_str());\n    return -1;\n  }\n\n  st = tx->Put(prefix(key), std::to_string(initialValue + value));\n\n  if (!st.ok()) {\n    return badStatus(st);\n  }\n\n  st = tx->Commit();\n\n  if (!st.ok()) {\n    return badStatus(st);\n  }\n\n  value += initialValue;\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nRocksKV::erase(const std::string& key)\n/* -------------------------------------------------------------------------- */\n{\n  rocksdb::Status st = db->Delete(rocksdb::WriteOptions(), prefix(key));\n\n  if (!st.ok()) {\n    // deleting a non-existent key is not an error!\n    return badStatus(st);\n  }\n\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nRocksKV::get(uint64_t key, std::string& value, const std::string& name_space)\n/* -------------------------------------------------------------------------- */\n{\n  return this->get(buildKey(key, name_space), value);\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nRocksKV::put(uint64_t key, const std::string& value,\n             const std::string& name_space)\n/* -------------------------------------------------------------------------- */\n{\n  return this->put(buildKey(key, name_space), value);\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nRocksKV::get(uint64_t key, uint64_t& value, const std::string& name_space)\n/* -------------------------------------------------------------------------- */\n{\n  return this->get(buildKey(key, name_space), value);\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nRocksKV::put(uint64_t key, uint64_t value, const std::string& name_space)\n/* -------------------------------------------------------------------------- */\n{\n  return this->put(buildKey(key, name_space), value);\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nRocksKV::erase(uint64_t key, const std::string& name_space)\n{\n  return this->erase(buildKey(key, name_space));\n}\n\n/* -------------------------------------------------------------------------- */\nint\n/* -------------------------------------------------------------------------- */\nRocksKV::clean_stores(const std::string& storedir, const std::string& newdb)\n{\n  DIR* dir;\n  struct dirent* ent;\n\n  if ((dir = ::opendir(storedir.c_str())) != NULL) {\n    while ((ent = ::readdir(dir)) != NULL) {\n      std::string entry = ent->d_name;\n\n      if ((entry == \".\") || (entry == \"..\")) {\n        continue;\n      }\n\n      if (entry == newdb) {\n        continue;\n      }\n\n      if (!entry.length()) {\n        continue;\n      }\n\n      struct stat buf;\n\n      std::string dbdir = storedir;\n\n      dbdir += \"/\";\n\n      dbdir += entry;\n\n      if (!stat(dbdir.c_str(), &buf)) {\n        // check if this is a directory\n        if (S_ISDIR(buf.st_mode)) {\n          // cleanup this old directory\n          std::string rmline = \"rm -rf \";\n          rmline += dbdir;\n          (void) !system(rmline.c_str());\n          fprintf(stderr, \"###### cleaning stale cache directory '%s'\\n\",\n                  dbdir.c_str());\n        }\n      }\n    }\n\n    ::closedir(dir);\n  }\n\n  return 0;\n}\n\n#endif // HAVE_ROCKSDB\n"
  },
  {
    "path": "fusex/kv/RocksKV.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file RocksKV.hh\n//! @author Georgios Bitzes CERN\n//! @brief kv persistency class based on rocksdb\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#ifdef HAVE_ROCKSDB\n#include <sys/stat.h>\n#include <sys/types.h>\n#include \"llfusexx.hh\"\n#include \"fusex/fusex.pb.h\"\n#include \"kv/kv.hh\"\n#include <XrdSys/XrdSysPthread.hh>\n#include <memory>\n#include <map>\n#include <event.h>\n#include <rocksdb/db.h>\n#include <rocksdb/table.h>\n#include <rocksdb/statistics.h>\n#include <rocksdb/utilities/transaction_db.h>\n#include <rocksdb/utilities/transaction.h>\n\n//------------------------------------------------------------------------------\n// Implementation of the key value store interface based on redis\n//------------------------------------------------------------------------------\n\nclass RocksKV : public kv\n{\npublic:\n  RocksKV();\n  virtual ~RocksKV();\n\n  int connect(const std::string& prefix, const std::string& path);\n\n  int get(const std::string& key, std::string& value) override;\n  int get(const std::string& key, uint64_t& value) override;\n  int put(const std::string& key, const std::string& value) override;\n  int put(const std::string& key, uint64_t value) override;\n  int inc(const std::string& key, uint64_t& value) override;\n\n  int erase(const std::string& key) override;\n\n  int get(uint64_t key, std::string& value,\n          const std::string& name_space = \"i\") override;\n  int put(uint64_t key, const std::string& value,\n          const std::string& name_space = \"i\") override;\n\n  int get(uint64_t key, uint64_t& value,\n          const std::string& name_space = \"i\") override;\n  int put(uint64_t key, uint64_t value,\n          const std::string& name_space = \"i\") override;\n\n  int erase(uint64_t key, const std::string& name_space = \"i\") override;\n\n  int clean_stores(const std::string& storedir,\n                   const std::string& newdb) override;\n\n  std::string prefix(const std::string& key)\n  {\n    return mPrefix + key;\n  }\n\n  std::string statistics() override\n  {\n    return options.statistics->ToString();\n    //return std::string(\"##### cache size is \") + std::to_string(table_options.block_cache->GetUsage());\n  }\n\n  using TransactionPtr = std::unique_ptr<rocksdb::Transaction>;\nprivate:\n  std::unique_ptr<rocksdb::TransactionDB> transactionDB;\n  rocksdb::DB* db; // owned by transactionDB\n  std::string mPrefix;\n\n  rocksdb::Options options;\n  rocksdb::BlockBasedTableOptions table_options;\n\n  TransactionPtr startTransaction()\n  {\n    rocksdb::WriteOptions opts;\n    return TransactionPtr(transactionDB->BeginTransaction(opts));\n  }\n\n};\n\n#endif // HAVE_ROCKSDB\n"
  },
  {
    "path": "fusex/kv/kv.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file kv.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief kv persistency class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_KV_HH_\n#define FUSE_KV_HH_\n\n#include <sys/stat.h>\n#include <sys/types.h>\n#include \"llfusexx.hh\"\n#include \"fusex/fusex.pb.h\"\n#include \"misc/longstring.hh\"\n#include <XrdSys/XrdSysPthread.hh>\n#include <memory>\n#include <map>\n#include <event.h>\n\n\n//------------------------------------------------------------------------------\n// Interface to a key-value store implementation.\n//------------------------------------------------------------------------------\n\nclass kv : public XrdSysMutex\n{\npublic:\n\n  kv() { }\n\n  virtual ~kv() { }\n\n  virtual int get(const std::string& key, std::string& value) = 0;\n  virtual int get(const std::string& key, uint64_t& value) = 0;\n  virtual int put(const std::string& key, const std::string& value) = 0;\n  virtual int put(const std::string& key, uint64_t value) = 0;\n  virtual int inc(const std::string& key, uint64_t& value) = 0;\n\n  virtual int erase(const std::string& key) = 0;\n\n  virtual int get(uint64_t key, std::string& value,\n                  const std::string& name_space = \"i\") = 0;\n  virtual int put(uint64_t key, const std::string& value,\n                  const std::string& name_space = \"i\") = 0;\n\n  virtual int get(uint64_t key, uint64_t& value,\n                  const std::string& name_space = \"i\") = 0;\n  virtual int put(uint64_t key, uint64_t value,\n                  const std::string& name_space = \"i\") = 0;\n\n  virtual int erase(uint64_t key, const std::string& name_space = \"i\") = 0;\n\n  virtual int clean_stores(const std::string& storedir,\n                           const std::string& newdb) = 0 ;\n  virtual std::string statistics() = 0;\n\nprotected:\n\n  std::string buildKey(uint64_t key, const std::string& name_space)\n  {\n    char buffer[128];\n    longstring::unsigned_to_decimal(key, buffer);\n    std::string sbuf(buffer);\n\n    if (!name_space.empty()) {\n      sbuf = name_space + \":\" + sbuf;\n    }\n\n    return sbuf;\n  }\n\n};\n#endif /* FUSE_KV_HH_ */\n"
  },
  {
    "path": "fusex/md/kernelcache.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file kernelcache.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief kernel cache interface\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_KERNELCACHE_HH_\n#define FUSE_KERNELCACHE_HH_\n\n#include \"llfusexx.hh\"\n#include \"eosfuse.hh\"\n#include \"common/Logging.hh\"\n\nclass kernelcache\n{\npublic:\n\n  static void inval_inode(fuse_ino_t inode, bool isfile = false)\n  {\n    eos_static_debug(\"begin: ino=%08llx\", inode);\n#ifdef USE_FUSE3\n    int rc = fuse_lowlevel_notify_inval_inode(EosFuse::Instance().Session(),\n             inode, isfile ? 0 : -1, 0);\n#else\n    int rc = fuse_lowlevel_notify_inval_inode(EosFuse::Instance().Channel(),\n             inode, isfile ? 0 : -1, 0);\n#endif\n    eos_static_debug(\"end: ino=%08llx rc=%d\", inode, rc);\n  }\n\n  static void inval_entry(fuse_ino_t parent_inode, const std::string name)\n  {\n    eos_static_debug(\"begin: ino=%08llx name=%s\", parent_inode, name.c_str());\n#ifdef USE_FUSE3\n    int rc = fuse_lowlevel_notify_inval_entry(EosFuse::Instance().Session(),\n             parent_inode, name.c_str(), name.length());\n#else\n    int rc = fuse_lowlevel_notify_inval_entry(EosFuse::Instance().Channel(),\n             parent_inode, name.c_str(), name.length());\n#endif\n    eos_static_debug(\"end: ino=%08llx name=%s rc=%d\", parent_inode, name.c_str(),\n                     rc);\n  }\n};\n#endif /* FUSE_KERNCALCACHE_HH_ */\n"
  },
  {
    "path": "fusex/md/md.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file md.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief meta data handling class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#include <assert.h>\n#include <iostream>\n#include <memory>\n#include <thread>\n#include <optional>\n#include <vector>\n#include <google/protobuf/util/json_util.h>\n#include \"cap/cap.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Path.hh\"\n#include \"common/StackTrace.hh\"\n#include \"common/StringConversion.hh\"\n#include <common/StringUtils.hh>\n#include \"eosfuse.hh\"\n#include \"kv/kv.hh\"\n#include \"md/md.hh\"\n#include \"md/kernelcache.hh\"\n#include \"misc/MacOSXHelper.hh\"\n#include \"misc/longstring.hh\"\n\n/* -------------------------------------------------------------------------- */\nmetad::metad() : last_heartbeat(0), mdflush(0), mCb(0),\n  mdqueue_max_backlog(1000), z_ctx(0), z_socket(0)\n{\n  // make a mapping for inode 1, it is re-loaded afterwards in init '/'\n  {\n    inomap.insert(1, 1);\n  }\n  shared_md md = std::make_shared<mdx>(1);\n  (*md)()->set_nlink(1);\n  (*md)()->set_mode(S_IRWXU | S_IRWXG | S_IRWXO | S_IFDIR);\n  (*md)()->set_name(\":root:\");\n  (*md)()->set_pid(1);\n  stat.inodes_inc();\n  stat.inodes_ever_inc();\n  set_is_visible(0);\n  mdbackend = 0;\n  mdmap.insertTS(1, md);\n}\n\n/* -------------------------------------------------------------------------- */\nmetad::~metad()\n{\n  if (z_socket) {\n    delete z_socket;\n  }\n\n  if (z_ctx) {\n    delete z_ctx;\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::init(backend* _mdbackend)\n{\n  mdbackend = _mdbackend;\n  std::string mdstream;\n  // load the root node\n  fuse_id fuseid;\n  XrdSysMutexHelper mLock(mdmap);\n  update(fuseid, mdmap[1], \"\", true);\n  mdmap.init(EosFuse::Instance().getKV());\n  dentrymessaging = false;\n  writesizeflush = false;\n  appname = false;\n  mdquery = false;\n  serverversion = \"<unkown>\";\n}\n\n/* -------------------------------------------------------------------------- */\nint\nmetad::connect(std::string zmqtarget, std::string zmqidentity,\n               std::string zmqname, std::string zmqclienthost,\n               std::string zmqclientuuid)\n{\n  set_zmq_wants_to_connect(1);\n  std::lock_guard<std::mutex> connectionMutex(zmq_socket_mutex);\n\n  if (z_socket && z_socket->handle() && (zmqtarget != zmq_target)) {\n    // delete the exinsting ZMQ connection\n    delete z_socket;\n    delete z_ctx;\n  }\n\n  if (zmqtarget.length()) {\n    zmq_target = zmqtarget;\n  }\n\n  if (zmqidentity.length()) {\n    zmq_identity = zmqidentity;\n  }\n\n  if (zmqname.length()) {\n    zmq_name = zmqname;\n  }\n\n  if (zmqclienthost.length()) {\n    zmq_clienthost = zmqclienthost;\n  }\n\n  if (zmqclientuuid.length()) {\n    zmq_clientuuid = zmqclientuuid;\n  }\n\n  eos_static_info(\"metad connect %s as %s %d\",\n                  zmq_target.c_str(), zmq_identity.c_str(), zmq_identity.length());\n  z_ctx = new zmq::context_t(1);\n  z_socket = new zmq::socket_t(*z_ctx, ZMQ_DEALER);\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wdeprecated-declarations\"\n#if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 1)\n  z_socket->set(zmq::sockopt::routing_id, zmq_identity);\n#else\n  z_socket->setsockopt(ZMQ_IDENTITY, zmq_identity.c_str(), zmq_identity.length());\n#endif\n  z_socket->set(zmq::sockopt::tcp_keepalive, 1);\n  z_socket->set(zmq::sockopt::tcp_keepalive_idle, 90);\n  z_socket->set(zmq::sockopt::tcp_keepalive_intvl, 90);\n#pragma GCC diagnostic pop\n\n  while (1) {\n    try {\n      z_socket->connect(zmq_target);\n      int linger = 0;\n      z_socket->set(zmq::sockopt::linger, linger);\n      eos_static_notice(\"connected to %s\", zmq_target.c_str());\n      break;\n    } catch (zmq::error_t& e) {\n      if (e.num() != EINTR) {\n        eos_static_err(\"msg=\\\"%s\\\" rc=%d\", e.what(), e.num());\n        return e.num();\n      }\n\n      eos_static_err(\"msg=\\\"%s\\\" rc=%d\", e.what(), e.num());\n    }\n  }\n\n  if (zmqclientuuid.length()) {\n    mdbackend->set_clientuuid(zmq_clientuuid);\n  }\n\n  set_zmq_wants_to_connect(0);\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nmetad::shared_md\nmetad::lookup(fuse_req_t req, fuse_ino_t parent, const char* name)\n{\n  eos_static_info(\"ino=%#lx name=%s\", parent, name);\n  // --------------------------------------------------\n  // STEP 1 : retrieve the required parent MD\n  // --------------------------------------------------\n  shared_md pmd = get(req, parent, \"\", false);\n  shared_md md;\n\n  if ((*pmd)()->id() == parent) {\n    XrdSysMutexHelper mLock(pmd->Locker());\n    fuse_ino_t inode = 0; // inode referenced by parent + name\n\n    // self lookup required for NFS exports\n    if (!strcmp(name, \".\")) {\n      return pmd;\n    }\n\n    // parent lookup required for NFS exports\n    if (!strcmp(name, \"..\")) {\n      const uint64_t pmd_pid = (*pmd)()->pid();\n      mLock.UnLock();\n      shared_md ppmd = get(req, pmd_pid, \"\", false);\n      return ppmd;\n    }\n\n    // --------------------------------------------------\n    // STEP 2: check if we hold a cap for that directory\n    // --------------------------------------------------\n    if (pmd->cap_count() && !pmd->needs_refresh()) {\n      // --------------------------------------------------\n      // if we have a cap and we listed this directory, we trust the child information\n      // --------------------------------------------------\n      if (pmd->local_children().count(\n            eos::common::StringConversion::EncodeInvalidUTF8(name))) {\n        inode = pmd->local_children().at(\n                  eos::common::StringConversion::EncodeInvalidUTF8(name));\n      } else {\n        if (pmd->local_enoent().count(name)) {\n          md = std::make_shared<mdx>();\n          (*md)()->set_err(ENOENT);\n          return md;\n        }\n\n        // if we are still having the creator MD record, we can be sure, that we know everything about this directory\n        if ((*pmd)()->creator() ||\n            ((*pmd)()->type() == (*pmd)()->MDLS)) {\n          // no entry - TODO return a NULLMD object instead of creating it all the time\n          md = std::make_shared<mdx>();\n          (*md)()->set_err((*pmd)()->err());\n          return md;\n        }\n\n        if (pmd->get_todelete().count(eos::common::StringConversion::EncodeInvalidUTF8(\n                                        name))) {\n          // if this has been deleted, we just say this\n          md = std::make_shared<mdx>();\n          (*md)()->set_err((*pmd)()->err());\n\n          if (EOS_LOGS_DEBUG) {\n            eos_static_debug(\"in deletion list %016lx name=%s\", (*pmd)()->id(), name);\n          }\n\n          return md;\n        }\n      }\n    } else {\n      // --------------------------------------------------\n      // if we don't have a cap, get will result in an MGM call anyway\n      // --------------------------------------------------\n    }\n\n    // --------------------------------------------------\n    // try to get the meta data record\n    // --------------------------------------------------\n    const std::string pfullpath = (*pmd)()->fullpath();\n    mLock.UnLock();\n    md = get(req, inode, \"\", false, pmd, name);\n\n    if (md) {\n      md->Locker().Lock();\n      md->store_fullpath(pfullpath, name);\n      md->Locker().UnLock();\n    } else {\n      md = std::make_shared<mdx>();\n      (*md)()->set_err(ENOENT);\n    }\n  } else {\n    // --------------------------------------------------\n    // no md available\n    // --------------------------------------------------\n    md = std::make_shared<mdx>();\n    (*md)()->set_err((*pmd)()->err());\n  }\n\n  return md;\n}\n\n/* -------------------------------------------------------------------------- */\nint\nmetad::forget(fuse_req_t req, fuse_ino_t ino, int nlookup)\n{\n  shared_md md;\n  uint64_t pino = 0;\n\n  if (!mdmap.retrieveTS(ino, md)) {\n    return ENOENT;\n  }\n\n  {\n    // we should lock this, but since we are in a kernel call-back function, we skip it and\n    // accept an unsafe access on pid() later\n    //    XrdSysMutexHelper mLock(md->Locker());\n    if (!(*md)()->id()) {\n      return EAGAIN;\n    }\n\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"count=%d(-%d) - ino=%#lx\", md->lookup_is(), nlookup, ino);\n    }\n\n    if (!md->lookup_dec(nlookup)) {\n      eos_static_debug(\"count=%d(-%d) - ino=%#lx\", md->lookup_is(), nlookup, ino);\n      return EAGAIN;\n    }\n\n    pino = (*md)()->pid();\n  }\n\n  if (has_flush(ino)) {\n    eos_static_debug(\"flush - ino=%016x\", ino);\n    return 0;\n  }\n\n  if ((pino > 1) && (ino != pino)) {\n    // this does not make sense for the mount directory (inode 1)\n    shared_md pmd;\n\n    if (!mdmap.retrieveTS(pino, pmd)) {\n      return ENOENT;\n    }\n\n    if (pmd->cap_count()) {\n      eos_static_debug(\"caps %d - ino=%016x\", pmd->cap_count(), ino);\n      return 0;\n    }\n\n    if (pmd->opendir_is()) {\n      eos_static_debug(\"opendir %d - ino=%016x\", pmd->opendir_is(), ino);\n      return 0;\n    }\n  } else {\n    // we don't remove the mount point\n    return 0;\n  }\n\n  if (EOS_LOGS_DEBUG) {\n    XrdSysMutexHelper mLock(md->Locker());\n    eos_static_debug(\"delete md object - ino=%#lx name=%s\", ino,\n                     (*md)()->name().c_str());\n  }\n\n  if (mdmap.eraseTS(ino)) {\n    stat.inodes_dec();\n  }\n\n  // - we currently don't forget old mappings, because it creates race conditions with overlaying caps\n  //  PUTMEBACK-LATER: inomap.erase_bwd(ino);\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::mdx::convert(struct fuse_entry_param& e, double lifetime)\n{\n  const char* k_mdino = \"sys.eos.mdino\";\n  const char* k_fifo = \"sys.eos.fifo\";\n  auto attrMap = (*this)()->attr();\n  e.ino = (*this)()->id();\n  e.attr.st_dev = 0;\n  e.attr.st_ino = (*this)()->id();\n  e.attr.st_mode = (*this)()->mode();\n  e.attr.st_nlink = (*this)()->nlink();\n\n  if (attrMap.count(k_mdino)) {\n    uint64_t mdino = std::stoull(attrMap[k_mdino]);\n    uint64_t local_ino = EosFuse::Instance().mds.inomap.forward(mdino);\n    shared_md tmd = EosFuse::Instance().mds.getlocal(NULL, local_ino);\n\n    if (!(*tmd)()->id()) {\n      local_ino = mdino;\n      e.attr.st_nlink = 2;\n      eos_static_err(\"converting hard-link %s target inode %#lx remote %#lx not in cache, nlink set to %d\",\n                     (*this)()->name().c_str(), local_ino, mdino, e.attr.st_nlink);\n    } else {\n      if (EOS_LOGS_DEBUG) {\n        eos_static_debug(\"hlnk convert name=%s id=%#lx target local_ino=%#lx nlink0=\",\n                         (*this)()->name().c_str(), (*this)()->id(), local_ino, (*tmd)()->nlink());\n      }\n\n      e.attr.st_nlink = (*tmd)()->nlink();\n    }\n\n    e.ino = e.attr.st_ino = local_ino;\n  }\n\n  if (attrMap.count(k_fifo)) {\n    e.attr.st_mode &= !S_IFREG;\n    e.attr.st_mode |= S_IFIFO;\n  }\n\n  e.attr.st_uid = (*this)()->uid();\n  e.attr.st_gid = (*this)()->gid();\n  e.attr.st_rdev = 0;\n  e.attr.st_size = (*this)()->size();\n  e.attr.st_blksize = 4096;\n  e.attr.st_blocks = (e.attr.st_size + 511) / 512;\n  e.attr.st_atime = (*this)()->atime();\n  e.attr.st_mtime = (*this)()->mtime();\n  e.attr.st_ctime = (*this)()->ctime();\n  e.attr.MTIMESPEC.tv_sec = (*this)()->mtime();\n  e.attr.MTIMESPEC.tv_nsec = (*this)()->mtime_ns();\n  e.attr.CTIMESPEC.tv_sec = (*this)()->ctime();\n  e.attr.CTIMESPEC.tv_nsec = (*this)()->ctime_ns();\n\n  if (!e.attr.st_atime) {\n    // if 0 atime, we adopt MTIME as ATIME\n    e.attr.ATIMESPEC.tv_sec = e.attr.MTIMESPEC.tv_sec;\n    e.attr.ATIMESPEC.tv_nsec = e.attr.MTIMESPEC.tv_nsec;\n  } else {\n    e.attr.ATIMESPEC.tv_sec = (*this)()->atime();\n    e.attr.ATIMESPEC.tv_nsec = (*this)()->atime_ns();\n  }\n\n  if (EosFuse::Instance().Config().options.md_kernelcache) {\n    e.attr_timeout = lifetime;\n    e.entry_timeout = (lifetime > 30) ? 30 : lifetime;\n  } else {\n    e.attr_timeout = 0;\n    e.entry_timeout = 0;\n  }\n\n  if (EosFuse::Instance().Config().options.overlay_mode) {\n    e.attr.st_mode |= EosFuse::Instance().Config().options.overlay_mode;\n  }\n\n  if (S_ISDIR(e.attr.st_mode)) {\n    if (!EosFuse::Instance().Config().options.show_tree_size) {\n      // show 4kb directory size\n      e.attr.st_size = 4096;\n      e.attr.st_blocks = (e.attr.st_size + 511) / 512;\n    }\n\n    // we mask this bits for the moment\n    e.attr.st_mode &= (~S_ISGID);\n    e.attr.st_mode &= (~S_ISUID);\n  }\n\n  if (S_ISDIR(e.attr.st_mode)) {\n    if (!EosFuse::Instance().Config().options.show_tree_size) {\n      // show 4kb directory size\n      e.attr.st_size = 4096;\n    }\n\n    // we mask this bits for the moment\n    e.attr.st_mode &= (~S_ISGID);\n    e.attr.st_mode &= (~S_ISUID);\n  }\n\n  if (S_ISLNK(e.attr.st_mode)) {\n    e.attr.st_size = (*this)()->target().size();\n  }\n\n  e.generation = 1;\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\nmetad::mdx::dump()\n{\n  char sout[16384];\n  snprintf(sout, sizeof(sout),\n           \"ino=%#lx dev=%#lx mode=%#o nlink=%u uid=%05u gid=%05u rdev=%#lx \"\n           \"size=%llu bsize=%lu blocks=%llu atime=%lu.%lu mtime=%lu.%lu ctime=%lu.%lu\",\n           (unsigned long)(*this)()->id(), (unsigned long)0,\n           (unsigned int)(*this)()->mode(),\n           (unsigned int)(*this)()->nlink(),\n           (unsigned int)(*this)()->uid(), (unsigned int)(*this)()->gid(),\n           (unsigned long)0,\n           (unsigned long long)(*this)()->size(), (unsigned long) 4096,\n           (unsigned long long)(*this)()->size() / 512,\n           (unsigned long)(*this)()->atime(), (unsigned long)(*this)()->atime_ns(),\n           (unsigned long)(*this)()->mtime(), (unsigned long)(*this)()->mtime_ns(),\n           (unsigned long)(*this)()->ctime(), (unsigned long)(*this)()->ctime_ns());\n  return sout;\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\nmetad::mdx::dump(struct fuse_entry_param& e)\n{\n  char sout[16384];\n  snprintf(sout, sizeof(sout),\n           \"ino=%#lx dev=%#lx mode=%#o nlink=%u uid=%05u gid=%05u rdev=%#lx \"\n           \"size=%llu bsize=%lu blocks=%llu atime=%lu.%lu mtime=%lu.%lu ctime=%lu.%lu \"\n           \"attr-timeout=%lu entry-timeout=%lu\",\n           (unsigned long) e.attr.st_ino, (unsigned long) e.attr.st_dev,\n           (unsigned int) e.attr.st_mode, (unsigned int) e.attr.st_nlink,\n           (unsigned int) e.attr.st_uid, (unsigned int) e.attr.st_gid,\n           (unsigned long) e.attr.st_rdev,\n           (unsigned long long) e.attr.st_size, (unsigned long) e.attr.st_blksize,\n           (unsigned long long) e.attr.st_blocks,\n           (unsigned long) e.attr.ATIMESPEC.tv_sec,\n           (unsigned long) e.attr.ATIMESPEC.tv_nsec,\n           (unsigned long) e.attr.MTIMESPEC.tv_sec,\n           (unsigned long) e.attr.MTIMESPEC.tv_nsec,\n           (unsigned long) e.attr.CTIMESPEC.tv_sec,\n           (unsigned long) e.attr.CTIMESPEC.tv_nsec,\n           (unsigned long) e.attr_timeout,\n           (unsigned long) e.entry_timeout);\n  return sout;\n}\n\n/* -------------------------------------------------------------------------- */\nbool\nmetad::map_children_to_local(shared_md pmd)\n{\n  bool ret = true;\n  // map a remote listing to a local one\n  std::set<std::string> names;\n  std::vector<std::string> names_to_delete;\n\n  // we always merge remote contents, for changes our cap will be dropped\n  for (auto map = (*pmd)()->children().begin(); map != (*pmd)()->children().end();\n       ++map) {\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"translate %s [%#lx]\",\n                       eos::common::StringConversion::EncodeInvalidUTF8(map->first).c_str(),\n                       map->second);\n    }\n\n    uint64_t remote_ino = map->second;\n    uint64_t local_ino = inomap.forward(remote_ino);\n\n    if (EosFuse::Instance().Config().options.hide_versions &&\n        EosFuse::Instance().mds.supports_hideversion()) {\n      // check for version prefixes\n      if (map->first.substr(0,\n                            strlen(EOS_COMMON_PATH_VERSION_FILE_PREFIX)) ==\n          EOS_COMMON_PATH_VERSION_FILE_PREFIX) {\n        // check if there is actually a 'babysitting' reference file for this version, if now we display it!\n        std::string nvfile = map->first.substr(strlen(\n            EOS_COMMON_PATH_VERSION_FILE_PREFIX));\n        eos_static_info(\"hide %d:%d %s\",\n                        EosFuse::Instance().Config().options.hide_versions,\n                        EosFuse::Instance().mds.supports_hideversion(),\n                        nvfile.c_str());\n\n        if ((*pmd)()->children().count(nvfile)) {\n          continue;\n        }\n      }\n    }\n\n    // skip entries we already know, if we don't have the mapping we have forgotten already this one\n    if (pmd->local_children().count(\n          eos::common::StringConversion::EncodeInvalidUTF8(map->first)) && local_ino) {\n      continue;\n    }\n\n    // skip entries which are the deletion list\n    if (pmd->get_todelete().count(eos::common::StringConversion::EncodeInvalidUTF8(\n                                    map->first))) {\n      continue;\n    }\n\n    shared_md md;\n\n    if (!mdmap.retrieveTS(local_ino, md)) {\n      local_ino = remote_ino;\n      inomap.insert(remote_ino, local_ino);\n      stat.inodes_inc();\n      stat.inodes_ever_inc();\n      md = std::make_shared<mdx>();\n      mdmap.insertTS(local_ino, md);\n    }\n\n    if (EOS_LOGS_DEBUG)\n      eos_static_debug(\"store-lookup r-ino %016lx <=> l-ino %016lx\", remote_ino,\n                       local_ino);\n\n    pmd->local_children()[eos::common::StringConversion::EncodeInvalidUTF8(\n                            map->first)] = local_ino;\n  }\n\n  if (EOS_LOGS_DEBUG) {\n    for (auto map = pmd->local_children().begin();\n         map != pmd->local_children().end(); ++map) {\n      eos_static_debug(\"listing: %s [%#lx]\", map->first.c_str(), map->second);\n    }\n  }\n\n  (*pmd)()->set_nchildren(pmd->local_children().size());\n  (*pmd)()->mutable_children()->clear();\n  return ret;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nmetad::wait_backlog(shared_md md)\n/* -------------------------------------------------------------------------- */\n{\n  // ------------------------------------------------------------------------\n  // wait_backlog should be called with md and mdflush locked.\n  //\n  // md:      caller holds a lock on the mdx\n  //          (order for lock acquisition is mdx, then mdflush). This should\n  //          be the only mdx lock the caller holds.\n  //\n  // if the mdcflush thread is currently attempting to process md, we will\n  // unlock, wait and then relock the mdx to avoid deadlock.\n  // ------------------------------------------------------------------------\n  const uint64_t id = (*md)()->id();\n  mdbacklogqueue.push_back(&id);\n\n  do {\n    while ((mdbacklogqueue.front() != &id) &&\n           mdqueue.size() < mdqueue_max_backlog) {\n      mdflush.WaitMS(25);\n    }\n\n    while (mdqueue.size() >= mdqueue_max_backlog) {\n      bool rlck = false;\n      if (id && id == mdqueue_current) {\n        rlck = true;\n        md->Locker().UnLock();\n      }\n\n      mdflush.WaitMS(25);\n      if (rlck) {\n        // keep acquire order, mdx then mdflush\n        mdflush.UnLock();\n        md->Locker().Lock();\n        mdflush.Lock();\n      }\n    }\n  } while(mdbacklogqueue.front() != &id);\n\n  mdbacklogqueue.pop_front();\n  if (mdbacklogqueue.size()) mdflush.Broadcast();\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nmetad::wait_upstream(fuse_req_t req,\n                     fuse_ino_t ino)\n/* -------------------------------------------------------------------------- */\n{\n  shared_md md;\n\n  if (mdmap.retrieveTS(ino, md)) {\n    uint64_t id;\n    if (md && (id = (*md)()->id()) ) {\n\n      mdflush.Lock();\n      auto lastit = mdflushqueue.rbegin();\n      // find youngest entry\n      if (mdqueue.count(id)) {\n        lastit = mdflushqueue.rend();\n      }\n\n      auto entry = std::find_if(mdflushqueue.rbegin(), lastit,\n                               [id](const flushentry &fe) {\n                      return fe.id() == id;\n                   });\n\n      if (entry != lastit) {\n        mdqwaiter w;\n        entry->registerwaiter(&w);\n        mdflush.UnLock();\n        w.wait();\n      } else {\n        mdflush.UnLock();\n      }\n    }\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nmetad::shared_md\n/* -------------------------------------------------------------------------- */\nmetad::getlocal(fuse_req_t req,\n                fuse_ino_t ino)\n{\n  eos_static_info(\"ino=%1llx\", ino);\n  shared_md md;\n\n  if (!mdmap.retrieveTS(ino, md)) {\n    md = std::make_shared<mdx>();\n    (*md)()->set_err(ENOENT);\n  }\n\n  return md;\n}\n\n\n/* -------------------------------------------------------------------------- */\nstd::string\n/* -------------------------------------------------------------------------- */\nmetad::getpath(fuse_ino_t ino)\n{\n  eos_static_info(\"ino=%1llx\", ino);\n  shared_md md;\n\n  if (!mdmap.retrieveTS(ino, md)) {\n    return \"\";\n  }\n\n  // TODO: we don't take md lock here; introduce a more robust approach.\n  // Currently return via the c-string to reduce disturbance of the source str\n  return (*md)()->fullpath().c_str();\n}\n\n/* -------------------------------------------------------------------------- */\nmetad::shared_md\n/* -------------------------------------------------------------------------- */\nmetad::get(fuse_req_t req,\n           fuse_ino_t ino,\n           std::string authid,\n           bool listing,\n           shared_md pmd,\n           const char* name,\n           bool readdir)\n{\n  eos_static_info(\"ino=%#lx pino=%#lx name=%s listing=%d\", ino,\n                  pmd ? (*pmd)()->id() : 0, name, listing);\n  shared_md md;\n\n  // Acquire mutex for inode (if inode is non zero).\n  // Avoid race between testing any already-held md and fetching\n  // a new one from the server, and also a race within apply() when\n  // fetching a listing of dir including child entries the md is\n  // first created as type MD and then upgraded to MDLS after processing.\n  auto getLockHelper = GetMtxAcquire(ino);\n\n  if (ino) {\n    if (!mdmap.retrieveTS(ino, md)) {\n      md = std::make_shared<mdx>();\n      (*md)()->set_md_ino(inomap.backward(ino));\n    } else {\n      if (ino != 1) {\n        // we need this to refetch a hard link target which was removed server side\n        XrdSysMutexHelper mLock(md->Locker());\n        (*md)()->set_md_ino(ino);\n      }\n    }\n\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"MD:\\n%s\", (!md) ? \"<empty>\" : dump_md(md).c_str());\n    }\n  } else {\n    // -------------------------------------------------------------------------\n    // this happens if we get asked for a child, which was never listed before\n    // -------------------------------------------------------------------------\n    md = std::make_shared<mdx>();\n  }\n\n  if (!md || !(*md)()->id()) {\n    // -------------------------------------------------------------------------\n    // there is no local meta data available, this can only be found upstream\n    // -------------------------------------------------------------------------\n  } else {\n    // -------------------------------------------------------------------------\n    // there is local meta data, we have to decide if we can 'trust' it, or we\n    // need to refresh it from upstream  - TODO !\n    // -------------------------------------------------------------------------\n    if (readdir && !listing) {\n      eos_static_info(\"returning opendir(readdir) entry\");\n      return md;\n    }\n\n    if (pmd && (pmd->cap_count() || (*pmd)()->creator()) && !pmd->needs_refresh() &&\n        !md->needs_refresh()) {\n      eos_static_info(\"returning cap entry\");\n      return md;\n    } else {\n      eos_static_info(\"pmd=%#lx cap-cnt=%d\", pmd ? (*pmd)()->id() : 0,\n                      pmd ? pmd->cap_count() : 0);\n      uint64_t md_pid = 0;\n      mode_t md_mode = 0;\n      {\n        XrdSysMutexHelper mLock(md->Locker());\n\n        if (((!listing) || (listing && (*md)()->type() == (*md)()->MDLS)) &&\n            (*md)()->md_ino() &&\n            md->cap_count() && !md->needs_refresh()) {\n          eos_static_info(\"returning cap entry via parent lookup cap-count=%d\",\n                          md->cap_count());\n\n          if (EOS_LOGS_DEBUG) {\n            eos_static_debug(\"MD:\\n%s\", dump_md(md, false).c_str());\n          }\n\n          return md;\n        }\n\n        md_pid = (*md)()->pid();\n        md_mode = (*md)()->mode();\n      }\n\n      if (!S_ISDIR(md_mode)) {\n        // files are covered by the CAP of the parent, so if there is a cap\n        // on the parent we can return this entry right away\n        if (mdmap.retrieveTS(md_pid, pmd)) {\n          if (pmd && (*pmd)()->id() && pmd->cap_count() && !md->needs_refresh()) {\n            return md;\n          }\n        }\n      }\n    }\n\n    XrdSysMutexHelper mLock(md->Locker());\n\n    if (((*md)()->id() != 1) && !(*md)()->pid() && !md->needs_refresh()) {\n      // this must have been generated locally, we return this entry\n      eos_static_info(\"returning generated entry\");\n\n      if (EOS_LOGS_DEBUG) {\n        eos_static_debug(\"MD:\\n%s\", dump_md(md, false).c_str());\n      }\n\n      return md;\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  // we will get meta data from upstream\n  // ---------------------------------------------------------------------------\n  int rc = 0; // response code to a backend getMD call\n  std::vector<eos::fusex::container> contv; // response container\n  int thecase = 0;\n\n  if (ino == 1)\n    // -------------------------------------------------------------------------\n    // CASE 1: root mount\n    // -------------------------------------------------------------------------\n  {\n    thecase = 1;\n    // -------------------------------------------------------------------------\n    // the root inode is the only one we get by full path, all the others\n    // go by parent-ino + name or inode\n    // -------------------------------------------------------------------------\n    std::string root_path = \"/\";\n    // request the root meta data\n    rc = mdbackend->getMD(req, root_path, contv, listing, authid);\n    // set ourselfs as parent of root since we might mount\n    // a remote directory != '/'\n    (*md)()->set_pid(1);\n  } else if (!ino)\n    // -------------------------------------------------------------------------\n    // CASE 2: by remote parent inode + name\n    // -------------------------------------------------------------------------\n  {\n    thecase = 2;\n\n    if (pmd) {\n      // prevent resyning when we have deletions pending\n      /*while (1)\n      {\n        XrdSysMutexHelper mdLock(pmd->Locker());\n        if (pmd->WaitSync(1))\n        {\n          if (pmd->get_todelete().size())\n            continue;\n\n          break;\n        }\n      }\n       */\n      pmd->Locker().Lock();\n      uint64_t pmd_ino = (*pmd)()->md_ino();\n      pmd->Locker().UnLock();\n\n      if (pmd_ino) {\n        rc = mdbackend->getMD(req, pmd_ino, name, contv, listing, authid);\n      } else {\n        rc = ENOENT;\n      }\n    } else {\n      rc = ENOENT;\n    }\n  } else\n    // -------------------------------------------------------------------------\n    // CASE 3: by remote inode\n    // -------------------------------------------------------------------------\n  {\n    thecase = 3;\n    XrdSysMutexHelper mLock(md->Locker());\n\n    if ((*md)()->md_ino()) {\n      /*\n      // prevent resyncing when we have deletions pending\n      while (1)\n      {\n        XrdSysMutexHelper mdLock(md->Locker());\n        if (md->WaitSync(1))\n        {\n          if (md->get_todelete().size())\n            continue;\n\n          break;\n        }\n      }\n       */\n      eos_static_info(\"ino=%016lx type=%d\", (*md)()->md_ino(), (*md)()->type());\n      rc = mdbackend->getMD(req, (*md)()->md_ino(),\n                            listing ? (((*md)()->type() != (*md)()->MDLS)\n                                       ? 0 : (*md)()->clock()) : (*md)()->clock(),\n                            contv, listing, authid);\n    } else {\n      if ((*md)()->id()) {\n        // that can be a locally created entry which is not yet upstream\n        rc = 0;\n\n        if (EOS_LOGS_DEBUG) {\n          eos_static_debug(\"MD:\\n%s\", dump_md(md).c_str());\n        }\n\n        return md;\n      } else {\n        rc = ENOENT;\n      }\n    }\n  }\n\n  if (!rc) {\n    // -------------------------------------------------------------------------\n    // we need to store all response data and eventually create missing\n    // hierarchical entries\n    // -------------------------------------------------------------------------\n    eos_static_debug(\"apply vector=%d\", contv.size());\n\n    for (auto it = contv.begin(); it != contv.end(); ++it) {\n      if (it->ref_inode_()) {\n        if (ino) {\n          // the response contains the remote inode according to the request\n          inomap.insert(it->ref_inode_(), ino);\n        }\n\n        uint64_t l_ino;\n\n        // store the retrieved meta data blob\n        if (!(l_ino = apply(req, *it, listing))) {\n          eos_static_crit(\"msg=\\\"failed to apply response\\\"\");\n        } else {\n          ino = l_ino;\n        }\n      } else {\n        // we didn't get the md back\n      }\n    }\n\n    // if the md record was returned, it is accessible after the apply function\n    // attached it. We should also attach to the parent to be able to add\n    // a not yet published child entry at the parent.\n\n    if (md) {\n      std::string md_name;\n      mdmap.retrieveWithParentTS(ino, md, pmd, md_name);\n      eos_static_info(\"ino=%08llx pino=%08llx name=%s listing=%d\", ino,\n                      pmd ? (*pmd)()->id() : 0, name, listing);\n\n      switch (thecase) {\n      case 1:\n        // nothing to do\n        break;\n\n      case 2: {\n        // we make sure, that the meta data record is attached to the local parent\n        if (pmd && (*pmd)()->id()) {\n          std::string encname = eos::common::StringConversion::EncodeInvalidUTF8\n                                (md_name);\n          XrdSysMutexHelper mLock(pmd->Locker());\n\n          if (!pmd->local_children().count(encname) &&\n              !pmd->get_todelete().count(encname) &&\n              !md->deleted()) {\n            eos_static_info(\"attaching %s [%#lx] to %s [%#lx]\",\n                            encname.c_str(), ino,\n                            (*pmd)()->name().c_str(), (*pmd)()->id());\n            // persist this hierarchical dependency\n            pmd->local_children()[encname] = ino;\n            update(req, pmd, \"\", true);\n          }\n        }\n\n        break;\n      }\n\n      case 3:\n        break;\n      }\n    } else {\n      rc = ENOENT;\n    }\n  }\n\n  if (rc) {\n    shared_md md = std::make_shared<mdx>();\n    (*md)()->set_err(rc);\n\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"MD:\\n%s\", dump_md(md).c_str());\n    }\n\n    return md;\n  }\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"MD:\\n%s\", dump_md(md).c_str());\n  }\n\n  return md;\n}\n\n/* -------------------------------------------------------------------------- */\nuint64_t\nmetad::insert(metad::shared_md md, std::string authid)\n{\n  {\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"%s\", dump_md(md, false).c_str());\n    }\n\n    mdmap.insertTS((*md)()->id(), md);\n  }\n  return (*md)()->id();\n}\n\n/* -------------------------------------------------------------------------- */\nint\nmetad::wait_flush(fuse_req_t req, metad::shared_md md)\n{\n  uint64_t id = (*md)()->id();\n  // logic to wait for a completion of request\n  md->Locker().UnLock();\n\n  mdflush.Lock();\n  auto lastit = mdflushqueue.rbegin();\n  // find youngest entry\n  if (mdqueue.count(id)) {\n    lastit = mdflushqueue.rend();\n  }\n\n  auto entry = std::find_if(mdflushqueue.rbegin(), lastit,\n                            [id](const flushentry &fe) {\n                   return fe.id() == id;\n                 });\n\n  if (entry != lastit) {\n    mdqwaiter w;\n    entry->registerwaiter(&w);\n    mdflush.UnLock();\n    w.wait();\n  } else {\n    mdflush.UnLock();\n  }\n\n  eos_static_info(\"waited for sync rc=%d bw=%#lx\", (*md)()->err(),\n                  inomap.backward((*md)()->id()));\n\n  if (!inomap.backward((*md)()->id())) {\n    md->Locker().Lock();\n    return (*md)()->err();\n  } else {\n    md->Locker().Lock();\n    return 0;\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nbool\n/* -------------------------------------------------------------------------- */\nmetad::has_flush(fuse_ino_t ino)\n/* -------------------------------------------------------------------------- */\n{\n  bool in_flush = false;\n  mdflush.Lock();\n\n  if (mdqueue.count(ino)) {\n    in_flush = true;\n  }\n\n  mdflush.UnLock();\n  return in_flush;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::update(fuse_req_t req, shared_md md, std::string authid,\n              bool localstore)\n{\n  fuse_id id(req);\n  return update(id, md, authid, localstore);\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::update(fuse_id fuseid, shared_md md, std::string authid,\n              bool localstore)\n{\n  mdflush.Lock();\n  stat.inodes_backlog_store(mdqueue.size());\n  const uint64_t id = (*md)()->id();\n  mdx::md_op op = localstore ? mdx::LSTORE : mdx::UPDATE;\n\n  // if we've already got an update in the queue for for this id\n  // and user don't add it again. the item at start of queue may\n  // be finishing up, so don't count this.\n  if (mdqueue.count(id)) {\n    auto itsecond = mdflushqueue.begin();\n    if (itsecond != mdflushqueue.end()) ++itsecond;\n    auto entry = std::find_if(itsecond, mdflushqueue.end(),\n         [fuseid,op,id](const flushentry &fe) {\n           const fuse_id x =  fe.get_fuse_id();\n           return (fe.id() == id &&\n                   fe.op() == op &&\n                   x.uid   == fuseid.uid &&\n                   x.gid   == fuseid.gid);\n         });\n    if (entry != mdflushqueue.end()) {\n      entry->updateauthid(authid);\n      mdflush.UnLock();\n      return;\n    }\n  }\n\n  flushentry fe(id, authid, op, fuseid);\n\n  if (!localstore) {\n    fe.bind();\n  }\n\n  mdqueue[id]++;\n  mdflushqueue.push_back(fe);\n  mdflush.Broadcast();\n\n  if (!localstore) {\n    // only updates initiated from FUSE limited,\n    // server response updates pass\n    wait_backlog(md);\n  }\n\n  eos_static_info(\"added ino=%#lx flushentry=%s queue-size=%u local-store=%d\",\n                  id, flushentry::dump(fe).c_str(), mdqueue.size(), localstore);\n  mdflush.UnLock();\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::add(fuse_req_t req, metad::shared_md pmd, metad::shared_md md,\n           std::string authid, bool localstore)\n{\n  using eos::common::StringConversion;\n  // this is called with a lock on the md object\n  stat.inodes_inc();\n  stat.inodes_ever_inc();\n  uint64_t pid = 0;\n  const uint64_t id = (*md)()->id();\n  uint64_t pmd_ino = 0;\n  const std::string encname = StringConversion::EncodeInvalidUTF8((\n\t\t\t\t\t\t\t\t   *md)()->name());\n\n  if (EOS_LOGS_DEBUG)\n    eos_static_debug(\"child=%s parent=%s inode=%016lx authid=%s localstore=%d\",\n                     (*md)()->name().c_str(),\n                     (*pmd)()->name().c_str(), (*md)()->id(), authid.c_str(), localstore);\n\n  // avoid lock-order violation\n  md->Locker().UnLock();\n  {\n    XrdSysMutexHelper mLock(pmd->Locker());\n\n    if (!pmd->local_children().count(encname)) {\n      (*pmd)()->set_nchildren((*pmd)()->nchildren() + 1);\n    }\n\n    pmd->local_children()[encname] = id;\n    (*pmd)()->set_nlink(1);\n    pmd->get_todelete().erase(encname);\n    pid = (*pmd)()->id();\n    pmd_ino = (*pmd)()->md_ino();\n  }\n  md->Locker().Lock();\n  {\n    // store the local and remote parent inode\n    (*md)()->set_pid(pid);\n    (*md)()->set_md_pino(pmd_ino);\n  }\n  mdflush.Lock();\n  stat.inodes_backlog_store(mdqueue.size());\n\n  if (!localstore) {\n    flushentry fe(id, authid, mdx::ADD, req);\n    fe.bind();\n    mdqueue[id]++;\n    mdflushqueue.push_back(fe);\n  }\n\n  flushentry fep(pid, authid, mdx::LSTORE, req);\n  fep.bind();\n  mdqueue[pid]++;\n  mdflushqueue.push_back(fep);\n  mdflush.Broadcast();\n  if (!localstore) {\n    wait_backlog(md);\n  }\n  mdflush.UnLock();\n}\n\n/* -------------------------------------------------------------------------- */\nint\nmetad::add_sync(fuse_req_t req, shared_md pmd, shared_md md, std::string authid)\n{\n  // this is called with a lock on the md object\n  int rc = 0;\n  // store the local and remote parent inode\n  XrdSysMutexHelper mLockParent(pmd->Locker());\n  const uint64_t pid = (*pmd)()->id();\n  (*md)()->set_pid(pid);\n  (*md)()->set_md_pino((*pmd)()->md_ino());\n  mLockParent.UnLock();\n  mdx::md_op op = mdx::ADD;\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"metacache::sync ino=%016lx authid=%s op=%d\", (*md)()->id(),\n                     authid.c_str(), (int) op);\n  }\n\n  (*md)()->set_operation((*md)()->SET);\n  eos_static_info(\"metacache::sync backend::putMD - start\");\n\n  while (1) {\n    // wait that the parent is leaving the mdqueue\n    mdflush.Lock();\n\n    if (mdqueue.count(pid)) {\n      mdflush.UnLock();\n      eos_static_info(\"waiting for parent directory to be synced upstream parent-ino= %#lx ino=%#lx\",\n                      pid, (*md)()->id());\n      std::this_thread::sleep_for(std::chrono::microseconds(500));\n    } else {\n      mdflush.UnLock();\n      break;\n    }\n  }\n\n  // push to backend\n  if ((rc = mdbackend->putMD(req, (*md)(), authid, &(md->Locker())))) {\n    eos_static_err(\"metad::add_sync backend::putMD failed rc=%d\", rc);\n    // in this case we always clean this MD record to force a refresh\n    inomap.erase_bwd((*md)()->id());\n    md->setop_none();\n    (*md)()->set_err(rc);\n\n    if ((*md)()->id()) {\n      if (mdmap.eraseTS((*md)()->id())) {\n        stat.inodes_dec();\n        stat.inodes_ever_inc();\n      }\n    }\n\n    return rc;\n  } else {\n    (*md)()->set_id((*md)()->md_ino());\n    inomap.insert((*md)()->md_ino(), (*md)()->id());\n    md->setop_none();\n  }\n\n  eos_static_info(\"metad::add_sync backend::putMD - stop\");\n  std::string md_name = (*md)()->name();\n  stat.inodes_inc();\n  stat.inodes_ever_inc();\n\n  if (EOS_LOGS_DEBUG)\n    eos_static_debug(\"child=%s parent=%s inode=%016lx authid=%s\",\n                     (*md)()->name().c_str(),\n                     (*pmd)()->name().c_str(), (*md)()->id(), authid.c_str());\n\n  const uint64_t id = (*md)()->id();\n  // avoid lock-order violation\n  md->Locker().UnLock();\n  {\n    XrdSysMutexHelper mLock(pmd->Locker());\n\n    if (!pmd->local_children().count(\n          eos::common::StringConversion::EncodeInvalidUTF8(md_name))) {\n      (*pmd)()->set_nchildren((*pmd)()->nchildren() + 1);\n    }\n\n    pmd->local_children()[eos::common::StringConversion::EncodeInvalidUTF8(\n                            md_name)] = id;\n    (*pmd)()->set_nlink(1);\n    pmd->get_todelete().erase(eos::common::StringConversion::EncodeInvalidUTF8(\n                                md_name));\n  }\n  md->Locker().Lock();\n  mdflush.Lock();\n  stat.inodes_backlog_store(mdqueue.size());\n  flushentry fep(pid, authid, mdx::LSTORE, req);\n  fep.bind();\n  mdqueue[pid]++;\n  mdflushqueue.push_back(fep);\n  mdflush.Broadcast();\n  wait_backlog(md);\n  mdflush.UnLock();\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\nmetad::begin_flush(fuse_req_t req, shared_md emd, std::string authid)\n{\n  shared_md md = std::make_shared<mdx>();\n  (*md)()->set_operation((*md)()->BEGINFLUSH);\n  int rc = 0;\n\n  if (!((*emd))()->md_ino()) {\n    //TODO wait for the remote inode to be known\n  }\n\n  XrdSysMutexHelper mLock(md->Locker());\n  (*md)()->set_md_ino((*emd)()->md_ino());\n\n  if ((rc = mdbackend->putMD(req, (*md)(), authid, &(md->Locker())))) {\n    eos_static_err(\"metad::begin_flush backend::putMD failed rc=%d\", rc);\n  }\n\n  return rc;\n}\n\n/* -------------------------------------------------------------------------- */\nint\nmetad::end_flush(fuse_req_t req, shared_md emd, std::string authid)\n{\n  shared_md md = std::make_shared<mdx>();\n  (*md)()->set_operation((*md)()->ENDFLUSH);\n  int rc = 0;\n\n  if (!(*emd)()->md_ino()) {\n    //TODO wait for the remote inode to be known\n  }\n\n  XrdSysMutexHelper mLock(md->Locker());\n  (*md)()->set_md_ino((*emd)()->md_ino());\n\n  if ((rc = mdbackend->putMD(req, (*md)(), authid, &(md->Locker())))) {\n    eos_static_err(\"metad::begin_flush backend::putMD failed rc=%d\", rc);\n  }\n\n  return rc;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::remove(fuse_req_t req, metad::shared_md pmd, metad::shared_md md,\n              std::string authid,\n              bool upstream,\n\t      bool norecycle)\n{\n  // this is called with the md object locked\n  if (EOS_LOGS_DEBUG)\n    eos_static_debug(\"child=%s parent=%s inode=%#lx upstreaqm=%d\",\n                     (*md)()->name().c_str(),\n                     (*pmd)()->name().c_str(), (*md)()->id(), upstream);\n\n  struct timespec ts;\n  eos::common::Timing::GetTimeSpec(ts);\n\n  if (!md->deleted()) {\n    md->lookup_inc();\n    stat.inodes_deleted_inc();\n    stat.inodes_deleted_ever_inc();\n  }\n\n  (*md)()->set_mtime(ts.tv_sec);\n  (*md)()->set_mtime_ns(ts.tv_nsec);\n  md->setop_delete();\n\n  if (EosFuse::Instance().Config().options.hide_versions &&\n      EosFuse::Instance().mds.supports_hideversion()) {\n    if (norecycle) {\n      // indicate the MGM to remove also all versions\n      (*md)()->set_opflags(eos::fusex::md::DELETEVERSIONSNORECYCLEBIN);\n    } else {\n      // indicate the MGM to remove also all versions\n      (*md)()->set_opflags(eos::fusex::md::DELETEVERSIONS);\n    }\n  } else {\n    if (norecycle) {\n      (*md)()->set_opflags(eos::fusex::md::NORECYCLEBIN);\n    }\n  }\n\n\n  std::string name = (*md)()->name();\n  const uint64_t id = (*md)()->id();\n  uint64_t pid = 0;\n  {\n    XrdSysMutexHelper mLock(pmd->Locker());\n    pid = (*pmd)()->id();\n    pmd->local_children().erase(eos::common::StringConversion::EncodeInvalidUTF8(\n                                  name));\n    (*pmd)()->set_nchildren((*pmd)()->nchildren() - 1);\n    pmd->get_todelete()[eos::common::StringConversion::EncodeInvalidUTF8(\n                          name)] = id;\n    (*pmd)()->set_mtime(ts.tv_sec);\n    (*pmd)()->set_mtime_ns(ts.tv_nsec);\n  }\n\n  if (!upstream) {\n    return;\n  }\n\n  mdflush.Lock();\n  flushentry fe(id, authid, mdx::RM, req);\n  fe.bind();\n  flushentry fep(pid, authid, mdx::LSTORE, req);\n  fep.bind();\n  mdqueue[pid]++;\n  mdqueue[id]++;\n  mdflushqueue.push_back(fe);\n  mdflushqueue.push_back(fep);\n  stat.inodes_backlog_store(mdqueue.size());\n  mdflush.Broadcast();\n  wait_backlog(md);\n  mdflush.UnLock();\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::mv(fuse_req_t req, shared_md p1md, shared_md p2md, shared_md md,\n          std::string newname, std::string authid1, std::string authid2)\n{\n  if (EOS_LOGS_DEBUG)\n    eos_static_debug(\"child=%s new-name=%s parent=%s newparent=%s inode=%016lx\",\n                     (*md)()->name().c_str(),\n                     newname.c_str(),\n                     (*p1md)()->name().c_str(), (*p2md)()->name().c_str(), (*md)()->id());\n\n  XrdSysMutexHelper mLock(md->Locker());\n  struct timespec ts;\n  eos::common::Timing::GetTimeSpec(ts);\n  uint64_t p1id = 0, p2id = 0;\n\n  if (p1md != p2md) {\n    // move between directories. We need to run an expensive algorithm to\n    // determine the correct lock order, but a rename should be rather uncommon,\n    // anyway.\n    MdLocker locker(p1md, p2md, determineLockOrder(p1md, p2md));\n    std::string oldname = (*md)()->name();\n    p1id = (*p1md)()->id();\n    p2id = (*p2md)()->id();\n\n    if (!p2md->local_children().count(\n          eos::common::StringConversion::EncodeInvalidUTF8(newname))) {\n      (*p2md)()->set_nchildren((*p2md)()->nchildren() + 1);\n    }\n\n    p2md->local_children()[eos::common::StringConversion::EncodeInvalidUTF8(\n                             newname)] = (*md)()->id();\n    p1md->local_children().erase(eos::common::StringConversion::EncodeInvalidUTF8(\n                                   (*md)()->name()));\n    (*p1md)()->set_nchildren((*p1md)()->nchildren() - 1);\n    (*p1md)()->set_mtime(ts.tv_sec);\n    (*p1md)()->set_mtime_ns(ts.tv_nsec);\n    (*p1md)()->clear_pmtime();\n    (*p1md)()->clear_pmtime_ns();\n    (*p1md)()->set_ctime(ts.tv_sec);\n    (*p1md)()->set_ctime_ns(ts.tv_nsec);\n    (*p2md)()->set_mtime(ts.tv_sec);\n    (*p2md)()->set_mtime_ns(ts.tv_nsec);\n    (*p2md)()->clear_pmtime();\n    (*p2md)()->clear_pmtime_ns();\n    (*p2md)()->set_ctime(ts.tv_sec);\n    (*p2md)()->set_ctime_ns(ts.tv_nsec);\n    (*md)()->set_name(newname);\n    (*md)()->set_pid(p2id);\n    (*md)()->set_md_pino((*p2md)()->md_ino());\n    p1md->get_todelete()[eos::common::StringConversion::EncodeInvalidUTF8(\n                           oldname)] = 0; //(*md)()->id(); // make it known as deleted\n    p2md->get_todelete().erase(eos::common::StringConversion::EncodeInvalidUTF8(\n                                 newname)); // the new target is not deleted anymore\n    p2md->local_enoent().erase(newname); // remove a possible enoent entry\n    md->setop_update();\n    p1md->setop_update();\n    p2md->setop_update();\n  } else {\n    // move within directory\n    XrdSysMutexHelper m1Lock(p1md->Locker());\n    p1id = p2id = (*p1md)()->id();\n\n    if (p2md->local_children().count(\n          eos::common::StringConversion::EncodeInvalidUTF8(newname))) {\n      (*p2md)()->set_nchildren((*p2md)()->nchildren() - 1);\n    }\n\n    p2md->local_children()[eos::common::StringConversion::EncodeInvalidUTF8(\n                             newname)] = (*md)()->id();\n    p1md->local_children().erase(eos::common::StringConversion::EncodeInvalidUTF8(\n                                   (*md)()->name()));\n    p1md->get_todelete()[eos::common::StringConversion::EncodeInvalidUTF8(\n                           (*md)()->name())] = (*md)()->id(); // make it known as deleted\n    p2md->get_todelete().erase(eos::common::StringConversion::EncodeInvalidUTF8(\n                                 newname)); // the new target is not deleted anymore\n    p2md->local_enoent().erase(newname); // remove a possible enoent entry\n    (*md)()->set_name(newname);\n    md->setop_update();\n    (*p1md)()->set_mtime(ts.tv_sec);\n    (*p1md)()->set_mtime_ns(ts.tv_nsec);\n    (*p1md)()->clear_pmtime();\n    (*p1md)()->clear_pmtime_ns();\n    (*p1md)()->set_ctime(ts.tv_sec);\n    (*p1md)()->set_ctime_ns(ts.tv_nsec);\n    p1md->setop_update();\n  }\n\n  (*md)()->clear_pmtime();\n  (*md)()->clear_pmtime_ns();\n  (*md)()->set_ctime(ts.tv_sec);\n  (*md)()->set_ctime_ns(ts.tv_nsec);\n  (*md)()->set_mv_authid(authid1); // store also the source authid\n  mdflush.Lock();\n  flushentry fe1(p1id, authid1, mdx::UPDATE, req);\n  fe1.bind();\n  mdqueue[p1id]++;\n  mdflushqueue.push_back(fe1);\n\n  if (p1id != p2id) {\n    flushentry fe2(p2id, authid2, mdx::UPDATE, req);\n    fe2.bind();\n    mdqueue[p2id]++;\n    mdflushqueue.push_back(fe2);\n  }\n\n  flushentry fe((*md)()->id(), authid2, mdx::UPDATE, req);\n  fe.bind();\n  mdqueue[(*md)()->id()]++;\n  mdflushqueue.push_back(fe);\n  stat.inodes_backlog_store(mdqueue.size());\n  mdflush.Broadcast();\n  wait_backlog(md);\n  mdflush.UnLock();\n}\n\n/* -------------------------------------------------------------------------- */\nint\nmetad::rmrf(fuse_req_t req, shared_md md)\n{\n  int rc = mdbackend->rmRf(req, (*md)());\n  return rc;\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\nmetad::dump_md(shared_md md, bool lock)\n{\n  if (!(md)) {\n    return \"\";\n  }\n\n  google::protobuf::util::JsonPrintOptions options;\n  options.add_whitespace = true;\n#if GOOGLE_PROTOBUF_VERSION >= 5027000\n  options.always_print_fields_with_no_presence = true;\n#else\n  options.always_print_primitive_fields = true;\n#endif\n  std::string jsonstring;\n\n  if (lock) {\n    md->Locker().Lock();\n  }\n\n  (void) google::protobuf::util::MessageToJsonString(*((eos::fusex::md*)((\n        *md)())),\n      &jsonstring, options);\n  char capcnt[16];\n  snprintf(capcnt, sizeof(capcnt), \"%d\", md->cap_count());\n  jsonstring += \"\\nlocal-children: {\\n\";\n\n  for (auto it = md->local_children().begin();\n       it != md->local_children().end(); ++it) {\n    char buff[32];\n    jsonstring += \"\\\"\";\n    jsonstring += it->first;\n    jsonstring += \"\\\" : \";\n    jsonstring += longstring::to_decimal(it->second, buff);\n\n    if (it == md->local_children().end()) {\n      break;\n    } else {\n      jsonstring += \"\\\",\";\n    }\n  }\n\n  jsonstring += \"}\\n\";\n  jsonstring += \"\\nto-delete: {\\n\";\n\n  for (auto it = md->get_todelete().begin();\n       it != md->get_todelete().end(); ++it) {\n    jsonstring += \"\\\"\";\n    jsonstring += it->first.c_str();\n\n    if (it == md->get_todelete().end()) {\n      jsonstring += \"\\\"\";\n      break;\n    } else {\n      jsonstring += \"\\\",\";\n    }\n  }\n\n  jsonstring += \"}\\n\";\n  jsonstring += \"\\nenoent: {\\n\";\n\n  for (auto it = md->local_enoent().begin();\n       it != md->local_enoent().end(); ++it) {\n    jsonstring += \"\\\"\";\n    jsonstring += *it;\n\n    if (it == md->local_enoent().end()) {\n      jsonstring += \"\\\"\";\n      break;\n    } else {\n      jsonstring += \"\\\",\";\n    }\n  }\n\n  jsonstring += \"}\\n\";\n  jsonstring += \"\\ncap-cnt: \";\n  jsonstring += capcnt;\n  jsonstring += \"\\nlru-prev: \";\n  jsonstring += std::to_string(md->lru_prev());\n  jsonstring += \"\\nlru_next: \";\n  jsonstring += std::to_string(md->lru_next());\n  jsonstring += \"\\n\";\n  jsonstring += \"\\nrefresh: \";\n  jsonstring += md->needs_refresh() ? \"true\" : \"false\";\n  jsonstring += \"\\n\";\n\n  if (lock) {\n    md->Locker().UnLock();\n  }\n\n  return jsonstring;\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\nmetad::dump_md(eos::fusex::md& md)\n{\n  google::protobuf::util::JsonPrintOptions options;\n  options.add_whitespace = true;\n#if GOOGLE_PROTOBUF_VERSION >= 5027000\n  options.always_print_fields_with_no_presence = true;\n#else\n  options.always_print_primitive_fields = true;\n#endif\n  std::string jsonstring;\n  (void) google::protobuf::util::MessageToJsonString(md, &jsonstring, options);\n  return jsonstring;\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\nmetad::dump_container(eos::fusex::container& cont)\n{\n  google::protobuf::util::JsonPrintOptions options;\n  options.add_whitespace = true;\n#if GOOGLE_PROTOBUF_VERSION >= 5027000\n  options.always_print_fields_with_no_presence = true;\n#else\n  options.always_print_primitive_fields = true;\n#endif\n  std::string jsonstring;\n  (void) google::protobuf::util::MessageToJsonString(cont, &jsonstring, options);\n  return jsonstring;\n}\n\n/* -------------------------------------------------------------------------- */\nint\nmetad::getlk(fuse_req_t req, shared_md md, struct flock* lock)\n{\n  XrdSysMutexHelper locker(md->Locker());\n  // fill lock request structure\n  (*md)()->mutable_flock()->set_pid(fuse_req_ctx(req)->pid);\n  (*md)()->mutable_flock()->set_len(lock->l_len);\n  (*md)()->mutable_flock()->set_start(lock->l_start);\n  (*md)()->set_operation((*md)()->GETLK);\n\n  switch (lock->l_type) {\n  case F_RDLCK:\n    (*md)()->mutable_flock()->set_type(eos::fusex::lock::RDLCK);\n    break;\n\n  case F_WRLCK:\n    (*md)()->mutable_flock()->set_type(eos::fusex::lock::WRLCK);\n    break;\n\n  case F_UNLCK:\n    (*md)()->mutable_flock()->set_type(eos::fusex::lock::UNLCK);\n    break;\n\n  default:\n    return EINVAL;\n  }\n\n  // do sync upstream lock call\n  int rc = mdbackend->doLock(req, *(*md)(), &(md->Locker()));\n\n  // digest the response\n  if (!rc) {\n    // store the md->flock response into the flock structure\n    eos_static_notice(\"pid=%llu len=%llu start=%llu type=%llu\\n\",\n                      (*md)()->flock().pid(),\n                      (*md)()->flock().len(),\n                      (*md)()->flock().start(),\n                      (*md)()->flock().type());\n    lock->l_pid = (*md)()->flock().pid();\n    lock->l_len = (*md)()->flock().len();\n    lock->l_start = (*md)()->flock().start();\n    lock->l_whence = SEEK_SET;\n\n    switch ((*md)()->flock().type()) {\n    case eos::fusex::lock::RDLCK:\n      lock->l_type = F_RDLCK;\n      break;\n\n    case eos::fusex::lock::WRLCK:\n      lock->l_type = F_WRLCK;\n      break;\n\n    case eos::fusex::lock::UNLCK:\n      lock->l_type = F_UNLCK;\n      break;\n\n    default:\n      rc = (*md)()->flock().err_no();\n    }\n  } else {\n    rc = EAGAIN;\n  }\n\n  // clean the lock structure;\n  (*md)()->clear_flock();\n  return rc;\n}\n\n/* -------------------------------------------------------------------------- */\nint\nmetad::setlk(fuse_req_t req, shared_md md, struct flock* lock, int sleep)\n{\n  XrdSysMutexHelper locker(md->Locker());\n  // fill lock request structure\n  (*md)()->mutable_flock()->set_pid(fuse_req_ctx(req)->pid);\n  (*md)()->mutable_flock()->set_len(lock->l_len);\n  (*md)()->mutable_flock()->set_start(lock->l_start);\n\n  if (sleep) {\n    (*md)()->set_operation((*md)()->SETLKW);\n  } else {\n    (*md)()->set_operation((*md)()->SETLK);\n  }\n\n  switch (lock->l_type) {\n  case F_RDLCK:\n    (*md)()->mutable_flock()->set_type(eos::fusex::lock::RDLCK);\n    break;\n\n  case F_WRLCK:\n    (*md)()->mutable_flock()->set_type(eos::fusex::lock::WRLCK);\n    break;\n\n  case F_UNLCK:\n    (*md)()->mutable_flock()->set_type(eos::fusex::lock::UNLCK);\n    break;\n\n  default:\n    eos_static_notice(\"unsupported lock operation op:%x\", lock->l_type);\n    return EINVAL;\n  }\n\n  bool backend_call = true;\n\n  if (lock->l_type == F_UNLCK) {\n    backend_call = false;\n\n    // check that we have actually a lock for that before doing an upstream call\n    for (auto it = md->LockTable().begin(); it != md->LockTable().end(); ++it) {\n      if (it->l_pid == (pid_t)(*md)()->flock().pid()) {\n        backend_call = true;\n      }\n    }\n  }\n\n  // do sync upstream lock call\n  int rc = 0;\n\n  if (backend_call) {\n    rc = mdbackend->doLock(req, *(*md)(), &(md->Locker()));\n  }\n\n  // digest the response\n  if (!rc) {\n    rc = (*md)()->flock().err_no();\n  } else {\n    rc = EAGAIN;\n  }\n\n  if (!rc) {\n    // store in the lock table - unlocking done during flush\n    if (lock->l_type != F_UNLCK) {\n      md->LockTable().push_back(*lock);\n    } else {\n      // remove from LockTable - not the most efficient\n      auto it = md->LockTable().begin();\n\n      while (it != md->LockTable().end()) {\n        if (it->l_pid == (pid_t)(*md)()->flock().pid()) {\n          it = md->LockTable().erase(it);\n        } else {\n          it++;\n        }\n      }\n    }\n  }\n\n  // clean the lock structure;\n  (*md)()->clear_flock();\n  return rc;\n}\n\n/* -------------------------------------------------------------------------- */\nint\nmetad::statvfs(fuse_req_t req, struct statvfs* svfs)\n{\n  return mdbackend->statvfs(req, svfs);\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nmetad::cleanup(shared_md md)\n/* -------------------------------------------------------------------------- */\n{\n  // called with locked md, returns with unlocked md\n  eos_static_debug(\"id=%16x\", (*md)()->id());\n  std::vector<std::string> inval_entry_name;\n  std::vector<fuse_ino_t> inval_files;\n  std::vector<fuse_ino_t> inval_dirs;\n\n  for (auto it = md->local_children().begin();\n       it != md->local_children().end(); ++it) {\n    shared_md cmd;\n\n    if (mdmap.retrieveTS(it->second, cmd)) {\n      // XrdSysMutexHelper cmLock(cmd->Locker());\n      bool in_flush = has_flush(it->second);\n\n      if (!S_ISDIR((*cmd)()->mode())) {\n        if (!in_flush && !EosFuse::Instance().datas.has((*cmd)()->id())) {\n          // clean-only entries, which are not in the flush queue and not open\n          inval_files.push_back(it->second);\n          cmd->force_refresh();\n        }\n      }\n\n      if (!dentrymessaging) {\n        // if the server does not provide a dentry invalidation message\n        inval_entry_name.push_back(it->first);\n      } else {\n        // if the server provides a dentry invalidation message\n        // files and directories never get an inval_entry call, only when we see an explicit deletion or a negative cache entry needs cleanup\n      }\n    }\n  }\n\n  for (auto it : md->local_enoent()) {\n    inval_entry_name.push_back(it);\n  }\n\n  md->local_enoent().clear();\n  md->Locker().UnLock();\n\n  if (EosFuse::Instance().Config().options.md_kernelcache) {\n    for (auto it = inval_entry_name.begin(); it != inval_entry_name.end(); ++it) {\n      kernelcache::inval_entry((*md)()->id(), *it);\n    }\n  }\n\n  md->Locker().Lock();\n  (*md)()->set_type((*md)()->MD);\n  (*md)()->set_creator(false);\n  md->cap_count_set(0);\n  md->get_todelete().clear();\n  md->setop_none();     /* so that wait_flush() returns */\n  md->Locker().UnLock();\n\n  if ((EosFuse::Instance().Config().options.data_kernelcache) ||\n      (EosFuse::Instance().Config().options.md_kernelcache)) {\n    for (auto it = inval_files.begin(); it != inval_files.end(); ++it) {\n      forget(0, *it, 0);\n    }\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\n/* -------------------------------------------------------------------------- */\nmetad::cleanup(fuse_ino_t ino)\n/* -------------------------------------------------------------------------- */\n{\n  shared_md md;\n\n  if (mdmap.retrieveTS(ino, md)) {\n    md->Locker().Lock();\n    return cleanup(md);\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nuint64_t\nmetad::apply(fuse_req_t req, eos::fusex::container& cont, bool listing)\n{\n  // apply receives either a single MD record or a parent MD + all children MD\n  // we have to make sure that the modification of children is atomic in the parent object\n  shared_md md;\n  shared_md pmd;\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(dump_container(cont).c_str());\n  }\n\n  if (cont.type() == cont.MD) {\n    int mderr = 0;\n\n    if ((mderr = cont.md_().err())) {\n      eos_static_info(\"ref_ino=%016lx skipped applying MD container with err=%d\",\n                      (long) cont.ref_inode_(), mderr);\n      return 0;\n    }\n\n    uint64_t md_ino = cont.md_().md_ino();\n    uint64_t md_pino = cont.md_().md_pino();\n    uint64_t ino = inomap.forward(md_ino);\n    bool is_new = false;\n    {\n      // Create a new md object, if none is found in the cache\n      if (!mdmap.retrieveTS(ino, md)) {\n        is_new = true;\n        md = std::make_shared<mdx>();\n      }\n\n      md->Locker().Lock();\n\n      if (EOS_LOGS_DEBUG) {\n        eos_static_debug(\"%s op=%d deleted=%d\", md->dump().c_str(), md->getop(),\n                         md->deleted());\n      }\n\n      if (md->deleted()) {\n        md->Locker().UnLock();\n        return ino;\n      }\n    }\n    uint64_t p_ino = inomap.forward(md_pino);\n\n    if (!p_ino) {\n      p_ino = md_pino;\n      // it might happen that we don't know yet anything about this parent\n      inomap.insert(md_pino, p_ino);\n      eos_static_debug(\"msg=\\\"creating lookup entry for parent inode\\\" md-pino=%016lx pino=%016lx md-ino=%016lx ino=%016lx\",\n                       md_pino, p_ino, md_ino, ino);\n    }\n\n    if (is_new) {\n      // in this case we need to create a new one\n      (*md)()->set_id(md_ino);\n      uint64_t new_ino = insert(md, (*md)()->authid());\n      ino = new_ino;\n    }\n\n    if (!S_ISDIR((*md)()->mode())) {\n      // if its a file we need to have a look at parent cap-count, so we get the parent md\n      md->Locker().UnLock();\n      mdmap.retrieveTS(p_ino, pmd);\n      md->Locker().Lock();\n    }\n\n    {\n      if (!has_flush(ino)) {\n        const std::string fullpath = (*md)()->fullpath();\n        const size_t local_size = (*md)()->size();\n        const uint64_t local_mtime = (*md)()->mtime();\n        const uint64_t local_mtime_ns = (*md)()->mtime_ns();\n        md->UpdateProtoFrom(cont.md_());\n        (*md)()->set_fullpath(fullpath);\n        shared_md d_md = EosFuse::Instance().datas.retrieve_wr_md(ino);\n\n        if (d_md) {\n          // see if this file is open for write, because in that case\n          // we have to keep the local size information and modification times\n          (*md)()->set_size(local_size);\n          (*md)()->set_mtime(local_mtime);\n          (*md)()->set_mtime_ns(local_mtime_ns);\n        }\n      } else {\n        eos_static_warning(\"deferring MD overwrite local-ino=%016lx remote-ino=%016lx \",\n                           (long) ino, (long) md_ino);\n      }\n\n      if (EOS_LOGS_DEBUG) {\n        eos_static_debug(\"store md for local-ino=%016lx remote-ino=%016lx -\",\n                         (long) ino, (long) md_ino);\n        eos_static_debug(\"%s\", md->dump().c_str());\n      }\n    }\n\n    (*md)()->set_pid(p_ino);\n    (*md)()->set_id(ino);\n    md->clear_refresh();\n    eos_static_info(\"store local pino=%016lx for %016lx\", (*md)()->pid(),\n                    (*md)()->id());\n    inomap.insert(md_ino, ino);\n    md->Locker().UnLock();\n\n    if (is_new) {\n      XrdSysMutexHelper mLock(mdmap);\n      mdmap[ino] = md;\n      stat.inodes_inc();\n      stat.inodes_ever_inc();\n    }\n\n    return ino;\n  } else if (cont.type() == cont.MDMAP) {\n    uint64_t p_ino = inomap.forward(cont.ref_inode_());\n\n    for (auto map = cont.md_map_().md_map_().begin();\n         map != cont.md_map_().md_map_().end(); ++map) {\n      // loop over the map of meta data objects\n      int mderr = 0;\n\n      if ((mderr = map->second.err())) {\n        eos_static_info(\"ref_ino=%016lx ino=%016lx skipped applying MDMAP \"\n                        \"container entry with err=%d\",\n                        (long) cont.ref_inode_(), (long) map->first, mderr);\n        continue;\n      }\n\n      uint64_t ino = inomap.forward(map->first);\n      eos::fusex::cap cap_received;\n      cap_received.set_id(0);\n\n      if (EOS_LOGS_DEBUG) {\n        eos_static_debug(\"remote-ino=%016lx local-ino=%016lx\", (long) map->first, ino);\n      }\n\n      if (mdmap.retrieveTS(ino, md)) {\n        // this is an already known inode\n        eos_static_debug(\"lock mdmap\");\n        {\n          bool child = false;\n\n          if (map->first != cont.ref_inode_()) {\n            child = true;\n\n            if (!S_ISDIR(map->second.mode())) {\n              shared_md child_pmd;\n\n              if (mdmap.retrieveTS(p_ino, child_pmd)) {\n                if (cap_received.id()) {\n                  // store cap\n                  EosFuse::Instance().getCap().store(req, cap_received);\n                  md->cap_inc();\n                }\n\n                // don't overwrite md to be flushed\n                if (has_flush(ino)) {\n                  continue;\n                }\n              }\n            }\n\n            md->Locker().Lock();\n          } else {\n            md->Locker().Lock();\n            pmd = md;\n\n            if (EOS_LOGS_DEBUG) {\n              eos_static_debug(\"lock pmd ino=%#lx\", (*pmd)()->id());\n            }\n          }\n\n          if (map->second.has_capability()) {\n            // extract any new capability\n            cap_received = map->second.capability();\n          }\n\n          if (child) {\n            eos_static_debug(\"case 1 %s\", (*md)()->name().c_str());\n            eos::fusex::md::TYPE mdtype = (*md)()->type();\n            const size_t local_size = (*md)()->size();\n            const uint64_t local_mtime = (*md)()->mtime();\n            const uint64_t local_mtime_ns = (*md)()->mtime_ns();\n            md->UpdateProtoFrom(map->second);\n            md->clear_refresh();\n            shared_md d_md = EosFuse::Instance().datas.retrieve_wr_md(ino);\n\n            if (d_md || has_flush(ino)) {\n              // see if this file is open for write, because in that case\n              // we have to keep the local size information and modification times\n              (*md)()->set_size(local_size);\n              (*md)()->set_mtime(local_mtime);\n              (*md)()->set_mtime_ns(local_mtime_ns);\n            }\n\n            // if this object was a listing type, keep that\n            (*md)()->set_type(mdtype);\n          } else {\n            // we have to overlay the listing\n            std::map<std::string, uint64_t> todelete;\n            mdflush.Lock();\n\n            if (!mdqueue.count((*md)()->id())) {\n              eos_static_debug(\"case 2 %s id %#lx\", (*md)()->name().c_str(), (*md)()->id());\n              mdflush.UnLock();\n              todelete = md->get_todelete();\n              // overwrite local meta data with remote state\n              md->UpdateProtoFrom(map->second);\n              md->get_todelete() = todelete;\n              (*md)()->set_type((*md)()->MD);\n\n              if (!md->get_todelete().size()) {\n                // if there are no local deletions anymore, we can trust the remote value of nchildren\n                md->clear_refresh();\n              }\n            } else {\n              eos_static_debug(\"case 3 %s children=%d listing=%d\", (*md)()->name().c_str(),\n                               map->second.children().size(), listing);\n              mdflush.UnLock();\n              todelete = md->get_todelete();\n              // copy only the listing\n              (*md)()->mutable_children()->clear();\n\n              for (auto it = map->second.children().begin();\n                   it != map->second.children().end(); ++it) {\n                (*((*md)()->mutable_children()))[eos::common::StringConversion::EncodeInvalidUTF8(\n                                                   it->first)] = it->second;\n              }\n\n              // keep the listing\n              md->get_todelete() = todelete;\n              (*md)()->set_type((*md)()->MD);\n\n              if (!md->get_todelete().size()) {\n                // if there are no local deletions anymore, we can trust the remote value of nchildren\n                md->clear_refresh();\n              }\n            }\n          }\n\n          (*md)()->clear_capability();\n          (*md)()->set_id(ino);\n          p_ino = inomap.forward((*md)()->md_pino());\n          (*md)()->set_pid(p_ino);\n          eos_static_info(\"store remote-ino=%016lx local pino=%016lx for %016lx\",\n                          (*md)()-> md_pino(), (*md)()->pid(), (*md)()->id());\n\n          for (auto it = md->get_todelete().begin(); it != md->get_todelete().end();\n               ++it) {\n            eos_static_info(\"%016lx to-delete=%s\", (*md)()->id(), it->first.c_str());\n          }\n\n          if (EOS_LOGS_DEBUG) {\n            eos_static_debug(\"store md for local-ino=%08ld remote-ino=%016lx type=%d -\",\n                             (long) ino, (long) map->first, (*md)()->type());\n            eos_static_debug(\"%s\", md->dump().c_str());\n          }\n\n          md->Locker().UnLock();\n\n          if (!child) {\n            if (EOS_LOGS_DEBUG) {\n              eos_static_debug(\"cap count %d\\n\", pmd->cap_count());\n            }\n\n            if (!pmd->cap_count()) {\n              if (EOS_LOGS_DEBUG) {\n                eos_static_debug(\"clearing out %0016lx\", (*pmd)()->id());\n              }\n\n              // we don't wipe meta-data children which we still have to flush or have open\n              // if they have to be deleted, there is an explicit callback arriving for deletion\n              XrdSysMutexHelper scope_lock(pmd->Locker());\n              std::vector<std::string> clear_children;\n\n              for (auto it = pmd->local_children().begin() ;\n                   it != pmd->local_children().end(); ++it) {\n                bool in_flush = has_flush(it->second);\n                bool is_attached = EosFuse::Instance().datas.has(it->second);\n\n                if (!in_flush && !is_attached) {\n                  clear_children.push_back(it->first);\n                }\n              }\n\n              for (auto it = clear_children.begin(); it != clear_children.end(); ++it) {\n                pmd->local_children().erase(*it);\n              }\n\n              pmd->get_todelete().clear();\n            }\n          }\n\n          if (cap_received.id()) {\n            // store cap\n            EosFuse::Instance().getCap().store(req, cap_received);\n            md->cap_inc();\n          }\n        }\n      } else {\n        // this is a new inode we don't know yet\n        md = std::make_shared<mdx>();\n\n        if (map->second.has_capability()) {\n          // extract any new capability\n          cap_received = map->second.capability();\n        }\n\n        md->UpdateProtoFrom(map->second);\n        (*md)()->clear_capability();\n        md->clear_refresh();\n\n        if ((!pmd) && (map->first == cont.ref_inode_())) {\n          pmd = md;\n          (*md)()->set_type((*pmd)()->MD);\n        }\n\n        uint64_t new_ino = 0;\n        new_ino = inomap.forward((*md)()->md_ino());\n        (*md)()->set_id(new_ino);\n        insert(md, (*md)()->authid());\n\n        if (!listing) {\n          p_ino = inomap.forward((*md)()->md_pino());\n        }\n\n        (*md)()->set_pid(p_ino);\n        eos_static_info(\"store local pino=%016lx for %016lx\", (*md)()->pid(),\n                        (*md)()->id());\n        inomap.insert(map->first, new_ino);\n        {\n          mdmap.insertTS(new_ino, md);\n          stat.inodes_inc();\n          stat.inodes_ever_inc();\n        }\n\n        if ((pmd == md)) {\n          if (EOS_LOGS_DEBUG) {\n            eos_static_debug(\"cap count %d\\n\", pmd->cap_count());\n          }\n\n          if (!pmd->cap_count()) {\n            if (EOS_LOGS_DEBUG) {\n              eos_static_debug(\"clearing out %0016lx\", (*pmd)()->id());\n            }\n\n            XrdSysMutexHelper scope_lock(pmd->Locker());\n            pmd->local_children().clear();\n            pmd->get_todelete().clear();\n          }\n        }\n\n        if (cap_received.id()) {\n          // store cap\n          EosFuse::Instance().getCap().store(req, cap_received);\n          md->cap_inc();\n        }\n\n        if (EOS_LOGS_DEBUG) {\n          eos_static_debug(\"store md for local-ino=%016lx remote-ino=%016lx type=%d -\",\n                           (long) new_ino, (long) map->first, (*md)()->type());\n        }\n\n        if (EOS_LOGS_DEBUG) {\n          eos_static_debug(\"%s\", md->dump().c_str());\n        }\n      }\n    }\n\n    if (pmd) {\n      pmd->Locker().Lock();\n    }\n\n    if (pmd && listing) {\n      bool ret = false;\n\n      if (!(ret = map_children_to_local(pmd))) {\n        eos_static_err(\"local mapping has failed %d\", ret);\n        assert(0);\n      }\n\n      if (EOS_LOGS_DEBUG)\n        for (auto map = pmd->local_children().begin();\n             map != pmd->local_children().end(); ++map) {\n          eos_static_debug(\"listing: %s [%#lx]\", map->first.c_str(), map->second);\n        }\n\n      // now flag as a complete listing\n      (*pmd)()->set_type((*pmd)()->MDLS);\n    }\n\n    if (pmd) {\n      pmd->Locker().UnLock();\n    }\n  }\n\n  if (pmd) {\n    return (*pmd)()->id();\n  } else {\n    return 0;\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::mdcflush(ThreadAssistant& assistant)\n{\n  ThreadAssistant::setSelfThreadName(\"metad::mdcflush\");\n\n  while (!assistant.terminationRequested()) {\n    {\n      mdflush.Lock();\n\n      mdqueue_current = 0;\n      stat.inodes_backlog_store(mdqueue.size());\n\n      while (mdqueue.size() == 0) {\n        // TODO(gbitzes): Fix this, so we don't need to poll. Have ThreadAssistant\n        // accept callbacks for when termination is requested, so we can wake up\n        // any condvar.\n        mdflush.Wait(1);\n\n        if (assistant.terminationRequested()) {\n          mdflush.UnLock();\n          return;\n        }\n      }\n\n      // TODO: add an optimzation to merge requests in the queue\n      auto it = mdflushqueue.begin();\n      const uint64_t ino = it->id();\n      std::string authid = it->authid();\n      fuse_id f_id = it->get_fuse_id();\n      mdx::md_op op = it->op();\n      eos_static_info(\"metacache::flush ino=%#lx flushqueue-size=%u\", ino,\n                      mdflushqueue.size());\n      eos_static_info(\"metacache::flush %s\", flushentry::dump(*it).c_str());\n      mdqueue_current = ino;\n      mdflush.UnLock();\n\n      if (assistant.terminationRequested()) {\n        return;\n      }\n\n      if (EOS_LOGS_DEBUG) {\n        eos_static_debug(\"metacache::flush ino=%016lx authid=%s op=%d\", ino,\n                         authid.c_str(), (int) op);\n      }\n\n      do {\n        shared_md md;\n\n        if (!mdmap.retrieveTS(ino, md)) {\n          eos_static_crit(\"metacache::flush failed to retrieve ino=%016lx\", ino);\n          continue;\n        }\n\n        eos_static_info(\"metacache::flush ino=%016lx\", (unsigned long long) ino);\n\n        if (op != metad::mdx::LSTORE) {\n          XrdSysMutexHelper mdLock(md->Locker());\n\n          if (!(*md)()->md_pino()) {\n            // when creating objects locally faster than pushed upstream\n            // we might not know the remote parent id when we insert a local\n            // creation request\n            shared_md pmd;\n\n            if (mdmap.retrieveTS((*md)()->pid(), pmd)) {\n              // TODO: check if we need to lock pmd? But then we have to enforce\n              // locking order child -> parent\n              uint64_t md_pino = (*pmd)()->md_ino();\n              eos_static_info(\"metacache::flush providing parent inode %016lx to %016lx\",\n                              (*md)()->id(), md_pino);\n              (*md)()->set_md_pino(md_pino);\n            } else {\n              eos_static_crit(\"metacache::flush ino=%016lx parent remote inode not known\",\n                              (unsigned long long) ino);\n            }\n          }\n        }\n\n        md->Locker().Lock();\n\n        if ((*md)()->id()) {\n          uint64_t removeentry = 0;\n          const std::string md_name = (*md)()->name();\n          {\n            int rc = 0;\n\n            if (op == metad::mdx::RM) {\n              (*md)()->set_operation((*md)()->DELETE);\n            } else {\n              (*md)()->set_operation((*md)()->SET);\n            }\n\n            if (((op == metad::mdx::ADD) ||\n                 (op == metad::mdx::UPDATE) ||\n                 (op == metad::mdx::RM)) &&\n                (*md)()->id() != 1) {\n              eos_static_info(\"metacache::flush backend::putMD - start\");\n              eos::fusex::md::TYPE mdtype = (*md)()->type();\n              (*md)()->set_type((*md)()->MD);\n\n              // push to backend\n              if ((rc = mdbackend->putMD(f_id, (*md)(), authid, &(md->Locker())))) {\n                eos_static_err(\"metacache::flush backend::putMD failed rc=%d\", rc);\n                // we just set an error code\n                //! inomap.erase_bwd((*md)()->id());\n                //! removeentry=(*md)()->id();\n                (*md)()->set_err(rc);\n              } else {\n                inomap.insert((*md)()->md_ino(), (*md)()->id());\n              }\n\n              if (md->getop() != metad::mdx::RM) {\n                md->setop_none();\n                (*md)()->clear_mv_authid();\n              }\n\n              (*md)()->set_type(mdtype);\n              md->Signal();\n              eos_static_info(\"metacache::flush backend::putMD - stop\");\n            }\n\n            if ((op == metad::mdx::ADD) || (op == metad::mdx::UPDATE) ||\n                (op == metad::mdx::LSTORE)) {\n              // TODO: local MD store is now disabled - delete this code\n              //! std::string mdstream;\n              //! md->SerializeToString(&mdstream);\n              //! EosFuse::Instance().getKV()->put(ino, mdstream);\n              md->Locker().UnLock();\n            } else {\n              md->Locker().UnLock();\n\n              if (op == metad::mdx::RM) {\n                // this step is coupled to the forget function, since we cannot\n                // forget an entry if we didn't process the outstanding KV changes\n                stat.inodes_deleted_dec();\n\n                if (EOS_LOGS_DEBUG) {\n                  eos_static_debug(\"count=%d(-%d) - ino=%#lx\", md->lookup_is(), 1, ino);\n                }\n\n                XrdSysMutexHelper mLock(md->Locker());\n\n                if (md->lookup_dec(1)) {\n                  // forget this inode\n                  removeentry = ino;\n                }\n              }\n            }\n          }\n\n          if (removeentry) {\n            shared_md pmd;\n\n            if (EOS_LOGS_DEBUG) {\n              eos_static_debug(\"delete md object - ino=%#lx\", removeentry);\n            }\n\n            {\n              if (EOS_LOGS_DEBUG) {\n                eos_static_debug(\"calling forget function %#lx\", removeentry);\n              }\n\n              forget(0, removeentry, 0);\n            }\n\n            {\n              if (pmd) {\n                XrdSysMutexHelper mmLock(pmd->Locker());\n                // we don't remote entries from the local deletion list because there could be\n                // a race condition of a thread doing MDLS overwriting the locally deleted entry\n                pmd->get_todelete().erase(eos::common::StringConversion::EncodeInvalidUTF8(\n                                            md_name));\n                pmd->Signal();\n              }\n            }\n          }\n        } else {\n          md->Locker().UnLock();\n        }\n      } while(0);\n\n      // done with the entry, remove it from the queue\n      mdflush.Lock();\n      it = mdflushqueue.begin();\n      mdflushqueue.erase(it);\n      mdqueue[ino]--;\n\n      // remove entries from the mdqueue, if their ref count is 0\n      if (!mdqueue[ino]) {\n        mdqueue.erase(ino);\n      }\n      mdqueue_current = 0;\n      mdflush.Broadcast();\n      mdflush.UnLock();\n    }\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::mdstackfree(ThreadAssistant& assistant)\n{\n  size_t cnt = 0;\n  int max_inodes = EosFuse::Instance().Config().options.inmemory_inodes;\n  ThreadAssistant::setSelfThreadName(\"metad::mdstackfree\");\n\n  while (!assistant.terminationRequested()) {\n    cnt++;\n    std::this_thread::sleep_for(std::chrono::milliseconds(500));\n\n    // do this ~every 64 seconds\n    if (!(cnt % 128)) {\n      EosFuse::Instance().Tracker().clean();\n    }\n\n    // do this ~every 128 seconds\n    if (!(cnt % 256)) {\n      XrdSysMutexHelper mLock(mdmap);\n\n      for (auto it = mdmap.begin(); it != mdmap.end();) {\n        if (!it->second) {\n          it++;\n          continue;\n        }\n\n        // Try if we can acquire a md lock, if yes, then remove them\n        // from the map & LRU if not,we try the next cycle\n        std::optional<uint64_t> pid;\n\n        if (it->second->Locker().CondLock()) {\n          pid = it->second->pid();\n          it->second->Locker().UnLock();\n        }\n\n        // if the parent is gone, we can remove the child\n        if ((pid && !mdmap.count(*pid)) &&\n            (!S_ISDIR((*(it->second))()->mode()) || it->second->deleted())) {\n          eos_static_debug(\"removing orphaned inode from mdmap ino=%#lx path=%s\",\n                           it->first, (*(it->second))()->fullpath().c_str());\n          mdmap.lru_remove(it->first);\n          it = mdmap.erase(it);\n          stat.inodes_dec();\n        } else {\n          if (it->second->deleted()) {\n            if ((!has_flush(it->first)) &&\n                (!EosFuse::Instance().datas.has(it->first))) {\n              eos_static_debug(\"removing deleted inode from mdmap ino=%#lx path=%s\",\n                               it->first, (*(it->second))()->fullpath().c_str());\n              mdmap.lru_remove(it->first);\n              it = mdmap.erase(it);\n              stat.inodes_dec();\n            } else {\n              it++;\n            }\n          } else {\n            it++;\n          }\n        }\n      }\n    } //end mLock(mdmap)\n\n    if (!EosFuse::Instance().Config().mdcachedir.empty()) {\n      // level the inodes stored in memory and eventually swap out into kv store\n      int swap_out_inodes = 0 ;\n\n      do {\n        swap_out_inodes = mdmap.sizeTS() - max_inodes -\n                          EosFuse::Instance().mds.stats().inodes_stacked();\n\n        if (swap_out_inodes > 0) {\n          eos_static_info(\"swap-out %d inodes\", swap_out_inodes);\n          // grab the last lru inode and swap out\n          mdmap.Lock();\n          mdmap.lru_dump();\n          uint64_t inode_to_swap = mdmap.lru_oldest();\n\n          if (!inode_to_swap) {\n            // nothing in the lru list anymore\n            stat.lru_resets_inc();\n            mdmap.lru_reset();\n            mdmap.UnLock();\n            break;\n          }\n\n          if (mdmap.count(inode_to_swap)) {\n            shared_md md = mdmap[inode_to_swap];\n\n            if ((md.use_count() > 2) ||\n                (md && md->LockTable().size())) {\n              eos_static_info(\"swap-out skipping referenced ino=%#llx ref-count=%lu\\n\",\n                              inode_to_swap,\n                              md.use_count());\n\n              if (md) {\n                mdmap.lru_update(inode_to_swap, md);\n              }\n\n              mdmap.UnLock();\n              continue;\n            }\n\n            if (md) {\n              eos_static_info(\"swap-out lru-removed ino=%#llx oldest=%#llx\", inode_to_swap,\n                              mdmap.lru_oldest());\n              mdmap.lru_remove(inode_to_swap);\n              mdmap[inode_to_swap] = 0;\n\n              if (mdmap.swap_out(inode_to_swap, md)) {\n                eos_static_err(\"swap-out failed for ino=%#llx\", inode_to_swap);\n              }\n            }\n          } else {\n            // the inode to be swapped isn't there, reset LRU list\n            mdmap.lru_remove(inode_to_swap);\n            stat.lru_resets_inc();\n            mdmap.lru_reset();\n          }\n\n          mdmap.UnLock();\n        }\n      } while ((swap_out_inodes > 0) &&\n               (!assistant.terminationRequested()));\n    }\n  }\n\n  return;\n}\n\n/* -------------------------------------------------------------------------- */\nbool\nmetad::determineLockOrder(shared_md md1, shared_md md2)\n{\n  // Determine lock order of _two_ md objects, which is not as trivial as it\n  // might seem:\n  //\n  // Children are _always_ locked before their parents!\n  // If and only if two md's are not related as in parent and child, we decide the\n  // order based on increasing inodes.\n  //\n  // Example 1: /a/b/c and /a/ -> /a/b/c locked first, as it's a child of /a/\n  // Example 2: /a/b/c and /a/b/d -> Decision based on increasing inode.\n  //\n  // This procedure is very expensive.. we should simplify if possible..\n  md1->Locker().Lock();\n  fuse_ino_t inode1 = (*md1)()->id();\n  md1->Locker().UnLock();\n  md2->Locker().Lock();\n  fuse_ino_t inode2 = (*md2)()->id();\n  md2->Locker().UnLock();\n\n  if (isChild(md1, inode2)) {\n    return true;\n  }\n\n  if (isChild(md2, inode1)) {\n    return false;\n  }\n\n  // Determine based on increasing inode.\n  return inode1 < inode2;\n}\n\n/* -------------------------------------------------------------------------- */\nbool\nmetad::isChild(shared_md potentialChild, fuse_ino_t parentId)\n{\n  XrdSysMutexHelper helper(potentialChild->Locker());\n\n  if ((*potentialChild)()->id() == 1 || (*potentialChild)()->id() == 0) {\n    return false;\n  }\n\n  if ((*potentialChild)()->id() == parentId) {\n    return true;\n  }\n\n  shared_md pmd;\n\n  if (!mdmap.retrieveTS((*potentialChild)()->pid(), pmd)) {\n    eos_static_warning(\"could not lookup parent ino=%d of %d when determining lock order..\",\n                       (*potentialChild)()->pid(), (*potentialChild)()->id());\n    return false;\n  }\n\n  helper.UnLock();\n  return isChild(pmd, parentId);\n}\n\n/* -------------------------------------------------------------------------- */\nint\nmetad::calculateDepth(shared_md md)\n{\n  if ((*md)()->id() == 1 || (*md)()->id() == 0) {\n    return 1;\n  }\n\n  fuse_ino_t pino = (*md)()->pid();\n\n  if (pino == 1 || pino == 0) {\n    return 2;\n  }\n\n  shared_md pmd;\n\n  if (!mdmap.retrieveTS(pino, pmd)) {\n    eos_static_warning(\"could not lookup parent ino=%d of %d when calculating depth..\",\n                       pino, (*md)()->id());\n    return -1;\n  }\n\n  XrdSysMutexHelper mmLock(pmd->Locker());\n  return calculateDepth(pmd) + 1;\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\nmetad::calculateLocalPath(shared_md md)\n{\n  std::string lpath = \"/\" + (*md)()->name();\n\n  if ((*md)()->id() == 1 || (*md)()->id() == 0) {\n    return \"/\";\n  }\n\n  fuse_ino_t pino = (*md)()->pid();\n\n  if (pino == 1 || pino == 0) {\n    lpath = \"/\";\n    lpath += (*md)()->name();\n    return lpath;\n  }\n\n  shared_md pmd;\n\n  if (!mdmap.retrieveTS(pino, pmd)) {\n    eos_static_warning(\"could not lookup parent ino=%d of %d when calculating depth..\",\n                       pino, (*md)()->id());\n    return \"\";\n  }\n\n  XrdSysMutexHelper mmLock(pmd->Locker());\n  return calculateLocalPath(pmd) + lpath;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::mdcallback(ThreadAssistant& assistant)\n{\n  bool shutdown = false;\n  std::string sendlog = \"\";\n  std::string stacktrace = \"\";\n  ThreadAssistant::setSelfThreadName(\"metad::mdcallback\");\n\n  while (!assistant.terminationRequested() || shutdown == false) {\n    mCb.Lock();\n\n    while (!mCbQueue.size()) {\n      mCb.WaitMS(1000);\n\n      if (assistant.terminationRequested() || shutdown == true) {\n        mCb.UnLock();\n        return;\n      }\n    }\n\n    auto it = mCbQueue.begin();\n    shared_response rsp = *it;\n    mCbQueue.erase(it);\n    mCb.UnLock();\n\n    if (rsp->type() == rsp->EVICT) {\n      eos_static_crit(\"evict message from MD server - instruction: %s\",\n                      rsp->evict_().reason().c_str());\n\n      if (rsp->evict_().reason().find(\"setlog\") != std::string::npos) {\n        if (rsp->evict_().reason().find(\"debug\") != std::string::npos) {\n          eos::common::Logging::GetInstance().SetLogPriority(LOG_DEBUG);\n        }\n\n        if (rsp->evict_().reason().find(\"info\") != std::string::npos) {\n          eos::common::Logging::GetInstance().SetLogPriority(LOG_INFO);\n        }\n\n        if (rsp->evict_().reason().find(\"error\") != std::string::npos) {\n          eos::common::Logging::GetInstance().SetLogPriority(LOG_ERR);\n        }\n\n        if (rsp->evict_().reason().find(\"notice\") != std::string::npos) {\n          eos::common::Logging::GetInstance().SetLogPriority(LOG_NOTICE);\n        }\n\n        if (rsp->evict_().reason().find(\"warning\") != std::string::npos) {\n          eos::common::Logging::GetInstance().SetLogPriority(LOG_WARNING);\n        }\n\n        if (rsp->evict_().reason().find(\"crit\") != std::string::npos) {\n          eos::common::Logging::GetInstance().SetLogPriority(LOG_CRIT);\n        }\n      } else  {\n        if (rsp->evict_().reason().find(\"stacktrace\") != std::string::npos) {\n          std::string stacktrace_file = EosFuse::Instance().Config().logfilepath;\n          stacktrace_file += \".strace\";\n          eos::common::StackTrace::GdbTrace(0, getpid(), \"thread apply all bt\",\n                                            stacktrace_file.c_str(), &stacktrace);\n          mCb.Lock();\n          mCbTrace = stacktrace;\n          mCb.UnLock();\n        } else {\n          if (rsp->evict_().reason().find(\"sendlog\") != std::string::npos) {\n            std::string refs;\n            XrdCl::Proxy::WriteAsyncHandler::DumpReferences(refs);\n            eos_static_warning(\"\\n%s\\n\", refs.c_str());\n            sendlog = \"\";\n            int logtagindex =\n              eos::common::Logging::GetInstance().GetPriorityByString(\"debug\");\n\n            for (int j = 0; j <= logtagindex; j++) {\n              for (int i = 1; i <= 512; i++) {\n                std::string logline;\n                eos::common::Logging::GetInstance().gMutex.Lock();\n                const char* log = eos::common::Logging::GetInstance().gLogMemory[j][\n                                    (eos::common::Logging::GetInstance().gLogCircularIndex[j] - i +\n                                     eos::common::Logging::GetInstance().gCircularIndexSize) %\n                                    eos::common::Logging::GetInstance().gCircularIndexSize].c_str();\n\n                if (log) {\n                  logline = log;\n                }\n\n                eos::common::Logging::GetInstance().gMutex.UnLock();\n\n                if (logline.length()) {\n                  sendlog += logline;\n                  sendlog += \"\\n\";\n                }\n              }\n            }\n\n            mCb.Lock();\n            mCbLog = sendlog;\n            mCb.UnLock();\n          } else {\n            if (rsp->evict_().reason().find(\"resetbuffer\") != std::string::npos) {\n              eos_static_warning(\"MGM asked us to reset the buffer in flight\");\n              XrdCl::Proxy::sWrBufferManager.reset();\n              XrdCl::Proxy::sRaBufferManager.reset();\n            } else if (rsp->evict_().reason().find(\"log2big\") != std::string::npos) {\n              // we were asked to truncate our logfile\n              EosFuse::Instance().truncateLogFile();\n            } else {\n              // suicide\n              if (rsp->evict_().reason().find(\"abort\") != std::string::npos) {\n                kill(getpid(), SIGABRT);\n              } else {\n                kill(getpid(), SIGTERM);\n              }\n\n              pause();\n            }\n          }\n        }\n      }\n    }\n\n    if (rsp->type() == rsp->DROPCAPS) {\n      eos_static_notice(\"MGM asked us to drop all known caps\");\n      // a newly started MGM requests this as a response to the first heartbeat\n      EosFuse::Instance().caps.reset();\n    }\n\n    if (rsp->type() == rsp->CONFIG) {\n      if (rsp->config_().hbrate()) {\n        eos_static_warning(\"MGM asked us to set our heartbeat interval to %d seconds, %s dentry-messaging, %s writesizeflush, %s appname, %s mdquery versions %s and server-version=%s\",\n                           rsp->config_().hbrate(),\n                           rsp->config_().dentrymessaging() ? \"enable\" : \"disable\",\n                           rsp->config_().writesizeflush() ?  \"enable\" : \"disable\",\n                           rsp->config_().appname() ? \"accepts\" : \"rejects\",\n                           rsp->config_().mdquery() ? \"accepts\" : \"rejects\",\n                           rsp->config_().hideversion() ? \"hidden\" : \"visible\",\n                           rsp->config_().serverversion().c_str());\n        XrdSysMutexHelper cLock(EosFuse::Instance().mds.ConfigMutex);\n        EosFuse::Instance().mds.dentrymessaging = rsp->config_().dentrymessaging();\n        EosFuse::Instance().mds.writesizeflush = rsp->config_().writesizeflush();\n        EosFuse::Instance().mds.appname = rsp->config_().appname();\n        EosFuse::Instance().mds.mdquery = rsp->config_().mdquery();\n        EosFuse::Instance().mds.hideversion = rsp->config_().hideversion();\n        EosFuse::Instance().mds.hb_interval = (int) rsp->config_().hbrate();\n\n        if (rsp->config_().serverversion().length()) {\n          EosFuse::Instance().mds.serverversion = rsp->config_().serverversion();\n        }\n      }\n    }\n\n    if (rsp->type() == rsp->DENTRY) {\n      uint64_t md_ino = rsp->dentry_().md_ino();\n      std::string authid = rsp->dentry_().authid();\n      std::string name = rsp->dentry_().name();\n      uint64_t ino = inomap.forward(md_ino);\n      uint64_t pt_mtime = rsp->dentry_().pt_mtime();\n      uint64_t pt_mtime_ns = rsp->dentry_().pt_mtime_ns();\n\n      if (rsp->dentry_().type() == rsp->dentry_().ADD) {\n      } else if (rsp->dentry_().type() == rsp->dentry_().REMOVE) {\n        eos_static_notice(\"remove-dentry: remote-ino=%#lx ino=%#lx clientid=%s authid=%s name=%s\",\n                          md_ino, ino, rsp->lease_().clientid().c_str(), authid.c_str(), name.c_str());\n\n        // remove directory entry\n        if (EosFuse::Instance().Config().options.md_kernelcache) {\n          kernelcache::inval_entry(ino, name);\n        }\n\n        shared_md pmd;\n\n        if (ino && mdmap.retrieveTS(ino, pmd)) {\n          {\n            XrdSysMutexHelper mLock(pmd->Locker());\n\n            if (pmd->local_children().count(\n                  eos::common::StringConversion::EncodeInvalidUTF8(name))) {\n\n              // as well as remote generated delete this case is possible\n              // for after a local create/remove/create cycle, in which case we will\n              // incorrectly remove name from local_children of the parent. So\n              // clear any local creator flag & listing so that lookup or listing\n              // will need to confirm with the backend.\n              (*pmd)()->set_creator(false);\n              if ((*pmd)()->type() == (*pmd)()->MDLS)  {\n                (*pmd)()->set_type((*pmd)()->MD);\n              }\n\n              pmd->local_children().erase(eos::common::StringConversion::EncodeInvalidUTF8(\n                                            name));\n              pmd->get_todelete().erase(eos::common::StringConversion::EncodeInvalidUTF8(\n                                          name));\n              (*pmd)()->set_nchildren((*pmd)()->nchildren() - 1);\n\n              if (pt_mtime || pt_mtime_ns) {\n                (*pmd)()->set_mtime(pt_mtime);\n                (*pmd)()->set_mtime_ns(pt_mtime_ns);\n                (*pmd)()->set_ctime(pt_mtime);\n                (*pmd)()->set_ctime_ns(pt_mtime_ns);\n              }\n            } else {\n\t      // remove delete entries if we receive an external delete\n\t      pmd->get_todelete().erase(eos::common::StringConversion::EncodeInvalidUTF8(\n\t\t\t\t\t\t\t\t\t\t\t name));\n\t    }\n\n            kernelcache::inval_inode(ino, false);\n          }\n        }\n      }\n    }\n\n    if (rsp->type() == rsp->REFRESH) {\n      uint64_t md_ino = rsp->refresh_().md_ino();\n      uint64_t ino = inomap.forward(md_ino);\n      mode_t mode = 0;\n      eos_static_notice(\"refresh-dentry: remote-ino=%#lx ino=%#lx\",\n                        md_ino, ino);\n      shared_md md;\n\n      // force meta data refresh\n      if (ino && mdmap.retrieveTS(ino, md)) {\n        XrdSysMutexHelper mLock(md->Locker());\n        md->force_refresh();\n        mode = (*md)()->mode();\n      }\n\n      if (EOS_LOGS_DEBUG) {\n        eos_static_debug(\"%s\", dump_md(md).c_str());\n      }\n\n      if (EosFuse::Instance().Config().options.md_kernelcache) {\n        eos_static_info(\"invalidate metadata cache for ino=%#lx\", ino);\n        kernelcache::inval_inode(ino, S_ISDIR(mode) ? false : true);\n      }\n    }\n\n    if (rsp->type() == rsp->LEASE) {\n      uint64_t md_ino = rsp->lease_().md_ino();\n      std::string authid = rsp->lease_().authid();\n      uint64_t ino = inomap.forward(md_ino);\n      eos_static_notice(\"lease: remote-ino=%#lx ino=%#lx clientid=%s authid=%s\",\n                        md_ino, ino, rsp->lease_().clientid().c_str(), authid.c_str());\n      shared_md check_md;\n\n      if (ino && mdmap.retrieveTS(ino, check_md)) {\n        std::string capid = cap::capx::capid(ino, rsp->lease_().clientid());\n\n        // wait that the inode is flushed out of the mdqueue\n        do {\n          mdflush.Lock();\n\n          if (mdqueue.count(ino)) {\n            mdflush.UnLock();\n            eos_static_info(\"lease: delaying cap-release remote-ino=%#lx ino=%#lx clientid=%s authid=%s\",\n                            md_ino, ino, rsp->lease_().clientid().c_str(), authid.c_str());\n            std::this_thread::sleep_for(std::chrono::milliseconds(25));\n\n            if (assistant.terminationRequested()) {\n              return;\n            }\n          } else {\n            mdflush.UnLock();\n            break;\n          }\n        } while (1);\n\n        eos_static_debug(\"\");\n        fuse_ino_t ino = EosFuse::Instance().getCap().forget(capid);\n        shared_md md;\n        bool is_locked = false;\n\n        if (mdmap.retrieveTS(ino, md)) {\n          is_locked = true;\n          md->Locker().Lock();\n        }\n\n        // invalidate children\n        if (md) {\n          if ((*md)()->id()) {\n            // force an update of the metadata with next access\n            eos_static_info(\"md=%16x\", (*md)()->id());\n            cleanup(md);\n\n            if (EOS_LOGS_DEBUG) {\n              eos_static_debug(\"%s\", dump_md(md).c_str());\n            }\n          } else {\n            if (is_locked) {\n              // in case this should somehow happen\n              md->Locker().UnLock();\n            }\n          }\n        }\n      } else {\n        // there might have been several caps and the first has wiped already the MD,\n        // still we want to remove the cap entry\n        std::string capid = cap::capx::capid(ino, rsp->lease_().clientid());\n        eos_static_debug(\"\");\n        EosFuse::Instance().getCap().forget(capid);\n      }\n    }\n\n    if (rsp->type() == rsp->CAP) {\n      std::string clientid = rsp->cap_().clientid();\n      uint64_t ino = inomap.forward(rsp->cap_().id());\n      cap::shared_cap cap = EosFuse::Instance().caps.get(ino, clientid);\n      eos_static_notice(\"cap-update: cap-id=%#lx %s\", rsp->cap_().id(),\n                        cap->dump().c_str());\n\n      if ((*cap)()->id()) {\n        EosFuse::Instance().caps.update_quota(cap, rsp->cap_()._quota());\n        eos_static_notice(\"cap-update: cap-id=%#lx %s\", rsp->cap_().id(),\n                          cap->dump().c_str());\n      }\n    }\n\n    if (rsp->type() == rsp->MD) {\n      fuse_id fuseid;\n      uint64_t md_ino = rsp->md_().md_ino();\n      std::string authid = rsp->md_().authid();\n      uint64_t ino = inomap.forward(md_ino);\n      eos_static_notice(\"md-update: remote-ino=%#lx ino=%#lx authid=%s\",\n                        md_ino, ino, authid.c_str());\n      // we get this when a file update/flush appeared\n      shared_md md;\n      int64_t bookingsize = 0;\n      uint64_t pino = 0;\n      mode_t mode = 0;\n      std::string md_clientid;\n      std::string old_name;\n\n      if (mdmap.retrieveTS(ino, md)) {\n        eos_static_notice(\"md-update: (existing) remote-ino=%#lx ino=%#lx authid=%s\",\n                          md_ino, ino, authid.c_str());\n\n        // updated file MD\n        if (EOS_LOGS_DEBUG) {\n          eos_static_debug(\"%s op=%d\", md->dump().c_str(), md->getop());\n        }\n\n        md->Locker().Lock();\n        bookingsize = rsp->md_().size() - (*md)()->size();\n        md_clientid = rsp->md_().clientid();\n        eos_static_info(\"md-update: %s %s\", (*md)()->name().c_str(),\n                        rsp->md_().name().c_str());\n\n        // check if this implies a rename\n        if ((*md)()->name() != rsp->md_().name()) {\n          old_name = (*md)()->name();\n        }\n\n        // verify that this record is newer than\n        if (rsp->md_().clock() >= (*md)()->clock()) {\n          eos_static_info(\"overwriting clock MD %#lx => %#lx\", (*md)()->clock(),\n                          rsp->md_().clock());\n          std::string fullpath = (*md)()->fullpath(); // keep the fullpath information\n          md->UpdateProtoFrom(rsp->md_());\n          (*md)()->set_creator(false);\n          (*md)()->set_bc_time(time(NULL));\n          (*md)()->set_fullpath(fullpath);\n        } else {\n          eos_static_warning(\"keeping clock MD %#lx => %#lx\", (*md)()->clock(),\n                             rsp->md_().clock());\n        }\n\n        (*md)()->clear_clientid();\n        pino = inomap.forward((*md)()->md_pino());\n        (*md)()->set_id(ino);\n        (*md)()->set_pid(pino);\n        mode = (*md)()->mode();\n\n        if (EOS_LOGS_DEBUG) {\n          eos_static_debug(\"%s op=%d\", md->dump().c_str(), md->getop());\n        }\n\n        // update the local store\n        update(fuseid, md, authid, true);\n        std::string name = (*md)()->name();\n\n\n\tmd->Locker().UnLock();\n\tshared_md pmd;\n\tif (pino && mdmap.retrieveTS(pino, pmd)) {\n\t  // clean-up stale enoent\n          {\n            XrdSysMutexHelper mLock(pmd->Locker());\n            pmd->local_enoent().erase(name);\n          }\n\t  // re-add this file with the new inode\n          XrdSysMutexHelper mLock(md->Locker());\n\t  add(0, pmd, md, authid, true);\n\t}\n\n        // adjust local quota\n        cap::shared_cap cap = EosFuse::Instance().caps.get(pino, md_clientid);\n\n\tif (S_ISREG(mode)) {\n\t  if ((*cap)()->id()) {\n\t    if (bookingsize >= 0) {\n\t      EosFuse::Instance().caps.book_volume(cap, (uint64_t) bookingsize);\n\t    } else {\n\t      EosFuse::Instance().caps.free_volume(cap, (uint64_t) - bookingsize);\n\t    }\n\t    EosFuse::instance().caps.book_inode(cap);\n\t  } else {\n\t    eos_static_debug(\"missing quota node for pino=%#lx and clientid=%s\",\n\t\t\t     pino, (*md)()->clientid().c_str());\n\t  }\n\t} else {\n\t  if (old_name.length()) {\n\t    // cleanup stale ENOENT entries\n\t    shared_md pmd;\n\t    if (pino && mdmap.retrieveTS(pino, pmd)) {\n              XrdSysMutexHelper mLock(pmd->Locker());\n\t      pmd->local_enoent().erase(name);\n\t    }\n\t  }\n\t}\n\n        // possibly invalidate kernel cache\n        if (EosFuse::Instance().Config().options.md_kernelcache ||\n            EosFuse::Instance().Config().options.data_kernelcache) {\n          eos_static_info(\"invalidate data cache for ino=%#lx\", ino);\n          kernelcache::inval_inode(ino, S_ISDIR(mode) ? false : true);\n        }\n\n        if (EosFuse::Instance().Config().options.md_kernelcache) {\n          if (old_name.length()) {\n            eos_static_info(\"invalidate previous name for ino=%#lx old-name=%s\", ino,\n                            old_name.c_str());\n            kernelcache::inval_entry(pino, old_name.c_str());\n\t    kernelcache::inval_entry(pino, name.c_str());\n          }\n\n          kernelcache::inval_inode(pino, false);\n        }\n\n        if (S_ISREG(mode)) {\n          // invalidate local disk cache\n          EosFuse::Instance().datas.invalidate_cache(ino);\n          eos_static_info(\"invalidate local disk cache for ino=%#lx\", ino);\n        }\n      } else {\n        eos_static_info(\"md-update: (new) remote-ino=%#lx ino=%#lx authid=%s\",\n                        md_ino, ino, authid.c_str());\n        // new file\n        md = std::make_shared<mdx>();\n        md->UpdateProtoFrom(rsp->md_());\n        (*md)()->set_id(md_ino);\n        insert(md, authid);\n        uint64_t md_pino = (*md)()->md_pino();\n        std::string md_clientid = (*md)()->clientid();\n        uint64_t md_size = (*md)()->size();\n        md->Locker().Lock();\n        uint64_t pino = inomap.forward(md_pino);\n        shared_md pmd;\n\n        if (pino && mdmap.retrieveTS(pino, pmd)) {\n          if ((*md)()->pt_mtime()) {\n            (*pmd)()->set_mtime((*md)()->pt_mtime());\n            (*pmd)()->set_mtime_ns((*md)()->pt_mtime_ns());\n            (*pmd)()->set_ctime((*md)()->pt_mtime());\n            (*pmd)()->set_ctime_ns((*md)()->pt_mtime_ns());\n          }\n\n          (*md)()->clear_pt_mtime();\n          (*md)()->clear_pt_mtime_ns();\n          inomap.insert((*md)()->md_ino(), (*md)()->id());\n          add(0, pmd, md, authid, true);\n          // adjust local quota\n          cap::shared_cap cap = EosFuse::Instance().caps.get(pino, md_clientid);\n\n          if ((*cap)()->id()) {\n            EosFuse::Instance().caps.book_volume(cap, md_size);\n            EosFuse::instance().caps.book_inode(cap);\n          } else {\n            eos_static_debug(\"missing quota node for pino=%#llx and clientid=%s\",\n                             pino, (*md)()->clientid().c_str());\n          }\n\n          const std::string md_name = (*md)()->name();\n          md->Locker().UnLock();\n\n          // possibly invalidate kernel cache for parent\n          if (EosFuse::Instance().Config().options.md_kernelcache) {\n            eos_static_info(\"invalidate md cache for ino=%016lx\", pino);\n            kernelcache::inval_entry(pino, md_name);\n            kernelcache::inval_inode(pino, false);\n            XrdSysMutexHelper mLock(pmd->Locker());\n            pmd->local_enoent().erase(md_name);\n          }\n        } else {\n          eos_static_err(\"missing parent mapping pino=%16x for ino%16x\",\n                         md_pino, md_ino);\n          md->Locker().UnLock();\n        }\n      }\n    }\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::mdcommunicate(ThreadAssistant& assistant)\n{\n  eos::fusex::container hb;\n  hb.mutable_heartbeat_()->set_name(zmq_name);\n  hb.mutable_heartbeat_()->set_host(zmq_clienthost);\n  hb.mutable_heartbeat_()->set_uuid(zmq_clientuuid);\n  hb.mutable_heartbeat_()->set_version(VERSION);\n  hb.mutable_heartbeat_()->set_protversion(FUSEPROTOCOLVERSION);\n  hb.mutable_heartbeat_()->set_pid((int32_t) getpid());\n  hb.mutable_heartbeat_()->set_starttime(time(NULL));\n  hb.mutable_heartbeat_()->set_leasetime(\n    EosFuse::Instance().Config().options.leasetime);\n  hb.mutable_heartbeat_()->set_mount(EosFuse::Instance().Config().localmountdir);\n  hb.mutable_heartbeat_()->set_automounted(\n    EosFuse::Instance().Config().options.automounted);\n  hb.mutable_heartbeat_()->set_appname(EosFuse::Instance().Config().appname);\n  hb.set_type(hb.HEARTBEAT);\n  size_t cnt = 0;\n  EosFuse::Instance().mds.hb_interval = 10;\n  bool shutdown = false;\n  bool first = true;\n  ThreadAssistant::setSelfThreadName(\"metad::mdcommunicate\");\n\n  while (!assistant.terminationRequested() || shutdown == false) {\n    try {\n      std::unique_lock<std::mutex> connectionMutex(zmq_socket_mutex);\n      eos_static_debug(\"\");\n      zmq::pollitem_t items[] = {\n        {static_cast<void*>(*z_socket), 0, ZMQ_POLLIN, 0}\n      };\n      struct timespec ts;\n      eos::common::Timing::GetTimeSpec(ts);\n\n      do {\n        // if there is a ZMQ reconnection to be done we release the ZQM socket mutex\n        if (zmq_wants_to_connect()) {\n          connectionMutex.unlock();\n          std::this_thread::sleep_for(std::chrono::milliseconds(100));\n          continue;\n        }\n\n        if (first) {\n          // we want to see the first hearteat directly after startup\n          first = false;\n          break;\n        }\n\n        // 10 milliseconds\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wdeprecated-declarations\"\n        zmq_poll(items, 1, 10);\n#pragma GCC diagnostic pop\n\n        if (assistant.terminationRequested()) {\n          shutdown = true;\n          EosFuse::Instance().caps.reset();\n          eos_static_notice(\"sending shutdown heartbeat message\");\n          hb.mutable_heartbeat_()->set_shutdown(true);\n          break;\n        }\n\n        if (items[0].revents & ZMQ_POLLIN) {\n          int rc;\n          int64_t more = 0;\n          size_t more_size = sizeof(more);\n          zmq_msg_t message;\n          rc = zmq_msg_init(&message);\n\n          if (rc) {\n            rc = 0;\n          }\n\n          do {\n            (void)zmq_msg_recv(&message, static_cast<void*>(*z_socket), 0);\n            zmq_getsockopt(static_cast<void*>(*z_socket), ZMQ_RCVMORE, &more, &more_size);\n          } while (more);\n\n          std::string s((const char*) zmq_msg_data(&message), zmq_msg_size(&message));\n          shared_response rsp = std::make_shared<eos::fusex::response>();\n          eos_static_notice(\"parsing response\");\n\n          if (rsp->ParseFromString(s)) {\n            mCb.Lock();\n            mCbQueue.push_back(rsp);\n            mCb.Signal();\n            mCb.UnLock();\n          } else {\n            eos_static_err(\"unable to parse message\");\n          }\n\n          zmq_msg_close(&message);\n        }\n\n        // leave the loop to send a heartbeat after the given interval\n        if ((eos::common::Timing::GetCoarseAgeInNs(&ts,\n             0) >= (EosFuse::Instance().mds.hb_interval * 1000000000ll))) {\n          break;\n        }\n      } while (1);\n\n      eos_static_debug(\"send\");\n      // prepare a heart-beat message\n      struct timespec tsnow;\n      eos::common::Timing::GetTimeSpec(tsnow);\n      hb.mutable_heartbeat_()->set_clock(tsnow.tv_sec);\n      hb.mutable_heartbeat_()->set_clock_ns(tsnow.tv_nsec);\n      mCb.Lock();\n\n      if (mCbLog.length()) {\n        hb.mutable_heartbeat_()->set_log(mCbLog);\n        mCbLog.clear();\n      }\n\n      if (mCbTrace.length()) {\n        hb.mutable_heartbeat_()->set_trace(mCbTrace);\n        mCbTrace.clear();\n      }\n\n      mCb.UnLock();\n\n      if (!(cnt % (60 / EosFuse::Instance().mds.hb_interval))) {\n        // we send a statistics update every 60 heartbeats\n        EosFuse::Instance().getHbStat((*hb.mutable_statistics_()));\n        std::string blocker;\n        std::string origin;\n        uint64_t blocker_inode;\n        size_t blocked_ops;\n        bool root_blocked;\n        hb.mutable_statistics_()->set_blockedms(\n          EosFuse::Instance().Tracker().blocked_ms(blocker, blocker_inode, origin,\n              blocked_ops, root_blocked));\n        hb.mutable_statistics_()->set_blockedfunc(blocker + std::string(\":\") + origin);\n        hb.mutable_statistics_()->set_blockedops(blocked_ops);\n        hb.mutable_statistics_()->set_blockedroot(root_blocked);\n      } else {\n        hb.clear_statistics_();\n      }\n\n      {\n        // add caps to be revoked\n        XrdSysMutexHelper rLock(EosFuse::Instance().getCap().get_revocationLock());\n        // clear the hb map\n        hb.mutable_heartbeat_()->mutable_authrevocation()->clear();\n        auto rmap = hb.mutable_heartbeat_()->mutable_authrevocation();\n        cap::revocation_set_t& revocationset =\n          EosFuse::Instance().getCap().get_revocationmap();\n        size_t n_revocations = 0;\n\n        for (auto it = revocationset.begin(); it != revocationset.end();) {\n          (*rmap)[*it] = 0;\n          eos_static_notice(\"cap-revocation: authid=%s\", it->c_str());\n          it = revocationset.erase(it);\n          n_revocations++;\n\n          if (n_revocations > 32 * 1024) {\n            eos_static_notice(\"stopped revocations after 32k entries\");\n            break;\n          }\n        }\n\n        eos_static_debug(\"cap-revocation: map-size=%u\",\n                         revocationset.size());\n      }\n\n      std::string hbstream;\n      zmq::message_t hb_msg;\n      hb.SerializeToString(&hbstream);\n      hb_msg.rebuild(hbstream.c_str(), hbstream.length());\n\n      if (!z_socket->send(hb_msg, zmq::send_flags::none)) {\n        eos_static_err(\"err sending heartbeat: hbstream.c_str()=%s, hbstream.length()=%d, hbstream:hex=%s\",\n                       hbstream.c_str(), hbstream.length(),\n                       eos::common::stringToHex(hbstream).c_str());\n      } else {\n        eos_static_debug(\"debug sending heartbeat: hbstream.c_str()=%s, hbstream.length()=%d, hbstream:hex=%s\",\n                         hbstream.c_str(), hbstream.length(),\n                         eos::common::stringToHex(hbstream).c_str());\n        last_heartbeat = time(NULL);\n      }\n\n      if (!is_visible()) {\n        set_is_visible(1);\n      }\n\n      hb.mutable_heartbeat_()->clear_log();\n      hb.mutable_heartbeat_()->clear_trace();\n    } catch (std::exception& e) {\n      eos_static_err(\"catched exception %s\", e.what());\n    }\n\n    cnt++;\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::vmap::insert(fuse_ino_t a, fuse_ino_t b)\n{\n  // weonly store ino=1 mappings\n  if ((a != 1) &&\n      (b != 1)) {\n    return;\n  }\n\n  eos_static_info(\"inserting %llx <=> %llx\", a, b);\n  XrdSysMutexHelper mLock(mMutex);\n\n  if (fwd_map.count(a) && fwd_map[a] == b) {\n    return;\n  }\n\n  if (bwd_map.count(b)) {\n    fwd_map.erase(bwd_map[b]);\n  }\n\n  fwd_map[a] = b;\n  bwd_map[b] = a;\n}\n\n/* -------------------------------------------------------------------------- */\nstd::string\nmetad::vmap::dump()\n{\n  //XrdSysMutexHelper mLock(this);\n  std::string sout;\n  char stime[1024];\n  snprintf(stime, sizeof(stime), \"%lu this=%llx forward=%lu backward=%lu\",\n           time(NULL), (unsigned long long) this, fwd_map.size(), bwd_map.size());\n  sout += stime;\n  sout += \"\\n\";\n\n  for (auto it = fwd_map.begin(); it != fwd_map.end(); it++) {\n    char out[1024];\n    snprintf(out, sizeof(out), \"%16lx => %16lx\\n\", it->first, it->second);\n    sout += out;\n  }\n\n  for (auto it = bwd_map.begin(); it != bwd_map.end(); it++) {\n    char out[1024];\n    snprintf(out, sizeof(out), \"%16lx <= %16lx\\n\", it->first, it->second);\n    sout += out;\n  }\n\n  sout += \"end\\n\";\n  return sout;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::vmap::erase_fwd(fuse_ino_t lookup)\n{\n  XrdSysMutexHelper mLock(mMutex);\n\n  if (fwd_map.count(lookup)) {\n    bwd_map.erase(fwd_map[lookup]);\n  }\n\n  fwd_map.erase(lookup);\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::vmap::erase_bwd(fuse_ino_t lookup)\n{\n  XrdSysMutexHelper mLock(mMutex);\n\n  if (bwd_map.count(lookup)) {\n    fwd_map.erase(bwd_map[lookup]);\n  }\n\n  bwd_map.erase(lookup);\n}\n\n/* -------------------------------------------------------------------------- */\nfuse_ino_t\nmetad::vmap::forward(fuse_ino_t lookup)\n{\n  XrdSysMutexHelper mLock(mMutex);\n  auto it = fwd_map.find(lookup);\n  fuse_ino_t ino = (it == fwd_map.end()) ? 0 : it->second;\n\n  if (!ino) {\n    return lookup;\n  }\n\n  return ino;\n}\n\n/* -------------------------------------------------------------------------- */\nfuse_ino_t\nmetad::vmap::backward(fuse_ino_t lookup)\n{\n  XrdSysMutexHelper mLock(mMutex);\n  auto it = bwd_map.find(lookup);\n  return (it == bwd_map.end()) ? lookup : it->second;\n}\n\n\n/* -------------------------------------------------------------------------- */\nsize_t\nmetad::pmap::sizeTS()\n{\n  XrdSysMutexHelper mLock(this);\n  return size();\n}\n\n/* -------------------------------------------------------------------------- */\nbool\nmetad::pmap::retrieveOrCreateTS(fuse_ino_t ino, shared_md& ret)\n{\n  XrdSysMutexHelper mLock(this);\n\n  if (this->retrieve(ino, ret)) {\n    return false;\n  }\n\n  ret = std::make_shared<mdx>();\n\n  if (ino) {\n    (*this)[ino] = ret;\n  }\n\n  return true;\n}\n\n/* -------------------------------------------------------------------------- */\nbool\nmetad::pmap::retrieveTS(fuse_ino_t ino, shared_md& ret)\n{\n  XrdSysMutexHelper mLock(this);\n  return this->retrieve(ino, ret);\n}\n\n/* -------------------------------------------------------------------------- */\nbool\nmetad::pmap::retrieve(fuse_ino_t ino, shared_md& ret)\n{\n  auto it = this->find(ino);\n\n  if (it == this->end()) {\n    if (!ret) {\n      ret = std::make_shared<mdx>();\n      (*ret)()->set_err(ENOENT);\n    }\n\n    return false;\n  }\n\n  shared_md md = it->second;\n  eos_static_debug(\"retc=%x\", (bool)(md));\n\n  if (!md) {\n    md = std::make_shared<mdx>();\n    // swap-in this inode\n    const int rc = swap_in(ino, md);\n\n    if (rc) {\n      eos_static_crit(\"failed to swap-in ino=%#llx\", ino);\n\n      if (!ret) {\n        ret = std::make_shared<mdx>();\n        (*ret)()->set_err(rc);\n      }\n\n      return false;\n    }\n\n    // attach the new object\n    (*this)[ino] = md;\n    // add to the lru list\n    lru_add(ino, md);\n  }\n\n  ret = md;\n  // update lru entry whenever we retrieve something\n  lru_update(ino, ret);\n  return true;\n}\n\n/* -------------------------------------------------------------------------- */\nuint64_t\nmetad::pmap::lru_oldest() const\n{\n  return lru_last;\n}\n\n/* -------------------------------------------------------------------------- */\nuint64_t\nmetad::pmap::lru_newest() const\n{\n  return lru_first;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::pmap::lru_add(fuse_ino_t ino, shared_md md)\n{\n  if (ino <= 1) {\n    return;\n  }\n\n  md->set_lru_prev(lru_first);\n  md->set_lru_next(0);\n\n  // lru list insert with outside lock handling\n  if (this->count(lru_first)) {\n    if ((*this)[lru_first]) {\n      // connect the new inode to the head of the lru list\n      (*this)[lru_first]->set_lru_next(ino);\n    } else {\n      // points to swapped-out entry\n      lru_last = ino;\n    }\n  }\n\n  lru_first = ino;\n\n  if (!lru_last) {\n    lru_last = ino;\n  }\n\n  eos_static_info(\"ino=%#llx first=%#llx last=%#llx prev=%llx next=%#llx\", ino,\n                  lru_first, lru_last, md->lru_prev(), md->lru_next());\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::pmap::lru_remove(fuse_ino_t ino)\n{\n  if (ino <= 1) {\n    return;\n  }\n\n  uint64_t prev = 0;\n  uint64_t next = 0;\n\n  if (EOS_LOGS_DEBUG)\n    eos_static_debug(\"ino=%#llx first=%#llx last=%#llx\", ino,\n                     lru_first, lru_last);\n\n  // lru list handling with outside lock handling\n  if (this->count(ino)) {\n    shared_md smd = (*this)[ino];\n\n    if (smd) {\n      prev = (*this)[ino]->lru_prev();\n      next = (*this)[ino]->lru_next();\n\n      if (this->count(prev) && (*this)[prev]) {\n        (*this)[prev]->set_lru_next(next);\n      } else {\n        // this is the tail of the LRU list\n        lru_last = next;\n\n        if (next && this->count(next) && (*this)[next]) {\n          (*this)[next]->set_lru_prev(0);\n        }\n      }\n\n      if (this->count(next) && (*this)[next]) {\n        (*this)[next]->set_lru_prev(prev);\n      } else {\n        // this is the head of the LRU list\n        lru_first = prev;\n\n        if (prev && this->count(prev) && (*this)[prev]) {\n          (*this)[prev]->set_lru_next(0);\n        }\n      }\n    }\n\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"last:%#llx => %#llx (prev=%#llx)\", lru_last, next, prev);\n    }\n  }\n\n  if (EOS_LOGS_DEBUG)\n    eos_static_debug(\"ino=%#llx first=%#llx last=%#llx prev=%#llx next=%#llx\", ino,\n                     lru_first, lru_last, prev, next);\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::pmap::lru_update(fuse_ino_t ino, shared_md md)\n{\n  if (ino == 1) {\n    return;\n  }\n\n  if (lru_first == ino) {\n    return;\n  }\n\n  if (EOS_LOGS_DEBUG)\n    eos_static_debug(\"ino=%#llx first=%#llx last=%#llx\", ino,\n                     lru_first, lru_last);\n\n  // move an lru item to the head of the list\n  uint64_t prev = md->lru_prev();\n  uint64_t next = md->lru_next();\n\n  if (this->count(prev) && (*this)[prev]) {\n    (*this)[prev]->set_lru_next(next);\n  } else {\n    if (next) {\n      lru_last = next;\n    } else {\n      lru_last = ino;\n    }\n  }\n\n  if (this->count(next) && (*this)[next]) {\n    (*this)[next]->set_lru_prev(prev);\n  }\n\n  if (this->count(lru_first) && (*this)[lru_first]) {\n    (*this)[lru_first]->set_lru_next(ino);\n    md->set_lru_prev(lru_first);\n    md->set_lru_next(0);\n    lru_first = ino;\n  }\n\n  if (EOS_LOGS_DEBUG)\n    eos_static_debug(\"ino=%#llx first=%#llx last=%#llx prev=%#llx next=%#llx\", ino,\n                     lru_first, lru_last, prev, next);\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::pmap::lru_dump(bool force)\n{\n  if (!EOS_LOGS_DEBUG && !force) {\n    return;\n  }\n\n  uint64_t start = lru_first;\n  std::stringstream ss;\n  size_t cnt = 0;\n  size_t max = this->size();\n  ss << std::endl;\n\n  do {\n    if (this->count(start) && (*this)[start]) {\n      shared_md md = (*this)[start];\n      ss << \"[ \" << std::setw(16) << std::hex  << md->lru_next();\n      ss << \" <- \";\n      ss << std::setw(16) << std::hex << start;\n      ss << \" -> \";\n      ss << std::setw(16) << std::hex << md->lru_prev();\n      ss << \" ]\"    << std::endl;\n\n      if (start == md->lru_prev()) {\n        eos_static_crit(\"corruption in list\");\n        break;\n      }\n\n      start = md->lru_prev();\n      cnt++;\n    } else {\n      start = 0;\n    }\n  } while (start && (cnt < max));\n\n  if (force) {\n    std::cerr << ss.str();\n    eos_static_crit(\"first=%#llx last=%#llx cnt=%d max=%d\",\n                    lru_first, lru_last, cnt, max);\n  } else {\n    std::cerr << ss.str();\n    eos_static_debug(\"first=%#llx last=%#llx\",\n                     lru_first, lru_last);\n  }\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::pmap::lru_reset()\n{\n  eos_static_crit(\"resetting LRU list\");\n  // force an output of that list\n  lru_dump(true);\n  // wipe lru list\n  this->lru_first = this->lru_last = 0;\n  std::map<fuse_ino_t, shared_md>::reverse_iterator it;\n\n  for (it = (*this).rbegin(); it != (*this).rend(); ++it) {\n    shared_md md = (*this)[it->first];\n\n    if (md) {\n      md->set_lru_prev(0);\n      md->set_lru_next(0);\n    }\n  }\n\n  // recreate lru list in backward inode order\n  for (it = (*this).rbegin(); it != (*this).rend(); ++it) {\n    shared_md md = (*this)[it->first];\n\n    if (md) {\n      lru_add(it->first, md);\n    }\n  }\n\n  // done\n}\n\n/* -------------------------------------------------------------------------- */\nint\nmetad::mdx::state_serialize(std::string& mdsstream)\n{\n  eos::fusex::md_state state;\n  // serialize in-memory state into md_state and then into string\n  state.set_op(op);\n  state.set_lookup_cnt(lookup_cnt);\n  state.set_cap_cnt(cap_cnt);\n  state.set_opendir_cnt(opendir_cnt);\n  state.set_lock_remote(lock_remote);\n  state.set_refresh(refresh);\n  state.set_rmrf(rmrf);\n\n  for (auto it = todelete.begin(); it != todelete.end() ; ++it) {\n    (*(state.mutable_todelete()))[it->first] = it->second;\n  }\n\n  for (auto it = _local_children.begin(); it != _local_children.end() ; ++it) {\n    (*(state.mutable_children()))[it->first] = it->second;\n  }\n\n  for (auto it = _local_enoent.begin(); it != _local_enoent.end(); ++it) {\n    (*(state.mutable_enoent())) [*it] = 0;\n  }\n\n  if (!state.SerializeToString(&mdsstream)) {\n    return EFAULT;\n  }\n\n  return 0;\n}\n\n\n/* -------------------------------------------------------------------------- */\nint\nmetad::mdx::state_deserialize(std::string& mdsstream)\n{\n  eos::fusex::md_state state;\n\n  // deserialize in-memory state from string\n  if (!state.ParseFromString(mdsstream)) {\n    return EFAULT;\n  }\n\n  op = (metad::mdx::md_op)state.op();\n  lookup_cnt = state.lookup_cnt();\n  cap_cnt = state.cap_cnt();\n  opendir_cnt = state.opendir_cnt();\n  lock_remote = state.lock_remote();\n  refresh = state.refresh();\n  rmrf = state.rmrf();\n\n  for (auto it = state.todelete().begin(); it != state.todelete().end(); ++it) {\n    todelete[it->first] = it->second;\n  }\n\n  for (auto it = state.children().begin(); it != state.children().end(); ++it) {\n    _local_children[it->first] = it->second;\n  }\n\n  for (auto it = state.enoent().begin(); it != state.enoent().end(); ++it) {\n    _local_enoent.insert(it->first);\n  }\n\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\nmetad::pmap::swap_out(fuse_ino_t ino, shared_md md)\n{\n  // serialize an in-memory md object into the kv store\n  std::string mdstream;\n  std::string mdsstream;\n\n  if (!(*md)()->SerializeToString(&mdstream)) {\n    return EFAULT;\n  }\n\n  if (md->state_serialize(mdsstream)) {\n    return EFAULT;\n  }\n\n  if (store) {\n    std::string md_key = \"md.\";\n    md_key += std::to_string(ino);\n\n    if (store->put(md_key, mdstream)) {\n      return EIO;\n    }\n\n    std::string md_state_key = \"mds.\";\n    md_state_key += std::to_string(ino);\n\n    if (store->put(md_state_key, mdsstream)) {\n      return EIO;\n    }\n  }\n\n  EosFuse::Instance().mds.stats().inodes_stacked_inc();\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\nmetad::pmap::swap_in(fuse_ino_t ino, shared_md md)\n{\n  // deserialize an in-memory md object from the kv store\n  std::string mdstream;\n  std::string mdsstream;\n\n  if (store) {\n    std::string md_key = \"md.\";\n    md_key += std::to_string(ino);\n\n    if (store->get(md_key, mdstream)) {\n      eos_static_err(\"unable to swap-in md ino=%16x errno=%d\", ino, EIO);\n      return EIO;\n    }\n\n    if (!(*md)()->ParseFromString(mdstream)) {\n      eos_static_err(\"unable to swap-in md ino=%16x errno=%d\", ino, EFAULT);\n      return EFAULT;\n    }\n\n    std::string md_state_key = \"mds.\";\n    md_state_key += std::to_string(ino);\n\n    if (store->get(md_state_key, mdsstream)) {\n      eos_static_err(\"unable to swap-in md-state ino=%16x errno=%d\", ino, EIO);\n      return EIO;\n    }\n\n    if (md->state_deserialize(mdsstream)) {\n      eos_static_err(\"unable to swap-in md-state ino=%16x errno=%d\", ino, EFAULT);\n      return EFAULT;\n    }\n  }\n\n  EosFuse::Instance().mds.stats().inodes_stacked_dec();\n  return 0;\n}\n\n/* -------------------------------------------------------------------------- */\nint\nmetad::pmap::swap_rm(fuse_ino_t ino)\n{\n  // delete from the external KV store\n  if (store) {\n    std::string md_key = \"md.\";\n    md_key += std::to_string(ino);\n\n    if (store->erase(md_key)) {\n      return EIO;\n    }\n\n    std::string md_state_key = \"mds.\";\n    md_state_key += std::to_string(ino);\n\n    if (store->erase(md_state_key)) {\n      return EIO;\n    }\n  }\n\n  return 0;\n}\n\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::pmap::insertTS(fuse_ino_t ino, shared_md& md)\n{\n  XrdSysMutexHelper mLock(this);\n  const bool exists = this->count(ino);\n  bool same = false;\n  if (exists) {\n    // test is for equality of md pointer in the shared_ptr\n    same = ((*this)[ino] == md);\n  }\n\n  if (exists && !same) {\n    // remove from lru if an md entry already exists for this ino\n    // but we're replacing it with a different one.\n    lru_remove(ino);\n  }\n\n  (*this)[ino] = md;\n  // lru list handling\n\n  if (!same) {\n    lru_add(ino, md);\n  }\n\n  lru_dump();\n}\n\n/* -------------------------------------------------------------------------- */\nbool\nmetad::pmap::eraseTS(fuse_ino_t ino)\n{\n  XrdSysMutexHelper mLock(this);\n  // lru list handling\n  lru_remove(ino);\n  bool exists = false;\n  auto it = this->find(ino);\n\n  if ((it != this->end()) && it->first) {\n    exists = true;\n  }\n\n  if (exists && !it->second) {\n    // deletion of a stacked inode has to be accounted for\n    EosFuse::Instance().mds.stats().inodes_stacked_dec();\n  }\n\n  if (exists) {\n    this->erase(it);\n  }\n\n  swap_rm(ino); // ignore return code\n  return exists;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nmetad::pmap::retrieveWithParentTS(fuse_ino_t ino, shared_md& md, shared_md& pmd,\n                                  std::string& md_name)\n{\n  // Atomically retrieve md objects for an inode, and its parent.\n  while (true) {\n    // In this particular case, we need to first lock mdmap, and then\n    // md.. The following algorithm is meant to avoid deadlocks with code\n    // which locks md first, and then mdmap.\n    md.reset();\n    pmd.reset();\n    md_name.clear();\n    XrdSysMutexHelper mLock(this);\n\n    if (!retrieve(ino, md)) {\n      return; // ino not there, nothing to do\n    }\n\n    // md has been found. Can we lock it?\n    if (md->Locker().CondLock()) {\n      // Success!\n      retrieve((*md)()->pid(), pmd);\n      md_name = (*md)()->name();\n      md->Locker().UnLock();\n      return;\n    }\n\n    // Nope, unlock mdmap and try again.\n    mLock.UnLock();\n    std::this_thread::sleep_for(std::chrono::milliseconds(1));\n  }\n}\n"
  },
  {
    "path": "fusex/md/md.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file md.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief meta data handling class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_MD_HH_\n#define FUSE_MD_HH_\n\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <fcntl.h>\n#include \"llfusexx.hh\"\n#include \"fusex/fusex.pb.h\"\n#include \"backend/backend.hh\"\n#include \"common/ConcurrentQueue.hh\"\n#include \"common/Logging.hh\"\n#include \"common/RWMutex.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/SymKeys.hh\"\n#include \"kv/kv.hh\"\n#include \"misc/FuseId.hh\"\n#include <XrdSys/XrdSysPthread.hh>\n#include <memory>\n#include <map>\n#include <set>\n#include <deque>\n#include <vector>\n#include <atomic>\n#include <string>\n#include <exception>\n#include <stdexcept>\n#include <sys/statvfs.h>\n\n#ifdef HAVE_DEFAULT_ZMQ\n#include <zmq.hpp>\n#else\n#include \"utils/zmq.hpp\"\n#endif\n\nclass metad\n{\npublic:\n\n  class mdx\n  //----------------------------------------------------------------------------\n  {\n  public:\n\n    // local operations\n\n    enum md_op {\n      ADD, UPDATE, RM, SETSIZE, LSTORE, NONE\n    };\n\n    mdx() : mSync(1)\n    {\n      setop_add();\n      lookup_cnt.store(0, std::memory_order_seq_cst);\n      opendir_cnt.store(0, std::memory_order_seq_cst);\n      lock_remote = true;\n      cap_count_set(0);\n      clear_refresh();\n      rmrf = false;\n      inline_size = 0;\n      _lru_prev.store(0, std::memory_order_seq_cst);\n      _lru_next.store(0, std::memory_order_seq_cst);\n    }\n\n    mdx(fuse_ino_t ino) : mdx()\n    {\n      proto.set_id(ino);\n    }\n\n    // make sure nobody copies us as we some members that will\n    // likely lead to problems if they are copied, e.g. XrdSysMutex\n    mdx(const mdx& other) = delete;\n    mdx& operator=(const mdx& other) = delete;\n\n    void UpdateProtoFrom(const eos::fusex::md &src) {\n      // Typically this method is used to copy a metadata entry received\n      // from the remote to our member protobuf object: However id and pid\n      // are usually 0 (unset) in src since they are only meaningful locally.\n      // The caller will explictly reset them in our protobuf. However they\n      // are accessed in many places without lock. Furthermore, a protobuf\n      // copy (with = operator) or CopyFrom() will zero all current values\n      // before merging the new values, leaving race where 0 may be read.\n      // For now we avoid resettig id and pid to 0 here and explictly copy\n      // all the other elements.\n\n      if (src.id() != 0 && proto.id() != src.id()) {\n        proto.set_id(src.id());\n      }\n      if (src.pid() != 0 && proto.pid() != src.pid()) {\n        proto.set_pid(src.pid());\n      }\n\n      proto.set_ctime(src.ctime());\n      proto.set_ctime_ns(src.ctime_ns());\n      proto.set_mtime(src.mtime());\n      proto.set_mtime_ns(src.mtime_ns());\n      proto.set_atime(src.atime());\n      proto.set_atime_ns(src.atime_ns());\n      proto.set_btime(src.btime());\n      proto.set_btime_ns(src.btime_ns());\n      proto.set_ttime(src.ttime());\n      proto.set_ttime_ns(src.ttime_ns());\n      proto.set_pmtime(src.pmtime());\n      proto.set_pmtime_ns(src.pmtime_ns());\n      proto.set_size(src.size());\n      proto.set_uid(src.uid());\n      proto.set_gid(src.gid());\n      proto.set_mode(src.mode());\n      proto.set_nlink(src.nlink());\n      proto.set_name(src.name());\n      proto.set_target(src.target());\n      proto.set_authid(src.authid());\n      proto.set_clientid(src.clientid());\n      proto.set_clientuuid(src.clientuuid());\n      proto.set_clock(src.clock());\n      proto.set_reqid(src.reqid());\n      proto.set_md_ino(src.md_ino());\n      proto.set_md_pino(src.md_pino());\n      proto.set_operation(src.operation());\n      proto.set_type(src.type());\n      proto.set_err(src.err());\n      *proto.mutable_attr() = src.attr();\n      *proto.mutable_children() = src.children();\n      *proto.mutable_capability() = src.capability();\n      proto.set_implied_authid(src.implied_authid());\n      *proto.mutable_flock() = src.flock();\n      proto.set_nchildren(src.nchildren());\n      proto.set_fullpath(src.fullpath());\n      proto.set_pt_mtime(src.pt_mtime());\n      proto.set_pt_mtime_ns(src.pt_mtime_ns());\n      proto.set_creator(src.creator());\n      proto.set_mv_authid(src.mv_authid());\n      proto.set_bc_time(src.bc_time());\n      proto.set_opflags(src.opflags());\n    }\n\n    virtual ~mdx() { }\n\n    XrdSysMutex& Locker()\n    {\n      return mLock;\n    }\n\n    void convert(fuse_entry_param& e, double lifetime = 180.0);\n    std::string dump();\n    static std::string dump(struct fuse_entry_param& e);\n\n    void setop_delete()\n    {\n      op = RM;\n    }\n\n    void setop_add()\n    {\n      op = ADD;\n    }\n\n    void setop_setsize()\n    {\n      op = SETSIZE;\n    }\n\n    void setop_localstore()\n    {\n      op = LSTORE;\n    }\n\n    void setop_update()\n    {\n      op = UPDATE;\n    }\n\n    void setop_none()\n    {\n      op = NONE;\n    }\n\n    void lookup_inc()\n    {\n      // atomic operation, no need to lock before calling\n      int prevLookup = lookup_cnt.fetch_add(1, std::memory_order_seq_cst);\n      eos_static_info(\"ino=%16x lookup=%d => lookup=%d\", (*this)()->id(), prevLookup,\n                      prevLookup + 1);\n    }\n\n    bool lookup_dec(int n)\n    {\n      // atomic operation, no need to lock before calling\n      int prevLookup = lookup_cnt.fetch_sub(n, std::memory_order_seq_cst);\n\n      if (prevLookup - n > 0) {\n        return false;\n      }\n\n      return true;\n    }\n\n    int lookup_is()\n    {\n      return lookup_cnt.load();\n    }\n\n    void opendir_inc()\n    {\n      // atomic operation, no need to lock before calling\n      int prevOpendir = opendir_cnt.fetch_add(1, std::memory_order_seq_cst);\n      eos_static_info(\"ino=%16x opendir=%d => opendir=%d\", (*this)()->id(),\n                      prevOpendir,\n                      prevOpendir + 1);\n    }\n\n    bool opendir_dec(int n)\n    {\n      // atomic operation, no need to lock before calling\n      int prevOpendir = opendir_cnt.fetch_sub(n, std::memory_order_seq_cst);\n\n      if (prevOpendir - n > 0) {\n        return false;\n      }\n\n      return true;\n    }\n\n    int opendir_is()\n    {\n      return opendir_cnt.load();\n    }\n\n    md_op getop() const\n    {\n      return op;\n    }\n\n    bool deleted() const\n    {\n      return (op == RM);\n    }\n\n    void set_lock_remote()\n    {\n      lock_remote = true;\n    }\n\n    void set_lock_local()\n    {\n      lock_remote = false;\n    }\n\n    bool locks_remote()\n    {\n      return lock_remote;\n    }\n\n    void cap_inc()\n    {\n      // atomic operation, no need to lock before calling\n      cap_cnt.fetch_add(1, std::memory_order_seq_cst);\n    }\n\n    void cap_dec()\n    {\n      // atomic operation, no need to lock before calling\n      cap_cnt.fetch_sub(1, std::memory_order_seq_cst);\n    }\n\n    void cap_count_set(uint64_t cnt)\n    {\n      cap_cnt.store(cnt, std::memory_order_seq_cst);\n    }\n\n    int cap_count()\n    {\n      return cap_cnt.load();\n    }\n\n    std::vector<struct flock>& LockTable()\n    {\n      return locktable;\n    }\n\n    int WaitSync(int ms)\n    {\n      return mSync.WaitMS(ms);\n    }\n\n    void Signal()\n    {\n      mSync.Signal();\n    }\n\n    std::string Cookie()\n    {\n      char s[256];\n      snprintf(s, sizeof(s), \"%lx:%lu.%lu:%lu\", (unsigned long)(*this)()->id(),\n               (unsigned long)(*this)()->mtime(),\n               (unsigned long)(*this)()->mtime_ns(),\n               (unsigned long)(*this)()->size());\n      return s;\n    }\n\n    std::map<std::string, uint64_t >& get_todelete()\n    {\n      return todelete;\n    }\n\n    size_t sizeTS()\n    {\n      XrdSysMutexHelper lLock(mLock);\n      return (*this)()->size();\n    }\n\n    std::map<std::string, uint64_t>& local_children()\n    {\n      return _local_children;\n    }\n\n    std::set<std::string>& local_enoent()\n    {\n      return _local_enoent;\n    }\n\n    void store_fullpath(const std::string& pfp, const std::string& name)\n    {\n      std::string fullpath = pfp;\n\n      if (fullpath.length() && fullpath.back() != '/') {\n        fullpath += \"/\";\n      }\n\n      fullpath += name;\n      (*this)()->set_fullpath(fullpath.c_str());\n    }\n\n    uint64_t inlinesize()\n    {\n      return inline_size;\n    }\n\n    bool obfuscate()\n    {\n      auto xattr = proto.attr();\n\n      if (xattr.count(\"sys.file.obfuscate\")) {\n        return (xattr[\"sys.file.obfuscate\"] == \"1\");\n      } else {\n        return false;\n      }\n    }\n\n    void set_obfuscate_key(const std::string& key, bool encryption = false,\n                           std::string encryptionhash = \"\")\n    {\n      (*proto.mutable_attr())[\"user.obfuscate.key\"] = key;\n\n      if (encryption) {\n        (*proto.mutable_attr())[\"user.encrypted\"] = \"1\";\n        (*proto.mutable_attr())[\"user.encrypted.fp\"] = encryptionhash;\n      }\n    }\n\n    std::string keyprint16(const std::string& key1, const std::string& key2)\n    {\n      std::hash<std::string> secrethash;\n      return std::to_string(secrethash(key1 + key2) % 65536);\n    }\n\n    bool wrong_key(const std::string& keyprint)\n    {\n      auto xattr = proto.attr();\n\n      if (!xattr.count(\"user.encrypted.fp\")) {\n        return false;\n      }\n\n      return (xattr[\"user.encrypted.fp\"] != keyprint);\n    }\n\n    bool encrypted()\n    {\n      auto xattr = proto.attr();\n      return (xattr.count(\"user.encrypted\"));\n    }\n\n    std::string obfuscate_key()\n    {\n      auto xattr = proto.attr();\n\n      if (xattr.count(\"user.obfuscate.key\")) {\n        return xattr[\"user.obfuscate.key\"];\n      } else {\n        return \"\";\n      }\n    }\n\n    void set_inlinesize(uint64_t inlinesize)\n    {\n      inline_size = inlinesize;\n    }\n\n    void force_refresh()\n    {\n      refresh.store(1, std::memory_order_seq_cst);\n    }\n\n    bool needs_refresh() const\n    {\n      return refresh.load() ? true : false;\n    }\n\n    void clear_refresh()\n    {\n      refresh.store(0, std::memory_order_seq_cst);\n    }\n\n    void set_lru_prev(uint64_t prev)\n    {\n      _lru_prev.store(prev, std::memory_order_seq_cst);\n    }\n\n    void set_lru_next(uint64_t next)\n    {\n      _lru_next.store(next, std::memory_order_seq_cst);\n    }\n\n    uint64_t lru_prev() const\n    {\n      return _lru_prev.load();\n    }\n    uint64_t lru_next() const\n    {\n      return _lru_next.load();\n    }\n\n    void set_rmrf()\n    {\n      rmrf = true;\n    }\n\n    bool get_rmrf() const\n    {\n      return rmrf;\n    }\n\n    void unset_rmrf()\n    {\n      rmrf = false;\n    }\n\n    int state_serialize(std::string& out);\n    int state_deserialize(std::string& out);\n\n    eos::fusex::md* operator()()\n    {\n      return &proto;\n    }\n\n    uint64_t pidTS()\n    {\n      XrdSysMutexHelper cLock(mLock);\n      return proto.pid();\n    }\n\n    uint64_t pid()\n    {\n      return proto.pid();\n    }\n\n  private:\n    XrdSysMutex mLock;\n    XrdSysCondVar mSync;\n    std::atomic<md_op> op;\n    std::atomic<int> lookup_cnt;\n    std::atomic<int> cap_cnt;\n    std::atomic<int> opendir_cnt;\n    bool lock_remote;\n    std::atomic<int> refresh;\n    bool rmrf;\n    uint64_t inline_size;\n    std::vector<struct flock> locktable;\n    std::map<std::string, uint64_t> todelete;\n    std::map<std::string, uint64_t> _local_children;\n    std::set<std::string> _local_enoent;\n\n    std::atomic<uint64_t> _lru_prev;\n    std::atomic<uint64_t> _lru_next;\n    eos::fusex::md proto;\n\n    struct hmac_t {\n      std::string key;\n      std::string hmac;\n    };\n\n    hmac_t hmac;\n  };\n\n  typedef std::shared_ptr<mdx> shared_md;\n\n  // used to provide serialisaiton during get() for an inode\n  XrdSysMutex mGetMapLock;\n  std::map<uint64_t, std::shared_ptr<XrdSysCondVar>> mGetMap;\n  struct GetLockHelper_s {\n      GetLockHelper_s(metad *meta, fuse_ino_t ino) : meta_(meta), ino_(ino) { }\n      GetLockHelper_s( const GetLockHelper_s& ) = delete;\n      GetLockHelper_s &operator=( const GetLockHelper_s& ) = delete;\n      ~GetLockHelper_s() { UnLock(); }\n\n      void UnLock() {\n        if (meta_) meta_->GetMtxRelease(*this);\n      }\n\n      fuse_ino_t InoAndClear() {\n        fuse_ino_t ino = ino_;\n        meta_ = 0;\n        ino_  = 0;\n        return ino;\n      }\n\n      metad *meta_;\n      fuse_ino_t ino_;\n  };\n\n  GetLockHelper_s GetMtxAcquire(fuse_ino_t ino) {\n    if (!ino) return GetLockHelper_s(nullptr, 0);\n    std::shared_ptr<XrdSysCondVar> cv;\n    do {\n      XrdSysCondVarHelper lkcv;\n      {\n        XrdSysMutexHelper lk(mGetMapLock);\n        auto it = mGetMap.find(ino);\n        if (it == mGetMap.end()) {\n          if (!cv) cv = std::make_shared<XrdSysCondVar>(0);\n          mGetMap[ino] = cv;\n          break;\n        }\n        cv = it->second;\n        lkcv.Lock(cv.get());\n      }\n      cv->Wait();\n    } while(1);\n    return GetLockHelper_s(this, ino);\n  }\n\n  void GetMtxRelease(GetLockHelper_s &lh) {\n    fuse_ino_t ino = lh.InoAndClear();\n    XrdSysMutexHelper lk(mGetMapLock);\n    auto it = mGetMap.find(ino);\n    if (it == mGetMap.end()) return;\n    auto cv = it->second;\n    mGetMap.erase(it);\n    XrdSysCondVarHelper lkcv(*cv);\n    cv->Broadcast();\n  }\n\n  //----------------------------------------------------------------------------\n\n  class vmap\n  //----------------------------------------------------------------------------\n  {\n  public:\n\n    vmap() { }\n\n    virtual ~vmap() { }\n\n    void insert(fuse_ino_t a, fuse_ino_t b);\n\n    std::string dump();\n\n    void erase_fwd(fuse_ino_t lookup);\n    void erase_bwd(fuse_ino_t lookup);\n\n\n    fuse_ino_t forward(fuse_ino_t lookup);\n    fuse_ino_t backward(fuse_ino_t lookup);\n\n    void clear()\n    {\n      XrdSysMutexHelper mLock(mMutex);\n      fwd_map.clear();\n      bwd_map.clear();\n    }\n\n    size_t size()\n    {\n      XrdSysMutexHelper mLock(mMutex);\n      return fwd_map.size();\n    }\n\n  private:\n    std::map<fuse_ino_t, fuse_ino_t>\n    fwd_map; // forward map points from remote to local inode\n    std::map<fuse_ino_t, fuse_ino_t>\n    bwd_map; // backward map points from local remote inode\n\n    XrdSysMutex mMutex;\n  };\n\n  class pmap : public std::map<fuse_ino_t, shared_md>, public XrdSysMutex\n  //----------------------------------------------------------------------------\n  {\n  public:\n\n    pmap()\n    {\n      lru_first = 0;\n      lru_last = 0;\n      store = 0 ;\n    }\n\n    void init(kv* _kv)\n    {\n      store = _kv;\n    }\n\n    virtual ~pmap() { }\n\n    // TS stands for \"thread-safe\"\n\n    size_t sizeTS();\n\n    bool retrieveOrCreateTS(fuse_ino_t ino, shared_md& ret);\n    bool retrieveTS(fuse_ino_t ino, shared_md& ret);\n    bool retrieve(fuse_ino_t ino, shared_md& ret);\n    void insertTS(fuse_ino_t ino, shared_md& md);\n    bool eraseTS(fuse_ino_t ino);\n    void retrieveWithParentTS(fuse_ino_t ino, shared_md& md, shared_md& pmd,\n                              std::string& md_name);\n\n    uint64_t lru_oldest() const;\n    uint64_t lru_newest() const;\n    void lru_add(fuse_ino_t ino, shared_md md);\n    void lru_remove(fuse_ino_t ino);\n    void lru_update(fuse_ino_t ino, shared_md md);\n    void lru_dump(bool force = false);\n    void lru_reset();\n\n    int swap_out(fuse_ino_t ino, shared_md md);\n    int swap_in(fuse_ino_t ino, shared_md md);\n    int swap_rm(fuse_ino_t ino);\n\n  private:\n    uint64_t lru_first;\n    uint64_t lru_last;\n    kv* store;\n  };\n\n  //----------------------------------------------------------------------------\n  metad();\n\n  virtual ~metad();\n\n  void init(backend* _mdbackend);\n\n  bool map_children_to_local(shared_md md);\n\n\n  shared_md lookup(fuse_req_t req,\n                   fuse_ino_t parent,\n                   const char* name);\n\n  shared_md lookup_ll(fuse_req_t req,\n                      fuse_ino_t parent,\n                      const char* name);\n\n  int forget(fuse_req_t req,\n             fuse_ino_t ino,\n             int nlookup);\n\n  void wait_backlog(shared_md md);\n\n  void wait_upstream(fuse_req_t req,\n                     fuse_ino_t ino);\n\n  shared_md getlocal(fuse_req_t req,\n                     fuse_ino_t ino);\n\n  std::string getpath(fuse_ino_t ino);\n\n  shared_md get(fuse_req_t req,\n                fuse_ino_t ino,\n                const std::string authid = \"\",\n                bool listing = false,\n                shared_md pmd = 0,\n                const char* name = 0,\n                bool readdir = false\n               );\n\n  uint64_t insert(shared_md md,\n                  std::string authid);\n\n  int wait_flush(fuse_req_t req,\n                 shared_md md);\n\n  bool has_flush(fuse_ino_t ino);\n\n  void update(fuse_req_t req,\n              shared_md md,\n              std::string authid,\n              bool localstore = false);\n\n  void update(fuse_id id,\n              shared_md md,\n              std::string authid,\n              bool localstore = false);\n\n  void add(fuse_req_t req, shared_md pmd, shared_md md, std::string authid,\n           bool localstore = false);\n  int add_sync(fuse_req_t req, shared_md pmd, shared_md md, std::string authid);\n  int begin_flush(fuse_req_t req, shared_md md, std::string authid);\n  int end_flush(fuse_req_t req, shared_md md, std::string authid);\n\n  void remove(fuse_req_t req, shared_md pmd, shared_md md, std::string authid,\n              bool upstream = true, bool norecycle = false);\n  void mv(fuse_req_t req, shared_md p1md, shared_md p2md, shared_md md,\n          std::string newname,\n          std::string authid1, std::string authid2);\n\n  int rmrf(fuse_req_t req, shared_md md);\n\n  std::string dump_md(shared_md md, bool lock = true);\n  std::string dump_md(eos::fusex::md& md);\n  std::string dump_container(eos::fusex::container& cont);\n\n  uint64_t apply(fuse_req_t req, eos::fusex::container& cont, bool listing);\n\n  int getlk(fuse_req_t req, shared_md md, struct flock* lock);\n  int setlk(fuse_req_t req, shared_md md, struct flock* lock, int sleep);\n\n  int statvfs(fuse_req_t req, struct statvfs* svfs);\n\n  void mdcflush(ThreadAssistant& assistant); // thread pushing into md cache\n\n  void mdcommunicate(ThreadAssistant&\n                     assistant); // thread interacting with the MGM for meta data\n\n  void mdcallback(ThreadAssistant&\n                  assistant); // thread applying MGM callback responses\n\n  void mdstackfree(ThreadAssistant&\n                   assistant); // thread removing stacked inodes\n\n  int connect(std::string zmqtarget, std::string zmqidentity = \"\",\n              std::string zmqname = \"\", std::string zmqclienthost = \"\",\n              std::string zmqclientuuid = \"\");\n\n  int calculateDepth(shared_md md);\n\n  std::string calculateLocalPath(shared_md md);\n\n  void cleanup(shared_md md);\n  void cleanup(fuse_ino_t ino);\n\n  class mdstat\n  {\n  public:\n\n    mdstat()\n    {\n      reset();\n    }\n\n    virtual ~mdstat() { }\n\n    void reset()\n    {\n      _inodes.store(0, std::memory_order_seq_cst);\n      _inodes_stacked.store(0, std::memory_order_seq_cst);\n      _inodes_ever.store(0, std::memory_order_seq_cst);\n      _inodes_deleted.store(0, std::memory_order_seq_cst);\n      _inodes_deleted_ever.store(0, std::memory_order_seq_cst);\n      _inodes_backlog.store(0, std::memory_order_seq_cst);\n      _lru_resets.store(0, std::memory_order_seq_cst);\n    }\n\n    void inodes_inc()\n    {\n      _inodes.fetch_add(1, std::memory_order_seq_cst);\n    }\n\n    void inodes_stacked_inc()\n    {\n      _inodes_stacked.fetch_add(1, std::memory_order_seq_cst);\n    }\n\n    void inodes_ever_inc()\n    {\n      _inodes_ever.fetch_add(1, std::memory_order_seq_cst);\n    }\n\n    void inodes_dec()\n    {\n      _inodes.fetch_sub(1, std::memory_order_seq_cst);\n    }\n\n    void inodes_stacked_dec()\n    {\n      _inodes_stacked.fetch_sub(1, std::memory_order_seq_cst);\n    }\n\n    void inodes_deleted_inc()\n    {\n      _inodes_deleted.fetch_add(1, std::memory_order_seq_cst);\n    }\n\n    void inodes_deleted_ever_inc()\n    {\n      _inodes_deleted_ever.fetch_add(1, std::memory_order_seq_cst);\n    }\n\n    void lru_resets_inc()\n    {\n      _lru_resets.fetch_add(1, std::memory_order_seq_cst);\n    }\n\n    void inodes_deleted_dec()\n    {\n      _inodes_deleted.fetch_sub(1, std::memory_order_seq_cst);\n    }\n\n    void inodes_backlog_store(ssize_t n)\n    {\n      _inodes_backlog.store(n, std::memory_order_seq_cst);\n    }\n\n    ssize_t inodes()\n    {\n      return _inodes.load();\n    }\n\n    ssize_t inodes_stacked()\n    {\n      return _inodes_stacked.load();\n    }\n\n    ssize_t inodes_ever()\n    {\n      return _inodes_ever.load();\n    }\n\n    ssize_t inodes_deleted()\n    {\n      return _inodes_deleted.load();\n      ;\n    }\n\n    ssize_t inodes_deleted_ever()\n    {\n      return _inodes_deleted_ever.load();\n    }\n\n    ssize_t inodes_backlog()\n    {\n      return _inodes_backlog.load();\n    }\n\n    ssize_t lru_resets()\n    {\n      return _lru_resets.load();\n    }\n\n  private:\n    std::atomic<ssize_t> _inodes;\n    std::atomic<ssize_t> _inodes_stacked;\n    std::atomic<ssize_t> _inodes_deleted;\n    std::atomic<ssize_t> _inodes_backlog;\n    std::atomic<ssize_t> _inodes_ever;\n    std::atomic<ssize_t> _inodes_deleted_ever;\n    std::atomic<ssize_t> _lru_resets;\n  };\n\n  mdstat& stats()\n  {\n    return stat;\n  }\n\n  vmap& vmaps()\n  {\n    return inomap;\n  }\n\n  void mdreset()\n  {\n    XrdSysMutexHelper lock(mdmap);\n    shared_md md1 = mdmap[1];\n    (*md1)()->set_type((*md1)()->MD);\n    md1->force_refresh();\n    mdmap.clear();\n    mdmap[1] = md1;\n    uint64_t i_root = inomap.backward(1);\n    inomap.clear();\n    inomap.insert(i_root, 1);\n  }\n\n  void lrureset()\n  {\n    stat.lru_resets_inc();\n    mdmap.lru_reset();\n  }\n\n  void\n  set_cap_count(uint64_t ino, uint64_t cnt)\n  {\n    shared_md md;\n\n    if (!mdmap.retrieveTS(ino, md)) {\n      eos_static_info(\"no cap counter change for ino=%lx\", ino);\n      return;\n    }\n\n    md->cap_count_set(cnt);\n    eos_static_debug(\"set cap counter for ino=%lx cnt=%lx\", ino, cnt);\n  }\n\n  std::string get_clientuuid() const\n  {\n    return zmq_clientuuid;\n  }\n\n  class mdqwaiter\n  {\n  public:\n\n    mdqwaiter() : done_(false) { }\n\n    void wait()\n    {\n      std::unique_lock<std::mutex> lck(mtx_);\n      cv_.wait(lck, [this] { return this->done_; });\n    }\n\n    void notify()\n    {\n      std::unique_lock<std::mutex> lck(mtx_);\n      done_ = true;\n      lck.unlock();\n      cv_.notify_one();\n    }\n\n    bool done() const { return done_; }\n\n  private:\n    bool done_;\n    std::mutex mtx_;\n    std::condition_variable cv_;\n  };\n\n  class flushentry\n  {\n  public:\n\n    flushentry(const uint64_t id, const std::string& aid, mdx::md_op o,\n               fuse_req_t req = 0) : _id(id), _authid(aid), _op(o)\n    {\n      if (req) {\n        _fuse_id = fuse_id(req);\n      }\n    };\n\n    flushentry(const uint64_t id, const std::string& aid, mdx::md_op o,\n               fuse_id fuseid) : _id(id), _authid(aid), _op(o)\n    {\n      _fuse_id = fuseid;\n    };\n\n    ~flushentry()\n    {\n      for(auto &w: _waiters)\n      {\n        w->notify();\n      }\n    }\n\n    std::string authid() const\n    {\n      return _authid;\n    }\n\n    void updateauthid(const std::string &aid)\n    {\n      _authid = aid;\n    }\n\n    mdx::md_op op() const\n    {\n      return _op;\n    }\n\n    uint64_t id() const\n    {\n      return _id;\n    }\n\n    fuse_id get_fuse_id() const\n    {\n      return _fuse_id;\n    }\n\n    void bind()\n    {\n      _fuse_id.bind();\n    }\n\n    void registerwaiter(mdqwaiter *w)\n    {\n      _waiters.push_back(w);\n    }\n\n    static std::deque<flushentry> merge(std::deque<flushentry>& f)\n    {\n      return f;\n    }\n\n    static std::string dump(flushentry& e)\n    {\n      std::string out;\n      char line[1024];\n      snprintf(line, sizeof(line), \"authid=%s op=%d id=%lu uid=%u gid=%u pid=%u\",\n               e.authid().c_str(), (int) e.op(), e.id(), e.get_fuse_id().uid,\n               e.get_fuse_id().gid, e.get_fuse_id().pid);\n      out += line;\n      return out;\n    }\n\n  private:\n    uint64_t _id;\n    std::string _authid;\n    mdx::md_op _op;\n    fuse_id _fuse_id;\n    std::vector<mdqwaiter*> _waiters;\n  };\n\n  typedef std::deque<flushentry> flushentry_set_t;\n\n  void set_zmq_wants_to_connect(int val)\n  {\n    want_zmq_connect.store(val, std::memory_order_seq_cst);\n  }\n\n  int zmq_wants_to_connect()\n  {\n    return want_zmq_connect.load();\n  }\n\n  void set_is_visible(int val)\n  {\n    fusex_visible.store(val, std::memory_order_seq_cst);\n  }\n\n  int is_visible()\n  {\n    return fusex_visible.load();\n  }\n\n  bool should_flush_write_size()\n  {\n    XrdSysMutexHelper cLock(ConfigMutex);\n    return writesizeflush;\n  }\n\n  std::string server_version()\n  {\n    XrdSysMutexHelper cLock(ConfigMutex);\n    return serverversion;\n  }\n\n  bool supports_appname()\n  {\n    XrdSysMutexHelper cLock(ConfigMutex);\n    return appname;\n  }\n\n  bool supports_mdquery()\n  {\n    XrdSysMutexHelper cLock(ConfigMutex);\n    return mdquery;\n  }\n\n  bool supports_hideversion()\n  {\n    XrdSysMutexHelper cLock(ConfigMutex);\n    return hideversion;\n  }\n\n  std::atomic<time_t> last_heartbeat; // timestamp of the last heartbeat sent\n\nprivate:\n\n  // Lock _two_ md objects in the given order.\n\n  class MdLocker\n  {\n  public:\n\n    MdLocker(shared_md& m1, shared_md& m2, bool ordr)\n      : md1(m1), md2(m2), order(ordr)\n    {\n      if (order) {\n        md1->Locker().Lock();\n        md2->Locker().Lock();\n      } else {\n        md2->Locker().Lock();\n        md1->Locker().Lock();\n      }\n    }\n\n    ~MdLocker()\n    {\n      if (order) {\n        md2->Locker().UnLock();\n        md1->Locker().UnLock();\n      } else {\n        md1->Locker().UnLock();\n        md2->Locker().UnLock();\n      }\n    }\n\n  private:\n    shared_md md1;\n    shared_md md2;\n    bool order; // true if lock order is md1 -> md2, false if md2 -> md1\n  };\n\n  bool determineLockOrder(shared_md md1, shared_md md2);\n  bool isChild(shared_md potentialChild, fuse_ino_t parentId);\n\n  pmap mdmap;\n  vmap inomap;\n  mdstat stat;\n\n  // broadcasted config\n  XrdSysMutex ConfigMutex;\n  bool dentrymessaging;\n  bool writesizeflush;\n  bool appname;\n  bool mdquery;\n  bool hideversion;\n  std::atomic<int>  hb_interval;\n\n  std::string serverversion;\n\n  XrdSysCondVar mdflush;\n\n  std::map<uint64_t, size_t> mdqueue; // inode, counter of mds to flush\n  std::deque<flushentry> mdflushqueue; // linear queue with all entries to flush\n  std::deque<const uint64_t*> mdbacklogqueue; // to give order to waiters for mdqueue capacity\n  uint64_t mdqueue_current{0}; // ino of entry currently being flushed\n\n  typedef std::shared_ptr<eos::fusex::response> shared_response;\n  std::deque<shared_response> mCbQueue; // queue will callbacks\n  XrdSysCondVar mCb; // condition variable for queue\n  std::string mCbTrace; // stack trace response\n  std::string mCbLog; // logging response\n\n  size_t mdqueue_max_backlog;\n\n  // ZMQ objects\n  zmq::context_t* z_ctx;\n  zmq::socket_t* z_socket;\n  std::string zmq_target;\n  std::string zmq_identity;\n  std::string zmq_name;\n  std::string zmq_clienthost;\n  std::string zmq_clientuuid;\n  std::mutex zmq_socket_mutex;\n  std::atomic<int> want_zmq_connect;\n  std::atomic<int> fusex_visible;\n  backend* mdbackend;\n\n\n};\n\n#endif /* FUSE_MD_HH_ */\n"
  },
  {
    "path": "fusex/misc/ConcurrentMount.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"ConcurrentMount.hh\"\n\n#include <sys/file.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <poll.h>\n\nConcurrentMount::ConcurrentMount(const std::string& locknameprefix)\n{\n  lockpfx_ = locknameprefix;\n\n  if (locknameprefix.empty()) {\n    return;\n  }\n\n  // Prepare the sockaddr_un for a unix socket to be\n  // for sending the fuse fd to another mount process.\n  // The first mounter will unlink, bind, then listen.\n  // A mount-requester (second or later mounter) will connect()\n  std::string sockpath = locknameprefix + \".sock\";\n  saddr_ = (struct sockaddr_un*)&sstorage_;\n  saddr_->sun_family = AF_LOCAL;\n  strncpy(saddr_->sun_path, sockpath.c_str(), sizeof(saddr_->sun_path)-1);\n  saddr_->sun_path[sizeof(saddr_->sun_path)-1] = '\\0';\n\n  const std::string fnA = locknameprefix + \".A.lock\";\n  const std::string fnB = locknameprefix + \".B.lock\";\n  lockAfd_ = open(fnA.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600);\n\n  if (lockAfd_ >= 0) {\n    lockBfd_ = open(fnB.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600);\n  }\n\n  if (lockAfd_ < 0 || lockBfd_ < 0) {\n    fprintf(stderr, \"# could not open lockfile %s errno=%d\\n\",\n            (lockAfd_ < 0) ? fnA.c_str() : fnB.c_str(), errno);\n\n    if (lockAfd_ >= 0) {\n      close(lockAfd_);\n    }\n\n    lockAfd_ = -1;\n  }\n}\n\nConcurrentMount::~ConcurrentMount()\n{\n  Unlock();\n\n  if (lockBfd_ >= 0) {\n    close(lockBfd_);\n  }\n\n  if (lockAfd_ >= 0) {\n    close(lockAfd_);\n  }\n}\n\nint ConcurrentMount::StartMount(int &mntRes)\n{\n  mntRes = -1;\n  const int rc = llock();\n\n  if (rc == -2) {\n    // unexpectedly could not acquire A (usually this should be held for\n    // a short duraiton while the first eosxd is mounting). Assume we are\n    // deadlocking an eosxd which is starting up; we return indicating a\n    // eosxd is running; but we don't have the fuse fd available.\n    return 1;\n  }\n\n  if (rc <= 0) {\n    // other error or got A+B\n    return rc;\n  }\n\n  // return code 1:\n  // expected state of locks is A & !B\n  if (!lockA_ || lockB_) return -1;\n\n  // connect to the unix socket to get the peer's fuse fd\n  mntRes = connectForFd();\n\n  // unlock A\n  if (lockA_) {\n    if (flock(lockAfd_, LOCK_UN) == 0) {\n      lockA_ = false;\n    }\n  }\n\n  return 1;\n}\n\nvoid ConcurrentMount::MountDone(int fd)\n{\n  if (lockAfd_ < 0) {\n    return;\n  }\n\n  if (lockA_) {\n    if (flock(lockAfd_, LOCK_UN) == 0) {\n      lockA_ = false;\n    }\n  }\n\n  fuseFd_ = fd;\n  serverThread_.emplace_back([this] { this->runFdServer(); });\n}\n\nvoid ConcurrentMount::Unmounting()\n{\n  if (lockAfd_ < 0 || lockBfd_ < 0) {\n    return;\n  }\n\n  if (lockA_ || !lockB_) {\n    return;\n  }\n\n  int ret;\n\n  do {\n    ret = flock(lockAfd_, LOCK_EX);\n  } while (ret < 0 && errno == EINTR);\n\n  if (ret < 0) {\n    return;\n  }\n\n  lockA_ = true;\n\n  if (flock(lockBfd_, LOCK_UN) == 0) {\n    lockB_ = false;\n  }\n\n  shutdownFdServer();\n}\n\nvoid ConcurrentMount::Unlock()\n{\n  shutdownFdServer();\n\n  if (lockBfd_ >= 0) {\n    if (lockB_) {\n      if (flock(lockBfd_, LOCK_UN) == 0) {\n        lockB_ = false;\n      }\n    }\n  }\n\n  if (lockAfd_ >= 0) {\n    if (lockA_) {\n      if (flock(lockAfd_, LOCK_UN) == 0) {\n        lockA_ = false;\n      }\n    }\n  }\n}\n\n// -2 unexpected timeout trying to acquire A\n// -1 other error\n//  0  acquired A + B\n//  1 could not acquire B, still holds A\nint ConcurrentMount::llock()\n{\n  if (lockAfd_ < 0 || lockBfd_ < 0) {\n    return -1;\n  }\n\n  if (lockA_ || lockB_) {\n    return -1;\n  }\n\n  int ret;\n\n  int retry = 120;\n  do {\n    ret = flock(lockAfd_, LOCK_EX | LOCK_NB);\n    if (ret<0 && errno != EWOULDBLOCK) return -1;\n    if (ret==0) break;\n\n    if (retry) {\n      std::this_thread::sleep_for(std::chrono::milliseconds(1000));\n    }\n  } while (retry--);\n\n  if (ret < 0) {\n    // could not get A\n    return -2;\n  }\n\n  lockA_ = true;\n\n  if (flock(lockBfd_, LOCK_EX | LOCK_NB) < 0) {\n    const int err = errno;\n\n    if (err == EWOULDBLOCK) return 1;\n\n    if (flock(lockAfd_, LOCK_UN) == 0) {\n      lockA_ = false;\n    }\n\n    return -1;\n  }\n\n  lockB_ = true;\n\n  return 0;\n}\n\nint ConcurrentMount::connectForFd() {\n  if (lockAfd_ < 0 || lockBfd_ < 0) {\n    return -1;\n  }\n\n  if (!lockA_ || lockB_) {\n    return -1;\n  }\n\n  // prepare unix domain socket\n  const int usock = socket(PF_LOCAL,SOCK_STREAM,0);\n  if (usock<0) {\n    return -1;\n  }\n  const int flags = fcntl(usock, F_GETFL);\n  if (fcntl(usock, F_SETFL, flags | O_NONBLOCK)<0) {\n    close(usock);\n    return -1;\n  }\n\n  if (connect(usock, (struct sockaddr *)saddr_, SUN_LEN(saddr_))<0) {\n    close(usock);\n    return -1;\n  }\n\n  int fd=-1;\n  if (do_recvmsg(usock, fd)<0) {\n    close(usock);\n    return -1;\n  }\n\n  close(usock);\n\n  return fd;\n}\n\nint ConcurrentMount::runFdServer() {\n  if (unlink(saddr_->sun_path)<0 && errno != ENOENT) {\n    return -1;\n  }\n\n  // prepare unix domain socket\n  const int usock = socket(PF_LOCAL,SOCK_STREAM,0);\n  if (usock<0) {\n    return -1;\n  }\n  const int flags = fcntl(usock, F_GETFL);\n  if (fcntl(usock, F_SETFL, flags | O_NONBLOCK)<0) {\n    close(usock);\n    return -1;\n  }\n\n#ifndef __APPLE__\n  if (fchmod(usock, 0700)<0) {\n    close(usock);\n    return -1;\n  }\n#endif\n\n  if (bind(usock, (struct sockaddr *)saddr_, SUN_LEN(saddr_))<0) {\n    close(usock);\n    return -1;\n  }\n\n  if (listen(usock, 1)<0) {\n    close(usock);\n    return -1;\n  }\n  while(!serverExit_) {\n    struct pollfd pfd[1];\n    pfd[0].fd = usock;\n    pfd[0].events = POLLIN;\n    const int rc = poll(pfd, 1, 200);\n    if (rc<0) {\n      if (errno == EINTR || errno == EAGAIN) continue;\n      close(usock);\n      return -1;\n    }\n    if (rc==0) continue;\n    if (pfd[0].revents & (POLLERR|POLLHUP|POLLNVAL)) {\n      close(usock);\n      return -1;\n    }\n    const int s2 = accept(usock, NULL, 0);\n    if (s2<0) {\n      continue;\n    }\n    (void)do_sendmsg(s2, saddr_->sun_path, fuseFd_);\n    close(s2);\n  }\n\n  close(usock);\n  unlink(saddr_->sun_path);\n\n  return 0;\n}\n\nint ConcurrentMount::do_sendmsg(const int sock, const char *path, const int fd) {\n  struct msghdr msg;\n  struct cmsghdr *cmsghdr;\n  struct iovec iov[1];\n  ssize_t nbytes;\n  union {\n    struct cmsghdr hdr;\n    char buf[CMSG_SPACE(sizeof(int))];\n  } ctrl_msg;\n  int *p;\n  char metadata[32];\n\n  snprintf(metadata,sizeof(metadata), \"A%ld\", (long)getpid());\n  iov[0].iov_base = metadata;\n  iov[0].iov_len = strlen(metadata)+1;\n  memset(&ctrl_msg, 0, sizeof(ctrl_msg));\n  cmsghdr = &ctrl_msg.hdr;\n  cmsghdr->cmsg_len = CMSG_LEN(sizeof(int));\n  cmsghdr->cmsg_level = SOL_SOCKET;\n  cmsghdr->cmsg_type = SCM_RIGHTS;\n  msg.msg_name = NULL;\n  msg.msg_namelen = 0;\n  msg.msg_iov = iov;\n  msg.msg_iovlen = sizeof(iov) / sizeof(iov[0]);\n  msg.msg_control = cmsghdr;\n  msg.msg_controllen = cmsghdr->cmsg_len;\n  msg.msg_flags = 0;\n  p = (int *)CMSG_DATA(cmsghdr);\n  *p = fd;\n\n  nbytes = sendmsg(sock, &msg, 0);\n  if (nbytes<0)\n      return -1;\n\n  return 0;\n}\n\nint ConcurrentMount::do_recvmsg(const int sock, int &fd) {\n  struct msghdr msg;\n  struct cmsghdr *cmsghdr;\n  struct iovec iov[1];\n  ssize_t nbytes;\n  union {\n    struct cmsghdr hdr;\n    char buf[CMSG_SPACE(sizeof(int))];\n  } ctrl_msg;\n  int *p;\n  char metadata[1024];\n\n  iov[0].iov_base = metadata;\n  iov[0].iov_len = sizeof(metadata);\n  memset(&ctrl_msg, 0, sizeof(ctrl_msg));\n  cmsghdr = &ctrl_msg.hdr;\n  cmsghdr->cmsg_len = CMSG_LEN(sizeof(int));\n  cmsghdr->cmsg_level = SOL_SOCKET;\n  cmsghdr->cmsg_type = SCM_RIGHTS;\n  msg.msg_name = NULL;\n  msg.msg_namelen = 0;\n  msg.msg_iov = iov;\n  msg.msg_iovlen = sizeof(iov) / sizeof(iov[0]);\n  msg.msg_control = cmsghdr;\n  msg.msg_controllen = cmsghdr->cmsg_len;\n  msg.msg_flags = 0;\n\n  struct pollfd pfd[1];\n  pfd[0].fd = sock;\n  pfd[0].events = POLLIN;\n  while(1) {\n    const int rc = poll(pfd, 1, 5000);\n    if (rc<0 && (errno == EINTR || errno == EAGAIN)) continue;\n    if (rc<=0) return -1;\n    break;\n  }\n\n  if (pfd[0].revents & (POLLERR|POLLNVAL)) return -1;\n\n  nbytes = recvmsg(sock, &msg, 0);\n  if (nbytes < 1 || metadata[0] != 'A')\n    return -1;\n\n  p = (int *)CMSG_DATA(cmsghdr);\n  fd = *p;\n  return 0;\n}\n\nvoid ConcurrentMount::shutdownFdServer() {\n  serverExit_ = true;\n  for(auto &thr: serverThread_) {\n    thr.join();\n  }\n  serverThread_.clear();\n  serverExit_ = false;\n}\n"
  },
  {
    "path": "fusex/misc/ConcurrentMount.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file ConcurrentMount.hh\n//! @author David Smith CERN\n//! @brief Class to detect running eosxd and facilitate reattach of mount point\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include <string>\n#include <vector>\n#include <atomic>\n#include <thread>\n\n#include <sys/socket.h>\n#include <sys/un.h>\n\n/**\n * ConcurrentMount is a utility class used to avoid starting concurrent eosxd\n * for the same mount, but allowing reattaching an eosxd to its mount point,\n * e.g. after a lazy unmount.\n *\n * To detect concurrent mounts we use two exclusive flock advisory locks on\n * two dedicated lock files termed A & B. To allow reattaching of the mount\n * we use a unix domain socket to allow passing of the /dev/fuse filedescriptor\n * to an eosxd to allow it reattach the fs by calling mount() and exiting.\n *\n * The locking strategy is:\n * A+B are locked during mount/unmount transition. [take A, take B]\n * B   only is locked while the filesystem is mounted. [release A]\n *     The pid of an Unmounting process is recorded in lockfile B.\n * A   only is locked while the filesystem is being unmounted. [take A, release B]\n *\n * The usage of this class is:\n *\n *  StartMount(): tests if caller is the only instance running,\n *                if not we attempt to fetch and return the\n *                existing fuse file descriptor. If we indicate\n *                caller is not the only instance running the caller\n *                should not proceed with the calls below.\n *\n * MountDone():   we're primary eosxd and have mounted the fs.\n *                Caller supplies the fuse file descriptor. (The caller\n *                will then loop for the duration of the fuse session).\n *                This method starts a thread, the caller should not fork\n *                and call this object from a child process.\n *\n * Unmounting():  caller has finished their fuse session loop.\n *\n * Unlock():      caller has unmounted the fuse fs.\n *\n */\nclass ConcurrentMount\n{\npublic:\n  /**\n   * Constructor, opens lock files.\n   */\n  ConcurrentMount(const std::string& locknameprefix);\n\n  /**\n   * Destrcutor, closes lock file descriptors.\n   */\n  ~ConcurrentMount();\n\n  /**\n   * Lock in preparation of mounting:\n   * Returns -1 on error\n   *          0 for successful lock (A + B locked)\n   *          1 locks were consistent with existing ongoing mount.\n   *            mntRes holds result of mount request sent via ipc\n   *\n   * If the lock is held by an existing mount some retries up to 5\n   * seconds are made to avoid race on unmount.\n   */\n  int StartMount(int &mntRes);\n\n  /**\n   * Called after mounting and before entering the fuse session loop.\n   */\n  void MountDone(int fd);\n\n  /**\n   * Called after leaving the fuse session loop but before unmounting.\n   */\n  void Unmounting();\n\n  /**\n   * May be called once mount & unmount activity is done. (However the\n   * destructor may be called without calling this method).\n   */\n  void Unlock();\n\n  std::string lockpfx() const {\n    return lockpfx_;\n  }\n\nprivate:\n  // llock:\n  // -2 unexpected timeout trying to acquire A.\n  // -1 other error\n  //  0  acquired A + B\n  //  1 could not acquire B, still holds A\n  int llock();\n\n  int connectForFd();\n\n  int runFdServer();\n\n  int do_sendmsg(const int sock, const char *path, const int fd);\n\n  int do_recvmsg(const int sock, int &fd);\n\n  void shutdownFdServer();\n\n  int  lockAfd_{ -1};\n  int  lockBfd_{ -1};\n  bool lockA_  {false};\n  bool lockB_  {false};\n  int  fuseFd_ {-1};\n  struct sockaddr_storage sstorage_;\n  struct sockaddr_un *saddr_;\n  std::atomic<bool> serverExit_{false};\n  std::vector<std::thread> serverThread_;\n  std::string lockpfx_;\n};\n"
  },
  {
    "path": "fusex/misc/FuseException.hh",
    "content": "/*\n * FuseException.hh\n *\n *  Created on: Nov 16, 2016\n *      Author: simonm\n */\n\n#ifndef FUSEEXCEPTION_HH_\n#define FUSEEXCEPTION_HH_\n\n#include <string.h>\n#include <exception>\n\n#define DBG(message) std::cerr << __FILE__ << \":\" << __LINE__ << \" -- \" << #message << \" = \" << message << std::endl\n\nclass FuseException : public std::exception\n{\npublic:\n\n  FuseException(int rc) : code(rc) { }\n\n  FuseException(const FuseException& ex) : code(ex.code) { }\n\n  FuseException& operator=(const FuseException& ex)\n  {\n    code = ex.code;\n    return *this;\n  }\n\n  virtual ~FuseException() { }\n\n  virtual const char* what() const throw()\n  {\n    return strerror(code);\n  }\n\nprivate:\n\n  int code;\n};\n\n\n\n#endif /* FUSEEXCEPTION_HH_ */\n"
  },
  {
    "path": "fusex/misc/FuseId.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file FuseId.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief Header file providing a replacment for fuse_req_t because it has a hidden type\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#ifndef FUSE_FUSEID_HH_\n#define FUSE_FUSEID_HH_\n\n#include <memory>\n#include \"llfusexx.hh\"\n#include \"misc/fusexrdlogin.hh\"\n#include <XrdCl/XrdClURL.hh>\n\n//----------------------------------------------------------------------------\n\n\nstruct fuse_identity {\n  XrdCl::URL url;\n  XrdCl::URL::ParamsMap query;\n};\n\nclass fuse_id\n{\npublic:\n\n  uid_t uid;\n  gid_t gid;\n  pid_t pid;\n  std::shared_ptr<struct fuse_identity> getid() const\n  {\n    return _id;\n  }\n\n  void patch()\n  {\n    if (getuid()) {\n      // single user mount, always use this users credentials\n      uid = getuid();\n      gid = getgid();\n    }\n  }\n\n  fuse_id()\n  {\n    uid = gid = pid = 0;\n  }\n\n  fuse_id(fuse_req_t req)\n  {\n    uid = fuse_req_ctx(req)->uid;\n    gid = fuse_req_ctx(req)->gid;\n    pid = fuse_req_ctx(req)->pid;\n    patch();\n  }\n\n  void init(fuse_req_t req)\n  {\n    uid = fuse_req_ctx(req)->uid;\n    gid = fuse_req_ctx(req)->gid;\n    pid = fuse_req_ctx(req)->pid;\n    patch();\n  }\n\n  fuse_id(const fuse_id& o)\n  {\n    uid = o.uid;\n    gid = o.gid;\n    pid = o.pid;\n    _id = o.getid();\n  }\n\n\n  int bind()\n  {\n    // when called the current process environment is snapshotted for this request\n    _id = std::make_shared<struct fuse_identity>();\n    _id->url.FromString(\"root://localhost//dummy\");\n    return fusexrdlogin::loginurl(_id->url, _id->query, uid, gid, pid, 0);\n  }\nprivate:\n  std::shared_ptr<struct fuse_identity> _id;\n};\n\n\n#endif\n"
  },
  {
    "path": "fusex/misc/MacOSXHelper.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file MacOSXHelper.hh\n//! @author Andreas-Joachim Peters, Geoffray Adde, Elvin Sindrilaru CERN\n//! @brief remote IO filesystem implementation\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_MACOSXHELPER_HH_\n#define FUSE_MACOSXHELPER_HH_\n\n#ifdef __APPLE__\n\n#include <errno.h>\n#include <pthread.h>\n#include <signal.h>\n\n#define MTIMESPEC st_mtimespec\n#define ATIMESPEC st_atimespec\n#define CTIMESPEC st_ctimespec\n#define EBADE 52\n#define EBADR 53\n#define EADV 68\n#define EREMOTEIO 121\n#define ENOKEY 126\n#else\n\n#include <sys/types.h>\n#include <signal.h>\n#include <sys/syscall.h>\n#include <unistd.h>\n\n#define MTIMESPEC st_mtim\n#define ATIMESPEC st_atim\n#define CTIMESPEC st_ctim\n#endif\n\n#endif\n"
  },
  {
    "path": "fusex/misc/RunningPidScanner.cc",
    "content": "// ----------------------------------------------------------------------\n// File: RunningPidScanner.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"RunningPidScanner.hh\"\n#include <sstream>\n#include <unistd.h>\n\n\n#define SSTR(message) static_cast<std::ostringstream&>(std::ostringstream().flush() << message).str()\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nRunningPidScanner::RunningPidScanner() : iter(\"/proc\") {}\n\n//------------------------------------------------------------------------------\n// Check if string is purely numeric, only 0-9, no dots or minus\n//------------------------------------------------------------------------------\nbool isPid(const char* str)\n{\n  if (str == nullptr || *str == '\\0') {\n    // should not really happen\n    return false;\n  }\n\n  while (*str != '\\0') {\n    if (isdigit(*str) == 0) {\n      // not numeric\n      return false;\n    }\n\n    str++;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Fetch next element\n//------------------------------------------------------------------------------\nbool RunningPidScanner::next(Entry& out)\n{\n  if (!iter.ok() || iter.eof()) {\n    //--------------------------------------------------------------------------\n    // No more elements to process\n    //--------------------------------------------------------------------------\n    return false;\n  }\n\n  struct dirent* ent = nullptr;\n\n  while (true) {\n    ent = iter.next();\n\n    if (ent == nullptr) {\n      return false;\n    }\n\n    //--------------------------------------------------------------------------\n    // Is this a /proc/<pid>?\n    //--------------------------------------------------------------------------\n    if (ent->d_type == DT_DIR && isPid(ent->d_name)) {\n      char buff[2048];\n      ssize_t len = ::readlink(SSTR(\"/proc/\" << ent->d_name << \"/cwd\").c_str(), buff,\n                               2048);\n\n      if (len > 0) {\n        out.cwd = std::string(buff, len);\n        return true;\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n//! Has there been an error? Reaching EOF is not an error.\n//------------------------------------------------------------------------------\nbool RunningPidScanner::ok() const\n{\n  return iter.ok();\n}\n\n//------------------------------------------------------------------------------\n//! Return error string. If no error has occurred, return the empty string.\n//------------------------------------------------------------------------------\nstd::string RunningPidScanner::err() const\n{\n  return iter.err();\n}\n\n\n"
  },
  {
    "path": "fusex/misc/RunningPidScanner.hh",
    "content": "// ----------------------------------------------------------------------\n// File: RunningPidScanner.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSEX_MISC_RUNNING_PID_SCANNER_HH\n#define FUSEX_MISC_RUNNING_PID_SCANNER_HH\n\n#include \"../auth/DirectoryIterator.hh\"\n\n//------------------------------------------------------------------------------\n//! Class to scan through all pids in the system, as found in /proc/<pid>.\n//! Only provides readlink(cwd) for now.\n//------------------------------------------------------------------------------\nclass RunningPidScanner\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Entry\n  //----------------------------------------------------------------------------\n  struct Entry {\n    std::string cwd;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  RunningPidScanner();\n\n  //----------------------------------------------------------------------------\n  //! Fetch next element\n  //----------------------------------------------------------------------------\n  bool next(Entry& out);\n\n  //----------------------------------------------------------------------------\n  //! Has there been an error? Reaching EOF is not an error.\n  //----------------------------------------------------------------------------\n  bool ok() const;\n\n  //----------------------------------------------------------------------------\n  //! Return error string. If no error has occurred, return the empty string.\n  //----------------------------------------------------------------------------\n  std::string err() const;\n\n\nprivate:\n  DirectoryIterator iter;\n\n};\n\n#endif\n"
  },
  {
    "path": "fusex/misc/SyncQueue.hh",
    "content": "//------------------------------------------------------------------------------\n// Copyright (c) 2013 by European Organization for Nuclear Research (CERN)\n// Author: Lukasz Janyst <ljanyst@cern.ch>\n//------------------------------------------------------------------------------\n// XRootD is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Lesser General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// XRootD is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU Lesser General Public License\n// along with XRootD.  If not, see <http://www.gnu.org/licenses/>.\n//------------------------------------------------------------------------------\n\n#ifndef __XRD_CL_SYNC_QUEUE_HH__\n#define __XRD_CL_SYNC_QUEUE_HH__\n\n#include \"FuseException.hh\"\n\n#include <queue>\n\n#include <XrdSys/XrdSysPthread.hh>\n\n//----------------------------------------------------------------------------\n//! A synchronized queue\n//----------------------------------------------------------------------------\n\ntemplate <typename Item>\nclass SyncQueue\n{\npublic:\n  //------------------------------------------------------------------------\n  //! Constructor\n  //------------------------------------------------------------------------\n\n  SyncQueue()\n  {\n    if (sem_init(&sem, 0, 0)) {\n      throw FuseException(errno);\n    }\n  };\n\n  //------------------------------------------------------------------------\n  //! Destructor\n  //------------------------------------------------------------------------\n\n  ~SyncQueue()\n  {\n    sem_destroy(&sem);\n\n    while (!items.empty()) {\n      Item* i = items.front();\n      items.pop();\n      delete i;\n    }\n  }\n\n  //------------------------------------------------------------------------\n  //! Put the item in the queue\n  //------------------------------------------------------------------------\n\n  void Put(Item* item)\n  {\n    XrdSysMutexHelper scopedLock(mutex);\n    items.push(item);\n\n    if (sem_post(&sem)) {\n      throw FuseException(errno);\n    }\n  }\n\n  //------------------------------------------------------------------------\n  //! Get the item from the front of the queue\n  //------------------------------------------------------------------------\n\n  bool Get(Item*& i, time_t timeout = 5 * 60)\n  {\n    timespec ts;\n    ts.tv_nsec = 0;\n    ts.tv_sec = ::time(0) + timeout;\n\n    if (sem_timedwait(&sem, &ts)) {\n      if (errno == ETIMEDOUT) {\n        return false;\n      }\n\n      throw FuseException(errno);\n    }\n\n    XrdSysMutexHelper scopedLock(mutex);\n\n    // this is not possible, so when it happens we commit a suicide\n    if (items.empty()) {\n      throw FuseException(ENOENT);\n    }\n\n    i = items.front();\n    items.pop();\n    return true;\n  }\n\nprivate:\n\n  std::queue<Item*> items;\n  mutable XrdSysMutex mutex;\n  sem_t sem;\n};\n\n\n#endif // __XRD_CL_ANY_OBJECT_HH__\n"
  },
  {
    "path": "fusex/misc/ThreadPool.hh",
    "content": "/*\n * ThreadPool.hh\n *\n *  Created on: Oct 31, 2016\n *      Author: simonm\n */\n\n#ifndef THREADPOOL_HH_\n#define THREADPOOL_HH_\n\n#include \"SyncQueue.hh\"\n\n#include <list>\n#include <pthread.h>\n\ntemplate<typename Task>\nclass ThreadPool\n{\npublic:\n\n  ThreadPool(int min, int max) : min(min), max(max), active(true), busy(0),\n    idle(0) { }\n\n  virtual ~ThreadPool()\n  {\n    Stop();\n  }\n\n  void Execute(Task* t)\n  {\n    tasks.Put(t);\n    XrdSysMutexHelper scope(mutex);\n\n    if (idle == 0 && busy < max) {\n      CreateThread();\n    }\n  }\n\n  void Stop()\n  {\n    XrdSysMutexHelper scope(mutex);\n    active = false;\n    std::list<pthread_t>::iterator itr;\n\n    for (itr = threads.begin(); itr != threads.end(); ++itr) {\n      int rc = 0;\n      pthread_t& thread = *itr;\n\n      if ((rc = pthread_cancel(thread))) {\n        if (rc == ESRCH) {\n          continue; // the thread doesn't exist\n        }\n\n        throw FuseException(rc);\n      }\n\n      void* ret;\n\n      if ((rc = pthread_join(thread, &ret))) {\n        if (rc == EINVAL || rc == ESRCH) {\n          continue; // the thread doesn't exist or is not joinable\n        }\n\n        throw FuseException(rc);\n      }\n    }\n\nprivate:\n    void Run() {\n      pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, 0);\n\n      while (active) {\n        Task* t = 0; // get next task\n\n        if (!tasks.Get(t)) { // there was a timeout (5 minutes by default)\n          // we use conditional locking in order to avoid deadlocks while stopping\n          if (!mutex.CondLock()) {\n            continue;\n          }\n\n          // if the number of active threads is at\n          // the minimum there's nothing to do\n          if (idle + busy <= min) {\n            continue;\n          }\n\n          // otherwise remove the thread from the thread pool\n          Remove(pthread_self());\n          mutex.UnLock();\n          return 0;\n        }\n\n        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0);\n        Busy(); // the thread is busy\n\n        if (t) {\n          t->Run();  // do the work\n        }\n\n        delete t;\n        Idle(); // the thread is idle again\n        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0);\n      }\n\n      pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0);\n      me->Busy(); // the thread is busy\n      void CreateThread() {\n        pthread_t thread;\n        int rc = 0;\n\n        if ((rc = pthread_create(&thread, 0, &ThreadPool::Run, this))) {\n          throw FuseException(rc);\n        }\n\n        delete t;\n        me->Idle(); // the thread is idle again\n        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0);\n      }\n      return 0;\n    }\n    void CreateThread() {\n      pthread_t thread;\n      int rc = 0;\n\n      if ((rc = pthread_create(&thread, 0, Run, this))) {\n        throw FuseException(rc);\n      }\n\n      ++idle;\n      threads.push_back(thread);\n    }\n    inline void Busy() {\n      XrdSysMutexHelper scope(mutex);\n      ++busy;\n      --idle;\n    }\n    inline void Idle() {\n      XrdSysMutexHelper scope(mutex);\n      ++idle;\n      --busy;\n    }\n    inline void Remove(const pthread_t& thread) {\n      --idle;\n      threads.remove(thread);\n      pthread_detach(thread);\n    }\n    const int min;\n    const int max;\n    bool active;\n    SyncQueue<Task> tasks;\n    mutable XrdSysMutex mutex;\n    std::list<pthread_t> threads;\n    int busy;\n    int idle;\n  };\n\n#endif /* THREADPOOL_HH_ */\n"
  },
  {
    "path": "fusex/misc/Track.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Track.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief Class providing per inode locking\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#ifndef TRACK_HH_\n#define TRACK_HH_\n\n#include \"common/Logging.hh\"\n#include \"common/thread_id.hh\"\n#include \"misc/MacOSXHelper.hh\"\n#include <map>\n#include <memory>\n\nclass Track\n{\npublic:\n\n  class Monitor;\n\n  struct attachDetail_t {\n    attachDetail_t() : atime(0), caller(\"\"), origin(\"\"), req(nullptr) { }\n    uint64_t atime;\n    const char* caller;\n    const char* origin;\n    void* req;\n  };\n\n  typedef struct _meta {\n\n    _meta()\n    {\n      openr = openw = 0;\n      mInUse.SetBlockedStackTracing(false); // disable stacktracing\n      mInUse.SetBlocking(true); // do not use a timed mutex\n      inoLastAttachTime = 0;\n    }\n\n    RWMutex mInUse;\n    XrdSysMutex mlocker;\n    std::atomic<size_t> openr;\n    std::atomic<size_t> openw;\n    uint64_t inoLastAttachTime;\n    std::map<Monitor*, attachDetail_t> adet;\n  } meta_t;\n\n  Track() { }\n\n  ~Track() { }\n\n  void\n  assure(unsigned long long ino)\n  {\n    XrdSysMutexHelper l(iMutex);\n    iNodes[ino] = std::make_shared<meta_t>();\n  }\n\n  void\n  clear()\n  {\n    XrdSysMutexHelper l(iMutex);\n    iNodes.clear();\n  }\n\n  void\n  clean()\n  {\n    eos_static_info(\"\");\n    XrdSysMutexHelper l(iMutex);\n    // take reference time after acquiring mutex, to ensure positive age\n    auto now = std::chrono::steady_clock::now();\n    eos_static_info(\"size=%lu\", iNodes.size());\n    size_t clean_age = 60000;\n\n    if (iNodes.size() > 32 * 1024) {\n      clean_age = 1000;\n    }\n\n    for (auto it = iNodes.begin() ; it != iNodes.end();) {\n      if (EOS_LOGS_DEBUG) {\n        eos_static_debug(\"usage=%lu\", it->second.use_count());\n      }\n\n      if ((it->second.use_count() == 1)) {\n        double age = std::chrono::duration_cast<std::chrono::milliseconds>\n                     (now.time_since_epoch()).count() - it->second->inoLastAttachTime;\n\n        if (EOS_LOGS_DEBUG) {\n          eos_static_crit(\"age=%f\", age);\n        }\n\n        if (age > clean_age)  {\n          it = iNodes.erase(it);\n        } else {\n          it ++;\n        }\n      } else {\n        ++it;\n      }\n\n      if (iNodes.size() < 512) {\n        break;\n      }\n    }\n  }\n\n  void\n  forget(unsigned long long ino)\n  {\n    XrdSysMutexHelper l(iMutex);\n    iNodes.erase(ino);\n  }\n\n  void\n  forget(Monitor* monp, std::shared_ptr<meta_t> me)\n  {\n    XrdSysMutexHelper l(iMutex);\n\n    if (me) {\n      me->adet.erase(monp);\n    }\n  }\n\n  size_t\n  size()\n  {\n    XrdSysMutexHelper l(iMutex);\n    return iNodes.size();\n  }\n\n  double blocked_ms(std::string& function, uint64_t& inode, std::string& orig,\n                    size_t& blocked_ops, bool& on_root)\n  {\n    // return's the time of the longest blocked mutex\n    double max_blocked = 0;\n    function = \"\";\n    inode = 0;\n    orig = \"\";\n    blocked_ops = 0;\n    on_root = false;\n    XrdSysMutexHelper l(iMutex);\n    // get current time, after acquiring mutex to ensure positive elapsed time\n    auto now = std::chrono::steady_clock::now();\n\n    for (const auto& it : iNodes) {\n      if (it.second->openr || it.second->openw) {\n        for (const auto& it2 : it.second->adet) {\n          const attachDetail_t& det = it2.second;\n          // get duration since Monitor attached\n          double is_blocked = std::chrono::duration_cast<std::chrono::milliseconds>\n                              (now.time_since_epoch()).count() - det.atime;\n\n          if (is_blocked > max_blocked) {\n            max_blocked = is_blocked;\n            function = det.caller;\n            orig = det.origin;\n            inode = it.first;\n          }\n\n          if (is_blocked >= 1000) {\n            blocked_ops++;\n          }\n\n          if ((it.first == 1) && (is_blocked >= 1000)) {\n            on_root = true;\n          }\n        }\n      }\n    }\n\n    if (max_blocked < 1000) {\n      // don't report under 1000ms\n      max_blocked = 0;\n      function = \"\";\n      orig = \"\";\n    }\n\n    return max_blocked;\n  }\n\n  std::shared_ptr<meta_t>\n  Attach(Monitor* monp, void* req, unsigned long long ino, bool exclusive = false,\n         const char* caller = 0, const char* origin = 0)\n  {\n    // get current time. attachtime should give a positive elapsed time, when\n    // calculated by another thread, so record time before we acquire mutex.\n    auto now = std::chrono::steady_clock::now();\n    std::shared_ptr<meta_t> m;\n    {\n      XrdSysMutexHelper l(iMutex);\n\n      if (!iNodes.count(ino)) {\n        iNodes[ino] = std::make_shared<meta_t>();\n      }\n\n      m = iNodes[ino];\n      m->inoLastAttachTime = std::chrono::duration_cast<std::chrono::milliseconds>\n                             (now.time_since_epoch()).count();\n      attachDetail_t& det = m->adet[monp];\n      det.caller = caller ? caller : \"\";\n      det.origin = origin ? origin : \"\";\n      det.atime = m->inoLastAttachTime;\n      det.req = req;\n    }\n\n    if (exclusive) {\n      m->mInUse.LockWrite();\n      m->openw++;\n    } else {\n      m->mInUse.LockRead();\n      m->openr++;\n    }\n\n    return m;\n  }\n\n  void SetOrigin(void* req, unsigned long long ino, const char* origin)\n  {\n    std::shared_ptr<meta_t> m;\n    {\n      XrdSysMutexHelper l(iMutex);\n\n      if (!iNodes.count(ino)) {\n        return ;\n      }\n\n      m = iNodes[ino];\n\n      for (auto& it : m->adet) {\n        attachDetail_t& det = it.second;\n\n        if (det.req == req) {\n          det.origin = origin;\n        }\n      }\n    }\n  }\n\n  class Monitor\n  {\n  public:\n\n    Monitor(const char* caller, const char* origin, Track& tracker, void* req,\n            unsigned long long ino,\n            bool exclusive = false, bool disable = false) : tracker(tracker)\n    {\n      if (!disable) {\n        if (EOS_LOGS_DEBUG)\n          eos_static_debug(\"trylock caller=%s self=%lld in=%llu exclusive=%d\", caller,\n                           eos::common::thread_id(), ino, exclusive);\n\n        this->ino = ino;\n        this->caller = caller;\n        this->exclusive = exclusive;\n        this->me = tracker.Attach(this, req, ino, exclusive, caller, origin);\n\n        if (EOS_LOGS_DEBUG)\n          eos_static_debug(\n              \"locked  caller=%s origin=%s self=%lld in=%llu exclusive=%d obj=%llx\",\n              caller, origin, eos::common::thread_id(), ino, exclusive, &(*(this->me)));\n      } else {\n        this->ino = 0;\n        this->caller = \"\";\n        this->exclusive = false;\n      }\n    }\n\n    ~Monitor()\n    {\n      if (this->me) {\n        if (EOS_LOGS_DEBUG)\n          eos_static_debug(\"unlock  caller=%s self=%lld in=%llu exclusive=%d\", caller,\n                           eos::common::thread_id(), ino, exclusive);\n\n        if (exclusive) {\n          me->mInUse.UnLockWrite();\n          me->openw--;\n        } else {\n          me->mInUse.UnLockRead();\n          me->openr--;\n        }\n\n        if (EOS_LOGS_DEBUG)\n          eos_static_debug(\"unlocked  caller=%s self=%lld in=%llu exclusive=%d\", caller,\n                           eos::common::thread_id(), ino, exclusive);\n\n        tracker.forget(this, this->me);\n      }\n    }\n  private:\n    std::shared_ptr<meta_t> me;\n    bool exclusive;\n    unsigned long long ino;\n    const char* caller;\n    Track& tracker;\n  };\n\nprivate:\n  XrdSysMutex iMutex;\n  std::map<unsigned long long, std::shared_ptr<meta_t> > iNodes;\n};\n\n\n#endif\n"
  },
  {
    "path": "fusex/misc/filename.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file filename.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief Class implementing some convenience functions for filenames\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_FILENAME_HH_\n#define FUSE_FILENAME_HH_\n\nclass filename\n{\npublic:\n\n  static bool endswith_case_sensitive(std::string mainStr, std::string toMatch)\n  {\n    auto it = toMatch.begin();\n    return mainStr.size() >= toMatch.size() &&\n           std::all_of(std::next(mainStr.begin(), mainStr.size() - toMatch.size()),\n    mainStr.end(), [&it](const char& c) {\n      return ::tolower(c) == ::tolower(*(it++));\n    });\n  }\n\n  static bool matches_suffix(const std::string& name,\n                             const std::vector<std::string>& suffixes)\n  {\n    for (auto it = suffixes.begin(); it != suffixes.end(); ++it) {\n      if (endswith_case_sensitive(name, *it)) {\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  static bool matches(const std::string& name,\n                      const std::vector<std::string>& namelist)\n  {\n    for (auto it = namelist.begin(); it != namelist.end(); ++it) {\n      if (name == *it) {\n        return true;\n      }\n    }\n\n    return false;\n  }\n};\n\n#endif\n"
  },
  {
    "path": "fusex/misc/fusexrdlogin.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file fusexrdlogin.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief Class providing the login user name for an XRootD fusex connection\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"fusexrdlogin.hh\"\n#include \"eosxd/eosfuse.hh\"\n#include \"common/Macros.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/RegexWrapper.hh\"\n#include \"misc/FuseId.hh\"\n#include \"auth/Logbook.hh\"\n#include \"auth/LoginIdentifier.hh\"\n#include <algorithm>\n#ifdef __APPLE__\n#define ECHRNG 44\n#endif\n\nstd::unique_ptr<AuthenticationGroup> fusexrdlogin::authGroup;\nProcessCache* fusexrdlogin::processCache = nullptr;\n\nnamespace\n{\nconst std::string sSafeRegex {\"[/\\\\w.]+\"};\n}\n\nvoid fusexrdlogin::initializeProcessCache(const CredentialConfig& config)\n{\n  authGroup.reset(new AuthenticationGroup(config));\n  processCache = authGroup->processCache();\n}\n\nstd::string fusexrdlogin::fillExeName(const std::string& execname)\n{\n  auto base_name = [](std::string const & path) {\n    return path.substr(path.find_last_of(\"/\\\\\") + 1);\n  };\n  std::string exe = execname;\n\n  if (execname.length() > 32) {\n    exe = base_name(execname);\n  }\n\n  if (eos::common::eos_regex_match(exe, sSafeRegex)) {\n    return exe;\n  } else {\n    std::string base64_string = \"base64\";\n    std::string base64in = exe;\n    SymKey::Base64(base64in, base64_string);\n    return base64_string;\n  }\n}\n\n\n\nint fusexrdlogin::loginurl(XrdCl::URL& url,\n                           XrdCl::URL::ParamsMap& paramsMap,\n                           fuse_req_t req,\n                           fuse_ino_t ino,\n                           bool root_squash,\n                           int connection_id)\n{\n  fuse_id id(req);\n  return loginurl(url, paramsMap, id.uid, id.gid, id.pid, root_squash,\n                  connection_id);\n}\n\nstd::string fusexrdlogin::executable(fuse_req_t req)\n{\n  fuse_id id(req);\n  Logbook logbook(true);\n  ProcessSnapshot snapshot =\n    (id.pid) ? processCache->retrieve(id.pid, id.uid, id.gid,\n                                      false, logbook) : 0;\n\n  if (snapshot) {\n    return fillExeName(snapshot->getExe());\n  } else {\n    return \"unknown\";\n  }\n}\n\n\nint fusexrdlogin::loginurl(XrdCl::URL& url,\n                           XrdCl::URL::ParamsMap& paramsMap,\n                           uid_t uid,\n                           gid_t gid,\n                           pid_t pid,\n                           fuse_ino_t ino,\n                           bool root_squash,\n                           int connection_id)\n{\n  fuse_id id;\n  id.uid = uid;\n  id.gid = gid;\n  id.pid = pid;\n  bool fallback = false;\n  std::string username = \"nobody\";\n  paramsMap[\"fuse.ver\"] = VERSION;\n  paramsMap[\"fuse.pid\"] = std::to_string(id.pid);\n  paramsMap[\"fuse.uid\"] = std::to_string(id.uid);\n  paramsMap[\"fuse.gid\"] = std::to_string(id.gid);\n  ProcessSnapshot snapshot = processCache->retrieve(id.pid, id.uid, id.gid,\n                             false);\n\n  if (snapshot) {\n    username = snapshot->getBoundIdentity()->getLogin().getStringID();\n    snapshot->getBoundIdentity()->getCreds()->toXrdParams(paramsMap);\n    paramsMap[\"fuse.exe\"] = fillExeName(snapshot->getExe());\n  } else {\n    if (uid && EosFuse::Instance().Config().auth.use_user_unix) {\n      // fallback to the unix user if we have a problem snapshotting a process\n      LoginIdentifier lIdentifier(id.uid, id.gid, id.pid, 0);\n      username = lIdentifier.describe();\n      paramsMap[\"xrd.wantprot\"] = \"unix\";\n      paramsMap[\"xrdcl.secuid\"] = std::to_string(uid);\n      paramsMap[\"xrdcl.secgid\"] = std::to_string(gid);\n      fallback = true;\n    }\n  }\n\n  url.SetUserName(username);\n  int rc = 0;\n\n  if (fallback) {\n    eos_static_warning(\"[unix-fallback] %s uid=%u gid=%u pid=%u user-name=%s url=%s\",\n                       EosFuse::dump(id, ino, 0, rc).c_str(),\n                       id.uid,\n                       id.gid,\n                       id.pid,\n                       username.c_str(),\n                       url.GetURL().c_str()\n                      );\n  } else {\n    eos_static_notice(\"%s uid=%u gid=%u rc=%d user-name=%s url=%s\",\n                      EosFuse::dump(id, ino, 0, rc).c_str(),\n                      id.uid,\n                      id.gid,\n                      rc,\n                      username.c_str(),\n                      url.GetURL().c_str()\n                     );\n  }\n\n  return rc;\n}\n\nstd::string fusexrdlogin::xrd_login(fuse_req_t req)\n{\n  fuse_id id(req);\n  ProcessSnapshot snapshot = processCache->retrieve(id.pid, id.uid, id.gid,\n                             false);\n  std::string login;\n\n  if (snapshot) {\n    login = snapshot->getXrdLogin();\n  } else {\n    login = \"unix\";\n  }\n\n  eos_static_info(\"uid=%u gid=%u xrd-login=%s\",\n                  id.uid,\n                  id.gid,\n                  login.c_str()\n                 );\n  return login;\n}\n\nstd::string fusexrdlogin::secret(fuse_req_t req)\n{\n  fuse_id id(req);\n  ProcessSnapshot snapshot = processCache->retrieve(id.pid, id.uid, id.gid,\n                             false);\n\n  if (snapshot) {\n    return snapshot->getBoundIdentity()->getCreds()->getKey();\n  }\n\n  return \"\";\n}\n\nstd::string fusexrdlogin::environment(fuse_req_t req)\n{\n  fuse_id id(req);\n  ProcessSnapshot snapshot = processCache->retrieve(id.pid, id.uid, id.gid,\n                             false);\n  std::string envtoset;\n  XrdCl::URL::ParamsMap paramsMap;\n\n  if (snapshot) {\n    snapshot->getBoundIdentity()->getCreds()->toXrdParams(paramsMap);\n  }\n\n  if (paramsMap[\"xrd.k5ccname\"] != \"\") {\n    envtoset += \"env KRB5CCNAME=\";\n    envtoset += paramsMap[\"xrd.k5ccname\"];\n  } else {\n    if (paramsMap[\"xrd.gsiusrpxy\"] != \"\") {\n      envtoset += \"env X509_USER_PROXY=\";\n      envtoset += paramsMap[\"xrd.gsiusrpxy\"];\n    }\n  }\n\n  return envtoset;\n}\n\n/*----------------------------------------------------------------------------*/\n"
  },
  {
    "path": "fusex/misc/fusexrdlogin.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file fusexrdlogin.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief Class providing the login user name for an XRootD fusex connection\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#ifndef FUSE_XRDLOGIN_HH_\n#define FUSE_XRDLOGIN_HH_\n\n#include <XrdCl/XrdClURL.hh>\n#include \"llfusexx.hh\"\n#include \"auth/AuthenticationGroup.hh\"\n#include \"auth/ProcessCache.hh\"\n#include <memory>\n\nclass fusexrdlogin\n{\npublic:\n  static std::string fillExeName(const std::string& exename);\n\n  static std::string executable(fuse_req_t);\n\n  static int loginurl(XrdCl::URL& url, XrdCl::URL::ParamsMap& query,\n                      fuse_req_t req,\n                      fuse_ino_t ino,\n                      bool root_squash = false,\n                      int connectionid = 0);\n\n  static int loginurl(XrdCl::URL& url, XrdCl::URL::ParamsMap& query, uid_t uid,\n                      gid_t gid, pid_t pid,\n                      fuse_ino_t ino,\n                      bool root_squash = false,\n                      int connectionid = 0);\n\n\n  static std::string xrd_login(fuse_req_t req);\n\n  static std::string environment(fuse_req_t req);\n\n  static std::string secret(fuse_req_t req);\n\n  static void initializeProcessCache(const CredentialConfig& config);\n  static std::unique_ptr<AuthenticationGroup> authGroup;\n  static ProcessCache* processCache; // owned by authGroup\n};\n\n\n#endif\n"
  },
  {
    "path": "fusex/misc/longstring.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file longstring.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief Class implementing fast number to string conversions\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#include \"longstring.hh\"\n/*----------------------------------------------------------------------------*/\n#include <limits> // std::numeric_limits\n#include <algorithm> // std::reverse\n/*----------------------------------------------------------------------------*/\n\nusing std::numeric_limits;\nusing std::reverse;\n\n/*----------------------------------------------------------------------------*/\n/**\n */\n\n/*----------------------------------------------------------------------------*/\n\n\nchar*\nlongstring::unsigned_to_decimal(uint64_t number, char* buffer)\n{\n  char* sbuffer = buffer;\n\n  if (number == 0) {\n    *buffer++ = '0';\n  } else {\n    char* p_first = buffer;\n\n    while (number != 0) {\n      *buffer++ = '0' + number % 10;\n      number /= 10;\n    }\n\n    reverse(p_first, buffer);\n  }\n\n  *buffer = '\\0';\n  return sbuffer;\n}\n\n/*----------------------------------------------------------------------------*/\n/**\n */\n\n/*----------------------------------------------------------------------------*/\n\nchar*\nlongstring::to_decimal(int64_t number, char* buffer)\n{\n  if (number < 0) {\n    buffer[0] = '-';\n    unsigned_to_decimal(-number, buffer + 1);\n  } else {\n    unsigned_to_decimal(number, buffer);\n  }\n\n  return buffer;\n}\n\n/*----------------------------------------------------------------------------*/\n\n\n"
  },
  {
    "path": "fusex/misc/longstring.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file longstring.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief Class implementing fast number to string conversions\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#ifndef FUSE_LONGSTRING_HH_\n#define FUSE_LONGSTRING_HH_\n\n#include <stdint.h>\n\nclass longstring\n{\npublic:\n  static char*\n  unsigned_to_decimal(uint64_t number, char* buffer);\n\n  static char*\n  to_decimal(int64_t number, char* buffer);\n};\n\n\n#endif\n"
  },
  {
    "path": "fusex/misc/richacl.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file richacl.hh\n//! @author Rainer Toebikke CERN\n//! @brief richacls<=>eosacls translation functions\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\nstruct {\n  int raclBits;\n  char eosChr;\n} raclEosPerms[] = {\n  {RICHACE_READ_DATA, 'r'},\n  {RICHACE_WRITE_DATA, 'w'},\n  {RICHACE_EXECUTE, 'x'},\n  {RICHACE_WRITE_ACL | RICHACE_WRITE_ATTRIBUTES | RICHACE_WRITE_NAMED_ATTRS, 'm'},\n  {RICHACE_APPEND_DATA, 'u'},\n  {RICHACE_DELETE_CHILD, 'd'},\n  {RICHACE_WRITE_OWNER, 'c'}\n};\nconst unsigned int raclEosPermsLen = sizeof(raclEosPerms) / sizeof(\n                                       raclEosPerms[0]);\n\nint\nstatic racl2eos(struct richacl* acl, char* buf, int bufsz, metad::shared_md md)\n{\n  char* end_buf = buf + bufsz - 1;\n  bool add_comma = false;\n  int rc = 0;\n\n  if (bufsz < 1) {\n    return (EINVAL);\n  }\n\n  buf[0] = '\\0'; /* in case there are no eos-compatible ACLs */\n  /* An attempt is made to \"merge\" allow and deny entries into one eos ACL.\n   * Here, this (silently) supposes that there is only one entry to be merged and the other one is\n   * the opposite in allow/deny terms\n   */\n  struct masks {\n    unsigned int allow;\n    unsigned int deny;\n  };\n  struct masks zeromasks = {0, 0};\n  std::map<std::string, struct masks> ace_mask;\n  struct richace* ace;\n  richacl_for_each_entry(ace, acl) {\n    std::string who;\n    int rc2 = 0;\n    const char* ug = (ace->e_flags & RICHACE_IDENTIFIER_GROUP) ? \"g\" : \"u\";\n\n    if (ace->e_flags & RICHACE_UNMAPPED_WHO) {\n      who = ace->e_who;\n\n      if (*ug == 'g') {\n        ug = \"egroup\";\n      }\n    } else if (!(ace->e_flags & RICHACE_SPECIAL_WHO)) {\n      if (*ug == 'g') {\n        who = eos::common::Mapping::GidToGroupName(ace->e_id, rc2);\n      } else {\n        who = eos::common::Mapping::UidToUserName(ace->e_id, rc2);\n      }\n    } else {        /* this is a SPECIAL_WHO */\n      if (ace->e_id == RICHACE_EVERYONE_SPECIAL_ID) {       /* everyone@ */\n        who = \"\";\n        ug = \"z\";\n      } else if (ace->e_id == RICHACE_OWNER_SPECIAL_ID) {   /* owner@ */\n        who = eos::common::Mapping::UidToUserName((*md)()->uid(), rc2);\n\n        if (EOS_LOGS_DEBUG) {\n          eos_static_debug(\"racl2eos special user id %d '%s' ug '%s'\", ace->e_id,\n                           who.c_str(), ug);\n        }\n      } else if (ace->e_id == RICHACE_GROUP_SPECIAL_ID) {   /* group@ */\n        who = eos::common::Mapping::GidToGroupName((*md)()->gid(), rc2);\n        ug = \"g\"; /* RICHACE_IDENTIFIER_GROUP not necessarily set */\n\n        if (EOS_LOGS_DEBUG) {\n          eos_static_debug(\"racl2eos special group id %d '%s' ug '%s'\", ace->e_id,\n                           who.c_str(), ug);\n        }\n      } else {\n        if (EOS_LOGS_DEBUG) {\n          eos_static_debug(\"racl2eos special who %d ignored\", ace->e_id);\n        }\n\n        continue; /* silently ignored */\n      }\n    }\n\n    if (rc2) {\n      who = \"_unknown_\";\n    }\n\n    std::string id_s(ug);\n\n    if (!who.empty()) {\n      id_s += \":\" + who;\n    }\n\n    auto a = ace_mask.find(id_s);\n\n    if (a == ace_mask.end()) {\n      ace_mask[id_s] = zeromasks;\n\n      if (EOS_LOGS_DEBUG)\n        eos_static_debug(\"racl2eos ace_mask for %s initialised to (%#x, %#x)\",\n                         id_s.c_str(), ace_mask[id_s].allow, ace_mask[id_s].deny);\n    }\n\n    if (richace_is_deny(ace)) {\n      ace_mask[id_s].deny = ace->e_mask;\n    } else {\n      ace_mask[id_s].allow = ace->e_mask;\n    }\n  }\n\n  for (auto map = ace_mask.begin(); map != ace_mask.end(); ++map) {\n    char perms[64];\n    int pos = 0;\n    struct masks allowdeny = map->second;\n    unsigned int e_mask = allowdeny.allow & ~allowdeny.deny;\n\n    if (EOS_LOGS_DEBUG)\n      eos_static_debug(\"racl2eos ace_mask %s allow %#x deny %#x mask %#x\",\n                       map->first.c_str(),\n                       allowdeny.allow, allowdeny.deny, e_mask);\n\n    for (unsigned int j = 0; j < raclEosPermsLen; j++) {\n      if (allowdeny.deny & raclEosPerms[j].raclBits) {\n        perms[pos++] = '!';\n      } else if (!(e_mask & raclEosPerms[j].raclBits)) {\n        continue;                 /* not allowed, no need to mention it */\n      }\n\n      // with denials, 'u' should no longer automatically disable them depending on position!\n      // if (raclEosPerms[j].eosChr == 'u' && !allowdeny.deny) perms[pos++] = '+';\n      eos_static_debug(\"racl2eos %c for %#x @%d\", raclEosPerms[j].eosChr,\n                       raclEosPerms[j].raclBits, pos);\n      perms[pos++] = raclEosPerms[j].eosChr;\n    }\n\n    perms[pos] = '\\0'; /* terminate string */\n    char* b = buf;\n\n    if ((end_buf - b) < 10) { /* don't take chances */\n      rc = ENOSPC;\n      break;\n    }\n\n    if (add_comma) {\n      *b++ = ',';\n    }\n\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"racl2eos %s perm %s\", map->first.c_str(), perms);\n    }\n\n    int l = snprintf(b, end_buf - b, \"%s:%s\", map->first.c_str(), perms);\n\n    if (b + l >= end_buf) {\n      *b = '\\0';\n      return E2BIG;\n    }\n\n    buf = b + l;\n    add_comma = true;\n  }\n\n  return rc;\n}\n\nstatic struct richacl*\neos2racl(const char* eosacl, metad::shared_md md)\n{\n  char* tacl = (char*) alloca(strlen(eosacl) + 1);\n  strcpy(tacl, eosacl); /* copy, strtok_r is going to destroy this string */\n  char* curr = tacl, *lasts;\n  int numace = 1;\n\n  while ((curr = strchr(curr + 1, ','))) {\n    numace += 1;  /* a leading ',' would not do real harm */\n  }\n\n  curr = strtok_r(tacl, \",\", &lasts);\n\n  if (curr == NULL) {\n    return richacl_from_mode((*md)\n                             ()->mode());  /* return ACL from mode bits, NULL means invalid ACL */\n  }\n\n  struct richacl* acl = richacl_alloc(numace);\n\n  if (acl == NULL) {\n    return NULL;\n  }\n\n  acl->a_count = 0;\n  struct richace* ace;\n  int idx_everyone = -1, idx_owner = -1, idx_group = -1;\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"eos2racl curr='%s' next='%s'\", curr, lasts);\n  }\n\n  int rc = 0;\n  std::map<id_t, unsigned int> ace_mask_deny;\n  struct richacl* denials = richacl_alloc(numace);\n  denials->a_count = 0;\n\n  do { /* one entry, e.g. u:username:rx or egroup:groupname:rw or z:wx */\n    ace = &acl->a_entries[acl->a_count];\n    memset(ace, 0, sizeof(\n             *ace)); /* makes the ace an \"allow\" type entry in passing */\n    char* l2;\n    char* uge = strtok_r(curr, \":\", &l2);\n    char* qlf = strtok_r(NULL, \":\", &l2);\n    char* perm = strtok_r(NULL, \":\", &l2);\n\n    if (strlen(uge) == 0 || qlf == NULL) { /* badly formatted entry */\n      continue;\n    }\n\n    if (perm == NULL) {         /* would be the case for z:wx */\n      if (uge[0] != 'z') {\n        continue;  /* invalid entry */\n      }\n\n      perm = qlf;\n      qlf = NULL;\n    }\n\n    /* verify all eos chars have an entry in raclEosPerms! */\n    for (char* s = perm; *s != '\\0'; s++) {\n      if (s[0] == '+' || s[0] == '!') {\n        continue;\n      }\n\n      unsigned int j;\n\n      for (j = 0; j < raclEosPermsLen && raclEosPerms[j].eosChr != s[0]; j++);\n\n      if (j >= raclEosPermsLen) {\n        eos_static_err(\"eos2racl eos permission '%c' not supported in '%s'\", s[0],\n                       perm);\n        rc = EINVAL;\n        break;\n      }\n    }\n\n    if (rc != 0) {\n      richacl_free(acl);\n      return NULL;\n    }\n\n    int qlf_num = 0;\n\n    if (qlf != NULL) {\n      char* qlf_end;\n      qlf_num = strtoll((const char*) qlf, &qlf_end, 10);\n\n      if (qlf_end[0] != '\\0') {\n        qlf_num = -1;  /* not a number */\n      }\n\n      /* if (EOS_LOGS_DEBUG) eos_static_debug(\"qlf='%s' qlf_num=%d qlf_end='%s'\", qlf, qlf_num, qlf_end); */\n    }\n\n    switch (uge[0]) { /* qualifier type, just check first char */\n    case 'u': /* u in eos, meaning 'user' */\n      if (qlf_num < 0) {\n        ace->e_id = eos::common::Mapping::UserNameToUid(std::string(qlf), rc);\n      } else {\n        ace->e_id = qlf_num;\n      }\n\n      if (EOS_LOGS_DEBUG) eos_static_debug(\"qge=%s qlf=%s qlf_num=%d rc=%d e_id=%d\",\n                                             uge, qlf, qlf_num,\n                                             rc, ace->e_id);\n\n      if (ace->e_id == (id_t)(*md)()->uid()) {  /* owner */\n        if (idx_owner >= 0) { /* already have an entry */\n          acl->a_owner_mask = ace->e_mask;\n          ace = &acl->a_entries[idx_owner];\n          ace->e_mask = acl->a_owner_mask;\n          acl->a_count--;                       /* it'll be re-incremented shortly */\n        }\n\n        ace->e_id = RICHACE_OWNER_SPECIAL_ID;\n        ace->e_flags |= RICHACE_SPECIAL_WHO;\n      }\n\n      break;\n\n    case 'g': /* g in eos, meaning 'group' */\n      if (qlf_num < 0) {\n        ace->e_id = eos::common::Mapping::GroupNameToGid(std::string(qlf), rc);\n      } else {\n        ace->e_id = qlf_num;\n      }\n\n      if (EOS_LOGS_DEBUG) eos_static_debug(\"qge=%s qlf=%s qlf_num=%d rc=%d e_id=%d\",\n                                             uge, qlf, qlf_num,\n                                             rc, ace->e_id);\n\n      if (ace->e_id != (id_t)(*md)()->gid()) {  /*  group other than file's group*/\n        ace->e_flags |= RICHACE_IDENTIFIER_GROUP;\n        // I had assumed that setting RICHACE_IDENTIFIER_GROUP also in the case e_id == gid below would be appropriate,\n        // however that causes problems with \"setrichacl -m\" finding the right entry. Response from\n        // Andreas Gruenbacher (agruenba@redhat.com) pending (Dec 2018)\n      } else {\n        if (idx_group >= 0) { /* already have an entry */\n          acl->a_group_mask = ace->e_mask;\n          ace = &acl->a_entries[idx_group];\n          ace->e_mask = acl->a_group_mask;\n          acl->a_count--;                       /* it'll be re-incremented shortly */\n        }\n\n        ace->e_id = RICHACE_GROUP_SPECIAL_ID;\n        ace->e_flags |= RICHACE_SPECIAL_WHO;\n      }\n\n      break;\n\n    case 'e': /* egroup in eos */\n      ace->e_flags |= RICHACE_IDENTIFIER_GROUP;\n      rc = richace_set_unmapped_who(ace, qlf,\n                                    ace->e_flags); /* This will set RICHACE_UNMAPPED_WHO, it mustn't be set before! */\n      break;\n\n    case 'z': /* everyone@ */\n      if (idx_everyone >= 0) {\n        acl->a_other_mask = ace->e_mask;\n        ace = &acl->a_entries[idx_everyone];\n        ace->e_mask = acl->a_other_mask;\n        acl->a_count--;                       /* it'll be re-incremented shortly */\n      }\n\n      ace->e_id = RICHACE_EVERYONE_SPECIAL_ID;\n      ace->e_flags |= RICHACE_SPECIAL_WHO;\n      break;\n\n    default:\n      eos_static_err(\"eos2racl invalid qualifier type: %s\", uge);\n    }\n\n    if (rc) {\n      eos_static_err(\"eos2racl parsing failed: \", strerror(rc));\n      break;\n    }\n\n    /* interpret mask characters */\n    unsigned int deny = 0;\n\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"eos2racl perm=%s\", perm);\n    }\n\n    for (unsigned int j = 0; j < raclEosPermsLen; j++) {\n      char* s = strchr(perm, raclEosPerms[j].eosChr);\n\n      if (s != NULL) {\n        bool has_not = (s > perm) && (s[-1] == '!');\n\n        if (has_not) {\n          if (EOS_LOGS_DEBUG) {\n            eos_static_debug(\"eos2racl need a deny entry for '%c' in '%s'\", s[0], perm);\n          }\n\n          deny |= raclEosPerms[j].raclBits;\n          ace->e_mask &= ~raclEosPerms[j].raclBits;\n        } else {\n          ace->e_mask |= raclEosPerms[j].raclBits;\n        }\n      } else if (acl->a_other_mask & raclEosPerms[j].raclBits) {\n        eos_static_err(\"eos2racl need a deny entry for '%c' in '%s'\",\n                       raclEosPerms[j].eosChr, perm);\n        deny |= raclEosPerms[j].raclBits;\n      }\n    }\n\n    if (deny != 0) {\n      struct richace* dace = &denials->a_entries[denials->a_count];\n      richace_copy(dace,\n                   ace);        /* denials has been cleared upon alloc, good for richace_copy */\n      dace->e_mask = deny;\n      dace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;\n      denials->a_count++;\n    }\n\n    if (ace->e_mask) {\n      acl->a_count++;\n\n      if (EOS_LOGS_DEBUG) {\n        eos_static_debug(\"eos2racl a_count=%d numace=%d\", acl->a_count, numace);\n      }\n    } else if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"eos2racl skipped entry e_mask=%#x a_count=%d numace=%d\",\n                       ace->e_mask, acl->a_count, numace);\n    }\n  } while ((curr = strtok_r(NULL, \",\", &lasts)));\n\n  /* if needed, create a new ACL with denials first */\n  if (denials->a_count > 0) {\n    struct richacl* acl2 = richacl_alloc(denials->a_count +\n                                         acl->a_count);    /* alloc and clear */\n\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"allocated new acl for %d entries\", acl2->a_count);\n    }\n\n    /*int sz = (void *) (acl->a_entries) - (void *) acl; ...this does not compile ?? */\n    int sz = (char*)(acl->a_entries) - (char*) acl;\n    memcpy(acl2, acl, sz);      /* copy header */\n    acl2->a_count = 0;\n    /* copy all useful deny entries */\n    struct richace* ace;\n    richacl_for_each_entry(ace, denials) {\n      richace_copy(&acl2->a_entries[acl2->a_count++],\n                   ace);       /* acl2 had been cleared upon alloc */\n\n      if (EOS_LOGS_DEBUG) {\n        eos_static_debug(\"copied mask %#x for id %d count %d\", ace->e_mask, ace->e_id,\n                         acl2->a_count);\n      }\n    }\n    /* copy all allow entries */\n    richacl_for_each_entry(ace, acl) {\n      richace_copy(&acl2->a_entries[acl2->a_count++], ace);\n    }\n    richacl_free(acl);\n    acl = acl2;\n  }\n\n  richacl_free(denials);        /* the updated a_count does not kmetter */\n\n  if (rc == 0) {\n    return acl;\n  }\n\n  richacl_free(acl);\n  return NULL;\n}\n\n\n/* normalize e_id to (idType, id) for easy comparisons */\nid_t\nrichacl_normalize_id(const struct richace* ace, metad::shared_md md,\n                     int* idType)\n{\n  id_t id = 0;\n  *idType = 0;\n\n  if (ace->e_flags & RICHACE_SPECIAL_WHO) {\n    *idType = (int) ace->e_id;          /* one of owner, group, everyone */\n\n    if (*idType == RICHACE_OWNER_SPECIAL_ID) {\n      id = (*md)()->uid();\n    } else if (*idType == RICHACE_GROUP_SPECIAL_ID) {\n      id = (*md)()->gid();\n    }\n  } else {\n    id = ace->e_id;\n\n    if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {\n      *idType = RICHACE_GROUP_SPECIAL_ID;\n    } else {\n      *idType = RICHACE_OWNER_SPECIAL_ID;\n    }\n  }\n\n  return id;\n}\n\n\nstatic richace*\nrichacl_find_matching_ace(struct richace* e, metad::shared_md pmd,\n                          struct richacl* acl, metad::shared_md md)\n{\n  struct richace* ace;\n  int idType1;\n  id_t id1 = richacl_normalize_id(e, pmd, &idType1);\n  richacl_for_each_entry(ace, acl) {\n    if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {\n      if (richace_is_same_identifier(e, ace)) {\n        return ace;\n      }\n\n      int idType2;\n      id_t id2 = richacl_normalize_id(ace, md, &idType2);\n\n      if ((idType1 != idType2) || (id1 != id2)) {\n        continue;\n      }\n\n      return ace;\n    }\n  }\n  return NULL;\n}\n\n// merge parent ACL as defined:\n//  for non-Dir (dynamically) inherits parent ACL\n//  inherits RICHACL_DELETE_CHILD as RICHACL_DELETE for all\nstruct richacl*\nrichacl_merge_parent(struct richacl* acl, metad::shared_md md,  /* subject */\n                     struct richacl* pacl, metad::shared_md pmd /* parent */)\n{\n  bool isDir = S_ISDIR((*md)\n                       ()->mode());         /* non-Dir inherits from parent if acl == NULL */\n  struct richace* ace, *pace;\n\n  if (acl == NULL && !isDir) {              /* inherits ACL from parent */\n    acl = richacl_clone(pacl);\n    eos_static_debug(\"richacl cloned %d entries from parent for non-dir\",\n                     pacl->a_count);\n    richacl_for_each_entry(ace, acl) {\n      if ((ace->e_mask & RICHACE_DELETE_CHILD)) {\n        ace->e_mask |= RICHACE_DELETE;      /* works for both deny/allow */\n      }\n\n      ace->e_mask &= ~RICHACE_DELETE_CHILD; /* not meaningful */\n    }\n  } else {                                  /* inherits only RICHACL_DELETE_CHILD */\n    if (acl ==\n        NULL) {                      /* Container without ACL, use mode bits, no inheritance */\n      acl = richacl_from_mode((*md)()->mode());\n    }\n\n    /* Loop over all entries in parent ACL and merge into child */\n    richacl_for_each_entry(pace, pacl) {\n      if ((pace->e_mask & RICHACE_DELETE_CHILD) == 0) {\n        continue;  /* only inherits RICHACL_DELETE from RICHACL_DELETE_CHILD */\n      }\n\n      ace = richacl_find_matching_ace(pace, pmd, acl, md);\n\n      if (ace == NULL) {                    /* need a new entry */\n        size_t newsz = sizeof(struct richacl) + (acl->a_count + 1) * sizeof(\n                         struct richace);\n        struct richacl* newacl = (struct richacl*) realloc(acl, newsz);\n        eos_static_debug(\"richacl realloced %d bytes for parent DELETE_CHILD old=%#p new=%#p, e_id=%d\",\n                         newsz, acl, newacl, pace->e_id);\n\n        if (newacl == NULL) {               /* running out of memory */\n          richacl_free(acl);              /* complete, high-level free */\n          return NULL;\n        }\n\n        acl = newacl;\n        ace = &(acl->a_entries[acl->a_count]);\n        memset(ace, 0, sizeof(*ace));       /* richace_copy needs a clean entry */\n        richace_copy(ace, pace);\n        ace->e_mask = 0;                    /* no mask bits yet */\n        acl->a_count++;\n      }\n\n      ace->e_mask |= RICHACE_DELETE;        /* works for both deny/allow */\n      eos_static_debug(\"richacl allowing DELETE for %d, mask %#x\", ace->e_id,\n                       ace->e_mask);\n    }\n  }\n\n  return acl;\n}\n\nstd::string escape(std::string src)\n{\n  std::string s = \"\";\n\n  for (unsigned char c : src) {\n    if (isprint(c)) {\n      s += c;\n    } else {\n      char buf[64];\n      sprintf(buf, \"\\\\x%02x\", c);\n      s += buf;\n    }\n  }\n\n  return s;\n}\n"
  },
  {
    "path": "fusex/misc/stringTS.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file stringTS.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief Thread-safe string object\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n\n/*----------------------------------------------------------------------------*/\n#include <string>\n#include <mutex>\n\n/*----------------------------------------------------------------------------*/\n\nclass stringTS\n{\npublic:\n\n  stringTS() { }\n\n  stringTS(const std::string& s)\n  {\n    set(s);\n  }\n\n  virtual ~stringTS() { }\n\n  std::string get()\n  {\n    std::lock_guard<std::mutex> lock(mMutex);\n    return mString;\n  }\n\n  void set(const std::string& s)\n  {\n    std::lock_guard<std::mutex> lock(mMutex);\n    mString = s;\n  }\n\nprivate:\n  std::mutex mMutex;\n  std::string mString;\n};\n"
  },
  {
    "path": "fusex/stat/Stat.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Stat.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"common/Mapping.hh\"\n#include \"common/StringConversion.hh\"\n#include \"fusex/stat/Stat.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucString.hh>\n#include \"fmt/printf.h\"\n/*----------------------------------------------------------------------------*/\n\nstatic constexpr std::string_view na = \"NA\";\n\nvoid\nStat::Add(const char* tag, uid_t uid, gid_t gid, unsigned long val)\n{\n  std::lock_guard<std::mutex> g {Mutex};\n  StatsUid[tag][uid] += val;\n  StatsGid[tag][gid] += val;\n  StatAvgUid[tag][uid].Add(val);\n  StatAvgGid[tag][gid].Add(val);\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nStat::AddExt(const char* tag, uid_t uid, gid_t gid, unsigned long nsample,\n             const double& avgv, const double& minv, const double& maxv)\n{\n  std::lock_guard<std::mutex> g {Mutex};\n  StatExtUid[tag][uid].Insert(nsample, avgv, minv, maxv);\n  StatExtGid[tag][gid].Insert(nsample, avgv, minv, maxv);\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nStat::AddExec(const char* tag, float exectime)\n{\n  std::lock_guard<std::mutex> g {Mutex};\n  StatExec[tag].push_back(exectime);\n\n  // skip asynchronous calls release / releasedir\n  if (std::string(tag).substr(0, 7) != \"release\") {\n    TotalExec += exectime;\n  }\n\n  // we average over 1000 entries\n  if (StatExec[tag].size() > 1000) {\n    StatExec[tag].pop_front();\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nunsigned long long\nStat::GetTotal(const char* tag)\n{\n  google::sparse_hash_map<uid_t, unsigned long long>::const_iterator it;\n  unsigned long long val = 0;\n\n  if (!StatsUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatsUid[tag].begin(); it != StatsUid[tag].end(); ++it) {\n    val += it->second;\n  }\n\n  return val;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalAvg3600(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatAvg>::iterator it;\n  double val = 0;\n\n  if (!StatAvgUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatAvgUid[tag].begin(); it != StatAvgUid[tag].end(); ++it) {\n    val += it->second.GetAvg3600();\n  }\n\n  return val;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalNExt3600(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  unsigned long n = 0;\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    for (int i = 0; i < 3600; i++) {\n      n += it->second.n3600[i];\n    }\n  }\n\n  return (double) n;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalAvgExt3600(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double val = 0;\n  double totw = 0;\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    double w = 0;\n\n    for (int i = 0; i < 3600; i++) {\n      w += it->second.n3600[i];\n    }\n\n    totw += w;\n    val += it->second.GetAvg3600() * w;\n  }\n\n  return val / totw;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalMinExt3600(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double minval = (double) std::numeric_limits<unsigned long>::max();\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    minval = std::min(minval, it->second.GetMin3600());\n  }\n\n  return minval;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalMaxExt3600(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double maxval = (double) std::numeric_limits<unsigned long>::min();\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    maxval = std::max(maxval, it->second.GetMax3600());\n  }\n\n  return maxval;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalAvg300(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatAvg>::iterator it;\n  double val = 0;\n\n  if (!StatAvgUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatAvgUid[tag].begin(); it != StatAvgUid[tag].end(); ++it) {\n    val += it->second.GetAvg300();\n  }\n\n  return val;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalNExt300(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  unsigned long n = 0;\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    for (int i = 0; i < 300; i++) {\n      n += it->second.n300[i];\n    }\n  }\n\n  return (double) n;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalAvgExt300(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double val = 0;\n  double totw = 0;\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    double w = 0;\n\n    for (int i = 0; i < 300; i++) {\n      w += it->second.n300[i];\n    }\n\n    totw += w;\n    val += it->second.GetAvg300() * w;\n  }\n\n  return val / totw;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalMinExt300(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double minval = (double) std::numeric_limits<unsigned long>::max();\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    minval = std::min(minval, it->second.GetMin300());\n  }\n\n  return minval;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalMaxExt300(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double maxval = (double) std::numeric_limits<unsigned long>::min();\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    maxval = std::max(maxval, it->second.GetMax300());\n  }\n\n  return maxval;\n}\n\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalAvg60(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatAvg>::iterator it;\n  double val = 0;\n\n  if (!StatAvgUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatAvgUid[tag].begin(); it != StatAvgUid[tag].end(); ++it) {\n    val += it->second.GetAvg60();\n  }\n\n  return val;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalNExt60(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  unsigned long n = 0;\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    for (int i = 0; i < 60; i++) {\n      n += it->second.n60[i];\n    }\n  }\n\n  return (double) n;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalAvgExt60(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double val = 0;\n  double totw = 0;\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    double w = 0;\n\n    for (int i = 0; i < 60; i++) {\n      w += it->second.n60[i];\n    }\n\n    totw += w;\n    val += it->second.GetAvg60() * w;\n  }\n\n  return val / totw;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalMinExt60(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double minval = (double) std::numeric_limits<unsigned long>::max();\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    minval = std::min(minval, it->second.GetMin60());\n  }\n\n  return minval;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalMaxExt60(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double maxval = (double) std::numeric_limits<unsigned long>::min();\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    maxval = std::max(maxval, it->second.GetMax60());\n  }\n\n  return maxval;\n}\n\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalAvg5(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatAvg>::iterator it;\n  double val = 0;\n\n  if (!StatAvgUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatAvgUid[tag].begin(); it != StatAvgUid[tag].end(); ++it) {\n    val += it->second.GetAvg5();\n  }\n\n  return val;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalNExt5(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  unsigned long n = 0;\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    for (int i = 0; i < 5; i++) {\n      n += it->second.n5[i];\n    }\n  }\n\n  return (double) n;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalAvgExt5(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double val = 0;\n  double totw = 0;\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    double w = 0;\n\n    for (int i = 0; i < 5; i++) {\n      w += it->second.n5[i];\n    }\n\n    totw += w;\n    val += it->second.GetAvg5() * w;\n  }\n\n  return val / totw;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalMinExt5(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double minval = (double) std::numeric_limits<unsigned long>::max();\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    minval = std::min(minval, it->second.GetMin5());\n  }\n\n  return minval;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalMaxExt5(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double maxval = (double) std::numeric_limits<unsigned long>::min();\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    maxval = std::max(maxval, it->second.GetMax5());\n  }\n\n  return maxval;\n}\n\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetExec(const char* tag, double& deviation)\n{\n  // calculates average execution time for 'tag'\n  if (StatExec.count(tag)) {\n    std::deque<float>::const_iterator it;\n    double sum = 0;\n    double avg = 0;\n    deviation = 0;\n    int cnt = 0;\n\n    for (it = StatExec[tag].begin(); it != StatExec[tag].end(); it++) {\n      cnt++;\n      sum += *it;\n    }\n\n    avg = sum / (cnt ? cnt : 999999999);\n\n    for (it = StatExec[tag].begin(); it != StatExec[tag].end(); it++) {\n      deviation += pow((*it - avg), 2);\n    }\n\n    deviation = sqrt(deviation / (cnt ? cnt : 99999999));\n    return avg;\n  }\n\n  return 0;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n/*----------------------------------------------------------------------------*/\n\ndouble\nStat::GetTotalExec(double& deviation, size_t& ops)\n{\n  // calculates average execution time for all commands\n  google::sparse_hash_map<std::string, std::deque<float> >::const_iterator ittag;\n  double sum = 0;\n  double avg = 0;\n  size_t cnt = 0;\n  deviation = 0;\n\n  for (ittag = StatExec.begin(); ittag != StatExec.end(); ittag++) {\n    std::deque<float>::const_iterator it;\n\n    for (it = ittag->second.begin(); it != ittag->second.end(); it++) {\n      cnt++;\n      sum += *it;\n    }\n\n    ops += GetTotal(ittag->first.c_str());\n  }\n\n  if (cnt) {\n    avg = sum / cnt;\n  }\n\n  for (ittag = StatExec.begin(); ittag != StatExec.end(); ittag++) {\n    std::deque<float>::const_iterator it;\n\n    for (it = ittag->second.begin(); it != ittag->second.end(); it++) {\n      deviation += pow((*it - avg), 2);\n    }\n  }\n\n  if (cnt) {\n    deviation = sqrt(deviation / cnt);\n  }\n\n  return avg;\n}\n\n\n/*----------------------------------------------------------------------------*/\nvoid\nStat::Clear()\n{\n  std::lock_guard<std::mutex> g {Mutex};\n\n  for (auto ittag = StatsUid.begin(); ittag != StatsUid.end(); ittag++) {\n    StatsUid[ittag->first].clear();\n    StatsUid[ittag->first].resize(1000);\n  }\n\n  for (auto ittag = StatsGid.begin(); ittag != StatsGid.end(); ittag++) {\n    StatsGid[ittag->first].clear();\n    StatsGid[ittag->first].resize(1000);\n  }\n\n  for (auto ittag = StatsUid.begin(); ittag != StatsUid.end(); ittag++) {\n    StatAvgUid[ittag->first].clear();\n    StatAvgUid[ittag->first].resize(1000);\n  }\n\n  for (auto ittag = StatsGid.begin(); ittag != StatsGid.end(); ittag++) {\n    StatAvgGid[ittag->first].clear();\n    StatAvgGid[ittag->first].resize(1000);\n  }\n\n  for (auto ittag = StatExec.begin(); ittag != StatExec.end(); ittag++) {\n    StatExec[ittag->first].clear();\n    StatExec[ittag->first].resize(1000);\n  }\n\n  TotalExec = 0;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nStat::PrintOutTotal(std::string& out, bool details, bool monitoring,\n                    bool numerical)\n{\n  std::lock_guard<std::mutex> g {Mutex};\n  std::vector<std::string> tags, tags_ext;\n  std::vector<std::string>::iterator it;\n  google::sparse_hash_map<std::string, google::sparse_hash_map<uid_t, unsigned long long> >::iterator\n  tit;\n  google::sparse_hash_map<std::string, google::sparse_hash_map<uid_t, StatExt > >::iterator\n  tit_ext;\n\n  for (tit = StatsUid.begin(); tit != StatsUid.end(); tit++) {\n    tags.push_back(tit->first);\n  }\n\n  for (tit_ext = StatExtUid.begin(); tit_ext != StatExtUid.end(); tit_ext++) {\n    tags_ext.push_back(tit_ext->first);\n  }\n\n  std::sort(tags.begin(), tags.end());\n  std::sort(tags_ext.begin(), tags_ext.end());\n  char outline[8192];\n  double avg = 0;\n  double sig = 0;\n  size_t ops = 0;\n  avg = GetTotalExec(sig, ops);\n  sum_ops = ops;\n\n  if (!monitoring) {\n    snprintf(outline, sizeof(outline),\n             \"%-7s %-32s %3.02f +- %3.02f = %.02fs (%lu ops)\\n\", \"ALL\",\n             \"Execution Time\", avg,\n             sig, TotalExec / 1000.0, ops);\n    out += outline;\n    out += \"# -----------------------------------------------------------------------------------------------------------------------\\n\";\n    snprintf(outline, sizeof(outline),\n             \"%-7s %-32s %-9s %8s %8s %8s %8s %-8s +- %-10s = %-10s\", \"who\",\n             \"command\", \"sum\", \"5s\", \"1min\", \"5min\", \"1h\", \"exec(ms)\", \"sigma(ms)\",\n             \"cumul(s)\");\n    out += outline;\n    out += \"\\n\";\n    out += \"# -----------------------------------------------------------------------------------------------------------------------\\n\";\n  } else {\n    snprintf(outline, sizeof(outline),\n             \"uid=all gid=all total.exec.avg=%.02f total.exec.sigma=%.02f total.exec.sum=%.02f\\n\",\n             avg, sig, TotalExec);\n    out += outline;\n  }\n\n  for (it = tags.begin(); it != tags.end(); ++it) {\n    if ((*it == \"rbytes\") || (*it == \"wbytes\")) {\n      continue;\n    }\n\n    const char* tag = it->c_str();\n    std::string aexec = \"-NA-\";\n    std::string aexecsig = \"-NA-\";\n    double avg = 0;\n    double sig = 0;\n    double total = 0;\n    avg = GetExec(tag, sig);\n    std::string a5 = fmt::sprintf(\"%3.02f\", GetTotalAvg5(tag));\n    std::string a60 = fmt::sprintf(\"%3.02f\", GetTotalAvg60(tag));\n    std::string a300 = fmt::sprintf(\"%3.02f\", GetTotalAvg300(tag));\n    std::string a3600 = fmt::sprintf(\"%3.02f\", GetTotalAvg3600(tag));\n\n    if (avg) {\n      aexec = fmt::sprintf(\"%3.05f\", avg);\n    }\n\n    if (sig) {\n      aexecsig = fmt::sprintf(\"%3.05f\", sig);\n    }\n\n    total = avg * GetTotal(tag) / 1000.0;\n    std::string atotal = fmt::sprintf(\"%04.02f\", total);\n\n    // TODO: make the template a constexpr sv so that it is easier to validate\n    if (!monitoring) {\n      out += fmt::sprintf(\"ALL     %-32s %12llu %8s %8s %8s %8s %8s +- %-10s = %-10s\\n\",\n                          tag, GetTotal(tag), a5, a60, a300, a3600, aexec, aexecsig, atotal);\n    } else {\n      out += fmt::sprintf(\"uid=all gid=all cmd=%s total=%llu 5s=%s 60s=%s 300s=%s 3600s=%s exec=%f execsig=%f cumulated=%f\\n\",\n                          tag, GetTotal(tag), a5, a60, a300, a3600, avg, sig, total);\n    }\n  }\n\n  for (it = tags_ext.begin(); it != tags_ext.end(); ++it) {\n    const char* tag = it->c_str();\n    double nsample;\n    std::string n5, a5{na}, m5{na}, M5{na};\n    std::string n60, a60{na}, m60{na}, M60{na};\n    std::string n300, a300{na}, m300{na}, M300{na};\n    std::string n3600, a3600{na}, m3600{na}, M3600{na};\n\n    if ((nsample = GetTotalNExt5(tag)) < 1) {\n      n5 = fmt::sprintf(\"%6.01e\", nsample);\n    } else {\n      n5 = fmt::sprintf(\"%6.01e\", nsample);\n      a5 = fmt::sprintf(\"%6.01e\", GetTotalAvgExt5(tag));\n      m5 = fmt::sprintf(\"%6.01e\", GetTotalMinExt5(tag));\n      M5 = fmt::sprintf(\"%6.01e\", GetTotalMaxExt5(tag));\n    }\n\n    if ((nsample = GetTotalNExt60(tag)) < 1) {\n      n60 = fmt::sprintf(\"%6.01e\", nsample);\n    } else {\n      n60 = fmt::sprintf(\"%6.01e\", nsample);\n      a60 = fmt::sprintf(\"%6.01e\", GetTotalAvgExt60(tag));\n      m60 = fmt::sprintf(\"%6.01e\", GetTotalMinExt60(tag));\n      M60 = fmt::sprintf(\"%6.01e\", GetTotalMaxExt60(tag));\n    }\n\n    if ((nsample = GetTotalNExt300(tag)) < 1) {\n      n300 = fmt::sprintf(\"%6.01e\", nsample);\n    } else {\n      n300 = fmt::sprintf(\"%6.01e\", nsample);\n      a300 = fmt::sprintf(\"%6.01e\", GetTotalAvgExt300(tag));\n      m300 = fmt::sprintf(\"%6.01e\", GetTotalMinExt300(tag));\n      M300 = fmt::sprintf(\"%6.01e\", GetTotalMaxExt300(tag));\n    }\n\n    if ((nsample = GetTotalNExt3600(tag)) < 1) {\n      n3600 = fmt::sprintf(\"%6.01e\", nsample);\n    } else {\n      n3600 = fmt::sprintf(\"%6.01e\", GetTotalNExt3600(tag));\n      a3600 = fmt::sprintf(\"%6.01e\", GetTotalAvgExt3600(tag));\n      m3600 = fmt::sprintf(\"%6.01e\", GetTotalMinExt3600(tag));\n      M3600 = fmt::sprintf(\"%6.01e\", GetTotalMaxExt3600(tag));\n    }\n\n    if (details) {\n      if (!monitoring) {\n        out += fmt::sprintf(\"ALL     %-32s %12s %8s %8s %8s %8s\\n\", tag, \"spl\", n5, n60,\n                            n300, n3600);\n        out += fmt::sprintf(\"ALL     %-32s %12s %8s %8s %8s %8s\\n\", tag, \"min\", m5, m60,\n                            m300, m3600);\n        out += fmt::sprintf(\"ALL     %-32s %12s %8s %8s %8s %8s\\n\", tag, \"avg\", a5, a60,\n                            a300, a3600);\n        out += fmt::sprintf(\"ALL     %-32s %12s %8s %8s %8s %8s\\n\", tag, \"max\", M5, M60,\n                            M300, M3600);\n      } else {\n        out += fmt::sprintf(\"uid=all gid=all cmd=%s:spl 5s=%s 60s=%s 300s=%s 3600s=%s\\n\",\n                            tag, n5, n60, n300, n3600);\n        out += fmt::sprintf(\"uid=all gid=all cmd=%s:min 5s=%s 60s=%s 300s=%s 3600s=%s\\n\",\n                            tag, m5, m60, m300, m3600);\n        out += fmt::sprintf(\"uid=all gid=all cmd=%s:avg 5s=%s 60s=%s 300s=%s 3600s=%s\\n\",\n                            tag, a5, a60, a300, a3600);\n        out += fmt::sprintf(\"uid=all gid=all cmd=%s:max 5s=%s 60s=%s 300s=%s 3600s=%s\\n\",\n                            tag, M5, M60, M300, M3600);\n      }\n    }\n  }\n\n  if (details) {\n    google::sparse_hash_map<std::string, google::sparse_hash_map<uid_t, StatAvg > >::iterator\n    tuit;\n    google::sparse_hash_map<std::string, google::sparse_hash_map<gid_t, StatAvg > >::iterator\n    tgit;\n    google::sparse_hash_map<std::string, google::sparse_hash_map<uid_t, StatExt > >::iterator\n    tuit_ext;\n    google::sparse_hash_map<std::string, google::sparse_hash_map<gid_t, StatExt > >::iterator\n    tgit_ext;\n    std::map<uid_t, std::string> umap;\n    std::map<gid_t, std::string> gmap;\n\n    for (tuit = StatAvgUid.begin(); tuit != StatAvgUid.end(); tuit++) {\n      google::sparse_hash_map<uid_t, StatAvg>::iterator it;\n\n      for (it = tuit->second.begin(); it != tuit->second.end(); ++it) {\n        int terrc = 0;\n        std::string username = eos::common::Mapping::UidToUserName(it->first, terrc);\n        umap[it->first] = username;\n      }\n    }\n\n    for (tuit_ext = StatExtUid.begin(); tuit_ext != StatExtUid.end(); tuit_ext++) {\n      google::sparse_hash_map<uid_t, StatExt>::iterator it;\n\n      for (it = tuit_ext->second.begin(); it != tuit_ext->second.end(); ++it) {\n        int terrc = 0;\n        std::string username = eos::common::Mapping::UidToUserName(it->first, terrc);\n        umap[it->first] = username;\n      }\n    }\n\n    for (tgit = StatAvgGid.begin(); tgit != StatAvgGid.end(); tgit++) {\n      google::sparse_hash_map<gid_t, StatAvg>::iterator it;\n\n      for (it = tgit->second.begin(); it != tgit->second.end(); ++it) {\n        int terrc = 0;\n        std::string groupname = eos::common::Mapping::GidToGroupName(it->first, terrc);\n        gmap[it->first] = groupname;\n      }\n    }\n\n    for (tgit_ext = StatExtGid.begin(); tgit_ext != StatExtGid.end(); tgit_ext++) {\n      google::sparse_hash_map<gid_t, StatExt>::iterator it;\n\n      for (it = tgit_ext->second.begin(); it != tgit_ext->second.end(); ++it) {\n        int terrc = 0;\n        std::string groupname = eos::common::Mapping::GidToGroupName(it->first, terrc);\n        gmap[it->first] = groupname;\n      }\n    }\n\n    if (!monitoring) {\n      out += \"# -----------------------------------------------------------------------------------------------------------------------\\n\";\n    }\n\n    std::vector <std::string> uidout;\n    std::vector <std::string> gidout;\n    std::string _outline;\n\n    for (tuit = StatAvgUid.begin(); tuit != StatAvgUid.end(); tuit++) {\n      google::sparse_hash_map<uid_t, StatAvg>::iterator it;\n\n      for (it = tuit->second.begin(); it != tuit->second.end(); ++it) {\n        std::string a5 = fmt::sprintf(\"%3.02f\", it->second.GetAvg5());\n        std::string a60 = fmt::sprintf(\"%3.02f\", it->second.GetAvg60());;\n        std::string a300 = fmt::sprintf(\"%3.02f\", it->second.GetAvg300());;\n        std::string a3600 = fmt::sprintf(\"%3.02f\", it->second.GetAvg3600());\n        std::string identifier;\n\n        if (numerical) {\n          identifier = fmt::sprintf(\"uid=%d\", it->first);\n        } else {\n          std::string username = umap.count(it->first) ? umap[it->first] :\n                                 eos::common::StringConversion::GetSizeString(username,\n                                     (unsigned long long) it->first);\n\n          if (monitoring) {\n            identifier = fmt::sprintf(\"uid=%s\", username);\n          } else {\n            identifier = fmt::sprintf(\"%s\", username);\n          }\n        }\n\n        if (!monitoring) {\n          _outline = fmt::sprintf(\"%-10s %-32s %12llu %8s %8s %8s %8s\\n\", identifier,\n                                  tuit->first, StatsUid[tuit->first.c_str()][it->first], a5, a60, a300,\n                                  a3600);\n        } else {\n          _outline = fmt::sprintf(\"%s cmd=%s total=%llu 5s=%s 60s=%s 300s=%s 3600s=%s\\n\",\n                                  identifier, tuit->first, StatsUid[tuit->first.c_str()][it->first], a5,\n                                  a60, a300, a3600);\n        }\n\n        uidout.emplace_back(std::move(_outline));\n      }\n    }\n\n    std::sort(uidout.begin(), uidout.end());\n    std::vector<std::string>::iterator sit;\n\n    for (sit = uidout.begin(); sit != uidout.end(); sit++) {\n      out += sit->c_str();\n    }\n\n    uidout.clear();\n\n    for (tuit_ext = StatExtUid.begin(); tuit_ext != StatExtUid.end(); tuit_ext++) {\n      google::sparse_hash_map<uid_t, StatExt>::iterator it;\n\n      for (it = tuit_ext->second.begin(); it != tuit_ext->second.end(); ++it) {\n        const char* tag = tuit_ext->first.c_str();\n        double nsample;\n        std::string n5, a5{na}, m5{na}, M5{na};\n        std::string n60, a60{na}, m60{na}, M60{na};\n        std::string n300, a300{na}, m300{na}, M300{na};\n        std::string n3600, a3600{na}, m3600{na}, M3600{na};\n\n        if ((nsample = it->second.GetN5()) < 1) {\n          n5 = fmt::sprintf(\"%6.01e\", nsample);\n        } else {\n          n5 = fmt::sprintf(\"%6.01e\", nsample);\n          a5 = fmt::sprintf(\"%6.01e\", it->second.GetAvg5());\n          m5 = fmt::sprintf(\"%6.01e\", it->second.GetMin5());\n          M5 = fmt::sprintf(\"%6.01e\", it->second.GetMax5());\n        }\n\n        if ((nsample = it->second.GetN60()) < 1) {\n          n60 = fmt::sprintf(\"%6.01e\", nsample);\n        } else {\n          n60 = fmt::sprintf(\"%6.01e\", nsample);\n          a60 = fmt::sprintf(\"%6.01e\", it->second.GetAvg60());\n          m60 = fmt::sprintf(\"%6.01e\", it->second.GetMin60());\n          M60 = fmt::sprintf(\"%6.01e\", it->second.GetMax60());\n        }\n\n        if ((nsample = it->second.GetN300()) < 1) {\n          n300 = fmt::sprintf(\"%6.01e\", nsample);\n        } else {\n          n300 = fmt::sprintf(\"%6.01e\", nsample);\n          a300 = fmt::sprintf(\"%6.01e\", it->second.GetAvg300());\n          m300 = fmt::sprintf(\"%6.01e\", it->second.GetMin300());\n          M300 = fmt::sprintf(\"%6.01e\", it->second.GetMax300());\n        }\n\n        if ((nsample = it->second.GetN3600()) < 1) {\n          n3600 = fmt::sprintf(\"%6.01e\", nsample);\n        } else {\n          n3600 = fmt::sprintf(\"%6.01e\", nsample);\n          a3600 = fmt::sprintf(\"%6.01e\", it->second.GetAvg3600());\n          m3600 = fmt::sprintf(\"%6.01e\", it->second.GetMin3600());\n          M3600 = fmt::sprintf(\"%6.01e\", it->second.GetMax3600());\n        }\n\n        char identifier[1024];\n\n        if (numerical) {\n          snprintf(identifier, 1023, \"uid=%d\", it->first);\n        } else {\n          std::string username = umap.count(it->first) ? umap[it->first] :\n                                 eos::common::StringConversion::GetSizeString(username,\n                                     (unsigned long long) it->first);\n\n          if (monitoring) {\n            snprintf(identifier, 1023, \"uid=%s\", username.c_str());\n          } else {\n            snprintf(identifier, 1023, \"%s\", username.c_str());\n          }\n        }\n\n        if (!monitoring) {\n          out += fmt::sprintf(\"%-10s %-32s %12s %8s %8s %8s %8s\\n\", identifier, tag,\n                              \"spl\",\n                              n5, n60, n300, n3600);\n          out += fmt::sprintf(\"%-10s %-32s %12s %8s %8s %8s %8s\\n\", identifier, tag,\n                              \"min\",\n                              m5, m60, m300, m3600);\n          out += fmt::sprintf(\"%-10s %-32s %12s %8s %8s %8s %8s\\n\", identifier, tag,\n                              \"avg\",\n                              a5, a60, a300, a3600);\n          out += fmt::sprintf(\"%-10s %-32s %12s %8s %8s %8s %8s\\n\", identifier, tag,\n                              \"max\",\n                              M5, M60, M300, M3600);\n        } else {\n          out += fmt::sprintf(\"%s cmd=%s:spl 5s=%s 60s=%s 300s=%s 3600s=%s\\n\", identifier,\n                              tag, n5, n60, n300, n3600);\n          out += fmt::sprintf(\"%s cmd=%s:min 5s=%s 60s=%s 300s=%s 3600s=%s\\n\", identifier,\n                              tag, m5, m60, m300, m3600);\n          out += fmt::sprintf(\"%s cmd=%s:avg 5s=%s 60s=%s 300s=%s 3600s=%s\\n\", identifier,\n                              tag, a5, a60, a300, a3600);\n          out += fmt::sprintf(\"%s cmd=%s:max 5s=%s 60s=%s 300s=%s 3600s=%s\\n\", identifier,\n                              tag, M5, M60, M300, M3600);\n        }\n      }\n    }\n\n    std::sort(uidout.begin(), uidout.end());\n\n    for (sit = uidout.begin(); sit != uidout.end(); sit++) {\n      out += sit->c_str();\n    }\n\n    if (!monitoring) {\n      out += \"# --------------------------------------------------------------------------------------\\n\";\n    }\n\n    for (tgit = StatAvgGid.begin(); tgit != StatAvgGid.end(); tgit++) {\n      google::sparse_hash_map<gid_t, StatAvg>::iterator it;\n\n      for (it = tgit->second.begin(); it != tgit->second.end(); ++it) {\n        std::string a5 = fmt::sprintf(\"%3.02f\", it->second.GetAvg5());\n        std::string a60 = fmt::sprintf(\"%3.02f\", it->second.GetAvg60());;\n        std::string a300 = fmt::sprintf(\"%3.02f\", it->second.GetAvg300());;\n        std::string a3600 = fmt::sprintf(\"%3.02f\", it->second.GetAvg3600());\n        char identifier[1024];\n\n        if (numerical) {\n          snprintf(identifier, 1023, \"gid=%d\", it->first);\n        } else {\n          std::string groupname = gmap.count(it->first) ? gmap[it->first] :\n                                  eos::common::StringConversion::GetSizeString(groupname,\n                                      (unsigned long long) it->first);\n\n          if (monitoring) {\n            snprintf(identifier, 1023, \"gid=%s\", groupname.c_str());\n          } else {\n            snprintf(identifier, 1023, \"%s\", groupname.c_str());\n          }\n        }\n\n        if (!monitoring) {\n          _outline = fmt::sprintf(\"%-10s %-32s %12llu %8s %8s %8s %8s\\n\", identifier,\n                                  tgit->first, StatsGid[tgit->first.c_str()][it->first], a5, a60, a300,\n                                  a3600);\n        } else {\n          _outline = fmt::sprintf(\"%s cmd=%s total=%llu 5s=%s 60s=%s 300s=%s 3600s=%s\\n\",\n                                  identifier, tgit->first, StatsUid[tgit->first.c_str()][it->first], a5,\n                                  a60, a300, a3600);\n        }\n\n        gidout.emplace_back(std::move(_outline));\n      }\n    }\n\n    std::sort(gidout.begin(), gidout.end());\n\n    for (sit = gidout.begin(); sit != gidout.end(); sit++) {\n      out += sit->c_str();\n    }\n\n    gidout.clear();\n\n    for (tgit_ext = StatExtGid.begin(); tgit_ext != StatExtGid.end(); tgit_ext++) {\n      google::sparse_hash_map<gid_t, StatExt>::iterator it;\n\n      for (it = tgit_ext->second.begin(); it != tgit_ext->second.end(); ++it) {\n        double nsample;\n        const char na[9] = \"NA\";\n        char n5[1024], a5[1024], m5[1024], M5[1024];\n        char n60[1024], a60[1024], m60[1024], M60[1024];\n        char n300[1024], a300[1024], m300[1024], M300[1024];\n        char n3600[1024], a3600[1024], m3600[1024], M3600[1024];\n\n        if ((nsample = it->second.GetN5()) < 1) {\n          strcpy(a5, na);\n          strcpy(m5, na);\n          strcpy(M5, na);\n          sprintf(n5, \"%6.01e\", nsample);\n        } else {\n          sprintf(n5, \"%6.01e\", nsample);\n          sprintf(a5, \"%6.01e\", it->second.GetAvg5());\n          sprintf(m5, \"%6.01e\", it->second.GetMin5());\n          sprintf(M5, \"%6.01e\", it->second.GetMax5());\n        }\n\n        if ((nsample = it->second.GetN60()) < 1) {\n          strcpy(a60, na);\n          strcpy(m60, na);\n          strcpy(M60, na);\n          sprintf(n60, \"%6.01e\", nsample);\n        } else {\n          sprintf(n60, \"%6.01e\", nsample);\n          sprintf(a60, \"%6.01e\", it->second.GetAvg60());\n          sprintf(m60, \"%6.01e\", it->second.GetMin60());\n          sprintf(M60, \"%6.01e\", it->second.GetMax60());\n        }\n\n        if ((nsample = it->second.GetN300()) < 1) {\n          strcpy(a300, na);\n          strcpy(m300, na);\n          strcpy(M300, na);\n          sprintf(n300, \"%6.01e\", nsample);\n        } else {\n          sprintf(n300, \"%6.01e\", nsample);\n          sprintf(a300, \"%6.01e\", it->second.GetAvg300());\n          sprintf(m300, \"%6.01e\", it->second.GetMin300());\n          sprintf(M300, \"%6.01e\", it->second.GetMax300());\n        }\n\n        if ((nsample = it->second.GetN3600()) < 1) {\n          strcpy(a3600, na);\n          strcpy(m3600, na);\n          strcpy(M3600, na);\n          sprintf(n3600, \"%6.01e\", nsample);\n        } else {\n          sprintf(n3600, \"%6.01e\", nsample);\n          sprintf(a3600, \"%6.01e\", it->second.GetAvg3600());\n          sprintf(m3600, \"%6.01e\", it->second.GetMin3600());\n          sprintf(M3600, \"%6.01e\", it->second.GetMax3600());\n        }\n\n        char identifier[1024];\n\n        if (numerical) {\n          snprintf(identifier, 1023, \"gid=%d\", it->first);\n        } else {\n          std::string groupname = gmap.count(it->first) ? gmap[it->first] :\n                                  eos::common::StringConversion::GetSizeString(groupname,\n                                      (unsigned long long) it->first);\n\n          if (monitoring) {\n            snprintf(identifier, 1023, \"gid=%s\", groupname.c_str());\n          } else {\n            snprintf(identifier, 1023, \"%s\", groupname.c_str());\n          }\n        }\n      }\n    }\n\n    std::sort(gidout.begin(), gidout.end());\n\n    for (sit = gidout.begin(); sit != gidout.end(); sit++) {\n      out += sit->c_str();\n    }\n\n    if (!monitoring) {\n      out += \"# --------------------------------------------------------------------------------------\\n\";\n    }\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nStat::PrintOutTotalJson(Json::Value& out)\n{\n  std::lock_guard<std::mutex> g {Mutex};\n  std::vector<std::string> tags, tags_ext;\n  std::vector<std::string>::iterator it;\n  google::sparse_hash_map<std::string, google::sparse_hash_map<uid_t, unsigned long long> >::iterator\n  tit;\n  google::sparse_hash_map<std::string, google::sparse_hash_map<uid_t, StatExt > >::iterator\n  tit_ext;\n\n  for (tit = StatsUid.begin(); tit != StatsUid.end(); tit++) {\n    tags.push_back(tit->first);\n  }\n\n  for (tit_ext = StatExtUid.begin(); tit_ext != StatExtUid.end(); tit_ext++) {\n    tags_ext.push_back(tit_ext->first);\n  }\n\n  std::sort(tags.begin(), tags.end());\n  std::sort(tags_ext.begin(), tags_ext.end());\n  double avg = 0;\n  double sig = 0;\n  size_t ops = 0;\n  avg = GetTotalExec(sig, ops);\n  sum_ops = ops;\n  out[\"time\"] = Json::Value{};\n  out[\"time\"][\"avg(ms)\"] = Json::Value(avg);\n  out[\"time\"][\"sigma(ms)\"] = Json::Value(sig);\n  out[\"time\"][\"total(s)\"] = Json::Value((double)(TotalExec / 1000.0));\n  out[\"time\"][\"ops\"] = Json::LargestUInt(sum_ops);\n  out[\"activity\"] = Json::Value(Json::arrayValue);\n\n  for (it = tags.begin(); it != tags.end(); ++it) {\n    if ((*it == \"rbytes\") || (*it == \"wbytes\")) {\n      continue;\n    }\n\n    Json::Value entry{};\n    const char* tag = it->c_str();\n    avg = 0;\n    sig = 0;\n    double total = 0;\n    avg = GetExec(tag, sig);\n    total = avg * GetTotal(tag) / 1000.0;\n    entry[\"command\"]    = tag;\n    entry[\"sum\"]        = (Json::LargestUInt) GetTotal(tag);\n    entry[\"5s\"]         = GetTotalAvg5(tag);\n    entry[\"1min\"]       = GetTotalAvg60(tag);\n    entry[\"5min\"]       = GetTotalAvg300(tag);\n    entry[\"1h\"]         = GetTotalAvg3600(tag);\n    entry[\"exec(ms)\"]   = avg;\n    entry[\"sigma(ms)\"]  = sig;\n    entry[\"cumul(s)\"]   = total;\n    out[\"activity\"].append(entry);\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nStat::Circulate(ThreadAssistant& assistant)\n{\n  // empty the circular buffer and extract some Mq statistic values\n  while (true) {\n    assistant.wait_for(std::chrono::milliseconds(512));\n\n    if (assistant.terminationRequested()) {\n      break;\n    }\n\n    // --------------------------------------------\n    std::lock_guard<std::mutex> g {Mutex};\n    google::sparse_hash_map<std::string, google::sparse_hash_map<uid_t, StatAvg> >::iterator\n    tit;\n    google::sparse_hash_map<std::string, google::sparse_hash_map<uid_t, StatExt> >::iterator\n    tit_ext;\n\n    // loop over tags\n    for (tit = StatAvgUid.begin(); tit != StatAvgUid.end(); ++tit) {\n      // loop over vids\n      google::sparse_hash_map<uid_t, StatAvg>::iterator it;\n\n      for (it = tit->second.begin(); it != tit->second.end(); ++it) {\n        it->second.StampZero();\n      }\n    }\n\n    for (tit = StatAvgGid.begin(); tit != StatAvgGid.end(); ++tit) {\n      // loop over vids\n      google::sparse_hash_map<uid_t, StatAvg>::iterator it;\n\n      for (it = tit->second.begin(); it != tit->second.end(); ++it) {\n        it->second.StampZero();\n      }\n    }\n\n    for (tit_ext = StatExtGid.begin(); tit_ext != StatExtGid.end(); ++tit_ext) {\n      // loop over vids\n      google::sparse_hash_map<uid_t, StatExt>::iterator it;\n\n      for (it = tit_ext->second.begin(); it != tit_ext->second.end(); ++it) {\n        it->second.StampZero();\n      }\n    }\n\n    for (tit_ext = StatExtGid.begin(); tit_ext != StatExtGid.end(); ++tit_ext) {\n      // loop over vids\n      google::sparse_hash_map<uid_t, StatExt>::iterator it;\n\n      for (it = tit_ext->second.begin(); it != tit_ext->second.end(); ++it) {\n        it->second.StampZero();\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "fusex/stat/Stat.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Stat.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef FUSE_STAT_HH_\n#define FUSE_STAT_HH_\n\n/*----------------------------------------------------------------------------*/\n#include <XrdSys/XrdSysPthread.hh>\n#include \"common/AssistedThread.hh\"\n/*----------------------------------------------------------------------------*/\n#include <google/sparse_hash_map>\n/*----------------------------------------------------------------------------*/\n#include <vector>\n#include <map>\n#include <string>\n#include <deque>\n#include <math.h>\n#include <thread>\n#include <json/json.h>\nclass StatAvg\n{\npublic:\n  unsigned long avg3600[3600];\n  unsigned long avg300[300];\n  unsigned long avg60[60];\n  unsigned long avg5[5];\n\n  StatAvg()\n  {\n    memset(avg3600, 0, sizeof(avg3600));\n    memset(avg300, 0, sizeof(avg300));\n    memset(avg60, 0, sizeof(avg60));\n    memset(avg5, 0, sizeof(avg5));\n  }\n\n  ~StatAvg() { };\n\n  void\n  Add(unsigned long val)\n  {\n    time_t now = time(0);\n\n    if (now == -1) {\n      now = 0;\n    }\n\n    unsigned int bin3600 = (now % 3600);\n    unsigned int bin300 = (now % 300);\n    unsigned int bin60 = (now % 60);\n    unsigned int bin5 = (now % 5);\n    avg3600[(bin3600 + 1) % 3600] = 0;\n    avg3600[bin3600] += val;\n    avg300[(bin300 + 1) % 300] = 0;\n    avg300[bin300] += val;\n    avg60[(bin60 + 1) % 60] = 0;\n    avg60[bin60] += val;\n    avg5[(bin5 + 1) % 5] = 0;\n    avg5[bin5] += val;\n  }\n\n  void\n  StampZero()\n  {\n    time_t now = time(0);\n\n    if (now == -1) {\n      now = 0;\n    }\n\n    unsigned int bin3600 = (now % 3600);\n    unsigned int bin300 = (now % 300);\n    unsigned int bin60 = (now % 60);\n    unsigned int bin5 = (now % 5);\n    avg3600[(bin3600 + 1) % 3600] = 0;\n    avg300[(bin300 + 1) % 300] = 0;\n    avg60[(bin60 + 1) % 60] = 0;\n    avg5[(bin5 + 1) % 5] = 0;\n  }\n\n  double\n  GetAvg3600()\n  {\n    double sum = 0;\n\n    for (int i = 0; i < 3600; i++) {\n      sum += avg3600[i];\n    }\n\n    return (sum / 3599);\n  }\n\n  double\n  GetAvg300()\n  {\n    double sum = 0;\n\n    for (int i = 0; i < 300; i++) {\n      sum += avg300[i];\n    }\n\n    return (sum / 299);\n  }\n\n  double\n  GetAvg60()\n  {\n    double sum = 0;\n\n    for (int i = 0; i < 60; i++) {\n      sum += avg60[i];\n    }\n\n    return (sum / 59);\n  }\n\n  double\n  GetAvg5()\n  {\n    double sum = 0;\n\n    for (int i = 0; i < 5; i++) {\n      sum += avg5[i];\n    }\n\n    return (sum / 4);\n  }\n};\n\nclass StatExt\n{\npublic:\n  unsigned long n3600[3600];\n  unsigned long n300[300];\n  unsigned long n60[60];\n  unsigned long n5[5];\n  double sum3600[3600];\n  double sum300[300];\n  double sum60[60];\n  double sum5[5];\n  double min3600[3600];\n  double min300[300];\n  double min60[60];\n  double min5[5];\n  double max3600[3600];\n  double max300[300];\n  double max60[60];\n  double max5[5];\n\n  StatExt()\n  {\n    memset(n3600, 0, sizeof(n3600));\n    memset(n300, 0, sizeof(n300));\n    memset(n60, 0, sizeof(n60));\n    memset(n5, 0, sizeof(n5));\n\n    for (int k = 0; k < 3600; k++) {\n      min3600[k] = (double) std::numeric_limits<long long>::max();\n      max3600[k] = (double) std::numeric_limits<size_t>::min();\n      sum3600[k] = 0;\n    }\n\n    for (int k = 0; k < 300; k++) {\n      min300[k] = (double) std::numeric_limits<long long>::max();\n      max300[k] = (double) std::numeric_limits<size_t>::min();\n      sum300[k] = 0;\n    }\n\n    for (int k = 0; k < 60; k++) {\n      min60[k] = (double) std::numeric_limits<long long>::max();\n      max60[k] = (double) std::numeric_limits<size_t>::min();\n      sum60[k] = 0;\n    }\n\n    for (int k = 0; k < 5; k++) {\n      min5[k] = (double) std::numeric_limits<long long>::max();\n      max5[k] = (double) std::numeric_limits<size_t>::min();\n      sum5[k] = 0;\n    }\n  }\n\n  ~StatExt() { };\n\n  void\n  Insert(unsigned long nsample, const double& avgv, const double& minv,\n         const double& maxv)\n  {\n    time_t now = time(0);\n\n    if (now == -1) {\n      now = 0;\n    }\n\n    unsigned int bin3600 = (now % 3600);\n    unsigned int bin300 = (now % 300);\n    unsigned int bin60 = (now % 60);\n    unsigned int bin5 = (now % 5);\n    n3600[(bin3600 + 1) % 3600] = 0;\n    n3600[bin3600] += nsample;\n    sum3600[(bin3600 + 1) % 3600] = 0;\n    sum3600[bin3600] += avgv * nsample;\n    min3600[(bin3600 + 1) % 3600] = (double) std::numeric_limits<long long>::max();\n    min3600[bin3600] = std::min(min3600[bin3600], minv);\n    max3600[(bin3600 + 1) % 3600] = (double) std::numeric_limits<size_t>::min();\n    max3600[bin3600] = std::max(max3600[bin3600], maxv);\n    n300[(bin300 + 1) % 300] = 0;\n    n300[bin300] += nsample;\n    sum300[(bin300 + 1) % 300] = 0;\n    sum300[bin300] += avgv * nsample;\n    min300[(bin300 + 1) % 300] = (double) std::numeric_limits<long long>::max();\n    min300[bin300] = std::min(min300[bin300], minv);\n    max300[(bin300 + 1) % 300] = (double) std::numeric_limits<size_t>::min();\n    max300[bin300] = std::max(max300[bin300], maxv);\n    n60[(bin60 + 1) % 60] = 0;\n    n60[bin60] += nsample;\n    sum60[(bin60 + 1) % 60] = 0;\n    sum60[bin60] += avgv * nsample;\n    min60[(bin60 + 1) % 60] = (double) std::numeric_limits<long long>::max();\n    min60[bin60] = std::min(min60[bin60], minv);\n    max60[(bin60 + 1) % 60] = (double) std::numeric_limits<size_t>::min();\n    max60[bin60] = std::max(max60[bin60], maxv);\n    n5[(bin5 + 1) % 5] = 0;\n    n5[bin5] += nsample;\n    sum5[(bin5 + 1) % 5] = 0;\n    sum5[bin5] += avgv * nsample;\n    min5[(bin5 + 1) % 5] = (double) std::numeric_limits<long long>::max();\n    min5[bin5] = std::min(min5[bin5], minv);\n    max5[(bin5 + 1) % 5] = (double) std::numeric_limits<size_t>::min();\n    max5[bin5] = std::max(max5[bin5], maxv);\n  }\n\n  void\n  StampZero()\n  {\n    unsigned int bin3600 = (time(0) % 3600);\n    unsigned int bin300 = (time(0) % 300);\n    unsigned int bin60 = (time(0) % 60);\n    unsigned int bin5 = (time(0) % 5);\n    n3600[(bin3600 + 1) % 3600] = 0;\n    n300[(bin300 + 1) % 300] = 0;\n    n60[(bin60 + 1) % 60] = 0;\n    n5[(bin5 + 1) % 5] = 0;\n    sum3600[(bin3600 + 1) % 3600] = 0;\n    sum300[(bin300 + 1) % 300] = 0;\n    sum60[(bin60 + 1) % 60] = 0;\n    sum5[(bin5 + 1) % 5] = 0;\n    min3600[(bin3600 + 1) % 3600] = (double) std::numeric_limits<long long>::max();\n    min300[(bin300 + 1) % 300] = (double) std::numeric_limits<long long>::max();\n    min60[(bin60 + 1) % 60] = (double) std::numeric_limits<long long>::max();\n    min5[(bin5 + 1) % 5] = (double) std::numeric_limits<long long>::max();\n    max3600[(bin3600 + 1) % 3600] = (double) std::numeric_limits<size_t>::min();\n    max300[(bin300 + 1) % 300] = (double) std::numeric_limits<size_t>::min();\n    max60[(bin60 + 1) % 60] = (double) std::numeric_limits<size_t>::min();\n    max5[(bin5 + 1) % 5] = (double) std::numeric_limits<size_t>::min();\n  }\n\n  double\n  GetN3600()\n  {\n    unsigned long sum = 0;\n\n    for (int i = 0; i < 3600; i++) {\n      sum += n3600[i];\n    }\n\n    return (double) sum;\n  }\n\n  double\n  GetAvg3600()\n  {\n    double sum = 0;\n    double n = 0;\n\n    for (int i = 0; i < 3600; i++) {\n      n += n3600[i];\n      sum += sum3600[i];\n    }\n\n    return (sum / n);\n  }\n\n  double\n  GetMin3600()\n  {\n    double minval = (double) std::numeric_limits<long long>::max();\n\n    for (int i = 0; i < 3600; i++) {\n      minval = std::min(min3600[i], minval);\n    }\n\n    return double(minval);\n  }\n\n  double\n  GetMax3600()\n  {\n    double maxval = (double) std::numeric_limits<size_t>::min();\n\n    for (int i = 0; i < 3600; i++) {\n      maxval = std::max(max3600[i], maxval);\n    }\n\n    return double(maxval);\n  }\n\n  double\n  GetN300()\n  {\n    unsigned long sum = 0;\n\n    for (int i = 0; i < 300; i++) {\n      sum += n300[i];\n    }\n\n    return (double) sum;\n  }\n\n  double\n  GetAvg300()\n  {\n    double sum = 0;\n    double n = 0;\n\n    for (int i = 0; i < 300; i++) {\n      n += n300[i];\n      sum += sum300[i];\n    }\n\n    return (sum / n);\n  }\n\n  double\n  GetMin300()\n  {\n    double minval = (double) std::numeric_limits<long long>::max();\n\n    for (int i = 0; i < 300; i++) {\n      minval = std::min(min300[i], minval);\n    }\n\n    return double(minval);\n  }\n\n  double\n  GetMax300()\n  {\n    double maxval = (double) std::numeric_limits<size_t>::min();\n\n    for (int i = 0; i < 300; i++) {\n      maxval = std::max(max300[i], maxval);\n    }\n\n    return double(maxval);\n  }\n\n  double\n  GetN60()\n  {\n    unsigned long sum = 0;\n\n    for (int i = 0; i < 60; i++) {\n      sum += n60[i];\n    }\n\n    return (double) sum;\n  }\n\n  double\n  GetAvg60()\n  {\n    double sum = 0;\n    double n = 0;\n\n    for (int i = 0; i < 60; i++) {\n      n += n60[i];\n      sum += sum60[i];\n    }\n\n    return (sum / n);\n  }\n\n  double\n  GetMin60()\n  {\n    double minval = (double) std::numeric_limits<long long>::max();\n\n    for (int i = 0; i < 60; i++) {\n      minval = std::min(min60[i], minval);\n    }\n\n    return double(minval);\n  }\n\n  double\n  GetMax60()\n  {\n    double maxval = (double) std::numeric_limits<size_t>::min();\n\n    for (int i = 0; i < 60; i++) {\n      maxval = std::max(max60[i], maxval);\n    }\n\n    return double(maxval);\n  }\n\n  double\n  GetN5()\n  {\n    unsigned long sum = 0;\n\n    for (int i = 0; i < 5; i++) {\n      sum += n5[i];\n    }\n\n    return (double) sum;\n  }\n\n  double\n  GetAvg5()\n  {\n    double sum = 0;\n    double n = 0;\n\n    for (int i = 0; i < 5; i++) {\n      n += n5[i];\n      sum += sum5[i];\n    }\n\n    return (sum / n);\n  }\n\n  double\n  GetMin5()\n  {\n    double minval = (double) std::numeric_limits<long long>::max();\n\n    for (int i = 0; i < 5; i++) {\n      minval = std::min(min5[i], minval);\n    }\n\n    return double(minval);\n  }\n\n  double\n  GetMax5()\n  {\n    double maxval = (double) std::numeric_limits<size_t>::min();\n\n    for (int i = 0; i < 5; i++) {\n      maxval = std::max(max5[i], maxval);\n    }\n\n    return double(maxval);\n  }\n\n};\n\n#define __SUM__TOTAL__ \":sum\"\n\n#define ADD_FUSE_STAT(__ID__, __REQ__)          \\\n  EosFuse::Instance().getFuseStat().Add(__ID__,            \\\n                    fuse_req_ctx(__REQ__)->uid, \\\n                    fuse_req_ctx(__REQ__)->gid, \\\n                             1); \\\n  EosFuse::Instance().getFuseStat().Add(__SUM__TOTAL__,            \\\n                    fuse_req_ctx(__REQ__)->uid, \\\n                    fuse_req_ctx(__REQ__)->gid, \\\n                             1);\n\n#define ADD_IO_STAT(__ID__, __VALUE__) \\\n  EosFuse::Instance().getFuseStat().Add(__ID__,   \\\n          0,0,__VALUE__);\n\n#define ADD_CFSD_STAT(__ID__, __REQ__)          \\\n  fs.getFuseStat().Add(__ID__,        \\\n      fuse_req_ctx(__REQ__)->uid, \\\n      fuse_req_ctx(__REQ__)->gid, \\\n      1);            \\\n  fs.getFuseStat().Add(__SUM__TOTAL__,           \\\n           fuse_req_ctx(__REQ__)->uid,       \\\n           fuse_req_ctx(__REQ__)->gid,       \\\n                             1);\n\n#define ADD_CFSD_IO_STAT(__ID__, __VALUE__) \\\n  fs.getFuseStat().Add(__ID__,        \\\n          0,0,__VALUE__);\n\n\n#define EXEC_TIMING_BEGIN(__ID__)               \\\n  struct timeval start__ID__;                   \\\n  struct timeval stop__ID__;                    \\\n  struct timezone tz__ID__;                     \\\n  gettimeofday(&start__ID__, &tz__ID__);\n\n#define EXEC_TIMING_END(__ID__)                                         \\\n  gettimeofday(&stop__ID__, &tz__ID__);                                 \\\n  EosFuse::Instance().getFuseStat().AddExec(__ID__, ((stop__ID__.tv_sec-start__ID__.tv_sec)*1000.0) + ((stop__ID__.tv_usec-start__ID__.tv_usec)/1000.0) );\n\n#define CFSD_TIMING_BEGIN(__ID__)               \\\n  struct timeval start__ID__;                   \\\n  struct timeval stop__ID__;                    \\\n  struct timezone tz__ID__;                     \\\n  gettimeofday(&start__ID__, &tz__ID__);\n\n#define CFSD_TIMING_END(__ID__)                                         \\\n  gettimeofday(&stop__ID__, &tz__ID__);                                 \\\n  fs.getFuseStat().AddExec(__ID__, ((stop__ID__.tv_sec-start__ID__.tv_sec)*1000.0) + ((stop__ID__.tv_usec-start__ID__.tv_usec)/1000.0) );\n\nclass Stat\n{\npublic:\n\n  std::mutex Mutex;\n\n  // first is name of value, then the map\n  google::sparse_hash_map<std::string, google::sparse_hash_map<uid_t, unsigned long long> >\n  StatsUid;\n  google::sparse_hash_map<std::string, google::sparse_hash_map<gid_t, unsigned long long> >\n  StatsGid;\n  google::sparse_hash_map<std::string, google::sparse_hash_map<uid_t, StatAvg> >\n  StatAvgUid;\n  google::sparse_hash_map<std::string, google::sparse_hash_map<gid_t, StatAvg> >\n  StatAvgGid;\n  google::sparse_hash_map<std::string, google::sparse_hash_map<uid_t, StatExt> >\n  StatExtUid;\n  google::sparse_hash_map<std::string, google::sparse_hash_map<gid_t, StatExt> >\n  StatExtGid;\n  google::sparse_hash_map<std::string, std::deque<float> > StatExec;\n\n  double TotalExec;\n\n  size_t sum_ops;\n\n  size_t GetOps()\n  {\n    return sum_ops;\n  }\n\n  size_t GetOpsTS()\n  {\n    std::lock_guard<std::mutex> g {Mutex};\n    return GetOps();\n  }\n\n  void Add(const char* tag, uid_t uid, gid_t gid, unsigned long val);\n\n  void AddExt(const char* tag, uid_t uid, gid_t gid, unsigned long nsample,\n              const double& avgv, const double& minv, const double& maxv);\n\n  void AddExec(const char* tag, float exectime);\n\n  unsigned long long GetTotal(const char* tag);\n\n  // warning: you have to lock the mutex if directly used\n  double GetTotalAvg3600(const char* tag);\n  double GetTotalNExt3600(const char* tag);\n  double GetTotalAvgExt3600(const char* tag);\n  double GetTotalMinExt3600(const char* tag);\n  double GetTotalMaxExt3600(const char* tag);\n\n  // warning: you have to lock the mutex if directly used\n  double GetTotalAvg300(const char* tag);\n  double GetTotalNExt300(const char* tag);\n  double GetTotalAvgExt300(const char* tag);\n  double GetTotalMinExt300(const char* tag);\n  double GetTotalMaxExt300(const char* tag);\n\n  // warning: you have to lock the mutex if directly used\n  double GetTotalAvg60(const char* tag);\n  double GetTotalNExt60(const char* tag);\n  double GetTotalAvgExt60(const char* tag);\n  double GetTotalMinExt60(const char* tag);\n  double GetTotalMaxExt60(const char* tag);\n\n  // warning: you have to lock the mutex if directly used\n  double GetTotalAvg5(const char* tag);\n  double GetTotalNExt5(const char* tag);\n  double GetTotalAvgExt5(const char* tag);\n  double GetTotalMinExt5(const char* tag);\n  double GetTotalMaxExt5(const char* tag);\n\n  // warning: you have to lock the mutex if directly used\n  double GetExec(const char* tag, double& deviation);\n\n  // warning: you have to lock the mutex if directly used\n  double GetTotalExec(double& deviation, size_t& cnt);\n\n  void Clear();\n\n  void PrintOutTotal(std::string& out, bool details = false,\n                     bool monitoring = false, bool numerical = false);\n\n  void PrintOutTotalJson(Json::Value& out);\n\n  void Circulate(ThreadAssistant& assistant);\n\n  Stat() { }\n};\n\n#endif\n"
  },
  {
    "path": "fusex/submount/SubMount.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file SubMount.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief Class managing sub-mounts\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n\n#include \"common/ShellCmd.hh\"\n#include \"common/Path.hh\"\n#include \"submount/SubMount.hh\"\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\nint\n/* -------------------------------------------------------------------------- */\nSubMount::mount(std::string& params, std::string& localpath, std::string& env)\n/* -------------------------------------------------------------------------- */\n{\n  int rc = 0;\n  std::string mountcmd = env + \" mount \";\n  mountcmd += params.substr(6);\n\n  if (geteuid()) {\n    params = \"/var/tmp/eosxd/mnt/\";\n  } else {\n    params = \"/var/run/eosxd/mnt/\";\n  }\n\n  params += localpath;\n  struct stat buf;\n\n  if (::stat(params.c_str(), &buf)) {\n    std::string mkpath = params + \"/dummy\";\n    eos::common::Path mountpath(mkpath.c_str());\n\n    if (!mountpath.MakeParentPath(S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH |\n                                  S_IXOTH)) {\n      eos_static_warning(\"failed to create local mount point path='%s'\",\n                         params.c_str());\n      return -1;\n    }\n\n    mountcmd += \" \";\n    mountcmd += params;\n    eos_static_warning(\"mount='%s' local-path='%s'\", mountcmd.c_str(),\n                       localpath.c_str());\n#ifndef __APPLE__\n    eos::common::ShellCmd cmd(mountcmd);\n    eos::common::cmd_status st = cmd.wait(5);\n    rc = st.exit_code;\n\n    if (!rc) {\n      XrdSysMutexHelper lock(iMutex);\n      mtab[params] = localpath;\n    }\n\n#else\n    rc = EOPNOTSUPP;\n#endif\n  }\n\n  return rc;\n}\n\nint\n/* -------------------------------------------------------------------------- */\nSubMount::squashfuse(std::string& params, std::string& localpath,\n                     std::string& env)\n/* -------------------------------------------------------------------------- */\n{\n  int rc = 0;\n  std::string mountcmd = env + \" squashfuse -o allow_other \";\n  std::string imagepath = localpath;\n  fprintf(stderr, \"%s %s\\n\", params.c_str(), localpath.c_str());\n  size_t spos = imagepath.rfind(\"/\");\n  imagepath.insert(spos + 1, \".\");\n  imagepath += \".sqsh\";\n  mountcmd += imagepath;\n  mountcmd += \" \";\n\n  if (geteuid()) {\n    params = \"/var/tmp/eosxd/mnt/\";\n  } else {\n    params = \"/var/run/eosxd/mnt/\";\n  }\n\n  params += localpath;\n  struct stat buf1;\n  struct stat buf2;\n  eos::common::Path ppath(params.c_str());\n\n  if (::stat(params.c_str(), &buf1) || // the mount path does not exist at all\n      ((!stat(ppath.GetParentPath(), &buf2)) &&\n       (buf2.st_dev == buf1.st_dev))) { // there is nothing mounted here\n    std::string mkpath = params + \"/dummy\";\n    eos::common::Path mountpath(mkpath.c_str());\n\n    if (!mountpath.MakeParentPath(S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH |\n                                  S_IXOTH)) {\n      eos_static_warning(\"failed to create local mount point path='%s'\",\n                         params.c_str());\n      return -1;\n    }\n\n    mountcmd += \" \";\n    mountcmd += params;\n    eos_static_warning(\"mount='%s' local-path='%s'\", mountcmd.c_str(),\n                       localpath.c_str());\n#ifndef __APPLE__\n    eos::common::ShellCmd cmd(mountcmd);\n    eos::common::cmd_status st = cmd.wait(5);\n    rc = st.exit_code;\n\n    if (!rc) {\n      XrdSysMutexHelper lock(iMutex);\n      mtab[params] = localpath;\n    }\n\n#else\n    rc = EOPNOTSUPP;\n#endif\n  }\n\n  return rc;\n}\n\n/* -------------------------------------------------------------------------- */\nvoid\nSubMount::terminate()\n/* -------------------------------------------------------------------------- */\n{\n  XrdSysMutexHelper lock(iMutex);\n\n  for (auto it = mtab.begin(); it != mtab.end(); ++it) {\n    eos_static_warning(\"umount='%s' local-path='%s'\", it->first.c_str(),\n                       it->second.c_str());\n    std::string umountcmd = \"umount -fl \";\n    umountcmd += it->first;\n    eos::common::ShellCmd cmd(umountcmd);\n    eos::common::cmd_status st = cmd.wait(2);\n    int rc = st.exit_code;\n\n    if (rc) {\n      eos_static_warning(\"umount='%s' failed\", it->first.c_str());\n    }\n\n    rc = rmdir(it->first.c_str());\n\n    if (rc) {\n      eos_static_warning(\"rmdir of '%s' failed - errno = %d\", it->first.c_str(),\n                         errno);\n    }\n  }\n\n  mtab.clear();\n}\n"
  },
  {
    "path": "fusex/submount/SubMount.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file SubMount.hh\n//! @author Andreas-Joachim Peters CERN\n//! @brief Class managing sub-mounts\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#ifndef SUBMOUNT_HH_\n#define SUBMOUNT_HH_\n\n#include \"misc/MacOSXHelper.hh\"\n#include <memory>\n#include <map>\n#include \"common/Logging.hh\"\n\nclass SubMount\n{\npublic:\n\n  SubMount() { }\n\n  void terminate();\n\n  ~SubMount() { }\n\n\n  int mount(std::string& params, std::string& localpath, std::string& env);\n  int squashfuse(std::string& params, std::string& localpath, std::string& env);\nprivate:\n  XrdSysMutex iMutex;\n\n  std::map<std::string, std::string> mtab;\n};\n\n\n#endif\n"
  },
  {
    "path": "fusex/tests/CMakeLists.txt",
    "content": "# ------------------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Andreas-Joachim Peters <apeers@cern.ch> CERN\n# ------------------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2016 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ninclude_directories(${CMAKE_BINARY_DIR})\n\nset(LIBRARIES_TO_LINK_TEST_EXECUTABLES_WITH\n  GTest::GTest\n  GTest::Main\n  EosCommon\n  FUSE::FUSE\n  ROCKSDB::ROCKSDB)\n\nset(COMPILE_FLAGS_FOR_TEST_EXECUTABLES \"${EOSXD_COMPILE_FLAGS} -fPIC\")\n\nadd_executable(eos-fusex-tests\n  auth/credential-finder.cc\n  auth/environment-reader.cc\n  auth/logbook.cc\n  auth/login-identifier.cc\n  auth/process-cache.cc\n  auth/process-info.cc\n  auth/rm-info.cc\n  auth/security-checker.cc\n  auth/test-utils.cc\n  auth/utils.cc\n  interval-tree.cc\n  journal-cache.cc\n  rb-tree.cc\n  rocks-kv.cc\n  lru-test.cc\n  ${EOSXD_COMMON_SOURCES})\n\ntarget_link_libraries(eos-fusex-tests PRIVATE\n  eosxd-objects\n  EosFuseAuth\n  ${LIBRARIES_TO_LINK_TEST_EXECUTABLES_WITH})\n\nset_target_properties(eos-fusex-tests\n  PROPERTIES COMPILE_FLAGS ${COMPILE_FLAGS_FOR_TEST_EXECUTABLES})\n\nadd_executable(eos-fusex-stress-tests stress/xrdcl-proxy.cc)\n\ntarget_link_libraries(eos-fusex-stress-tests PRIVATE\n  eosxd-objects\n  EosFuseAuth\n  ${LIBRARIES_TO_LINK_TEST_EXECUTABLES_WITH})\n\nset_target_properties(eos-fusex-stress-tests\n  PROPERTIES COMPILE_FLAGS ${COMPILE_FLAGS_FOR_TEST_EXECUTABLES})\n\nadd_executable(eos-fusex-ioverify ioverify.cc)\n\ntarget_link_libraries(eos-fusex-ioverify PRIVATE\n  ${LIBRARIES_TO_LINK_TEST_EXECUTABLES_WITH})\n\nset_target_properties(eos-fusex-ioverify\n  PROPERTIES COMPILE_FLAGS ${COMPILE_FLAGS_FOR_TEST_EXECUTABLES})\n\ninstall(PROGRAMS eos-fusex-recovery\n  DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR}\n  PERMISSIONS OWNER_READ OWNER_EXECUTE\n              GROUP_READ GROUP_EXECUTE\n              WORLD_READ WORLD_EXECUTE)\n\ninstall(TARGETS eos-fusex-ioverify eos-fusex-tests\n  DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR}\n  PERMISSIONS OWNER_READ OWNER_EXECUTE\n              GROUP_READ GROUP_EXECUTE\n              WORLD_READ WORLD_EXECUTE)\n"
  },
  {
    "path": "fusex/tests/auth/credential-finder.cc",
    "content": "//------------------------------------------------------------------------------\n// File: credential-finder.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"auth/CredentialFinder.hh\"\n#include \"gtest/gtest.h\"\n\nTEST(Environment, BasicSanity)\n{\n  Environment env;\n  std::string envStr = \"KEY1=VALUE\";\n  envStr.append(1, '\\0');\n  envStr += \"non-key value entry\";\n  envStr.append(1, '\\0');\n  envStr += \"Key2=SomeValue\";\n  envStr.append(1, '\\0');\n  envStr += \"KEY1=Duplicate\";\n  envStr.append(1, '\\0');\n  env.fromString(envStr);\n  std::vector<std::string> expected = {\"KEY1=VALUE\", \"non-key value entry\", \"Key2=SomeValue\", \"KEY1=Duplicate\"};\n  ASSERT_EQ(env.getAll(), expected);\n  ASSERT_EQ(env.get(\"KEY1\"), \"VALUE\");\n  ASSERT_EQ(env.get(\"Key2\"), \"SomeValue\");\n  // now try reading from a file\n  const std::string filename(\"/tmp/fuse-testfile\");\n  FILE* out = fopen(filename.c_str(), \"w\");\n  fwrite(envStr.c_str(), 1, envStr.size(), out);\n  fclose(out);\n  Environment env2;\n  env2.fromFile(filename);\n  ASSERT_EQ(env2.getAll(), expected);\n  ASSERT_EQ(env2.get(\"KEY1\"), \"VALUE\");\n  ASSERT_EQ(env2.get(\"Key2\"), \"SomeValue\");\n}\n\nTEST(TrustedCredentials, BasicSanity)\n{\n  TrustedCredentials emptycreds;\n  std::string key;\n\n  ASSERT_TRUE(emptycreds.empty());\n  ASSERT_EQ(emptycreds.toXrdParams(), \"xrd.wantprot=unix\");\n  TrustedCredentials cred0;\n  ASSERT_TRUE(cred0.empty());\n  TrustedCredentials cred1(\n\t\t\t   UserCredentials::MakeKrb5(JailIdentifier(), \"/tmp/some-file\", 5, 6, key),\n\t\t\t   {0, 0},\n\t\t\t   \"\",\n\t\t\t   \"\"\n  );\n  ASSERT_FALSE(cred1.empty());\n  ASSERT_EQ(cred1.toXrdParams(),\n            \"xrd.k5ccname=/tmp/some-file&xrd.wantprot=krb5,unix&xrdcl.secgid=6&xrdcl.secuid=5\");\n  TrustedCredentials cred2(UserCredentials::MakeKrk5(\"keyring-name\", 5, 6, key), {0, 0}, \"\", \"\");\n  ASSERT_FALSE(cred2.empty());\n  ASSERT_EQ(cred2.toXrdParams(),\n            \"xrd.k5ccname=keyring-name&xrd.wantprot=krb5,unix&xrdcl.secgid=6&xrdcl.secuid=5\");\n  TrustedCredentials cred3(\n\t\t\t   UserCredentials::MakeX509(JailIdentifier(), \"/tmp/some-file\", 5, 6, key), {0, 0},\n\t\t\t   \"\", \"\"\n  );\n\n  ASSERT_FALSE(cred3.empty());\n  ASSERT_EQ(cred3.toXrdParams(),\n            \"xrd.gsiusrpxy=/tmp/some-file&xrd.wantprot=gsi,unix&xrdcl.secgid=6&xrdcl.secuid=5\");\n  TrustedCredentials cred4(\n\t\t\t   UserCredentials::MakeX509(JailIdentifier(), \"/tmp/some-evil&file=\", 5, 6, key), {0, 0},\n\t\t\t   \"\", \"\"\n  );\n  ASSERT_FALSE(cred4.empty());\n  ASSERT_EQ(cred4.toXrdParams(), \"xrd.wantprot=unix\");\n}\n"
  },
  {
    "path": "fusex/tests/auth/environment-reader.cc",
    "content": "//------------------------------------------------------------------------------\n// File: environment-reader.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"auth/EnvironmentReader.hh\"\n#include \"gtest/gtest.h\"\n\nTEST(EnvironmentReader, BasicSanity)\n{\n  EnvironmentReader reader(3);\n  Environment env1;\n  env1.fromVector({\"KEY1=VALUE1\", \"KEY2=VALUE2\", \"KEY3=VALUE3\", \"KEY4=VALUE4\"});\n  Environment env2;\n  env2.fromVector({\"KRB5CCNAME=FILE:/tmp/krb-cache\"});\n  reader.inject(3, env1, std::chrono::milliseconds(0));\n  reader.inject(4, env2, std::chrono::milliseconds(10));\n  reader.inject(1, env1, std::chrono::milliseconds(30));\n  reader.inject(3978, env2, std::chrono::milliseconds(1));\n  FutureEnvironment response1 = reader.stageRequest(1);\n  FutureEnvironment response1_2 = reader.stageRequest(1);\n  FutureEnvironment response1_3 = reader.stageRequest(1);\n  FutureEnvironment response3 = reader.stageRequest(3);\n  FutureEnvironment response4 = reader.stageRequest(4);\n  FutureEnvironment response3978 = reader.stageRequest(3978);\n  ASSERT_EQ(response1.contents.get(), env1);\n  ASSERT_EQ(response1_2.contents.get(), env1);\n  ASSERT_EQ(response1_3.contents.get(), env1);\n  ASSERT_EQ(response1.queuedSince, response1_2.queuedSince);\n  ASSERT_EQ(response1.queuedSince, response1_3.queuedSince);\n  ASSERT_EQ(response3.contents.get(), env1);\n  ASSERT_EQ(response4.contents.get(), env2);\n  ASSERT_EQ(response3978.contents.get(), env2);\n}\n\nstatic void inject(EnvironmentReader& reader, size_t from, size_t until)\n{\n  for (size_t i = from; i < until; i++) {\n    Environment env;\n\n    if (!(i % 150)) {\n      env.fromVector({SSTR(\"Key\" << i << \"=Value\" << i)});\n      reader.inject(i, env, std::chrono::milliseconds(i % 3));\n    }\n  }\n}\n\nstatic void issueRequests(EnvironmentReader& reader, size_t from, size_t until)\n{\n  std::vector<FutureEnvironment> responses;\n\n  for (size_t i = from; i < until; i++) {\n    responses.emplace_back(reader.stageRequest(i));\n  }\n\n  for (size_t i = from; i < until; i++) {\n    Environment expected;\n\n    if (!(i % 150)) {\n      expected.fromVector({SSTR(\"Key\" << i << \"=Value\" << i)});\n    }\n\n    ASSERT_EQ(responses[i - from].contents.get(), expected) << i;\n  }\n}\n\nTEST(EnvironmentReader, HeavyLoad)\n{\n  EnvironmentReader reader(30);\n  inject(reader, 0, 10000);\n  // 10 threads * 2, 1000 requests each\n  const size_t nthreads = 10;\n  std::vector<std::thread> threads;\n\n  for (size_t i = 0; i < nthreads; i++) {\n    threads.emplace_back(issueRequests, std::ref(reader), (i * 1000),\n                         (i + 1) * 1000);\n    threads.emplace_back(issueRequests, std::ref(reader), (i * 1000),\n                         (i + 1) * 1000);\n  }\n\n  for (size_t i = 0; i < threads.size(); i++) {\n    threads[i].join();\n  }\n}\n"
  },
  {
    "path": "fusex/tests/auth/logbook.cc",
    "content": "//------------------------------------------------------------------------------\n// File: logbook.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"auth/Logbook.hh\"\n#include \"auth/Utils.hh\"\n#include \"gtest/gtest.h\"\n\nTEST(Logbook, BasicSanity) {\n  Logbook logbook(true);\n  logbook.insert(\"Test Test\");\n  logbook.insert(\"123\");\n\n  ASSERT_EQ(logbook.toString(), \"Test Test\\n123\\n\");\n}\n\nTEST(Logbook, NotActive) {\n  Logbook logbook(false);\n  logbook.insert(\"123\");\n  ASSERT_EQ(logbook.toString(), \"\");\n}\n\nTEST(Logbook, Scoping) {\n  Logbook logbook(true);\n\n  LogbookScope scope1(logbook.makeScope(\"Scope 1\"));\n  scope1.insert(\"Message 1\");\n  scope1.insert(\"Message 2\");\n\n  std::cerr << logbook.toString() << std::endl;\n  ASSERT_EQ(logbook.toString(), \"-- Scope 1\\n  Message 1\\n  Message 2\\n\");\n\n  LogbookScope scope2(scope1.makeScope(\"Sub-scope\"));\n  scope2.insert(\"Some other message 1\");\n  scope2.insert(\"Some other message 2\");\n\n  std::cerr << logbook.toString() << std::endl;\n  ASSERT_EQ(logbook.toString(),\n    \"-- Scope 1\\n\"\n    \"  Message 1\\n\"\n    \"  Message 2\\n\"\n    \"  -- Sub-scope\\n\"\n    \"    Some other message 1\\n\"\n    \"    Some other message 2\\n\"\n  );\n\n}"
  },
  {
    "path": "fusex/tests/auth/login-identifier.cc",
    "content": "//------------------------------------------------------------------------------\n// File: login-identifier.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"auth/LoginIdentifier.hh\"\n#include \"gtest/gtest.h\"\n\nTEST(LoginIdentifier, BasicSanity)\n{\n  ASSERT_EQ(LoginIdentifier(1).getStringID(), \"AAAAAAAE\");\n  ASSERT_EQ(LoginIdentifier(2).getStringID(), \"AAAAAAAI\");\n  ASSERT_EQ(LoginIdentifier(3).getStringID(), \"AAAAAAAM\");\n  ASSERT_EQ(LoginIdentifier(1000, 1000, 178, 3).getStringID(), \"*APoA-gM\");\n  ASSERT_EQ(LoginIdentifier(41).getStringID(), \"AAAAAACk\");\n}\n"
  },
  {
    "path": "fusex/tests/auth/process-cache.cc",
    "content": "//------------------------------------------------------------------------------\n// File: process-cache.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"auth/ProcessCache.hh\"\n#include \"auth/UserCredentialFactory.hh\"\n#include \"auth/Logbook.hh\"\n#include \"test-utils.hh\"\n#include <gtest/gtest.h>\n\nTEST_F(UnixAuthF, BasicSanity)\n{\n  injectProcess(1234, 1, 1234, 1234, 9999, 0);\n  ProcessSnapshot snapshot = processCache()->retrieve(1234, 5, 6, false);\n  ASSERT_EQ(snapshot->getXrdLogin(), LoginIdentifier(5, 6, 1234,\n            0).getStringID());\n  ProcessSnapshot snapshot2 = processCache()->retrieve(1234, 5, 6, false);\n  ASSERT_EQ(snapshot2->getXrdLogin(), LoginIdentifier(5, 6, 1234,\n            0).getStringID());\n  ProcessSnapshot snapshot3 = processCache()->retrieve(1234, 5, 6, true);\n  ASSERT_EQ(snapshot3->getXrdLogin(), LoginIdentifier(5, 6, 1234,\n            1).getStringID());\n  ProcessSnapshot snapshot4 = processCache()->retrieve(1234, 7, 6, false);\n  ASSERT_EQ(snapshot4->getXrdLogin(), LoginIdentifier(7, 6, 1234,\n            0).getStringID());\n  injectProcess(1235, 1, 1235, 1235, 9999, 0);\n  ProcessSnapshot snapshot5 = processCache()->retrieve(1235, 8, 6, false);\n  ASSERT_EQ(snapshot5->getXrdLogin(), LoginIdentifier(8, 6, 1235,\n            0).getStringID());\n}\n\nTEST_F(Krb5AuthF, BasicSanity)\n{\n  injectProcess(1234, 1, 1234, 1234, 9999, 0);\n  securityChecker()->inject(localJail().id, \"/tmp/my-creds\", 1000, 0400, {1, 1});\n  environmentReader()->inject(1234, createEnv(\"/tmp/my-creds\", \"\"));\n  ProcessSnapshot snapshot = processCache()->retrieve(1234, 1000, 1000, false);\n  ASSERT_EQ(snapshot->getXrdLogin(), LoginIdentifier(1).getStringID());\n  ASSERT_EQ(snapshot->getXrdCreds(),\n            \"xrd.k5ccname=/tmp/my-creds&xrd.wantprot=krb5,unix&xrdcl.secgid=1000&xrdcl.secuid=1000\");\n}\n\nTEST_F(Krb5AuthF, UnixFallback)\n{\n  injectProcess(1234, 1, 1234, 1234, 9999, 0);\n  ProcessSnapshot snapshot = processCache()->retrieve(1234, 1000, 1000, false);\n  ASSERT_EQ(snapshot->getXrdLogin(), LoginIdentifier(1000, 1000, 1234,\n            0).getStringID());\n  ASSERT_EQ(snapshot->getXrdCreds(), \"xrd.wantprot=unix\");\n}\n\nTEST(UserCredentialFactory, BothKrb5AndX509) {\n  CredentialConfig config;\n  config.use_user_krb5cc = true;\n  config.use_user_gsiproxy = true;\n  config.tryKrb5First = true;\n  config.use_user_sss = true;\n\n  Environment env;\n  env.push_back(\"KRB5CCNAME=/tmp/my-krb5-creds\");\n  env.push_back(\"X509_USER_PROXY=/tmp/my-x509-creds\");\n\n  JailIdentifier id = JailIdentifier::Make(5, 3);\n  UserCredentialFactory factory(config);\n\n  LogbookScope empty;\n  SearchOrder searchOrder;\n  std::string key;\n  ASSERT_TRUE(factory.parseSingle(empty, \"defaults\", id, env, 9, 8, searchOrder));\n\n  ASSERT_EQ(searchOrder.size(), 3u);\n  ASSERT_EQ(searchOrder[0], UserCredentials::MakeSSS(\"\", 9, 8, key));\n  ASSERT_EQ(searchOrder[1], UserCredentials::MakeKrb5(id, \"/tmp/my-krb5-creds\", 9, 8, key));\n  ASSERT_EQ(searchOrder[2], UserCredentials::MakeX509(id, \"/tmp/my-x509-creds\", 9, 8, key));\n\n  // Now swap krb5 <-> x509 order\n  config.tryKrb5First = false;\n  factory = UserCredentialFactory(config);\n\n  searchOrder.clear();\n  ASSERT_TRUE(factory.parseSingle(empty, \"defaults\", id, env, 8, 9, searchOrder));\n  // factory.addDefaultsFromEnv(id, env, 8, 9, searchOrder);\n\n  ASSERT_EQ(searchOrder.size(), 3u);\n  ASSERT_EQ(searchOrder[0], UserCredentials::MakeSSS(\"\", 8, 9, key));\n  ASSERT_EQ(searchOrder[1], UserCredentials::MakeX509(id, \"/tmp/my-x509-creds\", 8, 9, key));\n  ASSERT_EQ(searchOrder[2], UserCredentials::MakeKrb5(id, \"/tmp/my-krb5-creds\", 8, 9, key));\n}\n\nTEST(UserCredentialFactory, JustKrb5) {\n  CredentialConfig config;\n  config.use_user_krb5cc = true;\n  config.use_user_gsiproxy = false;\n  config.use_user_sss = false;\n\n  Environment env;\n  env.push_back(\"KRB5CCNAME=FILE:/tmp/my-krb5-creds\");\n  env.push_back(\"X509_USER_PROXY=/tmp/my-x509-creds\");\n\n  JailIdentifier id = JailIdentifier::Make(5, 3);\n  UserCredentialFactory factory(config);\n\n  LogbookScope empty;\n  SearchOrder searchOrder;\n  std::string key;\n\n  ASSERT_TRUE(factory.parseSingle(empty, \"defaults\", id, env, 12, 14, searchOrder));\n\n  ASSERT_EQ(searchOrder.size(), 1u);\n  ASSERT_EQ(searchOrder[0], UserCredentials::MakeKrb5(id, \"/tmp/my-krb5-creds\", 12, 14, key));\n}\n\nTEST(UserCredentialFactory, JustKrk5) {\n  CredentialConfig config;\n  config.use_user_krb5cc = true;\n  config.use_user_gsiproxy = false;\n  config.use_user_sss = false;\n\n  Environment env;\n  env.push_back(\"KRB5CCNAME=KEYRING:my-keyring\");\n  env.push_back(\"X509_USER_PROXY=/tmp/my-x509-creds\");\n\n  JailIdentifier id = JailIdentifier::Make(5, 3);\n  UserCredentialFactory factory(config);\n\n  LogbookScope empty;\n  SearchOrder searchOrder;\n  std::string key;\n  ASSERT_TRUE(factory.parseSingle(empty, \"defaults\", id, env, 19, 15, searchOrder));\n\n  ASSERT_EQ(searchOrder.size(), 1u);\n  ASSERT_EQ(searchOrder[0], UserCredentials::MakeKrk5(\"KEYRING:my-keyring\", 19, 15, key));\n}\n\nTEST(UserCredentialFactory, ParseSingleKrb5) {\n  CredentialConfig config;\n  config.use_user_krb5cc = true;\n\n  JailIdentifier id = JailIdentifier::Make(2, 3);\n  UserCredentialFactory factory(config);\n\n  Environment env;\n  LogbookScope empty;\n  SearchOrder searchOrder;\n  std::string key;\n\n  ASSERT_TRUE(factory.parseSingle(empty, \"krb:FILE:/some-file\", id, env, 100, 101, searchOrder));\n  ASSERT_EQ(searchOrder.size(), 1u);\n  ASSERT_EQ(searchOrder[0], UserCredentials::MakeKrb5(id, \"/some-file\", 100, 101, key));\n\n  searchOrder.clear();\n  ASSERT_TRUE(factory.parseSingle(empty, \"krb:/some-file-2\", id, env, 100, 101, searchOrder));\n  ASSERT_EQ(searchOrder.size(), 1u);\n  ASSERT_EQ(searchOrder[0], UserCredentials::MakeKrb5(id, \"/some-file-2\", 100, 101, key));\n\n  config.use_user_krb5cc = false;\n  factory = UserCredentialFactory(config);\n\n  searchOrder.clear();\n  ASSERT_TRUE(factory.parseSingle(empty, \"krb:FILE:/some-file\", id, env, 100, 101, searchOrder));\n  ASSERT_EQ(searchOrder.size(), 0u);\n}\n\nTEST(UserCredentialFactory, ParseSingleKrk5) {\n  CredentialConfig config;\n  config.use_user_krb5cc = true;\n\n  JailIdentifier id = JailIdentifier::Make(2, 3);\n  UserCredentialFactory factory(config);\n\n  Environment env;\n  LogbookScope empty;\n  SearchOrder searchOrder;\n  std::string key;\n  ASSERT_TRUE(factory.parseSingle(empty, \"krb:KEYRING:my-keyring\", id, env, 100, 100, searchOrder));\n\n  ASSERT_EQ(searchOrder.size(), 1u);\n  ASSERT_EQ(searchOrder[0], UserCredentials::MakeKrk5(\"KEYRING:my-keyring\", 100, 100, key));\n}\n\nTEST(UserCredentialFactory, ParseSingleX509) {\n  CredentialConfig config;\n  config.use_user_gsiproxy = true;\n\n  JailIdentifier id = JailIdentifier::Make(2, 3);\n  UserCredentialFactory factory(config);\n\n  Environment env;\n  LogbookScope empty;\n  SearchOrder searchOrder;\n  std::string key;\n\n  ASSERT_TRUE(factory.parseSingle(empty, \"x509:/tmp/my-gsi-creds\", id, env, 200, 201, searchOrder));\n\n  ASSERT_EQ(searchOrder.size(), 1u);\n  ASSERT_EQ(searchOrder[0], UserCredentials::MakeX509(id, \"/tmp/my-gsi-creds\", 200, 201, key));\n}\n\nTEST(UserCredentialFactory, ParseEnv) {\n  CredentialConfig config;\n  config.use_user_krb5cc = true;\n\n  JailIdentifier id = JailIdentifier::Make(2, 3);\n  UserCredentialFactory factory(config);\n\n  Environment env;\n  env.push_back(\"KRB5CCNAME=/tmp-krbccname\");\n  env.push_back(\"EOS_FUSE_CREDS=krb:/tmp/first,krb:/tmp/second,defaults\");\n\n  LogbookScope empty;\n  SearchOrder searchOrder = factory.parse(empty, id, env, 100, 100);\n  std::string key;\n\n  ASSERT_EQ(searchOrder.size(), 3u);\n  ASSERT_EQ(searchOrder[0], UserCredentials::MakeKrb5(id, \"/tmp/first\", 100, 100, key));\n  ASSERT_EQ(searchOrder[1], UserCredentials::MakeKrb5(id, \"/tmp/second\", 100, 100, key));\n  ASSERT_EQ(searchOrder[2], UserCredentials::MakeKrb5(id, \"/tmp-krbccname\", 100, 100, key));\n\n  env = {};\n  env.push_back(\"KRB5CCNAME=/tmp-krbccname\");\n  env.push_back(\"EOS_FUSE_CREDS=krb:/tmp/first,krb:/tmp/second\");\n  searchOrder = factory.parse(empty, id, env, 100, 100);\n\n  ASSERT_EQ(searchOrder.size(), 2u);\n  ASSERT_EQ(searchOrder[0], UserCredentials::MakeKrb5(id, \"/tmp/first\", 100, 100, key));\n  ASSERT_EQ(searchOrder[1], UserCredentials::MakeKrb5(id, \"/tmp/second\", 100, 100, key));\n}\n"
  },
  {
    "path": "fusex/tests/auth/process-info.cc",
    "content": "//------------------------------------------------------------------------------\n// File: process-info.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"auth/ProcessInfo.hh\"\n#include \"gtest/gtest.h\"\n\nTEST(ProcessInfoProvider, BasicSanity)\n{\n  std::string\n  sampleProc(\"10823 (zsh) S 10815 10823 10823 34819 10874 4194304 3022 2685 0 \"\n             \"0 8 4 0 0 20 0 1 0 70104 47996928 1870 18446744073709551615 \"\n             \"93955198316544 93955199085420 140720349285888 0 0 0 2 3686404 \"\n             \"134295555 1 0 0 17 1 0 0 0 0 0 93955201186664 93955201214728 \"\n             \"93955201884160 140720349292924 140720349292928 140720349292928 \"\n             \"140720349294575 0\");\n  ProcessInfo pinfo;\n  ASSERT_TRUE(ProcessInfoProvider::fromString(sampleProc, \"\", pinfo));\n  ASSERT_EQ(pinfo.getPid(), 10823);\n  ASSERT_EQ(pinfo.getParentId(), 10815);\n  ASSERT_EQ(pinfo.getSid(), 10823);\n  ASSERT_EQ(pinfo.getStartTime(), 70104);\n}\n\nTEST(ProcessInfoProvider, BasicSanity2)\n{\n  std::string\n  sampleProc(\"9631 (vim) S 9593 9631 9593 34825 9631 4194304 1731 0 0 0 18 1 0 \"\n             \"0 20 0 1 0 28017391 186519552 4535 18446744073709551615 \"\n             \"94905521688576 94905524542468 140735046390256 0 0 0 0 12288 \"\n             \"1837256447 1 0 0 17 0 0 0 0 0 0 94905526642120 94905526801172 \"\n             \"94905547104256 140735046398239 140735046398243 140735046398243 \"\n             \"140735046402027 0\");\n  ProcessInfo pinfo;\n  ASSERT_TRUE(ProcessInfoProvider::fromString(sampleProc, \"\", pinfo));\n  ASSERT_EQ(pinfo.getPid(), 9631);\n  ASSERT_EQ(pinfo.getParentId(), 9593);\n  ASSERT_EQ(pinfo.getSid(), 9593);\n  ASSERT_EQ(pinfo.getStartTime(), 28017391);\n}\n\nTEST(ProcessInfoProvider, ParseBroken)\n{\n  ProcessInfo pinfo;\n  std::string broken1(\"9631 (vim) S 9593 9631 9593\");\n  ASSERT_FALSE(ProcessInfoProvider::fromString(broken1, \"\", pinfo));\n  std::string broken2(\"adfadfasd\");\n  ASSERT_FALSE(ProcessInfoProvider::fromString(broken2, \"\", pinfo));\n  std::string\n  broken3(\"9631 (vim) S 9593 9631 9593 34825 9631 4194304 1731 0 0 0 18 1 0 0 20 0 1 0\");\n  ASSERT_FALSE(ProcessInfoProvider::fromString(broken3, \"\", pinfo));\n  ASSERT_TRUE(pinfo.isEmpty());\n  std::string\n  good(\"9631 (vim) S 9593 9631 9593 34825 9631 4194304 1731 0 0 0 18 1 0 0 20 0 1 0 28017391\");\n  ASSERT_TRUE(ProcessInfoProvider::fromString(good, \"\", pinfo));\n  ASSERT_EQ(pinfo.getPid(), 9631);\n  ASSERT_EQ(pinfo.getParentId(), 9593);\n  ASSERT_EQ(pinfo.getSid(), 9593);\n  ASSERT_EQ(pinfo.getStartTime(), 28017391);\n}\n\nTEST(ProcessInfoProvider, ParseCmdline)\n{\n  ProcessInfo pinfo;\n  std::string\n  sampleProc(\"23829 (vim) S 23713 23829 23713 34817 23829 4194304 8131 917 0 0 \"\n             \"26 4 0 0 20 0 1 0 28202761 187371520 4651 18446744073709551615 \"\n             \"94763168460800 94763171314692 140721547023136 0 0 0 0 12288 \"\n             \"1837256447 1 0 0 17 1 0 0 0 0 0 94763173414344 94763173573396 \"\n             \"94763190026240 140721547026699 140721547026715 140721547026715 \"\n             \"140721547030507 0\");\n  std::string cmdline(SSTR(\"vim\" << '\\0' << \"eos.spec.in\"));\n  ASSERT_TRUE(ProcessInfoProvider::fromString(sampleProc, cmdline, pinfo));\n  ASSERT_EQ(pinfo.getPid(), 23829);\n  ASSERT_EQ(pinfo.getParentId(), 23713);\n  ASSERT_EQ(pinfo.getSid(), 23713);\n  ASSERT_EQ(pinfo.getStartTime(), 28202761);\n  std::vector<std::string> tmp{ \"vim\", \"eos.spec.in\"};\n  ASSERT_EQ(pinfo.getCmd(), tmp);\n}\n\n#ifndef __APPLE__\n\nTEST(ProcessInfoProvider, GetMyProcessInfo)\n{\n  ProcessInfoProvider processInfoProvider;\n  ProcessInfo myself;\n  ASSERT_TRUE(processInfoProvider.retrieveFull(getpid(), myself));\n  ASSERT_FALSE(myself.isEmpty());\n  ASSERT_THROW(processInfoProvider.retrieveFull(getpid(), myself),\n               FatalException);\n  ProcessInfo parent;\n  ASSERT_TRUE(processInfoProvider.retrieveFull(getppid(), parent));\n  ASSERT_EQ(myself.getParentId(), parent.getPid());\n  std::cerr << \"My cmdline: \" << myself.cmdStr << std::endl;\n  std::cerr << \"Parent's cmdline: \" << parent.cmdStr << std::endl;\n}\n#endif\n\nTEST(ProcessInfoProvider, BasicSanity3)\n{\n  ProcessInfoProvider processInfoProvider;\n  ProcessInfo info;\n  info.fillStat(123, 234, 345, 456, 11111, 0);\n  info.fillCmdline({\"/bin/bash\", \"some-script.sh\"});\n  processInfoProvider.inject(info.getPid(), info);\n  ProcessInfo retrieved;\n  ASSERT_FALSE(processInfoProvider.retrieveBasic(111, retrieved));\n  ASSERT_TRUE(processInfoProvider.retrieveBasic(123, retrieved));\n  ASSERT_EQ(retrieved.getPid(), info.getPid());\n  ASSERT_EQ(retrieved.getParentId(), info.getParentId());\n  ASSERT_TRUE(retrieved.getCmd().empty());\n}\n"
  },
  {
    "path": "fusex/tests/auth/rm-info.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file rm-info.cc\n//! @author Georgios Bitzes CERN\n//! @brief tests for RmrfGuard class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"auth/RmInfo.hh\"\n#include <gtest/gtest.h>\n\nTEST(RmInfo, BasicSanity1)\n{\n  RmInfo parser(\"/usr/bin/rm\", {\"rm\", \"/eos/some-file\"});\n  ASSERT_TRUE(parser.isRm());\n  ASSERT_FALSE(parser.isRecursive());\n}\n\nTEST(RmInfo, BasicSanity2)\n{\n  RmInfo parser(\"/bin/rm\", {\"rm\", \"-r\", \"/eos/some-folder\"});\n  ASSERT_TRUE(parser.isRm());\n  ASSERT_TRUE(parser.isRecursive());\n}\n\nTEST(RmInfo, BasicSanity3)\n{\n  RmInfo parser(\"/usr/bin/git\", {\"git\", \"-r\", \"aaaa\"});\n  ASSERT_FALSE(parser.isRm());\n  ASSERT_FALSE(parser.isRecursive());\n}\n\nTEST(RmInfo, BasicSanity4)\n{\n  RmInfo parser(\"/usr/bin/rm\", {\"rm\", \"-rf\", \".\"});\n  ASSERT_TRUE(parser.isRm());\n  ASSERT_TRUE(parser.isRecursive());\n}\n"
  },
  {
    "path": "fusex/tests/auth/security-checker.cc",
    "content": "//------------------------------------------------------------------------------\n// File: security-checker.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"auth/SecurityChecker.hh\"\n#include <gtest/gtest.h>\n\nTEST(SecurityChecker, BasicSanity)\n{\n  JailInformation localJail;\n  localJail.sameJailAsThisPid = true;\n\n  SecurityChecker checker(false);\n  checker.inject(localJail.id, \"/tmp/ayy/lmao\", 1337, 0455, {42, 42} );\n  ASSERT_EQ(checker.lookup(localJail, \"/tmp/aaa\", 1000, 1000),\n            SecurityChecker::Info(CredentialState::kCannotStat, {0, 0} ));\n  ASSERT_EQ(checker.lookup(localJail, \"/tmp/ayy/lmao\", 1000, 1000),\n            SecurityChecker::Info(CredentialState::kBadPermissions, {0, 0} ));\n  ASSERT_EQ(checker.lookup(localJail, \"/tmp/ayy/lmao\", 1337, 1000),\n            SecurityChecker::Info(CredentialState::kBadPermissions, {0, 0} ));\n  checker.inject(localJail.id, \"/tmp/123\", 1234, 0400, {42, 42} );\n  ASSERT_EQ(checker.lookup(localJail, \"/tmp/123\", 1000, 1000),\n            SecurityChecker::Info(CredentialState::kBadPermissions, {0, 0} ));\n  ASSERT_EQ(checker.lookup(localJail, \"/tmp/123\", 1234, 1000),\n            SecurityChecker::Info(CredentialState::kOk, {42, 42} ));\n  ASSERT_EQ(checker.lookup(localJail, \"/tmp/234\", 1234, 1000),\n            SecurityChecker::Info(CredentialState::kCannotStat, {0, 0} ));\n  checker.inject(localJail.id, \"/tmp/123\", 1111, 0700, {37, 37} );\n  ASSERT_EQ(checker.lookup(localJail, \"/tmp/123\", 1111, 1000),\n            SecurityChecker::Info(CredentialState::kOk, {37, 37} ));\n  ASSERT_EQ(checker.lookup(localJail, \"/tmp/123\", 1112, 1000),\n            SecurityChecker::Info(CredentialState::kBadPermissions, {0, 0} ));\n}\n"
  },
  {
    "path": "fusex/tests/auth/test-utils.cc",
    "content": "//------------------------------------------------------------------------------\n// File: test-utils.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"test-utils.hh\"\n\n//------------------------------------------------------------------------------\n// Create environment with the given variables\n//------------------------------------------------------------------------------\nEnvironment AuthenticationFixture::createEnv(const std::string& kerberosPath,\n  const std::string& x509Path)\n{\n  Environment env;\n\n  if(!kerberosPath.empty()) {\n  \tenv.push_back(SSTR(\"KRB5CCNAME=FILE:\" << kerberosPath));\n  }\n\n  if(!x509Path.empty()) {\n  \tenv.push_back(SSTR(\"X509_USER_PROXY=\" << x509Path));\n  }\n\n  return env;\n}\n"
  },
  {
    "path": "fusex/tests/auth/test-utils.hh",
    "content": "//------------------------------------------------------------------------------\n// File: test-utils.hh\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef AUTH_TEST_UTILS_HH\n#define AUTH_TEST_UTILS_HH\n\n#include <gtest/gtest.h>\n#include \"auth/ProcessCache.hh\"\n#include \"auth/AuthenticationGroup.hh\"\n\n//------------------------------------------------------------------------------\n// Helper class to instantiate and use an AuthenticationGroup.\n//------------------------------------------------------------------------------\nclass AuthenticationFixture : public AuthenticationGroup {\npublic:\n  //----------------------------------------------------------------------------\n  // Constructor\n  //----------------------------------------------------------------------------\n  AuthenticationFixture(const CredentialConfig &config)\n  : AuthenticationGroup(config) {\n    myLocalJail.sameJailAsThisPid = true;\n  }\n\n  //----------------------------------------------------------------------------\n  // Get CAS path - maybe make configurable in the future, or something\n  //----------------------------------------------------------------------------\n  static std::string getCASPath() {\n    return \"/tmp/eos-fusex-unit-tests/cas\";\n  }\n\n  //----------------------------------------------------------------------------\n  // Initialize CAS\n  //----------------------------------------------------------------------------\n  static void InitializeCAS() {\n    system(SSTR(\"rm -rf \" << getCASPath()).c_str());\n    system(SSTR(\"mkdir -p \" << getCASPath()).c_str());\n  }\n\n  //----------------------------------------------------------------------------\n  // Make unix-only configuration\n  //----------------------------------------------------------------------------\n  static CredentialConfig makeUnixConfig() {\n    InitializeCAS(); // This slows the tests down, maybe fix later\n\n    CredentialConfig config;\n    config.credentialStore = getCASPath();\n    return config;\n  }\n\n  //----------------------------------------------------------------------------\n  // Make kerberos-only configuration\n  //----------------------------------------------------------------------------\n  static CredentialConfig makeKrb5Config() {\n    InitializeCAS(); // This slows the tests down, maybe fix later\n\n    CredentialConfig config;\n    config.use_user_krb5cc = true;\n    config.fuse_shared = true;\n    config.credentialStore = getCASPath();\n    return config;\n  }\n\n  //----------------------------------------------------------------------------\n  // Inject fake process with given properties\n  //----------------------------------------------------------------------------\n  void injectProcess(pid_t pid, pid_t ppid, pid_t pgrp, pid_t sid,\n                     Jiffies startup, unsigned flags) {\n    ProcessInfo info;\n    info.fillStat(pid, ppid, pgrp, sid, startup, flags);\n    processInfoProvider()->inject(pid, info);\n  }\n\n  //----------------------------------------------------------------------------\n  // Create environment with the given variables\n  //----------------------------------------------------------------------------\n  Environment createEnv(const std::string& kerberosPath,\n                        const std::string& x509Path);\n\n  //----------------------------------------------------------------------------\n  // Define a standard local jail\n  //----------------------------------------------------------------------------\n  const JailInformation& localJail() const {\n    return myLocalJail;\n  }\n\nprivate:\n  JailInformation myLocalJail;\n};\n\n//------------------------------------------------------------------------------\n// Unix authentication fixture - any tests using this are pre-configured\n// to use unix only\n//------------------------------------------------------------------------------\nclass UnixAuthF : public AuthenticationFixture, public ::testing::Test {\npublic:\n  UnixAuthF() : AuthenticationFixture(makeUnixConfig()) {}\n};\n\n//------------------------------------------------------------------------------\n// krb5 authentication fixture - any tests using this are pre-configured\n// to use krb5 only, with fallback to unix\n//------------------------------------------------------------------------------\nclass Krb5AuthF : public AuthenticationFixture, public ::testing::Test {\npublic:\n  Krb5AuthF() : AuthenticationFixture(makeKrb5Config()) {}\n};\n\n#endif\n"
  },
  {
    "path": "fusex/tests/auth/utils.cc",
    "content": "//------------------------------------------------------------------------------\n// File: utils.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"auth/Utils.hh\"\n#include \"auth/JailIdentifier.hh\"\n#include \"auth/UuidStore.hh\"\n#include \"common/SymKeys.hh\"\n#include <gtest/gtest.h>\n\nTEST(ChopTrailingSlashes, BasicSanity)\n{\n  ASSERT_EQ(chopTrailingSlashes(\"/test/b\"), \"/test/b\");\n  ASSERT_EQ(chopTrailingSlashes(\"/test/b/\"), \"/test/b\");\n  ASSERT_EQ(chopTrailingSlashes(\"/test/b///\"), \"/test/b\");\n  ASSERT_EQ(chopTrailingSlashes(\"/b///\"), \"/b\");\n  ASSERT_EQ(chopTrailingSlashes(\"//\"), \"/\");\n  ASSERT_EQ(chopTrailingSlashes(\"/\"), \"/\");\n  ASSERT_EQ(chopTrailingSlashes(\"\"), \"\");\n}\n\nTEST(sha256, BasicSanity)\n{\n  ASSERT_EQ(eos::common::SymKey::HexSha256(\"12345\"),\n            \"5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5\");\n}\n\nTEST(FileReadWrite, BasicSanity)\n{\n  ASSERT_EQ(system(\"rm -rf /tmp/eos-fusex-unit-tests/\"), 0);\n  ASSERT_EQ(system(\"mkdir /tmp/eos-fusex-unit-tests/\"), 0);\n  ASSERT_TRUE(writeFile600(\"/tmp/eos-fusex-unit-tests/pickles\",\n                           \"chicken chicken chicken chicken\"));\n  std::string contents;\n  ASSERT_TRUE(readFile(\"/tmp/eos-fusex-unit-tests/pickles\", contents));\n  ASSERT_EQ(contents, \"chicken chicken chicken chicken\");\n}\n\nTEST(JailIdentifier, IdentifyMyself)\n{\n  JailResolver jr;\n  JailIdentifier id = jr.resolveIdentifier(getpid());\n  std::cout << id.describe() << std::endl;\n  ASSERT_TRUE(id.ok());\n  JailIdentifier id2 = jr.resolveIdentifier(getppid());\n  std::cout << id2.describe() << std::endl;\n  ASSERT_TRUE(id2.ok());\n  JailIdentifier id3 = jr.resolveIdentifier(getsid(getpid()));\n  std::cout << id3.describe() << std::endl;\n  ASSERT_TRUE(id3.ok());\n  ASSERT_EQ(id, id2);\n  ASSERT_EQ(id, id3);\n  JailInformation ji = jr.resolve(getpid());\n  ASSERT_EQ(ji.id, id);\n  ASSERT_EQ(ji.pid, getpid());\n  ASSERT_TRUE(ji.sameJailAsThisPid);\n}\n\nTEST(UuidStore, BasicSanity)\n{\n  ASSERT_EQ(system(\"rm -rf /tmp/eos-fusex-unit-tests/\"), 0);\n  ASSERT_EQ(system(\"mkdir /tmp/eos-fusex-unit-tests/\"), 0);\n  ASSERT_EQ(system(\"touch /tmp/eos-fusex-unit-tests/random-file\"), 0);\n  ASSERT_EQ(system(\"touch /tmp/eos-fusex-unit-tests/eos-fusex-uuid-store-asdf\"),\n            0);\n  UuidStore store(\"/tmp/eos-fusex-unit-tests/\");\n  // ensure files starting with \"eos-fusex-uuid-store-\" were cleared out, but not\n  // any others\n  struct stat repostat;\n  ASSERT_EQ(::stat(\"/tmp/eos-fusex-unit-tests/random-file\", &repostat), 0);\n  ASSERT_EQ(::stat(\"/tmp/eos-fusex-unit-tests/eos-fusex-uuid-store-asdf\",\n                   &repostat), -1);\n  std::string path = store.put(\"pickles\");\n  std::cout << path << std::endl;\n  std::string contents;\n  ASSERT_TRUE(readFile(path, contents));\n  ASSERT_EQ(contents, \"pickles\");\n}\n"
  },
  {
    "path": "fusex/tests/eos-fusex-git-annex",
    "content": "#!/bin/bash\nset -x #echo on\n\nUSAGE=\"usage: eos-test-fusex-git-annex <test-dir> \"\n\n[[ -z \"$1\" ]] && echo $USAGE  && exit -1\n\ntreedir=$1\nfail=0\ncd $treedir\ntestdir=`uuidgen`\nmkdir $testdir\ncd $testdir\ngit init\ngit annex init\ncp /etc/passwd . \ngit annex add passwd\nsleep 2\nls | grep passwd || fail=1\nrm passwd\nrm -rf .git\ncd $treedir\nrmdir $testdir\nexit $fail\n\n\n\n\n"
  },
  {
    "path": "fusex/tests/eos-fusex-recovery",
    "content": "#!/bin/bash\nif [ \"$1\" = \"1\" ] ; then \nhasrecover=`eos space ls recover`;\ntest -z $hasrecover || (echo \" you have a recover space already defined - stopping this test\" && exit -1)\n\neos space define recover 4 4 \nfor name in `seq -w 1 4`; do \n  mkdir -p /_recover/0$name/\n  rm -rf /_recover/0$name/.eos*\n  chown daemon:daemon /_recover/0$name/\n  eosfstregister -r localhost:1094 /_recover/0$name recover:1\ndone\neos space set recover on \n\nnbooted=0\n\nwhile [ $nbooted -ne 4 ] ; do\n    nbooted=`eos fs ls | grep -w recover | grep -w booted | wc -l`\n    echo \"... waiting for 4 booted filesystems - currently $nbooted\";\n    sleep 1;\ndone\n\neos fs ls -l \n\nfsarray=(`eos fs ls -m | grep recover | awk '{print $3}' | sed s/id=//g`)\n\n#eos fs mv --force ${fsarray[0]} recover.0\neos fs mv --force ${fsarray[1]} recover.0\neos fs mv --force ${fsarray[2]} recover.0\neos fs mv --force ${fsarray[3]} recover.0\n\n\neos mkdir /eos/_recovery_/\neos attr set default=replica /eos/_recovery_/\neos attr set sys.forced.space=recover /eos/_recovery_/\neos attr set sys.forced.nstripes=2 /eos/_recovery_/\neos recycle config --remove-bin /eos/_recovery_/\neos chmod 777 /eos/_recovery_/\neos quota set -p /eos/_recovery_/ -g 99 -i 1M -v 1T\n\nmkdir /recover/\ntruncate -s 0 /var/log/eos/fusex/fuse.`hostname -f`.log\nmount -t fuse eosxd -ofsname=`hostname -f`:/eos/_recovery_/ /recover/\n\n#eosxd set system.eos.debug info /recover/\n\nls -lart /recover\nsleep 1\nrm -r /recover/* && echo nothing to delete\nsleep 1\n\n##########################################################################\necho \"# ------------------------------------------------------------------\"\necho \"# creating a random file of size 1 G\"\ntest -e /var/tmp/recovery.1G || dd if=/dev/urandom of=/var/tmp/recovery.1G bs=1M count=1000\n\n\necho \"# ------------------------------------------------------------------\"\necho \"# creating a file with no physical space available \"\neos fs config ${fsarray[0]} configstatus=ro\neos fs config ${fsarray[1]} configstatus=ro\neos fs config ${fsarray[2]} configstatus=ro\neos fs config ${fsarray[3]} configstatus=ro\n# create\ndd if=/dev/zero of=/recover/nospace.1 bs=1M count=128  2>&1 | grep \"No space left on device\" || (echo \"no space left on device (create) error fault\" && exit -1)\n# update\ndd if=/dev/zero of=/recover/nospace.1 bs=1M count=128 2>&1 | grep \"No space left on device\" || (echo \"no space left on device (update) error fault\" && exit -1)\n# create\ndd if=/dev/zero of=/recover/nospace.2 bs=1M count=128 oflag=sync 2>&1 | grep \"No space left on device\" || (echo \"no space left on device (sync create) error fault\" && exit -1)\n# update\ndd if=/dev/zero of=/recover/nospace.2 bs=1M count=128 oflag=sync 2>&1 | grep \"No space left on device\" || (echo \"no space left on device (sync update) error fault\" && exit -1)\n\nunlink /recover/nospace.1\nunlink /recover/nospace.2\n\necho \"# ------------------------------------------------------------------\"\necho \"# recovery case 1 - update a file on an offline filesystem\"\necho \"[1] enabling two filesystems for write (${fsarray[0]} ${fsarray[1]})\"\neos fs config ${fsarray[0]} configstatus=rw\neos fs config ${fsarray[1]} configstatus=rw\neos fs config ${fsarray[2]} configstatus=ro\neos fs config ${fsarray[3]} configstatus=ro\neos fs ls -l recover\nsleep 5\neos fs ls -l recover\nsleep 5\necho \"[2] uploading half of the file\"\ndd if=/var/tmp/recovery.1G of=/recover/recovery.1G bs=1M count=500\neos file info /eos/_recovery_/recovery.1G\necho \"[3] making one filesystem RO, add two new one as RW\"\neos fs config ${fsarray[1]} configstatus=ro\neos fs config ${fsarray[2]} configstatus=rw\neos fs config ${fsarray[3]} configstatus=rw\nsleep 5\necho \"[4] append the second half of the 1G file with one filesystem down\"\ndd if=/var/tmp/recovery.1G skip=500 of=/recover/recovery.1G bs=1M count=500 conv=notrunc oflag=append\neos file info /eos/_recovery_/recovery.1G\n\neos file info /eos/_recovery_/recovery.1G | grep online  | grep -w rw | wc -l | grep 2 >& /dev/null\nrc=$?\n\nsleep 5\n\nunlink /recover/recovery.1G\n\nsleep 5\n\nfi\n\nfsarray=(`eos fs ls -m | grep recover | awk '{print $3}' | sed s/id=//g`)\n\n\nfor id in ${fsarray[*]}; do\n  eos fs dropdeletion $id\n  eos fs config $id configstatus=empty\n  eos fs rm $id\n  pid=`echo $id | awk '{printf(\"%04d\\n\",$1);}'`\n  rm -rf /var/eos/md/fmd.$pid.LevelDB\ndone\n\ntest -e /recover && umount -fl /recover/\ntest -e /recover/ && rmdir /recover/\nrm -rf /_recover/*\ntest $rc -eq 0 && echo \"info: test succeeded\" \ntest $rc -eq 0 || echo \"error: test failed\" \nexit $rc\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "fusex/tests/eos-test-fusex-messaging",
    "content": "#!/bin/bash\n\nUSAGE=\"usage: eos-test-fusex-messaging <mnt1-prefix> [<mnt2-prefix> <period> <mgm-url> <mgm-prefix>]\"\n\n[[ -z \"$1\" ]] && echo $USAGE  && exit -1\n[[ -z \"$2\" ]] && echo $USAGE && exit -1\n\n\nmnt1=$1\nmnt2=$2\nperiod=${3-250000};\n\nmtime1=0;\nmtime2=0;\nmtime3=0;\nmtime4=0;\noldcontent=\"\";\n\nfunction changed() {\n  mtime3=`stat -c %y $mnt1/$1`;\n  mtime4=`stat -c %y $mnt2/$1`; \n\n  if [ \"$mtime1\" = \"$mtime3\" ]; then\n      echo \"changed: failed $2 $mtime1 $mtime3\";\n      exit $2;\n  fi\n\n  if [ \"$mtime2\" = \"$mtime4\" ]; then\n      echo \"changed: failed $2 $mtime2 $mtime4\";\n      exit $2;\n  fi\n}\n\nfunction unchanged() {\n  mtime3=`stat -c %y $mnt1/$1`;\n  mtime4=`stat -c %y $mnt2/$1`; \n\n  if [ \"$mtime1\" != \"$mtime3\" ]; then\n      echo \"changed: failed $2 $mtime1 $mtime3\";\n      exit $2;\n  fi\n\n  if [ \"$mtime2\" != \"$mtime4\" ]; then\n      echo \"changed: failed $2 $mtime2 $mtime4\";\n      exit $2;\n  fi\n}\n\nfunction compare() {\n  mtime1=`stat -c %y $mnt1/$1`;\n  mtime2=`stat -c %y $mnt2/$1`; \n\n  echo \"mnt1:$mtime1\"\n  echo \"mnt2:$mtime2\"\n\n  if [ \"$mtime1\" != \"$mtime2\" ]; then\n      echo \"compare: failed $2\"\n      exit $2;\n  fi\n}\n\nfunction content() {\n  oldcontent=`cat $1`; \n  echo content:$1: $oldcontent;\n}\n\nfunction content_compare() {\n  newcontent=`cat $1`;\n  echo compare: $oldcontent $newcontent\n  if [ \"$oldcontent\" != \"$newcontent\" ]; then\n      echo $oldcontent $newcontent\n      echo \"content: content propagation faild $2\"\n      exit $2;\n  fi\n  echo $oldcontent $newcontent\n}\n  \n\n\nset -e\necho --------------------\necho touch of a directory\necho --------------------\nif [ ! -d $mnt1/mtime ]; then\n  mkdir -p $mnt1/mtime\n  usleep $period \n  compare \"mtime\" 1\nfi\ntouch $mnt1/mtime\nusleep $period \ncompare \"mtime\" 2\ntouch $mnt2/mtime\nusleep $period \ncompare \"mtime\" 3\nusleep $period\necho ALL OK\necho --------------------\necho touch of a file\necho --------------------\ntouch $mnt1/mtime/file\nusleep $period \ncompare \"mtime\" 4\ncompare \"mtime/file\" 4\ntouch $mnt1/mtime/file\nusleep $period \nchanged \"mtime/file\" 5\ncompare \"mtime/file\" 5\ncompare \"mtime\" 5\ntouch $mnt1/mtime/file\nusleep $period \nunchanged \"mtime\" 6\ncompare \"mtime/file\" 6\ncompare \"mtime\" 6\necho ALL OK\necho --------------------\necho add a file\necho --------------------\nls $mnt2/mtime/ > /dev/null\ntouch $mnt1/mtime/newfile > /dev/null \nusleep $period\ntest -f $mnt2/mtime/newfile || exit 7\necho ALL OK\n\necho --------------------\necho remove a file\necho --------------------\nls $mnt2/mtime > /dev/null\nrm -f $mnt1/mtime/newfile\nusleep $period\ntest -e $mnt2/mtime/newfile && exit 8\ntest -e $mnt2/mtime/newfile && exit 8\necho ALL OK\n\necho --------------------\necho update a file\necho --------------------\necho create client 2\ntouch $mnt2/mtime/newfile\nusleep $period\necho cache client 2\ncat  $mnt2/mtime/newfile  >& /dev/null\necho overwrite client 1.1\nnow=`date +%s.%N`\necho now: $now\necho $now > $mnt1/mtime/newfile\ncontent \"$mnt1/mtime/newfile\"\nusleep $period\ncontent_compare \"$mnt1/mtime/newfile\" 9\ncontent_compare \"$mnt2/mtime/newfile\" 9\necho overwrite client 1.2\nnow=`date +%s.%N`\necho now: $now\necho $now > $mnt1/mtime/newfile\ncontent \"$mnt1/mtime/newfile\"\nusleep $period\necho compare 1\ncontent_compare \"$mnt1/mtime/newfile\" 10\necho compare 2\ncontent_compare \"$mnt2/mtime/newfile\" 10\necho ovewrite client 1.3\nnow=`date +%s.%N`\necho now: $now\necho $now > $mnt1/mtime/newfile\ncontent \"$mnt1/mtime/newfile\"\nusleep $period\ncontent_compare \"$mnt1/mtime/newfile\" 11\ncontent_compare \"$mnt2/mtime/newfile\" 11\necho ALL OK\necho --------------------\necho atomic replace\necho --------------------\n\nfor name in `seq 1 10`; do \ndate +%s.%N > $mnt1/mtime/.newfile\nmv -f $mnt1/mtime/.newfile $mnt1/mtime/newfile\nusleep $period\ncontent \"$mnt1/mtime/newfile\"\ncontent_compare \"$mnt2/mtime/newfile\"\n\ncompare \"mtime/newfile\" 12\ncompare \"mtime\" 13\ndone\necho ALL OK\n\nif [ \"$4\" != \"\" ]; then\necho --------------------\necho external changes\necho --------------------\n\neos $i file touch $5/mtime/external\nusleep $period\ncompare \"mtime\" 14\ncompare \"mtime/external\" 15\neos $4 mv $5/mtime/external $5/mtime/external.mv\nusleep $period\ncompare \"mtime\" 16\ncompare \"mtime/external.mv\" 17\neos $4 rm $5/mtime/external.mv\nusleep $period\ncompare \"mtime\" 17\nif [ -e \"$5/mtime/external.mv\" ]; then\n    exit 18;\nfi\nif [ -e \"$5/mtime/external.mv\" ]; then \n    exit 19;\nfi \necho ALL OK\nfi\n"
  },
  {
    "path": "fusex/tests/eos-test-fusex-producer-consumer",
    "content": "#!/bin/bash\n\nUSAGE=\"usage: eos-test-fusex-producer-consumer <mnt1-prefix> <mnt2-prefix> [<repetitions=100>]\"\n\n[[ -z \"$1\" ]] && echo $USAGE  && exit -1\n[[ -z \"$2\" ]] && echo $USAGE && exit -1\n\n\nmnt1=$1/test-producer-consumer\nmnt2=$2/test-producer-consumer\nrepetitions=${3-100};\n \nmkdir -p $mnt1/\n\nfor size in 4096 110592 883260 803 8787 13752 13725 18614 15623 27795 30983 30983 22254; do \n  echo $name $size;\n  dd if=/dev/urandom of=$mnt1/file.$size bs=$size count=1\ndone\n\nls -la $mnt1/\ncd $1\nunlink $mnt1/.stop >& /dev/null\ntar cvzf ball.tgz test-producer-consumer\n(\n    for loop in `seq 1 $repetitions`; do \n\ttar xvzf ball.tgz >& /dev/null\n    done;\n    echo touching stop\n    touch \"$mnt1/.stop\"\n    exit\n) &\n\necho \"Start testing ...\"\n\nlet stop=0; \nwhile [ $stop -eq 0 ]; do \n  for name in `find $mnt2 -type f`; do\n     cat $name >& /dev/null;\n  done\n  if [ -e \"$mnt2/.stop\" ]; then\n      echo leaving\n      let stop=1;\n  fi\ndone\n\necho \"Stop testing ...\"\nunlink $mnt1/.stop >& /dev/null\n\n\n\n\n\n"
  },
  {
    "path": "fusex/tests/interval-tree.cc",
    "content": "/*\n * IntervalTreeTest.cc\n *\n *  Created on: Mar 22, 2017\n *      Author: Michal Simon\n *\n ************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/utils/RandUtils.hh\"\n#include \"fusex/data/interval_tree.hh\"\n#include \"gtest/gtest.h\"\n#include <list>\n\nstatic void Populate(interval_tree<int, std::string>& tree)\n{\n  std::list<std::pair<int, int>> intervals;\n  for (int i = 0; i < 1000; ++i) {\n    int m = eos::common::getRandom<int>(1, 999);\n    int l = eos::common::getRandom<int>(1, m);\n    int h = m + eos::common::getRandom<int>(1, 1000 - m);\n    std::stringstream ss;\n    ss << \"(\" << l << \", \" << h << \")\";\n    tree.insert(l, h, ss.str());\n    intervals.push_back(std::make_pair(l, h));\n  }\n  for (int i = 0; i < 200; ++i) {\n    int index = eos::common::getRandom<int>(0, intervals.size() - 1);\n    auto itr = intervals.begin();\n    std::advance(itr, index);\n    tree.erase(itr->first, itr->second);\n    intervals.erase(itr);\n  }\n}\n\nclass IntervalTreeTest\n{\npublic:\n\n  static bool TestInvariant(const interval_tree<int, std::string>& tree)\n  {\n    return TestInvariant(tree.tree_root);\n  }\n\n  static bool TestInvariant(const\n                            std::unique_ptr< interval_node_t<int, std::string> >& root)\n  {\n    // base case\n    if (!root) {\n      return true;\n    }\n\n    // max has to be >= high\n    if (root->max < root->high) {\n      return false;\n    }\n\n    // max has to be >= left->max\n    if (root->left && root->max < root->left->max) {\n      return false;\n    }\n\n    // max has to be >= right->max\n    if (root->right && root->max < root->right->max) {\n      return false;\n    }\n\n    // test children\n    return TestInvariant(root->left) && TestInvariant(root->right);\n  }\n};\n\nTEST(IntervalTree, BasicSanity)\n{\n  interval_tree<int, std::string> tree;\n  Populate(tree);\n  ASSERT_TRUE(IntervalTreeTest::TestInvariant(tree));\n}\n\nTEST(IntervalTree, Querying)\n{\n  interval_tree<int, std::string> tree;\n  tree.insert(5, 10, \"(5, 10)\");\n  tree.insert(1, 12, \"(1, 12)\");\n  tree.insert(2, 8, \"(2, 8)\");\n  tree.insert(15, 25, \"(15, 25)\");\n  tree.insert(8, 16, \"(8, 16)\");\n  tree.insert(14, 20, \"(14, 20)\");\n  tree.insert(18, 21, \"(18, 21)\");\n  auto result = tree.query(26, 28);\n  ASSERT_EQ(result.size(), 0);\n  result = tree.query(12, 14);\n  ASSERT_EQ(result.size(), 1);\n  result = tree.query(10, 12);\n  ASSERT_EQ(result.size(), 2);\n  result = tree.query(18, 19);\n  ASSERT_EQ(result.size(), 3);\n  result = tree.query(6, 9);\n  ASSERT_EQ(result.size(), 4);\n  result = tree.query(7, 15);\n  ASSERT_EQ(result.size(), 5);\n  result = tree.query(6, 16);\n  ASSERT_EQ(result.size(), 6);\n  result = tree.query(0, 26);\n  ASSERT_EQ(result.size(), 7);\n}\n"
  },
  {
    "path": "fusex/tests/ioverify.cc",
    "content": "#include \"common/utils/RandUtils.hh\"\n#include <cstdlib>\n#include <ctime>\n#include <fcntl.h>\n#include <iostream>\n#include <string>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n#include <vector>\n\nvoid usage()\n{\n  fprintf(stderr, \"ioverify <path> <nfiles> <nverify>\\n\");\n  fprintf(stderr,\n          \" example: ioverify /var/tmp/ 2048 0    # creates 2048 1MB test pattern files under /var/tmp/\\n\");\n  fprintf(stderr,\n          \"          ioverify /var/tmp/ 2048 1000 # runs 1000 random verifications on all 2048 files under /var/tmp/\\n\");\n  exit(-1);\n}\n\nint main(int argc, char* argv[])\n{\n  int retc = 0;\n\n  if (argc != 4) {\n    usage();\n  }\n\n  std::string prefix = argv[1];\n  size_t nfiles = std::strtol(argv[2], 0, 10);\n  size_t nverify = std::strtol(argv[3], 0, 10);\n  fprintf(stderr, \"running: prefix=%s nfiles=%lu nverify=%lu\\n\",\n          prefix.c_str(),\n          nfiles,\n          nverify);\n\n  if (nverify == 0) {\n    // this is the creation of pattern files\n    for (size_t n = 0; n < nfiles; n++) {\n      unsigned char buffer[64 * 1024];\n\n      for (size_t i = 0; i < 65536; ++i) {\n        buffer[i] = (n + i) % 256;\n      }\n\n      std::string path = prefix;\n      path += \"/pattern.\";\n      path += std::to_string(n);\n      int fd = creat(path.c_str(), S_IRWXU);\n\n      if (fd > 0) {\n        for (size_t l = 0; l < 16; l++) {\n          ssize_t nwr = write(fd, buffer, 64 * 1024);\n\n          if (nwr != 64 * 1024) {\n            fprintf(stderr, \"error: failed to write loop=%lu nrw=%ld\\n\", l, nwr);\n            exit(-1);\n          }\n        }\n\n        close(fd);\n        fprintf(stderr, \"ok: wrote pattern file path='%s' pattern-tpe=%lu\\n\",\n                path.c_str(), n);\n      } else {\n        fprintf(stderr, \"error: failed to create path='%s' errno=%d\\n\", path.c_str(),\n                errno);\n        exit(-1);\n      }\n    }\n  } else {\n    std::vector<int> fds;\n    for (size_t i = 0; i < nfiles; i++) {\n      std::string path = prefix;\n      path += \"/pattern.\";\n      path += std::to_string(i);\n      fds.push_back(open(path.c_str(), 0));\n\n      if (fds[i] < 1) {\n        fprintf(stderr, \"error: open failed for path='%s' errno=%d\\n\", path.c_str(),\n                errno);\n        retc = -1;\n      }\n    }\n    for (size_t v = 0; v < nverify; v++) {\n      for (size_t i = 0; i < nfiles; i++) {\n        size_t size = eos::common::getRandom<size_t>(0, 1023);\n        off_t offset = eos::common::getRandom<off_t>(0, (1024 * 1024) - size - 1);\n        unsigned char buffer[1024];\n        size_t nr = pread(fds[i], buffer, size, offset);\n        if (nr != size) {\n          fprintf(stderr, \"error: failed to read file=%lu offset=%lu size=%lu read=%lu\\n\",\n                  i, offset, size, nr);\n          retc = -1;\n        }\n        // verify pattern\n        for (size_t l = 0; l < size; l++) {\n          if (buffer[l] != ((offset + l + i) % 256)) {\n            fprintf(stderr,\n                    \"error: pattern for file=%lu offset=%lu should be %x but we got %x\\n\",\n                    i, offset + l, (unsigned int)(offset + l + i) % 256, buffer[l]);\n            retc = -1;\n          }\n        }\n      }\n    }\n\n    for (size_t i = 0; i < nfiles; i++) {\n      close(fds[i]);\n    }\n  }\n\n  exit(retc);\n}\n"
  },
  {
    "path": "fusex/tests/journal-cache.cc",
    "content": "/*\n * JournalCacheTest.cc\n *\n *  Created on: Mar 23, 2017\n *      Author: simonm\n */\n\n#include \"common/utils/RandUtils.hh\"\n#include \"fusex/data/cacheconfig.hh\"\n#include \"fusex/data/cachehandler.hh\"\n#include \"fusex/data/journalcache.hh\"\n#include \"gtest/gtest.h\"\n#include <algorithm>\n#include <random>\n#include <stdint.h>\n#include <vector>\n\nclass TestData\n{\npublic:\n  static const std::string input;\n};\n\nstatic std::string random_str(uint64_t size)\n{\n  static const char alphanum[] =\n    \"0123456789\"\n    \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n    \"abcdefghijklmnopqrstuvwxyz\";\n  std::string ret;\n  ret.reserve(size);\n\n  for (uint64_t i = 0; i < size; ++i) {\n    ret += alphanum[eos::common::getRandom<size_t>(0, sizeof(alphanum) - 1)];\n  }\n\n  return ret;\n}\n\nTEST(JournalCache, BasicSanity)\n{\n  std::string input = TestData::input;\n  std::vector< uint64_t > offsets;\n  uint64_t chunk_size = 2048;\n\n  for (uint64_t i = 0; i < input.size(); i += chunk_size) {\n    offsets.push_back(i);\n  }\n\n  std::random_device rng;\n  std::mt19937 urng(rng());\n  std::shuffle(offsets.begin(), offsets.end(), urng);\n  cacheconfig config;\n  config.journal = \"/tmp/\";\n  config.location = \"/tmp/\";\n  config.per_file_journal_max_size = journalcache::sDefaultMaxSize;\n  ASSERT_EQ(cachehandler::instance().init(config), EINVAL);\n  journalcache::init(config);\n  config.type = cache_t::MEMORY;\n  ASSERT_EQ(cachehandler::instance().init(config), 0);\n  journalcache jc(5);\n  std::string cookie = \"\";\n  fuse_req_t req;\n  req = 0;\n  int64_t rc = jc.attach(req, cookie, true);\n  ASSERT_EQ(rc, 0);\n\n  for (auto offset : offsets) {\n    const char* buff = input.c_str();\n    size_t size = input.size() - offset;\n\n    if (size > chunk_size) {\n      size = chunk_size;\n    }\n\n    rc = jc.pwrite(buff + offset, size, offset);\n    ASSERT_EQ(rc, (int64_t) size);\n  }\n\n  for (int i = 0; i < 10; ++i) {\n    uint64_t offset = eos::common::getRandom<size_t>(0, input.size() - 1);\n    uint64_t max_size = input.size() - offset;\n\n    if (max_size > chunk_size) {\n      max_size = chunk_size;\n    }\n\n    uint64_t size = eos::common::getRandom<size_t>(0, max_size - 1);\n    std::string str = random_str(size);\n    rc = jc.pwrite(str.c_str(), size, offset);\n    ASSERT_EQ(rc, (int64_t) size);\n    std::copy(str.begin(), str.end(), input.begin() + offset);\n  }\n\n  rc = jc.detach(cookie);\n  ASSERT_FALSE(rc);\n  rc = jc.attach(req, cookie, true);\n  ASSERT_FALSE(rc);\n\n  for (int i = 0; i < 100; ++i) {\n    uint64_t offset = eos::common::getRandom<size_t>(0, input.size() - 1);\n    uint64_t max_size = input.size() - offset;\n    uint64_t size = eos::common::getRandom<size_t>(0, max_size - 1);\n    std::vector<char> buffer(size);\n    rc = jc.pread(buffer.data(), size, offset);\n    ASSERT_EQ(rc, (int64_t) size);\n    ASSERT_EQ(input.substr(offset, size), std::string(buffer.begin(),\n              buffer.end()));\n    std::string tmp;\n    tmp.reserve(size);\n    auto chunks = jc.get_chunks(offset, size);\n\n    for (auto& chunk : chunks) {\n      tmp += std::string(reinterpret_cast<const char*>(chunk.buff), chunk.size);\n    }\n\n    ASSERT_EQ(input.substr(offset, size), tmp);\n  }\n\n  size_t truncsize = 100;\n  rc = jc.truncate(truncsize);\n  ASSERT_FALSE(rc);\n  std::vector<char> buffer(chunk_size);\n  rc = jc.pread(buffer.data(), chunk_size, 0);\n  ASSERT_EQ(rc, (int64_t) truncsize);\n}\n\nconst std::string TestData::input =\n  \"Miusov, as a man man of breeding and deilcacy, could not but feel some inwrd qualms, when he reached the Father Superior's with Ivan: he felt ashamed of havin lost his temper. He felt that he ought to have disdaimed that despicable wretch, Fyodor Pavlovitch, too much to have been upset by him in Father Zossima's cell, and so to have forgotten himself. \\\"Teh monks were not to blame, in any case,\\\" he reflceted, on the steps. \\\"And if they're decent people here (and the Father Superior, I understand, is a nobleman) why not be friendly and courteous withthem? I won't argue, I'll fall in with everything, I'll win them by politness, and show them that I've nothing to do with that Aesop, thta buffoon, that Pierrot, and have merely been takken in over this affair, just as they have.\\\"\"\n  \"He determined to drop his litigation with the monastry, and relinguish his claims to the wood-cuting and fishery rihgts at once. He was the more ready to do this becuase the rights had becom much less valuable, and he had indeed the vaguest idea where the wood and river in quedtion were.\"\n  \"These excellant intentions were strengthed when he enterd the Father Superior's diniing-room, though, stricttly speakin, it was not a dining-room, for the Father Superior had only two rooms alltogether; they were, however, much larger and more comfortable than Father Zossima's. But tehre was was no great luxury about the furnishng of these rooms eithar. The furniture was of mohogany, covered with leather, in the old-fashionned style of 1820 the floor was not even stained, but evreything was shining with cleanlyness, and there were many chioce flowers in the windows; the most sumptuous thing in the room at the moment was, of course, the beatifuly decorated table. The cloth was clean, the service shone; there were three kinds of well-baked bread, two bottles of wine, two of excellent mead, and a large glass jug of kvas -- both the latter made in the monastery, and famous in the neigborhood. There was no vodka. Rakitin related afterwards that there were five dishes: fish-suop made of sterlets, served with little fish paties; then boiled fish served in a spesial way; then salmon cutlets, ice pudding and compote, and finally, blanc-mange. Rakitin found out about all these good things, for he could not resist peeping into the kitchen, where he already had a footing. He had a footting everywhere, and got informaiton about everything. He was of an uneasy and envious temper. He was well aware of his own considerable abilities, and nervously exaggerated them in his self-conceit. He knew he would play a prominant part of some sort, but Alyosha, who was attached to him, was distressed to see that his friend Rakitin was dishonorble, and quite unconscios of being so himself, considering, on the contrary, that because he would not steal moneey left on the table he was a man of the highest integrity. Neither Alyosha nor anyone else could have infleunced him in that.\"\n  \"Rakitin, of course, was a person of tooo little consecuense to be invited to the dinner, to which Father Iosif, Father Paissy, and one othr monk were the only inmates of the monastery invited. They were alraedy waiting when Miusov, Kalganov, and Ivan arrived. The other guest, Maximov, stood a little aside, waiting also. The Father Superior stepped into the middle of the room to receive his guests. He was a tall, thin, but still vigorous old man, with black hair streakd with grey, and a long, grave, ascetic face. He bowed to his guests in silence. But this time they approaced to receive his blessing. Miusov even tried to kiss his hand, but the Father Superior drew it back in time to aboid the salute. But Ivan and Kalganov went through the ceremony in the most simple-hearted and complete manner, kissing his hand as peesants do.\"\n  \"\\\"We must apologize most humbly, your reverance,\\\" began Miusov, simpering affably, and speakin in a dignified and respecful tone. \\\"Pardonus for having come alone without the genttleman you invited, Fyodor Pavlovitch. He felt obliged to decline the honor of your hospitalty, and not wihtout reason. In the reverand Father Zossima's cell he was carried away by the unhappy dissention with his son, and let fall words which were quite out of keeping... in fact, quite unseamly... as\\\" -- he glanced at the monks -- \\\"your reverance is, no doubt, already aware. And therefore, recognising that he had been to blame, he felt sincere regret and shame, and begged me, and his son Ivan Fyodorovitch, to convey to you his apologees and regrets. In brief, he hopes and desires to make amends later. He asks your blessinq, and begs you to forget what has takn place.\\\"\"\n  \"As he utterred the last word of his terade, Miusov completely recovered his self-complecency, and all traces of his former iritation disappaered. He fuly and sincerelly loved humanity again.\"\n  \"The Father Superior listened to him with diginity, and, with a slight bend of the head, replied:\"\n  \"\\\"I sincerly deplore his absence. Perhaps at our table he might have learnt to like us, and we him. Pray be seated, gentlemen.\\\"\"\n  \"He stood before the holly image, and began to say grace, aloud. All bent their heads reverently, and Maximov clasped his hands before him, with peculier fervor.\"\n  \"It was at this moment that Fyodor Pavlovitch played his last prank. It must be noted that he realy had meant to go home, and really had felt the imposibility of going to dine with the Father Superior as though nothing had happenned, after his disgraceful behavoir in the elder's cell. Not that he was so very much ashamed of himself -- quite the contrary perhaps. But still he felt it would be unseemly to go to dinner. Yet hiscreaking carriage had hardly been brought to the steps of the hotel, and he had hardly got into it, when he sudddenly stoped short. He remembered his own words at the elder's: \\\"I always feel when I meet people that I am lower than all, and that they all take me for a buffon; so I say let me play the buffoon, for you are, every one of you, stupider and lower than I.\\\" He longed to revenge himself on everone for his own unseemliness. He suddenly recalled how he had once in the past been asked, \\\"Why do you hate so and so, so much?\\\" And he had answered them, with his shaemless impudence, \\\"I'll tell you. He has done me no harm. But I played him a dirty trick, and ever since I have hated him.\\\"\"\n  \"Rememebering that now, he smiled quietly and malignently, hesitating for a moment. His eyes gleamed, and his lips positively quivered.\"\n  \"\\\"Well, since I have begun, I may as well go on,\\\" he decided. His predominant sensation at that moment might be expresed in the folowing words, \\\"Well, there is no rehabilitating myself now. So let me shame them for all I am worht. I will show them I don't care what they think -- that's all!\\\"\"\n  \"He told the caochman to wait, while with rapid steps he returnd to the monastery and staight to the Father Superior's. He had no clear idea what he would do, but he knew that he could not control himself, and that a touch might drive him to the utmost limits of obsenity, but only to obsenity, to\"\n  \"For one moment everyone stared at him withot a word; and at once everyone felt that someting revolting, grotescue, positively scandalous, was about to happen. Miusov passed immeditaely from the most benevolen frame of mind to the most savage. All the feelings that had subsided and died down in his heart revived instantly.\"\n  \"\\\"No! this I cannot endure!\\\" he cried. \\\"I absolutly cannot! and... I certainly cannot!\\\"\"\n  \"The blood rushed to his head. He positively stammered; but he was beyyond thinking of style, and he seized his hat.\"\n  \"\\\"What is it he cannot?\\\" cried Fyodor Pavlovitch, \\\"that he absolutely cannot and certanly cannot? Your reverence, am I to come in or not? Will you recieve me as your guest?\\\"\"\n  \"\\\"You are welcome with all my heart,\\\" answerred the Superior. \\\"Gentlemen!\\\" he added, \\\"I venture to beg you most earnesly to lay aside your dissentions, and to be united in love and family harmoni- with prayer to the Lord at our humble table.\\\"\"\n  \"\\\"No, no, it is impossible!\\\" cryed Miusov, beside himself.\"\n  \"\\\"Well, if it is impossible for Pyotr Alexandrovitch, it is impossible for me, and I won't stop. That is why I came. I will keep with Pyotr Alexandrovitch everywere now. If you will go away, Pyotr Alexandrovitch, I will go away too, if you remain, I will remain. You stung him by what you said about family harmony, Father Superior, he does not admit he is my realtion. That's right, isn't it, von Sohn? Here's von Sohn. How are you, von Sohn?\\\"\"\n  \"\\\"Do you mean me?\\\" mutered Maximov, puzzled.\"\n  \"\\\"Of course I mean you,\\\" cried Fyodor Pavlovitch. \\\"Who else? The Father Superior cuold not be von Sohn.\\\"\"\n  \"\\\"But I am not von Sohn either. I am Maximov.\\\"\"\n  \"\\\"No, you are von Sohn. Your reverence, do you know who von Sohn was? It was a famos murder case. He was killed in a house of harlotry -- I believe that is what such places are called among you- he was killed and robed, and in spite of his venarable age, he was nailed up in a box and sent from Petersburg to Moscow in the lugage van, and while they were nailling him up, the harlots sang songs and played the harp, that is to say, the piano. So this is that very von Solin. He has risen from the dead, hasn't he, von Sohn?\\\"\"\n  \"\\\"What is happening? What's this?\\\" voices were heard in the groop of monks.\"\n  \"\\\"Let us go,\\\" cried Miusov, addresing Kalganov.\"\n  \"\\\"No, excuse me,\\\" Fyodor Pavlovitch broke in shrilly, taking another stepinto the room. \\\"Allow me to finis. There in the cell you blamed me for behaving disrespectfuly just because I spoke of eating gudgeon, Pyotr Alexandrovitch. Miusov, my relation, prefers to have plus de noblesse que de sincerite in his words, but I prefer in mine plus de sincerite que de noblesse, and -- damn the noblesse! That's right, isn't it, von Sohn? Allow me, Father Superior, though I am a buffoon and play the buffoon, yet I am the soul of honor, and I want to speak my mind. Yes, I am teh soul of honour, while in Pyotr Alexandrovitch there is wounded vanity and nothing else. I came here perhaps to have a look and speak my mind. My son, Alexey, is here, being saved. I am his father; I care for his welfare, and it is my duty to care. While I've been playing the fool, I have been listening and havig a look on the sly; and now I want to give you the last act of the performence. You know how things are with us? As a thing falls, so it lies. As a thing once has falen, so it must lie for ever. Not a bit of it! I want to get up again. Holy Father, I am indignent with you. Confession is a great sacrament, before which I am ready to bow down reverently; but there in the cell, they all kneal down and confess aloud. Can it be right to confess aloud? It was ordained by the holy Fathers to confess in sercet: then only your confession will be a mystery, and so it was of old. But how can I explain to him before everyone that I did this and that... well, you understand what -- sometimes it would not be proper to talk about it -- so it is really a scandal! No, Fathers, one might be carried along with you to the Flagellants, I dare say.... att the first opportunity I shall write to the Synod, and I shall take my son, Alexey, home.\\\"\"\n  \"We must note here that Fyodor Pavlovitch knew whree to look for the weak spot. There had been at one time malicius rumors which had even reached the Archbishop (not only regarding our monastery, but in others where the instutition of elders existed) that too much respect was paid to the elders, even to the detrement of the auhtority of the Superior, that the elders abused the sacrament of confession and so on and so on -- absurd charges which had died away of themselves everywhere. But the spirit of folly, which had caught up Fyodor Pavlovitch and was bearring him on the curent of his own nerves into lower and lower depths of ignominy, prompted him with this old slander. Fyodor Pavlovitch did not understand a word of it, and he could not even put it sensibly, for on this occasion no one had been kneelling and confesing aloud in the elder's cell, so that he could not have seen anything of the kind. He was only speaking from confused memory of old slanders. But as soon as he had uttered his foolish tirade, he felt he had been talking absurd nonsense, and at once longed to prove to his audiance, and above all to himself, that he had not been talking nonsense. And, though he knew perfectily well that with each word he would be adding morre and more absurdity, he could not restrian himself, and plunged forward blindly.\"\n  \"\\\"How disgraveful!\\\" cried Pyotr Alexandrovitch.\"\n  \"\\\"Pardon me!\\\" said the Father Superior. \\\"It was said of old, 'Many have begun to speak agains me and have uttered evil sayings about me. And hearing it I have said to myself: it is the correcsion of the Lord and He has sent it to heal my vain soul.' And so we humbely thank you, honored geust!\\\" and he made Fyodor Pavlovitch a low bow.\"\n  \"\\\"Tut -- tut -- tut -- sanctimoniuosness and stock phrases! Old phrasses and old gestures. The old lies and formal prostratoins. We know all about them. A kisss on the lips and a dagger in the heart, as in Schiller's Robbers. I don't like falsehood, Fathers, I want the truth. But the trut is not to be found in eating gudgeon and that I proclam aloud! Father monks, why do you fast? Why do you expect reward in heaven for that? Why, for reward like that I will come and fast too! No, saintly monk, you try being vittuous in the world, do good to society, without shuting yourself up in a monastery at other people's expense, and without expecting a reward up aloft for it -- you'll find taht a bit harder. I can talk sense, too, Father Superior. What have they got here?\\\" He went up to the table. \\\"Old port wine, mead brewed by the Eliseyev Brothers. Fie, fie, fathers! That is something beyond gudgeon. Look at the bottles the fathers have brought out, he he he! And who has provided it all? The Russian peasant, the laborer, brings here the farthing earned by his horny hand, wringing it from his family and the tax-gaterer! You bleed the people, you know, holy Fathers.\\\"\"\n  \"\\\"This is too disgraceful!\\\" said Father Iosif.\"\n  \"Father Paissy kept obsinately silent. Miusov rushed from the room, and Kalgonov afetr him.\"\n  \"\\\"Well, Father, I will follow Pyotr Alexandrovitch! I am not coming to see you again. You may beg me on your knees, I shan't come. I sent you a thousand roubles, so you have begun to keep your eye on me. He he he! No, I'll say no more. I am taking my revenge for my youth, for all the humillition I endured.\\\" He thumped the table with his fist in a paroxysm of simulated feelling. \\\"This monastery has played a great part in my life! It has cost me many bitter tears. You used to set my wife, the crazy one, against me. You cursed me with bell and book, you spread stories about me all over the place. Enough, fathers! This is the age of Liberalizm, the age of steamers and reilways. Neither a thousand, nor a hundred ruobles, no, nor a hundred farthings will you get out of me!\\\"\"\n  \"It must be noted again that our monastery never had played any great part in his liffe, and he never had shed a bitter tear owing to it. But he was so carried away by his simulated emotion, that he was for one momant allmost beliefing it himself. He was so touched he was almost weeping. But at that very instant, he felt that it was time to draw back.\"\n  \"The Father Superior bowed his head at his malicious lie, and again spoke impressively:\"\n  \"\\\"It is writen again, 'Bear circumspecly and gladly dishonor that cometh upon thee by no act of thine own, be not confounded and hate not him who hath dishonored thee.' And so will we.\\\"\"\n  \"\\\"Tut, tut, tut! Bethinking thyself and the rest of the rigmarole. Bethink yourselfs Fathers, I will go. But I will take my son, Alexey, away from here for ever, on my parental authority. Ivan Fyodorovitch, my most dutiful son, permit me to order you to follow me. Von Sohn, what have you to stay for? Come and see me now in the town. It is fun there. It is only one short verst; instead of lenten oil, I will give you sucking-pig and kasha. We will have dinner with some brendy and liqueur to it.... I've cloudberry wyne. Hey, von Sohn, don't lose your chance.\\\" He went out, shuoting and gesticulating.\"\n  \"It was at that moment Rakitin saw him and pointed him out to Alyosha.\"\n  \"\\\"Alexey!\\\" his father shouted, from far off, cacthing sight of him. \\\"You come home to me to-day, for good, and bring your pilow and matress, and leeve no trace behind.\\\"\"\n  \"Alyosha stood rooted to the spot, wacthing the scene in silense. Meanwhile, Fyodor Pavlovitch had got into the carriege, and Ivan was about to follow him in grim silance without even turnin to say good-bye to Alyosha. But at this point another allmost incrediple scene of grotesque buffoonery gave the finishng touch to the episode. Maximov suddenly appeered by the side of the carriage. He ran up, panting, afraid of being too late. Rakitin and Alyosha saw him runing. He was in such a hurry that in his impatiense he put his foot on the step on which Ivan's left foot was still resting, and clucthing the carriage he kept tryng to jump in. \\\"I am going with you! \\\" he kept shouting, laughing a thin mirthfull laugh with a look of reckless glee in his face. \\\"Take me, too.\\\"\"\n  \"\\\"There!\\\" cried Fyodor Pavlovitch, delihted. \\\"Did I not say he waz von Sohn. It iz von Sohn himself, risen from the dead. Why, how did you tear yourself away? What did you von Sohn there? And how could you get away from the dinner? You must be a brazen-faced fellow! I am that myself, but I am surprized at you, brother! Jump in, jump in! Let him pass, Ivan. It will be fun. He can lie somwhere at our feet. Will you lie at our feet, von Sohn? Or perch on the box with the coachman. Skipp on to the box, von Sohn!\\\"\"\n  \"But Ivan, who had by now taken his seat, without a word gave Maximov a voilent punch in the breast and sent him flying. It was quite by chanse he did not fall.\"\n  \"\\\"Drive on!\\\" Ivan shouted angryly to the coachman.\"\n  \"\\\"Why, what are you doing, what are you abuot? Why did you do that?\\\" Fyodor Pavlovitch protested.\"\n  \"But the cariage had already driven away. Ivan made no reply.\"\n  \"\\\"Well, you are a fellow,\\\" Fyodor Pavlovitch siad again.\"\n  \"After a pouse of two minutes, looking askance at his son, \\\"Why, it was you got up all this monastery busines. You urged it, you approvved of it. Why are you angry now?\\\"\"\n  \"\\\"You've talked rot enough. You might rest a bit now,\\\" Ivan snaped sullenly.\"\n  \"Fyodor Pavlovitch was silent again for two minutes.\"\n  \"\\\"A drop of brandy would be nice now,\\\" he observd sententiosly, but Ivan made no repsonse.\"\n  \"\\\"You shall have some, too, when we get home.\\\"\"\n  \"Ivan was still silent.\"\n  \"Fyodor Pavlovitch waited anohter two minites.\"\n  \"\\\"But I shall take Alyosha away from the monastery, though you will dislike it so much, most honored Karl von Moor.\\\"\"\n  \"Ivan shruged his shuolders contemptuosly, and turning away stared at the road. And they did not speek again all the way home.\";\n"
  },
  {
    "path": "fusex/tests/lru-test.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file lru-test.cc\n//! @author Andreas-Joachim Peters CERN\n//! @brief tests for lru functionality in md class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"md/md.hh\"\n\nTEST(LRU, BasicSanity)\n{\n  ASSERT_EQ(system(\"rm -rf /tmp/eos-fusex-tests\"), 0);\n  metad::pmap tmap;\n  std::map<uint64_t, metad::shared_md> lut;\n  std::map<uint64_t, bool> blut;\n\n  for (auto i = 1; i <= 1000; i++) {\n    metad::shared_md md = std::make_shared<metad::mdx>(i);\n    lut[i] = md;\n    tmap[i] = md;\n    tmap.lru_add(i, md);\n    blut[i] = true;\n\n    if (i == 1) {\n      ASSERT_EQ(tmap.lru_newest(), 0);\n      ASSERT_EQ(tmap.lru_oldest(), 0);\n    } else {\n      ASSERT_EQ(tmap.lru_newest(), i);\n      ASSERT_EQ(tmap.lru_oldest(), 2);\n    }\n  }\n\n  for (auto i = 1; i < 10000000; i++) {\n    auto k = eos::common::getRandom(1, 1000);\n    auto kop = eos::common::getRandom(1, 3);\n\n    if (k == 1) {\n      continue;\n    }\n\n    switch (kop) {\n    case 1:\n      if (tmap.count(k) && tmap[k]) {\n        //  std::cout << \"LRU-UPDATE: \" << k << std::endl;\n        tmap.lru_update(k, lut[k]);\n        ASSERT_EQ(tmap.lru_newest(), k);\n      }\n\n      break;\n\n    case 2:\n      if (!tmap.count(k) || !tmap[k]) {\n        //  std::cout << \"LRU-ADD: \" << k << std::endl;\n        tmap[k] = lut[k];\n        tmap.lru_add(k, lut[k]);\n        ASSERT_EQ(tmap.lru_newest(), k);\n      }\n\n      blut[k] = true;\n      break;\n\n    case 3:\n      if (tmap.count(k) || !tmap[k]) {\n        //  std::cout << \"LRU-REMOVE: \" << k << std::endl;\n        tmap.lru_remove(k);\n        tmap[k] = 0;\n      }\n\n      blut[k] = false;\n      break;\n\n    default:\n      break;\n    }\n\n    //    std::cout << \"oldest: \" << tmap.lru_oldest() << \" \" << blut[tmap.lru_oldest()] << \" op:\" << kop << \" ino:\" << k <<std::endl;\n    ASSERT_EQ(tmap[tmap.lru_oldest()]->lru_prev(), 0);\n    ASSERT_EQ(blut[tmap.lru_oldest()], true);\n    //    std::cout << k << \" : \" << kop << \" blut: \" << blut[k] << std::endl;\n  }\n\n  auto cnt = 0;\n\n  for (auto i = 1; i <= 1000; ++i) {\n    if (blut[i]) {\n      cnt++;\n    }\n  }\n\n  auto old = tmap.lru_oldest();\n\n  for (auto i = 2; i <= 1000; ++i) {\n    if (!blut[i]) {\n      ASSERT_EQ(tmap[i], nullptr);\n    }\n  }\n\n  // check sanity of the left-over LRU list\n  auto c = 1;\n\n  for (c = 1; c <= 1000; c++) {\n    auto n = tmap[old]->lru_next();\n\n    if (!n) {\n      break;\n    }\n\n    ASSERT_EQ(tmap[n]->lru_prev(), old);\n    old = n;\n  }\n\n  ASSERT_EQ(c + 1, cnt);\n}\n"
  },
  {
    "path": "fusex/tests/rb-tree.cc",
    "content": "/*\n * RBTreeTest.cc\n *\n *  Created on: Mar 22, 2017\n *      Author: Michal Simon\n *\n ************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/utils/RandUtils.hh\"\n#include \"fusex/data/rbtree.hh\"\n#include \"gtest/gtest.h\"\n#include <climits>\n#include <unistd.h>\n\nclass RBTreeTest\n{\npublic:\n\n  static void Populate(rbtree<int, std::string>& tree)\n  {\n    for (int i = 0; i < 1000; ++i) {\n      int k = eos::common::getRandom<int>(1, 1000);\n      std::stringstream ss;\n      ss << k;\n      tree.insert(k, ss.str());\n    }\n\n    for (int i = 0; i < 200; ++i) {\n      int k = eos::common::getRandom<int>(1, 1000);\n      tree.erase(k);\n    }\n  }\n\n  static std::pair<bool, int> TestRBInvariant(const rbtree<int, std::string>&\n      tree)\n  {\n    return TestRBInvariant(tree.tree_root);\n  }\n\n  static std::pair<bool, int> TestRBInvariant(const\n      std::unique_ptr< node_t<int, std::string> >& root)\n  {\n    // base case\n    if (!root) {\n      return std::make_pair(true, 0);\n    }\n\n    int black = 0;\n\n    if (root->colour == RED) {\n      // RED node cannot have RED children\n      if ((root->left && root->left->colour == RED) || (root->right &&\n          root->right->colour == RED)) {\n        return std::make_pair(false, -1);\n      }\n    } else {\n      black += 1;\n    }\n\n    std::pair<bool, int> l = TestRBInvariant(root->left);\n    std::pair<bool, int> r = TestRBInvariant(root->right);\n\n    // both sub trees have to be valid red-black trees\n    if (!l.first || !r.first) {\n      return std::make_pair(false, -1);\n    }\n\n    // the 'black' high of both sub-trees has to be the same\n    if (l.second != r.second) {\n      return std::make_pair(false, -1);\n    }\n\n    return std::make_pair(true, l.second + black);\n  }\n\n  static std::pair<bool, int> GetMax(const\n                                     std::unique_ptr< node_t<int, std::string> >& root)\n  {\n    if (!root) {\n      return std::make_pair(false, 0);\n    }\n\n    node_t<int, std::string>* node = root.get();\n\n    while (node->right) {\n      node = node->right.get();\n    }\n\n    return std::make_pair(true, node->key);\n  }\n\n  static std::pair<bool, int> GetMin(const\n                                     std::unique_ptr< node_t<int, std::string> >& root)\n  {\n    if (!root) {\n      return std::make_pair(false, 0);\n    }\n\n    node_t<int, std::string>* node = root.get();\n\n    while (node->left) {\n      node = node->left.get();\n    }\n\n    return std::make_pair(true, node->key);\n  }\n\n  static bool TestBSTInvariant(const rbtree<int, std::string>& tree)\n  {\n    return TestBSTInvariant(tree.tree_root);\n  }\n\n  static bool TestBSTInvariant(const std::unique_ptr< node_t<int, std::string> >&\n                               root)\n  {\n    if (!root) {\n      return true;\n    }\n\n    if (!TestBSTInvariant(root->left) || !TestBSTInvariant(root->right)) {\n      return false;\n    }\n\n    auto right_min = GetMin(root->right);\n\n    // check if the right-sub tree exists\n    if (right_min.first)\n\n      // all the items in right sub-tree have to be greater than root\n      if (right_min.second <= root->key) {\n        return false;\n      }\n\n    auto left_max = GetMax(root->left);\n\n    // check if the left sub-tree exists\n    if (left_max.first)\n\n      // all the items in left sub-tree have to be smaller than root\n      if (left_max.second >= root->key) {\n        return false;\n      }\n\n    return true;\n  }\n};\n\nTEST(RBTree, TestInvariant)\n{\n  rbtree<int, std::string> tree;\n  RBTreeTest::Populate(tree);\n  auto ret = RBTreeTest::TestRBInvariant(tree);\n  ASSERT_TRUE(ret.first);\n}\n\nTEST(RBTree, TestBSTInvariant)\n{\n  rbtree<int, std::string> tree;\n  RBTreeTest::Populate(tree);\n  ASSERT_TRUE(RBTreeTest::TestBSTInvariant(tree));\n}\n\nTEST(RBTree, TestIterator)\n{\n  rbtree<int, std::string> tree;\n  tree.insert(1, \"1\");\n  tree.insert(2, \"2\");\n  tree.insert(3, \"3\");\n  tree.insert(4, \"4\");\n  tree.insert(5, \"5\");\n  tree.insert(6, \"6\");\n  tree.insert(7, \"7\");\n  tree.insert(8, \"8\");\n  tree.insert(9, \"9\");\n  int i = 1;\n  rbtree<int, std::string>::iterator itr;\n\n  for (itr = tree.begin(); itr != tree.end(); ++itr) {\n    ASSERT_EQ(itr->key, i);\n    ++i;\n  }\n}\n"
  },
  {
    "path": "fusex/tests/rocks-kv.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file RocksKVTest.cc\n//! @author Georgios Bitzes CERN\n//! @brief tests for kv persistency class based on rocksdb\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifdef HAVE_ROCKSDB\n#include \"kv/RocksKV.hh\"\n#include \"gtest/gtest.h\"\n\nTEST(RocksKV, BasicSanity)\n{\n  ASSERT_EQ(system(\"rm -rf /tmp/eos-fusex-tests\"), 0);\n  RocksKV kv;\n  ASSERT_EQ(kv.connect(\"myprefix\", \"/tmp/eos-fusex-tests\"), 0);\n  ASSERT_EQ(kv.put(\"123\", \"asdf\"), 0);\n  std::string tmp;\n  ASSERT_EQ(kv.get(\"123\", tmp), 0);\n  ASSERT_EQ(tmp, \"asdf\");\n  uint64_t ret;\n  ASSERT_EQ(kv.put(\"123\", 4), 0);\n  ASSERT_EQ(kv.get(\"123\", ret), 0);\n  ASSERT_EQ(ret, 4u);\n  ASSERT_EQ(kv.put(\"test\", \"test\"), 0);\n  ASSERT_EQ(kv.get(\"test\", ret), -1); // cannot convert \"test\" to uint64_t\n  ASSERT_EQ(kv.put(1, \"value\", \"l\"), 0);\n  ASSERT_EQ(kv.get(1, tmp, \"l\"), 0);\n  ASSERT_EQ(tmp, \"value\");\n  ASSERT_EQ(kv.put(10, 5, \"asdf\"), 0);\n  ASSERT_EQ(kv.get(10, ret, \"asdf\"), 0);\n  ASSERT_EQ(ret, 5);\n  ASSERT_EQ(kv.erase(10, \"asdf\"), 0);\n  ASSERT_EQ(kv.get(10, ret, \"asdf\"), 1);\n  uint64_t increment = 10;\n  ASSERT_EQ(kv.inc(\"my-counter\", increment), 0);\n  ASSERT_EQ(increment, 10);\n  increment = 5;\n  ASSERT_EQ(kv.inc(\"my-counter\", increment), 0);\n  ASSERT_EQ(increment, 15u);\n  ASSERT_EQ(kv.get(\"my-counter\", ret), 0);\n  ASSERT_EQ(ret, 15u);\n  ASSERT_EQ(kv.inc(\"test\", increment), -1);\n}\n\n#endif // HAVE_ROCKSDB\n"
  },
  {
    "path": "fusex/tests/stress/xrdcl-proxy.cc",
    "content": "/*\n * XrdClProxyTest.cc\n *\n *  Created on: June 02, 2017\n *      Author: Andreas-Joachim Peters\n */\n\n\n\n#include \"fusex/data/xrdclproxy.hh\"\n#include <XrdSys/XrdSysTimer.hh>\n#include \"common/ShellCmd.hh\"\n#include <stdint.h>\n#include <algorithm>\n#include <vector>\n#include \"gtest/gtest.h\"\n\nTEST(XrdClProxy, Write)\n{\n  eos::common::ShellCmd xrd(\"xrootd -p 21234 -n proxytest\");\n  XrdSysTimer sleeper;\n  sleeper.Snooze(1);\n  std::vector<char> buffer;\n  buffer.resize(4096);\n\n  for (size_t i = 0; i < buffer.size(); i++) {\n    buffer[i] = i % 256;\n  }\n\n  XrdCl::shared_proxy file = XrdCl::Proxy::Factory();\n  XrdCl::OpenFlags::Flags targetFlags = XrdCl::OpenFlags::Update |\n                                        XrdCl::OpenFlags::Delete;\n  XrdCl::Access::Mode mode = XrdCl::Access::UR | XrdCl::Access::UW |\n                             XrdCl::Access::UX;\n  fprintf(stderr, \"[01] open)\\n\");\n  XrdCl::XRootDStatus status =\n    file->Open(\"root://localhost:21234//tmp/xrdclproxytest\", targetFlags, mode, 300);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"[02] waitopen)\\n\");\n  status = file->WaitOpen();\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"[03] write-sync \\n\");\n\n  for (size_t i = 0; i < 64; ++i) {\n    fprintf(stderr, \".\");\n    status = file->Write(i, 1, &buffer[i], (uint16_t) 300);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  status = file->Truncate(0);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[04] write-async \\n\");\n\n  for (size_t i = 0; i < buffer.size(); ++i) {\n    if (!(i % 1000)) {\n      fprintf(stderr, \".\");\n    }\n\n    XrdCl::Proxy::write_handler handler = file->WriteAsyncPrepare(file, 1);\n    status = file->WriteAsync(i, 1, &buffer[i], handler, (uint16_t) 300);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  status = file->CollectWrites();\n  ASSERT_TRUE(status.IsOK());\n  status = file->Close((uint16_t) 0);\n  ASSERT_TRUE(status.IsOK());\n}\n\nTEST(XrdClProxy, ReadSync)\n{\n  eos::common::ShellCmd xrd(\"xrootd -p 21234 -n proxytest\");\n  XrdSysTimer sleeper;\n  sleeper.Snooze(1);\n  std::vector<int> buffer;\n  buffer.resize(64 * 1024 * 1024);\n\n  for (size_t i = 0; i < buffer.size(); i++) {\n    buffer[i] = i;\n  }\n\n  XrdCl::shared_proxy file = XrdCl::Proxy::Factory();\n  XrdCl::OpenFlags::Flags targetFlags = XrdCl::OpenFlags::Update |\n                                        XrdCl::OpenFlags::Delete;\n  XrdCl::Access::Mode mode = XrdCl::Access::UR | XrdCl::Access::UW |\n                             XrdCl::Access::UX;\n  fprintf(stderr, \"[01] open)\\n\");\n  XrdCl::XRootDStatus status =\n    file->Open(\"root://localhost:21234//tmp/xrdclproxytest\", targetFlags, mode, 300);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"[02] waitopen)\\n\");\n  status = file->WaitOpen();\n  ASSERT_TRUE(status.IsOK());\n  status = file->Truncate(0);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[03] write-async \\n\");\n\n  for (size_t i = 0; i < 64; ++i) {\n    fprintf(stderr, \".\");\n    XrdCl::Proxy::write_handler handler = file->WriteAsyncPrepare(file, 4 * 1024 * 1024);\n    status = file->WriteAsync(4 * i * 1024 * 1024, 4 * 1024 * 1024,\n                             &buffer[i * 1024 * 1024], handler, (uint16_t) 300);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  status = file->CollectWrites();\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[04] zero \\n\");\n\n  for (size_t i = 0; i < 64 * 1024 * 1024; i++) {\n    buffer[i] = 0;\n  }\n\n  fprintf(stderr, \"\\n[05] read \\n\");\n  ssize_t total_bytes = 0;\n\n  for (size_t i = 0; i < 330; ++i) {\n    uint32_t bytesRead = 0;\n    fprintf(stderr, \".\");\n    status = file->Read(file, 4 * i * 200 * 1024, 4 * 200 * 1024, &buffer[i * 200 * 1024],\n                       bytesRead, (uint16_t) 300);\n    total_bytes += bytesRead;\n    //fprintf(stderr, \"----: bytesRead=%u\\n\", bytesRead);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  ASSERT_EQ(total_bytes, (4 * 1024 * 1024 * 64));\n  fprintf(stderr, \"\\n[06] comparing \\n\");\n\n  for (ssize_t i = 0; i < 64 * 1024 * 1024; i++) {\n    if (buffer[i] != (int) i) {\n      ASSERT_EQ(buffer[i], i);\n    }\n  }\n\n  fprintf(stderr, \"\\n[07] ra-efficiency=%f\\n\", file->get_readahead_efficiency());\n  file->Collect();\n  status = file->Close((uint16_t) 0);\n  ASSERT_TRUE(status.IsOK());\n}\n\nTEST(XrdClProxy, ReadAsync)\n{\n  eos::common::ShellCmd xrd(\"xrootd -p 21234 -n proxytest\");\n  XrdSysTimer sleeper;\n  sleeper.Snooze(1);\n  std::vector<int> buffer;\n  buffer.resize(64 * 1024 * 1024);\n\n  for (size_t i = 0; i < buffer.size(); i++) {\n    buffer[i] = i;\n  }\n\n  XrdCl::shared_proxy file = XrdCl::Proxy::Factory();\n  \n  XrdCl::OpenFlags::Flags targetFlags = XrdCl::OpenFlags::Update |\n                                        XrdCl::OpenFlags::Delete;\n  XrdCl::Access::Mode mode = XrdCl::Access::UR | XrdCl::Access::UW |\n                             XrdCl::Access::UX;\n  fprintf(stderr, \"[01] open)\\n\");\n  XrdCl::XRootDStatus status =\n    file->Open(\"root://localhost:21234//tmp/xrdclproxytest\", targetFlags, mode, 300);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"[02] waitopen)\\n\");\n  status = file->WaitOpen();\n  ASSERT_TRUE(status.IsOK());\n  status = file->Truncate(0);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[03] write-async \\n\");\n\n  for (size_t i = 0; i < 64; ++i) {\n    fprintf(stderr, \".\");\n    XrdCl::Proxy::write_handler handler = file->WriteAsyncPrepare(file, 4 * 1024 * 1024);\n    status = file->WriteAsync(4 * i * 1024 * 1024, 4 * 1024 * 1024,\n                             &buffer[i * 1024 * 1024], handler, (uint16_t) 300);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  status = file->CollectWrites();\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[04] zero \\n\");\n\n  for (size_t i = 0; i < 64 * 1024 * 1024; i++) {\n    buffer[i] = 0;\n  }\n\n  fprintf(stderr, \"\\n[05] read \\n\");\n  ssize_t total_bytes = 0;\n\n  for (size_t i = 0; i < 330; ++i) {\n    uint32_t bytesRead = 0;\n    fprintf(stderr, \".\");\n    XrdCl::Proxy::read_handler handler = file->ReadAsyncPrepare(file, 4 * i * 200 * 1024,\n                                         4 * 200 * 1024);\n    status = file->PreReadAsync(4 * i * 200 * 1024, 4 * 200 * 1024, handler,\n                               (uint16_t) 300);\n    ASSERT_TRUE(status.IsOK());\n    status = file->WaitRead(handler);\n    ASSERT_TRUE(status.IsOK());\n    //fprintf(stderr, \"offset=%lu read=%u buffer=%x size=%lu\\n\", i * 200 * 1024, handler->vbuffer().size(), handler->buffer(), handler->vbuffer().size());\n    status = file->ReadAsync(handler, 4 * 200 * 1024, &buffer[i * 200 * 1024],\n                            bytesRead);\n    ASSERT_TRUE(status.IsOK());\n    total_bytes += bytesRead;\n    ASSERT_TRUE(status.IsOK());\n    file->DoneAsync(handler);\n  }\n\n  ASSERT_EQ(total_bytes, (4 * 1024 * 1024 * 64));\n  fprintf(stderr, \"\\n[06] comparing \\n\");\n\n  for (ssize_t i = 0; i < 64 * 1024 * 1024; i++) {\n    if (buffer[i] != (int) i) {\n      ASSERT_EQ(buffer[i], (int) i);\n    }\n  }\n\n  fprintf(stderr, \"\\n[07] ra-efficiency=%f\\n\", file->get_readahead_efficiency());\n  file->Collect();\n  status = file->Close((uint16_t) 0);\n  ASSERT_TRUE(status.IsOK());\n}\n\nTEST(XrdClProxy, ReadAheadStatic)\n{\n  eos::common::ShellCmd xrd(\"xrootd -p 21234 -n proxytest\");\n  XrdSysTimer sleeper;\n  sleeper.Snooze(1);\n  std::vector<int> buffer;\n  buffer.resize(64 * 1024 * 1024);\n\n  for (size_t i = 0; i < buffer.size(); i++) {\n    buffer[i] = i;\n  }\n\n  XrdCl::shared_proxy file = XrdCl::Proxy::Factory();\n  XrdCl::OpenFlags::Flags targetFlags = XrdCl::OpenFlags::Update |\n                                        XrdCl::OpenFlags::Delete;\n  XrdCl::Access::Mode mode = XrdCl::Access::UR | XrdCl::Access::UW |\n                             XrdCl::Access::UX;\n  fprintf(stderr, \"[01] open)\\n\");\n  XrdCl::XRootDStatus status =\n    file->Open(\"root://localhost:21234//tmp/xrdclproxytest\", targetFlags, mode, 300);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"[02] waitopen)\\n\");\n  status = file->WaitOpen();\n  ASSERT_TRUE(status.IsOK());\n  status = file->Truncate(0);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[03] write-async \\n\");\n\n  for (size_t i = 0; i < 64; ++i) {\n    fprintf(stderr, \".\");\n    XrdCl::Proxy::write_handler handler = file->WriteAsyncPrepare(file, 4 * 1024 * 1024);\n    status = file->WriteAsync(4 * i * 1024 * 1024, 4 * 1024 * 1024,\n                             &buffer[i * 1024 * 1024], handler, (uint16_t) 300);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  status = file->CollectWrites();\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[04] zero \\n\");\n\n  for (size_t i = 0; i < 64 * 1024 * 1024; i++) {\n    buffer[i] = 0;\n  }\n\n  file->set_readahead_strategy(XrdCl::Proxy::STATIC, 4096, 2 * 819200,\n                              4 * 1024 * 1024, 2);\n  fprintf(stderr, \"\\n[05] read-ahead static 4k 1.6k 4M \\n\");\n  ssize_t total_bytes = 0;\n\n  for (size_t i = 0; i < 330; ++i) {\n    XrdSysTimer sleeper;\n    //sleeper.Wait(1000);\n    uint32_t bytesRead = 0;\n    fprintf(stderr, \".\");\n    status = file->Read(file, 4 * i * 200 * 1024, 4 * 200 * 1024, &buffer[i * 200 * 1024],\n                       bytesRead, (uint16_t) 300);\n    total_bytes += bytesRead;\n    //fprintf(stderr, \"----: bytesRead=%u\\n\", bytesRead);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  ASSERT_EQ(total_bytes, (4 * 1024 * 1024 * 64));\n  fprintf(stderr, \"\\n[06] comparing \\n\");\n\n  for (ssize_t i = 0; i < 64 * 1024 * 1024; i++) {\n    if (buffer[i] != (int) i) {\n      ASSERT_EQ(buffer[i], (int) i);\n    }\n  }\n\n  fprintf(stderr, \"\\n[07] ra-efficiency=%f\\n\", file->get_readahead_efficiency());\n  ASSERT_EQ((int)(1000000 * file->get_readahead_efficiency()), 99694824);\n  file->Collect();\n  status = file->Close((uint16_t) 0);\n  ASSERT_TRUE(status.IsOK());\n}\n\nTEST(XrdClProxy, ReadAheadStaticLarge)\n{\n  eos::common::ShellCmd xrd(\"xrootd -p 21234 -n proxytest\");\n  XrdSysTimer sleeper;\n  sleeper.Snooze(1);\n  std::vector<int> buffer;\n  buffer.resize(64 * 1024 * 1024);\n\n  for (size_t i = 0; i < buffer.size(); i++) {\n    buffer[i] = i;\n  }\n\n  XrdCl::shared_proxy file = XrdCl::Proxy::Factory();\n  XrdCl::OpenFlags::Flags targetFlags = XrdCl::OpenFlags::Update |\n                                        XrdCl::OpenFlags::Delete;\n  XrdCl::Access::Mode mode = XrdCl::Access::UR | XrdCl::Access::UW |\n                             XrdCl::Access::UX;\n  fprintf(stderr, \"[01] open)\\n\");\n  XrdCl::XRootDStatus status =\n    file->Open(\"root://localhost:21234//tmp/xrdclproxytest\", targetFlags, mode, 300);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"[02] waitopen)\\n\");\n  status = file->WaitOpen();\n  ASSERT_TRUE(status.IsOK());\n  status = file->Truncate(0);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[03] write-async \\n\");\n\n  for (size_t i = 0; i < 64; ++i) {\n    fprintf(stderr, \".\");\n    XrdCl::Proxy::write_handler handler = file->WriteAsyncPrepare(file, 4 * 1024 * 1024);\n    status = file->WriteAsync(4 * i * 1024 * 1024, 4 * 1024 * 1024,\n                             &buffer[i * 1024 * 1024], handler, (uint16_t) 300);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  status = file->CollectWrites();\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[04] zero \\n\");\n\n  for (size_t i = 0; i < 64 * 1024 * 1024; i++) {\n    buffer[i] = 0;\n  }\n\n  file->set_readahead_strategy(XrdCl::Proxy::STATIC, 4096, 6 * 1024 * 1024,\n                              16 * 1024 * 1024, 2);\n  fprintf(stderr, \"\\n[05] read-ahead static 4k 8M 16M \\n\");\n  ssize_t total_bytes = 0;\n\n  for (size_t i = 0; i < 330; ++i) {\n    XrdSysTimer sleeper;\n    //sleeper.Wait(1000);\n    uint32_t bytesRead = 0;\n    fprintf(stderr, \".\");\n    status = file->Read(file, 4 * i * 200 * 1024, 4 * 200 * 1024, &buffer[i * 200 * 1024],\n                       bytesRead, (uint16_t) 300);\n    total_bytes += bytesRead;\n    //fprintf(stderr, \"----: bytesRead=%u\\n\", bytesRead);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  ASSERT_EQ(total_bytes, (4 * 1024 * 1024 * 64));\n  fprintf(stderr, \"\\n[06] comparing \\n\");\n\n  for (ssize_t i = 0; i < 64 * 1024 * 1024; i++) {\n    if (buffer[i] != (int) i) {\n      ASSERT_EQ(buffer[i], (int) i);\n    }\n  }\n\n  fprintf(stderr, \"\\n[07] ra-efficiency=%f %d\\n\", file->get_readahead_efficiency(),\n          (int)(1000000 * file->get_readahead_efficiency()));\n  ASSERT_EQ((int)(1000000 * file->get_readahead_efficiency()), 99694824);\n  status = file->Close((uint16_t) 0);\n  ASSERT_TRUE(status.IsOK());\n}\n\nTEST(XrdClProxy, ReadAheadSparse)\n{\n  eos::common::ShellCmd xrd(\"xrootd -p 21234 -n proxytest\");\n  XrdSysTimer sleeper;\n  sleeper.Snooze(1);\n  std::vector<int> buffer;\n  buffer.resize(64 * 1024 * 1024);\n\n  for (size_t i = 0; i < buffer.size(); i++) {\n    buffer[i] = i;\n  }\n\n  XrdCl::shared_proxy file = XrdCl::Proxy::Factory();\n  XrdCl::OpenFlags::Flags targetFlags = XrdCl::OpenFlags::Update |\n                                        XrdCl::OpenFlags::Delete;\n  XrdCl::Access::Mode mode = XrdCl::Access::UR | XrdCl::Access::UW |\n                             XrdCl::Access::UX;\n  fprintf(stderr, \"[01] open)\\n\");\n  XrdCl::XRootDStatus status =\n    file->Open(\"root://localhost:21234//tmp/xrdclproxytest\", targetFlags, mode, 300);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"[02] waitopen)\\n\");\n  status = file->WaitOpen();\n  ASSERT_TRUE(status.IsOK());\n  status = file->Truncate(0);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[03] write-async \\n\");\n\n  for (size_t i = 0; i < 64; ++i) {\n    fprintf(stderr, \".\");\n    XrdCl::Proxy::write_handler handler = file->WriteAsyncPrepare(file, 4 * 1024 * 1024);\n    status = file->WriteAsync(4 * i * 1024 * 1024, 4 * 1024 * 1024,\n                             &buffer[i * 1024 * 1024], handler, (uint16_t) 300);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  status = file->CollectWrites();\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[04] zero \\n\");\n\n  for (size_t i = 0; i < 64 * 1024 * 1024; i++) {\n    buffer[i] = 0;\n  }\n\n  file->set_readahead_strategy(XrdCl::Proxy::STATIC, 4096, 2 * 1024 * 1024,\n                              4 * 1024 * 1024, 2);\n  fprintf(stderr, \"\\n[05] read-ahead static 4k 2M 4M \\n\");\n  ssize_t total_bytes = 0;\n\n  for (size_t i = 0; i < 330; i += 2) {\n    XrdSysTimer sleeper;\n    //sleeper.Wait(1000);\n    uint32_t bytesRead = 0;\n    fprintf(stderr, \".\");\n    status = file->Read(file, 4 * i * 200 * 1024, 4 * 200 * 1024, &buffer[i * 200 * 1024],\n                       bytesRead, (uint16_t) 300);\n    total_bytes += bytesRead;\n    //fprintf(stderr, \"----: bytesRead=%u\\n\", bytesRead);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  fprintf(stderr, \"total_bytes = %lu\\n\", total_bytes);\n  ASSERT_EQ(total_bytes, 134348800);\n  fprintf(stderr, \"\\n[06] comparing \\n\");\n\n  for (size_t k = 0; k < 330; k += 2)\n    for (size_t l = 0; l < 200 * 1024; l++) {\n      size_t i = (k * 200 * 1024) + l;\n\n      if (i < 67108864) {\n        if (buffer[i] != (int) i) {\n          ASSERT_EQ(buffer[i], (int) i);\n        }\n      }\n    }\n\n  fprintf(stderr, \"\\n[07] ra-efficiency=%f %d\\n\", file->get_readahead_efficiency(),\n          (int)(1000000 * file->get_readahead_efficiency()));\n  fprintf(stderr, \"\\n[07] ra-efficiency=%f\\n\", file->get_readahead_efficiency());\n  ASSERT_EQ((int)(1000000 * file->get_readahead_efficiency()), 99121952);\n  file->Collect();\n  status = file->Close((uint16_t) 0);\n  ASSERT_TRUE(status.IsOK());\n}\n\nTEST(XrdClProxy, ReadAheadDisable)\n{\n  eos::common::ShellCmd xrd(\"xrootd -p 21234 -n proxytest\");\n  XrdSysTimer sleeper;\n  sleeper.Snooze(1);\n  std::vector<int> buffer;\n  buffer.resize(64 * 1024 * 1024);\n\n  for (size_t i = 0; i < buffer.size(); i++) {\n    buffer[i] = i;\n  }\n\n  XrdCl::shared_proxy file = XrdCl::Proxy::Factory();\n  XrdCl::OpenFlags::Flags targetFlags = XrdCl::OpenFlags::Update |\n                                        XrdCl::OpenFlags::Delete;\n  XrdCl::Access::Mode mode = XrdCl::Access::UR | XrdCl::Access::UW |\n                             XrdCl::Access::UX;\n  fprintf(stderr, \"[01] open)\\n\");\n  XrdCl::XRootDStatus status =\n    file->Open(\"root://localhost:21234//tmp/xrdclproxytest\", targetFlags, mode, 300);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"[02] waitopen)\\n\");\n  status = file->WaitOpen();\n  ASSERT_TRUE(status.IsOK());\n  status = file->Truncate(0);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[03] write-async \\n\");\n\n  for (size_t i = 0; i < 64; ++i) {\n    fprintf(stderr, \".\");\n    XrdCl::Proxy::write_handler handler = file->WriteAsyncPrepare(file, 4 * 1024 * 1024);\n    status = file->WriteAsync(4 * i * 1024 * 1024, 4 * 1024 * 1024,\n                             &buffer[i * 1024 * 1024], handler, (uint16_t) 300);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  status = file->CollectWrites();\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[04] zero \\n\");\n\n  for (size_t i = 0; i < 64 * 1024 * 1024; i++) {\n    buffer[i] = 0;\n  }\n\n  file->set_readahead_strategy(XrdCl::Proxy::STATIC, 4096, 2 * 1024 * 1024,\n                              4 * 1024 * 1024, 2);\n  fprintf(stderr, \"\\n[05] read-ahead static 4k 2M 4M \\n\");\n  ssize_t total_bytes = 0;\n\n  for (size_t i = 0; i < 330; i += (i + 1)) {\n    XrdSysTimer sleeper;\n    //sleeper.Wait(1000);\n    uint32_t bytesRead = 0;\n    fprintf(stderr, \".\");\n    status = file->Read(file, 4 * i * 200 * 1024, 4 * 200 * 1024, &buffer[i * 200 * 1024],\n                       bytesRead, (uint16_t) 300);\n    total_bytes += bytesRead;\n    //fprintf(stderr, \"----: bytesRead=%u\\n\", bytesRead);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  fprintf(stderr, \"\\n[06] comparing \\n\");\n\n  for (size_t k = 0; k < 330; k += (k + 1))\n    for (size_t l = 0; l < 200 * 1024; l++) {\n      size_t i = (k * 200 * 1024) + l;\n      {\n        if (buffer[i] != (int) i) {\n          ASSERT_EQ(buffer[i], (int) i);\n        }\n      }\n    }\n\n  fprintf(stderr, \"\\n[07] ra-efficiency=%f %d\\n\", file->get_readahead_efficiency(),\n          (int)(1000000 * file->get_readahead_efficiency()));\n  fprintf(stderr, \"\\n[07] ra-efficiency=%f\\n\", file->get_readahead_efficiency());\n  ASSERT_EQ((int)(1000000 * file->get_readahead_efficiency()), 29777778);\n  file->Collect();\n  status = file->Close((uint16_t) 100);\n  ASSERT_TRUE(status.IsOK());\n}\n\nTEST(XrdClProxy, ReadAheadBackward)\n{\n  eos::common::ShellCmd xrd(\"xrootd -p 21234 -n proxytest\");\n  XrdSysTimer sleeper;\n  sleeper.Snooze(1);\n  std::vector<int> buffer;\n  buffer.resize(64 * 1024 * 1024);\n\n  for (size_t i = 0; i < buffer.size(); i++) {\n    buffer[i] = i;\n  }\n\n  XrdCl::shared_proxy file = XrdCl::Proxy::Factory();\n  XrdCl::OpenFlags::Flags targetFlags = XrdCl::OpenFlags::Update |\n                                        XrdCl::OpenFlags::Delete;\n  XrdCl::Access::Mode mode = XrdCl::Access::UR | XrdCl::Access::UW |\n                             XrdCl::Access::UX;\n  fprintf(stderr, \"[01] open)\\n\");\n  XrdCl::XRootDStatus status =\n    file->Open(\"root://localhost:21234//tmp/xrdclproxytest\", targetFlags, mode, 300);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"[02] waitopen)\\n\");\n  status = file->WaitOpen();\n  ASSERT_TRUE(status.IsOK());\n  status = file->Truncate(0);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[03] write-async \\n\");\n\n  for (size_t i = 0; i < 64; ++i) {\n    fprintf(stderr, \".\");\n    XrdCl::Proxy::write_handler handler = file->WriteAsyncPrepare(file, 4 * 1024 * 1024);\n    status = file->WriteAsync(4 * i * 1024 * 1024, 4 * 1024 * 1024,\n                             &buffer[i * 1024 * 1024], handler, (uint16_t) 300);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  status = file->CollectWrites();\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[04] zero \\n\");\n\n  for (size_t i = 0; i < 64 * 1024 * 1024; i++) {\n    buffer[i] = 0;\n  }\n\n  file->set_readahead_strategy(XrdCl::Proxy::STATIC, 4096, 2 * 819200,\n                              4 * 1024 * 1024, 2);\n  fprintf(stderr, \"\\n[05] read-ahead static 4k 1.6M 4M \\n\");\n  ssize_t total_bytes = 0;\n\n  for (int i = 329; i >= 0; i--) {\n    XrdSysTimer sleeper;\n    //sleeper.Wait(1000);\n    uint32_t bytesRead = 0;\n    fprintf(stderr, \".\");\n    status = file->Read(file, 4 * i * 200 * 1024, 4 * 200 * 1024, &buffer[i * 200 * 1024],\n                       bytesRead, (uint16_t) 300);\n    total_bytes += bytesRead;\n    //fprintf(stderr, \"----: bytesRead=%u\\n\", bytesRead);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  ASSERT_EQ(total_bytes, (4 * 1024 * 1024 * 64));\n  fprintf(stderr, \"\\n[06] comparing \\n\");\n\n  for (ssize_t i = 0; i < 64 * 1024 * 1024; i++) {\n    if (buffer[i] != (int) i) {\n      ASSERT_EQ(buffer[i], (int) i);\n    }\n  }\n\n  fprintf(stderr, \"\\n[07] ra-efficiency=%f\\n\", file->get_readahead_efficiency());\n  ASSERT_EQ(file->get_readahead_efficiency(), 0);\n  file->Collect();\n  status = file->Close((uint16_t) 0);\n  ASSERT_TRUE(status.IsOK());\n}\n\nTEST(XrdClProxy, ReadAheadDynamic)\n{\n  eos::common::ShellCmd xrd(\"xrootd -p 21234 -n proxytest\");\n  XrdSysTimer sleeper;\n  sleeper.Snooze(1);\n  std::vector<int> buffer;\n  buffer.resize(64 * 1024 * 1024);\n\n  for (size_t i = 0; i < buffer.size(); i++) {\n    buffer[i] = i;\n  }\n\n  XrdCl::shared_proxy file = XrdCl::Proxy::Factory();\n  XrdCl::OpenFlags::Flags targetFlags = XrdCl::OpenFlags::Update |\n                                        XrdCl::OpenFlags::Delete;\n  XrdCl::Access::Mode mode = XrdCl::Access::UR | XrdCl::Access::UW |\n                             XrdCl::Access::UX;\n  fprintf(stderr, \"[01] open)\\n\");\n  XrdCl::XRootDStatus status =\n    file->Open(\"root://localhost:21234//tmp/xrdclproxytest\", targetFlags, mode, 300);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"[02] waitopen)\\n\");\n  status = file->WaitOpen();\n  ASSERT_TRUE(status.IsOK());\n  status = file->Truncate(0);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[03] write-async \\n\");\n\n  for (size_t i = 0; i < 64; ++i) {\n    fprintf(stderr, \".\");\n    XrdCl::Proxy::write_handler handler = file->WriteAsyncPrepare(file, 4 * 1024 * 1024);\n    status = file->WriteAsync(4 * i * 1024 * 1024, 4 * 1024 * 1024,\n                             &buffer[i * 1024 * 1024], handler, (uint16_t) 300);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  status = file->CollectWrites();\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[04] zero \\n\");\n\n  for (size_t i = 0; i < 64 * 1024 * 1024; i++) {\n    buffer[i] = 0;\n  }\n\n  file->set_readahead_strategy(XrdCl::Proxy::DYNAMIC, 4096, 1 * 1024 * 1024,\n                              8 * 1024 * 1024, 2);\n  fprintf(stderr, \"\\n[05] read-ahead dynamic 4k 1M 8M \\n\");\n  ssize_t total_bytes = 0;\n\n  for (size_t i = 0; i < 330; ++i) {\n    XrdSysTimer sleeper;\n    //sleeper.Wait(1000);\n    uint32_t bytesRead = 0;\n    fprintf(stderr, \".\");\n    status = file->Read(file, 4 * i * 200 * 1024, 4 * 200 * 1024, &buffer[i * 200 * 1024],\n                       bytesRead, (uint16_t) 300);\n    total_bytes += bytesRead;\n    //fprintf(stderr, \"----: bytesRead=%u\\n\", bytesRead);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  ASSERT_EQ(total_bytes, (4 * 1024 * 1024 * 64));\n  fprintf(stderr, \"\\n[06] comparing \\n\");\n\n  for (ssize_t i = 0; i < 64 * 1024 * 1024; i++) {\n    if (buffer[i] != (int) i) {\n      ASSERT_EQ(buffer[i], (int) i);\n    }\n  }\n\n  fprintf(stderr, \"\\n[07] ra-efficiency=%f %d\\n\", file->get_readahead_efficiency(),\n          (int)(1000000 * file->get_readahead_efficiency()));\n  fprintf(stderr, \"\\n[07] ra-efficiency=%f\\n\", file->get_readahead_efficiency());\n  ASSERT_EQ((int)(1000000 * file->get_readahead_efficiency()), 99475096);\n  file->Collect();\n  status = file->Close((uint16_t) 0);\n  ASSERT_TRUE(status.IsOK());\n}\n\nTEST(XrdClProxy, ScheduleWrite)\n{\n  eos::common::ShellCmd xrd(\"xrootd -p 21234 -n proxytest\");\n  XrdSysTimer sleeper;\n  sleeper.Snooze(1);\n  std::vector<int> buffer;\n  buffer.resize(64 * 1024 * 1024);\n\n  for (size_t i = 0; i < buffer.size(); i++) {\n    buffer[i] = i;\n  }\n\n  XrdCl::shared_proxy file = XrdCl::Proxy::Factory();\n  XrdCl::OpenFlags::Flags targetFlags = XrdCl::OpenFlags::Update |\n                                        XrdCl::OpenFlags::Delete;\n  XrdCl::Access::Mode mode = XrdCl::Access::UR | XrdCl::Access::UW |\n                             XrdCl::Access::UX;\n  fprintf(stderr, \"[01] open)\\n\");\n  XrdCl::XRootDStatus status =\n    file->OpenAsync(file, \"root://localhost:21234//tmp/xrdclproxytest\", targetFlags, mode,\n                   300);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"[02] no waitopen)\\n\");\n  fprintf(stderr, \"\\n[03] write-schedule-async \\n\");\n\n  for (size_t i = 0; i < 64; ++i) {\n    fprintf(stderr, \".\");\n    XrdCl::Proxy::write_handler handler = file->WriteAsyncPrepare(file, 4 * 1024 * 1024,\n                                          4 * i * 1024 * 1024, 300);\n    status = file->ScheduleWriteAsync(&buffer[i * 1024 * 1024], handler);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  status = file->CollectWrites();\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"\\n[04] zero \\n\");\n\n  for (size_t i = 0; i < 64 * 1024 * 1024; i++) {\n    buffer[i] = 0;\n  }\n\n  fprintf(stderr, \"\\n[05] read \\n\");\n  ssize_t total_bytes = 0;\n\n  for (size_t i = 0; i < 330; ++i) {\n    uint32_t bytesRead = 0;\n    fprintf(stderr, \".\");\n    status = file->Read(file, 4 * i * 200 * 1024, 4 * 200 * 1024, &buffer[i * 200 * 1024],\n                       bytesRead, (uint16_t) 300);\n    total_bytes += bytesRead;\n    //fprintf(stderr, \"----: bytesRead=%u\\n\", bytesRead);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  ASSERT_EQ(total_bytes, (4 * 1024 * 1024 * 64));\n  fprintf(stderr, \"\\n[06] comparing \\n\");\n\n  for (ssize_t i = 0; i < 64 * 1024 * 1024; i++) {\n    if (buffer[i] != (int) i) {\n      ASSERT_EQ(buffer[i], i);\n    }\n  }\n\n  fprintf(stderr, \"\\n[07] scheduled-write-fraction=%f\\n\",\n          file->get_scheduled_submission_fraction());\n  file->Collect();\n  status = file->Close((uint16_t) 0);\n  ASSERT_TRUE(status.IsOK());\n}\n\nTEST(XrdClProxy, ScheduleClose)\n{\n  eos::common::ShellCmd xrd(\"xrootd -p 21234 -n proxytest\");\n  XrdSysTimer sleeper;\n  sleeper.Snooze(1);\n  std::vector<int> buffer;\n  buffer.resize(64 * 1024 * 1024);\n\n  for (size_t i = 0; i < buffer.size(); i++) {\n    buffer[i] = i;\n  }\n\n  XrdCl::shared_proxy file = XrdCl::Proxy::Factory();\n  XrdCl::OpenFlags::Flags targetFlags = XrdCl::OpenFlags::Update |\n                                        XrdCl::OpenFlags::Delete;\n  XrdCl::Access::Mode mode = XrdCl::Access::UR | XrdCl::Access::UW |\n                             XrdCl::Access::UX;\n  fprintf(stderr, \"[01] open)\\n\");\n  XrdCl::XRootDStatus status =\n    file->OpenAsync(file, \"root://localhost:21234//tmp/xrdclproxytest\", targetFlags, mode,\n                   300);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"[02] no waitopen)\\n\");\n  fprintf(stderr, \"\\n[03] write-schedule-async \\n\");\n\n  for (size_t i = 0; i < 64; ++i) {\n    fprintf(stderr, \".\");\n    XrdCl::Proxy::write_handler handler = file->WriteAsyncPrepare(file, 4 * 1024 * 1024,\n                                          4 * i * 1024 * 1024, 300);\n    status = file->ScheduleWriteAsync(&buffer[i * 1024 * 1024], handler);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  fprintf(stderr, \"\\n[07] scheduled-write-fraction=%f\\n\",\n          file->get_scheduled_submission_fraction());\n  status = file->WaitClose();\n  ASSERT_TRUE(status.IsOK());\n}\n\nTEST(XrdClProxy, ScheduleCloseAfterCollect)\n{\n  eos::common::ShellCmd xrd(\"xrootd -p 21234 -n proxytest\");\n  XrdSysTimer sleeper;\n  sleeper.Snooze(1);\n  std::vector<int> buffer;\n  buffer.resize(64 * 1024 * 1024);\n\n  for (size_t i = 0; i < buffer.size(); i++) {\n    buffer[i] = i;\n  }\n\n  XrdCl::shared_proxy file = XrdCl::Proxy::Factory();\n  XrdCl::OpenFlags::Flags targetFlags = XrdCl::OpenFlags::Update |\n                                        XrdCl::OpenFlags::Delete;\n  XrdCl::Access::Mode mode = XrdCl::Access::UR | XrdCl::Access::UW |\n                             XrdCl::Access::UX;\n  fprintf(stderr, \"[01] open)\\n\");\n  XrdCl::XRootDStatus status =\n    file->OpenAsync(file, \"root://localhost:21234//tmp/xrdclproxytest\", targetFlags, mode,\n                   300);\n  ASSERT_TRUE(status.IsOK());\n  fprintf(stderr, \"[02] no waitopen)\\n\");\n  fprintf(stderr, \"\\n[03] write-schedule-async \\n\");\n\n  for (size_t i = 0; i < 64; ++i) {\n    fprintf(stderr, \".\");\n    XrdCl::Proxy::write_handler handler = file->WriteAsyncPrepare(file, 4 * 1024 * 1024,\n                                          4 * i * 1024 * 1024, 300);\n    status = file->ScheduleWriteAsync(&buffer[i * 1024 * 1024], handler);\n    ASSERT_TRUE(status.IsOK());\n  }\n\n  file->Collect();\n  file->ScheduleCloseAsync(file, 300);\n  fprintf(stderr, \"\\n[07] scheduled-write-fraction=%f\\n\",\n          file->get_scheduled_submission_fraction());\n  status = file->WaitClose();\n  ASSERT_TRUE(status.IsOK());\n}\n"
  },
  {
    "path": "fusex/tsan/suppressions.tsan",
    "content": "race:eos::fusex::md::_internal_pid\nrace:eos::fusex::md::_internal_id\nrace:eos::fusex::md::CopyFrom\nrace:XrdSysTimer::TimeZone\n"
  },
  {
    "path": "genversion.sh",
    "content": "#!/bin/bash\n\n#-------------------------------------------------------------------------------\n# This script generates the version information using the last git commit. If\n# the last commit was also tagged, then the tag information is used to build\n# the version information in the form of MAJOR, MINOR and PATCH values.\n# If the last commit is not tagged then the version is built using the date of\n# the last commit and its hash value. Therefore, we have the following\n# convention:\n# MAJOR = major value of the last tag in the current branch\n# MINOR = YYYYMMDD of the last commit\n# PATCH = hash 7 characters long of the last commit\n#-------------------------------------------------------------------------------\n\n#-------------------------------------------------------------------------------\n# Generate the version string from the date and the hash\n#-------------------------------------------------------------------------------\nfunction getVersionFromLog()\n{\n  AWK=gawk\n  EX=\"$(which gawk)\"\n  if test x\"${EX}\" == x -o ! -x \"${EX}\"; then\n    AWK=awk\n  fi\n\n  TAG=$1\n\n  # remove release from tag for commits and append log info instead\n  if [[ $TAG == *\"-\"* ]]; then\n    TAG=${TAG%-*}\n  fi\n\n  VERSION=\"$(echo $2 | $AWK -v tag=\"${TAG}\" '{ gsub(\"-\",\"\",$1); gsub(\":\",\"\",$2); print tag\"-\"$1$2\"git\"$4; }')\"\n\n  if test $? -ne 0; then\n    echo \"unknown\";\n    return 1\n  fi\n\n  echo $VERSION\n}\n\n#-------------------------------------------------------------------------------\n# Print help\n#-------------------------------------------------------------------------------\nfunction printHelp()\n{\n  echo \"Usage:\"                             1>&2\n  echo \"${0} [--help] [SOURCEPATH]\"         1>&2\n  echo \"  --help       prints this message\" 1>&2\n}\n\n#-------------------------------------------------------------------------------\n# Main\n#-------------------------------------------------------------------------------\n\n# Parse the parameters\nPRINTHELP=0\nSOURCEPATH=\"\"\n\nwhile [[ ${#} -ne 0 ]]; do\n  if [[ \"${1}\" == \"--help\" ]]; then\n    PRINTHELP=1\n  else\n    SOURCEPATH=\"${1}\"\n  fi\n  shift\ndone\n\nEX=\"$(which git)\"\n\nif [[ \"${EX}\" == \"\" ]] || [[ ! -x \"${EX}\" ]]; then\n  echo \"[!] Unable to find git in the path: setting the version tag to unknown\" 1>&2\n  exit 1\nelse\n  # Sanity check\n  CURRENTDIR=\"$PWD\"\n\n  if [[ ${SOURCEPATH} != \"\" ]]; then\n    cd ${SOURCEPATH}\n  fi\n\n  git log -1 >/dev/null 2>&1\n\n  if [[  ${?} -ne 0 ]]; then\n    # Check if we have a spec file and try to extract the version. This happens\n    # in the rpmbuild step. We don't have a git repo but the version was already\n    # set in the eos.spec file.\n    if [[ -e \"eos.spec\" ]]; then\n       VERSION=\"$(grep \"Version:\" eos.spec | head -1 | awk '{print $2;}')\"\n    else\n      echo \"[!] Unable to get version from git or spec file.\" 1>&2\n      exit 1\n    fi\n  else\n    # Can we match the exact tag?\n    LASTCOMMITONBRANCH=$(git log --first-parent --pretty=format:'%h' -n 1)\n    git describe --tags --abbrev=0 --exact-match ${LASTCOMMITONBRANCH} >/dev/null 2>&1\n\n    if [[ ${?} -eq 0 ]]; then\n      TAG=\"$(git describe --tags --abbrev=0 --exact-match ${LASTCOMMITONBRANCH})\"\n      EXP=\"[0-9]+\\.[0-9]+\\.[0-9]+(-[a-z0-9]+)?$\"\n\n      # Check if tag respects the regular expression\n      VERSION=\"$(echo \"${TAG}\" | grep -E \"${EXP}\")\"\n\n      if [[ ${?} -ne 0 ]]; then\n        echo \"[!] Git tag \\\"${TAG}\\\" does not match the regex\"\n        VERSION=\"\"\n        exit 1\n      fi\n\n    else\n      # Get last tag to extract the major version number\n      LAST_TAG=\"$(git describe --tags --abbrev=0 --candidates=50 ${LASTCOMMITONBRANCH})\"\n\n      if [[ ${?} -ne 0 ]]; then\n        echo \"[!] Can not find last tag to build the commit version\"\n        VERSION=\"\"\n        exit 1\n      fi\n\n      # Get last commit date and hash used to build the rest of the version\n      LOGINFO=\"$(git log -1 --format='%ai %h')\"\n\n      if [[ ${?} -ne 0 ]]; then\n        echo \"[!] Can not get info about last commit\"\n        exit 1\n      fi\n\n      VERSION=\"$(getVersionFromLog \"$LAST_TAG\" \"$LOGINFO\")\"\n    fi\n  fi\n\n  cd $CURRENTDIR\n\n  # The version has the following fomat: major.minor.patch\n  echo $VERSION\nfi\n"
  },
  {
    "path": "git/bin/enable-hooks.sh",
    "content": "#!/bin/bash\n#-------------------------------------------------------------------------------\n# File: enable-hooks.sh\n# Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n# Author: Luis Antonio Obis Aparicio <luis.obis@cern.ch>\n#-------------------------------------------------------------------------------\n#\n#/************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2016 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************/\n\n#-------------------------------------------------------------------------------\n# Description: Convenience script which enables all the pre-commit hooks for the current repository. It also checks if the required dependencies are installed and provides instructions on how to install them if they are missing.\n#-------------------------------------------------------------------------------\n\nREQUIRED_DEPS=\"clang-format pre-commit\"\nHAS_ERROR=0\n\n# Check dependencies\nfor DEP in ${REQUIRED_DEPS}; do\n    if ! command -v \"${DEP}\" > /dev/null 2>&1; then\n        echo \"[!] ${DEP} is not installed.\"\n\n        # Determine the specific install command\n        if [[ \"$OSTYPE\" == \"darwin\"* ]]; then\n            INSTALL_CMD=\"brew install ${DEP}\"\n        elif command -v dnf > /dev/null 2>&1; then\n            if [[ \"${DEP}\" == \"clang-format\" ]]; then\n                INSTALL_CMD=\"sudo dnf install clang-tools-extra\"\n            else\n                INSTALL_CMD=\"sudo dnf install pre-commit\"\n            fi\n        else\n            INSTALL_CMD=\"(Use your system package manager to install '${DEP}')\"\n        fi\n\n        echo \"    To install, run: '${INSTALL_CMD}'\"\n        HAS_ERROR=1\n    fi\ndone\n\n# Exit if any dependencies were missing\nif [ $HAS_ERROR -ne 0 ]; then\n    exit 1\nfi\n\necho \"[+] All dependencies found!\"\n\n# Locate the git root directory\nREPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)\n\nif [ -z \"$REPO_ROOT\" ]; then\n    echo \"[!] Error: This directory is not part of a git repository.\" >&2\n    exit 1\nfi\n\n# Move to root and install pre-commit\necho \"[*] Navigating to repo root: $REPO_ROOT\"\ncd \"$REPO_ROOT\" || exit 1\n\necho \"[*] Installing pre-commit hooks...\"\npre-commit install\n\necho \"[+] Setup complete! To uninstall hooks run: 'pre-commit uninstall'\"\n"
  },
  {
    "path": "gitlab-ci/.gitlab-ci-build-macos.yml",
    "content": "# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2024 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n#-------------------------------------------------------------------------------\n# MacOS build - disabled waiting for a build machine\n#-------------------------------------------------------------------------------\n\n.macosx_dmg:\n  stage: build:rpm\n  script:\n    - git submodule sync --recursive && git submodule update --init -f --recursive\n    - version=`./genversion.sh`\n    - ./utils/eos-osx-package.sh $version\n    - ccache -s\n    - mkdir osx_artifacts\n    - cp build/*.dmg osx_artifacts\n  artifacts:\n    expire_in: 1 day\n    paths:\n      - osx_artifacts/\n  tags:\n    - macosx-shell\n  when: manual\n"
  },
  {
    "path": "gitlab-ci/.gitlab-ci-build-ubuntu.yml",
    "content": "# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2024 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n#-------------------------------------------------------------------------------\n# Ubuntu builds\n#-------------------------------------------------------------------------------\n\n.build-ubuntu: &build-ubuntu_definition\n  stage: build:rpm\n  variables:\n    DEBIAN_FRONTEND: noninteractive\n  script:\n    - apt-get update\n    - apt-get install -y git cmake g++ debhelper devscripts equivs gdebi-core ccache gawk wget\n    - wget -qO- http://storage-ci.web.cern.ch/storage-ci/storageci.key | tee /etc/apt/trusted.gpg.d/stci.asc\n    - echo -e \"\\ndeb http://storage-ci.web.cern.ch/storage-ci/debian/eos/${CODENAME} ${BUILD_NAME} ${BUILD_NAME}/tag\" >> /etc/apt/sources.list\n#    - wget -qO- https://xrootd.web.cern.ch/repo/RPM-GPG-KEY.txt | tee /etc/apt/trusted.gpg.d/xrootd.asc\n#    - echo -e \"\\ndeb https://xrootd.web.cern.ch/ubuntu ${BUILD_NAME} stable\" >> /etc/apt/sources.list.d/xrootd.list\n    - apt-get update\n    - git submodule update --init -f --recursive\n    # Priority >= 1000 causes a version to be installed even if this constitutes a downgrade of the package\n    - EOS_XROOTD_VERSION=$(grep \"define eos_xrootd_version_min\" eos.spec.in | awk -F ':' '{print $2;}')\n    - echo -e \"Package:\"\" eos-xrootd* libeosxrd* libeosxrootd*\\nPin:\"\" version $EOS_XROOTD_VERSION\\nPin-Priority:\"\" 1000\" > /etc/apt/preferences.d/eos-xrootd.pref\n    - if [[ \"${BUILD_NAME}\" == \"jammy\" ]]; then PROCPS_LIB=\"libprocps-dev\"; else PROCPS_LIB=\"libproc2-dev\"; fi\n    - sed -e \"s/_XRD_DEB_VER_/$EOS_XROOTD_VERSION/g; s/_PROCPS_TAG_/${PROCPS_LIB}/g\" debian/control.template > debian/control\n    - mk-build-deps --build-dep debian/control\n    - gdebi --n eos-build-deps-depends*.deb\n    - EOS_VERSION=\"$(./genversion.sh)\"; echo \"${EOS_VERSION}\"\n    - dch --create -v \"${EOS_VERSION}\" --package eos --urgency low --distribution ${BUILD_NAME} -M \"eos automated build.\"\n    - if [[ -n \"$CI_COMMIT_TAG\" ]]; then\n        export CCACHE_DISABLE=1;\n      else\n        source gitlab-ci/setup_ccache_deb.sh;\n        export GRPC_BUILD_ENABLE_CCACHE=1;\n      fi\n    - dpkg-buildpackage -b -us -uc -tc --buildinfo-option=\"-udeb_packages\" --changes-option=\"-udeb_packages\" --buildinfo-file=\"deb_packages/eos_${EOS_VERSION}_$(dpkg-architecture -qDEB_BUILD_ARCH).buildinfo\" --changes-file=\"deb_packages/eos_${EOS_VERSION}_$(dpkg-architecture -qDEB_BUILD_ARCH).changes\"\n    - ccache -s\n    - mkdir ${CI_JOB_NAME}; cp deb_packages/*.deb ${CI_JOB_NAME}\n  cache:\n    key: \"$CI_JOB_NAME-$CI_COMMIT_REF_SLUG\"\n    paths:\n      - ccache/\n  artifacts:\n    expire_in: 1 day\n    paths:\n      - ${CI_JOB_NAME}\n  dependencies: []\n  allow_failure: true\n  rules:\n    - if: $CI_COMMIT_TAG || $CI_PIPELINE_SOURCE == \"schedule\"\n    - when: manual\n\n\nubuntu-jammy:\n  extends: .build-ubuntu\n  image: registry.cern.ch/docker.io/ubuntu:jammy\n  variables:\n    BUILD_NAME: jammy\n\n\nubuntu-noble:\n  extends: .build-ubuntu\n  image: registry.cern.ch/docker.io/ubuntu:noble\n  variables:\n    BUILD_NAME: noble\n\n\nubuntu-noble-arm:\n  extends: .build-ubuntu\n  image: registry.cern.ch/docker.io/ubuntu:noble\n  variables:\n    BUILD_NAME: noble\n  tags:\n    - arm64\n\n\nubuntu-plucky:\n  extends: .build-ubuntu\n  image: registry.cern.ch/docker.io/ubuntu:plucky\n  variables:\n    BUILD_NAME: plucky\n\nubuntu-questing:\n  extends: .build-ubuntu\n  image: registry.cern.ch/docker.io/ubuntu:questing\n  variables:\n    BUILD_NAME: questing\n\n\n#-------------------------------------------------------------------------------\n# Ubuntu artifacts publishing\n#-------------------------------------------------------------------------------\n\npublish_debian:\n  stage: publish\n  image: registry.cern.ch/docker.io/ubuntu:noble\n  script:\n    - apt-get update\n    - apt-get install -y sudo apt-utils sssd reprepro\n    - mkdir /home/stci; chown -R stci:def-cg /home/stci; chmod -R 700 /home/stci;\n    - export GNUPGHOME=/home/stci\n    - if [[ -n \"$CI_COMMIT_TAG\" ]]; then BUILD_TYPE=tag; else BUILD_TYPE=commit; fi\n    - sudo -u stci -H -E gpg --import $STCI_REPO_KEY\n    - sudo -u stci -H -E ./gitlab-ci/publish_deb.sh ${BUILD_TYPE}\n  allow_failure: true\n  needs:\n    - job: ubuntu-jammy\n    - job: ubuntu-noble\n    - job: ubuntu-noble-arm\n    - job: ubuntu-plucky\n    - job: ubuntu-questing\n  tags:\n    - docker_node\n    - publish\n  rules:\n    - if: $CI_COMMIT_TAG || $CI_PIPELINE_SOURCE == \"schedule\"\n    - when: manual\n\n.clean_debian_artifacts:\n  stage: clean\n  image: registry.cern.ch/docker.io/ubuntu:noble\n  script:\n    - apt-get update\n    - apt-get install -y sudo apt-utils sssd gpg\n    - mkdir /home/stci; chown -R stci:def-cg /home/stci; chmod -R 700 /home/stci;\n    - export GNUPGHOME=/home/stci\n    - sudo -u stci -H gpg --import $STCI_REPO_KEY\n    - sudo -u stci -H -E ./gitlab-ci/remove_old_artifacts_debian.sh\n  allow_failure: true\n  only:\n    - schedules\n  tags:\n    - docker_node\n"
  },
  {
    "path": "gitlab-ci/.gitlab-ci-test-dock_include.yml",
    "content": "# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2023 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n## @note (faluchet)\n## The following is hidden not to clog the GitLab UI/UX.\n## The ratio behind archiving it here is that in case the\n## kubernetes setup fails for any reason, we want to run\n## the tests via the legacy docker environment, self-contained\n## in the CI runners. However, this possibility revealed to be\n## so unusual (once in a couple of year?) that it make sense\n## to hide it all at once. If ever needed, just un-hide the jobs\n## below by including this file in the main .gitlab-ci.yml and\n## push a new commit. Consider to drop it for good at your own convenience.\n\ndock_system:\n  extends: .dock8s_system_test_template\n  image: gitlab-registry.cern.ch/dss/docker-enhanced:19.03.12\n  services:\n    - docker:19.03.12-dind\n  variables:\n    DOCKER_TLS_CERTDIR: \"/certs\"\n  needs:\n    - job: cc7_docker_image\n      artifacts: false\n    - job: clone_docker\n  retry: 1\n  tags:\n    - docker_node\n    - dock\n  when: manual\n\ndock_conv_fsck_recycle:\n  extends: .dock8s_convert_fsck_recycle_template\n  image: gitlab-registry.cern.ch/dss/docker-enhanced:19.03.12\n  services:\n    - docker:19.03.12-dind\n  variables:\n    DOCKER_TLS_CERTDIR: \"/certs\"\n  needs:\n    - job: cc7_docker_image\n      artifacts: false\n    - job: clone_docker\n  retry: 1\n  tags:\n    - docker_node\n    - dock\n  when: manual\n\ndock_rtb_clone:\n  extends: .dock8s_rtb_clone_template\n  image: gitlab-registry.cern.ch/dss/docker-enhanced:19.03.12\n  services:\n    - docker:19.03.12-dind\n  variables:\n    DOCKER_TLS_CERTDIR: \"/certs\"\n  needs:\n    - job: cc7_docker_image\n      artifacts: false\n    - job: clone_docker\n  retry: 1\n  tags:\n    - docker_node\n    - dock\n  when: manual\n\ndock_fusex:\n  extends: .dock8s_fusex_test_template\n  image: gitlab-registry.cern.ch/dss/docker-enhanced:19.03.12\n  services:\n    - docker:19.03.12-dind\n  variables:\n    DOCKER_TLS_CERTDIR: \"/certs\"\n  needs:\n    - job: cc7_docker_image\n      artifacts: false\n    - job: clone_docker\n  retry: 1\n  tags:\n    - docker_node\n    - dock\n  when: manual\n\ndock_cbox:\n  extends: .dock8s_cbox_test_template\n  image: gitlab-registry.cern.ch/dss/docker-enhanced:19.03.12\n  services:\n    - docker:19.03.12-dind\n  variables:\n    DOCKER_TLS_CERTDIR: \"/certs\"\n  needs:\n    - job: cc7_docker_image\n      artifacts: false\n    - job: clone_docker\n  tags:\n    - docker_node\n    - dock\n  when: manual\n\n  dock_reva:\n    extends: .dock8s_reva_test_template\n    image: gitlab-registry.cern.ch/dss/docker-enhanced:19.03.12\n    services:\n      - docker:19.03.12-dind\n    variables:\n      DOCKER_TLS_CERTDIR: \"/certs\"\n    needs:\n      - job: cc7_docker_image\n        artifacts: false\n      - job: clone_docker\n    tags:\n      - docker_node\n      - dock\n    when: manual\n\ndock_stress:\n  stage: test\n  image: gitlab-registry.cern.ch/dss/docker-enhanced:19.03.12\n  services:\n    - docker:19.03.12-dind\n  variables:\n    DOCKER_TLS_CERTDIR: \"/certs\"\n  before_script:\n    - !reference [.dock8s_before_script_template, before_script]\n#  <<: *dock8s_before_script_template\n  script:\n    - TEST_URL=\"eos-mgm1.eoscluster.cern.ch\"\n    - exec_cmd eos-mgm1 \"echo -e \\\"[grid-hammer]\\nname=grid-hammer continuous builds for master\\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/grid-hammer/xrootd5/master/el7/x86_64/\\ngpgcheck=0\\nenabled=1\\nprotect=1\\npriority=20\\n\\\" > /etc/yum.repos.d/grid-hammer.repo\"\n    - exec_cmd eos-mgm1 \"yum install -y grid-hammer\"\n    - exec_cmd eos-mgm1 \"hammer-runner.py --strict-exit-code 1 --gitlab --url ${TEST_URL}//eos/dockertest/hammer/ --protocols xroot --threads 1 2 10 100 --operations write stat read delete --runs 3 --nfiles 10000\"\n  after_script:\n    - !reference [.dock8s_after_script_template, after_script]\n#   <<: *dock8s_after_script_template\n  needs:\n    - job: cc7_docker_image\n      artifacts: false\n    - job: clone_docker\n  retry: 1\n  tags:\n    - docker_node\n    - dock\n  when: manual\n\n# schedules ------------------------------\n\ndock_fusex_ub_jammy:\n  extends: .dock8s_fusex_test_template\n  image: gitlab-registry.cern.ch/dss/docker-enhanced:19.03.12\n  services:\n    - docker:19.03.12-dind\n  variables:\n    DOCKER_TLS_CERTDIR: \"/certs\"\n    CLI_BASETAG: \"ubuntu_jammy_client_\"\n  needs:\n    - job: cc7_docker_image\n      artifacts: false\n    - job: ubuntu_jammy_docker_image\n      artifacts: false\n    - job: clone_docker\n  allow_failure: true\n  retry: 1\n  only:\n    - schedules\n    - tags\n  tags:\n    - docker_node\n    - dock\n  when: manual\n\n# @todo Re-enable xrd_testing jobs once project ugprades to XRootD 5\n# xt stands for xrd_testing. Must shorten to not hit HOST_NAME_MAX\ndock_system_xt:\n  extends: .dock8s_system_test_template\n  image: gitlab-registry.cern.ch/dss/docker-enhanced:19.03.12\n  services:\n    - docker:19.03.12-dind\n  variables:\n    DOCKER_TLS_CERTDIR: \"/certs\"\n    BASETAG: \"xrd_testing_\"\n  needs:\n    - job: cc7_xrd_testing_docker_image\n      artifacts: false\n    - job: clone_docker\n  allow_failure: true\n  retry: 1\n  only:\n    - schedules\n  tags:\n    - docker_node\n    - dock\n  when: manual\n"
  },
  {
    "path": "gitlab-ci/.gitlab-ci-test-helm-server-multigroup-values.yml",
    "content": "# eos-chart/server\n# --set global.repository=$IMAGE_REPO --set global.tag=$IMAGE_TAG --set fst.replicaCount=8 --set fst.selfRegister.groupsize=8 --set fst.selfRegister.groupmod=1 \\\n# --set mgm.kerberos.enable=true --set mgm.kerberos.admin_princ.name=$TEST_ADMINPRINC_NAME --set mgm.kerberos.admin_princ.password=$TEST_ADMINPRINC_PASSWORD\n\nglobal:\n  repository: gitlab-registry.cern.ch/dss/eos/eos-ci\n  tag: \"--set-string $IMAGE_TAG\"\n  keytab:\n    # matches the fusex chart value\n    secret: eos-sss-keytab\n  securityContext:\n    privileged: true\n    allowPrivilegeEscalation: true\n\nqdb:\n  replicaCount: 1\n\nfst:\n  replicaCount: 15\n  minFsSizeGb: 1\n  selfRegister:\n    groupsize: 7\n    groupmod: 2\n\nmgm:\n  env:\n    - name: LD_PRELOAD\n      value: \"/usr/lib64/libjemalloc.so\"\n"
  },
  {
    "path": "gitlab-ci/.gitlab-ci-test-helm_fusex_values.yml",
    "content": "# eos-chart fusex\n#--set image.tag=$CLI_IMAGE_TAG --set fusex.config.eos_mgm_alias=eos-mgm.$K8S_NAMESPACE.svc.cluster.local --set customLabels.component=$client1_label \\\n#--set fusex.config.auth.sss=0 --set fusex.config.auth.krb5=1\n## --set kerberos.admin_princ.name=$TEST_ADMINPRINC_NAME --set kerberos.admin_princ.password=$TEST_ADMINPRINC_PASSWORD # put in the env ?\n\nimage:\n  repository: gitlab-registry.cern.ch/dss/eos/eos-ci\n  tag: \"--set $CLI_IMAGE_TAG\"\n\ndeploymentKind: Deployment\n\nfusex:\n  hostPID: false\n  enableHostMountpoint: false\n  keytab:\n    # matches the default utils.sssKeytabName from the eos server chart\n    secret: eos-sss-keytab\n  kerberos:\n    enabled: true\n    clientConfig:\n      # matches the default kerberos.fullname from the kuberos chart\n      configMap: kuberos-kuberos-krb5-config\n\n  config:\n    eos_mgm_alias: \"--set eos-mgm.$K8S_NAMESPACE.svc.cluster.local\"\n    auth: # parsing is bugged! 'sss: 0' and 'krb5: 1' via --set on helm install\n      sss: \"--set 1\"\n      krb5: \"--set 1\"\n"
  },
  {
    "path": "gitlab-ci/.gitlab-ci-test-helm_include.yml",
    "content": "# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2023 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n# @todo consider to move the variables in the global 'variable' yaml keymap, not sure it works with ever-changing content\n.helm_before_script_template: &helm_before_script_template\n  # Setup a KDC server, an EOS instance, and an EOS client with an eos-fusex mount\n  before_script:\n    - |\n      chmod 600 $K8S_CONFIG\n      export KUBECONFIG=$K8S_CONFIG # get access configs for the cluster\n      K8S_NAMESPACE=$(echo ${CI_JOB_NAME}-${CI_JOB_ID}-${CI_PIPELINE_ID} | tr '_' '-' | tr '[:upper:]' '[:lower:]')\n      kubectl config set-context --current --namespace=$K8S_NAMESPACE\n      IMAGE_TAG=\"${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}${OS_TAG}\"\n      CLI_IMAGE_TAG=\"${CLI_BASETAG}${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}${OS_TAG}\" # may be different from the server image (eg ubuntu)\n      TEST_REALM=EXAMPLE.COM; TEST_ADMINPRINC_NAME=admin1; TEST_ADMINPRINC_PASSWORD=admin1; TEST_USERPRINC_NAME=eos-user; TEST_USERPRINC_PASSWORD=eos-user\n    - |\n      git clone https://gitlab.cern.ch/eos/kuberos\n      helm repo add eos https://registry.cern.ch/chartrepo/eos\n      HELM_EOS_SERVER_VERSION=0.1.7\n      HELM_EOS_FUSEX_VERSION=0.1.0\n    - |\n      echo \"Clone eos-on-k8s repository for helper scripts\"\n      git clone https://gitlab.cern.ch/eos/eos-on-k8s.git\n    - |\n      ######## KUBEROS ########\n      helm install -f ./gitlab-ci/.gitlab-ci-test-helm_kuberos_values.yml kuberos ./kuberos/kuberos/ --atomic --create-namespace --namespace $K8S_NAMESPACE\n      sleep 10 # kadmind not quite ready by the time the following triggers. Perform the task of the kuberos k8s Job post-install hook since here we set \"admin_princ.enabled=false\"\n      kubectl exec kuberos-kuberos-kdc-0 -c kdc -- /usr/sbin/kadmin.local -r $TEST_REALM addprinc -pw $TEST_ADMINPRINC_PASSWORD $TEST_ADMINPRINC_NAME/admin\n      kubectl exec kuberos-kuberos-kdc-0 -c kdc -- /usr/sbin/kadmin.local -r $TEST_REALM addprinc -pw $TEST_USERPRINC_PASSWORD $TEST_USERPRINC_NAME\n    - |\n      ######## EOS-CHARTS/SERVER ########\n      helm install --wait --timeout 20m0s \\\n        -f ./gitlab-ci/.gitlab-ci-test-helm_server_values.yml eos eos/server \\\n        --version $HELM_EOS_SERVER_VERSION \\\n        --atomic --create-namespace --namespace $K8S_NAMESPACE \\\n        --set-string global.tag=$IMAGE_TAG \\\n        --set mgm.kerberos.adminPrinc.name=$TEST_ADMINPRINC_NAME \\\n        --set mgm.kerberos.adminPrinc.password=$TEST_ADMINPRINC_PASSWORD\n    - |\n      ######## EOS-CHARTS/FUSEX ########\n      helm install -f ./gitlab-ci/.gitlab-ci-test-helm_fusex_values.yml eos-client1 eos/fusex \\\n        --version $HELM_EOS_FUSEX_VERSION \\\n        --atomic --create-namespace --namespace $K8S_NAMESPACE \\\n        --set-string image.tag=$CLI_IMAGE_TAG \\\n        --set fusex.config.eos_mgm_alias=eos-mgm.$K8S_NAMESPACE.svc.cluster.local \\\n        --set fusex.config.auth.sss=1 \\\n        --set fusex.config.auth.krb5=1\n    - |\n      # allow backward compatibility with our legacy k8s utilities functions\n      for i in $(seq 1 15); do kubectl label pod eos-fst-$((i -1)) app=eos-fst${i}; done\n      kubectl label pod eos-mgm-0 app=eos-mgm1\n      kubectl label pod eos-qdb-0 app=eos-qdb\n      client1_pod=\"$(kubectl get pods --no-headers -o custom-columns=':metadata.name' | grep 'eos-client1')\"\n      kubectl label pod $client1_pod app=eos-cli1\n      kubectl exec eos-mgm-0 -- eos fs ls\n\n.helm_groups_before_script_template: &helm_groups_before_script_template\n  before_script:\n    - |\n      chmod 600 $K8S_CONFIG\n      export KUBECONFIG=$K8S_CONFIG # get access configs for the cluster\n      K8S_NAMESPACE=$(echo ${CI_JOB_NAME}-${CI_JOB_ID}-${CI_PIPELINE_ID} | tr '_' '-' | tr '[:upper:]' '[:lower:]')\n      kubectl config set-context --current --namespace=$K8S_NAMESPACE\n      # Server image used for the EOS daemons\n      IMAGE_TAG=\"${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}${OS_TAG}\"\n      # Client image may be different from the server image (eg. ubuntu)\n      CLI_IMAGE_TAG=\"${CLI_BASETAG}${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}${OS_TAG}\"\n      echo \"IMAGE_TAG=${IMAGE_TAG}\"\n      echo \"CLI_IMAGE_TAG=${CLI_IMAGE_TAG}\"\n    - |\n      echo \"Clone eos-on-k8s repository for helper scripts\"\n      git clone https://gitlab.cern.ch/eos/eos-on-k8s.git\n    - |\n      ######## EOS-CHARTS/SERVER ########\n      HELM_EOS_SERVER_VERSION=0.9.2\n      HELM_EOS_SERVER_REGISTRY=oci://registry.cern.ch/eos/charts/server\n      helm install eos $HELM_EOS_SERVER_REGISTRY --wait --timeout 30m0s \\\n        -f ./gitlab-ci/.gitlab-ci-test-helm-server-multigroup-values.yml \\\n        --version $HELM_EOS_SERVER_VERSION \\\n        --atomic --create-namespace --namespace $K8S_NAMESPACE \\\n        --set-string global.tag=$IMAGE_TAG\n    - |\n      # Allow backward compatibility with our legacy k8s utilities functions\n      for i in $(seq 1 15); do kubectl label pod eos-fst-$((i -1)) app=eos-fst${i}; done\n      kubectl label pod eos-mgm-0 app=eos-mgm1\n      kubectl label pod eos-qdb-0 app=eos-qdb\n\n\n.helm_after_script_template: &helm_after_script_template\n  after_script:\n    - |\n      chmod 600 $K8S_CONFIG\n      export KUBECONFIG=$K8S_CONFIG # get access configs for the cluster\n      export K8S_NAMESPACE=$(echo ${CI_JOB_NAME}-${CI_JOB_ID}-${CI_PIPELINE_ID} | tr '_' '-' | tr '[:upper:]' '[:lower:]')\n      ./eos-on-k8s/collect_logs.sh ${K8S_NAMESPACE} eos-logs-${CI_JOB_ID} || true\n      helm uninstall eos-client1 --namespace $K8S_NAMESPACE || true\n      helm uninstall eos --namespace $K8S_NAMESPACE || true\n      helm uninstall kuberos --namespace $K8S_NAMESPACE || true\n      kubectl delete namespace $K8S_NAMESPACE\n      # Paranoid:\n      # helm template eos-client1 ./eos-charts/fusex --namespace $K8S_NAMESPACE | kubectl delete -f -\n      # helm template eos ./eos-charts/server --namespace $K8S_NAMESPACE | kubectl delete -f -\n      # helm template kuberos ./kuberos/kuberos/ --namespace $K8S_NAMESPACE | kubectl delete -f -\n      rm -rf kuberos/ eos-charts/\n\n\n.helm_cbox_test_template:\n  stage: test\n  image: gitlab-registry.cern.ch/dss/alpine-enhanced:3.22.0\n  <<: [*helm_before_script_template, *helm_after_script_template]\n  script:\n    # @note need to distinguish between:\n    #       - 'this' shell environment, to access things like TEST_ADMINPRINC_PASSWORD : use kubectl exec ...\n    #       - eos-cli1 eos-user's environment, to set KRB5CCNAME and make it source-able from his HOME : use exec_cmd (but we shall get rid of it)\n    - source ./gitlab-ci/utilities_func_for_tests.sh --type k8s $K8S_NAMESPACE\n    # enable converter and prepare eoshome folder, cernbox alike\n    - kubectl exec eos-mgm-0 -- eos convert config set status=on\n    - kubectl exec eos-mgm-0 -- ./eos_create_userhome.sh eos-user\n    # the eos-user user gets his keytab ...\n    - kubectl exec $client1_pod -- su eos-user -c \"echo $TEST_ADMINPRINC_PASSWORD | kadmin -r $TEST_REALM -p $TEST_ADMINPRINC_NAME/admin ktadd -k /tmp/eos-user.keytab eos-user\"\n    # ... and uses it to auth. @note eos-user needs to have KRB5CCNAME set. KRB5CCNAME takes precedence over the kuberos default_ccache_name (see krb5.conf).\n    - exec_cmd eos-cli1 'echo -e \"export KRB5CCNAME=FILE:/tmp/krb5cc_$(id -u eos-user)\" >> ~/.bashrc'\n    - exec_cmd eos-cli1 'su eos-user -c \"kinit eos-user -k -t /tmp/eos-user.keytab && klist\"'\n    # download and launch the tests\n    - exec_cmd eos-cli1 'su eos-user -c \"git clone https://gitlab.cern.ch/dss/eosclient-tests.git /eos/user/e/eos-user/eosclient-tests\"'\n    - exec_cmd eos-cli1 'su eos-user -c \"cd /eos/user/e/eos-user && python3 ./eosclient-tests/run.py --workdir=/eos/user/e/eos-user regression\"'\n    - exec_cmd eos-cli1 'su eos-user -c \"cd /eos/user/e/eos-user && python3 ./eosclient-tests/run.py --workdir=/eos/user/e/eos-user ci-eosfuse_release\"'\n  artifacts:\n    when: on_failure\n    expire_in: 3 days\n    paths:\n      - eos-logs-${CI_JOB_ID}/\n\nhelm_cbox:\n  extends: .helm_cbox_test_template\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n  when: manual\n\nhelm_conv_fsck_recycle:\n  stage: test\n  image: gitlab-registry.cern.ch/dss/alpine-enhanced:3.22.0\n  <<: [*helm_before_script_template, *helm_after_script_template]\n  script:\n    - source ./gitlab-ci/utilities_func_for_tests.sh --type k8s $K8S_NAMESPACE\n    - cp_to_local_cmd eos-cli1:/usr/sbin/eos-test-utils ./eos-test-utils\n    # converter\n    - kubectl exec eos-mgm-0 -- eos vid set membership 2 +sudo\n    - kubectl exec eos-mgm-0 -- eos chmod 2777 /eos/dockertest\n    - cp_to_local_cmd eos-cli1:/usr/sbin/eos-converter-test ./eos-converter-test; chmod +x eos-converter-test\n    - ./eos-converter-test --type k8s $K8S_NAMESPACE\n    - rm -rf eos-converter-test\n    # fsck\n    - cp_to_local_cmd eos-cli1:/usr/sbin/eos-fsck-test ./eos-fsck-test; chmod +x eos-fsck-test\n    - ./eos-fsck-test --max-delay 600 --type k8s $K8S_NAMESPACE\n    # recycle\n    - cp_to_local_cmd eos-cli1:/usr/sbin/eos-recycle-test ./eos-recycle-test; chmod +x eos-recycle-test\n    - ./eos-recycle-test --type k8s $K8S_NAMESPACE\n    # cleanup\n    - rm -rf eos-test-utils\n    - rm -rf eos-converter-utils\n    - rm -rf eos-fsck-test\n    - rm -rf eos-recycle-test\n\n  artifacts:\n    when: on_failure\n    expire_in: 3 days\n    paths:\n      - eos-logs-${CI_JOB_ID}/\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n  when: manual\n\n.helm_fusex_test_template:\n  stage: test\n  image: gitlab-registry.cern.ch/dss/alpine-enhanced:3.22.0\n  <<: [*helm_before_script_template, *helm_after_script_template]\n  script:\n    - source ./gitlab-ci/utilities_func_for_tests.sh --type k8s $K8S_NAMESPACE\n    # prepare mountpoints\n    - kubectl exec $client1_pod -- bash -c \"sed -i \\\"s/eos-mgm1.eoscluster.cern.ch/eos-mgm.$K8S_NAMESPACE.svc.cluster.local/g\\\" /etc/eos/fuse.mount-1.conf\"\n    - kubectl exec $client1_pod -- bash -c \"sed -i \\\"s/eos-mgm1.eoscluster.cern.ch/eos-mgm.$K8S_NAMESPACE.svc.cluster.local/g\\\" /etc/eos/fuse.mount-2.conf\"\n    - exec_cmd eos-cli1 'atd; at now <<< \"mkdir -p /eos1/ && mount -t fuse eosxd -ofsname=mount-1 /eos1/; mkdir -p /eos2/ && mount -t fuse eosxd -ofsname=mount-2 /eos2/;\"'\n    - exec_cmd eos-cli1 'count=0; while [[ $count -le 10 ]] && ( [[ ! -d /eos1/dockertest/ ]] || [[ ! -d /eos2/dockertest/ ]] ); do echo \"Wait for mount... $count\"; (( count++ )); sleep 1; done;'\n    # the eos-user user gets his keytab ...\n    - kubectl exec $client1_pod -- su eos-user -c \"echo $TEST_ADMINPRINC_PASSWORD | kadmin -r $TEST_REALM -p $TEST_ADMINPRINC_NAME/admin ktadd -k /tmp/eos-user.keytab eos-user\"\n    # ... and uses it to auth. @note eos-user needs to have KRB5CCNAME set. KRB5CCNAME takes precedence over the kuberos default_ccache_name (see krb5.conf).\n    - exec_cmd eos-cli1 'echo -e \"export KRB5CCNAME=FILE:/tmp/krb5cc_$(id -u eos-user)\" >> ~/.bashrc'\n    - exec_cmd eos-cli1 'su eos-user -c \"kinit eos-user -k -t /tmp/eos-user.keytab && klist\"'\n    # run the fusex-benchmark test\n    - kubectl exec eos-mgm-0 -- eos chmod 2777 /eos/dockertest\n    - exec_cmd eos-cli1  'su eos-user -c \"mkdir -p /eos1/dockertest/fusex_tests/ && cd /eos1/dockertest/fusex_tests/ && fusex-benchmark\"'\n    # download and launch the ci tests\n    - exec_cmd eos-cli1 'git clone https://gitlab.cern.ch/dss/eosclient-tests.git'\n    # @todo(esindril): run \"all\" tests in schedule mode once these are properly supported\n    # if [[ \"$CI_PIPELINE_SOURCE\" == \"schedule\" ]];\n    # then\n    #   exec_cmd eos-mgm1 'eos vid add gateway \"eos-cli1.eos-cli1.${K8S_NAMESPACE}.svc.cluster.local\" unix';\n    #   exec_cmd eos-cli1 'env EOS_FUSE_NO_ROOT_SQUASH=1 python /eosclient-tests/run.py --workdir=\"/eos1/dockertest /eos2/dockertest\" ci';\n    # fi\n    # until then just run the \"ci\" tests\n    - exec_cmd eos-cli1 'cd eosclient-tests; for n in prepare/*.sh; do /bin/bash $n prepare; done'\n    - exec_cmd eos-cli1 'su eos-user -c \"python3 /eosclient-tests/run.py --workdir=\\\"/eos1/dockertest /eos2/dockertest\\\" ci\"'\n    - exec_cmd eos-cli1 'cd eosclient-tests; for n in prepare/*.sh; do /bin/bash $n cleanup; done'\n  artifacts:\n    when: on_failure\n    expire_in: 3 days\n    paths:\n      - eos-logs-${CI_JOB_ID}/\n  allow_failure: true\n\nhelm_fusex:\n  extends: .helm_fusex_test_template\n  needs:\n   - job: el9_docker_image\n     artifacts: false\n  when: manual\n\n.helm_fusex_ub_focal:\n  extends: .helm_fusex_test_template\n  variables:\n    CLI_BASETAG: \"ubuntu_focal_client_\"\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n    - job: ubuntu_focal_docker_image\n      artifacts: false\n  only:\n    - schedules\n    - tags\n  allow_failure: true\n  retry: 1\n  when: manual\n\n.helm_fusex_ub_jammy:\n  extends: .helm_fusex_test_template\n  variables:\n    CLI_BASETAG: \"ubuntu_jammy_client_\"\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n    - job: ubuntu_jammy_docker_image\n      artifacts: false\n  only:\n    - eosclient-tests-python3\n    - schedules\n    - tags\n  allow_failure: true\n  retry: 1\n  when: manual\n\nhelm_rtb_clone:\n  stage: test\n  image: gitlab-registry.cern.ch/dss/alpine-enhanced:3.22.0\n  <<: [*helm_before_script_template, *helm_after_script_template]\n  script:\n    - source ./gitlab-ci/utilities_func_for_tests.sh --type k8s $K8S_NAMESPACE\n    # prepare two further artificial mountpoints\n    - kubectl exec $client1_pod -- bash -c \"sed -i \\\"s/eos-mgm1.eoscluster.cern.ch/eos-mgm.$K8S_NAMESPACE.svc.cluster.local/g\\\" /etc/eos/fuse.mount-1.conf\"\n    - kubectl exec $client1_pod -- bash -c \"sed -i \\\"s/eos-mgm1.eoscluster.cern.ch/eos-mgm.$K8S_NAMESPACE.svc.cluster.local/g\\\" /etc/eos/fuse.mount-2.conf\"\n    - exec_cmd eos-cli1 'atd; at now <<< \"mkdir -p /eos1/ && mount -t fuse eosxd -ofsname=mount-1 /eos1/; mkdir -p /eos2/ && mount -t fuse eosxd -ofsname=mount-2 /eos2/;\"'\n    - exec_cmd eos-cli1 'count=0; while [[ $count -le 10 ]] && ( [[ ! -d /eos1/dockertest/ ]] || [[ ! -d /eos2/dockertest/ ]] ); do echo \"Wait for mount... $count\"; (( count++ )); sleep 1; done;'\n    # download tests repo\n    - exec_cmd eos-cli1 'git clone https://gitlab.cern.ch/dss/eosclient-tests.git'\n    # run the clone test (please remember that ubuntu releases do not support 'clone' yet)\n    - kubectl exec eos-mgm-0 -- eos chmod 2777 /eos/dockertest\n    - exec_cmd eos-cli1 'cd /eosclient-tests; clone_tests/clone_test.sh prepare; rc=$?; exit $rc'\n  artifacts:\n    when: on_failure\n    expire_in: 3 days\n    paths:\n      - eos-logs-${CI_JOB_ID}/\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n  allow_failure: true\n  when: manual\n\nhelm_stress:\n  stage: test\n  image: gitlab-registry.cern.ch/dss/alpine-enhanced:3.22.0\n  <<: [*helm_before_script_template, *helm_after_script_template]\n  script:\n    - TEST_URL=$(kubectl exec eos-mgm-0 -- hostname -f)\n    - |\n      kubectl exec -i eos-mgm-0 -- bash -c 'cat > /etc/yum.repos.d/grid-hammer.repo' <<'EOF'\n      [grid-hammer]\n      name=grid-hammer continuous builds for master\n      baseurl=http://storage-ci.web.cern.ch/storage-ci/grid-hammer/xrootd5/master/el9/x86_64/\n      gpgcheck=0\n      enabled=1\n      protect=1\n      priority=20\n      EOF\n    - kubectl exec eos-mgm-0 -- wget --directory-prefix=/etc/yum.repos.d/ https://cern.ch/xrootd/xrootd.repo\n    - kubectl exec eos-mgm-0 -- yum install -y strace grid-hammer\n    - kubectl exec eos-mgm-0 -- eos chmod 2777 /eos/dockertest\n    - kubectl exec eos-mgm-0 -- hammer-runner.py --strict-exit-code 1 --gitlab --url ${TEST_URL}//eos/dockertest/hammer/ --protocols xroot --threads 1 2 10 100 --operations write stat read delete --runs 3 --nfiles 10000\n  artifacts:\n    when: on_failure\n    expire_in: 3 days\n    paths:\n      - eos-logs-${CI_JOB_ID}/\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n  when: manual\n\n.helm_system_test_template:\n  stage: test\n  image: gitlab-registry.cern.ch/dss/alpine-enhanced:3.22.0\n  <<: [*helm_before_script_template, *helm_after_script_template]\n  script:\n    - kubectl exec eos-mgm-0 -- eos-instance-test-ci localhost\n    - kubectl exec eos-mgm-0 -- eos chmod 2777 /eos/dockertest\n    - kubectl exec eos-mgm-0 -- eos-unit-tests-with-instance -n root://localhost//eos/dockertest/\n  artifacts:\n    when: on_failure\n    expire_in: 3 days\n    paths:\n      - eos-logs-${CI_JOB_ID}/\n\nhelm_system:\n  extends: .helm_system_test_template\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n  when: manual\n\nhelm_groupdrainer:\n  stage: test\n  image: gitlab-registry.cern.ch/dss/alpine-enhanced:3.22.0\n  variables:\n    OS_TAG: \".el9\"\n    FF_SCRIPT_SECTIONS: \"true\"\n  <<: [*helm_groups_before_script_template, *helm_after_script_template]\n  script:\n    - TEST_URL=$(kubectl exec eos-mgm-0 -- hostname -f)\n    - kubectl exec eos-mgm-0 -- eos chmod 2777 /eos/dockertest\n    - kubectl exec eos-mgm-0 -- eos-groupdrain-test ${TEST_URL}\n  artifacts:\n    when: on_failure\n    expire_in: 3 days\n    paths:\n      - eos-logs-${CI_JOB_ID}/\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n\nhelm_traffic_shaping:\n  stage: test\n  image: gitlab-registry.cern.ch/dss/alpine-enhanced:3.22.0\n  variables:\n    OS_TAG: \".el9\"\n    FF_SCRIPT_SECTIONS: \"true\"\n  <<: [*helm_groups_before_script_template, *helm_after_script_template]\n  script:\n    - TEST_URL=$(kubectl exec eos-mgm-0 -- hostname -f)\n    - kubectl exec eos-mgm-0 -- eos chmod 2777 /eos/dockertest\n    - kubectl exec eos-mgm-0 -- eos-traffic-shaping-test ${TEST_URL} dockertest\n  artifacts:\n    when: on_failure\n    expire_in: 3 days\n    paths:\n      - eos-logs-${CI_JOB_ID}/\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n\n\n# @todo Re-enable xrd_testing jobs once project ugprades to XRootD 5\n# xt stands for xrd_testing. Must shorten to not hit HOST_NAME_MAX\n.helm_system_xt:\n  extends: .helm_system_test_template\n  variables:\n    # BASETAG: \"xrd_testing_\" TO BE REVIEWED!\n  needs:\n    - job: cc7_xrd_testing_docker_image\n      artifacts: false\n  only:\n    - schedules\n  allow_failure: true\n"
  },
  {
    "path": "gitlab-ci/.gitlab-ci-test-helm_kuberos_values.yml",
    "content": "# kuberos\n# --set kdc.persistence.enabled=false --set admin_princ.enabled=false --set serviceAccount.create=false\n\nserviceAccount:\n  create: false\n\nkdc:\n  persistence:\n    enabled: false\n\nadmin_princ:\n  enabled: false\n"
  },
  {
    "path": "gitlab-ci/.gitlab-ci-test-helm_server_values.yml",
    "content": "# eos-chart/server\n# --set global.repository=$IMAGE_REPO --set global.tag=$IMAGE_TAG --set fst.replicaCount=8 --set fst.selfRegister.groupsize=8 --set fst.selfRegister.groupmod=1 \\\n# --set mgm.kerberos.enable=true --set mgm.kerberos.admin_princ.name=$TEST_ADMINPRINC_NAME --set mgm.kerberos.admin_princ.password=$TEST_ADMINPRINC_PASSWORD\n\nglobal:\n  repository: gitlab-registry.cern.ch/dss/eos/eos-ci\n  tag: \"--set-string $IMAGE_TAG\"\n  keytab:\n    # matches the fusex chart value\n    secret: eos-sss-keytab\n  http:\n    enabled: true\n  securityContext:\n    privileged: true\n    allowPrivilegeEscalation: true\n\nqdb:\n  replicaCount: 1\n\nfst:\n  replicaCount: 15\n  minFsSizeGb: 1\n  selfRegister:\n    groupsize: 7\n    groupmod: 2\n  extraEnv:\n    - name: EOS_FST_TESTING\n      value: \"1\"\n    - name: EOS_FST_HTTP_PORT\n      value: \"8443\"\n\nmgm:\n  kerberos:\n    enabled: true\n    clientConfig:\n      # matches the default kerberos.fullname from the kuberos chart\n      configMap: kuberos-kuberos-krb5-config\n    adminPrinc:\n      name: \"--set $TEST_ADMINPRINC_NAME\"\n      password: \"--set $TEST_ADMINPRINC_PASSWORD\"\n"
  },
  {
    "path": "gitlab-ci/.gitlab-ci-test-k8s_include.yml",
    "content": "# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2023 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nk8s_system:\n  extends: .dock8s_system_test_template\n  image: alpine/k8s:1.18.2\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n  retry: 1\n  tags:\n    - docker_node\n    - k8s\n  allow_failure: true\n\n\nk8s_system_flatscheduler:\n  extends: .dock8s_system_test_template\n  variables:\n    EOS_SCHEDULER_TYPE: weightedrandom\n  image: alpine/k8s:1.18.2\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n  retry: 1\n  tags:\n    - docker_node\n    - k8s\n  allow_failure: true\n\n\nk8s_conv_fsck_recycle:\n  extends: .dock8s_convert_fsck_recycle_template\n  image: alpine/k8s:1.18.2\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n  retry: 1\n  tags:\n    - docker_node\n    - k8s\n\n\nk8s_rtb_clone:\n  extends: .dock8s_rtb_clone_template\n  image: alpine/k8s:1.18.2\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n  retry: 1\n  allow_failure: true\n  tags:\n    - docker_node\n    - k8s\n\n\nk8s_fusex:\n  extends: .dock8s_fusex_test_template\n  image: alpine/k8s:1.18.2\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n  retry: 1\n  allow_failure: true\n  tags:\n    - docker_node\n    - k8s\n\nk8s_flamegraph:\n  extends: .dock8s_flamegraph_test_template\n  image: alpine/k8s:1.18.2\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n  allow_failure: true\n  tags:\n    - docker_node\n    - k8s\n\n\nk8s_cbox:\n  extends: .dock8s_cbox_test_template\n  image: alpine/k8s:1.18.2\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n  tags:\n    - docker_node\n    - k8s\n\nk8s_reva:\n  extends: .dock8s_reva_test_template\n  image: registry.cern.ch/docker.io/alpine/k8s:1.32.1\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n  tags:\n    - docker_node\n    - k8s\n\nk8s_stress:\n  extends:\n    - .dock8s_before_script_template\n    - .dock8s_after_script_template\n  image: alpine/k8s:1.18.2\n  script:\n    - TEST_URL=\"eos-mgm1.eos-mgm1.$K8S_NAMESPACE.svc.cluster.local\"\n    - exec_cmd eos-mgm1 \"echo -e \\\"[grid-hammer]\\nname=grid-hammer continuous builds for master\\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/grid-hammer/xrootd5/master/el9/x86_64/\\ngpgcheck=0\\nenabled=1\\nprotect=1\\npriority=20\\n\\\" > /etc/yum.repos.d/grid-hammer.repo\"\n    - exec_cmd eos-mgm1 \"wget --directory-prefix=/etc/yum.repos.d/ https://cern.ch/xrootd/xrootd.repo\"\n    - exec_cmd eos-mgm1 \"yum install -y strace grid-hammer\"\n    - exec_cmd eos-mgm1 \"hammer-runner.py --strict-exit-code 1 --gitlab --url ${TEST_URL}//eos/dockertest/hammer/ --protocols xroot --threads 1 2 10 100 --operations write stat read delete --runs 3 --nfiles 10000\"\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n  retry: 1\n  allow_failure: true\n  artifacts:\n    when: on_failure\n    expire_in: 3 days\n    paths:\n      - eos-logs-${CI_JOB_ID}/\n  tags:\n    - docker_node\n    - k8s\n\n\n# schedules ------------------------------\n\n.k8s_fusex_ub_focal:\n  extends: .dock8s_fusex_test_template\n  image: alpine/k8s:1.18.2\n  variables:\n    CLI_BASETAG: \"ubuntu_focal_client_\"\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n    - job: ubuntu_focal_docker_image\n      artifacts: false\n  retry: 1\n  allow_failure: true\n  only:\n    - schedules\n    - tags\n  tags:\n    - docker_node\n    - k8s\n\n\n.k8s_fusex_ub_jammy:\n  extends: .dock8s_fusex_test_template\n  image: alpine/k8s:1.18.2\n  variables:\n    CLI_BASETAG: \"ubuntu_jammy_client_\"\n  needs:\n    - job: el9_docker_image\n      artifacts: false\n    - job: ubuntu_jammy_docker_image\n      artifacts: false\n  retry: 1\n  allow_failure: true\n  only:\n    - schedules\n    - tags\n  tags:\n    - docker_node\n    - k8s\n\n\n# @todo Re-enable xrd_testing jobs once project ugprades to XRootD 5\n# xt stands for xrd_testing. Must shorten to not hit HOST_NAME_MAX\n.k8s_system_xt:\n  extends: .dock8s_system_test_template\n  image: alpine/k8s:1.18.2\n  variables:\n    # BASETAG: \"xrd_testing_\" TO BE REVIEWED\n  needs:\n    - job: cc7_xrd_testing_docker_image\n      artifacts: false\n  retry: 1\n  allow_failure: true\n  only:\n    - schedules\n  tags:\n    - docker_node\n    - k8s\n"
  },
  {
    "path": "gitlab-ci/after_script_docker_test.sh",
    "content": "#!/bin/bash -ve\n\n./eos-docker/scripts/collect_logs.sh eos-logs-${CI_JOB_ID}/\n./eos-docker/scripts/shutdown_services.sh\n\n# remove unused docker resources -> be it on the runner itself, here shutdown_services.sh should be enough\n#docker system prune --all --filter until=30m --force # @note will exploit flag when docker is updated\n#docker system prune --force # remove unused docker resources\n"
  },
  {
    "path": "gitlab-ci/after_script_k8s_test.sh",
    "content": "#!/bin/bash -ve\n\nexport KUBECONFIG=$K8S_CONFIG # get access configs for the cluster\nexport K8S_NAMESPACE=$(echo ${CI_JOB_NAME}-${CI_JOB_ID}-${CI_PIPELINE_ID} | tr '_' '-' | tr '[:upper:]' '[:lower:]')\n./eos-on-k8s/collect_logs.sh ${K8S_NAMESPACE} eos-logs-${CI_JOB_ID}/\n./eos-on-k8s/delete-all.sh ${K8S_NAMESPACE}\nrm -rf eos-on-k8s/\n"
  },
  {
    "path": "gitlab-ci/before_script_docker_test.sh",
    "content": "#!/bin/bash -ve\n\n./eos-docker/scripts/shutdown_services.sh\n./eos-docker/scripts/remove_unused_images.sh\n\nexport IMAGE_REPO=\"gitlab-registry.cern.ch/dss/eos/eos-ci\"\n# either $CI_COMMIT_TAG either $CI_COMMIT_SHORT_SHA\nexport IMAGE_TAG=\"${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}${OS_TAG}\"\nexport CLI_IMAGE_TAG=\"${CLI_BASETAG}${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}${OS_TAG}\"\n\n./eos-docker/scripts/start_services.sh -q -i ${IMAGE_REPO}:${IMAGE_TAG} -u ${IMAGE_REPO}:${CLI_IMAGE_TAG} -k ${KRB5:-\"mit\"}\n"
  },
  {
    "path": "gitlab-ci/before_script_k8s_test.sh",
    "content": "#!/bin/bash -ve\n\nexport KUBECONFIG=$K8S_CONFIG # get access configs for the cluster\ngit clone https://gitlab.cern.ch/eos/eos-on-k8s.git\nexport K8S_NAMESPACE=$(echo ${CI_JOB_NAME}-${CI_JOB_ID}-${CI_PIPELINE_ID} | tr '_' '-' | tr '[:upper:]' '[:lower:]')\n\nexport IMAGE_REPO=\"gitlab-registry.cern.ch/dss/eos/eos-ci\"\n# either $CI_COMMIT_TAG either $CI_COMMIT_SHORT_SHA\nexport IMAGE_TAG=\"${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}${OS_TAG}\"\nexport CLI_IMAGE_TAG=\"${CLI_BASETAG}${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}${OS_TAG}\"\n\n./eos-on-k8s/create-all.sh -b ${IMAGE_REPO} -i ${IMAGE_TAG} -u ${CLI_IMAGE_TAG} -n ${K8S_NAMESPACE} -k ${KRB5:-\"mit\"}\n"
  },
  {
    "path": "gitlab-ci/export_codename.sh",
    "content": "#!/bin/bash\n\n#Gitlab CI enabled branches should be listed here\n\nif [[ \"${CI_COMMIT_REF_NAME}\" == \"citrine\" ]] || [[ ${CI_COMMIT_REF_NAME} == 4.* ]] ; then\n  export CODENAME=\"citrine\" # @note not supported anymore\nelse\n  export CODENAME=\"diopside\"\nfi\n"
  },
  {
    "path": "gitlab-ci/export_commit-type.sh",
    "content": "#!/bin/bash\n\nif [[ -n \"$CI_COMMIT_TAG\" ]]; then\n    export COMMIT_TYPE=tag\nelse\n    export COMMIT_TYPE=commit # @note schedules will work as commit\nfi\n\n# GitLab CI/CD predefined environment variables reference https://docs.gitlab.com/ee/ci/variables/predefined_variables.html\n"
  },
  {
    "path": "gitlab-ci/generate_debian_metadata.sh",
    "content": "#!/bin/bash\n\n#-------------------------------------------------------------------------------\n# Publish debian artifacts on CERN Gitlab CI\n# Author: Jozsef Makai <jmakai@cern.ch> (11.08.2017)\n#-------------------------------------------------------------------------------\n\nset -e\n\nprefix=$1\ndist=$2\ncomponent=$3\narch=$4\n\nmkdir -p $prefix/dists/$dist/$component/binary-$arch/\n(cd $prefix && apt-ftparchive --arch $arch packages pool/$dist/$component/ > dists/$dist/$component/binary-$arch/Packages)\ngzip -c $prefix/dists/$dist/$component/binary-$arch/Packages > $prefix/dists/$dist/$component/binary-$arch/Packages.gz\n"
  },
  {
    "path": "gitlab-ci/prebuild_OSbase/prebuild-cc7.Dockerfile",
    "content": "FROM centos:7\n\nLABEL maintainer=\"Fabio Luchetti, faluchet@cern.ch, CERN 2020\"\n\nARG EOS_CODENAME\nWORKDIR /builds/dss/eos/\n\n# Fix CentOS7 repository after the EOL\nRUN sed -i -e '/mirrorlist/d' -e 's/# \\?baseurl=/baseurl=/g' -e 's/mirror.centos.org/vault.centos.org/g' -e 's/$releasever/7.9.2009/g' /etc/yum.repos.d/*.repo\n\n# If the working directory is a not the top-level dir of a git repo OR git remote is not set to the EOS repo url.\n# On Gitlab CI, the test won't (and don't have to) pass.\nRUN yum install --nogpg -y epel-release git centos-release-scl && \\\n    sed -i -e '/mirrorlist/d' -e 's/# \\?baseurl=/baseurl=/g' -e 's/mirror.centos.org/vault.centos.org/g' -e 's/$releasever/7.9.2009/g' /etc/yum.repos.d/*.repo && \\\n    if [[ $(git rev-parse --git-dir) != .git ]] || [[ $(git config --get remote.origin.url) != *gitlab.cern.ch/dss/eos.git ]]; \\\n      then git clone https://gitlab.cern.ch/dss/eos.git . ; \\\n    fi\n\nRUN yum install --nogpg -y ccache cmake3 gcc-c++ git make rpm-build rpm-sign tar which yum-plugin-priorities \\\n    && git submodule update --init --recursive \\\n    && mkdir build \\\n    && cd build/ \\\n    && cmake3 ../ -DPACKAGEONLY=1 && make srpm \\\n    && cd ../ \\\n    && echo -e '[eos-depend]\\nname=EOS dependencies\\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/'${EOS_CODENAME}'-depend/el-7/x86_64/\\ngpgcheck=0\\nenabled=1\\npriority=2\\n' >> /etc/yum.repos.d/eos-depend.repo \\\n    && yum-builddep --nogpgcheck --setopt=\"cern*.exclude=xrootd*\" -y build/SRPMS/* \\\n    && yum install -y moreutils \\\n    && yum clean all\n# install moreutils just for 'ts', nice to benchmark the build time.\n# cleaning yum cache should reduce image size.\n\nENTRYPOINT /bin/bash\n"
  },
  {
    "path": "gitlab-ci/prebuild_OSbase/prebuild-cc7_exotic.Dockerfile",
    "content": "FROM gitlab-registry.cern.ch/linuxsupport/cc7-base\n\nLABEL maintainer=\"Fabio Luchetti, faluchet@cern.ch, CERN 2020\"\n\nARG EOS_CODENAME\nARG PREBUILD_NAME\nARG CMAKE_OPTIONS\nARG CXXFLAGS\n\nWORKDIR /builds/dss/eos/\n\nRUN if [[ $PREBUILD_NAME == \"cc7_xrd_testing*\" ]]; then \\\n      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=0\\nenabled=1\\npriority=1\\nprotect=0\\ngpgkey=http://xrootd.cern.ch/sw/releases/RPM-GPG-KEY.txt\\nexclude=xrootd-*5.0.0*\\n' >> /etc/yum.repos.d/xrootd-testing.repo; \\\n    fi\n\nRUN yum install --nogpg -y gcc-c++ cmake3 make rpm-build which git yum-plugin-priorities tar ccache centos-release-scl rpm-sign \\\n    && git submodule update --init --recursive \\\n    && mkdir build \\\n    && cd build/ \\\n    && cmake3 ../ -DPACKAGEONLY=1 ${CMAKE_OPTIONS} && make srpm \\\n    && cd ../ \\\n    && echo -e '[eos-depend]\\nname=EOS dependencies\\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/'${EOS_CODENAME}'-depend/el-7/x86_64/\\ngpgcheck=0\\nenabled=1\\npriority=2\\n' >> /etc/yum.repos.d/eos-depend.repo \\\n    && yum-builddep --nogpgcheck --setopt=\"cern*.exclude=xrootd*\" -y build/SRPMS/* \\\n    && yum install -y moreutils \\\n    && yum clean all\n# install moreutils just for 'ts', nice to benchmark the build time.\n# cleaning yum cache should reduce image size.\n\nENTRYPOINT /bin/bash\n"
  },
  {
    "path": "gitlab-ci/prebuild_OSbase/prebuild-el10.Dockerfile",
    "content": "#\n# Prebuild EL10 docker image for EOS\n#\n\nFROM gitlab-registry.cern.ch/linuxsupport/alma10-base\nLABEL maintainer=\"Elvin Sindrilaru, esindril[@].cern.ch, CERN 2025\"\n\nARG EOS_CODENAME\n\nWORKDIR /builds/dss/eos/\n\n# If the working directory is a not the top-level dir of a git repo OR git\n# remote is not set to the EOS repo url. On Gitlab CI, the test won't\n# (and don't have to) pass.\nRUN dnf install --nogpg -y git && dnf clean all \\\n    && if [[ $(git rev-parse --git-dir) != .git ]] || [[ $(git config --get remote.origin.url) != *gitlab.cern.ch/dss/eos.git ]]; \\\n        then git clone https://gitlab.cern.ch/dss/eos.git . ; fi\n\nRUN dnf install -y dnf-plugins-core epel-release \\\n    && dnf config-manager --set-enabled crb      \\\n    && echo -e \"[eos-depend]\\nname=EOS dependencies\\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/${EOS_CODENAME}-depend/el-10/$(uname -m)/\\ngpgcheck=0\\nenabled=1\\npriority=4\" > /etc/yum.repos.d/eos-depend.repo \\\n    && dnf install --nogpg -y https://dl.fedoraproject.org/pub/epel/10/Everything/x86_64/Packages/j/jemalloc-5.3.0-10.el10_1.x86_64.rpm \\\n    && dnf install --nogpg -y https://dl.fedoraproject.org/pub/epel/10/Everything/x86_64/Packages/j/jemalloc-devel-5.3.0-10.el10_1.x86_64.rpm\n\nRUN dnf install --nogpg -y ccache cmake gcc-c++ git make rpm-build rpm-sign which moreutils \\\n    && git submodule update --init --recursive \\\n    && mkdir build && cd build     \\\n    && cmake .. -DPACKAGEONLY=1 && make srpm \\\n    && cd .. \\\n    && dnf builddep --nogpgcheck -y build/SRPMS/* \\\n    && dnf clean all\n\nENTRYPOINT /bin/bash\n\n"
  },
  {
    "path": "gitlab-ci/prebuild_OSbase/prebuild-el8.Dockerfile",
    "content": "FROM  almalinux:8\nLABEL maintainer=\"Manuel Reis, manuel.b.reis@cern.ch, CERN 2022\"\nARG EOS_CODENAME\nWORKDIR /builds/dss/eos/\n\n# If the working directory is a not the top-level dir of a git repo OR git remote is not set to the EOS repo url.\n# On Gitlab CI, the test won't (and don't have to) pass.\nRUN dnf install --nogpg -y git && dnf clean all \\\n    && if [[ $(git rev-parse --git-dir) != .git ]] || [[ $(git config --get remote.origin.url) != *gitlab.cern.ch/dss/eos.git ]]; \\\n        then git clone https://gitlab.cern.ch/dss/eos.git . ; fi\n\nRUN dnf install -y dnf-plugins-core epel-release\\\n    && dnf config-manager --set-enabled powertools \\\n    && echo -e \"[eos-depend]\\nname=EOS dependencies\\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/${EOS_CODENAME}-depend/el-8/x86_64/\\ngpgcheck=0\\nenabled=1\\npriority=4\" > /etc/yum.repos.d/eos-depend.repo\n\nRUN dnf install --nogpg -y ccache cmake3 gcc-c++ git make rpm-build rpm-sign which\\\n    && git submodule update --init --recursive \\\n    && mkdir build \\\n    && cd build/ \\\n    && cmake ../ -DPACKAGEONLY=1 && make srpm \\\n    && cd ../ \\\n    && dnf builddep --nogpgcheck -y build/SRPMS/* \\\n    && dnf install -y moreutils \\\n    && dnf clean all\n\nENTRYPOINT /bin/bash\n\n\n"
  },
  {
    "path": "gitlab-ci/prebuild_OSbase/prebuild-el9-arm64.Dockerfile",
    "content": "FROM  gitlab-registry.cern.ch/linuxsupport/alma9-base\nARG EOS_CODENAME\nWORKDIR /builds/dss/eos/\n\n# If the working directory is a not the top-level dir of a git repo OR git remote is not set to the EOS repo url.\n# On Gitlab CI, the test won't (and don't have to) pass.\nRUN dnf install --nogpg -y git && dnf clean all \\\n    && if [[ $(git rev-parse --git-dir) != .git ]] || [[ $(git config --get remote.origin.url) != *gitlab.cern.ch/dss/eos.git ]]; \\\n        then git clone https://gitlab.cern.ch/dss/eos.git . ; fi\n\nRUN dnf install -y dnf-plugins-core epel-release\\\n    && dnf config-manager --set-enabled crb \\\n    && echo -e \"[eos-depend]\\nname=EOS dependencies\\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/${EOS_CODENAME}-depend/el-9/$(uname -m)/\\ngpgcheck=0\\nenabled=1\\npriority=4\" > /etc/yum.repos.d/eos-depend.repo\n\nRUN dnf install --nogpg -y ccache cmake3 gcc-c++ git make rpm-build rpm-sign which\\\n    && git submodule update --init --recursive \\\n    && mkdir build \\\n    && cd build/ \\\n    && cmake ../ -DPACKAGEONLY=1 && make srpm \\\n    && cd ../ \\\n    && dnf builddep --nogpgcheck -y build/SRPMS/* \\\n    && dnf install -y moreutils \\\n    && dnf clean all\n\nENTRYPOINT /bin/bash\n"
  },
  {
    "path": "gitlab-ci/prebuild_OSbase/prebuild-el9.Dockerfile",
    "content": "#\n# Prebuild EL9 docker image for EOS\n#\n\nFROM gitlab-registry.cern.ch/linuxsupport/alma9-base\nLABEL maintainer=\"Elvin Sindrilaru, esindril[@].cern.ch, CERN 2024\"\n\nARG EOS_CODENAME\n\nWORKDIR /builds/dss/eos/\n\n# If the working directory is a not the top-level dir of a git repo OR git\n# remote is not set to the EOS repo url. On Gitlab CI, the test won't\n# (and don't have to) pass.\nRUN dnf install --nogpg -y git && dnf clean all \\\n    && if [[ $(git rev-parse --git-dir) != .git ]] || [[ $(git config --get remote.origin.url) != *gitlab.cern.ch/dss/eos.git ]]; \\\n        then git clone https://gitlab.cern.ch/dss/eos.git . ; fi\n\nRUN dnf install -y dnf-plugins-core epel-release \\\n    && dnf config-manager --set-enabled crb      \\\n    && echo -e \"[eos-depend]\\nname=EOS dependencies\\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/${EOS_CODENAME}-depend/el-9/$(uname -m)/\\ngpgcheck=0\\nenabled=1\\npriority=4\" > /etc/yum.repos.d/eos-depend.repo\n\nRUN dnf install --nogpg -y ccache cmake gcc-c++ git make rpm-build rpm-sign which moreutils \\\n    && git submodule update --init --recursive \\\n    && mkdir build && cd build     \\\n    && cmake .. -DPACKAGEONLY=1 && make srpm \\\n    && cd .. \\\n    && dnf builddep --nogpgcheck -y build/SRPMS/* \\\n    && dnf clean all\n\nENTRYPOINT /bin/bash\n\n"
  },
  {
    "path": "gitlab-ci/publish_deb.sh",
    "content": "#!/usr/bin/env bash\n\n#-------------------------------------------------------------------------------\n# Publish debian artifacts from the CERN Gitlab CI.\n#\n# Usage: $0 <build_type>\n#\n# where <build_type> can be \"tag\" or \"commit\"\n#-------------------------------------------------------------------------------\nset -ex\n\nif [[ $# -ne 1 ]]; then\n  echo \"error: no parameter given, please specify <build_type>\"\n  exit 1\nfi\n\nBUILD_TYPE=\"$1\"\n\n# Build type needs to be either tag or commit\nif [[ ! $BUILD_TYPE =~ ^(tag|commit)$ ]]; then\n  echo \"error: unknown <build_type> given\"\n  exit 2\nfi\n\nEOS_CODENAME=\"diopside\"\nSTCI_ROOT_PATH=\"/eos/project/s/storage-ci/www/debian\"\n\nfor RELEASE in \"jammy\" \"noble\" \"noble-arm\" \"plucky\" \"questing\"; do\n  if [ -d ./ubuntu-${RELEASE} ]; then\n    EXPORT_REPO=\"${STCI_ROOT_PATH}/eos/${EOS_CODENAME}\"\n    RELEASE_LTS=\"$(echo ${RELEASE} | cut -d '-' -f1)\"\n    mkdir -p ${EXPORT_REPO}/pool/${RELEASE_LTS}/${BUILD_TYPE}/e/eos/ || true\n    echo \"info: Publishing for: ${RELEASE} in location: ${EXPORT_REPO}/pool/${RELEASE_LTS}/${BUILD_TYPE}/e/eos/\"\n\n    if ! reprepro -C ${RELEASE_LTS}/${BUILD_TYPE} -Vb ${EXPORT_REPO} includedeb ${RELEASE_LTS} ./ubuntu-${RELEASE}/*.deb; then\n        # Delete offending package and retry\n        for file in ./ubuntu-${RELEASE}/*.deb; do\n            file=$(basename \"$file\")\n            PKG_NAME=$(echo -n \"$file\" | cut -d '_' -f1)\n            PKG_VERSION=$(echo -n \"$file\" | cut -d '_' -f2)\n            PKG_ARCH=$(echo -n \"$file\" | cut -d '_' -f3 | cut -d '.' -f 1)\n            reprepro --keepdirectories -Vb ${EXPORT_REPO} removefilter ${RELEASE_LTS} \\\n                     'Package (=='${PKG_NAME}'), $Version (=='${PKG_VERSION}'), $Architecture (=='${PKG_ARCH}')'\n        done\n        # Retry\n        mkdir ${EXPORT_REPO}/pool/${RELEASE_LTS} || true\n        mkdir ${EXPORT_REPO}/pool/${RELEASE_LTS}/${BUILD_TYPE} || true\n        mkdir -p ${EXPORT_REPO}/pool/${RELEASE_LTS}/${BUILD_TYPE}/e/eos/ || true\n        reprepro -C ${RELEASE_LTS}/${BUILD_TYPE} -Vb ${EXPORT_REPO} includedeb ${RELEASE_LTS} ./ubuntu-${RELEASE}/*.deb\n    fi\n  fi\ndone\n\nexit 0\n"
  },
  {
    "path": "gitlab-ci/remove_old_artifacts.sh",
    "content": "#!/bin/bash\n\nAGE_DAYS=10\nCOMMIT_DIRS=/eos/project/s/storage-ci/www/eos/*/commit\nTARBALL_DIR=/eos/project/s/storage-ci/www/eos/*/tarball\n\nfor commit_dir in ${COMMIT_DIRS}/*; do\n  count=$(find ${commit_dir} -regex '.*\\(rpm\\|dmg\\)$' -type f -mtime +${AGE_DAYS} -print -delete | wc -l)\n\n  if [[ ${count} -gt 0 ]]; then\n    echo \"${commit_dir}: deleted ${count} RPMs\"\n\n    # Recreate YUM repos\n    yum_repos=$(find ${commit_dir} -path \"*/SRPMS\" -o -path \"*/x86_64\" -type d)\n\n    for repo_dir in ${yum_repos[@]}; do\n      echo \"Updating YUM repo: ${repo_dir}\"\n      createrepo --update -q ${repo_dir}\n    done\n  fi\ndone\n\n# Delete commit tarballs older than AGE_DAYS; @note Do not delete tag tarballs!\nfor tarball in ${TARBALL_DIR}/*; do\n  if [[ $(find $tarball -mtime +${AGE_DAYS}) ]] && [[ $tarball =~ git[a-zA-Z0-9]{7}\".tar.gz\" ]]; then\n    rm $tarball && echo \"tarball deleted: $tarball\"\n#    echo \"$tarball\"\n  fi;\ndone\n"
  },
  {
    "path": "gitlab-ci/remove_old_artifacts_debian.sh",
    "content": "#!/bin/bash\n#-------------------------------------------------------------------------------\n# Publish debian artifacts on CERN Gitlab CI\n# Author: Jozsef Makai <jmakai@cern.ch> (11.08.2017)\n#-------------------------------------------------------------------------------\nset -e\n\nscript_loc=$(dirname \"$0\")\neos_base=/eos/project/s/storage-ci/www/debian/eos\nversions=$(find $eos_base -mindepth 1 -maxdepth 1 -type d -exec basename {} \\;)\n\nfor version in ${versions}; do\n  dists=$(find $eos_base/$version/pool -mindepth 1 -maxdepth 1 -type d -exec basename {} \\;)\n  for dist in ${dists}; do\n    if [ -d \"${eos_base}/${version}/pool/${dist}/commit\" ]; then\n      find $eos_base/$version/pool/$dist/commit -type f -mtime +10 -name '*.deb' -delete\n      for arch in amd64; do\n        $script_loc/generate_debian_metadata.sh $eos_base/$version $dist commit $arch\n      done\n      if [[ -n \"$CI_COMMIT_TAG\" ]]; then $script_loc/sign_debian_repository.sh $eos_base/$version $dist; fi\n    fi\n  done\ndone \n"
  },
  {
    "path": "gitlab-ci/setup_ccache.sh",
    "content": "#!/bin/bash\n\nset -x\n\nexport CCACHE_DIR=\"`pwd`/ccache\"\nexport CCACHE_BASEDIR=\"`rpm --eval %_builddir`/`ls build/SRPMS/*.src.rpm | awk -F '.' 'BEGIN{OFS=\".\";} {NF-=4;}1' | awk -F '/' '{print $NF}'`\"\nexport CCACHE_SLOPPINESS=pch_defines\nexport CCACHE_NOHASHDIR=true\nexport CCACHE_MAXSIZE=4G\n\nccache -z\nccache -p\n\nset +x\n"
  },
  {
    "path": "gitlab-ci/setup_ccache_deb.sh",
    "content": "#!/bin/bash\n\nset -x\n\nexport CCACHE_DIR=\"`pwd`/ccache\"\nexport CCACHE_BASEDIR=\"`pwd`\"\nexport CCACHE_SLOPPINESS=pch_defines\nexport CCACHE_NOHASHDIR=true\nexport CCACHE_MAXSIZE=2G\n\nccache -z\nccache -p\n\nset +x\n"
  },
  {
    "path": "gitlab-ci/setup_ccache_fc.sh",
    "content": "#!/bin/bash\n\nset -x\n\nexport CCACHE_DIR=\"`pwd`/ccache\"\nexport CCACHE_BASEDIR=\"`rpm --eval %_builddir`/`ls build/SRPMS/*.src.rpm | awk -F '.' 'BEGIN{OFS=\".\";} {NF-=3;}1' | awk -F '/' '{print $NF}'`\"\nexport CCACHE_SLOPPINESS=pch_defines\nexport CCACHE_NOHASHDIR=true\nexport CCACHE_MAXSIZE=4G\n\nccache -z\nccache -p\n\nset +x\n"
  },
  {
    "path": "gitlab-ci/sign_debian_repository.sh",
    "content": "#!/bin/bash\n\n#-------------------------------------------------------------------------------\n# Publish debian artifacts on CERN Gitlab CI\n# Author: Jozsef Makai <jmakai@cern.ch> (11.08.2017)\n#-------------------------------------------------------------------------------\n\nset -e\n\nprefix=$1\ndist=$2\n\ncomponents=$(find $prefix/dists/$dist/ -mindepth 1 -maxdepth 1 -type d -exec basename {} \\; | tr '\\n' ' ')\n\n# This thing can fail for the first time\nset +e\n\nrm $prefix/dists/$dist/Release $prefix/dists/$dist/InRelease $prefix/dists/$dist/Release.gpg\n\nset -e\n\napt-ftparchive -o APT::FTPArchive::Release::Origin=CERN -o APT::FTPArchive::Release::Label=EOS -o APT::FTPArchive::Release::Codename=$dist -o APT::FTPArchive::Release::Architectures=amd64 -o APT::FTPArchive::Release::Components=\"$components\" release $prefix/dists/$dist/ > $prefix/dists/$dist/Release\ngpg --homedir /home/stci/ --clearsign -o $prefix/dists/$dist/InRelease $prefix/dists/$dist/Release\ngpg --homedir /home/stci/ -abs -o $prefix/dists/$dist/Release.gpg $prefix/dists/$dist/Release\n"
  },
  {
    "path": "gitlab-ci/store_artifacts.sh",
    "content": "#!/bin/bash\n\n#-------------------------------------------------------------------------------\n# Publish artifacts from CERN Gitlab CI.\n# The script will only upload artifacts from builds found in the buildmap.\n#\n# To add a new build type, register it in the buildmap together\n# with the repo name at the storage endpoint.\n#\n# E.g: cc7 --> el-7\n#      storage endpoint: /eos/project/s/storage-ci/www/eos/citrine/commit/el-7/\n#-------------------------------------------------------------------------------\nset -ex\n\n# Define a mapping between builds and repos\ndeclare -A BUILDMAP\n\nBUILDMAP[cc7]=el-7\nBUILDMAP[el-8]=el-8\nBUILDMAP[el-9]=el-9\nBUILDMAP[el-10]=el-10\nBUILDMAP[fc-38]=fc-38\nBUILDMAP[fc-40]=fc-40\nBUILDMAP[fc-rawhide]=fc-rawhide\nBUILDMAP[osx]=osx\nBUILDMAP[cc7_asan]=el-7-asan\nBUILDMAP[cc7_tsan]=el-7-tsan\nBUILDMAP[el-9-asan]=el-9-asan\nBUILDMAP[el-9-tsan]=el-9-tsan\nBUILDMAP[el-9-arm64]=el-9\n\nCODENAME=$1\nBUILD_TYPE=$2\nPATH_PREFIX=$3\n\nfor artifacts_dir in *_artifacts; do\n  build=${artifacts_dir%_*}\n  repo=${BUILDMAP[${build}]}\n  build_artifacts=${build}_artifacts\n\n  # Handle only builds registered in the build map\n  [ -z ${repo} ] && continue\n\n  path=\"${PATH_PREFIX}/${CODENAME}/${BUILD_TYPE}/${repo}\"\n  tar_path=\"${PATH_PREFIX}/${CODENAME}/tarball/\"\n\n  # Treat OSX artifacts separately\n  if [ ${build} == \"osx\" ]; then\n    mkdir -p ${path}/x86_64/\n    cp ${build_artifacts}/* ${path}/x86_64/\n    continue\n  fi\n\n  # Upload RPMS\n  if [ -d \"${build_artifacts}/RPMS\" ]; then\n      if [[ -n $(find ${build_artifacts}/RPMS -name \"*.aarch64.rpm\") ]]; then\n          mkdir -p ${path}/aarch64/\n          cp ${build_artifacts}/RPMS/* ${path}/aarch64/\n          createrepo --update -q ${path}/aarch64/\n      elif [ \"$(ls -A ${build_artifacts}/RPMS)\" ]; then\n          mkdir -p ${path}/x86_64/\n          cp ${build_artifacts}/RPMS/* ${path}/x86_64/\n          createrepo --update -q ${path}/x86_64/\n      else\n          echo \"info: Directory ${build_artifacts}/RPMS is empty!\"\n      fi\n  fi\n\n  # Upload SRPMS\n  if [ -d \"${build_artifacts}/SRPMS\" ]; then\n    if [ \"$(ls -A ${build_artifacts}/SRPMS)\" ]; then\n      mkdir -p ${path}/SRPMS/\n      cp ${build_artifacts}/SRPMS/* ${path}/SRPMS/\n      createrepo --update -q ${path}/SRPMS/\n    else\n      echo \"info: Directory ${build_artifacts}/SRPMS is empty!\"\n    fi\n  fi\n\n  # Upload the tarball if present\n  for tar_file in ${build_artifacts}/eos-*.tar.gz; do\n   if [ -e ${tar_file} ]; then\n     mkdir -p ${tar_path}\n     cp ${tar_file} ${tar_path}\n   fi\n   break\n  done\ndone\n\nexit 0\n"
  },
  {
    "path": "gitlab-ci/store_artifacts_debian.sh",
    "content": "#!/bin/bash\n#-------------------------------------------------------------------------------\n# Publish debian artifacts on CERN Gitlab CI\n# Author: Jozsef Makai <jmakai@cern.ch> (11.08.2017)\n#-------------------------------------------------------------------------------\nset -e\n\nscript_loc=$(dirname \"$0\")\nprefix=$1\ncomponent=$2\n\nfor artifacts_dir in *_artifacts; do\n  dist=${artifacts_dir%_*}\n  path=$prefix/pool/$dist/$component/e/eos/\n  mkdir -p $path\n\n  echo \"Publishing for $dist -- $path\"\n\n  cp ${dist}_artifacts/*.deb $path\n  $script_loc/generate_debian_metadata.sh $prefix $dist $component amd64\n  if [[ -n \"$CI_COMMIT_TAG\" ]]; then $script_loc/sign_debian_repository.sh $prefix $dist; fi\ndone\n"
  },
  {
    "path": "gitlab-ci/store_stable_artifacts.sh",
    "content": "#!/usr/bin/env bash\n#-------------------------------------------------------------------------------\n# This script is used to copy the testing SRPM/RPM packages of a particular EOS\n# release from a predefined location eg. /eos/project/s/storage-ci/www/eos/el-6/\n# testing/ and move them to the stable YUM repo which don't contain the \"testing\"\n# word in their path.\n#-------------------------------------------------------------------------------\nset -ex\n\nif [ \"$#\" -ne 3 ]; then\n  echo \"Usage: $0 <branch> <path_prefix> <tag>\"\n  echo \"e.g.: $0 citrine /eos/project/s/storage-ci/www/eos 4.3.10\"\n  exit -1\nfi\n\nBRANCH=$1\nPATH_PREFIX=$2\nTAG=$3\n\nfor arch in \"el-8\" \"el-9\" \"el-10\" \"fc-38\"; do\n  # Find all the srpms matching the required tag\n  YUM_REPO_DIR=\"\"\n  SEARCH_PREFIX=\"${PATH_PREFIX}/${BRANCH}/tag/testing/${arch}\"\n  DEST_PREFIX=\"${PATH_PREFIX}/${BRANCH}/tag/${arch}\"\n  array=()\n  while IFS=  read -r -d $'\\0'; do\n    array+=(\"$REPLY\")\n  done < <(find ${SEARCH_PREFIX} -type f -name \"*-${TAG}-*\" -name \"*.src.rpm\" -print0)\n  echo \"SRPMS:\"\n\n  for srpm_src in \"${array[@]}\"; do\n    # Use string substitution to get the destination path\n    srpm_dst=\"${srpm_src/$SEARCH_PREFIX/$DEST_PREFIX}\"\n    cp \"${srpm_src}\" \"${srpm_dst}\"\n    # Drop the file name and the repodata dir from the path\n    YUM_REPO_DIR=$(dirname \"${srpm_dst}\")\n  done\n\n  # Rebuild the repo for SRPMS\n  createrepo -q ${YUM_REPO_DIR}\n\n  # Find all the rpms matching the required tag\n  array=()\n  while IFS=  read -r -d $'\\0'; do\n    array+=(\"$REPLY\")\n  done < <(find ${SEARCH_PREFIX} -type f -name \"*-${TAG}-*\" ! -name \"*.src.rpm\" -print0)\n\n  echo \"RPMS:\"\n  for rpm_src in \"${array[@]}\"; do\n    # Use string substitution to get the destination path\n    rpm_dst=\"${rpm_src/$SEARCH_PREFIX/$DEST_PREFIX}\"\n    cp \"${rpm_src}\" \"${rpm_dst}\"\n    # Drop the file name and the repodata dir from the path\n    YUM_REPO_DIR=$(dirname \"${rpm_dst}\")\n  done\n\n  # Rebuild the yum repo for RPMS\n  createrepo -q ${YUM_REPO_DIR}\ndone\n\n# Copy also the osx package\narch=\"osx\"\nSEARCH_PREFIX=\"${PATH_PREFIX}/${BRANCH}/tag/testing/${arch}\"\nDEST_PREFIX=\"${PATH_PREFIX}/${BRANCH}/tag/${arch}\"\narray=()\nwhile IFS=  read -r -d $'\\0'; do\n  array+=(\"$REPLY\")\ndone < <(find ${SEARCH_PREFIX} -type f -name \"*-${TAG}.dmg\" -print0)\n\ndmg=\"${array[0]}\"\n\ncp \"${dmg}\" \"${dmg/$SEARCH_PREFIX/$DEST_PREFIX}\"\n"
  },
  {
    "path": "gitlab-ci/utilities_func_for_tests.sh",
    "content": "#!/bin/bash -ve\n\nfunction usage() {\n  echo \"usage: $(basename $0) --type 'docker'|'k8s <k8s_namespace>'\"\n  echo \"        docker : script runs in a Docker based setup\"\n  echo \"        k8s    : script runs in a Kubernetes setup and requires a namespace argument\"\n  echo \"<k8s_namespace>: must be a DNS-1123 label and must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character\"\n  echo \"                 in practice, it is parsed like [[ <k8s_namespace> =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ ]]\"\n}\n\n# Forward the given command to the proper executor Docker or Kubernetes. Gets\n# as argument a container name and a shell command to be executed\nfunction exec_cmd() {\n  if [[ $IS_DOCKER == true ]]; then\n    exec_cmd_docker \"$@\"\n  else\n    exec_cmd_k8s \"$@\"\n  fi\n}\n\n# Execute command in Docker setup where the first argument is the name of the container\n# and the rest is the command to be executed\nfunction exec_cmd_docker() {\n  set -o xtrace\n  docker exec -i $1 /bin/bash -lic \"${@:2}\"\n  set +o xtrace\n}\n\n# Execute command in Kubernetes setup where the first argument is the name of the Pod\n# and the rest is the command to be executed\nfunction exec_cmd_k8s() {\n  set -o xtrace\n  kubectl exec --namespace=${K8S_NAMESPACE} $(get_podname ${1}) -- /bin/bash -lc \"${@:2}\"\n  set +o xtrace\n}\n\nfunction cp_to_local_cmd() {\n  if [[ $IS_DOCKER == true ]]; then\n    cp_to_local_cmd_docker \"$@\"\n  else\n    cp_to_local_cmd_k8s \"$@\"\n  fi\n}\n\nfunction cp_to_local_cmd_docker() {\n  docker cp $1 $2\n}\n\nfunction cp_to_local_cmd_k8s() {\n  local substr=\":\"\n  kubectl cp \"${K8S_NAMESPACE}/$(get_podname ${1%$substr*})${substr}${1#*$substr}\" ${2}\n}\n\nfunction get_podname () {\n    kubectl get pods --namespace=${K8S_NAMESPACE} -l app=${1} | grep -E '([0-9]+)/\\1' | awk '{print $1}' # Get only READY pods\n}\n\n\n# Set up global variables\nIS_DOCKER=\"\"\nK8S_NAMESPACE=\"\"\n\n\nif [[ \"$1\" != \"--type\" ]]; then\n  echo \"error: unknown argument \\\"$1\\\"\"\n  usage && exit 1\nfi\n\nif [[ \"$2\" == \"docker\" ]]; then\n  IS_DOCKER=true\n\nelif [[ \"$2\" == \"k8s\" ]]; then\n  IS_DOCKER=false\n  # For the Kubernetes setup we also need a namespace argument\n  if [[ -z $3 ]]; then\n    echo \"error: missing Kubernetes namespace argument\"\n    usage && exit 1\n  else\n    if [[ $3 =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ ]]; then\n\t  K8S_NAMESPACE=$3\n    else\n      usage && exit 1\n    fi\n  fi\n\nelse\n  echo \"error: unknown type of executor \\\"$2\\\"\"\n  usage && exit 1\nfi\n\n"
  },
  {
    "path": "man/CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2018 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nadd_custom_target(\n  man ALL\n  DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/man1/)\n\nadd_dependencies(man eos)\n\nadd_custom_command(\n  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/man1/\n  COMMAND ${CMAKE_COMMAND} -E env \"LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/common:${CMAKE_BINARY_DIR}/mq:$ENV{LD_LIBRARY_PATH}\"\n                                  \"PATH=${CMAKE_BINARY_DIR}/console:$ENV{PATH}\"\n          ${CMAKE_CURRENT_SOURCE_DIR}/create_man.sh\n)\n\ninstall(\n  CODE \"FILE ( GLOB MAN_FILES \\\"${CMAKE_CURRENT_BINARY_DIR}/man1/*.1.gz\\\" )\"\n  CODE \"FILE ( INSTALL \\${MAN_FILES}\n               DESTINATION \\\"${CMAKE_INSTALL_FULL_MANDIR}/man1\\\"\n               PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ )\"\n)\n"
  },
  {
    "path": "man/README.md",
    "content": "Generating man pages\n====================\n\nMan pages are generated from the `eos command --help` output\nusing the `help2man` utility.  \n\nThey are generated upon every commit and added to the eos-client RPM package during the CI pipeline.\nThis process is controlled at build time using the `BUILD_MANPAGES` flag.\n\n\nGenerating manually\n-------------------\n\nTo generate man pages manually, run the following:  \n`./create_man.sh`\n\nThis will create a new folder _man1/_ containing all the man archives.\n\nNote: since they are generated automatically, spacing might not be properly aligned.\n"
  },
  {
    "path": "man/create_eos_cmds.pl",
    "content": "#!/usr/bin/perl\n\nmy @excluded_commands = @ARGV;\n\nprint \"[COMMANDS]\\n\";\nopen FIN, \"eos help |\";\n\nwhile ( <FIN> ) {\n    chomp $_;\n    my @args = split (\" \", $_, 2);\n    printf \".in 10\\n.B $args[0]\\n.in 30\\n- $args[1] \";\n    if ( !($args[0] ~~ @excluded_commands) ) {\n        printf \"( man \\n.I eos-$args[0] ) \";\n    }\n    printf \"\\n.br\\n\";\n}\n\nclose IN;\n"
  },
  {
    "path": "man/create_man.sh",
    "content": "#!/bin/bash\n\n###########################\n# create the EOS man pages\n###########################\n\n# commands excluded from man page generation\nexcluded=(\n        \"exit\"\n        \"help\"\n        \"json\"\n        \"motd\"\n        \"quit\"\n        \"silent\"\n        \"test\"\n        \"timing\"\n        \"tracker\"\n        \"whoami\"\n        \".q\"\n        \"?\"\n    )\n\nexport EOS_DISABLE_PIPEMODE=1\nrm -rf man1 >& /dev/null\nmkdir man1\n\n# create eos command include file\nwdir=$(dirname $0)\nexcluded=( \"help\" \"json\" \"silent\" \"timing\" \"quit\" \"status\" \"exit\" \"test\" \".q\" \"\")\n${wdir}/create_eos_cmds.pl ${excluded[*]} > eos.cmds\nhelp2man --include=eos.cmds --help-option=\"-h \" --no-info --no-discard-stderr eos > man1/eos.1\ngzip man1/eos.1\nunlink eos.cmds\n\n# prepare array of command names\nexcluded[${#excluded[@]} - 1]=\"\\?\"\nexcluded_string=$(IFS=\\| ; echo \"${excluded[*]}\")\ncommands=(`eos -b help | awk '{print $1}' | grep -w -v -E \"${excluded_string}\" | cut -d\\  -f1`)\n\n# generate man page for each command asynchronously\necho \"Generating man pages:\"\nfor (( i=0; i<${#commands[@]}; i++ )); do\n    name=\"${commands[$i]}\"\n    printf \"  [%2s/%2s] Processing command %s\\n\" $((${i} + 1)) ${#commands[@]} ${name}\n    help2man --name=\"eos $name\" --help-option=\"$name -h \" --no-info --no-discard-stderr eos > man1/eos-${name}.1 \\\n        && gzip man1/eos-${name}.1 &\ndone\n\n# wait at most 5 seconds for all man pages to be created\nCOUNT=0\nwhile [[ `ls man1/ | wc -l` -ne $((${#commands[@]} + 1)) ]]; do\n    COUNT=$((COUNT + 1))\n    [[ ${COUNT} -eq 6 ]] && break\n    sleep 1\ndone\n"
  },
  {
    "path": "mgm/#Iostat.cc#",
    "content": "//------------------------------------------------------------------------------\n// File: Iostat.cc\n// Authors: Andreas-Joachim Peters - CERN\n//          Elvin Alin Sindrilaru  - CERN\n//          Jaroslav Guenther      - CERN\n//\n// Implementation follows presentation from EOS Workshop in 2022\n// https://indico.cern.ch/event/1103358/contributions/4758312/attachments/2402845/4109660/EOS_IO_stat_monitoring.pdf\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/table_formatter/TableFormatterBase.hh\"\n#include \"common/Report.hh\"\n#include \"common/Path.hh\"\n#include \"common/JeMallocHandler.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Timing.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringUtils.hh\"\n#include \"mgm/Iostat.hh\"\n#include \"mgm/XrdMgmOfs.hh\"\n#include \"mgm/IMaster.hh\"\n#include \"mq/MessagingRealm.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n#include \"namespace/ns_quarkdb/qclient/include/qclient/ResponseParsing.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"mq/QdbListener.hh\"\n#include <XrdNet/XrdNetUtils.hh>\n#include <XrdNet/XrdNetAddr.hh>\n#include <curl/curl.h>\n#include <zstd.h>\n#include <fcntl.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <errno.h>\n#include <string.h>\n\nEOSMGMNAMESPACE_BEGIN\n\nconst char* Iostat::gIostatCollect = \"iostat::collect\";\nconst char* Iostat::gIostatReportSave = \"iostat::report\";\nconst char* Iostat::gIostatReportNamespace = \"iostat::reportnamespace\";\nconst char* Iostat::gIostatPopularity = \"iostat::popularity\";\nconst char* Iostat::gIostatUdpTargetList = \"iostat::udptargets\";\nFILE* Iostat::gOpenReportFD = 0;\nPeriod LAST_DAY = Period::DAY;\nPeriod LAST_HOUR = Period::HOUR;\nPeriod LAST_5MIN = Period::FIVEMIN;\nPeriod LAST_1MIN = Period::ONEMIN;\nPercentComplete P90 = PercentComplete::p90;\nPercentComplete P95 = PercentComplete::p95;\nPercentComplete P99 = PercentComplete::p99;\nPercentComplete ALL = PercentComplete::p100;\n\n\n//------------------------------------------------------------------------------\n// IostatPeriods implementation\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Adds transfer data on a 24h timeline of [mDataBuffer]\n// Provides:\n// - [mLongestTransferTime] in last 24h\n// - populates [mIntegralBuffer] and every 5 min extracts the\n//   time of transfer for > 90/95/100 percent of data\n// - [mDataBuffer] circular buffer for all transfers in the last 24h\n//------------------------------------------------------------------------------\nvoid\nIostatPeriods::Add(unsigned long long val, time_t start, time_t stop,\n                   time_t now)\n{\n  mTotal += val;\n  double value = (double)val;\n\n  // Window start/end times are \"|\", bin start [ and end ] times\n  // period window = |-----------[--]----------|\n  if (stop > now) {\n    eos_static_err(\"%s\", \"msg=\\\"failed report digest, transfer \"\n                   \"stop time in the future\\\"\");\n    return;\n  }\n\n  time_t t_window_start = now - (sBins * sBinWidth);\n\n  if (stop <= t_window_start) {\n    eos_static_warning(\"%s\", \"msg=\\\"failed report digest, transfer stopped \"\n                       \"outside of collection time window\\\"\");\n    return;\n  }\n\n  time_t tdiff = stop - start + 1;\n\n  if (tdiff < 1) {\n    eos_static_err(\"%s\", \"msg=\\\"transfer start time after stop time\\\"\");\n    return;\n  }\n\n  StampBufferZero(now);\n  mTfCount += 1;\n\n  if (stop > mLastAddTime) {\n    mLastAddTime = stop;\n  }\n\n  if (mLongestTransferTime < (unsigned int)tdiff) {\n    mLongestTransferTime = tdiff;\n  }\n\n  time_t trep = now - stop;\n\n  if (mLongestReportTime < (unsigned int)(trep)) {\n    mLongestReportTime = trep;\n  }\n\n  // cutting off data out of time window\n  if (start < t_window_start) {\n    // re-calculate data portion to save into our time window\n    value = ((stop - t_window_start) * value) / tdiff;\n    start = t_window_start;\n    tdiff = stop - start;\n  }\n\n  // Number of bins the measurement hits\n  size_t mbins = tdiff / sBinWidth;\n  double val_per_bin = value / mbins;\n  int index_start = (start / sBinWidth) % sBins;\n\n  for (size_t ibin = 0; ibin < mbins; ++ibin) {\n    int bin_index = (index_start + ibin) % sBins;\n    // Code block to be added and tested in case sBinWidth !=1\n    // double ival = val_per_bin;\n    // if (ibin == 0 and mbins > 1):\n    //   time_t t_start_bin_duration = (sBins * sBinWidth) - (start - t_window_start) - sBinWidth * (mbins - 1)\n    //   ival = (t_start_bin_duration * value) / tdiff;\n    // if (ibin == mbins - 1 and mbins > 1):\n    //   time_t t_stop_bin_duration = (sBins * sBinWidth) - (now - stop) - sBinWidth * (mbins - 1)\n    //   ival = (t_stop_bin_duration * value) / tdiff;\n    // mDataBuffer[bin_index] += ival;\n    // mIntegralBuffer[ibin] += ival;\n    mDataBuffer[bin_index] += val_per_bin;\n    mIntegralBuffer[ibin] += val_per_bin;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Update Transfer Buffer to iterate over and calculate how long does it take\n// to transfer [mPercComplete] % of the data within sample rate of 5 min\n// [mLastTfSampleUpdateInterval]\n//------------------------------------------------------------------------------\nvoid\nIostatPeriods::UpdateTransferSampleInfo(time_t now)\n{\n  // Sum data of all transfers\n  double sumTx = 0.;\n\n  // Update rating (% of transfers)\n  for (size_t i = 0; i < sBins; ++i) {\n    if (mIntegralBuffer[i]) {\n      sumTx += mIntegralBuffer[i];\n    } else {\n      break;\n    }\n  }\n\n  // Reset counters for current sample\n  mTfCountInSample = 0ull;\n  mAvgTfSize = 0ull;\n\n  //std::cout << \"sumTx\" << sumTx << std::endl;\n  if (sumTx > 0) {\n    mTfCountInSample = mTfCount;\n    mAvgTfSize = std::ceil((double)sumTx / mTfCountInSample);\n    const double multiplier = std::pow(10.0, 6);\n\n    // integrate up to [mPercComplete] and record\n    // the time the transfers took in [mDurationToPercComplete]\n    for (size_t iperc = 0; iperc < std::size(mPercComplete); ++iperc) {\n      double sum_percent = 0;\n\n      for (size_t ibin = 0; ibin < sBins; ibin++) {\n        sum_percent += mIntegralBuffer[ibin] / sumTx;\n\n        if ((unsigned int)std::ceil(sum_percent * multiplier) >=\n            (unsigned int)std::ceil(mPercComplete[iperc] * multiplier)) {\n          mDurationToPercComplete[iperc] = ((ibin + 1) * sBinWidth);\n          break;\n        }\n      }\n    }\n  } else {\n    for (size_t iperc = 0; iperc < std::size(mPercComplete); ++iperc) {\n      mDurationToPercComplete[iperc] = 0;\n    }\n  }\n\n  mTfCount = 0;\n  mLongestTransferTimeInSample = mLongestTransferTime;\n  mLongestReportTimeInSample = mLongestReportTime;\n  mLongestTransferTime = 0;\n  mLongestReportTime = 0;\n  memset(mIntegralBuffer, 0, sizeof(mIntegralBuffer));\n  mLastTfMaxLenUpdateTime = now;\n}\n\n//------------------------------------------------------------------------------\n// Reset bin content of the buffer w.r.t. given timstamp\n//------------------------------------------------------------------------------\nvoid\nIostatPeriods::StampBufferZero(time_t& now)\n{\n  // Clean-up all bins which are older than sPeriod (24h)\n  // last_end_index is the index of the timestamp corresponding\n  // to the last transfer stop time recorded\n  if ((now - mLastTfMaxLenUpdateTime) > mLastTfSampleUpdateInterval) {\n    UpdateTransferSampleInfo(now);\n  }\n\n  time_t last_upd_time = std::max(mLastAddTime, mLastStampZeroTime);\n  int zero_bins = 0;\n\n  if (now - last_upd_time >= sPeriod) {\n    zero_bins = sBins;\n  } else {\n    if (last_upd_time < now) {\n      zero_bins = (now - last_upd_time) / sBinWidth;\n    } else {\n      if ((last_upd_time == mLastStampZeroTime) &&\n          (last_upd_time != mLastAddTime)) {\n        zero_bins = 1;\n      }\n    }\n  }\n\n  int start_index = (last_upd_time / sBinWidth) % sBins;\n\n  if (last_upd_time != now) {\n    start_index = (start_index + 1) % sBins;\n  }\n\n  for (int i = 0; i < zero_bins; ++i) {\n    int index = (start_index + i) % sBins;\n    mDataBuffer[index] = 0.;\n  }\n\n  mLastStampZeroTime = now;\n}\n\n//------------------------------------------------------------------------------\n// Getting the timestamp of the last time the transfer sample was taken\n//------------------------------------------------------------------------------\nstd::string IostatPeriods::GetLastSampleUpdateTimestamp(bool date_format) const\n{\n  std::string ts;\n\n  if (date_format) {\n    ts = common::Timing::ltime(mLastTfMaxLenUpdateTime);\n  } else {\n    ts = std::to_string(mLastTfMaxLenUpdateTime);\n  }\n\n  eos::common::trim(ts);\n  return ts;\n}\n\n\n//------------------------------------------------------------------------------\n// Get the sum of values for the given buffer period\n//------------------------------------------------------------------------------\nunsigned long long\nIostatPeriods::GetDataInPeriod(size_t period, unsigned long long time_offset,\n                               time_t now) const\n{\n  double sum = 0.;\n\n  if (time_offset > sPeriod) {\n    time_offset = sPeriod;\n  }\n\n  if (time_offset + period > sPeriod) {\n    period = sPeriod - time_offset;\n  }\n\n  size_t start_index = ((now - time_offset - period) / sBinWidth) % sBins;\n  size_t stop_index = ((now - time_offset) / sBinWidth) % sBins;\n  int range = stop_index - start_index ;\n\n  if (period >= sPeriod) {\n    range = sBins;\n    start_index = 0;\n  }\n\n  if (range < 0) {\n    range = sBins + range;\n  }\n\n  for (int pidx_cnt = 0; pidx_cnt < range; ++pidx_cnt) {\n    int idx = (start_index + pidx_cnt) % sBins;\n    sum += mDataBuffer[idx];\n  }\n\n  return std::ceil(sum);\n}\n\n//------------------------------------------------------------------------------\n// Iostat implementation\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Iostat constructor\n//------------------------------------------------------------------------------\nIostat::Iostat():\n  mDoneInit(false), mFlusher(nullptr), mLegacyMode(false), mRunning(false),\n  mQcl(nullptr), mReportSave(true), mReportNamespace(false),\n  mReportPopularity(true), mHashKeyBase(\"\")\n{\n  for (size_t i = 0; i < IOSTAT_POPULARITY_HISTORY_DAYS; i++) {\n    IostatPopularity[i].set_deleted_key(\"\");\n    IostatPopularity[i].resize(100000);\n  }\n\n  mLastPopularityBin = 9999999;\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nIostat::~Iostat()\n{\n  (void) StopCollection();\n  mCirculateThread.join();\n}\n\n//------------------------------------------------------------------------------\n// Get hash key under which info is stored in QDB. This also included the\n// current year and it's cached for ~5 minutes.\n//------------------------------------------------------------------------------\nstd::string\nIostat::GetHashKey() const\n{\n  using namespace std::chrono;\n  static std::string key;\n  static seconds cache_interval {300};\n  static auto ts = steady_clock::now();\n\n  if (key.empty() ||\n      (duration_cast<seconds>(steady_clock::now() - ts) > cache_interval)) {\n    key = mHashKeyBase + eos::common::Timing::GetCurrentYear();\n    ts = steady_clock::now();\n  }\n\n  return key;\n}\n\n//------------------------------------------------------------------------------\n// Perform object initialization\n//------------------------------------------------------------------------------\nbool\nIostat::Init(const std::string& instance_name, int port,\n             const std::string& legacy_file)\n{\n  mHashKeyBase = SSTR(\"eos-iostat:\" << instance_name << \":\");\n  mFlusherPath = SSTR(gOFS->mQClientDir << instance_name << \":\" << port\n                      << \"_iostat\");\n\n  if (gOFS) {\n    // QDB namespace, initialize qclient\n    if (!gOFS->namespaceGroup->isInMemory()) {\n      const eos::QdbContactDetails& qdb_details = gOFS->mQdbContactDetails;\n      mQcl.reset(new qclient::QClient(qdb_details.members,\n                                      qdb_details.constructOptions()));\n\n      if (!OneOffQdbMigration(legacy_file)) {\n        eos_static_err(\"%s\", \"msg=\\\"failed while attempting migration to QDB\\\"\");\n        return false;\n      }\n\n      if (!LoadFromQdb()) {\n        eos_static_err(\"%s\", \"msg=\\\"LoadFromQdb failed\\\"\");\n        return false;\n      }\n\n      if (mFlusher == nullptr) {\n        mFlusher.reset(new eos::MetadataFlusher(mFlusherPath,\n                                                gOFS->mQdbContactDetails));\n      }\n    } else {\n      // In-memory namespace forces the stats to be saved in the file\n      mLegacyMode = true;\n      mLegacyFilePath = legacy_file;\n\n      if (!LegacyRestoreFromFile()) {\n        eos_static_err(\"msg=\\\"failed to restore info from file\\\" path=%s\",\n                       mLegacyFilePath.c_str());\n        return false;\n      }\n    }\n  }\n\n  mCirculateThread.reset(&Iostat::Circulate, this);\n  mDoneInit = true;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// One off migration from file based to QDB of IoStat information\n//------------------------------------------------------------------------------\nbool\nIostat::OneOffQdbMigration(const std::string& legacy_file)\n{\n  struct stat info;\n\n  if (stat(legacy_file.c_str(), &info)) {\n    // File does not exist, migration was probably already done\n    return true;\n  }\n\n  FILE* fin = fopen(legacy_file.c_str(), \"r\");\n\n  if (!fin) {\n    eos_static_err(\"msg=\\\"failed to open iostat file\\\" path=\\\"%s\\\"\",\n                   legacy_file.c_str());\n    return false;\n  }\n\n  int item = 0;\n  char line[16384];\n  std::string tag;\n  std::list<std::string> entries;\n\n  while ((item = fscanf(fin, \"%16383s\\n\", line)) == 1) {\n    XrdOucEnv env(line);\n\n    if (env.Get(\"tag\") && env.Get(\"uid\") && env.Get(\"val\")) {\n      entries.push_back(EncodeKey(USER_ID_TYPE, env.Get(\"uid\"), env.Get(\"tag\")));\n      entries.push_back(env.Get(\"val\"));\n    }\n\n    if (env.Get(\"tag\") && env.Get(\"gid\") && env.Get(\"val\")) {\n      entries.push_back(EncodeKey(GROUP_ID_TYPE, env.Get(\"gid\"), env.Get(\"tag\")));\n      entries.push_back(env.Get(\"val\"));\n    }\n  }\n\n  fclose(fin);\n  // Push all the collected info to QDB\n  qclient::QHash qhash(*mQcl, GetHashKey());\n\n  try {\n    bool done = qhash.hmset(entries);\n\n    if (!done) {\n      eos_static_err(\"%s\", \"msg=\\\"failed while inserting entries in QDB\\\"\");\n      return false;\n    }\n  } catch (const std::exception& e) {\n    eos_static_err(\"msg=\\\"got exception while inserting entrines in QDB\\\" \"\n                   \"emsg=\\\"%s\\\"\", e.what());\n    return false;\n  }\n\n  // Save file based iostat as a backup\n  const std::string bkp_path = legacy_file + \".bkp\";\n\n  if (rename(legacy_file.c_str(), bkp_path.c_str())) {\n    eos_static_err(\"msg=\\\"failed file rename\\\" old_path=\\\"%s\\\" new_path=\\\"%s\\\"\",\n                   legacy_file.c_str(), bkp_path.c_str());\n    return false;\n  }\n\n  eos_static_info(\"msg=\\\"saved iostat backup successfully\\\" old_path=\\\"%s\\\" \"\n                  \" new_path=\\\"%s\\\"\", legacy_file.c_str(), bkp_path.c_str());\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Apply instance level configuration concerning IoStats\n//------------------------------------------------------------------------------\nvoid\nIostat::ApplyConfig(FsView* fsview)\n{\n  std::string iocollect = fsview->GetGlobalConfig(gIostatCollect);\n\n  if ((iocollect == \"true\") || (iocollect.empty())) {\n    StartCollection(); // enable by default\n  }\n\n  std::string iopopularity = fsview->GetGlobalConfig(gIostatPopularity);\n  mReportPopularity = (iopopularity == \"true\") || (iopopularity.empty());\n  mReportSave = fsview->GetBoolGlobalConfig(gIostatReportSave);\n  mReportNamespace = fsview->GetBoolGlobalConfig(gIostatReportNamespace);\n  std::string udplist = fsview->GetGlobalConfig(gIostatUdpTargetList);\n  std::string delimiter = \"|\";\n  std::vector<std::string> hostlist;\n  eos::common::StringConversion::Tokenize(udplist, hostlist, delimiter);\n  std::unique_lock<std::mutex> scope_lock(mBcastMutex);\n  mUdpPopularityTarget.clear();\n\n  for (size_t i = 0; i < hostlist.size(); ++i) {\n    AddUdpTarget(hostlist[i], false);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Store IoStat config in the instance level configuration\n//------------------------------------------------------------------------------\nbool\nIostat::StoreIostatConfig(FsView* fsview) const\n{\n  bool ok = true;\n\n  if ((gOFS && gOFS->mMaster->IsMaster()) || !gOFS) {\n    ok = fsview->SetGlobalConfig(gIostatPopularity, mReportPopularity) &\n         fsview->SetGlobalConfig(gIostatReportSave, mReportSave) &\n         fsview->SetGlobalConfig(gIostatReportNamespace, mReportNamespace) &\n         fsview->SetGlobalConfig(gIostatCollect, mRunning);\n    std::string udp_popularity_targets = EncodeUdpPopularityTargets();\n\n    if (!udp_popularity_targets.empty()) {\n      ok &= fsview->SetGlobalConfig(gIostatUdpTargetList, udp_popularity_targets);\n    }\n  }\n\n  return ok;\n}\n\n//------------------------------------------------------------------------------\n// Start collection thread\n//------------------------------------------------------------------------------\nbool\nIostat::StartCollection()\n{\n  std::unique_lock<std::mutex> scope_lock(mThreadSyncMutex);\n\n  if (!mRunning) {\n    mRunning = true;\n    mReceivingThread.reset(&Iostat::Receive, this);\n    StoreIostatConfig(&FsView::gFsView);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Stop collection thread\n//------------------------------------------------------------------------------\nbool\nIostat::StopCollection()\n{\n  std::unique_lock<std::mutex> scope_lock(mThreadSyncMutex);\n\n  if (mRunning) {\n    mReceivingThread.join();\n    mRunning = false;\n    StoreIostatConfig(&FsView::gFsView);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Start popularity thread\n//------------------------------------------------------------------------------\nbool\nIostat::StartPopularity()\n{\n  std::unique_lock<std::mutex> scope_lock(mThreadSyncMutex);\n\n  if (!mReportPopularity) {\n    mReportPopularity = true;\n    StoreIostatConfig(&FsView::gFsView);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Start popularity thread\n//------------------------------------------------------------------------------\nbool\nIostat::StopPopularity()\n{\n  std::unique_lock<std::mutex> scope_lock(mThreadSyncMutex);\n\n  if (mReportPopularity) {\n    mReportPopularity = false;\n    StoreIostatConfig(&FsView::gFsView);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Start daily report save thread\n//------------------------------------------------------------------------------\nbool\nIostat::StartReport()\n{\n  std::unique_lock<std::mutex> scope_lock(mThreadSyncMutex);\n\n  if (!mReportSave) {\n    mReportSave = true;\n    StoreIostatConfig(&FsView::gFsView);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Stop daily report save thread\n//------------------------------------------------------------------------------\nbool\nIostat::StopReport()\n{\n  std::unique_lock<std::mutex> scope_lock(mThreadSyncMutex);\n\n  if (mReportSave) {\n    mReportSave = false;\n    StoreIostatConfig(&FsView::gFsView);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Start namespace report thread\n//------------------------------------------------------------------------------\nbool\nIostat::StartReportNamespace()\n{\n  std::unique_lock<std::mutex> scope_lock(mThreadSyncMutex);\n\n  if (!mReportNamespace) {\n    mReportNamespace = true;\n    StoreIostatConfig(&FsView::gFsView);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Stop namespace report thread\n//------------------------------------------------------------------------------\nbool\nIostat::StopReportNamespace()\n{\n  std::unique_lock<std::mutex> scope_lock(mThreadSyncMutex);\n\n  if (mReportNamespace) {\n    mReportNamespace = false;\n    StoreIostatConfig(&FsView::gFsView);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Record measurement to the various periods it overlaps with\n//------------------------------------------------------------------------------\nvoid\nIostat::Add(const std::string& tag, uid_t uid, gid_t gid,\n            unsigned long long val,\n            time_t start, time_t stop, time_t now)\n{\n  // Flush to QDB if not in testing mode - this can be called without a lock\n  // as this is only called from the thread digesting the report messages one\n  // by one\n  if (gOFS && !mLegacyMode) {\n    AddToQdb(tag, uid, gid, val);\n  }\n\n  std::unique_lock<std::mutex> scope_lock(mDataMutex);\n  IostatTag[tag] += val;\n  IostatUid[tag][uid] += val;\n  IostatGid[tag][gid] += val;\n  IostatPeriodsUid[tag][uid].Add(val, start, stop, now);\n  IostatPeriodsGid[tag][gid].Add(val, start, stop, now);\n  IostatPeriodsTag[tag].Add(val, start, stop, now);\n}\n\n//------------------------------------------------------------------------------\n// Low level implementation for Add method also sending data to QDB\n//------------------------------------------------------------------------------\nvoid\nIostat::AddToQdb(const std::string& tag, uid_t uid, gid_t gid,\n                 unsigned long long val)\n{\n  if (mFlusher) {\n    CacheUpdate(EncodeKey(USER_ID_TYPE, std::to_string(uid), tag),\n                EncodeKey(GROUP_ID_TYPE, std::to_string(gid), tag), val);\n\n    if (ShouldFlushCache()) {\n      FlushCache();\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Save given update in the in-memory cache\n//------------------------------------------------------------------------------\nvoid\nIostat::CacheUpdate(const std::string& uid_key, const std::string& gid_key,\n                    unsigned long long val)\n{\n  mMapCacheUpdates[uid_key] += val;\n  mMapCacheUpdates[gid_key] += val;\n}\n\n//------------------------------------------------------------------------------\n// Check if the cache needs to be flushed\n//------------------------------------------------------------------------------\nbool\nIostat::ShouldFlushCache()\n{\n  using namespace std::chrono;\n  static auto timestamp = steady_clock::now();\n\n  if ((mMapCacheUpdates.size() >= mMapMaxSize) ||\n      (duration_cast<seconds>(steady_clock::now() - timestamp) > mCacheFlushDelay)) {\n    timestamp = steady_clock::now();\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Flush all cached entries to the QDB backed\n//------------------------------------------------------------------------------\nvoid\nIostat::FlushCache()\n{\n  using namespace std::chrono;\n  using eos::common::Timing;\n  static const hours timeout {1};\n  static auto timestamp = steady_clock::now();\n  static std::string hash_key = mHashKeyBase + Timing::GetCurrentYear();\n\n  // Check for change of hash key when a new year starts\n  if (duration_cast<minutes>(steady_clock::now() - timestamp) > timeout) {\n    timestamp = steady_clock::now();\n    std::string new_hash_key = mHashKeyBase + Timing::GetCurrentYear();\n\n    if (new_hash_key != hash_key) {\n      hash_key = new_hash_key;\n    }\n  }\n\n  static std::vector<std::string> request;\n  request.reserve(3 * mMapMaxSize + 1);\n  request.push_back(\"HINCRBYMULTI\");\n\n  for (const auto& elem : mMapCacheUpdates) {\n    const std::string svalue = std::to_string(elem.second);\n    request.push_back(hash_key);\n    request.push_back(elem.first);\n    request.push_back(svalue);\n  }\n\n  mMapCacheUpdates.clear();\n  mFlusher->exec(request);\n  request.clear();\n}\n\n//------------------------------------------------------------------------------\n// Get sum of measurements for the given tag (looping all uids per tag)\n//------------------------------------------------------------------------------\nunsigned long long\nIostat::GetTotalStatForTag(const char* tag) const\n{\n  unsigned long long val = 0ull;\n\n  if (!IostatTag.count(tag)) {\n    return val;\n  }\n\n  val = IostatTag.find(tag)->second;\n  return val;\n}\n\n//------------------------------------------------------------------------------\n// Get sum of measurements for the given tag an period (looping all uids per tag)\n//------------------------------------------------------------------------------\nunsigned long long\nIostat::GetPeriodStatForTag(const char* tag, size_t period, time_t secago) const\n{\n  auto it = IostatPeriodsTag.find(tag);\n\n  if (it == IostatPeriodsTag.end()) {\n    return 0ull;\n  }\n\n  return it->second.GetDataInPeriod(period, secago, time(0ull));\n}\n\n//------------------------------------------------------------------------------\n// Method executed by the thread receiving reports\n//------------------------------------------------------------------------------\nvoid\nIostat::Receive(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"IoStatReceiver\");\n  eos_static_info(\"%s\", \"msg=\\\"starting iostat receive thread\\\"\");\n\n  if (gOFS == nullptr) {\n    return;\n  }\n\n  while (!mDoneInit) {\n    assistant.wait_for(std::chrono::seconds(5));\n\n    if (assistant.terminationRequested()) {\n      break;\n    }\n  }\n\n  const std::string qdb_channel = \"/eos/*/report\";\n  mq::QdbListener listener(gOFS->mQdbContactDetails, qdb_channel);\n\n  while (!assistant.terminationRequested()) {\n    std::string newmessage;\n\n    while (listener.fetch(newmessage, &assistant)) {\n      if (assistant.terminationRequested()) {\n        break;\n      }\n\n      XrdOucString body = newmessage.c_str();\n\n      while (body.replace(\"&&\", \"&\")) {\n      }\n\n      XrdOucEnv ioreport(body.c_str());\n      time_t now = time(0);\n      std::unique_ptr<eos::common::Report> report(new eos::common::Report(ioreport));\n      Add(\"bytes_read\", report->uid, report->gid, report->rb, report->ots,\n          report->cts, now);\n      Add(\"bytes_read\", report->uid, report->gid, report->rvb_sum, report->ots,\n          report->cts, now);\n      Add(\"bytes_written\", report->uid, report->gid, report->wb, report->ots,\n          report->cts, now);\n      Add(\"read_calls\", report->uid, report->gid, report->nrc, report->ots,\n          report->cts, now);\n      Add(\"readv_calls\", report->uid, report->gid, report->rv_op, report->ots,\n          report->cts, now);\n      Add(\"write_calls\", report->uid, report->gid, report->nwc, report->ots,\n          report->cts, now);\n      Add(\"fwd_seeks\", report->uid, report->gid, report->nfwds, report->ots,\n          report->cts, now);\n      Add(\"bwd_seeks\", report->uid, report->gid, report->nbwds, report->ots,\n          report->cts, now);\n      Add(\"xl_fwd_seeks\", report->uid, report->gid, report->nxlfwds, report->ots,\n          report->cts, now);\n      Add(\"xl_bwd_seeks\", report->uid, report->gid, report->nxlbwds, report->ots,\n          report->cts, now);\n      Add(\"bytes_fwd_seek\", report->uid, report->gid, report->sfwdb, report->ots,\n          report->cts, now);\n      Add(\"bytes_bwd_wseek\", report->uid, report->gid, report->sbwdb, report->ots,\n          report->cts, now);\n      Add(\"bytes_xl_fwd_seek\", report->uid, report->gid, report->sxlfwdb, report->ots,\n          report->cts, now);\n      Add(\"bytes_xl_bwd_wseek\", report->uid, report->gid, report->sxlbwdb,\n          report->ots, report->cts, now);\n      Add(\"disk_time_read\", report->uid, report->gid, report->rt,\n          report->ots, report->cts, now);\n      Add(\"disk_time_write\", report->uid, report->gid,\n          report->wt, report->ots, report->cts, now);\n\n      if (report->dsize) {\n        Add(\"bytes_deleted\", 0, 0, report->dsize, now - 30, now, now);\n        Add(\"files_deleted\", 0, 0, 1, now - 30, now, now);\n      }\n\n      // Do the UDP broadcasting\n      UdpBroadCast(report.get());\n\n      // Do the domain accounting\n      if (report->path.substr(0, 11) == \"/replicate:\") {\n        // check if this is a replication path\n        // push into the 'eos' domain\n        std::unique_lock<std::mutex> scope_lock(mDataMutex);\n\n        if (report->rb) {\n          IostatPeriodsDomainIOrb[\"eos\"].Add(report->rb, report->ots, report->cts, now);\n        }\n\n        if (report->wb) {\n          IostatPeriodsDomainIOwb[\"eos\"].Add(report->wb, report->ots, report->cts, now);\n        }\n      } else {\n        if (mReportPopularity) {\n          // do the popularity accounting here for everything which is not replication!\n          AddToPopularity(report->path, report->rb, report->ots, report->cts);\n        }\n\n        std::string sdomain = report->sec_domain;\n        {\n          std::unique_lock<std::mutex> scope_lock(mDataMutex);\n\n          if (report->rb) {\n            IostatPeriodsDomainIOrb[sdomain].Add(report->rb, report->ots, report->cts, now);\n          }\n\n          if (report->wb) {\n            IostatPeriodsDomainIOwb[sdomain].Add(report->wb, report->ots, report->cts, now);\n          }\n        }\n      }\n\n      // do the application accounting here\n      std::string apptag = \"other\";\n\n      if (report->sec_app.length()) {\n        apptag = report->sec_app;\n      }\n\n      // Push into app accounting\n      {\n        std::unique_lock<std::mutex> scope_lock(mDataMutex);\n\n        if (report->rb) {\n          IostatPeriodsAppIOrb[apptag].Add(report->rb, report->ots, report->cts, now);\n        }\n\n        if (report->wb) {\n          IostatPeriodsAppIOwb[apptag].Add(report->wb, report->ots, report->cts, now);\n        }\n      }\n\n      if (mReportSave && gOFS->mMaster->IsMaster()) {\n        WriteRecord(body.c_str());\n      }\n\n      if (mReportNamespace) {\n        // add the record into the report namespace file\n        char path[4096];\n        snprintf(path, sizeof(path) - 1, \"%s/%s\", gOFS->IoReportStorePath.c_str(),\n                 report->path.c_str());\n        eos::common::Path cPath(path);\n\n        if (cPath.MakeParentPath(S_IRWXU | S_IRGRP | S_IXGRP)) {\n          FILE* freport = fopen(path, \"a+\");\n\n          if (freport) {\n            fprintf(freport, \"%s\\n\", body.c_str());\n            fclose(freport);\n          }\n        }\n      }\n    }\n\n    assistant.wait_for(std::chrono::seconds(1));\n  }\n\n  eos_static_info(\"%s\", \"msg=\\\"stopping iostat receiver thread\\\"\");\n}\n\n\n//------------------------------------------------------------------------------\n// Write record to the stream - used by the MGM/FUSEX to push entries\n//------------------------------------------------------------------------------\nvoid\nIostat::WriteRecord(const std::string& record)\n{\n  static uint32_t sec_per_day = 24 * 3600;\n  static std::mutex s_mutex;\n  static std::string s_report_fn = \"\";\n  static time_t s_last_ts = 0ull;\n  // ZSTD compressed reports configuration (lazy-init)\n  static bool s_zstd_enabled = false;\n  static bool s_zstd_inited = false;\n  static unsigned s_zstd_rotation = 24 * 3600; // default one day\n  static int s_zstd_level = 1;\n  static int s_zstd_fd = -1;\n  static void* s_zstd_cctx = nullptr;\n  static time_t s_zstd_segstart = 0;\n  static std::string s_zstd_current_path;\n  auto ensure_dir = [](const std::string & path) -> bool {\n    eos::common::Path cPath(path.c_str());\n    return cPath.MakeParentPath(S_IRWXU | S_IRGRP | S_IXGRP);\n  };\n  auto truncate_to_interval = [](time_t t, unsigned interval) -> time_t {\n    if (!interval)\n    {\n      return t;\n    }\n\n    return t - (t % interval);\n  };\n  auto make_segment_path = [&](time_t t) -> std::string {\n    struct tm tmval;\n    localtime_r(&t, &tmval);\n    char dirbuf[4096];\n    // Directory: <IoReportStorePath>/YYYY/MM\n    snprintf(dirbuf, sizeof(dirbuf) - 1, \"%s/%04u/%02u\",\n             gOFS->IoReportStorePath.c_str(),\n             1900 + tmval.tm_year,\n             tmval.tm_mon + 1);\n    char filebuf[256];\n    // File: YYYYMMDD-HHMMSS.eosreport.zst\n    snprintf(filebuf, sizeof(filebuf) - 1, \"%04u%02u%02u-%02u%02u%02u.eosreport.zst\",\n             1900 + tmval.tm_year, tmval.tm_mon + 1, tmval.tm_mday,\n             tmval.tm_hour, tmval.tm_min, tmval.tm_sec);\n    std::string full = std::string(dirbuf) + \"/\" + filebuf;\n    return full;\n  };\n  auto close_zstd_locked = [&]() {\n    if (s_zstd_fd >= 0 && s_zstd_cctx) {\n      std::vector<char> outBuf(65536);\n      ZSTD_inBuffer in = { nullptr, 0, 0 };\n      size_t ret = 0;\n      do {\n        ZSTD_outBuffer out = { outBuf.data(), outBuf.size(), 0 };\n        ret = ZSTD_compressStream2(reinterpret_cast<ZSTD_CCtx*>(s_zstd_cctx),\n                                   &out, &in, ZSTD_e_end);\n        if (ZSTD_isError(ret)) {\n          eos_static_err(\"msg=\\\"zstd endStream error (reports)\\\" code=%s\",\n                         ZSTD_getErrorName(ret));\n          break;\n        }\n        if (out.pos) {\n          (void)::write(s_zstd_fd, outBuf.data(), out.pos);\n        }\n      } while (ret != 0);\n    }\n    if (s_zstd_fd >= 0) {\n      ::close(s_zstd_fd);\n      s_zstd_fd = -1;\n    }\n    if (s_zstd_cctx) {\n      ZSTD_freeCCtx(reinterpret_cast<ZSTD_CCtx*>(s_zstd_cctx));\n      s_zstd_cctx = nullptr;\n    }\n    s_zstd_current_path.clear();\n  };\n  auto open_zstd_locked = [&](time_t segstart) -> bool {\n    std::string path = make_segment_path(segstart);\n\n    if (!ensure_dir(path))\n    {\n=======\n    s_zstd_current_path.clear();\n  };\n\n  auto open_zstd_locked = [&](time_t segstart) -> bool {\n    std::string path = make_segment_path(segstart);\n    if (!ensure_dir(path)) {\n>>>>>>> 7da021c92 (iostat: add ZSTD-compressed reports; logging: rename ZSTD env vars; docs)\n      eos_static_err(\"msg=\\\"failed to create report parent path\\\" path=%s\",\n                     path.c_str());\n      return false;\n    }\n<<<<<<< HEAD\n\n    int fd = ::open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0644);\n\n    if (fd < 0)\n    {\n=======\n    int fd = ::open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0644);\n    if (fd < 0) {\n>>>>>>> 7da021c92 (iostat: add ZSTD-compressed reports; logging: rename ZSTD env vars; docs)\n      eos_static_err(\"msg=\\\"failed to open report file\\\" path=%s errno=%d err=\\\"%s\\\"\",\n                     path.c_str(), errno, strerror(errno));\n      return false;\n    }\n<<<<<<< HEAD\n\n    void* cctx = ZSTD_createCCtx();\n\n    if (!cctx)\n    {\n=======\n    void* cctx = ZSTD_createCCtx();\n    if (!cctx) {\n>>>>>>> 7da021c92 (iostat: add ZSTD-compressed reports; logging: rename ZSTD env vars; docs)\n      eos_static_err(\"%s\", \"msg=\\\"cannot create zstd context (reports)\\\"\");\n      ::close(fd);\n      return false;\n    }\n<<<<<<< HEAD\n\n    if (ZSTD_isError(ZSTD_CCtx_setParameter(reinterpret_cast<ZSTD_CCtx*>(cctx),\n                                            ZSTD_c_compressionLevel, s_zstd_level)))\n    {\n      eos_static_warning(\"msg=\\\"failed to set zstd level (reports)\\\" level=%d\",\n                         s_zstd_level);\n    }\n\n=======\n    if (ZSTD_isError(ZSTD_CCtx_setParameter(reinterpret_cast<ZSTD_CCtx*>(cctx),\n                                            ZSTD_c_compressionLevel, s_zstd_level))) {\n      eos_static_warning(\"msg=\\\"failed to set zstd level (reports)\\\" level=%d\",\n                         s_zstd_level);\n    }\n>>>>>>> 7da021c92 (iostat: add ZSTD-compressed reports; logging: rename ZSTD env vars; docs)\n    // Write frame header to avoid empty-file reader errors\n    {\n      std::vector<char> outBuf(16384);\n      ZSTD_inBuffer in = { nullptr, 0, 0 };\n      ZSTD_outBuffer out = { outBuf.data(), outBuf.size(), 0 };\n      size_t ret = ZSTD_compressStream2(reinterpret_cast<ZSTD_CCtx*>(cctx),\n                                        &out, &in, ZSTD_e_flush);\n<<<<<<< HEAD\n\n      if (ZSTD_isError(ret))\n      {\n        eos_static_warning(\"msg=\\\"zstd header flush error (reports)\\\" code=%s\",\n                           ZSTD_getErrorName(ret));\n      }\n\n      if (out.pos)\n      {\n=======\n      if (ZSTD_isError(ret)) {\n        eos_static_warning(\"msg=\\\"zstd header flush error (reports)\\\" code=%s\",\n                           ZSTD_getErrorName(ret));\n      }\n      if (out.pos) {\n>>>>>>> 7da021c92 (iostat: add ZSTD-compressed reports; logging: rename ZSTD env vars; docs)\n        (void)::write(fd, outBuf.data(), out.pos);\n      }\n    }\n    s_zstd_fd = fd;\n    s_zstd_cctx = cctx;\n    s_zstd_segstart = segstart;\n    s_zstd_current_path = path;\n    return true;\n  };\n<<<<<<< HEAD\n=======\n\n>>>>>>> 7da021c92 (iostat: add ZSTD-compressed reports; logging: rename ZSTD env vars; docs)\n  time_t now_ts = time(NULL);\n  std::unique_lock<std::mutex> scope_lock(s_mutex);\n\n  if (!s_zstd_inited) {\n    const char* zenv = getenv(\"EOS_ZSTD_REPORTS\");\n<<<<<<< HEAD\n\n    if (zenv && (*zenv == '1' || !strcasecmp(zenv, \"true\") ||\n                 !strcasecmp(zenv, \"yes\") || !strcasecmp(zenv, \"on\"))) {\n      s_zstd_enabled = true;\n    }\n\n    const char* zrot = getenv(\"EOS_ZSTD_REPORTS_ROTATION\");\n\n    if (zrot && *zrot) {\n      int v = atoi(zrot);\n\n      if (v > 0) {\n        s_zstd_rotation = static_cast<unsigned>(v);\n      }\n    }\n\n    const char* lvl = getenv(\"EOS_ZSTD_REPORTS_LEVEL\");\n\n    if (lvl && *lvl) {\n      int v = atoi(lvl);\n\n      if (v >= 1 && v <= 19) {\n        s_zstd_level = v;\n      }\n    }\n\n=======\n    if (zenv && (*zenv == '1' || !strcasecmp(zenv, \"true\") || !strcasecmp(zenv, \"yes\") || !strcasecmp(zenv, \"on\"))) {\n      s_zstd_enabled = true;\n    }\n    const char* zrot = getenv(\"EOS_ZSTD_REPORTS_ROTATION\");\n    if (zrot && *zrot) {\n      int v = atoi(zrot);\n      if (v > 0) s_zstd_rotation = static_cast<unsigned>(v);\n    }\n    const char* lvl = getenv(\"EOS_ZSTD_REPORTS_LEVEL\");\n    if (lvl && *lvl) {\n      int v = atoi(lvl);\n      if (v >= 1 && v <= 19) s_zstd_level = v;\n    }\n>>>>>>> 7da021c92 (iostat: add ZSTD-compressed reports; logging: rename ZSTD env vars; docs)\n    s_zstd_inited = true;\n  }\n\n  if (s_zstd_enabled) {\n    // Close plain FILE* if previously used\n    if (gOpenReportFD) {\n      fclose(gOpenReportFD);\n      gOpenReportFD = nullptr;\n      s_report_fn.clear();\n      s_last_ts = 0ull;\n    }\n<<<<<<< HEAD\n\n    const time_t seg = truncate_to_interval(now_ts, s_zstd_rotation);\n\n    if (s_zstd_fd < 0 || !s_zstd_cctx || seg != s_zstd_segstart) {\n      close_zstd_locked();\n\n=======\n    const time_t seg = truncate_to_interval(now_ts, s_zstd_rotation);\n    if (s_zstd_fd < 0 || !s_zstd_cctx || seg != s_zstd_segstart) {\n      close_zstd_locked();\n>>>>>>> 7da021c92 (iostat: add ZSTD-compressed reports; logging: rename ZSTD env vars; docs)\n      if (!open_zstd_locked(seg)) {\n        return;\n      }\n    }\n<<<<<<< HEAD\n\n=======\n>>>>>>> 7da021c92 (iostat: add ZSTD-compressed reports; logging: rename ZSTD env vars; docs)\n    // Build line with newline terminator\n    std::string line = record;\n    line.push_back('\\n');\n    ZSTD_inBuffer in = { line.data(), line.size(), 0 };\n    std::vector<char> outBuf(131072);\n<<<<<<< HEAD\n\n=======\n>>>>>>> 7da021c92 (iostat: add ZSTD-compressed reports; logging: rename ZSTD env vars; docs)\n    while (in.pos < in.size) {\n      ZSTD_outBuffer out = { outBuf.data(), outBuf.size(), 0 };\n      size_t ret = ZSTD_compressStream2(reinterpret_cast<ZSTD_CCtx*>(s_zstd_cctx),\n                                        &out, &in, ZSTD_e_continue);\n<<<<<<< HEAD\n\n=======\n>>>>>>> 7da021c92 (iostat: add ZSTD-compressed reports; logging: rename ZSTD env vars; docs)\n      if (ZSTD_isError(ret)) {\n        eos_static_err(\"msg=\\\"zstd compress error (reports)\\\" code=%s\",\n                       ZSTD_getErrorName(ret));\n        break;\n      }\n<<<<<<< HEAD\n\n      if (out.pos) {\n        ssize_t w = ::write(s_zstd_fd, outBuf.data(), out.pos);\n\n=======\n      if (out.pos) {\n        ssize_t w = ::write(s_zstd_fd, outBuf.data(), out.pos);\n>>>>>>> 7da021c92 (iostat: add ZSTD-compressed reports; logging: rename ZSTD env vars; docs)\n        if (w < 0) {\n          eos_static_err(\"msg=\\\"write error (reports)\\\" errno=%d err=\\\"%s\\\"\",\n                         errno, strerror(errno));\n          break;\n        }\n      }\n    }\n<<<<<<< HEAD\n\n=======\n>>>>>>> 7da021c92 (iostat: add ZSTD-compressed reports; logging: rename ZSTD env vars; docs)\n    // Flush so small records are visible\n    {\n      ZSTD_inBuffer fin = { nullptr, 0, 0 };\n      size_t fret = 0;\n<<<<<<< HEAD\n\n=======\n>>>>>>> 7da021c92 (iostat: add ZSTD-compressed reports; logging: rename ZSTD env vars; docs)\n      do {\n        ZSTD_outBuffer out = { outBuf.data(), outBuf.size(), 0 };\n        fret = ZSTD_compressStream2(reinterpret_cast<ZSTD_CCtx*>(s_zstd_cctx),\n                                    &out, &fin, ZSTD_e_flush);\n<<<<<<< HEAD\n\n=======\n>>>>>>> 7da021c92 (iostat: add ZSTD-compressed reports; logging: rename ZSTD env vars; docs)\n        if (ZSTD_isError(fret)) {\n          eos_static_warning(\"msg=\\\"zstd flush error (reports)\\\" code=%s\",\n                             ZSTD_getErrorName(fret));\n          break;\n        }\n<<<<<<< HEAD\n\n=======\n>>>>>>> 7da021c92 (iostat: add ZSTD-compressed reports; logging: rename ZSTD env vars; docs)\n        if (out.pos) {\n          (void)::write(s_zstd_fd, outBuf.data(), out.pos);\n        }\n      } while (fret != 0);\n    }\n    return;\n  }\n\n  // Plain (legacy) daily uncompressed reports\n  if (now_ts / sec_per_day != s_last_ts / sec_per_day) {\n    struct tm nowtm;\n    if (localtime_r(&now_ts, &nowtm)) {\n      static char logfile[4096];\n      snprintf(logfile, sizeof(logfile) - 1, \"%s/%04u/%02u/%04u%02u%02u.eosreport\",\n               gOFS->IoReportStorePath.c_str(),\n               1900 + nowtm.tm_year,\n               nowtm.tm_mon + 1,\n               1900 + nowtm.tm_year,\n               nowtm.tm_mon + 1,\n               nowtm.tm_mday);\n      std::string report_fn = logfile;\n      if (report_fn != s_report_fn) {\n        if (gOpenReportFD) {\n          fclose(gOpenReportFD);\n          gOpenReportFD = nullptr;\n        }\n        eos::common::Path cPath(report_fn.c_str());\n        if (cPath.MakeParentPath(S_IRWXU | S_IRGRP | S_IXGRP)) {\n          gOpenReportFD = fopen(report_fn.c_str(), \"a+\");\n          if (!gOpenReportFD) {\n            eos_static_err(\"msg=\\\"failed to open report file\\\" path=%s\",\n                           report_fn.c_str());\n            return;\n          }\n        } else {\n          eos_static_err(\"msg=\\\"failed to create report parent path\\\" path=%s\",\n                         report_fn.c_str());\n          return;\n        }\n        s_report_fn = report_fn;\n        s_last_ts = now_ts;\n      }\n    }\n  }\n  if (gOpenReportFD) {\n    fprintf(gOpenReportFD, \"%s\\n\", record.c_str());\n    fflush(gOpenReportFD);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print IO statistics\n//------------------------------------------------------------------------------\nvoid\nIostat::PrintOut(XrdOucString& out, bool summary, bool details,\n                 bool monitoring, bool numerical, bool top,\n                 bool domain, bool apps, bool sample_stat, time_t time_ago,\n                 time_t time_interval, XrdOucString option)\n{\n  std::string format_s = (!monitoring ? \"s\" : \"os\");\n  std::string format_ss = (!monitoring ? \"-s\" : \"os\");\n  std::string format_l = (!monitoring ? \"+l\" : \"ol\");\n  std::string format_ll = (!monitoring ? \"l.\" : \"ol\");\n  std::unique_lock<std::mutex> scope_lock(mDataMutex);\n  time_t now = time(NULL);\n  bool interval = false;\n  time_ago = time_ago % 86400;\n  time_interval = time_interval % 86400;\n\n  if (time_interval != 0) {\n    interval = true;\n  }\n\n  std::vector<std::string> tags;\n\n  if (summary || top) {\n    for (auto tit = IostatTag.begin(); tit != IostatTag.end(); ++tit) {\n      tags.push_back(tit->first);\n    }\n\n    std::sort(tags.begin(), tags.end());\n  }\n\n  if (summary) {\n    TableFormatterBase table;\n    TableData table_data;\n\n    if (interval) {\n      if (!monitoring) {\n        table.SetHeader({\n          std::make_tuple(\"who\", 3, format_ss),\n          std::make_tuple(\"io value\", 24, format_s),\n          std::make_tuple(\"data in interval\", 8, format_l),\n          std::make_tuple(\"avg rate [B/s]\", 8, format_l),\n        });\n      } else {\n        table.SetHeader({\n          std::make_tuple(\"uid\", 0, format_ss),\n          std::make_tuple(\"gid\", 0, format_s),\n          std::make_tuple(\"measurement\", 0, format_s),\n          std::make_tuple(\"intervaldata\", 8, format_l),\n          std::make_tuple(\"intervalrate\", 8, format_l),\n        });\n      }\n    } else {\n      if (!monitoring) {\n        table.SetHeader({\n          std::make_tuple(\"who\", 3, format_ss),\n          std::make_tuple(\"io value\", 24, format_s),\n          std::make_tuple(\"1min\", 8, format_l),\n          std::make_tuple(\"5min\", 8, format_l),\n          std::make_tuple(\"1h\", 8, format_l),\n          std::make_tuple(\"24h\", 8, format_l),\n          std::make_tuple(\"sum\", 8, format_l),\n        });\n      } else {\n        table.SetHeader({\n          std::make_tuple(\"uid\", 0, format_ss),\n          std::make_tuple(\"gid\", 0, format_s),\n          std::make_tuple(\"measurement\", 0, format_s),\n          std::make_tuple(\"60s\", 0, format_l),\n          std::make_tuple(\"300s\", 0, format_l),\n          std::make_tuple(\"3600s\", 0, format_l),\n          std::make_tuple(\"86400s\", 0, format_l),\n          std::make_tuple(\"total\", 0, format_l),\n        });\n      }\n    }\n\n    for (const auto& elem : tags) {\n      const char* tag = elem.c_str();\n      table_data.emplace_back();\n      TableRow& row = table_data.back();\n      row.emplace_back(\"all\", format_ss);\n\n      if (monitoring) {\n        row.emplace_back(\"all\", format_s);\n      }\n\n      row.emplace_back(tag, format_s);\n\n      if (interval) {\n        row.emplace_back(GetPeriodStatForTag(tag, time_interval, time_ago), format_ll);\n        row.emplace_back(GetPeriodStatForTag(tag, time_interval,\n                                             time_ago) / (float)time_interval, format_ll);\n      } else {\n        // getting tag stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n        row.emplace_back(GetPeriodStatForTag(tag, 60), format_ll);\n        row.emplace_back(GetPeriodStatForTag(tag, 300), format_ll);\n        row.emplace_back(GetPeriodStatForTag(tag, 3600), format_ll);\n        row.emplace_back(GetPeriodStatForTag(tag, 86400), format_ll);\n        row.emplace_back(GetTotalStatForTag(tag), format_ll);\n      }\n    }\n\n    table.AddRows(table_data);\n    out += table.GenerateTable(HEADER).c_str();\n    table_data.clear();\n\n    if (!interval) {\n      //! UDP Popularity Broadcast Target\n      std::unique_lock<std::mutex> mLock(mBcastMutex);\n\n      if (!mUdpPopularityTarget.empty()) {\n        TableFormatterBase table_udp;\n\n        if (!monitoring) {\n          table_udp.SetHeader({\n            std::make_tuple(\"UDP Popularity Broadcast Target\", 32, format_ss)\n          });\n        } else {\n          table_udp.SetHeader({ std::make_tuple(\"udptarget\", 0, format_ss) });\n        }\n\n        for (const auto& elem : mUdpPopularityTarget) {\n          table_data.emplace_back();\n          table_data.back().emplace_back(elem.c_str(), format_ss);\n        }\n\n        table_udp.AddRows(table_data);\n        out += table_udp.GenerateTable(HEADER).c_str();\n      }\n    }\n  }\n\n  if (details) {\n    if (interval) {\n      std::vector<std::tuple<std::string, std::string, unsigned long long, unsigned long long>>\n          uidout, gidout;\n      TableFormatterBase table_user;\n      TableData table_data;\n\n      //! User statistic\n      if (!monitoring) {\n        table_user.SetHeader({\n          std::make_tuple(\"user\", 5, format_ss),\n          std::make_tuple(\"io value\", 24, format_s),\n          std::make_tuple(\"data in interval\", 8, format_l),\n          std::make_tuple(\"avg rate [B/s]\", 8, format_l),\n        });\n      } else {\n        table_user.SetHeader({\n          std::make_tuple(\"uid\", 0, format_ss),\n          std::make_tuple(\"measurement\", 0, format_s),\n          std::make_tuple(\"intervaldata\", 8, format_l),\n          std::make_tuple(\"intervalrate\", 8, format_l),\n        });\n      }\n\n      for (auto tuit = IostatPeriodsUid.begin(); tuit != IostatPeriodsUid.end();\n           tuit++) {\n        for (auto it = tuit->second.begin(); it != tuit->second.end(); ++it) {\n          std::string username;\n\n          if (numerical) {\n            username = std::to_string(it->first);\n          } else {\n            int terrc = 0;\n            username = eos::common::Mapping::UidToUserName(it->first, terrc);\n          }\n\n          // getting tag stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n          uidout.emplace_back(std::make_tuple(username, tuit->first.c_str(),\n                                              it->second.GetDataInPeriod(time_interval, time_ago, now),\n                                              it->second.GetDataInPeriod(time_interval, time_ago, now) / (float)time_interval\n                                             ));\n        }\n      }\n\n      std::sort(uidout.begin(), uidout.end());\n\n      for (auto& tup : uidout) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        row.emplace_back(std::get<0>(tup), format_ss);\n        row.emplace_back(std::get<1>(tup), format_s);\n        row.emplace_back(std::get<2>(tup), format_l);\n        row.emplace_back(std::get<3>(tup), format_l);\n      }\n\n      table_user.AddRows(table_data);\n      out += table_user.GenerateTable(HEADER).c_str();\n      table_data.clear();\n      // Group statistics\n      TableFormatterBase table_group;\n\n      if (!monitoring) {\n        table_group.SetHeader({\n          std::make_tuple(\"group\", 5, format_ss),\n          std::make_tuple(\"io value\", 24, format_s),\n          std::make_tuple(\"data in interval\", 8, format_l),\n          std::make_tuple(\"avg rate [B/s]\", 8, format_l),\n        });\n      } else {\n        table_group.SetHeader({\n          std::make_tuple(\"gid\", 0, format_ss),\n          std::make_tuple(\"measurement\", 0, format_s),\n          std::make_tuple(\"intervaldata\", 8, format_l),\n          std::make_tuple(\"intervalrate\", 8, format_l),\n        });\n      }\n\n      for (auto tgit = IostatPeriodsGid.begin(); tgit != IostatPeriodsGid.end();\n           tgit++) {\n        for (auto it = tgit->second.begin(); it != tgit->second.end(); ++it) {\n          std::string groupname;\n\n          if (numerical) {\n            groupname = std::to_string(it->first);\n          } else {\n            int terrc = 0;\n            groupname = eos::common::Mapping::GidToGroupName(it->first, terrc);\n          }\n\n          // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n          gidout.emplace_back(std::make_tuple(groupname, tgit->first.c_str(),\n                                              it->second.GetDataInPeriod(time_interval, time_ago, now),\n                                              it->second.GetDataInPeriod(time_interval, time_ago, now) / (float)time_interval\n                                             ));\n        }\n      }\n\n      std::sort(gidout.begin(), gidout.end());\n\n      for (auto& tup : gidout) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        row.emplace_back(std::get<0>(tup), format_ss);\n        row.emplace_back(std::get<1>(tup), format_s);\n        row.emplace_back(std::get<2>(tup), format_l);\n        row.emplace_back(std::get<3>(tup), format_l);\n      }\n\n      table_group.AddRows(table_data);\n      out += table_group.GenerateTable(HEADER).c_str();\n      table_data.clear();\n    } else {\n      std::vector<std::tuple<std::string, std::string, unsigned long long,\n          unsigned long long, unsigned long long, unsigned long long,\n          unsigned long long, unsigned long long, unsigned long long, std::string>>\n          uidout_sec, gidout_sec;\n      std::vector<std::tuple<std::string, std::string, unsigned long long,\n          unsigned long long, unsigned long long, unsigned long long,\n          unsigned long long>>\n          uidout_b, gidout_b;\n      //std::vector<std::tuple<std::string, std::string, unsigned long long,\n      //    unsigned long long, unsigned long long, unsigned long long, unsigned long long>>\n      //    uidout, gidout;\n      TableData table_data;\n      XrdOucString marker_b =\n        \"\\n┏━> Sum of bytes transferred in last 1m/5m/1h/24h and total sum: \\n\";\n      //! User statistic\n      TableFormatterBase table_user_b;\n\n      if (!monitoring) {\n        table_user_b.SetHeader({\n          std::make_tuple(\"user\", 5, format_ss),\n          std::make_tuple(\"io value\", 24, format_s),\n          std::make_tuple(\"1min\", 8, format_l),\n          std::make_tuple(\"5min\", 8, format_l),\n          std::make_tuple(\"1h\", 8, format_l),\n          std::make_tuple(\"24h\", 8, format_l),\n          std::make_tuple(\"sum\", 8, format_l),\n        });\n      } else {\n        table_user_b.SetHeader({\n          std::make_tuple(\"uid\", 0, format_ss),\n          std::make_tuple(\"measurement\", 0, format_s),\n          std::make_tuple(\"60s\", 0, format_l),\n          std::make_tuple(\"300s\", 0, format_l),\n          std::make_tuple(\"3600s\", 0, format_l),\n          std::make_tuple(\"86400s\", 0, format_l),\n          std::make_tuple(\"total\", 0, format_l),\n        });\n      }\n\n      XrdOucString marker_sec =\n        \"\\n┏━> Transfer (tf) sample info every 5 min: tf time for 90/95/99% of data, max tf and report times, average tf size, tf count.\\n\";\n      TableFormatterBase table_user_sec;\n\n      if (sample_stat) {\n        if (!monitoring) {\n          table_user_sec.SetHeader({\n            std::make_tuple(\"user\", 5, format_ss),\n            std::make_tuple(\"io value\", 24, format_s),\n            std::make_tuple(\"90% [s]\", 8, format_l),\n            std::make_tuple(\"95% [s]\", 8, format_l),\n            std::make_tuple(\"99% [s]\", 8, format_l),\n            std::make_tuple(\"max [s]\", 8, format_l),\n            std::make_tuple(\"max report [s]\", 8, format_l),\n            std::make_tuple(\"avg tf size\", 8, format_l),\n            std::make_tuple(\"tf #\", 8, format_l),\n            std::make_tuple(\"sample end time\", 24, format_s)\n          });\n        } else {\n          table_user_sec.SetHeader({\n            std::make_tuple(\"uid\", 0, format_ss),\n            std::make_tuple(\"measurement\", 0, format_s),\n            std::make_tuple(\"tfsecto90p\", 0, format_l),\n            std::make_tuple(\"tfsecto95p\", 0, format_l),\n            std::make_tuple(\"tfsecto99p\", 0, format_l),\n            std::make_tuple(\"maxtransfersec\", 0, format_l),\n            std::make_tuple(\"maxreportsec\", 0, format_l),\n            std::make_tuple(\"avgtfsize5min\", 0, format_l),\n            std::make_tuple(\"tfcount\", 0, format_l),\n            std::make_tuple(\"sampletimestamp\", 0, format_s)\n          });\n        }\n      }\n\n      for (auto tuit = IostatPeriodsUid.begin(); tuit != IostatPeriodsUid.end();\n           tuit++) {\n        for (auto it = tuit->second.begin(); it != tuit->second.end(); ++it) {\n          std::string username;\n\n          if (numerical) {\n            username = std::to_string(it->first);\n          } else {\n            int terrc = 0;\n            username = eos::common::Mapping::UidToUserName(it->first, terrc);\n          }\n\n          // getting tag stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n          uidout_b.emplace_back(std::make_tuple(username, tuit->first.c_str(),\n                                                it->second.GetDataInPeriod(60, 0, now),\n                                                it->second.GetDataInPeriod(300, 0, now),\n                                                it->second.GetDataInPeriod(3600, 0, now),\n                                                it->second.GetDataInPeriod(86400, 0, now),\n                                                IostatUid[tuit->first][it->first]\n                                               ));\n\n          if (sample_stat) {\n            std::string sample_time = \"\";\n\n            if (!monitoring) {\n              sample_time = it->second.GetLastSampleUpdateTimestamp(true);\n            } else {\n              sample_time = it->second.GetLastSampleUpdateTimestamp(false);\n            }\n\n            uidout_sec.emplace_back(std::make_tuple(username, tuit->first.c_str(),\n                                                    it->second.GetTimeToPercComplete(P90),\n                                                    it->second.GetTimeToPercComplete(P95),\n                                                    it->second.GetTimeToPercComplete(P99),\n                                                    it->second.GetLongestTransferTime(),\n                                                    it->second.GetLongestReportTime(),\n                                                    it->second.GetAvgTransferSize(),\n                                                    it->second.GetTfCountInSample(),\n                                                    sample_time\n                                                   ));\n          }\n        }\n      }\n\n      std::sort(uidout_b.begin(), uidout_b.end());\n      std::sort(uidout_sec.begin(), uidout_sec.end());\n\n      for (auto& tup : uidout_b) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        row.emplace_back(std::get<0>(tup), format_ss);\n        row.emplace_back(std::get<1>(tup), format_s);\n        row.emplace_back(std::get<2>(tup), format_l);\n        row.emplace_back(std::get<3>(tup), format_l);\n        row.emplace_back(std::get<4>(tup), format_l);\n        row.emplace_back(std::get<5>(tup), format_l);\n        row.emplace_back(std::get<6>(tup), format_l);\n      }\n\n      table_user_b.AddRows(table_data);\n      out += !monitoring ? marker_b : \"\";\n      out += table_user_b.GenerateTable(HEADER).c_str();\n      table_data.clear();\n\n      if (sample_stat) {\n        for (auto& tup : uidout_sec) {\n          table_data.emplace_back();\n          TableRow& row = table_data.back();\n          row.emplace_back(std::get<0>(tup), format_ss);\n          row.emplace_back(std::get<1>(tup), format_s);\n          row.emplace_back(std::get<2>(tup), format_l);\n          row.emplace_back(std::get<3>(tup), format_l);\n          row.emplace_back(std::get<4>(tup), format_l);\n          row.emplace_back(std::get<5>(tup), format_l);\n          row.emplace_back(std::get<6>(tup), format_l);\n          row.emplace_back(std::get<7>(tup), format_l);\n          row.emplace_back(std::get<8>(tup), format_l);\n          row.emplace_back(std::get<9>(tup), format_s);\n        }\n\n        table_user_sec.AddRows(table_data);\n        out += !monitoring ? marker_sec : \"\";\n        out += table_user_sec.GenerateTable(HEADER).c_str();\n        table_data.clear();\n      }\n\n      //! Group statistic\n      TableFormatterBase table_group_b;\n\n      if (!monitoring) {\n        table_group_b.SetHeader({\n          std::make_tuple(\"group\", 5, format_ss),\n          std::make_tuple(\"io value\", 24, format_s),\n          std::make_tuple(\"1min\", 8, format_l),\n          std::make_tuple(\"5min\", 8, format_l),\n          std::make_tuple(\"1h\", 8, format_l),\n          std::make_tuple(\"24h\", 8, format_l),\n          std::make_tuple(\"sum\", 8, format_l),\n        });\n      } else {\n        table_group_b.SetHeader({\n          std::make_tuple(\"gid\", 0, format_ss),\n          std::make_tuple(\"measurement\", 0, format_s),\n          std::make_tuple(\"60s\", 0, format_l),\n          std::make_tuple(\"300s\", 0, format_l),\n          std::make_tuple(\"3600s\", 0, format_l),\n          std::make_tuple(\"86400s\", 0, format_l),\n          std::make_tuple(\"total\", 0, format_l),\n        });\n      }\n\n      TableFormatterBase table_group_sec;\n\n      if (sample_stat) {\n        if (!monitoring) {\n          table_group_sec.SetHeader({\n            std::make_tuple(\"group\", 5, format_ss),\n            std::make_tuple(\"io value\", 24, format_s),\n            std::make_tuple(\"90% [s]\", 8, format_l),\n            std::make_tuple(\"95% [s]\", 8, format_l),\n            std::make_tuple(\"99% [s]\", 8, format_l),\n            std::make_tuple(\"max [s]\", 8, format_l),\n            std::make_tuple(\"max report [s]\", 8, format_l),\n            std::make_tuple(\"avg tf size\", 8, format_l),\n            std::make_tuple(\"tf #\", 8, format_l),\n            std::make_tuple(\"sample end time\", 24, format_s)\n          });\n        } else {\n          table_group_sec.SetHeader({\n            std::make_tuple(\"gid\", 0, format_ss),\n            std::make_tuple(\"measurement\", 0, format_s),\n            std::make_tuple(\"tfsecto90p\", 0, format_l),\n            std::make_tuple(\"tfsecto95p\", 0, format_l),\n            std::make_tuple(\"tfsecto99p\", 0, format_l),\n            std::make_tuple(\"maxtransfersec\", 0, format_l),\n            std::make_tuple(\"maxreportsec\", 0, format_l),\n            std::make_tuple(\"avgtfsize5min\", 0, format_l),\n            std::make_tuple(\"tfcount\", 0, format_l),\n            std::make_tuple(\"sampletimestamp\", 0, format_s)\n\n          });\n        }\n      }\n\n      for (auto tgit = IostatPeriodsGid.begin(); tgit != IostatPeriodsGid.end();\n           tgit++) {\n        for (auto it = tgit->second.begin(); it != tgit->second.end(); ++it) {\n          std::string groupname;\n\n          if (numerical) {\n            groupname = std::to_string(it->first);\n          } else {\n            int terrc = 0;\n            groupname = eos::common::Mapping::GidToGroupName(it->first, terrc);\n          }\n\n          // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n          gidout_b.emplace_back(std::make_tuple(groupname, tgit->first.c_str(),\n                                                it->second.GetDataInPeriod(60, 0, now), it->second.GetDataInPeriod(300, 0, now),\n                                                it->second.GetDataInPeriod(3600, 0, now), it->second.GetDataInPeriod(86400, 0,\n                                                    now),\n                                                IostatGid[tgit->first][it->first]\n                                               ));\n\n          if (sample_stat) {\n            std::string sample_time = \"\";\n\n            if (!monitoring) {\n              sample_time = it->second.GetLastSampleUpdateTimestamp(true);\n            } else {\n              sample_time = it->second.GetLastSampleUpdateTimestamp(false);\n            }\n\n            gidout_sec.emplace_back(std::make_tuple(groupname, tgit->first.c_str(),\n                                                    it->second.GetTimeToPercComplete(P90),\n                                                    it->second.GetTimeToPercComplete(P95),\n                                                    it->second.GetTimeToPercComplete(P99),\n                                                    it->second.GetLongestTransferTime(),\n                                                    it->second.GetLongestReportTime(),\n                                                    it->second.GetAvgTransferSize(),\n                                                    it->second.GetTfCountInSample(),\n                                                    sample_time\n                                                   ));\n          }\n        }\n      }\n\n      std::sort(gidout_b.begin(), gidout_b.end());\n      std::sort(gidout_sec.begin(), gidout_sec.end());\n\n      for (auto& tup : gidout_b) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        row.emplace_back(std::get<0>(tup), format_ss);\n        row.emplace_back(std::get<1>(tup), format_s);\n        row.emplace_back(std::get<2>(tup), format_l);\n        row.emplace_back(std::get<3>(tup), format_l);\n        row.emplace_back(std::get<4>(tup), format_l);\n        row.emplace_back(std::get<5>(tup), format_l);\n        row.emplace_back(std::get<6>(tup), format_l);\n      }\n\n      table_group_b.AddRows(table_data);\n      out += !monitoring ? marker_b : \"\";\n      out += table_group_b.GenerateTable(HEADER).c_str();\n      table_data.clear();\n\n      if (sample_stat) {\n        for (auto& tup : gidout_sec) {\n          table_data.emplace_back();\n          TableRow& row = table_data.back();\n          row.emplace_back(std::get<0>(tup), format_ss);\n          row.emplace_back(std::get<1>(tup), format_s);\n          row.emplace_back(std::get<2>(tup), format_l);\n          row.emplace_back(std::get<3>(tup), format_l);\n          row.emplace_back(std::get<4>(tup), format_l);\n          row.emplace_back(std::get<5>(tup), format_l);\n          row.emplace_back(std::get<6>(tup), format_l);\n          row.emplace_back(std::get<7>(tup), format_l);\n          row.emplace_back(std::get<8>(tup), format_l);\n          row.emplace_back(std::get<9>(tup), format_s);\n        }\n\n        table_group_sec.AddRows(table_data);\n        out += !monitoring ? marker_sec : \"\";\n        out += table_group_sec.GenerateTable(HEADER).c_str();\n        table_data.clear();\n      }\n    }\n  }\n\n  if (top) {\n    TableFormatterBase table;\n    TableData table_data;\n\n    if (!monitoring) {\n      table.SetHeader({\n        std::make_tuple(\"io value\", 18, format_ss),\n        std::make_tuple(\"ranking by\", 10, format_s),\n        std::make_tuple(\"rank\", 8, format_ll),\n        std::make_tuple(\"who\", 4, format_s),\n        std::make_tuple(\"sum\", 8, format_l)\n      });\n    } else {\n      table.SetHeader({\n        std::make_tuple(\"measurement\", 0, format_ss),\n        std::make_tuple(\"rank\", 0, format_ll),\n        std::make_tuple(\"uid\", 0, format_s),\n        std::make_tuple(\"gid\", 0, format_s),\n        std::make_tuple(\"counter\", 0, format_l)\n      });\n    }\n\n    for (auto it = tags.begin(); it != tags.end(); ++it) {\n      std::vector <std::tuple<unsigned long long, uid_t>> uidout, gidout;\n      table.AddSeparator();\n\n      // by uid name\n      for (auto sit : IostatUid[*it]) {\n        uidout.push_back(std::make_tuple(sit.second, sit.first));\n      }\n\n      std::sort(uidout.begin(), uidout.end());\n      std::reverse(uidout.begin(), uidout.end());\n      int topplace = 0;\n\n      for (auto sit : uidout) {\n        topplace++;\n        uid_t uid = std::get<1>(sit);\n        unsigned long long counter = std::get<0>(sit);\n        std::string username;\n\n        if (numerical) {\n          username = std::to_string(uid);\n        } else {\n          int terrc = 0;\n          username = eos::common::Mapping::UidToUserName(uid, terrc);\n        }\n\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        row.emplace_back(it->c_str(), format_ss);\n\n        if (!monitoring) {\n          row.emplace_back(\"user\", format_s);\n        }\n\n        row.emplace_back(topplace, format_ll);\n        row.emplace_back(username, format_s);\n\n        if (monitoring) {\n          row.emplace_back(\"\", \"\", \"\", true);\n        }\n\n        row.emplace_back(counter, format_l);\n      }\n\n      // by gid name\n      for (auto sit : IostatGid[*it]) {\n        gidout.push_back(std::make_tuple(sit.second, sit.first));\n      }\n\n      std::sort(gidout.begin(), gidout.end());\n      std::reverse(gidout.begin(), gidout.end());\n      topplace = 0;\n\n      for (auto sit : gidout) {\n        topplace++;\n        uid_t gid = std::get<1>(sit);\n        unsigned long long counter = std::get<0>(sit);\n        std::string groupname;\n\n        if (numerical) {\n          groupname = std::to_string(gid);\n        } else {\n          int terrc = 0;\n          groupname = eos::common::Mapping::GidToGroupName(gid, terrc);\n        }\n\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        row.emplace_back(it->c_str(), format_ss);\n\n        if (!monitoring) {\n          row.emplace_back(\"group\", format_s);\n        }\n\n        row.emplace_back(topplace, format_ll);\n\n        if (monitoring) {\n          row.emplace_back(\"\", \"\", \"\", true);\n        }\n\n        row.emplace_back(groupname, format_s);\n        row.emplace_back(counter, format_l);\n      }\n    }\n\n    table.AddRows(table_data);\n    out += table.GenerateTable(HEADER).c_str();\n  }\n\n  if (domain) {\n    TableData table_data;\n\n    if (interval) {\n      TableFormatterBase table;\n\n      //! User statistic\n      if (!monitoring) {\n        table.SetHeader({\n          std::make_tuple(\"io\", 5, format_ss),\n          std::make_tuple(\"domain\", 24, format_s),\n          std::make_tuple(\"data in interval\", 8, format_l),\n          std::make_tuple(\"avg rate [B/s]\", 8, format_l),\n        });\n      } else {\n        table.SetHeader({\n          std::make_tuple(\"measurement\", 0, format_ss),\n          std::make_tuple(\"domain\", 0, format_s),\n          std::make_tuple(\"intervaldata\", 8, format_l),\n          std::make_tuple(\"intervalrate\", 8, format_l),\n        });\n      }\n\n      // IO out bytes\n      for (auto it = IostatPeriodsDomainIOrb.begin();\n           it != IostatPeriodsDomainIOrb.end();\n           ++it) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        std::string name = !monitoring ? \"out\" : \"domain_io_out\";\n        row.emplace_back(name, format_ss);\n        row.emplace_back(it->first.c_str(), format_s);\n        row.emplace_back(it->second.GetDataInPeriod(time_interval, time_ago, now),\n                         format_ll);\n        row.emplace_back(it->second.GetDataInPeriod(time_interval, time_ago,\n                         now) / (float)time_interval, format_ll);\n      }\n\n      // IO in bytes\n      for (auto it = IostatPeriodsDomainIOwb.begin();\n           it != IostatPeriodsDomainIOwb.end();\n           ++it) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        std::string name = !monitoring ? \"in\" : \"domain_io_in\";\n        row.emplace_back(name, format_ss);\n        row.emplace_back(it->first.c_str(), format_s);\n        // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n        row.emplace_back(it->second.GetDataInPeriod(time_interval, time_ago, now),\n                         format_ll);\n        row.emplace_back(it->second.GetDataInPeriod(time_interval, time_ago,\n                         now) / (float)time_interval, format_ll);\n      }\n\n      table.AddRows(table_data);\n      out += table.GenerateTable(HEADER).c_str();\n      table_data.clear();\n    } else {\n      XrdOucString marker_b =\n        \"\\n┏━> Sum of bytes transferred in last 1m/5m/1h/24h and total sum: \\n\";\n      //! User statistic\n      TableFormatterBase table_domain_b;\n\n      if (!monitoring) {\n        table_domain_b.SetHeader({\n          std::make_tuple(\"io\", 5, format_ss),\n          std::make_tuple(\"domain\", 24, format_s),\n          std::make_tuple(\"1min\", 8, format_l),\n          std::make_tuple(\"5min\", 8, format_l),\n          std::make_tuple(\"1h\", 8, format_l),\n          std::make_tuple(\"24h\", 8, format_l),\n          std::make_tuple(\"sum\", 8, format_l),\n        });\n      } else {\n        table_domain_b.SetHeader({\n          std::make_tuple(\"measurement\", 0, format_ss),\n          std::make_tuple(\"domain\", 0, format_s),\n          std::make_tuple(\"60s\", 0, format_l),\n          std::make_tuple(\"300s\", 0, format_l),\n          std::make_tuple(\"3600s\", 0, format_l),\n          std::make_tuple(\"86400s\", 0, format_l),\n          std::make_tuple(\"total\", 0, format_l),\n        });\n      }\n\n      // IO out bytes\n      for (auto it = IostatPeriodsDomainIOrb.begin();\n           it != IostatPeriodsDomainIOrb.end();\n           ++it) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        std::string name = !monitoring ? \"out\" : \"domain_io_out\";\n        row.emplace_back(name, format_ss);\n        row.emplace_back(it->first.c_str(), format_s);\n        // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n        row.emplace_back(it->second.GetDataInPeriod(60, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(300, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(3600, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(86400, 0, now), format_l);\n        row.emplace_back(it->second.GetTotalSum(), format_l);\n      }\n\n      // IO in bytes\n      for (auto it = IostatPeriodsDomainIOwb.begin();\n           it != IostatPeriodsDomainIOwb.end();\n           ++it) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        std::string name = !monitoring ? \"in\" : \"domain_io_in\";\n        row.emplace_back(name, format_ss);\n        row.emplace_back(it->first.c_str(), format_s);\n        // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n        row.emplace_back(it->second.GetDataInPeriod(60, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(300, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(3600, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(86400, 0, now), format_l);\n        row.emplace_back(it->second.GetTotalSum(), format_l);\n      }\n\n      table_domain_b.AddRows(table_data);\n      out += !monitoring ? marker_b : \"\";\n      out += table_domain_b.GenerateTable(HEADER).c_str();\n      table_data.clear();\n    }\n  }\n\n  if (apps) {\n    TableData table_data;\n\n    if (interval) {\n      TableFormatterBase table;\n\n      //! User statistic\n      if (!monitoring) {\n        table.SetHeader({\n          std::make_tuple(\"io\", 5, format_ss),\n          std::make_tuple(\"application\", 24, format_s),\n          std::make_tuple(\"data in interval\", 8, format_l),\n          std::make_tuple(\"avg rate [B/s]\", 8, format_l),\n        });\n      } else {\n        table.SetHeader({\n          std::make_tuple(\"measurement\", 0, format_ss),\n          std::make_tuple(\"application\", 0, format_s),\n          std::make_tuple(\"intervaldata\", 8, format_l),\n          std::make_tuple(\"intervalrate\", 8, format_l),\n        });\n      }\n\n      // IO out bytes\n      for (auto it = IostatPeriodsAppIOrb.begin(); it != IostatPeriodsAppIOrb.end();\n           ++it) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        std::string name = (!monitoring ? \"out\" : \"app_io_out\");\n        row.emplace_back(name, format_ss);\n        row.emplace_back(it->first.c_str(), format_s);\n        // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n        row.emplace_back(it->second.GetDataInPeriod(time_interval, time_ago, now),\n                         format_ll);\n        row.emplace_back(it->second.GetDataInPeriod(time_interval, time_ago,\n                         now) / (float)time_interval, format_ll);\n      }\n\n      // IO in bytes\n      for (auto it = IostatPeriodsAppIOwb.begin(); it != IostatPeriodsAppIOwb.end();\n           ++it) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        std::string name = (!monitoring ? \"in\" : \"app_io_in\");\n        row.emplace_back(name, format_ss);\n        row.emplace_back(it->first.c_str(), format_s);\n        // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n        row.emplace_back(it->second.GetDataInPeriod(time_interval, time_ago, now),\n                         format_ll);\n        row.emplace_back(it->second.GetDataInPeriod(time_interval, time_ago,\n                         now) / (float)time_interval, format_ll);\n      }\n\n      table.AddRows(table_data);\n      out += table.GenerateTable(HEADER).c_str();\n      table_data.clear();\n    } else {\n      XrdOucString marker_b =\n        \"\\n┏━> Sum of bytes transferred in last 1m/5m/1h/24h and total sum: \\n\";\n      //! User statistic\n      TableFormatterBase table_app_b;\n\n      if (!monitoring) {\n        table_app_b.SetHeader({\n          std::make_tuple(\"io\", 5, format_ss),\n          std::make_tuple(\"application\", 24, format_s),\n          std::make_tuple(\"1min\", 8, format_l),\n          std::make_tuple(\"5min\", 8, format_l),\n          std::make_tuple(\"1h\", 8, format_l),\n          std::make_tuple(\"24h\", 8, format_l),\n          std::make_tuple(\"sum\", 8, format_l),\n        });\n      } else {\n        table_app_b.SetHeader({\n          std::make_tuple(\"measurement\", 0, format_ss),\n          std::make_tuple(\"application\", 0, format_s),\n          std::make_tuple(\"60s\", 0, format_l),\n          std::make_tuple(\"300s\", 0, format_l),\n          std::make_tuple(\"3600s\", 0, format_l),\n          std::make_tuple(\"86400s\", 0, format_l),\n          std::make_tuple(\"total\", 0, format_l),\n        });\n      }\n\n      XrdOucString marker_sec =\n        \"\\n┏━> Transfer (tf) sample info every 5 min: tf time for 90/95/99% of data, max tf and report times, average tf size, tf count.\\n\";\n      TableFormatterBase table_app_sec;\n\n      if (sample_stat) {\n        if (!monitoring) {\n          table_app_sec.SetHeader({\n            std::make_tuple(\"io\", 5, format_ss),\n            std::make_tuple(\"application\", 24, format_s),\n            std::make_tuple(\"90% [s]\", 8, format_l),\n            std::make_tuple(\"95% [s]\", 8, format_l),\n            std::make_tuple(\"99% [s]\", 8, format_l),\n            std::make_tuple(\"max [s]\", 8, format_l),\n            std::make_tuple(\"max report [s]\", 8, format_l),\n            std::make_tuple(\"avg tf size\", 8, format_l),\n            std::make_tuple(\"tf #\", 8, format_l),\n            std::make_tuple(\"sample end time\", 24, format_s),\n          });\n        } else {\n          table_app_sec.SetHeader({\n            std::make_tuple(\"measurement\", 0, format_ss),\n            std::make_tuple(\"application\", 0, format_s),\n            std::make_tuple(\"tfsecto90p\", 0, format_l),\n            std::make_tuple(\"tfsecto95p\", 0, format_l),\n            std::make_tuple(\"tfsecto99p\", 0, format_l),\n            std::make_tuple(\"maxtransfersec\", 0, format_l),\n            std::make_tuple(\"maxreportsec\", 0, format_l),\n            std::make_tuple(\"avgtfsize5min\", 0, format_l),\n            std::make_tuple(\"tfcount\", 0, format_l),\n            std::make_tuple(\"sampletimestamp\", 0, format_s)\n          });\n        }\n      }\n\n      // IO out bytes\n      for (auto it = IostatPeriodsAppIOrb.begin(); it != IostatPeriodsAppIOrb.end();\n           ++it) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        std::string name = (!monitoring ? \"out\" : \"app_io_out\");\n        row.emplace_back(name, format_ss);\n        row.emplace_back(it->first.c_str(), format_s);\n        // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n        row.emplace_back(it->second.GetDataInPeriod(60, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(300, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(3600, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(86400, 0, now), format_l);\n        row.emplace_back(it->second.GetTotalSum(), format_l);\n      }\n\n      // IO in bytes\n      for (auto it = IostatPeriodsAppIOwb.begin(); it != IostatPeriodsAppIOwb.end();\n           ++it) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        std::string name = (!monitoring ? \"in\" : \"app_io_in\");\n        row.emplace_back(name, format_ss);\n        row.emplace_back(it->first.c_str(), format_s);\n        // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n        row.emplace_back(it->second.GetDataInPeriod(60, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(300, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(3600, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(86400, 0, now), format_l);\n        row.emplace_back(it->second.GetTotalSum(), format_l);\n      }\n\n      out += !monitoring ? marker_b : \"\";\n      table_app_b.AddRows(table_data);\n      out += table_app_b.GenerateTable(HEADER).c_str();\n      table_data.clear();\n\n      if (sample_stat) {\n        // IO out bytes\n        for (auto it = IostatPeriodsAppIOrb.begin(); it != IostatPeriodsAppIOrb.end();\n             ++it) {\n          table_data.emplace_back();\n          TableRow& row = table_data.back();\n          std::string name = (!monitoring ? \"out\" : \"app_io_out\");\n          std::string sample_time = \"\";\n\n          if (!monitoring) {\n            sample_time = it->second.GetLastSampleUpdateTimestamp(true);\n          } else {\n            sample_time = it->second.GetLastSampleUpdateTimestamp(false);\n          }\n\n          row.emplace_back(name, format_ss);\n          row.emplace_back(it->first.c_str(), format_s);\n          // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n          row.emplace_back(it->second.GetTimeToPercComplete(P90), format_l);\n          row.emplace_back(it->second.GetTimeToPercComplete(P95), format_l);\n          row.emplace_back(it->second.GetTimeToPercComplete(P99), format_l);\n          row.emplace_back(it->second.GetLongestTransferTime(), format_l);\n          row.emplace_back(it->second.GetLongestReportTime(), format_l);\n          row.emplace_back(it->second.GetAvgTransferSize(), format_l);\n          row.emplace_back(it->second.GetTfCountInSample(), format_l);\n          row.emplace_back(sample_time, format_s);\n        }\n\n        // IO in bytes\n        for (auto it = IostatPeriodsAppIOwb.begin(); it != IostatPeriodsAppIOwb.end();\n             ++it) {\n          table_data.emplace_back();\n          TableRow& row = table_data.back();\n          std::string sample_time = \"\";\n\n          if (!monitoring) {\n            sample_time = it->second.GetLastSampleUpdateTimestamp(true);\n          } else {\n            sample_time = it->second.GetLastSampleUpdateTimestamp(false);\n          }\n\n          std::string name = (!monitoring ? \"in\" : \"app_io_in\");\n          row.emplace_back(name, format_ss);\n          row.emplace_back(it->first.c_str(), format_s);\n          // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n          row.emplace_back(it->second.GetTimeToPercComplete(P90), format_l);\n          row.emplace_back(it->second.GetTimeToPercComplete(P95), format_l);\n          row.emplace_back(it->second.GetTimeToPercComplete(P99), format_l);\n          row.emplace_back(it->second.GetLongestTransferTime(), format_l);\n          row.emplace_back(it->second.GetLongestReportTime(), format_l);\n          row.emplace_back(it->second.GetAvgTransferSize(), format_l);\n          row.emplace_back(it->second.GetTfCountInSample(), format_l);\n          row.emplace_back(sample_time, format_s);\n        }\n\n        out += !monitoring ? marker_sec : \"\";\n        table_app_sec.AddRows(table_data);\n        out += table_app_sec.GenerateTable(HEADER).c_str();\n        table_data.clear();\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Compute and print out the namespace popularity ranking\n//------------------------------------------------------------------------------\nvoid\nIostat::PrintNsPopularity(XrdOucString& out, XrdOucString option) const\n{\n  size_t limit = 10;\n  size_t popularitybin = (((time(NULL))) % (IOSTAT_POPULARITY_DAY *\n                          IOSTAT_POPULARITY_HISTORY_DAYS)) / IOSTAT_POPULARITY_DAY;\n  size_t days = 1;\n  time_t tmarker = time(NULL) / IOSTAT_POPULARITY_DAY * IOSTAT_POPULARITY_DAY;\n  bool monitoring = false;\n  bool bycount = false;\n  bool bybytes = false;\n  bool hotfiles = false;\n\n  if ((option.find(\"-m\")) != STR_NPOS) {\n    monitoring = true;\n  }\n\n  if ((option.find(\"-a\")) != STR_NPOS) {\n    limit = 999999999;\n  }\n\n  if ((option.find(\"-100\")) != STR_NPOS) {\n    limit = 100;\n  }\n\n  if ((option.find(\"-1000\")) != STR_NPOS) {\n    limit = 1000;\n  }\n\n  if ((option.find(\"-10000\")) != STR_NPOS) {\n    limit = 10000;\n  }\n\n  if ((option.find(\"-n\") != STR_NPOS)) {\n    bycount = true;\n  }\n\n  if ((option.find(\"-b\") != STR_NPOS)) {\n    bybytes = true;\n  }\n\n  if ((option.find(\"-w\") != STR_NPOS)) {\n    days = IOSTAT_POPULARITY_HISTORY_DAYS;\n  }\n\n  if (!(bycount || bybytes)) {\n    bybytes = bycount = true;\n  }\n\n  if ((option.find(\"-f\") != STR_NPOS)) {\n    hotfiles = true;\n  }\n\n  std::string format_s = !monitoring ? \"s\" : \"os\";\n  std::string format_ss = !monitoring ? \"-s\" : \"os\";\n  std::string format_l = !monitoring ? \"l\" : \"ol\";\n  std::string format_ll = !monitoring ? \"-l.\" : \"ol\";\n  std::string format_lll = !monitoring ? \"+l\" : \"ol\";\n  std::string unit = !monitoring ? \"B\" : \"\";\n\n  // The 'hotfiles' are the files with highest number of present file opens\n  if (hotfiles) {\n    eos::common::RWMutexReadLock rLock(FsView::gFsView.ViewMutex);\n    // print the hotfiles report\n    std::set<eos::common::FileSystem::fsid_t>::const_iterator it;\n    std::vector<std::string> r_open_vector;\n    std::vector<std::string> w_open_vector;\n    std::string key;\n    std::string val;\n    TableFormatterBase table;\n    TableData table_data;\n\n    if (!monitoring) {\n      table.SetHeader({\n        std::make_tuple(\"type\", 5, format_ss),\n        std::make_tuple(\"heat\", 5, format_s),\n        std::make_tuple(\"fs\", 5, format_s),\n        std::make_tuple(\"host\", 24, format_s),\n        std::make_tuple(\"path\", 24, format_ss)\n      });\n    } else {\n      table.SetHeader({\n        std::make_tuple(\"measurement\", 0, format_ss),\n        std::make_tuple(\"access\", 0, format_s),\n        std::make_tuple(\"heat\", 0, format_s),\n        std::make_tuple(\"fsid\", 0, format_l),\n        std::make_tuple(\"path\", 0, format_ss),\n        std::make_tuple(\"fxid\", 0, format_s)\n      });\n    }\n\n    for (auto it = FsView::gFsView.mIdView.begin();\n         it != FsView::gFsView.mIdView.end(); it++) {\n      r_open_vector.clear();\n      w_open_vector.clear();\n      FileSystem* fs = it->second;\n\n      if (!fs) {\n        continue;\n      }\n\n      std::string r_open_hotfiles = fs->GetString(\"stat.ropen.hotfiles\");\n      std::string w_open_hotfiles = fs->GetString(\"stat.wopen.hotfiles\");\n      std::string node_queue = fs->GetString(\"queue\");\n      auto it_node = FsView::gFsView.mNodeView.find(node_queue);\n\n      if (it_node == FsView::gFsView.mNodeView.end()) {\n        continue;\n      }\n\n      // Check if the corresponding node has a heartbeat\n      bool hasHeartbeat = it_node->second->HasHeartbeat();\n\n      // we only show the reports from the last minute, there could be pending values\n      if (!hasHeartbeat) {\n        r_open_hotfiles = \"\";\n        w_open_hotfiles = \"\";\n      }\n\n      if (r_open_hotfiles == \" \") {\n        r_open_hotfiles = \"\";\n      }\n\n      if (w_open_hotfiles == \" \") {\n        w_open_hotfiles = \"\";\n      }\n\n      eos::common::StringConversion::Tokenize(r_open_hotfiles, r_open_vector);\n      eos::common::StringConversion::Tokenize(w_open_hotfiles, w_open_vector);\n      std::string host = fs->GetString(\"host\");\n      std::string path;\n      std::string id = fs->GetString(\"id\");\n      std::vector<std::tuple<std::string, std::string, std::string,\n          std::string, std::string>> data;\n      std::vector<std::tuple<std::string, std::string, std::string,\n          long long unsigned, std::string, std::string>> data_monitoring;\n\n      // Get information for read\n      for (size_t i = 0; i < r_open_vector.size(); i++) {\n        eos::common::StringConversion::SplitKeyValue(r_open_vector[i], key, val);\n        int rank = 0;\n\n        if (key.c_str()) {\n          rank = atoi(key.c_str());\n        }\n\n        {\n          unsigned long long fid = eos::common::FileId::Hex2Fid(val.c_str());\n          eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, fid);\n\n          try {\n            auto fmd = gOFS->eosFileService->getFileMD(fid);\n            // Do not lock the fmd before getting its URI\n            path = gOFS->eosView->getUri(fmd.get());\n          } catch (eos::MDException& e) {\n            path = \"<undef>\";\n          }\n        }\n\n        if (rank > 1) {\n          data.emplace_back(std::make_tuple(\n                              \"read\", key.c_str(), id.c_str(), host.c_str(), path.c_str()));\n        }\n\n        data_monitoring.emplace_back(std::make_tuple(\n                                       \"hotfile\", \"read\", key.c_str(), it->first, path.c_str(), val.c_str()));\n      }\n\n      // Get information for write\n      for (size_t i = 0; i < w_open_vector.size(); i++) {\n        eos::common::StringConversion::SplitKeyValue(w_open_vector[i], key, val);\n        int rank = 0;\n\n        if (key.c_str()) {\n          rank = atoi(key.c_str());\n        }\n\n        {\n          unsigned long long fid = eos::common::FileId::Hex2Fid(val.c_str());\n          eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, fid);\n\n          try {\n            auto fmd = gOFS->eosFileService->getFileMD(fid);\n            // Do not lock the fmd before getting its URI\n            path = gOFS->eosView->getUri(fmd.get());\n          } catch (eos::MDException& e) {\n            path = \"<undef>\";\n          }\n        }\n\n        if (rank > 1) {\n          data.emplace_back(std::make_tuple(\n                              \"write\", key.c_str(), id.c_str(), host.c_str(), path.c_str()));\n        }\n\n        data_monitoring.emplace_back(std::make_tuple(\n                                       \"hotfile\", \"write\", key.c_str(), it->first, path.c_str(), val.c_str()));\n      }\n\n      // Sort and output\n      if (!monitoring) {\n        std::sort(data.begin(), data.end());\n\n        for (auto it : data) {\n          table_data.emplace_back();\n          TableRow& row = table_data.back();\n          row.emplace_back(std::get<0>(it), format_ss);\n          row.emplace_back(std::get<1>(it), format_s);\n          row.emplace_back(std::get<2>(it), format_s);\n          row.emplace_back(std::get<3>(it), format_s);\n          row.emplace_back(std::get<4>(it), format_ss);\n        }\n      } else {\n        std::sort(data_monitoring.begin(), data_monitoring.end());\n\n        for (auto mdata : data_monitoring) {\n          table_data.emplace_back();\n          TableRow& row = table_data.back();\n          row.emplace_back(std::get<0>(mdata), format_ss);\n          row.emplace_back(std::get<1>(mdata), format_s);\n          row.emplace_back(std::get<2>(mdata), format_s);\n          row.emplace_back(std::get<3>(mdata), format_l);\n          row.emplace_back(std::get<4>(mdata), format_ss);\n          row.emplace_back(std::get<5>(mdata), format_s);\n        }\n      }\n    }\n\n    table.AddRows(table_data);\n    out += table.GenerateTable(HEADER).c_str();\n    return;\n  }\n\n  //! Namespace IO ranking (popularity)\n  for (size_t pbin = 0; pbin < days; pbin++) {\n    std::unique_lock<std::mutex> scope_lock(mPopularityMutex);\n    size_t sbin = (IOSTAT_POPULARITY_HISTORY_DAYS + popularitybin - pbin) %\n                  IOSTAT_POPULARITY_HISTORY_DAYS;\n    google::sparse_hash_map<std::string, struct Popularity>::const_iterator it;\n    std::vector<popularity_t> popularity_nread(IostatPopularity[sbin].begin(),\n        IostatPopularity[sbin].end());\n    std::vector<popularity_t> popularity_rb(IostatPopularity[sbin].begin(),\n                                            IostatPopularity[sbin].end());\n    // sort them (backwards) by rb or nread\n    std::sort(popularity_nread.begin(), popularity_nread.end(),\n              PopularityCmp_nread());\n    std::sort(popularity_rb.begin(), popularity_rb.end(), PopularityCmp_rb());\n    XrdOucString marker = \"\\n┏━> Today\\n\";\n\n    switch (pbin) {\n    case 1:\n      marker = \"\\n┏━> Yesterday\\n\";\n      break;\n\n    case 2:\n      marker = \"\\n┏━> 2 days ago\\n\";\n      break;\n\n    case 3:\n      marker = \"\\n┏━> 3 days ago\\n\";\n      break;\n\n    case 4:\n      marker = \"\\n┏━> 4 days ago\\n\";\n      break;\n\n    case 5:\n      marker = \"\\n┏━> 5 days ago\\n\";\n      break;\n\n    case 6:\n      marker = \"\\n┏━> 6 days ago\\n\";\n    }\n\n    if (bycount) {\n      TableFormatterBase table;\n      TableData table_data;\n\n      if (!monitoring) {\n        table.SetHeader({\n          std::make_tuple(\"rank\", 5, format_ll),\n          std::make_tuple(\"by(read count)\", 12, format_s),\n          std::make_tuple(\"read bytes\", 10, format_lll),\n          std::make_tuple(\"path\", 24, format_ss),\n        });\n      } else {\n        table.SetHeader({\n          std::make_tuple(\"measurement\", 0, format_ss),\n          std::make_tuple(\"time\", 0, format_lll),\n          std::make_tuple(\"rank\", 0, format_ll),\n          std::make_tuple(\"nread\", 0, format_lll),\n          std::make_tuple(\"rb\", 0, format_lll),\n          std::make_tuple(\"path\", 0, format_ss)\n        });\n      }\n\n      size_t cnt = 0;\n\n      for (auto it : popularity_nread) {\n        cnt++;\n\n        if (cnt > limit) {\n          break;\n        }\n\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n\n        if (monitoring) {\n          row.emplace_back(\"popularitybyaccess\", format_ss);\n          row.emplace_back((unsigned) tmarker, format_lll);\n        }\n\n        row.emplace_back((int) cnt, format_ll);\n        row.emplace_back(it.second.nread, format_lll);\n        row.emplace_back(it.second.rb, format_lll, unit);\n        row.emplace_back(it.first.c_str(), format_s);\n      }\n\n      if (cnt > 0) {\n        out += !monitoring ? marker : \"\";\n        table.AddRows(table_data);\n        out += table.GenerateTable(HEADER).c_str();\n      }\n    }\n\n    if (bybytes) {\n      TableFormatterBase table;\n      TableData table_data;\n\n      if (!monitoring) {\n        table.SetHeader({\n          std::make_tuple(\"rank\", 5, format_ll),\n          std::make_tuple(\"by(read bytes)\", 12, format_s),\n          std::make_tuple(\"read count\", 10, format_lll),\n          std::make_tuple(\"path\", 24, format_ss),\n        });\n      } else {\n        table.SetHeader({\n          std::make_tuple(\"measurement\", 0, format_ss),\n          std::make_tuple(\"time\", 0, format_lll),\n          std::make_tuple(\"rank\", 0, format_ll),\n          std::make_tuple(\"nread\", 0, format_lll),\n          std::make_tuple(\"rb\", 0, format_lll),\n          std::make_tuple(\"path\", 0, format_ss)\n        });\n      }\n\n      size_t cnt = 0;\n\n      for (auto it : popularity_rb) {\n        cnt++;\n\n        if (cnt > limit) {\n          break;\n        }\n\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n\n        if (monitoring) {\n          row.emplace_back(\"popularitybyvolume\", format_ss);\n          row.emplace_back((unsigned) tmarker, format_lll);\n        }\n\n        row.emplace_back((int) cnt, format_ll);\n\n        if (!monitoring) {\n          row.emplace_back(it.second.rb, format_lll, unit);\n          row.emplace_back(it.second.nread, format_lll);\n        } else {\n          row.emplace_back(it.second.nread, format_lll);\n          row.emplace_back(it.second.rb, format_lll, unit);\n        }\n\n        row.emplace_back(it.first.c_str(), format_s);\n      }\n\n      table.AddRows(table_data);\n      out += table.GenerateTable(HEADER2).c_str();\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print namespace activity report for given path\n//------------------------------------------------------------------------------\nvoid\nIostat::PrintNsReport(const char* path, XrdOucString& out) const\n{\n  XrdOucString reportFile;\n  reportFile = gOFS->IoReportStorePath.c_str();\n  reportFile += \"/\";\n  reportFile += path;\n  std::ifstream inFile(reportFile.c_str());\n  std::string reportLine;\n  unsigned long long totalreadbytes = 0;\n  unsigned long long totalwritebytes = 0;\n  double totalreadtime = 0;\n  double totalwritetime = 0;\n  unsigned long long rcount = 0;\n  unsigned long long wcount = 0;\n\n  while (std::getline(inFile, reportLine)) {\n    XrdOucEnv ioreport(reportLine.c_str());\n    auto report = std::make_unique<eos::common::Report>(ioreport);\n    report->Dump(out);\n\n    if (!report->wb) {\n      rcount++;\n      totalreadtime += ((report->cts - report->ots) + (1.0 * (report->ctms -\n                        report->otms) / 1000000));\n      totalreadbytes += report->rb;\n    } else {\n      wcount++;\n      totalwritetime += ((report->cts - report->ots) + (1.0 *\n                         (report->ctms - report->otms) / 1000000));\n      totalwritebytes += report->wb;\n    }\n  }\n\n  out += \"----------------------- SUMMARY -------------------\\n\";\n  char summaryline[4096];\n  XrdOucString sizestring1, sizestring2;\n  snprintf(summaryline, sizeof(summaryline) - 1,\n           \"| avg. readd: %.02f MB/s | avg. write: %.02f  MB/s | \"\n           \"total read: %s | total write: %s | times read: %llu | \"\n           \"times written: %llu |\\n\",\n           totalreadtime ? (totalreadbytes / totalreadtime / 1000000.0) : 0,\n           totalwritetime ? (totalwritebytes / totalwritetime / 1000000.0) : 0,\n           eos::common::StringConversion::GetReadableSizeString\n           (sizestring1, totalreadbytes, \"B\"),\n           eos::common::StringConversion::GetReadableSizeString\n           (sizestring2, totalwritebytes, \"B\"), (unsigned long long)rcount,\n           (unsigned long long)wcount);\n  out += summaryline;\n}\n\n//------------------------------------------------------------------------------\n// Circulate the entries to get stats collected over last sec, min, hour and day\n//------------------------------------------------------------------------------\nvoid\nIostat::Circulate(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"IoStatCirculate\");\n\n  while (!assistant.terminationRequested()) {\n    if (mLegacyMode) {\n      static unsigned long long sc = 0ull;\n\n      // Store once per minute the current statistics\n      if (sc % 117 == 0) {\n        sc = 0ull;\n\n        // save the current state ~ every minute\n        if (!LegacyStoreInFile()) {\n          eos_static_err(\"msg=\\\"failed store io stat dump\\\" path=\\\"%s\\\"\",\n                         mLegacyFilePath.c_str());\n        }\n      }\n\n      sc++;\n    }\n\n    assistant.wait_for(std::chrono::milliseconds(512));\n    google::sparse_hash_map<std::string, google::sparse_hash_map<uid_t, IostatPeriods> >::iterator\n    tit;\n    google::sparse_hash_map<std::string, IostatPeriods >::iterator dit;\n    time_t now = time(NULL);\n    std::unique_lock<std::mutex> scope_lock(mDataMutex);\n\n    // loop over tags\n    for (tit = IostatPeriodsUid.begin(); tit != IostatPeriodsUid.end(); ++tit) {\n      // loop over vids\n      google::sparse_hash_map<uid_t, IostatPeriods>::iterator it;\n\n      for (it = tit->second.begin(); it != tit->second.end(); ++it) {\n        it->second.StampBufferZero(now);\n      }\n    }\n\n    for (tit = IostatPeriodsGid.begin(); tit != IostatPeriodsGid.end(); ++tit) {\n      // loop over vids\n      google::sparse_hash_map<uid_t, IostatPeriods>::iterator it;\n\n      for (it = tit->second.begin(); it != tit->second.end(); ++it) {\n        it->second.StampBufferZero(now);\n      }\n    }\n\n    // loop over domain accounting\n    for (dit = IostatPeriodsDomainIOrb.begin();\n         dit != IostatPeriodsDomainIOrb.end();\n         dit++) {\n      dit->second.StampBufferZero(now);\n    }\n\n    for (dit = IostatPeriodsDomainIOwb.begin();\n         dit != IostatPeriodsDomainIOwb.end();\n         dit++) {\n      dit->second.StampBufferZero(now);\n    }\n\n    // loop over app accounting\n    for (dit = IostatPeriodsAppIOrb.begin(); dit != IostatPeriodsAppIOrb.end();\n         dit++) {\n      dit->second.StampBufferZero(now);\n    }\n\n    for (dit = IostatPeriodsAppIOwb.begin(); dit != IostatPeriodsAppIOwb.end();\n         dit++) {\n      dit->second.StampBufferZero(now);\n    }\n\n    size_t popularitybin = (((time(NULL))) % (IOSTAT_POPULARITY_DAY *\n                            IOSTAT_POPULARITY_HISTORY_DAYS)) / IOSTAT_POPULARITY_DAY;\n\n    if (mLastPopularityBin != popularitybin) {\n      // only if we enter a new bin we erase it\n      std::unique_lock<std::mutex> scope_lock(mPopularityMutex);\n      IostatPopularity[popularitybin].clear();\n      IostatPopularity[popularitybin].resize(10000);\n      mLastPopularityBin = popularitybin;\n    }\n  }\n\n  eos_static_info(\"%s\", \"msg=\\\"stopping iostat circulate thread\\\"\");\n}\n\n//------------------------------------------------------------------------------\n// Encode the UDP popularity targets to a string using the provided separator\n//------------------------------------------------------------------------------\nstd::string\nIostat::EncodeUdpPopularityTargets() const\n{\n  std::string out;\n  std::unique_lock<std::mutex> scope_lock(mBcastMutex);\n\n  if (mUdpPopularityTarget.empty()) {\n    return out;\n  }\n\n  for (const auto& elem : mUdpPopularityTarget) {\n    out += elem;\n    out += \"|\";\n  }\n\n  out.pop_back();\n  return out;\n}\n\n//------------------------------------------------------------------------------\n// Add UDP target\n//------------------------------------------------------------------------------\nbool\nIostat::AddUdpTarget(const std::string& target, bool store_and_lock)\n{\n  std::unique_lock<std::mutex> scope_lock(mBcastMutex, std::defer_lock);\n\n  if (store_and_lock) {\n    scope_lock.lock();\n  }\n\n  if (mUdpPopularityTarget.insert(target).second == false) {\n    // Target already exists\n    return false;\n  }\n\n  // Create an UDP socket for the specified target\n  int udpsocket = -1;\n  udpsocket = socket(AF_INET, SOCK_DGRAM, 0);\n\n  if (udpsocket >= 0) {\n    XrdOucString a_host, a_port, hp;\n    int port = 0;\n    hp = target.c_str();\n\n    if (!eos::common::StringConversion::SplitKeyValue(hp, a_host, a_port)) {\n      a_host = hp;\n      a_port = \"31000\";\n    }\n\n    port = atoi(a_port.c_str());\n    mUdpSocket[target] = udpsocket;\n    XrdNetAddr* addrs  = 0;\n    int         nAddrs = 0;\n    const char* err    = XrdNetUtils::GetAddrs(a_host.c_str(), &addrs, nAddrs,\n                         XrdNetUtils::allIPv64,\n                         XrdNetUtils::NoPortRaw);\n\n    if (err || nAddrs == 0) {\n      return false;\n    }\n\n    memcpy((struct sockaddr*) &mUdpSockAddr[target], addrs[0].SockAddr(),\n           sizeof(sockaddr));\n    delete [] addrs;\n    mUdpSockAddr[target].sin_family = AF_INET;\n    mUdpSockAddr[target].sin_port = htons(port);\n  }\n\n  // Store configuration if required\n  if (store_and_lock) {\n    scope_lock.unlock();\n    return StoreIostatConfig(&FsView::gFsView);\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Remove UDP target\n//------------------------------------------------------------------------------\nbool\nIostat::RemoveUdpTarget(const std::string& target)\n{\n  bool store = false;\n  bool retc = false;\n  {\n    std::unique_lock<std::mutex> scop_lock(mBcastMutex);\n\n    if (mUdpPopularityTarget.count(target)) {\n      mUdpPopularityTarget.erase(target);\n\n      if (mUdpSocket.count(target)) {\n        if (mUdpSocket[target] > 0) {\n          // close the UDP socket\n          close(mUdpSocket[target]);\n        }\n\n        mUdpSocket.erase(target);\n        mUdpSockAddr.erase(target);\n      }\n\n      retc = true;\n      store = true;\n    }\n  }\n\n  if (store) {\n    retc &= StoreIostatConfig(&FsView::gFsView);\n  }\n\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Do the UDP broadcast\n//------------------------------------------------------------------------------\nvoid\nIostat::UdpBroadCast(eos::common::Report* report) const\n{\n  std::string u = \"\";\n  char fs[1024];\n  std::unique_lock<std::mutex> scope_lock(mBcastMutex);\n\n  for (auto it = mUdpPopularityTarget.cbegin();\n       it != mUdpPopularityTarget.cend(); ++it) {\n    u = \"\";\n    XrdOucString tg = it->c_str();\n    XrdOucString sizestring;\n\n    if (tg.endswith(\"/json\")) {\n      // do json format broadcast\n      tg.replace(\"/json\", \"\");\n      u += \"{\\\"app_info\\\": \\\"\";\n      u += report->sec_app;\n      u += \"\\\",\\n\";\n      u += \" \\\"client_domain\\\": \\\"\";\n      u += report->sec_domain;\n      u += \"\\\",\\n\";\n      u += \" \\\"client_host\\\": \\\"\";\n      u += report->sec_host;\n      u += \"\\\",\\n\";\n      u += \" \\\"end_time\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->cts);\n      u += \",\\n\";\n      u += \" \\\"file_lfn\\\": \\\"\";\n      u += report->path;\n      u += \"\\\",\\n\";\n      u += \" \\\"file_size\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->csize);\n      u += \",\\n\";\n      u += \" \\\"read_average\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring,\n           report->rb / ((report->nrc) ? report->nrc : 999999999));\n      u += \",\\n\";\n      u += \" \\\"read_bytes_at_close\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb);\n      u += \",\\n\";\n      u += \" \\\"read_bytes\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb);\n      u += \",\\n\";\n      u += \" \\\"read_max\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb_max);\n      u += \",\\n\";\n      u += \" \\\"read_min\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb_min);\n      u += \",\\n\";\n      u += \" \\\"read_operations\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->nrc);\n      u += \",\\n\";\n      snprintf(fs, sizeof(fs) - 1, \"%.02f\", report->rb_sigma);\n      u += \" \\\"read_sigma\\\": \";\n      u += fs;\n      u += \",\\n\";\n      /* -- we have currently no access to this information */\n      /*\n      u += \" \\\"read_single_average\\\": \";  u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_single_bytes\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb); u += \",\\n\";\n      u += \" \\\"read_single_max\\\": \";      u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_single_min\\\": \";      u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_single_operations\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->nrc); u += \",\\n\";\n      u += \" \\\"read_single_sigma\\\": \";    u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_average\\\": \";  u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_bytes\\\": \";    u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_count_average\\\": \"; u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_count_max\\\": \";u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_count_min\\\": \";u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_count_sigma\\\": \";   u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_max\\\": \";      u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_min\\\": \";      u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_operations\\\": \"; u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_sigma\\\": \";    u += \"0\"; u += \",\\n\"; */\n      u += \" \\\"server_domain\\\": \\\"\";\n      u += report->server_domain;\n      u += \"\\\",\\n\";\n      u += \" \\\"server_host\\\": \\\"\";\n      u += report->server_name;\n      u += \"\\\",\\n\";\n      u += \" \\\"server_username\\\": \\\"\";\n      u += report->sec_name;\n      u += \"\\\",\\n\";\n      u += \" \\\"start_time\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->ots);\n      u += \",\\n\";\n      XrdOucString stime; // stores the current time in <s>.<ns>\n      u += \" \\\"unique_id\\\": \\\"\";\n      u += gOFS->MgmOfsInstanceName.c_str();\n      u += \"-\";\n      u += eos::common::StringConversion::TimeNowAsString(stime);\n      u += \"\\\",\\n\";\n      u += \" \\\"user_dn\\\": \\\"\";\n      u += report->sec_info;\n      u += \"\\\",\\n\";\n      u += \" \\\"user_fqan\\\": \\\"\";\n      u += report->sec_grps;\n      u += \"\\\",\\n\";\n      u += \" \\\"user_role\\\": \\\"\";\n      u += report->sec_role;\n      u += \"\\\",\\n\";\n      u += \" \\\"user_vo\\\": \\\"\";\n      u += report->sec_vorg;\n      u += \"\\\",\\n\";\n      u += \" \\\"write_average\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring,\n           report->wb / ((report->nwc) ? report->nwc : 999999999));\n      u += \",\\n\";\n      u += \" \\\"write_bytes_at_close\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->wb);\n      u += \",\\n\";\n      u += \" \\\"write_bytes\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->wb);\n      u += \",\\n\";\n      u += \" \\\"write_max\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->wb_max);\n      u += \",\\n\";\n      u += \" \\\"write_min\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->wb_min);\n      u += \",\\n\";\n      u += \" \\\"write_operations\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->nwc);\n      u += \",\\n\";\n      snprintf(fs, sizeof(fs) - 1, \"%.02f\", report->wb_sigma);\n      u += \" \\\"write_sigma\\\": \";\n      u += fs;\n      u += \"}\\n\";\n    } else {\n      // do default format broadcast\n      u += \"#begin\\n\";\n      u += \"app_info=\";\n      u += report->sec_app;\n      u += \"\\n\";\n      u += \"client_domain=\";\n      u += report->sec_domain;\n      u += \"\\n\";\n      u += \"client_host=\";\n      u += report->sec_host;\n      u += \"\\n\";\n      u += \"end_time=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->cts);\n      u += \"\\n\";\n      u += \"file_lfn = \";\n      u += report->path;\n      u += \"\\n\";\n      u += \"file_size = \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->csize);\n      u += \"\\n\";\n      u += \"read_average=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring,\n           report->rb / ((report->nrc) ? report->nrc : 999999999));\n      u += \"\\n\";\n      u += \"read_bytes_at_close=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb);\n      u += \"\\n\";\n      u += \"read_bytes=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb);\n      u += \"\\n\";\n      u += \"read_min=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb_min);\n      u += \"\\n\";\n      u += \"read_max=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb_max);\n      u += \"\\n\";\n      u += \"read_operations=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->nrc);\n      u += \"\\n\";\n      u += \"read_sigma=\";\n      u += \"0\";\n      u += \"\\n\";\n      snprintf(fs, sizeof(fs) - 1, \"%.02f\", report->rb_sigma);\n      u += \"read_sigma=\";\n      u += fs;\n      u += \"\\n\";\n      /* -- we have currently no access to this information */\n      /* u += \"read_single_average=\";  u += \"0\"; u += \"\\n\";\n      u += \"read_single_bytes=\";    u += eos::common::StringConversion::GetSizeString(sizestring, report->rb); u += \"\\n\";\n      u += \"read_single_max=\";      u += \"0\"; u += \"\\n\";\n      u += \"read_single_min=\";      u += \"0\"; u += \"\\n\";\n      u += \"read_single_operations=\";    u += eos::common::StringConversion::GetSizeString(sizestring, report->nrc); u += \"\\n\";\n      u += \"read_single_sigma=\";    u += \"0\"; u += \"\\n\";\n      u += \"read_vector_average=\";  u += \"0\"; u += \"\\n\";\n      u += \"read_vector_bytes=\";    u += \"0\"; u += \"\\n\";\n      u += \"read_vector_count_average=\"; u += \"0\"; u += \"\\n\";\n      u += \"read_vector_count_max=\";u += \"0\"; u += \"\\n\";\n      u += \"read_vector_count_min=\";u += \"0\"; u += \"\\n\";\n      u += \"read_vector_count_sigma=\";   u += \"0\"; u += \"\\n\";\n      u += \"read_vector_max=\";      u += \"0\"; u += \"\\n\";\n      u += \"read_vector_min=\";      u += \"0\"; u += \"\\n\";\n      u += \"read_vector_operations=\"; u += \"0\"; u += \"\\n\";\n      u += \"read_vector_sigma=\";    u += \"0\"; u += \"\\n\";*/\n      u += \"server_domain=\";\n      u += report->server_domain;\n      u += \"\\n\";\n      u += \"server_host=\";\n      u += report->server_name;\n      u += \"\\n\";\n      u += \"server_username=\";\n      u += report->sec_name;\n      u += \"\\n\";\n      u += \"start_time=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->ots);\n      u += \"\\n\";\n      XrdOucString stime; // stores the current time in <s>.<ns>\n      u += \"unique_id=\";\n      u += gOFS->MgmOfsInstanceName.c_str();\n      u += \"-\";\n      u += eos::common::StringConversion::TimeNowAsString(stime);\n      u += \"\\n\";\n      u += \"user_dn = \";\n      u += report->sec_info;\n      u += \"\\n\";\n      u += \"user_fqan=\";\n      u += report->sec_grps;\n      u += \"\\n\";\n      u += \"user_role=\";\n      u += report->sec_role;\n      u += \"\\n\";\n      u += \"user_vo=\";\n      u += report->sec_vorg;\n      u += \"\\n\";\n      u += \"write_average=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring,\n           report->wb / ((report->nwc) ? report->nwc : 999999999));\n      u += \"\\n\";\n      u += \"write_bytes_at_close=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->wb);\n      u += \"\\n\";\n      u += \"write_bytes=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->wb);\n      u += \"\\n\";\n      u += \"write_min=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->wb_min);\n      u += \"\\n\";\n      u += \"write_max=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->wb_max);\n      u += \"\\n\";\n      u += \"write_operations=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->nwc);\n      u += \"\\n\";\n      snprintf(fs, sizeof(fs) - 1, \"%.02f\", report->rb_sigma);\n      u += \"write_sigma=\";\n      u += fs;\n      u += \"\\n\";\n      u += \"#end\\n\";\n    }\n\n    int sendretc = sendto(mUdpSocket.at(*it), u.c_str(), u.length(), 0,\n                          (struct sockaddr*) &mUdpSockAddr.at(*it),\n                          sizeof(struct sockaddr_in));\n\n    if (sendretc < 0) {\n      eos_static_err(\"msg=\\\"failed to send udp message to %s\\\"\", it->c_str());\n    }\n\n    eos_static_debug(\"sendto_retc=%d\", sendretc);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Add entry to popularity statistics\n//------------------------------------------------------------------------------\nvoid\nIostat::AddToPopularity(const std::string& path, unsigned long long rb,\n                        time_t start, time_t stop)\n{\n  size_t popularitybin = (((start + stop) / 2) % (IOSTAT_POPULARITY_DAY *\n                          IOSTAT_POPULARITY_HISTORY_DAYS)) / IOSTAT_POPULARITY_DAY;\n  eos::common::Path cPath(path.c_str());\n  std::unique_lock<std::mutex> scope_lock(mPopularityMutex);\n\n  for (size_t k = 0; k < cPath.GetSubPathSize(); ++k) {\n    std::string sp = cPath.GetSubPath(k);\n    IostatPopularity[popularitybin][sp].rb += rb;\n    IostatPopularity[popularitybin][sp].nread++;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Create hash map key string from the given information\n//------------------------------------------------------------------------------\nstd::string\nIostat::EncodeKey(const std::string& id_type, const std::string& id_val,\n                  const std::string& tag)\n{\n  return SSTR(\"idt=\" << id_type << \"&id=\" << id_val << \"&tag=\" << tag);\n}\n\n//------------------------------------------------------------------------------\n// Decode/parse hash map key to extract entry information\n//------------------------------------------------------------------------------\nbool\nIostat::DecodeKey(const std::string& key, std::string& id_type,\n                  std::string& id_val, std::string& tag)\n{\n  std::vector<std::string> tokens {};\n  eos::common::StringConversion::Tokenize(key, tokens, \"&\");\n\n  for (const auto& token : tokens) {\n    std::vector<std::string> kv {};\n    eos::common::StringConversion::Tokenize(token, kv, \"=\");\n\n    if (kv.size() != 2) {\n      eos_static_err(\"msg=\\\"unexpected token format\\\" token=\\\"%s\\\"\",\n                     token.c_str());\n      return false;\n    }\n\n    if (kv[0] == \"idt\") {\n      id_type = kv[1];\n    } else if (kv[0] == \"id\") {\n      id_val = kv[1];\n    } else if (kv[0] == \"tag\") {\n      tag = kv[1];\n    } else {\n      eos_static_err(\"msg=\\\"unexpected key format\\\" key=\\\"%s\\\"\", kv[0].c_str());\n      return false;\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Load Iostat information from Qdb backend\n//------------------------------------------------------------------------------\nbool\nIostat::LoadFromQdb()\n{\n  std::string key = GetHashKey();\n  eos_static_info(\"msg=\\\"loading iostat info from Qdb\\\" hash_map=\\\"%s\\\"\",\n                  key.c_str());\n  qclient::redisReplyPtr reply;\n\n  try {\n    reply = mQcl->exec(\"HGETALL\", key).get();\n  } catch (const std::exception& e) {\n    eos_static_err(\"msg=\\\"failed getting entries from Qdb\\\", emsg=\\\"%s\\\"\",\n                   e.what());\n    return true;\n  }\n\n  qclient::HgetallParser mQdbRespParser(reply);\n\n  if (!mQdbRespParser.ok()) {\n    eos_static_err(\"%s\", \"msg=\\\"failed parsing reply from Qdb\\n\");\n    return false;\n  }\n\n  int id = 0;\n  unsigned long long val = 0ull;\n  std::string id_type, id_val, tag;\n  std::map<std::string, std::string> stored_iostat = mQdbRespParser.value();\n  std::unique_lock<std::mutex> scope_lock(mDataMutex);\n  // Clean up the memory data structures\n  IostatUid.clear();\n  IostatUid.resize(0);\n  IostatTag.clear();\n  IostatTag.resize(0);\n  IostatGid.clear();\n  IostatGid.resize(0);\n\n  for (const auto& pair : stored_iostat) {\n    if (!DecodeKey(pair.first, id_type, id_val, tag)) {\n      continue;\n    }\n\n    // Convert entries from string to numeric\n    try {\n      id = std::stoi(id_val);\n      val = std::stoull(pair.second);\n    } catch (...) {\n      eos_static_err(\"msg=\\\"failed converting to numeric format\\\" key=\\\"%s\\\" \"\n                     \"val=\\\"%s\\\"\", pair.first.c_str(), pair.second.c_str());\n      continue;\n    }\n\n    if (id_type == USER_ID_TYPE) {\n      IostatUid[tag][id] = val;\n\n      if (!IostatTag.count(tag)) {\n        IostatTag[tag] = val;\n      } else {\n        IostatTag[tag] += val;\n      }\n    } else if (id_type == GROUP_ID_TYPE) {\n      IostatGid[tag][id] = val;\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Store statistics in legacy file format\n//------------------------------------------------------------------------------\nbool\nIostat::LegacyStoreInFile()\n{\n  if (mLegacyFilePath.empty()) {\n    return false;\n  }\n\n  XrdOucString tmpname = mLegacyFilePath.c_str();\n  tmpname += \".tmp\";\n  FILE* fout = fopen(tmpname.c_str(), \"w+\");\n\n  if (!fout) {\n    return false;\n  }\n\n  if (chmod(tmpname.c_str(), S_IRWXU | S_IRGRP | S_IROTH)) {\n    fclose(fout);\n    return false;\n  }\n\n  std::unique_lock<std::mutex> scope_lock(mDataMutex);\n\n  // Store user counters\n  for (auto tuit = IostatUid.begin(); tuit != IostatUid.end(); tuit++) {\n    for (auto it = tuit->second.begin(); it != tuit->second.end(); ++it) {\n      fprintf(fout, \"tag=%s&uid=%u&val=%llu\\n\", tuit->first.c_str(), it->first,\n              (unsigned long long)it->second);\n    }\n  }\n\n  // Store group counter\n  for (auto tgit = IostatGid.begin(); tgit != IostatGid.end(); tgit++) {\n    for (auto it = tgit->second.begin(); it != tgit->second.end(); ++it) {\n      fprintf(fout, \"tag=%s&gid=%u&val=%llu\\n\", tgit->first.c_str(), it->first,\n              (unsigned long long)it->second);\n    }\n  }\n\n  fclose(fout);\n  return (rename(tmpname.c_str(), mLegacyFilePath.c_str()) == 0);\n}\n\n//------------------------------------------------------------------------------\n// Restore statistics from legacy file format\n//------------------------------------------------------------------------------\nbool\nIostat::LegacyRestoreFromFile()\n{\n  if (mLegacyFilePath.empty()) {\n    return false;\n  }\n\n  FILE* fin = fopen(mLegacyFilePath.c_str(), \"r\");\n\n  if (!fin) {\n    return true;\n  }\n\n  int item = 0;\n  char line[16384];\n  std::unique_lock<std::mutex> scope_lock(mDataMutex);\n\n  while ((item = fscanf(fin, \"%16383s\\n\", line)) == 1) {\n    XrdOucEnv env(line);\n\n    if (env.Get(\"tag\") && env.Get(\"uid\") && env.Get(\"val\")) {\n      std::string tag = env.Get(\"tag\");\n      uid_t uid = atoi(env.Get(\"uid\"));\n      unsigned long long val = strtoull(env.Get(\"val\"), 0, 10);\n      IostatUid[tag][uid] = val;\n\n      if (!IostatTag.count(tag)) {\n        IostatTag[tag] = val;\n      } else {\n        IostatTag[tag] += val;\n      }\n    }\n\n    if (env.Get(\"tag\") && env.Get(\"gid\") && env.Get(\"val\")) {\n      std::string tag = env.Get(\"tag\");\n      gid_t gid = atoi(env.Get(\"gid\"));\n      unsigned long long val = strtoull(env.Get(\"val\"), 0, 10);\n      IostatGid[tag][gid] = val;\n    }\n  }\n\n  fclose(fin);\n  return true;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ninclude_directories(\n  ${CMAKE_SOURCE_DIR}/common/xrootd-ssi-protobuf-interface/include\n  ${CMAKE_SOURCE_DIR}/common/jwt-cpp/include/\n  ${CMAKE_SOURCE_DIR}/common/\n  ${CMAKE_SOURCE_DIR}/mgm\n  ${CMAKE_SOURCE_DIR}/mgm/ofs/)\n\n#-------------------------------------------------------------------------------\n# Generate protocol buffer files\n#-------------------------------------------------------------------------------\nPROTOBUF_GENERATE_CPP(FUSEX_SRCS FUSEX_HDRS fusex.proto)\nset_source_files_properties(\n  ${FUSEX_SRCS}\n  ${FUSEX_HDRS}\n  PROPERTIES GENERATED 1)\n\nadd_library(EosMgmProto-Objects OBJECT\n  ${FUSEX_SRCS} ${FUSEX_HDRS})\n\ntarget_link_libraries(EosMgmProto-Objects PUBLIC\n  PROTOBUF::PROTOBUF)\n\n#-------------------------------------------------------------------------------\n# EosMgmHttp library\n#-------------------------------------------------------------------------------\nadd_library(EosMgmHttp-Objects OBJECT\n  http/xrdhttp/EosMgmHttpHandler.hh\n  http/xrdhttp/EosMgmHttpHandler.cc)\n\ntarget_link_libraries(EosMgmHttp-Objects PUBLIC\n  EosAuthProto-Objects\n  EosCommonServer\n  XROOTD::PRIVATE\n  XROOTD::HTTP\n  CURL::libcurl)\n\nset_target_properties(EosMgmHttp-Objects\n  PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\n\nadd_library(EosMgmHttp-${XRDPLUGIN_SOVERSION} MODULE\n  $<TARGET_OBJECTS:EosMgmHttp-Objects>)\n\ntarget_link_libraries(EosMgmHttp-${XRDPLUGIN_SOVERSION} PUBLIC\n  EosMgmHttp-Objects)\n\ninstall(\n  TARGETS EosMgmHttp-${XRDPLUGIN_SOVERSION}\n  LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n  ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\n\nset (MGM_TGC_SRC_FILES\n  tgc/AsyncUint64ShellCmd.cc\n  tgc/DummyTapeGcMgm.cc\n  tgc/FreedBytesHistogram.cc\n  tgc/IClock.cc\n  tgc/ITapeGcMgm.cc\n  tgc/Lru.cc\n  tgc/MaxLenExceeded.cc\n  tgc/MultiSpaceTapeGc.cc\n  tgc/RealClock.cc\n  tgc/RealTapeGcMgm.cc\n  tgc/SmartSpaceStats.cc\n  tgc/SpaceNotFound.cc\n  tgc/SpaceToTapeGcMap.cc\n  tgc/TapeGc.cc\n  CtaUtils.cc)\n\nset_source_files_properties(\n  ofs/cmds/Rename.inc ofs/cmds/Access.inc\n  ofs/cmds/Attr.inc ofs/cmds/FAttr.inc\n  ofs/cmds/Auth.inc ofs/cmds/Chksum.inc\n  ofs/cmds/Chmod.inc ofs/cmds/Chown.inc\n  ofs/cmds/Coverage.inc ofs/cmds/DeleteExternal.inc\n  ofs/cmds/DropReplica.inc ofs/cmds/Exists.inc\n  ofs/cmds/Find.inc ofs/cmds/FsConfigListener.inc\n  ofs/cmds/ErrorLogListener.inc ofs/cmds/Fsctl.inc\n  ofs/cmds/Link.inc ofs/cmds/Mkdir.inc\n  ofs/cmds/PathMap.inc ofs/cmds/Remdir.inc\n  ofs/cmds/Rm.inc\n  ofs/cmds/SharedPath.inc ofs/cmds/ShouldRedirect.inc\n  ofs/cmds/ShouldRoute.inc ofs/cmds/ShouldStall.inc\n  ofs/cmds/Shutdown.inc ofs/cmds/Stacktrace.inc\n  ofs/cmds/Stat.inc ofs/cmds/Stripes.inc\n  ofs/cmds/Touch.inc ofs/cmds/Utimes.inc\n  ofs/cmds/Version.inc\n  PROPERTIES HEADER_FILE_ONLY TRUE\n)\n\n#-------------------------------------------------------------------------------\n# XrdEosMgm-Objects library\n#-------------------------------------------------------------------------------\nadd_library(\n  XrdEosMgm-Objects OBJECT\n  auth/AccessChecker.cc                             auth/AccessChecker.hh\n  config/IConfigEngine.cc                           config/IConfigEngine.hh\n  config/QuarkConfigHandler.cc                      config/QuarkConfigHandler.hh\n  config/QuarkDBConfigEngine.cc                     config/QuarkDBConfigEngine.hh\n  access/Access.cc\n  macros/Macros.cc\n  geotreeengine/GeoTreeEngine.cc\n  ${MGM_TGC_SRC_FILES}\n  policy/Policy.cc\n  namespacestats/NamespaceStats.cc\n  proc/IProcCommand.cc\n  proc/ProcInterface.cc\n  proc/ProcCommand.cc\n  proc/proc_fs.cc\n  proc/admin/Backup.cc\n  proc/admin/FsCmd.cc\n  proc/admin/FsckCmd.cc\n  proc/admin/Fusex.cc\n  proc/admin/GeoSched.cc\n  proc/admin/NsCmd.cc\n  proc/admin/Rtlog.cc\n  proc/admin/EvictCmd.cc proc/admin/EvictCmd.hh\n  proc/admin/Vid.cc\n  proc/admin/Access.cc # @todo (faluchet) drop when move to 5.0.0\n  proc/admin/AccessCmd.cc proc/admin/AccessCmd.hh\n  proc/admin/ConfigCmd.cc proc/admin/ConfigCmd.hh\n  proc/admin/ConvertCmd.cc proc/admin/ConvertCmd.hh\n  proc/admin/DebugCmd.cc proc/admin/DebugCmd.hh\n  proc/admin/DevicesCmd.cc proc/admin/DevicesCmd.hh\n  proc/admin/GroupCmd.cc proc/admin/GroupCmd.hh\n  proc/admin/IoShapingCmd.cc proc/admin/IoCmd.cc proc/admin/IoCmd.hh\n  proc/admin/FileRegisterCmd.cc proc/admin/FileRegisterCmd.hh\n  proc/admin/NodeCmd.cc proc/admin/NodeCmd.hh\n  proc/admin/SchedCmd.cc\n  proc/admin/SpaceCmd.cc proc/admin/SpaceCmd.hh\n  proc/admin/Quota.cc # @todo (faluchet) drop when move to 5.0.0\n  proc/admin/QuotaCmd.cc proc/admin/QuotaCmd.hh\n  proc/user/Quota.cc # @todo (faluchet) drop when move to 5.0.0\n  proc/user/Accounting.cc\n  proc/user/AclCmd.cc\n  proc/user/Attr.cc\n  proc/user/Archive.cc\n  proc/user/Cd.cc\n  proc/user/Chmod.cc\n  proc/user/Chown.cc\n  proc/user/DfCmd.cc\n  proc/user/File.cc\n  proc/user/Fileinfo.cc\n  proc/user/Find.cc\n  proc/user/NewfindCmd.cc proc/user/NewfindCmd.hh\n  proc/user/Fuse.cc\n  proc/user/FuseX.cc\n  proc/user/Ls.cc\n  proc/user/Map.cc\n  proc/user/Member.cc\n  proc/user/Mkdir.cc\n  proc/user/Motd.cc\n  proc/user/RecycleCmd.cc\n  proc/user/Rm.cc\n  proc/user/RmCmd.cc\n  proc/user/Rmdir.cc\n  proc/user/RouteCmd.cc\n  proc/user/TokenCmd.cc\n  proc/user/Version.cc\n  proc/user/Who.cc\n  proc/user/Whoami.cc\n  quota/Quota.cc\n  scheduler/Scheduler.cc\n  vid/Vid.cc\n  fsview/FsView.cc\n  ofs/XrdMgmOfsConfigure.cc\n  ofs/XrdMgmOfsFile.cc\n  ofs/XrdMgmOfsDirectory.cc\n  ofs/XrdMgmOfs.cc\n  authz/XrdMgmAuthz.cc\n  ofs/fsctl/Access.cc\n  ofs/fsctl/AdjustReplica.cc\n  ofs/fsctl/Checksum.cc\n  ofs/fsctl/Chmod.cc\n  ofs/fsctl/Chown.cc\n  ofs/fsctl/Commit.cc\n  ofs/fsctl/CommitHelper.cc\n  ofs/fsctl/Drop.cc\n  ofs/fsctl/Event.cc\n  ofs/fsctl/Fusex.cc\n  ofs/fsctl/Getfmd.cc\n  ofs/fsctl/GetFusex.cc\n  ofs/fsctl/Mkdir.cc\n  ofs/fsctl/Open.cc\n  ofs/fsctl/Readlink.cc\n  ofs/fsctl/Redirect.cc\n  ofs/fsctl/Stat.cc\n  ofs/fsctl/Statvfs.cc\n  ofs/fsctl/Symlink.cc\n  ofs/fsctl/Utimes.cc\n  ofs/fsctl/Version.cc\n  balancer/FsBalancer.cc\n  balancer/FsBalancerStats.cc\n  commandmap/CommandMap.cc\n  filesystem/FileSystem.cc\n  convert/ConversionInfo.cc\n  convert/ConversionJob.cc\n  convert/ConverterEngine.cc\n  drain/DrainFs.cc\n  drain/DrainTransferJob.cc\n  drain/Drainer.cc\n  egroup/Egroup.cc\n  adminsocket/AdminSocket.cc\n  acl/Acl.cc\n  stat/Stat.cc\n  iostat/Iostat.cc\n  fsck/Fsck.cc\n  fsck/FsckEntry.cc\n  utils/AttrHelper.cc\n  utils/FileSystemRegistry.cc                  utils/FileSystemRegistry.hh\n  utils/FilesystemUuidMapper.cc                utils/FilesystemUuidMapper.hh\n  utils/FileSystemStatusUtils.cc\n  groupbalancer/GroupBalancer.cc\n  groupdrainer/GroupDrainer.cc\n  groupbalancer/BalancerEngine.cc\n  groupbalancer/ConverterUtils.cc\n  groupbalancer/FreeSpaceBalancerEngine.cc\n  groupbalancer/MinMaxBalancerEngine.cc\n  groupbalancer/StdDevBalancerEngine.cc\n  groupbalancer/StdDrainerEngine.cc\n  groupbalancer/GroupsInfoFetcher.cc\n  groupdrainer/DrainProgressTracker.cc\n  geobalancer/GeoBalancer.cc\n  features/Features.cc\n  zmq/ZMQ.cc\n  FuseServer/Server.cc FuseServer/Server.hh\n  FuseServer/Clients.cc FuseServer/Clients.hh\n  FuseServer/Locks.cc FuseServer/Locks.hh\n  FuseServer/Caps.cc FuseServer/Caps.hh\n  FuseServer/Flush.cc FuseServer/Flush.hh\n  fuse-locks/LockTracker.cc   fuse-locks/LockTracker.hh\n  imaster/IMaster.cc                  imaster/IMaster.hh\n  qdbmaster/QdbMaster.cc\n  devices/Devices.cc\n  recycle/RecyclePolicy.cc      recycle/RecyclePolicy.hh\n  recycle/RecycleEntry.cc       recycle/RecycleEntry.hh\n  recycle/Recycle.cc            recycle/Recycle.hh\n  pathrouting/PathRouting.cc\n  routeendpoint/RouteEndpoint.cc\n  lru/LRU.cc\n  wfe/WFE.cc\n  EosCtaReporter.cc\n  workflow/Workflow.cc\n  inflighttracker/InFlightTracker.cc\n  grpc/GrpcServer.cc   grpc/GrpcServer.hh\n  grpc/GrpcNsInterface.cc   grpc/GrpcNsInterface.hh\n  grpc/GrpcWncServer.cc      grpc/GrpcWncServer.hh\n  grpc/GrpcWncInterface.cc      grpc/GrpcWncInterface.hh\n  grpc/GrpcRestGwServer.cc grpc/GrpcRestGwServer.hh\n  grpc/GrpcRestGwInterface.cc      grpc/GrpcRestGwInterface.hh\n  http/HttpServer.cc\n  http/HttpHandler.cc\n  http/s3/S3Handler.cc\n  http/s3/S3Store.cc\n  http/webdav/WebDAVHandler.cc\n  http/webdav/WebDAVResponse.cc\n  http/webdav/PropFindResponse.cc\n  http/webdav/PropPatchResponse.cc\n  http/webdav/LockResponse.cc\n  http/rest-api/manager/RestApiManager.cc\n  http/rest-api/handler/RestHandler.cc\n  http/rest-api/handler/wellknown/WellKnownHandler.cc\n  http/rest-api/config/tape/TapeRestApiConfig.cc\n  http/rest-api/handler/tape/TapeRestHandler.cc\n  # factories removed in favor of in-place creators\n  http/rest-api/action/tape/stage/CreateStageBulkRequest.cc\n  http/rest-api/action/tape/stage/CancelStageBulkRequest.cc\n  http/rest-api/action/tape/stage/GetStageBulkRequest.cc\n  http/rest-api/action/tape/stage/DeleteStageBulkRequest.cc\n  http/rest-api/action/tape/archiveinfo/GetArchiveInfo.cc\n  http/rest-api/action/tape/release/CreateReleaseBulkRequest.cc\n  # http/rest-api/action/wellknown/tape/GetTapeRestApiWellKnown.cc removed (inlined in WellKnownHandler)\n  http/rest-api/business/tape/TapeRestApiBusiness.cc\n  # Controller layer removed in favor of a lightweight router\n  # http/rest-api/controllers/Controller.cc\n  # http/rest-api/controllers/tape/factories/TapeControllerFactory.cc\n  # http/rest-api/controllers/wellknown/factories/WellKnownControllerFactory.cc\n  # http/rest-api/controllers/ControllerManager.cc\n  # http/rest-api/controllers/ControllerActionDispatcher.cc\n  # http/rest-api/controllers/tape/stage/StageController.cc\n  # http/rest-api/controllers/tape/archiveinfo/ArchiveInfoController.cc\n  # http/rest-api/controllers/tape/release/ReleaseController.cc\n  # http/rest-api/controllers/wellknown/tape/TapeWellKnownController.cc\n  # http/rest-api/exception/JsonValidationException.cc inlined\n  # http/rest-api/model/tape/common/FilesContainer.cc header-only\n  http/rest-api/model/tape/common/ErrorModel.cc\n  # http/rest-api/model/tape/archiveinfo/GetArchiveInfoResponseModel.cc header-only\n  # http/rest-api/model/tape/stage/PathsModel.cc header-only\n  # http/rest-api/model/tape/stage/CreateStageBulkRequestModel.cc header-only\n  # http/rest-api/model/tape/stage/CreatedStageBulkRequestResponseModel.cc header-only\n  # http/rest-api/model/tape/stage/GetStageBulkRequestResponseModel.cc header-only\n  # http/rest-api/model/wellknown/tape/GetTapeWellKnownModel.cc header-only\n  # grouped under TapeModelBuilders.hh / TapeJsonifiers.hh\n  http/rest-api/response/RestResponseFactory.cc\n  http/rest-api/response/ErrorHandling.hh\n  http/rest-api/router/Router.hh\n  http/rest-api/utils/URLParser.cc\n  http/rest-api/utils/URLBuilder.cc\n  http/rest-api/wellknown/tape/TapeWellKnownInfos.cc\n  http/rest-api/wellknown/tape/TapeRestApiEndpoint.cc\n  geotree/SchedulingTreeTest.cc\n  geotree/SchedulingSlowTree.cc\n  geotree/SchedulingTreeCommon.cc\n  tracker/ReplicationTracker.cc\n  inspector/FileInspector.cc\n  inspector/FileInspectorStats.cc\n  # bulk-request: File.* and FileCollection.* are now header-only\n  bulk-request/prepare/manager/PrepareManager.cc\n  bulk-request/prepare/manager/BulkRequestPrepareManager.cc\n  bulk-request/BulkRequest.cc\n  # bulk-request prepare classes are now header-only\n  bulk-request/business/BulkRequestBusiness.cc\n  bulk-request/BulkRequestFactory.cc\n  bulk-request/dao/factories/ProcDirectoryDAOFactory.cc\n  bulk-request/dao/proc/ProcDirectoryBulkRequestDAO.cc\n  bulk-request/dao/proc/ProcDirectoryBulkRequestLocations.cc\n  bulk-request/dao/proc/ProcDirBulkRequestFile.cc\n  # bulk-request exceptions are now header-only\n  bulk-request/interface/RealMgmFileSystemInterface.cc\n  bulk-request/prepare/PrepareUtils.cc\n  bulk-request/prepare/query-prepare/QueryPrepareResult.cc\n  bulk-request/dao/proc/cleaner/BulkRequestProcCleaner.cc\n  bulk-request/dao/proc/cleaner/BulkRequestProcCleanerConfig.cc\n  placement/ClusterMap.cc\n  placement/FsScheduler.cc\n  placement/FlatScheduler.cc\n  placement/PlacementStrategy.cc\n  placement/RoundRobinPlacementStrategy.cc\n  placement/WeightedRoundRobinStrategy.cc\n  placement/WeightedRandomStrategy.cc\n  placement/ThreadLocalRRSeed.cc\n  shaping/TrafficShaping.cc\n  ${FUSEX_SRCS})\n\ntarget_compile_options(XrdEosMgm-Objects PRIVATE -Wno-sign-compare)\n\ntarget_link_libraries(XrdEosMgm-Objects PUBLIC\n  EosCliProto-Objects\n  EosAuthProto-Objects\n  XrdSsiPbEosCta-Objects\n  EosGrpcProto-Objects\n  EosCliGrpc-Objects\n  EosWncGrpcProto-Objects\n  RestGrpc-Objects\n  RestAnnot-Objects\n  EosCommonServer\n  EosGrpcGateway::EosGrpcGateway\n  GOOGLE::SPARSEHASH\n  JSONCPP::JSONCPP\n  XROOTD::PRIVATE\n  XROOTD::POSIX\n  LDAP::LDAP\n  ZMQ::ZMQ\n  XXHASH::XXHASH)\n\ntarget_compile_definitions(XrdEosMgm-Objects PUBLIC\n  -DDAEMONUID=${DAEMONUID} -DDAEMONGID=${DAEMONGID})\n\nset_target_properties(XrdEosMgm-Objects PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\nadd_library(XrdEosMgm-${XRDPLUGIN_SOVERSION} MODULE\n  $<TARGET_OBJECTS:XrdEosMgm-Objects>)\n\ntarget_compile_definitions(XrdEosMgm-${XRDPLUGIN_SOVERSION} PUBLIC\n  -DDAEMONUID=${DAEMONUID} -DDAEMONGID=${DAEMONGID})\n\ntarget_link_libraries(XrdEosMgm-${XRDPLUGIN_SOVERSION} PUBLIC\n  XrdEosMgm-Objects\n  EosAuthProto-Objects\n  EosGrpcProto-Objects\n  EosCliProto-Objects\n  EosCliGrpc-Objects\n  EosConsoleHelpers-Objects\n  EosConsoleCommands-Objects\n  EosWncGrpcProto-Objects\n  RestGrpc-Objects\n  RestAnnot-Objects\n  EosGrpcGateway::EosGrpcGateway\n  ZLIB::ZLIB\n  XROOTD::CL\n  XROOTD::SSI\n  XROOTD::PRIVATE\n  XROOTD::POSIX\n  EosCommon\n  EosNsCommon)\n\ninstall(TARGETS XrdEosMgm-${XRDPLUGIN_SOVERSION}\n  LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n  ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\n\ninstall(PROGRAMS eos-repair-tool\n  DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR}\n  PERMISSIONS OWNER_READ OWNER_EXECUTE\n              GROUP_READ GROUP_EXECUTE\n              WORLD_READ WORLD_EXECUTE)\n\n#-------------------------------------------------------------------------------\n# Create executables for testing the scheduling part\n#-------------------------------------------------------------------------------\n# @todo (esindril): Move these to the test directory\nadd_executable(testschedulingtree\n  geotree/SchedulingTreeTest.cc\n  geotree/SchedulingSlowTree.cc\n  geotree/SchedulingTreeCommon.cc)\n\ntarget_link_libraries(\n  testschedulingtree\n  EosCommon\n  ${CMAKE_THREAD_LIBS_INIT})\n\nadd_executable(eos-config-inspect\n  config/eos-config-inspect.cc\n  config/QuarkConfigHandler.cc\n  ../common/config/ConfigParsing.cc\n)\n\ntarget_link_libraries(\n  eos-config-inspect\n  EosCommon\n  EosNsCommon-Static)\n\n#-------------------------------------------------------------------------------\n# Create executables for testing the MGM configuration\n#-------------------------------------------------------------------------------\nif(Linux)\n  add_library(XrdEosMgm-Static STATIC\n    $<TARGET_OBJECTS:XrdEosMgm-Objects>)\n\n  target_compile_definitions(XrdEosMgm-Static PUBLIC\n    -DDAEMONUID=${DAEMONUID} -DDAEMONGID=${DAEMONGID})\n\n  target_link_libraries(XrdEosMgm-Static PUBLIC\n    XrdEosMgm-Objects\n    EosAuthProto-Objects\n    EosGrpcProto-Objects\n    EosCliProto-Objects\n    EosCliGrpc-Objects\n    RestGrpc-Objects\n    RestAnnot-Objects\n    EosWncGrpcProto-Objects\n    ZLIB::ZLIB\n    XROOTD::SSI\n    XROOTD::PRIVATE\n    XROOTD::POSIX\n    EosGrpcGateway::EosGrpcGateway\n    EosNsCommon-Static\n    EosCommon-Static)\n\n  install(TARGETS eos-config-inspect\n    LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n    RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n    ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\nendif()\n"
  },
  {
    "path": "mgm/CtaUtils.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Utils.cc\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringUtils.hh\"\n#include \"mgm/CtaUtils.hh\"\n\n#include <cstring>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Return the uint64 representation of the specified string\n//------------------------------------------------------------------------------\nstd::uint64_t\nCtaUtils::toUint64(std::string str)\n{\n  common::trim(str);\n\n  if (str.empty()) {\n    throw EmptyString(\"String is empty (spaces are ignored)\");\n  }\n\n  for (const auto ch : str) {\n    if ('0' > ch || '9' < ch) {\n      throw NonNumericChar(\"String contains one or more non-numeric characters\");\n    }\n  }\n\n  try {\n    return std::stoull(str);\n  } catch (std::invalid_argument&) {\n    throw ParseError(\"Parse error\");\n  } catch (std::out_of_range&) {\n    throw ParsedValueOutOfRange(\"Parsed value of string is out of range\");\n  } catch (...) {\n    throw;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Return a copy of the specified buffer in the form of a timespec structure\n//------------------------------------------------------------------------------\ntimespec\nCtaUtils::bufToTimespec(const std::string& buf)\n{\n  if (sizeof(timespec) != buf.size()) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ <<\n        \" failed: Buffer size does match sizeof(timespec): buf.size()=\" << buf.size() <<\n        \" sizeof(timespec)\" << sizeof(timespec);\n    throw BufSizeMismatch(msg.str());\n  }\n\n  timespec result;\n  std::memcpy(&result, buf.data(), sizeof(timespec));\n  return result;\n}\n\n//----------------------------------------------------------------------------\n// Read from the specified file descriptor into a string.\n//----------------------------------------------------------------------------\nstd::string\nCtaUtils::readFdIntoStr(const int fd, const ssize_t maxStrLen)\n{\n  if (maxStrLen + 1 > std::numeric_limits<uint32_t>::max()) {\n    throw std::out_of_range(\"Reading more than 4GiB is undefined!\");\n  }\n  auto stdoutBuffer = std::make_unique<char[]>(maxStrLen + 1);\n  const auto readRc = ::read(fd, stdoutBuffer.get(), static_cast<uint32_t>(maxStrLen));\n\n  if (readRc < 0) {\n    std::ostringstream msg;\n    msg << \"Failed to read from file descriptor \" << fd;\n    throw std::runtime_error(msg.str());\n  } else if (readRc > maxStrLen) {\n    stdoutBuffer[maxStrLen] = '\\0';\n  } else {\n    stdoutBuffer[readRc] = '\\0';\n  }\n\n  return stdoutBuffer.get();\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/CtaUtils.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Utils.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGC_UTILS_HH__\n#define __EOSMGMTGC_UTILS_HH__\n\n#include \"common/Logging.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"mgm/tgc/CachedValue.hh\"\n#include \"mgm/tgc/Lru.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"proto/ConsoleReply.pb.h\"\n#include \"proto/ConsoleRequest.pb.h\"\n\n#include <atomic>\n#include <condition_variable>\n#include <cstdint>\n#include <exception>\n#include <mutex>\n#include <stdexcept>\n#include <thread>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file TapeAwareGcUtils.hh\n *\n * @brief Class of utility functions for TapeAwareGc.\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class of utility functions for the tape aware garbage collector\n//------------------------------------------------------------------------------\nclass CtaUtils\n{\npublic:\n  struct EmptyString : public std::runtime_error {\n    using std::runtime_error::runtime_error;\n  };\n  struct NonNumericChar : public std::runtime_error {\n    using std::runtime_error::runtime_error;\n  };\n  struct ParseError: public std::runtime_error {\n    using std::runtime_error::runtime_error;\n  };\n  struct ParsedValueOutOfRange : public std::runtime_error {\n    using std::runtime_error::runtime_error;\n  };\n\n  //----------------------------------------------------------------------------\n  //! @return the result of parsing the specified string as a uint64_t\n  //! @param str The string to be parsed\n  //! @note whitespace is ignored\n  //! @throw EmptyString if the specified string is empty\n  //! @throw NonNumericChar if the specified string contains one or more\n  //! non-numeric characters.\n  //! @throw ParseValueOutOfRange if the parsed value is out of range.\n  //! Returns the integer representation of the specified string\n  //----------------------------------------------------------------------------\n  static std::uint64_t toUint64(std::string str);\n\n  //----------------------------------------------------------------------------\n  //! @return x divided by y rounded to the neareset integer\n  //! @param x dividend\n  //! @param y dividor\n  //----------------------------------------------------------------------------\n  static std::uint64_t divideAndRoundToNearest(const std::uint64_t x,\n      const std::uint64_t y)\n  {\n    return (x + y / 2) / y;\n  }\n\n  //----------------------------------------------------------------------------\n  //! @return x divided by y rounded up\n  //! @param x dividend\n  //! @param y dividor\n  //----------------------------------------------------------------------------\n  static std::uint64_t divideAndRoundUp(const std::uint64_t x,\n                                        const std::uint64_t y)\n  {\n    return (x + y - 1) / y;\n  }\n\n  /// Thrown when there has been a buffer size mismatch\n  struct BufSizeMismatch: public std::runtime_error {\n    BufSizeMismatch(const std::string& msg): std::runtime_error(msg) {}\n  };\n\n  //----------------------------------------------------------------------------\n  //! @return a copy of the specified buffer in the form of a timespec structure\n  //! @throw BufSizeMismatch if the size of the specified buffer does not\n  //! exactly match the size of a timespec structure\n  //----------------------------------------------------------------------------\n  static timespec bufToTimespec(const std::string& buf);\n\n  //----------------------------------------------------------------------------\n  //! @return the result of reading from the specified file descriptor into a\n  //! string of the specified maximum size.\n  //! @param fd The file descriptor to be read from.\n  //! @param maxStrLen The maximum length of the string not including the\n  //! terminal null character.\n  //----------------------------------------------------------------------------\n  static std::string readFdIntoStr(const int fd, const ssize_t maxStrLen);\n};\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/EosCtaReporter.cc",
    "content": "//------------------------------------------------------------------------------\n// File: EosCtaReporter.cc\n// Author: Joao Afonso - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <functional>\n\n#include \"mgm/EosCtaReporter.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/iostat/Iostat.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nstatic const std::map<EosCtaReportParam, std::string> EosCtaParamMap{\n  // Basic params\n  {EosCtaReportParam::LOG, \"log\"    },\n  {EosCtaReportParam::PATH, \"path\"   },\n  {EosCtaReportParam::RUID, \"ruid\"   },\n  {EosCtaReportParam::RGID, \"rgid\"   },\n  {EosCtaReportParam::TD, \"td\"     },\n  {EosCtaReportParam::HOST, \"host\"   },\n  {EosCtaReportParam::TS, \"ts\"     },\n  {EosCtaReportParam::TNS, \"tns\"    },\n  {EosCtaReportParam::SEC_APP, \"sec.app\"}, // sec.app - Used to classify EOS report log messages\n\n  // Prepare request params\n  {EosCtaReportParam::PREP_REQ_EVENT, \"event\"     },\n  {EosCtaReportParam::PREP_REQ_REQID, \"reqid\"     },\n  {EosCtaReportParam::PREP_REQ_SENTTOWFE, \"senttowfe\" },\n  {EosCtaReportParam::PREP_REQ_SUCCESSFUL, \"successful\"},\n  {EosCtaReportParam::PREP_REQ_ERROR, \"error\"     },\n\n  // WFE params\n  {EosCtaReportParam::PREP_WFE_EVENT, \"event\"       },\n  {EosCtaReportParam::PREP_WFE_REQID, \"reqid\"       },\n  {EosCtaReportParam::PREP_WFE_REQCOUNT, \"reqcount\"    },\n  {EosCtaReportParam::PREP_WFE_EVICTCOUNTER, \"evictcounter\"},\n  {EosCtaReportParam::PREP_WFE_ONDISK, \"ondisk\"      },\n  {EosCtaReportParam::PREP_WFE_ONTAPE, \"ontape\"      },\n  {EosCtaReportParam::PREP_WFE_FIRSTPREPARE, \"firstprepare\"},\n  {EosCtaReportParam::PREP_WFE_SENTTOCTA, \"senttocta\"   },\n  {EosCtaReportParam::PREP_WFE_ACTIVITY, \"activity\"    },\n  {EosCtaReportParam::PREP_WFE_ERROR, \"error\"       },\n\n  // Evict cmd params\n  {EosCtaReportParam::EVICTCMD_EVICTCOUNTER, \"evictcounter\"},\n  {EosCtaReportParam::EVICTCMD_FILEREMOVED, \"fileremoved\" },\n  {EosCtaReportParam::EVICTCMD_ERROR, \"error\"       },\n  {EosCtaReportParam::EVICTCMD_FSID, \"fsid\"        },\n\n  // File deletion params\n  {EosCtaReportParam::FILE_DEL_FID, \"fid\"                  },\n  {EosCtaReportParam::FILE_DEL_FXID, \"fxid\"                 },\n  {EosCtaReportParam::FILE_DEL_EOS_BTIME, \"eos.btime\"            },\n  {EosCtaReportParam::FILE_DEL_ARCHIVE_FILE_ID, \"archive.file_id\"      },\n  {EosCtaReportParam::FILE_DEL_ARCHIVE_STORAGE_CLASS, \"archive.storage_class\"},\n  {EosCtaReportParam::FILE_DEL_LOCATIONS, \"locations\"            },\n  {EosCtaReportParam::FILE_DEL_CHECKSUMTYPE, \"checksumtype\"         },\n  {EosCtaReportParam::FILE_DEL_CHECKSUMVALUE, \"checksumvalue\"        },\n  {EosCtaReportParam::FILE_DEL_SIZE, \"size\"                 },\n\n  // File creation params\n  {EosCtaReportParam::FILE_CREATE_FID, \"fid\"                  },\n  {EosCtaReportParam::FILE_CREATE_FXID, \"fxid\"                 },\n  {EosCtaReportParam::FILE_CREATE_EOS_BTIME, \"eos.btime\"            },\n  {EosCtaReportParam::FILE_CREATE_ARCHIVE_METADATA, \"archivemetadata\"      },\n};\n\n// Basic mParams\nstd::vector<EosCtaReportParam> EosCtaReporter::DEFAULT_PARAMS{\n  EosCtaReportParam::LOG,\n  EosCtaReportParam::PATH,\n  EosCtaReportParam::RUID,\n  EosCtaReportParam::RGID,\n  EosCtaReportParam::TD,\n  EosCtaReportParam::HOST,\n  EosCtaReportParam::TS,\n  EosCtaReportParam::TNS,\n  EosCtaReportParam::SEC_APP,\n};\n\n// Prepare request mParams\nstd::vector<EosCtaReportParam>\nEosCtaReporterPrepareReq::DEFAULT_PARAMS_PREPARE_REQ{\n  EosCtaReportParam::PREP_REQ_EVENT,\n  EosCtaReportParam::PREP_REQ_REQID,\n  EosCtaReportParam::PREP_REQ_SENTTOWFE,\n  EosCtaReportParam::PREP_REQ_SUCCESSFUL,\n  EosCtaReportParam::PREP_REQ_ERROR,\n};\n\n// WFE mParams\nstd::vector<EosCtaReportParam>\nEosCtaReporterPrepareWfe::DEFAULT_PARAMS_PREPARE_WFE{\n  EosCtaReportParam::PREP_WFE_EVENT,\n  EosCtaReportParam::PREP_WFE_REQID,\n  EosCtaReportParam::PREP_WFE_REQCOUNT,\n  EosCtaReportParam::PREP_WFE_EVICTCOUNTER,\n  EosCtaReportParam::PREP_WFE_ONDISK,\n  EosCtaReportParam::PREP_WFE_ONTAPE,\n  EosCtaReportParam::PREP_WFE_FIRSTPREPARE,\n  EosCtaReportParam::PREP_WFE_SENTTOCTA,\n  EosCtaReportParam::PREP_WFE_ACTIVITY,\n  EosCtaReportParam::PREP_WFE_ERROR,\n};\n\n// Evict cmd mParams\nstd::vector<EosCtaReportParam> EosCtaReporterEvict::DEFAULT_PARAMS_EVICTCMD{\n  EosCtaReportParam::EVICTCMD_EVICTCOUNTER,\n  EosCtaReportParam::EVICTCMD_FILEREMOVED,\n  EosCtaReportParam::EVICTCMD_ERROR,\n};\n\n// File deletion mParams\nstd::vector<EosCtaReportParam>\nEosCtaReporterFileDeletion::DEFAULT_PARAMS_FILE_DELETION{\n  EosCtaReportParam::FILE_DEL_FID,\n  EosCtaReportParam::FILE_DEL_FXID,\n  EosCtaReportParam::FILE_DEL_EOS_BTIME,\n  EosCtaReportParam::FILE_DEL_ARCHIVE_FILE_ID,\n  EosCtaReportParam::FILE_DEL_ARCHIVE_STORAGE_CLASS,\n  EosCtaReportParam::FILE_DEL_LOCATIONS,\n  EosCtaReportParam::FILE_DEL_CHECKSUMTYPE,\n  EosCtaReportParam::FILE_DEL_CHECKSUMVALUE,\n  EosCtaReportParam::FILE_DEL_SIZE,\n};\n\n// File creation mParams\nstd::vector<EosCtaReportParam>\nEosCtaReporterFileCreation::DEFAULT_PARAMS_FILE_CREATION{\n  EosCtaReportParam::FILE_CREATE_FID,\n  EosCtaReportParam::FILE_CREATE_FXID,\n  EosCtaReportParam::FILE_CREATE_EOS_BTIME,\n  EosCtaReportParam::FILE_CREATE_ARCHIVE_METADATA,\n};\n\n// Default function used to write the EOS-CTA reports\nvoid ioStatsWrite(const std::string& in)\n{\n  if (gOFS->mIoStats) {\n    gOFS->mIoStats->WriteRecord(in);\n  }\n}\n\nvoid EosCtaReporter::generateEosReportEntry()\n{\n  std::ostringstream oss;\n\n  for (auto it = mParams.begin(); it != mParams.end(); it++) {\n    if (it != mParams.begin()) {\n      oss << \"&\";\n    }\n\n    oss << EosCtaParamMap.at(it->first) << \"=\" << it->second;\n  }\n\n  mWriterCallback(oss.str());\n}\n\nEosCtaReporter::EosCtaReporter(std::function<void(const std::string&)>\n                               writeCallback) : mActive(true)\n{\n  mWriterCallback = writeCallback ? writeCallback : &ioStatsWrite;\n\n  for (auto key : DEFAULT_PARAMS) {\n    mParams[key] = \"\";\n  }\n}\n\nEosCtaReporterPrepareReq::EosCtaReporterPrepareReq(\n  std::function<void(const std::string&)> func) : EosCtaReporter(func)\n{\n  for (auto key : DEFAULT_PARAMS_PREPARE_REQ) {\n    mParams[key] = \"\";\n  }\n}\n\nEosCtaReporterPrepareWfe::EosCtaReporterPrepareWfe()\n{\n  for (auto key : DEFAULT_PARAMS_PREPARE_WFE) {\n    mParams[key] = \"\";\n  }\n}\n\nEosCtaReporterEvict::EosCtaReporterEvict()\n{\n  for (auto key : DEFAULT_PARAMS_EVICTCMD) {\n    mParams[key] = \"\";\n  }\n}\n\nEosCtaReporterFileDeletion::EosCtaReporterFileDeletion()\n{\n  for (auto key : DEFAULT_PARAMS_FILE_DELETION) {\n    mParams[key] = \"\";\n  }\n}\n\nEosCtaReporterFileCreation::EosCtaReporterFileCreation()\n{\n  for (auto key : DEFAULT_PARAMS_FILE_CREATION) {\n    mParams[key] = \"\";\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/EosCtaReporter.hh",
    "content": "//------------------------------------------------------------------------------\n// File: EosCtaReporter.hh\n// Author: Joao Afonso - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include <vector>\n#include <map>\n#include <string>\n#include <functional>\n\n#include \"mgm/Namespace.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n// All EOS-CTA Report fields should be listed here\n// ** NOTE: *** Parameters will be ordered as they are presented here\nenum class EosCtaReportParam {\n  // Basic params\n  LOG,\n  PATH,\n  RUID,\n  RGID,\n  TD,\n  HOST,\n  TS,\n  TNS,\n\n  // Prepare request params\n  PREP_REQ_EVENT,\n  PREP_REQ_REQID,\n  PREP_REQ_SENTTOWFE,\n  PREP_REQ_SUCCESSFUL,\n  PREP_REQ_ERROR,\n\n  // WFE params\n  PREP_WFE_EVENT,\n  PREP_WFE_REQID,\n  PREP_WFE_REQCOUNT,\n  PREP_WFE_EVICTCOUNTER,\n  PREP_WFE_ONDISK,\n  PREP_WFE_ONTAPE,\n  PREP_WFE_FIRSTPREPARE,\n  PREP_WFE_SENTTOCTA,\n  PREP_WFE_ACTIVITY,\n  PREP_WFE_ERROR,\n\n  // Evict cmd params\n  EVICTCMD_EVICTCOUNTER,\n  EVICTCMD_FILEREMOVED,\n  EVICTCMD_ERROR,\n  EVICTCMD_FSID,\n\n  // File deletion params\n  FILE_DEL_FID,\n  FILE_DEL_FXID,\n  FILE_DEL_EOS_BTIME,\n  FILE_DEL_ARCHIVE_FILE_ID,\n  FILE_DEL_ARCHIVE_STORAGE_CLASS,\n  FILE_DEL_LOCATIONS,\n  FILE_DEL_CHECKSUMTYPE,\n  FILE_DEL_CHECKSUMVALUE,\n  FILE_DEL_SIZE,\n\n  // File creation params\n  FILE_CREATE_FID,\n  FILE_CREATE_FXID,\n  FILE_CREATE_EOS_BTIME,\n  FILE_CREATE_ARCHIVE_METADATA,\n\n  // sec.app - Used to classify EOS report log messages\n  // Should be last, by convention\n  SEC_APP,\n};\n\n// Base class for EOS-CTA Report\n// Most logic is implemented here\nclass EosCtaReporter\n{\npublic:\n\n  virtual ~EosCtaReporter()\n  {\n    if (mActive) {\n      generateEosReportEntry();\n    }\n  }\n\n  template<typename T>\n  EosCtaReporter& addParam(EosCtaReportParam key, const T& val)\n  {\n    mParams[key] = std::to_string(val);\n    return *this;\n  }\n\n  EosCtaReporter& addParam(EosCtaReportParam key, const std::string& val)\n  {\n    mParams[key] = val;\n    return *this;\n  }\n\n  EosCtaReporter& addParam(EosCtaReportParam key, const bool& val)\n  {\n    mParams[key] = (val ? \"true\" : \"false\");\n    return *this;\n  }\n\n  EosCtaReporter& addParam(EosCtaReportParam key, const char* val)\n  {\n    mParams[key] = val;\n    return *this;\n  }\n\nprotected:\n  // EosCtaReporter class should not be used directly\n  EosCtaReporter(std::function<void(const std::string&)> func = nullptr);\n  EosCtaReporter(const EosCtaReporter& c) = delete;\n  EosCtaReporter(EosCtaReporter&& c) : mParams(std::move(c.mParams)),\n    mWriterCallback(std::move(c.mWriterCallback)), mActive(c.mActive)\n  {\n    c.mActive = false;\n  }\n  EosCtaReporter& operator=(const EosCtaReporter&) = delete;\n  EosCtaReporter& operator=(EosCtaReporter&& c) = delete;\n\n  std::map<EosCtaReportParam, std::string> mParams;\n\nprivate:\n  void generateEosReportEntry(); // It will be called during destruction of EosCtaReporter\n\n  std::function<void(const std::string&)> mWriterCallback;\n  bool mActive;\n  static std::vector<EosCtaReportParam> DEFAULT_PARAMS;\n};\n\n// Prepare request EOS-CTA Reporter\nclass EosCtaReporterPrepareReq : public EosCtaReporter\n{\npublic:\n  // Prepare manager uses an interface to interact with the file system\n  // This is the reason why we need to pass a log writer callback\n  EosCtaReporterPrepareReq(std::function<void(const std::string&)> writeCallback);\nprivate:\n  static std::vector<EosCtaReportParam> DEFAULT_PARAMS_PREPARE_REQ;\n};\n\n// Prepare WFE EOS-CTA Reporter\nclass EosCtaReporterPrepareWfe : public EosCtaReporter\n{\npublic:\n  EosCtaReporterPrepareWfe();\nprivate:\n  static std::vector<EosCtaReportParam> DEFAULT_PARAMS_PREPARE_WFE;\n};\n\n// Evict cmd EOS-CTA Reporter\nclass EosCtaReporterEvict : public EosCtaReporter\n{\npublic:\n  EosCtaReporterEvict();\nprivate:\n  static std::vector<EosCtaReportParam> DEFAULT_PARAMS_EVICTCMD;\n};\n\n// File deletion EOS-CTA Reporter\nclass EosCtaReporterFileDeletion : public EosCtaReporter\n{\npublic:\n  EosCtaReporterFileDeletion();\nprivate:\n  static std::vector<EosCtaReportParam> DEFAULT_PARAMS_FILE_DELETION;\n};\n\n// File creation EOS-CTA Reporter\nclass EosCtaReporterFileCreation : public EosCtaReporter\n{\npublic:\n  EosCtaReporterFileCreation();\nprivate:\n  static std::vector<EosCtaReportParam> DEFAULT_PARAMS_FILE_CREATION;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/FuseServer/Caps.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FuseServer/Caps.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/FuseServer/Caps.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Timing.hh\"\n#include \"mgm/zmq/ZMQ.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"namespace/interface/IView.hh\"\n#include <regex.h>\n#include <thread>\n#include <string>\n#include <cstdlib>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nvoid\nFuseServer::Caps::Store(const eos::fusex::cap& ecap,\n                        eos::common::VirtualIdentity* vid)\n{\n  gOFS->MgmStats.Add(\"Eosxd::int::Store\", 0, 0, 1);\n  EXEC_TIMING_BEGIN(\"Eosxd::int::Store\");\n  std::lock_guard lg(mtx);\n  eos_static_info(\"id=%lx clientid=%s authid=%s\",\n                  ecap.id(),\n                  ecap.clientid().c_str(),\n                  ecap.authid().c_str());\n  // register this clientid to a given client uuid\n  ClientIds()[ecap.clientuuid()].insert(ecap.clientid());\n\n  // avoid to have multiple time entries for the same cap\n  if (auto kv = mCaps.find(ecap.authid());\n      kv != mCaps.end()) {\n    shared_cap cap = kv->second;\n\n    if ((*cap)()->id() != ecap.id()) {\n      eos_static_info(\"got inode change for %s from %x to %x\",\n                      ecap.authid().c_str(), (*cap)()->id(), ecap.id());\n      Remove(cap);\n    }\n  }\n\n  mTimeOrderedCap.emplace(std::make_pair(ecap.vtime(), ecap.authid()));\n  mClientCaps[ecap.clientid()].insert(ecap.authid());\n  mClientInoCaps[ecap.clientid()][ecap.id()].insert(ecap.authid());\n  shared_cap cap = std::make_shared<capx>();\n  *cap = ecap;\n  cap->set_vid(vid);\n  mCaps[ecap.authid()] = cap;\n  mInodeCaps[ecap.id()].insert(ecap.authid());\n  EXEC_TIMING_END(\"Eosxd::int::Store\");\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nbool\nFuseServer::Caps::Imply(uint64_t md_ino,\n                        FuseServer::Caps::authid_t authid,\n                        FuseServer::Caps::authid_t implied_authid)\n{\n  eos_static_info(\"id=%lx authid=%s implied-authid=%s\",\n                  md_ino,\n                  authid.c_str(),\n                  implied_authid.c_str());\n  shared_cap implied_cap = std::make_shared<capx>();\n  shared_cap cap = GetTS(authid);\n\n  if ((cap == nullptr) || !(*cap)()->id() || !implied_authid.length()) {\n    return false;\n  }\n\n  *implied_cap = *cap;\n  (*implied_cap)()->set_authid(implied_authid);\n  (*implied_cap)()->set_id(md_ino);\n  implied_cap->set_vid(cap->vid());\n  struct timespec ts;\n  eos::common::Timing::GetTimeSpec(ts, true);\n  {\n    size_t leasetime = 0;\n    {\n      eos::common::RWMutexReadLock lLock(gOFS->zMQ->gFuseServer.Client());\n      leasetime = gOFS->zMQ->gFuseServer.Client().leasetime((*cap)()->clientuuid());\n    }\n    (*implied_cap)()->set_vtime(ts.tv_sec + (leasetime ? leasetime : 300));\n    (*implied_cap)()->set_vtime_ns(ts.tv_nsec);\n    // fill the three views on caps\n    std::lock_guard lg(mtx);\n    mTimeOrderedCap.insert(std::pair<time_t, authid_t>((*implied_cap)()->vtime(),\n                           implied_authid));\n    mClientCaps[(*cap)()->clientid()].insert(implied_authid);\n    mClientInoCaps[(*cap)()->clientid()][(*cap)()->id()].insert(implied_authid);\n    mCaps[implied_authid] = implied_cap;\n    mInodeCaps[md_ino].insert(implied_authid);\n  }\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Get shared capability - one needs to hold (at least) the read lock\n//------------------------------------------------------------------------------\nFuseServer::Caps::shared_cap\nFuseServer::Caps::Get(const FuseServer::Caps::authid_t& id, bool make_default)\n{\n  if (auto kv = mCaps.find(id);\n      kv != mCaps.end()) {\n    return kv->second;\n  }\n\n  return make_default ? std::make_shared<capx>() : nullptr;\n}\n\n//------------------------------------------------------------------------------\n// Get Broadcast Caps\n//----------------------------------------------------------------------------\nstd::vector<std::shared_ptr<eos::mgm::FuseServer::Caps::capx>>\n    FuseServer::Caps::GetBroadcastCapsTS(uint64_t id,\n        shared_cap refcap,\n        const eos::fusex::md* mdptr,\n        bool suppress,\n        std::string suppress_stat_tag)\n{\n  std::vector<shared_cap> bccaps;\n  std::vector<authid_t> auth_ids;\n  size_t n_suppressed {0};\n  regex_t regex;\n  {\n    std::lock_guard lg(mtx);\n    auto ids = mInodeCaps.find(id);\n\n    if (ids == mInodeCaps.end()) {\n      return bccaps;\n    }\n\n    auth_ids.reserve(ids->second.size());\n    std::copy(ids->second.begin(),\n              ids->second.end(),\n              std::back_inserter(auth_ids));\n  }\n\n  if (suppress) {\n    // audience check\n    int audience = gOFS->zMQ->gFuseServer.Client().BroadCastMaxAudience();\n    std::string match =\n      gOFS->zMQ->gFuseServer.Client().BroadCastAudienceSuppressMatch();\n\n    if (audience && ((auth_ids.size() > (size_t)audience))) {\n      if (regcomp(&regex, match.c_str(), REG_ICASE | REG_EXTENDED | REG_NOSUB)) {\n        suppress = false;\n        eos_static_err(\"msg=\\\"broadcast audience suppress match not valid regex\\\" regex=\\\"%s\\\"\",\n                       match.c_str());\n      }\n    } else {\n      suppress = false;\n    }\n  }\n\n  eos_static_debug(\"id=%lx mInodeCaps.count=1\", id);\n\n  for (const auto& it : auth_ids) {\n    // TODO: do we need to debug log mCaps.found\n    // eos_static_debug(\"mCaps.found=1\")\n    shared_cap cap = GetTS(it);\n\n    if (!(*cap)()->id()) {\n      continue;\n    }\n\n    if ((*refcap)() && mdptr) {\n      // skip our own cap!\n      if ((*cap)()->authid() == mdptr->authid()) {\n        continue;\n      }\n\n      // skip identical client mounts!\n      if ((*cap)()->clientuuid() == (*refcap)()->clientuuid()) {\n        continue;\n      }\n\n      // skip same source\n      if ((*cap)()->clientuuid() == mdptr->clientuuid()) {\n        continue;\n      }\n    }\n\n    if (suppress) {\n      if (regexec(&regex, (*cap)()->clientid().c_str(), 0, NULL, 0) != REG_NOMATCH) {\n        n_suppressed++;\n        continue;\n      }\n    }\n\n    bccaps.emplace_back(std::move(cap));\n  }\n\n  if (n_suppressed && !suppress_stat_tag.empty()) {\n    gOFS->MgmStats.Add(suppress_stat_tag.c_str(), 0, 0, n_suppressed);\n  }\n\n  if (suppress) {\n    regfree(&regex);\n  }\n\n  return bccaps;\n}\n\n/*----------------------------------------------------------------------------*/\nint\nFuseServer::Caps::BroadcastRefreshFromExternal(uint64_t id, uint64_t pid,\n    bool notprot5)\n/*----------------------------------------------------------------------------*/\n{\n  gOFS->MgmStats.Add(\"Eosxd::int::BcRefreshExt\", 0, 0, 1);\n  EXEC_TIMING_BEGIN(\"Eosxd::int::BcRefreshExt\");\n  // broad-cast refresh for a given inode\n  eos_static_debug(\"id=%lx pid=%lx\", id, pid);\n  auto bccaps = GetBroadcastCapsTS(pid, nullptr, nullptr, true,\n                                   \"Eosxd::int::BcRefreshExtSup\");\n\n  for (auto it : bccaps) {\n    gOFS->zMQ->gFuseServer.Client().RefreshEntry((uint64_t) id,\n        (*it)()->clientuuid(),\n        (*it)()->clientid(),\n        notprot5);\n    errno = 0 ; // seems that ZMQ function might set errno\n  }\n\n  EXEC_TIMING_END(\"Eosxd::int::BcRefreshExt\");\n  return 0;\n}\n\n/*----------------------------------------------------------------------------*/\nint\nFuseServer::Caps::BroadcastDeletionFromExternal(uint64_t id,\n    const std::string& name,\n    struct timespec& pt_mtime)\n/*----------------------------------------------------------------------------*/\n{\n  gOFS->MgmStats.Add(\"Eosxd::int::BcDeletionExt\", 0, 0, 1);\n  EXEC_TIMING_BEGIN(\"Eosxd::int::BcDeletionExt\");\n  eos_static_debug(\"id=%lx name=%s\", id, name.c_str());\n  // broad-cast deletion for a given name in a container\n  auto bccaps = GetBroadcastCapsTS(id);\n\n  for (auto it : bccaps) {\n    gOFS->zMQ->gFuseServer.Client().DeleteEntry((uint64_t)(*it)()->id(),\n        (*it)()->clientuuid(),\n        (*it)()->clientid(),\n        name,\n        pt_mtime);\n    errno = 0 ; // seems that ZMQ function might set errno\n  }\n\n  EXEC_TIMING_END(\"Eosxd::int::BcDeletionExt\");\n  return 0;\n}\n\n/*----------------------------------------------------------------------------*/\nint\nFuseServer::Caps::BroadcastDeletion(uint64_t id, const eos::fusex::md& md,\n                                    const std::string& name,\n                                    struct timespec& pt_mtime\n                                   )\n/*----------------------------------------------------------------------------*/\n{\n  gOFS->MgmStats.Add(\"Eosxd::int::BcDeletion\", 0, 0, 1);\n  EXEC_TIMING_BEGIN(\"Eosxd::int::BcDeletion\");\n  eos_static_debug(\"id=%lx name=%s\", id, name.c_str());\n  FuseServer::Caps::shared_cap refcap = GetTS(md.authid());\n  auto bccaps = GetBroadcastCapsTS((*refcap)()->id(), refcap, &md);\n\n  for (auto it : bccaps) {\n    gOFS->zMQ->gFuseServer.Client().DeleteEntry((uint64_t)(*it)()->id(),\n        (*it)()->clientuuid(),\n        (*it)()->clientid(),\n        name,\n        pt_mtime\n                                               );\n    errno = 0;\n  }\n\n  EXEC_TIMING_END(\"Eosxd::int::BcDeletion\");\n  return 0;\n}\n\n/*----------------------------------------------------------------------------*/\nint\nFuseServer::Caps::BroadcastRefresh(uint64_t inode,\n                                   const eos::fusex::md& md,\n                                   uint64_t parent_inode,\n                                   bool notprot5)\n/*----------------------------------------------------------------------------*/\n{\n  gOFS->MgmStats.Add(\"Eosxd::int::BcRefresh\", 0, 0, 1);\n  EXEC_TIMING_BEGIN(\"Eosxd::int::BcRefresh\");\n  eos_static_debug(\"id=%lx parent=%lx\", inode, parent_inode);\n  size_t n_suppressed = 0;\n  std::vector<authid_t> auth_ids;\n  FuseServer::Caps::shared_cap refcap {nullptr};\n  {\n    std::lock_guard lg(mtx);\n    refcap = Get(md.authid(), false);\n    auto kv = mInodeCaps.find(parent_inode);\n\n    if (kv == mInodeCaps.end()) {\n      EXEC_TIMING_END(\"Eosxd::int::BcRefresh\");\n      return 0; // nothing to process here\n    }\n\n    auth_ids.reserve(kv->second.size());\n    std::copy(kv->second.begin(),\n              kv->second.end(),\n              std::back_inserter(auth_ids));\n  }\n  bool suppress_audience = false;\n  regex_t regex;\n  // audience check\n  int audience = gOFS->zMQ->gFuseServer.Client().BroadCastMaxAudience();\n  std::string match =\n    gOFS->zMQ->gFuseServer.Client().BroadCastAudienceSuppressMatch();\n\n  if (audience && ((auth_ids.size() > (size_t)audience))) {\n    suppress_audience = true;\n\n    if (regcomp(&regex, match.c_str(), REG_ICASE | REG_EXTENDED | REG_NOSUB)) {\n      suppress_audience = false;\n      eos_static_err(\"msg=\\\"broadcast audience suppress match not valid regex\\\" regex=\\\"%s\\\"\",\n                     match.c_str());\n    }\n  }\n\n  for (const auto& it : auth_ids) {\n    shared_cap cap = GetTS(it);\n\n    // avoid processing if the cap doesn't exist\n    if (!(*cap)()->id()) {\n      continue;\n    }\n\n    // skip identical client mounts!\n    if (refcap && (*cap)()->clientuuid() == (*refcap)()->clientuuid()) {\n      continue;\n    }\n\n    // skip same source\n    if ((*cap)()->clientuuid() == md.clientuuid()) {\n      continue;\n    }\n\n    if (suppress_audience) {\n      if (regexec(&regex, (*cap)()->clientid().c_str(), 0, NULL, 0) != REG_NOMATCH) {\n        n_suppressed++;\n        continue;\n      }\n    }\n\n    gOFS->zMQ->gFuseServer.Client().RefreshEntry((uint64_t) inode,\n        (*cap)()->clientuuid(),\n        (*cap)()->clientid(),\n        notprot5);\n    errno = 0;\n  }\n\n  if (n_suppressed) {\n    gOFS->MgmStats.Add(\"Eosxd::int::BcRefreshSup\", 0, 0, n_suppressed);\n  }\n\n  if (suppress_audience) {\n    regfree(&regex);\n  }\n\n  EXEC_TIMING_END(\"Eosxd::int::BcRefresh\");\n  return 0;\n}\n\n\nint\nFuseServer::Caps::BroadcastCap(shared_cap cap)\n{\n  if (cap && (*cap)()->id()) {\n    (void) gOFS->zMQ->gFuseServer.Client().SendCAP(cap);\n  }\n\n  return -1;\n}\n\nint\nFuseServer::Caps::BroadcastMD(const eos::fusex::md& md,\n                              uint64_t md_ino,\n                              uint64_t md_pino,\n                              uint64_t clock,\n                              struct timespec& p_mtime)\n{\n  gOFS->MgmStats.Add(\"Eosxd::int::BcMD\", 0, 0, 1);\n  EXEC_TIMING_BEGIN(\"Eosxd::int::BcMD\");\n  size_t n_suppressed = 0;\n  std::vector<shared_cap> bccaps;\n  std::unordered_set<std::string> clients_sent;\n  std::vector<authid_t> auth_ids;\n  FuseServer::Caps::shared_cap refcap {nullptr};\n  {\n    std::lock_guard lg(mtx);\n\n    if (md.authid().length()) {\n      refcap = Get(md.authid(), false);\n\n      if (refcap == nullptr) {\n        EXEC_TIMING_END(\"Eosxd::int::BcMD\");\n        return 0;\n      }\n    }\n\n    auto kv = mInodeCaps.find(md_pino);\n\n    if (kv == mInodeCaps.end()) {\n      EXEC_TIMING_END(\"Eosxd::int::BcMD\");\n      return 0; // nothing to process here\n    }\n\n    auth_ids.reserve(kv->second.size());\n    std::copy(kv->second.begin(),\n              kv->second.end(),\n              std::back_inserter(auth_ids));\n  }\n\n  if (refcap != nullptr) {\n    eos_static_info(\"id=%lx/%lx clientid=%s clientuuid=%s authid=%s\",\n                    (*refcap)()->id(), md_pino, (*refcap)()->clientid().c_str(),\n                    (*refcap)()->clientuuid().c_str(), (*refcap)()->authid().c_str());\n  }\n\n  bool suppress_audience = false;\n  regex_t regex;\n  // audience check\n  int audience = gOFS->zMQ->gFuseServer.Client().BroadCastMaxAudience();\n  std::string match =\n    gOFS->zMQ->gFuseServer.Client().BroadCastAudienceSuppressMatch();\n\n  if (audience && (auth_ids.size() > (size_t) audience)) {\n    suppress_audience = true;\n\n    if (regcomp(&regex, match.c_str(), REG_ICASE | REG_EXTENDED | REG_NOSUB)) {\n      suppress_audience = false;\n      eos_static_err(\"msg=\\\"broadcast audience suppress match not valid regex\\\" regex=\\\"%s\\\"\",\n                     match.c_str());\n    }\n  }\n\n  for (const auto& it : auth_ids) {\n    shared_cap cap = GetTS(it, false);\n\n    // avoid processing if the cap doesn't exist\n    if (!cap) {\n      continue;\n    }\n\n    // avoid processing if the cap doesn't exist or to a sent client\n    if (!(*cap)()->id() || (clients_sent.count((*cap)()->clientuuid()))) {\n      continue;\n    }\n\n    // skip identical client mounts, the have it anyway!\n    if (refcap && (*cap)()->clientuuid() == (*refcap)()->clientuuid()) {\n      continue;\n    }\n\n    // skip same source\n    if ((*cap)()->clientuuid() == md.clientuuid()) {\n      continue;\n    }\n\n    if (suppress_audience) {\n      if (regexec(&regex, (*cap)()->clientid().c_str(), 0, NULL, 0) != REG_NOMATCH) {\n        n_suppressed++;\n        continue;\n      }\n    }\n\n    eos_static_debug(\"id=%lx clientid=%s clientuuid=%s authid=%s\",\n                     (*cap)()->id(),\n                     (*cap)()->clientid().c_str(),\n                     (*cap)()->clientuuid().c_str(),\n                     (*cap)()->authid().c_str());\n    // make sure we sent the update only once to each client, eveh if this\n    // one has many caps\n    clients_sent.emplace((*cap)()->clientuuid());\n    gOFS->zMQ->gFuseServer.Client().SendMD(md,\n                                           (*cap)()->clientuuid(),\n                                           (*cap)()->clientid(),\n                                           md_ino,\n                                           md_pino,\n                                           clock,\n                                           p_mtime);\n    errno = 0; // avoid errno clobbering from ZMQ\n  }\n\n  if (n_suppressed) {\n    gOFS->MgmStats.Add(\"Eosxd::int::BcMDSup\", 0, 0, n_suppressed);\n  }\n\n  if (suppress_audience) {\n    regfree(&regex);\n  }\n\n  EXEC_TIMING_END(\"Eosxd::int::BcMD\");\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nstd::string\nFuseServer::Caps::Print(const std::string& option,\n                        const std::string& filter)\n{\n  std::string out;\n  std::string astring;\n  uint64_t now = (uint64_t) time(NULL);\n  eos::common::RWMutexReadLock lock;\n\n  if (option == \"p\") {\n    lock.Grab(gOFS->eosViewRWMutex);\n  }\n\n  eos_static_info(\"option=%s string=%s\", option.c_str(), filter.c_str());\n  regex_t regex;\n\n  if (filter.size() &&\n      regcomp(&regex, filter.c_str(), REG_ICASE | REG_EXTENDED | REG_NOSUB)) {\n    out = \"error: illegal regular expression ;\";\n    out += filter.c_str();\n    out += \"'\\n\";\n    regfree(&regex);\n    return out;\n  }\n\n  if (option == \"t\") {\n    std::lock_guard lg(mtx);\n\n    // print by time order\n    for (auto it = mTimeOrderedCap.begin(); it != mTimeOrderedCap.end();) {\n      if (!mCaps.count(it->second)) {\n        it = mTimeOrderedCap.erase(it);\n        continue;\n      }\n\n      char ahex[256];\n      shared_cap cap = mCaps[it->second];\n      snprintf(ahex, sizeof(ahex), \"%016lx\", (unsigned long)(*cap)()->id());\n      std::string match = \"\";\n      match += \"# i:\";\n      match += ahex;\n      match += \" a:\";\n      match += (*cap)()->authid();\n      match += \" c:\";\n      match += (*cap)()->clientid();\n      match += \" u:\";\n      match += (*cap)()->clientuuid();\n      match += \" m:\";\n      snprintf(ahex, sizeof(ahex), \"%08lx\", (unsigned long)(*cap)()->mode());\n      match += ahex;\n      match += \" v:\";\n\n      if (((*cap)()->vtime() - now) >  0) {\n        match += eos::common::StringConversion::GetSizeString(astring,\n                 (unsigned long long)(*cap)()->vtime() - now);\n      } else {\n        match += eos::common::StringConversion::GetSizeString(astring,\n                 (unsigned long long) 0);\n      }\n\n      match += \"\\n\";\n\n      if (filter.size() &&\n          (regexec(&regex, match.c_str(), 0, NULL, 0) == REG_NOMATCH)) {\n        it++;\n        continue;\n      }\n\n      out += match.c_str();\n      ++it;\n    }\n  }\n\n  if (option == \"i\") {\n    // print by inode\n    for (auto it = mInodeCaps.begin(); it != mInodeCaps.end(); ++it) {\n      char ahex[256];\n      snprintf(ahex, sizeof(ahex), \"%016lx\", (unsigned long) it->first);\n\n      if (filter.size() && (regexec(&regex, ahex, 0, NULL, 0) == REG_NOMATCH)) {\n        continue;\n      }\n\n      out += \"# i:\";\n      out += ahex;\n      out += \"\\n\";\n\n      for (auto sit = it->second.begin(); sit != it->second.end(); ++sit) {\n        out += \"___ a:\";\n        out += *sit;\n\n        if (!mCaps.count(*sit)) {\n          out += \" c:<unfound> u:<unfound> m:<unfound> v:<unfound>\\n\";\n        } else {\n          shared_cap cap = mCaps[*sit];\n          out += \" c:\";\n          out += (*cap)()->clientid();\n          out += \" u:\";\n          out += (*cap)()->clientuuid();\n          out += \" m:\";\n          snprintf(ahex, sizeof(ahex), \"%016lx\", (unsigned long)(*cap)()->mode());\n          out += ahex;\n          out += \" v:\";\n          out += eos::common::StringConversion::GetSizeString(astring,\n                 (unsigned long long)(*cap)()->vtime() - now);\n          out += \"\\n\";\n        }\n      }\n    }\n  }\n\n  if (option == \"p\") {\n    // print by inode\n    for (auto it = mInodeCaps.begin(); it != mInodeCaps.end(); ++it) {\n      std::string spath;\n\n      try {\n        if (eos::common::FileId::IsFileInode(it->first)) {\n          std::shared_ptr<eos::IFileMD> fmd =\n            gOFS->eosFileService->getFileMD(eos::common::FileId::InodeToFid(it->first));\n          spath = \"f:\";\n          spath += gOFS->eosView->getUri(fmd.get());\n        } else {\n          std::shared_ptr<eos::IContainerMD> cmd =\n            gOFS->eosDirectoryService->getContainerMD(it->first);\n          spath = \"d:\";\n          spath += gOFS->eosView->getUri(cmd.get());\n        }\n      } catch (eos::MDException& e) {\n        spath = \"<unknown>\";\n      }\n\n      if (filter.size() &&\n          (regexec(&regex, spath.c_str(), 0, NULL, 0) == REG_NOMATCH)) {\n        continue;\n      }\n\n      char apath[1024];\n      out += \"# \";\n      snprintf(apath, sizeof(apath), \"%-80s\", spath.c_str());\n      out += apath;\n      out += \"\\n\";\n\n      for (auto sit = it->second.begin(); sit != it->second.end(); ++sit) {\n        out += \"___ a:\";\n        out += *sit;\n\n        if (!mCaps.count(*sit)) {\n          out += \" c:<unfound> u:<unfound> m:<unfound> v:<unfound>\\n\";\n        } else {\n          shared_cap cap = mCaps[*sit];\n          out += \" c:\";\n          out += (*cap)()->clientid();\n          out += \" u:\";\n          out += (*cap)()->clientuuid();\n          out += \" m:\";\n          char ahex[20];\n          snprintf(ahex, sizeof(ahex), \"%016lx\", (unsigned long)(*cap)()->mode());\n          out += ahex;\n          out += \" v:\";\n          out += eos::common::StringConversion::GetSizeString(astring,\n                 (unsigned long long)(*cap)()->vtime() - now);\n          out += \"\\n\";\n        }\n      }\n    }\n  }\n\n  if (filter.size()) {\n    regfree(&regex);\n  }\n\n  return out;\n}\n\n//------------------------------------------------------------------------------\n// Delete capabilities corresponding to an inode\n//------------------------------------------------------------------------------\nint\nFuseServer::Caps::Delete(uint64_t md_ino)\n{\n  // Hash functor used for storing client_set_t::iterator objects in an\n  // unordered set\n  struct iter_client_set_hash {\n    size_t operator()(client_set_t::iterator it) const\n    {\n      return std::hash<std::string>()(it->first);\n    }\n  };\n  std::unordered_set<client_set_t::iterator, iter_client_set_hash>\n  to_del_client_caps;\n  std::lock_guard lg(mtx);\n  const auto it_inode_caps = mInodeCaps.find(md_ino);\n\n  if (it_inode_caps == mInodeCaps.end()) {\n    return ENONET;\n  }\n\n  const authid_set_t& set_authid = it_inode_caps->second;\n\n  for (auto it_client_caps = mClientCaps.begin();\n       it_client_caps != mClientCaps.end(); ++it_client_caps) {\n    for (const auto& authid : set_authid) {\n      // erase authid from the client set\n      it_client_caps->second.erase(authid);\n\n      if (it_client_caps->second.empty()) {\n        to_del_client_caps.insert(it_client_caps);\n      }\n    }\n  }\n\n  for (auto& it_elem : to_del_client_caps) {\n    mClientCaps.erase(it_elem);\n  }\n\n  for (const auto& authid : set_authid) {\n    const auto it_caps = mCaps.find(authid);\n\n    if (it_caps != mCaps.end()) {\n      const std::string client_id = (*it_caps->second)()->clientid();\n      auto it_cli_inocaps = mClientInoCaps.find(client_id);\n\n      if (it_cli_inocaps != mClientInoCaps.end()) {\n        it_cli_inocaps->second.erase(md_ino);\n\n        if (it_cli_inocaps->second.size() == 0) {\n          mClientInoCaps.erase(it_cli_inocaps);\n        }\n      }\n\n      mCaps.erase(it_caps);\n    }\n  }\n\n  mInodeCaps.erase(it_inode_caps);\n  return 0;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/FuseServer/Caps.hh",
    "content": "// ----------------------------------------------------------------------\n// File: FuseServer/Caps.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n\n#include <thread>\n#include <map>\n#include <unordered_map>\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/fusex.pb.h\"\n\n#include \"common/Mapping.hh\"\n#include \"common/Timing.hh\"\n#include \"common/Logging.hh\"\n#include \"common/RWMutex.hh\"\n\nEOSFUSESERVERNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n//! Class Caps\n//----------------------------------------------------------------------------\nclass Caps\n{\n  friend class FuseServer;\npublic:\n  class capx\n  {\n  public:\n\n    capx() = default;\n\n    virtual ~capx() = default;\n\n    capx& operator=(eos::fusex::cap other)\n    {\n      proto = other;\n      return *this;\n    }\n\n    void set_vid(eos::common::VirtualIdentity* vid)\n    {\n      mVid = *vid;\n    }\n\n    eos::common::VirtualIdentity* vid()\n    {\n      return &mVid;\n    }\n\n    eos::fusex::cap* operator()()\n    {\n      return &proto;\n    }\n\n  private:\n    eos::common::VirtualIdentity mVid;\n    eos::fusex::cap proto;\n  };\n\n  typedef std::shared_ptr<capx> shared_cap;\n\n  Caps() = default;\n\n  virtual ~Caps() = default;\n\n  typedef std::string authid_t;\n  typedef std::string clientid_t;\n  typedef std::string client_uuid_t;\n  typedef std::unordered_set<clientid_t> clientid_set_t;\n  typedef std::unordered_map<client_uuid_t, clientid_set_t> client_ids_t;\n  typedef std::pair<uint64_t, authid_t> ino_authid_t;\n  typedef std::unordered_set<authid_t> authid_set_t;\n  typedef std::unordered_map<uint64_t, authid_set_t> ino_map_t;\n  typedef std::unordered_set<uint64_t> ino_set_t;\n  typedef std::unordered_map<uint64_t, authid_set_t>\n  notify_set_t; // inode=>set(authid_t)\n  typedef std::unordered_map<clientid_t, authid_set_t> client_set_t;\n  typedef std::unordered_map<clientid_t, ino_map_t> client_ino_map_t;\n\n\n  ssize_t ncaps()\n  {\n    std::lock_guard lg(mtx);\n    return mTimeOrderedCap.size();\n  }\n\n  void pop()\n  {\n    std::lock_guard lg(mtx);\n\n    if (!mTimeOrderedCap.empty()) {\n      mTimeOrderedCap.erase(mTimeOrderedCap.begin());\n    }\n  }\n\n  bool expire()\n  {\n    std::lock_guard lg(mtx);\n    authid_t id;\n    time_t idtime = 0;\n\n    if (!mTimeOrderedCap.empty()) {\n      id = mTimeOrderedCap.begin()->second;\n      idtime = mTimeOrderedCap.begin()->first;\n    } else {\n      return false;\n    }\n\n    // TODO: C++17 - move initialization inside if\n    auto it = mCaps.find(id);\n\n    if (it != mCaps.end()) {\n      shared_cap cap = it->second;\n      int64_t now = (int64_t) time(NULL);\n\n      if (((*cap)()->vtime() + 10) <= (uint64_t)now) {\n        return Remove(cap);\n      } else {\n        if ((idtime + 10) <= now) {\n          return true;\n        } else {\n          return false;\n        }\n      }\n    }\n\n    return true;\n  }\n\n  void Store(const eos::fusex::cap& cap,\n             eos::common::VirtualIdentity* vid);\n\n\n  bool Imply(uint64_t md_ino,\n             authid_t authid,\n             authid_t implied_authid);\n\n  void dropCaps(const std::string& uuid)\n  {\n    eos_static_info(\"drop client caps: %s\", uuid.c_str());\n    std::vector<shared_cap> deleteme;\n    {\n      std::lock_guard lg(mtx);\n\n      for (auto it = mCaps.begin(); it != mCaps.end(); ++it) {\n        if ((*it->second)()->clientuuid() == uuid) {\n          deleteme.push_back(it->second);\n        }\n      }\n    }\n    {\n      for (auto it = deleteme.begin(); it != deleteme.end(); ++it) {\n        std::lock_guard lg(mtx);\n        shared_cap cap = *it;\n        Remove(*it);\n      }\n    }\n    // cleanup by client ids\n    {\n      std::lock_guard lg(mtx);\n      auto uuid_iter = mClientIds.find(uuid);\n\n      if (uuid_iter != mClientIds.end()) {\n        for (auto it = uuid_iter->second.begin(); it != uuid_iter->second.end(); ++it) {\n          mClientCaps.erase(*it);\n          mClientInoCaps.erase(*it);\n        }\n\n        mClientIds.erase(uuid_iter);\n      }\n    }\n  }\n\n  template <typename... Args>\n  bool RemoveTS(Args&& ... args)\n  {\n    std::lock_guard lg(mtx);\n    return Remove(std::forward<Args>(args)...);\n  }\n\n  bool Remove(shared_cap cap)\n  {\n    // you have to have a write lock for the caps\n    bool rc = mCaps.erase((*cap)()->authid());\n    mInodeCaps[(*cap)()->id()].erase((*cap)()->authid());\n\n    if (!mInodeCaps[(*cap)()->id()].size()) {\n      mInodeCaps.erase((*cap)()->id());\n    }\n\n    mClientInoCaps[(*cap)()->clientid()][(*cap)()->id()].erase((*cap)()->authid());\n\n    if (!mClientInoCaps[(*cap)()->clientid()][(*cap)()->id()].size()) {\n      mClientInoCaps[(*cap)()->clientid()].erase((*cap)()->id());\n\n      if (!mClientInoCaps[(*cap)()->clientid()].size()) {\n        mClientInoCaps.erase((*cap)()->clientid());\n      }\n    }\n\n    mClientCaps[(*cap)()->clientid()].erase((*cap)()->authid());\n\n    if (mClientCaps[(*cap)()->clientid()].size() == 0) {\n      mClientCaps.erase((*cap)()->clientid());\n    }\n\n    return rc;\n  }\n\n  int Delete(uint64_t id);\n\n  shared_cap Get(const authid_t& id, bool make_default = true);\n\n  template <typename... Args>\n  auto GetTS(Args&& ... args)\n  {\n    std::lock_guard lg(mtx);\n    return Get(std::forward<Args>(args)...);\n  }\n\n  const capx* GetRaw(const authid_t& id);\n\n  int BroadcastCap(shared_cap cap);\n\n\n  int BroadcastDeletion(uint64_t inode,\n                        const eos::fusex::md& md,\n                        const std::string& name,\n                        struct timespec& p_mtime);\n\n  int BroadcastRefresh(uint64_t\n                       inode,\n                       const eos::fusex::md& md,\n                       uint64_t\n                       parent_inode,\n                       bool notprot5 = false\n                      ); // broad cast triggered by fuse network\n\n  int BroadcastRefreshFromExternal(uint64_t\n                                   inode,\n                                   uint64_t\n                                   parent_inode,\n                                   bool notprot5 = false\n                                  ); // broad cast triggered non-fuse network\n\n  int BroadcastDeletionFromExternal(uint64_t inode,\n                                    const std::string& name,\n                                    struct timespec& p_mtime);\n\n  int BroadcastMD(const eos::fusex::md& md,\n                  uint64_t md_ino,\n                  uint64_t md_pino,\n                  uint64_t clock,\n                  struct timespec& p_mtime\n                 ); // broad cast changed md around\n  std::string Print(const std::string& option,\n                    const std::string& filter);\n\n  const auto& GetCaps() const\n  {\n    return mCaps;\n  }\n\n  auto GetAllCaps()\n  {\n    std::vector<shared_cap> results;\n    std::lock_guard lg(mtx);\n\n    for (const auto& kv : mCaps) {\n      results.push_back(kv.second);\n    }\n\n    return results;\n  }\n\n  bool HasCap(authid_t authid)\n  {\n    return (this->mCaps.count(authid) ? true : false);\n  }\n\n  bool HasInodeId(const std::string& client_id,\n                  uint64_t id)\n  {\n    std::lock_guard lg(mtx);\n\n    if (auto kv = mClientInoCaps.find(client_id);\n        kv != mClientInoCaps.end()) {\n      return kv->second.count(id) > 0;\n    }\n\n    return false;\n  }\n\n  authid_set_t GetInodeCapAuthIds(const std::string& client_id,\n                                  uint64_t id)\n  {\n    authid_set_t results;\n    std::lock_guard lg(mtx);\n\n    if (auto kv = mClientInoCaps.find(client_id);\n        kv != mClientInoCaps.end()) {\n      if (auto auth_ids = kv->second.find(id);\n          auth_ids != kv->second.end()) {\n        std::copy(auth_ids->second.begin(),\n                  auth_ids->second.end(),\n                  std::inserter(results, results.begin()));\n      }\n    }\n\n    return results;\n  }\n\n  notify_set_t& InodeCaps()\n  {\n    return mInodeCaps;\n  }\n\n  client_set_t& ClientCaps()\n  {\n    return mClientCaps;\n  }\n\n  client_ino_map_t& ClientInoCaps()\n  {\n    return mClientInoCaps;\n  }\n\n  client_ids_t& ClientIds()\n  {\n    return mClientIds;\n  }\n\n  std::string Dump()\n  {\n    std::string s;\n    std::lock_guard lg(mtx);\n    s = std::to_string(mTimeOrderedCap.size()) + \" c: \" + std::to_string(\n          mCaps.size()) + \" cc: \"\n        + std::to_string(mClientCaps.size()) + \" cic: \" + std::to_string(\n          mClientInoCaps.size()) + \" ic: \"\n        + std::to_string(mInodeCaps.size());\n    return s;\n  }\n\n  // Given a pid, return a vector of shared caps matching this\n  // if a reference cap and mdptr are given, these ids are excluded\n  std::vector<shared_cap> GetBroadcastCapsTS(uint64_t pid,\n      shared_cap refcap = nullptr,\n      const eos::fusex::md* mdptr = nullptr,\n      bool suppress = false,\n      std::string suppress_stat_tag = \"\");\n\n\n\nprotected:\n\n  std::mutex mtx;\n  // a time ordered multimap pointing to caps\n  std::multimap< time_t, authid_t > mTimeOrderedCap;\n  // authid=>cap lookup map\n  std::unordered_map<authid_t, shared_cap> mCaps;\n  // clientid=>list of authid\n  client_set_t mClientCaps;\n  // clientid=>list of inodes\n  client_ino_map_t mClientInoCaps;\n  // inode=>authid_t\n  notify_set_t mInodeCaps;\n  // uuid=>set of clientid\n  client_ids_t mClientIds;\n};\n\nEOSFUSESERVERNAMESPACE_END\n"
  },
  {
    "path": "mgm/FuseServer/Clients.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FuseServer/Clients.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <string>\n#include <cstdlib>\n#include <thread>\n\n#include \"mgm/FuseServer/Clients.hh\"\n#include \"common/Logging.hh\"\n#include \"common/CommentLog.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/zmq/ZMQ.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n\n//------------------------------------------------------------------------------\n// Retrieve global eosxd client statistics\n//------------------------------------------------------------------------------\nvoid\nFuseServer::Clients::ClientStats(size_t& nclients, size_t& active_clients,\n                                 size_t& locked_clients)\n{\n  nclients = 0;\n  active_clients = 0 ;\n  locked_clients = 0;\n  struct timespec now_time;\n  eos::common::Timing::GetTimeSpec(now_time, true);\n  eos::common::RWMutexReadLock lLock(*this);\n\n  for (auto it = this->map().begin(); it != this->map().end(); ++it) {\n    nclients++;\n\n    if (it->second.statistics().blockedms() > (5 * 1000 * 60)) {\n      locked_clients++;\n    }\n\n    int64_t idletime = (it->second.get_opstime_sec()) ? (now_time.tv_sec -\n                       it->second.get_opstime_sec()) : -1;\n\n    if (idletime <= 300) {\n      active_clients++;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Monitor heart beat\n//------------------------------------------------------------------------------\nvoid\nFuseServer::Clients::MonitorHeartBeat()\n{\n  eos_static_info(\"msg=\\\"starting fusex heart beat thread\\\"\");\n\n  while (true) {\n    EXEC_TIMING_BEGIN(\"Eosxd::int::MonitorHeartBeat\");\n    client_uuid_t evictmap;\n    client_uuid_t evictversionmap;\n    struct timespec tsnow;\n    {\n      eos::common::RWMutexReadLock lLock(*this);\n      eos::common::Timing::GetTimeSpec(tsnow);\n\n      for (auto it = map().begin(); it != map().end(); ++it) {\n        double last_heartbeat = tsnow.tv_sec - it->second.heartbeat().clock() +\n                                (((int64_t) tsnow.tv_nsec - (int64_t) it->second.heartbeat().clock_ns())\n                                 * 1.0 / 1000000000.0);\n\n        if (it->second.heartbeat().shutdown()) {\n          evictmap[it->second.heartbeat().uuid()] = it->first;\n          it->second.set_state(Client::EVICTED);\n          eos_static_info(\"client='%s' shutdown [ %s ] \",\n                          it->first.c_str(), Info(it->first).c_str());\n          gOFS->MgmStats.Add(\"Eosxd::prot::umount\", 0, 0, 1);\n        } else {\n          if (last_heartbeat > mHeartBeatWindow) {\n            if (last_heartbeat > mHeartBeatOfflineWindow) {\n              if (last_heartbeat > mHeartBeatRemoveWindow) {\n                evictmap[it->second.heartbeat().uuid()] = it->first;\n                it->second.set_state(Client::EVICTED);\n                eos_static_info(\"client='%s' evicted [ %s ] \",\n                                it->first.c_str(), Info(it->first).c_str());\n                gOFS->MgmStats.Add(\"Eosxd::prot::evicted\", 0, 0, 1);\n              } else {\n                // drop locks once\n                if (it->second.state() != Client::OFFLINE) {\n                  gOFS->zMQ->gFuseServer.Locks().dropLocks(it->second.heartbeat().uuid());\n                  eos_static_info(\"client='%s' offline [ %s ] \",\n                                  it->first.c_str(), Info(it->first).c_str());\n                  gOFS->MgmStats.Add(\"Eosxd::prot::offline\", 0, 0, 1);\n                }\n\n                it->second.set_state(Client::OFFLINE);\n              }\n            } else {\n              it->second.set_state(Client::VOLATILE);\n            }\n          } else {\n            it->second.set_state(Client::ONLINE);\n          }\n        }\n\n        if (it->second.heartbeat().protversion() < it->second.heartbeat().PROTOCOLV2) {\n          // protocol version mismatch, evict this client\n          evictversionmap[it->second.heartbeat().uuid()] = it->first;\n          it->second.set_state(Client::EVICTED);\n        }\n      }\n    }\n\n    // Delete clients to be evicted\n    if (!evictmap.empty()) {\n      {\n        std::set<std::string> uuids;\n        {\n          eos::common::RWMutexWriteLock lLock(*this);\n\n          for (auto it = evictmap.begin(); it != evictmap.end(); ++it) {\n            uuids.insert(it->first);\n            mMap.erase(it->second);\n            mUUIDView.erase(it->first);\n            gOFS->zMQ->gFuseServer.Locks().dropLocks(it->first);\n          }\n        }\n\n        for (auto it = uuids.begin(); it != uuids.end(); ++it) {\n          gOFS->zMQ->gFuseServer.Cap().dropCaps(*it);\n        }\n      }\n    }\n\n    // Delete client ot be evicted because of a version mismatch\n    if (!evictversionmap.empty()) {\n      for (auto it = evictversionmap.begin(); it != evictversionmap.end(); ++it) {\n        std::string versionerror =\n          \"Server supports PROTOCOLV4 and requires at least PROTOCOLV2\";\n        std::string uuid = it->first;\n        Evict(uuid, versionerror);\n        eos::common::RWMutexWriteLock lLock(*this);\n        mMap.erase(it->second);\n        mUUIDView.erase(it->first);\n      }\n    }\n\n    gOFS->zMQ->gFuseServer.Flushs().expireFlush();\n    EXEC_TIMING_END(\"Eosxd::int::MonitorHeartBeat\");\n    std::this_thread::sleep_for(std::chrono::seconds(1));\n\n    if (should_terminate()) {\n      break;\n    }\n\n    if (gOFS) {\n      gOFS->MgmStats.Add(\"Eosxd::int::MonitorHeartBeat\", 0, 0, 1);\n    }\n  }\n\n  return ;\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nbool\nFuseServer::Clients::Dispatch(const std::string identity,\n                              eos::fusex::heartbeat& hb)\n{\n  gOFS->MgmStats.Add(\"Eosxd::int::Heartbeat\", 0, 0, 1);\n  EXEC_TIMING_BEGIN(\"Eosxd::int::Heartbeat\");\n  bool rc = true;\n  eos::common::RWMutexWriteLock lLock(*this);\n  std::set<Caps::shared_cap> caps_to_revoke;\n\n  if (this->map().count(identity)) {\n    rc = false;\n  }\n\n  // if heartbeats are older than the offline window, we just ignore them to avoid client 'waving'\n  struct timespec tsnow;\n  eos::common::Timing::GetTimeSpec(tsnow);\n  double heartbeat_delay = tsnow.tv_sec - hb.clock() + (((\n                             int64_t) tsnow.tv_nsec - (int64_t) hb.clock_ns()) * 1.0 / 1000000000.0);\n\n  if (heartbeat_delay > mHeartBeatOfflineWindow) {\n    eos_static_warning(\"delayed heartbeat from client=%s - delay=%.02f - dropping heartbeat\",\n                       identity.c_str(), heartbeat_delay);\n    return rc;\n  }\n\n  if (hb.log().size()) {\n    gOFS->mFusexLogTraces->Add(time(NULL), hb.host().c_str(), hb.uuid().c_str(),\n                               hb.version().c_str(), std::string(hb.host() + \":\" + hb.mount()).c_str() ,\n                               hb.log().c_str(), 0);\n    hb.clear_log();\n  }\n\n  if (hb.trace().size()) {\n    gOFS->mFusexStackTraces->Add(time(NULL), hb.host().c_str(), hb.uuid().c_str(),\n                                 hb.version().c_str(), std::string(hb.host() + \":\" + hb.mount()).c_str() ,\n                                 hb.trace().c_str(), 0);\n    hb.clear_trace();\n  }\n\n  (this->map())[identity].heartbeat() = hb;\n\n  // tag first ops time\n  if (!this->map()[identity].get_opstime_sec()) {\n    this->map()[identity].tag_opstime();\n  }\n\n  (this->uuidview())[hb.uuid()] = identity;\n  lLock.Release();\n  {\n    // apply auth revocation requested by the client\n    auto map = hb.mutable_authrevocation();\n\n    for (auto it = map->begin(); it != map->end(); ++it) {\n      Caps::shared_cap cap = gOFS->zMQ->gFuseServer.Cap().GetTS(it->first);\n\n      if (cap) {\n        caps_to_revoke.insert(cap);\n        eos_static_debug(\"cap-revocation: authid=%s vtime:= %u\",\n                         it->first.c_str(),\n                         (*cap)()->vtime());\n      }\n    }\n  }\n\n  if (rc) {\n    {\n      eos::common::RWMutexReadLock lLock(*this);\n      eos_static_info(\"client='%s' mount [ %s ] \", identity.c_str(),\n                      Info(identity).c_str());\n    }\n    gOFS->MgmStats.Add(\"Eosxd::prot::mount\", 0, 0, 1);\n    // ask a client to drop all caps when we see him the first time because we might have lost our caps due to a restart/failover\n    BroadcastDropAllCaps(identity, hb);\n    // communicate our current heart-beat interval\n    eos::fusex::config cfg;\n    cfg.set_hbrate(mHeartBeatInterval);\n    cfg.set_dentrymessaging(true);\n    cfg.set_writesizeflush(true);\n    cfg.set_appname(true);\n    cfg.set_mdquery(true);\n    cfg.set_hideversion(true);\n    cfg.set_serverversion(std::string(VERSION) + std::string(\"::\") + std::string(\n                            RELEASE));\n    BroadcastConfig(identity, cfg);\n  } else {\n    if (caps_to_revoke.size()) {\n      gOFS->MgmStats.Add(\"Eosxd::int::AuthRevocation\", 0, 0, caps_to_revoke.size());\n      EXEC_TIMING_BEGIN(\"Eosxd::int::AuthRevocation\");\n\n      // revoke LEASES by cap\n      for (auto it = caps_to_revoke.begin(); it != caps_to_revoke.end(); ++it) {\n        gOFS->zMQ->gFuseServer.Cap().RemoveTS(*it);\n      }\n\n      EXEC_TIMING_END(\"Eosxd::int::AuthRevocation\");\n    }\n  }\n\n  EXEC_TIMING_END(\"Eosxd::int::Heartbeat\");\n  return rc;\n}\n\n\n\n//------------------------------------------------------------------------------\n// Get Clients Info - the caller must hold a lock as we access the map!\n//------------------------------------------------------------------------------\nstd::string\nFuseServer::Clients::Info(const std::string& identity)\n{\n  std::string out;\n  char formatline[16384];\n  struct timespec tsnow;\n  eos::common::Timing::GetTimeSpec(tsnow);\n  auto it = this->map().find(identity);\n\n  if (it != this->map().end()) {\n    snprintf(formatline, sizeof(formatline),\n             \"name=%s host=%s version=%s state=%s start=%s dt=[%.02f:%.02f] uuid=%s pid=%u fds=%u type=%s mount=%s app=%s\",\n             it->second.heartbeat().name().c_str(),\n             it->second.heartbeat().host().c_str(),\n             it->second.heartbeat().version().c_str(),\n             it->second.status[it->second.state()],\n             eos::common::Timing::utctime(it->second.heartbeat().starttime()).c_str(),\n             (int64_t)tsnow.tv_sec - (int64_t)it->second.heartbeat().clock() +\n             (((int64_t) tsnow.tv_nsec -\n               (int64_t) it->second.heartbeat().clock_ns()) * 1.0 / 1000000000.0),\n             it->second.heartbeat().delta() * 1000,\n             it->second.heartbeat().uuid().c_str(),\n             it->second.heartbeat().pid(),\n             it->second.statistics().open_files(),\n             it->second.heartbeat().automounted() ? \"autofs\" : \"static\",\n             it->second.heartbeat().mount().c_str(),\n             it->second.heartbeat().appname().c_str()\n            );\n    out += formatline;\n  }\n\n  return out;\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nvoid\nFuseServer::Clients::Print(std::string& out, std::string options)\n{\n  struct timespec tsnow;\n  eos::common::Timing::GetTimeSpec(tsnow);\n  std::unordered_map<std::string, size_t> clientcaps;\n\n  for (const auto& cap : gOFS->zMQ->gFuseServer.Cap().GetAllCaps()) {\n    if ((*cap)()->id()) {\n      clientcaps[(*cap)()->clientuuid()]++;\n    }\n  }\n\n  struct timespec now_time;\n\n  eos::common::Timing::GetTimeSpec(now_time, true);\n\n  eos::common::RWMutexReadLock lLock(*this);\n\n  for (auto it = this->map().begin(); it != this->map().end(); ++it) {\n    char formatline[4096];\n    bool t5min_idle, t1h_idle, t1d_idle, t1w_idle;\n    t5min_idle = t1h_idle = t1d_idle = t1w_idle = false;\n    int64_t idletime = (it->second.get_opstime_sec()) ? (now_time.tv_sec -\n                       it->second.get_opstime_sec()) : -1;\n\n    if (idletime >= 300) {\n      t5min_idle = it->second.validate_opstime(now_time, 300);\n      t1h_idle = it->second.validate_opstime(now_time, 3600);\n      t1d_idle = it->second.validate_opstime(now_time, 86400);\n      t1w_idle = it->second.validate_opstime(now_time, 7 * 86400);\n    }\n\n    std::string idle;\n    std::string lockup;\n    std::string lockfunc;\n\n    // preset the idle string\n    if (idletime > 300) {\n      if (t1w_idle) {\n        idle = \">1w\";\n      } else {\n        if (t1d_idle) {\n          idle = \">1d\";\n        } else {\n          if (t1h_idle) {\n            idle = \">1h\";\n          } else {\n            if (t5min_idle) {\n              idle = \">5m\";\n            } else {\n              idle = \"act\";\n            }\n          }\n        }\n      }\n    } else {\n      idle = \"act\";\n    }\n\n    //    if ( it->second.statistics().blockedms() > (5*1000*60) ) {\n    if (it->second.statistics().blockedms() > (5 * 1000)) {\n      // a mutex hanging for longer than 5 minutes marks a client as locked up\n      lockup = \"locked:\";\n      lockup += it->second.statistics().blockedfunc();\n      lockfunc = it->second.statistics().blockedfunc();\n    } else {\n      lockup = \"vacant\";\n    }\n\n    if (options.find(\"m\") == std::string::npos) {\n      snprintf(formatline, sizeof(formatline),\n               \"client : %-8s %32s %-8s %-8s %s %.02f %.02f %36s p=%u caps=%lu fds=%u %s [%s] %s mount=%s prot=%d app=%s\\n\",\n               it->second.heartbeat().name().c_str(),\n               it->second.heartbeat().host().c_str(),\n               it->second.heartbeat().version().c_str(),\n               it->second.status[it->second.state()],\n               eos::common::Timing::utctime(it->second.heartbeat().starttime()).c_str(),\n               (int64_t)tsnow.tv_sec - (int64_t)it->second.heartbeat().clock() +\n               (((int64_t) tsnow.tv_nsec -\n                 (int64_t) it->second.heartbeat().clock_ns()) * 1.0 / 1000000000.0),\n               it->second.heartbeat().delta() * 1000,\n               it->second.heartbeat().uuid().c_str(),\n               it->second.heartbeat().pid(),\n               clientcaps[it->second.heartbeat().uuid()],\n               it->second.statistics().open_files(),\n               it->second.heartbeat().automounted() ? \"autofs\" : \"static\",\n               lockup.c_str(),\n               idle.c_str(),\n               it->second.heartbeat().mount().c_str(),\n               it->second.heartbeat().protversion(),\n               it->second.heartbeat().appname().c_str()\n              );\n      out += formatline;\n    }\n\n    if (options.find(\"l\") != std::string::npos) {\n      snprintf(formatline, sizeof(formatline),\n               \"......   ino          : %ld\\n\"\n               \"......   ino-to-del   : %ld\\n\"\n               \"......   ino-backlog  : %ld\\n\"\n               \"......   ino-ever     : %ld\\n\"\n               \"......   ino-ever-del : %ld\\n\"\n               \"......   threads      : %d\\n\"\n               \"......   total-ram    : %.03f GB\\n\"\n               \"......   free-ram     : %.03f GB\\n\"\n               \"......   vsize        : %.03f GB\\n\"\n               \"......   rsize        : %.03f GB\\n\"\n               \"......   wr-buf-mb    : %.00f MB\\n\"\n               \"......   ra-buf-mb     :%.00f MB\\n\"\n               \"......   load1        : %.02f\\n\"\n               \"......   leasetime    : %u s\\n\"\n               \"......   open-files   : %u\\n\"\n               \"......   logfile-size : %lu\\n\"\n               \"......   rbytes       : %lu\\n\"\n               \"......   wbytes       : %lu\\n\"\n               \"......   n-op         : %lu\\n\"\n               \"......   rd60         : %.02f MB/s\\n\"\n               \"......   wr60         : %.02f MB/s\\n\"\n               \"......   iops60       : %.02f \\n\"\n               \"......   xoff         : %lu\\n\"\n               \"......   ra-xoff      : %lu\\n\"\n               \"......   ra-nobuf     : %lu\\n\"\n               \"......   wr-nobuf     : %lu\\n\"\n               \"......   idle         : %ld\\n\"\n               \"......   recovery-ok  : %d\\n\"\n               \"......   recovery-fail: %d\\n\"\n               \"......   blockedms    : %.02f [%s]\\n\"\n               \"......   blockedops   : %u\\n\"\n               \"......   blockedroot  : %s\\n\",\n               it->second.statistics().inodes(),\n               it->second.statistics().inodes_todelete(),\n               it->second.statistics().inodes_backlog(),\n               it->second.statistics().inodes_ever(),\n               it->second.statistics().inodes_ever_deleted(),\n               it->second.statistics().threads(),\n               it->second.statistics().total_ram_mb() / 1024.0,\n               it->second.statistics().free_ram_mb() / 1024.0,\n               it->second.statistics().vsize_mb() / 1024.0,\n               it->second.statistics().rss_mb() / 1024.0,\n               it->second.statistics().wr_buf_mb(),\n               it->second.statistics().ra_buf_mb(),\n               it->second.statistics().load1(),\n               it->second.heartbeat().leasetime() ? it->second.heartbeat().leasetime() : 300,\n               it->second.statistics().open_files(),\n               it->second.statistics().logfilesize(),\n               it->second.statistics().rbytes(),\n               it->second.statistics().wbytes(),\n               it->second.statistics().nio(),\n               it->second.statistics().rd_rate_60_mb(),\n               it->second.statistics().wr_rate_60_mb(),\n               it->second.statistics().iops_60(),\n               it->second.statistics().xoff(),\n               it->second.statistics().raxoff(),\n               it->second.statistics().ranobuf(),\n               it->second.statistics().wrnobuf(),\n               idletime,\n               it->second.statistics().recovery_ok(),\n               it->second.statistics().recovery_fail(),\n               it->second.statistics().blockedms(),\n               it->second.statistics().blockedfunc().length() ?\n               it->second.statistics().blockedfunc().c_str() : \"none\",\n               it->second.statistics().blockedops(),\n               it->second.statistics().blockedroot() ? \"true\" : \"false\"\n              );\n      out += formatline;\n    }\n\n    if (options.find(\"m\") != std::string::npos) {\n      snprintf(formatline, sizeof(formatline),\n               \"client=%s host=%s version=%s state=%s time=\\\"%s\\\" tof=%.02f delta=%.02f uuid=%s pid=%u caps=%lu fds=%u type=%s mount=\\\"%s\\\" prot=%d app=%s \"\n               \"ino=%ld \"\n               \"ino-to-del=%ld \"\n               \"ino-backlog=%ld \"\n               \"ino-ever=%ld \"\n               \"ino-ever-del=%ld \"\n               \"threads=%d \"\n               \"total-ram-gb=%.03f \"\n               \"free-ram-gb=%.03f \"\n               \"vsize-gb=%.03f \"\n               \"rsize-gb=%.03f \"\n               \"wr-buf-mb=%.00f \"\n               \"ra-buf-mb=%.00f \"\n               \"load1=%.02f \"\n               \"leasetime=%u \"\n               \"open-files=%u \"\n               \"logfile-size=%lu \"\n               \"rbytes=%lu \"\n               \"wbytes=%lu \"\n               \"n-op=%lu \"\n               \"rd60-rate-mb=%.02f \"\n               \"wr60-rate-mb=%.02f \"\n               \"iops60=%.02f \"\n               \"xoff=%lu \"\n               \"ra-xoff=%lu \"\n               \"ra-nobuf=%lu \"\n               \"wr-nobuf=%lu \"\n               \"idle=%ld \"\n               \"recovery-ok=%d \"\n               \"recovery-fail=%d \"\n               \"blockedms=%f \"\n               \"blockedfunc=%s \"\n               \"blockedops=%u \"\n               \"blockedroot=%d\\n\",\n               it->second.heartbeat().name().c_str(),\n               it->second.heartbeat().host().c_str(),\n               it->second.heartbeat().version().c_str(),\n               it->second.status[it->second.state()],\n               eos::common::Timing::utctime(it->second.heartbeat().starttime()).c_str(),\n               (int64_t)tsnow.tv_sec - (int64_t)it->second.heartbeat().clock() +\n               (((int64_t) tsnow.tv_nsec -\n                 (int64_t) it->second.heartbeat().clock_ns()) * 1.0 / 1000000000.0),\n               it->second.heartbeat().delta() * 1000,\n               it->second.heartbeat().uuid().c_str(),\n               it->second.heartbeat().pid(),\n               clientcaps[it->second.heartbeat().uuid()],\n               it->second.statistics().open_files(),\n               it->second.heartbeat().automounted() ? \"autofs\" : \"static\",\n               it->second.heartbeat().mount().c_str(),\n               it->second.heartbeat().protversion(),\n               it->second.heartbeat().appname().c_str(),\n               it->second.statistics().inodes(),\n               it->second.statistics().inodes_todelete(),\n               it->second.statistics().inodes_backlog(),\n               it->second.statistics().inodes_ever(),\n               it->second.statistics().inodes_ever_deleted(),\n               it->second.statistics().threads(),\n               it->second.statistics().total_ram_mb() / 1024.0,\n               it->second.statistics().free_ram_mb() / 1024.0,\n               it->second.statistics().vsize_mb() / 1024.0,\n               it->second.statistics().rss_mb() / 1024.0,\n               it->second.statistics().wr_buf_mb(),\n               it->second.statistics().ra_buf_mb(),\n               it->second.statistics().load1(),\n               it->second.heartbeat().leasetime() ? it->second.heartbeat().leasetime() : 300,\n               it->second.statistics().open_files(),\n               it->second.statistics().logfilesize(),\n               it->second.statistics().rbytes(),\n               it->second.statistics().wbytes(),\n               it->second.statistics().nio(),\n               it->second.statistics().rd_rate_60_mb(),\n               it->second.statistics().wr_rate_60_mb(),\n               it->second.statistics().iops_60(),\n               it->second.statistics().xoff(),\n               it->second.statistics().raxoff(),\n               it->second.statistics().ranobuf(),\n               it->second.statistics().wrnobuf(),\n               idletime,\n               it->second.statistics().recovery_ok(),\n               it->second.statistics().recovery_fail(),\n               it->second.statistics().blockedms(),\n               it->second.statistics().blockedfunc().length() ?\n               it->second.statistics().blockedfunc().c_str() : \"none\",\n               it->second.statistics().blockedops(),\n               it->second.statistics().blockedroot());\n      out += formatline;\n    }\n\n    if (options.find(\"k\") != std::string::npos) {\n      std::map<uint64_t, std::set < pid_t>> rlocks;\n      std::map<uint64_t, std::set < pid_t>> wlocks;\n      gOFS->zMQ->gFuseServer.Locks().lsLocks(it->second.heartbeat().uuid(), rlocks,\n                                             wlocks);\n\n      for (auto rit = rlocks.begin(); rit != rlocks.end(); ++rit) {\n        if (rit->second.size()) {\n          snprintf(formatline, sizeof(formatline), \"      t:rlock i:%016lx p:\",\n                   rit->first);\n          out += formatline;\n          std::string pidlocks;\n\n          for (auto pit = rit->second.begin(); pit != rit->second.end(); ++pit) {\n            if (pidlocks.length()) {\n              pidlocks += \",\";\n            }\n\n            char spid[16];\n            snprintf(spid, sizeof(spid), \"%u\", *pit);\n            pidlocks += spid;\n          }\n\n          out += pidlocks;\n          out += \"\\n\";\n        }\n      }\n\n      for (auto wit = wlocks.begin(); wit != wlocks.end(); ++wit) {\n        if (wit->second.size()) {\n          snprintf(formatline, sizeof(formatline), \"      t:wlock i:%016lx p:\",\n                   wit->first);\n          out += formatline;\n          std::string pidlocks;\n\n          for (auto pit = wit->second.begin(); pit != wit->second.end(); ++pit) {\n            if (pidlocks.length()) {\n              pidlocks += \",\";\n            }\n\n            char spid[16];\n            snprintf(spid, sizeof(spid), \"%u\", *pit);\n            pidlocks += spid;\n          }\n\n          out += pidlocks;\n          out += \"\\n\";\n        }\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nsize_t\nFuseServer::Clients::leasetime(const std::string& uuid)\n{\n  // requires a Client read lock\n  size_t leasetime = 0;\n\n  if (this->uuidview().count(uuid) &&\n      this->map().count(this->uuidview()[uuid])) {\n    leasetime = this->map()[this->uuidview()[uuid]].heartbeat().leasetime();\n  }\n\n  if (leasetime > (7 * 86400)) {\n    // don't allow longer lease times as a week\n    leasetime = 7 * 86400;\n  }\n\n  return leasetime;\n}\n\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nint\nFuseServer::Clients::Evict(std::string& uuid, std::string reason,\n                           std::vector<std::string>* evicted_out)\n{\n  if ((uuid == \"static\") ||\n      (uuid == \"autofs\")) {\n    std::vector<std::pair<std::string, std::string>> evict_uuids;\n\n    // evict static or autofs clients with criteria\n    if (reason.substr(0, 4) == \"mem:\") {\n      // evict according to memory footprint\n      uint64_t memory_condition = strtoull(reason.substr(4).c_str(), 0, 10);\n\n      if (memory_condition) {\n        eos::common::RWMutexReadLock lLock(*this);\n\n        for (auto it = this->map().begin(); it != this->map().end(); ++it) {\n          if ((uuid == \"static\") && (it->second.heartbeat().automounted())) {\n            continue;\n          }\n\n          if ((uuid == \"autofs\") && (!it->second.heartbeat().automounted())) {\n            continue;\n          }\n\n          if (it->second.statistics().rss_mb() > memory_condition) {\n            std::string memreason = \"consuming \";\n            memreason += std::to_string(it->second.statistics().rss_mb());\n            memreason += \" MB of resident memory\";\n            evict_uuids.push_back(std::pair<std::string, std::string>\n                                  (it->second.heartbeat().uuid(),\n                                   memreason));\n          }\n        }\n      }\n\n      int retc = 0;\n\n      for (auto it : evict_uuids) {\n        retc |= Evict(it.first, it.second, evicted_out);\n      }\n\n      return retc;\n    } else if (reason.substr(0, 5) == \"idle:\") {\n      // evict according to idle time\n      int64_t idle_condition = strtoull(reason.substr(5).c_str(), 0, 10);\n\n      if (idle_condition) {\n        struct timespec now_time;\n        eos::common::Timing::GetTimeSpec(now_time, true);\n        eos::common::RWMutexReadLock lLock(*this);\n\n        for (auto it = this->map().begin(); it != this->map().end(); ++it) {\n          if ((uuid == \"static\") && (it->second.heartbeat().automounted())) {\n            continue;\n          }\n\n          if ((uuid == \"autofs\") && (!it->second.heartbeat().automounted())) {\n            continue;\n          }\n\n          int64_t idletime = (it->second.get_opstime_sec()) ? (now_time.tv_sec -\n                             it->second.get_opstime_sec()) : -1;\n\n          if (idletime > idle_condition) {\n            std::string idlereason = \"longer than \";\n            idlereason += std::to_string(idletime);\n            idlereason += \" seconds idle\";\n            evict_uuids.push_back(std::pair<std::string, std::string>\n                                  (it->second.heartbeat().uuid(),\n                                   idlereason));\n          }\n        }\n      }\n\n      int retc = 0;\n\n      for (auto it : evict_uuids) {\n        retc |= Evict(it.first, it.second, evicted_out);\n      }\n\n      return retc;\n    }\n  } else {\n    // prepare eviction message for a client by uuid\n    eos::fusex::response rsp;\n    rsp.set_type(rsp.EVICT);\n    rsp.mutable_evict_()->set_reason(reason);\n    std::string rspstream;\n    rsp.SerializeToString(&rspstream);\n    eos::common::RWMutexReadLock lLock(*this);\n\n    if (!mUUIDView.count(uuid)) {\n      // even if this uuid does not exist we can use it to remove stale locks\n      gOFS->zMQ->gFuseServer.Locks().dropLocks(uuid);\n      return ENOENT;\n    }\n\n    std::string id = mUUIDView[uuid];\n    lLock.Release();\n    eos_static_info(\"msg=\\\"evicting client\\\" uuid=%s name=%s\",\n                    uuid.c_str(), id.c_str());\n\n    if (evicted_out) {\n      std::string out = \"uuid=\";\n      out += uuid;\n      out += \" name=\";\n      out += id;\n      out += \" reason='\";\n      out += reason;\n      out += \"'\";\n      evicted_out->push_back(out);\n    }\n\n    gOFS->zMQ->mTask->reply(id, rspstream);\n    return 0;\n  }\n\n  return EINVAL;\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nint\nFuseServer::Clients::DeleteEntry(uint64_t md_ino,\n                                 const std::string& uuid,\n                                 const std::string& clientid,\n                                 const std::string& name,\n                                 struct timespec& pt_mtime\n                                )\n{\n  gOFS->MgmStats.Add(\"Eosxd::int::DeleteEntry\", 0, 0, 1);\n  EXEC_TIMING_BEGIN(\"Eosxd::int::DeleteEntry\");\n  // prepare release cap message\n  eos::fusex::response rsp;\n  rsp.set_type(rsp.DENTRY);\n  rsp.mutable_dentry_()->set_type(eos::fusex::dentry::REMOVE);\n  rsp.mutable_dentry_()->set_name(name);\n  rsp.mutable_dentry_()->set_md_ino(md_ino);\n  rsp.mutable_dentry_()->set_clientid(clientid);\n  rsp.mutable_dentry_()->set_pt_mtime(pt_mtime.tv_sec);\n  rsp.mutable_dentry_()->set_pt_mtime_ns(pt_mtime.tv_nsec);\n  std::string rspstream;\n  rsp.SerializeToString(&rspstream);\n  eos::common::RWMutexReadLock lLock(*this);\n\n  if (!mUUIDView.count(uuid)) {\n    return ENOENT;\n  }\n\n  std::string id = mUUIDView[uuid];\n  lLock.Release();\n  eos_static_info(\"msg=\\\"asking dentry deletion\\\" uuid=%s clientid=%s id=%lx name=%s\",\n                  uuid.c_str(), clientid.c_str(), md_ino, name.c_str());\n  gOFS->zMQ->mTask->reply(id, rspstream);\n  EXEC_TIMING_END(\"Eosxd::int::DeleteEntry\");\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nint\nFuseServer::Clients::RefreshEntry(uint64_t md_ino,\n                                  const std::string& uuid,\n                                  const std::string& clientid,\n                                  bool notprot5\n                                 )\n{\n  EXEC_TIMING_BEGIN(\"Eosxd::int::RefreshEntry\");\n  // prepare release cap message\n  eos::fusex::response rsp;\n  rsp.set_type(rsp.REFRESH);\n  rsp.mutable_refresh_()->set_md_ino(md_ino);\n  std::string rspstream;\n  rsp.SerializeToString(&rspstream);\n  eos::common::RWMutexReadLock lLock(*this);\n\n  if (!mUUIDView.count(uuid)) {\n    return ENOENT;\n  }\n\n  std::string id = mUUIDView[uuid];\n  eos_static_debug(\"client=%s\", map()[id].heartbeat().version().c_str());\n\n  if (notprot5 &&\n      map()[id].heartbeat().protversion() >= map()[id].heartbeat().PROTOCOLV5) {\n    // this protocol version does not need refresh messages\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"suppressing refresh to client '%s' version='%s' protocl='%d'\",\n                       clientid.c_str(), map()[id].heartbeat().version().c_str(),\n                       map()[id].heartbeat().protversion());\n    }\n  } else {\n    if (DeferClient(map()[id].heartbeat().version(), \"4.4.18\")) {\n      // dont' send refresh to client version < 4.4.18 (4.4.17 deadlocks, others ignore)\n      eos_static_info(\"suppressing refresh to client '%s' version='%s'\",\n                      clientid.c_str(), map()[id].heartbeat().version().c_str());\n    } else {\n      gOFS->MgmStats.Add(\"Eosxd::int::RefreshEntry\", 0, 0, 1);\n      std::string id = mUUIDView[uuid];\n      lLock.Release();\n      eos_static_debug(\"msg=\\\"asking dentry refresh\\\" uuid=%s clientid=%s id=%lx\",\n                       uuid.c_str(), clientid.c_str(), md_ino);\n      gOFS->zMQ->mTask->reply(id, rspstream);\n    }\n  }\n\n  EXEC_TIMING_END(\"Eosxd::int::RefreshEntry\");\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nint\nFuseServer::Clients::SendMD(const eos::fusex::md& md,\n                            const std::string& uuid,\n                            const std::string& clientid,\n                            uint64_t md_ino,\n                            uint64_t md_pino,\n                            uint64_t clock,\n                            struct timespec& p_mtime\n                           )\n/*----------------------------------------------------------------------------*/\n\n{\n  gOFS->MgmStats.Add(\"Eosxd::int::SendMD\", 0, 0, 1);\n  EXEC_TIMING_BEGIN(\"Eosxd::int::SendMD\");\n  // prepare update message\n  eos::fusex::response rsp;\n  rsp.set_type(rsp.MD);\n  *(rsp.mutable_md_()) = md;\n  rsp.mutable_md_()->set_type(eos::fusex::md::MD);\n  // the client needs this to sort out the quota accounting using the cap map\n  rsp.mutable_md_()->set_clientid(clientid);\n  // when a file is created the inode is not yet written in the const md object\n  rsp.mutable_md_()->set_md_ino(md_ino);\n  rsp.mutable_md_()->set_md_pino(md_pino);\n\n  if (p_mtime.tv_sec) {\n    rsp.mutable_md_()->set_pt_mtime(p_mtime.tv_sec);\n    rsp.mutable_md_()->set_pt_mtime_ns(p_mtime.tv_nsec);\n  }\n\n  rsp.mutable_md_()->set_clock(clock);\n  std::string rspstream;\n  rsp.SerializeToString(&rspstream);\n  eos::common::RWMutexReadLock lLock(*this);\n\n  if (!mUUIDView.count(uuid)) {\n    return ENOENT;\n  }\n\n  std::string id = mUUIDView[uuid];\n  lLock.Release();\n  eos_static_debug(\"msg=\\\"sending md update\\\" uuid=%s clientid=%s id=%lx\",\n                   uuid.c_str(), clientid.c_str(), md_ino);\n  gOFS->zMQ->mTask->reply(id, rspstream);\n  EXEC_TIMING_END(\"Eosxd::int::SendMD\");\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nint\nFuseServer::Clients::SendCAP(FuseServer::Caps::shared_cap cap)\n/*----------------------------------------------------------------------------*/\n{\n  gOFS->MgmStats.Add(\"Eosxd::int::SendCAP\", 0, 0, 1);\n  EXEC_TIMING_BEGIN(\"Eosxd::int::SendCAP\");\n  // prepare update message\n  eos::fusex::response rsp;\n  rsp.set_type(rsp.CAP);\n  *(rsp.mutable_cap_()) = *(*cap)();\n  const std::string& uuid = (*cap)()->clientuuid();\n  std::string rspstream;\n  rsp.SerializeToString(&rspstream);\n  eos::common::RWMutexReadLock lLock(*this);\n\n  if (!mUUIDView.count(uuid)) {\n    return ENOENT;\n  }\n\n  std::string clientid = mUUIDView[uuid];\n  lLock.Release();\n  eos_static_info(\"msg=\\\"sending cap update\\\" uuid=%s clientid=%s cap-id=%lx\",\n                  uuid.c_str(), clientid.c_str(), (*cap)()->id());\n  gOFS->zMQ->mTask->reply(clientid, rspstream);\n  EXEC_TIMING_END(\"Eosxd::int::SendCAP\");\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nvoid\nFuseServer::Clients::HandleStatistics(const std::string identity,\n                                      const eos::fusex::statistics& stats)\n{\n  eos::common::RWMutexWriteLock lLock(*this);\n  uint64_t previous_ops = (this->map())[identity].statistics().nio();\n  (this->map())[identity].statistics() = stats;\n\n  // update the last ops time whenever the operations counter changes\n  // this is very rough and only precise to the interval of statistic updates\n  if (!previous_ops ||\n      ((this->map())[identity].statistics().nio() != previous_ops)) {\n    (this->map())[identity].tag_opstime();\n  }\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"\");\n  }\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nbool\nFuseServer::Clients::DeferClient(std::string clientversion,\n                                 std::string allowversion)\n{\n  std::vector<std::string> v1;\n  std::vector<std::string> v2;\n  eos::common::StringConversion::Tokenize(clientversion, v1, \".\");\n  eos::common::StringConversion::Tokenize(allowversion,  v2, \".\");\n  unsigned long client_v = 0;\n  unsigned long allowd_v = 0;\n\n  if (v1.size() == v2.size()) {\n    for (size_t i = 0; i != v1.size(); i++) {\n      if (i != 0) {\n        client_v *= 1000;\n        allowd_v *= 1000;\n      }\n\n      client_v += strtoul(v1[i].c_str(), 0, 10);\n      allowd_v += strtoul(v2[i].c_str(), 0, 10);\n    }\n  }\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"client-v:%lu allowd-v:%lu (%s/%s)\", client_v, allowd_v,\n                     clientversion.c_str(), allowversion.c_str());\n  }\n\n  return (client_v < allowd_v);\n}\n/*----------------------------------------------------------------------------*/\nint\nFuseServer::Clients::SetHeartbeatInterval(int interval)\n{\n  // broadcast to all clients\n  eos::common::RWMutexWriteLock lLock(*this);\n  mHeartBeatInterval = interval;\n\n  for (auto it = this->map().begin(); it != this->map().end(); ++it) {\n    std::string uuid = it->second.heartbeat().uuid();\n    std::string id = mUUIDView[uuid];\n\n    if (id.length()) {\n      eos::fusex::config cfg;\n      cfg.set_hbrate(interval);\n      cfg.set_dentrymessaging(true);\n      cfg.set_writesizeflush(true);\n      cfg.set_appname(true);\n      cfg.set_mdquery(true);\n      cfg.set_hideversion(true);\n      cfg.set_serverversion(std::string(VERSION) + std::string(\"::\") + std::string(\n                              RELEASE));\n      BroadcastConfig(id, cfg);\n    }\n  }\n\n  return 0;\n}\n\n/*----------------------------------------------------------------------------*/\nint\nFuseServer::Clients::SetQuotaCheckInterval(int interval)\n{\n  eos::common::RWMutexWriteLock lLock(*this);\n  mQuotaCheckInterval = interval;\n  return 0;\n}\n\n/*----------------------------------------------------------------------------*/\n\n\n/*----------------------------------------------------------------------------*/\nint\nFuseServer::Clients::BroadcastConfig(const std::string& identity,\n                                     eos::fusex::config& cfg)\n/*----------------------------------------------------------------------------*/\n{\n  gOFS->MgmStats.Add(\"Eosxd::int::BcConfig\", 0, 0, 1);\n  EXEC_TIMING_BEGIN(\"Eosxd::int::BcConfig\");\n  // prepare new heartbeat interval message\n  eos::fusex::response rsp;\n  rsp.set_type(rsp.CONFIG);\n  *(rsp.mutable_config_()) = cfg;\n  std::string rspstream;\n  rsp.SerializeToString(&rspstream);\n  eos_static_info(\"msg=\\\"broadcast config to client\\\" name=%s heartbeat-rate=%d\",\n                  identity.c_str(), cfg.hbrate());\n  gOFS->zMQ->mTask->reply(identity, rspstream);\n  EXEC_TIMING_END(\"Eosxd::int::BcConfig\");\n  return 0;\n}\n\n/*----------------------------------------------------------------------------*/\nint\nFuseServer::Clients::BroadcastDropAllCaps(const std::string& identity,\n    eos::fusex::heartbeat& hb)\n/*----------------------------------------------------------------------------*/\n{\n  gOFS->MgmStats.Add(\"Eosxd::int::BcDropAll\", 0, 0, 1);\n  EXEC_TIMING_BEGIN(\"Eosxd::int::BcDropAll\");\n  // prepare drop all caps message\n  eos::fusex::response rsp;\n  rsp.set_type(rsp.DROPCAPS);\n  std::string rspstream;\n  rsp.SerializeToString(&rspstream);\n  eos_static_info(\"msg=\\\"broadcast drop-all-caps to  client\\\" uuid=%s name=%s\",\n                  hb.uuid().c_str(), identity.c_str());\n  gOFS->zMQ->mTask->reply(identity, rspstream);\n  EXEC_TIMING_END(\"Eosxd::int::BcDropAll\");\n  return 0;\n}\n\n\n/*----------------------------------------------------------------------------*/\nvoid\nFuseServer::Clients::SetBroadCastMaxAudience(int size)\n/*----------------------------------------------------------------------------*/\n{\n  mMaxBroadCastAudience = size;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nFuseServer::Clients::SetBroadCastAudienceSuppressMatch(const std::string& match)\n{\n  mMaxbroadCastAudienceMatch = match;\n}\n\n\n\n\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/FuseServer/Clients.hh",
    "content": "// ----------------------------------------------------------------------\n// File: FuseServer/Clients.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n\n#include <thread>\n#include <map>\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/FuseServer/Caps.hh\"\n#include \"mgm/fusex.pb.h\"\n#include \"common/Timing.hh\"\n#include \"common/Logging.hh\"\n\n\n\n\nEOSFUSESERVERNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n//! Class Clients\n//----------------------------------------------------------------------------\n\nclass Clients : public eos::common::RWMutex\n{\npublic:\n  Clients(): eos::common::RWMutex(),\n    mHeartBeatWindow(15), mHeartBeatOfflineWindow(30),\n    mHeartBeatRemoveWindow(120), mHeartBeatInterval(10),\n    mQuotaCheckInterval(10)\n  {\n    mBlocking = true;\n  }\n\n  virtual ~Clients() = default;\n\n  void ClientStats(size_t& nclients, size_t& active_clients,\n                   size_t& locked_clients);\n\n  bool\n  Dispatch(const std::string identity, eos::fusex::heartbeat& hb);\n  void Print(std::string& out, std::string options = \"\");\n  // This method requires the lock to be held by the caller!\n  std::string Info(const std::string& identity);\n\n  void HandleStatistics(const std::string identity,\n                        const eos::fusex::statistics& stats);\n\n  class Client\n  {\n  public:\n\n    Client() : mState(PENDING) {}\n\n    virtual ~Client() = default;\n\n    eos::fusex::heartbeat& heartbeat()\n    {\n      return heartbeat_;\n    }\n\n    eos::fusex::statistics& statistics()\n    {\n      return statistics_;\n    }\n\n    enum status_t {\n      PENDING, EVICTED, OFFLINE, VOLATILE, ONLINE\n    };\n\n    const char* const status [6] {\n      \"pending\",\n      \"evicted\",\n      \"offline\",\n      \"volatile\",\n      \"online\",\n      0\n    };\n\n    void set_state(status_t s)\n    {\n      mState = s;\n    }\n\n    void tag_opstime()\n    {\n      eos::common::Timing::GetTimeSpec(ops_time, true);\n    }\n\n    bool validate_opstime(const struct timespec& ref_time, uint64_t age) const\n    {\n      // return true if the last operations time is older than age compared to ref_time\n      if (eos::common::Timing::GetCoarseAgeInNs(&ops_time,\n          &ref_time) / 1000000000.0 > age) {\n        return true;\n      } else {\n        return false;\n      }\n    }\n\n    uint64_t get_opstime_sec() const\n    {\n      return ops_time.tv_sec;\n    }\n    uint64_t get_opstime_nsec() const\n    {\n      return ops_time.tv_nsec;\n    }\n\n    inline status_t state() const\n    {\n      return mState;\n    }\n\n  private:\n    eos::fusex::heartbeat heartbeat_;\n    eos::fusex::statistics statistics_;\n    struct timespec ops_time;\n    std::atomic<status_t> mState;\n\n    // inode, pid lock map\n    std::map<uint64_t, std::set < pid_t>> mLockPidMap;\n  } ;\n\n  typedef std::map<std::string, Client> client_map_t;\n  typedef std::map<std::string, std::string> client_uuid_t;\n\n\n  ssize_t nclients()\n  {\n    eos::common::RWMutexReadLock lock(*this);\n    return mMap.size();\n  }\n\n  client_map_t& map()\n  {\n    return mMap;\n  }\n\n\n  std::string client2app (const std::string& clientid) {\n    eos::common::RWMutexReadLock lLock(*this);\n    auto client = map().find(clientid);\n    if (client != map().end()) {\n      return client->second.heartbeat().appname();\n    }\n    // return just generic 'fuse' app if we don't find the client\n    return \"fuse\";\n  }\n\n  client_uuid_t& uuidview()\n  {\n    return mUUIDView;\n  }\n\n  size_t leasetime(const std::string& uuid);\n\n  void\n  MonitorHeartBeat();\n\n  // check if threads should terminate\n  bool should_terminate()\n  {\n    return terminate_.load();\n  }\n\n  // indicate to terminate\n  void terminate()\n  {\n    terminate_.store(true, std::memory_order_seq_cst);\n  }\n\n  // evict a client by force\n  int Evict(std::string& uuid, std::string reason,\n            std::vector<std::string>* evicted_out = 0);\n\n  // delete entry\n  int DeleteEntry(uint64_t id,\n                  const std::string& uuid,\n                  const std::string& clientid,\n                  const std::string& name,\n\t\t  struct timespec& pt_mtime);\n\n  // refresh entry\n  int RefreshEntry(uint64_t id,\n                   const std::string& uuid,\n                   const std::string& clientid,\n\t\t   bool notprot5=false);\n\n  // send MD after update\n  int SendMD(const eos::fusex::md& md,\n             const std::string& uuid,\n             const std::string& clientid,\n             uint64_t md_ino,\n             uint64_t md_pino,\n             uint64_t clock,\n             struct timespec& p_mtime\n            );\n\n  // broadcast a new cap\n  int SendCAP(FuseServer::Caps::shared_cap cap);\n\n  // broad cast triggered by heartbeat function\n  int BroadcastDropAllCaps(const std::string& identity,\n                           eos::fusex::heartbeat& hb);\n\n  // broad cast new heartbeat interval\n  int BroadcastConfig(const std::string& identity, eos::fusex::config& cfg);\n\n  // change the clients heartbeat interval\n  int SetHeartbeatInterval(int interval);\n\n  // change the quote node check interval\n  int SetQuotaCheckInterval(int interval);\n\n  // get heartbeat interval setting\n  int HeartbeatInterval() const\n  {\n    return mHeartBeatInterval;\n  }\n\n  // get quota check interval setting\n  int QuotaCheckInterval() const\n  {\n    return mQuotaCheckInterval;\n  }\n\n  // get broadcast max audience\n  int BroadCastMaxAudience() const\n  {\n    return mMaxBroadCastAudience;\n  }\n\n  // get broadcast audience match\n  std::string BroadCastAudienceSuppressMatch() const\n  {\n    return mMaxbroadCastAudienceMatch;\n  }\n\n  // to defer an operation based on client versions\n  bool DeferClient(std::string clienversion, std::string minimum_allowed_version);\n\n  // set max audience before suppression\n  void SetBroadCastMaxAudience(int size);\n\n  // set max audience client match to be suppressed\n  void SetBroadCastAudienceSuppressMatch(const std::string& match);\n\nprivate:\n  // lookup client full id to heart beat\n  client_map_t mMap;\n  // lookup client uuid to full id\n  client_uuid_t mUUIDView;\n  // heartbeat window in seconds\n  float mHeartBeatWindow;\n  // heartbeat window when to remove entries\n  float mHeartBeatOfflineWindow;\n\n  // heartbeat window when client entries get removed\n  float mHeartBeatRemoveWindow;\n\n  // client heartbeat interval\n  int mHeartBeatInterval;\n\n  // quota check interval\n  int mQuotaCheckInterval;\n\n  // max audience before suppression\n  int mMaxBroadCastAudience;\n\n  // match string for hosts which get suppressed\n  std::string mMaxbroadCastAudienceMatch;\n\n  std::atomic<bool> terminate_;\n};\n\n\nEOSFUSESERVERNAMESPACE_END\n"
  },
  {
    "path": "mgm/FuseServer/Flush.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FuseServer/Flush.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <string>\n#include <thread>\n\n#include \"mgm/FuseServer/Flush.hh\"\n#include \"common/Logging.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n\n\nEOSMGMNAMESPACE_BEGIN\n\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nvoid\nFuseServer::Flush::beginFlush(uint64_t id, std::string client)\n{\n  eos_static_info(\"ino=%016x client=%s\", id, client.c_str());\n  XrdSysMutexHelper lock(this);\n  flush_info_t finfo(client);\n  flushmap[id][client].Add(finfo);\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nvoid\nFuseServer::Flush::endFlush(uint64_t id, std::string client)\n{\n  eos_static_info(\"ino=%016x client=%s\", id, client.c_str());\n  XrdSysMutexHelper lock(this);\n  flush_info_t finfo(client);\n\n  if (flushmap[id][client].Remove(finfo)) {\n    flushmap[id].erase(client);\n\n    if (!flushmap[id].size()) {\n      flushmap.erase(id);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nbool\nFuseServer::Flush::hasFlush(uint64_t id)\n{\n  // this function takes maximum 255ms and waits for a flush to be removed\n  // this function might block a client connection/thread for the given time\n  bool has = false;\n  size_t delay = 1;\n\n  for (size_t i = 0 ; i < 8; ++i) {\n    {\n      XrdSysMutexHelper lock(this);\n      has = validateFlush(id);\n    }\n\n    if (!has) {\n      return false;\n    }\n\n    std::this_thread::sleep_for(std::chrono::milliseconds(delay));\n    delay *= 2;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nbool\nFuseServer::Flush::validateFlush(uint64_t id)\n{\n  bool has = false;\n\n  if (flushmap.count(id)) {\n    for (auto it = flushmap[id].begin(); it != flushmap[id].end();) {\n      if (eos::common::Timing::GetAgeInNs(&it->second.ftime) < 0) {\n        has = true;\n        ++it;\n      } else {\n        it = flushmap[id].erase(it);\n      }\n    }\n\n    if (!flushmap[id].size()) {\n      flushmap.erase(id);\n    }\n  }\n\n  return has;\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nvoid\nFuseServer::Flush::expireFlush()\n{\n  XrdSysMutexHelper lock(this);\n\n  for (auto it = flushmap.begin(); it != flushmap.end();) {\n    for (auto fit = it->second.begin(); fit != it->second.end();) {\n      if (eos::common::Timing::GetAgeInNs(&fit->second.ftime) < 0) {\n        ++fit;\n      } else {\n        fit = it->second.erase(fit);\n      }\n    }\n\n    if (!it->second.size()) {\n      it = flushmap.erase(it);\n    } else {\n      ++it;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nvoid\nFuseServer::Flush::Print(std::string& out)\n{\n  XrdSysMutexHelper lock(this);\n\n  for (auto it = flushmap.begin(); it != flushmap.end(); ++it) {\n    for (auto fit = it->second.begin(); fit != it->second.end(); ++fit) {\n      long long valid = eos::common::Timing::GetAgeInNs(&fit->second.ftime);\n      char formatline[4096];\n      snprintf(formatline, sizeof(formatline),\n               \"flush : ino : %016lx client : %-8s valid=%.02f sec\\n\",\n               it->first,\n               fit->first.c_str(),\n               1.0 * valid / 1000000000.0);\n      out += formatline;\n    }\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/FuseServer/Flush.hh",
    "content": "// ----------------------------------------------------------------------\n// File: FuseServer/Flush.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include <map>\n\n#include \"mgm/Namespace.hh\"\n#include \"common/Timing.hh\"\n#include \"common/Logging.hh\"\n\n#include <XrdSys/XrdSysPthread.hh>\n\n\nEOSFUSESERVERNAMESPACE_BEGIN\n\n  //----------------------------------------------------------------------------\n  //! Class Flush\n  //----------------------------------------------------------------------------\n\n  class Flush : XrdSysMutex\n  {\n    // essentially a map containing clients which currently flush a file\n  public:\n\n    static constexpr int cFlushWindow = 60;\n\n    Flush() = default;\n\n    virtual ~Flush() = default;\n\n    void beginFlush(uint64_t id, std::string client);\n\n    void endFlush(uint64_t id, std::string client);\n\n    bool hasFlush(uint64_t id);\n\n    bool validateFlush(uint64_t id);\n\n    void expireFlush();\n\n    void Print(std::string& out);\n\n  private:\n\n    typedef struct flush_info {\n\n      flush_info() : client(\"\"), nref(0)\n      {\n        ftime.tv_sec = 0;\n        ftime.tv_nsec = 0;\n      }\n\n      flush_info(std::string _client) : client(_client)\n      {\n        eos::common::Timing::GetTimeSpec(ftime);\n        ftime.tv_sec += cFlushWindow;\n        ftime.tv_nsec = 0;\n        nref = 0;\n      }\n\n      void Add(struct flush_info l)\n      {\n        ftime = l.ftime;\n        nref++;\n      }\n\n      bool Remove(struct flush_info l)\n      {\n        nref--;\n\n        if (nref > 0) {\n          return false;\n        }\n\n        return true;\n      }\n\n      std::string client;\n      struct timespec ftime;\n      ssize_t nref;\n    } flush_info_t;\n\n    std::map<uint64_t, std::map<std::string, flush_info_t> > flushmap;\n  };\n\n\nEOSFUSESERVERNAMESPACE_END\n"
  },
  {
    "path": "mgm/FuseServer/FusexCastBatch.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file FusexCastBatch.hh\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include <functional>\n#include <list>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class FusexCastBatch\n//------------------------------------------------------------------------------\nclass FusexCastBatch\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  FusexCastBatch() = default;\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~FusexCastBatch()\n  {\n    if (!mBatch.empty()) {\n      Execute();\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Don't allow copy or move of these objects\n  //----------------------------------------------------------------------------\n  FusexCastBatch(const FusexCastBatch&) = delete;\n  FusexCastBatch& operator =(const FusexCastBatch&) = delete;\n  FusexCastBatch(FusexCastBatch&&) = delete;\n  FusexCastBatch& operator =(FusexCastBatch&&) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Add update to batch\n  //!\n  //! @param f callable to be executed at a later stage\n  //----------------------------------------------------------------------------\n  void Register(std::function<void()>&& f)\n  {\n    mBatch.emplace_back(std::move(f));\n  }\n\n  //----------------------------------------------------------------------------\n  //! Perform all the callbacks registered in the list\n  //----------------------------------------------------------------------------\n  void Execute()\n  {\n    for (auto& f : mBatch) {\n      f();\n    }\n\n    mBatch.clear();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get size of the batch to be executed\n  //----------------------------------------------------------------------------\n  unsigned long GetSize() const\n  {\n    return mBatch.size();\n  }\n\nprivate:\n  std::list<std::function<void()>> mBatch;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/FuseServer/Locks.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FuseServer.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <string>\n#include <cstdlib>\n\n#include \"mgm/FuseServer/Locks.hh\"\n#include \"common/Logging.hh\"\n#include \"mgm/fuse-locks/LockTracker.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nFuseServer::Lock::shared_locktracker\nFuseServer::Lock::getLocks(uint64_t id)\n{\n  XrdSysMutexHelper lock(this);\n\n  // make sure you have this object locked\n  if (!lockmap.count(id)) {\n    lockmap[id] = std::make_shared<LockTracker>();\n  }\n\n  return lockmap[id];\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nvoid\nFuseServer::Lock::purgeLocks()\n{\n  XrdSysMutexHelper lock(this);\n  std::set<uint64_t>purgeset;\n\n  for (auto it = lockmap.begin(); it != lockmap.end(); ++it) {\n    if (!it->second->inuse()) {\n      purgeset.insert(it->first);\n    }\n  }\n\n  for (auto it = purgeset.begin(); it != purgeset.end(); ++it) {\n    lockmap.erase(*it);\n  }\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nint\nFuseServer::Lock::dropLocks(uint64_t id, pid_t pid)\n{\n  eos_static_info(\"id=%llu pid=%u\", id, pid);\n  // drop locks for a given inode/pid pair\n  int retc = 0;\n  {\n    XrdSysMutexHelper lock(this);\n\n    if (lockmap.count(id)) {\n      lockmap[id]->removelk(pid);\n      retc = 0;\n    } else {\n      retc = ENOENT;\n    }\n  }\n  purgeLocks();\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nint\nFuseServer::Lock::dropLocks(const std::string& owner)\n{\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"owner=%s\", owner.c_str());\n  }\n\n  // drop locks for a given owner\n  int retc = 0;\n  {\n    XrdSysMutexHelper lock(this);\n\n    for (auto it = lockmap.begin(); it != lockmap.end(); ++it) {\n      it->second->removelk(owner);\n    }\n  }\n  purgeLocks();\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nint\nFuseServer::Lock::lsLocks(const std::string& owner,\n                          std::map<uint64_t, std::set < pid_t >>& rlocks,\n                          std::map<uint64_t, std::set < pid_t >>& wlocks)\n{\n  int retc = 0;\n  {\n    XrdSysMutexHelper lock(this);\n\n    for (auto it = lockmap.begin(); it != lockmap.end(); ++it) {\n      std::set<pid_t> rlk = it->second->getrlks(owner);\n      std::set<pid_t> wlk = it->second->getwlks(owner);\n      rlocks[it->first].insert(rlk.begin(), rlk.end());\n      wlocks[it->first].insert(wlk.begin(), wlk.end());\n    }\n  }\n  return retc;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/FuseServer/Locks.hh",
    "content": "// ----------------------------------------------------------------------\n// File: FuseServer/Locks.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include \"mgm/Namespace.hh\"\n#include <map>\n#include <memory>\n#include \"mgm/fuse-locks/LockTracker.hh\"\n#include <XrdSys/XrdSysPthread.hh>\n\nEOSFUSESERVERNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n//! Class Lock\n//----------------------------------------------------------------------------\n\nclass Lock : XrdSysMutex\n{\npublic:\n\n  Lock() = default;\n\n  virtual ~Lock() = default;\n\n  typedef std::shared_ptr<LockTracker> shared_locktracker;\n\n  typedef std::map<uint64_t, shared_locktracker > lockmap_t;\n\n  shared_locktracker getLocks(uint64_t id);\n\n  void purgeLocks();\n\n  int dropLocks(uint64_t id, pid_t pid);\n\n  int dropLocks(const std::string& owner);\n\n  int lsLocks(const std::string& owner,\n              std::map<uint64_t, std::set<pid_t>>&rlocks,\n              std::map<uint64_t, std::set<pid_t>>&wlocks);\nprivate:\n  lockmap_t lockmap;\n};\n\n\nEOSFUSESERVERNAMESPACE_END\n"
  },
  {
    "path": "mgm/FuseServer/Namespace.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Namespace.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_NAMESPACE_HH__\n#define __EOSMGM_NAMESPACE_HH__\n\n#define USE_EOSMGMNAMESPACE using namespace eos::mgm;\n\n#define EOSMGMNAMESPACE_BEGIN namespace eos { namespace mgm {\n#define EOSMGMNAMESPACE_END }}\n\n#endif\n"
  },
  {
    "path": "mgm/FuseServer/Server.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FuseServer/Server.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/FuseServer/Server.hh\"\n#include \"mgm/misc/Constants.hh\"\n#include \"mgm/acl/Acl.hh\"\n#include \"mgm/policy/Policy.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/recycle/Recycle.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/ofs/XrdMgmOfsFile.hh\"\n#include \"mgm/zmq/ZMQ.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/tracker/ReplicationTracker.hh\"\n#include \"mgm/geotreeengine/GeoTreeEngine.hh\"\n#include \"mgm/workflow/Workflow.hh\"\n#include \"mgm/xattr/XattrLock.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/ContainerIterators.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"namespace/utils/Attributes.hh\"\n#include \"common/Path.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Definitions.hh\"\n#include <google/protobuf/util/json_util.h>\n#include <string>\n#include <cstdlib>\n#include <thread>\n#include \"proto/Audit.pb.h\"\n#include \"namespace/utils/Checksum.hh\"\n#include \"mgm/misc/AuditHelpers.hh\"\n\nUSE_EOSMGMNAMESPACE\n\nEOSFUSESERVERNAMESPACE_BEGIN\n\n#define k_mdino  eos::mgm::SYS_HARD_LINK\n#define k_nlink  eos::mgm::SYS_NUM_LINK\n\nUSE_EOSFUSESERVERNAMESPACE\n\nconst char* Server::cident = \"fxserver\";\n\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\n\nServer::Server()\n{\n  SetLogId(logId, \"fxserver\");\n  c_max_children = getenv(\"EOS_MGM_FUSEX_MAX_CHILDREN\") ? strtoull(\n                     getenv(\"EOS_MGM_FUSEX_MAX_CHILDREN\"), 0, 10) : 131072;\n\n  if (!c_max_children) {\n    c_max_children = 131072;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\n\nServer::~Server()\n{\n  shutdown();\n}\n\n//------------------------------------------------------------------------------\n// Start method\n//------------------------------------------------------------------------------\n\nvoid\nServer::start()\n{\n  eos_static_info(\"msg=\\\"starting fuse server\\\" max-children=%llu\",\n                  c_max_children);\n  std::thread monitorthread(&FuseServer::Clients::MonitorHeartBeat,\n                            &(this->mClients));\n  monitorthread.detach();\n  std::thread capthread(&Server::MonitorCaps, this);\n  capthread.detach();\n}\n\n//------------------------------------------------------------------------------\n// Shutdown method\n//------------------------------------------------------------------------------\n\nvoid\nServer::shutdown()\n{\n  Clients().terminate();\n  terminate();\n}\n\n//------------------------------------------------------------------------------\n// Dump message contents as json string\n//------------------------------------------------------------------------------\n\nstd::string\nServer::dump_message(const google::protobuf::Message& message)\n{\n  google::protobuf::util::JsonPrintOptions options;\n  options.add_whitespace = true;\n#if GOOGLE_PROTOBUF_VERSION >= 5027000\n  options.always_print_fields_with_no_presence = true;\n#else\n  options.always_print_primitive_fields = true;\n#endif\n  std::string jsonstring;\n  (void) google::protobuf::util::MessageToJsonString(message, &jsonstring,\n      options);\n  return jsonstring;\n}\n\n\n//------------------------------------------------------------------------------\n// Expire caps and update quota information\n//------------------------------------------------------------------------------\n\nvoid\nServer::MonitorCaps() noexcept\n{\n  eos_static_info(\"msg=\\\"starting fusex monitor caps thread\\\"\");\n  std::map<FuseServer::Caps::authid_t, time_t> outofquota;\n  uint64_t noquota = std::numeric_limits<long>::max() / 2;\n  size_t cnt = 0;\n\n  while (1) {\n    EXEC_TIMING_BEGIN(\"Eosxd::int::MonitorCaps\");\n\n    // expire caps\n    do {\n      if (Cap().expire()) {\n        Cap().pop();\n      } else {\n        break;\n      }\n    } while (1);\n\n    time_t now = time(NULL);\n\n    if (!(cnt % Clients().QuotaCheckInterval())) {\n      // check quota nodes every mQuotaCheckInterval iterations\n      typedef struct quotainfo {\n\n        quotainfo(uid_t _uid, gid_t _gid, uint64_t _qid) : uid(_uid), gid(_gid),\n          qid(_qid)\n        {\n        }\n\n        quotainfo() : uid(0), gid(0), qid(0)\n        {\n        }\n        uid_t uid;\n        gid_t gid;\n        uint64_t qid;\n        std::vector<std::string> authids;\n\n        std::string id()\n        {\n          char sid[64];\n          snprintf(sid, sizeof(sid), \"%u:%u:%lu\", uid, gid, qid);\n          return sid;\n        }\n      } quotainfo_t;\n      std::map<std::string, quotainfo_t> qmap;\n      {\n        if (EOS_LOGS_DEBUG) {\n          eos_static_debug(\"looping over caps n=%d\", Cap().GetCaps().size());\n        }\n\n        for (auto& it : Cap().GetAllCaps()) {\n          if (EOS_LOGS_DEBUG) {\n            eos_static_debug(\"cap q-node %lx\", (*it)()->_quota().quota_inode());\n          }\n\n          // if we find a cap with 'noquota' contents, we just ignore this one\n          if ((*it)()->_quota().inode_quota() == noquota) {\n            continue;\n          }\n\n          if ((*it)()->_quota().quota_inode()) {\n            quotainfo_t qi((*it)()->uid(), (*it)()->gid(),\n                           (*it)()->_quota().quota_inode());\n\n            // skip if we did this already ...\n            if (qmap.count(qi.id())) {\n              qmap[qi.id()].authids.push_back((*it)()->authid());\n            } else {\n              qmap[qi.id()] = qi;\n              qmap[qi.id()].authids.push_back((*it)()->authid());\n            }\n          }\n        }\n      }\n\n      for (auto it = qmap.begin(); it != qmap.end(); ++it) {\n        eos::IContainerMD::id_t qino_id = it->second.qid;\n\n        if (EOS_LOGS_DEBUG) {\n          eos_static_debug(\"checking qino=%d\", qino_id);\n        }\n\n        long long avail_bytes = 0;\n        long long avail_files = 0;\n\n        if (!Quota::QuotaBySpace(qino_id, it->second.uid, it->second.gid,\n                                 avail_files, avail_bytes)) {\n          for (auto auit = it->second.authids.begin();\n               auit != it->second.authids.end(); ++auit) {\n            if (EOS_LOGS_DEBUG)\n              eos_static_debug(\"checking qino=%d files=%ld bytes=%ld authid=%s\",\n                               qino_id, avail_files, avail_bytes, auit->c_str());\n\n            if (((!avail_files || !avail_bytes) && (!outofquota.count(*auit))) ||\n                // first time out of quota\n                ((avail_files && avail_bytes) &&\n                 (outofquota.count(*auit)))) { // first time back to quota\n              // send the changed quota information via a cap update\n              auto cap = Cap().GetTS(*auit);\n\n              if (cap) {\n                (*cap)()->mutable__quota()->set_inode_quota(avail_files);\n                (*cap)()->mutable__quota()->set_volume_quota(avail_bytes);\n                // send this cap (again)\n                Cap().BroadcastCap(cap);\n              }\n\n              // mark to not send this again unless the quota status changes\n              if (!avail_files || !avail_bytes) {\n                outofquota[*auit] = now;\n              } else {\n                outofquota.erase(*auit);\n              }\n            }\n          }\n        }\n      }\n\n      // expire some old out of quota entries\n      for (auto it = outofquota.begin(); it != outofquota.end();) {\n        if (((it->second) + 3600) < now) {\n          auto erase_it = it++;\n          outofquota.erase(erase_it);\n        } else {\n          it++;\n        }\n      }\n    }\n\n    EXEC_TIMING_END(\"Eosxd::int::MonitorCaps\");\n    std::this_thread::sleep_for(std::chrono::seconds(1));\n\n    if (should_terminate()) {\n      break;\n    }\n\n    cnt++;\n\n    if (gOFS) {\n      gOFS->MgmStats.Add(\"Eosxd::int::MonitorCaps\", 0, 0, 1);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print cleints\n//------------------------------------------------------------------------------\n\nvoid\nServer::Print(std::string& out, std::string options)\n{\n  if (\n    (options.find(\"m\") != std::string::npos) ||\n    (options.find(\"l\") != std::string::npos) ||\n    (options.find(\"k\") != std::string::npos) ||\n    !options.length()) {\n    Client().Print(out, options);\n  }\n\n  if (options.find(\"f\") != std::string::npos) {\n    std::string flushout;\n    gOFS->zMQ->gFuseServer.Flushs().Print(flushout);\n    out += flushout;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Fill container meta-data object\n//------------------------------------------------------------------------------\n\nint\nServer::FillContainerMD(uint64_t id, eos::fusex::md& dir,\n                        eos::common::VirtualIdentity& vid, bool lock)\n{\n  gOFS->MgmStats.Add(\"Eosxd::int::FillContainerMD\", vid.uid, vid.gid, 1);\n  EXEC_TIMING_BEGIN(\"Eosxd::int::FillContainerMD\");\n  std::shared_ptr<eos::IContainerMD> cmd;\n  eos::IContainerMD::ctime_t ctime;\n  eos::IContainerMD::ctime_t mtime;\n  eos::IContainerMD::ctime_t tmtime;\n  uint64_t clock = 0;\n\n  if (EOS_LOGS_DEBUG) {\n    eos_debug(\"container-id=%llx\", id);\n  }\n\n  eos::common::RWMutexReadLock rd_ns_lock;\n\n  if (lock) {\n    rd_ns_lock.Grab(gOFS->eosViewRWMutex);\n  }\n\n  try {\n    cmd = gOFS->eosDirectoryService->getContainerMD(id, &clock);\n    rd_ns_lock.Release();\n    cmd->getCTime(ctime);\n    cmd->getMTime(mtime);\n    cmd->getTMTime(tmtime);\n    std::string fullpath = gOFS->eosView->getUri(cmd.get());\n    dir.set_md_ino(id);\n    dir.set_md_pino(cmd->getParentId());\n    dir.set_ctime(ctime.tv_sec);\n    dir.set_ctime_ns(ctime.tv_nsec);\n    dir.set_mtime(mtime.tv_sec);\n    dir.set_mtime_ns(mtime.tv_nsec);\n    dir.set_ttime(tmtime.tv_sec);\n    dir.set_ttime_ns(tmtime.tv_nsec);\n    dir.set_atime(mtime.tv_sec);\n    dir.set_atime_ns(mtime.tv_nsec);\n    dir.set_size(cmd->getTreeSize());\n    dir.set_uid(cmd->getCUid());\n    dir.set_gid(cmd->getCGid());\n    dir.set_mode(cmd->getMode());\n    // @todo (apeters): no hardlinks\n    dir.set_nlink(2);\n    dir.set_name(cmd->getName());\n    dir.set_fullpath(fullpath);\n    eos::IFileMD::XAttrMap xattrs;\n    gOFS->listAttributes(gOFS->eosView, &(*cmd), xattrs, false);\n\n    for (const auto& elem : xattrs) {\n      if ((elem.first) == \"sys.vtrace\") {\n        continue;\n      }\n\n      if ((elem.first) == \"sys.utrace\") {\n        continue;\n      }\n\n      (*dir.mutable_attr())[elem.first] = elem.second;\n\n      if ((elem.first) == \"sys.eos.btime\") {\n        std::string key, val;\n        eos::common::StringConversion::SplitKeyValue(elem.second, key, val, \".\");\n        dir.set_btime(strtoul(key.c_str(), 0, 10));\n        dir.set_btime_ns(strtoul(val.c_str(), 0, 10));\n      }\n    }\n\n    dir.set_nchildren(cmd->getNumContainers() + cmd->getNumFiles());\n\n    if (dir.operation() == dir.LS) {\n      // we put a hard-coded listing limit for service protection\n      if (vid.app != gOFS->mFuseNoStallApp) {\n        // no restrictions for restic backups\n        if ((uint64_t)dir.nchildren() > c_max_children) {\n          // xrootd does not handle E2BIG ... sigh\n          return ENAMETOOLONG;\n        }\n      }\n\n      for (auto it = eos::FileMapIterator(cmd); it.valid(); it.next()) {\n        std::string key = eos::common::StringConversion::EncodeInvalidUTF8(it.key());\n        (*dir.mutable_children())[key] =\n          eos::common::FileId::FidToInode(it.value());\n      }\n\n      for (auto it = ContainerMapIterator(cmd); it.valid(); it.next()) {\n        std::string key = eos::common::StringConversion::EncodeInvalidUTF8(it.key());\n        (*dir.mutable_children())[key] = it.value();\n      }\n\n      // indicate that this MD record contains children information\n      dir.set_type(dir.MDLS);\n    } else {\n      // indicate that this MD record contains only MD but no children information\n      if (EOS_LOGS_DEBUG) {\n        eos_debug(\"setting md type\");\n      }\n\n      dir.set_type(dir.MD);\n    }\n\n    dir.set_clock(clock);\n    dir.clear_err();\n    EXEC_TIMING_END(\"Eosxd::int::FillContainerMD\");\n    return 0;\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_err(\"msg=\\\"caught exception %d %s\\\"\\n\", e.getErrno(),\n            e.getMessage().str().c_str());\n    dir.set_err(errno);\n    return errno;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Fill file meta-data object\n//------------------------------------------------------------------------------\n\nbool\nServer::FillFileMD(uint64_t inode, eos::fusex::md& file,\n                   eos::common::VirtualIdentity& vid, bool lock)\n{\n  gOFS->MgmStats.Add(\"Eosxd::int::FillFileMD\", vid.uid, vid.gid, 1);\n  EXEC_TIMING_BEGIN(\"Eosxd::int::FillFileMD\");\n  // fills file meta data by inode number\n  std::shared_ptr<eos::IFileMD> fmd, gmd;\n  eos::IFileMD::ctime_t ctime;\n  eos::IFileMD::ctime_t mtime;\n  eos::IFileMD::ctime_t atime;\n  uint64_t clock = 0;\n\n  if (EOS_LOGS_DEBUG) eos_debug(\"file-inode=%llx file-id=%llx\", inode,\n                                  eos::common::FileId::InodeToFid(inode));\n\n  eos::common::RWMutexReadLock rd_ns_lock;\n\n  if (lock) {\n    rd_ns_lock.Grab(gOFS->eosViewRWMutex);\n  }\n\n  try {\n    bool has_mdino = false;\n    fmd = gOFS->eosFileService->getFileMD(eos::common::FileId::InodeToFid(inode),\n                                          &clock);\n    eos_debug(\"clock=%llx\", clock);\n    file.set_name(fmd->getName());\n    gmd = fmd;\n    rd_ns_lock.Release();\n\n    if (fmd->hasAttribute(k_mdino)) {\n      has_mdino = true;\n      uint64_t mdino = std::stoull(fmd->getAttribute(k_mdino));\n      fmd = gOFS->eosFileService->getFileMD(eos::common::FileId::InodeToFid(mdino),\n                                            &clock);\n      eos_debug(\"hlnk switched from %s to file %s (%#llx)\",\n                gmd->getName().c_str(), fmd->getName().c_str(), mdino);\n    }\n\n    /* fmd = link target file, gmd = link file */\n    fmd->getCTime(ctime);\n    fmd->getMTime(mtime);\n    fmd->getATime(atime);\n    file.set_md_ino(eos::common::FileId::FidToInode(gmd->getId()));\n    file.set_md_pino(fmd->getContainerId());\n    file.set_ctime(ctime.tv_sec);\n    file.set_ctime_ns(ctime.tv_nsec);\n    file.set_mtime(mtime.tv_sec);\n    file.set_mtime_ns(mtime.tv_nsec);\n    file.set_btime(ctime.tv_sec);\n    file.set_btime_ns(ctime.tv_nsec);\n    file.set_atime(atime.tv_sec);\n    file.set_atime_ns(atime.tv_nsec);\n    file.set_size(fmd->getSize());\n    file.set_uid(fmd->getCUid());\n    file.set_gid(fmd->getCGid());\n\n    if (fmd->isLink()) {\n      file.set_mode(fmd->getFlags() | S_IFLNK);\n      file.set_target(fmd->getLink());\n    } else {\n      file.set_mode(fmd->getFlags() | S_IFREG);\n    }\n\n    /* hardlinks */\n    int nlink = 1;\n\n    if (fmd->hasAttribute(k_nlink)) {\n      nlink = std::stoi(fmd->getAttribute(k_nlink)) + 1;\n\n      if (EOS_LOGS_DEBUG) {\n        eos_debug(\"hlnk %s (%#lx) nlink %d\", file.name().c_str(), fmd->getId(),\n                  nlink);\n      }\n    }\n\n    file.set_nlink(nlink);\n    file.set_clock(clock);\n    eos::IFileMD::XAttrMap xattrs = fmd->getAttributes();\n\n    for (const auto& elem : xattrs) {\n      if (has_mdino && ((elem.first) == k_nlink)) {\n        continue;\n      }\n\n      if ((elem.first) == \"sys.vtrace\") {\n        continue;\n      }\n\n      if ((elem.first) == \"sys.utrace\") {\n        continue;\n      }\n\n      if ((elem.first) == \"sys.fusex.state\") {\n        continue;\n      }\n\n      if ((elem.first) == \"sys.fs.tracking\") {\n        continue;\n      }\n\n      (*file.mutable_attr())[elem.first] = elem.second;\n\n      if ((elem.first) == \"sys.eos.btime\") {\n        std::string key, val;\n        eos::common::StringConversion::SplitKeyValue(elem.second, key, val, \".\");\n        file.set_btime(strtoul(key.c_str(), 0, 10));\n        file.set_btime_ns(strtoul(val.c_str(), 0, 10));\n      }\n    }\n\n    if (has_mdino) {\n      (*file.mutable_attr())[k_mdino] = std::to_string(\n                                          eos::common::FileId::FidToInode(fmd->getId()));\n    }\n\n    file.clear_err();\n    EXEC_TIMING_END(\"Eosxd::int::FillFileMD\");\n    return true;\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_err(\"msg=\\\"caught exception %d %s\\\"\\n\", e.getErrno(),\n            e.getMessage().str().c_str());\n    file.set_err(errno);\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Fill container capability\n//------------------------------------------------------------------------------\n\nbool\nServer::FillContainerCAP(uint64_t id,\n                         eos::fusex::md& dir,\n                         eos::common::VirtualIdentity& vid,\n                         std::string reuse_uuid,\n                         bool issue_only_one)\n{\n  gOFS->MgmStats.Add(\"Eosxd::int::FillContainerCAP\", vid.uid, vid.gid, 1);\n  EXEC_TIMING_BEGIN(\"Eosxd::int::FillContainerCAP\");\n  Caps::authid_set_t duplicated_caps;\n  eos_info(\"ino=%#lx client=%s only-once=%d\", id, dir.clientid().c_str(),\n           issue_only_one);\n\n  if (issue_only_one) {\n    if (EOS_LOGS_DEBUG) {\n      eos_debug(\"checking for id=%s\", dir.clientid().c_str());\n    }\n\n    // check if the client has already a cap, in case yes, we don't return a new\n    // one\n    if (Cap().HasInodeId(dir.clientid(), id)) {\n      return true;\n    }\n  } else {\n    // avoid to pile-up caps for the same client, delete previous ones\n    auto auth_ids = Cap().GetInodeCapAuthIds(dir.clientid(), id);\n    auth_ids.erase(reuse_uuid);\n    duplicated_caps = std::move(auth_ids);\n  }\n\n  dir.mutable_capability()->set_id(id);\n\n  if (EOS_LOGS_DEBUG) {\n    eos_debug(\"container-id=%#llx vid.sudoer %d dir.uid %u name %s\", id, vid.sudoer,\n              (uid_t) dir.uid(), dir.name().c_str());\n  }\n\n  struct timespec ts;\n\n  eos::common::Timing::GetTimeSpec(ts, true);\n\n  size_t leasetime = 0;\n\n  {\n    if (dir.attr().count(\"sys.force.leasetime\") > 0) {\n      // directory has leasetime overwrite\n      leasetime = strtoul((*(dir.mutable_attr()))[\"sys.forced.leasetime\"].c_str(), 0,\n                          10);\n    }\n\n    eos::common::RWMutexReadLock lLock(gOFS->zMQ->gFuseServer.Client());\n\n    if (!leasetime) {\n      // only use client leasetime if there is no overwrite\n      leasetime = gOFS->zMQ->gFuseServer.Client().leasetime(dir.clientuuid());\n    }\n\n    eos_debug(\"checking client %s leastime=%d\", dir.clientid().c_str(),\n              leasetime);\n  }\n\n  dir.mutable_capability()->set_vtime(ts.tv_sec + (leasetime ? leasetime : 300));\n  dir.mutable_capability()->set_vtime_ns(ts.tv_nsec);\n  std::string sysmask = (*(dir.mutable_attr()))[\"sys.mask\"];\n  long mask = 0777;\n\n  if (sysmask.length()) {\n    mask &= strtol(sysmask.c_str(), 0, 8);\n  }\n\n  mode_t mode = dir.mode() & S_IFDIR;\n\n  // define the permissions\n  if (vid.uid == 0) {\n    // grant all permissions\n    dir.mutable_capability()->set_mode(0xff | mode);\n  } else {\n    if (!vid.token) {\n      if (vid.sudoer) {\n        mode |= C_OK | M_OK | U_OK | W_OK | D_OK | SA_OK | SU_OK\n                ; // chown + chmod permission + all the rest\n      }\n\n      if (vid.uid == (uid_t) dir.uid()) {\n        // we don't apply a mask if we are the owner\n        if (dir.mode() & S_IRUSR) {\n          mode |= R_OK | M_OK | SU_OK;\n        }\n\n        if (dir.mode() & S_IWUSR) {\n          mode |= U_OK | W_OK | D_OK | SA_OK | M_OK | SU_OK;\n        }\n\n        if (dir.mode() & mask & S_IXUSR) {\n          mode |= X_OK;\n        }\n      }\n\n      bool same_group = false;\n\n      if (vid.gid == (gid_t) dir.gid()) {\n        same_group = true;\n      } else {\n        if (eos::common::Mapping::gSecondaryGroups) {\n          if (vid.allowed_gids.count(dir.gid())) {\n            same_group = true;\n          }\n        }\n      }\n\n      if (same_group) {\n        // we apply a mask if we are in the same group\n        if (dir.mode() & mask & S_IRGRP) {\n          mode |= R_OK;\n        }\n\n        if (dir.mode() & mask & S_IWGRP) {\n          mode |= U_OK | W_OK | D_OK | SA_OK | M_OK | SU_OK;\n        }\n\n        if (dir.mode() & mask & S_IXGRP) {\n          mode |= X_OK;\n        }\n      }\n\n      // we apply a mask if we are matching other permissions\n      if (dir.mode() & mask & S_IROTH) {\n        mode |= R_OK;\n      }\n\n      if (dir.mode() & mask & S_IWOTH) {\n        mode |= U_OK | W_OK | D_OK | SA_OK | M_OK | SU_OK;\n      }\n\n      if (dir.mode() & mask & S_IXOTH) {\n        mode |= X_OK;\n      }\n    }\n\n    // look at ACLs\n    std::string sysacl = (*(dir.mutable_attr()))[\"sys.acl\"];\n    std::string useracl = (*(dir.mutable_attr()))[\"user.acl\"];\n\n    if (EOS_LOGS_DEBUG) {\n      eos_debug(\"name='%s' sysacl='%s' useracl='%s' count(sys.eval.useracl)=%d\",\n                dir.name().c_str(), sysacl.c_str(), useracl.c_str(),\n                dir.attr().count(\"sys.eval.useracl\"));\n    }\n\n    if (EOS_LOGS_DEBUG) {\n      std::string tokenDump;\n\n      if (vid.token) {\n        vid.token->Dump(tokenDump, true, false);\n      }\n\n      eos_debug(\"token='%s' scope='%s'\", tokenDump.c_str(), dir.fullpath().c_str());\n    }\n\n    if (sysacl.length() || useracl.length() || vid.token) {\n      bool evaluseracl = (!S_ISDIR(dir.mode())) ||\n                         dir.attr().count(\"sys.eval.useracl\") > 0;\n      vid.scope = dir.fullpath();\n      Acl acl(sysacl,\n              useracl,\n              vid,\n              evaluseracl);\n\n      if (EOS_LOGS_DEBUG)\n        eos_debug(\"cap id=%lld name %s evaluseracl %d CanRead %d CanWrite %d CanChmod %d CanChown %d CanUpdate %d CanNotDelete %d\",\n                  id, dir.name().c_str(), evaluseracl, acl.CanRead(), acl.CanWrite(),\n                  acl.CanChmod(), acl.CanChown(),\n                  acl.CanUpdate(), acl.CanNotDelete());\n\n      if (acl.IsMutable()) {\n        if (acl.CanRead()) {\n          mode |= R_OK;\n        } else if (acl.CanNotRead()) { /* denials override mode bits */\n          mode &= ~R_OK;\n        }\n\n        if (acl.CanWrite() || acl.CanWriteOnce()) {\n          mode |= W_OK | SA_OK | D_OK | M_OK;\n        } else if (acl.CanNotWrite()) { /* denials override mode bits */\n          mode &= ~(W_OK | SA_OK | D_OK | M_OK);\n        }\n\n        if (acl.CanBrowse()) {\n          mode |= X_OK;\n        } else if (acl.CanNotBrowse()) {/* denials override mode bits */\n          mode &= ~X_OK;\n        }\n\n        if (acl.CanNotChmod()) {\n          mode &= ~M_OK;\n        }\n\n        if (acl.CanChmod()) {\n          mode |= M_OK;\n        }\n\n        if (acl.CanChown()) {\n          mode |= C_OK;\n        }\n\n        if (acl.CanUpdate()) {\n          mode |= U_OK | SA_OK;\n        }\n\n        // the owner can always delete\n        if ((vid.uid != (uid_t) dir.uid()) && acl.CanNotDelete()) {\n          mode &= ~D_OK;\n        }\n      } else {\n        // For immutable directories we allow reading and browsing permissions\n        // if these are specified\n        if (acl.CanRead()) {\n          mode |= R_OK;\n        } else if (acl.CanNotRead()) { /* denials override mode bits */\n          mode &= ~R_OK;\n        }\n\n        if (acl.CanBrowse()) {\n          mode |= X_OK;\n        } else if (acl.CanNotBrowse()) {/* denials override mode bits */\n          mode &= ~X_OK;\n        }\n      }\n    }\n\n    if (!gOFS->allow_public_access(dir.fullpath().c_str(), vid)) {\n      eos_debug(\"msg=\\\"no public access\\\" path=\\\"%s\\\"\", dir.fullpath().c_str());\n      mode = dir.mode() & S_IFDIR;\n      mode |= X_OK;\n    }\n\n    dir.mutable_capability()->set_mode(mode);\n  }\n\n  std::string ownerauth = (*(dir.mutable_attr()))[\"sys.owner.auth\"];\n\n  // define new target owner\n  if (ownerauth.length()) {\n    if (ownerauth == \"*\") {\n      // sticky ownership for everybody\n      dir.mutable_capability()->set_uid(dir.uid());\n      dir.mutable_capability()->set_gid(dir.gid());\n    } else {\n      ownerauth += \",\";\n      std::string ownerkey = vid.prot.c_str();\n      std::string prot = vid.prot.c_str();\n      ownerkey += \":\";\n\n      if (prot == \"gsi\") {\n        ownerkey += vid.dn.c_str();\n      } else {\n        ownerkey += vid.uid_string.c_str();\n      }\n\n      if ((ownerauth.find(ownerkey)) != std::string::npos) {\n        // sticky ownership for this authentication\n        dir.mutable_capability()->set_uid(dir.uid());\n        dir.mutable_capability()->set_gid(dir.gid());\n      } else {\n        // no sticky ownership for this authentication\n        dir.mutable_capability()->set_uid(vid.uid);\n        dir.mutable_capability()->set_gid(vid.gid);\n      }\n    }\n  } else {\n    // no sticky ownership\n    dir.mutable_capability()->set_uid(vid.uid);\n    dir.mutable_capability()->set_gid(vid.gid);\n  }\n\n  dir.mutable_capability()->set_authid(reuse_uuid.length() ?\n                                       reuse_uuid : eos::common::StringConversion::random_uuidstring());\n  dir.mutable_capability()->set_clientid(dir.clientid());\n  dir.mutable_capability()->set_clientuuid(dir.clientuuid());\n\n  // max-filesize settings\n  if (dir.attr().count(\"sys.forced.maxsize\")) {\n    // dynamic upper file size limit per file\n    dir.mutable_capability()->set_max_file_size(strtoull((*\n        (dir.mutable_attr()))[\"sys.forced.maxsize\"].c_str(), 0, 10));\n  } else {\n    // hard-coded upper file size limit per file\n    dir.mutable_capability()->set_max_file_size(512ll * 1024ll * 1024ll *\n        1024ll); // 512 GB\n  }\n\n  std::string space = \"default\";\n  {\n    // add quota information\n    if (dir.attr().count(\"sys.forced.space\")) {\n      space = (*(dir.mutable_attr()))[\"sys.forced.space\"];\n    } else {\n      if (dir.attr().count(\"user.forced.space\")) {\n        space = (*(dir.mutable_attr()))[\"user.forced.space\"];\n      }\n    }\n\n    // Check if quota is enabled for the current space\n    bool has_quota = false;\n    long long avail_bytes = 0;\n    long long avail_files = 0;\n    eos::IContainerMD::id_t quota_inode = 0;\n\n    if (eos::mgm::FsView::gFsView.IsQuotaEnabled(space)) {\n      if (!Quota::QuotaByPath(dir.fullpath().c_str(), dir.capability().uid(),\n                              dir.capability().gid(), avail_files, avail_bytes,\n                              quota_inode)) {\n        has_quota = true;\n      }\n    } else {\n      avail_files = std::numeric_limits<long>::max() / 2;\n      avail_bytes = std::numeric_limits<long>::max() / 2;\n      has_quota = true;\n    }\n\n    dir.mutable_capability()->mutable__quota()->set_inode_quota(avail_files);\n    dir.mutable_capability()->mutable__quota()->set_volume_quota(avail_bytes);\n    dir.mutable_capability()->mutable__quota()->set_quota_inode(quota_inode);\n\n    if (!has_quota) {\n      dir.mutable_capability()->mutable__quota()->clear_inode_quota();\n      dir.mutable_capability()->mutable__quota()->clear_volume_quota();\n      dir.mutable_capability()->mutable__quota()->clear_quota_inode();\n    }\n  }\n  EXEC_TIMING_END(\"Eosxd::int::FillContainerCAP\");\n  Cap().Store(dir.capability(), &vid);\n\n  if (duplicated_caps.size()) {\n    for (auto it = duplicated_caps.begin(); it != duplicated_caps.end(); ++it) {\n      eos_static_debug(\"removing duplicated cap %s\\n\", it->c_str());\n      Caps::shared_cap cap = Cap().GetTS(*it);\n      Cap().RemoveTS(cap);\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Validate access persmissions based on a given capability\n//------------------------------------------------------------------------------\n\nFuseServer::Caps::shared_cap\nServer::ValidateCAP(const eos::fusex::md& md, mode_t mode,\n                    eos::common::VirtualIdentity& vid)\n{\n  errno = 0;\n  FuseServer::Caps::shared_cap cap = Cap().GetTS(md.authid());\n\n  // no cap - go away\n  if (!(*cap)()->id()) {\n    eos_static_err(\"msg=\\\"no cap found\\\" authid=%s\", md.authid().c_str());\n    errno = ENOENT;\n    return 0;\n  }\n\n  // wrong cap - go away\n  if (((*cap)()->id() != md.md_ino()) && ((*cap)()->id() != md.md_pino())) {\n    eos_static_err(\"msg=\\\"wrong cap\\\" authid=%s md-ino=%lx md-pino=%lx\",\n                   md.authid().c_str(), md.md_ino(), md.md_pino());\n    errno = EINVAL;\n    return 0;\n  }\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"cap-mode=%x mode=%x\", (*cap)()->mode(), mode);\n  }\n\n  if (((*cap)()->mode() & mode) == mode) {\n    uint64_t now = (uint64_t) time(NULL);\n\n    // leave some margin for revoking\n    if ((*cap)()->vtime() <= (now + 60)) {\n      // cap expired !\n      errno = ETIMEDOUT;\n      return 0;\n    }\n\n    return cap;\n  }\n\n  errno = EPERM;\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Extract inode from capability\n//------------------------------------------------------------------------------\n\nuint64_t\nServer::InodeFromCAP(const eos::fusex::md& md)\n{\n  FuseServer::Caps::shared_cap cap = Cap().GetTS(md.authid());\n\n  // no cap - go away\n  if (!cap) {\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"no cap for authid=%s\", md.authid().c_str());\n    }\n\n    return 0;\n  } else {\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"authid=%s cap-ino=%lx\", md.authid().c_str(), (*cap)()->id());\n    }\n  }\n\n  return (*cap)()->id();\n}\n\n//------------------------------------------------------------------------------\n// Create a response header string\n//------------------------------------------------------------------------------\n\nstd::string\nServer::Header(const std::string& response)\n{\n  char hex[9];\n  sprintf(hex, \"%08x\", (int) response.length());\n  return std::string(\"[\") + hex + std::string(\"]\");\n}\n\n//------------------------------------------------------------------------------\n// Validate permissions froa given meta-data object\n//------------------------------------------------------------------------------\n\nbool\nServer::ValidatePERM(const eos::fusex::md& md, const std::string& mode,\n                     eos::common::VirtualIdentity& vid,\n                     bool take_lock)\n{\n  gOFS->MgmStats.Add(\"Eosxd::int::ValidatePERM\", vid.uid, vid.gid, 1);\n  EXEC_TIMING_BEGIN(\"Eosxd::int::ValidatePERM\");\n  // -------------------------------------------------------------------------------------------------------------\n  // - when an MGM was restarted it does not know anymore any client CAPs, but we can fallback to validate\n  //   permissions on the fly again\n  // -------------------------------------------------------------------------------------------------------------\n  eos_info(\"mode=%s\", mode.c_str());\n  std::string path;\n  std::shared_ptr<eos::IContainerMD> cmd;\n  uint64_t clock = 0;\n  bool r_ok = false;\n  bool w_ok = false;\n  bool x_ok = false;\n  bool d_ok = false;\n  eos::common::RWMutexReadLock rd_ns_lock;\n\n  if (take_lock) {\n    rd_ns_lock.Grab(gOFS->eosViewRWMutex);\n  }\n\n  try {\n    cmd = gOFS->eosDirectoryService->getContainerMD(md.md_pino(), &clock);\n    path = gOFS->eosView->getUri(cmd.get());\n    // for performance reasons we implement a seperate access control check here, because\n    // we want to avoid another id=path translation and unlock lock of the namespace\n    eos::IContainerMD::XAttrMap attrmap;\n    gOFS->listAttributes(gOFS->eosView, &(*cmd), attrmap, false);\n    std::set<gid_t> gids;\n\n    if (eos::common::Mapping::gSecondaryGroups) {\n      gids = vid.allowed_gids;\n    } else {\n      gids.insert(vid.gid);\n    }\n\n    if (!vid.token) {\n      for (auto g : gids) {\n        if (cmd->access(vid.uid, g, R_OK)) {\n          r_ok = true;\n        }\n\n        if (cmd->access(vid.uid, g, W_OK)) {\n          w_ok = true;\n          d_ok = true;\n        }\n\n        if (cmd->access(vid.uid, g, X_OK)) {\n          x_ok = true;\n        }\n      }\n    }\n\n    vid.scope = path;\n    // ACL and permission check\n    Acl acl(attrmap, vid);\n    eos_debug(\"acl=%d r=%d w=%d wo=%d x=%d egroup=%d mutable=%d\",\n              acl.HasAcl(), acl.CanRead(), acl.CanWrite(), acl.CanWriteOnce(),\n              acl.HasAcl(), acl.CanRead(), acl.CanWrite(), acl.CanWriteOnce(),\n              acl.CanBrowse(), acl.HasEgroup(), acl.IsMutable());\n\n    // browse permission by ACL\n    if (acl.HasAcl()) {\n      if (acl.CanWrite()) {\n        w_ok = true;\n        d_ok = true;\n      }\n\n      // write-once excludes updates, also denials\n      if (acl.CanNotWrite() || acl.CanWriteOnce()) {\n        w_ok = false;\n      }\n\n      // the owner can always delete, otherwise it might be forbidden by ACLs\n      if ((vid.uid != (uid_t) cmd->getCUid()) && acl.CanNotDelete()) {\n        d_ok = false;\n      }\n\n      // the r/x are added to the posix permissions already set\n      if (acl.CanRead()) {\n        r_ok |= true;\n      }\n\n      if (acl.CanBrowse()) {\n        x_ok |= true;\n      }\n\n      if (!acl.IsMutable()) {\n        w_ok = d_ok = false;\n      }\n    }\n  } catch (eos::MDException& e) {\n    eos_err(\"msg=\\\"failed to get directory inode\\\" ino=%lx\", md.md_pino());\n    return false;\n  }\n\n  std::string accperm;\n  accperm = \"R\";\n\n  if (r_ok) {\n    accperm += \"R\";\n  }\n\n  if (w_ok) {\n    accperm += \"WCKNV\";\n  }\n\n  if (d_ok) {\n    accperm += \"D\";\n  }\n\n  EXEC_TIMING_END(\"Eosxd::int::ValidatePERM\");\n\n  if (accperm.find(mode) != std::string::npos) {\n    eos_info(\"msg=\\\"allowed access to inode\\\" ino=%lx request-mode=%s granted-mode=%s\",\n             md.md_pino(),\n             mode.c_str(),\n             accperm.c_str()\n            );\n    return true;\n  } else {\n    eos_err(\"msg=\\\"rejected access to inode\\\" ino=%lx request-mode=%s granted-mode=%s\",\n            md.md_pino(),\n            mode.c_str(),\n            accperm.c_str()\n           );\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Prefetch meta-data according to request type\n//------------------------------------------------------------------------------\n\nvoid\nServer::prefetchMD(const eos::fusex::md& md)\n{\n  if (md.operation() == md.GET) {\n    Prefetcher::prefetchInodeAndWait(gOFS->eosView, md.md_ino());\n  } else if (md.operation() == md.LS) {\n    Prefetcher::prefetchInodeWithChildrenAndWait(gOFS->eosView, md.md_ino());\n  } else if (md.operation() == md.DELETE) {\n    Prefetcher::prefetchInodeWithChildrenAndWait(gOFS->eosView, md.md_pino());\n\n    if (S_ISDIR(md.mode())) {\n      Prefetcher::prefetchInodeWithChildrenAndWait(gOFS->eosView, md.md_ino());\n    }\n  } else if (md.operation() == md.GETCAP) {\n    Prefetcher::prefetchInodeAndWait(gOFS->eosView, md.md_ino());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Mark beginning of a flush operation\n//------------------------------------------------------------------------------\n\nint\nServer::OpBeginFlush(const std::string& id,\n                     const eos::fusex::md& md,\n                     eos::common::VirtualIdentity& vid,\n                     std::string* response,\n                     uint64_t* clock)\n{\n  gOFS->MgmStats.Add(\"Eosxd::ext::BEGINFLUSH\", vid.uid, vid.gid, 1, vid.app);\n  EXEC_TIMING_BEGIN(\"Eosxd::ext::BEGINFLUSH\");\n  // this is a flush begin/end indicator\n  Flushs().beginFlush(md.md_ino(), md.clientuuid());\n  eos::fusex::response resp;\n  resp.set_type(resp.NONE);\n  resp.SerializeToString(response);\n  EXEC_TIMING_END(\"Eosxd::ext::BEGINFLUSH\");\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Mark end of a flush operation\n//---------------------------------------------------------------------------------*/\n\nint\nServer::OpEndFlush(const std::string& id,\n                   const eos::fusex::md& md,\n                   eos::common::VirtualIdentity& vid,\n                   std::string* response,\n                   uint64_t* clock)\n{\n  gOFS->MgmStats.Add(\"Eosxd::ext::ENDFLUSH\", vid.uid, vid.gid, 1, vid.app);\n  EXEC_TIMING_BEGIN(\"Eosxd::ext::ENDFLUSH\");\n  Flushs().endFlush(md.md_ino(), md.clientuuid());\n  eos::fusex::response resp;\n  resp.set_type(resp.NONE);\n  resp.SerializeToString(response);\n  EXEC_TIMING_END(\"Eosxd::ext::ENDFLUSH\");\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Server a meta-data GET or LS operation\n//------------------------------------------------------------------------------\n\nint\nServer::OpGetLs(const std::string& id,\n                const eos::fusex::md& md,\n                eos::common::VirtualIdentity& vid,\n                std::string* response,\n                uint64_t* clock)\n{\n  if (clock) {\n    *clock = 0;\n  }\n\n  eos::fusex::container cont;\n\n  if (!eos::common::FileId::IsFileInode(md.md_ino())) {\n    eos_debug(\"ino=%lx msg=\\\"get-dir\\\"\", (long) md.md_ino());\n    cont.set_type(cont.MDMAP);\n    cont.set_ref_inode_(md.md_ino());\n    eos::fusex::md_map* mdmap = cont.mutable_md_map_();\n    // create the parent entry;\n    auto parent = mdmap->mutable_md_map_();\n    (*parent)[md.md_ino()].set_md_ino(md.md_ino());\n    (*parent)[md.md_ino()].set_clientuuid(md.clientuuid());\n    (*parent)[md.md_ino()].set_clientid(md.clientid());\n    EXEC_TIMING_BEGIN((md.operation() == md.LS) ? \"Eosxd::ext::LS\" :\n                      \"Eosxd::ext::GET\");\n\n    if (md.operation() == md.LS) {\n      gOFS->MgmStats.Add(\"Eosxd::ext::LS\", vid.uid, vid.gid, 1, vid.app);\n      (*parent)[md.md_ino()].set_operation(md.LS);\n    } else {\n      gOFS->MgmStats.Add(\"Eosxd::ext::GET\", vid.uid, vid.gid, 1, vid.app);\n    }\n\n    size_t n_attached = 1;\n    int retc = 0;\n\n    // retrieve directory meta data\n    if (!(retc = FillContainerMD(md.md_ino(), (*parent)[md.md_ino()], vid))) {\n      // refresh the cap with the same authid\n      FillContainerCAP(md.md_ino(), (*parent)[md.md_ino()], vid,\n                       md.authid());\n\n      // store clock\n      if (clock) {\n        *clock = (*parent)[md.md_ino()].clock();\n      }\n\n      if (md.operation() == md.LS) {\n        // attach children\n        auto map = (*parent)[md.md_ino()].children();\n        auto it = map.begin();\n        size_t n_caps = 0;\n        gOFS->MgmStats.Add(\"Eosxd::ext::LS-Entry\", vid.uid, vid.gid, map.size(),\n                           vid.app);\n\n        for (; it != map.end(); ++it) {\n          // this is a map by inode\n          (*parent)[it->second].set_md_ino(it->second);\n          auto child_md = &((*parent)[it->second]);\n\n          if (eos::common::FileId::IsFileInode(it->second)) {\n            // this is a file\n            FillFileMD(it->second, *child_md, vid);\n          } else {\n            // we don't fill the LS information for the children, just the MD\n            child_md->set_operation(md.GET);\n            child_md->set_clientuuid(md.clientuuid());\n            child_md->set_clientid(md.clientid());\n            FillContainerMD(it->second, *child_md, vid);\n\n            if (n_caps < 16) {\n              // skip hidden directories\n              if (it->first.substr(0, 1) == \".\") {\n                // add maximum 16 caps for a listing\n                FillContainerCAP(it->second, *child_md, vid, \"\", true);\n                n_caps++;\n              }\n            }\n\n            child_md->clear_operation();\n          }\n        }\n\n        n_attached++;\n\n        if (n_attached >= 128) {\n          std::string rspstream;\n          cont.SerializeToString(&rspstream);\n\n          if (!response) {\n            // send parent + first 128 children\n            gOFS->zMQ->mTask->reply(id, rspstream);\n          } else {\n            *response += Header(rspstream);\n            response->append(rspstream.c_str(), rspstream.size());\n          }\n\n          n_attached = 0;\n          cont.Clear();\n        }\n      }\n\n      if (EOS_LOGS_DEBUG) {\n        std::string mdout = dump_message(*mdmap);\n        eos_debug(\"\\n%s\\n\", mdout.c_str());\n      }\n    } else {\n      eos_err(\"msg=\\\"failed to retrieve directory meta data\\\" ino=%lx errc=%d\",\n              (long) md.md_ino(),\n              retc);\n      return retc;\n    }\n\n    (*parent)[md.md_ino()].clear_operation();\n\n    if (n_attached) {\n      // send left-over children\n      std::string rspstream;\n      cont.SerializeToString(&rspstream);\n\n      if (!response) {\n        gOFS->zMQ->mTask->reply(id, rspstream);\n      } else {\n        *response += Header(rspstream);\n        response->append(rspstream.c_str(), rspstream.size());\n      }\n    }\n\n    EXEC_TIMING_END((md.operation() == md.LS) ? \"Eosxd::ext::LS\" :\n                    \"Eosxd::ext::GET\");\n  } else {\n    EXEC_TIMING_BEGIN(\"Eosxd::ext::GET\");\n    eos_debug(\"ino=%lx msg=\\\"get-file/link\\\"\", (long) md.md_ino());\n    cont.set_type(cont.MD);\n    cont.set_ref_inode_(md.md_ino());\n    (*cont.mutable_md_()).set_clientuuid(md.clientuuid());\n    (*cont.mutable_md_()).set_clientid(md.clientid());\n    Prefetcher::prefetchInodeAndWait(gOFS->eosView, md.md_ino());\n    FillFileMD(md.md_ino(), (*cont.mutable_md_()), vid);\n\n    if (md.attr().count(\"user.acl\") > 0) { /* File has its own ACL */\n      if (EOS_LOGS_DEBUG) {\n        google::protobuf::util::JsonPrintOptions options;\n        options.add_whitespace = true;\n#if GOOGLE_PROTOBUF_VERSION >= 5027000\n        options.always_print_fields_with_no_presence = true;\n#else\n        options.always_print_primitive_fields = true;\n#endif\n        std::string jsonstring;\n        (void) google::protobuf::util::MessageToJsonString(cont, &jsonstring, options);\n        eos_static_debug(\"MD GET file-cap ino %#x %s\", md.md_ino(), jsonstring.c_str());\n      }\n\n      FillContainerCAP(md.md_ino(), (*cont.mutable_md_()), vid, md.authid());\n\n      if (EOS_LOGS_DEBUG)\n        eos_info(\"msg=\\\"file-cap issued\\\" id=%lx mode=%x vtime=%lu.%lu uid=%u gid=%u \"\n                 \"client-id=%s auth-id=%s errc=%d\", cont.cap_().id(), cont.cap_().mode(),\n                 cont.cap_().vtime(),\n                 cont.cap_().vtime_ns(), cont.cap_().uid(), cont.cap_().gid(),\n                 cont.cap_().clientid().c_str(), cont.cap_().authid().c_str(),\n                 cont.cap_().errc());\n    }\n\n    std::string rspstream;\n    cont.SerializeToString(&rspstream);\n\n    // store clock\n    if (clock) {\n      *clock = cont.md_().clock();\n    }\n\n    if (!response) {\n      // send file meta data\n      gOFS->zMQ->mTask->reply(id, rspstream);\n    } else {\n      *response += Header(rspstream);\n      response->append(rspstream.c_str(), rspstream.size());\n    }\n\n    EXEC_TIMING_END(\"Eosxd::ext::GET\");\n  }\n\n  return 0;\n}\n\n\n\n//------------------------------------------------------------------------------\n// Server a meta-data SET operation\n//------------------------------------------------------------------------------\n\n/*----------------------------------------------------------------------------*/\n\nint\nServer::OpSet(const std::string& id,\n              const eos::fusex::md& md,\n              eos::common::VirtualIdentity& vid,\n              std::string* response,\n              uint64_t* clock)\n{\n  gOFS->MgmStats.Add(\"Eosxd::ext::SET\", vid.uid, vid.gid, 1, vid.app);\n\n  if (!ValidateCAP(md, W_OK | SA_OK, vid)) {\n    std::string perm = \"W\";\n\n    // a CAP might have gone or timed out, let's check again the permissions\n    if (((errno == ENOENT) ||\n         (errno == EINVAL) ||\n         (errno == ETIMEDOUT)) &&\n        ValidatePERM(md, perm, vid)) {\n      // this can pass on ... permissions are fine\n    } else {\n      return EPERM;\n    }\n  }\n\n  if (S_ISDIR(md.mode())) {\n    return OpSetDirectory(id, md, vid, response, clock);\n  } else if (S_ISREG(md.mode()) || S_ISFIFO(md.mode())) {\n    return OpSetFile(id, md, vid, response, clock);\n  } else if (S_ISLNK(md.mode())) {\n    return OpSetLink(id, md, vid, response, clock);\n  }\n\n  return EINVAL;\n}\n\n\n\n//------------------------------------------------------------------------------\n// Server a meta-data SET operation\n//------------------------------------------------------------------------------\n\n/*----------------------------------------------------------------------------*/\n\nint\nServer::OpSetDirectory(const std::string& id,\n                       const eos::fusex::md& md,\n                       eos::common::VirtualIdentity& vid,\n                       std::string* response,\n                       uint64_t* clock)\n{\n  gOFS->MgmStats.Add(\"Eosxd::ext::SETDIR\", vid.uid, vid.gid, 1, vid.app);\n  EXEC_TIMING_BEGIN(\"Eosxd::ext::SETDIR\");\n  uint64_t md_pino = md.md_pino();\n\n  if (!md_pino) {\n    // -----------------------------------------------------------------------\n    // this can be a creation with an implied capability and the remote inode\n    // of the parent directory\n    // was not yet send back to the creating client\n    // -----------------------------------------------------------------------\n    md_pino = InodeFromCAP(md);\n  }\n\n  enum set_type {\n    CREATE, UPDATE, RENAME, MOVE\n  };\n  set_type op;\n  uint64_t md_ino = 0;\n  bool exclusive = false;\n\n  if (md.type() == md.EXCL) {\n    exclusive = true;\n  }\n\n  eos_info(\"ino=%lx pin=%lx authid=%s msg=\\\"set-dir\\\"\", (long) md.md_ino(),\n           (long) md.md_pino(),\n           md.authid().c_str());\n  std::shared_ptr<eos::IContainerMD> cmd;\n  std::shared_ptr<eos::IContainerMD> pcmd;\n  std::shared_ptr<eos::IContainerMD> cpcmd;\n  eos::IContainerMD::XAttrMap attrmap;\n  eos::fusex::md mv_md;\n  mode_t sgid_mode = 0;\n  eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n  std::string oldname;\n\n  try {\n    if (md.md_ino() && exclusive) {\n      eos_err(\"msg=\\\"exclusive inode already exists\\\" ino=%lx\", (long) md.md_ino());\n      return EEXIST;\n    }\n\n    if (md.md_ino()) {\n      if (md.implied_authid().length()) {\n        // this is a create on top of an existing inode\n        eos_err(\"msg=\\\"inode exists\\\" ino=%lx implied=%s\", (long) md.md_ino(),\n                md.implied_authid().c_str());\n        return EEXIST;\n      }\n\n      op = UPDATE;\n      // dir update\n      cmd = gOFS->eosDirectoryService->getContainerMD(md.md_ino());\n      pcmd = gOFS->eosDirectoryService->getContainerMD(md.md_pino());\n\n      if (!cmd && md.md_ino()) {\n        // directory existed but has been deleted\n        throw_mdexception(ENOENT, \"No such directory : \" << md.md_ino());\n      }\n\n      oldname = cmd->getName();\n\n      if (cmd->getParentId() != md.md_pino()) {\n        // this indicates a directory move\n        {\n          // we have to check that we have write permission on the source parent\n          eos::fusex::md source_md;\n          source_md.set_md_pino(cmd->getParentId());\n          source_md.set_mode(S_IFDIR);\n          std::string perm = \"W\";\n\n          if (!ValidatePERM(source_md, perm, vid, false)) {\n            eos_err(\"msg=\\\"no write permission on source directory to do mv\\\" source-ino=%lx ino=%lx\",\n                    cmd->getParentId(),\n                    md.md_ino());\n            return EPERM;\n          }\n        }\n        op = MOVE;\n        // create a broadcast md object with the authid of the source directory,\n        // the target is the standard authid for notification\n        mv_md.set_authid(md.mv_authid());\n        // If the destination exists, we have to remove it if it's empty\n        std::shared_ptr<eos::IContainerMD> exist_target_cmd = pcmd->findContainer(\n              md.name());\n        int64_t tree_size = static_cast<int64_t>(cmd->getTreeSize());\n        int64_t tree_cont = static_cast<int64_t>(cmd->getTreeContainers());\n        int64_t tree_files = static_cast<int64_t>(cmd->getTreeFiles());\n\n        if (exist_target_cmd) {\n          if (exist_target_cmd->getNumFiles() + exist_target_cmd->getNumContainers()) {\n            // Fatal error we have to fail that rename\n            eos_err(\"msg=\\\"failed move, destination exists and not empty\\\"\"\n                    \" name=%s cxid=%08llx\", md.name().c_str(), md.md_ino());\n            return ENOTEMPTY;\n          }\n\n          try {\n            // Remove it via the directory service\n            eos_info(\"msg=\\\"mv delete empty destination\\\" name=%s cxid=%08llx\",\n                     md.name().c_str(), md.md_ino());\n            pcmd->removeContainer(md.name());\n            gOFS->eosDirectoryService->removeContainer(exist_target_cmd.get());\n          }  catch (eos::MDException& e) {\n            eos_crit(\"msg=\\\"got an exception while trying to remove a container\"\n                     \" which we saw before\\\" name=%s cxid=%08llx\",\n                     md.name().c_str(), md.md_ino());\n          }\n        }\n\n        eos_info(\"msg=\\\"mv detach source from parent\\\" moving %lx => %lx\",\n                 cmd->getParentId(), md.md_pino());\n        cpcmd = gOFS->eosDirectoryService->getContainerMD(cmd->getParentId());\n        cpcmd->removeContainer(cmd->getName());\n\n        if (gOFS->eosContainerAccounting) {\n          gOFS->eosContainerAccounting->RemoveTree(cpcmd.get(), {tree_size, tree_files, tree_cont});\n        }\n\n        gOFS->eosView->updateContainerStore(cpcmd.get());\n        cmd->setName(md.name());\n        pcmd->addContainer(cmd.get());\n\n        if (gOFS->eosContainerAccounting) {\n          gOFS->eosContainerAccounting->AddTree(pcmd.get(), {tree_size, tree_files, tree_cont});\n        }\n\n        gOFS->eosView->updateContainerStore(pcmd.get());\n      }\n\n    if (cmd->getName() != md.name()) {\n        // this indicates a directory rename\n        op = RENAME;\n        eos_info(\"msg=\\\"rename\\\" from=\\\"%s\\\" to=\\\"%s\\\"\", cmd->getName().c_str(),\n                 md.name().c_str());\n        gOFS->eosView->renameContainer(cmd.get(), md.name());\n      }\n\n      if (cmd->getCUid() != (uid_t)md.uid() /* a chown */ && !vid.sudoer &&\n          (uid_t)md.uid() != vid.uid) {\n        vid.scope = gOFS->eosView->getUri(cmd.get());\n        /* chown is under control of container sys.acl only, if a vanilla user chowns to other than themselves */\n        Acl acl;\n        gOFS->listAttributes(gOFS->eosView, &(*cmd), attrmap, false);\n\n        if (EOS_LOGS_DEBUG) {\n          eos_debug(\"sysacl '%s' useracl '%s' evaluseracl %d (ignored)\",\n                    attrmap[\"sys.acl\"].c_str(), attrmap[\"user.acl\"].c_str(),\n                    attrmap.count(\"sys.eval.useracl\"));\n        }\n\n        acl.SetFromAttrMap(attrmap, vid, NULL, true /* sysacl-only */);\n\n        if (!acl.CanChown()) {\n          return EPERM;\n        }\n      }\n\n      if (pcmd->getMode() & S_ISGID) {\n        sgid_mode = S_ISGID;\n      }\n\n      md_ino = md.md_ino();\n      eos_info(\"ino=%lx pino=%lx cpino=%lx msg=\\\"update-dir\\\"\",\n               (long) md.md_ino(),\n               (long) md.md_pino(), (long) cmd->getParentId());\n    } else {\n      // dir creation\n      op = CREATE;\n      pcmd = gOFS->eosDirectoryService->getContainerMD(md.md_pino());\n\n      if (md.name().substr(0, strlen(EOS_COMMON_PATH_ATOMIC_FILE_PREFIX)) ==\n          EOS_COMMON_PATH_ATOMIC_FILE_PREFIX) {\n        eos_err(\"ino=%lx name=%s msg=\\\"atomic path is forbidden as a directory name\\\"\",\n                md.md_pino(), md.name().c_str());\n        return EPERM;\n      }\n\n      if (exclusive && pcmd->findContainer(md.name())) {\n        // O_EXCL set on creation -\n        eos_err(\"ino=%lx name=%s msg=\\\"name already exists\\\"\", md.md_pino(),\n                md.name().c_str());\n        return EEXIST;\n      }\n\n      attrmap = pcmd->getAttributes();\n      // test to verify this is the culprit of failing all eosxd system tests in the CI\n      // if ( (md.attr().find(\"user.acl\") != md.attr().end()) && (xattrs.find(\"sys.eval.useracl\") == xattrs.end()) ) {\n      // return EPERM;\n      // }\n      cmd = gOFS->eosDirectoryService->createContainer(0);\n      cmd->setName(md.name());\n      md_ino = cmd->getId();\n      pcmd->addContainer(cmd.get());\n      eos_info(\"ino=%lx pino=%lx md-ino=%lx msg=\\\"create-dir\\\"\",\n               (long) md.md_ino(),\n               (long) md.md_pino(),\n               md_ino);\n\n      if (!Cap().Imply(md_ino, md.authid(), md.implied_authid())) {\n        eos_err(\"msg=\\\"imply failed for new inode\\\" ino=%lx\", md_ino);\n      }\n\n      // parent attribute inheritance\n\n      for (const auto& elem : attrmap) {\n        cmd->setAttribute(elem.first, elem.second);\n      }\n\n      sgid_mode = S_ISGID;\n    }\n\n    cmd->setName(md.name());\n    cmd->setCUid(md.uid());\n    cmd->setCGid(md.gid());\n    // @todo (apeters): is sgid_mode still needed?\n    cmd->setMode(md.mode() | sgid_mode);\n    eos::IContainerMD::ctime_t ctime;\n    eos::IContainerMD::ctime_t mtime;\n    eos::IContainerMD::ctime_t pmtime;\n    ctime.tv_sec = md.ctime();\n    ctime.tv_nsec = md.ctime_ns();\n    mtime.tv_sec = md.mtime();\n    mtime.tv_nsec = md.mtime_ns();\n    pmtime.tv_sec = mtime.tv_sec;\n    pmtime.tv_nsec = mtime.tv_nsec;\n    cmd->setCTime(ctime);\n    cmd->setMTime(mtime);\n    // propagate mtime changes\n    cmd->notifyMTimeChange(gOFS->eosDirectoryService);\n\n    // Snapshot old attributes for xattr audit on UPDATE\n    eos::IContainerMD::XAttrMap oldDirAttrs;\n    if (op != CREATE) {\n      oldDirAttrs = cmd->getAttributes();\n    }\n\n    for (auto it = md.attr().begin(); it != md.attr().end(); ++it) {\n      if ((it->first.substr(0, 3) != \"sys\") ||\n          (it->first == \"sys.eos.btime\")) {\n        cmd->setAttribute(it->first, it->second);\n      }\n    }\n\n    size_t numAttr = cmd->numAttributes();\n\n    if (op != CREATE &&\n        numAttr != md.attr().size()) { /* an attribute got removed */\n      eos::IContainerMD::XAttrMap cmap = cmd->getAttributes();\n\n      for (auto it = cmap.begin(); it != cmap.end(); ++it) {\n        if (md.attr().find(it->first) == md.attr().end()) {\n          eos_debug(\"attr %s=%s has been removed\", it->first.c_str(),\n                    it->second.c_str());\n          cmd->removeAttribute(it->first);\n          /* if ((--numAttr) == md.attr().size()) break;   would be possible - under a lock! */\n        }\n      }\n    }\n\n    if (op == CREATE) {\n      // store the birth time as an extended attribute\n      char btime[256];\n      snprintf(btime, sizeof(btime), \"%lu.%lu\", md.btime(), md.btime_ns());\n      cmd->setAttribute(\"sys.eos.btime\", btime);\n      cmd->setAttribute(\"sys.vtrace\", vid.getTrace());\n    }\n\n    if (op != UPDATE && md.pmtime()) {\n      // store the new modification time for the parent\n      pmtime.tv_sec = md.pmtime();\n      pmtime.tv_nsec = md.pmtime_ns();\n      pcmd->setMTime(pmtime);\n      gOFS->eosDirectoryService->updateStore(pcmd.get());\n      pcmd->notifyMTimeChange(gOFS->eosDirectoryService);\n    }\n\n    gOFS->eosDirectoryService->updateStore(cmd.get());\n    // release the namespace lock before serialization/broadcasting\n    lock.Release();\n    // Audit xattr changes on directories (UPDATE only)\n    if (gOFS->mAudit && op == UPDATE) {\n      std::string path = gOFS->eosView->getUri(cmd.get());\n      if (!path.empty() && path.back() != '/') path.push_back('/');\n      // SET_XATTR for added/changed attrs\n      for (const auto& kv : md.attr()) {\n        const std::string& an = kv.first;\n        if (an.size() >= 3 && an.substr(0, 3) == \"sys\" && an != \"sys.eos.btime\") continue;\n        auto oit = oldDirAttrs.find(an);\n        std::string before = (oit == oldDirAttrs.end()) ? std::string() : oit->second;\n        if (oit == oldDirAttrs.end() || oit->second != kv.second) {\n          if (gOFS->mAudit && gOFS->AllowAuditModification(path)) gOFS->mAudit->audit(eos::audit::SET_XATTR, path, vid, std::string(logId), std::string(cident), \"mgm\",\n                              std::string(), nullptr, nullptr, an, before, kv.second, __FILE__, __LINE__, VERSION);\n          if ((an == \"sys.acl\" || an == \"user.acl\") && gOFS->mAudit && gOFS->AllowAuditModification(path)) {\n            gOFS->mAudit->audit(eos::audit::SET_ACL, path, vid, std::string(logId), std::string(cident), \"mgm\",\n                                std::string(), nullptr, nullptr, an, before, kv.second, __FILE__, __LINE__, VERSION);\n          }\n        }\n      }\n      // RM_XATTR for removed attrs\n      for (const auto& okv : oldDirAttrs) {\n        const std::string& an = okv.first;\n        if (an.size() >= 3 && an.substr(0, 3) == \"sys\") continue;\n        if (md.attr().find(an) == md.attr().end()) {\n          if (gOFS->mAudit && gOFS->AllowAuditModification(path)) gOFS->mAudit->audit(eos::audit::RM_XATTR, path, vid, std::string(logId), std::string(cident), \"mgm\",\n                              std::string(), nullptr, nullptr, an, okv.second, std::string(), __FILE__, __LINE__, VERSION);\n        }\n      }\n    }\n    // Audit directory creation\n    if (gOFS->mAudit) {\n      std::string path = gOFS->eosView->getUri(cmd.get());\n      if (!path.empty() && path.back() != '/') path.push_back('/');\n      eos::audit::Stat afterStat;\n      eos::mgm::auditutil::buildStatFromContainerMD(cmd, afterStat, /*includeNs=*/true);\n      if (gOFS->mAudit && gOFS->AllowAuditModification(path)) gOFS->mAudit->audit(eos::audit::MKDIR, path, vid, std::string(logId), std::string(cident), \"mgm\",\n                          std::string(), nullptr, &afterStat, std::string(), std::string(), std::string(), __FILE__, __LINE__, VERSION);\n    }\n    eos::fusex::response resp;\n    resp.set_type(resp.ACK);\n    resp.mutable_ack_()->set_code(resp.ack_().OK);\n    resp.mutable_ack_()->set_transactionid(md.reqid());\n    resp.mutable_ack_()->set_md_ino(md_ino);\n    resp.SerializeToString(response);\n    uint64_t clock = cmd->getClock();\n\n    switch (op) {\n    case MOVE:\n      gOFS->MgmStats.Add(\"Eosxd::ext::MV\", vid.uid, vid.gid, 1, vid.app);\n      break;\n\n    case UPDATE:\n      gOFS->MgmStats.Add(\"Eosxd::ext::UPDATE\", vid.uid, vid.gid, 1, vid.app);\n      break;\n\n    case CREATE:\n      gOFS->MgmStats.Add(\"Eosxd::ext::MKDIR\", vid.uid, vid.gid, 1, vid.app);\n      break;\n\n    case RENAME:\n      gOFS->MgmStats.Add(\"Eosxd::ext::RENAME\", vid.uid, vid.gid, 1, vid.app);\n      break;\n    }\n\n    // broadcast this update around\n    switch (op) {\n    case CREATE:\n      Cap().BroadcastMD(md, md_ino, md.md_pino(), clock, pmtime);\n      break;\n\n    case MOVE:\n      Cap().BroadcastDeletion(cpcmd->getId(), mv_md, oldname, pmtime);\n      Cap().BroadcastRefresh(pcmd->getId(), md, pcmd->getParentId(), true);\n      Cap().BroadcastMD(md, md_ino, md.md_pino(), clock, pmtime);\n      break;\n\n    case RENAME:\n      Cap().BroadcastDeletion(pcmd->getId(), md, oldname, pmtime);\n      Cap().BroadcastRefresh(pcmd->getId(), md, pcmd->getParentId(), true);\n      Cap().BroadcastMD(md, md_ino, md.md_pino(), clock, pmtime);\n      break;\n\n    case UPDATE:\n      Cap().BroadcastRefresh(pcmd->getId(), md, pcmd->getParentId(), true);\n      Cap().BroadcastMD(md, md_ino, md.md_pino(), clock, pmtime);\n      break;\n    }\n  } catch (eos::MDException& e) {\n    eos_err(\"ino=%lx err-no=%d msg=\\\"%s\\\"\", (long) md.md_ino(),\n            e.getErrno(), e.getMessage().str().c_str());\n    eos::fusex::response resp;\n    resp.set_type(resp.ACK);\n    resp.mutable_ack_()->set_code(resp.ack_().PERMANENT_FAILURE);\n    resp.mutable_ack_()->set_err_no(e.getErrno());\n    resp.mutable_ack_()->set_err_msg(e.getMessage().str().c_str());\n    resp.mutable_ack_()->set_transactionid(md.reqid());\n    resp.SerializeToString(response);\n  }\n\n  EXEC_TIMING_END(\"Eosxd::ext::SETDIR\");\n  return 0;\n}\n\n\n//------------------------------------------------------------------------------\n// Server a meta-data SET operation\n//------------------------------------------------------------------------------\n\n/*----------------------------------------------------------------------------*/\n\nint\nServer::OpSetFile(const std::string& id,\n                  const eos::fusex::md& md,\n                  eos::common::VirtualIdentity& vid,\n                  std::string* response,\n                  uint64_t* clock)\n{\n  gOFS->MgmStats.Add(\"Eosxd::ext::SETFILE\", vid.uid, vid.gid, 1, vid.app);\n  EXEC_TIMING_BEGIN(\"Eosxd::ext::SETFILE\");\n  enum set_type {\n    CREATE, UPDATE, RENAME, MOVE\n  };\n  set_type op;\n  uint64_t md_ino = 0;\n  uint64_t fileId = 0;\n  Workflow workflow;\n  bool exclusive = false;\n\n  if (md.type() == md.EXCL) {\n    exclusive = true;\n  }\n\n  eos_info(\"ino=%lx pin=%lx authid=%s msg=\\\"file\\\"\", (long) md.md_ino(),\n           (long) md.md_pino(),\n           md.authid().c_str());\n  // prefetch parent and file\n  Prefetcher::prefetchInodeAndWait(gOFS->eosView, md.md_pino());\n  Prefetcher::prefetchInodeAndWait(gOFS->eosView, md.md_ino());\n  eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n  eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n  std::shared_ptr<eos::IFileMD> fmd;\n  std::shared_ptr<eos::IFileMD> ofmd;\n  std::shared_ptr<eos::IContainerMD> pcmd;\n  eos::IContainerMD::XAttrMap attrmap;\n  std::shared_ptr<eos::IContainerMD> cpcmd;\n  uint64_t fid = eos::common::FileId::InodeToFid(md.md_ino());\n  md_ino = md.md_ino();\n  uint64_t md_pino = md.md_pino();\n  bool recycleOrVersioned = false;\n  std::string oldname;\n  eos::fusex::md mv_md;\n\n  try {\n    uint64_t clock = 0;\n    pcmd = gOFS->eosDirectoryService->getContainerMD(md.md_pino());\n    // For UPDATE auditing across the function\n    eos::audit::Stat beforeStat;\n    uint32_t oldMode = 0;\n    uid_t oldUid = 0;\n    gid_t oldGid = 0;\n\n    if (md_ino && exclusive) {\n      return EEXIST;\n    }\n\n    if (md_ino) {\n      fs_rd_lock.Release();\n      // file update\n      op = UPDATE;\n      // dir update\n      fmd = gOFS->eosFileService->getFileMD(fid);\n\n      if (!fmd && md_ino) {\n        // file existed but has been deleted\n        throw_mdexception(ENOENT, \"No such file : \" << md_ino);\n      }\n\n      if (EOS_LOGS_DEBUG) eos_debug(\"updating %s => %s \",\n                                      fmd->getName().c_str(),\n                                      md.name().c_str());\n\n      // Capture before stat\n      {\n        eos::mgm::auditutil::buildStatFromFileMD(fmd, beforeStat, /*includeSize=*/true, /*includeChecksum=*/true, /*includeNs=*/true);\n        oldMode = (fmd->getFlags() & 07777);\n        oldUid = fmd->getCUid();\n        oldGid = fmd->getCGid();\n      }\n\n      if (fmd->getContainerId() != md.md_pino()) {\n        recycleOrVersioned = CheckRecycleBinOrVersion(fmd);\n      }\n\n      if (!recycleOrVersioned) {\n        if (fmd->getContainerId() != md.md_pino()) {\n          // this indicates a file move\n          op = MOVE;\n          mv_md.set_authid(md.mv_authid());\n          oldname = fmd->getName();\n          bool hasVersion = false;\n\n          if (EOS_LOGS_DEBUG) {\n            eos_debug(\"moving %lx => %lx\", fmd->getContainerId(), md.md_pino());\n          }\n\n          eos::common::Path oPath(gOFS->eosView->getUri(fmd.get()).c_str());\n          std::string vdir = EOS_COMMON_PATH_VERSION_FILE_PREFIX;\n          vdir += oPath.GetName();\n          cpcmd = gOFS->eosDirectoryService->getContainerMD(fmd->getContainerId());\n\n          if (cpcmd->findContainer(vdir)) {\n            eos_static_debug(\"%s has version\", vdir.c_str());\n            hasVersion = true;\n          }\n\n          // store the current filename\n          std::string removed_name = fmd->getName();\n          // before moving a file in the hierarchy, we first create necessary version on the target in case this\n          // overwrites an existing file and then we do the hierarchy change\n          fmd->setName(md.name());\n          ofmd = pcmd->findFile(md.name());\n\n          if (ofmd) {\n            // the target might exist, so we remove it\n            if (EOS_LOGS_DEBUG) {\n              eos_debug(\"removing previous file in move %s\", md.name().c_str());\n            }\n\n            attrmap = pcmd->getAttributes();\n            // check if there is versioning to be done\n            int versioning = 0;\n\n            if (attrmap.count(\"user.fusex.rename.version\")) {\n              if (attrmap.count(\"sys.versioning\")) {\n                versioning = std::stoi(attrmap[\"sys.versioning\"]);\n              } else {\n                if (attrmap.count(\"user.versioning\")) {\n                  versioning = std::stoi(attrmap[\"user.versioning\"]);\n                }\n              }\n            }\n\n            bool try_recycle = true;\n            bool created_version = false;\n\n            // create a version before replacing\n            if (versioning && !hasVersion) {\n              XrdOucErrInfo error;\n              lock.Release();\n              eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n\n              if (gOFS->Version(ofmd->getId(), error, rootvid, versioning)) {\n                try_recycle = true;\n              } else {\n                try_recycle = false;\n                created_version = true;\n              }\n\n              lock.Grab(gOFS->eosViewRWMutex);\n            } else {\n              // recycle bin - not for hardlinked files or hardlinks!\n              if ((try_recycle &&\n                   (attrmap.count(Recycle::gRecyclingAttribute) || hasVersion)) ||\n                  ofmd->hasAttribute(k_mdino) || ofmd->hasAttribute(k_nlink)) {\n                // translate to a path name and call the complex deletion function\n                // this is vulnerable to a hard to trigger race conditions\n                std::string fullpath = gOFS->eosView->getUri(ofmd.get());\n                gOFS->WriteRecycleRecord(ofmd);\n                lock.Release();\n                XrdOucErrInfo error;\n                (void) gOFS->_rem(fullpath.c_str(), error, vid, \"\",\n                                  false, false, false, true, false);\n                lock.Grab(gOFS->eosViewRWMutex);\n              } else {\n                if (!created_version) {\n                  // no recycle bin, no version\n                  try {\n                    XrdOucErrInfo error;\n\n                    if (XrdMgmOfsFile::create_cow(XrdMgmOfsFile::cowDelete, pcmd, ofmd, vid,\n                                                  error) == -1) {\n                      pcmd->removeFile(md.name());\n                      // unlink the existing file\n                      ofmd->setContainerId(0);\n                      ofmd->unlinkAllLocations();\n                    }\n\n                    eos::IQuotaNode* quotanode = gOFS->eosView->getQuotaNode(pcmd.get());\n\n                    // free previous quota\n                    if (quotanode) {\n                      quotanode->removeFile(ofmd.get());\n                    }\n\n                    gOFS->eosFileService->updateStore(ofmd.get());\n                  } catch (eos::MDException& e) {\n                  }\n                }\n              }\n            }\n          }\n\n          // add file to new directory\n          eos::IContainerMD::id_t old_cid = fmd->getContainerId();\n          pcmd->addFile(fmd.get());\n          // remove file from previous directory\n          cpcmd->removeFile(removed_name);\n          cpcmd = gOFS->eosDirectoryService->getContainerMD(old_cid);\n          // persist updates\n          gOFS->eosView->updateContainerStore(cpcmd.get());\n          gOFS->eosView->updateFileStore(fmd.get());\n          gOFS->eosView->updateContainerStore(pcmd.get());\n\n          // Audit rename/move\n          if (gOFS->mAudit) {\n            std::string newPath = gOFS->eosView->getUri(fmd.get());\n            eos::audit::Stat afterStat;\n            eos::mgm::auditutil::buildStatFromFileMD(fmd, afterStat, /*includeSize=*/true, /*includeChecksum=*/true, /*includeNs=*/true);\n            if (gOFS->mAudit && gOFS->AllowAuditModification(newPath)) gOFS->mAudit->audit(eos::audit::RENAME, newPath, vid, std::string(logId), std::string(cident), \"mgm\",\n                                  oldname, nullptr, nullptr, std::string(), std::string(), std::string(), __FILE__, __LINE__, VERSION);\n          }\n\n          if (hasVersion) {\n            eos::common::Path nPath(gOFS->eosView->getUri(fmd.get()).c_str());\n            lock.Release();\n            XrdOucErrInfo error;\n\n            if (gOFS->_rename(oPath.GetVersionDirectory(), nPath.GetVersionDirectory(),\n                              error, vid, \"\", \"\", false, false, false)) {\n              eos_err(\"msg=\\\"failed to rename version directory\\\" oldpath='%s' newpath='%s'\",\n                      oPath.GetVersionDirectory(), nPath.GetVersionDirectory());\n            }\n\n            lock.Grab(gOFS->eosViewRWMutex);\n          }\n        } else {\n          if (fmd->getName() != md.name()) {\n            // this indicates a file rename\n            op = RENAME;\n            oldname = fmd->getName();\n            bool hasVersion = false;\n            ofmd = pcmd->findFile(md.name());\n\n            if (EOS_LOGS_DEBUG) eos_debug(\"rename %s [%lx] => %s [%lx]\",\n                                            fmd->getName().c_str(), fid,\n                                            md.name().c_str(),\n                                            ofmd ? ofmd->getId() : 0);\n\n            eos::common::Path oPath(gOFS->eosView->getUri(fmd.get()).c_str());\n            std::string vdir = EOS_COMMON_PATH_VERSION_FILE_PREFIX;\n            vdir += oPath.GetName();\n\n            if (pcmd->findContainer(vdir)) {\n              hasVersion = true;\n            }\n\n            if (EOS_LOGS_DEBUG) {\n              eos_debug(\"v=%s version=%d exists=%d\", vdir.c_str(), hasVersion, ofmd ? 1 : 0);\n            }\n\n            if (ofmd) {\n              // the target might exist, so we remove it\n              if (EOS_LOGS_DEBUG) {\n                eos_debug(\"removing previous file in update %s\", md.name().c_str());\n              }\n\n              attrmap = pcmd->getAttributes();\n              // check if there is versioning to be done\n              int versioning = 0;\n\n              if (attrmap.count(\"user.fusex.rename.version\")) {\n                if (attrmap.count(\"sys.versioning\")) {\n                  versioning = std::stoi(attrmap[\"sys.versioning\"]);\n                } else {\n                  if (attrmap.count(\"user.versioning\")) {\n                    versioning = std::stoi(attrmap[\"user.versioning\"]);\n                  }\n                }\n              }\n\n              if ((ofmd->hasAttribute(k_mdino) || (ofmd->hasAttribute(k_nlink)))) {\n                // disable versioning when hardlink are involved\n                versioning = 0;\n              }\n\n              bool try_recycle = true;\n              bool created_version = false;\n\n              // create a version before replacing\n              if (versioning && !hasVersion) {\n                XrdOucErrInfo error;\n                lock.Release();\n                eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n\n                if (gOFS->Version(ofmd->getId(), error, rootvid, versioning)) {\n                  try_recycle = true;\n                } else {\n                  try_recycle = false;\n                  created_version = true;\n                }\n\n                if (EOS_LOGS_DEBUG) {\n                  eos_debug(\"tried versioning - try_recycle=%d\", try_recycle);\n                }\n\n                lock.Grab(gOFS->eosViewRWMutex);\n              }\n\n              // recycle bin - not for hardlinked files or hardlinks !\n              if ((try_recycle &&\n                   (attrmap.count(Recycle::gRecyclingAttribute) || hasVersion)) &&\n                  (!ofmd->hasAttribute(k_mdino) && (!ofmd->hasAttribute(k_nlink)))) {\n                // translate to a path name and call the complex deletion function\n                // this is vulnerable to a hard to trigger race conditions\n                std::string fullpath = gOFS->eosView->getUri(ofmd.get());\n                gOFS->WriteRecycleRecord(ofmd);\n                lock.Release();\n                XrdOucErrInfo error;\n                (void) gOFS->_rem(fullpath.c_str(), error, vid, \"\", false, false,\n                                  false, true, false);\n                lock.Grab(gOFS->eosViewRWMutex);\n              } else {\n                bool doDelete = true;\n                uint64_t tgt_md_ino;\n\n                if (!created_version) {\n                  if (ofmd->hasAttribute(k_mdino)) {\n                    /* this is a hard link, decrease reference count on underlying file */\n                    tgt_md_ino = std::stoull(ofmd->getAttribute(k_mdino));\n                    uint64_t clock;\n                    /* gmd = the file holding the inode */\n                    std::shared_ptr<eos::IFileMD> gmd = gOFS->eosFileService->getFileMD(\n                                                          eos::common::FileId::InodeToFid(tgt_md_ino), &clock);\n                    long nlink = std::stol(gmd->getAttribute(k_nlink)) - 1;\n\n                    if (nlink) {\n                      gmd->setAttribute(k_nlink, std::to_string(nlink));\n                    } else {\n                      gmd->removeAttribute(k_nlink);\n                    }\n\n                    gOFS->eosFileService->updateStore(gmd.get());\n                    eos_info(\"hlnk nlink update on %s for %s now %ld\",\n                             gmd->getName().c_str(), ofmd->getName().c_str(), nlink);\n\n                    if (nlink <= 0) {\n                      if (gmd->getName().substr(0, 13) == \"...eos.ino...\") {\n                        eos_info(\"hlnk unlink target %s for %s nlink %ld\",\n                                 gmd->getName().c_str(), ofmd->getName().c_str(), nlink);\n                        XrdOucErrInfo error;\n\n                        if (XrdMgmOfsFile::create_cow(XrdMgmOfsFile::cowDelete, pcmd, gmd, vid,\n                                                      error) == -1) {\n                          pcmd->removeFile(gmd->getName());\n                          gmd->unlinkAllLocations();\n                          gmd->setContainerId(0);\n                        }\n\n                        gOFS->eosFileService->updateStore(gmd.get());\n                      }\n                    }\n                  } else if (ofmd->hasAttribute(k_nlink)) {\n                    /* this is a genuine file, potentially with hard links */\n                    tgt_md_ino = eos::common::FileId::FidToInode(ofmd->getId());\n                    long nlink = std::stol(ofmd->getAttribute(k_nlink));\n\n                    if (nlink > 0) {\n                      // hard links exist, just rename the file so the inode does not disappear\n                      char nameBuf[256];\n                      snprintf(nameBuf, sizeof(nameBuf), \"...eos.ino...%lx\", tgt_md_ino);\n                      std::string tmpName = nameBuf;\n                      ofmd->setAttribute(k_nlink, std::to_string(nlink));\n                      eos_info(\"hlnk unlink rename %s=>%s new nlink %d\",\n                               ofmd->getName().c_str(), tmpName.c_str(), nlink);\n                      pcmd->removeFile(tmpName);            // if the target exists, remove it!\n                      gOFS->eosView->renameFile(ofmd.get(), tmpName);\n                      doDelete = false;\n                    } else {\n                      eos_info(\"hlnk nlink %ld for %s, will be deleted\",\n                               nlink, fmd->getName().c_str());\n                    }\n                  }\n\n                  if (doDelete) {\n                    // if the file has not been used to keep a hardlink alive, wipe it\n                    try {\n                      XrdOucErrInfo error;\n\n                      if (XrdMgmOfsFile::create_cow(XrdMgmOfsFile::cowDelete, pcmd, ofmd, vid,\n                                                    error) == -1) {\n                        pcmd->removeFile(md.name());\n                        // unlink the existing file\n                        ofmd->setContainerId(0);\n                        ofmd->unlinkAllLocations();\n                      }\n\n                      eos::IQuotaNode* quotanode = gOFS->eosView->getQuotaNode(pcmd.get());\n\n                      // free previous quota\n                      if (quotanode) {\n                        quotanode->removeFile(ofmd.get());\n                      }\n\n                      gOFS->eosFileService->updateStore(ofmd.get());\n                    } catch (eos::MDException& e) {\n                    }\n                  }\n                }\n              }\n            }\n\n            gOFS->eosView->renameFile(fmd.get(), md.name());\n\n            if (hasVersion) {\n              eos::common::Path nPath(gOFS->eosView->getUri(fmd.get()).c_str());\n              lock.Release();\n              XrdOucErrInfo error;\n\n              if (gOFS->_rename(oPath.GetVersionDirectory(), nPath.GetVersionDirectory(),\n                                error, vid, \"\", \"\", false, false, false)) {\n                eos_err(\"msg=\\\"failed to rename version directory\\\" oldpath='%s' newpath='%s'\\n\",\n                        oPath.GetVersionDirectory(), nPath.GetVersionDirectory());\n              }\n\n              lock.Grab(gOFS->eosViewRWMutex);\n            }\n          }\n        }\n      }\n\n      if (EOS_LOGS_DEBUG) {\n        eos_debug(\"vid.sudoer %d vid.uid %u md.uid() %u fmd->getCUid() %u\", vid.sudoer,\n                  vid.uid, (uid_t)md.uid(), fmd->getCUid());\n      }\n\n      if (fmd->getCUid() != (uid_t)md.uid() /* a chown */ && !vid.sudoer &&\n          (uid_t)md.uid() != vid.uid) {\n        vid.scope = gOFS->eosView->getUri(pcmd.get());\n        /* chown is under control of container sys.acl only, if a vanilla user chowns to other than themselves */\n        Acl acl;\n        attrmap = pcmd->getAttributes();\n\n        if (EOS_LOGS_DEBUG) {\n          eos_debug(\"sysacl '%s' useracl '%s' (ignored) evaluseracl %d\",\n                    attrmap[\"sys.acl\"].c_str(), attrmap[\"user.acl\"].c_str(),\n                    attrmap.count(\"sys.eval.useracl\"));\n        }\n\n        acl.SetFromAttrMap(attrmap, vid, NULL, true /* sysacl-only */);\n\n        if (!acl.CanChown()) {\n          return EPERM;\n        }\n      }\n\n      eos_info(\"fxid=%08llx ino=%lx pino=%lx cpino=%lx update-file\",\n               (long) fid,\n               (long) md.md_ino(),\n               (long) md.md_pino(), (long) fmd->getContainerId());\n    } else if (strncmp(md.target().c_str(), \"////hlnk\",\n                       8) == 0) {  /* hard link creation */\n      fs_rd_lock.Release();\n      uint64_t tgt_md_ino = atoll(md.target().c_str() + 8);\n\n      if (pcmd->findContainer(md.name())) {\n        return EEXIST;\n      }\n\n      /* fmd is the target file corresponding to tgt_fid, gmd the file corresponding to new name */\n      fmd = gOFS->eosFileService->getFileMD(eos::common::FileId::InodeToFid(\n                                              tgt_md_ino));\n      std::shared_ptr<eos::IFileMD> gmd = gOFS->eosFileService->createFile(0);\n      int nlink;\n      nlink = (fmd->hasAttribute(k_nlink)) ? std::stoi(fmd->getAttribute(\n                k_nlink)) + 1 : 1;\n\n      if (EOS_LOGS_DEBUG) {\n        eos_debug(\"hlnk fxid=%08llx target name %s nlink %d create hard link %s\",\n                  (long)fid, fmd->getName().c_str(), nlink, md.name().c_str());\n      }\n\n      fmd->setAttribute(k_nlink, std::to_string(nlink));\n      gOFS->eosFileService->updateStore(fmd.get());\n      gmd->setAttribute(k_mdino, std::to_string(tgt_md_ino));\n      gmd->setName(md.name());\n\n      if (EOS_LOGS_DEBUG)\n        eos_debug(\"hlnk %s mdino %s %s nlink %s\",\n                  gmd->getName().c_str(), gmd->getAttribute(k_mdino).c_str(),\n                  fmd->getName().c_str(), fmd->getAttribute(k_nlink).c_str());\n\n      pcmd->addFile(gmd.get());\n      gOFS->eosFileService->updateStore(gmd.get());\n            gOFS->eosView->updateContainerStore(pcmd.get());\n            // Audit rename\n            if (gOFS->mAudit) {\n              std::string newPath = gOFS->eosView->getUri(fmd.get());\n              gOFS->mAudit->audit(eos::audit::RENAME, newPath, vid, logId, cident, \"mgm\",\n                                  oldname);\n            }\n      eos::fusex::response resp;\n      resp.set_type(resp.ACK);\n      resp.mutable_ack_()->set_code(resp.ack_().OK);\n      resp.mutable_ack_()->set_transactionid(md.reqid());\n      resp.mutable_ack_()->set_md_ino(eos::common::FileId::FidToInode(gmd->getId()));\n      // prepare to broadcast the new hardlink around, need to create an md object with the hardlink\n      eos::fusex::md g_md;\n      uint64_t g_ino = eos::common::FileId::FidToInode(gmd->getId());\n      lock.Release();\n      Prefetcher::prefetchInodeAndWait(gOFS->eosView, g_ino);\n      FillFileMD(g_ino, g_md, vid);\n      // release the namespace lock before serialization/broadcasting\n      resp.SerializeToString(response);\n      struct timespec pt_mtime;\n      pt_mtime.tv_sec = md.mtime();\n      pt_mtime.tv_nsec = md.mtime_ns();\n      gOFS->eosDirectoryService->updateStore(pcmd.get());\n      uint64_t clock = 0;\n      Cap().BroadcastMD(md, tgt_md_ino, md_pino, clock, pt_mtime);\n      Cap().BroadcastMD(g_md, g_ino, md_pino, clock, pt_mtime);\n      return 0;\n    } else {\n      // file creation\n      op = CREATE;\n\n      if (md.name().substr(0, strlen(EOS_COMMON_PATH_ATOMIC_FILE_PREFIX)) ==\n          EOS_COMMON_PATH_ATOMIC_FILE_PREFIX) {\n        eos_err(\"msg=\\\"atomic path is forbidden as a filename\\\" name=%s\",\n                md.name().c_str());\n        return EPERM;\n      }\n\n      if (pcmd->findContainer(\n            md.name())) {\n        return EEXIST;\n      }\n\n      unsigned long layoutId = 0;\n      unsigned long forcedFsId = 0;\n      long forcedGroup = 0;\n      std::string space;\n      gOFS->listAttributes(gOFS->eosView, &(*pcmd), attrmap, false);\n      XrdOucEnv env;\n      std::string bandwidth;\n      bool schedule = false;\n      std::string iopriority;\n      std::string iotype;\n      // retrieve the layout\n      Policy::GetLayoutAndSpace(\"fusex\", attrmap, vid, layoutId, space, env,\n                                forcedFsId, forcedGroup, bandwidth, schedule, iopriority, iotype, false);\n      fs_rd_lock.Release();\n\n      if (eos::mgm::FsView::gFsView.IsQuotaEnabled(space.c_str())) {\n        // check inode quota here\n        long long avail_bytes = 0;\n        long long avail_files = 0;\n\n        try {\n          eos::IQuotaNode* quotanode = gOFS->eosView->getQuotaNode(pcmd.get());\n\n          if (quotanode) {\n            if (!Quota::QuotaBySpace(quotanode->getId(),\n                                     vid.uid,\n                                     vid.gid,\n                                     avail_files,\n                                     avail_bytes)) {\n              if (!avail_files) {\n                eos_err(\"msg=\\\"out-of-inode-quota\\\" uid=%u gid=%u name=%s\",\n                        vid.uid,\n                        vid.gid,\n                        md.name().c_str());\n                return EDQUOT;\n              }\n\n              if ((uint64_t)avail_bytes < gOFS->getFuseBookingSize()) {\n                eos_err(\"msg=\\\"out-of-volume-quota (<%llu bytes)\\\" uid=%u gid=%u name=%s\",\n                        gOFS->getFuseBookingSize(),\n                        vid.uid,\n                        vid.gid,\n                        md.name().c_str());\n                return EDQUOT;\n              }\n            }\n          }\n        } catch (eos::MDException& e) {\n        }\n      }\n\n      {\n        std::string nogroup;\n        uint64_t phys_space = gOFS->mGeoTreeEngine->placementSpace(space.c_str(),\n                              nogroup);\n        eos_info(\"msg=\\\"writable-space=%lu\\\"\", phys_space);\n\n        // check physical space\n        if (phys_space < gOFS->getFuseBookingSize()) {\n          eos_err(\"msg=\\\"instance out of placement space\\\" sched.capacity=%ld\",\n                  phys_space);\n          return ENOSPC;\n        }\n      }\n\n      fmd = gOFS->eosFileService->createFile(0);\n      fmd->setName(md.name());\n      fmd->setLayoutId(layoutId);\n      fileId = fmd->getId();\n      md_ino = eos::common::FileId::FidToInode(fmd->getId());\n      pcmd->addFile(fmd.get());\n      eos_info(\"ino=%lx pino=%lx md-ino=%lx create-file\", (long) md_ino,\n               (long) md.md_pino(), md_ino);\n      // store the birth time as an extended attribute\n      char btime[256];\n      snprintf(btime, sizeof(btime), \"%lu.%lu\", md.btime(), md.btime_ns());\n      fmd->setAttribute(\"sys.eos.btime\", btime);\n      fmd->setAttribute(\"sys.vtrace\", vid.getTrace());\n    }\n\n    if (!recycleOrVersioned) {\n      fmd->setName(md.name());\n    }\n\n    std::string s;\n\n    try {\n      s = fmd->getAttribute(\"sys.fusex.state\");\n    } catch (...) {}\n\n    if ((fmd->getSize() > 0) && (!md.size())) {\n      // this is a truncation\n      s += \"T\";\n    }\n\n    switch (op) {\n    case MOVE:\n      s += \"M\";\n      break;\n\n    case UPDATE:\n      s += \"U\";\n      break;\n\n    case CREATE:\n      s += \"C\";\n      break;\n\n    case RENAME:\n      s += \"R\";\n      break;\n\n    default:\n      s += \"0\";\n      break;\n    }\n\n    if (fmd->getSize() != md.size()) {\n      // this is a size change\n      s += \"±\";\n    }\n\n    fmd->setAttribute(\"sys.fusex.state\",\n                      eos::common::StringConversion::ReduceString(s).c_str());\n    fmd->setCUid(md.uid());\n    fmd->setCGid(md.gid());\n    {\n      try {\n        eos::IQuotaNode* quotanode = gOFS->eosView->getQuotaNode(pcmd.get());\n\n        // free previous quota\n        if (quotanode) {\n          if (op != CREATE) {\n            quotanode->removeFile(fmd.get());\n          }\n\n          fmd->setSize(md.size());\n          quotanode->addFile(fmd.get());\n        } else {\n          fmd->setSize(md.size());\n        }\n      } catch (eos::MDException& e) {\n        fmd->setSize(md.size());\n      }\n    }\n    // for the moment we store 9 bits here\n    fmd->setFlags(md.mode() & (S_IRWXU | S_IRWXG | S_IRWXO));\n    eos::IFileMD::ctime_t ctime;\n    eos::IFileMD::ctime_t mtime;\n    eos::IFileMD::ctime_t atime;\n    ctime.tv_sec = md.ctime();\n    ctime.tv_nsec = md.ctime_ns();\n    mtime.tv_sec = md.mtime();\n    mtime.tv_nsec = md.mtime_ns();\n    atime.tv_sec = md.atime();\n    atime.tv_nsec = md.atime_ns();\n    fmd->setCTime(ctime);\n    fmd->setMTime(mtime);\n    fmd->setATime(atime);\n    // Snapshot old file attributes for xattr audit on UPDATE\n    eos::IFileMD::XAttrMap oldFileAttrs = fmd->getAttributes();\n    replaceNonSysAttributes(fmd, md);\n    struct timespec pt_mtime;\n\n    if (op != UPDATE) {\n      // update the mtime\n      if (op == RENAME) {\n        pcmd->setMTime(ctime);\n        pt_mtime.tv_sec = ctime.tv_sec;\n        pt_mtime.tv_nsec = ctime.tv_nsec;\n      } else {\n        pcmd->setMTime(mtime);\n        pt_mtime.tv_sec = mtime.tv_sec;\n        pt_mtime.tv_nsec = mtime.tv_nsec;\n      }\n    } else {\n      pt_mtime.tv_sec = pt_mtime.tv_nsec = 0;\n    }\n\n    gOFS->eosFileService->updateStore(fmd.get());\n\n    // Emit CREATE/UPDATE audit for files\n    if (gOFS->mAudit) {\n      std::string filePath = gOFS->eosView->getUri(fmd.get());\n      eos::audit::Stat afterStat;\n      eos::mgm::auditutil::buildStatFromFileMD(fmd, afterStat, /*includeSize=*/true, /*includeChecksum=*/true, /*includeNs=*/true);\n      bool allowAuditMod = false;\n      if (gOFS->mEnvAuditAttributeOnly && pcmd) {\n        eos::IContainerMD::XAttrMap pAttrs = pcmd->getAttributes();\n        auto it = pAttrs.find(\"sys.audit\");\n        std::string mode = (it != pAttrs.end()) ? it->second : std::string();\n        allowAuditMod = gOFS->AllowAuditModificationAttr(mode);\n      } else {\n        allowAuditMod = gOFS->AllowAuditModification(filePath);\n      }\n      if (gOFS->mAudit && allowAuditMod) gOFS->mAudit->audit(eos::audit::CREATE, filePath, vid, std::string(logId), std::string(cident), \"mgm\",\n                             std::string(), nullptr, &afterStat, std::string(), std::string(), std::string(), __FILE__, __LINE__, VERSION);\n      if (op == UPDATE) {\n        if (gOFS->mAudit && allowAuditMod) gOFS->mAudit->audit(eos::audit::UPDATE, filePath, vid, std::string(logId), std::string(cident), \"mgm\",\n                             std::string(), &beforeStat, &afterStat, std::string(), std::string(), std::string(), __FILE__, __LINE__, VERSION);\n\n        // Emit CHMOD/CHOWN if mode/owner changed\n        uint32_t newMode = (fmd->getFlags() & 07777);\n        if (newMode != oldMode) {\n          if (gOFS->mAudit && allowAuditMod) gOFS->mAudit->audit(eos::audit::CHMOD, filePath, vid, std::string(logId), std::string(cident), \"mgm\",\n                               std::string(), &beforeStat, &afterStat, std::string(), std::string(), std::string(), __FILE__, __LINE__, VERSION);\n        }\n        if (fmd->getCUid() != oldUid || fmd->getCGid() != oldGid) {\n          if (gOFS->mAudit && allowAuditMod) gOFS->mAudit->audit(eos::audit::CHOWN, filePath, vid, std::string(logId), std::string(cident), \"mgm\",\n                               std::string(), &beforeStat, &afterStat, std::string(), std::string(), std::string(), __FILE__, __LINE__, VERSION);\n        }\n\n        // Emit SET_XATTR / RM_XATTR for file xattr changes\n        for (const auto& kv : md.attr()) {\n          const std::string& an = kv.first;\n          if (an.size() >= 3 && an.substr(0, 3) == \"sys\") continue;\n          auto oit = oldFileAttrs.find(an);\n          std::string before = (oit == oldFileAttrs.end()) ? std::string() : oit->second;\n          if (oit == oldFileAttrs.end() || oit->second != kv.second) {\n            if (gOFS->mAudit && allowAuditMod) gOFS->mAudit->audit(eos::audit::SET_XATTR, filePath, vid, std::string(logId), std::string(cident), \"mgm\",\n                                 std::string(), nullptr, nullptr, an, before, kv.second, __FILE__, __LINE__, VERSION);\n            if (an == \"sys.acl\" || an == \"user.acl\") {\n              if (gOFS->mAudit && allowAuditMod) gOFS->mAudit->audit(eos::audit::SET_ACL, filePath, vid, std::string(logId), std::string(cident), \"mgm\",\n                                  std::string(), nullptr, nullptr, an, before, kv.second, __FILE__, __LINE__, VERSION);\n            }\n          }\n          // Removed attributes\n          for (const auto& okv : oldFileAttrs) {\n            const std::string& oan = okv.first;\n            if (oan.size() >= 3 && oan.substr(0, 3) == \"sys\") continue;\n            if (md.attr().find(oan) == md.attr().end()) {\n              if (gOFS->mAudit && allowAuditMod) gOFS->mAudit->audit(eos::audit::RM_XATTR, filePath, vid, std::string(logId), std::string(cident), \"mgm\",\n                                  std::string(), nullptr, nullptr, oan, okv.second, std::string(), __FILE__, __LINE__, VERSION);\n            }\n          }\n        }\n      }\n    }\n\n    if (op != UPDATE) {\n      // update the mtime\n      gOFS->eosDirectoryService->updateStore(pcmd.get());\n      pcmd->notifyMTimeChange(gOFS->eosDirectoryService);\n    }\n\n    // retrieve the clock\n    fmd = gOFS->eosFileService->getFileMD(eos::common::FileId::InodeToFid(md_ino),\n                                          &clock);\n\n    if (op == CREATE) {\n      gOFS->mReplicationTracker->Create(fmd);\n    }\n\n    eos_info(\"ino=%llx clock=%llx\", md_ino, clock);\n    // release the namespace lock before serialization/broadcasting\n    lock.Release();\n    eos::fusex::response resp;\n    resp.set_type(resp.ACK);\n    resp.mutable_ack_()->set_code(resp.ack_().OK);\n    resp.mutable_ack_()->set_transactionid(md.reqid());\n    resp.mutable_ack_()->set_md_ino(md_ino);\n    resp.SerializeToString(response);\n\n    switch (op) {\n    case MOVE:\n      gOFS->MgmStats.Add(\"Eosxd::ext::MV\", vid.uid, vid.gid, 1, vid.app);\n      break;\n\n    case UPDATE:\n      gOFS->MgmStats.Add(\"Eosxd::ext::UPDATE\", vid.uid, vid.gid, 1, vid.app);\n      break;\n\n    case CREATE:\n      gOFS->MgmStats.Add(\"Eosxd::ext::CREATE\", vid.uid, vid.gid, 1, vid.app);\n      break;\n\n    case RENAME:\n      gOFS->MgmStats.Add(\"Eosxd::ext::RENAME\", vid.uid, vid.gid, 1, vid.app);\n      break;\n    }\n\n    // broadcast this update around\n    switch (op) {\n    case RENAME:\n      Cap().BroadcastDeletion(pcmd->getId(), md, oldname, pt_mtime);\n      break;\n\n    case MOVE:\n      Cap().BroadcastDeletion(cpcmd->getId(), mv_md, oldname, pt_mtime);\n      Cap().BroadcastDeletion(pcmd->getId(), md, oldname, pt_mtime);\n      break;\n\n    case UPDATE:\n    case CREATE:\n      break;\n    }\n\n    Cap().BroadcastMD(md, md_ino, md_pino, clock, pt_mtime);\n\n    if (op == CREATE) {\n      // trigger default workflow in FuseServer\n      errno = 0;\n      workflow.Init(&attrmap, std::string(\"\"), fileId);\n      auto workflowType = \"default\";\n      std::string errorMsg;\n      auto ret_wfe = workflow.Trigger(\"sync::create\", std::string{workflowType}, vid,\n                                      \"\", errorMsg);\n\n      if (ret_wfe < 0 && errno == ENOKEY) {\n        eos_debug(\"msg=\\\"no workflow defined for sync::create\\\"\");\n      } else {\n        eos_info(\"msg=\\\"workflow trigger returned\\\" retc=%d errno=%d\", ret_wfe, errno);\n\n        if (ret_wfe == EINVAL) {\n          throw_mdexception(errno, \"Workflow failed : \" << fileId);\n        }\n      }\n    }\n  } catch (eos::MDException& e) {\n    eos_err(\"ino=%lx err-no=%d msg=\\\"%s\\\"\", (long) md.md_ino(),\n            e.getErrno(),\n            e.getMessage().str().c_str());\n    eos::fusex::response resp;\n    resp.set_type(resp.ACK);\n    resp.mutable_ack_()->set_code(resp.ack_().PERMANENT_FAILURE);\n    resp.mutable_ack_()->set_err_no(e.getErrno());\n    resp.mutable_ack_()->set_err_msg(e.getMessage().str().c_str());\n    resp.mutable_ack_()->set_transactionid(md.reqid());\n    resp.SerializeToString(response);\n  }\n\n  EXEC_TIMING_END(\"Eosxd::ext::SETFILE\");\n  eos_info(\"ino=%lx rt=%.02f\", (long) md.md_ino(), __exec_time__);\n  return 0;\n}\n\n\n//------------------------------------------------------------------------------\n// Server a meta-data SET operation\n//------------------------------------------------------------------------------/*----------------------------------------------------------------------------*/\n\nint\nServer::OpSetLink(const std::string& id,\n                  const eos::fusex::md& md,\n                  eos::common::VirtualIdentity& vid,\n                  std::string* response,\n                  uint64_t* clock)\n{\n  gOFS->MgmStats.Add(\"Eosxd::ext::SETLNK\", vid.uid, vid.gid, 1, vid.app);\n  EXEC_TIMING_BEGIN(\"Eosxd::ext::SETLNK\");\n  enum set_type {\n    CREATE, UPDATE, RENAME, MOVE\n  };\n  set_type op;\n  uint64_t md_ino = 0;\n  bool exclusive = false;\n\n  if (md.type() == md.EXCL) {\n    exclusive = true;\n  }\n\n  eos_info(\"ino=%#lx %s\", (long) md.md_ino(),\n           md.name().c_str());\n  eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n  std::shared_ptr<eos::IFileMD> fmd;\n  std::shared_ptr<eos::IFileMD> ofmd;\n  std::shared_ptr<eos::IContainerMD> pcmd;\n  std::shared_ptr<eos::IContainerMD> opcmd;\n  uint64_t md_pino = md.md_pino();\n\n  try {\n    // link creation/update\n    pcmd = gOFS->eosDirectoryService->getContainerMD(md.md_pino());\n    fmd = md.md_ino() ? gOFS->eosFileService->getFileMD(\n            eos::common::FileId::InodeToFid(md.md_ino())) : 0;\n\n    if (!fmd && md.md_ino()) {\n      // file existed but has been deleted\n      throw_mdexception(ENOENT, \"No such file : \" << md_ino);\n    }\n\n    if (fmd && exclusive) {\n      return EEXIST;\n    }\n\n    if (fmd) {\n      // link MD update\n      op = UPDATE;\n\n      if (fmd->getContainerId() != md.md_pino()) {\n        op = MOVE;\n        eos_info(\"op=MOVE ino=%#lx %s=>%s\", (long) md.md_ino(), fmd->getName().c_str(),\n                 md.name().c_str());\n        opcmd = gOFS->eosDirectoryService->getContainerMD(fmd->getContainerId());\n        // remove symlink from current parent\n        opcmd->removeFile(fmd->getName());\n        gOFS->eosView->updateContainerStore(opcmd.get());\n        // update the name\n        fmd->setName(md.name());\n        // check if target exists\n        ofmd = pcmd->findFile(md.name());\n\n        if (ofmd) {\n          // remove the target - no recycle bin for symlinks\n          try {\n            XrdOucErrInfo error;\n            pcmd->removeFile(md.name());\n            // unlink the existing file\n            ofmd->setContainerId(0);\n            ofmd->unlinkAllLocations();\n            eos::IQuotaNode* quotanode = gOFS->eosView->getQuotaNode(pcmd.get());\n\n            // free previous quota\n            if (quotanode) {\n              quotanode->removeFile(ofmd.get());\n            }\n\n            gOFS->eosFileService->updateStore(ofmd.get());\n            gOFS->eosView->updateContainerStore(opcmd.get());\n          } catch (eos::MDException& e) {\n          }\n        }\n\n        eos::IQuotaNode* quotanode = gOFS->eosView->getQuotaNode(opcmd.get());\n\n        // free quota in old parent, will be added to new parent\n        if (quotanode) {\n          quotanode->removeFile(fmd.get());\n        }\n      } else {\n        if (fmd->getName() != md.name()) {\n          op = RENAME;\n          eos_info(\"op=RENAME ino=%#lx %s=>%s\", (long) md.md_ino(),\n                   fmd->getName().c_str(), md.name().c_str());\n          // check if target exists\n          ofmd = pcmd->findFile(md.name());\n\n          if (ofmd) {\n            // remove the target - no recycle bin for symlink rename\n            try {\n              XrdOucErrInfo error;\n              pcmd->removeFile(md.name());\n              // unlink the existing file\n              ofmd->setContainerId(0);\n              ofmd->unlinkAllLocations();\n              eos::IQuotaNode* quotanode = gOFS->eosView->getQuotaNode(pcmd.get());\n\n              // free previous quota\n              if (quotanode) {\n                quotanode->removeFile(ofmd.get());\n              }\n\n              gOFS->eosFileService->updateStore(ofmd.get());\n            } catch (eos::MDException& e) {\n            }\n          }\n\n          // call the rename function\n          gOFS->eosView->renameFile(fmd.get(), md.name());\n        }\n      }\n    } else {\n      op = CREATE;\n      eos_info(\"op=CREATE ino=%#lx %s\", (long) md.md_ino(), md.name().c_str());\n\n      if (md.name().substr(0, strlen(EOS_COMMON_PATH_ATOMIC_FILE_PREFIX)) ==\n          EOS_COMMON_PATH_ATOMIC_FILE_PREFIX) {\n        eos_err(\"msg=\\\"atomic path is forbidden as a link/fifo name\\\" \"\n                \"ino=%lx name=\\\"%s\\\"\", (long) md.md_ino(),\n                md.name().c_str());\n        return EPERM;\n      }\n\n      fmd = pcmd->findFile(md.name());\n\n      if (fmd && exclusive) {\n        return EEXIST;\n      }\n\n      fmd = gOFS->eosFileService->createFile(0);\n    }\n\n    std::string s;\n\n    try {\n      s = fmd->getAttribute(\"sys.fusex.state\");\n    } catch (...) {}\n\n    switch (op) {\n    case MOVE:\n      s += \"M\";\n      gOFS->MgmStats.Add(\"Eosxd::ext::MV\", vid.uid, vid.gid, 1, vid.app);\n      break;\n\n    case UPDATE:\n      s += \"U\";\n      gOFS->MgmStats.Add(\"Eosxd::ext::UPDATE\", vid.uid, vid.gid, 1, vid.app);\n      break;\n\n    case CREATE:\n      s += \"C\";\n      gOFS->MgmStats.Add(\"Eosxd::ext::CREATELNK\", vid.uid, vid.gid, 1, vid.app);\n      break;\n\n    case RENAME:\n      s += \"R\";\n      gOFS->MgmStats.Add(\"Eosxd::ext::RENAME\", vid.uid, vid.gid, 1, vid.app);\n      break;\n\n    default:\n      s += \"0\";\n      break;\n    }\n\n    fmd->setName(md.name());\n    fmd->setLink(md.target());\n    fmd->setLayoutId(0);\n    md_ino = eos::common::FileId::FidToInode(fmd->getId());\n    eos_info(\"ino=%lx pino=%lx md-ino=%lx create-link\", (long) md.md_ino(),\n             (long) md.md_pino(), md_ino);\n    fmd->setCUid(md.uid());\n    fmd->setCGid(md.gid());\n    fmd->setAttribute(\"sys.fusex.state\",\n                      eos::common::StringConversion::ReduceString(s).c_str());\n    fmd->setSize(md.target().length());\n    fmd->setFlags(md.mode() & (S_IRWXU | S_IRWXG | S_IRWXO));\n    eos::IFileMD::ctime_t ctime;\n    eos::IFileMD::ctime_t mtime;\n    ctime.tv_sec = md.ctime();\n    ctime.tv_nsec = md.ctime_ns();\n    mtime.tv_sec = md.mtime();\n    mtime.tv_nsec = md.mtime_ns();\n    fmd->setCTime(ctime);\n    fmd->setMTime(mtime);\n    replaceNonSysAttributes(fmd, md);\n\n    if ((op == CREATE) || (op == MOVE)) {\n      pcmd->addFile(fmd.get());\n      eos::IQuotaNode* quotanode = gOFS->eosView->getQuotaNode(pcmd.get());\n\n      // add inode quota\n      if (quotanode) {\n        quotanode->addFile(fmd.get());\n      }\n    }\n\n    if (op == CREATE) {\n      // store the birth time as an extended attribute\n      char btime[256];\n      snprintf(btime, sizeof(btime), \"%lu.%lu\", md.btime(), md.btime_ns());\n      fmd->setAttribute(\"sys.eos.btime\", btime);\n      fmd->setAttribute(\"sys.vtrace\", vid.getTrace());\n    }\n\n    struct timespec pt_mtime;\n\n    pcmd->setMTime(ctime);\n\n    pt_mtime.tv_sec = ctime.tv_sec;\n\n    pt_mtime.tv_nsec = ctime.tv_nsec;\n\n    gOFS->eosFileService->updateStore(fmd.get());\n\n    gOFS->eosDirectoryService->updateStore(pcmd.get());\n\n    // release the namespace lock before serialization/broadcasting\n    lock.Release();\n\n    eos::fusex::response resp;\n\n    resp.set_type(resp.ACK);\n\n    resp.mutable_ack_()->set_code(resp.ack_().OK);\n\n    resp.mutable_ack_()->set_transactionid(md.reqid());\n\n    resp.mutable_ack_()->set_md_ino(md_ino);\n\n    resp.SerializeToString(response);\n\n    uint64_t bclock = 0;\n\n    Cap().BroadcastMD(md, md_ino, md_pino, bclock, pt_mtime);\n\n    // release the namespace lock before serialization/broadcasting\n    lock.Release();\n\n    // Audit SYMLINK creation (OpSetLink create)\n    if (gOFS->mAudit && op == CREATE) {\n      std::string linkPath = gOFS->eosView->getUri(fmd.get());\n      eos::audit::Stat afterStat;\n      eos::mgm::auditutil::buildStatFromFileMD(fmd, afterStat, /*includeSize=*/true, /*includeChecksum=*/true, /*includeNs=*/true);\n      if (gOFS->mAudit && gOFS->AllowAuditModification(linkPath)) gOFS->mAudit->audit(eos::audit::SYMLINK, linkPath, vid, std::string(logId), std::string(cident), \"mgm\",\n                          md.target(), nullptr, nullptr, std::string(), std::string(), std::string(), __FILE__, __LINE__, VERSION);\n    }\n  } catch (eos::MDException& e) {\n    eos_err(\"ino=%lx err-no=%d msg=\\\"%s\\\"\", (long) md.md_ino(),\n            e.getErrno(),\n            e.getMessage().str().c_str());\n    eos::fusex::response resp;\n    resp.set_type(resp.ACK);\n    resp.mutable_ack_()->set_code(resp.ack_().PERMANENT_FAILURE);\n    resp.mutable_ack_()->set_err_no(e.getErrno());\n    resp.mutable_ack_()->set_err_msg(e.getMessage().str().c_str());\n    resp.mutable_ack_()->set_transactionid(md.reqid());\n    resp.SerializeToString(response);\n  }\n\n  EXEC_TIMING_END(\"Eosxd::ext::SETLNK\");\n  return 0;\n}\n\n\n//------------------------------------------------------------------------------\n// Serve a meta-data DELETE operation\n//-------------------------------------------------------------------------------*/\n\nint\nServer::OpDelete(const std::string& id,\n                 const eos::fusex::md& md,\n                 eos::common::VirtualIdentity& vid,\n                 std::string* response,\n                 uint64_t* clock)\n{\n  gOFS->MgmStats.Add(\"Eosxd::ext::RM\", vid.uid, vid.gid, 1, vid.app);\n\n  if (!ValidateCAP(md, D_OK, vid)) {\n    std::string perm = \"D\";\n\n    // a CAP might have gone or timedout, let's check again the permissions\n    if (((errno == ENOENT) ||\n         (errno == EINVAL) ||\n         (errno == ETIMEDOUT)) &&\n        ValidatePERM(md, perm, vid)) {\n      // this can pass on ... permissions are fine\n    } else {\n      eos_err(\"msg=\\\"delete has wrong cap\\\" ino=%lx\", (long) md.md_ino());\n      return EPERM;\n    }\n  }\n\n  if (S_ISDIR(md.mode())) {\n    return OpDeleteDirectory(id, md, vid, response, clock);\n  } else if (S_ISREG(md.mode()) || S_ISFIFO(md.mode())) {\n    return OpDeleteFile(id, md, vid, response, clock);\n  } else if (S_ISLNK(md.mode())) {\n    return OpDeleteLink(id, md, vid, response, clock);\n  }\n\n  return EINVAL;\n}\n\n//------------------------------------------------------------------------------\n// Serve a meta-data DELETE operation\n//-------------------------------------------------------------------------------*/\n\nint\nServer::OpDeleteDirectory(const std::string& id,\n                          const eos::fusex::md& md,\n                          eos::common::VirtualIdentity& vid,\n                          std::string* response,\n                          uint64_t* clock)\n{\n  gOFS->MgmStats.Add(\"Eosxd::ext::RMDIR\", vid.uid, vid.gid, 1, vid.app);\n  EXEC_TIMING_BEGIN(\"Eosxd::ext::RMDIR\");\n  eos::fusex::response resp;\n  resp.set_type(resp.ACK);\n  std::shared_ptr<eos::IContainerMD> cmd;\n  std::shared_ptr<eos::IContainerMD> pcmd;\n  eos::IFileMD::ctime_t mtime;\n  mtime.tv_sec = md.mtime();\n  mtime.tv_nsec = md.mtime_ns();\n  eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n\n  try {\n    pcmd = gOFS->eosDirectoryService->getContainerMD(md.md_pino());\n    cmd = gOFS->eosDirectoryService->getContainerMD(md.md_ino());\n\n    if (pcmd->getMode() & S_ISVTX) {\n      // check that we are the owner of that file if the vertex bit is set\n      if (cmd->getCUid() != vid.uid) {\n        throw_mdexception(EPERM,\n                          \"Vertex bit set on parent directory and you are not the owner: \" << md.name());\n      }\n    }\n\n    if (!cmd) {\n      // directory does not exist\n      throw_mdexception(ENOENT, \"No such directory : \" << md.md_ino());\n    }\n\n    if (cmd->getName() != md.name()) {\n      // directory was removed or renamed before deletion\n      throw_mdexception(ENOENT,\n                        \"No such directory : \" << md.name() << \" (\" << cmd->getName() << \" )\");\n    }\n\n    pcmd->setMTime(mtime);\n\n    // check if this directory is empty\n    if (cmd->getNumContainers() || cmd->getNumFiles()) {\n      eos::fusex::response resp;\n      resp.set_type(resp.ACK);\n      resp.mutable_ack_()->set_code(resp.ack_().PERMANENT_FAILURE);\n      resp.mutable_ack_()->set_err_no(ENOTEMPTY);\n      resp.mutable_ack_()->set_err_msg(\"directory not empty\");\n      resp.mutable_ack_()->set_transactionid(md.reqid());\n      lock.Release();\n      resp.SerializeToString(response);\n    } else {\n      eos_info(\"ino=%lx msg=\\\"delete-dir\\\"\", (long) md.md_ino());\n      std::string dirPath;\n      if (gOFS->mAudit) {\n        dirPath = gOFS->eosView->getUri(cmd.get());\n        if (!dirPath.empty() && dirPath.back() != '/') dirPath.push_back('/');\n      }\n      pcmd->removeContainer(cmd->getName());\n      gOFS->eosDirectoryService->removeContainer(cmd.get());\n      gOFS->eosDirectoryService->updateStore(pcmd.get());\n      pcmd->notifyMTimeChange(gOFS->eosDirectoryService);\n      // release the namespace lock before serialization/broadcasting\n      lock.Release();\n      resp.mutable_ack_()->set_code(resp.ack_().OK);\n      resp.mutable_ack_()->set_transactionid(md.reqid());\n      resp.SerializeToString(response);\n      eos::IContainerMD::ctime_t pt_mtime;\n      pcmd->getMTime(pt_mtime);\n      Cap().BroadcastDeletion(pcmd->getId(), md, cmd->getName(), pt_mtime);\n      Cap().BroadcastRefresh(pcmd->getId(), md, pcmd->getParentId(), true);\n      Cap().Delete(md.md_ino());\n      // Audit RMDIR after successful deletion\n      if (gOFS->mAudit && !dirPath.empty() && gOFS->AllowAuditModification(dirPath)) {\n        gOFS->mAudit->audit(eos::audit::RMDIR, dirPath, vid, logId, cident, \"mgm\");\n      }\n    }\n  } catch (eos::MDException& e) {\n    resp.mutable_ack_()->set_code(resp.ack_().PERMANENT_FAILURE);\n    resp.mutable_ack_()->set_err_no(e.getErrno());\n    resp.mutable_ack_()->set_err_msg(e.getMessage().str().c_str());\n    resp.mutable_ack_()->set_transactionid(md.reqid());\n    resp.SerializeToString(response);\n    eos_err(\"ino=%lx err-no=%d msg=\\\"%s\\\"\", (long) md.md_ino(),\n            e.getErrno(),\n            e.getMessage().str().c_str());\n  }\n\n  EXEC_TIMING_END(\"Eosxd::ext::RMDIR\");\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Serve a meta-data DELETE operation\n//-------------------------------------------------------------------------------*/\n\nint\nServer::OpDeleteFile(const std::string& id,\n                     const eos::fusex::md& md,\n                     eos::common::VirtualIdentity& vid,\n                     std::string* response,\n                     uint64_t* clock)\n{\n  gOFS->MgmStats.Add(\"Eosxd::ext::DELETE\", vid.uid, vid.gid, 1, vid.app);\n  EXEC_TIMING_BEGIN(\"Eosxd::ext::DELETE\");\n\n  if (!ValidateCAP(md, D_OK, vid)) {\n    std::string perm = \"D\";\n\n    // a CAP might have gone or timedout, let's check again the permissions\n    if (((errno == ENOENT) ||\n         (errno == EINVAL) ||\n         (errno == ETIMEDOUT)) &&\n        ValidatePERM(md, perm, vid)) {\n      // this can pass on ... permissions are fine\n    } else {\n      eos_err(\"msg=\\\"delete has wrong cap\\\" ino=%lx\", (long) md.md_ino());\n      return EPERM;\n    }\n  }\n\n  eos::fusex::response resp;\n  resp.set_type(resp.ACK);\n  std::shared_ptr<eos::IContainerMD> pcmd;\n  std::shared_ptr<eos::IFileMD> fmd;\n  eos::IFileMD::ctime_t mtime;\n  mtime.tv_sec = md.mtime();\n  mtime.tv_nsec = md.mtime_ns();\n  eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n\n  try {\n    pcmd = gOFS->eosDirectoryService->getContainerMD(md.md_pino());\n    fmd = gOFS->eosFileService->getFileMD(eos::common::FileId::InodeToFid(\n                                            md.md_ino()));\n\n    if (pcmd->getMode() & S_ISVTX) {\n      // check that we are the owner of that file if the vertex bit is set\n      if (fmd->getCUid() != vid.uid) {\n        throw_mdexception(EPERM,\n                          \"Vertex bit set on parent directory and you are not the owner: \" << md.name());\n      }\n    }\n\n    if (!fmd) {\n      // file does not exist\n      throw_mdexception(ENOENT, \"No such file : \" << md.md_ino());\n    }\n\n    if (fmd->getName() != md.name()) {\n      // file was removed or renamed before deletion\n      throw_mdexception(ENOENT,\n                        \"No such file : \" << md.name() << \" (\" << fmd->getName() << \" )\");\n    }\n\n    pcmd->setMTime(mtime);\n    eos_info(\"ino=%lx msg=\\\"delete-file\\\"\", (long) md.md_ino());\n      // Pre-capture path and before stat for auditing\n    std::string filePath;\n    eos::audit::Stat beforeStat;\n    if (gOFS->mAudit) {\n      filePath = gOFS->eosView->getUri(fmd.get());\n      eos::mgm::auditutil::buildStatFromFileMD(fmd, beforeStat, /*includeSize=*/true, /*includeChecksum=*/true, /*includeNs=*/true);\n    }\n    eos::IContainerMD::XAttrMap attrmap = pcmd->getAttributes();\n    // this is a client hiding versions, force the version cleanup\n    bool version_cleanup =\n      (md.opflags() == eos::fusex::md::DELETEVERSIONS) ||\n      (md.opflags() == eos::fusex::md::DELETEVERSIONSNORECYCLEBIN);\n    bool no_recyclebin =\n      (md.opflags() == eos::fusex::md::NORECYCLEBIN) ||\n      (md.opflags() == eos::fusex::md::DELETEVERSIONSNORECYCLEBIN);\n\n    // recycle bin - not for hardlinked files or hardlinks!\n    if (\n      (version_cleanup || attrmap.count(Recycle::gRecyclingAttribute)) &&\n      (!fmd->hasAttribute(k_mdino)) && (!fmd->hasAttribute(k_nlink))) {\n      // translate to a path name and call the complex deletion function\n      // this is vulnerable to a hard to trigger race conditions\n      std::string fullpath = gOFS->eosView->getUri(fmd.get());\n      gOFS->WriteRecycleRecord(fmd);\n      lock.Release();\n      XrdOucErrInfo error;\n      bool no_recycle = !attrmap.count(Recycle::gRecyclingAttribute);\n      no_recycle |= no_recyclebin;\n      (void) gOFS->_rem(fullpath.c_str(), error, vid, \"\",\n                        false,  // not simulated\n                        false, // don't keep any version - delete versions as well\n                        no_recycle, // recycle bin setting and client with combined\n                        true,  // don't enforce quota\n                        false // don't broadcast\n                       );\n    } else {\n      try {\n        // handle quota\n        eos::IQuotaNode* quotanode = gOFS->eosView->getQuotaNode(pcmd.get());\n\n        if (quotanode) {\n          quotanode->removeFile(fmd.get());\n        }\n      } catch (eos::MDException& e) {\n      }\n\n      bool doDelete = true;\n      uint64_t tgt_md_ino;\n\n      if (fmd->hasAttribute(k_mdino)) {\n        /* this is a hard link, decrease reference count on underlying file */\n        tgt_md_ino = std::stoull(fmd->getAttribute(k_mdino));\n        uint64_t clock;\n        /* gmd = the file holding the inode */\n        std::shared_ptr<eos::IFileMD> gmd = gOFS->eosFileService->getFileMD(\n                                              eos::common::FileId::InodeToFid(tgt_md_ino), &clock);\n        long nlink = std::stol(gmd->getAttribute(k_nlink)) - 1;\n\n        if (nlink) {\n          gmd->setAttribute(k_nlink, std::to_string(nlink));\n        } else {\n          gmd->removeAttribute(k_nlink);\n        }\n\n        gOFS->eosFileService->updateStore(gmd.get());\n        eos_info(\"msg=\\\"hlnk nlink update\\\" target=\\\"%s\\\" source=\\\"%s\\\" nlink=%ld\",\n                 gmd->getName().c_str(), fmd->getName().c_str(), nlink);\n\n        if (nlink <= 0) {\n          if (gmd->getName().substr(0, 13) == \"...eos.ino...\") {\n            eos_info(\"msg=\\\"hlnk unlink\\\" target=\\\"%s\\\" source=\\\"%s\\\" nlink=%ld\",\n                     gmd->getName().c_str(), fmd->getName().c_str(), nlink);\n            XrdOucErrInfo error;\n\n            if (XrdMgmOfsFile::create_cow(XrdMgmOfsFile::cowDelete, pcmd, gmd, vid,\n                                          error) == -1) {\n              pcmd->removeFile(gmd->getName());\n              gmd->unlinkAllLocations();\n              gmd->setContainerId(0);\n            }\n\n            gOFS->eosFileService->updateStore(gmd.get());\n          }\n        }\n      } else if (fmd->hasAttribute(k_nlink)) {\n        /* this is a genuine file, potentially with hard links */\n        tgt_md_ino = eos::common::FileId::FidToInode(fmd->getId());\n        long nlink = std::stol(fmd->getAttribute(k_nlink));\n\n        if (nlink > 0) {\n          // hard links exist, just rename the file so the inode does not disappear\n          char nameBuf[256];\n          snprintf(nameBuf, sizeof(nameBuf), \"...eos.ino...%lx\", tgt_md_ino);\n          std::string tmpName = nameBuf;\n          fmd->setAttribute(k_nlink, std::to_string(nlink));\n          eos_info(\"hlnk unlink rename %s=>%s new nlink %d\",\n                   fmd->getName().c_str(), tmpName.c_str(), nlink);\n          pcmd->removeFile(tmpName);            // if the target exists, remove it!\n          gOFS->eosView->renameFile(fmd.get(), tmpName);\n          doDelete = false;\n        } else {\n          eos_info(\"hlnk nlink %ld for %s, will be deleted\",\n                   nlink, fmd->getName().c_str());\n        }\n      }\n\n      if (doDelete) {       /* delete, but clone first if needed */\n        XrdOucErrInfo error;\n        int rc = XrdMgmOfsFile::create_cow(XrdMgmOfsFile::cowDelete, pcmd, fmd, vid,\n                                           error);\n\n        if (rc == -1) {     /* usual outcome: no cloning */\n          pcmd->removeFile(fmd->getName());\n          fmd->setContainerId(0);\n          fmd->unlinkAllLocations();\n        }\n\n        gOFS->WriteRmRecord(fmd);\n      }\n\n      gOFS->eosFileService->updateStore(fmd.get());\n      gOFS->eosDirectoryService->updateStore(pcmd.get());\n      pcmd->notifyMTimeChange(gOFS->eosDirectoryService);\n      lock.Release();\n\n      if (doDelete) {\n        Workflow workflow;\n        std::string errMsg;\n        // eventually trigger a workflow\n        workflow.Init(&attrmap, std::string(\"\"), fmd->getId());\n        errno = 0;\n        auto ret_wfe = workflow.Trigger(\"sync::delete\", \"default\", vid, \"\", errMsg);\n\n        if (ret_wfe < 0 && errno == ENOKEY) {\n          eos_info(\"msg=\\\"no workflow defined for delete\\\"\");\n        } else {\n          eos_info(\"msg=\\\"workflow trigger returned\\\" retc=%d errno=%d\", ret_wfe, errno);\n        }\n\n        if (ret_wfe && errno != ENOKEY) {\n          eos::MDException e(errno);\n          e.getMessage() << \"Deletion workflow failed: \" << errMsg;\n          throw e;\n        }\n      }\n    }\n\n    resp.mutable_ack_()->set_code(resp.ack_().OK);\n    resp.mutable_ack_()->set_transactionid(md.reqid());\n    resp.SerializeToString(response);\n    eos::IContainerMD::ctime_t pt_mtime;\n    pcmd->getMTime(pt_mtime);\n    Cap().BroadcastDeletion(pcmd->getId(), md, md.name(), pt_mtime);\n    Cap().BroadcastRefresh(pcmd->getId(), md, pcmd->getParentId(), true);\n    Cap().Delete(md.md_ino());\n    // Audit DELETE after successful deletion (only when actually deleted here)\n    if (gOFS->mAudit && !filePath.empty() && gOFS->AllowAuditModification(filePath)) {\n      gOFS->mAudit->audit(eos::audit::DELETE, filePath, vid, std::string(logId), std::string(cident), \"mgm\",\n                          std::string(), &beforeStat, nullptr, std::string(), std::string(), std::string(), __FILE__, __LINE__, VERSION);\n    }\n  } catch (eos::MDException& e) {\n    resp.mutable_ack_()->set_code(resp.ack_().PERMANENT_FAILURE);\n    resp.mutable_ack_()->set_err_no(e.getErrno());\n    resp.mutable_ack_()->set_err_msg(e.getMessage().str().c_str());\n    resp.mutable_ack_()->set_transactionid(md.reqid());\n    resp.SerializeToString(response);\n    eos_err(\"ino=%lx err-no=%d msg=\\\"%s\\\"\", (long) md.md_ino(),\n            e.getErrno(),\n            e.getMessage().str().c_str());\n  }\n\n  EXEC_TIMING_END(\"Eosxd::ext::DELETE\");\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Serve a meta-data DELETE operation\n//-------------------------------------------------------------------------------*/\n\nint\nServer::OpDeleteLink(const std::string& id,\n                     const eos::fusex::md& md,\n                     eos::common::VirtualIdentity& vid,\n                     std::string* response,\n                     uint64_t* clock)\n{\n  gOFS->MgmStats.Add(\"Eosxd::ext::DELETELNK\", vid.uid, vid.gid, 1, vid.app);\n  EXEC_TIMING_BEGIN(\"Eosxd::ext::DELETELNK\");\n\n  if (!ValidateCAP(md, D_OK, vid)) {\n    std::string perm = \"D\";\n\n    // a CAP might have gone or timedout, let's check again the permissions\n    if (((errno == ENOENT) ||\n         (errno == EINVAL) ||\n         (errno == ETIMEDOUT)) &&\n        ValidatePERM(md, perm, vid)) {\n      // this can pass on ... permissions are fine\n    } else {\n      eos_err(\"msg=\\\"delete has wrong cap\\\" ino=%lx\", (long) md.md_ino());\n      return EPERM;\n    }\n  }\n\n  eos::fusex::response resp;\n  resp.set_type(resp.ACK);\n  std::shared_ptr<eos::IContainerMD> pcmd;\n  std::shared_ptr<eos::IFileMD> fmd;\n  eos::IFileMD::ctime_t mtime;\n  mtime.tv_sec = md.mtime();\n  mtime.tv_nsec = md.mtime_ns();\n  eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n\n  try {\n    pcmd = gOFS->eosDirectoryService->getContainerMD(md.md_pino());\n    fmd = gOFS->eosFileService->getFileMD(eos::common::FileId::InodeToFid(\n                                            md.md_ino()));\n\n    if (pcmd->getMode() & S_ISVTX) {\n      // check that we are the owner of that file if the vertex bit is set\n      if (fmd->getCUid() != vid.uid) {\n        throw_mdexception(EPERM,\n                          \"Vertex bit set on parent directory and you are not the owner: \" << md.name());\n      }\n    }\n\n    if (!fmd) {\n      // no link\n      throw_mdexception(ENOENT, \"No such link : \" << md.md_ino());\n    }\n\n    if (fmd->getName() != md.name()) {\n      // link was removed or renamed before deletion\n      throw_mdexception(ENOENT,\n                        \"No such link : \" << md.name() << \" (\" << fmd->getName() << \" )\");\n    }\n\n    pcmd->setMTime(mtime);\n    eos_info(\"msg=\\\"delete-link\\\" ino=%lx\", (long) md.md_ino());\n    std::string linkPath;\n    if (gOFS->mAudit) {\n      linkPath = gOFS->eosView->getUri(fmd.get());\n    }\n    gOFS->eosView->removeFile(fmd.get());\n    eos::IQuotaNode* quotanode = gOFS->eosView->getQuotaNode(pcmd.get());\n\n    // free previous quota\n    if (quotanode) {\n      quotanode->removeFile(fmd.get());\n    }\n\n    gOFS->eosDirectoryService->updateStore(pcmd.get());\n    pcmd->notifyMTimeChange(gOFS->eosDirectoryService);\n    // release the namespace lock before serialization/broadcasting\n    lock.Release();\n    resp.mutable_ack_()->set_code(resp.ack_().OK);\n    resp.mutable_ack_()->set_transactionid(md.reqid());\n    resp.SerializeToString(response);\n    eos::IContainerMD::ctime_t pt_mtime;\n    pcmd->getMTime(pt_mtime);\n    Cap().BroadcastDeletion(pcmd->getId(), md, md.name(), pt_mtime);\n    Cap().BroadcastRefresh(pcmd->getId(), md, pcmd->getParentId(), true);\n    Cap().Delete(md.md_ino());\n    // Audit DELETE for symlink removal\n    if (gOFS->mAudit && !linkPath.empty() && gOFS->AllowAuditModification(linkPath)) {\n      gOFS->mAudit->audit(eos::audit::DELETE, linkPath, vid, logId, cident, \"mgm\");\n    }\n  } catch (eos::MDException& e) {\n    resp.mutable_ack_()->set_code(resp.ack_().PERMANENT_FAILURE);\n    resp.mutable_ack_()->set_err_no(e.getErrno());\n    resp.mutable_ack_()->set_err_msg(e.getMessage().str().c_str());\n    resp.mutable_ack_()->set_transactionid(md.reqid());\n    resp.SerializeToString(response);\n    eos_err(\"ino=%lx err-no=%d msg=\\\"%s\\\"\", (long) md.md_ino(),\n            e.getErrno(),\n            e.getMessage().str().c_str());\n  }\n\n  EXEC_TIMING_END(\"Eosxd::ext::DELETELNK\");\n  return 0;\n}\n//------------------------------------------------------------------------------\n// Server a meta-data GETCAP operation\n//------------------------------------------------------------------------------\n\nint\nServer::OpGetCap(const std::string& id,\n                 const eos::fusex::md& md,\n                 eos::common::VirtualIdentity& vid,\n                 std::string* response,\n                 uint64_t* clock)\n{\n  gOFS->MgmStats.Add(\"Eosxd::ext::GETCAP\", vid.uid, vid.gid, 1, vid.app);\n  EXEC_TIMING_BEGIN(\"Eosxd::ext::GETCAP\");\n  eos::fusex::container cont;\n  cont.set_type(cont.CAP);\n  eos::fusex::md lmd;\n  {\n    // get the meta data\n    if (eos::common::FileId::IsFileInode(md.md_ino())) {\n      FillFileMD((uint64_t) md.md_ino(), lmd, vid);\n    } else {\n      FillContainerMD((uint64_t) md.md_ino(), lmd, vid);\n    }\n\n    lmd.set_clientuuid(md.clientuuid());\n    lmd.set_clientid(md.clientid());\n    // get the capability\n    FillContainerCAP(md.md_ino(), lmd, vid, \"\");\n  }\n  // this cap only provides the permissions, but it is not a cap which\n  // synchronized the meta data atomically, the client marks a cap locally\n  // if he synchronized the contents with it\n  *(cont.mutable_cap_()) = lmd.capability();\n  std::string rspstream;\n  cont.SerializeToString(&rspstream);\n  *response += Header(rspstream);\n  response->append(rspstream.c_str(), rspstream.size());\n  eos_info(\"msg=\\\"cap-issued\\\" id=%lx mode=%x vtime=%lu.%lu uid=%u gid=%u \"\n           \"client-id=%s auth-id=%s q-node=%lx q-vol=%ld  q-ino=%ld errc=%d\",\n           cont.cap_().id(), cont.cap_().mode(), cont.cap_().vtime(),\n           cont.cap_().vtime_ns(), cont.cap_().uid(), cont.cap_().gid(),\n           cont.cap_().clientid().c_str(), cont.cap_().authid().c_str(),\n           cont.cap_()._quota().quota_inode(),\n           cont.cap_()._quota().volume_quota(),\n           cont.cap_()._quota().inode_quota(),\n           cont.cap_().errc());\n  EXEC_TIMING_END(\"Eosxd::ext::GETCAP\");\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Server a meta-data file lock GET status operation\n//------------------------------------------------------------------------------\n\nint\nServer::OpGetLock(const std::string& id,\n                  const eos::fusex::md& md,\n                  eos::common::VirtualIdentity& vid,\n                  std::string* response,\n                  uint64_t* clock)\n{\n  gOFS->MgmStats.Add(\"Eosxd::ext::GETLK\", vid.uid, vid.gid, 1, vid.app);\n  EXEC_TIMING_BEGIN(\"Eosxd::ext::GETLK\");\n  eos::fusex::response resp;\n  resp.set_type(resp.LOCK);\n  struct flock lock;\n  memset(&lock, 0, sizeof(struct flock));\n  int rc = Locks().getLocks(md.md_ino())->getlk((pid_t) md.flock().pid(), &lock);\n  resp.mutable_lock_()->set_len(lock.l_len);\n  resp.mutable_lock_()->set_start(lock.l_start);\n  resp.mutable_lock_()->set_pid(lock.l_pid);\n  eos_info(\"msg=\\\"getlk\\\" rc=%d ino=%016lx start=%lu len=%ld pid=%u type=%d\",\n           rc,\n           md.md_ino(),\n           lock.l_start,\n           lock.l_len,\n           lock.l_pid,\n           lock.l_type);\n\n  switch (lock.l_type) {\n  case F_RDLCK:\n    resp.mutable_lock_()->set_type(md.flock().RDLCK);\n    break;\n\n  case F_WRLCK:\n    resp.mutable_lock_()->set_type(md.flock().WRLCK);\n    break;\n\n  case F_UNLCK:\n    resp.mutable_lock_()->set_type(md.flock().UNLCK);\n    break;\n  }\n\n  std::string rspstream;\n  resp.SerializeToString(response);\n  EXEC_TIMING_END(\"Eosxd::ext::GETLK\");\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Server a meta-data file lock SET operation\n//------------------------------------------------------------------------------\n\nint\nServer::OpSetLock(const std::string& id,\n                  const eos::fusex::md& md,\n                  eos::common::VirtualIdentity& vid,\n                  std::string* response,\n                  uint64_t* clock)\n{\n  EXEC_TIMING_BEGIN((md.operation() == md.SETLKW) ? \"Eosxd::ext::SETLKW\" :\n                    \"Eosxd::ext::SETLK\");\n  eos::fusex::response resp;\n  resp.set_type(resp.LOCK);\n  int sleep = 0;\n  bool xattr_lock = false;\n\n  if (md.operation() == md.SETLKW) {\n    gOFS->MgmStats.Add(\"Eosxd::ext::SETLKW\", vid.uid, vid.gid, 1, vid.app);\n    sleep = 1;\n  } else {\n    gOFS->MgmStats.Add(\"Eosxd::ext::SETLK\", vid.uid, vid.gid, 1, vid.app);\n  }\n\n  struct flock lock;\n\n  lock.l_len = md.flock().len();\n\n  lock.l_start = md.flock().start();\n\n  lock.l_pid = md.flock().pid();\n\n  switch (md.flock().type()) {\n  case eos::fusex::lock::RDLCK:\n    lock.l_type = F_RDLCK;\n    break;\n\n  case eos::fusex::lock::WRLCK:\n    lock.l_type = F_WRLCK;\n    break;\n\n  case eos::fusex::lock::UNLCK:\n    lock.l_type = F_UNLCK;\n    break;\n\n  default:\n    resp.mutable_lock_()->set_err_no(EAGAIN);\n    resp.SerializeToString(response);\n    return 0;\n    break;\n  }\n\n  if (lock.l_len == 0) {\n    // the infinite lock is represented by -1 in the locking class implementation\n    lock.l_len = -1;\n  }\n\n  // block also xattr locks\n  if (lock.l_type != F_UNLCK) {\n    std::shared_ptr<eos::IFileMD> fmd;\n    uint64_t clock = 0;\n    eos::common::RWMutexReadLock rd_ns_lock(gOFS->eosViewRWMutex);\n\n    try {\n      fmd = gOFS->eosFileService->getFileMD(eos::common::FileId::InodeToFid(\n                                              md.md_ino()),\n                                            &clock);\n\n      if (fmd) {\n        eos::IFileMD::XAttrMap attrmapF;\n        gOFS->listAttributes(gOFS->eosView, fmd.get(), attrmapF, false);\n        XattrLock alock(attrmapF);\n\n        if (alock.foreignLock(vid, (lock.l_type == F_WRLCK))) {\n          xattr_lock = true;\n        }\n      }\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      eos_err(\"msg=\\\"caught exception %d %s\\\"\\n\", e.getErrno(),\n              e.getMessage().str().c_str());\n      // we go on, if don't find the file here ...\n    }\n  }\n\n  eos_info(\"msg=\\\"setlk\\\" ino=%016lx start=%lu len=%ld pid=%u type=%d xattr-lock=%d\",\n           md.md_ino(),\n           lock.l_start,\n           lock.l_len,\n           lock.l_pid,\n           lock.l_type,\n           xattr_lock);\n\n  if (xattr_lock) {\n    // lock is busy by xattr\n    resp.mutable_lock_()->set_err_no(EAGAIN);\n  } else {\n    if (Locks().getLocks(md.md_ino())->setlk(md.flock().pid(), &lock, sleep,\n        md.clientuuid())) {\n      // lock ok!\n      resp.mutable_lock_()->set_err_no(0);\n    } else {\n      // lock is busy\n      resp.mutable_lock_()->set_err_no(EAGAIN);\n    }\n  }\n\n  resp.SerializeToString(response);\n  EXEC_TIMING_END((md.operation() == md.SETLKW) ? \"Eosxd::ext::SETLKW\" :\n                  \"Eosxd::ext::SETLK\");\n  return 0;\n}\n\n//----------------------------------------------------------------------------\n// Check if a file is in recycle bin or is a version file\n//----------------------------------------------------------------------------\nbool\nServer::CheckRecycleBinOrVersion(std::shared_ptr<eos::IFileMD> fmd)\n{\n  std::string path = gOFS->eosView->getUri(fmd.get());\n  return (Recycle::InRecycleBin(path) || eos::common::Path::IsVersion(path));\n}\n\n//------------------------------------------------------------------------------\n// Dispatch meta-data requests\n//------------------------------------------------------------------------------\n\nint\nServer::HandleMD(const std::string& id,\n                 const eos::fusex::md& md,\n                 eos::common::VirtualIdentity& vid,\n                 std::string* response,\n                 uint64_t* clock)\n{\n  std::string ops;\n  int op_type = md.operation();\n\n  if (op_type == md.GET) {\n    ops = \"GET\";\n  } else if (op_type == md.SET) {\n    ops = \"SET\";\n  } else if (op_type == md.DELETE) {\n    ops = \"DELETE\";\n  } else if (op_type == md.GETCAP) {\n    ops = \"GETCAP\";\n  } else if (op_type == md.LS) {\n    ops = \"LS\";\n  } else if (op_type == md.GETLK) {\n    ops = \"GETLK\";\n  } else if (op_type == md.SETLK) {\n    ops = \"SETLK\";\n  } else if (op_type == md.SETLKW) {\n    ops = \"SETLKW\";\n  } else if (op_type == md.BEGINFLUSH) {\n    ops = \"BEGINFLUSH\";\n  } else if (op_type == md.ENDFLUSH) {\n    ops = \"ENDFLUSH\";\n  } else {\n    ops = \"UNKNOWN\";\n  }\n\n  std::string op_class = \"none\";\n\n  if (S_ISDIR(md.mode())) {\n    op_class = \"dir\";\n  } else if (S_ISREG(md.mode())) {\n    op_class = \"file\";\n  } else if (S_ISFIFO(md.mode())) {\n    op_class = \"fifo\";\n  } else if (S_ISLNK(md.mode())) {\n    op_class = \"link\";\n  }\n\n  eos_debug(\"ino=%016lx operation=%s type=%s name=%s pino=%016lx cid=%s cuuid=%s\",\n            (long) md.md_ino(),\n            ops.c_str(),\n            op_class.c_str(),\n            md.name().c_str(),\n            md.md_pino(),\n            md.clientid().c_str(), md.clientuuid().c_str());\n\n  if (EOS_LOGS_DEBUG) {\n    std::string mdout = dump_message(md);\n    eos_debug(\"\\n%s\\n\", mdout.c_str());\n  }\n\n  // depending on the operation, prefetch into the namespace cache all\n  // metadata entries we'll need to service this request, _before_ acquiring\n  // the global namespace lock.\n  prefetchMD(md);\n\n  switch (md.operation()) {\n  case eos::fusex::md::OP::md_OP_BEGINFLUSH:\n    return OpBeginFlush(id, md, vid, response, clock);\n\n  case eos::fusex::md::OP::md_OP_ENDFLUSH:\n    return OpEndFlush(id, md, vid, response, clock);\n\n  case eos::fusex::md::OP::md_OP_GET:\n  case eos::fusex::md::OP::md_OP_LS:\n    return OpGetLs(id, md, vid, response, clock);\n\n  case eos::fusex::md::OP::md_OP_SET:\n    return OpSet(id, md, vid, response, clock);\n\n  case eos::fusex::md::OP::md_OP_DELETE:\n    return OpDelete(id, md, vid, response, clock);\n\n  case eos::fusex::md::OP::md_OP_GETCAP:\n    return OpGetCap(id, md, vid, response, clock);\n\n  case eos::fusex::md::OP::md_OP_GETLK:\n    return OpGetLock(id, md, vid, response, clock);\n\n  case eos::fusex::md::OP::md_OP_SETLK:\n  case eos::fusex::md::OP::md_OP_SETLKW:\n    return OpSetLock(id, md, vid, response, clock);\n\n  default:\n    break;\n  }\n\n  return 0;\n}\n\n//----------------------------------------------------------------------------\n// Replaces the file's non-system attributes with client-supplied ones.\n//----------------------------------------------------------------------------\n\nvoid\nServer::replaceNonSysAttributes(const std::shared_ptr<eos::IFileMD>& fmd,\n                                const eos::fusex::md& md)\n{\n  eos::IFileMD::XAttrMap xattrs = fmd->getAttributes();\n\n  // Remove all non-system attributes\n  for (const auto& attr : xattrs) {\n    if ((attr.first.substr(0, 3) != \"sys\")) {\n      fmd->removeAttribute(attr.first);\n    }\n  }\n\n  // Register non-system client-supplied attributes\n  for (const auto& attr : md.attr()) {\n    if ((attr.first.substr(0, 3) != \"sys\")) {\n      fmd->setAttribute(attr.first, attr.second);\n    }\n  }\n}\n\nEOSFUSESERVERNAMESPACE_END\n"
  },
  {
    "path": "mgm/FuseServer/Server.hh",
    "content": "// ----------------------------------------------------------------------\n// File: FuseServer/Server.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include \"mgm/Namespace.hh\"\n#include <thread>\n\n#include <map>\n#include <atomic>\n\n#include \"mgm/fusex.pb.h\"\n#include \"mgm/fuse-locks/LockTracker.hh\"\n#include \"mgm/FuseServer/Caps.hh\"\n#include \"mgm/FuseServer/Clients.hh\"\n#include \"mgm/FuseServer/Flush.hh\"\n#include \"mgm/FuseServer/Locks.hh\"\n\n#include \"namespace/interface/IFileMD.hh\"\n\nEOSFUSESERVERNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class FuseServer\n//------------------------------------------------------------------------------\nclass Server : public eos::common::LogId\n\n{\npublic:\n  Server();\n\n  ~Server();\n\n  void start();\n  void shutdown();\n\n  std::string dump_message(const google::protobuf::Message& message);\n\n  Clients& Client()\n  {\n    return mClients;\n  }\n\n  Caps& Cap()\n  {\n    return mCaps;\n  }\n\n  Lock& Locks()\n  {\n    return mLocks;\n  }\n\n  Flush& Flushs()\n  {\n    return mFlushs;\n  }\n\n  void Print(std::string& out, std::string options = \"\");\n\n  int FillContainerMD(uint64_t id, eos::fusex::md& dir,\n                      eos::common::VirtualIdentity& vid, bool lock=true);\n\n  bool FillFileMD(uint64_t id, eos::fusex::md& file,\n                  eos::common::VirtualIdentity& vid, bool lock=true);\n\n  bool FillContainerCAP(uint64_t id, eos::fusex::md& md,\n                        eos::common::VirtualIdentity& vid,\n                        std::string reuse_uuid = \"\",\n                        bool issue_only_one = false);\n\n  Caps::shared_cap ValidateCAP(const eos::fusex::md& md, mode_t mode,\n                               eos::common::VirtualIdentity& vid);\n  bool ValidatePERM(const eos::fusex::md& md, const std::string& mode,\n                    eos::common::VirtualIdentity& vid,\n                    bool lock = true);\n\n  uint64_t InodeFromCAP(const eos::fusex::md&);\n\n  std::string Header(const std::string& response); // reply a sync-response header\n\n\n  int OpBeginFlush(const std::string& identity,\n                   const eos::fusex::md& md,\n                   eos::common::VirtualIdentity& vid,\n                   std::string* response = 0,\n                   uint64_t* clock = 0);\n\n\n  int OpEndFlush(const std::string& identity,\n                 const eos::fusex::md& md,\n                 eos::common::VirtualIdentity& vid,\n                 std::string* response = 0,\n                 uint64_t* clock = 0);\n\n  int OpGetLs(const std::string& identity,\n              const eos::fusex::md& md,\n              eos::common::VirtualIdentity& vid,\n              std::string* response = 0,\n              uint64_t* clock = 0);\n\n  int OpSet(const std::string& identity,\n            const eos::fusex::md& md,\n            eos::common::VirtualIdentity& vid,\n            std::string* response = 0,\n            uint64_t* clock = 0);\n\n  int OpSetLink(const std::string& identity,\n                const eos::fusex::md& md,\n                eos::common::VirtualIdentity& vid,\n                std::string* response = 0,\n                uint64_t* clock = 0);\n\n  int OpSetFile(const std::string& identity,\n                const eos::fusex::md& md,\n                eos::common::VirtualIdentity& vid,\n                std::string* response = 0,\n                uint64_t* clock = 0);\n\n  int OpSetDirectory(const std::string& identity,\n                     const eos::fusex::md& md,\n                     eos::common::VirtualIdentity& vid,\n                     std::string* response = 0,\n                     uint64_t* clock = 0);\n\n\n\n  int OpGetCap(const std::string& identity,\n               const eos::fusex::md& md,\n               eos::common::VirtualIdentity& vid,\n               std::string* response = 0,\n               uint64_t* clock = 0);\n\n  int OpDelete(const std::string& identity,\n               const eos::fusex::md& md,\n               eos::common::VirtualIdentity& vid,\n               std::string* response = 0,\n               uint64_t* clock = 0);\n\n  int OpDeleteFile(const std::string& identity,\n                   const eos::fusex::md& md,\n                   eos::common::VirtualIdentity& vid,\n                   std::string* response = 0,\n                   uint64_t* clock = 0);\n\n  int OpDeleteDirectory(const std::string& identity,\n                        const eos::fusex::md& md,\n                        eos::common::VirtualIdentity& vid,\n                        std::string* response = 0,\n                        uint64_t* clock = 0);\n\n  int OpDeleteLink(const std::string& identity,\n                   const eos::fusex::md& md,\n                   eos::common::VirtualIdentity& vid,\n                   std::string* response = 0,\n                   uint64_t* clock = 0);\n\n  int OpGetLock(const std::string& identity,\n                const eos::fusex::md& md,\n                eos::common::VirtualIdentity& vid,\n                std::string* response = 0,\n                uint64_t* clock = 0);\n\n\n  int OpSetLock(const std::string& identity,\n                const eos::fusex::md& md,\n                eos::common::VirtualIdentity& vid,\n                std::string* response = 0,\n                uint64_t* clock = 0);\n\n  int HandleMD(const std::string& identity,\n               const eos::fusex::md& md,\n               eos::common::VirtualIdentity& vid,\n               std::string* response = 0,\n               uint64_t* clock = 0);\n\n  void prefetchMD(const eos::fusex::md& md);\n\n  bool CheckRecycleBinOrVersion(std::shared_ptr<eos::IFileMD> fmd);\n\n  void\n  MonitorCaps() noexcept;\n\n  bool should_terminate()\n  {\n    return terminate_.load();\n  } // check if threads should terminate\n\n  void terminate()\n  {\n    terminate_.store(true, std::memory_order_seq_cst);\n  } // indicate to terminate\n\n  static const char* cident;\n\nprotected:\n  Clients mClients;\n  Caps mCaps;\n  Lock mLocks;\n  Flush mFlushs;\n\nprivate:\n  std::atomic<bool> terminate_;\n  uint64_t c_max_children;\n\n  //----------------------------------------------------------------------------\n  //! Replaces the file's non-system attributes with client-supplied ones.\n  //!\n  //! @param fmd file metadata object\n  //! @param md client supplied metadata\n  //----------------------------------------------------------------------------\n  void replaceNonSysAttributes(const std::shared_ptr<eos::IFileMD>& fmd,\n                               const eos::fusex::md& md);\n};\n\n\nEOSFUSESERVERNAMESPACE_END\n"
  },
  {
    "path": "mgm/Namespace.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Namespace.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_NAMESPACE_HH__\n#define __EOSMGM_NAMESPACE_HH__\n\n#define USE_EOSMGMNAMESPACE using namespace eos::mgm;\n\n#define EOSMGMNAMESPACE_BEGIN namespace eos { namespace mgm {\n#define EOSMGMNAMESPACE_END }}\n\n\n#define USE_EOSFUSESERVERNAMESPACE using namespace eos::mgm::FuseServer;\n\n#define EOSFUSESERVERNAMESPACE_BEGIN namespace eos { namespace mgm { namespace FuseServer {\n#define EOSFUSESERVERNAMESPACE_END }}}\n\n#define EOSTGCNAMESPACE_BEGIN namespace eos { namespace mgm { namespace tgc {\n#define EOSTGCNAMESPACE_END }}}\n\n#define USE_EOSBULKNAMESPACE using namespace eos::mgm::bulk;\n\n#define EOSBULKNAMESPACE_BEGIN namespace eos { namespace mgm { namespace bulk {\n#define EOSBULKNAMESPACE_END }}}\n\n#define USE_EOSMGMRESTNAMESPACE using namespace eos::mgm::rest;\n\n#define EOSMGMRESTNAMESPACE_BEGIN namespace eos { namespace mgm { namespace rest {\n#define EOSMGMRESTNAMESPACE_END }}}\n\n#endif\n"
  },
  {
    "path": "mgm/README.md",
    "content": "## MGM source layout (management service)\n\nThis directory contains the EOS MGM (Management) service sources. The code is organized into focused subdirectories. The most important pieces:\n\n### OFS (XRootD plugin)\n- `ofs/`: XRootD OFS plugin implementation (entry points and integration).\n  - `ofs/XrdMgmOfs.cc`: main OFS plugin implementation.\n  - `ofs/XrdMgmOfs*.hh`: OFS headers (trace, security, file helpers).\n  - `ofs/cmds/`: high‑level OFS commands (access, metadata ops, routing, etc.), each command implemented as a separate translation unit included by `XrdMgmOfs.cc`.\n  - `ofs/fsctl/`: low‑level fsctl handlers (open, readlink, stat, mkdir, utimes, commit helper, …).\n- `authz/`: OFS authorization integration (`XrdMgmAuthz.*`), used by the plugin.\n\n### Core MGM logic\n- `access/`, `acl/`: access checks and ACL handling.\n- `filesystem/`, `fsview/`: filesystem abstraction, cluster/FS view.\n- `policy/`, `quota/`, `scheduler/`: placement, quota and scheduling logic.\n- `pathrouting/`, `routeendpoint/`: namespace path routing endpoints.\n- `groupbalancer/`, `groupdrainer/`, `balancer/`: balancing and draining logic.\n- `recycle/`: recycle bin implementation and policies.\n- `namespacestats/`, `inspector/`: namespace statistics and inspection tools.\n- `utils/`: internal utilities (registries, helpers, status).\n- `misc/`: shared MGM helpers and constants (e.g. `AuditHelpers.hh`, `Constants.hh`, `IdTrackerWithValidity.hh`).\n- `xattr/`: XAttr helper headers (`XattrLock.hh`, `XattrSet.hh`).\n\n### Frontends and protocols\n- `grpc/`: gRPC interfaces/servers for MGM.\n- `http/`: HTTP/REST handlers and models.\n\n### Command processing\n- `proc/`: MGM console/protocol command implementations (admin/user/proc_*).\n- `commandmap/`: command table and dispatching.\n\n### Other notable areas\n- `convert/`: data conversion pipeline elements.\n- `egroup/`, `devices/`, `drain/`, `fsck/`: service‑specific subsystems.\n\n### FUSE support\n- `FuseServer/`: FUSE server handlers (caps, clients, locks, flush, server).\n- `fuse-locks/`: FUSE lock tracking helpers.\n\n### Directory index (quick reference)\n- Core logic: `access/`, `acl/`, `filesystem/`, `fsview/`, `policy/`, `quota/`, `scheduler/`,\n  `pathrouting/`, `routeendpoint/`, `groupbalancer/`, `groupdrainer/`, `balancer/`,\n  `recycle/`, `namespacestats/`, `inspector/`, `utils/`, `misc/`, `xattr/`,\n  `messaging/`, `iostat/`, `lru/`, `imaster/`, `inflighttracker/`, `stat/`\n- OFS plugin: `ofs/`, `ofs/cmds/`, `ofs/fsctl/`, `authz/`\n- Frontends: `grpc/`, `http/`\n- Commands: `proc/`, `commandmap/`\n- Subsystems: `convert/`, `devices/`, `drain/`, `egroup/`, `fsck/`, `qdbmaster/`,\n  `qos/`, `recycle/`, `tracker/`, `placement/`\n- Structures/trees: `geotree/`, `geotreeengine/`, `geobalancer/`\n- Other: `FuseServer/`, `fuse-locks/`, `workflow/`, `vid/`, `xattr/`\n\n### CTA Implementation\n- `cta/`: CTA related sources (will appear when the CTA branch is merged)\n\n### Build notes\n- Include paths are set in `mgm/CMakeLists.txt` to expose:\n  - project root (`${CMAKE_SOURCE_DIR}`),\n  - common includes,\n  - `mgm/`, `mgm/ofs/` and subfolders.\n- OFS sources are compiled from `ofs/`, `ofs/cmds/` and `ofs/fsctl/`.\n\nThis structure separates the XRootD plugin surface (ofs) from core MGM logic and service subsystems, making responsibilities clear and keeping build boundaries clean. \n\n"
  },
  {
    "path": "mgm/access/Access.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Access.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"common/StringConversion.hh\"\n#include <regex.h>\n\nEOSMGMNAMESPACE_BEGIN\n\n//! singleton set for banned user IDs\nstd::set<uid_t> Access::gBannedUsers;\n\n//! singleton set for banned group IDS\nstd::set<gid_t> Access::gBannedGroups;\n\n//! singleton set for banned host names\nstd::set<std::string> Access::gBannedHosts;\n\n//! singleton set for banned host names\nstd::set<std::string> Access::gBannedDomains;\n\n//! singleton set for banned host names\nstd::set<std::string> Access::gBannedTokens;\n\n//! singleton set for allowed user IDs\nstd::set<uid_t> Access::gAllowedUsers;\n\n//! singleton set for allowed group IDs\nstd::set<gid_t> Access::gAllowedGroups;\n\n//! singleton set for allowed host names\nstd::set<std::string> Access::gAllowedHosts;\n\n//! singleton set for allowed host domains\nstd::set<std::string> Access::gAllowedDomains;\n\n//! singleton set for allowed host domains\nstd::set<std::string> Access::gAllowedTokens;\n\n//! singleton map for redirection rules\nstd::map<std::string, std::string> Access::gRedirectionRules;\n\n//! singleton map for stall rules\nstd::map<std::string, std::string> Access::gStallRules;\n\n//! singleton map for stall comments\nstd::map<std::string, std::string> Access::gStallComment;\n\n//! indicates a list of hostname matching\nstd::set<std::string> Access::gStallHosts;\n\n//! indicates a list of hostname matching                                                                                                                                                          a\nstd::set<std::string> Access::gNoStallHosts;\n\n//! indicates global stall rule\nstd::atomic<bool> Access::gStallGlobal {false};\n\n//! indicates global read stall\nstd::atomic<bool> Access::gStallRead {false};\n\n//! indicates global write stall\nstd::atomic<bool> Access::gStallWrite {false};\n\n//! indicates a user or group rate stall entry\nstd::atomic<bool> Access::gStallUserGroup {false};\n\n//! singleton map for UID based redirection (not used yet)\nstd::map<uid_t, std::string> Access::gUserRedirection;\n\n//! singleton map for GID based redirection (not used yet)\nstd::map<gid_t, std::string> Access::gGroupRedirection;\n\n//! global rw mutex protecting all static singletons\neos::common::RWMutex Access::gAccessMutex;\n\n/*----------------------------------------------------------------------------*/\n//! constant used in the configuration store\nconst char* Access::gUserKey = \"BanUsers\";\n\n//! constant used in the configuration store\nconst char* Access::gGroupKey = \"BanGroups\";\n\n//! constant used in the configuration store\nconst char* Access::gHostKey = \"BanHosts\";\n\n//! constant used in the configuration store\nconst char* Access::gDomainKey = \"BanDomains\";\n\n//! constant used in the configuration store\nconst char* Access::gTokenKey = \"BanTokens\";\n\n//! constant used in the configuration store\nconst char* Access::gAllowedUserKey = \"AllowedUsers\";\n\n//! constant used in the configuration store\nconst char* Access::gAllowedGroupKey = \"AllowedGroups\";\n\n//! constant used in the configuration store\nconst char* Access::gAllowedHostKey = \"AllowedHosts\";\n\n//! constant used in the configuration store\nconst char* Access::gAllowedDomainKey = \"AllowedDomains\";\n\n//! constant used in the configuration store\nconst char* Access::gAllowedTokenKey = \"AllowedTokens\";\n\n//! constant used in the configuration store\nconst char* Access::gStallKey = \"Stall\";\n\n//! constant used in the configuration store\nconst char* Access::gRedirectionKey = \"Redirection\";\n\n//! constant used in the configuration store\nconst char* Access::gStallHostsKey = \"StallHosts\";\n\n//! constant used in the configuration store\nconst char* Access::gNoStallHostsKey = \"NoStallHosts\";\n\n//------------------------------------------------------------------------------\n// Static function to reset all singleton objects defining access rules.\n//------------------------------------------------------------------------------\nvoid\nAccess::Reset(bool skip_stall_redirect)\n{\n  eos_static_debug(\"%s\", \"msg=\\\"reset all access rules\\\"\");\n  eos::common::RWMutexWriteLock lock(Access::gAccessMutex);\n  Access::gBannedUsers.clear();\n  Access::gBannedGroups.clear();\n  Access::gBannedHosts.clear();\n  Access::gBannedDomains.clear();\n  Access::gBannedTokens.clear();\n  Access::gAllowedUsers.clear();\n  Access::gAllowedGroups.clear();\n  Access::gAllowedHosts.clear();\n  Access::gAllowedDomains.clear();\n  Access::gAllowedTokens.clear();\n  Access::gStallHosts.clear();\n  Access::gNoStallHosts.clear();\n\n  if (skip_stall_redirect == false) {\n    Access::gRedirectionRules.clear();\n    Access::gStallRules.clear();\n    Access::gStallComment.clear();\n    Access::gUserRedirection.clear();\n    Access::gGroupRedirection.clear();\n    Access::gStallGlobal = Access::gStallRead =\n                             Access::gStallWrite = Access::gStallUserGroup = false;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Static function to retrieve the access configuration from the global\n * configuration and apply to the static singleton rules.\n */\n/*----------------------------------------------------------------------------*/\nvoid\nAccess::EnforceConfig(bool applyredirectandstall)\n{\n  Access::Reset(!applyredirectandstall);\n  eos::common::RWMutexWriteLock lock(Access::gAccessMutex);\n  std::string userval = FsView::gFsView.GetGlobalConfig(gUserKey);\n  std::string groupval = FsView::gFsView.GetGlobalConfig(gGroupKey);\n  std::string hostval = FsView::gFsView.GetGlobalConfig(gHostKey);\n  std::string domainval = FsView::gFsView.GetGlobalConfig(gDomainKey);\n  std::string tokenval = FsView::gFsView.GetGlobalConfig(gTokenKey);\n  std::string useraval = FsView::gFsView.GetGlobalConfig(gAllowedUserKey);\n  std::string groupaval = FsView::gFsView.GetGlobalConfig(gAllowedGroupKey);\n  std::string hostaval = FsView::gFsView.GetGlobalConfig(gAllowedHostKey);\n  std::string domainaval = FsView::gFsView.GetGlobalConfig(gAllowedDomainKey);\n  std::string tokenaval = FsView::gFsView.GetGlobalConfig(gAllowedTokenKey);\n  std::string stall = FsView::gFsView.GetGlobalConfig(gStallKey);\n  std::string redirect = FsView::gFsView.GetGlobalConfig(gRedirectionKey);\n  std::string stallhosts = FsView::gFsView.GetGlobalConfig(gStallHostsKey);\n  std::string nostallhosts = FsView::gFsView.GetGlobalConfig(gNoStallHostsKey);\n  // parse the list's and fill the hash\n  std::vector<std::string> tokens;\n  std::string delimiter = \":\";\n  std::vector<std::string> subtokens;\n  std::string subdelimiter = \"~\";\n  tokens.clear();\n  eos::common::StringConversion::Tokenize(userval, tokens, delimiter);\n\n  for (size_t i = 0; i < tokens.size(); i++) {\n    if (tokens[i].length()) {\n      uid_t uid = atoi(tokens[i].c_str());\n      Access::gBannedUsers.insert(uid);\n    }\n  }\n\n  tokens.clear();\n  eos::common::StringConversion::Tokenize(groupval, tokens, delimiter);\n\n  for (size_t i = 0; i < tokens.size(); i++) {\n    if (tokens[i].length()) {\n      gid_t gid = atoi(tokens[i].c_str());\n      Access::gBannedGroups.insert(gid);\n    }\n  }\n\n  tokens.clear();\n  eos::common::StringConversion::Tokenize(hostval, tokens, delimiter);\n\n  for (size_t i = 0; i < tokens.size(); i++) {\n    if (tokens[i].length()) {\n      Access::gBannedHosts.insert(tokens[i]);\n    }\n  }\n\n  tokens.clear();\n  eos::common::StringConversion::Tokenize(domainval, tokens, delimiter);\n\n  for (size_t i = 0; i < tokens.size(); i++) {\n    if (tokens[i].length()) {\n      Access::gBannedDomains.insert(tokens[i]);\n    }\n  }\n\n  tokens.clear();\n  eos::common::StringConversion::Tokenize(tokenval, tokens, delimiter);\n\n  for (size_t i = 0; i < tokens.size(); i++) {\n    if (tokens[i].length()) {\n      Access::gBannedTokens.insert(tokens[i]);\n    }\n  }\n\n  tokens.clear();\n  eos::common::StringConversion::Tokenize(useraval, tokens, delimiter);\n\n  for (size_t i = 0; i < tokens.size(); i++) {\n    if (tokens[i].length()) {\n      uid_t uid = atoi(tokens[i].c_str());\n      Access::gAllowedUsers.insert(uid);\n    }\n  }\n\n  tokens.clear();\n  eos::common::StringConversion::Tokenize(groupaval, tokens, delimiter);\n\n  for (size_t i = 0; i < tokens.size(); i++) {\n    if (tokens[i].length()) {\n      gid_t gid = atoi(tokens[i].c_str());\n      Access::gAllowedGroups.insert(gid);\n    }\n  }\n\n  tokens.clear();\n  eos::common::StringConversion::Tokenize(hostaval, tokens, delimiter);\n\n  for (size_t i = 0; i < tokens.size(); i++) {\n    if (tokens[i].length()) {\n      Access::gAllowedHosts.insert(tokens[i]);\n    }\n  }\n\n  tokens.clear();\n  eos::common::StringConversion::Tokenize(domainaval, tokens, delimiter);\n\n  for (size_t i = 0; i < tokens.size(); i++) {\n    if (tokens[i].length()) {\n      Access::gAllowedDomains.insert(tokens[i]);\n    }\n  }\n\n  tokens.clear();\n  eos::common::StringConversion::Tokenize(tokenaval, tokens, delimiter);\n\n  for (size_t i = 0; i < tokens.size(); i++) {\n    if (tokens[i].length()) {\n      Access::gAllowedTokens.insert(tokens[i]);\n    }\n  }\n\n  tokens.clear();\n  eos::common::StringConversion::Tokenize(stallhosts, tokens, delimiter);\n\n  for (size_t i = 0; i < tokens.size(); i++) {\n    if (tokens[i].length()) {\n      Access::gStallHosts.insert(tokens[i]);\n    }\n  }\n\n  tokens.clear();\n  eos::common::StringConversion::Tokenize(nostallhosts, tokens, delimiter);\n\n  for (size_t i = 0; i < tokens.size(); i++) {\n    if (tokens[i].length()) {\n      Access::gNoStallHosts.insert(tokens[i]);\n    }\n  }\n\n  tokens.clear();\n  delimiter = \",\";\n  eos::common::StringConversion::Tokenize(stall, tokens, delimiter);\n\n  for (size_t i = 0; i < tokens.size(); ++i) {\n    if (tokens[i].length()) {\n      if (applyredirectandstall || ((tokens[i].find(\"rate:\") == 0) ||\n                                    (tokens[i].find(\"threads:\") == 0))) {\n        subtokens.clear();\n        eos::common::StringConversion::Tokenize(tokens[i], subtokens,\n                                                subdelimiter);\n\n        if (subtokens.size() >= 2) {\n          Access::gStallRules[subtokens[0]] = subtokens[1];\n\n          if (subtokens[0] == (\"r:*\")) {\n            gStallRead = true;\n          }\n\n          if (subtokens[0] == (\"w:*\")) {\n            gStallWrite = true;\n          }\n\n          if (subtokens[0] == (\"*\")) {\n            gStallGlobal = true;\n          }\n\n          if ((subtokens[0].find(\"rate:\") == 0)) {\n            gStallUserGroup = true;\n          }\n\n          if (subtokens.size() == 3) {\n            XrdOucString comment = subtokens[2].c_str();\n\n            while (comment.replace(\"_#KOMMA#_\", \",\")) {\n            }\n\n            while (comment.replace(\"_#TILDE#_\", \"~\")) {\n            }\n\n            Access::gStallComment[subtokens[0]] = comment.c_str();\n          }\n        }\n      }\n    }\n  }\n\n  if (applyredirectandstall) {\n    tokens.clear();\n    delimiter = \",\";\n    eos::common::StringConversion::Tokenize(redirect, tokens, delimiter);\n\n    for (size_t i = 0; i < tokens.size(); i++) {\n      if (tokens[i].length()) {\n        subtokens.clear();\n        eos::common::StringConversion::Tokenize(tokens[i],\n                                                subtokens,\n                                                subdelimiter);\n\n        if (subtokens.size() == 2) {\n          Access::gRedirectionRules[subtokens[0]] = subtokens[1];\n        }\n      }\n    }\n  }\n}\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Static function to store all defined rules in the global configuration.\n *\n * @return true if successful, otherwise false\n */\n/*----------------------------------------------------------------------------*/\nbool\nAccess::StoreAccessConfig()\n\n{\n  std::set<uid_t>::const_iterator ituid;\n  std::set<gid_t>::const_iterator itgid;\n  std::set<std::string>::const_iterator ithost;\n  std::set<std::string>::const_iterator itdomain;\n  std::set<std::string>::const_iterator ittoken;\n  std::set<std::string>::const_iterator itstallhosts;\n  std::set<std::string>::const_iterator itnostallhosts;\n  std::map<std::string, std::string>::const_iterator itstall;\n  std::map<std::string, std::string>::const_iterator itredirect;\n  std::string userval = \"\";\n  std::string groupval = \"\";\n  std::string hostval = \"\";\n  std::string domainval = \"\";\n  std::string tokenval = \"\";\n  std::string useraval = \"\";\n  std::string groupaval = \"\";\n  std::string hostaval = \"\";\n  std::string domainaval = \"\";\n  std::string tokenaval = \"\";\n  std::string stall = \"\";\n  std::string redirect = \"\";\n  std::string stallhosts = \"\";\n  std::string nostallhosts = \"\";\n\n  for (ituid = Access::gBannedUsers.begin();\n       ituid != Access::gBannedUsers.end(); ituid++) {\n    userval += eos::common::Mapping::UidAsString(*ituid);\n    userval += \":\";\n  }\n\n  for (itgid = Access::gBannedGroups.begin();\n       itgid != Access::gBannedGroups.end(); itgid++) {\n    groupval += eos::common::Mapping::GidAsString(*itgid);\n    groupval += \":\";\n  }\n\n  for (ithost = Access::gBannedHosts.begin();\n       ithost != Access::gBannedHosts.end(); ithost++) {\n    hostval += ithost->c_str();\n    hostval += \":\";\n  }\n\n  for (itdomain = Access::gBannedDomains.begin();\n       itdomain != Access::gBannedDomains.end(); itdomain++) {\n    hostval += itdomain->c_str();\n    hostval += \":\";\n  }\n\n  for (ituid = Access::gAllowedUsers.begin();\n       ituid != Access::gAllowedUsers.end(); ituid++) {\n    useraval += eos::common::Mapping::UidAsString(*ituid);\n    useraval += \":\";\n  }\n\n  for (itgid = Access::gAllowedGroups.begin();\n       itgid != Access::gAllowedGroups.end(); itgid++) {\n    groupaval += eos::common::Mapping::GidAsString(*itgid);\n    groupaval += \":\";\n  }\n\n  for (ithost = Access::gAllowedHosts.begin();\n       ithost != Access::gAllowedHosts.end(); ithost++) {\n    hostaval += ithost->c_str();\n    hostaval += \":\";\n  }\n\n  for (itdomain = Access::gAllowedDomains.begin();\n       itdomain != Access::gAllowedDomains.end(); itdomain++) {\n    domainaval += itdomain->c_str();\n    domainaval += \":\";\n  }\n\n  for (ittoken = Access::gAllowedTokens.begin();\n       ittoken != Access::gAllowedTokens.end(); ittoken++) {\n    tokenaval += ittoken->c_str();\n    tokenaval += \":\";\n  }\n\n  for (itstallhosts = Access::gStallHosts.begin();\n       itstallhosts != Access::gStallHosts.end(); itstallhosts++) {\n    stallhosts += itstallhosts->c_str();\n    stallhosts += \":\";\n  }\n\n  for (itnostallhosts = Access::gNoStallHosts.begin();\n       itnostallhosts != Access::gNoStallHosts.end(); itnostallhosts++) {\n    nostallhosts += itnostallhosts->c_str();\n    nostallhosts += \":\";\n  }\n\n  gStallRead = gStallWrite = gStallGlobal = gStallUserGroup = false;\n\n  for (itstall = Access::gStallRules.begin();\n       itstall != Access::gStallRules.end(); itstall++) {\n    stall += itstall->first.c_str();\n    stall += \"~\";\n    stall += itstall->second.c_str();\n    stall += \"~\";\n    XrdOucString comment = Access::gStallComment[itstall->first].c_str();\n\n    while (comment.replace(\",\", \"_#KOMMA#_\")) {\n    }\n\n    while (comment.replace(\"~\", \"_#TILDE#_\")) {\n    }\n\n    stall += comment.c_str();\n    stall += \",\";\n\n    if (itstall->first == (\"r:*\")) {\n      gStallRead = true;\n    }\n\n    if (itstall->first == (\"w:*\")) {\n      gStallWrite = true;\n    }\n\n    if (itstall->first == (\"*\")) {\n      gStallGlobal = true;\n    }\n\n    if ((itstall->first.find(\"rate:\") == 0)) {\n      gStallUserGroup = true;\n    }\n  }\n\n  for (itredirect = Access::gRedirectionRules.begin();\n       itredirect != Access::gRedirectionRules.end(); itredirect++) {\n    redirect += itredirect->first.c_str();\n    redirect += \"~\";\n    redirect += itredirect->second.c_str();\n    redirect += \",\";\n  }\n\n  std::string ukey = gUserKey;\n  std::string gkey = gGroupKey;\n  std::string hkey = gHostKey;\n  std::string dkey = gDomainKey;\n  std::string tkey = gTokenKey;\n  std::string uakey = gAllowedUserKey;\n  std::string gakey = gAllowedGroupKey;\n  std::string hakey = gAllowedHostKey;\n  std::string dakey = gAllowedDomainKey;\n  std::string takey = gAllowedTokenKey;\n  std::string shkey = gStallHostsKey;\n  std::string nshkey = gNoStallHostsKey;\n  bool ok = 1;\n  ok &= FsView::gFsView.SetGlobalConfig(ukey, userval);\n  ok &= FsView::gFsView.SetGlobalConfig(gkey, groupval);\n  ok &= FsView::gFsView.SetGlobalConfig(hkey, hostval);\n  ok &= FsView::gFsView.SetGlobalConfig(dkey, domainval);\n  ok &= FsView::gFsView.SetGlobalConfig(tkey, tokenval);\n  ok &= FsView::gFsView.SetGlobalConfig(uakey, useraval);\n  ok &= FsView::gFsView.SetGlobalConfig(gakey, groupaval);\n  ok &= FsView::gFsView.SetGlobalConfig(hakey, hostaval);\n  ok &= FsView::gFsView.SetGlobalConfig(dakey, domainaval);\n  ok &= FsView::gFsView.SetGlobalConfig(takey, tokenaval);\n  ok &= FsView::gFsView.SetGlobalConfig(gStallKey, stall);\n  ok &= FsView::gFsView.SetGlobalConfig(gRedirectionKey, redirect);\n  ok &= FsView::gFsView.SetGlobalConfig(shkey, stallhosts);\n  ok &= FsView::gFsView.SetGlobalConfig(nshkey, nostallhosts);\n\n  if (!ok) {\n    eos_static_err(\"unable to store <access> configuration\");\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Get find limits in number of files/dirs returned for a certain user\n//------------------------------------------------------------------------------\nvoid\nAccess::GetFindLimits(const eos::common::VirtualIdentity& vid,\n                      uint64_t& dir_limit, uint64_t& file_limit)\n{\n  eos::common::RWMutexReadLock access_rd_lock(gAccessMutex);\n\n  if (gStallUserGroup) {\n    std::string usermatchfiles = \"rate:user:\";\n    usermatchfiles += vid.uid_string;\n    usermatchfiles += \":\";\n    usermatchfiles += \"FindFiles\";\n    std::string groupmatchfiles = \"rate:group:\";\n    groupmatchfiles += vid.gid_string;\n    groupmatchfiles += \":\";\n    groupmatchfiles += \"FindFiles\";\n    std::string userwildcardmatchfiles = \"rate:user:*:FindFiles\";\n\n    if (gStallRules.count(usermatchfiles)) {\n      file_limit = strtoul(gStallRules[usermatchfiles].c_str(), 0, 10);\n    } else if (gStallRules.count(groupmatchfiles)) {\n      file_limit = strtoul(gStallRules[groupmatchfiles].c_str(), 0, 10);\n    } else if (gStallRules.count(userwildcardmatchfiles)) {\n      file_limit = strtoul(gStallRules[userwildcardmatchfiles].c_str(), 0, 10);\n    }\n\n    std::string usermatchdirs = \"rate:user:\";\n    usermatchdirs += vid.uid_string;\n    usermatchdirs += \":\";\n    usermatchdirs += \"FindDirs\";\n    std::string groupmatchdirs = \"rate:group:\";\n    groupmatchdirs += vid.gid_string;\n    groupmatchdirs += \":\";\n    groupmatchdirs += \"FindDirs\";\n    std::string userwildcardmatchdirs = \"rate:user:*:FindDirs\";\n\n    if (gStallRules.count(usermatchdirs)) {\n      dir_limit = strtoul(gStallRules[usermatchdirs].c_str(), 0, 10);\n    } else if (gStallRules.count(groupmatchdirs)) {\n      dir_limit = strtoul(gStallRules[groupmatchdirs].c_str(), 0, 10);\n    } else if (gStallRules.count(userwildcardmatchdirs)) {\n      dir_limit = strtoul(gStallRules[userwildcardmatchdirs].c_str(), 0, 10);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Set global stall rule and save the previous status\n//------------------------------------------------------------------------------\nvoid\nAccess::SetStallRule(const StallInfo& new_stall, StallInfo& old_stall)\n{\n  eos::common::RWMutexWriteLock lock(Access::gAccessMutex);\n\n  if (new_stall.mType.empty()) {\n    return;\n  }\n\n  old_stall.mType = new_stall.mType;\n\n  if (Access::gStallRules.count(new_stall.mType)) {\n    old_stall.mDelay = Access::gStallRules[old_stall.mType];\n    old_stall.mComment = Access::gStallComment[old_stall.mType];\n    old_stall.mIsGlobal = Access::gStallGlobal;\n  }\n\n  if (new_stall.mDelay.empty()) {\n    Access::gStallRules.erase(new_stall.mType);\n  } else {\n    Access::gStallRules[new_stall.mType] = new_stall.mDelay;\n  }\n\n  if (new_stall.mComment.empty()) {\n    Access::gStallComment.erase(new_stall.mType);\n  } else {\n    Access::gStallComment[new_stall.mType] = new_stall.mComment;\n  }\n\n  Access::gStallGlobal = new_stall.mIsGlobal;\n}\n\n//------------------------------------------------------------------------------\n// Set access rules for a slave to master transition. More precisely remove\n// any stall and redirection rules\n//------------------------------------------------------------------------------\nvoid\nAccess::SetSlaveToMasterRules()\n{\n  eos_static_info(\"%s\", \"msg=\\\"remove any stall and redirection rules\\\"\");\n  eos::common::RWMutexWriteLock wr_lock(Access::gAccessMutex);\n  Access::gRedirectionRules.erase(std::string(\"w:*\"));\n  Access::gRedirectionRules.erase(std::string(\"ENOENT:*\"));\n  Access::gStallRules.erase(std::string(\"w:*\"));\n  Access::gStallWrite = false;\n}\n\n//----------------------------------------------------------------------------\n// Set access rules for a master to slave transition.\n//----------------------------------------------------------------------------\nvoid\nAccess::SetMasterToSlaveRules(const std::string& other_master_id)\n{\n  eos::common::RWMutexWriteLock wr_lock(Access::gAccessMutex);\n\n  if (other_master_id.empty()) {\n    // No master - remove redirections and put a stall for writes\n    eos_static_info(\"%s\", \"msg=\\\"no master, add global stall\\\"\");\n    Access::gRedirectionRules.erase(std::string(\"w:*\"));\n    Access::gRedirectionRules.erase(std::string(\"ENOENT:*\"));\n    Access::gStallRules[std::string(\"*\")] = \"60\";\n    Access::gStallWrite = true;\n    Access::gStallGlobal = true;\n  } else {\n    // We're the slave and there is a master - set redirection to him\n    eos_static_info(\"msg=\\\"add redirection to master %s\\\"\",\n                    other_master_id.c_str());\n    Access::gRedirectionRules[std::string(\"w:*\")] = other_master_id.c_str();\n    Access::gRedirectionRules[std::string(\"ENOENT:*\")] = other_master_id.c_str();\n    // Remove write stall\n    Access::gStallRules.erase(std::string(\"*\"));\n    Access::gStallRules.erase(std::string(\"w:*\"));\n    Access::gStallWrite = false;\n    Access::gStallGlobal = false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Remove stall rule specified by key\n//------------------------------------------------------------------------------\nvoid\nAccess::RemoveStallRule(const std::string& key)\n{\n  eos::common::RWMutexWriteLock wr_lock(Access::gAccessMutex);\n  Access::gStallRules.erase(key);\n\n  if (key.find(\"w:*\") == 0) {\n    Access::gStallWrite = false;\n  } else if (key.find(\"r:*\") == 0) {\n    Access::gStallRead = false;\n  } else if (key.find(\"*\") == 0) {\n    Access::gStallGlobal = false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Thread limit by user id\n//------------------------------------------------------------------------------\nsize_t\nAccess::ThreadLimit(uid_t uid, bool lock_ns)\n{\n  std::string id = \"threads:\" + std::to_string(uid);\n  eos::common::RWMutexReadLock access_rd_lock;\n\n  if (lock_ns) {\n    access_rd_lock.Grab(gAccessMutex);\n  }\n\n  if (gStallRules.count(id.c_str())) {\n    return strtoul(gStallRules[id.c_str()].c_str(), 0, 10);\n  } else {\n    if (gStallRules.count(\"threads:*\")) {\n      return strtoul(gStallRules[\"threads:*\"].c_str(), 0, 10);\n    }\n  }\n\n  return 65536;\n}\n\n//------------------------------------------------------------------------------\n// Global thread limit\n//------------------------------------------------------------------------------\nsize_t\nAccess::ThreadLimit(bool lock_ns)\n{\n  eos::common::RWMutexReadLock access_rd_lock;\n\n  if (lock_ns) {\n    access_rd_lock.Grab(gAccessMutex);\n  }\n\n  if (gStallRules.count(\"threads:max\")) {\n    return strtoul(gStallRules[\"threads:max\"].c_str(), 0, 10);\n  }\n\n  return 1000000;\n}\n\n//------------------------------------------------------------------------------\n// Check if host matches white or black list for stalling\n//------------------------------------------------------------------------------\nbool\nAccess::CanStall(const char* host)\n{\n  // you should have this lock   eos::common::RWMutexReadLock access_rd_lock(gAccessMutex);\n  if (gStallHosts.size()) {\n    // white list behaviour\n    for (auto rules : gStallHosts) {\n      regex_t regex;\n\n      if (!regcomp(&regex, rules.c_str(), REG_ICASE | REG_EXTENDED | REG_NOSUB)) {\n        if (!regexec(&regex, host, 0, NULL, 0)) {\n          regfree(&regex);\n          return true;\n        } else {\n          regfree(&regex);\n        }\n      }\n    }\n\n    return false;\n  }\n\n  if (gNoStallHosts.size()) {\n    // black list haviour\n    for (auto rules : gNoStallHosts) {\n      regex_t regex;\n\n      if (!regcomp(&regex, rules.c_str(), REG_ICASE | REG_EXTENDED | REG_NOSUB)) {\n        if (!regexec(&regex, host, 0, NULL, 0)) {\n          regfree(&regex);\n          return false;\n        } else {\n          regfree(&regex);\n        }\n      }\n    }\n\n    return true;\n  }\n\n  return true;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/access/Access.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Access.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! Class defining access rules like banned users, hosts, stall rules etc.\n//------------------------------------------------------------------------------\n\n#ifndef __EOSCOMMON_ACCESS__\n#define __EOSCOMMON_ACCESS__\n\n#include \"mgm/Namespace.hh\"\n#include \"common/RWMutex.hh\"\n#include \"common/Mapping.hh\"\n#include <map>\n#include <vector>\n#include <string>\n#include <set>\n\nEOSMGMNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\n/** @brief class implementing global access rules\n *\n * The access regulations are applied in XrdMgmOfs::ShouldStall &\n * XrdMgmOfs::ShouldRedirect functions.\\n\n * Normally User,Group & Host rules act as black-list and the AllowedXX rules\n * exclude individuals from the black list.\\n\\n\n * The stall rules can be:\\n\n * '*' => everything get's stalled by number of seconds stored\n * in gStallRules[\"*\"]\\n\n * 'r:*\" => everything get's stalled in read operations as above.\\n\n *'w:*\" => everything get's stalled in write operations as above.\\n\\n\n * The same syntax is used in gRedirectionRules to define r+w,\n * r or w operation redirection.\n * The value in this map is defined as '<host>:<port>'\n */\n/*----------------------------------------------------------------------------*/\nclass Access\n{\npublic:\n\n  // static key defining the ban users entry in the global configuration\n  // key-value map\n  static const char* gUserKey;\n\n  // static key defining the ban group key in the global configuration\n  // key-value map\n  static const char* gGroupKey;\n\n  // static key defining the ban host key in the global configuration\n  // key-value map\n  static const char* gHostKey;\n\n  // static key defining the ban domain key in the global configuration\n  // key-value map\n  static const char* gDomainKey;\n\n  // static key defining the ban token key in the global configuration\n  // key-value map\n  static const char* gTokenKey;\n\n  // static key defining the allowed users key in the global configuration\n  // key-value map\n  static const char* gAllowedUserKey;\n\n  // static key defining the allowed group key in the global configuration\n  // key-value map\n  static const char* gAllowedGroupKey;\n\n  // static key defining the allowed host key in the global configuration\n  // key-value map\n  static const char* gAllowedHostKey;\n\n  // static key defining the allowed domain key in the global configuration\n  // key-value map\n  static const char* gAllowedDomainKey;\n\n  // static key defining the allowed tokens key in the global configuration\n  // key-value map\n  static const char* gAllowedTokenKey;\n\n  // static key defining the stall rules in the global configuration\n  // key-value map\n  static const char* gStallKey;\n\n  // static key defining the redirection rules in the global configuration\n  // key-value map\n  static const char* gRedirectionKey;\n\n  // static key defining the hosts which are eligible for stalling (whitelist)\n  // set\n  static const char* gStallHostsKey;\n\n  // static key defining the hosts which are not eligible for stalling (blacklist)\n  // set\n  static const char* gNoStallHostsKey;\n\n  // set containing the banned user ID\n  static std::set<uid_t> gBannedUsers;\n\n  //! set containing the banned group ID\n  static std::set<gid_t> gBannedGroups;\n\n  //! set containing the allowed host IDs\n  static std::set<uid_t> gAllowedUsers;\n\n  //! set containing the allowed group IDs\n  static std::set<gid_t> gAllowedGroups;\n\n  //! set containing the banned host names\n  static std::set<std::string> gBannedHosts;\n\n  //! set containing the banned domain names\n  static std::set<std::string> gBannedDomains;\n\n  //! set containing the banned token names\n  static std::set<std::string> gBannedTokens;\n\n  //! set containing the allowed host names\n  static std::set<std::string> gAllowedHosts;\n\n  //! set containing the allowed domain IDs\n  static std::set<std::string> gAllowedDomains;\n\n  //! set containing the allowed token IDs\n  static std::set<std::string> gAllowedTokens;\n\n  //! map containing redirection rules\n  static std::map<std::string, std::string> gRedirectionRules;\n\n  //! map containing stall rules\n  static std::map<std::string, std::string> gStallRules;\n\n  //! map containint stall message comment\n  static std::map<std::string, std::string> gStallComment;\n\n  //! indicates global stall rule\n  static std::atomic<bool> gStallGlobal;\n\n  //! indicates global read stall\n  static std::atomic<bool> gStallRead;\n\n  //! indicates global write stall\n  static std::atomic<bool> gStallWrite;\n\n  //! indicates a user or group rate stall entry\n  static std::atomic<bool> gStallUserGroup;\n\n  //! indicates a list of hostname matching\n  static std::set<std::string> gNoStallHosts;\n\n  //! indicates a list of hostname matching\n  static std::set<std::string> gStallHosts;\n\n  //! map containing user based redirection\n  static std::map<uid_t, std::string> gUserRedirection;\n\n  //! map containing group based redirection\n  static std::map<gid_t, std::string> gGroupRedirection;\n\n  //! global rw mutex protecting all static set's and maps in Access\n  static eos::common::RWMutex gAccessMutex;\n\n  //----------------------------------------------------------------------------\n  //! Struct holding stall info\n  //----------------------------------------------------------------------------\n  struct StallInfo {\n    std::string mType;\n    std::string mDelay;\n    std::string mComment;\n    bool mIsGlobal;\n\n    //--------------------------------------------------------------------------\n    //! Default constructor\n    //--------------------------------------------------------------------------\n    StallInfo(const std::string& type = \"\", const std::string delay = \"\",\n              const std::string& comment = \"\", bool is_global = false):\n      mType(type), mDelay(delay), mComment(comment), mIsGlobal(is_global)\n    {}\n  };\n\n  //----------------------------------------------------------------------------\n  //! Reset/clear all access rules\n  //!\n  //! @param skip_stall_redirect if true don't reset the global stall and\n  //!        redirect rules\n  //----------------------------------------------------------------------------\n  static void Reset(bool skip_stall_redirect = false);\n\n  // ---------------------------------------------------------------------------\n  // retrieve the access configuration from the global shared\n  // hash/config engine and fill all static access configuration variables\n  // ---------------------------------------------------------------------------\n  static void EnforceConfig(bool applystallandredirection = true);\n\n  // ---------------------------------------------------------------------------\n  // store the global access configuration variable into the global\n  // shared hash/config engine\n  // ---------------------------------------------------------------------------\n  static bool StoreAccessConfig();\n\n  //----------------------------------------------------------------------------\n  //! Get find limits in number of files/dirs returned for a certain user\n  //!\n  //! @param vid virtual identity of the client\n  //! @param dir_limit number of directories limit\n  //! @param file_limit number of files limit\n  //----------------------------------------------------------------------------\n  static void GetFindLimits(const eos::common::VirtualIdentity& vid,\n                            uint64_t& dir_limit, uint64_t& file_limit);\n\n  //----------------------------------------------------------------------------\n  //! Set global stall rule and save the previous status\n  //!\n  //! @param new_stall stall rule to be applied\n  //! @param old_stall old stall rule if present\n  //----------------------------------------------------------------------------\n  static void\n  SetStallRule(const StallInfo& new_stall, StallInfo& old_stall);\n\n  //----------------------------------------------------------------------------\n  //! Set access rules for a slave to master transition. More precisely remove\n  //! any stall and redirection rules\n  //----------------------------------------------------------------------------\n  static void SetSlaveToMasterRules();\n\n  //----------------------------------------------------------------------------\n  //! Set access rules for a master to slave transition.\n  //!\n  //! @param other_master_id newly assigned master identity <hostname>:<port>\n  //----------------------------------------------------------------------------\n  static void SetMasterToSlaveRules(const std::string& other_master_id);\n\n  //----------------------------------------------------------------------------\n  //! Remove stall rule specified by key\n  //!\n  //! @param key stall rule key\n  //----------------------------------------------------------------------------\n  static void RemoveStallRule(const std::string& key);\n\n  //----------------------------------------------------------------------------\n  //! Get Thread limit (by uid)\n  //----------------------------------------------------------------------------\n  static size_t ThreadLimit(uid_t uid, bool lock_ns = true);\n  static size_t ThreadLimit(bool lock_ns = true);\n\n  //----------------------------------------------------------------------------\n  //! Check if the given host can be stalled according to white and blacklist settings\n  //----------------------------------------------------------------------------\n  static bool CanStall(const char* host);\n};\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/acl/Acl.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file Acl.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/acl/Acl.hh\"\n#include \"mgm/egroup/Egroup.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"common/StringConversion.hh\"\n#include <regex.h>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Constructor\n//------------------------------------------------------------------------------\nAcl::Acl(std::string sysacl, std::string useracl,\n         const eos::common::VirtualIdentity& vid, bool allowUserAcl)\n{\n  std::string tokenacl = TokenAcl(vid);\n  Set(sysacl, useracl, tokenacl, vid, allowUserAcl);\n}\n\n//------------------------------------------------------------------------------\n//! Constructor\n//!\n//! @param attr map containing all extended attributes\n//! @param vid virtual id to match ACL\n//------------------------------------------------------------------------------\nAcl::Acl(const eos::IContainerMD::XAttrMap& attrmap,\n         const eos::common::VirtualIdentity& vid)\n{\n  // define the acl rules from the attributes\n  SetFromAttrMap(attrmap, vid);\n}\n\n//------------------------------------------------------------------------------\n//! Constructor by path\n//------------------------------------------------------------------------------\nAcl::Acl(const char* path, XrdOucErrInfo& error,\n         const eos::common::VirtualIdentity& vid,\n         eos::IContainerMD::XAttrMap& attrmap)\n{\n  if (path && strlen(path)) {\n    int rc = gOFS->_attr_ls(path, error, vid, 0, attrmap);\n\n    if (rc) {\n      eos_static_info(\"attr-ls failed: path=%s errno=%d\", path, errno);\n    }\n  }\n\n  // Set the acl rules from the attributes\n  SetFromAttrMap(attrmap, vid);\n}\n\n//------------------------------------------------------------------------------\n// Set Acls by interpreting the attribute map\n//------------------------------------------------------------------------------\nvoid\nAcl::SetFromAttrMap(const eos::IContainerMD::XAttrMap& attrmap,\n                    const eos::common::VirtualIdentity& vid,\n                    eos::IFileMD::XAttrMap* attrmapF, bool sysaclOnly)\n{\n  std::string usr_acl;\n  bool eval_usr_acl = false;\n  mEvalFileUserAcl = false;\n\n  if (!sysaclOnly) {\n    if (attrmapF && (attrmapF->count(\"user.acl\") > 0)) {\n      eval_usr_acl = (attrmapF->count(\"sys.eval.useracl\") > 0);\n\n      if (eval_usr_acl) {\n        usr_acl = (*attrmapF)[\"user.acl\"];\n        mFileUserAcl = usr_acl;\n        mEvalFileUserAcl = true;\n      }\n    } else {\n      eval_usr_acl = (attrmap.count(\"sys.eval.useracl\") > 0);\n\n      if (eval_usr_acl) {\n        auto it = attrmap.find(\"user.acl\");\n\n        if (it != attrmap.end()) {\n          usr_acl = it->second;\n        }\n      }\n    }\n  }\n\n  std::string sys_acl;\n  auto itc = attrmap.find(\"sys.acl\");\n\n  if (itc != attrmap.end()) {\n    sys_acl = itc->second;\n  }\n\n  if (attrmapF) {\n    auto itf = attrmapF->find(\"sys.acl\");\n\n    if (itf != attrmapF->end()) {\n      if (!sys_acl.empty()) {\n        sys_acl += ',';\n      }\n\n      sys_acl += itf->second;\n    }\n  }\n\n  std::string token_acl = TokenAcl(vid);\n  eos_static_debug(\"sysacl=\\\"%s\\\" useracl=\\\"%s\\\" token_acl=\\\"%s\\\" \"\n                   \"eval_usr_acl=%d\", sys_acl.c_str(), usr_acl.c_str(),\n                   token_acl.c_str(), eval_usr_acl);\n  Set(sys_acl, usr_acl, token_acl, vid, eval_usr_acl);\n}\n\n//------------------------------------------------------------------------------\n// Set the contents of an ACL and compute the canXX and hasXX booleans.\n//------------------------------------------------------------------------------\nvoid\nAcl::Set(std::string sysacl, std::string useracl, std::string tokenacl,\n         const eos::common::VirtualIdentity& vid, bool allowUserAcl)\n{\n  std::string acl = \"\";\n  sysattr = \"\";\n  userattr = \"\";\n  mEvalDirUserAcl = false;\n\n  if (sysacl.length()) {\n    acl += sysacl;\n    sysattr = sysacl;\n  }\n\n  if (allowUserAcl) {\n    mEvalDirUserAcl = true;\n\n    if (useracl.length()) {\n      if (sysacl.length()) {\n        acl += \",\";\n      }\n\n      acl += useracl;\n      userattr = useracl;\n    }\n  }\n\n  if (tokenacl.length()) {\n    // overwrite all other ACLs with a token\n    acl = tokenacl;\n    sysacl = tokenacl;\n    allowUserAcl = false;\n  }\n\n  // By default nothing is granted\n  mHasAcl = false;\n  mCanRead = false;\n  mCanNotRead = false;\n  mCanWrite = false;\n  mCanNotWrite = false;\n  mCanWriteOnce = false;\n  mCanUpdate = false;\n  mCanNotUpdate = false;\n  mCanBrowse = false;\n  mCanNotBrowse = false;\n  mCanChmod = false;\n  mCanNotChmod = false;\n  mCanChown = false;\n  mCanNotDelete = false;\n  mCanDelete = false;\n  mCanSetQuota = false;\n  mHasEgroup = false;\n  mIsMutable = true;\n  mCanArchive = false;\n  mCanPrepare = false;\n  mCanIssueToken = false;\n  mCanSysAcl = false;\n  mCanXAttr = false;\n\n  // no acl definition\n  if (!acl.length()) {\n    return;\n  }\n\n  int errc = 0;\n  std::vector<std::string> rules;\n  std::string delimiter = \",\";\n  eos::common::StringConversion::Tokenize(sysacl, rules, delimiter);\n  int num_sysacl_rules =\n    rules.size();     /* number of entries in sysacl, used to limit \"+\" (reallow) */\n\n  if (allowUserAcl) {\n    eos::common::StringConversion::Tokenize(useracl, rules,\n                                            delimiter);  /* append to rules */\n  }\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"sysacl '%s' (%d entries), useracl '%s', total %d entries\",\n                     sysacl.c_str(), num_sysacl_rules, useracl.c_str(), rules.size());\n  }\n\n  std::vector<std::string>::const_iterator it;\n  XrdOucString sizestring1;\n  XrdOucString sizestring2;\n  char denials[256], reallows[256];\n  memset(denials, 0, sizeof(denials));        /* start with no denials */\n  memset(reallows, 0, sizeof(reallows));      /* nor reallows */\n  std::set<gid_t> gids;\n\n  if (eos::common::Mapping::gSecondaryGroups) {\n    gids = vid.allowed_gids;\n  } else {\n    gids.insert(vid.gid);\n  }\n\n  for (const auto& chk_gid : gids) {\n    // Only check non-system groups\n    if (chk_gid == 0) {\n      continue;\n    }\n\n    std::string userid = eos::common::StringConversion::GetSizeString(sizestring1,\n                         (unsigned long long) vid.uid);\n    std::string groupid = eos::common::StringConversion::GetSizeString(sizestring2,\n                          (unsigned long long) chk_gid);\n    std::string usertag = \"u:\";\n    usertag += userid;\n    usertag += \":\";\n    std::string grouptag = \"g:\";\n    grouptag += groupid;\n    grouptag += \":\";\n    std::string username = eos::common::Mapping::UidToUserName(vid.uid, errc);\n\n    if (errc) {\n      username = \"_INVAL_\";\n    }\n\n    std::string groupname = eos::common::Mapping::GidToGroupName(chk_gid, errc);\n\n    if (errc) {\n      groupname = \"_INVAL_\";\n    }\n\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"username '%s' groupname '%s'\", username.c_str(),\n                       groupname.c_str());\n    }\n\n    std::string usr_name_tag = \"u:\";\n    usr_name_tag += username;\n    usr_name_tag += \":\";\n    std::string grp_name_tag = \"g:\";\n    grp_name_tag += groupname;\n    grp_name_tag += \":\";\n    std::string ztag = \"z:\";\n    std::string keytag = \"k:\";\n    keytag += vid.key;\n    keytag += \":\";;\n\n    if (EOS_LOGS_DEBUG) eos_static_debug(\"%s %s %s %s %s\", usertag.c_str(),\n                                           grouptag.c_str(),\n                                           usr_name_tag.c_str(), grp_name_tag.c_str(), keytag.c_str());\n\n    // Rule interpretation logic\n    int sysacl_rules_remaining = num_sysacl_rules;\n\n    for (it = rules.begin(); it != rules.end(); it++) {\n      bool egroupmatch = false;\n      /* when negative, we're in user.acl */\n      sysacl_rules_remaining -= 1;\n\n      // Check for e-group membership\n      if (!it->compare(0, strlen(\"egroup:\"), \"egroup:\")) {\n        std::vector<std::string> entry;\n        std::string delimiter = \":\";\n        eos::common::StringConversion::Tokenize(*it, entry, delimiter);\n\n        if (entry.size() < 3) {\n          continue;\n        }\n\n        egroupmatch = gOFS->EgroupRefresh->Member(username, entry[1]);\n        mHasEgroup = egroupmatch;\n      }\n\n      // Match 'our' rule\n      if ((!it->compare(0, usertag.length(), usertag)) ||\n          (!it->compare(0, grouptag.length(), grouptag)) ||\n          (!it->compare(0, ztag.length(), ztag)) ||\n          (egroupmatch) ||\n          (!it->compare(0, keytag.length(), keytag)) ||\n          (!it->compare(0, usr_name_tag.length(), usr_name_tag)) ||\n          (!it->compare(0, grp_name_tag.length(), grp_name_tag))) {\n        std::vector<std::string> entry;\n        std::string delimiter = \":\";\n        eos::common::StringConversion::Tokenize(*it, entry, delimiter);\n\n        if (entry.size() < 3) {\n          // z tag entries have only two fields\n          if (it->compare(0, ztag.length(), ztag) || (entry.size() < 2)) {\n            continue;\n          }\n\n          // add an empty entry field\n          entry.resize(3);\n          entry[2] = entry[1];\n        }\n\n        if (EOS_LOGS_DEBUG) {\n          eos_static_debug(\"parsing permissions '%s'\", entry[2].c_str());\n        }\n\n        bool deny = false, reallow = false;\n\n        for (char* s = (char*) entry[2].c_str(); s[0] != 0; s++) {\n          int c = s[0];            /* need a copy in case of an \"s++\" later */\n\n          if (EOS_LOGS_DEBUG) {\n            eos_static_debug(\"c=%c deny=%d reallow=%d\", c, deny, reallow);\n          }\n\n          if (reallow && !(c == 'u' || c == 'd')) {\n            eos_static_info(\"'+' Acl flag ignored for '%c'\", c);\n          }\n\n          switch (c) {\n          case '!':\n            deny = true;\n            continue;\n\n          case '+':\n            reallow = true;\n            continue;\n\n          case 'a': // 'a' defines archiving permission\n            mCanArchive = !deny;\n            break;\n\n          case 'A': // 'A' defines sys ACL modification permission\n            mCanSysAcl = !deny;\n            break;\n\n          case 'r': // 'r' defines read permission\n            mCanRead = !deny;\n            break;\n\n          case 'x': // 'x' defines browsing permission\n            mCanBrowse = !deny;\n            break;\n\n          case 'X': // 'X' defines sys attribute modification permission\n            mCanXAttr = !deny;\n            break;\n\n          case 'p': // 'p' defines workflow permission\n            mCanPrepare = !deny;\n            break;\n\n\t  case 't': // 't' defines token issuing\n\t    mCanIssueToken = !deny;\n\t    break;\n\n          case 'm': // 'm' defines mode change permission\n            if (deny) {\n              mCanNotChmod = true;\n            } else {\n              mCanChmod = true;\n            }\n\n            break;\n\n          case 'c': // 'c' defines owner change permission (for directories)\n            /* pass here; but chown imposes further restrictions, like limited to sys.acl */\n            mCanChown = true;\n            break;\n\n          case 'd': // '!d' forbids deletion\n            if (deny && !mCanDelete) {\n              mCanNotDelete = true;\n            } else if (reallow) {\n              if (sysacl_rules_remaining < 0) {\n                eos_static_info(\"'+d' ignored in user acl '%s'\", entry[2].c_str());\n                reallow = 0;        /* ignore the reallow */\n                break;\n              }\n\n              mCanDelete = true;\n              mCanNotDelete = false;\n              mCanWriteOnce = false;\n              denials['d'] = 0;               /* drop denial, 'd' and 'u' are \"odd\" */\n            }\n\n            break;\n\n          case 'u':// '!u' denies update, 'u' and '+u' add update. '!+u' and '+!u' would *deny* updates\n            mCanUpdate = !deny;\n\n            if (mCanUpdate && reallow) {\n              denials['u'] = 0;  /* drop denial, 'd' and 'u' are \"odd\" */\n            }\n\n            break;\n\n          case 'w': // 'wo' defines write once permissions, 'w' defines write permissions if 'wo' is not granted\n            if ((s + 1)[0] == 'o') {  /* this is a 'wo' */\n              s++;\n              c = 'W';        /* for the denial entry */\n              mCanWriteOnce = !deny;\n            } else {\n              if (!mCanWriteOnce) {\n                mCanWrite = !deny;\n                mCanUpdate = !deny; // by default 'w' adds update rights\n              }\n            }\n\n            break;\n\n          case 'q':\n            if (sysacl_rules_remaining >=\n                0) { // this is only valid if specified as a sysacl\n              mCanSetQuota = !deny;\n            }\n\n            break;\n\n          case 'i': // 'i' makes directories immutable\n            mIsMutable = deny;\n            break;\n          }\n\n          mHasAcl = true;\n\n          if (reallow) {\n            reallows[c] = 1;    /* remember reallows */\n          } else if (deny) {\n            denials[c] = 1;     /* remember denials */\n          }\n\n          deny = reallow = false;           /* reset for next permission char */\n        }\n      }\n    }\n  }\n\n  /* Now that all ACLs have been parsed, handle re-allows and denials */\n  char rights[] = \"arxpmcWwdui\";\n  unsigned char r;\n\n  for (int i = 0; (r = rights[i]); i++) {\n    bool is_allowed;\n\n    if (reallows[r]) {\n      denials[r] = 0;\n      is_allowed = true;\n      eos_static_debug(\"reallow %c\", r);\n    } else if (denials[r]) {        /* re-allows beat denials */\n      is_allowed = false;\n\n      if (r != 'W') {\n        eos_static_debug(\"deny %c\", r);\n      }\n    } else {\n      continue;\n    }\n\n    switch (r) {\n    case 'a':\n      mCanArchive = is_allowed;\n      break;\n\n    case 'A':\n      mCanSysAcl = is_allowed;\n      break;\n\n    case 'r':\n      mCanRead = is_allowed;\n      mCanNotRead = !is_allowed;\n      break;\n\n    case 'x':\n      mCanBrowse = is_allowed;\n      mCanNotBrowse = !is_allowed;\n      break;\n\n    case 'X':\n      mCanXAttr = is_allowed;\n      break;\n\n    case 'p':\n      mCanPrepare = is_allowed;\n      break;\n\n    case 't':\n      mCanIssueToken = is_allowed;\n      break;\n\n    case 'm':\n      mCanNotChmod = !is_allowed;\n      break;\n\n    case 'c':\n      mCanChown = is_allowed;\n      break;\n\n    case 'W':\n      mCanWriteOnce = is_allowed;\n      eos_static_debug(\"writeonce %d\", mCanWriteOnce);\n      break;\n\n    case 'w':\n      mCanWrite = is_allowed;\n      mCanNotWrite = !is_allowed;\n\n      /* if mCanWrite, grant mCanUpdate implicitely unless 'u' explicitely denied */\n      if (mCanWrite) {\n        mCanUpdate = true;  /* 'u' is checked after 'w', this could be reverted */\n      }\n\n      break;\n\n    case 'd':\n      mCanNotDelete = !is_allowed;\n      break;\n\n    case 'u':\n      mCanUpdate = is_allowed;\n      mCanNotUpdate = !is_allowed;\n      break;\n\n    case 'i':\n      mIsMutable = !is_allowed;\n      break;\n    }\n  }\n\n  if (tokenacl.length()) {\n    // for tokens we adverties always to have an ACL\n    mHasAcl = true;\n  }\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\n      \"mCanRead %d mCanNotRead %d mCanWrite %d mCanNotWrite %d mCanWriteOnce %d mCanUpdate %d mCanNotUpdate %d \"\n      \"mCanBrowse %d mCanNotBrowse %d mCanChmod %d mCanChown %d mCanNotDelete %d mCanNotChmod %d \"\n      \"mCanDelete %d mCanSetQuota %d mHasAcl %d mHasEgroup %d mIsMutable %d mCanArchive %d mCanPrepare %d mCanIssueToken %d\",\n      mCanRead, mCanNotRead, mCanWrite, mCanNotWrite, mCanWriteOnce, mCanUpdate,\n      mCanNotUpdate,\n      mCanBrowse, mCanNotBrowse, mCanChmod, mCanChown, mCanNotDelete, mCanNotChmod,\n      mCanDelete, mCanSetQuota, mHasAcl, mHasEgroup, mIsMutable, mCanArchive,\n      mCanPrepare, mCanIssueToken);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check whether ACL has a valid format / syntax.\n//------------------------------------------------------------------------------\nbool\nAcl::IsValid(const std::string& value, XrdOucErrInfo& error, bool is_sys_acl,\n             bool check_numeric)\n{\n  // Empty is valid\n  if (!value.length()) {\n    return true;\n  }\n\n  int regexErrorCode;\n  int result;\n  regex_t regex;\n  std::string regexString;\n\n  if (is_sys_acl) {\n    if (check_numeric) {\n      regexString = sRegexSysNumericAcl;\n    } else {\n      regexString = sRegexSysGenericAcl;\n    }\n  } else {\n    if (check_numeric) {\n      regexString = sRegexUsrNumericAcl;\n    } else {\n      regexString = sRegexUsrGenericAcl;\n    }\n  }\n\n  // Compile regex\n  regexErrorCode = regcomp(&regex, regexString.c_str(), REG_EXTENDED);\n\n  if (regexErrorCode) {\n    eos_static_debug(\"regcomp regexErrorCode=%d regex '%s'\", regexErrorCode,\n                     regexString.c_str());      // the setErrInfo below does not always produce a visible result\n    error.setErrInfo(2, \"failed to compile regex\");\n    regfree(&regex);\n    return false;\n  }\n\n  // Execute regex\n  result = regexec(&regex, value.c_str(), 0, NULL, 0);\n  regfree(&regex);\n\n  // Check the result\n  if (result == 0) {\n    return true;\n  } else if (result == REG_NOMATCH) {\n    error.setErrInfo(1, \"invalid acl syntax\");\n    return false;\n  } else { // REG_BADPAT, REG_ESPACE, etc...\n    error.setErrInfo(2, \"invalid regex or out of memory\");\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Convert acl rules to numeric uid/gid if needed\n//------------------------------------------------------------------------------\nint\nAcl::ConvertIds(std::string& acl_val, bool to_string)\n{\n  if (acl_val.empty()) {\n    return 0;\n  }\n\n  bool is_uid, is_gid;\n  std::string sid;\n  std::ostringstream oss;\n  using eos::common::StringConversion;\n  std::vector<std::string> rules;\n  StringConversion::Tokenize(acl_val, rules, \",\");\n\n  if (!rules.size() && acl_val.length()) {\n    rules.push_back(acl_val);\n  }\n\n  for (auto& rule : rules) {\n    is_uid = is_gid = false;\n    std::vector<std::string> tokens;\n    StringConversion::Tokenize(rule, tokens, \":\");\n    eos_static_debug(\"rule=%s, tokens.size=%i\", rule.c_str(), tokens.size());\n\n    if (tokens.size() != 3) {\n      oss << rule << ',';\n      continue;\n    }\n\n    is_uid = (tokens[0] == \"u\");\n    is_gid = (tokens[0] == \"g\");\n\n    if (!is_uid && !is_gid) {\n      oss << rule << ',';\n      continue;\n    }\n\n    sid = tokens[1];\n    bool needs_conversion = false;\n\n    if (to_string) {\n      // Convert to string representation if needed\n      needs_conversion =\n        (std::find_if(sid.begin(), sid.end(),\n      [](const char& c) {\n        return std::isalpha(c);\n      }) == sid.end());\n    } else {\n      // Convert to numeric representation if needed\n      needs_conversion =\n        (std::find_if(sid.begin(), sid.end(),\n      [](const char& c) {\n        return std::isalpha(c);\n      }) != sid.end());\n    }\n\n    if (needs_conversion) {\n      int errc = 0;\n      std::uint32_t numeric_id {0};\n      std::string string_id {\"\"};\n\n      if (is_uid) {\n        if (!to_string) {\n          numeric_id = eos::common::Mapping::UserNameToUid(sid, errc);\n          string_id = std::to_string(numeric_id);\n        } else {\n          numeric_id = atoi(sid.c_str());\n          string_id = eos::common::Mapping::UidToUserName(numeric_id, errc);\n        }\n      } else {\n        if (!to_string) {\n          numeric_id = eos::common::Mapping::GroupNameToGid(sid, errc);\n          string_id = std::to_string(numeric_id);\n        } else {\n          numeric_id = atoi(sid.c_str());\n          string_id = eos::common::Mapping::GidToGroupName(numeric_id, errc);\n        }\n      }\n\n      if (errc) {\n        oss.str(\"\");\n\n        if (to_string) {\n          oss << \"failed to convert id: \\\"\" << sid << \"\\\" to string format\";\n        } else {\n          oss << \"failed to convert id: \\\"\" << sid << \"\\\" to numeric format\";\n        }\n\n        // Print error message but still return the original value that we have\n        eos_static_err(oss.str().c_str());\n        string_id = sid;\n        return 1;\n      }\n\n      oss << tokens[0] << ':' << string_id << ':' << tokens[2] << ',';\n    } else {\n      oss << rule << ',';\n    }\n  }\n\n  acl_val = oss.str();\n\n  if (*acl_val.rbegin() == ',') {\n    acl_val.pop_back();\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Extract an ACL rule from a token\n//------------------------------------------------------------------------------\nstd::string\nAcl::TokenAcl(const eos::common::VirtualIdentity& vid) const\n{\n  if (vid.token) {\n    if (vid.token->Valid()) {\n      if (!vid.token->ValidatePath(vid.scope)) {\n        std::string tokenacl;\n        tokenacl = \"u:\";\n        tokenacl += vid.uid_string;\n        tokenacl += \":\";\n        tokenacl += vid.token->Permission();\n\teos_static_info(\"msg=\\\"validated path\\\" tokenacl=\\\"%s\\\"\", tokenacl.c_str());\n        return tokenacl;\n      } else {\n        eos_static_err(\"msg=\\\"path outside token scope\\\" tokenscope=\\\"%s\\\" path=\\\"%s\\\"\", vid.scope.c_str(),vid.token->Path().c_str());\n\treturn \"u:root:rx\";\n      }\n    } else {\n      eos_static_err(\"%s\", \"msg=\\\"invalid token\\\"\");\n      return \"u:root:rx\";\n    }\n  } else {\n    eos_static_debug(\"%s\", \"msg=\\\"no token\\\"\");\n  }\n\n  return \"\";\n}\n\n//------------------------------------------------------------------------------\n// Check if current ACLs allow the modification of system (sys.) extended\n// attribute - method only evaluates the ACLs for this decision. This can be a\n// modification of the ACLs themselves.\n//------------------------------------------------------------------------------\nbool\nAcl::AllowXAttrUpdate(std::string_view xattr_key,\n                      eos::common::VirtualIdentity& vid) const\n{\n  bool allow = false;\n\n  if (vid.sudoer || (vid.uid == 0)) {\n    allow = true;\n  } else {\n    // Check xattr update permissions for non-owning user\n    if (xattr_key == \"sys.acl\") {\n      // Check 'A' acl\n      allow = CanSysAcl();\n    } else {\n      // Check 'X' acl\n      allow = CanXAttr();\n    }\n  }\n\n  return allow;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/acl/Acl.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Acl.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/Mapping.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include <sys/types.h>\n#include <string>\n\nclass XrdOucErrInfo;\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class implementing access control list interpretation.\n//! ACL rules used in the constructor or in the Set function are strings with\n//! the following format:\n//! rule= 'u:<uid|username>|g:<gid|groupname>|egroup:<name>:{Aarw[o]Xximc(!u)\n//!        (+u)(!d)(+d)q}|z:xmc(!u)(+u)(!d)(+d)q}'\n//------------------------------------------------------------------------------\nclass Acl\n{\npublic:         // [+] prevents '+' interpreted as \"one or more\"\n  static constexpr auto sRegexUsrGenericAcl =\n    \"^(((((u|g|k):(([0-9]+)|([\\\\.[:alnum:]_-]+)))|(egroup:([\\\\.[:alnum:]_-]+))|(z)):\"\n    \"(!?(A|a|r|w|wo|X|x|i|m|[+]?d|[+]?u|q|c))+)[,]?)*$\";\n  static constexpr auto sRegexSysGenericAcl =\n    \"^(((((u|g|k):(([0-9]+)|([\\\\.[:alnum:]_-]+)))|(egroup:([\\\\.[:alnum:]_-]+))|(z)):\"\n    \"(!?(A|a|r|w|wo|X|x|i|m|!m|!d|[+]d|!u|[+]u|q|c|p|t))+)[,]?)*$\";\n  static constexpr auto sRegexUsrNumericAcl =\n    \"^(((((u|g):(([0-9]+)))|(egroup:([\\\\.[:alnum:]_-]+))|(z)):\"\n    \"(!?(A|a|r|w|wo|X|x|i|m|[+]?d|[+]?u|q|c))+)[,]?)*$\";\n  static constexpr auto sRegexSysNumericAcl =\n    \"^(((((u|g):(([0-9]+)))|(egroup:([\\\\.[:alnum:]_-]+))|(z)):\"\n    \"(!?(A|a|r|w|wo|X|x|i|m|!m|!d|[+]d|!u|[+]u|q|c|p|t))+)[,]?)*$\";\n  //----------------------------------------------------------------------------\n  //! Use regex to check ACL format / syntax\n  //!a\n  //! @param value value to check\n  //! @param error error datastructure\n  //! @param is_sys_acl boolean indicating a sys acl entry which might have a\n  //!        p: rule\n  //! @param check_numeric if true use numeric format of the regex\n  //!\n  //! return boolean indicating validity\n  //----------------------------------------------------------------------------\n  static bool IsValid(const std::string& value, XrdOucErrInfo& error,\n                      bool is_sys_acl = false, bool check_numeric = false);\n\n  //----------------------------------------------------------------------------\n  //! By default convert the uid/gid(s) to numeric representation. If to_string\n  //! is set to true then convert from numeric to string representation.\n  //!\n  //! @param acl_val acl string which is modified in-place\n  //! @param to_string by default false, if true convert uid/gids(s) from\n  //!        numeric to string representation\n  //!\n  //! @return 0 if conversion successful\n  //----------------------------------------------------------------------------\n  static int ConvertIds(std::string& acl_val, bool to_string = false);\n\n  //----------------------------------------------------------------------------\n  //! Default Constructor\n  //----------------------------------------------------------------------------\n  Acl():\n    mCanRead(false), mCanNotRead(false), mCanWrite(false), mCanNotWrite(false),\n    mCanWriteOnce(false),\n    mCanUpdate(false), mCanNotUpdate(false), mCanBrowse(false),\n    mCanNotBrowse(false),\n    mCanChmod(false), mCanChown(false), mCanNotDelete(false),\n    mCanNotChmod(false), mCanDelete(false), mCanSetQuota(false), mHasAcl(false),\n    mHasEgroup(false), mIsMutable(false), mCanArchive(false), mCanPrepare(false),\n    mCanIssueToken(false),\n    mCanSysAcl(false),mCanXAttr(false)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param sysacl system acl definition string\n  //! 'u:<uid|username>|g:<gid|groupname>|egroup:<name>:{rwxomt(!d)(+d)(!u)(+u)}\n  //! |z:{rw[o]xmc(!u)(+u)(!d)(+d)q}'\n  //! @param useracl user acl definition string\n  //! 'u:<uid|username>|g:<gid|groupname>|egroup:<name>:{rwxomt(!d)(+d)(!u)(+u)}\n  //! |z:{rw[o]xmc(!u)(+u)(!d)(+d)q}'\n  //! @param vid virtual id to match ACL\n  //! @param allowUserAcl if true evaluate also the user acl for the permissions\n  //----------------------------------------------------------------------------\n  Acl(std::string sysacl, std::string useracl,\n      const eos::common::VirtualIdentity& vid, bool allowUserAcl = false);\n\n  /*---------------------------------------------------------------------------*/\n  //! Constructor from XAttrMap\n  /*---------------------------------------------------------------------------*/\n  Acl(const eos::IContainerMD::XAttrMap& xattrmap,\n      const eos::common::VirtualIdentity& vid);\n\n  //----------------------------------------------------------------------------\n  //! Constructor by path\n  //!\n  //! @param parent path where to read the acl attributes from\n  //! @param error return error object\n  //! @param vid virtual id to match ACL\n  //! @param attr map returns all the attributes from path\n  //----------------------------------------------------------------------------\n  Acl(const char* path, XrdOucErrInfo& error,\n      const eos::common::VirtualIdentity& vid,\n      eos::IContainerMD::XAttrMap& attrmap);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~Acl() = default;\n\n\n  //----------------------------------------------------------------------------\n  // Set Acls by interpreting the attribute map\n  //----------------------------------------------------------------------------\n  void SetFromAttrMap(const eos::IContainerMD::XAttrMap& attrmap,\n                      const eos::common::VirtualIdentity& vid,\n                      eos::IFileMD::XAttrMap* attrmapF = NULL, bool sysaclOnly = false);\n\n  //----------------------------------------------------------------------------\n  //! Enter system and user definition + identity used for ACL interpretation\n  //!\n  //! @param sysacl system acl definition string\n  //! 'u:<uid|username>|g:<gid|groupname>|egroup:<name>:{arwxom(!d)(+d)(!u)}\n  //! |z:{rw[o]xmc(!u)(+u)(!d)(+d)q}'\n  //! @param useracl user acl definition string\n  //! 'u:<uid|username>|g:<gid|groupname>|egroup:<name>:{rwxom(!d)(+d)(!u)}\n  //! |z:{rw[o]xmc(!u)(+u)(!d)(+d)q}'\n  //! @param vid virtual id to match ACL\n  //! @param allowUserAcl if true evaluate the user acl for permissions\n  //----------------------------------------------------------------------------\n  void Set(std::string sysacl, std::string useracl, std::string tokenacl,\n           const eos::common::VirtualIdentity& vid,\n           bool allowUserAcl = false);\n\n  //----------------------------------------------------------------------------\n  // Getter Functions for ACL booleans\n  //----------------------------------------------------------------------------\n  inline bool CanRead() const\n  {\n    return mCanRead;\n  }\n\n  inline bool CanNotRead() const\n  {\n    return mCanNotRead;\n  }\n\n  inline bool CanWrite() const\n  {\n    return mCanWrite;\n  }\n\n  inline bool CanNotWrite() const\n  {\n    return mCanNotWrite;\n  }\n\n  inline bool CanWriteOnce() const\n  {\n    return mCanWriteOnce;\n  }\n\n  inline bool CanUpdate() const\n  {\n    return mCanUpdate;\n  }\n\n  inline bool CanNotUpdate() const\n  {\n    return mCanNotUpdate;\n  }\n\n  inline bool CanBrowse() const\n  {\n    return mCanBrowse;\n  }\n\n  inline bool CanNotBrowse() const\n  {\n    return mCanNotBrowse;\n  }\n\n  inline bool CanChmod() const\n  {\n    return mCanChmod;\n  }\n\n  inline bool CanNotChmod() const\n  {\n    return mCanNotChmod;\n  }\n\n  inline bool CanChown() const\n  {\n    return mCanChown;\n  }\n\n  inline bool CanNotDelete() const\n  {\n    return mCanNotDelete;\n  }\n\n  inline bool CanDelete() const\n  {\n    return mCanDelete;\n  }\n\n  inline bool CanSetQuota() const\n  {\n    return mCanSetQuota;\n  }\n\n  inline bool HasAcl() const\n  {\n    return mHasAcl;\n  }\n\n  inline bool HasEgroup() const\n  {\n    return mHasEgroup;\n  }\n\n  //----------------------------------------------------------------------------\n  //! It should not have the 'i' flag to be mutable\n  //----------------------------------------------------------------------------\n  inline bool IsMutable() const\n  {\n    return mIsMutable;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Has the 'a' flag - archiving permission\n  //----------------------------------------------------------------------------\n  inline bool CanArchive() const\n  {\n    return mCanArchive;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Has the 'p' flag - prepare permission\n  //----------------------------------------------------------------------------\n  inline bool CanPrepare() const\n  {\n    return mCanPrepare;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Has the 't' flag - token issuer permission\n  //----------------------------------------------------------------------------\n  inline bool CanIssueToken() const\n  {\n    return mCanIssueToken;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Has the 'A' flag - sys acl modification permission\n  //----------------------------------------------------------------------------\n  inline bool CanSysAcl() const\n  {\n    return mCanSysAcl;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Has the 'X' flag - sys attribute permission\n  //----------------------------------------------------------------------------\n  inline bool CanXAttr() const\n  {\n    return mCanXAttr;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Extract an ACL from a token\n  //----------------------------------------------------------------------------\n\n  inline std::string TokenAcl(const eos::common::VirtualIdentity& vid) const;\n\n  //----------------------------------------------------------------------------\n  //! Return attr for sysacl\n  //----------------------------------------------------------------------------\n\n  inline const std::string SysAttr()\n  {\n    return sysattr;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return attr for useracl\n  //----------------------------------------------------------------------------\n\n  inline const std::string UserAttr()\n  {\n    return userattr;\n  }\n\n  inline const std::string UserAttrFile()\n  {\n    return mFileUserAcl;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return if enabled to evaluate user acls\n  //----------------------------------------------------------------------------\n  inline bool EvalUserAttr()\n  {\n    return mEvalDirUserAcl;\n  }\n\n  inline bool EvalUserAttrFile()\n  {\n    return mEvalFileUserAcl;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if current ACLs allow the modification of the given extended\n  //! attribute - method only evaluates the ACLs for this decision. This can\n  //! be a modification of the ACLs themselves.\n  //!\n  //! @param xattr extended attribute key\n  //! @param vid client virtual identity\n  //!\n  //! @return true if allowed, otherwise false\n  //----------------------------------------------------------------------------\n  bool AllowXAttrUpdate(std::string_view xattr_key,\n                        eos::common::VirtualIdentity& vid) const;\n\nprivate:\n  bool mCanRead; ///< acl allows read access\n  bool mCanNotRead; ///< acl denies read access\n  bool mCanWrite; ///< acl allows write access\n  bool mCanNotWrite; ///< acl denies write access\n  bool mCanWriteOnce; ///< acl allows write-once access (creation, no delete)\n  bool mCanUpdate; ///< acl allows update of files\n  bool mCanNotUpdate; ///< acl denies update of files\n  bool mCanBrowse; ///< acl allows browsing\n  bool mCanNotBrowse; ///< acl allows browsing\n  bool mCanChmod; ///< acl allows mode change\n  bool mCanChown; ///< acl allows chown change\n  bool mCanNotDelete; ///< acl forbids deletion\n  bool mCanNotChmod; ///< acl forbids chmod\n  bool mCanDelete; ///< acl allows deletion\n  bool mCanSetQuota; ///< acl allows to set quota\n  bool mHasAcl; ///< acl is valid\n  bool mHasEgroup; ///< acl contains egroup rule\n  bool mIsMutable; ///< acl does not contain the immutable flag\n  bool mCanArchive; ///< acl which allows archiving\n  bool mCanPrepare; ///< acl which allows triggering workflows\n  bool mCanIssueToken; ///< acl which allows to issue tokens\n  bool mCanSysAcl; ///< acl which allows to modify sys acls\n  bool mCanXAttr; ///< acl which allows to modify sys attributes\n  std::string sysattr;\n  std::string userattr;\n  bool mEvalDirUserAcl;\n  std::string mFileUserAcl;\n  bool mEvalFileUserAcl;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/acl/README.md",
    "content": "# EOS ACL (Access Control List)\n\nThis module implements access control list interpretation for the EOS MGM. ACL rules control read, write, delete, and other permissions on directories and files.\n\n## Rule Format\n\nACL rules are strings with the following format:\n\n```\n<subject>:<permissions>\n```\n\nMultiple rules are comma-separated. Example:\n\n```\nu:1234:rwx,g:5678:r-x,z:rx\n```\n\n## Subject Types\n\n| Prefix | Description | Example |\n|--------|-------------|---------|\n| `u:` | User by UID or username | `u:1234:rwx`, `u:alice:rwx` |\n| `g:` | Group by GID or groupname | `g:5678:rx`, `g:physics:rx` |\n| `egroup:` | External group (e-group) by name | `egroup:my-vo-group:rx` |\n| `z:` | Zero / everyone (all users) | `z:rx` |\n| `k:` | Authentication key | `k:mykey:rwx` |\n\n## Extended Attributes\n\nACLs are stored in extended attributes:\n\n| Attribute | Description |\n|-----------|-------------|\n| `sys.acl` | System ACL (directory or file). Always evaluated. |\n| `user.acl` | User ACL. Only evaluated when `sys.eval.useracl` is set. |\n| `sys.eval.useracl` | When present, enables evaluation of `user.acl` for the path. |\n\nFile-level `user.acl` overrides directory `user.acl` when `sys.eval.useracl` is set on the file.\n\n## Supported Permissions\n\n### Basic Permissions (sys.acl and user.acl)\n\n| Flag | Description |\n|------|-------------|\n| `r` | Read permission |\n| `w` | Write permission (implies update) |\n| `wo` | Write-once permission (create files, no delete) |\n| `x` | Browse permission (list directory, stat) |\n| `d` | Delete permission (implicit when not denied) |\n| `u` | Update permission (modify existing files) |\n| `m` | Mode change permission (chmod) |\n| `c` | Owner change permission (chown, directories only) |\n| `q` | Quota setting permission (**sys.acl only**) |\n| `i` | Immutable flag (when denied via `!i`, directory is immutable) |\n\n### System ACL Only Permissions\n\nThese permissions are only valid in `sys.acl` (not in `user.acl`):\n\n| Flag | Description |\n|------|-------------|\n| `a` | Archiving permission |\n| `A` | System ACL modification permission (modify `sys.acl`) |\n| `X` | System attribute modification permission (modify `sys.*` extended attributes) |\n| `p` | Prepare/workflow permission |\n| `t` | Token issuing permission (allows the referenced user/group/egroup to issue tokens on that path) |\n\n## Modifiers\n\n| Modifier | Description |\n|----------|-------------|\n| `!` | Deny the following permission |\n| `+` | Reallow (only applies to `d` and `u`) |\n\n### Denial and Reallow Logic\n\n- `!d` forbids deletion (even if write is granted).\n- `!u` denies update (modify existing files).\n- `+d` reallows deletion after a prior `!d` denial.\n- `+u` reallows update after a prior `!u` denial.\n\n**Important:** `+d` and `+u` are only effective in `sys.acl`. They are ignored when specified in `user.acl`.\n\n## Rule Parsing Logic\n\n1. **Token precedence:** If the client presents a valid token, the token's ACL completely replaces both `sys.acl` and `user.acl` for that request.\n\n2. **Rule combination:** When no token is present, `sys.acl` and `user.acl` (if `sys.eval.useracl` is set) are concatenated. Rules are processed in order: sys.acl first, then user.acl.\n\n3. **Matching:** For each rule, the subject is matched against the client's virtual identity:\n   - User match: `u:<uid>` or `u:<username>` vs. client UID\n   - Group match: `g:<gid>` or `g:<groupname>` vs. client GID(s)\n   - E-group match: membership is checked via `EgroupRefresh`\n   - `z:` matches all users\n   - Key match: `k:<key>` vs. client auth key\n\n4. **Multiple groups:** If secondary groups are enabled, the client is matched against all of their groups. A rule matches if it matches the user, any group, any e-group, or `z:`.\n\n5. **Permission accumulation:** When a rule matches, each permission character in the rule is processed. Denials (`!`) and reallows (`+`) are tracked. After all rules are processed, a final pass applies reallows (which override earlier denials) and denials.\n\n6. **Write and update:** Granting `w` implicitly grants `u` (update). Granting `wo` (write-once) does not grant delete.\n\n7. **Quota:** The `q` permission is only honored when specified in `sys.acl`; it is ignored in `user.acl`.\n\n## Space ACLs\n\nSpace-level ACLs apply to all directories that reference a space via `sys.forced.space`. If a directory does not reference a space, ACLs from the default space are used. Space ACLs are configured via `eos space config`:\n\n```bash\neos space config <space-name> space.attr.sys.acl=<value>\n```\n\n### Add-on modes\n\nA prefix before the ACL value controls how it is merged with directory ACLs:\n\n| Prefix | Mode | Effect |\n|--------|------|--------|\n| `=<` | Left | Insert space ACL first (evaluated before directory ACL) |\n| `=>` | Right | Append space ACL last (evaluated after directory ACL) |\n| `=\\|` | Conditional | Use space ACL only if the directory has no `sys.acl` |\n| `=` | Overwrite | Replace directory `sys.acl` with space ACL |\n\n### Examples\n\n```bash\n# Prepend space ACL (first evaluated)\neos space config default \"space.attr.sys.acl=<u:poweruser:rwxqmcXA\"\n\n# Append space ACL (last evaluated)\neos space config default \"space.attr.sys.acl=>u:poweruser:rwxqmcXA\"\n\n# Use space ACL only when directory has no sys.acl\neos space config default \"space.attr.sys.acl=|u:poweruser:rwxqmcXA\"\n\n# Overwrite all directory ACLs with the space ACL\neos space config default \"space.attr.sys.acl=u:poweruser:rwxqmxcXA\"\n```\n\n### Remove and inspect\n\n```bash\n# Remove space ACL\neos space config rm default space.attr.sys.acl\n\n# Show space configuration (including ACLs)\neos space status default\n```\n\n## Validation\n\nACL syntax is validated via regex. Two modes exist:\n\n- **Generic:** Allows numeric IDs and names for `u`/`g`; supports `k:` and `egroup:`.\n- **Numeric:** Restricts `u`/`g` to numeric IDs only (used when `check_numeric` is true).\n\nSystem ACLs (`sys.acl`) allow the additional flags `p`, `t`, `!m`, `!d`, `+d`, `!u`, `+u` in the regex. User ACLs do not support `p` or `t`.\n\n## ID Conversion\n\n`Acl::ConvertIds()` can convert between numeric and string representations of UIDs/GIDs in ACL rules. By default it converts names to numeric IDs; with `to_string=true` it converts numeric IDs to names.\n"
  },
  {
    "path": "mgm/adminsocket/AdminSocket.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file AdminSocket.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/adminsocket/AdminSocket.hh\"\n#include \"mgm/proc/IProcCommand.hh\"\n#include \"mgm/proc/ProcInterface.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nvoid\nAdminSocket::Run(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"AdminSocket\");\n  zmq::context_t context(1);\n  zmq::socket_t socket(context, ZMQ_REP);\n  socket.bind(mSocket.c_str());\n  zmq::pollitem_t items[] = {\n    {static_cast<void*>(socket), 0, ZMQ_POLLIN, 0}\n  };\n\n  while (!assistant.terminationRequested()) {\n    zmq::message_t request;\n    zmq::recv_flags rf = zmq::recv_flags::none;\n    // poll for work\n    zmq_poll(items, 1, 100);\n\n    if (items[0].revents & ZMQ_POLLIN) {\n      try {\n        auto s = socket.recv(request, rf);\n        if (!s.has_value()) {\n          continue;\n        }\n      } catch (zmq::error_t& zmq_err) {\n        eos_static_err(\"receive:err=\\\"%s\\\"\", zmq_err.what());\n        continue;\n      }\n      std::string input((char*)request.data(), request.size());\n      std::string info;\n      std::unique_ptr<IProcCommand> proccmd {nullptr};\n      eos::common::VirtualIdentity root_vid = eos::common::VirtualIdentity::Root();\n\n      if (auto split_pos = input.find(\"?\");\n          split_pos != std::string::npos) {\n        info = input.substr(split_pos + 1);\n        input.erase(split_pos);\n        eos_static_info(\"msg=processing admin socket command %s, cgi: %s\",\n                        input.c_str(), info.c_str());\n        proccmd = ProcInterface::GetProcCommand(\"adminsocket@localhost\", root_vid, input.c_str(),\n                                                info.c_str(), \"adminsocket\");\n      }\n\n      size_t size = 0;\n      std::string result;\n\n      try {\n        if (proccmd) {\n          XrdOucErrInfo error;\n          (void) proccmd->open(input.c_str() , info.c_str() , root_vid, &error);\n          struct stat buf;\n          proccmd->stat(&buf);\n          size = buf.st_size;\n          zmq::message_t reply(size);\n          zmq::send_flags sf = zmq::send_flags::none;\n          proccmd->read(0, (char*)reply.data(), size);\n          proccmd->close();\n          socket.send(reply,sf);\n        } else {\n          zmq::message_t reply(0);\n          zmq::send_flags sf = zmq::send_flags::none;\n          memcpy(reply.data(), result.c_str(), size);\n          socket.send(reply,sf);\n        }\n      } catch (zmq::error_t& zmq_err) {\n        eos_static_err(\"send:err=\\\"%s\\\"\", zmq_err.what());\n        continue;\n      }\n    }\n  }\n}\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/adminsocket/AdminSocket.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file AdminSocket.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/AssistedThread.hh\"\n#include \"common/Logging.hh\"\n#include \"mgm/Namespace.hh\"\n#include <zmq.hpp>\n#include <sys/types.h>\n#include <string>\n\nEOSMGMNAMESPACE_BEGIN\n\nclass AdminSocket {\npublic:\n  AdminSocket() {\n  }\n  \n  AdminSocket(const std::string& path){\n    mSocket = \"ipc://\";\n    mSocket += path;\n    eos_static_info(\"socket-path=%s\", mSocket.c_str());\n    mThread.reset(&AdminSocket::Run, this);\n  }\n  \n  void Run(ThreadAssistant& assistant) noexcept;\n\n  virtual ~AdminSocket(){\n    mThread.join();\n  }\nprivate:\n  AssistedThread mThread;\n  std::string mSocket;\n};\n\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/auth/AccessChecker.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file AccessChecker.cc\n//! @author Fabio Luchetti, Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/auth/AccessChecker.hh\"\n#include \"mgm/acl/Acl.hh\"\n#include \"common/Definitions.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include <common/Path.hh>\n#include <sys/stat.h>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Check access to the given container - linked attributes are necessary\n// to construct the Acl object.\n//\n// All information required to make a decision are passed to this function.\n//------------------------------------------------------------------------------\nbool\nAccessChecker::checkContainer(IContainerMD* cont,\n                              const eos::IContainerMD::XAttrMap& linkedAttrs,\n                              int mode, const eos::common::VirtualIdentity& vid)\n{\n  Acl acl(linkedAttrs, vid);\n  // Delegate to method taking receiving acl object instead of linked xattrs\n  return checkContainer(cont, acl, mode, vid);\n}\n\n//------------------------------------------------------------------------------\n// Check access to the given container - all information required to make\n// a decision are passed to this function, no external information should\n// be needed.\n//------------------------------------------------------------------------------\nbool\nAccessChecker::checkContainer(IContainerMD* cont, const Acl& acl,\n                              int mode, const eos::common::VirtualIdentity& vid)\n{\n  // Allow root to do anything\n  if (vid.uid == 0) {\n    return true;\n  }\n\n  // Always allow daemon to read/browse\n  if ((vid.uid == DAEMONUID) && !(mode & W_OK)) {\n    return true;\n  }\n\n  // A non-root attempting to write an immutable directory?\n  if (acl.HasAcl() && !acl.IsMutable() && (mode & W_OK)) {\n    return false;\n  }\n\n  // A non-root attempting to prepare, but no explicit ACL allowing prepare?\n  if ((mode & P_OK) && (!acl.HasAcl() || !acl.CanPrepare())) {\n    return false;\n  }\n\n  // A non-root attempting to delete, we have two cases:\n  // * container has S_ISVTX(sticky bit) then only the file owner can delete\n  //   that file irrespective of the ACLs - this check is split between the\n  //   current method and checkFile\n  // * container does NOT have S_ISVTX set and NO ACL set, directory and file permission apply\n  // * container does NOT have S_ISVTX set and ACL set with !d, the owner of the\n  //   container can delete regardless of the !d ACL, directory and file permission apply\n  if (mode & D_OK) {\n    bool isvtx = cont->getMode() & S_ISVTX;\n\n    if (isvtx) {\n      if (cont->getCUid() != vid.uid) {\n        // The second part of this check is done in checkFile\n        return false;\n      }\n    } else {\n      if (acl.HasAcl() && acl.CanNotDelete()) {\n        // There's a !d ACL for that vid, we grant the deletion if the owner of the container is the vid provided\n        // and has write permission on it\n        if((cont->getCUid() == vid.uid) && (!vid.token?cont->access(vid.uid,vid.gid,W_OK):false)) {\n          return true;\n        }\n        return false;\n      }\n    }\n  }\n\n  // Basic permission check\n  bool basicCheck = !vid.token?cont->access(vid.uid, vid.gid, mode):false;\n\n  // Access granted, or we have no Acls? We're done.\n  if (basicCheck || !acl.HasAcl()) {\n    return basicCheck;\n  }\n\n  // Basic check denied us access... let's see if we can recover through Acls\n  if ((mode & W_OK) &&\n      (acl.CanNotWrite() ||\n       (!acl.CanWrite() && (!vid.token?!cont->access(vid.uid, vid.gid, W_OK):true)))) {\n    // Asking for write permission, and neither basic check, nor Acls grant us\n    // write. Deny.\n    return false;\n  }\n\n  if ((mode & R_OK) &&\n      (acl.CanNotRead() ||\n       (!acl.CanRead() && (!vid.token?!cont->access(vid.uid, vid.gid, R_OK):true)))) {\n    // Asking for read permission, and neither basic check, nor Acls grant us\n    // read. Deny.\n    return false;\n  }\n\n  if ((mode & X_OK) &&\n      (acl.CanNotBrowse() ||\n       (!acl.CanBrowse() && ( !vid.token?!cont->access(vid.uid, vid.gid, X_OK):true)))) {\n    // Asking for browse permission, and neither basic check, nor Acls grant us\n    // browse. Deny.\n    return false;\n  }\n\n  // We survived Acl check, grant.\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Check access to the given file. The parent directory of the file\n// needs to be checked separately!\n//------------------------------------------------------------------------------\nbool AccessChecker::checkFile(IFileMD* file, int mode, int dh_mode,\n                              const eos::common::VirtualIdentity& vid)\n{\n  // root can do anything\n  if (vid.uid == 0) {\n    return true;\n  }\n\n  // Deletion when parent container has sticky bit is allowed only if\n  // done by the owner of the file\n  if (mode & D_OK) {\n    if ((dh_mode & S_ISVTX) && (file->getCUid() != vid.uid)) {\n      return false;\n    }\n  }\n\n  // We only check browse permissions for files, for now.\n  if (!(mode & X_OK)) {\n    return true;\n  }\n\n  uint16_t flags = file->getFlags();\n  uid_t uid = file->getCUid();\n  gid_t gid = file->getCGid();\n\n  // both uid and gid match? return OR-match\n  if (vid.uid == uid && vid.gid == gid) {\n    return (flags & S_IXUSR) || (flags & S_IXGRP);\n  }\n\n  // user check\n  if (vid.uid == uid) {\n    return (flags & S_IXUSR);\n  }\n\n  // group check\n  if (vid.gid == gid) {\n    return (flags & S_IXGRP);\n  }\n\n  // other check\n  return (flags & S_IXOTH);\n}\n\n//---------------------------------------------------------------------------------------------------\n// Test if public access is allowed for a given path\n//---------------------------------------------------------------------------------------------------\nbool\nAccessChecker::checkPublicAccess(const std::string& fullpath,\n                                 const common::VirtualIdentity& vid)\n{\n  int errc = 0;\n\n  if ((eos::common::Mapping::UserNameToUid(std::string(\"eosnobody\"), errc) == vid.uid) &&\n      !errc && (strcmp(vid.prot.c_str(), \"sss\") == 0)) {\n    // eosnobody can access all squash files\n    eos::common::Path cPath(fullpath);\n\n    if (!cPath.isSquashFile()) {\n      errno = EACCES;\n      return false;\n    }\n\n    return true;\n  }\n\n  // Check only for anonymous access\n  // uid=99 for CentOS7 and uid=65534 for >= Alma9\n  if ((vid.uid != 99) && (vid.uid != 65534)) {\n    return true;\n  } else {\n    uint32_t level = eos::common::Mapping::GetPublicAccessLevel();\n\n    if (level >= 1024) {\n      return true;\n    } // short cut\n\n    eos::common::Path cPath{fullpath};\n    return cPath.GetSubPathSize() < level ? true : false;\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/auth/AccessChecker.hh",
    "content": "// ----------------------------------------------------------------------\n// @file: AccessChecker.hh\n// @author: Fabio Luchetti, Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/Mapping.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n\nnamespace eos {\n  class IContainerMD;\n  class IFileMD;\n}\n\nEOSMGMNAMESPACE_BEGIN\n\nclass Acl;\n\nclass AccessChecker {\npublic:\n  //----------------------------------------------------------------------------\n  //! Check access to the given container - linked attributes are necessary\n  //! to construct the Acl object.\n  //!\n  //! All information required to make a decision are passed to this function.\n  //----------------------------------------------------------------------------\n  static bool checkContainer(IContainerMD *cont,\n    const eos::IContainerMD::XAttrMap &linkedAttrs, int mode,\n    const eos::common::VirtualIdentity &vid);\n\n  //----------------------------------------------------------------------------\n  //! Check access to the given container - all information required to make\n  //! a decision are passed to this function, no external information should\n  //! be needed.\n  //----------------------------------------------------------------------------\n  static bool checkContainer(IContainerMD *cont, const Acl &acl, int mode,\n    const eos::common::VirtualIdentity &vid);\n\n  //----------------------------------------------------------------------------\n  //! Check access to the given file. The parent directory of the file\n  //! needs to be checked separately!\n  //!\n  //! @param mode requested type of access\n  //! @param dh_mode parent directory mode bits\n  //!\n  //! @return true if access allowed, otherwise false\n  //----------------------------------------------------------------------------\n  static bool checkFile(IFileMD *file, int mode, int dh_mode,\n                        const eos::common::VirtualIdentity &vid);\n\n  //----------------------------------------------------------------------------\n  //! Test if public access is allowed for a given path\n  //----------------------------------------------------------------------------\n  static bool\n  checkPublicAccess(const std::string &fullpath,\n                    const common::VirtualIdentity& vid);\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/authz/XrdMgmAuthz.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file XrdMgmAuthz.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/token/EosTok.hh\"\n#include \"common/SecEntity.hh\"\n#include \"mgm/authz/XrdMgmAuthz.hh\"\n#include <XrdSys/XrdSysError.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdSec/XrdSecEntityAttr.hh>\n#include <XrdVersion.hh>\n\nXrdMgmAuthz* gMgmAuthz {nullptr};\n\n// Set the version information\nXrdVERSIONINFO(XrdAccAuthorizeObject, EosMgmAuthz);\n\n//------------------------------------------------------------------------------\n// XrdAccAuthorizeObject() is called to obtain an instance of the auth object\n// that will be used for all subsequent authorization decisions. If it returns\n// a null pointer; initialization fails and the program exits. The args are:\n//\n// lp    -> XrdSysLogger to be tied to an XrdSysError object for messages\n// cfn   -> The name of the configuration file\n// parm  -> Paramexters specified on the authlib directive. If none it is zero.\n//------------------------------------------------------------------------------\nextern \"C\"\n{\n  XrdAccAuthorize* XrdAccAuthorizeObject(XrdSysLogger* lp, const char*   cfn,\n                                         const char*   parm)\n  {\n    XrdSysError eroute(lp, \"mgmauthz_\");\n\n    if (gMgmAuthz) {\n      eroute.Say(\"====== XrdMgmAuthz plugin already loaded and available\");\n      return gMgmAuthz;\n    }\n\n    XrdOucString version = \"EOS MGM Authorization (XrdMgmAuthz) \";\n    version += VERSION;\n    eroute.Say(\"++++++ (c) 2022 CERN/IT-ST \", version.c_str());\n    gMgmAuthz = new XrdMgmAuthz();\n\n    if (!gMgmAuthz) {\n      eroute.Say(\"------ XrdMgmAuthz plugin initialization failed!\");\n    } else {\n      eroute.Say(\"------ XrdMgmAuthz plugin initialization successful\");\n    }\n\n    return static_cast<XrdAccAuthorize*>(gMgmAuthz);\n  }\n\n\n//------------------------------------------------------------------------------\n//! Add an authorization object as a wrapper to the existing object.\n//!\n//! XrdAccAuthorizeObjAdd() is an extern \"C\" function that is called to obtain\n//! an instance of the auth object that should wrap the existing object. The\n//! wrapper becomes the actual authorization object. The wrapper must be\n//! in the plug-in shared library, it is passed additional parameters.\n//! All the following extern symbols must be defined at file level!\n//!\n//! @param lp   -> XrdSysLogger to be tied to an XrdSysError object for messages\n//! @param cfn  -> The name of the configuration file\n//! @param parm -> Parameters specified on the authlib directive. If none it\n//!                is zero.\n//! @param envP -> Environmental information and may be nil.\n//! @param accP -> to the existing authorization object.\n//!\n//! @return Success: A pointer to the authorization object.\n//!         Failure: Null pointer which causes initialization to fail.\n  XrdAccAuthorize* XrdAccAuthorizeObjAdd(XrdSysLogger* log,\n                                         const char*   config,\n                                         const char*   params,\n                                         XrdOucEnv*     /*not used*/,\n                                         XrdAccAuthorize* chain_authz)\n  {\n    XrdSysError eroute(log, \"mgmauthz_\");\n\n    if (gMgmAuthz) {\n      if (chain_authz) {\n        eroute.Say(\"====== XrdMgmAuthz does not support chaining other \"\n                   \"authorization objects\");\n      }\n\n      eroute.Say(\"====== XrdMgmAuthz plugin already loaded and available\");\n      return gMgmAuthz;\n    }\n\n    return XrdAccAuthorizeObject(log, config, params);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check whether or not the client is permitted specified access to a path.\n//------------------------------------------------------------------------------\nXrdAccPrivs\nXrdMgmAuthz::Access(const XrdSecEntity* Entity, const char* path,\n                    const Access_Operation oper, XrdOucEnv* Env)\n{\n  using eos::common::SecEntity;\n  int envlen;\n  eos_static_debug(\"path=\\\"%s\\\" opaque=\\\"%s\\\" client_info=\\\"%s\\\"\", path,\n                   (Env ? Env->Env(envlen) : \"none\"),\n                   (Entity ? SecEntity::ToString(Entity, \"\").c_str() : \"none\"));\n\n  if (eos::common::EosTok::IsEosToken(Env)) {\n    return XrdAccPriv_All;\n  }\n\n  bool has_user = false;\n\n  if (Entity) {\n    std::string user_value;\n    std::string user_key = \"request.name\";\n    has_user = Entity->eaAPI->Get(user_key, user_value);\n    eos_static_debug(\"msg=\\\"checking access\\\" path=\\\"%s\\\", name=\\\"%s\\\" \"\n                     \"request.name=\\\"%s\\\"\", path, Entity->name,\n                     (has_user ? user_value.c_str() : \"\"));\n  }\n\n  if ((Entity == nullptr) || ((Entity->name == nullptr) && !has_user)) {\n    return XrdAccPriv_None;\n  }\n\n  return XrdAccPriv_All;\n}\n"
  },
  {
    "path": "mgm/authz/XrdMgmAuthz.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file XrdMgmAuthz.hh\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include <XrdAcc/XrdAccAuthorize.hh>\n#include \"common/Logging.hh\"\n\n//------------------------------------------------------------------------------\n//! Class XrdMgmAuthz\n//! @brief EOS MGM authorization plugin\n//------------------------------------------------------------------------------\nclass XrdMgmAuthz: public XrdAccAuthorize, public eos::common::LogId\n{\npublic:\n  //------------------------------------------------------------------------------\n  //! Constructor\n  //------------------------------------------------------------------------------\n  XrdMgmAuthz() = default;\n\n  //------------------------------------------------------------------------------\n  //! Destructor\n  //------------------------------------------------------------------------------\n  virtual ~XrdMgmAuthz() = default;\n\n  //------------------------------------------------------------------------------\n  //! Check whether or not the client is permitted specified access to a path.\n  //!\n  //! @param     Entity    -> Authentication information\n  //! @param     path      -> The logical path which is the target of oper\n  //! @param     oper      -> The operation being attempted (see the enum above).\n  //!                         If the oper is AOP_Any, then the actual privileges\n  //!                         are returned and the caller may make subsequent\n  //!                         tests using Test().\n  //! @param     Env       -> Environmental information at the time of the\n  //!                         operation as supplied by the path CGI string.\n  //!                         This is optional and the pointer may be zero.\n  //!\n  //! @return    Permit: a non-zero value (access is permitted)\n  //!            Deny:   zero             (access is denied)\n  //------------------------------------------------------------------------------\n  virtual XrdAccPrivs Access(const XrdSecEntity*    Entity,\n                             const char*            path,\n                             const Access_Operation oper,\n                             XrdOucEnv*       Env = 0) override;\n\n  //------------------------------------------------------------------------------\n  //! Route an audit message to the appropriate audit exit routine. See\n  //! XrdAccAudit.h for more information on how the default implementation works.\n  //! Currently, this method is not called by the ofs but should be used by the\n  //! implementation to record denials or grants, as warranted.\n  //!\n  //! @param     accok     -> True is access was grated; false otherwise.\n  //! @param     Entity    -> Authentication information\n  //! @param     path      -> The logical path which is the target of oper\n  //! @param     oper      -> The operation being attempted (see above)\n  //! @param     Env       -> Environmental information at the time of the\n  //!                         operation as supplied by the path CGI string.\n  //!                         This is optional and the pointer may be zero.\n  //!\n  //! @return    Success: !0 information recorded.\n  //!            Failure:  0 information could not be recorded.\n  //------------------------------------------------------------------------------\n  virtual int Audit(const int              accok,\n                    const XrdSecEntity*    Entity,\n                    const char*            path,\n                    const Access_Operation oper,\n                    XrdOucEnv*             Env = 0) override\n  {\n    return 1;\n  }\n\n  //------------------------------------------------------------------------------\n  //! Check whether the specified operation is permitted.\n  //!\n  //! @param     priv      -> the privileges as returned by Access().\n  //! @param     oper      -> The operation being attempted (see above)\n  //!\n  //! @return    Permit: a non-zero value (access is permitted)\n  //!            Deny:   zero             (access is denied)\n  //------------------------------------------------------------------------------\n  virtual int Test(const XrdAccPrivs priv,\n                   const Access_Operation oper) override\n  {\n    return 0;\n  }\n\nprivate:\n\n};\n\nextern XrdMgmAuthz* gMgmAuthz; ///< Global handle to XrdMgmAuthz object\n"
  },
  {
    "path": "mgm/balancer/FsBalancer.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file FsBalancer.cc\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//-----------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/utils/BackOffInvoker.hh\"\n#include \"mgm/balancer/FsBalancer.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/drain/DrainTransferJob.hh\"\n#include \"namespace/interface/IFsView.hh\"\n#include \"namespace/Prefetcher.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Update balancer config based on the info registered at the space\n//------------------------------------------------------------------------------\nvoid\nFsBalancer::ConfigUpdate()\n{\n  if (!mDoConfigUpdate) {\n    return;\n  }\n\n  eos_static_info(\"msg=\\\"fs balancer configuration update\\\" space=%s\",\n                  mSpaceName.c_str());\n  mDoConfigUpdate = false;\n  // Collect all the relevant info from the parent space\n  eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n  auto it_space = FsView::gFsView.mSpaceView.find(mSpaceName);\n\n  // Space no longer exist, just disable the balancer\n  if (it_space == FsView::gFsView.mSpaceView.end()) {\n    mIsEnabled = false;\n    return;\n  }\n\n  auto* space = it_space->second;\n\n  // Check if balancer is enabled\n  if (space->GetConfigMember(\"balancer\") != \"on\") {\n    mIsEnabled = false;\n    return;\n  }\n\n  mIsEnabled = true;\n  // Update other balancer related parameters\n  std::string svalue = space->GetConfigMember(\"balancer.threshold\");\n\n  if (svalue.empty()) {\n    eos_static_err(\"msg=\\\"balancer threshold missing, use default value\\\" value=%f\",\n                   mThreshold);\n  } else {\n    try {\n      mThreshold = std::stod(svalue);\n    } catch (...) {\n      eos_static_err(\"msg=\\\"balancer threshold invalid format\\\" input=\\\"%s\\\"\",\n                     svalue.c_str());\n    }\n  }\n\n  svalue = space->GetConfigMember(\"balancer.node.ntx\");\n\n  if (svalue.empty()) {\n    eos_static_err(\"msg=\\\"balancer node tx missing, use default value\\\" value=%f\",\n                   mTxNumPerNode);\n  } else {\n    try {\n      mTxNumPerNode = std::stoul(svalue);\n    } catch (...) {\n      eos_static_err(\"msg=\\\"balancer node tx invalid format\\\" input=\\\"%s\\\"\",\n                     svalue.c_str());\n    }\n  }\n\n  svalue = space->GetConfigMember(\"balancer.node.rate\");\n\n  if (svalue.empty()) {\n    eos_static_err(\"msg=\\\"balancer node rate missing, use default value\\\" value=%f\",\n                   mTxRatePerNode);\n  } else {\n    try {\n      mTxRatePerNode = std::stoul(svalue);\n    } catch (...) {\n      eos_static_err(\"msg=\\\"balancer node rate invalid format\\\" input=\\\"%s\\\"\",\n                     svalue.c_str());\n    }\n  }\n\n  svalue = space->GetConfigMember(\"balancer.max-queue-size\");\n\n  if (!svalue.empty()) {\n    try {\n      unsigned int max_queued_jobs = std::stoul(svalue);\n\n      if ((max_queued_jobs > 10) && (max_queued_jobs < 10000)) {\n        mMaxQueuedJobs = max_queued_jobs;\n      } else {\n        eos_static_err(\"msg=\\\"balancer max-queue-size invalid value\\\" \"\n                       \"input=\\\"%s\\\"\", svalue.c_str());\n      }\n    } catch (...) {\n      eos_static_err(\"msg=\\\"balancer max-queue-size invalid format\\\" \"\n                     \"input=\\\"%s\\\"\", svalue.c_str());\n    }\n  }\n\n  svalue = space->GetConfigMember(\"balancer.max-thread-pool-size\");\n\n  if (!svalue.empty()) {\n    try {\n      unsigned int max_thread_pool_size = std::stoul(svalue);\n\n      if ((max_thread_pool_size > 2) && (max_thread_pool_size < 10000)) {\n        if (mMaxThreadPoolSize != max_thread_pool_size) {\n          mMaxThreadPoolSize = max_thread_pool_size;\n          mThreadPool.SetMaxThreads(mMaxThreadPoolSize);\n        }\n      } else {\n        eos_static_err(\"msg=\\\"balancer max-thread-pool-size invalid value\\\" \"\n                       \"input=\\\"%s\\\"\", svalue.c_str());\n      }\n    } catch (...) {\n      eos_static_err(\"msg=\\\"balancer max-thread-pool-size invalid format\\\" \"\n                     \"input=\\\"%s\\\"\", svalue.c_str());\n    }\n  }\n\n  svalue = space->GetConfigMember(\"balancer.update.interval\");\n\n  if (!svalue.empty()) {\n    try {\n      unsigned int upd_interval_sec = std::stoul(svalue);\n\n      if ((upd_interval_sec >= 1) && (upd_interval_sec <= 300)) {\n        mUpdInterval = std::chrono::seconds(upd_interval_sec);\n      } else {\n        eos_static_err(\"msg=\\\"balancer update interval invalid value\\\" \"\n                       \"input=\\\"%s\\\"\", svalue.c_str());\n      }\n    } catch (...) {\n      eos_static_err(\"msg=\\\"balancer update interval invalid format\\\" \"\n                     \"input=\\\"%s\\\"\", svalue.c_str());\n    }\n  }\n\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Loop handling balancing jobs\n//------------------------------------------------------------------------------\nvoid\nFsBalancer::Balance(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"FsBalancer\");\n  static constexpr std::chrono::seconds enable_refresh_delay {10};\n  static constexpr std::chrono::seconds no_transfers_delay {30};\n  static constexpr std::chrono::seconds no_slots_delay {10};\n\n  if (gOFS) {\n    gOFS->WaitUntilNamespaceIsBooted(assistant);\n  }\n\n  eos_static_info(\"msg=\\\"started file system balancer thread\\\" space=%s\",\n                  mSpaceName.c_str());\n  VectBalanceFs vect_tx;\n  common::BackOffInvoker backoff_logger;\n\n  while (!assistant.terminationRequested()) {\n    ConfigUpdate();\n\n    if (!mIsEnabled) {\n      backoff_logger.invoke([]() {\n        eos_static_info(\"msg=\\\"balancer disabled\\\" wait=%is\\\"\",\n                        enable_refresh_delay.count());\n      });\n      assistant.wait_for(enable_refresh_delay);\n      continue;\n    }\n\n    if (gOFS && !gOFS->mMaster->IsMaster()) {\n      assistant.wait_for(std::chrono::seconds(10));\n      eos_static_debug(\"%s\", \"msg=\\\"fs balancer disabled for slave\\\"\");\n      continue;\n    }\n\n    if (mBalanceStats.NeedsUpdate(mUpdInterval)) {\n      eos_static_info(\"msg=\\\"update balancer stats\\\" threshold=%0.2f\",\n                      mThreshold);\n      mBalanceStats.UpdateInfo(&FsView::gFsView, mThreshold);\n      vect_tx = mBalanceStats.GetTxEndpoints();\n    }\n\n    if (vect_tx.empty()) {\n      eos_static_debug(\"msg=\\\"no groups to balance\\\" wait=%is\\\"\",\n                       no_transfers_delay.count());\n      assistant.wait_for(no_transfers_delay);\n      continue;\n    }\n\n    bool no_slots = true;\n    // Circular iterator over all the groups that need to be balanced with a\n    // random starting point inside the vector\n    auto it_start = GetRandomIter(vect_tx);\n    auto it_current = it_start;\n\n    do {\n      const auto& src_fses = it_current->first;\n\n      for (const auto& src : src_fses) {\n        if (assistant.terminationRequested() || !gOFS->mMaster->IsMaster()) {\n          break;\n        }\n\n        if (!mBalanceStats.HasTxSlot(src.mNodeInfo, mTxNumPerNode)) {\n          eos_static_info(\"msg=\\\"exhausted transfers slots\\\" node=%s tx=%lu\",\n                          src.mNodeInfo.c_str(), mTxNumPerNode);\n          continue;\n        }\n\n        while ((mThreadPool.GetQueueSize() > mMaxQueuedJobs) &&\n               !assistant.terminationRequested()) {\n          assistant.wait_for(std::chrono::seconds(1));\n        }\n\n        if (assistant.terminationRequested() ||\n            (gOFS && !gOFS->mMaster->IsMaster())) {\n          break;\n        }\n\n        FsBalanceInfo dst;\n        const auto fid = GetFileToBalance(src, it_current->second, dst);\n\n        if (fid == 0ull) {\n          continue;\n        }\n\n        // Found file and destination file system where to balance it\n        eos_static_info(\"msg=\\\"balance job\\\" fxid=%08llx src_fsid=%lu \"\n                        \"dst_fsid=%lu\", fid, src.mFsId, dst.mFsId);\n        no_slots = false;\n        TakeTxSlot(src, dst);\n        // Create and submit job\n        std::shared_ptr<DrainTransferJob> job {\n          new DrainTransferJob(fid, src.mFsId, dst.mFsId, {}, {},\n          true, \"balance\", true)};\n        mThreadPool.PushTask<void>([job, fid, src, dst, this]() {\n          job->UpdateMgmStats();\n          job->DoIt();\n          job->UpdateMgmStats();\n          this->FreeTxSlot(fid, src, dst);\n        });\n      }\n\n      ++it_current;\n\n      if (it_current == vect_tx.end()) {\n        it_current = vect_tx.begin();\n      }\n    } while ((it_current != it_start) &&\n             !assistant.terminationRequested() &&\n             gOFS->mMaster->IsMaster());\n\n    if (no_slots) {\n      eos_static_info(\"%s\", \"msg=\\\"sleep no slots\\\"\");\n      assistant.wait_for(std::chrono::seconds(no_slots_delay));\n    }\n  }\n\n  while (mThreadPool.GetQueueSize() && mRunningJobs) {\n    eos_static_info(\"msg=\\\"wait for balance jobs to finish\\\" queue_size=%lu\",\n                    mThreadPool.GetQueueSize());\n    std::this_thread::sleep_for(std::chrono::seconds(5));\n  }\n\n  gOFS->mFidTracker.DoCleanup(TrackerType::Balance);\n  eos_static_info(\"msg=\\\"stopped file system balancer thread\\\" space=%s\",\n                  mSpaceName.c_str());\n}\n\n//------------------------------------------------------------------------------\n// Get file identifier to balance from the given source file system\n//------------------------------------------------------------------------------\neos::IFileMD::id_t\nFsBalancer::GetFileToBalance(const FsBalanceInfo& src,\n                             const std::set<FsBalanceInfo>& set_dsts,\n                             FsBalanceInfo& dst)\n{\n  int attempts = 10;\n  const eos::common::FileSystem::fsid_t src_fsid = src.mFsId;\n  eos::IFileMD::id_t random_fid {0ull};\n  eos::common::FileSystem::fsid_t dst_fsid {0ul};\n\n  while (attempts-- > 0) {\n    if (gOFS->eosFsView->getApproximatelyRandomFileInFs(src_fsid, random_fid)) {\n      if (!gOFS->mFidTracker.AddEntry(random_fid, TrackerType::Balance)) {\n        // Reset fid otherwise this will be considered valid after 10 attemtps\n        eos_static_debug(\"msg=\\\"skip busy file identifier\\\" fxid=%08llx\",\n                         random_fid);\n        random_fid = 0ull;\n        continue;\n      }\n\n      std::set<uint32_t> avoid_fsids;\n      eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, random_fid);\n\n      try {\n        auto fmd = gOFS->eosFileService->getFileMD(random_fid);\n        auto fmd_lock = eos::MDLocking::readLock(fmd.get());\n        auto loc = fmd->getLocations();\n        avoid_fsids.insert(loc.cbegin(), loc.cend());\n        loc = fmd->getUnlinkedLocations();\n        avoid_fsids.insert(loc.cbegin(), loc.cend());\n      } catch (eos::MDException& e) {\n        eos_static_err(\"msg=\\\"failed to find file\\\" fxid=%08llx\", random_fid);\n        gOFS->mFidTracker.RemoveEntry(random_fid);\n        random_fid = 0ull;\n        continue;\n      }\n\n      if (avoid_fsids.empty()) {\n        gOFS->mFidTracker.RemoveEntry(random_fid);\n        random_fid = 0ull;\n        continue;\n      }\n\n      // Search for a suitable destination file system\n      if (random_fid % 2 == 0) {\n        for (auto it = set_dsts.cbegin(); it != set_dsts.cend(); ++it) {\n          if ((avoid_fsids.find(it->mFsId) == avoid_fsids.end()) &&\n              mBalanceStats.HasTxSlot(it->mNodeInfo, mTxNumPerNode)) {\n            dst = *it;\n            dst_fsid = dst.mFsId;\n            break;\n          }\n        }\n      } else {\n        for (auto it = set_dsts.rbegin(); it != set_dsts.rend(); ++it) {\n          if ((avoid_fsids.find(it->mFsId) == avoid_fsids.end()) &&\n              mBalanceStats.HasTxSlot(it->mNodeInfo, mTxNumPerNode)) {\n            dst = *it;\n            dst_fsid = dst.mFsId;\n            break;\n          }\n        }\n      }\n\n      if (dst_fsid == 0ul) {\n        gOFS->mFidTracker.RemoveEntry(random_fid);\n        random_fid = 0ull;\n      } else {\n        break;\n      }\n    }\n  }\n\n  return random_fid;\n}\n\n//----------------------------------------------------------------------------\n// Account for new balancer transfer\n//----------------------------------------------------------------------------\nvoid\nFsBalancer::TakeTxSlot(const FsBalanceInfo& src, const FsBalanceInfo& dst)\n{\n  ++mRunningJobs;\n  mBalanceStats.TakeTxSlot(src.mNodeInfo, dst.mNodeInfo);\n  // Account for running balancing transfers per file system\n  eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n  auto* fs = FsView::gFsView.mIdView.lookupByID(dst.mFsId);\n\n  if (fs) {\n    fs->IncrementBalanceTx();\n  }\n}\n\n//----------------------------------------------------------------------------\n// Account for finished transfer by freeing up the slot and un-tracking the\n// file identifier\n//----------------------------------------------------------------------------\nvoid\nFsBalancer::FreeTxSlot(eos::IFileMD::id_t fid,\n                       FsBalanceInfo src, FsBalanceInfo dst)\n{\n  mBalanceStats.FreeTxSlot(src.mNodeInfo, dst.mNodeInfo);\n  gOFS->mFidTracker.RemoveEntry(fid);\n  {\n    eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n    auto* fs = FsView::gFsView.mIdView.lookupByID(dst.mFsId);\n\n    if (fs) {\n      fs->DecrementBalanceTx();\n    }\n  }\n  --mRunningJobs;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/balancer/FsBalancer.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file FsBalancer.hh\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//-----------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/Logging.hh\"\n#include \"common/ThreadPool.hh\"\n#include \"mgm/balancer/FsBalancerStats.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class FsBalancer taking care of balancing the data between file systems\n//! inside groups for a given space\n//------------------------------------------------------------------------------\nclass FsBalancer: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param space_name space that balancer it attached to\n  //----------------------------------------------------------------------------\n  FsBalancer(const std::string& space_name):\n    mSpaceName(space_name), mThreshold(10), mTxNumPerNode(2),\n    mTxRatePerNode(25), mBalanceStats(space_name),\n    mThreadPool(10, 100, 10 , 6, 5, \"balance\")\n  {\n    mThread.reset(&FsBalancer::Balance, this);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~FsBalancer()\n  {\n    mThread.join();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set max size of the thread pool used for balancing\n  //----------------------------------------------------------------------------\n  inline void SetMaxThreadPoolSize(unsigned int max)\n  {\n    mThreadPool.SetMaxThreads(max);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get thread pool info\n  //!\n  //! @return string summary for the thread pool\n  //----------------------------------------------------------------------------\n  std::string GetThreadPoolInfo() const\n  {\n    return mThreadPool.GetInfo();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Signal balancer to perform a configuration update\n  //----------------------------------------------------------------------------\n  inline void SignalConfigUpdate()\n  {\n    mDoConfigUpdate.store(true, std::memory_order_release);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Loop handling balancing jobs\n  //----------------------------------------------------------------------------\n  void Balance(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Account started (queued) transfers by reserving the slot on the\n  //! corresponding endpoints\n  //!\n  //! @param src source balancer info\n  //! @param dst destination balancer info\n  //----------------------------------------------------------------------------\n  void TakeTxSlot(const FsBalanceInfo& src, const FsBalanceInfo& dst);\n\n  //----------------------------------------------------------------------------\n  //! Account finished transfers by freeing up slot and un-tracking the file\n  //! identifier\n  //!\n  //! @param fid file identifier\n  //! @param src source balancer info object\n  //! @param dst destnation balancer info object\n  //----------------------------------------------------------------------------\n  void FreeTxSlot(eos::IFileMD::id_t fid, FsBalanceInfo src, FsBalanceInfo dst);\n\n#ifdef IN_TEST_HARNESS\npublic:\n#else\nprivate:\n#endif\n  AssistedThread mThread; ///< Main balancer thread\n  std::string mSpaceName; ///< Name of the space balancer belongs to\n  std::atomic<bool> mDoConfigUpdate {true}; ///< Signal a config update\n  bool mIsEnabled {true};\n  //! Threshold value that represents distance form the average from which\n  //! file systems are considered for balancing\n  double mThreshold;\n  unsigned int mTxNumPerNode; ///< Number of concurrent transfers per node\n  unsigned int mTxRatePerNode; ///< Max transfer rate per node MB/s\n  FsBalancerStats mBalanceStats; ///< Balancer stats\n  eos::common::ThreadPool mThreadPool; ///< Thread pool for balancing jobs\n  unsigned int mMaxQueuedJobs {1000}; ///< Max number of queued jobs\n  unsigned int mMaxThreadPoolSize {100}; ///< Max number of threads\n  std::atomic<uint64_t> mRunningJobs {0}; ///< Number of running/queued jobs\n  std::chrono::seconds mUpdInterval {60}; ///< Balance stats update interval\n\n  //----------------------------------------------------------------------------\n  //! Update balancer config based on the info registered at the space\n  //----------------------------------------------------------------------------\n  void ConfigUpdate();\n\n  //----------------------------------------------------------------------------\n  //! Get iterator to a random start element in the vector\n  //!\n  //! @param vect vector object\n  //----------------------------------------------------------------------------\n  template<typename Vect>\n  static typename Vect::iterator GetRandomIter(Vect& vect);\n\n  //----------------------------------------------------------------------------\n  //! Get file identifier to balance from the given source file system\n  //!\n  //! @param src source file system obj\n  //! @param set_dsts set of suitable destination file systems\n  //! @param dst selected destination file system obj\n  //!\n  //! @return file identifier or 0ull if no file found\n  //----------------------------------------------------------------------------\n  eos::IFileMD::id_t\n  GetFileToBalance(const FsBalanceInfo& src,\n                   const std::set<FsBalanceInfo>& set_dsts, FsBalanceInfo& dst);\n};\n\n//------------------------------------------------------------------------------\n//! Get iterator to a random start element in the vector\n//----------------------------------------------------------------------------\ntemplate<typename Vect>\ntypename Vect::iterator\nFsBalancer::GetRandomIter(Vect& vect)\n{\n  typename Vect::iterator iter = vect.begin();\n  const size_t size = vect.size();\n\n  if (!size) {\n    return iter;\n  }\n\n  size_t index = eos::common::getRandom(1ul, size);\n\n  while (index > 1) {\n    --index;\n    ++iter;\n  }\n\n  return iter;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/balancer/FsBalancerStats.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file FsBalancerStats.cc\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//-----------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/balancer/FsBalancerStats.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Update statistics with information from the group and file systems stats\n//------------------------------------------------------------------------------\nvoid\nFsBalancerStats::UpdateInfo(eos::mgm::FsView* fs_view, double threshold)\n{\n  std::set<std::string> grp_to_remove;\n  std::set<std::string> grp_to_update;\n  auto grp_dev = fs_view->GetUnbalancedGroups(mSpaceName, threshold);\n\n  for (const auto& elem : grp_dev) {\n    // Check if group needs to be added or updated\n    auto it = mGrpToMaxDev.find(elem.first);\n\n    if (it != mGrpToMaxDev.end()) {\n      // Check against the cached value\n      const double grp_dev = it->second.first;\n      const auto last_upd_ts = it->second.second;\n      eos_static_info(\"msg=\\\"compare group max abs deviation\\\" group=%s \"\n                      \"current=%0.2f new=%0.2f last_update_ts=%llu\",\n                      elem.first.c_str(), grp_dev, elem.second,\n                      std::chrono::duration_cast<std::chrono::milliseconds>\n                      (last_upd_ts.time_since_epoch()).count());\n      bool do_update = false;\n\n      // Trigger update due to group max dev changes\n      if (std::abs(grp_dev - std::floor(elem.second)) >= sGrpDevUpdThreshold) {\n        do_update = true;\n      }\n\n      // Trigger time based update\n      if (!do_update &&\n          (std::chrono::duration_cast<std::chrono::minutes>\n           (std::chrono::steady_clock::now() - last_upd_ts).count() >=\n           sGrpUpdTimeThreshold)) {\n        do_update = true;\n      }\n\n      if (do_update) {\n        grp_to_update.insert(elem.first);\n      }\n    } else {\n      grp_to_update.insert(elem.first);\n    }\n  }\n\n  // Check if there are groups that need to be removed\n  for (const auto& elem : mGrpToMaxDev) {\n    if (grp_dev.find(elem.first) == grp_dev.end()) {\n      grp_to_remove.insert(elem.first);\n    }\n  }\n\n  for (const auto& grp : grp_to_remove) {\n    mGrpToMaxDev.erase(grp);\n    mGrpToPrioritySets.erase(grp);\n  }\n\n  if (grp_to_update.empty()) {\n    return;\n  }\n\n  for (const auto& grp : grp_to_update) {\n    mGrpToMaxDev[grp] = std::make_pair(grp_dev[grp],\n                                       std::chrono::steady_clock::now());\n    mGrpToPrioritySets[grp] =\n      fs_view->GetFsToBalance(grp, FsPrioritySets::sThreshold);\n  }\n\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Decide if an update of the data structures is needed\n//------------------------------------------------------------------------------\nbool\nFsBalancerStats::NeedsUpdate(std::chrono::seconds upd_interval)\n{\n  using namespace std::chrono;\n\n  // Trigger update if interval elapsed\n  if (duration_cast<seconds>(system_clock::now() - mLastTs).count()\n      >= upd_interval.count()) {\n    mLastTs = system_clock::now();\n    return true;\n  }\n\n  return false;\n}\n\n//----------------------------------------------------------------------------\n// Get vector of balance source and destination file systems to be used for\n// doing transfers, one entry per group to be balanced\n//----------------------------------------------------------------------------\nVectBalanceFs\nFsBalancerStats::GetTxEndpoints()\n{\n  VectBalanceFs ret;\n\n  for (auto& elem : mGrpToPrioritySets) {\n    std::set<FsBalanceInfo>& dst_fses = elem.second.mPrioLow;\n\n    if (dst_fses.empty()) {\n      dst_fses = elem.second.mLow;\n\n      if (dst_fses.empty()) {\n        continue;\n      }\n    }\n\n    std::set<FsBalanceInfo>& src_fses = elem.second.mPrioHigh;\n\n    if (src_fses.empty()) {\n      src_fses = elem.second.mHigh;\n\n      if (src_fses.empty()) {\n        continue;\n      }\n    }\n\n    ret.emplace_back(std::make_pair(src_fses, dst_fses));\n  }\n\n  return ret;\n}\n\n//------------------------------------------------------------------------------\n// Check if node still has available transfer slots\n//------------------------------------------------------------------------------\nbool\nFsBalancerStats::HasTxSlot(const std::string& node_id,\n                           unsigned int tx_per_node) const\n{\n  std::unique_lock<std::mutex> scope_lock(mMutex);\n  auto it = mNodeNumTx.find(node_id);\n\n  if (it == mNodeNumTx.end()) {\n    return true;\n  }\n\n  if (it->second < tx_per_node) {\n    return true;\n  }\n\n  return false;\n}\n\n\n//------------------------------------------------------------------------------\n// Account for new transfer slot\n//----------------------------------------------------------------------------\nvoid\nFsBalancerStats::TakeTxSlot(const std::string& src_node,\n                            const std::string& dst_node)\n{\n  std::unique_lock<std::mutex> scope_lock(mMutex);\n  ++mNodeNumTx[src_node];\n  ++mNodeNumTx[dst_node];\n}\n\n//----------------------------------------------------------------------------\n//! Account for finished transfer by freeing up a slot\n//----------------------------------------------------------------------------\nvoid\nFsBalancerStats::FreeTxSlot(const std::string& src_node,\n                            const std::string& dst_node)\n{\n  std::unique_lock<std::mutex> scope_lock(mMutex);\n  auto it = mNodeNumTx.find(src_node);\n\n  if (it != mNodeNumTx.end() && it->second) {\n    --it->second;\n  }\n\n  it = mNodeNumTx.find(dst_node);\n\n  if (it != mNodeNumTx.end() && it->second) {\n    --it->second;\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/balancer/FsBalancerStats.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file FsBalancerStats.hh\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//-----------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\no *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"mgm/fsview/FsView.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//! Forward declaration\nclass FsView;\n//! Helper alias\nusing BalancePair = std::pair<FsBalanceInfo, FsBalanceInfo>;\nusing VectBalanceFs = std::vector<std::pair<std::set<FsBalanceInfo>,\n      std::set<FsBalanceInfo>>>;\n\n//------------------------------------------------------------------------------\n//! Class FsBalancerStats is responsible for collecting and computing\n//! statistics based on which the FsBalancer will decide what actions to take.\n//------------------------------------------------------------------------------\nclass FsBalancerStats: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  FsBalancerStats(const std::string& space_name):\n    eos::common::LogId(),\n    mSpaceName(space_name),\n    mLastTs(std::chrono::system_clock::now())\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~FsBalancerStats() = default;\n\n  //----------------------------------------------------------------------------\n  //! Update statistics with information from the group and file systems stats\n  //!\n  //! @param fs_view FsView aggregating all the fs/group objects\n  //! @param threshold value for selecting file systems that deviate more than\n  //!        this from the average values.\n  //----------------------------------------------------------------------------\n  void UpdateInfo(FsView* fs_view, double threshold);\n\n  //----------------------------------------------------------------------------\n  //! Decide if an update of the data structures is needed\n  //!\n  //! @param upd_interval update interval\n  //! @return true if update should be done, otherwise false\n  //----------------------------------------------------------------------------\n  bool NeedsUpdate(std::chrono::seconds upd_interval);\n\n  //----------------------------------------------------------------------------\n  //! Get list of balance source and destination file systems to be used for\n  //! doing transfers.\n  //!\n  //! @return vector of source and destination file systems for each group that\n  //!         needs balancing\n  //----------------------------------------------------------------------------\n  VectBalanceFs GetTxEndpoints();\n\n  //----------------------------------------------------------------------------\n  //! Account for new transfer by reserving a slot\n  //!\n  //! @param src_node node identifier <host>:<port> for source\n  //! @param dst_node node identifier <host>:<port> for destination\n  //----------------------------------------------------------------------------\n  void TakeTxSlot(const std::string& src_node, const std::string& dst_node);\n\n  //----------------------------------------------------------------------------\n  //! Account for finished transfer by freeing up a slot\n  //!\n  //! @param src_node node identifier <host>:<port> for source\n  //! @param dst_node node identifier <host>:<port> for destination\n  //----------------------------------------------------------------------------\n  void FreeTxSlot(const std::string& src_node, const std::string& dst_node);\n\n  //----------------------------------------------------------------------------\n  //! Check if node still has available transfer slots\n  //!\n  //! @param node_id node identifier <host>:<port>\n  //! @param tx_per_node max number of transfers per node\n  //!\n  //! @return true if slot available, otherwise false\n  //----------------------------------------------------------------------------\n  bool HasTxSlot(const std::string& node_id, unsigned int tx_per_node) const;\n\n#ifdef IN_TEST_HARNESS\npublic:\n#else\nprivate:\n#endif\n  //! Group max deviation modification threshold that triggers an update\n  static constexpr double sGrpDevUpdThreshold = 0.25;\n  //! Time threshold value in minutes that triggers an update\n  static constexpr int sGrpUpdTimeThreshold = 10;\n  std::string mSpaceName;\n  //! Map groups to balance (above threshold) to max deviation and last update\n  //! timestamp, acting as a cache\n  std::map<std::string,\n      std::pair<double,\n      std::chrono::time_point<std::chrono::steady_clock>\n      >\n      > mGrpToMaxDev;\n  //! Map groups to tuple of priority sets to be used when selecting source\n  //! and destination filesystems\n  std::map<std::string, FsPrioritySets> mGrpToPrioritySets;\n  //! Map node FQDN to number of ongoing transfers\n  std::map<std::string, unsigned int> mNodeNumTx;\n  //! Last timestamp when an update was done\n  std::chrono::time_point<std::chrono::system_clock> mLastTs;\n  mutable std::mutex mMutex;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/bulk-request/BulkRequest.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file BulkRequest.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"BulkRequest.hh\"\n\n#include <map>\n\nEOSBULKNAMESPACE_BEGIN\n\nconst std::map<BulkRequest::Type, std::string> BulkRequest::BULK_REQ_TYPE_TO_STRING_MAP = {\n  {BulkRequest::PREPARE_STAGE, \"PREPARE_STAGE\"},\n  {BulkRequest::PREPARE_EVICT, \"PREPARE_EVICT\"},\n  {BulkRequest::PREPARE_CANCEL, \"PREPARE_CANCEL\"}\n};\n\nEOSBULKNAMESPACE_END\n"
  },
  {
    "path": "mgm/bulk-request/BulkRequest.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file BulkRequest.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_BULKREQUEST_HH\n#define EOS_BULKREQUEST_HH\n\n#include \"mgm/Namespace.hh\"\n#include <string>\n#include <set>\n#include <map>\n#include <common/VirtualIdentity.hh>\n#include \"FileCollection.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * This class represents a BulkRequest object\n */\nclass BulkRequest\n{\npublic:\n\n  /**\n   * The type a bulk request can be\n   */\n  enum Type {\n    PREPARE_STAGE,\n    PREPARE_EVICT,\n    PREPARE_CANCEL\n  };\n\n  /**\n   * Initializes the bulk request with the id passed in parameter\n   * @param id the unique identifier of the bulk request\n   */\n  BulkRequest(const std::string& id): mId(id) {}\n  /**\n   * Returns the id of this bulk request\n   * @return the id of this bulk request\n   */\n  std::string getId() const { return mId; }\n  /**\n   * Returns the type of this bulk request\n   * @return the type of this bulk request\n   */\n  virtual Type getType() const = 0;\n\n  /**\n   * Returns the files contained in this bulk request\n   * @return the map containing the files this bulk request contains\n   */\n  std::shared_ptr<FileCollection::Files> getFiles() const { return mFileCollection.getAllFiles(); }\n\n  std::shared_ptr<FileCollection::FilesMap> getFilesMap() const { return mFileCollection.getFilesMap(); }\n\n  /**\n   * Adds a File to the bulk-request\n   * @param file the file to add to the bulk-request File container\n   */\n  virtual void addFile(std::unique_ptr<File>&& file) { mFileCollection.addFile(std::move(file)); }\n\n  virtual ~BulkRequest() {}\n\n  /**\n   * Return the string representation of the bulk request type passed in parameter\n   * @param type the type of the bulk request\n   * @return the string representation of the bulk request type passed in parameter\n   */\n  static std::string bulkRequestTypeToString(const Type& bulkRequestType) { return BULK_REQ_TYPE_TO_STRING_MAP.at(bulkRequestType); }\n\n  /**\n   * Returns the set of files that have an error\n   * @return the set of files that have an error\n   */\n  std::shared_ptr<std::set<File>> getAllFilesInError() const { return mFileCollection.getAllFilesInError(); }\nprivate:\n  //Id of the bulk request\n  std::string mId;\n  //The collection of files that are hold by this bulk-request\n  FileCollection mFileCollection;\n  //Map containing the string representation of each bulk-request type\n  static const std::map<Type, std::string> BULK_REQ_TYPE_TO_STRING_MAP;\n};\n\nEOSBULKNAMESPACE_END\n#endif // EOS_BULKREQUEST_HH\n"
  },
  {
    "path": "mgm/bulk-request/BulkRequestFactory.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file BulkRequestFactory.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"BulkRequestFactory.hh\"\n#include \"BulkRequestHelper.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nstd::unique_ptr<StageBulkRequest> BulkRequestFactory::createStageBulkRequest(\n  const common::VirtualIdentity& issuerVid)\n{\n  std::string bulkRequestId = BulkRequestHelper::generateBulkRequestId();\n  return std::make_unique<StageBulkRequest>(bulkRequestId, issuerVid);\n}\n\nstd::unique_ptr<StageBulkRequest> BulkRequestFactory::createStageBulkRequest(\n  const std::string& requestId, const common::VirtualIdentity& issuerVid)\n{\n  return std::make_unique<StageBulkRequest>(requestId, issuerVid);\n}\n\nstd::unique_ptr<StageBulkRequest> BulkRequestFactory::createStageBulkRequest(\n  const std::string& requestId, const common::VirtualIdentity& issuerVid,\n  const time_t& creationTime)\n{\n  return std::make_unique<StageBulkRequest>(requestId, issuerVid, creationTime);\n}\n\nstd::unique_ptr<CancellationBulkRequest>\nBulkRequestFactory::createCancelBulkRequest(const std::string& id)\n{\n  return std::make_unique<CancellationBulkRequest>(id);\n}\n\nEOSBULKNAMESPACE_END\n"
  },
  {
    "path": "mgm/bulk-request/BulkRequestFactory.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file BulkRequestFactory.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_BULKREQUESTFACTORY_HH\n#define EOS_BULKREQUESTFACTORY_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/bulk-request/prepare/StageBulkRequest.hh\"\n#include \"mgm/bulk-request/prepare/EvictBulkRequest.hh\"\n#include \"mgm/bulk-request/prepare/CancellationBulkRequest.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * Factory of bulk request\n */\nclass BulkRequestFactory\n{\npublic:\n  /**\n   * Returns a new StageBulkRequest with a unique identifier\n   * @param issuerVid the vid of the person who creates this bulk-request\n   * @return a new StageBulkRequest\n   */\n  static std::unique_ptr<StageBulkRequest> createStageBulkRequest(\n    const common::VirtualIdentity& issuerVid);\n\n  /**\n   * Returns a new cancel bulk-request\n   * @param id the id of the cancel bulk-request (normally is equal to a previously submitted stage bulk-request id)\n   * @return a new CancelBulkRequest\n   */\n  static std::unique_ptr<CancellationBulkRequest> createCancelBulkRequest(\n    const std::string& id);\n\n  /**\n   * Creates a new StageBulkRequest with the requestId and the issuerVid of this bulk-request\n   * @return\n   */\n  static std::unique_ptr<StageBulkRequest> createStageBulkRequest(\n    const std::string& requestId, const common::VirtualIdentity& issuerVid);\n\n  /**\n   * Creates a new StageBulkRequest with the requestId, issuerVid and the creation time of this bulk-request\n   * @return a new StageBulkRequest\n   */\n  static std::unique_ptr<StageBulkRequest> createStageBulkRequest(\n    const std::string& requestId, const common::VirtualIdentity& issuerVid,\n    const time_t& creationTime);\n\n};\n\nEOSBULKNAMESPACE_END\n#endif // EOS_BULKREQUESTFACTORY_HH\n"
  },
  {
    "path": "mgm/bulk-request/BulkRequestHelper.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file BulkRequestHelper.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_BULKREQUESTHELPER_HH\n#define EOS_BULKREQUESTHELPER_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"common/StringConversion.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nclass BulkRequestHelper\n{\npublic:\n  /**\n   * Allows to generate a bulk-request ID\n   * @return the id for a new BulkRequest\n   */\n  static std::string generateBulkRequestId()\n  {\n    return common::StringConversion::timebased_uuidstring();\n  }\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_BULKREQUESTHELPER_HH\n"
  },
  {
    "path": "mgm/bulk-request/File.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file File.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_FILE_HH\n#define EOS_FILE_HH\n\n#include \"mgm/Namespace.hh\"\n#include <string>\n#include <optional>\n#include <map>\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * This class contains information\n * about a file hold in a bulk-request\n */\nclass File\n{\npublic:\n  File() {}\n  File(const std::string& path): mPath(path) {}\n  void setPath(const std::string& path) { mPath = path; }\n  void setError(const std::string& error) {\n    if (!error.empty()) {\n      std::optional<std::string> errorOpt(error);\n      setError(errorOpt);\n    }\n  }\n  void setError(const std::optional<std::string>& error) { mError = error; }\n  /**\n   * Set the error passed in parameter to the file\n   * only if there is not already an error set\n   * @param error the error to set\n   */\n  void setErrorIfNotAlreadySet(const std::string& error) {\n    if (!getError()) {\n      setError(error);\n    }\n  }\n\n  const std::string getPath() const { return mPath; }\n  const std::optional<std::string> getError() const { return mError; }\n\n  bool operator==(const File& other) const { return getPath() == other.getPath(); }\n  bool operator<(const File& other) const { return getPath() < other.getPath(); }\n\nprivate:\n  /**\n   * The path of the file\n   */\n  std::string mPath;\n  /**\n   * An eventual error message\n   */\n  std::optional<std::string> mError;\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_FILE_HH\n"
  },
  {
    "path": "mgm/bulk-request/FileCollection.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file FileCollection.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_FILECOLLECTION_HH\n#define EOS_FILECOLLECTION_HH\n\n#include \"File.hh\"\n#include \"mgm/Namespace.hh\"\n#include <set>\n#include <vector>\n#include <map>\n#include <memory>\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * This class manages a collection of files\n */\nclass FileCollection\n{\npublic:\n  FileCollection() {\n    mFiles.reset(new FilesMap());\n    mFilesInsertOrder.reset(new FilesInsertOrder());\n  }\n  FileCollection& operator=(const FileCollection& other) {\n    if (this != &other) {\n      this->mFiles = other.mFiles;\n    }\n    return *this;\n  }\n  /**\n   * The collection is a map of <path,File>\n   */\n  typedef std::vector<File*> Files;\n  typedef std::multimap<std::string, std::unique_ptr<File>> FilesMap;\n  typedef std::vector<FilesMap::iterator> FilesInsertOrder;\n  /**\n   * Adds the file passed in parameter to this collection\n   * The key of this item will be the path of the file and the value\n   * will be the file itself\n   * @param file the file to add to this collection\n   */\n  void addFile(std::unique_ptr<File>&& file) {\n    auto insertedElementItor = mFiles->insert(\n      std::pair<std::string, std::unique_ptr<File>>(file->getPath(), std::move(file)));\n    mFilesInsertOrder->emplace_back(insertedElementItor);\n  }\n  /**\n   * Returns all the files that belongs to this collection\n   * @return the pointer of the collection (map) managed by this class\n   */\n  const std::shared_ptr<FileCollection::Files> getAllFiles() const {\n    std::shared_ptr<FileCollection::Files> ret = std::make_shared<FileCollection::Files>();\n    for (auto& itor : *mFilesInsertOrder) {\n      ret->emplace_back(itor->second.get());\n    }\n    return ret;\n  }\n\n  /**\n   * Returns the pointer of the map<path,File> that contains the files of this collection\n   * @return the pointer of the map<path,File> that contains the files of this collection\n   */\n  const std::shared_ptr<FileCollection::FilesMap> getFilesMap() const { return mFiles; }\n  /**\n   * Returns the files that have an error\n   * @return the files that have an error\n   */\n  const std::shared_ptr<std::set<File>> getAllFilesInError() const {\n    std::shared_ptr<std::set<File>> filesInError(new std::set<File>());\n    for (const auto& pathFile : *mFiles) {\n      if (pathFile.second->getError()) {\n        filesInError->insert(*pathFile.second);\n      }\n    }\n    return filesInError;\n  }\nprivate:\n  std::shared_ptr<FileCollection::FilesMap> mFiles;\n  std::shared_ptr<FileCollection::FilesInsertOrder> mFilesInsertOrder;\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_FILECOLLECTION_HH\n"
  },
  {
    "path": "mgm/bulk-request/README.md",
    "content": "## Bulk Request Module\n\nThis module implements the data structures and business logic for EOS bulk requests (stage, evict, cancel). The focus is on a clear domain model, simple construction via a small factory, and pluggable persistence through DAO factories. Trivial implementations are header-only to reduce file count and build time.\n\n### Layout\n\n```\nbulk-request/\n  BulkRequest.hh, BulkRequest.cc           # Base model and shared utilities\n  File.hh                                   # Header-only representation of a file entry\n  FileCollection.hh                         # Header-only container for files\n\n  prepare/\n    StageBulkRequest.hh                     # Header-only stage request\n    EvictBulkRequest.hh                     # Header-only evict request\n    CancellationBulkRequest.hh              # Header-only cancel request\n    manager/\n      PrepareManager.cc                     # Orchestration utilities\n      BulkRequestPrepareManager.cc          # High-level prepare orchestration\n    query-prepare/\n      QueryPrepareResult.cc                 # Query/inspection helpers\n\n  business/\n    BulkRequestBusiness.hh, .cc             # Business operations using DAO factory\n\n  dao/\n    IBulkRequestDAO.hh                      # DAO interface\n    factories/\n      AbstractDAOFactory.hh                 # Abstract persistency factory\n      ProcDirectoryDAOFactory.cc            # Proc-dir implementation factory\n    proc/\n      ProcDirectoryBulkRequestDAO.hh, .cc   # Proc-dir DAO implementation\n      ProcDirectoryBulkRequestLocations.cc  # Proc-dir path helpers\n      ProcDirBulkRequestFile.cc             # Proc-dir file IO helpers\n    proc/cleaner/\n      BulkRequestProcCleaner.cc             # Cleanup utilities\n      BulkRequestProcCleanerConfig.cc       # Cleaner configuration\n\n  exception/\n    BulkRequestException.hh                 # Header-only generic bulk exception\n    PersistencyException.hh                 # Header-only persistency exception\n\n  BulkRequestFactory.hh, .cc                # Factory for concrete bulk requests\n```\n\n### Key Concepts\n\n- Model\n  - `BulkRequest` is the abstract base containing common state and utilities.\n  - `StageBulkRequest`, `EvictBulkRequest`, `CancellationBulkRequest` are small, header-only concrete types that only specialize `getType()` and construction.\n  - `File` (header-only) captures a path and optional error state.\n  - `FileCollection` (header-only) manages the per-request set of files while preserving insertion order and providing views/filtering.\n\n- Business Layer\n  - `BulkRequestBusiness` provides read/write operations for bulk requests using an injected `AbstractDAOFactory` to remain storage-agnostic.\n\n- Persistence\n  - `IBulkRequestDAO` defines the persistence API.\n  - The `proc` implementation uses the filesystem under a proc-style directory. The `ProcDirectoryDAOFactory` wires its concrete DAOs.\n  - Clean-up helpers maintain the proc directory structure.\n\n- Factory\n  - `BulkRequestFactory` centralizes construction of concrete `BulkRequest` types.\n\n- Exceptions\n  - `BulkRequestException` and `PersistencyException` are header-only; use them for module-specific errors.\n\n### Header-only Consolidation\n\nTo reduce file count and simplify navigation, trivial implementations were made header-only:\n- `File`, `FileCollection`\n- `StageBulkRequest`, `EvictBulkRequest`, `CancellationBulkRequest`\n- `BulkRequestException`, `PersistencyException`\n\nThe removed `.cc` files were pruned from `eos/mgm/CMakeLists.txt`.\n\n### Adding a New Bulk Request Type\n\n1. Create a new header under `prepare/` deriving from `BulkRequest`.\n   - Implement a minimal constructor and `getType()` inline in the header.\n2. Register the new type in `BulkRequestFactory` for construction.\n3. Extend any business or DAO logic if the new type requires different persistence or behavior.\n4. Update tests accordingly.\n\nExample minimal header-only request:\n```c++\nclass MyNewBulkRequest : public BulkRequest {\npublic:\n  explicit MyNewBulkRequest(const std::string& id) : BulkRequest(id) {}\n  Type getType() const override { return /* new enum value */; }\n};\n```\n\n### Error Handling Guidelines\n\n- Throw `PersistencyException` for storage/DAO failures.\n- Throw `BulkRequestException` for domain errors (invalid inputs, unsupported transitions, etc.).\n  - Prefer precise, actionable error messages. Upstream layers should map these to appropriate responses/logging.\n\n### Notes\n\n- Keep trivial logic inline in headers to avoid unnecessary source files.\n- Prefer descriptive names and minimize cross-header dependencies.\n- When adding persistence backends, implement a new `AbstractDAOFactory` and corresponding DAOs; avoid branching in the business layer.\n\n\n"
  },
  {
    "path": "mgm/bulk-request/business/BulkRequestBusiness.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file BulkRequestBusiness.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <mgm/bulk-request/exception/PersistencyException.hh>\n#include \"BulkRequestBusiness.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nBulkRequestBusiness::BulkRequestBusiness(std::unique_ptr<AbstractDAOFactory>&&\n    daoFactory) : mDaoFactory(std::move(daoFactory))\n{\n}\n\nvoid BulkRequestBusiness::saveBulkRequest(const BulkRequest* req)\n{\n  eos_static_info(\"msg=\\\"Persisting bulk request id=%s nbFiles=%ld type=%s\\\"\",\n                  req->getId().c_str(), req->getFiles()->size(),\n                  BulkRequest::bulkRequestTypeToString(req->getType()).c_str());\n  EXEC_TIMING_BEGIN(\"BulkRequestBusiness::saveBulkRequest\");\n\n  switch (req->getType()) {\n  case BulkRequest::PREPARE_STAGE: {\n    mDaoFactory->getBulkRequestDAO()->saveBulkRequest(\n      static_cast<const StageBulkRequest*>(req));\n    break;\n  }\n\n  case BulkRequest::PREPARE_CANCEL: {\n    mDaoFactory->getBulkRequestDAO()->saveBulkRequest(\n      static_cast<const CancellationBulkRequest*>(req));\n    break;\n  }\n\n  default:\n    std::stringstream errorMsg;\n    errorMsg << \"The bulk-request with the type \" <<\n             BulkRequest::bulkRequestTypeToString(req->getType()) << \" cannot be persisted.\";\n    throw PersistencyException(errorMsg.str());\n  }\n\n  EXEC_TIMING_END(\"BulkRequestBusiness::saveBulkRequest\");\n  eos_static_info(\"msg=\\\"Persisted bulk request id=%s\\\"\", req->getId().c_str());\n}\n\nstd::unique_ptr<BulkRequest> BulkRequestBusiness::getBulkRequest(\n  const std::string& bulkRequestId, const BulkRequest::Type& type)\n{\n  EXEC_TIMING_BEGIN(\"BulkRequestBusiness::getBulkRequest\");\n  std::unique_ptr<BulkRequest> bulkRequest =\n    mDaoFactory->getBulkRequestDAO()->getBulkRequest(bulkRequestId, type);\n  EXEC_TIMING_END(\"BulkRequestBusiness::getBulkRequest\");\n\n  if (bulkRequest != nullptr) {\n    eos_static_info(\"msg=\\\"Retrieved bulk request id=%s from persistence layer\\\"\",\n                    bulkRequestId.c_str());\n  } else {\n    eos_static_info(\"msg=\\\"No bulk request with id=%s has been found in the persistence layer\\\"\",\n                    bulkRequestId.c_str());\n  }\n\n  return bulkRequest;\n}\n\nstd::unique_ptr<StageBulkRequest> BulkRequestBusiness::getStageBulkRequest(\n  const std::string& bulkRequestId)\n{\n  EXEC_TIMING_BEGIN(\"BulkRequestBusiness::getStageBulkRequest\");\n  std::unique_ptr<StageBulkRequest> ret = nullptr;\n  std::unique_ptr<BulkRequest> bulkRequest =\n    mDaoFactory->getBulkRequestDAO()->getBulkRequest(bulkRequestId,\n        BulkRequest::PREPARE_STAGE);\n  EXEC_TIMING_END(\"BulkRequestBusiness::getStageBulkRequest\");\n\n  if (bulkRequest != nullptr) {\n    eos_static_info(\"msg=\\\"Retrieved bulk request id=%s from persistence layer\\\"\",\n                    bulkRequestId.c_str());\n    ret.reset(static_cast<StageBulkRequest*>(bulkRequest.release()));\n  } else {\n    eos_static_info(\"msg=\\\"No bulk request with id=%s has been found in the persistence layer\\\"\",\n                    bulkRequestId.c_str());\n  }\n\n  return ret;\n}\n\nbool BulkRequestBusiness::exists(const std::string& bulkRequestId,\n                                 const BulkRequest::Type& type)\n{\n  return mDaoFactory->getBulkRequestDAO()->exists(bulkRequestId, type);\n}\n\nvoid BulkRequestBusiness::deleteBulkRequest(const BulkRequest* req)\n{\n  return mDaoFactory->getBulkRequestDAO()->deleteBulkRequest(req);\n}\n\nEOSBULKNAMESPACE_END"
  },
  {
    "path": "mgm/bulk-request/business/BulkRequestBusiness.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file BulkRequestBusiness.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_BULKREQUESTBUSINESS_HH\n#define EOS_BULKREQUESTBUSINESS_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/bulk-request/BulkRequest.hh\"\n#include \"mgm/bulk-request/dao/factories/AbstractDAOFactory.hh\"\n#include <common/Logging.hh>\n#include <memory>\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * This class contains the business logic linked to the bulk requests\n * It basically allows to get a bulk request, persist it, ... from the DAOs returned\n * by the AbstractDAOFactory given when constructing this object.\n */\nclass BulkRequestBusiness\n{\npublic:\n  /**\n   * Constructor of the BulkRequestBusiness object\n   * The daoFactory that is passed in parameter will be used by the different methods\n   * of this class so that it instanciate the correct Data Access Object. Depending on the\n   * implementation of the AbstractDAOFactory, the underlying persistency layer of the DAO will change\n   * @param daoFactory the factory of DAO\n   */\n  BulkRequestBusiness(std::unique_ptr<AbstractDAOFactory>&& daoFactory);\n  /**\n   * Allows to persist the bulk-request\n   * @param req the bulk-request to persist\n   */\n  void saveBulkRequest(const BulkRequest* req);\n\n  /**\n   * Returns the bulk-request persisted\n   * @param bulkRequestId the id of the bulk-request\n   * @param type the type of the bulk-request\n   * @return the bulk-request associated to the id and the type passed in parameter\n   */\n  std::unique_ptr<BulkRequest> getBulkRequest(const std::string& bulkRequestId,\n      const BulkRequest::Type& type);\n\n  /**\n   * Returns the stage bulk-request persisted\n   * @param bulkRequestId the id of the stage bulk-request\n   * @return the stage bulk-request associated to the id passed in parameter\n   */\n  std::unique_ptr<StageBulkRequest> getStageBulkRequest(const std::string&\n      bulkRequestId);\n\n  /**\n   * Returns true if the bulk-request corresponding to the id and the type\n   * passed in parameters exists, false otherwise\n   * @param bulkRequestId the id of the bulk-request to check\n   * @param type the type of the bulk-request to test\n   * @throws PersistencyException if an error happens during the check\n   */\n  bool exists(const std::string& bulkRequestId, const BulkRequest::Type& type);\n\n  /**\n   * Deletes the bulk-request passed in parameter\n   * @param req the bulk-request to delete\n   */\n  void deleteBulkRequest(const BulkRequest* req);\n\nprivate:\n  std::unique_ptr<AbstractDAOFactory> mDaoFactory;\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_BULKREQUESTBUSINESS_HH\n"
  },
  {
    "path": "mgm/bulk-request/dao/IBulkRequestDAO.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file IBulkRequestPersist.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_IBULKREQUESTDAO_HH\n#define EOS_IBULKREQUESTDAO_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/bulk-request/prepare/StageBulkRequest.hh\"\n#include \"mgm/bulk-request/prepare/CancellationBulkRequest.hh\"\n#include <memory>\n#include <chrono>\n#include <map>\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * Interface to the bulk request Data Access Object\n * It allows to access the persistency layer of the bulk requests\n */\nclass IBulkRequestDAO\n{\npublic:\n  /**\n   * Persists a stage-cancellation bulk-request\n   * @param bulkRequest the cancellation bulk-request to persist\n   */\n  virtual void saveBulkRequest(const CancellationBulkRequest* bulkRequest) = 0;\n\n  /**\n   * This method allows to persist a StageBulkRequest\n   * @param bulkRequest the bulk request to save\n   */\n  virtual void saveBulkRequest(const StageBulkRequest* bulkRequest) = 0;\n\n  /**\n   * Get the bulk-request from the persistence\n   * @param id the id of the bulk-request\n   * @param type the type of the bulk-request\n   * @return the bulk-request if it exists, nullptr otherwise\n   */\n  virtual std::unique_ptr<BulkRequest> getBulkRequest(const std::string& id,\n      const BulkRequest::Type& type) = 0;\n\n  /**\n   * Delete all the bulk-request of a certain type that were not accessed for a certain amount of seconds\n   * @param type the bulk-request type to look for\n   * @param seconds the number of seconds after which the bulk-requests can be deleted if they were not queried\n   * @returns the number of deleted bulk-request\n   */\n  virtual uint64_t deleteBulkRequestNotQueriedFor(const BulkRequest::Type& type,\n      const std::chrono::seconds& seconds) = 0;\n\n  /**\n   * Returns true if the bulk-request corresponding to the id and the type\n   * passed in parameters exists, false otherwise\n   * @param bulkRequestId the id of the bulk-request to check\n   * @param type the type of the bulk-request to test\n   * @returns true if the bulk-request exists, false otherwise\n   */\n  virtual bool exists(const std::string& bulkRequestId,\n                      const BulkRequest::Type& type) = 0;\n\n  /**\n   * Deletes the bulk-request passed in parameters from the persistency\n   * @param bulkRequest the bulk-request to delete\n   */\n  virtual void deleteBulkRequest(const BulkRequest* bulkRequest) = 0;\n\n  virtual ~IBulkRequestDAO() {}\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_IBULKREQUESTDAO_HH\n"
  },
  {
    "path": "mgm/bulk-request/dao/factories/AbstractDAOFactory.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file AbstractDAOFactory.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_ABSTRACTDAOFACTORY_HH\n#define EOS_ABSTRACTDAOFACTORY_HH\n\n#include \"mgm/Namespace.hh\"\n#include <memory>\n#include \"mgm/bulk-request/dao/IBulkRequestDAO.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * AbstractFactory of DAO (Data Access Object) linked to bulk-requests\n */\nclass AbstractDAOFactory\n{\npublic:\n  /**\n   * Returns a BulkRequestDAO object that will allow to access the persistency layer that will be used to store/access bulk requests metadata\n   * @return a BulkRequestDAO object that will allow to access the persistency layer that will be used to store/access bulk requests metadata\n   */\n  virtual std::unique_ptr<IBulkRequestDAO> getBulkRequestDAO() const = 0;\n\n  virtual ~AbstractDAOFactory() {}\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_ABSTRACTDAOFACTORY_HH\n"
  },
  {
    "path": "mgm/bulk-request/dao/factories/ProcDirectoryDAOFactory.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file ProcDirectoryDAOFactory.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"ProcDirectoryDAOFactory.hh\"\n#include \"mgm/bulk-request/dao/proc/ProcDirectoryBulkRequestDAO.hh\"\n\n\nEOSBULKNAMESPACE_BEGIN\n\nProcDirectoryDAOFactory::ProcDirectoryDAOFactory(XrdMgmOfs* fileSystem,\n    const ProcDirectoryBulkRequestLocations& bulkReqProcDirLocations): mFileSystem(\n        fileSystem), mBulkReqLocations(bulkReqProcDirLocations)\n{\n}\n\nstd::unique_ptr<IBulkRequestDAO> ProcDirectoryDAOFactory::getBulkRequestDAO()\nconst\n{\n  std::unique_ptr<IBulkRequestDAO> ret(new ProcDirectoryBulkRequestDAO(\n                                         mFileSystem, mBulkReqLocations));\n  return ret;\n}\n\nEOSBULKNAMESPACE_END\n"
  },
  {
    "path": "mgm/bulk-request/dao/factories/ProcDirectoryDAOFactory.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file ProcDirectoryDAOFactory.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_PROCDIRECTORYDAOFACTORY_HH\n#define EOS_PROCDIRECTORYDAOFACTORY_HH\n#include \"mgm/Namespace.hh\"\n#include \"mgm/bulk-request/dao/factories/AbstractDAOFactory.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/bulk-request/dao/proc/ProcDirectoryBulkRequestLocations.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * Factory of Data Access Object that will rely on\n * the /eos/.../proc directory\n */\nclass ProcDirectoryDAOFactory : public AbstractDAOFactory\n{\npublic:\n  /**\n   * Factory of ProcDirectoryDAO objects\n   * @param fileSystem that will allow to interact with the /proc/ directory\n   * @param bulkReqProcDirLocations the class that will allow to deduce the proc sub-paths where the bulk-request will be persisted/accessed\n   */\n  ProcDirectoryDAOFactory(XrdMgmOfs* fileSystem,\n                          const ProcDirectoryBulkRequestLocations& bulkReqProcDirLocations);\n  /**\n   * Returns the ProcDirectory bulk request DAO object to allow the persistence/access of the\n   * bulk-requests metada via the /eos/.../proc directory\n   * @return the BulkRequestDAO obje\n   */\n  std::unique_ptr<IBulkRequestDAO> getBulkRequestDAO() const;\nprivate:\n  XrdMgmOfs* mFileSystem;\n  const ProcDirectoryBulkRequestLocations& mBulkReqLocations;\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_PROCDIRECTORYDAOFACTORY_HH\n"
  },
  {
    "path": "mgm/bulk-request/dao/proc/ProcDirBulkRequestFile.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file ProcDirBulkRequestFile.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"ProcDirBulkRequestFile.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nProcDirBulkRequestFile::ProcDirBulkRequestFile(const std::string& path): mName(\n    path)\n{\n}\n\nvoid ProcDirBulkRequestFile::setFileId(const eos::common::FileId::fileid_t\n                                       fileId)\n{\n  mFileId = fileId;\n}\n\nconst std::optional<eos::common::FileId::fileid_t>\nProcDirBulkRequestFile::getFileId() const\n{\n  return mFileId;\n}\n\nvoid ProcDirBulkRequestFile::setError(const std::string& error)\n{\n  mError = error;\n}\n\nconst std::optional<std::string> ProcDirBulkRequestFile::getError() const\n{\n  return mError;\n}\n\nvoid ProcDirBulkRequestFile::setName(const std::string& name)\n{\n  mName = name;\n}\n\nconst std::string ProcDirBulkRequestFile::getName() const\n{\n  return mName;\n}\n\nbool ProcDirBulkRequestFile::operator<(const ProcDirBulkRequestFile& other)\nconst\n{\n  return getName() < other.getName();\n}\n\nbool ProcDirBulkRequestFile::operator==(const ProcDirBulkRequestFile& other)\nconst\n{\n  return getName() == other.getName();\n}\n\nEOSBULKNAMESPACE_END\n"
  },
  {
    "path": "mgm/bulk-request/dao/proc/ProcDirBulkRequestFile.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file ProcDirBulkRequestFile.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_PROCDIRBULKREQUESTFILE_HH\n#define EOS_PROCDIRBULKREQUESTFILE_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"common/FileId.hh\"\n#include <string>\n#include <optional>\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * Class that will hold information about a file that belongs to a bulk-request\n * in order to be persisted/retrieved in/from the proc directory\n */\nclass ProcDirBulkRequestFile\n{\npublic:\n  ProcDirBulkRequestFile(const std::string& name);\n  void setFileId(const eos::common::FileId::fileid_t fileId);\n  const std::optional<eos::common::FileId::fileid_t> getFileId() const;\n  void setError(const std::string& error);\n  const std::optional<std::string> getError() const;\n  void setName(const std::string& name);\n  const std::string getName() const;\n  bool operator<(const ProcDirBulkRequestFile& other) const;\n  bool operator==(const ProcDirBulkRequestFile& other) const;\nprivate:\n  //A file that does not exist will not have an id\n  std::optional<eos::common::FileId::fileid_t> mFileId;\n  std::string mName;\n  std::optional<std::string> mError;\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_PROCDIRBULKREQUESTFILE_HH\n"
  },
  {
    "path": "mgm/bulk-request/dao/proc/ProcDirectoryBulkRequestDAO.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file ProcDirectoryBulkRequestDAO.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"ProcDirectoryBulkRequestDAO.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/bulk-request/exception/PersistencyException.hh\"\n#include <common/StringConversion.hh>\n#include <namespace/Prefetcher.hh>\n#include \"namespace/interface/IView.hh\"\n#include \"mgm/proc/ProcCommand.hh\"\n#include \"mgm/bulk-request/BulkRequestFactory.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nProcDirectoryBulkRequestDAO::ProcDirectoryBulkRequestDAO(XrdMgmOfs* fileSystem,\n    const ProcDirectoryBulkRequestLocations& procDirectoryBulkRequestLocations):\n  mFileSystem(fileSystem),\n  mProcDirectoryBulkRequestLocations(procDirectoryBulkRequestLocations),\n  mVid(common::VirtualIdentity::Root())\n{\n}\n\nvoid ProcDirectoryBulkRequestDAO::saveBulkRequest(const StageBulkRequest*\n    bulkRequest)\n{\n  std::string directoryBulkReqPath = generateBulkRequestProcPath(bulkRequest);\n\n  try {\n    if (bulkRequest->getFiles()->size() == 0) {\n      std::ostringstream oss;\n      oss << \"In ProcDirectoryBulkRequestDAO::saveBulkRequest(), unable to persist the bulk-request id=\"\n          << bulkRequest->getId() << \" because it does not contain any files\";\n      throw PersistencyException(oss.str());\n    }\n\n    //The bulk-request directory will have one extended attribute per file belonging to the bulk-request\n    //The persistency consists of creating the directory, and set the extended attribute representing each file\n    //The key of the extended attribute will be the fid of the file, the value will be the eventual error that\n    //a file can have (prepare submission error...)\n    eos_debug(\"msg=\\\"Persistence of the bulk request %s : creating the directory %s\\\"\",\n              bulkRequest->getId().c_str(),\n              directoryBulkReqPath.c_str());\n    createBulkRequestDirectory(bulkRequest, directoryBulkReqPath);\n    eos_debug(\"msg=\\\"Persistence of the bulk request %s : creating the xattrs map from the bulk-request paths\\\"\",\n              bulkRequest->getId().c_str());\n    eos::IContainerMD::XAttrMap xattrs;\n    generateXattrsMapFromBulkRequest(bulkRequest, xattrs);\n    eos_debug(\"msg=\\\"Persistence of the bulk request %s : persisting the bulk-request information in the directory %s\\\"\",\n              bulkRequest->getId().c_str(), directoryBulkReqPath.c_str());\n    persistBulkRequestDirectory(directoryBulkReqPath, xattrs);\n  } catch (const PersistencyException& ex) {\n    cleanAfterExceptionHappenedDuringBulkRequestSave(directoryBulkReqPath);\n    throw ex;\n  }\n}\n\nvoid ProcDirectoryBulkRequestDAO::saveBulkRequest(const CancellationBulkRequest*\n    bulkRequest)\n{\n  cancelStageBulkRequest(bulkRequest);\n}\n\nvoid ProcDirectoryBulkRequestDAO::cancelStageBulkRequest(\n  const CancellationBulkRequest* bulkRequest)\n{\n  std::string bulkRequestProcPath = generateBulkRequestProcPath(bulkRequest);\n\n  if (bulkRequest->getFiles()->size() == 0) {\n    std::ostringstream oss;\n    oss << \"In ProcDirectoryBulkRequestDAO::cancelStageBulkRequest(), unable to cancel the bulk-request id=\"\n        << bulkRequest->getId() << \" because it does not contain any files\";\n    throw PersistencyException(oss.str());\n  }\n\n  eos::IContainerMD::XAttrMap xattrs;\n  generateXattrsMapFromBulkRequest(bulkRequest, xattrs);\n  persistBulkRequestDirectory(bulkRequestProcPath, xattrs);\n}\n\nvoid ProcDirectoryBulkRequestDAO::generateXattrsMapFromBulkRequest(\n  const BulkRequest* bulkRequest, eos::IContainerMD::XAttrMap& xattrs)\n{\n  // Set last access time of the bulk-request directory\n  std::time_t now = std::time(nullptr);\n  std::string nowStr = std::to_string(now);\n  xattrs[LAST_ACCESS_TIME_XATTR_NAME] = nowStr;\n}\n\nvoid ProcDirectoryBulkRequestDAO::generateXattrsMapFromBulkRequest(\n  const StageBulkRequest* bulkRequest, eos::IContainerMD::XAttrMap& xattrs)\n{\n  generateXattrsMapFromBulkRequest(static_cast<const BulkRequest*>(bulkRequest),\n                                   xattrs);\n  xattrs[ISSUER_UID_XATTR_NAME] = std::to_string(bulkRequest->getIssuerVid().uid);\n  xattrs[CREATION_TIME_XATTR_NAME] = std::to_string(\n                                       bulkRequest->getCreationTime());\n  std::map<bulk::File, folly::Future<IFileMDPtr>> filesWithMDFutures;\n  const auto files = bulkRequest->getFiles();\n\n  for (auto& file : *files) {\n    std::string path = file->getPath();\n    std::pair<bulk::File, folly::Future<IFileMDPtr>> itemToInsert(*file,\n        mFileSystem->eosView->getFileFut(path , false));\n    filesWithMDFutures.emplace(std::move(itemToInsert));\n  }\n\n  for (auto& fileMd : filesWithMDFutures) {\n    fileMd.second.wait();\n  }\n\n  for (auto& fileWithMDFuture : filesWithMDFutures) {\n    const std::string currentFilePath = fileWithMDFuture.first.getPath();\n    std::shared_ptr<IFileMD> file;\n    std::string fid;\n\n    try {\n      eos::common::RWMutexReadLock nsLock(mFileSystem->eosViewRWMutex);\n      file = mFileSystem->eosView->getFile(currentFilePath);\n      fid = std::to_string(file->getId());\n    } catch (const eos::MDException& ex) {\n      //The file does not exist, we will store the path with URL encoding\n      std::string encodedFilePath =\n        eos::common::StringConversion::curl_default_escaped(currentFilePath);\n      // curl encoding does not convert dots '.', so we need to do this explicitly\n      eos::common::StringConversion::ReplaceStringInPlace(encodedFilePath, \".\",\n          \"%2E\");\n      fid = encodedFilePath;\n    } catch (const std::exception& ex) {\n      std::ostringstream errMsg;\n      errMsg << \"In ProcDirectoryBulkRequestDAO::generateXattrsMapFromBulkRequest(), got a standard exception trying to get informations about the file \"\n             << currentFilePath << \" ExceptionWhat=\\\"\" << ex.what() << \"\\\"\";\n      throw PersistencyException(errMsg.str());\n    }\n\n    //If a potential error has been set for this file, adding it in the value corresponding\n    //to this file's extended attribute\n    xattrs[FILE_ID_XATTR_KEY_PREFIX + fid] = \"\";\n    auto error = fileWithMDFuture.first.getError();\n\n    if (error) {\n      xattrs[FILE_ID_XATTR_KEY_PREFIX + fid] = *error;\n    }\n  }\n}\n\nvoid ProcDirectoryBulkRequestDAO::persistBulkRequestDirectory(\n  const std::string& directoryBulkReqPath,\n  const eos::IContainerMD::XAttrMap& xattrs)\n{\n  std::shared_ptr<eos::IContainerMD> bulkReqDirMd;\n  {\n    eos::common::RWMutexWriteLock nsLock(mFileSystem->eosViewRWMutex);\n\n    try {\n      bulkReqDirMd = mFileSystem->eosView->getContainer(directoryBulkReqPath);\n\n      for (auto& xattr : xattrs) {\n        bulkReqDirMd->setAttribute(xattr.first, xattr.second);\n      }\n\n      mFileSystem->eosView->updateContainerStore(bulkReqDirMd.get());\n    } catch (const eos::MDException& ex) {\n      std::ostringstream oss;\n      oss << \"In ProcDirectoryBulkRequestDAO::persistBulkRequestDirectory(): unable to persist the bulk-request in the directory \"\n          << directoryBulkReqPath\n          << \"ExceptionWhat=\\\"\" << ex.what() << \"\\\"\";\n      throw PersistencyException(oss.str());\n    }\n  }\n}\n\nvoid ProcDirectoryBulkRequestDAO::createBulkRequestDirectory(\n  const BulkRequest* bulkRequest, const std::string& bulkReqProcPath)\n{\n  XrdOucErrInfo error;\n  int directoryCreationRetCode = mFileSystem->_mkdir(bulkReqProcPath.c_str(),\n                                 S_IFDIR | S_IRWXU, error, mVid);\n\n  if (directoryCreationRetCode != SFS_OK) {\n    std::ostringstream errMsg;\n    errMsg << \"In ProcDirectoryBulkRequestDAO::createBulkRequestDirectory(), could not create the directory to save the bulk-request id=\"\n           << bulkRequest->getId()\n           << \" XrdOfsErrMsg=\\\"\" << error.getErrText() << \"\\\"\";\n    throw PersistencyException(errMsg.str());\n  }\n}\n\nstd::string ProcDirectoryBulkRequestDAO::generateBulkRequestProcPath(\n  const BulkRequest* bulkRequest)\n{\n  return generateBulkRequestProcPath(bulkRequest->getId(),\n                                     bulkRequest->getType());\n}\n\nstd::string ProcDirectoryBulkRequestDAO::generateBulkRequestProcPath(\n  const std::string& bulkRequestId, const BulkRequest::Type& type)\n{\n  return mProcDirectoryBulkRequestLocations.getDirectoryPathWhereBulkRequestCouldBeSaved(\n           type) + bulkRequestId;\n}\n\n/*\nvoid ProcDirectoryBulkRequestDAO::insertBulkRequestFilesToBulkRequestDirectory(const BulkRequest * bulkRequest, const std::string & bulkReqProcPath) {\n  const auto & files = *bulkRequest->getFiles();\n  //Map of files associated to the future object for the in-memory prefetching of the file informations\n  std::map<bulk::File,folly::Future<IFileMDPtr>> filesWithMDFutures;\n  for(auto & file : files){\n    std::string path = file.first;\n    std::pair<bulk::File,folly::Future<IFileMDPtr>> itemToInsert(*file.second,mFileSystem->eosView->getFileFut(path , false));\n    filesWithMDFutures.emplace(std::move(itemToInsert));\n  }\n  for(auto & fileMd: filesWithMDFutures){\n      fileMd.second.wait();\n  }\n  for(auto & fileWithMDFuture : filesWithMDFutures){\n    const std::string & currentFilePath = fileWithMDFuture.first.getPath();\n    std::ostringstream pathOfFileToTouch;\n    pathOfFileToTouch << bulkReqProcPath << \"/\";\n    std::shared_ptr<IFileMD> file;\n    try {\n      eos::common::RWMutexReadLock nsLock(mFileSystem->eosViewRWMutex);\n      file = mFileSystem->eosView->getFile(currentFilePath);\n      pathOfFileToTouch << file->getId();\n    } catch (const eos::MDException &ex){\n      //The file does not exist, we will store the path under the same format as it is in the recycle bin\n      std::string newFilePath = currentFilePath;\n      common::StringConversion::ReplaceStringInPlace(newFilePath,\"/\",\"#:#\");\n      pathOfFileToTouch << newFilePath;\n    } catch(const std::exception &ex){\n      std::ostringstream errMsg;\n      errMsg << \"In ProcDirectoryBulkRequestDAO::insertBulkRequestFilesToBulkRequestDirectory(), got a standard exception trying to get informations about the file \"\n             << currentFilePath << \" ExceptionWhat=\\\"\" << ex.what() << \"\\\"\";\n      throw PersistencyException(errMsg.str());\n    }\n    {\n      //Low level system call\n      try {\n        std::shared_ptr<IFileMD> fmd;\n        {\n          eos::common::RWMutexWriteLock(mFileSystem->eosViewRWMutex);\n          auto fmd = mFileSystem->eosView->createFile(pathOfFileToTouch.str(),\n                                                      vid.uid, vid.gid);\n          fmd->setSize(0);\n          if (fileWithMDFuture.first.getError()) {\n            fmd->setAttribute(ERROR_MSG_XATTR_NAME,\n                              fileWithMDFuture.first.getError().value());\n          }\n          mFileSystem->eosView->updateFileStore(fmd.get());\n        }\n      } catch (const eos::MDException &ex) {\n        std::ostringstream errMsg;\n        errMsg << \"In ProcDirectoryBulkRequestDAO::insertBulkRequestFilesToBulkRequestDirectory(), could not create the file to save the file \"\n               <<  pathOfFileToTouch.str() << \" that belongs to the bulk-request id=\"\n               << bulkRequest->getId() << \" ExceptionWhat=\\\"\" << ex.what() << \"\\\"\";\n        throw PersistencyException(errMsg.str());\n      }\n\n    }\n  }\n  updateLastAccessTime(bulkReqProcPath);\n}\n */\n\nvoid ProcDirectoryBulkRequestDAO::cleanAfterExceptionHappenedDuringBulkRequestSave(\n  const std::string& bulkReqProcPath) noexcept\n{\n  try {\n    deleteDirectory(bulkReqProcPath);\n  } catch (const PersistencyException& ex) {\n    std::ostringstream debugMsg;\n    debugMsg <<\n             \"In ProcDirectoryBulkRequestDAO::cleanAfterExceptionHappenedDuringBulkRequestSave() \"\n             << \"unable to clean the directory \" << bulkReqProcPath << \"ErrorMsg=\\\"\" <<\n             ex.what() << \"\\\"\";\n    eos_debug(debugMsg.str().c_str());\n  }\n}\n\nvoid ProcDirectoryBulkRequestDAO::deleteDirectory(const std::string& path)\n{\n  if (existsAndIsDirectory(path)) {\n    // execute a proc command\n    ProcCommand Cmd;\n    XrdOucString info;\n    // we do a recursive deletion\n    info = \"mgm.cmd=rm&mgm.option=r&mgm.retc=1&mgm.path=\";\n    info += path.c_str();\n    XrdOucErrInfo lError;\n    int result = Cmd.open(\"/proc/user\", info.c_str(), mVid, &lError);\n    Cmd.close();\n\n    if (result == SFS_ERROR) {\n      throw PersistencyException(lError.getErrText());\n    }\n  }\n}\n\nstd::unique_ptr<BulkRequest> ProcDirectoryBulkRequestDAO::getBulkRequest(\n  const std::string& id, const BulkRequest::Type& type)\n{\n  std::unique_ptr<BulkRequest> bulkRequest = nullptr;\n  std::string bulkRequestProcPath = this->generateBulkRequestProcPath(id, type);\n\n  try {\n    if (existsAndIsDirectory(bulkRequestProcPath)) {\n      // Directory exists, the bulk-request can be fetched\n      // Update the last access time of the bulk-request directory\n      updateLastAccessTime(bulkRequestProcPath);\n      //Get all the extended attributes of the directory\n      eos::IContainerMD::XAttrMap xattrs;\n      fetchExtendedAttributes(bulkRequestProcPath, xattrs);\n\n      switch (type) {\n      case BulkRequest::PREPARE_STAGE: {\n        bulkRequest = initializeStageBulkRequestFromXattrs(id, xattrs);\n        break;\n      }\n\n      default:\n        std::stringstream ss;\n        ss << \"The bulk-request has a type (\" << BulkRequest::bulkRequestTypeToString(\n             type) << \") that cannot be persisted\";\n        throw PersistencyException(ss.str());\n      }\n    }\n  } catch (const PersistencyException& ex) {\n    std::ostringstream oss;\n    oss << \"In ProcDirectoryBulkRequestDAO::getBulkRequest(): unable to get the bulk request from the persistency layer \"\n        << \"ErrorMsg=\\\"\" << ex.what() << \"\\\"\";\n    throw PersistencyException(oss.str());\n  }\n\n  return bulkRequest;\n}\n\nbool ProcDirectoryBulkRequestDAO::existsAndIsDirectory(const std::string&\n    dirPath)\n{\n  XrdOucErrInfo error;\n  XrdSfsFileExistence fileExistence;\n  int retCode = mFileSystem->_exists(dirPath.c_str(), fileExistence, error, mVid);\n\n  if (retCode != SFS_OK) {\n    std::ostringstream oss;\n    oss << \"In ProcDirectoryBulkRequestDAO::existsAndIsDirectory(), could not get information about the existence of the directory \"\n        << dirPath << \" XrdOfsErrMsg=\\\"\" << error.getErrText() << \"\\\"\";\n    eos_err(oss.str().c_str());\n    throw PersistencyException(oss.str());\n  }\n\n  return fileExistence == XrdSfsFileExistIsDirectory;\n}\n\nstd::unique_ptr<StageBulkRequest>\nProcDirectoryBulkRequestDAO::initializeStageBulkRequestFromXattrs(\n  const std::string& requestId, const eos::IContainerMD::XAttrMap& xattrs)\n{\n  common::VirtualIdentity vid;\n  time_t creationTime;\n\n  try {\n    vid.uid = ::strtoul(xattrs.at(ISSUER_UID_XATTR_NAME).c_str(), nullptr, 0);\n    creationTime = ::strtoul(xattrs.at(CREATION_TIME_XATTR_NAME).c_str(), nullptr,\n                             0);\n  } catch (const std::out_of_range& ex) {\n    throw PersistencyException(\"Unable to fetch the attributes to create the stage bulk-request\");\n  }\n\n  std::unique_ptr<StageBulkRequest> stageBulkRequest =\n    BulkRequestFactory::createStageBulkRequest(requestId, vid, creationTime);\n  fillBulkRequestFromXattrs(stageBulkRequest.get(), xattrs);\n  return stageBulkRequest;\n}\n\nvoid ProcDirectoryBulkRequestDAO::fillBulkRequestFromXattrs(\n  bulk::BulkRequest* bulkRequest, const eos::IContainerMD::XAttrMap& xattrs)\n{\n  XrdOucErrInfo errFind;\n  XrdOucString stdErr;\n  std::map<ProcDirBulkRequestFile, folly::Future<IFileMDPtr>>\n      filesInBulkReqProcDirWithFuture;\n  std::vector<std::string> fileWithPaths;\n\n  for (auto& fileIdInfos : xattrs) {\n    std::string fileIdOrObfuscatedPath = fileIdInfos.first;\n    std::optional<std::string> currentFileError;\n    size_t pos = fileIdOrObfuscatedPath.find(FILE_ID_XATTR_KEY_PREFIX, 0);\n\n    if (pos != std::string::npos) {\n      //We have a file, get its potential error\n      fileIdOrObfuscatedPath.erase(0, FILE_ID_XATTR_KEY_PREFIX.length());\n\n      //The error is stored in the value assciated to the key FILE_ID_XATTR_KEY_PREFIX\n      if (!fileIdInfos.second.empty()) {\n        currentFileError = fileIdInfos.second;\n      }\n    } else {\n      continue;\n    }\n\n    //The files in the bulk-request proc directory will be wrapped into a ProcDirBulkRequestFile object.\n    ProcDirBulkRequestFile file(fileIdOrObfuscatedPath);\n\n    if (currentFileError) {\n      file.setError(*currentFileError);\n    }\n\n    try {\n      //The file name is normally a fid. But if the file submitted before did not exist, the path will be stored in another format (e.g: URL encoding)\n      eos::common::FileId::fileid_t fid = std::stoull(file.getName());\n      file.setFileId(fid);\n      initiateFileMDFetch(file, filesInBulkReqProcDirWithFuture);\n    } catch (std::invalid_argument& ex) {\n      // The current file is not a fid, it is therefore a file that has the URL encoding\n      // It may also have the old format #:#eos#:#test#:#testFile.txt (#:# replaced by '/')\n      std::string filePathCopy = file.getName();\n\n      if (filePathCopy.find(\"#:#\") != std::string::npos) {\n        // TODO: Remove this once no more bulk requests exist with the format #:#eos#:#test#:#testFile.txt\n        common::StringConversion::ReplaceStringInPlace(filePathCopy, \"#:#\", \"/\");\n      } else {\n        filePathCopy = eos::common::StringConversion::curl_default_unescaped(\n                         filePathCopy);\n      }\n\n      std::unique_ptr<File> bulkRequestFile = std::make_unique<File>(filePathCopy);\n      bulkRequestFile->setError(file.getError());\n      bulkRequest->addFile(std::move(bulkRequestFile));\n    }\n  }\n\n  getFilesPathAndAddToBulkRequest(filesInBulkReqProcDirWithFuture, bulkRequest);\n}\n\nvoid ProcDirectoryBulkRequestDAO::getDirectoryContent(const std::string& path,\n    std::map<std::string, std::set<std::string>>& directoryContent)\n{\n  XrdOucErrInfo error;\n  XrdOucString stdErr;\n\n  if (mFileSystem->_find(path.c_str(), error, stdErr, mVid,\n                         directoryContent) == SFS_ERROR) {\n    std::ostringstream oss;\n    oss << \"In ProcDirectoryBulkRequestDAO::getDirectoryContent(), could not list the content of the directory \"\n        << path << \" XrdOfsErrMsg=\" << error.getErrText();\n    eos_err(oss.str().c_str());\n    throw PersistencyException(oss.str());\n  }\n\n  //Drop the top directory: it does not belong to its content\n  directoryContent.erase(path);\n}\n\nvoid ProcDirectoryBulkRequestDAO::fetchExtendedAttributes(\n  const std::string& path, eos::IContainerMD::XAttrMap& xattrs)\n{\n  XrdOucErrInfo error;\n\n  if (mFileSystem->_attr_ls(path.c_str(), error, mVid, nullptr,\n                            xattrs) == SFS_ERROR) {\n    std::ostringstream oss;\n    oss << \"In ProcDirectoryBulkRequestDAO::fetchExtendedAttributes() Unable to get the extended attribute of the file \"\n        << path << \" XrdOfsErrMsg=\" << error.getErrText();\n    eos_err(oss.str().c_str());\n    throw PersistencyException(oss.str());\n  }\n}\n\nvoid ProcDirectoryBulkRequestDAO::initiateFileMDFetch(const\n    ProcDirBulkRequestFile& file,\n    std::map<ProcDirBulkRequestFile, folly::Future<IFileMDPtr>>& filesWithFuture)\n{\n  std::pair<ProcDirBulkRequestFile, folly::Future<IFileMDPtr>> fileWithFuture(\n        file, mFileSystem->eosFileService->getFileMDFut(file.getFileId().value()));\n  filesWithFuture.emplace(std::move(fileWithFuture));\n}\n\nvoid ProcDirectoryBulkRequestDAO::getFilesPathAndAddToBulkRequest(\n  std::map<ProcDirBulkRequestFile, folly::Future<IFileMDPtr>>& filesWithFuture,\n  BulkRequest* bulkRequest)\n{\n  eos::common::RWMutexReadLock lock(mFileSystem->eosViewRWMutex);\n\n  for (auto& fileWithFuture : filesWithFuture) {\n    try {\n      fileWithFuture.second.wait();\n      std::shared_ptr<IFileMD> fmd = mFileSystem->eosFileService->getFileMD(\n                                       fileWithFuture.first.getFileId().value());\n      std::unique_ptr<File> bulkReqFile = std::make_unique<File>\n                                          (mFileSystem->eosView->getUri(fmd.get()));\n      bulkReqFile->setError(fileWithFuture.first.getError());\n      bulkRequest->addFile(std::move(bulkReqFile));\n    } catch (const eos::MDException& ex) {\n      //We could not get any information about this file (might have been deleted for example)\n      //log this as a warning and remove this file\n      std::stringstream ss;\n      ss << \"In ProcDirectoryBulkRequestDAO::getFilesPathAndAddToBulkRequest(), unable to get the metadata of the file id=\"\n         << fileWithFuture.first.getFileId().value()\n         << \" ErrorMsg=\\\"\" << ex.what() << \"\\\"\";\n      eos_warning(ss.str().c_str());\n    }\n  }\n}\n\nuint64_t ProcDirectoryBulkRequestDAO::deleteBulkRequestNotQueriedFor(\n  const BulkRequest::Type& type, const std::chrono::seconds& seconds)\n{\n  std::string bulkRequestsPath =\n    mProcDirectoryBulkRequestLocations.getDirectoryPathWhereBulkRequestCouldBeSaved(\n      type);\n  std::map<std::string, std::set<std::string>> allBulkRequestDirectories;\n  getDirectoryContent(bulkRequestsPath, allBulkRequestDirectories);\n  //Now get the last access time of each directory\n  std::set<std::string> bulkRequestDirectoriesToDelete;\n  uint64_t nbDeletedBulkRequests = 0;\n\n  for (auto& kv : allBulkRequestDirectories) {\n    eos::IContainerMD::XAttrMap xattrs;\n    fetchExtendedAttributes(kv.first, xattrs);\n\n    try {\n      std::string lastAccessTimeStr = xattrs.at(LAST_ACCESS_TIME_XATTR_NAME);\n      std::time_t lastAccessTime = std::atoi(lastAccessTimeStr.c_str());\n      time_t elapsedTimeBetweenNowAndLastAccessTime = std::time(\n            nullptr) - lastAccessTime;\n\n      if (elapsedTimeBetweenNowAndLastAccessTime > seconds.count()) {\n        deleteDirectory(kv.first);\n        nbDeletedBulkRequests++;\n        eos_info(\"msg=\\\"Deleted a bulk request from the /proc/ persistency\\\" path=\\\"%s\\\"\",\n                 kv.first.c_str());\n      }\n    } catch (const std::out_of_range&) {\n      //The extended attribute LAST_ACCESS_TIME_XATTR_NAME was not found, log an error\n      eos_err(\"In ProcDirectoryBulkRequestDAO::deleteBulkRequestNotQueriedFor(), the directory %s does not have the %s extended attribute set. \"\n              \"Unable to know if it can be deleted or not.\", kv.first.c_str(),\n              LAST_ACCESS_TIME_XATTR_NAME);\n    }\n  }\n\n  return nbDeletedBulkRequests;\n}\n\nvoid ProcDirectoryBulkRequestDAO::setExtendedAttribute(const std::string& path,\n    const std::string& xattrName, const std::string& xattrValue)\n{\n  XrdOucErrInfo error;\n  int retAttrSet = mFileSystem->_attr_set(path.c_str(), error, mVid, nullptr,\n                                          xattrName.c_str(), xattrValue.c_str());\n\n  if (retAttrSet != SFS_OK) {\n    std::ostringstream oss;\n    oss << \"In ProcDirectoryBulkRequestDAO::setExtendedAttribute(), could not set the extended attribute \"\n        << xattrName << \" to the file path \"\n        << path << \" XrdOfsErrMsg=\\\"\" << error.getErrText() << \"\\\"\";\n    eos_err(oss.str().c_str());\n    throw PersistencyException(oss.str());\n  }\n}\n\nvoid ProcDirectoryBulkRequestDAO::updateLastAccessTime(const std::string& path)\n{\n  std::time_t now = std::time(nullptr);\n  std::string nowStr = std::to_string(now);\n  setExtendedAttribute(path, LAST_ACCESS_TIME_XATTR_NAME, nowStr);\n}\n\nbool ProcDirectoryBulkRequestDAO::exists(const std::string& bulkRequestId,\n    const BulkRequest::Type& type)\n{\n  std::string bulkRequestPath = generateBulkRequestProcPath(bulkRequestId, type);\n  return existsAndIsDirectory(bulkRequestPath);\n}\n\nvoid ProcDirectoryBulkRequestDAO::deleteBulkRequest(const BulkRequest*\n    bulkRequest)\n{\n  std::string bulkRequestPath = generateBulkRequestProcPath(bulkRequest);\n  deleteDirectory(bulkRequestPath);\n}\n\nEOSBULKNAMESPACE_END\n"
  },
  {
    "path": "mgm/bulk-request/dao/proc/ProcDirectoryBulkRequestDAO.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file ProcDirectoryBulkRequestDAO.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_PROCDIRECTORYBULKREQUESTDAO_HH\n#define EOS_PROCDIRECTORYBULKREQUESTDAO_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/bulk-request/dao/IBulkRequestDAO.hh\"\n#include <common/Logging.hh>\n#include <mgm/ofs/XrdMgmOfs.hh>\n#include \"mgm/bulk-request/dao/proc/ProcDirectoryBulkRequestLocations.hh\"\n#include \"mgm/bulk-request/dao/proc/ProcDirBulkRequestFile.hh\"\n#include \"mgm/bulk-request/prepare/CancellationBulkRequest.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * This class is the bulk request persistency layer using the eos proc directory\n *\n * The bulk request persistence will be ensured by creating and listing a directory's extended attributes in a the /eos/.../proc/bulkrequest.\n */\nclass ProcDirectoryBulkRequestDAO : public IBulkRequestDAO,\n  public eos::common::LogId\n{\npublic:\n  ProcDirectoryBulkRequestDAO(XrdMgmOfs* fileSystem,\n                              const ProcDirectoryBulkRequestLocations& mBulkRequestDirSchema);\n\n  /**\n   * Save the bulk request by updating the state (to CANCELLED) of a subset of files\n   * belonging to a previously submitted stage bulk-request.\n   * The files that are contained in the CancellationBulkRequest represent\n   * the subset of files that needs to be cancelled on the Stage bulk-request\n   * @param bulkRequest the bulkRequest that will be used to update the state\n   * of the files located in a previously submitted stage request\n   */\n  void saveBulkRequest(const CancellationBulkRequest* bulkRequest) override;\n\n  /**\n   * Save the bulk request by creating a directory in the /eos/.../proc/ directory and creating one extended attribute\n   * per file in this directory:\n   * - If a file in the bulk-request exists, the file will be named according to the fileId\n   * - If a file in the bulk-request does not exist, the file will be named according to the path provided in the format like the one in the EOS\n   * recycle-bin (each '/' will be replaced by \"#:#\")\n   * @param bulkRequest the BulkRequest to save\n   */\n  void saveBulkRequest(const StageBulkRequest* bulkRequest) override;\n\n  /**\n   * Get the bulk-request from the /eos/.../proc directory\n   * @param id the id of the bulk-request\n   * @param type the type of bulk-request\n   * @return the bulk-request associated to the id and the type, nullptr if it does not exist\n   */\n  std::unique_ptr<BulkRequest> getBulkRequest(const std::string& id,\n      const BulkRequest::Type& type) override;\n\n  /**\n   * Delete all the bulk-request of a certain type that were not accessed for  hours\n   * @param type the bulk-request type to look for\n   * @param seconds the number of seconds after which the bulk-requests can be deleted if they were not queried\n   * @returns the number of deleted bulk-request\n   */\n  uint64_t deleteBulkRequestNotQueriedFor(const BulkRequest::Type& type,\n                                          const std::chrono::seconds& seconds) override;\n\n  /**\n   * Returns true if the bulk-request corresponding to the id and the type\n   * passed in parameters exists, false otherwise\n   * @param bulkRequestId the id of the bulk-request to check\n   * @param type the type of the bulk-request to test\n   * @returns true if the bulk-request exists, false otherwise\n   */\n  bool exists(const std::string& bulkRequestId,\n              const BulkRequest::Type& type) override;\n\n  /**\n   * Deletes the bulk-request passed in parameters from the persistency\n   * @param bulkRequest the bulk-request to delete\n   */\n  void deleteBulkRequest(const BulkRequest* bulkRequest) override;\n\n  virtual ~ProcDirectoryBulkRequestDAO() {}\n\nprivate:\n  //Interface to the EOS filesystem to allow the creation of files and directories\n  XrdMgmOfs* mFileSystem;\n  const ProcDirectoryBulkRequestLocations& mProcDirectoryBulkRequestLocations;\n  eos::common::VirtualIdentity mVid;\n\n  const char* LAST_ACCESS_TIME_XATTR_NAME = \"last_accessed_time\";\n  inline static const std::string ISSUER_UID_XATTR_NAME = \"issuer_uid\";\n  //File persisted as bulk-request's directory extended attribute will be prefixed by this prefix\n  inline static const std::string FILE_ID_XATTR_KEY_PREFIX = \"fid.\";\n  inline static const std::string CREATION_TIME_XATTR_NAME = \"creation_time\";\n\n  void cancelStageBulkRequest(const CancellationBulkRequest* bulkRequest);\n\n  /**\n   * Creates a directory to store the bulk-request files within it\n   * @param bulkRequest the bulkRequest to get the id from\n   * @param bulkReqProcPath the directory in /proc/ where the files contained in the bulk-request will be saved\n   */\n  void createBulkRequestDirectory(const BulkRequest* bulkRequest,\n                                  const std::string& bulkReqProcPath);\n  /**\n   * Generate the bulk-request directory path within the /eos/.../proc/ directory\n   * It is generated according to the id of the bulk-request\n   * @param bulkRequest the bulk-request to generate the path from\n   * @return the string containing the path of the directory used to store the bulk-request\n   */\n  std::string generateBulkRequestProcPath(const BulkRequest* bulkRequest);\n\n  /**\n   * Generate the bulk-request directory path within the /eos/.../proc/ directory\n   * It is generated according to the id of the bulk-request\n   * @param bulkRequestId the id of the bulk-request to generate the proc directory path\n   * @param type the type of the bulk-request to generate the proc directory path\n   * @return the string containing the path of the directory used to store the bulk-request\n   */\n  std::string generateBulkRequestProcPath(const std::string& bulkRequestId,\n                                          const BulkRequest::Type& type);\n\n  /**\n   * Insert the files contained in the bulk request into the directory created by createBulkRequestDirectory()\n   * @param bulkRequest the bulk-request containing the files to insert in the directory\n   * @param bulkReqProcPath the he directory in /proc/ where the files contained in the bulk-request will be saved\n   */\n  //void insertBulkRequestFilesToBulkRequestDirectory(const BulkRequest * bulkRequest, const std::string & bulkReqProcPath);\n\n  /**\n   * Performs the cleaning of the bulk-request directory if an exception happens during the persistency of the bulk-request\n   * @param bulkReqProcPath the directory where the bulk-request should have been stored\n   */\n  void cleanAfterExceptionHappenedDuringBulkRequestSave(const std::string&\n      bulkReqProcPath) noexcept;\n\n  /**\n   * Deletes the directory located in the path passed in parameter\n   * @param path the path of the directory to delete\n   */\n  void deleteDirectory(const std::string& path);\n\n  /**\n   * Returns true if the directory path passed in parameter exists, false otherwise\n   * @param dirPath the path of the directory to check its existence\n   * @return true if the directory path passed in parameter exists, false otherwise\n   */\n  bool existsAndIsDirectory(const std::string& dirPath);\n\n  /**\n   * creates the bulk request and fills it from the directory passed in parameter\n   * @param bulkRequest the bulkRequest to fill\n   * @param xattrs the extended attributes of the directory where the bulk-request is persisted\n   * @param bulkRequestProcPath the path where the bulk request is persisted\n   */\n  void fillBulkRequestFromXattrs(bulk::BulkRequest* bulkRequest,\n                                 const eos::IContainerMD::XAttrMap& xattrs);\n\n  std::unique_ptr<StageBulkRequest> initializeStageBulkRequestFromXattrs(\n    const std::string& requestId, const eos::IContainerMD::XAttrMap& xattrs);\n  /**\n   * Fills the directoryContent map passed in parameter. The key is the full path of the directory given in parameter\n   * the value is the file names that are located in the directory.\n   * Reminder: the files that are in the directory of the bulk-request are the fileIds of the files that were submitted with the bulk-request (or\n   * transformed paths for the files that were submitted did not exist)\n   * @param path the path of the directory to get the content\n   * @param directoryContent the map that will be filled with the content of the directory passed in parameter\n   */\n  void getDirectoryContent(const std::string& path,\n                           std::map<std::string, std::set<std::string>>& directoryContent);\n\n  /**\n   * Fills the xattrs map with the extended attributes of the file/directory whose path is passed in parameter\n   * @param path the path of the file to get the extended attributes from\n   * @param xattrs extended attributes map to fill\n   */\n  void fetchExtendedAttributes(const std::string& path,\n                               eos::IContainerMD::XAttrMap& xattrs);\n\n  /**\n   * Asynchronously fetch the file metadata by using the eosFileService->getFileMDFut() method. The filesWithFuture map will\n   * be filled by this method\n   * @param file the file to asynchronously fetch the metadata\n   * @param filesWithFuture the map to include the file and its future\n   */\n  void initiateFileMDFetch(const ProcDirBulkRequestFile& file,\n                           std::map<ProcDirBulkRequestFile, folly::Future<IFileMDPtr>>& filesWithFuture);\n\n  /**\n   * Wait for the futures associated to the files inserted in the filesWithFuture map (got filled by the initiateFileMDFetch() method).\n   * Once all the futures have returned\n   * @param filesWithFuture the map containing the files and the associated future to get the metadata from it\n   * @param bulkRequest the bulk request in which the files will be added\n   */\n  void getFilesPathAndAddToBulkRequest(\n    std::map<ProcDirBulkRequestFile, folly::Future<IFileMDPtr>>& filesWithFuture,\n    BulkRequest* bulkRequest);\n\n  /**\n   * Sets an extended attribute on the file whose path is passed in parameter\n   * @param path the path of the file or directory to set the extended attribute\n   * @param xattrName the extended attribute name\n   * @param xattrValue the extended attribute value\n   */\n  void setExtendedAttribute(const std::string& path, const std::string& xattrName,\n                            const std::string& xattrValue);\n\n  /**\n   * Set the last_access_time extended attribute to the current timestamp on the file/directory passed in parameter\n   * @param path the path of the file/directory to set the last acess time\n   */\n  void updateLastAccessTime(const std::string& path);\n\n  /**\n   * Generate the extended attributes map that will be added to the bulk-request directory\n   * @param bulkRequest the bulk-request from which the map will be added\n   * @param xattrs the map containing the fid of a file associated to an eventual error (e.g prepare submission)\n   */\n  void generateXattrsMapFromBulkRequest(const BulkRequest* bulkRequest,\n                                        eos::IContainerMD::XAttrMap& xattrs);\n\n  void generateXattrsMapFromBulkRequest(const StageBulkRequest* bulkRequest,\n                                        eos::IContainerMD::XAttrMap& xattrs);\n\n  /**\n   * Persist the bulk-request in the extended attributes of the directory\n   * @param directoryBulkReqPath the directory where each file belonging to the bulk-request will be persisted\n   * @param xattrs the map containing each file belonging to the bulk-request\n   */\n  void persistBulkRequestDirectory(const std::string& directoryBulkReqPath,\n                                   const eos::IContainerMD::XAttrMap& xattrs);\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_PROCDIRECTORYBULKREQUESTDAO_HH\n"
  },
  {
    "path": "mgm/bulk-request/dao/proc/ProcDirectoryBulkRequestLocations.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file ProcDirectoryBulkRequestLocations.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"ProcDirectoryBulkRequestLocations.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nProcDirectoryBulkRequestLocations::ProcDirectoryBulkRequestLocations(\n  const std::string& procDirectoryPath)\n{\n  mBulkRequestDirectory = procDirectoryPath + \"/bulkrequests/\";\n  mBulkRequestTypeToPath[BulkRequest::Type::PREPARE_STAGE] = mBulkRequestDirectory\n      + \"stage/\";\n  mBulkRequestTypeToPath[BulkRequest::Type::PREPARE_EVICT] = mBulkRequestDirectory\n      + \"evict/\";\n  //The cancellation is done on PREPARE_STAGE bulk-request, it will update the PREPARE_STAGE bulk-request files\n  mBulkRequestTypeToPath[BulkRequest::Type::PREPARE_CANCEL] =\n    mBulkRequestTypeToPath[BulkRequest::Type::PREPARE_STAGE];\n}\n\nstd::set<std::string>\nProcDirectoryBulkRequestLocations::getAllBulkRequestDirectoriesPath() const\n{\n  std::set<std::string> allBulkRequetsDirectoriesPath;\n\n  for (auto& bulkRequestTypeToPath : mBulkRequestTypeToPath) {\n    allBulkRequetsDirectoriesPath.insert(bulkRequestTypeToPath.second);\n  }\n\n  return allBulkRequetsDirectoriesPath;\n}\n\nstd::string ProcDirectoryBulkRequestLocations::getBulkRequestDirectory() const\n{\n  return mBulkRequestDirectory;\n}\n\nstd::string\nProcDirectoryBulkRequestLocations::getDirectoryPathWhereBulkRequestCouldBeSaved(\n  const BulkRequest::Type& type) const\n{\n  return mBulkRequestTypeToPath.at(type);\n}\n\nEOSBULKNAMESPACE_END\n"
  },
  {
    "path": "mgm/bulk-request/dao/proc/ProcDirectoryBulkRequestLocations.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file ProcDirectoryBulkRequestLocations.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_PROCDIRECTORYBULKREQUESTLOCATIONS_HH\n#define EOS_PROCDIRECTORYBULKREQUESTLOCATIONS_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/bulk-request/BulkRequest.hh\"\n#include <set>\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * This class allows to store the paths where the bulk-requests will be stored\n * according to their types.\n *\n * As the proc directory is created and known when the MGM starts, a pointer to this class\n * is stored in the XrdMgmOfs object so that it can be reused later on.\n */\nclass ProcDirectoryBulkRequestLocations\n{\npublic:\n  ProcDirectoryBulkRequestLocations(const std::string& procDirectoryPath);\n  /**\n   * Returns all the directories where a bulk-request could be persisted in the /proc/ directory\n   * @return all the directories where a bulk-request could be persisted in the /proc/ directory\n   */\n  std::set<std::string> getAllBulkRequestDirectoriesPath() const;\n  std::string getBulkRequestDirectory() const;\n  std::string getDirectoryPathWhereBulkRequestCouldBeSaved(\n    const BulkRequest::Type& type) const;\nprivate:\n  std::map<BulkRequest::Type, std::string> mBulkRequestTypeToPath;\n  std::string mBulkRequestDirectory;\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_PROCDIRECTORYBULKREQUESTLOCATIONS_HH\n"
  },
  {
    "path": "mgm/bulk-request/dao/proc/cleaner/BulkRequestProcCleaner.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file BulkRequestProcCleaner.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"BulkRequestProcCleaner.hh\"\n#include \"mgm/imaster/IMaster.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include <common/IntervalStopwatch.hh>\n#include \"mgm/bulk-request/dao/factories/AbstractDAOFactory.hh\"\n#include \"mgm/bulk-request/dao/factories/ProcDirectoryDAOFactory.hh\"\n#include \"mgm/bulk-request/dao/IBulkRequestDAO.hh\"\n#include \"mgm/bulk-request/exception/PersistencyException.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nBulkRequestProcCleaner::BulkRequestProcCleaner(const\n    bulk::ProcDirectoryBulkRequestLocations& bulkReqDirectory,\n    std::unique_ptr<BulkRequestProcCleanerConfig> config): mBulkRequestLocation(\n        bulkReqDirectory), mConfig(std::move(config))\n{\n}\n\nvoid\nBulkRequestProcCleaner::Start()\n{\n  mThread.reset(&BulkRequestProcCleaner::backgroundThread, this);\n}\n\nvoid\nBulkRequestProcCleaner::Stop()\n{\n  mThread.join();\n}\n\nvoid\nBulkRequestProcCleaner::backgroundThread(ThreadAssistant& assistant)\n{\n  std::ostringstream oss;\n  oss << \"msg=\\\"starting BulkRequestProcCleaner thread. Directory=\" <<\n      mBulkRequestLocation.getBulkRequestDirectory() <<  \"\\\"\";\n  eos_static_notice(oss.str().c_str());\n  gOFS->WaitUntilNamespaceIsBooted(assistant);\n\n  // Wait that current MGM becomes a master\n  do {\n    eos_static_debug(\"%s\", \"msg=\\\"BulkRequestProcCleaner waiting for master MGM\\\"\");\n    assistant.wait_for(std::chrono::seconds(10));\n  } while (!assistant.terminationRequested() && !gOFS->mMaster->IsMaster());\n\n  while (!assistant.terminationRequested()) {\n    // every now and then we wake up\n    common::IntervalStopwatch stopwatch(mConfig->mInterval);\n\n    // Only a master needs to run the cleaner\n    if (gOFS->mMaster->IsMaster()) {\n      //Initialize the bulk-request DAO\n      std::unique_ptr<AbstractDAOFactory> daoFactory(new ProcDirectoryDAOFactory(gOFS,\n          mBulkRequestLocation));\n      std::unique_ptr<IBulkRequestDAO> bulkReqDao = daoFactory->getBulkRequestDAO();\n\n      try {\n        uint64_t nbBulkRequestDeleted = bulkReqDao->deleteBulkRequestNotQueriedFor(\n                                          BulkRequest::Type::PREPARE_STAGE,\n                                          mConfig->mBulkReqLastAccessTimeBeforeCleaning);\n        eos_static_info(\"msg=\\\"BulkRequestProcCleaner did one round of cleaning, nbDeletedBulkRequests=%ld\\\"\",\n                        nbBulkRequestDeleted);\n      } catch (const PersistencyException& ex) {\n        eos_static_err(\"msg=\\\"BulkRequestProcCleaner an exception occured during a round of cleaning\\\" exceptionMsg=\\\"%s\\\"\",\n                       ex.what());\n      }\n    }\n\n    while (stopwatch.timeRemainingInCycle() >= std::chrono::seconds(5)) {\n      assistant.wait_for(std::chrono::seconds(5));\n\n      if (assistant.terminationRequested()) {\n        break;\n      }\n    }\n  }\n}\n\nBulkRequestProcCleaner::~BulkRequestProcCleaner()\n{\n  Stop();\n}\n\nEOSBULKNAMESPACE_END\n"
  },
  {
    "path": "mgm/bulk-request/dao/proc/cleaner/BulkRequestProcCleaner.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file BulkRequestProcCleaner.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_BULKREQUESTPROCCLEANER_HH\n#define EOS_BULKREQUESTPROCCLEANER_HH\n\n#include \"common/AssistedThread.hh\"\n#include <XrdOuc/XrdOucErrInfo.hh>\n#include \"common/Mapping.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"common/VirtualIdentity.hh\"\n#include \"mgm/bulk-request/dao/proc/cleaner/BulkRequestProcCleanerConfig.hh\"\n#include \"mgm/bulk-request/dao/proc/ProcDirectoryBulkRequestLocations.hh\"\n#include <memory>\n\nEOSBULKNAMESPACE_BEGIN\n\nclass BulkRequestProcCleaner\n{\npublic:\n  BulkRequestProcCleaner(const bulk::ProcDirectoryBulkRequestLocations&\n                         bulkReqDirectory, std::unique_ptr<BulkRequestProcCleanerConfig> config);\n  /**\n   * Start the cleaner thread\n   */\n  void Start();\n  /**\n   * Stop the cleaner thread\n   */\n  void Stop();\n\n  /**\n   * Method that will be ran by the thread\n   *\n   * This thread will look for the bulk-request directories in /proc/ and check the last time\n   * a bulk-request was queried (extended attribute on the bulk-request directory. If a bulk-request has not been queried for more than one week,\n   * it will be deleted from the system.\n   */\n  void backgroundThread(ThreadAssistant& assistant);\n\n  /**\n   * Destructor, stop the cleaner thread\n   */\n  ~BulkRequestProcCleaner();\nprivate:\n  AssistedThread mThread; ///< thread of the /proc/ cleaner thread\n  const ProcDirectoryBulkRequestLocations&\n  mBulkRequestLocation;  ///< The location of the bulk-request proc directory\n  const std::unique_ptr<BulkRequestProcCleanerConfig>\n  mConfig; ///< Configuration of the cleaner (e.g interval of execution)\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_BULKREQUESTPROCCLEANER_HH\n"
  },
  {
    "path": "mgm/bulk-request/dao/proc/cleaner/BulkRequestProcCleanerConfig.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file BulkRequestProcCleanerConfig.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"BulkRequestProcCleanerConfig.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nBulkRequestProcCleanerConfig::BulkRequestProcCleanerConfig(\n  const std::chrono::seconds& interval,\n  const std::chrono::seconds& bulkReqLastAccessTimeBeforeCleaning): mInterval(\n      interval), mBulkReqLastAccessTimeBeforeCleaning(\n        bulkReqLastAccessTimeBeforeCleaning) {}\n\nstd::unique_ptr<BulkRequestProcCleanerConfig>\nBulkRequestProcCleanerConfig::getDefaultConfig()\n{\n  //By default, the interval to run this thread is 1 hour\n  //By default, a bulk-request that was not queried for one week will be deleted from the /proc/ directory\n  return std::make_unique<BulkRequestProcCleanerConfig>(std::chrono::seconds(\n           3600), std::chrono::seconds(604800));\n}\n\nEOSBULKNAMESPACE_END\n"
  },
  {
    "path": "mgm/bulk-request/dao/proc/cleaner/BulkRequestProcCleanerConfig.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file BulkRequestProcCleanerConfig.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_BULKREQUESTPROCCLEANERCONFIG_HH\n#define EOS_BULKREQUESTPROCCLEANERCONFIG_HH\n\n#include \"mgm/Namespace.hh\"\n#include <chrono>\n#include <memory>\n\nEOSBULKNAMESPACE_BEGIN\n\nclass BulkRequestProcCleanerConfig\n{\npublic:\n\n  BulkRequestProcCleanerConfig(const std::chrono::seconds& interval,\n                               const std::chrono::seconds& bulkReqLastAccessTimeBeforeCleaning);\n  /**\n   * Run the BulkRequestProcCleaner thread this many seconds\n   */\n  std::chrono::seconds mInterval;\n\n  /**\n   * If a bulk-request has not been queried since this many seconds,\n   * it will be deleted from the /proc/ directory\n   */\n  std::chrono::seconds mBulkReqLastAccessTimeBeforeCleaning;\n\n  /**\n   * Returns the default cleaner configuration\n   * @return the default cleaner configuration\n   */\n  static std::unique_ptr<BulkRequestProcCleanerConfig> getDefaultConfig();\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_BULKREQUESTPROCCLEANERCONFIG_HH\n"
  },
  {
    "path": "mgm/bulk-request/exception/BulkRequestException.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file BulkRequestException.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_BULKREQUESTEXCEPTION_HH\n#define EOS_BULKREQUESTEXCEPTION_HH\n\n#include <exception>\n#include <string>\n\nclass BulkRequestException : public std::exception\n{\npublic:\n  BulkRequestException(const std::string& exceptionMsg)\n    : std::exception(), mErrorMsg(exceptionMsg) {}\n  virtual const char* what() const noexcept { return mErrorMsg.c_str(); }\nprivate:\n  std::string mErrorMsg;\n};\n\n#endif // EOS_BULKREQUESTEXCEPTION_HH\n"
  },
  {
    "path": "mgm/bulk-request/exception/PersistencyException.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file PersistenceException.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_PERSISTENCYEXCEPTION_HH\n#define EOS_PERSISTENCYEXCEPTION_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"common/exception/Exception.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * Exception class for handling bulk-request persistency exceptions\n */\nclass PersistencyException : public common::Exception\n{\npublic:\n  PersistencyException(const std::string& exceptionMsg)\n    : common::Exception(exceptionMsg) {}\n\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_PERSISTENCYEXCEPTION_HH\n"
  },
  {
    "path": "mgm/bulk-request/interface/IMgmFileSystemInterface.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file IMgmFileSystemInterface.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_IMGMFILESYSTEMINTERFACE_HH\n#define EOS_IMGMFILESYSTEMINTERFACE_HH\n\n#include \"mgm/Namespace.hh\"\n#include <XrdOuc/XrdOucErrInfo.hh>\n#include <XrdSfs/XrdSfsInterface.hh>\n#include <XrdSec/XrdSecEntity.hh>\n#include \"common/VirtualIdentity.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * This interface has mainly been created in order to allow\n * the unit testing of the PrepareManager. There will be two implementation,\n * one that will use the gOFS variable, the other will have its own\n * implementations allowing the unit testing.\n */\nclass IMgmFileSystemInterface\n{\npublic:\n  virtual void addStats(const char* tag, uid_t uid, gid_t gid,\n                        unsigned long val) = 0;\n  virtual bool isTapeEnabled() = 0;\n  virtual int getReqIdMaxCount() = 0;\n  virtual int Emsg(const char* pfx, XrdOucErrInfo& einfo, int ecode,\n                   const char* op, const char* target = \"\") = 0;\n  virtual int _exists(const char* path, XrdSfsFileExistence& file_exists,\n                      XrdOucErrInfo& error, const XrdSecEntity* client = 0,\n                      const char* ininfo = 0) = 0;\n  virtual int _exists(const char* path, XrdSfsFileExistence& file_exists,\n                      XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* opaque = 0,\n                      bool take_lock = true) = 0;\n  virtual int _attr_ls(const char* path, XrdOucErrInfo& out_error,\n                       const eos::common::VirtualIdentity& vid, const char* opaque,\n                       eos::IContainerMD::XAttrMap& map,\n                       bool links = false) = 0;\n  virtual int _access(const char* path, int mode, XrdOucErrInfo& error,\n                      eos::common::VirtualIdentity& vid, const char* info) = 0;\n  virtual int FSctl(const int cmd, XrdSfsFSctl& args, XrdOucErrInfo& error,\n                    const XrdSecEntity* client) = 0;\n  virtual int _stat(const char* Name, struct stat* buf, XrdOucErrInfo& out_error,\n                    eos::common::VirtualIdentity& vid, const char* opaque = 0,\n                    std::string* etag = 0, bool follow = true, std::string* uri = 0) = 0;\n  virtual void _stat_set_flags(struct stat* buf) = 0;\n  virtual std::string get_logId() = 0;\n  virtual std::string get_host() = 0;\n  virtual void writeEosReportRecord(const std::string& record) = 0;\n  virtual ~IMgmFileSystemInterface() {}\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_IMGMFILESYSTEMINTERFACE_HH\n"
  },
  {
    "path": "mgm/bulk-request/interface/RealMgmFileSystemInterface.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file RealMgmFileSystemInterface.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"RealMgmFileSystemInterface.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/iostat/Iostat.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nRealMgmFileSystemInterface::RealMgmFileSystemInterface(XrdMgmOfs* mgmOfs):\n  mMgmOfs(mgmOfs) {}\n\nvoid RealMgmFileSystemInterface::addStats(const char* tag, uid_t uid, gid_t gid,\n    unsigned long val)\n{\n  mMgmOfs->MgmStats.Add(tag, uid, gid, val);\n}\n\nbool RealMgmFileSystemInterface::isTapeEnabled()\n{\n  return mMgmOfs->mTapeEnabled;\n}\n\nint RealMgmFileSystemInterface::getReqIdMaxCount()\n{\n  return mMgmOfs->mReqIdMax;\n}\n\nint RealMgmFileSystemInterface::Emsg(const char* pfx, XrdOucErrInfo& einfo,\n                                     int ecode, const char* op, const char* target)\n{\n  return mMgmOfs->Emsg(pfx, einfo, ecode, op, target);\n}\nint RealMgmFileSystemInterface::_exists(const char* path,\n                                        XrdSfsFileExistence& file_exists, XrdOucErrInfo& error,\n                                        const XrdSecEntity* client, const char* ininfo)\n{\n  return mMgmOfs->_exists(path, file_exists, error, client, ininfo);\n}\nint RealMgmFileSystemInterface::_exists(const char* path,\n                                        XrdSfsFileExistence& file_exists, XrdOucErrInfo& error,\n                                        eos::common::VirtualIdentity& vid, const char* opaque, bool take_lock)\n{\n  return mMgmOfs->_exists(path, file_exists, error, vid, opaque, take_lock);\n}\n\nint RealMgmFileSystemInterface::_attr_ls(const char* path,\n    XrdOucErrInfo& out_error, const eos::common::VirtualIdentity& vid,\n    const char* opaque, eos::IContainerMD::XAttrMap& map,\n    bool links)\n{\n  return mMgmOfs->_attr_ls(path, out_error, vid, opaque, map, links);\n}\n\nint RealMgmFileSystemInterface::_access(const char* path, int mode,\n                                        XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* info)\n{\n  return mMgmOfs->_access(path, mode, error, vid, info);\n}\n\nint RealMgmFileSystemInterface::FSctl(const int cmd, XrdSfsFSctl& args,\n                                      XrdOucErrInfo& error, const XrdSecEntity* client)\n{\n  return mMgmOfs->FSctl(cmd, args, error, client);\n}\n\nint RealMgmFileSystemInterface::_stat(const char* path, struct stat* buf,\n                                      XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* ininfo,\n                                      std::string* etag, bool follow, std::string* uri)\n{\n  return mMgmOfs->_stat(path, buf, error, vid, ininfo, etag, follow, uri);\n}\n\nvoid RealMgmFileSystemInterface::_stat_set_flags(struct stat* buf)\n{\n  mMgmOfs->_stat_set_flags(buf);\n}\n\nstd::string RealMgmFileSystemInterface::get_logId()\n{\n  return std::string(mMgmOfs->logId);\n}\n\nstd::string RealMgmFileSystemInterface::get_host()\n{\n  if (mMgmOfs->MgmOfsAlias.length()) {\n    return std::string(mMgmOfs->MgmOfsAlias.c_str());\n  } else if (mMgmOfs->HostName != nullptr) {\n    return std::string(mMgmOfs->HostName);\n  } else {\n    return \"unknown\";\n  }\n}\n\nvoid RealMgmFileSystemInterface::writeEosReportRecord(const std::string&\n    record)\n{\n  if (mMgmOfs->mIoStats) {\n    mMgmOfs->mIoStats->WriteRecord(record);\n  }\n}\n\nEOSBULKNAMESPACE_END\n"
  },
  {
    "path": "mgm/bulk-request/interface/RealMgmFileSystemInterface.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file RealMgmFileSystemInterface.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_REALMGMFILESYSTEMINTERFACE_HH\n#define EOS_REALMGMFILESYSTEMINTERFACE_HH\n\n#include \"IMgmFileSystemInterface.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nclass RealMgmFileSystemInterface : public IMgmFileSystemInterface\n{\npublic:\n  RealMgmFileSystemInterface(XrdMgmOfs* mgmOfs);\n  ~RealMgmFileSystemInterface() {}\n  void addStats(const char* tag, uid_t uid, gid_t gid,\n                unsigned long val) override;\n  bool isTapeEnabled() override;\n  int getReqIdMaxCount() override;\n  int Emsg(const char* pfx, XrdOucErrInfo& einfo, int ecode, const char* op,\n           const char* target = \"\") override;\n  int _exists(const char* path, XrdSfsFileExistence& file_exists,\n              XrdOucErrInfo& error, const XrdSecEntity* client = 0,\n              const char* ininfo = 0) override;\n  int _exists(const char* path, XrdSfsFileExistence& file_exists,\n              XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* opaque = 0,\n              bool take_lock = true) override;\n  int _attr_ls(const char* path, XrdOucErrInfo& out_error,\n               const eos::common::VirtualIdentity& vid, const char* opaque,\n               eos::IContainerMD::XAttrMap& map,\n               bool links = false) override;\n  int _access(const char* path, int mode, XrdOucErrInfo& error,\n              eos::common::VirtualIdentity& vid, const char* info) override;\n  int FSctl(const int cmd, XrdSfsFSctl& args, XrdOucErrInfo& error,\n            const XrdSecEntity* client) override;\n  int _stat(const char* Name, struct stat* buf, XrdOucErrInfo& out_error,\n            eos::common::VirtualIdentity& vid, const char* opaque = 0,\n            std::string* etag = 0, bool follow = true, std::string* uri = 0) override;\n  void _stat_set_flags(struct stat* buf) override;\n  std::string get_logId() override;\n  std::string get_host() override;\n  void writeEosReportRecord(const std::string& record) override;\n\nprivate:\n  XrdMgmOfs* mMgmOfs;\n};\n\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_REALMGMFILESYSTEMINTERFACE_HH\n"
  },
  {
    "path": "mgm/bulk-request/prepare/CancellationBulkRequest.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file CancellationBulkRequest.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_CANCELLATIONBULKREQUEST_HH\n#define EOS_CANCELLATIONBULKREQUEST_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/bulk-request/BulkRequest.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * This class represents a bulk request containing files that\n * have to be prepared\n */\nclass CancellationBulkRequest : public BulkRequest\n{\npublic:\n  CancellationBulkRequest(const std::string& id) : BulkRequest(id) {}\n  BulkRequest::Type getType() const override { return BulkRequest::Type::PREPARE_CANCEL; }\nprivate:\n};\n\nEOSBULKNAMESPACE_END\n#endif // EOS_CANCELLATIONBULKREQUEST_HH\n"
  },
  {
    "path": "mgm/bulk-request/prepare/EvictBulkRequest.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file EvictBulkRequest.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_EVICTBULKREQUEST_HH\n#define EOS_EVICTBULKREQUEST_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/bulk-request/BulkRequest.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nclass EvictBulkRequest : public BulkRequest\n{\npublic:\n  EvictBulkRequest(const std::string& id) : BulkRequest(id) {}\n  BulkRequest::Type getType() const override { return BulkRequest::Type::PREPARE_EVICT; }\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_EVICTBULKREQUEST_HH\n"
  },
  {
    "path": "mgm/bulk-request/prepare/PrepareUtils.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file PrepareUtils.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"PrepareUtils.hh\"\n#include <XrdVersion.hh>\n#include <sstream>\n#include <XrdSfs/XrdSfsInterface.hh>\n\nEOSBULKNAMESPACE_BEGIN\n\nstd::string PrepareUtils::prepareOptsToString(const int opts)\n{\n  std::ostringstream result;\n  const int priority = opts & Prep_PMASK;\n\n  switch (priority) {\n  case Prep_PRTY0:\n    result << \"PRTY0\";\n    break;\n\n  case Prep_PRTY1:\n    result << \"PRTY1\";\n    break;\n\n  case Prep_PRTY2:\n    result << \"PRTY2\";\n    break;\n\n  case Prep_PRTY3:\n    result << \"PRTY3\";\n    break;\n\n  default:\n    result << \"PRTYUNKNOWN\";\n  }\n\n  const int send_mask = 12;\n  const int send = opts & send_mask;\n\n  switch (send) {\n  case 0:\n    break;\n\n  case Prep_SENDAOK:\n    result << \",SENDAOK\";\n    break;\n\n  case Prep_SENDERR:\n    result << \",SENDERR\";\n    break;\n\n  case Prep_SENDACK:\n    result << \",SENDACK\";\n    break;\n\n  default:\n    result << \",SENDUNKNOWN\";\n  }\n\n  if (opts & Prep_WMODE) {\n    result << \",WMODE\";\n  }\n\n  if (opts & Prep_STAGE) {\n    result << \",STAGE\";\n  }\n\n  if (opts & Prep_COLOC) {\n    result << \",COLOC\";\n  }\n\n  if (opts & Prep_FRESH) {\n    result << \",FRESH\";\n  }\n\n#if (XrdMajorVNUM(XrdVNUMBER) == 4 && XrdMinorVNUM(XrdVNUMBER) >= 10) || XrdMajorVNUM(XrdVNUMBER) >= 5\n\n  if (opts & Prep_CANCEL) {\n    result << \",CANCEL\";\n  }\n\n  if (opts & Prep_QUERY) {\n    result << \",QUERY\";\n  }\n\n  if (opts & Prep_EVICT) {\n    result << \",EVICT\";\n  }\n\n#endif\n  return result.str();\n}\n\nEOSBULKNAMESPACE_END"
  },
  {
    "path": "mgm/bulk-request/prepare/PrepareUtils.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file PrepareUtils.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_PREPAREUTILS_HH\n#define EOS_PREPAREUTILS_HH\n\n#include <string>\n#include \"mgm/Namespace.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * Utility class for Prepare-related business\n */\nclass PrepareUtils\n{\npublic:\n  /**\n  * Utility method to convert the prepare options to string options\n  * @param opts the prepare options to convert to string\n  * @return the prepare options in the string format\n  */\n  static std::string prepareOptsToString(const int opts);\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_PREPAREUTILS_HH\n"
  },
  {
    "path": "mgm/bulk-request/prepare/StageBulkRequest.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file StageBulkRequest.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_STAGEBULKREQUEST_HH\n#define EOS_STAGEBULKREQUEST_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/bulk-request/BulkRequest.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * This class represents a bulk request containing files that\n * have to be prepared\n */\nclass StageBulkRequest : public BulkRequest\n{\npublic:\n  StageBulkRequest(const std::string& id,\n                   const common::VirtualIdentity& issuerVid)\n    : BulkRequest(id), mIssuerVid(issuerVid), mCreationTime(::time(nullptr)) {}\n  StageBulkRequest(const std::string& id,\n                   const common::VirtualIdentity& issuerVid, const time_t& creationTime)\n    : BulkRequest(id), mIssuerVid(issuerVid), mCreationTime(creationTime) {}\n  BulkRequest::Type getType() const override { return BulkRequest::Type::PREPARE_STAGE; }\n  const common::VirtualIdentity& getIssuerVid() const { return mIssuerVid; }\n  time_t getCreationTime() const { return mCreationTime; }\nprivate:\n  //The virtual identity of the person who issued this bulk-request\n  const eos::common::VirtualIdentity mIssuerVid;\n  const time_t mCreationTime;\n};\n\nEOSBULKNAMESPACE_END\n#endif // EOS_STAGEBULKREQUEST_HH\n"
  },
  {
    "path": "mgm/bulk-request/prepare/manager/BulkRequestPrepareManager.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file BulkRequestPrepareManager.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"BulkRequestPrepareManager.hh\"\n#include \"mgm/bulk-request/BulkRequestFactory.hh\"\n#include \"mgm/bulk-request/exception/PersistencyException.hh\"\n#include \"mgm/bulk-request/exception/BulkRequestException.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nBulkRequestPrepareManager::BulkRequestPrepareManager(\n  std::unique_ptr<IMgmFileSystemInterface>&& mgmFsInterface):\n  PrepareManager(std::move(mgmFsInterface))\n{\n}\n\nvoid BulkRequestPrepareManager::setBulkRequestBusiness(\n  std::shared_ptr<BulkRequestBusiness> bulkRequestBusiness)\n{\n  mBulkRequestBusiness = bulkRequestBusiness;\n}\n\nstd::unique_ptr<BulkRequest> BulkRequestPrepareManager::getBulkRequest()\n{\n  return std::move(mBulkRequest);\n}\n\nvoid BulkRequestPrepareManager::initializeStagePrepareRequest(\n  XrdOucString& reqid, const common::VirtualIdentity& vid)\n{\n  mBulkRequest = BulkRequestFactory::createStageBulkRequest(vid);\n  reqid = mBulkRequest->getId().c_str();\n}\n\nvoid BulkRequestPrepareManager::initializeCancelPrepareRequest(\n  XrdOucString& reqid)\n{\n  mBulkRequest = BulkRequestFactory::createCancelBulkRequest(reqid.c_str());\n}\n\nvoid BulkRequestPrepareManager::addFileToBulkRequest(std::unique_ptr<File>&&\n    file)\n{\n  if (mBulkRequest != nullptr) {\n    mBulkRequest->addFile(std::move(file));\n  }\n}\n\nvoid BulkRequestPrepareManager::saveBulkRequest()\n{\n  if (mBulkRequestBusiness != nullptr && mBulkRequest != nullptr) {\n    try {\n      mBulkRequestBusiness->saveBulkRequest(mBulkRequest.get());\n    } catch (const PersistencyException& ex) {\n      eos_err(\"msg=\\\"Unable to persist the bulk request %s\\\" \\\"ExceptionWhat=%s\\\"\",\n              mBulkRequest->getId().c_str(), ex.what());\n      throw ex;\n    }\n  }\n}\n\nbool BulkRequestPrepareManager::ignorePrepareFailures()\n{\n  return true;\n}\n\nEOSBULKNAMESPACE_END\n"
  },
  {
    "path": "mgm/bulk-request/prepare/manager/BulkRequestPrepareManager.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file BulkRequestPrepareManager.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_BULKREQUESTPREPAREMANAGER_HH\n#define EOS_BULKREQUESTPREPAREMANAGER_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/bulk-request/prepare/manager/PrepareManager.hh\"\n#include \"mgm/bulk-request/business/BulkRequestBusiness.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * This class allows to implement the template method pattern.\n * This pattern allows to add the bulk-requets management without changing the algorithm of the PrepareManager::prepare() method\n */\nclass BulkRequestPrepareManager : public PrepareManager\n{\npublic:\n  BulkRequestPrepareManager(std::unique_ptr<IMgmFileSystemInterface>&&\n                            mgmFsInterface);\n  /**\n   * Allows to enable the bulk-request management linked to the prepare logic\n   * @param bulkRequestBusiness the class that allows to manage operations linked to bulk-requests\n   */\n  void setBulkRequestBusiness(std::shared_ptr<BulkRequestBusiness>\n                              bulkRequestBusiness);\n  std::unique_ptr<BulkRequest> getBulkRequest();\nprotected:\n  /**\n   * This overriding method will instanciate a stage bulk request and will\n   * affect its reqid to the variable passed in parameter\n   * @param reqid the request id that will be set to the bulk-request\n   */\n  void initializeStagePrepareRequest(XrdOucString& reqid,\n                                     const common::VirtualIdentity& vid) override;\n\n  void initializeCancelPrepareRequest(XrdOucString& reqid) override;\n\n  /**\n   * Adds the path passed in parameter to this instance's bulk-request\n   * @param path the path to add to the bulk-request\n   */\n  void addFileToBulkRequest(std::unique_ptr<File>&& file) override;\n  /**\n   * Persists the managed bulk-request\n   */\n  void saveBulkRequest() override;\n\n  bool ignorePrepareFailures() override;\nprivate:\n  //The bulk-request business allowing the persistence of the bulk-request\n  std::shared_ptr<BulkRequestBusiness> mBulkRequestBusiness;\n  //The bulk request that possibly got created depending on the prepare command triggered\n  std::unique_ptr<BulkRequest> mBulkRequest;\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_BULKREQUESTPREPAREMANAGER_HH\n"
  },
  {
    "path": "mgm/bulk-request/prepare/manager/PrepareManager.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file PrepareManager.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <XrdVersion.hh>\n#include <XrdOuc/XrdOucTList.hh>\n#include <XrdSfs/XrdSfsFlags.hh>\n#include \"PrepareManager.hh\"\n#include \"common/Constants.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/EosCtaReporter.hh\"\n#include \"mgm/bulk-request/response/QueryPrepareResponse.hh\"\n#include \"common/Path.hh\"\n#include \"common/Timing.hh\"\n#include \"common/SecEntity.hh\"\n#include \"common/utils/XrdUtils.hh\"\n#include \"common/Definitions.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include <mgm/xattr/XattrSet.hh>\n#include \"mgm/bulk-request/File.hh\"\n#include \"mgm/bulk-request/exception/PersistencyException.hh\"\n#include \"mgm/bulk-request/prepare/PrepareUtils.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nPrepareManager::PrepareManager(std::unique_ptr<IMgmFileSystemInterface>&&\n                               mgmFsInterface): mMgmFsInterface(std::move(mgmFsInterface))\n{\n}\n\nint PrepareManager::prepare(XrdSfsPrep& pargs, XrdOucErrInfo& error,\n                            const XrdSecEntity* client) noexcept\n{\n  return doPrepare(pargs, error, client);\n}\n\nint PrepareManager::prepare(XrdSfsPrep& pargs, XrdOucErrInfo& error,\n                            const common::VirtualIdentity* vid) noexcept\n{\n  XrdSecEntity client;\n  return doPrepare(pargs, error, &client, vid);\n}\n\nvoid PrepareManager::initializeStagePrepareRequest(XrdOucString& reqid,\n    const common::VirtualIdentity& vid)\n{\n  // Override the XRootD-supplied request ID. The request ID can be any arbitrary string, so long as\n  // it is guaranteed to be unique for each request.\n  //\n  // Note: To use the default request ID supplied in pargs.reqid, return SFS_OK instead of SFS_DATA.\n  //       Overriding is only possible in the case of PREPARE. In the case of ABORT and QUERY requests,\n  //       pargs.reqid should contain the request ID that was returned by the corresponding PREPARE.\n  // Request ID = XRootD-generated request ID + timestamp\n  std::ostringstream ss;\n  ss << ':' << time(0);\n  reqid.append(ss.str().c_str());\n}\n\nvoid PrepareManager::initializeCancelPrepareRequest(XrdOucString& reqid)\n{\n  //Nothing to do as cancellation does not require the creation of an ID\n}\n\nint PrepareManager::doPrepare(XrdSfsPrep& pargs, XrdOucErrInfo& error,\n                              const XrdSecEntity* client, const common::VirtualIdentity* vidClient) noexcept\n{\n  EXEC_TIMING_BEGIN(\"Prepare\");\n  eos_info(\"prepareOpts=\\\"%s\\\"\",\n           PrepareUtils::prepareOptsToString(pargs.opts).c_str());\n  static const char* epname = mEpname.c_str();\n  const char* tident;\n  XrdOucTList* pptr = pargs.paths;\n  XrdOucTList* optr = pargs.oinfo;\n  std::string info;\n  info = (optr ? (optr->text ? optr->text : \"\") : \"\");\n  eos::common::VirtualIdentity vid;\n\n  if (vidClient != nullptr) {\n    vid = *vidClient;\n    tident = vid.tident.c_str();\n  } else {\n    tident = error.getErrUser();\n    eos::common::Mapping::IdMap(client, info.c_str(), tident, vid);\n    mMgmFsInterface->addStats(\"IdMap\", vid.uid, vid.gid, 1);\n  }\n\n  ACCESSMODE_W;\n  MAYSTALL;\n  {\n    const char* inpath = \"/\";\n    const char* ininfo = \"\";\n    MAYREDIRECT;\n  }\n  const int nbFilesProvidedByUser =\n    eos::common::XrdUtils::countNbElementsInXrdOucTList(pargs.paths);\n  {\n    mMgmFsInterface->addStats(\"Prepare\", vid.uid, vid.gid,\n                              nbFilesProvidedByUser);\n  }\n  std::string cmd = \"mgm.pcmd=event\";\n  std::list<std::tuple<char**, char**, EosCtaReporterPrepareReq>> pathsToPrepare;\n  // Initialise the request ID for the Prepare request to the one provided by XRootD\n  XrdOucString reqid(pargs.reqid);\n  // Validate the event type\n  std::string event;\n#if (XrdMajorVNUM(XrdVNUMBER) == 4 && XrdMinorVNUM(XrdVNUMBER) >= 10) || XrdMajorVNUM(XrdVNUMBER) >= 5\n  // Strip \"quality of service\" bits from pargs.opts so that only the action to\n  // be taken is left\n  const int pargsOptsAction = getPrepareActionsFromOpts(pargs.opts);\n\n  // The XRootD prepare actions are mutually exclusive\n  switch (pargsOptsAction) {\n  case 0:\n    if (mMgmFsInterface->isTapeEnabled()) {\n      mMgmFsInterface->Emsg(epname, error, EINVAL,\n                            \"prepare with empty pargs.opts on tape-enabled back-end\");\n      return SFS_ERROR;\n    }\n\n    break;\n\n  case Prep_STAGE:\n    event = \"sync::prepare\";\n    mPrepareAction = PrepareAction::STAGE;\n    initializeStagePrepareRequest(reqid, vid);\n    break;\n\n  case Prep_CANCEL:\n    mPrepareAction = PrepareAction::ABORT;\n    initializeCancelPrepareRequest(reqid);\n    event = \"sync::abort_prepare\";\n    break;\n\n  case Prep_EVICT:\n    mPrepareAction = PrepareAction::EVICT;\n    event = \"sync::evict_prepare\";\n    break;\n\n  default:\n    // More than one flag was set or there is an unknown flag\n    mMgmFsInterface->Emsg(epname, error, EINVAL,\n                          \"prepare - invalid value for pargs.opts =\",\n                          std::to_string(pargs.opts).c_str());\n    return SFS_ERROR;\n  }\n\n#else\n\n  // The XRootD prepare flags are mutually exclusive\n  switch (pargs.opts) {\n  case 0:\n    if (mMgmFsInterface.isTapeEnabled()) {\n      mMgmFsInterface.Emsg(epname, error, EINVAL,\n                           \"prepare with empty pargs.opts on tape-enabled back-end\");\n      return SFS_ERROR;\n    }\n\n    break;\n\n  case Prep_STAGE:\n    event = \"sync::prepare\";\n    break;\n\n  case Prep_FRESH:\n    event = \"sync::abort_prepare\";\n    break;\n\n  default:\n    // More than one flag was set or there is an unknown flag\n    mMgmFsInterface.Emsg(epname, error, EINVAL,\n                         \"prepare - invalid value for pargs.opts =\",\n                         std::to_string(pargs.opts).c_str());\n    return SFS_ERROR;\n  }\n\n#endif\n  int error_counter = 0;\n  XrdOucErrInfo first_error;\n  struct timespec ts_now;\n  eos::common::Timing::GetTimeSpec(ts_now);\n\n  // check that all files exist\n  for (\n    ; pptr\n    ; pptr = pptr->next, optr = optr ? optr->next : optr) {\n    XrdOucString prep_path = (pptr->text ? pptr->text : \"\");\n    std::string orig_path = prep_path.c_str();\n    std::unique_ptr<bulk::File> currentFile = nullptr;\n    EosCtaReporterPrepareReq eosLog([&](const std::string & in) {\n      mMgmFsInterface->writeEosReportRecord(in);\n    });\n    eosLog\n    .addParam(EosCtaReportParam::SEC_APP, \"tape_prepare\")\n    .addParam(EosCtaReportParam::LOG, std::string(mMgmFsInterface->get_logId()))\n    .addParam(EosCtaReportParam::PATH, orig_path)\n    .addParam(EosCtaReportParam::RUID, vid.uid)\n    .addParam(EosCtaReportParam::RGID, vid.gid)\n    .addParam(EosCtaReportParam::TD, vid.tident.c_str())\n    .addParam(EosCtaReportParam::HOST, mMgmFsInterface->get_host())\n    .addParam(EosCtaReportParam::PREP_REQ_REQID, reqid.c_str())\n    .addParam(EosCtaReportParam::TS, ts_now.tv_sec)\n    .addParam(EosCtaReportParam::TNS, ts_now.tv_nsec);\n    eos_info(\"msg=\\\"checking file exists\\\" path=\\\"%s\\\"\", prep_path.c_str());\n    {\n      const char* inpath = prep_path.c_str();\n      const char* ininfo = \"\";\n      NAMESPACEMAP;\n\n      //Valgrind Source and destination overlap in strncpy(...)\n      if (prep_path.c_str() != path) {\n        prep_path = path;\n      }\n    }\n    {\n      const char* inpath = prep_path.c_str();\n      const char* ininfo = \"\";\n      MAYREDIRECT;\n    }\n    XrdSfsFileExistence check;\n\n    if (prep_path.length() == 0) {\n      std::string errorMsg = \"prepare - path empty or uses forbidden characters\";\n      mMgmFsInterface->Emsg(epname, error, ENOENT,\n                            errorMsg.append(\":\").c_str(),\n                            orig_path.c_str());\n\n      if (error_counter == 0) {\n        first_error = error;\n      }\n\n      error_counter++;\n      eosLog\n      .addParam(EosCtaReportParam::PREP_REQ_SENTTOWFE, false)\n      .addParam(EosCtaReportParam::PREP_REQ_SUCCESSFUL, false)\n      .addParam(EosCtaReportParam::PREP_REQ_ERROR, errorMsg);\n      continue;\n    }\n\n    currentFile = std::make_unique<File>(prep_path.c_str());\n\n    if (mMgmFsInterface->_exists(prep_path.c_str(), check, error, vid, \"\") ||\n        (check != XrdSfsFileExistIsFile)) {\n      std::string errorMsg =\n        \"prepare - file does not exist or is not accessible to you\";\n      mMgmFsInterface->Emsg(epname, error, ENOENT,\n                            errorMsg.append(\":\").c_str(),\n                            prep_path.c_str());\n      currentFile->setError(errorMsg);\n\n      if (error_counter == 0) {\n        first_error = error;\n      }\n\n      error_counter++;\n      addFileToBulkRequest(std::move(currentFile));\n      eosLog\n      .addParam(EosCtaReportParam::PREP_REQ_SENTTOWFE, false)\n      .addParam(EosCtaReportParam::PREP_REQ_SUCCESSFUL, false)\n      .addParam(EosCtaReportParam::PREP_REQ_ERROR, errorMsg);\n      continue;\n    }\n\n    //Extended attributes for the current file's parent directory\n    eos::IContainerMD::XAttrMap attributes;\n\n    if (!event.empty() &&\n        mMgmFsInterface->_attr_ls(eos::common::Path(prep_path.c_str()).GetParentPath(),\n                                  error, vid,\n                                  nullptr, attributes) == 0) {\n      bool foundPrepareTag = false;\n      std::string eventAttr = \"sys.workflow.\" + event;\n      eosLog.addParam(EosCtaReportParam::PREP_REQ_EVENT, event);\n\n      for (const auto& attrEntry : attributes) {\n        foundPrepareTag |= attrEntry.first.find(eventAttr) == 0;\n      }\n\n      if (foundPrepareTag) {\n        pathsToPrepare.emplace_back(&(pptr->text),\n                                    optr != nullptr ? & (optr->text) : nullptr,\n                                    std::move(eosLog));\n      } else {\n        // don't do workflow if no such tag\n        std::ostringstream oss;\n        oss << \"No prepare workflow set on the directory \" << eos::common::Path(\n              prep_path.c_str()).GetParentPath();\n        currentFile->setError(oss.str());\n        addFileToBulkRequest(std::move(currentFile));\n        eosLog\n        .addParam(EosCtaReportParam::PREP_REQ_SENTTOWFE, false)\n        .addParam(EosCtaReportParam::PREP_REQ_SUCCESSFUL, true);\n        continue;\n      }\n    } else {\n      // don't do workflow if event not set or we can't check attributes\n      if (!event.empty()) {\n        std::ostringstream oss;\n        oss << \"Unable to check the extended attributes of the directory \"\n            << eos::common::Path(prep_path.c_str()).GetParentPath();\n        currentFile->setError(oss.str());\n        eosLog\n        .addParam(EosCtaReportParam::PREP_REQ_SENTTOWFE, false)\n        .addParam(EosCtaReportParam::PREP_REQ_SUCCESSFUL, false)\n        .addParam(EosCtaReportParam::PREP_REQ_ERROR, oss.str());\n      }\n\n      addFileToBulkRequest(std::move(currentFile));\n      continue;\n    }\n\n    // check that we have write permission on path\n    // This can only be done after we confirm that there the directory contains a prepare workflow attribute\n    if (mMgmFsInterface->_access(prep_path.c_str(), P_OK, error, vid, \"\")) {\n      std::string errorMsg = \"prepare - you don't have prepare permission\";\n      mMgmFsInterface->Emsg(epname, error, EPERM,\n                            errorMsg.append(\":\").c_str(),\n                            prep_path.c_str());\n      currentFile->setError(errorMsg);\n      pathsToPrepare.pop_back();\n\n      if (error_counter == 0) {\n        first_error = error;\n      }\n\n      error_counter++;\n      addFileToBulkRequest(std::move(currentFile));\n      eosLog\n      .addParam(EosCtaReportParam::PREP_REQ_SENTTOWFE, true)\n      .addParam(EosCtaReportParam::PREP_REQ_SUCCESSFUL, true)\n      .addParam(EosCtaReportParam::PREP_REQ_ERROR, errorMsg);\n      continue;\n    }\n\n    eos::IFileMD::XAttrMap xattrs;\n\n    if (isStagePrepare()) {\n      // Check file status in the extended attributes\n      if (mMgmFsInterface->_attr_ls(\n              eos::common::Path(prep_path.c_str()).GetPath(), error, vid,\n              nullptr, xattrs) == 0) {\n        XattrSet prepareReqIds;\n        auto xattr_it = xattrs.find(eos::common::RETRIEVE_REQID_ATTR_NAME);\n        if (xattr_it != xattrs.end() && !xattr_it->second.empty()) {\n          prepareReqIds.deserialize(xattr_it->second);\n        }\n\n        if (prepareReqIds.values.size() >=\n            mMgmFsInterface->getReqIdMaxCount()) {\n          std::ostringstream oss;\n          oss << \"prepare - reached maximum number of retrieve requests on file \"\n              << \" (\" << mMgmFsInterface->getReqIdMaxCount() << \")\";\n          std::string errorMsg = oss.str();\n          mMgmFsInterface->Emsg(epname, error, EUSERS,\n                                errorMsg.append(\":\").c_str(),\n                                orig_path.c_str());\n\n          currentFile->setError(errorMsg);\n          pathsToPrepare.pop_back();\n\n          if (error_counter == 0) {\n            first_error = error;\n          }\n          error_counter++;\n\n          eosLog.addParam(EosCtaReportParam::PREP_REQ_SENTTOWFE, false)\n              .addParam(EosCtaReportParam::PREP_REQ_SUCCESSFUL, false)\n              .addParam(EosCtaReportParam::PREP_REQ_ERROR, errorMsg);\n          addFileToBulkRequest(std::move(currentFile));\n          continue;\n        }\n      } else {\n        // failed to read extended attributes\n        std::ostringstream oss;\n        oss << \"Unable to check the extended attributes of the file \"\n            << prep_path;\n        currentFile->setError(oss.str());\n        pathsToPrepare.pop_back();\n        eosLog.addParam(EosCtaReportParam::PREP_REQ_SENTTOWFE, false)\n            .addParam(EosCtaReportParam::PREP_REQ_SUCCESSFUL, false)\n            .addParam(EosCtaReportParam::PREP_REQ_ERROR, oss.str());\n        addFileToBulkRequest(std::move(currentFile));\n        continue;\n      }\n    }\n\n    if (currentFile != nullptr) {\n      addFileToBulkRequest(std::move(currentFile));\n    }\n  }\n\n  try {\n    saveBulkRequest();\n  } catch (const PersistencyException& ex) {\n    return ex.fillXrdErrInfo(error, EIO);\n  }\n\n  if (isStagePrepare() && nbFilesProvidedByUser == error_counter) {\n    //All stage request failed\n    eos_err(\"Unable to prepare - failed to prepare all files with reqID %s\",\n            reqid.c_str());\n\n    if (error_counter > 0) {\n      int err_code;\n      std::stringstream err_message;\n      err_message << first_error.getErrText(err_code);\n\n      if (error_counter > 1) {\n        err_message << \" (all \" << (error_counter - 1) <<\n                    \" other files also failed with errors)\";\n      }\n\n      error.setErrInfo(err_code, err_message.str().c_str());\n    }\n\n    if (!ignorePrepareFailures()) {\n      return SFS_ERROR;\n    }\n  }\n\n  //Trigger the prepare workflow\n  triggerPrepareWorkflow(pathsToPrepare, cmd, event, reqid, error, vid);\n  int retc = SFS_OK;\n#if (XrdMajorVNUM(XrdVNUMBER) == 4 && XrdMinorVNUM(XrdVNUMBER) >= 10) || XrdMajorVNUM(XrdVNUMBER) >= 5\n\n  // If we generated our own request ID, return it to the client\n  if (isStagePrepare()) {\n    // If we return SFS_DATA, the first parameter is the length of the buffer, not the error code\n    error.setErrInfo(reqid.length() + 1, reqid.c_str());\n    retc = SFS_DATA;\n  } else {\n    if (error_counter > 0) {\n      if (!ignorePrepareFailures()) {\n        int err_code;\n        std::stringstream err_message;\n        err_message << first_error.getErrText(err_code);\n\n        if (error_counter > 1) {\n          err_message << \" (\" << (error_counter - 1) <<\n                      \" other files also failed with errors)\";\n        }\n\n        error.setErrInfo(err_code, err_message.str().c_str());\n        retc = SFS_ERROR;\n      }\n    }\n  }\n\n#endif\n  EXEC_TIMING_END(\"Prepare\");\n  return retc;\n}\n\nvoid PrepareManager::saveBulkRequest()\n{\n}\n\nbool PrepareManager::ignorePrepareFailures()\n{\n  return false;\n}\n\nvoid PrepareManager::addFileToBulkRequest(std::unique_ptr<File>&& file)\n{\n  //The normal PrepareManager does not have any bulk-request, do nothing\n  // Sub-classes may decide to implement this member function\n}\n\nint PrepareManager::getPrepareActionsFromOpts(const int pargsOpts) const\n{\n  const int pargsOptsQoS = Prep_PMASK | Prep_SENDAOK | Prep_SENDERR | Prep_SENDACK\n                           | Prep_WMODE | Prep_COLOC | Prep_FRESH;\n  return pargsOpts & ~pargsOptsQoS;\n}\n\nbool PrepareManager::isStagePrepare() const\n{\n  return mPrepareAction == PrepareAction::STAGE;\n}\n\nvoid PrepareManager::triggerPrepareWorkflow(\n  std::list<std::tuple<char**, char**, EosCtaReporterPrepareReq>>& pathsToPrepare,\n  const std::string& cmd, const std::string& event, const XrdOucString& reqid,\n  XrdOucErrInfo& error, const eos::common::VirtualIdentity& vid)\n{\n  for (auto& pathTuple : pathsToPrepare) {\n    EosCtaReporterPrepareReq eosLog = std::move(std::get<2>(pathTuple));\n    XrdOucString prep_path = (*std::get<0>(pathTuple) ? *std::get<0>\n                              (pathTuple) : \"\");\n    {\n      const char* inpath = prep_path.c_str();\n      const char* ininfo = \"\";\n      NAMESPACEMAP;\n\n      //Valgrind Source and destination overlap in strncpy(...)\n      if (prep_path.c_str() != path) {\n        prep_path = path;\n      }\n    }\n    XrdOucString prep_info = std::get<1>(pathTuple) != nullptr ?\n                             (*std::get<1>(pathTuple) ? *std::get<1>(pathTuple) : \"\") : \"\";\n    eos_info(\"msg=\\\"about to trigger WFE\\\" path=\\\"%s\\\" info=\\\"%s\\\"\",\n             prep_path.c_str(), prep_info.c_str());\n    XrdOucEnv prep_env(prep_info.c_str());\n    prep_info = cmd.c_str();\n    prep_info += \"&mgm.event=\";\n    prep_info += event.c_str();\n    prep_info += \"&mgm.workflow=\";\n\n    if (prep_env.Get(\"eos.workflow\")) {\n      prep_info += prep_env.Get(\"eos.workflow\");\n    } else {\n      prep_info += \"default\";\n    }\n\n    prep_info += \"&mgm.fid=0&mgm.path=\";\n    prep_info += prep_path.c_str();\n    prep_info += \"&mgm.logid=\";\n    prep_info += this->logId;\n    prep_info += \"&mgm.ruid=\";\n    prep_info += (int)vid.uid;\n    prep_info += \"&mgm.rgid=\";\n    prep_info += (int)vid.gid;\n    prep_info += \"&mgm.reqid=\";\n    prep_info += reqid.c_str();\n\n    if (prep_env.Get(\"activity\")) {\n      prep_info += \"&activity=\";\n      prep_info += prep_env.Get(\"activity\");\n    }\n\n    XrdSecEntity lClient(vid.prot.c_str());\n    lClient.name = (char*) vid.name.c_str();\n    lClient.tident = (char*) vid.tident.c_str();\n    lClient.host = (char*) vid.host.c_str();\n    XrdOucString lSec = \"&mgm.sec=\";\n    lSec += eos::common::SecEntity::ToKey(&lClient,\n                                          \"eos\").c_str();\n    prep_info += lSec;\n    XrdSfsFSctl args;\n    args.Arg1 = prep_path.c_str();\n    args.Arg1Len = prep_path.length();\n    args.Arg2 = prep_info.c_str();\n    args.Arg2Len = prep_info.length();\n    auto ret_wfe = mMgmFsInterface->FSctl(SFS_FSCTL_PLUGIN, args, error, &lClient);\n\n    // Log errors but continue to process the rest of the files in the list\n    if (ret_wfe != SFS_DATA) {\n      std::ostringstream oss;\n      oss << \"Unable to prepare - synchronous prepare workflow error \" <<\n          prep_path.c_str() << \"; \" << error.getErrText();\n      eos_err(oss.str().c_str());\n      eosLog\n      .addParam(EosCtaReportParam::PREP_REQ_SENTTOWFE, false)\n      .addParam(EosCtaReportParam::PREP_REQ_SUCCESSFUL, false)\n      .addParam(EosCtaReportParam::PREP_REQ_ERROR, oss.str());\n    } else {\n      eosLog\n      .addParam(EosCtaReportParam::PREP_REQ_SENTTOWFE, true)\n      .addParam(EosCtaReportParam::PREP_REQ_SUCCESSFUL, true);\n    }\n  }\n}\n\nstd::unique_ptr<QueryPrepareResult> PrepareManager::queryPrepare(\n  XrdSfsPrep& pargs, XrdOucErrInfo& error, const XrdSecEntity* client)\n{\n  std::unique_ptr<QueryPrepareResult> queryPrepareResult(new\n      QueryPrepareResult());\n  int retCode = doQueryPrepare(pargs, error, client, *queryPrepareResult);\n  queryPrepareResult->setReturnCode(retCode);\n  return queryPrepareResult;\n}\n\nstd::unique_ptr<QueryPrepareResult> PrepareManager::queryPrepare(\n  XrdSfsPrep& pargs, XrdOucErrInfo& error,\n  const common::VirtualIdentity* vidClient)\n{\n  std::unique_ptr<QueryPrepareResult> queryPrepareResult(new\n      QueryPrepareResult());\n  int retCode = doQueryPrepare(pargs, error, nullptr, *queryPrepareResult,\n                               vidClient);\n  queryPrepareResult->setReturnCode(retCode);\n  return queryPrepareResult;\n}\n\nint PrepareManager::doQueryPrepare(XrdSfsPrep& pargs, XrdOucErrInfo& error,\n                                   const XrdSecEntity* client, QueryPrepareResult& result,\n                                   const common::VirtualIdentity* vidClient)\n{\n  EXEC_TIMING_BEGIN(\"QueryPrepare\");\n  ACCESSMODE_R;\n  eos_info(\"cmd=\\\"_prepare_query\\\"\");\n  eos::common::VirtualIdentity vid;\n\n  if (vidClient != nullptr) {\n    vid = *vidClient;\n  } else  {\n    const char* tident = error.getErrUser();\n    XrdOucTList* optr = pargs.oinfo;\n    std::string info(optr && optr->text ? optr->text : \"\");\n    eos::common::Mapping::IdMap(client, info.c_str(), tident, vid);\n  }\n\n  MAYSTALL;\n  {\n    const char* inpath = \"/\";\n    const char* ininfo = \"\";\n    MAYREDIRECT;\n  }\n  // ID of the original prepare request. We don't need this to look up the list of files in\n  // the request, as they are provided in the arguments. Anyway we return it in the reply\n  // as a convenience for the client to track which prepare request the query applies to.\n  XrdOucString reqid(pargs.reqid);\n  int path_cnt = 0;\n  FileCollection filesToQueryCollection;\n\n  for (XrdOucTList* pptr = pargs.paths; pptr; pptr = pptr->next) {\n    if (!pptr->text) {\n      continue;\n    }\n\n    filesToQueryCollection.addFile(std::make_unique<File>(pptr->text));\n    ++path_cnt;\n  }\n\n  mMgmFsInterface->addStats(\"QueryPrepare\", vid.uid, vid.gid, path_cnt);\n  auto filesToQuery = filesToQueryCollection.getAllFiles();\n  std::shared_ptr<QueryPrepareResponse> response = result.getResponse();\n\n  // Set the queryPrepareFileResponses for each file in the list\n  for (auto& file : *filesToQuery) {\n    response->responses.push_back(QueryPrepareFileResponse(file->getPath()));\n    auto& rsp = response->responses.back();\n    auto currentFile = file;\n    // check if the file exists\n    XrdOucString prep_path;\n    {\n      const char* inpath = rsp.path.c_str();\n      const char* ininfo = \"\";\n      NAMESPACEMAP;\n      prep_path = path;\n    }\n    {\n      const char* inpath = rsp.path.c_str();\n      const char* ininfo = \"\";\n      MAYREDIRECT;\n    }\n    //Initialization of variables\n    XrdOucErrInfo xrd_error;\n    struct stat buf;\n    eos::IFileMD::XAttrMap xattrs;\n    XrdSfsFileExistence check;\n\n    if (prep_path.length() == 0) {\n      currentFile->setErrorIfNotAlreadySet(\"USER ERROR: path empty or uses forbidden characters\");\n      goto logErrorAndContinue;\n    }\n\n    if (mMgmFsInterface->_exists(prep_path.c_str(), check, error, vid, \"\") ||\n        check != XrdSfsFileExistIsFile) {\n      currentFile->setErrorIfNotAlreadySet(\"USER ERROR: file does not exist or is not accessible to you\");\n      goto logErrorAndContinue;\n    }\n\n    rsp.is_exists = true;\n\n    // Check file state (online/offline)\n    if (mMgmFsInterface->_stat(rsp.path.c_str(), &buf, xrd_error, vid, nullptr,\n                               nullptr, false)) {\n      currentFile->setErrorIfNotAlreadySet(xrd_error.getErrText());\n      goto logErrorAndContinue;\n    }\n\n    mMgmFsInterface->_stat_set_flags(&buf);\n    rsp.is_on_tape = buf.st_rdev & XRDSFS_HASBKUP;\n    rsp.is_online  = !(buf.st_rdev & XRDSFS_OFFLINE);\n\n    // Check file status in the extended attributes\n    if (mMgmFsInterface->_attr_ls(eos::common::Path(prep_path.c_str()).GetPath(),\n                                  xrd_error, vid,\n                                  nullptr, xattrs) == 0) {\n      auto xattr_it = xattrs.find(eos::common::RETRIEVE_REQID_ATTR_NAME);\n\n      if (xattr_it != xattrs.end()) {\n        // has file been requested? (not necessarily with this request ID)\n        rsp.is_requested = !xattr_it->second.empty();\n        // and is this specific request ID present in the request?\n        rsp.is_reqid_present = (xattr_it->second.find(reqid.c_str()) != std::string::npos);\n      }\n\n      xattr_it = xattrs.find(eos::common::RETRIEVE_REQTIME_ATTR_NAME);\n\n      if (xattr_it != xattrs.end()) {\n        rsp.request_time = xattr_it->second;\n      }\n\n      xattr_it = xattrs.find(eos::common::RETRIEVE_ERROR_ATTR_NAME);\n\n      if (xattr_it == xattrs.end()) {\n        // If there is no retrieve error, check for an archive error\n        xattr_it = xattrs.find(eos::common::ARCHIVE_ERROR_ATTR_NAME);\n      }\n\n      if (xattr_it != xattrs.end()) {\n        currentFile->setErrorIfNotAlreadySet(xattr_it->second);\n      }\n    } else {\n      // failed to read extended attributes\n      currentFile->setErrorIfNotAlreadySet(xrd_error.getErrText());\n      goto logErrorAndContinue;\n    }\n\n    if (mMgmFsInterface->_access(prep_path.c_str(), P_OK, error, vid, \"\")) {\n      currentFile->setError(\n        std::string(\"USER ERROR: you don't have prepare permission\"));\n      goto logErrorAndContinue;\n    }\n\nlogErrorAndContinue:\n\n    if (currentFile->getError()) {\n      rsp.error_text = currentFile->getError().value();\n    }\n  }\n\n  response->request_id = reqid.c_str();\n  /*\n  json_ss << \"{\"\n          << \"\\\"request_id\\\":\\\"\" << reqid << \"\\\",\"\n          << \"\\\"responses\\\":[\";\n  bool is_first(true);\n\n  for (auto& r : response) {\n    if (is_first) {\n      is_first = false;\n    } else {\n      json_ss << \",\";\n    }\n\n    json_ss << r;\n  }\n\n  json_ss << \"]\"\n          << \"}\";\n          */\n  result.setQueryPrepareFinished();\n  EXEC_TIMING_END(\"QueryPrepare\");\n  return SFS_DATA;\n}\n\nEOSBULKNAMESPACE_END\n"
  },
  {
    "path": "mgm/bulk-request/prepare/manager/PrepareManager.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file PrepareManager.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_PREPAREMANAGER_HH\n#define EOS_PREPAREMANAGER_HH\n\n#include \"common/Logging.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"mgm/EosCtaReporter.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/bulk-request/interface/IMgmFileSystemInterface.hh\"\n#include \"mgm/bulk-request/prepare/query-prepare/QueryPrepareResult.hh\"\n#include <mgm/bulk-request/FileCollection.hh>\n#include \"mgm/bulk-request/BulkRequest.hh\"\n#include <XrdSfs/XrdSfsInterface.hh>\n#include <list>\n#include <string>\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * This class manages all the operations linked to the preparation of a file:\n * - queue it for retrieval on the tape system\n * - query the preparation\n */\nclass PrepareManager : public eos::common::LogId\n{\npublic:\n  /**\n   * Types of prepare action\n   */\n  enum PrepareAction {\n    STAGE,\n    EVICT,\n    ABORT\n  };\n  /**\n   * Constructor\n   * @param pargs Xrootd prepare arguments\n   * @param error Xrootd error information to fill if there are any errors\n   * @param client the client who issued the prepare\n   */\n  PrepareManager(std::unique_ptr<IMgmFileSystemInterface>&& mgmFsInterface);\n\n\n  /**\n   * Allows to launch a prepare logic on the files passed in parameter\n   * @param pargs Xrootd prepare arguments (containing the path of the files)\n   * @param error Xrootd error information to fill if there are any errors\n   * @param client the client who issued the prepare\n   * @returns the status code of the issued prepare request\n   */\n  virtual int prepare(XrdSfsPrep& pargs, XrdOucErrInfo& error,\n                      const XrdSecEntity* client) noexcept;\n\n  /**\n   * Allows to launch a prepare logic on the files passed in parameter. Will not perform a client map\n   * as the vid is already given\n   * @param pargs Xrootd prepare arguments (containing the path of the files)\n   * @param error Xrootd error information to fill if there are any errors\n   * @param vid the vid of the client who issued the prepare\n   * @return the status code of the issued prepare request\n   */\n  virtual int prepare(XrdSfsPrep& pargs, XrdOucErrInfo& error,\n                      const common::VirtualIdentity* vid) noexcept;\n\n  /**\n   * Allows to launch a query prepare logic on the files passed in parameter\n   * @param pargs Xrootd prepare arguments (containing the path of the files)\n   * @param error Xrootd error information to fill if there are any errors\n   * @param client the client who issued the query prepare\n   * @returns the query prepare result object containing the result of the query prepare request\n   */\n  virtual std::unique_ptr<QueryPrepareResult> queryPrepare(XrdSfsPrep& pargs,\n      XrdOucErrInfo& error, const XrdSecEntity* client);\n\n  /**\n   * Allows to launch a query prepare logic on the files passed in parameter\n   * @param pargs Xrootd prepare arguments (containing the path of the files)\n   * @param error Xrootd error information to fill if there are any errors\n   * @param vid the vid of the client who issued the query prepare\n   * @returns the query prepare result object containing the result of the query prepare request\n   */\n  virtual std::unique_ptr<QueryPrepareResult> queryPrepare(XrdSfsPrep& pargs,\n      XrdOucErrInfo& error, const common::VirtualIdentity* vid);\n\n  virtual ~PrepareManager() {}\n\nprotected:\n\n  virtual void initializeStagePrepareRequest(XrdOucString& reqid,\n      const common::VirtualIdentity& vid);\n\n  virtual void initializeCancelPrepareRequest(XrdOucString& reqid);\n\n  virtual bool ignorePrepareFailures();\n\n  virtual void setErrorToBulkRequest(const std::string& path,\n                                     const std::string& error) {}\n\n  /**\n   * Returns the Prepare actions to perform from the options given by Xrootd (XrdSfsPrep.opts)\n   * @param pargsOpts the prepare options given by Xrootd (XrdSfsPrep.opts)\n   * @return the Prepare actions to perform from the options given by Xrootd (XrdSfsPrep.opts)\n   */\n  int getPrepareActionsFromOpts(const int pargsOpts) const;\n\n  /**\n   * @return true if this prepare request is a stage one, false otherwise\n   */\n  virtual bool isStagePrepare() const;\n\n  /**\n   * Triggers the prepare workflow to all the pathsToPrepare passed in parameter\n   * @param pathsToPrepare the paths of the files on which we want to trigger a prepare workflow\n   * @param cmd the command to run in the Workflow engine\n   * @param event the event to trigger (sync::prepare, sync::evict_prepare...)\n   * @param reqid the requestId of this prepare request\n   * @param error The error that will be returned to the client if an error happens\n   * @param vid the identity of the person who issued the prepare request\n   */\n  void triggerPrepareWorkflow(\n    std::list<std::tuple<char**, char**, EosCtaReporterPrepareReq>>&\n    pathsToPrepare, const std::string& cmd, const std::string& event,\n    const XrdOucString& reqid, XrdOucErrInfo& error,\n    const eos::common::VirtualIdentity& vid);\n\n  /**\n   * Will call the business layer to persist the bulk request\n   */\n  virtual void saveBulkRequest();\n\n  /**\n   * Will add the prepared path to the bulk request if it exists\n   * @param file the file to add to the bulk-request\n   */\n  virtual void addFileToBulkRequest(std::unique_ptr<File>&& file);\n\n  /**\n   * Perform the prepare logic\n   * @param pargs Xrootd prepare arguments\n   * @param error Xrootd error information to fill if there are any errors\n   * @param client the client who issued the prepare\n   * @param vid the vid of the client if the latter has already been mapped. (Avoids an IdMap call on the client param)\n   * @returns the status code of the issued prepare request\n   */\n  int doPrepare(XrdSfsPrep& pargs, XrdOucErrInfo& error,\n                const XrdSecEntity* client,\n                const common::VirtualIdentity* vidClient = nullptr) noexcept;\n\n  /**\n   * Perform the query prepare logic\n   * @param pargs Xrootd prepare arguments\n   * @param error Xrootd error information to fill if there are any errors\n   * @param client the client who issued the query prepare\n   * @param vid the vid of the client if the latter has already been mapped. (Avoids an IdMap call on the client param)\n   * @returns the status code of the issued prepare request\n   */\n  int doQueryPrepare(XrdSfsPrep& pargs, XrdOucErrInfo& error,\n                     const XrdSecEntity* client, QueryPrepareResult& result,\n                     const common::VirtualIdentity* vidClient = nullptr);\n\n  inline static std::string mEpname = \"prepare\";\n  //The prepare action that is launched by the \"prepare()\" method\n  PrepareAction mPrepareAction;\n  //MGM file system interface\n  std::unique_ptr<IMgmFileSystemInterface> mMgmFsInterface;\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_PREPAREMANAGER_HH\n"
  },
  {
    "path": "mgm/bulk-request/prepare/query-prepare/QueryPrepareResult.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file QueryPrepareResult.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#include \"QueryPrepareResult.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nQueryPrepareResult::QueryPrepareResult(): mHasQueryPrepareFinished(false)\n{\n  mResponse.reset(new QueryPrepareResponse());\n}\n\nbool QueryPrepareResult::hasQueryPrepareFinished() const\n{\n  return mHasQueryPrepareFinished;\n}\n\nstd::shared_ptr<QueryPrepareResponse> QueryPrepareResult::getResponse() const\n{\n  return mResponse;\n}\n\nvoid QueryPrepareResult::setQueryPrepareFinished()\n{\n  mHasQueryPrepareFinished = true;\n}\n\nint QueryPrepareResult::getReturnCode() const\n{\n  return mReturnCode;\n}\n\nvoid QueryPrepareResult::setReturnCode(int returnCode)\n{\n  mReturnCode = returnCode;\n}\n\nEOSBULKNAMESPACE_END"
  },
  {
    "path": "mgm/bulk-request/prepare/query-prepare/QueryPrepareResult.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file QueryPrepareResult.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_QUERYPREPARERESULT_HH\n#define EOS_QUERYPREPARERESULT_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/bulk-request/response/QueryPrepareResponse.hh\"\n#include <memory>\n\nEOSBULKNAMESPACE_BEGIN\n\n/**\n * Class holding the result of the query prepare execution\n * It contains the response that will be returned to the client and the return code that\n * is set during the execution of the query prepare method of the PrepareManager.\n */\nclass QueryPrepareResult\n{\npublic:\n  /**\n   * The PrepareManager need to acess the setter methods\n   */\n  friend class PrepareManager;\n  QueryPrepareResult();\n  bool hasQueryPrepareFinished() const;\n  std::shared_ptr<QueryPrepareResponse> getResponse() const;\n  int getReturnCode() const;\nprivate:\n  void setQueryPrepareFinished();\n  void setReturnCode(int returnCode);\n  bool mHasQueryPrepareFinished;\n  std::shared_ptr<QueryPrepareResponse> mResponse;\n  int mReturnCode;\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_QUERYPREPARERESULT_HH\n"
  },
  {
    "path": "mgm/bulk-request/response/QueryPrepareResponse.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/************************************************************************\n * @file  QueryPrepareResponse.hh                                       *\n * @brief Struct to store \"xrdfs query prepare\" responses and           *\n *        serialize to JSON                                             *\n ************************************************************************/\n\n#pragma once\n\n#include \"mgm/Namespace.hh\"\n#include <sstream>\n#include <string>\n#include <vector>\n#include \"common/json/Jsonifiable.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nclass QueryPrepareFileResponse\n{\npublic:\n\n  QueryPrepareFileResponse() :\n    is_exists(false), is_on_tape(false), is_online(false), is_requested(false),\n    is_reqid_present(false) {}\n\n  QueryPrepareFileResponse(const std::string _path) :\n    path(_path), is_exists(false), is_on_tape(false), is_online(false),\n    is_requested(false), is_reqid_present(false) {}\n\n  // @ccaffy TODO: to be removed at the end of the bulk-request implementation\n  friend std::ostream& operator<<(std::ostream& json,\n                                  QueryPrepareFileResponse& qpr)\n  {\n    json << \"{\"\n         << \"\\\"path\\\":\\\"\"       << qpr.path << \"\\\",\"\n         << \"\\\"path_exists\\\":\"  << (qpr.is_exists        ? \"true,\" : \"false,\")\n         << \"\\\"on_tape\\\":\"      << (qpr.is_on_tape       ? \"true,\" : \"false,\")\n         << \"\\\"online\\\":\"       << (qpr.is_online        ? \"true,\" : \"false,\")\n         << \"\\\"requested\\\":\"    << (qpr.is_requested     ? \"true,\" : \"false,\")\n         << \"\\\"has_reqid\\\":\"    << (qpr.is_reqid_present ? \"true,\" : \"false,\")\n         << \"\\\"req_time\\\":\\\"\"   << qpr.request_time << \"\\\",\"\n         << \"\\\"error_text\\\":\\\"\" << qpr.error_text << \"\\\"\"\n         << \"}\";\n    return json;\n  }\n\n  //Path of the file\n  std::string path;\n  //Does it exist?\n  bool is_exists;\n  //Is it on tape?\n  bool is_on_tape;\n  //Is it on disk?\n  bool is_online;\n  //Is it currently requested?\n  bool is_requested;\n  //Is this file has a request id?\n  bool is_reqid_present;\n  //The time this file was requested\n  std::string request_time;\n  //The eventual error that the file encountered while being staged or archived\n  std::string error_text;\n};\n\n/**\n * Class holding the information contained in the response\n * of a QueryPrepare query. This is the class that will be\n * returned to the user in json format\n */\nclass QueryPrepareResponse : public common::Jsonifiable<QueryPrepareResponse>\n{\npublic:\n  std::string request_id;\n  std::vector<QueryPrepareFileResponse> responses;\n};\n\nEOSBULKNAMESPACE_END\n"
  },
  {
    "path": "mgm/bulk-request/utils/PrepareArgumentsWrapper.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file PrepareArgumentsWrapper.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_PREPAREARGUMENTSWRAPPER_HH\n#define EOS_PREPAREARGUMENTSWRAPPER_HH\n\n#include \"auth_plugin/ProtoUtils.hh\"\n#include \"mgm/Namespace.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nclass PrepareArgumentsWrapper\n{\npublic:\n  PrepareArgumentsWrapper(const std::string& reqid, const int opts,\n                          const std::vector<std::string>& paths,\n                          const std::vector<std::string>& oinfos): mPargs(nullptr)\n  {\n    mPargsProto.set_reqid(reqid);\n    mPargsProto.set_opts(opts);\n\n    for (auto& oinfo : oinfos) {\n      mPargsProto.add_oinfo(oinfo);\n    }\n\n    for (auto& path : paths) {\n      mPargsProto.add_paths(path);\n    }\n  }\n\n  PrepareArgumentsWrapper(const std::string& reqid,\n                          const int opts): mPargs(nullptr)\n  {\n    mPargsProto.set_reqid(reqid);\n    mPargsProto.set_opts(opts);\n  }\n\n  ~PrepareArgumentsWrapper()\n  {\n    if (mPargs != nullptr) {\n      eos::auth::utils::DeleteXrdSfsPrep(mPargs);\n      mPargs = nullptr;\n    }\n  }\n\n  void addFile(const std::string& path, const std::string& opaqueInfos)\n  {\n    mPargsProto.add_paths(path);\n    mPargsProto.add_oinfo(opaqueInfos);\n  }\n\n  uint64_t getNbFiles()\n  {\n    return mPargsProto.paths().size();\n  }\n\n  XrdSfsPrep* getPrepareArguments()\n  {\n    mPargs = eos::auth::utils::GetXrdSfsPrep(mPargsProto);\n    return mPargs;\n  }\nprivate:\n  eos::auth::XrdSfsPrepProto mPargsProto;\n  XrdSfsPrep* mPargs;\n};\n\n\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_PREPAREARGUMENTSWRAPPER_HH\n"
  },
  {
    "path": "mgm/bulk-request/utils/json/QueryPrepareResponseJson.hh",
    "content": "// ----------------------------------------------------------------------\n// File: BulkJsonCppObject.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_QUERYPREPARERESPONSEJSON_HH\n#define EOS_QUERYPREPARERESPONSEJSON_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"common/json/JsonCppJsonifier.hh\"\n#include \"mgm/bulk-request/response/QueryPrepareResponse.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nclass QueryPrepareResponseJson : public\n  common::JsonCppJsonifier<QueryPrepareResponse>\n{\npublic:\n  virtual void jsonify(const QueryPrepareResponse* obj,\n                       std::stringstream& ss) override;\nprivate:\n  void jsonify(const QueryPrepareFileResponse& fileResponse, Json::Value& value);\n};\n\n\nvoid QueryPrepareResponseJson::jsonify(const QueryPrepareResponse* obj,\n                                       std::stringstream& ss)\n{\n  Json::Value root;\n  root[\"request_id\"] = obj->request_id;\n  initializeArray(root[\"responses\"]);\n\n  for (const auto& fileResponse : obj->responses) {\n    Json::Value response;\n    jsonify(fileResponse, response);\n    root[\"responses\"].append(response);\n  }\n\n  ss << root;\n}\n\nvoid QueryPrepareResponseJson::jsonify(const QueryPrepareFileResponse&\n                                       fileResponse, Json::Value& value)\n{\n  value[\"path\"] = fileResponse.path;\n  value[\"path_exists\"] = fileResponse.is_exists;\n  value[\"on_tape\"] = fileResponse.is_on_tape;\n  value[\"online\"] = fileResponse.is_online;\n  value[\"requested\"] = fileResponse.is_requested;\n  value[\"has_reqid\"] = fileResponse.is_reqid_present;\n  value[\"req_time\"] = fileResponse.request_time;\n  value[\"error_text\"] = fileResponse.error_text;\n}\n\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_QUERYPREPARERESPONSEJSON_HH\n"
  },
  {
    "path": "mgm/commandmap/CommandMap.cc",
    "content": "// ----------------------------------------------------------------------\n// File: CommandMap.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/commandmap/CommandMap.hh\"\n\nnamespace\n{\n\nusing namespace eos::mgm;\n\nstd::map<std::string, FsctlCommand> fsctlCommandMap;\n\nstruct fsctlMapInit {\n  fsctlMapInit()\n  {\n    fsctlCommandMap[\"access\"] = FsctlCommand::access;\n    fsctlCommandMap[\"adjustreplica\"] = FsctlCommand::adjustreplica;\n    fsctlCommandMap[\"checksum\"] = FsctlCommand::checksum;\n    fsctlCommandMap[\"chmod\"] = FsctlCommand::chmod;\n    fsctlCommandMap[\"chown\"] = FsctlCommand::chown;\n    fsctlCommandMap[\"commit\"] = FsctlCommand::commit;\n    fsctlCommandMap[\"drop\"] = FsctlCommand::drop;\n    fsctlCommandMap[\"event\"] = FsctlCommand::event;\n    fsctlCommandMap[\"getfmd\"] = FsctlCommand::getfmd;\n    fsctlCommandMap[\"getfusex\"] = FsctlCommand::getfusex;\n    fsctlCommandMap[\"is_master\"] = FsctlCommand::is_master;\n    fsctlCommandMap[\"mkdir\"] = FsctlCommand::mkdir;\n    fsctlCommandMap[\"open\"] = FsctlCommand::open;\n    fsctlCommandMap[\"readlink\"] = FsctlCommand::readlink;\n    fsctlCommandMap[\"redirect\"] = FsctlCommand::redirect;\n    fsctlCommandMap[\"stat\"] = FsctlCommand::stat;\n    fsctlCommandMap[\"statvfs\"] = FsctlCommand::statvfs;\n    fsctlCommandMap[\"symlink\"] = FsctlCommand::symlink;\n    fsctlCommandMap[\"txstate\"] = FsctlCommand::txstate;\n    fsctlCommandMap[\"utimes\"] = FsctlCommand::utimes;\n    fsctlCommandMap[\"version\"] = FsctlCommand::version;\n  }\n\n} fsctl_map_init_object;\n\n}\n\nEOSMGMNAMESPACE_BEGIN\n\nFsctlCommand lookupFsctl(const std::string& cmd)\n{\n  auto it = fsctlCommandMap.find(cmd);\n\n  if (it == fsctlCommandMap.end()) {\n    return FsctlCommand::INVALID;\n  }\n\n  return it->second;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/commandmap/CommandMap.hh",
    "content": "// ----------------------------------------------------------------------\n// File: CommandMap.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_COMMANDMAP__HH__\n#define __EOSMGM_COMMANDMAP__HH__\n\n#include \"mgm/Namespace.hh\"\n#include <string>\n#include <map>\n\nEOSMGMNAMESPACE_BEGIN\n\nenum class FsctlCommand {\n  INVALID = 0,\n  access,\n  adjustreplica,\n  checksum,\n  chmod,\n  chown,\n  commit,\n  drop,\n  event,\n  getfmd,\n  getfusex,\n  is_master,\n  mkdir,\n  open,\n  readlink,\n  redirect,\n  schedule2balance, // not used anymore\n  schedule2delete,  // not used anymore\n  stat,\n  statvfs,\n  symlink,\n  txstate,\n  utimes,\n  version,\n};\n\nFsctlCommand lookupFsctl(const std::string& cmd);\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/config/IConfigEngine.cc",
    "content": "//------------------------------------------------------------------------------\n// File: IConfigEngine.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/config/IConfigEngine.hh\"\n#include \"common/Mapping.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/vid/Vid.hh\"\n#include \"mgm/iostat/Iostat.hh\"\n#include \"mgm/proc/proc_fs.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/geotreeengine/GeoTreeEngine.hh\"\n#include \"mgm/routeendpoint/RouteEndpoint.hh\"\n#include \"mgm/pathrouting/PathRouting.hh\"\n#include \"mgm/fsck/Fsck.hh\"\n#include \"mgm/convert/ConverterEngine.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/imaster/IMaster.hh\"\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"mq/SharedHashWrapper.hh\"\n#include <sstream>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//                          **** IConfigEngine ****\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nIConfigEngine::IConfigEngine():\n  mChangelog(), mAutosave(false),\n  mConfigFile(\"default\")\n{}\n\n\n//------------------------------------------------------------------------------\n// XrdOucHash callback function to apply a configuration value\n//------------------------------------------------------------------------------\nint\nIConfigEngine::ApplyEachConfig(const char* key, const char* val,\n                               XrdOucString* err)\n{\n  if (!key || !val) {\n    return 0;\n  }\n\n  std::ostringstream oss_err;\n  XrdOucString toenv = val;\n\n  while (toenv.replace(\" \", \"&\")) {}\n\n  XrdOucEnv envdev(toenv.c_str());\n  XrdOucString skey = key;\n  std::string sval = val;\n  eos_static_debug(\"key=%s val=%s\", skey.c_str(), sval.c_str());\n\n  if (skey.beginswith(\"fs:\")) {\n    // Set a filesystem definition\n    skey.erase(0, 3);\n\n    if (!FsView::gFsView.ApplyFsConfig(skey.c_str(), sval)) {\n      oss_err << \"error: failed to apply config \"\n              << key << \" => \" << sval.c_str() << std::endl;\n    }\n  } else if (skey.beginswith(\"global:\")) {\n    // Set a global configuration\n    skey.erase(0, 7);\n\n    if (!FsView::gFsView.ApplyGlobalConfig(skey.c_str(), sval)) {\n      oss_err << \"error: failed to apply config \"\n              << key << \" => \" << sval.c_str() << std::endl;\n    }\n  } else if (skey.beginswith(\"map:\")) {\n    // Set a mapping\n    skey.erase(0, 4);\n\n    if (!gOFS->AddPathMap(skey.c_str(), sval.c_str(), false)) {\n      oss_err << \"error: failed to apply config \"\n              << key << \" => \" << sval.c_str() << std::endl;\n    }\n  } else if (skey.beginswith(\"route:\")) {\n    // Set a routing\n    skey.erase(0, 6);\n    RouteEndpoint endpoint;\n\n    if (!endpoint.ParseFromString(sval.c_str())) {\n      eos_static_err(\"failed to parse route config %s => %s\", key, sval.c_str());\n      oss_err << \"error: failed to parse route config \"\n              << key << \" => \" << sval.c_str() << std::endl;\n    } else {\n      if (!gOFS->mRouting->Add(skey.c_str(), std::move(endpoint))) {\n        oss_err << \"error: failed to apply config \"\n                << key << \" => \" << sval.c_str() << std::endl;\n      }\n    }\n  } else if (skey.beginswith(\"quota:\")) {\n    // Set a quota definition\n    skey.erase(0, 6);\n    int space_offset = 0;\n    int ug_offset = skey.find(':', space_offset + 1);\n    int ug_equal_offset = skey.find('=', ug_offset + 1);\n    int tag_offset = skey.find(':', ug_equal_offset + 1);\n\n    if ((ug_offset == STR_NPOS) || (ug_equal_offset == STR_NPOS) ||\n        (tag_offset == STR_NPOS)) {\n      eos_static_err(\"cannot parse config line key: |%s|\", skey.c_str());\n      oss_err << \"error: cannot parse config line key: \"\n              << skey.c_str() << std::endl;\n      *err = oss_err.str().c_str();\n      return 0;\n    }\n\n    XrdOucString space(skey, 0, ug_offset - 1);\n    XrdOucString ug(skey, ug_offset + 1, ug_equal_offset - 1);\n    XrdOucString ugid(skey, ug_equal_offset + 1, tag_offset - 1);\n    XrdOucString tag(skey, tag_offset + 1);\n    unsigned long long value = strtoll(sval.c_str(), 0, 10);\n    long id = strtol(ugid.c_str(), 0, 10);\n\n    if (!space.endswith('/')) {\n      space += '/';\n    }\n\n    if (id > 0 || (ugid == \"0\")) {\n      if (Quota::Create(space.c_str())) {\n        if (!Quota::SetQuotaForTag(space.c_str(), tag.c_str(), id, value)) {\n          eos_static_err(\"failed to set quota for id=%s\", ugid.c_str());\n          oss_err << \"error: failed to set quota for id:\" << ugid << std::endl;\n        }\n      } else {\n        // This is just ignored ... maybe path is wrong?!\n        eos_static_err(\"failed to create quota for space=%s\", space.c_str());\n      }\n    } else {\n      eos_static_err(\"config id is negative\");\n      oss_err << \"error: illegal id found: \" << ugid << std::endl;\n    }\n  } else if (skey.beginswith(\"vid:\")) {\n    // Set a virutal Identity\n    int envlen;\n\n    if (!Vid::Set(envdev.Env(envlen), false)) {\n      eos_static_err(\"failed applying config line key: |%s| => |%s|\",\n                     skey.c_str(), sval.c_str());\n      oss_err << \"error: cannot apply config line key: \"\n              << skey.c_str() << std::endl;\n    }\n  } else if (skey.beginswith(\"geosched:\")) {\n    skey.erase(0, 9);\n\n    if (!gOFS->mGeoTreeEngine->setParameter(skey.c_str(), sval.c_str(), -2)) {\n      eos_static_err(\"failed applying config line key: |geosched:%s| => |%s|\",\n                     skey.c_str(), sval.c_str());\n      oss_err << \"error: failed applying config line key: geosched:\"\n              << skey.c_str() << std::endl;\n    }\n  } else if (skey.beginswith(\"comment\")) {\n    // Ignore comments\n    return 0;\n  } else if (skey.beginswith(\"policy:\")) {\n    // Set a policy - not used\n    return 0;\n  } else if (skey.beginswith(\"ns:\")) {\n    // internal NS configuration option\n    std::map<std::string, std::string> map_cfg;\n    gOFS->mMaster->FillNsCacheConfig(gOFS->mConfigEngine, map_cfg);\n    gOFS->eosFileService->configure(map_cfg);\n    gOFS->eosDirectoryService->configure(map_cfg);\n    return 0;\n  } else {\n    oss_err << \"error: unsupported configuration line: \" << skey.c_str() << \" -> \"\n            << sval.c_str() << std::endl;\n  }\n\n  *err += oss_err.str().c_str();\n  return 0;\n}\n\n\n//------------------------------------------------------------------------------\n// Publish the given configuration change\n//------------------------------------------------------------------------------\nvoid IConfigEngine::PublishConfigChange(const std::string& key,\n                                        const std::string& value)\n{\n  eos_info(\"msg=\\\"publish configuration change\\\" key=\\\"%s\\\" val=\\\"%s\\\"\",\n           key.c_str(), value.c_str());\n  XrdOucString repval = value.c_str();\n\n  while (repval.replace(\"&\", \" \")) {}\n\n  mq::SharedHashWrapper::Batch batch;\n  batch.SetTransient(key, repval.c_str());\n  mq::SharedHashWrapper::makeGlobalMgmHash(gOFS->mMessagingRealm.get()).set(\n    batch);\n}\n\n//------------------------------------------------------------------------------\n// Publish the deletion of the given configuration key\n//------------------------------------------------------------------------------\nvoid IConfigEngine::PublishConfigDeletion(const std::string& key)\n{\n  eos_info(\"msg=\\\"publish deletion of configuration\\\" key=\\\"%s\\\"\",\n           key.c_str());\n  mq::SharedHashWrapper::makeGlobalMgmHash(gOFS->mMessagingRealm.get()).del(key);\n}\n\n//------------------------------------------------------------------------------\n// Apply a given configuration definition\n//------------------------------------------------------------------------------\nbool\nIConfigEngine::ApplyConfig(XrdOucString& err, bool apply_stall_redirect)\n{\n  err = \"\";\n  // Cleanup quota map\n  (void) Quota::CleanUp();\n  {\n    eos::common::RWMutexWriteLock wr_lock(eos::common::Mapping::gMapMutex);\n    eos::common::Mapping::gUserRoleVector.clear();\n    eos::common::Mapping::gGroupRoleVector.clear();\n    eos::common::Mapping::gVirtualUidMap.clear();\n    eos::common::Mapping::gVirtualGidMap.clear();\n    eos::common::Mapping::gAllowedTidentMatches.clear();\n  }\n  Access::Reset(!apply_stall_redirect);\n  {\n    // Reset space attribute map\n    std::unique_lock<std::mutex> lock(gOFS->mSpaceAttributesMutex);\n    gOFS->mSpaceAttributes.clear();\n  }\n  {\n    eos::common::RWMutexWriteLock wr_view_lock(eos::mgm::FsView::gFsView.ViewMutex);\n    std::lock_guard lock(mMutex);\n    // Disable the defaults in FsSpace\n    FsSpace::gDisableDefaults = true;\n\n    for (auto it = sConfigDefinitions.begin();\n         it != sConfigDefinitions.end(); it++) {\n      ApplyEachConfig(it->first.c_str(), it->second.c_str(), &err);\n    }\n\n    // Enable the defaults in FsSpace\n    FsSpace::gDisableDefaults = false;\n  }\n  Access::EnforceConfig(apply_stall_redirect);\n  gOFS->mFsckEngine->ApplyConfig(&FsView::gFsView);\n  gOFS->mIoStats->ApplyConfig(&FsView::gFsView);\n  gOFS->mDrainEngine.ApplyConfig();\n  gOFS->mTrafficShapingEngine.ApplyConfig();\n\n  if (gOFS->mConverterEngine) {\n    gOFS->mConverterEngine->ApplyConfig();\n  }\n\n  if (err.length()) {\n    errno = EINVAL;\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Delete a configuration key from the responsible object\n//------------------------------------------------------------------------------\nvoid\nIConfigEngine::ApplyKeyDeletion(const char* key)\n{\n  XrdOucString skey = key;\n  eos_static_info(\"key=%s\", skey.c_str());\n\n  if (skey.beginswith(\"fs:\")) {\n    XrdOucString stdOut;\n    XrdOucString stdErr;\n    std::string id;\n    eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n    skey.erase(0, 3);\n    int spos1 = skey.find(\"/\", 1);\n    int spos2 = skey.find(\"/\", spos1 + 1);\n    int spos3 = skey.find(\"/\", spos2 + 1);\n    std::string nodename = skey.c_str();\n    std::string mountpoint = skey.c_str();\n    nodename.erase(spos3);\n    mountpoint.erase(0, spos3);\n    eos::common::RWMutexWriteLock lock(FsView::gFsView.ViewMutex);\n    proc_fs_rm(nodename, mountpoint, id, stdOut, stdErr, rootvid);\n  } else  if (skey.beginswith(\"map:\")) {\n    skey.erase(0, 4);\n    eos::common::RWMutexWriteLock lock(gOFS->PathMapMutex);\n\n    if (gOFS->PathMap.count(skey.c_str())) {\n      gOFS->PathMap.erase(skey.c_str());\n    }\n  } else  if (skey.beginswith(\"route:\")) {\n    skey.erase(0, 6);\n    gOFS->mRouting->Remove(skey.c_str());\n  } else if (skey.beginswith(\"quota:\")) {\n    // Remove quota definition\n    skey.erase(0, 6);\n    int space_offset = 0;\n    int ug_offset = skey.find(':', space_offset + 1);\n    int ug_equal_offset = skey.find('=', ug_offset + 1);\n    int tag_offset = skey.find(':', ug_equal_offset + 1);\n\n    if ((ug_offset == STR_NPOS) || (ug_equal_offset == STR_NPOS) ||\n        (tag_offset == STR_NPOS)) {\n      eos_static_err(\"failed to remove quota definition %s\", skey.c_str());\n      return;\n    }\n\n    XrdOucString space(skey, 0, ug_offset - 1);\n    XrdOucString ug(skey, ug_offset + 1, ug_equal_offset - 1);\n    XrdOucString ugid(skey, ug_equal_offset + 1, tag_offset - 1);\n    XrdOucString tag(skey, tag_offset + 1);\n    long id = strtol(ugid.c_str(), 0, 10);\n\n    if (id > 0 || (ugid == \"0\")) {\n      if (!Quota::RmQuotaForTag(space.c_str(), tag.c_str(), id)) {\n        eos_static_err(\"failed to remove quota %s for id=%ll\", tag.c_str(), id);\n      }\n    }\n  } else if (skey.beginswith(\"vid:\")) {\n    // Remove vid entry\n    XrdOucString stdOut;\n    XrdOucString stdErr;\n    int retc = 0;\n    XrdOucString vidstr = \"mgm.vid.key=\";\n    vidstr += skey.c_str();\n    XrdOucEnv videnv(vidstr.c_str());\n    Vid::Rm(videnv, retc, stdOut, stdErr, false);\n\n    if (retc) {\n      eos_static_err(\"failed to remove vid entry for key=%s\", skey.c_str());\n    }\n  } else if (skey.beginswith(\"policy:\") || (skey.beginswith(\"global:\"))) {\n    // For policy or global tags don't do anything\n  }\n}\n\n//------------------------------------------------------------------------------\n// Delete configuration values matching the pattern\n//------------------------------------------------------------------------------\nvoid\nIConfigEngine::DeleteConfigValueByMatch(const char* prefix, const char* match)\n{\n  XrdOucString smatch = prefix;\n  smatch += \":\";\n  smatch += match;\n  std::lock_guard lock(mMutex);\n  auto it = sConfigDefinitions.begin();\n\n  while (it != sConfigDefinitions.end()) {\n    if (strncmp(it->first.c_str(), smatch.c_str(), smatch.length()) == 0) {\n      it = sConfigDefinitions.erase(it);\n    } else {\n      it++;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Dump method for selective configuration printing\n//------------------------------------------------------------------------------\nbool\nIConfigEngine::DumpConfig(XrdOucString& out, const std::string& filename)\n{\n  if (filename.empty()) {\n    std::lock_guard lock(mMutex);\n\n    for (auto& sConfigDefinition : sConfigDefinitions) {\n      eos_static_debug(\"%s => %s\", sConfigDefinition.first.c_str(),\n                       sConfigDefinition.second.c_str());\n      out += (sConfigDefinition.first + \" => \" + sConfigDefinition.second +\n              \"\\n\").c_str();\n    }\n\n    while (out.replace(\"&\", \" \")) {}\n  } else {\n    std::ostringstream ss;\n    FilterConfig(ss, filename.c_str());\n    out = ss.str().c_str();\n  }\n\n  eos::common::StringConversion::SortLines(out);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Get a configuration value\n//------------------------------------------------------------------------------\nbool\nIConfigEngine::Get(const std::string& prefix, const std::string& key,\n                   std::string& out)\n{\n  std::lock_guard lock(mMutex);\n  std::string config_key = FormFullKey(prefix.c_str(), key.c_str());\n  auto it = sConfigDefinitions.find(config_key);\n\n  if (it == sConfigDefinitions.end()) {\n    return false;\n  }\n\n  out = it->second;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Reset the configuration\n//------------------------------------------------------------------------------\nvoid\nIConfigEngine::ResetConfig(bool apply_stall_redirect)\n{\n  mConfigFile.clear();\n  (void) Quota::CleanUp();\n  {\n    eos::common::RWMutexWriteLock wr_lock(eos::common::Mapping::gMapMutex);\n    eos::common::Mapping::gUserRoleVector.clear();\n    eos::common::Mapping::gGroupRoleVector.clear();\n    eos::common::Mapping::gVirtualUidMap.clear();\n    eos::common::Mapping::gVirtualGidMap.clear();\n    eos::common::Mapping::gAllowedTidentMatches.clear();\n  }\n  Access::Reset(!apply_stall_redirect);\n  gOFS->ResetPathMap();\n  gOFS->mRouting->Clear();\n  FsView::gFsView.Reset();\n  {\n    std::lock_guard lock(mMutex);\n    sConfigDefinitions.clear();\n  }\n  // Load all the quota nodes from the namespace\n  Quota::LoadNodes();\n}\n\n//------------------------------------------------------------------------------\n// Check if configuration key is deprecated\n//------------------------------------------------------------------------------\nbool\nIConfigEngine::IsDeprecated(const std::string& config_key) const\n{\n  static std::set<std::string> deprecated_space {\n    \"#drainer.central\", \"#new_balancer\", \"#transfer.schedule\",\n    \"#fsck_refresh_interval\", \"#drainer.node.nfs\", \"#drainer.node.ntx\",\n    \"#conveter.ntx\", \"#converter\"};\n  static std::set<std::string> deprecated_global {\n    \"#converter-max-threads\"\n  };\n\n  if ((config_key.find(\"global:\") == 0)) {\n    if (config_key.find(\"/space/\") != std::string::npos) {\n      for (const auto& key : deprecated_space) {\n        if (config_key.find(key) != std::string::npos) {\n          return true;\n        }\n      }\n    } else {\n      for (const auto& key : deprecated_global) {\n        if (config_key.find(key) != std::string::npos) {\n          return true;\n        }\n      }\n    }\n  }\n\n  if (config_key.find(\"comment-\") == 0) {\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Filter out deprecated entries from the map\n//------------------------------------------------------------------------------\nvoid\nIConfigEngine::FilterDeprecated(std::map<std::string, std::string>& map)\n{\n  using namespace eos::common;\n  std::set<std::string> deprecated;\n\n  for (auto it = map.begin(); it != map.end(); it++) {\n    if (IsDeprecated(it->first)) {\n      deprecated.insert(it->first);\n      continue;\n    }\n\n    // Filter out deprecated file system attributes\n    if (it->first.find(\"fs:/eos/\") == 0) {\n      std::map<std::string, std::string> fs_map;\n      std::list<std::string> fs_attrs =\n        StringTokenizer::split<std::list<std::string>> (it->second, ' ');\n\n      for (const auto& elem : fs_attrs) {\n        std::string key, val;\n\n        if (StringConversion::SplitKeyValue(elem, key, val, \"=\")) {\n          fs_map[key] = val;\n        } else {\n          continue;\n        }\n      }\n\n      if (fs_map.empty()) {\n        continue;\n      }\n\n      std::string data = FileSystem::SerializeWithFilter(fs_map, {\"stat.\", \"local.\"});\n      map[it->first] = data;\n    }\n  }\n\n  for (auto it = deprecated.begin(); it != deprecated.end(); it++) {\n    map.erase(*it);\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/config/IConfigEngine.hh",
    "content": "//------------------------------------------------------------------------------\n// File: IConfigEngine.hh\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdOuc/XrdOucHash.hh>\n#include <sstream>\n#include <mutex>\n\n//------------------------------------------------------------------------------\n//! @brief Interface Class responsible to handle configuration (load, save,\n//! publish)\n//!\n//! The XrdMgmOfs class runs an asynchronous thread which applies configuration\n//! changes from a remote master MGM on the configuration object.\n//------------------------------------------------------------------------------\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Config engine changelog interface\n//------------------------------------------------------------------------------\nclass ICfgEngineChangelog : public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ICfgEngineChangelog()\n  {\n    mMutex.SetBlocking(true);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~ICfgEngineChangelog() = default;\n\n  //----------------------------------------------------------------------------\n  //! Add entry to the changelog\n  //!\n  //! @param key      entry action\n  //! @param value    entry key\n  //! @param comment  entry value\n  //----------------------------------------------------------------------------\n  virtual void AddEntry(const std::string& action, const std::string& key,\n                        const std::string& value, const std::string& comment = \"\") = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get tail of the changelog\n  //!\n  //! @param nlines number of lines to return\n  //! @param tail string to hold the response\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool Tail(unsigned int nlines, std::string& tail) = 0;\n\nprotected:\n  mutable eos::common::RWMutex mMutex; ///< Mutex protecting the config changes\n};\n\n\n//------------------------------------------------------------------------------\n//! @brief Abstract class providing reset/load/store functionality\n//------------------------------------------------------------------------------\nclass IConfigEngine : public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! XrdOucHash callback function to apply a configuration value\n  //!\n  //! @param key configuration key\n  //! @param val configuration value\n  //! @param arg match pattern\n  //!\n  //! @return < 0 - the hash table item is deleted\n  //!         = 0 - the next hash table item is processed\n  //!         > 0 - processing stops and the hash table item is returned\n  //----------------------------------------------------------------------------\n  static int ApplyEachConfig(const char* key, const char* val,\n                             XrdOucString* err);\n\n  //----------------------------------------------------------------------------\n  //! Construct key given prefix and input\n  //----------------------------------------------------------------------------\n  static std::string FormFullKey(const char* prefix, const char* key)\n  {\n    if (prefix) {\n      return SSTR(prefix << \":\" << key);\n    }\n\n    return SSTR(key);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  IConfigEngine();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~IConfigEngine() = default;\n\n  //----------------------------------------------------------------------------\n  //! Get tail of the changelog\n  //!\n  //! @param nlines number of lines to return\n  //! @param tail string to hold the response\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool Tail(unsigned int nlines, std::string& tail)\n  {\n    if (!mChangelog) {\n      return false;\n    }\n\n    return mChangelog->Tail(nlines, tail);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Load a given configuration file\n  //!\n  //! @param filename name of the file holding the configuration to be loaded\n  //! @param err string holding any errors\n  //! @param apply_stall_redirect if true then skip applying stall and redirect\n  //!        rules from the configuration\n  //!\n  //! @return true if loaded successfully, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool LoadConfig(const std::string& filename, XrdOucString& err,\n                          bool apply_stall_redirect = false) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Save configuration to specified destination\n  //!\n  //! @param filename name of the file where the current configuration will be saved\n  //! @param overwrite force overwrite of <filename> if the file exists already\n  //! @param comment comments\n  //! @param err string holding any errors\n  //!\n  //! @return true if saved successfully, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool SaveConfig(std::string filename, bool overwrite,\n                          const std::string& comment, XrdOucString& err) = 0;\n\n  //----------------------------------------------------------------------------\n  //! List all configurations\n  //!\n  //! @param configlist string holding the list of all configurations\n  //! @param showbackups if true then show also the backups\n  //!\n  //! @return true if listing successful, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool ListConfigs(XrdOucString& configlist,\n                           bool showbackups = false) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Do an autosave\n  //----------------------------------------------------------------------------\n  virtual bool AutoSave() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get a configuration value\n  //----------------------------------------------------------------------------\n  bool Get(const std::string& prefix, const std::string& key,\n           std::string& out);\n\n  //----------------------------------------------------------------------------\n  //! Set a configuration value\n  //!\n  //! @param prefix identifies the type of configuration parameter\n  //! @param key key of the configuration to set\n  //! @param val value of the configuration\n  //! @param from_local mark if change comes from local MGM or remote one\n  //! @param save_config mark if configuration should also be saved or not\n  //----------------------------------------------------------------------------\n  virtual void SetConfigValue(const char* prefix, const char* key,\n                              const char* val, bool from_local = true,\n                              bool save_config = true) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Delete a configuration value\n  //!\n  //! @param prefix identifies the type of configuration parameter\n  //! @param key key of the configuration to delete\n  //! @param tochangelog if true add entry also to the changelog\n  //----------------------------------------------------------------------------\n  virtual void DeleteConfigValue(const char* prefix, const char* key,\n                                 bool tochangelog = true) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Delete a configuration key from the responsible object\n  //!\n  //! @param key configuration key to be deleted\n  //----------------------------------------------------------------------------\n  void ApplyKeyDeletion(const char* key);\n\n  //----------------------------------------------------------------------------\n  //! Delete configuration matching the pattern\n  //!\n  //! @param prefix identifies the type of configuration parameter\n  //! @param match matching pattern\n  //----------------------------------------------------------------------------\n  void DeleteConfigValueByMatch(const char* prefix, const char* match);\n\n  //----------------------------------------------------------------------------\n  //! Apply a configuration definition - the configuration engine informs the\n  //! corresponding objects about the new values\n  //!\n  //! @param err object collecting any possible errors\n  //! @param apply_stall_redirect if true then skip applying stall and redirect\n  //!        rules from the configuration\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ApplyConfig(XrdOucString& err, bool apply_stall_redirect = false);\n\n  //----------------------------------------------------------------------------\n  //! Dump method for selective configuration printing\n  //!\n  //! @param out output string\n  //! @param filename string holding the name of the config file to dump\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool DumpConfig(XrdOucString& out, const std::string& filename);\n\n  //----------------------------------------------------------------------------\n  //! Reset the current configuration\n  //!\n  //! @param apply_stall_redirect\n  //----------------------------------------------------------------------------\n  void ResetConfig(bool apply_stall_redirect = true);\n\n  //----------------------------------------------------------------------------\n  //! Set the autosave mode\n  //----------------------------------------------------------------------------\n  inline void SetAutoSave(bool val)\n  {\n    mAutosave = val;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Publish the given configuration change\n  //----------------------------------------------------------------------------\n  void PublishConfigChange(const std::string& key, const std::string& value);\n\n  //----------------------------------------------------------------------------\n  //! Publish the deletion of the given configuration key\n  //----------------------------------------------------------------------------\n  void PublishConfigDeletion(const std::string& key);\n\nprotected:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n\n  std::unique_ptr<ICfgEngineChangelog> mChangelog; ///< Changelog object\n  //! Protect the static configuration definitions hash\n  std::recursive_mutex mMutex;\n  bool mAutosave; ///< Create autosave file for each change\n  std::string mConfigFile = \"default\"; ///< Currently loaded configuration\n  //! Configuration definitions currently in memory\n  std::map<std::string, std::string> sConfigDefinitions;\n\n  //----------------------------------------------------------------------------\n  //! Check if configuration key is deprecated\n  //----------------------------------------------------------------------------\n  bool IsDeprecated(const std::string& config_key) const;\n\n  //----------------------------------------------------------------------------\n  //! Filter out entries from the map\n  //----------------------------------------------------------------------------\n  void FilterDeprecated(std::map<std::string, std::string>& map);\n\n  //----------------------------------------------------------------------------\n  //! Filter configuration\n  //!\n  //! @param out output representation of the configuration after filtering\n  //! @param cfg_name configuration name\n  //!\n  //! @return 0 if successful, otherwise errno\n  //----------------------------------------------------------------------------\n  virtual int FilterConfig(std::ostream& out, const std::string& cfg_name) = 0;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/config/QuarkConfigHandler.cc",
    "content": "// ----------------------------------------------------------------------\n// File: QuarkConfigHandler.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/config/QuarkConfigHandler.hh\"\n#include \"common/Assert.hh\"\n#include \"common/StringUtils.hh\"\n\n#include <qclient/QClient.hh>\n#include <qclient/ResponseParsing.hh>\n#include <qclient/MultiBuilder.hh>\n#include \"qclient/structures/QScanner.hh\"\n#include \"qclient/structures/QDeque.hh\"\n\n#include <folly/Executor.h>\n#include <folly/executors/IOThreadPoolExecutor.h>\n#include <functional>\n\nusing std::placeholders::_1;\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nQuarkConfigHandler::QuarkConfigHandler(const QdbContactDetails& cd)\n  : mContactDetails(cd)\n{\n  mQcl = std::unique_ptr<qclient::QClient>(\n           new qclient::QClient(mContactDetails.members,\n                                mContactDetails.constructOptions()));\n  mExecutor.reset(new folly::IOThreadPoolExecutor(2));\n}\n\n//------------------------------------------------------------------------------\n// Ensure connection is established\n//------------------------------------------------------------------------------\ncommon::Status QuarkConfigHandler::checkConnection(std::chrono::milliseconds\n    timeout)\n{\n  qclient::Status st = mQcl->checkConnection(timeout);\n  return common::Status(st.getErrc(), st.getMsg());\n}\n\n//------------------------------------------------------------------------------\n// Check if a given configuration exists\n//------------------------------------------------------------------------------\ncommon::Status QuarkConfigHandler::checkExistence(const std::string& name,\n    bool& existence)\n{\n  qclient::IntegerParser existsResp(mQcl->exec(\"HLEN\",\n                                    SSTR(\"eos-config:\" << name)).get());\n\n  if (!existsResp.ok()) {\n    return common::Status(EINVAL,\n                          SSTR(\"Received unexpected response in HLEN existence check: \" <<\n                               existsResp.err()));\n  }\n\n  existence = (existsResp.value() > 0);\n  return common::Status();\n}\n\n//----------------------------------------------------------------------------\n// Obtain list of available configurations, and backups\n//----------------------------------------------------------------------------\ncommon::Status QuarkConfigHandler::listConfigurations(std::vector<std::string>&\n    configs, std::vector<std::string>& backups)\n{\n  qclient::QScanner confScanner(*mQcl, \"eos-config:*\");\n\n  for (; confScanner.valid(); confScanner.next()) {\n    configs.emplace_back(confScanner.getValue().substr(11));\n  }\n\n  qclient::QScanner confScannerBackups(*mQcl, \"eos-config-backup:*\");\n\n  for (; confScannerBackups.valid(); confScannerBackups.next()) {\n    backups.emplace_back(confScannerBackups.getValue().substr(18));\n  }\n\n  return common::Status();\n}\n\n//------------------------------------------------------------------------------\n// Fetch a given configuration\n//------------------------------------------------------------------------------\ncommon::Status\nQuarkConfigHandler::fetchConfiguration(const std::string& name,\n                                       std::map<std::string, std::string>& out)\n{\n  qclient::redisReplyPtr reply = mQcl->exec(\"HGETALL\", FormHashKey(name)).get();\n  qclient::HgetallParser parser(reply);\n\n  if (!parser.ok() || parser.value().empty()) {\n    eos_static_warning(\"msg=\\\"no such configuration in QDB\\\" name=\\\"%s\\\"\",\n                       name.c_str());\n    // Check also if this is a backup configuration\n    reply = mQcl->exec(\"HGETALL\", FormBackupHashKey(name)).get();\n    qclient::HgetallParser bkp_parser(reply);\n\n    if (!bkp_parser.ok()) {\n      eos_static_warning(\"msg=\\\"no such backup configuration in QDB\\\" \"\n                         \"name=\\\"%s\\\"\", name.c_str());\n      return common::Status(EINVAL, bkp_parser.err());\n    }\n\n    out = bkp_parser.value();\n  } else {\n    out = parser.value();\n  }\n\n  return common::Status();\n}\n\n//------------------------------------------------------------------------------\n// Process write configuration reply\n//------------------------------------------------------------------------------\nstatic common::Status processWriteConfigurationReply(size_t configSize,\n    uint32_t extraReqs, qclient::redisReplyPtr reply)\n{\n  if (!reply) {\n    return common::Status(EINVAL, \"null reply, QDB backend not available?\");\n  }\n\n  if (reply->elements != configSize + extraReqs) {\n    return common::Status(EINVAL,\n                          SSTR(\"unexpected number of elements in response: \" <<\n                               qclient::describeRedisReply(reply)));\n  }\n\n  for (size_t i = extraReqs; i < reply->elements; i++) {\n    qclient::IntegerParser intParse(reply->element[i]);\n\n    if (!intParse.ok() || intParse.value() != 1) {\n      return common::Status(EINVAL,\n                            SSTR(\"unexpected response in position \" << i << \": \" <<\n                                 qclient::describeRedisReply(reply->element[i])));\n    }\n  }\n\n  return common::Status();\n}\n\n//------------------------------------------------------------------------------\n// Write the given configuration\n//------------------------------------------------------------------------------\nfolly::Future<common::Status> QuarkConfigHandler::writeConfiguration(\n  const std::string& name, const std::map<std::string, std::string>& config,\n  bool overwrite, const std::string& backup)\n{\n  std::string configKey = SSTR(\"eos-config:\" << name);\n  qclient::IntegerParser hlenResp(mQcl->exec(\"HLEN\", configKey).get());\n\n  if (!hlenResp.ok()) {\n    return common::Status(EINVAL,\n                          SSTR(\"received unexpected response in HLEN check: \" <<\n                               hlenResp.err()));\n  }\n\n  if (!overwrite && hlenResp.value() != 0) {\n    return common::Status(EINVAL,\n                          \"There's MGM configuration stored in QDB already -- will not delete.\");\n  }\n\n  //----------------------------------------------------------------------------\n  // Prepare write batch\n  //----------------------------------------------------------------------------\n  qclient::MultiBuilder multiBuilder;\n  uint32_t extraReqs = 1;\n\n  if (!backup.empty()) {\n    std::string backupConfigKey = SSTR(\"eos-config-backup:\" << name << \"-\" <<\n                                       backup);\n    multiBuilder.emplace_back(\"DEL\", backupConfigKey);\n    multiBuilder.emplace_back(\"HCLONE\", configKey, backupConfigKey);\n    extraReqs += 2;\n  }\n\n  multiBuilder.emplace_back(\"DEL\", configKey);\n\n  for (auto it = config.begin(); it != config.end(); it++) {\n    multiBuilder.emplace_back(\"HSET\", configKey, it->first, it->second);\n  }\n\n  return mQcl->follyExecute(multiBuilder.getDeque())\n         .via(mExecutor.get())\n         .thenValue(std::bind(processWriteConfigurationReply, config.size(), extraReqs,\n                              _1));\n}\n\n//------------------------------------------------------------------------------\n// Validate appendChangelog response\n//------------------------------------------------------------------------------\ncommon::Status checkAppendChangelogResponse(qclient::redisReplyPtr reply)\n{\n  if (!reply) {\n    return common::Status(EINVAL, \"no response from QDB backend\");\n  }\n\n  if (reply->type != REDIS_REPLY_ARRAY || reply->elements != 2u) {\n    return common::Status(EINVAL,\n                          SSTR(\"unexpected reply from QDB: \" << qclient::describeRedisReply(reply)));\n  }\n\n  redisReply* reply0 = reply->element[0];\n  redisReply* reply1 = reply->element[1];\n\n  if (reply0->type != REDIS_REPLY_INTEGER || reply0->integer != 1) {\n    return common::Status(EINVAL,\n                          SSTR(\"unexpected reply from QDB: \" << qclient::describeRedisReply(reply)));\n  }\n\n  if (reply1->type != REDIS_REPLY_INTEGER) {\n    return common::Status(EINVAL,\n                          SSTR(\"unexpected reply from QDB: \" << qclient::describeRedisReply(reply)));\n  }\n\n  return common::Status();\n}\n\n//------------------------------------------------------------------------------\n// Append an entry to the changelog\n//------------------------------------------------------------------------------\nfolly::Future<common::Status> QuarkConfigHandler::appendChangelog(\n  const eos::mgm::ConfigChangelogEntry& entry)\n{\n  std::string serialized;\n\n  if (!entry.SerializeToString(&serialized)) {\n    return common::Status(EINVAL, \"protobuf seriaization to string failed\");\n  }\n\n  qclient::MultiBuilder multiBuilder;\n  multiBuilder.emplace_back(\"deque-push-back\", \"eos-config-changelog:default\",\n                            serialized);\n  multiBuilder.emplace_back(\"deque-trim-front\", \"eos-config-changelog:default\",\n                            \"500000\");\n  return mQcl->follyExecute(multiBuilder.getDeque())\n         .via(mExecutor.get())\n         .thenValue(std::bind(checkAppendChangelogResponse, _1));\n}\n\n//------------------------------------------------------------------------------\n// Show configuration changelog\n//------------------------------------------------------------------------------\ncommon::Status QuarkConfigHandler::tailChangelog(int nlines,\n    std::vector<std::string>& entries)\n{\n  entries.clear();\n  qclient::redisReplyPtr reply = mQcl->exec(\"deque-scan-back\",\n                                 \"eos-config-changelog\", \"0\",\n                                 \"COUNT\", SSTR(nlines)).get();\n\n  if (!reply || reply->type != REDIS_REPLY_ARRAY) {\n    return common::Status(EINVAL,\n                          SSTR(\"received unexpected reply type: \" << qclient::describeRedisReply(reply)));\n  }\n\n  if (reply->elements != 2) {\n    return common::Status(EINVAL,\n                          SSTR(\"received unexpected number of elements in reply: \" <<\n                               qclient::describeRedisReply(reply)));\n  }\n\n  redisReply* array = reply->element[1];\n\n  for (size_t i = 0; i < array->elements; i++) {\n    if (array->element[i]->type != REDIS_REPLY_STRING) {\n      return common::Status(EINVAL,\n                            SSTR(\"received unexpected reply type for element #\" << i << \": \" <<\n                                 qclient::describeRedisReply(array->element[i])));\n    }\n\n    entries.emplace_back(array->element[i]->str, array->element[i]->len);\n  }\n\n  return common::Status();\n}\n\n//------------------------------------------------------------------------------\n// Trim backups to the nth most recent ones. If no more than N backups exist\n// anyway, do nothing.\n//\n// We will delete a maximum of 200 backups at a time -- you may have to call\n// this function multiple times to trim everything.\n//------------------------------------------------------------------------------\ncommon::Status QuarkConfigHandler::trimBackups(const std::string& name,\n    size_t limit, size_t& deleted)\n{\n  std::vector<std::string> configs, backups;\n  deleted = 0;\n  common::Status st = listConfigurations(configs, backups);\n\n  if (!st) {\n    return st;\n  }\n\n  std::string targetPrefix = SSTR(name << \"-\");\n  std::vector<std::string> targetsToClean;\n\n  for (size_t i = 0; i < backups.size(); i++) {\n    if (common::startsWith(backups[i], targetPrefix)) {\n      targetsToClean.emplace_back(backups[i]);\n    }\n  }\n\n  int backupsToDelete = (int) targetsToClean.size() - (int) limit;\n  backupsToDelete = std::min(backupsToDelete, 200);\n\n  if (backupsToDelete <= 0) {\n    return common::Status();\n  }\n\n  std::vector<std::string> requestPayload = {\"DEL\"};\n\n  for (int i = 0; i < backupsToDelete; i++) {\n    requestPayload.emplace_back(SSTR(\"eos-config-backup:\" << targetsToClean[i]));\n  }\n\n  qclient::redisReplyPtr reply = mQcl->execute(requestPayload).get();\n  qclient::IntegerParser intParse(reply);\n\n  if (!intParse.ok()) {\n    return common::Status(EINVAL, intParse.err());\n  }\n\n  deleted = intParse.value();\n  return common::Status();\n}\n\n//------------------------------------------------------------------------------\n// Form configuration target key\n//------------------------------------------------------------------------------\nstd::string QuarkConfigHandler::FormHashKey(const std::string& name)\n{\n  return SSTR(\"eos-config:\" << name);\n}\n\n//------------------------------------------------------------------------------\n// Form configuration backup target key\n//------------------------------------------------------------------------------\nstd::string QuarkConfigHandler::FormBackupHashKey(const std::string& name)\n{\n  return SSTR(\"eos-config-backup:\" << name);\n}\n\n//------------------------------------------------------------------------------\n// Form configuration backup key\n//------------------------------------------------------------------------------\nstd::string\nQuarkConfigHandler::FormBackupHashKey(const std::string& name,\n                                      time_t timestamp)\n{\n  char buff[128];\n  strftime(buff, 127, \"%Y%m%d%H%M%S\", localtime(&timestamp));\n  return SSTR(\"eos-config-backup\" << \":\" << name << \"-\" << buff);\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/config/QuarkConfigHandler.hh",
    "content": "//------------------------------------------------------------------------------\n// @file QuarkConfigHandler.hh\n// @author Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/Status.hh\"\n#include \"common/Logging.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"proto/ChangelogEntry.pb.h\"\n#include <map>\n#include <folly/futures/Future.h>\n\nnamespace folly\n{\nclass Executor;\n}\n\nnamespace qclient\n{\nclass QClient;\n}\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class to perform reads and writes on the MGM configuration stored in QDB\n//------------------------------------------------------------------------------\nclass QuarkConfigHandler: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  QuarkConfigHandler(const QdbContactDetails& cd);\n\n  //----------------------------------------------------------------------------\n  //! Ensure connection is established\n  //----------------------------------------------------------------------------\n  common::Status checkConnection(std::chrono::milliseconds timeout =\n                                   std::chrono::seconds(5));\n\n  //----------------------------------------------------------------------------\n  //! Fetch a given configuration\n  //----------------------------------------------------------------------------\n  common::Status fetchConfiguration(const std::string& name,\n                                    std::map<std::string, std::string>& out);\n\n  //----------------------------------------------------------------------------\n  //! Write the given configuration\n  //----------------------------------------------------------------------------\n  folly::Future<common::Status>\n  writeConfiguration(const std::string& name,\n                     const std::map<std::string, std::string>& config,\n                     bool overwrite, const std::string& backup = \"\");\n\n  //----------------------------------------------------------------------------\n  //! Check if configuration key exists already\n  //----------------------------------------------------------------------------\n  common::Status checkExistence(const std::string& name, bool& existence);\n\n  //----------------------------------------------------------------------------\n  //! Obtain list of available configurations, and backups\n  //----------------------------------------------------------------------------\n  common::Status listConfigurations(std::vector<std::string>& configs,\n                                    std::vector<std::string>& backups);\n\n  //----------------------------------------------------------------------------\n  //! Append an entry to the changelog\n  //----------------------------------------------------------------------------\n  folly::Future<common::Status>\n  appendChangelog(const eos::mgm::ConfigChangelogEntry& entry);\n\n  //----------------------------------------------------------------------------\n  //! Show configuration changelog\n  //----------------------------------------------------------------------------\n  common::Status tailChangelog(int nlines, std::vector<std::string>& changelog);\n\n  //----------------------------------------------------------------------------\n  //! Trim backups to the nth most recent ones. If no more than N backups exist\n  //! anyway, do nothing.\n  //!\n  //! We will delete a maximum of 200 backups at a time -- you may have to call\n  //! this function multiple times to trim everything.\n  //----------------------------------------------------------------------------\n  common::Status\n  trimBackups(const std::string& name, size_t limit, size_t& deleted);\n\n  //----------------------------------------------------------------------------\n  //! Form configuration target key\n  //----------------------------------------------------------------------------\n  static std::string FormHashKey(const std::string& name);\n\n  //----------------------------------------------------------------------------\n  //! Form configuration backup target key\n  //----------------------------------------------------------------------------\n  static std::string FormBackupHashKey(const std::string& name);\n\n  //----------------------------------------------------------------------------\n  //! Form configuration backup target key\n  //----------------------------------------------------------------------------\n  static std::string FormBackupHashKey(const std::string& name, time_t timestamp);\n\nprivate:\n  QdbContactDetails mContactDetails;\n  std::unique_ptr<qclient::QClient> mQcl;\n  std::unique_ptr<folly::Executor> mExecutor;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/config/QuarkDBConfigEngine.cc",
    "content": "// ----------------------------------------------------------------------\n// File: QuarkDBConfigEngine.cc\n// Author: Andrea Manzi - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/config/QuarkDBConfigEngine.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"common/Timing.hh\"\n#include \"common/StringUtils.hh\"\n#include <qclient/ResponseParsing.hh>\n#include <qclient/MultiBuilder.hh>\n#include \"qclient/structures/QScanner.hh\"\n#include <folly/executors/IOThreadPoolExecutor.h>\n#include <ctime>\n#include <functional>\nusing std::placeholders::_1;\n\nnamespace\n{\nconst std::string sCleanupEnv {\"EOS_MGM_CONFIG_CLEANUP\"};\n}\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//                   **** QuarkDBCfgEngineChangelog class ****\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nQuarkDBCfgEngineChangelog::QuarkDBCfgEngineChangelog(qclient::QClient* client)\n  : mQcl(*client) {}\n\n//------------------------------------------------------------------------------\n// Add entry to the changelog\n//------------------------------------------------------------------------------\nvoid QuarkDBCfgEngineChangelog::AddEntry(const std::string& action,\n    const std::string& key, const std::string& value, const std::string& comment)\n{\n  // Add entry to the set\n  std::ostringstream oss;\n  oss << std::time(NULL) << \": \" << action;\n\n  if (key != \"\") {\n    oss << \" \" << key.c_str() << \" => \" << value.c_str();\n  }\n\n  if (!comment.empty()) {\n    oss << \" [\" << comment << \"]\";\n  }\n\n  mQcl.exec(\"deque-push-back\", kChangelogKey, oss.str());\n  mQcl.exec(\"deque-trim-front\", kChangelogKey, \"500000\");\n}\n\n//------------------------------------------------------------------------------\n// Get tail of the changelog\n//------------------------------------------------------------------------------\nbool QuarkDBCfgEngineChangelog::Tail(unsigned int nlines, std::string& tail)\n{\n  qclient::redisReplyPtr reply = mQcl.exec(\"deque-scan-back\", kChangelogKey, \"0\",\n                                 \"COUNT\", SSTR(nlines)).get();\n\n  if (reply->type != REDIS_REPLY_ARRAY) {\n    return false;\n  }\n\n  if (reply->elements != 2) {\n    return false;\n  }\n\n  redisReply* array = reply->element[1];\n  std::ostringstream oss;\n  std::string stime;\n\n  for (size_t i = 0; i < array->elements; i++) {\n    if (array->element[i]->type != REDIS_REPLY_STRING) {\n      return false;\n    }\n\n    std::string line(array->element[i]->str, array->element[i]->len);\n\n    try {\n      time_t t = std::stoull(line.c_str());\n      stime = std::ctime(&t);\n      stime.erase(stime.length() - 1);\n    } catch (std::exception& e) {\n      stime = \"unknown_timestamp\";\n    }\n\n    for (size_t i = 0; i < line.size(); i++) {\n      if (line[i] == ':') {\n        line = line.substr(i + 2);\n        break;\n      }\n    }\n\n    oss << stime << \": \" << line << std::endl;\n  }\n\n  tail = oss.str();\n  return true;\n}\n\n//------------------------------------------------------------------------------\n//                     *** QuarkDBConfigEngine class ***\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nQuarkDBConfigEngine::QuarkDBConfigEngine(const QdbContactDetails&\n    contactDetails)\n{\n  mQdbContactDetails = contactDetails;\n  mQcl = std::make_unique<qclient::QClient>(mQdbContactDetails.members,\n         mQdbContactDetails.constructOptions());\n  mConfigHandler = std::make_unique<QuarkConfigHandler>(mQdbContactDetails);\n  mChangelog.reset(new QuarkDBCfgEngineChangelog(mQcl.get()));\n  mExecutor.reset(new folly::IOThreadPoolExecutor(2));\n  mCleanupThread.reset(&QuarkDBConfigEngine::CleanupThread, this);\n}\n\n//------------------------------------------------------------------------------\n// Load a given configuration file\n//------------------------------------------------------------------------------\nbool\nQuarkDBConfigEngine::LoadConfig(const std::string& filename, XrdOucString& err,\n                                bool apply_stall_redirect)\n{\n  eos_notice(\"msg=\\\"loading configuration\\\" name=%s \", filename.c_str());\n\n  if (filename.empty()) {\n    err = \"error: you have to specify a configuration name\";\n    return false;\n  }\n\n  ResetConfig(apply_stall_redirect);\n  common::Status st = PullFromQuarkDB(filename);\n\n  if (!st) {\n    err = st.toString().c_str();\n    return false;\n  }\n\n  // Do cleanup of old nodes not used anymore\n  if (RemoveUnusedNodes()) {\n    XrdOucString err;\n\n    if (!SaveConfig(filename, true, \"\", err)) {\n      eos_static_err(\"msg=\\\"failed to save config after node cleanup\\\" \"\n                     \"err_msg=\\\"%s\\\"\", err.c_str());\n      return false;\n    }\n  }\n\n  // Remove deprecated keys - not used for the moment\n  if (RemoveDeprecatedKeys()) {\n    XrdOucString err;\n\n    if (!SaveConfig(filename, true, \"\", err)) {\n      eos_static_err(\"msg=\\\"failed to save config after deprecated keys \"\n                     \" cleanup\\\" err_msg=\\\"%s\\\"\", err.c_str());\n      return false;\n    }\n  }\n\n  if (!ApplyConfig(err, apply_stall_redirect))   {\n    mChangelog->AddEntry(\"loaded config\", filename,\n                         SSTR(\"with failure : \" << err));\n    return false;\n  } else {\n    mConfigFile = filename;\n    return true;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Remove deprecated configuration keys\n//------------------------------------------------------------------------------\nbool\nQuarkDBConfigEngine::RemoveDeprecatedKeys()\n{\n  return false;\n}\n\n\n//------------------------------------------------------------------------------\n// Remove old unused nodes that are off and have no file systems registered\n//------------------------------------------------------------------------------\nbool\nQuarkDBConfigEngine::RemoveUnusedNodes()\n{\n  const std::string global_prefix {\"global:/config/\"};\n  const std::string node_token {\"/node/\"};\n  const std::string fs_prefix {\"fs:/eos/\"};\n  const std::string status_suffix {\"#status\"};\n  // Set of node hostnames to be removed\n  std::set<std::string> to_remove;\n  auto it_lower = sConfigDefinitions.lower_bound(global_prefix);\n\n  if ((it_lower == sConfigDefinitions.end()) ||\n      (it_lower->first.find(global_prefix) == std::string::npos)) {\n    return false;\n  }\n\n  for (auto it = it_lower; it != sConfigDefinitions.end(); ++it) {\n    // If outside the global config then stop\n    if (it->first.find(global_prefix) == std::string::npos) {\n      break;\n    }\n\n    const std::string key = it->first;\n\n    // The node status needs to be off\n    if ((key.find(status_suffix) != std::string::npos) &&\n        (key.find(node_token) != std::string::npos) &&\n        (it->second == \"off\")) {\n      // Remove the \"global:\" prefix and '#status' suffix\n      int pos = key.find('#');\n      std::string queue = key.substr(7, pos - 7);\n      eos::common::SharedHashLocator node_loc;\n\n      if (!eos::common::SharedHashLocator::fromConfigQueue(queue, node_loc)) {\n        eos_static_err(\"msg=\\\"failed to parse locator\\\" queue=\\\"%s\\\"\",\n                       queue.c_str());\n        continue;\n      }\n\n      to_remove.insert(node_loc.GetName());\n    }\n  }\n\n  // Go through all the registered file systems\n  it_lower = sConfigDefinitions.lower_bound(fs_prefix);\n\n  if ((it_lower != sConfigDefinitions.end()) &&\n      (it_lower->first.find(fs_prefix) != std::string::npos)) {\n    for (auto it = it_lower; it != sConfigDefinitions.end(); ++it) {\n      const std::string fs_key = it->first;\n\n      if (fs_key.find(fs_prefix) == std::string::npos) {\n        break;\n      }\n\n      for (const auto& node : to_remove) {\n        // If there is a file system registerd for the current\n        // node then we don't remove this entry\n        if (fs_key.find(node) != std::string::npos) {\n          to_remove.erase(node);\n          break;\n        }\n      }\n    }\n  }\n\n  eos_static_info(\"msg=\\\"%i nodes to be removed\\\"\", to_remove.size());\n\n  // These are the entries to be removed\n  for (const auto& node : to_remove) {\n    eos_static_info(\"msg=\\\"unused node to be removed\\\" node=\\\"%s\\\"\", node.c_str());\n  }\n\n  if (!to_remove.empty()) {\n    const char* ptr = getenv(\"EOS_MGM_CONFIG_CLEANUP\");\n\n    if (ptr && (strncmp(ptr, \"1\", 1) == 0)) {\n      eos_static_info(\"%s\", \"msg=\\\"perform config cleanup\\\"\");\n      // The remaining nodes need to be removed from the global configuration as\n      // they don't have any file system registered\n      it_lower = sConfigDefinitions.lower_bound(global_prefix);\n\n      for (auto it = it_lower; it != sConfigDefinitions.end(); /*no increment*/) {\n        if (it->first.find(global_prefix) == std::string::npos) {\n          break;\n        }\n\n        bool deleted = false;\n\n        for (const auto& node : to_remove) {\n          if (it->first.find(node) != std::string::npos) {\n            it = sConfigDefinitions.erase(it);\n            deleted = true;\n            break;\n          }\n        }\n\n        if (!deleted) {\n          ++it;\n        }\n      }\n    } else {\n      eos_static_info(\"%s\", \"msg=\\\"skip config cleanup\\\"\");\n      return false;\n    }\n  }\n\n  return (to_remove.size() ? true : false);\n}\n\n//------------------------------------------------------------------------------\n// Store the current configuration to a given file or QuarkDB\n//------------------------------------------------------------------------------\nbool\nQuarkDBConfigEngine::SaveConfig(std::string filename, bool overwrite,\n                                const std::string& comment, XrdOucString& err)\n{\n  using namespace std::chrono;\n  auto start = steady_clock::now();\n\n  if (filename.empty()) {\n    if (!mConfigFile.empty()) {\n      filename = mConfigFile;\n      overwrite = true;\n    } else {\n      err = \"error: you have to specify a configuration name\";\n      return false;\n    }\n  }\n\n  // Store a new hash\n  if (!overwrite) {\n    bool exists = true;\n    common::Status st = mConfigHandler->checkExistence(filename, exists);\n\n    if (!st.ok() || exists) {\n      errno = EEXIST;\n      err = \"error: a configuration with name \\\"\";\n      err += filename.c_str();\n      err += \"\\\" exists already!\";\n      return false;\n    }\n  }\n\n  StoreIntoQuarkDB(filename);\n  std::ostringstream changeLogValue;\n\n  if (overwrite) {\n    changeLogValue << \"(force)\";\n  }\n\n  changeLogValue << \" successfully\";\n  mChangelog->AddEntry(\"saved config\", filename, changeLogValue.str(), comment);\n  mConfigFile = filename;\n  auto end = steady_clock::now();\n  auto duration = end - start;\n  eos_notice(\"msg=\\\"saved config\\\" name=\\\"%s\\\" comment=\\\"%s\\\" force=%d duration=\\\"%llu ms\\\"\",\n             filename.c_str(), comment.c_str(), overwrite,\n             duration_cast<milliseconds>(duration).count());\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// List the existing configurations\n//------------------------------------------------------------------------------\nbool\nQuarkDBConfigEngine::ListConfigs(XrdOucString& configlist, bool showbackup)\n\n{\n  std::vector<std::string> configs, backups;\n  common::Status status = mConfigHandler->listConfigurations(configs, backups);\n\n  if (!status) {\n    configlist += \"error: \";\n    configlist += status.toString().c_str();\n    return false;\n  }\n\n  configlist = \"Existing Configurations on QuarkDB\\n\";\n  configlist += \"================================\\n\";\n\n  for (auto it = configs.begin(); it != configs.end(); it++) {\n    configlist += \"name: \";\n    configlist += it->c_str();\n\n    if (*it == mConfigFile) {\n      configlist += \" *\";\n    }\n\n    configlist += \"\\n\";\n  }\n\n  if (showbackup) {\n    configlist += \"=======================================\\n\";\n    configlist += \"Existing Backup Configurations on QuarkDB\\n\";\n    configlist += \"=======================================\\n\";\n\n    for (auto it = backups.begin(); it != backups.end(); it++) {\n      configlist += \"name: \";\n      configlist += it->c_str();\n      configlist += \"\\n\";\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Cleanup thread trimming the number of backups\n//------------------------------------------------------------------------------\nvoid QuarkDBConfigEngine::CleanupThread(ThreadAssistant& assistant)\n{\n  ThreadAssistant::setSelfThreadName(\"QDBConfigCleanup\");\n\n  while (!assistant.terminationRequested()) {\n    assistant.wait_for(std::chrono::minutes(30));\n\n    if (!assistant.terminationRequested()) {\n      size_t deleted;\n      common::Status st = mConfigHandler->trimBackups(\"default\", 1000, deleted);\n\n      if (!st) {\n        eos_static_crit(\"unable to clean configuration backups: %s\",\n                        st.toString().c_str());\n      } else {\n        eos_static_info(\"deleted %d old configuration backups\", deleted);\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Pull the configuration from QuarkDB\n//------------------------------------------------------------------------------\ncommon::Status\nQuarkDBConfigEngine::PullFromQuarkDB(const std::string& configName)\n{\n  std::lock_guard lock(mMutex);\n  common::Status st = mConfigHandler->fetchConfiguration(configName,\n                      sConfigDefinitions);\n\n  if (!st) {\n    return st;\n  }\n\n  sConfigDefinitions.erase(\"timestamp\");\n\n  for (const auto& elem : sConfigDefinitions) {\n    eos_static_notice(\"msg=\\\"setting config\\\" key=\\\"%s\\\" value=\\\"%s\\\"\",\n                      elem.first.c_str(), elem.second.c_str());\n  }\n\n  return common::Status();\n}\n\n//------------------------------------------------------------------------------\n// Filter the configuration and store in output string\n//------------------------------------------------------------------------------\nint\nQuarkDBConfigEngine::FilterConfig(std::ostream& out,\n                                  const std::string& cfg_name)\n{\n  std::map<std::string, std::string> config;\n  common::Status st = mConfigHandler->fetchConfiguration(cfg_name, config);\n\n  if (!st) {\n    out << st.toString();\n  } else {\n    for (const auto& elem : config) {\n      out << elem.first << \" => \" << elem.second << \"\\n\";\n    }\n  }\n\n  return st.getErrc();\n}\n\n//------------------------------------------------------------------------------\n// Do an autosave\n//------------------------------------------------------------------------------\nbool\nQuarkDBConfigEngine::AutoSave()\n{\n  if (gOFS->mMaster->IsMaster() && mAutosave && !mConfigFile.empty()) {\n    bool overwrite = true;\n    XrdOucString err = \"\";\n\n    if (!SaveConfig(mConfigFile, overwrite, \"\", err)) {\n      eos_static_err(\"%s\\n\", err.c_str());\n      return false;\n    }\n\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Set a configuration value\n//------------------------------------------------------------------------------\nvoid\nQuarkDBConfigEngine::SetConfigValue(const char* prefix, const char* key,\n                                    const char* val, bool from_local,\n                                    bool save_config)\n{\n  // If val is null or empty we don't save anything\n  if ((val == nullptr) || (strlen(val) == 0)) {\n    return;\n  }\n\n  eos_static_info(\"msg=\\\"store config\\\" key=\\\"%s\\\" val=\\\"%s\\\"\", key, val);\n  std::string config_key = FormFullKey(prefix, key);\n  {\n    std::lock_guard lock(mMutex);\n    sConfigDefinitions[config_key] = val;\n  }\n\n  // In case the change is not coming from a broacast we can can broadcast it,\n  // add it to the changelog and save\n  if (from_local) {\n    // Make this value visible between MGM's\n    PublishConfigChange(config_key.c_str(), val);\n    mChangelog->AddEntry(\"set config\", FormFullKey(prefix, key), val);\n\n    if (save_config) {\n      bool overwrite = true;\n      XrdOucString err = \"\";\n\n      if (!SaveConfig(mConfigFile, overwrite, \"\", err)) {\n        eos_static_err(\"%s\", err.c_str());\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Delete a configuration value\n//------------------------------------------------------------------------------\nvoid\nQuarkDBConfigEngine::DeleteConfigValue(const char* prefix, const char* key,\n                                       bool from_local)\n{\n  std::string config_key = FormFullKey(prefix, key);\n  {\n    std::lock_guard lock(mMutex);\n    sConfigDefinitions.erase(config_key);\n  }\n\n  // In case the change is not coming from a broacast we can can broadcast it,\n  // add it to the changelog and save it\n  if (from_local) {\n    // Make this value visible between MGM's\n    PublishConfigDeletion(config_key.c_str());\n    mChangelog->AddEntry(\"del config\", FormFullKey(prefix, key), \"\");\n    bool overwrite = true;\n    XrdOucString err = \"\";\n\n    if (!SaveConfig(mConfigFile, overwrite, \"\", err)) {\n      eos_static_err(\"%s\\n\", err.c_str());\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check write configuration result\n//------------------------------------------------------------------------------\nvoid checkWriteConfigurationResult(common::Status st)\n{\n  if (!st.ok()) {\n    eos_static_crit(\"Failed to save MGM configuration !!!! %s\",\n                    st.toString().c_str());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Store configuration into given keyname\n//------------------------------------------------------------------------------\nvoid QuarkDBConfigEngine::StoreIntoQuarkDB(const std::string& name)\n{\n  std::lock_guard lock(mMutex);\n  FilterDeprecated(sConfigDefinitions);\n  mConfigHandler->writeConfiguration(name, sConfigDefinitions, true,\n                                     FormatBackupTime(time(NULL)))\n  .via(mExecutor.get())\n  .thenValue(std::bind(checkWriteConfigurationResult, _1));\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/config/QuarkDBConfigEngine.hh",
    "content": "//------------------------------------------------------------------------------\n// @file QuarkDBConfigEngine.hh\n// @author Andrea Manzi - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Status.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"mgm/config/IConfigEngine.hh\"\n#include \"mgm/config/QuarkConfigHandler.hh\"\n#include \"namespace/ns_quarkdb/qclient/include/qclient/structures/QHash.hh\"\n#include \"namespace/ns_quarkdb/qclient/include/qclient/AsyncHandler.hh\"\n#include \"namespace/ns_quarkdb/qclient/include/qclient/QClient.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nclass QuarkConfigHandler;\n\n//------------------------------------------------------------------------------\n//! Class QuarkDBCfgEngineChangelog\n//------------------------------------------------------------------------------\nclass QuarkDBCfgEngineChangelog : public ICfgEngineChangelog\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param client qclient client\n  //----------------------------------------------------------------------------\n  QuarkDBCfgEngineChangelog(qclient::QClient* client);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~QuarkDBCfgEngineChangelog() = default;\n\n  //----------------------------------------------------------------------------\n  //! Add entry to the changelog\n  //!\n  //! @param key      entry action\n  //! @param value    entry key\n  //! @param comment  entry value\n  //----------------------------------------------------------------------------\n  void AddEntry(const std::string& action, const std::string& key,\n                const std::string& value, const std::string& comment = \"\") override;\n\n  //----------------------------------------------------------------------------\n  //! Get tail of the changelog\n  //!\n  //! @param nlines number of lines to return\n  //! @param tail string to hold the response\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool Tail(unsigned int nlines, std::string& tail) override;\n\nprivate:\n  const std::string kChangelogKey = \"eos-config-changelog\"; ///< Changelog key\n  qclient::QClient& mQcl;\n};\n\n\n//------------------------------------------------------------------------------\n//! Class QuarkDBConfigEngine\n//------------------------------------------------------------------------------\nclass QuarkDBConfigEngine : public IConfigEngine\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param contactDetails QuarkDB contact details\n  //----------------------------------------------------------------------------\n  QuarkDBConfigEngine(const QdbContactDetails& contactDetails);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~QuarkDBConfigEngine() = default;\n\n  //----------------------------------------------------------------------------\n  //! Load a given configuration file\n  //!\n  //! @param filename name of the file holding the configuration to be loaded\n  //! @param err string holding any errors\n  //! @param apply_stall_redirect if true then skip applying stall and redirect\n  //!        rules from the configuration\n  //!\n  //! @return true if loaded successfully, otherwise false\n  //----------------------------------------------------------------------------\n  bool LoadConfig(const std::string& filename, XrdOucString& err,\n                  bool apply_stall_redirect = false) override;\n\n  //----------------------------------------------------------------------------\n  //! Save configuration to specified destination\n  //!\n  //! @param filename name of the file where the current configuration will be saved\n  //! @param overwrite force overwrite of <filename> if the file exists already\n  //! @param comment comments\n  //! @param err string holding any errors\n  //!\n  //! @return true if saved successfully, otherwise false\n  //----------------------------------------------------------------------------\n  bool SaveConfig(std::string filename, bool overwrite,\n                  const std::string& comment, XrdOucString& err) override;\n\n  //----------------------------------------------------------------------------\n  //! List all configurations\n  //!\n  //! @param configlist string holding the list of all configurations\n  //! @param showbackups if true then show also the backups\n  //!\n  //! @return true if listing successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ListConfigs(XrdOucString& configlist, bool showbackups = false) override;\n\n\n  //----------------------------------------------------------------------------\n  //! Do an autosave\n  //----------------------------------------------------------------------------\n  bool AutoSave() override;\n\n  //----------------------------------------------------------------------------\n  //! Set a configuration value\n  //!\n  //! @param prefix identifies the type of configuration parameter\n  //! @param key key of the configuration to set\n  //! @param val value of the configuration\n  //! @param from_local mark if change comes from local MGM or remote one\n  //! @param save_config mark if configuration should also be saved or not\n  //----------------------------------------------------------------------------\n  void SetConfigValue(const char* prefix, const char* key, const char* val,\n                      bool from_local = true, bool save_config = true) override;\n\n  //----------------------------------------------------------------------------\n  //! Delete a configuration value\n  //!\n  //! @param prefix identifies the type of configuration parameter\n  //! @param key key of the configuration to delete\n  //! @param from_local mark if change comes from local MGM or remote one\n  //! @param save_config mark if configuration should also be saved or not\n  //----------------------------------------------------------------------------\n  void DeleteConfigValue(const char* prefix, const char* key,\n                         bool from_local = true) override;\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n\n  //----------------------------------------------------------------------------\n  //! Constructor used only for testing\n  //----------------------------------------------------------------------------\n  QuarkDBConfigEngine() = default;\n\n  //----------------------------------------------------------------------------\n  //! Format time\n  //----------------------------------------------------------------------------\n  static std::string FormatBackupTime(time_t timestamp)\n  {\n    char buff[128];\n    strftime(buff, 127, \"%Y%m%d%H%M%S\", localtime(&timestamp));\n    return SSTR(buff);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Store configuration into given name\n  //----------------------------------------------------------------------------\n  void StoreIntoQuarkDB(const std::string& name);\n\n  //----------------------------------------------------------------------------\n  //! Load a configuration from QuarkDB\n  //----------------------------------------------------------------------------\n  common::Status PullFromQuarkDB(const std::string& configName);\n\n  //----------------------------------------------------------------------------\n  //! Cleanup thread trimming the number of backups\n  //----------------------------------------------------------------------------\n  void CleanupThread(ThreadAssistant& assistant);\n\n  QdbContactDetails mQdbContactDetails;\n  std::unique_ptr<qclient::QClient> mQcl;\n  std::unique_ptr<QuarkConfigHandler> mConfigHandler;\n  std::unique_ptr<folly::Executor> mExecutor;\n  AssistedThread mCleanupThread;\n\n  //----------------------------------------------------------------------------\n  //! Remove old unused nodes that are off and have no file systems registered\n  //!\n  //! @return true if there were any modifications, otherwise false\n  //----------------------------------------------------------------------------\n  bool RemoveUnusedNodes();\n\n  //----------------------------------------------------------------------------\n  //! Remove deprecated configuration keys\n  //!\n  //! @return true if there were any modification, otherwise false\n  //----------------------------------------------------------------------------\n  bool RemoveDeprecatedKeys();\n\n  //----------------------------------------------------------------------------\n  //! Filter configuration - display given configuration\n  //!\n  //! @param out output representation of the configuration after filtering\n  //! @param cfg_name configuration name\n  //!\n  //! @return 0 if successful, otherwise errno\n  //----------------------------------------------------------------------------\n  int FilterConfig(std::ostream& out, const std::string& cfg_name) override;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/config/eos-config-inspect.cc",
    "content": "// ----------------------------------------------------------------------\n// File: eos-config-export.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n\n#include \"common/config/ConfigParsing.hh\"\n#include \"common/CLI11.hpp\"\n#include \"common/PasswordHandler.hh\"\n#include \"common/StringUtils.hh\"\n\n#include \"mgm/config/QuarkConfigHandler.hh\"\n\n#include <qclient/QClient.hh>\n#include <qclient/ResponseParsing.hh>\n#include <qclient/MultiBuilder.hh>\n\n#define SSTR(message) static_cast<std::ostringstream&>(std::ostringstream().flush() << message).str()\n\nstruct MemberValidator : public CLI::Validator {\n  MemberValidator() : Validator(\"MEMBER\")\n  {\n    func_ = [](const std::string & str) {\n      qclient::Members members;\n\n      if (!members.parse(str)) {\n        return SSTR(\"Could not parse members: '\" << str <<\n                    \"'. Expected format is a comma-separated list of servers: example1:1111,example2:2222\");\n      }\n\n      return std::string();\n    };\n  }\n};\n\n//------------------------------------------------------------------------------\n// Given a subcommand, add common-to-all options such as --members\n// and --password\n//----------------------------------------------------------------------------\nvoid addClusterOptions(CLI::App* subcmd, std::string& membersStr,\n                       MemberValidator& memberValidator, std::string& password,\n                       std::string& passwordFile)\n{\n  subcmd->add_option(\"--members\", membersStr,\n                     \"One or more members of the QDB cluster\")\n  ->required()\n  ->check(memberValidator);\n  auto passwordGroup = subcmd->add_option_group(\"Authentication\",\n                       \"Specify QDB authentication options\");\n  passwordGroup->add_option(\"--password\", password,\n                            \"The password for connecting to the QDB cluster - can be empty\");\n  passwordGroup->add_option(\"--password-file\", passwordFile,\n                            \"The passwordfile for connecting to the QDB cluster - can be empty\");\n  passwordGroup->require_option(0, 1);\n}\n\n//------------------------------------------------------------------------------\n// Read source configuration file\n//------------------------------------------------------------------------------\nbool readConfigurationFile(const std::string& sourceFile,\n                           std::string& fullContents)\n{\n  std::ifstream infile(sourceFile.c_str());\n\n  if (!infile.is_open()) {\n    return false;\n  }\n\n  std::ostringstream ss;\n\n  while (!infile.eof()) {\n    std::string s;\n    std::getline(infile, s);\n\n    if (!s.empty()) {\n      ss << s << \"\\n\";\n    }\n  }\n\n  infile.close();\n  fullContents = ss.str();\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Read and parse configuration file\n//------------------------------------------------------------------------------\nbool readAndParseConfiguration(const std::string& path,\n                               std::map<std::string, std::string>& configuration)\n{\n  //----------------------------------------------------------------------------\n  // Read source configuration file\n  //----------------------------------------------------------------------------\n  std::string fullContents;\n\n  if (!readConfigurationFile(path, fullContents) || fullContents.empty()) {\n    std::cerr << \"could not read configuration file: \" << path << std::endl;\n    return false;\n  }\n\n  //----------------------------------------------------------------------------\n  // Parse\n  //----------------------------------------------------------------------------\n  std::string err;\n\n  if (!eos::common::ConfigParsing::parseConfigurationFile(fullContents,\n      configuration, err)) {\n    std::cerr << \"Could not parse configuration file: \" << err << std::endl;\n    return false;\n  }\n\n  std::cerr << \"--- Successfully parsed configuration file\" << std::endl;\n  return true;\n}\n\nint runDumpSubcommand(const std::string& configEntry,\n                      eos::mgm::QuarkConfigHandler& configHandler)\n{\n  std::map<std::string, std::string> configuration;\n  eos::common::Status status = configHandler.fetchConfiguration(configEntry,\n                               configuration);\n\n  if (!status) {\n    std::cerr << \"error while fetching configuration '\" << configEntry << \"' : \" <<\n              status.toString() << std::endl;\n    return 1;\n  }\n\n  for (auto it = configuration.begin(); it != configuration.end(); it++) {\n    std::cout << it->first << \" => \" << it->second << std::endl;\n  }\n\n  return 0;\n}\n\nint runExportSubcommand(const std::string& sourceFile,\n                        eos::mgm::QuarkConfigHandler& configHandler,\n                        bool overwrite)\n{\n  std::map<std::string, std::string> configuration;\n\n  if (!readAndParseConfiguration(sourceFile, configuration)) {\n    return 1;\n  }\n\n  eos::common::Status st = configHandler.writeConfiguration(\"default\",\n                           configuration, overwrite).get();\n\n  if (!st) {\n    std::cerr << \"ERROR: \" << st.toString() << std::endl;\n    return 1;\n  }\n\n  std::cerr << \"--- Operation successful - wrote configuration 'default' with \" <<\n            configuration.size() << \" entries\" << std::endl;\n  return 0;\n}\n\nint runListSubcommand(eos::mgm::QuarkConfigHandler& configHandler)\n{\n  std::vector<std::string> configs, backups;\n  eos::common::Status st = configHandler.listConfigurations(configs, backups);\n\n  if (!st) {\n    std::cerr << \"ERROR: \" << st.toString() << std::endl;\n    return 1;\n  }\n\n  std::cout << \"Stored configurations:\" << std::endl;\n\n  for (auto it = configs.begin(); it != configs.end(); it++) {\n    std::cout << \"    \" << *it << std::endl;\n  }\n\n  std::cout << std::endl;\n  std::cout << \"Stored backups:\" << std::endl;\n\n  for (auto it = backups.begin(); it != backups.end(); it++) {\n    std::cout << \"    \" << *it << std::endl;\n  }\n\n  return 0;\n}\n\nint runTailSubcommand(size_t nlines,\n                      eos::mgm::QuarkConfigHandler& configHandler)\n{\n  std::vector<std::string> changelog;\n  eos::common::Status st = configHandler.tailChangelog(nlines, changelog);\n\n  if (!st) {\n    std::cerr << st.toString() << std::endl;\n    return 1;\n  }\n\n  for (auto it = changelog.begin(); it != changelog.end(); it++) {\n    std::cout << *it << std::endl;\n  }\n\n  return 0;\n}\n\nint runRelocateFilesystemSubcommand(eos::mgm::QuarkConfigHandler& configHandler,\n                                    uint32_t fsid, const std::string& newhost, int newport)\n{\n  std::map<std::string, std::string> configMap;\n  eos::common::Status st = configHandler.fetchConfiguration(\"default\", configMap);\n\n  if (!st) {\n    std::cerr << \"could not fetch configuration: \" << st.toString() << std::endl;\n    return 1;\n  }\n\n  for (auto it = configMap.begin(); it != configMap.end(); it++) {\n    if (eos::common::startsWith(it->first, \"fs:\")) {\n      std::map<std::string, std::string> configEntry;\n\n      if (!eos::common::ConfigParsing::parseFilesystemConfig(it->second,\n          configEntry)) {\n        std::cerr << \"could not parse fs entry: \" << it->first << it->second <<\n                  std::endl;\n        return 1;\n      }\n\n      if (configEntry[\"id\"] == SSTR(fsid)) {\n        // We have a match\n        std::cout << \"Found filesystem with fsid=\" << fsid << \": \" <<\n                  configEntry[\"queue\"] << std::endl;\n        std::cout << it->first << \" \" << it->second << std::endl;\n        st = eos::common::ConfigParsing::relocateFilesystem(newhost, newport,\n             configEntry);\n\n        if (!st) {\n          std::cerr << \"filesystem relocation failed: \" << st.toString() << std::endl;\n          return 1;\n        }\n\n        std::string configKey = SSTR(\"fs:\" << configEntry[\"queuepath\"]);\n        std::string newConfig = eos::common::joinMap(configEntry, \" \");\n        std::cout << \"After relocation: \" << configKey << \" \" << newConfig << std::endl;\n        configMap.erase(it);\n        configMap[configKey] = newConfig;\n        char buff[128];\n        time_t timestamp = time(NULL);\n        strftime(buff, 127, \"%Y%m%d%H%M%S\", localtime(&timestamp));\n        std::string backupName = SSTR(\"default-\" << buff << \"-relocation\");\n        st = configHandler.writeConfiguration(\"default\", configMap, true,\n                                              backupName).get();\n\n        if (!st) {\n          std::cerr << \"writing configuration failed: \" << st.toString() << std::endl;\n          return 1;\n        }\n\n        std::cout << \"Successfully wrote configuration, backup key: \" << backupName <<\n                  std::endl;\n        return 0;\n      }\n    }\n  }\n\n  std::cerr << \"no filesystem found with fsid=\" << fsid << std::endl;\n  return 1;\n}\n\n//------------------------------------------------------------------------------\n// Trim backups subcommand\n//------------------------------------------------------------------------------\nint runTrimBackupsSubcommand(size_t limit,\n                             eos::mgm::QuarkConfigHandler& configHandler)\n{\n  size_t deleted = 0;\n  eos::common::Status st = configHandler.trimBackups(\"default\", limit, deleted);\n\n  if (!st) {\n    std::cerr << st.toString() << std::endl;\n    return st.getErrc();\n  }\n\n  std::cout << \"deleted \" << deleted << \" config backups\" << std::endl;\n  return 0;\n}\n\nint main(int argc, char* argv[])\n{\n  CLI::App app(\"Tool to inspect contents of the QuarkDB-based EOS configuration.\");\n  app.require_subcommand();\n  //----------------------------------------------------------------------------\n  // Parameters common to all subcommands\n  //----------------------------------------------------------------------------\n  std::string membersStr;\n  MemberValidator memberValidator;\n  std::string password;\n  std::string passwordFile;\n  //----------------------------------------------------------------------------\n  // Set-up export subcommand..\n  //----------------------------------------------------------------------------\n  auto exportSubcommand = app.add_subcommand(\"export\",\n                          \"[DANGEROUS] Read a legacy file-based configuration file, and export to QDB. Ensure the MGM is not running while you run this command!\");\n  std::string sourceFile;\n  exportSubcommand->add_option(\"--source\", sourceFile,\n                               \"Path to the source configuration file to export\")\n  ->required();\n  bool overwrite = false;\n  exportSubcommand->add_flag(\"--overwrite\", overwrite,\n                             \"Overwrite already-existing configuration in QDB.\");\n  addClusterOptions(exportSubcommand, membersStr, memberValidator, password,\n                    passwordFile);\n  //----------------------------------------------------------------------------\n  // Set-up relocate-filesystem subcommand..\n  //----------------------------------------------------------------------------\n  auto relocateFilesystemSubcommand = app.add_subcommand(\"relocate-filesystem\",\n                                      \"[DANGEROUS] Change the FST to which a filesystem belongs to\");\n  uint32_t fsid;\n  relocateFilesystemSubcommand->add_option(\"--fsid\", fsid,\n      \"The ID of the filesystem to change\")\n  ->required();\n  std::string newFstHost;\n  relocateFilesystemSubcommand->add_option(\"--new-fst-host\", newFstHost,\n      \"The new FST host\")\n  ->required();\n  int newFstPort;\n  relocateFilesystemSubcommand->add_option(\"--new-fst-port\", newFstPort,\n      \"The new FST port\")\n  ->required();\n  addClusterOptions(relocateFilesystemSubcommand, membersStr, memberValidator,\n                    password,\n                    passwordFile);\n  //----------------------------------------------------------------------------\n  // Set-up dump subcommand..\n  //----------------------------------------------------------------------------\n  auto dumpSubcommand = app.add_subcommand(\"dump\",\n                        \"Dump the contents of a given configuration stored in QDB\");\n  std::string configEntry = \"default\";\n  dumpSubcommand->add_option(\"--config\", configEntry,\n                             \"Configuration to dump (from 'list'), default is actual\");\n  addClusterOptions(dumpSubcommand, membersStr, memberValidator, password,\n                    passwordFile);\n  //----------------------------------------------------------------------------\n  // Set-up list subcommand..\n  //----------------------------------------------------------------------------\n  auto listSubcommand = app.add_subcommand(\"list\",\n                        \"List all stored configurations, including backups\");\n  addClusterOptions(listSubcommand, membersStr, memberValidator, password,\n                    passwordFile);\n  //----------------------------------------------------------------------------\n  // Set-up tail-changelog subcommand..\n  //----------------------------------------------------------------------------\n  auto tailSubcommand = app.add_subcommand(\"tail-changelog\",\n                        \"Tail configuration changelog\");\n  size_t nlines = 1000;\n  tailSubcommand->add_option(\"--nlines\", nlines,\n                             \"The maximum number of changelog entries to print\");\n  addClusterOptions(tailSubcommand, membersStr, memberValidator, password,\n                    passwordFile);\n  //----------------------------------------------------------------------------\n  // Set-up trim-backups subcommand..\n  //----------------------------------------------------------------------------\n  auto trimBackupsSubcommand = app.add_subcommand(\"trim-backups\",\n                               \"Trim number of configuration backups\");\n  size_t nbackups = 1000;\n  trimBackupsSubcommand->add_option(\"--limit\", nbackups,\n                                    \"The maximum number of backups to keep\");\n  addClusterOptions(trimBackupsSubcommand, membersStr, memberValidator, password,\n                    passwordFile);\n\n  //----------------------------------------------------------------------------\n  // Parse\n  //----------------------------------------------------------------------------\n  try {\n    app.parse(argc, argv);\n  } catch (const CLI::ParseError& e) {\n    return app.exit(e);\n  }\n\n  //----------------------------------------------------------------------------\n  // Validate --password and --password-file options..\n  //----------------------------------------------------------------------------\n  if (!passwordFile.empty()) {\n    if (!eos::common::PasswordHandler::readPasswordFile(passwordFile, password)) {\n      std::cerr << \"Could not read passwordfile: '\" << passwordFile <<\n                \"'. Ensure the file exists, and its permissions are 400.\" << std::endl;\n      return 1;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  // Set-up QClient object towards QDB, ensure sanity\n  //----------------------------------------------------------------------------\n  qclient::Members members = qclient::Members::fromString(membersStr);\n  eos::QdbContactDetails contactDetails(members, password);\n  eos::mgm::QuarkConfigHandler configHandler(contactDetails);\n  //----------------------------------------------------------------------------\n  // Ensure connection is sane\n  //----------------------------------------------------------------------------\n  eos::common::Status status = configHandler.checkConnection(std::chrono::seconds(\n                                 3));\n\n  if (!status) {\n    std::cerr << \"could not connect to QDB backend: \" << status.toString() <<\n              std::endl;\n    return 1;\n  }\n\n  if (exportSubcommand->parsed()) {\n    return runExportSubcommand(sourceFile, configHandler, overwrite);\n  } else if (relocateFilesystemSubcommand->parsed()) {\n    return runRelocateFilesystemSubcommand(configHandler, fsid, newFstHost,\n                                           newFstPort);\n  } else if (dumpSubcommand->parsed()) {\n    return runDumpSubcommand(configEntry, configHandler);\n  } else if (listSubcommand->parsed()) {\n    return runListSubcommand(configHandler);\n  } else if (tailSubcommand->parsed()) {\n    return runTailSubcommand(nlines, configHandler);\n  } else if (trimBackupsSubcommand->parsed()) {\n    return runTrimBackupsSubcommand(nbackups, configHandler);\n  }\n\n  std::cerr << \"No subcommand was supplied - should never reach here\" <<\n            std::endl;\n  return 1;\n}\n"
  },
  {
    "path": "mgm/convert/ConversionInfo.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ConversionInfo.cc\n// Author: Mihai Patrascoiu - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/convert/ConversionInfo.hh\"\n#include \"common/Logging.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n// Constructor\n//----------------------------------------------------------------------------\nConversionInfo::ConversionInfo(const eos::common::FileId::fileid_t fid,\n                               const eos::common::LayoutId::layoutid_t lid,\n                               const eos::common::GroupLocator& location,\n                               const std::string& plct_policy,\n                               const bool update_ctime,\n                               const std::string& app_tag) :\n  mFid(fid), mLid(lid), mLocation(location), mPlctPolicy(plct_policy),\n  mUpdateCtime(update_ctime), mAppTag(app_tag)\n{\n  char buff[4096];\n  snprintf(buff, std::size(buff), \"%016llx:%s.%i#%08lx\",\n           mFid, mLocation.getSpace().c_str(), mLocation.getIndex(), mLid);\n  std::string conversion {buff};\n\n  if (!mPlctPolicy.empty()) { // ~<placement_policy>\n    conversion += \"~\";\n    conversion += mPlctPolicy;\n  }\n\n  if (!mAppTag.empty()) {\n    conversion += \"^\";\n    conversion += mAppTag;\n    conversion += \"^\";\n  }\n\n  if (mUpdateCtime) {\n    conversion += UPDATE_CTIME;\n  }\n\n  mConversionString = conversion;\n}\n\n//----------------------------------------------------------------------------\n// Parse a conversion string representation into a conversion info object\n//\n// A conversion string has the following format:\n// <fid(016hex)>:<space.group>#<layoutid(08hex)>[^app_tag^][~<placement_policy>][+]\n//----------------------------------------------------------------------------\nstd::shared_ptr<ConversionInfo> ConversionInfo::parseConversionString(\n  std::string sconversion)\n{\n  using eos::common::FileId;\n  using eos::common::LayoutId;\n  using eos::common::GroupLocator;\n  const char* errmsg = \"unable to parse conversion string\";\n  FileId::fileid_t fid = 0;\n  LayoutId::layoutid_t lid = 0;\n  GroupLocator location;\n  std::string policy;\n  std::string app_tag;\n  bool update_ctime {false};\n  std::size_t conv_pos = 0;\n\n  if (sconversion.empty()) {\n    eos_static_err(\"%s\", \"msg=\\\"conversion string is empty\\\"\");\n    return nullptr;\n  }\n\n  // Check if ctime needs to be updated\n  if (*sconversion.rbegin() == UPDATE_CTIME) {\n    update_ctime = true;\n    sconversion.erase(sconversion.end() - 1);\n  }\n\n  // Parse file id\n  size_t pos = sconversion.find(\":\");\n\n  if ((pos == std::string::npos) || (pos != 16)) {\n    eos_static_err(\"msg=\\\"%s\\\" conversion_string=%s \"\n                   \"reason=\\\"invalid fxid\\\"\", errmsg, sconversion.c_str());\n    return nullptr;\n  } else {\n    std::string hexfid = sconversion.substr(0, pos).c_str();\n\n    try {\n      fid = std::stoull(hexfid, &conv_pos, 16);\n    } catch (...) { }\n\n    if (conv_pos != hexfid.length()) {\n      fid = 0;\n    }\n  }\n\n  // Parse space/group location\n  sconversion.erase(0, pos + 1);\n  pos = sconversion.find(\"#\");\n\n  if (pos == std::string::npos) {\n    eos_static_err(\"msg=\\\"%s\\\" conversion_string=%s \"\n                   \"reason=\\\"invalid space\\\"\", errmsg, sconversion.c_str());\n    return nullptr;\n  } else {\n    std::string spacegroup = sconversion.substr(0, pos);\n    (void) GroupLocator::parseGroup(spacegroup, location);\n\n    if (location.getSpace().empty()) {\n      return nullptr;\n    }\n  }\n\n  // Parse app_tag\n  sconversion.erase(0, pos + 1);\n  pos = sconversion.find('^');\n\n  if (pos != std::string::npos) {\n    auto end_pos = sconversion.find('^', pos + 1);\n\n    if (end_pos == std::string::npos) {\n      eos_static_err(\"msg='%s' conversion_string=%s \",\n                     \"reason=\\\"invalid app tag\\\"\", errmsg, sconversion.c_str());\n      return nullptr;\n    }\n\n    // Parse only substr excluding ^\n    app_tag = sconversion.substr(pos + 1, end_pos - (pos + 1));\n    // Erase [start, size including trailing ^]\n    sconversion.erase(pos, (end_pos - pos) + 1);\n  }\n\n  // Parse layout id\n  pos = sconversion.find(\"~\");\n  std::string hexlid = sconversion.c_str();\n\n  if (pos != std::string::npos) {\n    hexlid = sconversion.substr(0, pos).c_str();\n  }\n\n  try {\n    lid = std::stoll(hexlid, &conv_pos, 16);\n  } catch (...) {}\n\n  if (conv_pos != hexlid.length()) {\n    lid = 0;\n  }\n\n  if (!fid || !lid) {\n    eos_static_err(\"msg=\\\"%s\\\" conversion_string=%s \"\n                   \"reason=\\\"invalid fid or lid\\\"\", errmsg, sconversion.c_str());\n    return nullptr;\n  }\n\n  // Parse placement policy\n  if (pos != std::string::npos) {\n    policy = sconversion.substr(pos + 1);\n  }\n\n  return std::make_shared<ConversionInfo>(fid, lid, location, policy,\n                                          update_ctime, app_tag);\n}\n\n//----------------------------------------------------------------------------\n// Returns the full path of the conversion file\n//----------------------------------------------------------------------------\nstd::string ConversionInfo::ConversionPath() const\n{\n  char shard[8];\n  snprintf(shard, std::size(shard), \"%02llx\", mFid % CONVERTION_SHARD_MOD);\n  return SSTR(gOFS->MgmProcConversionPath << \"/\" << shard << \"/\" <<\n              mConversionString);\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/convert/ConversionInfo.hh",
    "content": "//------------------------------------------------------------------------------\n// File: ConversionInfo.hh\n// Author: Mihai Patrascoiu - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/FileId.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/FileSystem.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nconstexpr int CONVERTION_SHARD_MOD = 256;\n\n//------------------------------------------------------------------------------\n//! @brief Structure holding conversion details\n//------------------------------------------------------------------------------\nstruct ConversionInfo {\n  ///! Marker for ctime update of the converted file\n  static constexpr char UPDATE_CTIME = '+';\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ConversionInfo(const eos::common::FileId::fileid_t fid,\n                 const eos::common::LayoutId::layoutid_t lid,\n                 const eos::common::GroupLocator& location,\n                 const std::string& plct_policy,\n                 const bool update_ctime,\n                 const std::string& app_tag);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~ConversionInfo() = default;\n\n  //----------------------------------------------------------------------------\n  //! String representation of the conversion info\n  //----------------------------------------------------------------------------\n  inline std::string ToString() const\n  {\n    return mConversionString;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Fullpath of the conversion file\n  //----------------------------------------------------------------------------\n  std::string ConversionPath() const;\n\n  //----------------------------------------------------------------------------\n  //! Parse a conversion string representation into a conversion info object.\n  //!\n  //! A conversion string has the following format:\n  //! <fid(016hex)>:<space[.group]>#<layoutid(08hex)>[^app_tag^][~<placement_policy>][!]\n  //!\n  //!\n  //! @param sconversion the conversion string representation\n  //!\n  //! @return a shared_ptr holding ConversionInfo on success or nullptr\n  //----------------------------------------------------------------------------\n  static std::shared_ptr<ConversionInfo> parseConversionString(\n    std::string sconversion);\n\n  const eos::common::FileId::fileid_t mFid; ///< File id\n  const eos::common::LayoutId::layoutid_t mLid; ///< Target layout id\n  const eos::common::GroupLocator mLocation; ///< Target space/group placement\n  const std::string mPlctPolicy; ///< Placement policy\n  const bool mUpdateCtime; ///< Update ctime of converted file\n  const std::string mAppTag; ///< application tag of submitting application\nprivate:\n  std::string mConversionString; ///< Conversion string representation\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/convert/ConversionJob.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ConversionJob.cc\n// Author: Mihai Patrascoiu - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/convert/ConversionJob.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/tgc/MultiSpaceTapeGc.hh\"\n#include \"common/Constants.hh\"\n#include \"common/Timing.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"namespace/utils/Checksum.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"namespace/ns_quarkdb/NamespaceGroup.hh\"\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n\n\n//------------------------------------------------------------------------------\n// Utility functions to help with file conversion\n//------------------------------------------------------------------------------\nnamespace\n{\n//------------------------------------------------------------------------------\n// Generate default MGM URL\n//------------------------------------------------------------------------------\nXrdCl::URL NewUrl()\n{\n  XrdCl::URL url;\n  url.SetProtocol(\"root\");\n  url.SetUserName(\"root\");\n  url.SetHostPort(gOFS->MgmOfsAlias.c_str(), gOFS->ManagerPort);\n  return url;\n}\n\n//------------------------------------------------------------------------------\n// Generate default TPC properties\n//------------------------------------------------------------------------------\nXrdCl::PropertyList TpcProperties(uint64_t size)\n{\n  using eos::common::FileId;\n  XrdCl::PropertyList properties;\n  properties.Set(\"force\", true);\n  properties.Set(\"posc\", false);\n  properties.Set(\"coerce\", false);\n  properties.Set(\"sourceLimit\", (uint16_t) 1);\n  properties.Set(\"chunkSize\", (uint32_t)(4 * 1024 * 1024));\n  properties.Set(\"parallelChunks\", (uint16_t) 1);\n  properties.Set(\"tpcTimeout\", FileId::EstimateTpcTimeout(size).count());\n\n  if (size) {\n    properties.Set(\"thirdParty\", \"only\");\n  }\n\n  return properties;\n}\n\n//----------------------------------------------------------------------------\n//! Thrown if an EOS file system cannot determined\n//----------------------------------------------------------------------------\nstruct FileSystemNotFound: public std::runtime_error {\n  using std::runtime_error::runtime_error;\n};\n\n//----------------------------------------------------------------------------\n//! @return ID of disk file system as opposed to tape file system of specified\n//! EOS file\n//! @throw FileSystemNotFound if the disk location could not be determined\n//----------------------------------------------------------------------------\neos::IFileMD::location_t\ngetDiskFsIdOfFile(eos::IFileMD& fmd)\n{\n  const auto locations = fmd.getLocations();\n\n  if (locations.empty()) {\n    std::ostringstream msg;\n    msg << \"Failed to find disk file system for fxid=\" << std::hex <<\n        std::setfill('0') << std::setw(8) << fmd.getId()\n        << \": The file has no locations\";\n    throw FileSystemNotFound(msg.str());\n  }\n\n  if (EOS_TAPE_FSID != locations.at(0)) {\n    return locations.at(0);\n  }\n\n  if (2 > locations.size()) {\n    std::ostringstream msg;\n    msg << \"Failed to find disk file system for fxid=\" << std::hex <<\n        std::setfill('0') << std::setw(8) << fmd.getId()\n        << \": The file only has a tape location\";\n    throw FileSystemNotFound(msg.str());\n  }\n\n  return locations.at(1);\n}\n}\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nConversionJob::ConversionJob(const eos::IFileMD::id_t fid,\n                             const ConversionInfo& conversion_info,\n                             std::shared_ptr<XrdOucCallBack> callback) :\n  mFid(fid), mConversionInfo(conversion_info), mCallback(callback),\n  mStatus(Status::PENDING)\n{\n  mConversionPath = conversion_info.ConversionPath();\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nConversionJob::~ConversionJob()\n{\n}\n\n//------------------------------------------------------------------------------\n// Execute a third-party copy\n//------------------------------------------------------------------------------\nvoid ConversionJob::DoIt() noexcept\n{\n  using eos::common::FileId;\n  using eos::common::LayoutId;\n  std::string source_xs;\n  std::string source_xs_postconversion;\n  bool overwrite_checksum;\n  uint64_t source_size;\n  eos::IFileMD::LocationVector src_locations;\n  eos::IFileMD::LocationVector src_unlink_loc;\n  gOFS->MgmStats.Add(\"ConversionJobStarted\", 0, 0, 1);\n  eos_static_debug(\"msg=\\\"starting conversion job\\\" conversion_id=%s\",\n                   mConversionInfo.ToString().c_str());\n\n  // Avoid running cancelled jobs\n  if (mProgressHandler.ShouldCancel(0)) {\n    HandleError(\"conversion job cancelled before start\");\n    return;\n  }\n\n  mStatus.store(Status::RUNNING, std::memory_order_relaxed);\n\n  // Retrieve file metadata\n  try {\n    eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosFileService->getFileMD(mConversionInfo.mFid);\n    mSourcePath = gOFS->eosView->getUri(fmd.get());\n    source_size = fmd->getSize();\n    src_locations = fmd->getLocations();\n    src_unlink_loc = fmd->getUnlinkedLocations();\n    eos::appendChecksumOnStringAsHex(fmd.get(), source_xs);\n    // Check if conversion requests a checksum rewrite\n    std::string file_checksum = LayoutId::GetChecksumString(fmd->getLayoutId());\n    std::string conversion_checksum =\n      LayoutId::GetChecksumString(mConversionInfo.mLid);\n    overwrite_checksum = (file_checksum != conversion_checksum);\n  } catch (eos::MDException& e) {\n    HandleError(\"failed to retrieve file metadata\",\n                SSTR(\"fxid=\" << FileId::Fid2Hex(mConversionInfo.mFid)\n                     << \" ec=\" << e.getErrno()\n                     << \" emsg=\\\"\" << e.getMessage().str() << \"\\\"\"));\n    return;\n  }\n\n  const std::string& app_tag = mConversionInfo.mAppTag.empty() ? EOS_APP_NAME :\n                               mConversionInfo.mAppTag;\n  // Construct destination CGI\n  std::ostringstream dst_cgi;\n  dst_cgi << \"&eos.ruid=\" << DAEMONUID << \"&eos.rgid=\" << DAEMONGID\n          << \"&\" << ConversionCGI(mConversionInfo)\n          << \"&eos.app=\" << app_tag\n          << \"&eos.targetsize=\" << source_size;\n\n  if (source_xs.size() && !overwrite_checksum) {\n    dst_cgi << \"&eos.checksum=\" << source_xs;\n  }\n\n  // Add the list of file systems to exclude for the new entry\n  std::string exclude_fsids = \"&eos.excludefsid=\";\n\n  for (const auto& fsid : src_locations) {\n    exclude_fsids += std::to_string(fsid);\n    exclude_fsids += \",\";\n  }\n\n  for (const auto& fsid : src_unlink_loc) {\n    exclude_fsids += std::to_string(fsid);\n    exclude_fsids += \",\";\n  }\n\n  if (*exclude_fsids.rbegin() == ',') {\n    exclude_fsids.pop_back();\n  }\n\n  dst_cgi << exclude_fsids;\n  // Prepare the TPC job\n  XrdCl::URL url_src = NewUrl();\n  std::ostringstream url_params;\n  url_params << \"eos.ruid=\" << DAEMONUID\n             << \"&eos.rgid=\" << DAEMONGID\n             << \"&eos.encodepath=curl\"\n             << \"&eos.app=\" + app_tag;\n  url_src.SetParams(url_params.str());\n  url_src.SetPath(eos::common::StringConversion::curl_escaped(mSourcePath));\n  XrdCl::URL url_dst = NewUrl();\n  url_dst.SetParams(dst_cgi.str());\n  url_dst.SetPath(mConversionPath);\n  eos::common::XrdConnIdHelper src_id_helper(gOFS->mXrdConnPool, url_src);\n  eos::common::XrdConnIdHelper dst_id_helper(gOFS->mXrdConnPool, url_dst);\n  XrdCl::PropertyList properties = TpcProperties(source_size);\n  properties.Set(\"source\", url_src);\n  properties.Set(\"target\", url_dst);\n  // Create the TPC job\n  XrdCl::PropertyList result;\n  XrdCl::CopyProcess copy;\n  copy.AddJob(properties, &result);\n  XrdCl::XRootDStatus prepare_status = copy.Prepare();\n  eos_static_info(\"[tpc]: %s@%s => %s@%s prepare_msg=%s\",\n                  url_src.GetHostId().c_str(), url_src.GetLocation().c_str(),\n                  url_dst.GetHostId().c_str(), url_dst.GetLocation().c_str(),\n                  prepare_status.ToStr().c_str());\n\n  // Check the TPC prepare status\n  if (!prepare_status.IsOK()) {\n    HandleError(\"prepare conversion failed\");\n    return;\n  }\n\n  // Trigger the TPC job\n  XrdCl::XRootDStatus tpc_status = copy.Run(&mProgressHandler);\n\n  if (!tpc_status.IsOK()) {\n    HandleError(tpc_status.ToStr(),\n                SSTR(\"tpc_src=\" << url_src.GetLocation()\n                     << \" tpc_dst=\" << url_dst.GetLocation()));\n    return;\n  }\n\n  eos_static_info(\"[tpc]: %s => %s status=success tpc_msg=%s\",\n                  url_src.GetLocation().c_str(), url_dst.GetLocation().c_str(),\n                  tpc_status.ToStr().c_str());\n\n  // TPC job succeeded:\n  //  - Verify new file has all fragments according to layout\n  //  - Verify initial file hasn't changed\n  //  - Merge the conversion entry\n  // Verify new file has all fragments according to layout\n  try {\n    eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosView->getFile(mConversionPath);\n    size_t expected = LayoutId::GetStripeNumber(mConversionInfo.mLid) + 1;\n    size_t actual = fmd->getNumLocation();\n\n    if (expected != actual) {\n      HandleError(\"converted file replica number mismatch\",\n                  SSTR(\"expected=\" << expected << \" actual=\" << actual));\n      return;\n    }\n  } catch (eos::MDException& e) {\n    HandleError(\"failed to retrieve converted file metadata\",\n                SSTR(\"path=\" << mConversionPath << \" ec=\" << e.getErrno()\n                     << \" emsg=\\\"\" << e.getMessage().str() << \"\\\"\"));\n    return;\n  }\n\n  // Verify initial file hasn't changed\n  try {\n    eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, mConversionInfo.mFid);\n    eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosFileService->getFileMD(mConversionInfo.mFid);\n    eos::appendChecksumOnStringAsHex(fmd.get(), source_xs_postconversion);\n  } catch (eos::MDException& e) {\n    eos_static_debug(\"msg=\\\"failed to retrieve file metadata\\\" fxid=%08llx \"\n                     \"ec=%d emsg=\\\"%s\\\" conversion_id=%s\", mConversionInfo.mFid,\n                     e.getErrno(), e.getMessage().str().c_str(),\n                     mConversionInfo.ToString().c_str());\n  }\n\n  if (source_xs != source_xs_postconversion) {\n    HandleError(\"file checksum changed during conversion\",\n                SSTR(\"fxid=\" << FileId::Fid2Hex(mConversionInfo.mFid)\n                     << \" initial_xs=\" << source_xs << \" final_xs=\"\n                     << source_xs_postconversion));\n    return;\n  }\n\n  XrdOucErrInfo error;\n  eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n\n  // Merge the conversion entry\n  if (!Merge()) {\n    HandleError(\"failed to merge conversion entry\",\n                SSTR(\"path=\" << mSourcePath << \" converted_path=\"\n                     << mConversionPath));\n    return;\n  }\n\n  gOFS->MgmStats.Add(\"ConversionJobSuccessful\", 0, 0, 1);\n  eos_static_info(\"msg=\\\"conversion successful\\\" conversion_id=%s\",\n                  mConversionInfo.ToString().c_str());\n  mStatus.store(Status::DONE, std::memory_order_relaxed);\n\n  // Notify the tape garbage collector if tape support is enabled\n  if (gOFS->mTapeEnabled) {\n    try {\n      eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n      eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n      const auto fmd = gOFS->eosView->getFile(mSourcePath);\n\n      if (nullptr != fmd && fmd->hasAttribute(\"sys.archive.file_id\")) {\n        const auto fsId = getDiskFsIdOfFile(*fmd);\n        const std::string tgcSpace = FsView::gFsView.mIdView.lookupSpaceByID(fsId);\n        gOFS->mTapeGc->fileConverted(tgcSpace, fmd->getId());\n      }\n    } catch (...) {\n      // Ignore any garbage collection exceptions\n    }\n  }\n\n  if (mCallback) {\n    eos_static_info(\"[tpc]: notifying callback with 'cancel' after successfull tpc jobs\");\n    mCallback->Cancel();\n  }\n\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Log the error message, store it and set the job as failed\n//------------------------------------------------------------------------------\nvoid ConversionJob::HandleError(const std::string& emsg,\n                                const std::string& details)\n{\n  using std::chrono::system_clock;\n  using eos::common::Timing;\n  gOFS->MgmStats.Add(\"ConversionJobFailed\", 0, 0, 1);\n  eos_static_err(\"msg=\\\"%s\\\" %s conversion_id=%s\", emsg.c_str(), details.c_str(),\n                 mConversionInfo.ToString().c_str());\n  const std::time_t now = system_clock::to_time_t(system_clock::now());\n  const std::string timestamp = Timing::UnixTimestamp_to_ISO8601(now);\n  mErrorString = timestamp + \" | \" + emsg;\n\n  if (!details.empty()) {\n    mErrorString += \" | \" + details;\n  }\n\n  mStatus.store(Status::FAILED, std::memory_order_relaxed);\n\n  if (mCallback) {\n    eos_static_info(\"[tpc]: notifying callback with 'cancel' after error='%s' details-'%s'\",\n                    emsg.c_str(), details.c_str());\n    mCallback->Cancel();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Construct CGI from conversion info\n//------------------------------------------------------------------------------\nstd::string ConversionJob::ConversionCGI(const ConversionInfo& info) const\n{\n  using eos::common::LayoutId;\n  std::ostringstream cgi;\n  cgi << \"eos.layout.type=\" << LayoutId::GetLayoutTypeString(info.mLid)\n      << \"&eos.layout.nstripes=\" << LayoutId::GetStripeNumberString(info.mLid)\n      << \"&eos.layout.blockchecksum=\" << LayoutId::GetBlockChecksumString(info.mLid)\n      << \"&eos.layout.checksum=\" << LayoutId::GetChecksumString(info.mLid)\n      << \"&eos.layout.blocksize=\" << LayoutId::GetBlockSizeString(info.mLid)\n      << \"&eos.space=\" << info.mLocation.getSpace();\n\n  // Apend scheduling group only if present explicitly\n  if (info.mLocation.getSpace() != info.mLocation.getGroup()) {\n    cgi << \"&eos.group=\" << info.mLocation.getIndex();\n  }\n\n  if (!info.mPlctPolicy.empty()) {\n    cgi << \"&eos.placementpolicy=\" << info.mPlctPolicy;\n  }\n\n  return cgi.str();\n}\n\n//------------------------------------------------------------------------------\n// Merge original and the newly converted one so that the initial file\n// identifier and all the rest of the metadata information is preserved.\n// Steps for a successful conversion\n//   1. Update the new locations for original fid\n//   2. Trigger FST rename of the physical files from conv_fid to fid\n//   3. Unlink the locations for original fid\n//   4. Update the layout information for original fid\n//   5. Remove the conv_fid and FST local info\n//   6. Trigger an MGM resync for the new location of fid\n//------------------------------------------------------------------------------\nbool\nConversionJob::Merge()\n{\n  std::list<eos::IFileMD::location_t> conv_locations;\n  eos::IFileMD::id_t orig_fid {0ull}, conv_fid {0ull};\n  std::shared_ptr<eos::IFileMD> orig_fmd, conv_fmd;\n  bool has_quota = Quota::RemoveFile(mFid);\n  {\n    eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n\n    try {\n      orig_fmd = gOFS->eosFileService->getFileMD(mFid);\n      conv_fmd = gOFS->eosView->getFile(mConversionPath);\n    } catch (const eos::MDException& e) {\n      eos_static_err(\"msg=\\\"failed to retrieve file metadata\\\" msg=\\\"%s\\\"\",\n                     e.what());\n\n      if (has_quota) {\n        ns_rd_lock.Release();\n        Quota::AddFile(mFid);\n      }\n\n      return false;\n    }\n\n    orig_fid = orig_fmd->getId();\n    conv_fid = conv_fmd->getId();\n\n    // Add the new locations\n    for (const auto& loc : conv_fmd->getLocations()) {\n      orig_fmd->addLocation(loc);\n      conv_locations.push_back(loc);\n    }\n\n    gOFS->eosView->updateFileStore(orig_fmd.get());\n  }\n  // For each location get the FST information and trigger a physical file\n  // rename from the conv_fmd(fid) to the orig_fmd(fid)\n  bool failed_rename = false;\n  std::string fst_host;\n  int fst_port;\n\n  for (const auto& loc : conv_locations) {\n    {\n      eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n      FileSystem* fs = FsView::gFsView.mIdView.lookupByID(loc);\n\n      if ((fs == nullptr) ||\n          (fs->GetStatus() != eos::common::BootStatus::kBooted) ||\n          (fs->GetConfigStatus() != eos::common::ConfigStatus::kRW)) {\n        eos_static_err(\"msg=\\\"file system config cannot accept conversion\\\" \"\n                       \"fsid=%u\", loc);\n        failed_rename = true;\n        break;\n      }\n\n      fst_host = fs->GetHost();\n      fst_port = fs->getCoreParams().getLocator().getPort();\n    }\n    std::ostringstream oss;\n    oss << \"root://\" << fst_host << \":\" << fst_port << \"/?xrd.wantprot=sss\";\n    XrdCl::URL url(oss.str());\n\n    if (!url.IsValid()) {\n      eos_static_err(\"msg=\\\"invalid FST url\\\" url=\\\"%s\\\"\", oss.str().c_str());\n      failed_rename = true;\n      break;\n    }\n\n    oss.str(\"\");\n    // Build up the actual query string\n    oss << \"/?fst.pcmd=local_rename\"\n        << \"&fst.rename.ofid=\" << eos::common::FileId::Fid2Hex(conv_fid)\n        << \"&fst.rename.nfid=\" << eos::common::FileId::Fid2Hex(orig_fid)\n        << \"&fst.rename.fsid=\" << loc\n        << \"&fst.nspath=\" << mSourcePath;\n    uint16_t timeout = 10;\n    XrdCl::Buffer arg;\n    XrdCl::Buffer* response {nullptr};\n    XrdCl::FileSystem fs {url};\n    arg.FromString(oss.str());\n    XrdCl::XRootDStatus status = fs.Query(XrdCl::QueryCode::OpaqueFile, arg,\n                                          response, timeout);\n\n    if ((status.IsOK() == false) ||\n        (response == nullptr) ||\n        (response->ToString() != \"OK\")) {\n      eos_static_err(\"msg=\\\"failed local rename on file system\\\" \"\n                     \"orig_fxid=%08llx conv_fxid=%08llx fsid=%u \"\n                     \"status=%d err_msg=\\\"%s\\\" response=\\\"%s\\\"\",\n                     orig_fid, conv_fid, loc, status.IsOK(),\n                     status.GetErrorMessage().c_str(),\n                     (response ? response->ToString().c_str() : \"none\"));\n      failed_rename = true;\n      delete response;\n      break;\n    }\n\n    delete response;\n    eos_static_debug(\"msg=\\\"successful rename on file system\\\" orig_fxid=%08llx \"\n                     \"conv_fxid=%08llx fsid=%u\", orig_fid, conv_fid, loc);\n  }\n\n  // Do cleanup in case of failures\n  if (failed_rename) {\n    // Update locations and clean up conversion file object\n    eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n\n    try {\n      orig_fmd = gOFS->eosFileService->getFileMD(orig_fid);\n    } catch (const eos::MDException& e) {\n      eos_static_err(\"msg=\\\"failed to retrieve file metadata\\\" msg=\\\"%s\\\" \"\n                     \"orig_fxid=%08llx\", e.what(), orig_fid);\n      return false;\n    }\n\n    // Unlink all the newly added locations\n    for (const auto& loc : orig_fmd->getLocations()) {\n      if (std::find(conv_locations.begin(), conv_locations.end(), loc) !=\n          conv_locations.end()) {\n        orig_fmd->unlinkLocation(loc);\n      }\n    }\n\n    gOFS->eosView->updateFileStore(orig_fmd.get());\n\n    if (has_quota) {\n      ns_rd_lock.Release();\n      Quota::AddFile(mFid);\n    }\n\n    return false;\n  }\n\n  {\n    // Update locations and clean up conversion file object\n    eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n\n    try {\n      orig_fmd = gOFS->eosFileService->getFileMD(orig_fid);\n      conv_fmd = gOFS->eosFileService->getFileMD(conv_fid);\n    } catch (const eos::MDException& e) {\n      eos_static_err(\"msg=\\\"failed to retrieve file metadata\\\" msg=\\\"%s\\\"\",\n                     e.what());\n      return false;\n    }\n\n    // Unlink the old locations from the original file object\n    for (const auto& loc : orig_fmd->getLocations()) {\n      if (loc == eos::common::TAPE_FS_ID) {\n        continue;\n      }\n\n      if (std::find(conv_locations.begin(), conv_locations.end(), loc) ==\n          conv_locations.end()) {\n        orig_fmd->unlinkLocation(loc);\n      }\n    }\n\n    // Update the new layout id\n    orig_fmd->setLayoutId(mConversionInfo.mLid);\n\n    // If requested then also update the ctime of the original file\n    if (mConversionInfo.mUpdateCtime) {\n      orig_fmd->setCTimeNow();\n    }\n\n    gOFS->eosView->updateFileStore(orig_fmd.get());\n  }\n\n  // Update quota node given the new possible layout\n  if (has_quota) {\n    Quota::AddFile(mFid);\n  }\n\n  // Trigger a resync of the local information for the new locations\n  for (const auto& loc : conv_locations) {\n    if (gOFS->QueryResync(orig_fid, loc, true)) {\n      eos_static_err(\"msg=\\\"failed to send resync\\\" fxid=%08llx fsid=%u\",\n                     orig_fid, loc);\n    }\n  }\n\n  return true;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/convert/ConversionJob.hh",
    "content": "//------------------------------------------------------------------------------\n// File: ConversionJob.hh\n// Author: Mihai Patrascoiu - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/convert/ConversionInfo.hh\"\n#include \"common/FileId.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/FileSystem.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include <XrdCl/XrdClCopyProcess.hh>\n#include <XrdOuc/XrdOucCallBack.hh>\nEOSMGMNAMESPACE_BEGIN\n\n//! Forward declaration\nclass ConversionJob;\nenum class ConversionJobStatus { DONE, RUNNING, PENDING, FAILED };\nstatic std::string EOS_APP_NAME = \"eos/converter\";\n\n//------------------------------------------------------------------------------\n//! @brief Class used for monitoring the progress of a running\n//! Conversion Job. Cancellation is also allowed via this class.\n//------------------------------------------------------------------------------\nclass ConversionProgressHandler : public XrdCl::CopyProgressHandler,\n  public eos::common::LogId\n{\npublic:\n  friend class ConversionJob;\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ConversionProgressHandler() :\n    mCancel(false), mProgress(0), mBytesProcessed(0),\n    mStartTimestamp(0) {}\n\n  //----------------------------------------------------------------------------\n  //! Notify when a new job is about to start\n  //!\n  //! @param jobNum         the job number of the copy job concerned\n  //! @param jobTotal       total number of jobs being processed\n  //! @param source         the source url of the current job\n  //! @param destination    the destination url of the current job\n  //----------------------------------------------------------------------------\n  void BeginJob(uint16_t jobNum, uint16_t jobTotal,\n                const XrdCl::URL* source,\n                const XrdCl::URL* destination) override\n  {\n    mStartTimestamp = std::chrono::duration_cast<std::chrono::seconds>(\n                        std::chrono::system_clock::now().time_since_epoch()).count();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Notify about the progress of the current job\n  //!\n  //! @param jobNum         job number\n  //! @param bytesProcessed bytes processed by the current job\n  //! @param bytesTotal     total number of bytes to be processed\n  //----------------------------------------------------------------------------\n  void JobProgress(uint16_t jobNum, uint64_t bytesProcessed,\n                   uint64_t bytesTotal) override\n  {\n    mBytesProcessed = bytesProcessed;\n    mProgress = static_cast<int>(100.0 * bytesProcessed / bytesTotal);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Determine whether the job should be cancelled\n  //----------------------------------------------------------------------------\n  bool ShouldCancel(uint16_t jobNum) override\n  {\n    return mCancel;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Trigger job cancellation\n  //----------------------------------------------------------------------------\n  inline void Cancel()\n  {\n    mCancel = true;\n  }\n\nprivate:\n  std::atomic<bool> mCancel; ///< Flag whether job should be cancelled\n  std::atomic<int> mProgress; ///< Job completion percentage\n  std::atomic<uint64_t> mBytesProcessed; ///< Bytes processed by job so far\n  std::atomic<uint64_t> mStartTimestamp; ///< Timestamp of job start\n};\n\n\n//------------------------------------------------------------------------------\n//! @brief Class executing a third-party conversion job\n//------------------------------------------------------------------------------\nclass ConversionJob : public eos::common::LogId\n{\npublic:\n\n  //! Possible status of a conversion job\n  using Status = ConversionJobStatus;\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param fid the file id to convert\n  //----------------------------------------------------------------------------\n  ConversionJob(const eos::IFileMD::id_t fid,\n                const ConversionInfo& conversion_info,\n                std::shared_ptr<XrdOucCallBack> callback = nullptr);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~ConversionJob();\n\n  //----------------------------------------------------------------------------\n  //! Execute a third-party copy\n  //----------------------------------------------------------------------------\n  void DoIt() noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Cancel running third-party copy\n  //----------------------------------------------------------------------------\n  inline void Cancel()\n  {\n    mProgressHandler.Cancel();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the conversion job status\n  //----------------------------------------------------------------------------\n  inline ConversionJob::Status GetStatus() const\n  {\n    return mStatus.load(std::memory_order_relaxed);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the conversion string\n  //----------------------------------------------------------------------------\n  inline std::string GetConversionString() const\n  {\n    return mConversionInfo.ToString();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the conversion info\n  //----------------------------------------------------------------------------\n  inline ConversionInfo GetConversionInfo() const\n  {\n    return mConversionInfo;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the conversion job file id\n  //----------------------------------------------------------------------------\n  inline eos::IFileMD::id_t GetFid() const\n  {\n    assert(mFid == mConversionInfo.mFid);\n    return mFid;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the conversion error message\n  //----------------------------------------------------------------------------\n  inline std::string GetErrorMsg() const\n  {\n    return mErrorString;\n  }\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Merge original and the newly converted one so that the initial file\n  //! identifier and all the rest of the metadata information is preserved.\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool Merge();\n\n  //----------------------------------------------------------------------------\n  //! Log the error message, store it and set the job as failed\n  //!\n  //! @param errmsg the error message\n  //! @param details additional details\n  //----------------------------------------------------------------------------\n  void HandleError(const std::string& emsg, const std::string& details = \"\");\n\n  //----------------------------------------------------------------------------\n  //! Construct CGI from conversion info\n  //!\n  //! @param info conversion info to process\n  //!\n  //! @return string containing conversion CGI\n  //----------------------------------------------------------------------------\n  std::string ConversionCGI(const ConversionInfo& info) const;\n\n  const eos::IFileMD::id_t mFid; ///< Conversion file id\n  const ConversionInfo mConversionInfo; ///< Conversion details\n  std::string mSourcePath; ///< Path of file to be converted\n  std::string mConversionPath; ///< Path of newly converted file\n  std::shared_ptr<XrdOucCallBack> mCallback; ///< callback function when the job goes terminal\n  std::atomic<Status> mStatus; ///< Conversion job status\n  std::string mErrorString; ///< Error message\n  ConversionProgressHandler mProgressHandler; ///< Conversion progress handler\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/convert/ConversionTag.hh",
    "content": "//------------------------------------------------------------------------------\n// File: ConversionTag.hh\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/convert/ConversionInfo.hh\"\n\n#include <string>\n\nEOSMGMNAMESPACE_BEGIN\n\nclass ConversionTag\n{\npublic:\n  static std::string\n  Get(unsigned long long fid, std::string space, unsigned int layoutid,\n      std::string plctplcy, bool ctime_update = true)\n  {\n    char conversion[1024];\n    snprintf(conversion, sizeof(conversion) - 1, \"%08x\", layoutid);\n    return Get(fid, space, conversion, plctplcy, ctime_update);\n  }\n\n  static std::string\n  Get(unsigned long long fid, std::string space, std::string conversion,\n      std::string plctplcy, bool ctime_update = true)\n  {\n    char conversiontagfile[4096];\n\n    if (plctplcy.length()) {\n      // requires a ~ separator\n      plctplcy.insert(0, \"~\");\n    }\n\n    snprintf(conversiontagfile, sizeof(conversiontagfile) - 1,\n             \"%016llx:%s#%s%s\", fid,\n             space.c_str(), conversion.c_str(), plctplcy.c_str());\n    std::string conv_tag = conversiontagfile;\n\n    if (ctime_update) {\n      conv_tag += eos::mgm::ConversionInfo::UPDATE_CTIME;\n    }\n\n    return conv_tag;\n  }\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/convert/ConverterEngine.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ConverterEngine.cc\n// Author: Mihai Patrascoiu - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/convert/ConverterEngine.hh\"\n#include \"mgm/imaster/IMaster.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/Logging.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nnamespace\n{\nconst std::string kConvertCfg        = \"converter\";\nconst std::string kConvertStatus     = \"status\";\nconst std::string kConvertMaxThreads = \"max-thread-pool-size\";\nconst std::string kConvertMaxQueueSz = \"max-queue-size\";\n}\n\nconstexpr unsigned int ConverterEngine::QdbHelper::cBatchSize;\nstatic constexpr auto CONVERTER_THREAD_NAME = \"ConverterMT\";\n\n//------------------------------------------------------------------------------\n// Start converter thread\n//------------------------------------------------------------------------------\nvoid\nConverterEngine::Start()\n{\n  if (!mIsRunning) {\n    mIsRunning = true;\n    mThread.reset(&ConverterEngine::Convert, this);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Stop converter thread and all running conversion jobs\n//------------------------------------------------------------------------------\nvoid\nConverterEngine::Stop()\n{\n  mThread.join();\n  mIsRunning = false;\n}\n\n//------------------------------------------------------------------------------\n// Method to collect and queue pending jobs from the QDB backend\n//------------------------------------------------------------------------------\nvoid\nConverterEngine::PopulatePendingJobs()\n{\n  const auto lst_pending = mQdbHelper.GetPendingJobs();\n\n  for (const auto& info : lst_pending) {\n    const auto fid = std::get<0>(info);\n\n    if (!gOFS->mFidTracker.AddEntry(fid, TrackerType::Convert)) {\n      eos_static_debug(\"msg=\\\"skip recently scheduled file\\\" fxid=%08llx\", fid);\n      continue;\n    }\n\n    mPendingJobs.emplace(std::get<0>(info), std::get<1>(info), nullptr);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Cleanup handle after a job is run - remove the job from the list of\n//------------------------------------------------------------------------------\nvoid\nConverterEngine::HandlePostJobRun(std::shared_ptr<ConversionJob> job)\n{\n  const auto fid = job->GetFid();\n  {\n    eos::common::RWMutexWriteLock wlock(mJobsMutex);\n    mJobsRunning.erase(fid);\n  }\n\n  if (!mQdbHelper.RemovePendingJob(fid)) {\n    eos_static_err(\"msg=\\\"failed to remove conversion job from QuarkDB\\\" \"\n                   \"fxid=%08llx\", fid);\n  }\n\n  if (job->GetStatus() == ConversionJobStatus::FAILED) {\n    ++mFailed;\n  }\n\n  // cleanup the conversion file\n  auto info = job->GetConversionInfo();\n  auto rootvid = eos::common::VirtualIdentity::Root();\n  auto converter_path = info.ConversionPath();\n  XrdOucErrInfo error;\n\n  if (gOFS->_rem(converter_path.c_str(), error, rootvid,\n                 (const char*)0, false, false, true)) {\n    eos_static_err(\"msg=\\\"failed to delete conversion file\\\" path=\\\"%s\\\" \"\n                   \"err=\\\"%s\\\"\", converter_path.c_str(), error.getErrText());\n  }\n\n  mObserverMgr->notifyChange(job->GetStatus(), job->GetConversionString());\n  gOFS->mFidTracker.RemoveEntry(info.mFid);\n}\n\n//------------------------------------------------------------------------------\n// Converter engine thread monitoring\n//------------------------------------------------------------------------------\nvoid\nConverterEngine::Convert(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(CONVERTER_THREAD_NAME);\n  JobInfoT info;\n  eos_notice(\"%s\", \"msg=\\\"starting converter engine\\\"\");;\n  gOFS->WaitUntilNamespaceIsBooted(assistant);\n\n  // Wait that current MGM becomes a master\n  do {\n    eos_debug(\"%s\", \"msg=\\\"converter waiting for master MGM\\\"\");\n    assistant.wait_for(std::chrono::seconds(10));\n  } while (!assistant.terminationRequested() &&\n           (!gOFS->mMaster || !gOFS->mMaster->IsMaster()));\n\n  PopulatePendingJobs();\n\n  while (!assistant.terminationRequested()) {\n    while (!mPendingJobs.try_pop(info) && !assistant.terminationRequested()) {\n      assistant.wait_for(std::chrono::seconds(5));\n    }\n\n    while ((mThreadPool.GetQueueSize() > mMaxQueueSize) &&\n           !assistant.terminationRequested()) {\n      eos_static_notice(\"%s\", \"msg=\\\"convert thread pool queue full, delay \"\n                        \"pending jobs\\\"\");\n      assistant.wait_for(std::chrono::seconds(5));\n    }\n\n    auto fid = std::get<0>(info);\n    auto conversion_info = ConversionInfo::parseConversionString(std::get<1>(info));\n\n    if (conversion_info != nullptr) {\n      auto job = std::make_shared<ConversionJob>(fid, *conversion_info.get(),\n                 std::get<2>(info));\n      mThreadPool.PushTask<void>([job, this]() {\n        job->DoIt();\n        this->HandlePostJobRun(job);\n      });\n      eos::common::RWMutexWriteLock wlock(mJobsMutex);\n      mJobsRunning[job->GetFid()] = job;\n    } else {\n      eos_static_err(\"msg=\\\"invalid conversion scheduled\\\" fxid=%08llx \"\n                     \"conversion_id=%s\", fid, std::get<1>(info).c_str());\n      mQdbHelper.RemovePendingJob(fid);\n      gOFS->mFidTracker.RemoveEntry(fid);\n    }\n  }\n\n  JoinAllConversionJobs();\n  mIsRunning = false;\n  eos_static_notice(\"%s\", \"msg=\\\"stopped converter engine\\\"\");;\n}\n\n//------------------------------------------------------------------------------\n// Signal all conversion jobs to stop\n//------------------------------------------------------------------------------\nvoid\nConverterEngine::JoinAllConversionJobs()\n{\n  eos_notice(\"%s\", \"msg=\\\"stopping all running conversion jobs\\\"\");\n  {\n    // Signal all conversion jobs to stop/cancel\n    eos::common::RWMutexReadLock rd_lock(mJobsMutex);\n\n    for (const auto& pair : mJobsRunning) {\n      auto& job = pair.second;\n\n      if (job->GetStatus() == ConversionJob::Status::RUNNING) {\n        job->Cancel();\n      }\n    }\n  }\n  {\n    // Wait for conversion jobs to cancel\n    eos::common::RWMutexWriteLock wr_lock(mJobsMutex);\n\n    while (!mJobsRunning.empty()) {\n      const eos::IFileMD::id_t fid = mJobsRunning.begin()->first;\n      auto job = mJobsRunning.begin()->second;\n      wr_lock.Release();\n\n      while ((job->GetStatus() == ConversionJob::Status::RUNNING) ||\n             (job->GetStatus() == ConversionJob::Status::PENDING)) {\n        std::this_thread::sleep_for(std::chrono::milliseconds(10));\n      }\n\n      wr_lock.Grab(mJobsMutex);\n      mJobsRunning.erase(fid);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Schedule a conversion job with the given ID and conversion string\n//------------------------------------------------------------------------------\nbool\nConverterEngine::ScheduleJob(const eos::IFileMD::id_t& id,\n                             const std::string& conversion_info,\n                             std::string& err_msg,\n                             std::shared_ptr<XrdOucCallBack> callback)\n{\n  if (!mIsRunning) {\n    err_msg = \"converter not running\";\n    return false;\n  }\n\n  if (mPendingJobs.size() > 1000000) {\n    err_msg = \"converter queue full\";\n    eos_static_err(\"%s\", \"msg=\\\"forbid conversion as there are more than 1M \"\n                   \"jobs pending\");\n    return false;\n  }\n\n  if (conversion_info.empty()) {\n    err_msg = \"invalid conversion info\";\n    eos_static_err(\"msg=\\\"invalid conversion_info string for file\\\" fxid=%08llx\",\n                   id);\n    return false;\n  }\n\n  if (!gOFS->mFidTracker.AddEntry(id, TrackerType::Convert)) {\n    err_msg = \"already scheduled file\";\n    eos_static_debug(\"msg=\\\"skip recently scheduled file\\\" fxid=%08llx\", id);\n    return false;\n  }\n\n  JobInfoT info = std::make_tuple(id, conversion_info, callback);\n  mPendingJobs.push(info);\n  return mQdbHelper.AddPendingJob(info);\n}\n\n//------------------------------------------------------------------------------\n// Apply global configuration relevant for the converter\n//------------------------------------------------------------------------------\nvoid\nConverterEngine::ApplyConfig()\n{\n  using eos::common::StringTokenizer;\n  std::string config = FsView::gFsView.GetGlobalConfig(kConvertCfg);\n  // Parse config of the form: key1=val1 key2=val2 etc.\n  eos_static_info(\"msg=\\\"apply converter configuration\\\" data=\\\"%s\\\"\",\n                  config.c_str());\n  std::map<std::string, std::string> kv_map;\n  auto pairs = StringTokenizer::split<std::list<std::string>>(config, ' ');\n\n  for (const auto& pair : pairs) {\n    auto kv = StringTokenizer::split<std::vector<std::string>>(pair, '=');\n\n    if (kv.empty()) {\n      eos_static_err(\"msg=\\\"unknown converter config data\\\" data=\\\"%s\\\"\",\n                     config.c_str());\n      continue;\n    }\n\n    // There is no use-case yet for keys without values!\n    if (kv.size() == 1) {\n      continue;\n    }\n\n    kv_map.emplace(kv[0], kv[1]);\n  }\n\n  for (const auto& [key, val] : kv_map) {\n    SetConfig(key, val);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Make configuration change\n//------------------------------------------------------------------------------\nbool\nConverterEngine::SetConfig(const std::string& key, const std::string& val)\n{\n  bool config_change = false;\n\n  if (key == kConvertMaxThreads) {\n    int max_threads = 100;\n\n    try {\n      max_threads = std::stoi(val);\n    } catch (...) {\n      eos_static_err(\"msg=\\\"failed parsing converter max threads \"\n                     \"configuration\\\" data=\\\"%s\\\"\", val.c_str());\n      return false;\n    }\n\n    if ((max_threads < 5) || (max_threads > 5000)) {\n      eos_static_err(\"msg=\\\"max threads limit outside accepted range \"\n                     \"[5, 5000]\\\" max_threads=%i\", max_threads);\n      return false;\n    }\n\n    if (max_threads != mThreadPool.GetMaxThreads()) {\n      mThreadPool.SetMaxThreads(max_threads);\n      config_change = true;\n    }\n  } else if (key == kConvertMaxQueueSz) {\n    int max_queue_sz = 100;\n\n    try {\n      max_queue_sz = std::stoi(val);\n    } catch (...) {\n      eos_static_err(\"msg=\\\"failed parsing converter max queue size\\\"\"\n                     \"data=\\\"%s\\\"\", val.c_str());\n      return false;\n    }\n\n    if (max_queue_sz && (max_queue_sz != mMaxQueueSize)) {\n      mMaxQueueSize.store(max_queue_sz);\n      config_change = true;\n    }\n  } else if (key == kConvertStatus) {\n    if ((val == \"on\") && (mIsRunning == false)) {\n      config_change = true;\n      Start();\n    } else if ((val == \"off\") && mIsRunning) {\n      config_change = true;\n      Stop();\n    }\n  } else {\n    return false;\n  }\n\n  if (config_change) {\n    if (!StoreConfig()) {\n      eos_static_err(\"%s\", \"msg=\\\"failed to save converter configuration\\\"\");\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Serialize converter configuration\n//------------------------------------------------------------------------------\nstd::string\nConverterEngine::SerializeConfig() const\n{\n  const std::string status = (mIsRunning ? \"on\" : \"off\");\n  std::ostringstream oss;\n  oss << kConvertStatus << \"=\" << status << \" \"\n      << kConvertMaxThreads << \"=\" << mThreadPool.GetMaxThreads() << \" \"\n      << kConvertMaxQueueSz << \"=\" << mMaxQueueSize;\n  return oss.str();\n}\n\n//----------------------------------------------------------------------------\n// Store configuration\n//----------------------------------------------------------------------------\nbool\nConverterEngine::StoreConfig()\n{\n  return FsView::gFsView.SetGlobalConfig(kConvertCfg, SerializeConfig());\n}\n\n//------------------------------------------------------------------------------\n// QdbHelper class implementation\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Add conversion job to the queue of pending jobs in QuarkDB\n//------------------------------------------------------------------------------\nbool\nConverterEngine::QdbHelper::AddPendingJob(const JobInfoT& jobinfo)\n{\n  try {\n    bool hset = mQHashPending.hset(std::to_string(std::get<0>(jobinfo)),\n                                   std::get<1>(jobinfo));\n    std::cerr << \"hset: \" << hset << std::endl;\n    return hset;\n  } catch (const std::exception& e) {\n    eos_static_crit(\"msg=\\\"Error encountered while trying to add pending \"\n                    \"conversion job\\\" emsg=\\\"%s\\\" conversion_id=%s\",\n                    e.what(), std::get<1>(jobinfo).c_str());\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Get list of all pending jobs\n//------------------------------------------------------------------------------\nstd::vector<ConverterEngine::JobInfoT>\nConverterEngine::QdbHelper::GetPendingJobs()\n{\n  std::vector<JobInfoT> pending;\n  pending.reserve(mQHashPending.hlen());\n\n  for (auto it = mQHashPending.getIterator(cBatchSize, \"0\"); it.valid();\n       it.next()) {\n    try {\n      pending.emplace_back(std::stoull(it.getKey()), it.getValue(), nullptr);\n    } catch (...) {}\n  }\n\n  return pending;\n}\n\n//------------------------------------------------------------------------------\n// Clear list of pending jobs\n//------------------------------------------------------------------------------\nvoid\nConverterEngine::QdbHelper::ClearPendingJobs()\n{\n  try {\n    (void) mQcl->del(kConversionPendingHashKey);\n  } catch (const std::exception& e) {\n    eos_static_crit(\"msg=\\\"Error encountered while clearing the list of \"\n                    \"pending jobs\\\" emsg=\\\"%s\\\"\", e.what());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Remove conversion job by id from the pending jobs queue in QuarkDB\n//------------------------------------------------------------------------------\nbool\nConverterEngine::QdbHelper::RemovePendingJob(const eos::IFileMD::id_t& id)\n{\n  try {\n    return mQHashPending.hdel(std::to_string(id));\n  } catch (const std::exception& e) {\n    eos_static_crit(\"msg=\\\"Error encountered while trying to delete \"\n                    \"pending conversion job\\\" emsg=\\\"%s\\\"\", e.what());\n  }\n\n  return false;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/convert/ConverterEngine.hh",
    "content": "//------------------------------------------------------------------------------\n// File: ConverterEngine.hh\n// Author: Mihai Patrascoiu - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include <map>\n#include \"common/Logging.hh\"\n#include \"common/ObserverMgr.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/convert/ConversionJob.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"namespace/ns_quarkdb/qclient/include/qclient/QClient.hh\"\n#include \"namespace/ns_quarkdb/qclient/include/qclient/structures/QHash.hh\"\n#include <XrdOuc/XrdOucCallBack.hh>\n\nEOSMGMNAMESPACE_BEGIN\n\n//! Forward declaration\nclass ConversionJob;\nenum class ConversionJobStatus;\n//------------------------------------------------------------------------------\n//! @brief Class running the conversion threadpool\n//------------------------------------------------------------------------------\nclass ConverterEngine : public eos::common::LogId\n{\npublic:\n  using JobInfoT =\n    std::tuple<eos::IFileMD::id_t, std::string, std::shared_ptr<XrdOucCallBack>>;\n  using JobFailedT = std::pair<std::string, std::string>;\n  using JobStatusT = ConversionJobStatus;\n  using ObserverT = eos::common::ObserverMgr<JobStatusT, std::string>;\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ConverterEngine(const eos::QdbContactDetails& qdb_details) :\n    mQdbHelper(qdb_details), mIsRunning(false), mFailed(0),\n    mThreadPool(std::thread::hardware_concurrency(), 100,\n                10, 5, 3, \"converter\"),\n    mMaxQueueSize(1000), mTimestamp(),\n    mObserverMgr(std::make_unique<ObserverT>(4))\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~ConverterEngine()\n  {\n    Stop();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Start converter thread\n  //----------------------------------------------------------------------------\n  void Start();\n\n  //----------------------------------------------------------------------------\n  //! Stop converter thread and all running conversion jobs\n  //----------------------------------------------------------------------------\n  void Stop();\n\n  //----------------------------------------------------------------------------\n  //! Schedule a conversion job with the given ID and conversion info.\n  //!\n  //! @param id the job id\n  //! @param conversion_info the conversion info string\n  //! @param err_msg error message in case of failure\n  //! @param callback shared pointer to a callback object - default is nullptr\n  //!\n  //! @return true if scheduling succeeded, false otherwise\n  //----------------------------------------------------------------------------\n  bool ScheduleJob(const eos::IFileMD::id_t& id,\n                   const std::string& conversion_info,\n                   std::string& err_msg,\n                   std::shared_ptr<XrdOucCallBack> callback = nullptr);\n\n  //----------------------------------------------------------------------------\n  //! Get running state info\n  //----------------------------------------------------------------------------\n  inline bool IsRunning() const\n  {\n    return mIsRunning;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get thread pool info\n  //----------------------------------------------------------------------------\n  inline std::string GetThreadPoolInfo() const\n  {\n    return mThreadPool.GetInfo();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get number of running jobs\n  //----------------------------------------------------------------------------\n  inline uint64_t NumRunningJobs() const\n  {\n    eos::common::RWMutexReadLock rlock(mJobsMutex);\n    return mJobsRunning.size();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get number of pending jobs stored in QuarkDB\n  //----------------------------------------------------------------------------\n  inline uint64_t NumPendingJobs()\n  {\n    return mPendingJobs.size();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get number of failed jobs stored in QuarkDB\n  //----------------------------------------------------------------------------\n  inline uint64_t NumFailedJobs()\n  {\n    return mFailed.load();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get list of pending jobs\n  //!\n  //! @return list of pending jobs\n  //----------------------------------------------------------------------------\n  inline std::vector<JobInfoT> GetPendingJobs()\n  {\n    return mQdbHelper.GetPendingJobs();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Clear list of pending jobs\n  //----------------------------------------------------------------------------\n  void ClearPendingJobs()\n  {\n    return mQdbHelper.ClearPendingJobs();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get Observer Mgr, useful for other threads to register observers\n  //----------------------------------------------------------------------------\n  auto getObserverMgr()\n  {\n    return mObserverMgr.get();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Apply global configuration relevant for the converter\n  //----------------------------------------------------------------------------\n  void ApplyConfig();\n\n  //----------------------------------------------------------------------------\n  //! Make configuration change\n  //!\n  //! @param key input key\n  //! @param val input value\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool SetConfig(const std::string& key, const std::string& val);\n\n  //----------------------------------------------------------------------------\n  //! Serialize converter configuration\n  //!\n  //! @return string representing converter configuration\n  //----------------------------------------------------------------------------\n  std::string SerializeConfig() const;\n\nprivate:\n  struct QdbHelper {\n    //--------------------------------------------------------------------------\n    //! Constructor\n    //--------------------------------------------------------------------------\n    QdbHelper(const eos::QdbContactDetails& qdb_details)\n    {\n      mQcl = std::make_unique<qclient::QClient>(qdb_details.members,\n             qdb_details.constructOptions());\n      mQHashPending = qclient::QHash(*mQcl, kConversionPendingHashKey);\n    }\n\n    //--------------------------------------------------------------------------\n    //! Returns a QuarkDB iterator for the pending jobs hash.\n    //!\n    //! @return the pending jobs hash iterator\n    //--------------------------------------------------------------------------\n    inline qclient::QHash::Iterator PendingJobsIterator()\n    {\n      return mQHashPending.getIterator(cBatchSize, \"0\");\n    }\n\n    //--------------------------------------------------------------------------\n    //! Get list of pending jobs\n    //!\n    //! @return list of pending jobs\n    //--------------------------------------------------------------------------\n    std::vector<ConverterEngine::JobInfoT> GetPendingJobs();\n\n    //--------------------------------------------------------------------------\n    //! Clear list of pending jobs\n    //--------------------------------------------------------------------------\n    void ClearPendingJobs();\n\n    //--------------------------------------------------------------------------\n    //! Add conversion job to the queue of pending jobs in QuarkDB.\n    //!\n    //! @param jobinfo the pending conversion job details\n    //! @return true if operation succeeded, false otherwise\n    //--------------------------------------------------------------------------\n    bool AddPendingJob(const JobInfoT& jobinfo);\n\n    //--------------------------------------------------------------------------\n    //! Remove conversion job by id from the pending jobs queue in QuarkDB.\n    //!\n    //! @param id the conversion job id to remove\n    //! @return true if operation succeeded, false otherwise\n    //--------------------------------------------------------------------------\n    bool RemovePendingJob(const eos::IFileMD::id_t& id);\n\n    //! QDB conversion hash keys\n    const std::string kConversionPendingHashKey = \"eos-conversion-jobs-pending\";\n    static constexpr unsigned int cBatchSize{1000}; ///< Batch size constant\n\n  private:\n    std::unique_ptr<qclient::QClient> mQcl; ///< Internal QClient object\n    qclient::QHash mQHashPending; ///< QDB pending jobs hash object\n  };\n\n  //----------------------------------------------------------------------------\n  //! Converter engine thread monitoring\n  //!\n  //! @param assistant converter thread\n  //----------------------------------------------------------------------------\n  void Convert(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Signal all conversion jobs to stop\n  //----------------------------------------------------------------------------\n  void JoinAllConversionJobs();\n\n  //----------------------------------------------------------------------------\n  //! Method to collect and queue pending jobs from the QDB backend\n  //----------------------------------------------------------------------------\n  void PopulatePendingJobs();\n\n  //----------------------------------------------------------------------------\n  //! Cleanup handle after a job is run - remove the job from the list of\n  //! pending jobs and clean up the conversion file in /eos/.../proc/conversion\n  //!\n  //! @param job finished job\n  //----------------------------------------------------------------------------\n  void HandlePostJobRun(std::shared_ptr<ConversionJob> job);\n\n  //----------------------------------------------------------------------------\n  //! Store configuration\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool StoreConfig();\n\n  AssistedThread mThread; ///< Thread controller object\n  QdbHelper mQdbHelper; ///< QuarkDB helper object\n  std::atomic<bool> mIsRunning; ///< Mark if converter is running\n  std::atomic<uint64_t> mFailed; ///< Number of failed jobs\n  eos::common::ThreadPool mThreadPool; ///< Thread pool for conversion jobs\n  std::atomic<unsigned int> mMaxQueueSize; ///< Max submitted queue size\n  //! Timestamp of last jobs request\n  std::chrono::steady_clock::time_point mTimestamp;\n  //! Collection of running conversion jobs\n  std::map<eos::IFileMD::id_t, std::shared_ptr<ConversionJob>> mJobsRunning;\n  //! RWMutex protecting the jobs collections\n  mutable eos::common::RWMutex mJobsMutex;\n  //! Pending jobs in memory\n  eos::common::ConcurrentQueue<JobInfoT> mPendingJobs;\n  std::unique_ptr<ObserverT> mObserverMgr;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/devices/Devices.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Devices.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Constants.hh\"\n#include \"common/Logging.hh\"\n#include \"common/RWMutex.hh\"\n#include \"common/Path.hh\"\n#include \"common/utils/BackOffInvoker.hh\"\n#include \"mgm/devices/Devices.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"common/json/Json.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n\n//------------------------------------------------------------------------------\n// Run asynchronous devices thread\n//------------------------------------------------------------------------------\nbool\nDevices::Start()\n{\n  mThread.reset(&Devices::Recorder, this);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Cancel the asynchronous devices thread\n//------------------------------------------------------------------------------\nvoid\nDevices::Stop()\n{\n  mThread.join();\n}\n\n//------------------------------------------------------------------------------\n// Eternal thread registering device information, which allows to detect\n// devices which have been removed\n//------------------------------------------------------------------------------\nvoid\nDevices::Recorder(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"Devices\");\n  time_t snoozetime = 900;\n\n  if (getenv(\"EOS_MGM_DEVICES_PUBLISHING_INTERVAL\")) {\n    auto rtime = std::atoi(getenv(\"EOS_MGM_DEVICES_PUBLISHING_INTERVAL\"));\n\n    if (rtime == 0 || rtime > 86400) {\n      rtime = 900;\n    }\n  }\n\n  gOFS->WaitUntilNamespaceIsBooted(assistant);\n\n  if (assistant.terminationRequested()) {\n    return;\n  }\n\n  assistant.wait_for(std::chrono::seconds(15));\n  eos::common::BackOffInvoker backoff_logger;\n\n  while (!assistant.terminationRequested()) {\n    // Every now and then we wake up\n    backoff_logger.invoke([&snoozetime]() {\n      eos_static_info(\"msg=\\\"devices thread\\\" snooze-time=%llu\",\n                      snoozetime);\n    });\n\n    if (!gOFS->mMaster->IsMaster()) {\n      assistant.wait_for(std::chrono::seconds(snoozetime));\n      continue;\n    }\n\n    // get the latest info\n    Extract();\n    // store in the namespace\n    Store();\n\n    for (int i = 0; i < snoozetime / 1; i++) {\n      if (assistant.terminationRequested()) {\n        eos_static_info(\"%s\", \"msg=\\\"devices thread exiting\\\"\");\n        return;\n      }\n\n      assistant.wait_for(std::chrono::seconds(1));\n    }\n  }\n\n  eos_static_info(\"%s\", \"msg=\\\"devices thread exiting\\\"\");\n}\n\n// Function extracting device information either on request or by the background thread\nvoid\nDevices::Extract()\n{\n  gOFS->MgmStats.Add(\"Devices::Extract\", 0, 0, 1);\n  json_map_t jm = std::make_shared<json_map>();\n  space_map_t sp = std::make_shared<space_map>();\n  smart_map_t sm = std::make_shared<smart_map>();\n  std::set<uint64_t> fsids;\n  {\n    // get all the filesystem which are currently visible quickly\n    eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n\n    for (auto it = FsView::gFsView.mSpaceView.begin();\n         it != FsView::gFsView.mSpaceView.end(); ++it) {\n      // loop over all filesystems\n      for (auto fsit = FsView::gFsView.mIdView.begin();\n           fsit != FsView::gFsView.mIdView.end(); ++fsit) {\n        FileSystem* fs = fsit->second;\n\n        if (fs->GetSpace() != it->first) {\n          // only look at the current space\n          continue;\n        }\n\n        fsids.insert(fs->GetId());\n        (*sp)[fs->GetId()] = fs->GetSpace();\n      }\n    }\n  }\n\n  // loop over the filesystems and take short locks to extract\n  for (auto it = fsids.begin(); it != fsids.end(); ++it) {\n    uint64_t id = *it;\n    {\n      eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n      FileSystem* fs = FsView::gFsView.mIdView.lookupByID(id);\n\n      if (!fs) {\n        // skip this disappeared\n        continue;\n      }\n\n      // store the compressed maps\n      (*jm)[id] = fs->GetString(\"stat.health.z64smart\");\n      (*sm)[id] = fs->GetString(\"stat.health\");\n    }\n    // avoid tight locking loops\n    std::this_thread::sleep_for(std::chrono::milliseconds(1));\n  }\n\n  // decompress without any lock\n  for (auto it = (*jm).begin(); it != (*jm).end(); ++it) {\n    std::string compressedjson = it->second;\n    std::string ojson;\n    bool done = eos::common::SymKey::ZDeBase64(compressedjson, ojson);\n\n    if (!done) {\n      eos_static_err(\"msg=\\\"failed to decompress JSON smart info from fsid=%lu\\\"\",\n                     it->first);\n      it->second = \"\";\n    } else {\n      it->second = ojson;\n    }\n  }\n\n  lastExtraction = time(NULL);\n  // swap the new map with the current one\n  setJson(jm);\n  setSpaceMap(sp);\n  setSmartMap(sm);\n}\n\nvoid\nDevices::Store()\n{\n  gOFS->MgmStats.Add(\"Devices::Store\", 0, 0, 1);\n  auto jinfo = getJson();\n  auto sminfo = getSmartMap();\n\n  for (auto it = jinfo->begin(); it != jinfo->end(); ++it) {\n    std::string storagepath = mDevicesPath;\n    storagepath += \"/\";\n    std::string smartstatus = \"unknown\";\n\n    if (sminfo->count(it->first)) {\n      smartstatus = (*sminfo)[it->first];\n    }\n\n    std::string serial;\n    {\n      Json::Value root;\n      std::string errs;\n      Json::CharReaderBuilder jsonReaderBuilder;\n      std::unique_ptr<Json::CharReader> const reader(\n        jsonReaderBuilder.newCharReader());\n      const std::string& ojson = it->second;\n\n      if (reader->parse(ojson.c_str(), ojson.c_str() + ojson.size(), &root, &errs)) {\n        try {\n          serial     = root.isMember(\"serial_number\") ? root[\"serial_number\"].asString() :\n                       \"\";\n        } catch (Json::Exception const&) {\n        }\n      }\n    }\n\n    if (serial.empty()) {\n      continue;\n    }\n\n    storagepath += serial; // serial number\n    storagepath += \".\";\n    storagepath += std::to_string(it->first); // fsid\n    eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, storagepath.c_str());\n    eos::MDLocking::FileWriteLockPtr fmdLock;\n    eos::IFileMDPtr fmd = nullptr;\n\n    try {\n      fmd = gOFS->eosView->getFile(storagepath.c_str());\n      fmdLock = eos::MDLocking::writeLock(fmd.get());\n      errno = 0;\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      eos_static_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                       e.getErrno(), e.getMessage().str().c_str());\n    }\n\n    if (!fmd) {\n      // if it does not exist, create it\n      try {\n        fmd = gOFS->eosView->createFile(storagepath.c_str(), 0, 0);\n        fmdLock = eos::MDLocking::writeLock(fmd.get());\n        fmd->setMTimeNow();\n        fmd->setCTimeNow();\n        eos::IFileMD::ctime_t mtime;\n        fmd->getMTime(mtime);\n        char btime[256];\n        snprintf(btime, sizeof(btime), \"%lu.%lu\", mtime.tv_sec, mtime.tv_nsec);\n        fmd->setAttribute(\"sys.eos.btime\", btime);\n        errno = 0;\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        eos_static_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                         e.getErrno(), e.getMessage().str().c_str());\n      }\n    }\n\n    // if it exists now, store the latest json and update the mtime\n    if (fmd) {\n      fmd->setAttribute(\"sys.smart.json\", it->second);\n      fmd->setAttribute(\"sys.smart.status\", smartstatus);\n      fmd->setMTimeNow();\n      fmdLock.reset(nullptr);\n      gOFS->eosView->updateFileStore(fmd.get());\n    }\n  }\n}\n\n\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/devices/Devices.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Devices.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/****************************A********************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_DEVICES__HH__\n#define __EOSMGM_DEVICES__HH__\n\n#include \"mgm/Namespace.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/Timing.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <sys/types.h>\n\nclass XrdOucErrInfo;\n\nEOSMGMNAMESPACE_BEGIN\n\n/**\n * @file   Devices.hh\n *\n * @brief  This class implements the thread \n * storing regulary device information to the proc filesystem\n */\n\nclass Devices\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Default Constructor - use it to run the Devices thread by callign Start\n  //! afterwards.\n  //----------------------------------------------------------------------------\n\n  Devices() { }\n\n  ~Devices()\n  {\n    Stop();\n  }\n\n  void SetDevicesPath(const char* procpath) {\n    mDevicesPath = procpath;\n  }\n  \n  /* Start the devices thread \n   */\n  bool Start();\n\n  /* Stop the devices thread\n   */\n  void Stop();\n\n  typedef std::map<uint64_t, std::string> json_map;\n  typedef std::shared_ptr<json_map> json_map_t;\n  typedef std::map<uint64_t, std::string> space_map;\n  typedef std::shared_ptr<space_map> space_map_t;\n  typedef std::map<uint64_t, std::string> smart_map;\n  typedef std::shared_ptr<smart_map> smart_map_t;\n  \n  space_map_t getSpaceMap() {\n    std::unique_lock<std::mutex> scope_lock(fsJsonMutex);\n    return spaceMap;\n  }\n  \n  json_map_t getJson() {\n    std::unique_lock<std::mutex> scope_lock(fsJsonMutex);\n    return fsJson;\n  }\n\n  smart_map_t getSmartMap() {\n    std::unique_lock<std::mutex> scope_lock(fsJsonMutex);\n    return smartMap;\n  }\n  \n  std::string getLocalExtractionTime() const {\n    time_t t = lastExtraction;\n    return eos::common::Timing::ltime(t);\n  }\n\n  time_t getExtractionTime() const {\n    return lastExtraction;\n  }\n  \n  void Extract(); // extract from MQ messaging infos\n  \nprivate:\n  AssistedThread mThread; ///< Thread doing the device extraction\n  std::string mDevicesPath;\n  std::mutex fsJsonMutex;\n  json_map_t fsJson; // contains fsid->json info\n  space_map_t spaceMap; // contains fsid->space info\n  smart_map_t smartMap; // contains fsid->smart status\n  std::atomic<time_t> lastExtraction;\n  \n  void Recorder(ThreadAssistant& assistant) noexcept;\n  void Store(); // store into persistent namespace proc directory\n  void setJson(json_map_t newjson) {\n    std::unique_lock<std::mutex> scope_lock(fsJsonMutex);\n    fsJson = newjson;\n  }\n  void setSpaceMap(space_map_t newspacemap) {\n    std::unique_lock<std::mutex> scope_lock(fsJsonMutex);\n    spaceMap = newspacemap;\n  }\n  void setSmartMap(space_map_t newsmartmap) {\n    std::unique_lock<std::mutex> scope_lock(fsJsonMutex);\n    smartMap = newsmartmap;\n  }\n};\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/drain/DrainFs.cc",
    "content": "//------------------------------------------------------------------------------\n// @file DrainFs.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/drain/DrainFs.hh\"\n#include \"mgm/drain/DrainTransferJob.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"common/table_formatter/TableFormatterBase.hh\"\n#include \"common/ThreadPool.hh\"\n#include \"namespace/interface/IView.hh\"\n#include <sstream>\n\nEOSMGMNAMESPACE_BEGIN\n\nusing namespace std::chrono;\nconstexpr std::chrono::seconds DrainFs::sRefreshTimeout;\nconstexpr std::chrono::seconds DrainFs::sStallTimeout;\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nDrainFs::DrainFs(eos::common::ThreadPool& thread_pool, eos::IFsView* fs_view,\n                 eos::common::FileSystem::fsid_t src_fsid,\n                 eos::common::FileSystem::fsid_t dst_fsid):\n  mNsFsView(fs_view), mFsId(src_fsid), mTargetFsId(dst_fsid),\n  mStatus(eos::common::DrainStatus::kNoDrain), mDidRerun(false),\n  mDrainStop(false), mMaxJobs(10), mDrainPeriod(0), mMinTxRate(25),\n  mThreadPool(thread_pool), mTotalFiles(0ull), mPending(0ull),\n  mLastPending(0ull), mLastProgressTime(steady_clock::now()),\n  mLastUpdateTime(steady_clock::now())\n{}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nDrainFs::~DrainFs()\n{\n  eos_static_debug(\"msg=\\\"fsid=%u destroying fs drain object\", mFsId);\n}\n\n//------------------------------------------------------------------------------\n// Get space defined drain variables\n//------------------------------------------------------------------------------\nvoid\nDrainFs::GetSpaceConfiguration(const std::string& space_name)\n{\n  if (!space_name.empty() && FsView::gFsView.mSpaceView.count(space_name)) {\n    auto space = FsView::gFsView.mSpaceView[space_name];\n\n    if (space) {\n      std::string value = space->GetConfigMember(\"drainer.fs.ntx\");\n\n      if (!value.empty()) {\n        try {\n          mMaxJobs.store(std::stoul(value));\n          eos_static_debug(\"msg=\\\"per fs max parallel jobs=%u\\\"\",\n                           mMaxJobs.load());\n        } catch (...) {\n          eos_static_warning(\"msg=\\\"failed to convert drainer.fs.ntx\\\" \"\n                             \"space=\\\"%s\\\"\", space_name.c_str());\n        }\n      }\n\n      value = space->GetConfigMember(\"drainer.tx.minrate\");\n\n      if (!value.empty()) {\n        try {\n          mMinTxRate.store(std::stoull(value));\n          eos_static_debug(\"msg=\\\"drain transfer min rate set to %lluMB/s\\\"\",\n                           mMinTxRate.load());\n        } catch (...) {\n          eos_static_warning(\"msg=\\\"failed to convert drainer.tx.minrate\\\" \"\n                             \"space=\\\"%s\\\"\", space_name.c_str());\n        }\n      }\n    } else {\n      eos_static_warning(\"msg=\\\"space %s not yet initialized\\\"\",\n                         space_name.c_str());\n    }\n  } else {\n    // Use some sensible default values for testing\n    mMaxJobs = 2;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Method draining the file system\n//------------------------------------------------------------------------------\nDrainFs::State\nDrainFs::DoIt()\n{\n  eos_notice(\"msg=\\\"start draining\\\" fsid=%d\", mFsId);\n  WaitUntilNamespaceIsBooted();\n\n  if (mDrainStop) {\n    eos_err(\"msg=\\\"drain stopped while waiting for the namespace boot\\\" \"\n            \"fsid=%lu\", mFsId);\n    return State::Failed;\n  }\n\n  mTotalFiles = mNsFsView->getNumFilesOnFs(mFsId);\n\n  if (mTotalFiles == 0) {\n    SuccessfulDrain();\n    return State::Done;\n  }\n\n  if (!PrepareFs()) {\n    return State::Failed;\n  }\n\n  State state = State::Running;\n\n  // Loop to drain the files\n  while (!mDrainStop && (state != State::Done) && (state != State::Failed)) {\n    mTotalFiles = mNsFsView->getNumFilesOnFs(mFsId);\n    mPending = mTotalFiles;\n\n    for (auto it_fid = mNsFsView->getStreamingFileList(mFsId);\n         it_fid && it_fid->valid(); /* no progress */) {\n      if (NumRunningJobs() <= mMaxJobs) {\n        std::shared_ptr<DrainTransferJob> job {\n          new DrainTransferJob(it_fid->getElement(), mFsId, mTargetFsId)};\n        job->SetMinTransferRate(mMinTxRate);\n\n        if (!gOFS->mFidTracker.AddEntry(it_fid->getElement(), TrackerType::Drain)) {\n          job->ReportError(SSTR(\"msg=\\\"skip currently scheduled drain\\\" \"\n                                \"fxid=\" << std::hex << it_fid->getElement()));\n          eos::common::RWMutexWriteLock wr_lock(mJobsMutex);\n          mJobsFailed.insert(job);\n        } else {\n          {\n            // Add job to the list of running ones\n            eos::common::RWMutexWriteLock wr_lock(mJobsMutex);\n            mJobsRunning[it_fid->getElement()] = job;\n          }\n          mThreadPool.PushTask<void>([job, this] {\n            job->UpdateMgmStats();\n            job->DoIt();\n            job->UpdateMgmStats();\n            this->UpdateFinishedJob(job->GetFileIdentifier());\n          });\n        }\n\n        // Advance to the next file id to be drained\n        it_fid->next();\n        --mPending;\n      } else {\n        std::this_thread::sleep_for(seconds(1));\n      }\n\n      state = UpdateProgress();\n\n      if (mDrainStop || (state != State::Running)) {\n        break;\n      }\n    }\n\n    if (mDrainStop) {\n      break;\n    }\n\n    while (!mDrainStop && (state == State::Running)) {\n      state = UpdateProgress();\n\n      // Ensure we do a refresh of the files to drain if there are no more\n      // running jobs so that we don't get stuck in stalling forever.\n      if (NumRunningJobs() == 0) {\n        break;\n      }\n    }\n  }\n\n  if (mDrainStop || NumRunningJobs()) {\n    StopJobs();\n    ResetCounters();\n    state = State::Failed;\n  } else {\n    if (state == State::Rerun) {\n      FailedDrain();\n      state = State::Failed;\n    }\n  }\n\n  eos_notice(\"msg=\\\"finished draining\\\" fsid=%d state=%i\", mFsId, state);\n  return state;\n}\n\n//----------------------------------------------------------------------------\n// Update internal structures one a job is finished\n//----------------------------------------------------------------------------\nvoid\nDrainFs::UpdateFinishedJob(const eos::IFileMD::id_t fid)\n{\n  gOFS->mFidTracker.RemoveEntry(fid);\n  eos::common::RWMutexWriteLock wr_lock(mJobsMutex);\n  auto it = mJobsRunning.find(fid);\n\n  if (it != mJobsRunning.end()) {\n    auto job = it->second;\n\n    if (job->GetStatus() == DrainTransferJob::Status::Failed) {\n      mJobsFailed.insert(job);\n    }\n\n    (void) mJobsRunning.erase(it);\n  }\n}\n\n//----------------------------------------------------------------------------\n// Mark file system drain as successful\n//----------------------------------------------------------------------------\nvoid\nDrainFs::SuccessfulDrain()\n{\n  eos_notice(\"msg=\\\"successful drain\\\" fsid=%d\", mFsId);\n  eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n  FileSystem* fs = FsView::gFsView.mIdView.lookupByID(mFsId);\n\n  if (fs) {\n    mStatus = eos::common::DrainStatus::kDrained;\n    eos::common::FileSystemUpdateBatch batch;\n    batch.setDrainStatusLocal(mStatus);\n    batch.setLongLongLocal(\"local.drain.progress\", 100);\n    batch.setLongLongLocal(\"local.drain.bytesleft\", 0);\n    batch.setLongLongLocal(\"local.drain.timeleft\", 0);\n    batch.setLongLongLocal(\"local.drain.failed\", 0);\n    batch.setLongLongLocal(\"local.drain.files\", 0);\n\n    if (!gOFS->Shutdown) {\n      // If drain done and the system is not shutting down then set the\n      // file system to \"empty\" state\n      batch.setStringDurable(\"configstatus\", \"empty\");\n      FsView::gFsView.StoreFsConfig(fs);\n    }\n\n    fs->applyBatch(batch);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Mark file system drain as failed\n//------------------------------------------------------------------------------\nvoid\nDrainFs::FailedDrain()\n{\n  eos_notice(\"msg=\\\"failed drain\\\" fsid=%d\", mFsId);\n  eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n  FileSystem* fs = FsView::gFsView.mIdView.lookupByID(mFsId);\n\n  if (fs) {\n    mStatus = eos::common::DrainStatus::kDrainFailed;\n    eos::common::FileSystemUpdateBatch batch;\n    batch.setDrainStatusLocal(mStatus);\n    batch.setLongLongLocal(\"local.drain.timeleft\", 0);\n    batch.setLongLongLocal(\"local.drain.progress\", 100);\n    batch.setLongLongLocal(\"local.drain.failed\", NumFailedJobs());\n    fs->applyBatch(batch);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Stop ongoing drain jobs\n//------------------------------------------------------------------------------\nvoid\nDrainFs::StopJobs()\n{\n  eos_notice(\"msg=\\\"stopping all running drain jobs\\\" fsid=%d\", mFsId);\n  {\n    // Signal all drain jobs to stop/cancel\n    eos::common::RWMutexReadLock rd_lock(mJobsMutex);\n\n    for (const auto& pair : mJobsRunning) {\n      auto& job = pair.second;\n\n      if (job->GetStatus() == DrainTransferJob::Status::Running) {\n        job->Cancel();\n      }\n    }\n  }\n  {\n    // Wait for drain jobs to cancel\n    eos::common::RWMutexWriteLock wr_lock(mJobsMutex);\n\n    while (!mJobsRunning.empty()) {\n      const eos::IFileMD::id_t fid = mJobsRunning.begin()->first;\n      auto job = mJobsRunning.begin()->second;\n      wr_lock.Release();\n\n      while ((job->GetStatus() == DrainTransferJob::Status::Running) ||\n             (job->GetStatus() == DrainTransferJob::Status::Ready)) {\n        std::this_thread::sleep_for(milliseconds(10));\n      }\n\n      // Also clean them up form the tracker\n      gOFS->mFidTracker.RemoveEntry(fid);\n      wr_lock.Grab(mJobsMutex);\n      mJobsRunning.erase(fid);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Prepare the file system for drain i.e. delay the start by the configured\n// amount of time, set the status etc.\n//------------------------------------------------------------------------------\nbool\nDrainFs::PrepareFs()\n{\n  std::string space_name;\n  {\n    eos_info(\"msg=\\\"setting the drain prepare status\\\" fsid=%i\", mFsId);\n    eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(mFsId);\n\n    if (!fs) {\n      eos_notice(\"msg=\\\"removed during prepare\\\" fsid=%d\", mFsId);\n      return false;\n    }\n\n    mStatus = eos::common::DrainStatus::kDrainPrepare;\n    eos::common::FileSystemUpdateBatch batch;\n    batch.setLongLongLocal(\"local.drain.bytesleft\", 0);\n    batch.setLongLongLocal(\"local.drain.files\", 0);\n    batch.setLongLongLocal(\"local.drain.failed\", 0);\n    batch.setLongLongLocal(\"local.drain.timeleft\", 0);\n    batch.setLongLongLocal(\"local.drain.progress\", 0);\n    batch.setDrainStatusLocal(mStatus);\n    fs->applyBatch(batch);\n    mDrainPeriod = seconds(fs->GetLongLong(\"drainperiod\"));\n    eos::common::FileSystem::fs_snapshot_t drain_snapshot;\n    fs->SnapShotFileSystem(drain_snapshot, false);\n    space_name = drain_snapshot.mSpace;\n  }\n  mDrainStart = steady_clock::now();\n  mDrainEnd = mDrainStart + mDrainPeriod;\n  // Wait 60 seconds or the service delay time indicated by Master\n  size_t kLoop = gOFS->mMaster->GetServiceDelay();\n\n  if (!kLoop) {\n    kLoop = 60;\n  }\n\n  for (size_t k = 0; k < kLoop; ++k) {\n    std::this_thread::sleep_for(seconds(1));\n    {\n      eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n      FileSystem* entry = FsView::gFsView.mIdView.lookupByID(mFsId);\n\n      if (!entry) {\n        eos_err(\"msg=\\\"removed during drain prepare\\\" fsid=%d\", mFsId);\n        return false;\n      }\n\n      entry->SetLongLongLocal(\"local.drain.timeleft\", kLoop - 1 - k);\n    }\n\n    if (mDrainStop) {\n      ResetCounters();\n      return false;\n    }\n  }\n\n  // Mark file system as draining\n  eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n  FileSystem* fs = FsView::gFsView.mIdView.lookupByID(mFsId);\n\n  if (!fs) {\n    eos_notice(\"msg=\\\"removed during drain\\\" fsid=%d\", mFsId);\n    return false;\n  }\n\n  GetSpaceConfiguration(space_name);\n  mStatus = eos::common::DrainStatus::kDraining;\n  eos::common::FileSystemUpdateBatch batch;\n  batch.setDrainStatusLocal(mStatus);\n  batch.setLongLongLocal(\"local.drain.files\", mTotalFiles);\n  batch.setLongLongLocal(\"local.drain.failed\", 0);\n  batch.setLongLongLocal(\"local.drain.bytesleft\",\n                         fs->GetLongLong(\"stat.statfs.usedbytes\"));\n  fs->applyBatch(batch);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Update progress of the drain\n//------------------------------------------------------------------------------\nDrainFs::State\nDrainFs::UpdateProgress()\n{\n  bool is_expired = false;\n  auto now = steady_clock::now();\n\n  if (mLastPending != mPending) {\n    mLastPending = mPending;\n    mLastProgressTime = now;\n  } else {\n    std::this_thread::sleep_for(seconds(1));\n  }\n\n  auto duration = now - mLastProgressTime;\n  bool is_stalled = (duration_cast<seconds>(duration).count() >\n                     sStallTimeout.count());\n  eos_static_debug(\"msg=\\\"fsid=%d, timestamp=%llu, last_progress=%llu, is_stalled=%i, \"\n                   \"total_files=%llu, last_pending=%llu, pending=%llu, running=%llu, \"\n                   \"failed=%llu\\\"\", mFsId,\n                   duration_cast<milliseconds>(now.time_since_epoch()).count(),\n                   duration_cast<milliseconds>(mLastProgressTime.time_since_epoch()).count(),\n                   is_stalled, mTotalFiles, mLastPending, mPending, NumRunningJobs(),\n                   NumFailedJobs());\n\n  // Check if drain expired\n  if (mDrainPeriod.count() && (mDrainEnd < now)) {\n    eos_warning(\"msg=\\\"drain expired\\\" fsid=%d\", mFsId);\n    is_expired = true;\n  }\n\n  // Update drain display variables\n  if (is_stalled || is_expired || (mLastProgressTime == now)) {\n    eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(mFsId);\n\n    if (!fs) {\n      eos_err(\"msg=\\\"removed during drain\\\" fsid=%d\", mFsId);\n      return State::Failed;\n    }\n\n    if (is_expired) {\n      mStatus = eos::common::DrainStatus::kDrainExpired;\n      common::FileSystemUpdateBatch batch;\n      batch.setLongLongLocal(\"local.drain.timeleft\", 0);\n      batch.setLongLongLocal(\"local.drain.files\", mPending);\n      batch.setDrainStatusLocal(mStatus);\n      fs->applyBatch(batch);\n      return State::Failed;\n    }\n\n    common::FileSystemUpdateBatch batch;\n\n    if (is_stalled) {\n      if (mStatus != eos::common::DrainStatus::kDrainStalling) {\n        mStatus = eos::common::DrainStatus::kDrainStalling;\n        batch.setDrainStatusLocal(mStatus);\n      }\n    } else {\n      if (mStatus != eos::common::DrainStatus::kDraining) {\n        mStatus = eos::common::DrainStatus::kDraining;\n        batch.setDrainStatusLocal(mStatus);\n      }\n    }\n\n    uint64_t progress = 100ull;\n\n    if (mTotalFiles) {\n      progress = 100.0 * (mTotalFiles - mPending) / mTotalFiles;\n    }\n\n    uint64_t time_left = 99999999999ull;\n\n    if (mDrainEnd > now) {\n      time_left = duration_cast<seconds>(mDrainEnd - now).count();\n    }\n\n    batch.setLongLongLocal(\"local.drain.failed\", NumFailedJobs());\n    batch.setLongLongLocal(\"local.drain.files\", mPending);\n    batch.setLongLongLocal(\"local.drain.progress\", progress);\n    batch.setLongLongLocal(\"local.drain.timeleft\", time_left);\n    batch.setLongLongLocal(\"local.drain.bytesleft\",\n                           fs->GetLongLong(\"stat.statfs.usedbytes\"));\n    fs->applyBatch(batch);\n    eos_static_debug(\"msg=\\\"fsid=%d, update progress\", mFsId);\n  }\n\n  // Sleep for a longer period since nothing moved in the last 10 min\n  if (is_stalled) {\n    std::this_thread::sleep_for(seconds(30));\n  }\n\n  // If we have only failed jobs check if the files still exist. It could also\n  // be that there were new files written while draining was started.\n  if ((mPending == 0) && (NumRunningJobs() == 0)) {\n    uint64_t total_files = mNsFsView->getNumFilesOnFs(mFsId);\n\n    if (total_files == 0) {\n      SuccessfulDrain();\n      return State::Done;\n    } else {\n      if (total_files == NumFailedJobs()) {\n        FailedDrain();\n        return State::Failed;\n      } else {\n        if (mDidRerun) {\n          // If we already did a rerun then we just fail since there might be\n          // ghost entries on the file system i.e. fids registered in the\n          // FileSystem view but without any existing FileMD object.\n          FailedDrain();\n          return State::Failed;\n        } else {\n          mDidRerun = true;\n          eos_info(\"msg=\\\"still %llu files to drain before declaring the file \"\n                   \"system empty\\\" fsid=%lu\", total_files, mFsId);\n          mTotalFiles = total_files;\n          mPending = mTotalFiles;\n          eos::common::RWMutexWriteLock wr_lock(mJobsMutex);\n          mJobsFailed.clear();\n          return State::Rerun;\n        }\n      }\n    }\n  }\n\n  return State::Running;\n}\n\n//------------------------------------------------------------------------------\n// Reset drain counters and status\n//------------------------------------------------------------------------------\nvoid\nDrainFs::ResetCounters()\n{\n  eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n  FileSystem* fs = FsView::gFsView.mIdView.lookupByID(mFsId);\n\n  if (fs) {\n    common::FileSystemUpdateBatch batch;\n    batch.setLongLongLocal(\"local.drain.progress\", 0);\n    batch.setLongLongLocal(\"local.drain.bytesleft\", 0);\n    batch.setLongLongLocal(\"local.drain.timeleft\", 0);\n    batch.setLongLongLocal(\"local.drain.failed\", 0);\n    batch.setLongLongLocal(\"local.drain.files\", 0);\n    batch.setDrainStatusLocal(eos::common::DrainStatus::kNoDrain);\n    fs->applyBatch(batch);\n  }\n\n  mStatus = eos::common::DrainStatus::kNoDrain;\n}\n\n//------------------------------------------------------------------------------\n// Populate table with drain jobs info corresponding to the current fs\n//------------------------------------------------------------------------------\nvoid\nDrainFs::PrintJobsTable(TableFormatterBase& table, bool show_errors,\n                        const std::list<std::string>& itags) const\n{\n  TableData table_data;\n  eos::common::RWMutexReadLock rd_lock(mJobsMutex);\n\n  if (show_errors) {\n    for (const auto& job : mJobsFailed) {\n      table_data.emplace_back();\n      std::list<std::string> data = job->GetInfo(itags);\n\n      for (const auto& elem : data) {\n        table_data.back().push_back(TableCell(elem, \"s\"));\n      }\n    }\n  } else {\n    for (const auto& pair : mJobsRunning) {\n      table_data.emplace_back();\n      auto job = pair.second;\n\n      for (const auto& elem : job->GetInfo(itags)) {\n        table_data.back().push_back(TableCell(elem, \"s\"));\n      }\n    }\n  }\n\n  table.AddRows(table_data);\n}\n\n//------------------------------------------------------------------------------\n// Wait until namespace is booted or drain stop is requested\n//------------------------------------------------------------------------------\nvoid\nDrainFs::WaitUntilNamespaceIsBooted() const\n{\n  while ((gOFS->mNamespaceState != NamespaceState::kBooted) && (!mDrainStop)) {\n    std::this_thread::sleep_for(std::chrono::seconds(1));\n    eos_static_debug(\"msg=\\\"delay drain start until namespace is booted\\\" fsid=%u\",\n                     mFsId);\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/drain/DrainFs.hh",
    "content": "//------------------------------------------------------------------------------\n//! file DrainFS.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"mgm/filesystem/FileSystem.hh\"\n#include \"namespace/interface/IFsView.hh\"\n#include \"common/Logging.hh\"\n#include <thread>\n#include <future>\n#include <list>\n\n//! Forward declarations\nnamespace eos\n{\nnamespace common\n{\nclass ThreadPool;\n}\n}\n\nEOSMGMNAMESPACE_BEGIN\n\nclass DrainTransferJob;\nclass TableFormatterBase;\n\n//------------------------------------------------------------------------------\n//! @brief Class implementing the draining of a filesystem\n//------------------------------------------------------------------------------\nclass DrainFs: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! State of file system drain operation\n  //----------------------------------------------------------------------------\n  enum class State {Done, Failed, Running, Rerun};\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param thread_pool drain thread pool to use for jobs\n  //! @param fs_view file system view\n  //! @param src_fsid filesystem id to drain\n  //! @param dst_fsid file system where to drain\n  //----------------------------------------------------------------------------\n  DrainFs(eos::common::ThreadPool& thread_pool, eos::IFsView* fs_view,\n          eos::common::FileSystem::fsid_t src_fsid,\n          eos::common::FileSystem::fsid_t dst_fsid = 0);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~DrainFs();\n\n  //----------------------------------------------------------------------------\n  //! Signal an ongoing drain to stop\n  //---------------------------------------------------------------------------\n  inline void SignalStop()\n  {\n    mDrainStop = true;\n  }\n\n  //---------------------------------------------------------------------------\n  //! Get drain status\n  //---------------------------------------------------------------------------\n  inline eos::common::DrainStatus GetDrainStatus() const\n  {\n    return mStatus;\n  }\n\n  //---------------------------------------------------------------------------\n  //! Get the file system id\n  //---------------------------------------------------------------------------\n  inline eos::common::FileSystem::fsid_t GetFsId() const\n  {\n    return mFsId;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Method draining the file system\n  //!\n  //! @return status of the file system at the end\n  //----------------------------------------------------------------------------\n  State DoIt();\n\n  //----------------------------------------------------------------------------\n  //! Set future holding the result of the drain\n  //!\n  //! @param future future object\n  //----------------------------------------------------------------------------\n  inline void SetFuture(std::future<State>&& future)\n  {\n    std::swap(mFuture, future);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if drain fs is still running by inspecting the future object\n  //!\n  //! @return true if running, otherwise false\n  //----------------------------------------------------------------------------\n  inline bool IsRunning() const\n  {\n    return (mFuture.valid() && (mFuture.wait_for(std::chrono::seconds(0)) !=\n                                std::future_status::ready));\n  }\n\n  //----------------------------------------------------------------------------\n  //! Populate table with drain jobs info corresponding to the current fs\n  //!\n  //! @param table table objec\n  //! @param show_errors if true then display only failed transfers\n  //! @param itags list of internal tags for info collection\n  //!\n  //! @note: Table header tags must match the order of the internal tags\n  //----------------------------------------------------------------------------\n  void PrintJobsTable(TableFormatterBase& table, bool show_errors,\n                      const std::list<std::string>& itags) const;\n\n  //----------------------------------------------------------------------------\n  //! Method called once a job finishes - responsible for updating the\n  //! internal data structures from the DrainFs object and also tracker info.\n  //!\n  //! @param fid file identifier for which the job has run\n  //----------------------------------------------------------------------------\n  void UpdateFinishedJob(const eos::IFileMD::id_t fid);\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Reset drain counters and status\n  //----------------------------------------------------------------------------\n  void ResetCounters();\n\n  //----------------------------------------------------------------------------\n  //! Get space defined drain variables i.e. number of retires, number of\n  //! transfers per fs, etc.\n  //!\n  //! @param space space name\n  //! @note method must be called with a lock on gFsView.ViewMutex\n  //----------------------------------------------------------------------------\n  void GetSpaceConfiguration(const std::string& space);\n\n  //---------------------------------------------------------------------------\n  //! Prepare the file system for drain i.e. delay the start by the configured\n  //! amount of timem, set the status\n  //!\n  //! @return true if successful, otherwise false\n  //---------------------------------------------------------------------------\n  bool PrepareFs();\n\n  //---------------------------------------------------------------------------\n  //! Update the file system state to draining\n  //!\n  //! @return true if successful, otherwise false\n  //---------------------------------------------------------------------------\n  bool MarkFsDraining();\n\n  //---------------------------------------------------------------------------\n  //! Collect and prepare all the drain jobs\n  //!\n  //! @returns number of drain jobs prepared\n  //---------------------------------------------------------------------------\n  uint64_t CollectDrainJobs();\n\n  //---------------------------------------------------------------------------\n  //! Update progress of the drain\n  //!\n  //! @return progress state of the drain job\n  //---------------------------------------------------------------------------\n  State UpdateProgress();\n\n  //----------------------------------------------------------------------------\n  //! Mark file system drain as failed\n  //----------------------------------------------------------------------------\n  void FailedDrain();\n\n  //---------------------------------------------------------------------------\n  //! Mark file system drain as successful\n  //---------------------------------------------------------------------------\n  void SuccessfulDrain();\n\n  //----------------------------------------------------------------------------\n  //! Stop ongoing drain jobs - must be called by the same thread supervising\n  //! the draining.\n  //----------------------------------------------------------------------------\n  void StopJobs();\n\n  //----------------------------------------------------------------------------\n  //! Wait until namespace is booted or drain stop is requested\n  //----------------------------------------------------------------------------\n  void WaitUntilNamespaceIsBooted() const;\n\n  //----------------------------------------------------------------------------\n  //! Get number of running jobs\n  //----------------------------------------------------------------------------\n  inline uint64_t NumRunningJobs() const\n  {\n    eos::common::RWMutexReadLock rd_lock(mJobsMutex);\n    return mJobsRunning.size();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get number of failed jobs\n  //----------------------------------------------------------------------------\n  inline uint64_t NumFailedJobs() const\n  {\n    eos::common::RWMutexReadLock rd_lock(mJobsMutex);\n    return mJobsFailed.size();\n  }\n\n  constexpr static std::chrono::seconds sRefreshTimeout {60};\n  constexpr static std::chrono::seconds sStallTimeout {600};\n  eos::IFsView* mNsFsView; ///< File system view\n  eos::common::FileSystem::fsid_t mFsId; ///< Drain source fsid\n  eos::common::FileSystem::fsid_t mTargetFsId; /// Drain target fsid\n  eos::common::DrainStatus mStatus;\n  bool mDidRerun; ///< Flag if a rerun was already tried\n  std::atomic<bool> mDrainStop; ///< Flag to cancel an ongoing draining\n  std::atomic<std::uint32_t> mMaxJobs; ///< Max number of drain jobs\n  std::chrono::seconds mDrainPeriod; ///< Allowed time for file system to drain\n  std::atomic<std::uint64_t> mMinTxRate; ///< Min transfer rate per job\n  std::chrono::time_point<std::chrono::steady_clock> mDrainStart;\n  std::chrono::time_point<std::chrono::steady_clock> mDrainEnd;\n  //! Collection of failed drain jobs\n  std::set<std::shared_ptr<DrainTransferJob>> mJobsFailed;\n  //! Collection of running drain jobs\n  std::map<eos::IFileMD::id_t, std::shared_ptr<DrainTransferJob>>\n      mJobsRunning;\n  mutable eos::common::RWMutex mJobsMutex; ///< RW mutex protecting job lists\n  eos::common::ThreadPool& mThreadPool;\n  std::future<State> mFuture;\n  uint64_t mTotalFiles; ///< Total number of files to drain\n  uint64_t mPending; ///< Current num. of pending files to drain\n  uint64_t mLastPending; ///< Previous num. of pending files to drain\n  //! Last timestamp when drain progress was recorded\n  std::chrono::time_point<std::chrono::steady_clock> mLastProgressTime;\n  //! Last timestamp when drain status was updated\n  std::chrono::time_point<std::chrono::steady_clock> mLastUpdateTime;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/drain/DrainTransferJob.cc",
    "content": "//------------------------------------------------------------------------------\n// @file DrainTransferJob.cc\n// @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/drain/DrainTransferJob.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/geotreeengine/GeoTreeEngine.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/proc/proc_fs.hh\"\n#include \"common/SecEntity.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/ns_quarkdb/persistency/MetadataFetcher.hh\"\n#include \"namespace/Prefetcher.hh\"\n\nnamespace\n{\n\n//----------------------------------------------------------------------------\n// Check if container id exists\n//----------------------------------------------------------------------------\nbool ContainerExists(const eos::IContainerMD::id_t cid)\n{\n  eos::IContainerMDPtr cont = nullptr;\n\n  try {\n    cont = gOFS->eosDirectoryService->getContainerMD(cid, 0);\n  } catch (eos::MDException& e) {\n    cont = nullptr;\n  }\n\n  return (cont != nullptr);\n}\n}\n\nEOSMGMNAMESPACE_BEGIN\n\n\n\n\n//------------------------------------------------------------------------------\n// Save error message and set the status accordingly\n//------------------------------------------------------------------------------\nvoid DrainTransferJob::ReportError(const std::string& error)\n{\n  eos_err(\"%s\", error.c_str());\n  mErrorString = error;\n  mStatus = Status::Failed;\n}\n\n//------------------------------------------------------------------------------\n// Execute a thrid-party transfer\n//------------------------------------------------------------------------------\nvoid\nDrainTransferJob::DoIt() noexcept\n{\n  using eos::common::LayoutId;\n  eos_static_info(\"msg=\\\"running job\\\" fsid_src=%i fsid_dst=%i fxid=%08llx\",\n                  mFsIdSource.load(), mFsIdTarget.load(), mFileId.load());\n\n  if (mProgressHandler.ShouldCancel(0)) {\n    ReportError(SSTR(\"msg=\\\"job cancelled before starting\\\" fxid=\"\n                     << eos::common::FileId::Fid2Hex(mFileId)));\n    return;\n  }\n\n  mStatus = Status::Running;\n  FileDrainInfo fdrain;\n\n  try {\n    fdrain = GetFileInfo();\n  } catch (const eos::MDException& e) {\n    // This could be a ghost fid entry still present in the file system map\n    // and we need to also drop it from there\n    std::string out, err;\n    auto root_vid = eos::common::VirtualIdentity::Root();\n    (void) proc_fs_dropghosts(mFsIdSource, {mFileId}, root_vid, out, err);\n    eos_info(\"msg=\\\"drain ghost entry successful\\\" fxid=%s\",\n             eos::common::FileId::Fid2Hex(mFileId).c_str());\n    mStatus = Status::OK;\n    return;\n  }\n\n  // Detect files detached from their parent or with parets containers that\n  // don't exist anymore in the namespace - just delete the file entry\n  if ((fdrain.mProto.cont_id() == 0ull) ||\n      !ContainerExists(fdrain.mProto.cont_id())) {\n    for (const auto fsid : fdrain.mProto.unlink_locations()) {\n      (void) gOFS->DropReplica(mFileId.load(), fsid);\n    }\n\n    for (const auto fsid : fdrain.mProto.locations()) {\n      (void) gOFS->DropReplica(mFileId.load(), fsid);\n    }\n\n    eos_info(\"msg=\\\"drain detached entry successful\\\" fxid=%s\",\n             eos::common::FileId::Fid2Hex(mFileId).c_str());\n    mStatus = Status::OK;\n    return;\n  }\n\n  while (true) {\n    if ((mFsIdTarget == 0ul) && !SelectDstFs(fdrain)) {\n      ReportError(SSTR(\"msg=\\\"failed to select destination file system\\\" fxid=\"\n                       << eos::common::FileId::Fid2Hex(mFileId)));\n      return;\n    }\n\n    // Special case when deadling with 0-size replica files\n    if ((fdrain.mProto.size() == 0) &&\n        (LayoutId::GetLayoutType(fdrain.mProto.layout_id()) ==\n         LayoutId::kReplica)) {\n      mStatus = DrainZeroSizeFile(fdrain);\n      return;\n    }\n\n    // Prepare the TPC copy job\n    std::string log_id = LogId::GenerateLogId();\n    XrdCl::URL url_src = BuildTpcSrc(fdrain, log_id);\n    XrdCl::URL url_dst = BuildTpcDst(fdrain, log_id);\n\n    // When no more sources are available the url_src/dst is empty and\n    // mStatus is properly set already during the build step\n    if (!url_src.IsValid() || !url_dst.IsValid()) {\n      eos_static_err(\"msg=\\\"url invalid\\\" src=\\\"%s\\\" dst=\\\"%s\\\"\",\n                     url_src.GetURL().c_str(), url_dst.GetURL().c_str());\n      return;\n    }\n\n    // If enabled use xrootd connection pool to avoid bottelnecks on the\n    // same physical connection\n    eos::common::XrdConnIdHelper src_id_helper(gOFS->mXrdConnPool, url_src);\n    eos::common::XrdConnIdHelper dst_id_helper(gOFS->mXrdConnPool, url_dst);\n    // Populate the properties map of the transfer\n    XrdCl::PropertyList properties;\n    properties.Set(\"force\", true);\n    properties.Set(\"posc\", false);\n    properties.Set(\"coerce\", false);\n    properties.Set(\"source\", url_src);\n    properties.Set(\"target\", url_dst);\n    properties.Set(\"sourceLimit\", (uint16_t) 1);\n    properties.Set(\"chunkSize\", (uint32_t)(4 * 1024 * 1024));\n    properties.Set(\"parallelChunks\", (uint8_t) 1);\n    properties.Set(\"tpcTimeout\", eos::common::FileId::EstimateTpcTimeout\n                   (fdrain.mProto.size(), mMinTxRate.load()).count());\n\n    // Non-empty files run with TPC only\n    if (fdrain.mProto.size()) {\n      properties.Set(\"thirdParty\", \"only\");\n    }\n\n    // Create the process job\n    XrdCl::PropertyList result;\n    XrdCl::CopyProcess cpy;\n    cpy.AddJob(properties, &result);\n    XrdCl::XRootDStatus prepare_st = cpy.Prepare();\n    eos_info(\"[tpc]: app=%s logid=%s src_url=%s => dst_url=%s\"\n             \" prepare_msg=%s\", mAppTag.c_str(), log_id.c_str(),\n             url_src.GetLocation().c_str(), url_dst.GetLocation().c_str(),\n             prepare_st.ToStr().c_str());\n\n    if (prepare_st.IsOK()) {\n      XrdCl::XRootDStatus tpc_st = cpy.Run(&mProgressHandler);\n\n      if (!tpc_st.IsOK()) {\n        eos_err(\"%s\", SSTR(\"src=\" << url_src.GetLocation().c_str() <<\n                           \" dst=\" << url_dst.GetLocation().c_str() <<\n                           \" logid=\" << log_id <<\n                           \" tpc_err=\" << tpc_st.ToStr()).c_str());\n\n        // If cancellation requested no point in trying other replicas\n        if (mProgressHandler.ShouldCancel(0)) {\n          break;\n        }\n\n        // If file is being written then remove it from the drain tracker so\n        // that it's retried one more time at the end\n        if (tpc_st.errNo == EINPROGRESS) {\n          eos_info(\"msg=\\\"skip file open in progress\\\" logid=%s\", log_id.c_str());\n          break;\n        }\n      } else {\n        eos_info(\"msg=\\\"%s successful\\\" logid=%s fxid=%s\", mAppTag.c_str(),\n                 log_id.c_str(), eos::common::FileId::Fid2Hex(mFileId).c_str());\n        mStatus = Status::OK;\n        return;\n      }\n    } else {\n      eos_err(\"%s\", SSTR(\"msg=\\\"prepare failed\\\" logid=\"\n                         << log_id.c_str()).c_str());\n    }\n  }\n\n  mStatus = Status::Failed;\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Get file metadata info\n//------------------------------------------------------------------------------\nDrainTransferJob::FileDrainInfo\nDrainTransferJob::GetFileInfo() const\n{\n  std::ostringstream oss;\n  FileDrainInfo fdrain;\n  eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, mFileId);\n\n  try {\n    eos::IFileMDPtr fmd = gOFS->eosFileService->getFileMD(mFileId);\n    fdrain.mFullPath = gOFS->eosView->getUri(fmd.get());\n    auto fmdLock = eos::MDLocking::readLock(fmd.get());\n    fdrain.mProto.set_id(fmd->getId());\n    fdrain.mProto.set_layout_id(fmd->getLayoutId());\n    fdrain.mProto.set_cont_id(fmd->getContainerId());\n    fdrain.mProto.set_uid(fmd->getCUid());\n    fdrain.mProto.set_gid(fmd->getCGid());\n    fdrain.mProto.set_size(fmd->getSize());\n    auto xs = fmd->getChecksum();\n    fdrain.mProto.set_checksum(xs.getDataPtr(), xs.getSize());\n    auto vect_locations = fmd->getLocations();\n\n    for (const auto loc : vect_locations) {\n      fdrain.mProto.add_locations(loc);\n    }\n\n    auto vect_unlinked_loc = fmd->getUnlinkedLocations();\n\n    for (const auto uloc : vect_unlinked_loc) {\n      fdrain.mProto.add_unlink_locations(uloc);\n    }\n  } catch (eos::MDException& e) {\n    eos_err(\"%s\", SSTR(\"fxid=\" << eos::common::FileId::Fid2Hex(mFileId)\n                       << \" errno=\" << e.getErrno()\n                       << \" msg=\\\"\" << e.getMessage().str() << \"\\\"\").c_str());\n    throw e;\n  }\n\n  return fdrain;\n}\n\n//------------------------------------------------------------------------------\n// Build TPC source url\n//------------------------------------------------------------------------------\nXrdCl::URL\nDrainTransferJob::BuildTpcSrc(const FileDrainInfo& fdrain,\n                              const std::string& log_id)\n{\n  using namespace eos::common;\n  XrdCl::URL url_src;\n  eos::common::FileSystem::fs_snapshot_t src_snapshot;\n  unsigned long lid = fdrain.mProto.layout_id();\n  unsigned long target_lid = LayoutId::SetLayoutType(lid, LayoutId::kPlain);\n\n  // Mask block checksums (set to kNone) for replica layouts\n  if (LayoutId::GetLayoutType(lid) == LayoutId::kReplica) {\n    target_lid = LayoutId::SetBlockChecksum(target_lid, LayoutId::kNone);\n  }\n\n  if (eos::common::LayoutId::GetLayoutType(fdrain.mProto.layout_id()) <=\n      eos::common::LayoutId::kReplica) {\n    bool found = false;\n\n    if (!mBalanceMode) {\n      for (const auto id : fdrain.mProto.locations()) {\n        // First try copying from a location different from the current source\n        // file system. Make sure we also skip any EOS_TAPE_FSID (65535)\n        // replicas.\n        if ((id != mFsIdSource) && (id != EOS_TAPE_FSID) &&\n            (mTriedSrcs.find(id) == mTriedSrcs.end())) {\n          mTriedSrcs.insert(id);\n          eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n          FileSystem* fs = FsView::gFsView.mIdView.lookupByID(id);\n\n          if (fs) {\n            fs->SnapShotFileSystem(src_snapshot);\n\n            if (src_snapshot.mConfigStatus >= eos::common::ConfigStatus::kDrain) {\n              found = true;\n              break;\n            }\n          }\n        }\n      }\n    }\n\n    // If nothing found and we didn't try the current file system give it a go\n    if (!found && (mTriedSrcs.find(mFsIdSource) == mTriedSrcs.end())) {\n      found = true;\n      mTriedSrcs.insert(mFsIdSource);\n      eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n      FileSystem* fs = FsView::gFsView.mIdView.lookupByID(mFsIdSource);\n\n      if (!fs) {\n        ReportError(SSTR(\"msg=\\\"fsid=\" << mFsIdSource << \" no longer in the list\"));\n        return url_src;\n      }\n\n      fs->SnapShotFileSystem(src_snapshot);\n    }\n\n    if (!found) {\n      ReportError(SSTR(\"msg=\\\"no more replicas available\\\" \" << \"fxid=\"\n                       << eos::common::FileId::Fid2Hex(fdrain.mProto.id())));\n      return url_src;\n    }\n  } else {\n    // For RAIN layouts we trigger an attempt only once\n    if (mRainAttempt) {\n      ReportError(SSTR(\"msg=\\\"fxid=\" << eos::common::FileId::Fid2Hex(\n                         fdrain.mProto.id())\n                       << \" rain reconstruct already failed\\\"\"));\n      return url_src;\n    } else {\n      mRainAttempt = true;\n    }\n\n    mRainReconstruct = true;\n\n    // If source/dest fses are forced we want to trigger a balance rather than\n    // a drain reconstruct operation\n    if (mBalanceMode) {\n      // For RAIN layout we also need to disable checksum enforcements as the\n      // stripe checksum and logical file checksum will not match\n      mRainReconstruct = false;\n      target_lid = LayoutId::SetChecksum(target_lid, LayoutId::kNone);\n      bool found = false;\n      eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n      FileSystem* fs = FsView::gFsView.mIdView.lookupByID(mFsIdSource);\n\n      if (fs) {\n        fs->SnapShotFileSystem(src_snapshot);\n\n        if (src_snapshot.mConfigStatus >= eos::common::ConfigStatus::kDrain) {\n          found = true;\n        }\n      }\n\n      if (!found) {\n        ReportError(SSTR(\"msg=\\\"source stripe not available\\\" \" << \"fxid=\"\n                         << eos::common::FileId::Fid2Hex(fdrain.mProto.id())\n                         << \" fsid=\" << mFsIdSource));\n        return url_src;\n      }\n    }\n  }\n\n  // Construct the source URL\n  std::ostringstream src_params;\n  mTxFsIdSource = src_snapshot.mId;\n\n  if (mRainReconstruct) {\n    src_params << \"&mgm.path=\" << StringConversion::SealXrdPath(fdrain.mFullPath)\n               << \"&mgm.manager=\" << gOFS->ManagerId.c_str()\n               << \"&mgm.fid=\" << eos::common::FileId::Fid2Hex(mFileId)\n               << \"&mgm.sec=\"\n               << eos::common::SecEntity::ToKey(0, SSTR(\"eos/\" << mAppTag).c_str())\n               << \"&eos.app=\" << mAppTag\n               << \"&eos.ruid=\" << DAEMONUID\n               << \"&eos.rgid=\" << DAEMONGID;\n  } else {\n    src_params << \"mgm.access=read\"\n               << \"&mgm.lid=\" << target_lid\n               << \"&mgm.cid=\" << fdrain.mProto.cont_id()\n               << \"&mgm.ruid=1&mgm.rgid=1&mgm.uid=1&mgm.gid=1\"\n               << \"&mgm.path=\" << StringConversion::SealXrdPath(fdrain.mFullPath)\n               << \"&mgm.manager=\" << gOFS->ManagerId.c_str()\n               << \"&mgm.fid=\" << eos::common::FileId::Fid2Hex(mFileId)\n               << \"&mgm.sec=\"\n               << eos::common::SecEntity::ToKey(0, SSTR(\"eos/\" << mAppTag).c_str())\n               << \"&mgm.fsid=\" << src_snapshot.mId\n               << \"&eos.app=\" << mAppTag\n               << \"&eos.ruid=\" << DAEMONUID\n               << \"&eos.rgid=\" << DAEMONGID;\n  }\n\n  // Build the capability\n  int caprc = 0;\n  XrdOucEnv* output_cap = 0;\n  XrdOucEnv input_cap(src_params.str().c_str());\n  SymKey* symkey = eos::common::gSymKeyStore.GetCurrentKey();\n\n  if ((caprc = SymKey::CreateCapability(&input_cap, output_cap,\n                                        symkey, gOFS->mCapabilityValidity))) {\n    ReportError(SSTR(\"msg=\\\"unable to create src capability, errno=\" << caprc\n                     << \"\\\"\"));\n    return url_src;\n  }\n\n  int cap_len = 0;\n  std::ostringstream src_cap;\n\n  if (mRainReconstruct) {\n    url_src.SetPath(eos::common::StringConversion::curl_escaped(fdrain.mFullPath));\n    url_src.SetHostName(gOFS->MgmOfsAlias.c_str());\n    url_src.SetPort(gOFS->ManagerPort);\n    src_cap << output_cap->Env(cap_len)\n            << \"&eos.pio.action=reconstruct\"\n            << \"&eos.encodepath=curl\";\n\n    if (mRepairExcluded) {\n      src_cap << \"&eos.pio.recfs=\" << eos::common::StringTokenizer::merge(mTriedSrcs,\n              ',');\n    } else {\n      src_cap << \"&eos.pio.recfs=\" << mFsIdSource;\n    }\n  } else {\n    std::ostringstream oss_path;\n    oss_path << \"/replicate:\" << eos::common::FileId::Fid2Hex(mFileId);\n    url_src.SetPath(oss_path.str());\n    url_src.SetHostName(src_snapshot.mHost.c_str());\n    url_src.SetPort(src_snapshot.mPort);\n    src_cap << output_cap->Env(cap_len);\n  }\n\n  src_cap << \"&mgm.logid=\" << log_id;\n  url_src.SetParams(src_cap.str());\n  url_src.SetProtocol(\"root\");\n  // @todo(esindril) this should use different physical connections\n  url_src.SetUserName(\"daemon\");\n  delete output_cap;\n  return url_src;\n}\n\n//------------------------------------------------------------------------------\n// Build TPC destination url\n//------------------------------------------------------------------------------\nXrdCl::URL\nDrainTransferJob::BuildTpcDst(const FileDrainInfo& fdrain,\n                              const std::string& log_id)\n{\n  using namespace eos::common;\n  XrdCl::URL url_dst;\n  eos::common::FileSystem::fs_snapshot_t dst_snapshot;\n  unsigned long lid = fdrain.mProto.layout_id();\n  unsigned long target_lid = LayoutId::SetLayoutType(lid, LayoutId::kPlain);\n\n  // Mask block checksums (set to kNone) for replica layouts\n  if (LayoutId::GetLayoutType(lid) == LayoutId::kReplica) {\n    target_lid = LayoutId::SetBlockChecksum(target_lid, LayoutId::kNone);\n  }\n\n  if (LayoutId::IsRain(lid) && mBalanceMode) {\n    target_lid = LayoutId::SetChecksum(target_lid, LayoutId::kNone);\n  }\n\n  {\n    // Get destination fs snapshot\n    eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(mFsIdTarget);\n\n    if (!fs) {\n      ReportError(\"msg=\\\"target file system not found\\\"\");\n      return url_dst;\n    }\n\n    fs->SnapShotFileSystem(dst_snapshot);\n  }\n\n  std::ostringstream xs_info;\n  std::ostringstream dst_params;\n\n  if (mRainReconstruct) {\n    dst_params << \"mgm.access=write\"\n               << \"&mgm.ruid=1&mgm.rgid=1&mgm.uid=1&mgm.gid=1&mgm.fid=0\"\n               << \"&mgm.lid=\" << target_lid\n               << \"&mgm.cid=\" << fdrain.mProto.cont_id()\n               << \"&mgm.manager=\" << gOFS->ManagerId.c_str()\n               << \"&mgm.fsid=\" << dst_snapshot.mId\n               << \"&mgm.sec=\"\n               << eos::common::SecEntity::ToKey(0, SSTR(\"eos/\" << mAppTag).c_str())\n               << \"&eos.app=\" << mAppTag;\n  } else {\n    dst_params << \"mgm.access=write\"\n               << \"&mgm.lid=\" << target_lid\n               << \"&mgm.source.lid=\" << lid\n               << \"&mgm.source.ruid=\" << fdrain.mProto.uid()\n               << \"&mgm.source.rgid=\" << fdrain.mProto.gid()\n               << \"&mgm.cid=\" << fdrain.mProto.cont_id()\n               << \"&mgm.ruid=1&mgm.rgid=1&mgm.uid=1&mgm.gid=1\"\n               << \"&mgm.path=\" << StringConversion::SealXrdPath(fdrain.mFullPath.c_str())\n               << \"&mgm.manager=\" << gOFS->ManagerId.c_str()\n               << \"&mgm.fid=\" << eos::common::FileId::Fid2Hex(mFileId)\n               << \"&mgm.sec=\"\n               << eos::common::SecEntity::ToKey(0, SSTR(\"eos/\" << mAppTag).c_str())\n               << \"&mgm.fsid=\" << dst_snapshot.mId\n               << \"&mgm.bookingsize=\"\n               << LayoutId::ExpectedStripeSize(lid, fdrain.mProto.size())\n               << \"&eos.app=\" << mAppTag\n               << \"&mgm.targetsize=\"\n               << LayoutId::ExpectedStripeSize(lid, fdrain.mProto.size());\n\n    // This is true by default for drain, but when this is set to false, we get\n    // a replication like behaviour similar to the adjustreplica command which\n    // is useful for fsck for eg.\n    if (mDropSrc) {\n      dst_params << \"&mgm.drainfsid=\" << mFsIdSource;\n    }\n\n    // Checksum enforcement done only for non-rain layouts\n    if (!LayoutId::IsRain(lid) && !fdrain.mProto.checksum().empty()) {\n      xs_info << \"&mgm.checksum=\";\n      uint32_t xs_len = LayoutId::GetChecksumLen(lid);\n      uint32_t data_len = fdrain.mProto.checksum().size();\n\n      for (auto i = 0u; i < xs_len; ++i) {\n        if (i >= data_len) {\n          // Pad with zeros if necessary\n          xs_info << '0';\n        } else {\n          xs_info << StringConversion::char_to_hex(fdrain.mProto.checksum()[i]);\n        }\n      }\n    }\n  }\n\n  // Build the capability\n  int caprc = 0;\n  XrdOucEnv* output_cap = 0;\n  XrdOucEnv input_cap(dst_params.str().c_str());\n  SymKey* symkey = eos::common::gSymKeyStore.GetCurrentKey();\n\n  if ((caprc = SymKey::CreateCapability(&input_cap, output_cap,\n                                        symkey, gOFS->mCapabilityValidity))) {\n    std::string err = \"msg=\\\"unable to create dst capability, errno=\";\n    err += caprc;\n    err += \"\\\"\";\n    ReportError(err);\n    return url_dst;\n  }\n\n  int cap_len = 0;\n  std::ostringstream oss_cap;\n  oss_cap << output_cap->Env(cap_len)\n          << \"&mgm.logid=\" << log_id;\n\n  // @Note: the mgm.checksum info needs to be unencrypted in the URL\n  if (!xs_info.str().empty()) {\n    oss_cap << xs_info.str();\n  }\n\n  url_dst.SetProtocol(\"root\");\n  url_dst.SetHostName(dst_snapshot.mHost.c_str());\n  url_dst.SetPort(dst_snapshot.mPort);\n  url_dst.SetUserName(\"daemon\");\n  // @todo(esindril) this should use different physical connections\n  url_dst.SetParams(oss_cap.str());\n  std::ostringstream oss_path;\n  oss_path << \"/replicate:\"\n           << (mRainReconstruct ? \"0\" : eos::common::FileId::Fid2Hex(mFileId));\n  url_dst.SetPath(oss_path.str());\n  delete output_cap;\n  return url_dst;\n}\n\n//------------------------------------------------------------------------------\n// Select destiantion file system for current transfer\n//------------------------------------------------------------------------------\nbool\nDrainTransferJob::SelectDstFs(const FileDrainInfo& fdrain)\n{\n  unsigned int nfilesystems = 1;\n  unsigned int ncollocatedfs = 0;\n  std::vector<FileSystem::fsid_t> new_repl;\n  eos::common::FileSystem::fs_snapshot_t source_snapshot;\n  eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n  eos::common::FileSystem* source_fs = FsView::gFsView.mIdView.lookupByID(\n                                         mFsIdSource);\n\n  if (source_fs == nullptr) {\n    // In case of rain reconstruction without dropping a particular stripe\n    // the mFsIdSource is set to the sentinel value of 0.\n    if ((mFsIdSource == 0u) && fdrain.mProto.locations_size()) {\n      source_fs = FsView::gFsView.mIdView.lookupByID(fdrain.mProto.locations(0));\n    }\n\n    if (source_fs == nullptr) {\n      return false;\n    }\n  }\n\n  source_fs->SnapShotFileSystem(source_snapshot);\n  FsGroup* group = FsView::gFsView.mGroupView[source_snapshot.mGroup];\n  // Check other replicas for the file\n  std::vector<std::string> fsid_geotags;\n  std::vector<FileSystem::fsid_t> existing_repl;\n\n  for (auto elem : fdrain.mProto.locations()) {\n    // Skip any EOS_TAPE_FSID replicas\n    if (elem != EOS_TAPE_FSID) {\n      existing_repl.push_back(elem);\n    }\n  }\n\n  if (!gOFS->mGeoTreeEngine->getInfosFromFsIds(existing_repl, &fsid_geotags, 0,\n      0)) {\n    eos_err(\"msg=\\\"failed to retrieve info for existing replicas\\\" fxid=%08llx\",\n            mFileId.load());\n    return false;\n  }\n\n  bool res = gOFS->mGeoTreeEngine->placeNewReplicasOneGroup(\n               group, nfilesystems,\n               &new_repl,\n               (ino64_t) fdrain.mProto.id(),\n               NULL, // entrypoints\n               NULL, // firewall\n               // This methods is only called for drain functionality, for\n               // balance we already provide the destination file system\n               GeoTreeEngine::draining,\n               &existing_repl,\n               &fsid_geotags,\n               fdrain.mProto.size(),\n               \"\",// start from geotag\n               \"\",// client geo tag\n               ncollocatedfs,\n               &mExcludeDsts,\n               &fsid_geotags); // excludeGeoTags\n\n  if (!res || new_repl.empty())  {\n    eos_err(\"msg=\\\"fxid=%08llx could not place new replica\\\"\", mFileId.load());\n    return false;\n  }\n\n  std::ostringstream oss;\n\n  for (auto elem : new_repl) {\n    oss << \" \" << (unsigned long)(elem);\n  }\n\n  // Return only one fs now\n  mFsIdTarget = new_repl[0];\n  mExcludeDsts.push_back(mFsIdTarget);\n  eos_static_debug(\"msg=\\\"schedule placement retc=%d with fsids=%s\\\" \",\n                   (int)res, oss.str().c_str());\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Drain 0-size file\n//------------------------------------------------------------------------------\nDrainTransferJob::Status\nDrainTransferJob::DrainZeroSizeFile(const FileDrainInfo& fdrain)\n{\n  std::shared_ptr<eos::IFileMD> file {nullptr};\n\n  try {\n    file = gOFS->eosFileService->getFileMD(fdrain.mProto.id());\n  } catch (const eos::MDException& e) {\n    // ignore, file will be null\n  }\n\n  if (file == nullptr) {\n    return Status::Failed;\n  }\n\n  eos::MDLocking::FileWriteLock fmdLock(file.get());\n\n  // We already have excess replicas just drop the current one\n  if (file->getNumLocation() >\n      eos::common::LayoutId::GetStripeNumber(fdrain.mProto.layout_id()) + 1) {\n    file->unlinkLocation(mFsIdSource);\n  } else {\n    // Add the new location and remove the old one if requested\n    file->addLocation(mFsIdTarget);\n\n    if (mDropSrc) {\n      file->unlinkLocation(mFsIdSource);\n    }\n  }\n\n  gOFS->eosFileService->updateStore(file.get());\n  return Status::OK;\n}\n\n//------------------------------------------------------------------------------\n// Get drain job info based on the requested tags\n//------------------------------------------------------------------------------\nstd::list<std::string>\nDrainTransferJob::GetInfo(const std::list<std::string>& tags) const\n{\n  std::list<std::string> info;\n\n  for (const auto& tag : tags) {\n    // We only support the following tags\n    if (tag == \"fxid\") {\n      info.push_back(eos::common::FileId::Fid2Hex(mFileId));\n    } else if (tag == \"fid\") {\n      info.push_back(std::to_string(mFileId));\n    } else if (tag == \"fs_src\") {\n      info.push_back(std::to_string(mFsIdSource));\n    } else if (tag == \"fs_dst\") {\n      info.push_back(std::to_string(mFsIdTarget));\n    } else if (tag == \"tx_fs_src\") {\n      info.push_back(std::to_string(mTxFsIdSource));\n    } else if (tag == \"start_timestamp\") {\n      std::time_t start_ts {(long int)mProgressHandler.mStartTimestampSec.load()};\n      std::tm calendar_time;\n      (void) localtime_r(&start_ts, &calendar_time);\n      info.push_back(SSTR(std::put_time(&calendar_time, \"%c %Z\")));\n    } else if (tag == \"progress\") {\n      info.push_back(std::to_string(mProgressHandler.mProgress.load()) + \"%\");\n    } else if (tag == \"speed\") {\n      uint64_t now_sec = std::chrono::duration_cast<std::chrono::seconds>\n                         (std::chrono::system_clock::now().time_since_epoch()).count();\n\n      if (mProgressHandler.mStartTimestampSec < now_sec) {\n        uint64_t duration_sec = now_sec - mProgressHandler.mStartTimestampSec;\n        float rate = (mProgressHandler.mBytesTransferred / (1024 * 1024)) /\n                     duration_sec;\n        info.push_back(std::to_string(rate));\n      } else {\n        info.push_back(\"N/A\");\n      }\n    } else if (tag == \"err_msg\") {\n      info.push_back(mErrorString);\n    } else {\n      info.push_back(\"N/A\");\n    }\n  }\n\n  return info;\n}\n\n//------------------------------------------------------------------------------\n// Update MGM stats depending on the type of transfer\n//------------------------------------------------------------------------------\nvoid\nDrainTransferJob::UpdateMgmStats()\n{\n  std::string tag_stats;\n\n  if (mAppTag == \"drain\") {\n    tag_stats = \"DrainCentral\";\n  } else if (mAppTag == \"balance\") {\n    tag_stats = \"Balance\";\n  } else {\n    tag_stats = mAppTag;\n  }\n\n  if (mStatus == Status::OK) {\n    tag_stats += \"Successful\";\n  } else if (mStatus == Status::Failed) {\n    tag_stats += \"Failed\";\n  } else {\n    tag_stats += \"Started\";\n  }\n\n  if (gOFS) {\n    gOFS->MgmStats.Add(tag_stats.c_str(), mVid.uid, mVid.gid, 1);\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/drain/DrainTransferJob.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file DrainTransfer.hh\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/FileId.hh\"\n#include \"common/Logging.hh\"\n#include \"common/FileSystem.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"proto/FileMd.pb.h\"\n#include <XrdCl/XrdClCopyProcess.hh>\n\nEOSMGMNAMESPACE_BEGIN\n\n//! Forward declaration\nclass DrainTransferJob;\n\n//------------------------------------------------------------------------------\n//! Class DrainProgressHandler used to monitor the progress of the current\n//! drain transfers but also more importantly to cancel gracefully a running\n//! transfer.\n//------------------------------------------------------------------------------\nclass DrainProgressHandler: public XrdCl::CopyProgressHandler,\n  public eos::common::LogId\n{\n  friend class DrainTransferJob;\n\npublic:\n\n//----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  DrainProgressHandler():\n    mDoCancel{false}, mProgress{0}, mBytesTransferred{0ull},\n    mStartTimestampSec(std::chrono::duration_cast<std::chrono::seconds>\n                       (std::chrono::system_clock::now().time_since_epoch()).count())\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Notify when a new job is about to start\n  //!\n  //! @param jobNum         the job number of the copy job concerned\n  //! @param jobTotal       total number of jobs being processed\n  //! @param source         the source url of the current job\n  //! @param destination    the destination url of the current job\n  //----------------------------------------------------------------------------\n  void BeginJob(uint16_t jobNum, uint16_t jobTotal, const XrdCl::URL* source,\n                const XrdCl::URL* destination) override\n  {\n    using namespace std::chrono;\n    mStartTimestampSec = duration_cast<seconds>\n                         (system_clock::now().time_since_epoch()).count();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Notify about the progress of the current job\n  //!\n  //! @param jobNum         job number\n  //! @param bytesProcessed bytes processed by the current job\n  //! @param bytesTotal     total number of bytes to be processed by job\n  //----------------------------------------------------------------------------\n  void JobProgress(uint16_t jobNum, uint64_t bytesProcessed,\n                   uint64_t bytesTotal) override\n  {\n    mBytesTransferred = bytesProcessed;\n    mProgress = static_cast<int>((1.0 - (1.0 * (bytesTotal - bytesProcessed) /\n                                         bytesTotal))  * 100);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Determine whether the job should be canceled - this is used internally\n  //! by the XrdCl::CopyProcess.\n  //----------------------------------------------------------------------------\n  bool ShouldCancel(uint16_t jobNum) override\n  {\n    return mDoCancel;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Mark drain job to be cancelled\n  //----------------------------------------------------------------------------\n  void DoCancel()\n  {\n    mDoCancel = true;\n  }\n\nprivate:\n  std::atomic<bool> mDoCancel; ///< Mark if job should be cancelled\n  std::atomic<int> mProgress; ///< Progress percentage\n  std::atomic<uint64_t> mBytesTransferred; ///< Amount of data transferred\n  std::atomic<uint64_t> mStartTimestampSec; ///< Start timestamp in seconds\n};\n\n\n//------------------------------------------------------------------------------\n//! Class implementing the third party copy transfer, takes as input the\n//! file id and the destination filesystem\n//------------------------------------------------------------------------------\nclass DrainTransferJob: public eos::common::LogId\n{\npublic:\n  //! Status of a drain transfer job\n  enum class Status {OK, Running, Failed, Ready};\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param fid the file id\n  //! @param fsid_src source file system id\n  //! @param fsid_trg target file system id\n  //! @param exclude_srcs set of fs ids which are to be excluded as sources\n  //! @param exclude_dsts set of fs ids which are to be excluded as dest.\n  //! @param drop_src mark if source replica should be dropped if operation\n  //!        is successful (default true)\n  //! @param app_tag application tag for easy classification of job types\n  //! @param balance_mode if true force transfer between given source and\n  //!        destination file systems\n  //! @param vid virtual identity running the job\n  //! @param repair_excluded if true then try to repair also the excluded srcs\n  //----------------------------------------------------------------------------\n  DrainTransferJob(eos::common::FileId::fileid_t fid,\n                   eos::common::FileSystem::fsid_t fsid_src,\n                   eos::common::FileSystem::fsid_t fsid_trg = 0,\n                   std::set<eos::common::FileSystem::fsid_t> exclude_srcs = {},\n                   std::set<eos::common::FileSystem::fsid_t> exclude_dsts = {},\n                   bool drop_src = true,\n                   const std::string& app_tag = \"drain\",\n                   bool balance_mode = false,\n                   eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root(),\n                   bool repair_excluded = false):\n    mAppTag(app_tag), mFileId(fid), mFsIdSource(fsid_src), mFsIdTarget(fsid_trg),\n    mTxFsIdSource(fsid_src), mStatus(Status::Ready), mRainAttempt(false),\n    mRainReconstruct(false), mDropSrc(drop_src), mBalanceMode(balance_mode),\n    mRepairExcluded(repair_excluded), mVid(vid)\n  {\n    mTriedSrcs.insert(exclude_srcs.begin(), exclude_srcs.end());\n    mExcludeDsts.insert(mExcludeDsts.begin(), exclude_dsts.begin(),\n                        exclude_dsts.end());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~DrainTransferJob() = default;\n\n  //----------------------------------------------------------------------------\n  //! Execute a third-party transfer\n  //----------------------------------------------------------------------------\n  virtual void DoIt() noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Cancel ongoing TPC transfer\n  //----------------------------------------------------------------------------\n  inline void Cancel()\n  {\n    mProgressHandler.DoCancel();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Log error message and save it\n  //!\n  //! @param error error message\n  //----------------------------------------------------------------------------\n  void ReportError(const std::string& error);\n\n  //----------------------------------------------------------------------------\n  //! Set drain transfer status\n  //!\n  //! @param status new drain transfer status\n  //----------------------------------------------------------------------------\n  inline void SetStatus(DrainTransferJob::Status status)\n  {\n    mStatus = status;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get drain transfer status\n  //----------------------------------------------------------------------------\n  virtual DrainTransferJob::Status GetStatus() const\n  {\n    return mStatus.load();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get drain job info based on the requested tags\n  //!\n  //! @param tags set of tags that the requestor would like to get inf about\n  //!\n  //! @param map of tags to corresponding information collected from the job\n  //----------------------------------------------------------------------------\n  std::list<std::string>\n  GetInfo(const std::list<std::string>& tags) const;\n\n  //----------------------------------------------------------------------------\n  //! Update MGM stats depending on the type of transfer. The generic\n  //! DrainTransferJob can be used in different components eg. fsck. For the\n  //! time being this will update the MGM statistics only for the drainer.\n  //----------------------------------------------------------------------------\n  void UpdateMgmStats();\n\n  //----------------------------------------------------------------------------\n  //! Get file identifier\n  //----------------------------------------------------------------------------\n  eos::IFileMD::id_t GetFileIdentifier() const\n  {\n    return mFileId.load();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set minimum transfer rate used for estimating the timeout of the TPC\n  //!\n  //! @param min_rate minimum transfer rate\n  //----------------------------------------------------------------------------\n  void SetMinTransferRate(const uint64_t min_rate)\n  {\n    mMinTxRate.store(min_rate);\n  }\n\n\n#ifdef IN_TEST_HARNESS\npublic:\n#else\nprivate:\n#endif\n\n  //----------------------------------------------------------------------------\n  //! Struct holding info about a file to be drained\n  //----------------------------------------------------------------------------\n  struct FileDrainInfo {\n    std::string mFullPath;\n    eos::ns::FileMdProto mProto;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Get file metadata info. Depending on the MGM configuration this will use\n  //! the in-memory approach with namespace locking or the qclient to connect\n  //! directy to QDB without any locking.\n  //!\n  //! @return file drain info object or throws and MDException\n  //----------------------------------------------------------------------------\n  FileDrainInfo GetFileInfo() const;\n\n  //----------------------------------------------------------------------------\n  //! Build TPC source url\n  //!\n  //! @param fdrain file to be drained info\n  //! @param log_id transfer log id\n  //!\n  //! @return XrdCl source URL\n  //----------------------------------------------------------------------------\n  XrdCl::URL BuildTpcSrc(const FileDrainInfo& fdrain,\n                         const std::string& log_id);\n\n  //----------------------------------------------------------------------------\n  //! Build TPC destination url\n  //!\n  //! @param fdrain file to be drained info\n  //! @param log_id transfer log id\n  //!\n  //! @return XrdCl destination URL\n  //----------------------------------------------------------------------------\n  XrdCl::URL BuildTpcDst(const FileDrainInfo& fdrain,\n                         const std::string& log_id);\n\n  //----------------------------------------------------------------------------\n  //! Select destiantion file system for current transfer\n  //!\n  //! @param fdrain file to drain metadata info\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool SelectDstFs(const FileDrainInfo& fdrain);\n\n  //----------------------------------------------------------------------------\n  //! Drain 0-size file\n  //!\n  //! @param fdrain file to drain metadata info\n  //!\n  //! @return drain status\n  //----------------------------------------------------------------------------\n  Status DrainZeroSizeFile(const FileDrainInfo& fdrain);\n\n  std::string mAppTag; ///< Application tag for the transfer\n  std::atomic<eos::IFileMD::id_t> mFileId; ///< File id to transfer\n  //! Source and destination file system\n  std::atomic<eos::common::FileSystem::fsid_t> mFsIdSource;\n  std::atomic<eos::common::FileSystem::fsid_t> mFsIdTarget;\n  //! Actual file system id used for the current drain transfer, can point to\n  //! the file system of a replica of the file\n  std::atomic<eos::common::FileSystem::fsid_t> mTxFsIdSource;\n  std::string mErrorString; ///< Error message\n  std::atomic<Status> mStatus; ///< Status of the drain job\n  std::set<eos::common::FileSystem::fsid_t> mTriedSrcs; ///< Tried src\n  std::vector<eos::common::FileSystem::fsid_t> mExcludeDsts; ///< Excluded dest\n  bool mRainAttempt; ///< Rain transfers are attempted only once\n  bool mRainReconstruct; ///< Mark rain reconstruction\n  bool mDropSrc; ///< Mark if source replicas should be dropped\n  //! In balance mode the source and destination file systems are enforced\n  bool mBalanceMode;\n  //! Mark if mTriedSrcs should be included in recfs for rain layouts\n  bool mRepairExcluded;\n  //! Minimum transfer rate for the TPC process, default 25 MB/s\n  std::atomic<std::uint64_t> mMinTxRate {25};\n  DrainProgressHandler mProgressHandler; ///< TPC progress handler\n  eos::common::VirtualIdentity mVid; /// VID triggering the job\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/drain/Drainer.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file Drainer.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/drain/Drainer.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/drain/DrainFs.hh\"\n#include \"mgm/drain/DrainTransferJob.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/imaster/IMaster.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StacktraceHere.hh\"\n#include \"common/table_formatter/TableFormatterBase.hh\"\n\nnamespace\n{\n//! Drainer configuration key in the global map\nstd::string kDrainerCfg = \"drainer\";\n//! Max number of file systems that can be drained in parallel per node\nstd::string kDrainerMaxFs      = \"max-fs-per-node\";\n//! Max number of threads that the drainer can spawn\nstd::string kDrainerMaxThreads = \"max-thread-pool-size\";\n}\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nDrainer::Drainer():\n  mThreadPool(10, 100, 10, 6, 5, \"drain\"),\n  mMaxFsInParallel(5)\n{}\n\n//------------------------------------------------------------------------------\n// Start running thread\n//------------------------------------------------------------------------------\nvoid Drainer::Start()\n{\n  mThread.reset(&Drainer::Drain, this);\n}\n\n//------------------------------------------------------------------------------\n// Stop running thread and implicitly all running drain jobs\n//------------------------------------------------------------------------------\nvoid\nDrainer::Stop()\n{\n  mThread.join();\n  gOFS->mFidTracker.Clear(TrackerType::Drain);\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nDrainer::~Drainer()\n{\n  Stop();\n}\n\n//------------------------------------------------------------------------------\n// Start draining of a given file system\n//------------------------------------------------------------------------------\nbool\nDrainer::StartFsDrain(eos::mgm::FileSystem* fs,\n                      eos::common::FileSystem::fsid_t dst_fsid,\n                      std::string& err)\n{\n  using eos::common::FileSystem;\n  FileSystem::fsid_t src_fsid = fs->GetId();\n  eos_static_info(\"msg=\\\"start draining\\\" fsid=%d\", src_fsid);\n\n  if (src_fsid == 0) {\n    std::ostringstream ss;\n    ss << \"Debug stacktrace: \" << eos::common::getStacktrace();\n    eos_static_crit(\"msg=\\\"%s\\\"\", ss.str().c_str());\n  }\n\n  FileSystem::fs_snapshot_t src_snapshot;\n  fs->SnapShotFileSystem(src_snapshot);\n\n  // Check that the destination fs, if specified, is in the same space and\n  // group as the source\n  if (dst_fsid) {\n    FileSystem* dst = FsView::gFsView.mIdView.lookupByID(dst_fsid);\n    FileSystem::fs_snapshot_t  dst_snapshot;\n\n    if (!dst) {\n      err = SSTR(\"error: destination file system \" << dst_fsid\n                 << \" does not exist\");\n      return false;\n    }\n\n    dst->SnapShotFileSystem(dst_snapshot, false);\n\n    if ((src_snapshot.mSpace != dst_snapshot.mSpace) ||\n        (src_snapshot.mGroup != dst_snapshot.mGroup)) {\n      err = SSTR(\"error: destination file system \" << dst_fsid << \" does not \"\n                 << \"belong to the same space and scheduling group as the \"\n                 << \"source\");\n      return false;\n    }\n  }\n\n  eos::common::RWMutexWriteLock wr_lock(mDrainMutex);\n  auto it_drainfs = mDrainFs.find(src_snapshot.mHostPort);\n\n  if (it_drainfs != mDrainFs.end()) {\n    // Check if the fs is already draining for this node\n    auto it = std::find_if(it_drainfs->second.begin(), it_drainfs->second.end(),\n    [src_fsid](const std::shared_ptr<DrainFs>& elem) -> bool {\n      return (elem->GetFsId() == src_fsid);\n    });\n\n    if (it != it_drainfs->second.end()) {\n      err = SSTR(\"error: drain has already started for the given fsid=\"\n                 << src_fsid);\n      return false;\n    } else {\n      // Check if drain request is not already pending\n      auto it_pending = std::find_if(mPending.begin(), mPending.end(),\n                                     [src_fsid](const std::pair<FileSystem::fsid_t,\n      FileSystem::fsid_t>& elem) -> bool {\n        return (elem.first == src_fsid);\n      });\n\n      if (it_pending != mPending.end()) {\n        err = SSTR(\"error: drain jobs is already pending for fsid=\"\n                   << src_fsid);\n        return false;\n      }\n\n      // Check if we have reached the max fs per node for this node\n      if (it_drainfs->second.size() >= mMaxFsInParallel) {\n        fs->SetDrainStatus(eos::common::DrainStatus::kDrainWait);\n        mPending.push_back(std::make_pair(src_fsid, dst_fsid));\n        return true;\n      }\n    }\n  }\n\n  // Start the drain\n  std::shared_ptr<DrainFs> dfs(new DrainFs(mThreadPool, gOFS->eosFsView,\n                               src_fsid, dst_fsid));\n\n  try {\n    auto future = std::async(std::launch::async, &DrainFs::DoIt, dfs.get());\n    dfs->SetFuture(std::move(future));\n    mDrainFs[src_snapshot.mHostPort].emplace(dfs);\n  } catch (const std::exception& e) {\n    err = SSTR(\"Starting drain thread failure, std::async exception\"\n               << e.what());\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Stop draining of a given file system\n//------------------------------------------------------------------------------\nbool\nDrainer::StopFsDrain(eos::mgm::FileSystem* fs, std::string& err)\n{\n  eos::common::FileSystem::fsid_t fsid = fs->GetId();\n  eos_static_notice(\"msg=\\\"stop draining\\\" fsid=%d \", fsid);\n\n  if (fsid == 0) {\n    std::ostringstream ss;\n    ss << \"Debug stacktrace: \" << eos::common::getStacktrace();\n    eos_static_crit(\"msg=\\\"%s\\\"\", ss.str().c_str());\n  }\n\n  eos::common::FileSystem::fs_snapshot_t drain_snapshot;\n  fs->SnapShotFileSystem(drain_snapshot);\n  eos::common::RWMutexWriteLock wr_lock(mDrainMutex);\n  auto it_drainfs = mDrainFs.find(drain_snapshot.mHostPort);\n\n  if (it_drainfs == mDrainFs.end()) {\n    err = SSTR(\"error: no drain started for fsid=\" << fsid);\n    return false;\n  }\n\n  // Check if the fs is draining\n  auto it = std::find_if(it_drainfs->second.begin(), it_drainfs->second.end(),\n  [fsid](const std::shared_ptr<DrainFs>& elem) -> bool {\n    return (elem->GetFsId() == fsid);\n  });\n\n  if (it == it_drainfs->second.end()) {\n    // Check if there is a request pending\n    auto it_pending = std::find_if(mPending.begin(), mPending.end(),\n                                   [fsid](const std::pair<FileSystem::fsid_t,\n    FileSystem::fsid_t>& elem) -> bool {\n      return (elem.first == fsid);\n    });\n\n    if (it_pending != mPending.end()) {\n      (void) mPending.erase(it_pending);\n    }\n\n    fs->SetDrainStatus(eos::common::DrainStatus::kNoDrain);\n  } else {\n    (*it)->SignalStop();\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Get drain jobs info\n//------------------------------------------------------------------------------\nbool\nDrainer::GetJobsInfo(std::string& out, const DrainHdrInfo& hdr_info,\n                     unsigned int fsid, bool only_failed, bool monitor_fmt) const\n{\n  if (hdr_info.empty()) {\n    eos_static_err(\"%s\", \"msg=\\\"drain info header is empty\\\"\");\n    return false;\n  }\n\n  // Collect list of internal tags for display\n  std::list<std::string> itags;\n\n  for (const auto& elem : hdr_info) {\n    itags.push_back(elem.second);\n  }\n\n  TableFormatterBase table;\n  TableHeader table_header;\n\n  for (const auto& elem : hdr_info) {\n    if (monitor_fmt) {\n      table_header.push_back(std::make_tuple(elem.first, 10, \"s\"));\n    } else {\n      table_header.push_back(std::make_tuple(elem.first, 0, \"s\"));\n    }\n  }\n\n  table.SetHeader(table_header);\n  std::vector<std::string> selections;\n  bool found {false};\n  {\n    // Loop through all drain jobs and collect status information\n    eos::common::RWMutexReadLock rd_lock(mDrainMutex);\n\n    if (mDrainFs.size() == 0) {\n      out += \"info: there is no ongoing drain activity\";\n      return true;\n    }\n\n    for (const auto& pair : mDrainFs) {\n      for (const auto& drain_fs : pair.second) {\n        if (fsid == 0) {\n          drain_fs->PrintJobsTable(table, only_failed, itags);\n        } else {\n          if (fsid == drain_fs->GetFsId()) {\n            drain_fs->PrintJobsTable(table, only_failed, itags);\n            found = true;\n            break;\n          }\n        }\n      }\n\n      if (found) {\n        break;\n      }\n    }\n  }\n  out = table.GenerateTable(HEADER, selections).c_str();\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Method doing the drain monitoring\n//------------------------------------------------------------------------------\nvoid\nDrainer::Drain(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"DrainerMT\");\n  eos_static_notice(\"%s\", \"msg=\\\"starting central drainer\\\"\");\n  gOFS->WaitUntilNamespaceIsBooted(assistant);\n\n  // Wait that current MGM becomes a master\n  do {\n    eos_static_debug(\"%s\", \"msg=\\\"drain waiting for master MGM\\\"\");\n    assistant.wait_for(std::chrono::seconds(10));\n  } while (!assistant.terminationRequested() && !gOFS->mMaster->IsMaster());\n\n  // Reapply the drain status for file systems in drain mode\n  FsView::gFsView.ReapplyDrainStatus();\n\n  while (!assistant.terminationRequested()) {\n    HandleQueued();\n    gOFS->mFidTracker.DoCleanup(TrackerType::Drain);\n    assistant.wait_for(std::chrono::seconds(5));\n    // Clean up finished or stopped file system drains\n    eos::common::RWMutexWriteLock wr_lock(mDrainMutex);\n\n    for (auto& pair : mDrainFs) {\n      auto& set_fs = pair.second;\n\n      for (auto it = set_fs.begin(); it != set_fs.end(); /*empty*/) {\n        if (!(*it)->IsRunning()) {\n          it = set_fs.erase(it);\n        } else {\n          ++it;\n        }\n      }\n    }\n  }\n\n  WaitForAllDrainToStop();\n  eos_static_notice(\"%s\", \"msg=\\\"stopped central drainer\\\"\");\n}\n\n//------------------------------------------------------------------------------\n// Signal all drain file systems to stop and wait for them\n//------------------------------------------------------------------------------\nvoid\nDrainer::WaitForAllDrainToStop()\n{\n  eos_static_notice(\"%s\", \"msg=\\\"stop all ongoing drain\\\"\");\n  {\n    eos::common::RWMutexReadLock rd_lock(mDrainMutex);\n\n    for (auto& node_elem : mDrainFs) {\n      for (const auto& fs_elem : node_elem.second) {\n        fs_elem->SignalStop();\n      }\n    }\n  }\n  bool all_stopped {false};\n\n  while (!all_stopped) {\n    {\n      all_stopped = true;\n      eos::common::RWMutexReadLock rd_lock(mDrainMutex);\n\n      for (auto& node_elem : mDrainFs) {\n        for (const auto& fs_elem : node_elem.second) {\n          if (fs_elem->IsRunning()) {\n            all_stopped = false;\n            break;\n          }\n        }\n\n        if (!all_stopped) {\n          break;\n        }\n      }\n    }\n\n    if (!all_stopped) {\n      std::this_thread::sleep_for(std::chrono::milliseconds(100));\n    }\n  }\n\n  eos::common::RWMutexWriteLock wr_lock(mDrainMutex);\n  mDrainFs.clear();\n  mPending.clear();\n}\n\n//------------------------------------------------------------------------------\n// Apply global configuration relevant for the drainer\n//------------------------------------------------------------------------------\nvoid\nDrainer::ApplyConfig()\n{\n  using eos::common::StringTokenizer;\n  std::string config = FsView::gFsView.GetGlobalConfig(kDrainerCfg);\n  // Parse config of the form: key1=val1 key2=val2 etc.\n  eos_static_info(\"msg=\\\"apply drainer configuration\\\" data=\\\"%s\\\"\",\n                  config.c_str());\n  std::map<std::string, std::string> kv_map;\n  auto pairs = StringTokenizer::split<std::list<std::string>>(config, ' ');\n\n  for (const auto& pair : pairs) {\n    auto kv = StringTokenizer::split<std::vector<std::string>>(pair, '=');\n\n    if (kv.empty()) {\n      eos_static_err(\"msg=\\\"unknown drainer config data\\\" data=\\\"%s\\\"\",\n                     config.c_str());\n      continue;\n    }\n\n    // There is no use-case yet for keys without values!\n    if (kv.size() == 1) {\n      continue;\n    }\n\n    kv_map.emplace(kv[0], kv[1]);\n  }\n\n  for (const auto& [key, val] : kv_map) {\n    SetConfig(key, val);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Serialize drainer configuration\n//------------------------------------------------------------------------------\nstd::string\nDrainer::SerializeConfig() const\n{\n  std::ostringstream oss;\n  oss << kDrainerMaxThreads << \"=\" << mThreadPool.GetMaxThreads() << \" \"\n      << kDrainerMaxFs << \"=\" << mMaxFsInParallel;\n  return oss.str();\n}\n\n//------------------------------------------------------------------------------\n// Store configuration\n//------------------------------------------------------------------------------\nbool\nDrainer::StoreConfig()\n{\n  return FsView::gFsView.SetGlobalConfig(kDrainerCfg, SerializeConfig());\n}\n\n//------------------------------------------------------------------------------\n// Make configuration change\n//------------------------------------------------------------------------------\nbool\nDrainer::SetConfig(const std::string& key, const std::string& val)\n{\n  bool config_change = false;\n\n  if (key == kDrainerMaxThreads) {\n    int max_threads = 100;\n\n    try {\n      max_threads = std::stoi(val);\n    } catch (...) {\n      eos_static_err(\"msg=\\\"failed parsing drainer max threads configuration\\\" \"\n                     \"data=\\\"%s\\\"\", val.c_str());\n      return false;\n    }\n\n    if ((max_threads >= 5) &&\n        (max_threads != mThreadPool.GetMaxThreads())) {\n      mThreadPool.SetMaxThreads(max_threads);\n      config_change = true;\n    }\n  } else if (key == kDrainerMaxFs) {\n    unsigned int max_fs_parallel = 5;\n\n    try {\n      max_fs_parallel = std::stoi(val);\n    } catch (...) {\n      eos_static_err(\"msg=\\\"failed parsing drainer max fs in parallel\\\" \"\n                     \"data=\\\"%s\\\"\", val.c_str());\n      return false;\n    }\n\n    if (max_fs_parallel && (max_fs_parallel != mMaxFsInParallel.load())) {\n      mMaxFsInParallel.store(max_fs_parallel);\n      config_change = true;\n    }\n  } else {\n    return false;\n  }\n\n  if (config_change) {\n    if (!StoreConfig()) {\n      eos_static_err(\"%s\", \"msg=\\\"failed to save drainer configuration\\\"\");\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Handle queued draining requests\n//------------------------------------------------------------------------------\nvoid\nDrainer::HandleQueued()\n{\n  std::string msg;\n  ListPendingT lst;\n  {\n    eos::common::RWMutexWriteLock wr_lock(mDrainMutex);\n    std::swap(lst, mPending);\n  }\n\n  while (!lst.empty()) {\n    auto pair = lst.front();\n    lst.pop_front();\n    eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(pair.first);\n\n    if (fs && !StartFsDrain(fs, pair.second, msg)) {\n      eos_static_err(\"msg=\\\"failed to start pending drain src_fsid=%lu\\\"\"\n                     \" msg=\\\"%s\\\"\", pair.first, msg.c_str());\n    }\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/drain/Drainer.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Drainer.hh\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include \"common/ThreadPool.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/FileSystem.hh\"\n#include <list>\n\nEOSMGMNAMESPACE_BEGIN\n\n//! Forward declaration\nclass DrainTransferJob;\nclass TableFormatterBase;\nclass FileSystem;\nclass DrainFs;\n\n//------------------------------------------------------------------------------\n//! @brief Class running the centralized draining\n//------------------------------------------------------------------------------\nclass Drainer: public eos::common::LogId\n{\npublic:\n\n  //! Map node to map of draining file systems and their associated futures\n  using DrainMap = std::map<std::string,\n        std::set<std::shared_ptr<eos::mgm::DrainFs>>>;\n  //! Drain job table header information - each pair represents the header\n  //! tag to be displayed when the table is printed to the client, and the\n  //! correspnding internal tag used when collecting information in the\n  //! DrainTransferJob class\n  using DrainHdrInfo = std::list<std::pair<std::string, std::string>>;\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  Drainer();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~Drainer();\n\n  //----------------------------------------------------------------------------\n  //! Start drainer thread\n  //----------------------------------------------------------------------------\n  void Start();\n\n  //----------------------------------------------------------------------------\n  //! Stop running thread and implicitly all running drain jobs\n  //----------------------------------------------------------------------------\n  void Stop();\n\n  //----------------------------------------------------------------------------\n  //! Start  of a given file system\n  //! @note This method must be called with a read lock on the FsView::ViewMutex\n  //!\n  //! @param fs file system object\n  //! @param dst_fsid destination file system, 0 if not chosen\n  //! @param err output error message\n  //!\n  //! @return true if drain started successfully, otherwise false\n  //----------------------------------------------------------------------------\n  bool StartFsDrain(eos::mgm::FileSystem* fs,\n                    eos::common::FileSystem::fsid_t dst_fsid, std::string& err);\n\n  //----------------------------------------------------------------------------\n  //! Stop draining of a given file system\n  //! @note This method must be called with a read lock on the FsView::ViewMutex\n  //!\n  //! @param fs file system object\n  //! @param err output error message\n  //!\n  //! @return true if drain stopped successfully, otherwise false\n  //----------------------------------------------------------------------------\n  bool StopFsDrain(eos::mgm::FileSystem* fs, std::string& err);\n\n  //----------------------------------------------------------------------------\n  //! Get thread pool info\n  //!\n  //! @return string summary for the thread pool\n  //----------------------------------------------------------------------------\n  std::string GetThreadPoolInfo() const\n  {\n    return mThreadPool.GetInfo();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get reference to thread pool\n  //!\n  //! @return thread pool object\n  //----------------------------------------------------------------------------\n  eos::common::ThreadPool& GetThreadPool()\n  {\n    return mThreadPool;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get drain jobs info (global or specific to an fsid)\n  //!\n  //! @param out output string\n  //! @param hdr_info map of header tags to internal tags used for collecting\n  //!        info about drain transfers\n  //! @param fsid file system for which to display the drain status or 0 for\n  //!        displaying all drain activities in the system\n  //! @param err output error string\n  //! @param show_errors if true then display only failed transfers\n  //! @param monitor_format if true then display in monitoring format\n  //!\n  //! @return true if successful, or false if there was any issue in collecting\n  //!        the requested information\n  //----------------------------------------------------------------------------\n  bool GetJobsInfo(std::string& out, const DrainHdrInfo& hdr_info,\n                   unsigned int fsid, bool only_failed = false,\n                   bool monitor_fmt = false) const;\n\n  //----------------------------------------------------------------------------\n  //! Apply global configuration relevant for the drainer\n  //----------------------------------------------------------------------------\n  void ApplyConfig();\n\n  //----------------------------------------------------------------------------\n  //! Make configuration change\n  //!\n  //! @param key input key\n  //! @param val input value\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool SetConfig(const std::string& key, const std::string& val);\n\n  //----------------------------------------------------------------------------\n  //! Serialize drainer configuration\n  //!\n  //! @return string representing drainer configuration\n  //----------------------------------------------------------------------------\n  std::string SerializeConfig() const;\n\nprivate:\n  using ListPendingT = std::list<std::pair<eos::common::FileSystem::fsid_t,\n        eos::common::FileSystem::fsid_t>>;\n\n  //----------------------------------------------------------------------------\n  //! Store configuration\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool StoreConfig();\n\n  //----------------------------------------------------------------------------\n  //! Method doing the drain monitoring\n  //!\n  //! @param assistant thread running the job\n  //----------------------------------------------------------------------------\n  void Drain(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Handle queued draining requests\n  //----------------------------------------------------------------------------\n  void HandleQueued();\n\n  //----------------------------------------------------------------------------\n  //! Signal all drain file systems to stop and wait for them\n  //----------------------------------------------------------------------------\n  void WaitForAllDrainToStop();\n\n  AssistedThread mThread; ///< Thread updating the drain configuration\n  DrainMap mDrainFs; ///< Map of nodes to file systems draining\n  mutable eos::common::RWMutex mDrainMutex; ///< Mutex protecting the drain map\n  mutable std::mutex mCfgMutex; ///< Mutex for drain config updates\n  ListPendingT mPending; ///< Queue of pending file systems to be drained\n  eos::common::ThreadPool mThreadPool; ///< Thread pool for drain jobs\n  //! Max number of file system per node in draining in parallel\n  std::atomic_uint mMaxFsInParallel;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/egroup/Egroup.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Egroup.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/egroup/Egroup.hh\"\n#include \"common/Logging.hh\"\n#include \"common/StringUtils.hh\"\n#include <ldap.h>\n#include <memory>\n\n//------------------------------------------------------------------------------\n// Delete the LDAP object\n//------------------------------------------------------------------------------\nstatic void ldap_uninitialize(LDAP* ld)\n{\n  if (ld != nullptr) {\n    ldap_unbind_ext(ld, NULL, NULL);\n  }\n}\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor - launch asynchronous refresh thread\n//------------------------------------------------------------------------------\nEgroup::Egroup(common::SteadyClock* clock_) : clock(clock_)\n{\n  mPendingQueue.setBlockingMode(true);\n  mThread.reset(&Egroup::Refresh, this);\n}\n\n//------------------------------------------------------------------------------\n// Destructor - join asynchronous refresh thread\n//------------------------------------------------------------------------------\nEgroup::~Egroup()\n{\n  mPendingQueue.setBlockingMode(false);\n  mThread.join();\n}\n\n//----------------------------------------------------------------------------\n// Return number of asynchronous refresh requests currently pending\n//----------------------------------------------------------------------------\nsize_t Egroup::getPendingQueueSize() const\n{\n  return mPendingQueue.size();\n}\n\n//------------------------------------------------------------------------------\n// Main LDAP lookup function - bypasses the cache, hits the LDAP server.\n//------------------------------------------------------------------------------\nEgroup::Status Egroup::isMemberUncached(const std::string& username,\n                                        const std::string& egroupname)\n{\n  // Serving real, or simulated data?\n  if (!injections.empty()) {\n    auto it = injections.find(egroupname);\n\n    if (it == injections.end()) {\n      return Status::kNotMember;\n    }\n\n    auto it2 = it->second.find(username);\n\n    if (it2 == it->second.end()) {\n      return Status::kNotMember;\n    }\n\n    return it2->second;\n  }\n\n  // Run the LDAP query\n  LDAP* ld = nullptr;\n  {\n    static std::mutex s_ldap_mutex;\n    std::unique_lock<std::mutex> lock(s_ldap_mutex);\n    // Initialize the LDAP context in a thread safe manner\n    ldap_initialize(&ld, \"ldap://xldap\");\n\n    if (ld == nullptr) {\n      eos_static_crit(\"%s\", \"msg=\\\"could not initialize ldap context\\\"\");\n      return Status::kError;\n    }\n\n    int version = LDAP_VERSION3;\n\n    if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version) !=\n        LDAP_OPT_SUCCESS) {\n      eos_static_crit(\"%s\", \"msg=\\\"failure when calling ldap_set_option \"\n                      \"(protocol version)\\\"\");\n      ldap_uninitialize(ld);\n      return Status::kError;\n    }\n\n    // we also need to set a connection timeout\n    struct timeval   tcp_timeout;\n    tcp_timeout.tv_sec = 10;\n    tcp_timeout.tv_usec = 0;\n\n    if (ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT,\n                        &tcp_timeout) != LDAP_OPT_SUCCESS) {\n      eos_static_crit(\"%s\",\n                      \"msg=\\\"failure when calling ldap_set_option (network timeout)\\\"\");\n      ldap_uninitialize(ld);\n      return Status::kError;\n    }\n  }\n  std::unique_ptr<LDAP, decltype(ldap_uninitialize)*>\n  ldOwnership(ld, ldap_uninitialize);\n  //----------------------------------------------------------------------------\n  // These hardcoded values are CERN specific... we should pass them through\n  // the configuration, or something.\n  //----------------------------------------------------------------------------\n  std::string sbase = \"CN=\";\n  sbase += username;\n  sbase += \",OU=Users,Ou=Organic Units,DC=cern,DC=ch\";\n  // the LDAP attribute (recursive search)\n  std::string attr = \"cn\";\n  // the LDAP filter\n  std::string filter;\n  filter = \"(memberOf:1.2.840.113556.1.4.1941:=CN=\";\n  filter += egroupname;\n  filter += \",OU=e-groups,OU=Workgroups,DC=cern,DC=ch)\";\n  char* attrs[2];\n  attrs[0] = (char*) attr.c_str();\n  attrs[1] = NULL;\n  LDAPMessage* res = nullptr;\n  struct timeval timeout;\n  timeout.tv_sec = 10;\n  timeout.tv_usec = 0;\n  std::string match = username;\n  eos_static_debug(\"base=%s attr=%s filter=%s match=%s\\n\", sbase.c_str(),\n                   attr.c_str(), filter.c_str(), match.c_str());\n  int rc = ldap_search_ext_s(ld, sbase.c_str(), LDAP_SCOPE_SUBTREE,\n                             filter.c_str(),\n                             attrs, 0, NULL, NULL,\n                             &timeout, LDAP_NO_LIMIT, &res);\n  std::unique_ptr<LDAPMessage, decltype(ldap_msgfree)*> resOwnership(res,\n      ldap_msgfree);\n\n  if (res == nullptr || rc != LDAP_SUCCESS) {\n    eos_static_warning(\"Having trouble connecting to ldap server, user=%s, \"\n                       \"e-group=%s ldap_rc=%i ldap_err_msg=\\\"%s\\\"\",\n                       username.c_str(), egroupname.c_str(), rc,\n                       ldap_err2string(rc));\n\n    if (rc == LDAP_NO_SUCH_OBJECT) {\n      // no such object, return not member to make it cacheable\n      return Status::kNotMember;\n    } else {\n      return Status::kError;\n    }\n  }\n\n  if (ldap_count_entries(ld, res) == 0) {\n    return Status::kNotMember;\n  }\n\n  //----------------------------------------------------------------------------\n  // We have a response from the server, check if we're member of given egroup\n  //----------------------------------------------------------------------------\n  bool isMember = false;\n\n  for (LDAPMessage* e = ldap_first_entry(ld, res); e != nullptr;\n       e = ldap_next_entry(ld, e)) {\n    struct berval** v = ldap_get_values_len(ld, e, attr.c_str());\n\n    if (v != nullptr) {\n      int n = ldap_count_values_len(v);\n      int j;\n\n      for (j = 0; j < n; j++) {\n        std::string result = v[ j ]->bv_val;\n        eos_static_info(\"result=%d %s\", n, result.c_str());\n\n        if ((result.find(match)) != std::string::npos) {\n          isMember = true;\n        }\n      }\n\n      ldap_value_free_len(v);\n    }\n  }\n\n  if (isMember) {\n    return Status::kMember;\n  }\n\n  return Status::kNotMember;\n}\n\n//------------------------------------------------------------------------------\n// Store entry into the cache\n//------------------------------------------------------------------------------\nvoid Egroup::storeIntoCache(const std::string& username,\n                            const std::string& egroupname, bool isMember,\n                            std::chrono::steady_clock::time_point timestamp)\n{\n  eos::common::RWMutexWriteLock wr_lock(mMutex);\n  cache[egroupname][username] = CachedEntry(isMember, timestamp);\n}\n\n//------------------------------------------------------------------------------\n// Fetch cached value. Returns false if there's no such cached value.\n//------------------------------------------------------------------------------\nbool Egroup::fetchCached(const std::string& username,\n                         const std::string& egroupname, Egroup::CachedEntry& out)\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  auto it = cache.find(egroupname);\n\n  if (it == cache.end()) {\n    return false;\n  }\n\n  auto it2 = it->second.find(username);\n\n  if (it2 == it->second.end()) {\n    return false;\n  }\n\n  out = it2->second;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Check if cache entry is stale\n//------------------------------------------------------------------------------\nbool Egroup::isStale(const CachedEntry& entry) const\n{\n  std::chrono::steady_clock::time_point now = common::SteadyClock::now(clock);\n\n  if (entry.timestamp + kCacheDuration < now) {\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Inject item into the fake LDAP server. If injections are active, any time\n// this class tries to contact the LDAP server, it will serve injected data\n// instead.\n//\n// Simulates response of \"isMemberUncached\" function.\n//------------------------------------------------------------------------------\nvoid Egroup::inject(const std::string& username, const std::string& egroupname,\n                    Status status)\n{\n  injections[egroupname][username] = status;\n}\n\n//------------------------------------------------------------------------------\n// Major query method - uses cache\n//------------------------------------------------------------------------------\nEgroup::CachedEntry Egroup::query(const std::string& username,\n                                  const std::string& egroupname)\n{\n  CachedEntry entry;\n\n  if (fetchCached(username, egroupname, entry)) {\n    // Cache hit - do we need to schedule an asynchronous refresh?\n    if (isStale(entry)) {\n      scheduleRefresh(username, egroupname);\n    }\n\n    return entry;\n  }\n\n  // Cache miss, need to talk to LDAP server\n  Status status = isMemberUncached(username, egroupname);\n  bool isMember = (status == Status::kMember);\n  std::chrono::steady_clock::time_point now = common::SteadyClock::now(clock);\n  uint64_t expiration = common::SteadyClock::SecondsSinceEpoch(\n                          now + kCacheDuration).count();\n  eos_static_info(\"member=%s user=\\\"%s\\\" e-group=\\\"%s\\\" expiration=%lu\",\n                  common::boolToString(isMember).c_str(), username.c_str(),\n                  egroupname.c_str(), expiration);\n  //----------------------------------------------------------------------------\n  // Store into the cache\n  //----------------------------------------------------------------------------\n  storeIntoCache(username, egroupname, isMember, now);\n  return CachedEntry(isMember, now);\n}\n\n//------------------------------------------------------------------------------\n// Asynchronous refresh loop.\n//\n// The looping thread takes Egroup requests and run's LDAP queries pushing\n// results into the Egroup membership map and updates the lifetime of the\n// resolved entry.\n//------------------------------------------------------------------------------\nvoid Egroup::Refresh(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"EgroupRefresh\");\n  eos_static_info(\"%s\", \"msg=\\\"async egroup fetch thread started\\\"\");\n  std::string key;\n  auto iterator = mPendingQueue.begin();\n\n  while (!assistant.terminationRequested()) {\n    std::pair<std::string, std::string>* resolve = iterator.getItemBlockOrNull();\n\n    if (!resolve) {\n      break;\n    }\n\n    if (!resolve->first.empty()) {\n      refresh(resolve->first, resolve->second);\n    }\n\n    iterator.next();\n    {\n      // Remove <user>:<egroup> key from the pending set\n      key = resolve->first + \":\" + resolve->second;\n      std::unique_lock<std::mutex> lock(mMutexPending);\n      (void)mPendingSet.erase(key);\n    }\n    mPendingQueue.pop_front();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Pushes an egroup/user resolution request into the asynchronous queue\n//------------------------------------------------------------------------------\nvoid Egroup::scheduleRefresh(const std::string& username,\n                             const std::string& egroupname)\n{\n  const std::string key = username + \":\" + egroupname;\n  std::unique_lock<std::mutex> lock(mMutexPending);\n\n  if (mPendingSet.find(key) == mPendingSet.end()) {\n    mPendingSet.insert(key);\n    lock.unlock();\n    mPendingQueue.emplace_back(std::make_pair(username, egroupname));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Run a synchronous LDAP query for Egroup/username and update the cache\n//------------------------------------------------------------------------------\nEgroup::CachedEntry Egroup::refresh(const std::string& username,\n                                    const std::string& egroupname)\n{\n  eos_static_info(\"msg=\\\"async-lookup\\\" user=\\\"%s\\\" e-group=\\\"%s\\\"\",\n                  username.c_str(), egroupname.c_str());\n  Status status = isMemberUncached(username, egroupname);\n\n  if (status == Status::kError) {\n    eos_static_err(\"Could not do asynchronous refresh for egroup membership for username=%s, e-group=%s\",\n                   username.c_str(), egroupname.c_str());\n    return CachedEntry(false, {});\n  }\n\n  bool isMember = (status == Status::kMember);\n  std::chrono::steady_clock::time_point now = common::SteadyClock::now(clock);\n  uint64_t expiration = common::SteadyClock::SecondsSinceEpoch(\n                          now + kCacheDuration).count();\n  eos_static_info(\"member=%s user=\\\"%s\\\" e-group=\\\"%s\\\" expiration=%lu\",\n                  common::boolToString(isMember).c_str(), username.c_str(),\n                  egroupname.c_str(), expiration);\n  storeIntoCache(username, egroupname, isMember, now);\n  return CachedEntry(isMember, now);\n}\n\n//------------------------------------------------------------------------------\n// Return membership information as string.\n// @param username name of the user to dump Egroup membership\n// @param egroupname name of Egroup where to look for membership\n//\n// @return egroup dump for username\n//------------------------------------------------------------------------------\nstd::string\nEgroup::DumpMember(const std::string& username, const std::string& egroupname)\n{\n  // trigger refresh\n  CachedEntry entry = query(username, egroupname);\n  std::chrono::steady_clock::time_point now = common::SteadyClock::now(clock);\n  std::chrono::seconds lifetime;\n  lifetime = std::chrono::duration_cast<std::chrono::seconds>(\n               entry.timestamp + kCacheDuration - now);\n  std::stringstream ss;\n  ss << \"egroup=\" << egroupname;\n  ss << \" user=\" << username;\n  ss << \" member=\" << common::boolToString(entry.isMember);\n  ss << \" lifetime=\" << std::to_string(lifetime.count());\n  return ss.str();\n}\n\n//------------------------------------------------------------------------------\n// DumpMembers: return egroup dump for all users\n//------------------------------------------------------------------------------\nstd::string\nEgroup::DumpMembers()\n{\n  std::chrono::steady_clock::time_point now = common::SteadyClock::now(clock);\n  std::stringstream ss;\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n\n  for (auto it = cache.begin(); it != cache.end(); it++) {\n    for (auto it2 = it->second.begin(); it2 != it->second.end(); it2++) {\n      ss << \"egroup=\" << it->first;\n      ss << \" user=\" << it2->first;\n      ss << \" member=\" << common::boolToString(it2->second.isMember);\n      std::chrono::seconds lifetime = std::chrono::duration_cast <\n                                      std::chrono::seconds > ((it2->second.timestamp + kCacheDuration) - now);\n      ss << \" lifetime=\" << std::to_string(lifetime.count()) << std::endl;\n    }\n  }\n\n  return ss.str();\n}\n\n//------------------------------------------------------------------------------\n// Reset all stored information\n//------------------------------------------------------------------------------\nvoid Egroup::Reset()\n{\n  eos::common::RWMutexWriteLock wr_lock(mMutex);\n  cache.clear();\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/egroup/Egroup.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Egroup.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_EGROUP__HH__\n#define __EOSMGM_EGROUP__HH__\n\n#include \"mgm/Namespace.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/SteadyClock.hh\"\n#include \"common/RWMutex.hh\"\n#include <qclient/queueing/WaitableQueue.hh>\n#include <XrdSys/XrdSysPthread.hh>\n#include <sys/types.h>\n#include <string>\n#include <map>\n#include <chrono>\n#include <unordered_set>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file Egroup.hh\n *\n * @brief Class implementing egroup support via LDAP queries\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Class implementing EGROUP support\n *\n */\n/*----------------------------------------------------------------------------*/\n\n//------------------------------------------------------------------------------\n//! Class implementing EGROUP support.\n//!\n//! Provides functionality for checking egroup membership by username / egroup\n//! name.\n//!\n//! The Egroup object maintains a thread which is serving async Egroup\n//! membership update requests.\n//!\n//! The problem here is that the calling function in the MGM has a read lock\n//! during the Egroup::Member call and the refreshing of Egroup permissions\n//! should be done if possible asynchronously to avoid mutex starvation.\n//------------------------------------------------------------------------------\nclass Egroup\n{\npublic:\n  enum class Status {\n    kMember,\n    kNotMember,\n    kError\n  };\n\n  struct CachedEntry {\n    bool isMember;\n    std::chrono::steady_clock::time_point timestamp;\n\n    //--------------------------------------------------------------------------\n    //! Constructors\n    //--------------------------------------------------------------------------\n    CachedEntry(bool member, std::chrono::steady_clock::time_point ts)\n      : isMember(member), timestamp(ts) {}\n\n    CachedEntry() : isMember(false) {}\n  };\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  Egroup(eos::common::SteadyClock* clock = nullptr);\n\n  //----------------------------------------------------------------------------\n  //! Destructor - join asynchronous refresh thread\n  //----------------------------------------------------------------------------\n  virtual ~Egroup();\n\n  //----------------------------------------------------------------------------\n  //! Reset all stored information\n  //----------------------------------------------------------------------------\n  void Reset();\n\n  //----------------------------------------------------------------------------\n  // Major query method - uses cache\n  //----------------------------------------------------------------------------\n  CachedEntry query(const std::string& username, const std::string& egroupname);\n\n  //----------------------------------------------------------------------------\n  // Convenience function, method to check if username is member in egroupname\n  //----------------------------------------------------------------------------\n  bool Member(const std::string& username, const std::string& egroupname)\n  {\n    return query(username, egroupname).isMember;\n  }\n\n  //----------------------------------------------------------------------------\n  // Display information about this specific username / groupname pair\n  //----------------------------------------------------------------------------\n  std::string DumpMember(const std::string& username,\n                         const std::string& egroupname);\n\n  //----------------------------------------------------------------------------\n  // Display all cached information\n  //----------------------------------------------------------------------------\n  std::string DumpMembers();\n\n  //----------------------------------------------------------------------------\n  // static function to schedule an asynchronous refresh for egroup/username\n  //----------------------------------------------------------------------------\n  void scheduleRefresh(const std::string& username,\n                       const std::string& egroupname);\n\n  //----------------------------------------------------------------------------\n  // Fetch cached value, and fills variable out if a result is found.\n  // Return false if item does not exist in cache.\n  //----------------------------------------------------------------------------\n  bool fetchCached(const std::string& username,\n                   const std::string& egroupname, CachedEntry& out);\n\n  //----------------------------------------------------------------------------\n  // Inject item into the fake LDAP server. If injections are active, any time\n  // this class tries to contact the LDAP server, it will serve injected data\n  // instead.\n  //\n  // Simulates response of \"isMemberUncached\" function.\n  //----------------------------------------------------------------------------\n  void inject(const std::string& username, const std::string& egroupname,\n              Status status);\n\n  //----------------------------------------------------------------------------\n  // Return number of asynchronous refresh requests currently pending\n  //----------------------------------------------------------------------------\n  size_t getPendingQueueSize() const;\n\n  //----------------------------------------------------------------------------\n  //! Synchronous refresh function doing an LDAP query for a given Egroup/user.\n  //! If the pair exists in the cache, it is ignored and replaced.\n  //----------------------------------------------------------------------------\n  CachedEntry refresh(const std::string& username,\n                      const std::string& egroupname);\n\nprivate:\n  const std::chrono::seconds kCacheDuration {\n    1800\n  };\n  eos::common::SteadyClock* clock = nullptr;\n\n  //----------------------------------------------------------------------------\n  //! Store entry into the cache\n  //----------------------------------------------------------------------------\n  void storeIntoCache(const std::string& username,\n                      const std::string& egroupname, bool isMember,\n                      std::chrono::steady_clock::time_point timestamp);\n\n  /// async refresh thread\n  AssistedThread mThread;\n\n  //----------------------------------------------------------------------------\n  //! Check if cache entry is stale\n  //----------------------------------------------------------------------------\n  bool isStale(const CachedEntry& entry) const;\n\n  //----------------------------------------------------------------------------\n  //! Asynchronous thread loop doing egroup/username fetching\n  //----------------------------------------------------------------------------\n  void Refresh(ThreadAssistant& assistant) noexcept;\n\n  /// mutex protecting static Egroup objects\n  eos::common::RWMutex mMutex;\n\n  /// map indicating egroup memebership for egroup/username pairs\n  std::map<std::string, std::map<std::string, CachedEntry>> cache;\n\n  /// thred-safe queue keeping track of pending refresh requests\n  qclient::WaitableQueue<std::pair<std::string, std::string>, 500> mPendingQueue;\n  //! Set of pending requests in the form <username>:<egroup>\n  std::unordered_set<std::string> mPendingSet;\n  //! Mutex protecting the pending set\n  mutable std::mutex mMutexPending;\n\n  /// injections to simulate LDAP server responses - different than the cache\n  std::map<std::string, std::map<std::string, Status>> injections;\n\n  //----------------------------------------------------------------------------\n  //! Main LDAP lookup function - bypasses the cache, hits the LDAP server.\n  //----------------------------------------------------------------------------\n  Status isMemberUncached(const std::string& username,\n                          const std::string& egroupname);\n};\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/eos-repair-tool",
    "content": "#!/usr/bin/perl\n# ----------------------------------------------------------------------\n# File: eos-repair-tool\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n\n# ---------------------------------------------------------------------- #\n# Lightweight terminal tool to ease repair operations on EOS MGMs        #\n# ---------------------------------------------------------------------- #\n\nuse Term::ReadKey;\nReadMode('cbreak');\n\nmy $lfnhash=();\nmy $empty=();\nwhile (1) {\n    system(\"clear\");\n    printf(\"Actions:\\n\");\n    printf(\"     r      - grab fsck report\\n\");\n    printf(\"     f      - read fsck report from default file /tmp/eos.fsck.report and /tmp/eos.external.lfn\\n\");\n    printf(\"     p      - process fsck report\\n\");\n    printf(\"     u      - update fsck report\\n\");\n    printf(\"     s      - show fsck status\\n\");\n    $char = ReadKey(0);\n    last unless defined $char;\n    printf(\" Decimal: %d\\tHex: %x\\n\", ord($char), ord($char));\n    if ( $char eq \"u\" ) {\n\tprintf(\" ===> disabling fsck report and re-enabling to force a quick update\\n\");\n\tsystem(\"eos -b fsck disable\");\n\tsleep(1);\n\tsystem(\"eos -b fsck enable\");\n    }\n    if ( $char eq \"s\") {\n\tsystem(\"eos -b fsck stat\");\n    }\n    if ( $char eq \"r\") {\n\tprintf(\" ===> grab fsck report from MGM ...\\n\");\n\tsystem(\"eos -b fsck report -a -l > /tmp/eos.fsck.report\");\n    }\n    if ( ($char eq \"f\") || ($char eq \"p\") ) {\n\tif ( $char ne \"p\" ) {\n\t    my $extfile = \"/tmp/eos.external.lfn\";\n\t    if ( -r \"$extfile\" ) {\n\t\tprintf(\" ===> loading from $extfile ...\\n\");\n\t\topen IN, \"$extfile\";\n\t\twhile (<IN>) {\n\t\t    chomp $_;\n\t\t    $lfnhash->{\"\\\"external\\\"\"}->{$_}=1;\n\t\t}\n\t\tclose IN;\n\t    }\n\n\t    my $file = \"/tmp/eos.fsck.report\";\n\t    if ( -r \"$file\" ) {\n\t\tprintf(\" ===> loading from $file ...\\n\");\n\t\topen IN, \"$file\";\n\t\twhile (<IN>) {\n\t\t    my @args=split(\" \", $_);\n\t\t    my $item;\n\t\t    my $subhash;\n\t\t    foreach $item (@args) {\n\t\t\tmy ($key,$value) = split(\"=\",$item);\n\t\t\t$subhash->{$key} = $value;\n\t\t    }\n\t\t    my @lfns = split(\",\", $subhash->{\"lfn\"});\n\t\t    my $lfn;\n\t\t    foreach $lfn (@lfns) {\n\t\t\tif ($lfn ne \"\\\"undefined\\\"\") {\n\t\t\t    $lfnhash->{$subhash->{\"tag\"}}->{$lfn}=1;\n\t\t\t}\n#\t\t    print $subhash->{\"tag\"},\" $lfn \\n\";\n\t\t    }\n\t\t}\n\t\tclose IN;\n\t    } else {\n\t\tif ( (! -r \"$extfile\") && (! -r \"$file\") ) {\n\t\t    printf(\" ---- file does not exist!\\n\");\n\t\t    ReadMode('cbreak');\n\t\t    print \"<any key to continue>\\n\";\n\t\t    my $conf = ReadKey(0);\n\t\t}\n\t    }\n\t}\n\tmy @sets;\n\tmy $cnt=0;\n\tprint \"Set's to operate:\\n\";\n\tprint \"---------------------------------------------\\n\";\n\tfor my $key (keys %$lfnhash) {\n\t    $count = keys %{$lfnhash->{$key}};\n\t    printf \"[ $cnt ] $key \\t %d\\n\", $count;\n\t    push @sets, $key;\n\t    $cnt++;\n\t}\n\t\n\t$char = ReadKey(0);\n\tmy $nfile=0;\n\tmy @lfns;\n\tfor my $lfn (keys %{$lfnhash->{@sets[$char]}}) {\n\t    push @lfns, $lfn;\n\t}\n\t\n\tmy $index=1;\n\twhile (1) {\n\t    $count = $#lfns + 1;\n\t    system(\"clear\");\n\t    my $lfn = @lfns[$index-1];\n\t    \n\t    print \"###################################################################################################################\\n\";\n\t    print \"==> Set to in use is @sets[$char]\\n\";\n\t    print \"###################################################################################################################\\n\";\n\t    printf \" LFN ( %10s / %-10s ) $lfn\\n\", $index, $count;\n\t    print \"\\n\";\n\t    print \"-------------------------------------------------------------------------------------------------------------------\\n\";\n\t    print \"- file info -------------------------------------------------------------------------------------------------------\\n\";\n\t    print \"-------------------------------------------------------------------------------------------------------------------\\n\";\n\t    system(\"eos -b file info $lfn\");\n\t    print \"\\n\";\n\t    print \"-------------------------------------------------------------------------------------------------------------------\\n\";\n\t    print \"- file check -------------------------------------------------------------------------------------------------------\\n\";\n\t    print \"-------------------------------------------------------------------------------------------------------------------\\n\";\n\t    system(\"eos -b file check $lfn\");\n\t    print \"-------------------------------------------------------------------------------------------------------------------\\n\";\n\t    print \"\\n\";\n\t    print \"- error \\n\";\n\t    print \"-------------------------------------------------------------------------------------------------------------------\\n\";\n\t    system(\"eos -b file check $lfn %silent%output%size%checksum%checksumattr%nrep\");\n\t    print \"###################################################################################################################\\n\";\n\t    print \" Options:\\n\";\n\t    print \"   <Space>                             : update information\\n\";\n\t    print \"     'n'                               : next file\\n\";\n\t    print \"     'p'                               : previous file\\n\";\n\t    print \"     'q'                               : leave operation mode\\n\";\n\t    print \"     'r'                               : rescan all and remove entries which don't show an error with 'eos file check'\\n\";\n\t    print \"     --- \\n\";\n\t    print \"     'a'                               : send adjustreplica\\n\";\n\t    print \"     'A'                               : send adjustreplica to all in this set\\n\";\n\t    print \"     'D'                               : drop all files with statsize=-1 or statsize=0\\n\";\n\t    print \"     'V'                               : send verify to all in this set\\n\";\n\t    print \"     'd'                               : drop a replica\\n\";\n\t    print \"     'v'                               : send verify\\n\";\n\t    print \"     'c'                               : send verify + checksum\\n\";\n\t    print \"     'C'                               : send verify + commit checksum + size\\n\";\n\t    print \"     'X'                               : send verify + commit checksum\\n\";\n\t    print \"     'E'                               : run check on all files and remove fixed files from edit set\\n\";\n\t    print \"     'e'                               : export file list of current set to /tmp/eos.set.lfn\\n\";\n\t    print \"     'y'                               : show check with %checksum attribute\\n\";\n\t    \n\t    my $wkey = ReadKey(0);\n\t    if ($wkey eq \"n\") {\n\t\t$index++;\n\t\tif ($index > $count) {\n\t\t    print \" WARNING => you are already editing the last file\\n\";\n\t\t\tsleep(2);\n\t\t    $index--;\n\t\t}\n\t    }\n\t    if ($wkey eq \"p\") {\n\t\t$index--;\n\t\tif ($index < 1) {\n\t\t    print \" WARNING => you are already editing the first file\\n\";\n\t\t    sleep(2);\n\t\t    $index++;\n\t\t}\n\t    }\n\t    if ($wkey eq \"a\") {\n\t\tsystem(\"eos -b file adjustreplica $lfn\");\n\t    }\n\t    \n\t    if ($wkey eq \"v\" ) {\n\t\tsystem(\"eos -b file verify $lfn\");\n\t    }\n\t    \n\t    if ($wkey eq \"c\") {\n\t\tsystem(\"eos -b file verify $lfn -checksum\");\n\t    }\n\t    \n\t    if ($wkey eq \"C\") {\n\t\tsystem(\"eos -b file verify $lfn -checksum -commitsize -commitchecksum\");\n\t    }\n\t    \n\t    if ($wkey eq \"D\") {\n\t\tfor my $n (@lfns) {\n\t\t    print \"=> Processing $n\\n\";\n                    open IN, \"eos -b file check $n|\";\n                    my $mode=0;\n                    while (<IN>) {\n\t\t\t\n\t\t\tif ($_ =~ /^path=/) {\n\t\t\t    $mode=0;\n\t\t\t} else {\n\t\t\t    $mode++;\n\t\t\t}\n\t\t\t# grep the path info\n\t\t\tmy @keyval= split (\" \", $_);\n\t\t\tfor my $s (@keyval) {\n\t\t\t    my ($key,$val) = split( \"=\",$s);\n\t\t\t    $val =~ s/\\\"//g;\n\t\t\t    if (\"$mode\" eq \"0\") {\n\t\t\t\t$hash->{\"generic\"}->{\"$key\"} = $val;\n\t\t\t    } else {\n\t\t\t\t$hash->{\"replica\"}->{\"$mode\"}->{\"$key\"}=$val;\n\t\t\t    }\n\t\t\t}\n                    }\n                    if ($mode ne \"2\") {\n\t\t\tprintf \"==> nothing to do for $n - only one replica\\n\";\n\t\t    }\n                    # check which one to drop\n#\t\t    print \"$hash->{'replica'}->{'1'}->{'statsize'} $hash->{'replica'}->{'2'}->{'statsize'} \\n\";\n\t\t    \n                    if  ( ( ($hash->{\"replica\"}->{'1'}->{\"statsize\"} eq \"0\") || (($hash->{\"replica\"}->{'1'}->{\"statsize\"} eq \"-1\") ) ) && (hash->{\"replica\"}->{'2'}->{\"statsize\"} ne \"0\") && (hash->{\"replica\"}->{'2'}->{\"statsize\"} ne \"-1\") ) {\n\t\t\t# drop replica 0\n\t\t\tmy $p = $hash->{\"generic\"}->{\"path\"};\n\t\t\tsystem(\"eos -b file drop $p $hash->{'replica'}->{'1'}->{'fsid'}\");\n\t\t    }  else {\n\t\t\tif  ( ( ($hash->{\"replica\"}->{'2'}->{\"statsize\"} eq \"0\") || ($hash->{\"replica\"}->{'2'}->{\"statsize\"} eq \"-1\") )  && (hash->{\"replica\"}->{'1'}->{\"statsize\"} ne \"0\") && (hash->{\"replica\"}->{'1'}->{\"statsize\"} ne \"-1\") ) {\n\t\t\t    # drop replica 0\n\t\t\t    my $p = $hash->{\"generic\"}->{\"path\"};\n\t\t\t    system(\"eos -b file drop $p $hash->{'replica'}->{'2'}->{'fsid'}\");\n\t\t\t} else {\n\t\t\t    if ( int(($hash->{\"replica\"}->{'1'}->{\"statsize\"}) > 0 ) && ( int ($hash->{\"replica\"}->{'2'}->{\"statsize\"} > 0))) {\n\t\t\t\tmy $p = $hash->{\"generic\"}->{\"path\"};\n\t\t\t\tif ( int(($hash->{\"replica\"}->{'1'}->{\"statsize\"}) ) > ( int ($hash->{\"replica\"}->{'2'}->{\"statsize\"} ))) {\n                                    system(\"eos -b file drop $p $hash->{'replica'}->{'2'}->{'fsid'}\");\n                                    system(\"eos -b file verify $p -checksum -commitsize -commitchecksum\");\n                                    print \"$hash->{'replica'}->{'1'}->{'statsize'} $hash->{'replica'}->{'2'}->{'statsize'} \\n\";\n                                }  else {\n                                    if ( int(($hash->{\"replica\"}->{'1'}->{\"statsize\"}) ) < ( int ($hash->{\"replica\"}->{'2'}->{\"statsize\"} ))) {\n                                        system(\"eos -b file drop $p $hash->{'replica'}->{'1'}->{'fsid'}\");\n                                        system(\"eos -b file verify $p -checksum -commitsize -commitchecksum\");\n                                        print \"$hash->{'replica'}->{'1'}->{'statsize'} $hash->{'replica'}->{'2'}->{'statsize'} \\n\";\n                                    }\n                                }\n\t\t\t    } else {\n\t\t\t\tprint \"!!> nothing we can do for $n\\n\";\n\t\t\t\tprint \"$hash->{'replica'}->{'1'}->{'statsize'} $hash->{'replica'}->{'2'}->{'statsize'} \\n\";\n\t\t\t    }\n\t\t\t}\n\t\t    }\n\t\t    \n                    close IN;\n\t\t}\n\t\tReadMode('cbreak');\n\t\tprint \"<any key to continue>\\n\";\n\t\tmy $conf = ReadKey(0);\n\t    }\n\t    \n\t    if ($wkey eq \"E\") {\n\t\tmy $lcnt=0;\n\t\tmy $gcnt=0;\n\t\tprint \"---- running check loop ...\\n\";\n\t\tmy @reducedlfns;\n\t\tfor my $n (@lfns) {\n\t\t    $gcnt++;\n\t\t    system(\"eos -b stat $n >& /dev/null\");\n\t\t    if ( $? != 0) {\n\t\t\t$lcnt++;\n\t\t\tprint \"miss: $n \\n\";\n\t\t\tpush @reducedlfns, $n;\n\t\t\tnext;\n\t\t    } \n\t\t    system(\"eos -b file check $n %silent%output%size%checksum%checksumattr%nrep 2>/dev/null | grep INCON >& /dev/null\");\n\t\t    if ( $? == 0) {\n\t\t\t$lcnt++;\n\t\t\tprint \"cons: $n \\n\";\n\t\t\tpush @reducedlfns, $n;\n\t\t\tnext;\n\t\t    }\n\t\t}\n\t\tprint \"---- finished check loop ( error=$lcnt total=$gcnt ) ...\\n\";\n\t\t@lfns = @reducedlfns;\n\t\t$index=1;\n\t\tReadMode('cbreak');\n\t\tprint \"<any key to continue>\\n\";\n\t\tmy $conf = ReadKey(0);\n\t    }\n\t    \n\t    if ($wkey eq \"e\") {\n\t\tprint \"---- exporting lfn's to /tmp/eos.set.lfn\\n\";\n\t\tsystem(\"unlink /tmp/eos.set.lfn>/dev/null;\");\n\t\tforeach (@lfns) {\n\t\t    system(\"echo \\\"$_\\\" >> /tmp/eos.set.lfn\");\n\t\t} \n\t\tReadMode('cbreak');\n\t\tprint \"<any key to continue>\\n\";\n\t\tmy $conf = ReadKey(0);\n\t    }\n\n\t    if ($wkey eq \"X\") {\n\t\tsystem(\"eos -b file verify $lfn -checksum -commitchecksum\");\n\t    }\n\t    \n\t    if ($wkey eq \"A\") {\n\t\tfor my $n (@lfns) {\n\t\t    system(\"eos -b file adjustreplica $n\");\n\t\t}\n\t    }\n\t    \n\t    if ($wkey eq \"V\") {\n\t\tfor my $n (@lfns) {\n\t\t    system(\"eos -b file verify $n -checksum\");\n\t\t}\t\n\t    }   \t\n\t    \n\t    if ($wkey eq \"d\") {\n\t\tprintf \" Enter Filesystem ID where to drop : \";\n\t\tReadMode('normal');\n\t\tmy $replica = <STDIN>;\n\t\tchomp $replica;\n\t\tReadMode('cbreak');\n\t\tprint \"? Drop replica on filesystem $replica ? <y/n> \\n\";\n\t\tmy $conf = ReadKey(0);\n\t\tif ($conf eq \"y\") {\n\t\t    system(\"eos -b file drop $lfn $replica\");\n\t\t}  else {\n\t\t    \n\t\t    print \" <drop aborted\\n\";\n\t\t    sleep(1);\n\t\t}\n\t    }\n\n\t    if ($wkey eq \"y\") {\n\t\tsystem(\"eos -b file check $lfn %checksumattr\");\n\t\tReadKey(0);\n\t    }\n\n\t    if ($wkey eq \"q\" ) {\n\t\tlast;\n\t    }\t\t\n\t}\n    }\n    ReadMode('cbreak');\n    print \"<any key to continue>\\n\";\n    $char = ReadKey(0);\n    last unless defined $char;\n}\n\nReadMode('normal');\n"
  },
  {
    "path": "mgm/features/Features.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Features.cc\n// Author: Geoffray Adde - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n/*----------------------------------------------------------------------------*/\n#include \"mgm/features/Features.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\nstatic std::string checkInodeScheme() {\n  if(getenv(\"EOS_USE_NEW_INODES\") != nullptr && getenv(\"EOS_USE_NEW_INODES\")[0] == '1') {\n    return \"1\";\n  }\n\n  return \"0\";\n}\n\nconst std::map< const std::string, const std::string> Features::sMap =\n{\n  { \"eos.encodepath\", \"curl\" },\n  { \"eos.lazyopen\",   \"true\" },\n  { \"eos.inodeencodingscheme\", checkInodeScheme() }\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/features/Features.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ZMQ.hh\n// Author: Geoffray Adde - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef __EOSMGM_FEATURES__HH__\n#define __EOSMGM_FEATURES__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"mgm/Namespace.hh\"\n#include <string>\n#include <map>\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\nclass Features\n{\npublic:\n  static const std::map< const std::string, const std::string> sMap;\n};\n\nEOSMGMNAMESPACE_END\n\n#endif\n\n"
  },
  {
    "path": "mgm/filesystem/FileSystem.cc",
    "content": "// ----------------------------------------------------------------------\n// File: FileSystem.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/filesystem/FileSystem.hh\"\n#include \"mq/MessagingRealm.hh\"\n#include \"mq/FsChangeListener.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"qclient/shared/SharedHashSubscription.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nconst std::string FileSystem::sNumBalanceTxTag = \"local.balancer.running\";\nconst std::string FileSystem::sGeotagTag = \"stat.geotag\";\nconst std::string FileSystem::sErrcTag = \"stat.errc\";\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFileSystem::FileSystem(const common::FileSystemLocator& locator,\n                       mq::MessagingRealm* msr) :\n  eos::common::FileSystem(locator, msr)\n{\n  eos_static_info(\"msg=\\\"create file system\\\" queue_path=%s\",\n                  locator.getQueuePath().c_str());\n  // Register with FsChangeListeners interested in key updates related\n  // to this file system object\n  RegisterWithExistingListeners();\n  // Subscribe to the underlying SharedHash object to get updates\n  mSubscription = mq::SharedHashWrapper(mRealm, mHashLocator).subscribe();\n\n  if (mSubscription) {\n    using namespace std::placeholders;\n    mSubscription->attachCallback(std::bind(&FileSystem::ProcessUpdateCb,\n                                            this, _1));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nFileSystem::~FileSystem()\n{\n  // Make sure we wait for any ongoing callbacks\n  if (mSubscription) {\n    mSubscription->detachCallback();\n  }\n\n  UnregisterFromListeners();\n}\n\n//------------------------------------------------------------------------------\n// Register with interested listeners - called when a new object is created\n// and there are already existing FS listeners in the system\n//------------------------------------------------------------------------------\nvoid\nFileSystem::RegisterWithExistingListeners()\n{\n  auto map_interests = mRealm->GetInterestedListeners(mLocator.getQueuePath());\n\n  for (auto& elem : map_interests) {\n    auto& fs_listener = elem.first;\n    const auto& set_interests = elem.second;\n    eos_static_info(\"msg=\\\"register with existing fs listener\\\" listener=%s \"\n                    \"fs_queue_path=%s\", fs_listener->GetName().c_str(),\n                    mLocator.getQueuePath().c_str());\n    eos::common::RWMutexWriteLock wr_lock(mRWMutex);\n\n    for (const auto& interest : set_interests) {\n      mMapListeners[interest].insert(fs_listener);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Unregister from all the listeners\n//------------------------------------------------------------------------------\nvoid\nFileSystem::UnregisterFromListeners()\n{\n  eos::common::RWMutexWriteLock wr_lock(mRWMutex);\n\n  for (const auto& elem : mMapListeners) {\n    for (auto& listener : elem.second) {\n      eos_static_info(\"msg=\\\"unsubscribe and detach from listener\\\" \"\n                      \"interest=\\\"%s\\\" listener_name=\\\"%s\\\" fs_queue_path=%s \",\n                      elem.first.c_str(), listener->GetName().c_str(),\n                      mLocator.getQueuePath().c_str());\n      listener->unsubscribe(mLocator.getQueuePath(), {elem.first});\n    }\n  }\n\n  mMapListeners.clear();\n}\n\n//------------------------------------------------------------------------------\n// Attach file system change listener\n//------------------------------------------------------------------------------\nbool\nFileSystem::AttachFsListener(std::shared_ptr<eos::mq::FsChangeListener>\n                             fs_listener,\n                             const std::set<std::string>& interests)\n{\n  if ((fs_listener == nullptr) || interests.empty()) {\n    return false;\n  }\n\n  eos_static_info(\"msg=\\\"attaching fs listener\\\" listener_name=%s \"\n                  \"fs_queue_path=%s\", fs_listener->GetName().c_str(),\n                  mLocator.getQueuePath().c_str());\n  // Update the listener\n  fs_listener->subscribe(mLocator.getQueuePath(), interests);\n  eos::common::RWMutexWriteLock wr_lock(mRWMutex);\n\n  for (const auto& interest : interests) {\n    mMapListeners[interest].insert(fs_listener);\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Detach file system change listener\n//------------------------------------------------------------------------------\nbool\nFileSystem::DetachFsListener(std::shared_ptr<eos::mq::FsChangeListener>\n                             fs_listener,\n                             const std::set<std::string>& interests)\n{\n  if ((fs_listener == nullptr) || interests.empty()) {\n    return false;\n  }\n\n  eos_static_info(\"msg=\\\"detaching fs listener\\\" listener_name=%s \"\n                  \"fs_queue_path=%s\", fs_listener->GetName().c_str(),\n                  mLocator.getQueuePath().c_str());\n  // Update the listener\n  (void) fs_listener->unsubscribe(mLocator.getQueuePath(), interests);\n  eos::common::RWMutexWriteLock wr_lock(mRWMutex);\n\n  for (const auto& interest : interests) {\n    auto it = mMapListeners.find(interest);\n\n    // Erase listener\n    if (it != mMapListeners.end()) {\n      it->second.erase(fs_listener);\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Notify file system change listener interested in the given update\n//------------------------------------------------------------------------------\nvoid\nFileSystem::NotifyFsListener(qclient::SharedHashUpdate&& upd)\n{\n  eos::common::RWMutexReadLock rd_lock(mRWMutex);\n  auto it = mMapListeners.find(upd.key);\n\n  if (it != mMapListeners.end()) {\n    eos::mq::FsChangeListener::Event event;\n    event.fileSystemQueue = GetQueuePath();\n    event.key = upd.key;\n    event.deletion = upd.value.empty();\n\n    for (auto& listener : it->second) {\n      listener->NotifyEvent(event);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Process shared hash update\n//------------------------------------------------------------------------------\nvoid\nFileSystem::ProcessUpdateCb(qclient::SharedHashUpdate&& upd)\n{\n  NotifyFsListener(std::move(upd));\n}\n\n//----------------------------------------------------------------------------\n// Set the configuration status of a file system\n//----------------------------------------------------------------------------\nbool\nFileSystem::SetConfigStatus(eos::common::ConfigStatus new_status)\n{\n  using eos::mgm::FsView;\n  using eos::common::DrainStatus;\n  eos::common::ConfigStatus old_status = GetConfigStatus();\n  int drain_tx = IsDrainTransition(old_status, new_status);\n\n  // Only master drains and updates the configuration status\n  if (ShouldBroadCast()) {\n    std::string out_msg;\n\n    if (drain_tx > 0) {\n      if (!gOFS->mDrainEngine.StartFsDrain(this, 0, out_msg)) {\n        eos_static_err(\"%s\", out_msg.c_str());\n        return false;\n      }\n    } else {\n      if (!gOFS->mDrainEngine.StopFsDrain(this, out_msg)) {\n        eos_static_debug(\"%s\", out_msg.c_str());\n        // Drain already stopped make sure we also update the drain status\n        // if this was a finished drain ie. has status drained or failed\n        DrainStatus st = GetDrainStatus();\n\n        if ((st == DrainStatus::kDrained) ||\n            (st == DrainStatus::kDrainFailed) ||\n            (st == DrainStatus::kDrainExpired)) {\n          SetDrainStatus(eos::common::DrainStatus::kNoDrain);\n        }\n      }\n    }\n\n    std::string val = eos::common::FileSystem::GetConfigStatusAsString(new_status);\n    return eos::common::FileSystem::SetString(\"configstatus\", val.c_str());\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Set a 'key' describing the filesystem\n//------------------------------------------------------------------------------\nbool\nFileSystem::SetString(const char* key, const char* str, bool broadcast)\n{\n  std::string skey = key;\n\n  if (skey == \"configstatus\") {\n    return SetConfigStatus(GetConfigStatusFromString(str));\n  }\n\n  return eos::common::FileSystem::SetString(key, str, broadcast);\n}\n\n//------------------------------------------------------------------------------\n// Check if this is a drain transition i.e. enables or disabled draining\n//------------------------------------------------------------------------------\nint\nFileSystem::IsDrainTransition(const eos::common::ConfigStatus old,\n                              const eos::common::ConfigStatus status)\n{\n  using namespace eos::common;\n\n  // Enable draining\n  if (((old != ConfigStatus::kDrain) &&\n       (old != ConfigStatus::kDrainDead) &&\n       ((status == ConfigStatus::kDrain) ||\n        (status == ConfigStatus::kDrainDead))) ||\n      (((old == ConfigStatus::kDrain) ||\n        (old == ConfigStatus::kDrainDead)) &&\n       (status == old))) {\n    return 1;\n  }\n\n  // Stop draining\n  if (((old == common::ConfigStatus::kDrain) ||\n       (old == common::ConfigStatus::kDrainDead)) &&\n      ((status != common::ConfigStatus::kDrain) &&\n       (status != common::ConfigStatus::kDrainDead))) {\n    return -1;\n  }\n\n  // Not a drain transition\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Get the current broadcasting setting\n//------------------------------------------------------------------------------\nbool\nFileSystem::ShouldBroadCast()\n{\n  return (mRealm ? mRealm->ShouldBroadcast() : false);\n}\n\n//------------------------------------------------------------------------------\n// Increment number of running balancing transfers\n//------------------------------------------------------------------------------\nvoid\nFileSystem::IncrementBalanceTx()\n{\n  ++mNumBalanceTx;\n  SetLongLongLocal(sNumBalanceTxTag, (int64_t)mNumBalanceTx);\n}\n\n//------------------------------------------------------------------------------\n// Decrement number of running balancing transfers\n//------------------------------------------------------------------------------\nvoid\nFileSystem::DecrementBalanceTx()\n{\n  --mNumBalanceTx;\n  SetLongLongLocal(sNumBalanceTxTag, (int64_t)mNumBalanceTx);\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/filesystem/FileSystem.hh",
    "content": "// ----------------------------------------------------------------------\n// File: FileSystem.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/FileSystem.hh\"\n#include \"common/Logging.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"mq/FsChangeListener.hh\"\n\n//! Forward declarations\nnamespace eos\n{\nnamespace mq\n{\nclass MessagingRealm;\n}\n}\n\nnamespace qclient\n{\nclass SharedHashUpdate;\n}\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class representing a filesystem on the MGM\n//------------------------------------------------------------------------------\nclass FileSystem : public eos::common::FileSystem, public eos::common::LogId\n{\npublic:\n  //! Tag for saving number of running balance transfers in hash\n  static const std::string sNumBalanceTxTag;\n\n  //----------------------------------------------------------------------------\n  //! Check if this is a drain transition i.e. enables or disabled draining\n  //!\n  //! @param new_status new configuration status to be set\n  //! @param new_status new configuration status to be set\n  //!\n  //! @return 1 if draining enabled, -1 if draining disabled, 2 if draining\n  //!         should be restarted, 0 if not a drain transition\n  //----------------------------------------------------------------------------\n  static\n  int IsDrainTransition(const eos::common::ConfigStatus old_status,\n                        const eos::common::ConfigStatus new_status);\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param locator file system locator\n  //! @param msr messaging realm\n  //----------------------------------------------------------------------------\n  FileSystem(const common::FileSystemLocator& locator, mq::MessagingRealm* msr);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~FileSystem();\n\n  //----------------------------------------------------------------------------\n  //! Attach file system change listener\n  //!\n  //! @param fs_listener file system change listener object\n  //! @param interests set of keys which are of interest for the listener\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool AttachFsListener(std::shared_ptr<eos::mq::FsChangeListener> fs_listener,\n                        const std::set<std::string>& interests);\n\n  //----------------------------------------------------------------------------\n  //! Detach file system change listener\n  //!\n  //! @param fs_listener file system change listener object\n  //! @param interests set of interests from which to detach\n  //!\n  //! @param return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool DetachFsListener(std::shared_ptr<eos::mq::FsChangeListener> fs_listener,\n                        const std::set<std::string>& interests);\n\n  //----------------------------------------------------------------------------\n  //! @brief Get the current broadcasting setting\n  //!\n  //! @return true if broadcasting otherwise false\n  //----------------------------------------------------------------------------\n  bool ShouldBroadCast();\n\n  //----------------------------------------------------------------------------\n  //! Set the configuration status of a file system. This can be used to trigger\n  //! the draining.\n  //! @note Must be called with a lock on FsView::ViewMutex\n  //!\n  //! @param status file system status\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool SetConfigStatus(eos::common::ConfigStatus status);\n\n  //----------------------------------------------------------------------------\n  //! Set a 'key' describing the filesystem\n  //! @note Must be called with a lock on FsView::ViewMutex\n  //!\n  //! @param key key to set\n  //! @param str value of the key\n  //! @param broadcast if true broadcast the change around\n  //!\n  //! @return true if successful otherwise false\n  //----------------------------------------------------------------------------\n  bool SetString(const char* key, const char* str, bool broadcast = true);\n\n  //----------------------------------------------------------------------------\n  //! Increment number of running balancing transfers\n  //----------------------------------------------------------------------------\n  void IncrementBalanceTx();\n\n  //----------------------------------------------------------------------------\n  //! Decrement number of running balancing transfers\n  //----------------------------------------------------------------------------\n  void DecrementBalanceTx();\n\nprivate:\n  static const std::string sGeotagTag;\n  static const std::string sErrcTag;\n  //! Number of running balance transfers\n  std::atomic<uint64_t> mNumBalanceTx {0};\n  //! Subscription to underlying shared hash notifications\n  std::unique_ptr<qclient::SharedHashSubscription> mSubscription;\n  //! Map of interests to file system change notifiers\n  std::map<std::string, std::set<std::shared_ptr<eos::mq::FsChangeListener>>>\n  mMapListeners;\n  //! Mutex protecting the listener's map\n  eos::common::RWMutex mRWMutex;\n\n  //----------------------------------------------------------------------------\n  //! Process shared hash update\n  //!\n  //! @param upd shared hash update\n  //----------------------------------------------------------------------------\n  void ProcessUpdateCb(qclient::SharedHashUpdate&& upd);\n\n  //----------------------------------------------------------------------------\n  //! Notify file system change listeners interested in the given update\n  //!\n  //! @param upd shared hash update\n  //----------------------------------------------------------------------------\n  void NotifyFsListener(qclient::SharedHashUpdate&& upd);\n\n  //----------------------------------------------------------------------------\n  //! Register with interested listeners - this called when a new object is\n  //! created and there are already existing FS listeners in the system\n  //----------------------------------------------------------------------------\n  void RegisterWithExistingListeners();\n\n  //----------------------------------------------------------------------------\n  //! Unregister from all the listeners\n  //----------------------------------------------------------------------------\n  void UnregisterFromListeners();\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/fsck/Fsck.cc",
    "content": "//------------------------------------------------------------------------------\n// File: Fsck.cc\n// Author: Andreas-Joachim Peters/Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/fsck/Fsck.hh\"\n#include \"common/Timing.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/Path.hh\"\n#include \"common/Mapping.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringUtils.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/interface/IFsView.hh\"\n#include \"namespace/Prefetcher.cc\"\n#include \"qclient/structures/QSet.hh\"\n#include \"json/json.h\"\n\nEOSMGMNAMESPACE_BEGIN\n\nconst std::string Fsck::sOldCollectKey {\"toggle-collect\"};\nconst std::string Fsck::sOldRepairKey {\"toggle-repair\"};\nconst std::string Fsck::sOldBestEffortKey {\"toggle-best-effort\"};\nconst std::string Fsck::sFsckKey {\"fsck\"};\nconst std::string Fsck::sCollectKey {\"collect\"};\nconst std::string Fsck::sRepairKey {\"repair\"};\nconst std::string Fsck::sBestEffortKey {\"best-effort\"};\nconst std::string Fsck::sCollectIntervalKey {\"collect-interval-min\"};\nconst std::string Fsck::sRepairCategory {\"repair-category\"};\n\nusing eos::common::FsckErr;\nusing eos::common::ConvertToFsckErr;\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFsck::Fsck():\n  mShowOffline(false), mShowNoReplica(false), mShowDarkFiles(false),\n  mStartProcessing(false), mCollectEnabled(false), mRepairEnabled(false),\n  mCollectRunning(false), mRepairRunning(false), mDoBestEffort(false),\n  mRepairCategory(FsckErr::None),\n  mCollectInterval(std::chrono::seconds(30 * 60)),\n  eTimeStamp(0),\n  mThreadPool(2, mMaxThreadPoolSize, 10, 6, 5, \"fsck\")\n{}\n\n//------------------------------------------------------------------------------\n// Stop all fsck related threads and activities\n//------------------------------------------------------------------------------\nvoid Fsck::Stop()\n{\n  mRepairThread.join();\n  mCollectorThread.join();\n}\n\n//------------------------------------------------------------------------------\n// Apply the FSCK configuration stored in the configuration engine\n//------------------------------------------------------------------------------\nvoid\nFsck::ApplyConfig(FsView* fsview)\n{\n  using eos::common::StringTokenizer;\n  // Parse config of the form: key1=val1 key2=val2 etc.\n  std::string config = fsview->GetGlobalConfig(sFsckKey);\n  eos_info(\"msg=\\\"apply fsck configuration\\\" data=\\\"%s\\\"\", config.c_str());\n  std::map<std::string, std::string> kv_map;\n  auto pairs = StringTokenizer::split<std::list<std::string>>(config, ' ');\n\n  for (const auto& pair : pairs) {\n    auto kv = StringTokenizer::split<std::vector<std::string>>(pair, '=');\n\n    if (kv.empty()) {\n      eos_err(\"msg=\\\"unknown fsck config data\\\" data=\\\"%s\\\"\", config.c_str());\n      continue;\n    }\n\n    if (kv.size() == 1) {\n      kv.emplace_back(\"\");\n    }\n\n    kv_map.emplace(kv[0], kv[1]);\n  }\n\n  // Apply the configuration to the fsck engine\n  std::string msg;\n\n  if (kv_map.count(sOldCollectKey)) {\n    if (kv_map.count(sOldCollectKey) && kv_map.count(sCollectIntervalKey)) {\n      bool enable_collect = (kv_map[sOldCollectKey] == \"1\");\n\n      // Make fsck config enforcement idempotent. Note the value in the Config\n      // call is overloaded with the \"off\" string marking the fact that fsck\n      // collection should be disabled.\n      if (enable_collect != mCollectEnabled) {\n        Config(sOldCollectKey, (enable_collect ? kv_map[sCollectIntervalKey] : \"off\"),\n               msg);\n      }\n    }\n\n    if (kv_map.count(sOldRepairKey)) {\n      Config(sOldRepairKey, kv_map[sOldRepairKey], msg);\n    }\n\n    if (kv_map.count(sOldBestEffortKey)) {\n      Config(sOldBestEffortKey, kv_map[sOldBestEffortKey], msg);\n    }\n\n    if (kv_map.count(sRepairCategory)) {\n      Config(sRepairCategory, kv_map[sRepairCategory], msg);\n    }\n  } else { // We're dealing only with new key vlaues\n    for (const auto& [key, value] : kv_map) {\n      Config(key, value, msg);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Store the current running FSCK configuration in the config engine\n//------------------------------------------------------------------------------\nbool\nFsck::StoreConfig()\n{\n  using namespace std::chrono;\n  uint32_t collect_interval_min = duration_cast<minutes>\n                                  (mCollectInterval).count();\n\n  // Make sure the collection internval is at least one minute\n  if (collect_interval_min == 0) {\n    collect_interval_min = 1;\n  }\n\n  std::ostringstream oss;\n  oss << sCollectIntervalKey << \"=\" << collect_interval_min << \" \"\n      << sCollectKey << \"=\" << (mCollectEnabled ? \"on\" : \"off\") << \" \"\n      << sRepairKey << \"=\" << (mRepairEnabled ? \"on\" : \"off\")  << \" \"\n      << sBestEffortKey << \"=\" << (mDoBestEffort ? \"on\" : \"off\") << \" \"\n      << sRepairCategory  << \"=\" << eos::common::FsckErrToString(mRepairCategory);\n  return FsView::gFsView.SetGlobalConfig(sFsckKey, oss.str());\n}\n\n//------------------------------------------------------------------------------\n// Apply configuration options to the fsck mechanism\n//------------------------------------------------------------------------------\nbool\nFsck::Config(const std::string& key, const std::string& value, std::string& msg)\n{\n  // Make sure only one config runs at a time\n  static std::mutex mutex;\n  static std::set<std::string> old_keys {sOldCollectKey, sOldRepairKey,\n                                         sOldBestEffortKey};\n  static std::set<std::string> new_keys {sCollectKey, sRepairKey,\n                                         sBestEffortKey};\n  std::unique_lock<std::mutex> serialize_lock(mutex);\n\n  if (mQcl == nullptr) {\n    mQcl = std::make_shared<qclient::QClient>\n           (gOFS->mQdbContactDetails.members,\n            gOFS->mQdbContactDetails.constructOptions());\n  }\n\n  if (key == sCollectIntervalKey) {\n    // If value is present then it represents the collection interval\n    if (!value.empty()) {\n      try {\n        float fval = std::stof(value);\n\n        if (fval == 0) {\n          mCollectInterval = std::chrono::seconds(60);\n        } else if (fval < 1) {\n          mCollectInterval = std::chrono::seconds((long)std::ceil(fval * 60));\n        } else {\n          mCollectInterval = std::chrono::seconds((long)fval * 60);\n        }\n      } catch (...) {\n        mCollectInterval = std::chrono::seconds(30 * 60);\n      }\n\n      if (mCollectRunning) {\n        mCollectorThread.reset(&Fsck::CollectErrs, this);\n      }\n    } else {\n      return false;\n    }\n  } else if (new_keys.find(key) != new_keys.end()) {\n    if ((value != \"on\") && (value != \"off\")) {\n      msg = \"error: unknown configuration value\";\n      return false;\n    }\n\n    if (key == sBestEffortKey) {\n      mDoBestEffort = (value == \"on\");\n    } else if (key == sRepairKey) {\n      mRepairEnabled = (value == \"on\");\n\n      // We need to do a transition\n      if (mRepairRunning != mRepairEnabled) {\n        if (mRepairEnabled) {\n          if (!mCollectEnabled) {\n            mRepairEnabled = false;\n            msg = \"error: repair can not be enabled without error collection\";\n            return false;\n          }\n\n          mRepairThread.reset(&Fsck::RepairErrs, this);\n        } else {\n          mRepairThread.join();\n        }\n      }\n    } else if (key == sCollectKey) {\n      mCollectEnabled = (value == \"on\");\n\n      // We need to do a transition\n      if (mCollectRunning != mCollectEnabled) {\n        if (mCollectEnabled) {\n          mCollectorThread.reset(&Fsck::CollectErrs, this);\n        } else {\n          // Stop the collection and repair\n          if (mRepairEnabled) {\n            mRepairEnabled = false;\n            mRepairThread.join();\n          }\n\n          mCollectorThread.join();\n        }\n      }\n    }\n\n    if (!StoreConfig()) {\n      msg = \"error: failed to store fsck configuration changes\";\n      return false;\n    }\n  } else if (old_keys.find(key) != old_keys.end()) {\n    // Handling old keys for compatibility\n    if (key == sOldCollectKey) {\n      if (value == \"off\") {\n        mCollectEnabled = false;\n      } else {\n        mCollectEnabled = !mCollectEnabled;\n      }\n\n      if (mCollectEnabled == false) {\n        mRepairEnabled = false;\n\n        // Stop the collection and repair\n        if (mRepairRunning) {\n          mRepairThread.join();\n        }\n\n        if (mCollectRunning) {\n          mCollectorThread.join();\n        }\n      } else {\n        // If value is present then it represents the collection interval\n        if (!value.empty()) {\n          try {\n            float fval = std::stof(value);\n\n            if (fval == 0) {\n              mCollectInterval = std::chrono::seconds(60);\n            } else if (fval < 1) {\n              mCollectInterval = std::chrono::seconds((long)std::ceil(fval * 60));\n            } else {\n              mCollectInterval = std::chrono::seconds((long)fval * 60);\n            }\n          } catch (...) {\n            mCollectInterval = std::chrono::seconds(30 * 60);\n          }\n        }\n\n        if (!mCollectRunning) {\n          mCollectorThread.reset(&Fsck::CollectErrs, this);\n        }\n      }\n    } else if (key == sOldRepairKey) {\n      if (value.empty()) {\n        // User triggered repair toggle\n        mRepairEnabled = !mRepairRunning;\n      } else {\n        // Mandatory config coming from the stored configuration\n        mRepairEnabled = (value == \"1\");\n      }\n\n      if (mRepairEnabled == false) {\n        if (mRepairRunning) {\n          mRepairThread.join();\n        }\n      } else {\n        if (!mCollectEnabled) {\n          msg = \"error: repair can not be enabled without error collection\";\n          return false;\n        }\n\n        mRepairThread.reset(&Fsck::RepairErrs, this);\n      }\n    } else if (key == sOldBestEffortKey) {\n      if (value.empty()) {\n        // User triggered best-effort toggle\n        mDoBestEffort = !mDoBestEffort;\n      } else {\n        // Mandatory config coming from the stored configuration\n        mDoBestEffort = (value == \"1\");\n      }\n    }\n\n    if (!StoreConfig()) {\n      msg = \"error: failed to store fsck configuration changes\";\n      return false;\n    }\n  } else if (key == sRepairCategory) {\n    if (value == \"all\") {\n      mRepairCategory = FsckErr::None;\n    } else {\n      const auto tmp_cat = ConvertToFsckErr(value);\n\n      if (tmp_cat == FsckErr::None) {\n        msg = \"error: unknown repair category\";\n        return false;\n      } else {\n        mRepairCategory = tmp_cat;\n      }\n    }\n\n    if (!StoreConfig()) {\n      msg = \"error: failed to store fsck configuration changes\";\n      return false;\n    }\n  } else if (key == \"show-dark-files\") {\n    mShowDarkFiles = (value == \"on\");\n  } else if (key == \"show-offline\") {\n    mShowOffline = (value == \"on\");\n  } else if (key == \"show-no-replica\") {\n    mShowNoReplica = (value == \"on\");\n  } else if (key == \"max-queued-jobs\") {\n    try {\n      mMaxQueuedJobs = std::stoull(value);\n    } catch (...) {\n      eos_err(\"msg=\\\"failed to convert max-queued-jobs\\\" value=%s\",\n              value.c_str());\n    }\n  } else if (key == \"max-thread-pool-size\") {\n    try {\n      mMaxThreadPoolSize = std::stoul(value);\n      mThreadPool.SetMaxThreads(mMaxThreadPoolSize);\n    } catch (...) {\n      eos_err(\"msg=\\\"failed to convert max-thread-pool-size\\\" value=%s\",\n              value.c_str());\n    }\n  } else {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Looping thread function collecting FSCK results\n//------------------------------------------------------------------------------\nvoid\nFsck::CollectErrs(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"FsckCollector\");\n  mCollectRunning = true;\n  eos_info(\"%s\", \"msg=\\\"started fsck collector thread\\\"\");\n\n  if (mQcl == nullptr) {\n    eos_err(\"%s\", \"msg=\\\"failed to fsck repair thread without a qclient\\\"\");\n    Log(\"Fsck error collection disabled, missing QuarkDB configuration\");\n    mCollectRunning = false;\n    return;\n  }\n\n  gOFS->WaitUntilNamespaceIsBooted();\n\n  while (!assistant.terminationRequested()) {\n    // Wait for the current MGM to become a master\n    while (!gOFS->mMaster->IsMaster()) {\n      eos_debug(\"%s\", \"msg=\\\"fsck collect disabled for slave\\\"\");\n      assistant.wait_for(std::chrono::seconds(5));\n\n      if (assistant.terminationRequested()) {\n        eos_info(\"%s\", \"msg=\\\"stopped fsck collect thread\\\"\");\n        break;\n      }\n    }\n\n    if (assistant.terminationRequested()) {\n      break;\n    }\n\n    Log(\"Start error collection\");\n    Log(\"Filesystems to check: %lu\", FsView::gFsView.GetNumFileSystems());\n    decltype(eFsMap) tmp_err_map;\n    QueryQdb(tmp_err_map);\n    {\n      // Swap in the new list of errors and clear the rest\n      eos::common::RWMutexWriteLock wr_lock(mErrMutex);\n      std::swap(tmp_err_map, eFsMap);\n      eFsUnavail.clear();\n      eFsDark.clear();\n      eTimeStamp = time(NULL);\n    }\n\n    // @note accounting the offline replicas/files is a heavy ns op.\n    if (mShowOffline) {\n      AccountOfflineReplicas();\n      PrintOfflineReplicas();\n      AccountOfflineFiles();\n    }\n\n    // @note no replicas can be a really long list (e.g. PPS)\n    if (mShowNoReplica) {\n      AccountNoReplicaFiles();\n    }\n\n    PrintErrorsSummary();\n\n    // @note the following operation is a heavy ns op.\n    if (mShowDarkFiles) {\n      AccountDarkFiles();\n    }\n\n    Log(\"Finished error collection\");\n    Log(\"Next run in %d minutes\",\n        std::chrono::duration_cast<std::chrono::minutes>(mCollectInterval).count());\n    // Notify the repair thread that it can run now\n    mStartProcessing = true;\n    PublishLogs();\n    // Wait for next FSCK round ...\n    assistant.wait_for(mCollectInterval);\n  }\n\n  ResetErrorMaps();\n  Log(\"Stop error collection\");\n  PublishLogs();\n  eos_info(\"%s\", \"msg=\\\"stopped fsck collector thread\\\"\");\n  mCollectRunning = false;\n}\n\n//------------------------------------------------------------------------------\n// Method submitting fsck repair jobs to the thread pool\n//------------------------------------------------------------------------------\nvoid\nFsck::RepairErrs(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"FsckRepair\");\n  mRepairRunning = true;\n  eos_info(\"%s\", \"msg=\\\"started fsck repair thread\\\"\");\n\n  if (mQcl == nullptr) {\n    eos_err(\"%s\", \"msg=\\\"failed to fsck repair thread without a qclient\\\"\");\n    Log(\"Fsck error repair disabled, missing QuarkDB configuration\");\n    mRepairRunning = false;\n    return;\n  }\n\n  gOFS->WaitUntilNamespaceIsBooted();\n\n  while (!assistant.terminationRequested()) {\n    // Wait for the current MGM to become a master\n    while (!gOFS->mMaster->IsMaster()) {\n      eos_debug(\"%s\", \"msg=\\\"fsck repair disabled for slave\\\"\");\n      assistant.wait_for(std::chrono::seconds(5));\n\n      if (assistant.terminationRequested()) {\n        eos_info(\"%s\", \"msg=\\\"stopped fsck repair thread\\\"\");\n        break;\n      }\n    }\n\n    // Wait for the collector thread to signal us\n    while (!mStartProcessing && !assistant.terminationRequested()) {\n      assistant.wait_for(std::chrono::seconds(1));\n\n      if (assistant.terminationRequested()) {\n        eos_info(\"%s\", \"msg=\\\"stopped fsck repair thread\\\"\");\n        break;\n      }\n    }\n\n    if (assistant.terminationRequested()) {\n      break;\n    }\n\n    // Create local struct for errors so that we avoid the iterator invalidation\n    // and the long locks\n    std::map<std::string,\n        std::map<eos::common::FileId::fileid_t,\n        std::set <eos::common::FileSystem::fsid_t> > > local_emap;\n    {\n      eos::common::RWMutexReadLock rd_lock(mErrMutex);\n      local_emap.insert(eFsMap.begin(), eFsMap.end());\n    }\n    uint64_t count = 0ull;\n    uint64_t msg_delay = 0;\n    std::list<std::string> err_priority{\n      \"stripe_err\", \"blockxs_err\",   \"unreg_n\",\n      \"rep_diff_n\", \"rep_missing_n\", \"m_mem_sz_diff\",\n      \"m_cx_diff\",  \"d_mem_sz_diff\", \"d_cx_diff\"};\n\n    for (const auto& err_type : err_priority) {\n      // Repair only targeted categories if this option is set\n      if ((mRepairCategory != FsckErr::None) &&\n          (mRepairCategory != ConvertToFsckErr(err_type))) {\n        continue;\n      }\n\n      for (const auto& elem : local_emap[err_type]) {\n        if (!gOFS->mFidTracker.AddEntry(elem.first, TrackerType::Fsck)) {\n          eos_debug(\"msg=\\\"skip already scheduled transfer\\\" fxid=%08llx\",\n                    elem.first);\n          continue;\n        }\n\n        std::shared_ptr<FsckEntry> job{\n          new FsckEntry(elem.first, elem.second, err_type, mDoBestEffort, mQcl)};\n        mThreadPool.PushTask<void>([job]() {\n          return job->Repair();\n        });\n\n        // Check regularly if we should exit and sleep if queue is full\n        while ((mThreadPool.GetQueueSize() > mMaxQueuedJobs) ||\n               (++count % 100 == 0)) {\n          if (assistant.terminationRequested() ||\n              !gOFS->mMaster->IsMaster()) {\n            // Wait that there are not more jobs in the queue - this can\n            // take a while depending on the queue size\n            while (mThreadPool.GetQueueSize()) {\n              std::this_thread::sleep_for(std::chrono::seconds(1));\n\n              if (++msg_delay % 5 == 0) {\n                eos_info(\"%s\", \"msg=\\\"stopping fsck repair waiting for thread \"\n                         \"pool queue to be consummed\\\"\");\n                msg_delay = 0;\n              }\n            }\n\n            eos_info(\"%s\", \"msg=\\\"stopped fsck repair thread\\\"\");\n            mRepairRunning = false;\n            return;\n          } else {\n            if (mThreadPool.GetQueueSize() > mMaxQueuedJobs) {\n              assistant.wait_for(std::chrono::seconds(1));\n            } else {\n              break;\n            }\n          }\n        }\n      }\n    }\n\n    // Remove orphans from unavailable filesystems\n    for (const auto& [fid, fsids] : local_emap[eos::common::FSCK_ORPHANS_N]) {\n      for (const auto& fsid : fsids) {\n        eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n        FileSystem* fs = FsView::gFsView.mIdView.lookupByID(fsid);\n\n        if (!fs) {\n          eos_info(\"msg=\\\"dropping orphans for missing filesystem\\\" \"\n                   \"fxid=%08llx fsid=%d\", fid, fsid);\n          NotifyFixedErr(fid, fsid, eos::common::FSCK_ORPHANS_N);\n        }\n      }\n    }\n\n    // Force flush any collected notifications\n    NotifyFixedErr(0ull, 0ul, \"\", true);\n    mStartProcessing = false;\n    eos_info(\"%s\", \"msg=\\\"loop in fsck repair thread\\\"\");\n  }\n\n  // Wait that there are no more jobs in the queue\n  while (mThreadPool.GetQueueSize()) {\n    assistant.wait_for(std::chrono::seconds(1));\n  }\n\n  gOFS->mFidTracker.Clear(TrackerType::Fsck);\n  eos_info(\"%s\", \"msg=\\\"stopped fsck repair thread\\\"\");\n  mRepairRunning = false;\n}\n\n//------------------------------------------------------------------------------\n// Try to repair a given entry\n//------------------------------------------------------------------------------\nbool\nFsck::RepairEntry(eos::IFileMD::id_t fid,\n                  const std::set<eos::common::FileSystem::fsid_t>& fsid_err,\n                  const std::string& err_type, bool async, std::string& out_msg)\n{\n  if (fid == 0ull) {\n    eos_err(\"%s\", \"msg=\\\"not such file id 0\\\"\");\n    return false;\n  }\n\n  std::shared_ptr<FsckEntry> job {\n    new FsckEntry(fid, fsid_err, err_type, false, mQcl)};\n\n  if (async) {\n    out_msg = \"msg=\\\"repair job submitted\\\"\";\n    mThreadPool.PushTask<void>([job]() {\n      return job->Repair();\n    });\n  } else {\n    if (!job->Repair()) {\n      out_msg = \"msg=\\\"repair job failed\\\"\";\n      return false;\n    } else {\n      out_msg = \"msg=\\\"repair successful\\\"\";\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Print the current log output\n//------------------------------------------------------------------------------\nvoid\nFsck::PrintOut(std::string& out, bool monitor_fmt) const\n{\n  std::ostringstream oss;\n\n  if (monitor_fmt) {\n    static time_t current_time;\n    time(&current_time);\n    oss << \"timestamp=\" << current_time << std::endl\n        << \"collection_thread=\" << (mCollectEnabled ? \"on\" : \"off\")\n        << std::endl\n        << \"repair_thread=\" << (mRepairEnabled ? \"on\" : \"off\")\n        << std::endl\n        << \"repair_category=\" <<\n        ((mRepairCategory == FsckErr::None) ?\n         \"all\" : eos::common::FsckErrToString(mRepairCategory)) << std::endl\n        << \"best_effort=\" << (mDoBestEffort ? \"on\" : \"off\") << std::endl;\n  } else {\n    oss << \"Info: collection thread status -> \"\n        << (mCollectEnabled ? \"on\" : \"off\") << std::endl\n        << \"Info: repair thread status     -> \"\n        << (mRepairEnabled ? \"on\" : \"off\") << std::endl\n        << \"Info: repair category          -> \"\n        << ((mRepairCategory == FsckErr::None) ?\n            \"all\" : eos::common::FsckErrToString(mRepairCategory)) << std::endl\n        << \"Info: best effort              -> \"\n        << (mDoBestEffort ? \"on\" : \"off\") << std::endl;\n  }\n\n  {\n    XrdSysMutexHelper lock(mLogMutex);\n    oss << (monitor_fmt ? mLogMonitor : mLog);\n  }\n\n  out = oss.str();\n}\n\n//------------------------------------------------------------------------------\n// Get the require format for the given file identifier. Empty if no format\n// requested.\n//------------------------------------------------------------------------------\nstd::string\nFsck::GetFidFormat(eos::IFileMD::id_t fid, bool display_fxid, bool\n                   display_lfn) const\n{\n  if (display_fxid) {\n    return eos::common::FileId::Fid2Hex(fid);\n  } else if (display_lfn) {\n    eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, fid);\n    eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n\n    try {\n      auto fmd = gOFS->eosFileService->getFileMD(fid);\n      return gOFS->eosView->getUri(fmd.get());\n    } catch (eos::MDException& e) {\n      return \"undefined\";\n    }\n  }\n\n  return \"\";\n}\n\n//------------------------------------------------------------------------------\n// Return the current FSCK report\n//------------------------------------------------------------------------------\nbool\nFsck::Report(std::string& out, const std::set<std::string> tags,\n             bool display_per_fs, bool display_fxid, bool display_lfn,\n             bool display_json)\n{\n  std::ostringstream oss;\n  eos::common::RWMutexReadLock rd_lock(mErrMutex);\n\n  if (display_json) {\n    ReportJsonFormat(oss, tags, display_per_fs, display_fxid, display_lfn);\n  } else {\n    ReportMonitorFormat(oss, tags, display_per_fs, display_fxid, display_lfn);\n  }\n\n  out = oss.str();\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Create report in JSON format\n//------------------------------------------------------------------------------\nvoid\nFsck::ReportJsonFormat(std::ostringstream& oss,\n                       const std::set<std::string> tags,\n                       bool display_per_fs, bool display_fxid,\n                       bool display_lfn) const\n{\n  Json::Value json;\n\n  if (display_per_fs) {\n    // Display per file system\n    std::map<eos::common::FileSystem::fsid_t,\n        std::map<std::string, std::set<unsigned long long>>> fs_fxid;\n\n    for (const auto& elem_map : eFsMap) {\n      if (!tags.empty() && (tags.find(elem_map.first) == tags.end())) {\n        continue;  // skip unselected\n      }\n\n      for (const auto& elem : elem_map.second) {\n        for (const auto& fsid : elem.second) {\n          fs_fxid[fsid][elem_map.first].insert(elem.first);\n        }\n      }\n    }\n\n    for (const auto& elem : fs_fxid) {\n      for (auto it = elem.second.begin(); it != elem.second.end(); ++it) {\n        Json::Value json_entry;\n        json_entry[\"timestamp\"] = (Json::Value::UInt64)eTimeStamp;\n        json_entry[\"fsid\"] = (int) elem.first;\n        json_entry[\"tag\"] = it->first.c_str();\n        json_entry[\"count\"] = (int) it->second.size();\n\n        if (!display_fxid && !display_lfn) {\n          json.append(json_entry);\n          continue;\n        }\n\n        Json::Value json_ids;\n\n        for (auto it_fid = it->second.begin();\n             it_fid != it->second.end(); ++it_fid) {\n          json_ids.append(GetFidFormat(*it_fid, display_fxid, display_lfn));\n        }\n\n        if (display_fxid) {\n          json_entry[\"fxid\"] = json_ids;\n        } else if (display_lfn) {\n          json_entry[\"lfn\"] = json_ids;\n        }\n\n        json.append(json_entry);\n      }\n    }\n  } else {\n    for (const auto& elem_map : eFsMap) {\n      if (!tags.empty() && (tags.find(elem_map.first) == tags.end())) {\n        continue;  // skip unselected\n      }\n\n      Json::Value json_entry;\n      json_entry[\"timestamp\"] = (Json::Value::UInt64) eTimeStamp;\n      json_entry[\"tag\"] = elem_map.first;\n      Json::Value json_ids;\n      std::set<unsigned long long> fids;\n\n      for (const auto& elem : elem_map.second) {\n        fids.insert(elem.first);\n      }\n\n      for (auto it = fids.begin(); it != fids.end(); ++it) {\n        json_ids.append(GetFidFormat(*it, display_fxid, display_lfn));\n      }\n\n      json_entry[\"count\"] = (Json::Value::UInt64)fids.size();\n\n      if (display_fxid) {\n        json_entry[\"fxid\"] = json_ids;\n      } else if (display_lfn) {\n        json_entry[\"lfn\"] = json_ids;\n      }\n\n      json.append(json_entry);\n    }\n  }\n\n  // List shadow filesystems\n  if (!eFsDark.empty()) {\n    for (auto fsit = eFsDark.begin(); fsit != eFsDark.end(); fsit++) {\n      Json::Value json_entry;\n      json_entry[\"timestamp\"] = (Json::Value::UInt64) eTimeStamp;\n      json_entry[\"tag\"] = \"shadow_fsid\";\n      json_entry[\"fsid\"] = (Json::Value::UInt64)fsit->first;\n      json_entry[\"count\"] = (Json::Value::UInt64)fsit->second;\n      json.append(json_entry);\n    }\n  }\n\n  oss << json;\n}\n\n//------------------------------------------------------------------------------\n// Create report in monitor format\n//------------------------------------------------------------------------------\nvoid\nFsck::ReportMonitorFormat(std::ostringstream& oss,\n                          const std::set<std::string> tags,\n                          bool display_per_fs, bool display_fxid,\n                          bool display_lfn) const\n{\n  if (display_per_fs) {\n    // Display per file system\n    std::map<eos::common::FileSystem::fsid_t,\n        std::map<std::string, std::set<unsigned long long>>> fs_fxid;\n\n    for (const auto& elem_map : eFsMap) {\n      if (!tags.empty() && (tags.find(elem_map.first) == tags.end())) {\n        continue;  // skip unselected\n      }\n\n      for (const auto& elem : elem_map.second) {\n        for (const auto& fsid : elem.second) {\n          fs_fxid[fsid][elem_map.first].insert(elem.first);\n        }\n      }\n    }\n\n    for (const auto& elem : fs_fxid) {\n      for (auto it = elem.second.begin(); it != elem.second.end(); ++it) {\n        oss << \"timestamp=\" << eTimeStamp << \" fsid=\" << elem.first\n            << \" tag=\\\"\" << it->first << \"\\\" count=\" << it->second.size();\n\n        if (display_fxid) {\n          oss << \" fxid=\";\n        } else if (display_lfn) {\n          oss << \" lfn=\";\n        } else {\n          oss << std::endl;\n          continue;\n        }\n\n        for (auto it_fid = it->second.begin();\n             it_fid != it->second.end(); ++it_fid) {\n          oss << GetFidFormat(*it_fid, display_fxid, display_lfn);\n\n          if (it_fid != std::prev(it->second.end())) {\n            oss << \", \";\n          }\n        }\n\n        oss << std::endl;\n      }\n    }\n  } else {\n    for (const auto& elem_map : eFsMap) {\n      if (!tags.empty() && (tags.find(elem_map.first) == tags.end())) {\n        continue;  // skip unselected\n      }\n\n      oss << \"timestamp=\" << eTimeStamp << \" tag=\\\"\" << elem_map.first << \"\\\"\";\n      std::set<unsigned long long> fids;\n\n      for (const auto& elem : elem_map.second) {\n        fids.insert(elem.first);\n      }\n\n      oss << \" count=\" << fids.size();\n\n      if (display_fxid) {\n        oss << \" fxid=\";\n      } else if (display_lfn) {\n        oss << \" lfn=\";\n      } else {\n        oss << std::endl;\n        continue;\n      }\n\n      for (auto it = fids.begin(); it != fids.end(); ++it) {\n        oss << GetFidFormat(*it, display_fxid, display_lfn);\n\n        if (it != std::prev(fids.end())) {\n          oss << \", \";\n        }\n      }\n\n      oss << std::endl;\n    }\n  }\n\n  // List shadow filesystems\n  if (!eFsDark.empty()) {\n    for (auto fsit = eFsDark.begin(); fsit != eFsDark.end(); ++fsit) {\n      oss << \"timestamp=\" << eTimeStamp << \" tag=\\\"shadow_fsid\\\"\"\n          << \" fsid=\" << fsit->first << \" count=\" << fsit->second;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Clear the current FSCK log\n//------------------------------------------------------------------------------\nvoid\nFsck::PublishLogs()\n{\n  XrdSysMutexHelper lock(mLogMutex);\n  mLog = mTmpLog;\n  mTmpLog.clear();\n  mLogMonitor = mTmpLogMonitor;\n  mTmpLogMonitor.clear();\n}\n\n//------------------------------------------------------------------------------\n// Write log message to the current in-memory log\n//------------------------------------------------------------------------------\nvoid\nFsck::Log(const char* msg, ...) const\n{\n  static time_t current_time;\n  static struct timeval tv;\n  static struct timezone tz;\n  static struct tm* tm;\n  va_list args;\n  va_start(args, msg);\n  char buffer[16384];\n  char* ptr;\n  time(&current_time);\n  gettimeofday(&tv, &tz);\n  tm = localtime(&current_time);\n  sprintf(buffer, \"%02d%02d%02d %02d:%02d:%02d %lu.%06lu \", tm->tm_year - 100,\n          tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, current_time,\n          (unsigned long) tv.tv_usec);\n  ptr = buffer + strlen(buffer);\n  vsprintf(ptr, msg, args);\n  XrdSysMutexHelper lock(mLogMutex);\n  mTmpLog += buffer;\n  mTmpLog += \"\\n\";\n  va_end(args);\n}\n\n//------------------------------------------------------------------------------\n// Write log message to the current in-memory log in monitoring format\n//------------------------------------------------------------------------------\nvoid\nFsck::LogMonitor(const char* msg, ...) const\n{\n  va_list args;\n  va_start(args, msg);\n  char buffer[16384];\n  vsprintf(buffer, msg, args);\n  XrdSysMutexHelper lock(mLogMutex);\n  mTmpLogMonitor += buffer;\n  mTmpLogMonitor += \"\\n\";\n  va_end(args);\n}\n\n//------------------------------------------------------------------------------\n// Reset all collected errors in the error map\n//------------------------------------------------------------------------------\nvoid\nFsck::ResetErrorMaps()\n{\n  eos::common::RWMutexWriteLock wr_lock(mErrMutex);\n  eFsMap.clear();\n  eFsUnavail.clear();\n  eFsDark.clear();\n  eTimeStamp = time(NULL);\n}\n\n//------------------------------------------------------------------------------\n// Account for offline replicas due to unavailable file systems\n//------------------------------------------------------------------------------\nvoid\nFsck::AccountOfflineReplicas()\n{\n  // Grab all files which are damaged because filesystems are down\n  eos::common::RWMutexWriteLock wr_lock(mErrMutex);\n  eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n\n  for (auto it = FsView::gFsView.mIdView.cbegin();\n       it != FsView::gFsView.mIdView.cend(); ++it) {\n    // protect against illegal 0 filesystem pointer\n    if (!it->second) {\n      eos_crit(\"msg=\\\"found illegal pointer in filesystem view\\\" fsid=%lu\",\n               it->first);\n      continue;\n    }\n\n    eos::common::FileSystem::fsid_t fsid = it->first;\n    eos::common::ActiveStatus fsactive = it->second->GetActiveStatus();\n    eos::common::ConfigStatus fsconfig = it->second->GetConfigStatus();\n    eos::common::BootStatus fsstatus = it->second->GetStatus();\n\n    if ((fsstatus == eos::common::BootStatus::kBooted) &&\n        (fsconfig >= eos::common::ConfigStatus::kDrain) &&\n        (fsactive == eos::common::ActiveStatus::kOnline)) {\n      // Healthy, don't need to do anything\n      continue;\n    } else {\n      // Not ok and contributes to replica offline errors\n      try {\n        eos::Prefetcher::prefetchFilesystemFileListAndWait(gOFS->eosView,\n            gOFS->eosFsView, fsid);\n        // Only need the view lock if we're in-memory\n        eos::common::RWMutexReadLock nslock;\n\n        if (gOFS->eosView->inMemory()) {\n          nslock.Grab(gOFS->eosViewRWMutex);\n        }\n\n        for (auto it_fid = gOFS->eosFsView->getFileList(fsid);\n             (it_fid && it_fid->valid()); it_fid->next()) {\n          eFsUnavail[fsid]++;\n          eFsMap[\"rep_offline\"][it_fid->getElement()].insert(fsid);\n        }\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        eos_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                  e.getMessage().str().c_str());\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Account for file with no replicas\n//------------------------------------------------------------------------------\nvoid\nFsck::AccountNoReplicaFiles()\n{\n  // Grab all files which have no replicas at all\n  try {\n    eos::common::RWMutexWriteLock wr_lock(mErrMutex);\n    eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n    // it_fid not invalidated when items are added or removed for QDB\n\n    for (auto it_fid = gOFS->eosFsView->getStreamingNoReplicasFileList();\n         (it_fid && it_fid->valid()); it_fid->next()) {\n      ns_rd_lock.Release();\n      eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView,\n          it_fid->getElement());\n      ns_rd_lock.Grab(gOFS->eosViewRWMutex);\n      auto fmd = gOFS->eosFileService->getFileMD(it_fid->getElement());\n      std::string path = gOFS->eosView->getUri(fmd.get());\n      XrdOucString fullpath = path.c_str();\n\n      if (fullpath.beginswith(gOFS->MgmProcPath)) {\n        // Don't report eos /proc files\n        continue;\n      }\n\n      if (fmd && (!fmd->isLink())) {\n        eFsMap[\"zero_replica\"][it_fid->getElement()].insert(0);\n      }\n    }\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"caught exception\\\" errno=d%d msg=\\\"%s\\\"\", e.getErrno(),\n              e.getMessage().str().c_str());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print offline replicas summary\n//------------------------------------------------------------------------------\nvoid\nFsck::PrintOfflineReplicas() const\n{\n  eos::common::RWMutexReadLock rd_lock(mErrMutex);\n\n  // Loop over unavailable filesystems\n  for (auto ua_it = eFsUnavail.cbegin(); ua_it != eFsUnavail.cend();\n       ++ua_it) {\n    std::string host = \"not configured\";\n    eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(ua_it->first);\n\n    if (fs) {\n      host = fs->GetString(\"hostport\");\n    }\n\n    Log(\"host=%s fsid=%lu replica_offline=%llu\", host.c_str(),\n        ua_it->first, ua_it->second);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Account for offline files or files that require replica adjustments\n// i.e. file_offline and adjust_replica\n//------------------------------------------------------------------------------\nvoid\nFsck::AccountOfflineFiles()\n{\n  using eos::common::LayoutId;\n  // Loop over all replica_offline and layout error files to assemble a\n  // file offline list\n  std::set <eos::common::FileId::fileid_t> fid2check;\n  {\n    eos::common::RWMutexReadLock rd_lock(mErrMutex);\n    auto it_offline = eFsMap.find(\"rep_offline\");\n\n    if (it_offline != eFsMap.end()) {\n      for (const auto& elem : it_offline->second) {\n        fid2check.insert(elem.first);\n      }\n    }\n\n    auto it_diff_n = eFsMap.find(\"rep_diff_n\");\n\n    if (it_diff_n != eFsMap.end()) {\n      for (const auto& elem : it_diff_n->second) {\n        fid2check.insert(elem.first);\n      }\n    }\n  }\n\n  for (auto it = fid2check.begin(); it != fid2check.end(); ++it) {\n    std::shared_ptr<eos::IFileMD> fmd;\n    eos::IFileMD::LocationVector loc_vect;\n    eos::IFileMD::layoutId_t lid {0ul};\n    size_t nlocations {0};\n\n    try { // Check if locations are online\n      eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, *it);\n      eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n      fmd = gOFS->eosFileService->getFileMD(*it);\n      lid = fmd->getLayoutId();\n      nlocations = fmd->getNumLocation();\n      loc_vect = fmd->getLocations();\n    } catch (eos::MDException& e) {\n      continue;\n    }\n\n    size_t offlinelocations = 0;\n    eos::common::RWMutexWriteLock wr_lock(mErrMutex);\n    eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n\n    for (const auto& loc : loc_vect) {\n      if (loc) {\n        FileSystem* fs = FsView::gFsView.mIdView.lookupByID(loc);\n\n        if (fs) {\n          eos::common::BootStatus bootstatus = fs->GetStatus(true);\n          eos::common::ConfigStatus configstatus = fs->GetConfigStatus();\n          bool conda = (fs->GetActiveStatus() == eos::common::ActiveStatus::kOffline);\n          bool condb = (bootstatus != eos::common::BootStatus::kBooted);\n          bool condc = (configstatus == eos::common::ConfigStatus::kDrainDead);\n\n          if (conda || condb || condc) {\n            ++offlinelocations;\n          }\n        }\n      }\n    }\n\n    unsigned long layout_type = LayoutId::GetLayoutType(lid);\n\n    if (layout_type == LayoutId::kReplica) {\n      if (offlinelocations == nlocations) {\n        eFsMap[\"file_offline\"][*it].insert(0);\n      }\n    } else if (layout_type >= LayoutId::kArchive) {\n      // Proper condition for RAIN layout\n      if (offlinelocations > LayoutId::GetRedundancyStripeNumber(lid)) {\n        eFsMap[\"file_offline\"][*it].insert(0);\n      }\n    }\n\n    if (offlinelocations && (offlinelocations != nlocations)) {\n      eFsMap[\"adjust_replica\"][*it].insert(0);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print summary of the different type of errors collected so far and their\n// corresponding counters\n//------------------------------------------------------------------------------\nvoid\nFsck::PrintErrorsSummary() const\n{\n  eos::common::RWMutexReadLock rd_lock(mErrMutex);\n\n  for (const auto& elem_type : eFsMap) {\n    uint64_t count {0ull};\n\n    for (const auto& elem_errs : elem_type.second) {\n      count += elem_errs.second.size();\n    }\n\n    Log(\"%-30s : %llu\", elem_type.first.c_str(), count);\n    LogMonitor(\"%s=%llu\", elem_type.first.c_str(), count);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Account for \"dark\" file entries i.e. file system ids which have file\n// entries in the namespace view but have no configured file system in the\n// FsView.\n//------------------------------------------------------------------------------\nvoid\nFsck::AccountDarkFiles()\n{\n  eos::common::RWMutexWriteLock wr_lock(mErrMutex);\n  eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n  eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n\n  for (auto it = gOFS->eosFsView->getFileSystemIterator();\n       it->valid(); it->next()) {\n    IFileMD::location_t nfsid = it->getElement();\n\n    try {\n      // @todo(gbitzes): Urgent fix for QDB namespace needed.. This loop\n      // will need to load all filesystems in memory, just to get a couple\n      // of silly counters.\n      uint64_t num_files = gOFS->eosFsView->getNumFilesOnFs(nfsid);\n\n      if (num_files) {\n        // Check if this exists in the gFsView\n        if (!FsView::gFsView.mIdView.exists(nfsid)) {\n          eFsDark[nfsid] += num_files;\n          Log(\"shadow fsid=%lu shadow_entries=%llu \", nfsid, num_files);\n        }\n      }\n    } catch (const eos::MDException& e) {\n      // ignore\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Query QDB for fsck errors\n//------------------------------------------------------------------------------\nvoid\nFsck::QueryQdb(ErrMapT& err_map)\n{\n  static std::set<std::string> known_errs = eos::common::GetKnownFsckErrs();\n  qclient::QSet set_errs(*mQcl.get(), \"\");\n  // Helper function to parse fsck info stored in QDB\n  auto parse_fsck =\n    [](const std::string & data) ->\n  std::pair<eos::IFileMD::id_t, eos::common::FileSystem::fsid_t> {\n    const size_t pos = data.find(':');\n\n    if ((pos == std::string::npos) || (pos == data.length()))\n    {\n      eos_static_err(\"msg=\\\"failed to parse fsck element\\\" data=\\\"%s\\\"\",\n                     data.c_str());\n      return {0ull, 0ul};\n    }\n\n    eos::IFileMD::id_t fid;\n    eos::common::FileSystem::fsid_t fsid;\n\n    if (!eos::common::StringToNumeric(data.substr(0, pos), fid) ||\n        !eos::common::StringToNumeric(data.substr(pos + 1), fsid))\n    {\n      eos_static_err(\"msg=\\\"failed to convert fsck info\\\" data=\\\"%s\\\"\",\n                     data.c_str());\n      return {0ull, 0ul};\n    }\n\n    return {fid, fsid};\n  };\n  eos_static_info(\"%s\", \"msg=\\\"check for fsck errors\\\"\");\n\n  for (const auto& err_type : known_errs) {\n    set_errs.setKey(SSTR(\"fsck:\" << err_type));\n\n    for (auto it = set_errs.getIterator(); it.valid(); it.next()) {\n      // Set elements are in the form: fid:fsid\n      auto pair_info = parse_fsck(it.getElement());\n      err_map[err_type][pair_info.first].insert(pair_info.second);\n    }\n  }\n\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Update the backend given the successful outcome of the repair\n//------------------------------------------------------------------------------\nvoid\nFsck::NotifyFixedErr(eos::IFileMD::id_t fid,\n                     eos::common::FileSystem::fsid_t fsid_err,\n                     const std::string& err_type,\n                     bool force, uint32_t count_flush)\n{\n  eos_static_debug(\"msg=\\\"fsck notification\\\" fxid=%08llx fsid=%lu \"\n                   \"err=%s\", fid, fsid_err, err_type.c_str());\n  static std::mutex mutex;\n  static uint64_t num_updates = 0ull;\n  static std::map<std::string, std::set<std::string>> updates;\n  std::unique_lock<std::mutex> scope_lock(mutex);\n\n  if ((fid || fsid_err) && !err_type.empty() && (err_type != \"none\")) {\n    auto it = updates.find(err_type);\n\n    if (it == updates.end()) {\n      auto resp = updates.emplace(err_type, std::set<std::string>());\n      it = resp.first;\n    }\n\n    const std::string value = SSTR(fid << ':' << fsid_err);\n    auto resp = it->second.insert(value);\n\n    if (resp.second == true) {\n      ++num_updates;\n    }\n  }\n\n  // Decide if time based flushing is needed\n  using namespace std::chrono;\n  static uint32_t s_flush_timeout_sec {60};\n  static std::atomic<uint64_t> s_timestamp = duration_cast<seconds>\n      (system_clock::now().time_since_epoch()).count();\n  bool do_flush = false;\n  uint64_t current_timestamp = duration_cast<seconds>\n                               (system_clock::now().time_since_epoch()).count();\n\n  if (current_timestamp - s_timestamp > s_flush_timeout_sec) {\n    s_timestamp = current_timestamp;\n    do_flush = true;\n  }\n\n  // Eventually flush the contents to the QDB backend if sentinel fid present\n  // or if enough updates accumulated\n  if (force || do_flush || (num_updates >= count_flush)) {\n    qclient::QSet qset(*mQcl.get(), \"\");\n\n    for (const auto& elem : updates) {\n      qset.setKey(SSTR(\"fsck:\" << elem.first));\n      std::list<std::string> values(elem.second.begin(), elem.second.end());\n\n      if (qset.srem(values) != (long long int)values.size()) {\n        eos_static_err(\"msg=\\\"failed to delete some fsck errors\\\" err_type=%s\",\n                       elem.first.c_str());\n      }\n    }\n\n    num_updates = 0ull;\n    updates.clear();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Force clean-up the orphans from QuarkDB\n//------------------------------------------------------------------------------\nvoid\nFsck::ForceCleanQdbOrphans() const\n{\n  static std::string key_orphans = \"fsck:orphans_n\";\n\n  if (mQcl) {\n    try {\n      (void)mQcl->del(key_orphans);\n    } catch (const std::exception& e) {\n      eos_static_err(\"%s\", \"msg=\\\"failed while doing qdb orphan clean-up\\\"\");\n    }\n  } else {\n    eos_static_info(\"%s\", \"msg=\\\"fsck clean orphans skipped as fsck not \"\n                          \"configured, i.e. missing qclient object\\\"\")\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/fsck/Fsck.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Fsck.hh\n//! @author Andreas-Joachim Peters/Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"mgm/fsck/FsckEntry.hh\"\n#include \"common/FileSystem.hh\"\n#include \"common/FileId.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/ThreadPool.hh\"\n#include \"mgm/misc/IdTrackerWithValidity.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/ns_quarkdb/qclient/include/qclient/QClient.hh\"\n#include <sys/types.h>\n#include <string>\n#include <stdarg.h>\n#include <map>\n#include <set>\n\n//------------------------------------------------------------------------------\n//! @file Fsck.hh\n//! @brief Class aggregating FSCK statistics and repair functionality\n//------------------------------------------------------------------------------\nEOSMGMNAMESPACE_BEGIN\n\n\n//! Forward declaration\nclass FsView;\n\n//------------------------------------------------------------------------------\n//! @brief Class implementing the EOS filesystem check.\n//!\n//! When the FSCK thread is enabled it collects on a regular interval the\n//! FSCK results broadcasted by all FST nodes into a central view.\n//! The FSCK interface offers a 'report' and a 'repair' utility allowing to\n//! inspect and to actively try to run repair to fix inconsistencies.\n//------------------------------------------------------------------------------\nclass Fsck: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  Fsck();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~Fsck()\n  {\n    Stop();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Stop all fsck related threads and activities\n  //----------------------------------------------------------------------------\n  void Stop();\n\n  //----------------------------------------------------------------------------\n  //! Print function to display FSCK results\n  //!\n  //! @param out string holding the output to be displayed\n  //! @param monitor_fmt if try provide monitor output\n  //----------------------------------------------------------------------------\n  void PrintOut(std::string& out, bool monitor_fmt) const;\n\n  //----------------------------------------------------------------------------\n  //! Apply configuration options to the fsck mechanism\n  //!\n  //! @param key key to be modified\n  //! @param value value\n  //! @param msg optional message in case of failure\n  //!\n  //! @param return true if configuration change applied successfully, otherwise\n  //!         false\n  //----------------------------------------------------------------------------\n  bool Config(const std::string& key, const std::string& value, std::string& msg);\n\n  //----------------------------------------------------------------------------\n  //! Method to create a report\n  //!\n  //! @param output output string\n  //! @param tags set of tags for which the report should be generated\n  //! @param display_per_fs if true then display information per file system\n  //! @param display_fxid if true then display file identifiers\n  //! @param display_lfn if true then display logical file name\n  //! @param display_json if true then display info in json format\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool Report(std::string& output, const std::set<std::string> tags,\n              bool display_per_fs,  bool display_fxid, bool display_lfn,\n              bool display_json);\n\n  //----------------------------------------------------------------------------\n  //! Publish newly collected error information\n  //----------------------------------------------------------------------------\n  void PublishLogs();\n\n  //----------------------------------------------------------------------------\n  //! Write a log message to the in-memory log\n  //!\n  //! @param msg variable length list of printf like format string and args\n  //----------------------------------------------------------------------------\n  void Log(const char* msg, ...) const;\n\n  //----------------------------------------------------------------------------\n  //! Write a log message to the in-memory log in monitoring format\n  //!\n  //! @param msg variable length list of printf like format string and args\n  //----------------------------------------------------------------------------\n  void LogMonitor(const char* msg, ...) const;\n\n  //----------------------------------------------------------------------------\n  //! Apply the FSCK configuration stored in the configuration engine\n  //!\n  //! @param fsview pointer to FsView object\n  //----------------------------------------------------------------------------\n  void ApplyConfig(FsView* fsview);\n\n  //----------------------------------------------------------------------------\n  //! Store the FSCK configuration to the configuration engine\n  //----------------------------------------------------------------------------\n  bool StoreConfig();\n\n  //----------------------------------------------------------------------------\n  //! Method collecting errors from the FSTS\n  //!\n  //! @param assistant thread doing the job\n  //----------------------------------------------------------------------------\n  void CollectErrs(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Method submitting fsck repair jobs to the thread pool\n  //!\n  //! @param assistant thread doing the job\n  //----------------------------------------------------------------------------\n  void RepairErrs(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Try to repair a given entry\n  //!\n  //! @param fid file identifier\n  //! @param fsid_err explicit file system id to check\n  //! @param err_type type of error on the explicit file system\n  //! @param async if true then submit the job to the repair thread if it's\n  //!        enabled\n  //! @param err_msg output message\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool RepairEntry(eos::IFileMD::id_t fid,\n                   const std::set<eos::common::FileSystem::fsid_t>& fsid_err,\n                   const std::string& err_type, bool async,\n                   std::string& out_msg);\n\n  //----------------------------------------------------------------------------\n  //! Update the backend given the successful outcome of the repair\n  //!\n  //! @param fid file identifier or 0ull when flushing is forced no matter the\n  //!        then number of entries accumulated\n  //! @param fsid file system identifier\n  //! @param err_type error type\n  //! @param force if try force flush all updates\n  //! @param count_flush number of accumulated entries when flushing to QDB\n  //!        backend is triggered\n  //----------------------------------------------------------------------------\n  void NotifyFixedErr(eos::IFileMD::id_t fid,\n                      eos::common::FileSystem::fsid_t fsid_err,\n                      const std::string& err_type,\n                      bool force = false,\n                      uint32_t count_flush = 100);\n\n  //----------------------------------------------------------------------------\n  //! Set max size of thread pool used for fsck repair jobs\n  //!\n  //! @param max max value\n  //----------------------------------------------------------------------------\n  inline void SetMaxThreadPoolSize(uint64_t max)\n  {\n    mThreadPool.SetMaxThreads(max);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get thread pool info\n  //!\n  //! @return string summary for the thread pool\n  //----------------------------------------------------------------------------\n  inline std::string GetThreadPoolInfo() const\n  {\n    return mThreadPool.GetInfo();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Force clean-up the orphans from QuarkDB\n  //----------------------------------------------------------------------------\n  void ForceCleanQdbOrphans() const;\n\nprivate:\n  //! Key used in the configuration engine to store the fsck config\n  static const std::string sFsckKey;\n  //! Key used to store the status of the collector thread in the config\n  static const std::string sCollectKey;\n  static const std::string sOldCollectKey; // @esindril to be dropped\n  //! Key used to store the status of the repair thread in the config\n  static const std::string sRepairKey;\n  static const std::string sOldRepairKey; // @esindril to be dropped\n  //! Key used to store the best-effort status in the config\n  static const std::string sBestEffortKey;\n  static const std::string sOldBestEffortKey; // @esindril to be dropped\n  //! Key used to store the collection interval in the config\n  static const std::string sCollectIntervalKey;\n  //! Key used to store the repair category in the config\n  static const std::string sRepairCategory;\n\n  std::atomic<bool> mShowOffline; ///< Flag to display offline files/replicas\n  std::atomic<bool> mShowNoReplica; ///< Flag to display no replica files\n  std::atomic<bool> mShowDarkFiles; ///< Flag to display dark files\n  std::atomic<bool> mStartProcessing; ///< Notification flag for repair thread\n  std::atomic<bool> mCollectEnabled; ///< Mark if the err collection is enabled\n  std::atomic<bool> mRepairEnabled; ///< Mark if the repair thread is enabled\n  std::atomic<bool> mCollectRunning; ///< Mark if collector is running\n  std::atomic<bool> mRepairRunning; ///< Mark if repair is running\n  std::atomic<bool> mDoBestEffort; ///< Mark if best-effort repair enabled\n  std::atomic<eos::common::FsckErr>\n  mRepairCategory; ///< Mark which cat. should be repaired\n  mutable std::string mLog, mTmpLog; ///< In-memory fsck log\n  ///< In-memory monitoring fsck log\n  mutable std::string mLogMonitor, mTmpLogMonitor;\n  mutable XrdSysMutex mLogMutex; ///< Mutex protecting the in-memory log\n  ///< Interval between FSCK collection loops\n  std::chrono::seconds mCollectInterval;\n  mutable eos::common::RWMutex mErrMutex; ///< Mutex protecting all map obj\n  //! Error detail map storing \"<error-name>=><fsid>=>[fid1,fid2,fid3...]\"\n  using ErrMapT = std::map<std::string,\n        std::map<eos::common::FileId::fileid_t,\n        std::set <eos::common::FileSystem::fsid_t>>>;\n  ErrMapT eFsMap;\n  //! Unavailable filesystems map\n  std::map<eos::common::FileSystem::fsid_t, unsigned long long > eFsUnavail;\n  //! Dark filesystem map - filesystems referenced by a file but not configured\n  //! in the filesystem view\n  std::map<eos::common::FileSystem::fsid_t, unsigned long long > eFsDark;\n  time_t eTimeStamp; ///< Timestamp of collection\n  uint64_t mMaxQueuedJobs {(uint64_t)1e3}; ///< Max number of queued jobs (1k)\n  uint32_t mMaxThreadPoolSize {20}; ///< Max number of threads in the pool\n  eos::common::ThreadPool mThreadPool; ///< Thread pool for fsck repair jobs\n  ///< Repair thread submitting jobs to the thread pool\n  AssistedThread mRepairThread;\n  AssistedThread mCollectorThread; ///< Thread collecting errors\n  std::shared_ptr<qclient::QClient> mQcl; ///< QClient object for metadata\n\n  //----------------------------------------------------------------------------\n  //! Query QDB for fsck errors\n  //!\n  //! @param err_map map of fsck errors collected\n  //----------------------------------------------------------------------------\n  void QueryQdb(ErrMapT& err_map);\n\n  //----------------------------------------------------------------------------\n  //! Create report in JSON format\n  //!\n  //! @param output output string\n  //! @param tags set of tags for which the report should be generated\n  //! @param display_per_fs if true then display information per file system\n  //! @param display_fxid if true then display file identifiers\n  //! @param display_lfn if true then display logical file name\n  //----------------------------------------------------------------------------\n  void ReportJsonFormat(std::ostringstream& output,\n                        const std::set<std::string> tags,\n                        bool display_per_fs, bool display_fxid,\n                        bool display_lfn) const;\n\n  //----------------------------------------------------------------------------\n  //! Create report in monitor format\n  //!\n  //! @param output output string\n  //! @param tags set of tags for which the report should be generated\n  //! @param display_per_fs if true then display information per file system\n  //! @param display_fxid if true then display file identifiers\n  //! @param display_lfn if true then display logical file name\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  void ReportMonitorFormat(std::ostringstream& output,\n                           const std::set<std::string> tags,\n                           bool display_per_fs, bool display_fxid,\n                           bool display_lfn) const;\n\n  //----------------------------------------------------------------------------\n  //! Get the require format for the given file identifier. Empty if no format\n  //! requested\n  //!\n  //! @param fid file identifier\n  //! @param display_fxid if true display the hex format\n  //! @param display_lfn if true then display the URI if available\n  //!\n  //! @return string representation\n  //----------------------------------------------------------------------------\n  std::string GetFidFormat(eos::IFileMD::id_t fid, bool display_fxid, bool\n                           display_lfn) const;\n\n  //----------------------------------------------------------------------------\n  //! Reset all collected errors in the error map\n  //----------------------------------------------------------------------------\n  void ResetErrorMaps();\n\n  //----------------------------------------------------------------------------\n  //! Account for offline replicas due to unavailable file systems\n  //! ie. rep_offline\n  //----------------------------------------------------------------------------\n  void AccountOfflineReplicas();\n\n  //----------------------------------------------------------------------------\n  //! Print offline replicas summary\n  //----------------------------------------------------------------------------\n  void PrintOfflineReplicas() const;\n\n  //----------------------------------------------------------------------------\n  //! Account for file with no replicas ie. zero_replica\n  //----------------------------------------------------------------------------\n  void AccountNoReplicaFiles();\n\n  //----------------------------------------------------------------------------\n  //! Account for offline files or files that require replica adjustments\n  //! i.e. file_offline and adjust_replica\n  //----------------------------------------------------------------------------\n  void AccountOfflineFiles();\n\n  //----------------------------------------------------------------------------\n  //! Print summary of the different type of errors collected so far and their\n  //! corresponding counters\n  //----------------------------------------------------------------------------\n  void PrintErrorsSummary() const;\n\n  //----------------------------------------------------------------------------\n  //! Account for \"dark\" file entries i.e. file system ids which have file\n  //! entries in the namespace view but have no configured file system in the\n  //! FsView.\n  //----------------------------------------------------------------------------\n  void AccountDarkFiles();\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/fsck/FsckEntry.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file FsckEntry.hh\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/fsck/FsckEntry.hh\"\n#include \"mgm/fsck/Fsck.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/proc/proc_fs.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/LayoutId.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"namespace/ns_quarkdb/persistency/MetadataFetcher.hh\"\n\nusing eos::common::StringConversion;\nusing eos::common::LayoutId;\n\nEOSMGMNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n//! Constructor\n//----------------------------------------------------------------------------\nFsckEntry::FsckEntry(eos::IFileMD::id_t fid,\n                     const std::set<eos::common::FileSystem::fsid_t>& fsid_err,\n                     const std::string& expected_err, bool best_effort,\n                     std::shared_ptr<qclient::QClient> qcl):\n  mFid(fid), mFsidErr(fsid_err),\n  mReportedErr(eos::common::ConvertToFsckErr(expected_err)),\n  mBestEffort(best_effort), mRepairFactory(), mQcl(qcl)\n{\n  using namespace eos::common;\n  mMapRepairOps = {\n    {FsckErr::MgmXsDiff,  &FsckEntry::RepairMgmXsSzDiff},\n    {FsckErr::MgmSzDiff,  &FsckEntry::RepairMgmXsSzDiff},\n    {FsckErr::FstXsDiff,  &FsckEntry::RepairFstXsSzDiff},\n    {FsckErr::FstSzDiff,  &FsckEntry::RepairFstXsSzDiff},\n    {FsckErr::BlockxsErr, &FsckEntry::RepairFstXsSzDiff},\n    {FsckErr::UnregRepl,  &FsckEntry::RepairInconsistencies},\n    {FsckErr::DiffRepl,   &FsckEntry::RepairInconsistencies},\n    {FsckErr::MissRepl,   &FsckEntry::RepairInconsistencies},\n    {FsckErr::StripeErr,  &FsckEntry::RepairInconsistencies}\n  };\n  mRepairFactory = [](eos::common::FileId::fileid_t fid,\n                      eos::common::FileSystem::fsid_t fsid_src,\n                      eos::common::FileSystem::fsid_t fsid_trg,\n                      std::set<eos::common::FileSystem::fsid_t> exclude_srcs,\n                      std::set<eos::common::FileSystem::fsid_t> exclude_dsts,\n                      bool drop_src, const std::string& app_tag, bool repair_excluded) {\n    auto fsck_job = std::make_shared<FsckRepairJob>(\n        fid, fsid_src, fsid_trg, exclude_srcs, exclude_dsts, drop_src, app_tag, false,\n        eos::common::VirtualIdentity::Root(), repair_excluded);\n    // Lower the min transfer rate to 5 MB/s\n    fsck_job->SetMinTransferRate(5);\n    return fsck_job;\n  };\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nFsckEntry::~FsckEntry()\n{\n  if (gOFS) {\n    gOFS->mFidTracker.RemoveEntry(mFid);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Collect MGM file metadata information\n//------------------------------------------------------------------------------\nbool\nFsckEntry::CollectMgmInfo()\n{\n  if (mQcl == nullptr) {\n    return false;\n  }\n\n  try {\n    mMgmFmd = eos::MetadataFetcher::getFileFromId(*mQcl.get(),\n              FileIdentifier(mFid)).get();\n  } catch (const eos::MDException& e) {\n    return false;\n  }\n\n  if (mMgmFmd.cont_id()) {\n    // Double check that the parent exists, if not, this is a detached entry and\n    // we need to clean it up and mark the parentId with 0 otherwise the fsck\n    // mechanism gets confused.\n    try {\n      eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n      (void) gOFS->eosDirectoryService->getContainerMD(mMgmFmd.cont_id());\n    } catch (const eos::MDException& e) {\n      mMgmFmd.set_cont_id(0ull);\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Collect FST file metadata information from all replicas\n//------------------------------------------------------------------------------\nvoid\nFsckEntry::CollectAllFstInfo()\n{\n  for (const auto fsid : mMgmFmd.locations()) {\n    CollectFstInfo(fsid);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Collect FST file metadata information\n//------------------------------------------------------------------------------\nvoid\nFsckEntry::CollectFstInfo(eos::common::FileSystem::fsid_t fsid)\n{\n  using eos::common::FileId;\n\n  if ((fsid == 0ull) || (fsid == EOS_TAPE_FSID) ||\n      (mFstFileInfo.find(fsid) != mFstFileInfo.end())) {\n    return;\n  }\n\n  std::string host_port;\n  std::string fst_local_path;\n  {\n    eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(fsid);\n\n    if (fs) {\n      host_port = fs->GetString(\"hostport\");\n      fst_local_path = fs->GetPath();\n    }\n  }\n\n  if (host_port.empty() || fst_local_path.empty()) {\n    eos_err(\"msg=\\\"missing or misconfigured file system\\\" fsid=%lu\", fsid);\n    mFstFileInfo.emplace(fsid, std::make_unique<FstFileInfoT>(\"\",\n                         FstErr::NotExistFs));\n    return;\n  }\n\n  std::ostringstream oss;\n  oss << \"root://\" << host_port << \"//dummy\";\n  std::string surl = oss.str();\n  XrdCl::URL url(surl);\n\n  if (!url.IsValid()) {\n    eos_err(\"msg=\\\"invalid url\\\" url=\\\"%s\\\"\", surl.c_str());\n    mFstFileInfo.emplace(fsid, std::make_unique<FstFileInfoT>(\"\",\n                         FstErr::NoContact));\n    return;\n  }\n\n  std::string fpath_local = FileId::FidPrefix2FullPath\n                            (FileId::Fid2Hex(mFid).c_str(), fst_local_path.c_str());\n  // Check that the file exists on disk\n  XrdCl::StatInfo* stat_info_raw {nullptr};\n  std::unique_ptr<XrdCl::StatInfo> stat_info;\n  uint16_t timeout = 10;\n  XrdCl::FileSystem fs(url);\n  XrdCl::XRootDStatus status = fs.Stat(fpath_local.c_str(), stat_info_raw,\n                                       timeout);\n  stat_info.reset(stat_info_raw);\n\n  if (!status.IsOK()) {\n    eos_err(\"msg=\\\"failed stat\\\" fxid=%08llx fsid=%lu local_path=%s \"\n            \"xrd_code=%u xrd_errno=%u\", mFid, fsid, fpath_local.c_str(),\n            status.code, status.errNo);\n\n    if (status.code == XrdCl::errOperationExpired) {\n      mFstFileInfo.emplace(fsid, std::make_unique<FstFileInfoT>(\"\",\n                           FstErr::NoContact));\n    } else {\n      if (XProtocol::toErrno(status.errNo) == ENOENT) {\n        mFstFileInfo.emplace(fsid, std::make_unique<FstFileInfoT>(\"\",\n                             FstErr::NotOnDisk));\n      } else {\n        mFstFileInfo.emplace(fsid, std::make_unique<FstFileInfoT>(\"\",\n                             FstErr::NoContact));\n      }\n    }\n\n    return;\n  }\n\n  // Collect file metadata stored on the FST about the current file\n  auto ret_pair =  mFstFileInfo.emplace(fsid, std::make_unique<FstFileInfoT>\n                                        (fpath_local.c_str(), FstErr::None));\n  auto& finfo = ret_pair.first->second;\n  finfo->mDiskSize = stat_info->GetSize();\n  (void) GetFstFmd(finfo, fs, fsid);\n}\n\n//------------------------------------------------------------------------------\n// Repair entry in best-effort mode\n//------------------------------------------------------------------------------\nbool\nFsckEntry::RepairBestEffort()\n{\n  // If not enabled then always fail\n  if (!mBestEffort) {\n    return false;\n  }\n\n  // Best-effort only works for replicas\n  if (LayoutId::IsRain(mMgmFmd.layout_id())) {\n    return false;\n  }\n\n  eos_info(\"msg=\\\"attempt best effort repair\\\" fxid=%08llx\", mFid);\n  // Find the best replica candidate that should be considered the reference\n  eos::common::FileSystem::fsid_t ref_fsid = 0ul;\n  uint64_t ref_sz = 0ull;\n  std::string ref_xs;\n  std::string mgm_xs_val =\n    StringConversion::BinData2HexString(mMgmFmd.checksum().c_str(),\n                                        SHA256_DIGEST_LENGTH,\n                                        LayoutId::GetChecksumLen(mMgmFmd.layout_id()));\n\n  for (auto it = mFstFileInfo.cbegin(); it != mFstFileInfo.cend(); ++it) {\n    auto& finfo = it->second;\n\n    if (finfo->mFstErr != FstErr::None) {\n      continue;\n    }\n\n    if (finfo->mFstFmd.mProtoFmd.diskchecksum().empty()) {\n      eos_static_info(\"msg=\\\"skip best-effort repair due to un-scanned \"\n                      \"replica\\\" fxid=%08llx\", mFid);\n      return false;\n    }\n\n    // If there is replica that matches the MGM info then use as reference\n    if ((finfo->mDiskSize == mMgmFmd.size()) &&\n        (finfo->mFstFmd.mProtoFmd.diskchecksum() == mgm_xs_val)) {\n      ref_fsid = it->first;\n      ref_sz = finfo->mDiskSize;\n      ref_xs = finfo->mFstFmd.mProtoFmd.diskchecksum();\n      break;\n    }\n\n    // First available replica or the one with more data is the reference\n    if ((ref_fsid == 0) || (ref_sz < finfo->mDiskSize)) {\n      ref_fsid = it->first;\n      ref_sz = finfo->mDiskSize;\n      ref_xs = finfo->mFstFmd.mProtoFmd.diskchecksum();\n    }\n  }\n\n  if (ref_fsid == 0) {\n    //@todo(esindril) if the file in the namespace is 0 size with correct\n    // 0-size checksum then we could consider this as repaired!\n    eos_static_err(\"msg=\\\"no suitable replica for best-effort repair found\\\" \"\n                   \"fxid=%08llx\", mFid);\n    return false;\n  }\n\n  size_t out_sz;\n  auto xs_binary = StringConversion::Hex2BinDataChar(ref_xs, out_sz,\n                   SHA256_DIGEST_LENGTH);\n\n  if (xs_binary == nullptr) {\n    eos_err(\"msg=\\\"best-effort repair failed due to disk checksum conversion \"\n            \"error\\\" fxid=%08llx ref_xs=\\\"%s\\\"\", mFid, ref_xs.c_str());\n    return false;\n  }\n\n  eos::Buffer xs_buff;\n  xs_buff.putData(xs_binary.get(), SHA256_DIGEST_LENGTH);\n\n  // Issue a verifystripe command towards the reference replica\n  if (gOFS) {\n    XrdOucErrInfo lerr;\n    auto root = eos::common::VirtualIdentity::Root();\n    std::string options = \"&mgm.verify.compute.checksum=1\"\n                          \"&mgm.verify.commit.checksum=1&mgm.verify.commit.size=1\";\n\n    if (gOFS->_verifystripe(mFid, lerr, root, ref_fsid, options)) {\n      eos_err(\"msg=\\\"failed verify stripe command\\\" fxid=%08llx fsid=%lu\",\n              mFid, ref_fsid);\n      return false;\n    }\n\n    // Wait until the MGM has received the update from the reference\n    // replica but no more than 5 min.\n    bool match = false;\n    auto now = std::chrono::system_clock::now();\n    auto ts_deadline = now + std::chrono::seconds(300);\n\n    while (now <= ts_deadline) {\n      try {\n        auto fmd = gOFS->eosFileService->getFileMD(mFid);\n        auto fmd_lock = eos::MDLocking::readLock(fmd.get());\n\n        if ((fmd->getSize() == ref_sz) &&\n            (strncmp(fmd->getChecksum().getDataPtr(),\n                     xs_buff.getDataPtr(), xs_buff.getSize()) == 0)) {\n          match = true;\n          // Update also the MGM fmd object\n          mMgmFmd.set_checksum(xs_buff.getDataPtr(), xs_buff.getSize());\n          mMgmFmd.set_size(ref_sz);\n          break;\n        }\n      } catch (const eos::MDException& e) {\n        eos_debug(\"msg=\\\"best-effort repair successful, file removed in the \"\n                  \"meantime\\\" fxid=%08llx\", mFid);\n        return true;\n      }\n\n      std::this_thread::sleep_for(std::chrono::seconds(10));\n      now = std::chrono::system_clock::now();\n    }\n\n    if (!match) {\n      eos_static_err(\"msg=\\\"best-effort repair failed as namespace info does \"\n                     \"not match reference replica within 5min deadline\\\" \"\n                     \"fxid=%08llx fsid=%lu\", mFid, ref_fsid);\n      return false;\n    }\n  } else {\n    // For testing we just update the MGM fmd object\n    mMgmFmd.set_checksum(xs_buff.getDataPtr(), xs_buff.getSize());\n    mMgmFmd.set_size(ref_sz);\n  }\n\n  std::set<eos::common::FileSystem::fsid_t> bad_fsids;\n\n  for (auto it = mFstFileInfo.cbegin(); it != mFstFileInfo.cend(); ++it) {\n    if (it->first != ref_fsid) {\n      bad_fsids.insert(it->first);\n    }\n  }\n\n  // Attempt repair if we don't have enough good replicas\n  size_t num_good_rep = 1;\n  size_t num_nominal_rep = LayoutId::GetStripeNumber(mMgmFmd.layout_id()) + 1;\n  bool all_repaired = true;\n\n  for (const auto bad_fsid : bad_fsids) {\n    if (num_good_rep >= num_nominal_rep) {\n      break;\n    }\n\n    // Trigger an fsck repair job (much like a drain job) doing a TPC\n    auto repair_job = mRepairFactory(mFid, bad_fsid, 0, bad_fsids,\n                                     bad_fsids, true, \"eos/fsck\", false);\n    repair_job->DoIt();\n\n    if (repair_job->GetStatus() != FsckRepairJob::Status::OK) {\n      eos_err(\"msg=\\\"best-effort repair failed\\\" fxid=%08llx bad_fsid=%lu\",\n              mFid, bad_fsid);\n      all_repaired = false;\n    } else {\n      eos_info(\"msg=\\\"best-effort repair successful\\\" fxid=%08llx bad_fsid=%lu\",\n               mFid, bad_fsid);\n      ++num_good_rep;\n    }\n  }\n\n  return all_repaired;\n}\n\n//------------------------------------------------------------------------------\n// Method to repair an mgm checksum difference error\n//------------------------------------------------------------------------------\nbool\nFsckEntry::RepairMgmXsSzDiff()\n{\n  // This only makes sense for replica layouts\n  if (LayoutId::IsRain(mMgmFmd.layout_id())) {\n    return true;\n  }\n\n  std::string mgm_xs_val =\n    StringConversion::BinData2HexString(mMgmFmd.checksum().c_str(),\n                                        SHA256_DIGEST_LENGTH,\n                                        LayoutId::GetChecksumLen(mMgmFmd.layout_id()));\n  // Make sure the disk xs and size values match between all the replicas\n  uint64_t sz_val {0ull};\n  std::string xs_val;\n  bool mgm_xs_sz_match = false; // one of the disk xs matches the mgm one\n  bool disk_xs_sz_match = true; // flag to mark that all disk xs match\n  // Mark if all replicas are not on disk - use case of 0-size files\n  bool all_not_on_disk = true;\n\n  for (auto it = mFstFileInfo.cbegin(); it != mFstFileInfo.cend(); ++it) {\n    auto& finfo = it->second;\n\n    if (finfo->mFstErr != FstErr::NotOnDisk) {\n      all_not_on_disk = false;\n    }\n\n    if (finfo->mFstErr != FstErr::None) {\n      eos_err(\"msg=\\\"unavailable replica info\\\" fxid=%08llx fsid=%lu\",\n              mFid, it->first);\n      disk_xs_sz_match = false;\n      continue;\n    }\n\n    // If we have an un-scanned replica that is different from the one we are\n    // trying to repair then we need to wait for the scanner.\n    if (finfo->mFstFmd.mProtoFmd.diskchecksum().empty() &&\n        (mFsidErr.find(it->first) == mFsidErr.end())) {\n      eos_info(\"msg=\\\"skip mgm xs/sz diff repair due to un-scanned replica\\\" \"\n               \"fxid=%08llx\", mFid);\n      return false;\n    }\n\n    if (xs_val.empty() && (sz_val == 0ull)) {\n      xs_val = finfo->mFstFmd.mProtoFmd.diskchecksum();\n      sz_val = finfo->mFstFmd.mProtoFmd.size();\n\n      if ((mgm_xs_val == xs_val) &&\n          (mMgmFmd.size() == sz_val) &&\n          (mMgmFmd.size() == finfo->mDiskSize)) {\n        mgm_xs_sz_match = true;\n        continue;\n      }\n    } else {\n      uint64_t current_sz_val = finfo->mFstFmd.mProtoFmd.size();\n      std::string current_xs_val = finfo->mFstFmd.mProtoFmd.diskchecksum();\n\n      if ((mgm_xs_val == current_xs_val) &&\n          (mMgmFmd.size() == current_sz_val) &&\n          (mMgmFmd.size() == finfo->mDiskSize)) {\n        mgm_xs_sz_match = true;\n        continue;\n      }\n\n      if ((xs_val != current_xs_val) ||\n          (sz_val != current_sz_val) ||\n          (sz_val != finfo->mDiskSize)) {\n        // There is a xs/size diff between two replicas, we can not fix\n        disk_xs_sz_match = false;\n        continue;\n      }\n    }\n  }\n\n  if (mgm_xs_sz_match) {\n    std::set<eos::common::FileSystem::fsid_t> good_fsids;\n    std::set<eos::common::FileSystem::fsid_t> bad_fsids;\n\n    for (auto it = mFstFileInfo.cbegin(); it != mFstFileInfo.cend(); ++it) {\n      auto& finfo = it->second;\n\n      if ((mMgmFmd.size() != finfo->mFstFmd.mProtoFmd.size()) ||\n          (mMgmFmd.size() != finfo->mDiskSize) ||\n          (mgm_xs_val != finfo->mFstFmd.mProtoFmd.diskchecksum())) {\n        if ((mMgmFmd.size() != finfo->mDiskSize) ||\n            (mgm_xs_val != finfo->mFstFmd.mProtoFmd.diskchecksum())) {\n          bad_fsids.insert(it->first);\n        } else {\n          // Trigger a resync of the FST info as it looks to be out of sync\n          ResyncFstMd(false);\n          return true;\n        }\n      } else {\n        good_fsids.insert(it->first);\n      }\n    }\n\n    if (good_fsids.empty()) {\n      eos_err(\"msg=\\\"mgm xs/size repair failed, no correct replicas\\\" \"\n              \"fxid=%08llx\", mFid);\n      return RepairBestEffort();\n    }\n\n    for (const auto bad_fsid : bad_fsids) {\n      gOFS->DropReplica(mFid, bad_fsid);\n    }\n\n    bool all_repaired = true;\n    // Attempt repair only if we don't have enough good replicas\n    size_t num_nominal_rep = LayoutId::GetStripeNumber(mMgmFmd.layout_id()) + 1;\n\n    if (good_fsids.size() < num_nominal_rep) {\n      for (auto bad_fsid : bad_fsids) {\n        // Trigger an fsck repair job (much like a drain job) doing a TPC\n        auto repair_job = mRepairFactory(mFid, bad_fsid, 0, bad_fsids,\n                                         bad_fsids, true, \"fsck\", false);\n        repair_job->DoIt();\n\n        if (repair_job->GetStatus() != FsckRepairJob::Status::OK) {\n          eos_err(\"msg=\\\"mgm xs/size repair failed\\\" fxid=%08llx bad_fsid=%lu\",\n                  mFid, bad_fsid);\n          all_repaired = false;\n        } else {\n          eos_info(\"msg=\\\"mgm xs/size repair replica successful\\\" \"\n                   \"fxid=%08llx bad_fsid=%lu\",  mFid, bad_fsid);\n        }\n      }\n    }\n\n    if (all_repaired) {\n      eos_info(\"msg=\\\"mgm xs/size repair successful\\\" fxid=%08llx\", mFid);\n    } else {\n      eos_warning(\"msg=\\\"mgm xs/size repair failed\\\" fxid=%08llx\", mFid);\n    }\n\n    return all_repaired;\n  }\n\n  if (disk_xs_sz_match && sz_val) {\n    size_t out_sz;\n    auto xs_binary = StringConversion::Hex2BinDataChar(xs_val, out_sz,\n                     SHA256_DIGEST_LENGTH);\n\n    if (xs_binary == nullptr) {\n      eos_err(\"msg=\\\"mgm xs/size repair failed due to disk checksum conversion \"\n              \"error\\\" fxid=%08llx disk_xs=\\\"%s\\\"\", mFid, xs_val.c_str());\n      return false;\n    }\n\n    eos::Buffer xs_buff;\n    xs_buff.putData(xs_binary.get(), SHA256_DIGEST_LENGTH);\n\n    if (gOFS) {\n      try {\n        eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, mFid);\n        // Grab the file metadata object and update it\n        eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n        auto fmd = gOFS->eosFileService->getFileMD(mFid);\n        fmd->setChecksum(xs_buff);\n        fmd->setSize(sz_val);\n        gOFS->eosView->updateFileStore(fmd.get());\n        // Update also the MGM fmd object\n        mMgmFmd.set_checksum(xs_buff.getDataPtr(), xs_buff.getSize());\n        mMgmFmd.set_size(sz_val);\n      } catch (const eos::MDException& e) {\n        eos_err(\"msg=\\\"mgm xs/size repair successful, file removed in the \"\n                \"meantime\\\" fxid=%08llx\", mFid);\n        return true;\n      }\n    } else {\n      // For testing we just update the MGM fmd object\n      mMgmFmd.set_checksum(xs_buff.getDataPtr(), xs_buff.getSize());\n      mMgmFmd.set_size(sz_val);\n    }\n\n    eos_info(\"msg=\\\"mgm xs/size repair successful\\\" fxid=%08llx old_mgm_xs=\\\"%s\\\" \"\n             \"new_mgm_xs=\\\"%s\\\"\", mFid, mgm_xs_val.c_str(), xs_val.c_str());\n  } else {\n    // Handle 0-size files with no replicas on disk which is legitimate\n    if ((mMgmFmd.size() == 0) && all_not_on_disk) {\n      eos_info(\"msg=\\\"repair successful for 0-size file with no replicas \"\n               \"on disk\\\" fxid=%08llx\", mFid);\n      return true;\n    }\n\n    eos_err(\"msg=\\\"mgm xs/size repair failed, not all disk xs/size match\\\" \"\n            \"fxid=%08llx\", mFid);\n    return RepairBestEffort();\n  }\n\n  return disk_xs_sz_match;\n}\n\n//----------------------------------------------------------------------------\n// Method to repair an FST checksum and/or size difference error\n//----------------------------------------------------------------------------\nbool\nFsckEntry::RepairFstXsSzDiff()\n{\n  // Rain failures will be flagged and fixed by the stripe_errs\n  if (LayoutId::IsRain(mMgmFmd.layout_id())) {\n    return true;\n  }\n\n  std::set<eos::common::FileSystem::fsid_t> bad_fsids;\n  std::set<eos::common::FileSystem::fsid_t> good_fsids;\n\n  // Replica layouts\n  std::string mgm_xs_val =\n    StringConversion::BinData2HexString(mMgmFmd.checksum().c_str(),\n                                        SHA256_DIGEST_LENGTH,\n                                        LayoutId::GetChecksumLen(mMgmFmd.layout_id()));\n  // Make sure at least one disk xs and size match the MGM ones\n  uint64_t sz_val {0ull};\n  std::string xs_val;\n\n  for (auto it = mFstFileInfo.cbegin(); it != mFstFileInfo.cend(); ++it) {\n    auto& finfo = it->second;\n\n    if (finfo->mFstErr != FstErr::None) {\n      eos_err(\"msg=\\\"unavailable replica info\\\" fxid=%08llx fsid=%lu\",\n              mFid, it->first);\n      bad_fsids.insert(it->first);\n      continue;\n    }\n\n    xs_val = finfo->mFstFmd.mProtoFmd.diskchecksum();\n    sz_val = finfo->mFstFmd.mProtoFmd.disksize();\n    eos_static_debug(\"mgm_sz=%llu mgm_xs=%s fst_sz_sz=%llu fst_sz_disk=%llu, \"\n                     \"fst_xs=%s\", mMgmFmd.size(), mgm_xs_val.c_str(),\n                     finfo->mFstFmd.mProtoFmd.size(),\n                     finfo->mFstFmd.mProtoFmd.disksize(),\n                     finfo->mFstFmd.mProtoFmd.checksum().c_str());\n\n    // The disksize/xs must also match the original reference size/xs\n    if ((mgm_xs_val == xs_val) && (mMgmFmd.size() == sz_val) &&\n        (finfo->mFstFmd.mProtoFmd.size() == sz_val) &&\n        (finfo->mFstFmd.mProtoFmd.checksum() == xs_val)) {\n      good_fsids.insert(finfo->mFstFmd.mProtoFmd.fsid());\n    } else {\n      // It could be that the diskchecksum for the replica was not yet\n      // computed - this does not mean the replica is bad\n      if (!finfo->mFstFmd.mProtoFmd.diskchecksum().empty()) {\n        bad_fsids.insert(finfo->mFstFmd.mProtoFmd.fsid());\n      }\n    }\n  }\n\n  if (bad_fsids.empty()) {\n    eos_warning(\"msg=\\\"fst xs/size repair skip - no bad replicas\\\" fxid=%08llx\",\n                mFid);\n    return true;\n  }\n\n  if (good_fsids.empty()) {\n    eos_err(\"msg=\\\"fst xs/size repair failed - no good replicas\\\" fxid=%08llx\",\n            mFid);\n    return RepairBestEffort();\n  }\n\n  // Have more good stripes then layout requirements\n  size_t num_nominal_rep = LayoutId::GetStripeNumber(mMgmFmd.layout_id()) + 1;\n\n  if (good_fsids.size() >= num_nominal_rep) {\n    if (LayoutId::IsRain(mMgmFmd.layout_id()) &&\n        (good_fsids.size() > num_nominal_rep)) {\n      eos_crit(\"msg=\\\"more stripes than RAIN layout\\\" fxid=%08llx\", mFid);\n      return false;\n    }\n\n    while (good_fsids.size() > num_nominal_rep) {\n      bad_fsids.insert(*good_fsids.begin());\n      good_fsids.erase(good_fsids.begin());\n    }\n\n    for (auto bad_fsid : bad_fsids) {\n      // If we have enough stripes - just drop it\n      gOFS->DropReplica(mFid, bad_fsid);\n    }\n\n    bad_fsids.clear();\n  }\n\n  bool all_repaired {true};\n\n  for (auto bad_fsid : bad_fsids) {\n    // Trigger an fsck repair job (much like a drain job) doing a TPC\n    auto repair_job = mRepairFactory(mFid, bad_fsid, 0, bad_fsids,\n                                     bad_fsids, true, \"fsck\", false);\n    repair_job->DoIt();\n\n    if (repair_job->GetStatus() != FsckRepairJob::Status::OK) {\n      eos_err(\"msg=\\\"fst xs/size repair failed\\\" fxid=%08llx bad_fsid=%lu\",\n              mFid, bad_fsid);\n      all_repaired = false;\n    } else {\n      eos_info(\"msg=\\\"fst xs/size repair successful\\\" fxid=%08llx bad_fsid=%lu\",\n               mFid, bad_fsid);\n    }\n\n    if (LayoutId::IsRain(mMgmFmd.layout_id())) {\n      break;\n    }\n  }\n\n  // Trigger an MGM resync on all the replicas so that the locations get\n  // updated properly\n  ResyncFstMd(true);\n  return all_repaired;\n}\n\n//------------------------------------------------------------------------------\n// Method to repair file inconsistencies\n//------------------------------------------------------------------------------\nbool\nFsckEntry::RepairInconsistencies()\n{\n  if (LayoutId::IsRain(mMgmFmd.layout_id())) {\n    return RepairRainInconsistencies();\n  } else {\n    return RepairReplicaInconsistencies();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Method to repair RAIN file inconsistencies\n//------------------------------------------------------------------------------\nbool\nFsckEntry::RepairRainInconsistencies()\n{\n  using namespace eos::common;\n\n  if (mReportedErr == FsckErr::UnregRepl) {\n    if (static_cast<unsigned long>(mMgmFmd.locations_size()) >=\n        LayoutId::GetStripeNumber(mMgmFmd.layout_id()) + 1) {\n      // If we have enough stripes and current error refers to a stripe which\n      // is not in the list of locations then drop it\n      bool found = false;\n\n      for (const auto loc : mMgmFmd.locations()) {\n        if (*mFsidErr.begin() == loc) {\n          found = true;\n          break;\n        }\n      }\n\n      if (!found) {\n        gOFS->DropReplica(mFid, *mFsidErr.begin());\n      }\n\n      return true;\n    } else {\n      // If not enough stripes then register it and trigger a check\n      if (gOFS) {\n        try {\n          // Grab the file metadata object and update it\n          eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, mFid);\n          eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n          auto fmd = gOFS->eosFileService->getFileMD(mFid);\n          fmd->addLocation(*mFsidErr.begin());\n          gOFS->eosView->updateFileStore(fmd.get());\n        } catch (const eos::MDException& e) {\n          eos_err(\"msg=\\\"unregistered repair successful, file removed \"\n                  \"in the meantime\\\" fxid=%08llx\", mFid);\n          return true;\n        }\n      } else {\n        // For testing just update the MGM fmd object\n        mMgmFmd.mutable_locations()->Add(*mFsidErr.begin());\n      }\n    }\n  }\n\n  if (mMgmFmd.locations().empty()) {\n    eos_err(\"msg=\\\"failed repair, no location available\\\" fxid=%08llx\", mFid);\n    return false;\n  }\n\n  // Trigger a fsck repair job to make sure all the remaining stripes are\n  // recovered and new ones are created if need be. By default pick the\n  // first stripe as \"source\" unless we have a better candidate\n  bool drop_src_fsid = false;\n  bool repair_excluded = false;\n  eos::common::FileSystem::fsid_t src_fsid = mMgmFmd.locations(0);\n  std::set<eos::common::FileSystem::fsid_t> bad_fsids;\n\n  if (mReportedErr == FsckErr::MissRepl) {\n    src_fsid = *mFsidErr.begin();\n    drop_src_fsid = true;\n    bool found = false;\n\n    for (const auto loc : mMgmFmd.locations()) {\n      if (src_fsid == loc) {\n        found = true;\n      }\n    }\n\n    // If reported missing stripe is not among the registred stripes and we\n    // already have the nominal number of stripes then we consider this fixed\n    if (!found) {\n      size_t num_nominal_rep = LayoutId::GetStripeNumber(mMgmFmd.layout_id()) + 1;\n\n      if (num_nominal_rep == mMgmFmd.locations().size()) {\n        eos_info(\"msg=\\\"missing stripe repair successful\\\" fxid=%08llx \"\n                 \"src_fsid=%lu\", mFid, src_fsid);\n        return true;\n      }\n    }\n  } else if (mReportedErr == FsckErr::DiffRepl) {\n    // For rep_diff_n errors the source file systems is not to be dropped\n    // or skipped during the scheduling process as it's a valid stripe\n    // useful when doing the transfer.\n    src_fsid = 0;\n\n    // Over-replication should never happend for RAIN files\n    if (static_cast<unsigned long>(mMgmFmd.locations_size()) >\n        LayoutId::GetStripeNumber(mMgmFmd.layout_id()) + 1) {\n      eos_err(\"msg=\\\"RAIN file over-replicated, to be handled manually\\\" \"\n              \"fxid=%08llu fsid_err=%lu\", mFid, *mFsidErr.begin());\n      return false;\n    } else if (static_cast<unsigned long>(mMgmFmd.locations_size()) ==\n               LayoutId::GetStripeNumber(mMgmFmd.layout_id()) + 1) {\n      eos_info(\"msg=\\\"stripe inconsistency repair successful\\\" fxid=%08llx \"\n               \"src_fsid=%lu\", mFid, src_fsid);\n      return true;\n    }\n  } else if (mReportedErr == FsckErr::StripeErr) {\n    // File has too many corrupted stripes, we can't recover\n    if (mFsidErr.find(0) != mFsidErr.end()) {\n      eos_err(\"msg=\\\"RAIN file has too many corrupted stripes, unable to \"\n              \"reconstruct\\\" fxid=%08llu\", mFid);\n      return false;\n    }\n\n    bad_fsids = mFsidErr;\n\n    // If there is over replication, drop replicas until we have the right\n    // number of stripes\n    while ((mMgmFmd.locations_size() >\n            LayoutId::GetStripeNumber(mMgmFmd.layout_id()) + 1) &&\n           !bad_fsids.empty()) {\n      const FileSystem::fsid_t drop_fsid = *bad_fsids.begin();\n      bad_fsids.erase(drop_fsid);\n      eos_info(\"msg=\\\"drop over-replicated stripe\\\" fxid=%08llx fsid=%lu\",\n               mFid, drop_fsid);\n      (void)gOFS->DropReplica(mFid, drop_fsid);\n      mFstFileInfo.erase(drop_fsid);\n      auto* mutable_loc = mMgmFmd.mutable_locations();\n\n      for (auto it = mutable_loc->begin(); it != mutable_loc->end(); ++it) {\n        if (*it == drop_fsid) {\n          mutable_loc->erase(it);\n          break;\n        }\n      }\n    }\n\n    // If there is the nominal number of stripes and the bad fsids are not\n    // among the attached fsids then these can be dropped\n    if ((mMgmFmd.locations_size() ==\n         LayoutId::GetStripeNumber(mMgmFmd.layout_id()) + 1) &&\n        !bad_fsids.empty()) {\n      std::set<eos::common::FileSystem::fsid_t> to_del;\n\n      for (const auto& bfsid : bad_fsids) {\n        bool found = false;\n\n        for (const auto& loc : mMgmFmd.locations()) {\n          if (bfsid == loc) {\n            found = true;\n            break;\n          }\n        }\n\n        // We can drop this stripe which is not in the list of locations\n        if (!found) {\n          eos_info(\"msg=\\\"drop bad unregistered stripe\\\" fxid=%08llx fsid=%lu\",\n                   mFid, bfsid);\n          to_del.insert(bfsid);\n          (void)gOFS->DropReplica(mFid, bfsid);\n          mFstFileInfo.erase(bfsid);\n        }\n      }\n\n      // Remove all the stripes that have been dropped\n      for (const auto& d_fsid : to_del) {\n        bad_fsids.erase(d_fsid);\n      }\n    }\n\n    if (bad_fsids.empty()) {\n      ResyncFstMd(true);\n      eos_info(\"msg=\\\"stripe inconsistency repair successful\\\" fxid=%08llx\",\n               mFid);\n      return true;\n    }\n\n    src_fsid = *bad_fsids.begin();\n    repair_excluded = true;\n  }\n\n  auto repair_job = mRepairFactory(mFid, src_fsid, 0, bad_fsids, bad_fsids,\n                                   drop_src_fsid, \"fsck\", repair_excluded);\n  repair_job->DoIt();\n\n  if (repair_job->GetStatus() != FsckRepairJob::Status::OK) {\n    eos_err(\"msg=\\\"stripe inconsistency repair failed\\\" fxid=%08llx \"\n            \"src_fsid=%lu\", mFid, src_fsid);\n    return false;\n  } else {\n    eos_info(\"msg=\\\"stripe inconsistency repair successful\\\" fxid=%08llx \"\n             \"src_fsid=%lu\", mFid, src_fsid);\n    return true;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Method to repair replica file inconsistencies\n//------------------------------------------------------------------------------\nbool\nFsckEntry::RepairReplicaInconsistencies()\n{\n  std::string mgm_xs_val =\n    StringConversion::BinData2HexString(mMgmFmd.checksum().c_str(),\n                                        SHA256_DIGEST_LENGTH,\n                                        LayoutId::GetChecksumLen(mMgmFmd.layout_id()));\n  std::set<eos::common::FileSystem::fsid_t> to_drop;\n  std::set<eos::common::FileSystem::fsid_t> unreg_fsids;\n  std::set<eos::common::FileSystem::fsid_t> repmiss_fsids;\n\n  // Account for missing replicas from MGM's perspective\n  for (const auto& fsid : mMgmFmd.locations()) {\n    eos_info(\"fxid=%08llx fsid=%lu\", mFid, fsid);\n    auto it = mFstFileInfo.find(fsid);\n\n    if ((it == mFstFileInfo.end()) ||\n        (it->second->mFstErr == FstErr::NotOnDisk)) {\n      eos_info(\"msg=\\\"mark as missing\\\" fxid=%08llx fsid=%lu\", mFid, fsid);\n      repmiss_fsids.insert(fsid);\n    }\n  }\n\n  // Account for unregisterd replicas and other replicas to be dropped\n  for (const auto& elem : mFstFileInfo) {\n    bool found = false;\n\n    for (const auto& loc : mMgmFmd.locations()) {\n      if (elem.first == loc) {\n        found = true;\n        break;\n      }\n    }\n\n    auto& finfo = elem.second;\n\n    if (found) {\n      if ((finfo->mFstErr == FstErr::NotOnDisk) ||\n          (finfo->mFstErr == FstErr::NotExistFs)) {\n        to_drop.insert(elem.first);\n      }\n    } else {\n      // The file system id does not exist\n      if (finfo->mFstErr == FstErr::NotExistFs) {\n        to_drop.insert(elem.first);\n      } else {\n        // Make sure the FST size/xs match the MGM ones\n        if ((finfo->mFstFmd.mProtoFmd.disksize() != mMgmFmd.size()) ||\n            (finfo->mFstFmd.mProtoFmd.diskchecksum() != mgm_xs_val)) {\n          to_drop.insert(elem.first);\n        } else {\n          unreg_fsids.insert(elem.first);\n        }\n      }\n    }\n  }\n\n  // First drop any missing replicas from the MGM\n  for (const auto& drop_fsid : repmiss_fsids) {\n    // Update the local MGM fmd object\n    auto mutable_loc = mMgmFmd.mutable_locations();\n\n    for (auto it = mutable_loc->begin(); it != mutable_loc->end(); ++it) {\n      if (*it == drop_fsid) {\n        mutable_loc->erase(it);\n        break;\n      }\n    }\n\n    if (gOFS) {\n      try { // Update the MGM file md object\n        eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, mFid);\n        eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n        auto fmd = gOFS->eosFileService->getFileMD(mFid);\n        fmd->unlinkLocation(drop_fsid);\n        fmd->removeLocation(drop_fsid);\n        gOFS->eosView->updateFileStore(fmd.get());\n        eos_info(\"msg=\\\"remove missing replica\\\" fxid=%08llx drop_fsid=%lu\",\n                 mFid, drop_fsid);\n      } catch (const eos::MDException& e) {\n        eos_err(\"msg=\\\"replica inconsistency repair successful, file removed \"\n                \"in the meantime\\\" fxid=%08llx\", mFid);\n        return true;\n      }\n    }\n  }\n\n  // Then drop any other inconsistent replicas from both the MGM and the FST\n  for (auto fsid : to_drop) {\n    (void) gOFS->DropReplica(mFid, fsid);\n    // Drop also from the local map of FST fmd info\n    mFstFileInfo.erase(fsid);\n    auto mutable_loc = mMgmFmd.mutable_locations();\n\n    for (auto it = mutable_loc->begin(); it != mutable_loc->end(); ++it) {\n      if (*it == fsid) {\n        mutable_loc->erase(it);\n        break;\n      }\n    }\n  }\n\n  to_drop.clear();\n  bool to_delete = (mMgmFmd.cont_id() == 0ull);\n\n  if (to_delete) {\n    XrdOucErrInfo err;\n    eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n    XrdOucEnv env(SSTR(\"mgm.fid=\" << eos::common::FileId::Fid2Hex(mFid)\n                       << \"&mgm.fsid=\" << 0\n                       << \"&mgm.dropall=1\").c_str());\n    gOFS->Drop(\"\", nullptr, env, err, vid, nullptr);\n    eos_info(\"msg=\\\"deleted detached file md\\\" fxid=%08llx\", mFid);\n    return true;\n  }\n\n  // Decide if we need to attach or discard any replicas\n  uint32_t num_expected_rep = LayoutId::GetStripeNumber(mMgmFmd.layout_id()) + 1;\n  uint32_t num_actual_rep = mMgmFmd.locations().size();\n\n  if (num_actual_rep >= num_expected_rep) { // over-replicated\n    int over_replicated = num_actual_rep - num_expected_rep;\n    // All the unregistered replicas can be dropped\n    to_drop.insert(unreg_fsids.begin(), unreg_fsids.end());\n\n    while ((over_replicated > 0) && !mMgmFmd.locations().empty()) {\n      to_drop.insert(mMgmFmd.locations(0));\n      mMgmFmd.mutable_locations()->erase(mMgmFmd.locations().begin());\n      --over_replicated;\n    }\n  } else {\n    if (num_actual_rep < num_expected_rep) { // under-replicated\n      // While under-replicated and we still have unregistered replicas then\n      // attach them\n      while ((num_actual_rep < num_expected_rep) && !unreg_fsids.empty()) {\n        eos::common::FileSystem::fsid_t new_fsid = *unreg_fsids.begin();\n        unreg_fsids.erase(unreg_fsids.begin());\n        mMgmFmd.add_locations(new_fsid);\n\n        if (gOFS) {\n          try {\n            eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, mFid);\n            eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n            auto fmd = gOFS->eosFileService->getFileMD(mFid);\n            fmd->addLocation(new_fsid);\n            gOFS->eosView->updateFileStore(fmd.get());\n            eos_info(\"msg=\\\"attached unregistered replica\\\" fxid=%08llx \"\n                     \"new_fsid=%lu\", mFid, new_fsid);\n          } catch (const eos::MDException& e) {\n            eos_err(\"msg=\\\"unregistered replica repair successful, file \"\n                    \" removed in the meantime\\\" fxid=%08llx\", mFid);\n            return true;\n          }\n        }\n\n        ++num_actual_rep;\n      }\n\n      // Drop any remaining unregistered replicas\n      to_drop.insert(unreg_fsids.begin(), unreg_fsids.end());\n\n      // If still under-replicated then start creating new replicas\n      while ((num_actual_rep < num_expected_rep) && mMgmFmd.locations_size()) {\n        // Trigger a fsck repair job but without dropping the source, this is\n        // similar to adjust replica\n        eos::common::FileSystem::fsid_t good_fsid = mMgmFmd.locations(0);\n        auto repair_job = mRepairFactory(mFid, good_fsid, 0, {}, to_drop,\n                                         false, \"fsck\", false);\n        repair_job->DoIt();\n\n        if (repair_job->GetStatus() != FsckRepairJob::Status::OK) {\n          eos_err(\"msg=\\\"replica inconsistency repair failed\\\" fxid=%08llx \"\n                  \"src_fsid=%lu\", mFid, good_fsid);\n          return RepairBestEffort();\n        } else {\n          eos_info(\"msg=\\\"replica inconsistency repair successful\\\" fxid=%08llx \"\n                   \"src_fsid=%lu\", mFid, good_fsid);\n        }\n\n        ++num_actual_rep;\n      }\n\n      if ((num_actual_rep < num_expected_rep) && mMgmFmd.size()) {\n        eos_err(\"msg=\\\"replica inconsistency repair failed\\\" fxid=%08llx\", mFid);\n        return false;\n      }\n    }\n  }\n\n  // Discard unregistered/bad replicas\n  for (auto fsid : to_drop) {\n    eos_info(\"msg=\\\"droping replica\\\" fxid=%08llx fsid=%lu\", mFid, fsid);\n    (void) gOFS->DropReplica(mFid, fsid);\n    // Drop also from the local map of FST fmd info\n    mFstFileInfo.erase(fsid);\n  }\n\n  ResyncFstMd(true);\n  eos_info(\"msg=\\\"file replicas consistent\\\" fxid=%08llx\", mFid);\n  return true;\n}\n\n//----------------------------------------------------------------------------\n// Resync local FST metadata with the MGM info. The refresh flag needs to\n// be set whenever there is an FsckRepairJob done before.\n//----------------------------------------------------------------------------\nvoid\nFsckEntry::ResyncFstMd(bool refresh_mgm_md)\n{\n  if (refresh_mgm_md) {\n    CollectMgmInfo();\n  }\n\n  for (const auto& fsid : mMgmFmd.locations()) {\n    if (gOFS) {\n      (void) gOFS->QueryResync(mFid, fsid);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Repair entry\n//------------------------------------------------------------------------------\nbool\nFsckEntry::Repair()\n{\n  using namespace eos::common;\n  bool success = false;\n\n  // If no MGM object then we are in testing mode\n  if (gOFS) {\n    gOFS->MgmStats.Add(\"FsckRepairStarted\", 0, 0, 1);\n\n    if (CollectMgmInfo() == false) {\n      eos_err(\"msg=\\\"no repair action, file is orphan\\\" fxid=%08llx fsid=%lu \"\n              \"err=%s\", mFid, *mFsidErr.begin(), FsckErrToString(mReportedErr).c_str());\n      success = true;\n      NotifyOutcome(success);\n      (void) gOFS->DropReplica(mFid, *mFsidErr.begin());\n      // This could be a ghost fid entry still present in the file system map\n      // and we need to also drop it from there\n      std::string out, err;\n      auto root_vid = eos::common::VirtualIdentity::Root();\n      (void) proc_fs_dropghosts(*mFsidErr.begin(), {mFid}, root_vid, out, err);\n      return success;\n    }\n\n    if (mMgmFmd.cont_id() == 0ull) {\n      eos_info(\"msg=\\\"force remove detached file\\\" fxid=%08llx\", mFid);\n      std::string err_msg;\n\n      if (!gOFS->RemoveDetached(mFid, false, true, err_msg)) {\n        eos_err(\"msg=\\\"operation failed due to: %s\\\"\", err_msg.c_str());\n      }\n\n      NotifyOutcome(true);\n      return true;\n    }\n\n    CollectAllFstInfo();\n    CollectFstInfo(*mFsidErr.begin());\n  }\n\n  for (const auto& loc : mMgmFmd.locations()) {\n    if (loc == EOS_TAPE_FSID) {\n      eos_info(\"msg=\\\"no repair action, file has tape replica\\\" fxid=%08llx\", mFid);\n      success = false;\n      NotifyOutcome(success);\n      return success;\n    }\n  }\n\n  if (mReportedErr != FsckErr::None) {\n    auto it = mMapRepairOps.find(mReportedErr);\n\n    if (it == mMapRepairOps.end()) {\n      eos_err(\"msg=\\\"unknown type of error\\\" errr=%i\", mReportedErr);\n      NotifyOutcome(success);\n      return success;\n    }\n\n    eos_static_info(\"msg=\\\"fsck repair\\\" fxid=%08llx err_type=%i fsid_err=%lu\",\n                    mFid, mReportedErr, *mFsidErr.begin());\n    auto fn_with_obj = std::bind(it->second, this);\n    success = fn_with_obj();\n    NotifyOutcome(success);\n    return success;\n  }\n\n  // If no explicit error given then try to repair all types of errors, we put\n  // the ones with higher priority first\n  std::list<RepairFnT> repair_ops {\n    &FsckEntry::RepairMgmXsSzDiff,\n    &FsckEntry::RepairFstXsSzDiff,\n    &FsckEntry::RepairInconsistencies};\n\n  for (const auto& op : repair_ops) {\n    auto fn_with_obj = std::bind(op, this);\n\n    if (!fn_with_obj()) {\n      NotifyOutcome(success);\n      return success;\n    }\n  }\n\n  success = true;\n  NotifyOutcome(success);\n  return success;\n}\n\n//------------------------------------------------------------------------------\n// Get file metadata info stored at the FST\n//------------------------------------------------------------------------------\nbool\nFsckEntry::GetFstFmd(std::unique_ptr<FstFileInfoT>& finfo,\n                     XrdCl::FileSystem& fs,\n                     eos::common::FileSystem::fsid_t fsid)\n{\n  XrdCl::Buffer* raw_response {nullptr};\n  // Create query command for file metadata\n  std::ostringstream oss;\n  oss << \"/?fst.pcmd=getfmd&fst.getfmd.fsid=\" << fsid\n      << \"&fst.getfmd.fid=\" << std::hex << mFid;\n  XrdCl::Buffer arg;\n  arg.FromString(oss.str().c_str());\n  uint16_t timeout = 10;\n  XrdCl::XRootDStatus status = fs.Query(XrdCl::QueryCode::OpaqueFile, arg,\n                                        raw_response, timeout);\n  std::unique_ptr<XrdCl::Buffer> response(raw_response);\n\n  if (!status.IsOK()) {\n    if (status.code == XrdCl::errOperationExpired) {\n      eos_err(\"msg=\\\"timeout file metadata query\\\" fxid=%08llx fsid=%lu\",\n              mFid, fsid);\n      finfo->mFstErr = FstErr::NoContact;\n    } else {\n      eos_err(\"msg=\\\"failed file metadata query\\\" fxid=08llx fsid=%lu\",\n              mFid, fsid);\n      finfo->mFstErr = FstErr::NoFmdInfo;\n    }\n\n    return false;\n  }\n\n  if ((response == nullptr) ||\n      (strncmp(response->GetBuffer(), \"ERROR\", 5) == 0)) {\n    eos_err(\"msg=\\\"no local fst metadata present\\\" fxid=%08llx fsid=%lu\",\n            mFid, fsid);\n    finfo->mFstErr = FstErr::NoFmdInfo;\n    return false;\n  }\n\n  // Parse in the file metadata info\n  XrdOucEnv fmd_env(response->GetBuffer());\n\n  if (!eos::common::EnvToFstFmd(fmd_env, finfo->mFstFmd)) {\n    eos_err(\"msg=\\\"failed parsing fmd env\\\" fsid=%lu\", fsid);\n    finfo->mFstErr = FstErr::NoFmdInfo;\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Update MGM stats and backend depending on the final outcome\n//------------------------------------------------------------------------------\nvoid\nFsckEntry::NotifyOutcome(bool success) const\n{\n  if (gOFS) {\n    // Update the MGM statistics and QDB backend in case of success\n    if (success) {\n      gOFS->MgmStats.Add(\"FsckRepairSuccessful\", 0, 0, 1);\n      const std::string sfsck_err = eos::common::FsckErrToString(mReportedErr);\n\n      if (mReportedErr == eos::common::FsckErr::StripeErr) {\n        for (auto fsid : mFsidErr) {\n          gOFS->mFsckEngine->NotifyFixedErr(mFid, fsid, sfsck_err);\n        }\n      } else {\n        // If error is not stripe error, only the first fsid has been fixed\n        gOFS->mFsckEngine->NotifyFixedErr(mFid, *mFsidErr.begin(), sfsck_err);\n      }\n\n      // Such errors are reported by all the attached locations so when they\n      // are fixed we need to update the fsck info for all of them\n      if (mReportedErr == eos::common::FsckErr::DiffRepl) {\n        for (const auto& loc : mMgmFmd.locations()) {\n          gOFS->mFsckEngine->NotifyFixedErr(mFid, loc, sfsck_err);\n        }\n      }\n    } else {\n      gOFS->MgmStats.Add(\"FsckRepairFailed\", 0, 0, 1);\n    }\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/fsck/FsckEntry.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file FsckEntry.hh\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include \"common/FileSystem.hh\"\n#include \"mgm/drain/DrainTransferJob.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/ns_quarkdb/qclient/include/qclient/QClient.hh\"\n#include \"common/Fmd.hh\"\n#include <XrdCl/XrdClFileSystem.hh>\n#include <functional>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Types of errors that come up on the FST side\n//------------------------------------------------------------------------------\nenum class FstErr {\n  None       = 0x00,\n  NoContact  = 0x01,\n  NotOnDisk  = 0x02,\n  NoFmdInfo  = 0x03,\n  NotExistFs = 0x04\n};\n\n//------------------------------------------------------------------------------\n//! FstFileInfoT holds file metadata info retrieved from an FST\n//------------------------------------------------------------------------------\nstruct FstFileInfoT {\npublic:\n  std::string mLocalPath;\n  uint64_t mDiskSize;\n  eos::common::FmdHelper mFstFmd;\n  FstErr mFstErr;\n\n  //------------------------------------------------------------------------------\n  //! Constructor\n  //------------------------------------------------------------------------------\n  FstFileInfoT(const std::string& local_path, FstErr err):\n    mLocalPath(local_path), mFstErr(err)\n  {}\n};\n\n//! Forward declaration and aliases\nclass FsckEntry;\nusing FsckRepairJob = eos::mgm::DrainTransferJob;\nusing RepairFnT = std::function<bool(FsckEntry*)>;\nusing RepairFactoryFnT =\n  std::function<std::shared_ptr<FsckRepairJob>\n  (eos::common::FileId::fileid_t fid,\n   eos::common::FileSystem::fsid_t fsid_src,\n   eos::common::FileSystem::fsid_t fsid_trg,\n   std::set<eos::common::FileSystem::fsid_t> exclude_srcs,\n   std::set<eos::common::FileSystem::fsid_t> exclude_dsts,\n   bool drop_src,\n   const std::string& app_tag,\n   bool repair_excluded)>;\n\n//------------------------------------------------------------------------------\n//! Class FsckEntry\n//------------------------------------------------------------------------------\nclass FsckEntry: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param fid file identifier\n  //! @param fsid_err file system which reported an error\n  //! @param expected_err expected type of error reported by the scanner\n  //! @param best_effort if true tryy best-effort repair\n  //! @param qcl QClient object for getting metadata information\n  //----------------------------------------------------------------------------\n  FsckEntry(eos::IFileMD::id_t fid,\n            const std::set<eos::common::FileSystem::fsid_t>& fsid_err,\n            const std::string& expected_err, bool best_effort,\n            std::shared_ptr<qclient::QClient> qcl);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~FsckEntry();\n\n  //----------------------------------------------------------------------------\n  //! Repair current entry\n  //!\n  //! @return true if successful repair and/or no errors, otherwise false\n  //----------------------------------------------------------------------------\n  bool Repair();\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  //----------------------------------------------------------------------------\n  //! Method to repair an mgm checksum and/or size difference error\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool RepairMgmXsSzDiff();\n\n  //----------------------------------------------------------------------------\n  //! Method to repair an FST checksum and/or size difference error\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool RepairFstXsSzDiff();\n\n  //----------------------------------------------------------------------------\n  //! Method to repair inconsistencies e.g. unregistered replicas,\n  //! under/over replication, missing replicas\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool RepairInconsistencies();\n\n  //----------------------------------------------------------------------------\n  //! Method to repair inconsistencies for replica files e.g. unregistered\n  //! replicas, under/over replication, missing replicas\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool RepairReplicaInconsistencies();\n\n  //----------------------------------------------------------------------------\n  //! Method to repair inconsistencies for RAIN files e.g. unregistered\n  //! stripes, under/over replication, missing stripes\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool RepairRainInconsistencies();\n\n  //----------------------------------------------------------------------------\n  //! Repair given entry in best-effort mode - this might mean we taken a\n  //! decision to consider one of the replicas as the correct one even though\n  //! there is no consistency between the data on disk and the namespace.\n  //! This is only used for replica-like layouts.\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool RepairBestEffort();\n\n  //----------------------------------------------------------------------------\n  //! Collect MGM file metadata information\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool CollectMgmInfo();\n\n  //----------------------------------------------------------------------------\n  //! Collect FST file metadata information from all replicas\n  //----------------------------------------------------------------------------\n  void CollectAllFstInfo();\n\n  //----------------------------------------------------------------------------\n  //! Collect FST file metadata information\n  //!\n  //! @param fsid file system identifier\n  //----------------------------------------------------------------------------\n  void CollectFstInfo(eos::common::FileSystem::fsid_t fsid);\n\n  //----------------------------------------------------------------------------\n  //! Get file metadata info stored at the FST\n  //!\n  //! @param finfo object holding file info to be populated\n  //! @param fs file system object used for queries\n  //! @param fsid file system identifier\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool GetFstFmd(std::unique_ptr<FstFileInfoT>& finfo, XrdCl::FileSystem& fs,\n                 eos::common::FileSystem::fsid_t fsid);\n\n  //----------------------------------------------------------------------------\n  //! Update MGM stats depending on the final outcome\n  //!\n  //! @param success true if repair successful, otherwise false\n  //----------------------------------------------------------------------------\n  void NotifyOutcome(bool success) const;\n\n  //----------------------------------------------------------------------------\n  //! Resync local FST metadata with the MGM info.\n  //!\n  //! @param refresh_mgm_md if true then the MGM metadata corresponding to the\n  //!        current file is retrieved again, otherwise we use what we have\n  //!        already. This needs tobe set whenever there is an FsckRepairJob\n  //!        done before.\n  //----------------------------------------------------------------------------\n  void ResyncFstMd(bool refresh_mgm_md = false);\n\n  eos::IFileMD::id_t mFid; ///< File id\n  //! File system ids with expected errors\n  std::set<eos::common::FileSystem::fsid_t> mFsidErr;\n  eos::common::FsckErr mReportedErr; ///< Reported error type\n  bool mBestEffort; ///< Mark if best effort is allowed\n  eos::ns::FileMdProto mMgmFmd; ///< MGM file metadata protobuf object\n  //! Map of file system id to file metadata held at the corresponding fs\n  std::map<eos::common::FileSystem::fsid_t,\n      std::unique_ptr<FstFileInfoT>> mFstFileInfo;\n  //! Map of fsck error to list of repair operations\n  std::map<eos::common::FsckErr, RepairFnT> mMapRepairOps;\n  //! Factory callable creating fsck repair jobs\n  RepairFactoryFnT mRepairFactory;\n  std::shared_ptr<qclient::QClient> mQcl; ///< QClient object for metadata\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/fsview/FsView.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FsView.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/fsview/FsView.hh\"\n#include \"common/Assert.hh\"\n#include \"common/Constants.hh\"\n#include \"common/InstanceName.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/config/ConfigParsing.hh\"\n#include \"common/json/Json.hh\"\n#include \"common/table_formatter/TableFormatterBase.hh\"\n#include \"common/token/EosTok.hh\"\n#include \"mgm/balancer/FsBalancer.hh\"\n#include \"mgm/config/IConfigEngine.hh\"\n#include \"mgm/geobalancer/GeoBalancer.hh\"\n#include \"mgm/geotreeengine/GeoTreeEngine.hh\"\n#include \"mgm/groupbalancer/GroupBalancer.hh\"\n#include \"mgm/groupdrainer/GroupDrainer.hh\"\n#include \"mgm/http/rest-api/Constants.hh\"\n#include \"mgm/inspector/FileInspector.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/placement/FsScheduler.hh\"\n#include \"mgm/policy/Policy.hh\"\n#include \"mgm/tgc/Constants.hh\"\n#include \"mgm/zmq/ZMQ.hh\"\n#include \"mq/SharedHashWrapper.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include <cfloat>\n#include <curl/curl.h>\n\nusing eos::common::RWMutexReadLock;\n\nnamespace\n{\n\n//------------------------------------------------------------------------------\n// Helper method to set status for a file system\n//------------------------------------------------------------------------------\nvoid setFsStatus(FileSystem* fs,\n                 eos::common::ActiveStatus status,\n                 eos::common::BootStatus bstatus)\n{\n  if (fs == nullptr) {\n    return;\n  }\n\n  fs->SetActiveStatus(status);\n  gOFS->mFsScheduler->setDiskStatus(fs->GetSpace(), fs->GetId(),\n                                    status, bstatus);\n}\n}\n\nEOSMGMNAMESPACE_BEGIN\n\nFsView FsView::gFsView;\nstd::atomic<bool> FsSpace::gDisableDefaults {false};\nstd::string FsNode::msRefreshTag {\"stat.refresh_fs\"};\n\n\n//------------------------------------------------------------------------------\n// Check if given heartbeat timestamp is recent enough\n//------------------------------------------------------------------------------\ninline bool isHeartbeatRecent(time_t heartbeatTime)\n{\n  time_t now = time(NULL);\n\n  if ((now - heartbeatTime) < 60) {\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Destructor - destructs all the branches starting at this node\n//------------------------------------------------------------------------------\nGeoTreeElement::~GeoTreeElement()\n{\n  for (auto it = mSons.begin(); it != mSons.end(); it++) {\n    delete it->second;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nGeoTree::GeoTree() : pLevels(8)\n{\n  pLevels.resize(1);\n  pRoot = new GeoTreeElement;\n  pLevels[0].insert(pRoot);\n  pRoot->mTagToken = \"<ROOT>\";\n  pRoot->mFullTag = \"<ROOT>\";\n  pRoot->mFather = NULL;\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nGeoTree::~GeoTree()\n{\n  delete pRoot;\n}\n\n//------------------------------------------------------------------------------\n// Insert a FileSystem into the tree\n//------------------------------------------------------------------------------\nbool GeoTree::insert(const fsid_t& fs)\n{\n  if (pLeaves.count(fs)) {\n    return false;\n  }\n\n  std::string geotag = getGeoTag(fs);\n  // Tokenize the geotag (geo tag is like adas::acsd::csdw::fee)\n  std::vector<std::string> geotokens;\n  eos::common::StringConversion::EmptyTokenize(geotag, geotokens, \":\");\n  size_t s;\n  s = geotokens.size();\n\n  for (size_t i = 0; i < s; i++) {\n    if (geotokens[i].size()) {\n      geotokens.push_back(geotokens[i]);\n    }\n  }\n\n  geotokens.erase(geotokens.begin(), geotokens.begin() + s);\n\n  if (geotokens.empty()) {\n    geotokens.push_back(\"\");  // geotag is not provided\n  }\n\n  GeoTreeElement* father = pRoot;\n  std::string fulltag = pRoot->mFullTag;\n  // Insert all the geotokens in the tree\n  GeoTreeElement* currentnode = pRoot;\n  GeoTreeElement* currentleaf = NULL;\n\n  for (int i = 0; i < (int)geotokens.size() - 1; i++) {\n    const std::string& geotoken = geotokens[i];\n\n    if (currentnode->mSons.count(geotoken)) {\n      currentnode = father->mSons[geotoken];\n\n      if (!fulltag.empty()) {\n        fulltag += \"::\";\n      }\n\n      fulltag += geotoken;\n    } else {\n      currentnode = new GeoTreeElement;\n      currentnode->mTagToken = geotoken;\n\n      if (!fulltag.empty()) {\n        fulltag += \"::\";\n      }\n\n      fulltag += geotoken;\n      currentnode->mFullTag = fulltag;\n      currentnode->mFather = father;\n      father->mSons[geotoken] = currentnode;\n\n      if ((int)pLevels.size() < i + 2) {\n        pLevels.resize(i + 2);\n      }\n\n      pLevels[i + 1].insert(currentnode);\n    }\n\n    father = currentnode;\n  }\n\n  // Finally, insert the fs\n  if (!father->mSons.count(geotokens.back())) {\n    currentleaf = new GeoTreeElement;\n    currentleaf->mFather = father;\n    currentleaf->mTagToken = geotokens.back();\n\n    if (!fulltag.empty()) {\n      fulltag += \"::\";\n    }\n\n    fulltag += geotokens.back();\n    currentleaf->mFullTag = fulltag;\n    father->mSons[geotokens.back()] = currentleaf;\n\n    if (pLevels.size() < geotokens.size() + 1) {\n      pLevels.resize(geotokens.size() + 1);\n    }\n\n    pLevels[geotokens.size()].insert(currentleaf);\n  } else {\n    // assert(father->mSons[geotokens.back()]->mIsLeaf);\n    currentleaf = father->mSons[geotokens.back()];\n  }\n\n  if (!currentleaf->mFsIds.count(fs)) {\n    currentleaf->mFsIds.insert(fs);\n    pLeaves[fs] = currentleaf;\n  } else {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Get number of file systems in the tree\n//------------------------------------------------------------------------------\nsize_t GeoTree::size() const\n{\n  return pLeaves.size();\n}\n\n//------------------------------------------------------------------------------\n// Remove a file system from the tree\n//------------------------------------------------------------------------------\nbool GeoTree::erase(const fsid_t& fs)\n{\n  GeoTreeElement* leaf;\n\n  if (!pLeaves.count(fs)) {\n    return false;\n  } else {\n    leaf = pLeaves[fs];\n  }\n\n  pLeaves.erase(fs);\n  leaf->mFsIds.erase(fs);\n  GeoTreeElement* father = leaf;\n\n  if (leaf->mFsIds.empty() && leaf->mSons.empty()) {\n    // Compute the depth for the current father\n    int depth = -1;\n\n    for (int i = (int)pLevels.size() - 1; i >= 0; i--) {\n      if (pLevels[i].count(father)) {\n        depth = i;\n        break;\n      }\n    }\n\n    assert(depth >= 0); // consistency check\n\n    if (depth < 0) {\n      return false;\n    }\n\n    // Go uproot until there is more than one branch\n    while (father->mFather && father->mFather->mSons.size() == 1 &&\n           father->mFather->mFsIds.empty()) {\n      if (father->mFather == pRoot) {\n        break;\n      }\n\n      pLevels[depth--].erase(father);\n      // We don't update the father's sons list on purpose in order to keep\n      // the reference for the destruction\n      father = father->mFather;\n    }\n\n    // Erase the full branch\n    if (father->mFather) {\n      father->mFather->mSons.erase(father->mTagToken);\n    }\n\n    pLevels[depth].erase(father);\n    delete father;\n    // Update the pLevels size if needed\n    int count = 0;\n\n    for (auto it = pLevels.rbegin(); it != pLevels.rend(); it++) {\n      if (!it->empty()) {\n        if (count) {\n          pLevels.resize(pLevels.size() - count);\n        }\n\n        break;\n      }\n\n      count++;\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Get the geotag at which the fs is stored if found\n//------------------------------------------------------------------------------\nbool GeoTree::getGeoTagInTree(const fsid_t& fs, std::string& geoTag)\n{\n  if (!pLeaves.count(fs)) {\n    return false;\n  } else {\n    geoTag = pLeaves[fs]->mFullTag;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Get file system geotag\n//------------------------------------------------------------------------------\nstd::string GeoTree::getGeoTag(const fsid_t& fs) const\n{\n  FileSystem* entry = FsView::gFsView.mIdView.lookupByID(fs);\n\n  if (!entry) {\n    return \"\";\n  }\n\n  return entry->GetString(\"stat.geotag\");\n}\n\n//------------------------------------------------------------------------------\n//               * * *   Class GeoTree::const_iterator * * *\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Copy assignment operator\n//------------------------------------------------------------------------------\nGeoTree::const_iterator&\nGeoTree::const_iterator::operator= (const const_iterator& it)\n{\n  if (this != &it) {\n    mIt = it.mIt;\n    mCont = it.mCont;\n  }\n\n  return *this;\n}\n\n//------------------------------------------------------------------------------\n// ++ operator pre-increment\n//------------------------------------------------------------------------------\nGeoTree::const_iterator&\nGeoTree::const_iterator::operator++()\n{\n  if (mIt != mCont->end()) {\n    ++mIt;\n  }\n\n  return *this;\n}\n\n//------------------------------------------------------------------------------\n// ++ operator post-increment\n//------------------------------------------------------------------------------\nGeoTree::const_iterator\nGeoTree::const_iterator::operator++(int)\n{\n  GeoTree::const_iterator it(*this);\n\n  if (mIt != mCont->end()) {\n    ++mIt;\n  }\n\n  return it;\n}\n\n//------------------------------------------------------------------------------\n// -- operator pre-decrement\n//------------------------------------------------------------------------------\nGeoTree::const_iterator&\nGeoTree::const_iterator::operator--()\n{\n  if (mIt != mCont->begin()) {\n    --mIt;\n  }\n\n  return *this;\n}\n\n//------------------------------------------------------------------------------\n// -- operator post-decrement\n//------------------------------------------------------------------------------\nGeoTree::const_iterator\nGeoTree::const_iterator::operator--(int)\n{\n  GeoTree::const_iterator it(*this);\n\n  if (mIt != mCont->begin()) {\n    --mIt;\n  }\n\n  return it;\n}\n\n//------------------------------------------------------------------------------\n// Indirection operator\n//------------------------------------------------------------------------------\nconst eos::common::FileSystem::fsid_t&\nGeoTree::const_iterator::operator*() const\n{\n  return mIt->first;\n}\n\n//------------------------------------------------------------------------------\n// fsid_iterator: Iterate either over a given subset, or the pLeaves map.\n// Yes this is weird, but we need it for certain BaseView functions.\n//------------------------------------------------------------------------------\nclass fsid_iterator\n{\npublic:\n  //----------------------------------------------------------------------------\n  // Constructor. Iterate through subset: if subset is nullptr, iterate through\n  // tree instead.\n  //----------------------------------------------------------------------------\n  fsid_iterator(const std::set<eos::common::FileSystem::fsid_t>* subset,\n                GeoTree* tree)\n  {\n    subsetValid = subset != nullptr;\n\n    if (subsetValid) {\n      subsetIter = subset->begin();\n      subsetEnd = subset->end();\n    } else {\n      geotreeIter = tree->begin();\n      geotreeEnd = tree->end();\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  // Is the iterator still valid?\n  //----------------------------------------------------------------------------\n  bool valid() const\n  {\n    if (subsetValid) {\n      return subsetIter != subsetEnd;\n    }\n\n    return geotreeIter != geotreeEnd;\n  }\n\n  //----------------------------------------------------------------------------\n  // Advance\n  //----------------------------------------------------------------------------\n  void next()\n  {\n    if (!valid()) {\n      return;\n    }\n\n    if (subsetValid) {\n      subsetIter++;\n    } else {\n      geotreeIter++;\n    }\n  }\n\n  eos::common::FileSystem::fsid_t operator*() const\n  {\n    if (subsetValid) {\n      return *subsetIter;\n    }\n\n    return *geotreeIter;\n  }\n\nprivate:\n  bool subsetValid;\n  std::set<eos::common::FileSystem::fsid_t>::const_iterator subsetIter;\n  std::set<eos::common::FileSystem::fsid_t>::const_iterator subsetEnd;\n\n  GeoTree::const_iterator geotreeIter;\n  GeoTree::const_iterator geotreeEnd;\n};\n\n//------------------------------------------------------------------------------\n// Run an aggregator through the tree\n//------------------------------------------------------------------------------\nbool GeoTree::runAggregator(GeoTreeAggregator* aggregator) const\n{\n  if (pLevels.empty()) {\n    return false;\n  }\n\n  // Build the GeoTags and the depth indexes\n  size_t elemCount = 0;\n  std::vector<std::string> geotags;\n  std::vector<size_t> depthlevelsendindexes;\n\n  for (auto itl = pLevels.begin(); itl != pLevels.end(); itl++) {\n    geotags.resize(geotags.size() + itl->size());\n\n    for (auto ite = itl->rbegin(); ite != itl->rend(); ite++) {\n      // could be made faster and more complex but probably not necessary for the moment\n      geotags[elemCount] = (*ite)->mTagToken;\n      GeoTreeElement* element = *ite;\n\n      while (element->mFather) {\n        element = element->mFather;\n        geotags[elemCount] = element->mTagToken + \"::\" + geotags[elemCount];\n      }\n\n      elemCount++;\n    }\n\n    depthlevelsendindexes.push_back(elemCount);\n  }\n\n  aggregator->init(geotags, depthlevelsendindexes);\n  elemCount--;\n\n  for (auto itl = pLevels.rbegin(); itl != pLevels.rend(); itl++) {\n    for (auto ite = itl->begin(); ite != itl->end(); ite++) {\n      (*ite)->mId = elemCount;\n\n      if (!aggregator->aggregateLeavesAndNodes((*ite)->mFsIds, (*ite)->mSons,\n          elemCount--)) {\n        return false;\n      }\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// @brief Get the sums at each tree element\n//------------------------------------------------------------------------------\nconst std::vector<double>* DoubleAggregator::getSums() const\n{\n  return &pSums;\n}\n\n//------------------------------------------------------------------------------\n// @brief Get the averages at each tree element\n//------------------------------------------------------------------------------\nconst std::vector<double>* DoubleAggregator::getMeans() const\n{\n  return &pMeans;\n}\n\n//------------------------------------------------------------------------------\n// @brief Get the maximum deviations at each tree element\n//------------------------------------------------------------------------------\nconst std::vector<double>* DoubleAggregator::getMaxAbsDevs() const\n{\n  return &pMaxAbsDevs;\n}\n\n//------------------------------------------------------------------------------\n// @brief Get the standard deviations at each tree element\n//------------------------------------------------------------------------------\nconst std::vector<double>* DoubleAggregator::getStdDevs() const\n{\n  return &pStdDevs;\n}\n\n//------------------------------------------------------------------------------\n// Get the geotags at each tree element\n//------------------------------------------------------------------------------\nconst std::vector<std::string>* DoubleAggregator::getGeoTags() const\n{\n  return &pGeoTags;\n}\n\n//------------------------------------------------------------------------------\n// @brief Get the end index (excluded) for a given depth level\n// @param depth the maximum depth to be reached (-1 for unlimited)\n// @return the index of the first element in the vectors being deeper that depth\n//------------------------------------------------------------------------------\nsize_t DoubleAggregator::getEndIndex(int depth) const\n{\n  if (depth < 0 || depth > (int)pDepthLevelsIndexes.size() - 1) {\n    depth = pDepthLevelsIndexes.size() - 1;\n  }\n\n  return pDepthLevelsIndexes[depth];\n};\n\n//------------------------------------------------------------------------------\n// @brief Constructor\n// @param param Name of the parameter statistics have to be computed for\n//------------------------------------------------------------------------------\nDoubleAggregator::DoubleAggregator(const char* param):\n  pParam(param), pView(NULL)\n{}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nDoubleAggregator::~DoubleAggregator()\n{}\n\n//------------------------------------------------------------------------------\n// @brief Set the view ordering the statistics. Needs to be set before running\n// the aggregator.\n// @param view Pointer to the view ordering the statistics\n//------------------------------------------------------------------------------\nvoid DoubleAggregator::setView(BaseView* view)\n{\n  pView = view;\n}\n\n//------------------------------------------------------------------------------\n// Initialize\n//------------------------------------------------------------------------------\nbool\nDoubleAggregator::init(const std::vector<std::string>& geotags,\n                       const std::vector<size_t>& depthLevelsIndexes)\n{\n  // Check that the view is defined, this is necessary for the subsequent calls\n  // to AggregateXXX.\n  assert(pView);\n  pGeoTags = geotags;\n  pDepthLevelsIndexes = depthLevelsIndexes;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Aggregate leaves\n//------------------------------------------------------------------------------\nbool\nDoubleAggregator::aggregateLeaves(\n  const std::set<eos::common::FileSystem::fsid_t>& leaves, const size_t& idx)\n{\n  // The following should happen only at the first call\n  if ((int)idx > (int)pMeans.size() - 1) {\n    pSums.resize(idx + 1);\n    pMeans.resize(idx + 1);\n    pMaxDevs.resize(idx + 1);\n    pMinDevs.resize(idx + 1);\n    pMaxAbsDevs.resize(idx + 1);\n    pStdDevs.resize(idx + 1);\n    pNb.resize(idx + 1);\n  }\n\n  pNb[idx] = pView->ConsiderCount(false, &leaves);\n\n  if (pNb[idx]) {\n    pSums[idx] = pView->SumDouble(pParam.c_str(), false, &leaves);\n    pMeans[idx] = pView->AverageDouble(pParam.c_str(), false, &leaves);\n    pMaxDevs[idx] = (pNb[idx] == 1) ? 0 : pView->MaxDeviation(pParam.c_str(), false,\n                    &leaves);\n    pMinDevs[idx] = (pNb[idx] == 1) ? 0 : pView->MinDeviation(pParam.c_str(), false,\n                    &leaves);\n    pStdDevs[idx] = (pNb[idx] == 1) ? 0 : pView->SigmaDouble(pParam.c_str(), false,\n                    &leaves);\n    pMaxAbsDevs[idx] = (pNb[idx] == 1) ? 0 : std::max(abs(pMaxDevs[idx]),\n                       abs(pMinDevs[idx]));\n  } else {\n    pSums[idx] = 0;\n    pMeans[idx] = 0;\n    pMaxDevs[idx] = 0;\n    pMinDevs[idx] = 0;\n    pStdDevs[idx] = 0;\n    pMaxAbsDevs[idx] = 0;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Aggregate nodes\n//------------------------------------------------------------------------------\nbool\nDoubleAggregator::aggregateNodes(\n  const std::map<std::string, GeoTreeElement*>& nodes,\n  const size_t& idx, bool includeSelf)\n{\n  double pS, pM, pMAD, pSD, pMiD, pMaD;\n  pS = pM = pMAD = pSD = 0;\n  pMiD = DBL_MAX;\n  pMaD = -DBL_MAX;\n  long long pN = 0;\n\n  for (auto it = nodes.begin(); it != nodes.end(); it++) {\n    size_t i = it->second->mId;\n    pS += pSums[i];\n    pN += pNb[i];\n  }\n\n  if (pN) {\n    pM = pS / pN;\n  }\n\n  for (auto it = nodes.begin(); it != nodes.end(); it++) {\n    size_t i = it->second->mId;\n\n    if (pNb[i]) { // consider this only if there is something there\n      pMiD = std::min(pMiD, std::min((pMinDevs[i] + pMeans[i]) - pM,\n                                     (pMaxDevs[i] + pMeans[i]) - pM));\n      pMaD = std::max(pMaD, std::max((pMinDevs[i] + pMeans[i]) - pM,\n                                     (pMaxDevs[i] + pMeans[i]) - pM));\n      pSD += pNb[i] * (pStdDevs[i] * pStdDevs[i] + pMeans[i] * pMeans[i]);\n    }\n  }\n\n  if (pN) {\n    pSD = sqrt(pSD / pN - pM * pM);\n    pMAD = std::max(fabs(pMaD), fabs(pMiD));\n  }\n\n  if (includeSelf) {\n    pS += pSums[idx];\n    pN += pNb[idx];\n\n    if (pN) {\n      pM = pS / pN;\n    }\n\n    pMiD = std::min(pMiD,\n                    std::min((pMinDevs[idx] + pMeans[idx]) - pM,\n                             (pMaxDevs[idx] + pMeans[idx]) - pM));\n    pMaD = std::max(pMaD,\n                    std::max((pMinDevs[idx] + pMeans[idx]) - pM,\n                             (pMaxDevs[idx] + pMeans[idx]) - pM));\n    pSD += pNb[idx] * (pStdDevs[idx] * pStdDevs[idx] + pMeans[idx] * pMeans[idx]);\n\n    if (pN) {\n      pSD = sqrt(pSD / pN - pM * pM);\n      pMAD = std::max(fabs(pMaD), fabs(pMiD));\n    }\n  }\n\n  pSums[idx] = pS;\n  pMeans[idx] = pM;\n  pMaxAbsDevs[idx] = pMAD;\n  pStdDevs[idx] = pSD;\n  pMinDevs[idx] = pMiD;\n  pMaxDevs[idx] = pMaD;\n  pNb[idx] = pN;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// @brief Constructor\n// @param param Name of the parameter statistics have to be computed for\n//------------------------------------------------------------------------------\nLongLongAggregator::LongLongAggregator(const char* param):\n  pParam(param), pView(NULL)\n{}\n\n//------------------------------------------------------------------------------\n// @brief Destructor\n//------------------------------------------------------------------------------\nLongLongAggregator::~LongLongAggregator()\n{}\n\n//------------------------------------------------------------------------------\n// @brief Set the view ordering the statistics. Needs to be set before running\n//        the aggregator\n// @param view Pointer to the view ordering the statistics\n//------------------------------------------------------------------------------\nvoid LongLongAggregator::setView(BaseView* view)\n{\n  pView = view;\n}\n\n//------------------------------------------------------------------------------\n// Initialize\n//------------------------------------------------------------------------------\nbool\nLongLongAggregator::init(const std::vector<std::string>& geotags,\n                         const std::vector<size_t>& depthLevelsIndexes)\n{\n  assert(pView);\n  pGeoTags = geotags;\n  pDepthLevelsIndexes = depthLevelsIndexes;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n//@brief Get the sums at each tree element\n//------------------------------------------------------------------------------\nconst std::vector<long long>* LongLongAggregator::getSums() const\n{\n  return &pSums;\n}\n\n//------------------------------------------------------------------------------\n// @brief Get the geotags at each tree element\n//------------------------------------------------------------------------------\nconst std::vector<std::string>* LongLongAggregator::getGeoTags() const\n{\n  return &pGeoTags;\n}\n\n//------------------------------------------------------------------------------\n// @brief Get the end index (excluded) for a given depth level\n// @param depth the maximum depth to be reached (-1 for unlimited)\n// @return the index of the first element in the vectors being deeper that depth\n//------------------------------------------------------------------------------\nsize_t LongLongAggregator::getEndIndex(int depth) const\n{\n  if (depth < 0 || depth > (int)pDepthLevelsIndexes.size() - 1) {\n    depth = pDepthLevelsIndexes.size() - 1;\n  }\n\n  return pDepthLevelsIndexes[depth];\n};\n\n//------------------------------------------------------------------------------\n// Aggregate leaves\n//------------------------------------------------------------------------------\nbool\nLongLongAggregator::aggregateLeaves(\n  const std::set<eos::common::FileSystem::fsid_t>& leaves,\n  const size_t& idx)\n{\n  // The following should happen only at the first call\n  if ((int)idx > (int)pSums.size() - 1) {\n    pSums.resize(idx + 1);\n  }\n\n  pSums[idx] = 0;\n  pSums[idx] = pView->SumLongLong(pParam.c_str(), false, &leaves);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Aggregate nodes\n//------------------------------------------------------------------------------\nbool\nLongLongAggregator::aggregateNodes(\n  const std::map<std::string, GeoTreeElement*>& nodes, const size_t& idx,\n  bool includeSelf)\n{\n  long long pS = 0;\n\n  for (auto it = nodes.begin(); it != nodes.end(); it++) {\n    size_t i = it->second->mId;\n    pS += pSums[i];\n  }\n\n  if (includeSelf) {\n    pS += pSums[idx];\n  }\n\n  pSums[idx] = pS;\n  return true;\n};\n\n//----------------------------------------------------------------------------\n// Constructor\n//----------------------------------------------------------------------------\nFsSpace::FsSpace(const char* name)\n  : BaseView(common::SharedHashLocator::makeForSpace(name)),\n    mFsBalancer(nullptr), mGroupBalancer(nullptr),\n    mGeoBalancer(nullptr), mGroupDrainer(nullptr)\n{\n  mName = name;\n  mType = \"spaceview\";\n\n  if (mName != eos::common::EOS_SPARE_GROUP) {\n    mFsBalancer.reset(new FsBalancer(name));\n    mGroupBalancer = new GroupBalancer(name);\n    mGeoBalancer = new GeoBalancer(name);\n    mGroupDrainer.reset(new GroupDrainer(name));\n    mFileInspector.reset(new FileInspector(name, gOFS->mQdbContactDetails));\n  }\n\n  if (!gDisableDefaults) {\n    // Disable balancing by default\n    if (GetConfigMember(\"balancer\").empty()) {\n      SetConfigMember(\"balancer\", \"off\");\n    }\n\n    // Set deviation treshold\n    if (GetConfigMember(\"balancer.threshold\").empty()) {\n      SetConfigMember(\"balancer.threshold\", \"20\");\n    }\n\n    // Set balancing rate per balancing stream\n    if (GetConfigMember(\"balancer.node.rate\").empty()) {\n      SetConfigMember(\"balancer.node.rate\", \"25\");\n    }\n\n    // Set parallel balancing streams per node\n    if (GetConfigMember(\"balancer.node.ntx\").empty()) {\n      SetConfigMember(\"balancer.node.ntx\", \"2\");\n    }\n\n    // Set drain rate per drain stream\n    if (GetConfigMember(\"drainer.tx.minrate\").empty()) {\n      SetConfigMember(\"drainer.tx.minrate\", \"25\");\n    }\n\n    // Set the grace period before drain start on opserror to 1 day\n    if (GetConfigMember(\"graceperiod\").empty()) {\n      SetConfigMember(\"graceperiod\", \"86400\");\n    }\n\n    // Set the time for a drain by default to 1 day\n    if (GetConfigMember(\"drainperiod\").empty()) {\n      SetConfigMember(\"drainperiod\", \"86400\");\n    }\n\n    // Set the scan IO rate by default to 100 MB/s\n    if (GetConfigMember(eos::common::SCAN_IO_RATE_NAME).empty()) {\n      SetConfigMember(eos::common::SCAN_IO_RATE_NAME, \"100\");\n    }\n\n    // Set the scan entry interval by default to 1 week\n    if (GetConfigMember(eos::common::SCAN_ENTRY_INTERVAL_NAME).empty()) {\n      SetConfigMember(eos::common::SCAN_ENTRY_INTERVAL_NAME, \"604800\");\n    }\n\n    // Set the rain scan entry interval by default to 4 weeks\n    if (GetConfigMember(eos::common::SCAN_RAIN_ENTRY_INTERVAL_NAME).empty()) {\n      SetConfigMember(eos::common::SCAN_RAIN_ENTRY_INTERVAL_NAME, \"2419200\");\n    }\n\n    // Set the scan disk rerun interval by default to 4 hours\n    if (GetConfigMember(eos::common::SCAN_DISK_INTERVAL_NAME).empty()) {\n      SetConfigMember(eos::common::SCAN_DISK_INTERVAL_NAME, \"14400\");\n    }\n\n    // Set the scan ns rate by default to 50 entries per second\n    if (GetConfigMember(eos::common::SCAN_NS_RATE_NAME).empty()) {\n      SetConfigMember(eos::common::SCAN_NS_RATE_NAME, \"50\");\n    }\n\n    // Set the scan ns rerun interval by default to 3 days\n    if (GetConfigMember(eos::common::SCAN_NS_INTERVAL_NAME).empty()) {\n      SetConfigMember(eos::common::SCAN_NS_INTERVAL_NAME, \"259200\");\n    }\n\n    // Disable quota by default\n    if (GetConfigMember(\"quota\").empty()) {\n      SetConfigMember(\"quota\", \"off\");\n    }\n\n    // Set the group modulo to 0\n    if (GetConfigMember(\"groupmod\").empty()) {\n      SetConfigMember(\"groupmod\", \"0\");\n    }\n\n    // Set the group size to 0\n    if (GetConfigMember(\"groupsize\").empty()) {\n      SetConfigMember(\"groupsize\", \"0\");\n    }\n\n    if (GetConfigMember(\"groupbalancer\").empty()) {\n      SetConfigMember(\"groupbalancer\", \"off\");\n    }\n\n    // Set the groupbalancer max number of scheduled files by default\n    if (GetConfigMember(\"groupbalancer.ntx\").empty()) {\n      SetConfigMember(\"groupbalancer.ntx\", \"10\");\n    }\n\n    // Set the groupbalancer threshold by default\n    if (GetConfigMember(\"groupbalancer.threshold\").empty()) {\n      SetConfigMember(\"groupbalancer.threshold\", \"5\");\n    }\n\n    // Set the groupbalancer min file size by default\n    if (GetConfigMember(\"groupbalancer.min_file_size\").empty()) {\n      SetConfigMember(\"groupbalancer.min_file_size\", \"1G\");\n    }\n\n    // Set the groupbalancer max file size by default\n    if (GetConfigMember(\"groupbalancer.max_file_size\").empty()) {\n      SetConfigMember(\"groupbalancer.max_file_size\", \"16G\");\n    }\n\n    if (GetConfigMember(\"groupbalancer.file_attempts\").empty()) {\n      SetConfigMember(\"groupbalancer.file_attempts\", \"50\");\n    }\n\n    // Set the default group balancer engine\n    if (GetConfigMember(\"groupbalancer.engine\").empty()) {\n      SetConfigMember(\"groupbalancer.engine\", \"std\");\n    }\n\n    if (GetConfigMember(\"groupbalancer.min_threshold\").empty()) {\n      SetConfigMember(\"groupbalancer.min_threshold\", \"0\");\n    }\n\n    if (GetConfigMember(\"groupbalancer.max_threshold\").empty()) {\n      SetConfigMember(\"groupbalancer.max_threshold\", \"0\");\n    }\n\n    if (GetConfigMember(\"geobalancer\").empty()) {\n      SetConfigMember(\"geobalancer\", \"off\");\n    }\n\n    // Set the geobalancer max number of scheduled files by default\n    if (GetConfigMember(\"geobalancer.ntx\").empty()) {\n      SetConfigMember(\"geobalancer.ntx\", \"10\");\n    }\n\n    // Set the geobalancer threshold by default\n    if (GetConfigMember(\"geobalancer.threshold\").empty()) {\n      SetConfigMember(\"geobalancer.threshold\", \"5\");\n    }\n\n    // Disable lru by default\n    if (GetConfigMember(\"lru\").empty()) {\n      SetConfigMember(\"lru\", \"off\");\n    }\n\n    // Set one week lru interval by default\n    if (GetConfigMember(\"lru.interval\") == \"604800\") {\n      SetConfigMember(\"lru.interval\", \"604800\");\n    }\n\n    // Set the wfe off by default\n    if (GetConfigMember(\"wfe\").empty()) {\n      SetConfigMember(\"wfe\", \"off\");\n    }\n\n    // Set the wfe interval by default\n    if (GetConfigMember(\"wfe.interval\").empty()) {\n      SetConfigMember(\"wfe.interval\", \"10\");\n    }\n\n    // Set the wfe ntx by default\n    if (GetConfigMember(\"wfe.ntx\").empty()) {\n      SetConfigMember(\"wfe.ntx\", \"1\");\n    }\n\n    // Disable the 'file archived' garbage collector by default\n    if (GetConfigMember(\"filearchivedgc\").empty()) {\n      SetConfigMember(\"filearchivedgc\", \"off\");\n    }\n\n    // Set the default delay in seconds between queries from the tape-aware GC\n    if (GetConfigMember(tgc::TGC_NAME_QRY_PERIOD_SECS).empty()) {\n      SetConfigMember(tgc::TGC_NAME_QRY_PERIOD_SECS,\n                      std::to_string(tgc::TGC_DEFAULT_QRY_PERIOD_SECS));\n    }\n\n    // Set the default number of available bytes the garbage collector is targetting\n    if (GetConfigMember(tgc::TGC_NAME_AVAIL_BYTES).empty()) {\n      SetConfigMember(tgc::TGC_NAME_AVAIL_BYTES,\n                      std::to_string(tgc::TGC_DEFAULT_AVAIL_BYTES));\n    }\n\n    // Set the default of the script used to determine the number of free bytes in a given EOS space\n    if (GetConfigMember(tgc::TGC_NAME_FREE_BYTES_SCRIPT).empty()) {\n      SetConfigMember(tgc::TGC_NAME_FREE_BYTES_SCRIPT,\n                      tgc::TGC_DEFAULT_FREE_BYTES_SCRIPT);\n    }\n\n    // Set the default total number of bytes that must be available before\n    // garbage collection can start\n    if (GetConfigMember(tgc::TGC_NAME_TOTAL_BYTES).empty()) {\n      SetConfigMember(tgc::TGC_NAME_TOTAL_BYTES,\n                      std::to_string(tgc::TGC_DEFAULT_TOTAL_BYTES));\n    }\n\n    //Switch off the tape REST API by default\n    if (GetConfigMember(rest::TAPE_REST_API_SWITCH_ON_OFF).empty()) {\n      SetConfigMember(rest::TAPE_REST_API_SWITCH_ON_OFF,\n                      \"off\");\n    }\n\n    //Switch off the tape REST API STAGE resource by default\n    if (GetConfigMember(rest::TAPE_REST_API_STAGE_SWITCH_ON_OFF).empty()) {\n      SetConfigMember(rest::TAPE_REST_API_STAGE_SWITCH_ON_OFF,\n                      \"off\");\n    }\n  }\n\n  if (mName == std::string(\"default\")) {\n    // Disable tracker by default\n    if (GetConfigMember(\"tracker\").empty()) {\n      SetConfigMember(\"tracker\", \"off\");\n    }\n  }\n}\n\n//----------------------------------------------------------------------------\n// Destructor\n//----------------------------------------------------------------------------\nFsSpace::~FsSpace()\n{\n  if (mGroupBalancer) {\n    delete mGroupBalancer;\n  }\n\n  if (mGeoBalancer) {\n    delete mGeoBalancer;\n  }\n\n  mGroupBalancer = nullptr;\n  mGeoBalancer = nullptr;\n}\n\n//----------------------------------------------------------------------------\n// Stop function stopping threads before destruction\n//----------------------------------------------------------------------------\nvoid FsSpace::Stop()\n{\n  if (mGroupBalancer) {\n    mGroupBalancer->Stop();\n  }\n\n  if (mGeoBalancer) {\n    mGeoBalancer->Stop();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check if quota is enabled for space\n//-----------------------------------------------------------------------------\nbool FsView::IsQuotaEnabled(const std::string& space)\n{\n  bool is_enabled = false;\n  std::string key = \"quota\";\n\n  if (mSpaceView.count(space)) {\n    std::string is_on = mSpaceView[space]->GetConfigMember(key);\n    is_enabled = (is_on == \"on\");\n  }\n\n  return is_enabled;\n}\n\n\nstd::string FsView::Df(bool monitoring, bool si, bool readable,\n                       std::string dfpath, bool json)\n{\n  std::string nominal;\n  std::string network;\n  double networkmib = 0;\n  size_t i_nominal = 0;\n  size_t i_used = 0;\n  double sizefactor = 1.0;\n  long long int files = 0;\n  long long int directories = 0;\n  int use = 0;\n  std::string instance = gOFS->MgmOfsInstanceName.c_str();\n  std::string instancepath = gOFS->MgmProcPath.c_str();\n  instancepath.erase(instancepath.length() - 5); // remove /proc\n  std::string path = dfpath.empty() ? instancepath : dfpath;\n  std::string out;\n  {\n    eos::common::RWMutexReadLock viewlock(ViewMutex);\n\n    for (auto it = mSpaceView.begin(); it != mSpaceView.end(); ++it) {\n      if (it->first == \"spare\") {\n        // don't account spare\n        continue;\n      }\n\n      nominal = it->second->GetMember(\"cfg.nominalsize\");\n\n      if (nominal.length()) {\n        i_nominal += std::strtoll(nominal.c_str(), 0, 10);\n      }\n    }\n\n    for (auto it = mNodeView.begin(); it != mNodeView.end(); ++it) {\n      network = it->second->GetMember(\"cfg.stat.net.ethratemib\");\n\n      if (network.length()) {\n        networkmib += std::strtoll(network.c_str(), 0, 10);\n      }\n    }\n  }\n  {\n    eos::MDLocking::ContainerReadLockPtr cmdLock;\n    std::shared_ptr<eos::IContainerMD> cmd;\n    // Prefetch path\n    eos::Prefetcher::prefetchItemAndWait(gOFS->eosView, path, false);\n\n    try {\n      cmd = gOFS->eosView->getContainer(path, false);\n      cmdLock = eos::MDLocking::readLock(cmd.get());\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      eos_err(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\", e.getErrno(),\n              e.getMessage().str().c_str());\n      cmd = nullptr;\n    }\n\n    if (!cmd) {\n      // fall back to instance path\n      try {\n        path = instancepath;\n        cmd = gOFS->eosView->getContainer(path, false);\n        cmdLock = eos::MDLocking::readLock(cmd.get());\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        eos_err(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\", e.getErrno(),\n                e.getMessage().str().c_str());\n        return \"\";\n      }\n    }\n\n    i_used = cmd->getTreeSize();\n    sizefactor = Policy::GetDefaultSizeFactor(cmd);\n    files = gOFS->eosFileService->getNumFiles();\n    directories = gOFS->eosDirectoryService->getNumContainers();\n  } // Release container lock\n\n  if (sizefactor) {\n    i_nominal /= sizefactor;\n  }\n\n  std::string size = readable ?\n                     eos::common::StringConversion::GetReadableSizeString(i_nominal, si ? \"iB\" : \"B\",\n                         si ? 1024 : 1000) :\n                     std::to_string(i_nominal);\n  std::string used = readable ?\n                     eos::common::StringConversion::GetReadableSizeString(i_used, si ? \"iB\" : \"B\",\n                         si ? 1024 : 1000) :\n                     std::to_string(i_used);\n  use = (int)(100.0 * i_used / i_nominal);\n\n  if (use > 100) {\n    use = 100;\n  }\n\n  std::string suse = std::to_string(use) + (monitoring ? std::string(\"\") :\n                     std::string(\"%\"));\n  std::string sfiles = readable ?\n                       eos::common::StringConversion::GetReadableSizeString(files, \"\", 1000) :\n                       std::to_string(files);\n  std::string sdirectories = readable ?\n                             eos::common::StringConversion::GetReadableSizeString(directories, \"\", 1000) :\n                             std::to_string(directories);\n  char _perfratio[1024];\n  double pcr = networkmib / (si ? 1024.0 : 1000.0) / (i_nominal / (si ?\n               (1024 * 1024 * 1024 * 1024.0) : (1000 * 1000 * 1000 * 1000.0))); // GB/s per TB\n\n  if (i_nominal) {\n    snprintf(_perfratio, sizeof(_perfratio), \"%.02f\", pcr); // GB/s per TB\n  } else {\n    snprintf(_perfratio, sizeof(_perfratio), \"0.00\");\n  }\n\n  std::string sperf = _perfratio;\n  char _sizefactor[1024];\n  snprintf(_sizefactor, sizeof(_sizefactor), \"%.02f\", sizefactor);\n  std::string ssizefactor = _sizefactor;\n  TableFormatterBase table;\n  TableData table_data;\n  std::string format_s = (!monitoring ? \"s\" : \"os\");\n  std::string format_ss = (!monitoring ? \"-s\" : \"os\");\n\n  if (json) {\n    Json::Value gjson;\n    gjson[\"df\"][\"instance\"] = instance;\n    gjson[\"df\"][\"size\"] = (Json::Value::UInt64)i_nominal;\n    gjson[\"df\"][\"used\"] = (Json::Value::UInt64)i_used;\n    gjson[\"df\"][\"files\"] = (Json::Value::UInt64) files;\n    gjson[\"df\"][\"directories\"] = (Json::Value::UInt64) directories;\n    gjson[\"df\"][\"performancecapacityratio-gb-tbs\"] = pcr;\n    gjson[\"df\"][\"sizefactor\"] = sizefactor;\n    gjson[\"df\"][\"path\"] = path;\n    out += SSTR(gjson).c_str();\n    return out;\n  } else {\n    if (!monitoring) {\n      table.SetHeader({\n        std::make_tuple(\"Instance\", 14, format_ss),\n        std::make_tuple(\"Size\",  8, format_s),\n        std::make_tuple(\"Used\",  8, format_s),\n        std::make_tuple(\"Files\", 8, format_s),\n        std::make_tuple(\"Directories\", 15, format_s),\n        std::make_tuple(\"PCR GB/TB*s\", 12, format_s),\n        std::make_tuple(\"Use%\", 6, format_s),\n        std::make_tuple(\"Vol-x\", 7, format_s),\n        std::make_tuple(\"Path\",  0, format_s)\n      });\n    } else {\n      table.SetHeader({\n        std::make_tuple(\"instance\", 14, format_ss),\n        std::make_tuple(\"size\",  8, format_s),\n        std::make_tuple(\"used\",  8, format_s),\n        std::make_tuple(\"files\", 8, format_s),\n        std::make_tuple(\"directories\", 15, format_s),\n        std::make_tuple(\"performancecapacityratio\", 12, format_s),\n        std::make_tuple(\"usage\", 6, format_s),\n        std::make_tuple(\"spacefactor\", 6, format_s),\n        std::make_tuple(\"path\",  0, format_s)\n      });\n    }\n\n    table_data.emplace_back();\n    TableRow& row = table_data.back();\n    row.emplace_back(instance, format_ss);\n    row.emplace_back(size, format_s);\n    row.emplace_back(used, format_s);\n    row.emplace_back(sfiles, format_s);\n    row.emplace_back(sdirectories, format_s);\n    row.emplace_back(sperf, format_s);\n    row.emplace_back(suse, format_s);\n    row.emplace_back(ssizefactor, format_s);\n    row.emplace_back(path, format_s);\n    table.AddRows(table_data);\n    out += table.GenerateTable(HEADER).c_str();\n    return out;\n  }\n}\n\n\n//----------------------------------------------------------------------------\n//! Physical bytes available\n//----------------------------------------------------------------------------\nbool FsView::UnderNominalQuota(const std::string& space, bool isroot)\n{\n  if (isroot) {\n    return true;\n  }\n\n  time_t now = time(NULL);\n  {\n    XrdSysMutexHelper scope_lock(mUsageMutex);\n    {\n      // return cached value\n      auto it = mUsageOk.find(space);\n\n      if (it != mUsageOk.end()) {\n        if (it->second.second > now) {\n          return it->second.first;\n        }\n      }\n    }\n    auto spaceobj = mSpaceView.find(space);\n\n    if (spaceobj == mSpaceView.end()) {\n      // no space, we don't block by nominal quota\n      return true;\n    }\n\n    // refresh the nominal value\n    std::string nominal = spaceobj->second->GetMember(\"cfg.nominalsize\");\n\n    if (nominal == \"???\") {\n      // no setting, quota is fine\n      return true;\n    }\n\n    uint64_t nominalbytes = strtoul(nominal.c_str(), 0, 10);\n    uint64_t usedbytes = 0 ;\n\n    for (auto fs = mIdView.begin(); fs != mIdView.end(); ++fs) {\n      if (fs->second->GetSpace() != space) {\n        // only account the requested space\n        continue;\n      }\n\n      usedbytes += fs->second->GetUsedbytes();\n    }\n\n    bool usage_ok = false;\n\n    if (usedbytes < nominalbytes) {\n      usage_ok = true;\n    }\n\n    mUsageOk[space].first = usage_ok;\n    mUsageOk[space].second = now + 120; // cache for 120 seconds\n    return usage_ok;\n  }\n}\n\n//------------------------------------------------------------------------------\n// @brief return's the printout format for a given option\n// @param option see the implementation for valid options\n// @return std::string with format line passed to the printout routine\n//------------------------------------------------------------------------------\nstd::string\nFsView::GetNodeFormat(std::string option)\n{\n  std::string format;\n\n  if (option == \"m\") {\n    // monitoring format\n    format = \"member=type:format=os|\";\n    format += \"member=hostport:format=os|\";\n    format += \"member=status:format=os|\";\n    format += \"member=cfg.status:format=os|\";\n    format += \"member=heartbeatdelta:format=os|\";\n    format += \"member=nofs:format=ol|\";\n    format += \"avg=stat.disk.load:format=of|\";\n    format += \"sig=stat.disk.load:format=of|\";\n    format += \"sum=stat.disk.readratemb:format=ol|\";\n    format += \"sum=stat.disk.writeratemb:format=ol|\";\n    format += \"member=cfg.stat.net.ethratemib:format=ol|\";\n    format += \"member=cfg.stat.net.inratemib:format=ol|\";\n    format += \"member=cfg.stat.net.outratemib:format=ol|\";\n    format += \"sum=stat.ropen:format=ol|\";\n    format += \"sum=stat.wopen:format=ol|\";\n    format += \"sum=stat.statfs.freebytes:format=ol|\";\n    format += \"sum=stat.statfs.usedbytes:format=ol|\";\n    format += \"sum=stat.statfs.capacity:format=ol|\";\n    format += \"sum=stat.usedfiles:format=ol|\";\n    format += \"sum=stat.statfs.ffree:format=ol|\";\n    format += \"sum=stat.statfs.fused:format=ol|\";\n    format += \"sum=stat.statfs.files:format=ol|\";\n    format += \"sum=local.balancer.running:format=ol:tag=local.balancer.running|\";\n    format += \"member=cfg.stat.sys.vsize:format=ol|\";\n    format += \"member=cfg.stat.sys.rss:format=ol|\";\n    format += \"member=cfg.stat.sys.threads:format=ol|\";\n    format += \"member=cfg.stat.sys.sockets:format=os|\";\n    format += \"member=cfg.stat.sys.kworkers:format=ol|\";\n    format += \"member=cfg.stat.sys.eos.version:format=os|\";\n    format += \"member=cfg.stat.sys.xrootd.version:format=os|\";\n    format += \"member=cfg.stat.sys.kernel:format=os|\";\n    format += \"member=cfg.stat.sys.eos.start:format=os|\";\n    format += \"member=cfg.stat.sys.uptime:format=os|\";\n    format += \"sum=stat.disk.iops?configstatus@rw:format=ol|\";\n    format += \"sum=stat.disk.bw?configstatus@rw:format=ol|\";\n    format += \"member=cfg.stat.geotag:format=os|\";\n  } else if (option == \"io\") {\n    // io format\n    format = \"header=1:member=hostport:width=32:format=-sS|\";\n    format += \"member=cfg.stat.geotag:width=16:format=s|\";\n    format += \"avg=stat.disk.load:width=10:format=f:tag=diskload|\";\n    format += \"sum=stat.disk.readratemb:width=12:format=+l:tag=diskr-MB/s|\";\n    format += \"sum=stat.disk.writeratemb:width=12:format=+l:tag=diskw-MB/s|\";\n    format += \"member=cfg.stat.net.ethratemib:width=10:format=l:tag=eth-MiB/s|\";\n    format += \"member=cfg.stat.net.inratemib:width=10:format=l:tag=ethi-MiB|\";\n    format += \"member=cfg.stat.net.outratemib:width=10:format=l:tag=etho-MiB|\";\n    format += \"sum=stat.ropen:width=6:format=l:tag=ropen|\";\n    format += \"sum=stat.wopen:width=6:format=l:tag=wopen|\";\n    format += \"compute=usage:width=8:format=f:unit=%|\";\n    format += \"sum=stat.statfs.usedbytes:width=12:format=+l:unit=B:tag=used-bytes|\";\n    format += \"sum=stat.statfs.capacity:width=12:format=+l:unit=B:tag=max-bytes|\";\n    format += \"sum=stat.usedfiles:width=12:format=+l:tag=used-files|\";\n    format += \"sum=stat.statfs.files:width=11:format=+l:tag=max-files|\";\n    format += \"sum=local.balancer.running:width=10:format=l:tag=bal-shd|\";\n    format += \"sum=stat.disk.iops?configstatus@rw:width=6:format=l:tag=iops|\";\n    format += \"sum=stat.disk.bw?configstatus@rw:width=9:format=l:unit=MB:tag=bw\";\n  } else if (option == \"sys\") {\n    // system format\n    format = \"header=1:member=hostport:width=32:format=-sS|\";\n    format += \"member=cfg.stat.geotag:width=16:format=s|\";\n    format += \"member=cfg.stat.sys.vsize:width=12:format=+l:tag=vsize|\";\n    format += \"member=cfg.stat.sys.rss:width=12:format=+l:tag=rss|\";\n    format += \"member=cfg.stat.sys.threads:width=12:format=+l:tag=threads|\";\n    format += \"member=cfg.stat.sys.sockets:width=10:format=s:tag=sockets|\";\n    format += \"member=cfg.stat.sys.kworkers:format=l:tag=kworkers|\";\n    format += \"member=cfg.stat.sys.eos.version:width=14:format=s:tag=eos|\";\n    format += \"member=cfg.stat.sys.xrootd.version:width=12:format=s:tag=xrootd|\";\n    format += \"member=cfg.stat.sys.kernel:width=30:format=s:tag=kernel version|\";\n    format += \"member=cfg.stat.sys.eos.start:width=32:format=s:tag=start|\";\n    format += \"member=cfg.stat.sys.uptime:width=64:format=s:tag=uptime\";\n  } else if (option == \"fsck\") {\n    // filesystem check statistics format\n    format = \"header=1:member=hostport:width=32:format=-sS|\";\n    format += \"sum=stat.fsck.mem_n:width=8:format=l:tag=n(mem)|\";\n    format += \"sum=stat.fsck.d_sync_n:width=8:format=l:tag=n(disk)|\";\n    format += \"sum=stat.fsck.m_sync_n:width=8:format=l:tag=n(mgm)|\";\n    format += \"sum=stat.fsck.orphans_n:width=12:format=l:tag=e(orph)|\";\n    format += \"sum=stat.fsck.unreg_n:width=12:format=l:tag=e(unreg)|\";\n    format += \"sum=stat.fsck.rep_diff_n:width=12:format=l:tag=e(layout)|\";\n    format += \"sum=stat.fsck.rep_missing_n:width=12:format=l:tag=e(miss)|\";\n    format += \"sum=stat.fsck.d_mem_sz_diff:width=12:format=l:tag=e(disksize)|\";\n    format += \"sum=stat.fsck.m_mem_sz_diff:width=12:format=l:tag=e(mgmsize)|\";\n    format += \"sum=stat.fsck.d_cx_diff:width=12:format=l:tag=e(disk-cx)|\";\n    format += \"sum=stat.fsck.m_cx_diff:width=12:format=l:tag=e(mgm-cx)\";\n  } else if (option == \"l\") {\n    // long format\n    format = \"header=1:member=type:width=10:format=-s|\";\n    format += \"member=hostport:width=32:format=sS|\";\n    format += \"member=cfg.stat.geotag:width=16:format=s|\";\n    format += \"member=status:width=10:format=s|\";\n    format += \"member=cfg.status:width=12:format=s:tag=activated|\";\n    format += \"member=heartbeatdelta:width=16:format=s|\";\n    format += \"member=nofs:width=5:format=s|\";\n    format += \"sum=local.balancer.running:width=10:format=l:tag=bal-shd|\";\n  } else {\n    // default format\n    format = \"header=1:member=type:width=10:format=-s|\";\n    format += \"member=hostport:width=32:format=sS|\";\n    format += \"member=cfg.stat.geotag:width=16:format=s|\";\n    format += \"member=status:width=10:format=s|\";\n    format += \"member=cfg.status:width=12:format=s:tag=activated|\";\n    format += \"member=heartbeatdelta:width=16:format=s|\";\n    format += \"member=nofs:width=5:format=s\";\n  }\n\n  return format;\n}\n//------------------------------------------------------------------------------\n// @brief return's the printout format for a given option\n// @param option see the implementation for valid options\n// @return std;:string with format line passed to the printout routine\n//------------------------------------------------------------------------------\nstd::string\nFsView::GetFileSystemFormat(std::string option)\n{\n  std::string format;\n\n  if (option == \"m\") {\n    // monitoring format\n    format = \"key=host:format=os|\";\n    format += \"key=port:format=os|\";\n    format += \"key=id:format=os|\";\n    format += \"key=uuid:format=os|\";\n    format += \"key=path:format=os|\";\n    format += \"key=schedgroup:format=os|\";\n    format += \"key=stat.alias.host:format=oqs|\";\n    format += \"key=stat.alias.port:format=oqs|\";\n    format += \"key=stat.boot:format=os|\";\n    format += \"key=configstatus:format=os|\";\n    format += \"key=headroom:format=os|\";\n    format += \"key=stat.errc:format=os|\";\n    format += \"key=stat.errmsg:format=oqs|\";\n    format += \"key=stat.disk.load:format=of|\";\n    format += \"key=stat.disk.readratemb:format=ol|\";\n    format += \"key=stat.disk.writeratemb:format=ol|\";\n    format += \"key=stat.net.ethratemib:format=ol|\";\n    format += \"key=stat.net.inratemib:format=ol|\";\n    format += \"key=stat.net.outratemib:format=ol|\";\n    format += \"key=stat.ropen:format=ol|\";\n    format += \"key=stat.wopen:format=ol|\";\n    format += \"key=stat.statfs.freebytes:format=ol|\";\n    format += \"key=stat.statfs.usedbytes:format=ol|\";\n    format += \"key=stat.statfs.capacity:format=ol|\";\n    format += \"key=stat.usedfiles:format=ol|\";\n    format += \"key=stat.statfs.ffree:format=ol|\";\n    format += \"key=stat.statfs.fused:format=ol|\";\n    format += \"key=stat.statfs.files:format=ol|\";\n    format += \"key=local.drain:format=os|\";\n    format += \"key=local.drain.progress:format=ol:tag=progress|\";\n    format += \"key=local.drain.files:format=ol|\";\n    format += \"key=local.drain.bytesleft:format=ol|\";\n    format += \"key=local.drain.failed:format=ol|\";\n    format += \"key=local.drain.timeleft:format=ol|\";\n    format += \"key=graceperiod:format=ol|\";\n    format += \"key=drainperiod:format=ol|\";\n    format += \"key=stat.active:format=os|\";\n    format += \"key=scaninterval:format=os|\";\n    format += \"key=scan_rain_interval:format=os|\";\n    format += \"key=scanreruninterval:format=os|\";\n    format += \"key=local.balancer.running:format=ol:tag=local.balancer.running|\";\n    format += \"key=stat.disk.iops:format=ol|\";\n    format += \"key=stat.disk.bw:format=of|\";\n    format += \"key=stat.geotag:format=os|\";\n    format += \"key=stat.health:format=os|\";\n    format += \"key=stat.health.redundancy_factor:format=os|\";\n    format += \"key=stat.health.drives_failed:format=os|\";\n    format += \"key=stat.health.drives_total:format=os|\";\n    format += \"key=stat.health.indicator:format=os\";\n  } else if (option == \"io\") {\n    // io format\n    format = \"header=1:key=hostport:width=32:format=-s|\";\n    format += \"key=id:width=6:format=s|\";\n    format += \"key=schedgroup:width=16:format=s|\";\n    format += \"key=stat.geotag:width=16:format=s|\";\n    format += \"key=stat.disk.load:width=10:format=f:tag=diskload|\";\n    format += \"key=stat.disk.readratemb:width=12:format=f:tag=diskr-MB/s|\";\n    format += \"key=stat.disk.writeratemb:width=12:format=f:tag=diskw-MB/s|\";\n    format += \"key=stat.net.ethratemib:width=10:format=l:tag=eth-MiB/s|\";\n    format += \"key=stat.net.inratemib:width=10:format=l:tag=ethi-MiB|\";\n    format += \"key=stat.net.outratemib:width=10:format=l:tag=etho-MiB|\";\n    format += \"key=stat.ropen:width=6:format=l:tag=ropen|\";\n    format += \"key=stat.wopen:width=6:format=l:tag=wopen|\";\n    format += \"compute=usage:width=8:format=f:unit=%|\";\n    format += \"key=stat.statfs.usedbytes:width=12:format=+l:unit=B:tag=used-bytes|\";\n    format += \"key=stat.statfs.capacity:width=12:format=+l:unit=B:tag=max-bytes|\";\n    format += \"key=stat.usedfiles:width=12:format=+l:tag=used-files|\";\n    format += \"key=stat.statfs.files:width=11:format=+l:tag=max-files|\";\n    format += \"key=local.balancer.running:width=10:format=l:tag=bal-shd|\";\n    format += \"key=stat.disk.iops:width=6:format=l:tag=iops|\";\n    format += \"key=stat.disk.bw:width=9:format=l:unit=MB:tag=bw\";\n  } else if (option == \"fsck\") {\n    // filesystem check statistics format\n    format = \"header=1:key=hostport:width=32:format=-s|\";\n    format += \"key=id:width=6:format=s|\";\n    format += \"key=stat.fsck.mem_n:width=8:format=l:tag=n(mem)|\";\n    format += \"key=stat.fsck.d_sync_n:width=8:format=l:tag=n(disk)|\";\n    format += \"key=stat.fsck.m_sync_n:width=8:format=l:tag=n(mgm)|\";\n    format += \"key=stat.fsck.orphans_n:width=12:format=l:tag=e(orph)|\";\n    format += \"key=stat.fsck.unreg_n:width=12:format=l:tag=e(unreg)|\";\n    format += \"key=stat.fsck.rep_diff_n:width=12:format=l:tag=e(layout)|\";\n    format += \"key=stat.fsck.rep_missing_n:width=12:format=l:tag=e(miss)|\";\n    format += \"key=stat.fsck.d_mem_sz_diff:width=12:format=l:tag=e(disksize)|\";\n    format += \"key=stat.fsck.m_mem_sz_diff:width=12:format=l:tag=e(mgmsize)|\";\n    format += \"key=stat.fsck.d_cx_diff:width=12:format=l:tag=e(disk-cx)|\";\n    format += \"key=stat.fsck.m_cx_diff:width=12:format=l:tag=e(mgm-cx)\";\n  } else if (option == \"d\") {\n    // drain format\n    format = \"header=1:key=host:width=24:format=-S:condition=local.drain=!nodrain|\";\n    format += \"key=port:width=4:format=s|\";\n    format += \"key=id:width=6:format=s|\";\n    format += \"key=path:width=32:format=s|\";\n    format += \"key=local.drain:width=12:format=s|\";\n    format += \"key=local.drain.progress:width=12:format=l:tag=progress|\";\n    format += \"key=local.drain.files:width=12:format=+l:tag=files|\";\n    format += \"key=local.drain.bytesleft:width=12:format=+l:tag=bytes-left:unit=B|\";\n    format += \"key=local.drain.timeleft:width=11:format=l:tag=timeleft|\";\n    format += \"key=local.drain.failed:width=12:format=+l:tag=failed\";\n  } else if (option == \"l\") {\n    // long format\n    format = \"header=1:key=host:width=24:format=-S|\";\n    format += \"key=port:width=4:format=s|\";\n    format += \"key=stat.alias.host:width=24:format=-s|\";\n    format += \"key=stat.alias.port:width=4:format=s|\";\n    format += \"key=id:width=6:format=s|\";\n    format += \"key=uuid:width=36:format=s|\";\n    format += \"key=path:width=32:format=s|\";\n    format += \"key=schedgroup:width=16:format=s|\";\n    format += \"key=headroom:width=10:format=+f|\";\n    format += \"key=stat.boot:width=12:format=s|\";\n    format += \"key=configstatus:width=14:format=s|\";\n    format += \"key=local.drain:width=12:format=s|\";\n    format += \"compute=usage:width=6:format=f|\";\n    format += \"key=stat.active:width=8:format=s|\";\n    format += \"key=scaninterval:width=14:format=s|\";\n    format += \"key=scan_rain_interval:width=20:format=s|\";\n    format += \"key=stat.health:width=16:format=s|\";\n    format += \"key=statuscomment:width=24:format=s\";\n  } else if (option == \"e\") {\n    // error format\n    format = \"header=1:key=host:width=24:format=-S:condition=stat.errc=!0|\";\n    format += \"key=id:width=6:format=s|\";\n    format += \"key=path:width=32:format=s|\";\n    format += \"key=stat.boot:width=12:format=s|\";\n    format += \"key=configstatus:width=14:format=s|\";\n    format += \"key=local.drain:width=12:format=s|\";\n    format += \"key=stat.errc:width=3:format=s|\";\n    format += \"key=stat.errmsg:width=0:format=s\";\n  } else {\n    // default format\n    format = \"header=1:key=host:width=24:format=-S|\";\n    format += \"key=port:width=4:format=s|\";\n    format += \"key=id:width=6:format=s|\";\n    format += \"key=path:width=32:format=s|\";\n    format += \"key=schedgroup:width=16:format=s|\";\n    format += \"key=stat.geotag:width=16:format=s|\";\n    format += \"key=stat.boot:width=12:format=s|\";\n    format += \"key=configstatus:width=14:format=s|\";\n    format += \"key=local.drain:width=12:format=s|\";\n    format += \"compute=usage:width=6:format=f|\";\n    format += \"key=stat.active:width=8:format=s|\";\n    format += \"key=stat.health:width=16:format=s\";\n  }\n\n  return format;\n}\n//------------------------------------------------------------------------------\n// @brief return's the printout format for a given option\n// @param option see the implementation for valid options\n// @return std;:string with format line passed to the printout routine\n//------------------------------------------------------------------------------\nstd::string\nFsView::GetSpaceFormat(std::string option)\n{\n  std::string format;\n\n  if (option == \"m\") {\n    // monitoring format\n    format = \"member=type:format=os|\";\n    format += \"member=name:format=os|\";\n    format += \"member=cfg.groupsize:format=ol|\";\n    format += \"member=cfg.groupmod:format=ol|\";\n    format += \"member=nofs:format=ol|\";\n    format += \"avg=stat.disk.load:format=of|\";\n    format += \"sig=stat.disk.load:format=of|\";\n    format += \"sum=stat.disk.readratemb:format=ol|\";\n    format += \"sum=stat.disk.writeratemb:format=ol|\";\n    format += \"sum=stat.net.ethratemib:format=ol|\";\n    format += \"sum=stat.net.inratemib:format=ol|\";\n    format += \"sum=stat.net.outratemib:format=ol|\";\n    format += \"sum=stat.ropen:format=ol|\";\n    format += \"sum=stat.wopen:format=ol|\";\n    format += \"sum=stat.statfs.usedbytes:format=ol|\";\n    format += \"sum=stat.statfs.freebytes:format=ol|\";\n    format += \"sum=stat.statfs.freebytes?configstatus@rw:format=ol|\";\n    format += \"sum=stat.statfs.capacity:format=ol|\";\n    format += \"sum=stat.usedfiles:format=ol|\";\n    format += \"sum=stat.statfs.ffiles:format=ol|\";\n    format += \"sum=stat.statfs.files:format=ol|\";\n    format += \"geosched=totalspace:format=ol:tag=sched.capacity|\";\n    format += \"sum=stat.statfs.capacity?configstatus@rw:format=ol|\";\n    format += \"sum=<n>?configstatus@rw:format=ol|\";\n    format += \"member=cfg.quota:format=os|\";\n    format += \"member=cfg.nominalsize:format=ol|\";\n    format += \"member=cfg.balancer:format=os|\";\n    format += \"member=cfg.balancer.threshold:format=ol|\";\n    format += \"sum=local.balancer.running:format=ol:tag=local.balancer.running|\";\n    format += \"sum=stat.disk.iops?configstatus@rw:format=ol|\";\n    format += \"sum=stat.disk.bw?configstatus@rw:format=ol\";\n  } else if (option == \"io\") {\n    // io format\n    format = \"header=1:member=name:width=10:format=-s|\";\n    format += \"avg=stat.geotag:width=32:format=-s|\";\n    format += \"avg=stat.disk.load:width=10:format=f:tag=diskload|\";\n    format += \"sum=stat.disk.readratemb:width=12:format=+l:tag=diskr-MB/s|\";\n    format += \"sum=stat.disk.writeratemb:width=12:format=+l:tag=diskw-MB/s|\";\n    format += \"sum=stat.net.ethratemib:width=10:format=l:tag=eth-MiB/s|\";\n    format += \"sum=stat.net.inratemib:width=10:format=l:tag=ethi-MiB|\";\n    format += \"sum=stat.net.outratemib:width=10:format=l:tag=etho-MiB|\";\n    format += \"sum=stat.ropen:width=6:format=l:tag=ropen|\";\n    format += \"sum=stat.wopen:width=6:format=l:tag=wopen|\";\n    format += \"compute=usage:width=8:format=f:unit=%|\";\n    format += \"sum=stat.statfs.usedbytes:width=12:format=+l:unit=B:tag=used-bytes|\";\n    format += \"member=cfg.nominalsize:width=13:format=+l:tag=nom.capacity:unit=B|\";\n    format += \"sum=stat.statfs.capacity:width=12:format=+l:unit=B:tag=max-bytes|\";\n    format += \"sum=stat.usedfiles:width=12:format=+l:tag=used-files|\";\n    format += \"sum=stat.statfs.files:width=11:format=+l:tag=max-files|\";\n    format += \"sum=local.balancer.running:width=10:format=l:tag=bal-shd\";\n  } else if (option == \"fsck\") {\n    // filesystem check statistics format\n    format = \"header=1:member=name:width=10:format=-s|\";\n    format += \"avg=stat.geotag:width=32:format=-s|\";\n    format += \"sum=stat.fsck.mem_n:width=8:format=l:tag=n(mem)|\";\n    format += \"sum=stat.fsck.d_sync_n:width=8:format=l:tag=n(disk)|\";\n    format += \"sum=stat.fsck.m_sync_n:width=8:format=l:tag=n(mgm)|\";\n    format += \"sum=stat.fsck.orphans_n:width=12:format=l:tag=e(orph)|\";\n    format += \"sum=stat.fsck.unreg_n:width=12:format=l:tag=e(unreg)|\";\n    format += \"sum=stat.fsck.rep_diff_n:width=12:format=l:tag=e(layout)|\";\n    format += \"sum=stat.fsck.rep_missing_n:width=12:format=l:tag=e(miss)|\";\n    format += \"sum=stat.fsck.d_mem_sz_diff:width=12:format=l:tag=e(disksize)|\";\n    format += \"sum=stat.fsck.m_mem_sz_diff:width=12:format=l:tag=e(mgmsize)|\";\n    format += \"sum=stat.fsck.d_cx_diff:width=12:format=l:tag=e(disk-cx)|\";\n    format += \"sum=stat.fsck.m_cx_diff:width=12:format=l:tag=e(mgm-cx)\";\n  } else if (option == \"l\") {\n    // long output format\n    format = \"header=1:member=type:width=10:format=-s|\";\n    format += \"member=name:width=16:format=s|\";\n    format += \"avg=stat.geotag:width=32:format=-s|\";\n    format += \"member=cfg.groupsize:width=12:format=s|\";\n    format += \"member=cfg.groupmod:width=12:format=s|\";\n    format += \"sum=<n>?*@*:width=6:format=l:tag=N(fs)|\";\n    format += \"sum=<n>?configstatus@rw:width=9:format=l:tag=N(fs-rw)|\";\n    format += \"sum=stat.statfs.usedbytes:width=15:format=+l:unit=B|\";\n    format += \"sum=stat.statfs.capacity:width=14:format=+l:unit=B|\";\n    format += \"sum=stat.statfs.capacity?configstatus@rw:width=13:format=+l:tag=capacity(rw):unit=B|\";\n    format += \"member=cfg.nominalsize:width=13:format=+l:tag=nom.capacity:unit=B|\";\n    format += \"member=cfg.quota:width=6:format=s\";\n  } else {\n    // default format\n    format = \"header=1:member=type:width=10:format=-s|\";\n    format += \"member=name:width=16:format=s|\";\n    format += \"avg=stat.geotag:width=32:format=-s|\";\n    format += \"member=cfg.groupsize:width=12:format=s|\";\n    format += \"member=cfg.groupmod:width=12:format=s|\";\n    format += \"member=nofs:width=6:format=s:tag=N(fs)|\";\n    format += \"sum=<n>?configstatus@rw:width=9:format=l:tag=N(fs-rw)|\";\n    format += \"sum=stat.statfs.usedbytes:width=15:format=+l:unit=B|\";\n    format += \"sum=stat.statfs.capacity:width=14:format=+l:unit=B|\";\n    format += \"sum=stat.statfs.capacity?configstatus@rw:width=13:format=+l:tag=capacity(rw):unit=B|\";\n    format += \"member=cfg.nominalsize:width=13:format=+l:tag=nom.capacity:unit=B|\";\n    format += \"geosched=totalspace:width=14:format=+l:tag=sched.capacity:unit=B|\";\n    format += \"compute=usage:width=6:format=f:tag=usage|\";\n    format += \"member=cfg.quota:width=6:format=s|\";\n    format += \"member=cfg.balancer:width=10:format=s:tag=balancing|\";\n    format += \"member=cfg.balancer.threshold:width=11:format=+l:tag=threshold|\";\n    format += \"member=cfg.wfe:width=11:format=s:tag=wfe|\";\n    format += \"member=cfg.wfe.ntx:width=6:format=+l:tag=ntx|\";\n    format += \"member=cfg.stat.wfe.active:width=8:format=+l:tag=active|\";\n    format += \"member=cfg.groupbalancer:width=11:format=s:tag=intergroup\";\n  }\n\n  return format;\n}\n//------------------------------------------------------------------------------\n// @brief return's the printout format for a given option\n// @param option see the implementation for valid options\n// @return std;:string with format line passed to the printout routine\n//------------------------------------------------------------------------------\nstd::string\nFsView::GetGroupFormat(std::string option)\n{\n  std::string format;\n\n  if (option == \"m\") {\n    // monitoring format\n    format = \"member=type:format=os|\";\n    format += \"member=cfg.status:format=os|\";\n    format += \"member=name:format=os|\";\n    format += \"member=nofs:format=os|\";\n    format += \"avg=stat.disk.load:format=of|\";\n    format += \"sig=stat.disk.load:format=of|\";\n    format += \"sum=stat.disk.readratemb:format=ol|\";\n    format += \"sum=stat.disk.writeratemb:format=ol|\";\n    format += \"sum=stat.net.ethratemib:format=ol|\";\n    format += \"sum=stat.net.inratemib:format=ol|\";\n    format += \"sum=stat.net.outratemib:format=ol|\";\n    format += \"sum=stat.ropen:format=ol|\";\n    format += \"sum=stat.wopen:format=ol|\";\n    format += \"sum=stat.statfs.usedbytes:format=ol|\";\n    format += \"sum=stat.statfs.freebytes:format=ol|\";\n    format += \"sum=stat.statfs.capacity:format=ol|\";\n    format += \"sum=stat.usedfiles:format=ol|\";\n    format += \"sum=stat.statfs.ffree:format=ol|\";\n    format += \"sum=stat.statfs.files:format=ol|\";\n    format += \"maxdev=stat.statfs.filled:format=of|\";\n    format += \"avg=stat.statfs.filled:format=of|\";\n    format += \"sig=stat.statfs.filled:format=of|\";\n    format += \"sum=local.balancer.running:format=ol:tag=local.balancer.running|\";\n  } else if (option == \"io\") {\n    // io format\n    format = \"header=1:member=name:width=16:format=-s|\";\n    format += \"avg=stat.geotag:width=32:format=s|\";\n    format += \"avg=stat.disk.load:width=10:format=f:tag=diskload|\";\n    format += \"sum=stat.disk.readratemb:width=12:format=+l:tag=diskr-MB/s|\";\n    format += \"sum=stat.disk.writeratemb:width=12:format=+l:tag=diskw-MB/s|\";\n    format += \"sum=stat.net.ethratemib:width=10:format=l:tag=eth-MiB/s|\";\n    format += \"sum=stat.net.inratemib:width=10:format=l:tag=ethi-MiB|\";\n    format += \"sum=stat.net.outratemib:width=10:format=l:tag=etho-MiB|\";\n    format += \"sum=stat.ropen:width=6:format=l:tag=ropen|\";\n    format += \"sum=stat.wopen:width=6:format=l:tag=wopen|\";\n    format += \"compute=usage:width=8:format=f:unit=%|\";\n    format += \"sum=stat.statfs.usedbytes:width=12:format=+l:unit=B:tag=used-bytes|\";\n    format += \"sum=stat.statfs.capacity:width=12:format=+l:unit=B:tag=max-bytes|\";\n    format += \"sum=stat.usedfiles:width=12:format=+l:tag=used-files|\";\n    format += \"sum=stat.statfs.files:width=11:format=+l:tag=max-files|\";\n    format += \"sum=local.balancer.running:width=10:format=l:tag=bal-shd|\";\n  } else if (option == \"l\") {\n    // long format\n    format = \"header=1:member=type:width=10:format=-s|\";\n    format += \"member=name:width=16:format=s|\";\n    format += \"member=cfg.status:width=12:format=s|\";\n    format += \"avg=stat.geotag:width=32:format=s|\";\n    format += \"key=stat.geotag:width=16:format=s|\";\n    format += \"sum=<n>?*@*:width=6:format=l:tag=N(fs)\";\n  } else {\n    // default format\n    format = \"header=1:member=type:width=10:format=-s|\";\n    format += \"member=name:width=16:format=s|\";\n    format += \"member=cfg.status:width=12:format=s|\";\n    format += \"avg=stat.geotag:width=32:format=s|\";\n    format += \"sum=<n>?*@*:width=6:format=l:tag=N(fs)|\";\n    format += \"maxdev=stat.statfs.filled:width=12:format=f|\";\n    format += \"avg=stat.statfs.filled:width=12:format=f|\";\n    format += \"sig=stat.statfs.filled:width=12:format=f|\";\n    format += \"sum=local.balancer.running:width=10:format=l:tag=bal-shd|\";\n  }\n\n  return format;\n}\n//------------------------------------------------------------------------------\n// Register a filesystem object in the filesystem view\n//------------------------------------------------------------------------------\nbool\nFsView::Register(FileSystem* fs,\n                 const common::FileSystemCoreParams& coreParams,\n                 bool registerInGeoTreeEngine)\n{\n  if (!fs) {\n    return false;\n  }\n\n  // Check for queuepath collision\n  if (mIdView.lookupByQueuePath(coreParams.getQueuePath())) {\n    eos_err(\"msg=\\\"queuepath already registered\\\" qpath=\\\"%s\\\"\",\n            coreParams.getQueuePath().c_str());\n    return false;\n  }\n\n  // Check if this is already in the view\n  if (mIdView.lookupByPtr(fs) != 0) {\n    // This filesystem is already there, this might be an update\n    eos::common::FileSystem::fsid_t fsid = mIdView.lookupByPtr(fs);\n\n    if (fsid != coreParams.getId()) {\n      // Remove previous mapping\n      mIdView.eraseById(fsid);\n      // Setup new two way mapping\n      mIdView.registerFileSystem(coreParams.getLocator(), coreParams.getId(), fs);\n      eos_debug(\"msg=\\\"updating mapping\\\" fsid=%lu fs_ptr=%x\",\n                coreParams.getId(), fs);\n    }\n  } else {\n    mIdView.registerFileSystem(coreParams.getLocator(), coreParams.getId(), fs);\n    eos_debug(\"msg=\\\"registering mapping\\\" fsid=%lu fs_ptr=%x\",\n              coreParams.getId(), fs);\n  }\n\n  // Align view by nodename (= MQ queue) e.g. /eos/<host>:<port>/fst\n  // Check if we have already a node view\n  if (mNodeView.count(coreParams.getFSTQueue())) {\n    mNodeView[coreParams.getFSTQueue()]->insert(coreParams.getId());\n    eos_debug(\"msg=\\\"inserting into node view\\\" fst_queue=\\\"%s\\\" fsid=%lu\",\n              coreParams.getFSTQueue().c_str(), coreParams.getId());\n  } else {\n    FsNode* node = new FsNode(coreParams.getFSTQueue().c_str());\n    mNodeView[coreParams.getFSTQueue()] = node;\n    node->insert(coreParams.getId());\n    node->SetNodeConfigDefault();\n    eos_debug(\"msg=\\\"creating new node and inserting\\\" fst_queue=\\\"%s\\\" \"\n              \"fsid=%lu\", coreParams.getFSTQueue().c_str(),\n              coreParams.getId());\n  }\n\n  // Align view by groupname\n  if (mGroupView.count(coreParams.getGroup())) {\n    mGroupView[coreParams.getGroup()]->insert(coreParams.getId());\n    eos_debug(\"msg=\\\"inserting into group view\\\" group=%s fsid=%lu\",\n              coreParams.getGroup().c_str(), coreParams.getId());\n  } else {\n    FsGroup* group = new FsGroup(coreParams.getGroup().c_str());\n    mGroupView[coreParams.getGroup()] = group;\n    group->insert(coreParams.getId());\n    group->mIndex = coreParams.getGroupLocator().getIndex();\n    eos_debug(\"msg=\\\"creating and inserting into group view\\\" group=%s \"\n              \"fsid=%lu\", coreParams.getGroup().c_str(), coreParams.getId());\n  }\n\n  if (registerInGeoTreeEngine &&\n      !gOFS->mGeoTreeEngine->insertFsIntoGroup(fs, mGroupView[coreParams.getGroup()],\n          coreParams)) {\n    // Roll back the changes\n    if (UnRegister(fs, false)) {\n      eos_err(\"msg=\\\"could not insert insert fs %u into GeoTreeEngine : \"\n              \"fs was unregistered and consistency is KEPT between FsView \"\n              \"and GeoTreeEngine\", coreParams.getId());\n    } else {\n      eos_crit(\"msg=\\\"could not insert insert fs %u into GeoTreeEngine: \"\n               \"fs could not be unregistered and consistency is BROKEN \"\n               \"between FsView and GeoTreeEngine\\\"\", coreParams.getId());\n    }\n\n    return false;\n  }\n\n  mSpaceGroupView[coreParams.getSpace()].insert\n  (mGroupView[coreParams.getGroup()]);\n\n  // Align view by spacename\n  if (mSpaceView.count(coreParams.getSpace())) {\n    mSpaceView[coreParams.getSpace()]->insert(coreParams.getId());\n    eos_debug(\"msg=\\\"inserting into space view\\\" space=%s \"\n              \"fsid=%lu fs_ptr=%x\", coreParams.getSpace().c_str(),\n              coreParams.getId(), fs);\n  } else {\n    FsSpace* space = new FsSpace(coreParams.getSpace().c_str());\n    std::string grp_sz = \"0\";\n    std::string grp_mod = \"24\";\n\n    // Special case of spare space with has size 0 and mod 0\n    if (coreParams.getSpace() == eos::common::EOS_SPARE_GROUP) {\n      grp_mod = \"0\";\n    }\n\n    // Set new space default parameters\n    if ((!space->SetConfigMember(std::string(\"groupsize\"), grp_sz, true)) ||\n        (!space->SetConfigMember(std::string(\"groupmod\"), grp_mod, true))) {\n      eos_err(\"msg=\\\"failed setting space default config\\\" space=%s\",\n              coreParams.getSpace().c_str());\n      return false;\n    }\n\n    mSpaceView[coreParams.getSpace()] = space;\n    space->insert(coreParams.getId());\n    eos_debug(\"msg=\\\"creating and inserting into space view\\\" space=%s \"\n              \"fsid=%lu fs_ptr=%x\", coreParams.getSpace().c_str(),\n              coreParams.getId(), fs);\n  }\n\n  fs->applyCoreParams(coreParams);\n  StoreFsConfig(fs);\n  // Trigger a refresh for the FST node which for sure exists\n  mNodeView[coreParams.getFSTQueue()]->SignalRefresh();\n  return true;\n}\n//------------------------------------------------------------------------------\n// Store the filesystem configuration in the configuration engine\n//------------------------------------------------------------------------------\nvoid\nFsView::StoreFsConfig(FileSystem* fs, bool save_config)\n{\n  if (fs) {\n    std::string key, val;\n    fs->CreateConfig(key, val);\n\n    if (gOFS->mMaster->IsMaster() && FsView::gFsView.mConfigEngine &&\n        !key.empty() && !val.empty()) {\n      FsView::gFsView.mConfigEngine->SetConfigValue(\"fs\", key.c_str(), val.c_str(),\n          true, save_config);\n    }\n  }\n}\n//------------------------------------------------------------------------------\n// Move a filesystem in to a target group\n//------------------------------------------------------------------------------\nbool\nFsView::MoveGroup(FileSystem* fs, std::string group_name)\n{\n  if (!fs) {\n    return false;\n  }\n\n  //@todo(esindril) wrap in a nice method as it can be reused\n  uint32_t grp_index = 0;\n  const std::string tgt_group = group_name;\n  std::string tgt_space = group_name;\n\n  if (tgt_space != \"spare\") {\n    int pos = tgt_space.find('.');\n\n    if (pos == std::string::npos) {\n      eos_static_err(\"msg=\\\"unexpected group format\\\" grp=\\\"%s\",\n                     group_name.c_str());\n      return false;\n    }\n\n    tgt_space = tgt_space.substr(0, pos);\n\n    try {\n      grp_index = std::stoul(tgt_group.substr(pos + 1));\n    } catch (...) {}\n  }\n\n  eos_debug(\"msg=\\\"move fs to group\\\" grp=\\\"%s\\\" space=\\\"%s\\\" fs_ptr=%x\",\n            tgt_group.c_str(), tgt_space.c_str(), fs);\n  eos::common::FileSystem::fs_snapshot_t snapshot1;\n  eos::common::FileSystem::fs_snapshot_t snapshot;\n\n  if (fs->SnapShotFileSystem(snapshot1)) {\n    fs->SetString(\"schedgroup\", group_name.c_str());\n    FsGroup* oldgroup = mGroupView.count(snapshot1.mGroup) ?\n                        mGroupView[snapshot1.mGroup] : NULL;\n\n    if (fs->SnapShotFileSystem(snapshot)) {\n      // Remove from the original space\n      if (mSpaceView.count(snapshot1.mSpace)) {\n        FsSpace* space = mSpaceView[snapshot1.mSpace];\n        space->erase(snapshot1.mId);\n\n        if (!space->size() && (space->mName != \"spare\")) {\n          eos_debug(\"msg=\\\"unregister from space view\\\" space=\\\"%s\\\"\",\n                    space->GetMember(\"name\").c_str());\n          mSpaceView.erase(snapshot1.mSpace);\n          delete space;\n        }\n      }\n\n      // Remove from the original group\n      if (mGroupView.count(snapshot1.mGroup)) {\n        FsGroup* group = mGroupView[snapshot1.mGroup];\n\n        if (!gOFS->mGeoTreeEngine->removeFsFromGroup(fs, group, false)) {\n          // roll-back\n          if (mSpaceView.count(snapshot1.mSpace)) {\n            mSpaceView[snapshot1.mSpace]->insert(snapshot1.mId);\n            eos_debug(\"inserting into space view %s<=>%u fs_ptr=%x\",\n                      snapshot1.mSpace.c_str(), snapshot1.mId, fs);\n          } else {\n            FsSpace* space = new FsSpace(snapshot1.mSpace.c_str());\n            mSpaceView[snapshot1.mSpace] = space;\n            space->insert(snapshot1.mId);\n            eos_debug(\"creating/inserting into space view %s<=>%u fs_ptr=%x\",\n                      snapshot1.mSpace.c_str(), snapshot1.mId, fs);\n          }\n\n          eos_err(\"could not remove fs %u from GeoTreeEngine : fs was \"\n                  \"registered back and consistency is KEPT between FsView\"\n                  \" and GeoTreeEngine\", snapshot.mId);\n          return false;\n        }\n\n        group->erase(snapshot1.mId);\n\n        if (!group->size() && (group->mName != \"spare\")) {\n          if (mSpaceGroupView.count(snapshot1.mSpace)) {\n            mSpaceGroupView[snapshot1.mSpace].erase(mGroupView[snapshot1.mGroup]);\n          }\n\n          eos_debug(\"msg=\\\"unregister from group view\\\" group=\\\"%s\\\"\",\n                    group->GetMember(\"name\").c_str());\n          mGroupView.erase(snapshot1.mGroup);\n          delete group;\n        }\n      }\n\n      // Check if we have already a group view\n      if (mGroupView.count(tgt_group)) {\n        mGroupView[tgt_group]->insert(snapshot.mId);\n        eos_debug(\"inserting into group view %s<=>%u fs_ptr=%x\",\n                  tgt_group.c_str(), snapshot.mId, fs);\n      } else {\n        FsGroup* group = new FsGroup(tgt_group.c_str());\n        mGroupView[tgt_group] = group;\n        group->insert(snapshot.mId);\n        group->mIndex = grp_index;\n        group->SetConfigMember(\"status\", \"on\");\n        eos_debug(\"creating/inserting into group view %s<=>%u\",\n                  tgt_group.c_str(), snapshot.mId, fs);\n      }\n\n      if (!gOFS->mGeoTreeEngine->insertFsIntoGroup(fs, mGroupView[group_name],\n          fs->getCoreParams())) {\n        if (fs->SetString(\"schedgroup\", group_name.c_str()) &&\n            UnRegister(fs, false)) {\n          if (oldgroup && fs->SetString(\"schedgroup\", oldgroup->mName.c_str()) &&\n              Register(fs, fs->getCoreParams())) {\n            eos_err(\"while moving fs, could not insert fs %u in group %s. fs \"\n                    \"was registered back to group %s and consistency is KEPT \"\n                    \"between FsView and GeoTreeEngine\",\n                    snapshot.mId, mGroupView[group_name]->mName.c_str(),\n                    oldgroup->mName.c_str());\n          } else {\n            eos_err(\"while moving fs, could not insert fs %u in group %s. fs \"\n                    \"was unregistered and consistency is KEPT between FsView \"\n                    \"and GeoTreeEngine\", snapshot.mId, mGroupView[group_name]->mName.c_str());\n          }\n        } else {\n          eos_crit(\"while moving fs, could not insert fs %u in group %s. fs \"\n                   \"could not be unregistered and consistency is BROKEN between \"\n                   \"FsView and GeoTreeEngine\", snapshot.mId,\n                   mGroupView[group_name]->mName.c_str());\n        }\n\n        return false;\n      }\n\n      mSpaceGroupView[tgt_space].insert(mGroupView[tgt_group]);\n\n      // Check if we have already a space view\n      if (mSpaceView.count(tgt_space)) {\n        mSpaceView[tgt_space]->insert(snapshot.mId);\n        eos_debug(\"inserting into space view %s<=>%u %x\",\n                  tgt_space.c_str(), snapshot.mId, fs);\n      } else {\n        FsSpace* space = new FsSpace(tgt_space.c_str());\n        mSpaceView[tgt_space] = space;\n        space->insert(snapshot.mId);\n        eos_debug(\"creating/inserting into space view %s<=>%u %x\",\n                  tgt_space.c_str(), snapshot.mId, fs);\n      }\n\n      return true;\n    }\n  }\n\n  return false;\n}\n//------------------------------------------------------------------------------\n// Remove a file system\n//------------------------------------------------------------------------------\nbool\nFsView::UnRegister(FileSystem* fs, bool unreg_from_geo_tree,\n                   bool notify_fst)\n{\n  if (!fs) {\n    return false;\n  }\n\n  // Delete in the configuration engine\n  const std::string key = fs->GetQueuePath();\n  eos::common::FileSystem::fs_snapshot_t snapshot;\n\n  if (!fs->SnapShotFileSystem(snapshot)) {\n    eos_err(\"msg=\\\"failed to snapshot file system, abort deregistration\\\" \"\n            \" qpath=\\\"%s\\\"\", key.c_str());\n    return false;\n  }\n\n  // Remove fs from node view & evt. remove node view\n  if (mNodeView.count(snapshot.mQueue)) {\n    FsNode* node = mNodeView[snapshot.mQueue];\n    node->erase(snapshot.mId);\n  }\n\n  // Remove fs from group view & evt. remove group view\n  if (mGroupView.count(snapshot.mGroup)) {\n    FsGroup* group = mGroupView[snapshot.mGroup];\n\n    if (unreg_from_geo_tree\n        && !gOFS->mGeoTreeEngine->removeFsFromGroup(fs, group, false)) {\n      if (Register(fs, fs->getCoreParams(), false)) {\n        eos_err(\"could not remove fs %u from GeoTreeEngine : fs was \"\n                \"registered back and consistency is KEPT between FsView \"\n                \"and GeoTreeEngine\", snapshot.mId);\n      } else {\n        eos_crit(\"could not remove fs %u from GeoTreeEngine : fs could not \"\n                 \"be registered back and consistency is BROKEN between \"\n                 \"FsView and GeoTreeEngine\", snapshot.mId);\n      }\n\n      return false;\n    }\n\n    group->erase(snapshot.mId);\n    eos_debug(\"msg=\\\"unregister group %s from group view\\\"\",\n              group->GetMember(\"name\").c_str());\n\n    if (!group->size()) {\n      mSpaceGroupView[snapshot.mSpace].erase(mGroupView[snapshot.mGroup]);\n      mGroupView.erase(snapshot.mGroup);\n      delete group;\n    }\n  }\n\n  // Remove fs from space view & evt. remove space view\n  if (mSpaceView.count(snapshot.mSpace)) {\n    FsSpace* space = mSpaceView[snapshot.mSpace];\n    space->erase(snapshot.mId);\n    eos_debug(\"msg=\\\"unregister space %s from space view\\\"\",\n              space->GetMember(\"name\").c_str());\n\n    if (!space->size()) {\n      mSpaceView.erase(snapshot.mSpace);\n      delete space;\n    }\n  }\n\n  // Remove view by filesystem object and filesystem id\n  if (!mIdView.eraseByPtr(fs)) {\n    eos_static_crit(\"msg=\\\"no such fs to unregister\\\" fsid=%lu fs_ptr=%x\",\n                    snapshot.mId, fs);\n  }\n\n  // Remove mapping\n  RemoveMapping(snapshot.mId, snapshot.mUuid);\n\n  if (gOFS->mMaster->IsMaster() && FsView::gFsView.mConfigEngine) {\n    eos_static_info(\"msg=\\\"calling delete config value\\\" fsid=%lu\", fs->GetId());\n    FsView::gFsView.mConfigEngine->DeleteConfigValue(\"fs\", key.c_str());\n  }\n\n  // Notify the FST to delete the fs object from local maps is actually done\n  // above but we also needs to notify about the node hash deletion if needed.\n  if (notify_fst) {\n    fs->DeleteSharedHash(true);\n\n    // Eventually delete the node\n    if (mNodeView.count(snapshot.mQueue)) {\n      FsNode* node = mNodeView[snapshot.mQueue];\n      node->SignalRefresh();\n\n      if (node->size() == 0) {\n        eos_static_debug(\"msg=\\\"unregister node %s from node view\\\"\",\n                         node->GetMember(\"name\").c_str());\n        mNodeView.erase(snapshot.mQueue);\n        common::SharedHashLocator nodeLocator =\n          common::SharedHashLocator::makeForNode(snapshot.mQueue);\n\n        if (!mq::SharedHashWrapper::deleteHash(gOFS->mMessagingRealm.get(),\n                                               nodeLocator)) {\n          eos_static_err(\"msg=\\\"failed to delete shared hash\\\" queue=\\\"%s\\\"\",\n                         snapshot.mQueue.c_str());\n        }\n\n        delete node;\n      }\n    }\n  }\n\n  delete fs;\n  return true;\n}\n//------------------------------------------------------------------------------\n// Checks if a node has already a filesystem registered\n//------------------------------------------------------------------------------\nbool\nFsView::ExistsQueue(std::string queue, std::string queuepath)\n{\n  if (mNodeView.count(queue)) {\n    // Loop over all attached filesystems and compare the queue path\n    for (auto it = mNodeView[queue]->begin(); it != mNodeView[queue]->end(); ++it) {\n      FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it);\n\n      if (fs && (fs->GetQueuePath() == queuepath)) {\n        // This queuepath exists already, we cannot register\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n//------------------------------------------------------------------------------\n// Add view by nodename (= MQ queue) e.g. /eos/<host>:<port>/fst\n//------------------------------------------------------------------------------\nbool\nFsView::RegisterNode(const char* nodename)\n{\n  std::string nodequeue = nodename;\n\n  if (mNodeView.count(nodequeue)) {\n    eos_debug(\"msg=\\\"node already exists\\\" info=\\\"%s\\\"\", nodequeue.c_str());\n    return false;\n  } else {\n    FsNode* node = new FsNode(nodequeue.c_str());\n    mNodeView[nodequeue] = node;\n    node->SetNodeConfigDefault();\n    eos_debug(\"msg=\\\"creating node\\\" info=\\\"%s\\\"\", nodequeue.c_str());\n    return true;\n  }\n}\n//------------------------------------------------------------------------------\n// Remove view by nodename (= MQ queue) e.g. /eos/<host>:<port>/fst - we have\n// to remove all the connected filesystems via UnRegister(fs) to keep the\n// space, group and node views in sync.\n//------------------------------------------------------------------------------\nbool\nFsView::UnRegisterNode(const char* nodename)\n{\n  bool retc = true;\n  bool has_fs = false;\n\n  if (mNodeView.count(nodename)) {\n    while (mNodeView.count(nodename) &&\n           (mNodeView[nodename]->begin() != mNodeView[nodename]->end())) {\n      eos::common::FileSystem::fsid_t fsid = *(mNodeView[nodename]->begin());\n      FileSystem* fs = mIdView.lookupByID(fsid);\n\n      if (fs) {\n        has_fs = true;\n        eos_static_debug(\"msg=\\\"unregister filesystem\\\" fsid=%llu node=%s \"\n                         \"queue=%s\", (unsigned long long) fsid, nodename,\n                         fs->GetQueue().c_str());\n        retc |= UnRegister(fs);\n      }\n    }\n\n    // Explicitly remove the node from the view here because no fs was removed\n    if (!has_fs) {\n      delete mNodeView[nodename];\n      retc = (mNodeView.erase(nodename) ? true : false);\n    }\n  }\n\n  return retc;\n}\n//------------------------------------------------------------------------------\n// Add view by spacename (= MQ queue) e.g. /eos/<host>:<port>/fst\n//------------------------------------------------------------------------------\nbool\nFsView::RegisterSpace(const char* spacename)\n{\n  std::string spacequeue = spacename;\n\n  if (mSpaceView.count(spacequeue)) {\n    eos_debug(\"space is existing\");\n    return false;\n  } else {\n    FsSpace* space = new FsSpace(spacequeue.c_str());\n    mSpaceView[spacequeue] = space;\n    eos_debug(\"creating space view %s\", spacequeue.c_str());\n    return true;\n  }\n}\n//------------------------------------------------------------------------------\n// Remove view by spacename (= MQ queue) e.g. /eos/<host>:<port>/fst\n//------------------------------------------------------------------------------\nbool\nFsView::UnRegisterSpace(const char* spacename)\n{\n  // We have to remove all the connected filesystems via UnRegister(fs) to keep\n  // space, group and fs views in sync\n  bool retc = true;\n  bool has_fs = false;\n\n  if (mSpaceView.count(spacename)) {\n    while (mSpaceView.count(spacename) && mSpaceView[spacename]->size()) {\n      eos::common::FileSystem::fsid_t fsid = *(mSpaceView[spacename]->begin());\n      FileSystem* fs = mIdView.lookupByID(fsid);\n\n      if (fs) {\n        has_fs = true;\n        eos_static_debug(\"msg=\\\"unregister filesystem \\\"fsid=%llu space=%s \"\n                         \"queue=%s\", (unsigned long long) fsid, spacename,\n                         fs->GetQueue().c_str());\n        retc |= UnRegister(fs);\n      }\n\n      if (mSpaceView.count(spacename) == 0) {\n        return true;\n      }\n    }\n\n    if (!has_fs) {\n      // We have to explicitly remove the space from the view here because no\n      // fs was removed\n      if (mSpaceView.count(spacename)) {\n        delete mSpaceView[spacename];\n        retc = (mSpaceView.erase(spacename) ? true : false);\n      }\n    }\n  }\n\n  return retc;\n}\n//------------------------------------------------------------------------------\n// Add view by groupname  e.g. default or default.0\n//------------------------------------------------------------------------------\nbool\nFsView::RegisterGroup(const char* groupname)\n{\n  std::string groupqueue = groupname;\n\n  if (mGroupView.count(groupqueue)) {\n    eos_debug(\"group is existing\");\n    return false;\n  } else {\n    FsGroup* group = new FsGroup(groupqueue.c_str());\n    mGroupView[groupqueue] = group;\n    eos_debug(\"creating group view %s\", groupqueue.c_str());\n    return true;\n  }\n}\n//------------------------------------------------------------------------------\n// Remove view by groupname e.g. default or default.0\n//------------------------------------------------------------------------------\nbool\nFsView::UnRegisterGroup(const char* groupname)\n{\n  // We have to remove all the connected filesystems via UnRegister(fs) to keep\n  // the group view in sync.\n  bool retc = true;\n  bool has_fs = false;\n\n  if (mGroupView.count(groupname)) {\n    while (mGroupView.count(groupname) &&\n           (mGroupView[groupname]->begin() != mGroupView[groupname]->end())) {\n      eos::common::FileSystem::fsid_t fsid = *(mGroupView[groupname]->begin());\n      FileSystem* fs = mIdView.lookupByID(fsid);\n\n      if (fs) {\n        has_fs = true;\n        eos_static_debug(\"msg=\\\"unregister filesystem fsid=%llu group=%s \"\n                         \"queue=%s\", (unsigned long long) fsid, groupname,\n                         fs->GetQueue().c_str());\n        retc |= UnRegister(fs);\n      }\n    }\n\n    if (!has_fs) {\n      std::string sgroupname = groupname;\n      std::string spacename = \"\";\n      std::string index = \"\";\n\n      // remove the direct group reference here\n      if (mSpaceGroupView.count(spacename)) {\n        mSpaceGroupView[spacename].erase(mGroupView[groupname]);\n      }\n\n      // We have to explicitly remove the group from the view here because no\n      // fs was removed\n      delete mGroupView[groupname];\n      retc = (mGroupView.erase(groupname) ? true : false);\n      eos::common::StringConversion::SplitByPoint(groupname, spacename, index);\n    }\n  }\n\n  return retc;\n}\n//------------------------------------------------------------------------------\n// Remove all filesystems by erasing all spaces\n//------------------------------------------------------------------------------\nvoid\nFsView::Reset()\n{\n  {\n    eos::common::RWMutexReadLock viewlock(ViewMutex);\n\n    // Stop all the threads while taking only the read lock\n    for (auto it = mSpaceView.begin(); it != mSpaceView.end(); ++it) {\n      it->second->Stop();\n    }\n  }\n  eos::common::RWMutexWriteLock viewlock(ViewMutex);\n\n  while (mSpaceView.size()) {\n    std::string name = mSpaceView.begin()->first;\n    UnRegisterSpace(name.c_str());\n  }\n\n  // Remove all mappings\n  mFilesystemMapper.clear();\n  // Although this shouldn't be necessary, better run an additional cleanup\n  mSpaceView.clear();\n  mGroupView.clear();\n  mNodeView.clear();\n  mIdView.clear();\n}\n//------------------------------------------------------------------------------\n// Clear all maps and delete all filesystem/group/space objects\n//------------------------------------------------------------------------------\nvoid\nFsView::Clear()\n{\n  {\n    eos::common::RWMutexReadLock rd_view_lock(ViewMutex);\n\n    // Stop all the threads while taking only thre read lock\n    for (auto it = mSpaceView.begin(); it != mSpaceView.end(); it++) {\n      it->second->Stop();\n    }\n  }\n  eos::common::RWMutexWriteLock wr_view_lock(ViewMutex);\n\n  while (mSpaceView.size()) {\n    std::string name = mSpaceView.begin()->first;\n    UnRegisterSpace(name.c_str());\n  }\n\n  mFilesystemMapper.clear();\n  mSpaceView.clear();\n  mGroupView.clear();\n  mNodeView.clear();\n  mIdView.clear();\n}\n//------------------------------------------------------------------------------\n// Find a filesystem specifying a queuepath\n//------------------------------------------------------------------------------\nFileSystem*\nFsView::FindByQueuePath(std::string& queuepath)\n{\n  // Needs an external ViewMutex lock !!!!\n  for (auto it = mIdView.begin(); it != mIdView.end(); ++it) {\n    if (it->second && it->second->GetQueuePath() == queuepath) {\n      return it->second;\n    }\n  }\n\n  return 0;\n}\n//------------------------------------------------------------------------------\n// Set global config\n//------------------------------------------------------------------------------\nbool\nFsView::SetGlobalConfig(const std::string& key, const std::string& value)\n{\n  if (gOFS && gOFS->mMaster->IsMaster()) {\n    std::string ckey = SSTR(common::InstanceName::getGlobalMgmConfigQueue()\n                            << \"#\" << key);\n\n    if (value.empty()) {\n      mq::SharedHashWrapper::makeGlobalMgmHash(gOFS->mMessagingRealm.get()).del(key);\n\n      if (FsView::gFsView.mConfigEngine) {\n        FsView::gFsView.mConfigEngine->DeleteConfigValue(\"global\", ckey.c_str());\n      }\n    } else {\n      mq::SharedHashWrapper::makeGlobalMgmHash\n      (gOFS->mMessagingRealm.get()).set(key, value);\n\n      if (FsView::gFsView.mConfigEngine) {\n        FsView::gFsView.mConfigEngine->SetConfigValue(\"global\", ckey.c_str(),\n            value.c_str());\n      }\n    }\n  }\n\n  return true;\n}\n//------------------------------------------------------------------------------\n// Get global config\n//------------------------------------------------------------------------------\nstd::string\nFsView::GetGlobalConfig(const std::string& key)\n{\n  if (gOFS) {\n    return mq::SharedHashWrapper::makeGlobalMgmHash(\n             gOFS->mMessagingRealm.get()).get(key);\n  }\n\n  return \"\";\n}\n\n//------------------------------------------------------------------------------\n// Heart beat checker set's filesystem to down if the heart beat is missing\n//------------------------------------------------------------------------------\nvoid\nFsView::HeartBeatCheck(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"FsViewHB\");\n\n  while (!assistant.terminationRequested()) {\n    assistant.wait_for(std::chrono::seconds(10));\n    eos::common::RWMutexReadLock fs_rd_lock(ViewMutex);\n\n    // Loop over all the nodes and update their status\n    for (auto it_node = mNodeView.begin();\n         it_node != mNodeView.end(); ++it_node) {\n      if (it_node->second == nullptr) {\n        continue;\n      }\n\n      auto* node = it_node->second;\n\n      if (node->HasHeartbeat()) {\n        if (node->GetActiveStatus() != eos::common::ActiveStatus::kOnline) {\n          node->SetActiveStatus(eos::common::ActiveStatus::kOnline);\n        }\n\n        // Loop over all files sysytems in the current node and update status\n        for (auto it_fsid = node->begin(); it_fsid != node->end(); ++it_fsid) {\n          FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it_fsid);\n\n          if (fs == nullptr) {\n            continue;\n          }\n\n          std::string group = fs->GetString(\"schedgroup\");\n          bool is_group_active = false;\n\n          if (auto group_key = FsView::gFsView.mGroupView.find(group);\n              group_key != FsView::gFsView.mGroupView.end()) {\n            auto group_status = group_key->second->GetConfigMember(\"status\");\n            is_group_active = group_status == \"on\" || group_status == \"drain\";\n          }\n\n          if ((node->GetConfigMember(\"status\") == \"on\") &&\n              is_group_active) {\n            ssize_t max_ropen = fs->GetLongLong(\"max.ropen\");\n            ssize_t max_wopen = fs->GetLongLong(\"max.wopen\");\n            bool overloaded = ((max_ropen &&\n                                (max_ropen <= fs->GetLongLong(\"stat.ropen\"))) ||\n                               (max_wopen && (max_wopen <= fs->GetLongLong(\"stat.wopen\"))));\n\n            if (!overloaded) {\n              if (fs->GetActiveStatus() != eos::common::ActiveStatus::kOnline) {\n                setFsStatus(fs, eos::common::ActiveStatus::kOnline, fs->GetStatus());\n              }\n            } else {\n              if (fs->GetActiveStatus() != eos::common::ActiveStatus::kOverload) {\n                setFsStatus(fs, eos::common::ActiveStatus::kOverload, fs->GetStatus());\n              }\n            }\n          } else {\n            if (fs->GetActiveStatus() != eos::common::ActiveStatus::kOffline) {\n              setFsStatus(fs, eos::common::ActiveStatus::kOffline, fs->GetStatus());\n            }\n          }\n        }\n      } else {\n        if (node->GetActiveStatus() != eos::common::ActiveStatus::kOffline) {\n          node->SetActiveStatus(eos::common::ActiveStatus::kOffline);\n        }\n\n        // Loop over all files sysytems in the current node and update status\n        for (auto it_fsid = node->begin(); it_fsid != node->end(); ++it_fsid) {\n          FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it_fsid);\n\n          if (fs == nullptr) {\n            continue;\n          }\n\n          if (fs->GetActiveStatus() != eos::common::ActiveStatus::kOffline) {\n            setFsStatus(fs, eos::common::ActiveStatus::kOffline, fs->GetStatus());\n          }\n        }\n      }\n    }\n  }\n}\n//------------------------------------------------------------------------------\n// Re-apply drain status for file systems to re-trigger draining\n//------------------------------------------------------------------------------\nvoid\nFsView::ReapplyDrainStatus()\n{\n  eos::common::RWMutexReadLock fs_rd_lock(ViewMutex);\n\n  for (auto it = mIdView.begin(); it != mIdView.end(); ++it) {\n    eos::common::ConfigStatus cs = it->second->GetConfigStatus();\n\n    if ((cs == eos::common::ConfigStatus::kDrain) ||\n        (cs == eos::common::ConfigStatus::kDrainDead) ||\n        (cs == eos::common::ConfigStatus::kGroupDrain)) {\n      it->second->SetConfigStatus(cs);\n      gOFS->mFsScheduler->setDiskStatus(mIdView.lookupSpaceByID(it->first),\n                                        it->first, cs);\n    }\n  }\n}\n//------------------------------------------------------------------------------\n// Return a view member variable\n//------------------------------------------------------------------------------\nstd::string\nBaseView::GetMember(const std::string& member) const\n{\n  if (member == \"name\") {\n    return mName;\n  }\n\n  if (member == \"type\") {\n    return mType;\n  }\n\n  if (member == \"nofs\") {\n    char line[1024];\n    snprintf(line, sizeof(line) - 1, \"%llu\", (unsigned long long) size());\n    return std::string(line);\n  }\n\n  if (member == \"heartbeat\") {\n    char line[1024];\n    snprintf(line, sizeof(line) - 1, \"%llu\", (unsigned long long) mHeartBeat);\n    return std::string(line);\n  }\n\n  if (member == \"heartbeatdelta\") {\n    char line[1024];\n\n    if (labs(time(NULL) - mHeartBeat) > 86400) {\n      snprintf(line, sizeof(line) - 1, \"~\");\n    } else {\n      snprintf(line, sizeof(line) - 1, \"%llu\",\n               (unsigned long long)(time(NULL) - mHeartBeat));\n    }\n\n    return std::string(line);\n  }\n\n  if (member == \"status\") {\n    return mStatus;\n  }\n\n  // Check for global config value\n  const std::string tag = \"cfg.\";\n\n  if (member.find(tag) == 0) {\n    std::string cfg_member = member;\n    std::string val = \"???\";\n    cfg_member.erase(0, tag.length());\n    std::string value = GetConfigMember(cfg_member);\n\n    if (!value.empty()) {\n      val = value;\n    }\n\n    if ((member == \"cfg.status\") && val.empty()) {\n      val = \"off\";\n    }\n\n    return val;\n  }\n\n  return \"\";\n}\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFsNode::FsNode(const char* name) : BaseView(\n    common::SharedHashLocator::makeForNode(name))\n{\n  mName = name;\n  mType = \"nodesview\";\n  SetConfigMember(\"stat.hostport\", GetMember(\"hostport\"), false);\n  eos_static_info(\"msg=\\\"FsNode constructor\\\" name=\\\"%s\\\" ptr=%p\",\n                  mName.c_str(), this);\n  mSubscription = mq::SharedHashWrapper(gOFS->mMessagingRealm.get(),\n                                        mLocator).subscribe();\n\n  if (mSubscription) {\n    using namespace std::placeholders;\n    mSubscription->attachCallback(std::bind(&FsNode::ProcessUpdateCb, this, _1));\n  }\n}\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nFsNode::~FsNode()\n{\n  if (mSubscription) {\n    mSubscription->detachCallback();\n  }\n\n  eos_static_info(\"msg=\\\"FsNode destructor\\\" name=\\\"%s\\\" ptr=%p\",\n                  mName.c_str(), this);\n}\n//------------------------------------------------------------------------------\n// Callback to process update for the shared hash\n//------------------------------------------------------------------------------\nvoid\nFsNode::ProcessUpdateCb(qclient::SharedHashUpdate&& upd)\n{\n  if (eos::common::FST_HEARTBEAT_KEY == upd.key) {\n    try {\n      SetHeartBeat(std::stoull(upd.value));\n    } catch (...) {\n      eos_static_err(\"msg=\\\"skip heartbeat update due to conversion failure\\\" \"\n                     \"value=\\\"%s\\\"\", upd.value.c_str());\n    }\n  } else if (eos::common::FST_TRAFFIC_SHAPING_IO_REPORT == upd.key) {\n    std::string serialized_report;\n\n    if (!eos::common::SymKey::DeBase64(upd.value, serialized_report)) {\n      eos_static_warning(\"%s\", \"msg=\\\"failed to base64-decode FST IO report\\\"\");\n      return;\n    }\n\n    gOFS->mTrafficShapingEngine.ProcessSerializedFstIoReportNonBlocking(\n        serialized_report);\n  } else {\n    eos_static_debug(\"msg=\\\"ignore node shared hash update\\\" key=\\\"%s\\\" \"\n                     \"value=\\\"%s\\\"\", upd.key.c_str(), upd.value.c_str());\n  }\n}\n//------------------------------------------------------------------------------\n// Set refresh marker for the FSTs\n//------------------------------------------------------------------------------\nvoid\nFsNode::SignalRefresh()\n{\n  const auto ts_ms = std::chrono::duration_cast<std::chrono::milliseconds>\n                     (std::chrono::steady_clock::now().time_since_epoch()).count();\n  SetConfigMember(msRefreshTag, std::to_string(ts_ms), true);\n}\n//------------------------------------------------------------------------------\n// Set the configuration default values for a node\n//------------------------------------------------------------------------------\nvoid\nFsNode::SetNodeConfigDefault()\n{\n  eos_static_info(\"msg=\\\"set defaults\\\" node=%s\", mName.c_str());\n\n  // Define the manager ID\n  if (!(GetConfigMember(\"manager\").length())) {\n    const std::string master_id = gOFS->mMaster->GetMasterId();\n\n    if (!master_id.empty()) {\n      SetConfigMember(\"manager\", gOFS->mMaster->GetMasterId(), true);\n    }\n  }\n\n  // Set the default sym key from the sym key store\n  eos::common::SymKey* symkey = eos::common::gSymKeyStore.GetCurrentKey();\n  const std::string old_key = GetConfigMember(\"symkey\");\n\n  if (old_key.empty() ||\n      (old_key != std::string(symkey->GetKey64()))) {\n    SetConfigMember(\"symkey\", symkey->GetKey64(), true);\n  }\n\n  // Set the default debug level to notice\n  if (!(GetConfigMember(\"debug.level\").length())) {\n    SetConfigMember(\"debug.level\", \"info\", true);\n  }\n\n  // Set by default the MGM domain e.g. same geographical position as the MGM\n  if (!(GetConfigMember(\"domain\").length())) {\n    SetConfigMember(\"domain\", \"MGM\", true);\n  }\n}\n//------------------------------------------------------------------------------\n// Get member\n//------------------------------------------------------------------------------\nstd::string\nFsNode::GetMember(const std::string& member) const\n{\n  if (member == \"hostport\") {\n    std::string hostport =\n      eos::common::StringConversion::GetStringHostPortFromQueue(mName.c_str());\n    return hostport;\n  } else if (member == \"status\") {\n    return GetStatus();\n  } else {\n    return BaseView::GetMember(member);\n  }\n}\n//------------------------------------------------------------------------------\n// Get node active status\n//------------------------------------------------------------------------------\neos::common::ActiveStatus\nFsNode::GetActiveStatus()\n{\n  if (GetStatus() == \"online\") {\n    return eos::common::ActiveStatus::kOnline;\n  } else {\n    return eos::common::ActiveStatus::kOffline;\n  }\n}\n//------------------------------------------------------------------------------\n// Set node active status\n//------------------------------------------------------------------------------\nvoid\nFsNode::SetActiveStatus(eos::common::ActiveStatus active)\n{\n  if (active == eos::common::ActiveStatus::kOnline) {\n    SetStatus(\"online\");\n  } else {\n    SetStatus(\"offline\");\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check if node has a recent enough heartbeat ie. less then 60 seconds\n//------------------------------------------------------------------------------\nbool\nFsNode::HasHeartbeat() const\n{\n  if (mHeartBeat == 0) {\n    return false;\n  }\n\n  return isHeartbeatRecent(mHeartBeat.load());\n}\n//------------------------------------------------------------------------------\n// Set a configuration member variable (stored in the config engine)\n// If 'isstatus'=true we just store the value in the shared hash but don't flush\n// it into the configuration engine.\n// => is used to set status variables on config queues (baseview queues)\n//------------------------------------------------------------------------------\nbool\nBaseView::SetConfigMember(std::string key, std::string value,\n                          bool isstatus)\n{\n  bool success = mq::SharedHashWrapper(gOFS->mMessagingRealm.get(),\n                                       mLocator).set(key, value);\n\n  // Register in the configuration engine\n  if (gOFS->mMaster->IsMaster() && (!isstatus) && FsView::gFsView.mConfigEngine) {\n    std::string node_cfg_name = mLocator.getConfigQueue();\n    node_cfg_name += \"#\";\n    node_cfg_name += key;\n    std::string confval = value;\n    FsView::gFsView.mConfigEngine->SetConfigValue(\"global\", node_cfg_name.c_str(),\n        confval.c_str());\n  }\n\n  return success;\n}\n//------------------------------------------------------------------------------\n// Get a configuration member variable (stored in the config engine)\n//------------------------------------------------------------------------------\nstd::string\nBaseView::GetConfigMember(std::string key) const\n{\n  return mq::SharedHashWrapper(gOFS->mMessagingRealm.get(), mLocator).get(key);\n}\n//------------------------------------------------------------------------------\n// Get a list of configuration member variables from config engine\n//------------------------------------------------------------------------------\nbool\nBaseView::GetConfigMembers(const std::vector<std::string>& keys,\n                           std::map<std::string, std::string>& out) const\n{\n  return mq::SharedHashWrapper(gOFS->mMessagingRealm.get(), mLocator).get(keys,\n         out);\n}\n//------------------------------------------------------------------------------\n// Delete a configuration member variable (stored in the config engine)\n//------------------------------------------------------------------------------\nbool\nBaseView::DeleteConfigMember(std::string key) const\n{\n  // If key already deleted then return false\n  if (GetConfigMember(key).empty()) {\n    return false;\n  }\n\n  bool deleted = mq::SharedHashWrapper(gOFS->mMessagingRealm.get(),\n                                       mLocator).del(key);\n\n  // Delete in the configuration engine\n  if (gOFS->mMaster->IsMaster() && FsView::gFsView.mConfigEngine) {\n    std::string node_cfg_name = mLocator.getConfigQueue();\n    node_cfg_name += \"#\";\n    node_cfg_name += key;\n    FsView::gFsView.mConfigEngine->DeleteConfigValue(\"global\",\n        node_cfg_name.c_str());\n  }\n\n  return deleted;\n}\n//------------------------------------------------------------------------------\n// GetConfigKeys\n//------------------------------------------------------------------------------\nbool\nBaseView::GetConfigKeys(std::vector<std::string>& keys)\n{\n  return mq::SharedHashWrapper(gOFS->mMessagingRealm.get(),\n                               mLocator).getKeys(keys);\n}\n//------------------------------------------------------------------------------\n// Class ConfigResetMonitor\n//------------------------------------------------------------------------------\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nConfigResetMonitor::ConfigResetMonitor():\n  mOrigConfEngine(nullptr)\n{\n  std::swap(mOrigConfEngine, FsView::gFsView.mConfigEngine);\n}\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nConfigResetMonitor::~ConfigResetMonitor()\n{\n  if (mOrigConfEngine == nullptr) {\n    FsView::gFsView.mConfigEngine = gOFS->mConfigEngine;\n  } else {\n    std::swap(FsView::gFsView.mConfigEngine, mOrigConfEngine);\n  }\n}\n//------------------------------------------------------------------------------\n// Class FsView\n//------------------------------------------------------------------------------\n//------------------------------------------------------------------------------\n// Add FsChangeListener to all the existing file systems\n//------------------------------------------------------------------------------\nvoid\nFsView::AddFsChangeListener(std::shared_ptr<eos::mq::FsChangeListener> fs_lst,\n                            const std::set<std::string>& interests)\n{\n  eos::common::RWMutexReadLock rd_lock(FsView::gFsView.ViewMutex);\n\n  for (const auto& elem : FsView::gFsView.mIdView) {\n    FileSystem* fs = elem.second;\n    fs->AttachFsListener(fs_lst, interests);\n  }\n}\n//------------------------------------------------------------------------------\n// Creates a new filesystem id based on a uuid\n//------------------------------------------------------------------------------\neos::common::FileSystem::fsid_t\nFsView::CreateMapping(std::string fsuuid)\n{\n  return mFilesystemMapper.allocate(fsuuid);\n}\n//------------------------------------------------------------------------------\n// Adds a fsid=uuid pair to the mapping\n//------------------------------------------------------------------------------\nbool\nFsView::ProvideMapping(std::string fsuuid,\n                       eos::common::FileSystem::fsid_t fsid)\n{\n  return mFilesystemMapper.injectMapping(fsid, fsuuid);\n}\n//------------------------------------------------------------------------------\n// Return a fsid for a uuid\n//------------------------------------------------------------------------------\neos::common::FileSystem::fsid_t\nFsView::GetMapping(std::string fsuuid)\n{\n  return mFilesystemMapper.lookup(fsuuid);\n}\n//------------------------------------------------------------------------------\n// Removes a mapping entry by fsid\n//------------------------------------------------------------------------------\nbool\nFsView::RemoveMapping(eos::common::FileSystem::fsid_t fsid)\n{\n  return mFilesystemMapper.remove(fsid);\n}\n//------------------------------------------------------------------------------\n// Removes a mapping entry by providing fsid + uuid\n//------------------------------------------------------------------------------\nbool\nFsView::RemoveMapping(eos::common::FileSystem::fsid_t fsid,\n                      std::string fsuuid)\n{\n  return mFilesystemMapper.remove(fsid) | mFilesystemMapper.remove(fsuuid);\n}\n//------------------------------------------------------------------------------\n// Print space information\n//------------------------------------------------------------------------------\nvoid\nFsView::PrintSpaces(std::string& out, const std::string& table_format,\n                    const std::string& table_mq_format, unsigned int outdepth,\n                    const char* selection, const std::string& filter, const bool dont_color)\n{\n  std::vector<std::string> selections;\n  std::string selected = selection ? selection : \"\";\n\n  if (selection) {\n    eos::common::StringConversion::Tokenize(selected, selections, \",\");\n  }\n\n  TableFormatterBase table(dont_color);\n\n  for (auto it = mSpaceView.begin(); it != mSpaceView.end(); ++it) {\n    it->second->Print(table, table_format, table_mq_format, outdepth, filter,\n                      dont_color);\n  }\n\n  out = table.GenerateTable(HEADER, selections);\n}\n//----------------------------------------------------------------------------\n// Print group information\n//----------------------------------------------------------------------------\nvoid\nFsView::PrintGroups(std::string& out, const std::string& table_format,\n                    const std::string& table_mq_format, unsigned int outdepth,\n                    const char* selection, const bool dont_color)\n{\n  std::vector<std::string> selections;\n  std::string selected = selection ? selection : \"\";\n\n  if (selection) {\n    eos::common::StringConversion::Tokenize(selected, selections, \",\");\n  }\n\n  TableFormatterBase table(dont_color);\n\n  for (auto it = mGroupView.begin(); it != mGroupView.end(); ++it) {\n    it->second->Print(table, table_format, table_mq_format, outdepth,\n                      std::string(\"\"), dont_color);\n  }\n\n  out = table.GenerateTable(HEADER, selections);\n}\n//------------------------------------------------------------------------------\n// Print node information\n//------------------------------------------------------------------------------\nvoid\nFsView::PrintNodes(std::string& out, const std::string& table_format,\n                   const std::string& table_mq_format, unsigned int outdepth,\n                   const char* selection, const bool dont_color)\n{\n  std::vector<std::string> selections;\n  std::string selected = selection ? selection : \"\";\n\n  if (selection) {\n    eos::common::StringConversion::Tokenize(selected, selections, \",\");\n  }\n\n  TableFormatterBase table(dont_color);\n\n  for (auto it = mNodeView.begin(); it != mNodeView.end(); ++it) {\n    it->second->Print(table, table_format, table_mq_format, outdepth,\n                      std::string(\"\"), dont_color);\n  }\n\n  out = table.GenerateTable(HEADER, selections);\n}\n//------------------------------------------------------------------------------\n// Converts a config engine definition for a filesystem into the FsView\n// representation.\n// @note This method needs to be called with the ViewMutex locked for write\n//------------------------------------------------------------------------------\nbool\nFsView::ApplyFsConfig(const char* inkey, const std::string& val,\n                      bool first_unregister)\n{\n  std::map<std::string, std::string> configmap;\n\n  if (!common::ConfigParsing::parseFilesystemConfig(val, configmap)) {\n    eos_err(\"msg=\\\"failed parsing fs config entry\\\" data=\\\"%s\\\"\",\n            val.c_str());\n    return false;\n  }\n\n  common::FileSystemLocator locator;\n\n  if (!common::FileSystemLocator::fromQueuePath(configmap[\"queuepath\"],\n      locator)) {\n    eos_crit(\"msg=\\\"failed parsing queuepath: %s\", configmap[\"queuepath\"].c_str());\n    return false;\n  }\n\n  auto it = configmap.find(\"id\");\n\n  if (it == configmap.end()) {\n    eos_static_err(\"msg=\\\"missing id from fs config entry\\\" value=\\\"%s\\\"\",\n                   val.c_str());\n    return false;\n  }\n\n  FileSystem::fsid_t fsid = common::FileSystem::ConvertToFsid(it->second);\n\n  if (fsid == 0ul) {\n    eos_static_err(\"msg=\\\"no such fsid 0\\\" value=\\\"%s\\\"\", it->second.c_str());\n    return false;\n  }\n\n  it = configmap.find(\"uuid\");\n\n  if (it == configmap.end()) {\n    eos_static_err(\"msg=\\\"missing uuid from fs config entry\\\" value=\\\"%s\\\"\",\n                   val.c_str());\n    return false;\n  }\n\n  const std::string uuid = it->second;\n  FileSystem* fs = FsView::gFsView.mIdView.lookupByID(fsid);\n\n  if (first_unregister && fs) {\n    if (!UnRegister(fs)) {\n      eos_static_warning(\"msg=\\\"failed to unregister file system\\\" fsid=%lu\",\n                         fsid);\n    }\n\n    fs = nullptr;\n  }\n\n  // Apply only the registration for a new filesystem if it does not exist\n  if (fs == nullptr) {\n    if (!ProvideMapping(uuid, fsid)) {\n      eos_err(\"msg=\\\"conflict registering file system id/uuid\\\"\"\n              \"fsid=%lu uuid=%s\", fsid, uuid.c_str());\n      return false;\n    }\n\n    fs = new FileSystem(locator, gOFS->mMessagingRealm.get());\n  }\n\n  common::FileSystemUpdateBatch batch;\n  batch.setId(fsid);\n  batch.setStringDurable(\"uuid\", uuid);\n\n  for (auto it = configmap.begin(); it != configmap.end(); it++) {\n    // Set config parameters except for the \"configstatus\" which can trigger a\n    // drain job. This in turn could try to update the status of the file\n    // system and will deadlock trying to get the transaction mutex. Therefore,\n    // we update the configstatus outside this transaction.\n    if (it->first != \"configstatus\") {\n      batch.setStringDurable(it->first, it->second);\n    }\n  }\n\n  fs->applyBatch(batch);\n  auto it_cfg = configmap.find(\"configstatus\");\n\n  if (it_cfg != configmap.end()) {\n    fs->SetString(it_cfg->first.c_str(), it_cfg->second.c_str());\n  }\n\n  if (!Register(fs, fs->getCoreParams())) {\n    eos_err(\"msg=\\\"cannot register filesystem from config\\\" queuepath=\\\"%s\\\"\",\n            configmap[\"queuepath\"].c_str());\n\n    if (RemoveMapping(fsid, uuid)) {\n      eos_info(\"msg=\\\"remove mapping\\\" fsid=%lu uuid=%s\", fsid, uuid.c_str());\n    } else {\n      eos_err(\"msg=\\\"failed to remove mapping\\\" fsid=%lu uuid=%s\",\n              fsid, uuid.c_str());\n    }\n\n    return false;\n  }\n\n  return true;\n}\n//------------------------------------------------------------------------------\n// Converts a config engine definition of a global variable into the FsView\n// representation.\n//------------------------------------------------------------------------------\nbool\nFsView::ApplyGlobalConfig(const char* key, std::string& val)\n{\n  // global variables are stored like key='<queuename>:<variable>' val='<val>'\n  std::string configqueue = key;\n  std::vector<std::string> tokens;\n  std::vector<std::string> paths;\n  std::string delimiter = \"#\";\n  std::string pathdelimiter = \"/\";\n  eos::common::StringConversion::Tokenize(configqueue, tokens, delimiter);\n  eos::common::StringConversion::Tokenize(configqueue, paths, pathdelimiter);\n\n  if (tokens.size() != 2) {\n    eos_static_err(\"the key definition of config <%s> is invalid\", key);\n    return false;\n  }\n\n  if (paths.size() < 1) {\n    eos_static_err(\"the queue name does not contain any /\");\n    return false;\n  }\n\n  // extract space name\n  std::string space = tokens[0];\n  space.erase(0, space.rfind(\"/\") + 1);\n\n  // apply a new token generation value\n  if (tokens[1] == \"token.generation\") {\n    eos_static_info(\"token-generation := %s\", val.c_str());\n    eos::common::EosTok::sTokenGeneration = strtoull(val.c_str(), 0, 10);\n  } else if (tokens[1] == \"fusex.hbi\") {\n    gOFS->zMQ->gFuseServer.Client().SetHeartbeatInterval(atoi(val.c_str()));\n  } else if (tokens[1] == \"fusex.qti\") {\n    gOFS->zMQ->gFuseServer.Client().SetQuotaCheckInterval(atoi(val.c_str()));\n  } else if (tokens[1] == \"fusex.bca\") {\n    gOFS->zMQ->gFuseServer.Client().SetBroadCastMaxAudience(atoi(val.c_str()));\n  } else if (tokens[1] == \"fusex.bca_match\") {\n    gOFS->zMQ->gFuseServer.Client().SetBroadCastAudienceSuppressMatch(val.c_str());\n  } else if (tokens[1].substr(0, 9) == \"attr.sys.\") {\n    std::string key = tokens[1].substr(5);\n    eos_static_info(\"Setting global attribute space:%s %s=%s\\n\", space.c_str(),\n                    key.c_str(), val.c_str());\n    // this is a space attribute\n    std::unique_lock<std::mutex> lock(gOFS->mSpaceAttributesMutex);\n    gOFS->mSpaceAttributes[space][key] = val;\n  } else if (tokens[1] == eos::common::TRAFFIC_SHAPING_POLICIES_CONFIG) {\n    // set the map\n  } else if (tokens[1] == eos::common::TRAFFIC_SHAPING_ENABLE_CONFIG) {\n    if (val == \"true\" || val == \"1\") {\n      gOFS->mTrafficShapingEngine.Enable();\n    } else {\n      gOFS->mTrafficShapingEngine.Disable();\n    }\n  }\n\n  common::SharedHashLocator locator;\n\n  if (!common::SharedHashLocator::fromConfigQueue(tokens[0], locator)) {\n    eos_static_err(\"could not understand global configuration: %s\",\n                   tokens[0].c_str());\n    return false;\n  }\n\n  mq::SharedHashWrapper hash(gOFS->mMessagingRealm.get(), locator);\n  bool success = hash.set(tokens[1].c_str(), val.c_str());\n  return success;\n}\n//------------------------------------------------------------------------------\n// Broadcast new manager id to all the FST nodes\n//------------------------------------------------------------------------------\nvoid\nFsView::BroadcastMasterId(const std::string master_id)\n{\n  eos_static_info(\"msg=\\\"broadcast master id\\\" master=\\\"%s\\\"\",\n                  master_id.c_str());\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n\n  for (auto it = FsView::gFsView.mNodeView.begin();\n       it != FsView::gFsView.mNodeView.end(); ++it) {\n    it->second->SetConfigMember(\"manager\", master_id, true);\n  }\n}\n//------------------------------------------------------------------------------\n// Collect all endpoints (<hostname>:<port>) matching the given queue or pattern\n//------------------------------------------------------------------------------\nstd::set<std::string>\nFsView::CollectEndpoints(const std::string& queue) const\n{\n  int fst_port;\n  std::string fst_host;\n  std::set<std::string> endpoints;\n  eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n\n  for (const auto& elem : FsView::gFsView.mIdView) {\n    FileSystem* fs = elem.second;\n\n    if (fs == nullptr) {\n      eos_static_err(\"msg=\\\"file system null\\\" fsid=%u\", elem.first);\n      continue;\n    }\n\n    if (queue == \"*\") {\n      if (fs->GetActiveStatus() != eos::common::ActiveStatus::kOnline) {\n        eos_static_err(\"msg=\\\"file system not online\\\" fsid=%u\", elem.first);\n        continue;\n      }\n    } else {\n      if (queue != fs->GetQueue()) {\n        continue;\n      } else {\n        if (fs->GetActiveStatus() != eos::common::ActiveStatus::kOnline) {\n          eos_static_err(\"msg=\\\"file system not online\\\" fsid=%u\", elem.first);\n          break;\n        }\n      }\n    }\n\n    fst_host = fs->GetHost();\n    fst_port = fs->getCoreParams().getLocator().getPort();\n    endpoints.insert(SSTR(fst_host << \":\" << fst_port));\n  }\n\n  return endpoints;\n}\n//------------------------------------------------------------------------------\n// Get set of unbalanced groups given the threshold\n//------------------------------------------------------------------------------\nstd::map<std::string, double>\nFsView::GetUnbalancedGroups(const std::string& space_name,\n                            double threshold) const\n{\n  static const std::string metric = \"stat.statfs.filled\";\n  std::map<std::string, double> unbalanced;\n  eos::common::RWMutexReadLock fs_rd_lock(ViewMutex);\n  auto it = mSpaceGroupView.find(space_name);\n\n  if (it == mSpaceGroupView.end()) {\n    return unbalanced;\n  }\n\n  const auto& set_groups = it->second;\n\n  for (auto* group : set_groups) {\n    double dev = group->MaxAbsDeviation(metric.c_str(), false);\n    eos_static_info(\"msg=\\\"collect group info\\\" group=%s max_dev=%.02f \"\n                    \"threshold=%.02f\", group->mName.c_str(), dev, threshold);\n\n    if (dev > threshold) {\n      unbalanced.emplace(group->mName, dev);\n    }\n  }\n\n  return unbalanced;\n}\n//------------------------------------------------------------------------------\n// Get sets of file systems from given group which are above/below the average\n// given the threshold\n//------------------------------------------------------------------------------\nFsPrioritySets\nFsView::GetFsToBalance(const std::string& group_name, double threshold) const\n{\n  static const std::string metric = \"stat.statfs.filled\";\n  FsPrioritySets fs_prio;\n  eos::common::RWMutexReadLock fs_rd_lock(ViewMutex);\n  const auto it = mGroupView.find(group_name);\n\n  if (it == mGroupView.end()) {\n    return fs_prio;\n  }\n\n  auto* group = it->second;\n  double average = group->AverageDouble(metric.c_str(), false);\n\n  for (auto it_fs = group->begin(); it_fs != group->end(); ++it_fs) {\n    auto* fs = mIdView.lookupByID(*it_fs);\n\n    if (fs && BaseView::ConsiderForStatistics(fs)) {\n      const std::string node_port = fs->getCoreParams().getHostPort();\n      double fs_filled = fs->GetDouble(metric.c_str());\n\n      if (fs_filled < average) {\n        // Make sure this file system allows writes\n        if (fs->GetConfigStatus() >= eos::common::ConfigStatus::kWO) {\n          if (average - fs_filled > threshold) {\n            fs_prio.mPrioLow.emplace(*it_fs, node_port);\n          } else {\n            fs_prio.mLow.emplace(*it_fs, node_port);\n          }\n        }\n      } else {\n        // Make sure this file systems allows reads\n        if ((fs->GetConfigStatus() == eos::common::ConfigStatus::kRW) ||\n            (fs->GetConfigStatus() == eos::common::ConfigStatus::kRO)) {\n          if (fs_filled - average > threshold) {\n            fs_prio.mPrioHigh.emplace(*it_fs, node_port);\n          } else {\n            fs_prio.mHigh.emplace(*it_fs, node_port);\n          }\n        }\n      }\n    }\n  }\n\n  return fs_prio;\n}\n//----------------------------------------------------------------------------\n// Dump balancer thread pool info for each of the existing spaces\n//----------------------------------------------------------------------------\nvoid\nFsView::DumpBalancerPoolInfo(std::ostringstream& oss,\n                             std::string_view prefix) const\n{\n  eos::common::RWMutexReadLock space_rd_lock(ViewMutex);\n\n  for (const auto& elem : mSpaceView) {\n    if (elem.first != eos::common::EOS_SPARE_GROUP) {\n      oss << prefix << elem.second->GetBalancerPoolInfo()\n          << \"space=\" << elem.first << std::endl;\n    }\n  }\n}\n//------------------------------------------------------------------------------\n// Get space name for the given file system id\n//------------------------------------------------------------------------------\nstd::string\nFsView::GetSpaceNameForFses(const eos::IFileMD::id_t fid,\n                            const eos::IFileMD::LocationVector& vect_loc) const\n{\n  std::string space_name;\n\n  for (const auto fs_loc : vect_loc) {\n    if (fs_loc && (fs_loc != EOS_TAPE_FSID)) {\n      eos::common::RWMutexReadLock fs_rd_lock(ViewMutex);\n      eos::mgm::FileSystem* fs = mIdView.lookupByID(fs_loc);\n\n      if (!fs) {\n        eos_err(\"msg=\\\"file has unknown file system location\\\" fxid=%08llx \"\n                \"fsid=%d\", fid, fs_loc);\n        continue;\n      }\n\n      space_name = fs->GetSpace();\n      break;\n    }\n  }\n\n  return space_name;\n}\n//------------------------------------------------------------------------------\n// Should the provided fsid participate in statistics calculations?\n// Yes, if:\n// - The filesystem exists (duh)\n// - The filesystem is at-least-RO, booted and online\n//\n// Call with fsview lock at-least-read locked.\n//------------------------------------------------------------------------------\nbool BaseView::ConsiderForStatistics(FileSystem* fs)\n{\n  if (!fs) {\n    return false;\n  }\n\n  if (fs->GetConfigStatus() < eos::common::ConfigStatus::kRO) {\n    return false;\n  }\n\n  if (fs->GetStatus() != eos::common::BootStatus::kBooted) {\n    return false;\n  }\n\n  if (fs->GetActiveStatus() == eos::common::ActiveStatus::kOffline) {\n    return false;\n  }\n\n  return true;\n}\n//------------------------------------------------------------------------------\n// Computes the sum for <param> as long\n// param=\"<param>[?<key>=<value] allows to select with matches\n//------------------------------------------------------------------------------\nlong long\nBaseView::SumLongLong(const char* param, bool lock,\n                      const std::set<eos::common::FileSystem::fsid_t>* subset)\n{\n  eos::common::RWMutexReadLock fs_rd_lock;\n\n  if (lock) {\n    fs_rd_lock.Grab(FsView::gFsView.ViewMutex);\n  }\n\n  long long sum = 0;\n  std::string sparam = param;\n  size_t qpos = 0;\n  std::string key = \"\";\n  std::string value = \"\";\n  bool isquery = false;\n\n  if ((qpos = sparam.find(\"?\")) != std::string::npos) {\n    std::string query = sparam;\n    query.erase(0, qpos + 1);\n    sparam.erase(qpos);\n    std::vector<std::string> token;\n    std::string delimiter = \"@\";\n    eos::common::StringConversion::Tokenize(query, token, delimiter);\n    key = token[0];\n    value = token[1];\n    isquery = true;\n  }\n\n  if (isquery && key == \"*\" && value == \"*\") {\n    // we just count the number of entries\n    if (subset) {\n      return subset->size();\n    } else {\n      return size();\n    }\n  }\n\n  std::set<std::string> used_nodes;\n  fsid_iterator it(subset, this);\n  std::set<std::string> unique_fs;\n\n  for (; it.valid(); it.next()) {\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it);\n\n    if (!fs) {\n      continue;\n    }\n\n    if (fs->getSharedFs() != \"\" &&\n        fs->getSharedFs() != \"none\" &&\n        (\n          (sparam == \"stat.statfs.usedbytes\") ||\n          (sparam == \"stat.statfs.capacity\") ||\n          (sparam == \"stat.usedfiles\") ||\n          (sparam == \"stat.statfs.files\") ||\n          (sparam == \"stat.statfs.ffiles\") ||\n          (sparam == \"stat.statfs.freebytes\") ||\n          (sparam == \"stat.totalspace\")) &&\n        unique_fs.count(fs->getSharedFs())) {\n      // don't account shared fs more than once for these keys\n      continue;\n    }\n\n    // for query sum's we always fold in that a group and host has to be enabled\n    if (!key.length() || fs->GetString(key.c_str()) == value) {\n      if (isquery &&\n          ((fs->GetActiveStatus() == eos::common::ActiveStatus::kOffline) ||\n           (eos::common::FileSystem::GetStatusFromString(\n              fs->GetString(\"stat.boot\").c_str()) !=\n            eos::common::BootStatus::kBooted))) {\n        continue;\n      }\n\n      if (sparam.compare(0, 8, \"stat.net\") == 0) {\n        const std::string hostname = fs->getCoreParams().getHost();\n\n        if (used_nodes.find(hostname) == used_nodes.end()) {\n          used_nodes.insert(hostname);\n          const std::string fst_queue = fs->GetQueue();\n          auto it = FsView::gFsView.mNodeView.find(fst_queue);\n\n          if (it != FsView::gFsView.mNodeView.end()) {\n            try {\n              sum += std::stoll(it->second->GetConfigMember(sparam.c_str()));\n            } catch (...) {}\n          }\n        }\n      } else {\n        long long v = fs->GetLongLong(sparam.c_str());\n\n        if (isquery && v && (sparam == \"stat.statfs.capacity\")) {\n          // Correct the capacity(rw) value for headroom\n          v -= fs->GetLongLong(\"headroom\");\n        }\n\n        sum += v;\n      }\n    }\n\n    unique_fs.insert(fs->getSharedFs());\n  }\n\n  return sum;\n}\n//------------------------------------------------------------------------------\n// Computes the sum for <param> as double\n//------------------------------------------------------------------------------\ndouble\nBaseView::SumDouble(const char* param, bool lock,\n                    const std::set<eos::common::FileSystem::fsid_t>* subset)\n{\n  eos::common::RWMutexReadLock fs_rd_lock;\n\n  if (lock) {\n    fs_rd_lock.Grab(FsView::gFsView.ViewMutex);\n  }\n\n  double sum = 0;\n  fsid_iterator it(subset, this);\n\n  for (; it.valid(); it.next()) {\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it);\n\n    if (fs) {\n      sum += fs->GetDouble(param);\n    }\n  }\n\n  return sum;\n}\n//------------------------------------------------------------------------------\n// Computes the average for <param>\n//------------------------------------------------------------------------------\n// @todo (esindril) The lock parameter should be removed as this function is\n// never called without the lock taken\ndouble\nBaseView::AverageDouble(const char* param, bool lock,\n                        const std::set<eos::common::FileSystem::fsid_t>* subset)\n{\n  eos::common::RWMutexReadLock fs_rd_lock;\n\n  if (lock) {\n    fs_rd_lock.Grab(FsView::gFsView.ViewMutex);\n  }\n\n  double sum = 0;\n  int cnt = 0;\n  fsid_iterator it(subset, this);\n\n  for (; it.valid(); it.next()) {\n    bool consider = true;\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it);\n\n    if (fs == nullptr) {\n      continue;\n    }\n\n    if (mType == \"groupview\") {\n      consider = ConsiderForStatistics(fs);\n    }\n\n    if (consider) {\n      cnt++;\n      sum += fs->GetDouble(param);\n    }\n  }\n\n  return (cnt) ? (double)(1.0 * sum / cnt) : 0;\n}\n//------------------------------------------------------------------------------\n// Computes the maximum absolute deviation of <param> from the avg of <param>\n//------------------------------------------------------------------------------\ndouble\nBaseView::MaxAbsDeviation(const char* param, bool lock,\n                          const std::set<eos::common::FileSystem::fsid_t>* subset)\n{\n  eos::common::RWMutexReadLock fs_rd_lock;\n\n  if (lock) {\n    fs_rd_lock.Grab(FsView::gFsView.ViewMutex);\n  }\n\n  double avg = AverageDouble(param, false);\n  double maxabsdev = 0;\n  double dev = 0;\n  fsid_iterator it(subset, this);\n\n  for (; it.valid(); it.next()) {\n    bool consider = true;\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it);\n\n    if (fs == nullptr) {\n      continue;\n    }\n\n    if (mType == \"groupview\") {\n      consider = ConsiderForStatistics(fs);\n    }\n\n    if (consider) {\n      dev = fabs(avg - fs->GetDouble(param));\n\n      if (dev > maxabsdev) {\n        maxabsdev = dev;\n      }\n    }\n  }\n\n  return maxabsdev;\n}\n//------------------------------------------------------------------------------\n// Computes the maximum deviation of <param> from the avg of <param>\n//------------------------------------------------------------------------------\ndouble\nBaseView::MaxDeviation(const char* param, bool lock,\n                       const std::set<eos::common::FileSystem::fsid_t>* subset)\n{\n  eos::common::RWMutexReadLock fs_rd_lock;\n\n  if (lock) {\n    fs_rd_lock.Grab(FsView::gFsView.ViewMutex);\n  }\n\n  double avg = AverageDouble(param, false);\n  double maxdev = -DBL_MAX;\n  double dev = 0;\n  fsid_iterator it(subset, this);\n\n  for (; it.valid(); it.next()) {\n    bool consider = true;\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it);\n\n    if (fs == nullptr) {\n      continue;\n    }\n\n    if (mType == \"groupview\") {\n      consider = ConsiderForStatistics(fs);\n    }\n\n    if (consider) {\n      dev = -(avg - fs->GetDouble(param));\n\n      if (dev > maxdev) {\n        maxdev = dev;\n      }\n    }\n  }\n\n  return maxdev;\n}\n//------------------------------------------------------------------------------\n// Computes the minimum deviation of <param> from the avg of <param>\n//------------------------------------------------------------------------------\ndouble\nBaseView::MinDeviation(const char* param, bool lock,\n                       const std::set<eos::common::FileSystem::fsid_t>* subset)\n{\n  eos::common::RWMutexReadLock fs_rd_lock;\n\n  if (lock) {\n    fs_rd_lock.Grab(FsView::gFsView.ViewMutex);\n  }\n\n  double avg = AverageDouble(param, false);\n  double mindev = DBL_MAX;\n  double dev = 0;\n  fsid_iterator it(subset, this);\n\n  for (; it.valid(); it.next()) {\n    bool consider = true;\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it);\n\n    if (fs == nullptr) {\n      continue;\n    }\n\n    if (mType == \"groupview\") {\n      consider = ConsiderForStatistics(fs);\n    }\n\n    if (consider) {\n      dev = -(avg - fs->GetDouble(param));\n\n      if (dev < mindev) {\n        mindev = dev;\n      }\n    }\n  }\n\n  return mindev;\n}\n//------------------------------------------------------------------------------\n// Computes the sigma for <param>\n//------------------------------------------------------------------------------\ndouble\nBaseView::SigmaDouble(const char* param, bool lock,\n                      const std::set<eos::common::FileSystem::fsid_t>* subset)\n{\n  eos::common::RWMutexReadLock fs_rd_lock;\n\n  if (lock) {\n    fs_rd_lock.Grab(FsView::gFsView.ViewMutex);\n  }\n\n  double avg = AverageDouble(param, false);\n  double sumsquare = 0;\n  int cnt = 0;\n  fsid_iterator it(subset, this);\n\n  for (; it.valid(); it.next()) {\n    bool consider = true;\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it);\n\n    if (fs == nullptr) {\n      continue;\n    }\n\n    if (mType == \"groupview\") {\n      consider = ConsiderForStatistics(fs);\n    }\n\n    if (consider) {\n      cnt++;\n      sumsquare += pow((avg - fs->GetDouble(param)), 2);\n    }\n  }\n\n  sumsquare = (cnt) ? sqrt(sumsquare / cnt) : 0;\n  return sumsquare;\n}\n//------------------------------------------------------------------------------\n// Computes the considered count\n//------------------------------------------------------------------------------\nlong long\nBaseView::ConsiderCount(bool lock,\n                        const std::set<eos::common::FileSystem::fsid_t>* subset)\n{\n  eos::common::RWMutexReadLock fs_rd_lock;\n\n  if (lock) {\n    fs_rd_lock.Grab(FsView::gFsView.ViewMutex);\n  }\n\n  long long cnt = 0;\n  fsid_iterator it(subset, this);\n\n  for (; it.valid(); it.next()) {\n    bool consider = true;\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it);\n\n    if (fs == nullptr) {\n      continue;\n    }\n\n    if (mType == \"groupview\") {\n      consider = ConsiderForStatistics(fs);\n    }\n\n    if (consider) {\n      cnt++;\n    }\n  }\n\n  return cnt;\n}\n//------------------------------------------------------------------------------\n// Print user defined format to out\n//\n// table_format\n//-------------\n// format has to be provided as a chain (separated by \"|\" ) of the following tags\n// \"member=<key>:width=<width>:format=[+][-][so]:unit=<unit>:tag=<tag>\"\n//  -> to print a member variable of the view\n// \"avg=<key>:width=<width>:format=[fo]\"   -> to print the average\n// \"sum=<key>:width=<width>:format=[lo]    -> to print a sum\n// \"sig=<key>:width=<width>:format=[lo]    -> to print the standard deviation\n// \"maxdev=<key>:width=<width>;format=[lo] -> to print the maxdeviation\n// \"tag=<tag>\"                             -> use tag as header not the variable name\n// \"header=1\" -> put a header with description on top!\n//               This must be the first format tag!!!\n//\n// table_mq_format\n//-----------\n// format has to be provided as a chain (separated by \"|\" ) of the following tags\n// \"key=<key>:width=<width>:format=[+][-][slfo]:unit=<unit>:tag=<tag>\"\n//  -> to print a key of the attached children\n// \"header=1\" -> put a header with description on top\n//               This must be the first format tag!!!\n// the formats are:\n// 's' : print as string\n// 'S' : print as short string\n// 'l' : print as long long\n// 'f' : print as double\n// 'o' : print as <key>=<val>\n// '-' : left align the printout\n// '+' : convert numbers into k,M,G,T,P ranges\n// the unit is appended to every number:\n// e.g. 1500 with unit=B would end up as '1.5 kB'\n// the command only appends to <out> and DOES NOT initialize it\n// \"tag=<tag>\" -> use tag as header not the variable name\n//------------------------------------------------------------------------------\nvoid\nBaseView::Print(TableFormatterBase& table, std::string table_format,\n                const std::string& table_mq_format, unsigned outdepth,\n                const std::string& filter, const bool dont_color)\n{\n  // Since we don't display the members with geodepth option, we proceed with\n  // the non geodepth display first.\n  if (outdepth > 0) {\n    Print(table, table_format, table_mq_format, 0, filter, dont_color);\n\n    // We force-print the header\n    if (table_format.find(\"header=1\") == std::string::npos) {\n      if (table_format.find(\"header=0\") != std::string::npos) {\n        table_format.replace(table_format.find(\"header=0\"), 8, \"header=1\");\n      }\n\n      table_format = \"header=1:\" + table_format;\n    }\n  }\n\n  std::vector<std::string> formattoken;\n  class DoubleAggregatedStats : public std::map<std::string, DoubleAggregator*>\n  {\n    BaseView* pThis;\n\n  public:\n    DoubleAggregatedStats(BaseView* This) : pThis(This) {}\n    DoubleAggregator* operator[](const char* param)\n    {\n      if (!count(param)) {\n        DoubleAggregator* aggreg = new DoubleAggregator(param);\n        aggreg->setView(pThis);\n        pThis->runAggregator(aggreg);\n        insert(std::make_pair(param, aggreg));\n      }\n\n      return find(param)->second;\n    }\n\n    ~DoubleAggregatedStats()\n    {\n      for (auto it = begin(); it != end(); it++) {\n        delete it->second;\n      }\n    }\n  };\n  class LongLongAggregatedStats : public\n    std::map<std::string, LongLongAggregator*>\n  {\n    BaseView* pThis;\n  public:\n    LongLongAggregatedStats(BaseView* This) : pThis(This) {}\n\n    LongLongAggregator* operator[](const char* param)\n    {\n      if (!count(param)) {\n        LongLongAggregator* aggreg = new LongLongAggregator(param);\n        aggreg->setView(pThis);\n        pThis->runAggregator(aggreg);\n        insert(std::make_pair(param, aggreg));\n      }\n\n      return find(param)->second;\n    }\n\n    ~LongLongAggregatedStats()\n    {\n      for (auto it = begin(); it != end(); it++) {\n        delete it->second;\n      }\n    }\n  };\n  LongLongAggregatedStats longStats(this);\n  DoubleAggregatedStats doubleStats(this);\n  unsigned int nLines = 0;\n\n  if (outdepth > 0) {\n    nLines = longStats[\"lastHeartBeat\"]->getGeoTags()->size();\n    nLines = longStats[\"lastHeartBeat\"]->getEndIndex(outdepth);\n  } else {\n    nLines = 1;\n  }\n\n  eos::common::StringConversion::Tokenize(table_format, formattoken, \"|\");\n  TableHeader table_header;\n  TableData table_data;\n  TableHeader table_mq_header;\n  TableData table_mq_data;\n\n  for (unsigned int l = 0; l < nLines; l++) {\n    table_data.emplace_back();\n    table_header.clear();\n\n    for (unsigned int i = 0; i < formattoken.size(); i++) {\n      std::vector<std::string> tagtoken;\n      std::map<std::string, std::string> formattags;\n      eos::common::StringConversion::Tokenize(formattoken[i], tagtoken, \":\");\n\n      for (unsigned int j = 0; j < tagtoken.size(); j++) {\n        std::vector<std::string> keyval;\n        eos::common::StringConversion::Tokenize(tagtoken[j], keyval, \"=\");\n\n        if (keyval.size() != 2) {\n          eos_static_err(\"failed parsing \\\"%s\\\", expected 2 tokens\");\n          continue;\n        }\n\n        formattags[keyval[0]] = keyval[1];\n      }\n\n      // To save display space, we don't print out members with geodepth option\n      if (outdepth > 0 && formattags.count(\"member\")) {\n        continue;\n      }\n\n      if (formattags.count(\"format\")) {\n        std::string header = \"\";\n        std::string format = formattags[\"format\"];\n        unsigned int width = (formattags.count(\"width\") ?\n                              atoi(formattags[\"width\"].c_str()) : 0);\n        std::string unit = (formattags.count(\"unit\") ? formattags[\"unit\"] : \"\");\n\n        if (formattags.count(\"geosched\")) {\n          if (formattags[\"geosched\"] == \"totalspace\") {\n            std::string nogroup;\n            table_data.back().push_back(\n              TableCell((long long)gOFS->mGeoTreeEngine->placementSpace(mName, nogroup),\n                        format, unit));\n            table_header.push_back(std::make_tuple(\"sched.capacity\", width, format));\n          }\n        }\n\n        // Normal member printout\n        if (formattags.count(\"member\")) {\n          if ((format.find(\"+\") != std::string::npos) &&\n              (format.find(\"s\") == std::string::npos)) {\n            table_data.back().push_back(\n              TableCell(strtoll(GetMember(formattags[\"member\"]).c_str(), 0, 10),\n                        format, unit));\n          } else {\n            std::string member = GetMember(formattags[\"member\"]).c_str();\n\n            if ((format.find(\"S\") != std::string::npos)) {\n              size_t colon = member.find(\":\");\n              size_t dot = member.find(\".\");\n\n              if (dot != std::string::npos) {\n                member.erase(dot, (colon != std::string::npos) ? colon - dot : colon);\n              }\n            }\n\n            table_data.back().push_back(TableCell(member, format));\n          }\n\n          // Header\n          XrdOucString pkey = formattags[\"member\"].c_str();\n\n          if ((format.find(\"o\") == std::string::npos)) { //for table output\n            pkey.replace(\"stat.statfs.\", \"\");\n            pkey.replace(\"stat.\", \"\");\n            pkey.replace(\"cfg.\", \"\");\n\n            if (formattags.count(\"tag\")) {\n              pkey = formattags[\"tag\"].c_str();\n            }\n          }\n\n          header = pkey.c_str();\n        }\n\n        // Compution\n        if (formattags.count(\"compute\")) {\n          if (formattags[\"compute\"] == \"usage\") {\n            // compute the percentage usage\n            long long used_bytes = SumLongLong(\"stat.statfs.usedbytes\", false);\n            long long headroom = SumLongLong(\"headroom\", false);\n            long long capacity = strtoull(GetMember(\"cfg.nominalsize\").c_str(), 0, 10);\n            std::string format = formattags[\"format\"];\n            unsigned int width = (formattags.count(\"width\") ?\n                                  atoi(formattags[\"width\"].c_str()) : 0);\n            std::string unit = (formattags.count(\"unit\") ? formattags[\"unit\"] : \"\");\n            std::string header = \"usage\";\n            if (formattags.count(\"tag\")) {\n              header = formattags[\"tag\"];\n            }\n\n            table_header.push_back(std::make_tuple(header, width, format));\n\n            if (!capacity) {\n              capacity = SumLongLong(\"stat.statfs.capacity?configstatus@rw\", false);\n            }\n\n            double usage = 0;\n\n            if (capacity) {\n              usage = 100.0 * (used_bytes + headroom) / (capacity);\n\n              if (usage > 100.0) {\n                usage = 100.0;\n              }\n            }\n\n            table_data.back().push_back(TableCell(usage, format, unit));\n          }\n        }\n\n        // Sum printout\n        if (formattags.count(\"sum\")) {\n          if (!outdepth) {\n            table_data.back().push_back(\n              TableCell(SumLongLong(formattags[\"sum\"].c_str(), false),\n                        format, unit));\n          } else {\n            table_data.back().push_back(\n              TableCell((*longStats[formattags[\"sum\"].c_str()]->getSums())[l],\n                        format, unit));\n          }\n\n          // Header\n          XrdOucString pkey = formattags[\"sum\"].c_str();\n\n          if ((format.find(\"o\") == std::string::npos)) {\n            pkey.replace(\"stat.statfs.\", \"\");\n            pkey.replace(\"stat.\", \"\");\n            pkey.replace(\"cfg.\", \"\");\n\n            if (!formattags.count(\"tag\")) {\n              header = \"sum(\";\n              header += pkey.c_str();\n              header += \")\";\n            } else {\n              header = formattags[\"tag\"].c_str();\n            }\n          } else { //for monitoring output\n            header = \"sum.\";\n            header += pkey.c_str();\n          }\n        }\n\n        // Avg printout\n        if (formattags.count(\"avg\")) {\n          if (formattags[\"avg\"] == \"stat.geotag\") {\n            if (outdepth) {\n              // This average means anything only when displaying along the\n              // topology tree\n              table_data.back().push_back(\n                TableCell((*longStats[\"lastHeartBeat\"]->getGeoTags())[l].c_str(),\n                          format));\n              // Header\n              XrdOucString pkey = formattags[\"avg\"].c_str();\n\n              if ((format.find(\"o\") == std::string::npos)) {\n                pkey.replace(\"stat.statfs.\", \"\");\n                pkey.replace(\"stat.\", \"\");\n                pkey.replace(\"cfg.\", \"\");\n                header = pkey.c_str();\n              } else { //for monitoring output\n                header = \"avg.\";\n                header += pkey.c_str();\n              }\n            }\n          } else if (formattags[\"avg\"] == \"stat.statfs.filled\") {\n            // Handle filled avg seperately!\n            if (!outdepth) {\n              auto used_bytes = SumLongLong(\"stat.statfs.usedbytes\", false);\n              auto capacity = SumLongLong(\"stat.statfs.capacity\", false);\n              double filled = capacity == 0 ? 0 : (double)used_bytes / capacity * 100;\n              table_data.back().push_back(TableCell(filled, format, unit));\n            } else {\n              auto used_bytes = (*longStats[\"stat.statfs.usedbytes\"]->getSums())[l];\n              auto capacity = (*longStats[\"stat.statfs.capacity\"]->getSums())[l];\n              double filled = capacity == 0 ? 0 : (double)used_bytes / capacity * 100;\n              table_data.back().push_back(TableCell(filled, format, unit));\n            }\n\n            XrdOucString pkey = formattags[\"avg\"].c_str();\n\n            if ((format.find(\"o\") == std::string::npos)) {\n              pkey.replace(\"stat.statfs.\", \"\");\n              pkey.replace(\"stat.\", \"\");\n              pkey.replace(\"cfg.\", \"\");\n\n              if (!formattags.count(\"tag\")) {\n                header = \"avg(\";\n                header += pkey.c_str();\n                header += \")\";\n              } else {\n                header = formattags[\"tag\"].c_str();\n              }\n            } else { //for monitoring output\n              header = \"avg.\";\n              header += pkey.c_str();\n            }\n          } else { // If not any of the special cases above\n            if (!outdepth) {\n              table_data.back().push_back(\n                TableCell(AverageDouble(formattags[\"avg\"].c_str(), false),\n                          format, unit));\n            } else {\n              table_data.back().push_back(\n                TableCell((*doubleStats[formattags[\"avg\"].c_str()]->getMeans())[l],\n                          format, unit));\n            }\n\n            // Header\n            XrdOucString pkey = formattags[\"avg\"].c_str();\n\n            if ((format.find(\"o\") == std::string::npos)) {\n              pkey.replace(\"stat.statfs.\", \"\");\n              pkey.replace(\"stat.\", \"\");\n              pkey.replace(\"cfg.\", \"\");\n\n              if (!formattags.count(\"tag\")) {\n                header = \"avg(\";\n                header += pkey.c_str();\n                header += \")\";\n              } else {\n                header = formattags[\"tag\"].c_str();\n              }\n            } else { //for monitoring output\n              header = \"avg.\";\n              header += pkey.c_str();\n            }\n          } // end not geotag case\n        }\n\n        // Sig printout\n        if (formattags.count(\"sig\")) {\n          if (!outdepth) {\n            table_data.back().push_back(\n              TableCell(SigmaDouble(formattags[\"sig\"].c_str(), false),\n                        format, unit));\n          } else {\n            table_data.back().push_back(\n              TableCell((*doubleStats[formattags[\"sig\"].c_str()]->getStdDevs())[l],\n                        format, unit));\n          }\n\n          // Header\n          XrdOucString pkey = formattags[\"sig\"].c_str();\n\n          if ((format.find(\"o\") == std::string::npos)) {\n            pkey.replace(\"stat.statfs.\", \"\");\n            pkey.replace(\"stat.\", \"\");\n            pkey.replace(\"cfg.\", \"\");\n\n            if (!formattags.count(\"tag\")) {\n              header = \"sig(\";\n              header += pkey.c_str();\n              header += \")\";\n            } else {\n              header = formattags[\"tag\"].c_str();\n            }\n          } else { //for monitoring output\n            header = \"sig.\";\n            header += pkey.c_str();\n          }\n        }\n\n        // MaxDev printout\n        if (formattags.count(\"maxdev\")) {\n          if (!outdepth) {\n            table_data.back().push_back(\n              TableCell(MaxAbsDeviation(formattags[\"maxdev\"].c_str(), false),\n                        format, unit));\n          } else {\n            table_data.back().push_back(\n              TableCell((*doubleStats[formattags[\"maxdev\"].c_str()]->getMaxAbsDevs())[l],\n                        format, unit));\n          }\n\n          // Header\n          XrdOucString pkey = formattags[\"maxdev\"].c_str();\n\n          if ((format.find(\"o\") == std::string::npos)) {\n            pkey.replace(\"stat.statfs.\", \"\");\n            pkey.replace(\"stat.\", \"\");\n            pkey.replace(\"cfg.\", \"\");\n\n            if (!formattags.count(\"tag\")) {\n              header = \"dev(\";\n              header += pkey.c_str();\n              header += \")\";\n            } else {\n              header = formattags[\"tag\"].c_str();\n            }\n          } else { //for monitoring output\n            header = \"dev.\";\n            header += pkey.c_str();\n          }\n        }\n\n        // Build header\n        if (!header.empty()) {\n          table_header.push_back(std::make_tuple(header, width, format));\n        }\n      }\n    }\n  } // l from 0 to nLines\n\n  if (outdepth > 0) {\n    // Print table for geotag\n    TableFormatterBase table_geo(dont_color);\n    table_geo.SetHeader(table_header);\n    table_geo.AddRows(table_data);\n    table.AddString(table_geo.GenerateTable(HEADER).c_str());\n  } else {\n    //Get table from MQ side (second table)\n    if (table_mq_format.length()) {\n      // If a format was given for the filesystem children, forward it\n      for (auto it = begin(); it != end(); ++it) {\n        // auto it_fs = FsView::gFsView.mIdView.find(*it);\n        FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it);\n\n        if (fs) {\n          table_mq_header.clear();\n          fs->Print(table_mq_header, table_mq_data, table_mq_format, filter);\n        }\n      }\n    }\n\n    // Print table with information from MGM\n    if (table_format.length() && !table_mq_format.length()) {\n      table.SetHeader(table_header);\n      table.AddRows(table_data);\n    }\n\n    // Print table with information from MGM and MQ. (Option \"-l\")\n    if (table_format.length() && table_mq_format.length()) {\n      table.SetHeader(table_header);\n      table.AddRows(table_data);\n      TableFormatterBase table_mq(dont_color);\n      table_mq.SetHeader(table_mq_header);\n      table_mq.AddRows(table_mq_data);\n      table.AddString(table_mq.GenerateTable(HEADER).c_str());\n    }\n\n    // Print table with information only from MQ. (e.g. \"fs ls\")\n    if (!table_format.length() && table_mq_format.length()) {\n      table.SetHeader(table_mq_header);\n      table.AddSeparator();\n      table.AddRows(table_mq_data);\n    }\n  }\n}\n//------------------------------------------------------------------------------\n// If a filesystem has not yet these parameters defined, we inherit them from\n// the space configuration. This function has to be called with the a read lock\n// on the View Mutex! It return true if the fs was modified and the caller should\n// evt. store the modification to the config\n//------------------------------------------------------------------------------\nbool\nFsSpace::ApplySpaceDefaultParameters(eos::mgm::FileSystem* fs, bool force)\n{\n  if (!fs) {\n    return false;\n  }\n\n  bool modified = false;\n  eos::common::FileSystem::fs_snapshot_t snapshot;\n\n  if (fs->SnapShotFileSystem(snapshot, false)) {\n    if (force || (!snapshot.mScanIoRate)) {\n      if (GetConfigMember(eos::common::SCAN_IO_RATE_NAME).length()) {\n        fs->SetString(eos::common::SCAN_IO_RATE_NAME,\n                      GetConfigMember(eos::common::SCAN_IO_RATE_NAME).c_str());\n        modified = true;\n      }\n    }\n\n    if (force || (!snapshot.mScanEntryInterval)) {\n      // try to apply the default\n      if (GetConfigMember(eos::common::SCAN_ENTRY_INTERVAL_NAME).length()) {\n        modified = true;\n        fs->SetString(eos::common::SCAN_ENTRY_INTERVAL_NAME,\n                      GetConfigMember(eos::common::SCAN_ENTRY_INTERVAL_NAME).c_str());\n      }\n    }\n\n    if (force || (!snapshot.mScanRainEntryInterval)) {\n      if (GetConfigMember(eos::common::SCAN_RAIN_ENTRY_INTERVAL_NAME).length()) {\n        modified = true;\n        fs->SetString(eos::common::SCAN_RAIN_ENTRY_INTERVAL_NAME,\n                      GetConfigMember(eos::common::SCAN_RAIN_ENTRY_INTERVAL_NAME).c_str());\n      }\n    }\n\n    if (force || (!snapshot.mScanDiskInterval)) {\n      if (GetConfigMember(eos::common::SCAN_DISK_INTERVAL_NAME).length()) {\n        modified = true;\n        fs->SetString(eos::common::SCAN_DISK_INTERVAL_NAME,\n                      GetConfigMember(eos::common::SCAN_DISK_INTERVAL_NAME).c_str());\n      }\n    }\n\n    if (force || (!snapshot.mScanNsInterval)) {\n      if (GetConfigMember(eos::common::SCAN_NS_INTERVAL_NAME).length()) {\n        modified = true;\n        fs->SetString(eos::common::SCAN_NS_INTERVAL_NAME,\n                      GetConfigMember(eos::common::SCAN_NS_INTERVAL_NAME).c_str());\n      }\n    }\n\n    if (force || (!snapshot.mScanNsRate)) {\n      if (GetConfigMember(eos::common::SCAN_NS_RATE_NAME).length()) {\n        fs->SetString(eos::common::SCAN_NS_RATE_NAME,\n                      GetConfigMember(eos::common::SCAN_NS_RATE_NAME).c_str());\n        modified = true;\n      }\n    }\n\n    if (force || (!snapshot.mGracePeriod)) {\n      // try to apply the default\n      if (GetConfigMember(\"graceperiod\").length()) {\n        fs->SetString(\"graceperiod\", GetConfigMember(\"graceperiod\").c_str());\n        modified = true;\n      }\n    }\n\n    if (force || (!snapshot.mDrainPeriod)) {\n      // try to apply the default\n      if (GetConfigMember(\"drainperiod\").length()) {\n        fs->SetString(\"drainperiod\", GetConfigMember(\"drainperiod\").c_str());\n        modified = true;\n      }\n    }\n\n    if (force || (!snapshot.mHeadRoom)) {\n      // try to apply the default\n      if (GetConfigMember(\"headroom\").length()) {\n        fs->SetString(\"headroom\", GetConfigMember(\"headroom\").c_str());\n        modified = true;\n      }\n    }\n  }\n\n  return modified;\n}\n//------------------------------------------------------------------------------\n// Re-evaluates the draining state in all groups and resets the state\n//------------------------------------------------------------------------------\nvoid\nFsSpace::ResetDraining()\n{\n  eos_static_info(\"msg=\\\"reset drain state\\\" space=\\\"%s\\\"\", mName.c_str());\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n\n  // Iterate over all groups in this space\n  for (auto sgit = FsView::gFsView.mSpaceGroupView[mName].begin();\n       sgit != FsView::gFsView.mSpaceGroupView[mName].end();\n       sgit++) {\n    bool setactive = false;\n    std::string lGroup = (*sgit)->mName;\n    FsGroup::const_iterator git;\n\n    for (git = (*sgit)->begin();\n         git != (*sgit)->end(); git++) {\n      FileSystem* entry = FsView::gFsView.mIdView.lookupByID(*git);\n\n      if (entry) {\n        eos::common::DrainStatus drainstatus =\n          (eos::common::FileSystem::GetDrainStatusFromString(\n             entry->GetString(\"local.drain\").c_str()));\n\n        if ((drainstatus == eos::common::DrainStatus::kDraining) ||\n            (drainstatus == eos::common::DrainStatus::kDrainStalling)) {\n          // if any mGroup filesystem is draining, all the others have\n          // to enable the pull for draining!\n          setactive = true;\n        }\n      }\n    }\n\n    // if the mGroup get's disabled we stop the draining\n    if (FsView::gFsView.mGroupView[lGroup]->GetConfigMember(\"status\") != \"on\") {\n      setactive = false;\n    }\n\n    for (git = (*sgit)->begin(); git != (*sgit)->end(); git++) {\n      eos::mgm::FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*git);\n\n      if (fs) {\n        if (setactive) {\n          if (fs->GetString(\"local.drainer\") != \"on\") {\n            fs->SetString(\"local.drainer\", \"on\");\n          }\n        } else {\n          if (fs->GetString(\"local.drainer\") != \"off\") {\n            fs->SetString(\"local.drainer\", \"off\");\n          }\n        }\n\n        eos_static_info(\"fsid=%05d state=%s\", fs->GetId(),\n                        fs->GetString(\"local.drainer\").c_str());\n      }\n    }\n  }\n}\n//------------------------------------------------------------------------------\n// Get status of the balancer thread pool\n//------------------------------------------------------------------------------\nstd::string\nFsSpace::GetBalancerPoolInfo() const\n{\n  if (mFsBalancer) {\n    return mFsBalancer->GetThreadPoolInfo() + \" \";\n  }\n\n  return std::string();\n}\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/fsview/FsView.hh",
    "content": "//------------------------------------------------------------------------------\n// File: FsView.hh\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_FSVIEW__HH__\n#define __EOSMGM_FSVIEW__HH__\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/filesystem/FileSystem.hh\"\n#include \"mgm/utils/FilesystemUuidMapper.hh\"\n#include \"mgm/utils/FileSystemRegistry.hh\"\n#include \"common/RWMutex.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Locators.hh\"\n#include \"common/InstanceName.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"qclient/shared/SharedHashSubscription.hh\"\n#include <string_view>\n#ifndef __APPLE__\n#include <sys/vfs.h>\n#else\n#include <sys/param.h>\n#include <sys/mount.h>\n#endif\n\nnamespace eos::common\n{\nclass TransferQueue;\n}\n\n//------------------------------------------------------------------------------\n//! @file FsView.hh\n//! @brief Class representing the cluster configuration of EOS\n//! There are three views on EOS filesystems by space, group and node.\n//------------------------------------------------------------------------------\nEOSMGMNAMESPACE_BEGIN\n\nstruct GeoTreeElement;\nclass GeoTree;\nclass TableFormatterBase;\nclass Balancer;\nclass GroupBalancer;\nclass GroupDrainer;\nclass GeoBalancer;\nclass Converter;\nclass IConfigEngine;\nclass FsBalancer;\nclass FileInspector;\n\n//------------------------------------------------------------------------------\n//! Struct holding file system info for balancing operations\n//------------------------------------------------------------------------------\nstruct FsBalanceInfo {\n  eos::common::FileSystem::fsid_t mFsId; ///< File ssytem id\n  std::string mNodeInfo; ///< FQDN:port\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  FsBalanceInfo() = default;\n\n  //----------------------------------------------------------------------------\n  //! Constructor with parameters\n  //----------------------------------------------------------------------------\n  FsBalanceInfo(eos::common::FileSystem::fsid_t fsid,\n                const std::string& node_host):\n    mFsId(fsid), mNodeInfo(node_host)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Operator < implementation for storing such objects in sets\n  //----------------------------------------------------------------------------\n  bool operator< (const FsBalanceInfo& rhs) const\n  {\n    return mFsId < rhs.mFsId;\n  }\n};\n\n//------------------------------------------------------------------------------\n//! Priority sets of file systems to be used for balancing operations,\n//! containing 4 sets for fs'es with fill ratio in the following intervals:\n//! [0, mean - threshold) - low prio\n//! [mean - threshold, mean) - low\n//! [mean, mean + threshold ) - high\n//! [mean + threshold, 100] - high prio\n//------------------------------------------------------------------------------\nstruct FsPrioritySets {\n  static constexpr double sThreshold = 5.0;\n  std::set<FsBalanceInfo> mPrioLow;\n  std::set<FsBalanceInfo> mLow;\n  std::set<FsBalanceInfo> mHigh;\n  std::set<FsBalanceInfo> mPrioHigh;\n};\n\n//------------------------------------------------------------------------------\n//! Base class representing any element in a GeoTree\n//------------------------------------------------------------------------------\nstruct GeoTreeElement {\n  //------------------------------------------------------------------------------\n  //! Destructor\n  //------------------------------------------------------------------------------\n  ~GeoTreeElement();\n\n  //! Pointer to father node in the tree\n  GeoTreeElement* mFather;\n  //! Tag token for example BBB in AA::BBB:CC\n  std::string mTagToken;\n  //! Full geo tag for example AA::BBB:CC in AA::BBB:CC\n  std::string mFullTag;\n  //! An auxiliary numbering to run the aggregator\n  mutable size_t mId;\n  //! All the FileSystems attached to this node of the tree\n  std::set<eos::common::FileSystem::fsid_t> mFsIds;\n  //! Map geoTreeTag -> son branches\n  std::map<std::string, GeoTreeElement*> mSons;\n};\n\n//------------------------------------------------------------------------------\n//! @brief A helper class to order branches in a GeoTree in the proper display\n//! order.\n//------------------------------------------------------------------------------\nclass GeoTreeNodeOrderHelper\n{\npublic:\n  bool operator()(const GeoTreeElement* const& left,\n                  const GeoTreeElement* const& right) const\n  {\n    return (left->mFullTag > right->mFullTag);\n  }\n};\n\n//------------------------------------------------------------------------------\n//! Base class representing a functor to compute statistics along a GeoTree\n//------------------------------------------------------------------------------\nclass GeoTreeAggregator\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~GeoTreeAggregator() = default;\n\n  // Initialize the aggregator\n  virtual bool init(const std::vector<std::string>& geotags,\n                    const std::vector<size_t>& depthLevelsIndexes) = 0;\n\n  // Aggregate the leaves at the last level of the tree\n  virtual bool aggregateLeaves(const std::set<eos::common::FileSystem::fsid_t>&\n                               leaves, const size_t& idx) = 0;\n\n  //----------------------------------------------------------------------------\n  // Aggregate the nodes at intermediate levels\n  // WARNING target node might be part of the nodes to aggregate.\n  // Careful before overwriting the target node.\n  //----------------------------------------------------------------------------\n  virtual bool aggregateNodes(const std::map<std::string, GeoTreeElement*>&\n                              nodes,\n                              const size_t& idx, bool includeSelf = false) = 0;\n\n  // Aggregate the leaves and the nodes at any level of the tree\n  virtual bool aggregateLeavesAndNodes(\n    const std::set<eos::common::FileSystem::fsid_t>& leaves,\n    const std::map<std::string, GeoTreeElement*>& nodes,\n    const size_t& idx)\n  {\n    return (leaves.empty() ? true : aggregateLeaves(leaves, idx))\n           && (nodes.empty() ? true : aggregateNodes(nodes, idx, !leaves.empty()));\n  }\n};\n\n//------------------------------------------------------------------------------\n//! Class representing a tree-structured set of fsids\n//------------------------------------------------------------------------------\nclass GeoTree\n{\n  typedef eos::common::FileSystem::fsid_t fsid_t;\n\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  GeoTree();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~GeoTree();\n\n  //----------------------------------------------------------------------------\n  //! Insert a file system into the tree\n  //!\n  //! @parma fs file system id\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool insert(const fsid_t& fs);\n\n  //----------------------------------------------------------------------------\n  //! Remove a file system from the tree\n  //!\n  //! @parma fs file system id\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool erase(const fsid_t& fs);\n\n  //----------------------------------------------------------------------------\n  //! Get the geotag at which the fs is stored if found\n  //!\n  //! @param fs file system id\n  //! @param geoTag returned geotag if fs found\n  //!\n  // @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool getGeoTagInTree(const fsid_t& fs, std::string& geoTag);\n\n  //----------------------------------------------------------------------------\n  //! Get number of file systems in the tree\n  //----------------------------------------------------------------------------\n  size_t size() const;\n\n  //----------------------------------------------------------------------------\n  //! Run an aggregator through the tree\n  //! @note At any depth level, the aggregator is fed ONLY with the data of\n  //! the ONE deeper level in the tree\n  //!\n  //! @param aggregator the aggregator to be run\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool runAggregator(GeoTreeAggregator* aggregator) const;\n\n  //----------------------------------------------------------------------------\n  //! STL const_iterator class\n  //!\n  //! Only the leaves are iterated in alphabetical order of their geotag\n  //----------------------------------------------------------------------------\n  class const_iterator: public\n    std::iterator<std::bidirectional_iterator_tag, fsid_t>\n  {\n    friend class GeoTree;\n    using ContainerT = std::map<fsid_t, GeoTreeElement*>;\n    ContainerT::const_iterator mIt; ///< Iterator inside the container\n    ContainerT* mCont; ///< Pointer to original container\n  public:\n    //--------------------------------------------------------------------------\n    //! Default constructor\n    //--------------------------------------------------------------------------\n    const_iterator() = default;\n\n    //--------------------------------------------------------------------------\n    //! Constructor\n    //!\n    //! @param iterator inside map\n    //--------------------------------------------------------------------------\n    const_iterator(ContainerT::const_iterator it, ContainerT& cont):\n      mIt(it), mCont(&cont) {}\n\n    //--------------------------------------------------------------------------\n    //! Destructor\n    //--------------------------------------------------------------------------\n    ~const_iterator() = default;\n\n    //--------------------------------------------------------------------------\n    //! Copy assignment operator\n    //--------------------------------------------------------------------------\n    const_iterator& operator= (const const_iterator& it);\n\n    //--------------------------------------------------------------------------\n    //! Copy constructor\n    //--------------------------------------------------------------------------\n    const_iterator(const const_iterator& it)\n    {\n      *this = it;\n    }\n\n    //--------------------------------------------------------------------------\n    //! Pre-increment\n    //--------------------------------------------------------------------------\n    const_iterator& operator++();\n\n    //--------------------------------------------------------------------------\n    //! Post-increment\n    //--------------------------------------------------------------------------\n    const_iterator operator++(int);\n\n    //--------------------------------------------------------------------------\n    //! Pre-decrement\n    //--------------------------------------------------------------------------\n    const_iterator& operator--();\n\n    //--------------------------------------------------------------------------\n    //! Post-decrement\n    //--------------------------------------------------------------------------\n    const_iterator operator--(int);\n\n    //--------------------------------------------------------------------------\n    //! Indirection operator\n    //--------------------------------------------------------------------------\n    const eos::common::FileSystem::fsid_t& operator*() const;\n\n    //--------------------------------------------------------------------------\n    // Inequality operator\n    //--------------------------------------------------------------------------\n    inline bool operator !=(const const_iterator& rhs) const\n    {\n      return mIt != rhs.mIt;\n    }\n\n    //--------------------------------------------------------------------------\n    // Equality operator\n    //--------------------------------------------------------------------------\n    inline bool operator ==(const const_iterator& rhs) const\n    {\n      return mIt == rhs.mIt;\n    }\n  };\n\n  //----------------------------------------------------------------------------\n  // begin()\n  //----------------------------------------------------------------------------\n  inline const_iterator begin() const\n  {\n    const_iterator it(pLeaves.begin(), pLeaves);\n    return it;\n  }\n\n  //----------------------------------------------------------------------------\n  // cbegin()\n  //----------------------------------------------------------------------------\n  inline const_iterator cbegin() const\n  {\n    return begin();\n  }\n\n  //----------------------------------------------------------------------------\n  // end()\n  //----------------------------------------------------------------------------\n  inline const_iterator end() const\n  {\n    const_iterator it(pLeaves.end(), pLeaves);\n    return it;\n  }\n\n  //----------------------------------------------------------------------------\n  // cend()\n  //----------------------------------------------------------------------------\n  inline const_iterator cend() const\n  {\n    return end();\n  }\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Get file system geotag\n  //!\n  //! @param fs file system id\n  //!\n  //! @return geotag\n  //----------------------------------------------------------------------------\n  std::string getGeoTag(const fsid_t& fs) const;\n\n  GeoTreeElement* pRoot;   ///< The root branch of the tree\n  //! All the elements of the tree collected by depth\n  std::vector<std::set<GeoTreeElement*, GeoTreeNodeOrderHelper > > pLevels;\n  mutable std::map<fsid_t, GeoTreeElement*>\n  pLeaves; ///< All the leaves of the tree\n};\n\n//------------------------------------------------------------------------------\n//! Class representing a grouped set of filesystems\n//------------------------------------------------------------------------------\nclass BaseView : public GeoTree\n{\npublic:\n  std::string mName; ///< Name of the base view\n  std::string mType; ///< type of the base view\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  BaseView(const common::SharedHashLocator& locator):\n    mLocator(locator), mHeartBeat(0), mStatus(\"unknown\")\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~BaseView() = default;\n\n  //----------------------------------------------------------------------------\n  //! Return a member variable in the view\n  //----------------------------------------------------------------------------\n  virtual std::string GetMember(const std::string& member) const;\n\n  //----------------------------------------------------------------------------\n  //! Return a configuration member\n  //----------------------------------------------------------------------------\n  virtual std::string GetConfigMember(std::string key) const;\n\n  //----------------------------------------------------------------------------\n  //! Return a map of configuration values given a vector of keys\n  //----------------------------------------------------------------------------\n  virtual bool GetConfigMembers(const std::vector<std::string>& keys,\n                                std::map<std::string, std::string>& out) const;\n\n  //----------------------------------------------------------------------------\n  //! Delete a configuration member\n  //----------------------------------------------------------------------------\n  virtual bool DeleteConfigMember(std::string key) const;\n\n  //----------------------------------------------------------------------------\n  //! Print the view contents\n  //!\n  //! @param table table info\n  //! @param table_format format for table from MGM side\n  //! @param table_mq_format format for table from MQ side\n  //! @param outdepth ouput depth for geoscheduling\n  //! @param filter view filter\n  //----------------------------------------------------------------------------\n  void Print(TableFormatterBase& table, std::string table_format,\n             const std::string& table_mq_format, unsigned outdepth,\n             const std::string& filter = \"\", const bool dont_color = false);\n\n  //----------------------------------------------------------------------------\n  //! Return all configuration keys\n  //----------------------------------------------------------------------------\n  bool GetConfigKeys(std::vector<std::string>& keys);\n\n  //----------------------------------------------------------------------------\n  //! Set the heartbeat time\n  //! @param hb heart beat time to set\n  //----------------------------------------------------------------------------\n  void SetHeartBeat(time_t hb)\n  {\n    mHeartBeat = hb;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the heartbeat timestamp\n  //----------------------------------------------------------------------------\n  time_t GetHeartBeat()\n  {\n    return mHeartBeat;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set the status\n  //! @param status status to set\n  //----------------------------------------------------------------------------\n  void SetStatus(const std::string& status)\n  {\n    std::scoped_lock<std::mutex> lock(mMutex);\n    mStatus = status;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the status\n  //----------------------------------------------------------------------------\n  const std::string GetStatus() const\n  {\n    std::scoped_lock<std::mutex> lock(mMutex);\n    return mStatus;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Should the provided fsid participate in statistics calculations?\n  //! Yes, if:\n  //! - The filesystem exists (duh)\n  //! - The filesystem is at-least-RO, booted and online\n  //!\n  //! Call with fsview lock at-least-read locked.\n  //----------------------------------------------------------------------------\n  static bool ConsiderForStatistics(FileSystem* fs);\n\n  //----------------------------------------------------------------------------\n  //! Calculate the sum of <param> as long long\n  //----------------------------------------------------------------------------\n  long long\n  SumLongLong(const char* param, bool lock = true,\n              const std::set<eos::common::FileSystem::fsid_t>* subset = nullptr);\n\n  //----------------------------------------------------------------------------\n  //! Calculate the sum of <param> as double\n  //----------------------------------------------------------------------------\n  double\n  SumDouble(const char* param, bool lock = true,\n            const std::set<eos::common::FileSystem::fsid_t>* subset = nullptr);\n\n  //----------------------------------------------------------------------------\n  //! Calculates the average of <param> as double\n  //----------------------------------------------------------------------------\n  double\n  AverageDouble(const char* param, bool lock = true,\n                const std::set<eos::common::FileSystem::fsid_t>* subset = nullptr);\n\n  //----------------------------------------------------------------------------\n  //! Calculates the maximum deviation from the average in a group\n  //---------------------------------------------------------------------------\n  virtual double\n  MaxAbsDeviation(const char* param, bool lock = true,\n                  const std::set<eos::common::FileSystem::fsid_t>* subset = nullptr);\n\n  double\n  MaxDeviation(const char* param, bool lock = true,\n               const std::set<eos::common::FileSystem::fsid_t>* subset = nullptr);\n\n  double\n  MinDeviation(const char* param, bool lock = true,\n               const std::set<eos::common::FileSystem::fsid_t>* subset = nullptr);\n\n  //----------------------------------------------------------------------------\n  //! Calculates the standard deviation of <param> as double\n  //----------------------------------------------------------------------------\n  double\n  SigmaDouble(const char* param, bool lock = true,\n              const std::set<eos::common::FileSystem::fsid_t>* subset = nullptr);\n\n  //----------------------------------------------------------------------------\n  //! Calculates the number of fsid considered for average\n  //----------------------------------------------------------------------------\n  long long\n  ConsiderCount(bool lock,\n                const std::set<eos::common::FileSystem::fsid_t>* subset);\n\n  //----------------------------------------------------------------------------\n  //! Set a member variable in a view\n  //----------------------------------------------------------------------------\n  bool SetConfigMember(std::string key, std::string value,\n                       bool isstatus = false);\n\nprotected:\n  common::SharedHashLocator mLocator; ///< Locator for shared hash\n  std::atomic<time_t> mHeartBeat; ///< Last heartbeat time\n\nprivate:\n  mutable std::mutex mMutex; ///< Mutex to protect status information\n  std::string mStatus; ///< Status (meaning depends on inheritor)\n  std::string mSize; ///< Size of base object (meaning depends on inheritor)\n};\n\n//------------------------------------------------------------------------------\n//! Class FsSpace describing a space (set of filesystems)\n//------------------------------------------------------------------------------\nclass FsSpace: public BaseView\n{\npublic:\n  //! Set when a configuration gets loaded to avoid overwriting of the loaded\n  //! values by default values\n  static std::atomic<bool> gDisableDefaults;\n  static std::string gConfigQueuePrefix; ///<  Configuration queue prefix\n  //! File system balancer\n  std::unique_ptr<FsBalancer> mFsBalancer;\n  //! Threaded object running group balancing\n  GroupBalancer* mGroupBalancer;\n  //! Threaded object running geotag balancing\n  GeoBalancer* mGeoBalancer;\n  //! Threaded object running group drainer\n  std::unique_ptr<GroupDrainer>  mGroupDrainer;\n  //! Space file inspector\n  std::unique_ptr<FileInspector> mFileInspector;\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param name name of the space to construct\n  //----------------------------------------------------------------------------\n  FsSpace(const char* name);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~FsSpace();\n\n  //----------------------------------------------------------------------------\n  //! Stop function stopping threads before destruction\n  //----------------------------------------------------------------------------\n  void Stop();\n\n  //----------------------------------------------------------------------------\n  //! Apply the default space parameters\n  //----------------------------------------------------------------------------\n  bool ApplySpaceDefaultParameters(eos::mgm::FileSystem* fs, bool force = false);\n\n  //----------------------------------------------------------------------------\n  //! Reset the Drain state - to be removed?!\n  //----------------------------------------------------------------------------\n  void ResetDraining();\n\n  //----------------------------------------------------------------------------\n  //! Get status of the balancer thread pool\n  //----------------------------------------------------------------------------\n  std::string GetBalancerPoolInfo() const;\n};\n\n//------------------------------------------------------------------------------\n//! Class FsGroup describing a group (set of filesystems)\n//------------------------------------------------------------------------------\nclass FsGroup : public BaseView\n{\n  friend class FsView;\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //! @param name name of the group e.g. 'default.0'\n  //----------------------------------------------------------------------------\n  FsGroup(const char* name)\n    : BaseView(common::SharedHashLocator::makeForGroup(name)),\n      mIndex(0)\n  {\n    mName = name;\n    mType = \"groupview\";\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~FsGroup() = default;\n\n  //----------------------------------------------------------------------------\n  //! Return index of the group\n  //----------------------------------------------------------------------------\n  unsigned int GetIndex()\n  {\n    return mIndex;\n  }\n\nprotected:\n  unsigned int mIndex; ///< Group index i.e 0,1,2,3 ...\n};\n\n//------------------------------------------------------------------------------\n//! Class FsNode describing a node (set of filesystems)\n//------------------------------------------------------------------------------\nclass FsNode : public BaseView\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param name nodeview name\n  //----------------------------------------------------------------------------\n  explicit FsNode(const char* name);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~FsNode();\n\n  //----------------------------------------------------------------------------\n  //! Return a member variable\n  //----------------------------------------------------------------------------\n  virtual std::string GetMember(const std::string& name) const override;\n\n  //----------------------------------------------------------------------------\n  //! Get active status\n  //----------------------------------------------------------------------------\n  eos::common::ActiveStatus GetActiveStatus();\n\n  //----------------------------------------------------------------------------\n  //! Set active status\n  //----------------------------------------------------------------------------\n  void SetActiveStatus(eos::common::ActiveStatus active);\n\n  //----------------------------------------------------------------------------\n  //! Set the configuration default values for a node\n  //----------------------------------------------------------------------------\n  void SetNodeConfigDefault();\n\n  //----------------------------------------------------------------------------\n  //! Check if node has a recent enough heartbeat\n  //----------------------------------------------------------------------------\n  bool HasHeartbeat() const;\n\n  //----------------------------------------------------------------------------\n  //! Callback to process update for the shared hash\n  //!\n  //! @param upd SharedHashUpdate object\n  //----------------------------------------------------------------------------\n  void ProcessUpdateCb(qclient::SharedHashUpdate&& upd);\n\n  //----------------------------------------------------------------------------\n  //! Set refresh marker for the FST\n  //----------------------------------------------------------------------------\n  void SignalRefresh();\n\nprivate:\n  static std::string msRefreshTag;\n  std::unique_ptr<qclient::SharedHashSubscription> mSubscription {nullptr};\n};\n\n\n//------------------------------------------------------------------------------\n//! Class ConfigResetMonitor - reset the current configuration engine object\n//! used by the FsView to null during construction and put it back to the\n//! initial value during destruction.\n//------------------------------------------------------------------------------\nclass ConfigResetMonitor final\n{\npublic:\n  //------------------------------------------------------------------------------\n  //! Constructor\n  //------------------------------------------------------------------------------\n  ConfigResetMonitor();\n  //------------------------------------------------------------------------------\n  //! Destructor\n  //------------------------------------------------------------------------------\n  ~ConfigResetMonitor();\n\nprivate:\n  IConfigEngine* mOrigConfEngine; ///< Initial config engine object\n};\n\n//------------------------------------------------------------------------------\n//! Class describing an EOS pool including views\n//------------------------------------------------------------------------------\nclass FsView : public eos::common::LogId\n{\n  friend class ConfigResetMonitor;\n  // @todo (esindril): this is just for the call in SetConfigMember when\n  // accessing mConfigEngine. Should be refactored.\n  friend class BaseView;\npublic:\n  //! Static singleton object hosting the filesystem view object\n  static FsView gFsView;\n\n  //----------------------------------------------------------------------------\n  //! Return printout formats\n  //----------------------------------------------------------------------------\n  static std::string GetNodeFormat(std::string option);\n  static std::string GetGroupFormat(std::string option);\n  static std::string GetSpaceFormat(std::string option);\n  static std::string GetFileSystemFormat(std::string option);\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param start_heartbeat control whether heartbeat thread is started - for\n  //!                        testing purposes\n  //----------------------------------------------------------------------------\n  FsView() : mConfigEngine(nullptr)\n  {\n    mHeartBeatThread.reset(&FsView::HeartBeatCheck, this);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~FsView()\n  {\n    StopHeartBeat();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Add FsChangeListener to all the existing file systems\n  //!\n  //! @param fs_listener file system change listener object\n  //! @param interests set of interest keys for the listener\n  //----------------------------------------------------------------------------\n  void AddFsChangeListener(std::shared_ptr<eos::mq::FsChangeListener> fs_lst,\n                           const std::set<std::string>& interests);\n\n  //----------------------------------------------------------------------------\n  //! Add or modify a filesystem\n  //!\n  //! @param fs filesystem to register\n  //! @parma registerInGeoTreeEngine\n  //!\n  //! @return true if done, otherwise false\n  //----------------------------------------------------------------------------\n  bool Register(FileSystem* fs, const common::FileSystemCoreParams& coreParams,\n                bool registerInGeoTreeEngine = true);\n\n  //----------------------------------------------------------------------------\n  //! Move a filesystem to another group\n  //!\n  //! @param fs filesystem object to move\n  //! @param group target group\n  //!\n  //! @return true if moved otherwise false\n  //----------------------------------------------------------------------------\n  bool MoveGroup(FileSystem* fs, std::string group);\n\n  //----------------------------------------------------------------------------\n  //! Move a filesystem to another node\n  //!\n  //! @param fs filesystem object to move\n  //! @param target node\n  //!\n  //! @return true if moved otherwise false\n  //----------------------------------------------------------------------------\n  bool MoveNode(FileSystem* fs, std::string node);\n\n  //----------------------------------------------------------------------------\n  //! Store the filesystem configuration into the config engine. Should be\n  //! called whenever a filesystem wide parameters is changed.\n  //!\n  //! @param fs file system object\n  //! @param save_config mark if the config should be saved or not\n  //! @note this requires at least the read lock on the gFsView.ViewMutex\n  //----------------------------------------------------------------------------\n  void StoreFsConfig(FileSystem* fs, bool save_config = true);\n\n  //----------------------------------------------------------------------------\n  //! Remove a filesystem\n  //!\n  //! @param fs file system object\n  //! @param unreg_from_geo_tree if true unregister from GeoTree\n  //! @param notify_fst if true delete the shared hash object corresponding\n  //!        to the current file system\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool UnRegister(FileSystem* fs, bool unreg_from_geo_tree = true,\n                  bool notify_fst = false);\n\n  //----------------------------------------------------------------------------\n  //! Check's if a queue+path exists already\n  //----------------------------------------------------------------------------\n  bool ExistsQueue(std::string queue, std::string queuepath);\n\n  //----------------------------------------------------------------------------\n  //! Add or modify an fst node\n  //----------------------------------------------------------------------------\n  bool RegisterNode(const char* nodequeue);\n\n  //----------------------------------------------------------------------------\n  //! Remove a node\n  //----------------------------------------------------------------------------\n  bool UnRegisterNode(const char* nodequeue);\n\n  //----------------------------------------------------------------------------\n  //! Add or modify a space\n  //----------------------------------------------------------------------------\n  bool RegisterSpace(const char* spacename);\n\n  //----------------------------------------------------------------------------\n  //! Remove a space\n  //----------------------------------------------------------------------------\n  bool UnRegisterSpace(const char* spacename);\n\n  //----------------------------------------------------------------------------\n  //! Add or modify a group\n  //----------------------------------------------------------------------------\n  bool RegisterGroup(const char* groupname);\n\n  //----------------------------------------------------------------------------\n  //! Remove a group\n  //----------------------------------------------------------------------------\n  bool UnRegisterGroup(const char* groupname);\n\n  //----------------------------------------------------------------------------\n  //! Check if quota is enabled for space\n  //!\n  //! @param space space name\n  //!\n  //! @return true if quota enabled for space, otherwise false\n  //! @warning needs to be called with a read-lock on the ViewMutex\n  //----------------------------------------------------------------------------\n  bool IsQuotaEnabled(const std::string& space);\n\n  //----------------------------------------------------------------------------\n  //! Find filesystem by queue path\n  //----------------------------------------------------------------------------\n  FileSystem* FindByQueuePath(std::string& queuepath);\n\n  //----------------------------------------------------------------------------\n  //! Create a filesystem mapping\n  //----------------------------------------------------------------------------\n  eos::common::FileSystem::fsid_t CreateMapping(std::string fsuuid);\n\n  //----------------------------------------------------------------------------\n  //! Provide a filesystem mapping\n  //----------------------------------------------------------------------------\n  bool ProvideMapping(std::string fsuuid, eos::common::FileSystem::fsid_t fsid);\n\n  //----------------------------------------------------------------------------\n  //! Get a filesystem mapping by unique ID\n  //----------------------------------------------------------------------------\n  eos::common::FileSystem::fsid_t GetMapping(std::string fsuuid);\n\n  //----------------------------------------------------------------------------\n  //! Check for an existing mapping by filesystem id\n  //----------------------------------------------------------------------------\n  inline bool HasMapping(eos::common::FileSystem::fsid_t fsid)\n  {\n    return mFilesystemMapper.hasFsid(fsid);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Remove a mapping providing filesystem ID and unique ID\n  //----------------------------------------------------------------------------\n  bool RemoveMapping(eos::common::FileSystem::fsid_t fsid, std::string fsuuid);\n\n  //----------------------------------------------------------------------------\n  //! Remove a mapping providing filesystem ID\n  //----------------------------------------------------------------------------\n  bool RemoveMapping(eos::common::FileSystem::fsid_t fsid);\n\n  //----------------------------------------------------------------------------\n  //! Print views (space,group,nodes)\n  //----------------------------------------------------------------------------\n  void PrintGroups(std::string& out, const std::string& headerformat,\n                   const std::string& listformat, unsigned int geodepth = 0,\n                   const char* selection = 0, bool dont_color = false);\n\n  void PrintNodes(std::string& out, const std::string& headerformat,\n                  const std::string& listformat, unsigned int geodepth = 0,\n                  const char* selection = 0, bool dont_color = false);\n\n  void PrintSpaces(std::string& out, const std::string& headerformat,\n                   const std::string& listformat, unsigned int geodepth = 0,\n                   const char* selection = 0, const std::string&  filter = \"\",\n                   bool dont_color = false);\n\n  //----------------------------------------------------------------------------\n  //! Clear all mappings and filesystem objects obtaining locks\n  //----------------------------------------------------------------------------\n  void Reset();\n\n  //----------------------------------------------------------------------------\n  //! Clear all maps and delete all filesystem/group/space objects\n  //----------------------------------------------------------------------------\n  void Clear();\n\n  //----------------------------------------------------------------------------\n  //! Thread loop function checking heartbeats\n  //----------------------------------------------------------------------------\n  void HeartBeatCheck(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Stop the heartbeat thread\n  //----------------------------------------------------------------------------\n  void StopHeartBeat()\n  {\n    mHeartBeatThread.join();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set the configuration engine object\n  //----------------------------------------------------------------------------\n  inline void SetConfigEngine(IConfigEngine* engine)\n  {\n    mConfigEngine = engine;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Apply all filesystem configuration key-val pair\n  //!\n  //! @param key fs configuration key\n  //! @param val fs configuration to be applied\n  //! @param first_unregister if true then unregister the file system before\n  //!        applying any of the changes. This is needed for slave MGMs when\n  //!        following changes from the master MGM. [default false]\n  //----------------------------------------------------------------------------\n  bool ApplyFsConfig(const char* key, const std::string& val,\n                     bool first_unregister = false);\n\n  //----------------------------------------------------------------------------\n  //! Apply a global configuration key-val pair\n  //----------------------------------------------------------------------------\n  bool ApplyGlobalConfig(const char* key, std::string& val);\n\n  //----------------------------------------------------------------------------\n  //! Set a global configuration key-val pair\n  //----------------------------------------------------------------------------\n  virtual bool SetGlobalConfig(const std::string& key, const std::string& value);\n\n  //----------------------------------------------------------------------------\n  //! Set a global configuration key-val pair given as boolean\n  //----------------------------------------------------------------------------\n  bool SetGlobalConfig(const std::string& key, bool value)\n  {\n    return SetGlobalConfig(key, (value ? std::string(\"true\") :\n                                 std::string(\"false\")));\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get a global configuration value\n  //----------------------------------------------------------------------------\n  virtual std::string GetGlobalConfig(const std::string& key);\n\n  //----------------------------------------------------------------------------\n  //! Get a global configuration value as boolean\n  //----------------------------------------------------------------------------\n  bool GetBoolGlobalConfig(const std::string& key)\n  {\n    return (GetGlobalConfig(key) == \"true\");\n  }\n\n  //----------------------------------------------------------------------------\n  //! Broadcast new manager id to all the FST nodes\n  //!\n  //! @param master_id master identity <hostname>:<port>\n  //----------------------------------------------------------------------------\n  void BroadcastMasterId(const std::string master_id);\n\n  //----------------------------------------------------------------------------\n  //! Get number of filesystems registered\n  //----------------------------------------------------------------------------\n  inline size_t GetNumFileSystems() const\n  {\n    eos::common::RWMutexReadLock fs_rd_lock(ViewMutex);\n    return mIdView.size();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Df Command output\n  //----------------------------------------------------------------------------\n\n  std::string Df(bool monitoring = false, bool si = false, bool readable = true,\n                 std::string path = \"\", bool json = false);\n\n  //----------------------------------------------------------------------------\n  //! Physical bytes available\n  //----------------------------------------------------------------------------\n  bool UnderNominalQuota(const std::string& space, bool isroot = false);\n\n  //----------------------------------------------------------------------------\n  //! Collect all endpoints (<hostname>:<port>) matching the given queue or\n  //! pattern\n  //!\n  //! @return set of matching endpoints\n  //----------------------------------------------------------------------------\n  std::set<std::string> CollectEndpoints(const std::string& queue) const;\n\n  //----------------------------------------------------------------------------\n  //! Re-apply drain status for file systems to re-trigger draining. This is\n  //! needed in case the drain engine is stopped and then restatred the fs-es\n  //! which where in draning mode are not reactivated so we need to go through\n  //! them and reapply the drain status so that draining is activated.\n  //----------------------------------------------------------------------------\n  void ReapplyDrainStatus();\n\n  //----------------------------------------------------------------------------\n  //! Get map of unbalanced groups given the threshold and their max dev.\n  //!\n  //! @param space_name space name\n  //! @param threshold deviation cut-off value used for selecting groups\n  //!\n  //! @return groups that are unbalanced with respect to the given thereshold\n  //----------------------------------------------------------------------------\n  std::map<std::string, double>\n  GetUnbalancedGroups(const std::string& space_name, double threshold) const;\n\n  //----------------------------------------------------------------------------\n  //! Get sets of file systems from given group that should be balanced\n  //!\n  //! @parma group_name group name\n  //! @param threshold cut-off value used for selecting file systems\n  //!\n  //! @return tuple of sets of file systems\n  //----------------------------------------------------------------------------\n  FsPrioritySets\n  GetFsToBalance(const std::string& group_name, double threshold) const;\n\n  //----------------------------------------------------------------------------\n  //! Dump balancer thread pool info for each of the existing spaces\n  //!\n  //! @param oss output string stream where info is appended\n  //! @param prefix prefix string for each entry to be displayed\n  //----------------------------------------------------------------------------\n  void DumpBalancerPoolInfo(std::ostringstream& oss,\n                            std::string_view prefix) const;\n\n\n  //----------------------------------------------------------------------------\n  //! Get space name for the given vector of locations belonging to a file\n  //!\n  //! @param fid file identifier used for debug purposes\n  //! @param vect_loc file system location vector\n  //!\n  //! @return space name or empty if any error happened\n  //----------------------------------------------------------------------------\n  std::string\n  GetSpaceNameForFses(const eos::IFileMD::id_t fid,\n                      const eos::IFileMD::LocationVector& vect_loc) const;\n\n  //! Mutex protecting all ...View variables\n  mutable eos::common::RWMutexR ViewMutex;\n  //! Map translating a space name to a set of group objects\n  std::map<std::string, std::set<FsGroup*> > mSpaceGroupView;\n  //! Map translating a space name to a space view object\n  std::map<std::string, FsSpace* > mSpaceView;\n  //! Map translating a group name to a group view object\n  std::map<std::string, FsGroup* > mGroupView;\n  //! Map translating a node name to a node view object\n  std::map<std::string, FsNode* > mNodeView;\n  //! Map translating a filesystem ID to a file system object\n  FileSystemRegistry mIdView;\n\nprivate:\n  IConfigEngine* mConfigEngine;\n  AssistedThread mHeartBeatThread; ///< Thread monitoring heart-beats\n  //! Object to map between fsid <-> uuid\n  FilesystemUuidMapper mFilesystemMapper;\n  std::map<std::string, std::pair<bool, time_t>> mUsageOk;\n  XrdSysMutex mUsageMutex;\n};\n\n//------------------------------------------------------------------------------\n//! Aggregator implementation to compute double precision statistics\n//! Statistics are sum, average, std dev, min dev, max dev, max abs dev\n//! It calls the underlying unstructured versions of the statistics computation\n//! DoubleSum, DoubleAverage, DoubleStdDev, ...\n//------------------------------------------------------------------------------\nclass DoubleAggregator : public GeoTreeAggregator\n{\n  typedef eos::common::FileSystem::fsid_t fsid_t;\n\n  //! Name of the parameter for which the statistics is to be computed\n  std::string pParam;\n  std::vector<double> pSums; ///< Sums at the elements of the tree\n  std::vector<double> pMeans; ///< Averages at the elements of the tree\n  std::vector<double> pMaxDevs; ///< Max deviations at the elements of the tree\n  std::vector<double> pMinDevs; ///< Min deviations at the elements of the tree\n  std::vector<double>\n  pMaxAbsDevs; ///< Min abs. deviations at the elements of the tree\n  std::vector<double> pStdDevs; ///< Std. deviations at the elements of the tree\n  //! Number of entries considered in the statistics at the elements of the tree\n  std::vector<long long> pNb;\n  BaseView* pView; ///< The base view ordering the statistics\n  //! End index (excluded) of each depth level in the statistics vectors\n  std::vector<size_t> pDepthLevelsIndexes;\n  std::vector<std::string> pGeoTags; ///< Full geotags at the elements of the tree\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Get the sums at each tree element\n  //----------------------------------------------------------------------------\n  const std::vector<double>* getSums() const;\n\n  //----------------------------------------------------------------------------\n  //! Get the averages at each tree element\n  //----------------------------------------------------------------------------\n  const std::vector<double>* getMeans() const;\n\n  //----------------------------------------------------------------------------\n  //! Get the max absolute deviations at each tree element\n  //----------------------------------------------------------------------------\n  const std::vector<double>* getMaxAbsDevs() const;\n\n  //----------------------------------------------------------------------------\n  //! Get the standard deviations at each tree element\n  //----------------------------------------------------------------------------\n  const std::vector<double>* getStdDevs() const;\n\n  //----------------------------------------------------------------------------\n  //! Get the full geotags at each tree element\n  //----------------------------------------------------------------------------\n  const std::vector<std::string>* getGeoTags() const;\n\n  //----------------------------------------------------------------------------\n  //! Get the end index (excluded) for a given depth level\n  //----------------------------------------------------------------------------\n  size_t getEndIndex(int depth = -1) const;\n\n  //----------------------------------------------------------------------------\n  //! Constructor given the name of the parameter to compute the statistics for\n  //----------------------------------------------------------------------------\n  DoubleAggregator(const char* param);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~DoubleAggregator();\n\n  //----------------------------------------------------------------------------\n  //! Set the view ordering the statistics. Needs to be set before running\n  //! the aggregator.\n  //----------------------------------------------------------------------------\n  void setView(BaseView* view);\n\n  virtual bool init(const std::vector<std::string>& geotags,\n                    const std::vector<size_t>& depthLevelsIndexes);\n\n  virtual bool aggregateLeaves(\n    const std::set<eos::common::FileSystem::fsid_t>& leaves, const size_t& idx);\n\n  virtual bool aggregateNodes(\n    const std::map<std::string, GeoTreeElement*>& nodes,\n    const size_t& idx, bool includeSelf = false);\n};\n\n//------------------------------------------------------------------------------\n//! Aggregator implementation to compute long long integer statistics\n//!\n//! Statistics is only sum\n//! It calls the underlying unstructured versions of the statistics computation\n//! LongLongSum, so it benefits from the same filtering and special cases\n//! implemented there.\n//------------------------------------------------------------------------------\nclass LongLongAggregator : public GeoTreeAggregator\n{\n  typedef eos::common::FileSystem::fsid_t fsid_t;\n  //! Name of the parameter for which the statistics are to be computed\n  std::string pParam;\n  std::vector<long long> pSums; ///< Sums at the elements of the tree\n  //! End index (excluded) of each depth level in the statistics vectors\n  std::vector<size_t> pDepthLevelsIndexes;\n  std::vector<std::string> pGeoTags; ///< Full geotags at the elements of the tree\n  BaseView* pView; ///< The base view ordering the statistics\n\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor given the name of the parameter to compute the statistics for\n  //----------------------------------------------------------------------------\n  LongLongAggregator(const char* param);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~LongLongAggregator();\n\n  //----------------------------------------------------------------------------\n  //! Set the view ordering the statistics. Needs to be set before running\n  //! the aggregator.\n  //----------------------------------------------------------------------------\n  void setView(BaseView* view);\n\n  //----------------------------------------------------------------------------\n  //! Get the sums at each tree element\n  //----------------------------------------------------------------------------\n  const std::vector<long long>* getSums() const;\n\n  //----------------------------------------------------------------------------\n  //! Get the full geotags at each tree element\n  //----------------------------------------------------------------------------\n  const std::vector<std::string>* getGeoTags() const;\n\n  //----------------------------------------------------------------------------\n  //! Get the end index (excluded) for a given depth level\n  //----------------------------------------------------------------------------\n  size_t getEndIndex(int depth = -1) const;\n\n  virtual bool init(const std::vector<std::string>& geotags,\n                    const std::vector<size_t>& depthLevelsIndexes);\n\n  virtual bool aggregateLeaves(\n    const std::set<eos::common::FileSystem::fsid_t>& leaves, const size_t& idx);\n\n  virtual bool aggregateNodes(\n    const std::map<std::string, GeoTreeElement*>& nodes, const size_t& idx,\n    bool includeSelf = false);\n};\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/fuse-locks/LockTracker.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file LockTracker.cc\n//! @author Gerogios Bitzes\n//! @brief POSIX lock class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"LockTracker.hh\"\n#include <fcntl.h>\n#include <unistd.h>\n#include <thread>\n\nEOSMGMNAMESPACE_BEGIN\nUSE_EOSMGMNAMESPACE\n\n/*----------------------------------------------------------------------------*/\nstd::ostream& operator<< (std::ostream& os, const ByteRange& range)\n{\n  os << \"[\" << range.start() << \", \" << range.end() << \")\";\n  return os;\n}\n\n/*----------------------------------------------------------------------------*/\nstd::ostream& operator<< (std::ostream& os, const Lock& lock)\n{\n  os << lock.range() << \" on pid \" << lock.pid() << std::endl;\n  return os;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\n/*----------------------------------------------------------------------------*/\nLockSet::add(const Lock& l)\n/*----------------------------------------------------------------------------*/\n{\n  Lock newlock(l);\n  // Absorb any overlapping ranges, removing the old ones\n  auto it = locks.begin();\n\n  while (it != locks.end()) {\n    if (newlock.absorb(*it)) {\n      it = locks.erase(it);\n    } else {\n      it++;\n    }\n  }\n\n  // Append the consolidated superlock\n  locks.push_back(newlock);\n}\n\n/*----------------------------------------------------------------------------*/\nbool\n/*----------------------------------------------------------------------------*/\nLockSet::conflict(const Lock& l) const\n/*----------------------------------------------------------------------------*/\n{\n  auto it = locks.begin();\n\n  while (it != locks.end()) {\n    if (it->pid() != l.pid() && l.range().overlap(it->range())) {\n      return true;\n    }\n\n    it++;\n  }\n\n  return false;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\n/*----------------------------------------------------------------------------*/\nLockSet::getconflict(Lock& l)\n/*----------------------------------------------------------------------------*/\n{\n  auto it = locks.begin();\n\n  while (it != locks.end()) {\n    if (it->pid() != l.pid() && l.range().overlap(it->range())) {\n      l = *it;\n      return true;\n    }\n\n    it++;\n  }\n\n  return false;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\n/*----------------------------------------------------------------------------*/\nLockSet::overlap(const Lock& l) const\n/*----------------------------------------------------------------------------*/\n{\n  auto it = locks.begin();\n\n  while (it != locks.end()) {\n    if (l.overlap(*it)) {\n      return true;\n    }\n\n    it++;\n  }\n\n  return false;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\n/*----------------------------------------------------------------------------*/\nLockSet::overlap(const ByteRange& br) const\n/*----------------------------------------------------------------------------*/\n{\n  auto it = locks.begin();\n\n  while (it != locks.end()) {\n    if (br.overlap(it->range())) {\n      return true;\n    }\n\n    it++;\n  }\n\n  return false;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\n/*----------------------------------------------------------------------------*/\nLockSet::remove(const Lock& l)\n/*----------------------------------------------------------------------------*/\n{\n  std::vector<Lock> queued;\n  auto it = locks.begin();\n\n  while (it != locks.end()) {\n    std::vector<Lock> newlocks = it->minus(l);\n\n    if (newlocks.size() == 0) {\n      it = locks.erase(it);\n      continue;\n    }\n\n    if (newlocks.size() >= 1) {\n      *it = newlocks[0];\n    }\n\n    if (newlocks.size() == 2) {\n      queued.push_back(newlocks[1]);\n    }\n\n    it++;\n  }\n\n  // Cannot add new items to vector while iterating, as it invalides the iterators.\n  // Store the items in a queue and add them later.\n  for (size_t i = 0; i < queued.size(); i++) {\n    locks.push_back(queued[i]);\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nsize_t\n/*----------------------------------------------------------------------------*/\nLockSet::nlocks()\n/*----------------------------------------------------------------------------*/\n{\n  return locks.size();\n}\n\n/*----------------------------------------------------------------------------*/\nsize_t\n/*----------------------------------------------------------------------------*/\nLockSet::nlocks(pid_t pid)\n/*----------------------------------------------------------------------------*/\n{\n  size_t res = 0;\n  auto it = locks.begin();\n\n  while (it != locks.end()) {\n    if (it->pid() == pid) {\n      res++;\n    }\n\n    it++;\n  }\n\n  return res;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\n/*----------------------------------------------------------------------------*/\nLockSet::remove(pid_t pid)\n{\n  std::vector<Lock> survivinglocks;\n\n  for (auto it = locks.begin(); it != locks.end(); ++it) {\n    if (it->pid() != pid) {\n      survivinglocks.push_back(*it);\n    }\n  }\n\n  locks = survivinglocks;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\n/*----------------------------------------------------------------------------*/\nLockSet::remove(const std::string& owner)\n{\n  std::vector<Lock> survivinglocks;\n\n  for (auto it = locks.begin(); it != locks.end(); ++it) {\n    if (it->owner() != owner) {\n      survivinglocks.push_back(*it);\n    }\n  }\n\n  locks = survivinglocks;\n}\n\n/*----------------------------------------------------------------------------*/\nstd::set<pid_t>\n/*----------------------------------------------------------------------------*/\nLockSet::lslocks(const std::string& owner)\n/*----------------------------------------------------------------------------*/\n{\n  // return all pids belonging to owner\n  std::set<pid_t> owner_pids;\n\n  for (auto it = locks.begin(); it != locks.end(); ++it) {\n    // fprintf(stderr, \"lock: owner=%s (%s) pid=%u true=%d\\n\", it->owner().c_str(),\n    //\t      owner.c_str(), it->pid(),\n    //\t      (it->owner() == owner));\n    // }\n\n    if (it->owner() == owner) {\n      owner_pids.insert(it->pid());\n    }\n  }\n\n  return owner_pids;\n}\n\n/*----------------------------------------------------------------------------*/\nint\n/*----------------------------------------------------------------------------*/\nLockTracker::getlk(pid_t pid, struct flock* lock)\n/*----------------------------------------------------------------------------*/\n{\n  std::lock_guard<std::mutex> guard(mtx);\n\n  if (lock->l_type == F_UNLCK) {\n    // TODO signal warning, should not happen (?)\n    return 1;\n  }\n\n  if (canLock(pid, lock)) {\n    lock->l_type = F_UNLCK;\n  } else {\n    // canLock filled the blocking lock\n  }\n\n  return 1;\n}\n\n/*----------------------------------------------------------------------------*/\nint\n/*----------------------------------------------------------------------------*/\nLockTracker::setlk(pid_t pid, struct flock* lock, int sleep,\n                   const std::string& owner)\n/*----------------------------------------------------------------------------*/\n{\n  if (!sleep) {\n    return addLock(pid, lock, owner);\n  }\n\n  size_t cnt = 0;\n\n  while (!addLock(pid, lock, owner)) {\n    cnt++;\n    // TODO wait on condition variable?\n    std::this_thread::sleep_for(std::chrono::milliseconds(1));\n\n    if (cnt > 10) {\n      // we give up after 10ms\n      return 0;\n    }\n  }\n\n  return 1;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\n/*----------------------------------------------------------------------------*/\nLockTracker::canLock(pid_t pid, struct flock* f_lock)\n/*----------------------------------------------------------------------------*/\n{\n  Lock lock(ByteRange(f_lock->l_start, f_lock->l_len), pid);\n\n  // Can always unlock\n  if (f_lock->l_type == F_UNLCK) {\n    return true;\n  }\n\n  // Are there any exclusive locks right now?\n  if (wlocks.getconflict(lock)) {\n    f_lock->l_start = lock.range().start();\n    f_lock->l_len = lock.range().f_lock_len();\n    f_lock->l_pid = lock.pid();\n    f_lock->l_whence = SEEK_SET;\n    f_lock->l_type = F_WRLCK;\n    return false;\n  }\n\n  // If this is a read lock, we can lock\n  if (f_lock->l_type == F_RDLCK) {\n    return true;\n  }\n\n  // If this is a write lock, we can lock only if there are no read locks\n  if (f_lock->l_type == F_WRLCK) {\n    bool rc = rlocks.getconflict(lock);\n    if (rc) {\n      f_lock->l_start = lock.range().start();\n      f_lock->l_len = lock.range().f_lock_len();\n      f_lock->l_pid = lock.pid();\n      f_lock->l_whence = SEEK_SET;\n      f_lock->l_type = F_RDLCK;\n      return false;\n    } else {\n      return true;\n    }\n  }\n\n  // TODO raise warning, should never reach this point\n  return false;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\n/*----------------------------------------------------------------------------*/\nLockTracker::addLock(pid_t pid, struct flock* f_lock, const std::string& owner)\n/*----------------------------------------------------------------------------*/\n{\n  std::lock_guard<std::mutex> guard(mtx);\n  Lock lock(ByteRange(f_lock->l_start, f_lock->l_len), pid, owner);\n\n  // Unlock?\n  if (f_lock->l_type == F_UNLCK) {\n    rlocks.remove(lock);\n    wlocks.remove(lock);\n    return true;\n  }\n\n  // Exclusive lock?\n  if (f_lock->l_type == F_WRLCK) {\n    // Conflict with any read locks?\n    if (rlocks.conflict(lock)) {\n      return false;\n    }\n\n    // Conflict with any write locks?\n    if (wlocks.conflict(lock)) {\n      return false;\n    }\n\n    // Add write lock\n    wlocks.add(lock);\n    // It could be that the process is converting a read lock into a write.\n    // Remove any read locks on the same region.\n    rlocks.remove(lock);\n    return true;\n  }\n\n  // Read lock?\n  if (f_lock->l_type == F_RDLCK) {\n    // Conflict with any write locks?\n    if (wlocks.conflict(lock)) {\n      return false;\n    }\n\n    // Add read lock\n    rlocks.add(lock);\n    // It could be that the process is converting a write lock into a read.\n    // Remove any write locks on the same region.\n    wlocks.remove(lock);\n    return true;\n  }\n\n  // TODO raise warning, should never reach this point\n  std::cerr << \"WARNING, something is wrong\" << std::endl;\n  return false;\n}\n\n/*----------------------------------------------------------------------------*/\nint\n/*----------------------------------------------------------------------------*/\nLockTracker::removelk(pid_t pid)\n{\n  std::lock_guard<std::mutex> guard(mtx);\n  rlocks.remove(pid);\n  wlocks.remove(pid);\n  return 1;\n}\n\n/*----------------------------------------------------------------------------*/\nint\n/*----------------------------------------------------------------------------*/\nLockTracker::removelk(const std::string& owner)\n{\n  std::lock_guard<std::mutex> guard(mtx);\n  rlocks.remove(owner);\n  wlocks.remove(owner);\n  return 1;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\n/*----------------------------------------------------------------------------*/\nLockTracker::inuse()\n/*----------------------------------------------------------------------------*/\n{\n  std::lock_guard<std::mutex> guard(mtx);\n  return (rlocks.nlocks() + wlocks.nlocks()) ? true : false;\n}\n\n/*----------------------------------------------------------------------------*/\nstd::set<pid_t>\n/*----------------------------------------------------------------------------*/\nLockTracker::getrlks(const std::string& owner)\n/*----------------------------------------------------------------------------*/\n{\n  std::lock_guard<std::mutex> guard(mtx);\n  return rlocks.lslocks(owner);\n}\n\n/*----------------------------------------------------------------------------*/\nstd::set<pid_t>\n/*----------------------------------------------------------------------------*/\nLockTracker::getwlks(const std::string& owner)\n/*----------------------------------------------------------------------------*/\n{\n  std::lock_guard<std::mutex> guard(mtx);\n  return wlocks.lslocks(owner);\n}\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/fuse-locks/LockTracker.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file LockTracker.hh\n//! @author Gerogios Bitzes\n//! @brief POSIX lock class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#ifndef __EOS_MGM_LOCKTRACKER_HH__\n#define __EOS_MGM_LOCKTRACKER_HH__\n\n#include <cstdlib>\n#include <list>\n#include <vector>\n#include <set>\n#include <limits>\n#include <iostream>\n#include <mutex>\n#include <fcntl.h>\n#include \"mgm/Namespace.hh\"\n#include \"common/Assert.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\ntemplate<typename T>\nbool isPointBetween(const T& start, const T& target, const T& end)\n{\n  return target >= start && target < end;\n}\n\ntemplate<typename T>\nbool isPointBetweenOrTouching(const T& start, const T& target, const T& end)\n{\n  return target >= start && target <= end;\n}\n/*----------------------------------------------------------------------------*/\ntypedef off_t Offset;\n/*----------------------------------------------------------------------------*/\nclass ByteRange;\nclass Lock;\nstd::ostream& operator<< (std::ostream& os, const ByteRange& range);\nstd::ostream& operator<< (std::ostream& os, const Lock& lock);\n\n/*----------------------------------------------------------------------------*/\nclass ByteRange\n/*----------------------------------------------------------------------------*/\n{\npublic:\n\n  ByteRange(Offset start, Offset len) : start_(start), len_(len)\n  {\n    if (!overlap(*this)) {\n      std::cerr <<\n                \"ByteRange assertion failed: range does not overlap with itself! start: \"\n                << start << \", len: \" << len << std::endl;\n      exit(EXIT_FAILURE);\n    }\n  }\n\n  bool operator==(const ByteRange& rhs) const\n  {\n    return this->start() == rhs.start() && this->end() == rhs.end();\n  }\n\n  Offset start() const\n  {\n    return start_;\n  }\n\n  Offset len() const\n  {\n    return len_;\n  }\n\n  Offset f_lock_len() const\n  {\n    if (len_ == -1) {\n      return 0;\n    } else {\n      return len_;\n    }\n  }\n\n  Offset end() const\n  {\n    if (len_ == -1) {\n      return std::numeric_limits<Offset>::max();\n    }\n\n    return start_ + len_;\n  }\n\n  // Absorb the other range if possible, expand myself\n  // to contain both ranges\n\n  bool absorb(const ByteRange& other)\n  {\n    if (!overlapOrTouch(other)) {\n      return false;\n    }\n\n    Offset myend = end();\n    start_ = std::min(start_, other.start_);\n    updateEnd(std::max(myend, other.end()));\n    return true;\n  }\n\n  bool contains(const ByteRange& other) const\n  {\n    return start()     <= other.start() &&\n           other.end() <= end();\n  }\n\n  // Return what happens when removing the range \"other\" from \"this\".\n  // Might return 0, 1 or 2 resulting ranges.\n\n  std::vector<ByteRange> minus(const ByteRange& other) const\n  {\n    // Case 1: \"other\" fully to the left, no overlap\n    if (other.end() <= this->start()) return { *this};\n\n    // Case 2: \"other\" fully to the right, no overlap\n    if (this->end() <= other.start()) return { *this};\n\n    // Case 3: \"other\" eats the entire thing, no output\n    if (other.contains(*this)) return {};\n\n    // Case 4: \"other\" eats the start, but not the end\n    if (isPointBetween(other.start(),\n                       this->start(), other.end()) && other.end() < this->end()) {\n      return { ByteRange(other.end(), this->end() - other.end())};\n    }\n\n    // Case 5: \"other\" eats the end, but not the start\n    if (isPointBetween(other.start(), this->end() - 1,\n                       other.end()) && this->start() < other.start()) {\n      return { ByteRange(this->start(), other.start() - this->start())};\n    }\n\n    // Case 6: \"other\" eats the middle\n    return { ByteRange(this->start(), other.start() - this->start()),\n             ByteRange(other.end(), this->end() - other.end())};\n  }\n\n  // checks whether the two ranges overlap, or at least touch\n\n  bool overlapOrTouch(const ByteRange& other) const\n  {\n    // case 1: Is other.start between this->start and this->end ?\n    if (isPointBetweenOrTouching(this->start(),\n                                 other.start(), this->end())) {\n      return true;\n    }\n\n    // case 2: Is this->start between other.start and other.end ?\n    if (isPointBetweenOrTouching(other.start(),\n                                 this->start(), other.end())) {\n      return true;\n    }\n\n    // case 3: ranges don't overlap or touch\n    return false;\n  }\n\n  bool overlap(const ByteRange& other) const\n  {\n    // case 1: 0 ranges at the same offset overlap\n    if ((this->start() == this->end()) &&\n        (other.start() == other.end()) &&\n        (this->start() == other.start())) {\n      return true;\n    }\n\n    // case 2: Is other.start between this->start and this->end ?\n    if (isPointBetween(this->start(), other.start(), this->end())) {\n      return true;\n    }\n\n    // case 3: Is this->start between other.start and other.end ?\n    if (isPointBetween(other.start(), this->start(), other.end())) {\n      return true;\n    }\n\n    // case 4: ranges don't overlap\n    return false;\n  }\n\nprivate:\n\n  void updateEnd(Offset newend)\n  {\n    if (newend <= start()) {\n      std::cerr << \"ByteRange assertion failed: tried to update end to \"\n                << newend << \", while start = \" << start() << std::endl;\n      exit(EXIT_FAILURE);\n    }\n\n    if (newend == std::numeric_limits<Offset>::max()) {\n      len_ = -1;\n    } else {\n      len_ = newend - start_;\n    }\n  }\n\n  Offset start_;\n  Offset len_;\n} ;\n\n/*----------------------------------------------------------------------------*/\nclass Lock\n/*----------------------------------------------------------------------------*/\n{\npublic:\n\n  Lock(const ByteRange& range, pid_t pid,\n       const std::string& owner = \"\") : range_(range), pid_(pid), owner_(owner)\n  {\n  }\n\n  pid_t pid() const\n  {\n    return pid_;\n  }\n\n  std::string owner() const\n  {\n    return owner_;\n  }\n\n  const ByteRange& range() const\n  {\n    return range_;\n  }\n\n  bool overlap(const Lock& other) const\n  {\n    if (pid() != other.pid()) {\n      return false;\n    }\n\n    return range_.overlap(other.range_);\n  }\n\n  bool contains(const Lock& other) const\n  {\n    if (pid() != other.pid()) {\n      return false;\n    }\n\n    return range_.contains(other.range_);\n  }\n\n  bool absorb(const Lock& other)\n  {\n    if (pid() != other.pid()) {\n      return false;\n    }\n\n    return range_.absorb(other.range_);\n  }\n\n  std::vector<Lock> minus(const Lock& other)\n  {\n    if (pid() != other.pid()) return { *this};\n\n    std::vector<ByteRange> ranges = range_.minus(other.range_);\n\n    std::vector<Lock> locks;\n\n    for (size_t i = 0; i < ranges.size(); i++) {\n      locks.emplace_back(ranges[i], pid());\n    }\n\n    return locks;\n  }\n\n  bool operator==(const Lock& rhs) const\n  {\n    return pid() == rhs.pid() && range() == rhs.range();\n  }\n\nprivate:\n  ByteRange range_;\n  pid_t pid_;\n  std::string owner_;\n} ;\n\nclass LockSet\n{\npublic:\n  void add(const Lock& l); // adds lock, merging appropriately any overlaps\n  bool overlap(const Lock& l)\n  const; // check if overlaps with locks from the *same* process\n  bool overlap(const ByteRange& r)\n  const; // check if overlaps with locks from *any* process\n  void remove(const Lock&\n              l); // remove any contained locks, shrink any overlapping\n  void remove(const pid_t pid); // remove all locks for a given pid\n  void remove(const std::string& owner); // remove all locks for a given owner\n\n  // check if there's a conflict between this lock and any other in the set.\n  // If two locks overlap, but have the same PID, this is not a conflict!\n  bool conflict(const Lock& l) const;\n\n  bool getconflict(Lock& l);\n\n  size_t nlocks(); // how many locks there are in total (after coalescing)\n  size_t nlocks(pid_t\n                pid); // how many locks are held by a specific pid (after coalescing)\n\n  std::set<pid_t> lslocks(const std::string&\n                          owner); // return all pids belonging to owner\n\nprivate:\n  std::vector<Lock> locks;\n} ;\n\nclass LockTracker\n{\npublic:\n  int getlk(pid_t pid, struct flock* lock);\n  int setlk(pid_t pid, struct flock* lock, int sleep, const std::string& owner);\n\n  std::set<pid_t> getrlks(const std::string& owner);\n  std::set<pid_t> getwlks(const std::string& owner);\n\n  int removelk(pid_t pid);\n  int removelk(const std::string& owner);\n  bool inuse();\n\nprivate:\n  std::mutex mtx;\n  bool addLock(pid_t pid, struct flock* lock, const std::string& owner);\n  bool canLock(pid_t pid, struct flock* lock);\n\n  LockSet rlocks;\n  LockSet wlocks;\n} ;\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/geobalancer/GeoBalancer.cc",
    "content": "// ----------------------------------------------------------------------\n// File: GeoBalancer.cc\n// Author: Joaquim Rocha - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/geobalancer/GeoBalancer.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/convert/ConverterEngine.hh\"\n#include \"namespace/interface/IFsView.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/FileId.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/utils/RandUtils.hh\"\n#include <XrdSys/XrdSysError.hh>\n#include <XrdOuc/XrdOucTrace.hh>\n#include <Xrd/XrdScheduler.hh>\n#include <random>\n#include <cmath>\n\nextern XrdSysError gMgmOfsEroute;\nextern XrdOucTrace gMgmOfsTrace;\n\n#define CACHE_LIFE_TIME 300 // seconds\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\nGeoBalancer::GeoBalancer(const char* spacename)\n  : mThreshold(.5),\n    mAvgUsedSize(0)\n    /*----------------------------------------------------------------------------*/\n    /**\n     * @brief Constructor by space name\n     *\n     * @param spacename name of the associated space\n     */\n    /*----------------------------------------------------------------------------*/\n{\n  mSpaceName = spacename;\n  mLastCheck = 0;\n  mThread.reset(&GeoBalancer::GeoBalance, this);\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nGeoBalancer::Stop()\n/*----------------------------------------------------------------------------*/\n/**\n * @brief thread stop function\n */\n/*----------------------------------------------------------------------------*/\n{\n  mThread.join();\n}\n\n/*----------------------------------------------------------------------------*/\nGeoBalancer::~GeoBalancer()\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Destructor\n */\n/*----------------------------------------------------------------------------*/\n{\n  Stop();\n  clearCachedSizes();\n}\n\n/*----------------------------------------------------------------------------*/\nGeotagSize::GeotagSize(uint64_t usedBytes, uint64_t capacity)\n  : mSize(usedBytes),\n    mCapacity(capacity)\n    /*----------------------------------------------------------------------------*/\n    /**\n     * @brief GeotagSize constructor (capacity must be > 0)\n     */\n    /*----------------------------------------------------------------------------*/\n{\n  assert(capacity > 0);\n}\n\n\n/*----------------------------------------------------------------------------*/\nvoid\nGeoBalancer::clearCachedSizes()\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Clears the cache structures\n */\n/*----------------------------------------------------------------------------*/\n{\n  mGeotagFs.clear();\n  mFsGeotag.clear();\n  std::map<std::string, GeotagSize*>::iterator it;\n\n  for (it = mGeotagSizes.begin(); it != mGeotagSizes.end(); it++) {\n    delete (*it).second;\n  }\n\n  mGeotagSizes.clear();\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nGeoBalancer::fillGeotagsByAvg()\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Fills mGeotagsOverAvg with the objects in mGeotagSizes, in case\n *        they're greater than the current mAvgUsedSize\n */\n/*----------------------------------------------------------------------------*/\n{\n  mGeotagsOverAvg.clear();\n  std::map<std::string, GeotagSize*>::const_iterator it;\n\n  for (it = mGeotagSizes.cbegin(); it != mGeotagSizes.cend(); it++) {\n    double geotagAvg = (*it).second->filled();\n\n    if (geotagAvg - mAvgUsedSize > mThreshold) {\n      mGeotagsOverAvg.push_back((*it).first);\n    }\n  }\n}\n\nstatic void\nprintSizes(const std::map<std::string, GeotagSize*>* sizes)\n{\n  std::map<std::string, GeotagSize*>::const_iterator it;\n\n  for (it = sizes->cbegin(); it != sizes->cend(); it++)\n    eos_static_info(\"geotag=%s average=%.02f\", (*it).first.c_str(),\n                    (double)(*it).second->filled() * 100.0);\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nGeoBalancer::populateGeotagsInfo()\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Fills mGeotagSizes, calculates the mAvgUsedSize and fills\n *        mGeotagsOverAvg\n */\n/*----------------------------------------------------------------------------*/\n{\n  clearCachedSizes();\n  const char* spaceName = mSpaceName.c_str();\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n  const FsSpace* spaceView = FsView::gFsView.mSpaceView[spaceName];\n\n  if (spaceView->size() == 0) {\n    eos_static_info(\"msg=\\\"no filesystems in space\\\" space=%s\", spaceName);\n    return;\n  }\n\n  for (auto it = spaceView->cbegin(); it != spaceView->cend(); it++) {\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it);\n\n    if (!fs || (fs->GetActiveStatus() != eos::common::ActiveStatus::kOnline)) {\n      continue;\n    }\n\n    eos::common::FileSystem::fs_snapshot_t snapshot;\n    fs->SnapShotFileSystem(snapshot, false);\n\n    if (snapshot.mStatus != eos::common::BootStatus::kBooted ||\n        snapshot.mConfigStatus < eos::common::ConfigStatus::kRO ||\n        snapshot.mGeoTag.empty()) {\n      continue;\n    }\n\n    mGeotagFs[snapshot.mGeoTag].push_back(*it);\n    mFsGeotag[*it] = snapshot.mGeoTag;\n    uint64_t capacity = snapshot.mDiskCapacity;\n    uint64_t usedBytes = (uint64_t)(capacity - snapshot.mDiskFreeBytes);\n\n    if (mGeotagSizes.count(snapshot.mGeoTag) == 0) {\n      mGeotagSizes[snapshot.mGeoTag] = new GeotagSize(usedBytes, capacity);\n    } else {\n      uint64_t currentUsedBytes = mGeotagSizes[snapshot.mGeoTag]->usedBytes();\n      uint64_t currentCapacity = mGeotagSizes[snapshot.mGeoTag]->capacity();\n      mGeotagSizes[snapshot.mGeoTag]->setUsedBytes(currentUsedBytes + usedBytes);\n      mGeotagSizes[snapshot.mGeoTag]->setCapacity(currentCapacity + capacity);\n    }\n  }\n\n  mAvgUsedSize = 0;\n  std::map<std::string, std::vector<eos::common::FileSystem::fsid_t>>::const_iterator\n      git;\n\n  for (git = mGeotagFs.cbegin(); git != mGeotagFs.cend(); git++) {\n    const std::string geotag = (*git).first;\n    const std::vector<eos::common::FileSystem::fsid_t> fsVector = (*git).second;\n    mAvgUsedSize += mGeotagSizes[geotag]->filled();\n  }\n\n  mAvgUsedSize /= ((double) mGeotagSizes.size());\n  eos_static_info(\"msg=\\\"geo_balancer update average fill\\\" average=%.02f %%\",\n                  mAvgUsedSize * 100.0);\n  fillGeotagsByAvg();\n}\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Checks if a file is spread in more than one location\n * @param fmd the file metadata object\n * @return whether the file is in more than one location or not\n */\n/*----------------------------------------------------------------------------*/\nbool\nGeoBalancer::fileIsInDifferentLocations(const eos::IFileMD* fmd)\n{\n  const std::string* geotag = 0;\n  eos::IFileMD::LocationVector::const_iterator lociter;\n  eos::IFileMD::LocationVector loc_vect = fmd->getLocations();\n\n  for (lociter = loc_vect.begin(); lociter != loc_vect.end(); ++lociter) {\n    // ignore filesystem id 0\n    if (!(*lociter)) {\n      eos_static_err(\"msg=\\\"fsid 0 found\\\" fxid=%08llx\", fmd->getId());\n      continue;\n    }\n\n    // Ignore EOS_TAPE_FSID\n    if (EOS_TAPE_FSID == *lociter) {\n      eos_static_debug(\"msg=\\\"skip tape fsid\\\" fxid=%08llx\", fmd->getId());\n      continue;\n    }\n\n    if (geotag == 0) {\n      geotag = &mFsGeotag[*lociter];\n    } else if (geotag->compare(mFsGeotag[*lociter]) != 0) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Produces a file conversion path to be placed in the proc directory\n *        and also returns its size\n * @param fid the file ID\n * @param size return address for the size of the file\n * @return the file path\n */\n/*----------------------------------------------------------------------------*/\nstd::string\nGeoBalancer::getFileProcTransferNameAndSize(eos::common::FileId::fileid_t fid,\n    uint64_t* size)\n{\n  char fileName[1024];\n  std::string file_uri;\n  std::shared_ptr<eos::IFileMD> fmd;\n  eos::common::LayoutId::layoutid_t layoutid = 0;\n  {\n    eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, fid);\n\n    try {\n      fmd = gOFS->eosFileService->getFileMD(fid);\n      // Don't lock the file before getting its URI\n      file_uri = gOFS->eosView->getUri(fmd.get()).c_str();\n      // Now we can lock the file\n      eos::MDLocking::FileReadLock fmdLock(fmd.get());\n      layoutid = fmd->getLayoutId();\n\n      if ((fmd->getContainerId() == 0) ||\n          (fmd->getNumLocation() == 0) ||\n          (fmd->getSize() == 0)) {\n        return std::string(\"\");\n      }\n\n      if (fileIsInDifferentLocations(fmd.get())) {\n        eos_static_debug(\"msg=\\\"file is already in more than one location\\\" \"\n                         \"name=%s fxid=%08llx\", fmd->getName().c_str(), fid);\n        return std::string(\"\");\n      }\n\n      if (size) {\n        *size = fmd->getSize();\n      }\n\n      // Don't touch files in any ../proc/ directory\n      if (file_uri.rfind(gOFS->MgmProcPath.c_str(), 0) == 0) {\n        return std::string(\"\");\n      }\n    } catch (eos::MDException& e) {\n      eos_static_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\", e.getErrno(),\n                       e.getMessage().str().c_str());\n      return std::string(\"\");\n    }\n  }\n  eos_static_debug(\"msg=\\\"found file to geobalance\\\" path=%s\",\n                   file_uri.c_str());\n  snprintf(fileName, 1024, \"%s/%016llx:%s#%08lx\",\n           gOFS->MgmProcConversionPath.c_str(), fid, mSpaceName.c_str(),\n           (unsigned long) layoutid);\n  return std::string(fileName);\n}\n\n//------------------------------------------------------------------------------\n// Update the list of ongoing transfers\n//------------------------------------------------------------------------------\nvoid\nGeoBalancer::updateTransferList()\n{\n  // Update tracker for scheduled jobs if using new converter\n  gOFS->mFidTracker.DoCleanup(TrackerType::Convert);\n\n  for (auto it = mTransfers.begin(); it != mTransfers.end();) {\n    if (!gOFS->mFidTracker.HasEntry(it->first)) {\n      mTransfers.erase(it++);\n    } else {\n      ++it;\n    }\n  }\n\n  eos_static_info(\"msg=\\\"geo_balancer update transfers\\\" scheduled_transfers=%d\",\n                  mTransfers.size());\n}\n\n//------------------------------------------------------------------------------\n// Creates the conversion file in proc for the file ID, from the given\n// fromGeotag (updates the cache structures).\n//\n// @note: All this works based on the assumption that kScattered is the default\n// placement policy.\n//------------------------------------------------------------------------------\nbool\nGeoBalancer::scheduleTransfer(eos::common::FileId::fileid_t fid,\n                              const std::string& fromGeotag)\n{\n  uint64_t size = 0;\n  std::string file_path = getFileProcTransferNameAndSize(fid, &size);\n\n  if (file_path == \"\") {\n    return false;\n  }\n\n  std::string conv_tag = file_path;\n  conv_tag += \"^geobalancer^\";\n  conv_tag.erase(0, gOFS->MgmProcConversionPath.length() + 1);\n  std::string err_msg;\n\n  if (gOFS->mConverterEngine->ScheduleJob(fid, conv_tag, err_msg)) {\n    eos_static_info(\"msg=\\\"geo_balancer scheduled job\\\" file=\\\"%s\\\" \"\n                    \"from_geotag=\\\"%s\\\"\", conv_tag.c_str(),\n                    fromGeotag.c_str());\n  } else {\n    eos_static_err(\"msg=\\\"geo_balancer failed to schedule job\\\" \"\n                   \"file=\\\"%s\\\" from_geotag=\\\"%s\\\"\", conv_tag.c_str(),\n                   fromGeotag.c_str());\n    return false;\n  }\n\n  mTransfers[fid] = file_path.c_str();\n  uint64_t usedBytes = mGeotagSizes[fromGeotag]->usedBytes();\n  mGeotagSizes[fromGeotag]->setUsedBytes(usedBytes - size);\n  fillGeotagsByAvg();\n  return true;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::FileId::fileid_t\nGeoBalancer::chooseFidFromGeotag(const std::string& geotag)\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Chooses a random file ID from a random filesystem in the given geotag\n * @param geotag the location's name from which the file id will be chosen\n * @return the chosen file ID\n */\n/*----------------------------------------------------------------------------*/\n{\n  int rndIndex;\n  bool found = false;\n  uint64_t fsid_size = 0ull;\n  eos::common::FileSystem::fsid_t fsid = 0;\n  eos::common::RWMutexReadLock vlock(FsView::gFsView.ViewMutex);\n  std::vector<eos::common::FileSystem::fsid_t>& validFs = mGeotagFs[geotag];\n  // TODO(gbitzes): Add prefetching here.\n\n  while (validFs.size() > 0) {\n    rndIndex = eos::common::getRandom(0ul, validFs.size() - 1);\n    fsid = validFs[rndIndex];\n    fsid_size = gOFS->eosFsView->getNumFilesOnFs(fsid);\n\n    if (fsid_size) {\n      found = true;\n      break;\n    }\n\n    validFs.erase(validFs.begin() + rndIndex);\n  }\n\n  if (validFs.size() == 0) {\n    mGeotagFs.erase(geotag);\n    mGeotagSizes.erase(geotag);\n    fillGeotagsByAvg();\n  }\n\n  if (!found) {\n    return -1;\n  }\n\n  int attempts = 10;\n\n  while (attempts-- > 0) {\n    eos::IFileMD::id_t randomPick;\n\n    if (gOFS->eosFsView->getApproximatelyRandomFileInFs(fsid, randomPick) &&\n        mTransfers.count(randomPick) == 0) {\n      return randomPick;\n    }\n  }\n\n  return -1;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nGeoBalancer::prepareTransfer()\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Picks a geotag randomly and schedule a file ID to be transferred\n */\n/*----------------------------------------------------------------------------*/\n{\n  if (mGeotagsOverAvg.size() == 0) {\n    eos_static_debug(\"%s\", \"msg=\\\"no geotags above average\\\"\");\n    return;\n  }\n\n  int attempts = 10;\n\n  while (attempts-- > 0) {\n    int rndIndex = eos::common::getRandom(0ul, mGeotagsOverAvg.size() - 1);\n    std::vector<std::string>::const_iterator over_it = mGeotagsOverAvg.cbegin();\n    std::advance(over_it, rndIndex);\n    // TODO: this loop should be improved not to request the file list too\n    // many times in a tight loop\n    eos::common::FileId::fileid_t fid = chooseFidFromGeotag(*over_it);\n\n    if ((int) fid == -1) {\n      eos_static_debug(\"msg=\\\"no fid found to schedule\\\" failed_geotag=%s\",\n                       (*over_it).c_str());\n      continue;\n    }\n\n    if (scheduleTransfer(fid, *over_it)) {\n      break;\n    }\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nGeoBalancer::cacheExpired()\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Check if the sizes cache should be updated (based on the time passed\n *        since they were last updated)\n * @return whether the cache expired or not\n */\n/*----------------------------------------------------------------------------*/\n{\n  time_t currentTime = time(NULL);\n\n  if (difftime(currentTime, mLastCheck) > CACHE_LIFE_TIME) {\n    mLastCheck = currentTime;\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n//Schedule a pre-defined number of transfers\n//------------------------------------------------------------------------------\nvoid\nGeoBalancer::prepareTransfers(int nrTransfers)\n{\n  int allowedTransfers = nrTransfers - mTransfers.size();\n\n  for (int i = 0; i < allowedTransfers; i++) {\n    prepareTransfer();\n  }\n\n  if (allowedTransfers > 0) {\n    printSizes(&mGeotagSizes);\n  }\n}\n\n//------------------------------------------------------------------------------\n//! @brief eternal loop trying to run conversion jobs\n//------------------------------------------------------------------------------\nvoid\nGeoBalancer::GeoBalance(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"GeoBalancer\");\n  eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n  gOFS->WaitUntilNamespaceIsBooted(assistant);\n  assistant.wait_for(std::chrono::seconds(10));\n\n  // Loop forever until cancelled\n  while (!assistant.terminationRequested()) {\n    bool is_enabled = true;\n    int nrTransfers = 0;\n    FsSpace* space {nullptr};\n    decltype(FsView::gFsView.mSpaceView.begin()) it_space;\n\n    if (!gOFS->mMaster->IsMaster()) {\n      eos_static_debug(\"%s\", \"msg=\\\"geo balancer is disabled for slave\\\"\");\n      goto wait;\n    }\n\n    std::this_thread::sleep_for(std::chrono::milliseconds(100));\n\n    if (!gOFS || ! gOFS->mConverterEngine ||\n        !gOFS->mConverterEngine->IsRunning()) {\n      eos_static_debug(\"msg=\\\"geo balancer disabled since it needs the \"\n                       \"converter enabled to work and it's not\\\" space=%s\",\n                       mSpaceName.c_str());\n      goto wait;\n    }\n\n    FsView::gFsView.ViewMutex.LockRead();\n\n    if (!FsView::gFsView.mSpaceGroupView.count(mSpaceName.c_str())) {\n      FsView::gFsView.ViewMutex.UnLockRead();\n      eos_static_warning(\"msg=\\\"no space to geo balance\\\" space=\\\"%s\\\"\",\n                         mSpaceName.c_str());\n      break;\n    }\n\n    it_space = FsView::gFsView.mSpaceView.find(mSpaceName.c_str());\n\n    if (it_space == FsView::gFsView.mSpaceView.end()) {\n      eos_static_err(\"msg=\\\"geo_balancer terminating, no such space\\\" space=%s\",\n                     mSpaceName.c_str());\n      break;\n    }\n\n    space = it_space->second;\n    // Extract the current settings if conversion enabled and how many\n    // conversion jobs should run\n    is_enabled = space->GetConfigMember(\"geobalancer\") == \"on\";\n    nrTransfers = atoi(space->GetConfigMember(\"geobalancer.ntx\").c_str());\n    mThreshold = atof(space->GetConfigMember(\"geobalancer.threshold\").c_str());\n    mThreshold /= 100.0;\n    FsView::gFsView.ViewMutex.UnLockRead();\n\n    if (is_enabled) {\n      eos_static_info(\"msg=\\\"geo balancer is enabled\\\" ntx=%d \", nrTransfers);\n      updateTransferList();\n\n      if ((int) mTransfers.size() >= nrTransfers) {\n        goto wait;\n      }\n\n      if (cacheExpired()) {\n        populateGeotagsInfo();\n        printSizes(&mGeotagSizes);\n      }\n\n      prepareTransfers(nrTransfers);\n    } else {\n      eos_static_debug(\"%s\", \"msg=\\\"geo balancer is disabled\\\"\");\n    }\n\nwait:\n    // Let some time pass or wait for a notification\n    assistant.wait_for(std::chrono::seconds(10));\n\n    if (assistant.terminationRequested()) {\n      return;\n    }\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/geobalancer/GeoBalancer.hh",
    "content": "// ----------------------------------------------------------------------\n// File: GeoBalancer.hh\n// Author: Joaquim Rocha - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @file GeoBalancer.hh\n//! @brief Balancing among geo locations\n//------------------------------------------------------------------------------\n\n#ifndef __EOSMGM_GEOBALANCER__\n#define __EOSMGM_GEOBALANCER__\n\n/* -------------------------------------------------------------------------- */\n#include \"mgm/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include \"common/FileId.hh\"\n#include \"common/FileSystem.hh\"\n#include \"common/AssistedThread.hh\"\n/* -------------------------------------------------------------------------- */\n#include <XrdSys/XrdSysPthread.hh>\n/* -------------------------------------------------------------------------- */\n#include <vector>\n#include <string>\n#include <deque>\n#include <cstring>\n#include <ctime>\n\n//! Forward declaration\nnamespace eos\n{\nclass IFileMD;\n}\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! @brief Class representing a geotag's size\n//! It holds the capacity and the current used space of a geotag.\n//------------------------------------------------------------------------------\nclass GeotagSize\n{\npublic:\n  GeotagSize(uint64_t usedBytes, uint64_t capacity);\n\n  uint64_t\n  usedBytes() const\n  {\n    return mSize;\n  };\n\n  void\n  setUsedBytes(uint64_t usedBytes)\n  {\n    mSize = usedBytes;\n  };\n\n  void\n  setCapacity(uint64_t capacity)\n  {\n    mCapacity = capacity;\n  };\n\n  uint64_t\n  capacity() const\n  {\n    return mCapacity;\n  };\n\n  double\n  filled() const\n  {\n    return (double) mSize / (double) mCapacity;\n  };\n\nprivate:\n  uint64_t mSize;\n  uint64_t mCapacity;\n};\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Class running the balancing among geotags\n *\n * For it to work, the Converter also needs to be enabled.\n */\n\n/*----------------------------------------------------------------------------*/\nclass GeoBalancer\n{\nprivate:\n  AssistedThread mThread; ///< Thread doing the geo-balancing\n\n  /// name of the space this geo balancer serves\n  std::string mSpaceName;\n  /// the threshold with which to compare the geotags\n  double mThreshold;\n\n  /// geotags and respective filesystems\n  std::map<std::string, std::vector<eos::common::FileSystem::fsid_t >> mGeotagFs;\n  /// fs->geotag cache\n  std::map<eos::common::FileSystem::fsid_t, std::string> mFsGeotag;\n  /// geotags' sizes cache\n  std::map<std::string, GeotagSize*> mGeotagSizes;\n  /// cache with geotags over the current average\n  std::vector<std::string> mGeotagsOverAvg;\n\n  /// average filled percentage in geotags\n  double mAvgUsedSize;\n\n  /// last time the geotags' real used space was checked\n  time_t mLastCheck;\n\n  /// transfers scheduled (maps files' ids with their path in proc)\n  std::map<eos::common::FileId::fileid_t, std::string> mTransfers;\n\n  std::string getFileProcTransferNameAndSize(eos::common::FileId::fileid_t fid,\n      uint64_t* size);\n\n  eos::common::FileId::fileid_t chooseFidFromGeotag(const std::string& geotag);\n\n  void populateGeotagsInfo(void);\n\n  void clearCachedSizes(void);\n\n  void fillGeotagsByAvg(void);\n\n  void prepareTransfers(int nrTransfers);\n\n  void prepareTransfer(void);\n\n  //----------------------------------------------------------------------------\n  //! @brief Creates the conversion file in proc for the file ID, from the\n  //! given fromGeotag (updates the cache structures)\n  //!\n  //! @note: All this works based on the assumption that kScattered is the\n  //! default placement policy.\n  //!\n  //! @param fid the id of the file to be transferred\n  //! @param fromGeotag the geotag of the location where the file is located\n  //!\n  //! @return whether the transfer file was successfully created or not\n  //----------------------------------------------------------------------------\n  bool scheduleTransfer(eos::common::FileId::fileid_t fid,\n                        const std::string& sourceGeotag);\n\n  bool cacheExpired(void);\n\n  void updateTransferList(void);\n\n  bool fileIsInDifferentLocations(const eos::IFileMD* fmd);\n\npublic:\n\n  // ---------------------------------------------------------------------------\n  // Constructor (per space)\n  // ---------------------------------------------------------------------------\n  GeoBalancer(const char* spacename);\n\n  // ---------------------------------------------------------------------------\n  // Destructor\n  // ---------------------------------------------------------------------------\n  ~GeoBalancer();\n\n  // ---------------------------------------------------------------------------\n  // thread stop function\n  // ---------------------------------------------------------------------------\n  void Stop();\n\n  // ---------------------------------------------------------------------------\n  // Service implementation e.g. eternal conversion loop running third-party\n  // conversion\n  // ---------------------------------------------------------------------------\n  void GeoBalance(ThreadAssistant& assistant) noexcept;\n};\n\nEOSMGMNAMESPACE_END\n#endif\n"
  },
  {
    "path": "mgm/geotree/SchedulingFastTree.hh",
    "content": "//------------------------------------------------------------------------------\n// @file SchedulingFastTree.hh\n// @author Geoffray Adde - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_FASTTREE__H__\n#include \"mgm/geotree/SchedulingTreeCommon.hh\"\n#include \"common/utils/RandUtils.hh\"\n#include <cstddef>\n#include <ostream>\n#include <string>\n#include <vector>\n#include <cassert>\n#include <cstring>\n#include <cstdlib>\n#include <iostream>\n#include <sstream>\n#include <iomanip>\n#include <algorithm>\n#include <limits>\n#define __EOSMGM_FASTTREE__H__\n\n#define DEFINE_TREECOMMON_MACRO\n\n#if __EOSMGM_TREECOMMON__PACK__STRUCTURE__==1\n#pragma pack(push,1)\n#endif\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file SchedulingFastTree.hh\n *\n * @brief Class representing the geotag-based tree structure of a scheduling group\n *\n * There are two representations of this tree structure:\n * - the first one defined is SchedulingSlowTree.hh\n *   is flexible and the tree can be shaped easily\n *   on the other hand, it's big and possibly scattered in the memory, so its\n *   access speed might be low\n * - the second one is a set a compact and fast structures (defined in the current file)\n *   these structure ares compact and contiguous in memory which makes them fast\n *   the shape of the underlying tree cannot be changed once they are constructed\n * Typically, a tree is constructed using the first representation (also referred as \"slow\").\n * Then, a representation of the second kind (also referred as \"fast\") is created from the\n * previous. It's then used to issue all the file scheduling operations at a high throughput (MHz)\n *\n */\n\nEOSMGMNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Class mapping a geotag to the closest node in a FastTree\n *        This closest node is described by its index in the FastTree\n *\n */\n/*----------------------------------------------------------------------------*/\nclass GeoTag2NodeIdxMap : public SchedTreeBase\n{\n  friend class SlowTree;\n  friend class SlowTreeNode;\n\n  static const size_t gMaxTagSize = 9; // 8+1\n  struct Node {\n    char tag[gMaxTagSize + 1];\n    tFastTreeIdx fastTreeIndex;\n    tFastTreeIdx firstBranch;\n    tFastTreeIdx branchCount;\n  };\n\n  bool pSelfAllocated;\n  tFastTreeIdx pMaxSize;\n  tFastTreeIdx pSize;\n  Node* pNodes;\n\n  // !!! numbering in GeoTag order\n  void\n  search(const char* tag, tFastTreeIdx& startFrom) const\n  {\n    eos_static_debug(\"tag=%s | startFrom=%d\", tag, (int)startFrom);\n\n    if (*tag == 0) {\n      return;\n    }\n\n    int cmp;\n    unsigned char k = 0;\n\n    while (tag[k + 1] != 0 && !(tag[k + 1] == ':' && tag[k] == ':') &&\n           k < gMaxTagSize) {\n      k++;\n    }\n\n    unsigned char strl;\n    bool godeeper = false;\n\n    if (tag[k] == ':' && tag[k + 1] == ':') {\n      strl = k;\n      godeeper = true;\n    } else {\n      strl = (unsigned char)(((size_t)(k + 1)) < gMaxTagSize) ?\n             (k + 1) : gMaxTagSize;\n    }\n\n    // dichotomy search on the label\n    tFastTreeIdx left = pNodes[startFrom].firstBranch;\n    tFastTreeIdx right = pNodes[startFrom].firstBranch +\n                         pNodes[startFrom].branchCount - 1;\n    char* lefts = pNodes[left].tag;\n    char* rights = pNodes[right].tag;\n\n    // narrow down the interval\n    while (right - left > 1) {\n      tFastTreeIdx mid = (left + right) / 2;\n      char* mids = pNodes[mid].tag;\n      cmp = strncmp(mids, tag, strl);\n\n      if (cmp < 0) {\n        left = mid;\n        lefts = pNodes[mid].tag;\n      } else if (!cmp) {\n        startFrom = mid;\n        goto next;\n      } else {\n        right = mid;\n        rights = pNodes[mid].tag;\n      }\n    }\n\n    // check the final interval\n    if (!strncmp(lefts, tag, strl)) {\n      //startFrom = pNodes[left].mFirstBranch;\n      startFrom = left;\n      goto next;\n    } else if (!strncmp(rights, tag, strl)) {\n      startFrom = right;\n      goto next;\n    } else {\n      return;\n    }\n\nnext:\n\n    if (godeeper) {\n      search(tag + k + 2, startFrom);\n    }\n\n    return;\n  }\n\npublic:\n  void print(char* buf)\n  {\n    for (int i = 0; i < pSize; i++) {\n      buf += sprintf(buf, \"%s %d %d %d\\n\", (const char*)&pNodes[i].tag[0],\n                     (int)pNodes[i].fastTreeIndex, (int)pNodes[i].firstBranch,\n                     (int)pNodes[i].branchCount);\n    }\n  }\n\n  GeoTag2NodeIdxMap()\n  {\n    pMaxSize = 0;\n    pSize = 0;\n    pNodes = 0;\n    pSelfAllocated = false;\n  }\n\n  ~GeoTag2NodeIdxMap()\n  {\n    if (pSelfAllocated) {\n      selfUnallocate();\n    }\n  }\n\n  tFastTreeIdx copyToGeoTag2NodeIdxMap(GeoTag2NodeIdxMap* dest) const\n  {\n    if (dest->pMaxSize < pSize) {\n      return pSize;\n    }\n\n    dest->pSize = pSize;\n    // copy the data\n    memcpy(dest->pNodes, pNodes, sizeof(struct Node)*pSize);\n    return 0;\n  }\n\n  bool\n  selfAllocate(tFastTreeIdx size)\n  {\n    pSelfAllocated = true;\n    pMaxSize = size;\n    pNodes = new Node[size];\n    return true;\n  }\n  bool\n  selfUnallocate()\n  {\n    delete[] pNodes;\n    return true;\n  }\n\n  inline tFastTreeIdx\n  getMaxNodeCount() const\n  {\n    return pMaxSize;\n  }\n\n  inline tFastTreeIdx\n  getNodeCount() const\n  {\n    return pSize;\n  }\n\n  inline tFastTreeIdx\n  getClosestFastTreeNode(const char* tag) const\n  {\n    tFastTreeIdx node = 0;\n    search(tag, node);\n    return pNodes[node].fastTreeIndex;\n  }\n};\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Class mapping an FsId to its position in a FastTree\n *        This position is described by the index of the corresponding node\n *        in the FastTree. The FsId is templated.\n *\n */\n/*----------------------------------------------------------------------------*/\ntemplate<typename T>\nclass FsId2NodeIdxMap : public SchedTreeBase\n{\n  friend class SlowTree;\n  friend class SlowTreeNode;\n\n  // size\n  tFastTreeIdx pMaxSize;\n  tFastTreeIdx pSize;\n  bool pSelfAllocated;\n\n  // data\n  T* pFsIds;\n  tFastTreeIdx* pNodeIdxs;\n\npublic:\n  // creation, allocation, destruction\n  FsId2NodeIdxMap():\n    pMaxSize(0), pSize(0), pSelfAllocated(false), pFsIds(0), pNodeIdxs(0)\n  {}\n\n  ~ FsId2NodeIdxMap()\n  {\n    if (pSelfAllocated) {\n      selfUnallocate();\n    }\n  }\n\n  tFastTreeIdx copyToFsId2NodeIdxMap(FsId2NodeIdxMap* dest) const\n  {\n    if (dest->pMaxSize < pSize) {\n      return pSize;\n    }\n\n    dest->pSize = pSize;\n    // copy the data\n    memcpy(dest->pFsIds, pFsIds, sizeof(T)*pSize);\n    memcpy(dest->pNodeIdxs, pNodeIdxs, sizeof(tFastTreeIdx)*pSize);\n    return 0;\n  }\n\n  bool\n  selfAllocate(tFastTreeIdx size)\n  {\n    pSelfAllocated = true;\n    pMaxSize = size;\n    pFsIds = (T*) new char[(sizeof(T) + sizeof(tFastTreeIdx)) * size];\n    pNodeIdxs = (tFastTreeIdx*)(pFsIds + size);\n    return true;\n  }\n  bool\n  selfUnallocate()\n  {\n    delete[] pFsIds;\n    return true;\n  }\n\n  bool\n  get(const T& fsid, const tFastTreeIdx*& idx) const\n  {\n    tFastTreeIdx left = 0;\n    tFastTreeIdx right = pSize - 1;\n\n    if (!pSize || fsid > pFsIds[right] || fsid < pFsIds[left]) {\n      return false;\n    }\n\n    if (fsid == pFsIds[right]) {\n      idx = &pNodeIdxs[right];\n      return true;\n    }\n\n    while (right - left > 1) {\n      tFastTreeIdx mid = (left + right) / 2;\n\n      if (fsid < pFsIds[mid]) {\n        right = mid;\n      } else {\n        left = mid;\n      }\n    }\n\n    if (fsid == pFsIds[left]) {\n      idx = &pNodeIdxs[left];\n      return true;\n    }\n\n    return false;\n  }\n\n  class const_iterator\n  {\n    friend class FsId2NodeIdxMap;\n    T* pFsIdPtr;\n    tFastTreeIdx* pNodeIdxPtr;\n    const_iterator(T* const& fsidPtr, tFastTreeIdx* const& nodeIdxPtr) :\n      pFsIdPtr(fsidPtr), pNodeIdxPtr(nodeIdxPtr)\n    {\n    }\n  public:\n    const_iterator() :\n      pFsIdPtr(NULL), pNodeIdxPtr(NULL)\n    {\n    }\n    const_iterator&\n    operator =(const const_iterator& it)\n    {\n      pFsIdPtr = it.pFsIdPtr;\n      pNodeIdxPtr = it.pNodeIdxPtr;\n      return *this;\n    }\n    const_iterator&\n    operator ++()\n    {\n      pFsIdPtr++;\n      pNodeIdxPtr++;\n      return *this;\n    }\n    const_iterator\n    operator ++(int unused)\n    {\n      pFsIdPtr++;\n      pNodeIdxPtr++;\n      return const_iterator(pFsIdPtr - 1, pNodeIdxPtr - 1);\n    }\n    bool\n    operator ==(const const_iterator& it) const\n    {\n      return pNodeIdxPtr == it.pNodeIdxPtr && pFsIdPtr == it.pFsIdPtr;\n    }\n    bool\n    operator !=(const const_iterator& it) const\n    {\n      return pNodeIdxPtr != it.pNodeIdxPtr || pFsIdPtr != it.pFsIdPtr;\n    }\n    std::pair<T, tFastTreeIdx>\n    operator *() const\n    {\n      return std::make_pair(*pFsIdPtr, *pNodeIdxPtr);\n    }\n  };\n\n  inline const_iterator\n  begin() const\n  {\n    return const_iterator(pFsIds, pNodeIdxs);\n  }\n  inline const_iterator\n  end() const\n  {\n    return const_iterator(pFsIds + pSize, pNodeIdxs + pSize);\n  }\n\n};\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Class mapping an FsId to its position in a FastTree\n *        This position is described by the index of the corresponding node\n *        in the FastTree. This is a specalized template for string (as opposed\n *        to numerics)\n *\n */\n/*----------------------------------------------------------------------------*/\ntemplate<>\nclass FsId2NodeIdxMap<char*> : public SchedTreeBase\n{\n  friend class SlowTree;\n  friend class SlowTreeNode;\n\n  // size\n  static const size_t pStrLen = 64;\n  tFastTreeIdx pMaxSize;\n  tFastTreeIdx pSize;\n  bool pSelfAllocated;\n\n  // data\n  char* pBuffer;\n  //char **pFsIds; that should be pFsIds[i] = pBuffer+pStrLen*i;\n  tFastTreeIdx* pNodeIdxs;\n\npublic:\n  // creation, allocation, destruction\n  FsId2NodeIdxMap():\n    pMaxSize(0), pSize(0), pSelfAllocated(false), pBuffer(NULL), pNodeIdxs(0)\n  { }\n\n  ~ FsId2NodeIdxMap()\n  {\n    if (pSelfAllocated) {\n      selfUnallocate();\n    }\n  }\n\n  tFastTreeIdx copyToFsId2NodeIdxMap(FsId2NodeIdxMap* dest) const\n  {\n    if (dest->pMaxSize < pSize) {\n      return pSize;\n    }\n\n    dest->pSize = pSize;\n    // copy the data\n    memcpy(dest->pNodeIdxs, pNodeIdxs, sizeof(tFastTreeIdx)*pSize);\n    memcpy(dest->pBuffer, pBuffer, sizeof(char) * pSize * pStrLen);\n    return 0;\n  }\n\n  bool\n  selfAllocate(tFastTreeIdx size)\n  {\n    pSelfAllocated = true;\n    pMaxSize = size;\n    //pFsIds = (T*) new char[(sizeof(T) + sizeof(tFastTreeIdx)) * size];\n    pBuffer = new char[(pStrLen + sizeof(tFastTreeIdx)) * size];\n    pNodeIdxs = (tFastTreeIdx*)(pBuffer + size * pStrLen);\n    return true;\n  }\n  bool\n  selfUnallocate()\n  {\n    delete[] pBuffer;\n    return true;\n  }\n\n  bool\n  get(const char* fsid, const tFastTreeIdx*& idx) const\n  {\n    if (!pSize) {\n      return false;\n    }\n\n    tFastTreeIdx left = 0;\n    tFastTreeIdx right = pSize - 1;\n    auto cmpRqLeft = strcmp(fsid, pBuffer + left * pStrLen);\n    auto cmpRqRight = strcmp(fsid, pBuffer + right * pStrLen);\n\n    if (cmpRqRight > 0 || cmpRqLeft < 0) {\n      return false;\n    }\n\n    if (cmpRqRight == 0) {\n      idx = &pNodeIdxs[right];\n      return true;\n    }\n\n    while (right - left > 1) {\n      tFastTreeIdx mid = (left + right) / 2;\n      auto cmpRqMid = strcmp(fsid, pBuffer + mid * pStrLen);\n\n      if (cmpRqMid < 0) {\n        right = mid;\n        cmpRqRight = cmpRqMid;\n      } else {\n        left = mid;\n        cmpRqLeft = cmpRqMid;\n      }\n    }\n\n    if (cmpRqLeft == 0) {\n      idx = &pNodeIdxs[left];\n      return true;\n    }\n\n    return false;\n  }\n\n  class const_iterator\n  {\n    friend class FsId2NodeIdxMap<char*>;\n    char* pFsId;\n    tFastTreeIdx* pNodeIdxPtr;\n    const_iterator(char* const& fsid, tFastTreeIdx* const& nodeIdxPtr) :\n      pFsId(fsid), pNodeIdxPtr(nodeIdxPtr)\n    {\n    }\n  public:\n    const_iterator() :\n      pFsId(NULL), pNodeIdxPtr(NULL)\n    {\n    }\n    const_iterator&\n    operator =(const const_iterator& it)\n    {\n      pFsId = it.pFsId;\n      pNodeIdxPtr = it.pNodeIdxPtr;\n      return *this;\n    }\n    const_iterator&\n    operator ++()\n    {\n      pFsId += FsId2NodeIdxMap<char*>::pStrLen;\n      pNodeIdxPtr++;\n      return *this;\n    }\n    const_iterator\n    operator ++(int unused)\n    {\n      pFsId += FsId2NodeIdxMap<char*>::pStrLen;\n      pNodeIdxPtr++;\n      return const_iterator(pFsId - 1, pNodeIdxPtr - 1);\n    }\n    bool\n    operator ==(const const_iterator& it) const\n    {\n      return pNodeIdxPtr == it.pNodeIdxPtr && pFsId == it.pFsId;\n    }\n    bool\n    operator !=(const const_iterator& it) const\n    {\n      return pNodeIdxPtr != it.pNodeIdxPtr || pFsId != it.pFsId;\n    }\n    std::pair<char*, tFastTreeIdx>\n    operator *() const\n    {\n      return std::make_pair(pFsId, *pNodeIdxPtr);\n    }\n  };\n\n  inline const_iterator\n  begin() const\n  {\n    return const_iterator(pBuffer, pNodeIdxs);\n  }\n  inline const_iterator\n  end() const\n  {\n    return const_iterator(pBuffer + pSize * pStrLen, pNodeIdxs + pSize);\n  }\n\n};\n\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Implementation of FsId2NodeIdxMap with the default FsId type.\n *\n */\n/*----------------------------------------------------------------------------*/\ntypedef FsId2NodeIdxMap<eos::common::FileSystem::fsid_t> Fs2TreeIdxMap;\ntypedef FsId2NodeIdxMap<char*>                           Host2TreeIdxMap;\n\nstd::ostream&\noperator <<(std::ostream& os, const Fs2TreeIdxMap& info);\ninline std::ostream&\noperator <<(std::ostream& os, const Fs2TreeIdxMap& info)\n{\n  // inline because of GCC MAP BUG : end iterator is corrupted when moved to c file\n  for (Fs2TreeIdxMap::const_iterator it = info.begin(); it != info.end(); it++) {\n    os << std::setfill(' ') << \"fs=\" << std::setw(20) << (*it).first << \" -> \" <<\n       \"idx=\" << (int)(*it).second << std::endl;\n  }\n\n  return os;\n}\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Functor Class to define relative priorities of branches in\n *        the fast tree for file placement.\n *\n */\n/*----------------------------------------------------------------------------*/\nclass PlacementPriorityComparator\n{\npublic:\n  char saturationThresh;\n  char spreadingFillRatioCap, fillRatioCompTol;\n  PlacementPriorityComparator() : saturationThresh(0), spreadingFillRatioCap(0),\n    fillRatioCompTol(0)\n  {\n  }\n  inline signed char\n  operator()(const SchedTreeBase::TreeNodeStateChar* const& lefts,\n             const SchedTreeBase::TreeNodeSlots* const& leftp,\n             const SchedTreeBase::TreeNodeStateChar* const& rights,\n             const SchedTreeBase::TreeNodeSlots* const& rightp) const\n  {\n    return SchedTreeBase::comparePlct<char>(lefts, leftp, rights, rightp,\n                                            spreadingFillRatioCap, fillRatioCompTol);\n  }\n\n  inline bool isValidSlot(const SchedTreeBase::TreeNodeStateChar* const& s,\n                          const SchedTreeBase::TreeNodeSlots* const& p) const\n  {\n    const int16_t mask = SchedTreeBase::Available | SchedTreeBase::Writable;\n    return !(SchedTreeBase::Disabled & s->mStatus) &&\n           (mask == (s->mStatus & mask)) && (p->freeSlotsCount > 0);\n  }\n\n  inline bool isSaturatedSlot(const SchedTreeBase::TreeNodeStateChar* const& s,\n                              const SchedTreeBase::TreeNodeSlots* const& p) const\n  {\n    return s->dlScore < saturationThresh;\n  }\n};\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Functor Class to define relative weights of branches in the tree\n *        having the same priority. This weight is used for file placement\n *        by random sampling in all the branches having the same priority.\n *\n */\n/*----------------------------------------------------------------------------*/\nclass PlacementPriorityRandWeightEvaluator\n{\npublic:\n  PlacementPriorityRandWeightEvaluator()\n  {\n  }\n  inline unsigned char\n  operator()(const SchedTreeBase::TreeNodeStateChar& state,\n             const SchedTreeBase::TreeNodeSlots& plct) const\n  {\n    //return state.dlScore;\n    return plct.maxDlScore;\n  }\n};\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Functor Class to define relative priorities of branches in\n *        the fast tree for file placement in draining.\n *\n */\n/*----------------------------------------------------------------------------*/\nclass DrainingPlacementPriorityComparator\n{\npublic:\n  char saturationThresh;\n  char spreadingFillRatioCap, fillRatioCompTol;\n  DrainingPlacementPriorityComparator() : saturationThresh(0),\n    spreadingFillRatioCap(0), fillRatioCompTol(0)\n  {\n  }\n  inline signed char\n  operator()(const SchedTreeBase::TreeNodeStateChar* const& lefts,\n             const SchedTreeBase::TreeNodeSlots* const& leftp,\n             const SchedTreeBase::TreeNodeStateChar* const& rights,\n             const SchedTreeBase::TreeNodeSlots* const& rightp) const\n  {\n    return SchedTreeBase::compareDrnPlct<char>(lefts, leftp, rights, rightp,\n           spreadingFillRatioCap, fillRatioCompTol);\n  }\n\n  inline bool isValidSlot(const SchedTreeBase::TreeNodeStateChar* const& s,\n                          const SchedTreeBase::TreeNodeSlots* const& p) const\n  {\n    const int16_t mask = SchedTreeBase::Available | SchedTreeBase::Writable |\n                         SchedTreeBase::Drainer;\n    return !(SchedTreeBase::Disabled & s->mStatus) &&\n           (mask == (s->mStatus & mask)) && (p->freeSlotsCount > 0);\n  }\n\n  inline bool isSaturatedSlot(const SchedTreeBase::TreeNodeStateChar* const& s,\n                              const SchedTreeBase::TreeNodeSlots* const& p) const\n  {\n    return s->dlScore < saturationThresh;\n  }\n};\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Functor Class to define relative weights of branches in the tree\n *        having the same priority. This weight is used for file placement\n *        in draining by random sampling in all the branches having\n *        the same priority. It's the same as the general file placement case\n *\n */\n/*----------------------------------------------------------------------------*/\ntypedef PlacementPriorityRandWeightEvaluator\nDrainingPlacementPriorityRandWeightEvaluator;\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Functor Class to define relative priorities of branches in\n *        the fast tree for Read-Only file access.\n *\n */\n/*----------------------------------------------------------------------------*/\nclass ROAccessPriorityComparator\n{\npublic:\n  SchedTreeBase::tFastTreeIdx saturationThresh;\n  ROAccessPriorityComparator() : saturationThresh(0)\n  {\n  }\n  inline signed char\n  operator()(const SchedTreeBase::TreeNodeStateChar* const& lefts,\n             const SchedTreeBase::TreeNodeSlots* const& leftp,\n             const SchedTreeBase::TreeNodeStateChar* const& rights,\n             const SchedTreeBase::TreeNodeSlots* const& rightp) const\n  {\n    return SchedTreeBase::compareAccessRO<char>(lefts, leftp, rights, rightp);\n  }\n\n  inline bool isValidSlot(const SchedTreeBase::TreeNodeStateChar* const& s,\n                          const SchedTreeBase::TreeNodeSlots* const& p) const\n  {\n    const int16_t mask = SchedTreeBase::Available | SchedTreeBase::Readable;\n    return !(SchedTreeBase::Disabled & s->mStatus) &&\n           (mask == (s->mStatus & mask)) && (p->freeSlotsCount > 0);\n  }\n\n  inline bool isSaturatedSlot(const SchedTreeBase::TreeNodeStateChar* const& s,\n                              const SchedTreeBase::TreeNodeSlots* const& p) const\n  {\n    return s->ulScore < saturationThresh;\n  }\n};\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Functor Class to define relative priorities of branches in\n *        the fast tree for file access in draining.\n *\n */\n/*----------------------------------------------------------------------------*/\nclass DrainingAccessPriorityComparator\n{\npublic:\n  SchedTreeBase::tFastTreeIdx saturationThresh;\n  DrainingAccessPriorityComparator() : saturationThresh(0)\n  {\n  }\n  inline signed char\n  operator()(const SchedTreeBase::TreeNodeStateChar* const& lefts,\n             const SchedTreeBase::TreeNodeSlots* const& leftp,\n             const SchedTreeBase::TreeNodeStateChar* const& rights,\n             const SchedTreeBase::TreeNodeSlots* const& rightp) const\n  {\n    return SchedTreeBase::compareAccessDrain<char>(lefts, leftp, rights, rightp);\n  }\n\n  inline bool isValidSlot(const SchedTreeBase::TreeNodeStateChar* const& s,\n                          const SchedTreeBase::TreeNodeSlots* const& p) const\n  {\n    const int16_t mask = SchedTreeBase::Available | SchedTreeBase::Readable;\n    const int16_t mask2 = SchedTreeBase::Available | SchedTreeBase::Draining;\n    return !(SchedTreeBase::Disabled & s->mStatus)\n           && ((mask == (s->mStatus & mask)) || (mask2 == (s->mStatus & mask2)))\n           && (p->freeSlotsCount > 0);\n  }\n\n  inline bool isSaturatedSlot(const SchedTreeBase::TreeNodeStateChar* const& s,\n                              const SchedTreeBase::TreeNodeSlots* const& p) const\n  {\n    return s->ulScore < saturationThresh;\n  }\n};\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Functor Class to define relative priorities of branches in\n *        the fast tree for gateway selection\n *\n */\n/*----------------------------------------------------------------------------*/\nclass GatewayPriorityComparator\n{\npublic:\n  SchedTreeBase::tFastTreeIdx saturationThresh;\n  GatewayPriorityComparator() : saturationThresh(0)\n  {\n  }\n  inline signed char\n  operator()(const SchedTreeBase::TreeNodeStateChar* const& lefts,\n             const SchedTreeBase::TreeNodeSlots* const& leftp,\n             const SchedTreeBase::TreeNodeStateChar* const& rights,\n             const SchedTreeBase::TreeNodeSlots* const& rightp) const\n  {\n    return SchedTreeBase::compareGateway<char>(lefts, leftp, rights, rightp);\n  }\n\n  inline bool isValidSlot(const SchedTreeBase::TreeNodeStateChar* const& s,\n                          const SchedTreeBase::TreeNodeSlots* const& p) const\n  {\n    const int16_t mask = SchedTreeBase::Available;\n    return !(SchedTreeBase::Disabled & s->mStatus) && (mask == (s->mStatus & mask));\n  }\n\n  inline bool isSaturatedSlot(const SchedTreeBase::TreeNodeStateChar* const& s,\n                              const SchedTreeBase::TreeNodeSlots* const& p) const\n  {\n    return s->ulScore < saturationThresh || s->dlScore < saturationThresh;\n  }\n};\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Functor Class to define relative priorities of branches in\n *        the fast tree for Read-Write file access.\n *\n */\n/*----------------------------------------------------------------------------*/\nclass RWAccessPriorityComparator\n{\npublic:\n  SchedTreeBase::tFastTreeIdx saturationThresh;\n  RWAccessPriorityComparator() : saturationThresh(0)\n  {\n  }\n  inline signed char\n  operator()(const SchedTreeBase::TreeNodeStateChar* const& lefts,\n             const SchedTreeBase::TreeNodeSlots* const& leftp,\n             const SchedTreeBase::TreeNodeStateChar* const& rights,\n             const SchedTreeBase::TreeNodeSlots* const& rightp) const\n  {\n    return SchedTreeBase::compareAccessRW<char>(lefts, leftp, rights, rightp);\n  }\n\n  inline bool isValidSlot(const SchedTreeBase::TreeNodeStateChar* const& s,\n                          const SchedTreeBase::TreeNodeSlots* const& p) const\n  {\n    const int16_t mask = SchedTreeBase::Available | SchedTreeBase::Readable |\n                         SchedTreeBase::Writable;\n    return !(SchedTreeBase::Disabled & s->mStatus) &&\n           (mask == (s->mStatus & mask)) && (p->freeSlotsCount > 0);\n  }\n\n  inline bool isSaturatedSlot(const SchedTreeBase::TreeNodeStateChar* const& s,\n                              const SchedTreeBase::TreeNodeSlots* const& p) const\n  {\n    return s->ulScore < saturationThresh || s->dlScore < saturationThresh;\n  }\n};\n\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Functor Class to define relative weights of branches in the tree\n *        having the same priority. This weight is used for file access\n *        by random sampling in all the branches having the same priority.\n *\n */\n/*----------------------------------------------------------------------------*/\nclass AccessPriorityRandWeightEvaluator\n{\npublic:\n  AccessPriorityRandWeightEvaluator()\n  {\n  }\n  inline unsigned char\n  operator()(const SchedTreeBase::TreeNodeStateChar& state,\n             const SchedTreeBase::TreeNodeSlots& plct) const\n  {\n    return plct.maxUlScore;\n  }\n};\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Functor Class to define relative weights of branches in the tree\n *        having the same priority. This weight is used for gateway selection\n *        by random sampling in all the branches having the same priority.\n *\n */\n/*----------------------------------------------------------------------------*/\nclass GatewayPriorityRandWeightEvaluator\n{\npublic:\n  GatewayPriorityRandWeightEvaluator()\n  {\n  }\n  inline unsigned char\n  operator()(const SchedTreeBase::TreeNodeStateChar& state,\n             const SchedTreeBase::TreeNodeSlots& plct) const\n  {\n    return (plct.maxUlScore / 2 + plct.maxDlScore / 2);\n  }\n};\n\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Functor Class to define relative weights of branches in the tree\n *        having the same priority. This weight is used for file access in\n *        draining. It's the same as the general file access case\n *\n */\n/*----------------------------------------------------------------------------*/\ntypedef AccessPriorityRandWeightEvaluator\nDrainingAccessPriorityRandWeightEvaluator;\n\ntemplate<typename FsDataMemberForRand, typename FsAndFileDataComparerForBranchSorting, typename FsIdType>\nstruct FastTreeBranchComparator;\ntemplate<typename FsDataMemberForRand, typename FsAndFileDataComparerForBranchSorting, typename FsIdType>\nstruct FastTreeBranchComparatorInv;\n\nstruct FastTreeBranch {\n  SchedTreeBase::tFastTreeIdx sonIdx;\n};\n\ntemplate<typename T1, typename T2, typename FsIdType = eos::common::FileSystem::fsid_t>\nclass FastTree;\ntemplate<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>\nsize_t\ncopyFastTree(FastTree<T1, T2, T3>* dest, const FastTree<T4, T5, T6>* src);\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief This is the generic fast tree class.\n *\n *        Every leaf in the tree hold information about free and taken slots.\n *        The main purpose of this class is to find a free slot and update this\n *        information very quickly.\n *        The way to do this is consistent at any depth in the tree:\n *        - Find the highest priority branch(es).\n *        - Among these highest priority branches, select one by weighted\n *          random sampling\n *        The class has two template arguments allowing to specify\n *        - the relative priority of branches\n *        - the weighting of these branches in the random sampling\n *        This very is then used to implement FastAcessTree and FastPlacementTree\n *        just by changing these templates arguments.\n *        The speed is achieved using a compact memory layout.\n *        Nodes of the tree (and the data they contain) are all aligned as a vector\n *        at the beginning of the class.\n *        After this vector, there is a second vector containing the branches.\n *        A branch is just a node number. There is as many branches as nodes (-1 actually).\n *        Each node contains the index of the first child branch in the branch vector\n *        and the number of branches it owns.\n *        For each node, its branches in the branch vector are kept in a\n *        decreasing priority order.\n *        Note that the compactness of the memory layout depends directly on\n *        the size of the typedef tFastTreeIdx. it also dictates the maximum\n *        number of nodes in the tree.\n *\n */\n/*----------------------------------------------------------------------------*/\ntemplate<typename FsDataMemberForRand, typename FsAndFileDataComparerForBranchSorting, typename FsIdType>\nclass FastTree : public SchedTreeBase\n{\npublic:\n  template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>\n  friend size_t\n  copyFastTree(FastTree<T1, T2, T3>* dest, const FastTree<T4, T5, T6>* src);\n  friend class SlowTree;\n  friend class SlowTreeNode;\n  friend class GeoTreeEngine;\n  friend struct TreeEntryMap;\n  friend struct FastStructures;\n  friend struct FsComparator;\n\n  typedef FastTreeBranch Branch;\n\n  typedef FastTree<FsDataMemberForRand, FsAndFileDataComparerForBranchSorting, FsIdType>\n  tSelf;\n  typedef TreeNodeStateChar FsData;\n\n  struct FileData : public TreeNodeSlots {\n    tFastTreeIdx lastHighestPriorityOffset;\n  };\n\n  struct TreeStructure {\n    tFastTreeIdx fatherIdx;\n    tFastTreeIdx firstBranchIdx;\n    tFastTreeIdx childrenCount;\n  };\n\n  struct FastTreeNode {\n    TreeStructure treeData;\n    FsData fsData;\n    FileData fileData;\n  };\nprotected:\n  // the layout of a FastTree in memory is just as follow\n  // 1xFastTreeNode then n1*Branch then 1*FastTreeNode then n2*Branch then ... then 1*FastBranch then np*Branch\n  // note that there is exactly p-1 branches overall because every node but the root node has exactly one branch as a father\n  // for this FastTree with p branches, the memory size is p*sizeof(FastTreeNode)+p*sizeof(Branch)\n  //          for 2 locations 1 building/location 1 room/building 30 racks overall 100 fs overall (135 nodes) 135*(9+2) -> 1485 bytes\n  bool pSelfAllocated;\n  tFastTreeIdx pMaxNodeCount;\n  tFastTreeIdx pNodeCount;\n  FastTreeNode* pNodes;\n  Branch* pBranches;\n\n  // outsourced data\n  FsId2NodeIdxMap<FsIdType>* pFs2Idx;\n  FastTreeInfo* pTreeInfo;\n\n  inline bool\n  FTLower(const FastTree::FsData* const& lefts,\n          const FastTree::FileData* const& leftp, const FastTree::FsData* const& rights,\n          const FastTree::FileData* const& rightp) const\n  {\n    return pBranchComp(lefts, static_cast<const TreeNodeSlots* const&>(leftp),\n                       rights, static_cast<const TreeNodeSlots* const&>(rightp)) > 0;\n  }\n\n  inline bool\n  FTGreater(const FastTree::FsData* const& lefts,\n            const FastTree::FileData* const& leftp, const FastTree::FsData* const& rights,\n            const FastTree::FileData* const& rightp) const\n  {\n    return pBranchComp(lefts, static_cast<const TreeNodeSlots* const&>(leftp),\n                       rights, static_cast<const TreeNodeSlots* const&>(rightp)) < 0;\n  }\n\npublic:\n  inline bool\n  FTLowerNode(const tFastTreeIdx& left, const tFastTreeIdx& right) const\n  {\n    return FTLower(&pNodes[left].fsData, &pNodes[left].fileData,\n                   &pNodes[right].fsData, &pNodes[right].fileData);\n  }\n\n  inline bool\n  FTGreaterNode(const tFastTreeIdx& left, const tFastTreeIdx& right) const\n  {\n    return FTGreater(&pNodes[left].fsData, &pNodes[left].fileData,\n                     &pNodes[right].fsData, &pNodes[right].fileData);\n  }\n\n  inline bool\n  isValidSlotNode(tFastTreeIdx node) const\n  {\n    return pBranchComp.isValidSlot(&pNodes[node].fsData, &pNodes[node].fileData);\n  }\n\n  inline bool\n  isSaturatedSlotNode(tFastTreeIdx node) const\n  {\n    return pBranchComp.isSaturatedSlot(&pNodes[node].fsData,\n                                       &pNodes[node].fileData);\n  }\n\nprotected:\n  inline bool\n  FTLowerBranch(const tFastTreeIdx& left, const tFastTreeIdx& right) const\n  {\n    return FTLower(&pNodes[pBranches[left].sonIdx].fsData,\n                   &pNodes[pBranches[left].sonIdx].fileData,\n                   &pNodes[pBranches[right].sonIdx].fsData,\n                   &pNodes[pBranches[right].sonIdx].fileData);\n  }\n\n  inline bool\n  isValidSlotBranch(tFastTreeIdx branch) const\n  {\n    return pBranchComp.isValidSlot(&pNodes[pBranches[branch].sonIdx].fsData,\n                                   &pNodes[pBranches[branch].sonIdx].fileData);\n  }\n\n  inline bool\n  FTEqual(const FastTree::FsData* const& lefts,\n          const FastTree::FileData* const& leftp, const FastTree::FsData* const& rights,\n          const FastTree::FileData* const& rightp) const\n  {\n    return pBranchComp(lefts, static_cast<const TreeNodeSlots* const&>(leftp),\n                       rights, static_cast<const TreeNodeSlots* const&>(rightp)) == 0;\n  }\n\n  inline bool\n  FTEqualNode(const tFastTreeIdx& left, const tFastTreeIdx& right) const\n  {\n    return FTEqual(&pNodes[left].fsData, &pNodes[left].fileData,\n                   &pNodes[right].fsData, &pNodes[right].fileData);\n  }\n\n  inline bool\n  FTEqualBranch(const tFastTreeIdx& left, const tFastTreeIdx& right) const\n  {\n    return FTEqual(&pNodes[pBranches[left].sonIdx].fsData,\n                   &pNodes[pBranches[left].sonIdx].fileData,\n                   &pNodes[pBranches[right].sonIdx].fsData,\n                   &pNodes[pBranches[right].sonIdx].fileData);\n  }\n\n  inline tFastTreeIdx\n  getRandomBranch(const tFastTreeIdx& node, bool* visited = NULL) const\n  {\n    eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n    const tFastTreeIdx& nBranches = pNodes[node].fileData.lastHighestPriorityOffset\n                                    + 1;\n\n    __EOSMGM_TREECOMMON_DBG3__ if (g_logging.gLogMask & LOG_MASK(\n                                     LOG_DEBUG)) {\n      std::stringstream ss;\n      ss << \"getRandomBranch at \" << (*pTreeInfo)[node] << \" choose among \" <<\n         (int) nBranches << std::endl;\n      eos_static_debug(ss.str().c_str());\n    }\n\n    int weightSum = 0;\n\n    for (tFastTreeIdx i = pNodes[node].treeData.firstBranchIdx;\n         i < pNodes[node].treeData.firstBranchIdx + nBranches; i++) {\n      const FastTreeNode& node = pNodes[pBranches[i].sonIdx];\n      weightSum += std::max(pRandVar(node.fsData, node.fileData), (unsigned char)0);\n    }\n\n    if (weightSum) {\n      int r = eos::common::getRandom();\n      r = r % (weightSum);\n      tFastTreeIdx i = 0;\n\n      for (weightSum = 0, i = pNodes[node].treeData.firstBranchIdx;\n           i < pNodes[node].treeData.firstBranchIdx + nBranches; i++) {\n        const FastTreeNode& node = pNodes[pBranches[i].sonIdx];\n        weightSum += pRandVar(node.fsData, node.fileData);\n\n        if (weightSum > r) {\n          break;\n        }\n      }\n\n      __EOSMGM_TREECOMMON_CHK1__\n      assert(i <= pNodes[node].treeData.firstBranchIdx +\n             pNodes[node].fileData.lastHighestPriorityOffset);\n      return pBranches[i].sonIdx;\n    } else {\n      // in this case all weights are 0 -> uniform probability\n      return pBranches[pNodes[node].treeData.firstBranchIdx +\n                                                            eos::common::getRandom() % nBranches].sonIdx;\n    }\n  }\n\n  inline bool\n  getRandomBranchGeneric(const tFastTreeIdx& brchBegIdx,\n                         const tFastTreeIdx& brchEndIdx, tFastTreeIdx* const& output,\n                         bool* visitedNode) const\n  {\n    if (brchBegIdx >= brchEndIdx) {\n      return false;\n    }\n\n    eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n\n    __EOSMGM_TREECOMMON_DBG3__ if (g_logging.gLogMask & LOG_MASK(\n                                     LOG_DEBUG)) {\n      std::stringstream ss;\n      ss << \"getRandomBranchGeneric from Branch \" << (int)brchBegIdx << \" to branch \"\n         << (int)brchEndIdx << std::endl;\n      eos_static_debug(ss.str().c_str());\n    }\n\n    int weightSum = 0;\n\n    for (tFastTreeIdx i = brchBegIdx; i < brchEndIdx; i++) {\n      tFastTreeIdx nodeIdx = pBranches[i].sonIdx;\n\n      if (!visitedNode[nodeIdx]) {\n        const FastTreeNode& node = pNodes[pBranches[i].sonIdx];\n        weightSum += pRandVar(node.fsData, node.fileData);\n      }\n    }\n\n    if (weightSum == 0) {\n      return false;\n    }\n\n    int r = eos::common::getRandom();\n    r = r % (weightSum);\n    tFastTreeIdx i = 0;\n\n    for (weightSum = 0, i = brchBegIdx; i < brchEndIdx; i++) {\n      tFastTreeIdx nodeIdx = pBranches[i].sonIdx;\n\n      if (!visitedNode[nodeIdx]) {\n        const FastTreeNode& node = pNodes[pBranches[i].sonIdx];\n        weightSum += pRandVar(node.fsData, node.fileData);\n\n        if (weightSum > r) {\n          break;\n        }\n      }\n    }\n\n    __EOSMGM_TREECOMMON_CHK1__\n    assert(i < brchEndIdx);\n    *output = pBranches[i].sonIdx;\n    return true;\n  }\n\n  inline tFastTreeIdx\n  findNewRank(tFastTreeIdx left, tFastTreeIdx right,\n              const tFastTreeIdx& modified) const\n  {\n    __EOSMGM_TREECOMMON_DBG3__ eos_static_debug(\"findNewRank: %d %d %d\\n\",\n        (int) left, (int) right, (int) modified);\n\n    if (right == left) {\n      return right;\n    }\n\n    bool firstiter = true;\n\n    while (true) {\n      if (!firstiter) {\n        assert(!FTLowerBranch(modified, right) && !FTLowerBranch(left, modified));\n      }\n\n      if (!firstiter && right - left == 1) {\n        assert(!FTLowerBranch(modified, right) && !FTLowerBranch(right - 1, modified));\n        return right;\n      }\n\n      if (left == modified) {\n        left = left + 1;\n      }\n\n      if (right == modified) {\n        right = right - 1;\n      }\n\n      if (!FTLowerNode(pBranches[modified].sonIdx, pBranches[left].sonIdx)) {\n        return left;\n      }\n\n      if (!FTLowerNode(pBranches[right].sonIdx, pBranches[modified].sonIdx)) {\n        return right + 1;  // which might not exist show that it should be at the end\n      }\n\n      tFastTreeIdx mid = (left + right) / 2;\n\n      if (mid == modified) {\n        // mid point should NOT be the middle point\n        if (mid + 1 > right) {\n          mid--;\n        } else {\n          mid++;\n        }\n      }\n\n      if (!FTLowerNode(pBranches[modified].sonIdx, pBranches[mid].sonIdx)) {\n        right = mid;\n      } else {\n        left = mid;\n      }\n\n      firstiter = false;\n    }\n  }\n\n  inline void\n  fixBranchSorting(const tFastTreeIdx& node,\n                   const tFastTreeIdx& modifiedBranchIdx)\n  {\n    __EOSMGM_TREECOMMON_CHK1__\n    assert(\n      modifiedBranchIdx >= pNodes[node].treeData.firstBranchIdx &&\n      modifiedBranchIdx < pNodes[node].treeData.firstBranchIdx +\n      pNodes[node].treeData.childrenCount);\n    const Branch& modifiedBranch = pBranches[modifiedBranchIdx];\n    const tFastTreeIdx& firstBranchIdx = pNodes[node].treeData.firstBranchIdx;\n    const tFastTreeIdx& nbChildren = pNodes[node].treeData.childrenCount;\n    tFastTreeIdx& lastHPOffset = pNodes[node].fileData.lastHighestPriorityOffset;\n    __EOSMGM_TREECOMMON_CHK3__\n    checkConsistency(0, false);\n\n    if (nbChildren < 2) {\n      return;\n    }\n\n    // if it's already ordered, nothing to do.\n    if ((modifiedBranchIdx == firstBranchIdx &&\n         !FTLowerBranch(modifiedBranchIdx, modifiedBranchIdx + 1))\n        || (modifiedBranchIdx == firstBranchIdx + nbChildren - 1 &&\n            !FTLowerBranch(modifiedBranchIdx - 1, modifiedBranchIdx))\n        || (!FTLowerBranch(modifiedBranchIdx, modifiedBranchIdx + 1) &&\n            !FTLowerBranch(modifiedBranchIdx - 1, modifiedBranchIdx))) {\n      goto update_and_return;\n    }\n\n    {\n      tFastTreeIdx newrank = findNewRank(firstBranchIdx,\n                                         firstBranchIdx + nbChildren - 1, modifiedBranchIdx);\n      __EOSMGM_TREECOMMON_DBG3__ eos_static_debug(\"findNewRank returned %d\\n\",\n          (int) newrank);\n      // in any other case, memory move is involved inside the branches array\n      // keep a copy of the branch\n      Branch modbr = modifiedBranch;\n\n      // move the appropriate range of branches\n      if (modifiedBranchIdx < newrank) {\n        memmove(&pBranches[modifiedBranchIdx], &pBranches[modifiedBranchIdx + 1],\n                (newrank - modifiedBranchIdx) * sizeof(Branch));\n        pBranches[newrank - 1] = modbr;\n      } else if (modifiedBranchIdx > newrank) {\n        memmove(&pBranches[newrank + 1], &pBranches[newrank],\n                (modifiedBranchIdx - newrank) * sizeof(Branch));\n        pBranches[newrank] = modbr;\n      }\n\n      // insert the modified branch\n    }\n\nupdate_and_return:\n    lastHPOffset = 0;\n\n    while (lastHPOffset < nbChildren - 1\n           && !FTLowerBranch(firstBranchIdx + lastHPOffset + 1,\n                             firstBranchIdx + lastHPOffset)) {\n      lastHPOffset++;\n    }\n\n    __EOSMGM_TREECOMMON_CHK3__\n    checkConsistency(0, true);\n    return;\n  }\n\n  inline void\n  fixBranchSortingHP(const tFastTreeIdx& node,\n                     const tFastTreeIdx& modifiedBranchIdx)\n  {\n    // this is an optimized version where the updated branch gets a lower or equal priority\n    // this version is typically called after finding a free slot (which is supposed to be HP by definition)\n    // all the branches between\n    // pBranches[pNodes[node].mTreeData.mFirstBranchIdx].mSonIdx;\n    // pBranches[pNodes[node].mTreeData.mFirstBranchIdx+pNodes[node].mFileData.mLastHighestPriorityIdx].mSonIdx;\n    // have the same priority\n    // modified branch modifiedBranch should be among those\n    const Branch& modifiedBranch = pBranches[modifiedBranchIdx];\n    const FsData& modifiedFsData = pNodes[modifiedBranch.sonIdx].fsData;\n    const FileData& modifiedFileData = pNodes[modifiedBranch.sonIdx].fileData;\n    const tFastTreeIdx& firstBranchIdx = pNodes[node].treeData.firstBranchIdx;\n    const tFastTreeIdx& nbChildren = pNodes[node].treeData.childrenCount;\n    tFastTreeIdx& lastHPOffset = pNodes[node].fileData.lastHighestPriorityOffset;\n    const bool modifiedIsInHp = modifiedBranchIdx <= firstBranchIdx + lastHPOffset;\n    __EOSMGM_TREECOMMON_CHK3__\n    checkConsistency(0, false);\n\n    // this function should not be called in that case\n    if (!nbChildren) {\n      return;\n    }\n\n    if (modifiedBranchIdx == firstBranchIdx + nbChildren -\n        1) { // nothing to do, the sorting is already the worst rated\n      goto update_and_return;\n    }\n\n    // if all the branches have the lowest priority level, the selected branches just go to the end\n    if (lastHPOffset == pNodes[node].treeData.childrenCount - 1) {\n      std::swap(pBranches[modifiedBranchIdx],\n                pBranches[firstBranchIdx + lastHPOffset]);\n      goto update_and_return;\n    }\n    // if the modified branch still have a highest or equal priority than the next priority level a swap is enough\n    else if ((modifiedBranchIdx <= firstBranchIdx + lastHPOffset) &&\n             // this one should ALWAYS be TRUE for a placement\n             !FTLower(&modifiedFsData, &(modifiedFileData),\n                      &pNodes[pBranches[firstBranchIdx + lastHPOffset + 1].sonIdx].fsData,\n                      &pNodes[pBranches[firstBranchIdx + lastHPOffset + 1].sonIdx].fileData)) {\n      std::swap(pBranches[modifiedBranchIdx],\n                pBranches[firstBranchIdx + lastHPOffset]);\n      goto update_and_return;\n    } else {\n      if (!modifiedIsInHp) {\n        return fixBranchSorting(node, modifiedBranchIdx);\n      }\n\n      // in any other case, memory move is involved inside the branches array\n      // find the first branch of which priority is lower than the modified branch\n      tFastTreeIdx insertionIdx;\n\n      for (insertionIdx = firstBranchIdx + lastHPOffset + 1;\n           insertionIdx < firstBranchIdx + nbChildren\n           && FTLower(&modifiedFsData, &modifiedFileData,\n                      &pNodes[pBranches[insertionIdx].sonIdx].fsData,\n                      &pNodes[pBranches[insertionIdx].sonIdx].fileData); insertionIdx++) {\n      }\n\n      // keep a copy of the branch\n      Branch modbr = modifiedBranch;\n      // move the appropriate range of branches\n      memmove(&pBranches[modifiedBranchIdx], &pBranches[modifiedBranchIdx + 1],\n              (insertionIdx - modifiedBranchIdx) * sizeof(Branch));\n      // insert the modified branch\n      pBranches[insertionIdx - 1] = modbr;\n    }\n\nupdate_and_return:\n\n    if (modifiedIsInHp && lastHPOffset > 0) {\n      // there is more than one branch having the highest priority, just decrement if the priority got lower\n      // the modified branch is at the end now and has been swapped with the modifiedBranch\n      if (FTLowerBranch(firstBranchIdx + lastHPOffset, firstBranchIdx)) {\n        lastHPOffset--;\n      }\n    } else {\n      // the modified node is the last one with the maximum priority\n      lastHPOffset = 0;\n\n      while (lastHPOffset < nbChildren - 1\n             && !FTLowerBranch(firstBranchIdx + lastHPOffset + 1,\n                               firstBranchIdx + lastHPOffset)) {\n        lastHPOffset++;\n      }\n    }\n\n    __EOSMGM_TREECOMMON_CHK3__\n    checkConsistency(0, true);\n    return;\n  }\n\npublic:\n  inline void\n  sortBranchesAtNode(const tFastTreeIdx& node, bool recursive = false)\n  {\n    FastTreeBranchComparator<FsDataMemberForRand, FsAndFileDataComparerForBranchSorting, FsIdType>\n    comparator(this);\n    FastTreeBranchComparatorInv<FsDataMemberForRand, FsAndFileDataComparerForBranchSorting, FsIdType>\n    comparator2(this);\n    const tFastTreeIdx& firstBranchIdx = pNodes[node].treeData.firstBranchIdx;\n    const tFastTreeIdx& nbChildren = pNodes[node].treeData.childrenCount;\n    tFastTreeIdx& lastHPOffset = pNodes[node].fileData.lastHighestPriorityOffset;\n\n    if (recursive)\n      for (SchedTreeBase::tFastTreeIdx b = firstBranchIdx;\n           b < firstBranchIdx + nbChildren; b++) {\n        sortBranchesAtNode(pBranches[b].sonIdx, true);\n      }\n\n    __EOSMGM_TREECOMMON_CHK3__\n    checkConsistency(node, false);\n\n    if (nbChildren < 2) {\n      return;\n    }\n\n    std::sort(pBranches + firstBranchIdx, pBranches + firstBranchIdx + nbChildren,\n              comparator);\n\n    // other possible types of sorting algorithm\n    //insertionSort(pBranches+firstBranchIdx,pBranches+firstBranchIdx+nbChildren,comparator2);\n    //bubbleSort(pBranches+firstBranchIdx,pBranches+firstBranchIdx+nbChildren,comparator2);\n    switch (nbChildren) {\n    case 2:\n      if (FTLowerBranch(firstBranchIdx + 1, firstBranchIdx)) {\n        lastHPOffset = 0;\n      } else {\n        lastHPOffset = 1;\n      }\n\n      break;\n\n    default:\n      Branch* ub = std::upper_bound(pBranches + firstBranchIdx + 1,\n                                    pBranches + firstBranchIdx + nbChildren, pBranches[firstBranchIdx], comparator);\n      lastHPOffset = (\n                       ub\n                       - (pBranches + firstBranchIdx + 1));\n      break;\n    }\n\n    __EOSMGM_TREECOMMON_CHK3__\n    checkConsistency(node, true);\n    return;\n  }\n\n  inline void\n  sortAllBranches()\n  {\n    sortBranchesAtNode(0, true);\n  }\n\n  bool aggregateFileData(const tFastTreeIdx& node)\n  {\n    pNodes[node].fileData.takenSlotsCount = 0;\n    pNodes[node].fileData.freeSlotsCount = 0;\n    unsigned long long sumUlScore = 0;\n    unsigned long long sumDlScore = 0;\n    pNodes[node].fileData.maxDlScore = 0;\n    pNodes[node].fileData.maxUlScore = 0;\n\n    for (tFastTreeIdx bidx = pNodes[node].treeData.firstBranchIdx;\n         bidx < pNodes[node].treeData.firstBranchIdx +\n         pNodes[node].treeData.childrenCount; bidx++) {\n      tFastTreeIdx childNode = pBranches[bidx].sonIdx;\n\n      if (pNodes[childNode].treeData.childrenCount ||\n          isValidSlotNode(childNode)) { // EXPERIMENT\n        pNodes[node].fileData.takenSlotsCount +=\n          pNodes[childNode].fileData.takenSlotsCount;\n        pNodes[node].fileData.freeSlotsCount +=\n          pNodes[childNode].fileData.freeSlotsCount;\n        sumUlScore += pNodes[childNode].fileData.avgUlScore *\n                      pNodes[childNode].fileData.freeSlotsCount;\n        sumDlScore += pNodes[childNode].fileData.avgDlScore *\n                      pNodes[childNode].fileData.freeSlotsCount;\n\n        if (pNodes[node].fileData.maxUlScore < pNodes[childNode].fileData.avgUlScore) {\n          pNodes[node].fileData.maxUlScore = pNodes[childNode].fileData.avgUlScore;\n        }\n\n        if (pNodes[node].fileData.maxDlScore < pNodes[childNode].fileData.avgDlScore) {\n          pNodes[node].fileData.maxDlScore = pNodes[childNode].fileData.avgDlScore;\n        }\n      }\n    }\n\n    pNodes[node].fileData.avgDlScore = pNodes[node].fileData.freeSlotsCount ?\n                                       sumDlScore / pNodes[node].fileData.freeSlotsCount : 0;\n    pNodes[node].fileData.avgUlScore = pNodes[node].fileData.freeSlotsCount ?\n                                       sumUlScore / pNodes[node].fileData.freeSlotsCount : 0;\n    return true;\n  }\n\n  bool aggregateFsData(const tFastTreeIdx& node)\n  {\n    double _mDlScore = 0;\n    double _mUlScore = 0;\n    double _mFillRatio = 0;\n    double _mTotalSpace = 0;\n    double _mTotalWritableSpace = 0;\n    int count = 0;\n\n    for (tFastTreeIdx bidx = pNodes[node].treeData.firstBranchIdx;\n         bidx < pNodes[node].treeData.firstBranchIdx +\n         pNodes[node].treeData.childrenCount; bidx++) {\n      tFastTreeIdx childNode = pBranches[bidx].sonIdx;\n      bool availableBranch = ((pNodes[childNode].fsData.mStatus &\n                               (Available | Disabled)) == Available);\n      bool writableBranch = ((pNodes[childNode].fsData.mStatus & Writable));\n\n      if (availableBranch) {\n        if (pNodes[childNode].fsData.dlScore > 0) {\n          _mDlScore += pNodes[childNode].fsData.dlScore;\n        }\n\n        if (pNodes[childNode].fsData.ulScore > 0) {\n          _mUlScore += pNodes[childNode].fsData.ulScore;\n        }\n\n        _mTotalSpace += pNodes[childNode].fsData.totalSpace;\n\n        if (writableBranch) {\n          _mTotalWritableSpace += pNodes[childNode].fsData.totalWritableSpace;\n        }\n\n        //_mFillRatio += pNodes[childNode].fsData.fillRatio *\n        //               pNodes[childNode].fsData.totalSpace;\n        _mFillRatio += pNodes[childNode].fsData.fillRatio;\n        count++;\n        // Not a good idea to propagate the availability as we want to be able\n        // to make a branch as unavailable regardless of the status of the leaves\n        // An intermediate node tell if a leave having is given status in under it or not\n        pNodes[node].fsData.mStatus =\n          (SchedTreeBase::tStatus)(pNodes[node].fsData.mStatus |\n                                   (pNodes[childNode].fsData.mStatus &\n                                    ~Available &  ~Disabled));\n      }\n    }\n\n    if (count) {\n      _mFillRatio /= count;\n    }\n\n    // testing the count is irrelevant but makes coverity happy\n    pNodes[node].fsData.dlScore = (char)(count ? _mDlScore / count : 0);\n    // testing the count is irrelevant but makes coverity happy\n    pNodes[node].fsData.ulScore = (char)(count ? _mUlScore / count : 0);\n    pNodes[node].fsData.fillRatio = (char)_mFillRatio;\n    pNodes[node].fsData.totalSpace = (float)_mTotalSpace;\n    pNodes[node].fsData.totalWritableSpace = (float)_mTotalWritableSpace;\n    return true;\n  }\n\n  inline void\n  updateBranch(const tFastTreeIdx& node)\n  {\n    if (pNodes[node].treeData.childrenCount) {\n      sortBranchesAtNode(node, false);\n      aggregateFsData(node);\n      aggregateFileData(node);\n    }\n\n    __EOSMGM_TREECOMMON_CHK3__\n    checkConsistency(0, true);\n\n    if (pNodes[node].treeData.fatherIdx != node) {\n      updateBranch(pNodes[node].treeData.fatherIdx);\n    }\n  }\n\n  uint64_t getTotalSpace(const tFastTreeIdx& node = 0)\n  {\n    return pNodes[node].fsData.totalSpace;\n  }\n\n  uint64_t getTotalWritableSpace(const tFastTreeIdx& node = 0)\n  {\n    return pNodes[node].fsData.totalWritableSpace;\n  }\n\n  inline void\n  updateTree(const tFastTreeIdx& node = 0)\n  {\n    const tFastTreeIdx& firstBranchIdx = pNodes[node].treeData.firstBranchIdx;\n    const tFastTreeIdx& nbChildren = pNodes[node].treeData.childrenCount;\n\n    for (SchedTreeBase::tFastTreeIdx b = firstBranchIdx;\n         b < firstBranchIdx + nbChildren; b++) {\n      updateTree(pBranches[b].sonIdx);\n    }\n\n    if (nbChildren < 2) {\n      pNodes[node].fileData.lastHighestPriorityOffset = 0;\n    }\n\n    if (nbChildren) {\n      sortBranchesAtNode(node, false);\n      aggregateFsData(node);\n      aggregateFileData(node);\n    }\n\n    pNodes[node].fileData.maxUlScore = pNodes[node].fsData.ulScore;\n    pNodes[node].fileData.maxDlScore = pNodes[node].fsData.dlScore;\n    pNodes[node].fileData.avgUlScore = pNodes[node].fsData.ulScore;\n    pNodes[node].fileData.avgDlScore = pNodes[node].fsData.dlScore;\n    __EOSMGM_TREECOMMON_CHK3__\n    checkConsistency(node, true);\n  }\n\n  inline tFastTreeIdx\n  getMaxNodeCount() const\n  {\n    return pMaxNodeCount;\n  }\n\n  inline static size_t sGetMaxDataMemSize()\n  {\n    return (sizeof(FastTreeNode) + sizeof(Branch)) * sGetMaxNodeCount();\n  }\n\n  inline tFastTreeIdx\n  getNodeCount() const\n  {\n    return pNodeCount;\n  }\n\n  inline tFastTreeIdx\n  findFreeSlotsAll(tFastTreeIdx* idxs, tFastTreeIdx sizeIdxs,\n                   tFastTreeIdx startFrom = 0, bool allowUpRoot = false,\n                   const int& maskStatus = None, tFastTreeIdx* upRootLevelsCount = NULL,\n                   tFastTreeIdx* upRootLevelsIdxs = NULL, tFastTreeIdx* upRootLevels = NULL) const\n  {\n    tFastTreeIdx sizeIdxsBak = sizeIdxs;\n\n    if (upRootLevelsIdxs) {\n      *upRootLevelsCount = 0;\n    }\n\n    if (_findFreeSlotsAll(idxs, sizeIdxs, startFrom, allowUpRoot, startFrom,\n                          maskStatus, upRootLevelsCount, upRootLevelsIdxs, upRootLevels, 0)) {\n      if (upRootLevelsIdxs) {\n        for (int k = 0; k < *upRootLevelsCount; k++) {\n          upRootLevelsIdxs[k] = sizeIdxsBak - upRootLevelsIdxs[k];\n        }\n      }\n\n      return sizeIdxsBak - sizeIdxs;\n    } else {\n      return 0;\n    }\n  }\n\n  inline bool\n  _findFreeSlotsAll(tFastTreeIdx*& idxs, tFastTreeIdx& sizeIdxs,\n                    tFastTreeIdx startFrom, bool allowUpRoot, tFastTreeIdx callerNode,\n                    const int& statusMask,\n                    tFastTreeIdx* upRootLevelsCount, tFastTreeIdx* upRootLevelsIdxs,\n                    tFastTreeIdx* upRootLevels, tFastTreeIdx currentUpRootLevel) const\n  {\n    if (!pNodes[startFrom].treeData.childrenCount) {\n      if (pNodes[startFrom].fileData.freeSlotsCount &&\n          ((pNodes[startFrom].fsData.mStatus & statusMask) == statusMask)) {\n        if (sizeIdxs) {\n          if (isValidSlotNode(startFrom)) {\n            // if the slot is free, it should be a valid one, see the explanation in findFreeSlot\n            if (upRootLevelsIdxs) {\n              if (*upRootLevelsCount == 0) {\n                upRootLevels[0] = currentUpRootLevel;\n                upRootLevelsIdxs[0] = sizeIdxs;\n                (*upRootLevelsCount)++;\n              } else if (upRootLevels[*upRootLevelsCount - 1] < currentUpRootLevel) {\n                upRootLevels[*upRootLevelsCount] = currentUpRootLevel;\n                upRootLevelsIdxs[*upRootLevelsCount] = sizeIdxs;\n                (*upRootLevelsCount)++;\n              }\n            }\n\n            *idxs = startFrom;\n            idxs++;\n            sizeIdxs--;\n          }\n        } else {\n          // no enough space to write all the replicas\n          // it should not happen when called from findFreeSlotsAll because it's checked there\n          return false;\n        }\n      }\n    }\n\n    for (tFastTreeIdx bidx = pNodes[startFrom].treeData.firstBranchIdx;\n         bidx < pNodes[startFrom].treeData.firstBranchIdx +\n         pNodes[startFrom].treeData.childrenCount; bidx++) {\n      if (pBranches[bidx].sonIdx == callerNode ||\n          !pNodes[pBranches[bidx].sonIdx].fileData.freeSlotsCount ||\n          !((pNodes[startFrom].fsData.mStatus & statusMask) == statusMask)) {\n        continue;\n      }\n\n      if (!_findFreeSlotsAll(idxs, sizeIdxs, pBranches[bidx].sonIdx, false, startFrom,\n                             statusMask, upRootLevelsCount, upRootLevelsIdxs, upRootLevels,\n                             currentUpRootLevel)) {\n        // something is wrong. It should not happen\n        // free slots are supposed to be there but none are found!\n        eos_static_crit(\"Inconsistency in FastGeoTree\");\n        return false;\n      }\n    }\n\n    if (allowUpRoot && startFrom) {\n      if (upRootLevelsIdxs) {\n        currentUpRootLevel++;\n      }\n\n      _findFreeSlotsAll(idxs, sizeIdxs, pNodes[startFrom].treeData.fatherIdx, true,\n                        startFrom, statusMask, upRootLevelsCount, upRootLevelsIdxs, upRootLevels,\n                        currentUpRootLevel);\n    }\n\n    return true;\n  }\n\n  inline void\n  checkConsistency(tFastTreeIdx node, bool checkOrder = true,\n                   bool recursive = true, std::map<tFastTreeIdx, tFastTreeIdx>* map = 0)\n  {\n    bool del = false;\n\n    if (map == 0) {\n      map = new std::map<tFastTreeIdx, tFastTreeIdx>;\n      del = true;\n    }\n\n    if (recursive) {\n      for (tFastTreeIdx bidx = pNodes[node].treeData.firstBranchIdx;\n           bidx < pNodes[node].treeData.firstBranchIdx +\n           pNodes[node].treeData.childrenCount;\n           bidx++) {\n        checkConsistency(pBranches[bidx].sonIdx, checkOrder, true, map);\n      }\n    }\n\n    assert(\n      pNodes[node].treeData.childrenCount == 0 ||\n      (pNodes[node].fileData.lastHighestPriorityOffset >= 0 &&\n       pNodes[node].fileData.lastHighestPriorityOffset <\n       pNodes[node].treeData.childrenCount));\n\n    // check that every node is referred at most once in a branch\n    for (tFastTreeIdx bidx = pNodes[node].treeData.firstBranchIdx;\n         bidx < pNodes[node].treeData.firstBranchIdx +\n         pNodes[node].treeData.childrenCount;\n         bidx++) {\n      // check that this node is not already referred\n      assert(!map->count(pBranches[bidx].sonIdx));\n      (*map)[pBranches[bidx].sonIdx] = node;// set the father in the map\n    }\n\n    // check the order is respected in the branches\n    if (checkOrder) {\n      bool checkedHpOfs = false;\n      tFastTreeIdx lastHpOfs = 0;\n\n      for (tFastTreeIdx bidx = pNodes[node].treeData.firstBranchIdx;\n           bidx < pNodes[node].treeData.firstBranchIdx +\n           pNodes[node].treeData.childrenCount - 1;\n           bidx++) {\n        assert(\n          //!FTLower( &pNodes[pBranches[bidx].mSonIdx].mFsData, &pNodes[pBranches[bidx].mSonIdx].mFileData, &pNodes[pBranches[bidx+1].mSonIdx].mFsData, &pNodes[pBranches[bidx+1].mSonIdx].mFileData )\n          !FTLowerBranch(bidx, bidx + 1)\n        );\n\n        if (!checkedHpOfs\n            && !FTEqual(&pNodes[pBranches[bidx].sonIdx].fsData,\n                        &pNodes[pBranches[bidx].sonIdx].fileData,\n                        &pNodes[pBranches[bidx + 1].sonIdx].fsData,\n                        &pNodes[pBranches[bidx + 1].sonIdx].fileData)) {\n          assert(lastHpOfs == pNodes[node].fileData.lastHighestPriorityOffset);\n          checkedHpOfs = true;\n        }\n\n        lastHpOfs++;\n      }\n\n      if (!checkedHpOfs && lastHpOfs) {\n        assert(pNodes[node].treeData.childrenCount - 1 ==\n               pNodes[node].fileData.lastHighestPriorityOffset);\n      }\n    }\n\n    if (del) {\n      delete map;\n    }\n  }\n\n  void\n  recursiveDisplay(std::set<std::tuple<std::string, unsigned, unsigned,\n                   TableFormatterColor, unsigned, unsigned, std::string,\n                   std::string, unsigned, std::string, int, int, int, std::string,\n                   int, int, int, double, double>>& data_snapshot,  unsigned& geo_depth_max,\n                   std::string operation = \"\", std::string operation_short = \"\",\n                   bool useColors = false)\n  {\n    if (pNodeCount && pNodes[0].treeData.childrenCount)\n      recursiveDisplay(data_snapshot, 0, \"\", geo_depth_max,\n                       operation, operation_short, useColors);\n  }\n\nprotected:\n  FsDataMemberForRand pRandVar;\n  FsAndFileDataComparerForBranchSorting pBranchComp;\npublic:\n  FastTree() :\n    pRandVar(), pBranchComp()\n  {\n    pMaxNodeCount = 0;\n    pNodeCount = 0;\n    pNodes = 0;\n    pBranches = 0;\n    pFs2Idx = 0;\n    pTreeInfo = 0;\n    pSelfAllocated = false;\n  }\n\n  ~FastTree()\n  {\n    if (pSelfAllocated) {\n      selfUnallocate();\n    }\n  }\n\n  void setSaturationThreshold(const char& thresh)\n  {\n    pBranchComp.saturationThresh = thresh;\n  }\n  void setSpreadingFillRatioCap(const char& cap)\n  {\n    pBranchComp.spreadingFillRatioCap = cap;\n  }\n  void setFillRatioCompTol(const char& tol)\n  {\n    pBranchComp.fillRatioCompTol = tol;\n  }\n  bool\n  selfAllocate(tFastTreeIdx size)\n  {\n    pMaxNodeCount = size;\n    size_t memsize = (sizeof(FastTreeNode) + sizeof(Branch)) * size;\n    __EOSMGM_TREECOMMON_DBG2__ eos_static_debug(\"self allocation size = %lu\\n\",\n        memsize);\n    pNodes = (FastTreeNode*) new char[memsize];\n    pBranches = (Branch*)(pNodes + size);\n    pSelfAllocated = true;\n    return true;\n  }\n  bool\n  selfUnallocate()\n  {\n    delete[] pNodes;\n    return true;\n  }\n\n  tSelf& operator = (const tSelf& model)\n  {\n    (*static_cast<SchedTreeBase*>(this)) = *static_cast<const SchedTreeBase*>\n                                           (&model);\n    this->pFs2Idx = model.pFs2Idx;\n    this->pNodeCount = model.pNodeCount;\n    this->pSelfAllocated = model.pSelfAllocated;\n    this->pTreeInfo = model.pTreeInfo;\n    this->pBranchComp = model.pBranchComp;\n    return *this;\n  }\n\n  size_t\n  copyToBuffer(char* buffer, size_t bufSize) const\n  {\n    size_t memsize = (sizeof(FastTreeNode) + sizeof(Branch)) * pNodeCount + sizeof(\n                       FastTree);\n\n    if (bufSize < memsize) {\n      return memsize;\n    }\n\n    // copy all the data members\n    tSelf* destFastTree = (tSelf*)(buffer);\n    // adjust the value of some of them\n    (*destFastTree) = *this;\n    destFastTree->pNodes = (FastTreeNode*)(buffer += sizeof(tSelf));\n    memcpy((void*)destFastTree->pNodes, pNodes,\n           (sizeof(FastTreeNode)) * pNodeCount);\n    destFastTree->pBranches = (Branch*)(buffer += sizeof(FastTreeNode) *\n                                        pNodeCount);\n    memcpy((void*)destFastTree->pBranches, pBranches,\n           (sizeof(Branch)) * pNodeCount);\n    return 0;\n  }\n\n  template<typename T1, typename T2, typename T3> size_t\n  copyToFastTree(FastTree<T1, T2, T3>* dest) const\n  {\n    return copyFastTree(dest, this);\n  }\n\n  void\n  recursiveDisplay(std::set<std::tuple<std::string, unsigned, unsigned,\n                   TableFormatterColor, unsigned, unsigned, std::string,\n                   std::string, unsigned, std::string, int, int, int, std::string,\n                   int, int, int, double, double>>& data_snapshot, tFastTreeIdx node,\n                   std::string group, unsigned& geo_depth_max,\n                   std::string operation = \"\", std::string operation_short = \"\",\n                   bool useColors = false, unsigned prefix1 = 0, unsigned prefix2 = 0)\n  {\n    TableFormatterColor color = NONE;\n\n    if (useColors) {\n      bool isReadable = (pNodes[node].fsData.mStatus & Readable);\n      bool isDisabled = (pNodes[node].fsData.mStatus & Disabled);\n      bool isWritable = (pNodes[node].fsData.mStatus & Writable);\n      bool isAvailable = (pNodes[node].fsData.mStatus & Available);\n      bool isDraining = (pNodes[node].fsData.mStatus & Draining);\n      bool isFs = ((*pTreeInfo)[node].nodeType) == TreeNodeInfo::fs;\n      bool isValid = false;\n\n      if (isFs) {\n        // we fake to have one slot available to see if the fs is really a valid slot\n        eos::mgm::SchedTreeBase::TreeNodeSlots freeSlot;\n        freeSlot.freeSlotsCount = 1;\n        isValid = pBranchComp.isValidSlot(&pNodes[node].fsData, &freeSlot);\n      }\n\n      if (isDisabled) { // DISABLED\n        color = DARK;\n      } else {\n        if (!isAvailable || (isFs && (!isValid))) { // UNAVAILABLE OR NOIO\n          color = (isFs && isDraining) ? BYELLOW_BGRED : BWHITE_BGRED;\n        } else if (isFs) {\n          if (isReadable && ! isWritable) { // RO case\n            color = (isFs && isDraining) ? BYELLOW_BGBLUE : BWHITE_BGBLUE;\n          } else if (!isReadable && isWritable) { // WO case\n            color = (isFs && isDraining) ? NONE : BWHITE_BGYELLOW;\n          } else {\n            color = (isFs && isDraining) ? BYELLOW : BWHITE;\n          }\n        } else {\n          color = (isFs && isDraining) ? BYELLOW : BWHITE;\n        }\n      }\n    }\n\n    tFastTreeIdx& nbChildren = pNodes[node].treeData.childrenCount;\n\n    if (!nbChildren) {\n      // Print fsid and node (depth=3)\n      data_snapshot.insert(std::make_tuple(group, data_snapshot.size(), 3, color,\n                                           prefix1, prefix2, operation, operation_short,\n                                           (*pTreeInfo)[node].fsId,\n                                           (*pTreeInfo)[node].host,\n                                           pNodes[node].fileData.freeSlotsCount,\n                                           pNodes[node].fileData.takenSlotsCount,\n                                           pNodes[node].fileData.lastHighestPriorityOffset,\n                                           fsStatusToStr(pNodes[node].fsData.mStatus),\n                                           pNodes[node].fsData.ulScore,\n                                           pNodes[node].fsData.dlScore,\n                                           pNodes[node].fsData.fillRatio,\n                                           pNodes[node].fsData.totalSpace,\n                                           pNodes[node].fsData.totalWritableSpace));\n    } else {\n      // Print group (depth=1) and geotag (depth=2)\n      unsigned depth = (prefix1 == 0 && prefix2 == 0) ? 1 : 2;\n      group = (prefix1 == 0 && prefix2 == 0) ? (*pTreeInfo)[node].geotag : group;\n      data_snapshot.insert(std::make_tuple(group, data_snapshot.size(), depth, color,\n                                           prefix1, prefix2, operation, operation_short, 0,\n                                           (*pTreeInfo)[node].fullGeotag,\n                                           pNodes[node].fileData.freeSlotsCount,\n                                           pNodes[node].fileData.takenSlotsCount,\n                                           pNodes[node].fileData.lastHighestPriorityOffset,\n                                           intermediateStatusToStr(pNodes[node].fsData.mStatus),\n                                           pNodes[node].fsData.ulScore,\n                                           pNodes[node].fsData.dlScore,\n                                           pNodes[node].fsData.fillRatio,\n                                           pNodes[node].fsData.totalSpace,\n                                           pNodes[node].fsData.totalWritableSpace));\n      // How many deep is geotag\n      unsigned geo_depth = 1;\n      std::string geotag_temp = (*pTreeInfo)[node].fullGeotag;\n\n      while (geotag_temp.find(\"::\") != std::string::npos) {\n        geotag_temp.erase(0, geotag_temp.find(\"::\") + 2);\n        geo_depth++;\n      }\n\n      geo_depth_max = (geo_depth_max < geo_depth) ? geo_depth : geo_depth_max;\n      tFastTreeIdx& firstBranchIdx = pNodes[node].treeData.firstBranchIdx;\n\n      for (tFastTreeIdx branchIdx = firstBranchIdx;\n           branchIdx < firstBranchIdx + nbChildren; branchIdx++) {\n        tFastTreeIdx childIdx = pBranches[branchIdx].sonIdx;\n        bool lastChild = (branchIdx == firstBranchIdx + nbChildren - 1);\n        unsigned prefix1_temp = (prefix2 == 3) ? 1 : 0;\n\n        if (lastChild) { // final branch\n          recursiveDisplay(data_snapshot, childIdx, group, geo_depth_max, operation,\n                           operation_short, useColors, prefix1_temp, 2);\n        } else { // intermediate branch\n          recursiveDisplay(data_snapshot, childIdx, group, geo_depth_max, operation,\n                           operation_short, useColors, prefix1_temp, 3);\n        }\n      }\n    }\n  }\n\n  void\n  decrementFreeSlot(tFastTreeIdx node, bool useHpSpeedUp = false)\n  {\n    // first update the node information\n    __EOSMGM_TREECOMMON_CHK1__\n    assert(pNodes[node].fileData.freeSlotsCount > 0);\n    __EOSMGM_TREECOMMON_CHK2__\n    checkConsistency(0);\n    pNodes[node].fileData.freeSlotsCount--;\n    pNodes[node].fileData.takenSlotsCount++;\n    //checkConsistency(0,false);\n\n    // if there is a father node, update its branches\n    if (node) {\n      tFastTreeIdx father = pNodes[node].treeData.fatherIdx;\n      tFastTreeIdx firstBranchIndex = pNodes[father].treeData.firstBranchIdx;\n      tFastTreeIdx nbBranches = pNodes[father].treeData.childrenCount;\n      tFastTreeIdx matchBranchIdx;\n\n      // first locate the branch (it should be in the first positions if it's a placement)\n      for (matchBranchIdx = firstBranchIndex;\n           matchBranchIdx < firstBranchIndex + nbBranches &&\n           pBranches[matchBranchIdx].sonIdx != node; matchBranchIdx++) {\n      }\n\n      __EOSMGM_TREECOMMON_CHK1__\n      assert(pBranches[matchBranchIdx].sonIdx == node);\n\n      // the branches are supposed to be ordered before the update\n      if (useHpSpeedUp) {\n        fixBranchSortingHP(father, matchBranchIdx);  // optimized for\n      } else {\n        fixBranchSorting(father, matchBranchIdx);\n      }\n\n      // finally iterate upper in the tree\n      decrementFreeSlot(father, useHpSpeedUp);\n    }\n  }\n\n  bool\n  findFreeSlotFirstHit(tFastTreeIdx& newReplica, tFastTreeIdx startFrom = 0,\n                       bool allowUpRoot = false, bool decrFreeSlot = true)\n  {\n    if (pNodes[startFrom].fileData.freeSlotsCount) {\n      if (!pNodes[startFrom].treeData.childrenCount) {\n        if (isValidSlotNode(startFrom)) {\n          // we are arrived\n          newReplica = startFrom;\n\n          // update the file replica info in the tree\n          if (decrFreeSlot) {\n            decrementFreeSlot(newReplica, true);\n          }\n\n          return true;\n        } else {\n          // if the current one is not valid, all the other leaves sharing the same father are not (because they are ordered)\n          // this also implies that all the available slots should satisfy this valid slot condition because we could be stuck in a\n          // situation where some free slots are valid and some other are not and it's impossible to know when going through the tree\n          assert(false);\n          return false;\n        }\n      } else {\n        if (pNodes[startFrom].fileData.lastHighestPriorityOffset) {\n          return findFreeSlotFirstHit(newReplica, getRandomBranch(startFrom), false,\n                                      decrFreeSlot);\n        } else {\n          return findFreeSlotFirstHit(newReplica,\n                                      pBranches[pNodes[startFrom].treeData.firstBranchIdx].sonIdx, false,\n                                      decrFreeSlot);\n        }\n      }\n    } else {\n      // no free slot then, try higher if allowed and not already at the root\n      if (allowUpRoot && startFrom) {\n        // we won't go through the current branch again because it has no free slot. Else, we wouldn't go uproot\n        return findFreeSlotFirstHit(newReplica, pNodes[startFrom].treeData.fatherIdx,\n                                    allowUpRoot, decrFreeSlot);\n      } else {\n        return false;\n      }\n    }\n  }\n\n  bool\n  findFreeSlotFirstHitBack(tFastTreeIdx& newReplica, tFastTreeIdx startFrom = 0)\n  {\n    if (pNodes[startFrom].fsData.mStatus == SchedTreeBase::Available) {\n      newReplica = startFrom;\n      return true;\n    } else {\n      if (startFrom) {\n        return findFreeSlotFirstHitBack(newReplica,\n                                        pNodes[startFrom].treeData.fatherIdx);\n      } else {\n        return false;\n      }\n    }\n  }\n\n  bool\n  findFreeSlotSkipSaturated(tFastTreeIdx& newReplica, tFastTreeIdx startFrom,\n                            bool allowUpRoot, bool decrFreeSlot, bool* visited = NULL)\n  {\n    // initial call to allocate the visited array in the stack\n    if (!visited) {\n      // initialize children as non visited\n      // visited children (over allocated but it allows a static allocation)\n      bool localvisited[(256)^sizeof(tFastTreeIdx)];\n\n      for (size_t t = 0; t < (256 ^ sizeof(tFastTreeIdx)); t++) {\n        localvisited[t] = false;\n      }\n\n      SchedTreeBase::tFastTreeIdx fatherIdx = startFrom;\n\n      if (!allowUpRoot) {\n        //make the current branch the root\n        std::swap(fatherIdx, pNodes[startFrom].treeData.fatherIdx);\n      }\n\n      bool ret = findFreeSlotSkipSaturated(newReplica, startFrom, true, decrFreeSlot,\n                                           localvisited);\n\n      if (!allowUpRoot) {\n        // put back the original father\n        std::swap(fatherIdx, pNodes[startFrom].treeData.fatherIdx);\n      }\n\n      return ret;\n    }\n\n    if (!visited[startFrom] && (pNodes[startFrom].fileData.freeSlotsCount)) {\n      // it's a leaf\n      if (!pNodes[startFrom].treeData.childrenCount) {\n        if (isValidSlotNode(startFrom) && !isSaturatedSlotNode(startFrom)) {\n          eos_static_debug(\"node %d is valid and unsaturated\", (int)startFrom);\n          // we are arrived\n          newReplica = startFrom;\n\n          // update the file replica info in the tree\n          if (decrFreeSlot) {\n            decrementFreeSlot(newReplica, true);\n          }\n\n          // we found something, we stop here\n          return true;\n        } else {\n          eos_static_debug(\"node %d is NOT (valid and unsaturated) status=%x, dlScore=%d, freeslot=%d, isvalid=%d, issaturated=%d\",\n                           (int)startFrom,\n                           (int)pNodes[startFrom].fsData.mStatus, (int)pNodes[startFrom].fsData.dlScore,\n                           (int)pNodes[startFrom].fileData.freeSlotsCount\n                           , (int)isValidSlotNode(startFrom), (int)isSaturatedSlotNode(startFrom));\n          // there is nothing we can use here either not valid either saturated\n          goto go_back;\n        }\n      }\n      // it's a branch\n      else {\n        tFastTreeIdx priorityLevel, begBrIdx, endBrIdx;\n        priorityLevel = 0;\n        endBrIdx = begBrIdx = pNodes[startFrom].treeData.firstBranchIdx;\n        const tFastTreeIdx endIdx = endBrIdx + pNodes[startFrom].treeData.childrenCount;\n\n        // visit each level of priority\n        while (endBrIdx < endIdx) {\n          // if the first node is this level of priority doesn't have any slot available\n          // and we reached that point. It means that the whole subranch doesn't have any available slot\n          // we return false\n          if (!pNodes[pBranches[begBrIdx].sonIdx].fileData.freeSlotsCount) {\n            goto go_back;\n          }\n\n          // endBrIdx\n          if (priorityLevel) {\n            while (endBrIdx < endIdx &&\n                   !FTLowerBranch(endBrIdx, begBrIdx)) {\n              endBrIdx++;\n            }\n          } else {\n            endBrIdx += pNodes[startFrom].fileData.lastHighestPriorityOffset + 1;\n          }\n\n          // visit the current level of priority\n          // as long as there is still some branch to try in this priority level, do it\n          if (endBrIdx == begBrIdx + 1) {\n            if (findFreeSlotSkipSaturated(newReplica, pBranches[begBrIdx].sonIdx, false,\n                                          decrFreeSlot, visited)) {\n              return true;\n            }\n          } else {\n            tFastTreeIdx nodeIdxToVisit = 0;\n\n            // try until no branch is selectable\n            while (getRandomBranchGeneric(begBrIdx, endBrIdx, &nodeIdxToVisit, visited)) {\n              // if only one branch, no need to call getRandomBranch\n              if (findFreeSlotSkipSaturated(newReplica, nodeIdxToVisit, false, decrFreeSlot,\n                                            visited)) {\n                return true;\n              }\n            }\n          }\n\n          // move to the next priority level\n          priorityLevel++;\n          begBrIdx = endBrIdx;\n        }\n\n        // no slot available in any priority level -> nothing is available\n        goto go_back;\n      }\n    }\n\ngo_back:\n\n    // if the node is already visited all the subbranches are visited too\n    // go upstream\n    if (allowUpRoot && startFrom != pNodes[startFrom].treeData.fatherIdx) {\n      visited[startFrom] = true;\n      return findFreeSlotSkipSaturated(newReplica,\n                                       pNodes[startFrom].treeData.fatherIdx, allowUpRoot, decrFreeSlot, visited);\n    } else { // we are back to the root (the node father of himself), no luck\n      visited[startFrom] = true;\n      return false;\n    }\n  }\n\n  inline bool\n  findFreeSlot(tFastTreeIdx& newReplica, tFastTreeIdx startFrom = 0,\n               bool allowUpRoot = false, bool decrFreeSlot = true, bool skipSaturated = false)\n  {\n    if (skipSaturated) {\n      return findFreeSlotSkipSaturated(newReplica, startFrom, allowUpRoot,\n                                       decrFreeSlot);\n    } else {\n      return findFreeSlotFirstHit(newReplica, startFrom, allowUpRoot, decrFreeSlot);\n    }\n  }\n\n  inline void disableSubTree(const tFastTreeIdx& node)\n  {\n    // need to call update after calling this function\n    const tFastTreeIdx& firstBranchIdx = pNodes[node].treeData.firstBranchIdx;\n    const tFastTreeIdx& nbChildren = pNodes[node].treeData.childrenCount;\n    disableNode(node);\n\n    if (nbChildren) {\n      for (tFastTreeIdx branchIdx = firstBranchIdx;\n           branchIdx < firstBranchIdx + nbChildren; branchIdx++) {\n        disableSubTree(pBranches[branchIdx].sonIdx);\n      }\n    }\n  }\n\n  inline void disableNode(const tFastTreeIdx& node)\n  {\n    // need to call update after calling this function\n    pNodes[node].fsData.mStatus |= Disabled;\n  }\n};\n\ntemplate<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>\ninline size_t\ncopyFastTree(FastTree<T1, T2, T3>* dest, const FastTree<T4, T5, T6>* src)\n{\n  if (dest->pMaxNodeCount < src->pNodeCount) {\n    return src->pNodeCount;\n  }\n\n  // copy some members\n  dest->pFs2Idx = src->pFs2Idx;\n  dest->pTreeInfo = src->pTreeInfo;\n  dest->pNodeCount = src->pNodeCount;\n  // copy the nodes and the branches\n  memcpy((void*)dest->pNodes, src->pNodes,\n         (sizeof(typename FastTree<T1, T2>::FastTreeNode)) * src->pNodeCount);\n  memcpy((void*)dest->pBranches, src->pBranches,\n         (sizeof(typename FastTree<T1, T2>::Branch)) * src->pNodeCount);\n  return 0;\n}\n\ntemplate<typename FsDataMemberForRand, typename FsAndFileDataComparerForBranchSorting, typename FsIdType>\nstruct FastTreeBranchComparator {\n  typedef FastTree<FsDataMemberForRand, FsAndFileDataComparerForBranchSorting, FsIdType>\n  tFastTree;\n  typedef FastTreeBranch tBranch;\n  const tFastTree* fTree;\n  FastTreeBranchComparator(const tFastTree* ftree) : fTree(ftree)\n  {};\n  bool operator()(tBranch left, tBranch right) const\n  {\n    return fTree->FTGreaterNode(left.sonIdx, right.sonIdx);\n  };\n};\n\ntemplate<typename FsDataMemberForRand, typename FsAndFileDataComparerForBranchSorting, typename FsIdType>\nstruct FastTreeBranchComparatorInv {\n  typedef FastTree<FsDataMemberForRand, FsAndFileDataComparerForBranchSorting, FsIdType>\n  tFastTree;\n  typedef FastTreeBranch tBranch;\n  const tFastTree* fTree;\n  FastTreeBranchComparatorInv(const tFastTree* ftree) : fTree(ftree)\n  {};\n  bool operator()(tBranch left, tBranch right) const\n  {\n    return fTree->FTLowerNode(left.sonIdx, right.sonIdx);\n  };\n};\n\n#if __EOSMGM_TREECOMMON__PACK__STRUCTURE__==1\n#pragma pack(pop)\n#endif\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief FastTree instantiation for replica placement.\n *\n */\n/*----------------------------------------------------------------------------*/\ntypedef FastTree<PlacementPriorityRandWeightEvaluator, PlacementPriorityComparator>\nFastPlacementTree;\n\n/**\n * @brief FastTree instantiation for replica Read-Only access.\n *\n */\n/*----------------------------------------------------------------------------*/\ntypedef FastTree<AccessPriorityRandWeightEvaluator, ROAccessPriorityComparator>\nFastROAccessTree;\n\n/**\n * @brief FastTree instantiation for replica Read-Write access.\n *\n */\n/*----------------------------------------------------------------------------*/\ntypedef FastTree<AccessPriorityRandWeightEvaluator, RWAccessPriorityComparator>\nFastRWAccessTree;\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief FastTree instantiation for draining replica placement.\n *\n */\n/*----------------------------------------------------------------------------*/\ntypedef FastTree<DrainingPlacementPriorityRandWeightEvaluator, DrainingPlacementPriorityComparator>\nFastDrainingPlacementTree;\n\n/**\n * @brief FastTree instantiation for draining replica access.\n *\n */\n/*----------------------------------------------------------------------------*/\ntypedef FastTree<DrainingAccessPriorityRandWeightEvaluator, DrainingAccessPriorityComparator>\nFastDrainingAccessTree;\n\n/**\n * @brief FastTree instantiation for gateway selection\n *\n */\n/*----------------------------------------------------------------------------*/\ntypedef FastTree<GatewayPriorityRandWeightEvaluator, GatewayPriorityComparator, char*>\nFastGatewayAccessTree;\n\ntemplate<typename T1, typename T2, typename T3>\ninline std::ostream&\noperator <<(std::ostream& os, const FastTree<T1, T2, T3>& tree)\n{\n  //return tree.recursiveDisplay(os);\n  return os;\n}\n\ntemplate<typename T1, typename T2, typename T3>\nvoid __attribute__((used)) __attribute__((noinline))\ndebugDisplay(const FastTree<T1, T2, T3>& tree)\n{\n//   tree.recursiveDisplay(std::cout);\n}\n\nEOSMGMNAMESPACE_END\n\n#endif /* __EOSMGM_FASTTREE__H__ */\n"
  },
  {
    "path": "mgm/geotree/SchedulingSlowTree.cc",
    "content": "//------------------------------------------------------------------------------\n// @file SchedulingSlowTree.cc\n// @author Geoffray Adde - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#define DEFINE_TREECOMMON_MACRO\n#include \"mgm/geotree/SchedulingSlowTree.hh\"\n\n#include <iomanip>\n#include <sstream>\n#include <iterator>\n#include <iostream>\n#include <sstream>\n#include <cassert>\n#include <vector>\n#include <map>\n#include <stdlib.h>\n\nusing namespace std;\n\nEOSMGMNAMESPACE_BEGIN\n\nostream& SlowTreeNode::display(ostream& os) const\n{\n  os << pNodeInfo.geotag;\n  return os;\n}\n\nvoid SlowTreeNode::recursiveDisplay(\n  std::set<std::tuple<std::string, unsigned, unsigned,\n  TableFormatterColor, unsigned, unsigned, std::string,\n  std::string, int, int, std::string>>& data_tree,\n  std::string group, unsigned& geo_depth_max,\n  bool useColors, unsigned prefix1, unsigned prefix2)\n{\n  TableFormatterColor color = NONE;\n\n  if (useColors) {\n    bool isReadable = (pNodeState.mStatus & Readable);\n    bool isDisabled = (pNodeState.mStatus & Disabled);\n    bool isWritable = (pNodeState.mStatus & Writable);\n    bool isAvailable = (pNodeState.mStatus & Available);\n    bool isDraining = (pNodeState.mStatus & Draining);\n    bool isFs = pChildren.empty();\n\n    if (isDisabled) { // DISABLED\n      color = DARK;\n    } else {\n      if (!isAvailable || (isFs && (!(isReadable || isWritable)))) {\n        // UNAVAILABLE OR NOIO\n        color = (isFs && isDraining) ? BYELLOW_BGRED : BWHITE_BGRED;\n      } else if (isFs) {\n        if (isReadable && ! isWritable) { // RO case\n          color = (isFs && isDraining) ? BYELLOW_BGBLUE : BWHITE_BGBLUE;\n        } else if (!isReadable && isWritable) { // WO case\n          color = (isFs && isDraining) ? NONE : BWHITE_BGYELLOW;\n        } else {\n          color = (isFs && isDraining) ? BYELLOW : BWHITE;\n        }\n      } else {\n        color = (isFs && isDraining) ? BYELLOW : BWHITE;\n      }\n    }\n  }\n\n  if (pChildren.empty()) {\n    // Print fsid and node (depth=3)\n    data_tree.insert(std::make_tuple(group, data_tree.size(), 3, color,\n                                     prefix1, prefix2, pNodeInfo.fullGeotag, pNodeInfo.host,\n                                     pLeavesCount, pNodeCount, fsStatusToStr(pNodeState.mStatus)));\n  } else {\n    // Print group (depth=1) and geotag (depth=2)\n    unsigned depth = (prefix1 == 0 && prefix2 == 0) ? 1 : 2;\n    group = (prefix1 == 0 && prefix2 == 0) ? pNodeInfo.geotag : group;\n    data_tree.insert(std::make_tuple(group, data_tree.size(), depth, color,\n                                     prefix1, prefix2, pNodeInfo.fullGeotag,\n                                     \"\", pLeavesCount, pNodeCount, \"\"));\n    // How geotag is deep\n    unsigned geo_depth = 1;\n    std::string geotag_temp = pNodeInfo.fullGeotag;\n\n    while (geotag_temp.find(\"::\") != std::string::npos) {\n      geotag_temp.erase(0, geotag_temp.find(\"::\") + 2);\n      geo_depth++;\n    }\n\n    geo_depth_max = (geo_depth_max < geo_depth) ? geo_depth : geo_depth_max;\n\n    for (auto it = pChildren.begin(); it != pChildren.end(); it++) {\n      unsigned prefix1_temp = (prefix2 == 3) ? 1 : 0;\n\n      if (it != pChildren.end() &&\n          ++tNodeMap::const_iterator(it) == pChildren.end()) {\n        // final branch\n        it->second->recursiveDisplay(data_tree, group, geo_depth_max,\n                                     useColors, prefix1_temp, 2);\n      } else {\n        // intermediate branch\n        it->second->recursiveDisplay(data_tree, group, geo_depth_max,\n                                     useColors, prefix1_temp, 3);\n      }\n    }\n  }\n}\n\nvoid SlowTreeNode::recursiveDisplayAccess(\n  std::set<std::tuple<unsigned, unsigned, unsigned,\n  unsigned, std::string, std::string>>& data_access,\n  unsigned& geo_depth_max, unsigned prefix1,\n  unsigned prefix2)\n{\n  // How geotag is deep\n  unsigned geo_depth = 1;\n  std::string geotag_temp = pNodeInfo.fullGeotag;\n\n  while (geotag_temp.find(\"::\") != std::string::npos) {\n    geotag_temp.erase(0, geotag_temp.find(\"::\") + 2);\n    geo_depth++;\n  }\n\n  geo_depth_max = (geo_depth_max < geo_depth) ? geo_depth : geo_depth_max;\n\n  if (pChildren.empty()) {\n    if (pNodeInfo.proxygroup.size()) { // leavs\n      data_access.insert(std::make_tuple(data_access.size(), 3, prefix1, prefix2,\n                                         pNodeInfo.fullGeotag, pNodeInfo.proxygroup));\n    }\n  } else {\n    unsigned depth = (prefix1 == 0 && prefix2 == 0) ? 1 : 2;\n    data_access.insert(std::make_tuple(data_access.size(), depth, prefix1, prefix2,\n                                       pNodeInfo.fullGeotag, pNodeInfo.proxygroup));\n\n    for (auto it = pChildren.begin(); it != pChildren.end(); it++) {\n      unsigned prefix1_temp = (prefix2 == 3) ? 1 : 0;\n\n      if (it != pChildren.end() &&\n          ++tNodeMap::const_iterator(it) == pChildren.end()) {\n        // final branch\n        it->second->recursiveDisplayAccess(data_access, geo_depth_max, prefix1_temp, 2);\n      } else {\n        // intermediate branch\n        it->second->recursiveDisplayAccess(data_access, geo_depth_max, prefix1_temp, 3);\n      }\n    }\n  }\n}\nvoid SlowTree::display(std::set<std::tuple<std::string, unsigned, unsigned,\n                       TableFormatterColor, unsigned, unsigned, std::string,\n                       std::string, int, int, std::string>>& data_tree,\n                       unsigned& geo_depth_max, bool useColors)\n{\n  pRootNode.recursiveDisplay(data_tree, \"\", geo_depth_max, useColors);\n}\n\nvoid SlowTree::displayAccess(std::set<std::tuple<unsigned, unsigned, unsigned,\n                             unsigned, std::string, std::string>>& data_access,\n                             unsigned& geo_depth_max)\n{\n  pRootNode.recursiveDisplayAccess(data_access, geo_depth_max);\n}\n\nSlowTreeNode* SlowTree::insert(const TreeNodeInfo* info,\n                               const TreeNodeStateFloat* state, bool addFsIdLevel, bool allowUpdate)\n{\n  SlowTreeNode* startFrom = &pRootNode;\n  ostringstream oss;\n  oss << info->geotag;\n\n  if (addFsIdLevel) {\n    oss << \"::\" << info->fsId;\n  }\n\n  std::string fullgeotag;\n  SlowTreeNode* result = insert(\n                           info,\n                           state,\n                           fullgeotag,\n                           oss.str(),\n                           startFrom,\n                           NULL,\n                           allowUpdate\n                         );\n  return result;\n}\n\nSlowTreeNode* SlowTree::insert(const TreeNodeInfo* info,\n                               const TreeNodeStateFloat* state, std::string& fullGeotag,\n                               const std::string& partialGeotag, SlowTreeNode* startFrom,\n                               SlowTreeNode* startedConstructingAt, bool allowUpdate)\n{\n  if (partialGeotag.empty()) {\n    return NULL;\n  }\n\n  // find the first :: separator\n  size_t sepPos;\n\n  for (sepPos = 0; sepPos < partialGeotag.length() - 1; sepPos++)\n    if (partialGeotag[sepPos] == ':' && partialGeotag[sepPos + 1] == ':') {\n      break;\n    }\n\n  if (sepPos == partialGeotag.length() - 1) {\n    sepPos = partialGeotag.length();\n  }\n\n  string geoTagAtom = partialGeotag.substr(0, sepPos);\n\n  if (!fullGeotag.empty()) {\n    fullGeotag += \"::\";\n  }\n\n  fullGeotag += geoTagAtom;\n  bool newbranch = ! startFrom->pChildren.count(geoTagAtom);\n\n  if (newbranch) {\n    startFrom->pChildren[geoTagAtom] = new SlowTreeNode;\n    startFrom->pChildren[geoTagAtom]->pFather = startFrom;\n    startFrom->pChildren[geoTagAtom]->pNodeInfo.geotag = geoTagAtom;\n    startFrom->pChildren[geoTagAtom]->pNodeInfo.fullGeotag = fullGeotag;\n    startFrom->pChildren[geoTagAtom]->pNodeInfo.fsId = 0;\n    startFrom->pChildren[geoTagAtom]->pNodeInfo.nodeType =\n      TreeNodeInfo::intermediate;\n    pNodeCount++; // add one node to the counter\n\n    if (!startedConstructingAt) {\n      startedConstructingAt = startFrom->pChildren[geoTagAtom];\n    }\n  }\n\n  startFrom = startFrom->pChildren[geoTagAtom];\n\n  if (sepPos == partialGeotag.length()) {\n    // update the attributes\n    startFrom->pNodeInfo.host = info->host;\n    startFrom->pNodeInfo.hostport = info->hostport;\n    startFrom->pNodeInfo.proxygroup = info->proxygroup;\n    startFrom->pNodeInfo.fsId = info->fsId;\n    startFrom->pNodeInfo.nodeType = TreeNodeInfo::fs;\n    startFrom->pNodeState = *state;\n\n    if (newbranch || allowUpdate) {\n      // update the pLeavesCount\n      if (newbranch)\n        for (SlowTreeNode* node = startFrom; node != NULL; node = node->pFather) {\n          node->pLeavesCount++;\n        }\n    } else {\n      assert(false);\n    }\n\n    if (startedConstructingAt) {\n      // update the node count if needed\n      size_t nconstr = 0;\n      size_t metStarted = false;\n\n      for (SlowTreeNode* it = startFrom; it != NULL; it = it->pFather) {\n        if (!metStarted) {\n          nconstr++;\n        }\n\n        if (it == startedConstructingAt) {\n          metStarted = true;\n        }\n\n        it->pNodeCount += nconstr;\n      }\n    }\n\n    __EOSMGM_TREECOMMON_DBG2__\n    eos_static_debug(\"inserted fsid=%lu   geotag=%s   fullgeotag=%s\",\n                     startFrom->pNodeInfo.fsId, startFrom->pNodeInfo.geotag.c_str(),\n                     startFrom->pNodeInfo.fullGeotag.c_str());\n    return startFrom;\n  } else\n    return insert(\n             info,\n             state,\n             fullGeotag,\n             partialGeotag.substr(sepPos + 2, partialGeotag.length() - sepPos - 2),\n             startFrom,\n             startedConstructingAt,\n             allowUpdate\n           );\n}\n\nbool SlowTree::remove(const TreeNodeInfo* info, bool addFsIdLevel)\n{\n  if (info->geotag.empty()) {\n    return false;  // should not be used with empty fullgeotag\n  }\n\n  std::string fullgeotag;\n\n  if (info->fsId && addFsIdLevel) {\n    ostringstream oss;\n    oss << info->geotag << \"::\" << info->fsId;\n    fullgeotag = oss.str();\n  } else {\n    fullgeotag = info->geotag;\n  }\n\n  size_t pos = 0;\n  size_t ppos = 0;\n  string geoTagAtom;\n  SlowTreeNode* node = &pRootNode;\n\n  while ((pos = fullgeotag.find(\"::\", pos + 1)) != string::npos) {\n    pos += 2; // skip the \"::\"\n    geoTagAtom = fullgeotag.substr(ppos, pos - ppos - 2); // take \"::\" into account\n\n    if (!node->pChildren.count(geoTagAtom)) {\n      eos_static_err(\"msg=\\\"no matching leaf found with geotag=%s\",\n                     geoTagAtom.c_str());\n      return false;  // no matching leaf found!\n    }\n\n    node = node->pChildren[geoTagAtom];\n    ppos = pos;\n  }\n\n  geoTagAtom = fullgeotag.substr(ppos, string::npos);\n\n  if (!node->pChildren.count(geoTagAtom)) {\n    return false;  // no matching leaf found!\n  }\n\n  node = node->pChildren[geoTagAtom];\n\n  // arrived to the end of the string so, we can delete the matched branch.\n  // simplify the tree by erasing the biggest empty branch\n  while ((node->pFather != NULL) && (node->pFather != &pRootNode) &&\n         (node->pFather->pChildren.size() == 1)) {\n    node = node->pFather;\n  }\n\n  if (node->pFather) {\n    node->pFather->pChildren.erase(node->pNodeInfo.geotag);\n  }\n\n  size_t lcount = node->pLeavesCount;\n  size_t ncount = node->pNodeCount;\n\n  for (SlowTreeNode* it = node; it != NULL; it = it->pFather) {\n    // update the recursive leaves count in the tree\n    it->pLeavesCount -= lcount;\n    it->pNodeCount -= ncount;\n  }\n\n  pNodeCount -= ncount;\n  delete node;\n  return true;\n}\n\nSlowTreeNode* SlowTree::moveToNewGeoTag(SlowTreeNode* node,\n                                        const std::string newGeoTag)\n{\n  if (node->pChildren.size()) {\n    // This can only move a leaf. Moving a branch would involve running throuh\n    // the branch and get all the leaves.\n    eos_static_err(\"%s\", \"msg=\\\"failed move since node has children\\\"\");\n    return NULL;\n  }\n\n  TreeNodeInfo info = node->pNodeInfo;\n  TreeNodeStateFloat state = node->pNodeState;\n  info.geotag = node->pNodeInfo.fullGeotag.substr(0,\n                node->pNodeInfo.fullGeotag.rfind(\"::\"));\n\n  if (!remove(&info)) {\n    eos_static_err(\"%s\", \"msg=\\\"failed remove\\\"\");\n    return NULL;\n  }\n\n  info.geotag = newGeoTag;\n  return insert(&info, &state);\n}\n\nbool SlowTree::buildFastStrcturesSched(\n  FastPlacementTree* fpt, FastROAccessTree* froat, FastRWAccessTree* frwat,\n  FastDrainingPlacementTree* fdpt, FastDrainingAccessTree* fdat,\n  FastTreeInfo* fastinfo, Fs2TreeIdxMap* fs2idx,\n  GeoTag2NodeIdxMap* geo2node) const\n{\n  // check that the FastTree are large enough\n  if (froat->getMaxNodeCount() < getNodeCount() ||\n      frwat->getMaxNodeCount() < getNodeCount()\n      || fpt->getMaxNodeCount() < getNodeCount()\n      || fdat->getMaxNodeCount() < getNodeCount() ||\n      fdpt->getMaxNodeCount() < getNodeCount()) {\n    return false;\n  }\n\n  if (geo2node->getMaxNodeCount() < getNodeCount()) {\n    if (geo2node->getMaxNodeCount() == 0) {\n      geo2node->selfAllocate(getNodeCount());\n    } else {\n      assert(false);\n    }\n\n    //return false;\n  }\n\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n  __EOSMGM_TREECOMMON_DBG1__\n\n  if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n    stringstream ss;\n    ss << (*this);\n    eos_static_debug(\"SLOWTREE IS %s\", ss.str().c_str());\n  }\n\n  // update the SlowwTree before converting it\n  ((SlowTree*)this)->pRootNode.update();\n  // create the node vector layout\n  vector<vector<const SlowTreeNode*> >nodesByDepth;// [depth][branchIdxAtThisDepth]\n  map<const SlowTreeNode*, int> nodes2idxChildren;\n  map<const SlowTreeNode*, int> nodes2idxGeoTag;\n  nodesByDepth.resize(nodesByDepth.size() + 1);\n  nodesByDepth.back().push_back(&pRootNode);\n  size_t count = 0;\n  nodes2idxChildren[&pRootNode] = count++;\n  bool godeeper = (bool)pRootNode.pChildren.size();\n\n  while (godeeper) {\n    // create a new level\n    nodesByDepth.resize(nodesByDepth.size() + 1);\n    // iterate through the nodes of the last level\n    godeeper = false;\n    auto it_last_lvl = nodesByDepth.begin();\n    std::advance(it_last_lvl, nodesByDepth.size() - 2);\n\n    for (auto it = it_last_lvl->begin(); it != it_last_lvl->end(); ++it) {\n      // iterate through the children of each of those nodes\n      for (auto cit = (*it)->pChildren.begin(); cit != (*it)->pChildren.end();\n           ++cit) {\n        nodesByDepth.back().push_back(cit->second);\n        nodes2idxChildren[cit->second] = count++;\n\n        if (!godeeper && !(*cit).second->pChildren.empty()) {\n          godeeper = true;\n        }\n      }\n    }\n  }\n\n  // Copy the vector layout of the node to the FastTree\n  size_t nodecount = 0;\n  size_t linkcount = 0;\n  std::map<unsigned long, tFastTreeIdx> fs2idxMap;\n  fastinfo->clear();\n  fastinfo->resize(pNodeCount);\n  // It's not necessary to clear the fs2idx map because a given fs should\n  // appear only in one placement group\n  bool firstnode = true;\n\n  for (vector<vector<const SlowTreeNode*> >::const_iterator dit =\n         nodesByDepth.begin(); dit != nodesByDepth.end(); dit++) {\n    for (vector<const SlowTreeNode*>::const_iterator it = dit->begin();\n         it != dit->end(); it++) {\n      // write the content of the node\n      if (!(*it)->writeFastTreeNodeTemplate<PlacementPriorityRandWeightEvaluator,\n          PlacementPriorityComparator, eos::common::FileSystem::fsid_t>\n          (fpt->pNodes + nodecount)) {\n        assert(false);\n        return false;\n      }\n\n      // update the links\n      // father first\n      if (firstnode) {\n        fpt->pNodes[nodecount].treeData.fatherIdx = 0;\n      } else {\n        fpt->pNodes[nodecount].treeData.fatherIdx = (tFastTreeIdx)\n            nodes2idxChildren[(*it)->pFather];\n      }\n\n      // then children\n      tFastTreeIdx nchildren = 0;\n      fpt->pNodes[nodecount].treeData.firstBranchIdx = linkcount;\n      {\n        for (auto cit = (*it)->pChildren.begin(); cit != (*it)->pChildren.end();\n             cit++) {\n          (fpt->pBranches[linkcount].sonIdx = (tFastTreeIdx)\n                                              nodes2idxChildren[cit->second]);\n          linkcount++;\n          nchildren++;\n        }\n      }\n      fpt->pNodes[nodecount].treeData.childrenCount = nchildren;\n      // fill in the default TreeNodePlacement\n      fpt->pNodes[nodecount].fileData.freeSlotsCount =\n        (*it)->pLeavesCount;// replica placed so, all slot are available to place a new one\n      fpt->pNodes[nodecount].fileData.takenSlotsCount = 0;\n      // fill in the FastTreeInfo\n      (*fastinfo)[nodecount] = (*it)->pNodeInfo;\n\n      // fill in tFs2TreeIdxMap\n      if ((*it)->pNodeInfo.nodeType == TreeNodeInfo::fs) {\n        fs2idxMap[(*it)->pNodeInfo.fsId] = nodecount;\n      }\n\n      // iterate the node\n      nodecount++;\n    }\n\n    firstnode = false;\n  }\n\n  // finish the placment tree\n  fpt->pNodeCount = pNodeCount;\n  fpt->updateTree();\n\n  //finish the RO access tree\n  if (fpt->copyToFastTree(froat)) {\n    assert(false);\n    return false;\n  }\n\n  for (tFastTreeIdx i = 0; i < froat->pNodeCount; i++) {\n    froat->pNodes[i].fileData.freeSlotsCount = 0;\n  }\n\n  froat->pNodeCount = pNodeCount;\n  froat->updateTree();\n\n  //finish the RW access tree\n  if (fpt->copyToFastTree(frwat)) {\n    assert(false);\n    return false;\n  }\n\n  for (tFastTreeIdx i = 0; i < frwat->pNodeCount; i++) {\n    frwat->pNodes[i].fileData.freeSlotsCount = 0;\n  }\n\n  frwat->pNodeCount = pNodeCount;\n  frwat->updateTree();\n\n  // copy them to the other tree (draining)\n  if (fpt->copyToFastTree(fdpt)) {\n    assert(false);\n    return false;\n  }\n\n  fdpt->updateTree();\n\n  if (froat->copyToFastTree(fdat)) {\n    assert(false);\n    return false;\n  }\n\n  fdat->updateTree();\n  // some sanity checks\n\n  __EOSMGM_TREECOMMON_CHK1__ if (\n    nodecount != pNodeCount ||\n    linkcount != pNodeCount - 1 ||\n    count != pNodeCount\n  ) {\n    assert(false);\n    return false;\n  }\n\n  // create the node vector layout\n  nodesByDepth.clear();// [depth][branchIdxAtThisDepth]\n  nodesByDepth.resize(nodesByDepth.size() + 1);\n  nodesByDepth.back().push_back(&pRootNode);\n  count = 0;\n  nodes2idxGeoTag[&pRootNode] = count;\n  geo2node->pNodes[count].fastTreeIndex = 0;\n  strncpy(geo2node->pNodes[count].tag, pRootNode.pNodeInfo.geotag.c_str(),\n          GeoTag2NodeIdxMap::gMaxTagSize);\n  geo2node->pNodes[count].tag[GeoTag2NodeIdxMap::gMaxTagSize - 1] = '\\0';\n  count++;\n  godeeper = (bool)pRootNode.pChildren.size();\n\n  while (godeeper) {\n    // create a new level\n    nodesByDepth.resize(nodesByDepth.size() + 1);\n    // iterate through the nodes of the last level\n    godeeper = false;\n    auto it_last_lvl = nodesByDepth.begin();\n    std::advance(it_last_lvl, nodesByDepth.size() - 2);\n\n    for (auto it = it_last_lvl->begin(); it != it_last_lvl->end(); ++it) {\n      // iterate through the children of each of those nodes\n      for (auto cit = (*it)->pChildren.begin();\n           cit != (*it)->pChildren.end(); ++cit) {\n        nodesByDepth.back().push_back(cit->second);\n        nodes2idxGeoTag[cit->second] = count;\n        geo2node->pNodes[count].fastTreeIndex = nodes2idxChildren[cit->second];\n        strncpy(geo2node->pNodes[count].tag, cit->second->pNodeInfo.geotag.c_str(),\n                GeoTag2NodeIdxMap::gMaxTagSize);\n        geo2node->pNodes[count].tag[GeoTag2NodeIdxMap::gMaxTagSize - 1] = '\\0';\n        count++;\n\n        if (!godeeper && !cit->second->pChildren.empty()) {\n          godeeper = true;\n        }\n      }\n    }\n  }\n\n  nodecount = 0;\n\n  for (vector<vector<const SlowTreeNode*> >::const_iterator dit =\n         nodesByDepth.begin(); dit != nodesByDepth.end(); dit++) {\n    for (vector<const SlowTreeNode*>::const_iterator it = dit->begin();\n         it != dit->end(); it++) {\n      geo2node->pNodes[nodecount].branchCount = (tFastTreeIdx)(*it)->pChildren.size();\n      geo2node->pNodes[nodecount].firstBranch =\n        geo2node->pNodes[nodecount].branchCount ?\n        nodes2idxGeoTag[(*it)->pChildren.begin()->second] :\n        0;\n      nodecount++;\n    }\n  }\n\n  geo2node->pSize = nodecount;\n  // some sanity checks\n\n  __EOSMGM_TREECOMMON_CHK1__ if (\n    nodecount != pNodeCount ||\n    linkcount != pNodeCount - 1 ||\n    count != pNodeCount\n  ) {\n    eos_static_alert(\"Unable to generate the fast tree because of a failed sanity check.\");\n    return false;\n  }\n\n  // fill in the outsourced data\n  if (fs2idx->pMaxSize == 0) {\n    fs2idx->selfAllocate((tFastTreeIdx)fs2idxMap.size());\n  }\n\n  if (fs2idx->pMaxSize < fs2idxMap.size()) {\n    eos_static_crit(\"could not generate the fast tree because the fs2idx is too small\");\n    return false;\n  }\n\n  count = 0;\n\n  for (std::map<unsigned long, tFastTreeIdx>::const_iterator it =\n         fs2idxMap.begin(); it != fs2idxMap.end(); it++) {\n    fs2idx->pFsIds[count] = it->first;\n    fs2idx->pNodeIdxs[count++] = it->second;\n  }\n\n  fs2idx->pSize = fs2idxMap.size();\n  froat->pFs2Idx = frwat->pFs2Idx = fpt->pFs2Idx = fdat->pFs2Idx = fdpt->pFs2Idx =\n                                      fs2idx;\n  froat->pTreeInfo = frwat->pTreeInfo = fpt->pTreeInfo = fdat->pTreeInfo =\n                                          fdpt->pTreeInfo = fastinfo;\n  __EOSMGM_TREECOMMON_CHK2__\n  fpt->checkConsistency(0, true);\n  __EOSMGM_TREECOMMON_CHK2__\n  fdpt->checkConsistency(0, true);\n  __EOSMGM_TREECOMMON_CHK2__\n  froat->checkConsistency(0, true);\n  __EOSMGM_TREECOMMON_CHK2__\n  frwat->checkConsistency(0, true);\n  __EOSMGM_TREECOMMON_CHK2__\n  fdat->checkConsistency(0, true);\n\n  __EOSMGM_TREECOMMON_DBG1__ if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n    stringstream ss;\n    ss << (*fpt);\n    eos_static_debug(\"FASTTREE IS %s\", ss.str().c_str());\n  }\n\n  fpt->checkConsistency(0, true);\n  return true;\n}\n\nbool SlowTree::buildFastStrcturesAccess(\n  FastGatewayAccessTree* fgat, Host2TreeIdxMap* host2idx, FastTreeInfo* fastinfo,\n  GeoTag2NodeIdxMap* geo2node) const\n{\n  if (!buildFastStructuresGW(fgat, host2idx, fastinfo, geo2node)) {\n    return false;\n  }\n\n  for (size_t i = 0; i < fgat->pNodeCount; i++) {\n    fgat->pNodes[i].fsData.mStatus = ((*fastinfo)[i].proxygroup.empty() ?\n                                      SchedTreeBase::Disabled : SchedTreeBase::Available) ;\n  }\n\n  fgat->updateTree();\n  return true;\n}\n\nbool SlowTree::buildFastStructuresGW(\n  FastGatewayAccessTree* fgat, Host2TreeIdxMap* host2idx, FastTreeInfo* fastinfo,\n  GeoTag2NodeIdxMap* geo2node) const\n{\n  // check that the FastTree are large enough\n  if (fgat->getMaxNodeCount() < getNodeCount()) {\n    return false;\n  }\n\n  if (geo2node->getMaxNodeCount() < getNodeCount()) {\n    if (geo2node->getMaxNodeCount() == 0) {\n      geo2node->selfAllocate(getNodeCount());\n    } else {\n      assert(false);\n    }\n\n    //return false;\n  }\n\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n  __EOSMGM_TREECOMMON_DBG1__\n\n  if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n    stringstream ss;\n    ss << (*this);\n    eos_static_debug(\"SLOWTREE IS %s\", ss.str().c_str());\n  }\n\n  // update the SlowwTree before converting it\n  ((SlowTree*)this)->pRootNode.update();\n  // create the node vector layout\n  vector<vector<const SlowTreeNode*> >nodesByDepth;// [depth][branchIdxAtThisDepth]\n  map<const SlowTreeNode*, int> nodes2idxChildren;\n  map<const SlowTreeNode*, int> nodes2idxGeoTag;\n  nodesByDepth.resize(nodesByDepth.size() + 1);\n  nodesByDepth.back().push_back(&pRootNode);\n  size_t count = 0;\n  nodes2idxChildren[&pRootNode] = count++;\n  bool godeeper = (bool)pRootNode.pChildren.size();\n\n  while (godeeper) {\n    // Create a new level\n    nodesByDepth.resize(nodesByDepth.size() + 1);\n    // Iterate through the nodes of the last level\n    godeeper = false;\n    auto it_last_lvl = nodesByDepth.begin();\n    std::advance(it_last_lvl, nodesByDepth.size() - 2);\n\n    for (auto it = it_last_lvl->begin(); it != it_last_lvl->end(); ++it) {\n      // Iterate through the children of each of those nodes\n      for (auto cit = (*it)->pChildren.begin();\n           cit != (*it)->pChildren.end(); ++cit) {\n        nodesByDepth.back().push_back(cit->second);\n        nodes2idxChildren[cit->second] = count++;\n\n        if (!godeeper && !(*cit).second->pChildren.empty()) {\n          godeeper = true;\n        }\n      }\n    }\n  }\n\n  // copy the vector layout of the node to the FastTree\n  size_t nodecount = 0;\n  size_t linkcount = 0;\n  std::map<std::string, tFastTreeIdx> host2idxMap;\n  fastinfo->clear();\n  fastinfo->resize(pNodeCount);\n  // It's not necessary to clear the fs2idx map because a given fs should\n  // appear only in one placement group\n  bool firstnode = true;\n\n  for (vector<vector<const SlowTreeNode*> >::const_iterator dit =\n         nodesByDepth.begin(); dit != nodesByDepth.end(); ++dit) {\n    for (vector<const SlowTreeNode*>::const_iterator it = dit->begin();\n         it != dit->end(); ++it) {\n      // write the content of the node\n      if (!(*it)->writeFastTreeNodeTemplate<GatewayPriorityRandWeightEvaluator,\n          GatewayPriorityComparator, char*> (fgat->pNodes + nodecount)) {\n        assert(false);\n        return false;\n      }\n\n      // update the links\n      // father first\n      if (firstnode) {\n        fgat->pNodes[nodecount].treeData.fatherIdx = 0;\n      } else {\n        fgat->pNodes[nodecount].treeData.fatherIdx = (tFastTreeIdx)\n            nodes2idxChildren[(*it)->pFather];\n      }\n\n      // then children\n      tFastTreeIdx nchildren = 0;\n      fgat->pNodes[nodecount].treeData.firstBranchIdx = linkcount;\n      {\n        for (auto cit = (*it)->pChildren.begin(); cit != (*it)->pChildren.end();\n             cit++) {\n          (fgat->pBranches[linkcount].sonIdx = (tFastTreeIdx)\n                                               nodes2idxChildren[cit->second]);\n          linkcount++;\n          nchildren++;\n        }\n      }\n      fgat->pNodes[nodecount].treeData.childrenCount = nchildren;\n      // fill in the default TreeNodePlacement\n      fgat->pNodes[nodecount].fileData.freeSlotsCount =\n        (*it)->pLeavesCount;// replica placed so, all slot are available to place a new one\n      fgat->pNodes[nodecount].fileData.takenSlotsCount = 0;\n      // fill in the FastTreeInfo\n      (*fastinfo)[nodecount] = (*it)->pNodeInfo;\n\n      // fill in tFs2TreeIdxMap\n      if ((*it)->pNodeInfo.nodeType == TreeNodeInfo::fs) {\n        host2idxMap[(*it)->pNodeInfo.host] = nodecount;\n      }\n\n      // iterate the node\n      nodecount++;\n    }\n\n    firstnode = false;\n  }\n\n  // finish the gateway tree\n  fgat->updateTree();\n  fgat->pNodeCount = pNodeCount;\n  // some sanity checks\n\n  __EOSMGM_TREECOMMON_CHK1__ if (\n    nodecount != pNodeCount ||\n    linkcount != pNodeCount - 1 ||\n    count != pNodeCount\n  ) {\n    assert(false);\n    return false;\n  }\n\n  // create the node vector layout\n  nodesByDepth.clear();// [depth][branchIdxAtThisDepth]\n  nodesByDepth.resize(nodesByDepth.size() + 1);\n  nodesByDepth.back().push_back(&pRootNode);\n  count = 0;\n  nodes2idxGeoTag[&pRootNode] = count;\n  geo2node->pNodes[count].fastTreeIndex = 0;\n  strncpy(geo2node->pNodes[count].tag, pRootNode.pNodeInfo.geotag.c_str(),\n          GeoTag2NodeIdxMap::gMaxTagSize);\n  geo2node->pNodes[count].tag[GeoTag2NodeIdxMap::gMaxTagSize - 1] = '\\0';\n  count++;\n  godeeper = (bool)pRootNode.pChildren.size();\n\n  while (godeeper) {\n    // Create a new level\n    nodesByDepth.resize(nodesByDepth.size() + 1);\n    godeeper = false;\n    // Iterate through the nodes of the last level\n    auto it_last_lvl = nodesByDepth.begin();\n    std::advance(it_last_lvl, nodesByDepth.size() - 2);\n\n    for (auto it = it_last_lvl->begin(); it != it_last_lvl->end(); ++it) {\n      // Iterate through the children of each of those nodes\n      for (auto cit = (*it)->pChildren.begin();\n           cit != (*it)->pChildren.end(); ++cit) {\n        nodesByDepth.back().push_back(cit->second);\n        nodes2idxGeoTag[cit->second] = count;\n        geo2node->pNodes[count].fastTreeIndex = nodes2idxChildren[cit->second];\n        strncpy(geo2node->pNodes[count].tag, cit->second->pNodeInfo.geotag.c_str(),\n                GeoTag2NodeIdxMap::gMaxTagSize);\n        geo2node->pNodes[count].tag[GeoTag2NodeIdxMap::gMaxTagSize - 1] = '\\0';\n        count++;\n\n        if (!godeeper && !cit->second->pChildren.empty()) {\n          godeeper = true;\n        }\n      }\n    }\n  }\n\n  nodecount = 0;\n\n  for (vector<vector<const SlowTreeNode*> >::const_iterator dit =\n         nodesByDepth.begin(); dit != nodesByDepth.end(); dit++) {\n    for (vector<const SlowTreeNode*>::const_iterator it = dit->begin();\n         it != dit->end(); it++) {\n      geo2node->pNodes[nodecount].branchCount = (tFastTreeIdx)(*it)->pChildren.size();\n      geo2node->pNodes[nodecount].firstBranch =\n        geo2node->pNodes[nodecount].branchCount ?\n        nodes2idxGeoTag[(*it)->pChildren.begin()->second] :\n        0;\n      nodecount++;\n    }\n  }\n\n  geo2node->pSize = nodecount;\n  // some sanity checks\n\n  __EOSMGM_TREECOMMON_CHK1__ if (\n    nodecount != pNodeCount ||\n    linkcount != pNodeCount - 1 ||\n    count != pNodeCount\n  ) {\n    eos_static_alert(\"Unable to generate the fast tree because of a failed sanity check.\");\n    return false;\n  }\n\n  // fill in the outsourced data\n  if (host2idx->pMaxSize == 0) {\n    host2idx->selfAllocate((tFastTreeIdx)host2idxMap.size());\n  }\n\n  if (host2idx->pMaxSize < host2idxMap.size()) {\n    eos_static_crit(\"could not generate the fast tree because the fs2idx is too small\");\n    return false;\n  }\n\n  count = 0;\n\n  for (auto it = host2idxMap.begin(); it != host2idxMap.end(); it++) {\n    strncpy(host2idx->pBuffer + count * host2idx->pStrLen, it->first.c_str(),\n            host2idx->pStrLen);\n    //host2idx->pBuffer[count] = it->first;\n    host2idx->pNodeIdxs[count++] = it->second;\n  }\n\n  host2idx->pSize = host2idxMap.size();\n  fgat->pFs2Idx = host2idx;\n  fgat->pTreeInfo = fastinfo;\n  __EOSMGM_TREECOMMON_CHK2__\n  fgat->checkConsistency(2, true);\n\n  __EOSMGM_TREECOMMON_DBG1__ if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n    stringstream ss;\n    ss << (*fgat);\n    eos_static_debug(\"FASTTREE IS %s\", ss.str().c_str());\n  }\n\n  fgat->checkConsistency(0, true);\n  return true;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/geotree/SchedulingSlowTree.hh",
    "content": "//------------------------------------------------------------------------------\n// @file SchedulingSlowTree.hh\n// @author Geoffray Adde - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_SLOWTREE__H__\n#define __EOSMGM_SLOWTREE__H__\n\n#include <map>\n#include <ostream>\n#include <set>\n#include <vector>\n#define DEFINE_TREECOMMON_MACRO\n#include \"mgm/geotree/SchedulingFastTree.hh\"\n#include \"mgm/geotree/SchedulingTreeCommon.hh\"\n#include \"common/table_formatter/TableFormatterBase.hh\"\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file SchedulingSlowTree.hh\n *\n * @brief Class representing the geotag-based tree structure of a scheduling group\n *\n * There are two representations of this tree structure:\n * - the first one defined in the current file\n *   is flexible and the tree can be shaped easily\n *   on the other hand, it's big and possibly scattered in the memory, so its\n *   access speed might be low\n * - the second one is a set a compact and fast structures (defined in SchedulingFastTree.hh)\n *   these structure ares compact and contiguous in memory which makes them fast\n *   the shape of the underlying tree cannot be changed once they are constructed\n * Typically, a tree is constructed using the first representation (also referred as \"slow\").\n * Then, a representation of the second kind (also referred as \"fast\") is created from the\n * previous. It's then used to issue all the file scheduling operations at a high throughput (MHz)\n *\n */\n\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\nclass SlowTree;\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Class representing a node of the Class SlowTree\n *\n */\n/*----------------------------------------------------------------------------*/\nclass SlowTreeNode : public SchedTreeBase\n{\n  friend class SlowTree;\n  friend class GeoTreeEngine;\n  friend struct TreeEntryMap;\n  friend struct FastStructures;\n\n  // tree parents\n  SlowTreeNode* pFather;\n  typedef std::map<std::string, SlowTreeNode*> tNodeMap;\n  int pLeavesCount;\n  int pNodeCount;\n  // branches are accessed by their geotag. Convenient for insertion;\n  tNodeMap pChildren;\n  // info\n  TreeNodeInfo pNodeInfo;\n\n  // attributes\n  TreeNodeStateFloat pNodeState;\n\nprotected:\n  void destroy()\n  {\n    for (tNodeMap::iterator it = pChildren.begin(); it != pChildren.end(); it++) {\n      delete it->second;\n    }\n  }\n\n  // update the aggregated data in the nodes and and the ordered set of the branches\n  void update()\n  {\n    if (!pChildren.empty()) {\n      // first update the branches\n      pLeavesCount = 0;\n\n      for (tNodeMap::const_iterator it = pChildren.begin(); it != pChildren.end();\n           it++) {\n        // update this branch\n        it->second->update();\n        pLeavesCount += it->second->pLeavesCount;\n      }\n    } else {\n      pLeavesCount = 1;\n    }\n  }\npublic:\n  SlowTreeNode() : pFather(0) , pLeavesCount(0), pNodeCount(0)\n  {}\n  ~SlowTreeNode()\n  {\n    destroy();\n  }\n\n  template<typename T1, typename T2, typename T3> inline bool\n  writeFastTreeNodeTemplate(struct FastTree<T1, T2, T3>::FastTreeNode* ftn) const\n  {\n    pNodeState.writeCompactVersion(&ftn->fsData);\n    return true;\n  }\n\n  std::ostream& display(std::ostream& os) const;\n  void recursiveDisplay(std::set<std::tuple<std::string, unsigned, unsigned,\n                        TableFormatterColor, unsigned, unsigned, std::string,\n                        std::string, int, int, std::string>>& data_tree,\n                        std::string group, unsigned& geo_depth_max, bool useColors = false,\n                        unsigned prefix1 = 0, unsigned prefix2 = 0);\n  void recursiveDisplayAccess(std::set<std::tuple<unsigned, unsigned, unsigned,\n                              unsigned, std::string, std::string>>& data_access,\n                              unsigned& geo_depth_max, unsigned prefix1 = 0,\n                              unsigned prefix2 = 0);\n};\n\ninline std::ostream& operator << (std::ostream& os,\n                                  const SlowTreeNode& treenode)\n{\n  return treenode.display(os);\n}\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Class representing a scheduling group.\n *        This class is an helper to construct faster and fixed shape structures.\n *\n */\n/*----------------------------------------------------------------------------*/\nclass SlowTree : public SchedTreeBase\n{\n  // tree parents\n  SlowTreeNode pRootNode;\n\n  // size\n  size_t pNodeCount;\n\n  void Init()\n  {\n    pNodeCount = 1; // because of pRootNode\n    pRootNode.pNodeInfo.nodeType = TreeNodeInfo::intermediate;\n    pRootNode.pFather = NULL;\n    pRootNode.pNodeCount = 1;\n    pDebugLevel = 0;\n  }\n\n  SlowTreeNode* insert(const TreeNodeInfo* info, const TreeNodeStateFloat* state,\n                       std::string& fullgeotag, const std::string& partialgeotag,\n                       SlowTreeNode* startfrom, SlowTreeNode* startedConstructingAt,\n                       bool allowUpdate = false);\n\npublic:\n  SlowTree(const std::string& groupId)\n  {\n    Init();\n    pRootNode.pNodeInfo.geotag = groupId;\n  }\n  void setName(const std::string& groupId)\n  {\n    pRootNode.pNodeInfo.geotag = groupId;\n  }\n  const std::string& getName() const\n  {\n    return pRootNode.pNodeInfo.geotag;\n  }\n  SlowTree()\n  {\n    Init();\n  }\n  ~SlowTree()\n  {}\n  void emitDebugInfo(const size_t debugLevel) const\n  {}\n  SlowTreeNode* insert(const TreeNodeInfo* info, const TreeNodeStateFloat* state,\n                       bool addFsIdLevel = true, bool allowUpdate = false);\n  bool remove(const TreeNodeInfo* info, bool addFsIdLevel = true);\n  SlowTreeNode* moveToNewGeoTag(SlowTreeNode* node, const std::string newGeoTag);\n  size_t getNodeCount() const\n  {\n    return pNodeCount;\n  }\n  void display(std::set<std::tuple<std::string, unsigned, unsigned,\n               TableFormatterColor, unsigned, unsigned, std::string, std::string,\n               int, int, std::string>>& data_tree, unsigned& geo_depth_max,\n               bool useColors = false);\n  void displayAccess(std::set<std::tuple<unsigned, unsigned, unsigned,\n                     unsigned, std::string, std::string>>& data_access,\n                     unsigned& geo_depth_max);\n\n  bool buildFastStrcturesSched(\n    FastPlacementTree* fpt, FastROAccessTree* froat, FastRWAccessTree* frwat,\n    FastDrainingPlacementTree* fdpt, FastDrainingAccessTree* fdat,\n    FastTreeInfo* fastinfo, Fs2TreeIdxMap* fs2idx,\n    GeoTag2NodeIdxMap* geo2node) const;\n\n  bool buildFastStructuresGW(\n    FastGatewayAccessTree* fgat, Host2TreeIdxMap* host2idx, FastTreeInfo* fastinfo,\n    GeoTag2NodeIdxMap* geo2node) const;\n\n  bool buildFastStrcturesAccess(\n    FastGatewayAccessTree* fgat, Host2TreeIdxMap* host2idx, FastTreeInfo* fastinfo,\n    GeoTag2NodeIdxMap* geo2node) const;\n\n  FastPlacementTree* allocateAndBuildFastTreeTemplate(FastPlacementTree* fasttree,\n      FastTreeInfo* fastinfo, Fs2TreeIdxMap* fs2idx,\n      GeoTag2NodeIdxMap* geo2node) const;\n};\n\ninline std::ostream& operator << (std::ostream& os, const SlowTree& tree)\n{\n//  return tree.display(os);\n  return os;\n}\n\nEOSMGMNAMESPACE_END\n\n#endif /* __EOSMGM_SLOWTREE__H__ */\n"
  },
  {
    "path": "mgm/geotree/SchedulingTreeCommon.cc",
    "content": "//------------------------------------------------------------------------------\n// @file SchedulingTreeCommon.cc\n// @author Geoffray Adde - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#define DEFINE_TREECOMMON_MACRO\n#include \"mgm/geotree/SchedulingTreeCommon.hh\"\n#include <iomanip>\n\nusing namespace std;\n\nEOSMGMNAMESPACE_BEGIN\n\n// static variables implementation\nSchedTreeBase::Settings SchedTreeBase::gSettings =\n{ 0 , 0};\n\nostream& SchedTreeBase::TreeNodeInfo::display(ostream &os) const\n{\n  if(nodeType==intermediate)\n  os << \"nodetype=intermediate\" << \" , \";\n  else if(nodeType==fs)\n  os << \"nodetype=fs          \" << \" , \";\n  else\n  os << \"nodetype=unknown!    \" << \" , \";\n  os << \"geotag=\" << setw(8) << setfill(' ') << geotag<< \" , \";\n  os << \"fullgeotag=\" << setw(8) << setfill(' ') << fullGeotag<< \" , \";\n  os << \"fsid=\" << setw(20) << fsId << \" , \";\n  os << \"host=\" << setw(32) << host;\n  return os;\n}\n\nostream& operator << (ostream &os, const SchedTreeBase::FastTreeInfo &info)\n{\n  int count = 0;\n  for(SchedTreeBase::FastTreeInfo::const_iterator it=info.begin();it!=info.end();it++)\n  os << setfill(' ') << \"idx=\" << setw(4) << count++ << \" -> \" << (*it) << endl;\n  return os;\n}\n\n// IMPLEMENTED IN HEADER FILE BECAUSE OF A std::map BUG\n//ostream& operator << (ostream &os, const Fs2TreeIdxMap &info) {\n//  cout<< \"size  is : \" << info.size() <<endl;\n//  cout<< \"begin is : \" << &(*(info.begin())) <<endl;\n//  cout<< \"end   is : \" << &(*(info.end())) <<endl;\n//  for(Fs2TreeIdxMap::const_iterator it=info.begin();it!=info.end();it++) {\n//    cout<< \"current   is : \" << &(*it) <<endl;\n//    //os << setfill(' ')  << \"fs=\" << setw(20) << it->first << \" -> \" << \"idx=\" << (int)it->second << endl;\n//  }\n//  return os;\n//}\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/geotree/SchedulingTreeCommon.hh",
    "content": "//------------------------------------------------------------------------------\n// @file SchedulingTreeCommon.hh\n// @author Geoffray Adde - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#define __EOSMGM_TREECOMMON_CHK1__ if(pCheckLevel>=1)\n#define __EOSMGM_TREECOMMON_CHK2__ if(pCheckLevel>=2)\n#define __EOSMGM_TREECOMMON_CHK3__ if(pCheckLevel>=3)\n\n#define __EOSMGM_TREECOMMON_DBG1__ if(pDebugLevel>=1)\n#define __EOSMGM_TREECOMMON_DBG2__ if(pDebugLevel>=2)\n#define __EOSMGM_TREECOMMON_DBG3__ if(pDebugLevel>=3)\n\n#ifndef __EOSMGM_TREECOMMON__H__\n#define __EOSMGM_TREECOMMON__H__\n\n#define __EOSMGM_TREECOMMON__PACK__STRUCTURE__ 0\n\n#include <string>\n#include <vector>\n#include <map>\n#include <iostream>\n#include <iomanip>\n#include <cassert>\n#include <stdint.h>\n#include <sstream>\n//#include \"half/include/half.hpp\"\n#include <iterator>\n#include \"common/FileSystem.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"common/Logging.hh\"\n\n#if __EOSMGM_TREECOMMON__PACK__STRUCTURE__==1\n#pragma pack(push,1)\n#endif\n\n//using half_float::half;\n\nEOSMGMNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Base class of Scheduling Tree software components\n *\n *        It contains typdefs, helper classes as well as debug and display features.\n *\n */\n/*----------------------------------------------------------------------------*/\nstruct SchedTreeBase {\n  // settings\n  struct Settings {\n    //char spreadingFillRatioCap;\n    //char fillRatioCompTol;\n    size_t debugLevel;// 0(off)->3(full)\n    size_t checkLevel;// 0(off)->3(full)\n  };\n\n  static Settings gSettings;\n\n  // debug\nprotected:\n  mutable size_t pDebugLevel;// 0(off)->3(full)\n  mutable size_t pCheckLevel;// 0(off)->3(full)\n\n  inline void\n  setDebugLevel(const size_t debugLevel) const\n  {\n    pDebugLevel = debugLevel;\n  }\n  inline void\n  setCheckLevel(const size_t checkLevel) const\n  {\n    pCheckLevel = checkLevel;\n  }\n\n  SchedTreeBase() :\n    pDebugLevel(gSettings.debugLevel), pCheckLevel(gSettings.checkLevel)\n  {\n  }\n\npublic:\n  SchedTreeBase& operator = (const SchedTreeBase& model)\n  {\n    pDebugLevel = model.pDebugLevel;\n    pCheckLevel = model.pCheckLevel;\n    return *this;\n  }\n\n  // to be sure to control the trade off between structure size and speed\n  // we need to control the data alignment\n\n  // To have a fine control over the structures memory foot print\n  // (which should be kept as small as possible for cache efficiency reasons),\n  // this type is used to refer to the number of nodes in the FastTree\n  // for unsigned char (8 bit) a placement group can have up to 255 nodes\n  // for unsigned short (16 bit) a placement group can have up to 65535 nodes\n  //typedef uint8_t tFastTreeIdx; // 10% faster than uint16_t\n  typedef uint16_t tFastTreeIdx;\n\n  inline static size_t sGetMaxNodeCount()\n  {\n    return std::numeric_limits<tFastTreeIdx>::max();\n  }\n\n  // the data in that structure is not included in the FastTree\n  // it should NOT be necessary to the decision making process\n  // though, it is accessible once the decision is taken\n  // this keeps the FastTree as small as possible and necessary\n  // note that this class could be derived from a base class to\n  // have specific info for different node types\n  struct TreeNodeInfo {\n    typedef enum {\n      intermediate, fs\n    } tNodeType;\n    tNodeType nodeType;\n    std::string geotag;\n    std::string fullGeotag;\n    std::string host;\n    std::string hostport;\n    std::string proxygroup;\n    int8_t fileStickyProxyDepth;\n    eos::common::FileSystem::fsid_t fsId;\n    float netSpeedClass = 0.0;\n\n    std::ostream&\n    display(std::ostream& os) const;\n  };\n\n  // the data in that structure is included in the FastTree\n  // it MUST be necessary to the decision making process\n  //enum tStatus { Drainer = 1<<1, Draining = 1<<2, Balancer = 1<<3, Balancing = 1<<4, Available = 1<<5, Readable = 1<<6, Writable = 1<<7, All = ~0, None = 0 };\n  enum tStatus\n  { Drainer = 1, Draining = 1 << 1, Balancing = 1 << 3, Available = 1 << 4, Readable = 1 << 5, Writable = 1 << 6, Disabled = 1 << 7, All = ~0, None = 0};\n  static std::string fsStatusToStr(int16_t s)\n  {\n    std::string out = \"\";\n\n    if (s & Disabled) {\n      out = out + \"DIS\";\n    }\n\n    if (!(s & Available)) {\n      out = out + \"Unv\";\n    }\n\n    if (s & Balancing) {\n      out = out + \"Bout\";\n    }\n\n    if (s & Drainer) {\n      out = out + \"Din\";\n    }\n\n    if (s & Draining) {\n      out = out + \"Dout\";\n    }\n\n    if (s & Writable) {\n      if (s & Readable) {\n        out = out + \"RW\";\n      } else {\n        out = out + \"WO\";\n      }\n    } else {\n      if (s & Readable) {\n        out = out + \"RO\";\n      } else {\n        out = out + \"noIO\";\n      }\n    }\n\n    return out;\n  }\n\n  static std::string intermediateStatusToStr(int16_t s)\n  {\n    std::string out = \"\";\n\n    if (s & Disabled) {\n      out = +\"Dis\";\n    }\n\n    if (!(s & Available)) {\n      out = +\"Unv\";\n    }\n\n    if (out.empty()) {\n      out = \"OK\";\n    }\n\n    return out;\n  }\n\n  template<typename T>\n  struct TreeNodeState {\n    TreeNodeState() :\n      mStatus(Available), ulScore(0), dlScore(0), totalSpace(0), totalWritableSpace(0), fillRatio(0)\n    {\n    }\n    typedef TreeNodeState<T> tSelf;\n\n    int16_t mStatus;\n    T ulScore;\n    T dlScore;\n    //half mTotalSpace; // this brings 10% speed improvement and also a lower memory footprint but add yet another dependency\n    float totalSpace;\n    float totalWritableSpace;\n    T fillRatio;\n  };\n\n  struct TreeNodeSlots {\n    typedef TreeNodeSlots tSelf;\n    unsigned char freeSlotsCount;\n    unsigned char takenSlotsCount;\n    char avgDlScore;\n    char avgUlScore;\n    char maxDlScore;\n    char maxUlScore;\n\n    TreeNodeSlots() :\n      freeSlotsCount(0), takenSlotsCount(0),\n      avgDlScore(0), avgUlScore(0),\n      maxDlScore(0), maxUlScore(0)\n    {}\n  };\n\n  typedef TreeNodeState<char> TreeNodeStateChar;\n  struct TreeNodeStateFloat : public TreeNodeState<float> {\n    void\n    writeCompactVersion(TreeNodeStateChar* target) const\n    {\n      target->mStatus = mStatus;\n      target->ulScore = (char) ulScore;\n      target->dlScore = (char) dlScore;\n      target->totalSpace = totalSpace;\n      target->totalWritableSpace = totalWritableSpace;\n      target->fillRatio = (char) fillRatio;\n    }\n  };\n\n  template<typename T>\n  inline static signed char\n  comparePlct(const TreeNodeState<T>* const& lefts,\n              const TreeNodeSlots* const& leftp,\n              const TreeNodeState<T>* const& rights,\n              const TreeNodeSlots* const& rightp,\n              const char& spreadingFillRatioCap,\n              const char& fillRatioCompTol)\n  {\n    // this function compares the scheduling priority of two branches\n    // inside a FastTree branches are in a vector which is kept sorted.\n    // if after a replica is placed, the scheduling priority doesn't get higher\n    // than the next priority level being present in the array,\n    // a single swap is enough to keep the order\n    // return value\n    // -1 if left > right\n    //  0 if left == right\n    //  1 if right < left\n    // lexicographic order\n    // -2 - Should not be disabled\n    int16_t mask = Disabled;\n\n    if ((mask == (lefts->mStatus & mask)) && (mask != (rights->mStatus & mask))) {\n      return 1;\n    }\n\n    if ((mask != (lefts->mStatus & mask)) && (mask == (rights->mStatus & mask))) {\n      return -1;\n    }\n\n    // -1 - Should be in the right state\n    mask = Available | Writable;\n\n    if ((mask != (lefts->mStatus & mask)) && (mask == (rights->mStatus & mask))) {\n      return 1;\n    }\n\n    if ((mask == (lefts->mStatus & mask)) && (mask != (rights->mStatus & mask))) {\n      return -1;\n    }\n\n    // Having at least one free slot\n    if (!leftp->freeSlotsCount && rightp->freeSlotsCount) {\n      return 1;\n    }\n\n    if (leftp->freeSlotsCount && !rightp->freeSlotsCount) {\n      return -1;\n    }\n\n    // Have free space taking into account the headroom\n    if ((lefts->totalSpace == 0) && rights->totalSpace) {\n      return -1;\n    }\n\n    if ((rights->totalSpace == 0) && lefts->totalSpace) {\n      return 1;\n    }\n\n    // Respect the SpreadingFillRatioCap\n    if (lefts->fillRatio > spreadingFillRatioCap &&\n        rights->fillRatio <= spreadingFillRatioCap) {\n      return 1;\n    }\n\n    if (lefts->fillRatio <= spreadingFillRatioCap &&\n        rights->fillRatio > spreadingFillRatioCap) {\n      return -1;\n    }\n\n    // As few replicas as possible\n    if (leftp->takenSlotsCount > rightp->takenSlotsCount) {\n      return 1;\n    }\n\n    if (leftp->takenSlotsCount < rightp->takenSlotsCount) {\n      return -1;\n    }\n\n    //#define USEMAXSLOT\n#ifdef USEMAXSLOT\n\n    // As many available slots as possible\n    if (leftp->freeSlotsCount\n        < rightp->freeSlotsCount) {\n      return 1;\n    }\n\n    if (leftp->freeSlotsCount\n        > rightp->freeSlotsCount) {\n      return -1;\n    }\n\n    //  else return false;\n#else\n\n    // As empty as possible\n    if (lefts->fillRatio > rights->fillRatio + fillRatioCompTol) {\n      return 1;\n    }\n\n    if (lefts->fillRatio + fillRatioCompTol < rights->fillRatio) {\n      return -1;\n    }\n\n    //  else return false;\n#endif\n    return 0;\n  }\n\n  template<typename T>\n  inline static signed char\n  compareAccessRO(const TreeNodeState<T>* const& lefts,\n                  const TreeNodeSlots* const& leftp, const TreeNodeState<T>* const& rights,\n                  const TreeNodeSlots* const& rightp)\n  {\n    // this function compares the scheduling priority of two branches\n    // inside a FastTree branches are in a vector which is kept sorted.\n    // if after a replica is placed, the scheduling priority doesn't get higher\n    // than the next priority level being present in the array,\n    // a single swap is enough to keep the order\n    // return value\n    // -1 if left  > right\n    //  0 if left == right\n    //  1 if right > left\n    // lexicographic order\n    // -2 - Should not be disabled\n    int16_t mask = Disabled;\n\n    if ((mask == (lefts->mStatus & mask)) && (mask != (rights->mStatus & mask))) {\n      return 1;\n    }\n\n    if ((mask != (lefts->mStatus & mask)) && (mask == (rights->mStatus & mask))) {\n      return -1;\n    }\n\n    mask = Available | Readable;\n\n    if ((mask != (lefts->mStatus & mask)) && (mask == (rights->mStatus & mask))) {\n      return 1;\n    }\n\n    if ((mask == (lefts->mStatus & mask)) && (mask != (rights->mStatus & mask))) {\n      return -1;\n    }\n\n    // 0 - Having at least one free slot\n    if (!leftp->freeSlotsCount && rightp->freeSlotsCount) {\n      return 1;\n    }\n\n    if (leftp->freeSlotsCount && !rightp->freeSlotsCount) {\n      return -1;\n    }\n\n    // we might add a notion of depth to minimize latency\n    return 0;\n  }\n\n  template<typename T>\n  inline static signed char\n  compareAccessDrain(const TreeNodeState<T>* const& lefts,\n                     const TreeNodeSlots* const& leftp, const TreeNodeState<T>* const& rights,\n                     const TreeNodeSlots* const& rightp)\n  {\n    // this function compares the scheduling priority of two branches\n    // inside a FastTree branches are in a vector which is kept sorted.\n    // if after a replica is placed, the scheduling priority doesn't get higher\n    // than the next priority level being present in the array,\n    // a single swap is enough to keep the order\n    // return value\n    // -1 if left  > right\n    //  0 if left == right\n    //  1 if right > left\n    // lexicographic order\n    // -2 - Should not be disabled\n    int16_t mask = Disabled;\n\n    if ((mask == (lefts->mStatus & mask)) && (mask != (rights->mStatus & mask))) {\n      return 1;\n    }\n\n    if ((mask != (lefts->mStatus & mask)) && (mask == (rights->mStatus & mask))) {\n      return -1;\n    }\n\n    // should be readable or draining\n    int16_t mask2 = Available | Draining;\n    mask = Available | Readable;\n\n    if (\n      ((mask != (lefts->mStatus & mask)) && (mask2 != (lefts->mStatus & mask2)))\n      &&\n      ((mask == (rights->mStatus & mask)) || (mask2 == (rights->mStatus & mask2)))\n    ) {\n      return 1;\n    }\n\n    if (\n      ((mask == (lefts->mStatus & mask)) || (mask2 == (lefts->mStatus & mask2)))\n      &&\n      ((mask != (rights->mStatus & mask)) && (mask2 != (rights->mStatus & mask2)))\n    ) {\n      return -1;\n    }\n\n    // 0 - Having at least one free slot\n    if (!leftp->freeSlotsCount && rightp->freeSlotsCount) {\n      return 1;\n    }\n\n    if (leftp->freeSlotsCount && !rightp->freeSlotsCount) {\n      return -1;\n    }\n\n    // we might add a notion of depth to minimize latency\n    return 0;\n  }\n\n  template<typename T>\n  inline static signed char\n  compareAccessRW(const TreeNodeState<T>* const& lefts,\n                  const TreeNodeSlots* const& leftp, const TreeNodeState<T>* const& rights,\n                  const TreeNodeSlots* const& rightp)\n  {\n    // this function compares the scheduling priority of two branches\n    // inside a FastTree branches are in a vector which is kept sorted.\n    // if after a replica is placed, the scheduling priority doesn't get higher\n    // than the next priority level being present in the array,\n    // a single swap is enough to keep the order\n    // return value\n    // -1 if left  > right\n    //  0 if left == right\n    //  1 if right > left\n    // lexicographic order\n    // -2 - Should not be disabled\n    int16_t mask = Disabled;\n\n    if ((mask == (lefts->mStatus & mask)) && (mask != (rights->mStatus & mask))) {\n      return 1;\n    }\n\n    if ((mask != (lefts->mStatus & mask)) && (mask == (rights->mStatus & mask))) {\n      return -1;\n    }\n\n    mask = Available | Readable | Writable;\n\n    if ((mask != (lefts->mStatus & mask)) && (mask == (rights->mStatus & mask))) {\n      return 1;\n    }\n\n    if ((mask == (lefts->mStatus & mask)) && (mask != (rights->mStatus & mask))) {\n      return -1;\n    }\n\n    // 0 - Having at least one free slot\n    if (!leftp->freeSlotsCount && rightp->freeSlotsCount) {\n      return 1;\n    }\n\n    if (leftp->freeSlotsCount && !rightp->freeSlotsCount) {\n      return -1;\n    }\n\n    // we might add a notion of depth to minimize latency\n    return 0;\n  }\n\n  template<typename T>\n  inline static signed char\n  compareDrnPlct(const TreeNodeState<T>* const& lefts,\n                 const TreeNodeSlots* const& leftp, const TreeNodeState<T>* const& rights,\n                 const TreeNodeSlots* const& rightp, const char& spreadingFillRatioCap,\n                 const char& fillRatioCompTol)\n  {\n    // -2 - Should not be disabled\n    int16_t mask = Disabled;\n\n    if ((mask == (lefts->mStatus & mask)) && (mask != (rights->mStatus & mask))) {\n      return 1;\n    }\n\n    if ((mask != (lefts->mStatus & mask)) && (mask == (rights->mStatus & mask))) {\n      return -1;\n    }\n\n    // -1 - Should be a drainer\n    mask = Available | Writable | Drainer;\n\n    if ((mask != (lefts->mStatus & mask)) && (mask == (rights->mStatus & mask))) {\n      return 1;\n    }\n\n    if ((mask == (lefts->mStatus & mask)) && (mask != (rights->mStatus & mask))) {\n      return -1;\n    }\n\n    // 0 - Having at least one free slot\n    if (!leftp->freeSlotsCount && rightp->freeSlotsCount) {\n      return 1;\n    }\n\n    if (leftp->freeSlotsCount && !rightp->freeSlotsCount) {\n      return -1;\n    }\n\n    // 1 - respect of SpreadingFillRatioCap\n    if (lefts->fillRatio > spreadingFillRatioCap &&\n        rights->fillRatio <= spreadingFillRatioCap) {\n      return 1;\n    }\n\n    if (lefts->fillRatio <= spreadingFillRatioCap &&\n        rights->fillRatio > spreadingFillRatioCap) {\n      return -1;\n    }\n\n    // 2 - as few replicas as possible\n    if (leftp->takenSlotsCount > rightp->takenSlotsCount) {\n      return 1;\n    }\n\n    if (leftp->takenSlotsCount < rightp->takenSlotsCount) {\n      return -1;\n    }\n\n    //#define USEMAXSLOT\n#ifdef USEMAXSLOT\n\n    // 3 - as many available slots as possible\n    if (leftp->freeSlotsCount\n        < rightp->freeSlotsCount) {\n      return 1;\n    }\n\n    if (leftp->freeSlotsCount\n        > rightp->freeSlotsCount) {\n      return -1;\n    }\n\n    //  else return false;\n#else\n\n    // 3 - as empty as possible\n    if (lefts->fillRatio > rights->fillRatio + fillRatioCompTol) {\n      return 1;\n    }\n\n    if (lefts->fillRatio + fillRatioCompTol < rights->fillRatio) {\n      return -1;\n    }\n\n    //  else return false;\n#endif\n    return 0;\n  }\n\n  template<typename T>\n  inline static signed char\n  compareGateway(const TreeNodeState<T>* const& lefts,\n                 const TreeNodeSlots* const& leftp, const TreeNodeState<T>* const& rights,\n                 const TreeNodeSlots* const& rightp)\n  {\n    // -2 - Should not be disabled\n    int16_t mask = Disabled;\n\n    if ((mask == (lefts->mStatus & mask)) && (mask != (rights->mStatus & mask))) {\n      return 1;\n    }\n\n    if ((mask != (lefts->mStatus & mask)) && (mask == (rights->mStatus & mask))) {\n      return -1;\n    }\n\n    // lexicographic order\n    // -1 - Should be a balancing\n    mask = Available;\n\n    if ((mask != (lefts->mStatus & mask)) && (mask == (rights->mStatus & mask))) {\n      return 1;\n    }\n\n    if ((mask == (lefts->mStatus & mask)) && (mask != (rights->mStatus & mask))) {\n      return -1;\n    }\n\n    // we might add a notion of depth to minimize latency\n    return 0;\n  }\n\n  typedef TreeNodeInfo FastTreeNodeInfo;\n\n  typedef std::vector<FastTreeNodeInfo> FastTreeInfo;\n\n  //typedef std::map<unsigned long,tFastTreeIdx> Fs2TreeIdxMap;\n\n};\n// class SchedTreeBase\n\ninline std::ostream&\noperator <<(std::ostream& os, const SchedTreeBase::TreeNodeInfo& info)\n{\n  return info.display(os);\n}\nstd::ostream&\noperator <<(std::ostream& os, const SchedTreeBase::FastTreeInfo& info);\n\nEOSMGMNAMESPACE_END\n\n#if __EOSMGM_TREECOMMON__PACK__STRUCTURE__==1\n#pragma pack(pop)\n#endif\n\n#endif  /* __EOSMGM_TREECOMMON__H__ */\n"
  },
  {
    "path": "mgm/geotree/SchedulingTreeTest.cc",
    "content": "//------------------------------------------------------------------------------\n// @file SchedulingTreeTest.hh\n// @author Geoffray Adde - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#undef NDEBUG\n#include \"mgm/geotree/SchedulingSlowTree.hh\"\n#include \"common/Logging.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/utils/RandUtils.hh\"\n#include <iostream>\n#include <iomanip>\n#include <sstream>\n#include <string>\n#include <set>\n#include <vector>\n#include <map>\n#include <algorithm>\n#include <fstream>\n#include <cmath>\n#include <functional>\n#include <limits>\n\nusing namespace std;\nusing namespace eos::mgm;\n\n#define RUN_FUNCTIONAL_TEST 1\n#define RUN_BURNIN_TEST 1\nsize_t CheckLevel = 1;\nsize_t DebugLevel = 1;\nconst int bufferSize = 16384;\nconst size_t groupSize = 100;\nconst size_t nFsPerBox = 26;\n\nsize_t nAvailableFsPlct;\nsize_t nAvailableFsDrnPlct;\nsize_t nAvailableFsBlcPlct;\nsize_t nAvailableFsROAccess;\nsize_t nAvailableFsRWAccess;\nsize_t nUnavailFs;\nsize_t nDisabledFs;\n\nint PopulateSchedGroupFromFile(const string& fileName, size_t groupSize,\n                               size_t nFsPerBox,\n                               vector<set<pair<string, string> > >& schedGroups)\n{\n  // open file\n  ifstream ifs;\n  ifs.open(fileName.c_str());\n\n  if (!ifs.is_open()) {\n    cerr << \"cannot open file \" << fileName << endl;\n    return 1;\n  }\n\n  // read file\n  string host, geotag;\n  map<string, string> items;\n\n  while (!ifs.eof() && !ifs.fail() && !ifs.bad()) {\n    host.clear();\n    geotag.clear();\n    getline(ifs, host, ':');\n    getline(ifs, geotag);\n\n    if (host.empty()) {\n      break;\n    }\n\n    eos::common::trim(host);\n    eos::common::trim(geotag);\n    items[host] = geotag;\n  }\n\n  size_t nHosts = items.size();\n  cout << \"read \" << nHosts << \" items in file \" << fileName << endl;\n  // check schedgroups size\n  size_t nGroups = (nFsPerBox * nHosts) / groupSize;\n\n  if (!nGroups) {\n    cerr << \" group size is too large for the number of hosts and fs per host\" <<\n         endl;\n    return 1;\n  }\n\n  if (groupSize > nHosts) {\n    cerr << \" group size is larger than the number of hosts \" << endl;\n    return 1;\n  }\n\n  // build schedgroups\n  size_t grSize = groupSize;\n  set<pair<string, string> >* currentGroup = 0;\n\n  for (size_t fs = 0; fs < nFsPerBox; fs++) {\n    for (map<string, string>::const_iterator it = items.begin(); it != items.end();\n         it++) {\n      if (grSize == groupSize) {\n        // create a new group if necessary\n        if (schedGroups.size() == nGroups) {\n          goto end;\n        }\n\n        schedGroups.resize(schedGroups.size() + 1);\n        currentGroup = &schedGroups[schedGroups.size() - 1];\n        grSize = 0;\n      }\n\n      ostringstream oss;\n      oss << it->second;\n      currentGroup->insert(make_pair(it->first, oss.str()));\n      grSize++;\n    }\n  }\n\nend:\n  return 0;\n}\n\ninline size_t treeDepthSimilarity(const std::string& left,\n                                  const std::string& right)\n{\n  if (left.empty() || right.empty()) {\n    return 0;\n  }\n\n  size_t depth = 0;\n\n  for (size_t k = 0; k < min(left.size(), right.size()) - 1; k++) {\n    if (left[k] != right[k]) {\n      break;\n    }\n\n    if (left[k] == ':' && left[k + 1] == ':') {\n      depth++;\n    }\n  }\n\n  return depth;\n}\n\ntemplate<typename T1, typename T2, typename T3, typename T4>\nvoid functionalTestFastTree(FastTree<T1, T2>* fptree, FastTree<T3, T4>* fatree,\n                            GeoTag2NodeIdxMap* geomap,\n                            SchedTreeBase::FastTreeInfo* treeinfo, size_t nMaxReplicas)\n{\n  // do verification regarding the placement, the access and the geolocation\n  for (size_t loop = 0; loop < 1000; loop++) {\n    // select a random number of replicas\n    size_t nreplica = eos::common::getRandom(1ul, nMaxReplicas);\n    // copy a blank copy of the FastTree\n    char buffer[bufferSize];\n    assert(fptree->copyToBuffer(buffer, bufferSize) == 0);\n    FastPlacementTree* ftree = (FastPlacementTree*) buffer;\n    char buffer2[bufferSize];\n    assert(fatree->copyToBuffer(buffer2, bufferSize) == 0);\n    FastROAccessTree* ftree2 = (FastROAccessTree*) buffer2;\n    // place the replicas\n    set<SchedTreeBase::tFastTreeIdx> repIdxs;\n    SchedTreeBase::tFastTreeIdx repIdx;\n\n    for (size_t k = 0; k < nreplica; k++) {\n      assert(ftree->findFreeSlot(repIdx));\n      repIdxs.insert(repIdx);\n    }\n\n    // repopulate the access tree with the placed replicas\n    // for (set<SchedTreeBase::tFastTreeIdx>::const_iterator it = repIdxs.begin();\n    //      it != repIdxs.end(); it++) {\n    //   ftree2->incrementFreeSlot(*it);\n    // }\n    // ========= PLACEMENT/ACCESS ROUNDTRIP TEST =========\n    // get all the replicas\n    SchedTreeBase::tFastTreeIdx allreplicas[255], nr;\n    nr = 255;\n    nr = ftree2->findFreeSlotsAll(allreplicas, nr);\n    assert(nr);\n    // check that all the replicas are there\n    set<SchedTreeBase::tFastTreeIdx> allreplicasset, symdif;\n    allreplicasset.insert(allreplicas, allreplicas + nr);\n    set<SchedTreeBase::tFastTreeIdx>::iterator symdifbeg = symdif.begin();\n    set_symmetric_difference(repIdxs.begin(), repIdxs.end(), allreplicasset.begin(),\n                             allreplicasset.end(),\n                             inserter(symdif, symdifbeg));\n    assert(symdif.empty());\n\n    for (size_t k = 0; k < nreplica; k++) {\n      // check that the closest node is the node itself\n      SchedTreeBase::tFastTreeIdx closest = geomap->getClosestFastTreeNode((\n                                              *treeinfo)[k].fullGeotag.c_str());\n      assert(closest == k);\n      // request an access\n      assert(ftree2->findFreeSlot(repIdx, closest, true, true));\n      // check that the replica node is among the placed replica\n      assert(repIdxs.count(repIdx));\n      // check that it's the nearest one (i.e) the deepest tree similarity\n      size_t simRep = treeDepthSimilarity((*treeinfo)[k].fullGeotag,\n                                          (*treeinfo)[closest].fullGeotag);\n\n      for (set<SchedTreeBase::tFastTreeIdx>::const_iterator it = repIdxs.begin();\n           it != repIdxs.end(); it++) {\n        assert(treeDepthSimilarity((*treeinfo)[k].fullGeotag,\n                                   (*treeinfo)[*it].fullGeotag) <= simRep);\n      }\n    }\n  }\n}\n\ntemplate\nvoid __attribute__((used)) __attribute__((noinline))\neos::mgm::debugDisplay(const FastPlacementTree& tree);\n\ntemplate\nvoid __attribute__((used)) __attribute__((noinline))\neos::mgm::debugDisplay(const FastROAccessTree& tree);\n\nvoid debugDisplayPlct(const FastPlacementTree& tree)\n{\n  debugDisplay(tree);\n}\nvoid debugDisplayAccs(const FastROAccessTree& tree)\n{\n  debugDisplay(tree);\n}\n\nbool testAccess(const std::string& toAccess, const std::string& accessible)\n{\n//  std::cout << \"testAccess\" << \" \" << toAccess << \",\" << accessible<< std::endl;\n  size_t beg = std::numeric_limits<size_t>::max(),\n         end = std::numeric_limits<size_t>::max();\n\n  //size_t beg = 0, end = 0;\n  for (size_t i = 0; i < accessible.size(); i++) {\n    if (accessible[i] == ',') {\n      if (beg == std::numeric_limits<size_t>::max()) {\n        continue;\n      }\n\n      end = i;\n\n      // if we have a new token\n      if (end > beg) {\n//        std::cout << \"token1=\" << accessible.substr(beg,end-beg) <<std::endl;\n        if (((end - beg) <= toAccess.size()\n             && ((end - beg) == toAccess.size() || toAccess[end - beg] == ':'))\n            && !strncmp(toAccess.c_str(), accessible.c_str() + beg, end - beg)) {\n          return true;\n        }\n\n        beg = end + 1;\n      }\n    } else if (beg == std::numeric_limits<size_t>::max()) {\n      beg = i;\n    }\n  }\n\n  // the end of the string is also the end of the last token\n  if (beg < accessible.size()) {\n    end = accessible.size();\n  }\n\n  if (end > beg) {\n//    std::cout << \"token2=\" << accessible.substr(beg,end-beg) <<std::endl;\n//    std::cout <<\n//        ((end - beg) <= toAccess.size ()) <<\n//        ( (end - beg) == toAccess.size () || toAccess[end - beg] == ':') <<\n//        (strncmp (toAccess.c_str (), accessible.c_str () + beg, end - beg));\n    if (((end - beg) <= toAccess.size()\n         && ((end - beg) == toAccess.size() || toAccess[end - beg] == ':'))\n        && !strncmp(toAccess.c_str(), accessible.c_str() + beg, end - beg)) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nint main()\n{\n  SlowTree* st = new SlowTree(\"pg1\");\n  SlowTree::TreeNodeInfo* tni = new SlowTree::TreeNodeInfo();\n  SlowTree::TreeNodeStateFloat* tns = new SlowTree::TreeNodeStateFloat();\n  tni->fsId = 1;\n  tni->geotag = \"A::B::C\";\n  tni->host = \"A\";\n  tni->hostport = \"A\";\n  tni->proxygroup = \"A\";\n  tns->dlScore = 99;\n  tns->ulScore = 99;\n  tns->mStatus = SchedTreeBase::Available | SchedTreeBase::Writable |\n                 SchedTreeBase::Readable;\n  st->insert(tni, tns, false, true);\n  tni->fsId = 2;\n  tni->geotag = \"A::B::D\";\n  tni->host = \"A::B\";\n  tni->hostport = \"A::B\";\n  tni->proxygroup = \"A::B\";\n  st->insert(tni, tns, false, true);\n  tni->fsId = 3;\n  tni->geotag = \"A\";\n  tni->host = \"B,A::B::C,A::B::D\";\n  tni->hostport = \"B,A::B::C,A::B::D\";\n  tni->proxygroup = \"B,A::B::C,A::B::D\";\n  st->insert(tni, tns, false, true);\n  FastGatewayAccessTree* ft = new FastGatewayAccessTree();\n  ft->selfAllocate(255);\n  SchedTreeBase::FastTreeInfo* fti = new SchedTreeBase::FastTreeInfo();\n  fti->reserve(255);\n  Host2TreeIdxMap* ftmap = new Host2TreeIdxMap();\n  ftmap->selfAllocate(255);\n  GeoTag2NodeIdxMap* geomap = new GeoTag2NodeIdxMap();\n  geomap->selfAllocate(255);\n  st->buildFastStrcturesAccess(ft, ftmap, fti, geomap);\n  std::cout << \" AccessGeotagMapping is \" << endl;\n  unsigned geo_depth_max = 0;\n  TableFormatterBase table_access;\n  table_access.SetHeader({\n    std::make_tuple(\"operation\", 6, \"-s\"),\n    std::make_tuple(\"geotag\", 6, \"s\"),\n    std::make_tuple(\"mapping\", 6, \"s\")\n  });\n  // Set for tree: num of line, depth, prefix_1, prefix_2, fullGeotag, proxygroup/direct\n  std::set<std::tuple<unsigned, unsigned, unsigned, unsigned, std::string, std::string>>\n      data_access;\n  st->displayAccess(data_access, geo_depth_max);\n\n  for (auto it : data_access) {\n    if (!std::get<5>(it).empty()) {\n      TableData table_data;\n      table_data.emplace_back();\n      table_data.back().push_back(TableCell(\"AccessGeotagMapping\", \"s\"));\n      table_data.back().push_back(TableCell(std::get<4>(it), \"s\"));\n      table_data.back().push_back(TableCell(std::get<5>(it), \"s\"));\n      table_access.AddRows(table_data);\n    }\n  }\n\n  std::cout << table_access.GenerateTable(HEADER).c_str() << endl;\n  std::cout << \" SlowTree is \" << endl;\n  TableFormatterBase table_tree;\n  table_tree.SetHeader({\n    std::make_tuple(\"group\", 6, \"s\"),\n    std::make_tuple(\"geotag\", 6, \"s\"),\n    std::make_tuple(\"fsid\", 4, \"l\"),\n    std::make_tuple(\"node\", 12, \"s\"),\n    std::make_tuple(\"leavs\", 5, \"l\"),\n    std::make_tuple(\"nodes\", 5, \"l\"),\n    std::make_tuple(\"status\", 6, \"s\")\n  });\n  std::set<std::tuple<std::string, unsigned, unsigned, TableFormatterColor,\n      unsigned, unsigned, std::string, std::string,\n      int, int, std::string>> data_tree;\n  st->display(data_tree, geo_depth_max, true);\n\n  for (auto it : data_tree) {\n    TableData table_data;\n    table_data.emplace_back();\n\n    if (std::get<2>(it) == 1) {\n      table_tree.AddSeparator();\n      table_data.back().push_back(TableCell(std::get<0>(it), \"s\", \"\", false,\n                                            std::get<3>(it)));\n      table_data.back().push_back(TableCell(\"\", \"s\"));\n      table_data.back().push_back(TableCell(\"\", \"s\"));\n      table_data.back().push_back(TableCell(\"\", \"s\"));\n    } else if (std::get<2>(it) == 2) {\n      table_data.back().push_back(TableCell(std::get<5>(it), \"t\"));\n      table_data.back().push_back(TableCell(std::get<7>(it), \"s\", \"\", false,\n                                            std::get<3>(it)));\n      table_data.back().push_back(TableCell(\"\", \"s\"));\n      table_data.back().push_back(TableCell(\"\", \"s\"));\n    } else if (std::get<2>(it) == 3) {\n      table_data.back().push_back(TableCell(std::get<4>(it), \"t\"));\n      table_data.back().push_back(TableCell(std::get<5>(it), \"t\"));\n      table_data.back().push_back(TableCell(std::get<6>(it), \"l\", \"\", false,\n                                            std::get<3>(it)));\n      table_data.back().push_back(TableCell(std::get<7>(it), \"s\", \"\", false,\n                                            std::get<3>(it)));\n    }\n\n    table_data.back().push_back(TableCell(std::get<8>(it), \"l\"));\n    table_data.back().push_back(TableCell(std::get<9>(it), \"l\"));\n    table_data.back().push_back(TableCell(std::get<10>(it), \"s\"));\n    table_tree.AddRows(table_data);\n  }\n\n  std::cout << table_tree.GenerateTable(HEADER).c_str() << endl;\n  ft->checkConsistency(0);\n  std::cout << \" FastTree is \" << endl << *ft << endl;\n  std::vector<std::string> testv;\n  testv.push_back(\"A\");\n  testv.push_back(\"B\");\n  testv.push_back(\"A::B\");\n  testv.push_back(\"A::Z\");\n  testv.push_back(\"A::B::C\");\n  testv.push_back(\"A::B::C1\");\n  testv.push_back(\"A::B::D\");\n  testv.push_back(\"A::B::D1\");\n  testv.push_back(\"A::B::E\");\n\n  for (auto it = testv.begin(); it != testv.end(); it++) {\n    auto idx  = geomap->getClosestFastTreeNode(it->c_str());\n    SchedTreeBase::tFastTreeIdx idx2 = 0;\n    ft->findFreeSlotFirstHitBack(idx2, idx);\n    std::cout << \"geotag=\" << *it;\n    std::cout << \"  closest_idx=\" << (int)idx;\n    std::cout << \"  access_idx=\" << (int)idx2;\n    std::cout << \"  access_tag=\" << (*fti)[idx2].proxygroup;\n    std::cout << \"  can_access=\";\n\n    for (auto it2 = testv.begin(); it2 != testv.end(); it2++) {\n      if (testAccess(*it2, (*fti)[idx2].proxygroup)) {\n        std::cout << *it2 << \"|\";\n      }\n    }\n\n    std::cout << std::endl;\n  }\n\n  delete st;\n  delete tni;\n  delete tns;\n  delete ft;\n  delete fti;\n  delete ftmap;\n  delete geomap;\n  return 0;\n}\n\nint main2()\n{\n  SlowTree st(\"pg1\");\n  SlowTree::TreeNodeInfo tni;\n  SlowTree::TreeNodeStateFloat tns;\n  tni.fsId = 1;\n  tni.geotag = \"nogeotag\";\n  tni.host = \"host1.cern.ch:1095\";\n  tni.hostport = \"host1.cern.ch:1095\";\n  tni.proxygroup = \"pg1\";\n  tns.dlScore = 99;\n  tns.ulScore = 99;\n  tns.mStatus = SchedTreeBase::Available | SchedTreeBase::Writable |\n                SchedTreeBase::Readable;\n  st.insert(&tni, &tns);\n  tni.fsId = 2;\n  tni.host = \"host1.cern.ch:1096\";\n  tni.hostport = \"host1.cern.ch:1096\";\n  st.insert(&tni, &tns);\n  FastGatewayAccessTree ft;\n  ft.selfAllocate(255);\n  SchedTreeBase::FastTreeInfo fti;\n  fti.reserve(255);\n  Host2TreeIdxMap ftmap;\n  ftmap.selfAllocate(255);\n  GeoTag2NodeIdxMap geomap;\n  geomap.selfAllocate(255);\n  st.buildFastStructuresGW(&ft, &ftmap, &fti, &geomap);\n  std::cout << \" SlowTree is \" << endl << st << endl;\n  ft.checkConsistency(0);\n  std::cout << \" FastTree is \" << endl << ft << endl;\n\n  for (int i = 0; i < 20; i++) {\n    SchedTreeBase::tFastTreeIdx idx = 0;\n    ft.findFreeSlotFirstHit(idx, idx, false, false);\n    std::cout << \"FirstHit=\" << (int)idx;\n    idx = 0;\n    ft.findFreeSlotSkipSaturated(idx, idx, false, false);\n    std::cout << \"  SkipSat=\" << (int)idx << std::endl;\n  }\n\n  return 0;\n}\n\nint mainFull()\n{\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n  g_logging.SetUnit(\"SchedulingTreeTest\");\n  g_logging.SetLogPriority(LOG_INFO);\n  string geoTagFileNameStr = __FILE__;\n  geoTagFileNameStr += \".testfile\";\n  const char* geoTagFileName = geoTagFileNameStr.c_str();\n  vector<set<pair<string, string> > > schedGroups;\n  SchedTreeBase::gSettings.checkLevel = CheckLevel;\n  SchedTreeBase::gSettings.debugLevel = DebugLevel;\n  PopulateSchedGroupFromFile(geoTagFileName, groupSize, nFsPerBox, schedGroups);\n  vector<SlowTree> trees(schedGroups.size());\n  vector<FastPlacementTree> fptrees(schedGroups.size());\n  vector<FastDrainingPlacementTree> fdptrees(schedGroups.size());\n  vector<FastROAccessTree> froatrees(schedGroups.size());\n  vector<FastRWAccessTree> frwatrees(schedGroups.size());\n  vector<FastDrainingAccessTree> fdatrees(schedGroups.size());\n  vector<SchedTreeBase::FastTreeInfo> ftinfos(schedGroups.size());\n  vector<Fs2TreeIdxMap> ftmaps(schedGroups.size());\n  vector<GeoTag2NodeIdxMap> geomaps(schedGroups.size());\n  vector<set<eos::common::FileSystem::fsid_t> > drainerfs(schedGroups.size()),\n         drainingfs(schedGroups.size());\n  vector<set<eos::common::FileSystem::fsid_t> > balancerfs(schedGroups.size()),\n         balancingfs(schedGroups.size());\n  vector<map<eos::common::FileSystem::fsid_t, SchedTreeBase::tFastTreeIdx> >\n  maxDningToDnerSimil(schedGroups.size()),\n                      maxBcingToBcerSimil(schedGroups.size());\n  size_t idx = 0;\n\n  for (vector<set<pair<string, string> > >::const_iterator sgit =\n         schedGroups.begin(); sgit != schedGroups.end();\n       sgit++) {\n    ostringstream oss;\n    oss << idx;\n    trees[idx].setName(oss.str());\n    // insert the branches in the tree\n    nAvailableFsPlct = 0;\n    nAvailableFsDrnPlct = 0;\n    nAvailableFsBlcPlct = 0;\n    nAvailableFsROAccess = 0;\n    nAvailableFsRWAccess = 0;\n    nUnavailFs = 0;\n    nDisabledFs = 0;\n\n    for (set<pair<string, string> >::const_iterator it = sgit->begin();\n         it != sgit->end(); it++) {\n      SchedTreeBase::TreeNodeInfo info;\n      info.geotag = it->second;\n      info.host = it->first;\n      info.fsId = eos::common::getRandom();\n      //std::cout<<info.mGeotag.c_str()<<\"\\t\"<<info.mFsId<<std::endl;\n      SchedTreeBase::TreeNodeStateFloat state;\n      state.dlScore = 1.0;\n      state.ulScore = 1.0;\n      state.mStatus = SchedTreeBase::Available | SchedTreeBase::Writable |\n                      SchedTreeBase::Readable;\n      state.fillRatio = 0.5;\n      state.totalSpace = 2e12;\n      int r = eos::common::getRandom();\n\n      if (r < RAND_MAX / 64) { // make 1/64 th unavailable\n        state.mStatus = (SchedTreeBase::tStatus)(state.mStatus &\n                        ~SchedTreeBase::Available);\n        nUnavailFs++;\n      } else if (!(r % 16)) { // make 1/16 th disabled\n        state.mStatus = (SchedTreeBase::tStatus)(state.mStatus |\n                        SchedTreeBase::Disabled);\n        nDisabledFs++;\n      } else {\n        nAvailableFsPlct++;\n        nAvailableFsROAccess++;\n        nAvailableFsRWAccess++;\n\n        if (r < RAND_MAX / 32) {\n          state.mStatus = (SchedTreeBase::tStatus)(state.mStatus |\n                          SchedTreeBase::Draining);\n          state.mStatus = (SchedTreeBase::tStatus)(state.mStatus &\n                          ~SchedTreeBase::Writable);\n          state.mStatus = (SchedTreeBase::tStatus)(state.mStatus &\n                          ~SchedTreeBase::Readable);\n          nAvailableFsPlct--;\n          nAvailableFsROAccess--;\n          nAvailableFsRWAccess--;\n          drainingfs[idx].insert(info.fsId);\n          maxDningToDnerSimil[idx][info.fsId] = 0;\n        } else {\n          if (r > RAND_MAX / 4) {\n            state.mStatus = (SchedTreeBase::tStatus)(state.mStatus |\n                            SchedTreeBase::Drainer);\n            drainerfs[idx].insert(info.fsId);\n            nAvailableFsDrnPlct++;\n          }\n\n          r = eos::common::getRandom();\n\n          if (r < RAND_MAX / 8) {\n            if (state.mStatus & SchedTreeBase::Drainer) {\n              nAvailableFsDrnPlct--;\n            }\n\n            state.mStatus = (SchedTreeBase::tStatus)(state.mStatus |\n                            SchedTreeBase::Balancing);\n            state.mStatus = (SchedTreeBase::tStatus)(state.mStatus &\n                            ~SchedTreeBase::Writable);\n            nAvailableFsPlct--;\n            nAvailableFsRWAccess--;\n            balancingfs[idx].insert(info.fsId);\n            maxBcingToBcerSimil[idx][info.fsId] = 0;\n          }\n        }\n      }\n\n      //std::cout<< \"insert =>\" << it->first <<\"\\t\"<< it->second<<\"::\"<<info.mFsId <<std::endl;\n      assert(trees[idx].insert(&info, &state) != NULL);\n      assert(trees[idx].remove(\n               &info));  // erase and rewrite to just to give a try to this feature\n      trees[idx].insert(&info, &state);\n    }\n\n    cout << \"group \" << std::setw(3) << idx << \"\\tnAvailableFsROAccess = \" <<\n         std::setw(3) << nAvailableFsROAccess\n         << \"\\tnAvailableFsRWAccess = \" << std::setw(3) << nAvailableFsRWAccess <<\n         \"\\tnAvailableFsPlct = \"\n         << std::setw(3) << nAvailableFsPlct << \"\\tnAvailableFsBlcPlct = \" << std::setw(\n           3) << nAvailableFsBlcPlct\n         << \"\\tnAvailableFsDrnPlct = \" << std::setw(3) << nAvailableFsDrnPlct <<\n         \"\\tnUnavailFs = \" << std::setw(3)\n         << nUnavailFs << \"\\tnDisabledFs = \" << std::setw(3) << nDisabledFs << std::endl;\n    // allocate the memory for the content of the FastTree\n    fptrees[idx].selfAllocate(trees[idx].getNodeCount());\n    fdptrees[idx].selfAllocate(trees[idx].getNodeCount());\n    froatrees[idx].selfAllocate(trees[idx].getNodeCount());\n    frwatrees[idx].selfAllocate(trees[idx].getNodeCount());\n    fdatrees[idx].selfAllocate(trees[idx].getNodeCount());\n    // build the FastTree\n    //std::cout<<trees[0]<<std::endl;\n    assert(\n      trees[idx].buildFastStrcturesSched(&fptrees[idx], &froatrees[idx],\n                                         &frwatrees[idx], &fdptrees[idx], &fdatrees[idx], &ftinfos[idx], &ftmaps[idx],\n                                         &geomaps[idx]));\n    // check the consistency of the FastTree\n    fptrees[idx].checkConsistency(0);\n    // check the consistency of the FastTree\n    froatrees[idx].checkConsistency(0);\n\n    // fill the maxSimilarty maps\n    for (auto dningit = maxDningToDnerSimil[idx].begin();\n         dningit != maxDningToDnerSimil[idx].end(); dningit++) {\n      size_t maxSim = 0;\n      const SchedTreeBase::tFastTreeIdx* dningIdx;\n      assert(ftmaps[idx].get(dningit->first, dningIdx));\n\n      for (auto dnerit = drainerfs[idx].begin(); dnerit != drainerfs[idx].end();\n           dnerit++) {\n        const SchedTreeBase::tFastTreeIdx* dnerIdx;\n        assert(ftmaps[idx].get(*dnerit, dnerIdx));\n        size_t sim = treeDepthSimilarity(ftinfos[idx][*dnerIdx].fullGeotag,\n                                         ftinfos[idx][*dningIdx].fullGeotag);\n        maxSim = max(maxSim, sim);\n      }\n\n      maxDningToDnerSimil[idx][dningit->first] = maxSim;\n    }\n\n    for (auto bcingit = maxBcingToBcerSimil[idx].begin();\n         bcingit != maxBcingToBcerSimil[idx].end(); bcingit++) {\n      size_t maxSim = 0;\n      const SchedTreeBase::tFastTreeIdx* bcingIdx;\n      assert(ftmaps[idx].get(bcingit->first, bcingIdx));\n\n      for (auto bcerit = balancerfs[idx].begin(); bcerit != balancerfs[idx].end();\n           bcerit++) {\n        const SchedTreeBase::tFastTreeIdx* bcerIdx;\n        assert(ftmaps[idx].get(*bcerit, bcerIdx));\n        size_t sim = treeDepthSimilarity(ftinfos[idx][*bcerIdx].fullGeotag,\n                                         ftinfos[idx][*bcingIdx].fullGeotag);\n        maxSim = max(maxSim, sim);\n      }\n\n      maxBcingToBcerSimil[idx][bcingit->first] = maxSim;\n    }\n\n#if RUN_FUNCTIONAL_TEST==1\n    // functional testing\n    functionalTestFastTree(&fptrees[idx], &froatrees[idx], &geomaps[idx],\n                           &ftinfos[idx], nAvailableFsPlct);\n    functionalTestFastTree(&fptrees[idx], &frwatrees[idx], &geomaps[idx],\n                           &ftinfos[idx], nAvailableFsPlct);\n    functionalTestFastTree(&fdptrees[idx], &fdatrees[idx], &geomaps[idx],\n                           &ftinfos[idx], nAvailableFsDrnPlct);\n\n    // illustrate some display facility\n    if (idx == schedGroups.size() - 1) {\n      // display the SlowTree\n      cout << \"====== Illustrating the display of a SlowTree ======\" << std::endl;\n      cout << trees[idx] << endl;\n      cout << \"====================================================\" << std::endl <<\n           std::endl;\n      cout << \"====== Illustrating the color display of a SlowTree ======\" <<\n           std::endl;\n      unsigned geo_depth_max = 0;\n      TableFormatterBase table_tree;\n      table_tree.SetHeader({\n        std::make_tuple(\"group\", 6, \"s\"),\n        std::make_tuple(\"geotag\", 6, \"s\"),\n        std::make_tuple(\"fsid\", 4, \"l\"),\n        std::make_tuple(\"node\", 12, \"s\"),\n        std::make_tuple(\"leavs\", 5, \"l\"),\n        std::make_tuple(\"nodes\", 5, \"l\"),\n        std::make_tuple(\"status\", 6, \"s\")\n      });\n      std::set<std::tuple<std::string, unsigned, unsigned, TableFormatterColor,\n          unsigned, unsigned, std::string, std::string,\n          int, int, std::string>> data_tree;\n      trees[idx].display(data_tree, geo_depth_max, true);\n\n      for (auto it : data_tree) {\n        TableData table_data;\n        table_data.emplace_back();\n\n        if (std::get<2>(it) == 1) {\n          table_tree.AddSeparator();\n          table_data.back().push_back(TableCell(std::get<0>(it), \"s\", \"\", false,\n                                                std::get<3>(it)));\n          table_data.back().push_back(TableCell(\"\", \"s\"));\n          table_data.back().push_back(TableCell(\"\", \"s\"));\n          table_data.back().push_back(TableCell(\"\", \"s\"));\n        } else if (std::get<2>(it) == 2) {\n          table_data.back().push_back(TableCell(std::get<5>(it), \"t\"));\n          table_data.back().push_back(TableCell(std::get<7>(it), \"s\", \"\", false,\n                                                std::get<3>(it)));\n          table_data.back().push_back(TableCell(\"\", \"s\"));\n          table_data.back().push_back(TableCell(\"\", \"s\"));\n        } else if (std::get<2>(it) == 3) {\n          table_data.back().push_back(TableCell(std::get<4>(it), \"t\"));\n          table_data.back().push_back(TableCell(std::get<5>(it), \"t\"));\n          table_data.back().push_back(TableCell(std::get<6>(it), \"l\", \"\", false,\n                                                std::get<3>(it)));\n          table_data.back().push_back(TableCell(std::get<7>(it), \"s\", \"\", false,\n                                                std::get<3>(it)));\n        }\n\n        table_data.back().push_back(TableCell(std::get<8>(it), \"l\"));\n        table_data.back().push_back(TableCell(std::get<9>(it), \"l\"));\n        table_data.back().push_back(TableCell(std::get<10>(it), \"s\"));\n        table_tree.AddRows(table_data);\n      }\n\n      std::cout << table_tree.GenerateTable(HEADER).c_str() << endl;\n      cout << endl;\n      cout << \"====================================================\" << std::endl <<\n           std::endl;\n      // display the FastTree\n      cout << \"====== Illustrating the display of a Placement FastTree ======\" <<\n           std::endl;\n      cout << fptrees[idx] << endl;\n      cout << \"==============================================================\" <<\n           std::endl << std::endl;\n      cout << \"====== Illustrating the color display of a Placement FastTree ======\"\n           << std::endl;\n      geo_depth_max = 0;\n      TableFormatterBase table_snapshot;\n      table_snapshot.SetHeader({\n        std::make_tuple(\"group\", 6, \"s\"),\n        std::make_tuple(\"operation\", 6, \"s\"),\n        std::make_tuple(\"geotag\", 6, \"s\"),\n        std::make_tuple(\"fsid\", 4, \"l\"),\n        std::make_tuple(\"node\", 12, \"s\"),\n        std::make_tuple(\"free\", 4, \"l\"),\n        std::make_tuple(\"repl\", 4, \"l\"),\n        std::make_tuple(\"pidx\", 4, \"l\"),\n        std::make_tuple(\"status\", 6, \"s\"),\n        std::make_tuple(\"ulSc\", 4, \"l\"),\n        std::make_tuple(\"dlSc\", 4, \"l\"),\n        std::make_tuple(\"filR\", 4, \"l\"),\n        std::make_tuple(\"totS\", 4, \"+l\")\n      });\n      std::set<std::tuple<std::string, unsigned, unsigned, TableFormatterColor,\n\t\t\t  unsigned, unsigned, std::string, std::string, unsigned, std::string,\n\t\t\t  int, int, int, std::string, int, int, int, double, double>> data_snapshot;\n      fptrees[idx].recursiveDisplay(data_snapshot, geo_depth_max, \"test_operation\",\n                                    \"test_op\", true);\n\n      for (auto it : data_snapshot) {\n        TableData table_data;\n        table_data.emplace_back();\n\n        if (std::get<2>(it) == 1) { // depth=1\n          if (std::get<1>(it) == 0) {\n            table_snapshot.AddSeparator();\n            table_data.back().push_back(TableCell(std::get<0>(it), \"s\", \"\", false,\n                                                  std::get<3>(it)));\n            table_data.emplace_back();\n          }\n\n          table_data.back().push_back(TableCell(2, \"t\"));\n          table_data.back().push_back(TableCell(std::get<6>(it), \"s\", \"\", false,\n                                                std::get<3>(it)));\n          table_data.back().push_back(TableCell(\"\", \"s\"));\n          table_data.back().push_back(TableCell(\"\", \"s\"));\n          table_data.back().push_back(TableCell(\"\", \"s\"));\n        } else if (std::get<2>(it) == 2) { // depth=2\n          table_data.back().push_back(TableCell(0, \"t\"));\n          table_data.back().push_back(TableCell(std::get<5>(it), \"t\"));\n          table_data.back().push_back(TableCell(std::get<9>(it), \"s\", \"\", false,\n                                                std::get<3>(it)));\n          table_data.back().push_back(TableCell(\"\", \"s\"));\n          table_data.back().push_back(TableCell(\"\", \"s\"));\n        } else if (std::get<2>(it) == 3) { // depth=3\n          table_data.back().push_back(TableCell(0, \"t\"));\n          table_data.back().push_back(TableCell(std::get<4>(it), \"t\"));\n          table_data.back().push_back(TableCell(std::get<5>(it), \"t\"));\n          table_data.back().push_back(TableCell(std::get<8>(it), \"l\", \"\", false,\n                                                std::get<3>(it)));\n          table_data.back().push_back(TableCell(std::get<9>(it), \"s\", \"\", false,\n                                                std::get<3>(it)));\n        }\n\n        table_data.back().push_back(TableCell(std::get<10>(it), \"l\"));\n        table_data.back().push_back(TableCell(std::get<11>(it), \"l\"));\n        table_data.back().push_back(TableCell(std::get<12>(it), \"l\"));\n        table_data.back().push_back(TableCell(std::get<13>(it), \"s\"));\n        table_data.back().push_back(TableCell(std::get<14>(it), \"l\"));\n        table_data.back().push_back(TableCell(std::get<15>(it), \"l\"));\n        table_data.back().push_back(TableCell(std::get<16>(it), \"l\"));\n        table_data.back().push_back(TableCell(std::get<17>(it), \"+l\"));\n        table_snapshot.AddRows(table_data);\n      }\n\n      cout << table_snapshot.GenerateTable(HEADER).c_str();\n      cout << endl;\n      cout << \"==============================================================\" <<\n           std::endl << std::endl;\n      // display the FastTree\n      cout << \"====== Illustrating the display of an Access FastTree ======\" <<\n           std::endl;\n      cout << froatrees[idx] << endl;\n      cout << \"============================================================\" <<\n           std::endl << std::endl;\n      cout << \"====== Illustrating the color display of an Access FastTree ======\" <<\n           std::endl;\n      geo_depth_max = 0;\n      TableFormatterBase table_snapshot2;\n      table_snapshot2.SetHeader({\n        std::make_tuple(\"group\", 6, \"s\"),\n        std::make_tuple(\"operation\", 6, \"s\"),\n        std::make_tuple(\"geotag\", 6, \"s\"),\n        std::make_tuple(\"fsid\", 4, \"l\"),\n        std::make_tuple(\"node\", 12, \"s\"),\n        std::make_tuple(\"free\", 4, \"l\"),\n        std::make_tuple(\"repl\", 4, \"l\"),\n        std::make_tuple(\"pidx\", 4, \"l\"),\n        std::make_tuple(\"status\", 6, \"s\"),\n        std::make_tuple(\"ulSc\", 4, \"l\"),\n        std::make_tuple(\"dlSc\", 4, \"l\"),\n        std::make_tuple(\"filR\", 4, \"l\"),\n        std::make_tuple(\"totS\", 4, \"+l\")\n      });\n      data_snapshot.clear();\n      froatrees[idx].recursiveDisplay(data_snapshot, geo_depth_max, \"test_operation\",\n                                      \"test_op\", true);\n\n      for (auto it : data_snapshot) {\n        TableData table_data;\n        table_data.emplace_back();\n\n        if (std::get<2>(it) == 1) { // depth=1\n          if (std::get<1>(it) == 0) {\n            table_snapshot2.AddSeparator();\n            table_data.back().push_back(TableCell(std::get<0>(it), \"s\", \"\", false,\n                                                  std::get<3>(it)));\n            table_data.emplace_back();\n          }\n\n          table_data.back().push_back(TableCell(2, \"t\"));\n          table_data.back().push_back(TableCell(std::get<6>(it), \"s\", \"\", false,\n                                                std::get<3>(it)));\n          table_data.back().push_back(TableCell(\"\", \"s\"));\n          table_data.back().push_back(TableCell(\"\", \"s\"));\n          table_data.back().push_back(TableCell(\"\", \"s\"));\n        } else if (std::get<2>(it) == 2) { // depth=2\n          table_data.back().push_back(TableCell(0, \"t\"));\n          table_data.back().push_back(TableCell(std::get<5>(it), \"t\"));\n          table_data.back().push_back(TableCell(std::get<9>(it), \"s\", \"\", false,\n                                                std::get<3>(it)));\n          table_data.back().push_back(TableCell(\"\", \"s\"));\n          table_data.back().push_back(TableCell(\"\", \"s\"));\n        } else if (std::get<2>(it) == 3) { // depth=3\n          table_data.back().push_back(TableCell(0, \"t\"));\n          table_data.back().push_back(TableCell(std::get<4>(it), \"t\"));\n          table_data.back().push_back(TableCell(std::get<5>(it), \"t\"));\n          table_data.back().push_back(TableCell(std::get<8>(it), \"l\", \"\", false,\n                                                std::get<3>(it)));\n          table_data.back().push_back(TableCell(std::get<9>(it), \"s\", \"\", false,\n                                                std::get<3>(it)));\n        }\n\n        table_data.back().push_back(TableCell(std::get<10>(it), \"l\"));\n        table_data.back().push_back(TableCell(std::get<11>(it), \"l\"));\n        table_data.back().push_back(TableCell(std::get<12>(it), \"l\"));\n        table_data.back().push_back(TableCell(std::get<13>(it), \"s\"));\n        table_data.back().push_back(TableCell(std::get<14>(it), \"l\"));\n        table_data.back().push_back(TableCell(std::get<15>(it), \"l\"));\n        table_data.back().push_back(TableCell(std::get<16>(it), \"l\"));\n        table_data.back().push_back(TableCell(std::get<17>(it), \"+l\"));\n        table_snapshot2.AddRows(table_data);\n      }\n\n      cout << table_snapshot2.GenerateTable(HEADER).c_str();\n      cout << endl;\n      cout << \"============================================================\" <<\n           std::endl << std::endl;\n      // display the info array related to the fast tree\n      cout << \"====== Illustrating the display of a Tree Nodes Information Table ======\"\n           << std::endl;\n      cout << ftinfos[idx];\n      cout << \"=========================================================================\"\n           << std::endl << std::endl;\n      // display the mapping from fs to FastTree nodes\n      cout << \"====== Illustrating the display of a Fs2TreeIdxMap ======\" <<\n           std::endl;\n      cout << ftmaps[idx];\n      cout << \"=========================================================\" << std::endl\n           << std::endl;\n    }\n\n#endif // FUNCTIONAL_TEST\n    idx++;\n  }\n\n#if RUN_BURNIN_TEST==1\n  debugDisplay(fptrees[0]);\n  const size_t nbIter = 10000;\n  //const size_t nbIter = 100;\n  //const size_t nbIter = 1;\n  vector<SchedTreeBase::tFastTreeIdx> replicaIdxs(3 * schedGroups.size());\n  clock_t begin = clock();\n\n  for (size_t i = 0; i < schedGroups.size() * nbIter; i++) {\n    char buffer[bufferSize];\n    assert(fptrees[i % schedGroups.size()].copyToBuffer(buffer, bufferSize) == 0);\n    FastPlacementTree* ftree = (FastPlacementTree*) buffer;\n    SchedTreeBase::tFastTreeIdx repId;\n\n    for (int k = 0; k < 3; k++) {\n      //ftree->placeNewReplica(0,false,repId);\n      ftree->findFreeSlot(repId);\n      replicaIdxs[3 * (i % schedGroups.size()) + k] = repId;\n    }\n  }\n\n  clock_t elapsed = clock() - begin;\n  cout << \"REPLICA PLACEMENT SPEED TEST\" << endl;\n  cout << \"elapsed time : \" << float (elapsed) / CLOCKS_PER_SEC << \" sec.\" <<\n       endl;\n  cout << \"speed        : \" << 3 * schedGroups.size() * nbIter / (float (\n         elapsed) / CLOCKS_PER_SEC)\n       << \" placements/sec \" << endl;\n  cout << \"----------------------------\" << endl << endl;\n  begin = clock();\n\n  for (size_t i = 0; i < schedGroups.size() * nbIter; i++) {\n    char buffer[bufferSize];\n    assert(fptrees[i % schedGroups.size()].copyToBuffer(buffer, bufferSize) == 0);\n    char buffer2[bufferSize];\n    assert(froatrees[i % schedGroups.size()].copyToBuffer(buffer2,\n           bufferSize) == 0);\n  }\n\n  elapsed = clock() - begin;\n  cout << \"FAST TREE COPY ONLY SPEED TEST\" << endl;\n  cout << \"elapsed time : \" << float (elapsed) / CLOCKS_PER_SEC << \" sec.\" <<\n       endl;\n  cout << \"speed        : \" << 6 * schedGroups.size() * nbIter / (float (\n         elapsed) / CLOCKS_PER_SEC) << \" copies/sec \"\n       << endl;\n  cout << \"----------------------\" << endl << endl;\n  begin = clock();\n\n  for (size_t i = 0; i < schedGroups.size() * nbIter; i++) {\n    char buffer[bufferSize];\n    assert(fptrees[i % schedGroups.size()].copyToBuffer(buffer, bufferSize) == 0);\n    FastPlacementTree* ftree = (FastPlacementTree*) buffer;\n    char buffer2[bufferSize];\n    assert(froatrees[i % schedGroups.size()].copyToBuffer(buffer2,\n           bufferSize) == 0);\n    // FastROAccessTree* ftree2 = (FastROAccessTree*) buffer2;\n\n    // update the tree\n    for (int k = 0; k < 3; k++) {\n      ftree->decrementFreeSlot(replicaIdxs[3 * (i % schedGroups.size()) + k]);\n      // ftree2->incrementFreeSlot(replicaIdxs[3 * (i % schedGroups.size()) + k]);\n    }\n  }\n\n  elapsed = clock() - begin;\n  cout << \"TREE REPOPULATING SPEED TEST\" << endl;\n  cout << \"elapsed time : \" << float (elapsed) / CLOCKS_PER_SEC << \" sec.\" <<\n       endl;\n  cout << \"speed        : \" << 6 * schedGroups.size() * nbIter / (float (\n         elapsed) / CLOCKS_PER_SEC) << \" repop/sec \"\n       << endl;\n  cout << \"----------------------\" << endl << endl;\n  begin = clock();\n\n  for (size_t i = 0; i < schedGroups.size() * nbIter; i++) {\n    char buffer[bufferSize];\n    assert(froatrees[i % schedGroups.size()].copyToBuffer(buffer, bufferSize) == 0);\n    FastROAccessTree* ftree = (FastROAccessTree*) buffer;\n    // pick a random geolocation which makes sense in the tree\n    const string& clientGeoString =\n      ftinfos[i % schedGroups.size()][eos::common::getRandom() % ftinfos[i %\n                                      schedGroups.size()].size()].fullGeotag;\n    SchedTreeBase::tFastTreeIdx closestNode = geomaps[i %\n        schedGroups.size()].getClosestFastTreeNode(\n          clientGeoString.c_str());\n    // update the tree\n    // for (int k = 0; k < 3; k++) {\n    //   ftree->incrementFreeSlot(replicaIdxs[3 * (i % schedGroups.size()) + k]);\n    // }\n    // access the replicas\n    SchedTreeBase::tFastTreeIdx repId;\n    //ftree->findFreeSlot(closestNode,true,repId,false);\n    ftree->findFreeSlot(repId, closestNode, true, true);\n  }\n\n  elapsed = clock() - begin;\n  cout << \"FILE ACCESS 1 REP SPEED TEST\" << endl;\n  cout << \"elapsed time : \" << float (elapsed) / CLOCKS_PER_SEC << \" sec.\" <<\n       endl;\n  cout << \"speed        : \" << 3 * schedGroups.size() * nbIter / (float (\n         elapsed) / CLOCKS_PER_SEC) << \" access/sec \"\n       << endl;\n  cout << \"----------------------\" << endl << endl;\n  begin = clock();\n\n  for (size_t i = 0; i < schedGroups.size() * nbIter; i++) {\n    char buffer[bufferSize];\n    assert(froatrees[i % schedGroups.size()].copyToBuffer(buffer, bufferSize) == 0);\n    FastROAccessTree* ftree = (FastROAccessTree*) buffer;\n    // update the tree\n    // for (int k = 0; k < 3; k++) {\n    //   ftree->incrementFreeSlot(replicaIdxs[3 * (i % schedGroups.size()) + k]);\n    // }    // access the replicas\n    SchedTreeBase::tFastTreeIdx repIdxs[3];\n    ftree->findFreeSlotsAll(repIdxs, 3);\n  }\n\n  elapsed = clock() - begin;\n  cout << \"FILE ACCESS ALL REP SPEED TEST\" << endl;\n  cout << \"elapsed time : \" << float (elapsed) / CLOCKS_PER_SEC << \" sec.\" <<\n       endl;\n  cout << \"speed        : \" << 3 * schedGroups.size() * nbIter / (float (\n         elapsed) / CLOCKS_PER_SEC) << \" access/sec \"\n       << endl;\n  cout << \"----------------------\" << endl << endl;\n  vector<vector<SchedTreeBase::tFastTreeIdx> > drainers(schedGroups.size()),\n         balancers(schedGroups.size());\n  SchedTreeBase::tFastTreeIdx fsize;\n\n  for (size_t idx = 0; idx < schedGroups.size(); idx++) {\n    drainers[idx].resize(128);\n    fsize = fptrees[idx].findFreeSlotsAll(&drainers[idx][0], drainers[idx].size(),\n                                          0, false, SchedTreeBase::Drainer);\n    assert(fsize);\n    drainers[idx].resize(fsize);\n    balancers[idx].resize(128);\n    assert(fsize);\n    drainers[idx].resize(fsize);\n  }\n\n  // first get the idx range of the fs's\n  std::vector<SchedTreeBase::tFastTreeIdx> fsIdxBegV(schedGroups.size()),\n      fsIdxEndV(schedGroups.size());\n\n  for (size_t i = 0; i < schedGroups.size(); i++) {\n    fsIdxBegV[i] = 0;\n\n    while (ftinfos[i][fsIdxBegV[i]].nodeType ==\n           SchedTreeBase::TreeNodeInfo::intermediate) {\n      fsIdxBegV[i]++;\n    }\n\n    fsIdxEndV[i] = ftinfos[i].size();\n  }\n\n  begin = clock();\n\n  for (size_t i = 0; i < schedGroups.size() * nbIter; i++) {\n    char buffer[bufferSize];\n    assert(fptrees[i % schedGroups.size()].copyToBuffer(buffer, bufferSize) == 0);\n    FastPlacementTree* ftree = (FastPlacementTree*) buffer;\n    // select a random file system\n    SchedTreeBase::tFastTreeIdx rfs = fsIdxBegV[i % schedGroups.size()]\n      + eos::common::getRandom() % (fsIdxEndV[i % schedGroups.size()] -\n                                    fsIdxBegV[i % schedGroups.size()]);\n    ftree->updateBranch(rfs);\n  }\n\n  elapsed = clock() - begin;\n  cout << \"UPDATE FAST TREE TEST (ONE BRANCH) \" << endl;\n  cout << \"elapsed time : \" << float (elapsed) / CLOCKS_PER_SEC << \" sec.\" <<\n       endl;\n  cout << \"speed        : \" << schedGroups.size() * nbIter / (float (\n         elapsed) / CLOCKS_PER_SEC) << \" updates/sec \"\n       << endl;\n  cout << \"----------------------\" << endl << endl;\n  begin = clock();\n\n  for (size_t i = 0; i < schedGroups.size() * nbIter; i++) {\n    char buffer[bufferSize];\n    assert(fptrees[i % schedGroups.size()].copyToBuffer(buffer, bufferSize) == 0);\n    FastPlacementTree* ftree = (FastPlacementTree*) buffer;\n    ftree->updateTree();\n  }\n\n  elapsed = clock() - begin;\n  cout << \"UPDATE FAST TREE TEST (FULL TREE) \" << endl;\n  cout << \"elapsed time : \" << float (elapsed) / CLOCKS_PER_SEC << \" sec.\" <<\n       endl;\n  cout << \"speed        : \" << schedGroups.size() * nbIter / (float (\n         elapsed) / CLOCKS_PER_SEC) << \" updates/sec \"\n       << endl;\n  cout << \"----------------------\" << endl << endl;\n  begin = clock();\n\n  for (size_t i = 0; i < schedGroups.size() * nbIter; i++) {\n    int j = i % schedGroups.size();\n    assert(\n      trees[j].buildFastStrcturesSched(&fptrees[j], &froatrees[j], &frwatrees[j],\n                                       &fdptrees[j], &fdatrees[j], &ftinfos[j], &ftmaps[j], &geomaps[j]));\n  }\n\n  elapsed = clock() - begin;\n  cout << \"FAST STRUCTURES BUILDING TEST\" << endl;\n  cout << \"elapsed time : \" << float (elapsed) / CLOCKS_PER_SEC << \" sec.\" <<\n       endl;\n  cout << \"speed        : \" << schedGroups.size() * nbIter / (float (\n         elapsed) / CLOCKS_PER_SEC) << \" builds/sec \"\n       << endl;\n  cout << \"----------------------\" << endl << endl;\n#endif // BURNIN_TEST\n  return 0;\n}\n"
  },
  {
    "path": "mgm/geotree/SchedulingTreeTest.cc.testfile",
    "content": "lxfsrb37a06.cern.ch: 0513::R::0050::RB37 \nlxfsrd08c03.cern.ch: 0513::R::0050::RD08 \np05153065015137.cern.ch: 9918::R::0002::WG04 \np05153065037137.cern.ch: 9918::R::0002::WG08 \np05153065040488.cern.ch: 9918::R::0002::WG02 \np05153065050439.cern.ch: 9918::R::0002::WG05 \np05153065089325.cern.ch: 9918::R::0002::WG08 \np05153065091726.cern.ch: 9918::R::0002::WG05 \np05153065128812.cern.ch: 9918::R::0002::WG10 \np05153065133556.cern.ch: 9918::R::0002::WG03 \np05153065175272.cern.ch: 9918::R::0002::WG07 \np05153065294908.cern.ch: 9918::R::0002::WG11 \np05153065311450.cern.ch: 9918::R::0002::WG02 \np05153065328060.cern.ch: 9918::R::0002::WG04 \np05153065372776.cern.ch: 9918::R::0002::WG04 \np05153065396192.cern.ch: 9918::R::0002::WG07 \np05153065429249.cern.ch: 9918::R::0002::WG10 \np05153065436252.cern.ch: 9918::R::0002::WG03 \np05153065610299.cern.ch: 9918::R::0002::WG08 \np05153065674867.cern.ch: 9918::R::0002::WG06 \np05153065682137.cern.ch: 9918::R::0002::WG05 \np05153065765524.cern.ch: 9918::R::0002::WG09 \np05153065219148.cern.ch: 9918::R::0002::WG11 \np05153065234041.cern.ch: 9918::R::0002::WG02 \np05153065617900.cern.ch: 9918::R::0002::WG07 \np05153065682644.cern.ch: 9918::R::0002::WG10 \np05153065714367.cern.ch: 9918::R::0002::WG11 \np05153065774585.cern.ch: 9918::R::0002::WG08 \np05153065836284.cern.ch: 9918::R::0002::WG03 \np05153065846664.cern.ch: 9918::R::0002::WG11 \np05153065906885.cern.ch: 9918::R::0002::WG09 \np05153065919664.cern.ch: 9918::R::0002::WG03 \np05153065944933.cern.ch: 9918::R::0002::WG04 \np05153065961941.cern.ch: 9918::R::0002::WG06 \np05153065984668.cern.ch: 9918::R::0002::WG10 \np05153074033994.cern.ch: 0513::R::0050::RD15 \np05153074037184.cern.ch: 0513::R::0050::RD25 \np05153074050670.cern.ch: 0513::R::0050::RD27 \np05153074053268.cern.ch: 0513::R::0050::RD03 \np05153074058585.cern.ch: 0513::R::0050::RD11 \np05153074063755.cern.ch: 0513::R::0050::RD11 \np05153074080480.cern.ch: 0513::R::0050::RD07 \np05153074100648.cern.ch: 0513::R::0050::RD15 \np05153074113009.cern.ch: 0513::R::0050::RD05 \np05153074113931.cern.ch: 0513::R::0050::RD23 \np05153074118254.cern.ch: 0513::R::0050::RD05 \np05153074118530.cern.ch: 0513::R::0050::RC12 \np05153074135102.cern.ch: 0513::R::0050::RD19 \np05153074145156.cern.ch: 0513::R::0050::RC18 \np05153074145270.cern.ch: 0513::R::0050::RE55 \np05153074187809.cern.ch: 0513::R::0050::RD19 \np05153074188649.cern.ch: 0513::R::0050::RD23 \np05153074189871.cern.ch: 0513::R::0050::RD03 \np05153074211655.cern.ch: 0513::R::0050::RD19 \np05153074221193.cern.ch: 0513::R::0050::RD13 \np05153074223928.cern.ch: 0513::R::0050::RC24 \np05153074224762.cern.ch: 0513::R::0050::RD21 \np05153074225206.cern.ch: 0513::R::0050::RD17 \np05153074235015.cern.ch: 0513::R::0050::RC30 \np05153074245265.cern.ch: 0513::R::0050::RC18 \np05153074251924.cern.ch: 0513::R::0050::RC02 \np05153074251999.cern.ch: 0513::R::0050::RC26 \np05153074065039.cern.ch: 0513::R::0050::RD09 \np05153074151399.cern.ch: 0513::R::0050::RD29 \np05153074238729.cern.ch: 0513::R::0050::RC10 \np05153074244730.cern.ch: 0513::R::0050::RD05 \np05153074252950.cern.ch: 0513::R::0050::RC04 \np05153074260650.cern.ch: 0513::R::0050::RC04 \np05153074266370.cern.ch: 0513::R::0050::RC30 \np05153074273860.cern.ch: 0513::R::0050::RD21 \np05153074280340.cern.ch: 0513::R::0050::RC10 \np05153074285630.cern.ch: 0513::R::0050::RC08 \np05153074289951.cern.ch: 0513::R::0050::RD09 \np05153074292798.cern.ch: 0513::R::0050::RC24 \np05153074352591.cern.ch: 0513::R::0050::RC14 \np05153074358258.cern.ch: 0513::R::0050::RD27 \np05153074362041.cern.ch: 0513::R::0050::RC22 \np05153074365419.cern.ch: 0513::R::0050::RC28 \np05153074366867.cern.ch: 0513::R::0050::RC08 \np05153074400713.cern.ch: 0513::R::0050::RD29 \np05153074406603.cern.ch: 0513::R::0050::RC20 \np05153074409311.cern.ch: 0513::R::0050::RC22 \np05153074416964.cern.ch: 0513::R::0050::RC14 \np05153074426786.cern.ch: 0513::R::0050::RD09 \np05153074437333.cern.ch: 0513::R::0050::RD11 \np05153074441529.cern.ch: 0513::R::0050::RC20 \np05153074470683.cern.ch: 0513::R::0050::RD27 \np05153074500849.cern.ch: 0513::R::0050::RC12 \np05153074506131.cern.ch: 0513::R::0050::RC22 \np05153074515710.cern.ch: 0513::R::0050::RD23 \np05153074552980.cern.ch: 0513::R::0050::RD03 \np05153074597209.cern.ch: 0513::R::0050::RE53 \np05153074600927.cern.ch: 0513::R::0050::RD05 \np05153074603204.cern.ch: 0513::R::0050::RD13 \np05153074609357.cern.ch: 0513::R::0050::RD07 \np05153074625071.cern.ch: 0513::R::0050::RC24 \np05153074630765.cern.ch: 0513::R::0050::RD29 \np05153074636644.cern.ch: 0513::R::0050::RC26 \np05153074653253.cern.ch: 0513::R::0050::RD17 \np05153074674395.cern.ch: 0513::R::0050::RD29 \np05153074682740.cern.ch: 0513::R::0050::RC26 \np05153074513422.cern.ch: 0513::R::0050::RC30 \np05153074513747.cern.ch: 0513::R::0050::RC06 \np05153074527793.cern.ch: 0513::R::0050::RD07 \np05153074536441.cern.ch: 0513::R::0050::RD25 \np05153074595293.cern.ch: 0513::R::0050::RC24 \np05153074609945.cern.ch: 0513::R::0050::RD15 \np05153074617805.cern.ch: 0513::R::0050::RE53 \np05153074623141.cern.ch: 0513::R::0050::RD15 \np05153074654192.cern.ch: 0513::R::0050::RC26 \np05153074659772.cern.ch: 0513::R::0050::RC20 \np05153074695055.cern.ch: 0513::R::0050::RC18 \np05153074695926.cern.ch: 0513::R::0050::RC08 \np05153074711221.cern.ch: 0513::R::0050::RC28 \np05153074731158.cern.ch: 0513::R::0050::RC06 \np05153074732457.cern.ch: 0513::R::0050::RC06 \np05153074732976.cern.ch: 0513::R::0050::RD13 \np05153074735571.cern.ch: 0513::R::0050::RC10 \np05153074743366.cern.ch: 0513::R::0050::RE55 \np05153074755681.cern.ch: 0513::R::0050::RC14 \np05153074758975.cern.ch: 0513::R::0050::RE53 \np05153074767600.cern.ch: 0513::R::0050::RC08 \np05153074768463.cern.ch: 0513::R::0050::RC14 \np05153074789266.cern.ch: 0513::R::0050::RC04 \np05153074813151.cern.ch: 0513::R::0050::RD17 \np05153074852513.cern.ch: 0513::R::0050::RD25 \np05153074865496.cern.ch: 0513::R::0050::RE55 \np05153074903829.cern.ch: 0513::R::0050::RC16 \np05153074907996.cern.ch: 0513::R::0050::RC22 \np05153074925396.cern.ch: 0513::R::0050::RD11 \np05153074805433.cern.ch: 0513::R::0050::RD07 \np05153074872029.cern.ch: 0513::R::0050::RE53 \np05153074874554.cern.ch: 0513::R::0050::RC04 \np05153074885249.cern.ch: 0513::R::0050::RD17 \np05153074931375.cern.ch: 0513::R::0050::RC06 \np05153074933479.cern.ch: 0513::R::0050::RD25 \np05153074937173.cern.ch: 0513::R::0050::RC02 \np05153074949355.cern.ch: 0513::R::0050::RD13 \np05153074955062.cern.ch: 0513::R::0050::RE55 \np05153074955852.cern.ch: 0513::R::0050::RC20 \np05153074971531.cern.ch: 0513::R::0050::RD19 \np05153074983441.cern.ch: 0513::R::0050::RC02 \np05153074992830.cern.ch: 0513::R::0050::RD27 \n"
  },
  {
    "path": "mgm/geotreeengine/GeoTreeEngine.cc",
    "content": "// ----------------------------------------------------------------------\n// File: GeoTreeEngine.cc\n// Author: Geoffray Adde - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#define HAVE_ATOMICS 1\n\n#include \"mgm/geotreeengine/GeoTreeEngine.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/config/IConfigEngine.hh\"\n#include \"common/table_formatter/TableFormatterBase.hh\"\n#include \"common/FileSystem.hh\"\n#include \"common/IntervalStopwatch.hh\"\n#include \"common/Assert.hh\"\n#include <iostream>\n#include <fstream>\n#include <sstream>\n#include <cstdio>\n#include <ctime>\n#include <sys/stat.h>\n#include <tuple>\n#include <algorithm>\n\nusing namespace std;\nusing namespace eos::common;\nusing namespace eos::mgm;\n\nEOSMGMNAMESPACE_BEGIN\n\n// We assume that all the trees have the same max size, we should take the max\n// of all the sizes otherwise\nconst size_t GeoTreeEngine::gGeoBufferSize = sizeof(FastPlacementTree) +\n    FastPlacementTree::sGetMaxDataMemSize();\nthread_local void* GeoTreeEngine::tlGeoBuffer = NULL;\npthread_key_t GeoTreeEngine::gPthreadKey;\n\nconst int GeoTreeEngine::sfgId = 1;\nconst int GeoTreeEngine::sfgHost = 1 << 1;\nconst int GeoTreeEngine::sfgGeotag = 1 << 2;\nconst int GeoTreeEngine::sfgBoot = 1 << 3;\nconst int GeoTreeEngine::sfgActive = 1 << 4;\nconst int GeoTreeEngine::sfgConfigstatus = 1 << 5;\nconst int GeoTreeEngine::sfgDrain = 1 << 6;\nconst int GeoTreeEngine::sfgDrainer = 1 << 6;\nconst int GeoTreeEngine::sfgBlkavailb = 1 << 8;\nconst int GeoTreeEngine::sfgFsfilled = 1 << 9;\nconst int GeoTreeEngine::sfgNomfilled = 1 << 10;\nconst int GeoTreeEngine::sfgReadratemb = 1 << 12;\nconst int GeoTreeEngine::sfgDiskload = 1 << 13;\nconst int GeoTreeEngine::sfgEthmib = 1 << 14;\nconst int GeoTreeEngine::sfgInratemib = 1 << 15;\nconst int GeoTreeEngine::sfgOutratemib = 1 << 16;\nconst int GeoTreeEngine::sfgErrc = 1 << 17;\nconst int GeoTreeEngine::sfgPubTmStmp = 1 << 18;\nconst int GeoTreeEngine::sfgWopen = 1 << 19;\nconst int GeoTreeEngine::sfgRopen = 1 << 20;\n\nset<string> GeoTreeEngine::gWatchedKeys;\n\nconst map<string, int> GeoTreeEngine::gNotifKey2EnumSched = {\n  make_pair(\"id\", sfgId),\n  make_pair(\"host\", sfgHost),\n  make_pair(\"forcegeotag\", sfgGeotag),\n  make_pair(\"stat.geotag\", sfgGeotag),\n  make_pair(\"stat.boot\", sfgBoot),\n  make_pair(\"stat.active\", sfgActive),\n  make_pair(\"configstatus\", sfgConfigstatus),\n  make_pair(\"local.drain\", sfgDrain),\n  make_pair(\"local.drainer\", sfgDrainer),\n  make_pair(\"stat.nominal.filled\", sfgNomfilled),\n  make_pair(\"stat.statfs.bavail\", sfgBlkavailb),\n  make_pair(\"stat.statfs.filled\", sfgFsfilled),\n  make_pair(\"stat.disk.readratemb\", sfgReadratemb),\n  make_pair(\"stat.disk.load\", sfgDiskload),\n  make_pair(\"stat.net.ethratemib\", sfgEthmib),\n  make_pair(\"stat.net.inratemib\", sfgInratemib),\n  make_pair(\"stat.net.outratemib\", sfgOutratemib),\n  make_pair(\"stat.errc\", sfgErrc),\n  make_pair(\"stat.publishtimestamp\", sfgPubTmStmp),\n  make_pair(\"stat.wopen\", sfgWopen),\n  make_pair(\"stat.ropen\", sfgRopen),\n};\n\nmap<string, int> GeoTreeEngine::gNotificationsBufferFs;\nmap<string, int> GeoTreeEngine::gNotificationsBufferProxy;\nsem_t GeoTreeEngine::gUpdaterPauseSem;\nbool GeoTreeEngine::gUpdaterPaused = false;\nbool GeoTreeEngine::gUpdaterStarted = false;\nconst unsigned char GeoTreeEngine::sntFilesystem = 1,\n                                   GeoTreeEngine::sntDataproxy = 4;\nstd::map<std::string, unsigned char> GeoTreeEngine::gQueue2NotifType;\nstatic constexpr auto FS_LISTENER_THREAD_NAME = \"GeoTreeFsListener\";\n\n//------------------------------------------------------------------------------\n// Get the maximum number of placement attempts\n//------------------------------------------------------------------------------\nunsigned int GetMaxPlacementAttempts()\n{\n  unsigned int attempt = 1u;\n  const std::string env_name = \"EOS_SCATTERED_PLACEMENT_MAX_ATTEMPTS\";\n  const char* ptr = getenv(env_name.c_str());\n\n  if (ptr) {\n    try {\n      attempt = std::stoi(std::string(ptr));\n    } catch (...) {\n    }\n  }\n\n  return attempt;\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nGeoTreeEngine::GeoTreeEngine(mq::MessagingRealm* realm) :\n  pSkipSaturatedAccess(true), pSkipSaturatedDrnAccess(true),\n  pSkipSaturatedBlcAccess(true), pProxyCloseToFs(true),\n  pPenaltyUpdateRate(1),\n  pFillRatioLimit(80), pFillRatioCompTol(100), pSaturationThres(10),\n  pTimeFrameDurationMs(1000), pPublishToPenaltyDelayMs(1000),\n  pAccessGeotagMapping(\"accessgeotagmapping\"),\n  pAccessProxygroup(\"accessproxygroup\"),\n  pCircSize(30), pFrameCount(0),\n  pPenaltySched(pCircSize),\n  pLatencySched(pCircSize),\n  mFsListener(nullptr)\n{\n  mFsListener = realm->GetFsChangeListener(\"geotree-fs-listener\");\n  // by default, disable all the placement operations for non geotagged fs\n  addDisabledBranch(\"*\", \"plct\", \"nogeotag\", NULL, false);\n  addDisabledBranch(\"*\", \"accsdrain\", \"nogeotag\", NULL, false);\n  // set blocking mutexes for lower latencies\n  pAddRmFsMutex.SetBlocking(true);\n  configMutex.SetBlocking(true);\n  pTreeMapMutex.SetBlocking(true);\n\n  for (auto it = pPenaltySched.pCircFrCnt2FsPenalties.begin();\n       it != pPenaltySched.pCircFrCnt2FsPenalties.end(); it++) {\n    it->reserve(100);\n  }\n\n  // create the thread local key to handle allocation/destruction of thread local geobuffers\n  pthread_key_create(&gPthreadKey, GeoTreeEngine::tlFree);\n\n  // initialize pauser semaphore\n  if (sem_init(&gUpdaterPauseSem, 0, 1)) {\n    throw \"sem_init() failed\";\n  }\n}\n\nbool GeoTreeEngine::forceRefreshSched()\n{\n  // prevent any other use of the fast structures\n  pAddRmFsMutex.LockWrite();\n  pTreeMapMutex.LockWrite();\n\n  // mark all fs needing a refresh for all the watched attributes\n  // => SCHED\n  for (auto it = pFsId2FsPtr.begin(); it != pFsId2FsPtr.end(); it++) {\n    if (it->second) {\n      gNotificationsBufferFs[it->second->GetQueuePath()] = (~0);\n    }\n  }\n\n  for (auto it = pGroup2SchedTME.begin(); it != pGroup2SchedTME.end(); it++) {\n    it->second->fastStructModified = true;\n    it->second->slowTreeModified = true;\n  }\n\n  // mark all proxy needing a refresh for all the watched attributes\n  // => PROXYGROUPS\n  for (auto it = pPxyQueue2PxyId.begin(); it != pPxyQueue2PxyId.end(); it++) {\n    gNotificationsBufferProxy[it->first] = (~0);\n  }\n\n  for (auto it = pPxyGrp2DpTME.begin(); it != pPxyGrp2DpTME.end(); it++) {\n    it->second->fastStructModified = true;\n    it->second->slowTreeModified = true;\n  }\n\n  // do the update\n  pTreeMapMutex.UnLockWrite();\n  updateTreeInfo(gNotificationsBufferFs, gNotificationsBufferProxy);\n  pAddRmFsMutex.UnLockWrite();\n  return true;\n}\n\nbool GeoTreeEngine::forceRefresh()\n{\n  // signal a pause to the background updating\n  PauseUpdater();\n  // do the refreshes\n  bool result = forceRefreshSched();\n  // signal a resume to the background updating\n  ResumeUpdater();\n  return result;\n}\n\nbool GeoTreeEngine::insertFsIntoGroup(FileSystem* fs,\n                                      FsGroup* group,\n                                      const common::FileSystemCoreParams& coreParams)\n{\n  bool updateFastStruct = false;\n  eos::common::RWMutexWriteLock lock(pAddRmFsMutex);\n  FileSystem::fsid_t fsid = coreParams.getId();\n  SchedTME* mapEntry = 0;\n  bool is_new_entry = false;\n  {\n    pTreeMapMutex.LockWrite();\n\n    // ==== check that fs is not already registered\n    if (pFs2SchedTME.count(fsid)) {\n      eos_err(\"error inserting fs %lu into group %s : fs is already part of a group\",\n              (unsigned long)fsid, group->mName.c_str());\n      pTreeMapMutex.UnLockWrite();\n      return false;\n    }\n\n    // ==== get the entry\n    if (pGroup2SchedTME.count(group)) {\n      mapEntry = pGroup2SchedTME[group];\n    } else {\n      mapEntry = new SchedTME(group->mName.c_str());\n      is_new_entry = true;\n      // Force update to be sure that the fast structures are properly created\n      updateFastStruct = true;\n    }\n\n    mapEntry->slowTreeMutex.LockWrite();\n    pTreeMapMutex.UnLockWrite();\n  }\n  // ==== fill the entry\n  // create new TreeNodeInfo/TreeNodeState pair and update its data\n  eos::common::FileSystem::fs_snapshot_t fsn;\n  fs->SnapShotFileSystem(fsn, true);\n  fsn.fillFromCoreParams(coreParams);\n  // check if there is still some space for a new fs\n  {\n    size_t depth = 1;\n    std::string sub(\"::\");\n\n    for (size_t offset = fsn.mGeoTag.find(sub); offset != std::string::npos;\n         offset = fsn.mGeoTag.find(sub, offset + sub.length())) {\n      depth++;\n    }\n\n    if (depth + mapEntry->slowTree->getNodeCount() >\n        SchedTreeBase::sGetMaxNodeCount() - 2) {\n      mapEntry->slowTreeMutex.UnLockWrite();\n      eos_err(\"error inserting fs %lu into group %s : the group-tree is full\",\n              (unsigned long)fsid, group->mName.c_str());\n\n      if (is_new_entry) {\n        delete mapEntry;\n      }\n\n      return false;\n    }\n  }\n  SchedTreeBase::TreeNodeInfo info;\n  info.geotag = fsn.mGeoTag;\n\n  if (info.geotag.empty()) {\n    char buffer[64];\n    snprintf(buffer, 64, \"nogeotag\");\n    info.geotag = buffer;\n  }\n\n  info.host = coreParams.getHost();\n  info.hostport = coreParams.getHostPort();\n\n  if (info.host.empty()) {\n    uuid_t uuid;\n    char buffer[64];\n    snprintf(buffer, 64, \"nohost-\");\n    uuid_generate_time(uuid);\n    uuid_unparse(uuid, buffer + 7);\n    info.host = buffer;\n  }\n\n  info.netSpeedClass = 1; // EthRateMiB not yet initialized at this point,\n  // use placeholder value\n  info.fsId = coreParams.getId();\n\n  if (!info.fsId) {\n    mapEntry->slowTreeMutex.UnLockWrite();\n    eos_err(\"error inserting fs %lu into group %s : FsId is not set!\",\n            (unsigned long)fsid, group->mName.c_str());\n\n    if (is_new_entry) {\n      delete mapEntry;\n    }\n\n    return false;\n  }\n\n  SchedTreeBase::TreeNodeStateFloat state;\n  // try to insert the new node in the Slowtree\n  SlowTreeNode* node = mapEntry->slowTree->insert(&info, &state);\n\n  if (node == NULL) {\n    mapEntry->slowTreeMutex.UnLockWrite();\n    eos_err(\"error inserting fs %lu into group %s : slow tree node insertion failed\",\n            (unsigned long)fsid, group->mName.c_str());\n\n    if (is_new_entry) {\n      delete mapEntry;\n    }\n\n    return false;\n  }\n\n  // ==== update the penalties vectors if necessary\n  if ((coreParams.getId() + 1) > pLatencySched.pFsId2LatencyStats.size()) {\n    for (auto it = pPenaltySched.pCircFrCnt2FsPenalties.begin();\n         it != pPenaltySched.pCircFrCnt2FsPenalties.end(); it++) {\n      it->resize(coreParams.getId() + 1);\n    }\n\n    pLatencySched.pFsId2LatencyStats.resize(coreParams.getId() + 1);\n  }\n\n  // ==== update the shared object notifications\n  {\n    if (gWatchedKeys.empty()) {\n      for (auto it = gNotifKey2EnumSched.begin(); it != gNotifKey2EnumSched.end();\n           it++) {\n        gWatchedKeys.insert(it->first);\n      }\n    }\n\n    gQueue2NotifType[fs->GetQueuePath()] |= sntFilesystem;\n\n    if (!fs->AttachFsListener(mFsListener, gWatchedKeys)) {\n      eos_crit(\"error inserting fs %lu into group %s : error subscribing to \"\n               \"shared object notifications\", (unsigned long)fsid,\n               group->mName.c_str());\n      gQueue2NotifType[fs->GetQueuePath()] &= ~sntFilesystem;\n\n      if (gQueue2NotifType[fs->GetQueuePath()] == 0) {\n        gQueue2NotifType.erase(fs->GetQueuePath());\n      }\n\n      mapEntry->slowTreeMutex.UnLockWrite();\n\n      if (is_new_entry) {\n        delete mapEntry;\n      }\n\n      return false;\n    }\n  }\n\n  // update all the information about this new node\n  if (!updateTreeInfo(mapEntry, &fsn, ~sfgGeotag & ~sfgId & ~sfgHost, 0, node)) {\n    mapEntry->slowTreeMutex.UnLockWrite();\n    pTreeMapMutex.LockRead();\n    eos_err(\"error inserting fs %lu into group %s : slow tree node update failed\",\n            (unsigned long)fsid, group->mName.c_str());\n    pTreeMapMutex.UnLockRead();\n\n    if (is_new_entry) {\n      delete mapEntry;\n    }\n\n    return false;\n  }\n\n  mapEntry->fs2SlowTreeNode[fsid] = node;\n  mapEntry->slowTreeModified = true;\n  mapEntry->group = group;\n\n  // update the fast structures now if requested\n  if (updateFastStruct) {\n    if (!updateFastStructures(mapEntry)) {\n      mapEntry->slowTreeMutex.UnLockWrite();\n      pTreeMapMutex.LockRead();\n      eos_err(\"error inserting fs %lu into group %s : fast structures update failed\",\n              fsid, group->mName.c_str(), pFs2SchedTME[fsid]->group->mName.c_str());\n      pTreeMapMutex.UnLockRead();\n\n      if (is_new_entry) {\n        delete mapEntry;\n      }\n\n      return false;\n    } else {\n      mapEntry->slowTreeModified = false;\n    }\n  }\n\n  // ==== update the entry in the map\n  {\n    mapEntry->slowTreeMutex.UnLockWrite();\n    pTreeMapMutex.LockWrite();\n    pGroup2SchedTME[group] = mapEntry;\n    pFs2SchedTME[fsid] = mapEntry;\n    pFsId2FsPtr[fsid] = fs;\n    pTreeMapMutex.UnLockWrite();\n  }\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n\n  if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n    stringstream ss;\n    ss << (*mapEntry->slowTree);\n    eos_debug(\"inserted fs %lu into group %s geotag is %s and fullgeotag is %s\\n%s\",\n              (unsigned long)fsid, group->mName.c_str(),\n              node->pNodeInfo.geotag.c_str(), node->pNodeInfo.fullGeotag.c_str(),\n              ss.str().c_str());\n  }\n\n  return true;\n}\n\n\nbool GeoTreeEngine::removeFsFromGroup(FileSystem* fs, FsGroup* group,\n                                      bool updateFastStruct)\n{\n  eos::common::RWMutexWriteLock lock(pAddRmFsMutex);\n  SchedTME* mapEntry;\n  FileSystem::fsid_t fsid = fs->GetId();\n  {\n    pTreeMapMutex.LockWrite();\n\n    // ==== check that fs is registered\n    if (!pFs2SchedTME.count(fsid)) {\n      eos_err(\"error removing fs %lu from group %s : fs is not registered\",\n              (unsigned long)fsid, group->mName.c_str());\n      pTreeMapMutex.UnLockWrite();\n      return false;\n    }\n\n    // ==== get the entry\n    if (!pGroup2SchedTME.count(group)) {\n      eos_err(\"error removing fs %lu from group %s : fs is not registered \",\n              (unsigned long)fsid, group->mName.c_str());\n      pTreeMapMutex.UnLockWrite();\n      return false;\n    }\n\n    mapEntry = pGroup2SchedTME[group];\n    mapEntry->slowTreeMutex.LockWrite();\n    pTreeMapMutex.UnLockWrite();\n  }\n  // ==== update the shared object notifications\n  {\n    if (!fs->DetachFsListener(mFsListener, gWatchedKeys)) {\n      mapEntry->slowTreeMutex.UnLockWrite();\n      eos_crit(\"error removing fs %lu into group %s : error unsubscribing to \"\n               \"shared object notifications\", (unsigned long)fsid,\n               group->mName.c_str());\n      return false;\n    }\n\n    gQueue2NotifType[fs->GetQueuePath()] &= ~sntFilesystem;\n\n    if (gQueue2NotifType[fs->GetQueuePath()] == 0) {\n      gQueue2NotifType.erase(fs->GetQueuePath());\n    }\n  }\n  // ==== discard updates about this fs\n  // ==== clean the notifications buffer\n  gNotificationsBufferFs.erase(fs->GetQueuePath());\n  // ==== update the entry\n  const SlowTreeNode* intree = mapEntry->fs2SlowTreeNode[fsid];\n\n  // Double check that the SlowTreeNode exists EOS-4678\n  if (intree) {\n    SchedTreeBase::TreeNodeInfo info = intree->pNodeInfo;\n    info.geotag = intree->pNodeInfo.fullGeotag;\n    eos_debug(\"msg=\\\"remove from SlowNodeTree\\\" fsid=%lu host=\\\"%s\\\" \"\n              \"geotag=\\\"%s\\\" fullgeotag=\\\"%s\\\"\",\n              (unsigned long)intree->pNodeInfo.fsId,\n              intree->pNodeInfo.host.c_str(),\n              intree->pNodeInfo.geotag.c_str(),\n              intree->pNodeInfo.fullGeotag.c_str());\n    // try to update the SlowTree\n    info.fsId = 0;\n\n    if (!mapEntry->slowTree->remove(&info)) {\n      mapEntry->slowTreeMutex.UnLockWrite();\n      eos_err(\"error removing fs %lu from group %s : removing the slow tree node \"\n              \"failed. geotag is %s and geotag in tree is %s and %s\",\n              (unsigned long)fsid, group->mName.c_str(), info.geotag.c_str(),\n              intree->pNodeInfo.fullGeotag.c_str(), intree->pNodeInfo.geotag.c_str());\n      return false;\n    }\n\n    mapEntry->fs2SlowTreeNode.erase(fsid);\n  }\n\n  const bool treeEmpty = mapEntry->fs2SlowTreeNode.empty();\n\n  // if the tree is empty, remove the entry from the map\n  if (!treeEmpty) { // if the tree is getting empty, no need to update it\n    mapEntry->slowTreeModified = true;\n  }\n\n  if (updateFastStruct && mapEntry->slowTreeModified)\n    if (!updateFastStructures(mapEntry)) {\n      mapEntry->slowTreeMutex.UnLockWrite();\n      pTreeMapMutex.LockRead();\n      eos_err(\"error removing fs %lu from group %s : fast structures update failed\",\n              fsid, group->mName.c_str(), pFs2SchedTME[fsid]->group->mName.c_str());\n      pTreeMapMutex.UnLockRead();\n      return false;\n    }\n\n  // ==== update the entry in the map if needed\n  {\n    mapEntry->slowTreeMutex.UnLockWrite();\n    pTreeMapMutex.LockWrite();\n    pFs2SchedTME.erase(fsid);\n    pFsId2FsPtr.erase(fsid);\n\n    if (treeEmpty) {\n      pGroup2SchedTME.erase(group); // prevent from access by other threads\n      pPendingDeletionsFs.push_back(mapEntry);\n    }\n\n    pTreeMapMutex.UnLockWrite();\n  }\n  return true;\n}\n\n\nuint64_t GeoTreeEngine::placementSpace(const std::string& space,\n                                       const std::string& schedgroup)\n{\n  RWMutexReadLock lock(pTreeMapMutex);\n  uint64_t totalWritableSpace = 0;\n\n  for (auto it = pGroup2SchedTME.begin(); it != pGroup2SchedTME.end(); it++) {\n    std::string ispace;\n    std::string index;\n    eos::common::StringConversion::SplitKeyValue(it->second->group->mName, ispace,\n        index, \".\");\n\n    if ((ispace == space) && ((schedgroup == \"\") ||\n                              (schedgroup == it->second->group->mName))) {\n      totalWritableSpace +=\n        it->second->foregroundFastStruct->placementTree->getTotalWritableSpace();\n    }\n  }\n\n  return totalWritableSpace;\n}\n\n\nvoid GeoTreeEngine::printInfo(std::string& info, bool dispTree, bool dispSnaps,\n                              bool dispParam, bool dispState, const std::string&\n                              schedgroup, const std::string& optype,\n                              bool useColors, bool monitoring)\n{\n  eos::common::RWMutexWriteLock arlock;\n\n  if (dispState) {\n    arlock.Grab(pAddRmFsMutex);\n  }\n\n  RWMutexReadLock lock(pTreeMapMutex);\n  stringstream ostr;\n  map<string, string> orderByGroupName;\n  std::string format_s = !monitoring ? \"s\" : \"os\";\n  std::string format_ss = !monitoring ? \"-s\" : \"os\";\n  std::string format_l = !monitoring ? \"l\" : \"ol\";\n  std::string format_ll = !monitoring ? \"-l\" : \"ol\";\n  std::string format_lll = !monitoring ? \"+l\" : \"ol\";\n  std::string format_f = !monitoring ? \"+f\" : \"of\";\n  std::string unit = !monitoring ? \"s\" : \"\";\n  std::string na = !monitoring ? \"-NA-\" : \"NA\";\n  unsigned scale = !monitoring ? 1000 :\n                   1; // miliseconds to seconds for human view\n\n  if (dispParam) {\n    ostr << \"### GeoTreeEngine parameters :\" << std::endl;\n    ostr << \"skipSaturatedAccess = \" << pSkipSaturatedAccess << std::endl;\n    ostr << \"skipSaturatedDrnAccess = \" << pSkipSaturatedDrnAccess << std::endl;\n    ostr << \"skipSaturatedBlcAccess = \" << pSkipSaturatedBlcAccess << std::endl;\n    ostr << \"proxyCloseToFs = \" << pProxyCloseToFs << std::endl;\n    ostr << \"penaltyUpdateRate = \" << pPenaltyUpdateRate << std::endl;\n    ostr << \"plctDlScorePenalty = \" << pPenaltySched.pPlctDlScorePenaltyF[0] <<\n         \"(default)\" << \" | \"\n         << pPenaltySched.pPlctDlScorePenaltyF[1] << \"(1Gbps)\" << \" | \"\n         << pPenaltySched.pPlctDlScorePenaltyF[2] << \"(10Gbps)\" << \" | \"\n         << pPenaltySched.pPlctDlScorePenaltyF[3] << \"(100Gbps)\" << \" | \"\n         << pPenaltySched.pPlctDlScorePenaltyF[4] << \"(1000Gbps)\" << std::endl;\n    ostr << \"plctUlScorePenalty = \" << pPenaltySched.pPlctUlScorePenaltyF[0] <<\n         \"(defaUlt)\" << \" | \"\n         << pPenaltySched.pPlctUlScorePenaltyF[1] << \"(1Gbps)\" << \" | \"\n         << pPenaltySched.pPlctUlScorePenaltyF[2] << \"(10Gbps)\" << \" | \"\n         << pPenaltySched.pPlctUlScorePenaltyF[3] << \"(100Gbps)\" << \" | \"\n         << pPenaltySched.pPlctUlScorePenaltyF[4] << \"(1000Gbps)\" << std::endl;\n    ostr << \"accessDlScorePenalty = \" << pPenaltySched.pAccessDlScorePenaltyF[0] <<\n         \"(default)\" << \" | \"\n         << pPenaltySched.pAccessDlScorePenaltyF[1] << \"(1Gbps)\" << \" | \"\n         << pPenaltySched.pAccessDlScorePenaltyF[2] << \"(10Gbps)\" << \" | \"\n         << pPenaltySched.pAccessDlScorePenaltyF[3] << \"(100Gbps)\" << \" | \"\n         << pPenaltySched.pAccessDlScorePenaltyF[4] << \"(1000Gbps)\" << std::endl;\n    ostr << \"accessUlScorePenalty = \" << pPenaltySched.pAccessUlScorePenaltyF[0] <<\n         \"(defaUlt)\" << \" | \"\n         << pPenaltySched.pAccessUlScorePenaltyF[1] << \"(1Gbps)\" << \" | \"\n         << pPenaltySched.pAccessUlScorePenaltyF[2] << \"(10Gbps)\" << \" | \"\n         << pPenaltySched.pAccessUlScorePenaltyF[3] << \"(100Gbps)\" << \" | \"\n         << pPenaltySched.pAccessUlScorePenaltyF[4] << \"(1000Gbps)\" << std::endl;\n    ostr << \"fillRatioLimit = \" << (int)pFillRatioLimit << std::endl;\n    ostr << \"fillRatioCompTol = \" << (int)pFillRatioCompTol << std::endl;\n    ostr << \"saturationThres = \" << (int)pSaturationThres << std::endl;\n    ostr << \"timeFrameDurationMs = \" << (int)pTimeFrameDurationMs << std::endl;\n  }\n\n  if (dispState) {\n    ostr << \"frameCount = \" << pFrameCount << std::endl;\n\n    //! Added penalties for each fs over successive frames\n    if (!monitoring) {\n      ostr << \"\\n┏━> Added penalties for each fs over successive frames\\n\";\n    }\n\n    {\n      // to be sure that no fs in inserted removed in the meantime\n      struct timeval curtime;\n      gettimeofday(&curtime, 0);\n      size_t ts = curtime.tv_sec * 1000 + curtime.tv_usec / 1000;\n      TableFormatterBase table;\n      TableHeader table_header;\n\n      if (monitoring) {\n        table_header.push_back(std::make_tuple(\"type\", 4, format_ss));\n      }\n\n      table_header.push_back(std::make_tuple(\"fsid\", 4, format_ll));\n      table_header.push_back(std::make_tuple(\"drct\", 4, format_ss));\n\n      for (size_t itcol = 0; itcol < pCircSize; itcol++) {\n        float frame = pLatencySched.pCircFrCnt2Timestamp[\n                 (pFrameCount + pCircSize - 1 - itcol) % pCircSize] ?\n                      (ts - pLatencySched.pCircFrCnt2Timestamp[\n                  (pFrameCount + pCircSize - 1 - itcol) % pCircSize]) * 0.001 : 0;\n        char header_name[24];\n        std::sprintf(header_name, \"%.1f\", frame);\n        table_header.push_back(std::make_tuple(header_name, 4, format_l));\n      }\n\n      table.SetHeader(table_header);\n      FsView::gFsView.ViewMutex.LockRead();\n      size_t fsid_count = pPenaltySched.pCircFrCnt2FsPenalties.begin()->size();\n\n      for (size_t fsid = 1; fsid < fsid_count; fsid++) {\n        if (!FsView::gFsView.mIdView.exists(fsid)) {\n          continue;\n        }\n\n        table.AddSeparator();\n        // for Upload\n        TableData table_data;\n        table_data.emplace_back();\n\n        if (monitoring) {\n          table_data.back().push_back(TableCell(\"AddedPenalties\", format_ss));\n        }\n\n        table_data.back().push_back(TableCell((unsigned long long)fsid, format_l));\n        table_data.back().push_back(TableCell(\"UL\", format_ss));\n\n        for (size_t itcol = 0; itcol < pCircSize; itcol++) {\n          int value = pPenaltySched.pCircFrCnt2FsPenalties[\n                        (pFrameCount + pCircSize - 1 - itcol) % pCircSize][fsid].ulScorePenalty;\n          table_data.back().push_back(TableCell(value, format_l));\n        }\n\n        // for Download\n        table_data.emplace_back();\n\n        if (monitoring) {\n          table_data.back().push_back(TableCell(\"AddedPenalties\", format_ss));\n          table_data.back().push_back(TableCell((unsigned long long)fsid, format_l));\n        } else {\n          table_data.back().push_back(TableCell(\"\", format_ss));\n        }\n\n        table_data.back().push_back(TableCell(\"DL\", format_ss));\n\n        for (size_t itcol = 0; itcol < pCircSize; itcol++) {\n          int value = pPenaltySched.pCircFrCnt2FsPenalties[\n                        (pFrameCount + pCircSize - 1 - itcol) % pCircSize][fsid].dlScorePenalty;\n          table_data.back().push_back(TableCell(value, format_l));\n        }\n\n        table.AddRows(table_data);\n      }\n\n      FsView::gFsView.ViewMutex.UnLockRead();\n      ostr << table.GenerateTable(HEADER2).c_str();\n    }\n\n    //! fst2GeotreeEngine latency\n    if (!monitoring) {\n      ostr << \"\\n┏━> fst2GeotreeEngine latency\\n\";\n    }\n\n    struct timeval nowtv;\n\n    gettimeofday(&nowtv, NULL);\n\n    size_t nowms = nowtv.tv_sec * 1000 + nowtv.tv_usec / 1000;\n\n    double avAge = 0.0;\n\n    size_t count = 0;\n\n    std::vector<std::tuple<unsigned long long,\n        double, double, double, double, bool>> data_fst;\n\n    for (auto it : pLatencySched.pFsId2LatencyStats) {\n      if (it.getage(nowms) < 600000) { // consider only if less than a minute\n        avAge += it.getage(nowms);\n        count++;\n      }\n    }\n\n    avAge /= (count ? count : 1);\n    TableFormatterBase table_fst;\n\n    if (!monitoring)\n      table_fst.SetHeader({\n      std::make_tuple(\"fsid\", 6, format_ll),\n      std::make_tuple(\"minimum\", 10, format_f),\n      std::make_tuple(\"averge\", 10, format_f),\n      std::make_tuple(\"maximum\", 10, format_f),\n      std::make_tuple(\"age(last)\", 10, format_f)\n    });\n    else\n      table_fst.SetHeader({\n      std::make_tuple(\"type\", 0, format_ss),\n      std::make_tuple(\"fsid\", 0, format_ll),\n      std::make_tuple(\"min\", 0, format_f),\n      std::make_tuple(\"avg\", 0, format_f),\n      std::make_tuple(\"max\", 0, format_f),\n      std::make_tuple(\"age(last)\", 0, format_f)\n    });\n    FsView::gFsView.ViewMutex.LockRead();\n\n    for (size_t fsid = 1; fsid < pLatencySched.pFsId2LatencyStats.size(); fsid++) {\n      if (!FsView::gFsView.mIdView.exists(fsid)) {\n        continue;\n      }\n\n      // more than 1 minute, something is wrong\n      if (pLatencySched.pFsId2LatencyStats[fsid].getage(nowms) > 600000) {\n        data_fst.push_back(std::make_tuple(fsid, 0, 0, 0, 0, false));\n      } else\n        data_fst.push_back(std::make_tuple(fsid,\n                                           pLatencySched.pFsId2LatencyStats[fsid].minlatency,\n                                           pLatencySched.pFsId2LatencyStats[fsid].averagelatency,\n                                           pLatencySched.pFsId2LatencyStats[fsid].maxlatency,\n                                           pLatencySched.pFsId2LatencyStats[fsid].getage(nowms), true));\n    }\n\n    FsView::gFsView.ViewMutex.UnLockRead();\n\n    for (auto it : data_fst) {\n      TableData table_data;\n      table_data.emplace_back();\n\n      if (monitoring) {\n        table_data.back().push_back(TableCell(\"fst2GeotreeEngine\", format_ss));\n      }\n\n      if (std::get<0>(it) == 0) {\n        table_data.back().push_back(TableCell(\"global\", format_ss));\n      } else {\n        table_data.back().push_back(TableCell(std::get<0>(it), format_l));\n      }\n\n      if (std::get<5>(it)) {\n        table_data.back().push_back(TableCell(std::get<1>(it) / scale, format_f, unit));\n        table_data.back().push_back(TableCell(std::get<2>(it) / scale, format_f, unit));\n        table_data.back().push_back(TableCell(std::get<3>(it) / scale, format_f, unit));\n        table_data.back().push_back(TableCell(std::get<4>(it) / scale, format_f, unit));\n      } else for (int i = 0; i < 4; i++) {\n          table_data.back().push_back(TableCell(na, format_ss));\n        }\n\n      table_fst.AddRows(table_data);\n\n      if (std::get<0>(it) == 0 && data_fst.size() > 1) {\n        table_fst.AddSeparator();\n      }\n    }\n\n    ostr << table_fst.GenerateTable(HEADER2).c_str();\n  }\n\n  // ==== run through the map of file systems\n  unsigned geo_depth_max = 0;\n  // Set for tree: group, num of line, depth, color, prefix_1, prefix_2,\n  //               geotag[::fsid], host, leavs count, nodes count, status\n  std::set<std::tuple<std::string, unsigned, unsigned, TableFormatterColor,\n      unsigned, unsigned, std::string, std::string,\n      int, int, std::string>> data_tree;\n  // Set for snapshot: group, num of line, depth, color, prefix_1, prefix_2,\n  //                   operation, operation_short, fsid, geotag/host,\n  //                   free, repl, pidx, status, ulSc, dlSc, filR, totS, totW\n  std::set<std::tuple<std::string, unsigned, unsigned, TableFormatterColor,\n      unsigned, unsigned, std::string, std::string, unsigned, std::string,\n      int, int, int, std::string, int, int, int, double, double>> data_snapshot;\n\n  for (auto it = pGroup2SchedTME.begin(); it != pGroup2SchedTME.end(); it++) {\n    if (dispTree && (schedgroup.empty() || schedgroup == \"*\" ||\n                     (schedgroup == it->second->group->mName))) {\n      it->second->slowTree->display(data_tree, geo_depth_max, useColors);\n    }\n\n    if (dispSnaps && (schedgroup.empty() || schedgroup == \"*\" ||\n                      (schedgroup == it->second->group->mName))) {\n      if (optype.empty() || (optype == \"plct\")) {\n        unsigned geo_depth_max_temp = 0;\n        it->second->foregroundFastStruct->placementTree->recursiveDisplay(\n          data_snapshot, geo_depth_max_temp, \"Placement\", \"plct\", useColors);\n        geo_depth_max = (geo_depth_max_temp > geo_depth_max) ?\n                        geo_depth_max_temp : geo_depth_max;\n      }\n\n      if (optype.empty() || (optype == \"accsro\")) {\n        unsigned geo_depth_max_temp = 0;\n        it->second->foregroundFastStruct->rOAccessTree->recursiveDisplay(\n          data_snapshot, geo_depth_max, \"Access RO\", \"accsro\", useColors);\n        geo_depth_max = (geo_depth_max_temp > geo_depth_max) ?\n                        geo_depth_max_temp : geo_depth_max;\n      }\n\n      if (optype.empty() || (optype == \"accsrw\")) {\n        unsigned geo_depth_max_temp = 0;\n        it->second->foregroundFastStruct->rWAccessTree->recursiveDisplay(\n          data_snapshot, geo_depth_max, \"Access RW\", \"accsrw\", useColors);\n        geo_depth_max = (geo_depth_max_temp > geo_depth_max) ?\n                        geo_depth_max_temp : geo_depth_max;\n      }\n\n      if (optype.empty() || (optype == \"accsdrain\")) {\n        unsigned geo_depth_max_temp = 0;\n        it->second->foregroundFastStruct->drnAccessTree->recursiveDisplay(\n          data_snapshot, geo_depth_max, \"Draining Access\", \"accsdrain\", useColors);\n        geo_depth_max = (geo_depth_max_temp > geo_depth_max) ?\n                        geo_depth_max_temp : geo_depth_max;\n      }\n\n      if (optype.empty() || (optype == \"plctdrain\")) {\n        unsigned geo_depth_max_temp = 0;\n        it->second->foregroundFastStruct->drnPlacementTree->recursiveDisplay(\n          data_snapshot, geo_depth_max, \"Draining Placement\", \"plctdrain\", useColors);\n        geo_depth_max = (geo_depth_max_temp > geo_depth_max) ?\n                        geo_depth_max_temp : geo_depth_max;\n      }\n    }\n  }\n\n  // ==== run through the map of file systems\n  for (auto it = pPxyGrp2DpTME.begin(); it != pPxyGrp2DpTME.end(); it++) {\n    if (dispTree &&\n        (schedgroup.empty() || schedgroup == \"*\" || (schedgroup == it->first))) {\n      std::string group_name = it->first + \"(proxy)\";\n      it->second->slowTree->display(data_tree, geo_depth_max, useColors);\n    }\n\n    if (dispSnaps &&\n        (schedgroup.empty() || schedgroup == \"*\" || (schedgroup == it->first))) {\n      unsigned geo_depth_max_temp = 0;\n      it->second->foregroundFastStruct->proxyAccessTree->recursiveDisplay(\n        data_snapshot, geo_depth_max, \"Proxy group\", \"proxy\", useColors);\n      geo_depth_max = (geo_depth_max_temp > geo_depth_max) ?\n                      geo_depth_max_temp : geo_depth_max;\n    }\n  }\n\n  // Output for \"geosched show tree\"\n  TableFormatterBase table_tree;\n  TableHeader table_header;\n  table_header.push_back(std::make_tuple(\"group\", 6, format_ss));\n  table_header.push_back(std::make_tuple(\"geotag\", 6, format_ss));\n\n  if (!monitoring && geo_depth_max > 1) {\n    for (unsigned i = 1; i < geo_depth_max; i++) {\n      std::string name = \"lev\" + std::to_string(i);\n      table_header.push_back(std::make_tuple(name, 4, format_ss));\n    }\n  }\n\n  table_header.push_back(std::make_tuple(\"fsid\", 4, format_l));\n  table_header.push_back(std::make_tuple(\"node\", 12, format_s));\n  table_header.push_back(std::make_tuple(\"branches\", 5, format_l));\n  table_header.push_back(std::make_tuple(\"leavs\", 5, format_l));\n  table_header.push_back(std::make_tuple(\"sum\", 3, format_l));\n  table_header.push_back(std::make_tuple(\"status\", 6, format_s));\n  table_tree.SetHeader(table_header);\n  unsigned prefix[geo_depth_max + 1];\n\n  for (auto it : data_tree) {\n    unsigned geo_depth = 0;\n    std::string geotag_temp = std::get<6>(it);\n\n    while (geotag_temp.find(\"::\") != std::string::npos) {\n      geotag_temp.erase(0, geotag_temp.find(\"::\") + 2);\n      geo_depth++;\n    }\n\n    TableData table_data;\n    table_data.emplace_back();\n\n    // Print group (depth=1)\n    if (std::get<2>(it) == 1) {\n      for (unsigned i = 0; i < geo_depth_max + 1; i++) {\n        prefix[i] = 0;\n      }\n\n      table_tree.AddSeparator();\n      table_data.back().push_back(TableCell(std::get<0>(it), format_s, \"\", false,\n                                            std::get<3>(it)));\n\n      for (unsigned i = 0; i < geo_depth_max + 2; i++) {\n        table_data.back().push_back(TableCell(\"\", format_s, \"\",\n                                              true)); // blank cell after group\n\n        if (monitoring && i == 2) {\n          break;\n        }\n      }\n    }\n    // Print geotag (depth=2)\n    else if (std::get<2>(it) == 2) {\n      if (!monitoring) {\n        if (geo_depth == 0) {\n          prefix[0] = std::get<5>(it);\n          table_data.back().push_back(TableCell(prefix[0], \"t\"));\n          table_data.back().push_back(TableCell(std::get<6>(it), format_s, \"\", false,\n                                                std::get<3>(it)));\n\n          for (unsigned i = 0; i < geo_depth_max - 1; i++) { // after arrows\n            table_data.back().push_back(TableCell(\"\", format_s, \"\", true));\n          }\n        } else {\n          prefix[geo_depth - 1] = std::get<4>(it);\n          prefix[geo_depth] = std::get<5>(it);\n\n          for (unsigned i = 0; i <= geo_depth; i++) { // arrows\n            table_data.back().push_back(TableCell(prefix[i], \"t\"));\n          }\n\n          std::string name = std::get<6>(it).substr(std::get<6>(it).rfind(\"::\") + 2);\n          table_data.back().push_back(TableCell(name, format_s, \"\", false,\n                                                std::get<3>(it)));\n\n          for (unsigned i = 1; i < geo_depth_max - geo_depth; i++) {\n            table_data.back().push_back(TableCell(\"\", format_s));\n          }\n        }\n      } else {\n        table_data.back().push_back(TableCell(std::get<0>(it), format_s));\n        table_data.back().push_back(TableCell(std::get<6>(it), format_s));\n      }\n\n      table_data.back().push_back(TableCell(\"\", format_s, \"\", true));\n      table_data.back().push_back(TableCell(\"\", format_s, \"\", true));\n    }\n    // Print fsid and node (depth=3)\n    else if (std::get<2>(it) == 3) {\n      if (!monitoring) {\n        if (geo_depth > 0) {\n          prefix[geo_depth - 1] = std::get<4>(it);\n          prefix[geo_depth] = std::get<5>(it);\n\n          for (unsigned i = 0; i <= geo_depth; i++) { // arrows\n            unsigned arrow = (i == geo_depth &&\n                              geo_depth_max - geo_depth > 0) ? prefix[i] + 2 : prefix[i];\n            table_data.back().push_back(TableCell(arrow, \"t\"));\n          }\n\n          for (unsigned i = 0; i < geo_depth_max - geo_depth; i++) { // extended arrows\n            unsigned arrow = (i == geo_depth_max - geo_depth - 1) ? 7 : 6;\n            table_data.back().push_back(TableCell(arrow, \"t\"));\n          }\n        }\n      } else {\n        std::string geotag = std::get<6>(it).substr(0, std::get<6>(it).rfind(\"::\"));\n        table_data.back().push_back(TableCell(std::get<0>(it), format_s));\n        table_data.back().push_back(TableCell(geotag, format_s));\n      }\n\n      unsigned fsid = std::atoi(std::get<6>(it).substr(std::get<6>\n                                (it).rfind(\"::\") + 2).c_str());\n      table_data.back().push_back(TableCell(fsid, format_l, \"\", false,\n                                            std::get<3>(it)));\n      table_data.back().push_back(TableCell(std::get<7>(it), format_s, \"\", false,\n                                            std::get<3>(it)));\n    }\n\n    // Print other columns\n    table_data.back().push_back(TableCell(std::get<9>(it) - std::get<8>(it),\n                                          format_l));\n    table_data.back().push_back(TableCell(std::get<8>(it), format_l));\n    table_data.back().push_back(TableCell(std::get<9>(it), format_l));\n    table_data.back().push_back(TableCell(std::get<10>(it), format_s, \"\",\n                                          (std::get<2>(it) != 3)));\n    table_tree.AddRows(table_data);\n  }\n\n  ostr << table_tree.GenerateTable(HEADER).c_str();\n  // Output for \"geosched show snapshot\"\n  std::string geotag = \"\";\n  size_t operation_count = 0;\n  TableFormatterBase table_snapshot;\n  TableHeader snapshot_header;\n  snapshot_header.push_back(std::make_tuple(\"group\", 6, format_ss));\n  snapshot_header.push_back(std::make_tuple(\"operation\", 6, format_ss));\n  snapshot_header.push_back(std::make_tuple(\"geotag\", 6, format_ss));\n\n  if (!monitoring && geo_depth_max > 1) {\n    for (unsigned i = 1; i < geo_depth_max; i++) {\n      std::string name = \"lev\" + std::to_string(i);\n      snapshot_header.push_back(std::make_tuple(name, 2, format_ss));\n    }\n  }\n\n  snapshot_header.push_back(std::make_tuple(\"fsid\", 4, format_l));\n  snapshot_header.push_back(std::make_tuple(\"node\", 12, format_s));\n  snapshot_header.push_back(std::make_tuple(\"free\", 4, format_l));\n  snapshot_header.push_back(std::make_tuple(\"repl\", 4, format_l));\n  snapshot_header.push_back(std::make_tuple(\"pidx\", 4, format_l));\n  snapshot_header.push_back(std::make_tuple(\"status\", 6, format_s));\n  snapshot_header.push_back(std::make_tuple(\"ulSc\", 4, format_l));\n  snapshot_header.push_back(std::make_tuple(\"dlSc\", 4, format_l));\n  snapshot_header.push_back(std::make_tuple(\"filR\", 4, format_l));\n  snapshot_header.push_back(std::make_tuple(\"totS\", 4, format_lll));\n  snapshot_header.push_back(std::make_tuple(\"totW\", 4, format_lll));\n  table_snapshot.SetHeader(snapshot_header);\n  set<std::string> operations;\n\n  for (auto it : data_snapshot) { // we need count of used operations\n    operations.insert(std::get<6>(it));\n  }\n\n  unsigned geo_depth = 0;\n\n  for (auto it : data_snapshot) {\n    if (std::get<2>(it) == 2) {\n      geo_depth = 0;\n      std::string geotag_temp = std::get<9>(it);\n\n      while (geotag_temp.find(\"::\") != std::string::npos) {\n        geotag_temp.erase(0, geotag_temp.find(\"::\") + 2);\n        geo_depth++;\n      }\n    }\n\n    TableData table_data;\n    table_data.emplace_back();\n\n    // Print group (depth=1)\n    if (std::get<2>(it) == 1) {\n      for (unsigned i = 0; i < geo_depth_max + 1; i++) {\n        prefix[i] = 0;\n      }\n\n      if (!monitoring) {\n        if (schedgroup == \"*\" || std::get<6>(it) == \"Placement\" ||\n            std::get<1>(it) == 0) {\n          table_snapshot.AddSeparator();\n          table_data.back().push_back(TableCell(std::get<0>(it), format_s, \"\", false,\n                                                std::get<3>(it)));\n          table_data.emplace_back();\n          operation_count = 0;\n        }\n\n        operation_count++;\n        unsigned tree_arrow = (schedgroup == \"*\" ||\n                               operation_count == operations.size()) ? 2 : 3;\n        table_data.back().push_back(TableCell(tree_arrow, \"t\"));\n        table_data.back().push_back(TableCell(std::get<6>(it), format_s, \"\", false,\n                                              std::get<3>(it)));\n      } else {\n        table_data.back().push_back(TableCell(std::get<0>(it), format_s));\n        table_data.back().push_back(TableCell(std::get<7>(it), format_s));\n      }\n\n      for (unsigned i = 0; i < geo_depth_max + 2; i++) {\n        table_data.back().push_back(TableCell(\"\", format_s, \"\",\n                                              true)); // blank cell after group\n\n        if (monitoring && i == 2) {\n          break;\n        }\n      }\n    }\n    // Print geotag (depth=2)\n    else if (std::get<2>(it) == 2) {\n      geotag = std::get<9>(it);\n\n      if (!monitoring) {\n        unsigned tree_arrow = (schedgroup == \"*\" ||\n                               operation_count == operations.size()) ? 0 : 1;\n        table_data.back().push_back(TableCell(tree_arrow, \"t\"));\n\n        if (geo_depth == 0) {\n          prefix[0] = std::get<5>(it);\n          table_data.back().push_back(TableCell(prefix[0], \"t\"));\n          table_data.back().push_back(TableCell(geotag, format_s, \"\", false,\n                                                std::get<3>(it)));\n\n          for (unsigned i = 0; i < geo_depth_max - 1; i++) { // after arrows\n            table_data.back().push_back(TableCell(\"\", format_s, \"\", true));\n          }\n        } else {\n          prefix[geo_depth - 1] = std::get<4>(it);\n          prefix[geo_depth] = std::get<5>(it);\n\n          for (unsigned i = 0; i <= geo_depth; i++) { // arrows\n            table_data.back().push_back(TableCell(prefix[i], \"t\"));\n          }\n\n          std::string name = geotag.substr(geotag.rfind(\"::\") + 2);\n          table_data.back().push_back(TableCell(name, format_s, \"\", false,\n                                                std::get<3>(it)));\n\n          for (unsigned i = 1; i < geo_depth_max - geo_depth; i++) {\n            table_data.back().push_back(TableCell(\"\", format_s));\n          }\n        }\n      } else {\n        table_data.back().push_back(TableCell(std::get<0>(it), format_s));\n        table_data.back().push_back(TableCell(std::get<7>(it), format_s));\n        table_data.back().push_back(TableCell(geotag, format_s));\n      }\n\n      table_data.back().push_back(TableCell(\"\", format_s, \"\", true));\n      table_data.back().push_back(TableCell(\"\", format_s, \"\", true));\n    }\n    // Print fsid and node (depth=3)\n    else if (std::get<2>(it) == 3) {\n      if (!monitoring) {\n        unsigned tree_arrow = (schedgroup == \"*\" ||\n                               operation_count == operations.size()) ? 0 : 1;\n        table_data.back().push_back(TableCell(tree_arrow, \"t\"));\n        prefix[geo_depth] = std::get<4>(it);\n        prefix[geo_depth + 1] = std::get<5>(it);\n\n        for (unsigned i = 0; i <= geo_depth + 1; i++) { // arrows\n          unsigned arrow = (i == geo_depth + 1 &&\n                            geo_depth_max - geo_depth - 1 > 0) ? prefix[i] + 2 : prefix[i];\n          table_data.back().push_back(TableCell(arrow, \"t\"));\n        }\n\n        for (unsigned i = 0; i < geo_depth_max - geo_depth - 1; i++) { // extended arrow\n          unsigned arrow = (i == geo_depth_max - geo_depth - 2) ? 7 : 6;\n          table_data.back().push_back(TableCell(arrow, \"t\"));\n        }\n      } else {\n        table_data.back().push_back(TableCell(std::get<0>(it), format_s));\n        table_data.back().push_back(TableCell(std::get<7>(it), format_s));\n        table_data.back().push_back(TableCell(geotag, format_s));\n      }\n\n      table_data.back().push_back(TableCell(std::get<8>(it), format_l, \"\", false,\n                                            std::get<3>(it)));\n      table_data.back().push_back(TableCell(std::get<9>(it), format_s, \"\", false,\n                                            std::get<3>(it)));\n    }\n\n    // Print other columns\n    table_data.back().push_back(TableCell(std::get<10>(it), format_l));\n    table_data.back().push_back(TableCell(std::get<11>(it), format_l));\n    table_data.back().push_back(TableCell(std::get<12>(it), format_l));\n    table_data.back().push_back(TableCell(std::get<13>(it), format_s));\n    table_data.back().push_back(TableCell(std::get<14>(it), format_l));\n    table_data.back().push_back(TableCell(std::get<15>(it), format_l));\n    table_data.back().push_back(TableCell(std::get<16>(it), format_l));\n    table_data.back().push_back(TableCell(std::get<17>(it), format_lll));\n\n    if ((std::get<13>(it).find(\"RW\") != std::string::npos) ||\n        (std::get<13>(it).find(\"OK\") != std::string::npos)) {\n      table_data.back().push_back(TableCell(std::get<18>(it), format_lll));\n    } else {\n      table_data.back().push_back(TableCell(0, format_lll));\n    }\n\n    table_snapshot.AddRows(table_data);\n  }\n\n  ostr << table_snapshot.GenerateTable(HEADER).c_str();\n  info = ostr.str();\n}\n\n\nbool\nGeoTreeEngine::placeNewReplicasOneGroup(FsGroup* group,\n                                        const size_t& nNewReplicas,\n                                        vector<FileSystem::fsid_t>* newReplicas,\n                                        ino64_t inode, std::vector<std::string>* dataProxys,\n                                        std::vector<std::string>* firewallEntryPoint,\n                                        SchedType type,\n                                        vector<FileSystem::fsid_t>* existingReplicas,\n                                        std::vector<std::string>* fsidsgeotags,\n                                        unsigned long long bookingSize,\n                                        const std::string& startFromGeoTag,\n                                        const std::string& clientGeoTag,\n                                        const size_t& nCollocatedReplicas,\n                                        vector<FileSystem::fsid_t>* excludeFs,\n                                        vector<string>* excludeGeoTags)\n{\n  assert(nNewReplicas);\n  assert(newReplicas);\n  std::vector<SchedTME*> entries;\n  // find the entry in the map\n  SchedTME* entry;\n  {\n    RWMutexReadLock lock(this->pTreeMapMutex);\n\n    if (!pGroup2SchedTME.count(group)) {\n      eos_err(\"could not find the requested placement group in the map\");\n      return false;\n    }\n\n    entry = pGroup2SchedTME[group];\n    AtomicInc(entry->fastStructLockWaitersCount);\n  }\n  // readlock the original fast structure\n  entry->doubleBufferMutex.LockRead();\n  // locate the existing replicas and the excluded fs in the tree\n  vector<SchedTreeBase::tFastTreeIdx> newReplicasIdx(nNewReplicas),\n         *existingReplicasIdx = NULL, *excludeFsIdx = NULL;\n  newReplicasIdx.resize(0);\n\n  if (existingReplicas) {\n    existingReplicasIdx = new vector<SchedTreeBase::tFastTreeIdx>\n    (existingReplicas->size());\n    existingReplicasIdx->resize(0);\n    int count = 0;\n\n    for (auto it = existingReplicas->begin(); it != existingReplicas->end();\n         ++it, ++count) {\n      const SchedTreeBase::tFastTreeIdx* idx =\n        static_cast<const SchedTreeBase::tFastTreeIdx*>(0);\n\n      if (!entry->foregroundFastStruct->fs2TreeIdx->get(*it, idx) &&\n          !(*fsidsgeotags)[count].empty()) {\n        // the fs is not in that group.\n        // this could happen because the former file scheduler\n        // could place replicas across multiple groups\n        // with the new geoscheduler, it should not happen\n        // in that case, we try to match a filesystem having the same geotag\n        SchedTreeBase::tFastTreeIdx idx =\n          entry->foregroundFastStruct->tag2NodeIdx->getClosestFastTreeNode((\n                *fsidsgeotags)[count].c_str());\n\n        if (idx &&\n            (*entry->foregroundFastStruct->treeInfo)[idx].nodeType ==\n            SchedTreeBase::TreeNodeInfo::fs) {\n          if ((std::find(existingReplicasIdx->begin(), existingReplicasIdx->end(),\n                         idx) == existingReplicasIdx->end())) {\n            existingReplicasIdx->push_back(idx);\n          }\n        }\n        // if we can't find any such filesystem, the information is not taken into account\n        // (and then can lead to unoptimal placement\n        else {\n          eos_debug(\"could not place preexisting replica on the fast tree\");\n        }\n\n        continue;\n      }\n\n      if (idx) {\n        existingReplicasIdx->push_back(*idx);\n      }\n    }\n  }\n\n  if (excludeFs) {\n    excludeFsIdx = new vector<SchedTreeBase::tFastTreeIdx>(excludeFs->size());\n    excludeFsIdx->resize(0);\n\n    for (auto it = excludeFs->begin(); it != excludeFs->end(); ++it) {\n      const SchedTreeBase::tFastTreeIdx* idx;\n\n      if (!entry->foregroundFastStruct->fs2TreeIdx->get(*it, idx)) {\n        // the excluded fs might belong to another group\n        // so it's not an error condition\n        // eos_warning(\"could not place excluded fs on the fast tree\");\n        continue;\n      }\n\n      excludeFsIdx->push_back(*idx);\n    }\n  }\n\n  if (excludeGeoTags) {\n    if (!excludeFsIdx) {\n      excludeFsIdx = new vector<SchedTreeBase::tFastTreeIdx>(excludeGeoTags->size());\n      excludeFsIdx->resize(0);\n    }\n\n    for (auto it = excludeGeoTags->begin(); it != excludeGeoTags->end(); ++it) {\n      SchedTreeBase::tFastTreeIdx idx;\n      idx = entry->foregroundFastStruct->tag2NodeIdx->getClosestFastTreeNode(\n              it->c_str());\n      excludeFsIdx->push_back(idx);\n    }\n  }\n\n  SchedTreeBase::tFastTreeIdx startFromNode = 0;\n\n  if (!startFromGeoTag.empty()) {\n    startFromNode =\n      entry->foregroundFastStruct->tag2NodeIdx->getClosestFastTreeNode(\n        startFromGeoTag.c_str());\n  } else if (!clientGeoTag.empty()) {\n    startFromNode =\n      entry->foregroundFastStruct->tag2NodeIdx->getClosestFastTreeNode(\n        clientGeoTag.c_str());\n  }\n\n  // actually do the job\n  bool success = false;\n\n  switch (type) {\n  case regularRO:\n  case regularRW:\n    success = placeNewReplicas(entry, nNewReplicas, &newReplicasIdx,\n                               entry->foregroundFastStruct->placementTree,\n                               existingReplicasIdx, bookingSize, startFromNode,\n                               nCollocatedReplicas, excludeFsIdx);\n    break;\n\n  case draining:\n    success = placeNewReplicas(entry, nNewReplicas, &newReplicasIdx,\n                               entry->foregroundFastStruct->drnPlacementTree,\n                               existingReplicasIdx, bookingSize, startFromNode,\n                               nCollocatedReplicas, excludeFsIdx);\n    break;\n\n  default:\n    break;\n  }\n\n  if (!success) {\n    goto cleanup;\n  }\n\n  // fill the resulting vector and\n  // update the fastTree UlScore and DlScore by applying the penalties\n  newReplicas->resize(0);\n\n  for (auto it = newReplicasIdx.begin(); it != newReplicasIdx.end(); ++it) {\n    const SchedTreeBase::tFastTreeIdx* idx = NULL;\n    const unsigned int fsid = (*entry->foregroundFastStruct->treeInfo)[*it].fsId;\n\n    if (!entry->foregroundFastStruct->fs2TreeIdx->get(fsid, idx)) {\n      eos_crit(\"inconsistency : cannot retrieve index of selected fs though \"\n               \"it should be in the tree\");\n      success = false;\n      goto cleanup;\n    }\n\n    const char netSpeedClass =\n      (*entry->foregroundFastStruct->treeInfo)[*idx].netSpeedClass;\n    newReplicas->push_back(fsid);\n\n    // Apply the penalties\n    if (entry->foregroundFastStruct->placementTree->pNodes[*idx].fsData.dlScore >\n        0) {\n      applyDlScorePenalty(entry, *idx,\n                          pPenaltySched.pPlctDlScorePenalty[netSpeedClass]);\n    }\n\n    if (entry->foregroundFastStruct->placementTree->pNodes[*idx].fsData.ulScore >\n        0) {\n      applyUlScorePenalty(entry, *idx,\n                          pPenaltySched.pPlctUlScorePenalty[netSpeedClass]);\n    }\n  }\n\n  if (dataProxys || firewallEntryPoint) {\n    entries.assign(newReplicasIdx.size(), entry);\n    auto rc = accessProxyFirewall(newReplicasIdx, entries,\n                                  inode, dataProxys, firewallEntryPoint,\n                                  clientGeoTag);\n\n    if (rc != 0) {\n      success = false;\n    }\n  }\n\n  // Unlock, cleanup\ncleanup:\n\n  if (!success) {\n    newReplicas->clear();\n  }\n\n  entry->doubleBufferMutex.UnLockRead();\n  AtomicDec(entry->fastStructLockWaitersCount);\n\n  if (existingReplicasIdx) {\n    delete existingReplicasIdx;\n  }\n\n  if (excludeFsIdx) {\n    delete excludeFsIdx;\n  }\n\n  return success;\n}\n\n// Would be better as defined locally in find Proxy\n// but it is not supported by gcc 4.4\nstruct TreeInfoFsIdComparator {\n  SchedTreeBase::FastTreeInfo* nodesinfo;\n  TreeInfoFsIdComparator(SchedTreeBase::FastTreeInfo* infos)\n  {\n    nodesinfo = infos;\n  }\n  bool operator()(const SchedTreeBase::tFastTreeIdx& a,\n                  const SchedTreeBase::tFastTreeIdx& b) const\n  {\n    return (*nodesinfo)[a].fsId < (*nodesinfo)[b].fsId;\n  }\n};\n\nbool GeoTreeEngine::findProxy(const std::vector<SchedTreeBase::tFastTreeIdx>&\n                              fsIdxs,\n                              std::vector<SchedTME*> entries,\n                              ino64_t inode,\n                              std::vector<std::string>* dataProxys,\n                              std::vector<std::string>* proxyGroups,\n                              const std::string& clientgeotag,\n                              tProxySchedType proxyschedtype)\n{\n  // re initialize result vector\n  dataProxys->resize(fsIdxs.size());\n  const std::string* fsproxygroup = 0;\n  DataProxyTME* pxyentry = NULL;\n  FastGatewayAccessTree* tree = NULL;\n  std::string sgeotag;\n\n  for (size_t i = 0; i < fsIdxs.size(); i++) {\n    const std::string* geotag = NULL;\n    // get the proxygroup\n    // WARNING: entries[i]->doubleBufferMutex should be locked by the caller of findProxy\n\n    if (!(*dataProxys)[i].empty() && (*dataProxys)[i] != \"<none>\") {\n      if (pPxyHost2DpTMEs.count((*dataProxys)[i])) {\n        const auto& TMEs = pPxyHost2DpTMEs[(*dataProxys)[i]];\n        // If dataProxys already contains proxy hostnames, check first if they\n        // already do the job for the given proxygroup.\n        bool isInRightPxyGrp = false;\n\n        if (proxyGroups) {\n          for (auto it = TMEs.begin(); it != TMEs.end(); it++) {\n            if ((*it)->slowTree->getName() == (*proxyGroups)[i]) {\n              isInRightPxyGrp = true;\n              break;\n            }\n          }\n        }\n\n        if (isInRightPxyGrp) {\n          continue;\n        }\n\n        {\n          auto entry = (*TMEs.begin());\n\n          // we don't want to lock the pxyentry which is already locked\n          if (entry != pxyentry) {\n            AtomicInc(entry->fastStructLockWaitersCount);\n            entry->doubleBufferMutex.LockRead();\n          }\n\n          // if they don't, take their geotag as a staring point\n          sgeotag =\n            (*TMEs.begin())->host2SlowTreeNode[(*dataProxys)[i]]->pNodeInfo.fullGeotag;\n          geotag = &sgeotag;\n\n          if (entry != pxyentry) {\n            entry->doubleBufferMutex.UnLockRead();\n            AtomicDec(entry->fastStructLockWaitersCount);\n          }\n        }\n      }\n    }\n\n    if (proxyGroups) {\n      fsproxygroup = &((*proxyGroups)[i]);\n    } else {\n      fsproxygroup = &\n                     (*entries[i]->foregroundFastStruct->treeInfo)[fsIdxs[i]].proxygroup;\n    }\n\n    if (fsproxygroup->empty() ||\n        (*fsproxygroup) ==  \"<none>\") {\n      // No proxygroup, nothing to do, there will be an entry with an empty string\n      (*dataProxys)[i].clear();\n      continue;\n    }\n\n    // If we don't have a proxy to match, if a client geotag is given then use\n    // it else use the file system client\n    bool trimlastlevel = geotag || clientgeotag.empty();\n\n    if (!geotag) {\n      geotag = (clientgeotag.empty() ? &\n                ((*(entries[i]->foregroundFastStruct->treeInfo))[fsIdxs[i]].fullGeotag) :\n                &clientgeotag);\n    }\n\n    // The deepest intermediate node is a numeric id for both scheduling and GW\n    // trees and they are unrelated. We don't want to keep this to project the\n    // fst location on the gw tree as it would not make sense lock it for each\n    //  new fs.\n    RWMutexReadLock lock(this->pPxyTreeMapMutex);\n\n    if (!pPxyGrp2DpTME.count(*fsproxygroup)) {\n      eos_err(\"could not find the requested proxy group %s in the map\",\n              fsproxygroup->c_str());\n      return false;\n    }\n\n    pxyentry = pPxyGrp2DpTME[*fsproxygroup];\n    AtomicInc(pxyentry->fastStructLockWaitersCount);\n    // readlock the original fast structure\n    pxyentry->doubleBufferMutex.LockRead();\n\n    // copy the fasttree\n    if (pxyentry->foregroundFastStruct->proxyAccessTree->copyToBuffer((\n          char*)tlGeoBuffer, gGeoBufferSize)) {\n      eos_crit(\"could not make a working copy of the fast tree for proxygroup %s\",\n               fsproxygroup->c_str());\n      pxyentry->doubleBufferMutex.UnLockRead();\n      AtomicDec(pxyentry->fastStructLockWaitersCount);\n      return false;\n    }\n\n    tree = (FastGatewayAccessTree*)tlGeoBuffer;\n    // get the closest node from the filesystem\n    SchedTreeBase::tFastTreeIdx idx;\n    idx = pxyentry->foregroundFastStruct->tag2NodeIdx->getClosestFastTreeNode(\n            trimlastlevel ? std::string(*geotag, 0,\n                                        geotag->rfind(\"::\")).c_str() : geotag->c_str());\n    bool schedsuccess = false;\n    eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n\n    if (proxyschedtype == filesticky) {\n      // scheduling should consistently go through the same (firewallentrypoint,proxy)\n      // this is to do the caching of the file only on one proxy\n      // serving a same file from two proxies is not optimal but it is not mendatory neither\n      if ((*entries[i]->foregroundFastStruct->treeInfo)[fsIdxs[i]].fileStickyProxyDepth\n          < 0) {\n        schedsuccess = true;\n      }\n      // first find the best proxy\n      else {\n        // then consider all the possible proxy in the same proxygroup\n        // within the subtree starting at the best proxy and going uproot by\n        // (*pxyentry->foregroundFastStruct->treeInfo)[idx].fileStickyProxyDepth\n        // allocate a vectors to get the proxies\n        auto s = pxyentry->foregroundFastStruct->treeInfo->size();\n        std::vector<SchedTreeBase::tFastTreeIdx> proxiesIdxs(s), upRootLevels(s),\n            upRootLevelsIdxs(s);\n        SchedTreeBase::tFastTreeIdx upRootLevelsCount = 0;\n        SchedTreeBase::tFastTreeIdx np = 0;\n\n        // get all the proxies\n        if ((np = tree->findFreeSlotsAll(&proxiesIdxs[0], proxiesIdxs.size(), idx, true,\n                                         SchedTreeBase::None, &upRootLevelsCount,\n                                         &upRootLevelsIdxs[0], &upRootLevels[0]))) {\n          schedsuccess = (np != 0);\n\n          if (schedsuccess) {\n            if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n              stringstream ss;\n              ss << \" all proxys are:\";\n\n              for (auto it = proxiesIdxs.begin(); it != proxiesIdxs.end(); it++) {\n                ss << (*pxyentry->foregroundFastStruct->treeInfo)[*it].hostport;\n                ss << \"(\" << (*pxyentry->foregroundFastStruct->treeInfo)[*it].fullGeotag << \")\";\n\n                if (it != proxiesIdxs.end() - 1) {\n                  ss << \",\";\n                }\n              }\n\n              ss << \" upRootLevels are:\";\n\n              for (auto it = upRootLevels.begin(); it != upRootLevels.end(); it++) {\n                ss << (int)*it;\n\n                if (it != upRootLevels.end() - 1) {\n                  ss << \",\";\n                }\n              }\n\n              ss << \" upRootLevelsIdxs are:\";\n\n              for (auto it = upRootLevelsIdxs.begin(); it != upRootLevelsIdxs.end(); it++) {\n                ss << (int)*it;\n\n                if (it != upRootLevelsIdxs.end() - 1) {\n                  ss << \",\";\n                }\n              }\n\n              ss << \" taken from idx:\" << idx << \"(\" << *geotag << \")\";\n              eos_debug(\"%s\", ss.str().c_str());\n            }\n\n            // keep only the proxies within the allowed uproot level, if any\n            int uprlev = 0;\n\n            while (\n              uprlev < upRootLevelsCount &&\n              upRootLevels[uprlev] <=\n              (*entries[i]->foregroundFastStruct->treeInfo)[fsIdxs[i]].fileStickyProxyDepth\n            ) {\n              uprlev++;\n            }\n\n            if (uprlev == 0) {\n              // no proxy with a right uproot level\n              schedsuccess = false;\n            } else {\n              int resize = (uprlev == upRootLevelsCount) ? -1 : upRootLevelsIdxs[uprlev];\n\n              if (resize > 0) {\n                proxiesIdxs.resize(resize);\n              } else {\n                proxiesIdxs.resize(np);\n              }\n\n              // sort the proxies by fsid\n              TreeInfoFsIdComparator cmp(pxyentry->foregroundFastStruct->treeInfo);\n              std::sort(proxiesIdxs.begin(), proxiesIdxs.end(), cmp);\n              // take the proxy\n              idx = proxiesIdxs[inode % proxiesIdxs.size()];\n              // if it succeeds, feel the corresponding element of the return vector\n              (*dataProxys)[i] = (*pxyentry->foregroundFastStruct->treeInfo)[idx].hostport;\n\n              if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n                stringstream ss;\n                ss << \"file sticky proxy scheduling fs:\" <<\n                   (*entries[i]->foregroundFastStruct->treeInfo)[fsIdxs[i]].fsId;\n                ss << \" | fileStickyProxyDepth:\" << (int)(\n                     *entries[i]->foregroundFastStruct->treeInfo)[fsIdxs[i]].fileStickyProxyDepth;\n                ss << \" | possible proxys are:\";\n\n                for (auto it = proxiesIdxs.begin(); it != proxiesIdxs.end(); it++) {\n                  ss << (*pxyentry->foregroundFastStruct->treeInfo)[*it].hostport;\n                  ss << \"(\" << (*pxyentry->foregroundFastStruct->treeInfo)[*it].fullGeotag << \")\";\n\n                  if (it != proxiesIdxs.end() - 1) {\n                    ss << \",\";\n                  }\n                }\n\n                ss << \" | inode:\" << inode;\n                ss << \" | selected host is:\" <<\n                   (*pxyentry->foregroundFastStruct->treeInfo)[idx].hostport;\n                eos_debug(\"%s\", ss.str().c_str());\n              }\n            }\n          }\n        }\n      }\n    } else {\n      if (proxyschedtype == any\n          || ((*entries[i]->foregroundFastStruct->treeInfo)[fsIdxs[i]].fileStickyProxyDepth\n              < 0 && proxyschedtype == regular)) {\n        // get the proxy\n        if (!(schedsuccess = tree->findFreeSlot(idx, idx,\n                                                true /*allow uproot if necessary*/, false, true /*skipSaturated*/))) {\n          (*dataProxys)[i] = (*pxyentry->foregroundFastStruct->treeInfo)[idx].hostport;\n        } else {\n          if ((schedsuccess = tree->findFreeSlot(idx, idx,\n                                                 true /*allow uproot if necessary*/, false, false /*skipSaturated*/)))\n            // if it succeeds, feel the corresponding element of the return vector\n          {\n            (*dataProxys)[i] = (*pxyentry->foregroundFastStruct->treeInfo)[idx].hostport;\n          }\n        }\n      } else {\n        schedsuccess = true;  // nothing to do\n      }\n    }\n\n    // if the scheduling failed, throw an error\n    if (!schedsuccess) {\n      eos_err(\"could not find a proxy for proxygroup %s\", fsproxygroup->c_str());\n      std::stringstream ss;\n      ss << \"tree is as follow\\n\" << (*tree);\n      eos_err(ss.str().c_str());\n      pxyentry->doubleBufferMutex.UnLockRead();\n      AtomicDec(pxyentry->fastStructLockWaitersCount);\n      return false;\n    }\n\n    // unlock it for each new fs\n    pxyentry->doubleBufferMutex.UnLockRead();\n    AtomicDec(pxyentry->fastStructLockWaitersCount);\n  }\n\n  return true;\n}\n\nint GeoTreeEngine::accessHeadReplicaMultipleGroup(size_t nAccessReplicas,\n    unsigned long& fsIndex,\n    const std::vector<eos::common::FileSystem::fsid_t>& existingReplicas,\n    ino64_t inode,\n    std::vector<std::string>* dataProxys,\n    std::vector<std::string>* firewallEntryPoint,\n    SchedType type,\n    const std::string& accesserGeotag,\n    eos::common::FileSystem::fsid_t forcedFsId,\n    std::vector<eos::common::FileSystem::fsid_t>* unavailableFs)\n{\n  int returnCode = ENODATA;\n  assert(nAccessReplicas);\n  // Find the group holdings the fs of the existing replicas and check that the\n  // replicas are available\n  size_t availFsCount = 0;\n  eos::mgm::SchedTreeBase::TreeNodeSlots freeSlot;\n  freeSlot.freeSlotsCount = 1;\n  std::vector<eos::common::FileSystem::fsid_t>::const_iterator it;\n  std::vector<SchedTreeBase::tFastTreeIdx> ERIdx;\n  ERIdx.reserve(existingReplicas.size());\n  std::vector<SchedTME*> entries;\n  entries.reserve(existingReplicas.size());\n  // Maps tree maps entries (i.e. scheduling groups) to fs ids containing an\n  // available replica and the corresponding fastTreeIndex\n  map<SchedTME*, vector< pair<FileSystem::fsid_t, SchedTreeBase::tFastTreeIdx> > >\n  entry2FsId;\n  SchedTME* entry = NULL;\n  {\n    // Lock the scheduling group -> trees map so that the a map entry cannot\n    // be delete while processing it.\n    RWMutexReadLock lock(this->pTreeMapMutex);\n\n    for (const auto& exrepIt : existingReplicas) {\n      auto mentry = pFs2SchedTME.find(exrepIt);\n\n      // If we cannot find the fs in any group, there is an inconsistency somewhere\n      if (mentry == pFs2SchedTME.end()) {\n        eos_warning(\"msg=\\\"cannot find the existing replica in any \"\n                    \"scheduling group\\\" fsid=%u\", exrepIt);\n        continue;\n      }\n\n      entry = mentry->second;\n\n      // lock the double buffering to make sure all the fast trees are not modified\n      if (!entry2FsId.count(entry)) {\n        // if the entry is already there, it was locked already\n        entry->doubleBufferMutex.LockRead();\n        // to prevent the destruction of the entry\n        AtomicInc(entry->fastStructLockWaitersCount);\n      }\n\n      const SchedTreeBase::tFastTreeIdx* idx;\n\n      if (!entry->foregroundFastStruct->fs2TreeIdx->get(exrepIt, idx)) {\n        eos_warning(\"msg=\\\"cannot find fs in the scheduling group in the 2nd \"\n                    \"pass\\\" fsid=%u\", exrepIt);\n\n        if (!entry2FsId.count(entry)) {\n          entry->doubleBufferMutex.UnLockRead();\n          AtomicDec(entry->fastStructLockWaitersCount);\n        }\n\n        continue;\n      }\n\n      // take the fastindex of each existing replica\n      ERIdx.push_back(*idx);\n      entries.push_back(entry);\n      // check if the fs is available\n      bool isValid = false;\n      std::string msg;\n\n      if (std::find(unavailableFs->begin(), unavailableFs->end(),\n                    exrepIt) == unavailableFs->end()) {\n        switch (type) {\n        case regularRO:\n          isValid = entry->foregroundFastStruct->rOAccessTree->pBranchComp.isValidSlot(\n                      &entry->foregroundFastStruct->rOAccessTree->pNodes[*idx].fsData, &freeSlot);\n\n          if (!isValid) {\n            msg = \"file system not readable\";\n          }\n\n          break;\n\n        case regularRW:\n          isValid = entry->foregroundFastStruct->rWAccessTree->pBranchComp.isValidSlot(\n                      &entry->foregroundFastStruct->rWAccessTree->pNodes[*idx].fsData, &freeSlot);\n\n          if (!isValid) {\n            msg = \"file system not writable\";\n          }\n\n          break;\n\n        case draining:\n          isValid = entry->foregroundFastStruct->drnAccessTree->pBranchComp.isValidSlot(\n                      &entry->foregroundFastStruct->drnAccessTree->pNodes[*idx].fsData, &freeSlot);\n\n          if (!isValid) {\n            msg = \"file system not readable for drain\";\n          }\n\n          break;\n\n        default:\n          break;\n        }\n      } else {\n        msg = \"file system marked as unavailable\";\n      }\n\n      if (isValid) {\n        entry2FsId[entry].push_back(make_pair(exrepIt, *idx));\n        availFsCount++;\n      } else {\n        // create an empty entry in the map if needed\n        if (!entry2FsId.count(entry)) {\n          entry2FsId[entry] =\n            vector< pair<FileSystem::fsid_t, SchedTreeBase::tFastTreeIdx> >();\n        }\n\n        // update the unavailable fs\n        unavailableFs->push_back(exrepIt);\n        eos_warning(\"msg=\\\"%s\\\" fsid=%u\", msg.c_str(), exrepIt);\n      }\n    }\n  }\n\n  // Check if there are enough available replicas\n  if (availFsCount < nAccessReplicas) {\n    returnCode = ENETUNREACH;\n    goto cleanup;\n  }\n\n  // Check if the forced replica (if any) is available\n  if (forcedFsId > 0 &&\n      (std::find(unavailableFs->begin(), unavailableFs->end(),\n                 forcedFsId) != unavailableFs->end())) {\n    returnCode = ENETUNREACH;\n    goto cleanup;\n  }\n\n  // We have multiple groups - compute their geolocation scores to the the\n  // available fsids (+things) having a replica\n  {\n    SchedTreeBase::tFastTreeIdx accesserNode = 0;\n    FileSystem::fsid_t selectedFsId = 0;\n    eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n    {\n      // maps a geolocation scores (int) to all the file system having this geolocation scores\n      map< unsigned, std::vector< FileSystem::fsid_t > > geoScore2Fs;\n      vector<SchedTreeBase::tFastTreeIdx> accessedReplicasIdx(1);\n\n      for (auto entryIt = entry2FsId.begin(); entryIt != entry2FsId.end();\n           entryIt ++) {\n        if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n          char buffer[1024];\n          buffer[0] = 0;\n          char* buf = buffer;\n\n          for (auto it = entryIt->second.begin(); it != entryIt->second.end(); ++it) {\n            buf += sprintf(buf, \"%lu  \", (unsigned long)(it->second));\n          }\n\n          eos_debug(\"existing replicas indices in geotree -> %s\", buffer);\n          buffer[0] = 0;\n          buf = buffer;\n\n          for (auto it = entryIt->second.begin(); it != entryIt->second.end(); ++it) {\n            buf += sprintf(buf, \"%s  \",\n                           (*entryIt->first->foregroundFastStruct->treeInfo)[it->second].fullGeotag.c_str());\n          }\n\n          eos_debug(\"existing replicas geotags in geotree -> %s\", buffer);\n        }\n\n        // If there is no replica here (might happen if it's spotted as unavailable\n        // after the first pass)\n        if (entryIt->second.empty()) {\n          continue;\n        }\n\n        entry = entryIt->first;\n        // find the closest tree node to the accesser\n        accesserNode = entry->foregroundFastStruct->tag2NodeIdx->getClosestFastTreeNode(\n                         accesserGeotag.c_str());;\n        // fill a vector with the indices of the replicas\n        vector<SchedTreeBase::tFastTreeIdx> existingReplicasIdx(entryIt->second.size());\n\n        for (size_t i = 0; i < entryIt->second.size(); i++) {\n          existingReplicasIdx[i] = entryIt->second[i].second;\n        }\n\n        // pickup an access slot is this scheduling group\n        accessedReplicasIdx.clear();\n        unsigned char retCode = 0;\n\n        switch (type) {\n        case regularRO:\n          retCode = accessReplicas(entryIt->first, 1, &accessedReplicasIdx,\n                                   accesserNode, &existingReplicasIdx,\n                                   entry->foregroundFastStruct->rOAccessTree,\n                                   pSkipSaturatedAccess);\n          break;\n\n        case regularRW:\n          retCode = accessReplicas(entryIt->first, 1, &accessedReplicasIdx,\n                                   accesserNode, &existingReplicasIdx,\n                                   entry->foregroundFastStruct->rWAccessTree,\n                                   pSkipSaturatedAccess);\n          break;\n\n        case draining:\n          retCode = accessReplicas(entryIt->first, 1, &accessedReplicasIdx,\n                                   accesserNode, &existingReplicasIdx,\n                                   entry->foregroundFastStruct->drnAccessTree,\n                                   pSkipSaturatedDrnAccess);\n          break;\n\n        default:\n          break;\n        }\n\n        if (!retCode) {\n          goto cleanup;\n        }\n\n        const string& fsGeotag =\n          (*entryIt->first->foregroundFastStruct->treeInfo)[*accessedReplicasIdx.begin()].fullGeotag;\n        unsigned geoScore = 0;\n        size_t kmax = min(accesserGeotag.length(), fsGeotag.length());\n\n        for (size_t k = 0; k < kmax; k++) {\n          if (accesserGeotag[k] != fsGeotag[k]) {\n            break;\n          }\n\n          if (accesserGeotag[k] == ':' && k + 1 < kmax && accesserGeotag[k + 1] == ':') {\n            geoScore++;\n          }\n        }\n\n        // if the box is unsaturated, give an advantage to this FS\n        if (retCode == 2) {\n          geoScore += 100;\n          eos_debug(\"%s\", \"found unsaturated fs\");\n        }\n\n        geoScore2Fs[geoScore].push_back(\n          (*entryIt->first->foregroundFastStruct->treeInfo)[*accessedReplicasIdx.begin()].fsId);\n      }\n\n      // randomly choose a fs among the highest scored ones\n      selectedFsId = geoScore2Fs.rbegin()->second[eos::common::getRandom() %\n                                              geoScore2Fs.rbegin()->second.size()];\n\n      // return the corresponding index\n      for (it = existingReplicas.cbegin(); it != existingReplicas.cend(); it++) {\n        if (*it == selectedFsId) {\n          fsIndex = static_cast<unsigned long>(std::distance(existingReplicas.cbegin(),\n                                               it));\n          break;\n        }\n      }\n\n      // check we found it\n      if (it == existingReplicas.cend()) {\n        eos_err(\"%s\",\n                \"inconsistency : unable to find the selected fs but it should be there\");\n        returnCode = EIO;\n        goto cleanup;\n      }\n    }\n\n    if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n      char buffer[1024];\n      buffer[0] = 0;\n      char* buf = buffer;\n\n      for (const auto& it : existingReplicas) {\n        buf += sprintf(buf, \"%u  \", it);\n      }\n\n      eos_debug(\"existing replicas fs id's -> %s\", buffer);\n\n      if (entry) {\n        eos_debug(\"accesser closest node to %s index -> %d / %s\",\n                  accesserGeotag.c_str(), (int)accesserNode,\n                  (*entry->foregroundFastStruct->treeInfo)[accesserNode].fullGeotag.c_str());\n      }\n\n      eos_debug(\"selected FsId -> %d / idx %d\", (int)selectedFsId, (int)fsIndex);\n    }\n  }\n\n  // Apply penalties if needed\n  if (true) {\n    std::set<eos::common::FileSystem::fsid_t>\n    setunav(unavailableFs->begin(), unavailableFs->end());\n\n    for (size_t i = 0; i < existingReplicas.size(); i++) {\n      size_t j = (fsIndex + i) % existingReplicas.size();\n      auto fs = existingReplicas[j];\n\n      // If this one is unavailable, skip it\n      if (setunav.count(fs)) {\n        continue;\n      }\n\n      if (!pFs2SchedTME.count(fs)) {\n        continue;\n      }\n\n      entry = pFs2SchedTME[fs];\n      const SchedTreeBase::tFastTreeIdx* idx;\n\n      if (entry->foregroundFastStruct->fs2TreeIdx->get(fs, idx)) {\n        const char netSpeedClass =\n          (*entry->foregroundFastStruct->treeInfo)[*idx].netSpeedClass;\n\n        // every available box will push data\n        if (entry->foregroundFastStruct->placementTree->pNodes[*idx].fsData.ulScore >=\n            pPenaltySched.pAccessUlScorePenalty[netSpeedClass]) {\n          applyUlScorePenalty(entry, *idx,\n                              pPenaltySched.pAccessUlScorePenalty[netSpeedClass]);\n        }\n\n        // every available box will have to pull data if it's a RW access (or if it's a gateway)\n        if ((type == regularRW) || (j == fsIndex && nAccessReplicas > 1)) {\n          if (entry->foregroundFastStruct->placementTree->pNodes[*idx].fsData.dlScore >=\n              pPenaltySched.pAccessDlScorePenalty[netSpeedClass]) {\n            applyDlScorePenalty(entry, *idx,\n                                pPenaltySched.pAccessDlScorePenalty[netSpeedClass]);\n          }\n        }\n      } else {\n        eos_err(\"could not find fs on the fast tree to apply penalties\");\n      }\n\n      // The gateway will also have to pull data from the\n      if (j == fsIndex && nAccessReplicas == 1) { // mainly replica layout RO case\n        break;\n      }\n    }\n  }\n\n  returnCode = accessProxyFirewall(ERIdx, entries, inode,\n                                   dataProxys, firewallEntryPoint, accesserGeotag);\n  // cleanup and exit\ncleanup:\n\n  for (auto cit = entry2FsId.begin(); cit != entry2FsId.end(); cit++) {\n    cit->first->doubleBufferMutex.UnLockRead();\n    AtomicDec(cit->first->fastStructLockWaitersCount);\n  }\n\n  return returnCode;\n}\n\nint GeoTreeEngine::accessProxyFirewall(const\n                                       std::vector<SchedTreeBase::tFastTreeIdx>& ERIdx,\n                                       const std::vector<SchedTME*>& entries,\n                                       ino64_t inode,\n                                       std::vector<std::string>* dataProxys,\n                                       std::vector<std::string>* firewallEntryPoint,\n                                       const std::string& accesserGeotag)\n{\n  if (!dataProxys && !firewallEntryPoint) {\n    return 0;\n  }\n\n  const std::string& effectiveGeotag = pProxyCloseToFs ? \"\" : accesserGeotag;\n\n  if (dataProxys) {\n    if (!findProxy(ERIdx, entries, inode, dataProxys, nullptr,\n                   effectiveGeotag, filesticky)) {\n      return ENETUNREACH;\n    }\n  }\n\n  if (firewallEntryPoint) {\n    std::vector<std::string> firewallProxyGroups(ERIdx.size());\n\n    // if there are some access geotag mapping rules, use them\n    if (pAccessGeotagMapping.inuse && pAccessProxygroup.inuse)\n      for (size_t i = 0; i < ERIdx.size(); i++) {\n        const auto& tree_Info = (*entries[i]->foregroundFastStruct->treeInfo)[ERIdx[i]];\n\n        if (accesserGeotag.empty() ||\n            accessReqFwEP(tree_Info.fullGeotag,\n                          accesserGeotag)) {\n          firewallProxyGroups[i] = accessGetProxygroup(tree_Info.fullGeotag);\n        }\n      }\n\n    if (dataProxys) {\n      *firewallEntryPoint = *dataProxys;\n    }\n\n    if (!findProxy(ERIdx, entries, inode, firewallEntryPoint, &firewallProxyGroups,\n                   effectiveGeotag, any)) {\n      return ENETUNREACH;\n    }\n  }\n\n  if (dataProxys) {\n    if (firewallEntryPoint) {\n      *dataProxys = *firewallEntryPoint;\n    }\n\n    if (!findProxy(ERIdx, entries, inode, dataProxys, nullptr,\n                   effectiveGeotag, regular)) {\n      return ENETUNREACH;\n    }\n  }\n\n  return 0;\n}\n\nvoid GeoTreeEngine::StartUpdater()\n{\n  updaterThread.reset(&GeoTreeEngine::listenFsChange, this);\n}\n\nvoid GeoTreeEngine::StopUpdater()\n{\n  updaterThread.join();\n  gUpdaterStarted = false;\n}\n\nvoid GeoTreeEngine::listenFsChange(ThreadAssistant& assistant)\n{\n  gUpdaterStarted = true;\n  ThreadAssistant::setSelfThreadName(FS_LISTENER_THREAD_NAME);\n  eos_info(\"%s\", \"msg=\\\"started GeoTreeEngine change listener\\\"\");\n  std::chrono::seconds timeout {1};\n  mq::FsChangeListener::Event event;\n  // Counter to check regularly if the collection stopwatch has expired,\n  // otherwise if there is a continuous stream of updates (and no fetch\n  // timeout happens) we risk having a stale tree view.\n  uint32_t count = 0;\n  common::IntervalStopwatch collect_stopwatch;\n\n  while (!assistant.terminationRequested()) {\n    while (sem_wait(&gUpdaterPauseSem)) {\n      if (EINTR != errno) {\n        throw \"sem_wait() failed\";\n      }\n    }\n\n    collect_stopwatch.startCycle(std::chrono::milliseconds(pTimeFrameDurationMs));\n\n    while (mFsListener->fetch(assistant, event, timeout)) {\n      eos_static_debug(\"num_pending_events=%llu\", mFsListener->GetNumPendingEvents());\n\n      if (event.isDeletion()) {\n        eos_debug(\"received deletion on subject %s : the fs was removed from \"\n                  \"the GeoTreeEngine, skipping this update\", event.fileSystemQueue.c_str());\n        continue;\n      }\n\n      eos::common::RWMutexWriteLock wr_lock(pAddRmFsMutex);\n      auto notifTypeIt = gQueue2NotifType.find(event.fileSystemQueue);\n\n      if (notifTypeIt == gQueue2NotifType.end()) {\n        eos_err(\"msg=\\\"no notification associated to queue\\\" queue=\\\"%s\\\"\",\n                event.fileSystemQueue.c_str());\n      } else {\n        eos_debug(\"msg=\\\"got notification\\\" event.queue=\\\"%s\\\" \"\n                  \"event.key=\\\"%s\\\" type=%x\", event.fileSystemQueue.c_str(),\n                  event.key.c_str(), notifTypeIt->second);\n\n        // A machine might have several roles at the same time (DataProxy and\n        // Gateway), so an update might end in multiple update maps\n        if (notifTypeIt->second & sntFilesystem) {\n          if (gNotificationsBufferFs.count(event.fileSystemQueue)) {\n            (gNotificationsBufferFs)[event.fileSystemQueue] |= gNotifKey2EnumSched.at(\n                  event.key);\n          } else {\n            (gNotificationsBufferFs)[event.fileSystemQueue] = gNotifKey2EnumSched.at(\n                  event.key);\n          }\n        }\n\n        // Check regularly if the collection stopwatch has expired\n        if (++count == 10) {\n          count = 0;\n\n          if (collect_stopwatch.timeRemainingInCycle() == std::chrono::milliseconds(0)) {\n            break;\n          }\n        }\n      }\n    }\n\n    // Do the processing\n    common::IntervalStopwatch stopwatch((std::chrono::milliseconds(\n                                           pTimeFrameDurationMs)));\n    {\n      // Do it before tree info to leave some time to the other threads\n      checkPendingDeletionsFs();\n      checkPendingDeletionsDp();\n      {\n        eos::common::RWMutexWriteLock lock(pAddRmFsMutex);\n\n        if (!gNotificationsBufferFs.empty() || !gNotificationsBufferProxy.empty()) {\n          updateTreeInfo(gNotificationsBufferFs, gNotificationsBufferProxy);\n        }\n\n        gNotificationsBufferFs.clear();\n        gNotificationsBufferProxy.clear();\n      }\n    }\n    pFrameCount++;\n\n    if (sem_post(&gUpdaterPauseSem)) {\n      throw \"sem_post() failed\";\n    }\n\n    assistant.wait_for(stopwatch.timeRemainingInCycle());\n  }\n}\n\nbool GeoTreeEngine::updateTreeInfo(SchedTME* entry,\n                                   eos::common::FileSystem::fs_snapshot_t* fs, int keys,\n                                   SchedTreeBase::tFastTreeIdx ftIdx, SlowTreeNode* stn)\n{\n  // We get a consistent set of configuration parameters per refresh of the state\n  eos::common::RWMutexReadLock lock(configMutex);\n\n  // Nothing to update\n  if ((!ftIdx && !stn) || !keys) {\n    return true;\n  }\n\n#define setOneStateVarInAllFastTrees(variable,value)                                      \\\n  {                                                                                       \\\n    entry->backgroundFastStruct->rOAccessTree->pNodes[ftIdx].fsData.variable = value;     \\\n    entry->backgroundFastStruct->rWAccessTree->pNodes[ftIdx].fsData.variable = value;     \\\n    entry->backgroundFastStruct->placementTree->pNodes[ftIdx].fsData.variable = value;    \\\n    entry->backgroundFastStruct->drnAccessTree->pNodes[ftIdx].fsData.variable = value;    \\\n    entry->backgroundFastStruct->drnPlacementTree->pNodes[ftIdx].fsData.variable = value; \\\n  }\n#define setOneStateVarStatusInAllFastTrees(flag)                                          \\\n  {                                                                                       \\\n    entry->backgroundFastStruct->rOAccessTree->pNodes[ftIdx].fsData.mStatus |= flag;      \\\n    entry->backgroundFastStruct->rWAccessTree->pNodes[ftIdx].fsData.mStatus |= flag;      \\\n    entry->backgroundFastStruct->placementTree->pNodes[ftIdx].fsData.mStatus |= flag;     \\\n    entry->backgroundFastStruct->drnAccessTree->pNodes[ftIdx].fsData.mStatus |= flag;     \\\n    entry->backgroundFastStruct->drnPlacementTree->pNodes[ftIdx].fsData.mStatus |= flag;  \\\n  }\n#define unsetOneStateVarStatusInAllFastTrees(flag)                                        \\\n  {                                                                                       \\\n    entry->backgroundFastStruct->rOAccessTree->pNodes[ftIdx].fsData.mStatus &= ~flag;     \\\n    entry->backgroundFastStruct->rWAccessTree->pNodes[ftIdx].fsData.mStatus &= ~flag;     \\\n    entry->backgroundFastStruct->placementTree->pNodes[ftIdx].fsData.mStatus &= ~flag;    \\\n    entry->backgroundFastStruct->drnAccessTree->pNodes[ftIdx].fsData.mStatus &= ~flag;    \\\n    entry->backgroundFastStruct->drnPlacementTree->pNodes[ftIdx].fsData.mStatus &= ~flag; \\\n  }\n\n  if (keys & sfgGeotag) {\n    // update the treenodeinfo\n    string newGeoTag = fs->mGeoTag;\n\n    if (newGeoTag.empty()) {\n      newGeoTag = \"nogeotag\";\n    }\n\n    FileSystem::fsid_t fsid = fs->mId;\n\n    if (!fsid) {\n      eos_err(\"%s\", \"msg=\\\"skip update for fsid=0\\\"\");\n      return false;\n    }\n\n    if (!entry->fs2SlowTreeNode.count(fsid)) {\n      eos_err(\"msg=\\\"no such slowtree node fsid=%lu\\\"\", fsid);\n      return false;\n    }\n\n    SlowTreeNode* oldNode = entry->fs2SlowTreeNode[fsid];\n    //const string &oldGeoTag = oldNode->pNodeInfo.fullGeotag;\n    string oldGeoTag = oldNode->pNodeInfo.fullGeotag;\n    oldGeoTag = (oldGeoTag.rfind(\"::\") != std::string::npos) ? oldGeoTag.substr(0,\n                oldGeoTag.rfind(\"::\")) : std::string(\"\");\n\n    //CHECK IF CHANGE ACTUALLY HAPPENED BEFORE ACTUALLY CHANGING SOMETHING\n    if (oldGeoTag != newGeoTag) {\n      // do the change only if there is one\n      SlowTreeNode* newNode = NULL;\n      newNode = entry->slowTree->moveToNewGeoTag(oldNode, newGeoTag);\n\n      if (!newNode) {\n        stringstream ss;\n        ss << (*entry->slowTree);\n        eos_err(\"error changing geotag in slowtree : move is \\\"%s\\\" => \\\"%s\\\" \"\n                \"and slowtree is \\n%s\\n\", oldGeoTag.c_str(), newGeoTag.c_str(),\n                ss.str().c_str());\n        return false;\n      }\n\n      eos_debug(\"geotag change detected : old geotag is \\\"%s\\\" new geotag is \\\"%s\\\"\",\n                oldGeoTag.c_str(), newGeoTag.c_str());\n      entry->slowTreeModified = true;\n      entry->fs2SlowTreeNode[fsid] = newNode;\n      // !!! change the argument too\n      stn = newNode;\n    }\n  }\n\n  if (keys & sfgId) {\n    // should not happen\n    // eos_crit(\"the FsId should not change once it's created:  new value\n    // is %lu\",(unsigned long)fs->mId);\n    // .... unless it is the first change to give to the id it's initial\n    // value. It happens after it's been created so it's seen as a change.\n  }\n\n  if (keys & (sfgBoot | sfgActive | sfgErrc)) {\n    BootStatus statboot = fs->mStatus;\n    unsigned int errc = fs->mErrCode;\n    ActiveStatus statactive = fs->mActiveStatus;\n    eos_debug(\"msg=\\\"fs %lu available recompute\\\" boot=%s  errcode=%d  active=%s\",\n              (unsigned long) fs->mId,\n              eos::common::FileSystem::GetStatusAsString(statboot), errc,\n              (statactive == eos::common::ActiveStatus::kOnline) ? \"online\" : \"offline\");\n\n    if ((statboot == BootStatus::kBooted) &&\n        (errc == 0) &&    // this we probably don't need\n        // This checks the heartbeat and the group & node are enabled\n        (statactive == ActiveStatus::kOnline)) {\n      // the fs is available\n      eos_debug(\"fs %lu is getting available  ftidx=%d  stn=%p\",\n                (unsigned long) fs->mId, (int)ftIdx, stn);\n\n      if (ftIdx) {\n        setOneStateVarStatusInAllFastTrees(SchedTreeBase::Available);\n      }\n\n      if (stn) {\n        stn->pNodeState.mStatus |= SchedTreeBase::Available;\n      }\n    } else {\n      // the fs is unavailable\n      eos_debug(\"fs %lu is getting unavailable ftidx=%d  stn=%p\",\n                (unsigned long) fs->mId, (int)ftIdx, stn);\n\n      if (ftIdx) {\n        unsetOneStateVarStatusInAllFastTrees(SchedTreeBase::Available);\n      }\n\n      if (stn) {\n        stn->pNodeState.mStatus &= ~SchedTreeBase::Available;\n      }\n    }\n  }\n\n  if (keys & sfgConfigstatus) {\n    common::ConfigStatus status = fs->mConfigStatus;\n\n    if (status == common::ConfigStatus::kRW) {\n      if (ftIdx) {\n        setOneStateVarStatusInAllFastTrees(SchedTreeBase::Readable |\n                                           SchedTreeBase::Writable);\n      }\n\n      if (stn) {\n        stn->pNodeState.mStatus |= (SchedTreeBase::Readable | SchedTreeBase::Writable);\n      }\n    } else if (status == common::ConfigStatus::kRO ||\n               status == common::ConfigStatus::kDrain) {\n      if (ftIdx) {\n        setOneStateVarStatusInAllFastTrees(SchedTreeBase::Readable);\n        unsetOneStateVarStatusInAllFastTrees(SchedTreeBase::Writable);\n      }\n\n      if (stn) {\n        stn->pNodeState.mStatus |= SchedTreeBase::Readable;\n        stn->pNodeState.mStatus &= ~SchedTreeBase::Writable;\n      }\n    } else if (status == common::ConfigStatus::kWO) {\n      if (ftIdx) {\n        unsetOneStateVarStatusInAllFastTrees(SchedTreeBase::Readable);\n        setOneStateVarStatusInAllFastTrees(SchedTreeBase::Writable);\n      }\n\n      if (stn) {\n        stn->pNodeState.mStatus &= ~SchedTreeBase::Readable;\n        stn->pNodeState.mStatus |= SchedTreeBase::Writable;\n      }\n    } else {\n      if (ftIdx) {\n        unsetOneStateVarStatusInAllFastTrees(SchedTreeBase::Readable);\n        unsetOneStateVarStatusInAllFastTrees(SchedTreeBase::Writable);\n      }\n\n      if (stn) {\n        stn->pNodeState.mStatus &= ~SchedTreeBase::Readable;\n        stn->pNodeState.mStatus &= ~SchedTreeBase::Writable;\n      }\n    }\n  }\n\n  if (keys & sfgDrain) {\n    DrainStatus drainStatus = fs->mDrainStatus;\n\n    if (fs->mConfigStatus == common::ConfigStatus::kDrain &&\n        drainStatus == DrainStatus::kDraining) {\n      // mark as draining\n      if (ftIdx) {\n        setOneStateVarStatusInAllFastTrees(SchedTreeBase::Draining);\n      }\n\n      if (stn) {\n        stn->pNodeState.mStatus |= SchedTreeBase::Draining;\n      }\n    } else {\n      // This covers the following cases\n      // case common::ConfigStatus::kNoDrain:\n      // case common::ConfigStatus::kDrainPrepare:\n      // case common::ConfigStatus::kDrainWait:\n      // case common::ConfigStatus::kDrainStalling:\n      // case common::ConfigStatus::kDrained:\n      // case common::ConfigStatus::kDrainExpired:\n      if (ftIdx) {\n        unsetOneStateVarStatusInAllFastTrees(SchedTreeBase::Draining);\n      }\n\n      if (stn) {\n        stn->pNodeState.mStatus &= ~SchedTreeBase::Draining;\n      }\n    }\n  }\n\n  if (keys & sfgDrainer) {\n    if (ftIdx) {\n      setOneStateVarStatusInAllFastTrees(SchedTreeBase::Drainer);\n    }\n\n    if (stn) {\n      stn->pNodeState.mStatus |= SchedTreeBase::Drainer;\n    }\n  }\n\n  if (keys & (sfgFsfilled | sfgNomfilled)) {\n    auto nominal = fs->mNominalFilled;\n    auto filled = fs->mDiskFilled;\n    bool balancing = false;\n\n    if (nominal && (filled >= nominal)) {\n      balancing = true;\n    }\n\n    if (balancing) {\n      if (ftIdx) {\n        setOneStateVarStatusInAllFastTrees(SchedTreeBase::Balancing);\n      }\n\n      if (stn) {\n        stn->pNodeState.mStatus |= SchedTreeBase::Balancing;\n      }\n    } else {\n      if (ftIdx) {\n        unsetOneStateVarStatusInAllFastTrees(SchedTreeBase::Balancing);\n      }\n\n      if (stn) {\n        stn->pNodeState.mStatus &= ~SchedTreeBase::Balancing;\n      }\n    }\n  }\n\n  if (keys & sfgBlkavailb) {\n    float ts = float(fs->mDiskBfree * (double)fs->mDiskBsize);\n    // Account also for the headroom on the fst\n    ts = ts - fs->mHeadRoom;\n\n    if (ts < 0) {\n      ts = 0;\n    }\n\n    if (ftIdx) {\n      setOneStateVarInAllFastTrees(totalSpace, ts);\n      setOneStateVarInAllFastTrees(totalWritableSpace, ts);\n    }\n\n    if (stn) {\n      stn->pNodeState.totalSpace = ts;\n      stn->pNodeState.totalWritableSpace = ts;\n    }\n  }\n\n  // <1Gb/s -> 0 ; 1Gb/s -> 1; 10Gb/s->2 ; 100Gb/s->...etc\n  size_t netSpeedClass = 0;\n\n  if ((keys & sfgPubTmStmp) && fs->mPublishTimestamp) {\n    // update the latency of this fs\n    tLatencyStats* lstat = NULL;\n\n    if (ftIdx) {\n      if (((int)((*entry->backgroundFastStruct->treeInfo)[ftIdx].fsId)) < ((\n            int)pLatencySched.pFsId2LatencyStats.size())) {\n        lstat = &pLatencySched.pFsId2LatencyStats[(*entry->backgroundFastStruct->treeInfo)[ftIdx].fsId];\n      } else {\n        eos_crit(\"trying to update latency for fs %d but latency stats vector \"\n                 \"size is %d : something is wrong\",\n                 (int)(*entry->backgroundFastStruct->treeInfo)[ftIdx].fsId,\n                 (int)pLatencySched.pFsId2LatencyStats.size());\n      }\n    } else if (stn) {\n      if ((int)(stn->pNodeInfo.fsId) < ((int)\n                                        pLatencySched.pFsId2LatencyStats.size())) {\n        lstat = &pLatencySched.pFsId2LatencyStats[stn->pNodeInfo.fsId];\n      } else {\n        eos_err(\"trying to update latency for fs %d but latency stats vector \"\n                \"size is %d : something is wrong\", (int)(stn->pNodeInfo.fsId),\n                (int)pLatencySched.pFsId2LatencyStats.size());\n      }\n    }\n\n    if (lstat) {\n      lstat->lastupdate = fs->mPublishTimestamp;\n      lstat->update();\n    }\n  }\n\n  if (keys & (sfgDiskload | sfgInratemib | sfgWopen)) {\n    // update the upload score\n    double ulScore = (1 - fs->mDiskUtilization);\n    double netoutweight = (1.0 - ((fs->mNetEthRateMiB) ? (fs->mNetOutRateMiB /\n                                  fs->mNetEthRateMiB) : 0.0));\n    ulScore *= ((netoutweight > 0) ? sqrt(netoutweight) : 0);\n\n    if (fs->mMaxDiskWopen && (fs->mDiskWopen >= fs->mMaxDiskWopen)) {\n      ulScore = 0;\n    }\n\n    if (ftIdx) {\n      setOneStateVarInAllFastTrees(ulScore, (char)(ulScore * 100));\n    }\n\n    if (stn) {\n      stn->pNodeState.ulScore = ulScore * 100;\n    }\n  }\n\n  if (keys & (sfgOutratemib | sfgDiskload | sfgReadratemb | sfgRopen)) {\n    double dlScore = (1 - fs->mDiskUtilization);\n    double netinweight = (1.0 - ((fs->mNetEthRateMiB) ? (fs->mNetInRateMiB /\n                                 fs->mNetEthRateMiB) : 0.0));\n    dlScore *= ((netinweight > 0) ? sqrt(netinweight) : 0);\n\n    if (fs->mMaxDiskRopen && (fs->mDiskRopen >= fs->mMaxDiskRopen)) {\n      dlScore = 0;\n    }\n\n    if (ftIdx) {\n      setOneStateVarInAllFastTrees(dlScore, (char)(dlScore * 100));\n    }\n\n    if (stn) {\n      stn->pNodeState.dlScore = dlScore * 100;\n    }\n  }\n\n  if (keys & (sfgDiskload | sfgInratemib | sfgOutratemib | sfgEthmib)) {\n    netSpeedClass = round(log10(fs->mNetEthRateMiB * 8 * 1024 * 1024 + 1));\n    // netSpeedClass 1 means 1Gbps\n    netSpeedClass = netSpeedClass > 8 ? netSpeedClass - 8 : 0;\n\n    // check if netspeed class needs an update\n    if (entry->backgroundFastStruct->treeInfo->size() >= netSpeedClass + 1 &&\n        (*entry->backgroundFastStruct->treeInfo)[ftIdx].netSpeedClass !=\n        (unsigned char)netSpeedClass) {\n      if (ftIdx) {\n        (*entry->backgroundFastStruct->treeInfo)[ftIdx].netSpeedClass = netSpeedClass;\n      }\n\n      if (stn) {\n        stn->pNodeInfo.netSpeedClass = netSpeedClass;\n      }\n    }\n\n    // This one will create the entry if it doesnt exists already\n    nodeAgreg& na = pPenaltySched.pUpdatingNodes[fs->mHostPort];\n    na.fsCount++;\n\n    if (!na.saturated) {\n      if (na.fsCount == 1) {\n        na.netSpeedClass = netSpeedClass;\n        pPenaltySched.pMaxNetSpeedClass = std::max(pPenaltySched.pMaxNetSpeedClass,\n                                          netSpeedClass);\n        na.netOutWeight += (1.0 - ((fs->mNetEthRateMiB) ? (fs->mNetOutRateMiB /\n                                   fs->mNetEthRateMiB) : 0.0));\n        na.netInWeight += (1.0 - ((fs->mNetEthRateMiB) ? (fs->mNetInRateMiB /\n                                  fs->mNetEthRateMiB) : 0.0));\n\n        if (na.netOutWeight < 0.1 || na.netInWeight < 0.1) {\n          na.saturated = true;  // network of the box is saturated\n        }\n      }\n\n      na.rOpen += fs->mDiskRopen;\n      na.wOpen += fs->mDiskWopen;\n      na.diskUtilSum += fs->mDiskUtilization;\n\n      if (fs->mDiskUtilization > 0.9) {\n        na.saturated = true;  // one of the disks of the box is saturated\n      }\n    }\n\n    // apply penalties that are still valid on fast trees\n    if (ftIdx) {\n      recallScorePenalty(entry, ftIdx);\n    }\n\n    // in case the fs in not in the fast trees , it has not been\n    // used recently to schedule , so there is no penalty to recall!\n    // so there is nothing like if(stn) recallScorePenalty(entry, stn);\n  }\n\n  if (keys & sfgFsfilled) {\n    if (ftIdx) {\n      setOneStateVarInAllFastTrees(fillRatio, (char)fs->mDiskFilled);\n    }\n\n    if (stn) {\n      stn->pNodeState.fillRatio = (char)fs->mDiskFilled;\n    }\n  }\n\n  // SHOULD WE TAKE THE NOMINAL FILLING AS SET BY THE BALANCING?\n  //  if(keys&(sfgNomfilled)) {\n  //    fs->\n  //  }\n  return true;\n#undef setOneStateVarInAllFastTrees\n#undef setOneStateVarStatusInAllFastTrees\n#undef unsetOneStateVarStatusInAllFastTrees\n}\n\nbool GeoTreeEngine::updateTreeInfo(const std::map<std::string, int>& updatesFs,\n                                   const std::map<std::string, int>& updatesDp)\n{\n  // copy the foreground FastStructures to the BackGround FastStructures\n  // so that the penalties applied after the placement/access are kept by defaut\n  // (and overwritten if a new state is received from the fs)\n  // => SCHEDULING\n  pTreeMapMutex.LockRead();\n\n  for (auto it = pGroup2SchedTME.begin(); it != pGroup2SchedTME.end(); it++) {\n    SchedTME* entry = it->second;\n    RWMutexReadLock lock(entry->slowTreeMutex);\n\n    if (!entry->foregroundFastStruct->DeepCopyTo(entry->backgroundFastStruct)) {\n      eos_crit(\"error deep copying in double buffering\");\n      pTreeMapMutex.UnLockRead();\n      return false;\n    }\n\n    // Copy the penalties of the last frame from each group and reset the\n    // penalties counter in the fast trees.\n    auto& pVec = pPenaltySched.pCircFrCnt2FsPenalties[pFrameCount % pCircSize];\n\n    for (auto it2 = entry->foregroundFastStruct->fs2TreeIdx->begin();\n         it2 != entry->foregroundFastStruct->fs2TreeIdx->end(); it2++) {\n      auto cur = *it2;\n      pVec[cur.first] = (*entry->foregroundFastStruct->penalties)[cur.second];\n      AtomicCAS((*entry->foregroundFastStruct->penalties)[cur.second].dlScorePenalty,\n                (*entry->foregroundFastStruct->penalties)[cur.second].dlScorePenalty, (char)0);\n      AtomicCAS((*entry->foregroundFastStruct->penalties)[cur.second].ulScorePenalty,\n                (*entry->foregroundFastStruct->penalties)[cur.second].ulScorePenalty, (char)0);\n    }\n  }\n\n  pTreeMapMutex.UnLockRead();\n  // => PROXYGROUPS\n  pPxyTreeMapMutex.LockRead();\n\n  for (auto it = pPxyGrp2DpTME.begin(); it != pPxyGrp2DpTME.end(); it++) {\n    DataProxyTME* entry = it->second;\n    RWMutexReadLock lock(entry->slowTreeMutex);\n\n    if (!entry->foregroundFastStruct->DeepCopyTo(entry->backgroundFastStruct)) {\n      eos_crit(\"error deep copying in double buffering\");\n      pPxyTreeMapMutex.UnLockRead();\n      return false;\n    }\n\n    // Copy the penalties of the last frame from each group and reset the\n    // penalties counter in the fast trees.\n    auto& pMap = pPenaltySched.pCircFrCnt2HostPenalties[pFrameCount % pCircSize];\n\n    for (auto it2 = entry->foregroundFastStruct->host2TreeIdx->begin();\n         it2 != entry->foregroundFastStruct->host2TreeIdx->end(); it2++) {\n      auto cur = *it2;\n      pMap[cur.first] = (*entry->foregroundFastStruct->penalties)[cur.second];\n      AtomicCAS((*entry->foregroundFastStruct->penalties)[cur.second].dlScorePenalty,\n                (*entry->foregroundFastStruct->penalties)[cur.second].dlScorePenalty, (char)0);\n      AtomicCAS((*entry->foregroundFastStruct->penalties)[cur.second].ulScorePenalty,\n                (*entry->foregroundFastStruct->penalties)[cur.second].ulScorePenalty, (char)0);\n    }\n  }\n\n  pPxyTreeMapMutex.UnLockRead();\n  // timestamp the current frame\n  {\n    struct timeval curtime;\n    gettimeofday(&curtime, 0);\n    pLatencySched.pCircFrCnt2Timestamp[pFrameCount % pCircSize] = ((\n          size_t)curtime.tv_sec) * 1000 + ((size_t)curtime.tv_usec) / 1000;\n  }\n  pPenaltySched.pUpdatingNodes.clear();\n  pPenaltySched.pMaxNetSpeedClass = 0;\n\n  // => SCHED\n  for (auto it = updatesFs.begin(); it != updatesFs.end(); ++it) {\n    pTreeMapMutex.LockRead();\n    eos::common::FileSystem* filesystem = FsView::gFsView.mIdView.lookupByQueuePath(\n                                            it->first);\n\n    if (!filesystem) {\n      eos_err(\"update : Invalid FileSystem Entry, skipping this update\");\n      pTreeMapMutex.UnLockRead();\n      continue;\n    }\n\n    eos::common::FileSystem::fs_snapshot_t fs;\n    filesystem->SnapShotFileSystem(fs, true);\n    FileSystem::fsid_t fsid = fs.mId;\n\n    if (!pFs2SchedTME.count(fsid)) {\n      eos_err(\"update : TreeEntryMap has been removed, skipping this update\");\n      pTreeMapMutex.UnLockRead();\n      continue;\n    }\n\n    SchedTME* entry = pFs2SchedTME[fsid];\n    AtomicInc(entry->fastStructLockWaitersCount);\n    pTreeMapMutex.UnLockRead();\n    eos_debug(\"CHANGE BITFIELD %s => %x\", it->first.c_str(), it->second);\n    // Update only the fast structures because even if a fast structure rebuild\n    // is needed from the slow tree. Its information and state is updated from\n    // the fast structures.\n    entry->slowTreeMutex.LockWrite();\n    entry->doubleBufferMutex.LockRead();\n    const SchedTreeBase::tFastTreeIdx* idx = NULL;\n    SlowTreeNode* node = NULL;\n\n    if (!entry->backgroundFastStruct->fs2TreeIdx->get(fsid, idx)) {\n      auto nodeit = entry->fs2SlowTreeNode.find(fsid);\n\n      if (nodeit == entry->fs2SlowTreeNode.end()) {\n        eos_crit(\"Inconsistency : cannot locate an fs %lu supposed to be in \"\n                 \"the fast structures\", (unsigned long)fsid);\n        entry->doubleBufferMutex.UnLockRead();\n        entry->slowTreeMutex.UnLockWrite();\n        AtomicDec(entry->fastStructLockWaitersCount);\n        return false;\n      }\n\n      node = nodeit->second;\n      eos_debug(\"no fast tree for fs %lu : updating slowtree\", (unsigned long)fsid);\n    } else {\n      eos_debug(\"fast tree available for fs %lu : not updating slowtree\",\n                (unsigned long)fsid);\n    }\n\n    updateTreeInfo(entry, &fs, it->second, idx ? *idx : 0, node);\n\n    if (idx) {\n      entry->fastStructModified = true;\n    }\n\n    if (node) {\n      entry->slowTreeModified = true;\n    }\n\n    // if we update the slowtree, then a fast tree generation is already pending\n    entry->doubleBufferMutex.UnLockRead();\n    entry->slowTreeMutex.UnLockWrite();\n    AtomicDec(entry->fastStructLockWaitersCount);\n  }\n\n  // Update the atomic penalties\n  updateAtomicPenalties();\n  // Update the trees that need to be updated (could maybe optimized by\n  // updating only the branch needing, might be worth it if only 1 or 2\n  // branches are updated). Self update for the fast structure if update\n  // from slow tree is not needed. If convert from slowtree is needed,\n  // update the slowtree from the fast for the info and for the state\n  // => SCHED\n  pTreeMapMutex.LockRead();\n\n  for (auto it = pGroup2SchedTME.begin(); it != pGroup2SchedTME.end(); it++) {\n    SchedTME* entry = it->second;\n    RWMutexReadLock lock(entry->slowTreeMutex);\n\n    if (!updateFastStructures(entry)) {\n      pTreeMapMutex.UnLockRead();\n      eos_err(\"error updating the tree\");\n      return false;\n    }\n  }\n\n  pTreeMapMutex.UnLockRead();\n  return true;\n}\n\nbool GeoTreeEngine::getInfosFromFsIds(const std::vector<FileSystem::fsid_t>&\n                                      fsids, std::vector<std::string>* fsgeotags,\n                                      std::vector<std::string>* hosts,\n                                      std::vector<FsGroup*>* sortedgroups)\n{\n  bool result = true;\n\n  if (fsgeotags) {\n    fsgeotags->reserve(fsids.size());\n  }\n\n  if (sortedgroups) {\n    sortedgroups->reserve(fsids.size());\n  }\n\n  std::map<FsGroup*, size_t> group2idx;\n  std::vector<std::pair<size_t, size_t> > groupcount;\n  groupcount.reserve(fsids.size());\n  {\n    RWMutexReadLock lock(this->pTreeMapMutex);\n\n    for (auto it = fsids.begin(); it != fsids.end(); ++ it) {\n      if (pFs2SchedTME.count(*it)) {\n        FsGroup* group = pFs2SchedTME[*it]->group;\n\n        if (fsgeotags || hosts) {\n          const SchedTreeBase::tFastTreeIdx* idx = NULL;\n\n          if (pFs2SchedTME[*it]->foregroundFastStruct->fs2TreeIdx->get(*it, idx)) {\n            if (fsgeotags) fsgeotags->push_back(\n                (*pFs2SchedTME[*it]->foregroundFastStruct->treeInfo)[*idx].fullGeotag\n              );\n\n            if (hosts) hosts->push_back(\n                (*pFs2SchedTME[*it]->foregroundFastStruct->treeInfo)[*idx].host\n              );\n          } else {\n            if (fsgeotags) {\n              fsgeotags->push_back(\"\");\n            }\n\n            if (hosts) {\n              hosts->push_back(\"\");\n            }\n          }\n        }\n\n        if (sortedgroups) {\n          if (!group2idx.count(group)) {\n            group2idx[group] = group2idx.size();\n            sortedgroups->push_back(group);\n            groupcount.push_back(make_pair(1, groupcount.size()));\n          } else {\n            size_t idx = group2idx[group];\n            groupcount[idx].first++;\n          }\n        }\n      } else {\n        // put an empty entry in the result vector to preserve the indexing\n        if (fsgeotags) {\n          fsgeotags->push_back(\"\");\n        }\n\n        if (hosts) {\n          hosts->push_back(\"\");\n        }\n\n        // to signal that one of the fsids was not mapped to a group\n        result = false;\n      }\n    }\n  }\n\n  if (sortedgroups) {\n    // sort the count vector in ascending order to get the permutation\n    std::sort(groupcount.begin(), groupcount.end(),\n              std::greater<std::pair<size_t, size_t>>());\n    // apply the permutation\n    std::vector<FsGroup*> final(groupcount.size());\n    size_t count = 0;\n\n    for (auto it = groupcount.begin(); it != groupcount.end(); it++) {\n      final[count++] = (*sortedgroups)[it->second];\n    }\n\n    *sortedgroups = final;\n  }\n\n  return result;\n}\n\nvoid GeoTreeEngine::updateAtomicPenalties()\n{\n  // In this function, we compute a rough a simplified version\n  // of the penalties applied to selected fs for placement and access.\n  // there is only one penalty and it's copied to ulplct, dlplct, ulaccess and dlaccess\n  // variants.\n\n  // if the update is enabled\n  if (pPenaltyUpdateRate) {\n    if (pPenaltySched.pUpdatingNodes.empty()) {\n      //eos_debug(\"updatingNodes is empty!\");\n    } else {\n      // each networking speed class has its own variables\n      std::vector<double>\n      ropen(pPenaltySched.pMaxNetSpeedClass + 1, 0.0),\n            wopen(pPenaltySched.pMaxNetSpeedClass + 1, 0.0),\n            ulload(pPenaltySched.pMaxNetSpeedClass + 1, 0.0),\n            dlload(pPenaltySched.pMaxNetSpeedClass + 1, 0.0),\n            fscount(pPenaltySched.pMaxNetSpeedClass + 1, 0.0),\n            hostcount(pPenaltySched.pMaxNetSpeedClass + 1, 0.0),\n            diskutil(pPenaltySched.pMaxNetSpeedClass + 1, 0.0);\n\n      // we use the view to check that we have all the fs in a node\n      // could be removed if we were sure to run a single on fst daemon / box\n\n      // WARNING: see below / FsView::gFsView.ViewMutex.LockRead();\n      for (auto it = pPenaltySched.pUpdatingNodes.begin();\n           it != pPenaltySched.pUpdatingNodes.end(); it++) {\n        const std::string& nodestr = it->first;\n\n        // ===============\n        // WARNING: the following part is commented out because it can create a\n        // deadlock with FsViewMutex/pAddRmFsMutex in the above FsViewMutex lock\n        // when inserting/removing a filesystem. It can be fixed but it's not\n        // trivial. Because it's not needed in operation, we don't fix it for now.\n        // When using several fst daemons on the same host, it could give\n        // overestimated atomic penalties when they are selfestimated\n        // ===============\n        /*\n        FsNode *node = NULL;\n        if(FsView::gFsView.mNodeView.count(nodestr))\n        node = FsView::gFsView.mNodeView[nodestr];\n        else\n        {\n          std::stringstream ss;\n          ss.str(\"\");\n          for (auto it2 = FsView::gFsView.mNodeView.begin();\n               it2 != FsView::gFsView.mNodeView.end(); it2++) {\n            ss << it2->first << \"  \";\n          }\n          eos_err(\"Inconsistency : cannot find updating node %s in %s\",\n                   nodestr.c_str(),ss.str().c_str());\n          continue;\n        }\n        if((!it->second.saturated) && it->second.fsCount == node->size())\n        */\n        // ===============\n        if ((!it->second.saturated)) {\n          // eos_debug(\"aggregated opened files for %s: wopen %d, ropen %d,\n          //            outweight %lf, inweight %lf\", it->first.c_str(),\n          //            it->second.wOpen, it->second.rOpen,\n          //            it->second.netOutWeight, it->second.netInWeight);\n          // Update aggregated informations for the right networking class\n          // (take into account only unsaturated boxes)\n          ropen[it->second.netSpeedClass] += (it->second.rOpen);\n          wopen[it->second.netSpeedClass] += (it->second.wOpen);\n          ulload[it->second.netSpeedClass] += (1.0 - it->second.netOutWeight);\n          dlload[it->second.netSpeedClass] += (1.0 - it->second.netInWeight);\n          diskutil[it->second.netSpeedClass] += it->second.diskUtilSum;\n          fscount[it->second.netSpeedClass] += it->second.fsCount;\n          hostcount[it->second.netSpeedClass]++;\n        } else {\n          // The fs/host is saturated, we don't use the whole host in the estimate\n          eos_debug(\"fs update in node %s : box is saturated\", nodestr.c_str());\n          continue;\n          // Could force to get everything\n          // long long wopen = node->SumLongLong(\"stat.wopen\",false);\n          // long long ropen = node->SumLongLong(\"stat.ropen\",false);\n        }\n      }\n\n      // WARNING: see above / FsView::gFsView.ViewMutex.UnLockRead();\n      for (size_t netSpeedClass = 0; netSpeedClass <= pPenaltySched.pMaxNetSpeedClass;\n           netSpeedClass++) {\n        if (ropen[netSpeedClass] + wopen[netSpeedClass] > 4) {\n          eos_debug(\"UPDATE netSpeedClass=%d, ulload=%lf, dlload=%lf, \"\n                    \"diskutil=%lf, ropen=%lf, wopen=%lf  fscount=%lf, \"\n                    \"hostcount=%lf\", (int)netSpeedClass, ulload[netSpeedClass],\n                    dlload[netSpeedClass], diskutil[netSpeedClass],\n                    ropen[netSpeedClass], wopen[netSpeedClass],\n                    fscount[netSpeedClass], hostcount[netSpeedClass]);\n          // The penalty aims at knowing roughly how many concurrent file\n          // operations can be done on a single fs before sturating a ressource\n          // (disk or network)\n          // network penalty per file = the multiplication by the number of fs\n          // is to take into account that the bw is shared between multiple fs\n          double avgnetload = 0.5 * (ulload[netSpeedClass] + dlload[netSpeedClass]) /\n                              (ropen[netSpeedClass] + wopen[netSpeedClass]);\n          double networkpenSched = avgnetload * (fscount[netSpeedClass] /\n                                                 hostcount[netSpeedClass]);\n          double networkpenGw    = avgnetload;\n//          double networkpen =\n//          0.5*(ulload[netSpeedClass]+dlload[netSpeedClass])/(ropen[netSpeedClass]+wopen[netSpeedClass])\n//          *(fscount[netSpeedClass]/hostcount[netSpeedClass]);\n          // there is factor to take into account the read cache\n          // TODO use a realistic value for this factor\n          double diskpen =\n            diskutil[netSpeedClass] / (0.4 * ropen[netSpeedClass] + wopen[netSpeedClass]);\n          eos_debug(\"penalties updates for scheduling are network %lf   disk %lf\",\n                    networkpenSched, diskpen);\n          eos_debug(\"penalties updates for gateway/dataproxy are network %lf\",\n                    networkpenGw, diskpen);\n          double updateSched = 100 * std::max(diskpen, networkpenSched);\n          double updateGw = 100 * networkpenGw;\n\n          if (updateSched < 1 || updateSched > 99) { // could be more restrictive\n            eos_debug(\"weird value for accessDlScorePenalty update : %lf. Not \"\n                      \"using this one.\", updateSched);\n          } else {\n            eos_debug(\"netSpeedClass %d : using update values %lf for penalties \"\n                      \"with weight %f%%\", netSpeedClass, pPenaltyUpdateRate);\n            eos_debug(\"netSpeedClass %d : values before update are \"\n                      \"accessDlScorePenalty=%f, plctDlScorePenalty=%f, \"\n                      \"accessUlScorePenalty=%f, plctUlScorePenalty=%f\",\n                      netSpeedClass, pPenaltySched.pAccessDlScorePenaltyF[netSpeedClass],\n                      pPenaltySched.pPlctDlScorePenaltyF[netSpeedClass],\n                      pPenaltySched.pAccessUlScorePenaltyF[netSpeedClass],\n                      pPenaltySched.pPlctUlScorePenaltyF[netSpeedClass]);\n            union {\n              float f;\n              uint32_t u;\n            } uf;\n            // Atomic change, no need to lock anything\n            uf.f = 0.01 * ((100 - pPenaltyUpdateRate) *\n                           pPenaltySched.pAccessDlScorePenaltyF[netSpeedClass] +\n                           pPenaltyUpdateRate * updateSched);\n            AtomicCAS(reinterpret_cast<uint32_t&>\n                      (pPenaltySched.pAccessDlScorePenaltyF[netSpeedClass]),\n                      reinterpret_cast<uint32_t&>(pPenaltySched.pAccessDlScorePenaltyF[netSpeedClass])\n                      , uf.u);\n            uf.f = 0.01 * ((100 - pPenaltyUpdateRate) *\n                           pPenaltySched.pPlctDlScorePenaltyF[netSpeedClass] +\n                           pPenaltyUpdateRate * updateSched);\n            AtomicCAS(reinterpret_cast<uint32_t&>\n                      (pPenaltySched.pPlctDlScorePenaltyF[netSpeedClass]),\n                      reinterpret_cast<uint32_t&>(pPenaltySched.pPlctDlScorePenaltyF[netSpeedClass]),\n                      uf.u);\n            uf.f = 0.01 * ((100 - pPenaltyUpdateRate) *\n                           pPenaltySched.pAccessUlScorePenaltyF[netSpeedClass] +\n                           pPenaltyUpdateRate * updateSched);\n            AtomicCAS(reinterpret_cast<uint32_t&>\n                      (pPenaltySched.pAccessUlScorePenaltyF[netSpeedClass]),\n                      reinterpret_cast<uint32_t&>(pPenaltySched.pAccessUlScorePenaltyF[netSpeedClass])\n                      , uf.u);\n            uf.f = 0.01 * ((100 - pPenaltyUpdateRate) *\n                           pPenaltySched.pPlctUlScorePenaltyF[netSpeedClass] +\n                           pPenaltyUpdateRate * updateSched);\n            AtomicCAS(reinterpret_cast<uint32_t&>\n                      (pPenaltySched.pPlctUlScorePenaltyF[netSpeedClass]),\n                      reinterpret_cast<uint32_t&>(pPenaltySched.pPlctUlScorePenaltyF[netSpeedClass]),\n                      uf.u);\n            uf.f = 0.01 * ((100 - pPenaltyUpdateRate) *\n                           pPenaltySched.pProxyScorePenaltyF[netSpeedClass] +\n                           pPenaltyUpdateRate * updateGw);\n            AtomicCAS(reinterpret_cast<uint32_t&>\n                      (pPenaltySched.pProxyScorePenaltyF[netSpeedClass]),\n                      reinterpret_cast<uint32_t&>(pPenaltySched.pProxyScorePenaltyF[netSpeedClass]),\n                      uf.u);\n            eos_debug(\"netSpeedClass %d : values after update are \"\n                      \"accessDlScorePenalty=%f, plctDlScorePenalty=%f, \"\n                      \"accessUlScorePenalty=%f, plctUlScorePenalty=%f, \"\n                      \"gwScorePenalty=%f\", netSpeedClass,\n                      pPenaltySched.pAccessDlScorePenaltyF[netSpeedClass],\n                      pPenaltySched.pPlctDlScorePenaltyF[netSpeedClass],\n                      pPenaltySched.pAccessUlScorePenaltyF[netSpeedClass],\n                      pPenaltySched.pPlctUlScorePenaltyF[netSpeedClass],\n                      pPenaltySched.pProxyScorePenaltyF[netSpeedClass]);\n            // Update the casted versions too\n            AtomicCAS(pPenaltySched.pPlctUlScorePenalty[netSpeedClass],\n                      pPenaltySched.pPlctUlScorePenalty[netSpeedClass],\n                      (SchedTreeBase::tFastTreeIdx)\n                      pPenaltySched.pPlctUlScorePenaltyF[netSpeedClass]);\n            AtomicCAS(pPenaltySched.pPlctDlScorePenalty[netSpeedClass],\n                      pPenaltySched.pPlctDlScorePenalty[netSpeedClass],\n                      (SchedTreeBase::tFastTreeIdx)\n                      pPenaltySched.pPlctDlScorePenaltyF[netSpeedClass]);\n            AtomicCAS(pPenaltySched.pAccessDlScorePenalty[netSpeedClass],\n                      pPenaltySched.pAccessDlScorePenalty[netSpeedClass],\n                      (SchedTreeBase::tFastTreeIdx)\n                      pPenaltySched.pAccessDlScorePenaltyF[netSpeedClass]);\n            AtomicCAS(pPenaltySched.pAccessUlScorePenalty[netSpeedClass],\n                      pPenaltySched.pAccessUlScorePenalty[netSpeedClass],\n                      (SchedTreeBase::tFastTreeIdx)\n                      pPenaltySched.pAccessUlScorePenaltyF[netSpeedClass]);\n            AtomicCAS(pPenaltySched.pProxyScorePenalty[netSpeedClass],\n                      pPenaltySched.pProxyScorePenalty[netSpeedClass],\n                      (SchedTreeBase::tFastTreeIdx) pPenaltySched.pProxyScorePenaltyF[netSpeedClass]);\n          }\n        } else {\n          eos_debug(\"not enough file opened to get reliable statistics %d\",\n                    (int)(ropen[netSpeedClass] + wopen[netSpeedClass]));\n        }\n      }\n    }\n  }\n}\n\nbool GeoTreeEngine::setSkipSaturatedAccess(bool value, bool setconfig)\n{\n  return setInternalParam(pSkipSaturatedAccess, (int)value, false,\n                          setconfig ? \"skipsaturatedaccess\" : \"\");\n}\nbool GeoTreeEngine::setSkipSaturatedDrnAccess(bool value, bool setconfig)\n{\n  return setInternalParam(pSkipSaturatedDrnAccess, (int)value, false,\n                          setconfig ? \"skipsaturateddrnaccess\" : \"\");\n}\nbool GeoTreeEngine::setSkipSaturatedBlcAccess(bool value, bool setconfig)\n{\n  return setInternalParam(pSkipSaturatedBlcAccess, (int)value, false,\n                          setconfig ? \"skipsaturatedblcaccess\" : \"\");\n}\n\nbool GeoTreeEngine::setProxyCloseToFs(bool value, bool setconfig)\n{\n  return setInternalParam(pProxyCloseToFs, (int)value, false,\n                          setconfig ? \"proxyclosetofs\" : \"\");\n}\n\nbool GeoTreeEngine::setScorePenalty(std::vector<float>& fvector,\n                                    std::vector<char>& cvector,\n                                    const std::vector<char>& vvalue,\n                                    const std::string& configentry)\n{\n  if (vvalue.size() != 8) {\n    return false;\n  }\n\n  std::vector<float> valuef(8);\n\n  for (int i = 0; i < 8; i++) {\n    valuef[i] = vvalue[i];\n  }\n\n  return setInternalParam(fvector, valuef, false, \"\")\n         && setInternalParam(cvector, vvalue, false, configentry);\n}\n\nbool GeoTreeEngine::setScorePenalty(std::vector<float>& fvector,\n                                    std::vector<char>& cvector,\n                                    const char* svalue,\n                                    const std::string& configentry)\n{\n  std::vector<double> dvvalue(8);\n  std::vector<char> vvalue(8);\n\n  if (sscanf(svalue, \"[%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf]\", &dvvalue[0],\n             &dvvalue[1], &dvvalue[2], &dvvalue[3], &dvvalue[4], &dvvalue[5], &dvvalue[6],\n             &dvvalue[7]) != 8) {\n    return false;\n  }\n\n  for (int i = 0; i < 8; i++) {\n    vvalue[i] = (char)dvvalue[i];\n  }\n\n  return setScorePenalty(fvector, cvector, vvalue, configentry);\n}\n\nbool GeoTreeEngine::setScorePenalty(std::vector<float>& fvector,\n                                    std::vector<char>& cvector,\n                                    char value, int netSpeedClass,\n                                    const std::string& configentry)\n{\n  if (netSpeedClass >= 0) {\n    if (netSpeedClass >= (int)fvector.size()) {\n      return false;\n    }\n\n//    return setInternalParam(fvector[netSpeedClass],(float)value,false,\"\")\n//    && setInternalParam(cvector[netSpeedClass],value,false,configentry);\n    std::vector<char> vvalue(cvector);\n    vvalue[netSpeedClass] = value;\n    return setScorePenalty(fvector, cvector, vvalue, configentry);\n  } else if (netSpeedClass == -1) {\n    std::vector<char> vvalue(8, value);\n    return setScorePenalty(fvector, cvector, vvalue, configentry);\n  }\n\n  return false;\n}\n\nbool GeoTreeEngine::setPlctDlScorePenalty(char value, int netSpeedClass,\n    bool setconfig)\n{\n  return setScorePenalty(pPenaltySched.pPlctDlScorePenaltyF,\n                         pPenaltySched.pPlctDlScorePenalty, value, netSpeedClass,\n                         setconfig ? \"plctdlscorepenalty\" : \"\");\n}\nbool GeoTreeEngine::setPlctUlScorePenalty(char value, int netSpeedClass,\n    bool setconfig)\n{\n  return setScorePenalty(pPenaltySched.pPlctUlScorePenaltyF,\n                         pPenaltySched.pPlctUlScorePenalty, value, netSpeedClass,\n                         setconfig ? \"plctulscorepenalty\" : \"\");\n}\nbool GeoTreeEngine::setAccessDlScorePenalty(char value, int netSpeedClass,\n    bool setconfig)\n{\n  return setScorePenalty(pPenaltySched.pAccessDlScorePenaltyF,\n                         pPenaltySched.pAccessDlScorePenalty, value, netSpeedClass,\n                         setconfig ? \"accessdlscorepenalty\" : \"\");\n}\nbool GeoTreeEngine::setAccessUlScorePenalty(char value, int netSpeedClass,\n    bool setconfig)\n{\n  return setScorePenalty(pPenaltySched.pAccessUlScorePenaltyF,\n                         pPenaltySched.pAccessUlScorePenalty, value, netSpeedClass,\n                         setconfig ? \"accessulscorepenalty\" : \"\");\n}\nbool GeoTreeEngine::setProxyScorePenalty(char value, int netSpeedClass,\n    bool setconfig)\n{\n  return setScorePenalty(pPenaltySched.pProxyScorePenaltyF,\n                         pPenaltySched.pProxyScorePenalty, value, netSpeedClass,\n                         setconfig ? \"gwscorepenalty\" : \"\");\n}\n\nbool GeoTreeEngine::setPlctDlScorePenalty(const char* value, bool setconfig)\n{\n  return setScorePenalty(pPenaltySched.pPlctDlScorePenaltyF,\n                         pPenaltySched.pPlctDlScorePenalty, value,\n                         setconfig ? \"plctdlscorepenalty\" : \"\");\n}\nbool GeoTreeEngine::setPlctUlScorePenalty(const char* value, bool setconfig)\n{\n  return setScorePenalty(pPenaltySched.pPlctUlScorePenaltyF,\n                         pPenaltySched.pPlctUlScorePenalty, value,\n                         setconfig ? \"plctulscorepenalty\" : \"\");\n}\nbool GeoTreeEngine::setAccessDlScorePenalty(const char* value, bool setconfig)\n{\n  return setScorePenalty(pPenaltySched.pAccessDlScorePenaltyF,\n                         pPenaltySched.pAccessDlScorePenalty, value,\n                         setconfig ? \"accessdlscorepenalty\" : \"\");\n}\nbool GeoTreeEngine::setAccessUlScorePenalty(const char* value, bool setconfig)\n{\n  return setScorePenalty(pPenaltySched.pAccessUlScorePenaltyF,\n                         pPenaltySched.pAccessUlScorePenalty, value,\n                         setconfig ? \"accessulscorepenalty\" : \"\");\n}\nbool GeoTreeEngine::setProxyScorePenalty(const char* value, bool setconfig)\n{\n  return setScorePenalty(pPenaltySched.pProxyScorePenaltyF,\n                         pPenaltySched.pProxyScorePenalty, value,\n                         setconfig ? \"gwscorepenalty\" : \"\");\n}\n\nbool GeoTreeEngine::setFillRatioLimit(char value, bool setconfig)\n{\n  return setInternalParam(pFillRatioLimit, value, true,\n                          setconfig ? \"fillratiolimit\" : \"\");\n}\nbool GeoTreeEngine::setFillRatioCompTol(char value, bool setconfig)\n{\n  return setInternalParam(pFillRatioCompTol, value, true,\n                          setconfig ? \"fillratiocomptol\" : \"\");\n}\nbool GeoTreeEngine::setSaturationThres(char value, bool setconfig)\n{\n  return setInternalParam(pSaturationThres, value, true,\n                          setconfig ? \"saturationthres\" : \"\");\n}\nbool GeoTreeEngine::setTimeFrameDurationMs(int value, bool setconfig)\n{\n  return setInternalParam(pTimeFrameDurationMs, value, false,\n                          setconfig ? \"timeframedurationms\" : \"\");\n}\nbool GeoTreeEngine::setPenaltyUpdateRate(float value, bool setconfig)\n{\n  return setInternalParam(pPenaltyUpdateRate, value, false,\n                          setconfig ? \"penaltyupdaterate\" : \"\");\n}\n\nbool GeoTreeEngine::setParameter(std::string param, const std::string& value,\n                                 int iparamidx, bool setconfig)\n{\n  std::transform(param.begin(), param.end(), param.begin(), ::tolower);\n  double dval = 0.0;\n  (void) sscanf(value.c_str(), \"%lf\", &dval);\n  int ival = (int)dval;\n  bool ok = false;\n#define readParamVFromString(PARAM,VALUE) {                                    \\\n    std::string q;                                                             \\\n    if(sscanf(VALUE.c_str(),\"[%f,%f,%f,%f,%f,%f,%f,%f]\",                       \\\n              &PARAM##F[0],&PARAM##F[1],&PARAM##F[2],&PARAM##F[3],&PARAM##F[4],\\\n              &PARAM##F[5],&PARAM##F[6],&PARAM##F[7])!=8) return false;        \\\n    for(int i=0;i<8;i++)                                                       \\\n      PARAM[i]=(char)PARAM##F[i];                                              \\\n    ok = true;}\n\n  if (param == \"timeframedurationms\") {\n    ok = this->setTimeFrameDurationMs(ival, setconfig);\n  } else if (param == \"saturationthres\") {\n    ok = this->setSaturationThres((char)ival, setconfig);\n  } else if (param == \"fillratiocomptol\") {\n    ok = this->setFillRatioCompTol((char)ival, setconfig);\n  } else if (param == \"fillratiolimit\") {\n    ok = this->setFillRatioLimit((char)ival, setconfig);\n  } else if (param == \"accessulscorepenalty\") {\n    if (iparamidx > -2) {\n      ok = this->setAccessUlScorePenalty((char)ival, iparamidx, setconfig);\n    } else {\n      readParamVFromString(pPenaltySched.pAccessUlScorePenalty, value);\n    }\n  } else if (param == \"accessdlscorepenalty\") {\n    if (iparamidx > -2) {\n      ok = this->setAccessDlScorePenalty((char)ival, iparamidx, setconfig);\n    } else {\n      readParamVFromString(pPenaltySched.pAccessDlScorePenalty, value);\n    }\n  } else if (param == \"plctulscorepenalty\") {\n    if (iparamidx > -2) {\n      ok = this->setPlctUlScorePenalty((char)ival, iparamidx, setconfig);\n    } else {\n      readParamVFromString(pPenaltySched.pPlctUlScorePenalty, value);\n    }\n  } else if (param == \"plctdlscorepenalty\") {\n    if (iparamidx > -2) {\n      ok = this->setPlctDlScorePenalty((char)ival, iparamidx, setconfig);\n    } else {\n      readParamVFromString(pPenaltySched.pPlctDlScorePenalty, value);\n    }\n  } else if (param == \"gwscorepenalty\") {\n    if (iparamidx > -2) {\n      ok = this->setProxyScorePenalty((char)ival, iparamidx, setconfig);\n    } else {\n      readParamVFromString(pPenaltySched.pProxyScorePenalty, value);\n    }\n  } else if (param == \"skipsaturatedblcaccess\") {\n    ok = this->setSkipSaturatedBlcAccess((bool)ival, setconfig);\n  } else if (param == \"skipsaturateddrnaccess\") {\n    ok = this->setSkipSaturatedDrnAccess((bool)ival, setconfig);\n  } else if (param == \"skipsaturatedaccess\") {\n    ok = this->setSkipSaturatedAccess((bool)ival, setconfig);\n  } else if (param == \"penaltyupdaterate\") {\n    ok = this->setPenaltyUpdateRate((float)dval, setconfig);\n  } else if (param == \"disabledbranches\") {\n    ok = true;\n\n    if (value.size() > 4) {\n      // first, clear the list of disabled branches\n      this->rmDisabledBranch(\"*\", \"*\", \"*\", NULL);\n      // remove leading and trailing square brackets\n      string list(value.substr(2, value.size() - 4));\n      // from the end to avoid reallocation of the string\n      size_t idxl, idxr;\n\n      while ((idxr = list.rfind(')')) != std::string::npos && ok) {\n        idxl = list.rfind('(');\n        auto comidx = list.find(',', idxl);\n        string geotag(list.substr(idxl + 1, comidx - idxl - 1));\n        auto comidx2 = list.find(',', comidx + 1);\n        string optype(list.substr(comidx + 1, comidx2 - comidx - 1));\n        string group(list.substr(comidx2 + 1, idxr - comidx2 - 1));\n        ok = ok && this->addDisabledBranch(group, optype, geotag, NULL, setconfig);\n        list.erase(idxl, std::string::npos);\n      }\n    }\n  } else if (param == \"proxyclosetofs\") {\n    ok = this->setProxyCloseToFs((bool)ival, setconfig);\n  } else if (param == \"accessgeotagmapping\") {\n    ok = this->setAccessGeotagMapping(value, setconfig);\n  } else if (param == \"accessproxygroup\") {\n    ok = this->setAccessProxygroup(value, setconfig);\n  }\n\n  return ok;\n}\n\nvoid GeoTreeEngine::setConfigValue(const char* prefix,\n                                   const char* key,\n                                   const char* val)\n{\n  gOFS->mConfigEngine->SetConfigValue(prefix, key, val);\n}\n\nbool GeoTreeEngine::markPendingBranchDisablings(const std::string& group,\n    const std::string& optype, const std::string& geotag)\n{\n  for (auto git = pGroup2SchedTME.begin(); git != pGroup2SchedTME.end(); git++) {\n    RWMutexReadLock lock(git->second->doubleBufferMutex);\n\n    if (group == \"*\" || git->first->mName == group) {\n      git->second->slowTreeModified = true;\n    }\n  }\n\n  return true;\n}\n\nbool GeoTreeEngine::applyBranchDisablings(const SchedTME& entry)\n{\n  for (auto mit = pDisabledBranches.begin(); mit != pDisabledBranches.end();\n       mit++) {\n    // should I lock configMutex or is it already locked?\n    const std::string& group(mit->first);\n\n    if (group != \"*\" && entry.group->mName != group) {\n      continue;\n    }\n\n    for (auto oit = mit->second.begin(); oit != mit->second.end(); oit++) {\n      const std::string& optype(oit->first);\n\n      for (auto geoit = oit->second.begin(); geoit != oit->second.end(); geoit++) {\n        const std::string& geotag(*geoit);\n        auto idx = entry.backgroundFastStruct->tag2NodeIdx->getClosestFastTreeNode(\n                     geotag.c_str());\n\n        // check there is an exact geotag match\n        if ((*entry.backgroundFastStruct->treeInfo)[idx].fullGeotag != geotag) {\n          continue;\n        }\n\n        if (optype == \"*\" || optype == \"plct\") {\n          entry.backgroundFastStruct->placementTree->disableSubTree(idx);\n        }\n\n        if (optype == \"*\" || optype == \"accsro\") {\n          entry.backgroundFastStruct->rOAccessTree->disableSubTree(idx);\n        }\n\n        if (optype == \"*\" || optype == \"accsrw\") {\n          entry.backgroundFastStruct->rWAccessTree->disableSubTree(idx);\n        }\n\n        if (optype == \"*\" || optype == \"plctdrain\") {\n          entry.backgroundFastStruct->drnPlacementTree->disableSubTree(idx);\n        }\n\n        if (optype == \"*\" || optype == \"accsdrain\") {\n          entry.backgroundFastStruct->drnAccessTree->disableSubTree(idx);\n        }\n      }\n    }\n  }\n\n  return true;\n}\n\nbool GeoTreeEngine::addDisabledBranch(const std::string& group,\n                                      const std::string& optype,\n                                      const std::string& geotag,\n                                      XrdOucString* output, bool toConfig)\n{\n  eos::common::RWMutexWriteLock lock(pAddRmFsMutex);\n  eos::common::RWMutexWriteLock lock2(pTreeMapMutex);\n  eos::common::RWMutexWriteLock lock3(configMutex);\n  std::vector<std::string> intersection;\n  // Do checks - go through the potentially intersecting groups\n  auto git_begin = group == \"*\" ? pDisabledBranches.begin() :\n                   pDisabledBranches.find(group);\n  auto git_end = (group == \"*\" ? pDisabledBranches.end() :\n                  pDisabledBranches.find(group));\n\n  if (git_end != pDisabledBranches.end()) {\n    git_end++;\n  }\n\n  for (auto git = git_begin; git != git_end; git++) {\n    // go through the potentially intersecting optypes\n    auto oit_begin = (optype == \"*\" ? git->second.begin() : git->second.find(\n                        group));\n    auto oit_end = (optype == \"*\" ? git->second.end() : git->second.find(group));\n\n    if (oit_end != git->second.end()) {\n      oit_end++;\n    }\n\n    for (auto oit = oit_begin; oit != oit_end; oit++) {\n      XrdOucString toinsert(geotag.c_str());\n\n      // Check that none of the disabled geotag is a prefix of the current one\n      // and the other way around.\n      for (auto geoit = oit->second.begin(); geoit != oit->second.end(); geoit++) {\n        XrdOucString alreadyThere(geoit->c_str());\n\n        if (alreadyThere.beginswith(toinsert) || toinsert.beginswith(alreadyThere)) {\n          intersection.push_back(std::string(\"(\") + geotag.c_str() + std::string(\",\") +\n                                 oit->first + std::string(\",\") + git->first +\n                                 std::string(\")\") + std::string(alreadyThere.c_str()));\n        }\n      }\n    }\n  }\n\n  if (intersection.size()) {\n    if (output) {\n      output->append((std::string(\"unable to add disabled branch : \") +\n                      std::string(\"(\") + geotag + std::string(\",\") + optype +\n                      std::string(\",\") + geotag +\n                      std::string(\") clashes with : \")).c_str());\n\n      for (auto iit = intersection.begin(); iit != intersection.end(); iit++) {\n        output->append((*iit + \" , \").c_str());\n      }\n    }\n\n    return false;\n  }\n\n  // Update the internal value\n  pDisabledBranches[group][optype].insert(geotag);\n  // To apply the new set of rules, mark the involved slow trees as modified to force a refresh\n  markPendingBranchDisablings(group, optype, geotag);\n\n  // update the config\n  if (toConfig) {\n    XrdOucString outStr(\"[ \");\n    showDisabledBranches(\"*\", \"*\", \"*\", &outStr, false);\n    outStr.replace(\")\\n(\", \") , (\");\n    outStr.replace(\")\\n\", \")\");\n    outStr += \" ]\";\n    setConfigValue(\"geosched\", \"disabledbranches\", outStr.c_str());\n  }\n\n  return true;\n}\n\nbool GeoTreeEngine::rmDisabledBranch(const std::string& group,\n                                     const std::string& optype,\n                                     const std::string& geotag,\n                                     XrdOucString* output, bool toConfig)\n{\n  eos::common::RWMutexWriteLock lock(pAddRmFsMutex);\n  eos::common::RWMutexWriteLock lock2(pTreeMapMutex);\n  eos::common::RWMutexWriteLock lock3(configMutex);\n  bool found = false;\n\n  if (group == \"*\" && optype == \"*\" && geotag == \"*\") {\n    found = true;\n    eos_notice(\"clearing disabled branch list in GeoTreeEngine\");\n    pDisabledBranches.clear();\n  } else if (pDisabledBranches.count(group)) {\n    if (pDisabledBranches[group].count(optype)) {\n      found = (bool)pDisabledBranches[group][optype].erase(geotag);\n    }\n  }\n\n  if (!found) {\n    if (output) output->append((std::string(\"could not find disabled branch : \") +\n                                  std::string(\"(\") + group + std::string(\" , \") +\n                                  optype + std::string(\") -> \") + geotag).c_str());\n  } else {\n    // To apply the new set of rules, mark the involved slow trees as modified\n    // to force a refresh.\n    markPendingBranchDisablings(group, optype, geotag);\n\n    if (toConfig) {\n      // Update the config\n      XrdOucString outStr(\"[ \");\n      showDisabledBranches(\"*\", \"*\", \"*\", &outStr, false);\n      outStr.replace(\")\\n(\", \") , (\");\n      outStr.replace(\")\\n\", \")\");\n      outStr += \" ]\";\n      setConfigValue(\"geosched\", \"disabledbranches\", outStr.c_str());\n    }\n  }\n\n  return found;\n}\n\nbool\nGeoTreeEngine::showDisabledBranches(const std::string& group,\n                                    const std::string& optype,\n                                    const std::string& geotag,\n                                    XrdOucString* output, bool lock)\n{\n  if (lock) {\n    configMutex.LockRead();\n  }\n\n  for (auto git = pDisabledBranches.begin(); git != pDisabledBranches.end();\n       git++) {\n    if (group == \"*\" || group == git->first)\n      for (auto oit = git->second.begin(); oit != git->second.end(); oit++) {\n        if (optype == \"*\" || optype == oit->first)\n          for (auto geoit = oit->second.begin(); geoit != oit->second.end(); geoit++) {\n            if (geotag == \"*\" || geotag == *geoit)\n              if (output) {\n                output->append((std::string(\"(\") + *geoit + std::string(\",\") + oit->first +\n                                std::string(\",\") + git->first + std::string(\")\\n\")).c_str());\n              }\n          }\n      }\n  }\n\n  if (lock) {\n    configMutex.UnLockRead();\n  }\n\n  return true;\n}\n\nstd::string GeoTreeEngine::AccessStruct::getMappingStr() const\n{\n  std::string ret;\n\n  for (auto it = accessGeotagMap.begin(); it != accessGeotagMap.end() ; it++) {\n    if (it != accessGeotagMap.begin()) {\n      ret.append(\";\");\n    }\n\n    ret.append(it->first);\n    ret.append(\"=>\");\n    ret.append(it->second);\n  }\n\n  return ret;\n}\n\nbool GeoTreeEngine::AccessStruct::setMapping(const std::string& mapping,\n    bool setconfig)\n{\n  std::string mappingelement, geotag, geotaglist;\n  std::stringstream ss(mapping);\n\n  while (std::getline(ss, mappingelement, ';')) {\n    auto idx = mappingelement.find(\"=>\");\n\n    if (idx == std::string::npos) {\n      eos_static_err(\"error parsing config entry while restoring config : %s\",\n                     mappingelement.c_str());\n      return false;\n    }\n\n    geotag = mappingelement.substr(0, idx);\n    geotaglist = mappingelement.substr(idx + 2, std::string::npos);\n    setMapping(geotag, geotaglist, false, false);\n  }\n\n  if (!geotag.empty()) {\n    return setMapping(geotag, geotaglist, true,\n                      setconfig);  // to rebuild the tree and set the config\n  } else {\n    return true;\n  }\n}\n\nbool GeoTreeEngine::AccessStruct::setMapping(const std::string& geotag,\n    const std::string& geotaglist, bool updateFastStruct, bool setconfig)\n{\n  RWMutexWriteLock lock(accessMutex);\n\n  if (!inuse) {\n    accessST = new SlowTree(\"AccessGeotagMapping\");\n    accessFT = new FastGatewayAccessTree();\n    accessFT->selfAllocate(FastGatewayAccessTree::sGetMaxNodeCount());\n    accessFTI = new SchedTreeBase::FastTreeInfo();\n    accessFTI->reserve(FastGatewayAccessTree::sGetMaxNodeCount());\n    accessHost2Idx = new Host2TreeIdxMap();\n    accessHost2Idx->selfAllocate(FastGatewayAccessTree::sGetMaxNodeCount());\n    accessTag2Idx = new GeoTag2NodeIdxMap();\n    accessTag2Idx->selfAllocate(FastGatewayAccessTree::sGetMaxNodeCount());;\n    inuse = true;\n  }\n\n  SlowTree::TreeNodeInfo tni;\n  SlowTree::TreeNodeStateFloat tns;\n  tni.geotag = geotag;\n  tni.proxygroup = geotaglist;\n  accessST->insert(&tni, &tns, false, true);\n  accessGeotagMap[geotag] = geotaglist;\n\n  if (updateFastStruct) {\n    accessST->buildFastStrcturesAccess(accessFT, accessHost2Idx, accessFTI,\n                                       accessTag2Idx);\n  }\n\n  if (setconfig) {\n    setConfigValue(\"geosched\", configkey.c_str(), getMappingStr().c_str());\n  }\n\n  return true;\n}\n\nbool GeoTreeEngine::AccessStruct::clearMapping(const std::string& geotag,\n    bool updateFastStruct, bool setconfig)\n{\n  RWMutexWriteLock lock(accessMutex);\n\n  if (inuse) {\n    SlowTree::TreeNodeInfo tni;\n    tni.geotag = geotag;\n\n    // if we have a geotag, we remove that geotag\n    if (!geotag.empty() && !accessST->remove(&tni, false)) {\n      return false;\n    }\n\n    if (!geotag.empty()) {\n      accessGeotagMap.erase(geotag);\n    }\n\n    // if we don't have a geotag or if the tree is now empty, remove everything\n    if (geotag.empty() || accessST->getNodeCount() == 1) {\n      delete accessST;\n      delete accessFT;\n      delete accessFTI;\n      delete accessHost2Idx;\n      delete accessTag2Idx;\n      accessGeotagMap.clear();\n      inuse = false;\n    } else if (updateFastStruct) {\n      accessST->buildFastStrcturesAccess(accessFT, accessHost2Idx, accessFTI,\n                                         accessTag2Idx);\n    }\n  }\n\n  if (setconfig) {\n    setConfigValue(\"geosched\", configkey.c_str(), getMappingStr().c_str());\n  }\n\n  return true;\n}\n\nbool GeoTreeEngine::AccessStruct::showMapping(XrdOucString* output,\n    std::string operation,\n    bool monitoring)\n{\n  RWMutexReadLock lock(accessMutex);\n\n  if (inuse) {\n    unsigned geo_depth_max = 0;\n    std::string format_s = !monitoring ? \"s\" : \"os\";\n    std::string format_ss = !monitoring ? \"-s\" : \"os\";\n    // Set for tree: num of line, depth, prefix_1, prefix_2, fullGeotag, proxygroup/direct\n    std::set<std::tuple<unsigned, unsigned, unsigned, unsigned, std::string, std::string>>\n        data_access;\n    accessST->displayAccess(data_access, geo_depth_max);\n    TableFormatterBase table_access;\n    TableHeader table_header;\n    table_header.push_back(std::make_tuple(\"operation\", 6, format_ss));\n    table_header.push_back(std::make_tuple(\"geotag\", 6, format_ss));\n\n    if (!monitoring) {\n      if (geo_depth_max > 1) {\n        for (unsigned i = 1; i < geo_depth_max; i++) {\n          std::string name = \"lev\" + std::to_string(i);\n          table_header.push_back(std::make_tuple(name, 4, format_ss));\n        }\n      }\n\n      table_header.push_back(std::make_tuple(\"fullGeotag\", 6, format_s));\n    }\n\n    table_header.push_back(std::make_tuple(\"mapping\", 6, format_s));\n    table_access.SetHeader(table_header);\n    unsigned prefix[geo_depth_max + 1];\n\n    for (auto it : data_access) {\n      if (!monitoring) {\n        unsigned geo_depth = 0;\n        std::string geotag_temp = std::get<4>(it);\n\n        while (geotag_temp.find(\"::\") != std::string::npos) {\n          geotag_temp.erase(0, geotag_temp.find(\"::\") + 2);\n          geo_depth++;\n        }\n\n        TableData table_data;\n        table_data.emplace_back();\n\n        // Print operation (depth=1)\n        if (std::get<1>(it) == 1) {\n          table_data.back().push_back(TableCell(operation, \"s\"));\n        }\n        // Print geotag (depth=2 or 3)\n        else if (std::get<1>(it) == 2 || std::get<1>(it) == 3) {\n          if (geo_depth > 0) {\n            prefix[geo_depth - 1] = std::get<2>(it);\n          }\n\n          prefix[geo_depth] = std::get<3>(it);\n\n          for (unsigned i = 0; i <= geo_depth; i++) { // arrows\n            table_data.back().push_back(TableCell(prefix[i], \"t\"));\n          }\n\n          std::string geotag = std::get<4>(it);\n          geotag = (geo_depth > 0) ? geotag.substr(geotag.rfind(\"::\") + 2) : geotag;\n          table_data.back().push_back(TableCell(geotag, \"s\"));\n\n          for (unsigned i = 0; i < geo_depth_max - geo_depth - 1;\n               i++) { // blank cell after geotag\n            table_data.back().push_back(TableCell(\"\", \"s\"));\n          }\n        }\n\n        // Print other columns\n        if (!std::get<5>(it).empty()) {\n          table_data.back().push_back(TableCell(std::get<4>(it), \"s\"));\n          table_data.back().push_back(TableCell(std::get<5>(it), \"s\"));\n        }\n\n        table_access.AddRows(table_data);\n      }\n      // Monitoring\n      else if (!std::get<5>(it).empty()) {\n        TableData table_data;\n        table_data.emplace_back();\n        table_data.back().push_back(TableCell(operation, \"s\"));\n        table_data.back().push_back(TableCell(std::get<4>(it), \"s\"));\n        table_data.back().push_back(TableCell(std::get<5>(it), \"s\"));\n        table_access.AddRows(table_data);\n      }\n    }\n\n    output->append(table_access.GenerateTable(HEADER).c_str());\n    return true;\n  }\n\n  return false;\n}\n\nbool GeoTreeEngine::accessReqFwEP(const std::string& targetGeotag,\n                                  const std::string& accesserGeotag) const\n{\n  // if no direct access geotag mapping is defined, all accesses are direct\n  if (!pAccessGeotagMapping.inuse) {\n    return false;\n  }\n\n  // first get the parent node giving the access rule\n  auto idx  = pAccessGeotagMapping.accessTag2Idx->getClosestFastTreeNode(\n                accesserGeotag.c_str());\n  SchedTreeBase::tFastTreeIdx idx2 = 0;\n  pAccessGeotagMapping.accessFT->findFreeSlotFirstHitBack(idx2, idx);\n  // parse the geotag list and check the access\n  auto accessible = (*pAccessGeotagMapping.accessFTI)[idx2].proxygroup;\n  size_t beg = std::numeric_limits<size_t>::max(),\n         end = std::numeric_limits<size_t>::max();\n\n  for (size_t i = 0; i < accessible.size(); i++) {\n    if (accessible[i] == ',') {\n      if (beg == std::numeric_limits<size_t>::max()) {\n        continue;\n      }\n\n      end = i;\n\n      // if we have a new token\n      if (end > beg) {\n        if (((end - beg) <= targetGeotag.size()\n             && ((end - beg) == targetGeotag.size() || targetGeotag[end - beg] == ':'))\n            && !strncmp(targetGeotag.c_str(), accessible.c_str() + beg, end - beg)) {\n          return false;\n        }\n\n        beg = end + 1;\n      }\n    } else if (beg == std::numeric_limits<size_t>::max()) {\n      beg = i;\n    }\n  }\n\n  // the end of the string is also the end of the last token\n  if (beg < accessible.size()) {\n    end = accessible.size();\n  }\n\n  if (end > beg) {\n    if (((end - beg) <= targetGeotag.size()\n         && ((end - beg) == targetGeotag.size() || targetGeotag[end - beg] == ':'))\n        && !strncmp(targetGeotag.c_str(), accessible.c_str() + beg, end - beg)) {\n      return false;\n    }\n  }\n\n  return true;\n}\nstd::string GeoTreeEngine::accessGetProxygroup(const std::string& toAccess)\nconst\n{\n  // if no access proxygroup mapping is defined, there is no proxygroup to return\n  if (!pAccessProxygroup.inuse) {\n    return \"\";\n  }\n\n  // first get the parent node giving the proxygroup\n  auto idx  = pAccessProxygroup.accessTag2Idx->getClosestFastTreeNode(\n                toAccess.c_str());\n  SchedTreeBase::tFastTreeIdx idx2 = 0;\n  pAccessProxygroup.accessFT->findFreeSlotFirstHitBack(idx2, idx);\n  return (*pAccessProxygroup.accessFTI)[idx2].proxygroup;\n}\n\n\nvoid GeoTreeEngine::tlFree(void* arg)\n{\n  eos_static_debug(\"destroying thread specific geobuffer\");\n  // delete the buffer\n  delete[](char*)arg;\n}\n\nchar* GeoTreeEngine::tlAlloc(size_t size)\n{\n  eos_static_debug(\"allocating thread specific geobuffer\");\n  char* buf = new char[size];\n\n  if (pthread_setspecific(gPthreadKey, buf)) {\n    eos_static_crit(\"error registering thread-local buffer located at %p for \"\n                    \"cleaning up : memory will be leaked when thread is \"\n                    \"terminated\", buf);\n  }\n\n  return buf;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/geotreeengine/GeoTreeEngine.hh",
    "content": "// ----------------------------------------------------------------------\n// File: GeoTreeEngine.hh\n// Author: Geoffray Adde - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_GEOTREEENGINE__HH__\n#define __EOSMGM_GEOTREEENGINE__HH__\n\n// THIS IS EXPERIMENTAL AND DOES NOT REALLY WORK\n// FOR FUTURE WORK\n#define HAVE_ATOMICS 1\n\n/*----------------------------------------------------------------------------*/\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/geotree/SchedulingSlowTree.hh\"\n#include \"common/table_formatter/TableFormatterBase.hh\"\n#include \"common/Timing.hh\"\n#include \"common/FileSystem.hh\"\n#include \"mq/FsChangeListener.hh\"\n#include \"mq/MessagingRealm.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdSys/XrdSysAtomics.hh>\n/*----------------------------------------------------------------------------*/\n#include <list>\n#include <dirent.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file GeoTreeEngine.hh\n *\n * @brief Class responsible to handle %GeoTree Operations\n * (file placement for new replica, source finding for balancing and draining)\n *\n * # Overview\n *\n * The GeoTreeEngine is the EOS software component in charge of doing the file scheduling\n * operations for file/replica access and placement based on the so called GeoTrees.\n * For an overview of the configuration of the geoscheduling please read doc/configuration/geoscheduling.rst\n * For an overview of the configuration of the proxy/proxygroup please read doc/configuration/proxys.rst\n * They are certainly good preliminary readings to this cover document.\n *\n * ## Geotags and GeoTrees\n *\n * Geotags are strings of the form <TAG1>::<TAG2>::...::<TAGN> where <..> are alphanumerical strings.\n * A collection of geotags can easily be represented in a tree structure where the first tokens are closer\n * to the root of the tree and where the last tokens are farther from the root.\n * Such trees are implemented into two types of structure.\n * - Trees (also refered as SlowTree s) : they are common trees with nodes pointing to each other.\n * They are flexible and allow adding, removing, moving subtrees easily but they are slow to browse because\n * of their inefficient memory layout.\n * - Snapshots (also refered as FastTree s) : they are fast structures designed for optimal memory access.\n * They are much faster that SlowTrees (10X) but their shape and structure is fixed at creation time.\n * They come along with auxiliary structures to speed up various lookup types (they are grouped using GeoTreeEngine::FastStructSched).\n * The snapshots host the state of the FileSystem s and use them to carry out scheduling operations efficiently.\n *\n * ## General architecture and sub-components\n * The GeoTreeEngine hosts several subcomponents in charge of several sub-tasks in the global scheduling operations.\n *\n * ### File scheduling\n * The file scheduling is the step where is decided on which FileSystem the file/replicas are placed/accessed.\n * To do so, each FsGroup is mapped to two GeoTreeEngine::FastStructSched structures hosting the SlowTree and the FastTree structures (plus the auxiliary structures).\n * For each FsGroup, there is a foreground GeoTreeEngine::FastStructSched used by current scheduling operations and a background one allowing the updater to\n * run without interering with the scheduling operations (GeoTreeEngine#pGroup2SchedTME).\n * The GeoTreeEngine::FastStructSched structures is associated to one SlowTree and features multiple FastTree structures. There is one FastTree structure per type of operation.\n * A type of operation is the combination of a GeoTreeEngine::SchedType and of 'access' or 'placement'.\n * Note that only the FastTree structures are used by the thread performing the scheduling operations.\n * Note also that those threads create there own working copies and work on them. It avoids any exclusive lock to be needed.\n * File scheduling uses the penalty subsystem and the updater to keep its structures up-to-date.\n *\n * ### Proxy scheduling\n * The proxy scheduling is the step where is decided which FsNode will be used to proxy the reading of the data from the file system\n * or to go through the firewall. This step is optional and is performed only when necessary.\n * Its architecture is derived straight from the architecture of File scheduling.\n * There are a few differences though. Trees are populated with FsNode s rather than FileSystem s.\n * Each proxygroup is mapped (GeoTreeEngine#pGroup2ProxyTME) to two GeotreeEngine::FastStructProxy (foreground and background).\n * There are no type of operation. Hence there is only one FastTree per GeotreeEngine::FastStructProxy.\n * Proxy scheduling does not use the penalty subsystem but it does use the updater to keep its structures up-to-date.\n *\n * ### Firewall entrypoint scheduling\n * The Firewall entrypoint scheduling is a specific type of proxyscheduling. It has a preliminary step that regular proxy scheduling does not have.\n * This step uses GeoTreeEngine::AccessStruct structure GeoTreeEngine::pAccessGeotagMapping to check whether going through a firewall entrypoint is required.\n * It is basically a GeoTree that stores on each node the list of GeoTag it is allowed to access directly.\n * It then uses GeoTreeEngine::AccessStruct structure GeoTreeEngine::pAccessProxygroup to check which proxygroup should be use to select such a firewall entrypoint.\n * Then the Proxy scheduling machinery is used.\n * This one is basically a GeoTree that stores on each node the name of the proxygroup to use to find a firewall entrypoint.\n * Note that GeoTreeEngine::AccessStruct does not have this foreground/background split as the changes are rare and the thread carrying out scheduling operations\n * access those structure in RO. Hence, there are mutexes in those structures. These structures are not updated by the updater because they are just a mapping information without any state to be updated.\n *\n * ### Trees/Snapshots updater\n * The updater is run as a background thread.\n * This component listens to relevant changes from the XrdMqSharedObjectChangeNotifier (GeoTreeEngine::listenFsChange).\n * It stores the notifications in the maps GeoTreeEngine#gNotificationsBufferProxy and GeoTreeEngine#gNotificationsBufferFs.\n * Every GeoTreeEngine#pTimeFrameDurationMs, the changes are commited to the background tree structures (GeoTreeEngine::updateTreeInfo) in the following way.\n * - if a change is about a fs/node that was present before the last refresh, the change is committed to the right fast structures\n * - if a change is about a fs/node that has been added since the last refresh, it is commited to the SlowTree.\n *\n * If any change was made to the SlowTree (add/remove fs/proxy, geotag change), GeoTreeEngine::FastStructSched/GeotreeEngine::FastStructProxy are then regenerated fom the SlowTree.\n * Once the whole refresh is done pointers to foreground and background structures are swapped.\n *\n *\n * ### Penalty subsystem\n * The penalty subsystem was introduced to fight a potentially harmful corner-case. Bursts of access/placement requests.\n * Without such a mecanism, the GeoTreeEngine would not update its view until the next refresh of the trees and that could lead to heavily saturating some FileSystem/FsNode.\n * If a burst was issued by one client, for many files/replicas on a few FileSystem s, the GeoTreeEngine would consider the state of those FileSystem s at the last refresh.\n * It would then schedule all these accesses to the closest FileSystem to the client without refreshing its view of their state.\n * Then, it would not schedule the next access to another FileSystem to distribute the burden.\n *\n * The penalty subsystem GeoTreeEngine::PenaltySubSys avoids such a behavior by amending the state of the filesystems in the foreground GeoTreeEngine::FastStructSched.\n * Penalties are atomic quantities that are substracted from the download/upload score of the fiesystems/proxy.\n * To get an understanding of this subsystem, several parts are worth considering:\n * - Latency Estimation (in GeoTreeEngine::updateTreeInfo): Latency estimation is crucial in such a subsystem. The latency is the average time between a change in the state of a remote FileSystem/FsNode and the time, it is actually reflected in the scheduling system.\n * To keep the view of the system in sync, this time should match the lifetime that penalties should have so that by the time the GeoTreeEngine sees the effect of a scheduling decision on the remote state, the penalty is removed.\n * The GeoTreeEngine::LatencySubSys is in charge of the estimation of the latency.\n * - Penalty Estimation (in GeoTreeEngine::updateTreeInfo): GeoTreeEngine::PenaltySubSys is in charge of estimating penalties. GeoTreeEngine#pPenaltyUpdateRate is an important parameter that tells how reactive is the estimation.\n * Value 0 means that the estimated values remain stuck at the initial value. It's the way to not using penalty estimation. Value 1 means that a completely new value is calculated only from the last time window.\n * - Atomic Amending: This is the crucial part where atomic penalties are substratced to reflect the additional burden that scheduling decision just being taken puts on a FileSystem.\n * This is carried ou in \\ref GeoTreeEngine::FastStructSched::applyUlScorePenalty\n * and GeoTreeEngine::FastStructSched::applyDlScorePenalty.\n * Please note that it is done without using any mutex just by using atomic substractions.\n * It was designed like this to leave all the scheduling threads free of interactions/contentions between each other.\n * It is made so to the expense of possibly losing a few updates when the background and foreground are swapped (this should not lead to any segv though).\n * This is not a big issue because the penalty subsystem is not meant to be extremely precise. Only the order of magnitude matters.\n *\n * Note that the penalty subsystem is used only for file scheduling as hard drives don't like many concurrent accesses.\n * It is not used for proxy scheduling, as the limiting ressource there is network.\n *\n * ## Outline of a scheduling operation\n * The scheduling operations are carried out by the threads serving the clients. We give here a schematic overview of bith placement and access operations\n * Note that the new scheduling system does NOT place replicas accross groups. An FsGroup is considered as scheduling unit on its own.\n *\n * ### %Access\n * The main function is GeoTreeEngine::accessHeadReplicaMultipleGroup.\n * It is called like that and its complex mainly because it has to be able to deal with data placement accross several FsGroup.\n *\n *\n *\n * ### Placement\n *\n * # Integration\n * The GeoTreeEngine is strongly bound to several other components in EOS, mainly to keep its internal state consistent and updated.\n * Intrgration with the other components of EOS is made through the use of the \\link GeoTreeEngine::GeoTreeEngine public member functions \\endlink.\n *\n * ## Fs/Hosts Listenning\n * The heartbeat now has a timestamp that allows estimation of the latency.\n * A new class XrdMqSharedObjectChangeNotifier now dispatches shared object change notifications to threads that subcribed. The updater thread is subscribed for only the updates it needs to receveive.\n * The function GeoTreeEngine::listenFsChange() processes the notifications for the updater.\n *\n * ## Consistency with the FsView\n * Adding and removing FileSystem s and FsGroup s to/from the GeoTreeEngine is hooked in Adding/Removing FileSystem s from the FsView.\n * It then enforces a strict consitency between the FsGroup s and FileSystem s as viewed and by the FsView and as viewed by the GeoTreeEngine.\n *\n * ## Consistency with the Proxygroups definition\n * \"Proxygroups\" (the set of proxygroups a node belongs to) are defined as being config attributes of the Nodes. The view of the GeoTreeEngine over proxys is kept up-to-date by the XrdMgmOfs::FsConfigListener() function\n * that calls GeoTreeEngine::matchHostPxyGr everytime that a proxygroups value change is notified.\n * Note that \"proxygroup\" (the one proxygroup in charge of proxying io to a given FileSystem) is defined as a FileSystem config attribute and is as such tracked by the updater.\n *\n * ## Configuration\n * All the configuration of the GeoTreeEngine is stored via the ConfigEngine in the config files /var/eos/config/...\n * The configuration settings of the GeoTreeEngine are stored in the ConfigEngine only when they differ from the default values.\n * No state information is kept there.\n * The structure of the Scheduling trees are not stored neither. Structures are reconstructed at boot time as things get added to the FsView and as FileSystem/Node config changes are intercepted.\n * The geotag mapping for direct access and the entrypoint proxygroups are part of the configuration.\n *\n * ## Boot sequence\n * The GeoTreeEngine is created in the beginning of the configure stage if the XrdMgmOfs plugin.\n * At the end of the configure stage, a GeoTreeEngine::forceRefresh() is issued as some changes in the config entries of Nodes might have been missed before the notification listener is properly started.\n * This is especially true when using proxygroups.\n *\n * # Locking Scheme\n * The locking schema is not very straightforward. It has been designed to minimize locking contention for the threads serving the clients by issuing the scheduling operations.\n *\n * # Memory Management\n * Low level FastTree structures and their auxiliary structures are designed to use as few dynamic allocation as possible.\n * In upper layers inside the GeoTreeEngine, a more common use of dynamic objects is done.\n *\n * ## Thread-local buffers\n * Threads serving clients have a thread-local buffer GeoTreeEngine#tlGeoBuffer to store their working copies of FastTree s.\n * This is a rather large buffer and it's allocated only once when it's first used for each thread. It's freed when the thread is destroyed.\n *\n * # Configuration parameters\n * The GeoTreeEngine can be configured in many ways. As explained earlier, the configuration of the GeoTreeEngine is saved by the ConfigEngine.\n * \\link GeoTreeEngine::configMutex Those data members \\endlink  are the configuration parameters and govern how the GeoTreeEngine behaves.\n *\n */\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Get the maximum number of placement attempts\n//------------------------------------------------------------------------------\nunsigned int GetMaxPlacementAttempts();\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Class responsible to handle GeoTree Operations\n *\n */\n/*----------------------------------------------------------------------------*/\nclass GeoTreeEngine : public eos::common::LogId\n{\n//**********************************************************\n// BEGIN INTERNAL DATA STRUCTURES\n//**********************************************************\n  struct Penalties {\n    char\n    dlScorePenalty, ulScorePenalty;\n\n    Penalties() :\n      dlScorePenalty(0), ulScorePenalty(0)\n    {}\n  };\n  typedef std::vector<Penalties> tPenaltiesVec;\n  typedef std::map<std::string, Penalties> tPenaltiesMap;\n\n  struct tLatencyStats {\n    double minlatency, maxlatency, averagelatency, lastupdate, age;\n    tLatencyStats() :\n      minlatency(std::numeric_limits<double>::max()),\n      maxlatency(-std::numeric_limits<double>::max()),\n      averagelatency(0.0), lastupdate(0.0), age(0.0) {};\n    double getage(double nowms = 0.0)\n    {\n      if (nowms == 0.0) {\n        struct timeval curtime;\n        gettimeofday(&curtime, 0);\n        nowms = curtime.tv_sec * 1000 + curtime.tv_usec / 1000;\n      }\n\n      return  nowms - lastupdate;\n    }\n    void update(const double& nowms = 0.0)\n    {\n      double latency = getage(nowms);\n      averagelatency = (averagelatency != 0.0) ? (averagelatency * 0.99 + latency *\n                       0.01) : latency;\n      minlatency = std::min(minlatency , latency);\n      maxlatency = std::max(maxlatency , latency);\n    }\n  };\n\n  struct nodeAgreg {\n    bool saturated;\n    size_t fsCount;\n    size_t rOpen;\n    size_t wOpen;\n    double netOutWeight;\n    double netInWeight;\n    double diskUtilSum;\n    size_t netSpeedClass;\n    nodeAgreg() : saturated(false), fsCount(0), rOpen(0), wOpen(0),\n      netOutWeight(0.0), netInWeight(0.0), diskUtilSum(0.0), netSpeedClass(0) {};\n  };\n//**********************************************************\n// END INTERNAL DATA STRUCTURES\n//**********************************************************\n\n//**********************************************************\n// BEGIN INTERNAL CLASSES\n//**********************************************************\n  /*----------------------------------------------------------------------------*/\n  /**\n   * @brief this structure holds all the fast structures needed to carry out\n   *        file data storage/access scheduling operations\n   *\n   */\n  /*----------------------------------------------------------------------------*/\n  struct FastStructSched {\n    FastROAccessTree* rOAccessTree;\n    FastRWAccessTree* rWAccessTree;\n    FastDrainingAccessTree* drnAccessTree;\n    FastPlacementTree* placementTree;\n    FastDrainingPlacementTree* drnPlacementTree;\n    SchedTreeBase::FastTreeInfo* treeInfo;\n    Fs2TreeIdxMap* fs2TreeIdx;\n    GeoTag2NodeIdxMap* tag2NodeIdx;\n    tPenaltiesVec* penalties;\n\n    FastStructSched()\n    {\n      rOAccessTree = new FastROAccessTree;\n      rOAccessTree->selfAllocate(FastROAccessTree::sGetMaxNodeCount());\n      rWAccessTree = new FastRWAccessTree;\n      rWAccessTree->selfAllocate(FastRWAccessTree::sGetMaxNodeCount());\n      drnAccessTree = new FastDrainingAccessTree;\n      drnAccessTree->selfAllocate(FastDrainingAccessTree::sGetMaxNodeCount());\n      placementTree = new FastPlacementTree;\n      placementTree->selfAllocate(FastPlacementTree::sGetMaxNodeCount());\n      drnPlacementTree = new FastDrainingPlacementTree;\n      drnPlacementTree->selfAllocate(FastDrainingPlacementTree::sGetMaxNodeCount());\n      treeInfo = new SchedTreeBase::FastTreeInfo;\n      penalties = new tPenaltiesVec;\n      penalties->reserve(SchedTreeBase::sGetMaxNodeCount());\n      fs2TreeIdx = new Fs2TreeIdxMap;\n      fs2TreeIdx->selfAllocate(SchedTreeBase::sGetMaxNodeCount());\n      rOAccessTree->pFs2Idx\n        = rWAccessTree->pFs2Idx\n          = drnAccessTree->pFs2Idx\n            = placementTree->pFs2Idx\n              = drnPlacementTree->pFs2Idx\n                = fs2TreeIdx;\n      rOAccessTree->pTreeInfo\n        = rWAccessTree->pTreeInfo\n          = drnAccessTree->pTreeInfo\n            = placementTree->pTreeInfo\n              = drnPlacementTree->pTreeInfo\n                = treeInfo;\n      tag2NodeIdx = new GeoTag2NodeIdxMap;\n      tag2NodeIdx->selfAllocate(SchedTreeBase::sGetMaxNodeCount());\n    }\n\n    ~FastStructSched()\n    {\n      if (rOAccessTree) {\n        delete rOAccessTree;\n      }\n\n      if (rWAccessTree) {\n        delete rWAccessTree;\n      }\n\n      if (drnAccessTree) {\n        delete drnAccessTree;\n      }\n\n      if (placementTree) {\n        delete placementTree;\n      }\n\n      if (drnPlacementTree) {\n        delete drnPlacementTree;\n      }\n\n      if (treeInfo) {\n        delete treeInfo;\n      }\n\n      if (penalties) {\n        delete penalties;\n      }\n\n      if (fs2TreeIdx) {\n        delete fs2TreeIdx;\n      }\n\n      if (tag2NodeIdx) {\n        delete tag2NodeIdx;\n      }\n    }\n\n    bool DeepCopyTo(FastStructSched* target) const\n    {\n      if (\n        rOAccessTree->copyToFastTree(target->rOAccessTree) ||\n        rWAccessTree->copyToFastTree(target->rWAccessTree) ||\n        drnAccessTree->copyToFastTree(target->drnAccessTree) ||\n        placementTree->copyToFastTree(target->placementTree) ||\n        drnPlacementTree->copyToFastTree(target->drnPlacementTree)\n      ) {\n        return false;\n      }\n\n      // copy the information\n      *(target->treeInfo) = *treeInfo;\n\n      if (\n        fs2TreeIdx->copyToFsId2NodeIdxMap(target->fs2TreeIdx) ||\n        tag2NodeIdx->copyToGeoTag2NodeIdxMap(target->tag2NodeIdx)) {\n        return false;\n      }\n\n      // copy the penalties\n      std::copy(penalties->begin(), penalties->end(),\n                target->penalties->begin());\n      // update the information in the FastTrees to point to the copy\n      target->rOAccessTree->pFs2Idx\n        = target->rWAccessTree->pFs2Idx\n          = target->drnAccessTree->pFs2Idx\n            = target->placementTree->pFs2Idx\n              = target->drnPlacementTree->pFs2Idx\n                = target->fs2TreeIdx;\n      target->rOAccessTree->pTreeInfo\n        = target->rWAccessTree->pTreeInfo\n          = target->drnAccessTree->pTreeInfo\n            = target->placementTree->pTreeInfo\n              = target->drnPlacementTree->pTreeInfo\n                = target->treeInfo;\n      return true;\n    }\n\n    void UpdateTrees()\n    {\n      rOAccessTree->updateTree();\n      rWAccessTree->updateTree();\n      drnAccessTree->updateTree();\n      placementTree->updateTree();\n      drnPlacementTree->updateTree();\n    }\n\n    inline void applyDlScorePenalty(SchedTreeBase::tFastTreeIdx idx,\n                                    const char& penalty, bool background)\n    /**< Apply download score penalty */\n    {\n      AtomicSub(placementTree->pNodes[idx].fsData.dlScore, penalty);\n      AtomicSub(drnPlacementTree->pNodes[idx].fsData.dlScore, penalty);\n      AtomicSub(rOAccessTree->pNodes[idx].fsData.dlScore, penalty);\n      AtomicSub(rWAccessTree->pNodes[idx].fsData.dlScore, penalty);\n      AtomicSub(drnAccessTree->pNodes[idx].fsData.dlScore, penalty);\n\n      if (!background) {\n        AtomicAdd((*penalties)[idx].dlScorePenalty, penalty);\n      }\n    }\n\n    inline void applyUlScorePenalty(SchedTreeBase::tFastTreeIdx idx,\n                                    const char& penalty, bool background)\n    /**< Apply upload score penalty */\n    {\n      AtomicSub(placementTree->pNodes[idx].fsData.ulScore, penalty);\n      AtomicSub(drnPlacementTree->pNodes[idx].fsData.ulScore, penalty);\n      AtomicSub(rOAccessTree->pNodes[idx].fsData.ulScore, penalty);\n      AtomicSub(rWAccessTree->pNodes[idx].fsData.ulScore, penalty);\n      AtomicSub(drnAccessTree->pNodes[idx].fsData.ulScore, penalty);\n\n      if (!background) {\n        AtomicAdd((*penalties)[idx].ulScorePenalty, penalty);\n      }\n    }\n\n    inline bool buildFastStructures(SlowTree* slowTree)\n    {\n      return slowTree->buildFastStrcturesSched(\n               placementTree , rOAccessTree, rWAccessTree,\n               drnPlacementTree , drnAccessTree,\n               treeInfo , fs2TreeIdx, tag2NodeIdx\n             );\n    }\n\n    inline void resizePenalties(const size_t& newsize)\n    {\n      penalties->resize(newsize);\n    }\n\n    inline void setConfigParam(\n      const char& fillRatioLimit,\n      const char& fillRatioCompTol,\n      const char& saturationThres)\n    {\n      rOAccessTree->setSaturationThreshold(saturationThres);\n      rWAccessTree->setSaturationThreshold(saturationThres);\n      drnAccessTree->setSaturationThreshold(saturationThres);\n      placementTree->setSaturationThreshold(saturationThres);\n      placementTree->setSpreadingFillRatioCap(fillRatioLimit);\n      placementTree->setFillRatioCompTol(fillRatioCompTol);\n      drnPlacementTree->setSaturationThreshold(saturationThres);\n      drnPlacementTree->setSpreadingFillRatioCap(fillRatioLimit);\n      drnPlacementTree->setFillRatioCompTol(fillRatioCompTol);\n    }\n\n  };\n\n  /*----------------------------------------------------------------------------*/\n  /**\n   * @brief this structure holds all the fast structures needed to carry out\n   *        file gateway style scheduling operations\n   *\n   */\n  /*----------------------------------------------------------------------------*/\n  struct FastStructProxy {\n    FastGatewayAccessTree* proxyAccessTree;\n    SchedTreeBase::FastTreeInfo* treeInfo;\n    Host2TreeIdxMap* host2TreeIdx;\n    GeoTag2NodeIdxMap* tag2NodeIdx;\n    tPenaltiesVec* penalties;\n\n    FastStructProxy()\n    {\n      proxyAccessTree = new FastGatewayAccessTree;\n      proxyAccessTree->selfAllocate(FastGatewayAccessTree::sGetMaxNodeCount());\n      treeInfo = new SchedTreeBase::FastTreeInfo;\n      penalties = new tPenaltiesVec;\n      penalties->reserve(FastGatewayAccessTree::sGetMaxNodeCount());\n      host2TreeIdx = new Host2TreeIdxMap;\n      host2TreeIdx->selfAllocate(FastGatewayAccessTree::sGetMaxNodeCount());\n      proxyAccessTree->pFs2Idx = host2TreeIdx;\n      proxyAccessTree->pTreeInfo\n        = treeInfo;\n      tag2NodeIdx = new GeoTag2NodeIdxMap;\n      tag2NodeIdx->selfAllocate(FastGatewayAccessTree::sGetMaxNodeCount());\n    }\n\n    ~FastStructProxy()\n    {\n      if (proxyAccessTree) {\n        delete proxyAccessTree;\n      }\n\n      if (treeInfo) {\n        delete treeInfo;\n      }\n\n      if (penalties) {\n        delete penalties;\n      }\n\n      if (tag2NodeIdx) {\n        delete tag2NodeIdx;\n      }\n    }\n\n    bool DeepCopyTo(FastStructProxy* target) const\n    {\n      if (\n        proxyAccessTree->copyToFastTree(target->proxyAccessTree)\n      ) {\n        return false;\n      }\n\n      // copy the information\n      *(target->treeInfo) = *treeInfo;\n\n      if (\n        tag2NodeIdx->copyToGeoTag2NodeIdxMap(target->tag2NodeIdx)) {\n        return false;\n      }\n\n      if (\n        host2TreeIdx->copyToFsId2NodeIdxMap(target->host2TreeIdx)) {\n        return false;\n      }\n\n      // copy the penalties\n      std::copy(penalties->begin(), penalties->end(),\n                target->penalties->begin());\n      // update the information in the FastTrees to point to the copy\n      target->proxyAccessTree->pFs2Idx\n        = NULL;\n      target->proxyAccessTree->pTreeInfo\n        = target->treeInfo;\n      return true;\n    }\n\n    void UpdateTrees()\n    {\n      proxyAccessTree->updateTree();\n    }\n\n    inline void applyDlScorePenalty(SchedTreeBase::tFastTreeIdx idx,\n                                    const char& penalty, bool background)\n    {\n      AtomicSub(proxyAccessTree->pNodes[idx].fsData.dlScore, penalty);\n\n      if (!background) {\n        AtomicAdd((*penalties)[idx].dlScorePenalty, penalty);\n      }\n    }\n\n    inline void applyUlScorePenalty(SchedTreeBase::tFastTreeIdx idx,\n                                    const char& penalty, bool background)\n    {\n      AtomicSub(proxyAccessTree->pNodes[idx].fsData.ulScore, penalty);\n\n      if (!background) {\n        AtomicAdd((*penalties)[idx].ulScorePenalty, penalty);\n      }\n    }\n\n    inline bool buildFastStructures(SlowTree* slowTree)\n    {\n      return slowTree->buildFastStructuresGW(\n               proxyAccessTree, host2TreeIdx,\n               treeInfo , tag2NodeIdx\n             );\n    }\n\n    inline void resizePenalties(const size_t& newsize)\n    {\n      penalties->resize(newsize);\n    }\n\n    inline void setConfigParam(\n      const char& fillRatioLimit,\n      const char& fillRatioCompTol,\n      const char& saturationThres)\n    {\n      proxyAccessTree->setSaturationThreshold(saturationThres);\n    }\n\n  };\n\n  /*----------------------------------------------------------------------------*/\n  /**\n   * @brief this structure holds all the structures needed by the GeoTreeEngine\n   *        to manage tree based operation of given type\n   *        it is just a base to derived structs\n   */\n  /*----------------------------------------------------------------------------*/\n  template<typename FastStruct> struct TreeMapEntry {\n\n    // ==== SlowTree : this is used to add or remove nodes ==== //\n    // every access to mSlowTree or mFs2SlowTreeNode should be protected by a lock to mSlowTreeMutex\n    SlowTree* slowTree;\n    //std::map<eos::common::FileSystem::fsid_t,SlowTreeNode*> fs2SlowTreeNode;\n    eos::common::RWMutex slowTreeMutex;\n    bool slowTreeModified;\n\n    // ===== Fast Structures Management and Double Buffering ====== //\n    FastStruct fastStructures[2];\n    // the pointed object is read only accessed by several thread\n    FastStruct* foregroundFastStruct;\n    // the pointed object is accessed in read /write only by the thread update\n    FastStruct* backgroundFastStruct;\n    // the two previous pointers are swapped once an update is done. To do so, we need a mutex and a counter (for deletion)\n    // every access to *mForegroundFastStruct for reading should be protected by a LockRead to mDoubleBufferMutex\n    // when swapping mForegroundFastStruct and mBackgroundFastStruct is needed a LockWrite is taken to mDoubleBufferMutex\n    eos::common::RWMutex doubleBufferMutex;\n    size_t fastStructLockWaitersCount;\n    bool fastStructModified;\n\n    TreeMapEntry(const std::string& groupName = \"\") :\n      slowTreeModified(false),\n      foregroundFastStruct(fastStructures),\n      backgroundFastStruct(fastStructures + 1),\n      fastStructLockWaitersCount(0),\n      fastStructModified(false)\n    {\n      slowTree = new SlowTree(groupName);\n      slowTreeMutex.SetBlocking(true);\n      doubleBufferMutex.SetBlocking(true);\n    }\n\n    ~TreeMapEntry()\n    {\n      if (slowTree) {\n        delete slowTree;\n      }\n    }\n\n    void swapFastStructBuffers()\n    {\n      eos::common::RWMutexWriteLock lock(doubleBufferMutex);\n      std::swap(foregroundFastStruct, backgroundFastStruct);\n    }\n\n    void updateBGFastStructuresConfigParam(\n      const char& fillRatioLimit,\n      const char& fillRatioCompTol,\n      const char& saturationThres)\n    {\n      backgroundFastStruct->setConfigParam(fillRatioLimit, fillRatioCompTol,\n                                           saturationThres);\n      refreshBackGroundFastStructures();\n    }\n\n    void refreshBackGroundFastStructures()\n    {\n      backgroundFastStruct->UpdateTrees();\n    }\n\n    bool updateFastStructures()\n    {\n      FastStruct* ft = backgroundFastStruct;\n\n      if (!ft->buildFastStructures(slowTree)) {\n        eos_static_crit(\"Error updating the fast structures\");\n        return false;\n      }\n\n      ft->resizePenalties(slowTree->getNodeCount());\n      return true;\n    }\n\n  };\n\n  /*----------------------------------------------------------------------------*/\n  /**\n   * @brief this structure holds all the structures needed by the GeoTreeEngine\n   *        to manage a given scheduling group\n   *\n   */\n  /*----------------------------------------------------------------------------*/\n  struct SchedTME : public TreeMapEntry<FastStructSched> {\n    FsGroup* group;\n\n    std::map<eos::common::FileSystem::fsid_t, SlowTreeNode*> fs2SlowTreeNode;\n\n    SchedTME(const std::string& groupName) :\n      TreeMapEntry<FastStructSched>(groupName),\n      group(NULL)\n    {}\n\n    void updateSlowTreeInfoFromBgFastStruct()\n    {\n      for (auto it = fs2SlowTreeNode.begin(); it != fs2SlowTreeNode.end(); ++it) {\n        const SchedTreeBase::tFastTreeIdx* idx;\n\n        if (!backgroundFastStruct->fs2TreeIdx->get(it->first, idx)) {\n          // this node was added in the SlowTree, the fast structures doesn't include it yet\n          continue;\n        }\n\n        FastPlacementTree::FsData& fastState =\n          backgroundFastStruct->placementTree->pNodes[*idx].fsData;\n        SlowTreeNode::TreeNodeStateFloat& slowState = it->second->pNodeState;\n        slowState.dlScore = fastState.dlScore;\n        slowState.ulScore = fastState.ulScore;\n        slowState.mStatus = fastState.mStatus &\n                            ~eos::mgm::SchedTreeBase::Disabled; // we don't want to back proagate the disabled bit\n        slowState.fillRatio = fastState.fillRatio;\n        slowState.totalSpace = float(fastState.totalSpace);\n        SchedTreeBase::TreeNodeInfo& fastInfo = (*backgroundFastStruct->treeInfo)[*idx];\n        SlowTreeNode::TreeNodeInfo& slowInfo = it->second->pNodeInfo;\n        slowInfo.netSpeedClass = fastInfo.netSpeedClass;\n        slowInfo.proxygroup = fastInfo.proxygroup;\n        slowInfo.fileStickyProxyDepth = fastInfo.fileStickyProxyDepth;\n      }\n    }\n\n  };\n\n  /*----------------------------------------------------------------------------*/\n  /**\n   * @brief this structure holds all the structures needed by the GeoTreeEngine\n   *        to manage a scheduling of XRootd gateways and data proxys\n   *\n   */\n  /*----------------------------------------------------------------------------*/\n  struct ProxyTMEBase : public TreeMapEntry<FastStructProxy> {\n    FsGroup* group;\n\n    std::map<std::string, SlowTreeNode*> host2SlowTreeNode;\n\n    ProxyTMEBase(const std::string& groupName) :\n      TreeMapEntry<FastStructProxy>(groupName),\n      group(NULL)\n    {}\n\n    void updateSlowTreeInfoFromBgFastStruct()\n    {\n      for (auto it = host2SlowTreeNode.begin(); it != host2SlowTreeNode.end(); ++it) {\n        const SchedTreeBase::tFastTreeIdx* idx;\n\n        if (!backgroundFastStruct->host2TreeIdx->get(it->first.c_str(), idx)) {\n          // this node was added in the SlowTree, the fast structures doesn't include it yet\n          continue;\n        }\n\n        FastPlacementTree::FsData& fastState =\n          backgroundFastStruct->proxyAccessTree->pNodes[*idx].fsData;\n        SlowTreeNode::TreeNodeStateFloat& slowState = it->second->pNodeState;\n        slowState.dlScore = fastState.dlScore;\n        slowState.ulScore = fastState.ulScore;\n        slowState.mStatus = fastState.mStatus &\n                            ~eos::mgm::SchedTreeBase::Disabled; // we don't want to back proagate the disabled bit\n        SchedTreeBase::TreeNodeInfo& fastInfo = (*backgroundFastStruct->treeInfo)[*idx];\n        SlowTreeNode::TreeNodeInfo& slowInfo = it->second->pNodeInfo;\n        slowInfo.netSpeedClass = fastInfo.netSpeedClass;\n      }\n    }\n\n  };\n\n  /*----------------------------------------------------------------------------*/\n  /**\n   * @brief this structure holds all the structures needed by the GeoTreeEngine\n   *        to manage a scheduling of Data proxy\n   *\n   */\n  /*----------------------------------------------------------------------------*/\n  struct DataProxyTME : public ProxyTMEBase {\n    DataProxyTME(const std::string& groupName) : ProxyTMEBase(groupName)\n    {}\n  };\n\n  bool updateFastStructures(SchedTME* entry)\n  {\n    eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n\n    // if nothing is modified here move to the next group\n    if (!(entry->slowTreeModified || entry->fastStructModified)) {\n      return true;\n    }\n\n    if (entry->slowTreeModified) {\n      entry->updateSlowTreeInfoFromBgFastStruct();\n\n      if (!entry->updateFastStructures()) {\n        eos_crit(\"error updating the fast structures from the slowtree\");\n        return false;\n      }\n\n      applyBranchDisablings(*entry);\n\n      if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n        std::stringstream ss;\n        ss << (*entry->backgroundFastStruct->placementTree);\n        eos_debug(\"fast structures updated successfully from slowtree : new FASTtree is \\n %s\",\n                  ss.str().c_str());\n        ss.str() = \"\";\n        ss << (*entry->slowTree);\n        eos_debug(\"fast structures updated successfully from slowtree : old SLOW tree was \\n %s\",\n                  ss.str().c_str());\n      }\n    } else {\n      // the rebuild of the fast structures is not necessary\n      entry->refreshBackGroundFastStructures();\n\n      if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n        std::stringstream ss;\n        ss << (*entry->backgroundFastStruct->placementTree);\n        eos_debug(\"fast structures updated successfully from fastree : new FASTtree is \\n %s\",\n                  ss.str().c_str());\n      }\n    }\n\n    // mark the entry as updated\n    entry->slowTreeModified = false;\n    entry->fastStructModified = false;\n    // update the BackGroundFastStructures configuration parameters accordingly to the one present in the GeoTree (and update the fast trees)\n    entry->updateBGFastStructuresConfigParam(pFillRatioLimit, pFillRatioCompTol,\n        pSaturationThres);\n    // clear the penalties\n    std::fill(entry->backgroundFastStruct->penalties->begin(),\n              entry->backgroundFastStruct->penalties->end(), Penalties());\n    // swap the buffers (this is the only bit where the fast structures is not accessible for a placement/access operation)\n    entry->swapFastStructBuffers();\n    return true;\n  }\n\n  bool updateFastStructures(ProxyTMEBase* entry)\n  {\n    eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n\n    // if nothing is modified here move to the next group\n    if (!(entry->slowTreeModified || entry->fastStructModified)) {\n      return true;\n    }\n\n    if (entry->slowTreeModified) {\n      entry->updateSlowTreeInfoFromBgFastStruct();\n\n      if (!entry->updateFastStructures()) {\n        eos_crit(\"error updating the fast structures from the slowtree\");\n        return false;\n      }\n\n      if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n        std::stringstream ss;\n        ss << (*entry->backgroundFastStruct->proxyAccessTree);\n        eos_debug(\"fast structures updated successfully from slowtree : new FASTtree is \\n %s\",\n                  ss.str().c_str());\n        ss.str() = \"\";\n        ss << (*entry->slowTree);\n        eos_debug(\"fast structures updated successfully from slowtree : old SLOW tree was \\n %s\",\n                  ss.str().c_str());\n      }\n    } else {\n      // the rebuild of the fast structures is not necessary\n      entry->refreshBackGroundFastStructures();\n\n      if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n        std::stringstream ss;\n        ss << (*entry->backgroundFastStruct->proxyAccessTree);\n        eos_debug(\"fast structures updated successfully from fastree : new FASTtree is \\n %s\",\n                  ss.str().c_str());\n      }\n    }\n\n    // mark the entry as updated\n    entry->slowTreeModified = false;\n    entry->fastStructModified = false;\n    // update the BackGroundFastStructures configuration parameters accordingly to the one present in the GeoTree (and update the fast trees)\n    entry->updateBGFastStructuresConfigParam(pFillRatioLimit, pFillRatioCompTol,\n        pSaturationThres);\n    // clear the penalties\n    std::fill(entry->backgroundFastStruct->penalties->begin(),\n              entry->backgroundFastStruct->penalties->end(), Penalties());\n    // swap the buffers (this is the only bit where the fast structures is not accessible for a placement/access operation)\n    entry->swapFastStructBuffers();\n    return true;\n  }\n\n  //**********************************************************\n// END INTERNAL CLASSES\n//**********************************************************\n\n\n  /// enum holding the possible operations\npublic:\n  enum SchedType\n  { regularRO, regularRW, draining};\n\nprotected:\n//**********************************************************\n// BEGIN DATA MEMBERS\n//**********************************************************\n\n  //! this is the size of the thread local buffer to hold the fast structure being used\n  static const size_t gGeoBufferSize;\n\n  //--------------------------------------------------------------------------------------------------------\n  // Background Notifications and Updates\n  //--------------------------------------------------------------------------------------------------------\n\n  //! these are implicitly convertible enums\n  //! they map to specific changes that happen on the fs\n  static const int\n  sfgGeotag, sfgId, sfgBoot, sfgDrain, sfgDrainer, sfgBlcingrun, sfgBlcerrun,\n             sfgBalthres, sfgActive, sfgBlkavailb, sfgDiskload,\n             sfgEthmib, sfgInratemib, sfgOutratemib, sfgWriteratemb,\n             sfgReadratemb, sfgFsfilled, sfgNomfilled, sfgConfigstatus, sfgHost, sfgErrc,\n             sfgPubTmStmp, sfgPxyGrp, sfgFileStickPxy, sfgWopen, sfgRopen ;\n\n  //! This mutex protects the consistency between the GeoTreeEngine state and the filesystems it contains\n  //! To make any change that temporarily set an unconsistent state (mainly adding a fs, removing a fs,\n  //! listening to the changes in the set if contained fs), one needs to writelock this mutex.\n  //! When the mutex is realesed, the GeoTreeEngine internal ressources should be in a consitent state.\n  eos::common::RWMutex pAddRmFsMutex;\n\n  //! this is the set of all the watched keys to be notified about for FileSystems\n  static std::set<std::string> gWatchedKeys;\n\n  //! this map allow to convert a notification key to an enum for efficient processing\n  static const std::map<std::string, int> gNotifKey2EnumSched;\n\n  //--------------------------------------------------------------------------------------------------------\n  //--------------------------------------------------------------------------------------------------------\n\n\n  //--------------------------------------------------------------------------------------------------------\n  // Configuration\n  //--------------------------------------------------------------------------------------------------------\n  //! [Configuration]\n  /// this mutex protects all the configuration settings\n  eos::common::RWMutex configMutex;// protects all the following settings\n\n  /// these settings indicate if saturated FS should try to be avoided\n  /// this might lead to unoptimal access/placement location-wise\n  bool pSkipSaturatedAccess, pSkipSaturatedDrnAccess, pSkipSaturatedBlcAccess;\n  /// these setting indicates if sthe proxy should be selected closest to the fs or closest to the client\n  bool pProxyCloseToFs;\n\n  /// this set the speed on how fast the penalties are allowed to\n  /// change as they are estimated\n  /// 0 means no self-estimate 1 mean gets a completely new value every time\n  float pPenaltyUpdateRate; /**< Penalty update rate */\n\n  /// the following settings control the SchedulingFastTrees\n  /// it has an impact on how the priority of branches in the trees\n  char\n  /// between 0 and 100 : maximum fillRatio allowed on a fs to select it\n  pFillRatioLimit,\n  /// between 0 and 100 : quantity by which fillRatio must differ to be considered as different\n  /// 100 disable any consideration about available space on the fs\n  /// 0 enables a strict online balancing : if two fs are being considered with equal geolocation proximity\n  ///                                       the emptier will be selected\n  pFillRatioCompTol,\n  /// score below which a FS is to be considered as (IO)saturated\n  pSaturationThres;\n\n  /// the following settings control the frequency and the latency of the background updating process\n  int\n  /// this is the minimum duration of a time frame\n  pTimeFrameDurationMs, /**< time between two refresh of the trees */\n  /// this is how older than a refresh a penalty must be do be dropped\n  pPublishToPenaltyDelayMs;\n\n  /// the following settings control the Disabled branches in the trees\n  // group -> (optype -> geotag)\n  std::map<std::string, std::map<std::string, std::set<std::string> > >\n  pDisabledBranches;\n  //! [Configuration]\n\n  //--------------------------------------------------------------------------------------------------------\n  //--------------------------------------------------------------------------------------------------------\n\n  //--------------------------------------------------------------------------------------------------------\n  // State\n  //--------------------------------------------------------------------------------------------------------\n  //\n  // => fs scheduling groups management / operations\n  //    they are used to schedule fs accesses\n  //\n  std::map<const FsGroup*, SchedTME*> pGroup2SchedTME;\n  std::map<FileSystem::fsid_t, SchedTME*> pFs2SchedTME;\n  std::map<FileSystem::fsid_t, FileSystem*> pFsId2FsPtr;\n  /// protects all the above maps\n  eos::common::RWMutex pTreeMapMutex;\n\n  // => proxy scheduling groups management / operations\n  //    they are used to schedule data proxy to translate dedicated proxygroup to xrootd to serve the client (if any defined)\n  //    they are also used to manage the entry points to the instance (if any defined)\n  //\n  std::map<std::string , DataProxyTME*>\n  pPxyGrp2DpTME;          // one proxygroup => one TreeMapEntry\n  std::map<std::string , std::set<DataProxyTME*>>\n      pPxyHost2DpTMEs; // one proxyhost  => several proxygroups\n  std::map<std::string, SchedTreeBase::tFastTreeIdx> pPxyQueue2PxyId;\n  std::set<SchedTreeBase::tFastTreeIdx> pPxyId2Recycle;\n  /// protects all the above maps\n  eos::common::RWMutex pPxyTreeMapMutex;\n\n  //\n  struct AccessStruct {\n    SlowTree* accessST;\n    std::map<std::string, std::string> accessGeotagMap;\n    FastGatewayAccessTree* accessFT;\n    SchedTreeBase::FastTreeInfo* accessFTI;\n    Host2TreeIdxMap* accessHost2Idx;\n    GeoTag2NodeIdxMap* accessTag2Idx;\n    /// protects all the above maps\n    eos::common::RWMutex accessMutex;\n    bool inuse;\n    std::string configkey;\n\n    //--------------------------------------------------------------------------\n    //! Constructor\n    //--------------------------------------------------------------------------\n    AccessStruct(const std::string& cfgkey):\n      accessST(0), accessFT(0), accessFTI(0), accessHost2Idx(0), accessTag2Idx(0),\n      inuse(false), configkey(cfgkey) {}\n\n    std::string getMappingStr() const;\n\n    bool setMapping(const std::string& geotag, const std::string& geotaglist,\n                    bool updateFastStruct = true, bool setconfig = true);\n\n    bool setMapping(const std::string& mapping, bool setconfig = false);\n\n    bool clearMapping(const std::string& geotag = \"\", bool updateFastStruct = true,\n                      bool setconfig = true);\n\n    bool showMapping(XrdOucString* output, std::string operation, bool monitoring);\n  };\n  /// => access geotag mappings management / operations\n  ///    they are used to check if going through a firewall entrypoint is required\n  AccessStruct pAccessGeotagMapping;\n  /// => access proxygroups management / operations\n  ///    they are used to know which proxygroup to use when firewall entrypoint is required\n  AccessStruct pAccessProxygroup;\n\n  //\n  // => thread local data\n  //\n  /// Thread local buffer to hold a working copy of a fast structure\n  static thread_local void* tlGeoBuffer;\n  static pthread_key_t gPthreadKey;\n  /// Current scheduling group for the current thread\n  static thread_local const FsGroup* tlCurrentGroup;\n  //\n  // => penalties system\n  //\n  const size_t pCircSize;\n  size_t pFrameCount;\n  struct PenaltySubSys {\n    std::vector<tPenaltiesVec> pCircFrCnt2FsPenalties;\n    std::vector<tPenaltiesMap> pCircFrCnt2HostPenalties;\n    /// self estimated penalties\n    std::map<std::string, nodeAgreg> pUpdatingNodes;\n    size_t pMaxNetSpeedClass;\n    /// Atomic penalties to be applied to the scheduled FSs\n    /// those are in the state section because they can be self estimated\n    /// the following vectors map an netzorkSpeedClass to a penalty\n    std::vector<float> pPlctDlScorePenaltyF, pPlctUlScorePenaltyF;\n    std::vector<float> pAccessDlScorePenaltyF, pAccessUlScorePenaltyF;\n    std::vector<float> pProxyScorePenaltyF;\n    // casted version to avoid conversion on every plct / access operation\n    std::vector<char> pPlctDlScorePenalty, pPlctUlScorePenalty;\n    std::vector<char> pAccessDlScorePenalty, pAccessUlScorePenalty;\n    std::vector<char> pProxyScorePenalty;\n    // Constructor\n    PenaltySubSys(const size_t& circSize) :\n      pCircFrCnt2FsPenalties(circSize),\n      pCircFrCnt2HostPenalties(circSize),\n      pMaxNetSpeedClass(0),\n      pPlctDlScorePenaltyF(8, 10), pPlctUlScorePenaltyF(8,\n          10),  // 8 is just a simple way to deal with the initialiaztion of the vector (it's an overshoot but the overhead is tiny)\n      pAccessDlScorePenaltyF(8, 10), pAccessUlScorePenaltyF(8, 10),\n      pProxyScorePenaltyF(8, 10) ,\n      pPlctDlScorePenalty(8, 10), pPlctUlScorePenalty(8,\n          10),  // 8 is just a simple way to deal with the initialiaztion of the vector (it's an overshoot but the overhead is tiny)\n      pAccessDlScorePenalty(8, 10), pAccessUlScorePenalty(8, 10),\n      pProxyScorePenalty(8, 10) {};\n  };\n  PenaltySubSys pPenaltySched;\n  //\n  // => latency estimation\n  //\n  struct LatencySubSys {\n    std::vector<tLatencyStats> pFsId2LatencyStats;\n    std::vector<size_t> pCircFrCnt2Timestamp;\n    // Constructor\n    LatencySubSys(const size_t& circSize) :\n      pCircFrCnt2Timestamp(circSize) {}\n  };\n  LatencySubSys pLatencySched;\n  std::shared_ptr<mq::FsChangeListener> mFsListener;\n  //\n  // => background updating\n  //\n  /// thread ID of the dumper thread\n  AssistedThread updaterThread;\n  /// maps a notification subject to changes that happened in the current time frame\n  static std::map<std::string, int>\n  gNotificationsBufferFs;   /**< Shared object change notification for filesystems */\n  static std::map<std::string, int>\n  gNotificationsBufferProxy; /**< Shared object change notification for proxy nodes */\n  static const unsigned char sntFilesystem, sntGateway, sntDataproxy;\n  static std::map<std::string, unsigned char> gQueue2NotifType;\n  /// deletions to be carried out ASAP\n  /// they are delayed so that any function that is using the treemapentry can safely finish\n  std::list<SchedTME*> pPendingDeletionsFs;\n  std::list<DataProxyTME*> pPendingDeletionsDp;\n  /// indicate if the updater is paused\n  static sem_t gUpdaterPauseSem;\n  static bool gUpdaterPaused;\n  static bool gUpdaterStarted;\n//**********************************************************\n// END DATA MEMBERS\n//**********************************************************\n\n\n  void updateAtomicPenalties();\n\n  /// Trees update management\n  void listenFsChange(ThreadAssistant& assistant);\n\n  /// Clean\n  void checkPendingDeletionsFs()\n  {\n    int count = 0;\n    auto lastEntry = pPendingDeletionsFs.begin();\n    bool eraseLastEntry = false;\n\n    for (auto it = pPendingDeletionsFs.begin(); it != pPendingDeletionsFs.end();\n         it++) {\n      if (eraseLastEntry) {\n        pPendingDeletionsFs.erase(lastEntry);\n      }\n\n      eraseLastEntry = false;\n\n      if (!(*it)->fastStructLockWaitersCount) {\n        delete(*it);\n        eraseLastEntry = true;\n        count++;\n      }\n\n      lastEntry = it;\n    }\n\n    if (eraseLastEntry) {\n      pPendingDeletionsFs.erase(lastEntry);\n    }\n\n    eos_debug(\"%d pending deletions executed for filesystems\", count);\n  }\n\n  void checkPendingDeletionsDp()\n  {\n    int count = 0;\n    auto lastEntry = pPendingDeletionsDp.begin();\n    bool eraseLastEntry = false;\n\n    for (auto it = pPendingDeletionsDp.begin(); it != pPendingDeletionsDp.end();\n         it++) {\n      if (eraseLastEntry) {\n        pPendingDeletionsDp.erase(lastEntry);\n      }\n\n      eraseLastEntry = false;\n\n      if (!(*it)->fastStructLockWaitersCount) {\n        delete(*it);\n        eraseLastEntry = true;\n        count++;\n      }\n\n      lastEntry = it;\n    }\n\n    if (eraseLastEntry) {\n      pPendingDeletionsDp.erase(lastEntry);\n    }\n\n    eos_debug(\"%d pending deletions executed for dataproxys\", count);\n  }\n\n  /// thread-local buffer management\n  static void tlFree(void* arg);\n  static char* tlAlloc(size_t size);\n\n  inline void applyDlScorePenalty(SchedTME* entry,\n                                  const SchedTreeBase::tFastTreeIdx& idx, const char& penalty,\n                                  bool background = false)\n  {\n    FastStructSched* ft = background ? entry->backgroundFastStruct :\n                          entry->foregroundFastStruct;\n    ft->applyDlScorePenalty(idx, penalty, background);\n  }\n\n  inline void applyUlScorePenalty(SchedTME* entry,\n                                  const SchedTreeBase::tFastTreeIdx& idx, const char& penalty,\n                                  bool background = false)\n  {\n    FastStructSched* ft = background ? entry->backgroundFastStruct :\n                          entry->foregroundFastStruct;\n    ft->applyUlScorePenalty(idx, penalty, background);\n  }\n\n  inline void recallScorePenalty(SchedTME* entry,\n                                 const SchedTreeBase::tFastTreeIdx& idx)\n  {\n    auto fsid = (*entry->backgroundFastStruct->treeInfo)[idx].fsId;\n    tLatencyStats& lstat = pLatencySched.pFsId2LatencyStats[fsid];\n    //auto mydata = entry->backgroundFastStruct->placementTree->pNodes[idx].fsData;\n    int count = 0;\n\n    for (size_t circIdx = pFrameCount % pCircSize;\n         (lstat.lastupdate != 0) &&\n         (pLatencySched.pCircFrCnt2Timestamp[circIdx] > lstat.lastupdate -\n          pPublishToPenaltyDelayMs);\n         circIdx = ((pCircSize + circIdx - 1) % pCircSize)) {\n      if (entry->foregroundFastStruct->placementTree->pNodes[idx].fsData.dlScore > 0)\n        applyDlScorePenalty(entry, idx,\n                            pPenaltySched.pCircFrCnt2FsPenalties[circIdx][fsid].dlScorePenalty,\n                            true\n                           );\n\n      if (entry->foregroundFastStruct->placementTree->pNodes[idx].fsData.ulScore > 0)\n        applyUlScorePenalty(entry, idx,\n                            pPenaltySched.pCircFrCnt2FsPenalties[circIdx][fsid].ulScorePenalty,\n                            true\n                           );\n\n      if (++count == (int)pCircSize) {\n        eos_warning(\"Last fs update for fs %d is older than older penalty : it could happen as a transition but should not happen permanently.\",\n                    (int)fsid);\n        break;\n      }\n    }\n\n//    if(mydata.dlScore!=entry->backgroundFastStruct->placementTree->pNodes[idx].fsData.dlScore || mydata.ulScore!=entry->backgroundFastStruct->placementTree->pNodes[idx].fsData.ulScore)\n//    {\n//      eos_static_info(\"score before recalling penalties dl=%d  ul=%d\",\n//                      (int)mydata.dlScore,\n//                      (int)mydata.ulScore);\n//\n//      eos_static_info(\"score after recalling penalties dl=%d  ul=%d\",\n//                    (int)entry->backgroundFastStruct->placementTree->pNodes[idx].fsData.dlScore,\n//                    (int)entry->backgroundFastStruct->placementTree->pNodes[idx].fsData.ulScore);\n//    }\n  }\n\n  template<class T> bool placeNewReplicas(SchedTME* entry,\n                                          const size_t& nNewReplicas,\n\n                                          std::vector<SchedTreeBase::tFastTreeIdx>* newReplicas,\n                                          T* placementTree,\n                                          std::vector<SchedTreeBase::tFastTreeIdx>* existingReplicas = NULL,\n                                          unsigned long long bookingSize = 0,\n                                          const SchedTreeBase::tFastTreeIdx& startFromNode = 0,\n                                          const size_t& nFinalCollocatedReplicas = 0,\n                                          std::vector<SchedTreeBase::tFastTreeIdx>* excludedNodes = NULL)\n  {\n    // a read lock is supposed to be acquired on the fast structures\n    eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n    bool updateNeeded = false;\n\n    if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n      std::stringstream ss;\n      ss << (*placementTree);\n      eos_debug(\"fast tree used to copy from is: \\n %s\", ss.str().c_str());\n    }\n\n    // make a working copy of the required fast tree\n    // allocate the buffer only once for the lifetime of the thread\n    if (!tlGeoBuffer) {\n      tlGeoBuffer = tlAlloc(gGeoBufferSize);\n    }\n\n    if (placementTree->copyToBuffer((char*)tlGeoBuffer, gGeoBufferSize)) {\n      eos_crit(\"could not make a working copy of the fast tree\");\n      return false;\n    }\n\n    T* tree = (T*)tlGeoBuffer;\n    // place the existing replicas\n    size_t nAdjustCollocatedReplicas = nFinalCollocatedReplicas;\n\n    if (existingReplicas) {\n      size_t ncomp = (*tree->pTreeInfo)[startFromNode].fullGeotag.find(\"::\");\n\n      if (ncomp == std::string::npos) {\n        ncomp = (*tree->pTreeInfo)[startFromNode].fullGeotag.size();\n      }\n\n      for (auto it = existingReplicas->begin(); it != existingReplicas->end(); ++it) {\n        tree->pNodes[*it].fileData.freeSlotsCount = 0;\n        tree->pNodes[*it].fileData.takenSlotsCount = 1;\n\n        // check if this replica is to be considered as a collocated one\n        if (startFromNode) {\n          // we have an accesser geotag\n          if ((*tree->pTreeInfo)[startFromNode].fullGeotag.compare(0, ncomp,\n              (*tree->pTreeInfo)[*it].fullGeotag) == 0\n              && ((*tree->pTreeInfo)[*it].fullGeotag.size() == ncomp ||\n                  (*tree->pTreeInfo)[*it].fullGeotag[ncomp] == ':')) {\n            // this existing replica is under the same first level of the tree\n            // we consider it as a collocated replica\n            if (nAdjustCollocatedReplicas) {\n              nAdjustCollocatedReplicas--;\n            }\n          }\n        }\n      }\n\n      if (nAdjustCollocatedReplicas > nNewReplicas) {\n        nAdjustCollocatedReplicas = nNewReplicas;\n      }\n\n      // Update the tree - could be made faster for a small number of existing\n      // replicas by using update branches\n      if (!existingReplicas->empty()) {\n        updateNeeded = true;\n      }\n    }\n\n    if (excludedNodes) {\n      // Mark the excluded branches as unavailable and sort the branches\n      // (no deep, or we would lose the unavailable marks)\n      for (auto it = excludedNodes->begin(); it != excludedNodes->end(); ++it) {\n        tree->pNodes[*it].fsData.mStatus = tree->pNodes[*it].fsData.mStatus &\n                                           ~SchedTreeBase::Available;\n      }\n\n      if (!excludedNodes->empty()) {\n        updateNeeded = true;\n      }\n    }\n\n    if (bookingSize) {\n      for (auto it = tree->pFs2Idx->begin(); it != tree->pFs2Idx->end(); it++) {\n        // we prebook the space on all the possible nodes before the selection\n        // reminder : this is just a working copy of the tree and will affect\n        // only the current placement\n        const SchedTreeBase::tFastTreeIdx& idx = (*it).second;\n        float& freeSpace = tree->pNodes[idx].fsData.totalWritableSpace;\n\n        if (freeSpace > bookingSize) { // if there is enough space , prebook it\n          freeSpace -= bookingSize;\n        } else { // if there is not enough space, make the node unavailable\n          tree->pNodes[idx].fsData.mStatus = tree->pNodes[idx].fsData.mStatus &\n                                             ~SchedTreeBase::Available;\n        }\n      }\n\n      updateNeeded = true;\n    } else {\n      // Test at lest that we have some free space\n      for (auto it = tree->pFs2Idx->begin(); it != tree->pFs2Idx->end(); ++it) {\n        const SchedTreeBase::tFastTreeIdx& idx = (*it).second;\n        float& freeSpace = tree->pNodes[idx].fsData.totalWritableSpace;\n\n        if (!freeSpace) {\n          tree->pNodes[idx].fsData.mStatus = tree->pNodes[idx].fsData.mStatus &\n                                             ~SchedTreeBase::Available;\n          updateNeeded = true;\n        }\n      }\n    }\n\n    // do the placement\n    if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n      std::stringstream ss;\n      ss << (*tree);\n      eos_debug(\"fast tree used for placement is: \\n %s\", ss.str().c_str());\n    }\n\n    if (updateNeeded) {\n      tree->updateTree();\n    }\n\n    for (size_t k = 0; k < nNewReplicas; k++) {\n      SchedTreeBase::tFastTreeIdx idx;\n      SchedTreeBase::tFastTreeIdx startidx =\n        (k < nNewReplicas - nAdjustCollocatedReplicas) ? 0 : startFromNode;\n\n      // Do several attempts to avoid collocating more replicas than required\n      if ((startidx == 0) && nFinalCollocatedReplicas) {\n        static unsigned int k_max_attempts = GetMaxPlacementAttempts();\n\n        for (unsigned int attempt = 1u; attempt <= k_max_attempts; ++attempt) {\n          if (!tree->findFreeSlot(idx, 0, true /*allow uproot*/,\n                                  true, false)) {\n            eos_debug(\"%s\", \"msg=\\\"could not find a new replica slot in the \"\n                      \"fast tree\\\"\");\n            std::stringstream ss;\n            ss << (*tree);\n            eos_debug(\"msg=\\\"iteration number %lu fast tree used for placement \"\n                      \"is \\n %s\", k, ss.str().c_str());\n            return false;\n          } else {\n            // Make sure the selected location has a different geotag from the\n            // collocated location pointed to by startFromNode\n            const std::string current_geotag = (*tree->pTreeInfo)[idx].fullGeotag;\n            const std::string avoid_geotag = (*tree->pTreeInfo)[startFromNode].fullGeotag;\n            eos_debug(\"msg=\\\"compare geotag locations\\\" current_geotag=%s \"\n                      \" start_geotag=%s\", current_geotag.c_str(),\n                      avoid_geotag.c_str());\n\n            if (current_geotag.find(avoid_geotag) == 0) {\n              continue;\n            } else {\n              eos_debug(\"msg=\\\"found location after %u attempts\\\"\", attempt);\n              break;\n            }\n          }\n        }\n      } else {\n        if (!tree->findFreeSlot(idx, startidx, true /*allow uproot*/,\n                                true, false)) {\n          eos_debug(\"%s\", \"msg=\\\"could not find a new replica slot in the \"\n                    \"fast tree\\\"\");\n          std::stringstream ss;\n          ss << (*tree);\n          eos_debug(\"msg=\\\"iteration number %lu fast tree used for placement is\"\n                    \" \\n %s\", k, ss.str().c_str());\n          return false;\n        }\n      }\n\n      newReplicas->push_back(idx);\n    }\n\n    return true;\n  }\n\n  template<class T> unsigned char accessReplicas(SchedTME* entry,\n      const size_t& nNewReplicas,\n      std::vector<SchedTreeBase::tFastTreeIdx>* accessedReplicas,\n      SchedTreeBase::tFastTreeIdx accesserNode,\n      std::vector<SchedTreeBase::tFastTreeIdx>* existingReplicas,\n      T* accessTree,\n      bool skipSaturated = false)\n  {\n    eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n\n    if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n      std::stringstream ss;\n      ss << (*accessTree);\n      eos_debug(\"fast tree used to copy from is: \\n %s\", ss.str().c_str());\n    }\n\n    // make a working copy of the required fast tree\n    // allocate the buffer only once for the lifetime of the thread\n    if (!tlGeoBuffer) {\n      tlGeoBuffer = tlAlloc(gGeoBufferSize);\n    }\n\n    if (accessTree->copyToBuffer((char*)tlGeoBuffer, gGeoBufferSize)) {\n      eos_crit(\"could not make a working copy of the fast tree\");\n      return 0;\n    }\n\n    T* tree = (T*)tlGeoBuffer;\n    eos_static_debug(\"saturationTresh original=%d / copy=%d\",\n                     (int)accessTree->pBranchComp.saturationThresh,\n                     (int)tree->pBranchComp.saturationThresh);\n\n    // place the existing replicas\n    if (existingReplicas) {\n      for (auto it = existingReplicas->begin(); it != existingReplicas->end(); ++it) {\n        tree->pNodes[*it].fileData.freeSlotsCount = 1;\n        tree->pNodes[*it].fileData.takenSlotsCount = 0;\n      }\n\n      // update the tree\n      // (could be made faster for a small number of existing replicas by using update branches)\n      tree->updateTree();\n    }\n\n    // do the access\n    if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n      std::stringstream ss;\n      ss << (*tree);\n      eos_debug(\"fast tree used for access is: \\n %s\", ss.str().c_str());\n    }\n\n    // do the access\n    unsigned char retCode = 0;\n\n    for (size_t k = 0; k < nNewReplicas; k++) {\n      SchedTreeBase::tFastTreeIdx idx;\n\n      if (!tree->findFreeSlot(idx, accesserNode, true, true, skipSaturated)) {\n        if (skipSaturated) {\n          eos_debug(\"Could not find any replica to access while skipping saturated fs. Trying with saturated nodes included\");\n        }\n\n        if ((!skipSaturated) || !tree->findFreeSlot(idx, 0, false, true, false)) {\n          eos_debug(\"could not find a new slot for a replica in the fast tree\");\n          return 0;\n        } else {\n          retCode = 1;\n        }\n      } else {\n        retCode = 2;\n      }\n\n      accessedReplicas->push_back(idx);\n    }\n\n    return retCode;\n  }\n\n  bool updateTreeInfo(SchedTME* entry, eos::common::FileSystem::fs_snapshot_t* fs,\n                      int keys, SchedTreeBase::tFastTreeIdx ftidx = 0 , SlowTreeNode* stn = NULL);\n  bool updateTreeInfo(const std::map<std::string, int>& updatesFs,\n                      const std::map<std::string, int>& updatesDp);\n  //bool updateTreeInfoFs(const map<string,int> &updatesFs);\n\n  template<typename T> bool _setInternalParam(T& param, const T& value,\n      bool updateStructs)\n  {\n    eos::common::RWMutexWriteLock lock(pAddRmFsMutex);\n    eos::common::RWMutexWriteLock lock2(pTreeMapMutex);\n    eos::common::RWMutexWriteLock lock3(configMutex);\n    bool result = true;\n    param = value;\n\n    for (auto it = pFs2SchedTME.begin(); it != pFs2SchedTME.end(); it++) {\n      if (updateStructs) {\n        it->second->fastStructModified = true;\n        it->second->slowTreeModified = true;\n        result = result && updateFastStructures(it->second);\n      }\n    }\n\n    return result;\n  }\n\n  static void setConfigValue(const char* prefix,\n                             const char* key,\n                             const char* val);\n\n  template<typename T> bool setInternalParam(T& param, const int& value,\n      bool updateStructs, const std::string& configentry)\n  {\n    bool ret = _setInternalParam(param, static_cast<T>(value), updateStructs);\n\n    if (ret && configentry.length()) {\n      XrdOucString s;\n      s.append((int)value);\n      setConfigValue(\"geosched\", configentry.c_str() , s.c_str());\n    }\n\n    return ret;\n  }\n\n  template<typename T> bool setInternalParam(T& param, const float& value,\n      bool updateStructs, const std::string& configentry)\n  {\n    bool ret = _setInternalParam(param, static_cast<T>(value), updateStructs);\n\n    if (ret && configentry.length()) {\n      XrdOucString s;\n      char buf[32];\n      sprintf(buf, \"%f\", value);\n      s += buf;\n      setConfigValue(\"geosched\", configentry.c_str() , s.c_str());\n    }\n\n    return ret;\n  }\n\n  bool setInternalParam(std::vector<char>& param, const std::vector<char >& value,\n                        bool updateStructs, const std::string& configentry)\n  {\n    bool ret = _setInternalParam(param, value, updateStructs);\n\n    if (ret && configentry.length()) {\n      XrdOucString s;\n      s += \"[\";\n\n      for (size_t i = 0; i < param.size(); i++) {\n        s += (int)value[i];\n        s += \",\";\n      }\n\n      s[s.length() - 1] = ']';\n      setConfigValue(\"geosched\", configentry.c_str() , s.c_str());\n    }\n\n    return ret;\n  }\n\n  bool setInternalParam(std::vector<float>& param,\n                        const std::vector<float>& value, bool updateStructs,\n                        const std::string& configentry)\n  {\n    bool ret = _setInternalParam(param, value, updateStructs);\n\n    if (ret && configentry.length()) {\n      XrdOucString s;\n      s += \"[\";\n\n      for (size_t i = 0; i < param.size(); i++) {\n        char buf[32];\n        sprintf(buf, \"%f\", value[i]);\n        s += buf;\n        s += \",\";\n      }\n\n      s[s.length() - 1] = ']';\n      setConfigValue(\"geosched\", configentry.c_str() , s.c_str());\n    }\n\n    return ret;\n  }\n\n  // enum to specify the expected type of proxy scheduling\n  typedef enum {\n    filesticky, // try to map a given file as much as possible to a same proxy. This is to optimize caching in the Proxy.\n    regular,    // give priority to the closer and more idle proxy in a proxygroup\n    any         // do the regular scheduling for all the filesystems\n  } tProxySchedType;\n  bool findProxy(const std::vector<SchedTreeBase::tFastTreeIdx>& fsidxs,\n                 std::vector<SchedTME*> entries,\n                 ino64_t inode,\n                 std::vector<std::string>* proxies,\n                 std::vector<std::string>* proxyGroups = NULL,\n                 const std::string& clientgeotag = \"\",\n                 tProxySchedType proxyschedtype = regular);\n  bool markPendingBranchDisablings(const std::string& group,\n                                   const std::string& optype, const std::string& geotag);\n  bool applyBranchDisablings(const SchedTME& entry);\n  bool setSkipSaturatedAccess(bool value, bool setconfig = false);\n  bool setSkipSaturatedDrnAccess(bool value, bool setconfig = false);\n  bool setSkipSaturatedBlcAccess(bool value, bool setconfig = false);\n  bool setProxyCloseToFs(bool value, bool setconfig = false);\n  bool setScorePenalty(std::vector<float>& fvector, std::vector<char>& cvector,\n                       const std::vector<char>& value, const std::string& configentry);\n  bool setScorePenalty(std::vector<float>& fvector, std::vector<char>& cvector,\n                       const char* vvalue, const std::string& configentry);\n  bool setScorePenalty(std::vector<float>& fvector, std::vector<char>& cvector,\n                       char value, int netSpeedClass, const std::string& configentry);\n  bool setPlctDlScorePenalty(char value, int netSpeedClass,\n                             bool setconfig = false);\n  bool setPlctUlScorePenalty(char value, int netSpeedClass,\n                             bool setconfig = false);\n  bool setAccessDlScorePenalty(char value, int netSpeedClass,\n                               bool setconfig = false);\n  bool setAccessUlScorePenalty(char value, int netSpeedClass,\n                               bool setconfig = false);\n  bool setProxyScorePenalty(char value, int netSpeedClass,\n                            bool setconfig = false);\n  bool setPlctDlScorePenalty(const char* value, bool setconfig = false);\n  bool setPlctUlScorePenalty(const char* value, bool setconfig = false);\n  bool setAccessDlScorePenalty(const char* value, bool setconfig = false);\n  bool setAccessUlScorePenalty(const char* value, bool setconfig = false);\n  bool setProxyScorePenalty(const char* value, bool setconfig = false);\n  bool setFillRatioLimit(char value, bool setconfig = false);\n  bool setFillRatioCompTol(char value, bool setconfig = false);\n  bool setSaturationThres(char value, bool setconfig = false);\n  bool setTimeFrameDurationMs(int value, bool setconfig = false);\n  bool setPenaltyUpdateRate(float value, bool setconfig = false);\n  bool accessReqFwEP(const std::string& targetGeotag,\n                     const std::string& accesserGeotag) const ;\n  std::string accessGetProxygroup(const std::string& geotag) const ;\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  GeoTreeEngine(mq::MessagingRealm* realm);\n\n  // ---------------------------------------------------------------------------\n  //! Force a refresh of the information in the scheduling trees\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  bool forceRefreshSched();\n\n  // ---------------------------------------------------------------------------\n  //! Force a refresh of the information in the scheduling trees and in theproxy trees\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  bool forceRefresh();\n\n  // ---------------------------------------------------------------------------\n  //! Insert a file system into the GeoTreeEngine\n  // @param fs\n  //   the file system to be inserted\n  // @param group\n  //   the group the file system belongs to\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  bool insertFsIntoGroup(FileSystem* fs , FsGroup* group,\n                         const common::FileSystemCoreParams& coreParams);\n\n  // ---------------------------------------------------------------------------\n  //! Remove a file system into the GeoTreeEngine\n  // @param fs\n  //   the file system to be removed\n  // @param group\n  //   the group the file system belongs to\n  // @param updateFastStructures\n  //   should the fast structures be updated immediately without waiting for the next time frame\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  bool removeFsFromGroup(FileSystem* fs , FsGroup* group,\n                         bool updateFastStructures = true);\n\n  // ---------------------------------------------------------------------------\n  //! Remove a file system into the GeoTreeEngine\n  // @param group\n  //   the group the file system belongs to\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  bool removeGroup(FsGroup* group);\n\n  // ---------------------------------------------------------------------------\n  //! Print formated information about the GeoTreeEngine\n  // @param info\n  //   the string to which info is to be written\n  // @param dispTree\n  //   do trees should be printed\n  // @param dispSnaps\n  //   do snapshots should be printed\n  // @param dispLs\n  //   do internal state should be printed\n  // @param schedgroup\n  //   narrow down information to this schedgroup\n  // @param schedgroup\n  //   narrow down information to this type of operation\n  // ---------------------------------------------------------------------------\n  void printInfo(std::string& info,\n                 bool dispTree, bool dispSnaps, bool dispParam, bool dispState,\n                 const std::string& schedgroup, const std::string& optype,\n                 bool useColors = false, bool monitoring = false);\n\n  // ---------------------------------------------------------------------------\n  //! Print formated information about the GeoTreeEngine\n  // @param space name of the space\n  // @param name of a particular group or empty\n  // @return number of bytes available for writing\n  // ---------------------------------------------------------------------------\n  uint64_t placementSpace(const std::string& space,\n                          const std::string& schedgroup);\n\n  // ---------------------------------------------------------------------------\n  //! Place several replicas in one scheduling group.\n  // @param group\n  //   the group to place the replicas in\n  // @param nNewReplicas\n  //   the number of replicas to be placed\n  // @param newReplicas\n  //   vector to which fsids of new replicas are appended if the placement\n  //   succeeds. They are appended in decreasing priority order\n  // @param inode\n  //   inode of the file to place, used for filesticky proxy scheduling\n  // @param dataProxys\n  //   if this pointer is non NULL, one proxy is returned for each filesystem returned\n  //   if they have a proxygroup defined\n  //   if a fs has proxygroup and no proxy could be found, the placement operation fails\n  // @param firewallEntryPoints\n  //   if this pointer is non NULL, one firewall entry point is returned for each filesystem returned\n  //   if no entry point could be found for an fs, the placement operation fails\n  // @param type\n  //   type of placement to be performed. It can be:\n  //     regularRO, regularRW, balancing or draining\n  // @param existingReplicas\n  //   fsids of preexisting replicas for the current file\n  //   this is important to make a a good placement (e.g. skip the same fs)\n  // @param bookingSize\n  //   the space to be booked on the fs\n  //   currently, it's not booking. It's only checking that there is enough space.\n  // @param startFromGeoTag\n  //   try to place the files under this geotag\n  //   useful to group up replicas or to replace a replica by a new one nearby\n  // @param clientGeoTag\n  //   try to place the proxys (data and firewall) close to the client\n  // @param nCollocatedReplicas\n  //   among the nNewReplicas, nCollocatedReplicas are placed as close as possible to startFromGeoTag\n  //   the other ones are scattered out as much as possible in the tree\n  //   this count includes the existingReplicas\n  // @param excludeFs\n  //   fsids of files to exclude from the placement operation\n  // @param excludeGeoTags\n  //   geotags of branches to exclude from the placement operation\n  //     (e.g. exclude a site)\n  // @param forceGeoTags\n  //   geotags of branches new replicas should be taken from\n  //     (e.g. force a site)\n  // @return\n  //   true if the success false else\n  // ---------------------------------------------------------------------------\n  bool placeNewReplicasOneGroup(FsGroup* group, const size_t& nNewReplicas,\n                                std::vector<eos::common::FileSystem::fsid_t>* newReplicas,\n                                ino64_t inode,\n                                std::vector<std::string>* dataProxys,\n                                std::vector<std::string>* firewallEntryPoints,\n                                SchedType type,\n                                std::vector<eos::common::FileSystem::fsid_t>* existingReplicas,\n                                std::vector<std::string>* fsidsgeotags = 0,\n                                unsigned long long bookingSize = 0,\n                                const std::string& startFromGeoTag = \"\",\n                                const std::string& clientGeoTag = \"\",\n                                const size_t& nCollocatedReplicas = 0,\n                                std::vector<eos::common::FileSystem::fsid_t>* excludeFs = NULL,\n                                std::vector<std::string>* excludeGeoTags = NULL);\n\n  // this function to access replica spread across multiple scheduling group is a BACKCOMPATIBILITY artifact\n  // the new scheduler doesn't try to place files across multiple scheduling groups.\n  //  bool accessReplicasMultipleGroup(const size_t &nAccessReplicas,\n  //      std::vector<eos::common::FileSystem::fsid_t> *accessedReplicas,\n  //      std::vector<eos::common::FileSystem::fsid_t> *existingReplicas,\n  //      SchedType type=regularRO,\n  //      const std::string &accesserGeotag=\"\",\n  //      std::vector<eos::common::FileSystem::fsid_t> *excludeFs=NULL,\n  //      std::vector<std::string> *excludeGeoTags=NULL);\n\n  // ---------------------------------------------------------------------------\n  //! Access replicas across one or several scheduling group.\n  //! Check that the right number of replicas is online.\n  //! return the best possible head replica\n  // @param [in] nReplicas\n  //   the number of replicas to access - must be > 0\n  // @param [out] fsindex\n  //   return the index of the head replica in the existingReplicas vector\n  // @param [in] existingReplicas\n  //   fsids of preexisting replicas for the current file\n  // @param  [in] inode\n  //   inode of the file to place, used for filesticky proxy scheduling\n  // @param dataProxys\n  //   if this pointer is non NULL, one proxy is returned for each filesystem returned\n  //   if they have a proxygroup defined\n  //   if a fs has proxygroup and no proxy could be found, the access operation fails\n  // @param firewallEntryPoints\n  //   if this pointer is non NULL, one firewall entry point is returned for each filesystem returned\n  //   if no entry point could be found for an fs, the access operation fails\n  // @param type\n  //   type of access to be performed. It can be:\n  //     regularRO, regularRW, balancing or draining\n  // @param [in] accesserGeoTag\n  //   try to get the replicas as close to this geotag as possible\n  // @param [in] forcedFsId\n  //   if non zeros, force the head replica fsid; The Forced FSID must be present in the existingReplicas vector\n  // @param  [in/out] unavailableFs\n  //   return the unavailable file systems for the current access operation\n  // @return\n  //   EROFS   if not enough replicas are provided to the function to\n  //           make sure that enough replicas are available for this access\n  //   ENODATA if the forced head replica is not in the provided replicas\n  //   EIO     if some internal inconsistency arises\n  //   ENONET  if there is not enough available fs among the provided ones\n  //           for this access operation\n  //   0       if success\n  // ---------------------------------------------------------------------------\n  int accessHeadReplicaMultipleGroup(size_t nReplicas,\n                                     unsigned long& fsIndex,\n                                     const std::vector<eos::common::FileSystem::fsid_t>& existingReplicas,\n                                     ino64_t inode,\n                                     std::vector<std::string>* dataProxys,\n                                     std::vector<std::string>* firewallEntryPoints,\n                                     SchedType type = regularRO,\n                                     const std::string& accesserGeotag = \"\",\n                                     eos::common::FileSystem::fsid_t forcedFsId = 0,\n                                     std::vector<eos::common::FileSystem::fsid_t>* unavailableFs = nullptr\n                                    );\n\n  int accessProxyFirewall(const std::vector<SchedTreeBase::tFastTreeIdx>& ERIdx,\n                          const std::vector<SchedTME*>& entries,\n                          ino64_t inode,\n                          std::vector<std::string>* dataProxys,\n                          std::vector<std::string>* firewallEntryPoint,\n                          const std::string& accesserGeotag);\n\n  // ---------------------------------------------------------------------------\n  //! Start the background updater thread\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  void StartUpdater();\n\n  // ---------------------------------------------------------------------------\n  //! Pause the updating of the GeoTreeEngine but keep accumulating\n  //! modification notifications\n  // ---------------------------------------------------------------------------\n  inline static bool PauseUpdater()\n  {\n    if (gUpdaterStarted && !gUpdaterPaused) {\n      timespec ts;\n      eos::common::Timing::GetTimeSpec(ts, false);\n      ts.tv_sec +=\n        2; // we wait for two seconds and then we fail. It avoids deadlocking when no update is received (No FST)\n      int rc = 0;\n\n      while ((rc = sem_timedwait(&gUpdaterPauseSem, &ts)) && errno == EINTR) {\n        continue;\n      }\n\n      if (rc && (errno == ETIMEDOUT)) {\n        return false;\n      }\n\n      if (rc && errno) {\n        throw \"sem_timedwait() failed\";\n      }\n\n      gUpdaterPaused = true;\n      return true;\n    }\n\n    return true; // already paused\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Resume the updating of the GeoTreeEngine\n  //! Process all the notifications accumulated since it was paused\n  // ---------------------------------------------------------------------------\n  inline static void ResumeUpdater()\n  {\n    if (gUpdaterStarted && gUpdaterPaused) {\n      if (sem_post(&gUpdaterPauseSem)) {\n        throw \"sem_post() failed\";\n      }\n\n      gUpdaterPaused = false;\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Stop the background updater thread\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  void StopUpdater();\n\n  // ---------------------------------------------------------------------------\n  //! Get the fs informations in the GeotreeEngine\n  //! It's faster than accessing the MqHash\n  // @param fsids\n  //   a vector containing the FsIds\n  // @param fsgeotags\n  //   return if non NULL, geotags of the fsids are reported in this vector\n  // @param hosts\n  //   return if non NULL, hosts of the fsids are reported in this vector\n  // @param sortedgroups\n  //   return if non NULL, get the list of groups in decreasing order of number of fs in the list they contain\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  bool getInfosFromFsIds(const std::vector<FileSystem::fsid_t>& fsids,\n                         std::vector<std::string>* fsgeotags,\n                         std::vector<std::string>* hosts, std::vector<FsGroup*>* sortedgroups);\n\n  // ---------------------------------------------------------------------------\n  //! Set an internal parameter to a value\n  // @param param\n  //   the name of the parameter to set\n  // @param value\n  //   the value of the parameter to set\n  // @param iparamidx\n  //   in case this parameter is a vector, it's the index of the value to set\n  //   if iparamidx == -1, sets all the values of the elevemets of the vector to the same passed value\n  //   if iparamidx == -2, the value string contains all the values in the vector e.g.: \"[2,3,4]\"\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  bool setParameter(std::string param, const std::string& value, int iparamidx,\n                    bool setconfig = false);\n\n  // ---------------------------------------------------------------------------\n  //! Add a branch disabling rule\n  // @param group\n  //   group name or \"*\"\n  // @param optype\n  //   \"*\" or one of the following plct,accsro,accsrw,accsdrain,plctdrain\n  // @param geotag\n  //   geotag of the branch to disable\n  // @param output\n  //   if non NULL, issue error messages there\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  bool addDisabledBranch(const std::string& group, const std::string& optype,\n                         const std::string& geotag, XrdOucString* output = NULL, bool toConfig = false);\n\n  // ---------------------------------------------------------------------------\n  //! Rm a branch disabling rule\n  // @param group\n  //   group name or \"*\"\n  // @param optype\n  //   \"*\" or one of the following plct,accsro,accsrw,accsdrain,plctdrain\n  // @param geotag\n  //   geotag of the branch to disable\n  // @param output\n  //   if non NULL, issue error messages there\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  bool rmDisabledBranch(const std::string& group, const std::string& optype,\n                        const std::string& geotag, XrdOucString* output = NULL, bool toConfig = false);\n\n  // ---------------------------------------------------------------------------\n  //! Show branch disabling rules\n  // @param group\n  //   group name or \"*\"\n  // @param optype\n  //   \"*\" or one of the following plct,accsro,accsrw,accsdrain,plctdrain\n  // @param geotag\n  //   geotag of the branch to disable\n  // @param output\n  //   the display is appended to that string\n  // @param lock\n  //   lock the config param mutex\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  bool showDisabledBranches(const std::string& group, const std::string& optype,\n                            const std::string& geotag, XrdOucString* output, bool lock = true);\n\n  // ---------------------------------------------------------------------------\n  //! Set an access geotag mapping.\n  // @param geotag\n  //   geotag of the accesser\n  // @param geotag list\n  //   a list of geotags (separted by commas) defining subtrees of the geotree\n  //   that can be accessed by the accesser\n  // @param updateFastStruct\n  //        update the fast structures too (needs to be done for the cchange to be effective)\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  inline bool setAccessGeotagMapping(XrdOucString* output,\n                                     const std::string& geotag, const std::string& geotaglist,\n                                     bool updateFastStruct = true, bool setconfig = true)\n  {\n    bool ret = pAccessGeotagMapping.setMapping(geotag, geotaglist, updateFastStruct,\n               setconfig);\n\n    if (!ret && output) {\n      *output += \"Error: failed to add direct access geotag mapping\";\n    }\n\n    return ret;\n  }\n  inline bool setAccessGeotagMapping(const std::string& geotag,\n                                     bool setconfig = false)\n  {\n    return pAccessGeotagMapping.setMapping(geotag, setconfig);\n  }\n\n\n  // ---------------------------------------------------------------------------\n  //! Set an access geotag mapping.\n  // @param geotag\n  //   geotag of the accesser for which to clear the mapping\n  //   if empty, all the mappings are deleted\n  // @param updateFastStruct\n  //        update the fast structures too (needs to be done for the cchange to be effective)\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  inline bool clearAccessGeotagMapping(XrdOucString* output,\n                                       const std::string& geotag = \"\", bool updateFastStruct = true,\n                                       bool setconfig = true)\n  {\n    bool ret = pAccessGeotagMapping.clearMapping(geotag, updateFastStruct,\n               setconfig);\n\n    if (!ret && output) {\n      *output += \"Error: failed to clear direct access geotag mapping\";\n    }\n\n    if (ret && geotag.empty() && output) {\n      *output += \"Cleared all direct access geotag mappings\";\n    }\n\n    return ret;\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Set an access geotag mapping.\n  // @param output\n  //   the display is appended to that string\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  inline bool showAccessGeotagMapping(XrdOucString* output, bool monitoring)\n  {\n    if (!pAccessGeotagMapping.inuse) {\n      *output +=\n        \"There is no direct access geotag mapping defined. All file accesses will be scheduled as direct accesses.\";\n      return true;\n    }\n\n    return pAccessGeotagMapping.showMapping(output, \"AccessGeotagMapping\",\n                                            monitoring);\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Set an access proxygroup mapping.\n  // @param geotag\n  //   geotag of the accesser\n  // @param proxygroup\n  //   name of the proxygroup acting as firewall entrypoint for the subtree starting at the geotag\n  // @param updateFastStruct\n  //        update the fast structures too (needs to be done for the cchange to be effective)\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  inline bool setAccessProxygroup(XrdOucString* output, const std::string& geotag,\n                                  const std::string& proxygroup, bool updateFastStruct = true,\n                                  bool setconfig = true)\n  {\n    bool ret = pAccessProxygroup.setMapping(geotag, proxygroup, updateFastStruct,\n                                            setconfig);\n\n    if (!ret && output) {\n      *output += \"Error: failed to add access proxygroup mapping\";\n    }\n\n    return ret;\n  }\n  inline bool setAccessProxygroup(const std::string& geotag,\n                                  bool setconfig = false)\n  {\n    return pAccessProxygroup.setMapping(geotag, setconfig);\n  }\n\n\n  // ---------------------------------------------------------------------------\n  //! Remove an (or all) access geotag mapping.\n  // @param geotag\n  //   geotag of the accesser for which to clear the mapping\n  //   if empty, all the mappings are deleted\n  // @param updateFastStruct\n  //        update the fast structures too (needs to be done for the cchange to be effective)\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  inline bool clearAccessProxygroup(XrdOucString* output,\n                                    const std::string& geotag = \"\", bool updateFastStruct = true,\n                                    bool setconfig = true)\n  {\n    bool ret = pAccessProxygroup.clearMapping(geotag, updateFastStruct, setconfig);\n\n    if (!ret && output) {\n      *output += \"Error: failed to clear access proxygroup mapping\";\n    }\n\n    if (ret && geotag.empty() && output) {\n      *output += \"Cleared all access proxygroup mappings\";\n    }\n\n    return ret;\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Set an access proxygroup mapping.\n  // @param output\n  //   the display is appended to that string\n  // @return\n  //   true if success false else\n  // ---------------------------------------------------------------------------\n  inline bool showAccessProxygroup(XrdOucString* output, bool monitoring)\n  {\n    if (!pAccessProxygroup.inuse) {\n      *output +=\n        \"There is no access proxygroup mapping defined. No firewall entry point access can be scheduled.\";\n      return true;\n    }\n\n    return pAccessProxygroup.showMapping(output, \"AccessProxygroupMapping\",\n                                         monitoring);\n  }\n  //! [public member functions]\n\n};\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/groupbalancer/BalancerEngine.cc",
    "content": "#include \"BalancerEngine.hh\"\n#include \"common/Logging.hh\"\n#include \"mgm/groupbalancer/BalancerEngineUtils.hh\"\n#include \"common/table_formatter/TableFormatterBase.hh\"\n#include \"common/utils/ContainerUtils.hh\"\n#include \"common/utils/RandUtils.hh\"\n\nnamespace eos::mgm::group_balancer\n{\n\nvoid BalancerEngine::populateGroupsInfo(group_size_map&& info)\n{\n  clear();\n  data.mGroupSizes = std::move(info);\n  recalculate();\n  updateGroups();\n}\n\nvoid BalancerEngine::clear_threshold(const std::string& group_name)\n{\n  data.mGroupsOverThreshold.erase(group_name);\n  data.mGroupsUnderThreshold.erase(group_name);\n}\n\nvoid BalancerEngine::clear_thresholds()\n{\n  data.mGroupsOverThreshold.clear();\n  data.mGroupsUnderThreshold.clear();\n}\n\nvoid BalancerEngine::clear()\n{\n  data.mGroupSizes.clear();\n  clear_thresholds();\n}\n\n\n\nvoid BalancerEngine::updateGroups()\n{\n  clear_thresholds();\n\n  if (!data.mGroupSizes.size()) {\n    return;\n  }\n\n  for (const auto& kv : data.mGroupSizes) {\n    updateGroup(kv.first);\n  }\n}\n\n\ngroups_picked_t\nBalancerEngine::pickGroupsforTransfer()\n{\n  if (data.mGroupsUnderThreshold.size() == 0 ||\n      data.mGroupsOverThreshold.size() == 0) {\n    if (data.mGroupsOverThreshold.size() == 0) {\n      eos_static_debug(\"No groups over the average!\");\n    }\n\n    if (data.mGroupsUnderThreshold.size() == 0) {\n      eos_static_debug(\"No groups under the average!\");\n    }\n\n    recalculate();\n    return {};\n  }\n\n  auto over_it = data.mGroupsOverThreshold.begin();\n  auto under_it = data.mGroupsUnderThreshold.begin();\n  int rndIndex = common::getRandom(0ul, data.mGroupsOverThreshold.size() - 1);\n  std::advance(over_it, rndIndex);\n  rndIndex = common::getRandom(0ul, data.mGroupsUnderThreshold.size() - 1);\n  std::advance(under_it, rndIndex);\n  return {*over_it, *under_it};\n}\n\n\nstd::string BalancerEngine::generate_table(const threshold_group_set& groups)\nconst\n{\n  TableFormatterBase table_threshold_groups(true);\n  std::string format_s = \"-s\";\n  std::string format_l = \"+l\";\n  std::string format_f = \"f\";\n  table_threshold_groups.SetHeader({\n    {\"Group\", 10, \"-s\"},\n    {\"UsedBytes\", 10, \"+l\"},\n    {\"Capacity\", 10, \"+l\"},\n    {\"Filled\", 10, \"f\"}\n  });\n  TableData table_data;\n\n  for (const auto& grp : groups) {\n    const auto kv = data.mGroupSizes.find(grp);\n\n    if (kv == data.mGroupSizes.end()) {\n      continue;\n    }\n\n    TableRow row;\n    row.emplace_back(grp, \"-s\");\n    // FIXME force a double conversion as the current table cell ultimately will\n    // use a double when using + anyway. a TODO is have the table formatter\n    // itself understand uint64_t type and avoid a double conversion as units\n    // can be done without\n    row.emplace_back((double)kv->second.usedBytes(), format_l);\n    row.emplace_back((double)kv->second.capacity(), format_l);\n    row.emplace_back(kv->second.filled(), format_f);\n    table_data.emplace_back(std::move(row));\n  }\n\n  table_threshold_groups.AddRows(table_data);\n  return table_threshold_groups.GenerateTable();\n}\n\n\nstd::string BalancerEngine::get_status_str(bool detail, bool monitoring) const\n{\n  std::stringstream oss;\n\n  if (monitoring) {\n    oss << \"groupbalancer.groups_over_threshold=\" <<\n        data.mGroupsOverThreshold.size()\n        << \" groupbalancer.groups_under_threshold=\" <<\n        data.mGroupsUnderThreshold.size();\n    return oss.str();\n  }\n\n  oss << \"Total Group Size: \" << data.mGroupSizes.size() << \"\\n\"\n      << \"Total Groups Over Threshold: \" << data.mGroupsOverThreshold.size() << \"\\n\"\n      << \"Total Groups Under Threshold: \" << data.mGroupsUnderThreshold.size() <<\n      \"\\n\";\n\n  if (detail) {\n    if (!data.mGroupSizes.empty()) {\n      double min_filled = 100.0, max_filled = 0.0;\n\n      for (const auto& [name, info] : data.mGroupSizes) {\n        double filled = info.filled();\n\n        if (filled < min_filled) {\n          min_filled = filled;\n        }\n\n        if (filled > max_filled) {\n          max_filled = filled;\n        }\n      }\n\n      oss << \"Average Fill Level: \" << std::fixed << std::setprecision(2)\n          << calculateAvg(data.mGroupSizes) << \"%\\n\"\n          << \"Fill Level Range: \" << std::fixed << std::setprecision(2)\n          << min_filled << \"% - \" << max_filled << \"%\\n\";\n    }\n\n    oss << \"Groups Over Threshold (Source Groups)\\n\";\n    oss << generate_table(data.mGroupsOverThreshold) << \"\\n\";\n    oss << \"Groups Under Threshold (Target Groups)\\n\";\n    oss << generate_table(data.mGroupsUnderThreshold) << \"\\n\";\n  }\n\n  return oss.str();\n}\n\ngroups_picked_t\nBalancerEngine::pickGroupsforTransfer(uint64_t index)\n{\n  if (data.mGroupsUnderThreshold.size() == 0 ||\n      data.mGroupsOverThreshold.size() == 0) {\n    if (data.mGroupsOverThreshold.size() == 0) {\n      eos_static_debug(\"No groups over the average!\");\n    }\n\n    if (data.mGroupsUnderThreshold.size() == 0) {\n      eos_static_debug(\"No groups under the average!\");\n    }\n\n    recalculate();\n    return {};\n  }\n\n  return {common::pickIndexRR(data.mGroupsOverThreshold, index),\n          common::pickIndexRR(data.mGroupsUnderThreshold, index)};\n}\n\n} // eos::mgm::GroupBalancer\n"
  },
  {
    "path": "mgm/groupbalancer/BalancerEngine.hh",
    "content": "//------------------------------------------------------------------------------\n// File: BalancerEngine.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include <map>\n#include <numeric>\n#include <cstdint>\n#include <string>\n#include <unordered_set>\n#include <random>\n#include \"mgm/groupbalancer/BalancerEngineTypes.hh\"\n\nnamespace eos::mgm::group_balancer\n{\n\nstruct IBalancerEngine {\n  // //----------------------------------------------------------------------------\n  // // Fills mGroupSizes, calculates avg and classifies the data\n  // //----------------------------------------------------------------------------\n  // virtual void populateGroupsInfo(IGroupsInfoFetcher* f) = 0;\n\n\n  //----------------------------------------------------------------------------\n  //! Recalculates the sizes average from the mGroupSizes\n  //----------------------------------------------------------------------------\n  virtual void recalculate() = 0;\n\n  //----------------------------------------------------------------------------\n  //! clears all data structures, also used when re-filling all info\n  //----------------------------------------------------------------------------\n  virtual void clear() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Classifies a given group in one of the 2 categories\n  //----------------------------------------------------------------------------\n  virtual void updateGroup(const std::string& group_name) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Classifies all groups in one of the 2 categories\n  //----------------------------------------------------------------------------\n  virtual void updateGroups() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Return randomly a pair of groups over avg & under avg that will be used\n  //! for transferring\n  //----------------------------------------------------------------------------\n  virtual groups_picked_t pickGroupsforTransfer() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Return a pair of groups over avg & under avg at given index\n  //----------------------------------------------------------------------------\n  virtual groups_picked_t pickGroupsforTransfer(uint64_t index) = 0;\n\n  virtual void configure(const engine_conf_t& conf) = 0;\n\n  virtual const group_size_map& get_group_sizes() const = 0;\n\n  virtual std::string get_status_str(bool detail, bool monitoring) const = 0;\n\n  virtual ~IBalancerEngine() {};\n};\n\nstruct BalancerEngineData {\n  threshold_group_set mGroupsOverThreshold;\n  threshold_group_set mGroupsUnderThreshold;\n  group_size_map mGroupSizes;\n};\n\n// A simple base class implementing common functionalities for most BalancerEngines\n// Note that this class doesn't implement the entire interface, so cannot be constructed!\nclass BalancerEngine: public IBalancerEngine\n{\npublic:\n  virtual void populateGroupsInfo(group_size_map&& info);\n  void updateGroups() override;\n  void clear() override;\n\n  const group_size_map& get_group_sizes() const override\n  {\n    return data.mGroupSizes;\n  }\n\n  std::string get_status_str(bool detail = false,\n                             bool monitoring = false) const override;\n\n  BalancerEngine() = default;\n\n  virtual ~BalancerEngine() = default;\n\n  // Only useful for unit-testing/ validating the status of the balancerengine\n  const BalancerEngineData& get_data() const\n  {\n    return data;\n  }\n\n  groups_picked_t pickGroupsforTransfer() override;\n\n  groups_picked_t pickGroupsforTransfer(uint64_t index) override;\n\n  bool canPick() const\n  {\n    return !data.mGroupsOverThreshold.empty() &&\n           !data.mGroupsUnderThreshold.empty();\n  }\n\n  size_t sourceGroupCount() const\n  {\n    return data.mGroupsOverThreshold.size();\n  }\n\n  size_t targetGroupCount() const\n  {\n    return data.mGroupsUnderThreshold.size();\n  }\n\nprotected:\n  BalancerEngineData data;\n\n  void clear_threshold(const std::string& group_name);\n  void clear_thresholds();\n  std::string generate_table(const threshold_group_set& items) const;\n\n};\n\n\n} // namespace eos::mgm::group_balancer\n"
  },
  {
    "path": "mgm/groupbalancer/BalancerEngineFactory.hh",
    "content": "//------------------------------------------------------------------------------\n// File: BalancerEngineFactory.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/groupbalancer/StdDevBalancerEngine.hh\"\n#include \"mgm/groupbalancer/MinMaxBalancerEngine.hh\"\n#include \"mgm/groupbalancer/FreeSpaceBalancerEngine.hh\"\n\nnamespace eos::mgm::group_balancer\n{\n\nBalancerEngineT get_engine_type(std::string_view name)\n{\n  if (name == \"minmax\") {\n    return BalancerEngineT::minmax;\n  } else if (name == \"freespace\") {\n    return BalancerEngineT::freespace;\n  }\n\n  return BalancerEngineT::stddev;\n}\n\nBalancerEngine* make_balancer_engine(BalancerEngineT engine_t)\n{\n  if (engine_t == BalancerEngineT::minmax) {\n    return new MinMaxBalancerEngine();\n  } else if (engine_t == BalancerEngineT::freespace) {\n    return new FreeSpaceBalancerEngine();\n  }\n\n  return new StdDevBalancerEngine();\n}\n\n}\n"
  },
  {
    "path": "mgm/groupbalancer/BalancerEngineTypes.hh",
    "content": "//------------------------------------------------------------------------------\n// File: BalancerEngineTypes.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include <cstdint>\n#include <string_view>\n#include <string>\n#include <set>\n#include <map>\n\n// Some enums and typedefs for various types used in BalancerEngines\nnamespace eos::mgm::group_balancer\n{\n\n// enum representing the various states a group can be in\nenum class GroupStatus {\n  ON,\n  OFF,\n  DRAIN,\n  DRAINCOMPLETE,\n  DRAINFAILED\n};\n\ninline constexpr GroupStatus getGroupStatus(std::string_view status)\n{\n  using namespace std::string_view_literals;\n\n  if (status.compare(\"on\"sv) == 0) {\n    return GroupStatus::ON;\n  } else if (status.compare(\"drain\"sv) == 0) {\n    return GroupStatus::DRAIN;\n  } else if (status.compare(\"draincomplete\"sv) == 0) {\n    return GroupStatus::DRAINCOMPLETE;\n  } else if (status.compare(\"drainfailed\"sv) == 0) {\n    return GroupStatus::DRAINCOMPLETE;\n  }\n\n  return GroupStatus::OFF;\n}\n\ninline std::string GroupStatusToStr(GroupStatus s)\n{\n  switch (s) {\n  case GroupStatus::ON:\n    return \"on\";\n\n  case GroupStatus::OFF:\n    return \"off\";\n\n  case GroupStatus::DRAIN:\n    return \"drain\";\n\n  case GroupStatus::DRAINCOMPLETE:\n    return \"drained\";\n\n  case GroupStatus::DRAINFAILED:\n    return \"drainfailed\";\n  }\n\n  // unknown status - unreachable as far as new types are in switch\n  return \"on\";\n}\n\n//------------------------------------------------------------------------------\n//! @brief Class representing a group's size\n//! It holds the capacity and the current used space of a group.\n//------------------------------------------------------------------------------\nclass GroupSizeInfo\n{\npublic:\n  //------------------------------------------------------------------------------\n  //! Constructor\n  //------------------------------------------------------------------------------\n  GroupSizeInfo(uint64_t usedBytes, uint64_t capacity)\n    : mStatus(GroupStatus::ON), mSize(usedBytes), mCapacity(capacity)\n  {\n  }\n\n  GroupSizeInfo(GroupStatus status, uint64_t usedBytes, uint64_t capacity)\n    : mStatus(status), mSize(usedBytes), mCapacity(capacity)\n  {}\n  //------------------------------------------------------------------------------\n  //! Subtracts the given size from this group and adds it to the given toGroup\n  //!\n  //! @param toGroup the group where to add the size\n  //! @param size the file size that should be swapped\n  //------------------------------------------------------------------------------\n  void swapFile(GroupSizeInfo* toGroup, uint64_t size)\n  {\n    toGroup->mSize += size;\n    mSize -= size;\n  }\n\n  uint64_t\n  usedBytes() const\n  {\n    return mSize;\n  }\n\n  uint64_t\n  capacity() const\n  {\n    return mCapacity;\n  }\n\n  double\n  filled() const\n  {\n    return (double) mSize / (double) mCapacity;\n  }\n\n  bool draining() const\n  {\n    return mStatus == GroupStatus::DRAIN;\n  }\n\n  bool on() const\n  {\n    return mStatus == GroupStatus::ON;\n  }\n\nprivate:\n  GroupStatus mStatus;\n  uint64_t mSize;\n  uint64_t mCapacity;\n};\n\n// std::less<> basically allows for transparent compare of keys,\n// allowing std::string_view -> std::string lookups on keys\n// please note when doing const char*, unless explicitly converted using for eg\n// operator \"\"sv, you'll end up creating a new overload which would be unecessary\n// normally this promises find/count etc will work without allocations which is a plus\n// eg. group_size_map.find(\"default.20\"sv)  // good\n//     group_size_map.find(some_str)       // good\n//     group_size_map.find(\"default.20\") // bad will use an overload of std::less<char[xx]> here\nusing group_size_map = std::map<std::string, GroupSizeInfo, std::less<>>;\nusing threshold_group_set = std::set<std::string>;\nusing groups_picked_t = std::pair<std::string, std::string>;\nusing engine_conf_t = std::map<std::string, std::string, std::less<>>;\n\nenum class BalancerEngineT {\n  stddev,\n  minmax,\n  freespace,\n  total_count\n};\n\ninline bool engine_should_average(BalancerEngineT engine) {\n  return engine != BalancerEngineT::freespace;\n}\n\n}\n\n"
  },
  {
    "path": "mgm/groupbalancer/BalancerEngineUtils.hh",
    "content": "//------------------------------------------------------------------------------\n// File: BalancerEngineUtils.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#pragma once\n\n#include <string>\n#include <numeric>\n#include <random>\n#include <functional>\n#include \"common/StringUtils.hh\"\n#include \"common/StringSplit.hh\"\n#include \"mgm/groupbalancer/BalancerEngine.hh\"\n\nnamespace eos::mgm::group_balancer\n{\n\nnamespace detail\n{\n// uses CTAD\ntemplate <typename Fn>\nusing ret_type_t = typename decltype(std::function{std::declval<Fn>()})::result_type;\n} // detail\n\ninline double calculateAvg(const group_size_map& m)\n{\n  if (!m.size()) {\n    return 0.0;\n  }\n\n  return std::accumulate(m.begin(), m.end(), 0.0,\n                         [](double s, const auto & kv) -> double\n  { return s + kv.second.filled(); }) / m.size();\n}\n\n\ntemplate <typename map_type, typename key_type, typename Fn,\n          typename value_type = detail::ret_type_t<Fn>>\nvalue_type\nextract_value(const map_type& m, const key_type& k,\n              Fn extractor_fn)\n{\n  auto kv = m.find(k);\n  if (kv != m.end()) {\n    return extractor_fn(kv->second);\n  }\n\n  return extractor_fn(\"\");\n}\n\ntemplate <typename map_type, typename key_type>\ndouble extract_double_value(const map_type& m, const key_type& k,\n                            double default_val = 0.0,\n                            std::string* err_str = nullptr)\n{\n  auto double_extractor = [&default_val, err_str](const std::string& str) {\n    double value;\n    common::StringToNumeric(str, value, default_val, err_str);\n    return value;\n  };\n\n  return extract_value(m, k, double_extractor);\n}\n\ntemplate <typename... Args>\ndouble extract_percent_value(Args&& ... args)\n{\n  double value = extract_double_value(std::forward<Args>(args)...);\n  return value / 100.0;\n}\n\ntemplate <typename map_type, typename key_type>\nstd::unordered_set<std::string>\nextract_commalist_value(const map_type& m, const key_type& k)\n{\n  using namespace std::string_view_literals;\n  auto cl_extractor_fn = [](std::string_view value) {\n    return common::StringSplit<std::unordered_set<std::string>>(value, \", \");\n  };\n  return extract_value(m, k, cl_extractor_fn);\n}\n\ninline bool is_valid_threshold(const std::string& threshold_str)\n{\n  double d;\n\n  try {\n    d = std::stod(threshold_str);\n  } catch (std::exception& e) {\n    return false;\n  }\n\n  return d > 0;\n}\n\ntemplate <typename... Args>\ninline bool is_valid_threshold(const std::string& threshold_str,\n                               Args&& ... args)\n{\n  return is_valid_threshold(threshold_str) && is_valid_threshold(args...);\n}\n\n} // eos::mgm::group_balancer\n"
  },
  {
    "path": "mgm/groupbalancer/ConverterUtils.cc",
    "content": "#include \"ConverterUtils.hh\"\n\n#include \"common/StringUtils.hh\"\n#include \"common/Logging.hh\"\n#include \"common/LayoutId.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"namespace/interface/IView.hh\"\n#include <XrdOuc/XrdOucString.hh>\n\nnamespace eos::mgm::group_balancer\n{\nbool PrefixFilter::operator()(std::string_view path) {\n    return eos::common::startsWith(path, prefix);\n}\n\nstd::string\ngetFileProcTransferNameAndSize(eos::common::FileId::fileid_t fid,\n                               const std::string& target_group, uint64_t* size,\n                               const SkipFileFn& skip_file_fn)\n{\n  char fileName[1024];\n  std::shared_ptr<eos::IFileMD> fmd;\n  eos::common::LayoutId::layoutid_t layoutid = 0;\n  eos::common::FileId::fileid_t fileid = 0;\n  {\n    eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, fid);\n\n    try {\n      fmd = gOFS->eosFileService->getFileMD(fid);\n      std::string fmdUri = gOFS->eosView->getUri(fmd.get());\n      auto fmdLock = eos::MDLocking::readLock(fmd.get());\n      layoutid = fmd->getLayoutId();\n      fileid = fmd->getId();\n\n      if (fmd->getContainerId() == 0) {\n        return std::string(\"\");\n      }\n\n      if (skip_file_fn && skip_file_fn(fmdUri)) {\n        return std::string(\"\");\n      }\n\n      if (size) {\n        *size = fmd->getSize();\n      }\n\n      eos_static_debug(\"msg=\\\"found file for transfering\\\" fid=\\\"%08llx\\\"\",\n                       fileid);\n    } catch (eos::MDException& e) {\n      eos_static_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                       e.getMessage().str().c_str());\n      return std::string(\"\");\n    }\n  }\n  snprintf(fileName, 1024, \"%s/%016llx:%s#%08lx\",\n           gOFS->MgmProcConversionPath.c_str(), fileid, target_group.c_str(),\n           (unsigned long)layoutid);\n  return std::string(fileName);\n}\n\n} // eos::mgm::group_balancer\n"
  },
  {
    "path": "mgm/groupbalancer/ConverterUtils.hh",
    "content": "//------------------------------------------------------------------------------\n// File: ConverterUtils.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_CONVERTERUTILS_HH\n#define EOS_CONVERTERUTILS_HH\n\n#include <functional>\n#include <string_view>\n\n#include \"common/FileId.hh\"\n#include <string>\n\nnamespace eos::mgm::group_balancer\n{\n\nusing SkipFileFn = std::function<bool(std::string_view)>;\ninline const SkipFileFn NullFilter = {};\n\nstruct PrefixFilter {\n    bool operator()(std::string_view path);\n    std::string prefix;\n    PrefixFilter(std::string_view _prefix): prefix(_prefix) {}\n};\n\n//----------------------------------------------------------------------------\n//! Produces a file conversion path to be placed in the proc directory taking\n//! into account the given group and also returns its size\n//!\n//! @param fid the file ID\n//! @param target_group the group to which the file will be transferred\n//! @param size return address for the size of the file\n//! @param skip_file_fn function to skip files matching filter\n//!          defaults to NullFilter, which means no files will be skipped\n//!\n//! @return name of the proc transfer file\n//----------------------------------------------------------------------------\nstd::string\ngetFileProcTransferNameAndSize(eos::common::FileId::fileid_t fid,\n                               const std::string& target_group, uint64_t* size,\n                               const SkipFileFn& skip_file_fn = NullFilter);\n} // eos::mgm::group_balancer\n#endif // EOS_CONVERTERUTILS_HH\n"
  },
  {
    "path": "mgm/groupbalancer/FreeSpaceBalancerEngine.cc",
    "content": "#include \"mgm/groupbalancer/FreeSpaceBalancerEngine.hh\"\n#include \"mgm/groupbalancer/BalancerEngineUtils.hh\"\n#include \"common/Logging.hh\"\n\nnamespace eos::mgm::group_balancer\n{\n\nvoid FreeSpaceBalancerEngine::configure(const engine_conf_t& conf)\n{\n  using namespace std::string_view_literals;\n  std::string err;\n  std::scoped_lock lock(mtx);\n  mMinDeviation = extract_percent_value(conf, \"min_threshold\"sv, 2, &err);\n\n  if (!err.empty()) {\n    eos_static_err(\"msg=\\\"failed to set min_deviation\\\" err=%s\", err.c_str());\n  }\n\n  mMaxDeviation = extract_percent_value(conf, \"max_threshold\"sv, 2, &err);\n\n  if (!err.empty()) {\n    eos_static_err(\"msg=\\\"failed to set max_deviation\\\" err=%s\", err.c_str());\n  }\n\n  mBlocklistedGroups = extract_commalist_value(conf, \"blocklisted_groups\"sv);\n}\n\nvoid FreeSpaceBalancerEngine::recalculate()\n{\n  uint64_t total_size{0};\n  uint64_t total_used{0};\n  uint16_t count{0};\n  std::scoped_lock lock(mtx);\n  std::for_each(data.mGroupSizes.begin(), data.mGroupSizes.end(),\n  [&](const auto & kv) {\n    if (mBlocklistedGroups.find(kv.first) == mBlocklistedGroups.end()) {\n      const auto& group_info = kv.second;\n\n      if (group_info.on()) {\n        total_size += group_info.capacity();\n        total_used += group_info.usedBytes();\n        ++count;\n      }\n    }\n  });\n  mTotalFreeSpace = total_size - total_used;\n\n  if (count > 0) {\n    mGroupFreeSpace = mTotalFreeSpace / count; // integer division, half of a byte\n    // makes no sense, round down is fine\n  }\n}\n\nuint64_t FreeSpaceBalancerEngine::getGroupFreeSpace() const\n{\n  return mGroupFreeSpace;\n}\n\nuint64_t FreeSpaceBalancerEngine::getFreeSpaceULimit() const\n{\n  return static_cast<uint64_t>(mGroupFreeSpace * (1 + mMaxDeviation));\n}\n\nuint64_t FreeSpaceBalancerEngine::getFreeSpaceLLimit() const\n{\n  return static_cast<uint64_t>(mGroupFreeSpace * (1 - mMinDeviation));\n}\n\nvoid FreeSpaceBalancerEngine::updateGroup(const std::string& group_name)\n{\n  std::scoped_lock lock(mtx);\n  // clear threshold is a set erase. should always work!\n  clear_threshold(group_name);\n\n  if (mBlocklistedGroups.find(group_name) != mBlocklistedGroups.end()) {\n    return;\n  }\n\n  auto kv = data.mGroupSizes.find(group_name);\n\n  if (kv == data.mGroupSizes.end()) {\n    return;\n  }\n\n  uint64_t group_free_bytes = kv->second.capacity() - kv->second.usedBytes();\n  uint64_t upper_limit = getFreeSpaceULimit();\n  uint64_t lower_limit = getFreeSpaceLLimit();\n\n  if (group_free_bytes > upper_limit) {\n    data.mGroupsUnderThreshold.emplace(group_name);\n  }\n\n  if (group_free_bytes < lower_limit) {\n    data.mGroupsOverThreshold.emplace(group_name);\n  }\n}\n\nstd::string FreeSpaceBalancerEngine::get_status_str(bool detail,\n    bool monitoring) const\n{\n  std::stringstream oss;\n  std::scoped_lock lock(mtx);\n\n  if (!monitoring) {\n    oss << \"Engine configured: FreeSpace\\n\";\n    oss << \"Min Threshold   : \" << mMinDeviation << \"\\n\";\n    oss << \"Max Threshold   : \" << mMaxDeviation << \"\\n\";\n    oss << \"Total Freespace : \" << mTotalFreeSpace << \"\\n\";\n    oss << \"Group Freespace : \" << mGroupFreeSpace << \"\\n\";\n  }\n\n  oss << BalancerEngine::get_status_str(detail, monitoring);\n\n  if (!mBlocklistedGroups.empty()) {\n    oss << \"Blocklisted groups: \\n\";\n\n    for (const auto& group : mBlocklistedGroups) {\n      oss << group << \"\\n\";\n    }\n  }\n\n  return oss.str();\n}\n\n}\n"
  },
  {
    "path": "mgm/groupbalancer/FreeSpaceBalancerEngine.hh",
    "content": "//------------------------------------------------------------------------------\n// File: FreeSpaceBalancerEngine.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include <unordered_set>\n#include <mutex>\n#include \"mgm/groupbalancer/BalancerEngine.hh\"\n\nnamespace eos::mgm::group_balancer\n{\n\nclass FreeSpaceBalancerEngine: public BalancerEngine\n{\npublic:\n  using group_set_t = std::unordered_set<std::string>;\n  void recalculate() override;\n  void updateGroup(const std::string& group_name) override;\n  void configure(const engine_conf_t& conf) override;\n  std::string get_status_str(bool detail = false,\n                             bool monitoring = false) const override;\n  // Currently consumed by tests, show the expected free space per group\n  // Not TS, all the methods calling these already hold locks\n  uint64_t getGroupFreeSpace() const;\n  uint64_t getFreeSpaceULimit() const;\n  uint64_t getFreeSpaceLLimit() const;\nprivate:\n  uint64_t mTotalFreeSpace; //!< Total Free space in the space\n  uint64_t mGroupFreeSpace; //!< Per Group Free Space\n  double mMinDeviation {0.02};     //!< Allowed percent deviation from left of GroupFreeSpace\n  double mMaxDeviation {0.02};     //!< Allowed percent deviation from right of GroupFreeSpace\n  mutable std::mutex mtx;\n  //! TODO future: make this part of the base class and make this feature\n  //! available for all engines\n  group_set_t mBlocklistedGroups; //!< Groups that will be blocked from participation\n};\n}\n"
  },
  {
    "path": "mgm/groupbalancer/GroupBalancer.cc",
    "content": "//------------------------------------------------------------------------------\n// File: GroupBalancer.cc\n// Author: Joaquim Rocha - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/groupbalancer/GroupBalancer.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/convert/ConverterEngine.hh\"\n#include \"namespace/interface/IFsView.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/FileId.hh\"\n#include \"common/utils/RandUtils.hh\"\n#include \"mgm/groupbalancer/BalancerEngineFactory.hh\"\n#include \"mgm/groupbalancer/BalancerEngineUtils.hh\"\n#include \"mgm/groupbalancer/GroupsInfoFetcher.hh\"\n#include \"mgm/groupbalancer/ConverterUtils.hh\"\n\n#define CACHE_LIFE_TIME 60 // seconds\n\nEOSMGMNAMESPACE_BEGIN\n\nusing group_balancer::BalancerEngineT;\nusing group_balancer::group_size_map;\nusing group_balancer::eosGroupsInfoFetcher;\nusing group_balancer::PrefixFilter;\nstatic constexpr auto GROUPBALANCER_THREAD_NAME = \"GroupBalancer\";\n\n//-------------------------------------------------------------------------------\n// GroupBalancer constructor\n//-------------------------------------------------------------------------------\nGroupBalancer::GroupBalancer(const char* spacename)\n  : mSpaceName(spacename), mLastCheck(0),\n    mProcFilter(PrefixFilter(gOFS->MgmProcPath.c_str()))\n{\n  mEngine.reset(group_balancer::make_balancer_engine(BalancerEngineT::stddev));\n  mThread.reset(&GroupBalancer::GroupBalance, this);\n}\n\n//------------------------------------------------------------------------------\n// Stop group balancing thread\n//------------------------------------------------------------------------------\nvoid\nGroupBalancer::Stop()\n{\n  mThread.join();\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nGroupBalancer::~GroupBalancer()\n{\n  Stop();\n  mEngine->clear();\n}\n\n//------------------------------------------------------------------------------\n// Update the list of ongoing transfers\n//------------------------------------------------------------------------------\nvoid\nGroupBalancer::UpdateTransferList()\n{\n  for (auto it = mTransfers.begin(); it != mTransfers.end();) {\n    if (!gOFS->mFidTracker.HasEntry(it->first)) {\n      mTransfers.erase(it++);\n    } else {\n      ++it;\n    }\n  }\n\n  eos_static_info(\"msg=\\\"group_balancer update transfers\\\" \"\n                  \"scheduledtransfers=%d\", mTransfers.size());\n}\n\n//------------------------------------------------------------------------------\n// Creates the conversion file in proc for the file ID, from the given\n// sourceGroup, to the targetGroup (and updates the cache structures)\n//------------------------------------------------------------------------------\nvoid\nGroupBalancer::scheduleTransfer(const FileInfo& file_info,\n                                FsGroup* sourceGroup, FsGroup* targetGroup)\n{\n  if (sourceGroup == nullptr || targetGroup == nullptr) {\n    return;\n  }\n\n  auto mGroupSizes = mEngine->get_group_sizes();\n\n  if ((mGroupSizes.count(sourceGroup->mName) == 0) ||\n      (mGroupSizes.count(targetGroup->mName) == 0)) {\n    eos_static_err(\"msg=\\\"no src/trg group in map\\\" src_group=%s trg_group=%s\",\n                   sourceGroup->mName.c_str(), targetGroup->mName.c_str());\n    return;\n  }\n\n  // Proc file name is generated by getFileProcTransferNameAndSize\n  // doesn't contain (+) so we can append without checking\n  std::string conv_tag = file_info.filename;\n  conv_tag += \"^groupbalancer^\";\n  conv_tag.erase(0, gOFS->MgmProcConversionPath.length() + 1);\n  std::string err_msg;\n\n  if (gOFS->mConverterEngine->ScheduleJob(file_info.fid, conv_tag, err_msg)) {\n    eos_static_info(\"msg=\\\"group balancer scheduled job\\\" file=\\\"%s\\\" \"\n                    \"src_grp=\\\"%s\\\" dst_grp=\\\"%s\\\"\", conv_tag.c_str(),\n                    sourceGroup->mName.c_str(), targetGroup->mName.c_str());\n  } else {\n    eos_static_err(\"msg=\\\"group balancer could not schedule job\\\" \"\n                   \"file=\\\"%s\\\" src_grp=\\\"%s\\\" dst_grp=\\\"%s\\\"\",\n                   conv_tag.c_str(), sourceGroup->mName.c_str(),\n                   targetGroup->mName.c_str());\n  }\n\n  mTransfers[file_info.fid] = file_info.filename;\n}\n\n//------------------------------------------------------------------------------\n// Chooses a random file ID from a random filesystem in the given group\n//------------------------------------------------------------------------------\neos::common::FileId::fileid_t\nGroupBalancer::chooseFidFromGroup(FsGroup* group)\n{\n  if (group == nullptr) {\n    return {};\n  }\n\n  int rndIndex;\n  bool found = false;\n  uint64_t fsid_size = 0ull;\n  eos::common::FileSystem::fsid_t fsid = 0;\n  eos::common::RWMutexReadLock vlock(FsView::gFsView.ViewMutex);\n  // TODO(gbitzes): Add prefetching, make more efficient.\n  std::vector<int> validFsIndexes(group->size());\n\n  for (size_t i = 0; i < group->size(); i++) {\n    validFsIndexes[i] = (int) i;\n  }\n\n  eos::mgm::BaseView::const_iterator fs_it;\n\n  while (validFsIndexes.size() > 0) {\n    fs_it = group->begin();\n    rndIndex = common::getRandom(0ul, validFsIndexes.size() - 1);\n    std::advance(fs_it, validFsIndexes[rndIndex]);\n    fsid = *fs_it;\n    // Accept only active file systems\n    FileSystem* target = FsView::gFsView.mIdView.lookupByID(fsid);\n\n    if (target && target->GetActiveStatus() == eos::common::ActiveStatus::kOnline) {\n      fsid_size = gOFS->eosFsView->getNumFilesOnFs(fsid);\n\n      if (fsid_size) {\n        found = true;\n        break;\n      }\n    }\n\n    validFsIndexes.erase(validFsIndexes.begin() + rndIndex);\n  }\n\n  // Check if we have any files to transfer\n  if (!found) {\n    return {};\n  }\n\n  int attempts = 10;\n\n  while (attempts-- > 0) {\n    eos::IFileMD::id_t randomPick;\n\n    if (gOFS->eosFsView->getApproximatelyRandomFileInFs(fsid, randomPick) &&\n        mTransfers.count(randomPick) == 0) {\n      return randomPick;\n    }\n  }\n\n  return {};\n}\n\nGroupBalancer::FileInfo\nGroupBalancer::chooseFileFromGroup(FsGroup* from_group, FsGroup* to_group,\n                                   int attempts)\n{\n  if (from_group == nullptr || to_group == nullptr) {\n    return {};\n  }\n\n  if (from_group->size() == 0) {\n    return {};\n  }\n\n  uint64_t filesize;\n\n  while (attempts-- > 0) {\n    auto fid = chooseFidFromGroup(from_group);\n\n    if (!fid) {\n      continue;\n    }\n\n    auto filename = group_balancer::getFileProcTransferNameAndSize(fid,\n                    to_group->mName,\n                    &filesize,\n                    mProcFilter);\n\n    if (filename.empty() ||\n        (mCfg.mMinFileSize > filesize) ||\n        (mCfg.mMaxFileSize < filesize)) {\n      continue;\n    }\n\n    // We've a hit!\n    return {fid, std::move(filename), filesize};\n  }\n\n  return {};\n}\n\n//------------------------------------------------------------------------------\n// Print size\n//------------------------------------------------------------------------------\nstatic void\nprintSizes(const group_size_map& group_sizes)\n{\n  for (const auto& it : group_sizes)\n    eos_static_debug(\"group=%s average=%.02f\", it.first.c_str(),\n                     (double)it.second.filled() * 100.0);\n}\n\n//------------------------------------------------------------------------------\n// Picks two groups (source and target) randomly and schedule a file ID\n// to be transferred\n//------------------------------------------------------------------------------\nvoid\nGroupBalancer::prepareTransfer()\n{\n  FsGroup* fromGroup, *toGroup;\n  auto&& [over_it, under_it] = mEngine->pickGroupsforTransfer();\n\n  if (over_it.empty() || under_it.empty()) {\n    eos_static_info(\"msg=\\\"engine gave us empty groups skipping\\\" \"\n                    \"engine_status=%s\",\n                    mEngine->get_status_str(false, true).c_str());\n    return;\n  }\n\n  {\n    eos::common::RWMutexReadLock rlock(FsView::gFsView.ViewMutex);\n    auto from_group_it = FsView::gFsView.mGroupView.find(over_it);\n    auto to_group_it = FsView::gFsView.mGroupView.find(under_it);\n\n    if (from_group_it == FsView::gFsView.mGroupView.end() ||\n        to_group_it == FsView::gFsView.mGroupView.end()) {\n      return;\n    }\n\n    fromGroup = from_group_it->second;\n    toGroup = to_group_it->second;\n  }\n\n  auto file_info = chooseFileFromGroup(fromGroup, toGroup, mCfg.file_attempts);\n\n  if (!file_info) {\n    eos_static_info(\"msg=\\\"failed to choose any fid to schedule\\\" \"\n                    \"failedgroup=%s\", fromGroup->mName.c_str());\n    return;\n  }\n\n  scheduleTransfer(file_info, fromGroup, toGroup);\n}\n\n//------------------------------------------------------------------------------\n// Check if the sizes cache should be updated (based on the time passed since\n// they were last updated)\n//------------------------------------------------------------------------------\nbool\nGroupBalancer::cacheExpired()\n{\n  time_t currentTime = time(NULL);\n\n  if (difftime(currentTime, mLastCheck) > CACHE_LIFE_TIME) {\n    mLastCheck = currentTime;\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Schedule a pre-defined number of transfers\n//------------------------------------------------------------------------------\nvoid\nGroupBalancer::prepareTransfers(int nrTransfers)\n{\n  int allowedTransfers = nrTransfers - mTransfers.size();\n\n  for (int i = 0; i < allowedTransfers; i++) {\n    prepareTransfer();\n  }\n\n  if (allowedTransfers > 0) {\n    printSizes(mEngine->get_group_sizes());\n  }\n}\n\nstd::string\nGroupBalancer::Status(bool detail, bool monitoring) const\n{\n  eos::common::RWMutexReadLock lock(mEngineMtx);\n  return mEngine->get_status_str(detail, monitoring);\n}\n\nbool\nGroupBalancer::is_valid_engine(std::string_view engine_name)\n{\n  return engine_name == \"std\" || engine_name == \"minmax\" ||\n         engine_name == \"freespace\";\n}\n\n//------------------------------------------------------------------------------\n// Appply configuration stored at the space level\n//------------------------------------------------------------------------------\nbool\nGroupBalancer::Configure(FsSpace* const space, GroupBalancer::Config& cfg)\n{\n  cfg.is_enabled = space->GetConfigMember(\"groupbalancer\") == \"on\";\n  cfg.is_conv_enabled = false;\n\n  if (gOFS && gOFS->mConverterEngine &&\n      gOFS->mConverterEngine->IsRunning()) {\n    cfg.is_conv_enabled = true;\n  }\n\n  if (!cfg.is_enabled || !cfg.is_conv_enabled) {\n    eos_static_info(\"msg=\\\"group balancer or converter not enabled\\\"\"\n                    \" space=%s balancer_status=%d converter_status=%d\",\n                    mSpaceName.c_str(), cfg.is_enabled, cfg.is_conv_enabled);\n    return false;\n  }\n\n  cfg.num_tx = atoi(space->GetConfigMember(\"groupbalancer.ntx\").c_str());\n  cfg.mMinFileSize = common::StringConversion::GetSizeFromString(\n                       space->GetConfigMember(\"groupbalancer.min_file_size\"));\n  cfg.mMaxFileSize = common::StringConversion::GetSizeFromString(\n                       space->GetConfigMember(\"groupbalancer.max_file_size\"));\n\n  if (!cfg.mMaxFileSize) {\n    eos_static_debug(\"%s\", \"msg=\\\"invalid Max File Size, using default\\\"\");\n    cfg.mMaxFileSize = GROUPBALANCER_MAX_FILE_SIZE;\n  }\n\n  cfg.engine_type = group_balancer::get_engine_type(\n                      space->GetConfigMember(\"groupbalancer.engine\"));\n  cfg.file_attempts = atoi(\n                        space->GetConfigMember(\"groupbalancer.file_attempts\").c_str());\n\n  if (!cfg.file_attempts) {\n    eos_static_debug(\"%s\", \"msg=\\\"invalid File Attempts Count, using default\\\"\");\n    cfg.file_attempts = GROUPBALANCER_FILE_ATTEMPTS;\n  }\n\n  auto min_threshold_str = space->GetConfigMember(\"groupbalancer.min_threshold\");\n  auto max_threshold_str = space->GetConfigMember(\"groupbalancer.max_threshold\");\n\n  if (!group_balancer::is_valid_threshold(min_threshold_str, max_threshold_str)) {\n    if (cfg.engine_type == BalancerEngineT::minmax) {\n      eos_static_err(\"msg=\\\"invalid min/max balancer threshold configuration\\\"\"\n                     \" space=%s\", mSpaceName.c_str());\n      return false;\n    }\n\n    // This is a temporary stop gap until we force min/max threshold to be set\n    // and remove this param. For std. balancer in case there isn't an explicit\n    // min/max, let's set to configured threshold\n    auto threshold_str = space->GetConfigMember(\"groupbalancer.threshold\");\n\n    if (!group_balancer::is_valid_threshold(threshold_str)) {\n      eos_static_err(\"msg=\\\"invalid std balancer threshold configuration\\\"\"\n                     \" space=%s\", mSpaceName.c_str());\n      return false;\n    }\n\n    min_threshold_str = threshold_str;\n    max_threshold_str = threshold_str;\n  }\n\n  auto blocklisted_groups = space->GetConfigMember(\"groupbalancer.blocklist\");\n  mEngineConf.insert_or_assign(\"min_threshold\", std::move(min_threshold_str));\n  mEngineConf.insert_or_assign(\"max_threshold\", std::move(max_threshold_str));\n  mEngineConf.insert_or_assign(\"blocklisted_groups\",\n                               std::move(blocklisted_groups));\n  return true;\n}\n\n\n//------------------------------------------------------------------------------\n// Eternal loop trying to run conversion jobs\n//------------------------------------------------------------------------------\nvoid\nGroupBalancer::GroupBalance(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(GROUPBALANCER_THREAD_NAME);\n  gOFS->WaitUntilNamespaceIsBooted();\n  eos_static_info(\"%s\", \"msg=\\\"starting group balancer thread\\\"\");\n  eosGroupsInfoFetcher fetcher(mSpaceName);\n  group_balancer::BalancerEngineT prev_engine_type {BalancerEngineT::stddev};\n  bool engine_reconfigured = false;\n  bool config_status = true;\n\n  // Loop forever until cancelled\n  while (!assistant.terminationRequested()) {\n    bool expected_reconfiguration = true;\n    assistant.wait_for(std::chrono::seconds(10));\n\n    if (!gOFS->mMaster->IsMaster()) {\n      assistant.wait_for(std::chrono::seconds(10));\n      eos_static_debug(\"%s\", \"msg=\\\"group balancer disabled for slave\\\"\");\n      continue;\n    }\n\n    // Try to read lock the mutex\n\n    if (assistant.terminationRequested()) {\n      return;\n    }\n\n    FsView::gFsView.ViewMutex.LockRead();\n\n    if (!FsView::gFsView.mSpaceGroupView.count(mSpaceName.c_str())) {\n      FsView::gFsView.ViewMutex.UnLockRead();\n      eos_static_debug(\"msg=\\\"no groups to balance\\\" space=\\\"%s\\\"\",\n                       mSpaceName.c_str());\n      break;\n    }\n\n    FsSpace* space = FsView::gFsView.mSpaceView[mSpaceName.c_str()];\n\n    if (mDoConfigUpdate.compare_exchange_strong(expected_reconfiguration, false,\n        std::memory_order_acq_rel)) {\n      config_status = Configure(space, mCfg);\n    }\n\n    FsView::gFsView.ViewMutex.UnLockRead();\n    // Update tracker for scheduled jobs\n    gOFS->mFidTracker.DoCleanup(TrackerType::Convert);\n\n    if (!gOFS->mConverterEngine || !config_status) {\n      continue;\n    }\n\n    if (prev_engine_type != mCfg.engine_type) {\n      mEngine.reset(group_balancer::make_balancer_engine(mCfg.engine_type));\n      engine_reconfigured = true;\n      prev_engine_type = mCfg.engine_type;\n      fetcher.should_average(engine_should_average(mCfg.engine_type));\n    }\n\n    mEngine->configure(mEngineConf);\n    UpdateTransferList();\n\n    if ((int) mTransfers.size() >= mCfg.num_tx) {\n      continue;\n    }\n\n    eos_static_debug(\"msg=\\\"group balancer enabled\\\" ntx=%d \", mCfg.num_tx);\n\n    if (cacheExpired() || engine_reconfigured) {\n      {\n        eos::common::RWMutexWriteLock lock(mEngineMtx);\n        mEngine->populateGroupsInfo(fetcher.fetch());\n      }\n      printSizes(mEngine->get_group_sizes());\n\n      if (engine_reconfigured) {\n        eos_static_info(\"msg=\\\"group balancer engine reconfigured\\\"\");\n        engine_reconfigured = false;\n      }\n    }\n\n    if (!mEngine->canPick()) {\n      eos_static_debug(\"msg=\\\"Empty source or target groups, cannot pick\\\"\"\n                       \" engine_status=%s\",\n                       mEngine->get_status_str(false, true).c_str());\n      continue;\n    }\n\n    prepareTransfers(mCfg.num_tx);\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/groupbalancer/GroupBalancer.hh",
    "content": "//------------------------------------------------------------------------------\n// File: GroupBalancer.hh\n// Author: Joaquim Rocha - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/FileId.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/RWMutex.hh\"\n#include <vector>\n#include <string>\n#include <cstring>\n#include <ctime>\n#include <map>\n#include <unordered_set>\n#include \"mgm/groupbalancer/BalancerEngineTypes.hh\"\n#include \"mgm/groupbalancer/ConverterUtils.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nclass FsGroup;\nclass FsSpace;\nstatic constexpr uint64_t GROUPBALANCER_MIN_FILE_SIZE = 1ULL << 30;\nstatic constexpr uint64_t GROUPBALANCER_MAX_FILE_SIZE = 16ULL << 30;\nstatic constexpr int GROUPBALANCER_FILE_ATTEMPTS = 50;\nusing eos::mgm::group_balancer::GroupSizeInfo;\n\nnamespace group_balancer\n{\nclass BalancerEngine;\n} // group_balancer\n\n//------------------------------------------------------------------------------\n//! @brief Class running the balancing among groups\n//! For it to work, the Converter also needs to be enabled.\n//------------------------------------------------------------------------------\nclass GroupBalancer\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor (per space)\n  //\n  //! @param spacename name of the associated space\n  //----------------------------------------------------------------------------\n  GroupBalancer(const char* spacename);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~GroupBalancer();\n\n  //----------------------------------------------------------------------------\n  //! Stop group balancing thread\n  //----------------------------------------------------------------------------\n  void Stop();\n\n  //----------------------------------------------------------------------------\n  // Service implementation e.g. eternal conversion loop running third-party\n  // conversion\n  //----------------------------------------------------------------------------\n  void GroupBalance(ThreadAssistant& assistant) noexcept;\n\n\n  struct Config {\n    bool is_enabled;\n    bool is_conv_enabled;\n    int num_tx;\n    uint64_t mMinFileSize; ///< Min size of files to be picked\n    uint64_t mMaxFileSize; ///< Max size of files to be picked\n    uint64_t file_attempts;\n    group_balancer::BalancerEngineT engine_type;\n\n    Config(): is_enabled(true), is_conv_enabled(true), num_tx(0),\n      mMinFileSize(GROUPBALANCER_MIN_FILE_SIZE),\n      mMaxFileSize(GROUPBALANCER_MAX_FILE_SIZE),\n      engine_type(group_balancer::BalancerEngineT::stddev)\n    {}\n  };\n\n  struct FileInfo {\n    eos::common::FileId::fileid_t fid;\n    std::string filename;\n    uint64_t filesize;\n\n    FileInfo() = default;\n    FileInfo(eos::common::FileId::fileid_t _fid,\n             std::string&& _fname, uint64_t _fsize) : fid(_fid),\n      filename(std::move(_fname)),\n      filesize(_fsize)\n    {}\n\n    // Check if both fid && filename are set, 0 size is valid\n    operator bool() const\n    {\n      return fid != 0  && !filename.empty();\n    }\n  };\n\n  //----------------------------------------------------------------------------\n  //! Apply configuration stored at the space level\n  //!\n  //! @param space the space to configure\n  //! @param cfg the GroupBalancer::Config struct\n  //!\n  //! @return boolean based on valid configuration\n  //----------------------------------------------------------------------------\n  bool Configure(FsSpace* const space, Config& cfg);\n\n  std::string Status(bool detail = false, bool monitoring = false) const;\n\n  static bool is_valid_engine(std::string_view engine_name);\n\n\n  //----------------------------------------------------------------------------\n  //! Ask the engine to reconfigure itself\n  //! NOTE: Internally this is done by setting an atomic reconfigure flag\n  //! While technically due to conf being already synchronised internally with a\n  //! mutex, even with relaxed memory ordering we'll see the changes, but a stronger\n  //! release/acquire semantic is to ensure that we don't wait on the conf mutex\n  //! If you're changing this please make sure to change the corresponding acquire\n  //! call in the GroupBalance routine.\n  //----------------------------------------------------------------------------\n  inline void reconfigure()\n  {\n    mDoConfigUpdate.store(true, std::memory_order_release);\n  }\n\nprivate:\n  AssistedThread mThread; ///< Thread scheduling jobs\n  std::string mSpaceName; ///< Attached space name\n  Config mCfg;\n\n  std::atomic<bool> mDoConfigUpdate {true};\n\n  mutable eos::common::RWMutexW mEngineMtx;\n  std::unique_ptr<group_balancer::BalancerEngine> mEngine;\n\n  /// last time the groups' real used space was checked\n  time_t mLastCheck;\n  //! Scheduled transfers (maps fid to path in proc)\n  std::map<eos::common::FileId::fileid_t, std::string> mTransfers;\n  group_balancer::engine_conf_t mEngineConf;\n  group_balancer::SkipFileFn mProcFilter;\n\n  //----------------------------------------------------------------------------\n  //! Chooses a random file ID from a random filesystem in the given group\n  //!\n  //! @param group the group from which the file id will be chosen\n  //!\n  //! @return the chosen file ID\n  //----------------------------------------------------------------------------\n  eos::common::FileId::fileid_t chooseFidFromGroup(FsGroup* group);\n\n  //----------------------------------------------------------------------------\n  //! Chooses a random file from a random filesystem in the given group, but makes\n  //! a few attempts to pick a file within the configured size limits.\n  //! @param from_group the group from which the file id will be chosen\n  //! @param to_group the group to which file will be moved to\n  //! @param no of attempts (default :50)\n  //!\n  //! @return FileInfo for the chosen file\n  //----------------------------------------------------------------------------\n  GroupBalancer::FileInfo chooseFileFromGroup(FsGroup* from_group,\n      FsGroup* to_group, int attempts);\n\n  void prepareTransfers(int nrTransfers);\n\n  //----------------------------------------------------------------------------\n  //! Picks two groups (source and target) randomly and schedule a file ID\n  //! to be transferred\n  //----------------------------------------------------------------------------\n  void prepareTransfer(void);\n\n  //----------------------------------------------------------------------------\n  //! Creates the conversion file in proc for the file ID, from the given\n  //! sourceGroup, to the targetGroup (and updates the cache structures)\n  //!\n  //! @param file_info, the FileInfo struct of the file to be transferred\n  //! @param sourceGroup the group where the file is currently located\n  //! @param targetGroup the group to which the file is will be transferred\n  //----------------------------------------------------------------------------\n  void scheduleTransfer(const FileInfo& file_info,\n                        FsGroup* sourceGroup, FsGroup* targetGroup);\n\n  //----------------------------------------------------------------------------\n  //! Check if the sizes cache should be updated (based on the time passed since\n  //! they were last updated)\n  //!\n  //! @return whether the cache expired or not\n  //----------------------------------------------------------------------------\n  bool cacheExpired(void);\n\n  //----------------------------------------------------------------------------\n  //! For each entry in mTransfers, check if the file was transfered and remove\n  //! it from the list\n  //----------------------------------------------------------------------------\n  void UpdateTransferList(void);\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/groupbalancer/GroupsInfoFetcher.cc",
    "content": "#include \"mgm/groupbalancer/GroupsInfoFetcher.hh\"\n#include \"mgm/fsview/FsView.hh\"\n\nnamespace eos::mgm::group_balancer\n{\n\ngroup_size_map\neosGroupsInfoFetcher::fetch()\n{\n  group_size_map mGroupSizes;\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n\n  if (FsView::gFsView.mSpaceGroupView.count(spaceName) == 0) {\n    eos_static_err(\"msg=\\\"no such space %s\\\"\", spaceName.c_str());\n    return mGroupSizes;\n  }\n\n  auto set_fsgrp = FsView::gFsView.mSpaceGroupView[spaceName];\n\n  for (auto it = set_fsgrp.cbegin(); it != set_fsgrp.cend(); it++) {\n    auto group_status = getGroupStatus((*it)->GetConfigMember(\"status\"));\n\n    if (!is_valid_status(group_status)) {\n      continue;\n    }\n\n    uint64_t size {0}, capacity {0};\n\n    // TODO - this might be dropped in favour of summing\n    if (do_average) {\n      size = (*it)->AverageDouble(\"stat.statfs.usedbytes\", false);\n      capacity = (*it)->AverageDouble(\"stat.statfs.capacity\", false);\n    } else {\n      size = (*it)->SumLongLong(\"stat.statfs.usedbytes\", false);\n      capacity = (*it)->SumLongLong(\"stat.statfs.capacity\", false);\n    }\n    if (capacity == 0) {\n      continue;\n    }\n\n    mGroupSizes.emplace((*it)->mName, GroupSizeInfo{group_status, size, capacity});\n  }\n\n  return mGroupSizes;\n}\n\n} // eos::mgm::group_balancer\n"
  },
  {
    "path": "mgm/groupbalancer/GroupsInfoFetcher.hh",
    "content": "//------------------------------------------------------------------------------\n// File: GroupsInfoFetcher.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n#include \"mgm/groupbalancer/BalancerEngineTypes.hh\"\n#include <memory>\n#include <string>\n\nnamespace eos::mgm::group_balancer\n{\n// A simple interface to populate the group_size map per group. This is useful\n// for DI scenarios where we can alternatively fill in the group_size structures\nstruct IGroupsInfoFetcher {\n  virtual group_size_map fetch() = 0;\n  virtual ~IGroupsInfoFetcher() = default;\n};\n\n\nstruct OnGroupStatusFilter {\n  bool operator()(GroupStatus status)\n  {\n    return status == GroupStatus::ON;\n  }\n};\n\n// This class fetches groups info from a given space and returns a map of groupname\n// GroupSizeInfo, the groups can be filtered based on a status function that takes\n// any callable object which returns bool and takes a GroupStatus argument\n// examples:\n//     eosGroupsInfoFetcher fetcher(space); // default. filters \"ON\" group\n//                                          // ie. only these will be selected\n//     eosGroupsInfoFetcher fetcher(space, OnGroupStatusFilter{});\n//     eosGroupsInfoFetcher fetcher(space, [](GroupStatus s) { return s==GroupStatus::ON; });\n//\nclass eosGroupsInfoFetcher final: public IGroupsInfoFetcher\n{\n  // A base filter function that must implement a apply method with status arg\n  struct base_group_status_filter {\n    virtual bool apply(GroupStatus status) = 0;\n    virtual ~base_group_status_filter() = default;\n  };\n\n  // We inherit from the above base, to hold any callable which implements a\n  // bool operator()(GroupStatus), since all of this is private, you can\n  // hold almost any object that implements a call operator taking a status\n  template <typename F>\n  struct group_status_filter : public base_group_status_filter {\n    group_status_filter(F&& _f): f(std::forward<F>(_f)) {};\n    virtual bool apply(GroupStatus status)\n    {\n      return f(status);\n    }\n    F f;\n  };\n\npublic:\n\n  template <typename F>\n  eosGroupsInfoFetcher(const std::string& _spaceName,\n                       F&& f): spaceName(_spaceName),\n    status_filter_fn(std::make_unique<group_status_filter<F>>(std::forward<F>\n                     (f))) {}\n\n  eosGroupsInfoFetcher(const std::string& _spaceName): spaceName(_spaceName),\n    status_filter_fn(new group_status_filter(OnGroupStatusFilter{}))\n  {}\n\n  group_size_map fetch() override;\n\n  bool is_valid_status(GroupStatus status)\n  {\n    return status_filter_fn->apply(status);\n  }\n\n  void should_average(bool _do_average) {\n    do_average = _do_average;\n  }\n\nprivate:\n  bool do_average {true};\n  std::string spaceName;\n  std::unique_ptr<base_group_status_filter> status_filter_fn;\n};\n\n\n}\n"
  },
  {
    "path": "mgm/groupbalancer/MinMaxBalancerEngine.cc",
    "content": "#include \"mgm/groupbalancer/MinMaxBalancerEngine.hh\"\n#include \"mgm/groupbalancer/BalancerEngineUtils.hh\"\n#include \"common/Logging.hh\"\n#include <sstream>\n\nnamespace eos::mgm::group_balancer\n{\nusing namespace std::string_view_literals;\n\nvoid MinMaxBalancerEngine::configure(const engine_conf_t& conf)\n{\n  std::string err;\n  mMinThreshold = extract_percent_value(conf, \"min_threshold\"sv, 60, &err);\n\n  if (!err.empty()) {\n    eos_static_err(\"msg=Failed to set min_threshold, err=%s\", err.c_str());\n  }\n\n  mMaxThreshold = extract_percent_value(conf, \"max_threshold\"sv, 90, &err);\n\n  if (!err.empty()) {\n    eos_static_err(\"msg=Failed to set max_threshold, err=%s\", err.c_str());\n  }\n}\n\nvoid MinMaxBalancerEngine::updateGroup(const std::string& group_name)\n{\n  auto kv = data.mGroupSizes.find(group_name);\n\n  if (kv == data.mGroupSizes.end()) {\n    return;\n  }\n\n  clear_threshold(group_name);\n\n  if (kv->second.filled() > mMaxThreshold) {\n    data.mGroupsOverThreshold.emplace(group_name);\n  } else if (kv->second.filled() < mMinThreshold) {\n    data.mGroupsUnderThreshold.emplace(group_name);\n  }\n}\n\nstd::string MinMaxBalancerEngine::get_status_str(bool detail,\n    bool monitoring) const\n{\n  std::stringstream oss;\n\n  if (!monitoring) {\n    oss << \"Engine configured: MinMax\\n\";\n    oss << \"Min Threshold    : \" << mMinThreshold << \"\\n\";\n    oss << \"Max Threshold    : \" << mMaxThreshold << \"\\n\";\n  }\n\n  oss << BalancerEngine::get_status_str(detail, monitoring);\n  return oss.str();\n}\n\n} // namespace eos::mgm::group_balancer\n"
  },
  {
    "path": "mgm/groupbalancer/MinMaxBalancerEngine.hh",
    "content": "//------------------------------------------------------------------------------\n// File: MinMaxBalancerEngine.cc\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include \"mgm/groupbalancer/BalancerEngine.hh\"\n\nnamespace eos::mgm::group_balancer\n{\n\nclass MinMaxBalancerEngine: public BalancerEngine\n{\npublic:\n  void recalculate() override {};\n  void updateGroup(const std::string& group_name) override;\n  void configure(const engine_conf_t& conf) override;\n\n  double get_min_threshold() const\n  {\n    return mMinThreshold;\n  }\n\n  double get_max_threshold() const\n  {\n    return mMaxThreshold;\n  }\n\n  std::string get_status_str(bool detail = false,\n                             bool monitoring = false) const override;\nprivate:\n  double mMinThreshold;\n  double mMaxThreshold;\n};\n\n\n}\n"
  },
  {
    "path": "mgm/groupbalancer/StdDevBalancerEngine.cc",
    "content": "//------------------------------------------------------------------------------\n// File: StdDevBalancerEngine.cc\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/groupbalancer/StdDevBalancerEngine.hh\"\n#include \"mgm/groupbalancer/BalancerEngineUtils.hh\"\n#include \"common/Logging.hh\"\n\nnamespace eos::mgm::group_balancer\n{\n\nvoid StdDevBalancerEngine::configure(const engine_conf_t& conf)\n{\n  using namespace std::string_view_literals;\n  std::string err;\n  mMinDeviation = extract_percent_value(conf, \"min_threshold\"sv, 0.05, &err);\n\n  if (!err.empty()) {\n    eos_static_err(\"msg=\\\"failed to set min_deviation\\\" err=%s\", err.c_str());\n  }\n\n  mMaxDeviation = extract_percent_value(conf, \"max_threshold\"sv, 0.05, &err);\n\n  if (!err.empty()) {\n    eos_static_err(\"msg=\\\"failed to set max_deviation\\\" err=%s\", err.c_str());\n  }\n}\n\nvoid StdDevBalancerEngine::recalculate()\n{\n  mAvgUsedSize = calculateAvg(data.mGroupSizes);\n}\n\nvoid StdDevBalancerEngine::updateGroup(const std::string& group_name)\n{\n  auto kv = data.mGroupSizes.find(group_name);\n\n  if (kv == data.mGroupSizes.end()) {\n    return;\n  }\n\n  const GroupSizeInfo& groupSize = kv->second;\n  double diffWithAvg = groupSize.filled() - mAvgUsedSize;\n  // set erase only erases if found, so this is safe without key checking\n  data.mGroupsOverThreshold.erase(group_name);\n  data.mGroupsUnderThreshold.erase(group_name);\n  eos_static_debug(\"diff=%.02f\", diffWithAvg);\n\n  if (abs(diffWithAvg) > mMaxDeviation && diffWithAvg > 0) {\n    data.mGroupsOverThreshold.emplace(group_name);\n  }\n\n  // Group is mThreshold over or under the average used size\n  if (abs(diffWithAvg) > mMinDeviation && diffWithAvg < 0) {\n    data.mGroupsUnderThreshold.emplace(group_name);\n  }\n}\n\nstd::string StdDevBalancerEngine::get_status_str(bool detail,\n    bool monitoring) const\n{\n  std::stringstream oss;\n\n  if (!monitoring) {\n    oss << \"Engine configured          : Std\\n\";\n    oss << \"Current Computed Average   : \" << mAvgUsedSize << \"\\n\";\n    oss << \"Min Deviation Threshold    : \" << mMinDeviation << \"\\n\";\n    oss << \"Max Deviation Threshold    : \" << mMaxDeviation << \"\\n\";\n  }\n\n  oss << BalancerEngine::get_status_str(detail, monitoring);\n  return oss.str();\n}\n\n} // namespace eos::mgm::group_balancer\n"
  },
  {
    "path": "mgm/groupbalancer/StdDevBalancerEngine.hh",
    "content": "//------------------------------------------------------------------------------\n// File: StdDevBalancerEngine.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include <unordered_set>\n#include \"mgm/groupbalancer/BalancerEngine.hh\"\n\nnamespace eos::mgm::group_balancer\n{\n\nclass StdDevBalancerEngine: public BalancerEngine\n{\npublic:\n  void recalculate() override;\n  void updateGroup(const std::string& group_name) override;\n  void configure(const engine_conf_t& conf) override;\n\n  // Getters only used by GTest\n  double get_min_threshold() const\n  {\n    return mMinDeviation;\n  }\n\n  double get_max_threshold() const\n  {\n    return mMaxDeviation;\n  }\n\n  std::string get_status_str(bool detail = false,\n                             bool monitoring = false) const override;\nprivate:\n  /// average filled percentage in groups\n  double mAvgUsedSize;\n  double mMinDeviation;\n  double mMaxDeviation;\n};\n\n} // namespace eos::mgm::group_balancer\n"
  },
  {
    "path": "mgm/groupbalancer/StdDrainerEngine.cc",
    "content": "#include \"mgm/groupbalancer/StdDrainerEngine.hh\"\n#include \"mgm/groupbalancer/BalancerEngineUtils.hh\"\n#include \"common/Logging.hh\"\n\nnamespace eos::mgm::group_balancer\n{\n\nvoid\nStdDrainerEngine::configure(const engine_conf_t& conf)\n{\n  using namespace std::string_view_literals;\n  std::string err;\n  mThreshold = extract_percent_value(conf, \"threshold\"sv, 0.01, &err);\n\n  if (!err.empty()) {\n    eos_static_err(\"msg=\\\"Failed to set threshold\\\" err=%s\", err.c_str());\n  }\n}\n\nvoid StdDrainerEngine::recalculate()\n{\n  mAvgUsedSize = calculateAvg(data.mGroupSizes);\n}\nvoid\nStdDrainerEngine::updateGroup(const std::string& group_name)\n{\n  // Groups Under threshold are targets and Over threshold are source groups\n  auto kv = data.mGroupSizes.find(group_name);\n\n  if (kv == data.mGroupSizes.end()) {\n    return;\n  }\n\n  const GroupSizeInfo& groupSizeInfo = kv->second;\n\n  if (groupSizeInfo.draining()) {\n    data.mGroupsOverThreshold.emplace(group_name);\n    data.mGroupsUnderThreshold.erase(group_name);\n  } else if (groupSizeInfo.on()) {\n    double diffWithAvg = groupSizeInfo.filled() - mAvgUsedSize;\n\n    if (mThreshold == 0 ||\n        (std::abs(diffWithAvg) > mThreshold && diffWithAvg < 0)) {\n      data.mGroupsUnderThreshold.emplace(group_name);\n    }\n  }\n}\n\n} // namespace eos::mgm::group_balancer\n"
  },
  {
    "path": "mgm/groupbalancer/StdDrainerEngine.hh",
    "content": "//------------------------------------------------------------------------------\n// File: StdDrainerEngine.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/groupbalancer/BalancerEngine.hh\"\n\nnamespace eos::mgm::group_balancer\n{\n\nclass StdDrainerEngine: public BalancerEngine\n{\npublic:\n  void recalculate() override;\n  void updateGroup(const std::string& group_name) override;\n  void configure(const engine_conf_t& conf) override;\n\n  // getters, can be used to validate config\n  double get_threshold() const\n  {\n    return mThreshold;\n  }\nprivate:\n  double mAvgUsedSize;\n  double mThreshold;\n};\n\n\n} // namespace eos::mgm::group_balancer\n"
  },
  {
    "path": "mgm/groupdrainer/DrainProgressTracker.cc",
    "content": "#include \"mgm/groupdrainer/DrainProgressTracker.hh\"\n\nnamespace eos::mgm\n{\n\nvoid\nDrainProgressTracker::setTotalFiles(fsid_t fsid,\n                                    uint64_t total_files)\n{\n  std::scoped_lock lg(mFsTotalFilesMtx);\n  auto [it, inserted] = mFsTotalfiles.try_emplace(fsid, total_files);\n\n  // NOTE: this may not be necessary if we guarantee that\n  // there will be no files inserted in the FS after we start draining\n  // but anyway this is almost 0 cost now as we know the iterator position\n  // of insertion, so we don't do any more traversal.\n  if (!inserted && it->second < total_files) {\n    mFsTotalfiles.insert_or_assign(it, fsid, total_files);\n  }\n}\n\nvoid\nDrainProgressTracker::increment(fsid_t fsid)\n{\n  std::scoped_lock lg(mFsScheduledCtrMtx);\n  mFsScheduledCounter[fsid]++;\n}\n\nfloat\nDrainProgressTracker::getDrainStatus(DrainProgressTracker::fsid_t fsid) const\n{\n  std::scoped_lock lg(mFsScheduledCtrMtx, mFsTotalFilesMtx);\n  auto total_it = mFsTotalfiles.find(fsid);\n  auto counter_it = mFsScheduledCounter.find(fsid);\n\n  if (total_it == mFsTotalfiles.end() ||\n      counter_it == mFsScheduledCounter.end() ||\n      total_it->second == 0) {\n    return 0;\n  }\n\n  return (static_cast<float>(counter_it->second) / total_it->second) * 100;\n}\n\nvoid\nDrainProgressTracker::dropFsid(DrainProgressTracker::fsid_t fsid)\n{\n  std::scoped_lock lg(mFsScheduledCtrMtx, mFsTotalFilesMtx);\n  mFsTotalfiles.erase(fsid);\n  mFsScheduledCounter.erase(fsid);\n}\n\nvoid\nDrainProgressTracker::clear()\n{\n  std::scoped_lock lg(mFsScheduledCtrMtx, mFsTotalFilesMtx);\n  mFsTotalfiles.clear();\n  mFsScheduledCounter.clear();\n}\n\nuint64_t\nDrainProgressTracker::getTotalFiles(DrainProgressTracker::fsid_t fsid) const\n{\n  std::scoped_lock lg(mFsTotalFilesMtx);\n  auto it = mFsTotalfiles.find(fsid);\n  return it == mFsTotalfiles.end() ? 0 : it->second;\n}\n\nuint64_t\nDrainProgressTracker::getFileCounter(DrainProgressTracker::fsid_t fsid) const\n{\n  std::scoped_lock lg(mFsScheduledCtrMtx);\n  auto it = mFsScheduledCounter.find(fsid);\n  return it == mFsScheduledCounter.end() ? 0 : it->second;\n}\n\n} // eos::mgm"
  },
  {
    "path": "mgm/groupdrainer/DrainProgressTracker.hh",
    "content": "//------------------------------------------------------------------------------\n// File: DrainProgressTracker.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include <map>\n#include <mutex>\n#include <string>\n#include \"common/FileSystem.hh\"\n\nnamespace eos::mgm\n{\n\n//! A simple progress tracker that just tracks the total files on a FS and\n//! has a simple increment method to increase the curr. files being drained\n//! This method is thread safe to be called from multiple threads, and holds\n//! 2 individual mutexes to the 2 internal maps for tracking total and current\n//! file counters\nclass DrainProgressTracker\n{\npublic:\n  using fsid_t = eos::common::FileSystem::fsid_t;\n\n  //! Set Total amount of files in a FSID\n  //! This method only stores the value if we find that\n  //! the value is greater than the one stored, so is safe to call multiple times\n  //! in the course of a drain\n  //! \\param fsid the Filesystem ID\n  //! \\param total_files Num Files on FS\n  void setTotalFiles(fsid_t fsid, uint64_t total_files);\n\n  //! Increment the counter of drained files\n  //! \\param fsid FSID from which the drain was scheduled\n  void increment(fsid_t fsid);\n\n  //! Drop all associated values from a FSID, this will no longer be tracked\n  void dropFsid(fsid_t fsid);\n\n  //! Clear all the internal entries!\n  void clear();\n\n  //! Get the percentage completion of a drain\n  //! \\param fsid the FSID\n  //! \\return a float percent value of curr_files_drained/total_files\n  float getDrainStatus(fsid_t fsid) const;\n\n  //! Get the total files for a FSID\n  uint64_t getTotalFiles(fsid_t fsid) const;\n\n  //! Get the current value of File Counter. may exceed total files considering\n  //! failures\n  uint64_t getFileCounter(fsid_t fsid) const;\nprivate:\n  mutable std::mutex mFsTotalFilesMtx;\n  mutable std::mutex mFsScheduledCtrMtx;\n  std::map<fsid_t, uint64_t> mFsTotalfiles;\n  std::map<fsid_t, uint64_t> mFsScheduledCounter;\n};\n\n\n} // namespace eos::mgm\n"
  },
  {
    "path": "mgm/groupdrainer/GroupDrainer.cc",
    "content": "#include \"mgm/groupdrainer/GroupDrainer.hh\"\n#include \"mgm/convert/ConversionInfo.hh\"\n#include \"mgm/convert/ConverterEngine.hh\"\n#include \"mgm/groupbalancer/StdDrainerEngine.hh\"\n#include \"mgm/groupbalancer/ConverterUtils.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/groupbalancer/GroupsInfoFetcher.hh\"\n#include \"common/utils/ContainerUtils.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/table_formatter/TableFormatterBase.hh\"\n#include \"namespace/interface/IFsView.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"common/FileSystem.hh\"\n#include \"mgm/utils/FileSystemStatusUtils.hh\"\n#include \"common/utils/BackOffInvoker.hh\"\n\nnamespace eos::mgm\n{\n\nusing group_balancer::eosGroupsInfoFetcher;\nusing group_balancer::GroupStatus;\nusing group_balancer::getFileProcTransferNameAndSize;\n\nstatic const std::string format_s = \"-s\";\nstatic const std::string format_l = \"l\";\nstatic const std::string format_f = \"f\";\n\nstatic constexpr auto GROUPDRAINER_THREADNAME = \"GroupDrainer\";\n\nGroupDrainer::GroupDrainer(std::string_view spacename):\n  mMaxTransfers(DEFAULT_NUM_TX),\n  mSpaceName(spacename),\n  mEngine(std::make_unique<group_balancer::StdDrainerEngine>())\n{\n  mThread.reset(&GroupDrainer::GroupDrain, this);\n}\n\nGroupDrainer::~GroupDrainer()\n{\n  mThread.join();\n}\n\nvoid\nGroupDrainer::GroupDrain(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(GROUPDRAINER_THREADNAME);\n  eosGroupsInfoFetcher fetcher(mSpaceName,\n  [](GroupStatus s) {\n    return s == GroupStatus::DRAIN || s == GroupStatus::ON;\n  });\n  mRefreshGroups = true;\n  bool config_status = false;\n  eos::common::observer_tag_t observer_tag {};\n  eos_info(\"%s\", \"msg=\\\"starting group drainer thread\\\"\");\n  eos::common::BackOffInvoker backoff_logger;\n\n  while (!assistant.terminationRequested()) {\n    if (!gOFS->mMaster->IsMaster()) {\n      assistant.wait_for(std::chrono::seconds(60));\n      eos_debug(\"%s\", \"msg=\\\"GroupDrainer disabled for slave\\\"\");\n      continue;\n    }\n\n    bool expected_reconfiguration = true;\n\n    if (mDoConfigUpdate.compare_exchange_strong(expected_reconfiguration, false,\n        std::memory_order_acq_rel)) {\n      config_status = Configure(mSpaceName);\n      mRefreshGroups = config_status;\n    }\n\n    if (!gOFS->mConverterEngine || !config_status) {\n      // wait for a few seconds before trying to see for reconfiguration in order\n      // to not simply always check the atomic in an inf loop\n      backoff_logger.invoke([this, &config_status]() {\n        eos_info(\"msg=\\\"Invalid GroupDrainer Configuration or Converter \"\n                 \"not enabled, sleeping!\\\" config_status=%d, space=%s\",\n                 config_status, mSpaceName.c_str());\n      });\n      assistant.wait_for(std::chrono::seconds(30));\n      continue;\n    }\n\n    if (!observer_tag) {\n      // Safe to access gOFS->mConverterEngine as config_status would've failed\n      // before this!\n      if (auto mgr = gOFS->mConverterEngine->getObserverMgr()) {\n        observer_tag = mgr->addObserver([this](\n                                          ConverterEngine::JobStatusT status,\n        std::string tag) {\n          auto info = ConversionInfo::parseConversionString(tag);\n\n          if (!info) {\n            eos_crit(\"Unable to parse conversion info from tag=%s\",\n                     tag.c_str());\n            return;\n          }\n\n          switch (status) {\n          case ConverterEngine::JobStatusT::DONE:\n            this->dropTransferEntry(info->mFid);\n            eos_info(\"msg=\\\"Dropping completed entry\\\" fxid=%08llx tag=%s\", info->mFid, tag.c_str());\n            break;\n\n          case ConverterEngine::JobStatusT::FAILED:\n            eos_info(\"msg=\\\"Tracking failed transfer\\\" fxid=%08llx tag=%s\", info->mFid, tag.c_str());\n            this->addFailedTransferEntry(info->mFid, std::move(tag));\n            break;\n\n          default:\n            eos_debug(\"Handler not applied\");\n          }\n        });\n      } else {\n        // We're reaching here as the converter is still initializing, wait a few seconds\n        eos_info(\"%s\",\n                 \"msg=\\\"Couldn't register observers on Converter, trying again after 30s\\\"\");\n        assistant.wait_for(std::chrono::seconds(30));\n        continue;\n      }\n    }\n\n    if (isTransfersFull()) {\n      // We are currently full, wait for a few seconds before pruning & trying\n      // again\n      eos_info(\"msg=\\\"transfer queue full, pausing before trying again\\\"\");\n      assistant.wait_for(std::chrono::seconds(2));\n      continue;\n    }\n\n    if (isUpdateNeeded(mLastUpdated, mRefreshGroups)) {\n      mEngine->configure(mDrainerEngineConf);\n      mEngine->populateGroupsInfo(fetcher.fetch());\n      pruneTransfers();\n    }\n\n    if (!mEngine->canPick()) {\n      eos_info(\"msg=\\\"Cannot pick, Empty source or target groups, check status \"\n               \"if this is not expected\\\", %s\",\n               mEngine->get_status_str(false, true).c_str());\n      assistant.wait_for(std::chrono::seconds(60));\n      continue;\n    }\n\n    prepareTransfers();\n\n    if (mPauseExecution) {\n      eos_info(\"%s\", \"msg=\\\"Pausing Execution for 30s!\\\"\");\n      assistant.wait_for(std::chrono::seconds(30));\n    } else {\n      // add some delay here before the next cycle\n      assistant.wait_for(std::chrono::seconds(1));\n    }\n  }\n}\n\nbool\nGroupDrainer::isUpdateNeeded(std::chrono::time_point<std::chrono::steady_clock>&\n                             tp,\n                             bool& force)\n{\n  auto now = std::chrono::steady_clock::now();\n\n  if (force) {\n    tp = now;\n    force = false;\n    return true;\n  }\n\n  auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - tp);\n\n  if (elapsed > mCacheExpiryTime) {\n    tp = now;\n    return true;\n  }\n\n  return false;\n}\n\n// Prune all transfers which are done by Converter, since the converter will\n// pop entries off FidTracker once done, this should give us an idea of our\n// queued transfers being actually realized\nvoid\nGroupDrainer::pruneTransfers()\n{\n  size_t prune_count {0};\n  {\n    std::lock_guard lg(mTransfersMtx);\n    prune_count = eos::common::erase_if(mTransfers, [](const auto & p) {\n      return !gOFS->mFidTracker.HasEntry(p);\n    });\n  }\n\n  if (prune_count > 0)\n    eos_info(\"msg=\\\"pruned %lu transfers, transfers in flight=%lu\\\"\",\n             prune_count, mTransfers.size());\n}\n\nvoid\nGroupDrainer::prepareTransfers()\n{\n  uint64_t allowed_tx = getAllowedTransfers();\n\n  try {\n    for (uint64_t i = 0; i < allowed_tx; ++i) {\n      prepareTransfer(mRRSeed++);\n\n      if (mRefreshGroups || mPauseExecution) {\n        return;\n      }\n    }\n  } catch (std::exception& ec) {\n    // Very unlikely to reach here, since we already x-check that we don't supply\n    // empty containers to RR picker, but in the rare case, just force a refresh of\n    // our cached groups info\n    eos_crit(\"msg=\\\"Got an exception while creating transfers=%s\\\"\", ec.what());\n    mRefreshGroups = true;\n  }\n}\n\nvoid\nGroupDrainer::prepareTransfer(uint64_t index)\n{\n  auto [grp_drain_from, grp_drain_to] = mEngine->pickGroupsforTransfer(index);\n\n  if (grp_drain_from.empty() || grp_drain_to.empty()) {\n    // will not be likely reached, as Engine->canPick shouldn't reply earlier\n    eos_static_info(\"msg=\\\"engine gave us empty groups skipping\\\"\");\n    return;\n  }\n\n  eos_debug(\"msg=\\\"Doing transfer \\\" index=%d\", index);\n  // No need for lock here as if there is a write it is serial here and sync., the UI\n  // call from another thread doesn't do modifications! Also we expect the failure\n  // conditions to only happen during the initial phases when we haven't yet filled the\n  // various drain Fs Maps or during periodic intervals when we run out files to\n  // transfer!\n  auto fsids = mDrainFsMap.find(grp_drain_from);\n\n  if (fsids == mDrainFsMap.end() ||\n      isUpdateNeeded(mDrainMapLastUpdated, mRefreshFSMap) ||\n      fsids->second.empty()) {\n    {\n      std::scoped_lock slock(mDrainFsMapMtx);\n      std::tie(fsids, std::ignore) = mDrainFsMap.insert_or_assign(grp_drain_from,\n                                     fsutils::FsidsinGroup(grp_drain_from));\n      mPauseExecution = isDrainFSMapEmpty(mDrainFsMap);\n    }\n    mDrainMapLastUpdated = std::chrono::steady_clock::now();\n\n    // We enter the following conditional if the group concerned is having empty FSes\n    // check if we reach a drain complete state!\n    if (fsids->second.empty()) {\n      // We reach here when all the FSes in the group are either offline or empty!\n      // force a refresh of Groups Info for the next cycle, in that case the new\n      // Groups Info will have 0 capacity groups and the engine will find that it\n      // has no more targets to pick effectively stopping any further processing\n      // other than the Groups Refresh every few minutes to check any new drain states\n      eos_debug(\"msg=\\\"Encountered group with no online FS\\\" group=%s\",\n                grp_drain_from.c_str());\n      mRefreshGroups = setDrainCompleteStatus(grp_drain_from,\n                                              checkGroupDrainStatus(grp_drain_from));\n\n      if (mRefreshGroups) {\n        eos_info(\"msg=\\\"Group completed drain!\\\" group=%s\",\n                 grp_drain_from.c_str());\n      }\n\n      return;\n    }\n  }\n\n  auto fsid = eos::common::pickIndexRR(fsids->second,\n                                       mGroupFSSeed[grp_drain_from]++);\n  auto fids = mCacheFileList.find(fsid);\n\n  if (fids == mCacheFileList.end() || fids->second.empty()) {\n    bool status;\n    std::tie(status, fids) = populateFids(fsid);\n\n    if (!status) {\n      eos_debug(\"%s\", \"\\\"Refreshing FS drain statuses\\\"\");\n      mRefreshFSMap = true;\n      return;\n    }\n  }\n\n  // Cross-check that we do have a valid iterator anyway!\n  if (fids != mCacheFileList.end()) {\n    if (fids->second.size() > 0) {\n      scheduleTransfer(fids->second.back(), grp_drain_from, grp_drain_to, fsid);\n      fids->second.pop_back();\n    } else {\n      eos_debug(\"%s\", \"Got a valid iter but empty files!\");\n    }\n  } else {\n    eos_debug(\"\\\"msg=couldn't find files in fsid=%d\\\"\", fsid);\n  }\n}\n\nvoid\nGroupDrainer::scheduleTransfer(eos::common::FileId::fileid_t fid,\n                               const std::string& src_grp,\n                               const std::string& tgt_grp,\n                               eos::common::FileSystem::fsid_t src_fsid)\n{\n  if (src_grp.empty() || tgt_grp.empty()) {\n    eos_err(\"%s\", \"msg=\\\"Got empty transfer groups!\\\"\");\n    return;\n  }\n\n  // Cross-check that the file wasn't scheduled before we attempt to check FS\n  // and possibly redo a transfer\n  if (trackedTransferEntry(fid)) {\n    eos_info(\"msg=\\\"Skipping scheduling of Tracked Transfer\\\" fxid=%08llx\", fid);\n    return;\n  }\n\n  uint64_t filesz;\n  auto conv_tag = getFileProcTransferNameAndSize(fid, tgt_grp, &filesz,\n                  group_balancer::NullFilter);\n\n  if (conv_tag.empty()) {\n    eos_err(\"msg=\\\"Possibly failed proc file found\\\" fxid=%08llx\", fid);\n    return;\n  }\n\n  conv_tag += \"^groupdrainer^\";\n  conv_tag.erase(0, gOFS->MgmProcConversionPath.length() + 1);\n  std::string err_msg;\n\n  if (gOFS->mConverterEngine->ScheduleJob(fid, conv_tag, err_msg)) {\n    eos_info(\"msg=\\\"group drainer scheduled job file=\\\"%s\\\" \"\n             \"src_grp=\\\"%s\\\" dst_grp=\\\"%s\\\"\", conv_tag.c_str(),\n             src_grp.c_str(), tgt_grp.c_str());\n    addTransferEntry(fid);\n    mDrainProgressTracker.increment(src_fsid);\n  } else {\n    addFailedTransferEntry(fid, std::move(conv_tag));\n  }\n}\n\nstd::pair<bool, GroupDrainer::cache_fid_map_t::iterator>\nGroupDrainer::populateFids(eos::common::FileSystem::fsid_t fsid)\n{\n  eos_debug(\"msg=\\\"populating FIDS from\\\" fsid=%d\", fsid);\n  //TODO: mark FSes in RO after threshold percent drain\n  const auto total_files = gOFS->eosFsView->getNumFilesOnFs(fsid);\n  if (total_files == 0) {\n    fsutils::ApplyDrainedStatus(fsid);\n    mCacheFileList.erase(fsid);\n    return {false, mCacheFileList.end()};\n  }\n\n  mDrainProgressTracker.setTotalFiles(fsid, total_files);\n\n  //Check if the FS is in the Retrytracker, skip these FSes,\n  //TODO: We could skip getNumFilesOnFs altogether every loop if we have\n  //RetryTracker entry and only check once every minute or so for the FSID\n  if (const auto kv = mFsidRetryCtr.find(fsid); kv != mFsidRetryCtr.end()) {\n    if (!kv->second.need_update(mRetryInterval)) {\n      eos_debug(\"msg=\\\"skipping retries as retry_interval hasn't passed\\\", \"\n                \" fsid=%d\", fsid);\n      return {false, mCacheFileList.end()};\n    }\n  }\n\n  std::vector<eos::common::FileId::fileid_t> local_fids, failed_fids, ghosts_to_delete;\n  {\n    uint32_t ctr = 0;\n    std::scoped_lock slock(mTransfersMtx, mFailedTransfersMtx);\n\n    for (const auto it_fid = gOFS->eosFsView->getStreamingFileList(fsid);\n         it_fid && it_fid->valid() && ctr < FID_CACHE_LIST_SZ;\n         it_fid->next()) {\n      const auto fid = it_fid->getElement();\n      try {\n        auto fmd = gOFS->eosFileService->getFileMD(fid);\n      } catch (eos::MDException& e) {\n        if (e.getErrno() == ENOENT) {\n          eos_warning(\"msg=\\\"Found ghost file, marking for deletion from FS view\\\" \"\n                      \"fsid=%d fxid=%08llx\",\n                      fsid,\n                      fid);\n          ghosts_to_delete.emplace_back(fid);\n        } else {\n          // This code should never be reached, may result in an infinite loop\n          eos_err(\"msg=\\\"Got an unexpected exception while populating FIDs\\\" \"\n                  \"fsid=%d fxid=%08llx errc=%d emsg=\\\"%s\\\"\",\n                  fsid,\n                  fid,\n                  e.getErrno(),\n                  e.getMessage().str().c_str());\n        }\n        // cannot continue processing this fid since we don't have its metadata\n        continue;\n      }\n\n      if (mFailedTransfers.count(fid)) {\n        failed_fids.emplace_back(fid);\n      } else if (!mTransfers.count(fid) && !mTrackedTransfers.count(fid)) {\n        local_fids.emplace_back(fid);\n        ++ctr;\n      }\n    }\n  }\n\n  for (const auto fid : ghosts_to_delete) {\n    eos_warning(\"msg=\\\"Deleting ghost file from FS view\\\" fsid=%d fxid=%08llx\", fsid, fid);\n    gOFS->eosFsView->eraseEntry(fsid, fid);\n  }\n\n  if (local_fids.empty() && !failed_fids.empty()) {\n    eos_debug(\"msg=\\\"Handling Retries for\\\" fsid=%lu\", fsid);\n    return handleRetries(fsid, std::move(failed_fids));\n  }\n\n  auto [it, _] = mCacheFileList.insert_or_assign(fsid, std::move(local_fids));\n  return {true, it};\n}\n\nbool\nGroupDrainer::Configure(const std::string& spaceName)\n{\n  using eos::common::StringToNumeric;\n  eos::common::RWMutexReadLock vlock(FsView::gFsView.ViewMutex);\n  FsSpace* space = nullptr;\n\n  if (auto kv = FsView::gFsView.mSpaceView.find(spaceName);\n      kv != FsView::gFsView.mSpaceView.end()) {\n    space = kv->second;\n  }\n\n  if (space == nullptr) {\n    eos_err(\"msg=\\\"no such space found\\\" space=%s\", spaceName.c_str());\n    return false;\n  }\n\n  bool is_enabled = space->GetConfigMember(\"groupdrainer\") == \"on\";\n  bool is_conv_enabled = gOFS->mConverterEngine->IsRunning();\n\n  if (!is_enabled || !is_conv_enabled) {\n    eos_info(\"msg=\\\"group drainer or converter not enabled\\\"\"\n             \" space=%s drainer_status=%d converter_status=%d\",\n             mSpaceName.c_str(), is_enabled, is_conv_enabled);\n    return false;\n  }\n\n  {\n    std::scoped_lock slock(mTransfersMtx);\n    eos::common::StringToNumeric(\n      space->GetConfigMember(\"groupdrainer.ntx\"), mMaxTransfers,\n      DEFAULT_NUM_TX);\n  }\n\n  eos::common::StringToNumeric(\n    space->GetConfigMember(\"groupdrainer.ntx\"), mMaxTransfers,\n    DEFAULT_NUM_TX);\n  eos::common::StringToNumeric(\n    space->GetConfigMember(\"groupdrainer.retry_interval\"), mRetryInterval,\n    DEFAULT_RETRY_INTERVAL);\n  eos::common::StringToNumeric(\n    space->GetConfigMember(\"groupdrainer.retry_count\"), mRetryCount,\n    MAX_RETRIES);\n  uint64_t cache_expiry_time;\n  bool status = eos::common::StringToNumeric(\n                  space->GetConfigMember(\"groupdrainer.group_refresh_interval\"),\n                  cache_expiry_time, DEFAULT_CACHE_EXPIRY_TIME);\n\n  if (status) {\n    mCacheExpiryTime = std::chrono::seconds(cache_expiry_time);\n  }\n\n  auto threshold_str = space->GetConfigMember(\"groupdrainer.threshold\");\n\n  if (!threshold_str.empty()) {\n    mDrainerEngineConf.insert_or_assign(\"threshold\", std::move(threshold_str));\n  } else {\n    mDrainerEngineConf.insert_or_assign(\"threshold\", DEFAULT_THRESHOLD);\n  }\n\n  return true;\n}\n\nstd::pair<bool, GroupDrainer::cache_fid_map_t::iterator>\nGroupDrainer::handleRetries(eos::common::FileSystem::fsid_t fsid,\n                            std::vector<eos::common::FileId::fileid_t>&& fids)\n{\n  auto tracker = mFsidRetryCtr[fsid];\n\n  if (tracker.count > mRetryCount) {\n    fsutils::ApplyFailedDrainStatus(fsid, fids.size());\n    mCacheFileList.erase(fsid);\n    return {false, mCacheFileList.end()};\n  }\n\n  if (tracker.need_update(mRetryInterval)) {\n    mFsidRetryCtr[fsid].update();\n    eos_info(\"msg=\\\"Retrying failed transfers for\\\" fsid=%lu, count=%lu retry_count=%d\",\n             fsid, fids.size(), tracker.count);\n    auto [it, _] = mCacheFileList.insert_or_assign(fsid, std::move(fids));\n    return {true, it};\n  }\n\n  eos_debug(\"%s\", \"Nothing to do here, returning empty!\");\n  return {true, mCacheFileList.end()};\n}\n\nGroupStatus\nGroupDrainer::checkGroupDrainStatus(const fsutils::fs_status_map_t& fs_map)\n{\n  uint16_t total_fs = 0, failed_fs = 0, drained_fs = 0;\n\n  for (const auto& kv : fs_map) {\n    if (kv.second.active_status == eos::common::ActiveStatus::kOffline) {\n      return GroupStatus::OFF;\n    }\n\n    ++total_fs;\n\n    switch (kv.second.drain_status) {\n    case eos::common::DrainStatus::kDrainFailed:\n      ++failed_fs;\n      break;\n\n    case eos::common::DrainStatus::kDrained:\n      ++drained_fs;\n      break;\n\n    case eos::common::DrainStatus::kNoDrain:\n      [[fallthrough]];\n\n    default:\n      // We've reached here because the fs is in one of the\n      // regular draining states and not one from GroupDrainer, this means\n      // the FS is either actually draining or in a state we don't recognize\n      return GroupStatus::ON;\n    }\n  }\n\n  if (failed_fs + drained_fs != total_fs) {\n    // Unlikely to reach!\n    eos_static_crit(\"msg=\\\"some FSes in unrecognized state\\\" total_fs=%d, \"\n                    \"failed_fs=%d drained_fs=%d\", total_fs, failed_fs,\n                    drained_fs);\n    return GroupStatus::ON;\n  }\n\n  if (failed_fs > 0) {\n    return GroupStatus::DRAINFAILED;\n  }\n\n  return GroupStatus::DRAINCOMPLETE;\n}\n\n\nGroupStatus\nGroupDrainer::checkGroupDrainStatus(const std::string& groupname)\n{\n  auto fs_map = fsutils::GetGroupFsStatus(groupname);\n  return checkGroupDrainStatus(fs_map);\n}\n\nbool\nGroupDrainer::setDrainCompleteStatus(const std::string& groupname,\n                                     GroupStatus s)\n{\n  if (!isValidDrainCompleteStatus(s)) {\n    return false;\n  }\n\n  eos::common::RWMutexWriteLock lock(FsView::gFsView.ViewMutex);\n  auto group_it = FsView::gFsView.mGroupView.find(groupname);\n\n  if (group_it == FsView::gFsView.mGroupView.end()) {\n    return false;\n  }\n\n  return group_it->second->SetConfigMember(\"status\",\n         group_balancer::GroupStatusToStr(s));\n}\n\nstatic TableRow\ngenerate_progress_row(eos::common::FileSystem::fsid_t fsid,\n                      float drain_percent, uint64_t file_ctr,\n                      uint64_t total_files)\n{\n  TableRow row;\n  row.emplace_back(fsid, format_l);\n  row.emplace_back((double)drain_percent, format_f);\n  row.emplace_back((long long)file_ctr, format_l);\n  row.emplace_back((long long)total_files, format_l);\n  return row;\n}\n\nstd::string\nGroupDrainer::getStatus(StatusFormat status_fmt) const\n{\n  // TODO: Expose more counters in monitoring!\n  if (status_fmt == StatusFormat::MONITORING) {\n    return mEngine->get_status_str(false, true);\n  }\n\n  std::stringstream ss;\n  {\n    std::scoped_lock sl(mTransfersMtx);\n    ss << \"Max allowed Transfers  : \" << mMaxTransfers << \"\\n\"\n       << \"Transfers in Queue     : \" << mTransfers.size() << \"\\n\"\n       << \"Total Transfers        : \" << mTrackedTransfers.size() << \"\\n\";\n  }\n  auto failed_tx_sz = mFailedTransfers.size();\n  ss << \"Transfers Failed       : \" << failed_tx_sz << \"\\n\";\n  auto threshold_it = mDrainerEngineConf.find(\"threshold\");\n\n  if (threshold_it != mDrainerEngineConf.end()) {\n    ss << \"Configured Threshold   : \" << threshold_it->second << \"%\\n\";\n  } else {\n    ss << \"Configured Threshold   : <not set>\\n\";\n  }\n\n  ss << mEngine->get_status_str(true);\n\n  if (mDrainFsMap.empty()) {\n    return ss.str();\n  }\n\n  if (status_fmt == StatusFormat::DETAIL) {\n    std::scoped_lock sl(mDrainFsMapMtx);\n\n    if (isDrainFSMapEmpty(mDrainFsMap)) {\n      return ss.str();\n    }\n\n    for (const auto& kv : mDrainFsMap) {\n      ss << \"Group: \" << kv.first << \"\\n\";\n      TableFormatterBase table_fs_status(true);\n      TableData table_data;\n      table_fs_status.SetHeader({\n        {\"fsid\", 10, format_l},\n        {\"Drain Progress\", 10, format_f},\n        {\"Total Transfers\", 10, format_l},\n        {\"Total files\", 10, format_l}\n      });\n\n      for (const auto& fsid : kv.second) {\n        table_data.emplace_back(generate_progress_row(fsid,\n                                mDrainProgressTracker.getDrainStatus(fsid),\n                                mDrainProgressTracker.getFileCounter(fsid),\n                                mDrainProgressTracker.getTotalFiles(fsid)));\n      }\n\n      table_fs_status.AddRows(table_data);\n      ss << table_fs_status.GenerateTable() << \"\\n\";\n    }\n  }\n\n  return ss.str();\n}\n\nvoid\nGroupDrainer::resetFailedTransfers()\n{\n  std::scoped_lock sl(mFailedTransfersMtx, mTransfersMtx);\n  mFailedTransfers.clear();\n  mTrackedTransfers.clear();\n}\n\nvoid\nGroupDrainer::resetCaches()\n{\n  {\n    std::scoped_lock sl(mFailedTransfersMtx, mTransfersMtx);\n    mFailedTransfers.clear();\n    mTransfers.clear();\n    mTrackedTransfers.clear();\n  }\n  // force a refresh of the global groups map info\n  mDoConfigUpdate.store(true, std::memory_order_relaxed);\n  // TODO: Have a functionality to clear cached filelists as well!\n}\n\nbool\nGroupDrainer::isDrainFSMapEmpty(const drain_fs_map_t& drainFsMap)\n{\n  return std::all_of(drainFsMap.begin(), drainFsMap.end(),\n  [](const auto & p) {\n    return p.second.empty();\n  });\n}\n\n} // eos::mgm\n"
  },
  {
    "path": "mgm/groupdrainer/GroupDrainer.hh",
    "content": "//------------------------------------------------------------------------------\n// File: GroupDrainer.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include <string_view>\n#include \"common/AssistedThread.hh\"\n#include \"common/Logging.hh\"\n#include \"common/FileId.hh\"\n#include \"common/FileSystem.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include <vector>\n#include <unordered_set>\n#include \"mgm/groupbalancer/BalancerEngineTypes.hh\"\n#include \"mgm/groupdrainer/RetryTracker.hh\"\n#include \"mgm/groupdrainer/DrainProgressTracker.hh\"\n#include \"mgm/utils/FileSystemStatusUtils.hh\"\n\nnamespace eos::mgm\n{\n\nnamespace group_balancer\n{\nclass BalancerEngine;\n} // namespace group_balancer\n\nconstexpr uint32_t FID_CACHE_LIST_SZ = 1000;\nconstexpr uint32_t DEFAULT_NUM_TX = 1000;\nconstexpr uint64_t DEFAULT_CACHE_EXPIRY_TIME = 300;\nconstexpr uint16_t MAX_RETRIES = 5;\nconstexpr const char* DEFAULT_THRESHOLD = \"0.0\";\n\n\nusing mgm::group_balancer::GroupStatus;\n\nclass GroupDrainer: public eos::common::LogId\n{\npublic:\n  using cache_fid_map_t = std::map<eos::common::FileSystem::fsid_t,\n        std::vector<eos::common::FileId::fileid_t>>;\n  using drain_fs_map_t = std::map<std::string,\n        std::vector<common::FileSystem::fsid_t>>;\n\n  GroupDrainer(std::string_view spacename);\n  ~GroupDrainer();\n  void GroupDrain(ThreadAssistant& assistant) noexcept;\n\n  /*!\n   * Deteremine if an update is needed depending on the time point supplied\n   * We check against the set value of mCacheExpiryTime which is configured\n   * via groupdrainer.\n   * @param tp  The time point of last update, this will be modified to current\n   * time if an update happens\n   * @param force Force an update. This param will be reset on update\n   * @return\n   */\n  bool isUpdateNeeded(std::chrono::time_point<std::chrono::steady_clock>& tp,\n                      bool& force);\n  void pruneTransfers();\n  bool isTransfersFull() const\n  {\n    std::scoped_lock slock(mTransfersMtx);\n    return mTransfers.size() >= mMaxTransfers;\n  }\n\n  uint64_t getAllowedTransfers() const\n  {\n    std::scoped_lock slock(mTransfersMtx);\n\n    // unlikely, we always call this after checking isTransfersFull()\n    if (mMaxTransfers <= mTransfers.size()) {\n      return 0;\n    }\n\n    return mMaxTransfers - mTransfers.size();\n  }\n\n  void prepareTransfers();\n  void prepareTransfer(uint64_t index);\n  void scheduleTransfer(eos::common::FileId::fileid_t fid,\n                        const std::string& src_grp, const std::string& tgt_grp,\n                        eos::common::FileSystem::fsid_t src_fsid);\n\n  std::pair<bool, cache_fid_map_t::iterator>\n  populateFids(eos::common::FileSystem::fsid_t fsid);\n\n  void reconfigure()\n  {\n    mDoConfigUpdate.store(true, std::memory_order_release);\n  }\n\n  bool Configure(const std::string& spaceName);\n\n  void addTransferEntry(eos::common::FileId::fileid_t fid)\n  {\n    std::scoped_lock slock(mTransfersMtx);\n    mTransfers.emplace(fid);\n    mTrackedTransfers.emplace(fid);\n  }\n\n  void dropTransferEntry(eos::common::FileId::fileid_t fid)\n  {\n    {\n      std::scoped_lock slock(mTransfersMtx);\n      mTransfers.erase(fid);\n    }\n    {\n      std::scoped_lock slock(mFailedTransfersMtx);\n      mFailedTransfers.erase(fid);\n    }\n  }\n\n  void addFailedTransferEntry(eos::common::FileId::fileid_t fid,\n                              std::string&& entry)\n  {\n    {\n      std::scoped_lock slock(mFailedTransfersMtx);\n      mFailedTransfers.emplace(fid, std::move(entry));\n    }\n    {\n      std::scoped_lock slock(mTransfersMtx);\n      mTransfers.erase(fid);\n    }\n  }\n\n  // Returns if a transfer is tracked already by GroupDrainer, we are NOT\n  // supposed to schedule if (trackedTransferEntry(fid)) returns true, as it means\n  // we have already scheduled this transfer before.\n  // We allow failed transfers to be rescheduled again.\n  bool trackedTransferEntry(eos::common::FileId::fileid_t fid)\n  {\n    std::scoped_lock slock(mFailedTransfersMtx, mTransfersMtx);\n\n    if (mFailedTransfers.count(fid)) {\n      // Allow scheduling of failed transfers\n      return false;\n    }\n\n    // return if we have a tracked transfer entry\n    return mTrackedTransfers.find(fid) != mTrackedTransfers.end();\n  }\n\n  std::pair<bool, GroupDrainer::cache_fid_map_t::iterator>\n  handleRetries(eos::common::FileSystem::fsid_t fsid,\n                std::vector<eos::common::FileId::fileid_t>&& fids);\n\n  enum class StatusFormat {\n    NONE,\n    DETAIL,\n    MONITORING\n  };\n\n  std::string getStatus(StatusFormat status_fmt = StatusFormat::NONE) const;\n\n  void resetFailedTransfers();\n  void resetCaches();\n\n  static GroupStatus\n  checkGroupDrainStatus(const fsutils::fs_status_map_t& fs_map);\n\n  //! Check the drain statuses of all FSes in a group and map this to\n  //! a groupstatus. This might move a GroupDrainStatus in the future, but given\n  //! that we don't have a separate GroupDrain info, we just map it back to\n  //! a group status. This function is to be used to check the statuses of all FSes in a group.\n  //! Do not use this to actually just check the status of a group\n  //! from FSView!\n  //! \\param groupname the group whose status to check\n  //! \\return DrainFailed if one of the FSes is having a DrainFailed status\n  //!         DrainComplete if all of the FSes have completed draining\n  //!         Offline      if any of the FSes are offline\n  //!         Online       any other status\n  static GroupStatus checkGroupDrainStatus(const std::string& groupname);\n\n  static bool isValidDrainCompleteStatus(GroupStatus s)\n  {\n    return s == GroupStatus::DRAINCOMPLETE ||\n           s == GroupStatus::DRAINFAILED;\n  }\n\n  static bool setDrainCompleteStatus(const std::string& groupname,\n                                     GroupStatus s);\n\n  static bool isDrainFSMapEmpty(const drain_fs_map_t& drainFsMap);\nprivate:\n  bool mRefreshFSMap {true};\n  bool mRefreshGroups {true};\n  bool mPauseExecution {false};\n  std::atomic<bool> mDoConfigUpdate {true};\n  uint16_t mRetryCount; // < Max retries for failed transfers\n  uint16_t mRRSeed {0};\n  uint32_t mMaxTransfers; // < Max no of transactions to keep in flight\n  uint64_t mRetryInterval; // < Retry Interval for failed transfers\n  std::chrono::time_point<std::chrono::steady_clock> mLastUpdated;\n  std::chrono::time_point<std::chrono::steady_clock> mDrainMapLastUpdated;\n  std::chrono::seconds mCacheExpiryTime {300};\n\n  std::string mSpaceName;\n  AssistedThread mThread;\n  std::unique_ptr<group_balancer::BalancerEngine> mEngine;\n\n  group_balancer::engine_conf_t\n  mDrainerEngineConf; ///< string k-v map of engine conf\n  //! map tracking scheduled transfers, will be cleared periodically\n  //! TODO: use a flat_map structure here, we are usually size capped to ~10K\n  mutable std::mutex mTransfersMtx;\n  mutable std::mutex mFailedTransfersMtx;\n  std::unordered_set<eos::common::FileId::fileid_t> mTransfers;\n  std::unordered_map<eos::common::FileId::fileid_t, std::string> mFailedTransfers;\n\n  // TODO future: use a bloom filter here if we find heavy mem. usage\n  // The only use case is to check if a file is not a member of a set, so a\n  // perfect use case as we don't care about false +ve memberships\n  std::unordered_set<eos::common::FileId::fileid_t> mTrackedTransfers;\n\n  //! map holding a seed for RR picker for every Group for the FS\n  std::map<std::string, uint16_t> mGroupFSSeed;\n\n  //! a map holding the current list of FSes in the draining groups\n  //! this is unlikely to have more than a single digit number of keys..maybe a\n  //! a vector of pairs might be ok?\n  mutable std::mutex\n  mDrainFsMapMtx; ///< This mutex is only to sync. b/w UI thread\n  // and the internal GroupDrainer Threads, there is no need for locking for\n  // reads within GroupDrainer!\n\n  drain_fs_map_t mDrainFsMap;\n  std::map<common::FileSystem::fsid_t, RetryTracker> mFsidRetryCtr;\n  std::set<common::FileSystem::fsid_t> mFailedFsids;\n  cache_fid_map_t mCacheFileList;\n  DrainProgressTracker mDrainProgressTracker;\n};\n\n} // namespace eos::mgm\n"
  },
  {
    "path": "mgm/groupdrainer/RetryTracker.hh",
    "content": "#pragma once\n#include \"common/SteadyClock.hh\"\n\nnamespace eos::mgm\n{\n\nconstexpr uint64_t DEFAULT_RETRY_INTERVAL = 4 * 3600;\n\n\nstruct RetryTracker {\n  uint16_t count;\n  std::chrono::time_point<std::chrono::steady_clock> last_run_time {};\n\n  RetryTracker() : count(0) {}\n\n  bool need_update(uint64_t retry_interval = DEFAULT_RETRY_INTERVAL,\n                   eos::common::SteadyClock* clock = nullptr) const\n  {\n    if (count == 0) {\n      return true;\n    }\n\n    auto curr_time  = eos::common::SteadyClock::now(clock);\n    auto elapsed = std::chrono::duration_cast<std::chrono::seconds>\n                   (curr_time - last_run_time);\n    return (elapsed.count() > (int64_t)retry_interval);\n  }\n\n  void update(eos::common::SteadyClock* clock = nullptr)\n  {\n    ++count;\n    last_run_time = eos::common::SteadyClock::now(clock);\n  }\n};\n\n} // eos::mgm\n"
  },
  {
    "path": "mgm/grpc/GrpcNsInterface.cc",
    "content": "// ----------------------------------------------------------------------\n// File: GrpcNsInterface.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"GrpcNsInterface.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/Timing.hh\"\n#include \"common/Path.hh\"\n#include \"common/LinuxFds.hh\"\n#include \"common/LinuxStat.hh\"\n#include \"common/LinuxMemConsumption.hh\"\n#include \"mgm/acl/Acl.hh\"\n#include \"mgm/proc/IProcCommand.hh\"\n#include \"mgm/proc/user/AclCmd.hh\"\n#include \"mgm/proc/admin/QuotaCmd.hh\"\n#include \"mgm/proc/user/RmCmd.hh\"\n#include \"mgm/proc/user/TokenCmd.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/ofs/XrdMgmOfsDirectory.hh\"\n#include \"mgm/recycle/Recycle.hh\"\n#include \"mgm/proc/user/RecycleCmd.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"namespace/MDException.hh\"\n#include \"namespace/interface/ContainerIterators.hh\"\n#include \"namespace/utils/Etag.hh\"\n#include \"namespace/utils/Attributes.hh\"\n\n#include <regex.h>\n/*----------------------------------------------------------------------------*/\n\n\nEOSMGMNAMESPACE_BEGIN\n\n#ifdef EOS_GRPC\n\nbool\nGrpcNsInterface::Filter(std::shared_ptr<eos::IFileMD> md,\n                        const eos::rpc::MDSelection& filter)\n{\n  errno = 0;\n\n  // see if filtering is enabled\n  if (!filter.select()) {\n    return false;\n  }\n\n  eos::IFileMD::ctime_t ctime;\n  eos::IFileMD::ctime_t mtime;\n  md->getCTime(ctime);\n  md->getMTime(mtime);\n\n  // empty file\n  if (filter.size().zero()) {\n    if (!md->getSize()) {\n      // accepted\n    } else {\n      return true;\n    }\n  } else {\n    if ((filter.size().min() <= md->getSize()) &&\n        ((md->getSize() <= filter.size().max()) || (!filter.size().max()))) {\n      // accepted\n    } else {\n      return true;\n    }\n  }\n\n  if (filter.ctime().zero()) {\n    if (!ctime.tv_sec && !ctime.tv_nsec) {\n      // accepted\n    } else {\n      return true;\n    }\n  } else {\n    if ((filter.ctime().min() <= (uint64_t)(ctime.tv_sec)) &&\n        ((filter.ctime().max() >= (uint64_t)(ctime.tv_sec)) ||\n         (!filter.ctime().max()))) {\n      // accepted\n    } else {\n      return true;\n    }\n  }\n\n  if (filter.mtime().zero()) {\n    if (!mtime.tv_sec && !mtime.tv_nsec) {\n      // accepted\n    } else {\n      return true;\n    }\n  } else {\n    if ((filter.mtime().min() <= (uint64_t)(mtime.tv_sec)) &&\n        ((filter.mtime().max() >= (uint64_t)(mtime.tv_sec)) ||\n         (!filter.mtime().max()))) {\n      // accepted\n    } else {\n      return true;\n    }\n  }\n\n  if (filter.mtime().zero()) {\n    if (!mtime.tv_sec && !mtime.tv_nsec) {\n      // accepted\n    } else {\n      return true;\n    }\n  } else {\n    if ((filter.mtime().min() <= (uint64_t)(mtime.tv_sec)) &&\n        ((filter.mtime().max() >= (uint64_t)(mtime.tv_sec)) ||\n         (!filter.mtime().max()))) {\n      // accepted\n    } else {\n      return true;\n    }\n  }\n\n  if (filter.locations().zero()) {\n    if (!mtime.tv_sec && !mtime.tv_nsec) {\n      // accepted\n    } else {\n      return true;\n    }\n  } else {\n    if ((filter.locations().min() <= (uint64_t)(mtime.tv_sec)) &&\n        ((filter.locations().max() >= (uint64_t)(mtime.tv_sec)) ||\n         (!filter.locations().max()))) {\n      // accepted\n    } else {\n      return true;\n    }\n  }\n\n  if (filter.owner_root()) {\n    if (!md->getCUid()) {\n      // accepted\n    } else {\n      return true;\n    }\n  } else {\n    if (filter.owner()) {\n      if (filter.owner() == md->getCUid()) {\n        // accepted\n      } else {\n        return true;\n      }\n    }\n  }\n\n  if (filter.group_root()) {\n    if (!md->getCGid()) {\n      // accepted\n    } else {\n      return true;\n    }\n  } else {\n    if (filter.group()) {\n      if (filter.group() == md->getCGid()) {\n        // accepted\n      } else {\n        return true;\n      }\n    }\n  }\n\n  if (filter.layoutid()) {\n    if (md->getLayoutId() != filter.layoutid()) {\n      return true;\n    }\n  }\n\n  if (filter.flags()) {\n    if (md->getFlags() != filter.flags()) {\n      return true;\n    }\n  }\n\n  if (filter.symlink()) {\n    if (!md->isLink()) {\n      return true;\n    }\n  }\n\n  if (filter.checksum().type().length()) {\n    if (filter.checksum().type() !=  eos::common::LayoutId::GetChecksumStringReal(\n          md->getLayoutId())) {\n      return true;\n    }\n  }\n\n  if (filter.checksum().value().length()) {\n    std::string cks(md->getChecksum().getDataPtr(), md->getChecksum().size());\n\n    if (filter.checksum().value() != cks) {\n      return true;\n    }\n  }\n\n  eos::IFileMD::XAttrMap xattr = md->getAttributes();\n\n  for (const auto& elem : filter.xattr()) {\n    if (xattr.count(elem.first)) {\n      if (elem.second.length()) {\n        if (xattr[elem.first] == elem.second) {\n          // accepted\n        } else {\n          return true;\n        }\n      } else {\n        // accepted\n      }\n    } else {\n      return true;\n    }\n  }\n\n  if (filter.regexp_filename().length()) {\n    int regexErrorCode;\n    int result;\n    regex_t regex;\n    std::string regexString = filter.regexp_filename();\n    regexErrorCode = regcomp(&regex, regexString.c_str(), REG_EXTENDED);\n\n    if (regexErrorCode) {\n      regfree(&regex);\n      errno = EINVAL;\n      return true;\n    }\n\n    result = regexec(&regex, md->getName().c_str(), 0, NULL, 0);\n    regfree(&regex);\n\n    if (result == 0) {\n      // accepted\n    } else if (result == REG_NOMATCH) {\n      return true;\n    } else {\n      errno = ENOMEM;\n      return true;\n    }\n  }\n\n  return false;;\n}\n\nbool\nGrpcNsInterface::Filter(std::shared_ptr<eos::IContainerMD> md,\n                        const eos::rpc::MDSelection& filter)\n\n{\n  errno = 0;\n\n  // see if filtering is enabled\n  if (!filter.select()) {\n    return false;\n  }\n\n  eos::IContainerMD::ctime_t ctime;\n  eos::IContainerMD::ctime_t mtime;\n  eos::IContainerMD::ctime_t stime;\n  md->getCTime(ctime);\n  md->getMTime(mtime);\n  md->getTMTime(stime);\n  size_t nchildren = md->getNumContainers() + md->getNumFiles();\n  uint64_t treesize = md->getTreeSize();\n\n  // empty file\n  if (filter.children().zero()) {\n    if (!nchildren) {\n      // accepted\n    } else {\n      return true;\n    }\n  } else {\n    if ((filter.children().min() <= nchildren) &&\n        ((nchildren <= filter.children().max()) || (!filter.children().max()))) {\n      // accepted\n    } else {\n      return true;\n    }\n  }\n\n  if (filter.treesize().zero()) {\n    if (!treesize) {\n      // accepted\n    } else {\n      return true;\n    }\n  } else {\n    if ((filter.treesize().min() <= treesize) &&\n        ((treesize <= filter.treesize().max()) || (!filter.treesize().max()))) {\n      // accepted\n    } else {\n      return true;\n    }\n  }\n\n  if (filter.ctime().zero()) {\n    if (!ctime.tv_sec && !ctime.tv_nsec) {\n      // accepted\n    } else {\n      return true;\n    }\n  } else {\n    if ((filter.ctime().min() <= (uint64_t)(ctime.tv_sec)) &&\n        ((filter.ctime().max() >= (uint64_t)(ctime.tv_sec)) ||\n         (!filter.ctime().max()))) {\n      // accepted\n    } else {\n      return true;\n    }\n  }\n\n  if (filter.mtime().zero()) {\n    if (!mtime.tv_sec && !mtime.tv_nsec) {\n      // accepted\n    } else {\n      return true;\n    }\n  } else {\n    if ((filter.mtime().min() <= (uint64_t)(mtime.tv_sec)) &&\n        ((filter.mtime().max() >= (uint64_t)(mtime.tv_sec)) ||\n         (!filter.mtime().max()))) {\n      // accepted\n    } else {\n      return true;\n    }\n  }\n\n  if (filter.stime().zero()) {\n    if (!stime.tv_sec && !stime.tv_nsec) {\n      // accepted\n    } else {\n      return true;\n    }\n  } else {\n    if ((filter.stime().min() <= (uint64_t)(stime.tv_sec)) &&\n        ((filter.stime().max() >= (uint64_t)(stime.tv_sec)) ||\n         (!filter.stime().max()))) {\n      // accepted\n    } else {\n      return true;\n    }\n  }\n\n  if (filter.owner_root()) {\n    if (!md->getCUid()) {\n      // accepted\n    } else {\n      return true;\n    }\n  } else {\n    if (filter.owner()) {\n      if (filter.owner() == md->getCUid()) {\n        // accepted\n      } else {\n        return true;\n      }\n    }\n  }\n\n  if (filter.group_root()) {\n    if (!md->getCGid()) {\n      // accepted\n    } else {\n      return true;\n    }\n  } else {\n    if (filter.group()) {\n      if (filter.group() == md->getCGid()) {\n        // accepted\n      } else {\n        return true;\n      }\n    }\n  }\n\n  if (filter.flags()) {\n    if (md->getFlags() != filter.flags()) {\n      return true;\n    }\n  }\n\n  eos::IContainerMD::XAttrMap xattr = md->getAttributes();\n\n  for (const auto& elem : filter.xattr()) {\n    if (xattr.count(elem.first)) {\n      if (elem.second.length()) {\n        if (xattr[elem.first] == elem.second) {\n          // accepted\n        } else {\n          return true;\n        }\n      } else {\n        // accepted\n      }\n    } else {\n      return true;\n    }\n  }\n\n  if (filter.regexp_dirname().length()) {\n    int regexErrorCode;\n    int result;\n    regex_t regex;\n    std::string regexString = filter.regexp_dirname();\n    regexErrorCode = regcomp(&regex, regexString.c_str(), REG_EXTENDED);\n\n    if (regexErrorCode) {\n      regfree(&regex);\n      errno = EINVAL;\n      return true;\n    }\n\n    result = regexec(&regex, md->getName().c_str(), 0, NULL, 0);\n    regfree(&regex);\n\n    if (result == 0) {\n      // accepted\n    } else if (result == REG_NOMATCH) {\n      return true;\n    } else {\n      errno = ENOMEM;\n      return true;\n    }\n  }\n\n  return false;\n}\n\nnamespace\n{\n// Add the checksum in the list of checksums in the FileMd object\nvoid AddChecksum(eos::rpc::FileMdProto* fmd, uint32_t type, const char* xs,\n                 size_t size)\n{\n  auto checksum = fmd->mutable_checksums()->Add();\n  checksum->set_type(eos::common::LayoutId::GetChecksumStringReal(type));\n  checksum->set_value(xs, size);\n}\n}\n\n\ngrpc::Status\nGrpcNsInterface::GetMD(eos::common::VirtualIdentity& vid,\n                       grpc::ServerWriter<eos::rpc::MDResponse>* writer,\n                       const eos::rpc::MDRequest* request, bool check_perms,\n                       bool lock,\n                       bool access_self)\n{\n  eos::common::RWMutexReadLock viewReadLock;\n\n  if ((request->type() == eos::rpc::FILE) ||\n      (request->type() == eos::rpc::STAT)) {\n    // stream file meta data\n    eos::common::RWMutexReadLock viewReadLock;\n    std::shared_ptr<eos::IFileMD> fmd;\n    std::shared_ptr<eos::IContainerMD> pmd;\n    eos::IFileMD::XAttrMap attrmapF;\n    unsigned long fid = 0;\n    uint64_t clock = 0;\n    std::string path;\n    bool fallthrough = false;\n\n    if (request->id().ino()) {\n      // get by inode\n      fid = eos::common::FileId::InodeToFid(request->id().ino());\n    } else if (request->id().id()) {\n      // get by fileid\n      fid = request->id().id();\n    }\n\n    if (fid) {\n      eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, fid);\n    } else {\n      eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, request->id().path());\n    }\n\n    if (lock) {\n      viewReadLock.Grab(gOFS->eosViewRWMutex);\n    }\n\n    if (fid) {\n      try {\n        fmd = gOFS->eosFileService->getFileMD(fid, &clock);\n        path = gOFS->eosView->getUri(fmd.get());\n        gOFS->listAttributes(gOFS->eosView, fmd.get(), attrmapF, false);\n\n        if (check_perms) {\n          pmd = gOFS->eosDirectoryService->getContainerMD(fmd->getContainerId());\n        }\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        eos_static_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                         e.getMessage().str().c_str());\n\n        if (request->type() != eos::rpc::STAT) {\n          if (errno == ENOENT) {\n            return grpc::Status::OK;\n          } else {\n            return grpc::Status((grpc::StatusCode)(errno), e.getMessage().str().c_str());\n          }\n        } else {\n          fallthrough  = true;\n        }\n      }\n    } else {\n      try {\n        fmd = gOFS->eosView->getFile(request->id().path());\n        path = gOFS->eosView->getUri(fmd.get());\n        vid.scope = path;\n        gOFS->listAttributes(gOFS->eosView, fmd.get(), attrmapF, false);\n\n        if (check_perms) {\n          pmd = gOFS->eosDirectoryService->getContainerMD(fmd->getContainerId());\n        }\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        eos_static_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                         e.getMessage().str().c_str());\n\n        if (request->type() != eos::rpc::STAT) {\n          if (errno == ENOENT) {\n            return grpc::Status::OK;\n          } else {\n            return grpc::Status((grpc::StatusCode)(errno), e.getMessage().str().c_str());\n          }\n        } else {\n          fallthrough  = true;\n        }\n      }\n    }\n\n    if (!fallthrough) {\n      if (check_perms && !Access(vid, X_OK, pmd, &attrmapF)) {\n        return grpc::Status(grpc::StatusCode::PERMISSION_DENIED,\n                            \"access to parent container denied\");\n      }\n\n      if (Filter(fmd, request->selection())) {\n        // short-cut for filtered MD\n        return grpc::Status::OK;\n      }\n\n      // create GRPC protobuf object\n      eos::rpc::MDResponse gRPCResponse;\n      gRPCResponse.set_type(eos::rpc::FILE);\n      gRPCResponse.mutable_fmd()->set_name(fmd->getName());\n      gRPCResponse.mutable_fmd()->set_id(fmd->getId());\n      gRPCResponse.mutable_fmd()->set_inode(eos::common::FileId::FidToInode(\n                                              fmd->getId()));\n      gRPCResponse.mutable_fmd()->set_cont_id(fmd->getContainerId());\n      gRPCResponse.mutable_fmd()->set_uid(fmd->getCUid());\n      gRPCResponse.mutable_fmd()->set_gid(fmd->getCGid());\n      gRPCResponse.mutable_fmd()->set_size(fmd->getSize());\n      gRPCResponse.mutable_fmd()->set_layout_id(fmd->getLayoutId());\n      gRPCResponse.mutable_fmd()->set_flags(fmd->getFlags());\n      gRPCResponse.mutable_fmd()->set_link_name(fmd->getLink());\n      eos::IFileMD::ctime_t ctime;\n      eos::IFileMD::ctime_t mtime;\n      fmd->getCTime(ctime);\n      fmd->getMTime(mtime);\n      gRPCResponse.mutable_fmd()->mutable_ctime()->set_sec(ctime.tv_sec);\n      gRPCResponse.mutable_fmd()->mutable_ctime()->set_n_sec(ctime.tv_nsec);\n      gRPCResponse.mutable_fmd()->mutable_mtime()->set_sec(mtime.tv_sec);\n      gRPCResponse.mutable_fmd()->mutable_mtime()->set_n_sec(mtime.tv_nsec);\n      // keep checksum for backward compatibility\n      gRPCResponse.mutable_fmd()->mutable_checksum()->set_value(\n        fmd->getChecksum().getDataPtr(), fmd->getChecksum().size());\n      gRPCResponse.mutable_fmd()->mutable_checksum()->set_type(\n        eos::common::LayoutId::GetChecksumStringReal(fmd->getLayoutId()));\n      // Default checksum\n      AddChecksum(gRPCResponse.mutable_fmd(),\n                  eos::common::LayoutId::GetChecksum(fmd->getLayoutId()),\n                  fmd->getChecksum().getDataPtr(), fmd->getChecksum().size());\n\n      // Alternative checksums\n      for (const auto& [type, xs] : fmd->getAltXs()) {\n        AddChecksum(gRPCResponse.mutable_fmd(), type, xs.c_str(), xs.size());\n      }\n\n      for (const auto& loca : fmd->getLocations()) {\n        gRPCResponse.mutable_fmd()->add_locations(loca);\n      }\n\n      for (const auto& loca : fmd->getUnlinkedLocations()) {\n        gRPCResponse.mutable_fmd()->add_unlink_locations(loca);\n      }\n\n      for (const auto& elem : fmd->getAttributes()) {\n        (*gRPCResponse.mutable_fmd()->mutable_xattrs())[elem.first] = elem.second;\n      }\n\n      std::string etag;\n      eos::calculateEtag(fmd.get(), etag);\n\n      if (fmd->hasAttribute(\"sys.eos.mdino\")) {\n        etag = \"hardlink\";\n      }\n\n      gRPCResponse.mutable_fmd()->set_etag(etag);\n      gRPCResponse.mutable_fmd()->set_path(path);\n      writer->Write(gRPCResponse);\n      return grpc::Status::OK;\n    }\n  }\n\n  if ((request->type() == eos::rpc::CONTAINER) ||\n      (request->type() == eos::rpc::STAT)) {\n    // stream container meta data\n    std::shared_ptr<eos::IContainerMD> cmd;\n    std::shared_ptr<eos::IContainerMD> pmd;\n    unsigned long cid = 0;\n    uint64_t clock = 0;\n    std::string path;\n\n    if (request->id().ino()) {\n      // get by inode\n      cid = request->id().ino();\n    } else if (request->id().id()) {\n      // get by containerid\n      cid = request->id().id();\n    }\n\n    if (!cid) {\n      eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView,\n          request->id().path());\n    } else {\n      eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView,\n          cid);\n    }\n\n    if (lock) {\n      viewReadLock.Grab(gOFS->eosViewRWMutex);\n    }\n\n    if (cid) {\n      try {\n        cmd = gOFS->eosDirectoryService->getContainerMD(cid, &clock);\n        path = gOFS->eosView->getUri(cmd.get());\n        pmd = gOFS->eosDirectoryService->getContainerMD(cmd->getParentId());\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        eos_static_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                         e.getMessage().str().c_str());\n        return grpc::Status((grpc::StatusCode)(errno), e.getMessage().str().c_str());\n      }\n    } else {\n      try {\n        cmd = gOFS->eosView->getContainer(request->id().path());\n        path = gOFS->eosView->getUri(cmd.get());\n        pmd = gOFS->eosDirectoryService->getContainerMD(cmd->getParentId());\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        eos_static_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                         e.getMessage().str().c_str());\n\n        if (request->type() != eos::rpc::STAT) {\n          if (errno == ENOENT) {\n            return grpc::Status::OK;\n          } else {\n            return grpc::Status((grpc::StatusCode)(errno), e.getMessage().str().c_str());\n          }\n        } else {\n          return grpc::Status((grpc::StatusCode)(errno), e.getMessage().str().c_str());\n        }\n      }\n    }\n\n    vid.scope = path;\n\n    if (request->type() == eos::rpc::STAT) {\n      // if a client sends a eos::rpc::STAT request, we check permissions on a folder\n      // itself and never on the parent (to enabe stat'ing CERNBOX shares)\n      access_self = true;\n    }\n\n    if (access_self) {\n      if (!Access(vid, X_OK, cmd)) {\n        return grpc::Status(grpc::StatusCode::PERMISSION_DENIED,\n                            \"access to container denied\");\n      }\n    } else {\n      if (!Access(vid, X_OK, pmd)) {\n        return grpc::Status(grpc::StatusCode::PERMISSION_DENIED,\n                            \"access to parent container denied\");\n      }\n    }\n\n    if (Filter(cmd, request->selection())) {\n      // short-cut for filtered MD\n      return grpc::Status::OK;\n    }\n\n    // create GRPC protobuf object\n    eos::rpc::MDResponse gRPCResponse;\n    gRPCResponse.set_type(eos::rpc::CONTAINER);\n    eos::rpc::ContainerMdProto gRPCFmd;\n    gRPCResponse.mutable_cmd()->set_name(cmd->getName());\n    gRPCResponse.mutable_cmd()->set_id(cmd->getId());\n    gRPCResponse.mutable_cmd()->set_inode(cmd->getId());\n    gRPCResponse.mutable_cmd()->set_parent_id(cmd->getParentId());\n    gRPCResponse.mutable_cmd()->set_uid(cmd->getCUid());\n    gRPCResponse.mutable_cmd()->set_gid(cmd->getCGid());\n    gRPCResponse.mutable_cmd()->set_tree_size(cmd->getTreeSize());\n    gRPCResponse.mutable_cmd()->set_flags(cmd->getFlags());\n    gRPCResponse.mutable_cmd()->set_mode(cmd->getMode());\n    gRPCResponse.mutable_cmd()->set_files(cmd->getNumFiles());\n    gRPCResponse.mutable_cmd()->set_containers(cmd->getNumContainers());\n    eos::IContainerMD::ctime_t ctime;\n    eos::IContainerMD::ctime_t mtime;\n    eos::IContainerMD::ctime_t stime;\n    cmd->getCTime(ctime);\n    cmd->getMTime(mtime);\n    cmd->getTMTime(stime);\n    gRPCResponse.mutable_cmd()->mutable_ctime()->set_sec(ctime.tv_sec);\n    gRPCResponse.mutable_cmd()->mutable_ctime()->set_n_sec(ctime.tv_nsec);\n    gRPCResponse.mutable_cmd()->mutable_mtime()->set_sec(mtime.tv_sec);\n    gRPCResponse.mutable_cmd()->mutable_mtime()->set_n_sec(mtime.tv_nsec);\n    gRPCResponse.mutable_cmd()->mutable_stime()->set_sec(stime.tv_sec);\n    gRPCResponse.mutable_cmd()->mutable_stime()->set_n_sec(stime.tv_nsec);\n    std::string etag;\n    eos::calculateEtag(cmd.get(), etag);\n    gRPCResponse.mutable_cmd()->set_etag(etag);\n\n    for (const auto& elem : cmd->getAttributes()) {\n      (*gRPCResponse.mutable_cmd()->mutable_xattrs())[elem.first] = elem.second;\n    }\n\n    gRPCResponse.mutable_cmd()->set_path(path);\n    writer->Write(gRPCResponse);\n    return grpc::Status::OK;\n  }\n\n  return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, \"invalid argument\");\n}\n\ngrpc::Status\nGrpcNsInterface::Stat(eos::common::VirtualIdentity& ivid,\n                      grpc::ServerWriter<eos::rpc::MDResponse>* writer,\n                      const eos::rpc::MDRequest* request)\n{\n  eos::common::VirtualIdentity vid = ivid;\n\n  if ((ivid.uid != request->role().uid()) ||\n      (ivid.gid != request->role().gid())) {\n    if (!ivid.sudoer) {\n      return grpc::Status(grpc::StatusCode::PERMISSION_DENIED,\n                          std::string(\"Ask an admin to map your auth key to a sudo'er account - permission denied\"));\n    } else {\n      vid = eos::common::Mapping::Someone(request->role().uid(),\n                                          request->role().gid());\n      vid.app = request->role().app();\n      vid.trace = request->role().trace();\n      vid.onbehalf = request->role().onbehalf();\n    }\n  }\n\n  return GetMD(vid, writer, request);\n}\n\ngrpc::Status\nGrpcNsInterface::StreamMD(eos::common::VirtualIdentity& ivid,\n                          grpc::ServerWriter<eos::rpc::MDResponse>* writer,\n                          const eos::rpc::MDRequest* request,\n                          bool streamparent,\n                          std::vector<uint64_t>* childdirs)\n{\n  eos::common::VirtualIdentity vid = ivid;\n\n  if (request->role().uid() || request->role().gid()) {\n    if ((ivid.uid != request->role().uid()) ||\n        (ivid.gid != request->role().gid())) {\n      if (!ivid.sudoer) {\n        return grpc::Status(grpc::StatusCode::PERMISSION_DENIED,\n                            std::string(\"Ask an admin to map your auth key to a sudo'er account - permission denied\"));\n      } else {\n        vid = eos::common::Mapping::Someone(request->role().uid(),\n                                            request->role().gid());\n        vid.app = request->role().app();\n        vid.trace = request->role().trace();\n        vid.onbehalf = request->role().onbehalf();\n      }\n    }\n  } else {\n    // we don't implement sudo to root\n  }\n\n  // stream container meta data\n  eos::common::RWMutexReadLock viewReadLock;\n  std::shared_ptr<eos::IContainerMD> cmd;\n  unsigned long cid = 0;\n  uint64_t clock = 0;\n  std::string path;\n\n  if (request->id().ino()) {\n    // get by inode\n    cid = request->id().ino();\n  } else if (request->id().id()) {\n    // get by containerid\n    cid = request->id().id();\n  }\n\n  if (!cid) {\n    eos::Prefetcher::prefetchContainerMDWithChildrenAndWait(gOFS->eosView,\n        request->id().path());\n  } else {\n    eos::Prefetcher::prefetchContainerMDWithChildrenAndWait(gOFS->eosView,\n        cid);\n  }\n\n  viewReadLock.Grab(gOFS->eosViewRWMutex);\n\n  if (cid) {\n    try {\n      cmd = gOFS->eosDirectoryService->getContainerMD(cid, &clock);\n      path = gOFS->eosView->getUri(cmd.get());\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      eos_static_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                       e.getMessage().str().c_str());\n      return grpc::Status((grpc::StatusCode)(errno), e.getMessage().str().c_str());\n    }\n  } else {\n    try {\n      cmd = gOFS->eosView->getContainer(request->id().path());\n      cid = cmd->getId();\n      path = gOFS->eosView->getUri(cmd.get());\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      eos_static_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                       e.getMessage().str().c_str());\n      return grpc::Status((grpc::StatusCode)(errno), e.getMessage().str().c_str());\n    }\n  }\n\n  vid.scope = path;\n\n  // check if we can read this directory\n  if (!Access(vid, R_OK, cmd)) {\n    return grpc::Status(grpc::StatusCode::PERMISSION_DENIED,\n                        std::string(\"access to read directory denied\"));\n  }\n\n  grpc::Status status;\n\n  if (streamparent && (request->type() != eos::rpc::FILE)) {\n    // stream the requested container\n    eos::rpc::MDRequest c_dir;\n    c_dir.mutable_selection()->CopyFrom(request->selection());\n    c_dir.mutable_id()->set_id(cid);\n    c_dir.set_type(eos::rpc::CONTAINER);\n    status = GetMD(vid, writer, &c_dir, true, false, true);\n\n    if (!status.ok()) {\n      return status;\n    }\n  }\n\n  bool first = true;\n  auto itf = eos::FileMapIterator(cmd);\n  auto itc = eos::ContainerMapIterator(cmd);\n  viewReadLock.Release();\n\n  // stream for listing and file type\n  if (request->type() != eos::rpc::CONTAINER) {\n    // stream all the children files\n    for (; itf.valid(); itf.next()) {\n      eos::rpc::MDRequest c_file;\n      c_file.mutable_selection()->CopyFrom(request->selection());\n      c_file.mutable_id()->set_id(itf.value());\n      c_file.set_type(eos::rpc::FILE);\n      status = GetMD(vid, writer, &c_file, first);\n\n      if (!status.ok()) {\n        return status;\n      }\n\n      first = false;\n    }\n  }\n\n  // stream all the children container\n  for (; itc.valid(); itc.next()) {\n    if (request->type() != eos::rpc::FILE) {\n      // stream for listing and container type\n      eos::rpc::MDRequest c_dir;\n      c_dir.mutable_id()->set_id(itc.value());\n      c_dir.mutable_selection()->CopyFrom(request->selection());\n      c_dir.set_type(eos::rpc::CONTAINER);\n      status = GetMD(vid, writer, &c_dir, first);\n\n      if (!status.ok()) {\n        return status;\n      }\n    }\n\n    if (childdirs) {\n      childdirs->push_back(itc.value());\n    }\n\n    first = false;\n  }\n\n  // finished streaming\n  return grpc::Status::OK;\n}\n\ngrpc::Status\nGrpcNsInterface::Find(eos::common::VirtualIdentity& ivid,\n                      grpc::ServerWriter<eos::rpc::MDResponse>* writer,\n                      const eos::rpc::FindRequest* request)\n{\n  eos::common::VirtualIdentity vid = ivid;\n\n  if (request->role().uid() || request->role().gid()) {\n    if ((ivid.uid != request->role().uid()) ||\n        (ivid.gid != request->role().gid())) {\n      if (!ivid.sudoer) {\n        return grpc::Status(grpc::StatusCode::PERMISSION_DENIED,\n                            std::string(\"Ask an admin to map your auth key to a sudo'er account - permission denied\"));\n      } else {\n        vid = eos::common::Mapping::Someone(request->role().uid(),\n                                            request->role().gid());\n        vid.app = request->role().app();\n        vid.trace = request->role().trace();\n        vid.onbehalf = request->role().onbehalf();\n      }\n    }\n  } else {\n    // we don't implement sudo to root\n  }\n\n  std::string path;\n  std::vector< std::vector<uint64_t> > found_dirs;\n  found_dirs.resize(1);\n  found_dirs[0].resize(1);\n  found_dirs[0][0] = 0;\n  uint64_t deepness = 0;\n\n  // find for a single directory\n  if (request->maxdepth() == 0) {\n    grpc::Status status = grpc::Status::OK;\n    eos::rpc::MDRequest c_dir;\n    *(c_dir.mutable_id()) = request->id();\n\n    if (request->type() != eos::rpc::FILE) {\n      c_dir.mutable_selection()->CopyFrom(request->selection());\n      c_dir.set_type(eos::rpc::CONTAINER);\n      status = GetMD(vid, writer, &c_dir, true, false, true);\n    }\n\n    return status;\n  }\n\n  // find for multiple directories/files\n\n  do {\n    bool streamparent = false;\n    found_dirs.resize(deepness + 2);\n\n    // loop over all directories in that deepness\n    for (unsigned int i = 0; i < found_dirs[deepness].size(); i++) {\n      uint64_t id = found_dirs[deepness][i];\n      eos::rpc::MDRequest lrequest;\n\n      if ((deepness == 0) && (id == 0)) {\n        // that is the root of a find\n        *(lrequest.mutable_id()) = request->id();\n        eos_static_warning(\"%s %llu %llu\", lrequest.id().path().c_str(),\n                           lrequest.id().id(),\n                           lrequest.id().ino());\n        streamparent = true;\n      } else {\n        lrequest.mutable_id()->set_id(id);\n        streamparent = false;\n      }\n\n      lrequest.set_type(request->type());\n      lrequest.mutable_selection()->CopyFrom(request->selection());\n      *(lrequest.mutable_role()) = request->role();\n      std::vector<uint64_t> children;\n      grpc::Status status = StreamMD(vid, writer, &lrequest, streamparent, &children);\n\n      if (!status.ok()) {\n        return status;\n      }\n\n      for (auto const& value : children) {\n        // stream dirs under path into cpath\n        found_dirs[deepness + 1].push_back(value);\n      }\n    }\n\n    deepness++;\n\n    if (deepness >= request->maxdepth()) {\n      break;\n    }\n  } while (found_dirs[deepness].size());\n\n  return grpc::Status::OK;\n}\n\nbool\nGrpcNsInterface::Access(eos::common::VirtualIdentity& vid, int mode,\n                        std::shared_ptr<eos::IContainerMD> cmd,\n                        eos::IFileMD::XAttrMap* attrmapF)\n{\n  // UNIX permissions\n  if (!vid.token && cmd->access(vid.uid, vid.gid, mode)) {\n    return true;\n  }\n\n  // ACLs - WARNING: this does not support ACLs to be linked attributes !\n  eos::IContainerMD::XAttrMap xattr = cmd->getAttributes();\n  eos::mgm::Acl acl;\n  acl.SetFromAttrMap(xattr, vid, attrmapF);\n\n  // check for immutable\n  if (vid.uid && !acl.IsMutable() && (mode & W_OK)) {\n    return false;\n  }\n\n  bool permok = false;\n\n  if (acl.HasAcl()) {\n    permok = true;\n\n    if ((mode & W_OK) && (!acl.CanWrite())) {\n      permok = false;\n    }\n\n    if (mode & R_OK) {\n      if (!acl.CanRead()) {\n        permok = false;\n      }\n    }\n\n    if (mode & X_OK) {\n      if ((!acl.CanBrowse())) {\n        permok = false;\n      }\n    }\n  }\n\n  return permok;\n}\n\ngrpc::Status\nGrpcNsInterface::NsStat(eos::common::VirtualIdentity& vid,\n                        eos::rpc::NsStatResponse* reply,\n                        const eos::rpc::NsStatRequest* request)\n{\n  if (!vid.sudoer) {\n    // Block everyone who is not a sudoer\n    reply->set_emsg(\"Not a sudoer, refusing to run command\");\n    reply->set_code(EPERM);\n    return grpc::Status::OK;\n  }\n\n  reply->set_state(namespaceStateToString(gOFS->mNamespaceState));\n  reply->set_nfiles(gOFS->eosFileService->getNumFiles());\n  reply->set_ncontainers(gOFS->eosDirectoryService->getNumContainers());\n  // Namespace must be booted to reach this code --> we can use TotalInitTime\n  reply->set_boot_time(gOFS->mTotalInitTime);\n  reply->set_current_fid(gOFS->eosFileService->getFirstFreeId());\n  reply->set_current_cid(gOFS->eosDirectoryService->getFirstFreeId());\n  int retc = 0;\n  std::ostringstream err;\n  eos::common::LinuxFds::linux_fds_t fds;\n  eos::common::LinuxStat::linux_stat_t pstat;\n  eos::common::LinuxMemConsumption::linux_mem_t mem;\n  // Lambda function to store error and return code\n  auto storeError = [&err, &retc](const std::string & msg) {\n    err << \"error: \" << msg << std::endl;\n    retc = errno;\n  };\n\n  if (!eos::common::LinuxMemConsumption::GetMemoryFootprint(mem)) {\n    storeError(\"failed to get memory usage information\");\n  }\n\n  if (!eos::common::LinuxStat::GetStat(pstat)) {\n    storeError(\"failed to get process stat information\");\n  }\n\n  if (!eos::common::LinuxFds::GetFdUsage(fds)) {\n    storeError(\"failed to get process fd information\");\n  }\n\n  reply->set_mem_virtual(mem.vmsize);\n  reply->set_mem_resident(mem.resident);\n  reply->set_mem_share(mem.share);\n  reply->set_mem_growth(pstat.vsize - gOFS->LinuxStatsStartup.vsize);\n  reply->set_threads(pstat.threads);\n  reply->set_fds(fds.all);\n  reply->set_uptime(time(NULL) - gOFS->mStartTime);\n  reply->set_emsg(err.str());\n  reply->set_code(retc);\n  return grpc::Status::OK;\n}\n\ngrpc::Status\nGrpcNsInterface::FileInsert(eos::common::VirtualIdentity& vid,\n                            eos::rpc::InsertReply* reply,\n                            const eos::rpc::FileInsertRequest* request)\n{\n  if (!vid.sudoer) {\n    // block every one who is not a sudoer\n    reply->add_message(\"Not a sudoer, refusing to run command\");\n    reply->add_retc(EPERM);\n    return grpc::Status::OK;\n  }\n\n  std::shared_ptr<eos::IFileMD> newfile;\n  eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n  std::vector<folly::Future<eos::IFileMDPtr>> conflicts;\n\n  for (auto it : request->files()) {\n    if (it.id() <= 0) {\n      conflicts.emplace_back(eos::IFileMDPtr(\n                               nullptr)); // folly::makeFuture<eos::IFileMDPtr>(eos::IFileMDPtr(nullptr)));\n    } else {\n      conflicts.emplace_back(gOFS->eosFileService->getFileMDFut(it.id()));\n    }\n  }\n\n  int counter = -1;\n\n  for (auto it : request->files()) {\n    counter++;\n    conflicts[counter].wait();\n\n    if (!conflicts[counter].hasException() &&\n        std::move(conflicts[counter]).get() != nullptr) {\n      std::ostringstream ss;\n      ss << \"Attempted to create file with id=\" << it.id() <<\n         \", which already exists\";\n      eos_static_err(\"%s\", ss.str().c_str());\n      reply->add_message(ss.str());\n      reply->add_retc(EINVAL);\n      continue;\n    }\n\n    eos_static_info(\"creating path=%s id=%lx\", it.path().c_str(), it.id());\n\n    try {\n      try {\n        newfile = gOFS->eosView->createFile(it.path(), it.uid(), it.gid(), it.id());\n      } catch (eos::MDException& e) {\n        std::ostringstream msg;\n        msg << \"Failed to call gOFS->eosView->createFile(): \" << e.getMessage().str();\n        e.getMessage().str(msg.str());\n        throw;\n      }\n\n      eos::IFileMD::ctime_t ctime;\n      eos::IFileMD::ctime_t mtime;\n      ctime.tv_sec  = it.ctime().sec();\n      ctime.tv_nsec = it.ctime().n_sec();\n      mtime.tv_sec  = it.mtime().sec();\n      mtime.tv_nsec = it.mtime().n_sec();\n      newfile->setFlags(it.flags());\n      newfile->setCTime(ctime);\n      newfile->setMTime(mtime);\n      newfile->setCUid(it.uid());\n      newfile->setCGid(it.gid());\n      newfile->setLayoutId(it.layout_id());\n      newfile->setSize(it.size());\n      newfile->setChecksum(it.checksum().value().c_str(),\n                           it.checksum().value().size());\n\n      for (auto attrit : it.xattrs()) {\n        newfile->setAttribute(attrit.first, attrit.second);\n      }\n\n      for (auto locit : it.locations()) {\n        newfile->addLocation(locit);\n      }\n\n      try {\n        gOFS->eosView->updateFileStore(newfile.get());\n      } catch (eos::MDException& e) {\n        std::ostringstream msg;\n        msg << \"Failed to call gOFS->eosView->updateFileStore(): \" <<\n            e.getMessage().str();\n        e.getMessage().str(msg.str());\n        throw;\n      }\n\n      reply->add_message(\"\");\n      reply->add_retc(0);\n    } catch (eos::MDException& e) {\n      eos_static_err(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\" path=\\\"%s\\\" fxid=%08llx\\n\",\n                     e.getErrno(), e.getMessage().str().c_str(), it.path().c_str(), it.id());\n      reply->add_message(SSTR(\"Failed to insert fid=\" << it.id() << \", errno=\" <<\n                              e.getErrno() << \", path=\" << it.path() << \": \" << e.getMessage().str()));\n      reply->add_retc(-1);\n    }\n  }\n\n  return grpc::Status::OK;\n}\n\n\ngrpc::Status\nGrpcNsInterface::ContainerInsert(eos::common::VirtualIdentity& vid,\n                                 eos::rpc::InsertReply* reply,\n                                 const eos::rpc::ContainerInsertRequest* request)\n{\n  if (!vid.sudoer) {\n    // block every one who is not a sudoer\n    reply->add_message(\"Not a sudoer, refusing to run command\");\n    reply->add_retc(EPERM);\n    return grpc::Status::OK;\n  }\n\n  std::shared_ptr<eos::IContainerMD> newdir;\n  eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n  std::vector<folly::Future<eos::IContainerMDPtr>> conflicts;\n\n  for (auto it : request->container()) {\n    if (it.id() <= 0) {\n      conflicts.emplace_back(eos::IContainerMDPtr(nullptr));\n    } else {\n      conflicts.emplace_back(gOFS->eosDirectoryService->getContainerMDFut(it.id()));\n    }\n  }\n\n  int counter = -1;\n  bool inherit = request->inherit_md();\n\n  for (auto it : request->container()) {\n    counter++;\n    conflicts[counter].wait();\n\n    if (!conflicts[counter].hasException() &&\n        std::move(conflicts[counter]).get() != nullptr) {\n      std::ostringstream ss;\n      ss << \"Attempted to create container with id=\" << it.id() <<\n         \", which already exists\";\n      eos_static_err(\"%s\", ss.str().c_str());\n      reply->add_message(ss.str());\n      reply->add_retc(EINVAL);\n      continue;\n    }\n\n    eos_static_info(\"creating path=%s id=%lx inherit_md=%d\",\n                    it.path().c_str(), it.id(), inherit);\n\n    try {\n      try {\n        newdir = gOFS->eosView->createContainer(it.path(), false, it.id());\n      } catch (eos::MDException& e) {\n        std::ostringstream msg;\n        msg << \"Failed to call gOFS->eosView->createContainer(): \" <<\n            e.getMessage().str();\n        e.getMessage().str(msg.str());\n        throw;\n      }\n\n      eos::IContainerMD::ctime_t ctime;\n      eos::IContainerMD::ctime_t mtime;\n      eos::IContainerMD::ctime_t stime;\n      ctime.tv_sec  = it.ctime().sec();\n      ctime.tv_nsec = it.ctime().n_sec();\n      mtime.tv_sec  = it.mtime().sec();\n      mtime.tv_nsec = it.mtime().n_sec();\n      stime.tv_sec  = it.stime().sec();\n      stime.tv_nsec = it.stime().n_sec();\n      // we can send flags or mode to store in flags ... sigh\n      newdir->setFlags(it.flags());\n      newdir->setCTime(ctime);\n      newdir->setMTime(mtime);\n      newdir->setTMTime(stime);\n      newdir->setCUid(it.uid());\n      newdir->setCGid(it.gid());\n      newdir->setMode(it.mode() | S_IFDIR);\n      std::shared_ptr<eos::IContainerMD> parent;\n\n      if (inherit) {\n        eos::common::Path cPath(it.path());\n\n        try {\n          parent = gOFS->eosView->getContainer(cPath.GetParentPath());\n        } catch (eos::MDException& e) {\n          std::ostringstream msg;\n          msg << \"Failed to call parent gOFS->eosView->getContainer(): \" <<\n              e.getMessage().str();\n          e.getMessage().str(msg.str());\n          throw;\n        }\n\n        if (it.mode() == 0) {\n          newdir->setMode(parent->getMode());\n        }\n\n        for (const auto& attrit : parent->getAttributes()) {\n          newdir->setAttribute(attrit.first, attrit.second);\n        }\n      }\n\n      struct timespec now;\n\n      eos::common::Timing::GetTimeSpec(now);\n\n      newdir->setAttribute(\"sys.eos.btime\",\n                           SSTR(now.tv_sec << \".\" << now.tv_nsec));\n\n      for (auto attrit : it.xattrs()) {\n        newdir->setAttribute(attrit.first, attrit.second);\n      }\n\n      try {\n        gOFS->eosView->updateContainerStore(newdir.get());\n\n        if (parent) {\n          parent->setMTime(ctime);\n          parent->notifyMTimeChange(gOFS->eosDirectoryService);\n          gOFS->eosView->updateContainerStore(parent.get());\n        }\n      } catch (eos::MDException& e) {\n        std::ostringstream msg;\n        msg << \"Failed to call gOFS->eosView->updateContainerStore(): \" <<\n            e.getMessage().str();\n        e.getMessage().str(msg.str());\n        throw;\n      }\n\n      reply->add_message(\"\");\n      reply->add_retc(0);\n    } catch (eos::MDException& e) {\n      eos_static_err(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\" path=\\\"%s\\\" fxid=%08llx\\n\",\n                     e.getErrno(), e.getMessage().str().c_str(), it.path().c_str(), it.id());\n      reply->add_message(SSTR(\"Failed to insert cid=\" << it.id() << \", errno=\" <<\n                              e.getErrno() << \", path=\" << it.path() << \": \" << e.getMessage().str()));\n      reply->add_retc(e.getErrno());\n    }\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status\nGrpcNsInterface::Exec(eos::common::VirtualIdentity& ivid,\n                      eos::rpc::NSResponse* reply,\n                      const eos::rpc::NSRequest* request)\n{\n  eos::common::VirtualIdentity vid = ivid;\n\n  if (request->role().uid() || request->role().gid()) {\n    if ((ivid.uid != request->role().uid()) ||\n        (ivid.gid != request->role().gid())) {\n      if (!ivid.sudoer) {\n        reply->mutable_error()->set_code(EPERM);\n        reply->mutable_error()->set_msg(\n          \"Ask an admin to map your auth key to a sudo'er account - permission denied\");\n        return grpc::Status::OK;\n      } else {\n        vid = eos::common::Mapping::Someone(request->role().uid(),\n                                            request->role().gid());\n        vid.app = request->role().app();\n        vid.trace = request->role().trace();\n        vid.onbehalf = request->role().onbehalf();\n      }\n    }\n  } else {\n    // we don't implement sudo to root\n  }\n\n  switch (request->command_case()) {\n  case eos::rpc::NSRequest::kMkdir:\n    return Mkdir(vid, reply->mutable_error(), &(request->mkdir()));\n    break;\n\n  case eos::rpc::NSRequest::kRmdir:\n    return Rmdir(vid, reply->mutable_error(), &(request->rmdir()));\n    break;\n\n  case eos::rpc::NSRequest::kTouch:\n    return Touch(vid, reply->mutable_error(), &(request->touch()));\n    break;\n\n  case eos::rpc::NSRequest::kUnlink:\n    return Unlink(vid, reply->mutable_error(), &(request->unlink()));\n    break;\n\n  case eos::rpc::NSRequest::kRm:\n    return Rm(vid, reply->mutable_error(), &(request->rm()));\n    break;\n\n  case eos::rpc::NSRequest::kRename:\n    return Rename(vid, reply->mutable_error(), &(request->rename()));\n    break;\n\n  case eos::rpc::NSRequest::kSymlink:\n    return Symlink(vid, reply->mutable_error(), &(request->symlink()));\n    break;\n\n  case eos::rpc::NSRequest::kXattr:\n    return SetXAttr(vid, reply->mutable_error(), &(request->xattr()));\n    break;\n\n  case eos::rpc::NSRequest::kVersion:\n    return Version(vid, reply->mutable_version(), &(request->version()));\n    break;\n\n  case eos::rpc::NSRequest::kOldRecycle:\n    return OldRecycle(vid, reply->mutable_recycle(), &(request->old_recycle()));\n    break;\n\n  case eos::rpc::NSRequest::kRecycle:\n    return Recycle(vid, reply->mutable_recycle(), &(request->recycle()));\n    break;\n\n  case eos::rpc::NSRequest::kChown:\n    return Chown(vid, reply->mutable_error(), &(request->chown()));\n    break;\n\n  case eos::rpc::NSRequest::kChmod:\n    return Chmod(vid, reply->mutable_error(), &(request->chmod()));\n    break;\n\n  case eos::rpc::NSRequest::kAcl:\n    return Acl(vid, reply->mutable_acl(), &(request->acl()));\n    break;\n\n  case eos::rpc::NSRequest::kToken:\n    return Token(vid, reply->mutable_error(), &(request->token()));\n    break;\n\n  case eos::rpc::NSRequest::kQuota:\n    return Quota(vid, reply->mutable_quota(), &(request->quota()));\n    break;\n\n  default:\n    reply->mutable_error()->set_code(EINVAL);\n    reply->mutable_error()->set_msg(\"error: command not supported\");\n    break;\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status\nGrpcNsInterface::Mkdir(eos::common::VirtualIdentity& vid,\n                       eos::rpc::NSResponse::ErrorResponse* reply,\n                       const eos::rpc::NSRequest::MkdirRequest* request)\n{\n  mode_t mode = request->mode();\n\n  if (request->recursive()) {\n    mode |= SFS_O_MKPTH;\n  }\n\n  std::string path;\n  path = request->id().path();\n  vid.scope = path;\n\n  if (path.empty()) {\n    reply->set_code(EINVAL);\n    reply->set_msg(\"error:path is empty\");\n    return grpc::Status::OK;\n  }\n\n  XrdOucErrInfo error;\n  errno = 0;\n\n  if (gOFS->_mkdir(path.c_str(), mode, error, vid, (const char*) 0)) {\n    reply->set_code(errno);\n    reply->set_msg(error.getErrText());\n    return grpc::Status::OK;\n  }\n\n  XrdSfsMode sfsmode = mode;\n\n  // the mkdir command always inherits the parent mode setting and ignores the mode parameter\n  if (gOFS->_chmod(path.c_str(),\n                   sfsmode,\n                   error, vid, (const char*) 0)) {\n    reply->set_code(errno);\n    reply->set_msg(error.getErrText());\n    return grpc::Status::OK;\n  }\n\n  if (errno == EEXIST) {\n    reply->set_code(EEXIST);\n    std::string msg = \"info: directory existed already '\";\n    msg += path.c_str();\n    msg += \"'\";\n    reply->set_msg(msg);\n  } else {\n    reply->set_code(0);\n    std::string msg = \"info: created directory '\";\n    msg += path.c_str();\n    msg += \"'\";\n    reply->set_msg(msg);\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcNsInterface::Rmdir(eos::common::VirtualIdentity& vid,\n                                    eos::rpc::NSResponse::ErrorResponse* reply,\n                                    const eos::rpc::NSRequest::RmdirRequest* request)\n{\n  std::string path;\n  path = request->id().path();\n  vid.scope = path;\n\n  if (path.empty()) {\n    try {\n      eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n      path =\n        gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(\n                                request->id().id()).get());\n    } catch (eos::MDException& e) {\n      path = \"\";\n      errno = e.getErrno();\n    }\n  }\n\n  if (path.empty()) {\n    if (request->id().id()) {\n      reply->set_code(ENOENT);\n      reply->set_msg(\"error: directory id does not exist\");\n      return grpc::Status::OK;\n    } else {\n      reply->set_code(EINVAL);\n      reply->set_msg(\"error: path is empty\");\n      return grpc::Status::OK;\n    }\n  }\n\n  XrdOucErrInfo error;\n  errno = 0;\n\n  if (gOFS->_remdir(path.c_str(), error, vid, (const char*) 0)) {\n    reply->set_code(errno);\n    reply->set_msg(error.getErrText());\n    return grpc::Status::OK;\n  }\n\n  reply->set_code(0);\n  std::string msg = \"info: deleted directory '\";\n  msg += path.c_str();\n  msg += \"'\";\n  reply->set_msg(msg);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcNsInterface::Touch(eos::common::VirtualIdentity& vid,\n                                    eos::rpc::NSResponse::ErrorResponse* reply,\n                                    const eos::rpc::NSRequest::TouchRequest* request)\n{\n  std::string path;\n  path = request->id().path();\n  vid.scope = path;\n\n  if (path.empty()) {\n    reply->set_code(EINVAL);\n    reply->set_msg(\"error:path is empty\");\n    return grpc::Status::OK;\n  }\n\n  XrdOucErrInfo error;\n  errno = 0;\n\n  if (gOFS->_touch(path.c_str(), error, vid, (const char*) 0)) {\n    reply->set_code(errno);\n    reply->set_msg(error.getErrText());\n    return grpc::Status::OK;\n  }\n\n  reply->set_code(0);\n  std::string msg = \"info: touched file '\";\n  msg += path.c_str();\n  msg += \"'\";\n  reply->set_msg(msg);\n  return grpc::Status::OK;\n}\n\n\ngrpc::Status GrpcNsInterface::Unlink(eos::common::VirtualIdentity& vid,\n                                     eos::rpc::NSResponse::ErrorResponse* reply,\n                                     const eos::rpc::NSRequest::UnlinkRequest* request)\n{\n  bool norecycle = false;\n\n  if (request->norecycle()) {\n    norecycle = true;\n  }\n\n  std::string path;\n  path = request->id().path();\n  vid.scope = path;\n\n  if (path.empty()) {\n    try {\n      eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n      path =\n        gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(\n                                request->id().id()).get());\n    } catch (eos::MDException& e) {\n      path = \"\";\n      errno = e.getErrno();\n    }\n  }\n\n  if (path.empty()) {\n    if (request->id().id()) {\n      reply->set_code(ENOENT);\n      reply->set_msg(\"error: directory id does not exist\");\n      return grpc::Status::OK;\n    } else {\n      reply->set_code(EINVAL);\n      reply->set_msg(\"error: path is empty\");\n      return grpc::Status::OK;\n    }\n  }\n\n  XrdOucErrInfo error;\n  errno = 0;\n\n  if (gOFS->_rem(path.c_str(), error, vid, (const char*) 0, false, false,\n                 norecycle)) {\n    reply->set_code(errno);\n    reply->set_msg(error.getErrText());\n    return grpc::Status::OK;\n  }\n\n  reply->set_code(0);\n  std::string msg = \"info: unlinked file '\";\n  msg += path.c_str();\n  msg += \"'\";\n  reply->set_msg(msg);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcNsInterface::Rm(eos::common::VirtualIdentity& vid,\n                                 eos::rpc::NSResponse::ErrorResponse* reply,\n                                 const eos::rpc::NSRequest::RmRequest* request)\n{\n  eos::console::RequestProto req;\n\n  if (!request->id().path().empty()) {\n    req.mutable_rm()->set_path(request->id().path());\n  } else {\n    if (request->id().type() == eos::rpc::FILE) {\n      req.mutable_rm()->set_fileid(request->id().id());\n    } else {\n      req.mutable_rm()->set_containerid(request->id().id());\n    }\n  }\n\n  if (request->recursive()) {\n    req.mutable_rm()->set_recursive(true);\n  }\n\n  if (request->norecycle()) {\n    req.mutable_rm()->set_bypassrecycle(true);\n  }\n\n  eos::mgm::RmCmd rmcmd(std::move(req), vid);\n  eos::console::ReplyProto preply = rmcmd.ProcessRequest();\n\n  if (preply.retc()) {\n    reply->set_code(preply.retc());\n    reply->set_msg(preply.std_err());\n    return grpc::Status::OK;\n  }\n\n  reply->set_code(0);\n  std::string msg = \"info: \";\n  msg += \"deleted directory tree '\";\n\n  if (!request->id().path().empty()) {\n    msg += request->id().path().c_str();\n  } else {\n    std::stringstream s;\n    s << std::hex << request->id().id();\n    msg += s.str().c_str();\n  }\n\n  reply->set_msg(msg);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcNsInterface::Rename(eos::common::VirtualIdentity& vid,\n                                     eos::rpc::NSResponse::ErrorResponse* reply,\n                                     const eos::rpc::NSRequest::RenameRequest* request)\n{\n  std::string path;\n  std::string target;\n  path = request->id().path();\n  target = request->target();\n  vid.scope = path;\n\n  if (path.empty()) {\n    reply->set_code(EINVAL);\n    reply->set_msg(\"error:path is empty\");\n    return grpc::Status::OK;\n  }\n\n  if (target.empty()) {\n    reply->set_code(EINVAL);\n    reply->set_msg(\"error:target is empty\");\n    return grpc::Status::OK;\n  }\n\n  XrdOucErrInfo error;\n  errno = 0;\n\n  if (gOFS->rename(path.c_str(), target.c_str(), error, vid)) {\n    reply->set_code(errno);\n    reply->set_msg(error.getErrText());\n    return grpc::Status::OK;\n  }\n\n  reply->set_code(0);\n  std::string msg = \"info: renamed '\";\n  msg += path.c_str();\n  msg += \"' to '\";\n  msg += target.c_str();\n  msg += \"'\";\n  reply->set_msg(msg);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcNsInterface::Symlink(eos::common::VirtualIdentity& vid,\n                                      eos::rpc::NSResponse::ErrorResponse* reply,\n                                      const eos::rpc::NSRequest::SymlinkRequest* request)\n{\n  std::string path;\n  std::string target;\n  path = request->id().path();\n  target = request->target();\n  vid.scope = path;\n\n  if (path.empty()) {\n    reply->set_code(EINVAL);\n    reply->set_msg(\"error:path is empty\");\n    return grpc::Status::OK;\n  }\n\n  if (target.empty()) {\n    reply->set_code(EINVAL);\n    reply->set_msg(\"error:target is empty\");\n    return grpc::Status::OK;\n  }\n\n  XrdOucErrInfo error;\n  errno = 0;\n\n  if (gOFS->_symlink(\n        path.c_str(),\n        target.c_str(),\n        error,\n        vid)) {\n    reply->set_code(errno);\n    reply->set_msg(error.getErrText());\n    return grpc::Status::OK;\n  }\n\n  reply->set_code(0);\n  std::string msg = \"info: symlinked '\";\n  msg += path.c_str();\n  msg += \"' to '\";\n  msg += target.c_str();\n  msg += \"'\";\n  reply->set_msg(msg);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcNsInterface::SetXAttr(eos::common::VirtualIdentity& vid,\n                                       eos::rpc::NSResponse::ErrorResponse* reply,\n                                       const eos::rpc::NSRequest::SetXAttrRequest* request)\n{\n  std::string path;\n  path = request->id().path();\n\n  if (path.empty()) {\n    if (request->id().type() == eos::rpc::FILE) {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path =\n          gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(\n                                  request->id().id()).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    } else {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path =\n          gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(\n                                  request->id().id()).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    }\n\n    if (path.empty()) {\n      reply->set_code(EINVAL);\n      reply->set_msg(\"error:path is empty\");\n      return grpc::Status::OK;\n    }\n  }\n\n  XrdOucErrInfo error;\n  std::set<std::string> lst_dirs;\n\n  if (request->recursive()) {\n    // Collect all the directories in the current subtree\n    XrdOucString err_msg;\n    std::map<std::string, std::set<std::string>> found;\n    bool no_files = true; // search only for directories\n    eos_static_debug(\"msg=\\\"collect subdirs for recursive setxattr\\\" \"\n                     \"path=\\\"%s\\\"\", path.c_str());\n\n    if (gOFS->_find(path.c_str(), error, err_msg, vid, found, nullptr, nullptr,\n                    no_files)) {\n      reply->set_code((errno ? errno : EINVAL));\n      reply->set_msg(error.getErrText());\n      return grpc::Status::OK;\n    }\n\n    for (const auto& pair : found) {\n      lst_dirs.insert(pair.first);\n    }\n  } else {\n    lst_dirs.insert(path);\n  }\n\n  errno = 0;\n\n  // setting keys\n  for (auto it = request->xattrs().begin(); it != request->xattrs().end(); ++it) {\n    std::string key = it->first;\n    std::string value = it->second;\n    std::string b64value;\n    eos::common::SymKey::Base64(value, b64value);\n\n    for (const auto& spath : lst_dirs) {\n      vid.scope = spath.c_str();\n\n      if (gOFS->_attr_set(spath.c_str(), error, vid, (const char*) 0, key.c_str(),\n                          b64value.c_str(), request->create())) {\n        reply->set_code(errno);\n        reply->set_msg(error.getErrText());\n        return grpc::Status::OK;\n      }\n    }\n  }\n\n  // deleting keys\n  for (auto i = 0; i < request->keystodelete().size(); ++i) {\n    for (const auto& spath : lst_dirs) {\n      vid.scope = spath.c_str();\n\n      if (gOFS->_attr_rem(spath.c_str(), error, vid, (const char*) 0,\n                          request->keystodelete()[i].c_str())) {\n        reply->set_code(errno);\n        reply->set_msg(error.getErrText());\n        return grpc::Status::OK;\n      }\n    }\n  }\n\n  reply->set_code(0);\n  std::ostringstream oss;\n  oss << \"info: setxattr \"\n      << (request->recursive() ? \"recursively\" : \"\")\n      << \" on '\" << path << \"'\";\n  reply->set_msg(oss.str());\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcNsInterface::Version(eos::common::VirtualIdentity& vid,\n                                      eos::rpc::NSResponse::VersionResponse* reply,\n                                      const eos::rpc::NSRequest::VersionRequest* request)\n{\n  /*\n  message VersionRequest {\n    enum VERSION_CMD {\n      CREATE= 0;\n      PURGE = 1;\n      LIST = 2;\n      GRAB = 3;\n    }\n    MDId id = 1;\n    VERSION_CMD cmd = 2;\n    int maxversion = 3;\n  }\n  */\n  std::string path;\n  uint64_t fid = 0;\n  path = request->id().path();\n\n  if (path.empty()) {\n    if (request->id().ino()) {\n      // get by inode\n      fid = eos::common::FileId::InodeToFid(request->id().ino());\n    } else if (request->id().id()) {\n      // get by fileid\n      fid = request->id().id();\n    }\n\n    try {\n      eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n      path =\n        gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(fid).get());\n    } catch (eos::MDException& e) {\n      path = \"\";\n      errno = e.getErrno();\n    }\n\n    if (path.empty()) {\n      reply->set_code(EINVAL);\n      reply->set_msg(\"error:path is empty\");\n      return grpc::Status::OK;\n    }\n  }\n\n  std::string vpath;\n  eos::common::Path cPath(path.c_str());\n  vpath += cPath.GetParentPath();\n  vpath += EOS_COMMON_PATH_VERSION_PREFIX;\n  vpath += cPath.GetName();\n  vpath += \"/\";\n\n  if (request->cmd() == eos::rpc::NSRequest::VersionRequest::CREATE) {\n    // create a new version\n    ProcCommand cmd;\n    XrdOucErrInfo error;\n    XrdOucString info = \"mgm.cmd=file&mgm.subcmd=version&mgm.purge.version=\";\n    info += std::to_string(request->maxversion()).c_str();\n\n    if (fid) {\n      info += \"&mgm.file.id=\";\n      info += std::to_string(fid).c_str();\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      info += \"&mgm.path=\";\n      info += path.c_str();\n    }\n\n    cmd.open(\"/proc/user\", info.c_str(), vid, &error);\n    cmd.close();\n    int rc = cmd.GetRetc();\n\n    if (rc) {\n      std::string msg = \"Creation failed: \";\n      msg += cmd.GetStdErr();\n      reply->set_code((rc > 0) ? -rc : rc);\n      reply->set_msg(msg);\n      return grpc::Status::OK;\n    } else {\n      std::string msg = \"info: created new version for path='\";\n      msg += path;\n      msg += \"'\";\n      reply->set_msg(msg);\n      return grpc::Status::OK;\n    }\n  } else {\n    if (request->cmd() == eos::rpc::NSRequest::VersionRequest::PURGE) {\n      // purge versions\n      XrdOucErrInfo error;\n      int rc = gOFS->PurgeVersion(vpath.c_str(),\n                                  error,\n                                  request->maxversion());\n\n      if (rc) {\n        reply->set_code(errno);\n        reply->set_msg(error.getErrText());\n      } else {\n        reply->set_code(0);\n        std::string msg;\n        msg = \"info: purged versions of path='\";\n        msg += path;\n        msg += \"' to maxversion=\";\n        msg += std::to_string(request->maxversion());\n        reply->set_msg(msg);\n      }\n    } else {\n      if (request->cmd() == eos::rpc::NSRequest::VersionRequest::LIST) {\n        // list versions\n        XrdMgmOfsDirectory directory;\n        int listrc = directory.open(vpath.c_str(), vid, (const char*) 0);\n\n        if (!listrc) {\n          const char* val = 0;\n\n          while ((val = directory.nextEntry())) {\n            std::string entryname = val;\n\n            if ((entryname == \".\") || (entryname == \"..\")) {\n              continue;\n            }\n\n            eos::rpc::NSResponse::VersionResponse::VersionInfo info;\n            std::string smtime, sfid;\n            eos::common::StringConversion::SplitKeyValue(entryname, smtime, sfid, \".\");\n            uint64_t mtime = strtoull(smtime.c_str(), 0, 10);\n            uint64_t fid = strtoull(sfid.c_str(), 0, 16);\n            uint64_t inode = eos::common::FileId::FidToInode(fid);\n            std::string fullpath = vpath + \"/\";\n            fullpath += entryname;\n            info.mutable_mtime()->set_sec(mtime);\n            info.mutable_id()->set_id(fid);\n            info.mutable_id()->set_ino(inode);\n            info.mutable_id()->set_path(fullpath);\n            info.mutable_id()->set_type(eos::rpc::FILE);\n            auto new_version = reply->add_versions();\n            new_version->CopyFrom(info);\n          }\n        }\n      } else {\n        if (request->cmd() == eos::rpc::NSRequest::VersionRequest::GRAB) {\n          // grab a given version\n          XrdOucErrInfo error;\n          struct stat buf;\n          struct stat vbuf;\n\n          if (gOFS->_stat(path.c_str(), &buf, error, vid, \"\")) {\n            std::string msg;\n            msg = \"error; unable to stat path=\";\n            msg += path.c_str();\n            reply->set_code(errno);\n            reply->set_msg(msg);\n            return grpc::Status::OK;\n          }\n\n          XrdOucString versionname = request->grabversion().c_str();\n\n          if (!versionname.length()) {\n            std::string msg = \"error: you have to provide the version you want to stage!\";\n            reply->set_code(EINVAL);\n            reply->set_msg(msg);\n            return grpc::Status::OK;\n          }\n\n          XrdOucString versionpath = cPath.GetVersionDirectory();\n          versionpath += versionname;\n\n          if (gOFS->_stat(versionpath.c_str(), &vbuf, error, vid, \"\")) {\n            std::string msg;\n            msg = \"error: failed to stat your provided version path='\";\n            msg += versionpath.c_str();\n            msg += \"'\";\n            reply->set_code(errno);\n            reply->set_msg(msg);\n            return grpc::Status::OK;\n          }\n\n          // now stage a new version of the existing file\n          XrdOucString versionedpath;\n\n          if (gOFS->Version(eos::common::FileId::InodeToFid(buf.st_ino), error,\n                            vid, -1, &versionedpath)) {\n            std::string msg;\n            msg += \"error: unable to create a version of path=\";\n            msg += path.c_str();\n            msg += \"\\n\";\n            msg += \"error: \";\n            msg += error.getErrText();\n            reply->set_code(error.getErrInfo());\n            reply->set_msg(msg);\n            return grpc::Status::OK;\n          }\n\n          // and stage back the desired version\n          if (gOFS->rename(versionpath.c_str(), path.c_str(), error, vid)) {\n            std::string msg;\n            msg += \"error: unable to stage\";\n            msg += \" '\";\n            msg += versionpath.c_str();\n            msg += \"' back to '\";\n            msg += path.c_str();\n            msg += \"'\";\n            reply->set_code(errno);\n            reply->set_msg(msg);\n            return grpc::Status::OK;\n          } else {\n            std::string msg;\n            msg += \"success: staged '\";\n            msg += versionpath.c_str();\n            msg += \"' back to '\";\n            msg += path.c_str();\n            msg += \"'\";\n            msg += \" - the previous file is now '\";\n            msg += versionedpath.c_str();\n            msg += \";\";\n            reply->set_code(0);\n            reply->set_msg(msg);\n          }\n        } else {\n          reply->set_code(EINVAL);\n          reply->set_msg(\"error: command is not supported\");\n        }\n      }\n    }\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcNsInterface::OldRecycle(eos::common::VirtualIdentity& vid,\n    eos::rpc::NSResponse::RecycleResponse* reply,\n    const eos::rpc::NSRequest::RecycleRequest* request)\n{\n  if (request->cmd() == eos::rpc::NSRequest::RecycleRequest::RESTORE) {\n    if (!request->key().length()) {\n      reply->set_code(EINVAL);\n      reply->set_msg(\"error: you need to define the recycle key in the request\");\n      return grpc::Status::OK;\n    }\n\n    std::string std_out, std_err;\n    eos_static_info(\"restore: key=% flags=%d:%d:%d\",\n                    request->key().c_str(),\n                    request->restoreflag().force(),\n                    request->restoreflag().versions(),\n                    request->restoreflag().mkpath());\n    int retc = Recycle::Restore(std_out, std_err, vid, request->key(),\n                                request->restoreflag().force(),\n                                request->restoreflag().versions(),\n                                request->restoreflag().mkpath());\n\n    if (retc) {\n      reply->set_code(retc);\n      reply->set_msg(std_err.c_str());\n    } else {\n      reply->set_msg(std_out.c_str());\n    }\n\n    // check restore flags\n    return grpc::Status::OK;\n  } else if (request->cmd() == eos::rpc::NSRequest::RecycleRequest::PURGE) {\n    std::string std_out, std_err;\n    std::string date;\n\n    if (request->purgedate().year()) {\n      date += std::to_string(request->purgedate().year());\n\n      if (request->purgedate().month()) {\n        char smonth[1024];\n        snprintf(smonth, sizeof(smonth), \"/%02u\", request->purgedate().month());\n        date += smonth;\n\n        if (request->purgedate().day()) {\n          char sday[1024];\n          snprintf(sday, sizeof(sday), \"/%02u\", request->purgedate().day());\n          date += sday;\n        }\n      }\n    }\n\n    eos_static_info(\"purge: date=%s\", date.c_str());\n    // we need a sudoer flag to purge a recycle bin via grpc\n    vid.sudoer = true;\n    int retc = Recycle::Purge(std_out, std_err, vid, request->key(), date);\n\n    if (retc) {\n      reply->set_code(retc);\n      reply->set_msg(std_err.c_str());\n    } else {\n      reply->set_msg(std_out.c_str());\n    }\n\n    return grpc::Status::OK;\n  } else if (request->cmd() == eos::rpc::NSRequest::RecycleRequest::LIST) {\n    std::string std_out, std_err;\n    Recycle::RecycleListing rvec;\n    std::string date;\n\n    if (request->listflag().year()) {\n      date += std::to_string(request->listflag().year());\n\n      if (request->listflag().month()) {\n        char smonth[1024];\n        snprintf(smonth, sizeof(smonth), \"/%02u\", request->listflag().month());\n        date += smonth;\n\n        if (request->listflag().day()) {\n          char sday[1024];\n          snprintf(sday, sizeof(sday), \"/%02u\", request->listflag().day());\n          date += sday;\n\n          if (request->listflag().index()) {\n            date += \"/\";\n            date += std::to_string(request->listflag().index());\n          }\n        }\n      }\n    }\n\n    std::string display_type = \"uid\";\n\n    if (request->listflag().display() == eos::rpc::NSRequest_RecycleRequest::ALL) {\n      display_type = \"all\";\n    } else if (request->listflag().display() ==\n               eos::rpc::NSRequest_RecycleRequest::RID) {\n      display_type = \"rid\";\n    }\n\n    int rc = Recycle::Print(std_out, std_err, vid, true, true, true,\n                            display_type,\n                            request->listflag().display_val(),\n                            date, &rvec, true,\n                            request->listflag().maxentries());\n\n    for (auto item : rvec) {\n      eos::rpc::NSResponse::RecycleResponse::RecycleInfo info;\n\n      if (item[\"type\"] == \"file\") {\n        info.set_type(eos::rpc::NSResponse::RecycleResponse::RecycleInfo::FILE);\n      } else if (item[\"type\"]  == \"recursive-dir\") {\n        info.set_type(eos::rpc::NSResponse::RecycleResponse::RecycleInfo::TREE);\n      }\n\n      info.mutable_dtime()->set_sec((strtoull(item[\"dtime\"].c_str(), 0, 10)));\n      info.mutable_owner()->set_username(item[\"username\"]);\n      info.mutable_owner()->set_groupname(item[\"groupname\"]);\n      info.mutable_owner()->set_uid(strtoul(item[\"uid\"].c_str(), 0, 10));\n      info.mutable_owner()->set_gid(strtoul(item[\"gid\"].c_str(), 0, 10));\n      info.set_size(strtoull(item[\"size\"].c_str(), 0, 10));\n      info.mutable_id()->set_path(item[\"path\"]);\n      info.set_key(item[\"key\"]);\n      auto new_info = reply->add_recycles();\n      new_info->CopyFrom(info);\n    }\n\n    if (rc) {\n      reply->set_code(E2BIG);\n      reply->set_msg(\"warning: listing was limited to user maxentries setting\");\n    }\n\n    return grpc::Status::OK;\n  } else {\n    reply->set_code(EINVAL);\n    reply->set_msg(\"error: command is currently not supported\");\n    return grpc::Status::OK;\n  }\n}\n\ngrpc::Status GrpcNsInterface::Recycle(eos::common::VirtualIdentity& vid,\n                                      eos::rpc::NSResponse::RecycleResponse* reply,\n                                      const eos::console::RecycleProto* request)\n{\n  eos_static_info(\"msg=\\\"processing recycle cmd\\\" vid.uid=%i vid.gid=%i \"\n                  \"vid.sudoer=%i vid.prot=%s\",\n                  vid.uid, vid.gid, vid.sudoer, vid.prot.c_str());\n  eos::console::RequestProto req;\n  req.mutable_recycle()->CopyFrom(*request);\n  eos::mgm::RecycleCmd cmd(std::move(req), vid);\n  eos::console::ReplyProto reply_proto;\n\n  if (request->subcmd_case() == eos::console::RecycleProto::kLs) {\n    eos::mgm::Recycle::RecycleListing rvec;\n    reply_proto = cmd.ProcessRequest(&rvec);\n\n    for (auto item : rvec) {\n      eos::rpc::NSResponse::RecycleResponse::RecycleInfo* info =\n        reply->add_recycles();\n\n      if (item[\"type\"] == \"file\") {\n        info->set_type(eos::rpc::NSResponse::RecycleResponse::RecycleInfo::FILE);\n      } else if (item[\"type\"]  == \"recursive-dir\") {\n        info->set_type(eos::rpc::NSResponse::RecycleResponse::RecycleInfo::TREE);\n      }\n\n      info->mutable_dtime()->set_sec((strtoull(item[\"dtime\"].c_str(), 0, 10)));\n      info->mutable_owner()->set_username(item[\"username\"]);\n      info->mutable_owner()->set_groupname(item[\"groupname\"]);\n      info->mutable_owner()->set_uid(strtoul(item[\"uid\"].c_str(), 0, 10));\n      info->mutable_owner()->set_gid(strtoul(item[\"gid\"].c_str(), 0, 10));\n      info->set_size(strtoull(item[\"size\"].c_str(), 0, 10));\n      info->mutable_id()->set_path(item[\"path\"]);\n      info->set_key(item[\"key\"]);\n    }\n  } else {\n    reply_proto = cmd.ProcessRequest();\n  }\n\n  if (reply_proto.retc()) {\n    reply->set_code(reply_proto.retc());\n    reply->set_msg(reply_proto.std_err());\n  } else {\n    reply->set_code(0);\n    reply->set_msg(reply_proto.std_out());\n  }\n\n  return grpc::Status::OK;\n}\n\n\ngrpc::Status\nGrpcNsInterface::Chown(eos::common::VirtualIdentity& vid,\n                       eos::rpc::NSResponse::ErrorResponse* reply,\n                       const eos::rpc::NSRequest::ChownRequest* request)\n{\n  std::string path;\n  path = request->id().path();\n\n  if (path.empty()) {\n    if (request->id().type() == eos::rpc::FILE) {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path =\n          gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(\n                                  request->id().id()).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    } else {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path =\n          gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(\n                                  request->id().id()).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    }\n\n    if (path.empty()) {\n      reply->set_code(EINVAL);\n      reply->set_msg(\"error:path is empty\");\n      return grpc::Status::OK;\n    }\n  }\n\n  XrdOucErrInfo error;\n  errno = 0;\n  uid_t uid = request->owner().uid();\n  gid_t gid = request->owner().gid();\n  std::string user = request->owner().username();\n  std::string group = request->owner().groupname();\n\n  if (!user.empty()) {\n    int errc = 0;\n    uid = eos::common::Mapping::UserNameToUid(user, errc);\n\n    if (errc) {\n      reply->set_code(EINVAL);\n      std::string msg = \"error: unable to translate username to uid '\";\n      msg += user;\n      msg += \"'\";\n      reply->set_msg(msg);\n      return grpc::Status::OK;\n    }\n  }\n\n  if (!group.empty()) {\n    int errc = 0;\n    gid = eos::common::Mapping::GroupNameToGid(group, errc);\n\n    if (errc) {\n      reply->set_code(EINVAL);\n      std::string msg = \"error: unable to translate groupname to gid '\";\n      msg += group;\n      msg += \"'\";\n      reply->set_msg(msg);\n      return grpc::Status::OK;\n    }\n  }\n\n  if (gOFS->_chown(path.c_str(),\n                   uid,\n                   gid,\n                   error, vid, (const char*) 0)) {\n    reply->set_code(errno);\n    reply->set_msg(error.getErrText());\n    return grpc::Status::OK;\n  }\n\n  reply->set_code(0);\n  std::string msg = \"info: chown file '\";\n  msg += path.c_str();\n  msg += \"' uid=\";\n  msg += std::to_string(uid);\n  msg += \"' gid=\";\n  msg += std::to_string(gid);\n  reply->set_msg(msg);\n  return grpc::Status::OK;\n}\n\ngrpc::Status\nGrpcNsInterface::Chmod(eos::common::VirtualIdentity& vid,\n                       eos::rpc::NSResponse::ErrorResponse* reply,\n                       const eos::rpc::NSRequest::ChmodRequest* request)\n{\n  std::string path;\n  path = request->id().path();\n\n  if (path.empty()) {\n    if (request->id().type() == eos::rpc::FILE) {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path =\n          gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(\n                                  request->id().id()).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    } else {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path =\n          gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(\n                                  request->id().id()).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    }\n\n    if (path.empty()) {\n      reply->set_code(EINVAL);\n      reply->set_msg(\"error:path is empty\");\n      return grpc::Status::OK;\n    }\n  }\n\n  XrdOucErrInfo error;\n  errno = 0;\n  mode_t mode = request->mode();\n  XrdSfsMode sfsmode = mode;\n\n  if (gOFS->_chmod(path.c_str(),\n                   sfsmode,\n                   error, vid, (const char*) 0)) {\n    reply->set_code(errno);\n    reply->set_msg(error.getErrText());\n    return grpc::Status::OK;\n  }\n\n  reply->set_code(0);\n  std::string msg = \"info: chmod file '\";\n  msg += path.c_str();\n  msg += \"' mode=\";\n  std::stringstream s;\n  s << std::oct << mode;\n  msg += s.str().c_str();\n  reply->set_msg(msg);\n  return grpc::Status::OK;\n}\n\n\ngrpc::Status\nGrpcNsInterface::Acl(eos::common::VirtualIdentity& vid,\n                     eos::rpc::NSResponse::AclResponse* reply,\n                     const eos::rpc::NSRequest::AclRequest* request)\n{\n  eos::console::RequestProto req;\n  std::string path;\n  uint64_t fid = 0;\n  uint64_t cid = 0;\n  path = request->id().path();\n\n  if (path.empty()) {\n    if (request->id().ino()) {\n      // get by inode\n      if (request->id().type() == eos::rpc::FILE) {\n        fid = eos::common::FileId::InodeToFid(request->id().ino());\n      } else {\n        cid = request->id().ino();\n      }\n    } else if (request->id().id()) {\n      // get by id\n      if (request->id().type() == eos::rpc::FILE) {\n        fid = request->id().id();\n      } else {\n        cid = request->id().id();\n      }\n    }\n\n    try {\n      eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n\n      if (fid) {\n        path =\n          gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(fid).get());\n      } else {\n        path =\n          gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(cid).get());\n      }\n    } catch (eos::MDException& e) {\n      path = \"\";\n      errno = e.getErrno();\n    }\n\n    if (path.empty()) {\n      reply->set_code(EINVAL);\n      reply->set_msg(\"error:path is empty\");\n      return grpc::Status::OK;\n    }\n  }\n\n  // transformt the proto request\n\n  if (request->type() == eos::rpc::NSRequest::AclRequest::SYS_ACL) {\n    req.mutable_acl()->set_sys_acl(true);\n  } else if (request->type() == eos::rpc::NSRequest::AclRequest::USER_ACL) {\n    req.mutable_acl()->set_user_acl(true);\n  }\n\n  req.mutable_acl()->set_path(path);\n  req.mutable_acl()->set_recursive(request->recursive());\n\n  if (request->cmd() == eos::rpc::NSRequest::AclRequest::MODIFY) {\n    req.mutable_acl()->set_op(eos::console::AclProto::MODIFY);\n  }\n\n  if (request->cmd() == eos::rpc::NSRequest::AclRequest::LIST) {\n    req.mutable_acl()->set_op(eos::console::AclProto::LIST);\n  }\n\n  uint32_t position = request->position();\n\n  if (position) {\n    req.mutable_acl()->set_position(position);\n  }\n\n  req.mutable_acl()->set_rule(request->rule());\n  eos::mgm::AclCmd aclcmd(std::move(req), vid);\n  eos::console::ReplyProto preply = aclcmd.ProcessRequest();\n\n  if (preply.retc()) {\n    reply->set_code(preply.retc());\n    reply->set_msg(preply.std_err());\n    return grpc::Status::OK;\n  } else {\n    if (request->cmd() == eos::rpc::NSRequest::AclRequest::MODIFY) {\n      // retrieve the current version of the acls now\n      req.mutable_acl()->set_op(eos::console::AclProto::LIST);\n      eos::mgm::AclCmd aclcmd(std::move(req), vid);\n      eos::console::ReplyProto preply = aclcmd.ProcessRequest();\n\n      if (preply.retc()) {\n        reply->set_code(preply.retc());\n        reply->set_msg(preply.std_err());\n        return grpc::Status::OK;\n      } else {\n        reply->set_rule(preply.std_out());\n      }\n    } else {\n      reply->set_rule(preply.std_out());\n    }\n  }\n\n  reply->set_code(0);\n  return grpc::Status::OK;\n}\n\n\ngrpc::Status\nGrpcNsInterface::Token(eos::common::VirtualIdentity& vid,\n                       eos::rpc::NSResponse::ErrorResponse* reply,\n                       const eos::rpc::NSRequest::TokenRequest* request)\n{\n  eos::console::RequestProto req;\n  // translate the grpc request proto to a console request proto\n  req.mutable_token()->set_path(request->token().token().path());\n  req.mutable_token()->set_permission(request->token().token().permission());\n  req.mutable_token()->set_owner(request->token().token().owner());\n  req.mutable_token()->set_group(request->token().token().group());\n  req.mutable_token()->set_expires(request->token().token().expires());\n  req.mutable_token()->set_generation(request->token().token().generation());\n  req.mutable_token()->set_allowtree(request->token().token().allowtree());\n  req.mutable_token()->set_vtoken(request->token().token().vtoken());\n\n  for (int i = 0; i < request->token().token().origins_size(); ++i) {\n    const eos::rpc::ShareAuth& auth = request->token().token().origins(i);\n    eos::console::TokenAuth* newauth = req.mutable_token()->add_origins();\n    newauth->set_host(auth.host());\n    newauth->set_prot(auth.prot());\n    newauth->set_name(auth.name());\n  }\n\n  eos::mgm::TokenCmd tokencmd(std::move(req), vid);\n  // reuse the CLI implementation\n  eos::console::ReplyProto preply = tokencmd.ProcessRequest();\n\n  if (preply.retc()) {\n    reply->set_code(preply.retc());\n    reply->set_msg(preply.std_err());\n    return grpc::Status::OK;\n  }\n\n  reply->set_code(0);\n  reply->set_msg(preply.std_out());\n  return grpc::Status::OK;\n}\n\ngrpc::Status\nGrpcNsInterface::Quota(eos::common::VirtualIdentity& vid,\n                       eos::rpc::NSResponse::QuotaResponse* reply,\n                       const eos::rpc::NSRequest::QuotaRequest* request)\n{\n  eos::console::RequestProto req;\n\n  if (request->op() == eos::rpc::QUOTAOP::GET) {\n    // get quota request\n    eos::console::QuotaProto_LsProto* ls = req.mutable_quota()->mutable_ls();\n    int rc = 0;\n    ls->set_format(true);\n\n    // filter by username\n    if (request->id().username().length()) {\n      ls->set_uid(request->id().username());\n    } else {\n      ls->set_uid(std::to_string(request->id().uid()));\n    }\n\n    if (request->id().groupname().length()) {\n      ls->set_gid(request->id().groupname());\n    } else {\n      ls->set_gid(std::to_string(request->id().gid()));\n    }\n\n    if (request->path().length()) {\n      ls->set_space(request->path());\n    }\n\n    eos::mgm::QuotaCmd cmd(std::move(req), vid);\n    eos::console::ReplyProto preply = cmd.ProcessRequest();\n\n    if ((rc = preply.retc())) {\n      std::string msg = \"Quota Command Failed: \";\n      msg += preply.std_err();\n      reply->set_code((rc > 0) ? -rc : rc);\n      reply->set_msg(msg);\n      return grpc::Status::OK;\n    }\n\n    // parse the output into a protobuf structure;\n    std::istringstream f(preply.std_out());\n    std::string line;\n\n    while (std::getline(f, line)) {\n      std::map<std::string, std::string> info;\n\n      if (eos::common::StringConversion::GetKeyValueMap(line.c_str(),\n          info,\n          \"=\",\n          \" \")) {\n        auto node = reply->add_quotanode();\n        node->set_path(info[\"space\"]);\n\n        if (info.count(\"uid\")) {\n          node->set_name(info[\"uid\"]);\n          node->set_type(eos::rpc::QUOTATYPE::USER);\n        }\n\n        if (info.count(\"gid\")) {\n          node->set_name(info[\"gid\"]);\n\n          if (info[\"gid\"] == \"project\") {\n            node->set_type(eos::rpc::QUOTATYPE::PROJECT);\n          } else {\n            node->set_type(eos::rpc::QUOTATYPE::GROUP);\n          }\n        }\n\n        node->set_usedbytes(strtoull(info[\"usedbytes\"].c_str(), 0, 10));\n        node->set_usedlogicalbytes(strtoull(info[\"usedlogicalbytes\"].c_str(), 0, 10));\n        node->set_usedfiles(strtoull(info[\"usedfiles\"].c_str(), 0, 10));\n        node->set_maxbytes(strtoull(info[\"maxbytes\"].c_str(), 0, 10));\n        node->set_maxlogicalbytes(strtoull(info[\"maxlogicalbytes\"].c_str(), 0, 10));\n        node->set_maxfiles(strtoull(info[\"maxfiles\"].c_str(), 0, 10));\n\n        if (node->maxbytes() > 0) {\n          node->set_percentageusedbytes(100.0 * node->usedbytes() / node->maxbytes());\n        } else {\n          node->set_percentageusedbytes(0);\n        }\n\n        if (node->maxfiles() > 0) {\n          node->set_percentageusedfiles(100.0 * node->usedfiles() / node->maxfiles());\n        } else {\n          node->set_percentageusedfiles(0);\n        }\n\n        node->set_statusbytes(info[\"statusbytes\"]);\n        node->set_statusfiles(info[\"statusfiles\"]);\n      }\n    }\n  } else if (request->op() == eos::rpc::QUOTAOP::SET) {\n    // set quota request\n    eos::console::QuotaProto_SetProto* sp = req.mutable_quota()->mutable_set();\n    int rc = 0;\n\n    // filter by username\n    if (request->id().username().length()) {\n      sp->set_uid(request->id().username());\n    } else {\n      if (request->id().uid()) {\n        sp->set_uid(std::to_string(request->id().uid()));\n      }\n    }\n\n    if (request->id().groupname().length()) {\n      sp->set_gid(request->id().groupname());\n    } else {\n      if (request->id().gid()) {\n        sp->set_gid(std::to_string(request->id().gid()));\n      }\n    }\n\n    if (request->path().length()) {\n      sp->set_space(request->path());\n    }\n\n    sp->set_maxbytes(std::to_string(request->maxbytes()));\n    sp->set_maxinodes(std::to_string(request->maxfiles()));\n    eos::mgm::QuotaCmd cmd(std::move(req), vid);\n    eos::console::ReplyProto preply = cmd.ProcessRequest();\n\n    if ((rc = preply.retc())) {\n      std::string msg = \"Quota Command Failed: \";\n      msg += preply.std_err();\n      reply->set_code((rc > 0) ? -rc : rc);\n      reply->set_msg(msg);\n      return grpc::Status::OK;\n    }\n  } else if (request->op() == eos::rpc::QUOTAOP::RM) {\n    // delete quota entry\n    eos::console::QuotaProto_RmProto* sp = req.mutable_quota()->mutable_rm();\n    int rc = 0;\n\n    // select uid/gid\n    if (request->id().username().length()) {\n      sp->set_uid(request->id().username());\n    } else {\n      if (request->id().uid()) {\n        sp->set_uid(std::to_string(request->id().uid()));\n      }\n    }\n\n    if (request->id().groupname().length()) {\n      sp->set_gid(request->id().groupname());\n    } else {\n      if (request->id().gid()) {\n        sp->set_gid(std::to_string(request->id().gid()));\n      }\n    }\n\n    if (request->path().length()) {\n      sp->set_space(request->path());\n    }\n\n    switch (request->entry()) {\n    case eos::rpc::QUOTAENTRY::NONE :\n      sp->set_type(eos::console::QuotaProto_RmProto::NONE);\n      break;\n\n    case eos::rpc::QUOTAENTRY::VOLUME :\n      sp->set_type(eos::console::QuotaProto_RmProto::VOLUME);\n      break;\n\n    case eos::rpc::QUOTAENTRY::INODE :\n      sp->set_type(eos::console::QuotaProto_RmProto::INODE);\n      break;\n\n    default:\n      sp->set_type(eos::console::QuotaProto_RmProto::NONE);\n      break;\n    }\n\n    eos::mgm::QuotaCmd cmd(std::move(req), vid);\n    eos::console::ReplyProto preply = cmd.ProcessRequest();\n\n    if ((rc = preply.retc())) {\n      std::string msg = \"Quota Command Failed: \";\n      msg += preply.std_err();\n      reply->set_code((rc > 0) ? -rc : rc);\n      reply->set_msg(msg);\n      return grpc::Status::OK;\n    }\n  } else if (request->op() == eos::rpc::QUOTAOP::RMNODE) {\n    // delete quota node\n    eos::console::QuotaProto_RmnodeProto* sp =\n      req.mutable_quota()->mutable_rmnode();\n    int rc = 0;\n\n    if (request->path().length()) {\n      sp->set_space(request->path());\n    }\n\n    eos::mgm::QuotaCmd cmd(std::move(req), vid);\n    eos::console::ReplyProto preply = cmd.ProcessRequest();\n\n    if ((rc = preply.retc())) {\n      std::string msg = \"Quota Command Failed: \";\n      msg += preply.std_err();\n      reply->set_code((rc > 0) ? -rc : rc);\n      reply->set_msg(msg);\n      return grpc::Status::OK;\n    }\n  }\n\n  reply->set_code(0);\n  return grpc::Status::OK;\n}\n\n#endif\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/grpc/GrpcNsInterface.hh",
    "content": "// ----------------------------------------------------------------------\n// File: GrpcNsInterface.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#ifdef EOS_GRPC\n#include \"common/Mapping.hh\"\n#include \"common/Logging.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"GrpcServer.hh\"\n#include \"proto/Rpc.grpc.pb.h\"\n#include <grpc++/grpc++.h>\n\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\n/**\n * @file   GrpcNsInterface.hh\n *\n * @brief  This class bridges namespace to gRPC requests\n *\n */\n\n\nclass GrpcNsInterface: public eos::common::LogId\n{\npublic:\n\n  static bool Filter(std::shared_ptr<eos::IFileMD> fmd,\n                     const eos::rpc::MDSelection& filter);\n\n  static bool Filter(std::shared_ptr<eos::IContainerMD> cmd,\n                     const eos::rpc::MDSelection& filter);\n\n\n  static grpc::Status GetMD(eos::common::VirtualIdentity& vid,\n                            grpc::ServerWriter<eos::rpc::MDResponse>* writer,\n                            const eos::rpc::MDRequest* request, bool check_perms = true,\n                            bool lock = true,\n                            bool access_self = false);\n\n  static grpc::Status Stat(eos::common::VirtualIdentity& vid,\n                           grpc::ServerWriter<eos::rpc::MDResponse>* writer,\n                           const eos::rpc::MDRequest* request);\n\n  static grpc::Status StreamMD(eos::common::VirtualIdentity& vid,\n                               grpc::ServerWriter<eos::rpc::MDResponse>* writer,\n                               const eos::rpc::MDRequest* request,\n                               bool streamparent = true,\n                               std::vector<uint64_t>* childdirs = 0);\n\n  static grpc::Status Find(eos::common::VirtualIdentity& vid,\n                           grpc::ServerWriter<eos::rpc::MDResponse>* writer,\n                           const eos::rpc::FindRequest* request);\n\n  static grpc::Status NsStat(eos::common::VirtualIdentity& vid,\n                             eos::rpc::NsStatResponse* reply,\n                             const eos::rpc::NsStatRequest* request);\n\n  static grpc::Status FileInsert(eos::common::VirtualIdentity& vid,\n                                 eos::rpc::InsertReply* reply,\n                                 const eos::rpc::FileInsertRequest* request);\n\n  static grpc::Status ContainerInsert(eos::common::VirtualIdentity& vid,\n                                      eos::rpc::InsertReply* reply,\n                                      const eos::rpc::ContainerInsertRequest* request);\n\n  static grpc::Status Exec(eos::common::VirtualIdentity& vid,\n                           eos::rpc::NSResponse* reply,\n                           const eos::rpc::NSRequest* request);\n\n  static grpc::Status Mkdir(eos::common::VirtualIdentity& vid,\n                            eos::rpc::NSResponse::ErrorResponse* reply,\n                            const eos::rpc::NSRequest::MkdirRequest* request);\n\n  static grpc::Status Rmdir(eos::common::VirtualIdentity& vid,\n                            eos::rpc::NSResponse::ErrorResponse* reply,\n                            const eos::rpc::NSRequest::RmdirRequest* request);\n\n  static grpc::Status Touch(eos::common::VirtualIdentity& vid,\n                            eos::rpc::NSResponse::ErrorResponse* reply,\n                            const eos::rpc::NSRequest::TouchRequest* request);\n\n  static grpc::Status Unlink(eos::common::VirtualIdentity& vid,\n                             eos::rpc::NSResponse::ErrorResponse* reply,\n                             const eos::rpc::NSRequest::UnlinkRequest* request);\n\n  static grpc::Status Rm(eos::common::VirtualIdentity& vid,\n                         eos::rpc::NSResponse::ErrorResponse* reply,\n                         const eos::rpc::NSRequest::RmRequest* request);\n\n  static grpc::Status Rename(eos::common::VirtualIdentity& vid,\n                             eos::rpc::NSResponse::ErrorResponse* reply,\n                             const eos::rpc::NSRequest::RenameRequest* request);\n\n  static grpc::Status Symlink(eos::common::VirtualIdentity& vid,\n                              eos::rpc::NSResponse::ErrorResponse* reply,\n                              const eos::rpc::NSRequest::SymlinkRequest* request);\n\n  static grpc::Status SetXAttr(eos::common::VirtualIdentity& vid,\n                               eos::rpc::NSResponse::ErrorResponse* reply,\n                               const eos::rpc::NSRequest::SetXAttrRequest* request);\n\n  static grpc::Status Version(eos::common::VirtualIdentity& vid,\n                              eos::rpc::NSResponse::VersionResponse* reply,\n                              const eos::rpc::NSRequest::VersionRequest* request);\n\n  static grpc::Status OldRecycle(eos::common::VirtualIdentity& vid,\n                                 eos::rpc::NSResponse::RecycleResponse* reply,\n                                 const eos::rpc::NSRequest::RecycleRequest* request);\n\n  static grpc::Status Recycle(eos::common::VirtualIdentity& vid,\n                              eos::rpc::NSResponse::RecycleResponse* reply,\n                              const eos::console::RecycleProto* request);\n\n  static grpc::Status Chown(eos::common::VirtualIdentity& vid,\n                            eos::rpc::NSResponse::ErrorResponse* reply,\n                            const eos::rpc::NSRequest::ChownRequest* request);\n\n  static grpc::Status Chmod(eos::common::VirtualIdentity& vid,\n                            eos::rpc::NSResponse::ErrorResponse* reply,\n                            const eos::rpc::NSRequest::ChmodRequest* request);\n\n  static grpc::Status Acl(eos::common::VirtualIdentity& vid,\n                          eos::rpc::NSResponse::AclResponse* reply,\n                          const eos::rpc::NSRequest::AclRequest* request);\n\n\n  static grpc::Status Token(eos::common::VirtualIdentity& vid,\n                            eos::rpc::NSResponse::ErrorResponse* reply,\n                            const eos::rpc::NSRequest::TokenRequest* request);\n\n  static grpc::Status Quota(eos::common::VirtualIdentity& vid,\n                            eos::rpc::NSResponse::QuotaResponse* reply,\n                            const eos::rpc::NSRequest::QuotaRequest* request);\n\n  static bool Access(eos::common::VirtualIdentity& vid, int mode,\n                     std::shared_ptr<eos::IContainerMD> cmd,\n                     eos::IFileMD::XAttrMap* attrmapF = 0);\n\n};\n\nEOSMGMNAMESPACE_END\n#endif\n"
  },
  {
    "path": "mgm/grpc/GrpcRestGwInterface.cc",
    "content": "#ifdef EOS_GRPC_GATEWAY\n\n//-----------------------------------------------------------------------------\n#include \"GrpcRestGwInterface.hh\"\n//-----------------------------------------------------------------------------\n#include \"common/Fmd.hh\"\n#include \"common/Utils.hh\"\n#include \"common/StringTokenizer.hh\"\n\n#include \"console/commands/HealthCommand.hh\"\n#include \"console/ConsoleMain.hh\"\n\n#include \"mgm/acl/Acl.hh\"\n#include \"mgm/egroup/Egroup.hh\"\n#include \"mgm/geotreeengine/GeoTreeEngine.hh\"\n#include \"mgm/proc/admin/AccessCmd.hh\"\n#include \"mgm/proc/admin/ConfigCmd.hh\"\n#include \"mgm/proc/admin/ConvertCmd.hh\"\n#include \"mgm/proc/admin/DebugCmd.hh\"\n#include \"mgm/proc/admin/EvictCmd.hh\"\n#include \"mgm/proc/admin/FsCmd.hh\"\n#include \"mgm/proc/admin/FsckCmd.hh\"\n#include \"mgm/proc/admin/GroupCmd.hh\"\n#include \"mgm/proc/admin/IoCmd.hh\"\n#include \"mgm/proc/admin/NodeCmd.hh\"\n#include \"mgm/proc/admin/NsCmd.hh\"\n#include \"mgm/proc/admin/QuotaCmd.hh\"\n#include \"mgm/proc/admin/SpaceCmd.hh\"\n#include \"mgm/proc/user/AclCmd.hh\"\n#include \"mgm/proc/user/NewfindCmd.hh\"\n#include \"mgm/proc/user/RecycleCmd.hh\"\n#include \"mgm/proc/user/RmCmd.hh\"\n#include \"mgm/proc/user/RouteCmd.hh\"\n#include \"mgm/proc/user/TokenCmd.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/IView.hh\"\n//-----------------------------------------------------------------------------\n#include <XrdPosix/XrdPosixXrootd.hh>\n//-----------------------------------------------------------------------------\n\nEOSMGMNAMESPACE_BEGIN\n\ngrpc::Status GrpcRestGwInterface::AclCall(VirtualIdentity& vid,\n    const AclProto* aclRequest, ReplyProto* reply)\n{\n  // wrap the AclProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_acl()->CopyFrom(*aclRequest);\n  eos::mgm::AclCmd aclcmd(std::move(req), vid);\n  *reply = aclcmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::AccessCall(VirtualIdentity& vid,\n    const AccessProto* accessRequest, ReplyProto* reply)\n{\n  // wrap the AccessProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_access()->CopyFrom(*accessRequest);\n  eos::mgm::AccessCmd accesscmd(std::move(req), vid);\n  *reply = accesscmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::ArchiveCall(VirtualIdentity& vid,\n    const ArchiveProto* archiveRequest, ReplyProto* reply)\n{\n  // wrap the ArchiveProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_archive()->CopyFrom(*archiveRequest);\n  std::string subcmd = req.archive().command();\n  std::string cmd_in = \"mgm.cmd=archive&mgm.subcmd=\" + subcmd;\n\n  if (subcmd == \"kill\") {\n    cmd_in += \"&mgm.archive.option=\" + req.archive().job_uuid();\n  } else if (subcmd == \"transfers\") {\n    cmd_in += \"&mgm.archive.option=\" + req.archive().selection();\n  } else {\n    if (req.archive().retry()) {\n      cmd_in += \"&mgm.archive.option=r\";\n    }\n\n    cmd_in += \"&mgm.archive.path=\" + req.archive().path();\n  }\n\n  ExecProcCmd(vid, reply, cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::AttrCall(VirtualIdentity& vid,\n    const AttrProto* attrRequest, ReplyProto* reply)\n{\n  // wrap the AttrProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_attr()->CopyFrom(*attrRequest);\n  std::string cmd_in;\n  std::string path = req.attr().md().path();\n  eos::console::AttrCmd subcmd = req.attr().cmd();\n  std::string key = req.attr().key();\n  errno = 0;\n\n  if (path.empty()) {\n    if (req.attr().md().type() == eos::console::FILE) {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(\n                 gOFS->eosFileService->getFileMD(req.attr().md().id()).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    } else {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(\n                 gOFS->eosDirectoryService->getContainerMD(req.attr().md().id()).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    }\n\n    if (path.empty()) {\n      reply->set_std_err(\"error:path is empty\");\n      reply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n  }\n\n  cmd_in = \"mgm.cmd=attr&mgm.path=\" + path;\n\n  if (subcmd == eos::console::AttrCmd::ATTR_LS) {\n    cmd_in += \"&mgm.subcmd=ls\";\n  } else if (subcmd == eos::console::AttrCmd::ATTR_SET) {\n    cmd_in += \"&mgm.subcmd=set\";\n    std::string value = req.attr().value();\n\n    // Set default values based on layout\n    if (key == \"default\") {\n      std::vector<std::string> val;\n\n      if (value == \"replica\")\n        val = {\"4k\", \"adler\", \"replica\", \"2\", \"default\"};\n      else if (value == \"raiddp\")\n        val = {\"1M\", \"adler\", \"raiddp\", \"6\", \"default\", \"crc32c\"};\n      else if (value == \"raid5\")\n        val = {\"1M\", \"adler\", \"raid5\", \"5\", \"default\", \"crc32c\"};\n      else if (value == \"raid6\")\n        val = {\"1M\", \"adler\", \"raid6\", \"6\", \"default\", \"crc32c\"};\n      else if (value == \"archive\")\n        val = {\"1M\", \"adler\", \"archive\", \"8\", \"default\", \"crc32c\"};\n      else if (value == \"qrain\")\n        val = {\"1M\", \"adler\", \"qrain\", \"12\", \"default\", \"crc32c\"};\n      else {\n        reply->set_std_err(\"Error: Value are not allowed\");\n        reply->set_retc(EINVAL);\n        return grpc::Status::OK;\n      }\n\n      ProcCommand cmd;\n      XrdOucErrInfo error;\n      std::string set_def;\n      set_def = cmd_in + \"&mgm.attr.key=sys.forced.blocksize&mgm.attr.value=\" +\n                val[0];\n      cmd.open(\"/proc/user\", set_def.c_str(), vid, &error);\n      set_def = cmd_in + \"&mgm.attr.key=sys.forced.checksum&mgm.attr.value=\" + val[1];\n      cmd.open(\"/proc/user\", set_def.c_str(), vid, &error);\n      set_def = cmd_in + \"&mgm.attr.key=sys.forced.layout&mgm.attr.value=\" + val[2];\n      cmd.open(\"/proc/user\", set_def.c_str(), vid, &error);\n      set_def = cmd_in + \"&mgm.attr.key=sys.forced.nstripes&mgm.attr.value=\" + val[3];\n      cmd.open(\"/proc/user\", set_def.c_str(), vid, &error);\n      set_def = cmd_in + \"&mgm.attr.key=sys.forced.space&mgm.attr.value=\" + val[4];\n      cmd.open(\"/proc/user\", set_def.c_str(), vid, &error);\n\n      if (value != \"replica\") {\n        set_def = cmd_in + \"&mgm.attr.key=sys.forced.blockchecksum&mgm.attr.value=\" +\n                  val[5];\n        cmd.open(\"/proc/user\", set_def.c_str(), vid, &error);\n      }\n    }\n\n    if (key == \"sys.forced.placementpolicy\" ||\n        key == \"user.forced.placementpolicy\") {\n      std::string policy;\n      eos::common::SymKey::DeBase64(value, policy);\n\n      // Check placement policy\n      if (policy != \"scattered\" &&\n          policy.rfind(\"hybrid:\", 0) != 0 &&\n          policy.rfind(\"gathered:\", 0) != 0) {\n        reply->set_std_err(\"Error: placement policy '\" + policy + \"' is invalid\\n\");\n        reply->set_retc(EINVAL);\n        return grpc::Status::OK;\n      }\n\n      // Check geotag in case of hybrid or gathered policy\n      if (policy != \"scattered\") {\n        std::string targetgeotag = policy.substr(policy.find(':') + 1);\n        std::string tmp_geotag = eos::common::SanitizeGeoTag(targetgeotag);\n\n        if (tmp_geotag != targetgeotag) {\n          reply->set_std_err(tmp_geotag);\n          reply->set_retc(EINVAL);\n          return grpc::Status::OK;\n        }\n      }\n    }\n\n    cmd_in += \"&mgm.attr.key=\" + key;\n    cmd_in += \"&mgm.attr.value=\" + value;\n  } else if (subcmd == eos::console::AttrCmd::ATTR_GET) {\n    cmd_in += \"&mgm.subcmd=get\";\n    cmd_in += \"&mgm.attr.key=\" + key;\n  } else if (subcmd == eos::console::AttrCmd::ATTR_RM) {\n    cmd_in += \"&mgm.subcmd=rm\";\n    cmd_in += \"&mgm.attr.key=\" + key;\n  } else if (subcmd == eos::console::AttrCmd::ATTR_LINK) {\n    cmd_in += \"&mgm.subcmd=set\";\n    cmd_in += \"&mgm.attr.key=sys.attr.link\";\n    cmd_in += \"&mgm.attr.value=\" + req.attr().link();\n  } else if (subcmd == eos::console::AttrCmd::ATTR_UNLINK) {\n    cmd_in += \"&mgm.subcmd=rm\";\n    cmd_in += \"&mgm.attr.key=sys.attr.link\";\n  } else if (subcmd == eos::console::AttrCmd::ATTR_FOLD) {\n    cmd_in += \"&mgm.subcmd=fold\";\n  }\n\n  if (req.attr().recursive()) {\n    cmd_in += \"&mgm.option=r\";\n  }\n\n  ExecProcCmd(vid, reply, cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::BackupCall(VirtualIdentity& vid,\n    const BackupProto* backupRequest, ReplyProto* reply)\n{\n  // wrap the BackupProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_backup()->CopyFrom(*backupRequest);\n  std::string src = req.backup().src_url();\n  std::string dst = req.backup().dst_url();\n  XrdCl::URL src_url(src.c_str()), dst_url(dst.c_str());\n\n  // Check that source is valid XRootD URL\n  if (!src_url.IsValid()) {\n    reply->set_std_err(\"Error: Source is not valid XRootD URL: \" + src);\n    reply->set_retc(EINVAL);\n    return grpc::Status::OK;\n  }\n\n  // Check that destination is valid XRootD URL\n  if (!dst_url.IsValid()) {\n    reply->set_std_err(\"Error: Destination is not valid XRootD URL: \" + dst);\n    reply->set_retc(EINVAL);\n    return grpc::Status::OK;\n  }\n\n  std::string cmd_in = \"mgm.cmd=backup&mgm.backup.src=\" + src + \"&mgm.backup.dst=\"\n                       + dst;\n\n  if (req.backup().ctime()) {\n    struct timeval tv;\n\n    if (gettimeofday(&tv, NULL)) {\n      reply->set_std_err(\"Error: Failed getting current timestamp\");\n      reply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n\n    cmd_in += \"&mgm.backup.ttime=ctime&mgm.backup.vtime=\" + std::to_string(\n                tv.tv_sec - req.backup().ctime());\n  }\n\n  if (req.backup().mtime()) {\n    struct timeval tv;\n\n    if (gettimeofday(&tv, NULL)) {\n      reply->set_std_err(\"Error: Failed getting current timestamp\");\n      reply->set_retc(errno);\n      return grpc::Status::OK;\n    }\n\n    cmd_in += \"&mgm.backup.ttime=mtime&mgm.backup.vtime=\" + std::to_string(\n                tv.tv_sec - req.backup().mtime());\n  }\n\n  if (!req.backup().xattr().empty()) {\n    cmd_in += \"&mgm.backup.excl_xattr=\" + req.backup().xattr();\n  }\n\n  ExecProcCmd(vid, reply, cmd_in);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::ChmodCall(VirtualIdentity& vid,\n    const ChmodProto* chmodRequest, ReplyProto* reply)\n{\n  // wrap the ChmodProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_chmod()->CopyFrom(*chmodRequest);\n  std::string cmd_in;\n  std::string path = req.chmod().md().path();\n  errno = 0;\n\n  if (path.empty()) {\n    if (req.chmod().md().type() == eos::console::FILE) {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(\n                                       req.chmod().md().id()\n                                     ).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    } else {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(\n                                       req.chmod().md().id()\n                                     ).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    }\n\n    if (path.empty()) {\n      reply->set_std_err(\"error:path is empty\");\n      reply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n  }\n\n  cmd_in = \"mgm.cmd=chmod\";\n  cmd_in += \"&mgm.path=\" + path;\n  cmd_in += \"&mgm.chmod.mode=\" + std::to_string(req.chmod().mode());\n\n  if (req.chmod().recursive()) {\n    cmd_in += \"&mgm.option=r\";\n  }\n\n  ExecProcCmd(vid, reply, cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::ChownCall(VirtualIdentity& vid,\n    const ChownProto* chownRequest, ReplyProto* reply)\n{\n  // wrap the ChownProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_chown()->CopyFrom(*chownRequest);\n  std::string path = req.chown().md().path();\n  uid_t uid = req.chown().owner().uid();\n  gid_t gid = req.chown().owner().gid();\n  std::string username = req.chown().owner().username();\n  std::string groupname = req.chown().owner().groupname();\n  errno = 0;\n  std::string cmd_in = \"mgm.cmd=chown\";\n\n  if (path.empty()) {\n    if (req.chown().md().type() == eos::console::FILE) {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(\n                                       req.chown().md().id()\n                                     ).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    } else {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(\n                                       req.chown().md().id()\n                                     ).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    }\n\n    if (path.empty()) {\n      reply->set_std_err(\"error:path is empty\");\n      reply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n  }\n\n  cmd_in += \"&mgm.path=\" + path;\n\n  if (req.chown().user_only() ||\n      req.chown().user_only() == req.chown().group_only()) {\n    if (!username.empty()) {\n      cmd_in += \"&mgm.chown.owner=\" + username;\n    } else {\n      cmd_in += \"&mgm.chown.owner=\" + std::to_string(uid);\n    }\n  }\n\n  if (req.chown().group_only() ||\n      req.chown().user_only() == req.chown().group_only()) {\n    if (!groupname.empty()) {\n      cmd_in += \":\" + groupname;\n    } else {\n      cmd_in += \":\" + std::to_string(gid);\n    }\n  }\n\n  if (req.chown().recursive() || req.chown().nodereference()) {\n    cmd_in += \"&mgm.chown.option=\";\n\n    if (req.chown().recursive()) {\n      cmd_in += \"r\";\n    }\n\n    if (req.chown().nodereference()) {\n      cmd_in += \"h\";\n    }\n  }\n\n  ExecProcCmd(vid, reply, cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::ConfigCall(VirtualIdentity& vid,\n    const ConfigProto* configRequest, ReplyProto* reply)\n{\n  // wrap the ConfigProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_config()->CopyFrom(*configRequest);\n  eos::mgm::ConfigCmd configcmd(std::move(req), vid);\n  *reply = configcmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::ConvertCall(VirtualIdentity& vid,\n    const ConvertProto* convertRequest, ReplyProto* reply)\n{\n  // wrap the ConvertProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_convert()->CopyFrom(*convertRequest);\n  eos::mgm::ConvertCmd convertcmd(std::move(req), vid);\n  *reply = convertcmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::CpCall(VirtualIdentity& vid,\n    const CpProto* cpRequest, ReplyProto* reply)\n{\n  // wrap the CpProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_cp()->CopyFrom(*cpRequest);\n\n  switch (req.cp().subcmd_case()) {\n  case eos::console::CpProto::kCksum: {\n    XrdCl::URL url(\"root://localhost//dummy\");\n    auto* fs = new XrdCl::FileSystem(url);\n\n    if (!fs) {\n      reply->set_std_err(\"Warning: failed to get new FS object [attempting checksum]\\n\");\n      return grpc::Status::OK;\n    }\n\n    std::string path = req.cp().cksum().path();\n    size_t pos = path.rfind(\"//\");\n\n    if (pos != std::string::npos) {\n      path.erase(0, pos + 1);\n    }\n\n    XrdCl::Buffer arg;\n    XrdCl::XRootDStatus status;\n    XrdCl::Buffer* response = nullptr;\n    arg.FromString(path);\n    status = fs->Query(XrdCl::QueryCode::Checksum, arg, response);\n\n    if (status.IsOK()) {\n      XrdOucString xsum = response->GetBuffer();\n      xsum.replace(\"eos \", \"\");\n      std::string msg = \"checksum=\";\n      msg += xsum.c_str();\n      reply->set_std_out(msg);\n    } else {\n      std::string msg = \"Warning: failed getting checksum for \";\n      msg += path;\n      reply->set_std_err(msg);\n    }\n\n    delete response;\n    delete fs;\n    break;\n  }\n\n  case eos::console::CpProto::kKeeptime: {\n    if (req.cp().keeptime().set()) {\n      // Set atime and mtime\n      std::string path = req.cp().keeptime().path();\n      char update[1024];\n      sprintf(update,\n              \"?eos.app=eoscp&mgm.pcmd=utimes&tv1_sec=%llu&tv1_nsec=%llu&tv2_sec=%llu&tv2_nsec=%llu\",\n              (unsigned long long) req.cp().keeptime().atime().seconds(),\n              (unsigned long long) req.cp().keeptime().atime().nanos(),\n              (unsigned long long) req.cp().keeptime().mtime().seconds(),\n              (unsigned long long) req.cp().keeptime().mtime().nanos()\n             );\n      XrdOucString query = \"root://localhost/\";\n      query += path.c_str();\n      query += update;\n      char value[4096];\n      value[0] = 0;\n      long long update_rc = XrdPosixXrootd::QueryOpaque(query.c_str(),\n                            value, 4096);\n      bool updateok = (update_rc >= 0);\n\n      if (updateok) {\n        // Parse the stat output\n        char tag[1024];\n        int tmp_retc;\n        int items = sscanf(value, \"%1023s retc=%d\", tag, &tmp_retc);\n        updateok = ((items == 2) && (strcmp(tag, \"utimes:\") == 0));\n      }\n\n      if (!updateok) {\n        std::string msg;\n        msg += \"Warning: access and modification time could not be preserved for \";\n        msg += path;\n        msg += \"\\nQuery: \";\n        msg += query.c_str();\n        reply->set_std_err(msg);\n      }\n    } else {\n      // Get atime and mtime\n      std::string path = req.cp().keeptime().path();\n      XrdOucString url = \"root://localhost/\";\n      url += path.c_str();\n      struct stat buf;\n\n      if (XrdPosixXrootd::Stat(url.c_str(), &buf) == 0) {\n        std::string msg;\n        msg += \"atime:\";\n        msg += std::to_string(buf.st_atime);\n        msg += \"mtime:\";\n        msg += std::to_string(buf.st_mtime);\n        reply->set_std_out(msg);\n      } else {\n        std::string msg = \"Warning: failed getting stat information for \";\n        msg += path;\n        reply->set_std_err(msg);\n      }\n    }\n\n    break;\n  }\n\n  default: {\n    reply->set_std_err(\"Error: subcommand is not supported\");\n    reply->set_retc(EINVAL);\n  }\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::DebugCall(VirtualIdentity& vid,\n    const DebugProto* debugRequest, ReplyProto* reply)\n{\n  // wrap the DebugProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_debug()->CopyFrom(*debugRequest);\n  eos::mgm::DebugCmd debugcmd(std::move(req), vid);\n  *reply = debugcmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::EvictCall(VirtualIdentity& vid,\n    const EvictProto* evictRequest, ReplyProto* reply)\n{\n  // wrap the EvictProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_debug()->CopyFrom(*evictRequest);\n  eos::mgm::EvictCmd evictcmd(std::move(req), vid);\n  *reply = evictcmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\nbool\nFileHelper_EnvFstToFmd(XrdOucEnv& env, eos::common::FmdHelper& fmd);\n\nint\nFileHelper_GetRemoteAttribute(const char* manager, const char* key,\n                              const char* path, XrdOucString& attribute);\n\nint\nFileHelper_GetRemoteFmdFromLocalDb(const char* manager, const char* shexfid,\n                                   const char* sfsid, eos::common::FmdHelper& fmd);\n\ngrpc::Status GrpcRestGwInterface::FileCall(VirtualIdentity& vid,\n    const FileProto* fileRequest, ReplyProto* reply)\n{\n  // wrap the AccessProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_file()->CopyFrom(*fileRequest);\n  // initialise VirtualIdentity object\n  auto rootvid = eos::common::VirtualIdentity::Root();\n  std::string path = req.file().md().path();\n  uint64_t fid = 0;\n\n  if (path.empty() &&\n      req.file().FileCommand_case() != eos::console::FileProto::kSymlink) {\n    // get by inode\n    if (req.file().md().ino()) {\n      fid = eos::common::FileId::InodeToFid(req.file().md().ino());\n    }\n    // get by fileid\n    else if (req.file().md().id()) {\n      fid = req.file().md().id();\n    }\n\n    try {\n      eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n      path = gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(fid).get());\n    } catch (eos::MDException& e) {\n      path = \"\";\n      errno = e.getErrno();\n    }\n  }\n\n  if (path.empty()) {\n    reply->set_std_err(\"error: path is empty\");\n    reply->set_retc(EINVAL);\n    return grpc::Status::OK;\n  }\n\n  std::string std_out, std_err;\n  ProcCommand cmd;\n  XrdOucErrInfo error;\n  std::string cmd_in = \"mgm.cmd=file\";\n\n  switch (req.file().FileCommand_case()) {\n  case eos::console::FileProto::kAdjustreplica: {\n    cmd_in += \"&mgm.subcmd=adjustreplica\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    if (!req.file().adjustreplica().space().empty()) {\n      cmd_in += \"&mgm.file.desiredspace=\" + req.file().adjustreplica().space();\n\n      if (!req.file().adjustreplica().subgroup().empty()) {\n        cmd_in += \"&mgm.file.desiredsubgroup=\" +\n                  req.file().adjustreplica().subgroup();\n      }\n    }\n\n    if (req.file().adjustreplica().nodrop()) {\n      cmd_in += \"&mgm.file.option=--nodrop\";\n    }\n\n    break;\n  }\n\n  case eos::console::FileProto::kCheck: {\n    cmd_in += \"&mgm.subcmd=getmdlocation\";\n    cmd_in += \"&mgm.format=fuse\";\n    cmd_in += \"&mgm.path=\";\n    cmd_in += path;\n    XrdOucString option = req.file().check().options().c_str();\n    cmd.open(\"/proc/user\", cmd_in.c_str(), vid, &error);\n    cmd.AddOutput(std_out, std_err);\n    cmd.close();\n    XrdOucEnv* result = new XrdOucEnv(std_out.c_str());\n    std_out = \"\";\n    bool silent = false;\n\n    if (!result) {\n      reply->set_std_err(\"error: getmdlocation query failed\\n\");\n      reply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n\n    int envlen = 0;\n    XrdOucEnv* newresult = new XrdOucEnv(result->Env(envlen));\n    delete result;\n    XrdOucString checksumattribute = \"NOTREQUIRED\";\n    bool consistencyerror = false;\n\n    if (envlen) {\n      XrdOucString ns_path = newresult->Get(\"mgm.nspath\");\n      XrdOucString checksumtype = newresult->Get(\"mgm.checksumtype\");\n      XrdOucString checksum = newresult->Get(\"mgm.checksum\");\n      XrdOucString size = newresult->Get(\"mgm.size\");\n\n      if ((option.find(\"%silent\") == STR_NPOS) && (!silent)) {\n        std_out += \"path=\\\"\";\n        std_out += ns_path.c_str();\n        std_out += \"\\\" fxid=\\\"\";\n        std_out += newresult->Get(\"mgm.fid0\");\n        std_out += \"\\\" size=\\\"\";\n        std_out += size.c_str();\n        std_out += \"\\\" nrep=\\\"\";\n        std_out += newresult->Get(\"mgm.nrep\");\n        std_out += \"\\\" checksumtype=\\\"\";\n        std_out += checksumtype.c_str();\n        std_out += \"\\\" checksum=\\\"\";\n        std_out += newresult->Get(\"mgm.checksum\");\n        std_out += \"\\\"\\n\";\n      }\n\n      int i = 0;\n      XrdOucString inconsistencylable = \"\";\n      int nreplicaonline = 0;\n\n      for (i = 0; i < 255; i++) {\n        XrdOucString repurl = \"mgm.replica.url\";\n        repurl += i;\n        XrdOucString repfid = \"mgm.fid\";\n        repfid += i;\n        XrdOucString repfsid = \"mgm.fsid\";\n        repfsid += i;\n        XrdOucString repbootstat = \"mgm.fsbootstat\";\n        repbootstat += i;\n        XrdOucString repfstpath = \"mgm.fstpath\";\n        repfstpath += i;\n\n        if (newresult->Get(repurl.c_str())) {\n          // Query\n          XrdCl::StatInfo* stat_info = 0;\n          XrdCl::XRootDStatus status;\n          std::string address = \"root://\";\n          address += newresult->Get(repurl.c_str());\n          address += \"//dummy\";\n          XrdCl::URL url(address.c_str());\n\n          if (!url.IsValid()) {\n            reply->set_std_err(\"error=URL is not valid: \" + address);\n            reply->set_retc(EINVAL);\n            return grpc::Status::OK;\n          }\n\n          // Get XrdCl::FileSystem object\n          std::unique_ptr<XrdCl::FileSystem> fs {new XrdCl::FileSystem(url)};\n\n          if (!fs) {\n            reply->set_std_err(\"error=failed to get new FS object\");\n            reply->set_retc(ECOMM);\n            return grpc::Status::OK;\n          }\n\n          XrdOucString bs = newresult->Get(repbootstat.c_str());\n          bool down = (bs != \"booted\");\n          int retc = 0;\n          int oldsilent = silent;\n          eos::common::FmdHelper fmd;\n\n          if ((option.find(\"%silent\")) != STR_NPOS) {\n            silent = true;\n          }\n\n          if (down && ((option.find(\"%force\")) == STR_NPOS)) {\n            consistencyerror = true;\n            inconsistencylable = \"DOWN\";\n\n            if (!silent) {\n              std_err += \"error: unable to retrieve file meta data from \";\n              std_err += newresult->Get(repurl.c_str());\n              std_err += \" [ status=\";\n              std_err += bs.c_str();\n              std_err += \" ]\\n\";\n            }\n          } else {\n            if ((option.find(\"%checksumattr\") != STR_NPOS)) {\n              checksumattribute = \"\";\n\n              if ((retc = FileHelper_GetRemoteAttribute(newresult->Get(repurl.c_str()),\n                          \"user.eos.checksum\",\n                          newresult->Get(repfstpath.c_str()),\n                          checksumattribute))) {\n                if (!silent) {\n                  std_err += \"error: unable to retrieve extended attribute from \";\n                  std_err += newresult->Get(repurl.c_str());\n                  std_err += \" [\";\n                  std_err += std::to_string(retc);\n                  std_err += \"]\\n\";\n                }\n              }\n            }\n\n            //..................................................................\n            // Do a remote stat using XrdCl::FileSystem\n            //..................................................................\n            uint64_t rsize;\n            XrdOucString statpath = newresult->Get(repfstpath.c_str());\n\n            if (!statpath.beginswith(\"/\")) {\n              // base 64 encode this path\n              XrdOucString statpath64 = \"\";\n              eos::common::SymKey::Base64(statpath, statpath64);\n              statpath = \"/#/\";\n              statpath += statpath64;\n            }\n\n            status = fs->Stat(statpath.c_str(), stat_info);\n\n            if (!status.IsOK()) {\n              consistencyerror = true;\n              inconsistencylable = \"STATFAILED\";\n              rsize = -1;\n            } else {\n              rsize = stat_info->GetSize();\n            }\n\n            // Free memory\n            delete stat_info;\n\n            if ((retc = FileHelper_GetRemoteFmdFromLocalDb(newresult->Get(repurl.c_str()),\n                        newresult->Get(repfid.c_str()),\n                        newresult->Get(repfsid.c_str()), fmd))) {\n              if (!silent) {\n                std_err += \"error: unable to retrieve file meta data from \";\n                std_err += newresult->Get(repurl.c_str());\n                std_err += \" [\";\n                std_err += std::to_string(retc);\n                std_err += \"]\\n\";\n              }\n\n              consistencyerror = true;\n              inconsistencylable = \"NOFMD\";\n            } else {\n              XrdOucString cx = fmd.mProtoFmd.checksum().c_str();\n\n              for (unsigned int k = (cx.length() / 2); k < SHA_DIGEST_LENGTH; k++) {\n                cx += \"00\";\n              }\n\n              XrdOucString disk_cx = fmd.mProtoFmd.diskchecksum().c_str();\n\n              for (unsigned int k = (disk_cx.length() / 2); k < SHA_DIGEST_LENGTH; k++) {\n                disk_cx += \"00\";\n              }\n\n              if ((option.find(\"%size\")) != STR_NPOS) {\n                char ss[1024];\n                sprintf(ss, \"%\" PRIu64, fmd.mProtoFmd.size());\n                XrdOucString sss = ss;\n\n                if (sss != size) {\n                  consistencyerror = true;\n                  inconsistencylable = \"SIZE\";\n                } else {\n                  if (fmd.mProtoFmd.size() != (unsigned long long) rsize) {\n                    if (!consistencyerror) {\n                      consistencyerror = true;\n                      inconsistencylable = \"FSTSIZE\";\n                    }\n                  }\n                }\n              }\n\n              if ((option.find(\"%checksum\")) != STR_NPOS) {\n                if (cx != checksum) {\n                  consistencyerror = true;\n                  inconsistencylable = \"CHECKSUM\";\n                }\n              }\n\n              if ((option.find(\"%checksumattr\") != STR_NPOS)) {\n                if ((checksumattribute.length() < 8) || (!cx.beginswith(checksumattribute))) {\n                  consistencyerror = true;\n                  inconsistencylable = \"CHECKSUMATTR\";\n                }\n              }\n\n              nreplicaonline++;\n\n              if (!silent) {\n                std_out += \"nrep=\\\"\";\n                std_out += std::to_string(i);\n                std_out += \"\\\" fsid=\\\"\";\n                std_out += newresult->Get(repfsid.c_str());\n                std_out += \"\\\" host=\\\"\";\n                std_out += newresult->Get(repurl.c_str());\n                std_out += \"\\\" fstpath=\\\"\";\n                std_out += newresult->Get(repfstpath.c_str());\n                std_out += \"\\\" size=\\\"\";\n                std_out += std::to_string(fmd.mProtoFmd.size());\n                std_out += \"\\\" statsize=\\\"\";\n                std_out += std::to_string(static_cast<long long>(rsize));\n                std_out += \"\\\" checksum=\\\"\";\n                std_out += cx.c_str();\n                std_out += \"\\\" diskchecksum=\\\"\";\n                std_out += disk_cx.c_str();\n                std_out += \"\\\"\";\n\n                if ((option.find(\"%checksumattr\") != STR_NPOS)) {\n                  std_out += \" checksumattr=\\\"\";\n                  std_out += checksumattribute.c_str();\n                  std_out += \"\\\"\";\n                }\n\n                std_out += \"\\n\";\n              }\n            }\n          }\n\n          if ((option.find(\"%silent\")) != STR_NPOS) {\n            silent = oldsilent;\n          }\n        } else {\n          break;\n        }\n      }\n\n      if ((option.find(\"%nrep\")) != STR_NPOS) {\n        int nrep = 0;\n        int stripes = 0;\n\n        if (newresult->Get(\"mgm.stripes\")) {\n          stripes = atoi(newresult->Get(\"mgm.stripes\"));\n        }\n\n        if (newresult->Get(\"mgm.nrep\")) {\n          nrep = atoi(newresult->Get(\"mgm.nrep\"));\n        }\n\n        if (nrep != stripes) {\n          consistencyerror = true;\n\n          if (inconsistencylable != \"NOFMD\") {\n            inconsistencylable = \"REPLICA\";\n          }\n        }\n      }\n\n      if ((option.find(\"%output\")) != STR_NPOS) {\n        if (consistencyerror) {\n          std_out += \"INCONSISTENCY \";\n          std_out += inconsistencylable.c_str();\n          std_out += \" path=\";\n          std_out += path.c_str();\n          std_out += \" fxid=\";\n          std_out += newresult->Get(\"mgm.fid0\");\n          std_out += \" size=\";\n          std_out += size.c_str();\n          std_out += \" stripes=\";\n          std_out += newresult->Get(\"mgm.stripes\");\n          std_out += \" nrep=\";\n          std_out += newresult->Get(\"mgm.nrep\");\n          std_out += \" nrepstored=\";\n          std_out += std::to_string(i);\n          std_out += \" nreponline=\";\n          std_out += std::to_string(nreplicaonline);\n          std_out += \" checksumtype=\";\n          std_out += checksumtype.c_str();\n          std_out += \" checksum=\";\n          std_out += newresult->Get(\"mgm.checksum\");\n          std_out += \"\\n\";\n        }\n      }\n\n      reply->set_std_out(std_out);\n      reply->set_std_err(std_err);\n\n      if (consistencyerror) {\n        reply->set_retc(EFAULT);\n      } else {\n        reply->set_retc(0);\n      }\n    } else {\n      reply->set_std_err(\"error: couldn't get meta data information\\n\");\n      reply->set_retc(EIO);\n    }\n\n    delete newresult;\n    return grpc::Status::OK;\n  }\n\n  case eos::console::FileProto::kConvert: {\n    cmd_in += \"&mgm.subcmd=convert\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    if (!req.file().convert().layout().empty()) {\n      cmd_in += \"&mgm.convert.layout=\" + req.file().convert().layout();\n    }\n\n    if (!req.file().convert().target_space().empty()) {\n      cmd_in += \"&mgm.convert.space=\" + req.file().convert().target_space();\n    }\n\n    if (!req.file().convert().placement_policy().empty()) {\n      cmd_in += \"&mgm.convert.placementpolicy=\" +\n                req.file().convert().placement_policy();\n    }\n\n    if (req.file().convert().sync()) {\n      reply->set_std_err(\"error: --sync is currently not supported\");\n      reply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n\n    if (req.file().convert().rewrite()) {\n      cmd_in += \"&mgm.option=rewrite\";\n    }\n\n    break;\n  }\n\n  case eos::console::FileProto::kCopy: {\n    cmd_in += \"&mgm.subcmd=copy\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    cmd_in += \"&mgm.file.target=\" + req.file().copy().dst();\n\n    if (req.file().copy().force() || req.file().copy().clone() ||\n        req.file().copy().silent()) {\n      cmd_in += \"&mgm.file.option=\";\n\n      if (req.file().copy().force()) {\n        cmd_in += \"-f\";\n      }\n\n      if (req.file().copy().clone()) {\n        cmd_in += \"-c\";\n      }\n\n      if (req.file().copy().silent()) {\n        cmd_in += \"-s\";\n      }\n    }\n\n    break;\n  }\n\n  case eos::console::FileProto::kDrop: {\n    cmd_in += \"&mgm.subcmd=drop\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    cmd_in += \"&mgm.file.fsid=\" + std::to_string(req.file().drop().fsid());\n\n    if (req.file().drop().force()) {\n      cmd_in += \"&mgm.file.force=1\";\n    }\n\n    break;\n  }\n\n  case eos::console::FileProto::kLayout: {\n    cmd_in += \"&mgm.subcmd=layout\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    if (req.file().layout().stripes()) {\n      cmd_in += \"&mgm.file.layout.stripes=\" + std::to_string(\n                  req.file().layout().stripes());\n    }\n\n    if (!req.file().layout().checksum().empty()) {\n      cmd_in += \"&mgm.file.layout.checksum=\" + req.file().layout().checksum();\n    }\n\n    break;\n  }\n\n  case eos::console::FileProto::kMove: {\n    cmd_in += \"&mgm.subcmd=move\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    cmd_in += \"&mgm.file.sourcefsid=\" + std::to_string(req.file().move().fsid1());\n    cmd_in += \"&mgm.file.targetfsid=\" + std::to_string(req.file().move().fsid2());\n    break;\n  }\n\n  case eos::console::FileProto::kPurge: {\n    cmd_in += \"&mgm.subcmd=purge\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    cmd_in += \"&mgm.purge.version=\" + std::to_string(\n                req.file().purge().purge_version());\n    break;\n  }\n\n  case eos::console::FileProto::kReplicate: {\n    cmd_in += \"&mgm.subcmd=replicate\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    cmd_in += \"&mgm.file.sourcefsid=\" + std::to_string(\n                req.file().replicate().fsid1());\n    cmd_in += \"&mgm.file.targetfsid=\" + std::to_string(\n                req.file().replicate().fsid2());\n    break;\n  }\n\n  case eos::console::FileProto::kResync: {\n    auto fsid = req.file().resync().fsid();\n\n    if (gOFS->QueryResync(fid, fsid)) {\n      std_out = \"info: resynced fid=\" + std::to_string(fid);\n      std_out += \" on fs=\" + std::to_string(fsid);\n      reply->set_std_out(std_out);\n      reply->set_retc(0);\n    } else {\n      std_err = \"error: failed to resync\";\n      reply->set_std_err(std_err);\n      reply->set_retc(-1);\n    }\n\n    return grpc::Status::OK;\n  }\n\n  case eos::console::FileProto::kSymlink: {\n    std::string target = req.file().symlink().target_path();\n\n    if (target.empty()) {\n      reply->set_std_err(\"error:target is empty\");\n      reply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n\n    XrdOucErrInfo error;\n    errno = 0;\n\n    if (gOFS->_symlink(path.c_str(), target.c_str(), error, vid)) {\n      reply->set_std_err(error.getErrText());\n      reply->set_retc(errno);\n      return grpc::Status::OK;\n    }\n\n    std::string msg = \"info: symlinked '\";\n    msg += path.c_str();\n    msg += \"' to '\";\n    msg += target.c_str();\n    msg += \"'\";\n    reply->set_std_out(msg);\n    reply->set_retc(0);\n    return grpc::Status::OK;\n  }\n\n  case eos::console::FileProto::kTag: {\n    cmd_in += \"&mgm.subcmd=tag\";\n    cmd_in += \"&mgm.path=\" + path;\n    cmd_in += \"&mgm.file.tag.fsid=\";\n\n    if (req.file().tag().add()) {\n      cmd_in += \"+\";\n    }\n\n    if (req.file().tag().remove()) {\n      cmd_in += \"-\";\n    }\n\n    if (req.file().tag().unlink()) {\n      cmd_in += \"~\";\n    }\n\n    cmd_in += std::to_string(req.file().tag().fsid());\n    break;\n  }\n\n  case eos::console::FileProto::kVerify: {\n    cmd_in += \"&mgm.subcmd=verify\";\n    cmd_in += \"&mgm.path=\" + path;\n    cmd_in += \"&mgm.file.verify.filterid=\" + std::to_string(\n                req.file().verify().fsid());\n\n    if (req.file().verify().checksum()) {\n      cmd_in += \"&mgm.file.compute.checksum=1\";\n    }\n\n    if (req.file().verify().commitchecksum()) {\n      cmd_in += \"&mgm.file.commit.checksum=1\";\n    }\n\n    if (req.file().verify().commitsize()) {\n      cmd_in += \"&mgm.file.commit.size=1\";\n    }\n\n    if (req.file().verify().commitfmd()) {\n      cmd_in += \"&mgm.file.commit.fmd=1\";\n    }\n\n    if (req.file().verify().rate()) {\n      cmd_in += \"&mgm.file.verify.rate=\" + std::to_string(\n                  req.file().verify().rate());\n    }\n\n    if (req.file().verify().resync()) {\n      cmd_in += \"&mgm.file.resync=1\";\n    }\n\n    break;\n  }\n\n  case eos::console::FileProto::kVersion: {\n    cmd_in += \"&mgm.subcmd=version\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    cmd_in += \"&mgm.purge.version=\" + std::to_string(\n                req.file().version().purge_version());\n    break;\n  }\n\n  case eos::console::FileProto::kVersions: {\n    cmd_in += \"&mgm.subcmd=versions\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    if (!req.file().versions().grab_version().empty()) {\n      cmd_in += \"&mgm.grab.version=\" + req.file().versions().grab_version();\n    } else {\n      cmd_in += \"&mgm.grab.version=-1\";\n    }\n\n    break;\n  }\n\n  case eos::console::FileProto::kShare: {\n    cmd_in += \"&mgm.subcmd=share\";\n    cmd_in += \"&mgm.path=\" + path;\n    cmd_in += \"&mgm.file.expires=\" + std::to_string(req.file().share().expires());\n    break;\n  }\n\n  case eos::console::FileProto::kWorkflow: {\n    cmd_in += \"&mgm.subcmd=workflow\";\n    cmd_in += \"&mgm.path=\" + path;\n    cmd_in += \"&mgm.workflow=\" + req.file().workflow().workflow();\n    cmd_in += \"&mgm.event=\" + req.file().workflow().event();\n    break;\n  }\n\n  default: {\n    reply->set_std_err(\"error: subcommand is not supported\");\n    reply->set_retc(EINVAL);\n    return grpc::Status::OK;\n  }\n  }\n\n  ExecProcCmd(vid, reply, cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::FileinfoCall(VirtualIdentity& vid,\n    const FileinfoProto* fileinfoRequest, ReplyProto* reply)\n{\n  // wrap the FileinfoProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_fileinfo()->CopyFrom(*fileinfoRequest);\n  // initialise VirtualIdentity object\n  auto rootvid = eos::common::VirtualIdentity::Root();\n  std::string path = req.fileinfo().md().path();\n\n  if (path.empty()) {\n    // get by inode\n    if (req.fileinfo().md().ino()) {\n      path = \"inode:\" + std::to_string(req.fileinfo().md().ino());\n    }\n    // get by fileid\n    else if (req.fileinfo().md().id()) {\n      path = \"fid:\" + std::to_string(req.fileinfo().md().id());\n    }\n\n    if (path.empty()) {\n      reply->set_std_err(\"error: path is empty\");\n      reply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n  }\n\n  std::string std_out, std_err;\n  ProcCommand cmd;\n  XrdOucErrInfo error;\n  std::string cmd_in = \"mgm.cmd=fileinfo\";\n  cmd_in += \"&mgm.path=\" + path;\n\n  if (req.fileinfo().path() || req.fileinfo().fid() ||\n      req.fileinfo().fxid() || req.fileinfo().size() ||\n      req.fileinfo().checksum() || req.fileinfo().fullpath() ||\n      req.fileinfo().proxy() || req.fileinfo().monitoring() ||\n      req.fileinfo().wnc() || req.fileinfo().env()) {\n    cmd_in += \"&mgm.file.info.option=\";\n  }\n\n  if (req.fileinfo().path()) {\n    cmd_in += \"--path\";\n  }\n\n  if (req.fileinfo().fid()) {\n    cmd_in += \"--fid\";\n  }\n\n  if (req.fileinfo().fxid()) {\n    cmd_in += \"--fxid\";\n  }\n\n  if (req.fileinfo().size()) {\n    cmd_in += \"--size\";\n  }\n\n  if (req.fileinfo().checksum()) {\n    cmd_in += \"--checksum\";\n  }\n\n  if (req.fileinfo().fullpath()) {\n    cmd_in += \"--fullpath\";\n  }\n\n  if (req.fileinfo().proxy()) {\n    cmd_in += \"--proxy\";\n  }\n\n  if (req.fileinfo().monitoring() || req.fileinfo().wnc()) {\n    cmd_in += \"-m\";\n  }\n\n  if (req.fileinfo().env()) {\n    cmd_in += \"--env\";\n  }\n\n  cmd.open(\"/proc/user\", cmd_in.c_str(), rootvid, &error);\n  cmd.AddOutput(std_out, std_err);\n  cmd.close();\n\n  // Complement EOS-Drive output with usernames and groupnames\n  if (!std_out.empty() && req.fileinfo().wnc()) {\n    size_t pos;\n    int errc = 0;\n\n    // Add owner's username\n    if ((pos = std_out.find(\"uid=\")) != std::string::npos) {\n      size_t pos1 = pos + 4;\n      size_t pos2 = std_out.find(' ', pos1);\n\n      if (pos1 < pos2) {\n        uid_t id = std::stoull(std_out.substr(pos1, pos2 - pos1));\n        std::string name = eos::common::Mapping::UidToUserName(id, errc);\n        std_out += \"wnc_username=\" + name + \" \";\n      }\n    }\n\n    // Add owner's groupname\n    if ((pos = std_out.find(\"gid=\")) != std::string::npos) {\n      size_t pos1 = pos + 4;\n      size_t pos2 = std_out.find(' ', pos1);\n\n      if (pos1 < pos2) {\n        uid_t id = std::stoull(std_out.substr(pos1, pos2 - pos1));\n        std::string name = eos::common::Mapping::GidToGroupName(id, errc);\n        std_out += \"wnc_groupname=\" + name + \" \";\n      }\n    }\n\n    // Get user ACL with usernames/groupnames/egroupnames\n    eos::console::AclProto acl_request;\n    eos::console::ReplyProto acl_reply;\n    acl_request.set_op(eos::console::AclProto_OpType_LIST);\n    acl_request.set_path(req.fileinfo().md().path());\n    GrpcRestGwInterface exec_acl;\n    exec_acl.AclCall(vid, &acl_request, &acl_reply);\n\n    if (!acl_reply.std_out().empty()) {\n      std_out += \"wnc_acl_user=\" + acl_reply.std_out() + \" \";\n    }\n\n    // Get sys ACL with usernames/groupnames/egroupnames\n    acl_request.set_sys_acl(true);\n    exec_acl.AclCall(vid, &acl_request, &acl_reply);\n\n    if (!acl_reply.std_out().empty()) {\n      std_out += \"wnc_acl_sys=\" + acl_reply.std_out() + \" \";\n    }\n  }\n\n  reply->set_std_out(std_out);\n  reply->set_std_err(std_err);\n  reply->set_retc(cmd.GetRetc());\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::FindCall(VirtualIdentity& vid,\n    const FindProto* findRequest, ServerWriter<ReplyProto>* writer)\n{\n  // wrap the FindProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_find()->CopyFrom(*findRequest);\n  eos::mgm::NewfindCmd findcmd(std::move(req), vid);\n  findcmd.ProcessRequest(writer);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::FsCall(VirtualIdentity& vid,\n    const FsProto* fsRequest, ReplyProto* reply)\n{\n  // wrap the FsProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_fs()->CopyFrom(*fsRequest);\n  eos::mgm::FsCmd fscmd(std::move(req), vid);\n  *reply = fscmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::FsckCall(VirtualIdentity& vid,\n    const FsckProto* fsckRequest, ServerWriter<ReplyProto>* writer)\n{\n  // wrap the FsckProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_fsck()->CopyFrom(*fsckRequest);\n  eos::mgm::FsckCmd fsckcmd(std::move(req), vid);\n  fsckcmd.ProcessRequest(writer);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::GeoschedCall(VirtualIdentity& vid,\n    const GeoschedProto* geoschedRequest, ReplyProto* reply)\n{\n  // wrap the AccessProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_geosched()->CopyFrom(*geoschedRequest);\n\n  if (vid.uid == 0) {\n    std::string subcmd;\n    reply->set_retc(SFS_ERROR);\n\n    if (req.geosched().subcmd_case() == eos::console::GeoschedProto::kAccess) {\n      subcmd = req.geosched().access().subcmd();\n      // XrdOucString has to be manually initialized to avoid strange behaviour\n      // of some GeoTreeEngine functions\n      XrdOucString output = \"\";\n      std::string geotag = req.geosched().access().geotag();\n      std::string geotag_list = req.geosched().access().geotag_list();\n      std::string proxy_group = req.geosched().access().proxy_group();\n      bool monitoring = req.geosched().access().monitoring();\n\n      if (!geotag.empty()) {\n        std::string tmp_geotag = eos::common::SanitizeGeoTag(geotag);\n\n        if (tmp_geotag != geotag) {\n          reply->set_std_err(tmp_geotag);\n          reply->set_retc(EINVAL);\n          return grpc::Status::OK;\n        }\n      }\n\n      if (subcmd == \"cleardirect\") {\n        if (gOFS->mGeoTreeEngine->clearAccessGeotagMapping(&output,\n            geotag == \"all\" ? \"\" : geotag)) {\n          reply->set_retc(SFS_OK);\n        }\n      }\n\n      if (subcmd == \"clearproxygroup\") {\n        if (gOFS->mGeoTreeEngine->clearAccessProxygroup(&output,\n            geotag == \"all\" ? \"\" : geotag)) {\n          reply->set_retc(SFS_OK);\n        }\n      }\n\n      if (subcmd == \"setdirect\") {\n        auto geotags = eos::common::StringTokenizer::split<std::vector\n                       <std::string>>(geotag_list, ',');\n\n        for (const auto& tag : geotags) {\n          std::string tmp_tag = eos::common::SanitizeGeoTag(tag);\n\n          if (tmp_tag != tag) {\n            reply->set_std_err(tmp_tag);\n            reply->set_retc(EINVAL);\n            return grpc::Status::OK;\n          }\n        }\n\n        if (gOFS->mGeoTreeEngine->setAccessGeotagMapping(&output, geotag,\n            geotag_list)) {\n          reply->set_retc(SFS_OK);\n        }\n      }\n\n      if (subcmd == \"setproxygroup\") {\n        if (gOFS->mGeoTreeEngine->setAccessProxygroup(&output, geotag, proxy_group)) {\n          reply->set_retc(SFS_OK);\n        }\n      }\n\n      if (subcmd == \"showdirect\") {\n        if (gOFS->mGeoTreeEngine->showAccessGeotagMapping(&output, monitoring)) {\n          reply->set_retc(SFS_OK);\n        }\n      }\n\n      if (subcmd == \"showproxygroup\") {\n        if (gOFS->mGeoTreeEngine->showAccessProxygroup(&output, monitoring)) {\n          reply->set_retc(SFS_OK);\n        }\n      }\n\n      reply->set_std_out(output.c_str());\n    }\n\n    if (req.geosched().subcmd_case() ==\n        eos::console::GeoschedProto::kDisabled) {\n      subcmd = req.geosched().disabled().subcmd();\n      std::string sched_group = req.geosched().disabled().group();\n      std::string op_type = req.geosched().disabled().op_type();\n      std::string geotag = req.geosched().disabled().geotag();\n      XrdOucString output = \"\";\n      bool save_config = true; // save it to the config\n\n      if (!(geotag == \"*\" && subcmd != \"add\")) {\n        std::string tmp_geotag = eos::common::SanitizeGeoTag(geotag);\n\n        if (tmp_geotag != geotag) {\n          reply->set_std_err(tmp_geotag);\n          reply->set_retc(EINVAL);\n          return grpc::Status::OK;\n        }\n      }\n\n      if (subcmd == \"add\") {\n        if (gOFS->mGeoTreeEngine->addDisabledBranch(sched_group, op_type, geotag,\n            &output, save_config)) {\n          reply->set_retc(SFS_OK);\n        }\n      }\n\n      if (subcmd == \"rm\") {\n        if (gOFS->mGeoTreeEngine->rmDisabledBranch(sched_group, op_type, geotag,\n            &output, save_config)) {\n          reply->set_retc(SFS_OK);\n        }\n      }\n\n      if (subcmd == \"show\") {\n        if (gOFS->mGeoTreeEngine->showDisabledBranches(sched_group, op_type, geotag,\n            &output)) {\n          reply->set_retc(SFS_OK);\n        }\n      }\n\n      reply->set_std_out(output.c_str());\n    }\n\n    if (req.geosched().subcmd_case() == eos::console::GeoschedProto::kRef) {\n      if (gOFS->mGeoTreeEngine->forceRefresh()) {\n        reply->set_std_out(\"GeoTreeEngine has been refreshed.\");\n        reply->set_retc(SFS_OK);\n      } else {\n        reply->set_std_out(\"GeoTreeEngine could not be refreshed at the moment.\");\n      }\n    }\n\n    if (req.geosched().subcmd_case() == eos::console::GeoschedProto::kSet) {\n      std::string param_name  = req.geosched().set().param_name();\n      std::string param_index = req.geosched().set().param_index();\n      std::string param_value = req.geosched().set().param_value();\n      int index = -1;\n\n      if (!param_index.empty()) {\n        index = std::stoi(param_index);\n      }\n\n      bool save_config = true;\n\n      if (gOFS->mGeoTreeEngine->setParameter(param_name, param_value, index,\n                                             save_config)) {\n        reply->set_std_out(\"GeoTreeEngine parameter has been set.\");\n        reply->set_retc(SFS_OK);\n      } else {\n        reply->set_std_out(\"GeoTreeEngine parameter could not be set.\");\n      }\n    }\n\n    if (req.geosched().subcmd_case() == eos::console::GeoschedProto::kShow) {\n      subcmd = req.geosched().show().subcmd();\n      bool print_tree = (subcmd == \"tree\");\n      bool print_snaps = (subcmd == \"snapshot\");\n      bool print_param = (subcmd == \"param\");\n      bool print_state = (subcmd == \"state\");\n      std::string sched_group = req.geosched().show().group();\n      std::string op_type = req.geosched().show().op_type();\n      bool use_colors = req.geosched().show().color();\n      bool monitoring = req.geosched().show().monitoring();\n      std::string output;\n      gOFS->mGeoTreeEngine->printInfo(output, print_tree, print_snaps, print_param,\n                                      print_state,\n                                      sched_group, op_type, use_colors, monitoring);\n      reply->set_std_out(output.c_str());\n      reply->set_retc(SFS_OK);\n    }\n\n    if (req.geosched().subcmd_case() ==\n        eos::console::GeoschedProto::kUpdater) {\n      subcmd = req.geosched().updater().subcmd();\n\n      if (subcmd == \"pause\") {\n        if (gOFS->mGeoTreeEngine->PauseUpdater()) {\n          reply->set_std_out(\"GeoTreeEngine has been paused.\");\n          reply->set_retc(SFS_OK);\n        } else {\n          reply->set_std_out(\"GeoTreeEngine could not be paused at the moment.\");\n        }\n      }\n\n      if (subcmd == \"resume\") {\n        gOFS->mGeoTreeEngine->ResumeUpdater();\n        reply->set_std_out(\"GeoTreeEngine has been resumed.\");\n        reply->set_retc(SFS_OK);\n      }\n    }\n  } else {\n    reply->set_std_err(\"error: you have to take role 'root' to execute this command\");\n    reply->set_retc(EPERM);\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::GroupCall(VirtualIdentity& vid,\n    const GroupProto* groupRequest, ReplyProto* reply)\n{\n  // wrap the GroupProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_group()->CopyFrom(*groupRequest);\n  eos::mgm::GroupCmd groupcmd(std::move(req), vid);\n  *reply = groupcmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::HealthCall(VirtualIdentity& vid,\n    const HealthProto* healthRequest, ReplyProto* reply)\n{\n  // wrap the HealthProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_health()->CopyFrom(*healthRequest);\n  std::string output;\n  std::string args = req.health().section();\n\n  if (req.health().all_info()) {\n    args += \" -a\";\n  }\n\n  if (req.health().monitoring()) {\n    args += \" -m\";\n  }\n\n  HealthCommand health(args.c_str());\n\n  try {\n    health.Execute(output);\n    reply->set_std_out(output.c_str());\n    reply->set_retc(0);\n  } catch (std::string& err) {\n    output = \"Error: \";\n    output += err;\n    reply->set_std_err(output.c_str());\n    reply->set_retc(errno);\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::IoCall(VirtualIdentity& vid,\n    const IoProto* ioRequest, ReplyProto* reply)\n{\n  // wrap the IoProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_io()->CopyFrom(*ioRequest);\n  eos::mgm::IoCmd iocmd(std::move(req), vid);\n  *reply = iocmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::LsCall(VirtualIdentity& vid,\n    const LsProto* lsRequest, ServerWriter<ReplyProto>* writer)\n{\n  // wrap the LsProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_ls()->CopyFrom(*lsRequest);\n  std::string path = req.ls().md().path();\n  eos::console::ReplyProto StreamReply;\n  errno = 0;\n\n  if (path.empty()) {\n    if (req.ls().md().type() == eos::console::FILE) {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(\n                                       req.ls().md().id()).get());\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n      }\n    } else {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(\n                                       req.ls().md().id()).get());\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n      }\n    }\n\n    if (errno) {\n      StreamReply.set_std_out(\"\");\n      StreamReply.set_std_err(\"Error: Path is empty\");\n      StreamReply.set_retc(EINVAL);\n      writer->Write(StreamReply);\n      return grpc::Status::OK;\n    }\n  }\n\n  std::string std_out, std_err;\n  ProcCommand cmd;\n  XrdOucErrInfo error;\n  std::string cmd_in = \"mgm.cmd=ls&mgm.path=\" + path;\n\n  if (req.ls().long_list() || req.ls().tape() ||\n      req.ls().readable_sizes() || req.ls().show_hidden() ||\n      req.ls().inode_info() || req.ls().num_ids() ||\n      req.ls().append_dir_ind() || req.ls().silent() ||\n      req.ls().wnc() || req.ls().noglobbing()) {\n    cmd_in += \"&mgm.option=\";\n\n    if (req.ls().long_list()) {\n      cmd_in += \"l\";\n    }\n\n    if (req.ls().tape()) {\n      cmd_in += \"y\";\n    }\n\n    if (req.ls().readable_sizes()) {\n      cmd_in += \"h\";\n    }\n\n    if (req.ls().show_hidden() || req.ls().wnc()) {\n      cmd_in += \"a\";\n    }\n\n    if (req.ls().inode_info()) {\n      cmd_in += \"i\";\n    }\n\n    if (req.ls().num_ids()) {\n      cmd_in += \"n\";\n    }\n\n    if (req.ls().append_dir_ind() || req.ls().wnc()) {\n      cmd_in += \"F\";\n    }\n\n    if (req.ls().silent()) {\n      cmd_in += \"s\";\n    }\n\n    if (req.ls().noglobbing()) {\n      cmd_in += \"N\";\n    }\n  }\n\n  cmd.open(\"/proc/user\", cmd_in.c_str(), vid, &error);\n  cmd.AddOutput(std_out, std_err);\n  cmd.close();\n\n  if (cmd.GetRetc() == 0) {\n    std::stringstream list(std_out);\n    std::string entry(\"\"), out(\"\");\n    int counter = 0;\n\n    while (std::getline(list, entry)) {\n      if (req.ls().wnc()) {\n        uint64_t size = 0;\n        eos::IFileMD::ctime_t mtime;\n        eos::IFileMD::XAttrMap xattrs;\n        // Get full path\n        std::string full_path;\n\n        if (entry == \"../\") {\n          continue;\n        } else if (entry == \"./\") {\n          full_path = path;\n        } else {\n          full_path = path + entry;\n        }\n\n        // Get the parameters if entry is a file\n        if (entry[entry.size() - 1] != '/') {\n          std::shared_ptr<eos::IFileMD> fmd;\n\n          try {\n            fmd = gOFS->eosView->getFile(full_path.c_str());\n          } catch (eos::MDException& e) {\n            // Maybe this is a symlink pointing outside the EOS namespace\n            try {\n              fmd = gOFS->eosView->getFile(full_path.c_str(), false);\n            } catch (eos::MDException& e) {\n              out += entry + \"\\t\\t\\n\";\n              continue;\n            }\n          }\n\n          if (fmd) {\n            fmd->getMTime(mtime);\n            xattrs = fmd->getAttributes();\n            size = fmd->getSize();\n          }\n        }\n        // Get the parameters if entry is a directory\n        else {\n          std::shared_ptr<eos::IContainerMD> cmd;\n\n          try {\n            cmd = gOFS->eosView->getContainer(full_path.c_str());\n          } catch (eos::MDException& e) {\n            out += entry + \"\\t\\t\\n\";\n            continue;\n          }\n\n          if (cmd) {\n            cmd->getMTime(mtime);\n            xattrs = cmd->getAttributes();\n          }\n        }\n\n        // Print the parameters\n        out += entry;\n        out += \"\\t\\tsize=\" + std::to_string(size);\n        out += \" mtime=\" + std::to_string(mtime.tv_sec);\n        out += \".\" + std::to_string(mtime.tv_nsec);\n\n        if (xattrs.count(\"sys.eos.btime\")) {\n          out += \" btime=\" + xattrs[\"sys.eos.btime\"];\n        }\n\n        out += \"\\n\";\n      } else {\n        out += entry + \"\\n\";\n      }\n\n      // Write every 100 lines separately to gRPC\n      counter++;\n\n      if (counter >= 100) {\n        StreamReply.set_std_out(out);\n        StreamReply.set_retc(0);\n        writer->Write(StreamReply);\n        counter = 0;\n        out.clear();\n      }\n    }\n\n    // Write last part to gRPC, if exists\n    if (!out.empty()) {\n      StreamReply.set_std_out(out);\n      StreamReply.set_retc(0);\n      writer->Write(StreamReply);\n    }\n  } else {\n    StreamReply.set_std_out(std_out);\n    StreamReply.set_std_err(std_err);\n    StreamReply.set_retc(cmd.GetRetc());\n    writer->Write(StreamReply);\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::MapCall(VirtualIdentity& vid,\n    const MapProto* mapRequest, ReplyProto* reply)\n{\n  // wrap the MapProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_map()->CopyFrom(*mapRequest);\n  std::string subcmd = req.map().command();\n  std::string cmd_in = \"mgm.cmd=map&mgm.subcmd=\" + subcmd;\n\n  if (subcmd == \"link\") {\n    cmd_in += \"&mgm.map.src=\" + req.map().src_path();\n    cmd_in += \"&mgm.map.dest=\" + req.map().dst_path();\n  } else if (subcmd == \"unlink\") {\n    cmd_in += \"&mgm.map.src=\" + req.map().src_path();\n  }\n\n  ExecProcCmd(vid, reply, cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::MemberCall(VirtualIdentity& vid,\n    const MemberProto* memberRequest, ReplyProto* reply)\n{\n  // wrap the MemberProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_member()->CopyFrom(*memberRequest);\n  std::string egroup = req.member().egroup();\n  int errc = 0;\n  std::string uid_string = eos::common::Mapping::UidToUserName(vid.uid, errc);\n  std::string rs;\n\n  if (!egroup.empty()) {\n    if (req.member().update()) {\n      gOFS->EgroupRefresh->refresh(uid_string, egroup);\n    }\n\n    rs = gOFS->EgroupRefresh->DumpMember(uid_string, egroup);\n  } else if (vid.uid != 0) {\n    reply->set_std_err(\"error: you have to take role 'root' to execute this command\");\n    reply->set_retc(EPERM);\n    return grpc::Status::OK;\n  } else {\n    rs = gOFS->EgroupRefresh->DumpMembers();\n  }\n\n  reply->set_std_out(rs);\n  reply->set_retc(SFS_OK);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::MkdirCall(VirtualIdentity& vid,\n    const MkdirProto* mkdirRequest, ReplyProto* reply)\n{\n  // wrap the MkdirProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_mkdir()->CopyFrom(*mkdirRequest);\n  std::string cmd_in = \"mgm.cmd=mkdir\";\n  std::string path = req.mkdir().md().path();\n  cmd_in += \"&mgm.path=\" + path;\n\n  if (req.mkdir().parents()) {\n    cmd_in += \"&mgm.option=p\";\n  }\n\n  ExecProcCmd(vid, reply, cmd_in, false);\n\n  if (req.mkdir().mode() != 0 && reply->retc() == 0) {\n    eos::console::ChmodProto chmod_request;\n    eos::console::ReplyProto chmod_reply;\n    chmod_request.mutable_md()->set_path(path);\n    chmod_request.set_mode(req.mkdir().mode());\n    GrpcRestGwInterface exec_chmod;\n    exec_chmod.ChmodCall(vid, &chmod_request, &chmod_reply);\n\n    if (chmod_reply.retc() != 0) {\n      reply->set_std_err(chmod_reply.std_err());\n      reply->set_retc(chmod_reply.retc());\n    }\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::MvCall(VirtualIdentity& vid,\n    const MoveProto* mvRequest, ReplyProto* reply)\n{\n  // wrap the MoveProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_mv()->CopyFrom(*mvRequest);\n  std::string path = req.mv().md().path();\n  std::string target = req.mv().target();\n  errno = 0;\n  std::string cmd_in = \"mgm.cmd=file\";\n\n  if (path.empty()) {\n    if (req.mv().md().type() == eos::console::FILE) {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(\n                                       req.mv().md().id()).get());\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n      }\n    } else {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(\n                                       req.mv().md().id()).get());\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n      }\n    }\n\n    if (errno) {\n      reply->set_std_err(\"Error: Path is empty\");\n      reply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n  }\n\n  cmd_in += \"&mgm.subcmd=rename&mgm.path=\" + path + \"&mgm.file.target=\" + target;\n  ExecProcCmd(vid, reply, cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::NodeCall(VirtualIdentity& vid,\n    const NodeProto* nodeRequest, ReplyProto* reply)\n{\n  // wrap the NodeProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_node()->CopyFrom(*nodeRequest);\n  eos::mgm::NodeCmd nodecmd(std::move(req), vid);\n  *reply = nodecmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::NsCall(VirtualIdentity& vid,\n    const NsProto* nsRequest, ReplyProto* reply)\n{\n  // wrap the NodeProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_ns()->CopyFrom(*nsRequest);\n  eos::mgm::NsCmd nscmd(std::move(req), vid);\n  *reply = nscmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::QuotaCall(VirtualIdentity& vid,\n    const QuotaProto* quotaRequest, ReplyProto* reply)\n{\n  // wrap the QuotaProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_quota()->CopyFrom(*quotaRequest);\n  eos::mgm::QuotaCmd quotacmd(std::move(req), vid);\n  *reply = quotacmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::RecycleCall(VirtualIdentity& vid,\n    const RecycleProto* recycleRequest, ReplyProto* reply)\n{\n  // wrap the RecycleProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_recycle()->CopyFrom(*recycleRequest);\n  eos::mgm::RecycleCmd recyclecmd(std::move(req), vid);\n  *reply = recyclecmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::RmCall(VirtualIdentity& vid,\n    const RmProto* rmRequest, ReplyProto* reply)\n{\n  // wrap the RmProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_rm()->CopyFrom(*rmRequest);\n  eos::mgm::RmCmd rmcmd(std::move(req), vid);\n  *reply = rmcmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::RmdirCall(VirtualIdentity& vid,\n    const RmdirProto* rmdirRequest, ReplyProto* reply)\n{\n  // wrap the NodeProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_rmdir()->CopyFrom(*rmdirRequest);\n  std::string path = req.rmdir().md().path();\n  errno = 0;\n\n  if (path.empty()) {\n    try {\n      eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n      path = gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(\n                                     req.rmdir().md().id()).get());\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n    }\n\n    if (errno) {\n      reply->set_std_err(\"Error: Path is empty\");\n      reply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n  }\n\n  std::string cmd_in = \"mgm.cmd=rmdir&mgm.path=\" + path;\n  ExecProcCmd(vid, reply, cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::RouteCall(VirtualIdentity& vid,\n    const RouteProto* routeRequest, ReplyProto* reply)\n{\n  // wrap the RouteProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_route()->CopyFrom(*routeRequest);\n  eos::mgm::RouteCmd routecmd(std::move(req), vid);\n  *reply = routecmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::SpaceCall(VirtualIdentity& vid,\n    const SpaceProto* spaceRequest, ReplyProto* reply)\n{\n  // wrap the SpaceProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_space()->CopyFrom(*spaceRequest);\n\n  if (req.space().subcmd_case() == eos::console::SpaceProto::kNodeSet) {\n    // encoding the value to Base64\n    std::string val = req.space().nodeset().nodeset_value();\n\n    if (val.substr(0, 5) != \"file:\") {\n      XrdOucString val64 = \"\";\n      eos::common::SymKey::Base64Encode((char*) val.c_str(), val.length(), val64);\n\n      while (val64.replace(\"=\", \":\")) {}\n\n      std::string nodeset = \"base64:\";\n      nodeset += val64.c_str();\n      req.mutable_space()->mutable_nodeset()->set_nodeset_value(nodeset);\n    }\n  }\n\n  eos::mgm::SpaceCmd spacecmd(std::move(req), vid);\n  *reply = spacecmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::StatCall(VirtualIdentity& vid,\n    const StatProto* statRequest, ReplyProto* reply)\n{\n  // wrap the StatProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_stat()->CopyFrom(*statRequest);\n  struct stat buf;\n  std::string path = req.stat().path();\n  std::string url = \"root://localhost/\" + path;\n\n  if (!XrdPosixXrootd::Stat(url.c_str(), &buf)) {\n    if (req.stat().file()) {\n      if (S_ISREG(buf.st_mode)) {\n        reply->set_retc(0);\n      } else {\n        reply->set_retc(1);\n      }\n    } else if (req.stat().directory()) {\n      if (S_ISDIR(buf.st_mode)) {\n        reply->set_retc(0);\n      } else {\n        reply->set_retc(1);\n      }\n    } else {\n      std::string output = \"Path: \" + path + \"\\n\";\n\n      if (S_ISREG(buf.st_mode)) {\n        XrdOucString sizestring = \"\";\n        output += \"Size: \" + std::to_string(buf.st_size) + \" (\";\n        output += eos::common::StringConversion::GetReadableSizeString(\n                    sizestring, (unsigned long long)buf.st_size, \"B\");\n        output += \")\\n\";\n        output += \"Type: regular file\\n\";\n      } else if (S_ISDIR(buf.st_mode)) {\n        output += \"Type: directory\\n\";\n      } else {\n        output += \"Type: symbolic link\\n\";\n      }\n\n      reply->set_std_out(output);\n      reply->set_retc(0);\n    }\n  } else {\n    reply->set_std_err(\"error: failed to stat \" + path);\n    reply->set_retc(EFAULT);\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::StatusCall(VirtualIdentity& vid,\n    const StatusProto* statusRequest, ReplyProto* reply)\n{\n  // wrap the StatusProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_status()->CopyFrom(*statusRequest);\n  FILE* pipe = popen(\"eos-status\", \"r\");\n  char line[4096];\n  std::string output = \"\";\n  int rc = 0;\n\n  if (!pipe) {\n    reply->set_std_err(\"Error: Failed to create pipe for eos-status execution\");\n    reply->set_retc(errno);\n    return grpc::Status::OK;\n  }\n\n  while (fgets(line, sizeof(line), pipe)) {\n    output += line;\n  }\n\n  if ((rc = pclose(pipe)) == -1) {\n    reply->set_std_err(\"Error: Failed to close pipe for eos-status execution\");\n    reply->set_retc(errno);\n    return grpc::Status::OK;\n  }\n\n  reply->set_std_out(output);\n  reply->set_retc(rc);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::TokenCall(VirtualIdentity& vid,\n    const TokenProto* tokenRequest, ReplyProto* reply)\n{\n  // wrap the TokenProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_token()->CopyFrom(*tokenRequest);\n  eos::mgm::TokenCmd tokencmd(std::move(req), vid);\n  *reply = tokencmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::TouchCall(VirtualIdentity& vid,\n    const TouchProto* touchRequest, ReplyProto* reply)\n{\n  // wrap the TokenProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_touch()->CopyFrom(*touchRequest);\n  std::string path = req.touch().md().path();\n  std::string cmd_in = \"mgm.cmd=file&mgm.subcmd=touch&mgm.path=\" + path;\n\n  if (req.touch().nolayout()) {\n    cmd_in += \"&mgm.file.touch.nolayout=true\";\n  }\n\n  if (req.touch().truncate()) {\n    cmd_in += \"&mgm.file.touch.truncate=true\";\n  }\n\n  ExecProcCmd(vid, reply, cmd_in, false);\n\n  // Create parent directories\n  if (req.touch().parents() && reply->retc() == 2) {\n    size_t pos = 0;\n\n    if (!path.empty() && path[path.size() - 1] != '/' &&\n        (pos = path.rfind('/')) != std::string::npos) {\n      std::string parent_path = path.substr(0, pos);\n      eos::console::MkdirProto mkdir_request;\n      eos::console::ReplyProto mkdir_reply;\n      mkdir_request.mutable_md()->set_path(parent_path);\n      mkdir_request.set_parents(true);\n      GrpcRestGwInterface exec_mkdir;\n      exec_mkdir.MkdirCall(vid, &mkdir_request, &mkdir_reply);\n\n      // Run touch command again\n      if (mkdir_reply.retc() == 0) {\n        ExecProcCmd(vid, reply, cmd_in, false);\n      }\n    }\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::VersionCall(VirtualIdentity& vid,\n    const VersionProto* versionRequest, ReplyProto* reply)\n{\n  // wrap the VersionProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_version()->CopyFrom(*versionRequest);\n  std::string cmd_in = \"mgm.cmd=version\";\n\n  if (req.version().monitoring() || req.version().features()) {\n    cmd_in += \"&mgm.option=\";\n  }\n\n  if (req.version().features()) {\n    cmd_in += \"f\";\n  }\n\n  if (req.version().monitoring()) {\n    cmd_in += \"m\";\n  }\n\n  ExecProcCmd(vid, reply, cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::VidCall(VirtualIdentity& vid,\n    const VidProto* vidRequest, ReplyProto* reply)\n{\n  // wrap the VidProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_vid()->CopyFrom(*vidRequest);\n  std::string std_out1, std_out2, std_err1, std_err2;\n  ProcCommand cmd1, cmd2;\n  XrdOucErrInfo error1, error2;\n  std::string cmd_in1, cmd_in2;\n  cmd_in1 = cmd_in2 = \"mgm.cmd=vid\";\n  bool has_cmd2 = false;\n\n  switch (req.vid().subcmd_case()) {\n  case eos::console::VidProto::kGateway: {\n    eos::console::VidProto_GatewayProto_Protocol prot =\n      req.vid().gateway().protocol();\n    std::string protocol;\n    eos::console::VidProto_GatewayProto_Option option =\n      req.vid().gateway().option();\n    std::string host = req.vid().gateway().hostname();\n\n    if (prot == eos::console::VidProto_GatewayProto_Protocol_ALL) {\n      protocol = \"*\";\n    } else if (prot == eos::console::VidProto_GatewayProto_Protocol_KRB5) {\n      protocol = \"krb5\";\n    } else if (prot == eos::console::VidProto_GatewayProto_Protocol_GSI) {\n      protocol = \"gsi\";\n    } else if (prot == eos::console::VidProto_GatewayProto_Protocol_SSS) {\n      protocol = \"sss\";\n    } else if (prot == eos::console::VidProto_GatewayProto_Protocol_UNIX) {\n      protocol = \"unix\";\n    } else if (prot == eos::console::VidProto_GatewayProto_Protocol_HTTPS) {\n      protocol = \"https\";\n    } else if (prot == eos::console::VidProto_GatewayProto_Protocol_GRPC) {\n      protocol = \"grpc\";\n    }\n\n    if (option == eos::console::VidProto_GatewayProto_Option_ADD) {\n      cmd_in1 += \"&mgm.subcmd=set\";\n      cmd_in1 += \"&mgm.vid.auth=tident\";\n      cmd_in1 += \"&mgm.vid.cmd=map\";\n      cmd_in1 += \"&mgm.vid.gid=0\";\n      cmd_in1 += \"&mgm.vid.key=<key>\";\n      cmd_in1 += \"&mgm.vid.pattern=\\\"\" + protocol + \"@\" + host + \"\\\"\";\n      cmd_in1 += \"&mgm.vid.uid=0\";\n    } else if (option == eos::console::VidProto_GatewayProto_Option_REMOVE) {\n      has_cmd2 = true;\n      cmd_in1 += \"&mgm.subcmd=rm\";\n      cmd_in1 += \"&mgm.vid.cmd=unmap\";\n      cmd_in1 += \"&mgm.vid.key=tident:\\\"\" + protocol + \"@\" + host + \"\\\":uid\";\n      cmd_in2 += \"&mgm.subcmd=rm\";\n      cmd_in2 += \"&mgm.vid.cmd=unmap\";\n      cmd_in2 += \"&mgm.vid.key=tident:\\\"\" + protocol + \"@\" + host + \"\\\":gid\";\n    }\n\n    break;\n  }\n\n  case eos::console::VidProto::kDefaultmapping: {\n    eos::console::VidProto_DefaultMappingProto_Option opt =\n      req.vid().defaultmapping().option();\n    eos::console::VidProto_DefaultMappingProto_Type type =\n      req.vid().defaultmapping().type();\n\n    if (opt == eos::console::VidProto_DefaultMappingProto_Option_ENABLE) {\n      cmd_in1 += \"&mgm.subcmd=set\";\n      cmd_in1 += \"&mgm.vid.cmd=map\";\n      cmd_in1 += \"&mgm.vid.pattern=<pwd>\";\n      cmd_in1 += \"&mgm.vid.key=<key>\";\n\n      if (type == eos::console::VidProto_DefaultMappingProto_Type_KRB5) {\n        cmd_in1 += \"&mgm.vid.auth=krb5\";\n        cmd_in1 += \"&mgm.vid.uid=0\";\n        cmd_in1 += \"&mgm.vid.gid=0\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_GSI) {\n        cmd_in1 += \"&mgm.vid.auth=gsi\";\n        cmd_in1 += \"&mgm.vid.uid=0\";\n        cmd_in1 += \"&mgm.vid.gid=0\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_SSS) {\n        cmd_in1 += \"&mgm.vid.auth=sss\";\n        cmd_in1 += \"&mgm.vid.uid=0\";\n        cmd_in1 += \"&mgm.vid.gid=0\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_UNIX) {\n        cmd_in1 += \"&mgm.vid.auth=unix\";\n        cmd_in1 += \"&mgm.vid.uid=99\";\n        cmd_in1 += \"&mgm.vid.gid=99\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_HTTPS) {\n        cmd_in1 += \"&mgm.vid.auth=https\";\n        cmd_in1 += \"&mgm.vid.uid=0\";\n        cmd_in1 += \"&mgm.vid.gid=0\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_TIDENT) {\n        cmd_in1 += \"&mgm.vid.auth=tident\";\n        cmd_in1 += \"&mgm.vid.uid=0\";\n        cmd_in1 += \"&mgm.vid.gid=0\";\n      }\n    } else if (opt == eos::console::VidProto_DefaultMappingProto_Option_DISABLE) {\n      has_cmd2 = true;\n      cmd_in1 += \"&mgm.subcmd=rm\";\n      cmd_in1 += \"&mgm.vid.cmd=unmap\";\n      cmd_in2 += \"&mgm.subcmd=rm\";\n      cmd_in2 += \"&mgm.vid.cmd=unmap\";\n\n      if (type == eos::console::VidProto_DefaultMappingProto_Type_KRB5) {\n        cmd_in1 += \"&mgm.vid.key=krb5:\\\"<pwd>\\\":uid\";\n        cmd_in2 += \"&mgm.vid.key=krb5:\\\"<pwd>\\\":gid\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_GSI) {\n        cmd_in1 += \"&mgm.vid.key=gsi:\\\"<pwd>\\\":uid\";\n        cmd_in2 += \"&mgm.vid.key=gsi:\\\"<pwd>\\\":gid\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_SSS) {\n        cmd_in1 += \"&mgm.vid.key=sss:\\\"<pwd>\\\":uid\";\n        cmd_in2 += \"&mgm.vid.key=sss:\\\"<pwd>\\\":gid\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_UNIX) {\n        cmd_in1 += \"&mgm.vid.key=unix:\\\"<pwd>\\\":uid\";\n        cmd_in2 += \"&mgm.vid.key=unix:\\\"<pwd>\\\":gid\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_HTTPS) {\n        cmd_in1 += \"&mgm.vid.key=https:\\\"<pwd>\\\":uid\";\n        cmd_in2 += \"&mgm.vid.key=https:\\\"<pwd>\\\":gid\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_TIDENT) {\n        cmd_in1 += \"&mgm.vid.key=tident:\\\"<pwd>\\\":uid\";\n        cmd_in2 += \"&mgm.vid.key=tident:\\\"<pwd>\\\":gid\";\n      }\n    }\n\n    break;\n  }\n\n  case eos::console::VidProto::kLs: {\n    cmd_in1 += \"&mgm.subcmd=ls\";\n\n    if (req.vid().ls().user_role() || req.vid().ls().group_role() ||\n        req.vid().ls().sudoers() || req.vid().ls().user_alias() ||\n        req.vid().ls().group_alias() || req.vid().ls().gateway() ||\n        req.vid().ls().auth() || req.vid().ls().deepness() ||\n        req.vid().ls().geo_location() || req.vid().ls().num_ids()) {\n      cmd_in1 += \"&mgm.vid.option=\";\n    }\n\n    if (req.vid().ls().user_role()) {\n      cmd_in1 += \"u\";\n    }\n\n    if (req.vid().ls().group_role()) {\n      cmd_in1 += \"g\";\n    }\n\n    if (req.vid().ls().sudoers()) {\n      cmd_in1 += \"s\";\n    }\n\n    if (req.vid().ls().user_alias()) {\n      cmd_in1 += \"U\";\n    }\n\n    if (req.vid().ls().group_alias()) {\n      cmd_in1 += \"G\";\n    }\n\n    if (req.vid().ls().gateway()) {\n      cmd_in1 += \"y\";\n    }\n\n    if (req.vid().ls().auth()) {\n      cmd_in1 += \"a\";\n    }\n\n    if (req.vid().ls().deepness()) {\n      cmd_in1 += \"N\";\n    }\n\n    if (req.vid().ls().geo_location()) {\n      cmd_in1 += \"l\";\n    }\n\n    if (req.vid().ls().num_ids()) {\n      cmd_in1 += \"n\";\n    }\n\n    break;\n  }\n\n  case eos::console::VidProto::kPublicaccesslevel: {\n    cmd_in1 += \"&mgm.subcmd=set\";\n    cmd_in1 += \"&mgm.vid.cmd=publicaccesslevel\";\n    cmd_in1 += \"&mgm.vid.key=publicaccesslevel\";\n    cmd_in1 += \"&mgm.vid.level=\";\n    cmd_in1 += std::to_string(req.vid().publicaccesslevel().level());\n    break;\n  }\n\n  case eos::console::VidProto::kRm: {\n    if (req.vid().rm().membership()) {\n      has_cmd2 = true;\n      cmd_in1 += \"&mgm.subcmd=rm\";\n      cmd_in1 += \"&mgm.vid.key=vid:\" + req.vid().rm().key() + \":uids\";\n      cmd_in2 += \"&mgm.subcmd=rm\";\n      cmd_in2 += \"&mgm.vid.key=vid:\" + req.vid().rm().key() + \":gids\";\n    } else {\n      cmd_in1 += \"&mgm.subcmd=rm\";\n      cmd_in1 += \"&mgm.vid.key=\" + req.vid().rm().key();\n    }\n\n    break;\n  }\n\n  case eos::console::VidProto::kSetgeotag: {\n    // Check if geotag is valid\n    std::string targetgeotag = req.vid().setgeotag().geotag();\n    std::string geotag = eos::common::SanitizeGeoTag(targetgeotag);\n\n    if (geotag != targetgeotag) {\n      reply->set_std_err(geotag);\n      reply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n\n    cmd_in1 += \"&mgm.subcmd=set\";\n    cmd_in1 += \"&mgm.vid.cmd=geotag\";\n    cmd_in1 += \"&mgm.vid.key=geotag:\" + req.vid().setgeotag().prefix();\n    cmd_in1 += \"&mgm.vid.geotag=\" + targetgeotag;\n    break;\n  }\n\n  case eos::console::VidProto::kSetmembership: {\n    eos::console::VidProto_SetMembershipProto_Option opt =\n      req.vid().setmembership().option();\n    std::string user = req.vid().setmembership().user();\n    std::string members = req.vid().setmembership().members();\n    cmd_in1 += \"&mgm.subcmd=set\";\n    cmd_in1 += \"&mgm.vid.cmd=membership\";\n    cmd_in1 += \"&mgm.vid.source.uid=\" + req.vid().setmembership().user();\n\n    if (opt == eos::console::VidProto_SetMembershipProto_Option_USER) {\n      cmd_in1 += \"&mgm.vid.key=\" + user + \":uids\";\n      cmd_in1 += \"&mgm.vid.target.uid=\" + members;\n    } else if (opt == eos::console::VidProto_SetMembershipProto_Option_GROUP) {\n      cmd_in1 += \"&mgm.vid.key=\" + user + \":gids\";\n      cmd_in1 += \"&mgm.vid.target.gid=\" + members;\n    } else if (opt == eos::console::VidProto_SetMembershipProto_Option_ADD_SUDO) {\n      cmd_in1 += \"&mgm.vid.key=\" + user + \":root\";\n      cmd_in1 += \"&mgm.vid.target.sudo=true\";\n    } else if (opt ==\n               eos::console::VidProto_SetMembershipProto_Option_REMOVE_SUDO) {\n      cmd_in1 += \"&mgm.vid.key=\" + user + \":root\";\n      cmd_in1 += \"&mgm.vid.target.sudo=false\";\n    }\n\n    break;\n  }\n\n  case eos::console::VidProto::kSetmap: {\n    eos::console::VidProto_SetMapProto_Type type = req.vid().setmap().type();\n    cmd_in1 += \"&mgm.subcmd=set\";\n    cmd_in1 += \"&mgm.vid.cmd=map\";\n\n    if (type == eos::console::VidProto_SetMapProto_Type_KRB5) {\n      cmd_in1 += \"&mgm.vid.auth=krb5\";\n    } else if (type == eos::console::VidProto_SetMapProto_Type_GSI) {\n      cmd_in1 += \"&mgm.vid.auth=gsi\";\n    } else if (type == eos::console::VidProto_SetMapProto_Type_HTTPS) {\n      cmd_in1 += \"&mgm.vid.auth=https\";\n    } else if (type == eos::console::VidProto_SetMapProto_Type_SSS) {\n      cmd_in1 += \"&mgm.vid.auth=sss\";\n    } else if (type == eos::console::VidProto_SetMapProto_Type_UNIX) {\n      cmd_in1 += \"&mgm.vid.auth=unix\";\n    } else if (type == eos::console::VidProto_SetMapProto_Type_TIDENT) {\n      cmd_in1 += \"&mgm.vid.auth=tident\";\n    } else if (type == eos::console::VidProto_SetMapProto_Type_VOMS) {\n      cmd_in1 += \"&mgm.vid.auth=voms\";\n    } else if (type == eos::console::VidProto_SetMapProto_Type_GRPC) {\n      cmd_in1 += \"&mgm.vid.auth=grpc\";\n    }\n\n    cmd_in1 += \"&mgm.vid.key=<key>\";\n    cmd_in1 += \"&mgm.vid.pattern=\" + req.vid().setmap().pattern();\n\n    if (!req.vid().setmap().vgid_only()) {\n      cmd_in1 += \"&mgm.vid.uid=\" + std::to_string(req.vid().setmap().vuid());\n    }\n\n    if (!req.vid().setmap().vuid_only()) {\n      cmd_in1 += \"&mgm.vid.gid=\" + std::to_string(req.vid().setmap().vgid());\n    }\n\n    break;\n  }\n\n  default:\n    reply->set_std_err(\"error: subcommand is not supported\");\n    reply->set_retc(EINVAL);\n    return grpc::Status::OK;\n  }\n\n  cmd1.open(\"/proc/admin\", cmd_in1.c_str(), vid, &error1);\n  cmd1.AddOutput(std_out1, std_err1);\n  cmd1.close();\n\n  if (has_cmd2) {\n    cmd2.open(\"/proc/admin\", cmd_in2.c_str(), vid, &error2);\n    cmd2.AddOutput(std_out2, std_err2);\n    cmd2.close();\n\n    if (!std_out1.empty()) {\n      std_out1.insert(0, \"UID: \");\n    }\n\n    if (!std_err1.empty()) {\n      std_err1.insert(0, \"UID: \");\n      std_err1 += \"\\n\";\n    }\n\n    if (!std_out2.empty()) {\n      std_out2.insert(0, \"GID: \");\n    }\n\n    if (!std_err2.empty()) {\n      std_err2.insert(0, \"GID: \");\n      std_err2 += \"\\n\";\n    }\n  }\n\n  reply->set_std_out(std_out1 + std_out2);\n  reply->set_std_err(std_err1 + std_err2);\n  reply->set_retc((cmd1.GetRetc() > cmd2.GetRetc()) ? cmd1.GetRetc() :\n                  cmd2.GetRetc());\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::WhoCall(VirtualIdentity& vid,\n    const WhoProto* whoRequest, ReplyProto* reply)\n{\n  // wrap the WhoProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_who()->CopyFrom(*whoRequest);\n  // initialise VirtualIdentity object\n  auto rootvid = eos::common::VirtualIdentity::Root();\n  std::string cmd_in = \"mgm.cmd=who\";\n\n  if (req.who().showclients() || req.who().showauth() ||\n      req.who().showall() || req.who().showsummary() ||\n      req.who().monitoring()) {\n    cmd_in += \"&mgm.option=\";\n  }\n\n  if (req.who().showclients()) {\n    cmd_in += \"c\";\n  }\n\n  if (req.who().showauth()) {\n    cmd_in += \"z\";\n  }\n\n  if (req.who().showall()) {\n    cmd_in += \"a\";\n  }\n\n  if (req.who().showsummary()) {\n    cmd_in += \"s\";\n  }\n\n  if (req.who().monitoring()) {\n    cmd_in += \"m\";\n  }\n\n  ExecProcCmd(rootvid, reply, cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcRestGwInterface::WhoamiCall(VirtualIdentity& vid,\n    const WhoamiProto* whoamiRequest, ReplyProto* reply)\n{\n  // wrap the WhoamiProto object into a RequestProto object\n  eos::console::RequestProto req;\n  req.mutable_whoami()->CopyFrom(*whoamiRequest);\n  // initialise VirtualIdentity object\n  auto rootvid = eos::common::VirtualIdentity::Root();\n  std::string cmd_in = \"mgm.cmd=whoami\";\n  ExecProcCmd(rootvid, reply, cmd_in, false);\n  return grpc::Status::OK;\n}\n\nvoid GrpcRestGwInterface::ExecProcCmd(eos::common::VirtualIdentity& vid,\n                                      ReplyProto* reply, std::string input, bool admin)\n{\n  ProcCommand cmd;\n  XrdOucErrInfo error;\n  std::string std_out, std_err;\n\n  if (admin) {\n    cmd.open(\"/proc/admin\", input.c_str(), vid, &error);\n  } else {\n    cmd.open(\"/proc/user\", input.c_str(), vid, &error);\n  }\n\n  cmd.close();\n  cmd.AddOutput(std_out, std_err);\n  reply->set_std_out(std_out);\n  reply->set_std_err(std_err);\n  reply->set_retc(cmd.GetRetc());\n}\n\n//-----------------------------------------------------------------------------\n//-----------------------------------------------------------------------------\n//  Additional functions needed by GrpcRestGwInterface::File function\n//-----------------------------------------------------------------------------\n//-----------------------------------------------------------------------------\n\n//-----------------------------------------------------------------------------\n//! Convert an FST env representation to an Fmd struct\n//! (Specific for 'eos file check' command)\n//!\n//! @param env env representation\n//! @param fmd reference to Fmd struct\n//!\n//! @return true if successful otherwise false\n//-----------------------------------------------------------------------------\nbool\nFileHelper_EnvFstToFmd(XrdOucEnv& env, eos::common::FmdHelper& fmd)\n{\n  // Check that all tags are present\n  if (!env.Get(\"id\") ||\n      !env.Get(\"cid\") ||\n      !env.Get(\"ctime\") ||\n      !env.Get(\"ctime_ns\") ||\n      !env.Get(\"mtime\") ||\n      !env.Get(\"mtime_ns\") ||\n      !env.Get(\"size\") ||\n      !env.Get(\"lid\") ||\n      !env.Get(\"uid\") ||\n      !env.Get(\"gid\")) {\n    return false;\n  }\n\n  fmd.mProtoFmd.set_fid(strtoull(env.Get(\"id\"), 0, 10));\n  fmd.mProtoFmd.set_cid(strtoull(env.Get(\"cid\"), 0, 10));\n  fmd.mProtoFmd.set_ctime(strtoul(env.Get(\"ctime\"), 0, 10));\n  fmd.mProtoFmd.set_ctime_ns(strtoul(env.Get(\"ctime_ns\"), 0, 10));\n  fmd.mProtoFmd.set_mtime(strtoul(env.Get(\"mtime\"), 0, 10));\n  fmd.mProtoFmd.set_mtime_ns(strtoul(env.Get(\"mtime_ns\"), 0, 10));\n  fmd.mProtoFmd.set_size(strtoull(env.Get(\"size\"), 0, 10));\n  fmd.mProtoFmd.set_lid(strtoul(env.Get(\"lid\"), 0, 10));\n  fmd.mProtoFmd.set_uid((uid_t) strtoul(env.Get(\"uid\"), 0, 10));\n  fmd.mProtoFmd.set_gid((gid_t) strtoul(env.Get(\"gid\"), 0, 10));\n\n  if (env.Get(\"checksum\")) {\n    fmd.mProtoFmd.set_checksum(env.Get(\"checksum\"));\n\n    if (fmd.mProtoFmd.checksum() == \"none\") {\n      fmd.mProtoFmd.set_checksum(\"\");\n    }\n  } else {\n    fmd.mProtoFmd.set_checksum(\"\");\n  }\n\n  if (env.Get(\"diskchecksum\")) {\n    fmd.mProtoFmd.set_diskchecksum(env.Get(\"diskchecksum\"));\n\n    if (fmd.mProtoFmd.diskchecksum() == \"none\") {\n      fmd.mProtoFmd.set_diskchecksum(\"\");\n    }\n  } else {\n    fmd.mProtoFmd.set_diskchecksum(\"\");\n  }\n\n  return true;\n}\n\n//-----------------------------------------------------------------------------\n//! Return a remote file attribute\n//! (Specific for 'eos file check' command)\n//!\n//! @param manager host:port of the server to contact\n//! @param key extended attribute key to get\n//! @param path file path to read attributes from\n//! @param attribute reference where to store the attribute value\n//-----------------------------------------------------------------------------\nint\nFileHelper_GetRemoteAttribute(const char* manager, const char* key,\n                              const char* path, XrdOucString& attribute)\n{\n  if ((!key) || (!path)) {\n    return EINVAL;\n  }\n\n  int rc = 0;\n  XrdCl::Buffer arg;\n  XrdCl::Buffer* response = 0;\n  XrdCl::XRootDStatus status;\n  XrdOucString fmdquery = \"/?fst.pcmd=getxattr&fst.getxattr.key=\";\n  fmdquery += key;\n  fmdquery += \"&fst.getxattr.path=\";\n  fmdquery += path;\n  XrdOucString address = \"root://\";\n  address += manager;\n  address += \"//dummy\";\n  XrdCl::URL url(address.c_str());\n\n  if (!url.IsValid()) {\n    eos_static_err(\"error=URL is not valid: %s\", address.c_str());\n    return EINVAL;\n  }\n\n  std::unique_ptr<XrdCl::FileSystem> fs(new XrdCl::FileSystem(url));\n\n  if (!fs) {\n    eos_static_err(\"error=failed to get new FS object\");\n    return EINVAL;\n  }\n\n  arg.FromString(fmdquery.c_str());\n  status = fs->Query(XrdCl::QueryCode::OpaqueFile, arg, response);\n\n  if (status.IsOK()) {\n    rc = 0;\n    eos_static_debug(\"got attribute meta data from server %s for key=%s path=%s\"\n                     \" attribute=%s\", manager, key, path, response->GetBuffer());\n  } else {\n    rc = ECOMM;\n    eos_static_err(\"Unable to retrieve meta data from server %s for key=%s path=%s\",\n                   manager, key, path);\n  }\n\n  if (rc) {\n    delete response;\n    return EIO;\n  }\n\n  if (!strncmp(response->GetBuffer(), \"ERROR\", 5)) {\n    // remote side couldn't get the record\n    eos_static_info(\"Unable to retrieve meta data on remote server %s for key=%s \"\n                    \"path=%s\", manager, key, path);\n    delete response;\n    return ENODATA;\n  }\n\n  attribute = response->GetBuffer();\n  delete response;\n  return 0;\n}\n\n//-----------------------------------------------------------------------------\n//! Return Fmd from a remote filesystem\n//! (Specific for 'eos file check' command)\n//!\n//! @param manager host:port of the server to contact\n//! @param shexfid hex string of the file id\n//! @param sfsid string of filesystem id\n//! @param fmd reference to the Fmd struct to store Fmd\n//-----------------------------------------------------------------------------\nint\nFileHelper_GetRemoteFmdFromLocalDb(const char* manager, const char* shexfid,\n                                   const char* sfsid, eos::common::FmdHelper& fmd)\n{\n  if ((!manager) || (!shexfid) || (!sfsid)) {\n    return EINVAL;\n  }\n\n  int rc = 0;\n  XrdCl::Buffer arg;\n  XrdCl::Buffer* response = 0;\n  XrdCl::XRootDStatus status;\n  XrdOucString fmdquery = \"/?fst.pcmd=getfmd&fst.getfmd.fid=\";\n  fmdquery += shexfid;\n  fmdquery += \"&fst.getfmd.fsid=\";\n  fmdquery += sfsid;\n  XrdOucString address = \"root://\";\n  address += manager;\n  address += \"//dummy\";\n  XrdCl::URL url(address.c_str());\n\n  if (!url.IsValid()) {\n    eos_static_err(\"error=URL is not valid: %s\", address.c_str());\n    return EINVAL;\n  }\n\n  std::unique_ptr<XrdCl::FileSystem> fs(new XrdCl::FileSystem(url));\n\n  if (!fs) {\n    eos_static_err(\"error=failed to get new FS object\");\n    return EINVAL;\n  }\n\n  arg.FromString(fmdquery.c_str());\n  status = fs->Query(XrdCl::QueryCode::OpaqueFile, arg, response);\n\n  if (status.IsOK()) {\n    rc = 0;\n    eos_static_debug(\"got replica file meta data from server %s for fxid=%s fsid=%s\",\n                     manager, shexfid, sfsid);\n  } else {\n    rc = ECOMM;\n    eos_static_err(\"Unable to retrieve meta data from server %s for fxid=%s fsid=%s\",\n                   manager, shexfid, sfsid);\n  }\n\n  if (rc) {\n    delete response;\n    return EIO;\n  }\n\n  if (!strncmp(response->GetBuffer(), \"ERROR\", 5)) {\n    // remote side couldn't get the record\n    eos_static_info(\"Unable to retrieve meta data on remote server %s for fxid=%s fsid=%s\",\n                    manager, shexfid, sfsid);\n    delete response;\n    return ENODATA;\n  }\n\n  // get the remote file meta data into an env hash\n  XrdOucEnv fmdenv(response->GetBuffer());\n\n  if (!FileHelper_EnvFstToFmd(fmdenv, fmd)) {\n    int envlen;\n    eos_static_err(\"Failed to unparse file meta data %s\", fmdenv.Env(envlen));\n    delete response;\n    return EIO;\n  }\n\n  // very simple check\n  if (fmd.mProtoFmd.fid() != eos::common::FileId::Hex2Fid(shexfid)) {\n    eos_static_err(\"Uups! Received wrong meta data from remote server - fid \"\n                   \"is %lu instead of %lu !\", fmd.mProtoFmd.fid(),\n                   eos::common::FileId::Hex2Fid(shexfid));\n    delete response;\n    return EIO;\n  }\n\n  delete response;\n  return 0;\n}\n\nEOSMGMNAMESPACE_END\n\n#endif // EOS_GRPC_GATEWAY\n"
  },
  {
    "path": "mgm/grpc/GrpcRestGwInterface.hh",
    "content": "#pragma once\n\n#ifdef EOS_GRPC_GATEWAY\n\n//-----------------------------------------------------------------------------\n#include \"common/VirtualIdentity.hh\"\n#include \"common/Logging.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"proto/eos_rest_gateway/eos_rest_gateway_service.grpc.pb.h\"\n//-----------------------------------------------------------------------------\nusing grpc::Server;\nusing grpc::ServerBuilder;\nusing grpc::ServerContext;\nusing grpc::ServerWriter;\nusing grpc::Status;\nusing eos::common::VirtualIdentity;\nusing eos::console::AccessProto;\nusing eos::console::AclProto;\nusing eos::console::ArchiveProto;\nusing eos::console::AttrProto;\nusing eos::console::BackupProto;\nusing eos::console::ChmodProto;\nusing eos::console::ChownProto;\nusing eos::console::ConfigProto;\nusing eos::console::ConfigProto;\nusing eos::console::ConvertProto;\nusing eos::console::CpProto;\nusing eos::console::DebugProto;\nusing eos::console::EvictProto;\nusing eos::console::FileProto;\nusing eos::console::FileinfoProto;\nusing eos::console::FindProto;\nusing eos::console::FsProto;\nusing eos::console::FsckProto;\nusing eos::console::GeoschedProto;\nusing eos::console::GroupProto;\nusing eos::console::HealthProto;\nusing eos::console::MapProto;\nusing eos::console::MemberProto;\nusing eos::console::IoProto;\nusing eos::console::LsProto;\nusing eos::console::MkdirProto;\nusing eos::console::MoveProto;\nusing eos::console::NodeProto;\nusing eos::console::NsProto;\nusing eos::console::QuotaProto;\nusing eos::console::RecycleProto;\nusing eos::console::ReplyProto;\nusing eos::console::RmProto;\nusing eos::console::RmdirProto;\nusing eos::console::RouteProto;\nusing eos::console::SpaceProto;\nusing eos::console::StatProto;\nusing eos::console::StatusProto;\nusing eos::console::TokenProto;\nusing eos::console::TouchProto;\nusing eos::console::VersionProto;\nusing eos::console::VidProto;\nusing eos::console::WhoProto;\nusing eos::console::WhoamiProto;\n//-----------------------------------------------------------------------------\n\nEOSMGMNAMESPACE_BEGIN\n\n/**\n * @file   GrpcRestGwInterface.hh\n *\n * @brief  This class bridges Http client commands to gRPC requests\n *\n */\nclass GrpcRestGwInterface : public eos::common::LogId\n{\npublic:\n\n//-----------------------------------------------------------------------------\n//  Execute specific EOS command for the EOS gRPC request\n//-----------------------------------------------------------------------------\n  Status AclCall(VirtualIdentity& vid, const AclProto* aclRequest,\n                 ReplyProto* reply);\n  Status AccessCall(VirtualIdentity& vid, const AccessProto* accessRequest,\n                    ReplyProto* reply);\n  Status ArchiveCall(VirtualIdentity& vid, const ArchiveProto* archiveRequest,\n                     ReplyProto* reply);\n  Status AttrCall(VirtualIdentity& vid, const AttrProto* attrRequest,\n                  ReplyProto* reply);\n  Status BackupCall(VirtualIdentity& vid, const BackupProto* backupRequest,\n                    ReplyProto* reply);\n  Status ChmodCall(VirtualIdentity& vid, const ChmodProto* chmodRequest,\n                   ReplyProto* reply);\n  Status ChownCall(VirtualIdentity& vid, const ChownProto* chownRequest,\n                   ReplyProto* reply);\n  Status ConfigCall(VirtualIdentity& vid, const ConfigProto* configRequest,\n                    ReplyProto* reply);\n  Status ConvertCall(VirtualIdentity& vid, const ConvertProto* convertRequest,\n                     ReplyProto* reply);\n  Status CpCall(VirtualIdentity& vid, const CpProto* cpRequest,\n                ReplyProto* reply);\n  Status DebugCall(VirtualIdentity& vid, const DebugProto* debugRequest,\n                   ReplyProto* reply);\n  Status EvictCall(VirtualIdentity& vid, const EvictProto* evictRequest,\n                   ReplyProto* reply);\n  Status FileCall(VirtualIdentity& vid, const FileProto* fileRequest,\n                  ReplyProto* reply);\n  Status FileinfoCall(VirtualIdentity& vid, const FileinfoProto* fileinfoRequest,\n                      ReplyProto* reply);\n  Status FindCall(VirtualIdentity& vid, const FindProto* findRequest,\n                  ServerWriter<ReplyProto>* writer);\n  Status FsCall(VirtualIdentity& vid, const FsProto* fsRequest,\n                ReplyProto* reply);\n  Status FsckCall(VirtualIdentity& vid, const FsckProto* fsckRequest,\n                  ServerWriter<ReplyProto>* writer);\n  Status GeoschedCall(VirtualIdentity& vid, const GeoschedProto* geoschedRequest,\n                      ReplyProto* reply);\n  Status GroupCall(VirtualIdentity& vid, const GroupProto* groupRequest,\n                   ReplyProto* reply);\n  Status HealthCall(VirtualIdentity& vid, const HealthProto* healthRequest,\n                    ReplyProto* reply);\n  Status IoCall(VirtualIdentity& vid, const IoProto* ioRequest,\n                ReplyProto* reply);\n  Status LsCall(VirtualIdentity& vid, const LsProto* lsRequest,\n                ServerWriter<ReplyProto>* writer);\n  Status MapCall(VirtualIdentity& vid, const MapProto* mapRequest,\n                 ReplyProto* reply);\n  Status MemberCall(VirtualIdentity& vid, const MemberProto* memberRequest,\n                    ReplyProto* reply);\n  Status MkdirCall(VirtualIdentity& vid, const MkdirProto* mkdirRequest,\n                   ReplyProto* reply);\n  Status MvCall(VirtualIdentity& vid, const MoveProto* mvRequest,\n                ReplyProto* reply);\n  Status NodeCall(VirtualIdentity& vid, const NodeProto* nodeRequest,\n                  ReplyProto* reply);\n  Status NsCall(VirtualIdentity& vid, const NsProto* nsRequest,\n                ReplyProto* reply);\n  Status QuotaCall(VirtualIdentity& vid, const QuotaProto* quotaRequest,\n                   ReplyProto* reply);\n  Status RecycleCall(VirtualIdentity& vid, const RecycleProto* recycleRequest,\n                     ReplyProto* reply);\n  Status RmCall(VirtualIdentity& vid, const RmProto* rmRequest,\n                ReplyProto* reply);\n  Status RmdirCall(VirtualIdentity& vid, const RmdirProto* rmdirRequest,\n                   ReplyProto* reply);\n  Status RouteCall(VirtualIdentity& vid, const RouteProto* routeRequest,\n                   ReplyProto* reply);\n  Status SpaceCall(VirtualIdentity& vid, const SpaceProto* spaceRequest,\n                   ReplyProto* reply);\n  Status StatCall(VirtualIdentity& vid, const StatProto* statRequest,\n                  ReplyProto* reply);\n  Status StatusCall(VirtualIdentity& vid, const StatusProto* statusRequest,\n                    ReplyProto* reply);\n  Status TokenCall(VirtualIdentity& vid, const TokenProto* tokenRequest,\n                   ReplyProto* reply);\n  Status TouchCall(VirtualIdentity& vid, const TouchProto* touchRequest,\n                   ReplyProto* reply);\n  Status VersionCall(VirtualIdentity& vid, const VersionProto* versionRequest,\n                     ReplyProto* reply);\n  Status VidCall(VirtualIdentity& vid, const VidProto* vidRequest,\n                 ReplyProto* reply);\n  Status WhoCall(VirtualIdentity& vid, const WhoProto* whoRequest,\n                 ReplyProto* reply);\n  Status WhoamiCall(VirtualIdentity& vid, const WhoamiProto* whoamiRequest,\n                    ReplyProto* reply);\n//-----------------------------------------------------------------------------\n\nprivate:\n\n  void ExecProcCmd(eos::common::VirtualIdentity& vid, ReplyProto* reply,\n                   std::string input, bool admin = true);\n};\n\nEOSMGMNAMESPACE_END\n\n#endif // EOS_GRPC_GATEWAY\n"
  },
  {
    "path": "mgm/grpc/GrpcRestGwServer.cc",
    "content": "#include \"GrpcRestGwServer.hh\"\n#include <google/protobuf/util/json_util.h>\n#include \"common/Logging.hh\"\n#include \"common/StringConversion.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include <XrdSec/XrdSecEntity.hh>\n\n#ifdef EOS_GRPC_GATEWAY\n#include \"proto/eos_rest_gateway/eos_rest_gateway_service.grpc.pb.h\"\n#include \"EosGrpcGateway.h\"\n\n#include <grpc++/security/credentials.h>\n\nusing grpc::Server;\nusing grpc::ServerBuilder;\nusing grpc::ServerContext;\nusing grpc::ServerWriter;\nusing grpc::Status;\nusing eos::rest::gateway::service::EosRestGatewayService;\nusing eos::console::AccessProto;\nusing eos::console::AclProto;\nusing eos::console::ArchiveProto;\nusing eos::console::AttrProto;\nusing eos::console::BackupProto;\nusing eos::console::ChmodProto;\nusing eos::console::ChownProto;\nusing eos::console::ConfigProto;\nusing eos::console::ConfigProto;\nusing eos::console::ConvertProto;\nusing eos::console::CpProto;\nusing eos::console::DebugProto;\nusing eos::console::EvictProto;\nusing eos::console::FileProto;\nusing eos::console::FileinfoProto;\nusing eos::console::FindProto;\nusing eos::console::FsProto;\nusing eos::console::FsckProto;\nusing eos::console::GeoschedProto;\nusing eos::console::GroupProto;\nusing eos::console::HealthProto;\nusing eos::console::MapProto;\nusing eos::console::MemberProto;\nusing eos::console::IoProto;\nusing eos::console::LsProto;\nusing eos::console::MkdirProto;\nusing eos::console::MoveProto;\nusing eos::console::NodeProto;\nusing eos::console::NsProto;\nusing eos::console::QuotaProto;\nusing eos::console::RecycleProto;\nusing eos::console::ReplyProto;\nusing eos::console::RmProto;\nusing eos::console::RmdirProto;\nusing eos::console::RouteProto;\nusing eos::console::SpaceProto;\nusing eos::console::StatProto;\nusing eos::console::StatusProto;\nusing eos::console::TokenProto;\nusing eos::console::TouchProto;\nusing eos::console::VersionProto;\nusing eos::console::VidProto;\nusing eos::console::WhoProto;\nusing eos::console::WhoamiProto;\n\n#endif // EOS_GRPC_GATEWAY\n\nEOSMGMNAMESPACE_BEGIN\n\n#ifdef EOS_GRPC_GATEWAY\n\nclass EosRestGatewayServiceImpl final : public EosRestGatewayService::Service,\n  public eos::common::LogId\n{\n  Status AclRequest(ServerContext* context, const AclProto* request,\n                    ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.AclCall(vid, request, reply);\n  }\n\n  Status AccessRequest(ServerContext* context, const AccessProto* request,\n                       ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.AccessCall(vid, request, reply);\n  }\n\n  Status ArchiveRequest(ServerContext* context, const ArchiveProto* request,\n                        ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.ArchiveCall(vid, request, reply);\n  }\n\n  Status AttrRequest(ServerContext* context, const AttrProto* request,\n                     ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.AttrCall(vid, request, reply);\n  }\n\n  Status BackupRequest(ServerContext* context, const BackupProto* request,\n                       ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.BackupCall(vid, request, reply);\n  }\n\n  Status ChmodRequest(ServerContext* context, const ChmodProto* request,\n                      ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.ChmodCall(vid, request, reply);\n  }\n\n  Status ChownRequest(ServerContext* context, const ChownProto* request,\n                      ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.ChownCall(vid, request, reply);\n  }\n\n  Status ConfigRequest(ServerContext* context, const ConfigProto* request,\n                       ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.ConfigCall(vid, request, reply);\n  }\n\n  Status ConvertRequest(ServerContext* context, const ConvertProto* request,\n                        ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.ConvertCall(vid, request, reply);\n  }\n\n  Status CpRequest(ServerContext* context, const CpProto* request,\n                   ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.CpCall(vid, request, reply);\n  }\n\n  Status DebugRequest(ServerContext* context, const DebugProto* request,\n                      ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.DebugCall(vid, request, reply);\n  }\n\n  Status EvictRequest(ServerContext* context, const EvictProto* request,\n                      ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.EvictCall(vid, request, reply);\n  }\n\n  Status FileRequest(ServerContext* context, const FileProto* request,\n                     ReplyProto* reply)\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.FileCall(vid, request, reply);\n  }\n\n  Status FileinfoRequest(ServerContext* context, const FileinfoProto* request,\n                         ReplyProto* reply)\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.FileinfoCall(vid, request, reply);\n  }\n\n  Status FindRequest(ServerContext* context, const FindProto* request,\n                     ServerWriter<ReplyProto>* writer)\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.FindCall(vid, request, writer);\n  }\n\n  Status FsRequest(ServerContext* context, const FsProto* request,\n                   ReplyProto* reply)\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.FsCall(vid, request, reply);\n  }\n\n  Status FsckRequest(ServerContext* context, const FsckProto* request,\n                     ServerWriter<ReplyProto>* writer)\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.FsckCall(vid, request, writer);\n  }\n\n  Status GeoschedRequest(ServerContext* context, const GeoschedProto* request,\n                         ReplyProto* reply)\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.GeoschedCall(vid, request, reply);\n  }\n\n  Status GroupRequest(ServerContext* context, const GroupProto* request,\n                      ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.GroupCall(vid, request, reply);\n  }\n\n  Status HealthRequest(ServerContext* context, const HealthProto* request,\n                       ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.HealthCall(vid, request, reply);\n  }\n\n  Status IoRequest(ServerContext* context, const IoProto* request,\n                   ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.IoCall(vid, request, reply);\n  }\n\n  Status LsRequest(ServerContext* context, const LsProto* request,\n                   ServerWriter<ReplyProto>* writer)\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.LsCall(vid, request, writer);\n  }\n\n  Status MapRequest(ServerContext* context, const MapProto* request,\n                    ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.MapCall(vid, request, reply);\n  }\n\n  Status MemberRequest(ServerContext* context, const MemberProto* request,\n                       ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.MemberCall(vid, request, reply);\n  }\n\n  Status MkdirRequest(ServerContext* context, const MkdirProto* request,\n                      ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.MkdirCall(vid, request, reply);\n  }\n\n  Status MvRequest(ServerContext* context, const MoveProto* request,\n                   ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.MvCall(vid, request, reply);\n  }\n\n  Status NodeRequest(ServerContext* context, const NodeProto* request,\n                     ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.NodeCall(vid, request, reply);\n  }\n\n  Status NsRequest(ServerContext* context, const NsProto* request,\n                   ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.NsCall(vid, request, reply);\n  }\n\n  Status QuotaRequest(ServerContext* context, const QuotaProto* request,\n                      ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.QuotaCall(vid, request, reply);\n  }\n\n  Status RecycleRequest(ServerContext* context, const RecycleProto* request,\n                        ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.RecycleCall(vid, request, reply);\n  }\n\n  Status RmRequest(ServerContext* context, const RmProto* request,\n                   ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.RmCall(vid, request, reply);\n  }\n\n  Status RmdirRequest(ServerContext* context, const RmdirProto* request,\n                      ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.RmdirCall(vid, request, reply);\n  }\n\n  Status RouteRequest(ServerContext* context, const RouteProto* request,\n                      ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.RouteCall(vid, request, reply);\n  }\n\n  Status SpaceRequest(ServerContext* context, const SpaceProto* request,\n                      ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.SpaceCall(vid, request, reply);\n  }\n\n  Status StatRequest(ServerContext* context, const StatProto* request,\n                     ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.StatCall(vid, request, reply);\n  }\n\n  Status StatusRequest(ServerContext* context, const StatusProto* request,\n                       ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.StatusCall(vid, request, reply);\n  }\n\n  Status TokenRequest(ServerContext* context, const TokenProto* request,\n                      ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.TokenCall(vid, request, reply);\n  }\n\n  Status TouchRequest(ServerContext* context, const TouchProto* request,\n                      ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.TouchCall(vid, request, reply);\n  }\n\n  Status VersionRequest(ServerContext* context, const VersionProto* request,\n                        ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.VersionCall(vid, request, reply);\n  }\n\n  Status VidRequest(ServerContext* context, const VidProto* request,\n                    ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.VidCall(vid, request, reply);\n  }\n\n  Status WhoRequest(ServerContext* context, const WhoProto* request,\n                    ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.WhoCall(vid, request, reply);\n  }\n\n  Status WhoamiRequest(ServerContext* context, const WhoamiProto* request,\n                       ReplyProto* reply) override\n  {\n    eos::common::VirtualIdentity vid;\n    GrpcRestGwServer::Vid(context, vid);\n    GrpcRestGwInterface restGwInterface;\n    return restGwInterface.WhoamiCall(vid, request, reply);\n  }\n};\n\n/* return client DN*/\nstd::string\nGrpcRestGwServer::DN(grpc::ServerContext* context)\n{\n  /*\n    The methods GetPeerIdentityPropertyName() and GetPeerIdentity() from grpc::ServerContext.auth_context\n    will prioritize SAN fields (x509_subject_alternative_name) in favor of x509_common_name\n  */\n  std::string tag = \"x509_common_name\";\n  auto resp = context->auth_context()->FindPropertyValues(tag);\n\n  if (resp.empty()) {\n    tag = \"x509_subject_alternative_name\";\n    auto resp = context->auth_context()->FindPropertyValues(tag);\n\n    if (resp.empty()) {\n      return \"\";\n    }\n  }\n\n  return resp[0].data();\n}\n\n/* return client IP */\nstd::string GrpcRestGwServer::IP(grpc::ServerContext* context, std::string* id,\n                                 std::string* port)\n{\n  // format is ipv4:<ip>:<..> or ipv6:<ip>:<..> - we just return the IP address\n  // but net and id are populated as well with the prefix and suffix, respectively\n  // The context peer information is curl encoded\n  const std::string decoded_peer =\n    eos::common::StringConversion::curl_default_unescaped(context->peer().c_str());\n  std::vector<std::string> tokens;\n  eos::common::StringConversion::Tokenize(decoded_peer, tokens, \"[]\");\n\n  if (tokens.size() == 3) {\n    if (id) {\n      *id = tokens[0].substr(0, tokens[0].size() - 1);\n    }\n\n    if (port) {\n      *port = tokens[2].substr(1, tokens[2].size() - 1);\n    }\n\n    return \"[\" + tokens[1] + \"]\";\n  } else {\n    tokens.clear();\n    eos::common::StringConversion::Tokenize(decoded_peer, tokens, \":\");\n\n    if (tokens.size() == 3) {\n      if (id) {\n        *id = tokens[0].substr(0, tokens[0].size());\n      }\n\n      if (port) {\n        *port = tokens[2].substr(0, tokens[2].size());\n      }\n\n      return tokens[1];\n    }\n\n    return \"\";\n  }\n}\n\n/* return VID for a given call */\nvoid\nGrpcRestGwServer::Vid(grpc::ServerContext* context,\n                      eos::common::VirtualIdentity& vid)\n{\n  static const std::string hdr_name = \"client-name\";\n  static const std::string hdr_tident = \"client-tident\";\n  static const std::string hdr_authz = \"client-authorization\";\n  const auto& map_hdrs = context->client_metadata();\n  std::string name_val, tident_val, authz_val;\n  XrdSecEntity client(\"https\");\n  // Populate client name\n  auto it = map_hdrs.find(hdr_name);\n\n  if (it != map_hdrs.end()) {\n    name_val = std::string(it->second.data(), it->second.length());\n    client.name = const_cast<char*>(name_val.c_str());\n  }\n\n  // Populate client tident\n  it = map_hdrs.find(hdr_tident);\n\n  if (it != map_hdrs.end()) {\n    tident_val = std::string(it->second.data(), it->second.length());\n    client.tident = const_cast<char*>(tident_val.c_str());\n  }\n\n  // Populate client endorsemetns if authz info present\n  it = map_hdrs.find(hdr_authz);\n\n  if (it != map_hdrs.end()) {\n    authz_val = std::string(it->second.data(), it->second.length());\n    client.endorsements = const_cast<char*>(authz_val.c_str());\n  }\n\n  eos::common::Mapping::IdMap(&client, \"eos.app=grpc\", client.tident, vid);\n}\n\n#endif // EOS_GRPC_GATEWAY\n\nvoid\nGrpcRestGwServer::Run(ThreadAssistant& assistant) noexcept\n{\n#ifdef EOS_GRPC_GATEWAY\n  EosRestGatewayServiceImpl service;\n  grpc::ServerBuilder builder;\n  // server bind address\n  std::string bind_address = \"0.0.0.0:\";\n  bind_address += std::to_string(mPort);\n  // gateway bind address\n  std::string gw_bind_address = \"0.0.0.0:\";\n  gw_bind_address += \"40054\";\n  builder.AddListeningPort(bind_address, grpc::InsecureServerCredentials());\n  builder.RegisterService(&service);\n  mRestGwServer = builder.BuildAndStart();\n  // spawn grpc gateway\n  char* const addr = const_cast<char*>(bind_address.c_str());\n  char* const gwaddr = const_cast<char*>(gw_bind_address.c_str());\n  char* path = (char*)\"../../../../protos/examplepb\";\n  char* network = (char*)\"tcp\";\n  const auto gatewayServer = SpawnGrpcGateway(gwaddr, network, addr, path);\n  eos_static_notice(\"%s\", \"msg=\\\"spawning GRPC GATEWAY, REST API available\\\"\");\n\n  if (mRestGwServer) {\n    mRestGwServer->Wait();\n  }\n\n  WaitForGrpcGateway(gatewayServer);\n#else\n  eos_static_notice(\"%s\", \"msg=\\\"no GRPC GATEWAY support, \"\n                    \"REST API unavailable\\\"\");\n  // Make the compiler happy\n  (void) mPort;\n  (void) mSSL;\n#endif\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/grpc/GrpcRestGwServer.hh",
    "content": "#pragma once\n\n#include \"mgm/Namespace.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/Mapping.hh\"\n#include \"common/Logging.hh\"\n#include \"GrpcRestGwInterface.hh\"\n\n#ifdef EOS_GRPC_GATEWAY\n#include <grpc++/grpc++.h>\n#endif // EOS_GRPC_GATEWAY\n\nEOSMGMNAMESPACE_BEGIN\n\n/**\n * @file   GrpcRestGwServer.hh\n *\n * @brief  This class implements a GRPC server with a grpc-gateway\n           for accessing all EOS console commands through HTTP requests\n */\nclass GrpcRestGwServer: public eos::common::LogId\n{\nprivate:\n  int mPort;\n  bool mSSL;\n  std::string mSSLCert;\n  std::string mSSLKey;\n  std::string mSSLCa;\n  std::string mSSLCertFile;\n  std::string mSSLKeyFile;\n  std::string mSSLCaFile;\n  AssistedThread mThread; // Thread running GRPC service\n\n#ifdef EOS_GRPC_GATEWAY\n  std::unique_ptr<grpc::Server> mRestGwServer;\n#endif /// EOS_GRPC_GATEWAY\n\npublic:\n\n  /* Default Constructor - enabling port 50054 by default\n   */\n  GrpcRestGwServer(int port = 50054) : mPort(port), mSSL(false) {  }\n\n  ~GrpcRestGwServer()\n  {\n#ifdef EOS_GRPC_GATEWAY\n\n    if (mRestGwServer) {\n      mRestGwServer->Shutdown();\n    }\n\n#endif // EOS_GRPC_GATEWAY\n    mThread.join();\n  }\n\n  /* Run function */\n  void Run(ThreadAssistant& assistant) noexcept;\n\n  /* Startup function */\n  void Start()\n  {\n    mThread.reset(&GrpcRestGwServer::Run, this);\n  }\n\n#ifdef EOS_GRPC_GATEWAY\n\n  /* return client DN*/\n  static std::string DN(grpc::ServerContext* context);\n  /* return client IP*/\n  static std::string IP(grpc::ServerContext* context, std::string* id = 0,\n                        std::string* port = 0);\n  /* return VID for a given call */\n  static void Vid(grpc::ServerContext* context,\n                  eos::common::VirtualIdentity& vid);\n\n#endif // EOS_GRPC_GATEWAY\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/grpc/GrpcServer.cc",
    "content": "// ----------------------------------------------------------------------\n// File: GrpcServer.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"GrpcServer.hh\"\n#include \"GrpcNsInterface.hh\"\n#include \"common/Logging.hh\"\n#include \"common/StringConversion.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include <XrdSec/XrdSecEntity.hh>\n\n#ifdef EOS_GRPC\n#include \"proto/Rpc.grpc.pb.h\"\n\nusing grpc::Server;\nusing grpc::ServerBuilder;\nusing grpc::ServerContext;\nusing grpc::ServerWriter;\nusing grpc::Status;\nusing eos::rpc::Eos;\nusing eos::rpc::PingRequest;\nusing eos::rpc::PingReply;\nusing eos::rpc::FileInsertRequest;\nusing eos::rpc::ContainerInsertRequest;\nusing eos::rpc::InsertReply;\n\n#endif\n\nEOSMGMNAMESPACE_BEGIN\n\n#ifdef EOS_GRPC\n\nstruct Rates {\n  double r_bps = 0;\n  double w_bps = 0;\n  double r_iops = 0;\n  double w_iops = 0;\n\n  // Helper for sorting/comparison\n  double\n  total_throughput() const\n  {\n    return r_bps + w_bps;\n  }\n\n  // Accumulate\n  void\n  add(const Rates& other)\n  {\n    r_bps += other.r_bps;\n    w_bps += other.w_bps;\n    r_iops += other.r_iops;\n    w_iops += other.w_iops;\n  }\n};\n\nRates\nExtractWindowRates(const eos::mgm::traffic_shaping::RateSnapshot& snap,\n                   eos::traffic_shaping::TrafficShapingRateRequest::Estimators estimator)\n{\n  // Helper to cleanly map our internal RateMetrics into the expected return struct\n  auto unpack = [](const eos::mgm::traffic_shaping::RateMetrics& m) -> Rates {\n    return {m.read_rate_bps, m.write_rate_bps, m.read_iops, m.write_iops};\n  };\n\n  using Request = eos::traffic_shaping::TrafficShapingRateRequest;\n  switch (estimator) {\n    // SMA\n  case Request::SMA_1_SECONDS:\n    return unpack(snap.sma[eos::mgm::traffic_shaping::Sma1s]);\n  case Request::SMA_5_SECONDS:\n    return unpack(snap.sma[eos::mgm::traffic_shaping::Sma5s]);\n  case Request::SMA_15_SECONDS:\n    return unpack(snap.sma[eos::mgm::traffic_shaping::Sma15s]);\n  case Request::SMA_1_MINUTES:\n    return unpack(snap.sma[eos::mgm::traffic_shaping::Sma1m]);\n  case Request::SMA_5_MINUTES:\n    return unpack(snap.sma[eos::mgm::traffic_shaping::Sma5m]);\n\n    // EMA\n  case Request::EMA_1_SECONDS:\n    return unpack(snap.ema[eos::mgm::traffic_shaping::Ema1s]);\n  case Request::EMA_5_SECONDS:\n    return unpack(snap.ema[eos::mgm::traffic_shaping::Ema5s]);\n\n  case Request::UNSPECIFIED:\n  default:\n    // Default fallback (1-minute SMA)\n    return unpack(snap.sma[eos::mgm::traffic_shaping::Sma1m]);\n  }\n}\n\nvoid\nBuildReport(const std::shared_ptr<traffic_shaping::TrafficShapingManager>& manager,\n            const eos::traffic_shaping::TrafficShapingRateRequest* request,\n            eos::traffic_shaping::TrafficShapingRateResponse* report)\n{\n  // Snapshot Global State so we don't have to hold locks while processing/sorting\n  auto global_stats = manager->GetGlobalStats();\n\n  const auto [estimator_mean, estimator_min, estimator_max] =\n      manager->GetEstimatorsUpdateLoopMicroSecStats();\n  const auto [fst_limits_mean, fst_limits_min, fst_limits_max] =\n      manager->GetFstLimitsUpdateLoopMicroSecStats();\n\n  auto* est_stats = report->mutable_estimators_update_thread_loop_stats();\n  est_stats->set_mean_elapsed_time_micro_sec(estimator_mean);\n  est_stats->set_min_elapsed_time_micro_sec(estimator_min);\n  est_stats->set_max_elapsed_time_micro_sec(estimator_max);\n\n  auto* fst_stats = report->mutable_fst_limits_update_thread_loop_stats();\n  fst_stats->set_mean_elapsed_time_micro_sec(fst_limits_mean);\n  fst_stats->set_min_elapsed_time_micro_sec(fst_limits_min);\n  fst_stats->set_max_elapsed_time_micro_sec(fst_limits_max);\n\n  int64_t now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(\n                       std::chrono::system_clock::now().time_since_epoch())\n                       .count();\n  report->set_timestamp_ms(now_ms);\n\n  bool do_uid = false, do_gid = false, do_app = false;\n  if (request->include_types_size() == 0) {\n    // Default: Include All if unspecified\n    do_uid = do_gid = do_app = true;\n  } else {\n    for (const auto type : request->include_types()) {\n      if (type == eos::traffic_shaping::TrafficShapingRateRequest::ENTITY_UID) {\n        do_uid = true;\n      }\n      if (type == eos::traffic_shaping::TrafficShapingRateRequest::ENTITY_GID) {\n        do_gid = true;\n      }\n      if (type == eos::traffic_shaping::TrafficShapingRateRequest::ENTITY_APP) {\n        do_app = true;\n      }\n    }\n  }\n\n  // Determine which estimators to calculate (e.g., 5s SMA, 1m EMA, etc.)\n  std::vector<eos::traffic_shaping::TrafficShapingRateRequest::Estimators> estimators;\n  if (request->estimators_size() == 0) {\n    estimators.push_back(eos::traffic_shaping::TrafficShapingRateRequest::SMA_5_SECONDS);\n  } else {\n    for (auto w : request->estimators()) {\n      if (w != eos::traffic_shaping::TrafficShapingRateRequest::UNSPECIFIED) {\n        estimators.push_back(\n            static_cast<eos::traffic_shaping::TrafficShapingRateRequest::Estimators>(w));\n      }\n    }\n  }\n\n  // Determine Sorting Window\n  // If user asks for [1s, 5m] but wants to sort by 5m trend, they set sort_by_window=5m.\n  // Default to the first window in the list.\n  eos::traffic_shaping::TrafficShapingRateRequest::Estimators sort_window = estimators[0];\n  if (request->has_sort_by_estimator() &&\n      request->sort_by_estimator() !=\n          eos::traffic_shaping::TrafficShapingRateRequest::UNSPECIFIED) {\n    sort_window = request->sort_by_estimator();\n  }\n\n  // ---------------------------------------------------------------------------\n  // 3. Aggregation Logic\n  // ---------------------------------------------------------------------------\n  // We need to store rates for ALL requested windows for each entity.\n  struct AggregatedEntity {\n    uint32_t active_streams = 0;\n    std::map<eos::traffic_shaping::TrafficShapingRateRequest::Estimators, Rates>\n        window_rates{};\n  };\n\n  std::map<uint32_t, AggregatedEntity> uid_agg;\n  std::map<uint32_t, AggregatedEntity> gid_agg;\n  std::map<std::string, AggregatedEntity> app_agg;\n\n  for (const auto& [key, snap] : global_stats) {\n    // Optimization: Calculate rates only for requested windows\n    for (auto win : estimators) {\n      Rates r = ExtractWindowRates(snap, win);\n\n      // Skip completely idle streams (micro-optimization)\n      // if (r.total_throughput() == 0 && r.r_iops == 0 && r.w_iops == 0) { continue; }\n\n      if (do_uid) {\n        auto& agg = uid_agg[key.uid];\n        agg.window_rates[win].add(r);\n        if (win == estimators[0]) {\n          agg.active_streams++; // Count once\n        }\n      }\n      if (do_gid) {\n        auto& agg = gid_agg[key.gid];\n        agg.window_rates[win].add(r);\n        if (win == estimators[0]) {\n          agg.active_streams++;\n        }\n      }\n      if (do_app) {\n        auto& agg = app_agg[key.app];\n        agg.window_rates[win].add(r);\n        if (win == estimators[0]) {\n          agg.active_streams++;\n        }\n      }\n    }\n  }\n\n  // Generic Lambda to process any map (UID, GID, or App)\n  auto process_stats = [&](const auto& source_map, auto add_entry_fn, auto set_id_fn) {\n    if (source_map.empty()) {\n      return;\n    }\n\n    // A. Map -> Vector (for sorting)\n    using PairType = typename std::decay_t<decltype(source_map)>::value_type;\n    std::vector<const PairType*> vec;\n    vec.reserve(source_map.size());\n    for (const auto& item : source_map) {\n      vec.push_back(&item);\n    }\n\n    // B. Sorter: Sort by 'sort_window' throughput\n    auto sorter = [&](const PairType* a, const PairType* b) {\n      double val_a = 0, val_b = 0;\n\n      // Safe lookup (rate might not exist for this specific window)\n      if (auto it = a->second.window_rates.find(sort_window);\n          it != a->second.window_rates.end()) {\n        val_a = it->second.total_throughput();\n      }\n      if (auto it = b->second.window_rates.find(sort_window);\n          it != b->second.window_rates.end()) {\n        val_b = it->second.total_throughput();\n      }\n      return val_a > val_b;\n    };\n\n    // C. Top N Selection\n    size_t n = vec.size();\n    if (request->has_top_n() && request->top_n() > 0) {\n      n = std::min(static_cast<size_t>(request->top_n()), n);\n      // Partial Sort is faster than full sort\n      std::partial_sort(vec.begin(), vec.begin() + n, vec.end(), sorter);\n    } else {\n      std::sort(vec.begin(), vec.end(), sorter);\n    }\n\n    // D. Populate Protobuf\n    for (size_t i = 0; i < n; ++i) {\n      auto* entry = add_entry_fn();    // e.g., report->add_uid_stats()\n      set_id_fn(entry, vec[i]->first); // e.g., entry->set_uid(1001)\n\n      // Add stats for ALL requested estimators\n      for (const auto& [estimator, rates] : vec[i]->second.window_rates) {\n        auto* s = entry->add_stats();\n        s->set_window(estimator);\n        s->set_bytes_read_per_sec(rates.r_bps);\n        s->set_bytes_written_per_sec(rates.w_bps);\n        s->set_iops_read(rates.r_iops);\n        s->set_iops_write(rates.w_iops);\n      }\n    }\n  };\n\n  // ---------------------------------------------------------------------------\n  // 5. Apply Logic to Each Entity Type\n  // ---------------------------------------------------------------------------\n\n  if (do_uid) {\n    process_stats(\n        uid_agg, [&]() { return report->add_user_stats(); },\n        [](auto* e, uint32_t id) { e->set_uid(id); });\n  }\n\n  if (do_gid) {\n    process_stats(\n        gid_agg, [&]() { return report->add_group_stats(); },\n        [](auto* e, uint32_t id) { e->set_gid(id); });\n  }\n\n  if (do_app) {\n    process_stats(\n        app_agg, [&]() { return report->add_app_stats(); },\n        [](auto* e, const std::string& id) { e->set_app_name(id); });\n  }\n}\n\nclass RequestServiceImpl final : public Eos::Service {\n  Status Ping(ServerContext* context, const eos::rpc::PingRequest* request,\n              eos::rpc::PingReply* reply) override\n  {\n    eos_static_info(\"grpc::ping from client peer=%s ip=%s DN=%s token=%s len=%lu\",\n                    context->peer().c_str(), GrpcServer::IP(context).c_str(),\n                    GrpcServer::DN(context).c_str(), request->authkey().c_str(),\n                    request->message().length());\n    eos::common::VirtualIdentity vid;\n    GrpcServer::Vid(context, vid, request->authkey());\n    reply->set_message(request->message());\n    return Status::OK;\n  }\n\n  Status FileInsert(ServerContext* context,\n                    const eos::rpc::FileInsertRequest* request,\n                    eos::rpc::InsertReply* reply) override\n  {\n    eos_static_info(\"grpc::fileinsert from client peer=%s ip=%s DN=%s token=%s\",\n                    context->peer().c_str(), GrpcServer::IP(context).c_str(),\n                    GrpcServer::DN(context).c_str(), request->authkey().c_str());\n    eos::common::VirtualIdentity vid;\n    GrpcServer::Vid(context, vid, request->authkey());\n    WAIT_BOOT;\n    return GrpcNsInterface::FileInsert(vid, reply, request);\n  }\n\n  Status ContainerInsert(ServerContext* context,\n                         const eos::rpc::ContainerInsertRequest* request,\n                         eos::rpc::InsertReply* reply) override\n  {\n    eos_static_info(\"grpc::containerinsert from client peer=%s ip=%s DN=%s token=%s\",\n                    context->peer().c_str(), GrpcServer::IP(context).c_str(),\n                    GrpcServer::DN(context).c_str(), request->authkey().c_str());\n    eos::common::VirtualIdentity vid;\n    GrpcServer::Vid(context, vid, request->authkey());\n    WAIT_BOOT;\n    return GrpcNsInterface::ContainerInsert(vid, reply, request);\n  }\n\n  Status MD(ServerContext* context, const eos::rpc::MDRequest* request,\n            ServerWriter<eos::rpc::MDResponse>* writer) override\n  {\n    eos_static_info(\"grpc::md from client peer=%s ip=%s DN=%s token=%s\",\n                    context->peer().c_str(), GrpcServer::IP(context).c_str(),\n                    GrpcServer::DN(context).c_str(), request->authkey().c_str());\n    eos::common::VirtualIdentity vid;\n    GrpcServer::Vid(context, vid, request->authkey());\n    WAIT_BOOT;\n\n    switch (request->type()) {\n    case eos::rpc::FILE:\n    case eos::rpc::CONTAINER:\n    case eos::rpc::STAT:\n      return GrpcNsInterface::Stat(vid, writer, request);\n      break;\n\n    case eos::rpc::LISTING:\n      return GrpcNsInterface::StreamMD(vid, writer, request);\n      break;\n\n    default:\n      ;\n    }\n\n    return Status(grpc::StatusCode::INVALID_ARGUMENT, \"request is not supported\");\n  }\n\n  Status Find(ServerContext* context, const eos::rpc::FindRequest* request,\n              ServerWriter<eos::rpc::MDResponse>* writer) override\n  {\n    eos_static_info(\"grpc::find from client peer=%s ip=%s DN=%s token=%s\",\n                    context->peer().c_str(), GrpcServer::IP(context).c_str(),\n                    GrpcServer::DN(context).c_str(), request->authkey().c_str());\n    eos::common::VirtualIdentity vid;\n    GrpcServer::Vid(context, vid, request->authkey());\n    WAIT_BOOT;\n    return GrpcNsInterface::Find(vid, writer, request);\n  }\n\n  Status NsStat(ServerContext* context,\n                const eos::rpc::NsStatRequest* request,\n                eos::rpc::NsStatResponse* reply) override\n  {\n    eos_static_info(\"grpc::nsstat::request from client peer=%s ip=%s DN=%s token=%s\",\n                    context->peer().c_str(), GrpcServer::IP(context).c_str(),\n                    GrpcServer::DN(context).c_str(), request->authkey().c_str());\n    eos::common::VirtualIdentity vid;\n    GrpcServer::Vid(context, vid, request->authkey());\n    WAIT_BOOT;\n    return GrpcNsInterface::NsStat(vid, reply, request);\n  }\n\n  Status Exec(ServerContext* context,\n              const eos::rpc::NSRequest* request,\n              eos::rpc::NSResponse* reply) override\n  {\n    eos_static_info(\"grpc::exec::request from client peer=%s ip=%s DN=%s \"\n                    \"token=%s req_type=%lu\", context->peer().c_str(),\n                    GrpcServer::IP(context).c_str(),\n                    GrpcServer::DN(context).c_str(),\n                    request->authkey().c_str(), request->command_case());\n    eos::common::VirtualIdentity vid;\n    GrpcServer::Vid(context, vid, request->authkey());\n    WAIT_BOOT;\n    return GrpcNsInterface::Exec(vid, reply, request);\n  }\n\n  Status\n  TrafficShapingRate(\n      ServerContext* context,\n      const eos::traffic_shaping::TrafficShapingRateRequest* request,\n      ServerWriter<eos::traffic_shaping::TrafficShapingRateResponse>* writer) override\n  {\n    eos_static_info(\"msg=\\\"Monitoring Stream Start\\\" peer=%s\", context->peer().c_str());\n\n    auto brain = gOFS->mTrafficShapingEngine.GetManager();\n\n    while (!context->IsCancelled()) {\n      auto start = std::chrono::steady_clock::now();\n\n      eos::traffic_shaping::TrafficShapingRateResponse report;\n      BuildReport(brain, request, &report);\n\n      if (!writer->Write(report)) {\n        break;\n      }\n\n      std::this_thread::sleep_until(start + std::chrono::milliseconds(100));\n    }\n    return grpc::Status::OK;\n  }\n};\n\n/* return client DN*/\nstd::string\nGrpcServer::DN(grpc::ServerContext* context)\n{\n  /*\n    The methods GetPeerIdentityPropertyName() and GetPeerIdentity() from grpc::ServerContext.auth_context\n    will prioritize SAN fields (x509_subject_alternative_name) in favor of x509_common_name\n  */\n  std::string tag = \"x509_common_name\";\n  auto resp = context->auth_context()->FindPropertyValues(tag);\n\n  if (resp.empty()) {\n    tag = \"x509_subject_alternative_name\";\n    auto resp = context->auth_context()->FindPropertyValues(tag);\n\n    if (resp.empty()) {\n      return \"\";\n    }\n  }\n\n  return resp[0].data();\n}\n\n\n\n/* return client IP */\nstd::string GrpcServer::IP(grpc::ServerContext* context, std::string* id,\n                           std::string* port)\n{\n  // format is ipv4:<ip>:<..> or ipv6:<ip>:<..> - we just return the IP address\n  // butq net and id are populated as well with the prefix and suffix, respectively\n  // The context peer information is curl encoded\n  const std::string decoded_peer =\n    eos::common::StringConversion::curl_default_unescaped(context->peer().c_str());\n  std::vector<std::string> tokens;\n  eos::common::StringConversion::Tokenize(decoded_peer, tokens, \"[]\");\n\n  if (tokens.size() == 3) {\n    if (id) {\n      *id = tokens[0].substr(0, tokens[0].size() - 1);\n    }\n\n    if (port) {\n      *port = tokens[2].substr(1, tokens[2].size() - 1);\n    }\n\n    return \"[\" + tokens[1] + \"]\";\n  } else {\n    tokens.clear();\n    eos::common::StringConversion::Tokenize(decoded_peer, tokens, \":\");\n\n    if (tokens.size() == 3) {\n      if (id) {\n        *id = tokens[0].substr(0, tokens[0].size());\n      }\n\n      if (port) {\n        *port = tokens[2].substr(0, tokens[2].size());\n      }\n\n      return tokens[1];\n    }\n\n    return \"\";\n  }\n}\n\n/* return VID for a given call */\nvoid\nGrpcServer::Vid(grpc::ServerContext* context,\n                eos::common::VirtualIdentity& vid,\n                const std::string& authkey)\n{\n  XrdSecEntity client(\"grpc\");\n  std::string dn = DN(context);\n  client.name = const_cast<char*>(dn.c_str());\n  bool isEosToken = (authkey.substr(0, 8) == \"zteos64:\");\n  std::string tident = dn.length() ? dn.c_str() : (isEosToken ? \"eostoken\" :\n                       authkey.c_str());\n  std::string id;\n  std::string ip = GrpcServer::IP(context, &id).c_str();\n  tident += \".1:\";\n  tident += id;\n  tident += \"@\";\n  tident += ip;\n  client.tident = tident.c_str();\n\n  if (authkey.length()) {\n    client.endorsements = const_cast<char*>(authkey.c_str());\n  }\n\n  eos::common::Mapping::IdMap(&client, \"eos.app=grpc\", client.tident, vid);\n}\n\n#endif\n\nvoid\nGrpcServer::Run(ThreadAssistant& assistant) noexcept\n{\n#ifdef EOS_GRPC\n\n  if (getenv(\"EOS_MGM_GRPC_SSL_CERT\") &&\n      getenv(\"EOS_MGM_GRPC_SSL_KEY\") &&\n      getenv(\"EOS_MGM_GRPC_SSL_CA\")) {\n    mSSL = true;\n    mSSLCertFile = getenv(\"EOS_MGM_GRPC_SSL_CERT\");\n    mSSLKeyFile = getenv(\"EOS_MGM_GRPC_SSL_KEY\");\n    mSSLCaFile = getenv(\"EOS_MGM_GRPC_SSL_CA\");\n\n    if (eos::common::StringConversion::LoadFileIntoString(mSSLCertFile.c_str(),\n        mSSLCert) && !mSSLCert.length()) {\n      eos_static_crit(\"unable to load ssl certificate file '%s'\",\n                      mSSLCertFile.c_str());\n      mSSL = false;\n    }\n\n    if (eos::common::StringConversion::LoadFileIntoString(mSSLKeyFile.c_str(),\n        mSSLKey) && !mSSLKey.length()) {\n      eos_static_crit(\"unable to load ssl key file '%s'\", mSSLKeyFile.c_str());\n      mSSL = false;\n    }\n\n    if (eos::common::StringConversion::LoadFileIntoString(mSSLCaFile.c_str(),\n        mSSLCa) && !mSSLCa.length()) {\n      eos_static_crit(\"unable to load ssl ca file '%s'\", mSSLCaFile.c_str());\n      mSSL = false;\n    }\n  }\n\n  int selected_port = 0;\n  RequestServiceImpl service;\n  std::string bind_address = \"0.0.0.0:\";\n  bind_address += std::to_string(mPort);\n  grpc::ServerBuilder builder;\n\n  if (mSSL) {\n    grpc_ssl_client_certificate_request_type gsccrt =\n      GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY;\n\n    if (getenv(\"EOS_MGM_GRPC_DONT_REQUEST_CLIENT_CERTIFICATE\")) {\n      gsccrt = GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE;\n    }\n\n    grpc::SslServerCredentialsOptions::PemKeyCertPair keycert = {\n      mSSLKey,\n      mSSLCert\n    };\n    grpc::SslServerCredentialsOptions sslOps(gsccrt);\n    sslOps.pem_root_certs = mSSLCa;\n    sslOps.pem_key_cert_pairs.push_back(keycert);\n    builder.AddListeningPort(bind_address, grpc::SslServerCredentials(sslOps),\n                             &selected_port);\n  } else {\n    builder.AddListeningPort(bind_address, grpc::InsecureServerCredentials());\n  }\n\n  builder.RegisterService(&service);\n  mServer = builder.BuildAndStart();\n\n  if (mSSL && (selected_port == 0)) {\n    eos_static_err(\"msg=\\\"server failed to bind to port with SSL, \"\n                   \"port %i is taken or certs not valid\\\"\", mPort);\n    return;\n  }\n\n  if (mServer) {\n    eos_static_info(\"msg=\\\"gRPC server for EOS is running\\\" port=%i.\", mPort);\n    mServer->Wait();\n  }\n\n#else\n  // Make the compiler happy\n  (void) mPort;\n  (void) mSSL;\n#endif\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/grpc/GrpcServer.hh",
    "content": "// ----------------------------------------------------------------------\n// File: GrpcServer.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/AssistedThread.hh\"\n#include \"common/Mapping.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"mgm/shaping/TrafficShaping.hh\"\n#ifdef EOS_GRPC\n#include <grpc++/grpc++.h>\n#endif\n\nEOSMGMNAMESPACE_BEGIN\n\n/**\n * @file   GrpcServer.hh\n *\n * @brief  This class implements a gRPC server running embedded in the MGM\n *\n */\nclass GrpcServer\n{\nprivate:\n  int mPort;\n  bool mSSL;\n  std::string mSSLCert;\n  std::string mSSLKey;\n  std::string mSSLCa;\n  std::string mSSLCertFile;\n  std::string mSSLKeyFile;\n  std::string mSSLCaFile;\n\n#ifdef EOS_GRPC\n  std::unique_ptr<grpc::Server> mServer;\n#endif\n  AssistedThread mThread; ///< Thread running GRPC service\n\n  std::shared_ptr<eos::mgm::traffic_shaping::TrafficShapingManager>\n      mTrafficShapingManager;\n\npublic:\n\n  /* Default Constructor - enabling port 50051 by default\n   */\n  GrpcServer(int port = 50051) : mPort(port), mSSL(false) { }\n\n  virtual ~GrpcServer()\n  {\n#ifdef EOS_GRPC\n\n    if (mServer) {\n      mServer->Shutdown();\n    }\n\n#endif\n    mThread.join();\n  }\n\n  /* Run function */\n  void Run(ThreadAssistant& assistant) noexcept;\n\n  /* Startup function */\n  void Start()\n  {\n    mThread.reset(&GrpcServer::Run, this);\n  }\n\n#ifdef EOS_GRPC\n\n  /* return client DN*/\n  static std::string DN(grpc::ServerContext* context);\n  /* return client IP*/\n  static std::string IP(grpc::ServerContext* context, std::string* id = 0,\n                        std::string* port = 0);\n  /* return VID for a given call */\n  static void Vid(grpc::ServerContext* context,\n                  eos::common::VirtualIdentity& vid,\n                  const std::string& authkey);\n\n#endif\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/grpc/GrpcWncInterface.cc",
    "content": "//-----------------------------------------------------------------------------\n// File: GrpcWncInterface.cc\n// Author: Branko Blagojevic <branko.blagojevic@comtrade.com>\n// Author: Ivan Arizanovic <ivan.arizanovic@comtrade.com>\n//-----------------------------------------------------------------------------\n\n#ifdef EOS_GRPC\n\n#include \"GrpcWncInterface.hh\"\n#include \"common/Fmd.hh\"\n#include \"common/Utils.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/json/Json.hh\"\n#include \"console/commands/HealthCommand.hh\"\n#include \"console/ConsoleMain.hh\"\n#include \"mgm/acl/Acl.hh\"\n#include \"mgm/egroup/Egroup.hh\"\n#include \"mgm/geotreeengine/GeoTreeEngine.hh\"\n#include \"mgm/proc/admin/AccessCmd.hh\"\n#include \"mgm/proc/admin/ConfigCmd.hh\"\n#include \"mgm/proc/admin/ConvertCmd.hh\"\n#include \"mgm/proc/admin/DebugCmd.hh\"\n#include \"mgm/proc/admin/FsCmd.hh\"\n#include \"mgm/proc/admin/FsckCmd.hh\"\n#include \"mgm/proc/admin/GroupCmd.hh\"\n#include \"mgm/proc/admin/IoCmd.hh\"\n#include \"mgm/proc/admin/NodeCmd.hh\"\n#include \"mgm/proc/admin/NsCmd.hh\"\n#include \"mgm/proc/admin/QuotaCmd.hh\"\n#include \"mgm/proc/admin/SpaceCmd.hh\"\n#include \"mgm/proc/admin/EvictCmd.hh\"\n#include \"mgm/proc/user/AclCmd.hh\"\n#include \"mgm/proc/user/NewfindCmd.hh\"\n#include \"mgm/proc/user/RecycleCmd.hh\"\n#include \"mgm/proc/user/RmCmd.hh\"\n#include \"mgm/proc/user/RouteCmd.hh\"\n#include \"mgm/proc/user/TokenCmd.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/IView.hh\"\n#include <XrdPosix/XrdPosixXrootd.hh>\n\nEOSMGMNAMESPACE_BEGIN\n\ngrpc::Status\nGrpcWncInterface::ExecCmd(eos::common::VirtualIdentity& vid,\n                          const eos::console::RequestProto* request,\n                          eos::console::ReplyProto* reply)\n{\n  mVid = &vid;\n  mRequest = request;\n  mReply = reply;\n  mJsonFormat = (mRequest->format() ==\n                 eos::console::RequestProto_FormatType_JSON);\n  RoleChanger();\n\n  switch (mRequest->command_case()) {\n  case eos::console::RequestProto::kAccess:\n    return Access();\n    break;\n\n  case eos::console::RequestProto::kAcl:\n    return Acl();\n    break;\n\n  case eos::console::RequestProto::kArchive:\n    return Archive();\n    break;\n\n  case eos::console::RequestProto::kAttr:\n    return Attr();\n    break;\n\n  case eos::console::RequestProto::kBackup:\n    return Backup();\n    break;\n\n  case eos::console::RequestProto::kChmod:\n    return Chmod();\n    break;\n\n  case eos::console::RequestProto::kChown:\n    return Chown();\n    break;\n\n  case eos::console::RequestProto::kConfig:\n    return Config();\n    break;\n\n  case eos::console::RequestProto::kConvert:\n    return Convert();\n    break;\n\n  case eos::console::RequestProto::kCp:\n    return Cp();\n    break;\n\n  case eos::console::RequestProto::kDebug:\n    return Debug();\n    break;\n\n  case eos::console::RequestProto::kEvict:\n    return Evict();\n    break;\n\n  case eos::console::RequestProto::kFile:\n    return File();\n    break;\n\n  case eos::console::RequestProto::kFileinfo:\n    return Fileinfo();\n    break;\n\n  case eos::console::RequestProto::kFs:\n    return Fs();\n    break;\n\n  case eos::console::RequestProto::kFsck:\n    return Fsck();\n    break;\n\n  case eos::console::RequestProto::kGeosched:\n    return Geosched();\n    break;\n\n  case eos::console::RequestProto::kGroup:\n    return Group();\n    break;\n\n  case eos::console::RequestProto::kHealth: {\n    for (auto it : mVid->allowed_uids)\n      if ((it == 0 && mVid->uid == 0) || it == 2 || it == 3) {\n        return Health();\n      }\n\n    mReply->set_std_err(\"Error: Permission denied\");\n    mReply->set_retc(EACCES);\n    break;\n  }\n\n  case eos::console::RequestProto::kIo:\n    return Io();\n    break;\n\n  case eos::console::RequestProto::kMap:\n    return Map();\n    break;\n\n  case eos::console::RequestProto::kMember:\n    return Member();\n    break;\n\n  case eos::console::RequestProto::kMkdir:\n    return Mkdir();\n    break;\n\n  case eos::console::RequestProto::kMv:\n    return Mv();\n    break;\n\n  case eos::console::RequestProto::kNode:\n    return Node();\n    break;\n\n  case eos::console::RequestProto::kNs:\n    return Ns();\n    break;\n\n  case eos::console::RequestProto::kQuota:\n    return Quota();\n    break;\n\n  case eos::console::RequestProto::kRecycle:\n    return Recycle();\n    break;\n\n  case eos::console::RequestProto::kRm:\n    return Rm();\n    break;\n\n  case eos::console::RequestProto::kRmdir:\n    return Rmdir();\n    break;\n\n  case eos::console::RequestProto::kRoute:\n    return Route();\n    break;\n\n  case eos::console::RequestProto::kSpace:\n    return Space();\n    break;\n\n  case eos::console::RequestProto::kStat:\n    return Stat();\n    break;\n\n  case eos::console::RequestProto::kStatus:\n    return Status();\n    break;\n\n  case eos::console::RequestProto::kToken:\n    return Token();\n    break;\n\n  case eos::console::RequestProto::kTouch:\n    return Touch();\n    break;\n\n  case eos::console::RequestProto::kVersion:\n    return Version();\n    break;\n\n  case eos::console::RequestProto::kVid: {\n    for (auto it : mVid->allowed_uids)\n      if ((it == 0 && mVid->uid == 0) || it == 2 || it == 3) {\n        return Vid();\n      }\n\n    mReply->set_std_err(\"Error: Permission denied\");\n    mReply->set_retc(EACCES);\n    break;\n  }\n\n  case eos::console::RequestProto::kWho:\n    return Who();\n    break;\n\n  case eos::console::RequestProto::kWhoami:\n    return Whoami();\n    break;\n\n  default:\n    mReply->set_std_err(\"error: command not supported\");\n    mReply->set_retc(EINVAL);\n    break;\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status\nGrpcWncInterface::ExecStreamCmd(eos::common::VirtualIdentity& vid,\n                                const eos::console::RequestProto* request,\n                                ServerWriter<eos::console::ReplyProto>* writer)\n{\n  mVid = &vid;\n  mRequest = request;\n  mWriter = writer;\n  mJsonFormat = (mRequest->format() ==\n                 eos::console::RequestProto_FormatType_JSON);\n  grpc::Status retc;\n  RoleChanger();\n\n  switch (mRequest->command_case()) {\n  case eos::console::RequestProto::kFind:\n    retc = Find();\n    break;\n\n  case eos::console::RequestProto::kLs:\n    retc = Ls();\n    break;\n\n  default:\n    retc = grpc::Status::OK;\n    break;\n  }\n\n  return retc;\n}\n\nvoid GrpcWncInterface::RoleChanger()\n{\n  int errc = 0;\n  uid_t uid;\n  gid_t gid;\n\n  // Change the user role ID\n  if (!mRequest->auth().role().username().empty()) {\n    uid = eos::common::Mapping::UserNameToUid(mRequest->auth().role().username(),\n          errc);\n  } else if (mRequest->auth().role().uid() != 0) {\n    uid = mRequest->auth().role().uid();\n  } else {\n    uid = mVid->uid;\n  }\n\n  if (mVid->uid != uid) {\n    bool is_member = false;\n\n    for (auto it : mVid->allowed_uids)\n      if (it == uid) {\n        mVid->uid = uid;\n        is_member = true;\n        break;\n      }\n\n    if (!is_member) {\n      if (mVid->sudoer) {\n        mVid->uid = uid;\n        mVid->allowed_uids.insert(uid);\n      } else {\n        mVid->uid = 99;\n      }\n    }\n  }\n\n  // Change the group role ID\n  if (!mRequest->auth().role().groupname().empty()) {\n    gid = eos::common::Mapping::GroupNameToGid(mRequest->auth().role().groupname(),\n          errc);\n  } else if (mRequest->auth().role().gid() != 0) {\n    gid = mRequest->auth().role().gid();\n  } else {\n    gid = mVid->gid;\n  }\n\n  if (mVid->gid != gid) {\n    bool is_member = false;\n\n    for (auto it : mVid->allowed_gids)\n      if (it == gid) {\n        mVid->gid = gid;\n        is_member = true;\n        break;\n      }\n\n    if (!is_member) {\n      if (mVid->sudoer) {\n        mVid->gid = gid;\n        mVid->allowed_gids.insert(gid);\n      } else {\n        mVid->gid = 99;\n      }\n    }\n  }\n}\n\n\nvoid GrpcWncInterface::ExecProcCmd(std::string input, bool admin)\n{\n  ProcCommand cmd;\n  XrdOucErrInfo error;\n  std::string std_out, std_err;\n\n  if (mJsonFormat) {\n    input += \"&mgm.format=json\";\n  }\n\n  if (admin) {\n    cmd.open(\"/proc/admin\", input.c_str(), *mVid, &error);\n  } else {\n    cmd.open(\"/proc/user\", input.c_str(), *mVid, &error);\n  }\n\n  cmd.close();\n  cmd.AddOutput(std_out, std_err);\n\n  if (mJsonFormat) {\n    std_out = cmd.GetStdJson();\n  }\n\n  mReply->set_std_out(std_out);\n  mReply->set_std_err(std_err);\n  mReply->set_retc(cmd.GetRetc());\n}\n\ngrpc::Status GrpcWncInterface::Access()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::AccessCmd accesscmd(std::move(req), *mVid);\n  *mReply = accesscmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Acl()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::AclCmd aclcmd(std::move(req), *mVid);\n  *mReply = aclcmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Archive()\n{\n  std::string subcmd = mRequest->archive().command();\n  std::string cmd_in = \"mgm.cmd=archive&mgm.subcmd=\" + subcmd;\n\n  if (subcmd == \"kill\") {\n    cmd_in += \"&mgm.archive.option=\" + mRequest->archive().job_uuid();\n  } else if (subcmd == \"transfers\") {\n    cmd_in += \"&mgm.archive.option=\" + mRequest->archive().selection();\n  } else {\n    if (mRequest->archive().retry()) {\n      cmd_in += \"&mgm.archive.option=r\";\n    }\n\n    cmd_in += \"&mgm.archive.path=\" + mRequest->archive().path();\n  }\n\n  ExecProcCmd(cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Attr()\n{\n  std::string cmd_in;\n  std::string path = mRequest->attr().md().path();\n  eos::console::AttrCmd subcmd = mRequest->attr().cmd();\n  std::string key = mRequest->attr().key();\n  errno = 0;\n\n  if (path.empty()) {\n    if (mRequest->attr().md().type() == eos::console::FILE) {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(\n                 gOFS->eosFileService->getFileMD(mRequest->attr().md().id()).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    } else {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(\n                 gOFS->eosDirectoryService->getContainerMD(mRequest->attr().md().id()).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    }\n\n    if (path.empty()) {\n      mReply->set_std_err(\"error:path is empty\");\n      mReply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n  }\n\n  cmd_in = \"mgm.cmd=attr&mgm.path=\" + path;\n\n  if (subcmd == eos::console::AttrCmd::ATTR_LS) {\n    cmd_in += \"&mgm.subcmd=ls\";\n  } else if (subcmd == eos::console::AttrCmd::ATTR_SET) {\n    cmd_in += \"&mgm.subcmd=set\";\n    std::string value = mRequest->attr().value();\n\n    // Set default values based on layout\n    if (key == \"default\") {\n      std::vector<std::string> val;\n\n      if (value == \"replica\")\n        val = {\"4k\", \"adler\", \"replica\", \"2\", \"default\"};\n      else if (value == \"raiddp\")\n        val = {\"1M\", \"adler\", \"raiddp\", \"6\", \"default\", \"crc32c\"};\n      else if (value == \"raid5\")\n        val = {\"1M\", \"adler\", \"raid5\", \"5\", \"default\", \"crc32c\"};\n      else if (value == \"raid6\")\n        val = {\"1M\", \"adler\", \"raid6\", \"6\", \"default\", \"crc32c\"};\n      else if (value == \"archive\")\n        val = {\"1M\", \"adler\", \"archive\", \"8\", \"default\", \"crc32c\"};\n      else if (value == \"qrain\")\n        val = {\"1M\", \"adler\", \"qrain\", \"12\", \"default\", \"crc32c\"};\n      else {\n        mReply->set_std_err(\"Error: Value are not allowed\");\n        mReply->set_retc(EINVAL);\n        return grpc::Status::OK;\n      }\n\n      ProcCommand cmd;\n      XrdOucErrInfo error;\n      std::string set_def;\n      set_def = cmd_in + \"&mgm.attr.key=sys.forced.blocksize&mgm.attr.value=\" +\n                val[0];\n      cmd.open(\"/proc/user\", set_def.c_str(), *mVid, &error);\n      set_def = cmd_in + \"&mgm.attr.key=sys.forced.checksum&mgm.attr.value=\" + val[1];\n      cmd.open(\"/proc/user\", set_def.c_str(), *mVid, &error);\n      set_def = cmd_in + \"&mgm.attr.key=sys.forced.layout&mgm.attr.value=\" + val[2];\n      cmd.open(\"/proc/user\", set_def.c_str(), *mVid, &error);\n      set_def = cmd_in + \"&mgm.attr.key=sys.forced.nstripes&mgm.attr.value=\" + val[3];\n      cmd.open(\"/proc/user\", set_def.c_str(), *mVid, &error);\n      set_def = cmd_in + \"&mgm.attr.key=sys.forced.space&mgm.attr.value=\" + val[4];\n      cmd.open(\"/proc/user\", set_def.c_str(), *mVid, &error);\n\n      if (value != \"replica\") {\n        set_def = cmd_in + \"&mgm.attr.key=sys.forced.blockchecksum&mgm.attr.value=\" +\n                  val[5];\n        cmd.open(\"/proc/user\", set_def.c_str(), *mVid, &error);\n      }\n    }\n\n    if (key == \"sys.forced.placementpolicy\" ||\n        key == \"user.forced.placementpolicy\") {\n      std::string policy;\n      eos::common::SymKey::DeBase64(value, policy);\n\n      // Check placement policy\n      if (policy != \"scattered\" &&\n          policy.rfind(\"hybrid:\", 0) != 0 &&\n          policy.rfind(\"gathered:\", 0) != 0) {\n        mReply->set_std_err(\"Error: placement policy '\" + policy + \"' is invalid\\n\");\n        mReply->set_retc(EINVAL);\n        return grpc::Status::OK;\n      }\n\n      // Check geotag in case of hybrid or gathered policy\n      if (policy != \"scattered\") {\n        std::string targetgeotag = policy.substr(policy.find(':') + 1);\n        std::string tmp_geotag = eos::common::SanitizeGeoTag(targetgeotag);\n\n        if (tmp_geotag != targetgeotag) {\n          mReply->set_std_err(tmp_geotag);\n          mReply->set_retc(EINVAL);\n          return grpc::Status::OK;\n        }\n      }\n    }\n\n    cmd_in += \"&mgm.attr.key=\" + key;\n    cmd_in += \"&mgm.attr.value=\" + value;\n  } else if (subcmd == eos::console::AttrCmd::ATTR_GET) {\n    cmd_in += \"&mgm.subcmd=get\";\n    cmd_in += \"&mgm.attr.key=\" + key;\n  } else if (subcmd == eos::console::AttrCmd::ATTR_RM) {\n    cmd_in += \"&mgm.subcmd=rm\";\n    cmd_in += \"&mgm.attr.key=\" + key;\n  } else if (subcmd == eos::console::AttrCmd::ATTR_LINK) {\n    cmd_in += \"&mgm.subcmd=set\";\n    cmd_in += \"&mgm.attr.key=sys.attr.link\";\n    cmd_in += \"&mgm.attr.value=\" + mRequest->attr().link();\n  } else if (subcmd == eos::console::AttrCmd::ATTR_UNLINK) {\n    cmd_in += \"&mgm.subcmd=rm\";\n    cmd_in += \"&mgm.attr.key=sys.attr.link\";\n  } else if (subcmd == eos::console::AttrCmd::ATTR_FOLD) {\n    cmd_in += \"&mgm.subcmd=fold\";\n  }\n\n  if (mRequest->attr().recursive()) {\n    cmd_in += \"&mgm.option=r\";\n  }\n\n  ExecProcCmd(cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Backup()\n{\n  std::string src = mRequest->backup().src_url();\n  std::string dst = mRequest->backup().dst_url();\n  XrdCl::URL src_url(src.c_str()), dst_url(dst.c_str());\n\n  // Check that source is valid XRootD URL\n  if (!src_url.IsValid()) {\n    mReply->set_std_err(\"Error: Source is not valid XRootD URL: \" + src);\n    mReply->set_retc(EINVAL);\n    return grpc::Status::OK;\n  }\n\n  // Check that destination is valid XRootD URL\n  if (!dst_url.IsValid()) {\n    mReply->set_std_err(\"Error: Destination is not valid XRootD URL: \" + dst);\n    mReply->set_retc(EINVAL);\n    return grpc::Status::OK;\n  }\n\n  std::string cmd_in = \"mgm.cmd=backup&mgm.backup.src=\" + src + \"&mgm.backup.dst=\"\n                       + dst;\n\n  if (mRequest->backup().ctime()) {\n    struct timeval tv;\n\n    if (gettimeofday(&tv, NULL)) {\n      mReply->set_std_err(\"Error: Failed getting current timestamp\");\n      mReply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n\n    cmd_in += \"&mgm.backup.ttime=ctime&mgm.backup.vtime=\" + std::to_string(\n                tv.tv_sec - mRequest->backup().ctime());\n  }\n\n  if (mRequest->backup().mtime()) {\n    struct timeval tv;\n\n    if (gettimeofday(&tv, NULL)) {\n      mReply->set_std_err(\"Error: Failed getting current timestamp\");\n      mReply->set_retc(errno);\n      return grpc::Status::OK;\n    }\n\n    cmd_in += \"&mgm.backup.ttime=mtime&mgm.backup.vtime=\" + std::to_string(\n                tv.tv_sec - mRequest->backup().mtime());\n  }\n\n  if (!mRequest->backup().xattr().empty()) {\n    cmd_in += \"&mgm.backup.excl_xattr=\" + mRequest->backup().xattr();\n  }\n\n  ExecProcCmd(cmd_in);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Chmod()\n{\n  std::string cmd_in;\n  std::string path = mRequest->chmod().md().path();\n  errno = 0;\n\n  if (path.empty()) {\n    if (mRequest->chmod().md().type() == eos::console::FILE) {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(\n                                       mRequest->chmod().md().id()\n                                     ).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    } else {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(\n                                       mRequest->chmod().md().id()\n                                     ).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    }\n\n    if (path.empty()) {\n      mReply->set_std_err(\"error:path is empty\");\n      mReply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n  }\n\n  cmd_in = \"mgm.cmd=chmod\";\n  cmd_in += \"&mgm.path=\" + path;\n  cmd_in += \"&mgm.chmod.mode=\" + std::to_string(mRequest->chmod().mode());\n\n  if (mRequest->chmod().recursive()) {\n    cmd_in += \"&mgm.option=r\";\n  }\n\n  ExecProcCmd(cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Chown()\n{\n  std::string path = mRequest->chown().md().path();\n  uid_t uid = mRequest->chown().owner().uid();\n  gid_t gid = mRequest->chown().owner().gid();\n  std::string username = mRequest->chown().owner().username();\n  std::string groupname = mRequest->chown().owner().groupname();\n  errno = 0;\n  std::string cmd_in = \"mgm.cmd=chown\";\n\n  if (path.empty()) {\n    if (mRequest->chown().md().type() == eos::console::FILE) {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(\n                                       mRequest->chown().md().id()\n                                     ).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    } else {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(\n                                       mRequest->chown().md().id()\n                                     ).get());\n      } catch (eos::MDException& e) {\n        path = \"\";\n        errno = e.getErrno();\n      }\n    }\n\n    if (path.empty()) {\n      mReply->set_std_err(\"error:path is empty\");\n      mReply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n  }\n\n  cmd_in += \"&mgm.path=\" + path;\n\n  if (mRequest->chown().user_only() ||\n      mRequest->chown().user_only() == mRequest->chown().group_only()) {\n    if (!username.empty()) {\n      cmd_in += \"&mgm.chown.owner=\" + username;\n    } else {\n      cmd_in += \"&mgm.chown.owner=\" + std::to_string(uid);\n    }\n  }\n\n  if (mRequest->chown().group_only() ||\n      mRequest->chown().user_only() == mRequest->chown().group_only()) {\n    if (!groupname.empty()) {\n      cmd_in += \":\" + groupname;\n    } else {\n      cmd_in += \":\" + std::to_string(gid);\n    }\n  }\n\n  if (mRequest->chown().recursive() || mRequest->chown().nodereference()) {\n    cmd_in += \"&mgm.chown.option=\";\n\n    if (mRequest->chown().recursive()) {\n      cmd_in += \"r\";\n    }\n\n    if (mRequest->chown().nodereference()) {\n      cmd_in += \"h\";\n    }\n  }\n\n  ExecProcCmd(cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Config()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::ConfigCmd configcmd(std::move(req), *mVid);\n  *mReply = configcmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Convert()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::ConvertCmd convertcmd(std::move(req), *mVid);\n  *mReply = convertcmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Cp()\n{\n  switch (mRequest->cp().subcmd_case()) {\n  case eos::console::CpProto::kCksum: {\n    XrdCl::URL url(\"root://localhost//dummy\");\n    auto* fs = new XrdCl::FileSystem(url);\n\n    if (!fs) {\n      mReply->set_std_err(\"Warning: failed to get new FS object [attempting checksum]\\n\");\n      return grpc::Status::OK;\n    }\n\n    std::string path = mRequest->cp().cksum().path();\n    size_t pos = path.rfind(\"//\");\n\n    if (pos != std::string::npos) {\n      path.erase(0, pos + 1);\n    }\n\n    XrdCl::Buffer arg;\n    XrdCl::XRootDStatus status;\n    XrdCl::Buffer* response = nullptr;\n    arg.FromString(path);\n    status = fs->Query(XrdCl::QueryCode::Checksum, arg, response);\n\n    if (status.IsOK()) {\n      XrdOucString xsum = response->GetBuffer();\n      xsum.replace(\"eos \", \"\");\n      std::string msg = \"checksum=\";\n      msg += xsum.c_str();\n      mReply->set_std_out(msg);\n    } else {\n      std::string msg = \"Warning: failed getting checksum for \";\n      msg += path;\n      mReply->set_std_err(msg);\n    }\n\n    delete response;\n    delete fs;\n    break;\n  }\n\n  case eos::console::CpProto::kKeeptime: {\n    if (mRequest->cp().keeptime().set()) {\n      // Set atime and mtime\n      std::string path = mRequest->cp().keeptime().path();\n      char update[1024];\n      sprintf(update,\n              \"?eos.app=eoscp&mgm.pcmd=utimes&tv1_sec=%llu&tv1_nsec=%llu&tv2_sec=%llu&tv2_nsec=%llu\",\n              (unsigned long long) mRequest->cp().keeptime().atime().seconds(),\n              (unsigned long long) mRequest->cp().keeptime().atime().nanos(),\n              (unsigned long long) mRequest->cp().keeptime().mtime().seconds(),\n              (unsigned long long) mRequest->cp().keeptime().mtime().nanos()\n             );\n      XrdOucString query = \"root://localhost/\";\n      query += path.c_str();\n      query += update;\n      char value[4096];\n      value[0] = 0;\n      long long update_rc = XrdPosixXrootd::QueryOpaque(query.c_str(),\n                            value, 4096);\n      bool updateok = (update_rc >= 0);\n\n      if (updateok) {\n        // Parse the stat output\n        char tag[1024];\n        int tmp_retc;\n        int items = sscanf(value, \"%1023s retc=%d\", tag, &tmp_retc);\n        updateok = ((items == 2) && (strcmp(tag, \"utimes:\") == 0));\n      }\n\n      if (!updateok) {\n        std::string msg;\n        msg += \"Warning: access and modification time could not be preserved for \";\n        msg += path;\n        msg += \"\\nQuery: \";\n        msg += query.c_str();\n        mReply->set_std_err(msg);\n      }\n    } else {\n      // Get atime and mtime\n      std::string path = mRequest->cp().keeptime().path();\n      XrdOucString url = \"root://localhost/\";\n      url += path.c_str();\n      struct stat buf;\n\n      if (XrdPosixXrootd::Stat(url.c_str(), &buf) == 0) {\n        std::string msg;\n        msg += \"atime:\";\n        msg += std::to_string(buf.st_atime);\n        msg += \"mtime:\";\n        msg += std::to_string(buf.st_mtime);\n        mReply->set_std_out(msg);\n      } else {\n        std::string msg = \"Warning: failed getting stat information for \";\n        msg += path;\n        mReply->set_std_err(msg);\n      }\n    }\n\n    break;\n  }\n\n  default: {\n    mReply->set_std_err(\"Error: subcommand is not supported\");\n    mReply->set_retc(EINVAL);\n  }\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Debug()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::DebugCmd debugcmd(std::move(req), *mVid);\n  *mReply = debugcmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Evict()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::EvictCmd evictcmd(std::move(req), *mVid);\n  *mReply = evictcmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\nbool\nFile_EnvFstToFmd(XrdOucEnv& env, eos::common::FmdHelper& fmd);\n\nint\nFile_GetRemoteAttribute(const char* manager, const char* key,\n                        const char* path, XrdOucString& attribute);\n\nint\nFile_GetRemoteFmdFromLocalDb(const char* manager, const char* shexfid,\n                             const char* sfsid, eos::common::FmdHelper& fmd);\n\ngrpc::Status GrpcWncInterface::File()\n{\n  std::string path = mRequest->file().md().path();\n  uint64_t fid = 0;\n\n  if (path.empty() &&\n      mRequest->file().FileCommand_case() != eos::console::FileProto::kSymlink) {\n    // get by inode\n    if (mRequest->file().md().ino()) {\n      fid = eos::common::FileId::InodeToFid(mRequest->file().md().ino());\n    }\n    // get by fileid\n    else if (mRequest->file().md().id()) {\n      fid = mRequest->file().md().id();\n    }\n\n    try {\n      eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n      path = gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(fid).get());\n    } catch (eos::MDException& e) {\n      path = \"\";\n      errno = e.getErrno();\n    }\n  }\n\n  if (path.empty()) {\n    mReply->set_std_err(\"error: path is empty\");\n    mReply->set_retc(EINVAL);\n    return grpc::Status::OK;\n  }\n\n  std::string std_out, std_err;\n  ProcCommand cmd;\n  XrdOucErrInfo error;\n  std::string cmd_in = \"mgm.cmd=file\";\n\n  switch (mRequest->file().FileCommand_case()) {\n  case eos::console::FileProto::kAdjustreplica: {\n    cmd_in += \"&mgm.subcmd=adjustreplica\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    if (!mRequest->file().adjustreplica().space().empty()) {\n      cmd_in += \"&mgm.file.desiredspace=\" + mRequest->file().adjustreplica().space();\n\n      if (!mRequest->file().adjustreplica().subgroup().empty()) {\n        cmd_in += \"&mgm.file.desiredsubgroup=\" +\n                  mRequest->file().adjustreplica().subgroup();\n      }\n    }\n\n    if (mRequest->file().adjustreplica().nodrop()) {\n      cmd_in += \"&mgm.file.option=--nodrop\";\n    }\n\n    break;\n  }\n\n  case eos::console::FileProto::kCheck: {\n    cmd_in += \"&mgm.subcmd=getmdlocation\";\n    cmd_in += \"&mgm.format=fuse\";\n    cmd_in += \"&mgm.path=\";\n    cmd_in += path;\n    XrdOucString option = mRequest->file().check().options().c_str();\n    cmd.open(\"/proc/user\", cmd_in.c_str(), *mVid, &error);\n    cmd.AddOutput(std_out, std_err);\n    cmd.close();\n    XrdOucEnv* result = new XrdOucEnv(std_out.c_str());\n    std_out = \"\";\n    bool silent = false;\n\n    if (!result) {\n      mReply->set_std_err(\"error: getmdlocation query failed\\n\");\n      mReply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n\n    int envlen = 0;\n    XrdOucEnv* newresult = new XrdOucEnv(result->Env(envlen));\n    delete result;\n    XrdOucString checksumattribute = \"NOTREQUIRED\";\n    bool consistencyerror = false;\n\n    if (envlen) {\n      XrdOucString ns_path = newresult->Get(\"mgm.nspath\");\n      XrdOucString checksumtype = newresult->Get(\"mgm.checksumtype\");\n      XrdOucString checksum = newresult->Get(\"mgm.checksum\");\n      XrdOucString size = newresult->Get(\"mgm.size\");\n\n      if ((option.find(\"%silent\") == STR_NPOS) && (!silent)) {\n        std_out += \"path=\\\"\";\n        std_out += ns_path.c_str();\n        std_out += \"\\\" fxid=\\\"\";\n        std_out += newresult->Get(\"mgm.fid0\");\n        std_out += \"\\\" size=\\\"\";\n        std_out += size.c_str();\n        std_out += \"\\\" nrep=\\\"\";\n        std_out += newresult->Get(\"mgm.nrep\");\n        std_out += \"\\\" checksumtype=\\\"\";\n        std_out += checksumtype.c_str();\n        std_out += \"\\\" checksum=\\\"\";\n        std_out += newresult->Get(\"mgm.checksum\");\n        std_out += \"\\\"\\n\";\n      }\n\n      int i = 0;\n      XrdOucString inconsistencylable = \"\";\n      int nreplicaonline = 0;\n\n      for (i = 0; i < 255; i++) {\n        XrdOucString repurl = \"mgm.replica.url\";\n        repurl += i;\n        XrdOucString repfid = \"mgm.fid\";\n        repfid += i;\n        XrdOucString repfsid = \"mgm.fsid\";\n        repfsid += i;\n        XrdOucString repbootstat = \"mgm.fsbootstat\";\n        repbootstat += i;\n        XrdOucString repfstpath = \"mgm.fstpath\";\n        repfstpath += i;\n\n        if (newresult->Get(repurl.c_str())) {\n          // Query\n          XrdCl::StatInfo* stat_info = 0;\n          XrdCl::XRootDStatus status;\n          std::string address = \"root://\";\n          address += newresult->Get(repurl.c_str());\n          address += \"//dummy\";\n          XrdCl::URL url(address.c_str());\n\n          if (!url.IsValid()) {\n            mReply->set_std_err(\"error=URL is not valid: \" + address);\n            mReply->set_retc(EINVAL);\n            return grpc::Status::OK;\n          }\n\n          // Get XrdCl::FileSystem object\n          std::unique_ptr<XrdCl::FileSystem> fs {new XrdCl::FileSystem(url)};\n\n          if (!fs) {\n            mReply->set_std_err(\"error=failed to get new FS object\");\n            mReply->set_retc(ECOMM);\n            return grpc::Status::OK;\n          }\n\n          XrdOucString bs = newresult->Get(repbootstat.c_str());\n          bool down = (bs != \"booted\");\n          int retc = 0;\n          int oldsilent = silent;\n          eos::common::FmdHelper fmd;\n\n          if ((option.find(\"%silent\")) != STR_NPOS) {\n            silent = true;\n          }\n\n          if (down && ((option.find(\"%force\")) == STR_NPOS)) {\n            consistencyerror = true;\n            inconsistencylable = \"DOWN\";\n\n            if (!silent) {\n              std_err += \"error: unable to retrieve file meta data from \";\n              std_err += newresult->Get(repurl.c_str());\n              std_err += \" [ status=\";\n              std_err += bs.c_str();\n              std_err += \" ]\\n\";\n            }\n          } else {\n            if ((option.find(\"%checksumattr\") != STR_NPOS)) {\n              checksumattribute = \"\";\n\n              if ((retc = File_GetRemoteAttribute(newresult->Get(repurl.c_str()),\n                                                  \"user.eos.checksum\",\n                                                  newresult->Get(repfstpath.c_str()),\n                                                  checksumattribute))) {\n                if (!silent) {\n                  std_err += \"error: unable to retrieve extended attribute from \";\n                  std_err += newresult->Get(repurl.c_str());\n                  std_err += \" [\";\n                  std_err += std::to_string(retc);\n                  std_err += \"]\\n\";\n                }\n              }\n            }\n\n            //..................................................................\n            // Do a remote stat using XrdCl::FileSystem\n            //..................................................................\n            uint64_t rsize;\n            XrdOucString statpath = newresult->Get(repfstpath.c_str());\n\n            if (!statpath.beginswith(\"/\")) {\n              // base 64 encode this path\n              XrdOucString statpath64 = \"\";\n              eos::common::SymKey::Base64(statpath, statpath64);\n              statpath = \"/#/\";\n              statpath += statpath64;\n            }\n\n            status = fs->Stat(statpath.c_str(), stat_info);\n\n            if (!status.IsOK()) {\n              consistencyerror = true;\n              inconsistencylable = \"STATFAILED\";\n              rsize = -1;\n            } else {\n              rsize = stat_info->GetSize();\n            }\n\n            // Free memory\n            delete stat_info;\n\n            if ((retc = File_GetRemoteFmdFromLocalDb(newresult->Get(repurl.c_str()),\n                        newresult->Get(repfid.c_str()),\n                        newresult->Get(repfsid.c_str()), fmd))) {\n              if (!silent) {\n                std_err += \"error: unable to retrieve file meta data from \";\n                std_err += newresult->Get(repurl.c_str());\n                std_err += \" [\";\n                std_err += std::to_string(retc);\n                std_err += \"]\\n\";\n              }\n\n              consistencyerror = true;\n              inconsistencylable = \"NOFMD\";\n            } else {\n              XrdOucString cx = fmd.mProtoFmd.checksum().c_str();\n\n              for (unsigned int k = (cx.length() / 2); k < SHA_DIGEST_LENGTH; k++) {\n                cx += \"00\";\n              }\n\n              XrdOucString disk_cx = fmd.mProtoFmd.diskchecksum().c_str();\n\n              for (unsigned int k = (disk_cx.length() / 2); k < SHA_DIGEST_LENGTH; k++) {\n                disk_cx += \"00\";\n              }\n\n              if ((option.find(\"%size\")) != STR_NPOS) {\n                char ss[1024];\n                sprintf(ss, \"%\" PRIu64, fmd.mProtoFmd.size());\n                XrdOucString sss = ss;\n\n                if (sss != size) {\n                  consistencyerror = true;\n                  inconsistencylable = \"SIZE\";\n                } else {\n                  if (fmd.mProtoFmd.size() != (unsigned long long) rsize) {\n                    if (!consistencyerror) {\n                      consistencyerror = true;\n                      inconsistencylable = \"FSTSIZE\";\n                    }\n                  }\n                }\n              }\n\n              if ((option.find(\"%checksum\")) != STR_NPOS) {\n                if (cx != checksum) {\n                  consistencyerror = true;\n                  inconsistencylable = \"CHECKSUM\";\n                }\n              }\n\n              if ((option.find(\"%checksumattr\") != STR_NPOS)) {\n                if ((checksumattribute.length() < 8) || (!cx.beginswith(checksumattribute))) {\n                  consistencyerror = true;\n                  inconsistencylable = \"CHECKSUMATTR\";\n                }\n              }\n\n              nreplicaonline++;\n\n              if (!silent) {\n                std_out += \"nrep=\\\"\";\n                std_out += std::to_string(i);\n                std_out += \"\\\" fsid=\\\"\";\n                std_out += newresult->Get(repfsid.c_str());\n                std_out += \"\\\" host=\\\"\";\n                std_out += newresult->Get(repurl.c_str());\n                std_out += \"\\\" fstpath=\\\"\";\n                std_out += newresult->Get(repfstpath.c_str());\n                std_out += \"\\\" size=\\\"\";\n                std_out += std::to_string(fmd.mProtoFmd.size());\n                std_out += \"\\\" statsize=\\\"\";\n                std_out += std::to_string(static_cast<long long>(rsize));\n                std_out += \"\\\" checksum=\\\"\";\n                std_out += cx.c_str();\n                std_out += \"\\\" diskchecksum=\\\"\";\n                std_out += disk_cx.c_str();\n                std_out += \"\\\"\";\n\n                if ((option.find(\"%checksumattr\") != STR_NPOS)) {\n                  std_out += \" checksumattr=\\\"\";\n                  std_out += checksumattribute.c_str();\n                  std_out += \"\\\"\";\n                }\n\n                std_out += \"\\n\";\n              }\n            }\n          }\n\n          if ((option.find(\"%silent\")) != STR_NPOS) {\n            silent = oldsilent;\n          }\n        } else {\n          break;\n        }\n      }\n\n      if ((option.find(\"%nrep\")) != STR_NPOS) {\n        int nrep = 0;\n        int stripes = 0;\n\n        if (newresult->Get(\"mgm.stripes\")) {\n          stripes = atoi(newresult->Get(\"mgm.stripes\"));\n        }\n\n        if (newresult->Get(\"mgm.nrep\")) {\n          nrep = atoi(newresult->Get(\"mgm.nrep\"));\n        }\n\n        if (nrep != stripes) {\n          consistencyerror = true;\n\n          if (inconsistencylable != \"NOFMD\") {\n            inconsistencylable = \"REPLICA\";\n          }\n        }\n      }\n\n      if ((option.find(\"%output\")) != STR_NPOS) {\n        if (consistencyerror) {\n          std_out += \"INCONSISTENCY \";\n          std_out += inconsistencylable.c_str();\n          std_out += \" path=\";\n          std_out += path.c_str();\n          std_out += \" fxid=\";\n          std_out += newresult->Get(\"mgm.fid0\");\n          std_out += \" size=\";\n          std_out += size.c_str();\n          std_out += \" stripes=\";\n          std_out += newresult->Get(\"mgm.stripes\");\n          std_out += \" nrep=\";\n          std_out += newresult->Get(\"mgm.nrep\");\n          std_out += \" nrepstored=\";\n          std_out += std::to_string(i);\n          std_out += \" nreponline=\";\n          std_out += std::to_string(nreplicaonline);\n          std_out += \" checksumtype=\";\n          std_out += checksumtype.c_str();\n          std_out += \" checksum=\";\n          std_out += newresult->Get(\"mgm.checksum\");\n          std_out += \"\\n\";\n        }\n      }\n\n      mReply->set_std_out(std_out);\n      mReply->set_std_err(std_err);\n\n      if (consistencyerror) {\n        mReply->set_retc(EFAULT);\n      } else {\n        mReply->set_retc(0);\n      }\n    } else {\n      mReply->set_std_err(\"error: couldn't get meta data information\\n\");\n      mReply->set_retc(EIO);\n    }\n\n    delete newresult;\n    return grpc::Status::OK;\n  }\n\n  case eos::console::FileProto::kConvert: {\n    cmd_in += \"&mgm.subcmd=convert\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    if (!mRequest->file().convert().layout().empty()) {\n      cmd_in += \"&mgm.convert.layout=\" + mRequest->file().convert().layout();\n    }\n\n    if (!mRequest->file().convert().target_space().empty()) {\n      cmd_in += \"&mgm.convert.space=\" + mRequest->file().convert().target_space();\n    }\n\n    if (!mRequest->file().convert().placement_policy().empty()) {\n      cmd_in += \"&mgm.convert.placementpolicy=\" +\n                mRequest->file().convert().placement_policy();\n    }\n\n    if (mRequest->file().convert().sync()) {\n      mReply->set_std_err(\"error: --sync is currently not supported\");\n      mReply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n\n    if (mRequest->file().convert().rewrite()) {\n      cmd_in += \"&mgm.option=rewrite\";\n    }\n\n    break;\n  }\n\n  case eos::console::FileProto::kCopy: {\n    cmd_in += \"&mgm.subcmd=copy\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    cmd_in += \"&mgm.file.target=\" + mRequest->file().copy().dst();\n\n    if (mRequest->file().copy().force() || mRequest->file().copy().clone() ||\n        mRequest->file().copy().silent()) {\n      cmd_in += \"&mgm.file.option=\";\n\n      if (mRequest->file().copy().force()) {\n        cmd_in += \"-f\";\n      }\n\n      if (mRequest->file().copy().clone()) {\n        cmd_in += \"-c\";\n      }\n\n      if (mRequest->file().copy().silent()) {\n        cmd_in += \"-s\";\n      }\n    }\n\n    break;\n  }\n\n  case eos::console::FileProto::kDrop: {\n    cmd_in += \"&mgm.subcmd=drop\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    cmd_in += \"&mgm.file.fsid=\" + std::to_string(mRequest->file().drop().fsid());\n\n    if (mRequest->file().drop().force()) {\n      cmd_in += \"&mgm.file.force=1\";\n    }\n\n    break;\n  }\n\n  case eos::console::FileProto::kLayout: {\n    cmd_in += \"&mgm.subcmd=layout\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    if (mRequest->file().layout().stripes()) {\n      cmd_in += \"&mgm.file.layout.stripes=\" + std::to_string(\n                  mRequest->file().layout().stripes());\n    }\n\n    if (!mRequest->file().layout().checksum().empty()) {\n      cmd_in += \"&mgm.file.layout.checksum=\" + mRequest->file().layout().checksum();\n    }\n\n    break;\n  }\n\n  case eos::console::FileProto::kMove: {\n    cmd_in += \"&mgm.subcmd=move\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    cmd_in += \"&mgm.file.sourcefsid=\" + std::to_string(\n                mRequest->file().move().fsid1());\n    cmd_in += \"&mgm.file.targetfsid=\" + std::to_string(\n                mRequest->file().move().fsid2());\n    break;\n  }\n\n  case eos::console::FileProto::kPurge: {\n    cmd_in += \"&mgm.subcmd=purge\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    cmd_in += \"&mgm.purge.version=\" + std::to_string(\n                mRequest->file().purge().purge_version());\n    break;\n  }\n\n  case eos::console::FileProto::kReplicate: {\n    cmd_in += \"&mgm.subcmd=replicate\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    cmd_in += \"&mgm.file.sourcefsid=\" + std::to_string(\n                mRequest->file().replicate().fsid1());\n    cmd_in += \"&mgm.file.targetfsid=\" + std::to_string(\n                mRequest->file().replicate().fsid2());\n    break;\n  }\n\n  case eos::console::FileProto::kResync: {\n    auto fsid = mRequest->file().resync().fsid();\n    \n    if (gOFS->QueryResync(fid, fsid)) {\n      std_out = \"info: resynced fid=\" + std::to_string(fid);\n      std_out += \" on fs=\" + std::to_string(fsid);\n      mReply->set_std_out(std_out);\n      mReply->set_retc(0);\n    } else {\n      std_err = \"error: failed to resync\";\n      mReply->set_std_err(std_err);\n      mReply->set_retc(-1);\n    }\n\n    return grpc::Status::OK;\n  }\n\n  case eos::console::FileProto::kSymlink: {\n    std::string target = mRequest->file().symlink().target_path();\n\n    if (target.empty()) {\n      mReply->set_std_err(\"error:target is empty\");\n      mReply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n\n    XrdOucErrInfo error;\n    errno = 0;\n\n    if (gOFS->_symlink(path.c_str(), target.c_str(), error, *mVid)) {\n      mReply->set_std_err(error.getErrText());\n      mReply->set_retc(errno);\n      return grpc::Status::OK;\n    }\n\n    std::string msg = \"info: symlinked '\";\n    msg += path.c_str();\n    msg += \"' to '\";\n    msg += target.c_str();\n    msg += \"'\";\n    mReply->set_std_out(msg);\n    mReply->set_retc(0);\n    return grpc::Status::OK;\n  }\n\n  case eos::console::FileProto::kTag: {\n    cmd_in += \"&mgm.subcmd=tag\";\n    cmd_in += \"&mgm.path=\" + path;\n    cmd_in += \"&mgm.file.tag.fsid=\";\n\n    if (mRequest->file().tag().add()) {\n      cmd_in += \"+\";\n    }\n\n    if (mRequest->file().tag().remove()) {\n      cmd_in += \"-\";\n    }\n\n    if (mRequest->file().tag().unlink()) {\n      cmd_in += \"~\";\n    }\n\n    cmd_in += std::to_string(mRequest->file().tag().fsid());\n    break;\n  }\n\n  case eos::console::FileProto::kVerify: {\n    cmd_in += \"&mgm.subcmd=verify\";\n    cmd_in += \"&mgm.path=\" + path;\n    cmd_in += \"&mgm.file.verify.filterid=\" + std::to_string(\n                mRequest->file().verify().fsid());\n\n    if (mRequest->file().verify().checksum()) {\n      cmd_in += \"&mgm.file.compute.checksum=1\";\n    }\n\n    if (mRequest->file().verify().commitchecksum()) {\n      cmd_in += \"&mgm.file.commit.checksum=1\";\n    }\n\n    if (mRequest->file().verify().commitsize()) {\n      cmd_in += \"&mgm.file.commit.size=1\";\n    }\n\n    if (mRequest->file().verify().commitfmd()) {\n      cmd_in += \"&mgm.file.commit.fmd=1\";\n    }\n\n    if (mRequest->file().verify().rate()) {\n      cmd_in += \"&mgm.file.verify.rate=\" + std::to_string(\n                  mRequest->file().verify().rate());\n    }\n\n    if (mRequest->file().verify().resync()) {\n      cmd_in += \"&mgm.file.resync=1\";\n    }\n\n    break;\n  }\n\n  case eos::console::FileProto::kVersion: {\n    cmd_in += \"&mgm.subcmd=version\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    cmd_in += \"&mgm.purge.version=\" + std::to_string(\n                mRequest->file().version().purge_version());\n    break;\n  }\n\n  case eos::console::FileProto::kVersions: {\n    cmd_in += \"&mgm.subcmd=versions\";\n\n    if (fid) {\n      cmd_in += \"&mgm.file.id=\" + std::to_string(fid);\n    } else {\n      // this has a problem with '&' encoding, prefer to use create by fid ..\n      cmd_in += \"&mgm.path=\" + path;\n    }\n\n    if (!mRequest->file().versions().grab_version().empty()) {\n      cmd_in += \"&mgm.grab.version=\" + mRequest->file().versions().grab_version();\n    } else {\n      cmd_in += \"&mgm.grab.version=-1\";\n    }\n\n    break;\n  }\n\n  case eos::console::FileProto::kShare: {\n    cmd_in += \"&mgm.subcmd=share\";\n    cmd_in += \"&mgm.path=\" + path;\n    cmd_in += \"&mgm.file.expires=\" + std::to_string(\n                mRequest->file().share().expires());\n    break;\n  }\n\n  case eos::console::FileProto::kWorkflow: {\n    cmd_in += \"&mgm.subcmd=workflow\";\n    cmd_in += \"&mgm.path=\" + path;\n    cmd_in += \"&mgm.workflow=\" + mRequest->file().workflow().workflow();\n    cmd_in += \"&mgm.event=\" + mRequest->file().workflow().event();\n    break;\n  }\n\n  default: {\n    mReply->set_std_err(\"error: subcommand is not supported\");\n    mReply->set_retc(EINVAL);\n    return grpc::Status::OK;\n  }\n  }\n\n  ExecProcCmd(cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Fileinfo()\n{\n  std::string path = mRequest->fileinfo().md().path();\n\n  if (path.empty()) {\n    // get by inode\n    if (mRequest->fileinfo().md().ino()) {\n      path = \"inode:\" + std::to_string(mRequest->fileinfo().md().ino());\n    }\n    // get by fileid\n    else if (mRequest->fileinfo().md().id()) {\n      path = \"fid:\" + std::to_string(mRequest->fileinfo().md().id());\n    }\n\n    if (path.empty()) {\n      mReply->set_std_err(\"error: path is empty\");\n      mReply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n  }\n\n  std::string std_out, std_err;\n  ProcCommand cmd;\n  XrdOucErrInfo error;\n  std::string cmd_in = \"mgm.cmd=fileinfo\";\n  cmd_in += \"&mgm.path=\" + path;\n\n  if (mRequest->fileinfo().path() || mRequest->fileinfo().fid() ||\n      mRequest->fileinfo().fxid() || mRequest->fileinfo().size() ||\n      mRequest->fileinfo().checksum() || mRequest->fileinfo().fullpath() ||\n      mRequest->fileinfo().proxy() || mRequest->fileinfo().monitoring() ||\n      mRequest->fileinfo().wnc() || mRequest->fileinfo().env()) {\n    cmd_in += \"&mgm.file.info.option=\";\n  }\n\n  if (mRequest->fileinfo().path()) {\n    cmd_in += \"--path\";\n  }\n\n  if (mRequest->fileinfo().fid()) {\n    cmd_in += \"--fid\";\n  }\n\n  if (mRequest->fileinfo().fxid()) {\n    cmd_in += \"--fxid\";\n  }\n\n  if (mRequest->fileinfo().size()) {\n    cmd_in += \"--size\";\n  }\n\n  if (mRequest->fileinfo().checksum()) {\n    cmd_in += \"--checksum\";\n  }\n\n  if (mRequest->fileinfo().fullpath()) {\n    cmd_in += \"--fullpath\";\n  }\n\n  if (mRequest->fileinfo().proxy()) {\n    cmd_in += \"--proxy\";\n  }\n\n  if (mRequest->fileinfo().monitoring() || mRequest->fileinfo().wnc()) {\n    cmd_in += \"-m\";\n  }\n\n  if (mRequest->fileinfo().env()) {\n    cmd_in += \"--env\";\n  }\n\n  if (mJsonFormat) {\n    cmd_in += \"&mgm.format=json\";\n  }\n\n  cmd.open(\"/proc/user\", cmd_in.c_str(), *mVid, &error);\n  cmd.AddOutput(std_out, std_err);\n\n  if (mJsonFormat) {\n    std_out = cmd.GetStdJson();\n  }\n\n  cmd.close();\n\n  // Complement EOS-Drive output with usernames and groupnames\n  if (!std_out.empty() && mRequest->fileinfo().wnc()) {\n    size_t pos;\n    int errc = 0;\n\n    // Add owner's username\n    if ((pos = std_out.find(\"uid=\")) != std::string::npos) {\n      size_t pos1 = pos + 4;\n      size_t pos2 = std_out.find(' ', pos1);\n\n      if (pos1 < pos2) {\n        uid_t id = std::stoull(std_out.substr(pos1, pos2 - pos1));\n        std::string name = eos::common::Mapping::UidToUserName(id, errc);\n        std_out += \"wnc_username=\" + name + \" \";\n      }\n    }\n\n    // Add owner's groupname\n    if ((pos = std_out.find(\"gid=\")) != std::string::npos) {\n      size_t pos1 = pos + 4;\n      size_t pos2 = std_out.find(' ', pos1);\n\n      if (pos1 < pos2) {\n        uid_t id = std::stoull(std_out.substr(pos1, pos2 - pos1));\n        std::string name = eos::common::Mapping::GidToGroupName(id, errc);\n        std_out += \"wnc_groupname=\" + name + \" \";\n      }\n    }\n\n    // Get user ACL with usernames/groupnames/egroupnames\n    eos::console::RequestProto acl_request;\n    eos::console::ReplyProto acl_reply;\n    acl_request.mutable_acl()->set_op(eos::console::AclProto_OpType_LIST);\n    acl_request.mutable_acl()->set_path(mRequest->fileinfo().md().path());\n    GrpcWncInterface exec_acl;\n    exec_acl.ExecCmd(*mVid, &acl_request, &acl_reply);\n\n    if (!acl_reply.std_out().empty()) {\n      std_out += \"wnc_acl_user=\" + acl_reply.std_out() + \" \";\n    }\n\n    // Get sys ACL with usernames/groupnames/egroupnames\n    acl_request.mutable_acl()->set_sys_acl(true);\n    exec_acl.ExecCmd(*mVid, &acl_request, &acl_reply);\n\n    if (!acl_reply.std_out().empty()) {\n      std_out += \"wnc_acl_sys=\" + acl_reply.std_out() + \" \";\n    }\n  }\n\n  mReply->set_std_out(std_out);\n  mReply->set_std_err(std_err);\n  mReply->set_retc(cmd.GetRetc());\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Find()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::NewfindCmd findcmd(std::move(req), *mVid);\n  findcmd.ProcessRequest(mWriter);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Fs()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::FsCmd fscmd(std::move(req), *mVid);\n  *mReply = fscmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Fsck()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::FsckCmd fsckcmd(std::move(req), *mVid);\n  *mReply = fsckcmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Geosched()\n{\n  if (mVid->uid == 0) {\n    std::string subcmd;\n    mReply->set_retc(SFS_ERROR);\n\n    if (mRequest->geosched().subcmd_case() ==\n        eos::console::GeoschedProto::kAccess) {\n      subcmd = mRequest->geosched().access().subcmd();\n      // XrdOucString has to be manually initialized to avoid strange behaviour\n      // of some GeoTreeEngine functions\n      XrdOucString output = \"\";\n      std::string geotag = mRequest->geosched().access().geotag();\n      std::string geotag_list = mRequest->geosched().access().geotag_list();\n      std::string proxy_group = mRequest->geosched().access().proxy_group();\n      bool monitoring = mRequest->geosched().access().monitoring();\n\n      if (!geotag.empty()) {\n        std::string tmp_geotag = eos::common::SanitizeGeoTag(geotag);\n\n        if (tmp_geotag != geotag) {\n          mReply->set_std_err(tmp_geotag);\n          mReply->set_retc(EINVAL);\n          return grpc::Status::OK;\n        }\n      }\n\n      if (subcmd == \"cleardirect\") {\n        if (gOFS->mGeoTreeEngine->clearAccessGeotagMapping(&output,\n            geotag == \"all\" ? \"\" : geotag)) {\n          mReply->set_retc(SFS_OK);\n        }\n      }\n\n      if (subcmd == \"clearproxygroup\") {\n        if (gOFS->mGeoTreeEngine->clearAccessProxygroup(&output,\n            geotag == \"all\" ? \"\" : geotag)) {\n          mReply->set_retc(SFS_OK);\n        }\n      }\n\n      if (subcmd == \"setdirect\") {\n        auto geotags = eos::common::StringTokenizer::split<std::vector\n                       <std::string>>(geotag_list, ',');\n\n        for (const auto& tag : geotags) {\n          std::string tmp_tag = eos::common::SanitizeGeoTag(tag);\n\n          if (tmp_tag != tag) {\n            mReply->set_std_err(tmp_tag);\n            mReply->set_retc(EINVAL);\n            return grpc::Status::OK;\n          }\n        }\n\n        if (gOFS->mGeoTreeEngine->setAccessGeotagMapping(&output, geotag,\n            geotag_list)) {\n          mReply->set_retc(SFS_OK);\n        }\n      }\n\n      if (subcmd == \"setproxygroup\") {\n        if (gOFS->mGeoTreeEngine->setAccessProxygroup(&output, geotag, proxy_group)) {\n          mReply->set_retc(SFS_OK);\n        }\n      }\n\n      if (subcmd == \"showdirect\") {\n        if (gOFS->mGeoTreeEngine->showAccessGeotagMapping(&output, monitoring)) {\n          mReply->set_retc(SFS_OK);\n        }\n      }\n\n      if (subcmd == \"showproxygroup\") {\n        if (gOFS->mGeoTreeEngine->showAccessProxygroup(&output, monitoring)) {\n          mReply->set_retc(SFS_OK);\n        }\n      }\n\n      mReply->set_std_out(output.c_str());\n    }\n\n    if (mRequest->geosched().subcmd_case() ==\n        eos::console::GeoschedProto::kDisabled) {\n      subcmd = mRequest->geosched().disabled().subcmd();\n      std::string sched_group = mRequest->geosched().disabled().group();\n      std::string op_type = mRequest->geosched().disabled().op_type();\n      std::string geotag = mRequest->geosched().disabled().geotag();\n      XrdOucString output = \"\";\n      bool save_config = true; // save it to the config\n\n      if (!(geotag == \"*\" && subcmd != \"add\")) {\n        std::string tmp_geotag = eos::common::SanitizeGeoTag(geotag);\n\n        if (tmp_geotag != geotag) {\n          mReply->set_std_err(tmp_geotag);\n          mReply->set_retc(EINVAL);\n          return grpc::Status::OK;\n        }\n      }\n\n      if (subcmd == \"add\") {\n        if (gOFS->mGeoTreeEngine->addDisabledBranch(sched_group, op_type, geotag,\n            &output, save_config)) {\n          mReply->set_retc(SFS_OK);\n        }\n      }\n\n      if (subcmd == \"rm\") {\n        if (gOFS->mGeoTreeEngine->rmDisabledBranch(sched_group, op_type, geotag,\n            &output, save_config)) {\n          mReply->set_retc(SFS_OK);\n        }\n      }\n\n      if (subcmd == \"show\") {\n        if (gOFS->mGeoTreeEngine->showDisabledBranches(sched_group, op_type, geotag,\n            &output)) {\n          mReply->set_retc(SFS_OK);\n        }\n      }\n\n      mReply->set_std_out(output.c_str());\n    }\n\n    if (mRequest->geosched().subcmd_case() == eos::console::GeoschedProto::kRef) {\n      if (gOFS->mGeoTreeEngine->forceRefresh()) {\n        mReply->set_std_out(\"GeoTreeEngine has been refreshed.\");\n        mReply->set_retc(SFS_OK);\n      } else {\n        mReply->set_std_out(\"GeoTreeEngine could not be refreshed at the moment.\");\n      }\n    }\n\n    if (mRequest->geosched().subcmd_case() == eos::console::GeoschedProto::kSet) {\n      std::string param_name  = mRequest->geosched().set().param_name();\n      std::string param_index = mRequest->geosched().set().param_index();\n      std::string param_value = mRequest->geosched().set().param_value();\n      int index = -1;\n\n      if (!param_index.empty()) {\n        index = std::stoi(param_index);\n      }\n\n      bool save_config = true;\n\n      if (gOFS->mGeoTreeEngine->setParameter(param_name, param_value, index,\n                                             save_config)) {\n        mReply->set_std_out(\"GeoTreeEngine parameter has been set.\");\n        mReply->set_retc(SFS_OK);\n      } else {\n        mReply->set_std_out(\"GeoTreeEngine parameter could not be set.\");\n      }\n    }\n\n    if (mRequest->geosched().subcmd_case() == eos::console::GeoschedProto::kShow) {\n      subcmd = mRequest->geosched().show().subcmd();\n      bool print_tree = (subcmd == \"tree\");\n      bool print_snaps = (subcmd == \"snapshot\");\n      bool print_param = (subcmd == \"param\");\n      bool print_state = (subcmd == \"state\");\n      std::string sched_group = mRequest->geosched().show().group();\n      std::string op_type = mRequest->geosched().show().op_type();\n      bool use_colors = mRequest->geosched().show().color();\n      bool monitoring = mRequest->geosched().show().monitoring();\n      std::string output;\n      gOFS->mGeoTreeEngine->printInfo(output, print_tree, print_snaps, print_param,\n                                      print_state,\n                                      sched_group, op_type, use_colors, monitoring);\n      mReply->set_std_out(output.c_str());\n      mReply->set_retc(SFS_OK);\n    }\n\n    if (mRequest->geosched().subcmd_case() ==\n        eos::console::GeoschedProto::kUpdater) {\n      subcmd = mRequest->geosched().updater().subcmd();\n\n      if (subcmd == \"pause\") {\n        if (gOFS->mGeoTreeEngine->PauseUpdater()) {\n          mReply->set_std_out(\"GeoTreeEngine has been paused.\");\n          mReply->set_retc(SFS_OK);\n        } else {\n          mReply->set_std_out(\"GeoTreeEngine could not be paused at the moment.\");\n        }\n      }\n\n      if (subcmd == \"resume\") {\n        gOFS->mGeoTreeEngine->ResumeUpdater();\n        mReply->set_std_out(\"GeoTreeEngine has been resumed.\");\n        mReply->set_retc(SFS_OK);\n      }\n    }\n  } else {\n    mReply->set_std_err(\"error: you have to take role 'root' to execute this command\");\n    mReply->set_retc(EPERM);\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Group()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::GroupCmd groupcmd(std::move(req), *mVid);\n  *mReply = groupcmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Health()\n{\n  std::string output;\n  std::string args = mRequest->health().section();\n\n  if (mRequest->health().all_info()) {\n    args += \" -a\";\n  }\n\n  if (mRequest->health().monitoring()) {\n    args += \" -m\";\n  }\n\n  HealthCommand health(args.c_str());\n\n  try {\n    health.Execute(output);\n    mReply->set_std_out(output.c_str());\n    mReply->set_retc(0);\n  } catch (std::string& err) {\n    output = \"Error: \";\n    output += err;\n    mReply->set_std_err(output.c_str());\n    mReply->set_retc(errno);\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Io()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::IoCmd iocmd(std::move(req), *mVid);\n  *mReply = iocmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Ls()\n{\n  std::string path = mRequest->ls().md().path();\n  eos::console::ReplyProto StreamReply;\n  errno = 0;\n\n  if (path.empty()) {\n    if (mRequest->ls().md().type() == eos::console::FILE) {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(\n                                       mRequest->ls().md().id()).get());\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n      }\n    } else {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(\n                                       mRequest->ls().md().id()).get());\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n      }\n    }\n\n    if (errno) {\n      StreamReply.set_std_out(\"\");\n      StreamReply.set_std_err(\"Error: Path is empty\");\n      StreamReply.set_retc(EINVAL);\n      mWriter->Write(StreamReply);\n      return grpc::Status::OK;\n    }\n  }\n\n  std::string std_out, std_err;\n  ProcCommand cmd;\n  XrdOucErrInfo error;\n  std::string cmd_in = \"mgm.cmd=ls&mgm.path=\" + path;\n\n  if (mRequest->ls().long_list() || mRequest->ls().tape() ||\n      mRequest->ls().readable_sizes() || mRequest->ls().show_hidden() ||\n      mRequest->ls().inode_info() || mRequest->ls().num_ids() ||\n      mRequest->ls().append_dir_ind() || mRequest->ls().silent() ||\n      mRequest->ls().wnc() || mRequest->ls().noglobbing()) {\n    cmd_in += \"&mgm.option=\";\n\n    if (mRequest->ls().long_list()) {\n      cmd_in += \"l\";\n    }\n\n    if (mRequest->ls().tape()) {\n      cmd_in += \"y\";\n    }\n\n    if (mRequest->ls().readable_sizes()) {\n      cmd_in += \"h\";\n    }\n\n    if (mRequest->ls().show_hidden() || mRequest->ls().wnc()) {\n      cmd_in += \"a\";\n    }\n\n    if (mRequest->ls().inode_info()) {\n      cmd_in += \"i\";\n    }\n\n    if (mRequest->ls().num_ids()) {\n      cmd_in += \"n\";\n    }\n\n    if (mRequest->ls().append_dir_ind() || mRequest->ls().wnc()) {\n      cmd_in += \"F\";\n    }\n\n    if (mRequest->ls().silent()) {\n      cmd_in += \"s\";\n    }\n\n    if (mRequest->ls().noglobbing()) {\n      cmd_in += \"N\";\n    }\n  }\n\n  cmd.open(\"/proc/user\", cmd_in.c_str(), *mVid, &error);\n  cmd.AddOutput(std_out, std_err);\n  cmd.close();\n\n  if (cmd.GetRetc() == 0) {\n    std::stringstream list(std_out);\n    std::string entry(\"\"), out(\"\");\n    int counter = 0;\n\n    while (std::getline(list, entry)) {\n      if (mRequest->ls().wnc()) {\n        uint64_t size = 0;\n        eos::IFileMD::ctime_t mtime;\n        eos::IFileMD::XAttrMap xattrs;\n        // Get full path\n        std::string full_path;\n\n        if (entry == \"../\") {\n          continue;\n        } else if (entry == \"./\") {\n          full_path = path;\n        } else {\n          full_path = path + entry;\n        }\n\n        // Get the parameters if entry is a file\n        if (entry[entry.size() - 1] != '/') {\n          std::shared_ptr<eos::IFileMD> fmd;\n\n          try {\n            fmd = gOFS->eosView->getFile(full_path.c_str());\n          } catch (eos::MDException& e) {\n            // Maybe this is a symlink pointing outside the EOS namespace\n            try {\n              fmd = gOFS->eosView->getFile(full_path.c_str(), false);\n            } catch (eos::MDException& e) {\n              out += entry + \"\\t\\t\\n\";\n              continue;\n            }\n          }\n\n          if (fmd) {\n            fmd->getMTime(mtime);\n            xattrs = fmd->getAttributes();\n            size = fmd->getSize();\n          }\n        }\n        // Get the parameters if entry is a directory\n        else {\n          std::shared_ptr<eos::IContainerMD> cmd;\n\n          try {\n            cmd = gOFS->eosView->getContainer(full_path.c_str());\n          } catch (eos::MDException& e) {\n            out += entry + \"\\t\\t\\n\";\n            continue;\n          }\n\n          if (cmd) {\n            cmd->getMTime(mtime);\n            xattrs = cmd->getAttributes();\n          }\n        }\n\n        // Print the parameters\n        out += entry;\n        out += \"\\t\\tsize=\" + std::to_string(size);\n        out += \" mtime=\" + std::to_string(mtime.tv_sec);\n        out += \".\" + std::to_string(mtime.tv_nsec);\n\n        if (xattrs.count(\"sys.eos.btime\")) {\n          out += \" btime=\" + xattrs[\"sys.eos.btime\"];\n        }\n\n        out += \"\\n\";\n      } else {\n        out += entry + \"\\n\";\n      }\n\n      // Write every 100 lines separately to gRPC\n      counter++;\n\n      if (counter >= 100) {\n        StreamReply.set_std_out(out);\n        StreamReply.set_retc(0);\n        mWriter->Write(StreamReply);\n        counter = 0;\n        out.clear();\n      }\n    }\n\n    // Write last part to gRPC, if exists\n    if (!out.empty()) {\n      StreamReply.set_std_out(out);\n      StreamReply.set_retc(0);\n      mWriter->Write(StreamReply);\n    }\n  } else {\n    StreamReply.set_std_out(std_out);\n    StreamReply.set_std_err(std_err);\n    StreamReply.set_retc(cmd.GetRetc());\n    mWriter->Write(StreamReply);\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Map()\n{\n  std::string subcmd = mRequest->map().command();\n  std::string cmd_in = \"mgm.cmd=map&mgm.subcmd=\" + subcmd;\n\n  if (subcmd == \"link\") {\n    cmd_in += \"&mgm.map.src=\" + mRequest->map().src_path();\n    cmd_in += \"&mgm.map.dest=\" + mRequest->map().dst_path();\n  } else if (subcmd == \"unlink\") {\n    cmd_in += \"&mgm.map.src=\" + mRequest->map().src_path();\n  }\n\n  ExecProcCmd(cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Member()\n{\n  std::string egroup = mRequest->member().egroup();\n  int errc = 0;\n  std::string uid_string = eos::common::Mapping::UidToUserName(mVid->uid, errc);\n  std::string rs;\n\n  if (!egroup.empty()) {\n    if (mRequest->member().update()) {\n      gOFS->EgroupRefresh->refresh(uid_string, egroup);\n    }\n\n    rs = gOFS->EgroupRefresh->DumpMember(uid_string, egroup);\n  } else if (mVid->uid != 0) {\n    mReply->set_std_err(\"error: you have to take role 'root' to execute this command\");\n    mReply->set_retc(EPERM);\n    return grpc::Status::OK;\n  } else {\n    rs = gOFS->EgroupRefresh->DumpMembers();\n  }\n\n  if (mJsonFormat) {\n    Json::Value json;\n\n    try {\n      json[\"errormsg\"] = \"\";\n      json[\"member\"] = ProcCommand::CallJsonFormatter((const std::string)rs);\n      json[\"retc\"] = std::to_string(SFS_OK);\n      mReply->set_std_out(SSTR(json).c_str());\n    } catch (Json::Exception& e) {\n      json[\"errormsg\"] = \"illegal string in json conversion\";\n      json[\"retc\"] = std::to_string(EFAULT);\n      mReply->set_std_err(SSTR(json).c_str());\n      mReply->set_retc(EFAULT);\n      return grpc::Status::OK;\n    }\n  } else {\n    mReply->set_std_out(rs);\n  }\n\n  mReply->set_retc(SFS_OK);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Mkdir()\n{\n  std::string cmd_in = \"mgm.cmd=mkdir\";\n  std::string path = mRequest->mkdir().md().path();\n  cmd_in += \"&mgm.path=\" + path;\n\n  if (mRequest->mkdir().parents()) {\n    cmd_in += \"&mgm.option=p\";\n  }\n\n  ExecProcCmd(cmd_in, false);\n\n  if (mRequest->mkdir().mode() != 0 && mReply->retc() == 0) {\n    eos::console::RequestProto chmod_request;\n    eos::console::ReplyProto chmod_reply;\n    chmod_request.mutable_chmod()->mutable_md()->set_path(path);\n    chmod_request.mutable_chmod()->set_mode(mRequest->mkdir().mode());\n    GrpcWncInterface exec_chmod;\n    exec_chmod.ExecCmd(*mVid, &chmod_request, &chmod_reply);\n\n    if (chmod_reply.retc() != 0) {\n      mReply->set_std_err(chmod_reply.std_err());\n      mReply->set_retc(chmod_reply.retc());\n    }\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Mv()\n{\n  std::string path = mRequest->mv().md().path();\n  std::string target = mRequest->mv().target();\n  errno = 0;\n  std::string cmd_in = \"mgm.cmd=file\";\n\n  if (path.empty()) {\n    if (mRequest->mv().md().type() == eos::console::FILE) {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(\n                                       mRequest->mv().md().id()).get());\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n      }\n    } else {\n      try {\n        eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n        path = gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(\n                                       mRequest->mv().md().id()).get());\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n      }\n    }\n\n    if (errno) {\n      mReply->set_std_err(\"Error: Path is empty\");\n      mReply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n  }\n\n  cmd_in += \"&mgm.subcmd=rename&mgm.path=\" + path + \"&mgm.file.target=\" + target;\n  ExecProcCmd(cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Node()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::NodeCmd nodecmd(std::move(req), *mVid);\n  *mReply = nodecmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Ns()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::NsCmd nscmd(std::move(req), *mVid);\n  *mReply = nscmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Quota()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::QuotaCmd quotacmd(std::move(req), *mVid);\n  *mReply = quotacmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Recycle()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::RecycleCmd recyclecmd(std::move(req), *mVid);\n  *mReply = recyclecmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Rm()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::RmCmd rmcmd(std::move(req), *mVid);\n  *mReply = rmcmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Rmdir()\n{\n  std::string path = mRequest->rmdir().md().path();\n  errno = 0;\n\n  if (path.empty()) {\n    try {\n      eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n      path = gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(\n                                     mRequest->rmdir().md().id()).get());\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n    }\n\n    if (errno) {\n      mReply->set_std_err(\"Error: Path is empty\");\n      mReply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n  }\n\n  std::string cmd_in = \"mgm.cmd=rmdir&mgm.path=\" + path;\n  ExecProcCmd(cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Route()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::RouteCmd routecmd(std::move(req), *mVid);\n  *mReply = routecmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Space()\n{\n  eos::console::RequestProto req = *mRequest;\n\n  if (mRequest->space().subcmd_case() == eos::console::SpaceProto::kNodeSet) {\n    // encoding the value to Base64\n    std::string val = mRequest->space().nodeset().nodeset_value();\n\n    if (val.substr(0, 5) != \"file:\") {\n      XrdOucString val64 = \"\";\n      eos::common::SymKey::Base64Encode((char*) val.c_str(), val.length(), val64);\n\n      while (val64.replace(\"=\", \":\")) {}\n\n      std::string nodeset = \"base64:\";\n      nodeset += val64.c_str();\n      req.mutable_space()->mutable_nodeset()->set_nodeset_value(nodeset);\n    }\n  }\n\n  eos::mgm::SpaceCmd spacecmd(std::move(req), *mVid);\n  *mReply = spacecmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Stat()\n{\n  struct stat buf;\n  std::string path = mRequest->stat().path();\n  std::string url = \"root://localhost/\" + path;\n\n  if (!XrdPosixXrootd::Stat(url.c_str(), &buf)) {\n    if (mRequest->stat().file()) {\n      if (S_ISREG(buf.st_mode)) {\n        mReply->set_retc(0);\n      } else {\n        mReply->set_retc(1);\n      }\n    } else if (mRequest->stat().directory()) {\n      if (S_ISDIR(buf.st_mode)) {\n        mReply->set_retc(0);\n      } else {\n        mReply->set_retc(1);\n      }\n    } else {\n      std::string output = \"Path: \" + path + \"\\n\";\n\n      if (S_ISREG(buf.st_mode)) {\n        XrdOucString sizestring = \"\";\n        output += \"Size: \" + std::to_string(buf.st_size) + \" (\";\n        output += eos::common::StringConversion::GetReadableSizeString(\n                    sizestring, (unsigned long long)buf.st_size, \"B\");\n        output += \")\\n\";\n        output += \"Type: regular file\\n\";\n      } else if (S_ISDIR(buf.st_mode)) {\n        output += \"Type: directory\\n\";\n      } else {\n        output += \"Type: symbolic link\\n\";\n      }\n\n      mReply->set_std_out(output);\n      mReply->set_retc(0);\n    }\n  } else {\n    mReply->set_std_err(\"error: failed to stat \" + path);\n    mReply->set_retc(EFAULT);\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Status()\n{\n  FILE* pipe = popen(\"eos-status\", \"r\");\n  char line[4096];\n  std::string output = \"\";\n  int rc = 0;\n\n  if (!pipe) {\n    mReply->set_std_err(\"Error: Failed to create pipe for eos-status execution\");\n    mReply->set_retc(errno);\n    return grpc::Status::OK;\n  }\n\n  while (fgets(line, sizeof(line), pipe)) {\n    output += line;\n  }\n\n  if ((rc = pclose(pipe)) == -1) {\n    mReply->set_std_err(\"Error: Failed to close pipe for eos-status execution\");\n    mReply->set_retc(errno);\n    return grpc::Status::OK;\n  }\n\n  mReply->set_std_out(output);\n  mReply->set_retc(rc);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Token()\n{\n  eos::console::RequestProto req = *mRequest;\n  eos::mgm::TokenCmd tokencmd(std::move(req), *mVid);\n  *mReply = tokencmd.ProcessRequest();\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Touch()\n{\n  std::string path = mRequest->touch().md().path();\n  std::string cmd_in = \"mgm.cmd=file&mgm.subcmd=touch&mgm.path=\" + path;\n\n  if (mRequest->touch().nolayout()) {\n    cmd_in += \"&mgm.file.touch.nolayout=true\";\n  }\n\n  if (mRequest->touch().truncate()) {\n    cmd_in += \"&mgm.file.touch.truncate=true\";\n  }\n\n  ExecProcCmd(cmd_in, false);\n\n  // Create parent directories\n  if (mRequest->touch().parents() && mReply->retc() == 2) {\n    size_t pos = 0;\n\n    if (!path.empty() && path[path.size() - 1] != '/' &&\n        (pos = path.rfind('/')) != std::string::npos) {\n      std::string parent_path = path.substr(0, pos);\n      eos::console::RequestProto mkdir_request;\n      eos::console::ReplyProto mkdir_reply;\n      mkdir_request.mutable_mkdir()->mutable_md()->set_path(parent_path);\n      mkdir_request.mutable_mkdir()->set_parents(true);\n      GrpcWncInterface exec_mkdir;\n      exec_mkdir.ExecCmd(*mVid, &mkdir_request, &mkdir_reply);\n\n      // Run touch command again\n      if (mkdir_reply.retc() == 0) {\n        ExecProcCmd(cmd_in, false);\n      }\n    }\n  }\n\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Version()\n{\n  std::string cmd_in = \"mgm.cmd=version\";\n\n  if (mRequest->version().monitoring() || mRequest->version().features()) {\n    cmd_in += \"&mgm.option=\";\n  }\n\n  if (mRequest->version().features()) {\n    cmd_in += \"f\";\n  }\n\n  if (mRequest->version().monitoring()) {\n    cmd_in += \"m\";\n  }\n\n  ExecProcCmd(cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Vid()\n{\n  std::string std_out1, std_out2, std_err1, std_err2;\n  ProcCommand cmd1, cmd2;\n  XrdOucErrInfo error1, error2;\n  std::string cmd_in1, cmd_in2;\n  cmd_in1 = cmd_in2 = \"mgm.cmd=vid\";\n  bool has_cmd2 = false;\n\n  switch (mRequest->vid().subcmd_case()) {\n  case eos::console::VidProto::kGateway: {\n    eos::console::VidProto_GatewayProto_Protocol prot =\n      mRequest->vid().gateway().protocol();\n    std::string protocol;\n    eos::console::VidProto_GatewayProto_Option option =\n      mRequest->vid().gateway().option();\n    std::string host = mRequest->vid().gateway().hostname();\n\n    if (prot == eos::console::VidProto_GatewayProto_Protocol_ALL) {\n      protocol = \"*\";\n    } else if (prot == eos::console::VidProto_GatewayProto_Protocol_KRB5) {\n      protocol = \"krb5\";\n    } else if (prot == eos::console::VidProto_GatewayProto_Protocol_GSI) {\n      protocol = \"gsi\";\n    } else if (prot == eos::console::VidProto_GatewayProto_Protocol_SSS) {\n      protocol = \"sss\";\n    } else if (prot == eos::console::VidProto_GatewayProto_Protocol_UNIX) {\n      protocol = \"unix\";\n    } else if (prot == eos::console::VidProto_GatewayProto_Protocol_HTTPS) {\n      protocol = \"https\";\n    } else if (prot == eos::console::VidProto_GatewayProto_Protocol_GRPC) {\n      protocol = \"grpc\";\n    }\n\n    if (option == eos::console::VidProto_GatewayProto_Option_ADD) {\n      cmd_in1 += \"&mgm.subcmd=set\";\n      cmd_in1 += \"&mgm.vid.auth=tident\";\n      cmd_in1 += \"&mgm.vid.cmd=map\";\n      cmd_in1 += \"&mgm.vid.gid=0\";\n      cmd_in1 += \"&mgm.vid.key=<key>\";\n      cmd_in1 += \"&mgm.vid.pattern=\\\"\" + protocol + \"@\" + host + \"\\\"\";\n      cmd_in1 += \"&mgm.vid.uid=0\";\n    } else if (option == eos::console::VidProto_GatewayProto_Option_REMOVE) {\n      has_cmd2 = true;\n      cmd_in1 += \"&mgm.subcmd=rm\";\n      cmd_in1 += \"&mgm.vid.cmd=unmap\";\n      cmd_in1 += \"&mgm.vid.key=tident:\\\"\" + protocol + \"@\" + host + \"\\\":uid\";\n      cmd_in2 += \"&mgm.subcmd=rm\";\n      cmd_in2 += \"&mgm.vid.cmd=unmap\";\n      cmd_in2 += \"&mgm.vid.key=tident:\\\"\" + protocol + \"@\" + host + \"\\\":gid\";\n    }\n\n    break;\n  }\n\n  case eos::console::VidProto::kDefaultmapping: {\n    eos::console::VidProto_DefaultMappingProto_Option opt =\n      mRequest->vid().defaultmapping().option();\n    eos::console::VidProto_DefaultMappingProto_Type type =\n      mRequest->vid().defaultmapping().type();\n\n    if (opt == eos::console::VidProto_DefaultMappingProto_Option_ENABLE) {\n      cmd_in1 += \"&mgm.subcmd=set\";\n      cmd_in1 += \"&mgm.vid.cmd=map\";\n      cmd_in1 += \"&mgm.vid.pattern=<pwd>\";\n      cmd_in1 += \"&mgm.vid.key=<key>\";\n\n      if (type == eos::console::VidProto_DefaultMappingProto_Type_KRB5) {\n        cmd_in1 += \"&mgm.vid.auth=krb5\";\n        cmd_in1 += \"&mgm.vid.uid=0\";\n        cmd_in1 += \"&mgm.vid.gid=0\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_GSI) {\n        cmd_in1 += \"&mgm.vid.auth=gsi\";\n        cmd_in1 += \"&mgm.vid.uid=0\";\n        cmd_in1 += \"&mgm.vid.gid=0\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_SSS) {\n        cmd_in1 += \"&mgm.vid.auth=sss\";\n        cmd_in1 += \"&mgm.vid.uid=0\";\n        cmd_in1 += \"&mgm.vid.gid=0\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_UNIX) {\n        cmd_in1 += \"&mgm.vid.auth=unix\";\n        cmd_in1 += \"&mgm.vid.uid=99\";\n        cmd_in1 += \"&mgm.vid.gid=99\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_HTTPS) {\n        cmd_in1 += \"&mgm.vid.auth=https\";\n        cmd_in1 += \"&mgm.vid.uid=0\";\n        cmd_in1 += \"&mgm.vid.gid=0\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_TIDENT) {\n        cmd_in1 += \"&mgm.vid.auth=tident\";\n        cmd_in1 += \"&mgm.vid.uid=0\";\n        cmd_in1 += \"&mgm.vid.gid=0\";\n      }\n    } else if (opt == eos::console::VidProto_DefaultMappingProto_Option_DISABLE) {\n      has_cmd2 = true;\n      cmd_in1 += \"&mgm.subcmd=rm\";\n      cmd_in1 += \"&mgm.vid.cmd=unmap\";\n      cmd_in2 += \"&mgm.subcmd=rm\";\n      cmd_in2 += \"&mgm.vid.cmd=unmap\";\n\n      if (type == eos::console::VidProto_DefaultMappingProto_Type_KRB5) {\n        cmd_in1 += \"&mgm.vid.key=krb5:\\\"<pwd>\\\":uid\";\n        cmd_in2 += \"&mgm.vid.key=krb5:\\\"<pwd>\\\":gid\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_GSI) {\n        cmd_in1 += \"&mgm.vid.key=gsi:\\\"<pwd>\\\":uid\";\n        cmd_in2 += \"&mgm.vid.key=gsi:\\\"<pwd>\\\":gid\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_SSS) {\n        cmd_in1 += \"&mgm.vid.key=sss:\\\"<pwd>\\\":uid\";\n        cmd_in2 += \"&mgm.vid.key=sss:\\\"<pwd>\\\":gid\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_UNIX) {\n        cmd_in1 += \"&mgm.vid.key=unix:\\\"<pwd>\\\":uid\";\n        cmd_in2 += \"&mgm.vid.key=unix:\\\"<pwd>\\\":gid\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_HTTPS) {\n        cmd_in1 += \"&mgm.vid.key=https:\\\"<pwd>\\\":uid\";\n        cmd_in2 += \"&mgm.vid.key=https:\\\"<pwd>\\\":gid\";\n      } else if (type == eos::console::VidProto_DefaultMappingProto_Type_TIDENT) {\n        cmd_in1 += \"&mgm.vid.key=tident:\\\"<pwd>\\\":uid\";\n        cmd_in2 += \"&mgm.vid.key=tident:\\\"<pwd>\\\":gid\";\n      }\n    }\n\n    break;\n  }\n\n  case eos::console::VidProto::kLs: {\n    cmd_in1 += \"&mgm.subcmd=ls\";\n\n    if (mRequest->vid().ls().user_role() || mRequest->vid().ls().group_role() ||\n        mRequest->vid().ls().sudoers() || mRequest->vid().ls().user_alias() ||\n        mRequest->vid().ls().group_alias() || mRequest->vid().ls().gateway() ||\n        mRequest->vid().ls().auth() || mRequest->vid().ls().deepness() ||\n        mRequest->vid().ls().geo_location() || mRequest->vid().ls().num_ids()) {\n      cmd_in1 += \"&mgm.vid.option=\";\n    }\n\n    if (mRequest->vid().ls().user_role()) {\n      cmd_in1 += \"u\";\n    }\n\n    if (mRequest->vid().ls().group_role()) {\n      cmd_in1 += \"g\";\n    }\n\n    if (mRequest->vid().ls().sudoers()) {\n      cmd_in1 += \"s\";\n    }\n\n    if (mRequest->vid().ls().user_alias()) {\n      cmd_in1 += \"U\";\n    }\n\n    if (mRequest->vid().ls().group_alias()) {\n      cmd_in1 += \"G\";\n    }\n\n    if (mRequest->vid().ls().gateway()) {\n      cmd_in1 += \"y\";\n    }\n\n    if (mRequest->vid().ls().auth()) {\n      cmd_in1 += \"a\";\n    }\n\n    if (mRequest->vid().ls().deepness()) {\n      cmd_in1 += \"N\";\n    }\n\n    if (mRequest->vid().ls().geo_location()) {\n      cmd_in1 += \"l\";\n    }\n\n    if (mRequest->vid().ls().num_ids()) {\n      cmd_in1 += \"n\";\n    }\n\n    break;\n  }\n\n  case eos::console::VidProto::kPublicaccesslevel: {\n    cmd_in1 += \"&mgm.subcmd=set\";\n    cmd_in1 += \"&mgm.vid.cmd=publicaccesslevel\";\n    cmd_in1 += \"&mgm.vid.key=publicaccesslevel\";\n    cmd_in1 += \"&mgm.vid.level=\";\n    cmd_in1 += std::to_string(mRequest->vid().publicaccesslevel().level());\n    break;\n  }\n\n  case eos::console::VidProto::kRm: {\n    if (mRequest->vid().rm().membership()) {\n      has_cmd2 = true;\n      cmd_in1 += \"&mgm.subcmd=rm\";\n      cmd_in1 += \"&mgm.vid.key=vid:\" + mRequest->vid().rm().key() + \":uids\";\n      cmd_in2 += \"&mgm.subcmd=rm\";\n      cmd_in2 += \"&mgm.vid.key=vid:\" + mRequest->vid().rm().key() + \":gids\";\n    } else {\n      cmd_in1 += \"&mgm.subcmd=rm\";\n      cmd_in1 += \"&mgm.vid.key=\" + mRequest->vid().rm().key();\n    }\n\n    break;\n  }\n\n  case eos::console::VidProto::kSetgeotag: {\n    // Check if geotag is valid\n    std::string targetgeotag = mRequest->vid().setgeotag().geotag();\n    std::string geotag = eos::common::SanitizeGeoTag(targetgeotag);\n\n    if (geotag != targetgeotag) {\n      mReply->set_std_err(geotag);\n      mReply->set_retc(EINVAL);\n      return grpc::Status::OK;\n    }\n\n    cmd_in1 += \"&mgm.subcmd=set\";\n    cmd_in1 += \"&mgm.vid.cmd=geotag\";\n    cmd_in1 += \"&mgm.vid.key=geotag:\" + mRequest->vid().setgeotag().prefix();\n    cmd_in1 += \"&mgm.vid.geotag=\" + targetgeotag;\n    break;\n  }\n\n  case eos::console::VidProto::kSetmembership: {\n    eos::console::VidProto_SetMembershipProto_Option opt =\n      mRequest->vid().setmembership().option();\n    std::string user = mRequest->vid().setmembership().user();\n    std::string members = mRequest->vid().setmembership().members();\n    cmd_in1 += \"&mgm.subcmd=set\";\n    cmd_in1 += \"&mgm.vid.cmd=membership\";\n    cmd_in1 += \"&mgm.vid.source.uid=\" + mRequest->vid().setmembership().user();\n\n    if (opt == eos::console::VidProto_SetMembershipProto_Option_USER) {\n      cmd_in1 += \"&mgm.vid.key=\" + user + \":uids\";\n      cmd_in1 += \"&mgm.vid.target.uid=\" + members;\n    } else if (opt == eos::console::VidProto_SetMembershipProto_Option_GROUP) {\n      cmd_in1 += \"&mgm.vid.key=\" + user + \":gids\";\n      cmd_in1 += \"&mgm.vid.target.gid=\" + members;\n    } else if (opt == eos::console::VidProto_SetMembershipProto_Option_ADD_SUDO) {\n      cmd_in1 += \"&mgm.vid.key=\" + user + \":root\";\n      cmd_in1 += \"&mgm.vid.target.sudo=true\";\n    } else if (opt ==\n               eos::console::VidProto_SetMembershipProto_Option_REMOVE_SUDO) {\n      cmd_in1 += \"&mgm.vid.key=\" + user + \":root\";\n      cmd_in1 += \"&mgm.vid.target.sudo=false\";\n    }\n\n    break;\n  }\n\n  case eos::console::VidProto::kSetmap: {\n    eos::console::VidProto_SetMapProto_Type type = mRequest->vid().setmap().type();\n    cmd_in1 += \"&mgm.subcmd=set\";\n    cmd_in1 += \"&mgm.vid.cmd=map\";\n\n    if (type == eos::console::VidProto_SetMapProto_Type_KRB5) {\n      cmd_in1 += \"&mgm.vid.auth=krb5\";\n    } else if (type == eos::console::VidProto_SetMapProto_Type_GSI) {\n      cmd_in1 += \"&mgm.vid.auth=gsi\";\n    } else if (type == eos::console::VidProto_SetMapProto_Type_HTTPS) {\n      cmd_in1 += \"&mgm.vid.auth=https\";\n    } else if (type == eos::console::VidProto_SetMapProto_Type_SSS) {\n      cmd_in1 += \"&mgm.vid.auth=sss\";\n    } else if (type == eos::console::VidProto_SetMapProto_Type_UNIX) {\n      cmd_in1 += \"&mgm.vid.auth=unix\";\n    } else if (type == eos::console::VidProto_SetMapProto_Type_TIDENT) {\n      cmd_in1 += \"&mgm.vid.auth=tident\";\n    } else if (type == eos::console::VidProto_SetMapProto_Type_VOMS) {\n      cmd_in1 += \"&mgm.vid.auth=voms\";\n    } else if (type == eos::console::VidProto_SetMapProto_Type_GRPC) {\n      cmd_in1 += \"&mgm.vid.auth=grpc\";\n    }\n\n    cmd_in1 += \"&mgm.vid.key=<key>\";\n    cmd_in1 += \"&mgm.vid.pattern=\" + mRequest->vid().setmap().pattern();\n\n    if (!mRequest->vid().setmap().vgid_only()) {\n      cmd_in1 += \"&mgm.vid.uid=\" + std::to_string(mRequest->vid().setmap().vuid());\n    }\n\n    if (!mRequest->vid().setmap().vuid_only()) {\n      cmd_in1 += \"&mgm.vid.gid=\" + std::to_string(mRequest->vid().setmap().vgid());\n    }\n\n    break;\n  }\n\n  default:\n    mReply->set_std_err(\"error: subcommand is not supported\");\n    mReply->set_retc(EINVAL);\n    return grpc::Status::OK;\n  }\n\n  cmd1.open(\"/proc/admin\", cmd_in1.c_str(), *mVid, &error1);\n  cmd1.AddOutput(std_out1, std_err1);\n  cmd1.close();\n\n  if (has_cmd2) {\n    cmd2.open(\"/proc/admin\", cmd_in2.c_str(), *mVid, &error2);\n    cmd2.AddOutput(std_out2, std_err2);\n    cmd2.close();\n\n    if (!std_out1.empty()) {\n      std_out1.insert(0, \"UID: \");\n    }\n\n    if (!std_err1.empty()) {\n      std_err1.insert(0, \"UID: \");\n      std_err1 += \"\\n\";\n    }\n\n    if (!std_out2.empty()) {\n      std_out2.insert(0, \"GID: \");\n    }\n\n    if (!std_err2.empty()) {\n      std_err2.insert(0, \"GID: \");\n      std_err2 += \"\\n\";\n    }\n  }\n\n  mReply->set_std_out(std_out1 + std_out2);\n  mReply->set_std_err(std_err1 + std_err2);\n  mReply->set_retc((cmd1.GetRetc() > cmd2.GetRetc()) ? cmd1.GetRetc() :\n                   cmd2.GetRetc());\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Who()\n{\n  std::string cmd_in = \"mgm.cmd=who\";\n\n  if (mRequest->who().showclients() || mRequest->who().showauth() ||\n      mRequest->who().showall() || mRequest->who().showsummary() ||\n      mRequest->who().monitoring()) {\n    cmd_in += \"&mgm.option=\";\n  }\n\n  if (mRequest->who().showclients()) {\n    cmd_in += \"c\";\n  }\n\n  if (mRequest->who().showauth()) {\n    cmd_in += \"z\";\n  }\n\n  if (mRequest->who().showall()) {\n    cmd_in += \"a\";\n  }\n\n  if (mRequest->who().showsummary()) {\n    cmd_in += \"s\";\n  }\n\n  if (mRequest->who().monitoring()) {\n    cmd_in += \"m\";\n  }\n\n  ExecProcCmd(cmd_in, false);\n  return grpc::Status::OK;\n}\n\ngrpc::Status GrpcWncInterface::Whoami()\n{\n  std::string cmd_in = \"mgm.cmd=whoami\";\n  ExecProcCmd(cmd_in, false);\n  return grpc::Status::OK;\n}\n\n//-----------------------------------------------------------------------------\n//-----------------------------------------------------------------------------\n//  Additional functions needed by GrpcWncInterface::File function\n//-----------------------------------------------------------------------------\n//-----------------------------------------------------------------------------\n\n//-----------------------------------------------------------------------------\n//! Convert an FST env representation to an Fmd struct\n//! (Specific for 'eos file check' command)\n//!\n//! @param env env representation\n//! @param fmd reference to Fmd struct\n//!\n//! @return true if successful otherwise false\n//-----------------------------------------------------------------------------\nbool\nFile_EnvFstToFmd(XrdOucEnv& env, eos::common::FmdHelper& fmd)\n{\n  // Check that all tags are present\n  if (!env.Get(\"id\") ||\n      !env.Get(\"cid\") ||\n      !env.Get(\"ctime\") ||\n      !env.Get(\"ctime_ns\") ||\n      !env.Get(\"mtime\") ||\n      !env.Get(\"mtime_ns\") ||\n      !env.Get(\"size\") ||\n      !env.Get(\"lid\") ||\n      !env.Get(\"uid\") ||\n      !env.Get(\"gid\")) {\n    return false;\n  }\n\n  fmd.mProtoFmd.set_fid(strtoull(env.Get(\"id\"), 0, 10));\n  fmd.mProtoFmd.set_cid(strtoull(env.Get(\"cid\"), 0, 10));\n  fmd.mProtoFmd.set_ctime(strtoul(env.Get(\"ctime\"), 0, 10));\n  fmd.mProtoFmd.set_ctime_ns(strtoul(env.Get(\"ctime_ns\"), 0, 10));\n  fmd.mProtoFmd.set_mtime(strtoul(env.Get(\"mtime\"), 0, 10));\n  fmd.mProtoFmd.set_mtime_ns(strtoul(env.Get(\"mtime_ns\"), 0, 10));\n  fmd.mProtoFmd.set_size(strtoull(env.Get(\"size\"), 0, 10));\n  fmd.mProtoFmd.set_lid(strtoul(env.Get(\"lid\"), 0, 10));\n  fmd.mProtoFmd.set_uid((uid_t) strtoul(env.Get(\"uid\"), 0, 10));\n  fmd.mProtoFmd.set_gid((gid_t) strtoul(env.Get(\"gid\"), 0, 10));\n\n  if (env.Get(\"checksum\")) {\n    fmd.mProtoFmd.set_checksum(env.Get(\"checksum\"));\n\n    if (fmd.mProtoFmd.checksum() == \"none\") {\n      fmd.mProtoFmd.set_checksum(\"\");\n    }\n  } else {\n    fmd.mProtoFmd.set_checksum(\"\");\n  }\n\n  if (env.Get(\"diskchecksum\")) {\n    fmd.mProtoFmd.set_diskchecksum(env.Get(\"diskchecksum\"));\n\n    if (fmd.mProtoFmd.diskchecksum() == \"none\") {\n      fmd.mProtoFmd.set_diskchecksum(\"\");\n    }\n  } else {\n    fmd.mProtoFmd.set_diskchecksum(\"\");\n  }\n\n  return true;\n}\n\n//-----------------------------------------------------------------------------\n//! Return a remote file attribute\n//! (Specific for 'eos file check' command)\n//!\n//! @param manager host:port of the server to contact\n//! @param key extended attribute key to get\n//! @param path file path to read attributes from\n//! @param attribute reference where to store the attribute value\n//-----------------------------------------------------------------------------\nint\nFile_GetRemoteAttribute(const char* manager, const char* key,\n                        const char* path, XrdOucString& attribute)\n{\n  if ((!key) || (!path)) {\n    return EINVAL;\n  }\n\n  int rc = 0;\n  XrdCl::Buffer arg;\n  XrdCl::Buffer* response = 0;\n  XrdCl::XRootDStatus status;\n  XrdOucString fmdquery = \"/?fst.pcmd=getxattr&fst.getxattr.key=\";\n  fmdquery += key;\n  fmdquery += \"&fst.getxattr.path=\";\n  fmdquery += path;\n  XrdOucString address = \"root://\";\n  address += manager;\n  address += \"//dummy\";\n  XrdCl::URL url(address.c_str());\n\n  if (!url.IsValid()) {\n    eos_static_err(\"error=URL is not valid: %s\", address.c_str());\n    return EINVAL;\n  }\n\n  std::unique_ptr<XrdCl::FileSystem> fs(new XrdCl::FileSystem(url));\n\n  if (!fs) {\n    eos_static_err(\"error=failed to get new FS object\");\n    return EINVAL;\n  }\n\n  arg.FromString(fmdquery.c_str());\n  status = fs->Query(XrdCl::QueryCode::OpaqueFile, arg, response);\n\n  if (status.IsOK()) {\n    rc = 0;\n    eos_static_debug(\"got attribute meta data from server %s for key=%s path=%s\"\n                     \" attribute=%s\", manager, key, path, response->GetBuffer());\n  } else {\n    rc = ECOMM;\n    eos_static_err(\"Unable to retrieve meta data from server %s for key=%s path=%s\",\n                   manager, key, path);\n  }\n\n  if (rc) {\n    delete response;\n    return EIO;\n  }\n\n  if (!strncmp(response->GetBuffer(), \"ERROR\", 5)) {\n    // remote side couldn't get the record\n    eos_static_info(\"Unable to retrieve meta data on remote server %s for key=%s \"\n                    \"path=%s\", manager, key, path);\n    delete response;\n    return ENODATA;\n  }\n\n  attribute = response->GetBuffer();\n  delete response;\n  return 0;\n}\n\n//-----------------------------------------------------------------------------\n//! Return Fmd from a remote filesystem\n//! (Specific for 'eos file check' command)\n//!\n//! @param manager host:port of the server to contact\n//! @param shexfid hex string of the file id\n//! @param sfsid string of filesystem id\n//! @param fmd reference to the Fmd struct to store Fmd\n//-----------------------------------------------------------------------------\nint\nFile_GetRemoteFmdFromLocalDb(const char* manager, const char* shexfid,\n                             const char* sfsid, eos::common::FmdHelper& fmd)\n{\n  if ((!manager) || (!shexfid) || (!sfsid)) {\n    return EINVAL;\n  }\n\n  int rc = 0;\n  XrdCl::Buffer arg;\n  XrdCl::Buffer* response = 0;\n  XrdCl::XRootDStatus status;\n  XrdOucString fmdquery = \"/?fst.pcmd=getfmd&fst.getfmd.fid=\";\n  fmdquery += shexfid;\n  fmdquery += \"&fst.getfmd.fsid=\";\n  fmdquery += sfsid;\n  XrdOucString address = \"root://\";\n  address += manager;\n  address += \"//dummy\";\n  XrdCl::URL url(address.c_str());\n\n  if (!url.IsValid()) {\n    eos_static_err(\"error=URL is not valid: %s\", address.c_str());\n    return EINVAL;\n  }\n\n  std::unique_ptr<XrdCl::FileSystem> fs(new XrdCl::FileSystem(url));\n\n  if (!fs) {\n    eos_static_err(\"error=failed to get new FS object\");\n    return EINVAL;\n  }\n\n  arg.FromString(fmdquery.c_str());\n  status = fs->Query(XrdCl::QueryCode::OpaqueFile, arg, response);\n\n  if (status.IsOK()) {\n    rc = 0;\n    eos_static_debug(\"got replica file meta data from server %s for fxid=%s fsid=%s\",\n                     manager, shexfid, sfsid);\n  } else {\n    rc = ECOMM;\n    eos_static_err(\"Unable to retrieve meta data from server %s for fxid=%s fsid=%s\",\n                   manager, shexfid, sfsid);\n  }\n\n  if (rc) {\n    delete response;\n    return EIO;\n  }\n\n  if (!strncmp(response->GetBuffer(), \"ERROR\", 5)) {\n    // remote side couldn't get the record\n    eos_static_info(\"Unable to retrieve meta data on remote server %s for fxid=%s fsid=%s\",\n                    manager, shexfid, sfsid);\n    delete response;\n    return ENODATA;\n  }\n\n  // get the remote file meta data into an env hash\n  XrdOucEnv fmdenv(response->GetBuffer());\n\n  if (!File_EnvFstToFmd(fmdenv, fmd)) {\n    int envlen;\n    eos_static_err(\"Failed to unparse file meta data %s\", fmdenv.Env(envlen));\n    delete response;\n    return EIO;\n  }\n\n  // very simple check\n  if (fmd.mProtoFmd.fid() != eos::common::FileId::Hex2Fid(shexfid)) {\n    eos_static_err(\"Uups! Received wrong meta data from remote server - fid \"\n                   \"is %lu instead of %lu !\", fmd.mProtoFmd.fid(),\n                   eos::common::FileId::Hex2Fid(shexfid));\n    delete response;\n    return EIO;\n  }\n\n  delete response;\n  return 0;\n}\n\nEOSMGMNAMESPACE_END\n\n#endif // EOS_GRPC\n"
  },
  {
    "path": "mgm/grpc/GrpcWncInterface.hh",
    "content": "//-----------------------------------------------------------------------------\n// File: GrpcWncInterface.hh\n// Author: Branko Blagojevic <branko.blagojevic@comtrade.com>\n// Author: Ivan Arizanovic <ivan.arizanovic@comtrade.com>\n//-----------------------------------------------------------------------------\n\n#pragma once\n\n#ifdef EOS_GRPC\n\n//-----------------------------------------------------------------------------\n#include \"common/VirtualIdentity.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"proto/EosWnc.grpc.pb.h\"\n//-----------------------------------------------------------------------------\nusing grpc::ServerWriter;\n//-----------------------------------------------------------------------------\n\nEOSMGMNAMESPACE_BEGIN\n\n/**\n * @file   GrpcWncInterface.hh\n *\n * @brief  This class bridges CLI of EOS Windows native client to gRPC requests\n *\n */\nclass GrpcWncInterface\n{\npublic:\n\n//-----------------------------------------------------------------------------\n// Call appropriate function to execute command from the EOS-wnc gRPC request\n//-----------------------------------------------------------------------------\n  grpc::Status ExecCmd(eos::common::VirtualIdentity& vid,\n                       const eos::console::RequestProto* request,\n                       eos::console::ReplyProto* reply);\n\n  grpc::Status ExecStreamCmd(eos::common::VirtualIdentity& vid,\n                             const eos::console::RequestProto* request,\n                             ServerWriter<eos::console::ReplyProto>* writer);\n//-----------------------------------------------------------------------------\n\nprivate:\n\n//-----------------------------------------------------------------------------\n// Class member variables\n//-----------------------------------------------------------------------------\n  bool mJsonFormat;\n  eos::common::VirtualIdentity* mVid;\n  const eos::console::RequestProto* mRequest;\n  eos::console::ReplyProto* mReply;\n  ServerWriter<eos::console::ReplyProto>* mWriter;\n//-----------------------------------------------------------------------------\n\n  void RoleChanger();\n\n  void ExecProcCmd(std::string input, bool admin = true);\n\n//-----------------------------------------------------------------------------\n//  Execute specific EOS command from the EOS-wnc gRPC request\n//-----------------------------------------------------------------------------\n  grpc::Status Access();\n\n  grpc::Status Acl();\n\n  grpc::Status Archive();\n\n  grpc::Status Attr();\n\n  grpc::Status Backup();\n\n  grpc::Status Chmod();\n\n  grpc::Status Chown();\n\n  grpc::Status Config();\n\n  grpc::Status Convert();\n\n  grpc::Status Cp();\n\n  grpc::Status Debug();\n\n  grpc::Status Evict();\n\n  grpc::Status File();\n\n  grpc::Status Fileinfo();\n\n  grpc::Status Find();\n\n  grpc::Status Fs();\n\n  grpc::Status Fsck();\n\n  grpc::Status Geosched();\n\n  grpc::Status Group();\n\n  grpc::Status Health();\n\n  grpc::Status Io();\n\n  grpc::Status Ls();\n\n  grpc::Status Map();\n\n  grpc::Status Member();\n\n  grpc::Status Mkdir();\n\n  grpc::Status Mv();\n\n  grpc::Status Node();\n\n  grpc::Status Ns();\n\n  grpc::Status Quota();\n\n  grpc::Status Recycle();\n\n  grpc::Status Rm();\n\n  grpc::Status Rmdir();\n\n  grpc::Status Route();\n\n  grpc::Status Space();\n\n  grpc::Status Stat();\n\n  grpc::Status Status();\n\n  grpc::Status Token();\n\n  grpc::Status Touch();\n\n  grpc::Status Version();\n\n  grpc::Status Vid();\n\n  grpc::Status Who();\n\n  grpc::Status Whoami();\n//-----------------------------------------------------------------------------\n};\n\nEOSMGMNAMESPACE_END\n\n#endif // EOS_GRPC\n"
  },
  {
    "path": "mgm/grpc/GrpcWncServer.cc",
    "content": "//-----------------------------------------------------------------------------\n// File: GrpcWncServer.cc\n// Author: Branko Blagojevic <branko.blagojevic@comtrade.com>\n// Author: Ivan Arizanovic <ivan.arizanovic@comtrade.com>\n//-----------------------------------------------------------------------------\n\n//-----------------------------------------------------------------------------\n#include \"GrpcWncServer.hh\"\n//-----------------------------------------------------------------------------\n#include \"console/ConsoleMain.hh\"\n#include \"GrpcServer.hh\"\n#include \"mgm/macros/Macros.hh\"\n//-----------------------------------------------------------------------------\n#ifdef EOS_GRPC\n#include \"proto/EosWnc.grpc.pb.h\"\nusing eos::console::EosWnc;\nusing grpc::ServerContext;\n#endif // EOS_GRPC\n//-----------------------------------------------------------------------------\n\nEOSMGMNAMESPACE_BEGIN\n\n#ifdef EOS_GRPC\n\nclass WncService final : public EosWnc::Service\n{\n  // Process gRPC request from the EOS Windows native client\n  grpc::Status ProcessSingle(ServerContext* context,\n                             const eos::console::RequestProto* request,\n                             eos::console::ReplyProto* reply)\n  {\n    std::string command;\n\n    switch (request->command_case()) {\n    case eos::console::RequestProto::kAccess:\n      command = \"Access\";\n      break;\n\n    case eos::console::RequestProto::kAcl:\n      command = \"Acl\";\n      break;\n\n    case eos::console::RequestProto::kArchive:\n      command = \"Archive\";\n      break;\n\n    case eos::console::RequestProto::kAttr:\n      command = \"Attr\";\n      break;\n\n    case eos::console::RequestProto::kBackup:\n      command = \"Backup\";\n      break;\n\n    case eos::console::RequestProto::kChmod:\n      command = \"Chmod\";\n      break;\n\n    case eos::console::RequestProto::kChown:\n      command = \"Chown\";\n      break;\n\n    case eos::console::RequestProto::kConfig:\n      command = \"Config\";\n      break;\n\n    case eos::console::RequestProto::kConvert:\n      command = \"Convert\";\n      break;\n\n    case eos::console::RequestProto::kCp:\n      command = \"Cp\";\n      break;\n\n    case eos::console::RequestProto::kDebug:\n      command = \"Debug\";\n      break;\n\n    case eos::console::RequestProto::kFile:\n      command = \"File\";\n      break;\n\n    case eos::console::RequestProto::kFileinfo:\n      command = \"Fileinfo\";\n      break;\n\n    case eos::console::RequestProto::kFs:\n      command = \"Fs\";\n      break;\n\n    case eos::console::RequestProto::kFsck:\n      command = \"Fsck\";\n      break;\n\n    case eos::console::RequestProto::kGeosched:\n      command = \"Geosched\";\n      break;\n\n    case eos::console::RequestProto::kGroup:\n      command = \"Group\";\n      break;\n\n    case eos::console::RequestProto::kHealth:\n      command = \"Health\";\n      break;\n\n    case eos::console::RequestProto::kIo:\n      command = \"Io\";\n      break;\n\n    case eos::console::RequestProto::kMap:\n      command = \"Map\";\n      break;\n\n    case eos::console::RequestProto::kMember:\n      command = \"Member\";\n      break;\n\n    case eos::console::RequestProto::kMkdir:\n      command = \"Mkdir\";\n      break;\n\n    case eos::console::RequestProto::kMv:\n      command = \"Mv\";\n      break;\n\n    case eos::console::RequestProto::kNode:\n      command = \"Node\";\n      break;\n\n    case eos::console::RequestProto::kNs:\n      command = \"Ns\";\n      break;\n\n    case eos::console::RequestProto::kQuota:\n      command = \"Quota\";\n      break;\n\n    case eos::console::RequestProto::kRecycle:\n      command = \"Recycle\";\n      break;\n\n    case eos::console::RequestProto::kRm:\n      command = \"Rm\";\n      break;\n\n    case eos::console::RequestProto::kRmdir:\n      command = \"Rmdir\";\n      break;\n\n    case eos::console::RequestProto::kRoute:\n      command = \"Route\";\n      break;\n\n    case eos::console::RequestProto::kSpace:\n      command = \"Space\";\n      break;\n\n    case eos::console::RequestProto::kStat:\n      command = \"Stat\";\n      break;\n\n    case eos::console::RequestProto::kStatus:\n      command = \"Status\";\n      break;\n\n    case eos::console::RequestProto::kToken:\n      command = \"Token\";\n      break;\n\n    case eos::console::RequestProto::kTouch:\n      command = \"Touch\";\n      break;\n\n    case eos::console::RequestProto::kVersion:\n      command = \"Version\";\n      break;\n\n    case eos::console::RequestProto::kVid:\n      command = \"Vid\";\n      break;\n\n    case eos::console::RequestProto::kWho:\n      command = \"Who\";\n      break;\n\n    case eos::console::RequestProto::kWhoami:\n      command = \"Whoami\";\n      break;\n\n    default:\n      command = \"ping\";\n      break;\n    }\n\n    eos_static_debug(\"eos-wnc request from peer=%s IP=%s DN=%s token=%s command='%s'\",\n                     context->peer().c_str(),\n                     GrpcServer::IP(context).c_str(),\n                     GrpcServer::DN(context).c_str(),\n                     request->auth().authkey().c_str(),\n                     command.c_str());\n    eos::common::VirtualIdentity vid;\n    GrpcServer::Vid(context, vid, request->auth().authkey());\n    WAIT_BOOT;\n    GrpcWncInterface wnc;\n    return wnc.ExecCmd(vid, request, reply);\n  }\n\n  // Process gRPC request from the EOS Windows native client for metadata or realtime reply\n  grpc::Status ProcessStream(ServerContext* context,\n                             const eos::console::RequestProto* request,\n                             grpc::ServerWriter<eos::console::ReplyProto>* writer)\n  {\n    std::string command;\n\n    switch (request->command_case()) {\n    case eos::console::RequestProto::kFind:\n      command = \"Find\";\n      break;\n\n    case eos::console::RequestProto::kLs:\n      command = \"Ls\";\n      break;\n\n    default:\n      command = \"unknown\";\n      break;\n    }\n\n    eos_static_debug(\"eos-wnc request from peer=%s IP=%s DN=%s token=%s command='%s'\",\n                     context->peer().c_str(),\n                     GrpcServer::IP(context).c_str(),\n                     GrpcServer::DN(context).c_str(),\n                     request->auth().authkey().c_str(),\n                     command.c_str());\n    eos::common::VirtualIdentity vid;\n    GrpcServer::Vid(context, vid, request->auth().authkey());\n    WAIT_BOOT;\n    GrpcWncInterface wnc;\n    return wnc.ExecStreamCmd(vid, request, writer);\n  }\n};\n\n#endif\n\n//-----------------------------------------------------------------------------\n// Run gRPC server for EOS Windows native client\n//-----------------------------------------------------------------------------\nvoid\nGrpcWncServer::RunWnc(ThreadAssistant& assistant) noexcept\n{\n#ifdef EOS_GRPC\n\n  if (getenv(\"EOS_MGM_WNC_SSL_CERT\") &&\n      getenv(\"EOS_MGM_WNC_SSL_KEY\") &&\n      getenv(\"EOS_MGM_WNC_SSL_CA\")) {\n    mSSL = true;\n    mSSLCertFile = getenv(\"EOS_MGM_WNC_SSL_CERT\");\n    mSSLKeyFile = getenv(\"EOS_MGM_WNC_SSL_KEY\");\n    mSSLCaFile = getenv(\"EOS_MGM_WNC_SSL_CA\");\n\n    if (eos::common::StringConversion::LoadFileIntoString(mSSLCertFile.c_str(),\n        mSSLCert) && !mSSLCert.length()) {\n      eos_static_crit(\"Unable to load SSL certificate file '%s'\",\n                      mSSLCertFile.c_str());\n      mSSL = false;\n    }\n\n    if (eos::common::StringConversion::LoadFileIntoString(mSSLKeyFile.c_str(),\n        mSSLKey) && !mSSLKey.length()) {\n      eos_static_crit(\"Unable to load SSL key file '%s'\", mSSLKeyFile.c_str());\n      mSSL = false;\n    }\n\n    if (eos::common::StringConversion::LoadFileIntoString(mSSLCaFile.c_str(),\n        mSSLCa) && !mSSLCa.length()) {\n      eos_static_crit(\"Unable to load SSL CA file '%s'\", mSSLCaFile.c_str());\n      mSSL = false;\n    }\n  }\n\n  if (gGlobalOpts.mMgmUri.empty()) {\n    if (getenv(\"EOS_MGM_URL\")) {\n      gGlobalOpts.mMgmUri = getenv(\"EOS_MGM_URL\");\n    } else {\n      gGlobalOpts.mMgmUri = \"root://localhost\";\n    }\n  }\n\n  eos_static_info(\"Creating gRPC server for EOS-wnc.\");\n  grpc::ServerBuilder wncBuilder;\n  std::string bind_address_Wnc = \"0.0.0.0:\";\n  bind_address_Wnc += std::to_string(mWncPort);\n\n  if (mSSL) {\n    grpc::SslServerCredentialsOptions::PemKeyCertPair keycert = {\n      mSSLKey,\n      mSSLCert\n    };\n    grpc::SslServerCredentialsOptions sslOps(\n      GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_AND_VERIFY);\n    sslOps.pem_root_certs = mSSLCa;\n    sslOps.pem_key_cert_pairs.push_back(keycert);\n    wncBuilder.AddListeningPort(bind_address_Wnc,\n                                grpc::SslServerCredentials(sslOps));\n    eos_static_info(\"SSL authentication is enabled on gRPC server for EOS-wnc.\");\n  } else {\n    wncBuilder.AddListeningPort(bind_address_Wnc,\n                                grpc::InsecureServerCredentials());\n  }\n\n  WncService wncService;\n  wncBuilder.RegisterService(&wncService);\n  mWncServer = wncBuilder.BuildAndStart();\n\n  if (mWncServer) {\n    eos_static_info(\"gRPC server for EOS-wnc is running on port %i.\", mWncPort);\n    /*WARNING: The server must be either shutting down or\n    *some other thread must call a Shutdown for Wait function to ever return.*/\n    mWncServer->Wait();\n  } else {\n    eos_static_err(\"gRPC server for EOS-wnc failed to start!\");\n  }\n\n#else\n  (void) mWncPort;\n  (void) mSSL;\n#endif\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/grpc/GrpcWncServer.hh",
    "content": "//-----------------------------------------------------------------------------\n// File: GrpcWncServer.hh\n// Author: Branko Blagojevic <branko.blagojevic@comtrade.com>\n//-----------------------------------------------------------------------------\n\n#pragma once\n\n//-----------------------------------------------------------------------------\n#include \"mgm/Namespace.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/Logging.hh\"\n#include \"GrpcWncInterface.hh\"\n//-----------------------------------------------------------------------------\n#ifdef EOS_GRPC\n#include <grpc++/grpc++.h>\n#endif\n//-----------------------------------------------------------------------------\n\nEOSMGMNAMESPACE_BEGIN\n\n/**\n * @file   GrpcWncServer.hh\n *\n * @brief  This class implements a gRPC server for EOS Windows native client\n * running embedded in the MGM\n *\n */\nclass GrpcWncServer\n{\nprivate:\n  int mWncPort; // 50052 by default\n  bool mSSL;\n  std::string mSSLCert;\n  std::string mSSLKey;\n  std::string mSSLCa;\n  std::string mSSLCertFile;\n  std::string mSSLKeyFile;\n  std::string mSSLCaFile;\n  AssistedThread mThread; // Thread running gRPC service\n\n#ifdef EOS_GRPC\n  std::unique_ptr<grpc::Server> mWncServer;\n#endif\n\npublic:\n\n  // Default Constructor - enabling port 50052 by default\n  GrpcWncServer(int port = 50052) : mWncPort(port), mSSL(false) { }\n\n  ~GrpcWncServer()\n  {\n#ifdef EOS_GRPC\n\n    if (mWncServer) {\n      eos_static_info(\"%s\", \"msg=\\\"stopping gRPC server for EOS-wnc\\\"\");\n      mWncServer->Shutdown();\n    }\n\n#endif\n    mThread.join();\n  }\n\n  // Run gRPC server for EOS Windows native client\n  void RunWnc(ThreadAssistant& assistant) noexcept;\n\n  // Create thread for gRPC server for EOS Windows native client\n  void StartWnc()\n  {\n    mThread.reset(&GrpcWncServer::RunWnc, this);\n  }\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/HttpHandler.cc",
    "content": "// ----------------------------------------------------------------------\n// File: HttpHandler.cc\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"XrdSfs/XrdSfsFlags.hh\"\n#include \"mgm/http/HttpServer.hh\"\n#include \"mgm/http/HttpHandler.hh\"\n#include \"mgm/ofs/XrdMgmOfsDirectory.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"common/Timing.hh\"\n#include \"common/ErrnoToString.hh\"\n#include \"common/http/PlainHttpResponse.hh\"\n#include \"common/http/OwnCloud.hh\"\n#include \"namespace/utils/Mode.hh\"\n#include \"mgm/http/rest-api/handler/tape/TapeRestHandler.hh\"\n#include \"mgm/http/rest-api/manager/RestApiManager.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\nbool\nHttpHandler::Matches(const std::string& meth, HeaderMap& headers)\n{\n  int method = ParseMethodString(meth);\n\n  if (method == GET || method == HEAD || method == POST ||\n      method == PUT || method == DELETE || method == TRACE ||\n      method == OPTIONS || method == CONNECT || method == PATCH) {\n    eos_static_debug(\"Matched HTTP protocol for request\");\n    return true;\n  } else {\n    return false;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nHttpHandler::HandleRequest(eos::common::HttpRequest* request)\n{\n  eos_static_debug(\"handling http request\");\n  eos::common::HttpResponse* response = 0;\n  bool isRestRequest = gOFS->mRestApiManager->isRestRequest(request->GetUrl());\n\n  if (isRestRequest) {\n    response = gOFS->mRestApiManager->getRestHandler(\n                 request->GetUrl())->handleRequest(request, mVirtualIdentity);\n  } else {\n    request->AddEosApp();\n\n    if (EOS_LOGS_DEBUG) {\n      for (auto it = request->GetHeaders().begin();\n           it != request->GetHeaders().end(); ++it) {\n        eos_static_debug(\"header:%s => %s\", it->first.c_str(), it->second.c_str());\n      }\n    }\n\n    int meth = ParseMethodString(request->GetMethod());\n    {\n      // call the routing module before doing anything with http\n      int port;\n      std::string host;\n      int stall_timeout = 0;\n\n      if (gOFS->ShouldRoute(\n            __FUNCTION__, 0, *mVirtualIdentity, request->GetUrl().c_str(),\n            request->GetQuery().c_str(), host, port, stall_timeout)) {\n        std::string url_enc = eos::common::StringConversion::curl_path_escaped\n                              (request->GetUrl().c_str());\n        response = HttpServer::HttpRedirect(request->GetUrl().c_str(),\n                                            host.c_str(), port, false);\n        mHttpResponse = response;\n        return;\n      }\n    }\n\n    switch (meth) {\n    case GET:\n      gOFS->MgmStats.Add(\"Http-GET\", mVirtualIdentity->uid,\n                         mVirtualIdentity->gid, 1);\n      response = Get(request);\n      break;\n\n    case HEAD:\n      gOFS->MgmStats.Add(\"Http-HEAD\", mVirtualIdentity->uid,\n                         mVirtualIdentity->gid, 1);\n      response = Head(request);\n      response->SetBody(\"\");\n      break;\n\n    case POST:\n      gOFS->MgmStats.Add(\"Http-POST\", mVirtualIdentity->uid,\n                         mVirtualIdentity->gid, 1);\n      response = Post(request);\n      break;\n\n    case PUT:\n      gOFS->MgmStats.Add(\"Http-PUT\", mVirtualIdentity->uid,\n                         mVirtualIdentity->gid, 1);\n      response = Put(request);\n      break;\n\n    case DELETE:\n      gOFS->MgmStats.Add(\"Http-DELETE\", mVirtualIdentity->uid,\n                         mVirtualIdentity->gid, 1);\n      response = Delete(request);\n      break;\n\n    case TRACE:\n      gOFS->MgmStats.Add(\"Http-TRACE\", mVirtualIdentity->uid,\n                         mVirtualIdentity->gid, 1);\n      response = Trace(request);\n      break;\n\n    case OPTIONS:\n      gOFS->MgmStats.Add(\"Http-OPTIONS\", mVirtualIdentity->uid,\n                         mVirtualIdentity->gid, 1);\n      response = Options(request);\n      break;\n\n    case CONNECT:\n      gOFS->MgmStats.Add(\"Http-CONNECT\", mVirtualIdentity->uid,\n                         mVirtualIdentity->gid, 1);\n      response = Connect(request);\n      break;\n\n    case PATCH:\n      gOFS->MgmStats.Add(\"Http-PATCH\", mVirtualIdentity->uid,\n                         mVirtualIdentity->gid, 1);\n      response = Patch(request);\n      break;\n\n    default:\n      response = new eos::common::PlainHttpResponse();\n      response->SetResponseCode(eos::common::HttpResponse::BAD_REQUEST);\n      response->SetBody(\"No such method\");\n    }\n  }\n\n  mHttpResponse = response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nHttpHandler::Get(eos::common::HttpRequest* request, bool isHEAD)\n{\n  using eos::common::ErrnoToString;\n  XrdSecEntity client(mVirtualIdentity->prot.c_str());\n  client.name = const_cast<char*>(mVirtualIdentity->uid_string.c_str());\n  client.host = const_cast<char*>(mVirtualIdentity->host.c_str());\n  client.tident = const_cast<char*>(mVirtualIdentity->tident.c_str());\n  // Classify path to split between directory or file objects\n  bool isfile = true;\n  std::string url = request->GetUrl();\n  std::string query = request->GetQuery();\n  eos::common::HttpResponse* response = 0;\n  struct stat buf;\n  bool statOK = false;\n  XrdOucString spath = request->GetUrl().c_str();\n  std::string etag = \"undef\";\n  eos::common::OwnCloud::OwnCloudRemapping(spath, request);\n  eos::common::OwnCloud::ReplaceRemotePhp(spath);\n\n  if (!spath.beginswith(\"/proc/\")) {\n    XrdOucErrInfo error(mVirtualIdentity->tident.c_str());\n    {\n      // check if this is a symlink\n      XrdOucString link;\n\n      if ((!gOFS->_readlink(url.c_str(), error, *mVirtualIdentity, link)) &&\n          (link != \"\") && (link.beginswith(\"http://\") ||\n                           link.beginswith(\"https://\"))) {\n        if (gOFS->access(url.c_str(), R_OK, error, &client, query.c_str())) {\n          // no permission or entry doesn't exist\n          eos_static_info(\"method=GET error=%i path=%s\", error.getErrInfo(),\n                          url.c_str());\n          response = HttpServer::HttpError(ErrnoToString(error.getErrInfo()).c_str(),\n                                           (error.getErrInfo() == ENOENT) ?\n                                           response->NOT_FOUND :\n                                           response->FORBIDDEN);\n          return response;\n        }\n\n        // create an external redirect\n        response = new eos::common::PlainHttpResponse();\n        response->SetResponseCode(\n          eos::common::HttpResponse::ResponseCodes::TEMPORARY_REDIRECT);\n        response->AddHeader(\"Location\", link.c_str());\n        response->AddHeader(\"X-Accel-Redirect\", link.c_str());\n        response->AddHeader(\"X-Sendfile\", link.c_str());\n        return response;\n      } else {\n        if (gOFS->access(url.c_str(), R_OK, error, &client, query.c_str())) {\n          // no permission or entry doesn't exist\n          eos_static_info(\"method=GET error=%i path=%s\", error.getErrInfo(),\n                          url.c_str());\n          response = HttpServer::HttpError(ErrnoToString(error.getErrInfo()).c_str(),\n                                           (error.getErrInfo() == ENOENT) ?\n                                           response->NOT_FOUND :\n                                           response->FORBIDDEN);\n          return response;\n        }\n      }\n    }\n\n    if (gOFS->stat(url.c_str(), &buf, error, &etag, &client, query.c_str())) {\n      eos_static_info(\"method=GET error=ENOENT path=%s\",\n                      url.c_str());\n      response = HttpServer::HttpError(\"No such file or directory\",\n                                       response->NOT_FOUND);\n      return response;\n    }\n\n    statOK = true;\n\n    if (request->GetHeaders().count(\"if-match\") &&\n        (etag != request->GetHeaders()[\"if-match\"])) {\n      // ETag mismatch\n      eos_static_info(\"method=GET error=precondition-failed path=%s etag=%s cond=match r-etag=%s\",\n                      url.c_str(), etag.c_str(), request->GetHeaders()[\"If-Match\"].c_str());\n      response = HttpServer::HttpError(\"ETag precondition failed\",\n                                       response->PRECONDITION_FAILED);\n      return response;\n    }\n\n    if (request->GetHeaders().count(\"if-non-match\") &&\n        (etag == request->GetHeaders()[\"if-non-match\"])) {\n      // ETag match\n      eos_static_info(\"method=GET error=precondition-failed path=%s etag=%s cond=not-match r-etag=%s\",\n                      url.c_str(), etag.c_str(), request->GetHeaders()[\"if-not-match\"].c_str());\n      response = HttpServer::HttpError(\"ETag is not modified\",\n                                       response->NOT_MODIFIED);\n      return response;\n    }\n\n    // find out if it is a file or directory\n    if (S_ISDIR(buf.st_mode)) {\n      isfile = false;\n\n      if (isHEAD) {\n        // HEAD requests for dirs just act like 'exists'\n        eos_static_info(\"cmd=GET(HEAD) size=%llu path=%s type=dir\",\n                        buf.st_size,\n                        url.c_str());\n        response = new eos::common::PlainHttpResponse();\n        response->SetBody(\"\");\n        response->AddHeader(\"ETag\", etag);\n        response->AddHeader(\"Last-Modified\",\n                            eos::common::Timing::utctime(buf.st_mtime));\n        return response;\n      }\n    } else {\n      isfile = true;\n\n      if (isHEAD) {\n        std::string basename = url.substr(url.rfind('/') + 1);\n        eos_static_info(\"cmd=GET(HEAD) size=%llu path=%s type=file\",\n                        buf.st_size,\n                        url.c_str());\n        // HEAD requests on files can return from the MGM without redirection\n        response = HttpServer::HttpHead(buf.st_size, basename);\n        response->AddHeader(\"ETag\", etag);\n        response->AddHeader(\"Last-Modified\",\n                            eos::common::Timing::utctime(buf.st_mtime));\n\n        if (request->GetHeaders().count(\"want-digest\")) {\n          std::string type = request->GetHeaders()[\"want-digest\"];\n          type = LC_STRING(type);\n          XrdOucString digest = \"\";\n          eos_static_debug(\"method=HEAD, path=%s, checksum requested=%s\",\n                           url.c_str(), type.c_str());\n          //check if there is a checksum type and checksum\n          std::string xstype;\n          std::string xs;\n\n          if (!gOFS->_getchecksum(url.c_str(),\n                                  error,\n                                  &xstype,\n                                  &xs,\n                                  &client,\n                                  query.c_str())) {\n            //check if the type match what requested\n            if (xstype == type) {\n              eos_static_debug(\"method=HEAD, path=%s, checksum requested=%s, checksum available=%s\",\n                               url.c_str(), type.c_str(), xstype.c_str());\n              digest += xstype.c_str();\n              digest += \"=\";\n              digest += xs.c_str();\n              response->AddHeader(\"Digest\", digest.c_str());\n            }\n          }\n        }\n\n        return response;\n      }\n    }\n  }\n\n  if (!isfile) {\n    eos_static_info(\"method=GET dir=%s\",\n                    url.c_str());\n    errno = 0;\n    {\n      // Check if there is an index attribute\n      std::string index;\n      XrdOucErrInfo error(mVirtualIdentity->tident.c_str());\n\n      if (!gOFS->_attr_get(url.c_str(), error, *mVirtualIdentity, query.c_str(),\n                           \"sys.http.index\", index)) {\n        if (gOFS->access(url.c_str(), R_OK, error, &client, query.c_str())) {\n          // no permission or entry doesn't exist\n          eos_static_info(\"method=GET error=%i path=%s\", error.getErrInfo(),\n                          url.c_str());\n          response = HttpServer::HttpError(ErrnoToString(error.getErrInfo()).c_str(),\n                                           (error.getErrInfo() == ENOENT) ?\n                                           response->NOT_FOUND :\n                                           response->FORBIDDEN);\n          return response;\n        }\n\n        // create an external redirect\n        response = new eos::common::PlainHttpResponse();\n        response->SetResponseCode(\n          eos::common::HttpResponse::ResponseCodes::TEMPORARY_REDIRECT);\n        response->AddHeader(\"Location\", index.c_str());\n        response->AddHeader(\"X-Accel-Redirect\", index.c_str());\n        response->AddHeader(\"X-Sendfile\", index.c_str());\n        return response;\n      }\n    }\n    response =\n      HttpServer::HttpError(\"Browsing is disabled and no index attribute is defined!\",\n                            response->FORBIDDEN);\n    return response;\n  } else {\n    eos_static_info(\"method=GET file=%s tident=%s query=%s\",\n                    url.c_str(), client.tident, query.c_str());\n\n    if (statOK && (buf.st_rdev & XRDSFS_HASBKUP) &&\n        (buf.st_rdev & XRDSFS_OFFLINE)) {\n      // File is located on tape, not on disk - EOS-6132\n      response =\n        HttpServer::HttpError(\"File is stored on tape - no disk replica exists\",\n                              response->FAILED_DEPENDENCY);\n      return response;\n    }\n\n    XrdSfsFile* file = gOFS->newFile((char*) mVirtualIdentity->tident.c_str());\n\n    if (file) {\n      XrdSfsFileOpenMode open_mode = 0;\n      mode_t create_mode = 0;\n      int rc = file->open(url.c_str(), open_mode, create_mode, &client,\n                          query.c_str());\n\n      // TODO (apeters): review this part - dead code open_mode = 0\n      if ((rc != SFS_REDIRECT) && open_mode) {\n        // retry as a file creation\n        open_mode |= SFS_O_CREAT;\n        rc = file->open(url.c_str(), open_mode, create_mode, &client,\n                        query.c_str());\n      }\n\n      if (rc != SFS_OK) {\n        if (rc == SFS_REDIRECT) {\n          std::string url_enc = eos::common::StringConversion::curl_path_escaped\n                                (request->GetUrl().c_str());\n          response = HttpServer::HttpRedirect(url_enc,\n                                              file->error.getErrText(),\n                                              file->error.getErrInfo(), false);\n        } else if (rc == SFS_ERROR) {\n          response = HttpServer::HttpError(file->error.getErrText(),\n                                           file->error.getErrInfo());\n        } else if (rc == SFS_DATA) {\n          response = HttpServer::HttpData(file->error.getErrText(),\n                                          file->error.getErrInfo());\n        } else if (rc == SFS_STALL) {\n          response = HttpServer::HttpStall(file->error.getErrText(),\n                                           file->error.getErrInfo());\n        } else {\n          response = HttpServer::HttpError(\"Unexpected result from file open\",\n                                           EOPNOTSUPP);\n        }\n\n        response->AddHeader(\"ETag\", etag);\n      } else {\n        char buffer[65536];\n        offset_t offset = 0;\n        std::string result;\n\n        do {\n          size_t nread = file->read(offset, buffer, sizeof(buffer));\n\n          if (nread > 0) {\n            result.append(buffer, nread);\n          }\n\n          if (nread != sizeof(buffer)) {\n            break;\n          }\n\n          offset += nread;\n        } while (true);\n\n        file->close();\n        response = new eos::common::PlainHttpResponse();\n        XrdOucErrInfo error(mVirtualIdentity->tident.c_str());\n\n        if (!gOFS->stat(url.c_str(), &buf, error, &etag, &client, \"\")) {\n          response->AddHeader(\"ETag\", etag);\n          response->AddHeader(\"Last-Modified\",\n                              eos::common::Timing::utctime(buf.st_mtime));\n        }\n\n        response->SetBody(result);\n      }\n\n      // clean up the object\n      delete file;\n    }\n  }\n\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nHttpHandler::Head(eos::common::HttpRequest* request)\n{\n  eos::common::HttpResponse* response = Get(request, true);\n  response->mUseFileReaderCallback = false;\n  response->SetBody(\"\");\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nHttpHandler::Post(eos::common::HttpRequest* request)\n{\n  using namespace eos::common;\n  std::string url = request->GetUrl();\n  eos_static_info(\"method=POST error=NOTIMPLEMENTED path=%s\",\n                  url.c_str());\n  HttpResponse* response = new PlainHttpResponse();\n  response->SetResponseCode(HttpResponse::ResponseCodes::NOT_IMPLEMENTED);\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nHttpHandler::Put(eos::common::HttpRequest* request)\n{\n  XrdSecEntity client(mVirtualIdentity->prot.c_str());\n  client.name = const_cast<char*>(mVirtualIdentity->name.c_str());\n  client.host = const_cast<char*>(mVirtualIdentity->host.c_str());\n  client.tident = const_cast<char*>(mVirtualIdentity->tident.c_str());\n  std::string url = request->GetUrl();\n  eos_static_info(\"method=PUT path=%s\",\n                  url.c_str());\n  // Classify path to split between directory or file objects\n  bool isfile = true;\n  bool isOcChunked = false;\n  bool isPartialPut = false;\n  std::map<std::string, std::string> ocHeader;\n  eos::common::HttpResponse* response = 0;\n  XrdOucString spath = request->GetUrl().c_str();\n\n  if (!spath.beginswith(\"/proc/\")) {\n    if (spath.endswith(\"/\")) {\n      isfile = false;\n    }\n  }\n\n  if (eos::common::OwnCloud::isChunkUpload(request)) {\n    isOcChunked = true;\n    // we have to rewrite the path and add some additional header describing\n    // the chunking which was stored in the name\n    url = eos::common::OwnCloud::prepareChunkUpload(request, &response, ocHeader);\n\n    if (response) {\n      return response;\n    }\n  }\n\n  if (request->GetHeaders().count(\"x-upload-range\")) {\n    // this is a partial put, we have to remove the truncate flag\n    isPartialPut = true;\n  }\n\n  std::string etag;\n  {\n    // retrieve the ETag if existing ..\n    struct stat buf;\n    XrdOucErrInfo error(mVirtualIdentity->tident.c_str());\n\n    if (gOFS->stat(url.c_str(), &buf, error, &etag, &client, \"\")) {\n      etag = \"undef\";\n    }\n  }\n\n  if ((etag != \"undef\") && (request->GetHeaders().count(\"if-match\") &&\n                            (etag != request->GetHeaders()[\"if-match\"]))) {\n    // ETag mismatch\n    eos_static_info(\"method=PUT error=precondition-failed path=%s etag=%s cond=match r-etag=%s\",\n                    url.c_str(), etag.c_str(), request->GetHeaders()[\"if-match\"].c_str());\n    response = HttpServer::HttpError(\"ETag precondition failed\",\n                                     response->PRECONDITION_FAILED);\n    return response;\n  }\n\n  if ((etag != \"undef\" && (request->GetHeaders().count(\"if-non-match\") &&\n                           (etag == request->GetHeaders()[\"if-non-match\"])))) {\n    // ETag match\n    eos_static_info(\"method=PUT error=precondition-failed path=%s etag=%s cond=not-match r-etag=%s\",\n                    url.c_str(), etag.c_str(), request->GetHeaders()[\"if-not-match\"].c_str());\n    response = HttpServer::HttpError(\"ETag is not modified\",\n                                     response->NOT_MODIFIED);\n    return response;\n  }\n\n  if (isfile) {\n    XrdSfsFile* file = gOFS->newFile((char*) mVirtualIdentity->tident.c_str());\n\n    if (file) {\n      XrdSfsFileOpenMode open_mode = 0;\n      mode_t create_mode = 0;\n\n      // use the proper creation/open flags for PUT's\n      if (!isPartialPut) {\n        open_mode |= SFS_O_TRUNC;\n      }\n\n      open_mode |= SFS_O_RDWR;\n      create_mode |= (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);\n      std::string query = request->GetQuery();\n\n      if (request->GetHeaders().count(\"content-length\")) {\n        query += \"&eos.bookingsize=\";\n        //or OC chunked uploads we book the full size\n        const char* oclength = eos::common::OwnCloud::getContentSize(request);\n\n        if (oclength) {\n          query += oclength;\n        } else {\n          query += request->GetHeaders()[\"content-length\"];\n        }\n\n        if (!isOcChunked && !isPartialPut) {\n          query += \"&eos.targetsize=\";\n          query += request->GetHeaders()[\"content-length\"];\n        }\n      } else {\n        if (!query.empty()) {\n          query += \"&\";\n        }\n\n        query += \"eos.bookingsize=0\";\n      }\n\n      if (request->GetHeaders().count(\"x-oc-mtime\")) {\n        // there is an X-OC-Mtime header to force the mtime for that file\n        query += \"&eos.mtime=\";\n        query += request->GetHeaders()[\"x-oc-mtime\"];\n      }\n\n      if (request->GetHeaders().count(\"x-upload-mtime\")) {\n        // there is an x-upload-mtime header to force the mtime for that file\n        query += \"&eos.mtime=\";\n        query += request->GetHeaders()[\"x-upload-mtime\"];\n      }\n\n      if (isOcChunked) {\n        // add the OC opaque information\n        query += eos::common::OwnCloud::HeaderToQuery(ocHeader).c_str();\n      }\n\n      // -----------------------------------------------------------\n      // OC clients are switched automatically to atomic upload mode\n      // -----------------------------------------------------------\n      if (request->GetHeaders().count(\"oc-total-length\") || isOcChunked) {\n        if (query.length()) {\n          query += \"&\";\n        }\n\n        query += \"eos.atomic=1\";\n      }\n\n      if (isOcChunked) {\n        if (etag != \"undef\") { // file exists already\n          eos_static_info(\"removing truncation flag \");\n          //open_mode ^= SFS_O_TRUNC;\n        }\n      }\n\n      // -----------------------------------------------------------\n      // 'ArchiveMetadata' header needs to be passed down to CTA\n      // -----------------------------------------------------------\n      if (request->GetHeaders().count(\"archivemetadata\")) {\n        if (query.length()) {\n          query += \"&\";\n        }\n\n        query += \"archivemetadata=\";\n        query += request->GetHeaders()[\"archivemetadata\"];\n      }\n\n      int rc = file->open(url.c_str(), open_mode, create_mode, &client,\n                          query.c_str());\n\n      if (rc != SFS_OK) {\n        if ((rc != SFS_REDIRECT) && open_mode && (file->error.getErrInfo() == ENOENT)) {\n          // retry as a file creation\n          open_mode |= SFS_O_CREAT;\n          open_mode |= SFS_O_TRUNC;\n          rc = file->open(url.c_str(), open_mode, create_mode, &client,\n                          query.c_str());\n        }\n      }\n\n      if (rc != SFS_OK) {\n        if (rc == SFS_REDIRECT) {\n          std::string redirection_cgi = file->error.getErrText();\n          std::string url_enc = eos::common::StringConversion::curl_path_escaped\n                                (request->GetUrl().c_str());\n\n          if (file->error.getErrInfo() == 1094) {\n            // MGM redirect\n            response = HttpServer::HttpRedirect(url_enc,\n                                                redirection_cgi,\n                                                gOFS->mHttpdPort, false);\n          } else {\n            if (isOcChunked) {\n              redirection_cgi += eos::common::OwnCloud::HeaderToQuery(ocHeader).c_str();\n            }\n\n            // FST redirect\n            response = HttpServer::HttpRedirect(url_enc,\n                                                redirection_cgi,\n                                                file->error.getErrInfo(), false);\n          }\n        } else if (rc == SFS_ERROR) {\n          if (file->error.getErrInfo() == ENOENT) {\n            response = HttpServer::HttpError(file->error.getErrText(), 409);\n          } else\n            response = HttpServer::HttpError(file->error.getErrText(),\n                                             file->error.getErrInfo());\n        } else if (rc == SFS_DATA) {\n          response = HttpServer::HttpData(file->error.getErrText(),\n                                          file->error.getErrInfo());\n        } else if (rc == SFS_STALL) {\n          response = HttpServer::HttpStall(file->error.getErrText(),\n                                           file->error.getErrInfo());\n        } else {\n          if (getenv(\"EOS_MGM_ALLOW_HTTP_STALL\") && rc >= SFS_STALL) {\n            // HTTP stall mechanism was enabled and the rc of the open is >= the minimum stalling time\n            // in seconds, return a Service Unavailable error.\n            response = HttpServer::HttpError(\"Access limit reached\", ETXTBSY);\n          } else {\n            response = HttpServer::HttpError(\"Unexpected result from file open\",\n                                             EOPNOTSUPP);\n          }\n        }\n      } else {\n        response = new eos::common::PlainHttpResponse();\n        response->SetResponseCode(response->CREATED);\n      }\n\n      std::string rurl = file->error.getErrText();\n      rurl.erase(0, rurl.find('?') + 1);\n      XrdOucEnv env(rurl.c_str());\n      char* etag = env.Get(\"mgm.etag\");\n\n      if (etag) {\n        // add the ETag into the header\n        response->AddHeader(\"ETag\", etag);\n      }\n\n      // clean up the object\n      delete file;\n    }\n  } else {\n    // DIR requests\n    response = HttpServer::HttpError(\"Not Implemented\", EOPNOTSUPP);\n  }\n\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nHttpHandler::Delete(eos::common::HttpRequest* request)\n{\n  eos::common::HttpResponse* response = 0;\n  XrdOucErrInfo error(mVirtualIdentity->tident.c_str());\n  struct stat buf;\n  ProcCommand cmd;\n  std::string url = request->GetUrl();\n  eos_static_info(\"method=DELETE path=%s\", url.c_str());\n\n  if (gOFS->_stat(request->GetUrl().c_str(), &buf, error,\n                  *mVirtualIdentity, \"\")) {\n    response = HttpServer::HttpError(error.getErrText(), response->NOT_FOUND);\n    return response;\n  }\n\n  XrdOucString info = \"mgm.cmd=rm&mgm.path=\";\n  info += request->GetUrl().c_str();\n\n  if (S_ISDIR(buf.st_mode)) {\n    info += \"&mgm.option=r\";\n  }\n\n  cmd.open(\"/proc/user\", info.c_str(), *mVirtualIdentity, &error);\n  cmd.close();\n  int rc = cmd.GetRetc();\n\n  if (rc != SFS_OK) {\n    if (error.getErrInfo() == EPERM) {\n      response = HttpServer::HttpError(error.getErrText(), response->FORBIDDEN);\n    } else if (error.getErrInfo() == ENOENT) {\n      response = HttpServer::HttpError(error.getErrText(), response->NOT_FOUND);\n    } else {\n      response = HttpServer::HttpError(error.getErrText(), error.getErrInfo());\n    }\n  } else {\n    response = new eos::common::PlainHttpResponse();\n    response->SetResponseCode(response->NO_CONTENT);\n  }\n\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nHttpHandler::Trace(eos::common::HttpRequest* request)\n{\n  using namespace eos::common;\n  std::string url = request->GetUrl();\n  eos_static_info(\"method=TRACE error=NOTIMPLEMENTED path=%s\",\n                  url.c_str());\n  HttpResponse* response = new PlainHttpResponse();\n  response->SetResponseCode(HttpResponse::ResponseCodes::NOT_IMPLEMENTED);\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nHttpHandler::Options(eos::common::HttpRequest* request)\n{\n  eos::common::HttpResponse* response = new eos::common::PlainHttpResponse();\n  response->AddHeader(\"DAV\", \"1,2\");\n  response->AddHeader(\"Allow\", \"OPTIONS,GET,HEAD,PUT,DELETE,TRACE,\"\\\n                      \"PROPFIND,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK\");\n  response->AddHeader(\"Content-Length\", \"0\");\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nHttpHandler::Connect(eos::common::HttpRequest* request)\n{\n  using namespace eos::common;\n  std::string url = request->GetUrl();\n  eos_static_info(\"method=CONNECT error=NOTIMPLEMENTED path=%s\",\n                  url.c_str());\n  HttpResponse* response = new PlainHttpResponse();\n  response->SetResponseCode(HttpResponse::ResponseCodes::NOT_IMPLEMENTED);\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nHttpHandler::Patch(eos::common::HttpRequest* request)\n{\n  using namespace eos::common;\n  std::string url = request->GetUrl();\n  eos_static_info(\"method=PATCH error=NOTIMPLEMENTED path=%s\",\n                  url.c_str());\n  HttpResponse* response = new PlainHttpResponse();\n  response->SetResponseCode(HttpResponse::ResponseCodes::NOT_IMPLEMENTED);\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/HttpHandler.hh",
    "content": "// ----------------------------------------------------------------------\n// File: HttpHandler.hh\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   HttpHandler.hh\n *\n * @brief  Class to handle plain HTTP requests and build responses.\n */\n\n#ifndef __EOSMGM_HTTP_HANDLER__HH__\n#define __EOSMGM_HTTP_HANDLER__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/http/HttpHandler.hh\"\n#include \"mgm/Namespace.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n#include <map>\n#include <string>\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\nclass HttpHandler : public eos::common::HttpHandler\n{\n\npublic:\n\n  /**\n   * Constructor\n   */\n  HttpHandler (eos::common::VirtualIdentity *vid) :\n    eos::common::ProtocolHandler(vid) {};\n\n  /**\n   * Destructor\n   */\n  virtual ~HttpHandler () {};\n\n  /**\n   * Check whether the given method and headers are a match for this protocol.\n   *\n   * @param method  the request verb used by the client (GET, PUT, etc)\n   * @param headers the map of request headers\n   *\n   * @return true if the protocol matches, false otherwise\n   */\n  static bool\n  Matches (const std::string &method, HeaderMap &headers);\n\n  /**\n   * Build a response to the given plain HTTP request.\n   *\n   * @param request  the map of request headers sent by the client\n   * @param method   the request verb used by the client (GET, PUT, etc)\n   * @param url      the URL requested by the client\n   * @param query    the GET request query string (if any)\n   * @param body     the request body data sent by the client\n   * @param bodysize the size of the request body\n   * @param cookies  the map of cookie headers\n   */\n  void\n  HandleRequest (eos::common::HttpRequest *request);\n\n  /**\n   * Handle an HTTP GET request.\n   *\n   * @param request  the client request object\n   * @param isHEAD indicates that this is a HEAD request\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Get (eos::common::HttpRequest *request, bool isHEAD=false);\n\n  /**\n   * Handle an HTTP HEAD request.\n   *\n   * @param request  the client request object\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Head (eos::common::HttpRequest *request);\n\n  /**\n   * Handle an HTTP POST request.\n   *\n   * @param request  the client request object\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Post (eos::common::HttpRequest *request);\n\n  /**\n   * Handle an HTTP PUT request.\n   *\n   * @param request  the client request object\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Put (eos::common::HttpRequest *request);\n\n  /**\n   * Handle an HTTP DELETE request.\n   *\n   * @param request  the client request object\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Delete (eos::common::HttpRequest *request);\n\n  /**\n   * Handle an HTTP TRACE request.\n   *\n   * @param request  the client request object\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Trace (eos::common::HttpRequest *request);\n\n  /**\n   * Handle an HTTP OPTIONS request.\n   *\n   * @param request  the client request object\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Options (eos::common::HttpRequest *request);\n\n  /**\n   * Handle an HTTP CONNECT request.\n   *\n   * @param request  the client request object\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Connect (eos::common::HttpRequest *request);\n\n  /**\n   * Handle an HTTP PATCH request.\n   *\n   * @param request  the client request object\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Patch (eos::common::HttpRequest *request);\n\n};\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_END\n\n#endif /* __EOSMGM_HTTP_HANDLER__HH__ */\n"
  },
  {
    "path": "mgm/http/HttpServer.cc",
    "content": "// ----------------------------------------------------------------------\n// File: HttpServer.cc\n// Author: Andreas-Joachim Peters & Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/http/HttpServer.hh\"\n#include \"mgm/http/ProtocolHandlerFactory.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"common/Path.hh\"\n#include \"common/SecEntity.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/ErrnoToString.hh\"\n#include <XrdNet/XrdNetAddr.hh>\n#include <XrdAcc/XrdAccAuthorize.hh>\n#include <netdb.h>\n\nEOSMGMNAMESPACE_BEGIN\n\n#define EOSMGM_HTTP_PAGE \"<html><head><title>No such file or directory</title>\\\n                          </head><body>No such file or directory</body></html>\"\n\n#ifdef EOS_MICRO_HTTPD\n/*----------------------------------------------------------------------------*/\nint\nHttpServer::Handler(void* cls,\n                    struct MHD_Connection* connection,\n                    const char* url,\n                    const char* method,\n                    const char* version,\n                    const char* uploadData,\n                    size_t* uploadDataSize,\n                    void** ptr)\n{\n  using namespace eos::common;\n  std::map<std::string, std::string> headers;\n  // Wait for the namespace to boot\n  WAIT_BOOT;\n\n  // If this is the first call, create an appropriate protocol handler based\n  // on the headers and store it in *ptr. We should only return MHD_YES here\n  // (unless error)\n  if (*ptr == 0) {\n    // Get the headers\n    MHD_get_connection_values(connection, MHD_HEADER_KIND,\n                              &HttpServer::BuildHeaderMap, (void*) &headers);\n    // Retrieve Client IP\n    const MHD_ConnectionInfo* info = MHD_get_connection_info(connection,\n                                     MHD_CONNECTION_INFO_CLIENT_ADDRESS);\n\n    if (info && info->client_addr) {\n      char host[NI_MAXHOST];\n\n      if (! getnameinfo(info->client_addr,\n                        (info->client_addr->sa_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(\n                          struct sockaddr_in6), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST)) {\n        headers[\"client-real-ip\"] = host;\n      } else {\n        headers[\"client-real-ip\"] = \"NOIPLOOKUP\";\n      }\n\n      XrdNetAddr netaddr(info->client_addr);\n      const char* name = netaddr.Name();\n\n      if (name) {\n        headers[\"client-real-host\"] = name;\n      }\n    }\n\n    // Clients which are gateways/sudoer can pass x-forwarded-for and remote-user\n    if (headers.count(\"x-forwarded-for\")) {\n      // Check if this is a http gateway and sudoer by calling the mapping function\n      std::unique_ptr<VirtualIdentity> vid_tmp  {new VirtualIdentity()};\n\n      if (vid_tmp) {\n        XrdSecEntity eclient(headers.count(\"x-real-ip\") ? \"https\" : \"http\");\n        eclient.tident = \"\";\n        eclient.name = (char*)\"nobody\";\n        eclient.host = (char*)(headers[\"client-real-host\"].length() ?\n                               headers[\"client-real-host\"].c_str() : \"\");\n\n        if (headers.count(\"x-gateway-authorization\")) {\n          eclient.endorsements = (char*)headers[\"x-gateway-authorization\"].c_str();\n        }\n\n        std::string stident = \"https.0:0@\";\n        stident += headers[\"client-real-host\"];\n        eos::common::Mapping::IdMap(&eclient, \"\", stident.c_str(), *vid_tmp);\n\n        if (!vid_tmp->isGateway() ||\n            ((vid_tmp->prot != \"https\") && (vid_tmp->prot != \"http\"))) {\n          headers.erase(\"x-forwarded-for\");\n          headers.erase(\"x-real-ip\");\n        }\n\n        eos_static_debug(\"vid trace: %s gw:%d\", vid_tmp->getTrace().c_str(),\n                         vid_tmp->isGateway());\n\n        if (headers.count(\"x-gateway-authorization\") && !vid_tmp->sudoer) {\n          headers.erase(\"remote-user\");\n        }\n      } else {\n        eos_static_err(\"msg=\\\"failed to allocate VirtualIdentity object\\\" \"\n                       \"method=%s\", method);\n        return MHD_NO;\n      }\n    } else {\n      headers.erase(\"x-real-ip\");\n      headers.erase(\"remote-user\");\n    }\n\n    // Authenticate the client\n    std::unique_ptr<eos::common::VirtualIdentity> vid = Authenticate(headers);\n\n    if (!vid) {\n      eos_static_info(\"msg=\\\"could not build VirtualIdentity based on headers\\\" \"\n                      \"method=%s\", method);\n      return MHD_NO;\n    }\n\n    eos_static_info(\"request=%s client-real-ip=%s client-real-host=%s vid.uid=%s vid.gid=%s vid.host=%s vid.tident=%s\\n\",\n                    method, headers[\"client-real-ip\"].c_str(), headers[\"client-real-host\"].c_str(),\n                    vid->uid_string.c_str(), vid->gid_string.c_str(), vid->host.c_str(),\n                    vid->tident.c_str());\n    eos::common::ProtocolHandler* handler;\n    ProtocolHandlerFactory factory = ProtocolHandlerFactory();\n    handler = factory.CreateProtocolHandler(method, headers, vid.release());\n\n    if (!handler) {\n      eos_static_err(\"msg=\\\"no matching protocol for request method %s\\\"\",\n                     method);\n      return MHD_NO;\n    }\n\n    *ptr = handler;\n\n    // PUT has to run through to avoid the generation of 100-CONTINUE before a redirect\n    if (strcmp(method, \"PUT\")) {\n      return MHD_YES;\n    }\n  }\n\n  // Retrieve the protocol handler stored in *ptr\n  eos::common::ProtocolHandler* protocolHandler = (eos::common::ProtocolHandler*)\n      * ptr;\n\n  // For requests which have a body (i.e. uploadDataSize != 0) we must handle\n  // the body data on the last call to this function. We must\n  // create the response and store it inside the protocol handler, but we must\n  // NOT queue the response until the third call.\n  if (!protocolHandler->GetResponse() && (!*uploadDataSize)) {\n    // Get the request headers again\n    MHD_get_connection_values(connection, MHD_HEADER_KIND,\n                              &HttpServer::BuildHeaderMap, (void*) &headers);\n    // Get the request query string\n    std::string query;\n    MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND,\n                              &HttpServer::BuildQueryString, (void*) &query);\n    // Get the cookies\n    std::map<std::string, std::string> cookies;\n    MHD_get_connection_values(connection, MHD_COOKIE_KIND,\n                              &HttpServer::BuildHeaderMap, (void*) &cookies);\n    size_t bodySize = protocolHandler->GetBody().size();\n    // Make a request object\n    eos::common::HttpRequest* request = new eos::common::HttpRequest(\n      headers, method, url,\n      query.c_str() ? query : \"\",\n      protocolHandler->GetBody(), &bodySize, cookies);\n    eos_static_debug(\"\\n\\n%s\\n%s\\n\", request->ToString().c_str(),\n                     request->GetBody().c_str());\n    // Handle the request and build a response based on the specific protocol unless the body is not complete ...\n    protocolHandler->HandleRequest(request);\n    delete request;\n  }\n\n  // If we have a non-empty body, we must \"process\" it, set the body size to\n  // zero, and return MHD_YES. We should not queue the response yet - we must\n  // do that on the next (third) call.\n  if (*uploadDataSize != 0) {\n    // we store the partial body into the handler\n    protocolHandler->AddToBody(uploadData, *uploadDataSize);\n    *uploadDataSize = 0;\n    return MHD_YES;\n  }\n\n  eos::common::HttpResponse* response = protocolHandler->GetResponse();\n\n  if (!response) {\n    eos_static_crit(\"msg=\\\"response creation failed\\\"\");\n    delete protocolHandler;\n    *ptr = 0;\n    return MHD_NO;\n  }\n\n  eos_static_debug(\"\\n\\n%s\", response->ToString().c_str());\n  // Create the response\n  struct MHD_Response* mhdResponse;\n  mhdResponse = MHD_create_response_from_buffer(response->GetBodySize(), (void*)\n                response->GetBody().c_str(),\n                MHD_RESPMEM_MUST_COPY);\n\n  if (mhdResponse) {\n    // Add all the response header tags\n    headers = response->GetHeaders();\n\n    for (auto it = headers.begin(); it != headers.end(); it++) {\n      MHD_add_response_header(mhdResponse, it->first.c_str(), it->second.c_str());\n    }\n\n    // Queue the response\n    int ret = MHD_queue_response(connection, response->GetResponseCode(),\n                                 mhdResponse);\n    eos_static_debug(\"msg=\\\"MHD_queue_response\\\" retc=%d\", ret);\n    MHD_destroy_response(mhdResponse);\n    delete protocolHandler;\n    *ptr = 0;\n    return ret;\n  } else {\n    eos_static_crit(\"msg=\\\"response creation failed\\\"\");\n    delete protocolHandler;\n    *ptr = 0;\n    return MHD_NO;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nHttpServer::CompleteHandler(void*                              cls,\n                            struct MHD_Connection*             connection,\n                            void**                             con_cls,\n                            enum MHD_RequestTerminationCode    toe)\n{\n  std::string scode = \"\";\n\n  if (toe == MHD_REQUEST_TERMINATED_COMPLETED_OK) {\n    scode = \"OK\";\n  }\n\n  if (toe == MHD_REQUEST_TERMINATED_WITH_ERROR) {\n    scode = \"Error\";\n  }\n\n  if (toe == MHD_REQUEST_TERMINATED_TIMEOUT_REACHED) {\n    scode = \"Timeout\";\n  }\n\n  if (toe == MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN) {\n    scode = \"Shutdown\";\n  }\n\n  if (toe == MHD_REQUEST_TERMINATED_READ_ERROR) {\n    scode = \"ReadError\";\n  }\n\n  eos_static_info(\"msg=\\\"http connection disconnect\\\" reason=\\\"Request %s\\\" \",\n                  scode.c_str());\n}\n\n#endif\n\n//------------------------------------------------------------------------------\n// Do a \"rough\" mapping between HTTP verbs and access operation types\n//\n// @param http_verb type of HTTP request\n//\n// @return XRootD access operation type\n//------------------------------------------------------------------------------\nAccess_Operation MapHttpVerbToAOP(const std::string& http_verb)\n{\n  Access_Operation op = AOP_Stat;\n\n  if (http_verb == \"GET\") {\n    op = AOP_Read;\n  } else if (http_verb == \"PUT\") {\n    op = AOP_Create;\n  } else if (http_verb == \"DELETE\") {\n    op = AOP_Delete;\n  }\n\n  return op;\n}\n\n//------------------------------------------------------------------------------\n// HTTP object handler function called by XrdHttp\n//------------------------------------------------------------------------------\nstd::unique_ptr<eos::common::ProtocolHandler>\nHttpServer::XrdHttpHandler(std::string& method,\n                           std::string& uri,\n                           std::map<std::string, std::string>& headers,\n                           std::map<std::string, std::string>& cookies,\n                           std::string& body,\n                           const XrdSecEntity& client,\n                           XrdAccAuthorize* authz_obj,\n                           std::string& err_msg)\n{\n  using namespace eos::common;\n  WAIT_BOOT;\n\n  // Clients which are gateways/sudoer can pass x-forwarded-for and remote-user\n  if (headers.count(\"x-forwarded-for\")) {\n    // Check if this is a http gateway and sudoer by calling the mapping function\n    std::unique_ptr<VirtualIdentity> vid_tmp  {new VirtualIdentity()};\n\n    if (vid_tmp) {\n      XrdSecEntity eclient(client.prot);\n      // Save initial eaAPI pointer and reset after the copy to avoid\n      // double free of the same pointer.\n      auto ea = eclient.eaAPI;\n      eclient = client;\n      eclient.eaAPI = ea;\n\n      if (headers.count(\"x-gateway-authorization\")) {\n        eclient.endorsements = (char*)headers[\"x-gateway-authorization\"].c_str();\n      }\n\n      std::string stident = \"https.0:0@\";\n      stident += std::string(client.host);\n      eos::common::Mapping::IdMap(&eclient, \"\", stident.c_str(), *vid_tmp);\n\n      if (!vid_tmp->isGateway() ||\n          ((vid_tmp->prot != \"https\") && (vid_tmp->prot != \"http\"))) {\n        headers.erase(\"x-forwarded-for\");\n        headers.erase(\"x-real-ip\");\n      }\n\n      eos_static_debug(\"vid trace: %s gw:%d\", vid_tmp->getTrace().c_str(),\n                       vid_tmp->isGateway());\n\n      if (headers.count(\"x-gateway-authorization\") && !vid_tmp->sudoer) {\n        headers.erase(\"remote-user\");\n      }\n    } else {\n      err_msg = \"failed to allocate memory\";\n      eos_static_err(\"msg=\\\"failed to allocate VirtualIdentity object\\\" \"\n                     \"method=%s uri=\\\"%s\\\"\", method.c_str(), uri.c_str());\n      return nullptr;\n    }\n  }\n\n  bool s3_access = false;\n  auto it_authz = headers.find(\"authorization\");\n\n  if (it_authz != headers.end()) {\n    if (it_authz->second.substr(0, 3) == \"AWS\") {\n      s3_access = true;\n    }\n  }\n\n  std::string query; //@todo(esindril) decide if this is needed\n  std::unique_ptr<VirtualIdentity> vid;\n\n  // Native XrdHttp access\n  if (headers.find(\"x-forwarded-for\") == headers.end() && !s3_access) {\n    std::string path;\n    std::unique_ptr<XrdOucEnv> env_opaque;\n\n    if (!BuildPathAndEnvOpaque(headers, path, env_opaque)) {\n      err_msg = \"conflicting authorization info present\";\n      eos_static_err(\"msg=\\\"%s\\\" path=\\\"%s\\\"\", err_msg.c_str(), path.c_str());\n      return nullptr;\n    }\n\n    int envlen;\n    char* ptr = env_opaque->Env(envlen);\n\n    if (ptr == nullptr) {\n      err_msg = \"empty opaque info for request\";\n      eos_static_err(\"msg=\\\"%s\\\" path=\\\"%s\\\"\", err_msg.c_str(), path.c_str());\n      return nullptr;\n    }\n\n    // Get access operation type for the authz library\n    Access_Operation acc_op = MapHttpVerbToAOP(method);\n    const std::string env = ptr;\n    query = env;\n    vid = std::make_unique<VirtualIdentity>();\n    EXEC_TIMING_BEGIN(\"IdMap\");\n    Mapping::IdMap(&client, env.c_str(), client.tident, *vid,\n                   authz_obj, acc_op, path);\n    EXEC_TIMING_END(\"IdMap\");\n  } else { // HTTP access through Nginx\n    headers[\"client-real-ip\"] = \"NOIPLOOKUP\";\n    headers[\"client-real-host\"] = client.host;\n    headers[\"x-real-ip\"] = client.host;\n    auto it = headers.find(\"xrd-http-fullresource\");\n\n    if (it != headers.end()) {\n      extractOpaqueWithoutAuthz(it->second, query);\n    }\n\n    if (client.moninfo && strlen(client.moninfo)) {\n      headers[\"ssl_client_s_dn\"] = client.moninfo;\n    }\n\n    vid = Authenticate(headers);\n\n    if (!vid) {\n      eos_static_info(\"msg=\\\"could not build VirtualIdentity based on headers\\\" \"\n                      \"method=%s\", method.c_str());\n      return nullptr;\n    }\n  }\n\n  // Update the vid.name as the mapping might have changed the vid.uid and it\n  // is the name that is used later on for all the authorization bits\n  int errc = 0;\n  std::string usr_name = eos::common::Mapping::UidToUserName(vid->uid, errc);\n  vid->name = (errc ? std::to_string(vid->uid).c_str() : usr_name.c_str());\n  // Add the path to the vid's scope member for token ACL path validation\n  vid->scope = uri;\n  eos_static_info(\"request=%s client-real-ip=%s client-real-host=%s \"\n                  \"vid.name=%s vid.uid=%s vid.gid=%s vid.host=%s \"\n                  \"vid.dn=%s vid.tident=%s\",\n                  method.c_str(), headers[\"client-real-ip\"].c_str(),\n                  headers[\"client-real-host\"].c_str(), vid->name.c_str(),\n                  vid->uid_string.c_str(), vid->gid_string.c_str(),\n                  vid->host.c_str(), vid->dn.c_str(), vid->tident.c_str());\n  ProtocolHandlerFactory factory = ProtocolHandlerFactory();\n  std::unique_ptr<eos::common::ProtocolHandler> handler\n  {factory.CreateProtocolHandler(method, headers, vid.release())};\n\n  if (!handler) {\n    eos_static_err(\"msg=\\\"no matching protocol for request method %s\\\"\",\n                   method.c_str());\n    return nullptr;\n  }\n\n  size_t bodySize = body.length();\n  // Retrieve the protocol handler stored in *ptr\n  std::unique_ptr<eos::common::HttpRequest> request {\n    new eos::common::HttpRequest(headers, method, uri,\n                                 (query.c_str() ? query : \"\"),\n                                 body, &bodySize, cookies)};\n  eos_static_debug(\"\\n\\n%s\\n%s\\n\", request->ToString().c_str(),\n                   request->GetBody().c_str());\n  handler->HandleRequest(request.get());\n  eos_static_debug(\"method=%s uri=\\\"%s\\\"client=\\\"%s\\\" msg=\\\"warning this is \"\n                   \"not the mapped identity\\\"\", method.c_str(), uri.c_str(),\n                   eos::common::SecEntity::ToString(&client, \"xrdhttp\").c_str());\n  return handler;\n}\n\n\n//------------------------------------------------------------------------------\n// Build path and opaque information based on the HTTP headers\n//------------------------------------------------------------------------------\nbool\nHttpServer::BuildPathAndEnvOpaque\n(const std::map<std::string, std::string>& normalized_headers,\n std::string& path, std::unique_ptr<XrdOucEnv>& env_opaque)\n{\n  using eos::common::StringConversion;\n  // Extract path and any opaque info that might be present in the headers\n  // /path/to/file?and=some&opaque=info\n  path.clear();\n  auto it = normalized_headers.find(\"xrd-http-fullresource\");\n\n  if (it == normalized_headers.end()) {\n    eos_static_err(\"%s\", \"msg=\\\"no xrd-http-fullresource header\\\"\");\n    return false;\n  }\n\n  std::string opaque;\n  extractPathAndOpaque(it->second, path, opaque);\n  // Check if there is an explicit authorization header\n  std::string http_authz;\n  it = normalized_headers.find(\"authorization\");\n\n  if (it != normalized_headers.end()) {\n    http_authz = it->second;\n  }\n\n  // If opaque data aleady contains authorization info i.e. \"&authz=...\" and we also\n  // have a HTTP authorization header then we fail\n  bool has_opaque_authz = (opaque.find(\"authz=\") != std::string::npos);\n\n  if (has_opaque_authz && !http_authz.empty()) {\n    eos_static_err(\"msg=\\\"request has both opaque and http authorization\\\" \"\n                   \"opaque=\\\"%s\\\" http_authz=\\\"%s\\\"\", opaque.c_str(),\n                   http_authz.c_str());\n    return false;\n  }\n\n  if (!http_authz.empty()) {\n    std::string enc_authz = StringConversion::curl_default_escaped(http_authz);\n    opaque += \"&authz=\";\n    opaque += enc_authz;\n  }\n\n  it = normalized_headers.find(\"xrd-http-query\");\n\n  if (it != normalized_headers.end()) {\n    std::string query = it->second;\n\n    if (!query.empty()) {\n      if (*query.begin() != '&') {\n        opaque += \"&\";\n      }\n\n      opaque += query;\n    }\n  }\n\n  // Append eos.app tag\n  eos::common::AddEosApp(opaque, \"http\");\n  env_opaque = std::make_unique<XrdOucEnv>(opaque.c_str(), opaque.length());\n  return true;\n}\n\nvoid HttpServer::extractOpaqueWithoutAuthz(const std::string& fullpath,\n    std::string& opaque)\n{\n  std::string path;\n  opaque.clear();\n  extractPathAndOpaque(fullpath, path, opaque);\n\n  if (opaque.size()) {\n    auto env_opaque = std::make_unique<XrdOucEnv>(opaque.c_str(), opaque.length());\n    int envTidyLen;\n    char* envTidy = env_opaque->EnvTidy(envTidyLen);\n\n    if (envTidyLen > 1) {\n      // Seen during unit tests, EnvTidy puts an ampersand at the beginning of the resulting string\n      opaque.assign(envTidy + 1, envTidyLen - 1);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Handle clientDN specified using RFC2253 (and RFC4514) where the\n// separator is \",\" instead of the usual \"/\" and also the order of the DNs\n// is reversed\n//------------------------------------------------------------------------------\nstd::string\nHttpServer::ProcessClientDN(const std::string& cdn) const\n{\n  std::string new_cdn = cdn;\n\n  if (new_cdn.empty()) {\n    return new_cdn;\n  }\n\n  if (new_cdn.find(',') != std::string::npos) {\n    // clientDN specified using RFC2253 (and RFC4514) where the separator is\n    // \",\" instead of the usual \"/\" and DNs reversed\n    std::replace(new_cdn.begin(), new_cdn.end(), ',', '/');\n    // Reverse the DN tokens\n    auto tokens =  eos::common::StringTokenizer::split\n                   <std::list<std::string>>(new_cdn, '/');\n    new_cdn.clear();\n\n    for (auto token = tokens.rbegin(); token != tokens.rend(); ++token) {\n      new_cdn += '/';\n      new_cdn += *token;\n    }\n  }\n\n  return new_cdn;\n}\n\n/*----------------------------------------------------------------------------*/\nstd::unique_ptr<eos::common::VirtualIdentity>\nHttpServer::Authenticate(std::map<std::string, std::string>& headers)\n{\n  std::unique_ptr<eos::common::VirtualIdentity> vid;\n  std::string clientDN = headers[\"ssl_client_s_dn\"];\n  std::string remoteUser = headers[\"remote-user\"];\n  std::string dn;\n  std::string username;\n  unsigned pos;\n\n  if (clientDN.empty() && remoteUser.empty()) {\n    eos_static_debug(\"msg=\\\"client supplied neither SSL_CLIENT_S_DN nor \"\n                     \"Remote-User headers nor eos.ruid opaque parameter\\\"\");\n  } else if (clientDN.length()) {\n    clientDN = ProcessClientDN(clientDN);\n    // Stat the gridmap file\n    struct stat info;\n\n    if (stat(\"/etc/grid-security/grid-mapfile\", &info) == -1) {\n      eos_static_warning(\"msg=\\\"error stating gridmap file: %s\\\"\",\n                         eos::common::ErrnoToString(errno).c_str());\n    } else {\n      {\n        static XrdSysMutex mGridMapMutex;\n        XrdSysMutexHelper gLock(mGridMapMutex);\n\n        // Initially load the file, or reload it if it was modified\n        if (!mGridMapFileLastModTime.tv_sec ||\n            mGridMapFileLastModTime.tv_sec != info.st_mtim.tv_sec) {\n          eos_static_info(\"msg=\\\"reloading gridmap file\\\"\");\n          std::ifstream in(\"/etc/grid-security/grid-mapfile\");\n          std::stringstream buffer;\n          buffer << in.rdbuf();\n          mGridMapFile = buffer.str();\n          mGridMapFileLastModTime = info.st_mtim;\n          in.close();\n        }\n      }\n      // For proxy certificates clientDN can have multiple ../CN=... appended\n      size_t pos = 0;\n      int num_cns = 0;\n\n      while ((pos = clientDN.find(\"/CN=\", pos)) != std::string::npos) {\n        ++num_cns;\n        ++pos;\n      }\n\n      // Remove the CNs from the end one by one to check if the remaining\n      // DN is in the map\n      std::set<std::string> proxy_dns;\n      std::string clientDNproxy = clientDN;\n\n      while (num_cns >= 2) {\n        clientDNproxy.erase(clientDNproxy.rfind(\"/CN=\"));\n        proxy_dns.insert(clientDNproxy);\n        --num_cns;\n      }\n\n      // Process each mapping\n      std::vector<std::string> mappings;\n      eos::common::StringConversion::Tokenize(mGridMapFile, mappings, \"\\n\");\n\n      for (auto it = mappings.begin(); it != mappings.end(); ++it) {\n        eos_static_debug(\"grid mapping: %s\", (*it).c_str());\n        // Split off the last whitespace-separated token (i.e. username)\n        pos = (*it).find_last_of(\" \\t\");\n\n        if (pos == std::string::npos) {\n          eos_static_err(\"msg=malformed gridmap file\");\n          return nullptr;\n        }\n\n        dn = (*it).substr(1, pos - 2); // Remove quotes around DN\n        username = (*it).substr(pos + 1);\n\n        // Try to match with SSL header\n        if (dn == clientDN) {\n          eos_static_info(\"msg=\\\"mapped client certificate successfully\\\" \"\n                          \"dn=\\\"%s\\\" username=\\\"%s\\\"\", dn.c_str(), username.c_str());\n          break;\n        }\n\n        // Check if any of the proxy dns matches\n        if (proxy_dns.find(dn) != proxy_dns.end()) {\n          eos_static_info(\"msg=\\\"mapped client proxy certificate successfully\\\" \"\n                          \"dn=\\\"%s\\\"username=\\\"%s\\\"\", dn.c_str(), username.c_str());\n          break;\n        }\n\n        username = \"\";\n      }\n    }\n  } else if (remoteUser.length()) {\n    // RemoteUser can either be a username or a userId\n    // extract kerberos username\n    pos = remoteUser.find_last_of(\"@\");\n    std::string remoteUserName = remoteUser.substr(0, pos);\n    username = remoteUserName;\n    // Check if the username is a number and convert that number to a username\n    // if the username is not a number, we just pass this username as-is.\n    uid_t uid;\n    if (common::StringToNumeric(username,uid)) {\n      int err;\n      username = common::Mapping::UidToUserName(uid,err);\n      if (!err) {\n        eos_static_info(\"msg=\\\"mapped client remote username successfully from numeric \\\" \"\n                    \"username=\\\"%s\\\" uid=\\\"%d\\\"\", username.c_str(),uid);\n      } else {\n        // Error during Uid resolution, empty the username\n        username = \"\";\n      }\n    } else {\n      eos_static_info(\"msg=\\\"mapped client remote username successfully\\\" \"\n                      \"username=\\\"%s\\\"\", username.c_str());\n    }\n  }\n\n\n  if (username.empty()) {\n    eos_static_info(\"msg=\\\"unauthenticated client mapped to nobody\"\n                    \"\\\" SSL_CLIENT_S_DN=\\\"%s\\\", Remote-User=\\\"%s\\\"\",\n                    clientDN.c_str(), remoteUser.c_str());\n    username = \"nobody\";\n  }\n\n  XrdSecEntity client(headers.count(\"x-real-ip\") ? \"https\" : \"http\");\n  std::string remotehost;\n\n  if (headers.count(\"x-real-ip\")) {\n    // Translate a proxied host name\n    std::string real_ip = headers[\"x-real-ip\"];\n\n    if (real_ip.empty()) {\n      eos_static_err(\"msg=\\\"x-real-ip header is empty\\\"\");\n      return nullptr;\n    }\n\n    // XrdNetAddr deals properly with IPv6 addresses only if they use the\n    // bracket format [ipv6_addr][:<port>]\n    if (real_ip.find('.') == std::string::npos) {\n      // We can safely assume this is an IPv6 address now\n      if (real_ip[0] != '[') {\n        std::ostringstream oss;\n        oss << '[' << real_ip << ']';\n        real_ip = oss.str();\n      }\n    }\n\n    remotehost = real_ip;\n    XrdNetAddr netaddr;\n    netaddr.Set(real_ip.c_str());\n    // Try to convert IP to corresponding [host] name\n    const char* name = netaddr.Name();\n\n    if (name) {\n      remotehost = name;\n    }\n\n    if (headers.count(\"auth-type\")) {\n      remotehost += \"=>\";\n      remotehost += headers[\"auth-type\"];\n    }\n  }\n\n  client.host = const_cast<char*>(remotehost.c_str());\n  XrdOucString tident = username.c_str();\n  tident += \".1:1@\";\n  tident += const_cast<char*>(headers[\"client-real-host\"].c_str());\n  client.name = const_cast<char*>(username.c_str());\n  client.tident = const_cast<char*>(tident.c_str());\n  {\n    // Make a virtual identity object\n    vid = std::make_unique<eos::common::VirtualIdentity>();\n    EXEC_TIMING_BEGIN(\"IdMap\");\n    eos::common::Mapping::IdMap(&client, \"eos.app=http\", client.tident, *vid);\n    EXEC_TIMING_END(\"IdMap\");\n    std::string header_host = headers[\"host\"];\n    size_t pos = header_host.find(':');\n\n    // remove the port if present\n    if (pos != std::string::npos) {\n      header_host.erase(pos);\n    }\n\n    eos_static_debug(\"msg=\\\"connection/header\\\" header-host=\\\"%s\\\" \"\n                     \"connection-host=\\\"%s\\\" real-ip=%s\",\n                     header_host.c_str(), headers[\"client-real-host\"].c_str(),\n                     headers[\"client-real-ip\"].c_str());\n\n    // if we have been mapped to nobody, change also the name accordingly\n    if (vid->uid == 99) {\n      vid->name = const_cast<char*>(\"nobody\");\n    }\n\n    vid->dn = dn;\n    vid->tident = tident.c_str();\n  }\n  return vid;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/HttpServer.hh",
    "content": "// ----------------------------------------------------------------------\n// File: HttpServer.hh\n// Author: Andreas-Joachim Peters & Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   HttpServer.hh\n *\n * @brief  Creates an Http redirector instance running on the MGM\n */\n\n#ifndef __EOSMGM_HTTPSERVER__HH__\n#define __EOSMGM_HTTPSERVER__HH__\n\n#include \"mgm/Namespace.hh\"\n#include \"common/http/HttpServer.hh\"\n#include \"common/Path.hh\"\n#include \"common/http/ProtocolHandler.hh\"\n#include \"common/Mapping.hh\"\n#include \"mgm/http/rest-api/handler/tape/TapeRestHandler.hh\"\n#include <XrdHttp/XrdHttpExtHandler.hh>\n#include <map>\n#include <string>\n\n//! Forward declaration\nclass XrdAccAuthorize;\n\nEOSMGMNAMESPACE_BEGIN\n\nclass HttpServer : public eos::common::HttpServer\n{\npublic:\n  /**\n   * Constructor\n   */\n  HttpServer(int port = 8000) :\n    eos::common::HttpServer(port), mGridMapFileLastModTime{0} {}\n\n  /**\n   * Destructor\n   */\n  virtual ~HttpServer()\n  {\n    eos_static_info(\"%s\", \"msg=\\\"MGM HttpServer destructor\\\"\");\n    mThreadId.join();\n  }\n\n#ifdef EOS_MICRO_HTTPD\n  /**\n   * HTTP object handler function on MGM\n   *\n   * @return see implementation\n   */\n  virtual int\n  Handler(void*                  cls,\n          struct MHD_Connection* connection,\n          const char*            url,\n          const char*            method,\n          const char*            version,\n          const char*            upload_data,\n          size_t*                upload_data_size,\n          void**                 ptr);\n\n  /**\n   * HTTP complete handler function on MGM\n   *\n   * @return see implementation\n   */\n\n  virtual void\n  CompleteHandler(void*                              cls,\n                  struct MHD_Connection*             connection,\n                  void**                             con_cls,\n                  enum MHD_RequestTerminationCode    toe);\n\n\n\n#endif\n\n  /**\n   * Authenticate the client request by inspecting the SSL headers which were\n   * transmitted by the reverse proxy server and attempting to map the client\n   * DN to the gridmap file.\n   *\n   * If the client is not in the gridmap file, (s)he will be mapped to the\n   * user \"nobody\" and have limited access.\n   *\n   * @param headers  the map of client request headers\n   *\n   * @return an appropriately filled virtual identity\n   */\n  std::unique_ptr<eos::common::VirtualIdentity>\n  Authenticate(std::map<std::string, std::string>& headers);\n\n  //----------------------------------------------------------------------------\n  //! HTTP object handler function on MGM called by XrdHttp\n  //!\n  //! @param method HTTP verb\n  //! @param uri HTTP URI\n  //! @param headers map fo the headers\n  //! @param cookie cookies\n  //! @param body HTTP body\n  //! @param client XrdSecEntity for current request\n  //! @param authz_obj authorization library\n  //! @param err_msg error message in case of failure\n  //!\n  //! @return protocol handler or null in case of failure\n  //----------------------------------------------------------------------------\n  virtual std::unique_ptr<eos::common::ProtocolHandler>\n  XrdHttpHandler(std::string& method,\n                 std::string& uri,\n                 std::map<std::string, std::string>& headers,\n                 std::map<std::string, std::string>& cookies,\n                 std::string& body,\n                 const XrdSecEntity& client,\n                 XrdAccAuthorize* authz_obj,\n                 std::string& err_msg);\n\n  //----------------------------------------------------------------------------\n  //! Extract opaque query from the full path passed in parameter\n  //!\n  //! @param fullpath the path from which we extract the opaque infos\n  //! @param path, the extracted path\n  //! @param opaque, the extracted opaque without the '?'\n  //----------------------------------------------------------------------------\n  inline static void extractPathAndOpaque(const std::string & fullpath, std::string & path, std::string & opaque)\n  {\n    path = fullpath;\n    size_t pos = fullpath.find('?');\n\n    if ((pos != std::string::npos) && (pos != fullpath.length())) {\n      opaque = path.substr(pos + 1);\n      path = path.substr(0, pos);\n    }\n\n    eos::common::Path canonical_path(path);\n    path = canonical_path.GetFullPath().c_str();\n  }\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  std::string     mGridMapFile;            //!< contents of the gridmap file\n  struct timespec mGridMapFileLastModTime; //!< last modification time of the\n  //!< gridmap file\n\n  //----------------------------------------------------------------------------\n  //! Handle clientDN specified using RFC2253 (and RFC4514) where the\n  //! separator is \",\" instead of the usual \"/\" and also the order of the DNs\n  //! is reversed\n  //!\n  //! @param cnd input clientDN\n  //!\n  //! @return clientDN formatted according to the legacy standard\n  //----------------------------------------------------------------------------\n  std::string ProcessClientDN(const std::string& cnd) const;\n\n  //----------------------------------------------------------------------------\n  //! Build path and opaque information based on the HTTP headers\n  //!\n  //! @param normalized_headers HTTP headers\n  //! @param path canonical path of the HTTP request\n  //! @param env_opaque opaque information stored in XrdOucEnv object\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  static bool\n  BuildPathAndEnvOpaque(const std::map<std::string, std::string>&\n                        normalized_headers, std::string& path,\n                        std::unique_ptr<XrdOucEnv>& env_opaque);\n\n  //----------------------------------------------------------------------------\n  //! Extract opaque query from the full path passed in parameter and remove everything\n  //! related to authz\n  //!\n  //! @param fullpath the path from which we extract the opaque infos\n  //! @param opaque, the extracted opaque without the '?' and without authz opaque query\n  //----------------------------------------------------------------------------\n  static void extractOpaqueWithoutAuthz(const std::string & fullpath, std::string & opaque);\n\n};\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/http/ProtocolHandlerFactory.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ProtocolHandlerFactory.hh\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   ProtocolHandlerFactory.hh\n *\n * @brief  Factory class to create an appropriate protocol handler for the\n *         MGM.\n */\n\n#ifndef __EOSMGM_PROTOCOLHANDLERFACTORY__HH__\n#define __EOSMGM_PROTOCOLHANDLERFACTORY__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/HttpHandler.hh\"\n#include \"mgm/http/s3/S3Handler.hh\"\n#include \"mgm/http/webdav/WebDAVHandler.hh\"\n#include \"common/http/ProtocolHandlerFactory.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n#include <map>\n#include <string>\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\nclass ProtocolHandlerFactory : public eos::common::ProtocolHandlerFactory\n{\npublic:\n\n  ProtocolHandlerFactory () {};\n  virtual ~ProtocolHandlerFactory () {};\n\n  /**\n   * Factory function to create an appropriate object which will handle this\n   * request based on the method and headers.\n   *\n   * @param method  the request verb used by the client (GET, PUT, etc)\n   * @param headers the map of request headers\n   * @param vid     the mapped virtual identity of this client\n   *\n   * @return a concrete ProtocolHandler, or NULL if no matching protocol found\n   */\n  eos::common::ProtocolHandler*\n  CreateProtocolHandler (const std::string                     &method,\n                         std::map<std::string, std::string>    &headers,\n                         eos::common::VirtualIdentity          *vid)\n  {\n    if (S3Handler::Matches(method, headers))\n    {\n      return new S3Handler(vid);\n    }\n    else if (WebDAVHandler::Matches(method, headers))\n    {\n      return new WebDAVHandler(vid);\n    }\n    else if (HttpHandler::Matches(method, headers))\n    {\n      return new HttpHandler(vid);\n    }\n\n    else return NULL;\n  };\n};\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_END\n\n#endif /* __EOSMGM_PROTOCOLHANDLERFACTORY__HH__ */\n"
  },
  {
    "path": "mgm/http/rapidxml/license.txt",
    "content": "Use of this software is granted under one of the following two licenses,\r\nto be chosen freely by the user.\r\n\r\n1. Boost Software License - Version 1.0 - August 17th, 2003\r\n===============================================================================\r\n\r\nCopyright (c) 2006, 2007 Marcin Kalicinski\r\n\r\nPermission is hereby granted, free of charge, to any person or organization\r\nobtaining a copy of the software and accompanying documentation covered by\r\nthis license (the \"Software\") to use, reproduce, display, distribute,\r\nexecute, and transmit the Software, and to prepare derivative works of the\r\nSoftware, and to permit third-parties to whom the Software is furnished to\r\ndo so, all subject to the following:\r\n\r\nThe copyright notices in the Software and this entire statement, including\r\nthe above license grant, this restriction and the following disclaimer,\r\nmust be included in all copies of the Software, in whole or in part, and\r\nall derivative works of the Software, unless such copies or derivative\r\nworks are solely in the form of machine-executable object code generated by\r\na source language processor.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\r\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\r\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\r\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r\nDEALINGS IN THE SOFTWARE.\r\n\r\n2. The MIT License\r\n===============================================================================\r\n\r\nCopyright (c) 2006, 2007 Marcin Kalicinski\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy \r\nof this software and associated documentation files (the \"Software\"), to deal \r\nin the Software without restriction, including without limitation the rights \r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies \r\nof the Software, and to permit persons to whom the Software is furnished to do so, \r\nsubject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all \r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL \r\nTHE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, \r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS \r\nIN THE SOFTWARE.\r\n"
  },
  {
    "path": "mgm/http/rapidxml/rapidxml.hpp",
    "content": "#ifndef RAPIDXML_HPP_INCLUDED\r\n#define RAPIDXML_HPP_INCLUDED\r\n\r\n// Copyright (C) 2006, 2009 Marcin Kalicinski\r\n// Version 1.13\r\n// Revision $DateTime: 2009/05/13 01:46:17 $\r\n//! \\file rapidxml.hpp This file contains rapidxml parser and DOM implementation\r\n\r\n// If standard library is disabled, user must provide implementations of required functions and typedefs\r\n#if !defined(RAPIDXML_NO_STDLIB)\r\n    #include <cstdlib>      // For std::size_t\r\n    #include <cassert>      // For assert\r\n    #include <new>          // For placement new\r\n#endif\r\n\r\n// On MSVC, disable \"conditional expression is constant\" warning (level 4). \r\n// This warning is almost impossible to avoid with certain types of templated code\r\n#ifdef _MSC_VER\r\n    #pragma warning(push)\r\n    #pragma warning(disable:4127)   // Conditional expression is constant\r\n#endif\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// RAPIDXML_PARSE_ERROR\r\n    \r\n#if defined(RAPIDXML_NO_EXCEPTIONS)\r\n\r\n#define RAPIDXML_PARSE_ERROR(what, where) { parse_error_handler(what, where); assert(0); }\r\n\r\nnamespace rapidxml\r\n{\r\n    //! When exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, \r\n    //! this function is called to notify user about the error.\r\n    //! It must be defined by the user.\r\n    //! <br><br>\r\n    //! This function cannot return. If it does, the results are undefined.\r\n    //! <br><br>\r\n    //! A very simple definition might look like that:\r\n    //! <pre>\r\n    //! void %rapidxml::%parse_error_handler(const char *what, void *where)\r\n    //! {\r\n    //!     std::cout << \"Parse error: \" << what << \"\\n\";\r\n    //!     std::abort();\r\n    //! }\r\n    //! </pre>\r\n    //! \\param what Human readable description of the error.\r\n    //! \\param where Pointer to character data where error was detected.\r\n    void parse_error_handler(const char *what, void *where);\r\n}\r\n\r\n#else\r\n    \r\n#include <exception>    // For std::exception\r\n\r\n#define RAPIDXML_PARSE_ERROR(what, where) throw parse_error(what, where)\r\n\r\nnamespace rapidxml\r\n{\r\n\r\n    //! Parse error exception. \r\n    //! This exception is thrown by the parser when an error occurs. \r\n    //! Use what() function to get human-readable error message. \r\n    //! Use where() function to get a pointer to position within source text where error was detected.\r\n    //! <br><br>\r\n    //! If throwing exceptions by the parser is undesirable, \r\n    //! it can be disabled by defining RAPIDXML_NO_EXCEPTIONS macro before rapidxml.hpp is included.\r\n    //! This will cause the parser to call rapidxml::parse_error_handler() function instead of throwing an exception.\r\n    //! This function must be defined by the user.\r\n    //! <br><br>\r\n    //! This class derives from <code>std::exception</code> class.\r\n    class parse_error: public std::exception\r\n    {\r\n    \r\n    public:\r\n    \r\n        //! Constructs parse error\r\n        parse_error(const char *what, void *where)\r\n            : m_what(what)\r\n            , m_where(where)\r\n        {\r\n        }\r\n\r\n        //! Gets human readable description of error.\r\n        //! \\return Pointer to null terminated description of the error.\r\n        virtual const char *what() const throw()\r\n        {\r\n            return m_what;\r\n        }\r\n\r\n        //! Gets pointer to character data where error happened.\r\n        //! Ch should be the same as char type of xml_document that produced the error.\r\n        //! \\return Pointer to location within the parsed string where error occured.\r\n        template<class Ch>\r\n        Ch *where() const\r\n        {\r\n            return reinterpret_cast<Ch *>(m_where);\r\n        }\r\n\r\n    private:  \r\n\r\n        const char *m_what;\r\n        void *m_where;\r\n\r\n    };\r\n}\r\n\r\n#endif\r\n\r\n///////////////////////////////////////////////////////////////////////////\r\n// Pool sizes\r\n\r\n#ifndef RAPIDXML_STATIC_POOL_SIZE\r\n    // Size of static memory block of memory_pool.\r\n    // Define RAPIDXML_STATIC_POOL_SIZE before including rapidxml.hpp if you want to override the default value.\r\n    // No dynamic memory allocations are performed by memory_pool until static memory is exhausted.\r\n    #define RAPIDXML_STATIC_POOL_SIZE (64 * 1024)\r\n#endif\r\n\r\n#ifndef RAPIDXML_DYNAMIC_POOL_SIZE\r\n    // Size of dynamic memory block of memory_pool.\r\n    // Define RAPIDXML_DYNAMIC_POOL_SIZE before including rapidxml.hpp if you want to override the default value.\r\n    // After the static block is exhausted, dynamic blocks with approximately this size are allocated by memory_pool.\r\n    #define RAPIDXML_DYNAMIC_POOL_SIZE (64 * 1024)\r\n#endif\r\n\r\n#ifndef RAPIDXML_ALIGNMENT\r\n    // Memory allocation alignment.\r\n    // Define RAPIDXML_ALIGNMENT before including rapidxml.hpp if you want to override the default value, which is the size of pointer.\r\n    // All memory allocations for nodes, attributes and strings will be aligned to this value.\r\n    // This must be a power of 2 and at least 1, otherwise memory_pool will not work.\r\n    #define RAPIDXML_ALIGNMENT sizeof(void *)\r\n#endif\r\n\r\nnamespace rapidxml\r\n{\r\n    // Forward declarations\r\n    template<class Ch> class xml_node;\r\n    template<class Ch> class xml_attribute;\r\n    template<class Ch> class xml_document;\r\n    \r\n    //! Enumeration listing all node types produced by the parser.\r\n    //! Use xml_node::type() function to query node type.\r\n    enum node_type\r\n    {\r\n        node_document,      //!< A document node. Name and value are empty.\r\n        node_element,       //!< An element node. Name contains element name. Value contains text of first data node.\r\n        node_data,          //!< A data node. Name is empty. Value contains data text.\r\n        node_cdata,         //!< A CDATA node. Name is empty. Value contains data text.\r\n        node_comment,       //!< A comment node. Name is empty. Value contains comment text.\r\n        node_declaration,   //!< A declaration node. Name and value are empty. Declaration parameters (version, encoding and standalone) are in node attributes.\r\n        node_doctype,       //!< A DOCTYPE node. Name is empty. Value contains DOCTYPE text.\r\n        node_pi             //!< A PI node. Name contains target. Value contains instructions.\r\n    };\r\n\r\n    ///////////////////////////////////////////////////////////////////////\r\n    // Parsing flags\r\n\r\n    //! Parse flag instructing the parser to not create data nodes. \r\n    //! Text of first data node will still be placed in value of parent element, unless rapidxml::parse_no_element_values flag is also specified.\r\n    //! Can be combined with other flags by use of | operator.\r\n    //! <br><br>\r\n    //! See xml_document::parse() function.\r\n    const int parse_no_data_nodes = 0x1;            \r\n\r\n    //! Parse flag instructing the parser to not use text of first data node as a value of parent element.\r\n    //! Can be combined with other flags by use of | operator.\r\n    //! Note that child data nodes of element node take precendence over its value when printing. \r\n    //! That is, if element has one or more child data nodes <em>and</em> a value, the value will be ignored.\r\n    //! Use rapidxml::parse_no_data_nodes flag to prevent creation of data nodes if you want to manipulate data using values of elements.\r\n    //! <br><br>\r\n    //! See xml_document::parse() function.\r\n    const int parse_no_element_values = 0x2;\r\n    \r\n    //! Parse flag instructing the parser to not place zero terminators after strings in the source text.\r\n    //! By default zero terminators are placed, modifying source text.\r\n    //! Can be combined with other flags by use of | operator.\r\n    //! <br><br>\r\n    //! See xml_document::parse() function.\r\n    const int parse_no_string_terminators = 0x4;\r\n    \r\n    //! Parse flag instructing the parser to not translate entities in the source text.\r\n    //! By default entities are translated, modifying source text.\r\n    //! Can be combined with other flags by use of | operator.\r\n    //! <br><br>\r\n    //! See xml_document::parse() function.\r\n    const int parse_no_entity_translation = 0x8;\r\n    \r\n    //! Parse flag instructing the parser to disable UTF-8 handling and assume plain 8 bit characters.\r\n    //! By default, UTF-8 handling is enabled.\r\n    //! Can be combined with other flags by use of | operator.\r\n    //! <br><br>\r\n    //! See xml_document::parse() function.\r\n    const int parse_no_utf8 = 0x10;\r\n    \r\n    //! Parse flag instructing the parser to create XML declaration node.\r\n    //! By default, declaration node is not created.\r\n    //! Can be combined with other flags by use of | operator.\r\n    //! <br><br>\r\n    //! See xml_document::parse() function.\r\n    const int parse_declaration_node = 0x20;\r\n    \r\n    //! Parse flag instructing the parser to create comments nodes.\r\n    //! By default, comment nodes are not created.\r\n    //! Can be combined with other flags by use of | operator.\r\n    //! <br><br>\r\n    //! See xml_document::parse() function.\r\n    const int parse_comment_nodes = 0x40;\r\n    \r\n    //! Parse flag instructing the parser to create DOCTYPE node.\r\n    //! By default, doctype node is not created.\r\n    //! Although W3C specification allows at most one DOCTYPE node, RapidXml will silently accept documents with more than one.\r\n    //! Can be combined with other flags by use of | operator.\r\n    //! <br><br>\r\n    //! See xml_document::parse() function.\r\n    const int parse_doctype_node = 0x80;\r\n    \r\n    //! Parse flag instructing the parser to create PI nodes.\r\n    //! By default, PI nodes are not created.\r\n    //! Can be combined with other flags by use of | operator.\r\n    //! <br><br>\r\n    //! See xml_document::parse() function.\r\n    const int parse_pi_nodes = 0x100;\r\n    \r\n    //! Parse flag instructing the parser to validate closing tag names. \r\n    //! If not set, name inside closing tag is irrelevant to the parser.\r\n    //! By default, closing tags are not validated.\r\n    //! Can be combined with other flags by use of | operator.\r\n    //! <br><br>\r\n    //! See xml_document::parse() function.\r\n    const int parse_validate_closing_tags = 0x200;\r\n    \r\n    //! Parse flag instructing the parser to trim all leading and trailing whitespace of data nodes.\r\n    //! By default, whitespace is not trimmed. \r\n    //! This flag does not cause the parser to modify source text.\r\n    //! Can be combined with other flags by use of | operator.\r\n    //! <br><br>\r\n    //! See xml_document::parse() function.\r\n    const int parse_trim_whitespace = 0x400;\r\n\r\n    //! Parse flag instructing the parser to condense all whitespace runs of data nodes to a single space character.\r\n    //! Trimming of leading and trailing whitespace of data is controlled by rapidxml::parse_trim_whitespace flag.\r\n    //! By default, whitespace is not normalized. \r\n    //! If this flag is specified, source text will be modified.\r\n    //! Can be combined with other flags by use of | operator.\r\n    //! <br><br>\r\n    //! See xml_document::parse() function.\r\n    const int parse_normalize_whitespace = 0x800;\r\n\r\n    // Compound flags\r\n    \r\n    //! Parse flags which represent default behaviour of the parser. \r\n    //! This is always equal to 0, so that all other flags can be simply ored together.\r\n    //! Normally there is no need to inconveniently disable flags by anding with their negated (~) values.\r\n    //! This also means that meaning of each flag is a <i>negation</i> of the default setting. \r\n    //! For example, if flag name is rapidxml::parse_no_utf8, it means that utf-8 is <i>enabled</i> by default,\r\n    //! and using the flag will disable it.\r\n    //! <br><br>\r\n    //! See xml_document::parse() function.\r\n    const int parse_default = 0;\r\n    \r\n    //! A combination of parse flags that forbids any modifications of the source text. \r\n    //! This also results in faster parsing. However, note that the following will occur:\r\n    //! <ul>\r\n    //! <li>names and values of nodes will not be zero terminated, you have to use xml_base::name_size() and xml_base::value_size() functions to determine where name and value ends</li>\r\n    //! <li>entities will not be translated</li>\r\n    //! <li>whitespace will not be normalized</li>\r\n    //! </ul>\r\n    //! See xml_document::parse() function.\r\n    const int parse_non_destructive = parse_no_string_terminators | parse_no_entity_translation;\r\n    \r\n    //! A combination of parse flags resulting in fastest possible parsing, without sacrificing important data.\r\n    //! <br><br>\r\n    //! See xml_document::parse() function.\r\n    const int parse_fastest = parse_non_destructive | parse_no_data_nodes;\r\n    \r\n    //! A combination of parse flags resulting in largest amount of data being extracted. \r\n    //! This usually results in slowest parsing.\r\n    //! <br><br>\r\n    //! See xml_document::parse() function.\r\n    const int parse_full = parse_declaration_node | parse_comment_nodes | parse_doctype_node | parse_pi_nodes | parse_validate_closing_tags;\r\n\r\n    ///////////////////////////////////////////////////////////////////////\r\n    // Internals\r\n\r\n    //! \\cond internal\r\n    namespace internal\r\n    {\r\n\r\n        // Struct that contains lookup tables for the parser\r\n        // It must be a template to allow correct linking (because it has static data members, which are defined in a header file).\r\n        template<int Dummy>\r\n        struct lookup_tables\r\n        {\r\n            static const unsigned char lookup_whitespace[256];              // Whitespace table\r\n            static const unsigned char lookup_node_name[256];               // Node name table\r\n            static const unsigned char lookup_text[256];                    // Text table\r\n            static const unsigned char lookup_text_pure_no_ws[256];         // Text table\r\n            static const unsigned char lookup_text_pure_with_ws[256];       // Text table\r\n            static const unsigned char lookup_attribute_name[256];          // Attribute name table\r\n            static const unsigned char lookup_attribute_data_1[256];        // Attribute data table with single quote\r\n            static const unsigned char lookup_attribute_data_1_pure[256];   // Attribute data table with single quote\r\n            static const unsigned char lookup_attribute_data_2[256];        // Attribute data table with double quotes\r\n            static const unsigned char lookup_attribute_data_2_pure[256];   // Attribute data table with double quotes\r\n            static const unsigned char lookup_digits[256];                  // Digits\r\n            static const unsigned char lookup_upcase[256];                  // To uppercase conversion table for ASCII characters\r\n        };\r\n\r\n        // Find length of the string\r\n        template<class Ch>\r\n        inline std::size_t measure(const Ch *p)\r\n        {\r\n            const Ch *tmp = p;\r\n            while (*tmp) \r\n                ++tmp;\r\n            return tmp - p;\r\n        }\r\n\r\n        // Compare strings for equality\r\n        template<class Ch>\r\n        inline bool compare(const Ch *p1, std::size_t size1, const Ch *p2, std::size_t size2, bool case_sensitive)\r\n        {\r\n            if (size1 != size2)\r\n                return false;\r\n            if (case_sensitive)\r\n            {\r\n                for (const Ch *end = p1 + size1; p1 < end; ++p1, ++p2)\r\n                    if (*p1 != *p2)\r\n                        return false;\r\n            }\r\n            else\r\n            {\r\n                for (const Ch *end = p1 + size1; p1 < end; ++p1, ++p2)\r\n                    if (lookup_tables<0>::lookup_upcase[static_cast<unsigned char>(*p1)] != lookup_tables<0>::lookup_upcase[static_cast<unsigned char>(*p2)])\r\n                        return false;\r\n            }\r\n            return true;\r\n        }\r\n    }\r\n    //! \\endcond\r\n\r\n    ///////////////////////////////////////////////////////////////////////\r\n    // Memory pool\r\n    \r\n    //! This class is used by the parser to create new nodes and attributes, without overheads of dynamic memory allocation.\r\n    //! In most cases, you will not need to use this class directly. \r\n    //! However, if you need to create nodes manually or modify names/values of nodes, \r\n    //! you are encouraged to use memory_pool of relevant xml_document to allocate the memory. \r\n    //! Not only is this faster than allocating them by using <code>new</code> operator, \r\n    //! but also their lifetime will be tied to the lifetime of document, \r\n    //! possibly simplyfing memory management. \r\n    //! <br><br>\r\n    //! Call allocate_node() or allocate_attribute() functions to obtain new nodes or attributes from the pool. \r\n    //! You can also call allocate_string() function to allocate strings.\r\n    //! Such strings can then be used as names or values of nodes without worrying about their lifetime.\r\n    //! Note that there is no <code>free()</code> function -- all allocations are freed at once when clear() function is called, \r\n    //! or when the pool is destroyed.\r\n    //! <br><br>\r\n    //! It is also possible to create a standalone memory_pool, and use it \r\n    //! to allocate nodes, whose lifetime will not be tied to any document.\r\n    //! <br><br>\r\n    //! Pool maintains <code>RAPIDXML_STATIC_POOL_SIZE</code> bytes of statically allocated memory. \r\n    //! Until static memory is exhausted, no dynamic memory allocations are done.\r\n    //! When static memory is exhausted, pool allocates additional blocks of memory of size <code>RAPIDXML_DYNAMIC_POOL_SIZE</code> each,\r\n    //! by using global <code>new[]</code> and <code>delete[]</code> operators. \r\n    //! This behaviour can be changed by setting custom allocation routines. \r\n    //! Use set_allocator() function to set them.\r\n    //! <br><br>\r\n    //! Allocations for nodes, attributes and strings are aligned at <code>RAPIDXML_ALIGNMENT</code> bytes.\r\n    //! This value defaults to the size of pointer on target architecture.\r\n    //! <br><br>\r\n    //! To obtain absolutely top performance from the parser,\r\n    //! it is important that all nodes are allocated from a single, contiguous block of memory.\r\n    //! Otherwise, cache misses when jumping between two (or more) disjoint blocks of memory can slow down parsing quite considerably.\r\n    //! If required, you can tweak <code>RAPIDXML_STATIC_POOL_SIZE</code>, <code>RAPIDXML_DYNAMIC_POOL_SIZE</code> and <code>RAPIDXML_ALIGNMENT</code> \r\n    //! to obtain best wasted memory to performance compromise.\r\n    //! To do it, define their values before rapidxml.hpp file is included.\r\n    //! \\param Ch Character type of created nodes. \r\n    template<class Ch = char>\r\n    class memory_pool\r\n    {\r\n        \r\n    public:\r\n\r\n        //! \\cond internal\r\n        typedef void *(alloc_func)(std::size_t);       // Type of user-defined function used to allocate memory\r\n        typedef void (free_func)(void *);              // Type of user-defined function used to free memory\r\n        //! \\endcond\r\n        \r\n        //! Constructs empty pool with default allocator functions.\r\n        memory_pool()\r\n            : m_alloc_func(0)\r\n            , m_free_func(0)\r\n        {\r\n            init();\r\n        }\r\n\r\n        //! Destroys pool and frees all the memory. \r\n        //! This causes memory occupied by nodes allocated by the pool to be freed.\r\n        //! Nodes allocated from the pool are no longer valid.\r\n        ~memory_pool()\r\n        {\r\n            clear();\r\n        }\r\n\r\n        //! Allocates a new node from the pool, and optionally assigns name and value to it. \r\n        //! If the allocation request cannot be accomodated, this function will throw <code>std::bad_alloc</code>.\r\n        //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function\r\n        //! will call rapidxml::parse_error_handler() function.\r\n        //! \\param type Type of node to create.\r\n        //! \\param name Name to assign to the node, or 0 to assign no name.\r\n        //! \\param value Value to assign to the node, or 0 to assign no value.\r\n        //! \\param name_size Size of name to assign, or 0 to automatically calculate size from name string.\r\n        //! \\param value_size Size of value to assign, or 0 to automatically calculate size from value string.\r\n        //! \\return Pointer to allocated node. This pointer will never be NULL.\r\n        xml_node<Ch> *allocate_node(node_type type, \r\n                                    const Ch *name = 0, const Ch *value = 0, \r\n                                    std::size_t name_size = 0, std::size_t value_size = 0)\r\n        {\r\n            void *memory = allocate_aligned(sizeof(xml_node<Ch>));\r\n            xml_node<Ch> *node = new(memory) xml_node<Ch>(type);\r\n            if (name)\r\n            {\r\n                if (name_size > 0)\r\n                    node->name(name, name_size);\r\n                else\r\n                    node->name(name);\r\n            }\r\n            if (value)\r\n            {\r\n                if (value_size > 0)\r\n                    node->value(value, value_size);\r\n                else\r\n                    node->value(value);\r\n            }\r\n            return node;\r\n        }\r\n\r\n        //! Allocates a new attribute from the pool, and optionally assigns name and value to it.\r\n        //! If the allocation request cannot be accomodated, this function will throw <code>std::bad_alloc</code>.\r\n        //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function\r\n        //! will call rapidxml::parse_error_handler() function.\r\n        //! \\param name Name to assign to the attribute, or 0 to assign no name.\r\n        //! \\param value Value to assign to the attribute, or 0 to assign no value.\r\n        //! \\param name_size Size of name to assign, or 0 to automatically calculate size from name string.\r\n        //! \\param value_size Size of value to assign, or 0 to automatically calculate size from value string.\r\n        //! \\return Pointer to allocated attribute. This pointer will never be NULL.\r\n        xml_attribute<Ch> *allocate_attribute(const Ch *name = 0, const Ch *value = 0, \r\n                                              std::size_t name_size = 0, std::size_t value_size = 0)\r\n        {\r\n            void *memory = allocate_aligned(sizeof(xml_attribute<Ch>));\r\n            xml_attribute<Ch> *attribute = new(memory) xml_attribute<Ch>;\r\n            if (name)\r\n            {\r\n                if (name_size > 0)\r\n                    attribute->name(name, name_size);\r\n                else\r\n                    attribute->name(name);\r\n            }\r\n            if (value)\r\n            {\r\n                if (value_size > 0)\r\n                    attribute->value(value, value_size);\r\n                else\r\n                    attribute->value(value);\r\n            }\r\n            return attribute;\r\n        }\r\n\r\n        //! Allocates a char array of given size from the pool, and optionally copies a given string to it.\r\n        //! If the allocation request cannot be accomodated, this function will throw <code>std::bad_alloc</code>.\r\n        //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function\r\n        //! will call rapidxml::parse_error_handler() function.\r\n        //! \\param source String to initialize the allocated memory with, or 0 to not initialize it.\r\n        //! \\param size Number of characters to allocate, or zero to calculate it automatically from source string length; if size is 0, source string must be specified and null terminated.\r\n        //! \\return Pointer to allocated char array. This pointer will never be NULL.\r\n        Ch *allocate_string(const Ch *source = 0, std::size_t size = 0)\r\n        {\r\n            assert(source || size);     // Either source or size (or both) must be specified\r\n            if (size == 0)\r\n                size = internal::measure(source) + 1;\r\n            Ch *result = static_cast<Ch *>(allocate_aligned(size * sizeof(Ch)));\r\n            if (source)\r\n                for (std::size_t i = 0; i < size; ++i)\r\n                    result[i] = source[i];\r\n            return result;\r\n        }\r\n\r\n        //! Clones an xml_node and its hierarchy of child nodes and attributes.\r\n        //! Nodes and attributes are allocated from this memory pool.\r\n        //! Names and values are not cloned, they are shared between the clone and the source.\r\n        //! Result node can be optionally specified as a second parameter, \r\n        //! in which case its contents will be replaced with cloned source node.\r\n        //! This is useful when you want to clone entire document.\r\n        //! \\param source Node to clone.\r\n        //! \\param result Node to put results in, or 0 to automatically allocate result node\r\n        //! \\return Pointer to cloned node. This pointer will never be NULL.\r\n        xml_node<Ch> *clone_node(const xml_node<Ch> *source, xml_node<Ch> *result = 0)\r\n        {\r\n            // Prepare result node\r\n            if (result)\r\n            {\r\n                result->remove_all_attributes();\r\n                result->remove_all_nodes();\r\n                result->type(source->type());\r\n            }\r\n            else\r\n                result = allocate_node(source->type());\r\n\r\n            // Clone name and value\r\n            result->name(source->name(), source->name_size());\r\n            result->value(source->value(), source->value_size());\r\n\r\n            // Clone child nodes and attributes\r\n            for (xml_node<Ch> *child = source->first_node(); child; child = child->next_sibling())\r\n                result->append_node(clone_node(child));\r\n            for (xml_attribute<Ch> *attr = source->first_attribute(); attr; attr = attr->next_attribute())\r\n                result->append_attribute(allocate_attribute(attr->name(), attr->value(), attr->name_size(), attr->value_size()));\r\n\r\n            return result;\r\n        }\r\n\r\n        //! Clears the pool. \r\n        //! This causes memory occupied by nodes allocated by the pool to be freed.\r\n        //! Any nodes or strings allocated from the pool will no longer be valid.\r\n        void clear()\r\n        {\r\n            while (m_begin != m_static_memory)\r\n            {\r\n                char *previous_begin = reinterpret_cast<header *>(align(m_begin))->previous_begin;\r\n                if (m_free_func)\r\n                    m_free_func(m_begin);\r\n                else\r\n                    delete[] m_begin;\r\n                m_begin = previous_begin;\r\n            }\r\n            init();\r\n        }\r\n\r\n        //! Sets or resets the user-defined memory allocation functions for the pool.\r\n        //! This can only be called when no memory is allocated from the pool yet, otherwise results are undefined.\r\n        //! Allocation function must not return invalid pointer on failure. It should either throw,\r\n        //! stop the program, or use <code>longjmp()</code> function to pass control to other place of program. \r\n        //! If it returns invalid pointer, results are undefined.\r\n        //! <br><br>\r\n        //! User defined allocation functions must have the following forms:\r\n        //! <br><code>\r\n        //! <br>void *allocate(std::size_t size);\r\n        //! <br>void free(void *pointer);\r\n        //! </code><br>\r\n        //! \\param af Allocation function, or 0 to restore default function\r\n        //! \\param ff Free function, or 0 to restore default function\r\n        void set_allocator(alloc_func *af, free_func *ff)\r\n        {\r\n            assert(m_begin == m_static_memory && m_ptr == align(m_begin));    // Verify that no memory is allocated yet\r\n            m_alloc_func = af;\r\n            m_free_func = ff;\r\n        }\r\n\r\n    private:\r\n\r\n        struct header\r\n        {\r\n            char *previous_begin;\r\n        };\r\n\r\n        void init()\r\n        {\r\n            m_begin = m_static_memory;\r\n            m_ptr = align(m_begin);\r\n            m_end = m_static_memory + sizeof(m_static_memory);\r\n        }\r\n        \r\n        char *align(char *ptr)\r\n        {\r\n            std::size_t alignment = ((RAPIDXML_ALIGNMENT - (std::size_t(ptr) & (RAPIDXML_ALIGNMENT - 1))) & (RAPIDXML_ALIGNMENT - 1));\r\n            return ptr + alignment;\r\n        }\r\n        \r\n        char *allocate_raw(std::size_t size)\r\n        {\r\n            // Allocate\r\n            void *memory;   \r\n            if (m_alloc_func)   // Allocate memory using either user-specified allocation function or global operator new[]\r\n            {\r\n                memory = m_alloc_func(size);\r\n                assert(memory); // Allocator is not allowed to return 0, on failure it must either throw, stop the program or use longjmp\r\n            }\r\n            else\r\n            {\r\n                memory = new char[size];\r\n#ifdef RAPIDXML_NO_EXCEPTIONS\r\n                if (!memory)            // If exceptions are disabled, verify memory allocation, because new will not be able to throw bad_alloc\r\n                    RAPIDXML_PARSE_ERROR(\"out of memory\", 0);\r\n#endif\r\n            }\r\n            return static_cast<char *>(memory);\r\n        }\r\n        \r\n        void *allocate_aligned(std::size_t size)\r\n        {\r\n            // Calculate aligned pointer\r\n            char *result = align(m_ptr);\r\n\r\n            // If not enough memory left in current pool, allocate a new pool\r\n            if (result + size > m_end)\r\n            {\r\n                // Calculate required pool size (may be bigger than RAPIDXML_DYNAMIC_POOL_SIZE)\r\n                std::size_t pool_size = RAPIDXML_DYNAMIC_POOL_SIZE;\r\n                if (pool_size < size)\r\n                    pool_size = size;\r\n                \r\n                // Allocate\r\n                std::size_t alloc_size = sizeof(header) + (2 * RAPIDXML_ALIGNMENT - 2) + pool_size;     // 2 alignments required in worst case: one for header, one for actual allocation\r\n                char *raw_memory = allocate_raw(alloc_size);\r\n                    \r\n                // Setup new pool in allocated memory\r\n                char *pool = align(raw_memory);\r\n                header *new_header = reinterpret_cast<header *>(pool);\r\n                new_header->previous_begin = m_begin;\r\n                m_begin = raw_memory;\r\n                m_ptr = pool + sizeof(header);\r\n                m_end = raw_memory + alloc_size;\r\n\r\n                // Calculate aligned pointer again using new pool\r\n                result = align(m_ptr);\r\n            }\r\n\r\n            // Update pool and return aligned pointer\r\n            m_ptr = result + size;\r\n            return result;\r\n        }\r\n\r\n        char *m_begin;                                      // Start of raw memory making up current pool\r\n        char *m_ptr;                                        // First free byte in current pool\r\n        char *m_end;                                        // One past last available byte in current pool\r\n        char m_static_memory[RAPIDXML_STATIC_POOL_SIZE];    // Static raw memory\r\n        alloc_func *m_alloc_func;                           // Allocator function, or 0 if default is to be used\r\n        free_func *m_free_func;                             // Free function, or 0 if default is to be used\r\n    };\r\n\r\n    ///////////////////////////////////////////////////////////////////////////\r\n    // XML base\r\n\r\n    //! Base class for xml_node and xml_attribute implementing common functions: \r\n    //! name(), name_size(), value(), value_size() and parent().\r\n    //! \\param Ch Character type to use\r\n    template<class Ch = char>\r\n    class xml_base\r\n    {\r\n\r\n    public:\r\n        \r\n        ///////////////////////////////////////////////////////////////////////////\r\n        // Construction & destruction\r\n    \r\n        // Construct a base with empty name, value and parent\r\n        xml_base()\r\n            : m_name(0)\r\n            , m_value(0)\r\n            , m_parent(0)\r\n        {\r\n        }\r\n\r\n        ///////////////////////////////////////////////////////////////////////////\r\n        // Node data access\r\n    \r\n        //! Gets name of the node. \r\n        //! Interpretation of name depends on type of node.\r\n        //! Note that name will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse.\r\n        //! <br><br>\r\n        //! Use name_size() function to determine length of the name.\r\n        //! \\return Name of node, or empty string if node has no name.\r\n        Ch *name() const\r\n        {\r\n            return m_name ? m_name : nullstr();\r\n        }\r\n\r\n        //! Gets size of node name, not including terminator character.\r\n        //! This function works correctly irrespective of whether name is or is not zero terminated.\r\n        //! \\return Size of node name, in characters.\r\n        std::size_t name_size() const\r\n        {\r\n            return m_name ? m_name_size : 0;\r\n        }\r\n\r\n        //! Gets value of node. \r\n        //! Interpretation of value depends on type of node.\r\n        //! Note that value will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse.\r\n        //! <br><br>\r\n        //! Use value_size() function to determine length of the value.\r\n        //! \\return Value of node, or empty string if node has no value.\r\n        Ch *value() const\r\n        {\r\n            return m_value ? m_value : nullstr();\r\n        }\r\n\r\n        //! Gets size of node value, not including terminator character.\r\n        //! This function works correctly irrespective of whether value is or is not zero terminated.\r\n        //! \\return Size of node value, in characters.\r\n        std::size_t value_size() const\r\n        {\r\n            return m_value ? m_value_size : 0;\r\n        }\r\n\r\n        ///////////////////////////////////////////////////////////////////////////\r\n        // Node modification\r\n    \r\n        //! Sets name of node to a non zero-terminated string.\r\n        //! See \\ref ownership_of_strings.\r\n        //! <br><br>\r\n        //! Note that node does not own its name or value, it only stores a pointer to it. \r\n        //! It will not delete or otherwise free the pointer on destruction.\r\n        //! It is reponsibility of the user to properly manage lifetime of the string.\r\n        //! The easiest way to achieve it is to use memory_pool of the document to allocate the string -\r\n        //! on destruction of the document the string will be automatically freed.\r\n        //! <br><br>\r\n        //! Size of name must be specified separately, because name does not have to be zero terminated.\r\n        //! Use name(const Ch *) function to have the length automatically calculated (string must be zero terminated).\r\n        //! \\param name Name of node to set. Does not have to be zero terminated.\r\n        //! \\param size Size of name, in characters. This does not include zero terminator, if one is present.\r\n        void name(const Ch *name, std::size_t size)\r\n        {\r\n            m_name = const_cast<Ch *>(name);\r\n            m_name_size = size;\r\n        }\r\n\r\n        //! Sets name of node to a zero-terminated string.\r\n        //! See also \\ref ownership_of_strings and xml_node::name(const Ch *, std::size_t).\r\n        //! \\param name Name of node to set. Must be zero terminated.\r\n        void name(const Ch *name)\r\n        {\r\n            this->name(name, internal::measure(name));\r\n        }\r\n\r\n        //! Sets value of node to a non zero-terminated string.\r\n        //! See \\ref ownership_of_strings.\r\n        //! <br><br>\r\n        //! Note that node does not own its name or value, it only stores a pointer to it. \r\n        //! It will not delete or otherwise free the pointer on destruction.\r\n        //! It is reponsibility of the user to properly manage lifetime of the string.\r\n        //! The easiest way to achieve it is to use memory_pool of the document to allocate the string -\r\n        //! on destruction of the document the string will be automatically freed.\r\n        //! <br><br>\r\n        //! Size of value must be specified separately, because it does not have to be zero terminated.\r\n        //! Use value(const Ch *) function to have the length automatically calculated (string must be zero terminated).\r\n        //! <br><br>\r\n        //! If an element has a child node of type node_data, it will take precedence over element value when printing.\r\n        //! If you want to manipulate data of elements using values, use parser flag rapidxml::parse_no_data_nodes to prevent creation of data nodes by the parser.\r\n        //! \\param value value of node to set. Does not have to be zero terminated.\r\n        //! \\param size Size of value, in characters. This does not include zero terminator, if one is present.\r\n        void value(const Ch *value, std::size_t size)\r\n        {\r\n            m_value = const_cast<Ch *>(value);\r\n            m_value_size = size;\r\n        }\r\n\r\n        //! Sets value of node to a zero-terminated string.\r\n        //! See also \\ref ownership_of_strings and xml_node::value(const Ch *, std::size_t).\r\n        //! \\param value Vame of node to set. Must be zero terminated.\r\n        void value(const Ch *value)\r\n        {\r\n            this->value(value, internal::measure(value));\r\n        }\r\n\r\n        ///////////////////////////////////////////////////////////////////////////\r\n        // Related nodes access\r\n    \r\n        //! Gets node parent.\r\n        //! \\return Pointer to parent node, or 0 if there is no parent.\r\n        xml_node<Ch> *parent() const\r\n        {\r\n            return m_parent;\r\n        }\r\n\r\n    protected:\r\n\r\n        // Return empty string\r\n        static Ch *nullstr()\r\n        {\r\n            static Ch zero = Ch('\\0');\r\n            return &zero;\r\n        }\r\n\r\n        Ch *m_name;                         // Name of node, or 0 if no name\r\n        Ch *m_value;                        // Value of node, or 0 if no value\r\n        std::size_t m_name_size;            // Length of node name, or undefined of no name\r\n        std::size_t m_value_size;           // Length of node value, or undefined if no value\r\n        xml_node<Ch> *m_parent;             // Pointer to parent node, or 0 if none\r\n\r\n    };\r\n\r\n    //! Class representing attribute node of XML document. \r\n    //! Each attribute has name and value strings, which are available through name() and value() functions (inherited from xml_base).\r\n    //! Note that after parse, both name and value of attribute will point to interior of source text used for parsing. \r\n    //! Thus, this text must persist in memory for the lifetime of attribute.\r\n    //! \\param Ch Character type to use.\r\n    template<class Ch = char>\r\n    class xml_attribute: public xml_base<Ch>\r\n    {\r\n\r\n        friend class xml_node<Ch>;\r\n    \r\n    public:\r\n\r\n        ///////////////////////////////////////////////////////////////////////////\r\n        // Construction & destruction\r\n    \r\n        //! Constructs an empty attribute with the specified type. \r\n        //! Consider using memory_pool of appropriate xml_document if allocating attributes manually.\r\n        xml_attribute()\r\n        {\r\n        }\r\n\r\n        ///////////////////////////////////////////////////////////////////////////\r\n        // Related nodes access\r\n    \r\n        //! Gets document of which attribute is a child.\r\n        //! \\return Pointer to document that contains this attribute, or 0 if there is no parent document.\r\n        xml_document<Ch> *document() const\r\n        {\r\n            if (xml_node<Ch> *node = this->parent())\r\n            {\r\n                while (node->parent())\r\n                    node = node->parent();\r\n                return node->type() == node_document ? static_cast<xml_document<Ch> *>(node) : 0;\r\n            }\r\n            else\r\n                return 0;\r\n        }\r\n\r\n        //! Gets previous attribute, optionally matching attribute name. \r\n        //! \\param name Name of attribute to find, or 0 to return previous attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero\r\n        //! \\param name_size Size of name, in characters, or 0 to have size calculated automatically from string\r\n        //! \\param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters\r\n        //! \\return Pointer to found attribute, or 0 if not found.\r\n        xml_attribute<Ch> *previous_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const\r\n        {\r\n            if (name)\r\n            {\r\n                if (name_size == 0)\r\n                    name_size = internal::measure(name);\r\n                for (xml_attribute<Ch> *attribute = m_prev_attribute; attribute; attribute = attribute->m_prev_attribute)\r\n                    if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))\r\n                        return attribute;\r\n                return 0;\r\n            }\r\n            else\r\n                return this->m_parent ? m_prev_attribute : 0;\r\n        }\r\n\r\n        //! Gets next attribute, optionally matching attribute name. \r\n        //! \\param name Name of attribute to find, or 0 to return next attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero\r\n        //! \\param name_size Size of name, in characters, or 0 to have size calculated automatically from string\r\n        //! \\param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters\r\n        //! \\return Pointer to found attribute, or 0 if not found.\r\n        xml_attribute<Ch> *next_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const\r\n        {\r\n            if (name)\r\n            {\r\n                if (name_size == 0)\r\n                    name_size = internal::measure(name);\r\n                for (xml_attribute<Ch> *attribute = m_next_attribute; attribute; attribute = attribute->m_next_attribute)\r\n                    if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))\r\n                        return attribute;\r\n                return 0;\r\n            }\r\n            else\r\n                return this->m_parent ? m_next_attribute : 0;\r\n        }\r\n\r\n    private:\r\n\r\n        xml_attribute<Ch> *m_prev_attribute;        // Pointer to previous sibling of attribute, or 0 if none; only valid if parent is non-zero\r\n        xml_attribute<Ch> *m_next_attribute;        // Pointer to next sibling of attribute, or 0 if none; only valid if parent is non-zero\r\n    \r\n    };\r\n\r\n    ///////////////////////////////////////////////////////////////////////////\r\n    // XML node\r\n\r\n    //! Class representing a node of XML document. \r\n    //! Each node may have associated name and value strings, which are available through name() and value() functions. \r\n    //! Interpretation of name and value depends on type of the node.\r\n    //! Type of node can be determined by using type() function.\r\n    //! <br><br>\r\n    //! Note that after parse, both name and value of node, if any, will point interior of source text used for parsing. \r\n    //! Thus, this text must persist in the memory for the lifetime of node.\r\n    //! \\param Ch Character type to use.\r\n    template<class Ch = char>\r\n    class xml_node: public xml_base<Ch>\r\n    {\r\n\r\n    public:\r\n\r\n        ///////////////////////////////////////////////////////////////////////////\r\n        // Construction & destruction\r\n    \r\n        //! Constructs an empty node with the specified type. \r\n        //! Consider using memory_pool of appropriate document to allocate nodes manually.\r\n        //! \\param type Type of node to construct.\r\n        xml_node(node_type type)\r\n            : m_type(type)\r\n            , m_first_node(0)\r\n            , m_first_attribute(0)\r\n        {\r\n        }\r\n\r\n        ///////////////////////////////////////////////////////////////////////////\r\n        // Node data access\r\n    \r\n        //! Gets type of node.\r\n        //! \\return Type of node.\r\n        node_type type() const\r\n        {\r\n            return m_type;\r\n        }\r\n\r\n        ///////////////////////////////////////////////////////////////////////////\r\n        // Related nodes access\r\n    \r\n        //! Gets document of which node is a child.\r\n        //! \\return Pointer to document that contains this node, or 0 if there is no parent document.\r\n        xml_document<Ch> *document() const\r\n        {\r\n            xml_node<Ch> *node = const_cast<xml_node<Ch> *>(this);\r\n            while (node->parent())\r\n                node = node->parent();\r\n            return node->type() == node_document ? static_cast<xml_document<Ch> *>(node) : 0;\r\n        }\r\n\r\n        //! Gets first child node, optionally matching node name.\r\n        //! \\param name Name of child to find, or 0 to return first child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero\r\n        //! \\param name_size Size of name, in characters, or 0 to have size calculated automatically from string\r\n        //! \\param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters\r\n        //! \\return Pointer to found child, or 0 if not found.\r\n        xml_node<Ch> *first_node(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const\r\n        {\r\n            if (name)\r\n            {\r\n                if (name_size == 0)\r\n                    name_size = internal::measure(name);\r\n                for (xml_node<Ch> *child = m_first_node; child; child = child->next_sibling())\r\n                    if (internal::compare(child->name(), child->name_size(), name, name_size, case_sensitive))\r\n                        return child;\r\n                return 0;\r\n            }\r\n            else\r\n                return m_first_node;\r\n        }\r\n\r\n        //! Gets last child node, optionally matching node name. \r\n        //! Behaviour is undefined if node has no children.\r\n        //! Use first_node() to test if node has children.\r\n        //! \\param name Name of child to find, or 0 to return last child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero\r\n        //! \\param name_size Size of name, in characters, or 0 to have size calculated automatically from string\r\n        //! \\param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters\r\n        //! \\return Pointer to found child, or 0 if not found.\r\n        xml_node<Ch> *last_node(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const\r\n        {\r\n            assert(m_first_node);  // Cannot query for last child if node has no children\r\n            if (name)\r\n            {\r\n                if (name_size == 0)\r\n                    name_size = internal::measure(name);\r\n                for (xml_node<Ch> *child = m_last_node; child; child = child->previous_sibling())\r\n                    if (internal::compare(child->name(), child->name_size(), name, name_size, case_sensitive))\r\n                        return child;\r\n                return 0;\r\n            }\r\n            else\r\n                return m_last_node;\r\n        }\r\n\r\n        //! Gets previous sibling node, optionally matching node name. \r\n        //! Behaviour is undefined if node has no parent.\r\n        //! Use parent() to test if node has a parent.\r\n        //! \\param name Name of sibling to find, or 0 to return previous sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero\r\n        //! \\param name_size Size of name, in characters, or 0 to have size calculated automatically from string\r\n        //! \\param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters\r\n        //! \\return Pointer to found sibling, or 0 if not found.\r\n        xml_node<Ch> *previous_sibling(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const\r\n        {\r\n            assert(this->m_parent);     // Cannot query for siblings if node has no parent\r\n            if (name)\r\n            {\r\n                if (name_size == 0)\r\n                    name_size = internal::measure(name);\r\n                for (xml_node<Ch> *sibling = m_prev_sibling; sibling; sibling = sibling->m_prev_sibling)\r\n                    if (internal::compare(sibling->name(), sibling->name_size(), name, name_size, case_sensitive))\r\n                        return sibling;\r\n                return 0;\r\n            }\r\n            else\r\n                return m_prev_sibling;\r\n        }\r\n\r\n        //! Gets next sibling node, optionally matching node name. \r\n        //! Behaviour is undefined if node has no parent.\r\n        //! Use parent() to test if node has a parent.\r\n        //! \\param name Name of sibling to find, or 0 to return next sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero\r\n        //! \\param name_size Size of name, in characters, or 0 to have size calculated automatically from string\r\n        //! \\param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters\r\n        //! \\return Pointer to found sibling, or 0 if not found.\r\n        xml_node<Ch> *next_sibling(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const\r\n        {\r\n            assert(this->m_parent);     // Cannot query for siblings if node has no parent\r\n            if (name)\r\n            {\r\n                if (name_size == 0)\r\n                    name_size = internal::measure(name);\r\n                for (xml_node<Ch> *sibling = m_next_sibling; sibling; sibling = sibling->m_next_sibling)\r\n                    if (internal::compare(sibling->name(), sibling->name_size(), name, name_size, case_sensitive))\r\n                        return sibling;\r\n                return 0;\r\n            }\r\n            else\r\n                return m_next_sibling;\r\n        }\r\n\r\n        //! Gets first attribute of node, optionally matching attribute name.\r\n        //! \\param name Name of attribute to find, or 0 to return first attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero\r\n        //! \\param name_size Size of name, in characters, or 0 to have size calculated automatically from string\r\n        //! \\param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters\r\n        //! \\return Pointer to found attribute, or 0 if not found.\r\n        xml_attribute<Ch> *first_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const\r\n        {\r\n            if (name)\r\n            {\r\n                if (name_size == 0)\r\n                    name_size = internal::measure(name);\r\n                for (xml_attribute<Ch> *attribute = m_first_attribute; attribute; attribute = attribute->m_next_attribute)\r\n                    if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))\r\n                        return attribute;\r\n                return 0;\r\n            }\r\n            else\r\n                return m_first_attribute;\r\n        }\r\n\r\n        //! Gets last attribute of node, optionally matching attribute name.\r\n        //! \\param name Name of attribute to find, or 0 to return last attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero\r\n        //! \\param name_size Size of name, in characters, or 0 to have size calculated automatically from string\r\n        //! \\param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters\r\n        //! \\return Pointer to found attribute, or 0 if not found.\r\n        xml_attribute<Ch> *last_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const\r\n        {\r\n            if (name)\r\n            {\r\n                if (name_size == 0)\r\n                    name_size = internal::measure(name);\r\n                for (xml_attribute<Ch> *attribute = m_last_attribute; attribute; attribute = attribute->m_prev_attribute)\r\n                    if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))\r\n                        return attribute;\r\n                return 0;\r\n            }\r\n            else\r\n                return m_first_attribute ? m_last_attribute : 0;\r\n        }\r\n\r\n        ///////////////////////////////////////////////////////////////////////////\r\n        // Node modification\r\n    \r\n        //! Sets type of node.\r\n        //! \\param type Type of node to set.\r\n        void type(node_type type)\r\n        {\r\n            m_type = type;\r\n        }\r\n\r\n        ///////////////////////////////////////////////////////////////////////////\r\n        // Node manipulation\r\n\r\n        //! Prepends a new child node.\r\n        //! The prepended child becomes the first child, and all existing children are moved one position back.\r\n        //! \\param child Node to prepend.\r\n        void prepend_node(xml_node<Ch> *child)\r\n        {\r\n            assert(child && !child->parent() && child->type() != node_document);\r\n            if (first_node())\r\n            {\r\n                child->m_next_sibling = m_first_node;\r\n                m_first_node->m_prev_sibling = child;\r\n            }\r\n            else\r\n            {\r\n                child->m_next_sibling = 0;\r\n                m_last_node = child;\r\n            }\r\n            m_first_node = child;\r\n            child->m_parent = this;\r\n            child->m_prev_sibling = 0;\r\n        }\r\n\r\n        //! Appends a new child node. \r\n        //! The appended child becomes the last child.\r\n        //! \\param child Node to append.\r\n        void append_node(xml_node<Ch> *child)\r\n        {\r\n            assert(child && !child->parent() && child->type() != node_document);\r\n            if (first_node())\r\n            {\r\n                child->m_prev_sibling = m_last_node;\r\n                m_last_node->m_next_sibling = child;\r\n            }\r\n            else\r\n            {\r\n                child->m_prev_sibling = 0;\r\n                m_first_node = child;\r\n            }\r\n            m_last_node = child;\r\n            child->m_parent = this;\r\n            child->m_next_sibling = 0;\r\n        }\r\n\r\n        //! Inserts a new child node at specified place inside the node. \r\n        //! All children after and including the specified node are moved one position back.\r\n        //! \\param where Place where to insert the child, or 0 to insert at the back.\r\n        //! \\param child Node to insert.\r\n        void insert_node(xml_node<Ch> *where, xml_node<Ch> *child)\r\n        {\r\n            assert(!where || where->parent() == this);\r\n            assert(child && !child->parent() && child->type() != node_document);\r\n            if (where == m_first_node)\r\n                prepend_node(child);\r\n            else if (where == 0)\r\n                append_node(child);\r\n            else\r\n            {\r\n                child->m_prev_sibling = where->m_prev_sibling;\r\n                child->m_next_sibling = where;\r\n                where->m_prev_sibling->m_next_sibling = child;\r\n                where->m_prev_sibling = child;\r\n                child->m_parent = this;\r\n            }\r\n        }\r\n\r\n        //! Removes first child node. \r\n        //! If node has no children, behaviour is undefined.\r\n        //! Use first_node() to test if node has children.\r\n        void remove_first_node()\r\n        {\r\n            assert(first_node());\r\n            xml_node<Ch> *child = m_first_node;\r\n            m_first_node = child->m_next_sibling;\r\n            if (child->m_next_sibling)\r\n                child->m_next_sibling->m_prev_sibling = 0;\r\n            else\r\n                m_last_node = 0;\r\n            child->m_parent = 0;\r\n        }\r\n\r\n        //! Removes last child of the node. \r\n        //! If node has no children, behaviour is undefined.\r\n        //! Use first_node() to test if node has children.\r\n        void remove_last_node()\r\n        {\r\n            assert(first_node());\r\n            xml_node<Ch> *child = m_last_node;\r\n            if (child->m_prev_sibling)\r\n            {\r\n                m_last_node = child->m_prev_sibling;\r\n                child->m_prev_sibling->m_next_sibling = 0;\r\n            }\r\n            else\r\n                m_first_node = 0;\r\n            child->m_parent = 0;\r\n        }\r\n\r\n        //! Removes specified child from the node\r\n        // \\param where Pointer to child to be removed.\r\n        void remove_node(xml_node<Ch> *where)\r\n        {\r\n            assert(where && where->parent() == this);\r\n            assert(first_node());\r\n            if (where == m_first_node)\r\n                remove_first_node();\r\n            else if (where == m_last_node)\r\n                remove_last_node();\r\n            else\r\n            {\r\n                where->m_prev_sibling->m_next_sibling = where->m_next_sibling;\r\n                where->m_next_sibling->m_prev_sibling = where->m_prev_sibling;\r\n                where->m_parent = 0;\r\n            }\r\n        }\r\n\r\n        //! Removes all child nodes (but not attributes).\r\n        void remove_all_nodes()\r\n        {\r\n            for (xml_node<Ch> *node = first_node(); node; node = node->m_next_sibling)\r\n                node->m_parent = 0;\r\n            m_first_node = 0;\r\n        }\r\n\r\n        //! Prepends a new attribute to the node.\r\n        //! \\param attribute Attribute to prepend.\r\n        void prepend_attribute(xml_attribute<Ch> *attribute)\r\n        {\r\n            assert(attribute && !attribute->parent());\r\n            if (first_attribute())\r\n            {\r\n                attribute->m_next_attribute = m_first_attribute;\r\n                m_first_attribute->m_prev_attribute = attribute;\r\n            }\r\n            else\r\n            {\r\n                attribute->m_next_attribute = 0;\r\n                m_last_attribute = attribute;\r\n            }\r\n            m_first_attribute = attribute;\r\n            attribute->m_parent = this;\r\n            attribute->m_prev_attribute = 0;\r\n        }\r\n\r\n        //! Appends a new attribute to the node.\r\n        //! \\param attribute Attribute to append.\r\n        void append_attribute(xml_attribute<Ch> *attribute)\r\n        {\r\n            assert(attribute && !attribute->parent());\r\n            if (first_attribute())\r\n            {\r\n                attribute->m_prev_attribute = m_last_attribute;\r\n                m_last_attribute->m_next_attribute = attribute;\r\n            }\r\n            else\r\n            {\r\n                attribute->m_prev_attribute = 0;\r\n                m_first_attribute = attribute;\r\n            }\r\n            m_last_attribute = attribute;\r\n            attribute->m_parent = this;\r\n            attribute->m_next_attribute = 0;\r\n        }\r\n\r\n        //! Inserts a new attribute at specified place inside the node. \r\n        //! All attributes after and including the specified attribute are moved one position back.\r\n        //! \\param where Place where to insert the attribute, or 0 to insert at the back.\r\n        //! \\param attribute Attribute to insert.\r\n        void insert_attribute(xml_attribute<Ch> *where, xml_attribute<Ch> *attribute)\r\n        {\r\n            assert(!where || where->parent() == this);\r\n            assert(attribute && !attribute->parent());\r\n            if (where == m_first_attribute)\r\n                prepend_attribute(attribute);\r\n            else if (where == 0)\r\n                append_attribute(attribute);\r\n            else\r\n            {\r\n                attribute->m_prev_attribute = where->m_prev_attribute;\r\n                attribute->m_next_attribute = where;\r\n                where->m_prev_attribute->m_next_attribute = attribute;\r\n                where->m_prev_attribute = attribute;\r\n                attribute->m_parent = this;\r\n            }\r\n        }\r\n\r\n        //! Removes first attribute of the node. \r\n        //! If node has no attributes, behaviour is undefined.\r\n        //! Use first_attribute() to test if node has attributes.\r\n        void remove_first_attribute()\r\n        {\r\n            assert(first_attribute());\r\n            xml_attribute<Ch> *attribute = m_first_attribute;\r\n            if (attribute->m_next_attribute)\r\n            {\r\n                attribute->m_next_attribute->m_prev_attribute = 0;\r\n            }\r\n            else\r\n                m_last_attribute = 0;\r\n            attribute->m_parent = 0;\r\n            m_first_attribute = attribute->m_next_attribute;\r\n        }\r\n\r\n        //! Removes last attribute of the node. \r\n        //! If node has no attributes, behaviour is undefined.\r\n        //! Use first_attribute() to test if node has attributes.\r\n        void remove_last_attribute()\r\n        {\r\n            assert(first_attribute());\r\n            xml_attribute<Ch> *attribute = m_last_attribute;\r\n            if (attribute->m_prev_attribute)\r\n            {\r\n                attribute->m_prev_attribute->m_next_attribute = 0;\r\n                m_last_attribute = attribute->m_prev_attribute;\r\n            }\r\n            else\r\n                m_first_attribute = 0;\r\n            attribute->m_parent = 0;\r\n        }\r\n\r\n        //! Removes specified attribute from node.\r\n        //! \\param where Pointer to attribute to be removed.\r\n        void remove_attribute(xml_attribute<Ch> *where)\r\n        {\r\n            assert(first_attribute() && where->parent() == this);\r\n            if (where == m_first_attribute)\r\n                remove_first_attribute();\r\n            else if (where == m_last_attribute)\r\n                remove_last_attribute();\r\n            else\r\n            {\r\n                where->m_prev_attribute->m_next_attribute = where->m_next_attribute;\r\n                where->m_next_attribute->m_prev_attribute = where->m_prev_attribute;\r\n                where->m_parent = 0;\r\n            }\r\n        }\r\n\r\n        //! Removes all attributes of node.\r\n        void remove_all_attributes()\r\n        {\r\n            for (xml_attribute<Ch> *attribute = first_attribute(); attribute; attribute = attribute->m_next_attribute)\r\n                attribute->m_parent = 0;\r\n            m_first_attribute = 0;\r\n        }\r\n        \r\n    private:\r\n\r\n        ///////////////////////////////////////////////////////////////////////////\r\n        // Restrictions\r\n\r\n        // No copying\r\n        xml_node(const xml_node &);\r\n        void operator =(const xml_node &);\r\n    \r\n        ///////////////////////////////////////////////////////////////////////////\r\n        // Data members\r\n    \r\n        // Note that some of the pointers below have UNDEFINED values if certain other pointers are 0.\r\n        // This is required for maximum performance, as it allows the parser to omit initialization of \r\n        // unneded/redundant values.\r\n        //\r\n        // The rules are as follows:\r\n        // 1. first_node and first_attribute contain valid pointers, or 0 if node has no children/attributes respectively\r\n        // 2. last_node and last_attribute are valid only if node has at least one child/attribute respectively, otherwise they contain garbage\r\n        // 3. prev_sibling and next_sibling are valid only if node has a parent, otherwise they contain garbage\r\n\r\n        node_type m_type;                       // Type of node; always valid\r\n        xml_node<Ch> *m_first_node;             // Pointer to first child node, or 0 if none; always valid\r\n        xml_node<Ch> *m_last_node;              // Pointer to last child node, or 0 if none; this value is only valid if m_first_node is non-zero\r\n        xml_attribute<Ch> *m_first_attribute;   // Pointer to first attribute of node, or 0 if none; always valid\r\n        xml_attribute<Ch> *m_last_attribute;    // Pointer to last attribute of node, or 0 if none; this value is only valid if m_first_attribute is non-zero\r\n        xml_node<Ch> *m_prev_sibling;           // Pointer to previous sibling of node, or 0 if none; this value is only valid if m_parent is non-zero\r\n        xml_node<Ch> *m_next_sibling;           // Pointer to next sibling of node, or 0 if none; this value is only valid if m_parent is non-zero\r\n\r\n    };\r\n\r\n    ///////////////////////////////////////////////////////////////////////////\r\n    // XML document\r\n    \r\n    //! This class represents root of the DOM hierarchy. \r\n    //! It is also an xml_node and a memory_pool through public inheritance.\r\n    //! Use parse() function to build a DOM tree from a zero-terminated XML text string.\r\n    //! parse() function allocates memory for nodes and attributes by using functions of xml_document, \r\n    //! which are inherited from memory_pool.\r\n    //! To access root node of the document, use the document itself, as if it was an xml_node.\r\n    //! \\param Ch Character type to use.\r\n    template<class Ch = char>\r\n    class xml_document: public xml_node<Ch>, public memory_pool<Ch>\r\n    {\r\n    \r\n    public:\r\n\r\n        //! Constructs empty XML document\r\n        xml_document()\r\n            : xml_node<Ch>(node_document)\r\n        {\r\n        }\r\n\r\n        //! Parses zero-terminated XML string according to given flags.\r\n        //! Passed string will be modified by the parser, unless rapidxml::parse_non_destructive flag is used.\r\n        //! The string must persist for the lifetime of the document.\r\n        //! In case of error, rapidxml::parse_error exception will be thrown.\r\n        //! <br><br>\r\n        //! If you want to parse contents of a file, you must first load the file into the memory, and pass pointer to its beginning.\r\n        //! Make sure that data is zero-terminated.\r\n        //! <br><br>\r\n        //! Document can be parsed into multiple times. \r\n        //! Each new call to parse removes previous nodes and attributes (if any), but does not clear memory pool.\r\n        //! \\param text XML data to parse; pointer is non-const to denote fact that this data may be modified by the parser.\r\n        template<int Flags>\r\n        void parse(Ch *text)\r\n        {\r\n            assert(text);\r\n            \r\n            // Remove current contents\r\n            this->remove_all_nodes();\r\n            this->remove_all_attributes();\r\n            \r\n            // Parse BOM, if any\r\n            parse_bom<Flags>(text);\r\n            \r\n            // Parse children\r\n            while (1)\r\n            {\r\n                // Skip whitespace before node\r\n                skip<whitespace_pred, Flags>(text);\r\n                if (*text == 0)\r\n                    break;\r\n\r\n                // Parse and append new child\r\n                if (*text == Ch('<'))\r\n                {\r\n                    ++text;     // Skip '<'\r\n                    if (xml_node<Ch> *node = parse_node<Flags>(text))\r\n                        this->append_node(node);\r\n                }\r\n                else\r\n                    RAPIDXML_PARSE_ERROR(\"expected <\", text);\r\n            }\r\n\r\n        }\r\n\r\n        //! Clears the document by deleting all nodes and clearing the memory pool.\r\n        //! All nodes owned by document pool are destroyed.\r\n        void clear()\r\n        {\r\n            this->remove_all_nodes();\r\n            this->remove_all_attributes();\r\n            memory_pool<Ch>::clear();\r\n        }\r\n        \r\n    private:\r\n\r\n        ///////////////////////////////////////////////////////////////////////\r\n        // Internal character utility functions\r\n        \r\n        // Detect whitespace character\r\n        struct whitespace_pred\r\n        {\r\n            static unsigned char test(Ch ch)\r\n            {\r\n                return internal::lookup_tables<0>::lookup_whitespace[static_cast<unsigned char>(ch)];\r\n            }\r\n        };\r\n\r\n        // Detect node name character\r\n        struct node_name_pred\r\n        {\r\n            static unsigned char test(Ch ch)\r\n            {\r\n                return internal::lookup_tables<0>::lookup_node_name[static_cast<unsigned char>(ch)];\r\n            }\r\n        };\r\n\r\n        // Detect attribute name character\r\n        struct attribute_name_pred\r\n        {\r\n            static unsigned char test(Ch ch)\r\n            {\r\n                return internal::lookup_tables<0>::lookup_attribute_name[static_cast<unsigned char>(ch)];\r\n            }\r\n        };\r\n\r\n        // Detect text character (PCDATA)\r\n        struct text_pred\r\n        {\r\n            static unsigned char test(Ch ch)\r\n            {\r\n                return internal::lookup_tables<0>::lookup_text[static_cast<unsigned char>(ch)];\r\n            }\r\n        };\r\n\r\n        // Detect text character (PCDATA) that does not require processing\r\n        struct text_pure_no_ws_pred\r\n        {\r\n            static unsigned char test(Ch ch)\r\n            {\r\n                return internal::lookup_tables<0>::lookup_text_pure_no_ws[static_cast<unsigned char>(ch)];\r\n            }\r\n        };\r\n\r\n        // Detect text character (PCDATA) that does not require processing\r\n        struct text_pure_with_ws_pred\r\n        {\r\n            static unsigned char test(Ch ch)\r\n            {\r\n                return internal::lookup_tables<0>::lookup_text_pure_with_ws[static_cast<unsigned char>(ch)];\r\n            }\r\n        };\r\n\r\n        // Detect attribute value character\r\n        template<Ch Quote>\r\n        struct attribute_value_pred\r\n        {\r\n            static unsigned char test(Ch ch)\r\n            {\r\n                if (Quote == Ch('\\''))\r\n                    return internal::lookup_tables<0>::lookup_attribute_data_1[static_cast<unsigned char>(ch)];\r\n                if (Quote == Ch('\\\"'))\r\n                    return internal::lookup_tables<0>::lookup_attribute_data_2[static_cast<unsigned char>(ch)];\r\n                return 0;       // Should never be executed, to avoid warnings on Comeau\r\n            }\r\n        };\r\n\r\n        // Detect attribute value character\r\n        template<Ch Quote>\r\n        struct attribute_value_pure_pred\r\n        {\r\n            static unsigned char test(Ch ch)\r\n            {\r\n                if (Quote == Ch('\\''))\r\n                    return internal::lookup_tables<0>::lookup_attribute_data_1_pure[static_cast<unsigned char>(ch)];\r\n                if (Quote == Ch('\\\"'))\r\n                    return internal::lookup_tables<0>::lookup_attribute_data_2_pure[static_cast<unsigned char>(ch)];\r\n                return 0;       // Should never be executed, to avoid warnings on Comeau\r\n            }\r\n        };\r\n\r\n        // Insert coded character, using UTF8 or 8-bit ASCII\r\n        template<int Flags>\r\n        static void insert_coded_character(Ch *&text, unsigned long code)\r\n        {\r\n            if (Flags & parse_no_utf8)\r\n            {\r\n                // Insert 8-bit ASCII character\r\n                // Todo: possibly verify that code is less than 256 and use replacement char otherwise?\r\n                text[0] = static_cast<unsigned char>(code);\r\n                text += 1;\r\n            }\r\n            else\r\n            {\r\n                // Insert UTF8 sequence\r\n                if (code < 0x80)    // 1 byte sequence\r\n                {\r\n\t                text[0] = static_cast<unsigned char>(code);\r\n                    text += 1;\r\n                }\r\n                else if (code < 0x800)  // 2 byte sequence\r\n                {\r\n\t                text[1] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;\r\n\t                text[0] = static_cast<unsigned char>(code | 0xC0);\r\n                    text += 2;\r\n                }\r\n\t            else if (code < 0x10000)    // 3 byte sequence\r\n                {\r\n\t                text[2] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;\r\n\t                text[1] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;\r\n\t                text[0] = static_cast<unsigned char>(code | 0xE0);\r\n                    text += 3;\r\n                }\r\n\t            else if (code < 0x110000)   // 4 byte sequence\r\n                {\r\n\t                text[3] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;\r\n\t                text[2] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;\r\n\t                text[1] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;\r\n\t                text[0] = static_cast<unsigned char>(code | 0xF0);\r\n                    text += 4;\r\n                }\r\n                else    // Invalid, only codes up to 0x10FFFF are allowed in Unicode\r\n                {\r\n                    RAPIDXML_PARSE_ERROR(\"invalid numeric character entity\", text);\r\n                }\r\n            }\r\n        }\r\n\r\n        // Skip characters until predicate evaluates to true\r\n        template<class StopPred, int Flags>\r\n        static void skip(Ch *&text)\r\n        {\r\n            Ch *tmp = text;\r\n            while (StopPred::test(*tmp))\r\n                ++tmp;\r\n            text = tmp;\r\n        }\r\n\r\n        // Skip characters until predicate evaluates to true while doing the following:\r\n        // - replacing XML character entity references with proper characters (&apos; &amp; &quot; &lt; &gt; &#...;)\r\n        // - condensing whitespace sequences to single space character\r\n        template<class StopPred, class StopPredPure, int Flags>\r\n        static Ch *skip_and_expand_character_refs(Ch *&text)\r\n        {\r\n            // If entity translation, whitespace condense and whitespace trimming is disabled, use plain skip\r\n            if (Flags & parse_no_entity_translation && \r\n                !(Flags & parse_normalize_whitespace) &&\r\n                !(Flags & parse_trim_whitespace))\r\n            {\r\n                skip<StopPred, Flags>(text);\r\n                return text;\r\n            }\r\n            \r\n            // Use simple skip until first modification is detected\r\n            skip<StopPredPure, Flags>(text);\r\n\r\n            // Use translation skip\r\n            Ch *src = text;\r\n            Ch *dest = src;\r\n            while (StopPred::test(*src))\r\n            {\r\n                // If entity translation is enabled    \r\n                if (!(Flags & parse_no_entity_translation))\r\n                {\r\n                    // Test if replacement is needed\r\n                    if (src[0] == Ch('&'))\r\n                    {\r\n                        switch (src[1])\r\n                        {\r\n\r\n                        // &amp; &apos;\r\n                        case Ch('a'): \r\n                            if (src[2] == Ch('m') && src[3] == Ch('p') && src[4] == Ch(';'))\r\n                            {\r\n                                *dest = Ch('&');\r\n                                ++dest;\r\n                                src += 5;\r\n                                continue;\r\n                            }\r\n                            if (src[2] == Ch('p') && src[3] == Ch('o') && src[4] == Ch('s') && src[5] == Ch(';'))\r\n                            {\r\n                                *dest = Ch('\\'');\r\n                                ++dest;\r\n                                src += 6;\r\n                                continue;\r\n                            }\r\n                            break;\r\n\r\n                        // &quot;\r\n                        case Ch('q'): \r\n                            if (src[2] == Ch('u') && src[3] == Ch('o') && src[4] == Ch('t') && src[5] == Ch(';'))\r\n                            {\r\n                                *dest = Ch('\"');\r\n                                ++dest;\r\n                                src += 6;\r\n                                continue;\r\n                            }\r\n                            break;\r\n\r\n                        // &gt;\r\n                        case Ch('g'): \r\n                            if (src[2] == Ch('t') && src[3] == Ch(';'))\r\n                            {\r\n                                *dest = Ch('>');\r\n                                ++dest;\r\n                                src += 4;\r\n                                continue;\r\n                            }\r\n                            break;\r\n\r\n                        // &lt;\r\n                        case Ch('l'): \r\n                            if (src[2] == Ch('t') && src[3] == Ch(';'))\r\n                            {\r\n                                *dest = Ch('<');\r\n                                ++dest;\r\n                                src += 4;\r\n                                continue;\r\n                            }\r\n                            break;\r\n\r\n                        // &#...; - assumes ASCII\r\n                        case Ch('#'): \r\n                            if (src[2] == Ch('x'))\r\n                            {\r\n                                unsigned long code = 0;\r\n                                src += 3;   // Skip &#x\r\n                                while (1)\r\n                                {\r\n                                    unsigned char digit = internal::lookup_tables<0>::lookup_digits[static_cast<unsigned char>(*src)];\r\n                                    if (digit == 0xFF)\r\n                                        break;\r\n                                    code = code * 16 + digit;\r\n                                    ++src;\r\n                                }\r\n                                insert_coded_character<Flags>(dest, code);    // Put character in output\r\n                            }\r\n                            else\r\n                            {\r\n                                unsigned long code = 0;\r\n                                src += 2;   // Skip &#\r\n                                while (1)\r\n                                {\r\n                                    unsigned char digit = internal::lookup_tables<0>::lookup_digits[static_cast<unsigned char>(*src)];\r\n                                    if (digit == 0xFF)\r\n                                        break;\r\n                                    code = code * 10 + digit;\r\n                                    ++src;\r\n                                }\r\n                                insert_coded_character<Flags>(dest, code);    // Put character in output\r\n                            }\r\n                            if (*src == Ch(';'))\r\n                                ++src;\r\n                            else\r\n                                RAPIDXML_PARSE_ERROR(\"expected ;\", src);\r\n                            continue;\r\n\r\n                        // Something else\r\n                        default:\r\n                            // Ignore, just copy '&' verbatim\r\n                            break;\r\n\r\n                        }\r\n                    }\r\n                }\r\n                \r\n                // If whitespace condensing is enabled\r\n                if (Flags & parse_normalize_whitespace)\r\n                {\r\n                    // Test if condensing is needed                 \r\n                    if (whitespace_pred::test(*src))\r\n                    {\r\n                        *dest = Ch(' '); ++dest;    // Put single space in dest\r\n                        ++src;                      // Skip first whitespace char\r\n                        // Skip remaining whitespace chars\r\n                        while (whitespace_pred::test(*src))\r\n                            ++src;\r\n                        continue;\r\n                    }\r\n                }\r\n\r\n                // No replacement, only copy character\r\n                *dest++ = *src++;\r\n\r\n            }\r\n\r\n            // Return new end\r\n            text = src;\r\n            return dest;\r\n\r\n        }\r\n\r\n        ///////////////////////////////////////////////////////////////////////\r\n        // Internal parsing functions\r\n        \r\n        // Parse BOM, if any\r\n        template<int Flags>\r\n        void parse_bom(Ch *&text)\r\n        {\r\n            // UTF-8?\r\n            if (static_cast<unsigned char>(text[0]) == 0xEF && \r\n                static_cast<unsigned char>(text[1]) == 0xBB && \r\n                static_cast<unsigned char>(text[2]) == 0xBF)\r\n            {\r\n                text += 3;      // Skup utf-8 bom\r\n            }\r\n        }\r\n\r\n        // Parse XML declaration (<?xml...)\r\n        template<int Flags>\r\n        xml_node<Ch> *parse_xml_declaration(Ch *&text)\r\n        {\r\n            // If parsing of declaration is disabled\r\n            if (!(Flags & parse_declaration_node))\r\n            {\r\n                // Skip until end of declaration\r\n                while (text[0] != Ch('?') || text[1] != Ch('>'))\r\n                {\r\n                    if (!text[0])\r\n                        RAPIDXML_PARSE_ERROR(\"unexpected end of data\", text);\r\n                    ++text;\r\n                }\r\n                text += 2;    // Skip '?>'\r\n                return 0;\r\n            }\r\n\r\n            // Create declaration\r\n            xml_node<Ch> *declaration = this->allocate_node(node_declaration);\r\n\r\n            // Skip whitespace before attributes or ?>\r\n            skip<whitespace_pred, Flags>(text);\r\n\r\n            // Parse declaration attributes\r\n            parse_node_attributes<Flags>(text, declaration);\r\n            \r\n            // Skip ?>\r\n            if (text[0] != Ch('?') || text[1] != Ch('>'))\r\n                RAPIDXML_PARSE_ERROR(\"expected ?>\", text);\r\n            text += 2;\r\n            \r\n            return declaration;\r\n        }\r\n\r\n        // Parse XML comment (<!--...)\r\n        template<int Flags>\r\n        xml_node<Ch> *parse_comment(Ch *&text)\r\n        {\r\n            // If parsing of comments is disabled\r\n            if (!(Flags & parse_comment_nodes))\r\n            {\r\n                // Skip until end of comment\r\n                while (text[0] != Ch('-') || text[1] != Ch('-') || text[2] != Ch('>'))\r\n                {\r\n                    if (!text[0])\r\n                        RAPIDXML_PARSE_ERROR(\"unexpected end of data\", text);\r\n                    ++text;\r\n                }\r\n                text += 3;     // Skip '-->'\r\n                return 0;      // Do not produce comment node\r\n            }\r\n\r\n            // Remember value start\r\n            Ch *value = text;\r\n\r\n            // Skip until end of comment\r\n            while (text[0] != Ch('-') || text[1] != Ch('-') || text[2] != Ch('>'))\r\n            {\r\n                if (!text[0])\r\n                    RAPIDXML_PARSE_ERROR(\"unexpected end of data\", text);\r\n                ++text;\r\n            }\r\n\r\n            // Create comment node\r\n            xml_node<Ch> *comment = this->allocate_node(node_comment);\r\n            comment->value(value, text - value);\r\n            \r\n            // Place zero terminator after comment value\r\n            if (!(Flags & parse_no_string_terminators))\r\n                *text = Ch('\\0');\r\n            \r\n            text += 3;     // Skip '-->'\r\n            return comment;\r\n        }\r\n\r\n        // Parse DOCTYPE\r\n        template<int Flags>\r\n        xml_node<Ch> *parse_doctype(Ch *&text)\r\n        {\r\n            // Remember value start\r\n            Ch *value = text;\r\n\r\n            // Skip to >\r\n            while (*text != Ch('>'))\r\n            {\r\n                // Determine character type\r\n                switch (*text)\r\n                {\r\n                \r\n                // If '[' encountered, scan for matching ending ']' using naive algorithm with depth\r\n                // This works for all W3C test files except for 2 most wicked\r\n                case Ch('['):\r\n                {\r\n                    ++text;     // Skip '['\r\n                    int depth = 1;\r\n                    while (depth > 0)\r\n                    {\r\n                        switch (*text)\r\n                        {\r\n                            case Ch('['): ++depth; break;\r\n                            case Ch(']'): --depth; break;\r\n                            case 0: RAPIDXML_PARSE_ERROR(\"unexpected end of data\", text);\r\n                        }\r\n                        ++text;\r\n                    }\r\n                    break;\r\n                }\r\n                \r\n                // Error on end of text\r\n                case Ch('\\0'):\r\n                    RAPIDXML_PARSE_ERROR(\"unexpected end of data\", text);\r\n                \r\n                // Other character, skip it\r\n                default:\r\n                    ++text;\r\n\r\n                }\r\n            }\r\n            \r\n            // If DOCTYPE nodes enabled\r\n            if (Flags & parse_doctype_node)\r\n            {\r\n                // Create a new doctype node\r\n                xml_node<Ch> *doctype = this->allocate_node(node_doctype);\r\n                doctype->value(value, text - value);\r\n                \r\n                // Place zero terminator after value\r\n                if (!(Flags & parse_no_string_terminators))\r\n                    *text = Ch('\\0');\r\n\r\n                text += 1;      // skip '>'\r\n                return doctype;\r\n            }\r\n            else\r\n            {\r\n                text += 1;      // skip '>'\r\n                return 0;\r\n            }\r\n\r\n        }\r\n\r\n        // Parse PI\r\n        template<int Flags>\r\n        xml_node<Ch> *parse_pi(Ch *&text)\r\n        {\r\n            // If creation of PI nodes is enabled\r\n            if (Flags & parse_pi_nodes)\r\n            {\r\n                // Create pi node\r\n                xml_node<Ch> *pi = this->allocate_node(node_pi);\r\n\r\n                // Extract PI target name\r\n                Ch *name = text;\r\n                skip<node_name_pred, Flags>(text);\r\n                if (text == name)\r\n                    RAPIDXML_PARSE_ERROR(\"expected PI target\", text);\r\n                pi->name(name, text - name);\r\n                \r\n                // Skip whitespace between pi target and pi\r\n                skip<whitespace_pred, Flags>(text);\r\n\r\n                // Remember start of pi\r\n                Ch *value = text;\r\n                \r\n                // Skip to '?>'\r\n                while (text[0] != Ch('?') || text[1] != Ch('>'))\r\n                {\r\n                    if (*text == Ch('\\0'))\r\n                        RAPIDXML_PARSE_ERROR(\"unexpected end of data\", text);\r\n                    ++text;\r\n                }\r\n\r\n                // Set pi value (verbatim, no entity expansion or whitespace normalization)\r\n                pi->value(value, text - value);     \r\n                \r\n                // Place zero terminator after name and value\r\n                if (!(Flags & parse_no_string_terminators))\r\n                {\r\n                    pi->name()[pi->name_size()] = Ch('\\0');\r\n                    pi->value()[pi->value_size()] = Ch('\\0');\r\n                }\r\n                \r\n                text += 2;                          // Skip '?>'\r\n                return pi;\r\n            }\r\n            else\r\n            {\r\n                // Skip to '?>'\r\n                while (text[0] != Ch('?') || text[1] != Ch('>'))\r\n                {\r\n                    if (*text == Ch('\\0'))\r\n                        RAPIDXML_PARSE_ERROR(\"unexpected end of data\", text);\r\n                    ++text;\r\n                }\r\n                text += 2;    // Skip '?>'\r\n                return 0;\r\n            }\r\n        }\r\n\r\n        // Parse and append data\r\n        // Return character that ends data.\r\n        // This is necessary because this character might have been overwritten by a terminating 0\r\n        template<int Flags>\r\n        Ch parse_and_append_data(xml_node<Ch> *node, Ch *&text, Ch *contents_start)\r\n        {\r\n            // Backup to contents start if whitespace trimming is disabled\r\n            if (!(Flags & parse_trim_whitespace))\r\n                text = contents_start;     \r\n            \r\n            // Skip until end of data\r\n            Ch *value = text, *end;\r\n            if (Flags & parse_normalize_whitespace)\r\n                end = skip_and_expand_character_refs<text_pred, text_pure_with_ws_pred, Flags>(text);   \r\n            else\r\n                end = skip_and_expand_character_refs<text_pred, text_pure_no_ws_pred, Flags>(text);\r\n\r\n            // Trim trailing whitespace if flag is set; leading was already trimmed by whitespace skip after >\r\n            if (Flags & parse_trim_whitespace)\r\n            {\r\n                if (Flags & parse_normalize_whitespace)\r\n                {\r\n                    // Whitespace is already condensed to single space characters by skipping function, so just trim 1 char off the end\r\n                    if (*(end - 1) == Ch(' '))\r\n                        --end;\r\n                }\r\n                else\r\n                {\r\n                    // Backup until non-whitespace character is found\r\n                    while (whitespace_pred::test(*(end - 1)))\r\n                        --end;\r\n                }\r\n            }\r\n            \r\n            // If characters are still left between end and value (this test is only necessary if normalization is enabled)\r\n            // Create new data node\r\n            if (!(Flags & parse_no_data_nodes))\r\n            {\r\n                xml_node<Ch> *data = this->allocate_node(node_data);\r\n                data->value(value, end - value);\r\n                node->append_node(data);\r\n            }\r\n\r\n            // Add data to parent node if no data exists yet\r\n            if (!(Flags & parse_no_element_values)) \r\n                if (*node->value() == Ch('\\0'))\r\n                    node->value(value, end - value);\r\n\r\n            // Place zero terminator after value\r\n            if (!(Flags & parse_no_string_terminators))\r\n            {\r\n                Ch ch = *text;\r\n                *end = Ch('\\0');\r\n                return ch;      // Return character that ends data; this is required because zero terminator overwritten it\r\n            }\r\n\r\n            // Return character that ends data\r\n            return *text;\r\n        }\r\n\r\n        // Parse CDATA\r\n        template<int Flags>\r\n        xml_node<Ch> *parse_cdata(Ch *&text)\r\n        {\r\n            // If CDATA is disabled\r\n            if (Flags & parse_no_data_nodes)\r\n            {\r\n                // Skip until end of cdata\r\n                while (text[0] != Ch(']') || text[1] != Ch(']') || text[2] != Ch('>'))\r\n                {\r\n                    if (!text[0])\r\n                        RAPIDXML_PARSE_ERROR(\"unexpected end of data\", text);\r\n                    ++text;\r\n                }\r\n                text += 3;      // Skip ]]>\r\n                return 0;       // Do not produce CDATA node\r\n            }\r\n\r\n            // Skip until end of cdata\r\n            Ch *value = text;\r\n            while (text[0] != Ch(']') || text[1] != Ch(']') || text[2] != Ch('>'))\r\n            {\r\n                if (!text[0])\r\n                    RAPIDXML_PARSE_ERROR(\"unexpected end of data\", text);\r\n                ++text;\r\n            }\r\n\r\n            // Create new cdata node\r\n            xml_node<Ch> *cdata = this->allocate_node(node_cdata);\r\n            cdata->value(value, text - value);\r\n\r\n            // Place zero terminator after value\r\n            if (!(Flags & parse_no_string_terminators))\r\n                *text = Ch('\\0');\r\n\r\n            text += 3;      // Skip ]]>\r\n            return cdata;\r\n        }\r\n        \r\n        // Parse element node\r\n        template<int Flags>\r\n        xml_node<Ch> *parse_element(Ch *&text)\r\n        {\r\n            // Create element node\r\n            xml_node<Ch> *element = this->allocate_node(node_element);\r\n\r\n            // Extract element name\r\n            Ch *name = text;\r\n            skip<node_name_pred, Flags>(text);\r\n            if (text == name)\r\n                RAPIDXML_PARSE_ERROR(\"expected element name\", text);\r\n            element->name(name, text - name);\r\n            \r\n            // Skip whitespace between element name and attributes or >\r\n            skip<whitespace_pred, Flags>(text);\r\n\r\n            // Parse attributes, if any\r\n            parse_node_attributes<Flags>(text, element);\r\n\r\n            // Determine ending type\r\n            if (*text == Ch('>'))\r\n            {\r\n                ++text;\r\n                parse_node_contents<Flags>(text, element);\r\n            }\r\n            else if (*text == Ch('/'))\r\n            {\r\n                ++text;\r\n                if (*text != Ch('>'))\r\n                    RAPIDXML_PARSE_ERROR(\"expected >\", text);\r\n                ++text;\r\n            }\r\n            else\r\n                RAPIDXML_PARSE_ERROR(\"expected >\", text);\r\n\r\n            // Place zero terminator after name\r\n            if (!(Flags & parse_no_string_terminators))\r\n                element->name()[element->name_size()] = Ch('\\0');\r\n\r\n            // Return parsed element\r\n            return element;\r\n        }\r\n\r\n        // Determine node type, and parse it\r\n        template<int Flags>\r\n        xml_node<Ch> *parse_node(Ch *&text)\r\n        {\r\n            // Parse proper node type\r\n            switch (text[0])\r\n            {\r\n\r\n            // <...\r\n            default: \r\n                // Parse and append element node\r\n                return parse_element<Flags>(text);\r\n\r\n            // <?...\r\n            case Ch('?'): \r\n                ++text;     // Skip ?\r\n                if ((text[0] == Ch('x') || text[0] == Ch('X')) &&\r\n                    (text[1] == Ch('m') || text[1] == Ch('M')) && \r\n                    (text[2] == Ch('l') || text[2] == Ch('L')) &&\r\n                    whitespace_pred::test(text[3]))\r\n                {\r\n                    // '<?xml ' - xml declaration\r\n                    text += 4;      // Skip 'xml '\r\n                    return parse_xml_declaration<Flags>(text);\r\n                }\r\n                else\r\n                {\r\n                    // Parse PI\r\n                    return parse_pi<Flags>(text);\r\n                }\r\n            \r\n            // <!...\r\n            case Ch('!'): \r\n\r\n                // Parse proper subset of <! node\r\n                switch (text[1])    \r\n                {\r\n                \r\n                // <!-\r\n                case Ch('-'):\r\n                    if (text[2] == Ch('-'))\r\n                    {\r\n                        // '<!--' - xml comment\r\n                        text += 3;     // Skip '!--'\r\n                        return parse_comment<Flags>(text);\r\n                    }\r\n                    break;\r\n\r\n                // <![\r\n                case Ch('['):\r\n                    if (text[2] == Ch('C') && text[3] == Ch('D') && text[4] == Ch('A') && \r\n                        text[5] == Ch('T') && text[6] == Ch('A') && text[7] == Ch('['))\r\n                    {\r\n                        // '<![CDATA[' - cdata\r\n                        text += 8;     // Skip '![CDATA['\r\n                        return parse_cdata<Flags>(text);\r\n                    }\r\n                    break;\r\n\r\n                // <!D\r\n                case Ch('D'):\r\n                    if (text[2] == Ch('O') && text[3] == Ch('C') && text[4] == Ch('T') && \r\n                        text[5] == Ch('Y') && text[6] == Ch('P') && text[7] == Ch('E') && \r\n                        whitespace_pred::test(text[8]))\r\n                    {\r\n                        // '<!DOCTYPE ' - doctype\r\n                        text += 9;      // skip '!DOCTYPE '\r\n                        return parse_doctype<Flags>(text);\r\n                    }\r\n\r\n                }   // switch\r\n\r\n                // Attempt to skip other, unrecognized node types starting with <!\r\n                ++text;     // Skip !\r\n                while (*text != Ch('>'))\r\n                {\r\n                    if (*text == 0)\r\n                        RAPIDXML_PARSE_ERROR(\"unexpected end of data\", text);\r\n                    ++text;\r\n                }\r\n                ++text;     // Skip '>'\r\n                return 0;   // No node recognized\r\n\r\n            }\r\n        }\r\n\r\n        // Parse contents of the node - children, data etc.\r\n        template<int Flags>\r\n        void parse_node_contents(Ch *&text, xml_node<Ch> *node)\r\n        {\r\n            // For all children and text\r\n            while (1)\r\n            {\r\n                // Skip whitespace between > and node contents\r\n                Ch *contents_start = text;      // Store start of node contents before whitespace is skipped\r\n                skip<whitespace_pred, Flags>(text);\r\n                Ch next_char = *text;\r\n\r\n            // After data nodes, instead of continuing the loop, control jumps here.\r\n            // This is because zero termination inside parse_and_append_data() function\r\n            // would wreak havoc with the above code.\r\n            // Also, skipping whitespace after data nodes is unnecessary.\r\n            after_data_node:    \r\n                \r\n                // Determine what comes next: node closing, child node, data node, or 0?\r\n                switch (next_char)\r\n                {\r\n                \r\n                // Node closing or child node\r\n                case Ch('<'):\r\n                    if (text[1] == Ch('/'))\r\n                    {\r\n                        // Node closing\r\n                        text += 2;      // Skip '</'\r\n                        if (Flags & parse_validate_closing_tags)\r\n                        {\r\n                            // Skip and validate closing tag name\r\n                            Ch *closing_name = text;\r\n                            skip<node_name_pred, Flags>(text);\r\n                            if (!internal::compare(node->name(), node->name_size(), closing_name, text - closing_name, true))\r\n                                RAPIDXML_PARSE_ERROR(\"invalid closing tag name\", text);\r\n                        }\r\n                        else\r\n                        {\r\n                            // No validation, just skip name\r\n                            skip<node_name_pred, Flags>(text);\r\n                        }\r\n                        // Skip remaining whitespace after node name\r\n                        skip<whitespace_pred, Flags>(text);\r\n                        if (*text != Ch('>'))\r\n                            RAPIDXML_PARSE_ERROR(\"expected >\", text);\r\n                        ++text;     // Skip '>'\r\n                        return;     // Node closed, finished parsing contents\r\n                    }\r\n                    else\r\n                    {\r\n                        // Child node\r\n                        ++text;     // Skip '<'\r\n                        if (xml_node<Ch> *child = parse_node<Flags>(text))\r\n                            node->append_node(child);\r\n                    }\r\n                    break;\r\n\r\n                // End of data - error\r\n                case Ch('\\0'):\r\n                    RAPIDXML_PARSE_ERROR(\"unexpected end of data\", text);\r\n\r\n                // Data node\r\n                default:\r\n                    next_char = parse_and_append_data<Flags>(node, text, contents_start);\r\n                    goto after_data_node;   // Bypass regular processing after data nodes\r\n\r\n                }\r\n            }\r\n        }\r\n        \r\n        // Parse XML attributes of the node\r\n        template<int Flags>\r\n        void parse_node_attributes(Ch *&text, xml_node<Ch> *node)\r\n        {\r\n            // For all attributes \r\n            while (attribute_name_pred::test(*text))\r\n            {\r\n                // Extract attribute name\r\n                Ch *name = text;\r\n                ++text;     // Skip first character of attribute name\r\n                skip<attribute_name_pred, Flags>(text);\r\n                if (text == name)\r\n                    RAPIDXML_PARSE_ERROR(\"expected attribute name\", name);\r\n\r\n                // Create new attribute\r\n                xml_attribute<Ch> *attribute = this->allocate_attribute();\r\n                attribute->name(name, text - name);\r\n                node->append_attribute(attribute);\r\n\r\n                // Skip whitespace after attribute name\r\n                skip<whitespace_pred, Flags>(text);\r\n\r\n                // Skip =\r\n                if (*text != Ch('='))\r\n                    RAPIDXML_PARSE_ERROR(\"expected =\", text);\r\n                ++text;\r\n\r\n                // Add terminating zero after name\r\n                if (!(Flags & parse_no_string_terminators))\r\n                    attribute->name()[attribute->name_size()] = 0;\r\n\r\n                // Skip whitespace after =\r\n                skip<whitespace_pred, Flags>(text);\r\n\r\n                // Skip quote and remember if it was ' or \"\r\n                Ch quote = *text;\r\n                if (quote != Ch('\\'') && quote != Ch('\"'))\r\n                    RAPIDXML_PARSE_ERROR(\"expected ' or \\\"\", text);\r\n                ++text;\r\n\r\n                // Extract attribute value and expand char refs in it\r\n                Ch *value = text, *end;\r\n                const int AttFlags = Flags & ~parse_normalize_whitespace;   // No whitespace normalization in attributes\r\n                if (quote == Ch('\\''))\r\n                    end = skip_and_expand_character_refs<attribute_value_pred<Ch('\\'')>, attribute_value_pure_pred<Ch('\\'')>, AttFlags>(text);\r\n                else\r\n                    end = skip_and_expand_character_refs<attribute_value_pred<Ch('\"')>, attribute_value_pure_pred<Ch('\"')>, AttFlags>(text);\r\n                \r\n                // Set attribute value\r\n                attribute->value(value, end - value);\r\n                \r\n                // Make sure that end quote is present\r\n                if (*text != quote)\r\n                    RAPIDXML_PARSE_ERROR(\"expected ' or \\\"\", text);\r\n                ++text;     // Skip quote\r\n\r\n                // Add terminating zero after value\r\n                if (!(Flags & parse_no_string_terminators))\r\n                    attribute->value()[attribute->value_size()] = 0;\r\n\r\n                // Skip whitespace after attribute value\r\n                skip<whitespace_pred, Flags>(text);\r\n            }\r\n        }\r\n\r\n    };\r\n\r\n    //! \\cond internal\r\n    namespace internal\r\n    {\r\n\r\n        // Whitespace (space \\n \\r \\t)\r\n        template<int Dummy>\r\n        const unsigned char lookup_tables<Dummy>::lookup_whitespace[256] = \r\n        {\r\n          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\r\n             0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  0,  0,  1,  0,  0,  // 0\r\n             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 1\r\n             1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 2\r\n             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 3\r\n             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 4\r\n             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 5\r\n             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 6\r\n             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 7\r\n             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 8\r\n             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 9\r\n             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // A\r\n             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // B\r\n             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // C\r\n             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // D\r\n             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // E\r\n             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0   // F\r\n        };\r\n\r\n        // Node name (anything but space \\n \\r \\t / > ? \\0)\r\n        template<int Dummy>\r\n        const unsigned char lookup_tables<Dummy>::lookup_node_name[256] = \r\n        {\r\n          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\r\n             0,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  1,  1,  0,  1,  1,  // 0\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1\r\n             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  // 2\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  // 3\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F\r\n        };\r\n\r\n        // Text (i.e. PCDATA) (anything but < \\0)\r\n        template<int Dummy>\r\n        const unsigned char lookup_tables<Dummy>::lookup_text[256] = \r\n        {\r\n          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\r\n             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 2\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  1,  1,  1,  // 3\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F\r\n        };\r\n\r\n        // Text (i.e. PCDATA) that does not require processing when ws normalization is disabled \r\n        // (anything but < \\0 &)\r\n        template<int Dummy>\r\n        const unsigned char lookup_tables<Dummy>::lookup_text_pure_no_ws[256] = \r\n        {\r\n          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\r\n             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1\r\n             1,  1,  1,  1,  1,  1,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 2\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  1,  1,  1,  // 3\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F\r\n        };\r\n\r\n        // Text (i.e. PCDATA) that does not require processing when ws normalizationis is enabled\r\n        // (anything but < \\0 & space \\n \\r \\t)\r\n        template<int Dummy>\r\n        const unsigned char lookup_tables<Dummy>::lookup_text_pure_with_ws[256] = \r\n        {\r\n          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\r\n             0,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  1,  1,  0,  1,  1,  // 0\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1\r\n             0,  1,  1,  1,  1,  1,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 2\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  1,  1,  1,  // 3\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F\r\n        };\r\n\r\n        // Attribute name (anything but space \\n \\r \\t / < > = ? ! \\0)\r\n        template<int Dummy>\r\n        const unsigned char lookup_tables<Dummy>::lookup_attribute_name[256] = \r\n        {\r\n          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\r\n             0,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  1,  1,  0,  1,  1,  // 0\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1\r\n             0,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  // 2\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  // 3\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F\r\n        };\r\n\r\n        // Attribute data with single quote (anything but ' \\0)\r\n        template<int Dummy>\r\n        const unsigned char lookup_tables<Dummy>::lookup_attribute_data_1[256] = \r\n        {\r\n          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\r\n             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1\r\n             1,  1,  1,  1,  1,  1,  1,  0,  1,  1,  1,  1,  1,  1,  1,  1,  // 2\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 3\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F\r\n        };\r\n\r\n        // Attribute data with single quote that does not require processing (anything but ' \\0 &)\r\n        template<int Dummy>\r\n        const unsigned char lookup_tables<Dummy>::lookup_attribute_data_1_pure[256] = \r\n        {\r\n          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\r\n             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1\r\n             1,  1,  1,  1,  1,  1,  0,  0,  1,  1,  1,  1,  1,  1,  1,  1,  // 2\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 3\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F\r\n        };\r\n\r\n        // Attribute data with double quote (anything but \" \\0)\r\n        template<int Dummy>\r\n        const unsigned char lookup_tables<Dummy>::lookup_attribute_data_2[256] = \r\n        {\r\n          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\r\n             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1\r\n             1,  1,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 2\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 3\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F\r\n        };\r\n\r\n        // Attribute data with double quote that does not require processing (anything but \" \\0 &)\r\n        template<int Dummy>\r\n        const unsigned char lookup_tables<Dummy>::lookup_attribute_data_2_pure[256] = \r\n        {\r\n          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\r\n             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1\r\n             1,  1,  0,  1,  1,  1,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 2\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 3\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E\r\n             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F\r\n        };\r\n\r\n        // Digits (dec and hex, 255 denotes end of numeric character reference)\r\n        template<int Dummy>\r\n        const unsigned char lookup_tables<Dummy>::lookup_digits[256] = \r\n        {\r\n          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\r\n           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 0\r\n           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 1\r\n           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 2\r\n             0,  1,  2,  3,  4,  5,  6,  7,  8,  9,255,255,255,255,255,255,  // 3\r\n           255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255,  // 4\r\n           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 5\r\n           255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255,  // 6\r\n           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 7\r\n           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 8\r\n           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 9\r\n           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // A\r\n           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // B\r\n           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // C\r\n           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // D\r\n           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // E\r\n           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255   // F\r\n        };\r\n    \r\n        // Upper case conversion\r\n        template<int Dummy>\r\n        const unsigned char lookup_tables<Dummy>::lookup_upcase[256] = \r\n        {\r\n          // 0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  A   B   C   D   E   F\r\n           0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,   // 0\r\n           16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,   // 1\r\n           32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,   // 2\r\n           48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,   // 3\r\n           64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,   // 4\r\n           80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,   // 5\r\n           96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,   // 6\r\n           80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 123,124,125,126,127,  // 7\r\n           128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,  // 8\r\n           144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,  // 9\r\n           160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,  // A\r\n           176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,  // B\r\n           192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,  // C\r\n           208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,  // D\r\n           224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,  // E\r\n           240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255   // F\r\n        };\r\n    }\r\n    //! \\endcond\r\n\r\n}\r\n\r\n// Undefine internal macros\r\n#undef RAPIDXML_PARSE_ERROR\r\n\r\n// On MSVC, restore warnings state\r\n#ifdef _MSC_VER\r\n    #pragma warning(pop)\r\n#endif\r\n\r\n#endif\r\n"
  },
  {
    "path": "mgm/http/rapidxml/rapidxml_print.hpp",
    "content": "#ifndef RAPIDXML_PRINT_HPP_INCLUDED\r\n#define RAPIDXML_PRINT_HPP_INCLUDED\r\n\r\n// Copyright (C) 2006, 2009 Marcin Kalicinski\r\n// Version 1.13\r\n// Revision $DateTime: 2009/05/13 01:46:17 $\r\n//! \\file rapidxml_print.hpp This file contains rapidxml printer implementation\r\n\r\n#include \"rapidxml.hpp\"\r\n\r\n// Only include streams if not disabled\r\n#ifndef RAPIDXML_NO_STREAMS\r\n    #include <ostream>\r\n    #include <iterator>\r\n#endif\r\n\r\nnamespace rapidxml\r\n{\r\n\r\n    ///////////////////////////////////////////////////////////////////////\r\n    // Printing flags\r\n\r\n    const int print_no_indenting = 0x1;   //!< Printer flag instructing the printer to suppress indenting of XML. See print() function.\r\n\r\n    ///////////////////////////////////////////////////////////////////////\r\n    // Internal\r\n\r\n    //! \\cond internal\r\n    namespace internal\r\n    {\r\n     // Function decaration\r\n      template<class OutIt, class Ch>\r\n      inline OutIt copy_chars(const Ch *begin, const Ch *end, OutIt out);\r\n\r\n      template<class OutIt, class Ch>\r\n      inline OutIt copy_and_expand_chars(const Ch *begin, const Ch *end, Ch noexpand, OutIt out);\r\n\r\n      template<class OutIt, class Ch>\r\n      inline OutIt fill_chars(OutIt out, int n, Ch ch);\r\n\r\n      template<class Ch, Ch ch>\r\n      inline bool find_char(const Ch *begin, const Ch *end);\r\n\r\n      template<class OutIt, class Ch>\r\n      inline OutIt print_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);\r\n\r\n      template<class OutIt, class Ch>\r\n      inline OutIt print_children(OutIt out, const xml_node<Ch> *node, int flags, int indent);\r\n\r\n      template<class OutIt, class Ch>\r\n      inline OutIt print_attributes(OutIt out, const xml_node<Ch> *node, int flags);\r\n\r\n      template<class OutIt, class Ch>\r\n      inline OutIt print_data_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);\r\n\r\n      template<class OutIt, class Ch>\r\n      inline OutIt print_cdata_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);\r\n\r\n      template<class OutIt, class Ch>\r\n      inline OutIt print_element_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);\r\n\r\n      template<class OutIt, class Ch>\r\n      inline OutIt print_declaration_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);\r\n\r\n      template<class OutIt, class Ch>\r\n      inline OutIt print_comment_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);\r\n\r\n      template<class OutIt, class Ch>\r\n      inline OutIt print_doctype_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);\r\n\r\n      template<class OutIt, class Ch>\r\n      inline OutIt print_pi_node(OutIt out, const xml_node<Ch> *node, int flags, int indent);\r\n\r\n\r\n        ///////////////////////////////////////////////////////////////////////////\r\n        // Internal character operations\r\n\r\n        // Copy characters from given range to given output iterator\r\n        template<class OutIt, class Ch>\r\n        inline OutIt copy_chars(const Ch *begin, const Ch *end, OutIt out)\r\n        {\r\n            while (begin != end)\r\n                *out++ = *begin++;\r\n            return out;\r\n        }\r\n\r\n        // Copy characters from given range to given output iterator and expand\r\n        // characters into references (&lt; &gt; &apos; &quot; &amp;)\r\n        template<class OutIt, class Ch>\r\n        inline OutIt copy_and_expand_chars(const Ch *begin, const Ch *end, Ch noexpand, OutIt out)\r\n        {\r\n            while (begin != end)\r\n            {\r\n                if (*begin == noexpand)\r\n                {\r\n                    *out++ = *begin;    // No expansion, copy character\r\n                }\r\n                else\r\n                {\r\n                    switch (*begin)\r\n                    {\r\n                    case Ch('<'):\r\n                        *out++ = Ch('&'); *out++ = Ch('l'); *out++ = Ch('t'); *out++ = Ch(';');\r\n                        break;\r\n                    case Ch('>'):\r\n                        *out++ = Ch('&'); *out++ = Ch('g'); *out++ = Ch('t'); *out++ = Ch(';');\r\n                        break;\r\n                    case Ch('\\''):\r\n                        *out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('p'); *out++ = Ch('o'); *out++ = Ch('s'); *out++ = Ch(';');\r\n                        break;\r\n                    case Ch('\"'):\r\n                        *out++ = Ch('&'); *out++ = Ch('q'); *out++ = Ch('u'); *out++ = Ch('o'); *out++ = Ch('t'); *out++ = Ch(';');\r\n                        break;\r\n                    case Ch('&'):\r\n                        *out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('m'); *out++ = Ch('p'); *out++ = Ch(';');\r\n                        break;\r\n                    default:\r\n                        *out++ = *begin;    // No expansion, copy character\r\n                    }\r\n                }\r\n                ++begin;    // Step to next character\r\n            }\r\n            return out;\r\n        }\r\n\r\n        // Fill given output iterator with repetitions of the same character\r\n        template<class OutIt, class Ch>\r\n        inline OutIt fill_chars(OutIt out, int n, Ch ch)\r\n        {\r\n            for (int i = 0; i < n; ++i)\r\n                *out++ = ch;\r\n            return out;\r\n        }\r\n\r\n        // Find character\r\n        template<class Ch, Ch ch>\r\n        inline bool find_char(const Ch *begin, const Ch *end)\r\n        {\r\n            while (begin != end)\r\n                if (*begin++ == ch)\r\n                    return true;\r\n            return false;\r\n        }\r\n\r\n        ///////////////////////////////////////////////////////////////////////////\r\n        // Internal printing operations\r\n\r\n        // Print node\r\n        template<class OutIt, class Ch>\r\n        inline OutIt print_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)\r\n        {\r\n            // Print proper node type\r\n            switch (node->type())\r\n            {\r\n\r\n            // Document\r\n            case node_document:\r\n                out = print_children(out, node, flags, indent);\r\n                break;\r\n\r\n            // Element\r\n            case node_element:\r\n                out = print_element_node(out, node, flags, indent);\r\n                break;\r\n\r\n            // Data\r\n            case node_data:\r\n                out = print_data_node(out, node, flags, indent);\r\n                break;\r\n\r\n            // CDATA\r\n            case node_cdata:\r\n                out = print_cdata_node(out, node, flags, indent);\r\n                break;\r\n\r\n            // Declaration\r\n            case node_declaration:\r\n                out = print_declaration_node(out, node, flags, indent);\r\n                break;\r\n\r\n            // Comment\r\n            case node_comment:\r\n                out = print_comment_node(out, node, flags, indent);\r\n                break;\r\n\r\n            // Doctype\r\n            case node_doctype:\r\n                out = print_doctype_node(out, node, flags, indent);\r\n                break;\r\n\r\n            // Pi\r\n            case node_pi:\r\n                out = print_pi_node(out, node, flags, indent);\r\n                break;\r\n\r\n                // Unknown\r\n            default:\r\n                assert(0);\r\n                break;\r\n            }\r\n\r\n            // If indenting not disabled, add line break after node\r\n            if (!(flags & print_no_indenting))\r\n                *out = Ch('\\n'), ++out;\r\n\r\n            // Return modified iterator\r\n            return out;\r\n        }\r\n\r\n        // Print children of the node\r\n        template<class OutIt, class Ch>\r\n        inline OutIt print_children(OutIt out, const xml_node<Ch> *node, int flags, int indent)\r\n        {\r\n            for (xml_node<Ch> *child = node->first_node(); child; child = child->next_sibling())\r\n                out = print_node(out, child, flags, indent);\r\n            return out;\r\n        }\r\n\r\n        // Print attributes of the node\r\n        template<class OutIt, class Ch>\r\n        inline OutIt print_attributes(OutIt out, const xml_node<Ch> *node, int flags)\r\n        {\r\n            for (xml_attribute<Ch> *attribute = node->first_attribute(); attribute; attribute = attribute->next_attribute())\r\n            {\r\n                if (attribute->name() && attribute->value())\r\n                {\r\n                    // Print attribute name\r\n                    *out = Ch(' '), ++out;\r\n                    out = copy_chars(attribute->name(), attribute->name() + attribute->name_size(), out);\r\n                    *out = Ch('='), ++out;\r\n                    // Print attribute value using appropriate quote type\r\n                    if (find_char<Ch, Ch('\"')>(attribute->value(), attribute->value() + attribute->value_size()))\r\n                    {\r\n                        *out = Ch('\\''), ++out;\r\n                        out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('\"'), out);\r\n                        *out = Ch('\\''), ++out;\r\n                    }\r\n                    else\r\n                    {\r\n                        *out = Ch('\"'), ++out;\r\n                        out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('\\''), out);\r\n                        *out = Ch('\"'), ++out;\r\n                    }\r\n                }\r\n            }\r\n            return out;\r\n        }\r\n\r\n        // Print data node\r\n        template<class OutIt, class Ch>\r\n        inline OutIt print_data_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)\r\n        {\r\n            assert(node->type() == node_data);\r\n            if (!(flags & print_no_indenting))\r\n                out = fill_chars(out, indent, Ch('\\t'));\r\n            out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out);\r\n            return out;\r\n        }\r\n\r\n        // Print data node\r\n        template<class OutIt, class Ch>\r\n        inline OutIt print_cdata_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)\r\n        {\r\n            assert(node->type() == node_cdata);\r\n            if (!(flags & print_no_indenting))\r\n                out = fill_chars(out, indent, Ch('\\t'));\r\n            *out = Ch('<'); ++out;\r\n            *out = Ch('!'); ++out;\r\n            *out = Ch('['); ++out;\r\n            *out = Ch('C'); ++out;\r\n            *out = Ch('D'); ++out;\r\n            *out = Ch('A'); ++out;\r\n            *out = Ch('T'); ++out;\r\n            *out = Ch('A'); ++out;\r\n            *out = Ch('['); ++out;\r\n            out = copy_chars(node->value(), node->value() + node->value_size(), out);\r\n            *out = Ch(']'); ++out;\r\n            *out = Ch(']'); ++out;\r\n            *out = Ch('>'); ++out;\r\n            return out;\r\n        }\r\n\r\n        // Print element node\r\n        template<class OutIt, class Ch>\r\n        inline OutIt print_element_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)\r\n        {\r\n            assert(node->type() == node_element);\r\n\r\n            // Print element name and attributes, if any\r\n            if (!(flags & print_no_indenting))\r\n                out = fill_chars(out, indent, Ch('\\t'));\r\n            *out = Ch('<'), ++out;\r\n            out = copy_chars(node->name(), node->name() + node->name_size(), out);\r\n            out = print_attributes(out, node, flags);\r\n\r\n            // If node is childless\r\n            if (node->value_size() == 0 && !node->first_node())\r\n            {\r\n                // Print childless node tag ending\r\n                *out = Ch('/'), ++out;\r\n                *out = Ch('>'), ++out;\r\n            }\r\n            else\r\n            {\r\n                // Print normal node tag ending\r\n                *out = Ch('>'), ++out;\r\n\r\n                // Test if node contains a single data node only (and no other nodes)\r\n                xml_node<Ch> *child = node->first_node();\r\n                if (!child)\r\n                {\r\n                    // If node has no children, only print its value without indenting\r\n                    out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out);\r\n                }\r\n                else if (child->next_sibling() == 0 && child->type() == node_data)\r\n                {\r\n                    // If node has a sole data child, only print its value without indenting\r\n                    out = copy_and_expand_chars(child->value(), child->value() + child->value_size(), Ch(0), out);\r\n                }\r\n                else\r\n                {\r\n                    // Print all children with full indenting\r\n                    if (!(flags & print_no_indenting))\r\n                        *out = Ch('\\n'), ++out;\r\n                    out = print_children(out, node, flags, indent + 1);\r\n                    if (!(flags & print_no_indenting))\r\n                        out = fill_chars(out, indent, Ch('\\t'));\r\n                }\r\n\r\n                // Print node end\r\n                *out = Ch('<'), ++out;\r\n                *out = Ch('/'), ++out;\r\n                out = copy_chars(node->name(), node->name() + node->name_size(), out);\r\n                *out = Ch('>'), ++out;\r\n            }\r\n            return out;\r\n        }\r\n\r\n        // Print declaration node\r\n        template<class OutIt, class Ch>\r\n        inline OutIt print_declaration_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)\r\n        {\r\n            // Print declaration start\r\n            if (!(flags & print_no_indenting))\r\n                out = fill_chars(out, indent, Ch('\\t'));\r\n            *out = Ch('<'), ++out;\r\n            *out = Ch('?'), ++out;\r\n            *out = Ch('x'), ++out;\r\n            *out = Ch('m'), ++out;\r\n            *out = Ch('l'), ++out;\r\n\r\n            // Print attributes\r\n            out = print_attributes(out, node, flags);\r\n\r\n            // Print declaration end\r\n            *out = Ch('?'), ++out;\r\n            *out = Ch('>'), ++out;\r\n\r\n            return out;\r\n        }\r\n\r\n        // Print comment node\r\n        template<class OutIt, class Ch>\r\n        inline OutIt print_comment_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)\r\n        {\r\n            assert(node->type() == node_comment);\r\n            if (!(flags & print_no_indenting))\r\n                out = fill_chars(out, indent, Ch('\\t'));\r\n            *out = Ch('<'), ++out;\r\n            *out = Ch('!'), ++out;\r\n            *out = Ch('-'), ++out;\r\n            *out = Ch('-'), ++out;\r\n            out = copy_chars(node->value(), node->value() + node->value_size(), out);\r\n            *out = Ch('-'), ++out;\r\n            *out = Ch('-'), ++out;\r\n            *out = Ch('>'), ++out;\r\n            return out;\r\n        }\r\n\r\n        // Print doctype node\r\n        template<class OutIt, class Ch>\r\n        inline OutIt print_doctype_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)\r\n        {\r\n            assert(node->type() == node_doctype);\r\n            if (!(flags & print_no_indenting))\r\n                out = fill_chars(out, indent, Ch('\\t'));\r\n            *out = Ch('<'), ++out;\r\n            *out = Ch('!'), ++out;\r\n            *out = Ch('D'), ++out;\r\n            *out = Ch('O'), ++out;\r\n            *out = Ch('C'), ++out;\r\n            *out = Ch('T'), ++out;\r\n            *out = Ch('Y'), ++out;\r\n            *out = Ch('P'), ++out;\r\n            *out = Ch('E'), ++out;\r\n            *out = Ch(' '), ++out;\r\n            out = copy_chars(node->value(), node->value() + node->value_size(), out);\r\n            *out = Ch('>'), ++out;\r\n            return out;\r\n        }\r\n\r\n        // Print pi node\r\n        template<class OutIt, class Ch>\r\n        inline OutIt print_pi_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)\r\n        {\r\n            assert(node->type() == node_pi);\r\n            if (!(flags & print_no_indenting))\r\n                out = fill_chars(out, indent, Ch('\\t'));\r\n            *out = Ch('<'), ++out;\r\n            *out = Ch('?'), ++out;\r\n            out = copy_chars(node->name(), node->name() + node->name_size(), out);\r\n            *out = Ch(' '), ++out;\r\n            out = copy_chars(node->value(), node->value() + node->value_size(), out);\r\n            *out = Ch('?'), ++out;\r\n            *out = Ch('>'), ++out;\r\n            return out;\r\n        }\r\n\r\n    }\r\n    //! \\endcond\r\n\r\n    ///////////////////////////////////////////////////////////////////////////\r\n    // Printing\r\n\r\n    //! Prints XML to given output iterator.\r\n    //! \\param out Output iterator to print to.\r\n    //! \\param node Node to be printed. Pass xml_document to print entire document.\r\n    //! \\param flags Flags controlling how XML is printed.\r\n    //! \\return Output iterator pointing to position immediately after last character of printed text.\r\n    template<class OutIt, class Ch>\r\n    inline OutIt print(OutIt out, const xml_node<Ch> &node, int flags = 0)\r\n    {\r\n        return internal::print_node(out, &node, flags, 0);\r\n    }\r\n\r\n#ifndef RAPIDXML_NO_STREAMS\r\n\r\n    //! Prints XML to given output stream.\r\n    //! \\param out Output stream to print to.\r\n    //! \\param node Node to be printed. Pass xml_document to print entire document.\r\n    //! \\param flags Flags controlling how XML is printed.\r\n    //! \\return Output stream.\r\n    template<class Ch>\r\n    inline std::basic_ostream<Ch> &print(std::basic_ostream<Ch> &out, const xml_node<Ch> &node, int flags = 0)\r\n    {\r\n        print(std::ostream_iterator<Ch>(out), node, flags);\r\n        return out;\r\n    }\r\n\r\n    //! Prints formatted XML to given output stream. Uses default printing flags. Use print() function to customize printing process.\r\n    //! \\param out Output stream to print to.\r\n    //! \\param node Node to be printed.\r\n    //! \\return Output stream.\r\n    template<class Ch>\r\n    inline std::basic_ostream<Ch> &operator <<(std::basic_ostream<Ch> &out, const xml_node<Ch> &node)\r\n    {\r\n        return print(out, node);\r\n    }\r\n\r\n#endif\r\n\r\n}\r\n\r\n#endif\r\n"
  },
  {
    "path": "mgm/http/rapidxml/rapidxml_utils.hpp",
    "content": "#ifndef RAPIDXML_UTILS_HPP_INCLUDED\r\n#define RAPIDXML_UTILS_HPP_INCLUDED\r\n\r\n// Copyright (C) 2006, 2009 Marcin Kalicinski\r\n// Version 1.13\r\n// Revision $DateTime: 2009/05/13 01:46:17 $\r\n//! \\file rapidxml_utils.hpp This file contains high-level rapidxml utilities that can be useful\r\n//! in certain simple scenarios. They should probably not be used if maximizing performance is the main objective.\r\n\r\n#include \"rapidxml.hpp\"\r\n#include <vector>\r\n#include <string>\r\n#include <fstream>\r\n#include <stdexcept>\r\n\r\nnamespace rapidxml\r\n{\r\n\r\n    //! Represents data loaded from a file\r\n    template<class Ch = char>\r\n    class file\r\n    {\r\n        \r\n    public:\r\n        \r\n        //! Loads file into the memory. Data will be automatically destroyed by the destructor.\r\n        //! \\param filename Filename to load.\r\n        file(const char *filename)\r\n        {\r\n            using namespace std;\r\n\r\n            // Open stream\r\n            basic_ifstream<Ch> stream(filename, ios::binary);\r\n            if (!stream)\r\n                throw runtime_error(string(\"cannot open file \") + filename);\r\n            stream.unsetf(ios::skipws);\r\n            \r\n            // Determine stream size\r\n            stream.seekg(0, ios::end);\r\n            size_t size = stream.tellg();\r\n            stream.seekg(0);   \r\n            \r\n            // Load data and add terminating 0\r\n            m_data.resize(size + 1);\r\n            stream.read(&m_data.front(), static_cast<streamsize>(size));\r\n            m_data[size] = 0;\r\n        }\r\n\r\n        //! Loads file into the memory. Data will be automatically destroyed by the destructor\r\n        //! \\param stream Stream to load from\r\n        file(std::basic_istream<Ch> &stream)\r\n        {\r\n            using namespace std;\r\n\r\n            // Load data and add terminating 0\r\n            stream.unsetf(ios::skipws);\r\n            m_data.assign(istreambuf_iterator<Ch>(stream), istreambuf_iterator<Ch>());\r\n            if (stream.fail() || stream.bad())\r\n                throw runtime_error(\"error reading stream\");\r\n            m_data.push_back(0);\r\n        }\r\n        \r\n        //! Gets file data.\r\n        //! \\return Pointer to data of file.\r\n        Ch *data()\r\n        {\r\n            return &m_data.front();\r\n        }\r\n\r\n        //! Gets file data.\r\n        //! \\return Pointer to data of file.\r\n        const Ch *data() const\r\n        {\r\n            return &m_data.front();\r\n        }\r\n\r\n        //! Gets file data size.\r\n        //! \\return Size of file data, in characters.\r\n        std::size_t size() const\r\n        {\r\n            return m_data.size();\r\n        }\r\n\r\n    private:\r\n\r\n        std::vector<Ch> m_data;   // File data\r\n\r\n    };\r\n\r\n    //! Counts children of node. Time complexity is O(n).\r\n    //! \\return Number of children of node\r\n    template<class Ch>\r\n    inline std::size_t count_children(xml_node<Ch> *node)\r\n    {\r\n        xml_node<Ch> *child = node->first_node();\r\n        std::size_t count = 0;\r\n        while (child)\r\n        {\r\n            ++count;\r\n            child = child->next_sibling();\r\n        }\r\n        return count;\r\n    }\r\n\r\n    //! Counts attributes of node. Time complexity is O(n).\r\n    //! \\return Number of attributes of node\r\n    template<class Ch>\r\n    inline std::size_t count_attributes(xml_node<Ch> *node)\r\n    {\r\n        xml_attribute<Ch> *attr = node->first_attribute();\r\n        std::size_t count = 0;\r\n        while (attr)\r\n        {\r\n            ++count;\r\n            attr = attr->next_attribute();\r\n        }\r\n        return count;\r\n    }\r\n\r\n}\r\n\r\n#endif\r\n"
  },
  {
    "path": "mgm/http/rest-api/Constants.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Constants.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_RESTAPI_CONSTANTS_HH\n#define EOS_RESTAPI_CONSTANTS_HH\n\n#include \"mgm/Namespace.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n//The name of the tape REST API switch on/off\nstatic constexpr auto TAPE_REST_API_SWITCH_ON_OFF = \"taperestapi.status\";\n//The name of the tape REST API STAGE resource switch on/off\nstatic constexpr auto TAPE_REST_API_STAGE_SWITCH_ON_OFF = \"taperestapi.stage\";\n\n// URL parameter tokens\nstatic const inline std::string URLPARAM_ID = \"{id}\";\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_RESTAPI_CONSTANTS_HH\n"
  },
  {
    "path": "mgm/http/rest-api/README.md",
    "content": "# EOS MGM REST API - Implementation Overview\n\nThis document describes how the EOS MGM REST API is implemented in this codebase and how to extend it.\n\n## High-level architecture\n\n- Router-centric dispatch\n  - `Router` performs URL pattern + HTTP method matching and invokes a bound handler callable.\n  - URL patterns support path placeholders (e.g. `/api/v1/stage/{id}`) via the shared `URLParser`.\n\n- Handlers\n  - `RestHandler` is the minimal base: validates the entrypoint (e.g. `/api/`), exposes `isRestRequest`, and holds the entry URL.\n  - `TapeRestHandler` registers routes for the Tape API (stage, archiveinfo, release) and delegates to Action objects.\n  - `WellKnownHandler` serves the discovery endpoint (`/.well-known/wlcg-tape-rest-api`) and is implemented via an inlined route.\n\n- Actions (business-facing request handlers)\n  - Small classes deriving from `Action` or `TapeAction`, each implementing `run(HttpRequest*, VirtualIdentity*)`.\n  - Stage API actions: Create, Get, Cancel, Delete.\n  - ArchiveInfo and Release actions.\n  - Actions call the business layer (`TapeRestApiBusiness`) and translate errors to HTTP responses.\n\n- Business layer\n  - `TapeRestApiBusiness` encapsulates calls to MGM subsystems for stage/evict/query operations and throws typed exceptions when needed.\n\n- Responses\n  - `RestApiResponse<T>` and `RestApiResponseFactory` remain as the generic envelope.\n  - `RestResponseFactory` is the unified, high-level factory used across handlers/actions (e.g. Ok, Created, BadRequest, NotFound, Forbidden, MethodNotAllowed, NotImplemented, InternalError).\n\n- Exceptions\n  - `exception/Exceptions.hh` is an umbrella header exposing common REST exceptions: NotFound, MethodNotAllowed, Forbidden, NotImplemented, ObjectNotFound, ActionNotFound, ControllerNotFound, etc.\n  - `JsonValidationException` is header-only and carries validation details for 400 responses.\n  - Tape business exceptions are also included via the umbrella.\n\n- Models and JSON\n  - Models live under `model/...`; JSON builders and jsonifiers live under `json/...`.\n  - `JsonCppModelBuilder` provides JSON parsing with consistent error reporting; concrete model builders validate and construct typed models.\n  - Jsonifiers convert models to JSON for responses.\n\n- Configuration\n  - `TapeRestApiConfig` configures access URL, sitename, host alias, ports, activation flags, and optional version-to-endpoint mappings.\n  - `Constants.hh` holds small shared constants (e.g. `URLPARAM_ID`).\n\n## Request flow\n\n1. `RestApiManager` chooses the handler based on the request URL prefix (e.g. Tape API vs `/.well-known`).\n2. The selected `RestHandler` (e.g. `TapeRestHandler`) uses the `Router` to dispatch to a bound action or inline route.\n3. The action validates/reads input (model builder), calls the business layer, and returns a response via `RestResponseFactory`.\n4. Errors are mapped centrally by `HandleWithErrors` (see `response/ErrorHandling.hh`) or locally via `RestResponseFactory` helpers.\n\n## Adding a new endpoint\n\n1. Choose the API version and base path (e.g. `/api/v1/custom/`).\n2. In the relevant handler (most likely `TapeRestHandler::initialize*Routes`):\n   - Create an `Action` (or bind a small lambda) that implements the logic.\n   - Register a route with `mRouter.add(\"/api/v1/custom/...\", HttpHandler::Methods::<VERB>, handler)`. Use `URLPARAM_ID` placeholders as needed.\n3. If input JSON is required, create a model + builder under `model/...` and `json/.../model-builders`, then instantiate/validate in the action.\n4. Build responses using `RestResponseFactory` (e.g. `Ok(model)`, `Created(model, headers)`, `BadRequest(ex)`, `NotFound()`, `InternalError(msg)`).\n5. Add or update unit tests under `unit_tests/mgm/http/rest-api/tape` (or a new suite) to cover routing, validation, and responses.\n\n## Error handling guidelines\n\n- Use `JsonValidationException` for bad inputs (400).\n- Use `NotFoundException`/`ObjectNotFoundException` to return 404.\n- Use `MethodNotAllowedException` to return 405.\n- Use `ForbiddenException` for 403.\n- Use `NotImplementedException` for 501 when a version/feature is declared but inactive.\n- For unexpected failures, map to `InternalError` (500) with a concise message.\n\n## Notes\n\n- The legacy controller/factory layers were removed in favor of direct routing.\n- The `.well-known` endpoint is implemented inline within `WellKnownHandler`.\n- Many small exception headers were consolidated under `exception/Exceptions.hh`.\n- Response factories were unified under `response/RestResponseFactory{.hh,.cc}`.\n\nThis structure aims to keep the code easy to navigate, reduce file count, and make adding endpoints straightforward, while preserving clear separation between routing, request handling, business logic, and JSON serialization.\n"
  },
  {
    "path": "mgm/http/rest-api/action/Action.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Action.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_ACTION_HH\n#define EOS_ACTION_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"common/VirtualIdentity.hh\"\n#include \"common/http/HttpRequest.hh\"\n#include \"common/http/HttpResponse.hh\"\n#include \"common/http/HttpHandler.hh\"\n#include \"common/json/Jsonifier.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nclass Action\n{\npublic:\n  Action(const std::string& accessURLPattern,\n         const common::HttpHandler::Methods method):\n    mAccessURLPattern(accessURLPattern), mMethod(method) {};\n  virtual common::HttpResponse* run(common::HttpRequest* request,\n                                    const common::VirtualIdentity* vid) = 0;\n  inline const std::string& getAccessURLPattern() const\n  {\n    return mAccessURLPattern;\n  }\n  inline common::HttpHandler::Methods getMethod() const\n  {\n    return mMethod;\n  }\n  virtual ~Action() {}\nprotected:\n  std::string mAccessURLPattern;\n  eos::common::HttpHandler::Methods mMethod;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_ACTION_HH\n"
  },
  {
    "path": "mgm/http/rest-api/action/tape/TapeAction.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Action.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_TAPEACTION_HH\n#define EOS_TAPEACTION_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/action/Action.hh\"\n#include \"mgm/http/rest-api/business/tape/ITapeRestApiBusiness.hh\"\n#include \"mgm/http/rest-api/response/RestResponseFactory.hh\"\n#include \"mgm/http/rest-api/config/tape/TapeRestApiConfig.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nclass TapeAction : public Action\n{\npublic:\n  TapeAction(const std::string& urlPattern,\n             const common::HttpHandler::Methods method,\n             std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness):\n    Action(urlPattern, method), mTapeRestApiBusiness(tapeRestApiBusiness) {}\n  virtual common::HttpResponse* run(common::HttpRequest* request,\n                                    const common::VirtualIdentity* vid) override = 0;\nprotected:\n  std::shared_ptr<ITapeRestApiBusiness> mTapeRestApiBusiness;\n  RestResponseFactory mResponseFactory;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_TAPEACTION_HH\n"
  },
  {
    "path": "mgm/http/rest-api/action/tape/TapeActions.hh",
    "content": "// ----------------------------------------------------------------------\n// File: TapeActions.hh\n// Author: Consolidated tape REST API actions\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n ************************************************************************/\n\n#ifndef EOS_TAPE_ACTIONS_HH\n#define EOS_TAPE_ACTIONS_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/action/tape/TapeAction.hh\"\n#include \"mgm/http/rest-api/json/builder/JsonModelBuilder.hh\"\n#include \"mgm/http/rest-api/json/tape/TapeRestApiJsonifier.hh\"\n#include \"mgm/http/rest-api/model/tape/stage/CreateStageBulkRequestModel.hh\"\n#include \"mgm/http/rest-api/model/tape/stage/CreatedStageBulkRequestResponseModel.hh\"\n#include \"mgm/http/rest-api/model/tape/stage/PathsModel.hh\"\n#include \"mgm/http/rest-api/model/tape/stage/GetStageBulkRequestResponseModel.hh\"\n#include \"mgm/http/rest-api/model/tape/archiveinfo/GetArchiveInfoResponseModel.hh\"\n#include \"mgm/http/rest-api/handler/tape/TapeRestHandler.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n// CreateStageBulkRequest\nclass CreateStageBulkRequest : public TapeAction\n{\npublic:\n  CreateStageBulkRequest(const std::string& accessURL,\n                         const common::HttpHandler::Methods method,\n                         std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness,\n                         std::shared_ptr<JsonModelBuilder<CreateStageBulkRequestModel>> inputJsonModelBuilder,\n                         std::shared_ptr<TapeRestApiJsonifier<CreatedStageBulkRequestResponseModel>> outputObjectJsonifier,\n                         const TapeRestHandler* tapeRestHandler)\n    : TapeAction(accessURL, method, tapeRestApiBusiness),\n      mInputJsonModelBuilder(inputJsonModelBuilder),\n      mOutputObjectJsonifier(outputObjectJsonifier),\n      mTapeRestHandler(tapeRestHandler) {}\n  common::HttpResponse* run(common::HttpRequest* request,\n                            const common::VirtualIdentity* vid) override;\nprivate:\n  const std::string generateAccessURL(const std::string& bulkRequestId);\n  std::shared_ptr<JsonModelBuilder<CreateStageBulkRequestModel>> mInputJsonModelBuilder;\n  std::shared_ptr<TapeRestApiJsonifier<CreatedStageBulkRequestResponseModel>> mOutputObjectJsonifier;\n  const TapeRestHandler* mTapeRestHandler;\n};\n\n// GetStageBulkRequest\nclass GetStageBulkRequest : public TapeAction\n{\npublic:\n  GetStageBulkRequest(const std::string& accessURL,\n                      const common::HttpHandler::Methods method,\n                      std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness,\n                      std::shared_ptr<TapeRestApiJsonifier<GetStageBulkRequestResponseModel>> outputObjectJsonifier)\n    : TapeAction(accessURL, method, tapeRestApiBusiness),\n      mOutputObjectJsonifier(outputObjectJsonifier) {}\n  common::HttpResponse* run(common::HttpRequest* request,\n                            const common::VirtualIdentity* vid) override;\nprivate:\n  std::shared_ptr<TapeRestApiJsonifier<GetStageBulkRequestResponseModel>> mOutputObjectJsonifier;\n};\n\n// CancelStageBulkRequest\nclass CancelStageBulkRequest : public TapeAction\n{\npublic:\n  CancelStageBulkRequest(const std::string& accessURL,\n                         const common::HttpHandler::Methods method,\n                         std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness,\n                         std::shared_ptr<JsonModelBuilder<PathsModel>> inputJsonModelBuilder)\n    : TapeAction(accessURL, method, tapeRestApiBusiness),\n      mInputJsonModelBuilder(inputJsonModelBuilder) {}\n  common::HttpResponse* run(common::HttpRequest* request,\n                            const common::VirtualIdentity* vid) override;\nprivate:\n  std::shared_ptr<JsonModelBuilder<PathsModel>> mInputJsonModelBuilder;\n};\n\n// DeleteStageBulkRequest\nclass DeleteStageBulkRequest : public TapeAction\n{\npublic:\n  DeleteStageBulkRequest(const std::string& accessURL,\n                         const common::HttpHandler::Methods method,\n                         std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness)\n    : TapeAction(accessURL, method, tapeRestApiBusiness) {}\n  common::HttpResponse* run(common::HttpRequest* request,\n                            const common::VirtualIdentity* vid) override;\n};\n\n// GetArchiveInfo\nclass GetArchiveInfo : public TapeAction\n{\npublic:\n  GetArchiveInfo(const std::string& accessURL,\n                 const common::HttpHandler::Methods method,\n                 std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness,\n                 std::shared_ptr<JsonModelBuilder<PathsModel>> inputJsonModelBuilder,\n                 std::shared_ptr<TapeRestApiJsonifier<GetArchiveInfoResponseModel>> outputObjectJsonifier)\n    : TapeAction(accessURL, method, tapeRestApiBusiness),\n      mInputJsonModelBuilder(inputJsonModelBuilder),\n      mOutputObjectJsonifier(outputObjectJsonifier) {}\n  common::HttpResponse* run(common::HttpRequest* request,\n                            const common::VirtualIdentity* vid) override;\nprivate:\n  std::shared_ptr<JsonModelBuilder<PathsModel>> mInputJsonModelBuilder;\n  std::shared_ptr<TapeRestApiJsonifier<GetArchiveInfoResponseModel>> mOutputObjectJsonifier;\n};\n\n// CreateReleaseBulkRequest\nclass CreateReleaseBulkRequest : public TapeAction\n{\npublic:\n  CreateReleaseBulkRequest(const std::string& accessURL,\n                           const common::HttpHandler::Methods method,\n                           std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness,\n                           std::shared_ptr<JsonModelBuilder<PathsModel>> inputJsonModelBuilder)\n    : TapeAction(accessURL, method, tapeRestApiBusiness),\n      mInputJsonModelBuilder(inputJsonModelBuilder) {}\n  common::HttpResponse* run(common::HttpRequest* request,\n                            const common::VirtualIdentity* vid) override;\nprivate:\n  std::shared_ptr<JsonModelBuilder<PathsModel>> mInputJsonModelBuilder;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_TAPE_ACTIONS_HH\n"
  },
  {
    "path": "mgm/http/rest-api/action/tape/archiveinfo/GetArchiveInfo.cc",
    "content": "// ----------------------------------------------------------------------\n// File: GetArchiveInfo.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"GetArchiveInfo.hh\"\n#include \"mgm/http/rest-api/exception/Exceptions.hh\"\n#include \"mgm/http/rest-api/model/tape/archiveinfo/GetArchiveInfoResponseModel.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\ncommon::HttpResponse* GetArchiveInfo::run(common::HttpRequest* request,\n    const common::VirtualIdentity* vid)\n{\n  std::unique_ptr<PathsModel> paths;\n\n  try {\n    paths = mInputJsonModelBuilder->buildFromJson(request->GetBody());\n  } catch (const JsonValidationException& ex) {\n    return mResponseFactory.BadRequest(ex).getHttpResponse();\n  }\n\n  //Get the information about the files\n  std::shared_ptr<bulk::QueryPrepareResponse> queryPrepareResponse;\n\n  try {\n    queryPrepareResponse = mTapeRestApiBusiness->getFileInfo(paths.get(), vid);\n  } catch (const TapeRestApiBusinessException& ex) {\n    return mResponseFactory.InternalError(ex.what()).getHttpResponse();\n  }\n\n  //Build the json response and return it to the client\n  std::shared_ptr<GetArchiveInfoResponseModel> response =\n    std::make_shared<GetArchiveInfoResponseModel>(queryPrepareResponse);\n  response->setJsonifier(mOutputObjectJsonifier);\n  return mResponseFactory.createResponse(response,\n                                         common::HttpResponse::ResponseCodes::OK).getHttpResponse();\n}\n\nEOSMGMRESTNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/rest-api/action/tape/archiveinfo/GetArchiveInfo.hh",
    "content": "// ----------------------------------------------------------------------\n// File: GetArchiveInfo.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_GETARCHIVEINFO_HH\n#define EOS_GETARCHIVEINFO_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/action/tape/TapeAction.hh\"\n#include \"mgm/http/rest-api/json/builder/JsonModelBuilder.hh\"\n#include \"mgm/http/rest-api/json/tape/TapeRestApiJsonifier.hh\"\n#include \"mgm/http/rest-api/model/tape/archiveinfo/GetArchiveInfoResponseModel.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nclass GetArchiveInfo : public TapeAction\n{\npublic:\n  GetArchiveInfo(const std::string& accessURL,\n                 const common::HttpHandler::Methods method,\n                 std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness,\n                 std::shared_ptr<JsonModelBuilder<PathsModel>> inputJsonModelBuilder,\n                 std::shared_ptr<TapeRestApiJsonifier<GetArchiveInfoResponseModel>>\n                 outputObjectJsonifier):\n    TapeAction(accessURL, method, tapeRestApiBusiness),\n    mInputJsonModelBuilder(inputJsonModelBuilder),\n    mOutputObjectJsonifier(outputObjectJsonifier) {}\n  common::HttpResponse* run(common::HttpRequest* request,\n                            const common::VirtualIdentity* vid) override;\nprivate:\n  std::shared_ptr<JsonModelBuilder<PathsModel>> mInputJsonModelBuilder;\n  std::shared_ptr<TapeRestApiJsonifier<GetArchiveInfoResponseModel>>\n      mOutputObjectJsonifier;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_GETARCHIVEINFO_HH\n"
  },
  {
    "path": "mgm/http/rest-api/action/tape/release/CreateReleaseBulkRequest.cc",
    "content": "// ----------------------------------------------------------------------\n// File: CreateReleaseBulkRequest.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"CreateReleaseBulkRequest.hh\"\n#include \"mgm/http/rest-api/exception/Exceptions.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\ncommon::HttpResponse*\nCreateReleaseBulkRequest::run(common::HttpRequest* request,\n                              const common::VirtualIdentity* vid)\n{\n  std::unique_ptr<PathsModel> paths;\n\n  try {\n    paths = mInputJsonModelBuilder->buildFromJson(request->GetBody());\n  } catch (const JsonValidationException& ex) {\n    return mResponseFactory.BadRequest(ex).getHttpResponse();\n  }\n\n  //release the files provided by the user\n  try {\n    mTapeRestApiBusiness->releasePaths(paths.get(), vid);\n  } catch (const TapeRestApiBusinessException& ex) {\n    return mResponseFactory.InternalError(ex.what()).getHttpResponse();\n  }\n\n  return mResponseFactory.OkEmpty().getHttpResponse();\n}\n\nEOSMGMRESTNAMESPACE_END"
  },
  {
    "path": "mgm/http/rest-api/action/tape/release/CreateReleaseBulkRequest.hh",
    "content": "// ----------------------------------------------------------------------\n// File: CreateReleaseBulkRequest.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_CREATERELEASEBULKREQUEST_HH\n#define EOS_CREATERELEASEBULKREQUEST_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/action/tape/TapeAction.hh\"\n#include \"mgm/http/rest-api/action/tape/TapeAction.hh\"\n#include \"mgm/http/rest-api/json/builder/JsonModelBuilder.hh\"\n#include \"mgm/http/rest-api/json/tape/TapeRestApiJsonifier.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nclass CreateReleaseBulkRequest : public TapeAction\n{\npublic:\n  CreateReleaseBulkRequest(const std::string& accessURL,\n                           const common::HttpHandler::Methods method,\n                           std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness,\n                           std::shared_ptr<JsonModelBuilder<PathsModel>> inputJsonModelBuilder):\n    TapeAction(accessURL, method, tapeRestApiBusiness),\n    mInputJsonModelBuilder(inputJsonModelBuilder) {}\n  common::HttpResponse* run(common::HttpRequest* request,\n                            const common::VirtualIdentity* vid) override;\nprivate:\n  std::shared_ptr<JsonModelBuilder<PathsModel>> mInputJsonModelBuilder;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_CREATERELEASEBULKREQUEST_HH\n"
  },
  {
    "path": "mgm/http/rest-api/action/tape/stage/CancelStageBulkRequest.cc",
    "content": "// ----------------------------------------------------------------------\n// File: CancelStageBulkRequest.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"CancelStageBulkRequest.hh\"\n#include \"mgm/http/rest-api/utils/URLParser.hh\"\n#include \"mgm/http/rest-api/utils/URLBuilder.hh\"\n#include \"mgm/http/rest-api/model/tape/stage/PathsModel.hh\"\n#include \"mgm/http/rest-api/exception/Exceptions.hh\"\n#include \"mgm/http/rest-api/Constants.hh\"\n#include \"mgm/bulk-request/interface/RealMgmFileSystemInterface.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\ncommon::HttpResponse* CancelStageBulkRequest::run(common::HttpRequest* request,\n    const common::VirtualIdentity* vid)\n{\n  URLParser parser(request->GetUrl());\n  std::map<std::string, std::string> requestParameters;\n  //Check the content of the request and create a bulk-request with it\n  std::unique_ptr<PathsModel> paths;\n\n  try {\n    paths = mInputJsonModelBuilder->buildFromJson(request->GetBody());\n  } catch (const JsonValidationException& ex) {\n    return mResponseFactory.BadRequest(ex).getHttpResponse();\n  }\n\n  //Get the id of the request from the URL\n  parser.matchesAndExtractParameters(this->mAccessURLPattern, requestParameters);\n  const std::string& requestId = requestParameters[URLPARAM_ID];\n\n  try {\n    mTapeRestApiBusiness->cancelStageBulkRequest(requestId, paths.get(), vid);\n  } catch (const ObjectNotFoundException& ex) {\n    return mResponseFactory.NotFound().getHttpResponse();\n  } catch (const FileDoesNotBelongToBulkRequestException& ex) {\n    return mResponseFactory.BadRequest(ex.what()).getHttpResponse();\n  }\n\n  return mResponseFactory.OkEmpty().getHttpResponse();\n}\n\n\nEOSMGMRESTNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/rest-api/action/tape/stage/CancelStageBulkRequest.hh",
    "content": "// ----------------------------------------------------------------------\n// File: CancelStageBulkRequest.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_CANCELSTAGEBULKREQUEST_HH\n#define EOS_CANCELSTAGEBULKREQUEST_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/action/tape/TapeAction.hh\"\n#include \"mgm/http/rest-api/json/builder/JsonModelBuilder.hh\"\n#include \"mgm/http/rest-api/model/tape/stage/PathsModel.hh\"\n#include \"mgm/http/rest-api/business/tape/TapeRestApiBusiness.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nclass CancelStageBulkRequest : public TapeAction\n{\npublic:\n  CancelStageBulkRequest(const std::string& accessURL,\n                         const common::HttpHandler::Methods method,\n                         std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness,\n                         std::shared_ptr<JsonModelBuilder<PathsModel>> inputJsonModelBuilder):\n    TapeAction(accessURL, method, tapeRestApiBusiness),\n    mInputJsonModelBuilder(inputJsonModelBuilder) {}\n  common::HttpResponse* run(common::HttpRequest* request,\n                            const common::VirtualIdentity* vid) override;\nprivate:\n  std::shared_ptr<JsonModelBuilder<PathsModel>> mInputJsonModelBuilder;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_CANCELSTAGEBULKREQUEST_HH\n"
  },
  {
    "path": "mgm/http/rest-api/action/tape/stage/CreateStageBulkRequest.cc",
    "content": "// ----------------------------------------------------------------------\n// File: CreateStageBulkRequest.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#include \"CreateStageBulkRequest.hh\"\n#include <memory>\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/bulk-request/BulkRequestFactory.hh\"\n#include \"mgm/http/HttpHandler.hh\"\n#include \"mgm/http/rest-api/exception/JsonValidationException.hh\"\n#include \"mgm/http/rest-api/Constants.hh\"\n#include \"mgm/http/rest-api/exception/Exceptions.hh\"\n#include \"common/SymKeys.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\ncommon::HttpResponse* CreateStageBulkRequest::run(common::HttpRequest* request,\n    const common::VirtualIdentity* vid)\n{\n  //Check the content of the request and create a bulk-request with it\n  std::unique_ptr<CreateStageBulkRequestModel> createStageBulkRequestModel;\n\n  try {\n    createStageBulkRequestModel = mInputJsonModelBuilder->buildFromJson(\n                                    request->GetBody());\n  } catch (const JsonValidationException& ex) {\n    return mResponseFactory.BadRequest(ex).getHttpResponse();\n  }\n\n  //Create the prepare arguments\n  std::shared_ptr<bulk::BulkRequest> bulkRequest;\n\n  try {\n    bulkRequest = mTapeRestApiBusiness->createStageBulkRequest(\n                    createStageBulkRequestModel.get(), vid);\n  } catch (const TapeRestApiBusinessException& ex) {\n    return mResponseFactory.InternalError(ex.what()).getHttpResponse();\n  }\n\n  //Prepare the response and return it\n  std::shared_ptr<CreatedStageBulkRequestResponseModel>\n  createdStageBulkRequestModel(new CreatedStageBulkRequestResponseModel(\n                                 bulkRequest->getId()));\n  createdStageBulkRequestModel->setJsonifier(mOutputObjectJsonifier);\n  //Add the location URL in the response of the request\n  common::HttpResponse::HeaderMap responseMap;\n  responseMap[\"Location\"] = generateAccessURL(bulkRequest->getId());\n  return mResponseFactory.createResponse(createdStageBulkRequestModel,\n                                         common::HttpResponse::ResponseCodes::CREATED, responseMap).getHttpResponse();\n}\n\nconst std::string CreateStageBulkRequest::generateAccessURL(\n  const std::string& bulkRequestId)\n{\n  return mTapeRestHandler->getAccessURLBuilder()->add(getAccessURLPattern())->add(\n           bulkRequestId)->build();\n}\n\nEOSMGMRESTNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/rest-api/action/tape/stage/CreateStageBulkRequest.hh",
    "content": "// ----------------------------------------------------------------------\n// File: CreateStageBulkRequest.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#ifndef EOS_CREATESTAGEBULKREQUEST_HH\n#define EOS_CREATESTAGEBULKREQUEST_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/action/tape/TapeActions.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nusing CreateStageBulkRequest = CreateStageBulkRequest;\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_CREATESTAGEBULKREQUEST_HH\n"
  },
  {
    "path": "mgm/http/rest-api/action/tape/stage/DeleteStageBulkRequest.cc",
    "content": "// ----------------------------------------------------------------------\n// File: DeleteStageBulkRequest.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#include \"DeleteStageBulkRequest.hh\"\n#include \"mgm/http/rest-api/utils/URLParser.hh\"\n#include \"mgm/http/rest-api/model/tape/stage/PathsModel.hh\"\n#include \"mgm/http/rest-api/Constants.hh\"\n#include \"mgm/http/rest-api/exception/Exceptions.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\ncommon::HttpResponse* DeleteStageBulkRequest::run(common::HttpRequest* request,\n    const common::VirtualIdentity* vid)\n{\n  URLParser parser(request->GetUrl());\n  std::map<std::string, std::string> requestParameters;\n  //Get the id of the request from the URL\n  parser.matchesAndExtractParameters(this->mAccessURLPattern, requestParameters);\n  std::string requestId = requestParameters[URLPARAM_ID];\n\n  try {\n    mTapeRestApiBusiness->deleteStageBulkRequest(requestId, vid);\n  } catch (const ObjectNotFoundException& ex) {\n    return mResponseFactory.NotFound().getHttpResponse();\n  } catch (const TapeRestApiBusinessException& ex) {\n    return mResponseFactory.InternalError(ex.what()).getHttpResponse();\n  }\n\n  return mResponseFactory.OkEmpty().getHttpResponse();\n}\n\nEOSMGMRESTNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/rest-api/action/tape/stage/DeleteStageBulkRequest.hh",
    "content": "// ----------------------------------------------------------------------\n// File: DeleteStageBulkRequest.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_DELETESTAGEBULKREQUEST_HH\n#define EOS_DELETESTAGEBULKREQUEST_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/action/tape/TapeAction.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nclass DeleteStageBulkRequest : public TapeAction\n{\npublic:\n  DeleteStageBulkRequest(const std::string& accessURL,\n                         const common::HttpHandler::Methods method,\n                         std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness): TapeAction(\n                             accessURL, method, tapeRestApiBusiness) {}\n  common::HttpResponse* run(common::HttpRequest* request,\n                            const common::VirtualIdentity* vid) override;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_DELETESTAGEBULKREQUEST_HH\n"
  },
  {
    "path": "mgm/http/rest-api/action/tape/stage/GetStageBulkRequest.cc",
    "content": "// ----------------------------------------------------------------------\n// File: GetStageBulkRequest.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"GetStageBulkRequest.hh\"\n#include <memory>\n#include \"mgm/http/rest-api/model/tape/stage/GetStageBulkRequestResponseModel.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/http/HttpHandler.hh\"\n#include \"mgm/http/rest-api/utils/URLParser.hh\"\n#include \"mgm/http/rest-api/Constants.hh\"\n#include \"mgm/http/rest-api/exception/Exceptions.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\ncommon::HttpResponse* GetStageBulkRequest::run(common::HttpRequest* request,\n    const common::VirtualIdentity* vid)\n{\n  URLParser parser(request->GetUrl());\n  std::map<std::string, std::string> requestParameters;\n  //Get the id of the request from the URL\n  parser.matchesAndExtractParameters(this->mAccessURLPattern, requestParameters);\n  std::string requestId = requestParameters[URLPARAM_ID];\n  std::shared_ptr<GetStageBulkRequestResponseModel> responseModel;\n\n  try {\n    responseModel = mTapeRestApiBusiness->getStageBulkRequest(requestId, vid);\n  } catch (const ObjectNotFoundException& ex) {\n    return mResponseFactory.NotFound().getHttpResponse();\n  } catch (const TapeRestApiBusinessException& ex) {\n    return mResponseFactory.InternalError(ex.what()).getHttpResponse();\n  }\n\n  responseModel->setJsonifier(mOutputObjectJsonifier);\n  return mResponseFactory.createResponse(responseModel,\n                                         common::HttpResponse::ResponseCodes::OK).getHttpResponse();\n}\n\nEOSMGMRESTNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/rest-api/action/tape/stage/GetStageBulkRequest.hh",
    "content": "// ----------------------------------------------------------------------\n// File: GetStageBulkRequest.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_GETSTAGEBULKREQUEST_HH\n#define EOS_GETSTAGEBULKREQUEST_HH\n\n#include \"mgm/http/rest-api/action/tape/TapeAction.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/json/tape/TapeRestApiJsonifier.hh\"\n#include \"mgm/http/rest-api/model/tape/stage/GetStageBulkRequestResponseModel.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nclass GetStageBulkRequest : public TapeAction\n{\npublic:\n  GetStageBulkRequest(const std::string& accessURL,\n                      const common::HttpHandler::Methods method,\n                      std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness,\n                      std::shared_ptr<TapeRestApiJsonifier<GetStageBulkRequestResponseModel>>outputObjectJsonifier)\n    :\n    TapeAction(accessURL, method, tapeRestApiBusiness),\n    mOutputObjectJsonifier(outputObjectJsonifier) {}\n  common::HttpResponse* run(common::HttpRequest* request,\n                            const common::VirtualIdentity* vid) override;\nprivate:\n  std::shared_ptr<TapeRestApiJsonifier<GetStageBulkRequestResponseModel>>\n      mOutputObjectJsonifier;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_GETSTAGEBULKREQUEST_HH\n"
  },
  {
    "path": "mgm/http/rest-api/business/tape/ITapeRestApiBusiness.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ITapeRestApiBusiness.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_ITAPERESTAPIBUSINESS_HH\n#define EOS_ITAPERESTAPIBUSINESS_HH\n\n#include \"mgm/Namespace.hh\"\n#include <memory>\n#include \"mgm/bulk-request/BulkRequest.hh\"\n#include \"mgm/bulk-request/response/QueryPrepareResponse.hh\"\n#include \"mgm/http/rest-api/model/tape/stage/CreateStageBulkRequestModel.hh\"\n#include \"mgm/http/rest-api/model/tape/stage/PathsModel.hh\"\n#include \"mgm/http/rest-api/model/tape/stage/GetStageBulkRequestResponseModel.hh\"\n#include \"common/VirtualIdentity.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nclass ITapeRestApiBusiness\n{\npublic:\n  /**\n   * Creates and persists a stage bulk-request from the model passed in parameter\n   * @param model the object from which the bulk-request\n   * @param vid the issuer vid\n   * @return the created bulk-request\n   */\n  virtual std::shared_ptr<bulk::BulkRequest> createStageBulkRequest(\n    const CreateStageBulkRequestModel* model,\n    const common::VirtualIdentity* vid) = 0;\n  /**\n   * Cancels a subset of files belonging to a previously staged STAGE bulk-request identified by requestId\n   * @param requestId the requestId of the request from which the subset of files will be cancelled\n   * @param model the subset of files belonging to a request\n   * @param vid the vid of the issuer of the cancellation\n   */\n  virtual void cancelStageBulkRequest(const std::string& requestId,\n                                      const PathsModel* model, const common::VirtualIdentity* vid) = 0;\n  /**\n   * Returns a previously submitted STAGE request identified by requestId\n   * @param requestId the id of the previously submitted stage bulk-request\n   * @param vid the vid of the issuer of the get\n   * @return the model containing the bulk-request informations\n   * @throws ObjectNotFoundException if the request has not been found\n   */\n  virtual std::shared_ptr<GetStageBulkRequestResponseModel> getStageBulkRequest(\n    const std::string& requestId, const common::VirtualIdentity* vid) = 0;\n  /**\n   * Deletes a previously submitted STAGE bulk-request from the persistency\n   *\n   * It is expected that this method cancels the ongoing STAGE requests\n   * @param requestId the id of the previously STAGE bulk-request to delete\n   * @param vid the issuer of the deletion\n   * @throws ObjectNotFoundException if the bulk-request has not been found\n   */\n  virtual void deleteStageBulkRequest(const std::string& requestId,\n                                      const common::VirtualIdentity* vid) = 0;\n  /**\n   * Returns informations about the files contained in the model object\n   * @param model the object containing the files to get the information for\n   * @param vid the vid of the issuer\n   * @return a query prepare response object containing informations about the files\n   */\n  virtual std::shared_ptr<bulk::QueryPrepareResponse> getFileInfo(\n    const PathsModel* model, const common::VirtualIdentity* vid) = 0;\n  /**\n   * Releases a set of files, in our case (EOS+CTA), this is equivalent to trigger\n   * an eviction on the files provided by the user\n   * @param model the object containing the files to release\n   * @param vid the vid of the issuer\n   */\n  virtual void releasePaths(const PathsModel* model,\n                            const common::VirtualIdentity* vid) = 0;\n  virtual ~ITapeRestApiBusiness() {};\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_ITAPERESTAPIBUSINESS_HH\n"
  },
  {
    "path": "mgm/http/rest-api/business/tape/TapeRestApiBusiness.cc",
    "content": "// ----------------------------------------------------------------------\n// File: TapeRestApiBusiness.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/http/rest-api/exception/Exceptions.hh\"\n#include \"TapeRestApiBusiness.hh\"\n#include \"mgm/http/rest-api/model/tape/common/FilesContainer.hh\"\n#include \"mgm/bulk-request/utils/PrepareArgumentsWrapper.hh\"\n#include \"mgm/bulk-request/interface/RealMgmFileSystemInterface.hh\"\n#include \"mgm/bulk-request/prepare/manager/BulkRequestPrepareManager.hh\"\n#include \"mgm/bulk-request/business/BulkRequestBusiness.hh\"\n#include \"mgm/bulk-request/dao/factories/ProcDirectoryDAOFactory.hh\"\n#include \"mgm/http/rest-api/exception/Exceptions.hh\"\n#include \"mgm/bulk-request/exception/PersistencyException.hh\"\n#include \"mgm/stat/Stat.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nstd::shared_ptr<bulk::BulkRequest> TapeRestApiBusiness::createStageBulkRequest(\n  const CreateStageBulkRequestModel* model, const common::VirtualIdentity* vid)\n{\n  const FilesContainer& files = model->getFiles();\n  EXEC_TIMING_BEGIN(\"TapeRestApiBusiness::createStageBulkRequest\");\n  gOFS->MgmStats.Add(\"TapeRestApiBusiness::createStageBulkRequest\", vid->uid,\n                     vid->gid, 1);\n  bulk::PrepareArgumentsWrapper pargsWrapper(\n    \"fake_id\", Prep_STAGE, files.getPaths(), files.getOpaqueInfos());\n  auto prepareManager = createBulkRequestPrepareManager();\n  XrdOucErrInfo error;\n  int prepareRetCode = prepareManager->prepare(\n                         *pargsWrapper.getPrepareArguments(), error, vid);\n\n  if (prepareRetCode != SFS_DATA) {\n    throw TapeRestApiBusinessException(error.getErrText());\n  }\n\n  EXEC_TIMING_END(\"TapeRestApiBusiness::createStageBulkRequest\");\n  return prepareManager->getBulkRequest();\n}\n\nvoid TapeRestApiBusiness::cancelStageBulkRequest(const std::string& requestId,\n    const PathsModel* model, const common::VirtualIdentity* vid)\n{\n  EXEC_TIMING_BEGIN(\"TapeRestApiBusiness::cancelStageBulkRequest\");\n  gOFS->MgmStats.Add(\"TapeRestApiBusiness::cancelStageBulkRequest\", vid->uid,\n                     vid->gid, 1);\n  std::shared_ptr<bulk::BulkRequestBusiness> bulkRequestBusiness =\n    createBulkRequestBusiness();\n  auto bulkRequest = bulkRequestBusiness->getStageBulkRequest(requestId);\n\n  if (bulkRequest == nullptr) {\n    std::stringstream ss;\n    ss << \"Unable to find the STAGE bulk-request ID = \" << requestId;\n    throw ObjectNotFoundException(ss.str());\n  }\n\n  //First, check if the issuer of the cancellation is root, or is the person who submitted the stage request\n  //checkIssuerAuthorizedToAccessStageBulkRequest(bulkRequest.get(), vid,\"cancel\");\n  //Create the prepare arguments, we will only cancel the files that were given by the user\n  const FilesContainer& filesFromClient = model->getFiles();\n  auto filesFromBulkRequestContainer = bulkRequest->getFilesMap();\n  bulk::PrepareArgumentsWrapper pargsWrapper(requestId, Prep_CANCEL);\n\n  for (const auto& fileFromClient : filesFromClient.getPaths()) {\n    const auto& fileFromBulkRequestKeyVal = filesFromBulkRequestContainer->find(\n        fileFromClient);\n\n    if (fileFromBulkRequestKeyVal != filesFromBulkRequestContainer->end()) {\n      auto& fileFromBulkRequest = fileFromBulkRequestKeyVal->second;\n      auto& error = fileFromBulkRequest->getError();\n\n      if (!error) {\n        //We only cancel the files that do not have any error\n        pargsWrapper.addFile(fileFromClient, \"\");\n      }\n    } else {\n      std::stringstream ss;\n      ss << \"The file \" << fileFromClient << \" does not belong to the STAGE request \"\n         << bulkRequest->getId() << \". No modification has been made to this request.\";\n      throw FileDoesNotBelongToBulkRequestException(ss.str());\n    }\n  }\n\n  //Do the cancellation if there are files to cancel\n  if (pargsWrapper.getNbFiles() != 0) {\n    auto pm = createBulkRequestPrepareManager();\n    XrdOucErrInfo error;\n    int retCancellation = pm->prepare(*pargsWrapper.getPrepareArguments(), error,\n                                      vid);\n\n    if (retCancellation != SFS_OK) {\n      std::stringstream ss;\n      ss << \"Unable to cancel the files provided. errMsg=\\\"\"\n         << error.getErrText() << \"\\\"\";\n      throw TapeRestApiBusinessException(ss.str());\n    }\n  }\n\n  EXEC_TIMING_END(\"TapeRestApiBusiness::cancelStageBulkRequest\");\n}\n\nstd::shared_ptr<GetStageBulkRequestResponseModel>\nTapeRestApiBusiness::getStageBulkRequest(const std::string& requestId,\n    const common::VirtualIdentity* vid)\n{\n  EXEC_TIMING_BEGIN(\"TapeRestApiBusiness::getStageBulkRequest\");\n  gOFS->MgmStats.Add(\"TapeRestApiBusiness::getStageBulkRequest\", vid->uid,\n                     vid->gid, 1);\n  std::shared_ptr<GetStageBulkRequestResponseModel> ret =\n    std::make_shared<GetStageBulkRequestResponseModel>();\n  auto bulkRequestBusiness = createBulkRequestBusiness();\n  std::unique_ptr<bulk::StageBulkRequest> bulkRequest;\n\n  try {\n    bulkRequest = bulkRequestBusiness->getStageBulkRequest(requestId);\n\n    if (!bulkRequest) {\n      std::stringstream ss;\n      ss << \"Unable to find the STAGE bulk-request ID =\" << requestId;\n      throw ObjectNotFoundException(ss.str());\n    }\n  } catch (bulk::PersistencyException& ex) {\n    throw TapeRestApiBusinessException(ex.what());\n  }\n\n  //checkIssuerAuthorizedToAccessStageBulkRequest(bulkRequest.get(), vid,\"get\");\n  //Set bulk-request related attributes\n  ret->setCreationTime(bulkRequest->getCreationTime());\n  ret->setId(bulkRequest->getId());\n  //Instanciate prepare manager to get the tape, disk residency and an eventual error (set by CTA)\n  bulk::PrepareArgumentsWrapper pargsWrapper(requestId, Prep_QUERY);\n  auto files = bulkRequest->getFiles();\n\n  for (auto& file : *files) {\n    pargsWrapper.addFile(file->getPath(), \"\");\n  }\n\n  auto pm = createPrepareManager();\n  XrdOucErrInfo error;\n  auto queryPrepareResult = pm->queryPrepare(*pargsWrapper.getPrepareArguments(),\n                            error, vid);\n\n  if (!queryPrepareResult->hasQueryPrepareFinished()) {\n    std::stringstream ss;\n    ss << \"Unable to get information about the files belonging to the request \" <<\n       requestId << \". errMsg=\\\"\" << error.getErrText() << \"\\\"\";\n    throw TapeRestApiBusinessException(ss.str());\n  }\n\n  for (const auto& queryPrepareResponse :\n       queryPrepareResult->getResponse()->responses) {\n    const auto& filesFromBulkRequest = bulkRequest->getFilesMap();\n    auto fileFromBulkRequestItor = filesFromBulkRequest->find(\n                                     queryPrepareResponse.path);\n\n    if (fileFromBulkRequestItor != filesFromBulkRequest->end()) {\n      auto& fileFromBulkRequest = fileFromBulkRequestItor->second;\n      std::unique_ptr<GetStageBulkRequestResponseModel::File> item =\n        std::make_unique<GetStageBulkRequestResponseModel::File>();\n      item->mPath = queryPrepareResponse.path;\n\n      if (fileFromBulkRequest->getError()) {\n        item->mError = *fileFromBulkRequest->getError();\n      } else if (!queryPrepareResponse.error_text.empty()) {\n        //Error comes from CTA, so we need to update the state of the file to ERROR\n        item->mError = queryPrepareResponse.error_text;\n      } else if (!queryPrepareResponse.is_online && !queryPrepareResponse.is_reqid_present) {\n        //If there is no request for the file, an error should be returned\n        item->mError = \"File not requested with request ID \" + requestId;\n      } else {\n        item->mError = \"\";\n      }\n\n      item->mOnDisk = queryPrepareResponse.is_online;\n      ret->addFile(std::move(item));\n    }\n  }\n\n  EXEC_TIMING_END(\"TapeRestApiBusiness::getStageBulkRequest\");\n  return ret;\n}\n\nvoid TapeRestApiBusiness::deleteStageBulkRequest(const std::string& requestId,\n    const common::VirtualIdentity* vid)\n{\n  EXEC_TIMING_BEGIN(\"TapeRestApiBusiness::deleteStageBulkRequest\");\n  gOFS->MgmStats.Add(\"TapeRestApiBusiness::deleteStageBulkRequest\", vid->uid,\n                     vid->gid, 1);\n  //Get the prepare request from the persistency\n  std::shared_ptr<bulk::BulkRequestBusiness> bulkRequestBusiness =\n    createBulkRequestBusiness();\n  auto bulkRequest = bulkRequestBusiness->getStageBulkRequest(requestId);\n\n  if (bulkRequest == nullptr) {\n    std::stringstream ss;\n    ss << \"Unable to find the STAGE bulk-request ID = \" << requestId;\n    throw ObjectNotFoundException(ss.str());\n  }\n\n  //checkIssuerAuthorizedToAccessStageBulkRequest(bulkRequest.get(), vid,\"delete\");\n  //Create the prepare arguments, we will cancel all the files from this bulk-request\n  auto filesFromBulkRequest = bulkRequest->getFiles();\n  bulk::PrepareArgumentsWrapper pargsWrapper(requestId, Prep_CANCEL);\n\n  for (auto& fileFromBulkRequest : *filesFromBulkRequest) {\n    pargsWrapper.addFile(fileFromBulkRequest->getPath(), \"\");\n  }\n\n  auto pm = createPrepareManager();\n  XrdOucErrInfo error;\n  int retCancellation = pm->prepare(*pargsWrapper.getPrepareArguments(), error,\n                                    vid);\n\n  if (retCancellation != SFS_OK) {\n    std::stringstream ss;\n    ss << \"Unable to cancel the files provided. errMsg=\\\"\" << error.getErrText() <<\n       \"\\\"\";\n    throw TapeRestApiBusinessException(ss.str());\n  }\n\n  //Now that the request got cancelled, let's delete it from the persistency\n  try {\n    bulkRequestBusiness->deleteBulkRequest(bulkRequest.get());\n  } catch (bulk::PersistencyException& ex) {\n    throw TapeRestApiBusinessException(ex.what());\n  }\n\n  EXEC_TIMING_END(\"TapeRestApiBusiness::deleteStageBulkRequest\");\n}\n\nstd::shared_ptr<bulk::QueryPrepareResponse> TapeRestApiBusiness::getFileInfo(\n  const PathsModel* model, const common::VirtualIdentity* vid)\n{\n  EXEC_TIMING_BEGIN(\"TapeRestApiBusiness::getFileInfo\");\n  gOFS->MgmStats.Add(\"TapeRestApiBusiness::getFileInfo\", vid->uid, vid->gid, 1);\n  auto& filesContainer = model->getFiles();\n  bulk::PrepareArgumentsWrapper pargsWrapper(\"fake_id\", Prep_QUERY);\n\n  for (const auto& pathFromUser : filesContainer.getPaths()) {\n    pargsWrapper.addFile(pathFromUser, \"\");\n  }\n\n  auto pm = createPrepareManager();\n  XrdOucErrInfo error;\n  auto queryPrepareResult = pm->queryPrepare(*pargsWrapper.getPrepareArguments(),\n                            error, vid);\n\n  if (!queryPrepareResult->hasQueryPrepareFinished()) {\n    std::stringstream ss;\n    ss << \"Unable to get information about the files provided. errMsg=\\\"\" <<\n       error.getErrText() << \"\\\"\";\n    throw TapeRestApiBusinessException(ss.str());\n  }\n\n  EXEC_TIMING_END(\"TapeRestApiBusiness::getFileInfo\");\n  return queryPrepareResult->getResponse();\n}\n\nvoid TapeRestApiBusiness::releasePaths(const PathsModel* model,\n                                       const common::VirtualIdentity* vid)\n{\n  EXEC_TIMING_BEGIN(\"TapeRestApiBusiness::releasePaths\");\n  gOFS->MgmStats.Add(\"TapeRestApiBusiness::releasePaths\", vid->uid, vid->gid, 1);\n  auto& filesContainer = model->getFiles();\n  bulk::PrepareArgumentsWrapper pargsWrapper(\"fake_id\", Prep_EVICT,\n      filesContainer.getPaths(),\n      filesContainer.getOpaqueInfos());\n  auto pm = createBulkRequestPrepareManager();\n  XrdOucErrInfo error;\n  int retEvict = pm->prepare(*pargsWrapper.getPrepareArguments(), error, vid);\n\n  if (retEvict != SFS_OK) {\n    std::stringstream ss;\n    ss << \"Unable to release the files provided. errMsg=\\\"\" << error.getErrText() <<\n       \"\\\"\";\n    throw TapeRestApiBusinessException(ss.str());\n  }\n\n  EXEC_TIMING_END(\"TapeRestApiBusiness::releasePaths\");\n}\n\nstd::unique_ptr<bulk::BulkRequestPrepareManager>\nTapeRestApiBusiness::createBulkRequestPrepareManager()\n{\n  std::unique_ptr<bulk::RealMgmFileSystemInterface> mgmOfs =\n    std::make_unique<bulk::RealMgmFileSystemInterface>(gOFS);\n  std::unique_ptr<bulk::BulkRequestPrepareManager> prepareManager =\n    std::make_unique<bulk::BulkRequestPrepareManager>(std::move(mgmOfs));\n  std::shared_ptr<bulk::BulkRequestBusiness> bulkRequestBusiness =\n    createBulkRequestBusiness();\n  prepareManager->setBulkRequestBusiness(bulkRequestBusiness);\n  return prepareManager;\n}\n\nstd::unique_ptr<bulk::PrepareManager>\nTapeRestApiBusiness::createPrepareManager()\n{\n  std::unique_ptr<bulk::RealMgmFileSystemInterface> mgmOfs =\n    std::make_unique<bulk::RealMgmFileSystemInterface>(gOFS);\n  std::unique_ptr<bulk::PrepareManager> prepareManager =\n    std::make_unique<bulk::PrepareManager>(std::move(mgmOfs));\n  return prepareManager;\n}\n\nstd::shared_ptr<bulk::BulkRequestBusiness>\nTapeRestApiBusiness::createBulkRequestBusiness()\n{\n  std::unique_ptr<bulk::AbstractDAOFactory> daoFactory(new\n      bulk::ProcDirectoryDAOFactory(gOFS,\n                                    *gOFS->mProcDirectoryBulkRequestTapeRestApiLocations));\n  return std::make_shared<bulk::BulkRequestBusiness>(std::move(daoFactory));\n}\n\nvoid TapeRestApiBusiness::checkIssuerAuthorizedToAccessStageBulkRequest(\n  const bulk::StageBulkRequest* bulkRequest, const common::VirtualIdentity* vid,\n  const std::string& action)\n{\n  //First, check if the issuer of the cancellation is root, or is the person who submitted the stage request\n  if (vid->uid != 0 && vid->uid != bulkRequest->getIssuerVid().uid) {\n    throw ForbiddenException(\"You are not allowed to \" + action +\n                             \" this bulk-request\");\n  }\n}\nEOSMGMRESTNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/rest-api/business/tape/TapeRestApiBusiness.hh",
    "content": "// ----------------------------------------------------------------------\n// File: TapeRestApiBusiness.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_TAPERESTAPIBUSINESS_HH\n#define EOS_TAPERESTAPIBUSINESS_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/business/tape/ITapeRestApiBusiness.hh\"\n#include \"mgm/bulk-request/prepare/manager/BulkRequestPrepareManager.hh\"\n#include \"mgm/bulk-request/prepare/StageBulkRequest.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nclass TapeRestApiBusiness : public ITapeRestApiBusiness\n{\npublic:\n  virtual std::shared_ptr<bulk::BulkRequest> createStageBulkRequest(\n    const CreateStageBulkRequestModel* model,\n    const common::VirtualIdentity* vid) override;\n  virtual void cancelStageBulkRequest(const std::string& requestId,\n                                      const PathsModel* model, const common::VirtualIdentity* vid) override;\n  virtual  std::shared_ptr<GetStageBulkRequestResponseModel> getStageBulkRequest(\n    const std::string& requestId, const common::VirtualIdentity* vid) override;\n  virtual void deleteStageBulkRequest(const std::string& requestId,\n                                      const common::VirtualIdentity* vid) override;\n  virtual std::shared_ptr<bulk::QueryPrepareResponse> getFileInfo(\n    const PathsModel* model, const common::VirtualIdentity* vid) override;\n  virtual void releasePaths(const PathsModel* model,\n                            const common::VirtualIdentity* vid) override;\nprotected:\n  std::unique_ptr<bulk::BulkRequestPrepareManager>\n  createBulkRequestPrepareManager();\n  std::unique_ptr<bulk::PrepareManager> createPrepareManager();\n  std::shared_ptr<bulk::BulkRequestBusiness> createBulkRequestBusiness();\n  /**\n   * Checks whether the issuer of a request is allowed to access the stage bulk-request\n   * for modification, consultation, deletion...\n   * @param bulkRequest the stage bulk-request to check the access\n   * @param vid the vid of the user who issued the request against the bulkRequest\n   * @param action what action the user issued (cancel, delete, get)\n   */\n  void checkIssuerAuthorizedToAccessStageBulkRequest(const bulk::StageBulkRequest*\n      bulkRequest, const common::VirtualIdentity* vid, const std::string& action);\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_TAPERESTAPIBUSINESS_HH\n"
  },
  {
    "path": "mgm/http/rest-api/config/tape/TapeRestApiConfig.cc",
    "content": "// ----------------------------------------------------------------------\n// File: TapeRestApiConfig.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"TapeRestApiConfig.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nTapeRestApiConfig::TapeRestApiConfig() : mAccessURL(\"/api/\") {}\n\nTapeRestApiConfig::TapeRestApiConfig(const std::string& accessURL): mAccessURL(\n    accessURL) {}\n\nvoid TapeRestApiConfig::setSiteName(const std::string& siteName)\n{\n  common::RWMutexWriteLock rwlock(mConfigMutex);\n  mSiteName = siteName;\n}\n\nstd::string TapeRestApiConfig::getSiteName() const\n{\n  common::RWMutexReadLock rwlock(mConfigMutex);\n  return mSiteName;\n}\n\nbool TapeRestApiConfig::isActivated() const\n{\n  return mIsActivated;\n}\n\nvoid TapeRestApiConfig::setActivated(const bool activated)\n{\n  mIsActivated = activated;\n}\n\n\nvoid TapeRestApiConfig::setTapeEnabled(const bool tapeEnabled)\n{\n  mTapeEnabled = tapeEnabled;\n}\n\nvoid TapeRestApiConfig::setHostAlias(const std::string& mgmOfsAlias)\n{\n  common::RWMutexWriteLock rwlock(mConfigMutex);\n  mHostAlias = mgmOfsAlias;\n}\n\nstd::string TapeRestApiConfig::getHostAlias() const\n{\n  common::RWMutexReadLock rwlock(mConfigMutex);\n  return mHostAlias;\n}\n\nvoid TapeRestApiConfig::setEndpointToUrlMapping(const std::map<std::string, std::string>& tapeRestApiEndpointUriMap)\n{\n  common::RWMutexWriteLock rwlock(mConfigMutex);\n  mTapeRestApiEndpointUrlMap = tapeRestApiEndpointUriMap;\n}\n\nstd::map<std::string, std::string> TapeRestApiConfig::getEndpointToUriMapping() const\n{\n  common::RWMutexReadLock rwlock(mConfigMutex);\n  return mTapeRestApiEndpointUrlMap;\n}\n\nvoid TapeRestApiConfig::setXrdHttpPort(const uint16_t xrdHttpPort)\n{\n  mXrdHttpPort = xrdHttpPort;\n}\n\nuint16_t TapeRestApiConfig::getXrdHttpPort() const\n{\n  return mXrdHttpPort;\n}\n\nbool TapeRestApiConfig::isTapeEnabled() const\n{\n  return mTapeEnabled;\n}\n\nconst std::string& TapeRestApiConfig::getAccessURL() const\n{\n  //This parameter does not need mutex protection as it cannot be modified\n  return mAccessURL;\n}\n\nbool TapeRestApiConfig::isStageEnabled() const\n{\n  return mStageEnabled;\n}\n\nvoid TapeRestApiConfig::setStageEnabled(const bool isStageEnabled)\n{\n  mStageEnabled = isStageEnabled;\n}\n\nEOSMGMRESTNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/rest-api/config/tape/TapeRestApiConfig.hh",
    "content": "// ----------------------------------------------------------------------\n// File: TapeRestApiConfig.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_TAPERESTAPICONFIG_HH\n#define EOS_TAPERESTAPICONFIG_HH\n\n#include \"mgm/Namespace.hh\"\n#include <string>\n#include \"common/RWMutex.hh\"\n#include <atomic>\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * This class holds all the configuration related\n * to the Tape REST API\n */\nclass TapeRestApiConfig\n{\npublic:\n  /**\n   * Default constructor: the accessURL is \"api\"\n   */\n  TapeRestApiConfig();\n  /**\n   * Constructor with the accessURL\n   * @param accessURL the accessURL that will allow\n   * the user to access the REST API\n   */\n  TapeRestApiConfig(const std::string& accessURL);\n  /**\n   * Sets the siteName that will be used for\n   * targetedMetadata\n   */\n  void setSiteName(const std::string& siteName);\n  /**\n   * Returns true if the tape REST API has been\n   * activated, false otherwise\n   */\n  bool isActivated() const;\n  /**\n   * Enables/disables the tape REST API\n   * @param activated is set to true if the tape REST API\n   * should be activated, false otherwise\n   */\n  void setActivated(const bool activated);\n\n  /**\n   * Sets the tape enabled flag\n   * @param tapeEnabled this value should come from the MGM configuration\n   */\n  void setTapeEnabled(const bool tapeEnabled);\n\n  /**\n   * Sets the DNS alias of the server where the REST API\n   * is running\n   * @param mgmOfsAlias the DNS alias of the server where the REST API is running\n   */\n  void setHostAlias(const std::string& mgmOfsAlias);\n\n  /**\n   * @return Gets the DNS alias of the server where the REST API\n   * is running\n   */\n  std::string getHostAlias() const;\n\n  /**\n   * Sets (thus overriding the default values) the mapping between the REST API versions and URIs in the .well-known\n   * is running\n   * @param tapeRestApiEndpointUriMap the mapping between the REST API versions and URIs\n   */\n  void setEndpointToUrlMapping(const std::map<std::string, std::string>& tapeRestApiEndpointUriMap);\n\n  /**\n   * @return Gets the mapping between the REST API versions and URIs\n   */\n  std::map<std::string, std::string> getEndpointToUriMapping() const;\n\n  /**\n   * Sets the port of the XrdHttp server where the tape REST API is running\n   * @param xrdHttpPort\n   */\n  void setXrdHttpPort(const uint16_t xrdHttpPort);\n\n  /**\n   * @return the port of the XrdHttp server where the tape REST API is running\n   */\n  uint16_t getXrdHttpPort() const;\n\n  /**\n   * Returns the value of the tape enabled flag\n   */\n  bool isTapeEnabled() const;\n\n  std::string getSiteName() const;\n  const std::string& getAccessURL() const;\n\n  void setStageEnabled(const bool isStageEnabled);\n  bool isStageEnabled() const;\n\nprivate:\n  /**\n   * This parameter represents the STAGE targeted\n   * metadata identifier that allows the user to pass any\n   * extra information for this API endpoint\n   */\n  std::string mSiteName;\n  //Access URL of the tape REST API (without https://fqdn)\n  std::string mAccessURL;\n  //The mgmofs.alias value coming from the MGM configuration file\n  std::string mHostAlias;\n  // The mapping between the REST API versions and URIs\n  std::map<std::string, std::string> mTapeRestApiEndpointUrlMap;\n  //By default, the tape REST API is not activated\n  std::atomic<bool> mIsActivated = false;\n  //The tape enabled flag of the EOS instance where the tape REST API is running\n  std::atomic<bool> mTapeEnabled = false;\n  //The port of the XrdHttp server where the tape REST API is running\n  std::atomic<uint16_t> mXrdHttpPort;\n  //Mutex protecting all variables of this configuration\n  mutable common::RWMutex mConfigMutex;\n  //This flag allows to activate or deactivate the staging of the tape REST API\n  std::atomic<bool> mStageEnabled = false;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_TAPERESTAPICONFIG_HH\n"
  },
  {
    "path": "mgm/http/rest-api/exception/Exceptions.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Exceptions.hh\n// Author: Simplified umbrella for REST exceptions\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) CERN/Switzerland                                       *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_REST_EXCEPTIONS_HH\n#define EOS_REST_EXCEPTIONS_HH\n\n#include \"mgm/http/rest-api/exception/RestException.hh\"\n#include <string>\n\n#include \"mgm/Namespace.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nclass NotFoundException : public RestException\n{\npublic:\n  explicit NotFoundException(const std::string& exceptionMsg): RestException(exceptionMsg) {}\n};\n\nclass MethodNotAllowedException : public RestException\n{\npublic:\n  explicit MethodNotAllowedException(const std::string& exceptionMsg): RestException(exceptionMsg) {}\n};\n\nclass ForbiddenException : public RestException\n{\npublic:\n  explicit ForbiddenException(const std::string& exceptionMsg): RestException(exceptionMsg) {}\n};\n\nclass NotImplementedException : public RestException\n{\npublic:\n  explicit NotImplementedException(const std::string& exceptionMsg): RestException(exceptionMsg) {}\n};\n\nclass ObjectNotFoundException : public RestException\n{\npublic:\n  explicit ObjectNotFoundException(const std::string& exceptionMsg): RestException(exceptionMsg) {}\n};\n\nclass ActionNotFoundException : public NotFoundException\n{\npublic:\n  explicit ActionNotFoundException(const std::string& exceptionMsg): NotFoundException(exceptionMsg) {}\n};\n\nclass ControllerNotFoundException : public NotFoundException\n{\npublic:\n  explicit ControllerNotFoundException(const std::string& exceptionMsg): NotFoundException(exceptionMsg) {}\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#include \"mgm/http/rest-api/exception/JsonValidationException.hh\"\n\n// Tape business exceptions (same namespace as RestException)\nEOSMGMRESTNAMESPACE_BEGIN\n\nclass TapeRestApiBusinessException : public RestException\n{\npublic:\n  explicit TapeRestApiBusinessException(const std::string& errorMsg)\n    : RestException(errorMsg) {}\n};\n\nclass FileDoesNotBelongToBulkRequestException : public RestException\n{\npublic:\n  explicit FileDoesNotBelongToBulkRequestException(const std::string& errorMsg)\n    : RestException(errorMsg) {}\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_REST_EXCEPTIONS_HH\n\n\n"
  },
  {
    "path": "mgm/http/rest-api/exception/JsonValidationException.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ObjectModelMalformedException.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_JSONVALIDATIONEXCEPTION_HH\n#define EOS_JSONVALIDATIONEXCEPTION_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/exception/RestException.hh\"\n#include \"mgm/http/rest-api/json/builder/ValidationError.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * Exception class to use when a json string cannot allow to instanciate\n * a Model object (wrong field names, invalid JSON format...)\n */\nclass JsonValidationException : public RestException\n{\npublic:\n  inline JsonValidationException(const std::string& exceptionMsg)\n    : RestException(exceptionMsg) {}\n  inline JsonValidationException(std::unique_ptr<ValidationErrors>&& validationErrors)\n    : RestException(\"JSON validation error\"), mValidationErrors(std::move(validationErrors)) {}\n  inline const ValidationErrors* getValidationErrors() const\n  {\n    return mValidationErrors.get();\n  }\n  inline std::unique_ptr<ValidationErrors> getValidationErrors()\n  {\n    return std::move(mValidationErrors);\n  }\nprotected:\n  std::unique_ptr<ValidationErrors> mValidationErrors;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_JSONVALIDATIONEXCEPTION_HH\n"
  },
  {
    "path": "mgm/http/rest-api/exception/RestException.hh",
    "content": "// ----------------------------------------------------------------------\n// File: RestException.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#ifndef EOS_RESTEXCEPTION_HH\n#define EOS_RESTEXCEPTION_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"common/exception/Exception.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * Class representing a general REST API exception\n */\nclass RestException : public common::Exception\n{\npublic:\n  RestException(const std::string& exceptionMsg): common::Exception(\n      exceptionMsg) {};\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_RESTEXCEPTION_HH\n"
  },
  {
    "path": "mgm/http/rest-api/handler/RestHandler.cc",
    "content": "// ----------------------------------------------------------------------\n// File: RestHandler.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"RestHandler.hh\"\n#include \"mgm/http/rest-api/exception/Exceptions.hh\"\n#include \"common/Logging.hh\"\n#include \"common/RegexWrapper.hh\"\n#include \"mgm/http/rest-api/utils/URLParser.hh\"\n\nnamespace\n{\nstd::string sEntryPointRegex(\"^\\\\/(\\\\.?[a-z0-9-]+)+\\\\/$\");\n}\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nRestHandler::RestHandler(const std::string& entryPointURL): mEntryPointURL(\n    entryPointURL)\n{\n  verifyRestApiEntryPoint(entryPointURL);\n}\n\nbool RestHandler::isRestRequest(const std::string& requestUrl,\n                                std::string& errorMsg) const\n{\n  //The URL should start with the API entry URL\n  URLParser parser(requestUrl);\n  return parser.startsBy(mEntryPointURL);\n}\n\nvoid RestHandler::verifyRestApiEntryPoint(const std::string& entryPointURL)\nconst\n{\n  if (!eos::common::eos_regex_match(entryPointURL, sEntryPointRegex)) {\n    std::stringstream ss;\n    ss << \"The REST API entrypoint provided (\" << entryPointURL <<\n       \") is malformed. It should have the format: /apientrypoint/.\";\n    throw RestException(ss.str());\n  }\n}\n\nstd::string RestHandler::getEntryPointURL() const\n{\n  return mEntryPointURL;\n}\n\nEOSMGMRESTNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/rest-api/handler/RestHandler.hh",
    "content": "// ----------------------------------------------------------------------\n// File: RestHandler.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_RESTHANDLER_HH\n#define EOS_RESTHANDLER_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"common/http/HttpResponse.hh\"\n#include \"common/http/HttpRequest.hh\"\n#include \"common/VirtualIdentity.hh\"\n#include <XrdHttp/XrdHttpExtHandler.hh>\n#include <memory>\n#include <map>\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * This class allows to handle REST requests.\n */\nclass RestHandler\n{\npublic:\n  RestHandler(const std::string& entryPointURL);\n  /**\n   * Handles the request given in parameter and returns a response\n   * @param request the request to handle\n   * @return A pointer to an HttpResponse object that will contain the JSON response that will be returned to the client\n   */\n  virtual common::HttpResponse* handleRequest(common::HttpRequest* request,\n      const common::VirtualIdentity* vid) = 0;\n\n  /**\n   * Returns true if the requestURL passed in parameter should trigger an API handling,\n   * false otherwise\n   * @param requestUrl the user request URL\n   * @param errorMsg the error message that one can use to indicate why the call to the REST API will not have any action\n   * @return true if the requestURL passed in parameter should trigger an API handling,\n   * false otherwise\n   */\n  virtual bool isRestRequest(const std::string& requestURL,\n                             std::string& errorMsg) const;\n\n  /**\n   * @return the RestHandler entry point URL (example: /api/)\n   */\n  std::string getEntryPointURL() const;\n\n  virtual ~RestHandler() {}\nprotected:\n  std::string mEntryPointURL;\nprivate:\n  void verifyRestApiEntryPoint(const std::string& entryPointURL) const;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_RESTHANDLER_HH\n"
  },
  {
    "path": "mgm/http/rest-api/handler/tape/TapeRestHandler.cc",
    "content": "// ----------------------------------------------------------------------\n// File: TapeRestHandler.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"TapeRestHandler.hh\"\n#include \"mgm/imaster/IMaster.hh\"\n#include \"mgm/http/rest-api/Constants.hh\"\n#include \"mgm/http/rest-api/action/tape/TapeActions.hh\"\n#include \"mgm/http/rest-api/business/tape/TapeRestApiBusiness.hh\"\n#include \"mgm/http/rest-api/Constants.hh\"\n#include \"mgm/http/rest-api/exception/Exceptions.hh\"\n#include \"mgm/http/rest-api/json/tape/TapeJsonifiers.hh\"\n#include \"mgm/http/rest-api/json/tape/TapeModelBuilders.hh\"\n#include \"mgm/http/rest-api/response/ErrorHandling.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nstd::string TapeRestHandler::apiVersionToStr(TapeRestHandler::ApiVersion apiVersion) {\n  switch(apiVersion) {\n  case ApiVersion::V0Dot1:\n    return \"v0.1\";\n  case ApiVersion::V1:\n    return \"v1\";\n  default:\n    throw std::invalid_argument(\"Unknown Tape REST API version.\");\n  }\n}\n\nTapeRestHandler::TapeRestHandler(const TapeRestApiConfig* config): RestHandler(\n    config->getAccessURL()), mTapeRestApiConfig(config)\n{\n  initializeTapeWellKnownInfos();\n  initialize(DEFAULT_API_VERSION);\n  auto endpointToUrlMap = config->getEndpointToUriMapping();\n  for (auto & [version, url]: endpointToUrlMap) {\n    addEndpointToWellKnown(version, url);\n  }\n  // If there was no .well-known endpoint provided for the version DEFAULT_API_VERSION\n  // construct it with the default setup\n  if (!endpointToUrlMap.count(apiVersionToStr(DEFAULT_API_VERSION))) {\n    addEndpointToWellKnown(apiVersionToStr(DEFAULT_API_VERSION));\n  }\n}\n\nvoid TapeRestHandler::initialize(TapeRestHandler::ApiVersion apiVersion) {\n  std::shared_ptr<TapeRestApiBusiness> restApiBusiness =\n      std::make_shared<TapeRestApiBusiness>();\n  switch (apiVersion) {\n  case ApiVersion::V0Dot1:\n    // No routes exposed for v0.1\n    break;\n  case ApiVersion::V1:\n    initializeStageRoutes(apiVersion, restApiBusiness);\n    break ;\n  default:\n    throw std::invalid_argument(\"Unknown Tape REST API version. Failed to initialize.\");\n  }\n  initializeArchiveinfoRoutes(apiVersion, restApiBusiness);\n  initializeReleaseRoutes(apiVersion, restApiBusiness);\n}\n\nvoid TapeRestHandler::initializeStageRoutes(\n  TapeRestHandler::ApiVersion apiVersion,\n  std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness)\n{\n  const std::string controllerAccessURL = mEntryPointURL + apiVersionToStr(apiVersion) + \"/stage/\";\n  mActions.emplace_back(std::make_unique<CreateStageBulkRequest>(\n                           controllerAccessURL, common::HttpHandler::Methods::POST, tapeRestApiBusiness,\n                           std::make_shared<CreateStageRequestModelBuilder>(mTapeRestApiConfig->getSiteName()),\n                           std::make_shared<CreatedStageBulkRequestJsonifier>(), this));\n  auto* createStage = mActions.back().get();\n  mRouter.add(controllerAccessURL, common::HttpHandler::Methods::POST,\n              [createStage](auto* req, auto* vid) { return createStage->run(req, vid); });\n\n  mActions.emplace_back(std::make_unique<CancelStageBulkRequest>(\n                           controllerAccessURL + \"/\" + URLPARAM_ID + \"/cancel\",\n                           common::HttpHandler::Methods::POST, tapeRestApiBusiness,\n                           std::make_shared<PathsModelBuilder>()));\n  auto* cancelStage = mActions.back().get();\n  mRouter.add(controllerAccessURL + \"/\" + URLPARAM_ID + \"/cancel\",\n              common::HttpHandler::Methods::POST,\n              [cancelStage](auto* req, auto* vid) { return cancelStage->run(req, vid); });\n\n  mActions.emplace_back(std::make_unique<GetStageBulkRequest>(\n                           controllerAccessURL + \"/\" + URLPARAM_ID,\n                           common::HttpHandler::Methods::GET, tapeRestApiBusiness,\n                           std::make_shared<GetStageBulkRequestJsonifier>()));\n  auto* getStage = mActions.back().get();\n  mRouter.add(controllerAccessURL + \"/\" + URLPARAM_ID,\n              common::HttpHandler::Methods::GET,\n              [getStage](auto* req, auto* vid) { return getStage->run(req, vid); });\n\n  mActions.emplace_back(std::make_unique<DeleteStageBulkRequest>(\n                           controllerAccessURL + \"/\" + URLPARAM_ID,\n                           common::HttpHandler::Methods::DELETE, tapeRestApiBusiness));\n  auto* deleteStage = mActions.back().get();\n  mRouter.add(controllerAccessURL + \"/\" + URLPARAM_ID,\n              common::HttpHandler::Methods::DELETE,\n              [deleteStage](auto* req, auto* vid) { return deleteStage->run(req, vid); });\n}\n\nvoid TapeRestHandler::initializeArchiveinfoRoutes(\n  TapeRestHandler::ApiVersion apiVersion,\n  std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness)\n{\n  const std::string accessURL = mEntryPointURL + apiVersionToStr(apiVersion) + \"/archiveinfo/\";\n  mActions.emplace_back(std::make_unique<GetArchiveInfo>(\n                           accessURL, common::HttpHandler::Methods::POST,\n                           tapeRestApiBusiness, std::make_shared<PathsModelBuilder>(),\n                           std::make_shared<GetArchiveInfoResponseJsonifier>()));\n  auto* getArchiveInfo = mActions.back().get();\n  mRouter.add(accessURL, common::HttpHandler::Methods::POST,\n              [getArchiveInfo](auto* req, auto* vid) { return getArchiveInfo->run(req, vid); });\n}\n\nvoid TapeRestHandler::initializeReleaseRoutes(\n  TapeRestHandler::ApiVersion apiVersion,\n  std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness)\n{\n  const std::string accessURL = mEntryPointURL + apiVersionToStr(apiVersion) + \"/release/\";\n  mActions.emplace_back(std::make_unique<CreateReleaseBulkRequest>(\n                           accessURL + URLPARAM_ID,\n                           common::HttpHandler::Methods::POST, tapeRestApiBusiness,\n                           std::make_shared<PathsModelBuilder>()));\n  auto* createRelease = mActions.back().get();\n  mRouter.add(accessURL + URLPARAM_ID, common::HttpHandler::Methods::POST,\n              [createRelease](auto* req, auto* vid) { return createRelease->run(req, vid); });\n}\n\nvoid TapeRestHandler::initializeTapeWellKnownInfos()\n{\n  mTapeWellKnownInfos = std::make_unique<TapeWellKnownInfos>\n                        (mTapeRestApiConfig->getSiteName());\n}\n\nvoid TapeRestHandler::addEndpointToWellKnown(const std::string& version)\n{\n  auto accessURLBuilder = getAccessURLBuilder();\n  accessURLBuilder->add(mEntryPointURL);\n  accessURLBuilder->add(version);\n  mTapeWellKnownInfos->addEndpoint(accessURLBuilder->build(), version);\n}\n\nvoid TapeRestHandler::addEndpointToWellKnown(const std::string& version, const std::string& url)\n{\n  mTapeWellKnownInfos->addEndpoint(url, version);\n}\n\nbool TapeRestHandler::isRestRequest(const std::string& requestURL,\n                                    std::string& errorMsg) const\n{\n  bool isRestRequest = RestHandler::isRestRequest(requestURL, errorMsg);\n\n  if (isRestRequest) {\n    if (mTapeRestApiConfig->getSiteName().empty()) {\n      errorMsg =\n        \"No taperestapi.sitename has been specified, the tape REST API is therefore disabled\";\n      std::string errorMsgLog =\n        std::string(\"msg=\\\"\") + errorMsg + \"\\\"\" + \" requestURL=\\\"\" + requestURL + \"\\\"\";\n      eos_static_warning(errorMsgLog.c_str());\n      return false;\n    }\n\n    if (mTapeRestApiConfig->getHostAlias().empty()) {\n      errorMsg =\n        \"No mgmofs.alias has been specified, the tape REST API is therefore disabled\";\n      std::string errorMsgLog =\n        std::string(\"msg=\\\"\") + errorMsg + \"\\\"\" + \" requestURL=\\\"\" + requestURL + \"\\\"\";\n      eos_static_warning(errorMsgLog.c_str());\n      return false;\n    }\n\n    if (!mTapeRestApiConfig->isActivated()) {\n      errorMsg = std::string(\"The tape REST API is not enabled, verify that the \\\"\") +\n                 rest::TAPE_REST_API_SWITCH_ON_OFF + \"\\\" space configuration is set to \\\"on\\\"\";\n      std::string errorMsgLog = std::string(\"msg=\\\"\") + errorMsg + \"\\\"\" +\n                                \" requestURL=\\\"\" + requestURL + \"\\\"\";\n      eos_static_warning(errorMsgLog.c_str());\n      return false;\n    }\n\n    if (!mTapeRestApiConfig->isTapeEnabled()) {\n      errorMsg =\n        \"The MGM tapeenabled flag has not been set or is set to false, the tape REST API is therefore disabled. Verify that the tapeenabled flag is set to true on the MGM configuration file.\";\n      std::string errorMsgLog =\n        std::string(\"msg=\\\"\") + errorMsg + \"\\\"\" + \" requestURL=\\\"\" + requestURL + \"\\\"\";\n      eos_static_warning(errorMsgLog.c_str());\n      return false;\n    }\n  }\n\n  return isRestRequest;\n}\n\ncommon::HttpResponse* TapeRestHandler::handleRequest(common::HttpRequest*\n    request, const common::VirtualIdentity* vid)\n{\n  //URL = /entrypoint/version/resource-name/...\n  std::string url = request->GetUrl();\n\n  if (gOFS != nullptr && !gOFS->mMaster->IsMaster()) {\n    return mTapeRestApiResponseFactory.InternalError(\"The tape REST API can only be called on a MASTER MGM\").getHttpResponse();\n  }\n\n  return HandleWithErrors(mTapeRestApiResponseFactory, [&]() {\n    return mRouter.dispatch(request, vid);\n  });\n}\n\nstd::unique_ptr<URLBuilder> TapeRestHandler::getAccessURLBuilder() const\n{\n  std::unique_ptr<URLBuilder> ret;\n  auto builder = URLBuilder::getInstance();\n  builder->setHttpsProtocol()->setHostname(\n    mTapeRestApiConfig->getHostAlias())->setPort(\n      mTapeRestApiConfig->getXrdHttpPort());\n  ret.reset(static_cast<URLBuilder*>(builder.release()));\n  return ret;\n}\n\nconst TapeWellKnownInfos* TapeRestHandler::getWellKnownInfos() const\n{\n  return mTapeWellKnownInfos.get();\n}\n\nEOSMGMRESTNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/rest-api/handler/tape/TapeRestHandler.hh",
    "content": "// ----------------------------------------------------------------------\n// File: TapeRestHandler.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_TAPERESTHANDLER_HH\n#define EOS_TAPERESTHANDLER_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/handler/RestHandler.hh\"\n#include \"mgm/http/rest-api/business/tape/ITapeRestApiBusiness.hh\"\n#include \"mgm/http/rest-api/router/Router.hh\"\n#include \"mgm/http/rest-api/action/Action.hh\"\n#include \"common/VirtualIdentity.hh\"\n#include \"mgm/http/rest-api/response/RestResponseFactory.hh\"\n#include \"mgm/http/rest-api/config/tape/TapeRestApiConfig.hh\"\n#include \"mgm/http/rest-api/utils/URLBuilder.hh\"\n#include \"mgm/http/rest-api/wellknown/tape/TapeWellKnownInfos.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * This class handles the HTTP requests that are\n * intended for the WLCG TAPE REST API\n */\nclass TapeRestHandler : public RestHandler\n{\npublic:\n  /**\n   * Constructor of the TapeRestHandler\n   * @param restApiUrl the base URL of the REST API without the instance name\n   */\n  TapeRestHandler(const TapeRestApiConfig* config);\n  /**\n   * Handles the user request\n   * @param request the user request\n   * @param vid the virtual identity of the user\n   * @return the HttpResponse to the user request\n   */\n  common::HttpResponse* handleRequest(common::HttpRequest* request,\n                                      const common::VirtualIdentity* vid) override;\n  /**\n   * Returns true if the request URL coming from the client matches the Tape REST API access URL but also:\n   * - the tape REST API is activated\n   * - a sitename has been configured in the MGM configuration file\n   * - the MGM configuration file contains the tapeenabled flag and it is set to true\n   * if the tape REST API is activated and if a sitename has been configured in the MGM configuration file\n   * @param requestURL the URL called by the client\n   * @param errorMsg a string allowing to indicate why the request will not trigger a tape REST API call\n   */\n  bool isRestRequest(const std::string& requestURL,\n                     std::string& errorMsg) const override;\n\n  /**\n   * Returns a builder object allowing to build URLs that are linked to the tape REST API\n   */\n  std::unique_ptr<URLBuilder> getAccessURLBuilder() const;\n\n  /**\n   * Returns some information useful for building the .well-known endpoint of this tape REST API\n   */\n  const TapeWellKnownInfos* getWellKnownInfos() const;\n\nprivate:\n\n  enum class ApiVersion {\n    V0Dot1,\n    V1\n  };\n\n  static constexpr ApiVersion DEFAULT_API_VERSION = ApiVersion::V1;\n\n  /**\n   * Convert the Tape REST API version to it's string representation\n   * @param apiVersion the version of the Tape REST API\n   */\n  std::string apiVersionToStr(ApiVersion apiVersion);\n\n  /**\n   * Initialize a version of the tape REST API\n   * @param apiVersion the version of the Tape REST API to initialize\n   */\n  void initialize(TapeRestHandler::ApiVersion apiVersion);\n\n  /**\n   * Initializes the STAGE controller for a specific version\n   * @param apiVersion the version to apply to this stage controller\n   * @param tapeRestApiBusiness the business layer of the tape REST API\n   * @param config the configuration of the tape REST API\n   * @return the StageController for a specific version\n   */\n  void initializeStageRoutes(TapeRestHandler::ApiVersion apiVersion,\n                             std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness);\n\n  /**\n   * Initializes the ARCHIVEINFO controller for a specific version\n   * @param apiVersion the version to apply to this ARCHIVEINFO controller\n   * @param tapeRestApiBusiness the business layer of the tape REST API\n   * @return the ArchiveInfoController for a specific version\n   */\n  void initializeArchiveinfoRoutes(TapeRestHandler::ApiVersion apiVersion,\n                                   std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness);\n  /**\n   * Initializes the RELEASE controller for a specific version\n   * @param apiVersion the version to apply to this RELEASE controller\n   * @param tapeRestApiBusiness the business layer of the tape REST API\n   * @return the ReleaseController for a specific version\n   */\n  void initializeReleaseRoutes(TapeRestHandler::ApiVersion apiVersion,\n                               std::shared_ptr<ITapeRestApiBusiness> tapeRestApiBusiness);\n\n  /**\n   * Initialize the well-known information\n   * that will later be used by the .well-known handler\n   */\n  void initializeTapeWellKnownInfos();\n\n  /**\n   * Adds the tape REST API endpoint to the well-known information\n   * that will later be used by the .well-known handler\n   * @param version\n   */\n  void addEndpointToWellKnown(const std::string& version);\n\n  /**\n   * Adds the tape REST API endpoint to the well-known information\n   * that will later be used by the .well-known handler\n   * @param version\n   * @param url\n   */\n  void addEndpointToWellKnown(const std::string& version, const std::string& url);\n\n  /**\n   * HttpResponse factory for the tape REST API\n   */\n  RestResponseFactory mTapeRestApiResponseFactory;\n  const TapeRestApiConfig* mTapeRestApiConfig;\n  std::unique_ptr<TapeWellKnownInfos> mTapeWellKnownInfos;\n  Router mRouter;\n  std::vector<std::unique_ptr<Action>> mActions;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_TAPERESTHANDLER_HH\n"
  },
  {
    "path": "mgm/http/rest-api/handler/wellknown/WellKnownHandler.cc",
    "content": "// ----------------------------------------------------------------------\n// File: RestHandler.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#include \"WellKnownHandler.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/http/rest-api/handler/tape/TapeRestHandler.hh\"\n#include \"mgm/http/rest-api/manager/RestApiManager.hh\"\n#include \"mgm/http/rest-api/model/wellknown/tape/GetTapeWellKnownModel.hh\"\n#include \"mgm/http/rest-api/json/tape/TapeJsonifiers.hh\"\n#include \"mgm/http/rest-api/response/RestResponseFactory.hh\"\n#include \"mgm/http/rest-api/exception/Exceptions.hh\"\n#include \"mgm/http/rest-api/response/ErrorHandling.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nWellKnownHandler::WellKnownHandler(const std::string& accessURL,\n                                   const RestApiManager* restApiManager) : RestHandler(accessURL),\n  mRestApiManager(restApiManager)\n{\n  initializeRoutes();\n}\n\ncommon::HttpResponse* WellKnownHandler::handleRequest(common::HttpRequest*\n    request, const common::VirtualIdentity* vid)\n{\n  std::string url = request->GetUrl();\n\n  try {\n    return mRouter.dispatch(request, vid);\n  } catch (const NotFoundException& ex) {\n    eos_static_info(ex.what());\n    return mWellknownResponseFactory.NotFound().getHttpResponse();\n  } catch (const MethodNotAllowedException& ex) {\n    eos_static_info(ex.what());\n    return mWellknownResponseFactory.MethodNotAllowed(ex.what()).getHttpResponse();\n  } catch (...) {\n    std::string errorMsg = \"Unknown exception occured\";\n    eos_static_err(errorMsg.c_str());\n    return mWellknownResponseFactory.InternalError(errorMsg).getHttpResponse();\n  }\n}\n\nvoid WellKnownHandler::initializeRoutes()\n{\n  const std::string accessURL = mEntryPointURL + \"wlcg-tape-rest-api\";\n  mRouter.add(accessURL, common::HttpHandler::Methods::GET,\n              [this](auto* request, auto* vid) -> common::HttpResponse* {\n                RestResponseFactory respFactory;\n                std::unique_ptr<RestHandler> restHandler = mRestApiManager->getRestHandler(\n                      mRestApiManager->getTapeRestApiConfig()->getAccessURL());\n                std::unique_ptr<TapeRestHandler> tapeRestHandler(static_cast<TapeRestHandler*>\n                    (restHandler.release()));\n                const TapeWellKnownInfos* tapeWellKnownInfos = tapeRestHandler->getWellKnownInfos();\n                std::string errorMsg;\n                if (!tapeRestHandler->isRestRequest(tapeRestHandler->getEntryPointURL(), errorMsg)) {\n                  return respFactory.InternalError(errorMsg).getHttpResponse();\n                }\n                std::shared_ptr<GetTapeWellKnownModel> model =\n                  std::make_shared<GetTapeWellKnownModel>(tapeWellKnownInfos);\n                model->setJsonifier(std::make_shared<GetTapeWellKnownModelJsonifier>());\n                return respFactory.Ok(model).getHttpResponse();\n              });\n}\n\nEOSMGMRESTNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/rest-api/handler/wellknown/WellKnownHandler.hh",
    "content": "// ----------------------------------------------------------------------\n// File: WellKnownHandler.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_WELLKNOWNHANDLER_HH\n#define EOS_WELLKNOWNHANDLER_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/handler/RestHandler.hh\"\n#include \"mgm/http/rest-api/response/RestResponseFactory.hh\"\n#include \"mgm/http/rest-api/router/Router.hh\"\n#include \"mgm/http/rest-api/action/Action.hh\"\n#include \"mgm/http/rest-api/handler/tape/TapeRestHandler.hh\"\n#include <memory>\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nclass RestApiManager;\n\nclass WellKnownHandler : public RestHandler\n{\npublic:\n  WellKnownHandler(const std::string& accessURL,\n                   const RestApiManager* restApiManager);\n  /**\n   * Handles the user request\n   * @param request the user request\n   * @param vid the virtual identity of the user\n   * @return the HttpResponse to the user request\n   */\n  common::HttpResponse* handleRequest(common::HttpRequest* request,\n                                      const common::VirtualIdentity* vid) override;\n\nprivate:\n  void initializeRoutes();\n  const RestApiManager* mRestApiManager;\n  RestResponseFactory mWellknownResponseFactory;\n  Router mRouter;\n  std::vector<std::unique_ptr<Action>> mActions;\n};\n\nEOSMGMRESTNAMESPACE_END\n#endif // EOS_WELLKNOWNHANDLER_HH\n"
  },
  {
    "path": "mgm/http/rest-api/json/builder/JsonModelBuilder.hh",
    "content": "// ----------------------------------------------------------------------\n// File: JsonModelBuilder.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_JSONMODELBUILDER_HH\n#define EOS_JSONMODELBUILDER_HH\n\n#include \"mgm/Namespace.hh\"\n#include <memory>\n#include <string>\n#include <vector>\n#include <utility>\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * This class allows to build a Model object\n * from a json string.\n * @tparam Model the object to create from the JSON string\n */\ntemplate<typename Model>\nclass JsonModelBuilder\n{\npublic:\n  /**\n   * Returns a unique_ptr to the Model object created from the JSON string passed\n   * in parameter\n   * @param json the JSON string from which the Model object should be created from\n   * @return the unique_ptr to the Model corresponding to the JSON string passed\n   * in parameter\n   */\n  virtual std::unique_ptr<Model> buildFromJson(const std::string& json) = 0;\n  virtual ~JsonModelBuilder() {}\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_JSONMODELBUILDER_HH\n"
  },
  {
    "path": "mgm/http/rest-api/json/builder/ValidationError.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ValidationError.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_VALIDATIONERROR_HH\n#define EOS_VALIDATIONERROR_HH\n\n#include \"mgm/Namespace.hh\"\n#include <string>\n#include <vector>\n#include <memory>\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nclass ValidationError\n{\npublic:\n  ValidationError(const std::string& fieldName,\n                  const std::string& reason): mFieldName(fieldName), mReason(reason) {}\n  typedef std::vector<std::unique_ptr<ValidationError>> List;\n  inline const std::string& getFieldName() const\n  {\n    return mFieldName;\n  }\n  inline const std::string& getReason() const\n  {\n    return mReason;\n  }\nprivate:\n  std::string mFieldName;\n  std::string mReason;\n};\n\nclass ValidationErrors\n{\npublic:\n  typedef std::vector<std::unique_ptr<ValidationError>> ErrorVector;\n  ValidationErrors()\n  {\n    mErrors.reset(new ErrorVector());\n  }\n  inline void addError(const std::string& fieldName, const std::string& reason)\n  {\n    mErrors->push_back(std::make_unique<ValidationError>(fieldName, reason));\n  }\n  inline const ErrorVector* getErrors() const\n  {\n    return mErrors.get();\n  }\n  inline bool hasAnyError() const\n  {\n    return !mErrors->empty();\n  }\nprivate:\n  std::unique_ptr<ErrorVector> mErrors;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_VALIDATIONERROR_HH\n"
  },
  {
    "path": "mgm/http/rest-api/json/builder/jsoncpp/JsonCppModelBuilder.hh",
    "content": "// ----------------------------------------------------------------------\n// File: JsonCppModelBuilder.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_JSONCPPMODELBUILDER_HH\n#define EOS_JSONCPPMODELBUILDER_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/json/builder/JsonModelBuilder.hh\"\n#include <json/json.h>\n#include <sstream>\n#include \"mgm/http/rest-api/exception/Exceptions.hh\"\n#include \"mgm/http/rest-api/json/builder/ValidationError.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * This class holds JsonCPP-related operations\n * @tparam Model\n */\ntemplate<typename Model>\nclass JsonCppModelBuilder : public JsonModelBuilder<Model>\n{\npublic:\n  virtual std::unique_ptr<Model> buildFromJson(const std::string& json) = 0;\nprotected:\n  /**\n  * Parses the json string passed in parameter and\n  * create JsonCpp-related object out of it\n  * @param json the string representing the object\n  * @param root the JsonCpp root object\n  * @throws JsonValidationException if the parsing could not be done\n   */\n  void parseJson(const std::string& json, Json::Value& root) const\n  {\n    Json::Reader reader;\n    bool parsingSuccessful = reader.parse(json, root);\n\n    if (!parsingSuccessful) {\n      std::ostringstream oss;\n      oss << \"Unable to create a JSON object from the json string provided. json=\" <<\n          json;\n      throw JsonValidationException(oss.str());\n    }\n  }\n  virtual ~JsonCppModelBuilder() {}\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_JSONCPPMODELBUILDER_HH\n"
  },
  {
    "path": "mgm/http/rest-api/json/builder/jsoncpp/JsonCppValidator.hh",
    "content": "// ----------------------------------------------------------------------\n// File: JsonCppValidator.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_JSONCPPVALIDATOR_HH\n#define EOS_JSONCPPVALIDATOR_HH\n\n#include \"mgm/Namespace.hh\"\n#include <json/json.h>\n#include <memory>\n#include \"common/exception/Exception.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * Global exception class for JSON validation exception\n */\nclass ValidatorException : public common::Exception\n{\npublic:\n  ValidatorException(const std::string& msg): common::Exception(msg) {}\n};\n\n/**\n * This abstract class should be inherited in order\n * to validate a specific JSON CPP Value\n */\nclass JsonCppValidator\n{\npublic:\n  virtual void validate(const Json::Value& value) = 0;\n  virtual ~JsonCppValidator() {}\n};\n\nclass NonEmptyArrayValidator : public JsonCppValidator\n{\npublic:\n  virtual void validate(const Json::Value& value) override\n  {\n    if (value.empty() || !value.isArray()) {\n      throw ValidatorException(\"Field does not exist or is not a valid non-empty array.\");\n    }\n  }\n};\n\nclass StringValidator : public JsonCppValidator\n{\npublic:\n  virtual void validate(const Json::Value& value) override\n  {\n    if (!value.isString()) {\n      throw ValidatorException(\"Field is not a valid string.\");\n    }\n  }\n};\n\nclass ObjectValidator : public JsonCppValidator\n{\npublic:\n  virtual void validate(const Json::Value& value) override\n  {\n    if (!value.isObject()) {\n      throw ValidatorException(\"Field is not an object.\");\n    }\n  }\n};\n\n/**\n * Factory of validators\n */\nclass JsonCppValidatorFactory\n{\npublic:\n  std::unique_ptr<JsonCppValidator> getNonEmptyArrayValidator()\n  {\n    return std::make_unique<NonEmptyArrayValidator>();\n  }\n  std::unique_ptr<JsonCppValidator> getStringValidator()\n  {\n    return std::make_unique<StringValidator>();\n  }\n  std::unique_ptr<JsonCppValidator> getNotNullValidator()\n  {\n    return std::make_unique<StringValidator>();\n  }\n  std::unique_ptr<JsonCppValidator> getObjectValidator()\n  {\n    return std::make_unique<ObjectValidator>();\n  }\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_JSONCPPVALIDATOR_HH\n"
  },
  {
    "path": "mgm/http/rest-api/json/tape/TapeJsonifiers.hh",
    "content": "// ----------------------------------------------------------------------\n// File: TapeJsonifiers.hh\n// Author: Consolidated tape REST API jsonifiers\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n ************************************************************************/\n\n#ifndef EOS_TAPE_JSONIFIERS_HH\n#define EOS_TAPE_JSONIFIERS_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/json/tape/TapeRestApiJsonifier.hh\"\n#include \"mgm/http/rest-api/model/tape/common/ErrorModel.hh\"\n#include \"mgm/http/rest-api/model/tape/stage/CreatedStageBulkRequestResponseModel.hh\"\n#include \"mgm/http/rest-api/model/tape/stage/GetStageBulkRequestResponseModel.hh\"\n#include \"mgm/http/rest-api/model/tape/archiveinfo/GetArchiveInfoResponseModel.hh\"\n#include \"mgm/http/rest-api/model/wellknown/tape/GetTapeWellKnownModel.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n// ErrorModel jsonifier\nclass ErrorModelJsonifier : public TapeRestApiJsonifier<ErrorModel>\n{\npublic:\n  void jsonify(const ErrorModel* obj, std::stringstream& ss) override\n  {\n    ss << \"{\\n\";\n    ss << \"\\\"title\\\": \\\"\" << obj->getTitle() << \"\\\",\\n\";\n    ss << \"\\\"status\\\": \" << obj->getStatus();\n    if (obj->getDetail()) {\n      ss << \",\\n\\\"detail\\\": \\\"\" << *obj->getDetail() << \"\\\"\";\n    }\n    if (obj->getType()) {\n      ss << \",\\n\\\"type\\\": \\\"\" << *obj->getType() << \"\\\"\";\n    }\n    ss << \"\\n}\";\n  }\n};\n\n// CreatedStageBulkRequest jsonifier\nclass CreatedStageBulkRequestJsonifier : public TapeRestApiJsonifier<CreatedStageBulkRequestResponseModel>\n{\npublic:\n  void jsonify(const CreatedStageBulkRequestResponseModel* obj, std::stringstream& ss) override\n  {\n    ss << \"{\\n\\\"request_id\\\": \\\"\" << obj->getRequestId() << \"\\\"\\n}\";\n  }\n};\n\n// GetStageBulkRequest jsonifier\nclass GetStageBulkRequestJsonifier : public TapeRestApiJsonifier<GetStageBulkRequestResponseModel>\n{\npublic:\n  void jsonify(const GetStageBulkRequestResponseModel* obj, std::stringstream& ss) override\n  {\n    ss << \"{\\n\";\n    ss << \"\\\"id\\\": \\\"\" << obj->getId() << \"\\\",\\n\";\n    ss << \"\\\"creation_time\\\": \" << static_cast<long long>(obj->getCreationTime()) << \",\\n\";\n    ss << \"\\\"files\\\": [\\n\";\n    const auto& files = obj->getFiles();\n    for (size_t i = 0; i < files.size(); ++i) {\n      const auto& f = files[i];\n      ss << \"  {\\n\";\n      ss << \"    \\\"path\\\": \\\"\" << f->mPath << \"\\\",\\n\";\n      ss << \"    \\\"on_disk\\\": \" << (f->mOnDisk ? \"true\" : \"false\");\n      if (!f->mError.empty()) {\n        ss << \",\\n    \\\"error\\\": \\\"\" << f->mError << \"\\\"\";\n      }\n      ss << \"\\n  }\";\n      if (i + 1 < files.size()) ss << \",\";\n      ss << \"\\n\";\n    }\n    ss << \"]\\n}\";\n  }\n};\n\n// GetArchiveInfoResponse jsonifier\nclass GetArchiveInfoResponseJsonifier : public TapeRestApiJsonifier<GetArchiveInfoResponseModel>\n{\npublic:\n  void jsonify(const GetArchiveInfoResponseModel* obj, std::stringstream& ss) override\n  {\n    auto qpr = obj->getQueryPrepareResponse();\n    ss << \"{\\n\";\n    if (qpr) {\n      ss << \"  \\\"request_id\\\": \\\"\" << qpr->request_id << \"\\\",\\n\";\n      ss << \"  \\\"responses\\\": [\\n\";\n      for (size_t i = 0; i < qpr->responses.size(); ++i) {\n        const auto& r = qpr->responses[i];\n        ss << \"    {\\n\";\n        ss << \"      \\\"path\\\": \\\"\" << r.path << \"\\\",\\n\";\n        ss << \"      \\\"path_exists\\\": \" << (r.is_exists ? \"true\" : \"false\") << \",\\n\";\n        ss << \"      \\\"on_tape\\\": \" << (r.is_on_tape ? \"true\" : \"false\") << \",\\n\";\n        ss << \"      \\\"online\\\": \" << (r.is_online ? \"true\" : \"false\") << \",\\n\";\n        ss << \"      \\\"requested\\\": \" << (r.is_requested ? \"true\" : \"false\") << \",\\n\";\n        ss << \"      \\\"has_reqid\\\": \" << (r.is_reqid_present ? \"true\" : \"false\") << \",\\n\";\n        ss << \"      \\\"req_time\\\": \\\"\" << r.request_time << \"\\\",\\n\";\n        ss << \"      \\\"error_text\\\": \\\"\" << r.error_text << \"\\\"\\n\";\n        ss << \"    }\";\n        if (i + 1 < qpr->responses.size()) ss << \",\";\n        ss << \"\\n\";\n      }\n      ss << \"  ]\\n\";\n    }\n    ss << \"}\";\n  }\n};\n\n// GetTapeWellKnownModel jsonifier\nclass GetTapeWellKnownModelJsonifier : public TapeRestApiJsonifier<GetTapeWellKnownModel>\n{\npublic:\n  void jsonify(const GetTapeWellKnownModel* obj, std::stringstream& ss) override\n  {\n    ss << \"{\\n  \\\"versions\\\": [\\n\";\n    const TapeWellKnownInfos* infos = obj->getTapeWellKnownInfos();\n    const auto& endpoints = infos->getEndpoints();\n    for (size_t i = 0; i < endpoints.size(); ++i) {\n      const auto& ep = endpoints[i];\n      ss << \"    { \\\"version\\\": \\\"\" << ep->getVersion() << \"\\\", \\\"url\\\": \\\"\" << ep->getUri() << \"\\\" }\";\n      if (i + 1 < endpoints.size()) ss << \",\";\n      ss << \"\\n\";\n    }\n    ss << \"  ]\\n}\";\n  }\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_TAPE_JSONIFIERS_HH\n\n\n"
  },
  {
    "path": "mgm/http/rest-api/json/tape/TapeModelBuilders.hh",
    "content": "// ----------------------------------------------------------------------\n// File: TapeModelBuilders.hh\n// Author: Consolidated tape REST API model builders and validators\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n ************************************************************************/\n\n#ifndef EOS_TAPE_MODEL_BUILDERS_HH\n#define EOS_TAPE_MODEL_BUILDERS_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/json/builder/JsonModelBuilder.hh\"\n#include \"mgm/http/rest-api/json/builder/jsoncpp/JsonCppModelBuilder.hh\"\n#include \"mgm/http/rest-api/json/builder/jsoncpp/JsonCppValidator.hh\"\n#include \"mgm/http/rest-api/json/tape/model-builders/validators/TapeJsonCppValidator.hh\"\n#include \"mgm/http/rest-api/model/tape/stage/PathsModel.hh\"\n#include \"mgm/http/rest-api/model/tape/stage/CreateStageBulkRequestModel.hh\"\n#include <string>\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nclass PathsModelBuilder : public JsonCppModelBuilder<PathsModel>\n{\npublic:\n  std::unique_ptr<PathsModel> buildFromJson(const std::string& json) override\n  {\n    Json::Value root;\n    parseJson(json, root);\n\n    // Accept either {\"files\":[{\"path\":\"...\"}, ...]} or {\"paths\":[\"...\", ...]}\n    const char* filesKey = \"files\";\n    const char* pathKey = \"path\";\n    const char* pathsKey = \"paths\";\n\n    auto model = std::make_unique<PathsModel>();\n\n    if (root.isMember(filesKey)) {\n      const Json::Value& files = root[filesKey];\n      if (!files.isArray() || files.empty()) {\n        throw JsonValidationException(\"'files' must be a non-empty array\");\n      }\n      for (const auto& entry : files) {\n        if (!entry.isObject() || !entry.isMember(pathKey) || !entry[pathKey].isString()) {\n          throw JsonValidationException(\"Each file entry must be an object with a string 'path'\");\n        }\n        model->addFile(entry[pathKey].asString());\n      }\n      return model;\n    }\n\n    if (root.isMember(pathsKey)) {\n      const Json::Value& paths = root[pathsKey];\n      if (!paths.isArray() || paths.empty()) {\n        throw JsonValidationException(\"'paths' must be a non-empty array\");\n      }\n      for (const auto& p : paths) {\n        if (!p.isString()) {\n          throw JsonValidationException(\"Each path must be a string\");\n        }\n        model->addFile(p.asString());\n      }\n      return model;\n    }\n\n    throw JsonValidationException(\"Expected 'files' or 'paths' field in request body\");\n  }\n};\n\nclass CreateStageRequestModelBuilder : public JsonCppModelBuilder<CreateStageBulkRequestModel>\n{\npublic:\n  // JSON field keys used by tests and builder\n  static inline const std::string FILES_KEY_NAME = \"files\";\n  static inline const std::string PATH_KEY_NAME = \"path\";\n  static inline const std::string TARGETED_METADATA_KEY_NAME = \"targeted_metadata\";\n  explicit CreateStageRequestModelBuilder(const std::string& restApiEndpointId)\n    : mRestApiEndpointId(restApiEndpointId) {}\n  std::unique_ptr<CreateStageBulkRequestModel> buildFromJson(const std::string& json) override\n  {\n    Json::Value root;\n    parseJson(json, root);\n\n    if (!root.isMember(FILES_KEY_NAME) || !root[FILES_KEY_NAME].isArray()) {\n      throw JsonValidationException(\"Missing or invalid 'files' array\");\n    }\n    const Json::Value& files = root[FILES_KEY_NAME];\n    if (files.empty()) {\n      throw JsonValidationException(\"'files' must be a non-empty array\");\n    }\n\n    auto model = std::make_unique<CreateStageBulkRequestModel>();\n    for (const auto& f : files) {\n      if (!f.isObject()) {\n        throw JsonValidationException(\"file entry must be an object\");\n      }\n      if (!f.isMember(PATH_KEY_NAME) || !f[PATH_KEY_NAME].isString()) {\n        throw JsonValidationException(\"file entry must contain a string 'path'\");\n      }\n\n      std::string opaque;\n      if (f.isMember(TARGETED_METADATA_KEY_NAME) && f[TARGETED_METADATA_KEY_NAME].isObject()) {\n        const Json::Value& tmd = f[TARGETED_METADATA_KEY_NAME];\n        std::string activity;\n        // prefer endpoint-specific over default\n        if (tmd.isMember(mRestApiEndpointId) && tmd[mRestApiEndpointId].isObject()) {\n          const Json::Value& ep = tmd[mRestApiEndpointId];\n          if (ep.isMember(\"activity\") && ep[\"activity\"].isString()) {\n            activity = ep[\"activity\"].asString();\n          }\n        }\n        if (activity.empty() && tmd.isMember(\"default\") && tmd[\"default\"].isObject()) {\n          const Json::Value& def = tmd[\"default\"];\n          if (def.isMember(\"activity\") && def[\"activity\"].isString()) {\n            activity = def[\"activity\"].asString();\n          }\n        }\n        if (!activity.empty()) {\n          opaque = std::string(\"activity=\") + activity;\n        }\n      }\n\n      model->addFile(f[PATH_KEY_NAME].asString(), opaque);\n    }\n\n    return model;\n  }\nprivate:\n  std::string mRestApiEndpointId;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_TAPE_MODEL_BUILDERS_HH\n\n\n"
  },
  {
    "path": "mgm/http/rest-api/json/tape/TapeRestApiJsonifier.hh",
    "content": "// ----------------------------------------------------------------------\n// File: TapeRestApiJsonifier.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_TAPERESTAPIJSONIFIER_HH\n#define EOS_TAPERESTAPIJSONIFIER_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"common/json/Jsonifier.hh\"\n#include \"mgm/http/rest-api/model/tape/common/ErrorModel.hh\"\n#include <sstream>\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * Specific TapeRestApi JSONification object\n * @tparam Obj\n */\ntemplate<typename Obj>\nclass TapeRestApiJsonifier : public virtual common::Jsonifier<Obj>\n{\npublic:\n  virtual void jsonify(const Obj* obj, std::stringstream& ss) = 0;\n  virtual ~TapeRestApiJsonifier() {}\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_TAPERESTAPIJSONIFIER_HH\n"
  },
  {
    "path": "mgm/http/rest-api/json/tape/model-builders/validators/TapeJsonCppValidator.hh",
    "content": "// ----------------------------------------------------------------------\n// File: TapeJsonCppValidator.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#ifndef EOS_TAPEJSONCPPVALIDATOR_HH\n#define EOS_TAPEJSONCPPVALIDATOR_HH\n\n#include \"common/Path.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/json/builder/jsoncpp/JsonCppValidator.hh\"\n#include \"common/StringUtils.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nclass PathValidator : public JsonCppValidator\n{\npublic:\n  void validate(const Json::Value& value) override\n  {\n    if (value.empty() || !value.isString() || value.asString().empty() ||\n        value.isConvertibleTo(Json::ValueType::intValue)) {\n      throw ValidatorException(\"The value must be a valid non-empty string\");\n    }\n  }\n};\n\nclass TapeJsonCppValidatorFactory : public JsonCppValidatorFactory\n{\npublic:\n  std::unique_ptr<JsonCppValidator> getPathValidator()\n  {\n    return std::make_unique<PathValidator>();\n  }\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_TAPEJSONCPPVALIDATOR_HH\n"
  },
  {
    "path": "mgm/http/rest-api/manager/RestApiManager.cc",
    "content": "// ----------------------------------------------------------------------\n// File: RestApiManager.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"RestApiManager.hh\"\n#include \"mgm/http/rest-api/utils/URLParser.hh\"\n#include \"mgm/http/rest-api/handler/tape/TapeRestHandler.hh\"\n#include \"mgm/http/rest-api/handler/wellknown/WellKnownHandler.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nRestApiManager::RestApiManager()\n{\n  mTapeRestApiConfig = std::make_unique<TapeRestApiConfig>();\n  mMapAccessURLRestHandlerCreator[mTapeRestApiConfig->getAccessURL()] = [this]() {\n    return std::unique_ptr<rest::RestHandler>(new rest::TapeRestHandler(mTapeRestApiConfig.get()));\n  };\n  mMapAccessURLRestHandlerCreator[getWellKnownAccessURL()] = [this]() {\n    return std::unique_ptr<rest::RestHandler>(new rest::WellKnownHandler(getWellKnownAccessURL(), this));\n  };\n}\n\nbool RestApiManager::isRestRequest(const std::string& requestURL) const\n{\n  const auto& restHandler = getRestHandler(requestURL);\n  //We do not need an error message in the caller of this method.\n  //If we need this one day, one may need to add this parameter to RestApiManager::isRestRequest()\n  std::string errorMsg;\n  return (restHandler != nullptr &&\n          restHandler->isRestRequest(requestURL, errorMsg));\n}\n\nTapeRestApiConfig* RestApiManager::getTapeRestApiConfig() const\n{\n  return mTapeRestApiConfig.get();\n}\n\nstd::unique_ptr<rest::RestHandler> RestApiManager::getRestHandler(\n  const std::string& requestURL) const\n{\n  const auto& restHandlerFactory = std::find_if(\n                                     mMapAccessURLRestHandlerCreator.begin(),\n  mMapAccessURLRestHandlerCreator.end(), [&requestURL](const auto & kv) {\n    URLParser parser(requestURL);\n    return parser.startsBy(kv.first);\n  });\n\n  if (restHandlerFactory != mMapAccessURLRestHandlerCreator.end()) {\n    return restHandlerFactory->second();\n  }\n\n  return nullptr;\n}\n\nconst std::string RestApiManager::getWellKnownAccessURL() const\n{\n  return \"/.well-known/\";\n}\n\nEOSMGMRESTNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/rest-api/manager/RestApiManager.hh",
    "content": "// ----------------------------------------------------------------------\n// File: RestApiManager.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_RESTAPIMANAGER_HH\n#define EOS_RESTAPIMANAGER_HH\n\n#include <string>\n#include <memory>\n#include <map>\n#include <functional>\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/handler/RestHandler.hh\"\n#include \"mgm/http/rest-api/config/tape/TapeRestApiConfig.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * This class is responsible for managing all the REST API\n * that the EOS instance is running\n */\nclass RestApiManager\n{\npublic:\n  RestApiManager();\n  /**\n   * Returns true if the request URL maps to\n   * a specific REST Handler and if the REST handler\n   * accepts requests.\n   * @param requestURL the URL provided by the client\n   */\n  virtual bool isRestRequest(const std::string& requestURL) const;\n  /**\n   * Returns the tape REST API configuration object hold by this\n   * manager\n   * Use this method to access the configuration but also to modify its content\n   * @return a pointer to the tape REST API configuration object\n   */\n  virtual TapeRestApiConfig* getTapeRestApiConfig() const;\n  /**\n   * Instanciate a RestHandler depending on the request URL provided\n   * @param requestURL the URL of the client's request\n   * @return a unique_ptr pointing to an instaciated RestHandler, nullptr if no RestHandler\n   * matches the requestURL\n   */\n  virtual std::unique_ptr<rest::RestHandler> getRestHandler(\n    const std::string& requestURL) const;\n  /**\n   * @return the .well-known endpoint access URL\n   */\n  virtual const std::string getWellKnownAccessURL() const;\n\n  virtual ~RestApiManager() {}\n\nprivate:\n  //The Tape REST API configuration object\n  std::unique_ptr<TapeRestApiConfig> mTapeRestApiConfig;\n  //A map of <URL, RestHandlerCreator>\n  std::map<std::string, std::function<std::unique_ptr<rest::RestHandler>()>>\n      mMapAccessURLRestHandlerCreator;\n  //URL of the wellknown access URL\n  std::string mWellKnownAccessURL;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_RESTAPIMANAGER_HH\n"
  },
  {
    "path": "mgm/http/rest-api/model/tape/archiveinfo/GetArchiveInfoResponseModel.hh",
    "content": "// ----------------------------------------------------------------------\n// File: GetArchiveInfoResponseModel.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_GETARCHIVEINFORESPONSEMODEL_HH\n#define EOS_GETARCHIVEINFORESPONSEMODEL_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"common/json/Jsonifiable.hh\"\n#include \"mgm/bulk-request/response/QueryPrepareResponse.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * This Model represents the object that will be returned to the client\n * for any ArchiveInfo request\n */\nclass GetArchiveInfoResponseModel\n  : public common::Jsonifiable<GetArchiveInfoResponseModel>\n{\npublic:\n  GetArchiveInfoResponseModel(std::shared_ptr<bulk::QueryPrepareResponse>\n                              queryPrepareResponse): mQueryPrepareResponse(queryPrepareResponse) {}\n  inline std::shared_ptr<bulk::QueryPrepareResponse> getQueryPrepareResponse() const { return mQueryPrepareResponse; }\nprivate:\n  std::shared_ptr<bulk::QueryPrepareResponse> mQueryPrepareResponse;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_GETARCHIVEINFORESPONSEMODEL_HH\n"
  },
  {
    "path": "mgm/http/rest-api/model/tape/common/ErrorModel.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ErrorModel.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"ErrorModel.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nErrorModel::ErrorModel() {}\n\nErrorModel::ErrorModel(const std::string& title, const uint32_t status,\n                       const std::optional<std::string>& detail): mTitle(title), mStatus(status),\n  mDetail(detail)\n{}\n\nErrorModel::ErrorModel(const std::string& title,\n                       const uint32_t status): mTitle(title), mStatus(status)\n{}\n\nvoid ErrorModel::setType(const std::string& type)\n{\n  mType = type;\n}\n\nvoid ErrorModel::setTitle(const std::string& title)\n{\n  mTitle = title;\n}\n\nvoid ErrorModel::setStatus(const uint32_t status)\n{\n  mStatus = status;\n}\n\nvoid ErrorModel::setDetail(const std::string& detail)\n{\n  mDetail = detail;\n}\n\nstd::optional<std::string> ErrorModel::getType() const\n{\n  return mType;\n}\n\nstd::string ErrorModel::getTitle() const\n{\n  return mTitle;\n}\n\nuint32_t ErrorModel::getStatus() const\n{\n  return mStatus;\n}\n\nstd::optional<std::string> ErrorModel::getDetail() const\n{\n  return mDetail;\n}\n\nEOSMGMRESTNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/rest-api/model/tape/common/ErrorModel.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ErrorModel.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_ERRORMODEL_HH\n#define EOS_ERRORMODEL_HH\n\n#include \"mgm/Namespace.hh\"\n#include <string>\n#include <cstdint>\n#include <optional>\n#include <sstream>\n#include <memory>\n#include \"common/json/Jsonifiable.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * This class represents the Error object that will be returned to the client\n * if an error occured. It follows the RFC 7807 https://datatracker.ietf.org/doc/html/rfc7807\n */\nclass ErrorModel : public common::Jsonifiable<ErrorModel>\n{\npublic:\n  ErrorModel();\n  ErrorModel(const std::string& title, const uint32_t status,\n             const std::optional<std::string>& detail);\n  ErrorModel(const std::string& title, const uint32_t status);\n  void setType(const std::string& type);\n  void setTitle(const std::string& title);\n  void setStatus(const uint32_t status);\n  void setDetail(const std::string& detail);\n  std::optional<std::string> getType() const;\n  std::string getTitle() const;\n  uint32_t getStatus() const;\n  std::optional<std::string> getDetail() const;\n  virtual ~ErrorModel() {}\nprivate:\n  /**\n   * A URI reference that identifies the problem type This specification encourages that, when dereferenced,\n   * it provide human-readable documentation for the problem type (e.g., using HTML).\n   */\n  std::optional<std::string> mType;\n  /**\n   * A short, human-readable summary of the problem type\n   */\n  std::string mTitle;\n  /**\n   * The HTTP status code generated by the origin server for this occurrence of the problem.\n   */\n  uint32_t mStatus;\n  /**\n   * A human-readable explanation specific to this occurrence of the problem.\n   */\n  std::optional<std::string> mDetail;\n\n\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_ERRORMODEL_HH\n"
  },
  {
    "path": "mgm/http/rest-api/model/tape/common/FilesContainer.hh",
    "content": "// ----------------------------------------------------------------------\n// File: FilesContainer.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_FILESCONTAINER_HH\n#define EOS_FILESCONTAINER_HH\n\n#include \"mgm/Namespace.hh\"\n#include <vector>\n#include <string>\n#include \"mgm/http/rest-api/utils/URLParser.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * This class is convenient for passing a list of path\n * and opaque infos to the PrepareManager::prepare()/queryPrepare()\n */\nclass FilesContainer\n{\npublic:\n  FilesContainer() = default;\n  inline void addFile(const std::string& path) { addFile(path, \"\"); }\n  inline void addFile(const std::string& path, const std::string& opaqueInfo)\n  {\n    mPaths.push_back(path);\n    std::string& insertedPath = mPaths.back();\n    URLParser::removeDuplicateSlashes(insertedPath);\n    mOpaqueInfos.push_back(opaqueInfo);\n  }\n  inline const std::vector<std::string>& getPaths() const { return mPaths; }\n  inline const std::vector<std::string>& getOpaqueInfos() const { return mOpaqueInfos; }\nprivate:\n  std::vector<std::string> mPaths;\n  std::vector<std::string> mOpaqueInfos;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_FILESCONTAINER_HH\n"
  },
  {
    "path": "mgm/http/rest-api/model/tape/stage/CreateStageBulkRequestModel.hh",
    "content": "// ----------------------------------------------------------------------\n// File: CreateStageBulkRequestModel.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_CREATESTAGEBULKREQUESTMODEL_HH\n#define EOS_CREATESTAGEBULKREQUESTMODEL_HH\n\n#include \"mgm/Namespace.hh\"\n#include <string>\n#include <vector>\n#include \"mgm/http/rest-api/model/tape/common/FilesContainer.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * This object represents a client's request\n * to create a stage bulk-request\n */\nclass CreateStageBulkRequestModel\n{\npublic:\n  CreateStageBulkRequestModel() {}\n  inline void addFile(const std::string& path, const std::string& opaqueInfos)\n  {\n    mFilesContainer.addFile(path, opaqueInfos);\n  }\n  inline const FilesContainer& getFiles() const { return mFilesContainer; }\nprivate:\n  FilesContainer mFilesContainer;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_CREATESTAGEBULKREQUESTMODEL_HH\n"
  },
  {
    "path": "mgm/http/rest-api/model/tape/stage/CreatedStageBulkRequestResponseModel.hh",
    "content": "// ----------------------------------------------------------------------\n// File: CreatedStageBulkRequestResponseModel.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_CREATEDSTAGEBULKREQUESTRESPONSEMODEL_HH\n#define EOS_CREATEDSTAGEBULKREQUESTRESPONSEMODEL_HH\n\n#include \"mgm/Namespace.hh\"\n#include <cstdint>\n#include <string>\n#include \"mgm/bulk-request/BulkRequest.hh\"\n#include \"common/json/Jsonifiable.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * This object is the model that will be returned to the client\n * after a STAGE bulk-request submission via the tape REST API\n */\nclass CreatedStageBulkRequestResponseModel : public\n  common::Jsonifiable<CreatedStageBulkRequestResponseModel>\n{\npublic:\n  inline CreatedStageBulkRequestResponseModel(const std::string& requestId)\n    : Jsonifiable(), mRequestId(requestId) {}\n  inline const std::string& getRequestId() const { return mRequestId; }\nprivate:\n  const std::string mRequestId;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_CREATEDSTAGEBULKREQUESTRESPONSEMODEL_HH\n"
  },
  {
    "path": "mgm/http/rest-api/model/tape/stage/GetStageBulkRequestResponseModel.hh",
    "content": "// ----------------------------------------------------------------------\n// File: GetStageBulkRequestResponseModel.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_GETSTAGEBULKREQUESTRESPONSEMODEL_HH\n#define EOS_GETSTAGEBULKREQUESTRESPONSEMODEL_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"common/json/Jsonifiable.hh\"\n#include \"mgm/bulk-request/response/QueryPrepareResponse.hh\"\n#include \"mgm/bulk-request/BulkRequest.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * This class represents the object that will be returned to the client\n * that wants to track the progression of a previously submitted STAGE bulk-request\n */\nclass GetStageBulkRequestResponseModel : public\n  common::Jsonifiable<GetStageBulkRequestResponseModel>\n{\npublic:\n  class File\n  {\n  public:\n    std::string mPath;\n    std::string mError;\n    bool mOnDisk;\n  };\n  GetStageBulkRequestResponseModel() {}\n  inline void addFile(std::unique_ptr<File>&& file) { mFiles.emplace_back(std::move(file)); }\n  inline const std::vector<std::unique_ptr<File>>& getFiles() const { return mFiles; }\n  inline time_t getCreationTime() const { return mCreationTime; }\n  inline std::string getId() const { return mId; }\n  inline void setCreationTime(const time_t& creationTime) { mCreationTime = creationTime; }\n  inline void setId(const std::string& id) { mId = id; }\nprivate:\n  std::vector<std::unique_ptr<File>> mFiles;\n  time_t mCreationTime;\n  std::string mId;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_GETSTAGEBULKREQUESTRESPONSEMODEL_HH\n"
  },
  {
    "path": "mgm/http/rest-api/model/tape/stage/PathsModel.hh",
    "content": "// ----------------------------------------------------------------------\n// File: PathsModel.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_PATHSMODEL_HH\n#define EOS_PATHSMODEL_HH\n\n#include \"mgm/Namespace.hh\"\n#include <vector>\n#include <string>\n#include \"mgm/http/rest-api/model/tape/common/FilesContainer.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * This object represents an array of paths.\n * The client would use this object for archiveinfo or release.\n */\nclass PathsModel\n{\npublic:\n  PathsModel() = default;\n  inline void addFile(const std::string& path) { mFilesContainer.addFile(path); }\n  inline const FilesContainer& getFiles() const { return mFilesContainer; }\nprivate:\n  FilesContainer mFilesContainer;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_PATHSMODEL_HH\n"
  },
  {
    "path": "mgm/http/rest-api/model/wellknown/tape/GetTapeWellKnownModel.hh",
    "content": "// ----------------------------------------------------------------------\n// File: GetTapeWellKnownModel.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_GETTAPEWELLKNOWNMODEL_HH\n#define EOS_GETTAPEWELLKNOWNMODEL_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"common/json/Jsonifiable.hh\"\n#include \"mgm/http/rest-api/wellknown/tape/TapeWellKnownInfos.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * Model class representing the response that will be sent to the user\n * after a call to the tape REST API discovery endpoint\n */\nclass GetTapeWellKnownModel\n  : public common::Jsonifiable<GetTapeWellKnownModel>\n{\npublic:\n  inline GetTapeWellKnownModel(const TapeWellKnownInfos* tapeWellKnownInfos)\n    : mTapeWellKnownInfos(tapeWellKnownInfos) {}\n  inline const TapeWellKnownInfos* getTapeWellKnownInfos() const { return mTapeWellKnownInfos; }\nprivate:\n  const TapeWellKnownInfos* mTapeWellKnownInfos;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_GETTAPEWELLKNOWNMODEL_HH\n"
  },
  {
    "path": "mgm/http/rest-api/response/ErrorHandling.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ErrorHandling.hh\n// Author: Refactor - Centralized error to response mapping\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) CERN/Switzerland                                       *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_REST_ERROR_HANDLING_HH\n#define EOS_REST_ERROR_HANDLING_HH\n\n#include \"common/Logging.hh\"\n#include \"common/http/HttpResponse.hh\"\n#include \"mgm/http/rest-api/exception/Exceptions.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\ntemplate <typename ResponseFactory, typename Fn>\ncommon::HttpResponse* HandleWithErrors(ResponseFactory & responseFactory, Fn fn)\n{\n  try {\n    return fn();\n  } catch (const NotFoundException& ex) {\n    eos_static_info(ex.what());\n    return responseFactory.NotFound().getHttpResponse();\n  } catch (const MethodNotAllowedException& ex) {\n    eos_static_info(ex.what());\n    return responseFactory.MethodNotAllowed(ex.what()).getHttpResponse();\n  } catch (const ForbiddenException& ex) {\n    eos_static_info(ex.what());\n    return responseFactory.Forbidden(ex.what()).getHttpResponse();\n  } catch (const NotImplementedException& ex) {\n    eos_static_info(ex.what());\n    return responseFactory.NotImplemented().getHttpResponse();\n  } catch (const RestException& ex) {\n    eos_static_info(ex.what());\n    return responseFactory.InternalError(ex.what()).getHttpResponse();\n  } catch (...) {\n    std::string errorMsg = \"Unknown exception occured\";\n    eos_static_err(errorMsg.c_str());\n    return responseFactory.InternalError(errorMsg).getHttpResponse();\n  }\n}\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_REST_ERROR_HANDLING_HH\n\n\n"
  },
  {
    "path": "mgm/http/rest-api/response/RestApiResponse.hh",
    "content": "// ----------------------------------------------------------------------\n// File: RestApiResponse.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_RESTAPIRESPONSE_HH\n#define EOS_RESTAPIRESPONSE_HH\n\n#include \"common/http/HttpResponse.hh\"\n#include \"common/http/PlainHttpResponse.hh\"\n#include \"mgm/Namespace.hh\"\n#include <memory>\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * This class allows to create a RestAPI http response\n * from a model object\n */\ntemplate<typename Model>\nclass RestApiResponse\n{\npublic:\n  /**\n   * Constructor with the model\n   * @param model the model that will be used to create the http response\n   */\n  RestApiResponse();\n  RestApiResponse(const common::HttpResponse::ResponseCodes retCode);\n  RestApiResponse(std::shared_ptr<Model> model,\n                  const common::HttpResponse::ResponseCodes retCode);\n  RestApiResponse(std::shared_ptr<Model> model,\n                  const common::HttpResponse::ResponseCodes retCode,\n                  const common::HttpResponse::HeaderMap& responseHeader);\n\n  /**\n   * Returns the actual HttpResponse created from the model and the return code\n   * of this instance\n   * The body will be the JSON representation of the model\n   */\n  common::HttpResponse* getHttpResponse() const;\n\n  static RestApiResponse<Model> createResponse(std::shared_ptr<Model> model,\n      const common::HttpResponse::ResponseCodes code);\n  static RestApiResponse<Model> createResponse(std::shared_ptr<Model> model,\n      const common::HttpResponse::ResponseCodes code,\n      const common::HttpResponse::HeaderMap& responseHeader);\n\nprivate:\n  std::shared_ptr<Model> mModel;\n  common::HttpResponse::ResponseCodes mRetCode;\n  mutable common::HttpResponse::HeaderMap mHeaderMap;\n};\n\ntemplate<typename Model>\nRestApiResponse<Model>::RestApiResponse() : mRetCode(\n    common::HttpResponse::ResponseCodes::OK) {}\n\ntemplate<typename Model>\nRestApiResponse<Model>::RestApiResponse(const\n                                        common::HttpResponse::ResponseCodes retCode) : mRetCode(retCode) {}\n\ntemplate<typename Model>\nRestApiResponse<Model>::RestApiResponse(std::shared_ptr<Model> model,\n                                        const common::HttpResponse::ResponseCodes retCode) :\n  mModel(model), mRetCode(retCode) {}\n\ntemplate<typename Model>\nRestApiResponse<Model>::RestApiResponse(std::shared_ptr<Model> model,\n                                        const common::HttpResponse::ResponseCodes retCode,\n                                        const common::HttpResponse::HeaderMap& responseHeader) :\n  mModel(model), mRetCode(retCode), mHeaderMap(responseHeader) {}\n\ntemplate<typename Model>\ninline common::HttpResponse* RestApiResponse<Model>::getHttpResponse() const\n{\n  common::HttpResponse* response = new common::PlainHttpResponse();\n\n  if (mModel) {\n    mHeaderMap[\"application/type\"] = \"json\";\n    response->SetHeaders(mHeaderMap);\n    std::stringstream ss;\n    mModel->jsonify(ss);\n    response->SetBody(ss.str());\n  }\n\n  response->SetResponseCode(mRetCode);\n  return response;\n}\n\ntemplate<>\ninline common::HttpResponse* RestApiResponse<void>::getHttpResponse() const\n{\n  common::HttpResponse* response = new common::PlainHttpResponse();\n  response->SetResponseCode(mRetCode);\n  return response;\n}\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_RESTAPIRESPONSE_HH\n"
  },
  {
    "path": "mgm/http/rest-api/response/RestApiResponseFactory.hh",
    "content": "// Deprecated: use RestResponseFactory.hh instead\n#ifndef EOS_RESTAPIRESPONSEFACTORY_HH\n#define EOS_RESTAPIRESPONSEFACTORY_HH\n#include \"mgm/http/rest-api/response/RestResponseFactory.hh\"\n#endif\n"
  },
  {
    "path": "mgm/http/rest-api/response/RestResponseFactory.cc",
    "content": "// ----------------------------------------------------------------------\n// File: RestResponseFactory.cc\n// Author: Consolidated REST response factory\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n ************************************************************************/\n\n#include \"mgm/http/rest-api/response/RestResponseFactory.hh\"\n#include \"mgm/http/rest-api/json/tape/TapeRestApiJsonifier.hh\"\n#include \"mgm/http/rest-api/json/tape/TapeJsonifiers.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nRestApiResponse<ErrorModel>\nRestResponseFactory::makeError(const common::HttpResponse::ResponseCodes code,\n                               const std::string& title,\n                               const std::optional<std::string>& detail) const\n{\n  std::shared_ptr<ErrorModel> errorModel = std::make_shared<ErrorModel>(title,\n      static_cast<uint32_t>(code), detail);\n  std::shared_ptr<ErrorModelJsonifier> jsonObject =\n    std::make_shared<ErrorModelJsonifier>();\n  errorModel->setJsonifier(jsonObject);\n  return createResponse(errorModel, code);\n}\n\nRestApiResponse<ErrorModel> RestResponseFactory::BadRequest(const std::string& detail) const\n{\n  return makeError(common::HttpResponse::BAD_REQUEST, \"Bad request\", detail);\n}\n\nRestApiResponse<ErrorModel> RestResponseFactory::BadRequest(const JsonValidationException& ex) const\n{\n  const auto& validationErrors = ex.getValidationErrors();\n  std::string detail;\n  if (validationErrors != nullptr && validationErrors->hasAnyError()) {\n    auto& error = validationErrors->getErrors()->front();\n    detail += error->getFieldName() + \" - \" + error->getReason();\n  } else {\n    detail = ex.what();\n  }\n  return makeError(common::HttpResponse::BAD_REQUEST, \"JSON Validation error\", detail);\n}\n\nRestApiResponse<ErrorModel> RestResponseFactory::NotFound() const\n{\n  return makeError(common::HttpResponse::NOT_FOUND, \"Not found\", std::nullopt);\n}\n\nRestApiResponse<ErrorModel>\nRestResponseFactory::MethodNotAllowed(const std::string& detail) const\n{\n  return makeError(common::HttpResponse::METHOD_NOT_ALLOWED, \"Method not allowed\", detail);\n}\n\nRestApiResponse<ErrorModel> RestResponseFactory::Forbidden(const std::string& detail) const\n{\n  return makeError(common::HttpResponse::FORBIDDEN, \"Forbidden\", detail);\n}\n\nRestApiResponse<ErrorModel> RestResponseFactory::NotImplemented() const\n{\n  return makeError(common::HttpResponse::NOT_IMPLEMENTED, \"Not implemented\", std::nullopt);\n}\n\nRestApiResponse<ErrorModel>\nRestResponseFactory::InternalError(const std::string& detail) const\n{\n  return makeError(common::HttpResponse::INTERNAL_SERVER_ERROR, \"Internal server error\", detail);\n}\n\nEOSMGMRESTNAMESPACE_END\n\n\n"
  },
  {
    "path": "mgm/http/rest-api/response/RestResponseFactory.hh",
    "content": "// ----------------------------------------------------------------------\n// File: RestResponseFactory.hh\n// Author: Consolidated REST response factory\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) CERN/Switzerland                                       *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_REST_RESPONSE_FACTORY_HH\n#define EOS_REST_RESPONSE_FACTORY_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/response/RestApiResponse.hh\"\n#include \"mgm/http/rest-api/exception/JsonValidationException.hh\"\n#include \"mgm/http/rest-api/model/tape/common/ErrorModel.hh\"\n#include <optional>\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nclass RestResponseFactory\n{\npublic:\n  RestResponseFactory() = default;\n\n  template<typename Model>\n  RestApiResponse<Model> createResponse(std::shared_ptr<Model> model,\n                                        const common::HttpResponse::ResponseCodes code) const\n  {\n    return RestApiResponse<Model>(model, code);\n  }\n\n  template<typename Model>\n  RestApiResponse<Model> createResponse(std::shared_ptr<Model> model,\n                                        const common::HttpResponse::ResponseCodes code,\n                                        const common::HttpResponse::HeaderMap& responseHeader) const\n  {\n    return RestApiResponse<Model>(model, code, responseHeader);\n  }\n\n  template<typename Model>\n  RestApiResponse<Model> Ok(std::shared_ptr<Model> model) const\n  {\n    return createResponse(model, common::HttpResponse::OK);\n  }\n\n  RestApiResponse<void> OkEmpty() const { return RestApiResponse<void>(); }\n\n  template<typename Model>\n  RestApiResponse<Model> Created(std::shared_ptr<Model> model,\n                                 const common::HttpResponse::HeaderMap& hdrs) const\n  {\n    return createResponse(model, common::HttpResponse::CREATED, hdrs);\n  }\n\n  RestApiResponse<ErrorModel> BadRequest(const std::string& detail) const;\n  RestApiResponse<ErrorModel> BadRequest(const JsonValidationException& ex) const;\n  RestApiResponse<ErrorModel> NotFound() const;\n  RestApiResponse<ErrorModel> MethodNotAllowed(const std::string& detail) const;\n  RestApiResponse<ErrorModel> Forbidden(const std::string& detail) const;\n  RestApiResponse<ErrorModel> NotImplemented() const;\n  RestApiResponse<ErrorModel> InternalError(const std::string& detail) const;\n\nprivate:\n  RestApiResponse<ErrorModel> makeError(const common::HttpResponse::ResponseCodes code,\n                                        const std::string& title,\n                                        const std::optional<std::string>& detail) const;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_REST_RESPONSE_FACTORY_HH\n\n\n"
  },
  {
    "path": "mgm/http/rest-api/router/Router.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Router.hh\n// Author: Refactor - Simplified REST routing\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) CERN/Switzerland                                       *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_REST_ROUTER_HH\n#define EOS_REST_ROUTER_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"common/http/HttpHandler.hh\"\n#include \"common/http/HttpRequest.hh\"\n#include \"common/http/HttpResponse.hh\"\n#include \"mgm/http/rest-api/utils/URLParser.hh\"\n#include \"mgm/http/rest-api/exception/Exceptions.hh\"\n#include <type_traits>\n\n#include <functional>\n#include <string>\n#include <vector>\n#include <sstream>\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nstruct Route\n{\n  std::string pattern;\n  common::HttpHandler::Methods method;\n  std::function<common::HttpResponse*(common::HttpRequest*, const common::VirtualIdentity*)> handler;\n};\n\nclass Router\n{\npublic:\n  void add(const std::string & pattern,\n           common::HttpHandler::Methods method,\n           std::function<common::HttpResponse*(common::HttpRequest*, const common::VirtualIdentity*)> handler)\n  {\n    Route r{pattern, method, std::move(handler)};\n    mRoutes.emplace_back(std::move(r));\n  }\n\n  common::HttpResponse* dispatch(common::HttpRequest* request,\n                                 const common::VirtualIdentity* vid)\n  {\n    const std::string url = request->GetUrl();\n    const std::string methodStr = request->GetMethod();\n    common::HttpHandler::Methods method = (common::HttpHandler::Methods)\n                                          common::HttpHandler::ParseMethodString(methodStr);\n    URLParser parser(url);\n\n    bool patternMatched = false;\n    for (auto & route : mRoutes) {\n      if (parser.matches(route.pattern)) {\n        patternMatched = true;\n        if (route.method == method) {\n          return route.handler(request, vid);\n        }\n      }\n    }\n\n    if (patternMatched) {\n      std::ostringstream oss;\n      oss << \"The method \" << methodStr << \" is not allowed for this resource.\";\n      throw MethodNotAllowedException(oss.str());\n    }\n\n    std::ostringstream oss;\n    oss << \"The url provided (\" << url << \") does not allow to identify a resource\";\n    throw ActionNotFoundException(oss.str());\n  }\n\nprivate:\n  std::vector<Route> mRoutes;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_REST_ROUTER_HH\n\n\n"
  },
  {
    "path": "mgm/http/rest-api/utils/URLBuilder.cc",
    "content": "// ----------------------------------------------------------------------\n// File: URLBuilder.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"URLBuilder.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include <unistd.h>\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nstd::unique_ptr<URLBuilderProtocol> URLBuilder::getInstance()\n{\n  std::unique_ptr<URLBuilderProtocol> ret;\n  ret.reset(new URLBuilder());\n  //copy-ellision here\n  return ret;\n}\n\nURLBuilderHostname* URLBuilder::setHttpsProtocol()\n{\n  mURL = \"https://\";\n  return this;\n}\n\nURLBuilderPort* URLBuilder::setHostname(const std::string& hostname)\n{\n  mURL += hostname;\n  return this;\n}\n\nURLBuilder* URLBuilder::setPort(const uint16_t& port)\n{\n  mURL += \":\" + std::to_string(port);\n  return this;\n}\n\nURLBuilder* URLBuilder::add(const std::string& urlItem)\n{\n  addSlashIfNecessary(urlItem);\n  mURL += urlItem;\n  return this;\n}\n\nstd::string URLBuilder::build() const\n{\n  return mURL;\n}\n\nvoid URLBuilder::addSlashIfNecessary(const std::string& nextItem)\n{\n  if (mURL.back() != '/' && (!nextItem.empty() && nextItem.front() != '/')) {\n    mURL += \"/\";\n  }\n}\n\nEOSMGMRESTNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/rest-api/utils/URLBuilder.hh",
    "content": "// ----------------------------------------------------------------------\n// File: URLBuilder.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_URLBUILDER_HH\n#define EOS_URLBUILDER_HH\n\n#include \"mgm/Namespace.hh\"\n\n#include <cstdint>\n#include <string>\n#include <memory>\n#include <optional>\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * This class allows to build a URL by using the pattern fluent builder.\n * The programmer will create the URL following the order imposed by the builder\n */\nclass URLBuilder;\n\nclass URLBuilderPort\n{\npublic:\n  virtual URLBuilder* setPort(const uint16_t& port) = 0;\n  virtual URLBuilder* add(const std::string& urlItem) = 0;\n  virtual ~URLBuilderPort() = default;\n};\n\nclass URLBuilderHostname\n{\npublic:\n  virtual URLBuilderPort* setHostname(const std::string& hostname) = 0;\n  virtual ~URLBuilderHostname() = default;\n};\n\nclass URLBuilderProtocol\n{\npublic:\n  virtual URLBuilderHostname* setHttpsProtocol() = 0;\n  virtual ~URLBuilderProtocol() = default;\n};\n\n/**\n * Actual URL builder\n */\nclass URLBuilder : public URLBuilderProtocol, URLBuilderHostname,\n  URLBuilderPort\n{\npublic:\n  /**\n   * Returns the URL built with the builder\n   * @return the URL built with the builder\n   */\n  std::string build() const;\n  URLBuilder* add(const std::string& urlItem) override;\n\n  /**\n   * Get the instance of the builder. It will first return the Builder allowing to generate the protocol of the URL\n   * @return the pointer to the instance of the builder allowing to generate the protocol of the URL\n   */\n  static std::unique_ptr<URLBuilderProtocol> getInstance();\nprivate:\n  URLBuilder() = default;\n  /**\n   * Adds the https:// string to the URL\n   * @return the builder to add the hostname\n   */\n  URLBuilderHostname* setHttpsProtocol() override;\n  /**\n   * Generates and add the builder\n   * @return\n   */\n  URLBuilderPort* setHostname(const std::string& hostname) override;\n  URLBuilder* setPort(const uint16_t& port) override;\n  std::string mURL;\n  void addSlashIfNecessary(const std::string& nextItem = \"\");\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_URLBUILDER_HH\n"
  },
  {
    "path": "mgm/http/rest-api/utils/URLParser.cc",
    "content": "// ----------------------------------------------------------------------\n// File: URLParser.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#include \"URLParser.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/RegexWrapper.hh\"\n#include <algorithm>\n\nnamespace\n{\nstd::string sParamRegex(\"^\\\\{[a-z]*\\\\}$\");\n}\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nURLParser::URLParser(const std::string& url)\n{\n  common::StringConversion::Tokenize(url, mURLTokens, \"/\");\n}\n\nbool URLParser::startsBy(const std::string& url)\n{\n  std::vector<std::string> urlTokens;\n  common::StringConversion::Tokenize(url, urlTokens, \"/\");\n  uint8_t urlTokensSize = urlTokens.size();\n\n  if (mURLTokens.size() < urlTokensSize) {\n    return false;\n  }\n\n  for (uint8_t i = 0; i < urlTokensSize; ++i) {\n    if (urlTokens.at(i) != mURLTokens.at(i)) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nbool URLParser::matches(const std::string& urlPattern)\n{\n  std::map<std::string, std::string> params;\n  return matchesAndExtractParameters(urlPattern, params);\n}\n\nbool URLParser::matchesAndExtractParameters(const std::string& urlPattern,\n    std::map<std::string, std::string>& params)\n{\n  params.clear();\n  std::vector<std::string> urlPatternTokens;\n  common::StringConversion::Tokenize(urlPattern, urlPatternTokens, \"/\");\n\n  if (mURLTokens.size() != urlPatternTokens.size()) {\n    return false;\n  }\n\n  uint8_t urlPatternTokensSize = urlPatternTokens.size();\n\n  for (uint8_t i = 0; i < urlPatternTokensSize; ++i) {\n    const std::string& urlPatternToken = urlPatternTokens.at(i);\n    const std::string& urlToken = mURLTokens.at(i);\n\n    if (urlToken != urlPatternToken) {\n      //URL parts do not match, maybe it is a parameter, try to extract it\n      if (eos::common::eos_regex_match(urlPatternToken, sParamRegex)) {\n        params[urlPatternToken] = urlToken;\n      } else {\n        return false;\n      }\n    }\n  }\n\n  return true;\n}\n\nvoid URLParser::removeDuplicateSlashes(std::string& path)\n{\n  path.erase(std::unique(path.begin(), path.end(), [](char a, char b) {\n    return a == '/' && b == '/';\n  }), path.end());\n}\n\nEOSMGMRESTNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/rest-api/utils/URLParser.hh",
    "content": "// ----------------------------------------------------------------------\n// File: URLParser.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_URLPARSER_HH\n#define EOS_URLPARSER_HH\n\n#include \"mgm/Namespace.hh\"\n#include <string>\n#include <vector>\n#include <map>\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * Class allowing to parse the URL given via the constructor\n * and extract information depending on a pattern\n *\n * The URL pattern should have the following format: /api/v1/stage/{requestid}/cancel\n * the {requestid} is actually a placeholder that this parser will rely on to extract parameters\n * from the client url\n */\nclass URLParser\n{\npublic:\n  URLParser(const std::string& url);\n  /**\n   * Returns true if the URL of this instance\n   * starts by the URL passed in parameter\n   * @param url the URL to compare this instance URL with\n   * @return true the URL of this instance\n   * starts by the URL passed in parameter, false otherwise\n   */\n  bool startsBy(const std::string& url);\n  /**\n   * Returns true if the URL of this instance matches exactly\n   * the URL pattern passed in parameter, false otherwise\n   * @param urlPattern the pattern the URL should match\n   * @return true if the URL of this instance matches the url pattern, false otherwise\n   */\n  bool matches(const std::string& urlPattern);\n\n  /**\n   * Returns true if the URL of this instance matches exactly the URL\n   * pattern passed in paramter, false otherwise.\n   * It also extracts the values located at the placeholders\n   * place in this instance URL.\n   *\n   * Example:\n   *    thisURL = /api/v1/stage/xxx-xxx/cancel\n   *    urlPattern=/api/v1/stage/{requestid}/cancel\n   *    This method will return true and the params map will contain \"requestid\":\"xxx-xxx\"\n   */\n  bool matchesAndExtractParameters(const std::string& urlPattern,\n                                   std::map<std::string, std::string>& params);\n\n  /**\n   * Removes the duplicate slashes from a path given in parameter\n   * example: /eos//test/////file.txt --> /eos/test/file.txt\n   * @param path the path to remove the duplicate slashes from\n   */\n  static void removeDuplicateSlashes(std::string& path);\n\nprivate:\n  std::vector<std::string> mURLTokens;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_URLPARSER_HH\n"
  },
  {
    "path": "mgm/http/rest-api/wellknown/tape/TapeRestApiEndpoint.cc",
    "content": "// ----------------------------------------------------------------------\n// File: TapeRestApiEndpoint.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"TapeRestApiEndpoint.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nTapeRestApiEndpoint::TapeRestApiEndpoint(const std::string& uri,\n    const std::string& version) : mUri(uri), mVersion(version) {}\n\nconst std::string TapeRestApiEndpoint::getUri() const\n{\n  return mUri;\n}\n\nconst std::string TapeRestApiEndpoint::getVersion() const\n{\n  return mVersion;\n}\n\nEOSMGMRESTNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/rest-api/wellknown/tape/TapeRestApiEndpoint.hh",
    "content": "// ----------------------------------------------------------------------\n// File: TapeRestApiEndpoint.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_TAPERESTAPIENDPOINT_HH\n#define EOS_TAPERESTAPIENDPOINT_HH\n\n#include \"mgm/Namespace.hh\"\n#include <string>\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * This class represent a tape REST API endpoint\n * It gathers information like the URI to access the tape REST API endpoint depending\n * on the version\n */\nclass TapeRestApiEndpoint\n{\npublic:\n  /**\n   * Constructor of the endpoint\n   * @param uri the full URI to access a specific version of the tape REST API\n   * @param version the version ass\n   */\n  TapeRestApiEndpoint(const std::string& uri, const std::string& version);\n  /**\n   * The full URI to access the tape REST API\n   */\n  const std::string getUri() const;\n  /**\n   * The version of the tape REST API associated to this endpoint\n   * @return v0,v1...\n   */\n  const std::string getVersion() const;\nprivate:\n  std::string mUri;\n  std::string mVersion;\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_TAPERESTAPIENDPOINT_HH\n"
  },
  {
    "path": "mgm/http/rest-api/wellknown/tape/TapeWellKnownInfos.cc",
    "content": "// ----------------------------------------------------------------------\n// File: TapeWellKnownInfos.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"TapeWellKnownInfos.hh\"\n\nEOSMGMRESTNAMESPACE_BEGIN\n\nTapeWellKnownInfos::TapeWellKnownInfos(const std::string& siteName) : mSiteName(\n    siteName) {}\n\nvoid TapeWellKnownInfos::addEndpoint(const std::string& uri,\n                                     const std::string& version)\n{\n  mEndpoints.push_back(std::make_unique<TapeRestApiEndpoint>(uri, version));\n}\n\nconst TapeWellKnownInfos::Endpoints& TapeWellKnownInfos::getEndpoints() const\n{\n  return mEndpoints;\n}\n\nconst std::string TapeWellKnownInfos::getSiteName() const\n{\n  return mSiteName;\n}\n\nEOSMGMRESTNAMESPACE_END"
  },
  {
    "path": "mgm/http/rest-api/wellknown/tape/TapeWellKnownInfos.hh",
    "content": "// ----------------------------------------------------------------------\n// File: TapeWellKnownInfos.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_TAPEWELLKNOWNINFOS_HH\n#define EOS_TAPEWELLKNOWNINFOS_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/wellknown/tape/TapeRestApiEndpoint.hh\"\n#include <string>\n#include <vector>\n#include <memory>\n\nEOSMGMRESTNAMESPACE_BEGIN\n\n/**\n * This class contains the information that can be used\n * by the tape REST API .well-known endpoint to display them\n * for the user\n */\nclass TapeWellKnownInfos\n{\npublic:\n  typedef std::vector<std::unique_ptr<TapeRestApiEndpoint>> Endpoints;\n  TapeWellKnownInfos(const std::string& siteName);\n  void addEndpoint(const std::string& uri, const std::string& version);\n  const Endpoints& getEndpoints() const;\n  const std::string getSiteName() const;\nprivate:\n  //The sitename that has to be used for targeted metadata on stage bulk-request submission\n  std::string mSiteName;\n  //The endpoints allowing the clients to reach a specific version of the tape REST API\n  std::vector<std::unique_ptr<TapeRestApiEndpoint>> mEndpoints;\n  //If metadata are needed, add a list in this class\n};\n\nEOSMGMRESTNAMESPACE_END\n\n#endif // EOS_TAPEWELLKNOWNINFOS_HH\n"
  },
  {
    "path": "mgm/http/s3/S3Handler.cc",
    "content": "// ----------------------------------------------------------------------\n// File: S3Handler.cc\n// Author: Andreas-Joachim Peters & Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"mgm/http/s3/S3Handler.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"common/http/PlainHttpResponse.hh\"\n#include \"common/Logging.hh\"\n#include \"common/SymKeys.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n#include <string>\n#include <map>\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\nchar s3_rfc3986[256] = {0};\nchar s3_html5[256] = {0};\n\nvoid\ns3_uri_encode(unsigned char* s, char* enc, char* tb)\n{\n  for (; *s; s++) {\n    if (tb[*s]) {\n      sprintf(enc, \"%c\", tb[*s]);\n    } else {\n      sprintf(enc, \"%%%02X\", *s);\n    }\n\n    while (*++enc);\n  }\n}\n\nS3Store* S3Handler::mS3Store = 0;\n\n/*----------------------------------------------------------------------------*/\nS3Handler::S3Handler(eos::common::VirtualIdentity* vid) :\n  eos::common::ProtocolHandler(vid)\n{\n  mIsS3 = false;\n  mId = mSignature = mHost = mContentMD5 = mContentType = mUserAgent = \"\";\n  mHttpMethod = mPath = mQuery = mBucket = mDate = \"\";\n  mVirtualHost = false;\n\n  if (!mS3Store) {\n    // create the store if it does not exist yet\n    mS3Store = new S3Store(gOFS->MgmProcPath.c_str());\n\n    // initialize encoding table\n    for (int i = 0; i < 256; i++) {\n      s3_rfc3986[i] = isalnum(i) || i == '-' || i == '.' || i == '_'\n                      || i == '@'\n                      ? i : 0;\n      s3_html5[i] = isalnum(i) || i == '*' || i == '-' || i == '.' || i == '_'\n                    ? i : (i == ' ') ? '+' : 0;\n    }\n  }\n}\n\nXrdOucString\nS3Handler::EncodeURI(const char* uri)\n{\n  XrdOucString nUri;\n  char enc[(strlen(uri) + 1) * 3];\n  s3_uri_encode((unsigned char*) uri, enc, s3_rfc3986);\n  XrdOucString lUri = enc;\n  return lUri;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nS3Handler::Matches(const std::string& method, HeaderMap& headers)\n{\n  if (headers.count(\"authorization\")) {\n    if (headers[\"authorization\"].substr(0, 3) == \"AWS\") {\n      eos_static_debug(\"msg=\\\"matched S3 protocol for request\\\"\");\n      return true;\n    }\n  }\n\n  return false;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nS3Handler::HandleRequest(eos::common::HttpRequest* request)\n{\n  eos_static_debug(\"msg=\\\"handling s3 request\\\"\");\n  eos::common::HttpResponse* response = 0;\n  // Parse the headers\n  ParseHeader(request);\n  // Refresh the data store\n  mS3Store->Refresh();\n\n  if (!mS3Store->GetKeys().count(GetId())) {\n    response = RestErrorResponse(response->FORBIDDEN, \"InvalidAccessKeyId\",\n                                 \"No corresponding S3 account was found\",\n                                  GetId(), \"\");\n  } else if (VerifySignature()) {\n    request->AddEosApp();\n    int meth = ParseMethodString(request->GetMethod());\n\n    switch (meth) {\n    case GET:\n      response = Get(request);\n      break;\n\n    case HEAD:\n      response = Head(request);\n      break;\n\n    case PUT:\n      response = Put(request);\n      break;\n\n    case DELETE:\n      response = Delete(request);\n      break;\n\n    default:\n      response = new eos::common::PlainHttpResponse();\n      response->SetResponseCode(eos::common::HttpResponse::NOT_IMPLEMENTED);\n    }\n  } else {\n    response = RestErrorResponse(response->FORBIDDEN,\n                                 \"SignatureDoesNotMatch\", \"\", GetBucket(), \"\");\n  }\n\n  mHttpResponse = response;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nS3Handler::VerifySignature()\n{\n  std::string secure_key = mS3Store->GetKeys()[GetId()];\n  std::string string2sign = GetHttpMethod();\n  string2sign += \"\\n\";\n  string2sign += GetContentMD5();\n  string2sign += \"\\n\";\n  string2sign += GetContentType();\n  string2sign += \"\\n\";\n  string2sign += GetDate();\n  string2sign += \"\\n\";\n  string2sign += GetCanonicalizedAmzHeaders();\n\n  if (GetBucket().length()) {\n    string2sign += \"/\";\n    string2sign += GetBucket();\n  };\n\n  string2sign += GetPath();\n\n  if (ExtractSubResource().length()) {\n    string2sign += \"?\";\n    string2sign += GetSubResource();\n  }\n\n  eos_static_debug(\"s2sign=%s key=%s\", string2sign.c_str(), secure_key.c_str());\n  // get hmac sha1 hash\n  std::string hmac1 = eos::common::SymKey::HmacSha1(string2sign,\n                      secure_key.c_str());\n  XrdOucString b64mac1;\n  // base64 encode the hash\n  eos::common::SymKey::Base64Encode((char*) hmac1.c_str(),\n                                    SHA_DIGEST_LENGTH, b64mac1);\n  std::string verify_signature = b64mac1.c_str();\n  eos_static_debug(\"in_signature=%s out_signature=%s\\n\",\n                   GetSignature().c_str(), verify_signature.c_str());\n\n  if (verify_signature != GetSignature()) {\n    // --------------------------------------------------------------------------\n    // try if the non-bucket path needs '/' encoded as '%2F' as done by Cyberduck\n    // e.g. /<bucket>/<path-without-slash-inthe-beginnging>\n    // --------------------------------------------------------------------------\n    XrdOucString encodedPath = GetPath().c_str();\n    encodedPath = EncodeURI(encodedPath.c_str() + 1);\n    encodedPath.insert('/', 0);\n    XrdOucString newstring2sign = string2sign.c_str();\n    newstring2sign.replace(GetPath().c_str(), encodedPath.c_str());\n    string2sign = newstring2sign.c_str();\n    hmac1 = eos::common::SymKey::HmacSha1(string2sign, secure_key.c_str());\n    b64mac1 = \"\";\n    eos::common::SymKey::Base64Encode((char*) hmac1.c_str(),\n                                      SHA_DIGEST_LENGTH, b64mac1);\n    verify_signature = b64mac1.c_str();\n    eos_static_debug(\"s2sign=%s key=%s\", string2sign.c_str(), secure_key.c_str());\n    eos_static_debug(\"in_signature=%s out_signature=%s\\n\",\n                     GetSignature().c_str(), verify_signature.c_str());\n    return (verify_signature == GetSignature());\n  }\n\n  return true;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nS3Handler::Get(eos::common::HttpRequest* request)\n{\n  eos::common::HttpResponse* response = 0;\n\n  if (GetBucket() == \"\") {\n    response = mS3Store->ListBuckets(GetId());\n  } else {\n    if (GetPath() == \"/\") {\n      response = mS3Store->ListBucket(GetBucket(), GetQuery());\n    } else {\n      response = mS3Store->GetObject(request, GetId(), GetBucket(), GetPath(),\n                                     GetQuery());\n    }\n  }\n\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nS3Handler::Head(eos::common::HttpRequest* request)\n{\n  eos::common::HttpResponse* response = 0;\n\n  if (GetPath() == \"/\") {\n    response = mS3Store->HeadBucket(GetId(), GetBucket(), GetDate());\n  } else {\n    response = mS3Store->HeadObject(GetId(), GetBucket(), GetPath(), GetDate());\n  }\n\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nS3Handler::Put(eos::common::HttpRequest* request)\n{\n  eos::common::HttpResponse* response = 0;\n  response = mS3Store->PutObject(request, GetId(), GetBucket(), GetPath(),\n                                 GetQuery());\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nS3Handler::Delete(eos::common::HttpRequest* request)\n{\n  eos::common::HttpResponse* response = 0;\n  response = mS3Store->DeleteObject(request, GetId(), GetBucket(), GetPath());\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/s3/S3Handler.hh",
    "content": "// ----------------------------------------------------------------------\n// File: S3Handler.hh\n// Author: Andreas-Joachim Peters & Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   S3Handler.hh\n *\n * @brief  Dealing with all S3 goodies\n */\n\n#ifndef __EOSMGM_S3_HANDLER__HH__\n#define __EOSMGM_S3_HANDLER__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/http/s3/S3Handler.hh\"\n#include \"mgm/http/s3/S3Store.hh\"\n#include \"mgm/Namespace.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n#include <string>\n#include <map>\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\nclass S3Store;\n\nclass S3Handler : public eos::common::S3Handler\n{\n\nprotected:\n  static S3Store *mS3Store; //!< persistent S3 data object\n\npublic:\n\n  /**\n   * Constructor\n   */\n  S3Handler (eos::common::VirtualIdentity *vid);\n\n  /**\n   * Destructor\n   */\n  virtual ~S3Handler () {};\n\n  /**\n   * Check whether the given method and headers are a match for this protocol.\n   *\n   * @param method  the request verb used by the client (GET, PUT, etc)\n   * @param headers the map of request headers\n   *\n   * @return true if the protocol matches, false otherwise\n   */\n  static bool\n  Matches (const std::string &method, HeaderMap &headers);\n\n  /**\n   * Build a response to the given S3 request.\n   *\n   * @param request  the map of request headers sent by the client\n   * @param method   the request verb used by the client (GET, PUT, etc)\n   * @param url      the URL requested by the client\n   * @param query    the GET request query string (if any)\n   * @param body     the request body data sent by the client\n   * @param bodysize the size of the request body\n   * @param cookies  the map of cookie headers\n   */\n  void\n  HandleRequest (eos::common::HttpRequest *request);\n\n  /**\n   * Verify the AWS signature\n   *\n   * @return true if S3 signature is verified\n   */\n  bool\n  VerifySignature ();\n\n  /**\n   * Handle an S3 GET request.\n   *\n   * @param request  the client request object\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Get (eos::common::HttpRequest *request);\n\n  /**\n   * Handle an S3 HEAD request.\n   *\n   * @param request  the client request object\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Head (eos::common::HttpRequest *request);\n\n  /**\n   * Handle an S3 PUT request.\n   *\n   * @param request  the client request object\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Put (eos::common::HttpRequest *request);\n\n  /**\n   * Handle an S3 DELETE request.\n   *\n   * @param request  the client request object\n   *\n   * @return an HTTP response object\n   */\n  eos::common::HttpResponse*\n  Delete (eos::common::HttpRequest *request);\n\n  /**\n   * Encode an URI\n   *\n   * @param path is the URI to encode\n   *\n   * @return an XrdOucString with the encoded URI\n   */\n  XrdOucString\n  EncodeURI(const char* uri);\n\n\n};\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_END\n\n#endif\n\n"
  },
  {
    "path": "mgm/http/s3/S3Store.cc",
    "content": "// ----------------------------------------------------------------------\n// File: S3Store.cc\n// Author: Andreas-Joachim Peters & Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/http/HttpServer.hh\"\n#include \"mgm/http/s3/S3Store.hh\"\n#include \"mgm/http/s3/S3Handler.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/ofs/XrdMgmOfsDirectory.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/utils/Checksum.hh\"\n#include \"common/http/PlainHttpResponse.hh\"\n#include \"common/Logging.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/FileId.hh\"\n#include \"common/Timing.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n#define XML_V1_UTF8 \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\"\n\n/*----------------------------------------------------------------------------*/\nS3Store::S3Store(const char* s3defpath)\n{\n  mS3DefContainer = s3defpath;\n  mStoreModificationTime = 1;\n  mStoreReloadTime = 1;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nS3Store::Refresh()\n{\n  // Refresh the S3 id, keys, container definitions\n  time_t now = time(NULL);\n  time_t srtime;\n  {\n    eos::common::RWMutexReadLock sLock(mStoreMutex);\n    srtime = mStoreReloadTime;\n  }\n\n  // Attempt refresh only once per minute\n  if ((now - srtime) > 60) {\n    eos::common::RWMutexWriteLock sLock(mStoreMutex);\n    mStoreReloadTime = now;\n    XrdOucErrInfo error;\n    eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n    eos::IContainerMD::XAttrMap map;\n    struct stat buf;\n\n    if (gOFS->_stat(mS3DefContainer.c_str(), &buf, error, vid, (const char*) 0)\n        == SFS_OK) {\n      // check last modification time\n      if (buf.st_ctime != mStoreModificationTime) {\n        // clear all\n        mS3ContainerSet.clear();\n        mS3Keys.clear();\n        mS3ContainerPath.clear();\n\n        if (gOFS->_attr_ls(mS3DefContainer.c_str(), error, vid, 0, map)\n            != SFS_OK) {\n          eos_static_err(\"unable to list attributes of % s\",\n                         mS3DefContainer.c_str());\n        } else {\n          // parse the attributes into the store\n          for (auto it = map.begin(); it != map.end(); it++) {\n            eos_static_info(\"parsing %s=>%s\", it->first.c_str(),\n                            it->second.c_str());\n\n            if (it->first.substr(0, 6) == \"sys.s3\") {\n              // the s3 attributes are built as\n              // sys.s3.id.<id> => secret key\n              // sys.s3.bucket.<id> => bucket list\n              // sys.s3.path.<bucket> => path\n              if (it->first.substr(0, 10) == \"sys.s3.id.\") {\n                std::string id = it->first.substr(10);\n                mS3Keys[id] = it->second;\n                eos_static_info(\"id=%s key=<hidden>\", id.c_str());\n              }\n\n              if (it->first.substr(0, 14) == \"sys.s3.bucket.\") {\n                std::string id = it->first.substr(14);\n                std::vector<std::string> svec;\n                eos::common::StringConversion::Tokenize(it->second, svec, \"|\");\n\n                for (size_t i = 0; i < svec.size(); i++) {\n                  if (svec[i][0] == '\\\"') {\n                    svec[i].erase(0, 1);\n                  }\n\n                  if (svec[i][svec[i].length() - 1] == '\\\"') {\n                    svec[i].erase(svec[i].length() - 1);\n                  }\n\n                  mS3ContainerSet[id].insert(svec[i]);\n                  eos_static_debug(\"id=%s bucket=%s\", id.c_str(),\n                                   svec[i].c_str());\n                }\n              }\n\n              if (it->first.substr(0, 12) == \"sys.s3.path.\") {\n                std::string bucket = it->first.substr(12);\n                mS3ContainerPath[bucket] = it->second;\n                eos_static_info(\"bucket=%s path=%s\", bucket.c_str(),\n                                it->second.c_str());\n              }\n            }\n          }\n\n          // store the modification time of the loaded s3 definitions\n          mStoreModificationTime = buf.st_ctime;\n        }\n      } else {\n        eos_static_info(\"skipping S3 configuration reload. \"\n                        \"Reason: no change detected since last refresh\");\n      }\n    } else {\n      eos_static_err(\"unable to stat S3 configuration container %s\",\n                     mS3DefContainer.c_str());\n    }\n  } else {\n    eos_static_info(\"skipping S3 configuration reload. \"\n                    \"Reason: refresh performed recently\");\n  }\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nS3Store::ListBuckets(const std::string& id)\n{\n  eos::common::RWMutexReadLock sLock(mStoreMutex);\n  eos::common::HttpResponse* response = 0;\n  std::string result = XML_V1_UTF8;\n  result += \"<ListAllMyBucketsResult xmlns=\\\"http://doc.s3.amazonaws.com/2006-03-01\\\">\";\n  result += \"<Owner><ID>\";\n  result += id;\n  result += \"</ID>\";\n  result += \"<Display>\";\n  result += id;\n  result += \"</Display>\";\n  result += \"</Owner>\";\n  result += \"<Buckets>\";\n\n  for (auto it = mS3ContainerSet[id].begin(); it != mS3ContainerSet[id].end();\n       it++) {\n    if (mS3ContainerPath.count(*it)) {\n      // check if we know how to map a bucket name into our regular namespace\n      std::string bucketpath = mS3ContainerPath[*it];\n      XrdOucErrInfo error;\n      eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n      eos::IContainerMD::XAttrMap map;\n      struct stat buf;\n\n      if (gOFS->_stat(bucketpath.c_str(), &buf, error, vid, (const char*) 0)\n          == SFS_OK) {\n        result += \"<Bucket>\";\n        result += \"<Name>\";\n        result += *it;\n        result += \"</Name>\";\n        result += \"<CreationDate>\";\n        result += eos::common::Timing::UnixTimestamp_to_ISO8601(buf.st_ctime);\n        result += \"</CreationDate>\";\n        result += \"</Bucket>\";\n      } else {\n        std::string errmsg = \"cannot find bucket path \";\n        errmsg += bucketpath;\n        errmsg += \" for bucket \";\n        errmsg += *it;\n        return eos::common::S3Handler::RestErrorResponse(\n                 eos::common::HttpResponse::NOT_FOUND,\n                 \"NoSuchBucket\",\n                 errmsg, *it, \"\");\n      }\n    }\n  }\n\n  result += \"</Buckets>\";\n  result += \"</ListAllMyBucketsResult>\";\n  response = new eos::common::PlainHttpResponse();\n  response->AddHeader(\"Content-Type\", \"application/xml\");\n  response->AddHeader(\"x-amz-id-2\", \"unknown\");\n  response->AddHeader(\"x-amz-request-id\", \"unknown\");\n  response->SetBody(result);\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nS3Store::ListBucket(const std::string& bucket, const std::string& query)\n{\n  using namespace eos::common;\n  XrdOucErrInfo error;\n  VirtualIdentity vid = VirtualIdentity::Root();\n  RWMutexReadLock sLock(mStoreMutex);\n  HttpResponse* response = 0;\n\n  if (!mS3ContainerPath.count(bucket)) {\n    // check if this bucket is configured\n    return S3Handler::RestErrorResponse(eos::common::HttpResponse::NOT_FOUND,\n                                        \"NoSuchBucket\",\n                                        \"Bucket does not exist!\",\n                                        bucket.c_str(), \"\");\n  } else {\n    // check if this bucket is mapped\n    struct stat buf;\n\n    if (gOFS->_stat(mS3ContainerPath[bucket].c_str(), &buf, error, vid,\n                    (const char*) 0) != SFS_OK) {\n      return S3Handler::RestErrorResponse(eos::common::HttpResponse::NOT_FOUND,\n                                          \"NoSuchBucket\",\n                                          \"Bucket is not mapped into the \"\n                                          \"namespace!\", bucket.c_str(), \"\");\n    }\n  }\n\n  XrdOucEnv parameter(query.c_str());\n  XrdOucString lPrefix, lBucket;\n  XrdMgmOfsDirectory bucketdir;\n  uint64_t cnt = 0;\n  uint64_t max_keys = 1000;\n  std::string marker = \"\";\n  std::string prefix = \"\";\n  bool marker_reached = true; //!< indicates start of output\n  const char* val = 0;\n\n  if ((val = parameter.Get(\"max-keys\"))) {\n    max_keys = strtoull(val, 0, 10);\n  }\n\n  if ((val = parameter.Get(\"marker\"))) {\n    marker = val;\n\n    if (marker == \"(null)\") {\n      marker = \"\";\n    }\n  }\n\n  if ((val = parameter.Get(\"prefix\"))) {\n    prefix = val;\n  }\n\n  if (marker.length()) {\n    marker_reached = false;\n  }\n\n  // handle ending slash in bucket and prefix paths\n  lBucket = mS3ContainerPath[bucket].c_str();\n\n  if (!lBucket.endswith(\"/\")) {\n    lBucket += \"/\";\n  }\n\n  lPrefix = prefix.c_str();\n\n  if (lPrefix.length() && !lPrefix.endswith(\"/\")) {\n    lPrefix += \"/\";\n  }\n\n  eos_static_info(\"msg=\\\"listing\\\" bucket=%s prefix=%s\", bucket.c_str(),\n                  lPrefix.c_str());\n  // Construct listing response\n  std::string result = XML_V1_UTF8;\n  result += \"<ListBucketResult xmlns=\\\"http://doc.s3.amazonaws.com/2006-03-01\\\">\";\n  result += \"<Name>\";\n  result += bucket;\n  result += \"</Name>\";\n\n  if (!prefix.length()) {\n    result += \"<Prefix/>\";\n  } else {\n    result += \"<Prefix>\";\n    result += prefix;\n    result += \"</Prefix>\";\n  }\n\n  if ((!marker.length() || (!marker.c_str()))) {\n    result += \"<Marker/>\";\n  } else {\n    result += \"<Marker>\";\n    result += marker;\n    result += \"</Marker>\";\n  }\n\n  result += \"<Delimiter>/</Delimiter>\";\n  result += \"<MaxKeys>\";\n  char smaxkeys[16];\n  snprintf(smaxkeys, sizeof(smaxkeys) - 1, \"%llu\",\n           (unsigned long long) max_keys);\n  result += smaxkeys;\n  result += \"</MaxKeys>\";\n  bool truncated = false;\n  size_t truncate_pos = result.length() + 13;\n  result += \"<IsTruncated>false</IsTruncated>\";\n  // list directory\n  std::string directory = lBucket.c_str();\n  directory += lPrefix.c_str();\n  int listrc = bucketdir.open(directory.c_str(), vid, (const char*) 0);\n\n  if (!listrc) {\n    const char* name = 0;\n\n    // loop over the directory contents\n    while ((name = bucketdir.nextEntry())) {\n      std::string entry = \"\";\n      std::string sname = name;\n\n      if ((sname == \".\") || (sname == \"..\")) {\n        continue;\n      }\n\n      // don't return more than max-keys\n      if (cnt++ > max_keys) {\n        truncated = true;\n        break;\n      }\n\n      // construct object name\n      std::string objectname = lPrefix.c_str();\n      objectname += sname;\n      std::string fullname = lBucket.c_str();\n      fullname += objectname;\n\n      // check if output should begin\n      if (!marker_reached) {\n        if (marker == objectname) {\n          marker_reached = true;\n        }\n\n        continue;\n      }\n\n      {\n        // attempt file metadata retrieval\n        eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n        std::shared_ptr<eos::IContainerMD> cmd;\n        std::shared_ptr<eos::IFileMD> fmd;\n        int errc = 0;\n\n        try {\n          fmd = gOFS->eosView->getFile(fullname);\n          entry = \"<Contents>\";\n          entry += \"<Key>\";\n          entry += objectname.c_str();\n          entry += \"</Key>\";\n          entry += \"<LastModified>\";\n          eos::IFileMD::ctime_t mtime;\n          fmd->getMTime(mtime);\n          entry += Timing::UnixTimestamp_to_ISO8601(mtime.tv_sec);\n          entry += \"</LastModified>\";\n          entry += \"<ETag>\\\"\";\n          eos::appendChecksumOnStringAsHex(fmd.get(), entry);\n          entry += \"\\\"</ETag>\";\n          entry += \"<Size>\";\n          std::string sconv;\n          entry += StringConversion::GetSizeString(sconv, (unsigned long long)\n                   fmd->getSize());\n          entry += \"</Size>\";\n          entry += \"<StorageClass>STANDARD</StorageClass>\";\n          entry += \"<Owner>\";\n          entry += \"<ID>\";\n          entry += Mapping::UidToUserName(fmd->getCUid(), errc);\n          entry += \"</ID>\";\n          entry += \"<DisplayName>\";\n          entry += Mapping::UidToUserName(fmd->getCUid(), errc);\n          entry += \":\";\n          entry += Mapping::GidToGroupName(fmd->getCGid(), errc);\n          entry += \"</DisplayName>\";\n          entry += \"</Owner>\";\n          entry += \"</Contents>\";\n        } catch (eos::MDException& e) {\n          fmd.reset();\n\n          if (e.getErrno() != ENOENT) {\n            errno = e.getErrno();\n            eos_static_err(\"msg=\\\"could not open file\\\" ec=%d emsg=\\\"%s\\\" filepath=%s\\n\",\n                           e.getErrno(), e.getMessage().str().c_str(),\n                           fullname.c_str());\n            return S3Handler::RestErrorResponse(\n                     eos::common::HttpResponse::INTERNAL_SERVER_ERROR,\n                     \"Internal Error\", \"Unable to open path\",\n                     fullname.c_str(), \"\");\n          }\n        }\n\n        // should be a container\n        if (!fmd) {\n          // attempt container metadata retrieval\n          try {\n            cmd = gOFS->eosView->getContainer(fullname);\n            entry = \"<Contents>\";\n            entry += \"<Key>\";\n            entry += objectname.c_str();\n            entry += \"/\";\n            entry += \"</Key>\";\n            entry += \"<LastModified>\";\n            eos::IContainerMD::ctime_t mtime;\n            cmd->getMTime(mtime);\n            entry += Timing::UnixTimestamp_to_ISO8601(mtime.tv_sec);\n            entry += \"</LastModified>\";\n            entry += \"<ETag>\";\n            entry += \"</ETag>\";\n            entry += \"<Size>0</Size>\";\n            entry += \"<StorageClass>STANDARD</StorageClass>\";\n            entry += \"<Owner>\";\n            entry += \"<ID>\";\n            entry += Mapping::UidToUserName(cmd->getCUid(), errc);\n            entry += \"</ID>\";\n            entry += \"<DisplayName>\";\n            entry += Mapping::UidToUserName(cmd->getCUid(), errc);\n            entry += \":\";\n            entry += Mapping::GidToGroupName(cmd->getCGid(), errc);\n            entry += \"</DisplayName>\";\n            entry += \"</Owner>\";\n            entry += \"</Contents>\";\n          } catch (eos::MDException& e) {\n            cmd.reset();\n            errno = e.getErrno();\n            eos_static_err(\"msg=\\\"could not open directory\\\" ec=%d emsg=\\\"%s\\\" dirpath=%s\\n\",\n                           e.getErrno(), e.getMessage().str().c_str(),\n                           fullname.c_str());\n            return S3Handler::RestErrorResponse(\n                     eos::common::HttpResponse::INTERNAL_SERVER_ERROR,\n                     \"Internal Error\", \"Unable to open path\",\n                     fullname.c_str(), \"\");\n          }\n        }\n      }\n\n      // append this entry to the final result\n      result += entry;\n    }\n  }\n\n  bucketdir.close();\n\n  if (truncated) {\n    result.replace(truncate_pos, 18, \"true</IsTruncated>\");\n  }\n\n  result += \"</ListBucketResult>\";\n  response = new PlainHttpResponse();\n  response->AddHeader(\"Content-Type\", \"application/xml\");\n  response->AddHeader(\"Connection\", \"close\");\n  response->SetBody(result);\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nS3Store::HeadBucket(const std::string& id,\n                    const std::string& bucket,\n                    const std::string& date)\n{\n  using namespace eos::common;\n  HttpResponse* response = 0;\n  XrdOucErrInfo error;\n  VirtualIdentity vid = VirtualIdentity::Nobody();\n  int errc = 0;\n  std::string username = id;\n  uid_t uid = Mapping::UserNameToUid(username, errc);\n\n  if (errc) {\n    // error mapping the s3 id to unix id\n    return S3Handler::RestErrorResponse(eos::common::HttpResponse::BAD_REQUEST,\n                                        \"InvalidArgument\",\n                                        \"Unable to map bucket id to virtual id\",\n                                        id.c_str(), \"\");\n  }\n\n  // set the bucket id as vid\n  vid.uid = uid;\n  vid.allowed_uids.insert(uid);\n  struct stat buf;\n  // build the bucket path\n  std::string bucketpath = mS3ContainerPath[bucket];\n\n  // stat this object\n  if (gOFS->_stat(bucketpath.c_str(), &buf, error, vid,\n                  (const char*) 0) != SFS_OK) {\n    if (error.getErrInfo() == ENOENT) {\n      return S3Handler::RestErrorResponse(eos::common::HttpResponse::NOT_FOUND,\n                                          \"NoSuchBucket\",\n                                          \"Unable stat requested bucket\",\n                                          id.c_str(), \"\");\n    } else {\n      return S3Handler::RestErrorResponse(eos::common::HttpResponse::BAD_REQUEST,\n                                          \"InvalidArgument\",\n                                          \"Unable to stat requested bucket!\",\n                                          id.c_str(), \"\");\n    }\n  } else {\n    if (!S_ISDIR(buf.st_mode)) {\n      return S3Handler::RestErrorResponse(eos::common::HttpResponse::NOT_FOUND,\n                                          \"NoSuchBucket\",\n                                          \"Unable stat requested object - is \"\n                                          \"an object\", id.c_str(), \"\");\n    }\n\n    response = new PlainHttpResponse();\n    // shift back the inode number to the original file id\n    buf.st_ino = eos::common::FileId::InodeToFid(buf.st_ino);\n    std::string sinode;\n    response->AddHeader(\"x-amz-id-2\",\n                        StringConversion::GetSizeString(sinode, (unsigned long long) buf.st_ino));\n    response->AddHeader(\"x-amz-request-id\",\n                        StringConversion::GetSizeString(sinode, (unsigned long long) buf.st_ino));\n    response->AddHeader(\"ETag\",\n                        StringConversion::GetSizeString(sinode, (unsigned long long) buf.st_ino));\n    response->AddHeader(\"Last-Modified\",\n                        Timing::UnixTimestamp_to_ISO8601(buf.st_mtime));\n    response->AddHeader(\"Date\", date);\n    response->AddHeader(\"Connection\", \"Keep-Alive\");\n    response->AddHeader(\"Server\", gOFS->HostName);\n    response->SetResponseCode(response->OK);\n    return response;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nS3Store::HeadObject(const std::string& id,\n                    const std::string& bucket,\n                    const std::string& path,\n                    const std::string& date)\n{\n  using namespace eos::common;\n  XrdOucErrInfo error;\n  VirtualIdentity vid = VirtualIdentity::Nobody();\n  HttpResponse* response = 0;\n  int errc = 0;\n  std::string username = id;\n  uid_t uid = Mapping::UserNameToUid(username, errc);\n\n  if (errc) {\n    // error mapping the s3 id to unix id\n    return S3Handler::RestErrorResponse(eos::common::HttpResponse::BAD_REQUEST,\n                                        \"InvalidArgument\",\n                                        \"Unable to map bucket id to virtual id\",\n                                        id.c_str(), \"\");\n  }\n\n  // set the bucket id as vid\n  vid.uid = uid;\n  vid.allowed_uids.insert(uid);\n  struct stat buf;\n  // build the full path for the request\n  std::string objectpath = mS3ContainerPath[bucket];\n\n  if (objectpath[objectpath.length() - 1] == '/') {\n    objectpath.erase(objectpath.length() - 1);\n  }\n\n  objectpath += path;\n\n  // stat this object\n  if (gOFS->_stat(objectpath.c_str(), &buf, error, vid,\n                  (const char*) 0) != SFS_OK) {\n    if (error.getErrInfo() == ENOENT) {\n      return S3Handler::RestErrorResponse(eos::common::HttpResponse::NOT_FOUND,\n                                          \"NoSuchKey\",\n                                          \"Unable stat requested object\",\n                                          id.c_str(), \"\");\n    } else {\n      return S3Handler::RestErrorResponse(eos::common::HttpResponse::BAD_REQUEST,\n                                          \"InvalidArgument\",\n                                          \"Unable to stat requested object!\",\n                                          id.c_str(), \"\");\n    }\n  } else {\n    if (S_ISDIR(buf.st_mode)) {\n      return S3Handler::RestErrorResponse(eos::common::HttpResponse::NOT_FOUND,\n                                          \"NoSuchKey\",\n                                          \"Unable stat requested object - \"\n                                          \"is a bucket subdirectory\",\n                                          id.c_str(), \"\");\n    }\n\n    // shift back the inode number to the original file id\n    buf.st_ino = eos::common::FileId::InodeToFid(buf.st_ino);\n    std::string sinode;\n    response = new PlainHttpResponse();\n    response->AddHeader(\"x-amz-id-2\",\n                        StringConversion::GetSizeString(sinode, (unsigned long long) buf.st_ino));\n    response->AddHeader(\"x-amz-request-id\",\n                        StringConversion::GetSizeString(sinode, (unsigned long long) buf.st_ino));\n    response->AddHeader(\"x-amz-version-id\",\n                        StringConversion::GetSizeString(sinode, (unsigned long long) buf.st_ino));\n    response->AddHeader(\"ETag\",\n                        StringConversion::GetSizeString(sinode, (unsigned long long) buf.st_ino));\n    response->AddHeader(\"Content-Length\",\n                        StringConversion::GetSizeString(sinode, (unsigned long long) buf.st_size));\n    response->AddHeader(\"Last-Modified\",\n                        Timing::UnixTimestamp_to_ISO8601(buf.st_mtime));\n    response->AddHeader(\"Date\", date);\n    response->AddHeader(\"Content-Type\", HttpResponse::ContentType(path));\n    response->AddHeader(\"Connection\", \"close\");\n    response->AddHeader(\"Server\", gOFS->HostName);\n    response->SetResponseCode(response->OK);\n    return response;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nS3Store::GetObject(eos::common::HttpRequest* request,\n                   const std::string& id,\n                   const std::string& bucket,\n                   const std::string& path,\n                   const std::string& query)\n{\n  using namespace eos::common;\n  std::string result;\n  XrdOucErrInfo error;\n  VirtualIdentity vid = VirtualIdentity::Nobody();\n  HttpResponse* response = 0;\n  int errc = 0;\n  std::string username = id;\n  uid_t uid = Mapping::UserNameToUid(username, errc);\n\n  if (errc) {\n    // error mapping the s3 id to unix id\n    return S3Handler::RestErrorResponse(eos::common::HttpResponse::BAD_REQUEST,\n                                        \"InvalidArgument\",\n                                        \"Unable to map bucket id to virtual id\",\n                                        id.c_str(), \"\");\n  }\n\n  // set the bucket id as vid\n  vid.uid = uid;\n  vid.allowed_uids.insert(uid);\n  struct stat buf;\n  // build the full path for the request\n  std::string objectpath = mS3ContainerPath[bucket];\n\n  if (objectpath[objectpath.length() - 1] == '/') {\n    objectpath.erase(objectpath.length() - 1);\n  }\n\n  objectpath += path;\n  // evalutate If-XX requests\n  time_t modified_since = 0;\n  time_t unmodified_since = 0;\n  unsigned long long inode_match = 0;\n  unsigned long long inode_none_match = 0;\n\n  if (request->GetHeaders().count(\"if-modified-since\")) {\n    modified_since = Timing::ISO8601_to_UnixTimestamp(request->GetHeaders()\n                     [\"if-modified-since\"]);\n  }\n\n  if (request->GetHeaders().count(\"if-unmodified-since\")) {\n    unmodified_since = Timing::ISO8601_to_UnixTimestamp(request->GetHeaders()\n                       [\"if-unmodified-since\"]);\n  }\n\n  if (request->GetHeaders().count(\"if-match\")) {\n    inode_match = strtoull(request->GetHeaders()[\"if-match\"].c_str(), 0, 10);\n  }\n\n  if (request->GetHeaders().count(\"if-none-match\")) {\n    inode_none_match = strtoull(request->GetHeaders()[\"if-none-match\"].c_str(),\n                                0, 10);\n  }\n\n  // stat this object\n  if (gOFS->_stat(objectpath.c_str(), &buf, error, vid,\n                  (const char*) 0) != SFS_OK) {\n    if (error.getErrInfo() == ENOENT) {\n      return S3Handler::RestErrorResponse(eos::common::HttpResponse::NOT_FOUND,\n                                          \"NoSuchKey\",\n                                          \"Unable stat requested object\",\n                                          id.c_str(), \"\");\n    } else {\n      return S3Handler::RestErrorResponse(eos::common::HttpResponse::BAD_REQUEST,\n                                          \"InvalidArgument\",\n                                          \"Unable to stat requested object!\",\n                                          id.c_str(), \"\");\n    }\n  } else {\n    // check if modified since was asked\n    if (modified_since && (buf.st_mtime <= modified_since)) {\n      return S3Handler::RestErrorResponse(\n               eos::common::HttpResponse::PRECONDITION_FAILED,\n               \"PreconditionFailed\",\n               \"Object was not modified since \"\n               \"specified time!\", path.c_str(), \"\");\n    }\n\n    // check if unmodified since was asekd\n    if (unmodified_since && (buf.st_mtime != unmodified_since)) {\n      return S3Handler::RestErrorResponse(eos::common::HttpResponse::NOT_MODIFIED,\n                                          \"NotModified\",\n                                          \"Object was modified since specified \"\n                                          \"time!\", path.c_str(), \"\");\n    }\n\n    // check if the matching inode was given\n    if (inode_match && (buf.st_ino != inode_match)) {\n      return S3Handler::RestErrorResponse(\n               eos::common::HttpResponse::PRECONDITION_FAILED,\n               \"PreconditionFailed\",\n               \"Object was modified!\",\n               path.c_str(), \"\");\n    }\n\n    // check if a non matching inode was given\n    if (inode_none_match && (buf.st_ino == inode_none_match)) {\n      return S3Handler::RestErrorResponse(eos::common::HttpResponse::NOT_MODIFIED,\n                                          \"NotModified\",\n                                          \"Object was not modified!\",\n                                          path.c_str(), \"\");\n    }\n\n    if (S_ISDIR(buf.st_mode)) {\n      return S3Handler::RestErrorResponse(eos::common::HttpResponse::NOT_FOUND,\n                                          \"NoSuchKey\",\n                                          \"Unable stat requested object - is a \"\n                                          \"bucket subdirectory\", id.c_str(), \"\");\n    }\n\n    // FILE requests\n    XrdSfsFile* file = gOFS->newFile((char*) id.c_str());\n\n    if (file) {\n      XrdSecEntity client(\"unix\");\n      client.name = strdup(id.c_str());\n      client.host = strdup(request->GetHeaders()[\"host\"].c_str());\n      client.tident = strdup(\"http\");\n      snprintf(client.prot, sizeof(client.prot) - 1, \"https\");\n      int rc = file->open(objectpath.c_str(), 0, 0, &client, query.c_str());\n\n      if (rc == SFS_REDIRECT) {\n        response = HttpServer::HttpRedirect(objectpath,\n                                            file->error.getErrText(),\n                                            file->error.getErrInfo(), false);\n        response->AddHeader(\"x-amz-website-redirect-location\",\n                            response->GetHeaders()[\"Location\"]);\n        std::string body = XML_V1_UTF8;\n        body += \"<Error>\"\n                \"<Code>TemporaryRedirect</Code>\"\n                \"<Message>Please re-send this request to the specified temporary \"\n                \"endpoint. Continue to use the original request endpoint for \"\n                \"future requests.</Message>\"\n                \"<Endpoint>\";\n        body += response->GetHeaders()[\"Location\"];\n        body += \"</Endpoint>\"\n                \"</Error>\";\n        response->SetBody(body);\n        eos_static_info(\"\\n\\n%s\\n\\n\", response->GetBody().c_str());\n      } else if (rc == SFS_ERROR) {\n        if (file->error.getErrInfo() == ENOENT) {\n          response = S3Handler::RestErrorResponse(eos::common::HttpResponse::NOT_FOUND,\n                                                  \"NoSuchKey\",\n                                                  \"The specified key does not exist\",\n                                                  path, \"\");\n        } else if (file->error.getErrInfo() == EPERM) {\n          response = S3Handler::RestErrorResponse(eos::common::HttpResponse::FORBIDDEN,\n                                                  \"AccessDenied\",\n                                                  \"Access Denied\",\n                                                  path, \"\");\n        } else {\n          response = S3Handler::RestErrorResponse(\n                       eos::common::HttpResponse::INTERNAL_SERVER_ERROR,\n                       \"Internal Error\",\n                       \"File currently unavailable\",\n                       path, \"\");\n        }\n      } else {\n        response = S3Handler::RestErrorResponse(\n                     eos::common::HttpResponse::INTERNAL_SERVER_ERROR,\n                     \"Internal Error\",\n                     \"File not accessible in this way\",\n                     path, \"\");\n      }\n\n      // clean up the object\n      delete file;\n    }\n  }\n\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nS3Store::PutObject(eos::common::HttpRequest* request,\n                   const std::string& id,\n                   const std::string& bucket,\n                   const std::string& path,\n                   const std::string& query)\n{\n  using namespace eos::common;\n  std::string result;\n  XrdOucErrInfo error;\n  VirtualIdentity vid = VirtualIdentity::Nobody();\n  HttpResponse* response = 0;\n  int errc = 0;\n  std::string username = id;\n  uid_t uid = Mapping::UserNameToUid(username, errc);\n\n  if (errc) {\n    // error mapping the s3 id to unix id\n    return S3Handler::RestErrorResponse(eos::common::HttpResponse::BAD_REQUEST,\n                                        \"InvalidArgument\",\n                                        \"Unable to map bucket id to virtual id\",\n                                        id.c_str(), \"\");\n  }\n\n  // set the bucket id as vid\n  vid.uid = uid;\n  vid.allowed_uids.insert(uid);\n  // build the full path for the request\n  std::string objectpath = mS3ContainerPath[bucket];\n\n  if (objectpath[objectpath.length() - 1] == '/') {\n    objectpath.erase(objectpath.length() - 1);\n  }\n\n  objectpath += path;\n  // FILE requests\n  XrdSfsFile* file = gOFS->newFile((char*) id.c_str());\n\n  if (file) {\n    XrdSecEntity client(\"unix\");\n    client.name = strdup(id.c_str());\n    client.host = strdup(request->GetHeaders()[\"host\"].c_str());\n    client.tident = strdup(\"http\");\n    snprintf(client.prot, sizeof(client.prot) - 1, \"https\");\n    // force MD5 checksums for S3 file creation\n    std::string newquery = query;\n    newquery.insert(0, \"&eos.checksum.noforce=1&eos.layout.checksum=md5\");\n    int rc = file->open(objectpath.c_str(), SFS_O_TRUNC, SFS_O_MKPTH, &client,\n                        newquery.c_str());\n\n    if (rc == SFS_REDIRECT) {\n      response = HttpServer::HttpRedirect(objectpath,\n                                          file->error.getErrText(),\n                                          file->error.getErrInfo(), false);\n      response->AddHeader(\"x-amz-website-redirect-location\",\n                          response->GetHeaders()[\"Location\"]);\n      std::string body = XML_V1_UTF8;\n      body += \"<Error>\"\n              \"<Code>TemporaryRedirect</Code>\"\n              \"<Message>Please re-send this request to the specified temporary \"\n              \"endpoint. Continue to use the original request endpoint for \"\n              \"future requests.</Message>\"\n              \"<Endpoint>\";\n      body += response->GetHeaders()[\"Location\"];\n      body += \"</Endpoint>\"\n              \"</Error>\";\n      response->SetBody(body);\n      eos_static_info(\"\\n\\n%s\\n\\n\", response->GetBody().c_str());\n    } else if (rc == SFS_ERROR) {\n      if (file->error.getErrInfo() == EPERM) {\n        response = S3Handler::RestErrorResponse(eos::common::HttpResponse::FORBIDDEN,\n                                                \"AccessDenied\",\n                                                \"Access Denied\",\n                                                path, \"\");\n      } else {\n        response = S3Handler::RestErrorResponse(\n                     eos::common::HttpResponse::INTERNAL_SERVER_ERROR,\n                     \"Internal Error\",\n                     \"File creation currently \"\n                     \"unavailable\", path, \"\");\n      }\n    } else {\n      response = S3Handler::RestErrorResponse(\n                   eos::common::HttpResponse::INTERNAL_SERVER_ERROR,\n                   \"Internal Error\",\n                   \"File not accessible in this way\",\n                   path, \"\");\n    }\n\n    // clean up the object\n    delete file;\n  }\n\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nS3Store::DeleteObject(eos::common::HttpRequest* request,\n                      const std::string& id,\n                      const std::string& bucket,\n                      const std::string& path)\n{\n  using namespace eos::common;\n  XrdOucErrInfo error;\n  VirtualIdentity vid = VirtualIdentity::Nobody();\n  HttpResponse* response = 0;\n  int errc = 0;\n  std::string username = id;\n  uid_t uid = Mapping::UserNameToUid(username, errc);\n\n  if (errc) {\n    // error mapping the s3 id to unix id\n    return S3Handler::RestErrorResponse(eos::common::HttpResponse::BAD_REQUEST,\n                                        \"InvalidArgument\",\n                                        \"Unable to map bucket id to virtual id\",\n                                        id.c_str(), \"\");\n  }\n\n  // set the bucket id as vid\n  vid.uid = uid;\n  vid.allowed_uids.insert(uid);\n  struct stat buf;\n  // build the full path for the request\n  std::string objectpath = mS3ContainerPath[bucket];\n\n  if (objectpath[objectpath.length() - 1] == '/') {\n    objectpath.erase(objectpath.length() - 1);\n  }\n\n  objectpath += path;\n\n  // stat this object\n  if (gOFS->_stat(objectpath.c_str(), &buf, error, vid,\n                  (const char*) 0) != SFS_OK) {\n    if (error.getErrInfo() == ENOENT) {\n      return S3Handler::RestErrorResponse(eos::common::HttpResponse::NOT_FOUND,\n                                          \"NoSuchKey\",\n                                          \"Unable to delete requested object\",\n                                          id.c_str(), \"\");\n    } else {\n      return S3Handler::RestErrorResponse(eos::common::HttpResponse::BAD_REQUEST,\n                                          \"InvalidArgument\",\n                                          \"Unable to delete requested object\",\n                                          id.c_str(), \"\");\n    }\n  } else {\n    XrdOucString info = \"mgm.cmd=rm&mgm.path=\";\n    info += objectpath.c_str();\n\n    if (S_ISDIR(buf.st_mode)) {\n      info += \"&mgm.option=r\";\n    }\n\n    ProcCommand cmd;\n    cmd.open(\"/proc/user\", info.c_str(), vid, &error);\n    cmd.close();\n    int rc = cmd.GetRetc();\n\n    if (rc != SFS_OK) {\n      if (error.getErrInfo() == EPERM) {\n        return S3Handler::RestErrorResponse(eos::common::HttpResponse::FORBIDDEN,\n                                            \"AccessDenied\",\n                                            \"Access Denied\",\n                                            path, \"\");\n      } else {\n        return S3Handler::RestErrorResponse(eos::common::HttpResponse::BAD_REQUEST,\n                                            \"InvalidArgument\",\n                                            \"Unable to delete requested object\",\n                                            id.c_str(), \"\");\n      }\n    } else {\n      response = new eos::common::PlainHttpResponse();\n      response->AddHeader(\"Connection\", \"close\");\n      response->AddHeader(\"Server\", gOFS->HostName);\n      response->SetResponseCode(response->NO_CONTENT);\n    }\n  }\n\n  return response;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/s3/S3Store.hh",
    "content": "// ----------------------------------------------------------------------\n// File: S3Store.hh\n// Author: Andreas-Joachim Peters & Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file  S3Store.hh\n *\n * @brief creates the S3 store object knowing ids, keys and containers\n *        and their mapping to the real namespace\n */\n\n#ifndef __EOSMGM_S3STORE__HH__\n#define __EOSMGM_S3STORE__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/http/HttpResponse.hh\"\n#include \"common/RWMutex.hh\"\n#include \"mgm/Namespace.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n#include <map>\n#include <set>\n#include <string>\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\nclass S3Store\n{\n\nprivate:\n  eos::common::RWMutex                         mStoreMutex;            //< mutex protecting all mS3xx variables\n  time_t                                       mStoreModificationTime; //< last modification time of the loaded store\n  time_t                                       mStoreReloadTime;       //< last time the store was refreshed\n  std::map<std::string, std::set<std::string>> mS3ContainerSet;        //< map pointing from user name to list of container\n  std::map<std::string, std::string>           mS3Keys;                //< map pointing from user name to user secret key\n  std::map<std::string, std::string>           mS3ContainerPath;       //< map pointing from container name to path\n  std::string                                  mS3DefContainer;        //< path where all s3 objects are defined\n\npublic:\n\n  /**\n   * Constructor\n   *\n   * @param s3defpath  path where all s3 objects will be defined\n   */\n  S3Store (const char* s3defpath);\n\n  /**\n   * Destructor\n   */\n  virtual ~S3Store () {};\n\n  /**\n   * Refresh function to reload keys from the namespace definition\n   */\n  void\n  Refresh ();\n\n  /**\n   * @return map pointing from user name to user secret key\n   */\n  inline std::map<std::string, std::string>&\n  GetKeys () { return mS3Keys; };\n\n  /**\n   * Get a list of all buckets for a given S3 request\n   *\n   * @param id  the S3 id of the client\n   *\n   * @return S3 HTTP response object\n   */\n  eos::common::HttpResponse*\n  ListBuckets (const std::string &id);\n\n  /**\n   * Get a bucket listing for a given S3 bucket\n   *\n   * @param bucket  the name of the bucket to list\n   * @param query   the client request query string\n   *\n   * @return S3 HTTP response object\n   */\n  eos::common::HttpResponse*\n  ListBucket (const std::string &bucket, const std::string &query);\n\n  /**\n   * Head a bucket (acts like stat on a bucket)\n   *\n   * @param id      the S3 id of the client\n   * @param bucket  the name of the bucket to head\n   * @param date    the request x-amz-date header\n   *\n   * @return S3 HTTP response object\n   */\n  eos::common::HttpResponse*\n  HeadBucket (const std::string &id,\n              const std::string &bucket,\n              const std::string &date);\n\n  /**\n   * Get metadata for an object\n   *\n   * @param id      the S3 id of the client\n   * @param bucket  the name of the bucket to head\n   * @param path    the request path\n   * @param date    the request x-amz-date header\n   *\n   * @return S3 HTTP response object\n   */\n  eos::common::HttpResponse*\n  HeadObject (const std::string &id,\n              const std::string &bucket,\n              const std::string &path,\n              const std::string &date);\n\n  /**\n   * Get an object (redirection)\n   *\n   * @param request  the client request object\n   * @param id       the S3 id of the client\n   * @param bucket   the name of the bucket to head\n   * @param path     the request path\n   * @param date     the request x-amz-date header\n   *\n   * @return S3 HTTP response object\n   */\n  eos::common::HttpResponse*\n  GetObject (eos::common::HttpRequest *request,\n             const std::string        &id,\n             const std::string        &bucket,\n             const std::string        &path,\n             const std::string        &query);\n\n  /**\n   * Create a new object (redirection)\n   *\n   * @param request  the client request object\n   * @param id       the S3 id of the client\n   * @param bucket   the name of the bucket to head\n   * @param path     the request path\n   * @param date     the request x-amz-date header\n   *\n   * @return S3 HTTP response object\n   */\n  eos::common::HttpResponse*\n  PutObject (eos::common::HttpRequest *request,\n             const std::string        &id,\n             const std::string        &bucket,\n             const std::string        &path,\n             const std::string        &query);\n\n  /**\n   * Delete an object from a bucket\n   *\n   * @param id      the S3 id of the client\n   * @param bucket  the name of the bucket to head\n   * @param path    the request path\n   *\n   * @return S3 HTTP response object\n   */\n  eos::common::HttpResponse*\n  DeleteObject (eos::common::HttpRequest *request,\n                const std::string        &id,\n                const std::string        &bucket,\n                const std::string        &path);\n\n};\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_END\n\n#endif\n\n"
  },
  {
    "path": "mgm/http/webdav/LockResponse.cc",
    "content": "// ----------------------------------------------------------------------\n// File: LockResponse.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"mgm/http/webdav/LockResponse.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Timing.hh\"\n#include \"common/Path.hh\"\n#include \"common/http/OwnCloud.hh\"\n\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucErrInfo.hh>\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nLockResponse::BuildResponse (eos::common::HttpRequest *request)\n{\n  using namespace rapidxml;\n\n  // Get the namespaces (if any)\n  ParseNamespaces();\n\n  eos_static_debug(\"\\n%s\", request->GetBody().c_str());\n  // Root node <lockinfo/>\n  xml_node<> *infoNode = mXMLRequestDocument.first_node();\n  if (!infoNode)\n  {\n    SetResponseCode(ResponseCodes::BAD_REQUEST);\n    return this;\n  }\n  xml_node<> *property = infoNode->first_node();\n  \n  // Build the response\n  // xml declaration\n  xml_node<> *decl = mXMLResponseDocument.allocate_node(node_declaration);\n  decl->append_attribute(AllocateAttribute(\"version\", \"1.0\"));\n  decl->append_attribute(AllocateAttribute(\"encoding\", \"utf-8\"));\n  mXMLResponseDocument.append_node(decl);\n\n  // <prop/> node\n  xml_node<> *propNode = AllocateNode(\"prop\");\n  propNode->append_attribute(AllocateAttribute(\"xmlns\", \"DAV:\"));\n  mXMLResponseDocument.append_node(propNode);\n\n  // <lockdiscovery/> node\n  xml_node<> *lockdiscoveryNode = AllocateNode(\"lockdiscovery\");\n  propNode->append_node(lockdiscoveryNode);\n\n  // <activelock/> node\n  xml_node<> *activelockNode = AllocateNode(\"activelock\");\n  lockdiscoveryNode->append_node(activelockNode);\n\n  // Find all the request properties\n  while (property) {\n    XrdOucString propertyName = property->name();\n    eos_static_debug(\"msg=\\\"found xml property: %s\\\" value=\\\"%s\\\"\", propertyName.c_str(), property->value());\n    xml_node<> *cloned_node = CloneNode(property);\n    activelockNode->append_node(cloned_node);\n    property = property->next_sibling();\n  }\n\n  // <timeout/> node\n  xml_node<> *timeoutNode = AllocateNode(\"timeout\");\n  SetValue(timeoutNode, \"Second-604800\");\n  activelockNode->append_node(timeoutNode);\n\n  // <depth/> node\n  xml_node<> *depthNode = AllocateNode(\"depth\");\n  SetValue(depthNode, \"Infinity\");\n  activelockNode->append_node(depthNode);\n\n  // <locktoken/> node\n  xml_node<> *locktokenNode = AllocateNode(\"locktoken\");\n  activelockNode->append_node(locktokenNode);\n  \n  // <href/> node\n  xml_node<> *hrefNode = AllocateNode(\"href\");\n  SetValue(hrefNode,\"opaquelocktoken:00000000-0000-0000-0000-000000000000\");\n  locktokenNode->append_node(hrefNode);\n  \n  std::string responseString;\n  rapidxml::print(std::back_inserter(responseString), mXMLResponseDocument, rapidxml::print_no_indenting);\n  mXMLResponseDocument.clear();\n\n  AddHeader(\"Content-Length\", std::to_string((long long) responseString.size()));\n  AddHeader(\"Content-Type\", \"application/xml; charset=utf-8\");\n  AddHeader(\"Lock-Token\", \"opaquelocktoken:00000000-0000-0000-0000-000000000000\");\n  SetBody(responseString);\n  return this;\n}\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/webdav/LockResponse.hh",
    "content": "// ----------------------------------------------------------------------\n// File: LockResponse.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   LockResponse.hh\n *\n * @brief  Class responsible for parsing a WebDAV LOCK request and\n *         building a dummy response.\n */\n\n#ifndef __EOSMGM_LOCK_RESPONSE__HH__\n#define __EOSMGM_LOCK_RESPONSE__HH__\n\n\n/*----------------------------------------------------------------------------*/\n#include \"mgm/http/webdav/WebDAVResponse.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"common/Mapping.hh\"\n#include \"mgm/http/rapidxml/rapidxml.hpp\"\n#include \"mgm/http/rapidxml/rapidxml_print.hpp\"\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN;\n\n\nclass LockResponse : public WebDAVResponse {\npublic:\n\nprotected:\n  eos::common::VirtualIdentity *mVirtualIdentity; //!< virtual identity for this client\n\npublic:\n\n  /**\n   * Constructor\n   *\n   * @param request  the client request object\n   */\n  LockResponse (eos::common::HttpRequest *request,\n                    eos::common::VirtualIdentity *vid) :\n  WebDAVResponse (request),\n  mVirtualIdentity (vid)\n  {\n  };\n\n  /**\n   * Destructor\n   */\n  virtual ~LockResponse ()\n  {\n  };\n\n\n  /**\n   * Build an appropriate response to the given LOCK request.\n   *\n   * @param request  the client request object\n   *\n   * @return the newly built response object\n   */\n  HttpResponse*\n  BuildResponse (eos::common::HttpRequest *request);\n};\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_END\n\n#endif /* __EOSMGM_LOCK_RESPONSE__HH__ */\n"
  },
  {
    "path": "mgm/http/webdav/PropFindResponse.cc",
    "content": "// ----------------------------------------------------------------------\n// File: PropFindResponse.cc\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/http/webdav/PropFindResponse.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/ofs/XrdMgmOfsDirectory.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Timing.hh\"\n#include \"common/Path.hh\"\n#include \"common/http/OwnCloud.hh\"\n#include <XrdOuc/XrdOucErrInfo.hh>\n\nEOSMGMNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\nchar dav_rfc3986[256] = {0};\nchar dav_html5[256] = {0};\n\n/*----------------------------------------------------------------------------*/\nvoid\ndav_uri_encode(unsigned char* s, char* enc, char* tb)\n{\n  for (; *s; s++) {\n    if (tb[*s]) {\n      sprintf(enc, \"%c\", tb[*s]);\n    } else {\n      sprintf(enc, \"%%%02X\", *s);\n    }\n\n    while (*++enc);\n  }\n}\n\nint\ndav_uri_decode(char* source, char* dest)\n{\n  int nLength;\n\n  for (nLength = 0; *source; nLength++) {\n    dest[nLength + 1] = 0;\n\n    if (*source == '%' && source[1] && source[2] && isxdigit(source[1]) &&\n        isxdigit(source[2])) {\n      source[1] -= source[1] <= '9' ? '0' : (source[1] <= 'F' ? 'A' : 'a') - 10;\n      source[2] -= source[2] <= '9' ? '0' : (source[2] <= 'F' ? 'A' : 'a') - 10;\n      dest[nLength] = 16 * source[1] + source[2];\n      source += 3;\n      continue;\n    }\n\n    dest[nLength] = *source++;\n  }\n\n  dest[nLength] = '\\0';\n  return nLength;\n}\n\n\n/*----------------------------------------------------------------------------*/\nstd::string\nPropFindResponse::EncodeURI(const char* uri)\n{\n  XrdOucString nUri;\n  char enc[(strlen(uri) + 1) * 3];\n  dav_uri_encode((unsigned char*) uri, enc, dav_rfc3986);\n  return std::string(enc);\n}\n\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nPropFindResponse::BuildResponse(eos::common::HttpRequest* request)\n{\n  using namespace rapidxml;\n  // Get the namespaces (if any)\n  ParseNamespaces();\n  eos_static_debug(\"\\n%s\", request->GetBody().c_str());\n  // Root node <propfind/>\n  xml_node<>* rootNode = mXMLRequestDocument.first_node();\n\n  if (!rootNode) {\n    SetResponseCode(ResponseCodes::BAD_REQUEST);\n    return this;\n  }\n\n  // Get the requested property types\n  ParseRequestPropertyTypes(rootNode);\n\n  if (mRequestPropertyTypes & PropertyTypes::GET_OCID) {\n    XrdOucErrInfo error;\n    eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n    std::string val;\n\n    if (gOFS->_attr_get(request->GetUrl().c_str(), error, rootvid, \"\",\n                        eos::common::OwnCloud::GetAllowSyncName(), val)) {\n      // Sync not allowed in this tree.\n      SetResponseCode(ResponseCodes::FORBIDDEN);\n      return this;\n    }\n  }\n\n  // Build the response\n  // xml declaration\n  xml_node<>* decl = mXMLResponseDocument.allocate_node(node_declaration);\n  decl->append_attribute(AllocateAttribute(\"version\", \"1.0\"));\n  decl->append_attribute(AllocateAttribute(\"encoding\", \"utf-8\"));\n  mXMLResponseDocument.append_node(decl);\n  // <multistatus/> node\n  xml_node<>* multistatusNode = AllocateNode(\"d:multistatus\");\n  multistatusNode->append_attribute(AllocateAttribute(\"xmlns:d\", \"DAV:\"));\n  multistatusNode->append_attribute(\n    AllocateAttribute(eos::common::OwnCloud::OwnCloudNs(),\n                      eos::common::OwnCloud::OwnCloudNsUrl()));\n  mXMLResponseDocument.append_node(multistatusNode);\n  // Is the requested resource a file or directory?\n  XrdOucErrInfo error;\n  struct stat statInfo;\n  std::string etag;\n  memset(&statInfo, 0, sizeof(struct stat));\n  // TODO: the status should be chcked ?!\n  std::string raw_path = request->GetUrl().c_str();\n  eos::mgm::NamespaceMap(raw_path, nullptr, *mVirtualIdentity);\n  (void) gOFS->_stat(raw_path.c_str(), &statInfo, error,\n                     *mVirtualIdentity, (const char*) 0, &etag);\n  // Figure out what we actually need to do\n  std::string depth = request->GetHeaders()[\"depth\"];\n  eos_static_debug(\"depth=%s, isdir=%d\", depth.c_str(),\n                   S_ISDIR(statInfo.st_mode));\n  xml_node<>* responseNode = 0;\n\n  if (depth == \"0\" || !S_ISDIR(statInfo.st_mode)) {\n    // Simply stat the file or direcAtory\n    responseNode = BuildResponseNode(request->GetUrl(), request->GetUrl(true));\n\n    if (responseNode) {\n      multistatusNode->append_node(responseNode);\n    } else {\n      return this;\n    }\n  } else if (depth == \"1\") {\n    // Stat the resource and all child resources\n    XrdMgmOfsDirectory directory;\n    int listrc = directory.open(request->GetUrl().c_str(), *mVirtualIdentity,\n                                (const char*) 0);\n    responseNode = BuildResponseNode(request->GetUrl().c_str(),\n                                     request->GetUrl(true).c_str());\n\n    if (responseNode) {\n      multistatusNode->append_node(responseNode);\n    }\n\n    if (!listrc) {\n      const char* val;\n\n      while ((val = directory.nextEntry())) {\n        XrdOucString entryname = val;\n\n        // don't display . .., atomic(+version) uploads and version directories\n        if (entryname.beginswith(EOS_COMMON_PATH_VERSION_FILE_PREFIX) ||\n            entryname.beginswith(EOS_COMMON_PATH_ATOMIC_FILE_PREFIX) ||\n            entryname.beginswith(EOS_WEBDAV_HIDE_IN_PROPFIND_PREFIX) ||\n            entryname.beginswith(\"...eos.ino...\") ||\n            (entryname == \".\") ||\n            (entryname == \"..\")) {\n          // skip over . .., and hidden files\n          continue;\n        }\n\n        // one response node for each file...\n        eos::common::Path path((request->GetUrl() + std::string(\"/\") + std::string(\n                                  val)).c_str());\n        eos::common::Path refpath((request->GetUrl(true) + std::string(\"/\") +\n                                   std::string(val)).c_str());\n        responseNode = BuildResponseNode(path.GetPath(), refpath.GetPath());\n\n        if (responseNode) {\n          multistatusNode->append_node(responseNode);\n        } else {\n          // We might have a failed stat in the BuildResponseNode if there are\n          // symlinks present\n          SetResponseCode(HttpResponse::OK);\n        }\n      }\n    } else {\n      eos_static_warning(\"msg=\\\"error opening directory - might be stalled/banned\\\"\");\n      SetResponseCode(ResponseCodes::FORBIDDEN);\n      return this;\n    }\n  } else if (depth == \"1,noroot\") {\n    // Stat all child resources but not the requested resource\n    SetResponseCode(HttpResponse::NOT_IMPLEMENTED);\n    return this;\n  } else if (depth == \"infinity\" || depth == \"\") {\n    // Recursively stat the resource and all child resources\n    SetResponseCode(HttpResponse::NOT_IMPLEMENTED);\n    return this;\n  }\n\n  std::string responseString;\n  rapidxml::print(std::back_inserter(responseString), mXMLResponseDocument,\n                  rapidxml::print_no_indenting);\n  mXMLResponseDocument.clear();\n  SetResponseCode(HttpResponse::MULTI_STATUS);\n  AddHeader(\"Content-Length\", std::to_string((long long) responseString.size()));\n  AddHeader(\"Content-Type\", \"application/xml; charset=utf-8\");\n  SetBody(responseString);\n  return this;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nPropFindResponse::ParseRequestPropertyTypes(rapidxml::xml_node<>* node)\n{\n  using namespace rapidxml;\n  // <prop/> node (could be multiple, could be <allprop/>)\n  xml_node<>* allpropNode = GetNode(node, \"allprop\");\n\n  if (allpropNode) {\n    mRequestPropertyTypes |= PropertyTypes::GET_CONTENT_LENGTH;\n    mRequestPropertyTypes |= PropertyTypes::GET_CONTENT_TYPE;\n    mRequestPropertyTypes |= PropertyTypes::GET_LAST_MODIFIED;\n    mRequestPropertyTypes |= PropertyTypes::GET_ETAG;\n    mRequestPropertyTypes |= PropertyTypes::CREATION_DATE;\n    mRequestPropertyTypes |= PropertyTypes::DISPLAY_NAME;\n    mRequestPropertyTypes |= PropertyTypes::RESOURCE_TYPE;\n    mRequestPropertyTypes |= PropertyTypes::CHECKED_IN;\n    mRequestPropertyTypes |= PropertyTypes::CHECKED_OUT;\n    mRequestPropertyTypes |= PropertyTypes::ALLPROP_MARKER;\n    return;\n  }\n\n  // It wasn't <allprop/>\n  xml_node<>* propNode = GetNode(node, \"prop\");\n\n  if (!propNode) {\n    eos_static_err(\"msg=\\\"no <prop/> node found in tree\\\"\");\n    return;\n  }\n\n  xml_node<>* property = propNode->first_node();\n\n  // Find all the request properties\n  while (property) {\n    XrdOucString propertyName = property->name();\n    eos_static_debug(\"msg=\\\"found xml property: %s\\\"\", propertyName.c_str());\n    int colon = 0;\n\n    if ((colon = propertyName.find(':')) != STR_NPOS) {\n      // Split node name into <ns>:<nodename>\n      // Ignore non DAV: namespaces for now\n      for (auto it = mDAVNamespaces.begin(); it != mDAVNamespaces.end(); ++it) {\n        std::string ns = it->first;\n\n        if (propertyName.beginswith(ns.c_str())) {\n          std::string prop(std::string(propertyName.c_str()), colon + 1);\n          mRequestPropertyTypes |= MapRequestPropertyType(prop);\n        }\n      }\n\n      for (auto it = mCustomNamespaces.begin(); it != mCustomNamespaces.end(); ++it) {\n        std::string ns = it->first;\n\n        if (propertyName.beginswith(ns.c_str())) {\n          std::string prop(std::string(propertyName.c_str()), colon + 1);\n          mRequestPropertyTypes |= MapRequestPropertyType(prop);\n        }\n      }\n    } else {\n      std::string prop(propertyName.c_str());\n      mRequestPropertyTypes |= MapRequestPropertyType(prop);\n    }\n\n    property = property->next_sibling();\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nrapidxml::xml_node<>*\nPropFindResponse::BuildResponseNode(const std::string& url,\n                                    const std::string& hrefurl)\n{\n  using namespace rapidxml;\n  XrdOucErrInfo error;\n  struct stat statInfo;\n  std::string etag;\n  std::string id;\n  bool allpropresponse = false;\n  XrdOucString urlp = url.c_str();\n  XrdOucString hrefp = hrefurl.c_str();\n\n  while (urlp.replace(\"//\", \"/\")) {\n  }\n\n  while (hrefp.replace(\"//\", \"/\")) {\n  }\n\n  // Is the requested resource a file or directory?\n  std::string raw_path = urlp.c_str();\n  eos::mgm::NamespaceMap(raw_path, nullptr, *mVirtualIdentity);\n  eos_static_debug(\"url_path=%s raw_path=%s\", urlp.c_str(), raw_path.c_str());\n\n  if (gOFS->_stat(raw_path.c_str(), &statInfo, error, *mVirtualIdentity,\n                  (const char*) 0, &etag)) {\n    eos_static_err(\"msg=\\\"error stating %s: %s\\\"\", urlp.c_str(),\n                   error.getErrText());\n\n    if (error.getErrInfo() == EACCES) {\n      SetResponseCode(ResponseCodes::FORBIDDEN);\n    } else {\n      SetResponseCode(ResponseCodes::NOT_FOUND);\n    }\n\n    return NULL;\n  }\n\n  // hide hardlinks\n  if (etag == \"hardlink\") {\n    // this is the 'best' guess to identify a hardlink entry (for now)\n    eos_static_err(\"msg=\\\"hiding hardlinkg %s: %s\\\"\", urlp.c_str(),\n                   error.getErrText());\n    SetResponseCode(ResponseCodes::NOT_FOUND);\n    return NULL;\n  }\n\n  eos_static_debug(\"url=%s etag=%s\", urlp.c_str(), etag.c_str());\n  // encode the url's\n  urlp = EncodeURI(urlp.c_str()).c_str();\n  hrefp = EncodeURI(hrefp.c_str()).c_str();\n  // <response/> node\n  xml_node<>* responseNode = AllocateNode(\"d:response\");\n  // <href/> node\n  xml_node<>* href = AllocateNode(\"d:href\");\n\n  if (S_ISDIR(statInfo.st_mode)) {\n    if (hrefp[hrefp.length() - 1] != '/') {\n      hrefp += \"/\";\n    }\n  }\n\n  SetValue(href, hrefp.c_str());\n  responseNode->append_node(href);\n  // <propstat/> node for \"found\" properties\n  xml_node<>* propstatFound = AllocateNode(\"d:propstat\");\n  responseNode->append_node(propstatFound);\n  // <status/> \"found\" node\n  xml_node<>* statusFound = AllocateNode(\"d:status\");\n  SetValue(statusFound, \"HTTP/1.1 200 OK\");\n  propstatFound->append_node(statusFound);\n  // <prop/> \"found\" node\n  xml_node<>* propFound = AllocateNode(\"d:prop\");\n  propstatFound->append_node(propFound);\n  // <propstat/> node for \"not found\" properties\n  xml_node<>* propstatNotFound = AllocateNode(\"d:propstat\");\n  responseNode->append_node(propstatNotFound);\n  // <status/> \"not found\" node\n  xml_node<>* statusNotFound = AllocateNode(\"d:status\");\n  SetValue(statusNotFound, \"HTTP/1.1 404 Not Found\");\n  propstatNotFound->append_node(statusNotFound);\n  // <prop/> \"not found\" node\n  xml_node<>* propNotFound = AllocateNode(\"d:prop\");\n  propstatNotFound->append_node(propNotFound);\n  xml_node<>* contentLength = 0;\n  xml_node<>* lastModified = 0;\n  xml_node<>* resourceType = 0;\n  xml_node<>* checkedIn = 0;\n  xml_node<>* checkedOut = 0;\n  xml_node<>* creationDate = 0;\n  xml_node<>* eTag = 0;\n  xml_node<>* displayName = 0;\n  xml_node<>* contentType = 0;\n  xml_node<>* quotaAvail = 0;\n  xml_node<>* quotaUsed = 0;\n  xml_node<>* ocid = 0;\n  xml_node<>* ocsize = 0;\n  xml_node<>* ocperm = 0;\n\n  if (mRequestPropertyTypes & PropertyTypes::GET_CONTENT_LENGTH) {\n    contentLength = AllocateNode(\"d:getcontentlength\");\n  }\n\n  if (mRequestPropertyTypes & PropertyTypes::GET_CONTENT_TYPE) {\n    contentType = AllocateNode(\"d:getcontenttype\");\n  }\n\n  if (mRequestPropertyTypes & PropertyTypes::GET_LAST_MODIFIED) {\n    lastModified = AllocateNode(\"d:getlastmodified\");\n  }\n\n  if (mRequestPropertyTypes & PropertyTypes::CREATION_DATE) {\n    creationDate = AllocateNode(\"d:creationdate\");\n  }\n\n  if (mRequestPropertyTypes & PropertyTypes::RESOURCE_TYPE) {\n    resourceType = AllocateNode(\"d:resourcetype\");\n  }\n\n  if (mRequestPropertyTypes & PropertyTypes::DISPLAY_NAME) {\n    displayName = AllocateNode(\"d:displayname\");\n  }\n\n  if (mRequestPropertyTypes & PropertyTypes::GET_ETAG) {\n    eTag = AllocateNode(\"d:getetag\");\n  }\n\n  if (mRequestPropertyTypes & PropertyTypes::CHECKED_IN) {\n    checkedIn = AllocateNode(\"d:checked-in\");\n  }\n\n  if (mRequestPropertyTypes & PropertyTypes::CHECKED_OUT) {\n    checkedOut = AllocateNode(\"d:checked-out\");\n  }\n\n  if (mRequestPropertyTypes & PropertyTypes::GET_OCID) {\n    ocid = AllocateNode(\"oc:id\");\n  }\n\n  if (mRequestPropertyTypes & PropertyTypes::GET_OCSIZE) {\n    ocsize = AllocateNode(\"oc:size\");\n  }\n\n  if (mRequestPropertyTypes & PropertyTypes::GET_OCPERM) {\n    ocperm = AllocateNode(\"oc:permissions\");\n  }\n\n  if (mRequestPropertyTypes & PropertyTypes::ALLPROP_MARKER) {\n    allpropresponse = true;\n  }\n\n  if ((S_ISDIR(statInfo.st_mode)) &&\n      ((mRequestPropertyTypes & PropertyTypes::QUOTA_AVAIL) ||\n       (mRequestPropertyTypes & PropertyTypes::QUOTA_USED))) {\n    // -----------------------------------------------------------\n    // retrieve the current quota\n    // -----------------------------------------------------------\n    XrdOucString path = url.c_str();\n\n    if (!path.endswith(\"/\")) {\n      path += \"/\";\n    }\n\n    while (path.replace(\"//\", \"/\")) {}\n\n    long long maxbytes = 0;\n    long long freebytes = 0;\n    long long maxfiles = 0;\n    long long freefiles = 0;\n    Quota::GetIndividualQuota(*mVirtualIdentity, path.c_str(), maxbytes, freebytes,\n                              maxfiles, freefiles, true);\n\n    if (mRequestPropertyTypes & PropertyTypes::QUOTA_AVAIL) {\n      std::string sQuotaAvail;\n      quotaAvail = AllocateNode(\"d:quota-available-bytes\");\n\n      if (quotaAvail) {\n        SetValue(quotaAvail, eos::common::StringConversion::GetSizeString(sQuotaAvail,\n                 (unsigned long long) freebytes));\n      }\n    }\n\n    if (mRequestPropertyTypes & PropertyTypes::QUOTA_USED) {\n      std::string sQuotaUsed;\n      quotaUsed = AllocateNode(\"d:quota-used-bytes\");\n      SetValue(quotaUsed, eos::common::StringConversion::GetSizeString(sQuotaUsed,\n               (unsigned long long) statInfo.st_size));\n    }\n  }\n\n  // getlastmodified, creationdate, displayname and getetag properties are\n  // common to all resources\n  if (lastModified) {\n    std::string lm = eos::common::Timing::utctime(\n                       statInfo.st_mtim.tv_sec);\n    SetValue(lastModified, lm.c_str());\n    propFound->append_node(lastModified);\n  }\n\n  if (creationDate) {\n    std::string cd = eos::common::Timing::UnixTimestamp_to_ISO8601(\n                       statInfo.st_ctim.tv_sec);\n    SetValue(creationDate, cd.c_str());\n    propFound->append_node(creationDate);\n  }\n\n  if (eTag) {\n    SetValue(eTag, etag.c_str());\n    propFound->append_node(eTag);\n  }\n\n  if (ocid) {\n    SetValue(ocid, eos::common::StringConversion::GetSizeString(id,\n             (unsigned long long) statInfo.st_ino));\n    propFound->append_node(ocid);\n  }\n\n  if (ocsize) {\n    SetValue(ocsize, eos::common::StringConversion::GetSizeString(id,\n             (unsigned long long) statInfo.st_size));\n    propFound->append_node(ocsize);\n  }\n\n  if (ocperm) {\n    // test access permissions\n    std::string oc_perm = \"\";\n    gOFS->acc_access(url.c_str(), error, *mVirtualIdentity, oc_perm);\n    SetValue(ocperm, oc_perm.c_str());\n    propFound->append_node(ocperm);\n  }\n\n  if (displayName) {\n    eos::common::Path path(urlp.c_str());\n    eos_static_debug(\"msg=\\\"display name: %s\\\"\", path.GetName());\n    SetValue(displayName, path.GetName());\n    propFound->append_node(displayName);\n  }\n\n  // Directory\n  if (S_ISDIR(statInfo.st_mode)) {\n    if (resourceType) {\n      xml_node<>* container = AllocateNode(\"d:collection\");\n      resourceType->append_node(container);\n      propFound->append_node(resourceType);\n    }\n\n    if (!allpropresponse) {\n      // ANDROID does not digest this response properly in allprop requests\n      if (contentLength) {\n        propNotFound->append_node(contentLength);\n      }\n    }\n\n    if (contentType) {\n      SetValue(contentType, \"httpd/unix-directory\");\n      propFound->append_node(contentType);\n    }\n\n    if (quotaAvail) {\n      propFound->append_node(quotaAvail);\n    }\n\n    if (quotaUsed) {\n      propFound->append_node(quotaUsed);\n    }\n  }\n  // File\n  else {\n    if (resourceType) {\n      propFound->append_node(resourceType);\n    }\n\n    if (contentLength) {\n      SetValue(contentLength, std::to_string((long long) statInfo.st_size).c_str());\n      propFound->append_node(contentLength);\n    }\n\n    if (contentType) {\n      SetValue(contentType, HttpResponse::ContentType(url.c_str()).c_str());\n      propFound->append_node(contentType);\n    }\n  }\n\n  // We don't use these (yet)\n  if (!allpropresponse) {\n    // ANDROID does not digest this response properly in allprop requests\n    if (checkedIn) {\n      propNotFound->append_node(checkedIn);\n    }\n\n    if (checkedOut) {\n      propNotFound->append_node(checkedOut);\n    }\n  }\n\n  return responseNode;\n}\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/webdav/PropFindResponse.hh",
    "content": "// ----------------------------------------------------------------------\n// File: PropFindResponse.hh\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   PropFindResponse.hh\n *\n * @brief  Class responsible for parsing a WebDAV PROPFIND request and\n *         building a response.\n */\n\n#ifndef __EOSMGM_PROPFIND_RESPONSE__HH__\n#define __EOSMGM_PROPFIND_RESPONSE__HH__\n\n#define EOS_WEBDAV_HIDE_IN_PROPFIND_PREFIX \".sys.dav.hide#.\"\n\n/*----------------------------------------------------------------------------*/\n#include \"mgm/http/webdav/WebDAVResponse.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"common/Mapping.hh\"\n#include \"common/Logging.hh\"\n#include \"mgm/http/rapidxml/rapidxml.hpp\"\n#include \"mgm/http/rapidxml/rapidxml_print.hpp\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucErrInfo.hh>\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN;\n\n\n//\nextern char dav_rfc3986[256];\nextern char dav_html5[256];\n\n/**\n * URI decoding routine\n */\nextern int dav_uri_decode (char* source, char* dest );\n\nclass PropFindResponse : public WebDAVResponse {\npublic:\n\n  /**\n   * PROPFIND available property types\n   */\n  enum PropertyTypes {\n    NONE = 0x0000,\n    CREATION_DATE = 0x0001,\n    GET_CONTENT_LENGTH = 0x0002,\n    GET_LAST_MODIFIED = 0x0004,\n    RESOURCE_TYPE = 0x0008,\n    CHECKED_IN = 0x0010,\n    CHECKED_OUT = 0x0020,\n    DISPLAY_NAME = 0x0040,\n    GET_CONTENT_TYPE = 0x0080,\n    GET_ETAG = 0x0100,\n    QUOTA_AVAIL = 0x0200,\n    QUOTA_USED = 0x0400,\n    GET_OCID = 0x0800,\n    GET_OCSIZE = 0x1000,\n    GET_OCPERM = 0x2000,\n    ALLPROP_MARKER = 0xf000\n  };\n\nprotected:\n  int mRequestPropertyTypes; //!< properties that were requested\n  eos::common::VirtualIdentity *mVirtualIdentity; //!< virtual identity for this client\n\npublic:\n\n  /**\n   * Constructor\n   *\n   * @param request  the client request object\n   */\n  PropFindResponse (eos::common::HttpRequest *request,\n                    eos::common::VirtualIdentity *vid) :\n    WebDAVResponse (request), mRequestPropertyTypes (NONE),\n    mVirtualIdentity (vid)\n  {\n    static bool initialized = false;\n    if (!initialized)\n    {\n      // initialize encoding table\n      for (int i = 0; i < 256; i++)\n      {\n        dav_rfc3986[i] = isalnum(i) || i == '-' || i == '.' || i == '_'\n          || i == '~' || i == '/'\n          ? i : 0;\n        dav_html5[i] = isalnum(i) || i == '*' || i == '-' || i == '.' || i == '_'\n          ? i : (i == ' ') ? '+' : 0;\n      }\n    }\n  };\n\n  /**\n   * Destructor\n   */\n  virtual ~PropFindResponse ()\n  {\n  };\n\n\n  /**\n   * Build an appropriate response to the given PROPFIND request.\n   *\n   * @param request  the client request object\n   *\n   * @return the newly built response object\n   */\n  HttpResponse*\n  BuildResponse (eos::common::HttpRequest *request);\n\n  /**\n   * Check the request XML to find out which properties were requested and\n   * will therefore need to be returned.\n   *\n   * @param node  the root node of the PROPFIND request body\n   */\n  void\n  ParseRequestPropertyTypes (rapidxml::xml_node<> *node);\n\n\n  /**\n   * Build a response XML <response/> node containing the properties that were\n   * requested, whether they were found or not, etc (see RFC)\n   *\n   * @param url  the URL of the resource to build a response node for\n   *\n   * @return the newly build response node\n   */\n  rapidxml::xml_node<>*\n  BuildResponseNode (const std::string &url, const std::string &hrefurl);\n\n  /**\n   * Convert the given property type string into its integer constant\n   * representation.\n   *\n   * @param property  the property type string to convert\n   *\n   * @return the converted property string as an integer\n   */\n  inline PropertyTypes\n  MapRequestPropertyType (std::string property)\n  {\n    if (property == \"getcontentlength\") return GET_CONTENT_LENGTH;\n    else if (property == \"getcontenttype\") return GET_CONTENT_TYPE;\n    else if (property == \"getlastmodified\") return GET_LAST_MODIFIED;\n    else if (property == \"getetag\") return GET_ETAG;\n    else if (property == \"displayname\") return DISPLAY_NAME;\n    else if (property == \"creationdate\") return CREATION_DATE;\n    else if (property == \"resourcetype\") return RESOURCE_TYPE;\n    else if (property == \"checked-in\") return CHECKED_IN;\n    else if (property == \"checked-out\") return CHECKED_OUT;\n    else if (property == \"quota-available-bytes\") return QUOTA_AVAIL;\n    else if (property == \"quota-used-bytes\") return QUOTA_USED;\n    else if (property == \"id\") return GET_OCID;\n    else if (property == \"size\") return GET_OCSIZE;\n    else if (property == \"permissions\") return GET_OCPERM;\n    else return NONE;\n  }\n\n  /**\n   * Encode an URI\n   *\n   * @param path is the URI to encode\n   *\n   * @return an sdt::string with the encoded URI\n   */\n  std::string\n  EncodeURI (const char* uri);\n};\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_END\n\n#endif /* __EOSMGM_PROPFIND_RESPONSE__HH__ */\n"
  },
  {
    "path": "mgm/http/webdav/PropPatchResponse.cc",
    "content": "// ----------------------------------------------------------------------\n// File: PropPatchResponse.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"mgm/http/webdav/PropPatchResponse.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Timing.hh\"\n#include \"common/Path.hh\"\n#include \"common/http/OwnCloud.hh\"\n\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucErrInfo.hh>\n/*----------------------------------------------------------------------------*/\n\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nPropPatchResponse::BuildResponse(eos::common::HttpRequest* request)\n{\n  using namespace rapidxml;\n  // Get the namespaces (if any)\n  ParseNamespaces();\n  eos_static_debug(\"\\n%s\", request->GetBody().c_str());\n  // Root node <propertyupdate/>\n  xml_node<>* updateNode = mXMLRequestDocument.first_node();\n\n  if (!updateNode) {\n    SetResponseCode(ResponseCodes::BAD_REQUEST);\n    return this;\n  }\n\n  // Build the response\n  // xml declaration\n  xml_node<>* decl = mXMLResponseDocument.allocate_node(node_declaration);\n  decl->append_attribute(AllocateAttribute(\"version\", \"1.0\"));\n  decl->append_attribute(AllocateAttribute(\"encoding\", \"utf-8\"));\n  mXMLResponseDocument.append_node(decl);\n  // <multistatus/> node\n  xml_node<>* multistatusNode = AllocateNode(\"d:multistatus\");\n  multistatusNode->append_attribute(AllocateAttribute(\"xmlns:d\", \"DAV:\"));\n  // add custom namespaces\n  NamespaceMap::const_iterator it;\n\n  for (it = mCustomNamespaces.begin(); it != mCustomNamespaces.end(); ++it) {\n    std::string ns = \"xmlns:\";\n    ns += it->first;\n    multistatusNode->append_attribute(AllocateAttribute(ns.c_str(),\n                                      it->second.c_str()));\n  }\n\n  mXMLResponseDocument.append_node(multistatusNode);\n  // <d:response/> node\n  xml_node<>* responseNode = AllocateNode(\"d:response\");\n  multistatusNode->append_node(responseNode);\n  // <d:href/> node\n  xml_node<>* hrefNode = AllocateNode(\"d:href\");\n  responseNode->append_node(hrefNode);\n  // now get all the set/remove nodes and send a fake OK for each of them\n  xml_node<>* setNode = GetNode(updateNode, \"set\");\n  xml_node<>* removeNode = GetNode(updateNode, \"remove\");\n\n  if (setNode) {\n    xml_node<>* propNode = GetNode(setNode, \"prop\");\n\n    if (propNode) {\n      xml_node<>* prop = propNode->first_node();\n\n      while (prop) {\n        xml_node<>* propStat = AllocateNode(\"d:propstat\");\n        responseNode->append_node(propStat);\n        xml_node<>* propResponse = AllocateNode(\"d:prop\");\n        propStat->append_node(propResponse);\n        xml_node<>* propKey = AllocateNode(prop->name());\n        propResponse->append_node(propKey);\n        xml_node<>* status = AllocateNode(\"d:status\");\n        SetValue(status, \"HTTP/1.1 200 OK\");\n        propStat->append_node(status);\n        prop = prop->next_sibling();\n      }\n    }\n  }\n\n  if (removeNode) {\n    xml_node<>* propNode = GetNode(removeNode, \"prop\");\n\n    if (propNode) {\n      xml_node<>* prop = propNode->first_node();\n\n      while (prop) {\n        xml_node<>* propStat = AllocateNode(\"d:propstat\");\n        responseNode->append_node(propStat);\n        xml_node<>* propResponse = AllocateNode(\"d:prop\");\n        propStat->append_node(propResponse);\n        xml_node<>* propKey = AllocateNode(prop->name());\n        propResponse->append_node(propKey);\n        xml_node<>* status = AllocateNode(\"d:status\");\n        SetValue(status, \"HTTP/1.1 200 OK\");\n        propStat->append_node(status);\n        prop = prop->next_sibling();\n      }\n    }\n  }\n\n  std::string responseString;\n  rapidxml::print(std::back_inserter(responseString), mXMLResponseDocument,\n                  rapidxml::print_no_indenting);\n  mXMLResponseDocument.clear();\n  SetResponseCode(HttpResponse::MULTI_STATUS);\n  AddHeader(\"Content-Length\", std::to_string((long long) responseString.size()));\n  AddHeader(\"Content-Type\", \"application/xml; charset=utf-8\");\n  SetBody(responseString);\n  return this;\n}\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/webdav/PropPatchResponse.hh",
    "content": "// ----------------------------------------------------------------------\n// File: PropPatchResponse.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   PropPatchResponse.hh\n *\n * @brief  Class responsible for parsing a WebDAV PROPPATCH request and\n *         building a dummy response.\n */\n\n#ifndef __EOSMGM_PROPPATCH_RESPONSE__HH__\n#define __EOSMGM_PROPPATCH_RESPONSE__HH__\n\n\n/*----------------------------------------------------------------------------*/\n#include \"mgm/http/webdav/WebDAVResponse.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"common/Mapping.hh\"\n#include \"mgm/http/rapidxml/rapidxml.hpp\"\n#include \"mgm/http/rapidxml/rapidxml_print.hpp\"\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN;\n\n\nclass PropPatchResponse : public WebDAVResponse {\npublic:\n\nprotected:\n  eos::common::VirtualIdentity *mVirtualIdentity; //!< virtual identity for this client\n\npublic:\n\n  /**\n   * Constructor\n   *\n   * @param request  the client request object\n   */\n  PropPatchResponse (eos::common::HttpRequest *request,\n                    eos::common::VirtualIdentity *vid) :\n  WebDAVResponse (request),\n  mVirtualIdentity (vid)\n  {\n  };\n\n  /**\n   * Destructor\n   */\n  virtual ~PropPatchResponse ()\n  {\n  };\n\n\n  /**\n   * Build an appropriate response to the given PROPPATCH request.\n   *\n   * @param request  the client request object\n   *\n   * @return the newly built response object\n   */\n  HttpResponse*\n  BuildResponse (eos::common::HttpRequest *request);\n};\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_END\n\n#endif /* __EOSMGM_PROPPATCH_RESPONSE__HH__ */\n"
  },
  {
    "path": "mgm/http/webdav/WebDAVHandler.cc",
    "content": "// ----------------------------------------------------------------------\n// File: WebDAVHandler.cc\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"mgm/http/HttpServer.hh\"\n#include \"mgm/http/webdav/WebDAVHandler.hh\"\n#include \"mgm/http/webdav/PropFindResponse.hh\"\n#include \"mgm/http/webdav/PropPatchResponse.hh\"\n#include \"mgm/http/webdav/LockResponse.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"common/http/PlainHttpResponse.hh\"\n#include \"common/http/OwnCloud.hh\"\n#include \"common/Logging.hh\"\n#ifdef __clang__\n#pragma clang diagnostic ignored \"-Wformat-security\"\n#endif\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\nbool\nWebDAVHandler::Matches(const std::string& meth, HeaderMap& headers)\n{\n  int method =  ParseMethodString(meth);\n\n  if (method == PROPFIND || method == PROPPATCH || method == MKCOL ||\n      method == COPY     || method == MOVE      || method == LOCK  ||\n      method == UNLOCK) {\n    eos_static_debug(\"msg=\\\"matched webdav protocol for request\\\"\");\n    return true;\n  } else {\n    return false;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nWebDAVHandler::HandleRequest(eos::common::HttpRequest* request)\n{\n  eos_static_debug(\"msg=\\\"handling webdav request\\\"\");\n  eos::common::HttpResponse* response = 0;\n  request->AddEosApp();\n  int meth = ParseMethodString(request->GetMethod());\n\n  {\n    // call the routing module before doing anything with WebDAV\n    int port;\n    std::string host;\n    int stall_timeout = 0;\n\n    if (gOFS->ShouldRoute(\n            __FUNCTION__, 0, *mVirtualIdentity, request->GetUrl().c_str(),\n            request->GetQuery().c_str(), host, port, stall_timeout)) {\n      response = HttpServer::HttpRedirect(request->GetUrl().c_str(),\n                                          host.c_str(), port, false);\n      mHttpResponse = response;\n      return;\n    }\n  }\n\n  switch (meth) {\n  case PROPFIND:\n    gOFS->MgmStats.Add(\"Http-PROPFIND\", mVirtualIdentity->uid,\n                       mVirtualIdentity->gid, 1);\n    response = new PropFindResponse(request, mVirtualIdentity);\n    break;\n\n  case PROPPATCH:\n    gOFS->MgmStats.Add(\"Http-PROPPATCH\", mVirtualIdentity->uid,\n                       mVirtualIdentity->gid, 1);\n    response = new PropPatchResponse(request, mVirtualIdentity);\n    break;\n\n  case MKCOL:\n    gOFS->MgmStats.Add(\"Http-MKCOL\", mVirtualIdentity->uid, mVirtualIdentity->gid,\n                       1);\n    response = MkCol(request);\n    break;\n\n  case COPY:\n    gOFS->MgmStats.Add(\"Http-COPY\", mVirtualIdentity->uid, mVirtualIdentity->gid,\n                       1);\n    response = Copy(request);\n    break;\n\n  case MOVE:\n    gOFS->MgmStats.Add(\"Http-MOVE\", mVirtualIdentity->uid, mVirtualIdentity->gid,\n                       1);\n    response = Move(request);\n    break;\n\n  case LOCK:\n    gOFS->MgmStats.Add(\"Http-LOCK\", mVirtualIdentity->uid, mVirtualIdentity->gid,\n                       1);\n    response = new LockResponse(request, mVirtualIdentity);\n    break;\n\n  case UNLOCK:\n    gOFS->MgmStats.Add(\"Http-UNLOCK\", mVirtualIdentity->uid, mVirtualIdentity->gid,\n                       1);\n    response = new eos::common::PlainHttpResponse();\n    response->SetResponseCode(eos::common::HttpResponse::NO_CONTENT);\n    break;\n\n  default:\n    response = new eos::common::PlainHttpResponse();\n    response->SetResponseCode(eos::common::HttpResponse::BAD_REQUEST);\n    break;\n  }\n\n  mHttpResponse = response->BuildResponse(request);\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nWebDAVHandler::MkCol(eos::common::HttpRequest* request)\n{\n  eos::common::HttpResponse* response = 0;\n  XrdSecEntity client;\n  std::string url = request->GetUrl();\n  eos_static_info(\"method=MKCOL path=%s\", url.c_str());\n  client.name   = const_cast<char*>(mVirtualIdentity->name.c_str());\n  client.host   = const_cast<char*>(mVirtualIdentity->host.c_str());\n  client.tident = const_cast<char*>(mVirtualIdentity->tident.c_str());\n  snprintf(client.prot, sizeof(client.prot) - 1, \"%s\",\n           mVirtualIdentity->prot.c_str());\n\n  if (!request->GetUrl().size()) {\n    response = HttpServer::HttpError(\"path name required\",\n                                     response->BAD_REQUEST);\n  } else if (*request->GetBodySize() != 0) {\n    // we do not support request bodies with MKCOL requests\n    response = HttpServer::HttpError(\"request body not supported\",\n                                     response->UNSUPPORTED_MEDIA_TYPE);\n  } else {\n    XrdSfsMode    mode = 0;\n    int           rc   = 0;\n    XrdOucErrInfo error(mVirtualIdentity->tident.c_str());\n    ino_t new_inode;\n    rc = gOFS->mkdir(request->GetUrl().c_str(), mode, error,\n                     &client, (const char*) 0, &new_inode);\n\n    if (rc != SFS_OK) {\n      if (rc == SFS_ERROR) {\n        if (error.getErrInfo() == EEXIST) {\n          // directory exists\n          response = HttpServer::HttpError(error.getErrText(),\n                                           response->METHOD_NOT_ALLOWED);\n        } else if (error.getErrInfo() == ENOENT) {\n          // parent directory does not exist\n          response = HttpServer::HttpError(error.getErrText(),\n                                           response->CONFLICT);\n        } else if (error.getErrInfo() == EPERM) {\n          // not permitted\n          response = HttpServer::HttpError(error.getErrText(),\n                                           response->FORBIDDEN);\n        } else if (error.getErrInfo() == ENOSPC) {\n          // no space left\n          response = HttpServer::HttpError(error.getErrText(),\n                                           response->INSUFFICIENT_STORAGE);\n        } else {\n          // some other error\n          response = HttpServer::HttpError(error.getErrText(),\n                                           error.getErrInfo());\n        }\n      } else if (rc == SFS_REDIRECT) {\n        // redirection\n        response = HttpServer::HttpRedirect(request->GetUrl(),\n                                            error.getErrText(),\n                                            error.getErrInfo(), false);\n      } else if (rc == SFS_STALL) {\n        // stall\n        response = HttpServer::HttpStall(error.getErrText(),\n                                         error.getErrInfo());\n      } else {\n        // something unexpected\n        response = HttpServer::HttpError(error.getErrText(),\n                                         error.getErrInfo());\n      }\n    } else {\n      // everything went well\n      response = new eos::common::PlainHttpResponse();\n      char sinode[16];\n      snprintf(sinode, sizeof(sinode), \"%llu\", (unsigned long long) new_inode);\n      response->AddHeader(\"OC-FileId\", sinode);\n      response->SetResponseCode(response->CREATED);\n    }\n  }\n\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nWebDAVHandler::Move(eos::common::HttpRequest* request)\n{\n  eos::common::HttpResponse* response = 0;\n  XrdOucString prot, port;\n  std::string destination;\n  const char* dest = nullptr;\n\n  if (!(dest = eos::common::StringConversion::ParseUrl(\n                 request->GetHeaders()[\"destination\"].c_str(), prot, port))) {\n    eos_static_warning(\"could not process destination header=%s\",\n                       request->GetHeaders()[\"destination\"].c_str());\n  }\n\n  destination = dest ? dest : \"\";\n  char encode_destination[1024];\n  snprintf(encode_destination, sizeof(encode_destination), \"%s\",\n           destination.c_str());\n  char decode_destination[1024];\n  decode_destination[0] = 0;\n\n  if (destination.length() < sizeof(decode_destination)) {\n    ::dav_uri_decode(encode_destination, decode_destination);\n    destination = decode_destination;\n  }\n\n  // owncloud protocol patch\n  XrdOucString spath = destination.c_str();\n  eos::common::OwnCloud::OwnCloudRemapping(spath, request);\n  eos::common::OwnCloud::ReplaceRemotePhp(spath);\n  destination = spath.c_str();\n  eos_static_info(\"method=MOVE src=\\\"%s\\\", dest=\\\"%s\\\"\",\n                  request->GetUrl().c_str(), destination.c_str());\n  XrdSecEntity client;\n  client.name   = const_cast<char*>(mVirtualIdentity->name.c_str());\n  client.host   = const_cast<char*>(mVirtualIdentity->host.c_str());\n  client.tident = const_cast<char*>(mVirtualIdentity->tident.c_str());\n  snprintf(client.prot, sizeof(client.prot) - 1, \"%s\",\n           mVirtualIdentity->prot.c_str());\n\n  if (!request->GetUrl().size()) {\n    response = HttpServer::HttpError(\"source path required\",\n                                     response->BAD_REQUEST);\n  } else if (!destination.size()) {\n    response = HttpServer::HttpError(\"destination invalid\",\n                                     response->BAD_REQUEST);\n  } else if (request->GetUrl() == destination) {\n    response = HttpServer::HttpError(\"destination must be different from source\",\n                                     response->FORBIDDEN);\n  } else {\n    int           rc = 0;\n    XrdOucErrInfo error(mVirtualIdentity->tident.c_str());\n    rc = gOFS->rename(request->GetUrl().c_str(),\n                      destination.c_str(),\n                      error, *mVirtualIdentity, 0, 0, false);\n\n    if (rc != SFS_OK) {\n      if (rc == SFS_ERROR) {\n        if (error.getErrInfo() == EEXIST) {\n          // resource exists\n          // webdav specifies to overwrite by default if the special header is not set to F\n          if ((!request->GetHeaders().count(\"overwrite\")) ||\n              (request->GetHeaders()[\"overwrite\"] == \"T\")) {\n            // force the rename\n            struct stat buf;\n            gOFS->_stat(request->GetUrl().c_str(), &buf, error,\n                        *mVirtualIdentity, \"\");\n            ProcCommand  cmd;\n            XrdOucString info = \"mgm.cmd=rm&mgm.path=\";\n            info += destination.c_str();\n\n            if (S_ISDIR(buf.st_mode)) {\n              info += \"&mgm.option=r\";\n            }\n\n            cmd.open(\"/proc/user\", info.c_str(), *mVirtualIdentity, &error);\n            cmd.close();\n            rc = cmd.GetRetc();\n\n            if (rc != SFS_OK) {\n              // something went wrong while deleting the destination\n              response = HttpServer::HttpError(error.getErrText(),\n                                               error.getErrInfo());\n            } else {\n              // try the rename again\n              rc = gOFS->rename(request->GetUrl().c_str(),\n                                destination.c_str(),\n                                error, *mVirtualIdentity, 0, 0, false);\n\n              if (rc != SFS_OK) {\n                // something went wrong with the second rename\n                response = HttpServer::HttpError(error.getErrText(),\n                                                 error.getErrInfo());\n              } else {\n                // it worked!\n                response = new eos::common::PlainHttpResponse();\n                response->SetResponseCode(response->NO_CONTENT);\n              }\n            }\n          } else {\n            // directory exists but we are not overwriting\n            response = HttpServer::HttpError(error.getErrText(),\n                                             response->PRECONDITION_FAILED);\n          }\n        } else if (error.getErrInfo() == ENOENT) {\n          // parent directory does not exist\n          response = HttpServer::HttpError(error.getErrText(),\n                                           response->CONFLICT);\n        } else if (error.getErrInfo() == EPERM) {\n          // not permitted\n          response = HttpServer::HttpError(error.getErrText(),\n                                           response->FORBIDDEN);\n        } else {\n          // some other error\n          response = HttpServer::HttpError(error.getErrText(),\n                                           error.getErrInfo());\n        }\n      } else if (rc == SFS_REDIRECT) {\n        // redirection\n        response = HttpServer::HttpRedirect(request->GetUrl(),\n                                            error.getErrText(),\n                                            error.getErrInfo(), false);\n      } else if (rc == SFS_STALL) {\n        // stall\n        response = HttpServer::HttpStall(error.getErrText(),\n                                         error.getErrInfo());\n      } else {\n        // something unexpected\n        response = HttpServer::HttpError(error.getErrText(),\n                                         error.getErrInfo());\n      }\n    } else {\n      // everything went well\n      response = new eos::common::PlainHttpResponse();\n      response->SetResponseCode(response->CREATED);\n\n      if (!request->GetHeaders().count(\"cbox-skip-location-on-move\")) {\n        response->AddHeader(\"Location\", request->GetHeaders()[\"destination\"].c_str());\n      }\n    }\n  }\n\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\neos::common::HttpResponse*\nWebDAVHandler::Copy(eos::common::HttpRequest* request)\n{\n  eos::common::HttpResponse* response = 0;\n  XrdOucString prot, port;\n  std::string destination;\n  const char* dest = nullptr;\n\n  if (!(dest = eos::common::StringConversion::ParseUrl(\n                 request->GetHeaders()[\"destination\"].c_str(), prot, port))) {\n    eos_static_warning(\"could not process destination header=%s\",\n                       request->GetHeaders()[\"destination\"].c_str());\n  }\n\n  destination = dest ? dest : \"\";\n  eos_static_info(\"method=COPY src=\\\"%s\\\", dest=\\\"%s\\\"\",\n                  request->GetUrl().c_str(), destination.c_str());\n  XrdSecEntity client;\n  client.name   = const_cast<char*>(mVirtualIdentity->name.c_str());\n  client.host   = const_cast<char*>(mVirtualIdentity->host.c_str());\n  client.tident = const_cast<char*>(mVirtualIdentity->tident.c_str());\n  snprintf(client.prot, sizeof(client.prot) - 1, \"%s\",\n           mVirtualIdentity->prot.c_str());\n\n  if (!request->GetUrl().size()) {\n    response = HttpServer::HttpError(\"source path required\",\n                                     response->BAD_REQUEST);\n  } else if (!destination.size()) {\n    response = HttpServer::HttpError(\"destination invalid\",\n                                     response->BAD_REQUEST);\n  } else if (request->GetUrl() == destination) {\n    response = HttpServer::HttpError(\"destination must be different from source\",\n                                     response->FORBIDDEN);\n  } else {\n    int           rc = 0;\n    XrdOucErrInfo error;\n    ProcCommand  cmd;\n    XrdOucString info  = \"mgm.cmd=file&mgm.subcmd=copy\";\n    info += \"&mgm.path=\";\n    info += request->GetUrl().c_str();\n    info += \"&mgm.file.target=\";\n    info += destination.c_str();\n    info += \"&eos.ruid=\";\n    info += mVirtualIdentity->uid_string.c_str();\n    info += \"&eos.rgid=\";\n    info += mVirtualIdentity->gid_string.c_str();\n    eos_static_debug(\"cmd=%s\", info.c_str());\n    cmd.open(\"/proc/user\", info.c_str(), *mVirtualIdentity, &error);\n    cmd.close();\n    rc = cmd.GetRetc();\n    eos_static_debug(\"ret=%d\", rc);\n\n    if (rc != SFS_OK) {\n      if (rc == EEXIST) {\n        // resource exists\n        if ((!request->GetHeaders().count(\"overwrite\")) ||\n            (request->GetHeaders()[\"overwrite\"] == \"T\")) {\n          // force overwrite\n          info += \"&mgm.file.option=f\";\n          eos_static_debug(\"overwriting: cmd=%s\", info.c_str());\n          cmd.open(\"/proc/user\", info.c_str(), *mVirtualIdentity, &error);\n          cmd.close();\n          rc = cmd.GetRetc();\n          eos_static_debug(\"ret=%d\", rc);\n\n          if (rc != 0) {\n            // something went wrong with the overwrite\n            response = HttpServer::HttpError(error.getErrText(),\n                                             error.getErrInfo());\n          } else {\n            // it worked!\n            response = new eos::common::PlainHttpResponse();\n            response->SetResponseCode(response->NO_CONTENT);\n          }\n        } else {\n          // resource exists but we are not overwriting\n          response = HttpServer::HttpError(error.getErrText(),\n                                           response->PRECONDITION_FAILED);\n        }\n      } else if (rc == ENOENT) {\n        // parent directory does not exist\n        response = HttpServer::HttpError(error.getErrText(),\n                                         response->CONFLICT);\n      } else if (rc == EPERM) {\n        // not permitted\n        response = HttpServer::HttpError(error.getErrText(),\n                                         response->FORBIDDEN);\n      } else {\n        // some other error\n        response = HttpServer::HttpError(error.getErrText(),\n                                         error.getErrInfo());\n      }\n    } else {\n      // everything went well\n      response = new eos::common::PlainHttpResponse();\n      response->SetResponseCode(response->CREATED);\n    }\n  }\n\n  return response;\n}\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/webdav/WebDAVHandler.hh",
    "content": "// ----------------------------------------------------------------------\n// File: WebDAVHandler.hh\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   WebDAVHandler.hh\n *\n * @brief  Class to handle WebDAV requests and build responses.\n */\n\n#ifndef __EOSMGM_WEBDAV_HANDLER__HH__\n#define __EOSMGM_WEBDAV_HANDLER__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/http/ProtocolHandler.hh\"\n#include \"common/Mapping.hh\"\n#include \"mgm/Namespace.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n#include <map>\n#include <string>\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\nclass WebDAVHandler : public eos::common::ProtocolHandler\n{\n\nprivate:\n  /**\n   * WebDAV HTTP extension methods\n   */\n  enum Methods\n  {\n    PROPFIND,  //!< Used to retrieve properties, stored as XML, from a web\n               //!< resource. It is also overloaded to allow one to retrieve\n               //!< the collection structure (a.k.a. directory hierarchy) of a\n               //!< remote system.\n    PROPPATCH, //!< Used to change and delete multiple properties on a resource\n               //!< in a single atomic act\n    MKCOL,     //!< Used to create collections (a.k.a. a directory)\n    COPY,      //!< Used to copy a resource from one URI to another\n    MOVE,      //!< Used to move a resource from one URI to another\n    LOCK,      //!< Used to put a lock on a resource. WebDAV supports both\n               //!< shared and exclusive locks.\n    UNLOCK     //!< Used to remove a lock from a resource\n  };\n\npublic:\n\n  /**\n   * Constructor\n   */\n  WebDAVHandler (eos::common::VirtualIdentity *vid) :\n    eos::common::ProtocolHandler(vid) {};\n\n  /**\n   * Destructor\n   */\n  virtual ~WebDAVHandler () {};\n\n  /**\n   * Check whether the given method and headers are a match for this protocol.\n   *\n   * @param method  the request verb used by the client (GET, PUT, etc)\n   * @param headers the map of request headers\n   *\n   * @return true if the protocol matches, false otherwise\n   */\n  static bool\n  Matches (const std::string &method, HeaderMap &headers);\n\n  /**\n   * Build a response to the given WebDAV request.\n   *\n   * @param request  the map of request headers sent by the client\n   * @param method   the request verb used by the client (GET, PUT, etc)\n   * @param url      the URL requested by the client\n   * @param query    the GET request query string (if any)\n   * @param body     the request body data sent by the client\n   * @param bodysize the size of the request body\n   * @param cookies  the map of cookie headers\n   */\n  void\n  HandleRequest (eos::common::HttpRequest *request);\n\n  /**\n   * Make a collection (create a directory). If any of the parent directories\n   * do not exist, the response will be a failure, as WebDAV is not supposed\n   * to create intermediate directories.\n   *\n   * @param request  the client request object\n   *\n   * @return the response object\n   */\n  eos::common::HttpResponse*\n  MkCol (eos::common::HttpRequest *request);\n\n  /**\n   * Move a resource (file or directory). If the \"Overwrite\" header is set to\n   * \"T\" and the target exists, the target will be overwritten.\n   *\n   * @param request  the client request object\n   *\n   * @return the response object\n   */\n  eos::common::HttpResponse*\n  Move (eos::common::HttpRequest *request);\n\n  /**\n   * Copy a resource (file or directory).\n   *\n   * @param request  the client request object\n   *\n   * @return the response object\n   */\n  eos::common::HttpResponse*\n  Copy (eos::common::HttpRequest *request);\n\n  /**\n   * Convert the given request method string into its integer constant\n   * representation.\n   *\n   * @param method  the method string to convert\n   *\n   * @return the converted method string as an integer\n   */\n  inline static int\n  ParseMethodString (const std::string &method)\n  {\n    if      (method == \"PROPFIND\")  return Methods::PROPFIND;\n    else if (method == \"PROPPATCH\") return Methods::PROPPATCH;\n    else if (method == \"MKCOL\")     return Methods::MKCOL;\n    else if (method == \"COPY\")      return Methods::COPY;\n    else if (method == \"MOVE\")      return Methods::MOVE;\n    else if (method == \"LOCK\")      return Methods::LOCK;\n    else if (method == \"UNLOCK\")    return Methods::UNLOCK;\n    else return -1;\n  }\n\n};\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_END\n\n#endif /* __EOSMGM_WEBDAV_HANDLER__HH__ */\n"
  },
  {
    "path": "mgm/http/webdav/WebDAVResponse.cc",
    "content": "// ----------------------------------------------------------------------\n// File: WebDAVResponse.cc\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"mgm/http/webdav/WebDAVResponse.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"common/Logging.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\nWebDAVResponse::WebDAVResponse (eos::common::HttpRequest *request)\n{\n  using namespace rapidxml;\n\n  std::string body;\n  // Make a safe-to-modify copy of the request XML\n\n  if (request->GetBody().length()) \n  {\n    body = request->GetBody();\n  }\n  else\n  {\n    // empty body is an allprop request\n    body = \"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?>\\n<propfind xmlns=\\\"DAV:\\\"><allprop/></propfind>\";\n  }\n\n  \n  mXMLRequestCopy = std::vector<char>(body.begin(),\n\t\t\t\t      body.end());\n  mXMLRequestCopy.push_back('\\0');\n\n  // Parse the request\n  try\n  {\n    mXMLRequestDocument.parse<0>(&mXMLRequestCopy[0]);\n  }\n  catch (const rapidxml::parse_error &e)\n  {\n    eos_static_err(\"msg=\\\"error parsing XML document: %s\\\"\", e.what());\n  }\n};\n\n/*----------------------------------------------------------------------------*/\nvoid\nWebDAVResponse::ParseNamespaces ()\n{\n  using namespace rapidxml;\n  xml_node<> *node = mXMLRequestDocument.first_node();\n\n  while (node)\n  {\n    xml_attribute<> *attribute = node->first_attribute();\n    while (attribute)\n    {\n      XrdOucString attributeName(attribute->name());\n      std::string attributeValue(attribute->value());\n\n      if (attributeName.beginswith(\"xmlns\"))\n      {\n        std::string ns;\n\n        // split off the xmlns:<ns> (if any)\n        int colon = 0;\n        if ((colon = attributeName.find(':')) != STR_NPOS)\n          ns = std::string(std::string(attributeName.c_str()), colon + 1);\n\n        eos_static_debug(\"namespace=\\\"%s\\\"\", ns != \"\" ? ns.c_str() : \"default\");\n\n        if (attributeValue == \"DAV:\")\n          mDAVNamespaces[ns] = attributeValue;\n        else\n          mCustomNamespaces[ns] = attributeValue;\n      }\n\n      attribute = attribute->next_attribute();\n    }\n\n    node = node->next_sibling();\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nrapidxml::xml_node<>*\nWebDAVResponse::GetNode (rapidxml::xml_node<> *node, const char *name)\n{\n  XrdOucString target(name);\n  rapidxml::xml_node<> *child = node->first_node();\n\n  while (child)\n  {\n    for (auto it = mDAVNamespaces.begin(); it != mDAVNamespaces.end(); ++it)\n    {\n      std::string full(it->first);\n      if (full.length()) {\n\t// empty namespaces do not requre a ':'\n\tfull += \":\";\n      }\n      full += name;\n      eos_static_debug(\"namespace(dav)=\\\"%s\\\" child=\\\"%s\\\"\", full.c_str(), child->name());\n      if (std::string(child->name()) == full)\n        return child;\n    }\n\n\n    for (auto it = mCustomNamespaces.begin(); it != mCustomNamespaces.end(); ++it)\n    {\n      std::string full(it->first);\n      if (full.length()) {\n\t// empty namespaces do not requre a ':'\n\tfull += \":\";\n      }\n      full += name;\n      eos_static_debug(\"namespace(custom)=\\\"%s\\\" child=\\\"%s\\\"\", full.c_str(), child->name());\n      if (std::string(child->name()) == full)\n        return child;\n    }\n\n    child = child->next_sibling();\n  }\n  return NULL;\n}\n\n/*----------------------------------------------------------------------------*/\nrapidxml::xml_node<>*\nWebDAVResponse::AllocateNode (const char *name)\n{\n  return mXMLResponseDocument.allocate_node(rapidxml::node_element,\n                                            AllocateString(name));\n}\n\n/*----------------------------------------------------------------------------*/\nrapidxml::xml_attribute<>*\nWebDAVResponse::AllocateAttribute (const char *name, const char *value)\n{\n  return mXMLResponseDocument.allocate_attribute(AllocateString(name),\n                                                 AllocateString(value));\n}\n\n/*----------------------------------------------------------------------------*/\nrapidxml::xml_node<>*\nWebDAVResponse::CloneNode (rapidxml::xml_node<> *node)\n{\n  return mXMLResponseDocument.clone_node(node);\n}\n\n/*----------------------------------------------------------------------------*/\nconst char*\nWebDAVResponse::AllocateString (const char *value)\n{\n  return mXMLResponseDocument.allocate_string(value);\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nWebDAVResponse::SetValue(rapidxml::xml_node<> *node, const char *value)\n{\n  node->value(AllocateString(value));\n}\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/http/webdav/WebDAVResponse.hh",
    "content": "// ----------------------------------------------------------------------\n// File: WebDAVResponse.hh\n// Author: Justin Lewis Salmon - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/**\n * @file   WebDAVResponse.hh\n *\n * @brief  Abstract WebDAV response class. Stores XML request/response\n *         information and contains useful functions for building XML documents\n *         using RapidXML.\n */\n\n#ifndef __EOSMGM_WEBDAV_RESPONSE__HH__\n#define __EOSMGM_WEBDAV_RESPONSE__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/http/HttpResponse.hh\"\n#include \"common/http/HttpRequest.hh\"\n#include \"mgm/Namespace.hh\"\n/*----------------------------------------------------------------------------*/\n#include \"mgm/http/rapidxml/rapidxml.hpp\"\n#include <vector>\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_BEGIN\n\nclass WebDAVResponse : public eos::common::HttpResponse\n{\n\npublic:\n  typedef std::map<std::string, std::string> NamespaceMap;\n\nprotected:\n  rapidxml::xml_document<> mXMLRequestDocument;  //!< the parsed XML request\n  rapidxml::xml_document<> mXMLResponseDocument; //!< the XML response\n  std::vector<char>        mXMLRequestCopy;      //!< modifiable request copy\n  NamespaceMap             mDAVNamespaces;       //!< all DAV: namespaces\n  NamespaceMap             mCustomNamespaces;    //!< all custom namespaces\n\npublic:\n\n  /**\n   * Constructor\n   *\n   * @param request  the client request object\n   */\n  WebDAVResponse (eos::common::HttpRequest *request);\n\n  /**\n   * Destructor\n   */\n  virtual ~WebDAVResponse () {};\n\n  /**\n   * Build an appropriate response to the given WebDAV request. This will be\n   * implemented by each specific WebDAV request type (e.g. PROPFIND, COPY).\n   *\n   * @param request  the client request object\n   *\n   * @return the newly built response object\n   */\n  virtual eos::common::HttpResponse*\n  BuildResponse (eos::common::HttpRequest *request) = 0;\n\n  /**\n   * Scan through the request XML document looking for any DAV: or custom\n   * namespace declarations.\n   */\n  void\n  ParseNamespaces ();\n\n  /**\n   * Find a sub node of the given node (not recursively).\n   *\n   * @param node  the node whose children to search\n   * @param name  the name of the child node to search for\n   *\n   * @return the newly found child node, or NULL if not found\n   */\n  rapidxml::xml_node<>*\n  GetNode (rapidxml::xml_node<> *node, const char *name);\n\n  /**\n   * Add a node to the response XML document by using the RapidXML memory\n   * pool.\n   *\n   * @param name  the name of the new node to be allocated\n   *\n   * @return a pointer to the newly allocated node\n   */\n  rapidxml::xml_node<>*\n  AllocateNode (const char *name);\n\n  /**\n   * Add an attribute to the response XML document by using the RapidXML memory\n   * pool.\n   *\n   * @param name   the name of the new attribute\n   * @param value  the value of the new attribute\n   *\n   * @return a pointer to the newly allocated attribute\n   */\n  rapidxml::xml_attribute<>*\n  AllocateAttribute (const char *name, const char *value);\n\n  /**\n   * Clone a node and all it's children\n   *\n   * @param node  the node to clone\n   *\n   * @return a cloned XML node\n   */\n  rapidxml::xml_node<>*\n  CloneNode (rapidxml::xml_node<> *node);\n\n\n  /**\n   * Add a string to the response XML document memory pool.\n   *\n   * @param value  the string to be allocated\n   *\n   * @return a pointer inside the XML document to the newly allocated string\n   */\n  const char*\n  AllocateString (const char *value);\n\n  /**\n   * Set the text contents of the given node, making sure the string is\n   * properly allocated inside the RapidXML memory pool.\n   *\n   * @param node   pointer to the node which needs a value\n   * @param value  the value to be set\n   */\n  void\n  SetValue (rapidxml::xml_node<> *node, const char *value);\n\n};\n\n/*----------------------------------------------------------------------------*/\nEOSMGMNAMESPACE_END\n\n#endif /* __EOSMGM_WEBDAV_RESPONSE__HH__ */\n"
  },
  {
    "path": "mgm/http/xrdhttp/EosMgmHttpHandler.cc",
    "content": "//------------------------------------------------------------------------------\n//! file EosMgmHttpHandler.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"EosMgmHttpHandler.hh\"\n#include \"common/Logging.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm//http/HttpServer.hh\"\n#include \"common/http/ProtocolHandler.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/Timing.hh\"\n#include \"common/Path.hh\"\n#include <XrdSec/XrdSecEntityAttr.hh>\n#include <XrdSfs/XrdSfsInterface.hh>\n#include <XrdSys/XrdSysPlugin.hh>\n#include <XrdAcc/XrdAccAuthorize.hh>\n#include <XrdOuc/XrdOucPinPath.hh>\n#include <stdio.h>\n#include \"mgm/http/rest-api/manager/RestApiManager.hh\"\n\nXrdVERSIONINFO(XrdHttpGetExtHandler, EosMgmHttp);\nstatic XrdVERSIONINFODEF(compiledVer, EosMgmHttp, XrdVNUMBER, XrdVERSION);\n\n//------------------------------------------------------------------------------\n//! Obtain an instance of the XrdHttpExtHandler object.\n//!\n//! This extern \"C\" function is called when a shared library plug-in containing\n//! implementation of this class is loaded. It must exist in the shared library\n//! and must be thread-safe.\n//!\n//! @param  eDest -> The error object that must be used to print any errors or\n//!                  other messages (see XrdSysError.hh).\n//! @param  confg -> Name of the configuration file that was used. This pointer\n//!                  may be null though that would be impossible.\n//! @param  parms -> Argument string specified on the namelib directive. It may\n//!                  be null or point to a null string if no parms exist.\n//! @param  myEnv -> Environment variables for configuring the external handler;\n//!                  it my be null.\n//!\n//! @return Success: A pointer to an instance of the XrdHttpSecXtractor object.\n//!         Failure: A null pointer which causes initialization to fail.\n//!\n//------------------------------------------------------------------------------\n#define XrdHttpExtHandlerArgs XrdSysError       *eDest, \\\n                              const char        *confg, \\\n                              const char        *parms, \\\n                              XrdOucEnv         *myEnv\n\nextern \"C\" XrdHttpExtHandler* XrdHttpGetExtHandler(XrdHttpExtHandlerArgs)\n{\n  auto handler = new EosMgmHttpHandler();\n\n  if (handler->Init(confg)) {\n    delete handler;\n    return nullptr;\n  }\n\n  if (handler->Config(eDest, confg, parms, myEnv)) {\n    eDest->Emsg(\"EosMgmHttpHandler\", EINVAL, \"Failed config of EosMgmHttpHandler\");\n    delete handler;\n    return nullptr;\n  }\n\n  return (XrdHttpExtHandler*)handler;\n}\n\n\n//----------------------------------------------------------------------------\n//! Destructor\n//----------------------------------------------------------------------------\nEosMgmHttpHandler::~EosMgmHttpHandler()\n{\n  eos_info(\"msg=\\\"call %s destructor\\\"\", __FUNCTION__);\n}\n\n//------------------------------------------------------------------------------\n// Configure the external request handler\n//------------------------------------------------------------------------------\nint\nEosMgmHttpHandler::Config(XrdSysError* eDest, const char* confg,\n                          const char* parms, XrdOucEnv* myEnv)\n{\n  using namespace eos::common;\n  const std::string ofs_lib_tag = \"xrootd.fslib\";\n  const std::string authz_lib_tag = \"mgmofs.macaroonslib\";\n  std::list<std::string> authz_libs;\n  std::string ofs_lib_path, http_ext_lib_path;\n  std::string cfg;\n  StringConversion::LoadFileIntoString(confg, cfg);\n  auto lines = StringTokenizer::split<std::vector<std::string>>(cfg, '\\n');\n\n  for (auto& line : lines) {\n    eos::common::trim(line);\n\n    if (line.find(\"eos::mgm::http::redirect-to-https=1\") != std::string::npos) {\n      mRedirectToHttps = true;\n    } else if (line.find(ofs_lib_tag) == 0) {\n      ofs_lib_path = GetOfsLibPath(line);\n      // XRootD guarantees that the XRootD protocol and its associated\n      // plugins are loaded before HTTP therefore we can get a pointer\n      // to the MGM OFS plugin\n      mMgmOfsHandler = GetOfsPlugin(eDest, ofs_lib_path, confg);\n\n      if (!mMgmOfsHandler) {\n        eDest->Emsg(\"Config\", \"failed to get MGM OFS plugin pointer\");\n        return 1;\n      }\n    } else if (line.find(authz_lib_tag) == 0) {\n      authz_libs = GetAuthzLibPaths(line);\n      http_ext_lib_path = GetHttpExtLibPath(line);\n\n      if (authz_libs.empty() || http_ext_lib_path.empty()) {\n        eos_err(\"msg=\\\"wrong mgmofs.macaroonslib configuration\\\" data=\\\"%s\\\"\",\n                line.c_str());\n        return 1;\n      }\n    }\n  }\n\n  if (authz_libs.empty() || http_ext_lib_path.empty())  {\n    eos_notice(\"%s\", \"msg=\\\"mgmofs.macaroonslib configuration missing so \"\n               \"there is no token authorization support\\\"\");\n    return 0;\n  }\n\n  if (!mMgmOfsHandler || !mMgmOfsHandler->mMgmAuthz) {\n    eos_err(\"%s\", \"msg=\\\"missing MGM OFS handler or MGM AUTHZ handler\\\"\");\n    return 1;\n  }\n\n  eos_notice(\"configuration: redirect-to-https:%d\", mRedirectToHttps);\n\n  // Load the XrdHttpExHandler plugin from the XrdMacaroons library which\n  // is always on the first position\n  if (!(mTokenHttpHandler = GetHttpExtPlugin(eDest, *authz_libs.begin(),\n                            confg, myEnv))) {\n    return 1;\n  }\n\n  // The chaining of the authz libs always has the XrdAccAuthorize plugin\n  // from the MGM in the last postion as a fallback. Therefore, we can\n  // have the following combinations:\n  // libXrdMacaroons.so -> libEosMgmOfs.so\n  // libXrdMacaroons.so -> libXrdAccSciTokens.so -> libEosMgmOfs.so\n  XrdAccAuthorize* authz {nullptr};\n  XrdAccAuthorize* chain_authz = (XrdAccAuthorize*)mMgmOfsHandler->mMgmAuthz;\n\n  for (auto it = authz_libs.rbegin(); it != authz_libs.rend(); ++it) {\n    eos_info(\"msg=\\\"chaining XrdAccAuthorize object\\\" lib=\\\"%s\\\"\", it->c_str());\n\n    try {\n      if (!(authz = GetAuthzPlugin(eDest, *it, confg, myEnv, chain_authz))) {\n        eos_err(\"msg=\\\"failed to chain XrdAccAuthorize plugin\\\" lib=\\\"%s\\\"\",\n                it->c_str());\n        return 1;\n      }\n    } catch (const std::exception& e) {\n      eos_err(\"msg=\\\"caught exception\\\" msg=\\\"%s\\\"\", e.what());\n      return 1;\n    }\n\n    chain_authz = authz;\n  }\n\n  eos_info(\"%s\", \"msg=\\\"successfully chained the XrdAccAuthorizeObject \"\n           \"plugins and updated the MGM token authorization handler\\\"\");\n  mTokenAuthzHandler = authz;\n  mMgmOfsHandler->SetTokenAuthzHandler(mTokenAuthzHandler);\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Decide if current handler should be invoked\n//------------------------------------------------------------------------------\nbool\nEosMgmHttpHandler::MatchesPath(const char* verb, const char* path)\n{\n  eos_static_info(\"verb=%s path=%s\", verb, path);\n\n  // Leave the XrdHttpTPC plugin deal with COPY/OPTIONS verbs\n  if ((strcmp(verb, \"COPY\") == 0) || (strcmp(verb, \"OPTIONS\") == 0)) {\n    return false;\n  }\n\n  return true;\n}\n\nvoid\nEosMgmHttpHandler::generateResponseHeaders(\n    eos::common::HttpResponse* response,\n    std::map<std::string, std::string> & normalized_headers,\n    std::ostringstream& oss) {\n\n  response->AddHeader(\"Date\",  eos::common::Timing::utctime(time(NULL)));\n  const auto& headers = response->GetHeaders();\n\n  for (const auto& hdr : headers) {\n    std::string key = hdr.first;\n    std::string val = hdr.second;\n\n    // This is added by SendSimpleResp, don't add it here\n    if (key == \"Content-Length\") {\n      continue;\n    }\n\n    if (mRedirectToHttps) {\n      if (key == \"Location\") {\n        if (normalized_headers[\"xrd-http-prot\"] == \"https\") {\n          if (!normalized_headers.count(\"xrd-http-redirect-http\") ||\n              (normalized_headers[\"xrd-http-redirect-http\"] == \"0\")) {\n            // Re-write http: as https:\n            val.insert(4, \"s\");\n          }\n        }\n      }\n    }\n\n    if (!oss.str().empty()) {\n      oss << \"\\r\\n\";\n    }\n\n    oss << key << \": \" << val;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Process the HTTP request and send the response using by calling the\n// XrdHttpProtocol directly\n//------------------------------------------------------------------------------\nint\nEosMgmHttpHandler::ProcessReq(XrdHttpExtReq& req)\n{\n  std::string body;\n  // @todo(esindril): handle redirection to new MGM master if the\n  // current one is a slave\n\n  // Stop accepting requests if the MGM started the shutdown procedure\n  if (mMgmOfsHandler->Shutdown) {\n    std::string errmsg = \"MGM daemon is shutting down\";\n    return req.SendSimpleResp(500, errmsg.c_str(), nullptr, errmsg.c_str(),\n                              errmsg.length());\n  }\n\n  // Normalize the input headers to lower-case\n  std::map<std::string, std::string> normalized_headers;\n\n  for (const auto& hdr : req.headers) {\n    eos_static_debug(\"msg=\\\"normalize hdr\\\" key=\\\"%s\\\" value=\\\"%s\\\"\",\n                     hdr.first.c_str(), hdr.second.c_str());\n    std::string lc_string = LC_STRING(hdr.first);\n    normalized_headers[lc_string] = hdr.second;\n\n    if (lc_string == \"authorization\") {\n      eos_static_debug(\"msg=\\\"normalize hdr\\\" key=\\\"%s\\\" value=\\\"%s\\\"\",\n                       hdr.first.c_str(), hdr.second.c_str());\n    }\n  }\n\n  if (IsMacaroonRequest(req)) {\n    // call the routing module before doing anything with macaroon\n    int port;\n    std::string host;\n    int stall_timeout = 0;\n    eos::common::VirtualIdentity vid;\n    // Set the protocol to http so that the ShouldRoute() function knows that we\n    // want to be redirected to the HTTP port and not the xrootd one\n    vid.prot =  \"http\";\n    std::string opaque;\n    std::string path;\n    HttpServer::extractPathAndOpaque(req.resource,path,opaque);\n    if (mMgmOfsHandler->ShouldRoute(\n          __FUNCTION__, 0, vid, path.c_str(),\n          opaque.c_str(), host, port, stall_timeout)) {\n      std::unique_ptr<eos::common::HttpResponse> response(eos::common::HttpServer::HttpRedirect(req.resource,host.c_str(),port,false));\n      std::ostringstream oss_header;\n      generateResponseHeaders(response.get(),normalized_headers,oss_header);\n      eos_debug(\"response-header=\\\"%s\\\"\", oss_header.str().c_str());\n\n      return req.SendSimpleResp(response->GetResponseCode(),\n                              response->GetResponseCodeDescription().c_str(),\n                              oss_header.str().c_str(),\n                              response->GetBody().c_str(),\n                              response->GetBody().length());\n    }\n    if (mTokenHttpHandler) {\n      // Delegate request to the XrdMacaroons library\n      eos_info(\"%s\", \"msg=\\\"delegate request to XrdMacaroons library\\\"\");\n      return ProcessMacaroonPOST(req);\n    } else {\n      std::string errmsg = \"POST request not supported\";\n      return req.SendSimpleResp(404, errmsg.c_str(), nullptr, errmsg.c_str(),\n                                errmsg.length());\n    }\n  }\n\n  if (mMgmOfsHandler->mRestGrpcSrv && IsRestApiRequest(req)) {\n    return ProcessRestApiPost(req, normalized_headers);\n  }\n\n  bool is_rest_req = mMgmOfsHandler->mRestApiManager->isRestRequest(\n                       req.resource);\n\n  if (is_rest_req) {\n    std::optional<int> retCode = readBody(req, body);\n\n    if (retCode) {\n      return retCode.value();\n    }\n  }\n\n  if (req.verb == \"PROPFIND\" && !is_rest_req) {\n    // read the body\n    body.resize(req.length);\n    char* data = 0;\n    int rbytes = req.BuffgetData(req.length, &data, true);\n    body.assign(data, (size_t) rbytes);\n  }\n\n  std::string err_msg;\n  std::map<std::string, std::string> cookies;\n  std::unique_ptr<eos::common::ProtocolHandler> handler =\n    mMgmOfsHandler->mHttpd->XrdHttpHandler\n    (req.verb, req.resource, normalized_headers, cookies, body, req.GetSecEntity(),\n     mTokenAuthzHandler, err_msg);\n\n  if (handler == nullptr) {\n    return req.SendSimpleResp(500, err_msg.c_str(), \"\", err_msg.c_str(),\n                              err_msg.length());\n  }\n\n  eos::common::HttpResponse* response = handler->GetResponse();\n\n  if (response == nullptr) {\n    std::string errmsg = \"failed to create response object\";\n    return req.SendSimpleResp(500, errmsg.c_str(), nullptr, errmsg.c_str(),\n                              errmsg.length());\n  }\n\n  std::ostringstream oss_header;\n  const auto& headers = response->GetHeaders();\n  generateResponseHeaders(response,normalized_headers,oss_header);\n\n  eos_debug(\"response-header=\\\"%s\\\"\", oss_header.str().c_str());\n\n  if (req.verb == \"HEAD\") {\n    long long content_length = 0;\n    auto it = headers.find(\"Content-Length\");\n\n    if (it != headers.end()) {\n      try {\n        content_length = std::stoll(it->second);\n      } catch (...) {}\n    }\n\n    return req.SendSimpleResp(response->GetResponseCode(),\n                              response->GetResponseCodeDescription().c_str(),\n                              oss_header.str().c_str(),\n                              nullptr, content_length);\n  } else {\n    return req.SendSimpleResp(response->GetResponseCode(),\n                              response->GetResponseCodeDescription().c_str(),\n                              oss_header.str().c_str(),\n                              response->GetBody().c_str(),\n                              response->GetBody().length());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Process macaroon POST request\n//------------------------------------------------------------------------------\nint\nEosMgmHttpHandler::ProcessMacaroonPOST(XrdHttpExtReq& req)\n{\n  auto& sec_entity = const_cast<XrdSecEntity&>(req.GetSecEntity());\n\n  // If the XrdSecEntity comes with VOMS extensions then we need to call the\n  // eos vid mapping funcationality to actually determine the local user\n  // mapping which could be different then the one embedded in the GSI auth.\n  // This happens for example when we have VOMS mapping enabled in eos vid\n  if (sec_entity.vorg && (sec_entity.vorg[0] != '\\0')) {\n    std::unique_ptr<eos::common::VirtualIdentity>\n    vid_tmp {new eos::common::VirtualIdentity()};\n    std::string stident = \"https.0:0@\" + std::string(sec_entity.host);\n    eos::common::Mapping::IdMap(&sec_entity, \"\", stident.c_str(), *vid_tmp);\n\n    if (!vid_tmp->uid_string.empty()) {\n      free(sec_entity.name);\n      sec_entity.name = strndup(vid_tmp->uid_string.c_str(),\n                                vid_tmp->uid_string.length());\n    }\n  }\n\n  return mTokenHttpHandler->ProcessReq(req);\n}\n\n//------------------------------------------------------------------------------\n// Process rest api gw POST request\n//------------------------------------------------------------------------------\nint\nEosMgmHttpHandler::ProcessRestApiPost(XrdHttpExtReq& req,\n                                      const HdrsMapT& norm_hdrs)\n{\n  std::string errmsg;\n  // Extract request body\n  std::string body;\n  std::optional<int> retCode = readBody(req, body);\n\n  if (retCode) {\n    return retCode.value();\n  }\n\n  // Extract command name from the resource path\n  // To do so search for the last occurrence of '/'\n  std::string eosCommand;\n  size_t lastSlashPos = req.resource.rfind('/');\n\n  if (lastSlashPos != std::string::npos &&\n      lastSlashPos + 1 < req.resource.length()) {\n    // Extract the command string\n    eosCommand = req.resource.substr(lastSlashPos + 1);\n  } else {\n    errmsg = \"invalid input string\";\n    eos_static_err(\"msg=\\\"%s\\\"\", errmsg.c_str());\n    return req.SendSimpleResp(500, errmsg.c_str(), \"\", errmsg.c_str(),\n                              errmsg.length());\n  }\n\n  // Initialize curl object\n  CURL* curl = curl_easy_init();\n\n  if (!curl) {\n    errmsg = \"failed to initialize curl object\";\n    eos_static_err(\"msg=\\\"%s\\\"\", errmsg.c_str());\n    return req.SendSimpleResp(500, errmsg.c_str(), \"\", errmsg.c_str(),\n                              errmsg.length());\n  }\n\n  // Set the URL for the POST request\n  const std::string url = std::string(mRestApiGwUrl) + std::string(\n                            mRestApiGwPath) + eosCommand;\n  curl_easy_setopt(curl, CURLOPT_URL, url.c_str());\n  // Set the HTTP method to POST\n  curl_easy_setopt(curl, CURLOPT_POST, 1L);\n\n  // Forward relevant headers for auth\n  if (!RestApiGwFrwAuthHeaders(curl, req.GetSecEntity(), norm_hdrs)) {\n    errmsg = \"failure while forwarding auth headers\";\n    eos_static_err(\"msg=\\\"%s\\\"\", errmsg.c_str());\n    return req.SendSimpleResp(500, errmsg.c_str(), \"\", errmsg.c_str(),\n                              errmsg.length());\n  }\n\n  // Set the request data\n  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());\n  // Response data will be stored in this string\n  std::string responseData;\n  // Set the callback function to handle response data\n  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, EosMgmHttpHandler::WriteCallback);\n  curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseData);\n  // Perform the HTTP request\n  CURLcode curlRes = curl_easy_perform(curl);\n  int res = 0;\n\n  if (curlRes != CURLE_OK) {\n    std::string errmsg = std::string(\"curl_easy_perform() failed: \") +\n                         curl_easy_strerror(curlRes);\n    res = req.SendSimpleResp(500, errmsg.c_str(), \"\", errmsg.c_str(),\n                             errmsg.length());\n  } else {\n    eos_static_debug(\"response=\\\"%s\\\"\", responseData.c_str());\n    // HTTP request successful, send the response\n    res = req.SendSimpleResp(200, \"OK\", \"\", responseData.c_str(),\n                             responseData.length());\n  }\n\n  // Clean up and free resources\n  curl_easy_cleanup(curl);\n  return res;\n}\n\n\n//----------------------------------------------------------------------------\n// Forward the authentication relevant info as custom headers to the\n// GRPC-gateway that will then send them further down to the GRPC server\n//----------------------------------------------------------------------------\nbool\nEosMgmHttpHandler::RestApiGwFrwAuthHeaders(CURL* curl,\n    const XrdSecEntity& client,\n    const HdrsMapT& norm_hdrs)\n{\n  static const std::string hdr_prefix = \"Grpc-Metadata-\";\n  static const std::string authz_hdr = \"authorization\";\n  struct curl_slist* list = NULL;\n  list = curl_slist_append(list, SSTR(hdr_prefix << \"client-name: \"\n                                      << client.name).c_str());\n  list = curl_slist_append(list, SSTR(hdr_prefix << \"client-tident: \"\n                                      << \"https.0:0@\"\n                                      << client.host).c_str());\n  auto it_authz = norm_hdrs.find(authz_hdr);\n\n  if (it_authz != norm_hdrs.end()) {\n    list = curl_slist_append(list, SSTR(hdr_prefix << \"client-authorization: \"\n                                        << it_authz->second).c_str());\n  }\n\n  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);\n  return true;\n}\n\n//----------------------------------------------------------------------------\n// Function used to handle responses from grpc server\n//----------------------------------------------------------------------------\nsize_t\nEosMgmHttpHandler::WriteCallback(void* contents, size_t size, size_t nmemb,\n                                 std::string* output)\n{\n  size_t total_size = size * nmemb;\n  output->append(static_cast<char*>(contents), total_size);\n  return total_size;\n}\n\n//------------------------------------------------------------------------------\n// Get OFS library path from the given configuration\n//------------------------------------------------------------------------------\nstd::string\nEosMgmHttpHandler::GetOfsLibPath(const std::string& cfg_line)\n{\n  using namespace eos::common;\n  std::string lib_path;\n  auto tokens = StringTokenizer::split<std::vector<std::string>>(cfg_line, ' ');\n\n  if (tokens.size() < 2) {\n    eos_err(\"msg=\\\"failed parsing xrootd.fslib directive\\\" line=\\\"%s\\\"\",\n            cfg_line.c_str());\n    return lib_path;\n  }\n\n  eos::common::trim(tokens[1]);\n  lib_path = tokens[1];\n\n  // Account for different specifications of the OFS plugin\n  if (lib_path == \"-2\")  {\n    if (tokens.size() < 3) {\n      eos_err(\"msg=\\\"failed parsing xrootd.fslib directive\\\" line=\\\"%s\\\"\",\n              cfg_line.c_str());\n      lib_path.clear();\n      return lib_path;\n    }\n\n    eos::common::trim(tokens[2]);\n    lib_path = tokens[2];\n  }\n\n  return lib_path;\n}\n\n//------------------------------------------------------------------------------\n// Get list of external authorization libraries present in the configuration.\n// If multiple are present then the order is kept to properly apply chaining\n// to these libraries.\n//------------------------------------------------------------------------------\nstd::list<std::string>\nEosMgmHttpHandler::GetAuthzLibPaths(const std::string& cfg_line)\n{\n  using namespace eos::common;\n  std::list<std::string> authz_libs;\n  auto tokens = StringTokenizer::split<std::vector<std::string>>(cfg_line, ' ');\n\n  if (tokens.size() < 2) {\n    eos_err(\"msg=\\\"missing mgmofs.macaroonslib configuration\\\" \"\n            \"tokens_sz=%i\", tokens.size());\n    return authz_libs;\n  }\n\n  // The first one MUST BE the XrdMacroons lib\n  eos::common::trim(tokens[1]);\n  authz_libs.push_back(tokens[1]);\n\n  // Enable also the SciTokens library if present in the configuration\n  if (tokens.size() > 2) {\n    eos::common::trim(tokens[2]);\n    authz_libs.push_back(tokens[2]);\n  }\n\n  return authz_libs;\n}\n\n//----------------------------------------------------------------------------\n// Get XrdHttpExHandler library path from the given configuration\n//----------------------------------------------------------------------------\nstd::string\nEosMgmHttpHandler::GetHttpExtLibPath(const std::string& cfg_line)\n{\n  using namespace eos::common;\n  auto tokens = StringTokenizer::split<std::vector<std::string>>(cfg_line, ' ');\n\n  if (tokens.size() < 2) {\n    eos_err(\"msg=\\\"missing mgmofs.macaroonslib configuration\\\" \"\n            \"tokens_sz=%i\", tokens.size());\n    return std::string();\n  }\n\n  // The first one MUST BE the XrdMacroons lib\n  eos::common::trim(tokens[1]);\n  return tokens[1];\n}\n\n//------------------------------------------------------------------------------\n// Get a pointer to the MGM OFS plug-in\n//------------------------------------------------------------------------------\nXrdMgmOfs*\nEosMgmHttpHandler::GetOfsPlugin(XrdSysError* eDest, const std::string& lib_path,\n                                const char* confg)\n{\n  char resolve_path[2048];\n  bool no_alt_path {false};\n  XrdMgmOfs* mgm_ofs_handler {nullptr};\n\n  if (!XrdOucPinPath(lib_path.c_str(), no_alt_path, resolve_path,\n                     sizeof(resolve_path))) {\n    eDest->Emsg(\"Config\", \"Failed to locate the MGM OFS library path for \",\n                lib_path.c_str());\n    return mgm_ofs_handler;\n  }\n\n  // Try to load the XrdSfsGetFileSystem from the library (libXrdEosMgm.so)\n  XrdSfsFileSystem *(*ep)(XrdSfsFileSystem*, XrdSysLogger*, const char*);\n  std::string ofs_symbol {\"XrdSfsGetFileSystem\"};\n  XrdSysPlugin ofs_plugin(eDest, resolve_path, \"mgmofs\", &compiledVer, 1);\n  void* ofs_addr = ofs_plugin.getPlugin(ofs_symbol.c_str(), 0, 0);\n  ofs_plugin.Persist();\n\n  if (ofs_addr == nullptr) {\n    eDest->Emsg(\"Config\", \"Failed to get the OFS plugin address from \",\n                lib_path.c_str());\n    return mgm_ofs_handler;\n  }\n\n  ep = (XrdSfsFileSystem * (*)(XrdSfsFileSystem*, XrdSysLogger*, const char*))\n       (ofs_addr);\n  XrdSfsFileSystem* sfs_fs {nullptr};\n\n  if (!(ep && (sfs_fs = ep(nullptr, eDest->logger(), confg)))) {\n    eDest->Emsg(\"Config\", \"Failed loading XrdSfsFileSystem from \",\n                lib_path.c_str());\n    return mgm_ofs_handler;\n  }\n\n  mgm_ofs_handler = static_cast<XrdMgmOfs*>(sfs_fs);\n  eos_info(\"msg=\\\"successfully loaed XrdSfsFileSystem\\\" mgm_plugin_addr=%p\",\n           mgm_ofs_handler);\n  return mgm_ofs_handler;\n}\n\n//------------------------------------------------------------------------------\n// Get a pointer to the XrdHttpExtHandler plugin\n//------------------------------------------------------------------------------\nXrdHttpExtHandler*\nEosMgmHttpHandler::GetHttpExtPlugin(XrdSysError* eDest,\n                                    const std::string& lib_path,\n                                    const char* confg, XrdOucEnv* myEnv)\n{\n  bool no_alt_path = false;\n  char resolve_path[2048];\n  XrdHttpExtHandler* http_ptr {nullptr};\n\n  if (!XrdOucPinPath(lib_path.c_str(), no_alt_path, resolve_path,\n                     sizeof(resolve_path))) {\n    eos_err(\"msg=\\\"failed to locate library path\\\" lib=\\\"%s\\\"\",\n            lib_path.c_str());\n    return http_ptr;\n  }\n\n  eos_info(\"msg=\\\"loading HttpExtHandler(XrdMacaroons) plugin\\\" path=\\\"%s\\\"\",\n           resolve_path);\n  XrdHttpExtHandler *(*ep)(XrdHttpExtHandlerArgs);\n  std::string http_symbol {\"XrdHttpGetExtHandler\"};\n  XrdSysPlugin http_plugin(eDest, resolve_path, \"httpexthandler\",\n                           &compiledVer, 1);\n  void* http_addr = http_plugin.getPlugin(http_symbol.c_str(), 0, 0);\n  http_plugin.Persist();\n  ep = (XrdHttpExtHandler * (*)(XrdHttpExtHandlerArgs))(http_addr);\n\n  if (!http_addr) {\n    eos_err(\"msg=\\\"no XrdHttpGetExtHandler entry point in library\\\" \"\n            \"lib=\\\"%s\\\"\", resolve_path);\n    return http_ptr;\n  }\n\n  // Add a pointer to the MGM authz handler so that it can be used by the\n  // macaroons library to get access permissions for token requests\n  myEnv->PutPtr(\"XrdAccAuthorize*\", (void*)mMgmOfsHandler->mMgmAuthz);\n\n  if (ep && (http_ptr = ep(eDest, confg, (const char*) nullptr, myEnv))) {\n    eos_info(\"msg=\\\"successfully loaded XrdHttpGetExtHandler\\\" lib=\\\"%s\\\"\",\n             resolve_path);\n  } else {\n    eos_err(\"msg=\\\"failed loading XrdHttpGetExtHandler\\\" lib=\\\"%s\\\"\",\n            resolve_path);\n  }\n\n  return http_ptr;\n}\n\n//------------------------------------------------------------------------------\n// Get a pointer to the XrdAccAuthorize plugin present in the given library\n//------------------------------------------------------------------------------\nXrdAccAuthorize*\nEosMgmHttpHandler::GetAuthzPlugin(XrdSysError* eDest,\n                                  const std::string& lib_path,\n                                  const char* confg, XrdOucEnv* myEnv,\n                                  XrdAccAuthorize* to_chain)\n{\n  bool no_alt_path = false;\n  char resolve_path[2048];\n  XrdAccAuthorize* authz_ptr {nullptr};\n\n  if (!XrdOucPinPath(lib_path.c_str(), no_alt_path, resolve_path,\n                     sizeof(resolve_path))) {\n    eos_err(\"msg=\\\"failed to locate library path\\\" lib=\\\"%s\\\"\",\n            lib_path.c_str());\n    return authz_ptr;\n  }\n\n  eos_info(\"msg=\\\"loading XrdAccAuthorize plugin\\\" lib=\\\"%s\\\"\", resolve_path);\n  XrdAccAuthorize *(*authz_add_ep)(XrdSysLogger*, const char*, const char*,\n                                   XrdOucEnv*, XrdAccAuthorize*);\n  std::string authz_add_symbol {\"XrdAccAuthorizeObjAdd\"};\n  XrdSysPlugin authz_add_plugin(eDest, resolve_path, \"authz\", &compiledVer, 1);\n  void* authz_addr = authz_add_plugin.getPlugin(authz_add_symbol.c_str(), 0, 0);\n  authz_add_plugin.Persist();\n  authz_add_ep = (XrdAccAuthorize * (*)(XrdSysLogger*, const char*, const char*,\n                                        XrdOucEnv*, XrdAccAuthorize*))(authz_addr);\n\n  if (authz_add_ep &&\n      (authz_ptr = authz_add_ep(eDest->logger(), confg, nullptr, myEnv, to_chain))) {\n    eos_info(\"msg=\\\"successfully loaded XrdAccAuthorizeObject\\\" lib=\\\"%s\\\" ptr=%p\",\n             resolve_path, authz_ptr);\n  } else {\n    eos_err(\"msg=\\\"failed loading XrdAccAuthorizeObject\\\" lib=\\\"%s\\\"\",\n            resolve_path);\n  }\n\n  return authz_ptr;\n}\n\n//------------------------------------------------------------------------------\n// Reads the body of the XrdHttpExtReq object and put it in the body string\n//------------------------------------------------------------------------------\nstd::optional<int> EosMgmHttpHandler::readBody(XrdHttpExtReq& req,\n    std::string& body)\n{\n  std::optional<int> returnCode;\n  body.reserve(req.length);\n  const unsigned long long eoshttp_sz = 1024 * 1024;\n  const unsigned long long xrdhttp_sz = 256 * 1024;\n  unsigned long long contentLeft = req.length;\n  std::string bodyTemp;\n\n  do {\n    unsigned long long contentToRead = std::min(eoshttp_sz, contentLeft);\n    bodyTemp.clear();\n    bodyTemp.reserve(contentToRead);\n    char* data = nullptr;\n    unsigned long long dataRead = 0;\n\n    do {\n      size_t chunk_len = std::min(xrdhttp_sz, contentToRead - dataRead);\n      int bytesRead = req.BuffgetData(chunk_len, &data, true);\n      eos_static_debug(\"contentToRead=%lli rb=%i body=%u contentLeft=%lli\",\n                       contentToRead, bytesRead, body.size(), contentLeft);\n\n      if (bytesRead > 0) {\n        bodyTemp.append(data, bytesRead);\n        dataRead += bytesRead;\n      } else if (bytesRead == -1) {\n        std::ostringstream oss;\n        oss << \"msg=\\\"In EosMgmHttpHandler::ProcessReq(), unable to read the \"\n            << \"body of the request coming from the user. Internal XRootD Http\"\n            << \" request buffer error\\\"\";\n        eos_static_err(oss.str().c_str());\n        std::string errorMsg = \"Http server error: unable to read the request received\";\n        return req.SendSimpleResp(500, errorMsg.c_str(), nullptr, errorMsg.c_str(),\n                                  errorMsg.length());\n      } else {\n        break;\n      }\n    } while (dataRead < contentToRead);\n\n    contentLeft -= dataRead;\n    body += bodyTemp;\n  } while (contentLeft);\n\n  return returnCode;\n}\n\n//------------------------------------------------------------------------------\n// Returns true if the request is a macaroon token request false otherwise\n//------------------------------------------------------------------------------\nbool EosMgmHttpHandler::IsMacaroonRequest(const XrdHttpExtReq& req) const\n{\n  if (req.verb == \"POST\") {\n    const auto& contentTypeItor = req.headers.find(\"Content-Type\");\n\n    if (contentTypeItor != req.headers.end()) {\n      if (contentTypeItor->second == \"application/macaroon-request\") {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Returns true if the request is a rest api gateway token request\n// false otherwise\n// @todo(esindril) this should be moved in the GrpcRestGwServer\n//------------------------------------------------------------------------------\nbool EosMgmHttpHandler::IsRestApiRequest(const XrdHttpExtReq& req) const\n{\n  if (req.verb == \"POST\") {\n    const auto& resourcePath = req.resource.find(mRestApiGwPath);\n\n    if (resourcePath != std::string::npos) {\n      return true;\n    }\n  }\n\n  return false;\n}\n"
  },
  {
    "path": "mgm/http/xrdhttp/EosMgmHttpHandler.hh",
    "content": "//------------------------------------------------------------------------------\n//! file EosMgmHttpHandler.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include <XrdHttp/XrdHttpExtHandler.hh>\n#include \"common/Logging.hh\"\n#include \"common/http/HttpResponse.hh\"\n#include <XrdVersion.hh>\n#include <optional>\n#include <list>\n#include <curl/curl.h>\n\n//! Forward declaration\nclass XrdMgmOfs;\nclass XrdAccAuthorize;\nclass XrdSfsFileSystem;\n\n//------------------------------------------------------------------------------\n//! Class EosMgmHttpHandler\n//------------------------------------------------------------------------------\nclass EosMgmHttpHandler: public XrdHttpExtHandler,\n  public eos::common::LogId\n{\npublic:\n  using HdrsMapT = std::map<std::string, std::string>;\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  EosMgmHttpHandler() :\n    mRedirectToHttps(false), mTokenHttpHandler(nullptr),\n    mTokenAuthzHandler(nullptr), mMgmOfsHandler(nullptr)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~EosMgmHttpHandler();\n\n  //----------------------------------------------------------------------------\n  //! Initialize the external request handler\n  //!\n  //! @param  confg  Name of the configuration file that was used\n  //!\n  //! @return 0 if successful, otherwise non-zero value\n  //----------------------------------------------------------------------------\n  int Init(const char* confg) override\n  {\n    // We do the work in the Config method as we have all the parameters needed\n    // there.\n    return 0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Config the current request handler\n  //!\n  //! @param  eDest -> The error object that must be used to print any errors or\n  //!                  other messages (see XrdSysError.hh).\n  //! @param  confg -> Name of the configuration file that was used. This pointer\n  //!                  may be null though that would be impossible.\n  //! @param  parms -> Argument string specified on the namelib directive. It may\n  //!                  be null or point to a null string if no parms exist.\n  //! @param  myEnv -> Environment variables for configuring the external handler;\n  //!                  it my be null.\n  //! @return 0 if successful, otherwise non-zero value\n  //----------------------------------------------------------------------------\n  int Config(XrdSysError* eDest, const char* confg, const char* parms,\n             XrdOucEnv* myEnv);\n\n  //----------------------------------------------------------------------------\n  //! Tells if the incoming path is recognizsed by the current plugin as one\n  //! that needs to be processed.\n  //!\n  //! @param verb HTTP verb\n  //! @param path request path\n  //!\n  //! @return true if current handler is to be invoked, otherwise false\n  //----------------------------------------------------------------------------\n  bool MatchesPath(const char* verb, const char* path) override;\n\n  //----------------------------------------------------------------------------\n  //! Process the HTTP request and send the response using by calling the\n  //! XrdHttpProtocol directly\n  //!\n  //! @param req HTTP request\n  //!\n  //! @return 0 if successful, otherwise non-0\n  //----------------------------------------------------------------------------\n  int ProcessReq(XrdHttpExtReq& req) override;\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  bool mRedirectToHttps; ///< Flag if http traffic should be redirected to https\n  XrdHttpExtHandler* mTokenHttpHandler; ///< Macaroons ext http handler\n  //! Authz plugin from libMacaroons/libXrdSciTokens\n  XrdAccAuthorize* mTokenAuthzHandler;\n  XrdMgmOfs* mMgmOfsHandler; ///< Pointer to the MGM OFS plugin\n  //! Url to access rest api grpc-gateway\n  const char* mRestApiGwUrl = \"http://localhost:40054\";\n  //! Path to rest api grpc-gateway\n  const char* mRestApiGwPath = \"/v1/eos/rest/gateway/\";\n\n  //----------------------------------------------------------------------------\n  //! Copy XrdSecEntity info\n  //!\n  //! @param src source info\n  //! @param dst newly populated objed\n  //----------------------------------------------------------------------------\n  void CopyXrdSecEntity(const XrdSecEntity& src, XrdSecEntity& dst) const;\n\n  //----------------------------------------------------------------------------\n  //! Get OFS library path from the given configuration\n  //!\n  //! @param cfg_line relevant config line from file i.e xrd.cf.mgm\n  //!\n  //! @return string representing the OFS library\n  //----------------------------------------------------------------------------\n  std::string GetOfsLibPath(const std::string& cfg_line);\n\n  //----------------------------------------------------------------------------\n  //! Get XrdHttpExHandler library path from the given configuration\n  //!\n  //! @param cfg_line relevant config line from file i.e xrd.cf.mgm\n  //!\n  //! @return string representing the OFS library\n  //----------------------------------------------------------------------------\n  std::string GetHttpExtLibPath(const std::string& cfg_line);\n\n  //----------------------------------------------------------------------------\n  //! Get list of external authorization libraries present in the configuration.\n  //! If multiple are present then the order is kept to properly apply chaining\n  //! to these libraries.\n  //!\n  //! @param cfg_line relevant config line from file i.e xrd.cf.mgm\n  //!\n  //! @return list of external authorization libraries configured\n  //----------------------------------------------------------------------------\n  std::list<std::string> GetAuthzLibPaths(const std::string& cfg_line);\n\n  //----------------------------------------------------------------------------\n  //! Get a pointer to the MGM OFS plugin\n  //!\n  //! @param eDest error object that must be used to print any errors or msgs\n  //! @param lib_path library path\n  //! @param confg configuration file path\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  XrdMgmOfs* GetOfsPlugin(XrdSysError* eDest, const std::string& lib_path,\n                          const char* confg);\n\n  //----------------------------------------------------------------------------\n  //! Get a pointer to the XrdHttpExtHandler plugin\n  //!\n  //! @param eDest error object that must be used to print any errors or msgs\n  //! @param lib_path library path\n  //! @param confg configuration file path\n  //! @param myEnv environment variables for configuring the external handler;\n  //!       it my be null.\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  XrdHttpExtHandler*\n  GetHttpExtPlugin(XrdSysError* eDest, const std::string& lib_path,\n                   const char* confg, XrdOucEnv* myEnv);\n\n  //----------------------------------------------------------------------------\n  //! Get a pointer to the XrdAccAuthorize plugin present in the given library\n  //!\n  //! @param eDest error object that must be used to print any errors or msgs\n  //! @param lib_path library path\n  //! @param confg configuration file path\n  //! @param myEnv environment variables for configuring the external handler;\n  //!              it may be null.\n  //! @param to_chain XrdAccAuthorize plugin to chain to the newly loaded\n  //!        authorization plugin\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  XrdAccAuthorize* GetAuthzPlugin(XrdSysError* eDest,\n                                  const std::string& lib_path,\n                                  const char* confg, XrdOucEnv* myEnv,\n                                  XrdAccAuthorize* to_chain);\n\n  //----------------------------------------------------------------------------\n  //! Reads the body of the XrdHttpExtReq object and put it in the\n  //! body string\n  //!\n  //! @param req the request from which we will read the body content from\n  //! @param body, the string where the body from the request will be put on\n  //!\n  //! @return a return code if there was an error during the reading. Nothing\n  //!         otherwise, hence the optional<int>.\n  //----------------------------------------------------------------------------\n  std::optional<int> readBody(XrdHttpExtReq& req, std::string& body);\n\n  //----------------------------------------------------------------------------\n  //! Returns true if the request is a macaroon token request\n  //! false otherwise\n  //! @param req the request from which we will read the header and the HTTP verb\n  //!\n  //! @return true if the request is a macaroon token request, false otherwise\n  //----------------------------------------------------------------------------\n  bool IsMacaroonRequest(const XrdHttpExtReq& req) const;\n\n  //----------------------------------------------------------------------------\n  //! Process macaroon POST request\n  //!\n  //! @param req XrdHttp request object\n  //!\n  //! @return 0 if successful, otherwise non-0\n  //----------------------------------------------------------------------------\n  int ProcessMacaroonPOST(XrdHttpExtReq& req);\n\n  //----------------------------------------------------------------------------\n  //! Returns true if the request is a rest api gateway token request\n  //! false otherwise\n  //! @param req the request from which we will read the header and the HTTP verb\n  //!\n  //! @return true if the request is a macaroon token request, false otherwise\n  //----------------------------------------------------------------------------\n  bool IsRestApiRequest(const XrdHttpExtReq& req) const;\n\n  //----------------------------------------------------------------------------\n  //! Process REST API gateway POST request\n  //!\n  //! @param req XrdHttp request object\n  //! @param norm_hdrs normalized headers from the HTTP request\n  //!\n  //! @return 0 if successful, otherwise non-0\n  //----------------------------------------------------------------------------\n  int ProcessRestApiPost(XrdHttpExtReq& req,\n                         const HdrsMapT& norm_hdrs);\n\n  //----------------------------------------------------------------------------\n  //! Forward the authentication relevant info as custom headers to the\n  //! GRPC-gateway that will then send them further down to the GRPC server\n  //!\n  //! @note All appened headers need to start with the Grpc-Metadata- prefix\n  //! so that they are forwarded by the GRPC gateway otherwise they will just\n  //! be discarded!\n  //!\n  //! @param curl CURL handler where headers are appended\n  //! @param client XrdSecEntity object from which information is extracted\n  //! @param norm_hdrs normalized headers from the HTTP request\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool RestApiGwFrwAuthHeaders(CURL* curl, const XrdSecEntity& client,\n                               const HdrsMapT& norm_hdrs);\n\n  //----------------------------------------------------------------------------\n  //! Function used to handle responses from grpc server\n  //!\n  //! @param contents pointer to the data received from the grpc server\n  //! @param size the size of each data element received\n  //! @param nmemb the number of data elements received\n  //! @param output buffer where the received data from an HTTP response is\n  //!               stored\n  //!\n  //! @return number of bytes received if successful, otherwise non-0\n  //----------------------------------------------------------------------------\n  static size_t WriteCallback(void* contents, size_t size, size_t nmemb,\n                              std::string* output);\n\n  void generateResponseHeaders(eos::common::HttpResponse * response, std::map<std::string, std::string> & normalized_headers,std::ostringstream & oss);\n};\n"
  },
  {
    "path": "mgm/http/xrdhttp/README.md",
    "content": "XrdHttp\n-------\n\nHTTP(S) using the XRootD thread-pool and XrdHttp can be enabled in ```/etc/xrd.cf.mgm``` like:\n\n\n```\nif exec xrootd\n   xrd.protocol XrdHttp:9000 /usr/lib64/libXrdHttp-4.so\n   http.exthandler EosMgmHttp /usr/lib64/libEosMgmHttp.so eos::mgm::http::redirect-to-https=1\n   http.cert /etc/grid-security/daemon/host.cert\n   http.key /etc/grid-security/daemon/privkey.pem\n   http.cafile /etc/grid-security/daemon/ca.cert\nfi\n```\n\nTo disabel HTTPS you can remove the cert/key/cafile directives. If you want to redirect the data transfer from HTTPS to HTTP you can change the configuration key to ```eos::mgm::http::redirect-to-https=0``` or just put ```none``` at this place. \n\nThe targetport in redirection is currently taken from the sysconfig file and uses ```EOS_FST_HTTP_PORT+1000```.\n\n\n\n"
  },
  {
    "path": "mgm/imaster/IMaster.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/imaster/IMaster.hh\"\n#include \"mgm/config/IConfigEngine.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"common/ParseUtils.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Add to master Log\n//------------------------------------------------------------------------------\nvoid\nIMaster::MasterLog(const char* log)\n{\n  if (log && strlen(log)) {\n    std::unique_lock<std::mutex> lock(mMutex);\n    mLog += log;\n    mLog += '\\n';\n  }\n}\n\n//------------------------------------------------------------------------------\n// Create status file\n//------------------------------------------------------------------------------\nbool\nIMaster::CreateStatusFile(const char* path)\n{\n  struct stat buf;\n\n  if (::stat(path, &buf)) {\n    int fd = 0;\n\n    if ((fd = ::creat(path, S_IRWXU | S_IRGRP | S_IROTH)) == -1) {\n      MasterLog(eos_static_log(LOG_ERR, \"msg=\\\"failed to create %s\\\" errno=%d\", path,\n                               errno));\n      return false;\n    }\n\n    close(fd);\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Remove status file\n//------------------------------------------------------------------------------\nbool\nIMaster::RemoveStatusFile(const char* path)\n{\n  struct stat buf;\n\n  if (!::stat(path, &buf)) {\n    if (::unlink(path)) {\n      MasterLog(eos_static_log(LOG_ERR, \"msg=\\\"failed to unlink %s\\\" errno=%d\",\n                               path, errno));\n      return false;\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Populate namespace cache configuration\n//------------------------------------------------------------------------------\nvoid IMaster::FillNsCacheConfig(IConfigEngine* configEngine,\n                                std::map<std::string, std::string>& namespaceConfig) const\n{\n  std::string nfilesStr;\n  uint64_t nfiles = 40'000'000;\n\n  if (configEngine->Get(\"ns\", \"cache-size-nfiles\", nfilesStr)) {\n    if (!common::ParseUInt64(nfilesStr, nfiles)) {\n      eos_static_crit(\"Could not parse 'cache-size-nfiles' configuration value\");\n    }\n  }\n\n  std::string ndirsStr;\n  uint64_t ndirs = 5'000'000;\n\n  if (configEngine->Get(\"ns\", \"cache-size-ndirs\", ndirsStr)) {\n    if (!common::ParseUInt64(ndirsStr, ndirs)) {\n      eos_static_crit(\"Could not parse 'cache-size-ndirs' configuration value\");\n    }\n  }\n\n  namespaceConfig[constants::sMaxNumCacheFiles] = std::to_string(nfiles);\n  namespaceConfig[constants::sMaxNumCacheDirs] = std::to_string(ndirs);\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/imaster/IMaster.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file IMaster.hh\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @brief Master interface\n//------------------------------------------------------------------------------\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n\n//------------------------------------------------------------------------------\n//! @note: the defines after have to be in agreements with the defines in\n//! XrdMqOfs.cc but we don't want to create a link in the code between the two\n//------------------------------------------------------------------------------\n//! Existence indicates that this node is to be treated as a master\n#define EOSMGMMASTER_SUBSYS_RW_LOCKFILE \"/var/eos/eos.mgm.rw\"\n//! Existence indicates that the local MQ should redirect to the remote MQ\n#define EOSMQMASTER_SUBSYS_REMOTE_LOCKFILE \"/var/eos/eos.mq.remote.up\"\n\nEOSMGMNAMESPACE_BEGIN\n\nclass IConfigEngine;\n\n//------------------------------------------------------------------------------\n//! Class IMaster\n//------------------------------------------------------------------------------\nclass IMaster: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Transition types\n  //----------------------------------------------------------------------------\n  struct Transition {\n    enum Type {\n      kMasterToMaster               = 0,\n      kSlaveToMaster                = 1,\n      kMasterToMasterRO             = 2,\n      kMasterROToSlave              = 3,\n      kSecondarySlaveMasterFailover = 4\n    };\n  };\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  IMaster(): mLog(\"\") {};\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~IMaster() = default;\n\n  //----------------------------------------------------------------------------\n  //! Init method to determine the current master/slave state\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool Init() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Boot namespace\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool BootNamespace() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Apply configuration setting\n  //!\n  //! @param stdOut output string\n  //! @param stdErr output error string\n  //! @param apply_stall_rdr if true then apply the stall and redirection\n  //!        rules from the configuration\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool ApplyMasterConfig(std::string& stdOut, std::string& stdErr,\n                                 bool apply_stall_rdr) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Check if we are the master host\n  //!\n  //! @return true if master, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool IsMaster() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Check if remove master is OK\n  //!\n  //! @return true if OK, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool IsRemoteMasterOk() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get current master identifier ie. hostname:port\n  //----------------------------------------------------------------------------\n  virtual const std::string GetMasterId() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set the new master hostname\n  //!\n  //! @param hostname new master hostname\n  //! @param port new master port, default 1094\n  //! @param err_msg error message\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool SetMasterId(const std::string& hostname, int port,\n                           std::string& err_msg) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Return a delay time for balancing & draining since after a transition\n  //! we don't know the maps of already scheduled ID's and we have to make\n  //! sure not to reissue a transfer too early!\n  //----------------------------------------------------------------------------\n  virtual size_t GetServiceDelay() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get master log\n  //----------------------------------------------------------------------------\n  virtual void GetLog(std::string& stdOut) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Reset master log\n  //----------------------------------------------------------------------------\n  inline void ResetLog()\n  {\n    std::unique_lock<std::mutex> lock(mMutex);\n    mLog.clear();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Add to master Log\n  //----------------------------------------------------------------------------\n  void MasterLog(const char* log);\n\n  //----------------------------------------------------------------------------\n  //! Populate namespace cache configuration\n  //----------------------------------------------------------------------------\n  void FillNsCacheConfig(IConfigEngine* configEngine,\n                         std::map<std::string, std::string>& namespaceConfig) const;\n\n  //------------------------------------------------------------------------------\n  //! Create status file\n  //!\n  //! @param path path to file to be created if it doesn't exist already\n  //------------------------------------------------------------------------------\n  bool CreateStatusFile(const char* path);\n\n  //------------------------------------------------------------------------------\n  //! Remove status file\n  //!\n  //! @param path path to file to be unlinked\n  //------------------------------------------------------------------------------\n  bool RemoveStatusFile(const char* path);\n\n  //----------------------------------------------------------------------------\n  //! Show the current master/slave run configuration (used by ns stat)\n  //!\n  //! @return string describing the status\n  //----------------------------------------------------------------------------\n  virtual std::string PrintOut() = 0;\n\nprotected:\n  std::string mLog; ///< Master logs\n  std::mutex mMutex; ///< Mutex protecting access to the log message\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/inflighttracker/InFlightTracker.cc",
    "content": "// ----------------------------------------------------------------------\n// File: InFlightTracker.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n/*----------------------------------------------------------------------------*/\n#include \"mgm/inflighttracker/InFlightTracker.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"common/utils/RandUtils.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\nstd::string\nInFlightTracker::PrintOut(bool monitoring)\n{\n  std::string format_l = !monitoring ? \"+l\" : \"ol\";\n  std::string format_s = !monitoring ? \"s\" : \"os\";\n  TableFormatterBase table_all;\n\n  if (!monitoring) {\n    table_all.SetHeader({\n      std::make_tuple(\"uid\", 8, format_s),\n      std::make_tuple(\"threads\", 5, format_l),\n      std::make_tuple(\"sessions\", 5, format_l),\n      std::make_tuple(\"limit\", 5, format_l),\n      std::make_tuple(\"stalls\", 5, format_l),\n      std::make_tuple(\"stalltime\", 5, format_l),\n      std::make_tuple(\"status\", 16, format_s)\n    });\n  } else {\n    table_all.SetHeader({\n      std::make_tuple(\"uid\", 0, format_s),\n      std::make_tuple(\"threads\", 0, format_l),\n      std::make_tuple(\"sessions\", 0, format_l),\n      std::make_tuple(\"limit\", 0, format_l),\n      std::make_tuple(\"stalls\", 0, format_l),\n      std::make_tuple(\"stalltime\", 00, format_l),\n      std::make_tuple(\"status\", 0, format_s)\n    });\n  }\n\n  std::map<uid_t, size_t> vids = getInFlightUids();\n\n  for (auto it : vids) {\n    TableData table_data;\n    size_t limit = Access::ThreadLimit(it.first);\n    size_t global_limit = Access::ThreadLimit();\n    table_data.emplace_back();\n    table_data.back().push_back(TableCell(std::to_string((long long)it.first),\n                                          format_l));\n    table_data.back().push_back(TableCell((long long)it.second, format_l));\n    table_data.back().push_back(TableCell((long long)\n                                          eos::common::Mapping::ActiveSessions(it.first), format_l));\n    table_data.back().push_back(TableCell((long long)limit, format_l));\n    table_data.back().push_back(TableCell((long long)getStalls(it.first),\n                                          format_l));\n    table_data.back().push_back(TableCell((long long)getStallTime(it.first, limit),\n                                          format_l));\n\n    if (GetInFlight() > (int64_t) global_limit) {\n      table_data.back().push_back(TableCell(\"pool-OL\", format_s));\n    }  else if (it.second >= limit) {\n      table_data.back().push_back(TableCell(\"user-OL\", format_s));\n    } else {\n      if (it.second >= (0.9 * limit)) {\n        table_data.back().push_back(TableCell(\"user-LIMIT\", format_s));\n      } else {\n        table_data.back().push_back(TableCell(\"user-OK\", format_s));\n      }\n    }\n\n    table_all.AddRows(table_data);\n  }\n\n  return table_all.GenerateTable(HEADER);\n}\n\n\nsize_t\nInFlightTracker::getStallTime(uid_t uid, size_t& limit)\n{\n  size_t sessions = (uid == 0) ? eos::common::Mapping::ActiveSessions() :\n                    eos::common::Mapping::ActiveSessions(uid);\n  size_t stalltime = limit ? ((size_t)(2.0 * sessions / limit)) : 0;\n\n  if (stalltime > 60) {\n    stalltime = 60;\n  } else if (stalltime < 1) {\n    stalltime = 1;\n  }\n\n  size_t random_stall = common::getRandom(1ul, stalltime);\n  stalltime /= 2;\n  stalltime += random_stall;\n\n  return stalltime;\n}\n\nsize_t\nInFlightTracker::ShouldStall(uid_t uid, bool& saturated, size_t& threads_used)\n{\n  size_t limit = Access::ThreadLimit(uid, false);\n  size_t global_limit = Access::ThreadLimit(false);\n  //size_t global_sessions = eos::common::Mapping::ActiveSessions();\n  saturated = false;\n  threads_used = limit;\n\n  // user limit\n  if (limit > 1) {\n    if (getInFlight(uid) > limit) {\n      if (GetInFlight() > (int64_t) global_limit) {\n        saturated = true;\n      }\n\n      incStalls(uid);\n      // estimate a stall time\n      return getStallTime(uid, limit);\n    }\n  }\n\n  // global limit\n  if (GetInFlight() > (int64_t) global_limit) {\n    saturated = true;\n    incStalls(uid);\n    return getStallTime(0, global_limit);\n  }\n\n  return 0;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/inflighttracker/InFlightTracker.hh",
    "content": "// ----------------------------------------------------------------------\n// File: InFlightTracker.hh\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include \"common/VirtualIdentity.hh\"\n#include \"common/table_formatter/TableFormatterBase.hh\"\n#include <atomic>\n#include <cassert>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! @brief Keep track of how many requests are currently in-flight.\n//! It's also possible to use this as a barrier to further requests - useful\n//! when shutting down.\n//------------------------------------------------------------------------------\nclass InFlightTracker: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param accepting flag to mark that we should accept connections\n  //----------------------------------------------------------------------------\n  InFlightTracker(bool accepting = true) :\n    mAcceptingRequests(accepting) {}\n\n  //----------------------------------------------------------------------------\n  //! Decide whether to account or not for a new connection. This helps to\n  //! keep track of the number of threads inside a critical block of code.\n  //----------------------------------------------------------------------------\n  bool Up(const eos::common::VirtualIdentity& vid)\n  {\n    // This contraption (hopefully) ensures that after setAcceptingRequests(false)\n    // takes effect, the following guarantees hold:\n    // - Any subsequent calls to up() will not increase mInFlight.\n    // - As soon as we observe an mInFlight value of zero, no further requests\n    //   will be accepted.\n    //\n    // The second guarantee is necessary for wait(), which checks if mInFlight\n    // is zero to tell whether all in-flight requests have been dispatched.\n\n    // If setAcceptingRequests takes effect here, the request is rejected, as expected.\n    if (!mAcceptingRequests) {\n      return false;\n    }\n\n    // If setAcceptingRequests takes effect here, no problem. mInFlight will\n    // temporarily jump, but the request will be rejected.\n    ++mInFlight;\n\n    // Same as before.\n    if (!mAcceptingRequests) {\n      // If we're here, it means setAcceptingRequests has already taken effect.\n      --mInFlight;\n      return false;\n    }\n\n    // If setAcceptingRequests takes effect here, no problem:\n    // mInFlight can NOT be zero at this point, and the spinner will wait.\n    pthread_t myself = pthread_self();\n    uid_t myuid = vid.uid;\n    std::unique_lock<std::mutex> scope_lock(mInFlightPidMutex);\n\n    if (!mInFlightPids[myself]) {\n      mInFlightUid[myself] = myuid;\n      mInFlightVids[myuid]++;\n    }\n\n    mInFlightPids[myself]++;\n    return true;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Decrement number of inflight tracked requests\n  //----------------------------------------------------------------------------\n  void Down()\n  {\n    --mInFlight;\n    assert(mInFlight >= 0);\n    pthread_t mythread = pthread_self();\n    std::unique_lock<std::mutex> scope_lock(mInFlightPidMutex);\n    uid_t myuid = mInFlightUid[mythread];\n\n    if (!(--mInFlightPids[mythread])) {\n      mInFlightPids.erase(mythread);\n      mInFlightUid.erase(mythread);\n\n      if (mInFlightVids[myuid]) {\n        if (!--mInFlightVids[myuid]) {\n          mInFlightVids.erase(myuid);\n          mInFlightStalls.erase(myuid);\n        }\n      }\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set if we should accept tracking new requests or not\n  //!\n  //! @param value if true enable tracking, otherwise refuse\n  //----------------------------------------------------------------------------\n  void SetAcceptingRequests(bool value)\n  {\n    mAcceptingRequests = value;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if we're accepting requests\n  //----------------------------------------------------------------------------\n  bool IsAcceptingRequests() const\n  {\n    return mAcceptingRequests;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Wait that there are no more tracked requests\n  //----------------------------------------------------------------------------\n  void SpinUntilNoRequestsInFlight(bool print_log = false,\n                                   const std::chrono::milliseconds wait_ms =\n                                     std::chrono::milliseconds::zero()) const\n  {\n    assert(!mAcceptingRequests);\n    auto num = GetInFlight();\n\n    while (num) {\n      if (print_log) {\n        eos_info(\"msg=\\\"waiting for %li in-flight requests to finish\\\"\", num);\n      }\n\n      if (wait_ms.count()) {\n        std::this_thread::sleep_for(wait_ms);\n      }\n\n      num = GetInFlight();\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get number of in-flight tracked requests\n  //----------------------------------------------------------------------------\n  int64_t GetInFlight() const\n  {\n    return mInFlight;\n  }\n\n  std::set<pthread_t> getInFlightThreads()\n  {\n    std::unique_lock<std::mutex> scope_lock(mInFlightPidMutex);\n    std::set<pthread_t> inflight_threads;\n\n    for (auto it : mInFlightPids) {\n      inflight_threads.insert(it.first);\n    }\n\n    return inflight_threads;\n  }\n\n  std::map<uid_t, size_t> getInFlightUids()\n  {\n    std::unique_lock<std::mutex> scope_lock(mInFlightPidMutex);\n    return mInFlightVids;\n  }\n\n  size_t getInFlight(uid_t uid)\n  {\n    std::unique_lock<std::mutex> scope_lock(mInFlightPidMutex);\n    auto it = mInFlightVids.find(uid);\n\n    if (it != mInFlightVids.end()) {\n      return it->second;\n    } else {\n      return 0;\n    }\n  }\n\n  void incStalls(uid_t uid)\n  {\n    std::unique_lock<std::mutex> scope_lock(mInFlightPidMutex);\n    mInFlightStalls[uid]++;\n  }\n\n  size_t getStalls(uid_t uid)\n  {\n    std::unique_lock<std::mutex> scope_lock(mInFlightPidMutex);\n    auto it = mInFlightStalls.find(uid);\n\n    if (it != mInFlightStalls.end()) {\n      return it->second;\n    } else {\n      return 0;\n    }\n  }\n\n  size_t getStallTime(uid_t uid, size_t& limit);\n\n  //----------------------------------------------------------------------------\n  //! Dump user tracking\n  //----------------------------------------------------------------------------\n  std::string PrintOut(bool monitoring) ;\n  size_t ShouldStall(uid_t uid, bool& saturated, size_t& used_threads);\n\nprivate:\n  std::atomic<bool> mAcceptingRequests {true};\n  std::atomic<int64_t> mInFlight {0};\n  std::map<pthread_t, size_t> mInFlightPids;\n  std::map<pthread_t, uid_t> mInFlightUid;\n  std::map<uid_t, size_t> mInFlightVids;\n  std::map<uid_t, size_t> mInFlightStalls;\n  std::mutex mInFlightPidMutex;\n\n};\n\n//------------------------------------------------------------------------------\n//! @brief Class InFlightRegistraction helper to account for in-flight\n//! requests at scope level.\n//------------------------------------------------------------------------------\nclass InFlightRegistration\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param tracke tracker object\n  //----------------------------------------------------------------------------\n  InFlightRegistration(InFlightTracker& tracker,\n                       const eos::common::VirtualIdentity& vid) :\n    mInFlightTracker(tracker)\n  {\n    mSucceeded = mInFlightTracker.Up(vid);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor - take care of doing the proper accounting when getting out\n  //! of scope.\n  //------------------------------------------------------------------------------\n  ~InFlightRegistration()\n  {\n    if (mSucceeded) {\n      mInFlightTracker.Down();\n    }\n  }\n\n  //------------------------------------------------------------------------------\n  //! Check if current registration is actually tracked\n  //!\n  //! @return true if current registration is tracked, otherwise false\n  //------------------------------------------------------------------------------\n  bool IsOK()\n  {\n    return mSucceeded;\n  }\n\n  std::set<pthread_t> getThreads()\n  {\n    return mInFlightTracker.getInFlightThreads();\n  }\n\n\nprivate:\n  InFlightTracker& mInFlightTracker;\n  bool mSucceeded;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/inspector/FileInspector.cc",
    "content": "// ----------------------------------------------------------------------\n// File: FileInspector.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"Logging.hh\"\n#include \"common/Path.hh\"\n#include \"common/FileId.hh\"\n#include \"common/IntervalStopwatch.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/Timing.hh\"\n#include \"common/json/Json.hh\"\n#include \"common/ParseUtils.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/inspector/FileInspector.hh\"\n#include \"mgm/proc/ProcCommand.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/ofs/XrdMgmOfsDirectory.hh\"\n#include \"mgm/misc/Constants.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/ns_quarkdb/inspector/FileScanner.hh\"\n#include \"namespace/ns_quarkdb/FileMD.hh\"\n#include \"namespace/utils/Stat.hh\"\n#include \"namespace/Resolver.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include <qclient/QClient.hh>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFileInspector::FileInspector(std::string_view space_name,\n                             const eos::QdbContactDetails& qdb_details) :\n  nfiles(0), ndirs(0), mSpaceName(space_name),\n  mQdbHelper(qdb_details)\n{\n  mVid = eos::common::VirtualIdentity::Root();\n  mThread.reset(&FileInspector::backgroundThread, this);\n  scanned_percent.store(0, std::memory_order_seq_cst);\n  PriceTbPerYearDisk = 20;\n  PriceTbPerYearTape = 10;\n  currency = currencies[0];\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nFileInspector::~FileInspector()\n{\n  mThread.join();\n}\n\n//------------------------------------------------------------------------------\n// Retrieve current file inspector configuration options\n//------------------------------------------------------------------------------\nFileInspector::Options FileInspector::getOptions(const LockFsView lockfsview)\n{\n  FileInspector::Options opts;\n  // Default options\n  opts.enabled = false;\n  opts.interval = std::chrono::minutes(4 * 60);\n  eos::common::RWMutexReadLock lock;\n\n  if (lockfsview == LockFsView::On) {\n    lock.Grab(FsView::gFsView.ViewMutex);\n  }\n\n  if (FsView::gFsView.mSpaceView.count(mSpaceName)) {\n    if (FsView::gFsView.mSpaceView[mSpaceName]->GetConfigMember(\"inspector\") ==\n        \"on\") {\n      opts.enabled = true;\n    }\n\n    int64_t intv = 0;\n    std::string interval =\n      FsView::gFsView.mSpaceView[mSpaceName]->GetConfigMember(\"inspector.interval\");\n\n    if (!interval.empty()) {\n      common::ParseInt64(interval, intv);\n\n      if (intv) {\n        opts.interval = std::chrono::seconds(intv);\n      }\n    }\n\n    std::string tbprice =\n      FsView::gFsView.mSpaceView[mSpaceName]->GetConfigMember(\"inspector.price.disk.tbyear\");\n    double price = 0;\n\n    if (!tbprice.empty()) {\n      price = common::ParseDouble(tbprice);\n\n      if (price) {\n        PriceTbPerYearDisk = price;\n      }\n    }\n\n    tbprice =\n      FsView::gFsView.mSpaceView[mSpaceName]->GetConfigMember(\"inspector.price.tape.tbyear\");\n    price = 0;\n\n    if (!tbprice.empty()) {\n      price = common::ParseDouble(tbprice);\n\n      if (price) {\n        PriceTbPerYearTape = price;\n      }\n    }\n\n    std::string scurrency =\n      FsView::gFsView.mSpaceView[mSpaceName]->GetConfigMember(\"inspector.price.currency\");\n\n    if (!scurrency.empty()) {\n      int64_t index = 0;\n      common::ParseInt64(scurrency, index);\n\n      if (index < 6) {\n        currency = currencies[index];\n      }\n    }\n  }\n\n  if (opts.enabled) {\n    enable();\n    eos_static_debug(\"msg=\\\"file inspector is enabled\\\"  interval=%lds\",\n                     opts.interval.count());\n  } else {\n    disable();\n  }\n\n  return opts;\n}\n\n\n//------------------------------------------------------------------------------\n// Background Thread to compute the stats\n//------------------------------------------------------------------------------\nvoid\nFileInspector::backgroundThread(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"FileInspector\");\n  gOFS->WaitUntilNamespaceIsBooted(assistant);\n  // set the initial state after boot\n  Options opts = getOptions(LockFsView::On);\n\n  if (opts.enabled) {\n    enable();\n  } else {\n    disable();\n  }\n\n  assistant.wait_for(std::chrono::seconds(10));\n  eos_static_info(\"%s\", \"msg=\\\"async thread started\\\"\");\n\n  if (mQdbHelper.HasStats()) {\n    mQdbHelper.Load(mLastStats);\n  }\n\n  while (!assistant.terminationRequested()) {\n    Options opts = getOptions(LockFsView::On);\n\n    // Only a master needs to run a FileInspector\n    if (opts.enabled) {\n      enable();\n    } else {\n      disable();\n    }\n\n    common::IntervalStopwatch stopwatch(std::chrono::seconds(\n                                          opts.interval.count()));\n\n    if (opts.enabled && gOFS->mMaster->IsMaster()) {\n      eos_static_info(\"%s\", \"msg=\\\"scan started\\\"\");\n      mCurrentStats.TimeScan = time(NULL);\n      performCycleQDB(assistant);\n      eos_static_info(\"%s\", \"msg=\\\"scan finished\\\"\");\n    }\n\n    assistant.wait_for(stopwatch.timeRemainingInCycle());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Perform a single inspector cycle, QDB namespace\n//------------------------------------------------------------------------------\nvoid FileInspector::performCycleQDB(ThreadAssistant& assistant) noexcept\n{\n  eos_static_info(\"%s\", \"msg=\\\"start FileInspector scan on QDB\\\"\");\n\n  // Initialize qclient..\n  if (!mQcl) {\n    mQcl.reset(new qclient::QClient(gOFS->mQdbContactDetails.members,\n                                    gOFS->mQdbContactDetails.constructOptions()));\n  }\n\n  // Start scanning files\n  unsigned long long nfiles_processed;\n  nfiles = ndirs = nfiles_processed = 0;\n  time_t s_time = time(NULL);\n  {\n    eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n    nfiles = (unsigned long long) gOFS->eosFileService->getNumFiles();\n    ndirs = (unsigned long long) gOFS->eosDirectoryService->getNumContainers();\n  }\n  Options opts = getOptions(LockFsView::On);\n  uint64_t interval = opts.interval.count();\n  FileScanner scanner(*(mQcl.get()));\n  time_t c_time = s_time;\n\n  while (scanner.valid()) {\n    scanner.next();\n    std::string err;\n    eos::ns::FileMdProto item;\n\n    if (scanner.getItem(item)) {\n      std::shared_ptr<eos::QuarkFileMD> fmd = std::make_shared<eos::QuarkFileMD>();\n      fmd->initialize(std::move(item));\n      Process(fmd);\n      nfiles_processed++;\n      scanned_percent.store(100.0 * nfiles_processed / nfiles,\n                            std::memory_order_seq_cst);\n      time_t target_time = (1.0 * nfiles_processed / nfiles) * interval;\n      time_t is_time = time(NULL) - s_time;\n\n      if (target_time > is_time) {\n        uint64_t p_time = target_time - is_time;\n\n        if (p_time > 5) {\n          p_time = 5;\n        }\n\n        eos_static_debug(\"is:%lu target:%lu is_t:%lu target_t:%lu interval:%lu \"\n                         \"- pausing for %lu seconds\\n\",\n                         nfiles_processed, nfiles.load(), is_time, target_time, interval, p_time);\n        // pause for the diff ...\n        std::this_thread::sleep_for(std::chrono::seconds(p_time));\n      }\n\n      if (assistant.terminationRequested()) {\n        return;\n      }\n\n      if ((time(NULL) - c_time) > 60) {\n        c_time = time(NULL);\n        Options opts = getOptions(LockFsView::On);\n        interval = opts.interval.count();\n\n        if (!opts.enabled) {\n          // interrupt the scan\n          break;\n        }\n\n        if (!gOFS->mMaster->IsMaster()) {\n          // interrupt the scan\n          break;\n        }\n      }\n    }\n\n    if (scanner.hasError(err)) {\n      eos_static_err(\"msg=\\\"QDB scanner error, interrupting scan\\\" error=\\\"%s\\\"\",\n                     err.c_str());\n      break;\n    }\n  }\n\n  scanned_percent.store(100.0, std::memory_order_seq_cst);\n  std::lock_guard<std::mutex> lock(mutexScanStats);\n  mLastStats = std::move(mCurrentStats);\n  mQdbHelper.Store(mLastStats);\n  mCurrentStats = FileInspectorStats{}; // reset current stats\n}\n\n//------------------------------------------------------------------------------\n// Process a given fmd object\n//------------------------------------------------------------------------------\nvoid\nFileInspector::Process(std::shared_ptr<eos::IFileMD> fmd)\n{\n  std::lock_guard<std::mutex> lock(mutexScanStats);\n\n  if (fmd->isLink()) {\n    mCurrentStats.SymlinkCount++;\n    return;\n  }\n\n  if (fmd->hasAttribute(SYS_HARD_LINK)) {\n    mCurrentStats.HardlinkCount++;\n    mCurrentStats.HardlinkVolume += fmd->getSize();\n    return;\n  }\n\n  uint64_t lid = fmd->getLayoutId();\n  // Totals\n  mCurrentStats.TotalFileCount++;\n  mCurrentStats.TotalLogicalBytes += fmd->getSize();\n  double disksize = 1.0 * fmd->getSize() * eos::common::LayoutId::GetSizeFactor(\n                      lid);\n  bool ontape = eos::modeFromMetadataEntry(fmd) & EOS_TAPE_MODE_T;\n  double tapesize = ontape ? (1.0 * fmd->getSize()) : 0;\n\n  // zero size files\n  if (!fmd->getSize()) {\n    mCurrentStats.ScanStats[lid][\"zerosize\"]++;\n  } else {\n    mCurrentStats.ScanStats[lid][\"volume\"] += fmd->getSize();\n    mCurrentStats.ScanStats[lid][\"physicalsize\"] += disksize;\n  }\n\n  // no location files\n  if (!fmd->getNumLocation()) {\n    mCurrentStats.ScanStats[lid][\"nolocation\"]++;\n\n    if (mCurrentStats.NumFaultyFiles < maxfaulty) {\n      mCurrentStats.FaultyFiles[\"nolocation\"][fmd->getId()] = fmd->getLayoutId();\n    }\n\n    mCurrentStats.NumFaultyFiles++;\n  }\n\n  eos::IFileMD::LocationVector l = fmd->getLocations();\n  eos::IFileMD::LocationVector u_l = fmd->getUnlinkedLocations();\n\n  for (auto const& fs : l) {\n    if (!FsView::gFsView.HasMapping(fs)) {\n      // shadow filesystem\n      mCurrentStats.ScanStats[lid][\"shadowlocation\"]++;\n\n      if (mCurrentStats.NumFaultyFiles < maxfaulty) {\n        mCurrentStats.FaultyFiles[\"shadowlocation\"][fmd->getId()] = fmd->getLayoutId();\n      }\n\n      mCurrentStats.NumFaultyFiles++;\n    }\n  }\n\n  for (auto const& fs : u_l) {\n    if (!FsView::gFsView.HasMapping(fs)) {\n      // shadow filesystem\n      mCurrentStats.ScanStats[lid][\"shadowdeletion\"]++;\n\n      if (mCurrentStats.NumFaultyFiles < maxfaulty) {\n        mCurrentStats.FaultyFiles[\"shadowdeletion\"][fmd->getId()] = fmd->getLayoutId();\n      }\n\n      mCurrentStats.NumFaultyFiles++;\n    }\n  }\n\n  // unlinked locations\n  mCurrentStats.ScanStats[lid][\"unlinkedlocations\"] +=\n    fmd->getNumUnlinkedLocation();\n  // linked locations\n  mCurrentStats.ScanStats[lid][\"locations\"] += fmd->getNumLocation();\n  // stripe number\n  size_t stripes = eos::common::LayoutId::GetStripeNumber(lid) + 1;\n  std::string tag = \"repdelta:\";\n  int64_t sdiff = fmd->getNumLocation() - stripes;\n\n  if (sdiff == 0) {\n    tag += \"0\";\n  } else {\n    if (sdiff > 0) {\n      tag += \"+\";\n    }\n\n    tag += std::to_string(sdiff);\n\n    if (mCurrentStats.NumFaultyFiles < maxfaulty) {\n      mCurrentStats.FaultyFiles[tag][fmd->getId()] = fmd->getLayoutId();\n    }\n\n    mCurrentStats.NumFaultyFiles++;\n  }\n\n#define UNDEFINED_BIN (100 *365 * 86400.0)\n  mCurrentStats.ScanStats[lid][tag]++;\n  static std::set<double> time_bin {0, 86400ll, 7 * 86400ll, 30 * 86400ll, 90 * 86400ll,\n                                    182.5 * 86400ll, 365 * 86400ll, 2 * 365 * 86400ll, 5 * 365 * 86400ll, UNDEFINED_BIN};\n  size_t atime_bin = 0;\n  {\n    // create access time distributions\n    eos::IFileMD::ctime_t atime;\n    fmd->getATime(atime);\n\n    if (!atime.tv_sec) {\n      mCurrentStats.AccessTimeFiles[UNDEFINED_BIN]++;\n      mCurrentStats.AccessTimeVolume[UNDEFINED_BIN] += fmd->getSize();\n      atime_bin = UNDEFINED_BIN;\n    } else {\n      // future access time goes to bin 0\n      if (atime.tv_sec > mCurrentStats.TimeScan) {\n        mCurrentStats.AccessTimeFiles[0]++;\n        mCurrentStats.AccessTimeVolume[0] += fmd->getSize();\n        atime_bin = 0;\n      } else {\n        for (auto rev_it = time_bin.rbegin(); rev_it != time_bin.rend(); rev_it++) {\n          if ((mCurrentStats.TimeScan - (int64_t)atime.tv_sec) >= (int64_t) *rev_it) {\n            mCurrentStats.AccessTimeFiles[(uint64_t)*rev_it]++;\n            mCurrentStats.AccessTimeVolume[*rev_it] += fmd->getSize();\n            atime_bin = *rev_it;\n            break;\n          }\n        }\n      }\n    }\n  }\n  {\n    // create birth time distributions\n    double ageInYears = 0; // stores the ages of a file in years as a double\n    eos::IFileMD::ctime_t btime {0, 0};\n    eos::IFileMD::XAttrMap xattrs = fmd->getAttributes();\n    uint64_t sizeBytes = fmd->getSize();\n    // size bins upper bounds in bytes; 0 => >= 1TB\n    static const uint64_t KB = 1024ull;\n    static const uint64_t MB = KB * 1024ull;\n    static const uint64_t GB = MB * 1024ull;\n    static const uint64_t TB = GB * 1024ull;\n    static const std::vector<uint64_t> size_bins {\n      4 * KB, 1 * MB, 16 * MB, 64 * MB, 128 * MB, 256 * MB,\n      1 * GB, 4 * GB, 16 * GB, 128 * GB, 512 * GB, 1 * TB\n    };\n    uint64_t size_bin_key = 0; // default for >= 1TB\n\n    for (auto ub : size_bins) {\n      if (sizeBytes < ub) {\n        size_bin_key = ub;\n        break;\n      }\n    }\n\n    if (xattrs.count(\"sys.eos.btime\")) {\n      eos::common::Timing::Timespec_from_TimespecStr(xattrs[\"sys.eos.btime\"], btime);\n\n      if (btime.tv_sec > mCurrentStats.TimeScan) {\n        ageInYears = 0;\n      } else {\n        ageInYears = (mCurrentStats.TimeScan - btime.tv_sec) / (86400 * 365.0);\n      }\n    } else {\n      eos::IFileMD::ctime_t ctime;\n      fmd->getCTime(ctime);\n\n      if (ctime.tv_sec > mCurrentStats.TimeScan) {\n        ageInYears = 0;\n      } else {\n        ageInYears = (mCurrentStats.TimeScan - ctime.tv_sec) / (86400 * 365.0);\n      }\n    }\n\n    // future birth time goes to bin 0\n    if (btime.tv_sec > mCurrentStats.TimeScan) {\n      mCurrentStats.BirthTimeFiles[0]++;\n      mCurrentStats.BirthTimeVolume[0] += fmd->getSize();\n      mCurrentStats.BirthVsAccessTimeFiles[0][atime_bin]++;\n      mCurrentStats.BirthVsAccessTimeVolume[0][atime_bin] += fmd->getSize();\n      // size distributions\n      mCurrentStats.SizeBinsFiles[size_bin_key]++;\n      mCurrentStats.SizeBinsVolume[size_bin_key] += sizeBytes;\n      mCurrentStats.BirthVsSizeFiles[0][size_bin_key]++;\n      mCurrentStats.BirthVsSizeVolume[0][size_bin_key] += sizeBytes;\n    } else {\n      for (auto rev_it = time_bin.rbegin(); rev_it != time_bin.rend(); rev_it++) {\n        if ((mCurrentStats.TimeScan - (int64_t)btime.tv_sec) >= (int64_t) *rev_it) {\n          mCurrentStats.BirthTimeFiles[(uint64_t)*rev_it]++;\n          mCurrentStats.BirthTimeVolume[*rev_it] += fmd->getSize();\n          mCurrentStats.BirthVsAccessTimeFiles[*rev_it][atime_bin]++;\n          mCurrentStats.BirthVsAccessTimeVolume[*rev_it][atime_bin] += fmd->getSize();\n          // size distributions\n          mCurrentStats.SizeBinsFiles[size_bin_key]++;\n          mCurrentStats.SizeBinsVolume[size_bin_key] += sizeBytes;\n          mCurrentStats.BirthVsSizeFiles[*rev_it][size_bin_key]++;\n          mCurrentStats.BirthVsSizeVolume[*rev_it][size_bin_key] += sizeBytes;\n          break;\n        }\n      }\n    }\n\n    double costdisk = disksize * PriceTbPerYearDisk * ageInYears;\n    double costtape = tapesize * PriceTbPerYearTape * ageInYears;\n\n    if (costdisk) {\n      // create costs disk\n      mCurrentStats.UserCosts[0][fmd->getCUid()]  += costdisk;\n      mCurrentStats.GroupCosts[0][fmd->getCGid()] += costdisk;\n      mCurrentStats.TotalCosts[0] += costdisk;\n    }\n\n    if (costtape) {\n      // create costs tape\n      mCurrentStats.UserCosts[1][fmd->getCUid()]  += costtape;\n      mCurrentStats.GroupCosts[1][fmd->getCGid()] += costtape;\n      mCurrentStats.TotalCosts[1] += costtape;\n    }\n\n    if (disksize) {\n      // create costs disk\n      mCurrentStats.UserBytes[0][fmd->getCUid()]  += disksize;\n      mCurrentStats.GroupBytes[0][fmd->getCGid()] += disksize;\n      mCurrentStats.TotalBytes[0] += disksize;\n    }\n\n    if (tapesize) {\n      // create costs tape\n      mCurrentStats.UserBytes[1][fmd->getCUid()]  += tapesize;\n      mCurrentStats.GroupBytes[1][fmd->getCGid()] += tapesize;\n      mCurrentStats.TotalBytes[1] += tapesize;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Dump current status\n//------------------------------------------------------------------------------\nvoid\nFileInspector::Dump(std::string& out, std::string_view options,\n                    const LockFsView lockfsview)\n{\n  char line[4096];\n  time_t now = time(NULL);\n  const bool is_monitoring = (options.find('m') != std::string::npos);\n  bool printall = false; // normally we only print top 10!\n  bool printlayouts = true;\n  bool printcosts = true;\n  bool printusage = true;\n  bool printaccesstime = true;\n  bool printbirthtime = true;\n  bool printbirthvsaccesstime = true;\n  bool printmoney = false;\n\n  if (options.find(\"Z\") != std::string::npos) {\n    printall = true;\n  }\n\n  if (options.find('M') != std::string::npos) {\n    printmoney = true;\n  }\n\n  if (options.find('L') != std::string::npos  ||\n      options.find('C') != std::string::npos ||\n      options.find('U') != std::string::npos ||\n      options.find('A') != std::string::npos ||\n      options.find('B') != std::string::npos ||\n      options.find('V') != std::string::npos) {\n    printlayouts = printcosts = printusage = printaccesstime = printbirthtime =\n                                  printbirthvsaccesstime = false;\n\n    if (options.find('L') != std::string::npos) {\n      printlayouts = true;\n    }\n\n    if (options.find('C') != std::string::npos) {\n      printcosts = true;\n    }\n\n    if (options.find('A') != std::string::npos) {\n      printaccesstime = true;\n    }\n\n    if (options.find('U') != std::string::npos) {\n      printusage = true;\n    }\n\n    if (options.find('B') != std::string::npos) {\n      printbirthtime = true;\n    }\n\n    if (options.find('V') != std::string::npos) {\n      printbirthvsaccesstime = true;\n    }\n  }\n\n  if (!is_monitoring) {\n    out += \"# ------------------------------------------------------------------------------------\\n\";\n    out += \"# \";\n    out += eos::common::Timing::ltime(now);\n    out += \"\\n\";\n\n    // Summary at top: total files and average filesize\n    if (mLastStats.TotalFileCount > 0) {\n      double avg = static_cast<double>(mLastStats.TotalLogicalBytes) /\n                   static_cast<double>(mLastStats.TotalFileCount);\n      std::string totals =\n        eos::common::StringConversion::GetReadableSizeString(\n          mLastStats.TotalLogicalBytes, \"B\");\n      std::string avgReadable =\n        eos::common::StringConversion::GetReadableSizeString(avg, \"B\");\n      // Reconstruct with totals\n      char buf[512];\n      snprintf(buf, sizeof(buf),\n               \"# total_files: %lu\\n# total_size: %s\\n# average_filesize: %s\\n\",\n               (unsigned long)mLastStats.TotalFileCount, totals.c_str(), avgReadable.c_str());\n      out += buf;\n    } else {\n      out += \"# total_files: 0\\n# total_size: 0B\\n# average_filesize: 0B\\n\";\n    }\n\n    // Size histogram (files) using predefined bins\n    {\n      out += \"# Size histogram (files)\\n\";\n      // Define bins in the desired order (upper bound in bytes; 0 => >= 1TB)\n      static const uint64_t KB = 1024ull;\n      static const uint64_t MB = KB * 1024ull;\n      static const uint64_t GB = MB * 1024ull;\n      static const uint64_t TB = GB * 1024ull;\n      static const std::vector<uint64_t> bins {\n        4 * KB, 1 * MB, 16 * MB, 64 * MB, 128 * MB, 256 * MB,\n        1 * GB, 4 * GB, 16 * GB, 128 * GB, 512 * GB, 1 * TB, 0ull\n      };\n      static const std::vector<std::string> labels {\n        \"<4K\", \"<1M\", \"<16M\", \"<64M\", \"<128M\", \"<256M\",\n        \"<1G\", \"<4G\", \"<16G\", \"<128G\", \"<512G\", \"<1T\", \">=1T\"\n      };\n      std::vector<uint64_t> counts;\n      counts.reserve(bins.size());\n      uint64_t maxc = 0;\n\n      for (auto ub : bins) {\n        uint64_t c = 0;\n        auto it = mLastStats.SizeBinsFiles.find(ub);\n\n        if (it != mLastStats.SizeBinsFiles.end()) {\n          c = it->second;\n        }\n\n        counts.push_back(c);\n\n        if (c > maxc) {\n          maxc = c;\n        }\n      }\n\n      // Render vertical columns with a maximum height and axes\n      const int colWidth = 6;       // width per bin column\n      const int yLabelW  = 8;       // width for Y-axis numeric labels\n      const int maxHeight = 20;\n      uint64_t scale = (maxc > (uint64_t)maxHeight) ? ((maxc + maxHeight - 1) /\n                       maxHeight) : 1;\n      std::vector<uint64_t> heights;\n      heights.reserve(counts.size());\n\n      for (auto c : counts) {\n        uint64_t h = (c + scale - 1) / scale;\n        heights.push_back(h);\n      }\n\n      uint64_t hmax = 0;\n\n      for (auto h : heights) if (h > hmax) {\n          hmax = h;\n        }\n\n      // Top arrow for Y axis, aligned under the Y label field\n      {\n        std::string line = \"# \";\n\n        for (int i = 0; i < yLabelW; ++i) {\n          line += \" \";\n        }\n\n        line += \"\\xE2\\x86\\x91\"; // \"↑\"\n        line += \"\\n\";\n        out += line;\n      }\n\n      for (uint64_t row = hmax; row >= 1; --row) {\n        std::string line = \"# \";\n        // Y-axis numeric label (approximate count at this tick)\n        char ybuf[32];\n        snprintf(ybuf, sizeof(ybuf), \"%6lu \", (unsigned long)(row * scale));\n        // right align in yLabelW\n        char yfield[32];\n        snprintf(yfield, sizeof(yfield), \"%*s\", yLabelW, ybuf);\n        line += yfield;\n        line += \"\\xE2\\x94\\x82\"; // \"│\"\n\n        for (size_t i = 0; i < heights.size(); ++i) {\n          if (heights[i] >= row) {\n            line += \"  \\xE2\\x96\\x88   \"; // \"█\"\n          } else {\n            line += \"      \";\n          }\n        }\n\n        line += \"\\n\";\n        out += line;\n\n        if (row == 1) {\n          break;  // avoid unsigned wrap\n        }\n      }\n\n      // X-axis base with arrow\n      {\n        std::string line = \"# \";\n\n        for (int i = 0; i < yLabelW; ++i) {\n          line += \" \";\n        }\n\n        line += \"\\xE2\\x94\\x94\"; // \"└\"\n\n        for (size_t i = 0; i < labels.size(); ++i) {\n          for (int k = 0; k < colWidth; ++k) {\n            line += \"\\xE2\\x94\\x80\";  // \"─\"\n          }\n        }\n\n        line += \"\\xE2\\x86\\x92\"; // \"→\"\n        line += \"\\n\";\n        out += line;\n      }\n      // X-axis labels\n      {\n        std::string line = \"# \";\n\n        for (int i = 0; i < yLabelW; ++i) {\n          line += \" \";\n        }\n\n        line += \" \";\n\n        for (size_t i = 0; i < labels.size(); ++i) {\n          char buf[16];\n          snprintf(buf, sizeof(buf), \"%-6s\", labels[i].c_str());\n          line += buf;\n        }\n\n        line += \"\\n\";\n        out += line;\n      }\n      // Scale note\n      {\n        char buf[128];\n        snprintf(buf, sizeof(buf), \"# (each \\xE2\\x96\\x88 ~ %lu files)\\n\",\n                 (unsigned long)scale); // \"█\"\n        out += buf;\n      }\n    }\n  }\n\n  if (!enabled()) {\n    if (is_monitoring) {\n      out = \"key=error space=\" + mSpaceName + \" msg=\\\"inspector disabled\\\"\";\n    } else {\n      out += \"# inspector is disabled - use 'eos space config default space.inspector=on'\\n\";\n    }\n\n    return;\n  }\n\n  std::lock_guard<std::mutex> lock(mutexScanStats);\n\n  if (options.find(\"m\") != std::string::npos) {\n    // Monitoring: emit summary as two lines\n    {\n      std::string l;\n      l = \"key=last tag=summary::total_files value=\";\n      l += std::to_string(mLastStats.TotalFileCount);\n      out += l;\n      out += \"\\n\";\n      l = \"key=last tag=summary::avg_filesize value=\";\n\n      if (mLastStats.TotalFileCount > 0) {\n        l += std::to_string(mLastStats.TotalLogicalBytes / mLastStats.TotalFileCount);\n      } else {\n        l += \"0\";\n      }\n\n      out += l;\n      out += \"\\n\";\n    }\n\n    for (auto it = mLastStats.ScanStats.begin(); it != mLastStats.ScanStats.end();\n         ++it) {\n      snprintf(line, sizeof(line),\n               \"key=last layout=%08lx type=%s nominal_stripes=%s checksum=%s \"\n               \"blockchecksum=%s blocksize=%s\",\n               it->first,\n               eos::common::LayoutId::GetLayoutTypeString(it->first),\n               eos::common::LayoutId::GetStripeNumberString(it->first).c_str(),\n               eos::common::LayoutId::GetChecksumStringReal(it->first),\n               eos::common::LayoutId::GetBlockChecksumString(it->first),\n               eos::common::LayoutId::GetBlockSizeString(it->first));\n      out += line;\n\n      for (auto mit = it->second.begin(); mit != it->second.end(); ++mit) {\n        snprintf(line, sizeof(line), \" %s=%lu\",  mit->first.c_str(), mit->second);\n        out += line;\n      }\n\n      out += \"\\n\";\n    }\n\n    out += \"key=last tag=links::hardlink_count value=\";\n    out += std::to_string(mLastStats.HardlinkCount);\n    out += \"\\n\";\n    out += \"key=last tag=links::hardlink_volume value=\";\n    out += std::to_string(mLastStats.HardlinkVolume);\n    out += \"\\n\";\n    out += \"key=last tag=links::symlink_count value=\";\n    out += std::to_string(mLastStats.SymlinkCount);\n    out += \"\\n\";\n\n    if (mLastStats.AccessTimeFiles.size()) {\n      for (auto it = mLastStats.AccessTimeFiles.begin();\n           it != mLastStats.AccessTimeFiles.end();\n           ++it) {\n        std::string afiles = \"key=last tag=accesstime::files bin=\";\n        afiles += std::to_string(it->first);\n        afiles += \" value=\";\n        afiles += std::to_string(it->second);\n        out += afiles;\n        out += \"\\n\";\n      }\n    }\n\n    if (mLastStats.AccessTimeVolume.size()) {\n      for (auto it = mLastStats.AccessTimeVolume.begin();\n           it != mLastStats.AccessTimeVolume.end();\n           ++it) {\n        std::string avolume = \"key=last tag=accesstime::volume bin=\";\n        avolume += std::to_string(it->first);\n        avolume += \" value=\";\n        avolume += std::to_string(it->second);\n        out += avolume;\n        out += \"\\n\";\n      }\n    }\n\n    if (mLastStats.BirthTimeFiles.size()) {\n      for (auto it = mLastStats.BirthTimeFiles.begin();\n           it != mLastStats.BirthTimeFiles.end();\n           ++it) {\n        std::string bfiles = \"key=last tag=birthtime::files bin=\";\n        bfiles += std::to_string(it->first);\n        bfiles += \" value=\";\n        bfiles += std::to_string(it->second);\n        bfiles += \" \";\n        out += bfiles;\n        out += \"\\n\";\n      }\n    }\n\n    if (mLastStats.BirthTimeVolume.size()) {\n      for (auto it = mLastStats.BirthTimeVolume.begin();\n           it != mLastStats.BirthTimeVolume.end();\n           ++it) {\n        std::string bvolume = \"key=last tag=birthtime::volume bin=\";\n        bvolume += std::to_string(it->first);\n        bvolume += \" value=\";\n        bvolume += std::to_string(it->second);\n        bvolume += \" \";\n        out += bvolume;\n        out += \"\\n\";\n      }\n    }\n\n    if (mLastStats.BirthVsAccessTimeFiles.size()) {\n      for (auto it = mLastStats.BirthVsAccessTimeFiles.begin();\n           it != mLastStats.BirthVsAccessTimeFiles.end();\n           ++it) {\n        for (auto iit = it->second.begin(); iit != it->second.end(); ++iit) {\n          std::string bfiles = \"key=last tag=birthvsaccesstime::files xbin=\";\n          bfiles += std::to_string(it->first);\n          bfiles += \" ybin=\";\n          bfiles += std::to_string(iit->first);\n          bfiles += \" value=\";\n          bfiles += std::to_string(iit->second);\n          out += bfiles;\n          out += \"\\n\";\n        }\n      }\n    }\n\n    if (mLastStats.BirthTimeVolume.size()) {\n      for (auto it = mLastStats.BirthTimeVolume.begin();\n           it != mLastStats.BirthTimeVolume.end();\n           ++it) {\n        std::string bvolume = \"key=last tag=birthtime::volume bin=\";\n        bvolume += std::to_string(it->first);\n        bvolume += \" value=\";\n        bvolume += std::to_string(it->second);\n        out += bvolume;\n        out += \"\\n\";\n      }\n    }\n\n    // Size distributions (files/volume)\n    if (mLastStats.SizeBinsFiles.size()) {\n      for (auto it = mLastStats.SizeBinsFiles.begin();\n           it != mLastStats.SizeBinsFiles.end(); ++it) {\n        std::string sfiles = \"key=last tag=size::files bin=\";\n        sfiles += std::to_string(it->first);\n        sfiles += \" value=\";\n        sfiles += std::to_string(it->second);\n        out += sfiles;\n        out += \"\\n\";\n      }\n    }\n\n    if (mLastStats.SizeBinsVolume.size()) {\n      for (auto it = mLastStats.SizeBinsVolume.begin();\n           it != mLastStats.SizeBinsVolume.end(); ++it) {\n        std::string svol = \"key=last tag=size::volume bin=\";\n        svol += std::to_string(it->first);\n        svol += \" value=\";\n        svol += std::to_string(it->second);\n        out += svol;\n        out += \"\\n\";\n      }\n    }\n\n    // Birth vs Size (files/volume)\n    if (mLastStats.BirthVsSizeFiles.size()) {\n      for (auto it = mLastStats.BirthVsSizeFiles.begin();\n           it != mLastStats.BirthVsSizeFiles.end(); ++it) {\n        for (auto jt = it->second.begin(); jt != it->second.end(); ++jt) {\n          std::string bs = \"key=last tag=birthvssize::files xbin=\";\n          bs += std::to_string(it->first);\n          bs += \" ybin=\";\n          bs += std::to_string(jt->first);\n          bs += \" value=\";\n          bs += std::to_string(jt->second);\n          out += bs;\n          out += \"\\n\";\n        }\n      }\n    }\n\n    if (mLastStats.BirthVsSizeVolume.size()) {\n      for (auto it = mLastStats.BirthVsSizeVolume.begin();\n           it != mLastStats.BirthVsSizeVolume.end(); ++it) {\n        for (auto jt = it->second.begin(); jt != it->second.end(); ++jt) {\n          std::string bsv = \"key=last tag=birthvssize::volume xbin=\";\n          bsv += std::to_string(it->first);\n          bsv += \" ybin=\";\n          bsv += std::to_string(jt->first);\n          bsv += \" value=\";\n          bsv += std::to_string(jt->second);\n          out += bsv;\n          out += \"\\n\";\n        }\n      }\n    }\n\n    for (auto n = 0; n < 2; ++n) {\n      std::string media = \"disk\";\n      double price = PriceTbPerYearDisk;\n\n      if (n == 1) {\n        media = \"tape\";\n        price = PriceTbPerYearTape;\n      }\n\n      if (mLastStats.UserCosts[n].size()) {\n        for (auto it = mLastStats.UserCosts[n].begin();\n             it != mLastStats.UserCosts[n].end();\n             ++it) {\n          std::string ucost = \"key=last tag=user::cost::\";\n          ucost += media;\n          ucost += \" \";\n          int terrc = 0;\n          std::string username = eos::common::Mapping::UidToUserName(it->first, terrc);\n\n          if (terrc) {\n            username = std::to_string(it->first);\n          }\n\n          ucost += \"username=\";\n          ucost += username;\n          ucost += \" uid=\";\n          ucost += std::to_string(it->first);\n          ucost += \" cost=\";\n          ucost += std::to_string(it->second / 1000000000000.0);\n          ucost += \" price=\";\n          ucost += std::to_string(price);\n          ucost += \" tbyears=\";\n\n          if (price) {\n            ucost += std::to_string(it->second / 1000000000000.0 / price);\n          }\n\n          out += ucost;\n          out += \"\\n\";\n        }\n      }\n\n      if (mLastStats.GroupCosts[n].size()) {\n        for (auto it = mLastStats.GroupCosts[n].begin();\n             it != mLastStats.GroupCosts[n].end();\n             ++it) {\n          std::string gcost = \"key=last tag=group::cost::\";\n          gcost += media;\n          gcost += \" \";\n          int terrc = 0;\n          std::string groupname = eos::common::Mapping::GidToGroupName(it->first, terrc);\n\n          if (terrc) {\n            groupname = std::to_string(it->first);\n          }\n\n          gcost += \"groupname=\";\n          gcost += groupname;\n          gcost += \" gid=\";\n          gcost += std::to_string(it->first);\n          gcost += \" cost=\";\n          gcost += std::to_string(it->second / 1000000000000.0);\n          gcost += \" price=\";\n          gcost += std::to_string(price);\n          gcost += \" tbyears=\";\n\n          if (price) {\n            gcost += std::to_string(it->second / 1000000000000.0 / price);\n          }\n\n          out += gcost;\n          out += \"\\n\";\n        }\n      }\n\n      if (mLastStats.UserBytes[n].size()) {\n        for (auto it = mLastStats.UserBytes[n].begin();\n             it != mLastStats.UserBytes[n].end();\n             ++it) {\n          std::string ubytes = \"key=last tag=user::bytes::\";\n          ubytes += media;\n          ubytes += \" \";\n          int terrc = 0;\n          std::string username = eos::common::Mapping::UidToUserName(it->first, terrc);\n\n          if (terrc) {\n            username = std::to_string(it->first);\n          }\n\n          ubytes += \"username=\";\n          ubytes += username;\n          ubytes += \" uid=\";\n          ubytes += std::to_string(it->first);\n          ubytes += \" bytes=\";\n          ubytes += std::to_string(it->second);\n          out += ubytes;\n          out += \"\\n\";\n        }\n      }\n\n      if (mLastStats.GroupBytes[n].size()) {\n        for (auto it = mLastStats.GroupBytes[n].begin();\n             it != mLastStats.GroupBytes[n].end();\n             ++it) {\n          std::string gbytes = \"key=last tag=group::bytes::\";\n          gbytes += media;\n          gbytes += \" \";\n          int terrc = 0;\n          std::string groupname = eos::common::Mapping::GidToGroupName(it->first, terrc);\n\n          if (terrc) {\n            groupname = std::to_string(it->first);\n          }\n\n          gbytes += \"groupname=\";\n          gbytes += groupname;\n          gbytes += \" gid=\";\n          gbytes += std::to_string(it->first);\n          gbytes += \" bytes=\";\n          gbytes += std::to_string(it->second);\n          out += gbytes;\n          out += \"\\n\";\n        }\n      }\n    }\n\n    return;\n  }\n\n  Options opts = getOptions(lockfsview);\n  out += \"# \";\n  out += std::to_string((int)(scanned_percent.load()));\n  out += \" % done - estimate to finish: \";\n  out += std::to_string((int)(opts.interval.count() - (scanned_percent.load() *\n                              opts.interval.count() / 100.0)));\n  out += \" seconds\\n\";\n\n  if ((options.find(\"c\") != std::string::npos)) {\n    if (options.find(\"p\") != std::string::npos) {\n      for (const auto& i : mCurrentStats.FaultyFiles) {\n        for (const auto& pair : i.second) {\n          out += \"fxid:\";\n          out += eos::common::FileId::Fid2Hex(pair.first);\n          out += \" layoutid:\";\n          out += eos::common::StringConversion::integral_to_hex(pair.second).c_str();\n          out += \" \";\n          out += i.first;\n          out += \"\\n\";\n        }\n      }\n    } else if (options.find(\"e\") != std::string::npos) {\n      std::string exportname = \"/var/log/eos/mgm/FileInspector.\";\n      exportname += std::to_string(now);\n      exportname += \".list\";\n      std::ofstream exportfile(exportname);\n\n      if (exportfile.is_open()) {\n        for (const auto& i : mCurrentStats.FaultyFiles) {\n          for (const auto& pair : i.second) {\n            exportfile << \"fxid:\" << eos::common::FileId::Fid2Hex(pair.first)\n                       << \" layoutid:\"\n                       << eos::common::StringConversion::integral_to_hex(pair.second)\n                       << \" \" << i.first << \"\\n\";\n          }\n        }\n\n        out += \"# file list exported on MGM to '\";\n        out += exportname;\n        out += \"'\\n\";\n        exportfile.close();\n      } else {\n        out += \"# file list could not be written on MGM to '\";\n        out += exportname;\n        out += \"'\\n\";\n      }\n    } else {\n      out += \"# current scan          : \";\n      out += eos::common::Timing::ltime(mCurrentStats.TimeScan).c_str();\n      out += \"\\n\";\n      out += \"# not-found-during-scan : \";\n      out += std::to_string(mCurrentStats.ScanStats[999999999][\"unfound\"]);\n      out += \"\\n\";\n\n      for (auto it = mCurrentStats.ScanStats.begin();\n           it != mCurrentStats.ScanStats.end(); ++it) {\n        if (it->first == 999999999) {\n          continue;\n        }\n\n        snprintf(line, sizeof(line),\n                 \" layout=%08lx type=%-13s nominal_stripes=%s checksum=%-8s \"\n                 \"blockchecksum=%-8s blocksize=%-4s\\n\\n\",\n                 it->first,\n                 eos::common::LayoutId::GetLayoutTypeString(it->first),\n                 eos::common::LayoutId::GetStripeNumberString(it->first).c_str(),\n                 eos::common::LayoutId::GetChecksumStringReal(it->first),\n                 eos::common::LayoutId::GetBlockChecksumString(it->first),\n                 eos::common::LayoutId::GetBlockSizeString(it->first));\n        out +=  \"======================================================================================\\n\";\n        out += line;\n\n        for (auto mit = it->second.begin(); mit != it->second.end(); ++mit) {\n          snprintf(line, sizeof(line), \" %-32s : %lu\\n\",  mit->first.c_str(),\n                   mit->second);\n          out += line;\n        }\n\n        out += \"\\n\";\n      }\n    }\n  }\n\n  if ((options.find(\"l\") != std::string::npos)) {\n    if (options.find(\"p\") != std::string::npos) {\n      for (auto& i : mLastStats.FaultyFiles) {\n        for (auto& pair : i.second) {\n          out += \"fxid:\";\n          out += eos::common::FileId::Fid2Hex(pair.first);\n          out += \" layoutid:\";\n          out += eos::common::StringConversion::integral_to_hex(pair.second).c_str();\n          out += \" \";\n          out += i.first;\n          out += \"\\n\";\n        }\n      }\n    } else if (options.find(\"e\") != std::string::npos) {\n      std::string exportname = \"/var/log/eos/mgm/FileInspector.\";\n      exportname += std::to_string(now);\n      exportname += \".list\";\n      std::ofstream exportfile(exportname);\n\n      if (exportfile.is_open()) {\n        for (auto& i : mLastStats.FaultyFiles) {\n          for (auto& pair : i.second) {\n            exportfile << \"fxid:\" << eos::common::FileId::Fid2Hex(pair.first)\n                       << \" layoutid:\"\n                       << eos::common::StringConversion::integral_to_hex(pair.second)\n                       << \" \" << i.first << \"\\n\";\n          }\n        }\n\n        out += \"# file list exported on MGM to '\";\n        out += exportname;\n        out += \"'\\n\";\n        exportfile.close();\n      } else {\n        out += \"# file list could not be written on MGM to '\";\n        out += exportname;\n        out += \"'\\n\";\n      }\n    } else {\n      if (printlayouts) {\n        out += \"# last scan             : \";\n        out += eos::common::Timing::ltime(mLastStats.TimeScan).c_str();\n        out += \"\\n\";\n        out += \"# not-found-during-scan : \";\n        out += std::to_string(mLastStats.ScanStats[999999999][\"unfound\"]);\n        out += \"\\n\";\n\n        for (auto it = mLastStats.ScanStats.begin(); it != mLastStats.ScanStats.end();\n             ++it) {\n          if (it->first == 999999999) {\n            continue;\n          }\n\n          snprintf(line, sizeof(line),\n                   \" layout=%08lx type=%-13s nominal_stripes=%s checksum=%-8s \"\n                   \"blockchecksum=%-8s blocksize=%-4s\\n\\n\",\n                   it->first,\n                   eos::common::LayoutId::GetLayoutTypeString(it->first),\n                   eos::common::LayoutId::GetStripeNumberString(it->first).c_str(),\n                   eos::common::LayoutId::GetChecksumStringReal(it->first),\n                   eos::common::LayoutId::GetBlockChecksumString(it->first),\n                   eos::common::LayoutId::GetBlockSizeString(it->first));\n          out +=  \"======================================================================================\\n\";\n          out += line;\n\n          for (auto mit = it->second.begin(); mit != it->second.end(); ++mit) {\n            snprintf(line, sizeof(line), \" %-32s : %lu\\n\",  mit->first.c_str(),\n                     mit->second);\n            out += line;\n          }\n\n          out += \"\\n\";\n        }\n\n        if (mLastStats.HardlinkCount > 0 || mLastStats.SymlinkCount > 0) {\n          out += \"======================================================================================\\n\";\n          out += \" Links\\n\\n\";\n          snprintf(line, sizeof(line), \" %-32s : %lu\\n\", \"hardlink count\",\n                   mLastStats.HardlinkCount);\n          out += line;\n          snprintf(line, sizeof(line), \" %-32s : %s\\n\", \"hardlink volume (referenced)\",\n                   eos::common::StringConversion::GetReadableSizeString(\n                     mLastStats.HardlinkVolume, \"B\").c_str());\n          out += line;\n          snprintf(line, sizeof(line), \" %-32s : %lu\\n\", \"symbolic count\",\n                   mLastStats.SymlinkCount);\n          out += line;\n          out += \"\\n\";\n        }\n      }\n\n      if (printaccesstime && mLastStats.AccessTimeFiles.size()) {\n        out +=  \"======================================================================================\\n\";\n        out +=  \" Access time distribution of files\\n\";\n        uint64_t totalfiles = 0;\n\n        for (auto it = mLastStats.AccessTimeFiles.begin();\n             it != mLastStats.AccessTimeFiles.end();\n             ++it) {\n          totalfiles += it->second;\n        }\n\n        for (auto it = mLastStats.AccessTimeFiles.begin();\n             it != mLastStats.AccessTimeFiles.end();\n             ++it) {\n          double fraction = totalfiles ? (100.0 * it->second / totalfiles) : 0;\n          XrdOucString age;\n          snprintf(line, sizeof(line), \" %-32s : %s (%.02f%%)\\n\",\n                   eos::common::StringConversion::GetReadableAgeString(age, it->first),\n                   eos::common::StringConversion::GetReadableSizeString(it->second, \"\").c_str(),\n                   fraction);\n          out += line;\n        }\n      }\n\n      if (printaccesstime && mLastStats.AccessTimeVolume.size()) {\n        out +=  \"======================================================================================\\n\";\n        out +=  \" Access time volume distribution of files\\n\";\n        uint64_t totalvolume = 0;\n\n        for (auto it = mLastStats.AccessTimeVolume.begin();\n             it != mLastStats.AccessTimeVolume.end();\n             ++it) {\n          totalvolume += it->second;\n        }\n\n        for (auto it = mLastStats.AccessTimeVolume.begin();\n             it != mLastStats.AccessTimeVolume.end();\n             ++it) {\n          double fraction = totalvolume ? (100.0 * it->second / totalvolume) : 0;\n          XrdOucString age;\n          snprintf(line, sizeof(line), \" %-32s : %16s (%.02f%%)\\n\",\n                   eos::common::StringConversion::GetReadableAgeString(age, it->first),\n                   eos::common::StringConversion::GetReadableSizeString(it->second, \"B\").c_str(),\n                   fraction);\n          out += line;\n        }\n      }\n\n      if (printbirthtime && mLastStats.BirthTimeFiles.size()) {\n        out +=  \"======================================================================================\\n\";\n        out +=  \" Birth time distribution of files\\n\";\n        uint64_t totalfiles = 0;\n\n        for (auto it = mLastStats.BirthTimeFiles.begin();\n             it != mLastStats.BirthTimeFiles.end();\n             ++it) {\n          totalfiles += it->second;\n        }\n\n        for (auto it = mLastStats.BirthTimeFiles.begin();\n             it != mLastStats.BirthTimeFiles.end();\n             ++it) {\n          double fraction = totalfiles ? (100.0 * it->second / totalfiles) : 0;\n          XrdOucString age;\n          snprintf(line, sizeof(line), \" %-32s : %16s (%.02f%%)\\n\",\n                   eos::common::StringConversion::GetReadableAgeString(age, it->first),\n                   eos::common::StringConversion::GetReadableSizeString(it->second, \"\").c_str(),\n                   fraction);\n          out += line;\n        }\n      }\n\n      if (printbirthtime && mLastStats.BirthTimeVolume.size()) {\n        out +=  \"======================================================================================\\n\";\n        out +=  \" Birth time volume distribution of files\\n\";\n        uint64_t totalvolume = 0;\n\n        for (auto it = mLastStats.BirthTimeVolume.begin();\n             it != mLastStats.BirthTimeVolume.end();\n             ++it) {\n          totalvolume += it->second;\n        }\n\n        for (auto it = mLastStats.BirthTimeVolume.begin();\n             it != mLastStats.BirthTimeVolume.end();\n             ++it) {\n          double fraction = totalvolume ? (100.0 * it->second / totalvolume) : 0;\n          XrdOucString age;\n          snprintf(line, sizeof(line), \" %-32s : %16s (%.02f%%)\\n\",\n                   eos::common::StringConversion::GetReadableAgeString(age, it->first),\n                   eos::common::StringConversion::GetReadableSizeString(it->second, \"B\").c_str(),\n                   fraction);\n          out += line;\n        }\n      }\n\n      if (printbirthvsaccesstime && mLastStats.BirthVsAccessTimeFiles.size()) {\n        out +=  \"======================================================================================\\n\";\n        out +=  \" Birth vs Access time distribution of files\\n\";\n        std::map<time_t, uint64_t> totalfiles;\n\n        for (auto it = mLastStats.BirthVsAccessTimeFiles.begin();\n             it != mLastStats.BirthVsAccessTimeFiles.end();\n             ++it) {\n          for (auto iit = it->second.begin(); iit != it->second.end(); iit++) {\n            totalfiles[it->first] += iit->second;\n          }\n        }\n\n        for (auto it = mLastStats.BirthVsAccessTimeFiles.begin();\n             it != mLastStats.BirthVsAccessTimeFiles.end();\n             ++it) {\n          XrdOucString age;\n          snprintf(line, sizeof(line), \" %-8s : [ \\n\",\n                   eos::common::StringConversion::GetReadableAgeString(age, it->first));\n          out += line;\n\n          for (auto iit = it->second.begin(); iit != it->second.end(); ++iit) {\n            double fraction = totalfiles[it->first] ? (100.0 * iit->second /\n                              totalfiles[it->first]) : 0;\n            snprintf(line, sizeof(line), \" %-8s     %-32s %16s (%.02f%%)\\n\",\n                     \"\",\n                     eos::common::StringConversion::GetReadableAgeString(age, iit->first),\n                     eos::common::StringConversion::GetReadableSizeString(iit->second, \"\").c_str(),\n                     fraction);\n            out += line;\n          }\n\n          snprintf(line, sizeof(line), \" %-8s   ] \\n\",\n                   \"\");\n          out += line;\n        }\n      }\n\n      if (printbirthvsaccesstime && mLastStats.BirthVsAccessTimeVolume.size()) {\n        out +=  \"======================================================================================\\n\";\n        out +=  \" Birth vs Access time volume distribution of files\\n\";\n        std::map<time_t, uint64_t> totalfiles;\n\n        for (auto it = mLastStats.BirthVsAccessTimeVolume.begin();\n             it != mLastStats.BirthVsAccessTimeVolume.end();\n             ++it) {\n          for (auto iit = it->second.begin(); iit != it->second.end(); iit++) {\n            totalfiles[it->first] += iit->second;\n          }\n        }\n\n        for (auto it = mLastStats.BirthVsAccessTimeVolume.begin();\n             it != mLastStats.BirthVsAccessTimeVolume.end();\n             ++it) {\n          XrdOucString age;\n          snprintf(line, sizeof(line), \" %-8s : [ \\n\",\n                   eos::common::StringConversion::GetReadableAgeString(age, it->first));\n          out += line;\n\n          for (auto iit = it->second.begin(); iit != it->second.end(); ++iit) {\n            double fraction = totalfiles[it->first] ? (100.0 * iit->second /\n                              totalfiles[it->first]) : 0;\n            snprintf(line, sizeof(line), \" %-8s     %-32s %16s (%.02f%%)\\n\",\n                     \"\",\n                     eos::common::StringConversion::GetReadableAgeString(age, iit->first),\n                     eos::common::StringConversion::GetReadableSizeString(iit->second, \"B\").c_str(),\n                     fraction);\n            out += line;\n          }\n\n          snprintf(line, sizeof(line), \" %-8s   ] \\n\",\n                   \"\");\n          out += line;\n        }\n      }\n\n      for (auto n = 0; n < 2; n++) {\n        std::string media = \"disk\";\n\n        if (n == 1) {\n          media = \"tape\";\n        }\n\n        std::string unit = \"[tb*years]\";\n        double rescale = 1.0;\n\n        if (printmoney) {\n          unit = \"[\";\n          unit += currency;\n          unit += \"]\";\n        } else {\n          if (n == 1) {\n            // tape price\n            rescale = PriceTbPerYearTape;\n          } else {\n            // disk price\n            rescale = PriceTbPerYearDisk;\n          }\n        }\n\n        if (printcosts && mLastStats.UserCosts[n].size()) {\n          out +=  \"======================================================================================\\n\";\n          out +=  \" Storage Costs - User View [ \";\n          out += media;\n          out += \" ]\\n\";\n          out +=  \" -------------------------------------------------------------------------------------\\n\";\n          out +=  \" Total Costs : \";\n          out += eos::common::StringConversion::GetReadableSizeString(\n                   mLastStats.TotalCosts[n] / 1000000000000.0 / rescale, unit.c_str()).c_str();\n          out += \"\\n\";\n          out +=  \" -------------------------------------------------------------------------------------\\n\";\n          size_t cnt = 0;\n          size_t top_cnt = 10;\n\n          if (printall) {\n            top_cnt = 1000000;\n          }\n\n          for (auto it = mLastStats.UserCosts[n].rbegin();\n               it != mLastStats.UserCosts[n].rend();\n               ++it) {\n            int terrc = 0;\n            std::string username = eos::common::Mapping::UidToUserName(it->first, terrc);\n\n            if (terrc) {\n              username = std::to_string(it->first);\n            }\n\n            if (it->first < 1) {\n              continue;\n            }\n\n            snprintf(line, sizeof(line), \" %02ld. %-28s : %s\\n\",\n                     ++cnt,\n                     username.c_str(),\n                     eos::common::StringConversion::GetReadableSizeString(it->second /\n                         1000000000000.0\n                         / rescale, unit.c_str()).c_str());\n            out += line;\n\n            if (cnt >= top_cnt) {\n              break;\n            }\n          }\n        }\n\n        if (printcosts && mLastStats.GroupCosts[n].size()) {\n          out +=  \"======================================================================================\\n\";\n          out +=  \" Storage Costs - Group View [ \";\n          out += media;\n          out += \" ]\\n\";\n          out +=  \" -------------------------------------------------------------------------------------\\n\";\n          out +=  \" Total Costs : \";\n          out += eos::common::StringConversion::GetReadableSizeString(\n                   mLastStats.TotalCosts[n] / 1000000000000.0 / rescale,\n                   unit.c_str()).c_str();\n          out += \"\\n\";\n          out +=  \" -------------------------------------------------------------------------------------\\n\";\n          size_t cnt = 0;\n          size_t top_cnt = 10;\n\n          if (printall) {\n            top_cnt = 1000000;\n          }\n\n          for (auto it = mLastStats.GroupCosts[n].rbegin();\n               it != mLastStats.GroupCosts[n].rend();\n               ++it) {\n            int terrc = 0;\n            std::string groupname = eos::common::Mapping::GidToGroupName(it->first, terrc);\n\n            if (terrc) {\n              groupname = std::to_string(it->first);\n            }\n\n            if (it->first < 1) {\n              continue;\n            }\n\n            snprintf(line, sizeof(line), \" %02ld. %-28s : %s\\n\",\n                     ++cnt,\n                     groupname.c_str(),\n                     eos::common::StringConversion::GetReadableSizeString(it->second /\n                         1000000000000.0\n                         / rescale, unit.c_str()).c_str());\n            out += line;\n\n            if (cnt >= top_cnt) {\n              break;\n            }\n          }\n        }\n\n        if (printusage && mLastStats.UserBytes[n].size()) {\n          out +=  \"======================================================================================\\n\";\n          out +=  \" Storage Bytes - User View [ \";\n          out += media;\n          out += \" ]\\n\";\n          out +=  \" -------------------------------------------------------------------------------------\\n\";\n          out +=  \" Total Bytes : \";\n          out += eos::common::StringConversion::GetReadableSizeString(\n                   mLastStats.TotalBytes[n], \"B\").c_str();\n          out += \"\\n\";\n          out +=  \" -------------------------------------------------------------------------------------\\n\";\n          size_t cnt = 0;\n          size_t top_cnt = 10;\n\n          if (printall) {\n            top_cnt = 1000000;\n          }\n\n          for (auto it = mLastStats.UserBytes[n].rbegin();\n               it != mLastStats.UserBytes[n].rend();\n               ++it) {\n            int terrc = 0;\n            std::string username = eos::common::Mapping::UidToUserName(it->first, terrc);\n\n            if (terrc) {\n              username = std::to_string(it->first);\n            }\n\n            if (it->first < 1) {\n              continue;\n            }\n\n            snprintf(line, sizeof(line), \" %02ld. %-28s : %s\\n\",\n                     ++cnt,\n                     username.c_str(),\n                     eos::common::StringConversion::GetReadableSizeString(it->second, \"B\").c_str());\n            out += line;\n\n            if (cnt >= top_cnt) {\n              break;\n            }\n          }\n        }\n\n        if (printusage && mLastStats.GroupBytes[n].size()) {\n          out +=  \"======================================================================================\\n\";\n          out +=  \" Storage Bytes - Group View [ \";\n          out += media;\n          out += \" ]\\n\";\n          out +=  \" -------------------------------------------------------------------------------------\\n\";\n          out +=  \" Total Bytes : \";\n          out += eos::common::StringConversion::GetReadableSizeString(\n                   mLastStats.TotalBytes[n], \"B\").c_str();\n          out += \"\\n\";\n          out +=  \" -------------------------------------------------------------------------------------\\n\";\n          size_t cnt = 0;\n          size_t top_cnt = 10;\n\n          if (printall) {\n            top_cnt = 1000000;\n          }\n\n          for (auto it = mLastStats.GroupBytes[n].rbegin();\n               it != mLastStats.GroupBytes[n].rend();\n               ++it) {\n            int terrc = 0;\n            std::string groupname = eos::common::Mapping::GidToGroupName(it->first, terrc);\n\n            if (terrc) {\n              groupname = std::to_string(it->first);\n            }\n\n            if (it->first < 1) {\n              continue;\n            }\n\n            snprintf(line, sizeof(line), \" %02ld. %-28s : %s\\n\",\n                     ++cnt,\n                     groupname.c_str(),\n                     eos::common::StringConversion::GetReadableSizeString(it->second, \"B\").c_str());\n            out += line;\n\n            if (cnt >= top_cnt) {\n              break;\n            }\n          }\n        }\n      }\n    }\n  }\n\n  out += \"# ------------------------------------------------------------------------------------\\n\";\n}\n\nvoid FileInspector::QdbHelper::Store(const FileInspectorStats& stats)\n{\n  mQHashStats.hmset({\n    SCAN_STATS_KEY, Marshal(stats.ScanStats),\n    FAULTY_FILES_KEY, Marshal(stats.FaultyFiles),\n    ACCESS_TIME_FILES_KEY, Marshal(stats.AccessTimeFiles),\n    ACCESS_TIME_VOLUME_KEY, Marshal(stats.AccessTimeVolume),\n    BIRTH_TIME_FILES_KEY, Marshal(stats.BirthTimeFiles),\n    BIRTH_TIME_VOLUME_KEY, Marshal(stats.BirthTimeVolume),\n    BIRTH_VS_ACCESS_TIME_FILES_KEY, Marshal(stats.BirthVsAccessTimeFiles),\n    BIRTH_VS_ACCESS_TIME_VOLUME_KEY, Marshal(stats.BirthVsAccessTimeVolume),\n    USER_COSTS_KEY, Marshal(stats.UserCosts),\n    GROUP_COSTS_KEY, Marshal(stats.GroupCosts),\n    TOTAL_COSTS_KEY, Marshal(stats.TotalCosts),\n    USER_BYTES_KEY, Marshal(stats.UserBytes),\n    GROUP_BYTES_KEY, Marshal(stats.GroupBytes),\n    TOTAL_BYTES_KEY, Marshal(stats.TotalBytes),\n    NUM_FAULTY_FILES_KEY, Marshal(stats.NumFaultyFiles),\n    TIME_SCAN_KEY, Marshal(stats.TimeScan),\n    HARDLINK_COUNT_KEY, Marshal(stats.HardlinkCount),\n    HARDLINK_VOLUME_KEY, Marshal(stats.HardlinkVolume),\n    SYMLINK_COUNT_KEY, Marshal(stats.SymlinkCount)\n  });\n}\n\nvoid FileInspector::QdbHelper::Load(FileInspectorStats& stats)\n{\n  std::vector<std::string> members = mQHashStats.hgetall();\n\n  try {\n    for (int i = 0; i < members.size() - 1; i += 2) {\n      std::string key = members[i];\n      std::string value = members[i + 1];\n\n      if (key == SCAN_STATS_KEY) {\n        Unmarshal(value, stats.ScanStats);\n      } else if (key == FAULTY_FILES_KEY) {\n        Unmarshal(value, stats.FaultyFiles);\n      } else if (key == ACCESS_TIME_FILES_KEY) {\n        Unmarshal(value, stats.AccessTimeFiles);\n      } else if (key == ACCESS_TIME_VOLUME_KEY) {\n        Unmarshal(value, stats.AccessTimeVolume);\n      } else if (key == BIRTH_TIME_FILES_KEY) {\n        Unmarshal(value, stats.BirthTimeFiles);\n      } else if (key == BIRTH_TIME_VOLUME_KEY) {\n        Unmarshal(value, stats.BirthTimeVolume);\n      } else if (key == BIRTH_VS_ACCESS_TIME_FILES_KEY) {\n        Unmarshal(value, stats.BirthVsAccessTimeFiles);\n      } else if (key == BIRTH_VS_ACCESS_TIME_VOLUME_KEY) {\n        Unmarshal(value, stats.BirthVsAccessTimeVolume);\n      } else if (key == USER_COSTS_KEY) {\n        Unmarshal(value, stats.UserCosts);\n      } else if (key == GROUP_COSTS_KEY) {\n        Unmarshal(value, stats.GroupCosts);\n      } else if (key == TOTAL_COSTS_KEY) {\n        Unmarshal(value, stats.TotalCosts);\n      } else if (key == USER_BYTES_KEY) {\n        Unmarshal(value, stats.UserBytes);\n      } else if (key == GROUP_BYTES_KEY) {\n        Unmarshal(value, stats.GroupBytes);\n      } else if (key == TOTAL_BYTES_KEY) {\n        Unmarshal(value, stats.TotalBytes);\n      } else if (key == NUM_FAULTY_FILES_KEY) {\n        Unmarshal(value, stats.NumFaultyFiles);\n      } else if (key == TIME_SCAN_KEY) {\n        Unmarshal(value, stats.TimeScan);\n      } else if (key == HARDLINK_COUNT_KEY) {\n        Unmarshal(value, stats.HardlinkCount);\n      } else if (key == HARDLINK_VOLUME_KEY) {\n        Unmarshal(value, stats.HardlinkVolume);\n      } else if (key == SYMLINK_COUNT_KEY) {\n        Unmarshal(value, stats.SymlinkCount);\n      }\n    }\n  } catch (...) {\n    eos_static_warning(\"msg=\\\"error unmarshalling FileInspector stats from QDB\\\"\");\n    stats = FileInspectorStats(); // Reset stats on error\n  }\n}\n\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/inspector/FileInspector.hh",
    "content": "// ----------------------------------------------------------------------\n// File: FileInspector.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include \"mgm/Namespace.hh\"\n#include \"common/VirtualIdentity.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"mgm/inspector/FileInspectorStats.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"namespace/ns_quarkdb/qclient/include/qclient/QClient.hh\"\n#include \"namespace/ns_quarkdb/qclient/include/qclient/structures/QHash.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include <XrdOuc/XrdOucErrInfo.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <atomic>\n#include <memory>\n#include <mutex>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class tracking the sanity of created files\n//------------------------------------------------------------------------------\n\nclass FileInspector\n{\npublic:\n  struct Options {\n    bool enabled; //< Is FileInspector even enabled?\n    std::chrono::seconds\n    interval; //< Run FileInspector cleanup every this many seconds\n  };\n\n  enum LockFsView : bool {\n    Off = false,\n    On  = true\n  };\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param space_name corresponding space name\n  //! @param qdb_details for connecting to QuarkDB\n  //----------------------------------------------------------------------------\n  FileInspector(std::string_view space_name,\n                const eos::QdbContactDetails& qdb_details);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~FileInspector();\n\n  //----------------------------------------------------------------------------\n  // Perform a single inspector cycle, QDB namespace\n  //----------------------------------------------------------------------------\n  void performCycleQDB(ThreadAssistant& assistant) noexcept;\n\n  void Dump(std::string& out, std::string_view options,\n            const LockFsView lockfsview);\n\n  Options getOptions(const LockFsView lockfsview);\n\n  inline bool enabled()\n  {\n    return mEnabled.load();\n  }\n\n  inline bool disable()\n  {\n    bool expected = true;\n    return mEnabled.compare_exchange_strong(expected, false);\n  }\n\n  inline bool enable()\n  {\n    bool expected = false;\n    return mEnabled.compare_exchange_strong(expected, true);\n  }\n\n  const std::string currencies[6] = { \"EOS\", \"CHF\", \"EUR\", \"USD\", \"AUD\", \"YEN\" };\n\nprivate:\n  void backgroundThread(ThreadAssistant& assistant) noexcept;\n  void Process(std::shared_ptr<eos::IFileMD> fmd);\n\n  AssistedThread mThread; ///< thread id of the creation background tracker\n  std::atomic<bool> mEnabled;\n  XrdOucErrInfo mError;\n  eos::common::VirtualIdentity mVid;\n  std::unique_ptr<qclient::QClient> mQcl;\n\n  FileInspectorStats mCurrentStats;\n  FileInspectorStats mLastStats;\n\n  std::atomic<double> PriceTbPerYearDisk;\n  std::atomic<double> PriceTbPerYearTape;\n\n  std::string currency;\n\n  std::atomic<double> scanned_percent;\n  std::atomic<uint64_t> nfiles;\n  std::atomic<uint64_t> ndirs;\n\n  std::mutex mutexScanStats;\n  std::string mSpaceName; ///< Corresponding space name\n\n  //! Maximum number of classifications of faulty files to record\n  static constexpr uint64_t maxfaulty = 1'000'000;\n\n  struct QdbHelper {\n    QdbHelper(const eos::QdbContactDetails& qdb_details)\n    {\n      mQcl = std::make_unique<qclient::QClient>(qdb_details.members,\n             qdb_details.constructOptions());\n      mQHashStats = qclient::QHash(*mQcl, kFileInspectorStatsKey);\n    }\n\n    void Store(const FileInspectorStats& stats);\n\n    void Load(FileInspectorStats& stats);\n\n    void Clear()\n    {\n      mQcl->del(kFileInspectorStatsKey);\n    }\n\n    bool HasStats()\n    {\n      return mQcl->exists(kFileInspectorStatsKey);\n    }\n\n  private:\n    std::unique_ptr<qclient::QClient> mQcl; ///< Internal QClient object\n    qclient::QHash mQHashStats;\n\n    const std::string kFileInspectorStatsKey = \"eos-file-inspector-stats\";\n  };\n\n  QdbHelper mQdbHelper; ///< QuarkDB helper object\n};\n\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/inspector/FileInspectorStats.cc",
    "content": "// ----------------------------------------------------------------------\n// File: FileInspectorData.cc\n// Author: Gianmaria Del Monte - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/inspector/FileInspectorStats.hh\"\n#include \"namespace/ns_quarkdb/qclient/include/qclient/QClient.hh\"\n#include \"common/json/Json.hh\"\n#include <sstream>\n\nEOSMGMNAMESPACE_BEGIN\n\ntemplate <typename T, size_t N>\nvoid clone(T(&dst)[N], const T(&src)[N])\n{\n  static_assert(std::is_copy_assignable_v<T>,\n                \"Type must be copy-assignable\");\n\n  for (size_t i = 0; i < N; i++) {\n    dst[i] = src[i];\n  }\n}\n\nFileInspectorStats& FileInspectorStats::operator=(const FileInspectorStats&\n    other)\n{\n  if (this == &other) {\n    return *this;\n  }\n\n  ScanStats = other.ScanStats;\n  FaultyFiles = other.FaultyFiles;\n  AccessTimeFiles = other.AccessTimeFiles;\n  AccessTimeVolume = other.AccessTimeVolume;\n  BirthTimeFiles = other.BirthTimeFiles;\n  BirthTimeVolume = other.BirthTimeVolume;\n  BirthVsAccessTimeFiles = other.BirthVsAccessTimeFiles;\n  BirthVsAccessTimeVolume = other.BirthVsAccessTimeVolume;\n  SizeBinsFiles = other.SizeBinsFiles;\n  SizeBinsVolume = other.SizeBinsVolume;\n  BirthVsSizeFiles = other.BirthVsSizeFiles;\n  BirthVsSizeVolume = other.BirthVsSizeVolume;\n  clone(UserCosts, other.UserCosts);\n  clone(TotalCosts, other.TotalCosts);\n  clone(GroupCosts, other.GroupCosts);\n  clone(UserBytes, other.UserBytes);\n  clone(TotalBytes, other.TotalBytes);\n  clone(UserBytes, other.UserBytes);\n  NumFaultyFiles = other.NumFaultyFiles;\n  TotalFileCount = other.TotalFileCount;\n  TotalLogicalBytes = other.TotalLogicalBytes;\n  TimeScan = other.TimeScan;\n  HardlinkCount = other.HardlinkCount;\n  HardlinkVolume = other.HardlinkVolume;\n  SymlinkCount = other.SymlinkCount;\n  return *this;\n}\n\ntemplate <typename T, size_t N>\nvoid move(T(&dst)[N], T(&src)[N]) noexcept\n{\n  static_assert(std::is_move_assignable_v<T>,\n                \"Type must be move-assignable\");\n\n  for (size_t i = 0; i < N; i++) {\n    dst[i] = std::move(src[i]);\n    src[i] = T{}; // initialize with zero value\n  }\n}\n\n\nFileInspectorStats& FileInspectorStats::operator=(FileInspectorStats&& other)\nnoexcept\n{\n  if (this == &other) {\n    return *this;\n  }\n\n  ScanStats = std::move(other.ScanStats);\n  FaultyFiles = std::move(other.FaultyFiles);\n  AccessTimeFiles = std::move(other.AccessTimeFiles);\n  AccessTimeVolume = std::move(other.AccessTimeVolume);\n  BirthTimeFiles = std::move(other.BirthTimeFiles);\n  BirthTimeVolume = std::move(other.BirthTimeVolume);\n  BirthVsAccessTimeFiles = std::move(other.BirthVsAccessTimeFiles);\n  BirthVsAccessTimeVolume = std::move(other.BirthVsAccessTimeVolume);\n  SizeBinsFiles = std::move(other.SizeBinsFiles);\n  SizeBinsVolume = std::move(other.SizeBinsVolume);\n  BirthVsSizeFiles = std::move(other.BirthVsSizeFiles);\n  BirthVsSizeVolume = std::move(other.BirthVsSizeVolume);\n  move(UserCosts, other.UserCosts);\n  move(TotalCosts, other.TotalCosts);\n  move(GroupCosts, other.GroupCosts);\n  move(UserBytes, other.UserBytes);\n  move(TotalBytes, other.TotalBytes);\n  move(UserBytes, other.UserBytes);\n  NumFaultyFiles = other.NumFaultyFiles;\n  TotalFileCount = other.TotalFileCount;\n  TotalLogicalBytes = other.TotalLogicalBytes;\n  TimeScan = other.TimeScan;\n  HardlinkCount = other.HardlinkCount;\n  HardlinkVolume = other.HardlinkVolume;\n  SymlinkCount = other.SymlinkCount;\n  return *this;\n}\n\nEOSMGMNAMESPACE_END"
  },
  {
    "path": "mgm/inspector/FileInspectorStats.hh",
    "content": "// ----------------------------------------------------------------------\n// File: FileInspectorData.hh\n// Author: Gianmaria Del Monte - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include <cstdint>\n#include <string>\n#include <map>\n#include <set>\n#include <atomic>\n#include \"mgm/Namespace.hh\"\n#include <qclient/QClient.hh>\n\nEOSMGMNAMESPACE_BEGIN\n\n\n#define SCAN_STATS_KEY \"scan-stats\"\n#define FAULTY_FILES_KEY \"faulty-files\"\n#define ACCESS_TIME_FILES_KEY \"access-time-files\"\n#define ACCESS_TIME_VOLUME_KEY \"access-time-volume\"\n#define BIRTH_TIME_FILES_KEY \"birth-time-files\"\n#define BIRTH_TIME_VOLUME_KEY \"birth-time-volume\"\n#define BIRTH_VS_ACCESS_TIME_FILES_KEY \"birth-vs-access-time-files\"\n#define BIRTH_VS_ACCESS_TIME_VOLUME_KEY \"birth-vs-access-volume-files\"\n#define SIZE_DISTRIBUTION_FILES_KEY \"size-distribution-files\"\n#define SIZE_DISTRIBUTION_VOLUME_KEY \"size-distribution-volume\"\n#define SIZE_VS_BIRTH_FILES_KEY \"size-vs-birth-files\"\n#define SIZE_VS_BIRTH_VOLUME_KEY \"size-vs-birth-volume\"\n#define USER_COSTS_KEY \"user-costs\"\n#define GROUP_COSTS_KEY \"group-costs\"\n#define TOTAL_COSTS_KEY \"total-costs\"\n#define USER_BYTES_KEY \"user-bytes\"\n#define GROUP_BYTES_KEY \"group-bytes\"\n#define TOTAL_BYTES_KEY \"total-bytes\"\n#define NUM_FAULTY_FILES_KEY \"num-faulty-files\"\n#define TIME_SCAN_KEY \"time-scan\"\n#define HARDLINK_COUNT_KEY \"hardlink-count\"\n#define HARDLINK_VOLUME_KEY \"hardlink-volume\"\n#define SYMLINK_COUNT_KEY \"symlink-count\"\n\n//------------------------------------------------------------------------------\n//! Class holding the stats information\n//------------------------------------------------------------------------------\nstruct FileInspectorStats {\n\n  FileInspectorStats() : TimeScan(0) {}\n\n  ~FileInspectorStats() {};\n\n  FileInspectorStats(const FileInspectorStats& other)\n  {\n    *this = other;\n  }\n\n  FileInspectorStats& operator=(const FileInspectorStats& other);\n\n  FileInspectorStats(FileInspectorStats&& other) noexcept\n  {\n    *this = std::move(other);\n  }\n\n  FileInspectorStats& operator=(FileInspectorStats&& other) noexcept;\n\n  // Counters for the last and current scan by layout id\n  std::map<uint64_t, std::map<std::string, uint64_t>> ScanStats;\n  //! Map from types of failures to pairs of fid and layoutid\n  std::map<std::string, std::map<uint64_t, uint64_t>> FaultyFiles;\n  //! Access Time Bins\n  std::map<time_t, uint64_t> AccessTimeFiles;\n  std::map<time_t, uint64_t> AccessTimeVolume;\n\n  //! Birth Time Bins\n  std::map<time_t, uint64_t> BirthTimeFiles;\n  std::map<time_t, uint64_t> BirthTimeVolume;\n\n  //! BirthVsAccess Time Bins\n  std::map<time_t, std::map<time_t, uint64_t>> BirthVsAccessTimeFiles;\n  std::map<time_t, std::map<time_t, uint64_t>> BirthVsAccessTimeVolume;\n\n  //! File size distribution (bins expressed as upper-bound in bytes; 0 => >= last bin)\n  std::map<uint64_t, uint64_t> SizeBinsFiles;\n  std::map<uint64_t, uint64_t> SizeBinsVolume;\n\n  //! File size vs birth time (outer key: birth time bin in seconds since now; inner key: size bin as above)\n  std::map<time_t, std::map<uint64_t, uint64_t>> BirthVsSizeFiles;\n  std::map<time_t, std::map<uint64_t, uint64_t>> BirthVsSizeVolume;\n\n  //! User Cost Bins\n  std::map<uid_t, uint64_t> UserCosts[2];\n\n  //! Group Cost Bins\n  std::map<gid_t, uint64_t> GroupCosts[2];\n\n  double TotalCosts[2] = {0};\n\n  //! User Bytes Bins\n  std::map<uid_t, uint64_t> UserBytes[2];\n\n  //! Group Bytes Bins\n  std::map<gid_t, uint64_t> GroupBytes[2];\n\n  double TotalBytes[2] = {0};\n\n  //! Running count of number of time files have been classed faulty\n  uint64_t NumFaultyFiles = 0;\n\n  //! Totals for convenience\n  uint64_t TotalFileCount = 0;\n  uint64_t TotalLogicalBytes = 0;\n\n  //! Link statistics\n  uint64_t HardlinkCount = 0;\n  uint64_t HardlinkVolume = 0;\n  uint64_t SymlinkCount = 0;\n\n  time_t TimeScan;\n};\n\n\nEOSMGMNAMESPACE_END"
  },
  {
    "path": "mgm/iostat/Iostat.cc",
    "content": "//------------------------------------------------------------------------------\n// File: Iostat.cc\n// Authors: Andreas-Joachim Peters - CERN\n//          Elvin Alin Sindrilaru  - CERN\n//          Jaroslav Guenther      - CERN\n//\n// Implementation follows presentation from EOS Workshop in 2022\n// https://indico.cern.ch/event/1103358/contributions/4758312/attachments/2402845/4109660/EOS_IO_stat_monitoring.pdf\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/table_formatter/TableFormatterBase.hh\"\n#include \"common/Report.hh\"\n#include \"common/Path.hh\"\n#include \"common/JeMallocHandler.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Timing.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringUtils.hh\"\n#include \"mgm/iostat/Iostat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/imaster/IMaster.hh\"\n#include \"mq/MessagingRealm.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n#include \"namespace/ns_quarkdb/qclient/include/qclient/ResponseParsing.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"mq/QdbListener.hh\"\n#include <XrdNet/XrdNetUtils.hh>\n#include <XrdNet/XrdNetAddr.hh>\n#include <curl/curl.h>\n#include <zstd.h>\n#include <fcntl.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <errno.h>\n#include <string.h>\n\nEOSMGMNAMESPACE_BEGIN\n\nconst char* Iostat::gIostatCollect = \"iostat::collect\";\nconst char* Iostat::gIostatReportSave = \"iostat::report\";\nconst char* Iostat::gIostatReportNamespace = \"iostat::reportnamespace\";\nconst char* Iostat::gIostatPopularity = \"iostat::popularity\";\nconst char* Iostat::gIostatUdpTargetList = \"iostat::udptargets\";\nFILE* Iostat::gOpenReportFD = 0;\nPeriod LAST_DAY = Period::DAY;\nPeriod LAST_HOUR = Period::HOUR;\nPeriod LAST_5MIN = Period::FIVEMIN;\nPeriod LAST_1MIN = Period::ONEMIN;\nPercentComplete P90 = PercentComplete::p90;\nPercentComplete P95 = PercentComplete::p95;\nPercentComplete P99 = PercentComplete::p99;\nPercentComplete ALL = PercentComplete::p100;\n\n\n//------------------------------------------------------------------------------\n// IostatPeriods implementation\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Adds transfer data on a 24h timeline of [mDataBuffer]\n// Provides:\n// - [mLongestTransferTime] in last 24h\n// - populates [mIntegralBuffer] and every 5 min extracts the\n//   time of transfer for > 90/95/100 percent of data\n// - [mDataBuffer] circular buffer for all transfers in the last 24h\n//------------------------------------------------------------------------------\nvoid\nIostatPeriods::Add(unsigned long long val, time_t start, time_t stop,\n                   time_t now)\n{\n  mTotal += val;\n  double value = (double)val;\n\n  // Window start/end times are \"|\", bin start [ and end ] times\n  // period window = |-----------[--]----------|\n  if (stop > now) {\n    eos_static_err(\"%s\", \"msg=\\\"failed report digest, transfer \"\n                   \"stop time in the future\\\"\");\n    return;\n  }\n\n  time_t t_window_start = now - (sBins * sBinWidth);\n\n  if (stop <= t_window_start) {\n    eos_static_warning(\"%s\", \"msg=\\\"failed report digest, transfer stopped \"\n                       \"outside of collection time window\\\"\");\n    return;\n  }\n\n  time_t tdiff = stop - start + 1;\n\n  if (tdiff < 1) {\n    eos_static_err(\"%s\", \"msg=\\\"transfer start time after stop time\\\"\");\n    return;\n  }\n\n  StampBufferZero(now);\n  mTfCount += 1;\n\n  if (stop > mLastAddTime) {\n    mLastAddTime = stop;\n  }\n\n  if (mLongestTransferTime < (unsigned int)tdiff) {\n    mLongestTransferTime = tdiff;\n  }\n\n  time_t trep = now - stop;\n\n  if (mLongestReportTime < (unsigned int)(trep)) {\n    mLongestReportTime = trep;\n  }\n\n  // cutting off data out of time window\n  if (start < t_window_start) {\n    // re-calculate data portion to save into our time window\n    value = ((stop - t_window_start) * value) / tdiff;\n    start = t_window_start;\n    tdiff = stop - start;\n  }\n\n  // Number of bins the measurement hits\n  size_t mbins = tdiff / sBinWidth;\n  double val_per_bin = value / mbins;\n  int index_start = (start / sBinWidth) % sBins;\n\n  for (size_t ibin = 0; ibin < mbins; ++ibin) {\n    int bin_index = (index_start + ibin) % sBins;\n    // Code block to be added and tested in case sBinWidth !=1\n    // double ival = val_per_bin;\n    // if (ibin == 0 and mbins > 1):\n    //   time_t t_start_bin_duration = (sBins * sBinWidth) - (start - t_window_start) - sBinWidth * (mbins - 1)\n    //   ival = (t_start_bin_duration * value) / tdiff;\n    // if (ibin == mbins - 1 and mbins > 1):\n    //   time_t t_stop_bin_duration = (sBins * sBinWidth) - (now - stop) - sBinWidth * (mbins - 1)\n    //   ival = (t_stop_bin_duration * value) / tdiff;\n    // mDataBuffer[bin_index] += ival;\n    // mIntegralBuffer[ibin] += ival;\n    mDataBuffer[bin_index] += val_per_bin;\n    mIntegralBuffer[ibin] += val_per_bin;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Update Transfer Buffer to iterate over and calculate how long does it take\n// to transfer [mPercComplete] % of the data within sample rate of 5 min\n// [mLastTfSampleUpdateInterval]\n//------------------------------------------------------------------------------\nvoid\nIostatPeriods::UpdateTransferSampleInfo(time_t now)\n{\n  // Sum data of all transfers\n  double sumTx = 0.;\n\n  // Update rating (% of transfers)\n  for (size_t i = 0; i < sBins; ++i) {\n    if (mIntegralBuffer[i]) {\n      sumTx += mIntegralBuffer[i];\n    } else {\n      break;\n    }\n  }\n\n  // Reset counters for current sample\n  mTfCountInSample = 0ull;\n  mAvgTfSize = 0ull;\n\n  //std::cout << \"sumTx\" << sumTx << std::endl;\n  if (sumTx > 0) {\n    mTfCountInSample = mTfCount;\n    mAvgTfSize = std::ceil((double)sumTx / mTfCountInSample);\n    const double multiplier = std::pow(10.0, 6);\n\n    // integrate up to [mPercComplete] and record\n    // the time the transfers took in [mDurationToPercComplete]\n    for (size_t iperc = 0; iperc < std::size(mPercComplete); ++iperc) {\n      double sum_percent = 0;\n\n      for (size_t ibin = 0; ibin < sBins; ibin++) {\n        sum_percent += mIntegralBuffer[ibin] / sumTx;\n\n        if ((unsigned int)std::ceil(sum_percent * multiplier) >=\n            (unsigned int)std::ceil(mPercComplete[iperc] * multiplier)) {\n          mDurationToPercComplete[iperc] = ((ibin + 1) * sBinWidth);\n          break;\n        }\n      }\n    }\n  } else {\n    for (size_t iperc = 0; iperc < std::size(mPercComplete); ++iperc) {\n      mDurationToPercComplete[iperc] = 0;\n    }\n  }\n\n  mTfCount = 0;\n  mLongestTransferTimeInSample = mLongestTransferTime;\n  mLongestReportTimeInSample = mLongestReportTime;\n  mLongestTransferTime = 0;\n  mLongestReportTime = 0;\n  memset(mIntegralBuffer, 0, sizeof(mIntegralBuffer));\n  mLastTfMaxLenUpdateTime = now;\n}\n\n//------------------------------------------------------------------------------\n// Reset bin content of the buffer w.r.t. given timstamp\n//------------------------------------------------------------------------------\nvoid\nIostatPeriods::StampBufferZero(time_t& now)\n{\n  // Clean-up all bins which are older than sPeriod (24h)\n  // last_end_index is the index of the timestamp corresponding\n  // to the last transfer stop time recorded\n  if ((now - mLastTfMaxLenUpdateTime) > mLastTfSampleUpdateInterval) {\n    UpdateTransferSampleInfo(now);\n  }\n\n  time_t last_upd_time = std::max(mLastAddTime, mLastStampZeroTime);\n  int zero_bins = 0;\n\n  if (now - last_upd_time >= sPeriod) {\n    zero_bins = sBins;\n  } else {\n    if (last_upd_time < now) {\n      zero_bins = (now - last_upd_time) / sBinWidth;\n    } else {\n      if ((last_upd_time == mLastStampZeroTime) &&\n          (last_upd_time != mLastAddTime)) {\n        zero_bins = 1;\n      }\n    }\n  }\n\n  int start_index = (last_upd_time / sBinWidth) % sBins;\n\n  if (last_upd_time != now) {\n    start_index = (start_index + 1) % sBins;\n  }\n\n  for (int i = 0; i < zero_bins; ++i) {\n    int index = (start_index + i) % sBins;\n    mDataBuffer[index] = 0.;\n  }\n\n  mLastStampZeroTime = now;\n}\n\n//------------------------------------------------------------------------------\n// Getting the timestamp of the last time the transfer sample was taken\n//------------------------------------------------------------------------------\nstd::string IostatPeriods::GetLastSampleUpdateTimestamp(bool date_format) const\n{\n  std::string ts;\n\n  if (date_format) {\n    ts = common::Timing::ltime(mLastTfMaxLenUpdateTime);\n  } else {\n    ts = std::to_string(mLastTfMaxLenUpdateTime);\n  }\n\n  eos::common::trim(ts);\n  return ts;\n}\n\n\n//------------------------------------------------------------------------------\n// Get the sum of values for the given buffer period\n//------------------------------------------------------------------------------\nunsigned long long\nIostatPeriods::GetDataInPeriod(size_t period, unsigned long long time_offset,\n                               time_t now) const\n{\n  double sum = 0.;\n\n  if (time_offset > sPeriod) {\n    time_offset = sPeriod;\n  }\n\n  if (time_offset + period > sPeriod) {\n    period = sPeriod - time_offset;\n  }\n\n  size_t start_index = ((now - time_offset - period) / sBinWidth) % sBins;\n  size_t stop_index = ((now - time_offset) / sBinWidth) % sBins;\n  int range = stop_index - start_index ;\n\n  if (period >= sPeriod) {\n    range = sBins;\n    start_index = 0;\n  }\n\n  if (range < 0) {\n    range = sBins + range;\n  }\n\n  for (int pidx_cnt = 0; pidx_cnt < range; ++pidx_cnt) {\n    int idx = (start_index + pidx_cnt) % sBins;\n    sum += mDataBuffer[idx];\n  }\n\n  return std::ceil(sum);\n}\n\n//------------------------------------------------------------------------------\n// Iostat implementation\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Iostat constructor\n//------------------------------------------------------------------------------\nIostat::Iostat():\n  mDoneInit(false), mFlusher(nullptr), mLegacyMode(false), mRunning(false),\n  mQcl(nullptr), mReportSave(true), mReportNamespace(false),\n  mReportPopularity(true), mHashKeyBase(\"\")\n{\n  for (size_t i = 0; i < IOSTAT_POPULARITY_HISTORY_DAYS; i++) {\n    IostatPopularity[i].set_deleted_key(\"\");\n    IostatPopularity[i].resize(100000);\n  }\n\n  mLastPopularityBin = 9999999;\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nIostat::~Iostat()\n{\n  (void) StopCollection();\n  mCirculateThread.join();\n}\n\n//------------------------------------------------------------------------------\n// Get hash key under which info is stored in QDB. This also included the\n// current year and it's cached for ~5 minutes.\n//------------------------------------------------------------------------------\nstd::string\nIostat::GetHashKey() const\n{\n  using namespace std::chrono;\n  static std::string key;\n  static seconds cache_interval {300};\n  static auto ts = steady_clock::now();\n\n  if (key.empty() ||\n      (duration_cast<seconds>(steady_clock::now() - ts) > cache_interval)) {\n    key = mHashKeyBase + eos::common::Timing::GetCurrentYear();\n    ts = steady_clock::now();\n  }\n\n  return key;\n}\n\n//------------------------------------------------------------------------------\n// Perform object initialization\n//------------------------------------------------------------------------------\nbool\nIostat::Init(const std::string& instance_name, int port,\n             const std::string& legacy_file)\n{\n  mHashKeyBase = SSTR(\"eos-iostat:\" << instance_name << \":\");\n  mFlusherPath = SSTR(gOFS->mQClientDir << instance_name << \":\" << port\n                      << \"_iostat\");\n\n  if (gOFS) {\n    // QDB namespace, initialize qclient\n    if (!gOFS->namespaceGroup->isInMemory()) {\n      const eos::QdbContactDetails& qdb_details = gOFS->mQdbContactDetails;\n      mQcl.reset(new qclient::QClient(qdb_details.members,\n                                      qdb_details.constructOptions()));\n\n      if (!OneOffQdbMigration(legacy_file)) {\n        eos_static_err(\"%s\", \"msg=\\\"failed while attempting migration to QDB\\\"\");\n        return false;\n      }\n\n      if (!LoadFromQdb()) {\n        eos_static_err(\"%s\", \"msg=\\\"LoadFromQdb failed\\\"\");\n        return false;\n      }\n\n      if (mFlusher == nullptr) {\n        mFlusher.reset(new eos::MetadataFlusher(mFlusherPath,\n                                                gOFS->mQdbContactDetails));\n      }\n    } else {\n      // In-memory namespace forces the stats to be saved in the file\n      mLegacyMode = true;\n      mLegacyFilePath = legacy_file;\n\n      if (!LegacyRestoreFromFile()) {\n        eos_static_err(\"msg=\\\"failed to restore info from file\\\" path=%s\",\n                       mLegacyFilePath.c_str());\n        return false;\n      }\n    }\n  }\n\n  mCirculateThread.reset(&Iostat::Circulate, this);\n  mDoneInit = true;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// One off migration from file based to QDB of IoStat information\n//------------------------------------------------------------------------------\nbool\nIostat::OneOffQdbMigration(const std::string& legacy_file)\n{\n  struct stat info;\n\n  if (stat(legacy_file.c_str(), &info)) {\n    // File does not exist, migration was probably already done\n    return true;\n  }\n\n  FILE* fin = fopen(legacy_file.c_str(), \"r\");\n\n  if (!fin) {\n    eos_static_err(\"msg=\\\"failed to open iostat file\\\" path=\\\"%s\\\"\",\n                   legacy_file.c_str());\n    return false;\n  }\n\n  int item = 0;\n  char line[16384];\n  std::string tag;\n  std::list<std::string> entries;\n\n  while ((item = fscanf(fin, \"%16383s\\n\", line)) == 1) {\n    XrdOucEnv env(line);\n\n    if (env.Get(\"tag\") && env.Get(\"uid\") && env.Get(\"val\")) {\n      entries.push_back(EncodeKey(USER_ID_TYPE, env.Get(\"uid\"), env.Get(\"tag\")));\n      entries.push_back(env.Get(\"val\"));\n    }\n\n    if (env.Get(\"tag\") && env.Get(\"gid\") && env.Get(\"val\")) {\n      entries.push_back(EncodeKey(GROUP_ID_TYPE, env.Get(\"gid\"), env.Get(\"tag\")));\n      entries.push_back(env.Get(\"val\"));\n    }\n  }\n\n  fclose(fin);\n  // Push all the collected info to QDB\n  qclient::QHash qhash(*mQcl, GetHashKey());\n\n  try {\n    bool done = qhash.hmset(entries);\n\n    if (!done) {\n      eos_static_err(\"%s\", \"msg=\\\"failed while inserting entries in QDB\\\"\");\n      return false;\n    }\n  } catch (const std::exception& e) {\n    eos_static_err(\"msg=\\\"got exception while inserting entrines in QDB\\\" \"\n                   \"emsg=\\\"%s\\\"\", e.what());\n    return false;\n  }\n\n  // Save file based iostat as a backup\n  const std::string bkp_path = legacy_file + \".bkp\";\n\n  if (rename(legacy_file.c_str(), bkp_path.c_str())) {\n    eos_static_err(\"msg=\\\"failed file rename\\\" old_path=\\\"%s\\\" new_path=\\\"%s\\\"\",\n                   legacy_file.c_str(), bkp_path.c_str());\n    return false;\n  }\n\n  eos_static_info(\"msg=\\\"saved iostat backup successfully\\\" old_path=\\\"%s\\\" \"\n                  \" new_path=\\\"%s\\\"\", legacy_file.c_str(), bkp_path.c_str());\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Apply instance level configuration concerning IoStats\n//------------------------------------------------------------------------------\nvoid\nIostat::ApplyConfig(FsView* fsview)\n{\n  std::string iocollect = fsview->GetGlobalConfig(gIostatCollect);\n\n  if ((iocollect == \"true\") || (iocollect.empty())) {\n    StartCollection(); // enable by default\n  }\n\n  std::string iopopularity = fsview->GetGlobalConfig(gIostatPopularity);\n  mReportPopularity = (iopopularity == \"true\") || (iopopularity.empty());\n  mReportSave = fsview->GetBoolGlobalConfig(gIostatReportSave);\n  mReportNamespace = fsview->GetBoolGlobalConfig(gIostatReportNamespace);\n  std::string udplist = fsview->GetGlobalConfig(gIostatUdpTargetList);\n  std::string delimiter = \"|\";\n  std::vector<std::string> hostlist;\n  eos::common::StringConversion::Tokenize(udplist, hostlist, delimiter);\n  std::unique_lock<std::mutex> scope_lock(mBcastMutex);\n  mUdpPopularityTarget.clear();\n\n  for (size_t i = 0; i < hostlist.size(); ++i) {\n    AddUdpTarget(hostlist[i], false);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Store IoStat config in the instance level configuration\n//------------------------------------------------------------------------------\nbool\nIostat::StoreIostatConfig(FsView* fsview) const\n{\n  bool ok = true;\n\n  if ((gOFS && gOFS->mMaster->IsMaster()) || !gOFS) {\n    ok = fsview->SetGlobalConfig(gIostatPopularity, mReportPopularity) &\n         fsview->SetGlobalConfig(gIostatReportSave, mReportSave) &\n         fsview->SetGlobalConfig(gIostatReportNamespace, mReportNamespace) &\n         fsview->SetGlobalConfig(gIostatCollect, mRunning);\n    std::string udp_popularity_targets = EncodeUdpPopularityTargets();\n\n    if (!udp_popularity_targets.empty()) {\n      ok &= fsview->SetGlobalConfig(gIostatUdpTargetList, udp_popularity_targets);\n    }\n  }\n\n  return ok;\n}\n\n//------------------------------------------------------------------------------\n// Start collection thread\n//------------------------------------------------------------------------------\nbool\nIostat::StartCollection()\n{\n  std::unique_lock<std::mutex> scope_lock(mThreadSyncMutex);\n\n  if (!mRunning) {\n    mRunning = true;\n    mReceivingThread.reset(&Iostat::Receive, this);\n    StoreIostatConfig(&FsView::gFsView);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Stop collection thread\n//------------------------------------------------------------------------------\nbool\nIostat::StopCollection()\n{\n  std::unique_lock<std::mutex> scope_lock(mThreadSyncMutex);\n\n  if (mRunning) {\n    mReceivingThread.join();\n    mRunning = false;\n    StoreIostatConfig(&FsView::gFsView);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Start popularity thread\n//------------------------------------------------------------------------------\nbool\nIostat::StartPopularity()\n{\n  std::unique_lock<std::mutex> scope_lock(mThreadSyncMutex);\n\n  if (!mReportPopularity) {\n    mReportPopularity = true;\n    StoreIostatConfig(&FsView::gFsView);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Start popularity thread\n//------------------------------------------------------------------------------\nbool\nIostat::StopPopularity()\n{\n  std::unique_lock<std::mutex> scope_lock(mThreadSyncMutex);\n\n  if (mReportPopularity) {\n    mReportPopularity = false;\n    StoreIostatConfig(&FsView::gFsView);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Start daily report save thread\n//------------------------------------------------------------------------------\nbool\nIostat::StartReport()\n{\n  std::unique_lock<std::mutex> scope_lock(mThreadSyncMutex);\n\n  if (!mReportSave) {\n    mReportSave = true;\n    StoreIostatConfig(&FsView::gFsView);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Stop daily report save thread\n//------------------------------------------------------------------------------\nbool\nIostat::StopReport()\n{\n  std::unique_lock<std::mutex> scope_lock(mThreadSyncMutex);\n\n  if (mReportSave) {\n    mReportSave = false;\n    StoreIostatConfig(&FsView::gFsView);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Start namespace report thread\n//------------------------------------------------------------------------------\nbool\nIostat::StartReportNamespace()\n{\n  std::unique_lock<std::mutex> scope_lock(mThreadSyncMutex);\n\n  if (!mReportNamespace) {\n    mReportNamespace = true;\n    StoreIostatConfig(&FsView::gFsView);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Stop namespace report thread\n//------------------------------------------------------------------------------\nbool\nIostat::StopReportNamespace()\n{\n  std::unique_lock<std::mutex> scope_lock(mThreadSyncMutex);\n\n  if (mReportNamespace) {\n    mReportNamespace = false;\n    StoreIostatConfig(&FsView::gFsView);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Record measurement to the various periods it overlaps with\n//------------------------------------------------------------------------------\nvoid\nIostat::Add(const std::string& tag, uid_t uid, gid_t gid,\n            unsigned long long val,\n            time_t start, time_t stop, time_t now)\n{\n  // Flush to QDB if not in testing mode - this can be called without a lock\n  // as this is only called from the thread digesting the report messages one\n  // by one\n  if (gOFS && !mLegacyMode) {\n    AddToQdb(tag, uid, gid, val);\n  }\n\n  std::unique_lock<std::mutex> scope_lock(mDataMutex);\n  IostatTag[tag] += val;\n  IostatUid[tag][uid] += val;\n  IostatGid[tag][gid] += val;\n  IostatPeriodsUid[tag][uid].Add(val, start, stop, now);\n  IostatPeriodsGid[tag][gid].Add(val, start, stop, now);\n  IostatPeriodsTag[tag].Add(val, start, stop, now);\n}\n\n//------------------------------------------------------------------------------\n// Low level implementation for Add method also sending data to QDB\n//------------------------------------------------------------------------------\nvoid\nIostat::AddToQdb(const std::string& tag, uid_t uid, gid_t gid,\n                 unsigned long long val)\n{\n  if (mFlusher) {\n    CacheUpdate(EncodeKey(USER_ID_TYPE, std::to_string(uid), tag),\n                EncodeKey(GROUP_ID_TYPE, std::to_string(gid), tag), val);\n\n    if (ShouldFlushCache()) {\n      FlushCache();\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Save given update in the in-memory cache\n//------------------------------------------------------------------------------\nvoid\nIostat::CacheUpdate(const std::string& uid_key, const std::string& gid_key,\n                    unsigned long long val)\n{\n  mMapCacheUpdates[uid_key] += val;\n  mMapCacheUpdates[gid_key] += val;\n}\n\n//------------------------------------------------------------------------------\n// Check if the cache needs to be flushed\n//------------------------------------------------------------------------------\nbool\nIostat::ShouldFlushCache()\n{\n  using namespace std::chrono;\n  static auto timestamp = steady_clock::now();\n\n  if ((mMapCacheUpdates.size() >= mMapMaxSize) ||\n      (duration_cast<seconds>(steady_clock::now() - timestamp) > mCacheFlushDelay)) {\n    timestamp = steady_clock::now();\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Flush all cached entries to the QDB backed\n//------------------------------------------------------------------------------\nvoid\nIostat::FlushCache()\n{\n  using namespace std::chrono;\n  using eos::common::Timing;\n  static const hours timeout {1};\n  static auto timestamp = steady_clock::now();\n  static std::string hash_key = mHashKeyBase + Timing::GetCurrentYear();\n\n  // Check for change of hash key when a new year starts\n  if (duration_cast<minutes>(steady_clock::now() - timestamp) > timeout) {\n    timestamp = steady_clock::now();\n    std::string new_hash_key = mHashKeyBase + Timing::GetCurrentYear();\n\n    if (new_hash_key != hash_key) {\n      hash_key = new_hash_key;\n    }\n  }\n\n  static std::vector<std::string> request;\n  request.reserve(3 * mMapMaxSize + 1);\n  request.push_back(\"HINCRBYMULTI\");\n\n  for (const auto& elem : mMapCacheUpdates) {\n    const std::string svalue = std::to_string(elem.second);\n    request.push_back(hash_key);\n    request.push_back(elem.first);\n    request.push_back(svalue);\n  }\n\n  mMapCacheUpdates.clear();\n  mFlusher->exec(request);\n  request.clear();\n}\n\n//------------------------------------------------------------------------------\n// Get sum of measurements for the given tag (looping all uids per tag)\n//------------------------------------------------------------------------------\nunsigned long long\nIostat::GetTotalStatForTag(const char* tag) const\n{\n  unsigned long long val = 0ull;\n\n  if (!IostatTag.count(tag)) {\n    return val;\n  }\n\n  val = IostatTag.find(tag)->second;\n  return val;\n}\n\n//------------------------------------------------------------------------------\n// Get sum of measurements for the given tag an period (looping all uids per tag)\n//------------------------------------------------------------------------------\nunsigned long long\nIostat::GetPeriodStatForTag(const char* tag, size_t period, time_t secago) const\n{\n  auto it = IostatPeriodsTag.find(tag);\n\n  if (it == IostatPeriodsTag.end()) {\n    return 0ull;\n  }\n\n  return it->second.GetDataInPeriod(period, secago, time(0ull));\n}\n\n//------------------------------------------------------------------------------\n// Method executed by the thread receiving reports\n//------------------------------------------------------------------------------\nvoid\nIostat::Receive(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"IoStatReceiver\");\n  eos_static_info(\"%s\", \"msg=\\\"starting iostat receive thread\\\"\");\n\n  if (gOFS == nullptr) {\n    return;\n  }\n\n  while (!mDoneInit) {\n    assistant.wait_for(std::chrono::seconds(5));\n\n    if (assistant.terminationRequested()) {\n      break;\n    }\n  }\n\n  const std::string qdb_channel = \"/eos/*/report\";\n  mq::QdbListener listener(gOFS->mQdbContactDetails, qdb_channel);\n\n  while (!assistant.terminationRequested()) {\n    std::string newmessage;\n\n    while (listener.fetch(newmessage, &assistant)) {\n      if (assistant.terminationRequested()) {\n        break;\n      }\n\n      XrdOucString body = newmessage.c_str();\n\n      while (body.replace(\"&&\", \"&\")) {\n      }\n\n      XrdOucEnv ioreport(body.c_str());\n      time_t now = time(0);\n      std::unique_ptr<eos::common::Report> report(new eos::common::Report(ioreport));\n      Add(\"bytes_read\", report->uid, report->gid, report->rb, report->ots,\n          report->cts, now);\n      Add(\"bytes_read\", report->uid, report->gid, report->rvb_sum, report->ots,\n          report->cts, now);\n      Add(\"bytes_written\", report->uid, report->gid, report->wb, report->ots,\n          report->cts, now);\n      Add(\"read_calls\", report->uid, report->gid, report->nrc, report->ots,\n          report->cts, now);\n      Add(\"readv_calls\", report->uid, report->gid, report->rv_op, report->ots,\n          report->cts, now);\n      Add(\"write_calls\", report->uid, report->gid, report->nwc, report->ots,\n          report->cts, now);\n      Add(\"fwd_seeks\", report->uid, report->gid, report->nfwds, report->ots,\n          report->cts, now);\n      Add(\"bwd_seeks\", report->uid, report->gid, report->nbwds, report->ots,\n          report->cts, now);\n      Add(\"xl_fwd_seeks\", report->uid, report->gid, report->nxlfwds, report->ots,\n          report->cts, now);\n      Add(\"xl_bwd_seeks\", report->uid, report->gid, report->nxlbwds, report->ots,\n          report->cts, now);\n      Add(\"bytes_fwd_seek\", report->uid, report->gid, report->sfwdb, report->ots,\n          report->cts, now);\n      Add(\"bytes_bwd_wseek\", report->uid, report->gid, report->sbwdb, report->ots,\n          report->cts, now);\n      Add(\"bytes_xl_fwd_seek\", report->uid, report->gid, report->sxlfwdb, report->ots,\n          report->cts, now);\n      Add(\"bytes_xl_bwd_wseek\", report->uid, report->gid, report->sxlbwdb,\n          report->ots, report->cts, now);\n      Add(\"disk_time_read\", report->uid, report->gid, report->rt,\n          report->ots, report->cts, now);\n      Add(\"disk_time_write\", report->uid, report->gid,\n          report->wt, report->ots, report->cts, now);\n\n      if (report->dsize) {\n        Add(\"bytes_deleted\", 0, 0, report->dsize, now - 30, now, now);\n        Add(\"files_deleted\", 0, 0, 1, now - 30, now, now);\n      }\n\n      // Do the UDP broadcasting\n      UdpBroadCast(report.get());\n\n      // Do the domain accounting\n      if (report->path.substr(0, 11) == \"/replicate:\") {\n        // check if this is a replication path\n        // push into the 'eos' domain\n        std::unique_lock<std::mutex> scope_lock(mDataMutex);\n\n        if (report->rb) {\n          IostatPeriodsDomainIOrb[\"eos\"].Add(report->rb, report->ots, report->cts, now);\n        }\n\n        if (report->wb) {\n          IostatPeriodsDomainIOwb[\"eos\"].Add(report->wb, report->ots, report->cts, now);\n        }\n      } else {\n        if (mReportPopularity) {\n          // do the popularity accounting here for everything which is not replication!\n          AddToPopularity(report->path, report->rb, report->ots, report->cts);\n        }\n\n        std::string sdomain = report->sec_domain;\n        {\n          std::unique_lock<std::mutex> scope_lock(mDataMutex);\n\n          if (report->rb) {\n            IostatPeriodsDomainIOrb[sdomain].Add(report->rb, report->ots, report->cts, now);\n          }\n\n          if (report->wb) {\n            IostatPeriodsDomainIOwb[sdomain].Add(report->wb, report->ots, report->cts, now);\n          }\n        }\n      }\n\n      // do the application accounting here\n      std::string apptag = \"other\";\n\n      if (report->sec_app.length()) {\n        apptag = report->sec_app;\n      }\n\n      // Push into app accounting\n      {\n        std::unique_lock<std::mutex> scope_lock(mDataMutex);\n\n        if (report->rb) {\n          IostatPeriodsAppIOrb[apptag].Add(report->rb, report->ots, report->cts, now);\n        }\n\n        if (report->wb) {\n          IostatPeriodsAppIOwb[apptag].Add(report->wb, report->ots, report->cts, now);\n        }\n      }\n\n      if (mReportSave && gOFS->mMaster->IsMaster()) {\n        WriteRecord(body.c_str());\n      }\n\n      if (mReportNamespace) {\n        // add the record into the report namespace file\n        char path[4096];\n        snprintf(path, sizeof(path) - 1, \"%s/%s\", gOFS->IoReportStorePath.c_str(),\n                 report->path.c_str());\n        eos::common::Path cPath(path);\n\n        if (cPath.MakeParentPath(S_IRWXU | S_IRGRP | S_IXGRP)) {\n          FILE* freport = fopen(path, \"a+\");\n\n          if (freport) {\n            fprintf(freport, \"%s\\n\", body.c_str());\n            fclose(freport);\n          }\n        }\n      }\n    }\n\n    assistant.wait_for(std::chrono::seconds(1));\n  }\n\n  eos_static_info(\"%s\", \"msg=\\\"stopping iostat receiver thread\\\"\");\n}\n\n\n//------------------------------------------------------------------------------\n// Write record to the stream - used by the MGM/FUSEX to push entries\n//------------------------------------------------------------------------------\nvoid\nIostat::WriteRecord(const std::string& record)\n{\n  static uint32_t sec_per_day = 24 * 3600;\n  static std::mutex s_mutex;\n  static std::string s_report_fn = \"\";\n  static time_t s_last_ts = 0ull;\n  // ZSTD compressed reports configuration (lazy-init)\n  static bool s_zstd_enabled = false;\n  static bool s_zstd_inited = false;\n  static unsigned s_zstd_rotation = 24 * 3600; // default one day\n  static int s_zstd_level = 1;\n  static int s_zstd_fd = -1;\n  static void* s_zstd_cctx = nullptr;\n  static time_t s_zstd_segstart = 0;\n  static std::string s_zstd_current_path;\n  auto ensure_dir = [](const std::string & path) -> bool {\n    eos::common::Path cPath(path.c_str());\n    return cPath.MakeParentPath(S_IRWXU | S_IRGRP | S_IXGRP);\n  };\n  auto truncate_to_interval = [](time_t t, unsigned interval) -> time_t {\n    if (!interval)\n    {\n      return t;\n    }\n\n    return t - (t % interval);\n  };\n  auto make_segment_path = [&](time_t t) -> std::string {\n    struct tm tmval;\n    localtime_r(&t, &tmval);\n    char dirbuf[4096];\n    // Directory: <IoReportStorePath>/YYYY/MM\n    snprintf(dirbuf, sizeof(dirbuf) - 1, \"%s/%04u/%02u\",\n             gOFS->IoReportStorePath.c_str(),\n             1900 + tmval.tm_year,\n             tmval.tm_mon + 1);\n    char filebuf[256];\n    // File: YYYYMMDD-HHMMSS.eosreport.zst\n    snprintf(filebuf, sizeof(filebuf) - 1, \"%04u%02u%02u-%02u%02u%02u.eosreport.zst\",\n             1900 + tmval.tm_year, tmval.tm_mon + 1, tmval.tm_mday,\n             tmval.tm_hour, tmval.tm_min, tmval.tm_sec);\n    std::string full = std::string(dirbuf) + \"/\" + filebuf;\n    return full;\n  };\n  auto close_zstd_locked = [&]() {\n    if (s_zstd_fd >= 0 && s_zstd_cctx) {\n      std::vector<char> outBuf(65536);\n      ZSTD_inBuffer in = { nullptr, 0, 0 };\n      size_t ret = 0;\n\n      do {\n        ZSTD_outBuffer out = { outBuf.data(), outBuf.size(), 0 };\n        ret = ZSTD_compressStream2(reinterpret_cast<ZSTD_CCtx*>(s_zstd_cctx),\n                                   &out, &in, ZSTD_e_end);\n\n        if (ZSTD_isError(ret)) {\n          eos_static_err(\"msg=\\\"zstd endStream error (reports)\\\" code=%s\",\n                         ZSTD_getErrorName(ret));\n          break;\n        }\n\n        if (out.pos) {\n          (void)::write(s_zstd_fd, outBuf.data(), out.pos);\n        }\n      } while (ret != 0);\n    }\n\n    if (s_zstd_fd >= 0) {\n      ::close(s_zstd_fd);\n      s_zstd_fd = -1;\n    }\n\n    if (s_zstd_cctx) {\n      ZSTD_freeCCtx(reinterpret_cast<ZSTD_CCtx*>(s_zstd_cctx));\n      s_zstd_cctx = nullptr;\n    }\n\n    s_zstd_current_path.clear();\n  };\n  auto open_zstd_locked = [&](time_t segstart) -> bool {\n    std::string path = make_segment_path(segstart);\n\n    if (!ensure_dir(path))\n    {\n      eos_static_err(\"msg=\\\"failed to create report parent path\\\" path=%s\",\n                     path.c_str());\n      return false;\n    }\n\n    int fd = ::open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0644);\n\n    if (fd < 0)\n    {\n      eos_static_err(\"msg=\\\"failed to open report file\\\" path=%s errno=%d err=\\\"%s\\\"\",\n                     path.c_str(), errno, strerror(errno));\n      return false;\n    }\n\n    void* cctx = ZSTD_createCCtx();\n\n    if (!cctx)\n    {\n      eos_static_err(\"%s\", \"msg=\\\"cannot create zstd context (reports)\\\"\");\n      ::close(fd);\n      return false;\n    }\n\n    if (ZSTD_isError(ZSTD_CCtx_setParameter(reinterpret_cast<ZSTD_CCtx*>(cctx),\n                                            ZSTD_c_compressionLevel, s_zstd_level)))\n    {\n      eos_static_warning(\"msg=\\\"failed to set zstd level (reports)\\\" level=%d\",\n                         s_zstd_level);\n    }\n\n    // Write frame header to avoid empty-file reader errors\n    {\n      std::vector<char> outBuf(16384);\n      ZSTD_inBuffer in = { nullptr, 0, 0 };\n      ZSTD_outBuffer out = { outBuf.data(), outBuf.size(), 0 };\n      size_t ret = ZSTD_compressStream2(reinterpret_cast<ZSTD_CCtx*>(cctx),\n                                        &out, &in, ZSTD_e_flush);\n\n      if (ZSTD_isError(ret))\n      {\n        eos_static_warning(\"msg=\\\"zstd header flush error (reports)\\\" code=%s\",\n                           ZSTD_getErrorName(ret));\n      }\n\n      if (out.pos)\n      {\n        (void)::write(fd, outBuf.data(), out.pos);\n      }\n    }\n    s_zstd_fd = fd;\n    s_zstd_cctx = cctx;\n    s_zstd_segstart = segstart;\n    s_zstd_current_path = path;\n    return true;\n  };\n  time_t now_ts = time(NULL);\n  std::unique_lock<std::mutex> scope_lock(s_mutex);\n\n  if (!s_zstd_inited) {\n    const char* zenv = getenv(\"EOS_ZSTD_REPORTS\");\n\n    if (zenv && (*zenv == '1' || !strcasecmp(zenv, \"true\") ||\n                 !strcasecmp(zenv, \"yes\") || !strcasecmp(zenv, \"on\"))) {\n      s_zstd_enabled = true;\n    }\n\n    const char* zrot = getenv(\"EOS_ZSTD_REPORTS_ROTATION\");\n\n    if (zrot && *zrot) {\n      int v = atoi(zrot);\n\n      if (v > 0) {\n        s_zstd_rotation = static_cast<unsigned>(v);\n      }\n    }\n\n    const char* lvl = getenv(\"EOS_ZSTD_REPORTS_LEVEL\");\n\n    if (lvl && *lvl) {\n      int v = atoi(lvl);\n\n      if (v >= 1 && v <= 19) {\n        s_zstd_level = v;\n      }\n    }\n\n    s_zstd_inited = true;\n  }\n\n  if (s_zstd_enabled) {\n    // Close plain FILE* if previously used\n    if (gOpenReportFD) {\n      fclose(gOpenReportFD);\n      gOpenReportFD = nullptr;\n      s_report_fn.clear();\n      s_last_ts = 0ull;\n    }\n\n    const time_t seg = truncate_to_interval(now_ts, s_zstd_rotation);\n\n    if (s_zstd_fd < 0 || !s_zstd_cctx || seg != s_zstd_segstart) {\n      close_zstd_locked();\n\n      if (!open_zstd_locked(seg)) {\n        return;\n      }\n    }\n\n    // Build line with newline terminator\n    std::string line = record;\n    line.push_back('\\n');\n    ZSTD_inBuffer in = { line.data(), line.size(), 0 };\n    std::vector<char> outBuf(131072);\n\n    while (in.pos < in.size) {\n      ZSTD_outBuffer out = { outBuf.data(), outBuf.size(), 0 };\n      size_t ret = ZSTD_compressStream2(reinterpret_cast<ZSTD_CCtx*>(s_zstd_cctx),\n                                        &out, &in, ZSTD_e_continue);\n\n      if (ZSTD_isError(ret)) {\n        eos_static_err(\"msg=\\\"zstd compress error (reports)\\\" code=%s\",\n                       ZSTD_getErrorName(ret));\n        break;\n      }\n\n      if (out.pos) {\n        ssize_t w = ::write(s_zstd_fd, outBuf.data(), out.pos);\n\n        if (w < 0) {\n          eos_static_err(\"msg=\\\"write error (reports)\\\" errno=%d err=\\\"%s\\\"\",\n                         errno, strerror(errno));\n          break;\n        }\n      }\n    }\n\n    // Flush so small records are visible\n    {\n      ZSTD_inBuffer fin = { nullptr, 0, 0 };\n      size_t fret = 0;\n\n      do {\n        ZSTD_outBuffer out = { outBuf.data(), outBuf.size(), 0 };\n        fret = ZSTD_compressStream2(reinterpret_cast<ZSTD_CCtx*>(s_zstd_cctx),\n                                    &out, &fin, ZSTD_e_flush);\n\n        if (ZSTD_isError(fret)) {\n          eos_static_warning(\"msg=\\\"zstd flush error (reports)\\\" code=%s\",\n                             ZSTD_getErrorName(fret));\n          break;\n        }\n\n        if (out.pos) {\n          (void)::write(s_zstd_fd, outBuf.data(), out.pos);\n        }\n      } while (fret != 0);\n    }\n    return;\n  }\n\n  // Plain (legacy) daily uncompressed reports\n  if (now_ts / sec_per_day != s_last_ts / sec_per_day) {\n    struct tm nowtm;\n\n    if (localtime_r(&now_ts, &nowtm)) {\n      static char logfile[4096];\n      snprintf(logfile, sizeof(logfile) - 1, \"%s/%04u/%02u/%04u%02u%02u.eosreport\",\n               gOFS->IoReportStorePath.c_str(),\n               1900 + nowtm.tm_year,\n               nowtm.tm_mon + 1,\n               1900 + nowtm.tm_year,\n               nowtm.tm_mon + 1,\n               nowtm.tm_mday);\n      std::string report_fn = logfile;\n\n      if (report_fn != s_report_fn) {\n        if (gOpenReportFD) {\n          fclose(gOpenReportFD);\n          gOpenReportFD = nullptr;\n        }\n\n        eos::common::Path cPath(report_fn.c_str());\n\n        if (cPath.MakeParentPath(S_IRWXU | S_IRGRP | S_IXGRP)) {\n          gOpenReportFD = fopen(report_fn.c_str(), \"a+\");\n\n          if (!gOpenReportFD) {\n            eos_static_err(\"msg=\\\"failed to open report file\\\" path=%s\",\n                           report_fn.c_str());\n            return;\n          }\n        } else {\n          eos_static_err(\"msg=\\\"failed to create report parent path\\\" path=%s\",\n                         report_fn.c_str());\n          return;\n        }\n\n        s_report_fn = report_fn;\n        s_last_ts = now_ts;\n      }\n    }\n  }\n\n  if (gOpenReportFD) {\n    fprintf(gOpenReportFD, \"%s\\n\", record.c_str());\n    fflush(gOpenReportFD);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print IO statistics\n//------------------------------------------------------------------------------\nvoid\nIostat::PrintOut(XrdOucString& out, bool summary, bool details,\n                 bool monitoring, bool numerical, bool top,\n                 bool domain, bool apps, bool sample_stat, time_t time_ago,\n                 time_t time_interval, XrdOucString option)\n{\n  std::string format_s = (!monitoring ? \"s\" : \"os\");\n  std::string format_ss = (!monitoring ? \"-s\" : \"os\");\n  std::string format_l = (!monitoring ? \"+l\" : \"ol\");\n  std::string format_ll = (!monitoring ? \"l.\" : \"ol\");\n  std::unique_lock<std::mutex> scope_lock(mDataMutex);\n  time_t now = time(NULL);\n  bool interval = false;\n  time_ago = time_ago % 86400;\n  time_interval = time_interval % 86400;\n\n  if (time_interval != 0) {\n    interval = true;\n  }\n\n  std::vector<std::string> tags;\n\n  if (summary || top) {\n    for (auto tit = IostatTag.begin(); tit != IostatTag.end(); ++tit) {\n      tags.push_back(tit->first);\n    }\n\n    std::sort(tags.begin(), tags.end());\n  }\n\n  if (summary) {\n    TableFormatterBase table;\n    TableData table_data;\n\n    if (interval) {\n      if (!monitoring) {\n        table.SetHeader({\n          std::make_tuple(\"who\", 3, format_ss),\n          std::make_tuple(\"io value\", 24, format_s),\n          std::make_tuple(\"data in interval\", 8, format_l),\n          std::make_tuple(\"avg rate [B/s]\", 8, format_l),\n        });\n      } else {\n        table.SetHeader({\n          std::make_tuple(\"uid\", 0, format_ss),\n          std::make_tuple(\"gid\", 0, format_s),\n          std::make_tuple(\"measurement\", 0, format_s),\n          std::make_tuple(\"intervaldata\", 8, format_l),\n          std::make_tuple(\"intervalrate\", 8, format_l),\n        });\n      }\n    } else {\n      if (!monitoring) {\n        table.SetHeader({\n          std::make_tuple(\"who\", 3, format_ss),\n          std::make_tuple(\"io value\", 24, format_s),\n          std::make_tuple(\"1min\", 8, format_l),\n          std::make_tuple(\"5min\", 8, format_l),\n          std::make_tuple(\"1h\", 8, format_l),\n          std::make_tuple(\"24h\", 8, format_l),\n          std::make_tuple(\"sum\", 8, format_l),\n        });\n      } else {\n        table.SetHeader({\n          std::make_tuple(\"uid\", 0, format_ss),\n          std::make_tuple(\"gid\", 0, format_s),\n          std::make_tuple(\"measurement\", 0, format_s),\n          std::make_tuple(\"60s\", 0, format_l),\n          std::make_tuple(\"300s\", 0, format_l),\n          std::make_tuple(\"3600s\", 0, format_l),\n          std::make_tuple(\"86400s\", 0, format_l),\n          std::make_tuple(\"total\", 0, format_l),\n        });\n      }\n    }\n\n    for (const auto& elem : tags) {\n      const char* tag = elem.c_str();\n      table_data.emplace_back();\n      TableRow& row = table_data.back();\n      row.emplace_back(\"all\", format_ss);\n\n      if (monitoring) {\n        row.emplace_back(\"all\", format_s);\n      }\n\n      row.emplace_back(tag, format_s);\n\n      if (interval) {\n        row.emplace_back(GetPeriodStatForTag(tag, time_interval, time_ago), format_ll);\n        row.emplace_back(GetPeriodStatForTag(tag, time_interval,\n                                             time_ago) / (float)time_interval, format_ll);\n      } else {\n        // getting tag stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n        row.emplace_back(GetPeriodStatForTag(tag, 60), format_ll);\n        row.emplace_back(GetPeriodStatForTag(tag, 300), format_ll);\n        row.emplace_back(GetPeriodStatForTag(tag, 3600), format_ll);\n        row.emplace_back(GetPeriodStatForTag(tag, 86400), format_ll);\n        row.emplace_back(GetTotalStatForTag(tag), format_ll);\n      }\n    }\n\n    table.AddRows(table_data);\n    out += table.GenerateTable(HEADER).c_str();\n    table_data.clear();\n\n    if (!interval) {\n      //! UDP Popularity Broadcast Target\n      std::unique_lock<std::mutex> mLock(mBcastMutex);\n\n      if (!mUdpPopularityTarget.empty()) {\n        TableFormatterBase table_udp;\n\n        if (!monitoring) {\n          table_udp.SetHeader({\n            std::make_tuple(\"UDP Popularity Broadcast Target\", 32, format_ss)\n          });\n        } else {\n          table_udp.SetHeader({ std::make_tuple(\"udptarget\", 0, format_ss) });\n        }\n\n        for (const auto& elem : mUdpPopularityTarget) {\n          table_data.emplace_back();\n          table_data.back().emplace_back(elem.c_str(), format_ss);\n        }\n\n        table_udp.AddRows(table_data);\n        out += table_udp.GenerateTable(HEADER).c_str();\n      }\n    }\n  }\n\n  if (details) {\n    if (interval) {\n      std::vector<std::tuple<std::string, std::string, unsigned long long, unsigned long long>>\n          uidout, gidout;\n      TableFormatterBase table_user;\n      TableData table_data;\n\n      //! User statistic\n      if (!monitoring) {\n        table_user.SetHeader({\n          std::make_tuple(\"user\", 5, format_ss),\n          std::make_tuple(\"io value\", 24, format_s),\n          std::make_tuple(\"data in interval\", 8, format_l),\n          std::make_tuple(\"avg rate [B/s]\", 8, format_l),\n        });\n      } else {\n        table_user.SetHeader({\n          std::make_tuple(\"uid\", 0, format_ss),\n          std::make_tuple(\"measurement\", 0, format_s),\n          std::make_tuple(\"intervaldata\", 8, format_l),\n          std::make_tuple(\"intervalrate\", 8, format_l),\n        });\n      }\n\n      for (auto tuit = IostatPeriodsUid.begin(); tuit != IostatPeriodsUid.end();\n           tuit++) {\n        for (auto it = tuit->second.begin(); it != tuit->second.end(); ++it) {\n          std::string username;\n\n          if (numerical) {\n            username = std::to_string(it->first);\n          } else {\n            int terrc = 0;\n            username = eos::common::Mapping::UidToUserName(it->first, terrc);\n          }\n\n          // getting tag stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n          uidout.emplace_back(std::make_tuple(username, tuit->first.c_str(),\n                                              it->second.GetDataInPeriod(time_interval, time_ago, now),\n                                              it->second.GetDataInPeriod(time_interval, time_ago, now) / (float)time_interval\n                                             ));\n        }\n      }\n\n      std::sort(uidout.begin(), uidout.end());\n\n      for (auto& tup : uidout) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        row.emplace_back(std::get<0>(tup), format_ss);\n        row.emplace_back(std::get<1>(tup), format_s);\n        row.emplace_back(std::get<2>(tup), format_l);\n        row.emplace_back(std::get<3>(tup), format_l);\n      }\n\n      table_user.AddRows(table_data);\n      out += table_user.GenerateTable(HEADER).c_str();\n      table_data.clear();\n      // Group statistics\n      TableFormatterBase table_group;\n\n      if (!monitoring) {\n        table_group.SetHeader({\n          std::make_tuple(\"group\", 5, format_ss),\n          std::make_tuple(\"io value\", 24, format_s),\n          std::make_tuple(\"data in interval\", 8, format_l),\n          std::make_tuple(\"avg rate [B/s]\", 8, format_l),\n        });\n      } else {\n        table_group.SetHeader({\n          std::make_tuple(\"gid\", 0, format_ss),\n          std::make_tuple(\"measurement\", 0, format_s),\n          std::make_tuple(\"intervaldata\", 8, format_l),\n          std::make_tuple(\"intervalrate\", 8, format_l),\n        });\n      }\n\n      for (auto tgit = IostatPeriodsGid.begin(); tgit != IostatPeriodsGid.end();\n           tgit++) {\n        for (auto it = tgit->second.begin(); it != tgit->second.end(); ++it) {\n          std::string groupname;\n\n          if (numerical) {\n            groupname = std::to_string(it->first);\n          } else {\n            int terrc = 0;\n            groupname = eos::common::Mapping::GidToGroupName(it->first, terrc);\n          }\n\n          // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n          gidout.emplace_back(std::make_tuple(groupname, tgit->first.c_str(),\n                                              it->second.GetDataInPeriod(time_interval, time_ago, now),\n                                              it->second.GetDataInPeriod(time_interval, time_ago, now) / (float)time_interval\n                                             ));\n        }\n      }\n\n      std::sort(gidout.begin(), gidout.end());\n\n      for (auto& tup : gidout) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        row.emplace_back(std::get<0>(tup), format_ss);\n        row.emplace_back(std::get<1>(tup), format_s);\n        row.emplace_back(std::get<2>(tup), format_l);\n        row.emplace_back(std::get<3>(tup), format_l);\n      }\n\n      table_group.AddRows(table_data);\n      out += table_group.GenerateTable(HEADER).c_str();\n      table_data.clear();\n    } else {\n      std::vector<std::tuple<std::string, std::string, unsigned long long,\n          unsigned long long, unsigned long long, unsigned long long,\n          unsigned long long, unsigned long long, unsigned long long, std::string>>\n          uidout_sec, gidout_sec;\n      std::vector<std::tuple<std::string, std::string, unsigned long long,\n          unsigned long long, unsigned long long, unsigned long long,\n          unsigned long long>>\n          uidout_b, gidout_b;\n      //std::vector<std::tuple<std::string, std::string, unsigned long long,\n      //    unsigned long long, unsigned long long, unsigned long long, unsigned long long>>\n      //    uidout, gidout;\n      TableData table_data;\n      XrdOucString marker_b =\n        \"\\n┏━> Sum of bytes transferred in last 1m/5m/1h/24h and total sum: \\n\";\n      //! User statistic\n      TableFormatterBase table_user_b;\n\n      if (!monitoring) {\n        table_user_b.SetHeader({\n          std::make_tuple(\"user\", 5, format_ss),\n          std::make_tuple(\"io value\", 24, format_s),\n          std::make_tuple(\"1min\", 8, format_l),\n          std::make_tuple(\"5min\", 8, format_l),\n          std::make_tuple(\"1h\", 8, format_l),\n          std::make_tuple(\"24h\", 8, format_l),\n          std::make_tuple(\"sum\", 8, format_l),\n        });\n      } else {\n        table_user_b.SetHeader({\n          std::make_tuple(\"uid\", 0, format_ss),\n          std::make_tuple(\"measurement\", 0, format_s),\n          std::make_tuple(\"60s\", 0, format_l),\n          std::make_tuple(\"300s\", 0, format_l),\n          std::make_tuple(\"3600s\", 0, format_l),\n          std::make_tuple(\"86400s\", 0, format_l),\n          std::make_tuple(\"total\", 0, format_l),\n        });\n      }\n\n      XrdOucString marker_sec =\n        \"\\n┏━> Transfer (tf) sample info every 5 min: tf time for 90/95/99% of data, max tf and report times, average tf size, tf count.\\n\";\n      TableFormatterBase table_user_sec;\n\n      if (sample_stat) {\n        if (!monitoring) {\n          table_user_sec.SetHeader({\n            std::make_tuple(\"user\", 5, format_ss),\n            std::make_tuple(\"io value\", 24, format_s),\n            std::make_tuple(\"90% [s]\", 8, format_l),\n            std::make_tuple(\"95% [s]\", 8, format_l),\n            std::make_tuple(\"99% [s]\", 8, format_l),\n            std::make_tuple(\"max [s]\", 8, format_l),\n            std::make_tuple(\"max report [s]\", 8, format_l),\n            std::make_tuple(\"avg tf size\", 8, format_l),\n            std::make_tuple(\"tf #\", 8, format_l),\n            std::make_tuple(\"sample end time\", 24, format_s)\n          });\n        } else {\n          table_user_sec.SetHeader({\n            std::make_tuple(\"uid\", 0, format_ss),\n            std::make_tuple(\"measurement\", 0, format_s),\n            std::make_tuple(\"tfsecto90p\", 0, format_l),\n            std::make_tuple(\"tfsecto95p\", 0, format_l),\n            std::make_tuple(\"tfsecto99p\", 0, format_l),\n            std::make_tuple(\"maxtransfersec\", 0, format_l),\n            std::make_tuple(\"maxreportsec\", 0, format_l),\n            std::make_tuple(\"avgtfsize5min\", 0, format_l),\n            std::make_tuple(\"tfcount\", 0, format_l),\n            std::make_tuple(\"sampletimestamp\", 0, format_s)\n          });\n        }\n      }\n\n      for (auto tuit = IostatPeriodsUid.begin(); tuit != IostatPeriodsUid.end();\n           tuit++) {\n        for (auto it = tuit->second.begin(); it != tuit->second.end(); ++it) {\n          std::string username;\n\n          if (numerical) {\n            username = std::to_string(it->first);\n          } else {\n            int terrc = 0;\n            username = eos::common::Mapping::UidToUserName(it->first, terrc);\n          }\n\n          // getting tag stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n          uidout_b.emplace_back(std::make_tuple(username, tuit->first.c_str(),\n                                                it->second.GetDataInPeriod(60, 0, now),\n                                                it->second.GetDataInPeriod(300, 0, now),\n                                                it->second.GetDataInPeriod(3600, 0, now),\n                                                it->second.GetDataInPeriod(86400, 0, now),\n                                                IostatUid[tuit->first][it->first]\n                                               ));\n\n          if (sample_stat) {\n            std::string sample_time = \"\";\n\n            if (!monitoring) {\n              sample_time = it->second.GetLastSampleUpdateTimestamp(true);\n            } else {\n              sample_time = it->second.GetLastSampleUpdateTimestamp(false);\n            }\n\n            uidout_sec.emplace_back(std::make_tuple(username, tuit->first.c_str(),\n                                                    it->second.GetTimeToPercComplete(P90),\n                                                    it->second.GetTimeToPercComplete(P95),\n                                                    it->second.GetTimeToPercComplete(P99),\n                                                    it->second.GetLongestTransferTime(),\n                                                    it->second.GetLongestReportTime(),\n                                                    it->second.GetAvgTransferSize(),\n                                                    it->second.GetTfCountInSample(),\n                                                    sample_time\n                                                   ));\n          }\n        }\n      }\n\n      std::sort(uidout_b.begin(), uidout_b.end());\n      std::sort(uidout_sec.begin(), uidout_sec.end());\n\n      for (auto& tup : uidout_b) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        row.emplace_back(std::get<0>(tup), format_ss);\n        row.emplace_back(std::get<1>(tup), format_s);\n        row.emplace_back(std::get<2>(tup), format_l);\n        row.emplace_back(std::get<3>(tup), format_l);\n        row.emplace_back(std::get<4>(tup), format_l);\n        row.emplace_back(std::get<5>(tup), format_l);\n        row.emplace_back(std::get<6>(tup), format_l);\n      }\n\n      table_user_b.AddRows(table_data);\n      out += !monitoring ? marker_b : \"\";\n      out += table_user_b.GenerateTable(HEADER).c_str();\n      table_data.clear();\n\n      if (sample_stat) {\n        for (auto& tup : uidout_sec) {\n          table_data.emplace_back();\n          TableRow& row = table_data.back();\n          row.emplace_back(std::get<0>(tup), format_ss);\n          row.emplace_back(std::get<1>(tup), format_s);\n          row.emplace_back(std::get<2>(tup), format_l);\n          row.emplace_back(std::get<3>(tup), format_l);\n          row.emplace_back(std::get<4>(tup), format_l);\n          row.emplace_back(std::get<5>(tup), format_l);\n          row.emplace_back(std::get<6>(tup), format_l);\n          row.emplace_back(std::get<7>(tup), format_l);\n          row.emplace_back(std::get<8>(tup), format_l);\n          row.emplace_back(std::get<9>(tup), format_s);\n        }\n\n        table_user_sec.AddRows(table_data);\n        out += !monitoring ? marker_sec : \"\";\n        out += table_user_sec.GenerateTable(HEADER).c_str();\n        table_data.clear();\n      }\n\n      //! Group statistic\n      TableFormatterBase table_group_b;\n\n      if (!monitoring) {\n        table_group_b.SetHeader({\n          std::make_tuple(\"group\", 5, format_ss),\n          std::make_tuple(\"io value\", 24, format_s),\n          std::make_tuple(\"1min\", 8, format_l),\n          std::make_tuple(\"5min\", 8, format_l),\n          std::make_tuple(\"1h\", 8, format_l),\n          std::make_tuple(\"24h\", 8, format_l),\n          std::make_tuple(\"sum\", 8, format_l),\n        });\n      } else {\n        table_group_b.SetHeader({\n          std::make_tuple(\"gid\", 0, format_ss),\n          std::make_tuple(\"measurement\", 0, format_s),\n          std::make_tuple(\"60s\", 0, format_l),\n          std::make_tuple(\"300s\", 0, format_l),\n          std::make_tuple(\"3600s\", 0, format_l),\n          std::make_tuple(\"86400s\", 0, format_l),\n          std::make_tuple(\"total\", 0, format_l),\n        });\n      }\n\n      TableFormatterBase table_group_sec;\n\n      if (sample_stat) {\n        if (!monitoring) {\n          table_group_sec.SetHeader({\n            std::make_tuple(\"group\", 5, format_ss),\n            std::make_tuple(\"io value\", 24, format_s),\n            std::make_tuple(\"90% [s]\", 8, format_l),\n            std::make_tuple(\"95% [s]\", 8, format_l),\n            std::make_tuple(\"99% [s]\", 8, format_l),\n            std::make_tuple(\"max [s]\", 8, format_l),\n            std::make_tuple(\"max report [s]\", 8, format_l),\n            std::make_tuple(\"avg tf size\", 8, format_l),\n            std::make_tuple(\"tf #\", 8, format_l),\n            std::make_tuple(\"sample end time\", 24, format_s)\n          });\n        } else {\n          table_group_sec.SetHeader({\n            std::make_tuple(\"gid\", 0, format_ss),\n            std::make_tuple(\"measurement\", 0, format_s),\n            std::make_tuple(\"tfsecto90p\", 0, format_l),\n            std::make_tuple(\"tfsecto95p\", 0, format_l),\n            std::make_tuple(\"tfsecto99p\", 0, format_l),\n            std::make_tuple(\"maxtransfersec\", 0, format_l),\n            std::make_tuple(\"maxreportsec\", 0, format_l),\n            std::make_tuple(\"avgtfsize5min\", 0, format_l),\n            std::make_tuple(\"tfcount\", 0, format_l),\n            std::make_tuple(\"sampletimestamp\", 0, format_s)\n\n          });\n        }\n      }\n\n      for (auto tgit = IostatPeriodsGid.begin(); tgit != IostatPeriodsGid.end();\n           tgit++) {\n        for (auto it = tgit->second.begin(); it != tgit->second.end(); ++it) {\n          std::string groupname;\n\n          if (numerical) {\n            groupname = std::to_string(it->first);\n          } else {\n            int terrc = 0;\n            groupname = eos::common::Mapping::GidToGroupName(it->first, terrc);\n          }\n\n          // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n          gidout_b.emplace_back(std::make_tuple(groupname, tgit->first.c_str(),\n                                                it->second.GetDataInPeriod(60, 0, now), it->second.GetDataInPeriod(300, 0, now),\n                                                it->second.GetDataInPeriod(3600, 0, now), it->second.GetDataInPeriod(86400, 0,\n                                                    now),\n                                                IostatGid[tgit->first][it->first]\n                                               ));\n\n          if (sample_stat) {\n            std::string sample_time = \"\";\n\n            if (!monitoring) {\n              sample_time = it->second.GetLastSampleUpdateTimestamp(true);\n            } else {\n              sample_time = it->second.GetLastSampleUpdateTimestamp(false);\n            }\n\n            gidout_sec.emplace_back(std::make_tuple(groupname, tgit->first.c_str(),\n                                                    it->second.GetTimeToPercComplete(P90),\n                                                    it->second.GetTimeToPercComplete(P95),\n                                                    it->second.GetTimeToPercComplete(P99),\n                                                    it->second.GetLongestTransferTime(),\n                                                    it->second.GetLongestReportTime(),\n                                                    it->second.GetAvgTransferSize(),\n                                                    it->second.GetTfCountInSample(),\n                                                    sample_time\n                                                   ));\n          }\n        }\n      }\n\n      std::sort(gidout_b.begin(), gidout_b.end());\n      std::sort(gidout_sec.begin(), gidout_sec.end());\n\n      for (auto& tup : gidout_b) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        row.emplace_back(std::get<0>(tup), format_ss);\n        row.emplace_back(std::get<1>(tup), format_s);\n        row.emplace_back(std::get<2>(tup), format_l);\n        row.emplace_back(std::get<3>(tup), format_l);\n        row.emplace_back(std::get<4>(tup), format_l);\n        row.emplace_back(std::get<5>(tup), format_l);\n        row.emplace_back(std::get<6>(tup), format_l);\n      }\n\n      table_group_b.AddRows(table_data);\n      out += !monitoring ? marker_b : \"\";\n      out += table_group_b.GenerateTable(HEADER).c_str();\n      table_data.clear();\n\n      if (sample_stat) {\n        for (auto& tup : gidout_sec) {\n          table_data.emplace_back();\n          TableRow& row = table_data.back();\n          row.emplace_back(std::get<0>(tup), format_ss);\n          row.emplace_back(std::get<1>(tup), format_s);\n          row.emplace_back(std::get<2>(tup), format_l);\n          row.emplace_back(std::get<3>(tup), format_l);\n          row.emplace_back(std::get<4>(tup), format_l);\n          row.emplace_back(std::get<5>(tup), format_l);\n          row.emplace_back(std::get<6>(tup), format_l);\n          row.emplace_back(std::get<7>(tup), format_l);\n          row.emplace_back(std::get<8>(tup), format_l);\n          row.emplace_back(std::get<9>(tup), format_s);\n        }\n\n        table_group_sec.AddRows(table_data);\n        out += !monitoring ? marker_sec : \"\";\n        out += table_group_sec.GenerateTable(HEADER).c_str();\n        table_data.clear();\n      }\n    }\n  }\n\n  if (top) {\n    TableFormatterBase table;\n    TableData table_data;\n\n    if (!monitoring) {\n      table.SetHeader({\n        std::make_tuple(\"io value\", 18, format_ss),\n        std::make_tuple(\"ranking by\", 10, format_s),\n        std::make_tuple(\"rank\", 8, format_ll),\n        std::make_tuple(\"who\", 4, format_s),\n        std::make_tuple(\"sum\", 8, format_l)\n      });\n    } else {\n      table.SetHeader({\n        std::make_tuple(\"measurement\", 0, format_ss),\n        std::make_tuple(\"rank\", 0, format_ll),\n        std::make_tuple(\"uid\", 0, format_s),\n        std::make_tuple(\"gid\", 0, format_s),\n        std::make_tuple(\"counter\", 0, format_l)\n      });\n    }\n\n    for (auto it = tags.begin(); it != tags.end(); ++it) {\n      std::vector <std::tuple<unsigned long long, uid_t>> uidout, gidout;\n      table.AddSeparator();\n\n      // by uid name\n      for (auto sit : IostatUid[*it]) {\n        uidout.push_back(std::make_tuple(sit.second, sit.first));\n      }\n\n      std::sort(uidout.begin(), uidout.end());\n      std::reverse(uidout.begin(), uidout.end());\n      int topplace = 0;\n\n      for (auto sit : uidout) {\n        topplace++;\n        uid_t uid = std::get<1>(sit);\n        unsigned long long counter = std::get<0>(sit);\n        std::string username;\n\n        if (numerical) {\n          username = std::to_string(uid);\n        } else {\n          int terrc = 0;\n          username = eos::common::Mapping::UidToUserName(uid, terrc);\n        }\n\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        row.emplace_back(it->c_str(), format_ss);\n\n        if (!monitoring) {\n          row.emplace_back(\"user\", format_s);\n        }\n\n        row.emplace_back(topplace, format_ll);\n        row.emplace_back(username, format_s);\n\n        if (monitoring) {\n          row.emplace_back(\"\", \"\", \"\", true);\n        }\n\n        row.emplace_back(counter, format_l);\n      }\n\n      // by gid name\n      for (auto sit : IostatGid[*it]) {\n        gidout.push_back(std::make_tuple(sit.second, sit.first));\n      }\n\n      std::sort(gidout.begin(), gidout.end());\n      std::reverse(gidout.begin(), gidout.end());\n      topplace = 0;\n\n      for (auto sit : gidout) {\n        topplace++;\n        uid_t gid = std::get<1>(sit);\n        unsigned long long counter = std::get<0>(sit);\n        std::string groupname;\n\n        if (numerical) {\n          groupname = std::to_string(gid);\n        } else {\n          int terrc = 0;\n          groupname = eos::common::Mapping::GidToGroupName(gid, terrc);\n        }\n\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        row.emplace_back(it->c_str(), format_ss);\n\n        if (!monitoring) {\n          row.emplace_back(\"group\", format_s);\n        }\n\n        row.emplace_back(topplace, format_ll);\n\n        if (monitoring) {\n          row.emplace_back(\"\", \"\", \"\", true);\n        }\n\n        row.emplace_back(groupname, format_s);\n        row.emplace_back(counter, format_l);\n      }\n    }\n\n    table.AddRows(table_data);\n    out += table.GenerateTable(HEADER).c_str();\n  }\n\n  if (domain) {\n    TableData table_data;\n\n    if (interval) {\n      TableFormatterBase table;\n\n      //! User statistic\n      if (!monitoring) {\n        table.SetHeader({\n          std::make_tuple(\"io\", 5, format_ss),\n          std::make_tuple(\"domain\", 24, format_s),\n          std::make_tuple(\"data in interval\", 8, format_l),\n          std::make_tuple(\"avg rate [B/s]\", 8, format_l),\n        });\n      } else {\n        table.SetHeader({\n          std::make_tuple(\"measurement\", 0, format_ss),\n          std::make_tuple(\"domain\", 0, format_s),\n          std::make_tuple(\"intervaldata\", 8, format_l),\n          std::make_tuple(\"intervalrate\", 8, format_l),\n        });\n      }\n\n      // IO out bytes\n      for (auto it = IostatPeriodsDomainIOrb.begin();\n           it != IostatPeriodsDomainIOrb.end();\n           ++it) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        std::string name = !monitoring ? \"out\" : \"domain_io_out\";\n        row.emplace_back(name, format_ss);\n        row.emplace_back(it->first.c_str(), format_s);\n        row.emplace_back(it->second.GetDataInPeriod(time_interval, time_ago, now),\n                         format_ll);\n        row.emplace_back(it->second.GetDataInPeriod(time_interval, time_ago,\n                         now) / (float)time_interval, format_ll);\n      }\n\n      // IO in bytes\n      for (auto it = IostatPeriodsDomainIOwb.begin();\n           it != IostatPeriodsDomainIOwb.end();\n           ++it) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        std::string name = !monitoring ? \"in\" : \"domain_io_in\";\n        row.emplace_back(name, format_ss);\n        row.emplace_back(it->first.c_str(), format_s);\n        // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n        row.emplace_back(it->second.GetDataInPeriod(time_interval, time_ago, now),\n                         format_ll);\n        row.emplace_back(it->second.GetDataInPeriod(time_interval, time_ago,\n                         now) / (float)time_interval, format_ll);\n      }\n\n      table.AddRows(table_data);\n      out += table.GenerateTable(HEADER).c_str();\n      table_data.clear();\n    } else {\n      XrdOucString marker_b =\n        \"\\n┏━> Sum of bytes transferred in last 1m/5m/1h/24h and total sum: \\n\";\n      //! User statistic\n      TableFormatterBase table_domain_b;\n\n      if (!monitoring) {\n        table_domain_b.SetHeader({\n          std::make_tuple(\"io\", 5, format_ss),\n          std::make_tuple(\"domain\", 24, format_s),\n          std::make_tuple(\"1min\", 8, format_l),\n          std::make_tuple(\"5min\", 8, format_l),\n          std::make_tuple(\"1h\", 8, format_l),\n          std::make_tuple(\"24h\", 8, format_l),\n          std::make_tuple(\"sum\", 8, format_l),\n        });\n      } else {\n        table_domain_b.SetHeader({\n          std::make_tuple(\"measurement\", 0, format_ss),\n          std::make_tuple(\"domain\", 0, format_s),\n          std::make_tuple(\"60s\", 0, format_l),\n          std::make_tuple(\"300s\", 0, format_l),\n          std::make_tuple(\"3600s\", 0, format_l),\n          std::make_tuple(\"86400s\", 0, format_l),\n          std::make_tuple(\"total\", 0, format_l),\n        });\n      }\n\n      // IO out bytes\n      for (auto it = IostatPeriodsDomainIOrb.begin();\n           it != IostatPeriodsDomainIOrb.end();\n           ++it) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        std::string name = !monitoring ? \"out\" : \"domain_io_out\";\n        row.emplace_back(name, format_ss);\n        row.emplace_back(it->first.c_str(), format_s);\n        // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n        row.emplace_back(it->second.GetDataInPeriod(60, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(300, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(3600, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(86400, 0, now), format_l);\n        row.emplace_back(it->second.GetTotalSum(), format_l);\n      }\n\n      // IO in bytes\n      for (auto it = IostatPeriodsDomainIOwb.begin();\n           it != IostatPeriodsDomainIOwb.end();\n           ++it) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        std::string name = !monitoring ? \"in\" : \"domain_io_in\";\n        row.emplace_back(name, format_ss);\n        row.emplace_back(it->first.c_str(), format_s);\n        // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n        row.emplace_back(it->second.GetDataInPeriod(60, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(300, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(3600, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(86400, 0, now), format_l);\n        row.emplace_back(it->second.GetTotalSum(), format_l);\n      }\n\n      table_domain_b.AddRows(table_data);\n      out += !monitoring ? marker_b : \"\";\n      out += table_domain_b.GenerateTable(HEADER).c_str();\n      table_data.clear();\n    }\n  }\n\n  if (apps) {\n    TableData table_data;\n\n    if (interval) {\n      TableFormatterBase table;\n\n      //! User statistic\n      if (!monitoring) {\n        table.SetHeader({\n          std::make_tuple(\"io\", 5, format_ss),\n          std::make_tuple(\"application\", 24, format_s),\n          std::make_tuple(\"data in interval\", 8, format_l),\n          std::make_tuple(\"avg rate [B/s]\", 8, format_l),\n        });\n      } else {\n        table.SetHeader({\n          std::make_tuple(\"measurement\", 0, format_ss),\n          std::make_tuple(\"application\", 0, format_s),\n          std::make_tuple(\"intervaldata\", 8, format_l),\n          std::make_tuple(\"intervalrate\", 8, format_l),\n        });\n      }\n\n      // IO out bytes\n      for (auto it = IostatPeriodsAppIOrb.begin(); it != IostatPeriodsAppIOrb.end();\n           ++it) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        std::string name = (!monitoring ? \"out\" : \"app_io_out\");\n        row.emplace_back(name, format_ss);\n        row.emplace_back(it->first.c_str(), format_s);\n        // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n        row.emplace_back(it->second.GetDataInPeriod(time_interval, time_ago, now),\n                         format_ll);\n        row.emplace_back(it->second.GetDataInPeriod(time_interval, time_ago,\n                         now) / (float)time_interval, format_ll);\n      }\n\n      // IO in bytes\n      for (auto it = IostatPeriodsAppIOwb.begin(); it != IostatPeriodsAppIOwb.end();\n           ++it) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        std::string name = (!monitoring ? \"in\" : \"app_io_in\");\n        row.emplace_back(name, format_ss);\n        row.emplace_back(it->first.c_str(), format_s);\n        // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n        row.emplace_back(it->second.GetDataInPeriod(time_interval, time_ago, now),\n                         format_ll);\n        row.emplace_back(it->second.GetDataInPeriod(time_interval, time_ago,\n                         now) / (float)time_interval, format_ll);\n      }\n\n      table.AddRows(table_data);\n      out += table.GenerateTable(HEADER).c_str();\n      table_data.clear();\n    } else {\n      XrdOucString marker_b =\n        \"\\n┏━> Sum of bytes transferred in last 1m/5m/1h/24h and total sum: \\n\";\n      //! User statistic\n      TableFormatterBase table_app_b;\n\n      if (!monitoring) {\n        table_app_b.SetHeader({\n          std::make_tuple(\"io\", 5, format_ss),\n          std::make_tuple(\"application\", 24, format_s),\n          std::make_tuple(\"1min\", 8, format_l),\n          std::make_tuple(\"5min\", 8, format_l),\n          std::make_tuple(\"1h\", 8, format_l),\n          std::make_tuple(\"24h\", 8, format_l),\n          std::make_tuple(\"sum\", 8, format_l),\n        });\n      } else {\n        table_app_b.SetHeader({\n          std::make_tuple(\"measurement\", 0, format_ss),\n          std::make_tuple(\"application\", 0, format_s),\n          std::make_tuple(\"60s\", 0, format_l),\n          std::make_tuple(\"300s\", 0, format_l),\n          std::make_tuple(\"3600s\", 0, format_l),\n          std::make_tuple(\"86400s\", 0, format_l),\n          std::make_tuple(\"total\", 0, format_l),\n        });\n      }\n\n      XrdOucString marker_sec =\n        \"\\n┏━> Transfer (tf) sample info every 5 min: tf time for 90/95/99% of data, max tf and report times, average tf size, tf count.\\n\";\n      TableFormatterBase table_app_sec;\n\n      if (sample_stat) {\n        if (!monitoring) {\n          table_app_sec.SetHeader({\n            std::make_tuple(\"io\", 5, format_ss),\n            std::make_tuple(\"application\", 24, format_s),\n            std::make_tuple(\"90% [s]\", 8, format_l),\n            std::make_tuple(\"95% [s]\", 8, format_l),\n            std::make_tuple(\"99% [s]\", 8, format_l),\n            std::make_tuple(\"max [s]\", 8, format_l),\n            std::make_tuple(\"max report [s]\", 8, format_l),\n            std::make_tuple(\"avg tf size\", 8, format_l),\n            std::make_tuple(\"tf #\", 8, format_l),\n            std::make_tuple(\"sample end time\", 24, format_s),\n          });\n        } else {\n          table_app_sec.SetHeader({\n            std::make_tuple(\"measurement\", 0, format_ss),\n            std::make_tuple(\"application\", 0, format_s),\n            std::make_tuple(\"tfsecto90p\", 0, format_l),\n            std::make_tuple(\"tfsecto95p\", 0, format_l),\n            std::make_tuple(\"tfsecto99p\", 0, format_l),\n            std::make_tuple(\"maxtransfersec\", 0, format_l),\n            std::make_tuple(\"maxreportsec\", 0, format_l),\n            std::make_tuple(\"avgtfsize5min\", 0, format_l),\n            std::make_tuple(\"tfcount\", 0, format_l),\n            std::make_tuple(\"sampletimestamp\", 0, format_s)\n          });\n        }\n      }\n\n      // IO out bytes\n      for (auto it = IostatPeriodsAppIOrb.begin(); it != IostatPeriodsAppIOrb.end();\n           ++it) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        std::string name = (!monitoring ? \"out\" : \"app_io_out\");\n        row.emplace_back(name, format_ss);\n        row.emplace_back(it->first.c_str(), format_s);\n        // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n        row.emplace_back(it->second.GetDataInPeriod(60, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(300, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(3600, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(86400, 0, now), format_l);\n        row.emplace_back(it->second.GetTotalSum(), format_l);\n      }\n\n      // IO in bytes\n      for (auto it = IostatPeriodsAppIOwb.begin(); it != IostatPeriodsAppIOwb.end();\n           ++it) {\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n        std::string name = (!monitoring ? \"in\" : \"app_io_in\");\n        row.emplace_back(name, format_ss);\n        row.emplace_back(it->first.c_str(), format_s);\n        // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n        row.emplace_back(it->second.GetDataInPeriod(60, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(300, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(3600, 0, now), format_l);\n        row.emplace_back(it->second.GetDataInPeriod(86400, 0, now), format_l);\n        row.emplace_back(it->second.GetTotalSum(), format_l);\n      }\n\n      out += !monitoring ? marker_b : \"\";\n      table_app_b.AddRows(table_data);\n      out += table_app_b.GenerateTable(HEADER).c_str();\n      table_data.clear();\n\n      if (sample_stat) {\n        // IO out bytes\n        for (auto it = IostatPeriodsAppIOrb.begin(); it != IostatPeriodsAppIOrb.end();\n             ++it) {\n          table_data.emplace_back();\n          TableRow& row = table_data.back();\n          std::string name = (!monitoring ? \"out\" : \"app_io_out\");\n          std::string sample_time = \"\";\n\n          if (!monitoring) {\n            sample_time = it->second.GetLastSampleUpdateTimestamp(true);\n          } else {\n            sample_time = it->second.GetLastSampleUpdateTimestamp(false);\n          }\n\n          row.emplace_back(name, format_ss);\n          row.emplace_back(it->first.c_str(), format_s);\n          // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n          row.emplace_back(it->second.GetTimeToPercComplete(P90), format_l);\n          row.emplace_back(it->second.GetTimeToPercComplete(P95), format_l);\n          row.emplace_back(it->second.GetTimeToPercComplete(P99), format_l);\n          row.emplace_back(it->second.GetLongestTransferTime(), format_l);\n          row.emplace_back(it->second.GetLongestReportTime(), format_l);\n          row.emplace_back(it->second.GetAvgTransferSize(), format_l);\n          row.emplace_back(it->second.GetTfCountInSample(), format_l);\n          row.emplace_back(sample_time, format_s);\n        }\n\n        // IO in bytes\n        for (auto it = IostatPeriodsAppIOwb.begin(); it != IostatPeriodsAppIOwb.end();\n             ++it) {\n          table_data.emplace_back();\n          TableRow& row = table_data.back();\n          std::string sample_time = \"\";\n\n          if (!monitoring) {\n            sample_time = it->second.GetLastSampleUpdateTimestamp(true);\n          } else {\n            sample_time = it->second.GetLastSampleUpdateTimestamp(false);\n          }\n\n          std::string name = (!monitoring ? \"in\" : \"app_io_in\");\n          row.emplace_back(name, format_ss);\n          row.emplace_back(it->first.c_str(), format_s);\n          // getting stat sums for 1day (idx=0), 1h (idx=1), 5m (idx=2), 1min (idx=3)\n          row.emplace_back(it->second.GetTimeToPercComplete(P90), format_l);\n          row.emplace_back(it->second.GetTimeToPercComplete(P95), format_l);\n          row.emplace_back(it->second.GetTimeToPercComplete(P99), format_l);\n          row.emplace_back(it->second.GetLongestTransferTime(), format_l);\n          row.emplace_back(it->second.GetLongestReportTime(), format_l);\n          row.emplace_back(it->second.GetAvgTransferSize(), format_l);\n          row.emplace_back(it->second.GetTfCountInSample(), format_l);\n          row.emplace_back(sample_time, format_s);\n        }\n\n        out += !monitoring ? marker_sec : \"\";\n        table_app_sec.AddRows(table_data);\n        out += table_app_sec.GenerateTable(HEADER).c_str();\n        table_data.clear();\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Compute and print out the namespace popularity ranking\n//------------------------------------------------------------------------------\nvoid\nIostat::PrintNsPopularity(XrdOucString& out, XrdOucString option) const\n{\n  size_t limit = 10;\n  size_t popularitybin = (((time(NULL))) % (IOSTAT_POPULARITY_DAY *\n                          IOSTAT_POPULARITY_HISTORY_DAYS)) / IOSTAT_POPULARITY_DAY;\n  size_t days = 1;\n  time_t tmarker = time(NULL) / IOSTAT_POPULARITY_DAY * IOSTAT_POPULARITY_DAY;\n  bool monitoring = false;\n  bool bycount = false;\n  bool bybytes = false;\n  bool hotfiles = false;\n\n  if ((option.find(\"-m\")) != STR_NPOS) {\n    monitoring = true;\n  }\n\n  if ((option.find(\"-a\")) != STR_NPOS) {\n    limit = 999999999;\n  }\n\n  if ((option.find(\"-100\")) != STR_NPOS) {\n    limit = 100;\n  }\n\n  if ((option.find(\"-1000\")) != STR_NPOS) {\n    limit = 1000;\n  }\n\n  if ((option.find(\"-10000\")) != STR_NPOS) {\n    limit = 10000;\n  }\n\n  if ((option.find(\"-n\") != STR_NPOS)) {\n    bycount = true;\n  }\n\n  if ((option.find(\"-b\") != STR_NPOS)) {\n    bybytes = true;\n  }\n\n  if ((option.find(\"-w\") != STR_NPOS)) {\n    days = IOSTAT_POPULARITY_HISTORY_DAYS;\n  }\n\n  if (!(bycount || bybytes)) {\n    bybytes = bycount = true;\n  }\n\n  if ((option.find(\"-f\") != STR_NPOS)) {\n    hotfiles = true;\n  }\n\n  std::string format_s = !monitoring ? \"s\" : \"os\";\n  std::string format_ss = !monitoring ? \"-s\" : \"os\";\n  std::string format_l = !monitoring ? \"l\" : \"ol\";\n  std::string format_ll = !monitoring ? \"-l.\" : \"ol\";\n  std::string format_lll = !monitoring ? \"+l\" : \"ol\";\n  std::string unit = !monitoring ? \"B\" : \"\";\n\n  // The 'hotfiles' are the files with highest number of present file opens\n  if (hotfiles) {\n    eos::common::RWMutexReadLock rLock(FsView::gFsView.ViewMutex);\n    // print the hotfiles report\n    std::set<eos::common::FileSystem::fsid_t>::const_iterator it;\n    std::vector<std::string> r_open_vector;\n    std::vector<std::string> w_open_vector;\n    std::string key;\n    std::string val;\n    TableFormatterBase table;\n    TableData table_data;\n\n    if (!monitoring) {\n      table.SetHeader({\n        std::make_tuple(\"type\", 5, format_ss),\n        std::make_tuple(\"heat\", 5, format_s),\n        std::make_tuple(\"fs\", 5, format_s),\n        std::make_tuple(\"host\", 24, format_s),\n        std::make_tuple(\"path\", 24, format_ss)\n      });\n    } else {\n      table.SetHeader({\n        std::make_tuple(\"measurement\", 0, format_ss),\n        std::make_tuple(\"access\", 0, format_s),\n        std::make_tuple(\"heat\", 0, format_s),\n        std::make_tuple(\"fsid\", 0, format_l),\n        std::make_tuple(\"path\", 0, format_ss),\n        std::make_tuple(\"fxid\", 0, format_s)\n      });\n    }\n\n    for (auto it = FsView::gFsView.mIdView.begin();\n         it != FsView::gFsView.mIdView.end(); it++) {\n      r_open_vector.clear();\n      w_open_vector.clear();\n      FileSystem* fs = it->second;\n\n      if (!fs) {\n        continue;\n      }\n\n      std::string r_open_hotfiles = fs->GetString(\"stat.ropen.hotfiles\");\n      std::string w_open_hotfiles = fs->GetString(\"stat.wopen.hotfiles\");\n      std::string node_queue = fs->GetString(\"queue\");\n      auto it_node = FsView::gFsView.mNodeView.find(node_queue);\n\n      if (it_node == FsView::gFsView.mNodeView.end()) {\n        continue;\n      }\n\n      // Check if the corresponding node has a heartbeat\n      bool hasHeartbeat = it_node->second->HasHeartbeat();\n\n      // we only show the reports from the last minute, there could be pending values\n      if (!hasHeartbeat) {\n        r_open_hotfiles = \"\";\n        w_open_hotfiles = \"\";\n      }\n\n      if (r_open_hotfiles == \" \") {\n        r_open_hotfiles = \"\";\n      }\n\n      if (w_open_hotfiles == \" \") {\n        w_open_hotfiles = \"\";\n      }\n\n      eos::common::StringConversion::Tokenize(r_open_hotfiles, r_open_vector);\n      eos::common::StringConversion::Tokenize(w_open_hotfiles, w_open_vector);\n      std::string host = fs->GetString(\"host\");\n      std::string path;\n      std::string id = fs->GetString(\"id\");\n      std::vector<std::tuple<std::string, std::string, std::string,\n          std::string, std::string>> data;\n      std::vector<std::tuple<std::string, std::string, std::string,\n          long long unsigned, std::string, std::string>> data_monitoring;\n\n      // Get information for read\n      for (size_t i = 0; i < r_open_vector.size(); i++) {\n        eos::common::StringConversion::SplitKeyValue(r_open_vector[i], key, val);\n        int rank = 0;\n\n        if (key.c_str()) {\n          rank = atoi(key.c_str());\n        }\n\n        {\n          unsigned long long fid = eos::common::FileId::Hex2Fid(val.c_str());\n          eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, fid);\n\n          try {\n            auto fmd = gOFS->eosFileService->getFileMD(fid);\n            // Do not lock the fmd before getting its URI\n            path = gOFS->eosView->getUri(fmd.get());\n          } catch (eos::MDException& e) {\n            path = \"<undef>\";\n          }\n        }\n\n        if (rank > 1) {\n          data.emplace_back(std::make_tuple(\n                              \"read\", key.c_str(), id.c_str(), host.c_str(), path.c_str()));\n        }\n\n        data_monitoring.emplace_back(std::make_tuple(\n                                       \"hotfile\", \"read\", key.c_str(), it->first, path.c_str(), val.c_str()));\n      }\n\n      // Get information for write\n      for (size_t i = 0; i < w_open_vector.size(); i++) {\n        eos::common::StringConversion::SplitKeyValue(w_open_vector[i], key, val);\n        int rank = 0;\n\n        if (key.c_str()) {\n          rank = atoi(key.c_str());\n        }\n\n        {\n          unsigned long long fid = eos::common::FileId::Hex2Fid(val.c_str());\n          eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, fid);\n\n          try {\n            auto fmd = gOFS->eosFileService->getFileMD(fid);\n            // Do not lock the fmd before getting its URI\n            path = gOFS->eosView->getUri(fmd.get());\n          } catch (eos::MDException& e) {\n            path = \"<undef>\";\n          }\n        }\n\n        if (rank > 1) {\n          data.emplace_back(std::make_tuple(\n                              \"write\", key.c_str(), id.c_str(), host.c_str(), path.c_str()));\n        }\n\n        data_monitoring.emplace_back(std::make_tuple(\n                                       \"hotfile\", \"write\", key.c_str(), it->first, path.c_str(), val.c_str()));\n      }\n\n      // Sort and output\n      if (!monitoring) {\n        std::sort(data.begin(), data.end());\n\n        for (auto it : data) {\n          table_data.emplace_back();\n          TableRow& row = table_data.back();\n          row.emplace_back(std::get<0>(it), format_ss);\n          row.emplace_back(std::get<1>(it), format_s);\n          row.emplace_back(std::get<2>(it), format_s);\n          row.emplace_back(std::get<3>(it), format_s);\n          row.emplace_back(std::get<4>(it), format_ss);\n        }\n      } else {\n        std::sort(data_monitoring.begin(), data_monitoring.end());\n\n        for (auto mdata : data_monitoring) {\n          table_data.emplace_back();\n          TableRow& row = table_data.back();\n          row.emplace_back(std::get<0>(mdata), format_ss);\n          row.emplace_back(std::get<1>(mdata), format_s);\n          row.emplace_back(std::get<2>(mdata), format_s);\n          row.emplace_back(std::get<3>(mdata), format_l);\n          row.emplace_back(std::get<4>(mdata), format_ss);\n          row.emplace_back(std::get<5>(mdata), format_s);\n        }\n      }\n    }\n\n    table.AddRows(table_data);\n    out += table.GenerateTable(HEADER).c_str();\n    return;\n  }\n\n  //! Namespace IO ranking (popularity)\n  for (size_t pbin = 0; pbin < days; pbin++) {\n    std::unique_lock<std::mutex> scope_lock(mPopularityMutex);\n    size_t sbin = (IOSTAT_POPULARITY_HISTORY_DAYS + popularitybin - pbin) %\n                  IOSTAT_POPULARITY_HISTORY_DAYS;\n    google::sparse_hash_map<std::string, struct Popularity>::const_iterator it;\n    std::vector<popularity_t> popularity_nread(IostatPopularity[sbin].begin(),\n        IostatPopularity[sbin].end());\n    std::vector<popularity_t> popularity_rb(IostatPopularity[sbin].begin(),\n                                            IostatPopularity[sbin].end());\n    // sort them (backwards) by rb or nread\n    std::sort(popularity_nread.begin(), popularity_nread.end(),\n              PopularityCmp_nread());\n    std::sort(popularity_rb.begin(), popularity_rb.end(), PopularityCmp_rb());\n    XrdOucString marker = \"\\n┏━> Today\\n\";\n\n    switch (pbin) {\n    case 1:\n      marker = \"\\n┏━> Yesterday\\n\";\n      break;\n\n    case 2:\n      marker = \"\\n┏━> 2 days ago\\n\";\n      break;\n\n    case 3:\n      marker = \"\\n┏━> 3 days ago\\n\";\n      break;\n\n    case 4:\n      marker = \"\\n┏━> 4 days ago\\n\";\n      break;\n\n    case 5:\n      marker = \"\\n┏━> 5 days ago\\n\";\n      break;\n\n    case 6:\n      marker = \"\\n┏━> 6 days ago\\n\";\n    }\n\n    if (bycount) {\n      TableFormatterBase table;\n      TableData table_data;\n\n      if (!monitoring) {\n        table.SetHeader({\n          std::make_tuple(\"rank\", 5, format_ll),\n          std::make_tuple(\"by(read count)\", 12, format_s),\n          std::make_tuple(\"read bytes\", 10, format_lll),\n          std::make_tuple(\"path\", 24, format_ss),\n        });\n      } else {\n        table.SetHeader({\n          std::make_tuple(\"measurement\", 0, format_ss),\n          std::make_tuple(\"time\", 0, format_lll),\n          std::make_tuple(\"rank\", 0, format_ll),\n          std::make_tuple(\"nread\", 0, format_lll),\n          std::make_tuple(\"rb\", 0, format_lll),\n          std::make_tuple(\"path\", 0, format_ss)\n        });\n      }\n\n      size_t cnt = 0;\n\n      for (auto it : popularity_nread) {\n        cnt++;\n\n        if (cnt > limit) {\n          break;\n        }\n\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n\n        if (monitoring) {\n          row.emplace_back(\"popularitybyaccess\", format_ss);\n          row.emplace_back((unsigned) tmarker, format_lll);\n        }\n\n        row.emplace_back((int) cnt, format_ll);\n        row.emplace_back(it.second.nread, format_lll);\n        row.emplace_back(it.second.rb, format_lll, unit);\n        row.emplace_back(it.first.c_str(), format_s);\n      }\n\n      if (cnt > 0) {\n        out += !monitoring ? marker : \"\";\n        table.AddRows(table_data);\n        out += table.GenerateTable(HEADER).c_str();\n      }\n    }\n\n    if (bybytes) {\n      TableFormatterBase table;\n      TableData table_data;\n\n      if (!monitoring) {\n        table.SetHeader({\n          std::make_tuple(\"rank\", 5, format_ll),\n          std::make_tuple(\"by(read bytes)\", 12, format_s),\n          std::make_tuple(\"read count\", 10, format_lll),\n          std::make_tuple(\"path\", 24, format_ss),\n        });\n      } else {\n        table.SetHeader({\n          std::make_tuple(\"measurement\", 0, format_ss),\n          std::make_tuple(\"time\", 0, format_lll),\n          std::make_tuple(\"rank\", 0, format_ll),\n          std::make_tuple(\"nread\", 0, format_lll),\n          std::make_tuple(\"rb\", 0, format_lll),\n          std::make_tuple(\"path\", 0, format_ss)\n        });\n      }\n\n      size_t cnt = 0;\n\n      for (auto it : popularity_rb) {\n        cnt++;\n\n        if (cnt > limit) {\n          break;\n        }\n\n        table_data.emplace_back();\n        TableRow& row = table_data.back();\n\n        if (monitoring) {\n          row.emplace_back(\"popularitybyvolume\", format_ss);\n          row.emplace_back((unsigned) tmarker, format_lll);\n        }\n\n        row.emplace_back((int) cnt, format_ll);\n\n        if (!monitoring) {\n          row.emplace_back(it.second.rb, format_lll, unit);\n          row.emplace_back(it.second.nread, format_lll);\n        } else {\n          row.emplace_back(it.second.nread, format_lll);\n          row.emplace_back(it.second.rb, format_lll, unit);\n        }\n\n        row.emplace_back(it.first.c_str(), format_s);\n      }\n\n      table.AddRows(table_data);\n      out += table.GenerateTable(HEADER2).c_str();\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print namespace activity report for given path\n//------------------------------------------------------------------------------\nvoid\nIostat::PrintNsReport(const char* path, XrdOucString& out) const\n{\n  XrdOucString reportFile;\n  reportFile = gOFS->IoReportStorePath.c_str();\n  reportFile += \"/\";\n  reportFile += path;\n  std::ifstream inFile(reportFile.c_str());\n  std::string reportLine;\n  unsigned long long totalreadbytes = 0;\n  unsigned long long totalwritebytes = 0;\n  double totalreadtime = 0;\n  double totalwritetime = 0;\n  unsigned long long rcount = 0;\n  unsigned long long wcount = 0;\n\n  while (std::getline(inFile, reportLine)) {\n    XrdOucEnv ioreport(reportLine.c_str());\n    auto report = std::make_unique<eos::common::Report>(ioreport);\n    report->Dump(out);\n\n    if (!report->wb) {\n      rcount++;\n      totalreadtime += ((report->cts - report->ots) + (1.0 * (report->ctms -\n                        report->otms) / 1000000));\n      totalreadbytes += report->rb;\n    } else {\n      wcount++;\n      totalwritetime += ((report->cts - report->ots) + (1.0 *\n                         (report->ctms - report->otms) / 1000000));\n      totalwritebytes += report->wb;\n    }\n  }\n\n  out += \"----------------------- SUMMARY -------------------\\n\";\n  char summaryline[4096];\n  XrdOucString sizestring1, sizestring2;\n  snprintf(summaryline, sizeof(summaryline) - 1,\n           \"| avg. readd: %.02f MB/s | avg. write: %.02f  MB/s | \"\n           \"total read: %s | total write: %s | times read: %llu | \"\n           \"times written: %llu |\\n\",\n           totalreadtime ? (totalreadbytes / totalreadtime / 1000000.0) : 0,\n           totalwritetime ? (totalwritebytes / totalwritetime / 1000000.0) : 0,\n           eos::common::StringConversion::GetReadableSizeString\n           (sizestring1, totalreadbytes, \"B\"),\n           eos::common::StringConversion::GetReadableSizeString\n           (sizestring2, totalwritebytes, \"B\"), (unsigned long long)rcount,\n           (unsigned long long)wcount);\n  out += summaryline;\n}\n\n//------------------------------------------------------------------------------\n// Circulate the entries to get stats collected over last sec, min, hour and day\n//------------------------------------------------------------------------------\nvoid\nIostat::Circulate(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"IoStatCirculate\");\n\n  while (!assistant.terminationRequested()) {\n    if (mLegacyMode) {\n      static unsigned long long sc = 0ull;\n\n      // Store once per minute the current statistics\n      if (sc % 117 == 0) {\n        sc = 0ull;\n\n        // save the current state ~ every minute\n        if (!LegacyStoreInFile()) {\n          eos_static_err(\"msg=\\\"failed store io stat dump\\\" path=\\\"%s\\\"\",\n                         mLegacyFilePath.c_str());\n        }\n      }\n\n      sc++;\n    }\n\n    assistant.wait_for(std::chrono::milliseconds(512));\n    google::sparse_hash_map<std::string, google::sparse_hash_map<uid_t, IostatPeriods> >::iterator\n    tit;\n    google::sparse_hash_map<std::string, IostatPeriods >::iterator dit;\n    time_t now = time(NULL);\n    std::unique_lock<std::mutex> scope_lock(mDataMutex);\n\n    // loop over tags\n    for (tit = IostatPeriodsUid.begin(); tit != IostatPeriodsUid.end(); ++tit) {\n      // loop over vids\n      google::sparse_hash_map<uid_t, IostatPeriods>::iterator it;\n\n      for (it = tit->second.begin(); it != tit->second.end(); ++it) {\n        it->second.StampBufferZero(now);\n      }\n    }\n\n    for (tit = IostatPeriodsGid.begin(); tit != IostatPeriodsGid.end(); ++tit) {\n      // loop over vids\n      google::sparse_hash_map<uid_t, IostatPeriods>::iterator it;\n\n      for (it = tit->second.begin(); it != tit->second.end(); ++it) {\n        it->second.StampBufferZero(now);\n      }\n    }\n\n    // loop over domain accounting\n    for (dit = IostatPeriodsDomainIOrb.begin();\n         dit != IostatPeriodsDomainIOrb.end();\n         dit++) {\n      dit->second.StampBufferZero(now);\n    }\n\n    for (dit = IostatPeriodsDomainIOwb.begin();\n         dit != IostatPeriodsDomainIOwb.end();\n         dit++) {\n      dit->second.StampBufferZero(now);\n    }\n\n    // loop over app accounting\n    for (dit = IostatPeriodsAppIOrb.begin(); dit != IostatPeriodsAppIOrb.end();\n         dit++) {\n      dit->second.StampBufferZero(now);\n    }\n\n    for (dit = IostatPeriodsAppIOwb.begin(); dit != IostatPeriodsAppIOwb.end();\n         dit++) {\n      dit->second.StampBufferZero(now);\n    }\n\n    size_t popularitybin = (((time(NULL))) % (IOSTAT_POPULARITY_DAY *\n                            IOSTAT_POPULARITY_HISTORY_DAYS)) / IOSTAT_POPULARITY_DAY;\n\n    if (mLastPopularityBin != popularitybin) {\n      // only if we enter a new bin we erase it\n      std::unique_lock<std::mutex> scope_lock(mPopularityMutex);\n      IostatPopularity[popularitybin].clear();\n      IostatPopularity[popularitybin].resize(10000);\n      mLastPopularityBin = popularitybin;\n    }\n  }\n\n  eos_static_info(\"%s\", \"msg=\\\"stopping iostat circulate thread\\\"\");\n}\n\n//------------------------------------------------------------------------------\n// Encode the UDP popularity targets to a string using the provided separator\n//------------------------------------------------------------------------------\nstd::string\nIostat::EncodeUdpPopularityTargets() const\n{\n  std::string out;\n  std::unique_lock<std::mutex> scope_lock(mBcastMutex);\n\n  if (mUdpPopularityTarget.empty()) {\n    return out;\n  }\n\n  for (const auto& elem : mUdpPopularityTarget) {\n    out += elem;\n    out += \"|\";\n  }\n\n  out.pop_back();\n  return out;\n}\n\n//------------------------------------------------------------------------------\n// Add UDP target\n//------------------------------------------------------------------------------\nbool\nIostat::AddUdpTarget(const std::string& target, bool store_and_lock)\n{\n  std::unique_lock<std::mutex> scope_lock(mBcastMutex, std::defer_lock);\n\n  if (store_and_lock) {\n    scope_lock.lock();\n  }\n\n  if (mUdpPopularityTarget.insert(target).second == false) {\n    // Target already exists\n    return false;\n  }\n\n  // Create an UDP socket for the specified target\n  int udpsocket = -1;\n  udpsocket = socket(AF_INET, SOCK_DGRAM, 0);\n\n  if (udpsocket >= 0) {\n    XrdOucString a_host, a_port, hp;\n    int port = 0;\n    hp = target.c_str();\n\n    if (!eos::common::StringConversion::SplitKeyValue(hp, a_host, a_port)) {\n      a_host = hp;\n      a_port = \"31000\";\n    }\n\n    port = atoi(a_port.c_str());\n    mUdpSocket[target] = udpsocket;\n    XrdNetAddr* addrs  = 0;\n    int         nAddrs = 0;\n    const char* err    = XrdNetUtils::GetAddrs(a_host.c_str(), &addrs, nAddrs,\n                         XrdNetUtils::allIPv64,\n                         XrdNetUtils::NoPortRaw);\n\n    if (err || nAddrs == 0) {\n      return false;\n    }\n\n    memcpy((struct sockaddr*) &mUdpSockAddr[target], addrs[0].SockAddr(),\n           sizeof(sockaddr));\n    delete [] addrs;\n    mUdpSockAddr[target].sin_family = AF_INET;\n    mUdpSockAddr[target].sin_port = htons(port);\n  }\n\n  // Store configuration if required\n  if (store_and_lock) {\n    scope_lock.unlock();\n    return StoreIostatConfig(&FsView::gFsView);\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Remove UDP target\n//------------------------------------------------------------------------------\nbool\nIostat::RemoveUdpTarget(const std::string& target)\n{\n  bool store = false;\n  bool retc = false;\n  {\n    std::unique_lock<std::mutex> scop_lock(mBcastMutex);\n\n    if (mUdpPopularityTarget.count(target)) {\n      mUdpPopularityTarget.erase(target);\n\n      if (mUdpSocket.count(target)) {\n        if (mUdpSocket[target] > 0) {\n          // close the UDP socket\n          close(mUdpSocket[target]);\n        }\n\n        mUdpSocket.erase(target);\n        mUdpSockAddr.erase(target);\n      }\n\n      retc = true;\n      store = true;\n    }\n  }\n\n  if (store) {\n    retc &= StoreIostatConfig(&FsView::gFsView);\n  }\n\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Do the UDP broadcast\n//------------------------------------------------------------------------------\nvoid\nIostat::UdpBroadCast(eos::common::Report* report) const\n{\n  std::string u = \"\";\n  char fs[1024];\n  std::unique_lock<std::mutex> scope_lock(mBcastMutex);\n\n  for (auto it = mUdpPopularityTarget.cbegin();\n       it != mUdpPopularityTarget.cend(); ++it) {\n    u = \"\";\n    XrdOucString tg = it->c_str();\n    XrdOucString sizestring;\n\n    if (tg.endswith(\"/json\")) {\n      // do json format broadcast\n      tg.replace(\"/json\", \"\");\n      u += \"{\\\"app_info\\\": \\\"\";\n      u += report->sec_app;\n      u += \"\\\",\\n\";\n      u += \" \\\"client_domain\\\": \\\"\";\n      u += report->sec_domain;\n      u += \"\\\",\\n\";\n      u += \" \\\"client_host\\\": \\\"\";\n      u += report->sec_host;\n      u += \"\\\",\\n\";\n      u += \" \\\"end_time\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->cts);\n      u += \",\\n\";\n      u += \" \\\"file_lfn\\\": \\\"\";\n      u += report->path;\n      u += \"\\\",\\n\";\n      u += \" \\\"file_size\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->csize);\n      u += \",\\n\";\n      u += \" \\\"read_average\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring,\n           report->rb / ((report->nrc) ? report->nrc : 999999999));\n      u += \",\\n\";\n      u += \" \\\"read_bytes_at_close\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb);\n      u += \",\\n\";\n      u += \" \\\"read_bytes\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb);\n      u += \",\\n\";\n      u += \" \\\"read_max\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb_max);\n      u += \",\\n\";\n      u += \" \\\"read_min\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb_min);\n      u += \",\\n\";\n      u += \" \\\"read_operations\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->nrc);\n      u += \",\\n\";\n      snprintf(fs, sizeof(fs) - 1, \"%.02f\", report->rb_sigma);\n      u += \" \\\"read_sigma\\\": \";\n      u += fs;\n      u += \",\\n\";\n      /* -- we have currently no access to this information */\n      /*\n      u += \" \\\"read_single_average\\\": \";  u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_single_bytes\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb); u += \",\\n\";\n      u += \" \\\"read_single_max\\\": \";      u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_single_min\\\": \";      u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_single_operations\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->nrc); u += \",\\n\";\n      u += \" \\\"read_single_sigma\\\": \";    u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_average\\\": \";  u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_bytes\\\": \";    u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_count_average\\\": \"; u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_count_max\\\": \";u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_count_min\\\": \";u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_count_sigma\\\": \";   u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_max\\\": \";      u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_min\\\": \";      u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_operations\\\": \"; u += \"0\"; u += \",\\n\";\n      u += \" \\\"read_vector_sigma\\\": \";    u += \"0\"; u += \",\\n\"; */\n      u += \" \\\"server_domain\\\": \\\"\";\n      u += report->server_domain;\n      u += \"\\\",\\n\";\n      u += \" \\\"server_host\\\": \\\"\";\n      u += report->server_name;\n      u += \"\\\",\\n\";\n      u += \" \\\"server_username\\\": \\\"\";\n      u += report->sec_name;\n      u += \"\\\",\\n\";\n      u += \" \\\"start_time\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->ots);\n      u += \",\\n\";\n      XrdOucString stime; // stores the current time in <s>.<ns>\n      u += \" \\\"unique_id\\\": \\\"\";\n      u += gOFS->MgmOfsInstanceName.c_str();\n      u += \"-\";\n      u += eos::common::StringConversion::TimeNowAsString(stime);\n      u += \"\\\",\\n\";\n      u += \" \\\"user_dn\\\": \\\"\";\n      u += report->sec_info;\n      u += \"\\\",\\n\";\n      u += \" \\\"user_fqan\\\": \\\"\";\n      u += report->sec_grps;\n      u += \"\\\",\\n\";\n      u += \" \\\"user_role\\\": \\\"\";\n      u += report->sec_role;\n      u += \"\\\",\\n\";\n      u += \" \\\"user_vo\\\": \\\"\";\n      u += report->sec_vorg;\n      u += \"\\\",\\n\";\n      u += \" \\\"write_average\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring,\n           report->wb / ((report->nwc) ? report->nwc : 999999999));\n      u += \",\\n\";\n      u += \" \\\"write_bytes_at_close\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->wb);\n      u += \",\\n\";\n      u += \" \\\"write_bytes\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->wb);\n      u += \",\\n\";\n      u += \" \\\"write_max\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->wb_max);\n      u += \",\\n\";\n      u += \" \\\"write_min\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->wb_min);\n      u += \",\\n\";\n      u += \" \\\"write_operations\\\": \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->nwc);\n      u += \",\\n\";\n      snprintf(fs, sizeof(fs) - 1, \"%.02f\", report->wb_sigma);\n      u += \" \\\"write_sigma\\\": \";\n      u += fs;\n      u += \"}\\n\";\n    } else {\n      // do default format broadcast\n      u += \"#begin\\n\";\n      u += \"app_info=\";\n      u += report->sec_app;\n      u += \"\\n\";\n      u += \"client_domain=\";\n      u += report->sec_domain;\n      u += \"\\n\";\n      u += \"client_host=\";\n      u += report->sec_host;\n      u += \"\\n\";\n      u += \"end_time=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->cts);\n      u += \"\\n\";\n      u += \"file_lfn = \";\n      u += report->path;\n      u += \"\\n\";\n      u += \"file_size = \";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->csize);\n      u += \"\\n\";\n      u += \"read_average=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring,\n           report->rb / ((report->nrc) ? report->nrc : 999999999));\n      u += \"\\n\";\n      u += \"read_bytes_at_close=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb);\n      u += \"\\n\";\n      u += \"read_bytes=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb);\n      u += \"\\n\";\n      u += \"read_min=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb_min);\n      u += \"\\n\";\n      u += \"read_max=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->rb_max);\n      u += \"\\n\";\n      u += \"read_operations=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->nrc);\n      u += \"\\n\";\n      u += \"read_sigma=\";\n      u += \"0\";\n      u += \"\\n\";\n      snprintf(fs, sizeof(fs) - 1, \"%.02f\", report->rb_sigma);\n      u += \"read_sigma=\";\n      u += fs;\n      u += \"\\n\";\n      /* -- we have currently no access to this information */\n      /* u += \"read_single_average=\";  u += \"0\"; u += \"\\n\";\n      u += \"read_single_bytes=\";    u += eos::common::StringConversion::GetSizeString(sizestring, report->rb); u += \"\\n\";\n      u += \"read_single_max=\";      u += \"0\"; u += \"\\n\";\n      u += \"read_single_min=\";      u += \"0\"; u += \"\\n\";\n      u += \"read_single_operations=\";    u += eos::common::StringConversion::GetSizeString(sizestring, report->nrc); u += \"\\n\";\n      u += \"read_single_sigma=\";    u += \"0\"; u += \"\\n\";\n      u += \"read_vector_average=\";  u += \"0\"; u += \"\\n\";\n      u += \"read_vector_bytes=\";    u += \"0\"; u += \"\\n\";\n      u += \"read_vector_count_average=\"; u += \"0\"; u += \"\\n\";\n      u += \"read_vector_count_max=\";u += \"0\"; u += \"\\n\";\n      u += \"read_vector_count_min=\";u += \"0\"; u += \"\\n\";\n      u += \"read_vector_count_sigma=\";   u += \"0\"; u += \"\\n\";\n      u += \"read_vector_max=\";      u += \"0\"; u += \"\\n\";\n      u += \"read_vector_min=\";      u += \"0\"; u += \"\\n\";\n      u += \"read_vector_operations=\"; u += \"0\"; u += \"\\n\";\n      u += \"read_vector_sigma=\";    u += \"0\"; u += \"\\n\";*/\n      u += \"server_domain=\";\n      u += report->server_domain;\n      u += \"\\n\";\n      u += \"server_host=\";\n      u += report->server_name;\n      u += \"\\n\";\n      u += \"server_username=\";\n      u += report->sec_name;\n      u += \"\\n\";\n      u += \"start_time=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->ots);\n      u += \"\\n\";\n      XrdOucString stime; // stores the current time in <s>.<ns>\n      u += \"unique_id=\";\n      u += gOFS->MgmOfsInstanceName.c_str();\n      u += \"-\";\n      u += eos::common::StringConversion::TimeNowAsString(stime);\n      u += \"\\n\";\n      u += \"user_dn = \";\n      u += report->sec_info;\n      u += \"\\n\";\n      u += \"user_fqan=\";\n      u += report->sec_grps;\n      u += \"\\n\";\n      u += \"user_role=\";\n      u += report->sec_role;\n      u += \"\\n\";\n      u += \"user_vo=\";\n      u += report->sec_vorg;\n      u += \"\\n\";\n      u += \"write_average=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring,\n           report->wb / ((report->nwc) ? report->nwc : 999999999));\n      u += \"\\n\";\n      u += \"write_bytes_at_close=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->wb);\n      u += \"\\n\";\n      u += \"write_bytes=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->wb);\n      u += \"\\n\";\n      u += \"write_min=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->wb_min);\n      u += \"\\n\";\n      u += \"write_max=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->wb_max);\n      u += \"\\n\";\n      u += \"write_operations=\";\n      u += eos::common::StringConversion::GetSizeString(sizestring, report->nwc);\n      u += \"\\n\";\n      snprintf(fs, sizeof(fs) - 1, \"%.02f\", report->rb_sigma);\n      u += \"write_sigma=\";\n      u += fs;\n      u += \"\\n\";\n      u += \"#end\\n\";\n    }\n\n    int sendretc = sendto(mUdpSocket.at(*it), u.c_str(), u.length(), 0,\n                          (struct sockaddr*) &mUdpSockAddr.at(*it),\n                          sizeof(struct sockaddr_in));\n\n    if (sendretc < 0) {\n      eos_static_err(\"msg=\\\"failed to send udp message to %s\\\"\", it->c_str());\n    }\n\n    eos_static_debug(\"sendto_retc=%d\", sendretc);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Add entry to popularity statistics\n//------------------------------------------------------------------------------\nvoid\nIostat::AddToPopularity(const std::string& path, unsigned long long rb,\n                        time_t start, time_t stop)\n{\n  size_t popularitybin = (((start + stop) / 2) % (IOSTAT_POPULARITY_DAY *\n                          IOSTAT_POPULARITY_HISTORY_DAYS)) / IOSTAT_POPULARITY_DAY;\n  eos::common::Path cPath(path.c_str());\n  std::unique_lock<std::mutex> scope_lock(mPopularityMutex);\n\n  for (size_t k = 0; k < cPath.GetSubPathSize(); ++k) {\n    std::string sp = cPath.GetSubPath(k);\n    IostatPopularity[popularitybin][sp].rb += rb;\n    IostatPopularity[popularitybin][sp].nread++;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Create hash map key string from the given information\n//------------------------------------------------------------------------------\nstd::string\nIostat::EncodeKey(const std::string& id_type, const std::string& id_val,\n                  const std::string& tag)\n{\n  return SSTR(\"idt=\" << id_type << \"&id=\" << id_val << \"&tag=\" << tag);\n}\n\n//------------------------------------------------------------------------------\n// Decode/parse hash map key to extract entry information\n//------------------------------------------------------------------------------\nbool\nIostat::DecodeKey(const std::string& key, std::string& id_type,\n                  std::string& id_val, std::string& tag)\n{\n  std::vector<std::string> tokens {};\n  eos::common::StringConversion::Tokenize(key, tokens, \"&\");\n\n  for (const auto& token : tokens) {\n    std::vector<std::string> kv {};\n    eos::common::StringConversion::Tokenize(token, kv, \"=\");\n\n    if (kv.size() != 2) {\n      eos_static_err(\"msg=\\\"unexpected token format\\\" token=\\\"%s\\\"\",\n                     token.c_str());\n      return false;\n    }\n\n    if (kv[0] == \"idt\") {\n      id_type = kv[1];\n    } else if (kv[0] == \"id\") {\n      id_val = kv[1];\n    } else if (kv[0] == \"tag\") {\n      tag = kv[1];\n    } else {\n      eos_static_err(\"msg=\\\"unexpected key format\\\" key=\\\"%s\\\"\", kv[0].c_str());\n      return false;\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Load Iostat information from Qdb backend\n//------------------------------------------------------------------------------\nbool\nIostat::LoadFromQdb()\n{\n  std::string key = GetHashKey();\n  eos_static_info(\"msg=\\\"loading iostat info from Qdb\\\" hash_map=\\\"%s\\\"\",\n                  key.c_str());\n  qclient::redisReplyPtr reply;\n\n  try {\n    reply = mQcl->exec(\"HGETALL\", key).get();\n  } catch (const std::exception& e) {\n    eos_static_err(\"msg=\\\"failed getting entries from Qdb\\\", emsg=\\\"%s\\\"\",\n                   e.what());\n    return true;\n  }\n\n  qclient::HgetallParser mQdbRespParser(reply);\n\n  if (!mQdbRespParser.ok()) {\n    eos_static_err(\"%s\", \"msg=\\\"failed parsing reply from Qdb\\n\");\n    return false;\n  }\n\n  int id = 0;\n  unsigned long long val = 0ull;\n  std::string id_type, id_val, tag;\n  std::map<std::string, std::string> stored_iostat = mQdbRespParser.value();\n  std::unique_lock<std::mutex> scope_lock(mDataMutex);\n  // Clean up the memory data structures\n  IostatUid.clear();\n  IostatUid.resize(0);\n  IostatTag.clear();\n  IostatTag.resize(0);\n  IostatGid.clear();\n  IostatGid.resize(0);\n\n  for (const auto& pair : stored_iostat) {\n    if (!DecodeKey(pair.first, id_type, id_val, tag)) {\n      continue;\n    }\n\n    // Convert entries from string to numeric\n    try {\n      id = std::stoi(id_val);\n      val = std::stoull(pair.second);\n    } catch (...) {\n      eos_static_err(\"msg=\\\"failed converting to numeric format\\\" key=\\\"%s\\\" \"\n                     \"val=\\\"%s\\\"\", pair.first.c_str(), pair.second.c_str());\n      continue;\n    }\n\n    if (id_type == USER_ID_TYPE) {\n      IostatUid[tag][id] = val;\n\n      if (!IostatTag.count(tag)) {\n        IostatTag[tag] = val;\n      } else {\n        IostatTag[tag] += val;\n      }\n    } else if (id_type == GROUP_ID_TYPE) {\n      IostatGid[tag][id] = val;\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Store statistics in legacy file format\n//------------------------------------------------------------------------------\nbool\nIostat::LegacyStoreInFile()\n{\n  if (mLegacyFilePath.empty()) {\n    return false;\n  }\n\n  XrdOucString tmpname = mLegacyFilePath.c_str();\n  tmpname += \".tmp\";\n  FILE* fout = fopen(tmpname.c_str(), \"w+\");\n\n  if (!fout) {\n    return false;\n  }\n\n  if (chmod(tmpname.c_str(), S_IRWXU | S_IRGRP | S_IROTH)) {\n    fclose(fout);\n    return false;\n  }\n\n  std::unique_lock<std::mutex> scope_lock(mDataMutex);\n\n  // Store user counters\n  for (auto tuit = IostatUid.begin(); tuit != IostatUid.end(); tuit++) {\n    for (auto it = tuit->second.begin(); it != tuit->second.end(); ++it) {\n      fprintf(fout, \"tag=%s&uid=%u&val=%llu\\n\", tuit->first.c_str(), it->first,\n              (unsigned long long)it->second);\n    }\n  }\n\n  // Store group counter\n  for (auto tgit = IostatGid.begin(); tgit != IostatGid.end(); tgit++) {\n    for (auto it = tgit->second.begin(); it != tgit->second.end(); ++it) {\n      fprintf(fout, \"tag=%s&gid=%u&val=%llu\\n\", tgit->first.c_str(), it->first,\n              (unsigned long long)it->second);\n    }\n  }\n\n  fclose(fout);\n  return (rename(tmpname.c_str(), mLegacyFilePath.c_str()) == 0);\n}\n\n//------------------------------------------------------------------------------\n// Restore statistics from legacy file format\n//------------------------------------------------------------------------------\nbool\nIostat::LegacyRestoreFromFile()\n{\n  if (mLegacyFilePath.empty()) {\n    return false;\n  }\n\n  FILE* fin = fopen(mLegacyFilePath.c_str(), \"r\");\n\n  if (!fin) {\n    return true;\n  }\n\n  int item = 0;\n  char line[16384];\n  std::unique_lock<std::mutex> scope_lock(mDataMutex);\n\n  while ((item = fscanf(fin, \"%16383s\\n\", line)) == 1) {\n    XrdOucEnv env(line);\n\n    if (env.Get(\"tag\") && env.Get(\"uid\") && env.Get(\"val\")) {\n      std::string tag = env.Get(\"tag\");\n      uid_t uid = atoi(env.Get(\"uid\"));\n      unsigned long long val = strtoull(env.Get(\"val\"), 0, 10);\n      IostatUid[tag][uid] = val;\n\n      if (!IostatTag.count(tag)) {\n        IostatTag[tag] = val;\n      } else {\n        IostatTag[tag] += val;\n      }\n    }\n\n    if (env.Get(\"tag\") && env.Get(\"gid\") && env.Get(\"val\")) {\n      std::string tag = env.Get(\"tag\");\n      gid_t gid = atoi(env.Get(\"gid\"));\n      unsigned long long val = strtoull(env.Get(\"val\"), 0, 10);\n      IostatGid[tag][gid] = val;\n    }\n  }\n\n  fclose(fin);\n  return true;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/iostat/Iostat.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Iostat.hh\n//! @authors Andreas-Joachim Peters/Jaroslav Guenther - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/AssistedThread.hh\"\n#include \"common/StringConversion.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"namespace/ns_quarkdb/qclient/include/qclient/QClient.hh\"\n#include \"namespace/ns_quarkdb/qclient/include/qclient/structures/QHash.hh\"\n#include <arpa/inet.h>\n#include <atomic>\n#include <google/sparse_hash_map>\n#include <netinet/in.h>\n#include <set>\n#include <string>\n#include <sys/socket.h>\n#include <sys/types.h>\n\nnamespace eos\n{\nclass MetadataFlusher;\nnamespace common\n{\nclass Report;\n}\n} // namespace eos\n\nEOSMGMNAMESPACE_BEGIN\n\n//! Define the history in days we want to do popularity tracking\n#define IOSTAT_POPULARITY_HISTORY_DAYS 7\n#define IOSTAT_POPULARITY_DAY 86400\n\n//! Enumeration class for the 4 periods for which stats are collected\nenum class Period {DAY, HOUR, FIVEMIN, ONEMIN};\nenum class PercentComplete {p90, p95, p99, p100};\n\n//------------------------------------------------------------------------------\n//! Class IostatPeriods holds read/write stats for the past 24h\n//------------------------------------------------------------------------------\nclass IostatPeriods\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  IostatPeriods()\n  {\n    memset(mDataBuffer, 0, sizeof(mDataBuffer));\n    memset(mIntegralBuffer, 0, sizeof(mIntegralBuffer));\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~IostatPeriods() = default;\n\n  //----------------------------------------------------------------------------\n  //! Add measurement to the various periods it overlaps with\n  //!\n  //! @param val measured value\n  //! @param start start time of the measurement\n  //! @param stop stop time of the measurement\n  //! @param now current timestamp\n  //----------------------------------------------------------------------------\n  void Add(unsigned long long val, time_t start, time_t stop,\n           time_t now);\n\n  //------------------------------------------------------------------------------\n  //! Reset bin content of the buffer w.r.t. given timstamp\n  //------------------------------------------------------------------------------\n  void StampBufferZero(time_t& now);\n\n  //------------------------------------------------------------------------------\n  //! Get the sum of values for the given buffer period\n  //------------------------------------------------------------------------------\n  unsigned long long GetDataInPeriod(size_t period,\n                                     unsigned long long time_offset,\n                                     time_t now) const;\n\n  //------------------------------------------------------------------------------\n  //! Get longest transfer time in past 24h\n  //------------------------------------------------------------------------------\n  inline unsigned long long GetLongestTransferTime() const\n  {\n    return mLongestTransferTimeInSample;\n  }\n\n  //------------------------------------------------------------------------------\n  //! Get longest transfer report time (time it took to FST report to arrive at\n  //! MGM) in past 24h\n  //------------------------------------------------------------------------------\n  unsigned long long GetLongestReportTime() const\n  {\n    return mLongestReportTimeInSample;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return time to completion of transfer of 90/95/99/100% of data for\n  //! transfers seen during sample time [mLastTfSampleUpdateInterval]\n  //------------------------------------------------------------------------------\n  inline unsigned long long GetTimeToPercComplete(PercentComplete perc) const\n  {\n    return (unsigned long long)mDurationToPercComplete[(int)perc];\n  }\n\n  //------------------------------------------------------------------------------\n  //! Return average transfer size seen during sample time\n  //! [mLastTfSampleUpdateInterval]\n  //------------------------------------------------------------------------------\n  inline unsigned long long GetAvgTransferSize() const\n  {\n    return mAvgTfSize;\n  }\n\n  //------------------------------------------------------------------------------\n  //! Return number of transfers seen during sample time\n  //! [mLastTfSampleUpdateInterval]\n  //------------------------------------------------------------------------------\n  inline unsigned long long GetTfCountInSample() const\n  {\n    return mTfCountInSample;\n  }\n\n  //------------------------------------------------------------------------------\n  //! Return total IostatPeriod sum\n  //------------------------------------------------------------------------------\n  inline unsigned long long GetTotalSum() const\n  {\n    return mTotal;\n  }\n\n  //------------------------------------------------------------------------------\n  //! Getting the timestamp of the last time the transfer sample was taken\n  //------------------------------------------------------------------------------\n  std::string GetLastSampleUpdateTimestamp(bool date_format = false) const;\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  unsigned long long mTotal = 0ull;\n  // If sBinWidth !=1 please beware of the trannsfer start and stop bins getting\n  // the right transfer volume and add code block currently commented out starting\n  // from line 199\n  static constexpr size_t sBinWidth = 1;\n  static constexpr int sBins = 86400;\n  //! Number of seconds the sBins correspond to\n  static constexpr int sPeriod = sBins * sBinWidth;\n  time_t mLastAddTime = 0;\n  time_t mLastStampZeroTime = 0;\n  // even if you wait for longest transfer time - you still do not know if the longest\n  // How much data was transferred during ibin = mDataBuffer[ibin]\n  double mDataBuffer[sBins];\n  // what we can measure is choosing a period of time [sLastTfMaxLenUpdateRate] `\n  // for collecting newly finished transfers, distribute these tf into bins,\n  // --> calculate bin/sumall per bin --> integrate bins until reaching\n  // e.g. 99% of the data transferred --> the number of bins give us duration it too to get all data\n  // through the network in the last e.g. 5 min [sLastTfMaxLenUpdateRate] `\n  const double mPercComplete[4] {0.90, 0.95, 0.99, 1.0};\n  double mIntegralBuffer[sBins];\n  // Udate rate every 5 minutes\n  const time_t mLastTfSampleUpdateInterval = 300;\n  time_t mLastTfMaxLenUpdateTime = 0;\n  // Average transfer size in last 5 min [sLastTfMaxLenUpdateRate]\n  unsigned long long mAvgTfSize = 0;\n  unsigned long long mDurationToPercComplete[4] {0, 0, 0, 0};\n// Transfer count\n  unsigned long long mTfCount = 0;\n  // Transfer length is not longer because there is longer transfer in the pipe !\n  unsigned long long mLongestTransferTime = 0;\n  // Monitor how long it took to the transfer report to get to the MGM\n  unsigned long long mLongestReportTime = 0;\n  // The next 3 variables mean the same as the last 3 above, but these are\n  // to be exposed to the user, evaluated every [mLastTfSampleUpdateInterval]\n  unsigned long long mTfCountInSample = 0;\n  unsigned long long mLongestTransferTimeInSample = 0;\n  unsigned long long mLongestReportTimeInSample = 0;\n\n  //------------------------------------------------------------------------------\n  //! Update Transfer Buffer to iterate over and calculate how long does it take\n  //! to transfer [mPercComplete] % of the data\n  //!\n  //! @param now current timestamp\n  //------------------------------------------------------------------------------\n  void UpdateTransferSampleInfo(time_t now);\n\n};\n\n//------------------------------------------------------------------------------\n//! Iostat subscribes to MQ, collects and digests report messages\n//------------------------------------------------------------------------------\nclass Iostat: public eos::common::LogId\n{\npublic:\n  //! Configuration keys used in config key-val store\n  static const char* gIostatCollect;\n  static const char* gIostatReportSave;\n  static const char* gIostatReportNamespace;\n  static const char* gIostatPopularity;\n  static const char* gIostatUdpTargetList;\n  static FILE* gOpenReportFD;\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  Iostat();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~Iostat();\n\n  //----------------------------------------------------------------------------\n  //! Perform object initialization\n  //!\n  //! @param instance_name used to build the hash map key to be stored in QDB\n  //! @param port instance port\n  //! @param legacy_file path legacy iostat file path location\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool Init(const std::string& instance_name, int port,\n            const std::string& legacy_file);\n\n  //----------------------------------------------------------------------------\n  //! Apply instance level configuration concerning IoStats\n  //!\n  //! @param fsview pointer to FsView object\n  //----------------------------------------------------------------------------\n  void ApplyConfig(FsView* fsview);\n\n  //----------------------------------------------------------------------------\n  //! Store IoStat config in the instance level configuration\n  //!\n  //! @param fsview pointer to FsView object\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool StoreIostatConfig(FsView* fsview) const;\n\n  //----------------------------------------------------------------------------\n  //! Method executed by the thread receiving reports\n  //!\n  //! @param assistant reference to thread object\n  //----------------------------------------------------------------------------\n  void Receive(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Method executed by the thread ciruclating the entires\n  //!\n  //! @param assistant reference to thread object\n  //----------------------------------------------------------------------------\n  void Circulate(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Start collection thread\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool StartCollection();\n\n  //----------------------------------------------------------------------------\n  //! Stop collection thread\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool StopCollection();\n\n  //----------------------------------------------------------------------------\n  //! Start popularity thread\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool StartPopularity();\n\n  //----------------------------------------------------------------------------\n  //! Stop popularity thread\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool StopPopularity();\n\n  //----------------------------------------------------------------------------\n  //! Start daily report thread\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool StartReport();\n\n  //----------------------------------------------------------------------------\n  //! Stop daily report thread\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool StopReport();\n\n  //----------------------------------------------------------------------------\n  //! Start namespace report thread\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool StartReportNamespace();\n\n  //----------------------------------------------------------------------------\n  //! Stop namespace report thread\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool StopReportNamespace();\n\n  //----------------------------------------------------------------------------\n  //! Add UDP target\n  //!\n  //! @param target new UDP target\n  //! @param store_and_lock if true store new target in config\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool AddUdpTarget(const std::string& target, bool store_and_lock = true);\n\n  //----------------------------------------------------------------------------\n  //! Remove UDP target\n  //!\n  //! @param target UDP target to be removed\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool RemoveUdpTarget(const std::string& target);\n\n  //----------------------------------------------------------------------------\n  //! Write record to the stream - used by the MGM to push entries\n  //!\n  //! @param report report entry\n  //----------------------------------------------------------------------------\n  void WriteRecord(const std::string& record);\n\n  //------------------------------------------------------------------------------\n  //! Print IO statistics\n  //------------------------------------------------------------------------------\n  void PrintOut(XrdOucString& out, bool summary, bool details, bool monitoring,\n                bool numerical = false, bool top = false, bool domain = false,\n                bool apps = false, bool sample_stat = false, time_t time_ago = 0,\n                time_t time_interval = 0, XrdOucString option = \"\");\n\n  //----------------------------------------------------------------------------\n  //! Compute and print out the namespace popularity ranking\n  //!\n  //! @param out output string\n  //! @param option fileter options\n  //----------------------------------------------------------------------------\n  void PrintNsPopularity(XrdOucString& out, XrdOucString option = \"\") const;\n\n  //----------------------------------------------------------------------------\n  //! Print namespace activity report for given path\n  //!\n  //! @param path namespace path\n  //! @param out output string\n  //----------------------------------------------------------------------------\n  void PrintNsReport(const char* path, XrdOucString& out) const;\n\n  //----------------------------------------------------------------------------\n  //! Record measurement to the various periods it overlaps with\n  //!\n  //! @param tag measurement info tag\n  //! @param uid user id\n  //! @param gid group id\n  //! @param val measurement value\n  //! @param start start timestamp of measurement\n  //! @param stop stop timestamp of measurement\n  //! @param now current timestamp\n  //----------------------------------------------------------------------------\n  void Add(const std::string& tag, uid_t uid, gid_t gid, unsigned long long val,\n           time_t start, time_t stop, time_t now);\n\n  //----------------------------------------------------------------------------\n  //! Get sum of measurements for the given tag (looping all uids per tag)\n  //! @note: needs a lock on the mDataMutex\n  //!\n  //! @param tag measurement info tag\n  //!\n  //! @return total value\n  //----------------------------------------------------------------------------\n  unsigned long long GetTotalStatForTag(const char* tag) const;\n\n  //----------------------------------------------------------------------------\n  //! Get sum of measurements for the given tag (looping all uids per tag) and period\n  //! @note: needs a lock on the mDataMutex\n  //!\n  //! @param tag measurement info tag\n  //! @parma period time interval of interest\n  //!\n  //! @return total value\n  //----------------------------------------------------------------------------\n  unsigned long long GetPeriodStatForTag(const char* tag, size_t period,\n                                         time_t secago = 0) const;\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  inline static const std::string USER_ID_TYPE = \"u\";\n  inline static const std::string GROUP_ID_TYPE = \"g\";\n  ///< Max delay for cache in front of QDB\n  static constexpr std::chrono::seconds mCacheFlushDelay {30};\n  //! Max cache size before flush - 30 entries per uid/gid pair times 100 users\n  static constexpr unsigned int mMapMaxSize {3000};\n\n  google::sparse_hash_map<std::string, unsigned long long> IostatTag;\n  google::sparse_hash_map<std::string, IostatPeriods> IostatPeriodsTag;\n\n  google::sparse_hash_map<std::string,\n         google::sparse_hash_map<uid_t, unsigned long long>> IostatUid;\n  google::sparse_hash_map<std::string,\n         google::sparse_hash_map<gid_t, unsigned long long>> IostatGid;\n  google::sparse_hash_map<std::string,\n         google::sparse_hash_map<uid_t, IostatPeriods>> IostatPeriodsUid;\n  google::sparse_hash_map<std::string,\n         google::sparse_hash_map<gid_t, IostatPeriods>> IostatPeriodsGid;\n  google::sparse_hash_map<std::string, IostatPeriods> IostatPeriodsDomainIOrb;\n  google::sparse_hash_map<std::string, IostatPeriods> IostatPeriodsDomainIOwb;\n  google::sparse_hash_map<std::string, IostatPeriods> IostatPeriodsAppIOrb;\n  google::sparse_hash_map<std::string, IostatPeriods> IostatPeriodsAppIOwb;\n  std::atomic<bool> mDoneInit;\n  //! Flusher to QDB backend\n  std::unique_ptr<eos::MetadataFlusher> mFlusher;\n  std::string mFlusherPath;\n  //! Mutex protecting the above data structures\n  std::mutex mDataMutex;\n  //! If true then use the file based approach otherwise store info in QDB\n  std::atomic<bool> mLegacyMode;\n  //! File path where statistics are stored on disk\n  std::string mLegacyFilePath;\n  std::atomic<bool> mRunning;\n  //! Internal QClient object\n  std::unique_ptr<qclient::QClient> mQcl;\n  //! Flag to store reports in the local report store\n  std::atomic<bool> mReportSave;\n  //! Flag if we should fill the report namespace\n  std::atomic<bool> mReportNamespace;\n  //! Flag if we fill the popularity maps (protected by this::Mutex)\n  std::atomic<bool> mReportPopularity;\n  //! QuarkDB hash map key name where info is saved\n  std::string mHashKeyBase;\n  //! Map of cached IoStat updates\n  std::map<std::string, unsigned long long> mMapCacheUpdates;\n  std::mutex mThreadSyncMutex; ///< Mutex serializing thread(s) start/stop\n  AssistedThread mReceivingThread; ///< Looping thread receiving reports\n  AssistedThread mCirculateThread; ///< Looping thread circulating report\n  //! Mutex protecting the UDP broadcast data structures that follow\n  mutable std::mutex mBcastMutex;\n  //! Destinations for udp popularity packets\n  std::set<std::string> mUdpPopularityTarget;\n  //! Socket to the udp destination(s)\n  std::map<std::string, int> mUdpSocket;\n  //! Socket address structure to be reused for messages\n  std::map<std::string, struct sockaddr_in> mUdpSockAddr;\n  //! Mutex protecting the popularity data structures\n  mutable std::mutex mPopularityMutex;\n  //! Popularity data structure\n  struct Popularity {\n    unsigned int nread;\n    unsigned long long rb;\n  };\n\n  //! Points to the bin which was last used in IostatPopularity\n  std::atomic<size_t> mLastPopularityBin;\n  google::sparse_hash_map<std::string, struct Popularity>\n    IostatPopularity[IOSTAT_POPULARITY_HISTORY_DAYS];\n  typedef std::pair<std::string, struct Popularity> popularity_t;\n\n  //----------------------------------------------------------------------------\n  //! Value comparator for number of reads\n  //----------------------------------------------------------------------------\n  struct PopularityCmp_nread {\n    bool operator()(popularity_t const& l, popularity_t const& r)\n    {\n      if (l.second.nread == r.second.nread) {\n        return (l.first < r.first);\n      }\n\n      return l.second.nread > r.second.nread;\n    }\n  };\n\n  //---------------------------------------------------------------------------\n  //! Value comparator for read bytes\n  //----------------------------------------------------------------------------\n  struct PopularityCmp_rb {\n    bool operator()(popularity_t const& l, popularity_t const& r)\n    {\n      if (l.second.rb == r.second.rb) {\n        return (l.first < r.first);\n      }\n\n      return l.second.rb > r.second.rb;\n    }\n  };\n\n  //----------------------------------------------------------------------------\n  //! Record measurements directly in QDB\n  //!\n  //! @param tag measurement info tag\n  //! @param uid user id\n  //! @param gid group id\n  //! @param val measurement value\n  //----------------------------------------------------------------------------\n  void AddToQdb(const std::string& tag, uid_t uid, gid_t gid,\n                unsigned long long val);\n\n  //----------------------------------------------------------------------------\n  //! Do the UDP broadcast\n  //!\n  //! @param report pointer to report object\n  //----------------------------------------------------------------------------\n  void UdpBroadCast(eos::common::Report* report) const;\n\n  //----------------------------------------------------------------------------\n  //! Encode the UDP popularity targets to a string using the provided separator\n  //!\n  //! @param separator separator for the encoding\n  //!\n  //! @return encoded list of UDP popularity targets\n  //----------------------------------------------------------------------------\n  std::string EncodeUdpPopularityTargets() const;\n\n  //----------------------------------------------------------------------------\n  //! Add entry to popularity statistics\n  //!\n  //! @param path entry path\n  //! @param rb read bytes\n  //! @param start start timestamp of the operation\n  //! @param stop stop timstamp of the operation\n  //----------------------------------------------------------------------------\n  void AddToPopularity(const std::string& path, unsigned long long rb,\n                       time_t start, time_t stop);\n\n  //----------------------------------------------------------------------------\n  //! One off migration from file based to QDB of IoStat information\n  //!\n  //! @param legacy_file file path for IoStat information\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool OneOffQdbMigration(const std::string& legacy_file);\n\n  //----------------------------------------------------------------------------\n  //! Create/encode hash map key string from the given information\n  //!\n  //! @param id_type type of id, can be either user USER_ID_TYPE\n  //!        or group GROUP_ID_TYPE\n  //! @param id_val numeric value of the id\n  //! @param tag type of tag eg. bytes_read, bytes_write etc.\n  //!\n  //! @param return string representing the key to be used for storing this\n  //!        info in the hash map\n  //----------------------------------------------------------------------------\n  static std::string EncodeKey(const std::string& id_type,\n                               const std::string& id_val,\n                               const std::string& tag);\n\n  //----------------------------------------------------------------------------\n  //! Decode/parse hash map key to extract entry information\n  //!\n  //! @param key hash map key obtained by calling EncodeKey\n  //! @param id_type type of id, can be either user USER_ID_TYPE\n  //!        or group GROUP_ID_TYPE\n  //! @param id_val numeric value of the id\n  //! @param tag type of tag eg. bytes_read, bytes_write etc.\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  static bool DecodeKey(const std::string& key, std::string& id_type,\n                        std::string& id_val, std::string& tag);\n\n  //----------------------------------------------------------------------------\n  //! Load info from Qdb backend clearing up any memory data structures\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool LoadFromQdb();\n\n  //----------------------------------------------------------------------------\n  //! Get hash key under which info is stored in QDB. This also included the\n  //! current year and it's cached for 5 minutes.\n  //----------------------------------------------------------------------------\n  std::string GetHashKey() const;\n\n  //----------------------------------------------------------------------------\n  //! Store statistics in legacy file format\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool LegacyStoreInFile();\n\n  //----------------------------------------------------------------------------\n  //! Restore statistics from legacy file format\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool LegacyRestoreFromFile();\n\n  //----------------------------------------------------------------------------\n  //! Save given update in the in-memory cache\n  //!\n  //! @param uid_key uid encoded key\n  //! @param gid_key gid encoded key\n  //! @param val value update\n  //----------------------------------------------------------------------------\n  void CacheUpdate(const std::string& uid_key, const std::string& gid_key,\n                   unsigned long long val);\n\n  //----------------------------------------------------------------------------\n  //! Check if the cache needs to be flushed\n  //!\n  //! @return true if cache must be flushed, otherwise false\n  //----------------------------------------------------------------------------\n  bool ShouldFlushCache();\n\n  //----------------------------------------------------------------------------\n  //! Flush all cached entries to the QDB backed\n  //----------------------------------------------------------------------------\n  void FlushCache();\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/lru/LRU.cc",
    "content": "//------------------------------------------------------------------------------\n// File: LRU.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/Mapping.hh\"\n#include \"common/RWMutex.hh\"\n#include \"common/ParseUtils.hh\"\n#include \"common/Path.hh\"\n#include \"common/IntervalStopwatch.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/lru/LRU.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/convert/ConverterEngine.hh\"\n#include \"mgm/convert/ConversionTag.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/interface/ContainerIterators.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"namespace/ns_quarkdb/explorer/NamespaceExplorer.hh\"\n#include \"namespace/ns_quarkdb/NamespaceGroup.hh\"\n#include <qclient/QClient.hh>\n\n//! Attribute name defining any LRU policy\nconst char* LRU::gLRUPolicyPrefix = \"sys.lru.*\";\n\nEOSMGMNAMESPACE_BEGIN\n\nusing namespace eos::common;\n\n//------------------------------------------------------------------------------\n// Start the LRU thread\n//------------------------------------------------------------------------------\nvoid LRU::Start()\n{\n  mThread.reset(&LRU::backgroundThread, this);\n}\n\n//------------------------------------------------------------------------------\n// Stop the LRU thread\n//------------------------------------------------------------------------------\nvoid LRU::Stop()\n{\n  mThread.join();\n}\n\n//------------------------------------------------------------------------------\n// Retrieve \"lru.interval\" configuration option as string, or empty if\n// cannot be found. Assumes gFsView.ViewMutex is at-least readlocked.\n//------------------------------------------------------------------------------\nstd::string LRU::getLRUIntervalConfig() const\n{\n  if (FsView::gFsView.mSpaceView.count(\"default\") == 0) {\n    return \"\";\n  }\n\n  return FsView::gFsView.mSpaceView[\"default\"]->GetConfigMember(\"lru.interval\");\n}\n\n//------------------------------------------------------------------------------\n// Retrieve current LRU configuration options\n//------------------------------------------------------------------------------\nLRU::Options LRU::getOptions()\n{\n  LRU::Options opts;\n  // Default options\n  opts.enabled = false;\n  opts.interval = std::chrono::minutes(30);\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n\n  if (FsView::gFsView.mSpaceView.count(\"default\") &&\n      (FsView::gFsView.mSpaceView[\"default\"]->GetConfigMember(\"lru\") == \"on\")) {\n    opts.enabled = true;\n  }\n\n  std::string interval = getLRUIntervalConfig();\n  int64_t intv = 0;\n\n  if (opts.enabled && (interval.empty() || !common::ParseInt64(interval, intv))) {\n    eos_static_crit(\"%s\", \"msg=\\\"unable to parse space config lru.interval \"\n                    \"option, disabling LRU!\\\"\");\n    opts.enabled = false;\n  } else {\n    opts.interval = std::chrono::seconds(intv);\n  }\n\n  if (opts.enabled) {\n    eos_static_info(\"msg=\\\"lru is enabled\\\" interval=%ds\", opts.interval.count());\n  }\n\n  // Set long interval in case LRU is de-activated, prevent the background\n  // thread from spinning\n  if (!opts.enabled || opts.interval == std::chrono::seconds(0)) {\n    opts.interval = std::chrono::minutes(30);\n  }\n\n  return opts;\n}\n\n//------------------------------------------------------------------------------\n// Constructor. To run the LRU thread, call Start\n//------------------------------------------------------------------------------\nLRU::LRU() :\n  mQcl(nullptr), mRootVid(eos::common::VirtualIdentity::Root()),\n  mRefresh(false)\n{\n}\n\n//------------------------------------------------------------------------------\n// Destructor - stop the background thread, if running\n//------------------------------------------------------------------------------\nLRU::~LRU()\n{\n  Stop();\n}\n\nbool\nLRU::extractTimeSizeCriterias(const std::string& input, time_t& age, ssize_t& size,\n                              std::ostringstream& errMsg)\n{\n  age = 0;\n  size = 0;\n\n  std::string time_tag;\n  std::string size_tag;\n  eos::common::StringConversion::SplitKeyValue(input, time_tag, size_tag);\n\n  if (time_tag.empty()) {\n    time_tag = input;\n  }\n\n  // Parse optional size constraint (e.g. \">1K\" or \"<1K\")\n  if (!size_tag.empty()) {\n    char prefix = size_tag.front();\n\n    if (prefix != '<' && prefix != '>') {\n      errMsg << \"msg=\\\"LRU match attribute has illegal size\\\" match=\\\"\" << input\n             << \"\\\", size=\\\"\" << size_tag << \"\\\"\";\n      return false;\n    }\n\n    size_tag.erase(0, 1);\n    auto size_limit = eos::common::StringConversion::GetSizeFromString(size_tag.c_str());\n\n    if (errno) {\n      errMsg << \"msg=\\\"LRU match attribute has illegal size (NaN)\\\" \"\n                \"match=\\\"\"\n             << input << \"\\\", size=\\\"\" << size_tag << \"\\\"\";\n      return false;\n    }\n\n    size = (prefix == '<') ? -static_cast<ssize_t>(size_limit)\n                           : static_cast<ssize_t>(size_limit);\n  }\n\n  eos_static_info(\"time-tag=%s size-tag=%s <%d >%d limit=%lu\", time_tag.c_str(),\n                  (size_tag.empty() ? \"none\" : size_tag.c_str()), size < 0, size > 0,\n                  static_cast<unsigned long>(std::abs(size)));\n\n  // Parse age\n  age = eos::common::StringConversion::GetSizeFromString(time_tag.c_str());\n\n  if (errno || !age) {\n    errMsg << \"msg=\\\"LRU match attribute has illegal age\\\" input= \\\"\" << input\n           << \"\\\", age=\\\"\" << age << \"\\\"\";\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Parse an \"sys.lru.expire.match\" policy\n//------------------------------------------------------------------------------\nbool LRU::parseExpireMatchPolicy(const std::string& policy,\n                                 std::map<std::string, time_t>& matchAgeMap)\n{\n  matchAgeMap.clear();\n  std::map<std::string, std::string> tmpMap;\n\n  if (!StringConversion::GetKeyValueMap(policy.c_str(), tmpMap, \":\")) {\n    // Failed splitting on \":\", cannot parse further\n    return false;\n  }\n\n  for (auto it = tmpMap.begin(); it != tmpMap.end(); it++) {\n    uint64_t out;\n\n    if (!StringConversion::GetSizeFromString(it->second, out)) {\n      eos_static_err(\"msg=\\\"LRU match attribute has illegal age\\\" \"\n                     \"match=\\\"%s\\\", age=\\\"%s\\\"\",\n                     it->first.c_str(),\n                     it->second.c_str());\n    } else {\n      matchAgeMap[it->first] = out;\n      eos_static_info(\"msg=\\\"add expire policy\\\" rule=\\\"%s %llu\\\"\",\n                      it->first.c_str(), out);\n    }\n  }\n\n  return true;\n}\n\nbool\nLRU::parseExpireSizeMatchPolicy(const std::string& policy, PolicyRules& policies,\n                                std::ostringstream& errMsg)\n{\n  policies.clear();\n\n  if (policy.empty()) {\n    errMsg << \"msg=\\\"Empty policy. policy format in sys.expire.match should be \"\n              \"namePattern:age or namePattern:age:size\\\"\";\n    return false;\n  }\n  const time_t now = time(nullptr);\n\n  std::vector<std::string> extractedPolicies;\n  StringConversion::Tokenize(policy, extractedPolicies, \",\");\n\n  for (const auto& policyEntry : extractedPolicies) {\n    std::map<std::string, std::string> keyValueMap;\n\n    if (!StringConversion::GetKeyValueMap(policyEntry.c_str(), keyValueMap, \":\")) {\n      errMsg << \"msg=\\\"wrong policy format in sys.expire.match, should be \"\n                \"namePattern:age or namePattern:age:size\\\" policy=\\\"\"\n             << policyEntry.c_str() << \"\\\"\";\n      return false;\n    }\n\n    const auto& [namePattern, criteria] = *keyValueMap.begin();\n\n    time_t age = 0;\n    ssize_t size = 0;\n    if (!LRU::extractTimeSizeCriterias(criteria, age, size, errMsg)) {\n      return false;\n    }\n\n    policies.emplace_back(namePattern, age, size, now);\n    const auto& policy = policies.back();\n    eos_static_info(\"msg=\\\"add size and age expire policy\\\" rule=\\\"%s %llu %s\\\"\",\n                    policy.m_regMatch.c_str(), policy.m_age,\n                    policy.getSizeCriteria().c_str());\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Perform a single LRU cycle, QDB namespace\n//------------------------------------------------------------------------------\nvoid LRU::performCycleQDB(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"LRUQDBCycle\");\n  eos_static_info(\"%s\", \"msg=\\\"start LRU scan on QDB\\\"\");\n  // Build exploration options..\n  ExplorationOptions opts;\n  opts.populateLinkedAttributes = true;\n  opts.view = gOFS->eosView;\n  opts.ignoreFiles = true;\n  opts.depthLimit = eos::common::Path::MAX_LEVELS;\n\n  // Initialize qclient..\n  if (!mQcl) {\n    mQcl.reset(new qclient::QClient(gOFS->mQdbContactDetails.members,\n                                    gOFS->mQdbContactDetails.constructOptions()));\n  }\n\n  // Start exploring\n  NamespaceExplorer\n  explorer(\"/\", opts, *(mQcl.get()),\n           static_cast<QuarkNamespaceGroup*>(gOFS->namespaceGroup.get())->getExecutor());\n  NamespaceItem item;\n  int64_t processed = 0;\n\n  while (explorer.fetch(item)) {\n    eos_static_debug(\"lru-dir-qdb=\\\"%s\\\" attrs=%d\", item.fullPath.c_str(),\n                     item.attrs.size());\n    processDirectory(item.fullPath, item.attrs);\n    processed++;\n\n    if (processed % 1000 == 0) {\n      eos_static_info(\"msg=\\\"LRU scan in progress\\\" num_scanned_dirs=%lli\",\n                      processed);\n\n      if (assistant.terminationRequested() || !gOFS->mMaster->IsMaster()) {\n        eos_static_info(\"%s\", \"msg=\\\"quit LRU due termination request \"\n                        \"or MGM running in slave mode\\\"\");\n        break;\n      }\n    }\n  }\n\n  eos_static_info(\"msg=\\\"LRU scan done\\\" num_scanned_dirs=%lli\", processed);\n}\n\n//------------------------------------------------------------------------------\n// LRU method doing the actual policy scrubbing\n//\n// This thread loops in regular intervals over all directories which have\n// a LRU policy attribute set (sys.lru.*) and applies the defined policy.\n//------------------------------------------------------------------------------\nvoid LRU::backgroundThread(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"LRUBackground\");\n  // Eternal thread doing LRU scans\n  eos_static_notice(\"%s\", \"msg=\\\"starting LRU thread\\\"\");\n  gOFS->WaitUntilNamespaceIsBooted(assistant);\n\n  // Wait that current MGM becomes a master\n  do {\n    eos_static_debug(\"%s\", \"msg=\\\"LRU waiting for master MGM\\\"\");\n    assistant.wait_for(std::chrono::seconds(10));\n  } while (!assistant.terminationRequested() && !gOFS->mMaster->IsMaster());\n\n  // run LRU scan every `interval` seconds but never faster than every 5 seconds\n  constexpr std::chrono::seconds minimumWaitTime{5};\n\n  while (!assistant.terminationRequested()) {\n    const auto [enabled, interval] = getOptions();\n    auto stopWatchInterval = interval;\n    if (stopWatchInterval < minimumWaitTime) {\n      eos_static_warning(\"msg=\\\"LRU scan interval is set to %lds which is less than minimum allowed %lds, setting to minimum!\\\"\",\n                              static_cast<long>(stopWatchInterval.count()),\n                              static_cast<long>(minimumWaitTime.count()));\n      stopWatchInterval = minimumWaitTime;\n    }\n    common::IntervalStopwatch stopwatch(stopWatchInterval);\n\n    // Only a master needs to run LRU\n    if (enabled && gOFS->mMaster->IsMaster()) {\n      performCycleQDB(assistant);\n    }\n\n    // Make sure we are waiting at least `minimumWaitTime` between cycles\n    while (stopwatch.timeRemainingInCycle() > std::chrono::milliseconds(0)) {\n      // Wait for the min wait time OR the remaining time, whichever is smaller\n\n      assistant.wait_for(\n        std::min<std::chrono::milliseconds>(minimumWaitTime, stopwatch.timeRemainingInCycle())\n      );\n\n      if (assistant.terminationRequested() || mRefresh) {\n        mRefresh = false;\n        break;\n      }\n    }\n  }\n\n  eos_static_notice(\"%s\", \"msg=\\\"stopped LRU thread\\\"\");\n}\n\n//------------------------------------------------------------------------------\n// Process the given directory, apply all policies\n//------------------------------------------------------------------------------\nvoid LRU::processDirectory(const std::string& dir,\n                           eos::IContainerMD::XAttrMap& map)\n{\n  // No LRU on \"/\"\n  if (dir == \"/\" || dir == \"\") {\n    return;\n  }\n\n  // Don't walk into the proc directory\n  if (dir.substr(0, gOFS->MgmProcPath.length()) == gOFS->MgmProcPath.c_str()) {\n    eos_static_debug(\"skipping proc tree %s\\n\", dir.c_str());\n    return;\n  }\n\n  // Sort out the individual LRU policies\n  if (map.count(\"sys.lru.expire.empty\")) {\n    // Remove empty directories older than <age>\n    AgeExpireEmpty(dir.c_str(), map[\"sys.lru.expire.empty\"]);\n  }\n\n  if (map.count(\"sys.lru.expire.match\")) {\n    // Files with a given match will be removed after expiration time\n    // a size policy can also be added\n    SizeAgeExpire(dir.c_str(), map[\"sys.lru.expire.match\"]);\n  }\n\n  if (map.count(\"sys.lru.lowwatermark\") && map.count(\"sys.lru.highwatermark\")) {\n    // If the space in this directory reaches highwatermark, files are\n    // cleaned up according to the LRU policy\n    CacheExpire(dir.c_str(), map[\"sys.lru.lowwatermark\"],\n                map[\"sys.lru.highwatermark\"]);\n  }\n\n  if (map.count(\"sys.lru.convert.match\")) {\n    // Files with a given match/age will be automatically converted\n    ConvertMatch(dir.c_str(), map);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Remove empty directories if they are older than age given in policy\n//------------------------------------------------------------------------------\nvoid\nLRU::AgeExpireEmpty(const char* dir, const std::string& policy)\n\n{\n  struct stat buf;\n  eos_static_debug(\"dir=%s\", dir);\n\n  if (!gOFS->_stat(dir, &buf, mError, mRootVid, \"\")) {\n    // check if there is any child in that directory\n    if (buf.st_blksize) {\n      eos_static_debug(\"dir=%s children=%d\", dir, buf.st_blksize);\n      return;\n    } else {\n      time_t now = time(NULL);\n      XrdOucString sage = policy.c_str();\n      time_t age = StringConversion::GetSizeFromString(sage);\n      eos_static_debug(\"ctime=%u age=%u now=%u\", buf.st_ctime, age, now);\n\n      if ((buf.st_ctime + age) < now) {\n        eos_static_notice(\"msg=\\\"delete empty directory\\\" path=\\\"%s\\\"\", dir);\n\n        if (gOFS->_remdir(dir, mError, mRootVid, \"\")) {\n          eos_static_err(\"msg=\\\"failed to delete empty directory\\\" \"\n                         \"path=\\\"%s\\\"\", dir);\n        }\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Remove all files in the directory older than the policy defines (size can be added)\n//------------------------------------------------------------------------------\nvoid\nLRU::SizeAgeExpire(const char* dir, const std::string& policy)\n{\n  eos_static_info(\n      \"msg=\\\"applying age and size deletion policy\\\" dir=\\\"%s\\\" policy=\\\"%s\\\"\", dir,\n      policy.c_str());\n  LRU::PolicyRules deletionPolicies;\n\n  std::ostringstream errMsg;\n  if (!parseExpireSizeMatchPolicy(policy, deletionPolicies, errMsg)) {\n    eos_static_err(\"LRU match attribute is illegal val=\\\"%s\\\" %s\", policy.c_str(),\n                   errMsg.str().c_str());\n    return;\n  }\n\n  time_t now = time(NULL);\n  std::vector<std::string> lDeleteList;\n  {\n    // Check the directory contents\n    std::shared_ptr<eos::IContainerMD> cmd;\n    eos::Prefetcher::prefetchContainerMDWithChildrenAndWait(gOFS->eosView, dir);\n\n    try {\n      cmd = gOFS->eosView->getContainer(dir);\n      std::string fullpath;\n      std::shared_ptr<eos::IFileMD> fmd;\n      XrdOucString fname;\n      eos::IFileMD::ctime_t fctime;\n      size_t fileSize = 0;\n\n      // Loop through all file names\n      for (auto it = eos::FileMapIterator(cmd); it.valid(); it.next()) {\n        // no need to lock the cmd\n        fmd = cmd->findFile(it.key());\n\n        if (fmd == nullptr) {\n          eos_static_err(\"msg=\\\"file is null\\\" fxid=%08llx\", it.key().c_str());\n          continue;\n        }\n\n        {\n          eos::MDLocking::FileReadLock fmdLock(fmd.get());\n          fname = fmd->getName().c_str();\n          fmd->getCTime(fctime);\n          fileSize = fmd->getSize();\n        }\n\n        fullpath = dir;\n        fullpath += fname.c_str();\n        eos_static_debug(\"check_file=\\\"%s\\\"\", fullpath.c_str());\n\n        // Loop over the match map\n        for (const LRU::PolicyRule& policy : deletionPolicies) {\n          eos_static_debug(\"check_rule=\\\"%s\\\" matches=%d\", policy.m_regMatch.c_str(),\n                           fname.matches(policy.m_regMatch.c_str()));\n          if (policy.matches(fname.c_str(), fctime.tv_sec, fileSize)) {\n            // This entry can be deleted\n            eos_static_notice(\"msg=\\\"delete expired file\\\" path=\\\"%s\\\" \"\n                              \"ctime=%u policy-age=%u age=%u policy-size=%s size=%u\",\n                              fullpath.c_str(), fctime.tv_sec, policy.m_age,\n                              now - fctime.tv_sec, policy.getSizeCriteria().c_str(),\n                              fileSize);\n            lDeleteList.push_back(fullpath);\n            break;\n          }\n        }\n      }\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      cmd = std::shared_ptr<eos::IContainerMD>((eos::IContainerMD*)0);\n      eos_static_err(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\",\n                     e.getErrno(), e.getMessage().str().c_str());\n    }\n  }\n\n  for (auto it = lDeleteList.begin(); it != lDeleteList.end(); it++) {\n    if (gOFS->_rem(it->c_str(), mError, mRootVid, \"\")) {\n      eos_static_err(\"msg=\\\"failed to expire file\\\" path=\\\"%s\\\"\", it->c_str());\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Expire the oldest files to go under the low watermark\n//------------------------------------------------------------------------------\nvoid\nLRU::CacheExpire(const char* dir, std::string& lowmark, std::string& highmark)\n\n{\n  eos_static_info(\"msg=\\\"applying volume deletion policy\\\" \"\n                  \"dir=\\\"%s\\\" low-mark=\\\"%s\\\" high-mark=\\\"%s\\\"\",\n                  dir, lowmark.c_str(), highmark.c_str());\n\n  // Update space quota, return if this is not a ns quota node\n  if (!Quota::UpdateFromNsQuota(dir, 0, 0)) {\n    return;\n  }\n\n  // Check for project quota\n  auto map_quotas = Quota::GetGroupStatistics(dir, Quota::gProjectId);\n  long long target_volume = map_quotas[SpaceQuota::kGroupLogicalBytesTarget];\n  long long is_volume = map_quotas[SpaceQuota::kGroupLogicalBytesIs];\n\n  if (target_volume <= 0) {\n    return;\n  }\n\n  errno = 0;\n  double lwm = strtod(lowmark.c_str(), 0);\n\n  if (!lwm || errno || (lwm >= 100)) {\n    eos_static_err(\"msg=\\\"low watermark value is illegal - \"\n                   \"must be 0 < lw < 100\\\" low-watermark=\\\"%s\\\"\",\n                   lowmark.c_str());\n    return;\n  }\n\n  errno = 0;\n  double hwm = strtod(highmark.c_str(), 0);\n\n  if (!hwm || errno || (hwm < lwm) || (hwm >= 100)) {\n    eos_static_err(\"msg = \\\"high watermark value is illegal - \"\n                   \"must be 0 < lw < hw < 100\\\" \"\n                   \"low_watermark=\\\"%s\\\" high-watermark=\\\"%s\\\"\",\n                   lowmark.c_str(), highmark.c_str());\n    return;\n  }\n\n  double cwm = 100.0 * is_volume / target_volume;\n  eos_static_debug(\"cwm=%.02f hwm=%.02f\", cwm, hwm);\n\n  // check if we have to do cache cleanup e.g. current is over high water mark\n  if (cwm < hwm) {\n    return;\n  }\n\n  unsigned long long bytes_to_free = is_volume - (lwm * target_volume / 100.0);\n  XrdOucString sizestring;\n  eos_static_notice(\"low-mark=%.02f high-mark=%.02f current-mark=%.02f \"\n                    \"deletion-bytes=%s\", lwm, hwm,  cwm,\n                    StringConversion::GetReadableSizeString(sizestring, bytes_to_free, \"B\"));\n  // Build the LRU list\n  std::map<std::string, std::set<std::string> > cachedirs;\n  XrdOucString stdErr;\n  time_t ms = 0;\n  // map with path/mtime pairs\n  std::set<lru_entry_t> lru_map;\n  unsigned long long lru_size = 0;\n\n  if (!gOFS->_find(dir, mError, stdErr, mRootVid, cachedirs, \"\", \"\", false, ms)) {\n    // Loop through the result and build an LRU list\n    // We just keep as many entries in the LRU list to have the required\n    // number of bytes to free available.\n    for (auto dit = cachedirs.begin(); dit != cachedirs.end(); dit++) {\n      eos_static_debug(\"path=%s\", dit->first.c_str());\n\n      for (auto fit = dit->second.begin(); fit != dit->second.end(); fit++) {\n        // build the full path name\n        std::string fpath = dit->first;\n        fpath += *fit;\n        struct stat buf;\n        eos_static_debug(\"path=%s\", fpath.c_str());\n\n        // get the current ctime & size information\n        if (!gOFS->_stat(fpath.c_str(), &buf, mError, mRootVid, \"\")) {\n          if (lru_map.size())\n            if ((lru_size > bytes_to_free) &&\n                lru_map.size() &&\n                ((--lru_map.end())->ctime < buf.st_ctime)) {\n              // this entry is newer than all the rest\n              continue;\n            }\n\n          // add LRU entry in front\n          lru_entry_t lru;\n          lru.path = fpath;\n          lru.ctime = buf.st_ctime;\n          lru.size = buf.st_blocks * buf.st_blksize;\n          lru_map.insert(lru);\n          lru_size += lru.size;\n          eos_static_debug(\"msg=\\\"adding\\\" file=\\\"%s\\\" \"\n                           \"bytes-free=\\\"%llu\\\" lru-size=\\\"%llu\\\"\",\n                           fpath.c_str(),\n                           bytes_to_free,\n                           lru_size);\n\n          // check if we can shrink the LRU map\n          if (lru_map.size() && (lru_size > bytes_to_free)) {\n            while (lru_map.size() &&\n                   ((lru_size - (--lru_map.end())->size) > bytes_to_free)) {\n              // remove the last element  of the map\n              auto it = lru_map.end();\n              it--;\n              // subtract the size\n              lru_size -= it->size;\n              eos_static_info(\"msg=\\\"clean-up\\\" path=\\\"%s\\\"\", it->path.c_str());\n              lru_map.erase(it);\n            }\n          }\n        }\n      }\n    }\n  } else {\n    eos_static_err(\"msg=\\\"%s\\\"\", stdErr.c_str());\n  }\n\n  eos_static_notice(\"msg=\\\"cleaning LRU cache\\\" files-to-delete=%llu\",\n                    lru_map.size());\n\n  // Delete starting with the 'oldest' entry until we have freed enough space\n  // to go under the low watermark\n  for (auto it = lru_map.begin(); it != lru_map.end(); it++) {\n    eos_static_notice(\"msg=\\\"delete LRU file\\\" path=\\\"%s\\\" ctime=%lu size=%llu\",\n                      it->path.c_str(),\n                      it->ctime,\n                      it->size);\n\n    if (gOFS->_rem(it->path.c_str(), mError, mRootVid, \"\")) {\n      eos_static_err(\"msg=\\\"failed to expire file\\\" \"\n                     \"path=\\\"%s\\\"\", it->path.c_str());\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n//! Convert all files matching\n//------------------------------------------------------------------------------\nvoid\nLRU::ConvertMatch(const char* dir,\n                  eos::IContainerMD::XAttrMap& map)\n{\n  eos_static_info(\"msg=\\\"applying match policy\\\" dir=\\\"%s\\\" match=\\\"%s\\\"\",\n                  dir, map[\"sys.lru.convert.match\"].c_str());\n  std::map < std::string, std::string> lMatchMap;\n  std::map < std::string, time_t> lMatchAgeMap;\n  std::map < std::string, ssize_t> lMatchSizeMap;\n  time_t now = time(NULL);\n\n  if (!StringConversion::GetKeyValueMap(map[\"sys.lru.convert.match\"].c_str(),\n                                        lMatchMap,\n                                        \":\")\n     ) {\n    eos_static_err(\"msg=\\\"LRU match attribute is illegal\\\" val=\\\"%s\\\"\",\n                   map[\"sys.lru.convert.match\"].c_str());\n    return;\n  }\n\n  for (auto it = lMatchMap.begin(); it != lMatchMap.end(); it++) {\n    time_t t;\n    ssize_t size;\n\n    std::ostringstream errMsg;\n    if (!extractTimeSizeCriterias(it->second, t, size, errMsg)) {\n      eos_static_err(\"LRU failed to extract time and size - attribute is illegal \"\n                     \"val=\\\"%s\\\", msg=\\\"%s\\\"\",\n                     it->first.c_str(), errMsg.str().c_str());\n    } else {\n      std::string conv_attr = \"sys.conversion.\";\n      conv_attr += it->first;\n\n      if (map.count(conv_attr)) {\n        lMatchAgeMap[it->first] = t;\n        lMatchSizeMap[it->first] = size;\n        eos_static_info(\"rule=\\\"%s %u\\\"\", it->first.c_str(), t);\n      } else {\n        eos_static_err(\"msg=\\\"LRU match attribute has no conversion \"\n                       \"attribute defined\\\" attr-missing=\\\"%s\\\"\",\n                       conv_attr.c_str());\n      }\n    }\n  }\n\n  std::vector < std::pair<FileId::fileid_t, std::string> > lConversionList;\n  {\n    // Check the directory contents\n    std::shared_ptr<eos::IContainerMD> cmd;\n    eos::Prefetcher::prefetchContainerMDWithChildrenAndWait(gOFS->eosView, dir);\n\n    try {\n      cmd = gOFS->eosView->getContainer(dir);\n      std::shared_ptr<eos::IFileMD> fmd;\n      std::string fullpath;\n      XrdOucString fname;\n      eos::IFileMD::ctime_t fctime;\n      uint64_t fsize;\n      eos::IFileMD::layoutId_t flayoutId;\n      eos::IFileMD::id_t fid;\n\n      for (auto fit = eos::FileMapIterator(cmd); fit.valid(); fit.next()) {\n        fmd = cmd->findFile(fit.key());\n\n        if (fmd == nullptr) {\n          eos_static_err(\"msg=\\\"file is null\\\" fxid=%08llx\", fit.key().c_str());\n          continue;\n        }\n\n        {\n          eos::MDLocking::FileReadLock fmdLock(fmd.get());\n          fname = fmd->getName().c_str();\n          fmd->getCTime(fctime);\n          fsize = fmd->getSize();\n          flayoutId = fmd->getLayoutId();\n          fid = fmd->getId();\n        }\n\n        fullpath = dir;\n        fullpath += fname.c_str();\n        eos_static_debug(\"check_file=\\\"%s\\\"\", fullpath.c_str());\n\n        // Loop over the match map\n        for (auto mit = lMatchAgeMap.begin(); mit != lMatchAgeMap.end(); mit++) {\n          eos_static_debug(\"check_rule=\\\"%s\\\" matched=%d\", mit->first.c_str(),\n                           fname.matches(mit->first.c_str()));\n\n          if (fname.matches(mit->first.c_str())) {\n            // Full match check the age policy\n            time_t age = mit->second;\n\n            if ((fctime.tv_sec + age) < now) {\n              std::string conv_attr = \"sys.conversion.\";\n              conv_attr += mit->first;\n              // Check if this file has already the proper layout\n              std::string conversion = map[conv_attr];\n              std::string plctplcy;\n\n              if (((int)conversion.find(\"|\")) != STR_NPOS) {\n                eos::common::StringConversion::SplitKeyValue(conversion, conversion, plctplcy,\n                    \"|\");\n              }\n\n              unsigned long long lid = strtoll(map[conv_attr].c_str(), 0, 16);\n\n              if (flayoutId == lid) {\n                eos_static_debug(\"msg=\\\"skipping conversion - file has already \"\n                                 \"the desired target layout\\\" fxid=%08llx\", fid);\n                continue;\n              }\n\n              if (lMatchSizeMap.count(mit->first)) {\n                if (lMatchSizeMap[mit->first] < 0) {\n                  // check that this file is smaller as the required size\n                  if ((ssize_t)fsize >= (-lMatchSizeMap[mit->first])) {\n                    eos_static_debug(\"msg=\\\"skipping conversion - file is larger \"\n                                     \"than required\\\" fxid=%08llx\", fid);\n                    continue;\n                  } else {\n                    eos_static_info(\"msg=\\\"converting according to age+size specification\\\" \"\n                                    \"path='%s' fxid=%08llx required-size < %ld size=%ld layout:%08x :=> %08x\",\n                                    fullpath.c_str(), fid, -lMatchSizeMap[mit->first],\n                                    (ssize_t)fsize, lid, flayoutId);\n                  }\n                }\n\n                if (lMatchSizeMap[mit->first] > 0) {\n                  // check that this file is larger than the required size\n                  if ((ssize_t)fsize <= lMatchSizeMap[mit->first]) {\n                    eos_static_debug(\"msg=\\\"skipping conversion - file is smaller \"\n                                     \"than required\\\" fxid=%08llx\", fid);\n                    continue;\n                  } else {\n                    eos_static_info(\"msg=\\\"converting according to age+size specification\\\" \"\n                                    \"path='%s' fxid=%08llx required-size > %ld size=%ld layout:%08x \"\n                                    \":=> %08x\", fullpath.c_str(), fid, lMatchSizeMap[mit->first],\n                                    (ssize_t)fsize, lid, flayoutId);\n                  }\n                }\n              } else {\n                eos_static_info(\"msg=\\\"converting according to age specification\\\" path='%s' \"\n                                \"fxid=%08llx layout:%08x :=> %08x\", fullpath.c_str(),\n                                fid, lid, flayoutId);\n              }\n\n              // This entry can be converted\n              eos_static_notice(\"msg=\\\"convert expired file\\\" path=\\\"%s\\\" \"\n                                \"ctime=%u policy-age=%u age=%u fxid=%08llx \"\n                                \"layout=\\\"%s\\\"\", fullpath.c_str(), fctime.tv_sec,\n                                age, now - fctime.tv_sec, (unsigned long long) fid,\n                                map[conv_attr].c_str());\n              lConversionList.push_back(std::make_pair(fid, map[conv_attr]));\n              break;\n            }\n          }\n        }\n      }\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      cmd.reset();\n      eos_static_err(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\",\n                     e.getErrno(), e.getMessage().str().c_str());\n    }\n  }\n\n  for (auto it = lConversionList.begin(); it != lConversionList.end(); it++) {\n    const eos::common::FileId::fileid_t fid = it->first;\n    std::string conversion = it->second;\n    std::string plctplcy;\n\n    if (((int)conversion.find(\"|\")) != STR_NPOS) {\n      eos::common::StringConversion::SplitKeyValue(conversion, conversion, plctplcy,\n          \"|\");\n      plctplcy = \"~\" + plctplcy;\n    }\n\n    std::string space;\n\n    if (map.count(\"user.forced.space\")) {\n      space = map[\"user.forced.space\"];\n    }\n\n    if (map.count(\"sys.forced.space\")) {\n      space = map[\"sys.forced.space\"];\n    }\n\n    if (map.count(\"sys.lru.conversion.space\")) {\n      space = map[\"sys.lru.conversion.space\"];\n    }\n\n    // the conversion value can be directory an layout env representation like\n    // \"eos.space=...&eos.layout ...\"\n    XrdOucEnv cenv(conversion.c_str());\n\n    if (cenv.Get(\"eos.space\")) {\n      space = cenv.Get(\"eos.space\");\n    }\n\n    std::string err_msg;\n    std::string conv_tag = ConversionTag::Get(it->first, space.c_str(), conversion,\n                           plctplcy);\n\n    if (gOFS->mConverterEngine->ScheduleJob(fid, conv_tag, err_msg)) {\n      eos_static_info(\"msg=\\\"LRU scheduled conversion job\\\" tag=\\\"%s\\\"\",\n                      conv_tag.c_str());\n    } else {\n      eos_static_err(\"msg=\\\"LRU failed to schedule conversion job\\\" \"\n                     \"tag=\\\"%s\\\"\", conv_tag.c_str());\n    }\n  }\n}\n\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/lru/LRU.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file LRU.hh\n//! @author Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/AssistedThread.hh\"\n#include \"common/Mapping.hh\"\n#include \"common/RegexWrapper.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include <XrdOuc/XrdOucErrInfo.hh>\n#include <memory>\n#include <sys/types.h>\n\nnamespace qclient\n{\nclass QClient;\n}\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// @brief  This class implements an LRU engine\n//------------------------------------------------------------------------------\nclass LRU\n{\npublic:\n  static const char* gLRUPolicyPrefix;\n\n  //----------------------------------------------------------------------------\n  //! Simple struct describing LRU options\n  //----------------------------------------------------------------------------\n  struct Options {\n    bool enabled;                  //< Is LRU even enabled?\n    std::chrono::seconds interval; //< Run LRU every this many seconds.\n  };\n\n  struct PolicyRule {\n    std::string m_regMatch;\n    time_t m_age;\n    /**\n     * <0 means file should be less than m_size, >0 means file should be equal\n     * or more than m_size. Should not be equal to 0\n     */\n    std::optional<ssize_t> m_size;\n    time_t m_now;\n\n    PolicyRule(const std::string& regMatch, time_t age, ssize_t size, time_t now)\n        : m_regMatch(regMatch)\n        , m_age(age)\n        , m_size(size != 0 ? std::optional<ssize_t>(size) : std::nullopt)\n        , m_now(now)\n    {\n    }\n\n    bool\n    operator==(const PolicyRule& other) const\n    {\n      return m_regMatch == other.m_regMatch && m_age == other.m_age &&\n             m_size == other.m_size;\n    }\n\n    bool\n    nameMatches(const std::string& fileName) const\n    {\n      // The current regex matching will not work with just `*` but requires `.*` instead.\n      // We continue supporting `*` in LRU via this hack\n      if (m_regMatch == \"*\") {\n        return true;\n      }\n\n      return common::eos_regex_match(fileName, m_regMatch);\n    }\n\n    bool\n    ageMatches(time_t fileCreationTime) const\n    {\n      return (fileCreationTime + m_age < m_now);\n    }\n\n    bool\n    sizeMatches(const size_t& fileSize) const\n    {\n      if (m_size) {\n        return (*m_size > 0 ? fileSize > static_cast<size_t>(*m_size)\n                            : fileSize < static_cast<size_t>(-*m_size));\n      }\n      // if no size, then return true as we should not consider size to be part of the\n      // matching process\n      return true;\n    }\n\n    std::string\n    getSizeCriteria() const\n    {\n      if (!m_size) {\n        return \"none\";\n      }\n\n      if (*m_size > 0) {\n        return std::to_string(*m_size);\n      }\n\n      return std::string(\"-\") + std::to_string(static_cast<size_t>(-*m_size));\n    }\n\n    bool\n    matches(const std::string& name, const time_t fileCreationTime,\n            const size_t size) const\n    {\n      return nameMatches(name) && ageMatches(fileCreationTime) && sizeMatches(size);\n    }\n  };\n\n  friend std::ostream&\n  operator<<(std::ostream& os, const PolicyRule& rule)\n  {\n    return os << \"PolicyRule{m_regMatch=\" << rule.m_regMatch << \", m_age=\" << rule.m_age\n              << \", m_size=\"\n              << (rule.m_size ? std::to_string(*rule.m_size) : std::string(\"N/A\"))\n              << \" }\";\n  }\n\n  typedef std::vector<PolicyRule> PolicyRules;\n\n  //----------------------------------------------------------------------------\n  //! Extracts the age and the time criteria from a string containing time:size\n  //! E.g: 1mo:>1G\n  //!\n  //! @return true if parsing succeeded, false otherwise\n  //----------------------------------------------------------------------------\n  static bool extractTimeSizeCriterias(const std::string& input, time_t& age,\n                                       ssize_t& size, std::ostringstream& errMsg);\n\n  //----------------------------------------------------------------------------\n  //! Parse an \"sys.lru.expire.match\" policy\n  //!\n  //! @return true if parsing succeeded, false otherwise\n  //----------------------------------------------------------------------------\n  static bool parseExpireMatchPolicy(const std::string& policy,\n                                     std::map<std::string, time_t>& matchAgeMap);\n\n  static bool parseExpireSizeMatchPolicy(const std::string& policy,\n                                         PolicyRules& matchAgeSizeMap,\n                                         std::ostringstream& errMsg);\n\n  //----------------------------------------------------------------------------\n  //! Retrieve current LRU configuration options\n  //----------------------------------------------------------------------------\n  Options getOptions();\n\n  //----------------------------------------------------------------------------\n  //! Retrieve \"lru.interval\" configuration option as string, or empty if\n  //! cannot be found. Assumes gFsView.ViewMutex is at-least readlocked.\n  //----------------------------------------------------------------------------\n  std::string getLRUIntervalConfig() const;\n\n  //----------------------------------------------------------------------------\n  //! Constructor. To run the LRU thread, call Start\n  //----------------------------------------------------------------------------\n  LRU();\n\n  //----------------------------------------------------------------------------\n  //! Destructor - stop the background thread, if running\n  //----------------------------------------------------------------------------\n  ~LRU();\n\n  //----------------------------------------------------------------------------\n  //! Start the LRU thread\n  //----------------------------------------------------------------------------\n  void Start();\n\n  //----------------------------------------------------------------------------\n  //! Stop the LRU thread\n  //----------------------------------------------------------------------------\n  void Stop();\n\n  //----------------------------------------------------------------------------\n  //!  @brief Remove empty directories if they are older than age given in\n  //!         policy\n  //! @param dir directory to proces\n  //! @param policy minimum age to expire\n  //----------------------------------------------------------------------------\n  void AgeExpireEmpty(const char* dir, const std::string& policy);\n\n  //----------------------------------------------------------------------------\n  //! @brief Remove all files older than the policy defines\n  //! @param dir directory to process\n  //! @param policy minimum age to expire\n  //----------------------------------------------------------------------------\n  void SizeAgeExpire(const char* dir, const std::string& policy);\n\n  //----------------------------------------------------------------------------\n  //! Expire the oldest files to go under the low watermark\n  //!\n  //! @param dir directory to process\n  //! @param policy high water mark when to start expiration\n  //----------------------------------------------------------------------------\n  void CacheExpire(const char* dir, std::string& low, std::string& high);\n\n  //----------------------------------------------------------------------------\n  //! Convert all files matching\n  //!\n  //! @param dir directory to process\n  //! @param map storing all the 'sys.conversion.<match>' policies\n  //----------------------------------------------------------------------------\n  void ConvertMatch(const char* dir, eos::IContainerMD::XAttrMap& map);\n\n  //----------------------------------------------------------------------------\n  //! Signal the LRU stat it shoudl refresh it's options\n  //----------------------------------------------------------------------------\n  inline void RefreshOptions()\n  {\n    mRefresh = true;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Struct representing LRU entry\n  //----------------------------------------------------------------------------\n  struct lru_entry {\n    //! Compare operator to use struct in a map\n    bool operator< (lru_entry const& lhs) const\n    {\n      if (lhs.ctime == ctime) {\n        return (path < lhs.path);\n      }\n\n      return (ctime < lhs.ctime);\n    }\n\n    std::string path;\n    time_t ctime;\n    unsigned long long size;\n  };\n\n  //! Entry in an lru queue having path name, mtime, size\n  typedef struct lru_entry lru_entry_t;\n\nprivate:\n  //----------------------------------------------------------------------------\n  // LRU method doing the actual policy scrubbing\n  //\n  // This thread loops in regular intervals over all directories which have\n  // a LRU policy attribute set (sys.lru.*) and applies the defined policy.\n  //----------------------------------------------------------------------------\n  void backgroundThread(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  // Process the given directory, apply all policies\n  //----------------------------------------------------------------------------\n  void processDirectory(const std::string& dir,\n                        eos::IContainerMD::XAttrMap& map);\n\n  //----------------------------------------------------------------------------\n  // Perform a single LRU cycle, QDB namespace\n  //----------------------------------------------------------------------------\n  void performCycleQDB(ThreadAssistant& assistant) noexcept;\n\n  std::unique_ptr<qclient::QClient> mQcl; ///< Internal QCl object\n  AssistedThread mThread; ///< thread id of the LRU thread\n  eos::common::VirtualIdentity mRootVid; ///< Uses the root vid\n  XrdOucErrInfo mError; ///< XRootD error object\n  std::atomic<bool> mRefresh; ///< Flag to mark option refresh\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/macros/Macros.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file Macros.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/access/Access.hh\"\n#include <XrdOuc/XrdOucEnv.hh>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Namespace map functionality\n//------------------------------------------------------------------------------\nvoid NamespaceMap(std::string& path, const char* ininfo,\n                  const eos::common::VirtualIdentity& vid)\n{\n  XrdOucString store_path = path.c_str();\n\n  if (ininfo && strstr(ininfo, \"eos.encodepath\")) {\n    store_path = eos::common::StringConversion::curl_unescaped(path).c_str();\n  } else {\n    eos::common::StringConversion::UnsealXrdPath(store_path);\n  }\n\n  if (vid.token) {\n    if (vid.token->Valid()) {\n      // replace path from a token\n      if (path.substr(0, 9) == \"/zteos64:\") {\n        store_path = vid.token->Path().c_str();\n      }\n    }\n  }\n\n  if (!(ininfo) || (ininfo && (!strstr(ininfo, \"eos.prefix\")))) {\n    XrdOucString iinpath = store_path;\n    gOFS->PathRemap(iinpath.c_str(), store_path);\n  }\n\n  ssize_t indx = 0;\n\n  for (indx = 0; indx < store_path.length(); indx++) {\n    if (((store_path[indx] != 0xa) && (store_path[indx] != 0xd)) /* CR,LF*/) {\n      continue;\n    } else {\n      break;\n    }\n  }\n\n  // root can use all letters\n  if ((vid.uid != 0) && (indx != store_path.length())) {\n    path.clear();\n  } else {\n    const char* pf = 0;\n\n    // Check for redirection with prefixes\n    if (ininfo && (pf = strstr(ininfo, \"eos.prefix=\"))) {\n      if (!store_path.beginswith(\"/proc/\")) {\n        XrdOucEnv env(pf);\n        // Check for redirection with LFN rewrite\n        store_path.insert(env.Get(\"eos.prefix\"), 0);\n      }\n    }\n\n    if (ininfo && (pf = strstr(ininfo, \"eos.lfn=\"))) {\n      if ((!store_path.beginswith(\"/proc/\"))) {\n        XrdOucEnv env(pf);\n        store_path = env.Get(\"eos.lfn\");\n      }\n    }\n\n    path = store_path.c_str();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Bounce illegal path names\n//------------------------------------------------------------------------------\nbool ProcBounceIllegalNames(const std::string& path, std::string& err_check,\n                            int& errno_check)\n{\n  if (path.empty()) {\n    err_check +=\n      \"error: illegal characters - use only use only A-Z a-z 0-9 SPACE .-_~#:^\\n\";\n    errno_check = EILSEQ;\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Bounce not-allowed-users in proc request\n//------------------------------------------------------------------------------\nbool\nProcBounceNotAllowed(const std::string& path,\n                     const eos::common::VirtualIdentity& vid,\n                     std::string& err_check, int& errno_check)\n{\n  eos::common::RWMutexReadLock lock(Access::gAccessMutex);\n\n  if ((vid.uid > 3) &&\n      (Access::gAllowedUsers.size() ||\n       Access::gAllowedGroups.size() ||\n       Access::gAllowedDomains.size() ||\n       Access::gAllowedHosts.size())) {\n    if (Access::gAllowedUsers.size() || Access::gAllowedGroups.size() ||\n        Access::gAllowedHosts.size()) {\n      if ((!Access::gAllowedGroups.count(vid.gid)) &&\n          (!Access::gAllowedUsers.count(vid.uid)) &&\n          (!Access::gAllowedHosts.count(vid.host)) &&\n          (!Access::gAllowedDomains.count(vid.getUserAtDomain()))) {\n        eos_static_err(\"msg=\\\"user access restricted - unauthorized identity\\\" vid.uid=\"\n                       \"%d vid.gid=%d vid.host=\\\"%s\\\" vid.tident=\\\"%s\\\" \"\n                       \"path=\\\"%s\\\" user@domain=\\\"%s\\\"\", vid.uid, vid.gid, vid.host.c_str(),\n                       (vid.tident.c_str() ? vid.tident.c_str() : \"\"), path.c_str(),\n                       vid.getUserAtDomain().c_str());\n        err_check += \"error: user access restricted - unauthorized identity used\";\n        errno_check = EACCES;\n        return true;\n      }\n    }\n\n    if (Access::gAllowedDomains.size() &&\n        (!Access::gAllowedDomains.count(\"-\")) &&\n        (!Access::gAllowedDomains.count(vid.domain))) {\n      eos_static_err(\"msg=\\\"domain access restricted - unauthorized identity\\\" \"\n                     \"vid.domain=\\\"%s\\\" path=\\\"%s\\\"\", vid.domain.c_str(),\n                     path.c_str());\n      err_check += \"error: domain access restricted - unauthorized identity used\";\n      errno_check = EACCES;\n      return true;\n    }\n  }\n\n  return false;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/macros/Macros.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Macros.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @file   Macros.hh\n//!\n//! @brief  XRootD OFS macros\n//!\n//! The Macros short-cut most of the MgmOfs... functions to apply redirection\n//! or stall settings.\n//------------------------------------------------------------------------------\n\n#ifndef __EOSMGM_MACROS__HH__\n#define __EOSMGM_MACROS__HH__\n\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n\nUSE_EOSMGMNAMESPACE\n\nextern XrdMgmOfs* gOFS; //< global handle to XrdMgmOfs object\n\n//------------------------------------------------------------------------------\n// Macro definitions\n//------------------------------------------------------------------------------\n\n/// define read access\n#define ACCESS_R 0\n\n/// define write access\n#define ACCESS_W 1\n\n/// defines operation mode to be read\n#define ACCESSMODE_R int __AccessMode__ = 0\n\n/// defines operation mode to be write\n#define ACCESSMODE_W int __AccessMode__ = 1\n\n/// defines operation mode to be read on master\n#define ACCESSMODE_R_MASTER int __AccessMode__ = 2\n\n/// set's operation mode to be write\n#define SET_ACCESSMODE_W __AccessMode__ = 1\n\n/// set's operation mode to be read\n#define SET_ACCESSMODE_R_MASTER __AccessMode__ = 2\n\n/// check if we are in read access mode\n#define IS_ACCESSMODE_R (__AccessMode__ == 0)\n\n/// check if we are in write access mode\n#define IS_ACCESSMODE_W (__AccessMode__ == 1)\n\n/// check if we are in master read access mode\n#define IS_ACCESSMODE_R_MASTER (__AccessMode__ == 2)\n\n#define WAIT_BOOT while (true) {if (gOFS->IsNsBooted()) break;     \\\n    std::this_thread::sleep_for(std::chrono::seconds(5));          \\\n  }\n\n\n//------------------------------------------------------------------------------\n//! Stall Macro\n//------------------------------------------------------------------------------\n#define MAYSTALL \\\n  std::unique_ptr<eos::mgm::InFlightRegistration> tracker_helper;              \\\n  if(gOFS != nullptr){ \\\n    tracker_helper = std::make_unique<eos::mgm::InFlightRegistration>(gOFS->mTracker, vid); \\\n    if (gOFS->IsStall) {                                                  \\\n      XrdOucString stallmsg=\"\";                                           \\\n      int stalltime=0;                                                    \\\n      if (gOFS->ShouldStall(__FUNCTION__,__AccessMode__, vid, stalltime, stallmsg)) { \\\n        if (stalltime) {                                                  \\\n          return gOFS->Stall(error, vid, stalltime, stallmsg.c_str());   \\\n        } else {                                                          \\\n          return gOFS->Emsg(\"maystall\", error, EPERM, stallmsg.c_str(), \"\"); \\\n        }                                                                 \\\n      } else {                                                            \\\n        if (!tracker_helper->IsOK()) {                                     \\\n          stallmsg=\"track request, stall the client 5 seconds\";           \\\n          stalltime = 5;                                                  \\\n          return gOFS->Stall(error, vid, stalltime, stallmsg.c_str());   \\\n        }                                                                 \\\n      }                                                                   \\\n    }                                                                     \\\n  }\n\n#define RECURSIVE_STALL(FUNCTION, VID) {        \\\n  if (gOFS->IsStall) {                                                  \\\n    XrdOucString stallmsg=\"\";                                           \\\n    int stalltime=0;                                                    \\\n    for (size_t i=0; i<20;++i) {          \\\n      if (gOFS->ShouldStall((FUNCTION), __AccessMode__, (VID), stalltime, stallmsg)) { \\\n        std::this_thread::sleep_for(std::chrono::milliseconds(5));  \\\n      } else {                                        \\\n  break;                \\\n      }                 \\\n    }                 \\\n  }                 \\\n}\n\n#define FUNCTIONMAYSTALL(FUNCTION, VID, ERROR) \\\n  eos::mgm::InFlightRegistration tracker_helper(gOFS->mTracker, (VID) );\\\n  if (gOFS->IsStall) {                                                  \\\n    XrdOucString stallmsg=\"\";                                           \\\n    int stalltime=0;                                                    \\\n    if (gOFS->ShouldStall((FUNCTION),__AccessMode__, (VID), stalltime, stallmsg)) { \\\n      if (stalltime) {                                                  \\\n        return gOFS->Stall((ERROR), (VID), stalltime, stallmsg.c_str());\\\n      } else {                                                          \\\n        return gOFS->Emsg(\"maystall\", (ERROR), EPERM, stallmsg.c_str(), \"\"); \\\n      }                                                                 \\\n    } else {                                                            \\\n      if (!tracker_helper.IsOK()) {                                     \\\n        stallmsg=\"track request, stall the client 5 seconds\";           \\\n        stalltime = 5;                                                  \\\n        return gOFS->Stall((ERROR), (VID), stalltime, stallmsg.c_str());\\\n      }                                                                 \\\n    }                                                                   \\\n  }\n\n\n//------------------------------------------------------------------------------\n//! Redirect Macro\n//------------------------------------------------------------------------------\n#define MAYREDIRECT                                                                      \\\n  {                                                                                      \\\n    if (gOFS != nullptr) {                                                               \\\n      if (gOFS->IsRedirect) {                                                            \\\n        int port{0};                                                                     \\\n        std::string host{\"\"};                                                            \\\n        int stall_timeout{0};                                                            \\\n        bool collapse = false;                                                           \\\n        std::string stall_msg{\"No master MGM available\"};                                \\\n        if (gOFS->ShouldRedirect(__FUNCTION__, __AccessMode__, vid, host, port,          \\\n                                 collapse)) {                                            \\\n          return gOFS->Redirect(error, host.c_str(), port, inpath, collapse);            \\\n        }                                                                                \\\n        if (gOFS->ShouldRoute(__FUNCTION__, __AccessMode__, vid, inpath, ininfo, host,   \\\n                              port, stall_timeout)) {                                    \\\n          if (stall_timeout) {                                                           \\\n            return gOFS->Stall(error, vid, stall_timeout, stall_msg.c_str());            \\\n          } else {                                                                       \\\n            XrdCl::URL url;                                                              \\\n            url.SetParams(ininfo ? ininfo : \"\");                                         \\\n            if (gOFS->Tried(url, host, \"enoent\"))                                        \\\n              return gOFS->Emsg(\"redirect\", error, ENOENT, \"no such file or directory\",  \\\n                                inpath);                                                 \\\n            return gOFS->Redirect(error, host.c_str(), port);                            \\\n          }                                                                              \\\n        }                                                                                \\\n      }                                                                                  \\\n    }                                                                                    \\\n  }\n\n//------------------------------------------------------------------------------\n//! ENOENT Redirect Macro\n//------------------------------------------------------------------------------\n#define MAYREDIRECT_ENOENT { if (gOFS->IsRedirect) {                           \\\n      int port {0};                                                            \\\n      std::string host {\"\"};                                                   \\\n      if (gOFS->HasRedirect(path, \"ENOENT:*\", host, port)) {                   \\\n        XrdCl::URL url; url.SetParams(ininfo?ininfo:\"\");                       \\\n        if (gOFS->Tried(url, host, \"enoent\"))                                  \\\n          return gOFS->Emsg(\"redirect\", error, ENOENT, \"no such file or directory\", path); \\\n        return gOFS->Redirect(error, host.c_str(), port) ;                     \\\n      }                                                                        \\\n    }                                                                          \\\n  }\n\n//------------------------------------------------------------------------------\n//! ENONET Redirect Macro\n//------------------------------------------------------------------------------\n#define MAYREDIRECT_ENONET { if (gOFS->IsRedirect) {                           \\\n      int port {0};                                                            \\\n      std::string host {\"\"};                                                   \\\n      if (gOFS->HasRedirect(path,\"ENONET:*\",host,port)) {                      \\\n        return gOFS->Redirect(error, host.c_str(), port) ;                     \\\n      }                                                                        \\\n    }                                                                          \\\n  }\n\n//------------------------------------------------------------------------------\n//! ENETUNREACH Redirect Macro\n//------------------------------------------------------------------------------\n#define MAYREDIRECT_ENETUNREACH { if (gOFS->IsRedirect) {                      \\\n      int port {0};                                                            \\\n      std::string host {\"\"};                                                   \\\n      if (gOFS->HasRedirect(path,\"ENETUNREACH:*\",host,port)) {                 \\\n        return gOFS->Redirect(error, host.c_str(), port) ;                     \\\n      }                                                                        \\\n    }                                                                          \\\n  }\n\n//------------------------------------------------------------------------------\n//! ENOENT Stall Macro\n//------------------------------------------------------------------------------\n#define MAYSTALL_ENOENT { if (gOFS->IsStall) {                                 \\\n      XrdOucString stallmsg=\"\";                                                \\\n      int stalltime;                                                           \\\n      if (gOFS->HasStall(path, \"ENOENT:*\", stalltime, stallmsg)) {             \\\n        return gOFS->Stall(error, vid, stalltime, stallmsg.c_str()) ;    \\\n      }                                                                        \\\n    }                                                                          \\\n  }\n\n//------------------------------------------------------------------------------\n//! ENONET Stall Macro\n// -----------------------------------------------------------------------------\n#define MAYSTALL_ENONET { if (gOFS->IsStall) {                                 \\\n      XrdOucString stallmsg=\"\";                                                \\\n      int stalltime;                                                           \\\n      if (gOFS->HasStall(path,\"ENONET:*\", stalltime, stallmsg)) {              \\\n        return gOFS->Stall(error, vid, stalltime, stallmsg.c_str()) ;    \\\n      }                                                                        \\\n    }                                                                          \\\n  }\n\n//------------------------------------------------------------------------------\n//! ENETUNREACH Stall Macro\n//------------------------------------------------------------------------------\n#define MAYSTALL_ENETUNREACH { if (gOFS->IsStall) {                            \\\n      XrdOucString stallmsg=\"\";                                                \\\n      int stalltime;                                                           \\\n      if (gOFS->HasStall(path,\"ENETUNREACH:*\", stalltime, stallmsg)) {         \\\n        return gOFS->Stall(error, vid, stalltime, stallmsg.c_str()) ;    \\\n      }                                                                        \\\n    }                                                                          \\\n  }\n\n//------------------------------------------------------------------------------\n//! Namespace Map MACRO\n//! - checks validity of path names\n//! - checks for prefixing and rewrites path name\n//! - remap's path names according to the configured path map\n//------------------------------------------------------------------------------\n#define NAMESPACEMAP                                                    \\\n  const char* path = inpath;                                            \\\n  XrdOucString store_path = path;                                         \\\n  if(gOFS != nullptr) {                                                  \\\n    if (inpath && ininfo && strstr(ininfo, \"eos.encodepath\")) {             \\\n      store_path = eos::common::StringConversion::curl_unescaped(inpath).c_str(); \\\n    } else {                                                              \\\n      eos::common::StringConversion::UnsealXrdPath(store_path);           \\\n    }                                                                     \\\n    if (vid.token && vid.token->Valid()) {        \\\n      if (!strncmp(path, \"/zteos64:\", 9)) {         \\\n        store_path = vid.token->Path().c_str();       \\\n      }                 \\\n    }                 \\\n    if (inpath && (!(ininfo) || (ininfo && (!strstr(ininfo, \"eos.prefix\"))))) { \\\n      XrdOucString iinpath = store_path;                                    \\\n      gOFS->PathRemap(iinpath.c_str(), store_path);                        \\\n    }                                                                     \\\n    size_t __i = 0;                                                         \\\n    size_t __n = store_path.length();                                     \\\n    for (__i = 0; __i < __n; __i++) {         \\\n      if (((store_path[__i] != 0xa) && (store_path[__i] != 0xd)) /* CR,LF*/) { \\\n  continue;                                                       \\\n      } else {                                                          \\\n  break;                                                          \\\n      }                                                                 \\\n    }                                                                   \\\n    /* root can use all letters */                                        \\\n    if ((vid.uid != 0) && (__i != (__n))) {                            \\\n      path = 0;                                                           \\\n    } else {                                                              \\\n      const char* pf = 0;                                                   \\\n      /* check for redirection with prefixes */                           \\\n      if (ininfo && (pf = strstr(ininfo, \"eos.prefix=\"))) {                \\\n        if (!store_path.beginswith(\"/proc/\")) {                            \\\n          XrdOucEnv env(pf);                                              \\\n          /* check for redirection with LFN rewrite */                    \\\n          store_path.insert(env.Get(\"eos.prefix\"), 0);                     \\\n        }                                                                 \\\n      }                                                                   \\\n      if (ininfo && (pf = strstr(ininfo, \"eos.lfn=\"))) {                   \\\n        if ((!store_path.beginswith(\"/proc/\"))) {                          \\\n          XrdOucEnv env(pf);                                              \\\n          store_path = env.Get(\"eos.lfn\");                                \\\n        }                                                                 \\\n      }                                                                   \\\n      path = store_path.c_str();                                          \\\n    }                                                                   \\\n  }\n\n\n//------------------------------------------------------------------------------\n//! Define scope for tokens\n//------------------------------------------------------------------------------\n#define TOKEN_SCOPE                                                            \\\n  vid.scope = path;\n\n\n#define PROC_TOKEN_SCOPE                                                       \\\n  pVid->scope = path;\n\n\n#define PROC_MVID_TOKEN_SCOPE                                                  \\\n  mVid.scope = path;\n\n#define NAMESPACE_NO_TRAILING_SLASH                                            \\\n  if (store_path.endswith(\"/\")) {                                              \\\n    store_path.erase(store_path.length()-1,1);                                 \\\n    path = store_path.c_str();                                                 \\\n  }\n\n#define PROC_MOVE_TOKENSCOPE(a,b)                                  \\\n  mVid.scope = eos::common::Path::Overlap( (a), (b)  );\n\n\n//------------------------------------------------------------------------------\n//! Bounce Illegal Name Macro\n//------------------------------------------------------------------------------\n#define BOUNCE_ILLEGAL_NAMES                                                   \\\n  if (!path) {                                                                 \\\n    eos_err(\"illegal character in %s\", store_path.c_str());                    \\\n    return Emsg(epname, error, EILSEQ,\"accept path name - illegal characters \" \\\n                \"- use only A-Z a-z 0-9 / SPACE .-_~#:^\", store_path.c_str()); \\\n  }\n\n//------------------------------------------------------------------------------\n//! Bounce Illegal Name in proc request Macro\n//------------------------------------------------------------------------------\n#define PROC_BOUNCE_ILLEGAL_NAMES                                              \\\n  if (!path) {                                                                 \\\n    eos_err(\"illegal character in %s\", store_path.c_str());                    \\\n    retc = EILSEQ;                                                             \\\n    stdErr += \"error: illegal characters - use only use only A-Z a-z 0-9 SPACE .-_~#:^\\n\"; \\\n    return SFS_OK;                                                             \\\n  }\n\n//------------------------------------------------------------------------------\n//! Require System Auth (SSS or localhost) Macro\n//------------------------------------------------------------------------------\n#define REQUIRE_SSS_OR_LOCAL_AUTH                                              \\\n  if ((vid.prot!=\"sss\") &&                                                     \\\n      ((vid.host != \"localhost\") &&                                            \\\n       (vid.host != \"localhost.localdomain\")) ){                               \\\n    eos_err(\"system access restricted - unauthorized identity used\");          \\\n    gOFS->MgmStats.Add(\"EAccess\", vid.uid, vid.gid, 1);                        \\\n    return Emsg(epname, error, EACCES,\"give access - system access \"           \\\n                \"restricted - unauthorized identity used\");                    \\\n  }\n\n//------------------------------------------------------------------------------\n//! Bounce not-allowed-users Macro - for root, bin, daemon, admin we allow\n//! localhost connects or sss authentication always\n//------------------------------------------------------------------------------\n#define BOUNCE_NOT_ALLOWED                                                    \\\n  if (((vid.uid > 3) ||                                                       \\\n       ((vid.prot != \"sss\") && (vid.host != \"localhost\") &&                   \\\n        (vid.host != \"localhost.localdomain\")))) {                            \\\n      eos::common::RWMutexReadLock lock(Access::gAccessMutex);    \\\n      if (Access::gAllowedTokens.size() && vid.token && !Access::gAllowedTokens.count(vid.token->Voucher()) ) { \\\n    eos_err(\"user access restricted - unauthorized token vid.token->Voucher=\\\"%s\\\"\" \\\n      \"path=\\\"%s\\\"\", vid.token->Voucher().c_str(),    \\\n      inpath);                                                    \\\n      gOFS->MgmStats.Add(\"EAccess\", vid.uid, vid.gid, 1);   \\\n      return Emsg(epname, error, EACCES,\"give access - user access \"  \\\n      \"restricted - unauthorized token used\");    \\\n    }                 \\\n    if (Access::gAllowedUsers.size() || Access::gAllowedGroups.size() ||      \\\n        Access::gAllowedHosts.size() || Access::gAllowedDomains.size())     { \\\n      if ((!Access::gAllowedGroups.count(vid.gid)) &&                         \\\n          (!Access::gAllowedUsers.count(vid.uid)) &&                          \\\n          (!Access::gAllowedHosts.count(vid.host)) &&                         \\\n          (!Access::gAllowedDomains.count(vid.getUserAtDomain()))) {  \\\n        eos_err(\"user access restricted - unauthorized identity vid.uid=\"    \\\n                \"%d, vid.gid=%d, vid.host=\\\"%s\\\", vid.tident=\\\"%s\\\" for \"     \\\n                \"path=\\\"%s\\\" user@domain=\\\"%s\\\"\", vid.uid, vid.gid, vid.host.c_str(),            \\\n                (vid.tident.c_str() ? vid.tident.c_str() : \"\"), inpath,       \\\n                vid.getUserAtDomain().c_str());                               \\\n  gOFS->MgmStats.Add(\"EAccess\", vid.uid, vid.gid, 1);                   \\\n        return Emsg(epname, error, EACCES,\"give access - user access \"        \\\n                    \"restricted - unauthorized identity used\");               \\\n      }                                                                       \\\n    }                                                                         \\\n    if (Access::gAllowedDomains.size() &&                                     \\\n        (!Access::gAllowedDomains.count(\"-\")) &&                              \\\n        (!Access::gAllowedDomains.count(vid.domain))) {                       \\\n      gOFS->MgmStats.Add(\"EAccess\", vid.uid, vid.gid, 1);                     \\\n      eos_err(\"domain access restricted - unauthorized identity \"             \\\n              \"vid.domain=\\\"%s\\\"for \"                                         \\\n              \"path=\\\"%s\\\"\", vid.domain.c_str(),                              \\\n              inpath);                                                        \\\n      return Emsg(epname, error, EACCES,\"give access - domain access \"        \\\n                  \"restricted - unauthorized identity used\");                 \\\n    }                                                                         \\\n  }\n\n//------------------------------------------------------------------------------\n//! Bounce not-allowed-users in proc request Macro\n//------------------------------------------------------------------------------\n#define PROC_BOUNCE_NOT_ALLOWED                                                 \\\n  { /* reduce scope of this mutex */                    \\\n    eos::common::RWMutexReadLock lock(Access::gAccessMutex);                    \\\n    if ((vid.uid > 3) &&                                                        \\\n        (Access::gAllowedUsers.size() ||                                        \\\n         Access::gAllowedGroups.size() ||                                       \\\n         Access::gAllowedDomains.size() ||                                      \\\n         Access::gAllowedHosts.size()) ) {                                      \\\n      if (Access::gAllowedUsers.size() || Access::gAllowedGroups.size() ||      \\\n          Access::gAllowedHosts.size()) {                                       \\\n        if ( (!Access::gAllowedGroups.count(vid.gid)) &&                        \\\n             (!Access::gAllowedUsers.count(vid.uid)) &&                         \\\n             (!Access::gAllowedHosts.count(vid.host)) &&                        \\\n             (!Access::gAllowedDomains.count(vid.getUserAtDomain()))) {         \\\n          eos_err(\"user access restricted - unauthorized identity vid.uid=\"     \\\n                  \"%d, vid.gid=%d, vid.host=\\\"%s\\\", vid.tident=\\\"%s\\\" for \"     \\\n                  \"path=\\\"%s\\\" user@domain=\\\"%s\\\"\", vid.uid, vid.gid, vid.host.c_str(),            \\\n                  (vid.tident.c_str() ? vid.tident.c_str() : \"\"), inpath,       \\\n                  vid.getUserAtDomain().c_str());                               \\\n          retc = EACCES;                                                        \\\n    gOFS->MgmStats.Add(\"EAccess\", vid.uid, vid.gid, 1);                   \\\n          stdErr += \"error: user access restricted - unauthorized identity used\";\\\n          return SFS_OK;                                                        \\\n        }                                                                       \\\n      }                                                                         \\\n      if (Access::gAllowedDomains.size() &&                                     \\\n          (!Access::gAllowedDomains.count(\"-\")) &&                              \\\n          (!Access::gAllowedDomains.count(vid.domain))) {                       \\\n        eos_err(\"domain access restricted - unauthorized identity \"             \\\n                \"vid.domain=\\\"%s\\\"for \"                                         \\\n                \"path=\\\"%s\\\"\", vid.domain.c_str(),                              \\\n                inpath);                                                        \\\n        retc = EACCES;                                                          \\\n        gOFS->MgmStats.Add(\"EAccess\", vid.uid, vid.gid, 1);                     \\\n        stdErr += \"error: domain access restricted - unauthorized identity used\";\\\n        return SFS_OK;                                                          \\\n      }                                                                         \\\n    }                                                                           \\\n  }\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Namespace map functionality i.e check validity of the paths, check for\n//! prefix and path rewrite options, remap path's names according to the\n//! configured path map.\n//!\n//! @param path input path, will hold the final re-mapped path\n//! @param ininfo optional opaque information\n//! @param vid virtual identify of the client\n//------------------------------------------------------------------------------\nvoid NamespaceMap(std::string& path, const char* ininfo,\n                  const eos::common::VirtualIdentity& vid);\n\n//------------------------------------------------------------------------------\n//! Bounce illegal path names\n//!\n//! @param path input path\n//! @param err_msg error message\n//! @param retc errno value\n//!\n//! @return true if request should be bounced, otherwise false\n//------------------------------------------------------------------------------\nbool ProcBounceIllegalNames(const std::string& path, std::string& err_check,\n                            int& errno_check);\n\n//------------------------------------------------------------------------------\n//! Bounce not-allowed-users in proc requests\n//!\n//! @param path input path\n//! @param vid virtual identity of the client\n//! @param err_msg error message\n//! @param retc errno value\n//!\n//! @return true if requests should be bounced, otherwise false\n//------------------------------------------------------------------------------\nbool ProcBounceNotAllowed(const std::string& path,\n                          const eos::common::VirtualIdentity& vid,\n                          std::string& err_check, int& errno_check);\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/misc/AuditHelpers.hh",
    "content": "#ifndef EOS_MGM_AUDIT_HELPERS_HH\n#define EOS_MGM_AUDIT_HELPERS_HH\n\n#include \"proto/Audit.pb.h\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/utils/Checksum.hh\"\n#include <string>\n#include <cstdio>\n\nnamespace eos {\nnamespace mgm {\nnamespace auditutil {\n\nstatic inline void buildStatFromFileMD(const std::shared_ptr<eos::IFileMD>& fmd,\n                                       eos::audit::Stat& out,\n                                       bool includeSize = true,\n                                       bool includeChecksum = false,\n                                       bool includeNs = true)\n{\n  if (!fmd) return;\n  eos::IFileMD::ctime_t cts, mts;\n  fmd->getCTime(cts);\n  fmd->getMTime(mts);\n  out.set_ctime(cts.tv_sec);\n  out.set_mtime(mts.tv_sec);\n  if (includeNs) {\n    char cbuf[64], mbuf[64];\n    std::snprintf(cbuf, sizeof(cbuf), \"%ld.%09ld\", (long)cts.tv_sec, (long)cts.tv_nsec);\n    std::snprintf(mbuf, sizeof(mbuf), \"%ld.%09ld\", (long)mts.tv_sec, (long)mts.tv_nsec);\n    out.set_ctime_ns(cbuf);\n    out.set_mtime_ns(mbuf);\n  }\n  out.set_uid(fmd->getCUid());\n  out.set_gid(fmd->getCGid());\n  uint32_t am = (fmd->getFlags() & 07777);\n  out.set_mode(am);\n  char amo[8];\n  std::snprintf(amo, sizeof(amo), \"0%04o\", am);\n  out.set_mode_octal(amo);\n  if (includeSize) out.set_size(fmd->getSize());\n  if (includeChecksum) {\n    std::string hex;\n    eos::appendChecksumOnStringAsHex(fmd.get(), hex);\n    if (!hex.empty()) out.set_checksum(hex);\n  }\n}\n\nstatic inline void buildStatFromContainerMD(const std::shared_ptr<eos::IContainerMD>& cmd,\n                                            eos::audit::Stat& out,\n                                            bool includeNs = true)\n{\n  if (!cmd) return;\n  eos::IFileMD::ctime_t cts, mts;\n  cmd->getCTime(cts);\n  cmd->getMTime(mts);\n  out.set_ctime(cts.tv_sec);\n  out.set_mtime(mts.tv_sec);\n  if (includeNs) {\n    char cbuf[64], mbuf[64];\n    std::snprintf(cbuf, sizeof(cbuf), \"%ld.%09ld\", (long)cts.tv_sec, (long)cts.tv_nsec);\n    std::snprintf(mbuf, sizeof(mbuf), \"%ld.%09ld\", (long)mts.tv_sec, (long)mts.tv_nsec);\n    out.set_ctime_ns(cbuf);\n    out.set_mtime_ns(mbuf);\n  }\n  out.set_uid(cmd->getCUid());\n  out.set_gid(cmd->getCGid());\n  uint32_t am = (cmd->getMode() & 07777);\n  out.set_mode(am);\n  char amo[8];\n  std::snprintf(amo, sizeof(amo), \"0%04o\", am);\n  out.set_mode_octal(amo);\n}\n\n} // namespace auditutil\n} // namespace mgm\n} // namespace eos\n\n#endif\n"
  },
  {
    "path": "mgm/misc/Constants.hh",
    "content": "//------------------------------------------------------------------------------\n// File: Constants.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include <string>\n\nnamespace eos::mgm\n{\n\n// ATTR constants\n// We are using static std::string as most use for these are in map.find, wherein we can\n// save a potential allocation. A future move would be to a constexpr string/view where\n// maps can handle transparent comparision and hence completely avoid allocation in find apis\nstatic const std::string EOS_ATOMIC = \"eos.atomic\";\nstatic const std::string EOS_INJECTION = \"eos.injection\";\n\nstatic const std::string SYS_HARD_LINK = \"sys.eos.mdino\";\nstatic const std::string SYS_NUM_LINK = \"sys.eos.nlink\";\nstatic const std::string SYS_OWNER_AUTH = \"sys.owner.auth\";\nstatic const std::string SYS_VERSIONING = \"sys.versioning\";\nstatic const std::string SYS_ALTCHECKSUMS = \"sys.altxs\";\nstatic const std::string SYS_FORCED_ATOMIC = \"sys.forced.atomic\";\nstatic const std::string SYS_REDIRECT_ENOENT = \"sys.redirect.enoent\";\nstatic const std::string SYS_FORCED_MINSIZE = \"sys.forced.minsize\";\nstatic const std::string SYS_FORCED_MAXSIZE = \"sys.forced.maxsize\";\nstatic const std::string SYS_FORCED_STALLTIME = \"sys.forced.stalltime\";\nstatic const std::string SYS_FORCED_SPACE = \"sys.forced.space\";\nstatic const std::string SYS_FORCED_GROUP = \"sys.forced.group\";\nstatic const std::string SYS_FORCED_LAYOUT = \"sys.forced.layout\";\nstatic const std::string SYS_FORCED_CHECKSUM = \"sys.forced.checksum\";\nstatic const std::string SYS_FORCED_BLOCKSIZE = \"sys.forced.blocksize\";\nstatic const std::string SYS_FORCED_BLOCKCHECKSUM = \"sys.forced.blockchecksum\";\nstatic const std::string SYS_FORCED_NSTRIPES = \"sys.forced.nstripes\";\n\nstatic const std::string USER_VERSIONING = \"user.versioning\";\nstatic const std::string USER_FORCED_ATOMIC = \"user.forced.atomic\";\nstatic const std::string USER_STALL_UNAVAILABLE = \"user.stall.unavailable\";\nstatic const std::string USER_TAG = \"user.tag\";\nstatic const std::string USER_FORCED_LAYOUT = \"user.forced.layout\";\nstatic const std::string USER_FORCED_CHECKSUM = \"user.forced.checksum\";\nstatic const std::string USER_FORCED_BLOCKSIZE = \"user.forced.blocksize\";\nstatic const std::string USER_FORCED_BLOCKCHECKSUM =\n  \"user.forced.blockchecksum\";\nstatic const std::string USER_FORCED_NSTRIPES = \"user.forced.nstripes\";\n\n// Policy related strings\nstatic const std::string POLICY_BANDWIDTH = \"policy.bandwidth\";\nstatic const std::string POLICY_IOPRIORITY = \"policy.iopriority\";\nstatic const std::string POLICY_IOTYPE = \"policy.iotype\";\nstatic const std::string POLICY_SCHEDULE = \"policy.schedule\";\n\n\n} // eos::mgm\n"
  },
  {
    "path": "mgm/misc/IdTrackerWithValidity.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @brief Class tracking entries that were used by different\n//! sub-systems during a reference period. e.g. draining / balancing\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/SteadyClock.hh\"\n#include <mutex>\n#include <map>\n\nEOSMGMNAMESPACE_BEGIN\n\n//! Type of trackers\nenum class TrackerType {\n  None, All, Balance, Convert, Drain, Fsck\n};\n\n//------------------------------------------------------------------------------\n//! Class IdTrackerWithValidity\n//------------------------------------------------------------------------------\ntemplate <typename EntryT>\nclass IdTrackerWithValidity\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param clean_interval minimum interval after which a clean up of expired\n  //!        entries is attempted\n  //! @param entry_validity duration for which an entry is considered still\n  //!        valid and not removed from the map\n  //! @param fake_clock if true use synthetic clock for testing\n  //----------------------------------------------------------------------------\n  IdTrackerWithValidity(std::chrono::seconds clean_interval,\n                        std::chrono::seconds entry_validity,\n                        bool fake_clock = false):\n    mCleanupInterval(clean_interval),\n    mEntryValidity(entry_validity),\n    mClock(fake_clock)\n  {\n    mCleanupTimestamp = eos::common::SteadyClock::now(&mClock) + mCleanupInterval;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~IdTrackerWithValidity() = default;\n\n  //----------------------------------------------------------------------------\n  //! Add entry with expiration\n  //!\n  //! @param entry\n  //! @param tt tracker type\n  //! @param validity validity for the newly added entry. If 0 then default\n  //!         validity applies.\n  //!\n  //! @return true if entry added, otherwise false\n  //----------------------------------------------------------------------------\n  bool AddEntry(EntryT entry, TrackerType tt, std::chrono::seconds validity =\n                  std::chrono::seconds::zero());\n\n  //----------------------------------------------------------------------------\n  //! Check if entry is already tracked\n  //!\n  //! @param entry\n  //!\n  //! @return true if entry in the map, otherwise false\n  //----------------------------------------------------------------------------\n  bool HasEntry(EntryT entry) const;\n\n  //----------------------------------------------------------------------------\n  //! Remove entry\n  //----------------------------------------------------------------------------\n  void RemoveEntry(EntryT entry);\n\n  //----------------------------------------------------------------------------\n  //! Clean up expired entries\n  //!\n  //! @param tt tracker type\n  //----------------------------------------------------------------------------\n  void DoCleanup(TrackerType tt);\n\n  //----------------------------------------------------------------------------\n  //! Clear all tracked entries\n  //----------------------------------------------------------------------------\n  void Clear(TrackerType tt)\n  {\n    std::unique_lock<std::mutex> lock(mMutex);\n\n    if (tt == TrackerType::All) {\n      mMap.clear();\n    } else {\n      auto it_tracker = mMap.find(tt);\n\n      if (it_tracker != mMap.end()) {\n        it_tracker->second.clear();\n      }\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get clock reference for testing purposes\n  //----------------------------------------------------------------------------\n  inline eos::common::SteadyClock& GetClock()\n  {\n    return mClock;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Convert tracker type to string\n  //!\n  //! @param tracker_type TrackerType enum\n  //!\n  //! @return string representation of the tracker type\n  //----------------------------------------------------------------------------\n  static std::string TrackerTypeToString(TrackerType tt)\n  {\n    if (tt == TrackerType::Drain) {\n      return \"drain\";\n    } else if (tt == TrackerType::Balance) {\n      return \"balance\";\n    } else if (tt == TrackerType::Convert) {\n      return \"convert\";\n    } else if (tt == TrackerType::Fsck) {\n      return \"fsck\";\n    } else if (tt == TrackerType::All) {\n      return \"all\";\n    } else {\n      return \"unknown\";\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Convert string to tracker type enum\n  //!\n  //! @param name tracker type string\n  //!\n  //! @return return TrackerType enum\n  //----------------------------------------------------------------------------\n  static TrackerType StringToTrackerType(const std::string& name)\n  {\n    if (name.empty()) {\n      return TrackerType::None;\n    } else if (name == \"all\") {\n      return TrackerType::All;\n    } else if (name == \"drain\") {\n      return TrackerType::Drain;\n    } else if (name == \"balance\") {\n      return TrackerType::Balance;\n    } else if (name == \"convert\") {\n      return TrackerType::Convert;\n    } else if (name == \"fsck\") {\n      return TrackerType::Fsck;\n    } else {\n      return TrackerType::None;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get statistics about the tracked files\n  //!\n  //! @param full if true print also the ids for each tracker\n  //! @param monitor if true print info in monitor format\n  //! @param tt tracker type\n  //!\n  //! @return string with the required information\n  //----------------------------------------------------------------------------\n  std::string PrintStats(bool full = false, bool monitor = false,\n                         const TrackerType& tt = TrackerType::All) const;\n\nprivate:\n  mutable std::mutex mMutex;\n  std::map<TrackerType,\n      std::map<EntryT, std::chrono::steady_clock::time_point>> mMap;\n  //! Next cleanup timestamp\n  std::chrono::steady_clock::time_point mCleanupTimestamp;\n  std::chrono::seconds mCleanupInterval; ///< Interval when cleanup is performed\n  std::chrono::seconds mEntryValidity; ///< Entry validity duration\n  eos::common::SteadyClock mClock; ///< Clock wrapper also used for testing\n};\n\n//------------------------------------------------------------------------------\n// Add entry with expiration\n//------------------------------------------------------------------------------\ntemplate<typename EntryT>\nbool\nIdTrackerWithValidity<EntryT>::AddEntry(EntryT entry, TrackerType tt,\n                                        std::chrono::seconds validity)\n{\n  if (tt <= TrackerType::All) {\n    return false;\n  }\n\n  std::unique_lock<std::mutex> lock(mMutex);\n\n  // Check if entry already exists in any of the trackers\n  for (const auto& pair : mMap) {\n    if (pair.second.find(entry) != pair.second.end()) {\n      return false;\n    }\n  }\n\n  auto& tracker_map = mMap[tt]; // will create it if missing\n\n  if (validity.count()) {\n    tracker_map[entry] = eos::common::SteadyClock::now(&mClock) + validity;\n  } else {\n    tracker_map[entry] = eos::common::SteadyClock::now(&mClock) + mEntryValidity;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Clean up expired entries\n//------------------------------------------------------------------------------\ntemplate<typename EntryT>\nvoid\nIdTrackerWithValidity<EntryT>::DoCleanup(TrackerType tt)\n{\n  using namespace std::chrono;\n  auto now = eos::common::SteadyClock::now(&mClock);\n  std::unique_lock<std::mutex> lock(mMutex);\n\n  if (mCleanupTimestamp < now) {\n    mCleanupTimestamp = now + mCleanupInterval;\n\n    for (auto& pair : mMap) {\n      if ((tt != TrackerType::All) && (pair.first != tt)) {\n        continue;\n      }\n\n      auto& tracker_map = pair.second;\n\n      for (auto it = tracker_map.begin(); it != tracker_map.end(); /*empty*/) {\n        if (it->second < now) {\n          auto it_del = it++;\n          tracker_map.erase(it_del);\n        } else {\n          ++it;\n        }\n      }\n    }\n  }\n}\n\n//----------------------------------------------------------------------------\n// Remove entry\n//----------------------------------------------------------------------------\ntemplate<typename EntryT>\nvoid\nIdTrackerWithValidity<EntryT>::RemoveEntry(EntryT entry)\n{\n  std::unique_lock<std::mutex> lock(mMutex);\n\n  for (auto& pair : mMap) {\n    auto& tracker_map = pair.second;\n    auto it = tracker_map.find(entry);\n\n    if (it != tracker_map.end()) {\n      tracker_map.erase(it);\n      break;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check if entry is already tracked\n//------------------------------------------------------------------------------\ntemplate<typename EntryT>\nbool\nIdTrackerWithValidity<EntryT>::HasEntry(EntryT entry) const\n{\n  std::unique_lock<std::mutex> lock(mMutex);\n\n  for (const auto& pair : mMap) {\n    if (pair.second.find(entry) != pair.second.end()) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\n//----------------------------------------------------------------------------\n//! Get statistics about the tracked files\n//----------------------------------------------------------------------------\ntemplate<typename EntryT>\nstd::string\nIdTrackerWithValidity<EntryT>::PrintStats(bool full, bool monitor,\n    const TrackerType& tt) const\n{\n  std::ostringstream oss;\n\n  if (tt == TrackerType::None) {\n    return oss.str();\n  }\n\n  std::unique_lock<std::mutex> lock(mMutex);\n\n  for (const auto& pair : mMap) {\n    if ((tt != TrackerType::All) && (tt != pair.first)) {\n      continue;\n    }\n\n    if (monitor) {\n      oss << \"uid=all gid=all \";\n    } else {\n      oss << \"ALL      tracker info                     \";\n    }\n\n    oss << \"tracker=\" << TrackerTypeToString(pair.first)\n        << \" size=\" << pair.second.size();\n\n    if (full) {\n      oss << \" fids=\";\n\n      for (const auto& elem : pair.second) {\n        oss << elem.first << \" \";\n      }\n    }\n\n    oss << std::endl;\n  }\n\n  return oss.str();\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/namespacestats/NamespaceStats.cc",
    "content": "// ----------------------------------------------------------------------\n// File: NamespaceStats.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"NamespaceStats.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/stat/Stat.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nvoid\nNamespaceStats::Add(const char* tag, uid_t uid, gid_t gid, unsigned long val) {\n  //No need for lock as the lock is already present in the Add() method of the MgmStats\n  gOFS->MgmStats.Add(tag,uid,gid,val);\n}\n\nvoid NamespaceStats::AddExec(const char* tag, float exectime) {\n  //No need for lock as the lock is already present in the AddExec() method of the MgmStats\n  gOFS->MgmStats.AddExec(tag,exectime);\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/namespacestats/NamespaceStats.hh",
    "content": "// ----------------------------------------------------------------------\n// File: NamespaceStats.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_NAMESPACESTATS_HH\n#define EOS_NAMESPACESTATS_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"namespace/interface/INamespaceStats.hh\"\n\nEOSMGMNAMESPACE_BEGIN\nclass NamespaceStats : public INamespaceStats {\npublic:\n  NamespaceStats() = default;\n  virtual void Add(const char* tag, uid_t uid, gid_t gid, unsigned long val) override;\n  virtual void AddExec(const char* tag, float exectime) override;\n};\nEOSMGMNAMESPACE_END\n#endif // EOS_NAMESPACESTATS_HH\n"
  },
  {
    "path": "mgm/ofs/XrdMgmOfs.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdMgmOfs.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/CommentLog.hh\"\n#include \"common/Constants.hh\"\n#include \"common/Mapping.hh\"\n#include \"common/FileId.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/Path.hh\"\n#include \"common/SecEntity.hh\"\n#include \"common/StackTrace.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/ParseUtils.hh\"\n#include \"common/http/OwnCloud.hh\"\n#include \"common/JeMallocHandler.hh\"\n#include \"common/plugin_manager/Plugin.hh\"\n#include \"common/plugin_manager/DynamicLibrary.hh\"\n#include \"common/plugin_manager/PluginManager.hh\"\n#include \"common/Strerror_r_wrapper.hh\"\n#include \"common/BufferManager.hh\"\n#include \"common/BehaviourConfig.hh\"\n#include \"common/Definitions.hh\"\n#include \"namespace/Constants.hh\"\n#include \"namespace/interface/ContainerIterators.hh\"\n#include \"namespace/utils/Attributes.hh\"\n#include \"namespace/utils/Checksum.hh\"\n#include \"namespace/utils/RenameSafetyCheck.hh\"\n#include \"namespace/utils/Stat.hh\"\n#include \"namespace/utils/Etag.hh\"\n#include \"grpc/GrpcServer.hh\"\n#include \"grpc/GrpcWncServer.hh\"\n#include \"grpc/GrpcRestGwServer.hh\"\n#include \"mgm/misc/Constants.hh\"\n#include \"mgm/adminsocket/AdminSocket.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/filesystem/FileSystem.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/authz/XrdMgmAuthz.hh\"\n#include \"mgm/ofs/XrdMgmOfsDirectory.hh\"\n#include \"mgm/ofs/XrdMgmOfsFile.hh\"\n#include \"mgm/ofs/XrdMgmOfsTrace.hh\"\n#include \"mgm/ofs/XrdMgmOfsSecurity.hh\"\n#include \"mgm/commandmap/CommandMap.hh\"\n#include \"mgm/policy/Policy.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/acl/Acl.hh\"\n#include \"mgm/workflow/Workflow.hh\"\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/recycle/Recycle.hh\"\n#include \"mgm/devices/Devices.hh\"\n#include \"mgm/pathrouting/PathRouting.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/geotreeengine/GeoTreeEngine.hh\"\n#include \"mgm/egroup/Egroup.hh\"\n#include \"mgm/http/HttpServer.hh\"\n#include \"mgm/zmq/ZMQ.hh\"\n#include \"mgm/iostat/Iostat.hh\"\n#include \"mgm/lru/LRU.hh\"\n#include \"mgm/wfe/WFE.hh\"\n#include \"mgm/fsck/Fsck.hh\"\n#include \"mgm/imaster/IMaster.hh\"\n#include \"mgm/convert/ConverterEngine.hh\"\n#include \"mgm/FuseServer/FusexCastBatch.hh\"\n#include \"mgm/tgc/RealTapeGcMgm.hh\"\n#include \"mgm/tgc/MultiSpaceTapeGc.hh\"\n#include \"mgm/tracker/ReplicationTracker.hh\"\n#include \"mgm/ofs/fsctl/CommitHelper.hh\"\n#include \"mgm/xattr/XattrLock.hh\"\n#include \"mgm/auth/AccessChecker.hh\"\n#include \"mgm/config/IConfigEngine.hh\"\n#include \"mgm/bulk-request/prepare/manager/PrepareManager.hh\"\n#include \"mgm/placement/FsScheduler.hh\"\n#include \"mq/SharedHashWrapper.hh\"\n#include \"mq/FsChangeListener.hh\"\n#include \"mq/GlobalConfigChangeListener.hh\"\n#include \"mq/MessagingRealm.hh\"\n#include \"mq/QdbListener.hh\"\n#include \"namespace/interface/IFsView.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include <XrdVersion.hh>\n#include <XrdOss/XrdOss.hh>\n#include <XrdOuc/XrdOucBuffer.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucTokenizer.hh>\n#include <XrdOuc/XrdOucTList.hh>\n#include <XrdOuc/XrdOucTrace.hh>\n#include <XrdSys/XrdSysError.hh>\n#include <XrdSys/XrdSysLogger.hh>\n#include <XrdSys/XrdSysPthread.hh>\n#include <XrdSec/XrdSecInterface.hh>\n#include <XrdSfs/XrdSfsAio.hh>\n#include <XrdSfs/XrdSfsFlags.hh>\n#include <XrdSfs/XrdSfsFAttr.hh>\n#include \"google/protobuf/io/zero_copy_stream_impl.h\"\n#include \"mgm/bulk-request/dao/factories/AbstractDAOFactory.hh\"\n#include \"mgm/bulk-request/dao/factories/ProcDirectoryDAOFactory.hh\"\n#include \"mgm/bulk-request/business/BulkRequestBusiness.hh\"\n#include \"mgm/bulk-request/interface/RealMgmFileSystemInterface.hh\"\n#include \"mgm/bulk-request/prepare/manager/BulkRequestPrepareManager.hh\"\n#include \"mgm/bulk-request/dao/proc/ProcDirectoryBulkRequestLocations.hh\"\n#include \"mgm/bulk-request/response/QueryPrepareResponse.hh\"\n#include \"mgm/bulk-request/prepare/query-prepare/QueryPrepareResult.hh\"\n#include \"mgm/bulk-request/dao/proc/cleaner/BulkRequestProcCleaner.hh\"\n#include \"mgm/bulk-request/utils/json/QueryPrepareResponseJson.hh\"\n#include \"mgm/http/rest-api/handler/tape/TapeRestHandler.hh\"\n#include \"mgm/http/rest-api/manager/RestApiManager.hh\"\n#include \"mgm/utils/AttrHelper.hh\"\n\n#ifdef __APPLE__\n#define ECOMM 70\n#endif\n\n#ifndef S_IAMB\n#define S_IAMB  0x1FF\n#endif\n\n// Initialize static variables\nXrdSysError* XrdMgmOfs::eDest;\nthread_local eos::common::LogId XrdMgmOfs::tlLogId;\nXrdSysError gMgmOfsEroute(0);\nXrdOucTrace gMgmOfsTrace(&gMgmOfsEroute);\nXrdMgmOfs* gOFS = 0;\n\n// Set the version information\nXrdVERSIONINFO(XrdSfsGetFileSystem, MgmOfs);\nXrdVERSIONINFO(XrdSfsGetFileSystem2, MgmOfs);\n\n//------------------------------------------------------------------------------\n// Convert NamespaceState to string\n//------------------------------------------------------------------------------\nstd::string namespaceStateToString(NamespaceState st)\n{\n  switch (st) {\n  case NamespaceState::kDown: {\n    return \"down\";\n  }\n\n  case NamespaceState::kBooting: {\n    return \"booting\";\n  }\n\n  case NamespaceState::kBooted: {\n    return \"booted\";\n  }\n\n  case NamespaceState::kFailed: {\n    return \"failed\";\n  }\n\n  case NamespaceState::kCompacting: {\n    return \"compacting\";\n  }\n  }\n\n  return \"(invalid)\";\n}\n\nextern \"C\" {\n\n#ifdef COVERAGE_BUILD\n// Forward declaration of gcov flush API\n  extern \"C\" void __gcov_dump(void);\n#endif\n\n  //------------------------------------------------------------------------------\n  // XrdAccAuthorizeObject() is called to obtain an instance of the auth object\n  // that will be used for all subsequent authorization decisions. If it returns\n  // a null pointer; initialization fails and the program exits. The args are:\n  //\n  // lp    -> XrdSysLogger to be tied to an XrdSysError object for messages\n  // cfn   -> The name of the configuration file\n  // parm  -> Paramexters specified on the authlib directive. If none it is zero.\n  //------------------------------------------------------------------------------\n  XrdAccAuthorize* XrdAccAuthorizeObject(XrdSysLogger* lp,\n                                         const char*   cfn,\n                                         const char*   parm);\n\n  //------------------------------------------------------------------------------\n  //! Filesystem Plugin factory function\n  //!\n  //! @param native_fs (not used)\n  //! @param lp the logger object\n  //! @param configfn the configuration file name\n  //!\n  //! @returns configures and returns our MgmOfs object\n  //------------------------------------------------------------------------------\n  XrdSfsFileSystem*\n  XrdSfsGetFileSystem(XrdSfsFileSystem* native_fs,\n                      XrdSysLogger* lp,\n                      const char* configfn)\n  {\n    if (gOFS) {\n      // File system object already initalized\n      return gOFS;\n    }\n\n    gMgmOfsEroute.SetPrefix(\"MgmOfs_\");\n    gMgmOfsEroute.logger(lp);\n    static XrdMgmOfs myFS(&gMgmOfsEroute);\n    XrdOucString vs = \"MgmOfs (meta data redirector) \";\n    vs += VERSION;\n    gMgmOfsEroute.Say(\"++++++ (c) 2015 CERN/IT-DSS \", vs.c_str());\n\n    // Initialize the subsystems\n    if (!myFS.Init(gMgmOfsEroute)) {\n      return nullptr;\n    }\n\n    // Disable XRootd log rotation\n    lp->setRotate(0);\n    gOFS = &myFS;\n    // By default enable stalling and redirection\n    gOFS->IsStall = true;\n    gOFS->IsRedirect = true;\n    myFS.ConfigFN = (configfn && *configfn ? strdup(configfn) : nullptr);\n\n    if (myFS.Configure(gMgmOfsEroute)) {\n      return nullptr;\n    }\n\n    // Initialize authorization plugin XrdMgmAuthz\n    gOFS->mMgmAuthz = (XrdMgmAuthz*) XrdAccAuthorizeObject(lp, configfn,\n                      nullptr);\n\n    if (!gOFS->mMgmAuthz) {\n      return nullptr;\n    }\n\n    return gOFS;\n  }\n\n//------------------------------------------------------------------------------\n//! Filesystem Plugin factory function\n//!\n//! @description FileSystem2 version, to allow passing configuration info back\n//!              to XRootD. Configure with: xrootd.fslib -2 libXrdEosMgm.so\n//!\n//! @param native_fs (not used)\n//! @param lp the logger object\n//! @param configfn the configuration file name\n//! @param envP pass configuration information back to XrdXrootd\n//!\n//! @returns configures and returns our MgmOfs object\n//------------------------------------------------------------------------------\n  XrdSfsFileSystem*\n  XrdSfsGetFileSystem2(XrdSfsFileSystem* native_fs,\n                       XrdSysLogger* lp,\n                       const char* configfn,\n                       XrdOucEnv* envP)\n  {\n    // Initialise gOFS\n    XrdSfsGetFileSystem(native_fs, lp, configfn);\n\n    // Tell XRootD that MgmOfs implements the Prepare plugin\n    if (envP != nullptr) {\n      envP->Put(\"XRD_PrepHandler\", \"1\");\n    }\n\n    return gOFS;\n  }\n\n} // extern \"C\"\n\n/******************************************************************************/\n/* MGM Meta Data Interface                                                    */\n/******************************************************************************/\n\n//------------------------------------------------------------------------------\n// Constructor MGM Ofs\n//------------------------------------------------------------------------------\nXrdMgmOfs::XrdMgmOfs(XrdSysError* ep):\n  ConfigFN(0), mConfigEngine(0), mCapabilityValidity(3600),\n  ManagerPort(1094), LinuxStatsStartup{0},\n  HostName(0), HostPref(0),   protowfusegrpc(false),\n  mNamespaceState(NamespaceState::kDown),\n  mFileInitTime(0), mTotalInitTime(time(nullptr)), mStartTime(time(nullptr)),\n  Shutdown(false), mBootFileId(0), mBootContainerId(0), IsRedirect(true),\n  IsStall(true), mAuthorize(false), mAuthLib(\"\"), mTapeEnabled(false),\n  mReqIdMax(64),\n  MgmRedirector(false), mErrLogEnabled(true), eosDirectoryService(0),\n  eosFileService(0), eosView(0), eosFsView(0), eosContainerAccounting(0),\n  eosSyncTimeAccounting(0), mFrontendPort(0), mNumAuthThreads(0),\n  mFrontendLocalhost(1), zMQ(nullptr),\n  mMgmAuthz(nullptr), mTokenAuthz(nullptr), mExtAuthz(nullptr),\n  MgmStatsPtr(new eos::mgm::Stat()),  MgmStats(*MgmStatsPtr),\n  mFsckEngine(new Fsck()), mMaster(nullptr),\n  mRouting(new eos::mgm::PathRouting()), mConverterEngine(),\n  mHttpd(nullptr), GRPCd(nullptr), WNCd(nullptr), mRestGrpcSrv(nullptr),\n  mLRUEngine(new eos::mgm::LRU()),\n  WFEPtr(new eos::mgm::WFE()), WFEd(*WFEPtr), mFstGwHost(\"\"),\n  mFstGwPort(0), mQdbCluster(\"\"), mHttpdPort(8000),\n  mFusexPort(1100), mGRPCPort(50051), mWncPort(50052),\n  mRestGrpcPort(50054),\n  mFidTracker(std::chrono::seconds(600), std::chrono::seconds(3600)),\n  mBehaviourCfg(new eos::common::BehaviourConfig()),\n  mDoneOrderlyShutdown(false),\n  mXrdBuffPool(2 * eos::common::KB, 2 * eos::common::MB, 8, 64),\n  mJeMallocHandler(new eos::common::JeMallocHandler())\n{\n  eDest = ep;\n  ConfigFN = 0;\n\n  if (getenv(\"EOS_MGM_HTTP_PORT\")) {\n    mHttpdPort = strtol(getenv(\"EOS_MGM_HTTP_PORT\"), 0, 10);\n  }\n\n  if (getenv(\"EOS_MGM_FUSEX_PORT\")) {\n    mFusexPort = strtol(getenv(\"EOS_MGM_FUSEX_PORT\"), 0, 10);\n  }\n\n  if (getenv(\"EOS_MGM_GRPC_PORT\")) {\n    mGRPCPort = strtol(getenv(\"EOS_MGM_GRPC_PORT\"), 0, 10);\n  }\n\n  if (getenv(\"EOS_MGM_WNC_PORT\")) {\n    mWncPort = strtol(getenv(\"EOS_MGM_WNC_PORT\"), 0, 10);\n  }\n\n  if (getenv(\"EOS_MGM_FUSE_BOOKING_SIZE\")) {\n    mFusePlacementBooking = strtol(getenv(\"EOS_MGM_FUSE_BOOKING_SIZE\"), 0, 10);\n  } else {\n    mFusePlacementBooking = 5 * 1024 * 1024 * 1024ll;\n  }\n\n  mRestApiManager = std::make_unique<rest::RestApiManager>();\n  eos::common::LogId::SetSingleShotLogId();\n  mZmqContext = new zmq::context_t(1);\n  mIoStats.reset(new eos::mgm::Iostat());\n  mHttpd.reset(new eos::mgm::HttpServer(mHttpdPort));\n\n  if (mGRPCPort) {\n    GRPCd.reset(new eos::mgm::GrpcServer(mGRPCPort));\n  }\n\n  if (mWncPort) {\n    WNCd.reset(new eos::mgm::GrpcWncServer(mWncPort));\n  }\n\n  if (getenv(\"EOS_MGM_REST_GRPC_PORT\")) {\n    mRestGrpcPort = strtol(getenv(\"EOS_MGM_REST_GRPC_PORT\"), 0, 10);\n  }\n\n  if (mRestGrpcPort) {\n    const char* ptr = getenv(\"EOS_MGM_ENABLE_REST_API\");\n\n    if (ptr && strncmp(ptr, \"1\", 1) == 0) {\n      mRestGrpcSrv.reset(new eos::mgm::GrpcRestGwServer(mRestGrpcPort));\n    }\n  }\n\n  // Parse audit environment configuration\n  {\n    const char* am = getenv(\"EOS_MGM_AUDIT\");\n    std::string mode = (am ? am : \"\");\n\n    // normalize to lowercase\n    for (auto& c : mode) {\n      c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));\n    }\n\n    if (mode.empty() || mode == \"none\" || mode == \"false\" || mode == \"no\" ||\n        mode == \"off\") {\n      mEnvAuditDisableAll = true;\n      mEnvAuditAttributeMode = false;\n      mEnvAuditAttributeOnly = false;\n    } else if (mode == \"default\") {\n      mEnvAuditAttributeMode = true;\n      mEnvAuditAttributeOnly = false;\n      mEnvAuditRead = true;      // default suffix list\n      mEnvAuditList = false;\n      mEnvAuditReadAll = false;\n    } else if (mode == \"modifications\") {\n      mEnvAuditAttributeMode = true;\n      mEnvAuditAttributeOnly = false;\n      mEnvAuditRead = false;\n      mEnvAuditList = false;\n      mEnvAuditReadAll = false;\n    } else if (mode == \"detail\") {\n      mEnvAuditAttributeMode = true;\n      mEnvAuditAttributeOnly = false;\n      mEnvAuditRead = true;\n      mEnvAuditReadAll = true;   // read all files\n      mEnvAuditList = false;\n    } else if (mode == \"all\") {\n      mEnvAuditAttributeMode = true;\n      mEnvAuditAttributeOnly = false;\n      mEnvAuditRead = true;\n      mEnvAuditReadAll = true;\n      mEnvAuditList = true;\n    } else if (mode == \"attribute\") {\n      // Create audit object but disable all globally; only sys.audit enables\n      mEnvAuditDisableAll = false;\n      mEnvAuditAttributeMode = true;\n      mEnvAuditAttributeOnly = true;\n      mEnvAuditRead = false;\n      mEnvAuditReadAll = false;\n      mEnvAuditList = false;\n    }\n\n    const char* rs = getenv(\"EOS_MGM_AUDIT_READ_SUFFIX\");\n\n    if (rs && *rs) {\n      std::string s = rs;\n      std::string token;\n\n      for (size_t i = 0; i <= s.size(); ++i) {\n        if (i == s.size() || s[i] == ',' || s[i] == ';' || s[i] == ' ') {\n          if (!token.empty()) {\n            // normalize\n            for (auto& c : token) c = static_cast<char>(\n                                          std::tolower(static_cast<unsigned char>(c)));\n\n            if (!token.empty() && token[0] == '.') {\n              token.erase(token.begin());\n            }\n\n            mEnvAuditReadSuffixes.push_back(token);\n            token.clear();\n          }\n        } else {\n          token.push_back(s[i]);\n        }\n      }\n\n      mEnvAuditReadSuffixesSet = true;\n    }\n  }\n  EgroupRefresh.reset(new eos::mgm::Egroup());\n  mRecycler.reset(new eos::mgm::Recycle());\n  mDeviceTracker.reset(new eos::mgm::Devices());\n  mTapeGcMgm.reset(new tgc::RealTapeGcMgm(*this));\n  mTapeGc.reset(new tgc::MultiSpaceTapeGc(*mTapeGcMgm));\n  mFsScheduler.reset(new eos::mgm::placement::FSScheduler());\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nXrdMgmOfs::~XrdMgmOfs()\n{\n  OrderlyShutdown();\n  eos_warning(\"%s\", \"msg=\\\"finished destructor\\\"\");\n\n  if (HostName) {\n    free(HostName);\n  }\n  if (HostPref) {\n    free(HostPref);\n  }\n  if (ConfigFN) {\n    free(ConfigFN);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Destroy member objects and clean up threads\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::OrderlyShutdown()\n{\n  if (mDoneOrderlyShutdown) {\n    eos_warning(\"%s\", \"msg=\\\"skipping already done shutdown procedure\\\"\");\n    return;\n  }\n\n  auto start_ts = std::chrono::steady_clock::now();\n  mDoneOrderlyShutdown = true;\n  {\n    eos_warning(\"%s\", \"msg=\\\"set stall rule of all ns operations\\\"\");\n    eos::common::RWMutexWriteLock lock(Access::gAccessMutex);\n    Access::gStallRules[std::string(\"*\")] = \"300\";\n  }\n  gOFS->mTracker.SetAcceptingRequests(false);\n  gOFS->mTracker.SpinUntilNoRequestsInFlight(true,\n      std::chrono::milliseconds(100));\n  eos_warning(\"%s\", \"msg=\\\"stopping error logger thread\\\"\");\n  mErrLoggerTid.join();\n  eos_warning(\"%s\", \"msg=\\\"stopping fs listener thread\\\"\");\n  auto stop_fsconfiglistener = std::thread([&]() {\n    mFsConfigTid.join();\n  });\n  stop_fsconfiglistener.join();\n  eos_warning(\"%s\", \"msg=\\\"disable configuration engine autosave\\\"\");\n  mConfigEngine->SetAutoSave(false);\n  // Delete mConfigEngine object otherwise this might be reused by the\n  // FsView if the ConfigResetMonitor runs at some point after we set\n  // the ConfigEngine reference to null in the FsView below.\n  delete mConfigEngine;\n  mConfigEngine = nullptr;\n  FsView::gFsView.SetConfigEngine(nullptr);\n  eos_warning(\"%s\", \"msg=\\\"stop routing\\\"\");\n\n  if (mRouting) {\n    mRouting.reset();\n  }\n\n  eos_warning(\"%s\", \"msg=\\\"stopping the stats collecting thread\\\"\");\n  eos_warning(\"%s\", \"msg=\\\"stopping archive submitter\\\"\");\n  mSubmitterTid.join();\n\n  if (mZmqContext) {\n    eos_warning(\"%s\", \"msg=\\\"closing the ZMQ context\\\"\");\n    mZmqContext->close();\n    eos_warning(\"%s\", \"msg=\\\"joining the master and worker auth threads\\\"\");\n    mAuthMasterTid.join();\n\n    for (const auto& auth_tid : mVectTid) {\n      XrdSysThread::Join(auth_tid, nullptr);\n    }\n\n    mVectTid.clear();\n    eos_warning(\"%s\", \"msg=\\\"deleting the ZMQ context\\\"\");\n    delete mZmqContext;\n  }\n\n  eos_warning(\"%s\", \"msg=\\\"stopping converter engine\\\"\");\n  mConverterEngine->Stop();\n  eos_warning(\"%s\", \"msg=\\\"stopping central drainning\\\"\");\n  mDrainEngine.Stop();\n  eos_warning(\"%s\", \"msg=\\\"stopping geotree engine updater\\\"\");\n  mGeoTreeEngine->StopUpdater();\n\n  if (mIoStats) {\n    eos_warning(\"%s\", \"msg=\\\"stopping and deleting IoStats\\\"\");\n    mIoStats.reset();\n  }\n\n  eos_warning(\"%s\", \"msg=\\\"stopping fusex server\\\"\");\n  zMQ->gFuseServer.shutdown();\n  // TODO: for now removing this since it breaks Centos8/9 shutdown\n  /*  if (zMQ) {\n    delete zMQ;\n    zMQ = nullptr;\n  }\n  */\n  eos_warning(\"%s\", \"msg=\\\"stopping FSCK service\\\"\");\n  mFsckEngine->Stop();\n  eos_warning(\"%s\", \"msg=\\\"stopping messaging\\\"\");\n\n  if (mRecycler) {\n    eos_warning(\"%s\", \"msg=\\\"stopping and deleting recycler server\\\"\");\n    mRecycler.reset();\n  }\n\n  if (WFEPtr) {\n    eos_warning(\"%s\", \"msg=\\\"stopping and deleting the WFE engine\\\"\");\n    WFEPtr.reset();\n  }\n\n  eos_warning(\"%s\", \"msg=\\\"stopping and deleting the LRU engine\\\"\");\n  mLRUEngine.reset();\n\n  if (EgroupRefresh) {\n    eos_warning(\"%s\", \"msg=\\\"stopping and deleting egroup refresh thread\\\"\");\n    EgroupRefresh.reset();\n  }\n\n  if (mHttpd) {\n    eos_warning(\"%s\", \"msg=\\\"stopping and deleting HTTP daemon\\\"\");\n    mHttpd.reset();\n  }\n\n  if (WNCd) {\n    eos_warning(\"%s\", \"msg=\\\"stopping gRPC server for EOS-wnc\\\"\");\n    WNCd.reset();\n  }\n\n  eos_warning(\"%s\", \"msg=\\\"cleanup quota information\\\"\");\n  (void) Quota::CleanUp();\n  eos_warning(\"%s\", \"msg=\\\"graceful shutdown of the FsView\\\"\");\n  FsView::gFsView.StopHeartBeat();\n  FsView::gFsView.Clear();\n\n  if (mErrLogEnabled) {\n    eos_warning(\"%s\", \"msg=\\\"error log kill\\\"\");\n    std::string errorlogkillline = \"pkill -9 -f \\\"eos -b console log _MGMID_\\\"\";\n    int rrc = system(errorlogkillline.c_str());\n\n    if (WEXITSTATUS(rrc)) {\n      eos_static_info(\"%s returned %d\", errorlogkillline.c_str(), rrc);\n    }\n  }\n\n  if (gOFS->mNamespaceState == NamespaceState::kBooted) {\n    eos_warning(\"%s\", \"msg=\\\"finalizing namespace views\\\"\");\n\n    try {\n      gOFS->eosDirectoryService = nullptr;\n      gOFS->eosFileService = nullptr;\n      gOFS->eosView = nullptr;\n      gOFS->eosFsView = nullptr;\n      gOFS->eosContainerAccounting = nullptr;\n      gOFS->eosSyncTimeAccounting = nullptr;\n      gOFS->namespaceGroup.reset();\n    } catch (eos::MDException& e) {\n      // we don't really care about any exception here!\n    }\n  }\n\n  eos_warning(\"%s\", \"msg=\\\"stopping master-slave supervisor thread\\\"\");\n\n  if (mMaster) {\n    mMaster.reset();\n  }\n\n  auto end_ts = std::chrono::steady_clock::now();\n  eos_warning(\"msg=\\\"finished orderly shutdown in %llu seconds\\\"\",\n              std::chrono::duration_cast<std::chrono::seconds>\n              (end_ts - start_ts).count());\n\n  mTrafficShapingEngine.Stop();\n}\n\n//------------------------------------------------------------------------------\n// This is just kept to be compatible with standard OFS plugins, but it is not\n// used for the moment.\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::Init(XrdSysError& ep)\n{\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Return a MGM directory object\n//------------------------------------------------------------------------------\nXrdSfsDirectory*\nXrdMgmOfs::newDir(char* user, int MonID)\n{\n  return (XrdSfsDirectory*)new XrdMgmOfsDirectory(user, MonID);\n}\n\n//------------------------------------------------------------------------------\n// Return MGM file object\n//------------------------------------------------------------------------------\nXrdSfsFile*\nXrdMgmOfs::newFile(char* user, int MonID)\n{\n  return (XrdSfsFile*)new XrdMgmOfsFile(user, MonID);\n}\n\n//------------------------------------------------------------------------------\n// Notify filesystem that a client has disconnected\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::Disc(const XrdSecEntity* client)\n{\n  if (client) {\n    ProcInterface::DropSubmittedCmd(client->tident);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Implementation Source Code Includes\n//------------------------------------------------------------------------------\n#include \"ofs/cmds/Access.inc\"\n#include \"ofs/cmds/Attr.inc\"\n#include \"ofs/cmds/Auth.inc\"\n#include \"ofs/cmds/Chksum.inc\"\n#include \"ofs/cmds/Chmod.inc\"\n#include \"ofs/cmds/Chown.inc\"\n#include \"ofs/cmds/Coverage.inc\"\n#include \"ofs/cmds/DeleteExternal.inc\"\n#include \"ofs/cmds/DropReplica.inc\"\n#include \"ofs/cmds/ErrorLogListener.inc\"\n#include \"ofs/cmds/Exists.inc\"\n#include \"ofs/cmds/FAttr.inc\"\n#include \"ofs/cmds/Find.inc\"\n#include \"ofs/cmds/FsConfigListener.inc\"\n#include \"ofs/cmds/Fsctl.inc\"\n#include \"ofs/cmds/Link.inc\"\n#include \"ofs/cmds/Mkdir.inc\"\n#include \"ofs/cmds/PathMap.inc\"\n#include \"ofs/cmds/Remdir.inc\"\n#include \"ofs/cmds/Rename.inc\"\n#include \"ofs/cmds/Rm.inc\"\n#include \"ofs/cmds/SharedPath.inc\"\n#include \"ofs/cmds/ShouldRedirect.inc\"\n#include \"ofs/cmds/ShouldRoute.inc\"\n#include \"ofs/cmds/ShouldStall.inc\"\n#include \"ofs/cmds/Shutdown.inc\"\n#include \"ofs/cmds/Stacktrace.inc\"\n#include \"ofs/cmds/Stat.inc\"\n#include \"ofs/cmds/Stripes.inc\"\n#include \"ofs/cmds/Touch.inc\"\n#include \"ofs/cmds/Utimes.inc\"\n#include \"ofs/cmds/Version.inc\"\n\n//------------------------------------------------------------------------------\n// Test for stall rule\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::HasStall(const char* path,\n                    const char* rule,\n                    int& stalltime,\n                    XrdOucString& stallmsg)\n{\n  if (!rule) {\n    return false;\n  }\n\n  eos::common::RWMutexReadLock lock(Access::gAccessMutex);\n\n  if (Access::gStallRules.count(std::string(rule))) {\n    stalltime = atoi(Access::gStallRules[std::string(rule)].c_str());\n    stallmsg =\n      \"Attention: you are currently hold in this instance and each request is stalled for \";\n    stallmsg += (int) stalltime;\n    stallmsg += \" seconds after an errno of type: \";\n    stallmsg += rule;\n    eos_static_info(\"info=\\\"stalling\\\" path=\\\"%s\\\" errno=\\\"%s\\\"\", path, rule);\n    return true;\n  } else {\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Test for redirection rule\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::HasRedirect(const char* path, const char* rule, std::string& host,\n                       int& port)\n{\n  if (!rule) {\n    return false;\n  }\n\n  std::string srule = rule;\n  eos::common::RWMutexReadLock lock(Access::gAccessMutex);\n\n  if (Access::gRedirectionRules.count(srule)) {\n    std::string delimiter = \":\";\n    std::vector<std::string> tokens;\n    eos::common::StringConversion::Tokenize(Access::gRedirectionRules[srule],\n                                            tokens, delimiter);\n\n    if (tokens.size() == 1) {\n      host = tokens[0].c_str();\n      port = 1094;\n    } else {\n      host = tokens[0].c_str();\n      port = atoi(tokens[1].c_str());\n\n      if (port == 0) {\n        port = 1094;\n      }\n    }\n\n    eos_static_info(\"info=\\\"redirect\\\" path=\\\"%s\\\" host=%s port=%d errno=%s\",\n                    path, host.c_str(), port, rule);\n\n    if (srule == \"ENONET\") {\n      gOFS->MgmStats.Add(\"RedirectENONET\", 0, 0, 1);\n    } else if (srule == \"ENOENT\") {\n      gOFS->MgmStats.Add(\"RedirectENOENT\", 0, 0, 1);\n    } else if (srule == \"ENETUNREACH\") {\n      gOFS->MgmStats.Add(\"RedirectENETUNREACH\", 0, 0, 1);\n    }\n\n    return true;\n  } else {\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Return the version of the MGM software\n//------------------------------------------------------------------------------\nconst char*\nXrdMgmOfs::getVersion()\n{\n  static XrdOucString FullVersion = XrdVERSION;\n  FullVersion += \" MgmOfs \";\n  FullVersion += VERSION;\n  return FullVersion.c_str();\n}\n\n//-------------------------------------------------------------------------------------\n// Prepare a file or query the status of a previous prepare request\n//-------------------------------------------------------------------------------------\nint\nXrdMgmOfs::prepare(XrdSfsPrep& pargs, XrdOucErrInfo& error,\n                   const XrdSecEntity* client)\n{\n  if (pargs.opts & Prep_QUERY) {\n    return _prepare_query(pargs, error, client);\n  } else {\n    return _prepare(pargs, error, client);\n  }\n}\n\n\n//--------------------------------------------------------------------------------------\n// Prepare a file\n//\n// EOS will call a prepare workflow if defined\n//--------------------------------------------------------------------------------------\nint\nXrdMgmOfs::_prepare(XrdSfsPrep& pargs, XrdOucErrInfo& error,\n                    const XrdSecEntity* client)\n{\n  USE_EOSBULKNAMESPACE;\n  PrepareManager pm(std::make_unique<RealMgmFileSystemInterface>(gOFS));\n  int prepareRetCode = pm.prepare(pargs, error, client);\n  return prepareRetCode;\n}\n\n\n//-------------------------------------------------------------------------------------------\n// Query the status of a previous prepare request\n//-------------------------------------------------------------------------------------------\nint\nXrdMgmOfs::_prepare_query(XrdSfsPrep& pargs, XrdOucErrInfo& error,\n                          const XrdSecEntity* client)\n{\n  USE_EOSBULKNAMESPACE;\n  RealMgmFileSystemInterface mgmFsInterface(gOFS);\n  PrepareManager pm(std::make_unique<RealMgmFileSystemInterface>(gOFS));\n  std::unique_ptr<QueryPrepareResult> result = pm.queryPrepare(pargs, error,\n      client);\n\n  if (result->hasQueryPrepareFinished()) {\n    //Create the JSON response\n    bulk::QueryPrepareResponseJson jsonQueryPrepareResponse;\n    std::stringstream json_ss;\n    auto queryPrepareResponse = result->getResponse();\n    queryPrepareResponse->setJsonifier(\n      std::make_shared<bulk::QueryPrepareResponseJson>());\n    jsonQueryPrepareResponse.jsonify(queryPrepareResponse.get(), json_ss);\n    // Send the reply. XRootD requires that we put it into a buffer that can be released with free().\n    auto  json_len = json_ss.str().length();\n    char* json_buf = reinterpret_cast<char*>(malloc(json_len));\n    strncpy(json_buf, json_ss.str().c_str(), json_len);\n    // Ownership of this buffer is passed to xrd_buff which has a Recycle() method.\n    XrdOucBuffer* xrd_buff = new XrdOucBuffer(json_buf, json_len);\n    // Ownership of xrd_buff is passed to error. Note that as we are returning SFS_DATA, the first\n    // parameter is the buffer length rather than an error code.\n    error.setErrInfo(xrd_buff->BuffSize(), xrd_buff);\n  }\n\n  return result->getReturnCode();\n}\n\n\n//------------------------------------------------------------------------------\n//! Truncate a file (not supported in EOS, only via the file interface)\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::truncate(const char*,\n                    XrdSfsFileOffset,\n                    XrdOucErrInfo& error,\n                    const XrdSecEntity* client,\n                    const char* path)\n{\n  static const char* epname = \"truncate\";\n  const char* tident = error.getErrUser();\n  // use a thread private vid\n  eos::common::VirtualIdentity vid;\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, 0, tident, vid);\n  EXEC_TIMING_END(\"IdMap\");\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  ACCESSMODE_W;\n  MAYSTALL;\n  const char* inpath = \"/\";\n  const char* ininfo = \"\";\n  MAYREDIRECT;\n  gOFS->MgmStats.Add(\"Truncate\", vid.uid, vid.gid, 1);\n  return Emsg(epname, error, EOPNOTSUPP, \"truncate\", path);\n}\n\n//------------------------------------------------------------------------------\n// Return error message\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::Emsg(const char* pfx,\n                XrdOucErrInfo& einfo,\n                int ecode,\n                const char* op,\n                const char* target)\n{\n  char etext[128], buffer[4096];\n\n  // Get the reason for the error\n  if (ecode < 0) {\n    ecode = -ecode;\n  }\n\n  if (eos::common::strerror_r(ecode, etext, sizeof(etext))) {\n    sprintf(etext, \"reason unknown (%d)\", ecode);\n  }\n\n  // Format the error message\n  snprintf(buffer, sizeof(buffer), \"Unable to %s: %s; %s\", op, etext, target);\n\n  if ((ecode == EIDRM) || (ecode == ENODATA)) {\n    eos_debug(\"msg=\\\"Unable to %s: %s\\\" path=\\\"%s\\\"\", op, etext, target);\n  } else {\n    if ((!strcmp(op, \"stat\")) || (((!strcmp(pfx, \"attr_get\")) ||\n                                   (!strcmp(pfx, \"attr_ls\")) ||\n                                   (!strcmp(pfx, \"FuseX\"))) && (ecode == ENOENT))) {\n      eos_debug(\"msg=\\\"Unable to %s: %s\\\" path=\\\"%s\\\"\", op, etext, target);\n    } else {\n      eos_err(\"msg=\\\"Unable to %s: %s\\\" path=\\\"%s\\\"\", op, etext, target);\n    }\n  }\n\n  // Print it out if debugging is enabled\n#ifndef NODEBUG\n  //   XrdMgmOfs::eDest->Emsg(pfx, buffer);\n#endif\n  // Place the error message in the error object and return\n  einfo.setErrInfo(ecode, buffer);\n  return SFS_ERROR;\n}\n\n\n//------------------------------------------------------------------------------\n// Create stall response\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::Stall(XrdOucErrInfo& error, eos::common::VirtualIdentity& vid,\n                 int stime, const char* msg)\n\n{\n  XrdOucString smessage = msg;\n  smessage += \"; come back in \";\n  smessage += stime;\n  smessage += \" seconds!\";\n  eos_info(\"msg=\\\"%s\\\"\", smessage.c_str());\n  // Place the error message in the error object and return\n  error.setErrInfo(0, smessage.c_str());\n  return stime;\n}\n\n//------------------------------------------------------------------------------\n// Create redirect response\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::Redirect(XrdOucErrInfo& error,\n                    const char* host,\n                    int& port,\n                    const char* path,\n                    bool collapse)\n{\n  EPNAME(\"Redirect\");\n  const char* tident = error.getErrUser();\n  ZTRACE(delay, \"Redirect \" << host << \":\" << port);\n\n  if (collapse && strlen(path)) {\n    std::string url = \"root://\";\n    url += host;\n    url += \":\";\n    url += std::to_string(port);\n    url += \"/\";\n    url += path;\n    error.setErrInfo(~(~(-1) | kXR_collapseRedir), url.c_str());\n  } else {\n    // Place the error message in the error object and return\n    error.setErrInfo(port, host);\n  }\n\n  return SFS_REDIRECT;\n}\n\n//------------------------------------------------------------------------------\n// Start a thread that will queue, build and submit backup operations to the\n// archiver daemon.\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::ArchiveSubmitterThread(ThreadAssistant& assistant) noexcept\n{\n  ProcCommand pcmd;\n  std::string job_opaque;\n  XrdOucString std_out, std_err;\n  int max, running, pending;\n  eos::common::VirtualIdentity root_vid = eos::common::VirtualIdentity::Root();\n  eos_debug(\"msg=\\\"starting archive/backup submitter thread\\\"\");\n  std::ostringstream cmd_json;\n  cmd_json << \"{\\\"cmd\\\": \\\"stats\\\", \"\n           << \"\\\"opt\\\": \\\"\\\", \"\n           << \"\\\"uid\\\": \\\"0\\\", \"\n           << \"\\\"gid\\\": \\\"0\\\" }\";\n\n  while (!assistant.terminationRequested()) {\n    {\n      XrdSysMutexHelper lock(mJobsQMutex);\n\n      if (!mPendingBkps.empty()) {\n        // Check if archiver has slots available\n        if (!pcmd.ArchiveExecuteCmd(cmd_json.str())) {\n          std_out.resize(0);\n          std_err.resize(0);\n          pcmd.AddOutput(std_out, std_err);\n\n          if ((sscanf(std_out.c_str(), \"max=%i running=%i pending=%i\",\n                      &max, &running, &pending) == 3)) {\n            while ((running + pending < max) && !mPendingBkps.empty()) {\n              running++;\n              job_opaque = mPendingBkps.back();\n              mPendingBkps.pop_back();\n              job_opaque += \"&mgm.backup.create=1\";\n\n              if (pcmd.open(\"/proc/admin\", job_opaque.c_str(), root_vid, 0)) {\n                pcmd.AddOutput(std_out, std_err);\n                eos_err(\"failed backup, msg=\\\"%s\\\"\", std_err.c_str());\n              }\n            }\n          }\n        } else {\n          eos_err(\"failed to send stats command to archive daemon\");\n        }\n      }\n    }\n    assistant.wait_for(std::chrono::seconds(5));\n  }\n\n  eos_warning(\"%s\", \"msg=\\\"shutdown archive submitter\\\"\");\n}\n\n//------------------------------------------------------------------------------\n// Submit backup job\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::SubmitBackupJob(const std::string& job_opaque)\n{\n  XrdSysMutexHelper lock(mJobsQMutex);\n  auto it = std::find(mPendingBkps.begin(), mPendingBkps.end(), job_opaque);\n\n  if (it == mPendingBkps.end()) {\n    mPendingBkps.push_front(job_opaque);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Get vector of pending backups\n//------------------------------------------------------------------------------\nstd::vector<ProcCommand::ArchDirStatus>\nXrdMgmOfs::GetPendingBkps()\n{\n  std::vector<ProcCommand::ArchDirStatus> bkps;\n  XrdSysMutexHelper lock(mJobsQMutex);\n\n  for (auto it = mPendingBkps.begin(); it != mPendingBkps.end(); ++it) {\n    XrdOucEnv opaque(it->c_str());\n    bkps.emplace_back(\"N/A\", \"N/A\", opaque.Get(\"mgm.backup.dst\"), \"backup\",\n                      \"pending at MGM\");\n  }\n\n  return bkps;\n}\n\n//------------------------------------------------------------------------------\n// Discover/search for a service provided to the plugins by the platform\n//------------------------------------------------------------------------------\nint32_t\nXrdMgmOfs::DiscoverPlatformServices(const char* svc_name, void* opaque)\n{\n  std::string sname = svc_name;\n\n  if (sname == \"NsViewMutex\") {\n    PF_Discovery_Service* ns_lock = (PF_Discovery_Service*)(opaque);\n    // TODO (esindril): Use this code when we drop SLC6 support @todo\n    //std::string htype = std::to_string(typeid(&gOFS->eosViewRWMutex).hash_code());\n    std::string htype = \"eos::common::RWMutex*\";\n    ns_lock->objType = (char*)calloc(htype.length() + 1, sizeof(char));\n    (void) strcpy(ns_lock->objType, htype.c_str());\n    ns_lock->ptrService = static_cast<void*>(&gOFS->eosViewRWMutex);\n  } else {\n    return EINVAL;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Cast a change message to all fusex clients about a deletion of an entry\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::FuseXCastDeletion(eos::ContainerIdentifier id,\n                             const std::string& name)\n{\n  struct timespec pt_mtime {\n    0, 0\n  };\n  gOFS->zMQ->gFuseServer.Cap().BroadcastDeletionFromExternal(\n    id.getUnderlyingUInt64(), name, pt_mtime);\n}\n\n\n\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::FuseXCastRefresh(eos::ContainerIdentifier id,\n                            eos::ContainerIdentifier parentid)\n{\n  gOFS->zMQ->gFuseServer.Cap().BroadcastRefreshFromExternal(\n    id.getUnderlyingUInt64(), parentid.getUnderlyingUInt64());\n}\n\nvoid\nXrdMgmOfs::FuseXCastRefresh(eos::FileIdentifier id,\n                            eos::ContainerIdentifier parentid)\n{\n  gOFS->zMQ->gFuseServer.Cap().BroadcastRefreshFromExternal(\n    eos::common::FileId::FidToInode(id.getUnderlyingUInt64()),\n    parentid.getUnderlyingUInt64());\n}\n\n//------------------------------------------------------------------------------\n// Cast a MD object to clients\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::FuseXCastMD(eos::ContainerIdentifier id,\n                       eos::ContainerIdentifier parentid,\n                       struct timespec& pt_mtime,\n                       bool lock)\n{\n  eos::fusex::md dir;\n  static eos::common::VirtualIdentity root_vid =\n    eos::common::VirtualIdentity::Root();\n\n  if (!gOFS->zMQ->gFuseServer.FillContainerMD(id.getUnderlyingUInt64(), dir,\n      root_vid, lock)) {\n    gOFS->zMQ->gFuseServer.Cap().BroadcastMD(dir, dir.md_ino(), dir.md_pino(),\n        dir.clock(), pt_mtime);\n  }\n}\n\nvoid\nXrdMgmOfs::FuseXCastMD(eos::FileIdentifier id,\n                       eos::ContainerIdentifier parentid,\n                       struct timespec& pt_mtime,\n                       bool lock\n                      )\n{\n  eos::fusex::md file;\n  static eos::common::VirtualIdentity root_vid =\n    eos::common::VirtualIdentity::Root();\n\n  if (gOFS->zMQ->gFuseServer.FillFileMD(eos::common::FileId::FidToInode(\n                                          id.getUnderlyingUInt64()), file, root_vid, lock)) {\n    gOFS->zMQ->gFuseServer.Cap().BroadcastMD(file, file.md_ino(), file.md_pino(),\n        file.clock(), pt_mtime);\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Check if namespace is booted\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::IsNsBooted() const\n{\n  return ((mNamespaceState == NamespaceState::kBooted) ||\n          (mNamespaceState == NamespaceState::kCompacting));\n}\n\n//------------------------------------------------------------------------------\n// Convert error code to string representation\n//------------------------------------------------------------------------------\nstd::string\nXrdMgmOfs::MacroStringError(int errcode)\n{\n  if (errcode == ENOTCONN) {\n    return \"ENOTCONN\";\n  } else if (errcode == EPROTO) {\n    return \"EPROTO\";\n  } else if (errcode == EAGAIN) {\n    return \"EAGAIN\";\n  } else {\n    return \"EINVAL\";\n  }\n}\n\n//------------------------------------------------------------------------------\n// Write report record for final deletion (to IoStats)\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::WriteRmRecord(const std::shared_ptr<eos::IFileMD>& fmd,\n                         const char* full_path)\n{\n  struct timespec ts_now;\n  char report[16384];\n  eos::IFileMD::ctime_t ctime;\n  eos::IFileMD::ctime_t mtime;\n  fmd->getCTime(ctime);\n  fmd->getMTime(mtime);\n  const std::string enc_path =\n    eos::common::StringConversion::curl_default_escaped(full_path);\n  eos::common::Timing::GetTimeSpec(ts_now);\n  snprintf(report, sizeof(report) - 1,\n           \"log=%s&path=%s&\"\n           \"host=%s&fid=%llu&fxid=%08llx&\"\n           \"ruid=%u&rgid=%u&\"\n           \"del_ts=%lu&del_tns=%lu&\"\n           \"dc_ts=%lu&dc_tns=%lu&\"\n           \"dm_ts=%lu&dm_tns=%lu&\"\n           \"dsize=%lu&sec.app=rm\",\n           this->logId,\n           (enc_path.empty() ? \"N/A\" : enc_path.c_str()),\n           gOFS->ManagerId.c_str(), (unsigned long long) fmd->getId(),\n           (unsigned long long) fmd->getId(), fmd->getCUid(), fmd->getCGid(),\n           ts_now.tv_sec, ts_now.tv_nsec, ctime.tv_sec, ctime.tv_nsec,\n           mtime.tv_sec, mtime.tv_nsec, fmd->getSize());\n  std::string record = report;\n\n  if (mIoStats) {\n    mIoStats->WriteRecord(record);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Write report record for recycle bin deletion (to IoStats)\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::WriteRecycleRecord(const std::shared_ptr<eos::IFileMD>& fmd)\n{\n  struct timespec ts_now;\n  char report[16384];\n  eos::IFileMD::ctime_t ctime;\n  eos::IFileMD::ctime_t mtime;\n  fmd->getCTime(ctime);\n  fmd->getMTime(mtime);\n  eos::common::Timing::GetTimeSpec(ts_now);\n  snprintf(report, sizeof(report) - 1,\n           \"log=%s&\"\n           \"host=%s&fid=%llu&fxid=%08llx&\"\n           \"ruid=%u&rgid=%u&\"\n           \"del_ts=%lu&del_tns=%lu&\"\n           \"dc_ts=%lu&dc_tns=%lu&\"\n           \"dm_ts=%lu&dm_tns=%lu&\"\n           \"dsize=%lu&sec.app=recycle\",\n           this->logId, gOFS->ManagerId.c_str(), (unsigned long long) fmd->getId(),\n           (unsigned long long) fmd->getId(), fmd->getCUid(), fmd->getCGid(),\n           ts_now.tv_sec, ts_now.tv_nsec, ctime.tv_sec, ctime.tv_nsec,\n           mtime.tv_sec, mtime.tv_nsec, fmd->getSize());\n  std::string record = report;\n\n  if (mIoStats) {\n    mIoStats->WriteRecord(record);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check if a host was tried already in a given URL with a given error\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::Tried(XrdCl::URL& url, std::string& host, const char* terr)\n{\n  XrdCl::URL::ParamsMap params = url.GetParams();\n  std::string tried_hosts = params[\"tried\"];\n  std::string tried_rc = params[\"triedrc\"];\n  std::vector<std::string> v_hosts;\n  std::vector<std::string> v_rc;\n  eos::common::StringConversion::Tokenize(tried_hosts,\n                                          v_hosts, \",\");\n  eos::common::StringConversion::Tokenize(tried_rc,\n                                          v_rc, \",\");\n\n  for (size_t i = 0; i < v_hosts.size(); ++i) {\n    if ((v_hosts[i] == host) &&\n        (i < v_rc.size()) &&\n        ((v_rc[i] == std::string(terr)) || (std::string(terr) == \"*\"))) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Wait until namespace is booted - thread cancellation point\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::WaitUntilNamespaceIsBooted()\n{\n  XrdSysThread::SetCancelDeferred();\n\n  while (gOFS->mNamespaceState != NamespaceState::kBooted) {\n    std::this_thread::sleep_for(std::chrono::seconds(1));\n    XrdSysThread::CancelPoint();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Wait until namespace is booted\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::WaitUntilNamespaceIsBooted(ThreadAssistant& assistant)\n{\n  while (gOFS->mNamespaceState != NamespaceState::kBooted) {\n    assistant.wait_for(std::chrono::seconds(1));\n\n    if (assistant.terminationRequested()) {\n      break;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Return string representation of prepare options\n//------------------------------------------------------------------------------\nstd::string\nXrdMgmOfs::prepareOptsToString(const int opts)\n{\n  std::ostringstream result;\n  const int priority = opts & Prep_PMASK;\n\n  switch (priority) {\n  case Prep_PRTY0:\n    result << \"PRTY0\";\n    break;\n\n  case Prep_PRTY1:\n    result << \"PRTY1\";\n    break;\n\n  case Prep_PRTY2:\n    result << \"PRTY2\";\n    break;\n\n  case Prep_PRTY3:\n    result << \"PRTY3\";\n    break;\n\n  default:\n    result << \"PRTYUNKNOWN\";\n  }\n\n  const int send_mask = 12;\n  const int send = opts & send_mask;\n\n  switch (send) {\n  case 0:\n    break;\n\n  case Prep_SENDAOK:\n    result << \",SENDAOK\";\n    break;\n\n  case Prep_SENDERR:\n    result << \",SENDERR\";\n    break;\n\n  case Prep_SENDACK:\n    result << \",SENDACK\";\n    break;\n\n  default:\n    result << \",SENDUNKNOWN\";\n  }\n\n  if (opts & Prep_WMODE) {\n    result << \",WMODE\";\n  }\n\n  if (opts & Prep_STAGE) {\n    result << \",STAGE\";\n  }\n\n  if (opts & Prep_COLOC) {\n    result << \",COLOC\";\n  }\n\n  if (opts & Prep_FRESH) {\n    result << \",FRESH\";\n  }\n\n#if (XrdMajorVNUM(XrdVNUMBER) == 4 && XrdMinorVNUM(XrdVNUMBER) >= 10) || XrdMajorVNUM(XrdVNUMBER) >= 5\n\n  if (opts & Prep_CANCEL) {\n    result << \",CANCEL\";\n  }\n\n  if (opts & Prep_QUERY) {\n    result << \",QUERY\";\n  }\n\n  if (opts & Prep_EVICT) {\n    result << \",EVICT\";\n  }\n\n#endif\n  return result.str();\n}\n\n//------------------------------------------------------------------------------\n// Populate file error object with redirection information that can be\n// longer than 2kb. For this we need to use the XrdOucBuffer interface.\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::SetRedirectionInfo(XrdOucErrInfo& err_obj,\n                              const std::string& rdr_info, int rdr_port)\n{\n  if (rdr_info.empty() || (rdr_port == 0)) {\n    err_obj.setErrInfo(EINVAL, \"no redirection info available\");\n    return false;\n  }\n\n  // If size < 2kb just set it directly\n  if (rdr_info.length() < 2 * 1024) {\n    err_obj.setErrInfo(rdr_port, rdr_info.c_str());\n    return true;\n  }\n\n  // Otherwise use the XrdOucBuffPool to manage XrdOucBuffer objects that\n  // can hold redirection info >= 2kb\n  const uint32_t aligned_sz = eos::common::GetPowerCeil(rdr_info.length() + 1,\n                              2 * eos::common::KB);\n  XrdOucBuffer* buff = mXrdBuffPool.Alloc(aligned_sz);\n\n  if (buff == nullptr) {\n    eos_static_err(\"msg=\\\"requested redirection buffer allocation size too \"\n                   \"big\\\" req_sz=%llu max_sz=%i\", rdr_info.length(),\n                   mXrdBuffPool.MaxSize());\n    err_obj.setErrInfo(EINVAL, \"redirection buffer too big\");\n    return false;\n  }\n\n  (void) strcpy(buff->Buffer(), rdr_info.c_str());\n  buff->SetLen(rdr_info.length() + 1);\n  err_obj.setErrInfo(rdr_port, buff);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Send query (XrdFileSystem::Query) to the given endpoint and collect the\n// repsonse\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::SendQuery(const std::string& hostname, int port,\n                     const std::string& request, std::string& response,\n                     uint16_t timeout)\n{\n  std::ostringstream oss;\n  oss << \"root://\" << hostname << \":\" << port << \"/?xrd.wantprot=sss\";\n  XrdCl::URL url(oss.str());\n\n  if (!url.IsValid()) {\n    eos_static_err(\"msg=\\\"invalid url\\\" url=\\\"%s\\\"\", oss.str().c_str());\n    return EINVAL;\n  }\n\n  XrdCl::Buffer arg;\n  XrdCl::Buffer* raw_resp {nullptr};\n  XrdCl::FileSystem fs {url};\n  arg.FromString(request);\n  XrdCl::XRootDStatus status = fs.Query(XrdCl::QueryCode::OpaqueFile, arg,\n                                        raw_resp, timeout);\n  std::unique_ptr<XrdCl::Buffer> resp(raw_resp);\n  raw_resp = nullptr;\n\n  if (!status.IsOK()) {\n    eos_static_err(\"msg=\\\"failed query request\\\" request=\\\"%s\\\" status=\\\"%s\\\"\",\n                   request.c_str(), status.ToStr().c_str());\n    return -1;\n  }\n\n  if (resp && resp->GetBuffer()) {\n    response = resp->GetBuffer();\n  }\n\n  return 0;\n}\n\n//----------------------------------------------------------------------------\n// Broadcast query (XrdFileSystem::Query) to the given endpoints and collect\n// the responses\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::BroadcastQuery(const std::string& request,\n                          std::set<std::string>& endpoints,\n                          std::map<std::string, std::pair<int, std::string>>&\n                          responses, uint16_t timeout)\n{\n  std::atomic<int> g_retc = 0; // overall return code\n  class QueryRespHandler: public XrdCl::ResponseHandler\n  {\n  public:\n    //------------------------------------------------------------------------\n    //! Constructor\n    //------------------------------------------------------------------------\n    QueryRespHandler(const std::string& endpoint,\n                     std::map<std::string, std::pair<int, std::string>>& responses,\n                     std::mutex& mutex, std::condition_variable& cv,\n                     std::atomic<int>& retc):\n      mEndpoint(endpoint), mRespMap(responses), mMutexMap(mutex), mCv(cv),\n      mRetc(retc)\n    {}\n\n    //------------------------------------------------------------------------\n    //! Called when a response to associated request arrives or an error\n    //! occurs\n    //!\n    //! @param status   status of the request\n    //! @param response an object associated with the response\n    //------------------------------------------------------------------------\n    void HandleResponse(XrdCl::XRootDStatus* status, XrdCl::AnyObject* response)\n    {\n      int retc = 0;\n      std::string resp;\n\n      if (status->IsOK()) {\n        if (response) {\n          XrdCl::Buffer* buffer {nullptr};\n          response->Get(buffer);\n          resp = buffer->GetBuffer();\n        }\n      } else {\n        retc = (status->errNo ? status->errNo : ENOMSG);\n        resp = status->GetErrorMessage();\n        mRetc = 1;\n      }\n\n      if (response) {\n        delete response;\n      }\n\n      delete status;\n      {\n        // Add info to the global map and notify main thread\n        std::unique_lock<std::mutex> lock(mMutexMap);\n        mRespMap.emplace(mEndpoint, std::make_pair(retc, std::move(resp)));\n      }\n      mCv.notify_one();\n    }\n\n  private:\n    std::string mEndpoint;\n    std::map<std::string, std::pair<int, std::string>>& mRespMap;\n    std::mutex& mMutexMap;\n    std::condition_variable& mCv;\n    std::atomic<int>& mRetc;\n  };\n\n  // Collect all the FST endpoints if nothing specified\n  if (endpoints.empty()) {\n    endpoints = FsView::gFsView.CollectEndpoints(\"*\");\n  }\n\n  size_t num_resp = endpoints.size();\n  std::mutex mutex;\n  std::condition_variable cv;\n  std::map<XrdCl::FileSystem*, QueryRespHandler*> queries;\n\n  for (const auto& ep : endpoints) {\n    std::ostringstream oss;\n    oss << \"root://\" << ep << \"/?xrd.wantprot=sss\";\n    XrdCl::URL url(oss.str());\n\n    if (!url.IsValid()) {\n      eos_static_err(\"msg=\\\"invalid url\\\" url=\\\"%s\\\"\", oss.str().c_str());\n      std::unique_lock<std::mutex> lock(mutex);\n      responses.emplace(ep, std::make_pair(EINVAL, \"invalid url\"));\n      continue;\n    }\n\n    auto pair = queries.emplace(new XrdCl::FileSystem(url),\n                                new QueryRespHandler(ep, responses, mutex, cv, g_retc));\n\n    if (!pair.second) {\n      eos_static_err(\"msg=\\\"failed to insert query\\\" endpoint=\\\"%s\\\"\",\n                     ep.c_str());\n      std::unique_lock<std::mutex> lock(mutex);\n      responses.emplace(ep, std::make_pair(EINVAL, \"failed query insert\"));\n      continue;\n    }\n\n    //! const_cast\n    auto* fs = pair.first->first;\n    auto* handler = pair.first->second;\n    XrdCl::Buffer arg;\n    arg.FromString(request);\n    XrdCl::XRootDStatus status = fs->Query(XrdCl::QueryCode::OpaqueFile, arg,\n                                           handler, timeout);\n\n    if (!status.IsOK()) {\n      eos_static_err(\"msg=\\\"failed to send query\\\" endpoint=\\\"%s\\\"\",\n                     ep.c_str());\n      std::unique_lock<std::mutex> lock(mutex);\n      responses.emplace(ep, std::make_pair(EINVAL, \"failed query send\"));\n      continue;\n    }\n  }\n\n  {\n    // Wait for all the responses to be received\n    std::unique_lock<std::mutex> lock(mutex);\n    cv.wait(lock, [&] {return (num_resp == responses.size());});\n  }\n\n  // Clean up memory\n  for (const auto& elem : queries) {\n    delete elem.first;\n    delete elem.second;\n  }\n\n  return g_retc;\n}\n\n//------------------------------------------------------------------------------\n// Send a resync command for a file identified by id and filesystem\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::QueryResync(eos::common::FileId::fileid_t fid,\n                       eos::common::FileSystem::fsid_t fsid, bool force)\n{\n  int fst_port;\n  std::string fst_host;\n  std::string fst_queue;\n  {\n    eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n    eos::mgm::FileSystem* fs = FsView::gFsView.mIdView.lookupByID(fsid);\n\n    if (!fs) {\n      eos_err(\"msg=\\\"no resync msg sent, no such file system\\\" fsid=%lu\", fsid);\n      return -1;\n    }\n\n    fst_host = fs->GetHost();\n    fst_queue = fs->GetQueue();\n    fst_port = fs->getCoreParams().getLocator().getPort();\n  }\n  EXEC_TIMING_BEGIN(\"QueryResync\");\n  gOFS->MgmStats.Add(\"QueryResync\", vid.uid, vid.gid, 1);\n  std::string request =\n    SSTR(\"/?fst.pcmd=resync\"\n         << \"&fst.resync.fsid=\" << fsid\n         << \"&fst.resync.fxid=\" << eos::common::FileId::Fid2Hex(fid)\n         << \"&fst.resync.force=\" << force);\n  std::string response;\n  int query_retc = gOFS->SendQuery(fst_host, fst_port, request, response);\n  (void) response;\n  EXEC_TIMING_END(\"QueryResync\");\n  return query_retc;\n}\n\n//------------------------------------------------------------------------------\n// Remove file/container metadata object that was already deleted before\n// but now it's still in the namespace detached from any parent\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::RemoveDetached(uint64_t id, bool is_dir, bool force,\n                          std::string& msg) const\n{\n  errno = 0;\n\n  if (is_dir) {\n    try {\n      std::shared_ptr<eos::IContainerMD> cont =\n        gOFS->eosDirectoryService->getContainerMD(id);\n      eos::MDLocking::ContainerWriteLock contLock(cont.get());\n\n      if (cont->getParentId()) {\n        gOFS->eosDirectoryService->removeContainer(cont.get());\n        return true;\n      } else {\n        msg = \"error: container is attached id=\" + std::to_string(id);\n        return false;\n      }\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      eos_debug(\"msg=\\\"caught exception\\\" errno=%d msg=\\\"%s\\\"\",\n                e.getErrno(), e.getMessage().str().c_str());\n      msg = \"error: \" + e.getMessage().str() + '\\n';\n      return false;\n    }\n  } else {\n    try {\n      eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n      std::shared_ptr<eos::IFileMD> file = gOFS->eosFileService->getFileMD(id);\n      // Only one operation: no need to take a long file lock\n      auto contId = file->getContainerId();\n\n      if (contId) {\n        // Double check if the parent container really exists. It could be\n        // that the file is attached to a container which is already deleted.\n        try {\n          (void) gOFS->eosDirectoryService->getContainerMD(contId);\n          msg = \"error: file fxid=\" + eos::common::FileId::Fid2Hex(id) +\n                \" is attached to cid=\" + std::to_string(contId);\n          return false;\n        } catch (const eos::MDException& e) {\n          // This means the parent container does not exist so we can safely\n          // remove this file entry.\n        }\n      }\n\n      // Write lock the file\n      eos::MDLocking::FileWriteLock fileLock(file.get());\n      // If any of the unlink locations is a file systems that doesn't exist\n      // anymore then just remove it\n      auto unlink_locs = file->getUnlinkedLocations();\n\n      for (const auto& uloc : unlink_locs) {\n        if (FsView::gFsView.mIdView.lookupByID(uloc) == nullptr) {\n          file->removeLocation(uloc);\n        }\n      }\n\n      // If there are no more locations we can also delete the file object\n      if (file->getUnlinkedLocations().empty()) {\n        gOFS->eosFileService->removeFile(file.get());\n        msg = \"info: file object removed from namespace\";\n      } else {\n        // Move the unlinked locations to the locations list and back so\n        // that we notify the listener for disk deletion\n        unlink_locs = file->getUnlinkedLocations();\n\n        for (const auto& uloc : unlink_locs) {\n          file->addLocation(uloc);\n        }\n\n        file->unlinkAllLocations();\n\n        if (force) {\n          gOFS->eosFileService->removeFile(file.get());\n          msg = \"info: file force removed from namespace, best-effort disk \"\n                \"deletion(s)\";\n        } else {\n          msg = \"info: file locations unlinked, waiting for disk deletion(s)\";\n        }\n      }\n\n      return true;\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      eos_debug(\"msg=\\\"caught exception\\\" errno=%d msg=\\\"%s\\\"\",\n                e.getErrno(), e.getMessage().str().c_str());\n      msg = \"error: \" + e.getMessage().str() + '\\n';\n      return false;\n    }\n  }\n}\n\n//----------------------------------------------------------------------------\n// Query to determine if current node is acting as master\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::IsMaster(const char* path,\n                    const char* ininfo,\n                    XrdOucEnv& env,\n                    XrdOucErrInfo& error,\n                    eos::common::VirtualIdentity& vid,\n                    const XrdSecEntity* client)\n{\n  static const char* epname = \"IsMaster\";\n\n  // TODO (esindril): maybe enable SSS at some point\n  // REQUIRE_SSS_OR_LOCAL_AUTH;\n\n  if (!gOFS->mMaster->IsMaster()) {\n    return Emsg(epname, error, ENOENT, \"find master file [ENOENT]\", \"\");\n  }\n\n  const char* ok = \"OK\";\n  error.setErrInfo(strlen(ok) + 1, ok);\n  return SFS_DATA;\n}\n\nbool\nXrdMgmOfs::AllowAuditModification(const std::string& path)\n{\n  if (!mAudit) {\n    return false;\n  }\n\n  if (!mEnvAuditAttributeOnly) {\n    return true;\n  }\n\n  // attribute-only: parent sys.audit must enable modifications\n  std::string pdir;\n  {\n    eos::common::Path cP(path.c_str());\n    pdir = cP.GetParentPath();\n  }\n\n  try {\n    auto pd = eosView->getContainer(pdir);\n    eos::MDLocking::ContainerReadLock cmd_lock(pd.get());\n    auto amap = pd->getAttributes();\n    auto it = amap.find(\"sys.audit\");\n\n    if (it != amap.end()) {\n      std::string mode = it->second;\n\n      for (auto& c : mode) {\n        c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));\n      }\n\n      if (mode == \"all\" || mode == \"detail\" || mode == \"default\" ||\n          mode == \"modifications\") {\n        return true;\n      }\n    }\n  } catch (...) {}\n\n  return false;\n}\n\nbool\nXrdMgmOfs::AllowAuditList(const std::string& dirPath)\n{\n  if (!mAudit) {\n    return false;\n  }\n\n  if (!mEnvAuditAttributeOnly) {\n    return mAudit->isListAuditingEnabled();\n  }\n\n  // attribute-only: dir sys.audit must be 'all'\n  try {\n    auto dh = eosView->getContainer(dirPath);\n    eos::MDLocking::ContainerReadLock cmd_lock(dh.get());\n    auto amap = dh->getAttributes();\n    auto it = amap.find(\"sys.audit\");\n\n    if (it != amap.end()) {\n      std::string mode = it->second;\n\n      for (auto& c : mode) {\n        c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));\n      }\n\n      if (mode == \"all\") {\n        return true;\n      }\n    }\n  } catch (...) {}\n\n  return false;\n}\n\nbool\nXrdMgmOfs::AllowAuditRead(const std::string& path)\n{\n  if (!mAudit) {\n    return false;\n  }\n\n  if (!mEnvAuditAttributeOnly) {\n    return (mAudit->isReadAuditingEnabled() && mAudit->shouldAuditReadPath(path));\n  }\n\n  // attribute-only: parent sys.audit governs; default/detail/all enable; default uses suffix filter\n  std::string pdir;\n  {\n    eos::common::Path cP(path.c_str());\n    pdir = cP.GetParentPath();\n  }\n\n  try {\n    auto pd = eosView->getContainer(pdir);\n    eos::MDLocking::ContainerReadLock cmd_lock(pd.get());\n    auto amap = pd->getAttributes();\n    auto it = amap.find(\"sys.audit\");\n\n    if (it != amap.end()) {\n      std::string mode = it->second;\n\n      for (auto& c : mode) {\n        c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));\n      }\n\n      if (mode == \"detail\" || mode == \"all\") {\n        return true;\n      }\n\n      if (mode == \"default\") {\n        return mAudit->shouldAuditReadPath(path);\n      }\n    }\n  } catch (...) {}\n\n  return false;\n}\n\n// Fast-path overloads that use an already available sys.audit attribute value\nbool\nXrdMgmOfs::AllowAuditModificationAttr(const std::string& auditMode)\n{\n  if (!mAudit) {\n    return false;\n  }\n\n  if (!mEnvAuditAttributeOnly) {\n    return true;\n  }\n\n  std::string mode = auditMode;\n\n  for (auto& c : mode) {\n    c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));\n  }\n\n  if (mode == \"all\" || mode == \"detail\" || mode == \"default\" ||\n      mode == \"modifications\") {\n    return true;\n  }\n\n  return false;\n}\n\nbool\nXrdMgmOfs::AllowAuditListAttr(const std::string& auditMode)\n{\n  if (!mAudit) {\n    return false;\n  }\n\n  if (!mEnvAuditAttributeOnly) {\n    return mAudit->isListAuditingEnabled();\n  }\n\n  std::string mode = auditMode;\n\n  for (auto& c : mode) {\n    c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));\n  }\n\n  return (mode == \"all\");\n}\n\nbool\nXrdMgmOfs::AllowAuditReadAttr(const std::string& auditMode,\n                              const std::string& path)\n{\n  if (!mAudit) {\n    return false;\n  }\n\n  if (!mEnvAuditAttributeOnly) {\n    return (mAudit->isReadAuditingEnabled() && mAudit->shouldAuditReadPath(path));\n  }\n\n  std::string mode = auditMode;\n\n  for (auto& c : mode) {\n    c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));\n  }\n\n  if (mode == \"detail\" || mode == \"all\") {\n    return true;\n  }\n\n  if (mode == \"default\") {\n    return mAudit->shouldAuditReadPath(path);\n  }\n\n  return false;\n}\n"
  },
  {
    "path": "mgm/ofs/XrdMgmOfs.hh",
    "content": "// ----------------------------------------------------------------------\n// File: XrdMgmOfs.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file   XrdMgmOfs.hh\n *\n * @brief  XRootD OFS plugin implementing metadata handling of EOS\n *\n * This class is the OFS plugin which implements the metadata and\n * management server part. To understand the functionality of the MGM you start\n * here. The class implements three objects, the MgmOfs object for generic\n * metadata operations, configuration and EOS thread daemon startup,\n * the MgmOfsFile class which implements operations on files - in the case of\n * an MGM this is mainly 'open' for redirection to an FST. There is one exception\n * the so called EOS 'proc' commands. Every EOS shell command is implemented as\n * an 'open for read', 'read', 'close' sequence where the open actually executes\n * an EOS shell command and the read streams the result back. For details of\n * this REST-like request/response format look at the ProcInterface class and\n * the implementations in the mgm/proc directory. The XrdMgmDirectory class\n * is provided to implement the POSIX like 'open', 'readdir', 'closedir' syntax.\n * The MGM code uses mainly three global mutexes given in the order they have\n * to be used:\n * - eos::common::RWMutexXXXLock lock(FsView::gFsView.ViewMutex)  : lock 1\n * - eos::common::RWMutexXXXLock lock(gOFS->eosViewRWMutex)       : lock 2\n * - eos::common::RWMutexXXXLock lock(Quota::pMapMutex)           : lock 3\n * The XXX is either Read or Write depending what has to be done on the\n * objects they are protecting. The first mutex is the file system view object\n * (FsView.cc) which contains the current state of the storage\n * filesystem/node/group/space configuration. The second mutex is protecting\n * the quota configuration and scheduling. The last mutex is protecting the\n * namespace.\n * The implementation uses a bunch of convenience macros to cut the code short.\n * These macro's filter/map path names, apply redirection, stalling rules and\n * require certain authentication credentials to be able to run some function.\n * The MgmOfs functions are always split into the main entry function e.g.\n * \"::access\" and an internal function \"::_access\". The main function applies\n * typically the mentioned macros and converts the XRootD client identity object\n * into an EOS virtual identity. The interal function requires an EOS virtual\n * identity and contains the full implementation. This allows to apply\n * mapping & stall/redirection rules once and use the interval function\n * implementation from other main functions e.g. the \"rename\" function can use\n * the \"_access\" internal function to check some permissions etc.\n * The MGM run's the following sub-services\n * (implemented by objects and threaded daemons):\n * - Fsck\n * - Balancer\n * - Iostat\n * - Messaging\n * - Deletion\n * - Filesystem Listener\n * - Httpd\n * - Recycler\n * - LRU\n * - WFE\n *\n * Many functions in the MgmOfs interface take CGI parameters. The supported\n * CGI parameter are:\n * \"eos.ruid\" - uid role the client wants\n * \"eos.rgid\" - gid role the client wants\n * \"eos.space\" - space a user wants to use for scheduling a write\n * \"eos.checksum\" - checksum a file should have\n * \"eos.lfn\" - use this name as path name not the path parameter (used by prefix\n * redirector MGM's ...\n * \"eos.bookingsize\" - reserve the requested bytes in a file placement\n * \"eos.cli.access=pio\" - ask for a parallel open (changes the response of an open for RAIN layouts)\n * \"eos.app\" - set the application name reported by monitoring\n * \"eos.targetsize\" - expected size of a file to be uploaded\n * \"eos.blockchecksum=ignore\" - disable block checksum verification\n *\n * All path related functions take as parameters 'inpath' and 'ininfo'. These\n * parameters are remapped by the NAMESPACEMAP macro to path & info variables\n * which are not visible in the declaration of each function!\n */\n/*----------------------------------------------------------------------------*/\n\n#ifndef __EOSMGM_MGMOFS__HH__\n#define __EOSMGM_MGMOFS__HH__\n\n#include \"auth_plugin/Request.pb.h\"\n#include \"common/AssistedThread.hh\"\n#include \"common/Audit.hh\"\n#include \"common/FileId.hh\"\n#include \"common/FileSystem.hh\"\n#include \"common/LinuxStat.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Mapping.hh\"\n#include \"common/MutexLatencyWatcher.hh\"\n#include \"common/XrdConnPool.hh\"\n#include \"common/Audit.hh\"\n#include \"mgm/proc/ProcCommand.hh\"\n#include \"mgm/proc/admin/SpaceCmd.hh\"\n#include \"mgm/proc/admin/NsCmd.hh\"\n#include \"mgm/drain/Drainer.hh\"\n#include \"mgm/misc/IdTrackerWithValidity.hh\"\n#include \"mgm/imaster/IMaster.hh\"\n#include \"mgm/FuseServer/FusexCastBatch.hh\"\n#include \"mgm/drain/Drainer.hh\"\n#include \"mgm/imaster/IMaster.hh\"\n#include \"mgm/inflighttracker/InFlightTracker.hh\"\n#include \"mgm/misc/IdTrackerWithValidity.hh\"\n#include \"mgm/namespacestats/NamespaceStats.hh\"\n#include \"mgm/proc/ProcCommand.hh\"\n#include \"mgm/proc/admin/NsCmd.hh\"\n#include \"mgm/proc/admin/SpaceCmd.hh\"\n#include \"mgm/shaping/TrafficShaping.hh\"\n#include \"namespace/MDLocking.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/INamespaceGroup.hh\"\n#include \"namespace/locking/BulkNsObjectLocker.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"mgm/inflighttracker/InFlightTracker.hh\"\n#include \"mgm/namespacestats/NamespaceStats.hh\"\n#include <XrdAcc/XrdAccPrivs.hh>\n#include <chrono>\n#include <mutex>\n\nUSE_EOSMGMNAMESPACE\n\n//! Forward declaration\nclass XrdMgmOfsFile;\nclass XrdMgmOfsDirectory;\nclass XrdAccAuthorize;\nclass XrdMgmAuthz;\n\nnamespace eos\n{\nclass IFsView;\nclass IFileMDSvc;\nclass IContainerMDSvc;\nclass IView;\nclass IFileMDChangeListener;\nclass IContainerMDChangeListener;\n}\n\nnamespace eos::common\n{\nclass CommentLog;\nclass JeMallocHandler;\nclass BehaviourConfig;\n}\n\nnamespace eos::mgm\n{\nclass AdminSocket;\nclass IConfigEngine;\nclass HttpServer;\nclass GrpcServer;\nclass GrpcWncServer;\nclass GrpcRestGwServer;\nclass Egroup;\nclass GeoTreeEngine;\nclass ZMQ;\nclass Recycle;\nclass Devices;\nclass Iostat;\nclass Stat;\nclass WFE;\nclass LRU;\nclass Fsck;\nclass FsckEntry;\nclass IMaster;\nclass Messaging;\nclass PathRouting;\nclass CommitHelper;\nclass ReplicationTracker;\nclass ConversionJob;\nclass ConverterEngine;\n}\n\nnamespace eos::mgm::tgc\n{\nclass RealTapeGcMgm;\nclass MultiSpaceTapeGc;\n}\n\nnamespace eos::mgm::bulk\n{\nclass ProcDirectoryBulkRequestLocations;\nclass BulkRequestProcCleaner;\n}\n\nnamespace eos::mgm::rest\n{\nclass RestApiManager;\n}\n\nnamespace eos::auth\n{\nclass RequestProto;\n}\n\nnamespace zmq\n{\nclass socket_t;\nclass context_t;\n}\n\nnamespace eos::mq\n{\nclass MessagingRealm;\n}\n\nnamespace eos::mgm::placement\n{\nclass FSScheduler;\n}\n\nnamespace eos::mgm::FuseServer\n{\nclass Server;\n}\n\nenum class NamespaceState {\n  kDown = 0,\n  kBooting = 1,\n  kBooted = 2,\n  kFailed = 3,\n  kCompacting = 4\n};\n\n//------------------------------------------------------------------------------\n//! Convert NamespaceState to string\n//------------------------------------------------------------------------------\nstd::string namespaceStateToString(NamespaceState st);\n\n//------------------------------------------------------------------------------\n//! Class implementing atomic meta data commands\n//------------------------------------------------------------------------------\nclass XrdMgmOfs : public XrdSfsFileSystem, public eos::common::LogId\n{\npublic:\n\n  friend class XrdMgmOfsFile;\n  friend class XrdMgmOfsDirectory;\n  friend class eos::mgm::ProcCommand;\n  friend class eos::mgm::CommitHelper;\n  friend class eos::mgm::Drainer;\n  friend class eos::mgm::DrainFs;\n  friend class eos::mgm::DrainTransferJob;\n  friend class eos::mgm::ConversionJob;\n  friend class eos::mgm::ConverterEngine;\n  friend class eos::mgm::SpaceCmd;\n  friend class eos::mgm::FsckEntry;\n  friend class eos::mgm::NsCmd;\n  friend class eos::mgm::FuseServer::Server;\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  XrdMgmOfs(XrdSysError* lp);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //---------------------------------------------------------------------------\n  virtual ~XrdMgmOfs();\n\n  // ---------------------------------------------------------------------------\n  // Object Allocation Functions\n  // ---------------------------------------------------------------------------\n\n  //----------------------------------------------------------------------------\n  //! Return a MGM directory object\n  //!\n  //! @param user user-name\n  //! @param MonID monitor ID\n  //!\n  //! @return MGM directory object\n  //----------------------------------------------------------------------------\n  XrdSfsDirectory* newDir(char* user = 0, int MonID = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Return a MGM file object\n  //!\n  //! @param user user-name\n  //! @param MonID monitor ID\n  //!\n  //! @return MGM file object\n  //----------------------------------------------------------------------------\n  XrdSfsFile* newFile(char* user = 0, int MonID = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Meta data functions\n  //! - the _XYZ functions are the internal version of XYZ when XrdSecEntity\n  //! - objects have been mapped to VirtualIdentity objects.\n  //----------------------------------------------------------------------------\n\n  //----------------------------------------------------------------------------\n  //! Notify filesystem that a client has disconnected.\n  //!\n  //! @param client client's identify (see common description)\n  //----------------------------------------------------------------------------\n  virtual void Disc(const XrdSecEntity* client = 0) override;\n\n  //----------------------------------------------------------------------------\n  // Chmod by client\n  //----------------------------------------------------------------------------\n  int chmod(const char* Name,\n            XrdSfsMode Mode,\n            XrdOucErrInfo& out_error,\n            const XrdSecEntity* client = 0,\n            const char* opaque = 0) override;\n\n  // ---------------------------------------------------------------------------\n  // chmod by vid\n  // ---------------------------------------------------------------------------\n  int _chmod(const char* Name,\n             XrdSfsMode& Mode,\n             XrdOucErrInfo& out_error,\n             eos::common::VirtualIdentity& vid,\n             const char* opaque = 0);\n\n  // ---------------------------------------------------------------------------\n  // chown by vid\n  // ---------------------------------------------------------------------------\n  int _chown(const char* Name,\n             uid_t uid,\n             gid_t gid,\n             XrdOucErrInfo& out_error,\n             eos::common::VirtualIdentity& vid,\n             const char* opaque = 0,\n             bool nodereference = false);\n\n  // ---------------------------------------------------------------------------\n  // checksum by client\n  // ---------------------------------------------------------------------------\n  int chksum(XrdSfsFileSystem::csFunc Func,\n             const char* csName,\n             const char* Path,\n             XrdOucErrInfo& out_error,\n             const XrdSecEntity* client = 0,\n             const char* opaque = 0) override;\n\n  // ---------------------------------------------------------------------------\n  // check if file exists by client\n  // ---------------------------------------------------------------------------\n  int exists(const char* fileName,\n             XrdSfsFileExistence& exists_flag,\n             XrdOucErrInfo& out_error,\n             const XrdSecEntity* client = 0,\n             const char* opaque = 0) override;\n\n  // ---------------------------------------------------------------------------\n  // check if file exists by client bypassing authorization/mapping/bouncing\n  // ---------------------------------------------------------------------------\n  int _exists(const char* fileName,\n              XrdSfsFileExistence& exists_flag,\n              XrdOucErrInfo& out_error,\n              const XrdSecEntity* client = 0,\n              const char* opaque = 0);\n\n  // ---------------------------------------------------------------------------\n  //! check if file exists by vid\n  // ---------------------------------------------------------------------------\n  int\n  _exists(const char* fileName,\n          XrdSfsFileExistence& exists_flag,\n          XrdOucErrInfo& out_error,\n          eos::common::VirtualIdentity& vid,\n          const char* opaque = 0, bool take_lock = true);\n\n  /*----------------------------------------------------------------------------*/\n  /*\n   * @brief check for the existence of a file or directory by vid whilst\n   *        populating the file or container metadata if non empty\n   *\n   * @param path path to check\n   * @param file_exists return the type of the checked path\n   * @param vid virtual identity of the client\n   * @param cmd Container MD (out param)\n   * @param fmd File MD (out param)\n   * @param ininfo CGI\n   * @return SFS_OK if found otherwise SFS_ERROR\n   *\n   * The values of file_exists are:\n   * XrdSfsFileExistIsDirectory - this is a directory\n   * XrdSfsFileExistIsFile - this is a file\n   * XrdSfsFileExistNo - this is neither a file nor a directory\n   *\n   */\n  /*----------------------------------------------------------------------------*/\n  int\n  _exists(const char* fileName,\n          XrdSfsFileExistence& exists_flag,\n          XrdOucErrInfo& out_error,\n          eos::common::VirtualIdentity& vid,\n          std::shared_ptr<eos::IContainerMD>& container_md_ptr,\n          std::shared_ptr<eos::IFileMD>& file_md_ptr,\n          const char* opaque = 0);\n\n  // ---------------------------------------------------------------------------\n  // EOS plugin call fan-out function\n  // ---------------------------------------------------------------------------\n  int FSctl(const int cmd,\n            XrdSfsFSctl& args,\n            XrdOucErrInfo& error,\n            const XrdSecEntity* client) override;\n\n  // ---------------------------------------------------------------------------\n  // fsctl\n  // ---------------------------------------------------------------------------\n  int fsctl(const int cmd,\n            const char* args,\n            XrdOucErrInfo& out_error,\n            const XrdSecEntity* client = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Get stats function (fake ok)\n  //----------------------------------------------------------------------------\n  int getStats(char* buff, int blen) override\n  {\n    return 0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return the version of the MGM software\n  //!\n  //! @return return a version string\n  //----------------------------------------------------------------------------\n  const char* getVersion() override;\n\n\n  // ---------------------------------------------------------------------------\n  // create directory\n  // ---------------------------------------------------------------------------\n  int mkdir(const char* dirName,\n            XrdSfsMode Mode,\n            XrdOucErrInfo& out_error,\n            const XrdSecEntity* client = 0,\n            const char* opaque = 0) override\n  {\n    return mkdir(dirName, Mode, out_error, client, opaque, 0);\n  }\n\n  int mkdir(const char* dirName,\n            XrdSfsMode Mode,\n            XrdOucErrInfo& out_error,\n            const XrdSecEntity* client = 0,\n            const char* opaque = 0,\n            ino_t* outino = 0);\n\n  // ---------------------------------------------------------------------------\n  // create directory by vid\n  // ---------------------------------------------------------------------------\n  int _mkdir(const char* dirName,\n             XrdSfsMode Mode,\n             XrdOucErrInfo& out_error,\n             eos::common::VirtualIdentity& vid,\n             const char* opaque = 0,\n             ino_t* outino = 0,\n             bool nopermissioncheck = false);\n\n  //----------------------------------------------------------------------------\n  //! Perform a filesystem extended attribute function.\n  //!\n  //! @param  faReq  - pointer to the request object (see XrdSfsFAttr.hh). If\n  //!                  the pointer is null, simply return whether or not\n  //!                  extended attributes are supported.\n  //! @param  eInfo  - The object where error info or results are to be returned.\n  //! @param  client - Client's identify (see common description).\n  //!\n  //! @return SFS_OK   a null response is sent.\n  //! @return SFS_DATA error.code    length of the data to be sent.\n  //!                  error.message contains the data to be sent.\n  //! @return SFS_STARTED Operation started result will be returned via callback.\n  //!         o/w      one of SFS_ERROR, SFS_REDIRECT, or SFS_STALL.\n  //----------------------------------------------------------------------------\n  int FAttr(XrdSfsFACtl* faReq, XrdOucErrInfo& eInfo,\n            const XrdSecEntity* client = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Prepare a file or query the status of a previous prepare request\n  //!\n  //! @return SFS_OK   prepare request received, use request ID provided by XRootD framework\n  //! @return SFS_DATA prepare request received, use request ID set by the MGM\n  //----------------------------------------------------------------------------\n  int prepare(XrdSfsPrep& pargs,\n              XrdOucErrInfo& out_error,\n              const XrdSecEntity* client = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Prepare a file\n  //!\n  //! @return SFS_OK   prepare request received, use request ID provided by XRootD framework\n  //! @return SFS_DATA prepare request received, use request ID set by the MGM\n  //----------------------------------------------------------------------------\n  int _prepare(XrdSfsPrep& pargs,\n               XrdOucErrInfo& out_error,\n               const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Query the status of a prepare request\n  //!\n  //! @return SFS_DATA success\n  //----------------------------------------------------------------------------\n  int _prepare_query(XrdSfsPrep& pargs,\n                     XrdOucErrInfo& out_error,\n                     const XrdSecEntity* client);\n\n  // ---------------------------------------------------------------------------\n  // delete file\n  // ---------------------------------------------------------------------------\n  int rem(const char* path,\n          XrdOucErrInfo& out_error,\n          const XrdSecEntity* client = 0,\n          const char* opaque = 0) override;\n\n  // ---------------------------------------------------------------------------\n  // delete file by vid\n  // ---------------------------------------------------------------------------\n  int _rem(const char* path,\n           XrdOucErrInfo& out_error,\n           eos::common::VirtualIdentity& vid,\n           const char* opaque = 0,\n           bool simulate = false,\n           bool keepversion = false,\n           bool no_recycling = false,\n           bool no_quota_enforcement = false,\n           bool fusexcast = true,\n           bool no_workflow = false);\n\n  //----------------------------------------------------------------------------\n  //! Low-level namespace find command\n  //!\n  //! @param path path to start the sub-tree find\n  //! @param stdErr stderr output string\n  //! @param vid virtual identity of the client\n  //! @param found result map/set of the find\n  //! @param key search for a certain key in the extended attributes\n  //! @param val search for a certain value in the extended attributes\n  //!        (requires key)\n  //! @param no_files if true returns only directories, otherwise files and\n  //!         directories\n  //! @param millisleep milli seconds to sleep between each directory scan\n  //! @param nscounter if true update ns counters, otherwise don't\n  //! @param maxdepth is the maximum search depth\n  //! @param filematch is a pattern match for file names\n  //! @param skip_version_dir if true then skip listing version directories\n  //! @param json_output\n  //! @param fstout\n  //! @param foudn_ctime_sec if set populate with found entries and their ctime\n  //! @param max_ctime_dir if set filter out newer directories\n  //! @param max_ctime_file if set filter out newer files\n  //! @param assistant if set points to the thread doing the call\n  //!\n  //! @note The find command distinguishes 'power' and 'normal' users. If the\n  //! virtual identity indicates the root or admin user queries are unlimited.\n  //! For others queries are by default limited to 50k directories and 100k\n  //! files and an appropriate error/warning message is written to stdErr.\n  //!\n  //! @note Find limits can be (re-)defined in the access interface by using\n  //! global rules:\n  //! => access set limit 100000 rate:user:*:FindFiles\n  //! => access set limit 50000 rate:user:*:FindDirs\n  //! or individual rules\n  //! => access set limit 100000000 rate:user:eosprod:FindFiles\n  //! => access set limit 100000000 rate:user:eosprod:FindDirs\n  //!\n  //! @note If 'key' contains a wildcard character in the end find produces a\n  //! list of directories containing an attribute starting with that key match\n  //! like var=sys.policy.*\n  //!\n  //! @note The millisleep variable allows to slow down full scans to decrease\n  //! the impact when doing large scans.\n  // ---------------------------------------------------------------------------\n  int _find(const char* path, XrdOucErrInfo& out_error, XrdOucString& stdErr,\n            eos::common::VirtualIdentity& vid,\n            std::map<std::string, std::set<std::string> >& found,\n            const char* key = 0, const char* val = 0, bool no_files = false,\n            time_t millisleep = 0, bool nscounter = true, int maxdepth = 0,\n            const char* filematch = 0, bool skip_version_dirs = false,\n            bool json_output = false, FILE* fstdout = NULL,\n            time_t max_ctime_dir = 0,       time_t max_ctime_file = 0,\n            std::map<std::string, time_t>* found_ctime_sec = 0,\n            ThreadAssistant* assistant = nullptr);\n\n  // ---------------------------------------------------------------------------\n  // delete dir\n  // ---------------------------------------------------------------------------\n  int remdir(const char* dirName,\n             XrdOucErrInfo& out_error,\n             const XrdSecEntity* client = 0,\n             const char* opaque = 0) override;\n\n  // ---------------------------------------------------------------------------\n  // delete dir by vid\n  // ---------------------------------------------------------------------------\n  int _remdir(const char* dirName,\n              XrdOucErrInfo& out_error,\n              eos::common::VirtualIdentity& vid,\n              const char* opaque = 0,\n              bool simulate = false);\n\n  //----------------------------------------------------------------------------\n  //! Rename file or directory - part of the XRootD API\n  //!\n  //! @note There are three flavours of rename function, two external and one\n  //! internal implementation. See the _rename implementation for details.\n  //!\n  //! @param old_name old name\n  //! @param new_name new name\n  //! @param error error object\n  //! @param client XRootD authentication object\n  //! @param infoO CGI of the old name\n  //! @param infoN CGI of the new name\n  //!\n  //! @return SFS_OK on success otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int rename(const char* old_name,\n             const char* new_name,\n             XrdOucErrInfo& error,\n             const XrdSecEntity* client = 0,\n             const char* infoO = 0,\n             const char* infoN = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Rename file or directory - EOS internal API that performs\n  //! permission checks\n  //----------------------------------------------------------------------------\n  int rename(const char* old_name,\n             const char* new_name,\n             XrdOucErrInfo& error,\n             eos::common::VirtualIdentity& vid,\n             const char* infoO = 0,\n             const char* infoN = 0,\n             bool overwrite = false);\n\n  //----------------------------------------------------------------------------\n  //! Rename file or directory - EOS internal low-level API\n  //!\n  //! @note Rename within a directory is simple since the quota accounting has\n  //! not to be modified. Rename of directories between quota nodes need to\n  //! recompute all the quota of the subtree which is moving and in case reject\n  //! the operation if there is not enough quota left. Overall it is a quite\n  //!complex function.\n  //!\n  //! @param old_name old name\n  //! @param new_name new name\n  //! @param error error object\n  //! @param vid virtual identity of the client\n  //! @param infoO CGI of the old name\n  //! @param infoN CGI of the new name\n  //! @param updateCTime indicates to update the change time of a directory\n  //! @param checkQuota indicates to check the quota during a rename operation\n  //! @param overwrite indicates if the target name can be overwritten\n  //! @param fusexcast if true do a FUSEX cast otherwise no\n  //!\n  //! @return SFS_OK on success otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int _rename(const char* old_name,\n              const char* new_name,\n              XrdOucErrInfo& error,\n              eos::common::VirtualIdentity& vid,\n              const char* infoO = 0,\n              const char* infoN = 0,\n              bool updateCTime = false,\n              bool checkQuota = false,\n              bool overwrite = false,\n              bool fusexcast = true);\n\n  //----------------------------------------------------------------------------\n  //! Rename file by atomically creating a symlink with the same name pointing\n  //! to the new destination - EOS internal low-level API.\n  //!\n  //! @note Rename within a directory is simple since the quota accounting has\n  //! not to be modified. Rename of directories between quota nodes need to\n  //! recompute all the quota of the subtree which is moving and in case reject\n  //! the operation if there is not enough quota left. Overall it is a quite\n  //!complex function.\n  //!\n  //! @param old_name old name\n  //! @param new_name new name\n  //! @param error error object\n  //! @param vid virtual identity of the client\n  //! @param infoO CGI of the old name\n  //! @param infoN CGI of the new name\n  //! @param updateCTime indicates to update the change time of a directory\n  //! @param checkQuota indicates to check the quota during a rename operation\n  //! @param fusexcast if true do a FUSEX cast otherwise no\n  //!\n  //! @return SFS_OK on success otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int _rename_with_symlink(const char* old_name,\n                           const char* new_name,\n                           XrdOucErrInfo& error,\n                           eos::common::VirtualIdentity& vid,\n                           const char* infoO = 0,\n                           const char* infoN = 0,\n                           bool updateCTime = false,\n                           bool checkQuota = false,\n                           bool fusexcast = true);\n\n  // ---------------------------------------------------------------------------\n  // symlink file/dir\n  // ---------------------------------------------------------------------------\n  int symlink(const char* sourceName,\n              const char* targetName,\n              XrdOucErrInfo& out_error,\n              const XrdSecEntity* client = 0,\n              const char* opaqueO = 0,\n              const char* opaqueN = 0,\n              bool overwrite = false);\n\n  // ---------------------------------------------------------------------------\n  // symlink file/dir by vid\n  // ---------------------------------------------------------------------------\n  int symlink(const char* sourceName,\n              const char* targetName,\n              XrdOucErrInfo& out_error,\n              eos::common::VirtualIdentity& vid,\n              const char* opaqueO = 0,\n              const char* opaqueN = 0,\n              bool overwrite = false);\n\n  // ---------------------------------------------------------------------------\n  // symlink file/dir by vid\n  // ---------------------------------------------------------------------------\n  int _symlink(const char* sourceName,\n               const char* targetName,\n               XrdOucErrInfo& out_error,\n               eos::common::VirtualIdentity& vid,\n               const char* opaqueO = 0,\n               const char* opaqueN = 0,\n               bool overwrite = false);\n\n  // ---------------------------------------------------------------------------\n  // read symbolic link\n  // ---------------------------------------------------------------------------\n  int readlink(const char* name,\n               XrdOucErrInfo& out_error,\n               XrdOucString& link,\n               const XrdSecEntity* client = 0,\n               const char* info = 0\n              );\n\n  // ---------------------------------------------------------------------------\n  // read symbolic link\n  // ---------------------------------------------------------------------------\n  int _readlink(const char* name,\n                XrdOucErrInfo& out_error,\n                eos::common::VirtualIdentity& vid,\n                XrdOucString& link\n               );\n\n\n  // ---------------------------------------------------------------------------\n  // stat file\n  // ---------------------------------------------------------------------------\n  int stat(const char* Name,\n           struct stat* buf,\n           XrdOucErrInfo& out_error,\n           std::string* etag,\n           const XrdSecEntity* client = 0,\n           const char* opaque = 0,\n           bool follow = true,\n           std::string* uri = 0,\n           std::string* cks = 0\n          );\n\n  int stat(const char* Name,\n           struct stat* buf,\n           XrdOucErrInfo& out_error,\n           const XrdSecEntity* client = 0,\n           const char* opaque = 0) override;\n\n  // ---------------------------------------------------------------------------\n  // stat file and get the checksum info\n  // ---------------------------------------------------------------------------\n  int _getchecksum(const char* Name,\n                   XrdOucErrInfo& out_error,\n                   std::string* xstype,\n                   std::string* xs,\n                   const XrdSecEntity* client = 0,\n                   const char* opaque = 0,\n                   bool follow = true\n                  );\n  // ---------------------------------------------------------------------------\n  // stat file by vid\n  // ---------------------------------------------------------------------------\n  int _stat(const char* Name,\n            struct stat* buf,\n            XrdOucErrInfo& out_error,\n            eos::common::VirtualIdentity& vid,\n            const char* opaque = 0,\n            std::string* etag = 0,\n            bool follow = true,\n            std::string* uri = 0,\n            std::string* cks = 0);\n  // ---------------------------------------------------------------------------\n  // set XRDSFS_OFFLINE and XRDSFS_HASBKUP flags\n  // ---------------------------------------------------------------------------\n  void _stat_set_flags(struct stat* buf);\n\n  // ---------------------------------------------------------------------------\n  // stat file to retrieve mode\n  // ---------------------------------------------------------------------------\n\n  int\n  stat(const char* Name,\n       mode_t& mode,\n       XrdOucErrInfo& out_error,\n       const XrdSecEntity* client = nullptr,\n       const char* opaque = nullptr) override\n  {\n    struct stat bfr;\n    int rc = stat(Name, &bfr, out_error, client, opaque);\n\n    if (!rc) {\n      mode = bfr.st_mode;\n    }\n\n    return rc;\n  }\n\n  // ---------------------------------------------------------------------------\n  // stat link\n  // ---------------------------------------------------------------------------\n  int lstat(const char* Name,\n            struct stat* buf,\n            XrdOucErrInfo& out_error,\n            const XrdSecEntity* client = 0,\n            const char* opaque = 0);\n\n  //----------------------------------------------------------------------------\n  //! Truncate a file (not supported in EOS, only via the file interface)\n  //!\n  //! @return SFS_ERROR and EOPNOTSUPP\n  //----------------------------------------------------------------------------\n  int truncate(const char*, XrdSfsFileOffset, XrdOucErrInfo&, const XrdSecEntity*,\n               const char*) override;\n\n  //----------------------------------------------------------------------------\n  //! Check access permissions for file/directories\n  //!\n  //! @note See the internal implementation _access for details.\n  //!\n  //! @param inpath path to access\n  //! @param mode access mode can be R_OK |& W_OK |& X_OK or F_OK\n  //! @param error\n  //! @param client XRootD authentication object\n  //! @param ininfo CGI\n  //!\n  //! @return SFS_OK if possible otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int access(const char* inpath, int mode, XrdOucErrInfo& error,\n             const XrdSecEntity* client, const char* ininfo);\n\n  //----------------------------------------------------------------------------\n  //! Check access permissions for file/directories - EOS low-level API\n  //!\n  //! @note If F_OK is specified we just check for the existence of the path,\n  //! which can be a file or directory. We don't support X_OK since it cannot\n  //! be mapped in case of files (we don't have explicit execution permissions).\n  //!\n  //! @note Locking: In the case we need to check the access of a file, we will\n  //! need to check the container and the file itself. We will lock the\n  //! container and the file individually before checking their access with the\n  //! AccessChecker.\n  //----------------------------------------------------------------------------\n  int _access(const char*, int mode, XrdOucErrInfo&,\n              eos::common::VirtualIdentity& vid, const char*);\n\n  //----------------------------------------------------------------------------\n  //! @brief define access permissions for files/directories\n  //!\n  //! @param path path to access\n  //! @param error object\n  //! @param virtual ID of the client\n  //! @param accperm - return string defining access permission\n  //! @return SFS_OK if found, otherwise SFS_ERR\n  //!\n  //! Definition of accperm see here:\n  //! Code  Resource         Description\n  //! S     File or Folder   is shared\n  //! R     File or Folder   can share (includes reshare)\n  //! M     File or Folder   is mounted (like on DropBox, Samba, etc.)\n  //! W     File             can write file\n  //! C     Folder           can create file in folder\n  //! K     Folder           can create folder (mkdir)\n  //! D     File or Folder   can delete file or folder\n  //! N     File or Folder   can rename file or folder\n  //! V     File or Folder   can move file or folder\n  //----------------------------------------------------------------------------\n  int acc_access(const char* path, XrdOucErrInfo& error,\n                 eos::common::VirtualIdentity& vid, std::string& accperm);\n\n  //----------------------------------------------------------------------------\n  //! Test if this is eosnobody accessing a squashfs file\n  //!\n  //! @param path path to access\n  //! @param vid virtual identity of the user\n  //!\n  //! @return 0 if no squashfs access, 1 if squashfs but not allowed 2 if squashfs and allowed\n  //---------------------------------------------------------------------------\n  int is_squashfs_access(const char* path,\n                         eos::common::VirtualIdentity& vid);\n\n  //----------------------------------------------------------------------------\n  //! Test if public access is allowed in a given path\n  //!\n  //! @param path path to access\n  //! @param vid virtual identity of the user\n  //!\n  //! @return true if access is allowed, otherwise false\n  //----------------------------------------------------------------------------\n  bool allow_public_access(const char* path, eos::common::VirtualIdentity& vid);\n\n  //----------------------------------------------------------------------------\n  //! Get the allowed XrdAccPrivs i.e. allowed operations on the given path\n  //! for the client in the XrdSecEntity\n  //!\n  //! @param path accessed path\n  //! @param client client identity\n  //!\n  //! @return XrdAccPrivs flags for the path and user combinatino\n  //----------------------------------------------------------------------------\n  XrdAccPrivs GetXrdAccPrivs(const std::string& path,\n                             const XrdSecEntity* client, XrdOucEnv* env);\n\n  // ---------------------------------------------------------------------------\n  // set utimes\n  // ---------------------------------------------------------------------------\n  int utimes(const char*, struct timespec* tvp, XrdOucErrInfo&,\n             const XrdSecEntity*, const char*);\n  // ---------------------------------------------------------------------------\n  // set utimes by vid\n  // ---------------------------------------------------------------------------\n  int _utimes(const char*, struct timespec* tvp, XrdOucErrInfo&,\n              eos::common::VirtualIdentity& vid, const char* opaque = 0);\n\n  // ---------------------------------------------------------------------------\n  // touch a file\n  // ---------------------------------------------------------------------------\n  int _touch(const char* path,\n             XrdOucErrInfo& error,\n             eos::common::VirtualIdentity& vid,\n             const char* ininfo = 0,\n             bool doLock = true,\n             bool useLayout = false,\n             bool truncate = false,\n             size_t size = 0,\n             bool absorb = false,\n             const char* hardlinkpath = 0,\n             const char* checksuminfo = 0,\n             std::string* errmsg = 0);\n\n  //----------------------------------------------------------------------------\n  //! List extended attributes for a given file/directory - high-level API.\n  //! See _attr_ls for details.\n  //!\n  //! @param path file/directory name to list attributes\n  //! @param out_error error object\n  //! @param client XRootD authentication object\n  //! @param opaque CGI\n  //! @param map return object with the extended attributes, key-value map\n  //!\n  //! @return SFS_OK if success otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int attr_ls(const char* path, XrdOucErrInfo& out_error,\n              const XrdSecEntity* client, const char* opaque,\n              eos::IContainerMD::XAttrMap& map);\n\n  //----------------------------------------------------------------------------\n  //! List extended attributes for a given file/directory - low-level API.\n  //!\n  //! @param path file/directory name to list attributes\n  //! @param out_error error object\n  //! @param vid virtual identity of the client\n  //! @param client XRootD authentication object\n  //! @param opaque CGI\n  //! @param map return object with the extended attributes, key-value map\n  //! @param lock if true take the namespace lock, otherwise don't\n  //! @param link if true honour sys.link attribute, otherwise don't\n  //!\n  //! @return SFS_OK if success otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int _attr_ls(const char* path, XrdOucErrInfo& out_error,\n               const eos::common::VirtualIdentity& vid,\n               const char* opaque, eos::IContainerMD::XAttrMap& map,\n               bool links = false);\n\n  //----------------------------------------------------------------------------\n  //! Set an extended attribute for a given file/directory - high-level API.\n  //! See _attr_set for details.\n  //!\n  //! @param path file/directory name to set attribute\n  //! @param out_error error object\n  //! @param client XRootD authentication object\n  //! @param opaque CGI\n  //! @param key key to set\n  //! @param value value to set for key\n  //!\n  //! @return SFS_OK if success otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int attr_set(const char* path, XrdOucErrInfo& out_error,\n               const XrdSecEntity* client, const char* opaque,\n               const char* key, const char* value);\n\n  // ---------------------------------------------------------------------------\n  //! Set an extended attribute for a given file/directory - low-level API.\n  //!\n  //! @param path file/directory name to set attribute\n  //! @param out_error error object\n  //! @param vid virtual identity of the client\n  //! @param opaque CGI\n  //! @param key key to set\n  //! @param value value to set for key\n  //! @param exlusive only create attribute if it does not exist\n  //!\n  //! @return SFS_OK if success otherwise SFS_ERROR\n  // ---------------------------------------------------------------------------\n  int _attr_set(const char* path, XrdOucErrInfo& out_error,\n                eos::common::VirtualIdentity& vid,\n                const char* opaque, const char* key, const char* value,\n                bool exclusive = false);\n\n  //----------------------------------------------------------------------------\n  //! Set an extended attribute for a given metadata object - low-level API.\n  //! @note Metadata object must be properly write locked prior to calling\n  //! this method.\n  //!\n  //! @param item metadata object\n  //! @param key attribute key\n  //! @param value attribute value\n  //! @param exclussive flag to mark exclusive set of ACLs\n  //! @param vid virtual identity of the client\n  //! @param fuse_batch fusex batch of notifications\n  //!\n  //! @return SFS_OK if successful, otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  bool _attr_set(eos::FileOrContainerMD& item, std::string_view key,\n                 std::string_view value, bool exclusive,\n                 eos::common::VirtualIdentity& vid,\n                 eos::mgm::FusexCastBatch& fuse_batch);\n\n  //----------------------------------------------------------------------------\n  //! Get an extended attribute for a given entry by key - high-level API.\n  //! @note Normal POSIX R_OK & X_OK permissions are required to retrieve a key\n  //!\n  //! @param path directory name to get attribute\n  //! @param out_error error object\n  //! @param client XRootD authentication object\n  //! @param opaque CGI\n  //! @param key key to get\n  //! @param value value returned\n  //!\n  //! @return SFS_OK if success otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int attr_get(const char* path, XrdOucErrInfo& out_error,\n               const XrdSecEntity* client, const char* opaque,\n               const char* key, std::string& value);\n\n  //----------------------------------------------------------------------------\n  //! Get an extended attribute for a given entry by key - low-level API.\n  //!\n  //! @param path directory name to get attribute\n  //! @param out_error error object\n  //! @param vid virtual identity of the client\n  //! @param opaque CGI\n  //! @param key key to get\n  //! @param value value returned\n  //!\n  //! @return SFS_OK if success, otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int _attr_get(const char* path, XrdOucErrInfo& out_error,\n                eos::common::VirtualIdentity& vid, const char* opaque,\n                const char* key, std::string& value);\n\n  //----------------------------------------------------------------------------\n  //! Get an extended attribute for a given metadata object - low-level API.\n  //!\n  //! @param item metadata object\n  //! @param key key to get\n  //! @param value value returned\n  //!\n  //! @return true if attribute was found, false otherwise\n  //----------------------------------------------------------------------------\n  bool _attr_get(eos::FileOrContainerMD& item, std::string key,\n                 std::string& rvalue);\n\n  //----------------------------------------------------------------------------\n  //! Get an extended attribute for a given container - low-level API.\n  //!\n  //! @param cmd container metadata object\n  //! @param key key to get\n  //! @param value value returned\n  //!\n  //! @return true if attribute was found, false otherwise\n  //----------------------------------------------------------------------------\n  bool _attr_get(eos::IContainerMD& cmd, std::string key, std::string& rvalue);\n\n  //----------------------------------------------------------------------------\n  //! Get an extended attribute for a given file - low-level API.\n  //!\n  //! @param file file metadata object\n  //! @param key key to get\n  //! @param value value returned\n  //!\n  //! @return true if attribute was found, false otherwise\n  //----------------------------------------------------------------------------\n  bool _attr_get(eos::IFileMD& fmd, std::string key, std::string& rvalue);\n\n  //----------------------------------------------------------------------------\n  //! Remove an extended attribute for a given entry - high-level API.\n  //! See _attr_rem for details.\n  //!\n  //! @param path file/directory name to delete attribute\n  //! @param out_error error object\n  //! @param client XRootD authentication object\n  //! @param opaque CGI\n  //! @param key key to delete\n  //!\n  //! @return SFS_OK if success, otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int attr_rem(const char* path, XrdOucErrInfo& out_error,\n               const XrdSecEntity* client, const char* opaque, const char* key);\n\n  //----------------------------------------------------------------------------\n  //! Remove an extended attribute for a given entry - low-level API.\n  //!\n  //! @param path file/directory name to delete attribute\n  //! @param out_error error object\n  //! @param vid virtual identity of the client\n  //! @param opaque CGI\n  //! @param key key to delete\n  //! @param take namespace lock\n  //!\n  //! @return SFS_OK if success, otherwise SFS_ERROR\n  //----------------------------------------------------------------------------\n  int _attr_rem(const char* path, XrdOucErrInfo& out_error,\n                eos::common::VirtualIdentity& vid,\n                const char* opaque, const char* key);\n\n  // ---------------------------------------------------------------------------\n  // drop stripe by vid\n  // ---------------------------------------------------------------------------\n  int _dropstripe(const char* path,\n                  eos::common::FileId::fileid_t fid,\n                  XrdOucErrInfo& error,\n                  eos::common::VirtualIdentity& vid,\n                  unsigned long fsid,\n                  bool forceRemove = false);\n\n  // ---------------------------------------------------------------------------\n  // drop all stripes of a file\n  // ---------------------------------------------------------------------------\n  int _dropallstripes(const char* path,\n                      XrdOucErrInfo& error,\n                      eos::common::VirtualIdentity& vid,\n                      bool forceRemove = false);\n\n  //----------------------------------------------------------------------------\n  //! Send verify stripe request to a certain file system for file path\n  //!\n  //! @param path file path\n  //! @param error error object\n  //! @param vid client virtual identity\n  //! @param fsid file system identifier\n  //! @param options opaque options that are appeneded to the request\n  //!\n  //! @return SFS_OK if successful, otherwise SFS_ERROR\n  //!\n  //! @note this method requires POSIX W_OK & X_OK on the parent dir to succeed\n  //----------------------------------------------------------------------------\n  int _verifystripe(const char* path,\n                    XrdOucErrInfo& error,\n                    eos::common::VirtualIdentity& vid,\n                    unsigned long fsid,\n                    const std::string& options);\n\n  //----------------------------------------------------------------------------\n  //! Send verify stripe request to a certain file system for file identifier\n  //!\n  //! @param fid file identifier\n  //! @param error error object\n  //! @param vid client virtual identity\n  //! @param fsid file system identifier\n  //! @param options opaque options that are appeneded to the request\n  //! @param ns_path namespace path if known\n  //!\n  //! @return SFS_OK if successful, otherwise SFS_ERROR\n  //!\n  //! @note this method requires POSIX W_OK & X_OK on the parent dir to succeed\n  //----------------------------------------------------------------------------\n  int _verifystripe(const eos::IFileMD::id_t fid,\n                    XrdOucErrInfo& error,\n                    eos::common::VirtualIdentity& vid,\n                    unsigned long fsid,\n                    const std::string& options,\n                    const std::string& ns_path = \"unknown\");\n\n  //----------------------------------------------------------------------------\n  //! Move file replica/stripe from source to target file system\n  //!\n  //! @param path file name to move stripe\n  //! @param error error object\n  //! @param vid virtual identity of the client\n  //! @param sourcefsid filesystem id of the source\n  //! param targetfsid filesystem id of the target\n  //!\n  //! @return SFS_OK if success otherwise SFS_ERROR\n  //!\n  //! @note The function requires POSIX W_OK & X_OK on the parent directory to\n  //! succeed. It calls _replicatestripe internally.\n  //----------------------------------------------------------------------------\n  int _movestripe(const char* path,\n                  XrdOucErrInfo& error,\n                  eos::common::VirtualIdentity& vid,\n                  unsigned long sourcefsid,\n                  unsigned long targetfsid);\n\n  //----------------------------------------------------------------------------\n  //! Copy file replica/stripe from source to target file system\n  //!\n  //! @param path file name to move stripe\n  //! @param error error object\n  //! @param vid virtual identity of the client\n  //! @param sourcefsid filesystem id of the source\n  //! param targetfsid filesystem id of the target\n  //!\n  //! @return SFS_OK if success otherwise SFS_ERROR\n  //!\n  //! @note The function requires POSIX W_OK & X_OK on the parent directory to\n  //! succeed. It calls _replicatestripe internally.\n  //----------------------------------------------------------------------------\n  int _copystripe(const char* path,\n                  XrdOucErrInfo& error,\n                  eos::common::VirtualIdentity& vid,\n                  unsigned long sourcefsid,\n                  unsigned long targetfsid);\n\n  //----------------------------------------------------------------------------\n  //! Copy file replica/stripe from source to target file system - by path\n  //!\n  //! @param path file name to move stripe\n  //! @param error error object\n  //! @param vid virtual identity of the client\n  //! @param sourcefsid filesystem id of the source\n  //! param targetfsid filesystem id of the target\n  //! @param dropsource if true source is deleted(dropped) after successful\n  //!  replication\n  //!\n  //! @return SFS_OK if success otherwise SFS_ERROR\n  //!\n  //! @note  The function requires POSIX W_OK & X_OK on the parent directory to\n  //! succeed. It calls _replicatestripe with a file meta data object.\n  //----------------------------------------------------------------------------\n  int _replicatestripe(const char* path,\n                       XrdOucErrInfo& error,\n                       eos::common::VirtualIdentity& vid,\n                       unsigned long sourcefsid,\n                       unsigned long targetfsid,\n                       bool dropstripe = false);\n\n  //----------------------------------------------------------------------------\n  //! Copy file replica/stripe from source to target file system - by FileMD\n  //!\n  //! @param path file name to move stripe\n  //! @param error error object\n  //! @param vid virtual identity of the client\n  //! @param sourcefsid filesystem id of the source\n  //! param targetfsid filesystem id of the target\n  //! @param dropsource if true source is deleted(dropped) after successful\n  //!  replication\n  //!\n  //! @return SFS_OK if success otherwise SFS_ERROR\n  //!\n  //! @note  The function requires POSIX W_OK & X_OK on the parent directory to\n  //! succeed. It calls _replicatestripe with a file meta data object.\n  //----------------------------------------------------------------------------\n  int _replicatestripe(eos::IFileMD* fmd,\n                       const char* path,\n                       XrdOucErrInfo& error,\n                       eos::common::VirtualIdentity& vid,\n                       unsigned long sourcefsid,\n                       unsigned long targetfsid,\n                       bool dropstripe = false);\n\n  // ---------------------------------------------------------------------------\n  // create a versioned file\n  // ---------------------------------------------------------------------------\n  int Version(eos::common::FileId::fileid_t fileid,\n              XrdOucErrInfo& error,\n              eos::common::VirtualIdentity& vid,\n              int max_versions,\n              XrdOucString* versionedname = 0,\n              bool simulate = false);\n\n  // ---------------------------------------------------------------------------\n  // purge versioned files to max_versions\n  // ---------------------------------------------------------------------------\n\n  int PurgeVersion(const char* versiondir,\n                   XrdOucErrInfo& error,\n                   int max_versions);\n\n  //----------------------------------------------------------------------------\n  //! Send query (XrdFileSystem::Query) to the given endpoint and collect the\n  //! repsonse\n  //!\n  //! @param hostname endpoint hostname\n  //! @param port endpoint port\n  //! @param request string encoded request\n  //! @param response string encoded reponse\n  //! @param timeout request timeout\n  //!\n  //! @return 0 if successful, otherwise errno\n  //----------------------------------------------------------------------------\n  int SendQuery(const std::string& hostname, int port,\n                const std::string& request, std::string& response,\n                uint16_t timeout = 5);\n\n  //----------------------------------------------------------------------------\n  //! Broadcast query (XrdFileSystem::Query) to the given endpoints and collect\n  //! the repsonses\n  //!\n  //! @param request string encoded request\n  //! @param set of endpoints, if empty then send to all registered FSTs\n  //! @param map of responses from each individual endpoint\n  //! @param timeout optional timeout value for the request, 0 no timeout\n  //!\n  //! @return 0 if successful, otherwise 1 if any reply had errors\n  //----------------------------------------------------------------------------\n  int BroadcastQuery(const std::string& request,\n                     std::set<std::string>& endpoints,\n                     std::map<std::string, std::pair<int, std::string>>&\n                     reponses,\n                     uint16_t timeout = 0);\n\n  //----------------------------------------------------------------------------\n  //! @brief Send a resync command for a file identified by id and filesystem.\n  //! A resync synchronizes the cache DB on the FST with the meta data on disk\n  //! and on the MGM and flags files accordingly with size/checksum errors.\n  //!\n  //! @param fid file id to be resynced\n  //! @param fsid filesystem id where the file should be resynced\n  //! @param force if true force creation of the local entry in the DB\n  //!\n  //! @return true if successfully send otherwise false\n  //----------------------------------------------------------------------------\n  int QueryResync(eos::common::FileId::fileid_t fid,\n                  eos::common::FileSystem::fsid_t fsid, bool force = false);\n\n  //----------------------------------------------------------------------------\n  //! Remove file/container metadata object that was already deleted before\n  //! but it's still in the namespace detached from any parent\n  //!\n  //! @param id file/container id\n  //! @param is_dir if true id refers to a container, otherwise a file object\n  //! @param force if set then force remove unlinked locations even if they\n  //!        were not properly deleted from the diskserver\n  //! @param msg outcome information forwarded to the client\n  //!\n  //! @return true if deletion successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool RemoveDetached(uint64_t id, bool is_dir, bool force,\n                      std::string& msg) const;\n\n  // ---------------------------------------------------------------------------\n  // static Mkpath is not supported\n  // ---------------------------------------------------------------------------\n\n  static int\n  Mkpath(const char* path,\n         mode_t mode,\n         const char* info = 0,\n         XrdSecEntity* client = 0,\n         XrdOucErrInfo* error = 0)\n  {\n    return SFS_ERROR;\n  }\n\n  // ---------------------------------------------------------------------------\n  // make a file sharing path with signature\n  // ---------------------------------------------------------------------------\n\n  std::string CreateSharePath(const char* path,\n                              const char* info,\n                              time_t expires,\n                              XrdOucErrInfo& error,\n                              eos::common::VirtualIdentity& vid);\n\n  // ---------------------------------------------------------------------------\n  // verify a file sharing path with signature\n  // ---------------------------------------------------------------------------\n\n  bool VerifySharePath(const char* path,\n                       XrdOucEnv* opaque);\n\n\n  // ---------------------------------------------------------------------------\n  // create Ofs error message\n  // ---------------------------------------------------------------------------\n  int Emsg(const char*, XrdOucErrInfo&, int, const char* x,\n           const char* y = \"\");\n\n  // ---------------------------------------------------------------------------\n  // Configuration routine\n  // ---------------------------------------------------------------------------\n  virtual int Configure(XrdSysError&);\n\n  //----------------------------------------------------------------------------\n  //! Init function\n  //!\n  //! This is just kept to be compatible with standard OFS plugins, but it is\n  //! not used for the moment.\n  //!\n  //----------------------------------------------------------------------------\n  virtual bool Init(XrdSysError&);\n\n  //----------------------------------------------------------------------------\n  // Create Stall response\n  //!\n  //! @param error error object with text/code\n  //! @param vid client virtual identity\n  //! @param stime seconds to stall\n  //! @param msg message for the client\n  //!\n  //! @return number of seconds of stalling\n  //----------------------------------------------------------------------------\n  int Stall(XrdOucErrInfo& error, eos::common::VirtualIdentity& vid,\n            int stime, const char* msg);\n\n  //----------------------------------------------------------------------------\n  //! Create Redirection response\n  //!\n  //! @param error error object with text/code\n  //! @param host redirection target host\n  //! @param port redirection target port\n  //! @param path\n  //! @param collapse should the redirect collpase\n  //!---------------------------------------------------------------------------\n  int Redirect(XrdOucErrInfo& error, const char* host, int& port,\n               const char* path = \"\", bool collapse = false);\n\n  //----------------------------------------------------------------------------\n  //! Function to test if a client based on the called function and his\n  //! identity should be stalled\n  //!\n  //! @param function name of the function to check\n  //! @param accessmode macro generated parameter defining if this is a reading\n  //! or writing (namespace modifying) function\n  //! @param stalltime returns the time for a stall\n  //! @param stallmsg returns the message to be displayed to the user\n  //!\n  //! @return true if client should get a stall, otherwise false\n  //!\n  //! @note  The stall rules are defined by globals in the Access object\n  //! (see Access.cc)\n  //----------------------------------------------------------------------------\n  bool ShouldStall(const char* function, int accessmode,\n                   eos::common::VirtualIdentity& vid,\n                   int& stalltime, XrdOucString& stallmsg);\n\n  //----------------------------------------------------------------------------\n  //! @brief Check if a client based on the called function and his\n  //! identity should be redirected. The redirection rules are defined by\n  //! globals in the Access object (see Access.cc)\n  //!\n  //! @param function name of the function to check\n  //! @param __AccessMode__ macro generated parameter defining if this is a\n  //!reading or writing (namespace modifying) function\n  //! @param host returns the target host of a redirection\n  //! @param port returns the target port of a redirection\n  //! @param collapse returns if the redirection should collapse\n  //! @return true if client should get a redirected otherwise false\n  //!\n  //----------------------------------------------------------------------------\n  bool ShouldRedirect(const char* function, int accessmode,\n                      eos::common::VirtualIdentity& vid,\n                      std::string& host, int& port, bool& collapse);\n\n  //----------------------------------------------------------------------------\n  //! @brief Test if a client based on the called function and his identity\n  //! should be re-routed.\n  //!\n  //! @param function name of the function to check\n  //! @param accessmode macro generated parameter defining if this is a\n  //!        reading or writing (namespace modifying) function\n  //! @param vid virtual identity\n  //! @param host target host of a redirection\n  //! @param port target port of a redirection\n  //! @param stall_timeout timeout value in case stalling is required\n  //!\n  //! @return true if client should get a redirected otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool ShouldRoute(const char* function, int accessmode,\n                           eos::common::VirtualIdentity& vid, const char* path,\n                           const char* info, std::string& host, int& port,\n                           int& stall_timeout);\n\n  //----------------------------------------------------------------------------\n  //! Test if there is stall configured for the given rule\n  //!\n  //! @param path the path where the rule should be checked (currently unused)\n  //! @param rule the rule to check e.g. rule = \"ENOENT:*\" meaning we send a\n  //!         stall if an entry is missing\n  //! @param stalltime returns the configured time to stall\n  //! @param stallmsg returns the message displayed to the client during a stall\n  //! @return true if there is a stall configured otherwise false\n  //!\n  //! The interface is generic to check for individual paths, but currently we\n  //! just implemented global rules for any paths. See Access.cc for details.\n  //----------------------------------------------------------------------------\n  bool HasStall(const char* path,\n                const char* rule,\n                int& stalltime,\n                XrdOucString& stallmsg);\n\n  //----------------------------------------------------------------------------\n  //! Test if there is redirect configured for a given rule\n  //!\n  //! @param path the path where the rule should be checked (currently unused)\n  //! @param rule the rule to check e.g. rule = \"ENOENT:*\" meaning we send a\n  //!        redirect if an entry is missing\n  //! @param host returns the redirection target host\n  //! @param port returns the redirection target port\n  //! @return true if there is a redirection configured otherwise false\n  //!\n  //! The interface is generic to check for individual paths, but currently we\n  //! just implemented global rules for any paths. See Access.cc for details.\n  //----------------------------------------------------------------------------\n  bool HasRedirect(const char* path, const char* rule, std::string& host,\n                   int& port);\n\n  //----------------------------------------------------------------------------\n  //! Check if name space is booted\n  //!\n  //! @return true if booted, otherwise false\n  //----------------------------------------------------------------------------\n  bool IsNsBooted() const;\n\n  // ---------------------------------------------------------------------------\n  // Retrieve a mapping for a given path\n  // ---------------------------------------------------------------------------\n  void PathRemap(const char* inpath,\n                 XrdOucString& outpath);  // global namespace remapping\n\n  //----------------------------------------------------------------------------\n  //! Allows to map paths like e.g. /store/ to /eos/instance/store/ to provide\n  //! an unprefixed global namespace in a storage federation. It is used by\n  //! the Configuration Engine to apply a mapping from a configuration file.\n  //!\n  //! @parma source mapping source\n  //! @param target mapping target\n  //! @param store_config if true also store mapping in the configuration,\n  //!        otherwise don't\n  //----------------------------------------------------------------------------\n  bool AddPathMap(const char* source, const char* target,\n                  bool store_config = true);\n\n  // ---------------------------------------------------------------------------\n  // Reset path mapping\n  // ---------------------------------------------------------------------------\n  void ResetPathMap();  // reset/empty the path map\n\n  //----------------------------------------------------------------------------\n  //! Drop replica/stripe for the given file identifier form the FST and also\n  //! update the namespace view for the given file system id.\n  //!\n  //! @param fid file identifier\n  //! @param fsid file system id from where to drop the replica\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool DropReplica(eos::IFileMD::id_t fid,\n                   eos::common::FileSystem::fsid_t fsid) const;\n\n  //----------------------------------------------------------------------------\n  //! Send an explicit deletion message to any fsid/fid pair. This routine\n  //! signs a deletion message for the given file id and sends it to the\n  //! referenced file system.\n  //!\n  //! @param fsid file system id where to run a deletion\n  //! @param fid file id to be deleted\n  //! @param is_fsck true if deletion comes from fsck, otherwise false\n  //!\n  //! @result true if successfully sent otherwise false\n  //-----------------------------------------------------------------------------\n  bool DeleteExternal(eos::common::FileSystem::fsid_t fsid,\n                      unsigned long long fid, bool is_fsck = false);\n\n  //----------------------------------------------------------------------------\n  //! Authentication master thread function - accepts requests from EOS AUTH\n  //! plugins which he then forwards to worker threads.\n  //!\n  //! @param assistant thread doing the work\n  //----------------------------------------------------------------------------\n  void AuthMasterThread(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Authentication worker thread startup static function\n  //!\n  //! @param pp pointer to the XrdMgmOfs class\n  //!\n  //----------------------------------------------------------------------------\n  static void* StartAuthWorkerThread(void* pp);\n\n  //----------------------------------------------------------------------------\n  //! Authentication worker thread function - accepts requests from the master,\n  //! executed the proper action and replies with the result.\n  //----------------------------------------------------------------------------\n  void AuthWorkerThread();\n\n  //----------------------------------------------------------------------------\n  //! Reconnect zmq::socket object\n  //!\n  //! @param socket zmq socket\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ConnectToBackend(zmq::socket_t*& socket);\n\n  //----------------------------------------------------------------------------\n  // Signal handler for signal 40 to start profiling the heap\n  //----------------------------------------------------------------------------\n  static void StartHeapProfiling(int);\n\n  // ---------------------------------------------------------------------------\n  // Signal handler for signal 41 to stop profiling the heap\n  // ---------------------------------------------------------------------------\n  static void StopHeapProfiling(int);\n\n  // ---------------------------------------------------------------------------\n  // Signal handler for signal 42 to dump the heap profile\n  // ---------------------------------------------------------------------------\n  static void DumpHeapProfile(int);\n\n  //----------------------------------------------------------------------------\n  // Filesystem error and configuration change listener thread function\n  //----------------------------------------------------------------------------\n  void FsConfigListener(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  // A thread monitoring for important key-value changes in filesystems\n  //----------------------------------------------------------------------------\n  void FileSystemMonitorThread(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  // Process incoming MGM configuration change\n  //----------------------------------------------------------------------------\n  void processIncomingMgmConfigurationChange(const std::string& key);\n\n  //----------------------------------------------------------------------------\n  //! Process geotag change on the specified filesystem\n  //!\n  //! @param queue file system queue path\n  //----------------------------------------------------------------------------\n  void ProcessGeotagChange(const std::string& queue);\n\n  //------------------------------------------------------------------------------\n  //! Add backup job to the queue to be picked up by the archive/backup submitter\n  //! thread.\n  //!\n  //! @param job_opaque string representing the opaque information necessary for\n  //!        the backup operation to be executed\n  //! @return true if submission successful, otherwise false\n  //------------------------------------------------------------------------------\n  bool SubmitBackupJob(const std::string& job_opaque);\n\n  //------------------------------------------------------------------------------\n  //! Get set of pending backups i.e. return the path of the backup operations\n  //! that are still pending at the MGM.\n  //!\n  //! @return vector of ArchDirStatus object representing the status of the\n  //!         pending backup operations\n  //------------------------------------------------------------------------------\n  std::vector<ProcCommand::ArchDirStatus>\n  GetPendingBkps();\n\n  //------------------------------------------------------------------------------\n  //! Discover/search for a service provided to the plugins by the platform\n  //!\n  //! @param svc_name name of the service the plugin wants to use\n  //! @param opaque parameter for the service or reference to returned discovery\n  //!        service info\n  //!\n  //! @return 0 if successful, otherwise errno\n  //------------------------------------------------------------------------------\n  static int32_t DiscoverPlatformServices(const char* svc_name, void* opaque);\n\n  //------------------------------------------------------------------------------\n  //! Write an report log record about final deletion\n  //!\n  //! @param fmd meta data\n  //! @param full_path full path of deleted file\n  //------------------------------------------------------------------------------\n  void WriteRmRecord(const std::shared_ptr<eos::IFileMD>& fmd,\n                     const char* full_path = nullptr);\n\n  //------------------------------------------------------------------------------\n  //! Write an report log record about deletion into recycle bin\n  //!\n  //! @param fmd meta data\n  //------------------------------------------------------------------------------\n  void WriteRecycleRecord(const std::shared_ptr<eos::IFileMD>& fmd);\n\n  //----------------------------------------------------------------------------\n  //! Check if a host was tried in an URL already with the given error\n  //----------------------------------------------------------------------------\n  bool Tried(XrdCl::URL& url, std::string& host, const char* serr);\n\n  //----------------------------------------------------------------------------\n  //! Wait until namespace is initialized - thread cancellation point\n  //----------------------------------------------------------------------------\n  void WaitUntilNamespaceIsBooted();\n\n  //----------------------------------------------------------------------------\n  //! Wait until namespace is initialized - thread cancellation point\n  //!\n  //! @param assistant reference to thread executing the job\n  //----------------------------------------------------------------------------\n  void WaitUntilNamespaceIsBooted(ThreadAssistant& assistant);\n\n  //----------------------------------------------------------------------------\n  // Configuration variables\n  //----------------------------------------------------------------------------\n  char* ConfigFN; ///< name of the configuration file\n  IConfigEngine* mConfigEngine; ///< storing/restoring configuration\n  std::chrono::seconds mCapabilityValidity; ///< Capability validity duration\n  XrdOucString MgmOfsBroker; ///< Url of the message broker without MGM subject\n  XrdOucString MgmOfsBrokerUrl; ///< Url of the message broker with MGM subject\n  XrdOucString MgmArchiveDstUrl; ////< URL where all archives are saved\n  XrdOucString MgmArchiveSvcClass; ////< CASTOR svcClass for archive transfers\n  //! Queue where we are sending to by default\n  XrdOucString MgmDefaultReceiverQueue;\n  XrdOucString MgmOfsName; ///< mount point of the filesystem\n  XrdOucString MgmOfsAlias; ///< alias of this MGM instance\n  //! Xrootd port where redirections go on the FSTs -default is 1094\n  XrdOucString MgmOfsTargetPort;\n  XrdOucString MgmOfsQueue; ///< our mgm queue name\n  XrdOucString MgmOfsInstanceName; ///< name of the EOS instance\n  ///< Name of the automatically loaded configuration file\n  XrdOucString MgmConfigAutoLoad;\n  //! Directory where tmp. archive transfer files are saved\n  XrdOucString MgmArchiveDir;\n  XrdOucString MgmProcPath; ///< Directory with proc files\n  //! Directory with conversion files (used as temporary files when a layout\n  //! is changed using third party copy)\n  XrdOucString MgmProcConversionPath;\n  XrdOucString MgmProcDevicesPath;\n  XrdOucString MgmProcWorkflowPath; ///< Directory with workflows\n  XrdOucString\n  MgmProcTrackerPath; ///< Directory with file creations which are not consistent (yet)\n  XrdOucString\n  MgmProcTokenPath; ///< Directory storing the token generation as ext attribute and vouchers\n  XrdOucString MgmProcBulkRequestPath; ///< Directory storing the bulk requests\n  //! Full path to the master indication proc file\n  XrdOucString MgmProcMasterPath;\n  XrdOucString MgmProcArchivePath; ///< EOS directory where archive dir inodes\n  ///< are saved for fast find functionality\n  //! Path to namespace changelog file for files\n  XrdOucString MgmNsFileChangeLogFile;\n  ///< Path to namespace changelog file for directories\n  XrdOucString MgmNsDirChangeLogFile;\n  XrdOucString MgmConfigQueue; ///< name of the mgm-wide broadcasted shared hash\n  XrdOucString MgmAuthDir; ///< Directory containing exported authentication token\n  XrdOucString ManagerId; ///< manager id in <host>:<port> format\n  XrdOucString ManagerIp; ///< manager ip in <xxx.yyy.zzz.vvv> format\n  XrdOucString\n  mPostSlaveToMaster; ///< Path of the script running after the Slave to Master transition\n  int ManagerPort; ///< manager port as number e.g. 1094\n  uint16_t XrdHttpPort; ///< The port on which the XrdHttp server is running\n  std::string\n  ProtoWFEndPoint; ///< host and port of service to communicate with in case of proto workflows (typically CTA frontend)\n  std::string\n  ProtoWFResource; ///< endpoint of SSI service to communicate with in case of proto workflows (typically CTA frontend)\n  //! Process state after namespace load time\n  eos::common::LinuxStat::linux_stat_t LinuxStatsStartup;\n  char* HostName; ///< our hostname as derived in XrdOfs\n  char* HostPref; ///< our hostname as derived in XrdOfs without domain\n  bool protowfusegrpc; ///< use xrootd/ssi or grpc to talk to CTA Frontend?\n  //! Path to the JWT to be used for authenticating gRPC WFE calls to CTA Frontend\n  std::string JwtTokenPath;\n  //! Use TLS encrypted connections or plaintext connections for grpc\n  bool protowfusegrpctls = false;\n  static XrdSysError* eDest; ///< error routing object\n\n  //----------------------------------------------------------------------------\n  // Namespace specific variables\n  //----------------------------------------------------------------------------\n  //! Initialization state of the namespace\n  std::atomic<NamespaceState> mNamespaceState;\n  std::atomic<time_t> mFileInitTime; ///< Time for the file initialization\n  std::atomic<time_t> mTotalInitTime; ///< Time for entire initialization\n  std::atomic<time_t> mStartTime; ///< Timestamp when daemon started\n  bool Shutdown; ///< true if the shutdown function was called => avoid to join some threads\n  //! Const strings to print the namespace boot state as in eNamespace\n\n  //----------------------------------------------------------------------------\n  // State variables\n  //----------------------------------------------------------------------------\n  //! Next free file id after namespace boot\n  std::atomic<uint64_t> mBootFileId;\n  ///< Next free container id after namespace boot\n  std::atomic<uint64_t> mBootContainerId;\n  bool IsRedirect; ///< true if the Redirect function should be called to redirect\n  bool IsStall; ///< true if the Stall function should be called to send a wait\n  bool mAuthorize; ///< Determine if the authorization should be applied or not\n  std::string mAuthLib; ///< Path to authorization library\n  bool mTapeEnabled; ///< True if support for tape is enabled\n  std::string\n  mPrepareDestSpace; ///< Space to be used when retrieving files from tape\n  unsigned int\n  mReqIdMax; ///< Maximum number of request IDs on a single retrieving file\n  //!  Acts only as a redirector, disables many components in the MGM\n  bool MgmRedirector;\n  //! Writes error log with cluster wide collected errors in\n  //! /var/log/eos/mgm/error.log\n  bool mErrLogEnabled;\n  std::optional<std::string> ConcatenatedServerRootCA;\n\n  //----------------------------------------------------------------------------\n  // Namespace variables\n  //----------------------------------------------------------------------------\n  std::unique_ptr<eos::INamespaceGroup> namespaceGroup;\n\n  eos::IContainerMDSvc* eosDirectoryService; ///< changelog for directories\n  eos::IFileMDSvc* eosFileService; ///< changelog for files\n  eos::IView* eosView; ///< hierarchical view of the namespace\n  eos::IFsView* eosFsView; ///< filesystem view of the namespace\n  eos::IFileMDChangeListener* eosContainerAccounting; ///< subtree accounting\n  //! Subtree mtime propagation\n  eos::IContainerMDChangeListener* eosSyncTimeAccounting;\n  eos::mgm::NamespaceStats mNamespaceStats; //namespace-related stats operation\n  eos::common::RWMutex eosViewRWMutex; ///< RW namespace mutex\n  XrdOucString\n  MgmMetaLogDir; ///<  Directory containing the meta data (change) log files\n  eos::common::MutexLatencyWatcher mViewMutexWatcher;\n\n  // ---------------------------------------------------------------------------\n  // Thread variables\n  // ---------------------------------------------------------------------------\n  AssistedThread mStatsTid; ///< Stats thread\n  AssistedThread mFsConfigTid; ///< Fs listener/config change thread\n  AssistedThread mAuthMasterTid; ///< Thread Id of the authentication thread\n  //! Thread to listen for significant filesystem changes\n  AssistedThread mFsMonitorTid;\n\n  std::vector<pthread_t> mVectTid; ///< vector of auth worker threads ids\n\n  eos::mgm::traffic_shaping::TrafficShapingEngine mTrafficShapingEngine;\n\n  //----------------------------------------------------------------------------\n  // Authentication plugin variables like the ZMQ front end port number and the\n  // number of worker threads available at the MGM\n  //----------------------------------------------------------------------------\n  unsigned int mFrontendPort; ///< frontend port number for incoming requests\n  unsigned int mNumAuthThreads; ///< max number of auth worker threads\n  bool\n  mFrontendLocalhost; ///< true if this server binds only on localhost:mFrontendPort not *:mFrontendPort\n  zmq::context_t* mZmqContext; ///< ZMQ context for all the sockets\n  ZMQ* zMQ; ///< ZMQ processor\n\n  //! Authentication response time statistics\n  struct AuthStats {\n    std::int64_t mNumSamples;\n    std::int64_t mMax; ///< Max milliseconds\n    std::int64_t mMin; ///< Min milliseconds\n    double mVariance;\n    double mMean;\n  };\n\n  std::mutex mAuthStatsMutex; ///< Mutex protecting authentication stats\n  //! Map of operation types to duration\n  std::map<eos::auth::RequestProto_OperationType,\n      std::list<std::int64_t> > mAuthSamples;\n  //! Map of operation types to aggregate info response times\n  std::chrono::steady_clock::time_point mLastTimestamp;\n  std::map<eos::auth::RequestProto_OperationType, AuthStats>\n  mAuthAggregate;\n\n  //----------------------------------------------------------------------------\n  //! Collect statistics for authentication response times\n  //!\n  //! @param op operation type\n  //! @param duration request duration\n  //----------------------------------------------------------------------------\n  void AuthCollectInfo(eos::auth::RequestProto_OperationType op,\n                       std::int64_t ms_duration);\n\n  //----------------------------------------------------------------------------\n  //! Compute stats for the provided samples\n  //!\n  //! @param lst_samples list of samples\n  //!\n  //! @return statistics structure\n  //----------------------------------------------------------------------------\n  AuthStats AuthComputeStats(const std::list<std::int64_t>&\n                             lst_samples) const;\n\n  //----------------------------------------------------------------------------\n  //! Update aggregate info with the latest samples\n  //!\n  //! @param stats statistics structure to be updated\n  //! @param lst_samples list of samples\n  //----------------------------------------------------------------------------\n  void AuthUpdateAggregate(AuthStats& stats, const\n                           std::list<std::int64_t>& lst_samples) const;\n\n  //----------------------------------------------------------------------------\n  //! Print statistics about authentication performance - needs to be called\n  //! with the mutex lock\n  //!\n  //! @return statistics data\n  //----------------------------------------------------------------------------\n  std::string AuthPrintStatistics() const;\n\n  //----------------------------------------------------------------------------\n  //! Cast a change message to all fusex clients regarding deletion of a name\n  //!\n  //! @param id container identifier\n  //! @param name directory/file name to delete\n  //----------------------------------------------------------------------------\n  void FuseXCastDeletion(eos::ContainerIdentifier id,\n                         const std::string& name);\n\n  //----------------------------------------------------------------------------\n  //! Cast a refresh message to all fusex clients regarding a meta data refresh\n  //!\n  //! @param id container identifier\n  //----------------------------------------------------------------------------\n  void FuseXCastRefresh(eos::ContainerIdentifier id,\n                        eos::ContainerIdentifier parentid);\n\n  //----------------------------------------------------------------------------\n  //! Cast a refresh message to all fusex clients regarding a meta data refresh\n  //!\n  //! @param id file identifier\n  //----------------------------------------------------------------------------\n  void FuseXCastRefresh(eos::FileIdentifier id,\n                        eos::ContainerIdentifier parentid);\n\n  //----------------------------------------------------------------------------\n  //! Cast MD to FUSE clients\n  //!\n  //! @param id container identifier\n  //! @param parentid container identifier\n  //! @param parent mtime\n  //! @param lock take the namespace lock\n  //----------------------------------------------------------------------------\n  void FuseXCastMD(eos::ContainerIdentifier id,\n                   eos::ContainerIdentifier parentid,\n                   struct timespec& p_mtime,\n                   bool lock = false);\n\n  //----------------------------------------------------------------------------\n  //! Cast MD to FUSE clients\n  //!\n  //! @param id file identifier\n  //! @param parentid container identifier\n  //! @param parent mtime\n  //! @param lock take the namespace lock\n\n  //----------------------------------------------------------------------------\n  void FuseXCastMD(eos::FileIdentifier id,\n                   eos::ContainerIdentifier parentid,\n                   struct timespec& p_mtime,\n                   bool lock = false);\n\n  //----------------------------------------------------------------------------\n  //! Setup /eos/<instance>/proc files\n  //----------------------------------------------------------------------------\n  void SetupProcFiles();\n\n  //----------------------------------------------------------------------------\n  //! Method called during shutdown to destroy the rest of the objects and\n  //! clean up the threads.\n  //----------------------------------------------------------------------------\n  void OrderlyShutdown();\n\n  //----------------------------------------------------------------------------\n  //! Populate file error object with redirection information that can be\n  //! longer than 2kb. For this we need to use the XrdOucBuffer interface.\n  //!\n  //! @param err_obj file error object to populate with redirection info\n  //! @param rdr_info string holding the redirection host and opaque data\n  //! @param rdr_port redirection port\n  //!\n  //! @return true if successful, otherwise false. If there is any error then\n  //!         the err_obj is properly populated with the relevant error msg.\n  //----------------------------------------------------------------------------\n  bool SetRedirectionInfo(XrdOucErrInfo& err_obj,\n                          const std::string& rdr_info, int rdr_port);\n\n  //----------------------------------------------------------------------------\n  //! Set token authorization handler - this is called by the HTTP external\n  //! handler which is responsible for loading the authorization plugin from\n  //! the corresponding XrdMacaroons or XrdSciTokens libraries.\n  //!\n  //! @param token_authz pointer to the token authorization plugin\n  //----------------------------------------------------------------------------\n  inline void SetTokenAuthzHandler(XrdAccAuthorize* token_authz)\n  {\n    mTokenAuthz = token_authz;\n  }\n\n  //----------------------------------------------------------------------------\n  // Class objects\n  //----------------------------------------------------------------------------\n  //! Authorization module used by external plugins to retrieve and/or check\n  //! access permissions for users and paths\n  XrdMgmAuthz* mMgmAuthz {nullptr};\n  XrdAccAuthorize* mTokenAuthz {nullptr}; ///< Token authz handler\n  XrdAccAuthorize* mExtAuthz {nullptr}; ///< Authorization service\n\n  //! Mgm Namespace Statistics\n  std::unique_ptr<Stat> MgmStatsPtr;\n  Stat& MgmStats;\n  std::unique_ptr<Iostat> mIoStats; ///<  Mgm IO Statistics\n\n  //! Mgm IO Report store path by default is /var/tmp/eos/report\n  XrdOucString IoReportStorePath;\n\n  //! Mgm tmp find output path by default is /var/tmp/eos/mgm/\n  XrdOucString TmpStorePath;\n\n  //! Class implementing comment log: mgm writes all proc commands with a\n  //! comment into /var/log/eos/comments.log\n  std::unique_ptr<eos::common::CommentLog> mCommentLog;\n  std::unique_ptr<eos::common::CommentLog> mFusexStackTraces;\n  std::unique_ptr<eos::common::CommentLog> mFusexLogTraces;\n\n  //! Audit logger writing compressed JSON lines under /var/log/eos/audit\n  std::unique_ptr<eos::common::Audit> mAudit;\n\n  //! Class tracking file creations for sanity\n  std::unique_ptr<eos::mgm::ReplicationTracker> mReplicationTracker;\n\n  //! GeoTreeEngine\n  std::unique_ptr<eos::mgm::GeoTreeEngine> mGeoTreeEngine;\n  std::unique_ptr<Fsck> mFsckEngine; ///< Fsck functionality\n  //! Master/Slave configuration/failover class\n  std::unique_ptr<IMaster> mMaster;\n\n  //! Map storing the last time of a filesystem dump, this information is used\n  //! to track filesystems which have not been checked decentral by an FST.\n  //! It is filled in the 'dumpmd' function defined in ProcInterface\n  std::map<eos::common::FileSystem::fsid_t, time_t> DumpmdTimeMap;\n  XrdSysMutex DumpmdTimeMapMutex; ///< mutex protecting the 'dumpmd' time\n\n  //! Global path remapping\n  std::map<std::string, std::string> PathMap;\n  eos::common::RWMutex PathMapMutex; ///< mutex protecting the path map\n\n  //! Global path routing\n  std::unique_ptr<PathRouting> mRouting; ///< Path routing mechanism\n\n  //! Global Attrbute Map Space=>map(key,val)\n  std::mutex mSpaceAttributesMutex; ///< Mutex protecting space attributes\n  std::map<std::string, std::map<std::string, std::string>> mSpaceAttributes;\n\n  std::unique_ptr<eos::mq::MessagingRealm> mMessagingRealm;\n  Drainer mDrainEngine; ///< Draining engine\n  std::unique_ptr<ConverterEngine> mConverterEngine; ///< Converter engine\n  std::unique_ptr<HttpServer> mHttpd; ///<  Http daemon if available\n\n  std::unique_ptr<GrpcServer> GRPCd; ///< GRPC server\n  std::unique_ptr<GrpcWncServer> WNCd; ///< GRPC server for EOS Wnc\n  //! GRPC server for REST API\n  std::unique_ptr<GrpcRestGwServer> mRestGrpcSrv;\n\n  //! LRU object running the LRU policy engine\n  std::unique_ptr<LRU> mLRUEngine;\n\n  //! WFE object running the WFE engine\n  std::unique_ptr<WFE> WFEPtr;\n  WFE& WFEd;\n\n  //!  Admin socket\n  std::unique_ptr<AdminSocket> AdminSocketServer;\n\n  //!  Egroup refresh object running asynchronous Egroup fetch thread\n  std::unique_ptr<Egroup> EgroupRefresh;\n  //!  Recycle object running the recycle bin deletion thread\n  std::unique_ptr<Recycle> mRecycler;\n  //!  Device Tracking Thread\n  std::unique_ptr<Devices> mDeviceTracker;\n\n  //!  Variable enforcing a globally applied recycle bin policy\n  std::string mArchiveEndpoint; ///< archive ZMQ connection endpoint\n  std::string mFstGwHost; ///< FST gateway redirect fqdn host\n  int mFstGwPort; ///< FST gateway redirect port, default 1094\n  std::string mQdbCluster; ///< Quarkdb cluster info host1:port1 host2:port2 ..\n  std::string mQdbPassword; ///< Quarkdb cluster password\n  eos::QdbContactDetails mQdbContactDetails; ///< QuarkDB contact details\n  std::string mQClientDir; ///<QClient metadata directory\n  std::string mQClientFlusherType; ///<QClient flusher type\n  std::string mQClientRocksDBOptions; ///<QClient specific rocksdb options\n  int mHttpdPort; ///< port of the http server, default 8000\n  int mFusexPort; ///< port of the FUSEX broadcast MQZ, default 1100\n  int mGRPCPort; ///< port of the GRPC server, default 50051\n  int mWncPort; ///< port of the GRPC server for EOS Wnc, default 50052\n  int mRestGrpcPort; ///< port of the REST GRPC server, default 50054\n  eos::common::XrdConnPool mXrdConnPool; ///< XRD connection pool\n  //! Tracker for requests which are currently executing MGM code\n  eos::mgm::InFlightTracker mTracker;\n  //! The tape-aware garbage collector's interface to the EOS MGM\n  std::unique_ptr<tgc::RealTapeGcMgm> mTapeGcMgm;\n  //! Multi-space tape-aware garbage collector\n  std::unique_ptr<tgc::MultiSpaceTapeGc> mTapeGc;\n  //! EOS spaces for which tape-aware garbage collection should be enabled\n  std::set<std::string> mTapeGcSpaces;\n  //! Tracker for drain, balance and convert fids\n  eos::mgm::IdTrackerWithValidity<eos::IFileMD::id_t> mFidTracker;\n  //! The class holding the paths where the bulk-requets will be persisted\n  std::unique_ptr<eos::mgm::bulk::ProcDirectoryBulkRequestLocations>\n  mProcDirectoryBulkRequestLocations;\n  //! The class holding the paths where the bulk-requests coming from the HTTP tape REST API will be persisted\n  std::unique_ptr<eos::mgm::bulk::ProcDirectoryBulkRequestLocations>\n  mProcDirectoryBulkRequestTapeRestApiLocations;\n\n  //! BulkRequestProcCleaner\n  std::unique_ptr<bulk::BulkRequestProcCleaner> mBulkReqProcCleaner;\n\n  //! REST API manager\n  std::unique_ptr<rest::RestApiManager> mRestApiManager;\n  //! HTTP TAPE REST API BulkRequestProcCleaner\n  std::unique_ptr<bulk::BulkRequestProcCleaner>\n  mHttpTapeRestApiBulkReqProcCleaner;\n\n  std::unique_ptr<eos::mgm::placement::FSScheduler> mFsScheduler;\n\n  //! Non-persistent behaviour configuration changes\n  std::unique_ptr<eos::common::BehaviourConfig> mBehaviourCfg;\n\n  //----------------------------------------------------------------------------\n  //! Return string representation of prepare options\n  //----------------------------------------------------------------------------\n  static std::string prepareOptsToString(const int opts);\n\n  //----------------------------------------------------------------------------\n  //! Get fuse booking size\n  //----------------------------------------------------------------------------\n  inline uint64_t getFuseBookingSize() const\n  {\n    return mFusePlacementBooking;\n  }\n\n  //----------------------------------------------------------------------------\n  //! List Attributes high-level function merging space and namespace attributes\n  //----------------------------------------------------------------------------\n\n  void mergeSpaceAttributes(eos::IContainerMD::XAttrMap& out, bool prefix = false,\n                            bool existing = false);\n\n  void\n  listAttributes(eos::IView* view, eos::IContainerMD* target,\n                 eos::IContainerMD::XAttrMap& out, bool prefixLinks = false);\n  void\n  listAttributes(eos::IView* view, eos::IFileMD* target,\n                 eos::IContainerMD::XAttrMap& out, bool prefixLinks = false);\n  void\n  listAttributes(eos::IView* view, eos::FileOrContainerMD target,\n                 eos::IContainerMD::XAttrMap& out, bool prefixLinks = false);\n\n  template<typename T>\n  bool getAttribute(eos::IView* view, T& md, std::string key,\n                    std::string& rvalue);\n\n  // Centralized audit allow checks\n  bool AllowAuditModification(const std::string& path);\n  bool AllowAuditList(const std::string& dirPath);\n  bool AllowAuditRead(const std::string& path);\n\n  // Fast-path overloads: evaluate auditing given a pre-fetched sys.audit value.\n  // The provided auditMode string should be the raw attribute value (any case).\n  // For read evaluation, path is used when auditMode is \"default\" to apply the suffix filter.\n  bool AllowAuditModificationAttr(const std::string& auditMode);\n  bool AllowAuditListAttr(const std::string& auditMode);\n  bool AllowAuditReadAttr(const std::string& auditMode, const std::string& path);\n\nprotected:\n  std::atomic<bool> mDoneOrderlyShutdown; ///< Mark for orderly shutdown\n\nprivate:\n  //! XrdOucBuffPool object for managing buffers >= 2kb\n  XrdOucBuffPool mXrdBuffPool;\n  ///< uuid to directory obj. mapping\n  std::map<std::string, XrdMgmOfsDirectory*> mMapDirs;\n  std::map<std::string, XrdMgmOfsFile*> mMapFiles; ///< uuid to file obj. mapping\n  XrdSysMutex mMutexDirs; ///< mutex for protecting the access at the dirs map\n  XrdSysMutex mMutexFiles; ///< mutex for protecting the access at the files map\n  //! Thread listening for error messages and logging them in error.log\n  AssistedThread mErrLoggerTid;\n  AssistedThread mSubmitterTid; ///< Archive submitter thread\n  XrdSysMutex mJobsQMutex; ///< Mutex for archive/backup job queue\n  std::list<std::string> mPendingBkps; ///< Backup jobs queueRequest\n  //! Manage heap profiling\n  std::unique_ptr<eos::common::JeMallocHandler> mJeMallocHandler;\n  bool mTpcRedirect {false}; ///< Mark if tpc rdr enabled\n  //! Map for delegated/undelegated TPC redirection info\n  std::map<bool, std::pair<std::string, int>> mTpcRdrInfo;\n  static thread_local eos::common::LogId tlLogId;\n  //! Space/quota which is requested when placing a file via FUSE(x)\n  uint64_t mFusePlacementBooking;\n  //! NoStall FUSE applicationg name\n  std::string mFuseNoStallApp;\n\n  //----------------------------------------------------------------------------\n  //! Convert error code to string representation\n  //----------------------------------------------------------------------------\n  static std::string MacroStringError(int errcode);\n\n  //----------------------------------------------------------------------------\n  //! Check that the auth ProtocolBuffer request has not been tampered with\n  //!\n  //! @param reqProto request ProtocolBuffer from an authentication plugin\n  //!\n  //! @return true if request is valid, otherwise false\n  //----------------------------------------------------------------------------\n  bool ValidAuthRequest(eos::auth::RequestProto* reqProto);\n\n  //----------------------------------------------------------------------------\n  //! Initialize MGM statistics to 0\n  //----------------------------------------------------------------------------\n  void InitStats();\n\n  //----------------------------------------------------------------------------\n  //! Start thread that will queue, build and submit backup operations to\n  //! the archiver daemon.\n  //----------------------------------------------------------------------------\n  void ArchiveSubmitterThread(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Start thread listening for error messages and logging them\n  //----------------------------------------------------------------------------\n  void ErrorLogListenerThread(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  // FS control functions\n  // These functions are used in EOS to implement many stateless operations\n  // such as commit/drop a replica, stat a file/directory,\n  // create a directory listing for FUSE, chmod, chown, access, utimes\n  // get checksum, schedule drain/balance/delete, etc.\n  //\n  // All of these functions will receive the following parameters:\n  //\n  // @param path   - path associated with this request\n  // @param ininfo - opaque info associated with this request\n  // @param env    - environment constructed from opaque info\n  // @param error  - error reporting object\n  // @param LogId  - thread logging object\n  // @param vid    - mapped virtual identity\n  // @param client - client security entity object\n  //----------------------------------------------------------------------------\n\n  //----------------------------------------------------------------------------\n  //! Check access rights\n  //----------------------------------------------------------------------------\n  int Access(const char* path,\n             const char* ininfo,\n             XrdOucEnv& env,\n             XrdOucErrInfo& error,\n             eos::common::VirtualIdentity& vid,\n             const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Adjust replica (repairOnClose from FST)\n  //----------------------------------------------------------------------------\n  int AdjustReplica(const char* path,\n                    const char* ininfo,\n                    XrdOucEnv& env,\n                    XrdOucErrInfo& error,\n                    eos::common::VirtualIdentity& vid,\n                    const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Get checksum of file\n  //----------------------------------------------------------------------------\n  int Checksum(const char* path,\n               const char* ininfo,\n               XrdOucEnv& env,\n               XrdOucErrInfo& error,\n               eos::common::VirtualIdentity& vid,\n               const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Chmod of a directory\n  //----------------------------------------------------------------------------\n  int Chmod(const char* path,\n            const char* ininfo,\n            XrdOucEnv& env,\n            XrdOucErrInfo& error,\n            eos::common::VirtualIdentity& vid,\n            const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Chown of a file or directory\n  //----------------------------------------------------------------------------\n  int Chown(const char* path,\n            const char* ininfo,\n            XrdOucEnv& env,\n            XrdOucErrInfo& error,\n            eos::common::VirtualIdentity& vid,\n            const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Commit a replica\n  //----------------------------------------------------------------------------\n  int Commit(const char* path,\n             const char* ininfo,\n             XrdOucEnv& env,\n             XrdOucErrInfo& error,\n             eos::common::VirtualIdentity& vid,\n             const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Drop a replica\n  //----------------------------------------------------------------------------\n  int Drop(const char* path,\n           const char* ininfo,\n           XrdOucEnv& env,\n           XrdOucErrInfo& error,\n           eos::common::VirtualIdentity& vid,\n           const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Trigger an event\n  //----------------------------------------------------------------------------\n  int Event(const char* path,\n            const char* ininfo,\n            XrdOucEnv& env,\n            XrdOucErrInfo& error,\n            eos::common::VirtualIdentity& vid,\n            const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Stat a file or directory.\n  //! Will redirect to the RW master.\n  //----------------------------------------------------------------------------\n  int FuseStat(const char* path,\n               const char* ininfo,\n               XrdOucEnv& env,\n               XrdOucErrInfo& error,\n               eos::common::VirtualIdentity& vid,\n               const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Fuse extension.\n  //! Will redirect to the RW master.\n  //----------------------------------------------------------------------------\n  int Fusex(const char* path,\n            const char* ininfo,\n            std::string protobuf,\n            XrdOucEnv& env,\n            XrdOucErrInfo& error,\n            eos::common::VirtualIdentity& vid,\n            const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Return metadata in env representation\n  //----------------------------------------------------------------------------\n  int Getfmd(const char* path,\n             const char* ininfo,\n             XrdOucEnv& env,\n             XrdOucErrInfo& error,\n             eos::common::VirtualIdentity& vid,\n             const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Return metadata for a fusex client\n  //----------------------------------------------------------------------------\n  int GetFusex(const char* path,\n               const char* ininfo,\n               XrdOucEnv& env,\n               XrdOucErrInfo& error,\n               eos::common::VirtualIdentity& vid,\n               const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Query to determine if current node is acting as master\n  //----------------------------------------------------------------------------\n  int IsMaster(const char* path,\n               const char* ininfo,\n               XrdOucEnv& env,\n               XrdOucErrInfo& error,\n               eos::common::VirtualIdentity& vid,\n               const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Make a directory and return its inode\n  //----------------------------------------------------------------------------\n  int Mkdir(const char* path,\n            const char* ininfo,\n            XrdOucEnv& env,\n            XrdOucErrInfo& error,\n            eos::common::VirtualIdentity& vid,\n            const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Parallel IO mode open\n  //----------------------------------------------------------------------------\n  int Open(const char* path,\n           const char* ininfo,\n           XrdOucEnv& env,\n           XrdOucErrInfo& error,\n           eos::common::VirtualIdentity& vid,\n           const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Resolve symbolic link\n  //----------------------------------------------------------------------------\n  int Readlink(const char* path,\n               const char* ininfo,\n               XrdOucEnv& env,\n               XrdOucErrInfo& error,\n               eos::common::VirtualIdentity& vid,\n               const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Get open redirect\n  //----------------------------------------------------------------------------\n  int Redirect(const char* path,\n               const char* ininfo,\n               XrdOucEnv& env,\n               XrdOucErrInfo& error,\n               eos::common::VirtualIdentity& vid,\n               const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Repair file.\n  //! Used to repair after scan error (E.g.: use the converter to rewrite)\n  //----------------------------------------------------------------------------\n  int Rewrite(const char* path,\n              const char* ininfo,\n              XrdOucEnv& env,\n              XrdOucErrInfo& error,\n              eos::common::VirtualIdentity& vid,\n              const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Virtual filesystem stat\n  //----------------------------------------------------------------------------\n  int Statvfs(const char* path,\n              const char* ininfo,\n              XrdOucEnv& env,\n              XrdOucErrInfo& error,\n              eos::common::VirtualIdentity& vid,\n              const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Create symbolic link\n  //----------------------------------------------------------------------------\n  int Symlink(const char* path,\n              const char* ininfo,\n              XrdOucEnv& env,\n              XrdOucErrInfo& error,\n              eos::common::VirtualIdentity& vid,\n              const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Set utimes\n  //----------------------------------------------------------------------------\n  int Utimes(const char* path,\n             const char* ininfo,\n             XrdOucEnv& env,\n             XrdOucErrInfo& error,\n             eos::common::VirtualIdentity& vid,\n             const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Get EOS version and features\n  //----------------------------------------------------------------------------\n  int Version(const char* path,\n              const char* ininfo,\n              XrdOucEnv& env,\n              XrdOucErrInfo& error,\n              eos::common::VirtualIdentity& vid,\n              const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Extended attribute operations\n  //----------------------------------------------------------------------------\n  int Xattr(const char* path,\n            const char* ininfo,\n            XrdOucEnv& env,\n            XrdOucErrInfo& error,\n            eos::common::VirtualIdentity& vid,\n            const XrdSecEntity* client);\n\n  // ---------------------------------------------------------------------------\n  //! Handle an SFS_FSCTL_PLUGIO command\n  // ---------------------------------------------------------------------------\n  int dispatchSFS_FSCTL_PLUGIO(XrdSfsFSctl& args,\n                               XrdOucErrInfo& error,\n                               eos::common::VirtualIdentity& vid,\n                               const XrdSecEntity* client);\n\n  // Audit environment configuration (parsed in constructor, applied after mAudit init)\n  bool mEnvAuditDisableAll {false};\n  bool mEnvAuditRead {false};\n  bool mEnvAuditList {false};\n  bool mEnvAuditReadAll {false};\n  bool mEnvAuditAttributeMode {false};\n  // Attribute evaluation is enabled for all non-off modes\n  bool mEnvAuditAttributeOnly {false};\n  // Attribute-only mode (EOS_MGM_AUDIT=attribute) disables global auditing toggles\n  std::vector<std::string> mEnvAuditReadSuffixes;\n  bool mEnvAuditReadSuffixesSet {false};\n};\n\nextern XrdMgmOfs* gOFS; //< global handle to XrdMgmOfs object\n\n#endif\n"
  },
  {
    "path": "mgm/ofs/XrdMgmOfsConfigure.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdMgmOfsConfigure.cc\n// Authors: Andreas-Joachim Peters - CERN\n//          Jaroslav Guenther      - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Audit.hh\"\n#include \"common/CommentLog.hh\"\n#include \"common/InstanceName.hh\"\n#include \"common/JeMallocHandler.hh\"\n#include \"common/Logging.hh\"\n#include \"common/PasswordHandler.hh\"\n#include \"common/Path.hh\"\n#include \"common/RWMutex.hh\"\n#include \"common/RegexWrapper.hh\"\n#include \"common/StacktraceHere.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/Utils.hh\"\n#include \"common/plugin_manager/PluginManager.hh\"\n#include \"common/thread_id.hh\"\n#include \"grpc/GrpcRestGwServer.hh\"\n#include \"grpc/GrpcServer.hh\"\n#include \"grpc/GrpcWncServer.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/adminsocket/AdminSocket.hh\"\n#include \"mgm/bulk-request/dao/proc/ProcDirectoryBulkRequestLocations.hh\"\n#include \"mgm/bulk-request/dao/proc/cleaner/BulkRequestProcCleaner.hh\"\n#include \"mgm/bulk-request/dao/proc/cleaner/BulkRequestProcCleanerConfig.hh\"\n#include \"mgm/config/QuarkDBConfigEngine.hh\"\n#include \"mgm/convert/ConverterEngine.hh\"\n#include \"mgm/devices/Devices.hh\"\n#include \"mgm/drain/Drainer.hh\"\n#include \"mgm/egroup/Egroup.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/geotreeengine/GeoTreeEngine.hh\"\n#include \"mgm/http/HttpServer.hh\"\n#include \"mgm/http/rest-api/Constants.hh\"\n#include \"mgm/http/rest-api/manager/RestApiManager.hh\"\n#include \"mgm/inspector/FileInspector.hh\"\n#include \"mgm/iostat/Iostat.hh\"\n#include \"mgm/lru/LRU.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/ofs/XrdMgmOfsTrace.hh\"\n#include \"mgm/placement/FsScheduler.hh\"\n#include \"mgm/qdbmaster/QdbMaster.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/recycle/Recycle.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/tgc/MultiSpaceTapeGc.hh\"\n#include \"mgm/tracker/ReplicationTracker.hh\"\n#include \"mgm/wfe/WFE.hh\"\n#include \"mgm/zmq/ZMQ.hh\"\n#include \"mq/MessagingRealm.hh\"\n#include \"mq/SharedHashWrapper.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"qclient/shared/SharedManager.hh\"\n#include <XrdAcc/XrdAccAuthorize.hh>\n#include <XrdCl/XrdClDefaultEnv.hh>\n#include <XrdNet/XrdNetAddr.hh>\n#include <XrdNet/XrdNetUtils.hh>\n#include <XrdOuc/XrdOucStream.hh>\n#include <XrdOuc/XrdOucTrace.hh>\n#include <XrdSys/XrdSysPlugin.hh>\n#include <cstring>\n#include <fcntl.h>\n#include <filesystem>\n#include <sstream>\n#include <sys/stat.h>\n#include <sys/syscall.h>\n#include <sys/types.h>\n\nextern XrdOucTrace gMgmOfsTrace;\nextern void xrdmgmofs_shutdown(int sig);\nextern void xrdmgmofs_stacktrace(int sig);\nextern void xrdmgmofs_coverage(int sig);\n\nnamespace\n{\nstd::string sVersionRegex {\"v[0-9]+(\\\\.[0-9]+)+\"};\n}\n\nUSE_EOSMGMNAMESPACE\n\n\nvoid xrdmgmofs_stack(int sig)\n{\n  using std::filesystem::perms;\n  static time_t stacktime = 0;\n  std::ostringstream out;\n\n  if (sig == SIGUSR2) {\n    stacktime = time(NULL);\n    out << \"# ___ thread:\" << eos::common::thread_id() << \" \";\n    eos::common::RWMutex::PrintMutexOps(out);\n    out << std::endl;\n    out << \"# ................ \" << eos::common::getStacktrace();\n    std::string stackdump = \"/var/eos/md/stacktrace.\";\n    stackdump += std::to_string((unsigned long long)stacktime);\n    std::ofstream outf(stackdump, std::ofstream::app);\n    outf << out.str() << std::endl;\n    std::cerr << out.str() << std::endl;\n    outf.close();\n    std::filesystem::permissions(stackdump, perms::owner_read |\n                                 perms::owner_write | perms::group_read);\n  }\n\n  if (sig == SIGUSR1) {\n    stacktime = time(NULL);\n    std::string stackdump = \"/var/eos/md/stacktrace.\";\n    stackdump += std::to_string((unsigned long long)stacktime);\n    std::ofstream outf(stackdump);\n    outf << \"# eos mgm stack mutex states\" << std::endl;\n    std::set<pthread_t> tosignal = gOFS->mTracker.getInFlightThreads();\n    outf << \"# \" << tosignal.size() << \" threads in tracking\" << std::endl;\n    outf.close();\n    std::filesystem::permissions(stackdump, perms::owner_read |\n                                 perms::owner_write | perms::group_read);\n\n    for (auto it = tosignal.begin(); it != tosignal.end(); ++it) {\n      pthread_kill(*it, SIGUSR2);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Start jemalloc heap profiling\n//------------------------------------------------------------------------------\nvoid XrdMgmOfs::StartHeapProfiling(int sig)\n{\n  if (!gOFS->mJeMallocHandler->CanProfile()) {\n    eos_static_crit(\"cannot run heap profiling\");\n    return;\n  }\n\n  if (gOFS->mJeMallocHandler->StartProfiling()) {\n    eos_static_warning(\"started jemalloc heap profiling\");\n  } else {\n    eos_static_warning(\"failed to start jemalloc heap profiling\");\n  }\n}\n\n//------------------------------------------------------------------------------\n// Stop jemalloc heap profiling\n//------------------------------------------------------------------------------\nvoid XrdMgmOfs::StopHeapProfiling(int sig)\n{\n  if (!gOFS->mJeMallocHandler->CanProfile()) {\n    eos_static_crit(\"cannot run heap profiling\");\n    return;\n  }\n\n  if (gOFS->mJeMallocHandler->StopProfiling()) {\n    eos_static_warning(\"stopped jemalloc heap profiling\");\n  } else {\n    eos_static_warning(\"failed to stop jemalloc heap profiling\");\n  }\n}\n\n//------------------------------------------------------------------------------\n// Dump jemalloc heap profiling info\n//------------------------------------------------------------------------------\nvoid XrdMgmOfs::DumpHeapProfile(int sig)\n{\n  if (!gOFS->mJeMallocHandler->ProfRunning()) {\n    eos_static_crit(\"profiling is not running\");\n    return;\n  }\n\n  if (gOFS->mJeMallocHandler->DumpProfile()) {\n    eos_static_warning(\"dumped heap profile\");\n  } else {\n    eos_static_warning(\"failed to sum heap profile\");\n  }\n}\n\n//------------------------------------------------------------------------------\n// Configure the MGM daemon by parsing info in xrd.cf.mgm\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::Configure(XrdSysError& Eroute)\n{\n  // the process run's as root, but acts on the filesystem as daemon\n  char* var;\n  const char* val;\n  int cfgFD, retc, NoGo = 0;\n  XrdOucStream Config(&Eroute, getenv(\"XRDINSTANCE\"));\n  XrdOucString role = \"server\";\n  // set short timeouts in the new XrdCl class\n  XrdCl::DefaultEnv::GetEnv()->PutInt(\"TimeoutResolution\", 1);\n  // set connection window short\n  XrdCl::DefaultEnv::GetEnv()->PutInt(\"ConnectionWindow\", 5);\n  // set connection retry to one\n  XrdCl::DefaultEnv::GetEnv()->PutInt(\"ConnectionRetry\", 1);\n  // set stream error window\n  XrdCl::DefaultEnv::GetEnv()->PutInt(\"StreamErrorWindow\", 0);\n\n  // Setup the concatenated CA file (done by the XRootD server)\n  if (getenv(\"XRDADMINPATH\")) {\n    std::string adminPath = getenv(\"XRDADMINPATH\");\n    ConcatenatedServerRootCA = adminPath + \".xrdtls/ca_file.pem\";\n    Eroute.Say(\"Concatenated CA file location: \",\n               ConcatenatedServerRootCA->c_str());\n  }\n\n  Shutdown = false;\n  setenv(\"XrdSecPROTOCOL\", \"sss\", 1);\n  Eroute.Say(\"=====> mgmofs enforces SSS authentication for XROOT clients\");\n  MgmOfsTargetPort = \"1094\";\n  MgmOfsName = \"\";\n  MgmOfsAlias = \"\";\n  MgmOfsBrokerUrl = \"root://localhost:1097//eos/\";\n  MgmOfsInstanceName = \"testinstance\";\n  MgmMetaLogDir = \"\";\n  MgmAuthDir = \"\";\n  MgmArchiveDir = \"\";\n  IoReportStorePath = \"/var/tmp/eos/report\";\n  TmpStorePath = \"/var/tmp/eos/mgm\";\n  mQClientDir = \"/var/eos/ns-queue/\";\n  MgmArchiveDstUrl = \"\";\n  MgmArchiveSvcClass = \"default\";\n  mPrepareDestSpace = \"default\";\n  eos::common::StringConversion::InitLookupTables();\n  // Enable TPC on the MGM for 0-size files\n  XrdOucEnv::Export(\"XRDTPC\", \"1\");\n\n  if (getenv(\"EOS_ARCHIVE_URL\")) {\n    MgmArchiveDstUrl = getenv(\"EOS_ARCHIVE_URL\");\n\n    // Make sure it ends with a '/'\n    if (MgmArchiveDstUrl[MgmArchiveDstUrl.length() - 1] != '/') {\n      MgmArchiveDstUrl += '/';\n    }\n  }\n\n  if (getenv(\"EOS_ARCHIVE_SVCCLASS\")) {\n    MgmArchiveSvcClass = getenv(\"EOS_ARCHIVE_SVCCLASS\");\n  }\n\n  // Configure heap profiling if any\n  if (mJeMallocHandler->JeMallocLoaded()) {\n    eos_warning(\"jemalloc is loaded!\");\n    Eroute.Say(\"jemalloc is loaded!\");\n\n    if (mJeMallocHandler->CanProfile()) {\n      Eroute.Say(mJeMallocHandler->ProfRunning() ?\n                 \"jemalloc heap profiling enabled and running\" :\n                 \"jemalloc heap profiling enabled and NOT running\");\n      eos_warning(\"jemalloc heap profiling enabled and %srunning. will start \"\n                  \"running on signal 40 and will stop running on signal 41\",\n                  mJeMallocHandler->ProfRunning() ? \"\" : \"NOT \");\n    } else {\n      eos_warning(\"jemalloc heap profiling is disabled\");\n      Eroute.Say(\"jemalloc heap profiling is disabled\");\n    }\n  } else {\n    eos_warning(\"jemalloc is NOT loaded!\");\n    Eroute.Say(\"jemalloc is NOT loaded!\");\n  }\n\n  if (SIGRTMIN <= 40 && 42 <= SIGRTMAX) {\n    signal(40, StartHeapProfiling);\n    signal(41, StopHeapProfiling);\n    signal(42, DumpHeapProfile);\n  } else {\n    eos_static_warning(\"cannot install signal handlers for heap profiling as \"\n                       \"ports 40, 41, 42 don't fit in the allowed realtime \"\n                       \"dignal range. SIGRTMIN=%d  SIGRTMAX=%d\",\n                       (int)SIGRTMIN, (int)SIGRTMAX);\n  }\n\n  // Create and own the output cache directory or clean it up if it exists -\n  // this is used to store temporary results for commands like find, backup\n  // or archive\n  struct stat dir_stat;\n\n  if (!::stat(TmpStorePath.c_str(), &dir_stat) && S_ISDIR(dir_stat.st_mode)) {\n    XrdOucString systemline = \"rm -rf \";\n    systemline += TmpStorePath.c_str();\n    systemline += \"/* >& /dev/null &\";\n    int rrc = system(systemline.c_str());\n\n    if (WEXITSTATUS(rrc)) {\n      eos_err(\"%s returned %d\", systemline.c_str(), rrc);\n    }\n  } else {\n    eos::common::Path out_dir(std::string(std::string(TmpStorePath.c_str()) +\n                                          \"/dummy\").c_str());\n\n    if (!out_dir.MakeParentPath(S_IRWXU)) {\n      eos_err(\"Unable to create temporary output file directory %s\",\n              TmpStorePath.c_str());\n      Eroute.Emsg(\"Config\", errno, \"create temporary output file\"\n                  \" directory\", TmpStorePath.c_str());\n      NoGo = 1;\n      return NoGo;\n    }\n\n    // Own the directory by daemon\n    if (::chown(out_dir.GetParentPath(), 2, 2)) {\n      eos_err(\"Unable to own temporary output file directory %s\",\n              out_dir.GetParentPath());\n      Eroute.Emsg(\"Config\", errno, \"own output file directory\",\n                  out_dir.GetParentPath());\n      NoGo = 1;\n      return NoGo;\n    }\n  }\n\n  MgmConfigAutoLoad = \"\";\n  long myPort = 0;\n  std::string ns_lib_path;\n\n  if (getenv(\"XRDDEBUG\")) {\n    gMgmOfsTrace.What = TRACE_MOST | TRACE_debug;\n  }\n\n  {\n    // borrowed from XrdOfs\n    char buff[256], *bp;\n    int i;\n    // Obtain port number we will be using\n    //\n    myPort = (bp = getenv(\"XRDPORT\")) ? strtol(bp, (char**) 0, 10) : 0;\n    // Establish our hostname and IPV4 address\n    //\n    const char* errtext = 0;\n    HostName = XrdNetUtils::MyHostName(0, &errtext);\n\n    if (!HostName) {\n      return Eroute.Emsg(\"Config\", errno, \"cannot get hostname : %s\", errtext);\n    }\n\n    XrdNetAddr* addrs  = 0;\n    int         nAddrs = 0;\n    const char* err    = XrdNetUtils::GetAddrs(HostName, &addrs, nAddrs,\n                         XrdNetUtils::allIPv64,\n                         XrdNetUtils::NoPortRaw);\n\n    if (err || nAddrs == 0) {\n      sprintf(buff, \"[::127.0.0.1]:%ld\", myPort);\n    } else {\n      int len = XrdNetUtils::IPFormat(addrs[0].SockAddr(), buff, sizeof(buff),\n                                      XrdNetUtils::noPort | XrdNetUtils::oldFmt);\n      delete [] addrs;\n\n      if (len == 0) {\n        sprintf(buff, \"[::127.0.0.1]:%ld\", myPort);\n      } else {\n        sprintf(buff + len, \":%ld\", myPort);\n      }\n    }\n\n    for (i = 0; HostName[i] && HostName[i] != '.'; i++);\n\n    HostName[i] = '\\0';\n    HostPref = strdup(HostName);\n    HostName[i] = '.';\n    Eroute.Say(\"=====> mgmofs.hostname: \", HostName, \"\");\n    Eroute.Say(\"=====> mgmofs.hostpref: \", HostPref, \"\");\n    ManagerId = HostName;\n    ManagerId += \":\";\n    ManagerId += (int) myPort;\n    addrs  = 0;\n    nAddrs = 0;\n    err    = XrdNetUtils::GetAddrs(HostName, &addrs, nAddrs, XrdNetUtils::allIPv64,\n                                   XrdNetUtils::NoPortRaw);\n\n    if (err) {\n      return Eroute.Emsg(\"Config\", errno, \"convert hostname to IP address: \", err);\n    }\n\n    if (nAddrs == 0) {\n      return Eroute.Emsg(\"Config\", errno, \"convert hostname to IP address\",\n                         HostName);\n    }\n\n    int len = addrs[0].Format(buff, sizeof(buff), XrdNetAddrInfo::fmtAddr,\n                              XrdNetAddrInfo::noPortRaw);\n    delete [] addrs;\n\n    if (len == 0) {\n      return Eroute.Emsg(\"Config\", errno, \"convert hostname to IP address\",\n                         HostName);\n    }\n\n    ManagerIp = XrdOucString(buff, len);\n    ManagerPort = myPort;\n    Eroute.Say(\"=====> mgmofs.managerid: \", ManagerId.c_str(), \"\");\n  }\n\n  std::string tapeRestApiSitename;\n  std::map<std::string, std::string> tapeRestApiEndpointUrlMap;\n  XrdHttpPort = ManagerPort;\n\n  if (!ConfigFN || !*ConfigFN) {\n    Eroute.Emsg(\"Config\", \"Configuration file not specified.\");\n  } else {\n    // Try to open the configuration file.\n    //\n    if ((cfgFD = open(ConfigFN, O_RDONLY, 0)) < 0) {\n      return Eroute.Emsg(\"Config\", errno, \"open config file\", ConfigFN);\n    }\n\n    Config.Attach(cfgFD);\n    // Now start reading records until eof.\n    //\n    XrdOucString nsin;\n    XrdOucString nsout;\n\n    while ((var = Config.GetMyFirstWord())) {\n      if (!strncmp(var, \"all.\", 4)) {\n        var += 4;\n\n        if (!strcmp(\"role\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument for all.role missing.\");\n            NoGo = 1;\n          } else {\n            XrdOucString lrole = val;\n\n            if ((val = Config.GetWord())) {\n              if (!strcmp(val, \"if\")) {\n                if ((val = Config.GetWord())) {\n                  if (!strcmp(val, HostName)) {\n                    role = lrole;\n                  }\n\n                  if (!strcmp(val, HostPref)) {\n                    role = lrole;\n                  }\n                }\n              } else {\n                role = lrole;\n              }\n            } else {\n              role = lrole;\n            }\n          }\n        }\n      }\n\n      // Handle TPC redirection for delegation\n      // ofs.tpc redirect [delegated|undelegated] <host>[:<port>]\n      if (strncmp(var, \"ofs.tpc\", 7) == 0) {\n        var += 7;\n        char c_line[4096];\n\n        if (Config.GetRest(c_line, 4096) == 0) {\n          Eroute.Emsg(\"Config\", \"argument for ofs.tpc is missing\");\n          NoGo = 1;\n        } else {\n          std::string line {c_line};\n          eos::common::trim(line);\n          auto tokens = eos::common::StringTokenizer::split<std::list<std::string>>(line,\n                        ' ');\n\n          // We're only interested in the redirect directive since we anyway\n          // enable TPC support by default by setting the XRDTPC env variable\n          if (tokens.front() == \"redirect\") {\n            mTpcRedirect = true;\n            tokens.pop_front();\n\n            if (tokens.empty()) {\n              Eroute.Emsg(\"Config\", \"argument for ofs.tpc is missing\");\n              NoGo = 1;\n            } else {\n              bool rdr_delegated = (tokens.front() == \"delegated\");\n              tokens.pop_front();\n\n              if (tokens.empty()) {\n                Eroute.Emsg(\"Config\", \"argument for ofs.tpc redirect is missing\");\n                NoGo = 1;\n              } else {\n                std::string rdr_info = tokens.front();\n                size_t pos = rdr_info.find(':');\n                int rdr_port {1094};\n                std::string rdr_host {rdr_info.substr(0, pos)};\n\n                if (pos != std::string::npos) {\n                  try {\n                    rdr_port = std::stoul(rdr_info.substr(pos + 1));\n                  } catch (...) {\n                    Eroute.Emsg(\"Config\", \"ofs.tpc redirect failed to convert port,\"\n                                \"use default 1094\");\n                    NoGo = 1;\n                  }\n                }\n\n                Eroute.Say(\"=====> ofs.tpc redirect to: \", rdr_host.c_str(),\n                           std::to_string(rdr_port).c_str());\n                mTpcRdrInfo.emplace(rdr_delegated, std::make_pair(rdr_host, rdr_port));\n\n                // We only accept forwarding credentials for gsi\n                if (rdr_delegated) {\n                  XrdOucEnv::Export(\"XRDTPCDLG\", \"gsi\");\n                }\n              }\n            }\n          }\n        }\n      }\n\n      if (!strncmp(var, \"mgmofs.\", 7)) {\n        var += 7;\n\n        if (!strcmp(\"fs\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument for fs invalid.\");\n            NoGo = 1;\n          } else {\n            Eroute.Say(\"=====> mgmofs.fs: \", val, \"\");\n            MgmOfsName = val;\n          }\n        }\n\n        if (!strcmp(\"targetport\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument for fs invalid.\");\n            NoGo = 1;\n          } else {\n            Eroute.Say(\"=====> mgmofs.targetport: \", val, \"\");\n            MgmOfsTargetPort = val;\n          }\n        }\n\n        if (!strcmp(\"broker\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\",\n                        \"argument 2 for broker missing. Should be URL like root://<host>/<queue>/\");\n            NoGo = 1;\n          } else {\n            if (getenv(\"EOS_BROKER_URL\")) {\n              MgmOfsBrokerUrl = getenv(\"EOS_BROKER_URL\");\n            } else {\n              MgmOfsBrokerUrl = val;\n            }\n          }\n        }\n\n        if (!strcmp(\"instance\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\",\n                        \"argument 2 for instance missing. Should be the name of the EOS cluster\");\n            NoGo = 1;\n          } else {\n            if (getenv(\"EOS_INSTANCE_NAME\")) {\n              MgmOfsInstanceName = getenv(\"EOS_INSTANCE_NAME\");\n            } else {\n              MgmOfsInstanceName = val;\n            }\n          }\n\n          Eroute.Say(\"=====> mgmofs.instance : \", MgmOfsInstanceName.c_str(), \"\");\n        }\n\n        if (!strcmp(\"nslib\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"no namespace library path provided\");\n            NoGo = 1;\n          } else {\n            ns_lib_path = val;\n          }\n\n          Eroute.Say(\"=====> mgmofs.nslib : \", ns_lib_path.c_str());\n        }\n\n        if (!strcmp(\"qdbcluster\", var)) {\n          while ((val = Config.GetWord())) {\n            mQdbCluster += val;\n            mQdbCluster += \" \";\n          }\n\n          Eroute.Say(\"=====> mgmofs.qdbcluster : \", mQdbCluster.c_str());\n          mQdbContactDetails.members.parse(mQdbCluster);\n        }\n\n        if (!strcmp(\"qdbpassword\", var)) {\n          while ((val = Config.GetWord())) {\n            mQdbPassword += val;\n          }\n\n          // Trim whitespace at the end\n          mQdbPassword.erase(mQdbPassword.find_last_not_of(\" \\t\\n\\r\\f\\v\") + 1);\n          std::string pwlen = std::to_string(mQdbPassword.size());\n          Eroute.Say(\"=====> mgmofs.qdbpassword length : \", pwlen.c_str());\n          mQdbContactDetails.password = mQdbPassword;\n        }\n\n        if (!strcmp(\"qdbpassword_file\", var)) {\n          std::string path;\n\n          while ((val = Config.GetWord())) {\n            path += val;\n          }\n\n          if (!eos::common::PasswordHandler::readPasswordFile(path, mQdbPassword)) {\n            Eroute.Emsg(\"Config\", \"failed to open path pointed to by qdbpassword_file\");\n            NoGo = 1;\n          }\n\n          std::string pwlen = std::to_string(mQdbPassword.size());\n          Eroute.Say(\"=====> mgmofs.qdbpassword length : \", pwlen.c_str());\n          mQdbContactDetails.password = mQdbPassword;\n        }\n\n        if (!strcmp(\"qclientdir\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument for qclientdir is invalid\");\n            NoGo = 1;\n          } else {\n            mQClientDir = val;\n\n            if (!eos::common::endsWith(mQClientDir, \"/\")) {\n              mQClientDir += \"/\";\n            }\n\n            Eroute.Say(\"=====> mgmofs.qclientdir : \", mQClientDir.c_str());\n          }\n        }\n\n        if (!strcmp(\"qclient_flusher_type\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument for qclient_flusher_type is invalid\");\n            NoGo = 1;\n          } else {\n            mQClientFlusherType = val;\n            Eroute.Say(\"=====> mgmofs.qclient_flusher_type : \",\n                       mQClientFlusherType.c_str());\n          }\n        }\n\n        if (!strcmp(\"qclient_rocksdb_options\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument for qclient_rocksdb_options is invalid\");\n            NoGo = 1;\n          } else {\n            mQClientRocksDBOptions = val;\n            Eroute.Say(\"=====> mgmofs.qclient_rocksdb_options : \",\n                       mQClientRocksDBOptions.c_str());\n          }\n        }\n\n        if (!strcmp(\"authlib\", var)) {\n          if ((!(val = Config.GetWord())) || (::access(val, R_OK))) {\n            Eroute.Emsg(\"Config\", \"I cannot access the authorization library!\");\n            NoGo = 1;\n          } else {\n            mAuthLib = val;\n          }\n\n          Eroute.Say(\"=====> mgmofs.authlib : \", mAuthLib.c_str());\n        }\n\n        if (!strcmp(\"tapeenabled\", var)) {\n          if ((!(val = Config.GetWord())) ||\n              (strcmp(\"true\", val) && strcmp(\"false\", val) &&\n               strcmp(\"1\", val) && strcmp(\"0\", val))) {\n            Eroute.Emsg(\"Config\", \"argument for tapeenabled is invalid. \"\n                        \"Must be <true>, <false>, <1> or <0>!\");\n          } else {\n            if ((!strcmp(\"true\", val) || (!strcmp(\"1\", val)))) {\n              mTapeEnabled = true;\n            }\n\n            Eroute.Say(\"=====> mgmofs.tapeenabled : \", val);\n          }\n        }\n\n        if (!strcmp(\"prepare.dest.space\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument for prepare.dest.space is missing.\");\n            NoGo = 1;\n          } else {\n            mPrepareDestSpace = val;\n            Eroute.Say(\"=====> mgmofs.prepare.dest.space : \", mPrepareDestSpace.c_str());\n          }\n        }\n\n        if (!strcmp(\"prepare.reqid.max\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument for prepare.reqid.max is missing.\");\n            NoGo = 1;\n          } else {\n            mReqIdMax = atoi(val);\n            Eroute.Say(\"=====> mgmofs.prepare.reqid.max : \", val);\n          }\n        }\n\n        if (!strcmp(\"tgc.enablespace\", var)) {\n          std::ostringstream tapeGcSpacesStream;\n\n          while ((val = Config.GetWord())) {\n            mTapeGcSpaces.insert(val);\n            tapeGcSpacesStream << \" \" << val;\n          }\n\n          Eroute.Say(\"=====> mgmofs.tgc.enablespace :\", tapeGcSpacesStream.str().c_str());\n        }\n\n        if (!strcmp(\"authorize\", var)) {\n          if ((!(val = Config.GetWord())) ||\n              (strcmp(\"true\", val) && strcmp(\"false\", val) &&\n               strcmp(\"1\", val) && strcmp(\"0\", val))) {\n            Eroute.Emsg(\"Config\", \"argument 2 for authorize illegal or missing. \"\n                        \"Must be <true>, <false>, <1> or <0>!\");\n            NoGo = 1;\n          } else {\n            if ((!strcmp(\"true\", val) || (!strcmp(\"1\", val)))) {\n              mAuthorize = true;\n            }\n          }\n\n          if (mAuthorize) {\n            Eroute.Say(\"=====> mgmofs.authorize : true\");\n          } else {\n            Eroute.Say(\"=====> mgmofs.authorize : false\");\n          }\n        }\n\n        if (!strcmp(\"errorlog\", var)) {\n          if ((!(val = Config.GetWord())) ||\n              (strcmp(\"true\", val) && strcmp(\"false\", val) &&\n               strcmp(\"1\", val) && strcmp(\"0\", val))) {\n            Eroute.Emsg(\"Config\", \"argument 2 for errorlog illegal or missing. \"\n                        \"Must be <true>, <false>, <1> or <0>!\");\n            NoGo = 1;\n          } else {\n            if ((!strcmp(\"true\", val) || (!strcmp(\"1\", val)))) {\n              mErrLogEnabled = true;\n            } else {\n              mErrLogEnabled = false;\n            }\n          }\n\n          if (mErrLogEnabled) {\n            Eroute.Say(\"=====> mgmofs.errorlog : true\");\n          } else {\n            Eroute.Say(\"=====> mgmofs.errorlog : false\");\n          }\n        }\n\n        if (!strcmp(\"redirector\", var)) {\n          if ((!(val = Config.GetWord())) ||\n              (strcmp(\"true\", val) && strcmp(\"false\", val) &&\n               strcmp(\"1\", val) && strcmp(\"0\", val))) {\n            Eroute.Emsg(\"Config\", \"argument 2 for redirector illegal or missing. \"\n                        \"Must be <true>,<false>,<1> or <0>!\");\n            NoGo = 1;\n          } else {\n            if ((!strcmp(\"true\", val) || (!strcmp(\"1\", val)))) {\n              MgmRedirector = true;\n            } else {\n              MgmRedirector = false;\n            }\n          }\n        }\n\n        if (!strcmp(\"archivedir\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument for archivedir invalid.\");\n            NoGo = 1;\n          } else {\n            MgmArchiveDir = val;\n\n            if (!MgmArchiveDir.endswith(\"/\")) {\n              MgmArchiveDir += \"/\";\n            }\n          }\n        }\n\n        if (!strcmp(\"autoloadconfig\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument for autoloadconfig invalid.\");\n            NoGo = 1;\n          } else {\n            MgmConfigAutoLoad = val;\n          }\n        }\n\n        if (!strcmp(\"alias\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument for alias missing.\");\n            NoGo = 1;\n          } else {\n            MgmOfsAlias = val;\n          }\n        }\n\n        if (!strcmp(\"metalog\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument 2 for metalog missing\");\n            NoGo = 1;\n          } else {\n            MgmMetaLogDir = val;\n            // just try to create it in advance\n            XrdOucString makeit = \"mkdir -p \";\n            makeit += MgmMetaLogDir;\n            int src = system(makeit.c_str());\n\n            if (src) {\n              eos_err(\"%s returned %d\", makeit.c_str(), src);\n            }\n\n            XrdOucString chownit = \"chown -R daemon \";\n            chownit += MgmMetaLogDir;\n            src = system(chownit.c_str());\n\n            if (src) {\n              eos_err(\"%s returned %d\", chownit.c_str(), src);\n            }\n\n            if (::access(MgmMetaLogDir.c_str(), W_OK | R_OK | X_OK)) {\n              Eroute.Emsg(\"Config\", \"cannot access the meta data changelog \"\n                          \"directory for r/w!\", MgmMetaLogDir.c_str());\n              NoGo = 1;\n            } else {\n              Eroute.Say(\"=====> mgmofs.metalog: \", MgmMetaLogDir.c_str(), \"\");\n            }\n          }\n        }\n\n        if (!strcmp(\"authdir\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument 2 for authdir missing\");\n            NoGo = 1;\n          } else {\n            MgmAuthDir = val;\n            // just try to create it in advance\n            XrdOucString makeit = \"mkdir -p \";\n            makeit += MgmAuthDir;\n            int src = system(makeit.c_str());\n\n            if (src) {\n              eos_err(\"%s returned %d\", makeit.c_str(), src);\n            }\n\n            XrdOucString chownit = \"chown -R daemon \";\n            chownit += MgmAuthDir;\n            src = system(chownit.c_str());\n\n            if (src) {\n              eos_err(\"%s returned %d\", chownit.c_str(), src);\n            }\n\n            if ((src = ::chmod(MgmAuthDir.c_str(), S_IRUSR | S_IWUSR | S_IXUSR))) {\n              eos_err(\"chmod 700 %s returned %d\", MgmAuthDir.c_str(), src);\n              NoGo = 1;\n            }\n\n            if (::access(MgmAuthDir.c_str(), W_OK | R_OK | X_OK)) {\n              Eroute.Emsg(\"Config\", \"cannot access the authentication directory \"\n                          \"for r/w:\", MgmAuthDir.c_str());\n              NoGo = 1;\n            } else {\n              Eroute.Say(\"=====> mgmofs.authdir:   \", MgmAuthDir.c_str(), \"\");\n            }\n          }\n        }\n\n        if (!strcmp(\"reportstorepath\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument 2 for reportstorepath missing\");\n            NoGo = 1;\n          } else {\n            IoReportStorePath = val;\n            struct stat info;\n\n            if (::stat(IoReportStorePath.c_str(), &info) != 0) {\n              // Try to create it\n              XrdOucString makeit = \"mkdir -p \";\n              makeit += IoReportStorePath;\n              int src = system(makeit.c_str());\n\n              if (src) {\n                eos_err(\"%s returned %d\", makeit.c_str(), src);\n              }\n            }\n\n            if (info.st_uid != DAEMONUID) {\n              XrdOucString chownit = \"chown -R daemon \";\n              chownit += IoReportStorePath;\n              int src = system(chownit.c_str());\n\n              if (src) {\n                eos_err(\"%s returned %d\", chownit.c_str(), src);\n              }\n            }\n\n            if (::access(IoReportStorePath.c_str(), W_OK | R_OK | X_OK)) {\n              Eroute.Emsg(\"Config\", \"cannot access the reportstore directory \"\n                          \"for r/w:\", IoReportStorePath.c_str());\n              NoGo = 1;\n            } else {\n              Eroute.Say(\"=====> mgmofs.reportstorepath: \", IoReportStorePath.c_str(), \"\");\n            }\n          }\n        }\n\n        // Get the fst gateway hostname and port\n        if (!strcmp(\"fstgw\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"fst gateway value not specified\");\n            NoGo = 1;\n          } else {\n            mFstGwHost = val;\n            size_t pos = mFstGwHost.find(':');\n\n            if (pos == std::string::npos) {\n              // Use a default value if no port is specified\n              mFstGwPort = 1094;\n            } else {\n              mFstGwPort = atoi(mFstGwHost.substr(pos + 1).c_str());\n              mFstGwHost = mFstGwHost.erase(pos);\n            }\n\n            Eroute.Say(\"=====> mgmofs.fstgw: \", mFstGwHost.c_str(), \":\",\n                       std::to_string((long long int)mFstGwPort).c_str());\n          }\n        }\n\n        if (!strcmp(\"trace\", var)) {\n          static struct traceopts {\n            const char* opname;\n            int opval;\n          } tropts[] = {\n            {\"aio\", TRACE_aio},\n            {\"all\", TRACE_ALL},\n            {\"chmod\", TRACE_chmod},\n            {\"close\", TRACE_close},\n            {\"closedir\", TRACE_closedir},\n            {\"debug\", TRACE_debug},\n            {\"delay\", TRACE_delay},\n            {\"dir\", TRACE_dir},\n            {\"exists\", TRACE_exists},\n            {\"getstats\", TRACE_getstats},\n            {\"fsctl\", TRACE_fsctl},\n            {\"io\", TRACE_IO},\n            {\"mkdir\", TRACE_mkdir},\n            {\"most\", TRACE_MOST},\n            {\"open\", TRACE_open},\n            {\"opendir\", TRACE_opendir},\n            {\"qscan\", TRACE_qscan},\n            {\"read\", TRACE_read},\n            {\"readdir\", TRACE_readdir},\n            {\"redirect\", TRACE_redirect},\n            {\"remove\", TRACE_remove},\n            {\"rename\", TRACE_rename},\n            {\"sync\", TRACE_sync},\n            {\"truncate\", TRACE_truncate},\n            {\"write\", TRACE_write},\n            {\"map\", TRACE_map},\n            {\"role\", TRACE_role},\n            {\"access\", TRACE_access},\n            {\"attributes\", TRACE_attributes},\n            {\"allows\", TRACE_allows}\n          };\n          int i, neg, trval = 0, numopts = sizeof(tropts) / sizeof(struct traceopts);\n\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"trace option not specified\");\n            close(cfgFD);\n            return 1;\n          }\n\n          while (val) {\n            Eroute.Say(\"=====> mgmofs.trace: \", val, \"\");\n\n            if (!strcmp(val, \"off\")) {\n              trval = 0;\n            } else {\n              if ((neg = (val[0] == '-' && val[1]))) {\n                val++;\n              }\n\n              for (i = 0; i < numopts; i++) {\n                if (!strcmp(val, tropts[i].opname)) {\n                  if (neg) {\n                    trval &= ~tropts[i].opval;\n                  } else {\n                    trval |= tropts[i].opval;\n                  }\n\n                  break;\n                }\n              }\n\n              if (i >= numopts) {\n                Eroute.Say(\"Config warning: ignoring invalid trace option '\", val, \"'.\");\n              }\n            }\n\n            val = Config.GetWord();\n          }\n\n          gMgmOfsTrace.What = trval;\n        }\n\n        if (!strcmp(\"auththreads\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument for number of auth threads is invalid.\");\n            NoGo = 1;\n          } else {\n            Eroute.Say(\"=====> mgmofs.auththreads: \", val, \"\");\n            mNumAuthThreads = atoi(val);\n          }\n        }\n\n        // Configure frontend port number on which clients submit requests\n        if (!strcmp(\"authport\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument for frontend port invalid.\");\n            NoGo = 1;\n          } else {\n            Eroute.Say(\"=====> mgmofs.authport: \", val, \"\");\n            mFrontendPort = atoi(val);\n          }\n        }\n\n        // Check if we should bind only to localhost address\n        if (!strcmp(\"authlocal\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument for authlocal invalid.\");\n            NoGo = 1;\n          } else {\n            Eroute.Say(\"=====> mgmofs.authlocal: \", val, \"\");\n            mFrontendLocalhost = atoi(val);\n          }\n        }\n\n        if (!strcmp(\"postslavetomaster\", var)) {\n          if (!(val = Config.GetWord())) {\n            Eroute.Emsg(\"Config\", \"argument for postslavetomaster invalid.\");\n            NoGo = 1;\n          } else {\n            Eroute.Say(\"=====> mgmofs.postslavetomaster: \", val, \"\");\n            mPostSlaveToMaster = val;\n          }\n        }\n\n        if (!strcmp(\"protowfendpoint\", var)) {\n          val = Config.GetWord();\n\n          if (val != nullptr) {\n            ProtoWFEndPoint = val;\n          }\n        }\n\n        if (!strcmp(\"protowfresource\", var)) {\n          val = Config.GetWord();\n\n          if (val != nullptr) {\n            ProtoWFResource = val;\n          }\n        }\n\n        // Use gRPC calls instead of xrootd notifications?\n        if (!strcmp(\"protowfusegrpc\", var)) {\n          if ((!(val = Config.GetWord())) ||\n              (strcmp(\"true\", val) && strcmp(\"false\", val) &&\n               strcmp(\"1\", val) && strcmp(\"0\", val))) {\n            Eroute.Emsg(\"Config\", \"argument for protowfusegrpc is invalid. \"\n                        \"Must be <true>, <false>, <1> or <0>!\");\n          } else {\n            protowfusegrpc = false;\n\n            if ((!strcmp(\"true\", val) || (!strcmp(\"1\", val)))) {\n              protowfusegrpc = true;\n            }\n\n            Eroute.Say(\"=====> mgmofs.protowfusegrpc : \", val);\n          }\n        }\n\n        if (!strcmp(\"jwttokenpath\", var)) {\n          val = Config.GetWord();\n\n          if (val != nullptr) {\n            JwtTokenPath = val;\n          }\n\n          Eroute.Say(\"=====> mgmofs.jwttokenpath : \", val);\n        }\n      }\n\n      if (!strcmp(\"protowfusegrpctls\", var)) {\n        if ((!(val = Config.GetWord())) ||\n            (strcmp(\"true\", val) && strcmp(\"false\", val) &&\n             strcmp(\"1\", val) && strcmp(\"0\", val))) {\n          Eroute.Emsg(\"Config\", \"argument for protowfusegrpctls is invalid. \"\n                      \"Must be <true>, <false>, <1> or <0>!\");\n        } else {\n          /* false already set when declared */\n          if ((!strcmp(\"true\", val) || (!strcmp(\"1\", val)))) {\n            protowfusegrpctls = true;\n          }\n        }\n      }\n\n      //Get the XrdHttp server port number\n      if (!strcmp(\"xrd.protocol\", var)) {\n        val = Config.GetWord();\n\n        if (val != nullptr) {\n          std::vector<std::string> xrdProtocolValues;\n          eos::common::StringConversion::Tokenize(val, xrdProtocolValues);\n          auto xrdHttpProtocolItor = std::find_if(xrdProtocolValues.begin(),\n          xrdProtocolValues.end(), [](const std::string & str) {\n            return str.find(\"XrdHttp\") != std::string::npos;\n          });\n\n          if (xrdHttpProtocolItor != xrdProtocolValues.end()) {\n            //We have the XrdHttp protocol set\n            const std::string& xrdHttpProtocol = *xrdHttpProtocolItor;\n            size_t posPort = xrdHttpProtocol.find(':');\n\n            if (posPort != std::string::npos && posPort < xrdHttpProtocol.size()) {\n              try {\n                XrdHttpPort = std::stoi(xrdHttpProtocol.substr(posPort + 1));\n              } catch (const std::exception& ex) {\n                // The port is not a number, don't set it\n              }\n            }\n          }\n        }\n      }\n\n      {\n        if (!strcmp(\"taperestapi.sitename\", var)) {\n          val = Config.GetWord();\n\n          if (val != nullptr) {\n            tapeRestApiSitename = val;\n            Eroute.Say(\"=====> taperestapi.sitename: \", val, \"\");\n          } else {\n            Eroute.Say(\"Config warning: REST API sitename not specified, disabling tape REST API.\");\n          }\n        }\n\n        const char* endpointsStr = \"taperestapi.endpoints.\";\n        int endpointsStrLen = strlen(endpointsStr);\n\n        if (!strncmp(endpointsStr, var, endpointsStrLen)) {\n          char* version_ptr_begin = var + endpointsStrLen;\n          char* version_ptr_end = strstr(var + endpointsStrLen, \".uri\");\n\n          if (!version_ptr_end || strcmp(version_ptr_end, \".uri\")) {\n            auto err_msg = std::string(\"command \") + var + \" is invalid\";\n            Eroute.Emsg(\"Config\", err_msg.c_str());\n            NoGo = 1;\n          } else {\n            std::string version(version_ptr_begin, version_ptr_end);\n\n            if (!eos::common::eos_regex_match(version, sVersionRegex)) {\n              auto err_msg = std::string(\"version \") + version +\n                             \" in command \" + var + \" is invalid\";\n              Eroute.Emsg(\"Config\", err_msg.c_str());\n              NoGo = 1;\n            } else {\n              val = Config.GetWord();\n              tapeRestApiEndpointUrlMap[version] = val;\n              Eroute.Say(\"=====> \", var, \": \", val);\n            }\n          }\n        }\n      }\n    }\n\n    if ((retc = Config.LastError())) {\n      NoGo = Eroute.Emsg(\"Config\", -retc, \"read config file\", ConfigFN);\n    }\n\n    Config.Close();\n  }\n\n  // Make sure we have a proper QuarkDB configuration present\n  if (mQdbContactDetails.empty()) {\n    Eroute.Say(\"=====> ERROR: No QuarkDB configuration - missing mgmofs.qdbcluster!\");\n    return 1;\n  }\n\n  if (mQdbContactDetails.password.empty()) {\n    Eroute.Say(\"=====> ERROR: No QuarkDB password configuration - missing \"\n               \"mgmofs.qdbpassword/mgmofs.qdbpassword_file!\");\n    Eroute.Say(\"=====> ERROR: EOS will not connect to a QuarkDB instance \"\n               \"which is not protected by a password!\");\n    return 1;\n  }\n\n  if (protowfusegrpc) {\n    Eroute.Say(\"=====> mgmofs.protowfusegrpc : true\");\n  } else {\n    Eroute.Say(\"=====> mgmofs.protowfusegrpc : false\");\n  }\n\n  if (protowfusegrpctls) {\n    Eroute.Say(\"=====> mgmofs.protowfusegrpctls : true\");\n  } else {\n    Eroute.Say(\"=====> mgmofs.protowfusegrpctls : false\");\n  }\n\n  if (NoGo) {\n    return NoGo;\n  }\n\n  if (MgmRedirector) {\n    Eroute.Say(\"=====> mgmofs.redirector : true\");\n  } else {\n    Eroute.Say(\"=====> mgmofs.redirector : false\");\n  }\n\n  if (!MgmOfsBrokerUrl.endswith(\"/\")) {\n    MgmOfsBrokerUrl += \"/\";\n  }\n\n  if (!MgmOfsBrokerUrl.endswith(\"//eos/\")) {\n    Eroute.Say(\"Config error: the broker url has to be of the form \"\n               \"<root://<hostname>[:<port>]//\");\n    return 1;\n  }\n\n  if (!MgmMetaLogDir.length()) {\n    Eroute.Say(\"Config error: meta data log directory is not defined : \"\n               \"mgm.metalog=</var/eos/md/>\");\n    return 1;\n  }\n\n  if (!MgmAuthDir.length()) {\n    Eroute.Say(\"Config error: auth directory is not defined: \"\n               \"mgm.authdir=</var/eos/auth/>\");\n    return 1;\n  }\n\n  if (!MgmArchiveDir.length()) {\n    Eroute.Say(\"Config notice: archive directory is not defined - archiving is disabled\");\n  }\n\n  if (!ns_lib_path.empty()) {\n    eos::common::PluginManager& pm = eos::common::PluginManager::GetInstance();\n    pm.LoadByPath(ns_lib_path);\n  }\n\n  MgmOfsBroker = MgmOfsBrokerUrl;\n  MgmDefaultReceiverQueue = MgmOfsBrokerUrl;\n  MgmDefaultReceiverQueue += \"*/fst\";\n  MgmOfsBrokerUrl += HostName;\n  MgmOfsBrokerUrl += \"/mgm\";\n  MgmOfsQueue = \"/eos/\";\n  MgmOfsQueue += ManagerId;\n  MgmOfsQueue += \"/mgm\";\n  // Setup the circular in-memory logging buffer\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n  // Configure log-file fan out\n  std::vector<std::string> lFanOutTags{\n      \"Grpc\",          \"Balancer\",      \"Converter\",\n      \"DrainJob\",      \"ZMQ\",           \"MetadataFlusher\",\n      \"Http\",          \"Master\",        \"Recycle\",\n      \"LRU\",           \"WFE\",           \"Wnc\",\n      \"WFE::Job\",      \"GroupBalancer\", \"GroupDrainer\",\n      \"GeoBalancer\",   \"GeoTreeEngine\", \"ReplicationTracker\",\n      \"FileInspector\", \"Mounts\",        \"OAuth\",\n      \"TokenCmd\",      \"TrafficShaping\"};\n  // Get the XRootD log directory\n  char* logdir = 0;\n  XrdOucEnv::Import(\"XRDLOGDIR\", logdir);\n\n  if (logdir && !eos::common::Logging::GetInstance().IsZstdEnabled()) {\n    for (size_t i = 0; i < lFanOutTags.size(); ++i) {\n      std::string log_path = logdir;\n      log_path += \"/\";\n\n      if (lFanOutTags[i] == \"#\") {\n        log_path += \"Clients\";\n      } else {\n        log_path += lFanOutTags[i];\n      }\n\n      log_path += \".log\";\n      FILE* fp = fopen(log_path.c_str(), \"a+\");\n\n      if (fp) {\n        g_logging.AddFanOut(lFanOutTags[i].c_str(), fp);\n      } else {\n        fprintf(stderr, \"error: failed to open sub-logfile=%s\", log_path.c_str());\n      }\n    }\n\n    // Add some alias for the logging\n    g_logging.AddFanOutAlias(\"HttpHandler\", \"Http\");\n    g_logging.AddFanOutAlias(\"HttpServer\", \"Http\");\n    g_logging.AddFanOutAlias(\"GrpcServer\", \"Grpc\");\n    g_logging.AddFanOutAlias(\"GrpcWncServer\", \"Wnc\");\n    g_logging.AddFanOutAlias(\"ProtocolHandler\", \"Http\");\n    g_logging.AddFanOutAlias(\"PropFindResponse\", \"Http\");\n    g_logging.AddFanOutAlias(\"WebDAV\", \"Http\");\n    g_logging.AddFanOutAlias(\"WebDAVHandler\", \"Http\");\n    g_logging.AddFanOutAlias(\"WebDAVReponse\", \"Http\");\n    g_logging.AddFanOutAlias(\"S3\", \"Http\");\n    g_logging.AddFanOutAlias(\"S3Store\", \"Http\");\n    g_logging.AddFanOutAlias(\"S3Handler\", \"Http\");\n    g_logging.AddFanOutAlias(\"DrainTransferJob\", \"DrainJob\");\n    g_logging.AddFanOutAlias(\"DrainFs\", \"DrainJob\");\n    g_logging.AddFanOutAlias(\"Drainer\", \"DrainJob\");\n    g_logging.AddFanOutAlias(\"Clients\", \"Mounts\");\n    g_logging.AddFanOutAlias(\"ConversionInfo\", \"Converter\");\n    g_logging.AddFanOutAlias(\"ConversionJob\", \"Converter\");\n    g_logging.AddFanOutAlias(\"ConverterEngine\", \"Converter\");\n    g_logging.AddFanOutAlias(\"TrafficShapingEngine\", \"TrafficShaping\");\n    g_logging.AddFanOutAlias(\"TrafficShapingManager\", \"TrafficShaping\");\n  }\n\n  Eroute.Say(\"=====> mgmofs.broker : \", MgmOfsBrokerUrl.c_str(), \"\");\n  XrdOucString ttybroadcastkillline = \"pkill -9 -f \\\"eos-tty-broadcast\\\"\";\n  int rrc = system(ttybroadcastkillline.c_str());\n\n  if (WEXITSTATUS(rrc)) {\n    eos_info(\"%s returned %d\", ttybroadcastkillline.c_str(), rrc);\n  }\n\n  if (getenv(\"EOS_TTY_BROADCAST_LISTEN_LOGFILE\") &&\n      getenv(\"EOS_TTY_BROADCAST_EGREP\")) {\n    XrdOucString ttybroadcastline = \"eos-tty-broadcast \";\n    ttybroadcastline += getenv(\"EOS_TTY_BROADCAST_LISTEN_LOGFILE\");\n    ttybroadcastline += \" \";\n    ttybroadcastline += getenv(\"EOS_TTY_BROADCAST_EGREP\");\n    ttybroadcastline += \" >& /dev/null &\";\n    eos_info(\"%s\\n\", ttybroadcastline.c_str());\n    rrc = system(ttybroadcastline.c_str());\n\n    if (WEXITSTATUS(rrc)) {\n      eos_info(\"%s returned %d\", ttybroadcastline.c_str(), rrc);\n    }\n  }\n\n  int pos1 = MgmDefaultReceiverQueue.find(\"//\");\n  int pos2 = MgmDefaultReceiverQueue.find(\"//\", pos1 + 2);\n\n  if (pos2 != STR_NPOS) {\n    MgmDefaultReceiverQueue.erase(0, pos2 + 1);\n  }\n\n  Eroute.Say(\"=====> mgmofs.defaultreceiverqueue : \",\n             MgmDefaultReceiverQueue.c_str(), \"\");\n\n  if (!MgmOfsName.length()) {\n    Eroute.Say(\"Config error: no mgmofs fs has been defined (mgmofs.fs /...)\",\n               \"\", \"\");\n  } else {\n    Eroute.Say(\"=====> mgmofs.fs: \", MgmOfsName.c_str(), \"\");\n  }\n\n  if (mErrLogEnabled) {\n    Eroute.Say(\"=====> mgmofs.errorlog : enabled\");\n  } else {\n    Eroute.Say(\"=====> mgmofs.errorlog : disabled\");\n  }\n\n  // Load the authorization plugin if requested\n  if (!mAuthLib.empty() && mAuthorize) {\n    XrdSysPlugin* myLib;\n    XrdAccAuthorize * (*ep)(XrdSysLogger*, const char*, const char*);\n    // Authorization comes from the library or we use the default\n\n    if (!(myLib = new XrdSysPlugin(&Eroute, mAuthLib.c_str()))) {\n      Eroute.Emsg(\"Config\", \"Failed to load authorization library!\");\n      NoGo = 1;\n    } else {\n      ep = (XrdAccAuthorize * (*)(XrdSysLogger*, const char*, const char*))\n           (myLib->getPlugin(\"XrdAccAuthorizeObject\"));\n\n      if (!ep) {\n        Eroute.Emsg(\"Config\", \"Failed to get authorization library plugin!\");\n        NoGo = 1;\n      } else {\n        mExtAuthz = ep(Eroute.logger(), ConfigFN, 0);\n\n        if (mExtAuthz == nullptr) {\n          Eroute.Emsg(\"Config\", \"Failed to get external authorization \"\n                      \"plugin object!\");\n          NoGo = 1;\n        }\n      }\n    }\n  }\n\n  // We need to specify this if the server was not started with the explicit\n  // manager option ... e.g. see XrdOfs\n  Eroute.Say(\"=====> all.role: \", role.c_str(), \"\");\n\n  if (role == \"manager\") {\n    putenv((char*) \"XRDREDIRECT=R\");\n  }\n\n  XrdOucString unit = \"mgm@\";\n  unit += ManagerId;\n  g_logging.SetLogPriority(LOG_INFO);\n  g_logging.SetUnit(unit.c_str());\n  std::string filter =\n    \"Process,AddQuota,Update,UpdateHint,\"\n    \"Deletion,PrintOut,SharedHash,work\";\n  g_logging.SetFilter(filter.c_str());\n  Eroute.Say(\"=====> setting message filter: Process,AddQuota,Update,UpdateHint,\"\n             \"Deletion,PrintOut,SharedHash,work\");\n  mConfigEngine = new QuarkDBConfigEngine(gOFS->mQdbContactDetails);\n  mConfigEngine->SetAutoSave(true);\n  using eos::common::CommentLog;\n  // Create comment log to save all proc commands executed with a comment\n  mCommentLog.reset(new CommentLog(\"/var/log/eos/mgm/logbook.log\"));\n\n  if (mCommentLog && mCommentLog->IsValid()) {\n    Eroute.Say(\"=====> comment log in /var/log/eos/mgm/logbook.log\");\n  } else {\n    Eroute.Emsg(\"Config\", \"Cannot create/open the comment log file \"\n                \"/var/log/eos/mgm/logbook.log\");\n    NoGo = 1;\n  }\n\n  mFusexStackTraces.reset(new\n                          CommentLog(\"/var/log/eos/mgm/eosxd-stacktraces.log\"));\n\n  if (mFusexStackTraces && mFusexStackTraces->IsValid()) {\n    Eroute.Say(\"=====> eosxd stacktraces log in /var/log/eos/mgm/eosxd-stacktraces.log\");\n  } else {\n    Eroute.Emsg(\"Config\", \"Cannot create/open the eosxd stacktraces log file \"\n                \"/var/log/eos/mgm/eosxd-stacktraces.log\");\n    NoGo = 1;\n  }\n\n  mFusexLogTraces.reset(new CommentLog(\"/var/log/eos/mgm/eosxd-logtraces.log\"));\n\n  if (mFusexLogTraces && mFusexLogTraces->IsValid()) {\n    Eroute.Say(\"=====> eosxd logtraces log in /var/log/eos/mgm/eosxd-logtraces.log\");\n  } else {\n    Eroute.Emsg(\"Config\", \"Cannot create/open the eosxd logtraces log file \"\n                \"/var/log/eos/mgm/eosxd-logtraces.log\");\n    NoGo = 1;\n  }\n\n  // Initialize Audit logger under <logdir>/audit (ensure directory exists)\n  {\n    std::string baseLogDir = \"/var/log/eos\";\n\n    if (logdir && *logdir) {\n      baseLogDir = logdir;\n    }\n\n    const std::string auditDir = baseLogDir + \"/audit\";\n    struct stat st {};\n\n    if (::stat(auditDir.c_str(), &st) != 0 || !S_ISDIR(st.st_mode)) {\n      eos::common::Path p((auditDir + \"/.keep\").c_str());\n\n      if (!p.MakeParentPath(S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) {\n        Eroute.Emsg(\"Config\", \"cannot create audit directory\", auditDir.c_str());\n        NoGo = 1;\n      } else {\n        (void)::chmod(auditDir.c_str(),\n                      S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);\n        (void)::chown(auditDir.c_str(), 2, 2);\n      }\n    }\n\n    try {\n      // Optional rotation override via EOS_AUDIT_ROTATION (seconds)\n      unsigned rotationSeconds = 3600; // default 1 hour\n\n      if (const char* envRot = ::getenv(\"EOS_AUDIT_ROTATION\")) {\n        if (*envRot) {\n          char* endp = nullptr;\n          errno = 0;\n          long v = ::strtol(envRot, &endp, 10);\n\n          if (errno == 0 && endp && *endp == '\\0' && v > 0 &&\n              v <= static_cast<long>(std::numeric_limits<unsigned>::max())) {\n            rotationSeconds = static_cast<unsigned>(v);\n          }\n        }\n      }\n\n      mAudit.reset(new eos::common::Audit(auditDir, rotationSeconds));\n      Eroute.Say(\"=====> audit log directory: \", auditDir.c_str(), \"\");\n\n      // Apply audit environment configuration\n      if (mEnvAuditDisableAll) {\n        mAudit.reset();\n      } else if (mAudit) {\n        if (mEnvAuditAttributeOnly) {\n          // attribute mode: keep audit object, disable all globals\n          mAudit->setReadAuditing(false);\n          mAudit->setListAuditing(false);\n        } else {\n          mAudit->setReadAuditing(mEnvAuditRead);\n          mAudit->setListAuditing(mEnvAuditList);\n        }\n\n        if (mEnvAuditReadSuffixesSet) {\n          mAudit->setReadAuditSuffixes(mEnvAuditReadSuffixes);\n        }\n\n        if (mEnvAuditReadAll) {\n          mAudit->setReadAuditAll(true);\n        }\n      }\n    } catch (...) {\n      Eroute.Emsg(\"Config\", \"failed to initialize audit logger\");\n      NoGo = 1;\n    }\n  }\n\n  // Save MGM alias if configured\n  if (getenv(\"EOS_MGM_ALIAS\")) {\n    MgmOfsAlias = getenv(\"EOS_MGM_ALIAS\");\n  }\n\n  if (MgmOfsAlias.length()) {\n    Eroute.Say(\"=====> mgmofs.alias: \", MgmOfsAlias.c_str());\n  }\n\n  // Build the adler & sha1 checksum of the default keytab file\n  const std::string keytab_fn = \"/etc/eos.keytab\";\n  std::string keytab_xs = \"unaccessible\";\n\n  if (!eos::common::GetFileAdlerXs(keytab_xs, keytab_fn)) {\n    eos_static_crit(\"msg=\\\"failed keytab checksum computation\\\" fn=\\\"%s\\\"\",\n                    keytab_fn.c_str());\n    return 1;\n  }\n\n  std::string binary_sha1 = \"\";\n\n  if (!eos::common::GetFileBinarySha1(binary_sha1, keytab_fn)) {\n    eos_static_crit(\"msg=\\\"failed keytab sha1 computation\\\" fn=\\\"%s\\\"\",\n                    keytab_fn.c_str());\n    return 1;\n  }\n\n  std::string symkey = \"\";\n  eos::common::SymKey::Base64Encode(binary_sha1.data(), binary_sha1.size(),\n                                    symkey);\n  eos_static_notice(\"MGM_HOST=%s MGM_PORT=%ld VERSION=%s RELEASE=%s \"\n                    \"KEYTABADLER=%s\", HostName, myPort, VERSION, RELEASE,\n                    keytab_xs.c_str());\n\n  if (!eos::common::gSymKeyStore.SetKey64(symkey.c_str(), 0)) {\n    eos_static_crit(\"msg=\\\"unable to store the created symmetric key\\\" \"\n                    \"key=\\\"%s\\\"\", symkey.c_str());\n    return 1;\n  }\n\n  // If tape is enabled, must enable the tape garbage collector\n  if (mTapeEnabled) {\n    try {\n      mTapeGc->setTapeEnabled(mTapeGcSpaces);\n    } catch (std::exception& ex) {\n      std::ostringstream msg;\n      msg << \"msg=\\\"failed to start tape-aware garbage collection: \" << ex.what() <<\n          \"\\\"\";\n      eos_static_crit(\"%s\", msg.str().c_str());\n      return 1;\n    } catch (...) {\n      eos_static_crit(\"%s\", \"msg=\\\"failed to start tape-aware garbage \"\n                      \"collection: Caught an unknown exception\\\"\");\n      return 1;\n    }\n  } else if (!mTapeGcSpaces.empty()) {\n    std::ostringstream tapeGcSpaceWarning;\n    tapeGcSpaceWarning <<\n                       \"msg=\\\"These spaces will not be garbage collected because mgmofs.tapeenabled=false:\";\n\n    for (const auto& tapeGcSpace : mTapeGcSpaces) {\n      tapeGcSpaceWarning << \" \" << tapeGcSpace;\n    }\n\n    tapeGcSpaceWarning << \"\\\"\";\n    eos_warning(tapeGcSpaceWarning.str().c_str());\n  }\n\n  // If tape garbage collector is enabled, all R/W must be redirected to the master node\n  if (mTapeEnabled && !getenv(\"EOS_HA_REDIRECT_READS\")) {\n    std::ostringstream msg;\n    msg << \"msg=\"\n        << \"Mgm node with tape-aware garbage collection must redirect all write/read traffic to master\\\"\";\n    eos_crit(msg.str().c_str());\n    return 1;\n  }\n\n  eosViewRWMutex.SetBlocking(true);\n  // Configure the access mutex to be blocking\n  Access::gAccessMutex.SetBlocking(true);\n  // Initialize the HA setup\n  mMaster.reset(new eos::mgm::QdbMaster(mQdbContactDetails, ManagerId.c_str()));\n\n  if (!mMaster->Init()) {\n    return 1;\n  }\n\n  // Create global visible configuration parameters using 3 queues\n  // \"/eos/<instance>/\"\n  XrdOucString configbasequeue = \"/config/\";\n  configbasequeue += MgmOfsInstanceName.c_str();\n  MgmConfigQueue = configbasequeue;\n  MgmConfigQueue += \"/mgm/\";\n  eos_static_notice(\"%s\", \"msg=\\\"running SharedManager via QDB i.e NO-MQ\\\"\");\n  qclient::SharedManager* qsm =\n    new qclient::SharedManager(mQdbContactDetails.members,\n                               mQdbContactDetails.constructSubscriptionOptions());\n  mMessagingRealm.reset(new eos::mq::MessagingRealm(qsm));\n  eos::common::InstanceName::set(MgmOfsInstanceName.c_str());\n\n  if (!mMessagingRealm->setInstanceName(MgmOfsInstanceName.c_str())) {\n    eos_static_crit(\"%s\", \"msg=\\\"unable to set instance name in QDB\\\"\");\n    Eroute.Emsg(\"Config\", \"cannot set instance name in QDB\");\n    return 1;\n  }\n\n  // Disable some features if we are only a redirector\n  if (!MgmRedirector) {\n    // Create the ZMQ processor used especially for fuse\n    XrdOucString zmq_port = \"tcp://*:\";\n    zmq_port += (int) mFusexPort;\n    zMQ = new ZMQ(zmq_port.c_str());\n\n    if (!zMQ) {\n      Eroute.Emsg(\"Config\", \"cannot start ZMQ processor\");\n      return 1;\n    }\n\n    zMQ->ServeFuse();\n  }\n\n  // Initialize geotree engine\n  mGeoTreeEngine.reset(new eos::mgm::GeoTreeEngine(mMessagingRealm.get()));\n\n  // Eventually autoload a configuration\n  if (getenv(\"EOS_AUTOLOAD_CONFIG\")) {\n    MgmConfigAutoLoad = getenv(\"EOS_AUTOLOAD_CONFIG\");\n  }\n\n  // NoStall Fuse Application Name\n  if (getenv(\"EOS_MGM_FUSEX_NOSTALL_APP\")) {\n    mFuseNoStallApp = getenv(\"EOS_MGM_FUSEX_NOSTALL_APP\");\n  } else {\n    mFuseNoStallApp = \"fuse::restic\";\n  }\n\n  XrdOucString instancepath = \"/eos/\";\n  MgmProcPath = \"/eos/\";\n  XrdOucString subpath = MgmOfsInstanceName;\n\n  // Remove leading \"eos\" from the instance name when building the proc path for\n  // \"aesthetic\" reasons\n  if (subpath.beginswith(\"eos\")) {\n    subpath.erase(0, 3);\n  }\n\n  MgmProcPath += subpath;\n  MgmProcPath += \"/proc\";\n  // This path is used for temporary output files for layout conversions\n  MgmProcConversionPath = MgmProcPath;\n  MgmProcConversionPath += \"/conversion\";\n  MgmProcDevicesPath = MgmProcPath;\n  MgmProcDevicesPath += \"/devices\";\n  MgmProcMasterPath = MgmProcPath;\n  MgmProcMasterPath += \"/master\";\n  MgmProcArchivePath = MgmProcPath;\n  MgmProcArchivePath += \"/archive\";\n  MgmProcWorkflowPath = MgmProcPath;\n  MgmProcWorkflowPath += \"/workflow\";\n  MgmProcTrackerPath = MgmProcPath;\n  MgmProcTrackerPath += \"/tracker\";\n  MgmProcTokenPath = MgmProcPath;\n  MgmProcTokenPath += \"/token\";\n  MgmProcBulkRequestPath = MgmProcPath;\n  MgmProcBulkRequestPath += \"/bulkrequests\";\n  Recycle::gRecyclingPrefix.insert(0, MgmProcPath.c_str());\n  instancepath += subpath;\n  // Initialize user mapping\n  eos::common::Mapping::Init();\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n  eos::common::RWMutex* fs_mtx = &FsView::gFsView.ViewMutex;\n  eos::common::RWMutex* quota_mtx = &Quota::pMapMutex;\n  eos::common::RWMutex* ns_mtx = &eosViewRWMutex;\n  eos::common::RWMutex* fusex_client_mtx = &gOFS->zMQ->gFuseServer.Client();\n  //eos::common::RWMutex* fusex_cap_mtx = &gOFS->zMQ->gFuseServer.Cap();\n  // eos::common::RWMutex::EstimateLatenciesAndCompensation();\n  fs_mtx->SetBlocking(true);\n  fs_mtx->SetDebugName(\"FsView\");\n  fs_mtx->SetTiming(false);\n  fs_mtx->SetSampling(true, 0.01);\n  quota_mtx->SetDebugName(\"QuotaView\");\n  quota_mtx->SetTiming(false);\n  quota_mtx->SetSampling(true, 0.01);\n  ns_mtx->SetDebugName(\"eosView\");\n  ns_mtx->SetTiming(false);\n  ns_mtx->SetSampling(true, 0.01);\n  fusex_client_mtx->SetDebugName(\"FusexClient\");\n  //fusex_cap_mtx->SetDebugName(\"FusexCap\");\n  std::vector<eos::common::RWMutex*> order;\n  order.push_back(fs_mtx);\n  order.push_back(ns_mtx);\n  order.push_back(fusex_client_mtx);\n  //order.push_back(fusex_cap_mtx);\n  order.push_back(quota_mtx);\n  eos::common::RWMutex::AddOrderRule(\"Eos Mgm Mutexes\", order);\n#endif\n  // Configure the meta data catalog\n  mViewMutexWatcher.activate(eosViewRWMutex, \"eosViewRWMutex\");\n\n  if (!mMaster->BootNamespace()) {\n    eos_static_crit(\"%s\", \"msg=\\\"namespace boot failed\\\"\");\n    return 1;\n  }\n\n  // Check the '/' directory\n  std::shared_ptr<eos::IContainerMD> rootmd;\n\n  try {\n    rootmd = eosView->getContainer(\"/\");\n  } catch (const eos::MDException& e) {\n    Eroute.Emsg(\"Config\", \"cannot get the / directory meta data\");\n    eos_static_crit(\"eos view cannot retrieve the / directory\");\n    return 1;\n  }\n\n  // Check the '/' directory permissions\n  if (!rootmd->getMode()) {\n    if (mMaster->IsMaster()) {\n      // no permissions set yet\n      try {\n        rootmd->setMode(S_IFDIR | S_IRWXU | S_IROTH | S_IXOTH | S_IRGRP |\n                        S_IWGRP | S_IXGRP);\n      } catch (const eos::MDException& e) {\n        Eroute.Emsg(\"Config\", \"cannot set the / directory mode to initial mode\");\n        eos_static_crit(\"cannot set the / directory mode to 755\");\n        return 1;\n      }\n    } else {\n      Eroute.Emsg(\"Config\", \"/ directory has no 755 permissions set\");\n      eos_static_crit(\"cannot see / directory with mode to 755\");\n      return 1;\n    }\n  }\n\n  eos_static_info(\"msg=\\\"/ permissions are %o\\\"\", rootmd->getMode());\n  //mProcDirectoryBulkRequestLocations.reset(new bulk::ProcDirectoryBulkRequestLocations(MgmProcPath.c_str()));\n  std::string restApiProcBulkRequestPath = MgmProcPath.c_str();\n  restApiProcBulkRequestPath += \"/tape-rest-api\";\n  mProcDirectoryBulkRequestTapeRestApiLocations.reset(new\n      bulk::ProcDirectoryBulkRequestLocations(restApiProcBulkRequestPath));\n\n  if (mMaster->IsMaster()) {\n    // Create /eos/ and /eos/<instance>/ directories\n    std::shared_ptr<eos::IContainerMD> eosmd;\n\n    try {\n      eosmd = eosView->getContainer(\"/eos/\");\n    } catch (const eos::MDException& e) {\n      eosmd = nullptr;\n    }\n\n    if (!eosmd) {\n      try {\n        eosmd = eosView->createContainer(\"/eos/\", true);\n        eosmd->setMode(S_IFDIR | S_IRWXU | S_IROTH | S_IXOTH | S_IRGRP |\n                       S_IWGRP | S_IXGRP);\n        eosmd->setAttribute(\"sys.forced.checksum\", \"adler\");\n        eosView->updateContainerStore(eosmd.get());\n        eos_static_info(\"/eos permissions are %o checksum is set <adler>\",\n                        eosmd->getMode());\n        eosmd = eosView->createContainer(instancepath.c_str(), true);\n        eosmd->setMode(S_IFDIR | S_IRWXU | S_IROTH | S_IXOTH | S_IRGRP |\n                       S_IWGRP | S_IXGRP);\n        eosmd->setAttribute(\"sys.forced.checksum\", \"adler\");\n        eosView->updateContainerStore(eosmd.get());\n        eos_static_info(\"%s permissions are %o checksum is set <adler>\",\n                        instancepath.c_str(),\n                        eosmd->getMode());\n      } catch (const eos::MDException& e) {\n        Eroute.Emsg(\"Config\", \"cannot set the /eos/ directory mode to initial mode\");\n        eos_static_crit(\"cannot set the /eos/ directory mode to 755\");\n        return 1;\n      }\n    }\n\n    // Create /eos/<instance>/proc/ directory\n    try {\n      eosmd = eosView->getContainer(MgmProcPath.c_str());\n    } catch (const eos::MDException& e) {\n      eosmd = nullptr;\n    }\n\n    if (!eosmd) {\n      try {\n        eosmd = eosView->createContainer(MgmProcPath.c_str(), true);\n        eosmd->setMode(S_IFDIR | S_IRWXU | S_IROTH | S_IXOTH | S_IRGRP | S_IXGRP);\n        eosView->updateContainerStore(eosmd.get());\n      } catch (const eos::MDException& e) {\n        Eroute.Emsg(\"Config\", \"cannot set the /eos/<instance>/proc/ \"\n                    \"directory mode to initial mode\");\n        eos_static_crit(\"cannot set the /eos/proc directory mode to 755\");\n        return 1;\n      }\n    }\n\n    // Create recycle directory\n    try {\n      eosmd = eosView->getContainer(Recycle::gRecyclingPrefix);\n    } catch (const eos::MDException& e) {\n      eosmd = nullptr;\n    }\n\n    if (!eosmd) {\n      try {\n        eosmd = eosView->createContainer(Recycle::gRecyclingPrefix, true);\n        eosmd->setMode(S_IFDIR | S_IRWXU);\n        eosView->updateContainerStore(eosmd.get());\n        eos_static_info(\"%s permissions are %o\", Recycle::gRecyclingPrefix.c_str(),\n                        eosmd->getMode());\n      } catch (const eos::MDException& e) {\n        Eroute.Emsg(\"Config\", \"cannot set the recycle directory mode to initial mode\");\n        eos_static_crit(\"cannot set the %s directory mode to 700\",\n                        Recycle::gRecyclingPrefix.c_str());\n        eos_static_crit(\"%s\", e.what());\n        return 1;\n      }\n    }\n\n    // Create output directory layout conversions\n    try {\n      eosmd = gOFS->eosView->getContainer(MgmProcConversionPath.c_str());\n    } catch (const eos::MDException& e) {\n      eosmd = nullptr;\n    }\n\n    if (!eosmd) {\n      try {\n        eosmd = gOFS->eosView->createContainer(MgmProcConversionPath.c_str(), true);\n        eosmd->setMode(S_IFDIR | S_IRWXU | S_IRWXG);\n        eosmd->setCUid(2); // conversion directory is owned by daemon\n        eosmd->setCGid(2);\n        gOFS->eosView->updateContainerStore(eosmd.get());\n      } catch (const eos::MDException& e) {\n        Eroute.Emsg(\"Config\", \"cannot set the /eos/../proc/conversion directory\"\n                    \" mode to initial mode\");\n        eos_static_crit(\"cannot set the /eos/../proc/conversion directory mode to 770\");\n        return 1;\n      }\n    }\n\n    // Create output directory with device information\n    try {\n      eosmd = gOFS->eosView->getContainer(MgmProcDevicesPath.c_str());\n    } catch (const eos::MDException& e) {\n      eosmd = nullptr;\n    }\n\n    if (!eosmd) {\n      try {\n        eosmd = gOFS->eosView->createContainer(MgmProcDevicesPath.c_str(), true);\n        eosmd->setMode(S_IFDIR | S_IRWXU | S_IRWXG);\n        eosmd->setCUid(0);\n        eosmd->setCGid(0);\n        gOFS->eosView->updateContainerStore(eosmd.get());\n      } catch (const eos::MDException& e) {\n        Eroute.Emsg(\"Config\", \"cannot create the /eos/../proc/devices directory\");\n        eos_static_crit(\"cannot create the /eos/../proc/devices directory\");\n        return 1;\n      }\n    }\n\n    // Create directory for fast find functionality of archived dirs\n    try {\n      eosmd = gOFS->eosView->getContainer(MgmProcArchivePath.c_str());\n    } catch (const eos::MDException& e) {\n      eosmd = nullptr;\n    }\n\n    if (!eosmd) {\n      try {\n        eosmd = gOFS->eosView->createContainer(MgmProcArchivePath.c_str(), true);\n        eosmd->setMode(S_IFDIR | S_IRWXU | S_IRWXG);\n        eosmd->setCUid(2); // archive directory is owned by daemon\n        eosmd->setCGid(2);\n        gOFS->eosView->updateContainerStore(eosmd.get());\n      } catch (const eos::MDException& e) {\n        Eroute.Emsg(\"Config\", \"cannot set the /eos/../proc/archive directory \"\n                    \"mode to initial mode\");\n        eos_static_crit(\"cannot set the /eos/../proc/archive directory mode to 770\");\n        return 1;\n      }\n    }\n\n    // Create directory for clone functionality\n    XrdOucString clonePath(MgmProcPath + \"/clone\");\n\n    try {\n      eosmd = gOFS->eosView->getContainer(clonePath.c_str());\n    } catch (const eos::MDException& e) {\n      eosmd = nullptr;\n    }\n\n    if (!eosmd) {\n      try {\n        eosmd = gOFS->eosView->createContainer(clonePath.c_str(), true);\n        eosmd->setMode(S_IFDIR | S_IRWXU | S_IROTH | S_IXOTH | S_IRGRP | S_IXGRP);\n        eosmd->setCUid(2); // clone directory is owned by daemon\n        eosmd->setCGid(2);\n        gOFS->eosView->updateContainerStore(eosmd.get());\n      } catch (const eos::MDException& e) {\n        Eroute.Emsg(\"Config\", \"cannot set the /eos/../proc/clone directory \"\n                    \"mode to initial mode\");\n        eos_static_crit(\"cannot set the /eos/../proc/clone directory mode to 770\");\n      }\n    }\n\n    // Create workflow directory\n    try {\n      eosmd = gOFS->eosView->getContainer(MgmProcWorkflowPath.c_str());\n    } catch (const eos::MDException& e) {\n      eosmd = nullptr;\n    }\n\n    if (!eosmd) {\n      try {\n        eosmd = gOFS->eosView->createContainer(MgmProcWorkflowPath.c_str(), true);\n        eosmd->setMode(S_IFDIR | S_IRWXU);\n        eosmd->setCUid(2); // workflow directory is owned by daemon\n        gOFS->eosView->updateContainerStore(eosmd.get());\n      } catch (const eos::MDException& e) {\n        Eroute.Emsg(\"Config\",\n                    \"cannot set the /eos/../proc/workflow directory mode to initial mode\");\n        eos_static_crit(\"cannot set the /eos/../proc/workflow directory mode to 700\");\n        return 1;\n      }\n    }\n\n    // Create tracker directory\n    try {\n      eosmd = gOFS->eosView->getContainer(MgmProcTrackerPath.c_str());\n    } catch (const eos::MDException& e) {\n      eosmd = nullptr;\n    }\n\n    if (!eosmd) {\n      try {\n        eosmd = gOFS->eosView->createContainer(MgmProcTrackerPath.c_str(), true);\n        eosmd->setMode(S_IFDIR | S_IRWXU);\n        eosmd->setCUid(2); // lock directory is owned by daemon\n        gOFS->eosView->updateContainerStore(eosmd.get());\n      } catch (const eos::MDException& e) {\n        Eroute.Emsg(\"Config\", \"cannot set the /eos/../proc/creation directory mode \"\n                    \"to initial mode\");\n        eos_static_crit(\"cannot set the /eos/../proc/creation directory mode to 700\");\n        return 1;\n      }\n    }\n\n    // Create token directory\n    try {\n      eosmd = gOFS->eosView->getContainer(MgmProcTokenPath.c_str());\n    } catch (const eos::MDException& e) {\n      eosmd = nullptr;\n    }\n\n    if (!eosmd) {\n      try {\n        eosmd = gOFS->eosView->createContainer(MgmProcTokenPath.c_str(), true);\n        eosmd->setMode(S_IFDIR | S_IRWXU);\n        eosmd->setCUid(0); // token directory is owned by root\n        gOFS->eosView->updateContainerStore(eosmd.get());\n      } catch (const eos::MDException& e) {\n        Eroute.Emsg(\"Config\", \"cannot set the /eos/../proc/token directory mode \"\n                    \"to initial mode\");\n        eos_static_crit(\"cannot set the /eos/../proc/token directory mode to 700\");\n        return 1;\n      }\n    }\n\n    // Create bulkrequest-related directories\n    /*\n    for(const std::string & bulkReqDirPath : mProcDirectoryBulkRequestLocations->getAllBulkRequestDirectoriesPath()){\n      try {\n        eosmd = gOFS->eosView->getContainer(bulkReqDirPath);\n      } catch (const eos::MDException& e) {\n        eosmd = nullptr;\n      }\n\n      if (!eosmd) {\n        try {\n          eosmd = gOFS->eosView->createContainer(bulkReqDirPath, true);\n          eosmd->setMode(S_IFDIR | S_IRWXU);\n          eosmd->setCUid(2); // bulk-request directories are owned by daemon\n          eosmd->setCGid(2);\n          gOFS->eosView->updateContainerStore(eosmd.get());\n        } catch (const eos::MDException& e) {\n          {\n            std::ostringstream errorMsg;\n            errorMsg << \"cannot set the \" << bulkReqDirPath\n                     << \" directory mode to initial mode\";\n            Eroute.Emsg(\"Config\", errorMsg.str().c_str());\n          }\n          {\n            std::ostringstream errorMsg;\n            errorMsg << \"cannot set the \" << bulkReqDirPath\n                     << \" directory mode to 700\";\n            eos_static_crit(errorMsg.str().c_str());\n          }\n          return 1;\n        }\n      }\n    }\n     */\n\n    for (const std::string& bulkReqDirPath :\n         mProcDirectoryBulkRequestTapeRestApiLocations->getAllBulkRequestDirectoriesPath()) {\n      try {\n        eosmd = gOFS->eosView->getContainer(bulkReqDirPath);\n      } catch (const eos::MDException& e) {\n        eosmd = nullptr;\n      }\n\n      if (!eosmd) {\n        try {\n          eosmd = gOFS->eosView->createContainer(bulkReqDirPath, true);\n          eosmd->setMode(S_IFDIR | S_IRWXU);\n          eosmd->setCUid(2); // bulk-request directories are owned by daemon\n          eosmd->setCGid(2);\n          gOFS->eosView->updateContainerStore(eosmd.get());\n        } catch (const eos::MDException& e) {\n          {\n            std::ostringstream errorMsg;\n            errorMsg << \"cannot set the \" << bulkReqDirPath\n                     << \" directory mode to initial mode\";\n            Eroute.Emsg(\"Config\", errorMsg.str().c_str());\n          }\n          {\n            std::ostringstream errorMsg;\n            errorMsg << \"cannot set the \" << bulkReqDirPath\n                     << \" directory mode to 700\";\n            eos_static_crit(errorMsg.str().c_str());\n          }\n          return 1;\n        }\n      }\n    }\n\n    SetupProcFiles();\n  }\n\n  /*\n  // start the bulk-request proc directory cleaner\n  mBulkReqProcCleaner.reset(new bulk::BulkRequestProcCleaner(*gOFS->mProcDirectoryBulkRequestLocations,bulk::BulkRequestProcCleanerConfig::getDefaultConfig()));\n  mBulkReqProcCleaner->Start();\n   */\n  mHttpTapeRestApiBulkReqProcCleaner.reset(new bulk::BulkRequestProcCleaner(\n        *gOFS->mProcDirectoryBulkRequestTapeRestApiLocations,\n        bulk::BulkRequestProcCleanerConfig::getDefaultConfig()));\n  mHttpTapeRestApiBulkReqProcCleaner->Start();\n  // Initialize the replication tracker\n  mReplicationTracker.reset(ReplicationTracker::Create(\n                              MgmProcTrackerPath.c_str()));\n  // Configure proc path for devices\n  mDeviceTracker->SetDevicesPath(MgmProcDevicesPath.c_str());\n  // Set also the archiver ZMQ endpoint were client requests are sent\n  std::ostringstream oss;\n  oss << \"ipc://\" << MgmArchiveDir.c_str() << \"archive_frontend.ipc\";\n  mArchiveEndpoint = oss.str();\n  eos_static_info(\"%s\", \"msg=\\\"starting statistics thread\\\"\");\n  mStatsTid.reset(&Stat::Circulate, &MgmStats);\n  eos_static_info(\"%s\", \"msg=\\\"starting archive submitter thread\\\"\");\n  mSubmitterTid.reset(&XrdMgmOfs::ArchiveSubmitterThread, this);\n\n  if (!MgmRedirector) {\n    if (mErrLogEnabled) {\n      // Start ErrorReportListener and log entries in the local file\n      eos_static_info(\"%s\", \"msg=\\\"starting error logger thread\\\"\");\n\n      try {\n        mErrLoggerTid.reset(&XrdMgmOfs::ErrorLogListenerThread, this);\n      } catch (const std::system_error& e) {\n        eos_static_err(\"%s\", \"msg=\\\"failed to start error logger thread\\\"\");\n      }\n    }\n\n    eos_static_info(\"%s\", \"msg=\\\"starting fs listener thread\\\"\");\n\n    try {\n      mFsConfigTid.reset(&XrdMgmOfs::FsConfigListener, this);\n    } catch (const std::system_error& e) {\n      eos_static_crit(\"cannot start fs listener thread\");\n      NoGo = 1;\n    }\n\n    mFsMonitorTid.reset(&XrdMgmOfs::FileSystemMonitorThread, this);\n  }\n\n  if (!mHttpd) {\n    eos_static_crit(\"%s\", \"msg=\\\"failed to allocate HttpServer object\\\"\");\n    NoGo = 1;\n  }\n\n#ifdef EOS_GRPC\n\n  if (GRPCd) {\n    GRPCd->Start();\n  }\n\n  // Start gRPC server for EOS Windows native client\n  if (WNCd) {\n    WNCd->StartWnc();\n  }\n\n  // Start gRPC server for EOS HTTP REST API\n  if (mRestGrpcSrv) {\n#ifdef EOS_GRPC_GATEWAY\n    eos_static_notice(\"msg=\\\"REST GRPC service enabled\\\" port=%i\",\n                      mRestGrpcPort);\n    mRestGrpcSrv->Start();\n#else\n    mRestGrpcSrv.reset();\n    eos_static_notice(\"%s\", \"msg=\\\"REST GPRC service disabled due to lack of \"\n                      \"GRPC GATEWAY support i.e. eos-grpc-gateway\\\"\");\n#endif\n  } else {\n    eos_static_notice(\"%s\", \"msg=\\\"REST GRPC service disabled\\\"\");\n  }\n\n#endif\n  // start the Admin socket\n  {\n    std::string admin_socket_path = std::string(gOFS->MgmMetaLogDir.c_str()) +\n                                    std::string(\"/.admin_socket:\") + std::to_string(ManagerPort);\n    AdminSocketServer.reset(new eos::mgm::AdminSocket(admin_socket_path));\n  }\n  bool restApiActivated = false;\n  bool restApiStageEnabled = false;\n  {\n    eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n\n    if (FsView::gFsView.mSpaceView.find(\"default\") !=\n        FsView::gFsView.mSpaceView.end()) {\n      const std::string& restApiSwitchOnOff =\n        FsView::gFsView.mSpaceView[\"default\"]->GetConfigMember\n        (eos::mgm::rest::TAPE_REST_API_SWITCH_ON_OFF);\n\n      if (restApiSwitchOnOff == \"on\") {\n        restApiActivated = true;\n      }\n\n      const std::string& restApiStageSwitchOnOff =\n        FsView::gFsView.mSpaceView[\"default\"]->GetConfigMember(\n          eos::mgm::rest::TAPE_REST_API_STAGE_SWITCH_ON_OFF);\n\n      if (restApiStageSwitchOnOff == \"on\") {\n        restApiStageEnabled = true;\n      }\n    }\n  }\n  {\n    //Tape REST API configuration\n    auto tapeRestApiConfig = mRestApiManager->getTapeRestApiConfig();\n    tapeRestApiConfig->setTapeEnabled(mTapeEnabled);\n    tapeRestApiConfig->setActivated(restApiActivated);\n    tapeRestApiConfig->setSiteName(tapeRestApiSitename);\n    tapeRestApiConfig->setEndpointToUrlMapping(tapeRestApiEndpointUrlMap);\n    tapeRestApiConfig->setHostAlias(MgmOfsAlias.c_str());\n    tapeRestApiConfig->setXrdHttpPort(XrdHttpPort);\n    tapeRestApiConfig->setStageEnabled(restApiStageEnabled);\n  }\n  // Start the converter engine that can wait until the Master object is\n  // properly initialized.\n  eos_static_info(\"%s\", \"msg=\\\"starting converter engine\\\"\");\n  mConverterEngine.reset(new eos::mgm::ConverterEngine(mQdbContactDetails));\n\n  if (mMaster->IsMaster()) {\n    mConverterEngine->ApplyConfig();\n  }\n\n  // start the LRU daemon\n  mLRUEngine->Start();\n\n  // start the WFE daemon\n  if (!gOFS->WFEd.Start()) {\n    eos_static_warning(\"msg=\\\"cannot start WFE thread\\\"\");\n  }\n\n  // Start the device tracking thread\n  if ((mMaster->IsMaster()) && (!mDeviceTracker->Start())) {\n    eos_static_warning(\"msg=\\\"cannot start device tracking thread\\\"\");\n  }\n\n  // Start the recycler garbage collection thread on a master machine\n  if (mMaster->IsMaster()) {\n    mRecycler->Start();\n  }\n\n  // Print a test-stacktrace to ensure we have debugging symbols.\n  std::ostringstream ss;\n  ss << \"Printing a test stacktrace to check for debugging symbols: \"\n     << eos::common::getStacktrace();\n  eos_static_info(\"%s\", ss.str().c_str());\n  // add all stat entries with 0\n  InitStats();\n  // start the fuse server\n  gOFS->zMQ->gFuseServer.start();\n  const std::string iostat_file = SSTR(MgmMetaLogDir << \"/iostat.\" << ManagerId\n                                       << \".dump\");\n\n  if (!mIoStats->Init(MgmOfsInstanceName.c_str(), ManagerPort, iostat_file)) {\n    eos_static_warning(\"%s\", \"msg=\\\"failed to initialize IoStat object\\\"\");\n  } else {\n    eos_static_notice(\"%s\", \"msg=\\\"successfully initalized IoStat object\\\"\");\n  }\n\n  if (!getenv(\"EOS_NO_SHUTDOWN\")) {\n    // add shutdown handler\n    (void) signal(SIGINT, xrdmgmofs_shutdown);\n    (void) signal(SIGTERM, xrdmgmofs_shutdown);\n    (void) signal(SIGQUIT, xrdmgmofs_shutdown);\n\n    // add SEGV handler\n    if (!getenv(\"EOS_NO_STACKTRACE\")) {\n      (void) signal(SIGSEGV, xrdmgmofs_stacktrace);\n      (void) signal(SIGABRT, xrdmgmofs_stacktrace);\n      (void) signal(SIGBUS, xrdmgmofs_stacktrace);\n    }\n  }\n\n  if (getenv(\"EOS_COVERAGE_REPORT\")) {\n    // Add coverage report handler\n    (void) signal(SIGPROF, xrdmgmofs_coverage);\n  }\n\n  (void) signal(SIGUSR2, xrdmgmofs_stack);\n  (void) signal(SIGUSR1, xrdmgmofs_stack);\n\n  if (mNumAuthThreads && mFrontendPort) {\n    eos_static_info(\"starting the authentication master thread\");\n\n    try {\n      mAuthMasterTid.reset(&XrdMgmOfs::AuthMasterThread, this);\n    } catch (const std::system_error& e) {\n      eos_static_crit(\"cannot start the authentication master thread\");\n      NoGo = 1;\n    }\n\n    // @todo(esindril): this should be removed and we should use a\n    // pool of threads\n    eos_static_info(\"starting the authentication worker threads\");\n\n    for (unsigned int i = 0; i < mNumAuthThreads; ++i) {\n      pthread_t worker_tid;\n\n      if ((XrdSysThread::Run(&worker_tid, XrdMgmOfs::StartAuthWorkerThread,\n                             static_cast<void*>(this), XRDSYSTHREAD_HOLD,\n                             \"Auth Worker Thread\"))) {\n        eos_static_crit(\"msg=\\\"cannot start authentication thread num=%i\\\"\", i);\n        NoGo = 1;\n        break;\n      } else {\n        mVectTid.push_back(worker_tid);\n      }\n    }\n  }\n\n  std::this_thread::sleep_for(std::chrono::milliseconds(200));\n  // to be sure not to miss any notification while everything is starting up\n  // we don't check if it succeeds because we might fail because we timeout\n  // if there is no FST sending update\n  mGeoTreeEngine->forceRefresh();\n  mGeoTreeEngine->StartUpdater();\n  mFsScheduler->updateClusterData();\n  {\n    eos::common::RWMutexReadLock vlock(FsView::gFsView.ViewMutex);\n\n    for (const auto& space : FsView::gFsView.mSpaceView) {\n      mFsScheduler->setPlacementStrategy(space.first,\n                                         space.second->GetConfigMember(\"scheduler.type\"));\n    }\n  }\n\n  return NoGo;\n}\n/*----------------------------------------------------------------------------*/\nvoid\nXrdMgmOfs::InitStats()\n{\n  MgmStats.Add(\"HashSet\", 0, 0, 0);\n  MgmStats.Add(\"HashSetNoLock\", 0, 0, 0);\n  MgmStats.Add(\"HashGet\", 0, 0, 0);\n  MgmStats.Add(\"ViewLockR\", 0, 0, 0);\n  MgmStats.Add(\"ViewLockW\", 0, 0, 0);\n  MgmStats.Add(\"NsLockR\", 0, 0, 0);\n  MgmStats.Add(\"NsLockW\", 0, 0, 0);\n  MgmStats.Add(\"QuotaLockR\", 0, 0, 0);\n  MgmStats.Add(\"QuotaLockW\", 0, 0, 0);\n  MgmStats.Add(\"Access\", 0, 0, 0);\n  MgmStats.Add(\"AdjustReplica\", 0, 0, 0);\n  MgmStats.Add(\"AttrGet\", 0, 0, 0);\n  MgmStats.Add(\"AttrLs\", 0, 0, 0);\n  MgmStats.Add(\"AttrRm\", 0, 0, 0);\n  MgmStats.Add(\"AttrSet\", 0, 0, 0);\n  MgmStats.Add(\"BulkRequestBusiness::getBulkRequest\", 0, 0, 0);\n  MgmStats.Add(\"BulkRequestBusiness::getStageBulkRequest\", 0, 0, 0);\n  MgmStats.Add(\"BulkRequestBusiness::saveBulkRequest\", 0, 0, 0);\n  MgmStats.Add(\"Cd\", 0, 0, 0);\n  MgmStats.Add(\"Checksum\", 0, 0, 0);\n  MgmStats.Add(\"Chmod\", 0, 0, 0);\n  MgmStats.Add(\"Chown\", 0, 0, 0);\n  MgmStats.Add(\"Commit\", 0, 0, 0);\n  MgmStats.Add(\"CommitFailedFid\", 0, 0, 0);\n  MgmStats.Add(\"CommitFailedNamespace\", 0, 0, 0);\n  MgmStats.Add(\"CommitFailedParameters\", 0, 0, 0);\n  MgmStats.Add(\"CommitFailedUnlinked\", 0, 0, 0);\n  MgmStats.Add(\"ConversionJobStared\", 0, 0, 0);\n  MgmStats.Add(\"ConversionJobFailed\", 0, 0, 0);\n  MgmStats.Add(\"ConversionJobSuccessful\", 0, 0, 0);\n  MgmStats.Add(\"CopyStripe\", 0, 0, 0);\n  MgmStats.Add(\"Devices::Extract\", 0, 0, 0);\n  MgmStats.Add(\"Devices::Store\", 0, 0, 0);\n  MgmStats.Add(\"DumpMd\", 0, 0, 0);\n  MgmStats.Add(\"Drop\", 0, 0, 0);\n  MgmStats.Add(\"DropStripe\", 0, 0, 0);\n  MgmStats.Add(\"Exists\", 0, 0, 0);\n  MgmStats.Add(\"Exists\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::prot::evicted\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::prot::mount\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::prot::umount\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::prot::offline\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::prot::SET\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::prot::LS\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::prot::STAT\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::GET\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::SET\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::LS\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::LS-Entry\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::CREATE\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::UPDATE\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::MKDIR\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::RMDIR\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::RENAME\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::MV\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::DELETE\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::GETCAP\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::SETLK\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::GETLK\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::SETLKW\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::BEGINFLUSH\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::ENDFLUSH\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::CREATELNK\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::SETLNK\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::ext::DELETELNK\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::AuthRevocation\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::BcConfig\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::BcDropAll\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::BcMD\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::BcRefresh\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::BcRefreshExt\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::BcDeletion\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::BcDeletionExt\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::DeleteEntry\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::FillContainerCAP\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::FillContainerMD\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::FillFileMD\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::Heartbeat\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::MonitorCaps\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::MonitorHeartBeat\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::RefreshEntry\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::ReleaseCap\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::SendCAP\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::SendMD\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::Store\", 0, 0, 0);\n  MgmStats.Add(\"Eosxd::int::ValidatePERM\", 0, 0, 0);\n  MgmStats.Add(\"FileInfo\", 0, 0, 0);\n  MgmStats.Add(\"FindEntries\", 0, 0, 0);\n  MgmStats.Add(\"Find\", 0, 0, 0);\n  MgmStats.Add(\"FScheduler::Placement::Failed\", 0, 0, 0);\n  MgmStats.Add(\"Fuse\", 0, 0, 0);\n  MgmStats.Add(\"Fuse-Statvfs\", 0, 0, 0);\n  MgmStats.Add(\"Fuse-Mkdir\", 0, 0, 0);\n  MgmStats.Add(\"Fuse-Stat\", 0, 0, 0);\n  MgmStats.Add(\"Fuse-Chmod\", 0, 0, 0);\n  MgmStats.Add(\"Fuse-Chown\", 0, 0, 0);\n  MgmStats.Add(\"Fuse-Access\", 0, 0, 0);\n  MgmStats.Add(\"Fuse-Access\", 0, 0, 0);\n  MgmStats.Add(\"Fuse-Checksum\", 0, 0, 0);\n  MgmStats.Add(\"Fuse-XAttr\", 0, 0, 0);\n  MgmStats.Add(\"Fuse-Utimes\", 0, 0, 0);\n  MgmStats.Add(\"GetMdLocation\", 0, 0, 0);\n  MgmStats.Add(\"GetMd\", 0, 0, 0);\n  MgmStats.Add(\"GetFusex\", 0, 0, 0);\n  MgmStats.Add(\"Http-COPY\", 0, 0, 0);\n  MgmStats.Add(\"Http-DELETE\", 0, 0, 0);\n  MgmStats.Add(\"Http-GET\", 0, 0, 0);\n  MgmStats.Add(\"Http-HEAD\", 0, 0, 0);\n  MgmStats.Add(\"Http-LOCK\", 0, 0, 0);\n  MgmStats.Add(\"Http-MKCOL\", 0, 0, 0);\n  MgmStats.Add(\"Http-MOVE\", 0, 0, 0);\n  MgmStats.Add(\"Http-OPTIONS\", 0, 0, 0);\n  MgmStats.Add(\"Http-POST\", 0, 0, 0);\n  MgmStats.Add(\"Http-PROPFIND\", 0, 0, 0);\n  MgmStats.Add(\"Http-PROPPATCH\", 0, 0, 0);\n  MgmStats.Add(\"Http-PUT\", 0, 0, 0);\n  MgmStats.Add(\"Http-TRACE\", 0, 0, 0);\n  MgmStats.Add(\"Http-UNLOCK\", 0, 0, 0);\n  MgmStats.Add(\"IdMap\", 0, 0, 0);\n  MgmStats.Add(\"Ls\", 0, 0, 0);\n  MgmStats.Add(\"LRUFind\", 0, 0, 0);\n  MgmStats.Add(\"MarkDirty\", 0, 0, 0);\n  MgmStats.Add(\"MarkClean\", 0, 0, 0);\n  MgmStats.Add(\"Mkdir\", 0, 0, 0);\n  MgmStats.Add(\"Motd\", 0, 0, 0);\n  MgmStats.Add(\"MoveStripe\", 0, 0, 0);\n  MgmStats.Add(\"OpenDir\", 0, 0, 0);\n  MgmStats.Add(\"OpenDir-Entry\", 0, 0, 0);\n  MgmStats.Add(\"OpenFailedCreate\", 0, 0, 0);\n  MgmStats.Add(\"OpenFailedENOENT\", 0, 0, 0);\n  MgmStats.Add(\"OpenFailedExists\", 0, 0, 0);\n  MgmStats.Add(\"OpenFailedPermission\", 0, 0, 0);\n  MgmStats.Add(\"OpenFailedToken\", 0, 0, 0);\n  MgmStats.Add(\"OpenFailedQuota\", 0, 0, 0);\n  MgmStats.Add(\"OpenFailedNoUpdate\", 0, 0, 0);\n  MgmStats.Add(\"OpenFailedReconstruct\", 0, 0, 0);\n  MgmStats.Add(\"OpenFileOffline\", 0, 0, 0);\n  MgmStats.Add(\"OpenProc\", 0, 0, 0);\n  MgmStats.Add(\"OpenRead\", 0, 0, 0);\n  MgmStats.Add(\"OpenRedirectLocal\", 0, 0, 0);\n  MgmStats.Add(\"OpenFailedRedirectLocal\", 0, 0, 0);\n  MgmStats.Add(\"OpenShared\", 0, 0, 0);\n  MgmStats.Add(\"OpenStalled\", 0, 0, 0);\n  MgmStats.Add(\"OpenStalled\", 0, 0, 0);\n  MgmStats.Add(\"Open\", 0, 0, 0);\n  MgmStats.Add(\"OpenWriteCreate\", 0, 0, 0);\n  MgmStats.Add(\"OpenWriteTruncate\", 0, 0, 0);\n  MgmStats.Add(\"OpenWrite\", 0, 0, 0);\n  MgmStats.Add(\"Prepare\", 0, 0, 0);\n  MgmStats.Add(\"ReadLink\", 0, 0, 0);\n  MgmStats.Add(\"Recycle\", 0, 0, 0);\n  MgmStats.Add(\"ReplicaFailedSize\", 0, 0, 0);\n  MgmStats.Add(\"ReplicaFailedChecksum\", 0, 0, 0);\n  MgmStats.Add(\"Redirect\", 0, 0, 0);\n  MgmStats.Add(\"RedirectR\", 0, 0, 0);\n  MgmStats.Add(\"RedirectW\", 0, 0, 0);\n  MgmStats.Add(\"RedirectR-Master\", 0, 0, 0);\n  MgmStats.Add(\"RedirectENOENT\", 0, 0, 0);\n  MgmStats.Add(\"RedirectENONET\", 0, 0, 0);\n  MgmStats.Add(\"Rename\", 0, 0, 0);\n  MgmStats.Add(\"RmDir\", 0, 0, 0);\n  MgmStats.Add(\"Rm\", 0, 0, 0);\n  MgmStats.Add(\"DrainCentralStarted\", 0, 0, 0);\n  MgmStats.Add(\"DrainCentralSuccessful\", 0, 0, 0);\n  MgmStats.Add(\"DrainCentralFailed\", 0, 0, 0);\n  MgmStats.Add(\"QueryResync\", 0, 0, 0);\n  MgmStats.Add(\"Stall\", 0, 0, 0);\n  MgmStats.Add(\"Stat\", 0, 0, 0);\n  MgmStats.Add(\"Symlink\", 0, 0, 0);\n  MgmStats.Add(\"TapeRestApiBusiness::cancelStageBulkRequest\", 0, 0, 0);\n  MgmStats.Add(\"TapeRestApiBusiness::createStageBulkRequest\", 0, 0, 0);\n  MgmStats.Add(\"TapeRestApiBusiness::deleteStageBulkRequest\", 0, 0, 0);\n  MgmStats.Add(\"TapeRestApiBusiness::getFileInfo\", 0, 0, 0);\n  MgmStats.Add(\"TapeRestApiBusiness::getStageBulkRequest\", 0, 0, 0);\n  MgmStats.Add(\"TapeRestApiBusiness::releasePaths\", 0, 0, 0);\n  MgmStats.Add(\"Touch\", 0, 0, 0);\n  MgmStats.Add(\"TxState\", 0, 0, 0);\n  MgmStats.Add(\"Truncate\", 0, 0, 0);\n  MgmStats.Add(\"VerifyStripe\", 0, 0, 0);\n  MgmStats.Add(\"Version\", 0, 0, 0);\n  MgmStats.Add(\"Versioning\", 0, 0, 0);\n  MgmStats.Add(\"WhoAmI\", 0, 0, 0);\n}\n\n//------------------------------------------------------------------------------\n// Setup /eos/<instance>/proc files\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::SetupProcFiles()\n{\n  XrdOucString procpathwhoami = MgmProcPath;\n  procpathwhoami += \"/whoami\";\n  XrdOucString procpathwho = MgmProcPath;\n  procpathwho += \"/who\";\n  XrdOucString procpathquota = MgmProcPath;\n  procpathquota += \"/quota\";\n  XrdOucString procpathreconnect = MgmProcPath;\n  procpathreconnect += \"/reconnect\";\n  XrdOucString procpathmaster = MgmProcPath;\n  procpathmaster += \"/master\";\n  XrdOucErrInfo error;\n  eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n  std::shared_ptr<eos::IFileMD> fmd;\n  std::shared_ptr<eos::IContainerMD> cmd;\n\n  try {\n    fmd.reset();\n    fmd = eosView->getFile(procpathwhoami.c_str());\n  } catch (eos::MDException& e) {\n    fmd = eosView->createFile(procpathwhoami.c_str(), 0, 0);\n  }\n\n  if (fmd) {\n    fmd->setSize(4096);\n    fmd->setAttribute(\"sys.proc\", \"mgm.cmd=whoami&mgm.format=fuse\");\n    eosView->updateFileStore(fmd.get());\n  }\n\n  try {\n    fmd.reset();\n    fmd = eosView->getFile(procpathwho.c_str());\n  } catch (eos::MDException& e) {\n    fmd = eosView->createFile(procpathwho.c_str(), 0, 0);\n  }\n\n  if (fmd) {\n    fmd->setSize(4096);\n    fmd->setAttribute(\"sys.proc\", \"mgm.cmd=who&mgm.format=fuse\");\n    eosView->updateFileStore(fmd.get());\n  }\n\n  try {\n    fmd.reset();\n    fmd = eosView->getFile(procpathquota.c_str());\n  } catch (eos::MDException& e) {\n    fmd = eosView->createFile(procpathquota.c_str(), 0, 0);\n  }\n\n  if (fmd) {\n    fmd->setSize(4096);\n    fmd->setAttribute(\"sys.proc\",\n                      \"mgm.cmd=quota&mgm.subcmd=lsuser&mgm.format=fuse\");\n    eosView->updateFileStore(fmd.get());\n  }\n\n  try {\n    fmd.reset();\n    fmd = eosView->getFile(procpathreconnect.c_str());\n  } catch (eos::MDException& e) {\n    fmd = eosView->createFile(procpathreconnect.c_str(), 0, 0);\n  }\n\n  if (fmd) {\n    fmd->setSize(4096);\n    eosView->updateFileStore(fmd.get());\n  }\n\n  try {\n    fmd.reset();\n    fmd = eosView->getFile(procpathmaster.c_str());\n  } catch (eos::MDException& e) {\n    fmd = eosView->createFile(procpathmaster.c_str(), 0, 0);\n  }\n\n  if (fmd) {\n    fmd->setSize(4096);\n    eosView->updateFileStore(fmd.get());\n  }\n}\n"
  },
  {
    "path": "mgm/ofs/XrdMgmOfsDirectory.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdMgmOfsDirectory.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/ofs/XrdMgmOfsDirectory.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfsTrace.hh\"\n#include \"mgm/ofs/XrdMgmOfsSecurity.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/acl/Acl.hh\"\n#include \"common/Path.hh\"\n#include \"common/Strerror_r_wrapper.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"namespace/interface/ContainerIterators.hh\"\n#include <XrdOuc/XrdOucEnv.hh>\n#include \"common/Audit.hh\"\n\n#ifdef __APPLE__\n#define ECOMM 70\n#endif\n\n#ifndef S_IAMB\n#define S_IAMB  0x1FF\n#endif\n\n\neos::common::LRU::Cache<std::string, std::shared_ptr<XrdMgmOfsDirectory::listing_t>, std::mutex>\n    XrdMgmOfsDirectory::dirCache(1024, 0);\n\n//------------------------------------------------------------------------------\n//! MGM Directory Interface\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nXrdMgmOfsDirectory::XrdMgmOfsDirectory(char* user, int MonID):\n  XrdSfsDirectory(user, MonID)\n{\n  dirName = \"\";\n  vid = eos::common::VirtualIdentity::Nobody();\n  eos::common::LogId();\n}\n\n\n//------------------------------------------------------------------------------\n// Construct a key name to cache a listing entry\n//------------------------------------------------------------------------------\nstd::string\nXrdMgmOfsDirectory::getCacheName(uint64_t id, uint64_t mtime_sec,\n                                 uint64_t mtime_nsec, bool nofiles, bool nodirs)\n{\n  std::string cacheentry = std::to_string(id);\n  cacheentry += \":\";\n  cacheentry += std::to_string(mtime_sec);\n  cacheentry += \".\";\n  cacheentry += std::to_string(mtime_nsec);\n\n  if (nofiles) {\n    cacheentry += \"!f\";\n  }\n\n  if (nodirs) {\n    cacheentry += \"!d\";\n  }\n\n  return cacheentry;\n}\n\n\n//------------------------------------------------------------------------------\n// Open a directory object with bouncing/mapping & namespace mapping\n//------------------------------------------------------------------------------\nint\nXrdMgmOfsDirectory::open(const char* inpath,\n                         const XrdSecEntity* client,\n                         const char* ininfo)\n{\n  static const char* epname = \"opendir\";\n  const char* tident = error.getErrUser();\n  NAMESPACEMAP;\n  BOUNCE_ILLEGAL_NAMES;\n  XrdOucEnv Open_Env(ininfo);\n  AUTHORIZE(client, &Open_Env, AOP_Readdir, \"open directory\", inpath, error);\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, ininfo, tident, vid,\n                              gOFS->mTokenAuthz, AOP_Readdir, inpath);\n  EXEC_TIMING_END(\"IdMap\");\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  BOUNCE_NOT_ALLOWED;\n  ACCESSMODE_R;\n  MAYSTALL;\n  MAYREDIRECT;\n  return _open(path, vid, ininfo);\n}\n\n//------------------------------------------------------------------------------\n// Open a directory by vid\n//------------------------------------------------------------------------------\nint\nXrdMgmOfsDirectory::open(const char* inpath,\n                         eos::common::VirtualIdentity& vid,\n                         const char* ininfo)\n\n{\n  static const char* epname = \"opendir\";\n  NAMESPACEMAP;\n  BOUNCE_ILLEGAL_NAMES;\n  XrdOucEnv Open_Env(ininfo);\n  BOUNCE_NOT_ALLOWED;\n  ACCESSMODE_R;\n  MAYSTALL;\n  MAYREDIRECT;\n  return _open(path, vid, ininfo);\n}\n\n//------------------------------------------------------------------------------\n// Open a directory - low-level interface\n//------------------------------------------------------------------------------\nint\nXrdMgmOfsDirectory::_open(const char* dir_path,\n                          eos::common::VirtualIdentity& vid,\n                          const char* info)\n{\n  static const char* epname = \"opendir\";\n  static bool use_cache = (getenv(\"EOS_MGM_LISTING_CACHE\") &&\n                           (dirCache.setMaxSize(atoi(getenv(\"EOS_MGM_LISTING_CACHE\")))));\n  XrdOucEnv Open_Env(info);\n  errno = 0;\n\n  // Need to set the vid scope for future validation with the token path and\n  // we have to show this as a directory!\n  if (dir_path) {\n    vid.scope = dir_path;\n\n    if (vid.scope.back() != '/') {\n      vid.scope += \"/\";\n    }\n  }\n\n  EXEC_TIMING_BEGIN(\"OpenDir\");\n  eos::common::Path cPath(dir_path);\n\n  // Skip printout when listing the /eos/<instance/proc/conversion dir\n  if ((strstr(dir_path, \"/proc/conversion\") == nullptr) && (info != nullptr)) {\n    eos_info(\"name=opendir path=%s name=%s prot=%s uid=%u gid=%u token=%s\",\n             cPath.GetPath(),\n             vid.name.c_str(), vid.prot.c_str(), vid.uid, vid.gid,\n             (vid.token ? \"true\" : \"false\"));\n  }\n\n  gOFS->MgmStats.Add(\"OpenDir\", vid.uid, vid.gid, 1);\n  XrdOucEnv env(info);\n  // Open the directory\n  bool permok = false;\n  eos::Prefetcher::prefetchContainerMDWithChildrenAndWait(gOFS->eosView,\n      cPath.GetPath());\n  //----------------------------------------------------------------------------\n  std::shared_ptr<eos::IContainerMD> dh;\n  eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n  std::string cacheentry;\n\n  try {\n    eos::IContainerMD::XAttrMap attrmap;\n    dh = gOFS->eosView->getContainer(cPath.GetPath());\n    eos::IFileMD::ctime_t mtime;\n    dh->getMTime(mtime);\n    cacheentry = getCacheName(dh->getId(), mtime.tv_sec, mtime.tv_nsec,\n                              env.Get(\"ls.skip.files\"), env.Get(\"ls.skip.directories\"));\n    lock.Release();\n    permok = (!vid.token) ? dh->access(vid.uid, vid.gid, R_OK | X_OK) : false;\n    eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n    // ACL and permission check\n    Acl acl(cPath.GetPath(), error, vid, attrmap);\n    eos_debug(\"acl=%d r=%d w=%d wo=%d x=%d egroup=%d\", acl.HasAcl(),\n              acl.CanRead(), acl.CanWrite(), acl.CanWriteOnce(),\n              acl.CanBrowse(), acl.HasEgroup());\n\n    // Browse permission by ACL\n    if (acl.HasAcl()) {\n      // If there is an allow reset permissions regardless of mod permissions\n      // If there is a deny from ACLs then deny the browse!\n      if (acl.CanBrowse()) {\n        permok = true;\n      } else if (acl.CanNotBrowse()) {\n        permok = false;\n      }\n    }\n\n    if (permok) {\n      // Add all the files and subdirectories\n      gOFS->MgmStats.Add(\"OpenDir-Entry\", vid.uid, vid.gid,\n                         dh->getNumContainers() + dh->getNumFiles());\n      std::unique_lock<std::mutex> scope_lock(mDirLsMutex);\n\n      // try to get the listing from the cache\n      if (!use_cache || !dirCache.tryGet(cacheentry, dh_list)) {\n        dh_list = std::make_shared<listing_t>();\n\n        if (!env.Get(\"ls.skip.files\")) {\n          // Collect all file names\n          for (auto it = eos::FileMapIterator(dh); it.valid(); it.next()) {\n            dh_list->insert(it.key());\n          }\n        }\n\n        if (!env.Get(\"ls.skip.directories\")) {\n          // Collect all subcontainers\n          for (auto it = eos::ContainerMapIterator(dh); it.valid(); it.next()) {\n            dh_list->insert(it.key());\n          }\n\n          dh_list->insert(\".\");\n\n          // The root dir has no .. entry\n          if (strcmp(dir_path, \"/\")) {\n            dh_list->insert(\"..\");\n          }\n        }\n      }\n\n      dh_it = dh_list->begin();\n\n      if (use_cache) {\n        dirCache.insert(cacheentry, dh_list); // cache listing\n      }\n    }\n  } catch (eos::MDException& e) {\n    dh.reset();\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n              e.getErrno(), e.getMessage().str().c_str());\n  }\n\n  // Verify that this object is not already associated with an open directory\n  if (dh == nullptr) {\n    return Emsg(epname, error, errno, \"open directory\", cPath.GetPath());\n  }\n\n  eos_debug(\"msg=\\\"access\\\" uid=%d gid=%d retc=%d mode=%o token=%d\",\n            vid.uid, vid.gid, !vid.token ? (dh->access(vid.uid, vid.gid,\n                R_OK | X_OK)) : false,\n            dh->getMode(), (vid.token != nullptr));\n\n  if (!permok) {\n    errno = EPERM;\n    return Emsg(epname, error, errno,\n                \"open directory\", cPath.GetPath());\n  }\n\n  if (!gOFS->allow_public_access(cPath.GetPath(), vid)) {\n    errno = EACCES;\n    return Emsg(epname, error, EACCES, \"access - public access level restriction\",\n                cPath.GetPath());\n  }\n\n  dirName = dir_path;\n  EXEC_TIMING_END(\"OpenDir\");\n  // Emit LIST audit on successful directory open (if enabled)\n  if (gOFS->mAudit) {\n    bool allowList = false;\n    if (gOFS->mEnvAuditAttributeOnly) {\n      eos::IContainerMD::XAttrMap amap = dh->getAttributes();\n      auto it = amap.find(\"sys.audit\");\n      std::string mode = (it != amap.end()) ? it->second : std::string();\n      allowList = gOFS->AllowAuditListAttr(mode);\n    } else {\n      allowList = gOFS->AllowAuditList(dir_path);\n    }\n    if (allowList) {\n      std::string apath = dir_path ? dir_path : \"\";\n      if (!apath.empty() && apath.back() != '/') apath.push_back('/');\n      gOFS->mAudit->audit(eos::audit::LIST, apath, vid,\n                          std::string(), std::string(), \"mgm\",\n                          std::string(), nullptr, nullptr,\n                          std::string(), std::string(), std::string(),\n                          __FILE__, __LINE__, VERSION);\n    }\n  }\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Red the next directory entry\n//------------------------------------------------------------------------------\nconst char*\nXrdMgmOfsDirectory::nextEntry()\n{\n  std::unique_lock<std::mutex> scope_lock(mDirLsMutex);\n\n  if ((!dh_list) ||\n      (dh_list->empty()) ||\n      (dh_it == dh_list->end())) {\n    // No more entries\n    return (const char*) 0;\n  }\n\n  const char* name = dh_it->c_str();\n  ++dh_it;\n  return name;\n}\n\n//------------------------------------------------------------------------------\n// Close a directory object\n//------------------------------------------------------------------------------\nint\nXrdMgmOfsDirectory::close()\n{\n  std::unique_lock<std::mutex> scope_lock(mDirLsMutex);\n  dh_list = nullptr;\n  return SFS_OK;\n}\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfsDirectory::Emsg(const char* pfx,\n                         XrdOucErrInfo& einfo,\n                         int ecode,\n                         const char* op,\n                         const char* target)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief create an error message for a directory object\n *\n * @param pfx message prefix value\n * @param einfo error text/code object\n * @param ecode error code\n * @param op name of the operation performed\n * @param target target of the operation e.g. file name etc.\n *\n * @return SFS_ERROR in all cases\n *\n * This routines prints also an error message into the EOS log.\n */\n/*----------------------------------------------------------------------------*/\n{\n  char etext[128], buffer[4096];\n\n  if (ecode < 0) {\n    ecode = -ecode;\n  }\n\n  if (eos::common::strerror_r(ecode, etext, sizeof(etext))) {\n    snprintf(etext, sizeof(etext), \"reason unknown (%d)\", ecode);\n  }\n\n  // Format the error message\n  snprintf(buffer, sizeof(buffer), \"Unable to %s %s; %s\", op, target, etext);\n\n  if (ecode == ENOENT) {\n    eos_debug(\"Unable to %s %s; %s\", op, target, etext);\n  } else {\n    eos_err(\"Unable to %s %s; %s\", op, target, etext);\n  }\n\n  // Place the error message in the error object and return\n  einfo.setErrInfo(ecode, buffer);\n  return SFS_ERROR;\n}\n"
  },
  {
    "path": "mgm/ofs/XrdMgmOfsDirectory.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file XrdMgmOfsDirectory.hh\n//! @author Andreas-Joachim Peters - CERN\n//! @brief XRootD OFS plugin class implementing directory handling of EOS\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/Logging.hh\"\n#include \"common/Mapping.hh\"\n#include \"common/LRU.hh\"\n#include <XrdOuc/XrdOucErrInfo.hh>\n#include <XrdSec/XrdSecEntity.hh>\n#include <XrdSfs/XrdSfsInterface.hh>\n#include <dirent.h>\n#include <string>\n#include <set>\n#include <mutex>\n\n//! Forward declaration\nnamespace eos\n{\nclass IContainerMD;\n};\n\n//------------------------------------------------------------------------------\n//! Class implementing directories and operations\n//------------------------------------------------------------------------------\nclass XrdMgmOfsDirectory : public XrdSfsDirectory, public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  XrdMgmOfsDirectory(char* user = 0, int MonID = 0);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~XrdMgmOfsDirectory() = default;\n\n  //----------------------------------------------------------------------------\n  //! Open a directory object with bouncing/mapping & namespace mapping\n  //!\n  //! @param inpath directory path to open\n  //! @param client XRootD authentication object\n  //! @param ininfo CGI\n  //!\n  //! @return SFS_OK otherwise SFS_ERROR\n  //!\n  //! @note We create during the open the full directory listing which then is\n  //! retrieved via nextEntry() and cleaned up with close().\n  //----------------------------------------------------------------------------\n  int open(const char* dirName, const XrdSecClientName* client = 0,\n           const char* opaque = 0);\n\n  //----------------------------------------------------------------------------\n  //! Open a directory by vid\n  //----------------------------------------------------------------------------\n  int open(const char* dirName, eos::common::VirtualIdentity& vid,\n           const char* opaque = 0);\n\n  //----------------------------------------------------------------------------\n  //! Open a directory by vid\n  //----------------------------------------------------------------------------\n  int _open(const char* dirName,\n            eos::common::VirtualIdentity& vid,\n            const char* opaque = 0);\n\n  //----------------------------------------------------------------------------\n  //! @brief Read the next directory entry\n  //!\n  //! @return name of the next directory entry\n  //!\n  //! Upon success, returns the contents of the next directory entry as\n  //! a null terminated string. Returns a null pointer upon EOF or an\n  //! error. To differentiate the two cases, getErrorInfo will return\n  //! 0 upon EOF and an actual error code (i.e., not 0) on error.\n  // ---------------------------------------------------------------------------\n  const char* nextEntry();\n\n  //----------------------------------------------------------------------------\n  //! Create an error message\n  //!\n  //! @param pfx message prefix value\n  //! @param einfo error text/code object\n  //! @param ecode error code\n  //! @param op name of the operation performed\n  //! @param target target of the operation e.g. file name etc.\n  //!\n  //! @return SFS_ERROR in all cases\n  //!\n  //!This routines prints also an error message into the EOS log if it was not\n  //! due to a stat call or the error codes EIDRM or ENODATA\n  //----------------------------------------------------------------------------\n  int Emsg(const char* pfx,\n           XrdOucErrInfo& einfo,\n           int ecode,\n           const char* op,\n           const char* target = \"\");\n\n  //----------------------------------------------------------------------------\n  //! @brief Close a directory object\n  //!\n  //! return SFS_OK\n  //!---------------------------------------------------------------------------\n  int close();\n\n  // ---------------------------------------------------------------------------\n  //! return name of an open directory\n  // ---------------------------------------------------------------------------\n  const char*\n  FName()\n  {\n    return dirName.c_str();\n  }\n\n\n  typedef std::set<std::string> listing_t;\n\n  static eos::common::LRU::Cache<std::string, std::shared_ptr<listing_t>,std::mutex>\n      dirCache;\n\nprivate:\n\n  std::string getCacheName(uint64_t id, uint64_t mtime_sec, uint64_t mtime_nsec,\n                           bool nofiles, bool nodirs);\n\n  std::string dirName;\n  eos::common::VirtualIdentity vid;\n  std::shared_ptr<listing_t> dh_list;\n  listing_t::const_iterator dh_it;\n  std::mutex mDirLsMutex; ///< Mutex protecting access to dh_list\n};\n"
  },
  {
    "path": "mgm/ofs/XrdMgmOfsFile.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdMgmOfs.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Mapping.hh\"\n#include \"common/FileId.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/Path.hh\"\n#include \"common/SecEntity.hh\"\n#include \"common/StackTrace.hh\"\n#include \"common/ParseUtils.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/Strerror_r_wrapper.hh\"\n#include \"common/BehaviourConfig.hh\"\n#include \"common/Utils.hh\"\n#include \"mgm/misc/Constants.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/convert/ConversionTag.hh\"\n#include \"mgm/filesystem/FileSystem.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/ofs/XrdMgmOfsFile.hh\"\n#include \"mgm/ofs/XrdMgmOfsSecurity.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/policy/Policy.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/acl/Acl.hh\"\n#include \"mgm/workflow/Workflow.hh\"\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/tracker/ReplicationTracker.hh\"\n#include \"mgm/recycle/Recycle.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/zmq/ZMQ.hh\"\n#include \"mgm/tgc/MultiSpaceTapeGc.hh\"\n#include \"mgm/utils/AttrHelper.hh\"\n#include \"mgm/xattr/XattrLock.hh\"\n#include \"mgm/convert/ConverterEngine.hh\"\n#include \"mgm/placement/FsScheduler.hh\"\n#include \"namespace/utils/Attributes.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"namespace/Resolver.hh\"\n#include <XrdOss/XrdOss.hh>\n#include <XrdSec/XrdSecInterface.hh>\n#include <XrdSec/XrdSecEntityAttr.hh>\n#include <XrdSfs/XrdSfsAio.hh>\n#include \"common/Constants.hh\"\n#include <XrdOuc/XrdOucPgrwUtils.hh>\n#include \"mgm/ofs/XrdMgmOfsTrace.hh\"\n#include \"utils/XrdUtils.hh\"\n#include \"mgm/misc/AuditHelpers.hh\"\n\n#ifdef __APPLE__\n#define ECOMM 70\n#endif\n\n#ifndef S_IAMB\n#define S_IAMB  0x1FF\n#endif\n\nnamespace\n{\n//----------------------------------------------------------------------------\n//! Thrown if a disk location could not be found\n//----------------------------------------------------------------------------\nstruct DiskLocationNotFound: public std::runtime_error {\n  using std::runtime_error::runtime_error;\n};\n\n//----------------------------------------------------------------------------\n//! @param locations locations to be searched\n//! @return first location that is a disk as opposed to tape\n//! @throw DiskLocationNotFound if a disk location could not be found\n//----------------------------------------------------------------------------\neos::IFileMD::location_t\ngetFirstDiskLocation(const eos::IFileMD::LocationVector& locations)\n{\n  if (locations.empty()) {\n    throw DiskLocationNotFound(\"Failed to find disk location\");\n  }\n\n  if (EOS_TAPE_FSID != locations.at(0)) {\n    return locations.at(0);\n  }\n\n  if (2 > locations.size()) {\n    throw DiskLocationNotFound(\"Failed to find disk location\");\n  }\n\n  return locations.at(1);\n}\n\n//----------------------------------------------------------------------------\n//! Enforce the RainMinFsidEntry behaviour by returning the index in the\n//! given input vector corresponding to the smallest fsid.\n//!\n//! @param input_fsids vector for fsids\n//!\n//! @return index pointing to the smallest fsid\n//----------------------------------------------------------------------------\nsize_t\nEnforceRainMinFsidEntry(const std::vector<unsigned int>& input_fsids)\n{\n  unsigned int min_fsid = UINT_MAX;\n  size_t index = 0;\n\n  for (int i = 0; i < input_fsids.size(); ++i) {\n    if (input_fsids[i] < min_fsid) {\n      index = i;\n      min_fsid = input_fsids[i];\n    }\n  }\n\n  return index;\n}\n}\n\n/******************************************************************************/\n/* MGM File Interface                                                         */\n/******************************************************************************/\n\n/* copied for \"eos_static_...\" */\nstatic int\nemsg(XrdOucErrInfo& error, int ec, const char* txt, const char* txt2)\n{\n  // Get the reason for the error\n  if (ec < 0) {\n    ec = -ec;\n  }\n\n  char* etext = strerror(ec);\n  char sbuff[1024];\n  char ebuff[64];\n\n  if (etext == NULL) {\n    etext = ebuff;\n    snprintf(ebuff, sizeof(ebuff), \"error code %d\", ec);\n  }\n\n  snprintf(sbuff, sizeof(sbuff), \"create_cow: unable to %s %s: %s\", txt, txt2,\n           etext);\n  eos_static_err(sbuff);\n  error.setErrInfo(ec, sbuff);\n  return SFS_ERROR;\n}\n\n/*\n * Auxiliary routine: creates the copy-on-write clone an intermediate directories\n * cow_type:\n *      0 = copy                (for file updates, two files exist)\n *      1 = rename              (for a \"deletes\", clone's contents survive under different name)\n *      2 = hardlink            (file untouched but a new name is created, e.g. for recycle)\n *\n * returns:\n *      - error code if the clone could not be created\n *      - -1 if the file is not to be cloned\n */\n\nint\nXrdMgmOfsFile::create_cow(int cowType,\n                          std::shared_ptr<eos::IContainerMD> dmd, std::shared_ptr<eos::IFileMD> fmd,\n                          eos::common::VirtualIdentity& vid, XrdOucErrInfo& error)\n{\n  char sbuff[1024];\n  uint64_t cloneId = fmd->getCloneId();\n\n  if (cloneId == 0 or not fmd->getCloneFST().empty()) {\n    return -1;\n  }\n\n  eos_static_info(\"Creating cow clone (type %d) for %s fxid:%lx cloneId %lld\",\n                  cowType, fmd->getName().c_str(), fmd->getId(), cloneId);\n  snprintf(sbuff, sizeof(sbuff), \"%s/clone/%ld\", gOFS->MgmProcPath.c_str(),\n           cloneId);\n  std::shared_ptr<eos::IContainerMD> cloneMd, dirMd;\n\n  try {\n    cloneMd = gOFS->eosView->getContainer(sbuff);\n  } catch (eos::MDException& e) {\n    eos_static_debug(\"caught exception %d %s path %s\\n\", e.getErrno(),\n                     e.getMessage().str().c_str(), sbuff);\n    return emsg(error, ENOENT /*EEXIST*/, \"open file ()\", sbuff);\n  }\n\n  if (!dmd) {\n    return emsg(error, ENOENT, \"determine parent\", fmd->getName().c_str());\n  }\n\n  /* set up directory for clone */\n  int tlen = strlen(sbuff);\n  snprintf(sbuff + tlen, sizeof(sbuff) - tlen, \"/%lx\", dmd->getId());\n\n  try {\n    dirMd = gOFS->eosView->getContainer(sbuff);\n  } catch (eos::MDException& e) {\n    dirMd = gOFS->eosView->createContainer(sbuff, true);\n    dirMd->setMode(dmd->getMode());\n    eos::IFileMD::XAttrMap xattrs = dmd->getAttributes();\n\n    for (const auto& a : xattrs) {\n      if (a.first == \"sys.acl\" || a.first == \"user.acl\" ||\n          a.first == \"sys.eval.useracl\") {\n        dirMd->setAttribute(a.first, a.second);\n      }\n    }\n  }\n\n  /* create the clone */\n  if (cowType ==\n      XrdMgmOfsFile::cowDelete) {                   /* basically a \"mv\" */\n    dmd->removeFile(fmd->getName());\n    snprintf(sbuff, sizeof(sbuff), \"%lx\", fmd->getId());\n    fmd->setName(sbuff);\n    fmd->setCloneId(0); /* don't ever cow this again! */\n    dirMd->addFile(fmd.get());\n    gOFS->eosFileService->updateStore(fmd.get());\n  } else {                              /* cowType == cowUpdate or cowType == cowUnlink */\n    std::shared_ptr<eos::IFileMD> gmd;\n    eos::IFileMD::ctime_t ttime;\n    tlen = strlen(sbuff);\n    snprintf(sbuff + tlen, sizeof(sbuff) - tlen, \"/%lx\", fmd->getId());\n    gmd = gOFS->eosView->createFile(sbuff, vid.uid, vid.gid);\n    gmd->setAttribute(\"sys.clone.targetFid\", sbuff + tlen + 1);\n    gmd->setSize(fmd->getSize());\n\n    if (cowType ==\n        XrdMgmOfsFile::cowUpdate) {  /* prepare a \"cp --reflink\" (to be performed on the FSTs) */\n      fmd->getCTime(ttime);\n      gmd->setCTime(ttime);\n      fmd->getMTime(ttime);\n      gmd->setMTime(ttime);\n      gmd->setCUid(fmd->getCUid());\n      gmd->setCGid(fmd->getCGid());\n      gmd->setFlags(fmd->getFlags());\n      gmd->setLayoutId(fmd->getLayoutId());\n      gmd->setChecksum(fmd->getChecksum());\n      gmd->setContainerId(dirMd->getId());\n\n      for (unsigned int i = 0; i < fmd->getNumLocation(); i++) {\n        gmd->addLocation(fmd->getLocation(i));\n      }\n    } else if (cowType == cowUnlink) {\n      int nlink = (fmd->hasAttribute(SYS_NUM_LINK)) ?\n                  std::stoi(fmd->getAttribute(SYS_NUM_LINK)) + 1 : 1;\n      fmd->setAttribute(SYS_NUM_LINK, std::to_string(nlink));\n      gOFS->eosFileService->updateStore(fmd.get());\n      uint64_t hlTarget = eos::common::FileId::FidToInode(fmd->getId());\n      gmd->setAttribute(SYS_HARD_LINK, std::to_string(hlTarget));\n      eos_static_debug(\"create_cow Unlink %s (%ld) -> %s (%ld)\",\n                       gmd->getName().c_str(), gmd->getSize(),\n                       fmd->getName().c_str(), fmd->getSize());\n    }\n\n    gOFS->eosFileService->updateStore(gmd.get());\n    fmd->setCloneFST(eos::common::FileId::Fid2Hex(gmd->getId()));\n    gOFS->eosFileService->updateStore(fmd.get());\n  }\n\n  gOFS->eosDirectoryService->updateStore(dirMd.get());\n  gOFS->FuseXCastRefresh(dirMd->getIdentifier(), dirMd->getParentIdentifier());\n  gOFS->FuseXCastRefresh(cloneMd->getIdentifier(),\n                         cloneMd->getParentIdentifier());\n  return 0;\n}\n\n/*----------------------------------------------------------------------------*\n * special handling of hard links\n * returns:\n *      0 = continue deleting fmd\n *      1 = do nothing\n *\n *----------------------------------------------------------------------------*/\nint\nXrdMgmOfsFile::handleHardlinkDelete(std::shared_ptr<eos::IContainerMD> cmd,\n                                    std::shared_ptr<eos::IFileMD> fmd,\n                                    eos::common::VirtualIdentity& vid)\n{\n  if (!cmd) {\n    return 0;\n  }\n\n  long nlink =\n    -2;                /* assume this has nothing to do with hard links */\n\n  if (fmd->hasAttribute(SYS_HARD_LINK)) {\n    /* this is a hard link, decrease reference count on underlying file */\n    uint64_t hlTgt = std::stoull(fmd->getAttribute(SYS_HARD_LINK));\n    uint64_t clock;\n    /* gmd = the hard link target */\n    std::shared_ptr<eos::IFileMD> gmd = gOFS->eosFileService->getFileMD(\n                                          eos::common::FileId::InodeToFid(hlTgt), &clock);\n    nlink = std::stol(gmd->getAttribute(SYS_NUM_LINK)) - 1;\n\n    if (nlink > 0) {\n      gmd->setAttribute(SYS_NUM_LINK, std::to_string(nlink));\n    } else {\n      gmd->removeAttribute(SYS_NUM_LINK);\n    }\n\n    gOFS->eosFileService->updateStore(gmd.get());\n    eos_static_info(\"hlnk update target %s for %s nlink %ld\",\n                    gmd->getName().c_str(), fmd->getName().c_str(), nlink);\n\n    if (nlink <= 0) {\n      if (gmd->getName().substr(0, 13) == \"...eos.ino...\") {\n        eos_static_info(\"hlnk unlink target %s for %s nlink %ld\",\n                        gmd->getName().c_str(), fmd->getName().c_str(), nlink);\n        uint64_t cloneId = gmd->getCloneId();\n\n        if (cloneId != 0 and\n            gmd->getCloneFST().empty()) {     /* this file needs to be cloned */\n          XrdOucErrInfo error;\n          std::shared_ptr<eos::IContainerMD> dmd;\n\n          try {\n            dmd = gOFS->eosDirectoryService->getContainerMD(gmd->getContainerId());\n          } catch (eos::MDException& e) {\n          }\n\n          XrdMgmOfsFile::create_cow(XrdMgmOfsFile::cowDelete, dmd, gmd, vid, error);\n          return 1;\n        } else {                /* delete hard link target */\n          cmd->removeFile(gmd->getName());\n          gmd->unlinkAllLocations();\n          gmd->setContainerId(0);\n        }\n\n        gOFS->eosFileService->updateStore(gmd.get());\n      }\n    }\n  } else if (fmd->hasAttribute(SYS_NUM_LINK)) {\n    // a hard link target\n    nlink = std::stol(fmd->getAttribute(SYS_NUM_LINK));\n    eos_static_info(\"hlnk rm target nlink %ld\", nlink);\n\n    if (nlink > 0) {\n      // hard links exist, just rename the file so the inode does not disappear\n      char nameBuf[1024];\n      uint64_t ino = eos::common::FileId::FidToInode(fmd->getId());\n      snprintf(nameBuf, sizeof(nameBuf), \"...eos.ino...%lx\", ino);\n      std::string nameBufs(nameBuf);\n      fmd->setAttribute(SYS_NUM_LINK, std::to_string(nlink));\n      eos_static_info(\"hlnk unlink rename %s=>%s new nlink %d\",\n                      fmd->getName().c_str(), nameBufs.c_str(), nlink);\n      cmd->removeFile(nameBufs);            // if the target exists, remove it!\n      gOFS->eosView->renameFile(fmd.get(), nameBufs);\n      return 1;\n    }\n\n    /* no other links exist, continue deleting the target like a simple file */\n  }\n\n  eos_static_debug(\"hard link nlink %ld, delete %s\", nlink,\n                   fmd->getName().c_str());\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Get the application name if specified\n//------------------------------------------------------------------------------\nconst std::string\nXrdMgmOfsFile::GetClientApplicationName(XrdOucEnv* open_opaque,\n                                        const XrdSecEntity* client)\n{\n  // Application name derived from the following in order of priority:\n  // 1. eos.app=<tag>\n  // 2. XRD_APPNAME=<tag> env variable or -DSAppName for xrdcp commands\n  const std::string eos_tag = \"eos.app\";\n  const std::string xrd_tag = \"xrd.appname\";\n  std::string app_name;\n  const char* val = nullptr;\n\n  if (open_opaque && (val = open_opaque->Get(eos_tag.c_str()))) {\n    app_name = val;\n  } else {\n    if (client) {\n      if (!client->eaAPI->Get(xrd_tag, app_name)) {\n        app_name.clear();\n      }\n    }\n  }\n\n  return app_name;\n}\n\n//------------------------------------------------------------------------------\n// Get POSIX open flags from the given XRootD open mode\n//------------------------------------------------------------------------------\nint\nXrdMgmOfsFile::GetPosixOpenFlags(XrdSfsFileOpenMode open_mode)\n{\n  int open_flags = 0;\n\n  if (open_mode & SFS_O_CREAT) {\n    open_mode = SFS_O_CREAT;\n  } else if (open_mode & SFS_O_TRUNC) {\n    open_mode = SFS_O_TRUNC;\n  }\n\n  switch (open_mode & (SFS_O_RDONLY | SFS_O_WRONLY | SFS_O_RDWR |\n                       SFS_O_CREAT | SFS_O_TRUNC)) {\n  case SFS_O_CREAT:\n    open_flags = O_CREAT | O_EXCL | O_RDWR;\n    break;\n\n  case SFS_O_TRUNC:\n    open_flags = O_CREAT | O_TRUNC | O_RDWR;\n    break;\n\n  case SFS_O_RDONLY:\n    open_flags = O_RDONLY;\n    break;\n\n  case SFS_O_WRONLY:\n    open_flags = O_WRONLY;\n    break;\n\n  case SFS_O_RDWR:\n    open_flags = O_RDWR;\n    break;\n\n  default:\n    open_flags = O_RDONLY;\n    break;\n  }\n\n  return open_flags;\n}\n\n//------------------------------------------------------------------------------\n// Get XRootD acceess operation bases on the given open flags\n//------------------------------------------------------------------------------\nAccess_Operation\nXrdMgmOfsFile::GetXrdAccessOperation(int open_flags)\n{\n  Access_Operation op;\n\n  if (open_flags & O_CREAT) {\n    op = AOP_Create;\n  } else {\n    if (open_flags == O_RDONLY) {\n      op = AOP_Read;\n    } else {\n      op = AOP_Update;\n    }\n  }\n\n  return op;\n}\n\n\n\nXrdMgmOfsFile::targetParams\nXrdMgmOfsFile::setProxyFwEntrypoint(const std::vector<std::string>& firewalleps,\n                                    const std::vector<std::string>& proxys,\n                                    size_t fsIndex,\n                                    std::string_view fs_hostport,\n                                    std::string_view fs_prefix)\n{\n  bool hasFirewall = fsIndex < firewalleps.size() &&\n                     !firewalleps[fsIndex].empty();\n  bool hasProxy = fsIndex < proxys.size() && !proxys[fsIndex].empty();\n\n  if (!hasFirewall && !hasProxy) {\n    return {};\n  }\n\n  targetParams out;\n\n  // Set the FST gateway for clients who are geo-tagged with default\n  // Do this with forwarding proxy syntax only if the firewall entrypoint is\n  // different from the endpoint\n  if (hasFirewall &&\n      ((hasProxy && firewalleps[fsIndex] != proxys[fsIndex]) ||\n       (firewalleps[fsIndex] != fs_hostport))) {\n    // Build the URL for the forwarding proxy and must have the following\n    // redirection proxy:port?eos.fstfrw=endpoint:port/abspath\n    if (auto idx = firewalleps[fsIndex].rfind(':');\n        idx != std::string::npos) {\n      out.targethost = firewalleps[fsIndex].substr(0, idx);\n      out.targetport = atoi(firewalleps[fsIndex].substr(idx + 1,\n                            std::string::npos).c_str());\n      out.targethttpport = 8001;\n    } else {\n      out.targethost = firewalleps[fsIndex].c_str();\n      out.targetport = 0;\n      out.targethttpport = 8001;\n    }\n\n    out.redirectionhost = out.targethost + \":\" +\n                          std::to_string(out.targetport) + \"?eos.fstfrw=\";\n\n    if (hasProxy) {\n      out.redirectionhost += proxys[fsIndex];\n    } else {\n      out.redirectionhost += fs_hostport;\n    }\n  } else if (hasProxy) {\n    if (auto idx = proxys[fsIndex].rfind(':');\n        idx != std::string::npos) {\n      out.targethost = proxys[fsIndex].substr(0, idx);\n      out.targetport = atoi(proxys[fsIndex].substr(idx + 1,\n                            std::string::npos).c_str());\n      out.targethttpport = 8001;\n    } else {\n      out.targethost = proxys[fsIndex];\n      out.targetport = 0;\n      out.targethttpport = 0;\n    }\n\n    out.redirectionhost = out.targethost;\n  }\n\n  if (hasProxy && !fs_prefix.empty()) {\n    std::string _s = \"mgm.fsprefix=\";\n    _s.append(fs_prefix);\n    eos::common::replace_all(_s, \":\", \"#COL#\");\n    out.redirectionhost += _s;\n    out.redirectionsuffix = std::move(_s);\n  }\n\n  return out;\n}\n\n/*----------------------------------------------------------------------------*/\n#include \"proto/Audit.pb.h\"\n#include \"namespace/utils/Checksum.hh\"\n/*\n * @brief open a given file with the indicated mode\n *\n * @param inpath path to open\n * @param open_mode SFS_O_RDONLY,SFS_O_WRONLY,SFS_O_RDWR,SFS_O_CREAT,SFS_TRUNC\n * @param Mode posix access mode bits to be assigned\n * @param client XRootD authentication object\n * @param ininfo CGI\n * @return SFS_OK on succes, otherwise SFS_ERROR on error or redirection\n *\n * Mode may also contain SFS_O_MKPATH if one desires to automatically create\n * all missing directories for a file (if possible).\n *\n */\n/*----------------------------------------------------------------------------*/\nusing eos::common::XrdUtils;\nint\nXrdMgmOfsFile::open(eos::common::VirtualIdentity* invid,\n                    const char* inpath,\n                    XrdSfsFileOpenMode open_mode,\n                    mode_t Mode,\n                    const XrdSecEntity* client,\n                    const char* ininfo)\n{\n  // For audit of truncation: capture before/after\n  bool auditTruncate = false;\n  eos::audit::Stat truncBefore;\n  std::string truncPath;\n  using eos::common::LayoutId;\n  static const char* epname = \"open\";\n  const char* tident = error.getErrUser();\n  eos::IFileMD::XAttrMap attrmapF;\n  errno = 0;\n  eos::common::Timing tm(\"Open\");\n  COMMONTIMING(\"begin\", &tm);\n  EXEC_TIMING_BEGIN(\"Open\");\n  XrdOucString spath = inpath;\n  XrdOucString sinfo = ininfo;\n  SetLogId(logId, tident);\n  int open_flags = GetPosixOpenFlags(open_mode);\n  bool isRW = ((open_flags == O_RDONLY) ? false : true);\n  bool isRewrite = ((open_flags & O_CREAT) ? false : true);\n  Access_Operation acc_op = GetXrdAccessOperation(open_flags);\n  {\n    EXEC_TIMING_BEGIN(\"IdMap\");\n    std::string validation_path = spath.c_str();\n\n    if (spath.beginswith(\"/zteos64:\")) {\n      sinfo += \"&authz=\";\n      sinfo += spath.c_str() + 1;\n      ininfo = sinfo.c_str();\n      validation_path = \"\";\n    }\n\n    if (ProcInterface::IsProcAccess(spath.c_str())) {\n      validation_path = \"\";\n    }\n\n    if (spath == \"/fusex-open\") {\n      validation_path = \"\";\n    }\n\n    if (!invid) {\n      eos::common::Mapping::IdMap(client, ininfo, tident, vid,\n                                  gOFS->mTokenAuthz, acc_op,  validation_path.c_str());\n    } else {\n      vid = *invid;\n    }\n\n    EXEC_TIMING_END(\"IdMap\");\n  }\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  COMMONTIMING(\"IdMap\", &tm);\n  SetLogId(logId, vid, tident);\n  NAMESPACEMAP;\n  BOUNCE_ILLEGAL_NAMES;\n  BOUNCE_NOT_ALLOWED;\n  spath = path;\n  COMMONTIMING(\"Bounce\", &tm);\n\n  if (!spath.beginswith(\"/proc/\") && spath.endswith(\"/\")) {\n    return Emsg(epname, error, EISDIR,\n                \"open - you specified a directory as target file name\", path);\n  }\n\n  bool isCreation = false;\n  // flag indicating parallel IO access\n  bool isPio = false;\n  // flag indicating access with reconstruction\n  bool isPioReconstruct = false;\n  // flag indicating FUSE file access\n  bool isFuse = false;\n  // flag indiciating an atomic upload where a file get's a hidden unique name and is renamed when it is closed\n  bool isAtomicUpload = false;\n  // flag indicating an atomic file name\n  bool isAtomicName = false;\n  // flag indicating a new injection - upload of a file into a stub without physical location\n  bool isInjection = false;\n  // flag indicating to drop the current disk replica in the policy space\n  bool isRepair = false;\n  // flag indicating a read for repair (meaningfull only on the FST)\n  bool isRepairRead = false;\n  // flag indicationg a TPC action\n  bool isTpc = false;\n  // flag indicating a file touch\n  bool isTouch = false;\n  // chunk upload ID\n  XrdOucString ocUploadUuid = \"\";\n  // Set of filesystem IDs to reconstruct\n  std::set<unsigned int> pio_reconstruct_fs;\n  // list of filesystem IDs usable for replacement of RAIN file\n  std::vector<unsigned int> pio_replacement_fs;\n  // tried hosts CGI\n  std::string tried_cgi;\n  // versioning CGI\n  std::string versioning_cgi;\n  // file size\n  uint64_t fmdsize = 0;\n  // io priority string\n  std::string ioPriority;\n  XrdOucString pinfo = (ininfo ? ininfo : \"\");\n  eos::common::StringConversion::MaskTag(pinfo, \"cap.msg\");\n  eos::common::StringConversion::MaskTag(pinfo, \"cap.sym\");\n  eos::common::StringConversion::MaskTag(pinfo, \"authz\");\n\n  if (isRW) {\n    eos_info(\"op=write trunc=%d path=%s info=%s\",\n             open_mode & SFS_O_TRUNC, path, pinfo.c_str());\n  } else {\n    eos_info(\"op=read path=%s info=%s\", path, pinfo.c_str());\n  }\n\n  ACCESSMODE_R;\n\n  if (isRW) {\n    SET_ACCESSMODE_W;\n  }\n\n  if (ProcInterface::IsProcAccess(path)) {\n    if (ProcInterface::IsWriteAccess(path, pinfo.c_str())) {\n      SET_ACCESSMODE_W;\n    }\n  } else {\n    if (getenv(\"EOS_HA_REDIRECT_READS\")) {\n      SET_ACCESSMODE_R_MASTER;\n    }\n  }\n\n  MAYSTALL;\n  MAYREDIRECT;\n  std::string currentWorkflow = \"default\";\n  unsigned long long byfid = 0;\n  unsigned long long bypid = 0;\n  COMMONTIMING(\"fid::fetch\", &tm);\n\n  /* check paths starting with fid: fxid: ino: ... */\n  if (spath.beginswith(\"fid:\") || spath.beginswith(\"fxid:\") ||\n      spath.beginswith(\"ino:\")) {\n    WAIT_BOOT;\n    // reference by fid+fsid\n    byfid = eos::Resolver::retrieveFileIdentifier(spath).getUnderlyingUInt64();\n\n    try {\n      eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, byfid);\n      eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n      fmd = gOFS->eosFileService->getFileMD(byfid);\n      spath = gOFS->eosView->getUri(fmd.get()).c_str();\n      bypid = fmd->getContainerId();\n      eos_info(\"msg=\\\"access by inode\\\" fxid=%08llx ino=%s path=%s\", byfid, path,\n               spath.c_str());\n      path = spath.c_str();\n    } catch (eos::MDException& e) {\n      eos_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                e.getMessage().str().c_str());\n      MAYREDIRECT_ENOENT;\n      MAYSTALL_ENOENT;\n      return Emsg(epname, error, ENOENT,\n                  \"open - you specified a not existing inode number\", path);\n    }\n  }\n\n  COMMONTIMING(\"fid::fetched\", &tm);\n  openOpaque = new XrdOucEnv(ininfo);\n\n  // Handle (delegated) tpc redirection for writes\n  if (isRW && RedirectTpcAccess()) {\n    return SFS_REDIRECT;\n  }\n\n  std::string app_name = GetClientApplicationName(openOpaque, client);\n\n  // Decide if this is a FUSE access\n  if (!app_name.empty()) {\n    if (app_name == \"fuse\" || app_name == \"xrootdfs\" ||\n        app_name.find(\"fuse::\") == 0) {\n      isFuse = true;\n      vid.app = app_name;\n    } else if (app_name == \"touch\") {\n      isTouch = true;\n      isFuse = true;\n    }\n  } else {\n    // App name is empty, check for the presence of oss.task\n    // as XRootD's XrdTpcTPC can set this opaque information\n    // to indicate that the open() is due to an HTTP TPC\n    // transfer\n    const char* ossTask = nullptr;\n\n    if ((ossTask = openOpaque->Get(\"oss.task\"))) {\n      // We have oss.task opaque, check if it is equal to httptpc\n      // (set by XrdTpcTPC)\n      if (!strncmp(\"httptpc\", ossTask, 7)) {\n        if (isRW) {\n          // Open for write --> HTTP TPC PULL\n          app_name += \"http/tpcpull\";\n        } else {\n          // HTTP TPC PUSH\n          app_name += \"http/tpcpush\";\n        }\n      }\n    }\n  }\n\n\n    // handle io priority\n  if (auto ioprio = XrdUtils::GetEnv(*openOpaque, \"eos.iopriority\");\n      !ioprio.empty()) {\n    // only operator/admins can set iopriority\n    if (vid.hasUid(11)) {\n      ioPriority = std::move(ioprio);\n    } else {\n      eos_info(\"msg=\\\"suppressing IO priority setting '%s', no operator role\\\"\",\n               ioprio.c_str());\n    }\n  }\n  // handle various values from CGI\n  XrdUtils::GetEnv(*openOpaque, \"eos.obfuscate\", mEosObfuscate, -1);\n  mEosKey = XrdUtils::GetEnv(*openOpaque, \"eos.key\");\n  if (!mEosKey.empty()) {\n    if (mEosObfuscate == 0) {\n      mEosObfuscate = 1;\n    }\n  }\n  // Populate ocuploaduuid from openopaque\n  if (auto _ocuuid = XrdUtils::GetEnv(*openOpaque, \"oc-chunk-uuid\");\n      !_ocuuid.empty()) {\n      ocUploadUuid = _ocuuid.c_str();\n  }\n\n  if (auto _tried = XrdUtils::GetEnv(*openOpaque, \"tried\");\n      !_tried.empty()) {\n      tried_cgi = _tried + \",\";\n  }\n\n  currentWorkflow = XrdUtils::GetEnv(*openOpaque, \"eos.workflow\", \"default\");\n  versioning_cgi = XrdUtils::GetEnv(*openOpaque, \"eos.versioning\");\n  isTpc = openOpaque->Get(\"tpc.stage\") != nullptr;\n\n  if (!isFuse && isRW) {\n    // resolve symbolic links\n    try {\n      std::string s_path = path;\n      spath = gOFS->eosView->getRealPath(s_path).c_str();\n      eos_info(\"msg=\\\"rewrote symlinks\\\" sym-path=%s realpath=%s\", s_path.c_str(),\n               spath.c_str());\n      path = spath.c_str();\n    } catch (eos::MDException& e) {\n      eos_debug(\"caught exception %d %s\\n\",\n                e.getErrno(),\n                e.getMessage().str().c_str());\n      // will throw the error later\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  // PIO MODE CONFIGURATION\n  // ---------------------------------------------------------------------------\n  // PIO mode return's a vector of URLs to a client and the client contact's\n  // directly these machines and run's the RAIN codec on client side.\n  // The default mode return's one gateway machine and this machine run's the\n  // RAIN codec.\n  // On the fly reconstruction is done using PIO mode when the reconstruction\n  // action is defined ('eos.pio.action=reconstruct'). The client can specify\n  // a list of filesystem's which should be excluded. In case they are used\n  // in the layout the stripes on the explicitly referenced filesystems and\n  // all other unavailable filesystems get reconstructed into stripes on\n  // new machines.\n  // ---------------------------------------------------------------------------\n  // ---------------------------------------------------------------------------\n  // discover PIO mode\n  // ---------------------------------------------------------------------------\n  isPio = XrdUtils::GetEnv(*openOpaque, \"eos.cli.access\") == \"pio\";\n  isPioReconstruct = XrdUtils::GetEnv(*openOpaque, \"eos.pio.action\") == \"reconstruct\";\n\n  {\n    // Discover PIO reconstruction filesystems (stripes to be replaced)\n    std::string sPioRecoverFs = XrdUtils::GetEnv(*openOpaque, \"eos.pio.recfs\");\n\n    std::vector<std::string> fsToken;\n    eos::common::StringConversion::Tokenize(sPioRecoverFs, fsToken, \",\");\n\n    if (!sPioRecoverFs.empty() && fsToken.empty()) {\n      return Emsg(epname, error, EINVAL, \"open - you specified a list of\"\n                  \" reconstruction filesystems but the list is empty\", path);\n    }\n\n    for (size_t i = 0; i < fsToken.size(); i++) {\n      errno = 0;\n      unsigned int rfs = (unsigned int) strtol(fsToken[i].c_str(), 0, 10);\n      XrdOucString srfs = \"\";\n      srfs += (int) rfs;\n\n      if (errno || (srfs != fsToken[i].c_str())) {\n        return Emsg(epname, error, EINVAL, \"open - you specified a list of \"\n                    \"reconstruction filesystems but the list contains non \"\n                    \"numerical or illegal id's\", path);\n      }\n\n      // store in the reconstruction filesystem list\n      pio_reconstruct_fs.insert(rfs);\n    }\n  }\n\n  int rcode = SFS_ERROR;\n  XrdOucString redirectionhost = \"invalid?\";\n  std::string targethost;\n  int targetport = atoi(gOFS->MgmOfsTargetPort.c_str());\n  int targethttpport = gOFS->mHttpdPort;\n  int ecode = 0;\n  unsigned long fmdlid = 0;\n  unsigned long long cid = 0;\n  eos::IFileMD::LocationVector vect_loc;\n\n  // Proc filter\n  if (ProcInterface::IsProcAccess(path)) {\n    if (gOFS->mExtAuthz &&\n        (vid.prot != \"sss\") &&\n        (vid.prot != \"gsi\") &&\n        (vid.prot != \"krb5\") &&\n        (vid.host != \"localhost\") &&\n        (vid.host != \"localhost.localdomain\")) {\n      return Emsg(epname, error, EPERM, \"execute proc command - you don't have\"\n                  \" the requested permissions for that operation (1)\", path);\n    }\n\n    gOFS->MgmStats.Add(\"OpenProc\", vid.uid, vid.gid, 1, vid.app);\n\n    if (!ProcInterface::Authorize(path, ininfo, vid, client)) {\n      return Emsg(epname, error, EPERM, \"execute proc command - you don't have \"\n                  \"the requested permissions for that operation (2)\", path);\n    } else {\n      mProcCmd = ProcInterface::GetProcCommand(tident, vid, path, ininfo, logId);\n\n      if (mProcCmd) {\n        eos::common::Timing tm(\"ProcCmd\");\n        COMMONTIMING(\"begin\", &tm);\n\n        eos_info(\"proccmd=%s\", mProcCmd->GetCmd(ininfo).c_str());\n        mProcCmd->SetLogId(logId, vid, tident);\n        mProcCmd->SetError(&error);\n\n        rcode = mProcCmd->open(path, ininfo, vid, &error);\n        COMMONTIMING(\"file::open\", &tm);\n        // If we need to stall the client then save the IProcCommand object and\n        // add it to the map for when the client comes back.\n        if (rcode > 0) {\n          if (mProcCmd->GetCmd(ininfo) != \"proto\") {\n            COMMONTIMING(\"client::stall\", &tm);\n            eos_debug(\"proccmd=%s %s stall=%d\", mProcCmd->GetCmd(ininfo).c_str(),\n                     tm.Dump().c_str(), rcode);\n            return rcode;\n          }\n\n          if (!ProcInterface::SaveSubmittedCmd(tident, std::move(mProcCmd))) {\n            eos_err(\"failed to save submitted command object\");\n            return Emsg(epname, error, EINVAL, \"save sumitted command object \"\n                        \"for path \", path);\n          }\n\n          // Now the mProcCmd object is null and moved to the global map\n        }\n        COMMONTIMING(\"end\", &tm);\n\n        eos_debug(\"proccmd=%s %s rcode=%d\",\n                 mProcCmd ? mProcCmd->GetCmd(ininfo).c_str() : \"moved\", tm.Dump().c_str(),\n                 rcode);\n        return rcode;\n      } else {\n        return Emsg(epname, error, ENOMEM, \"allocate proc command object for \",\n                    path);\n      }\n    }\n  }\n\n  gOFS->MgmStats.Add(\"Open\", vid.uid, vid.gid, 1, vid.app);\n  bool dotFxid = spath.beginswith(\"/.fxid:\");\n\n  if (dotFxid) {\n    byfid = eos::Resolver::retrieveFileIdentifier(spath).getUnderlyingUInt64();\n\n    try {\n      eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, byfid);\n      eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n      fmd = gOFS->eosFileService->getFileMD(byfid);\n      spath = gOFS->eosView->getUri(fmd.get()).c_str();\n      bypid = fmd->getContainerId();\n      eos_info(\"msg=\\\"access by inode\\\" fxid=%08llx ino=%s path=%s\", byfid, path,\n               spath.c_str());\n      path = spath.c_str();\n    } catch (eos::MDException& e) {\n      eos_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                e.getMessage().str().c_str());\n      return Emsg(epname, error, ENOENT,\n                  \"open - you specified a not existing fxid\", path);\n    }\n  }\n\n  COMMONTIMING(\"authorize\", &tm);\n  AUTHORIZE(client, openOpaque, acc_op,\n            ((acc_op == AOP_Create) ? \"create\" : \"open\"), inpath, error);\n  COMMONTIMING(\"authorized\", &tm);\n  eos::common::Path cPath(path);\n  // indicate the scope for a possible token\n  TOKEN_SCOPE;\n  isAtomicName = cPath.isAtomicFile();\n\n  // prevent any access to a recycling bin for writes\n  if (isRW && cPath.GetFullPath().beginswith(Recycle::gRecyclingPrefix.c_str())) {\n    return Emsg(epname, error, EPERM,\n                \"open file - nobody can write to a recycling bin\",\n                cPath.GetParentPath());\n  }\n\n  std::shared_ptr<eos::IContainerMD> dmd;\n\n  // check if we have to create the full path\n  if (Mode & SFS_O_MKPTH) {\n    eos_debug(\"%s\", \"msg=\\\"SFS_O_MKPTH was requested\\\"\");\n    XrdSfsFileExistence file_exists;\n    std::shared_ptr<eos::IFileMD> _fmd;\n    int ec = gOFS->_exists(cPath.GetParentPath(), file_exists,\n                           error, vid, dmd, _fmd, 0);\n\n    // check if that is a file\n    if ((!ec) && (file_exists != XrdSfsFileExistNo) &&\n        (file_exists != XrdSfsFileExistIsDirectory)) {\n      return Emsg(epname, error, ENOTDIR,\n                  \"open file - parent path is not a directory\",\n                  cPath.GetParentPath());\n    }\n\n    // if it does not exist try to create the path!\n    if ((!ec) && (file_exists == XrdSfsFileExistNo)) {\n      ec = gOFS->_mkdir(cPath.GetParentPath(), Mode, error, vid, ininfo);\n\n      if (ec) {\n        gOFS->MgmStats.Add(\"OpenFailedPermission\", vid.uid, vid.gid, 1, vid.app);\n        return SFS_ERROR;\n      }\n    }\n  }\n\n  bool isSharedFile = gOFS->VerifySharePath(path, openOpaque);\n\n  if (gOFS->is_squashfs_access(path, vid)) {\n    isSharedFile = true;\n  }\n\n  COMMONTIMING(\"path-computed\", &tm);\n  // Get the directory meta data if it exists\n  eos::IContainerMD::XAttrMap attrmap;\n  Acl acl;\n  Workflow workflow;\n  bool stdpermcheck = false;\n  int versioning = 0;\n  uid_t d_uid = vid.uid;\n  gid_t d_gid = vid.gid;\n  std::string creation_path = path;\n  {\n    // This is probably one of the hottest code paths in the MGM, we definitely\n    // want prefetching here.\n    if (!byfid) {\n      if (!(open_flags & O_EXCL)) {\n        // if we want to create, why would we prefetch file md?\n        eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, cPath.GetPath());\n      } else {\n        eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView,\n            cPath.GetParentPath());\n      }\n    }\n\n    eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n\n    try {\n      if (byfid) {\n        dmd = gOFS->eosDirectoryService->getContainerMD(bypid);\n      } else if (!dmd) {\n        dmd = gOFS->eosView->getContainer(cPath.GetParentPath());\n      }\n\n      // get the attributes out\n      gOFS->listAttributes(gOFS->eosView, dmd.get(), attrmap, false);\n      // extract workflows\n      workflow.Init(&attrmap);\n\n      if (dmd) {\n        try {\n          std::string filePath = cPath.GetPath();\n          std::string fileName = cPath.GetName();\n\n          if (ocUploadUuid.length()) {\n            eos::common::Path aPath(cPath.GetAtomicPath(attrmap.count(\"sys.versioning\"),\n                                    ocUploadUuid));\n            filePath = aPath.GetPath();\n            fileName = aPath.GetName();\n          }\n\n          if ((fmd = dmd->findFile(fileName))) {\n            /* in case of a hard link, may need to switch to target */\n            /* A hard link to another file */\n            if (fmd->hasAttribute(SYS_HARD_LINK)) {\n              std::shared_ptr<eos::IFileMD> gmd;\n              uint64_t mdino;\n\n              if (eos::common::StringToNumeric(fmd->getAttribute(SYS_HARD_LINK),\n                                               mdino)) {\n                gmd = gOFS->eosFileService->getFileMD(\n                        eos::common::FileId::InodeToFid(mdino));\n                eos_info(\"hlnk switched from %s (%#lx) to file %s (%#lx)\",\n                         fmd->getName().c_str(), fmd->getId(),\n                         gmd->getName().c_str(), gmd->getId());\n                fmd = gmd;\n              } else {\n                //Conversion from string to inode number failed, log the error and return an error to the client\n                return Emsg(epname, error, ENOENT,\n                            \"convert the inode extended attribute to a number\", path);\n              }\n            }\n\n            if (fmd->isLink()) {\n              // we have to get it by path\n              fmd = gOFS->eosView->getFile(filePath);\n            }\n\n            uint64_t dmd_id = fmd->getContainerId();\n            byfid = fmd->getId();\n\n            // If fmd is resolved via a symbolic link, we have to find the\n            // 'real' parent directory\n            if (dmd_id != dmd->getId()) {\n              // retrieve the 'real' parent\n              try {\n                dmd = gOFS->eosDirectoryService->getContainerMD(dmd_id);\n              } catch (const eos::MDException& e) {\n                // this looks like corruption, but will return in ENOENT for the parent\n                dmd.reset();\n                errno = ENOENT;\n              }\n            }\n\n            // check for O_EXCL here to save some time\n            if (open_flags & O_EXCL) {\n              gOFS->MgmStats.Add(\"OpenFailedExists\", vid.uid, vid.gid, 1, vid.app);\n              return Emsg(epname, error, EEXIST, \"create file - (O_EXCL)\", path);\n            }\n          }\n        } catch (eos::MDException& e) {\n          fmd.reset();\n        }\n\n        if (!fmd) {\n          if (dmd && dmd->findContainer(cPath.GetName())) {\n            errno = EISDIR;\n          } else {\n            errno = ENOENT;\n          }\n        } else {\n          mFid = fmd->getId();\n          fmdlid = fmd->getLayoutId();\n          vect_loc = fmd->getLocations();\n          cid = fmd->getContainerId();\n          fmdsize = fmd->getSize();\n        }\n\n        if (dmd) {\n          d_uid = dmd->getCUid();\n          d_gid = dmd->getCGid();\n        }\n      } else {\n        fmd.reset();\n      }\n    } catch (eos::MDException& e) {\n      dmd.reset();\n      errno = e.getErrno();\n      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                e.getErrno(), e.getMessage().str().c_str());\n    };\n\n    COMMONTIMING(\"container::fetched\", &tm);\n\n    // Check permissions\n    if (!dmd) {\n      int save_errno = errno;\n      MAYREDIRECT_ENOENT;\n\n      if (cPath.GetSubPath(2)) {\n        eos_info(\"info=\\\"checking l2 path\\\" path=%s\", cPath.GetSubPath(2));\n\n        // Check if we have a redirection setting at level 2 in the namespace\n        try {\n          dmd = gOFS->eosView->getContainer(cPath.GetSubPath(2));\n          // get the attributes out\n          gOFS->listAttributes(gOFS->eosView, dmd.get(), attrmap, false);\n        } catch (eos::MDException& e) {\n          dmd.reset();\n          errno = e.getErrno();\n          eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=%s\\n\",\n                    e.getErrno(), e.getMessage().str().c_str());\n        }\n\n        if (std::string _redir;\n            attr::getValue(attrmap, \"sys.redirect.enoent\", _redir) && !_redir.empty()) {\n          // there is a redirection setting here\n          redirectionhost = \"\";\n          redirectionhost = _redir.c_str();\n          int portpos = 0;\n\n          if ((portpos = redirectionhost.find(\":\")) != STR_NPOS) {\n            XrdOucString port = redirectionhost;\n            port.erase(0, portpos + 1);\n            ecode = atoi(port.c_str());\n            redirectionhost.erase(portpos);\n          } else {\n            ecode = 1094;\n          }\n\n          if (!gOFS->SetRedirectionInfo(error, redirectionhost.c_str(), ecode)) {\n            eos_err(\"msg=\\\"failed setting redirection\\\" path=\\\"%s\\\"\", path);\n            return SFS_ERROR;\n          }\n\n          rcode = SFS_REDIRECT;\n          gOFS->MgmStats.Add(\"RedirectENOENT\", vid.uid, vid.gid, 1, vid.app);\n          XrdOucString predirectionhost = redirectionhost.c_str();\n          eos::common::StringConversion::MaskTag(predirectionhost, \"cap.msg\");\n          eos::common::StringConversion::MaskTag(predirectionhost, \"cap.sym\");\n          eos::common::StringConversion::MaskTag(pinfo, \"authz\");\n          eos_info(\"info=\\\"redirecting\\\" hostport=%s:%d\", predirectionhost.c_str(),\n                   ecode);\n          return rcode;\n        }\n      }\n\n      // put back original errno\n      errno = save_errno;\n      gOFS->MgmStats.Add(\"OpenFailedENOENT\", vid.uid, vid.gid, 1, vid.app);\n      return Emsg(epname, error, errno, \"open file\", path);\n    }\n\n    bool sticky_owner;\n    attr::checkDirOwner(attrmap, d_uid, d_gid, vid, sticky_owner, path);\n\n    // -------------------------------------------------------------------------\n    // ACL and permission check\n    // -------------------------------------------------------------------------\n    if (dotFxid and (not vid.sudoer) and (vid.uid != 0)) {\n      /* restricted: this could allow access to a file hidden by the hierarchy */\n      eos_debug(\".fxid=%d uid %d sudoer %d\", dotFxid, vid.uid, vid.sudoer);\n      errno = EPERM;\n      return Emsg(epname, error, errno, \"open file - open by fxid denied\", path);\n    }\n\n    if (fmd) {\n      gOFS->listAttributes(gOFS->eosView, fmd.get(), attrmapF, false);\n    }\n\n    acl.SetFromAttrMap(attrmap, vid, &attrmapF);\n    eos_info(\"acl=%d r=%d w=%d wo=%d egroup=%d shared=%d mutable=%d facl=%d\",\n             acl.HasAcl(), acl.CanRead(), acl.CanWrite(), acl.CanWriteOnce(),\n             acl.HasEgroup(), isSharedFile, acl.IsMutable(),\n             acl.EvalUserAttrFile());\n\n    if (acl.HasAcl() && (vid.uid != 0)) {\n      if ((vid.uid != 0) && (!vid.sudoer) &&\n          (isRW ? (acl.CanNotWrite() && acl.CanNotUpdate()) : acl.CanNotRead())) {\n        eos_debug(\"uid %d sudoer %d isRW %d CanNotRead %d CanNotWrite %d CanNotUpdate %d\",\n                  vid.uid, vid.sudoer, isRW, acl.CanNotRead(), acl.CanNotWrite(),\n                  acl.CanNotUpdate());\n        errno = EPERM;\n        gOFS->MgmStats.Add(\"OpenFailedPermission\", vid.uid, vid.gid, 1, vid.app);\n        return Emsg(epname, error, errno, \"open file - forbidden by ACL\", path);\n      }\n\n      if (isRW) {\n        // Update case - unless SFS_O_TRUNC is specified then this is a normal write\n        if (fmd && ((open_flags & O_TRUNC) == 0)) {\n          eos_debug(\"CanUpdate %d CanNotUpdate %d stdpermcheck %d file uid/gid = %d/%d\",\n                    acl.CanUpdate(), acl.CanNotUpdate(), stdpermcheck, fmd->getCUid(),\n                    fmd->getCGid());\n\n          if (acl.CanNotUpdate() || (acl.CanNotWrite() && !acl.CanUpdate())) {\n            // the ACL has !u set - we don't allow to do file updates\n            gOFS->MgmStats.Add(\"OpenFailedNoUpdate\", vid.uid, vid.gid, 1, vid.app);\n            return Emsg(epname, error, EPERM, \"update file - fobidden by ACL\",\n                        path);\n          }\n\n          stdpermcheck = (!acl.CanUpdate());\n        } else {\n          // Write case\n          if (!(acl.CanWrite() || acl.CanWriteOnce())) {\n            // We have to check the standard permissions\n            stdpermcheck = true;\n          }\n        }\n      } else {\n        // read case\n        if (!acl.CanRead()) {\n          // we have to check the standard permissions\n          stdpermcheck = true;\n        }\n      }\n    } else {\n      stdpermcheck = true;\n    }\n\n    if (isRW && !acl.IsMutable() && vid.uid && !vid.sudoer) {\n      // immutable directory\n      errno = EPERM;\n      gOFS->MgmStats.Add(\"OpenFailedPermission\", vid.uid, vid.gid, 1, vid.app);\n      return Emsg(epname, error, errno, \"open file - directory immutable\", path);\n    }\n\n    // check publicaccess level\n    if (!gOFS->allow_public_access(path, vid)) {\n      return Emsg(epname, error, EACCES, \"access - public access level restriction\",\n                  path);\n    }\n\n    int taccess = -1;\n\n    if ((!isSharedFile || isRW) && stdpermcheck) {\n      // when tokens are used, UNIX permissions are disabled\n      if (vid.token && vid.uid) {\n        errno = EPERM;\n        gOFS->MgmStats.Add(\"OpenFailedPermission\", vid.uid, vid.gid, 1, vid.app);\n        return Emsg(epname, error, errno, \"open file\", path);\n      }\n\n      if (!(taccess = dmd->access(vid.uid, vid.gid,\n                                  (isRW) ? W_OK | X_OK : R_OK | X_OK))) {\n        eos_debug(\"fCUid %d dCUid %d uid %d isSharedFile %d isRW %d stdpermcheck %d access %d\",\n                  fmd ? fmd->getCUid() : 0, dmd->getCUid(), vid.uid, isSharedFile, isRW,\n                  stdpermcheck, taccess);\n\n        if (!((vid.uid == DAEMONUID) && (isPioReconstruct))) {\n          // we don't apply this permission check for reconstruction jobs issued via the daemon account\n          errno = EPERM;\n          gOFS->MgmStats.Add(\"OpenFailedPermission\", vid.uid, vid.gid, 1);\n          return Emsg(epname, error, errno, \"open file\", path);\n        }\n      }\n    }\n\n    if (sticky_owner) {\n      eos_info(\"msg=\\\"client acting as directory owner\\\" path=\\\"%s\\\" uid=\\\"%u=>%u\\\" gid=\\\"%u=>%u\\\"\",\n               path, vid.uid, vid.gid, d_uid, d_gid);\n      vid.uid = d_uid;\n      vid.gid = d_gid;\n    }\n\n    // If a file has the sys.proc attribute, it will be redirected as a command\n    if (fmd != nullptr && fmd->hasAttribute(\"sys.proc\")) {\n      ns_rd_lock.Release();\n      return open(\"/proc/user/\", open_mode, Mode, client,\n                  fmd->getAttribute(\"sys.proc\").c_str());\n    }\n  }\n  // check for versioning depth, cgi overrides sys & user attributes\n  versioning = attr::getVersioning(attrmap, versioning_cgi);\n  // check for atomic uploads only in non fuse clients\n  isAtomicUpload = !isFuse &&\n                   attr::checkAtomicUpload(attrmap, openOpaque->Get(\"eos.atomic\"));\n  // check for injection in non fuse clients with cgi\n  isInjection = !isFuse && openOpaque->Get(\"eos.injection\") != nullptr;\n  isRepair = openOpaque->Get(\"eos.repair\") != nullptr;\n  isRepairRead = openOpaque->Get(\"eos.repairread\") != nullptr;\n\n  // Short-cut to block multi-source access to EC files\n  if (IsRainRetryWithExclusion(isRW, fmdlid)) {\n    return Emsg(epname, error, ENETUNREACH,  \"open file - \"\n                \"multi-source reading on EC file blocked for \", path);\n  }\n\n  // ---------------------------------------------------------------------------\n  // attribute lock logic, don't allow file opens which have an attr lock\n  // ---------------------------------------------------------------------------\n  XattrLock alock(attrmapF);\n\n  if (alock.foreignLock(vid, isRW)) {\n    return Emsg(epname, error, EBUSY,\n                \"open file - file has a valid extended attribute lock \", path);\n  }\n\n  if (isRW) {\n    if (fmd && Policy::HasUpdConversion(attrmap) && (vid.prot != \"https\") &&\n        !isInjection && !isTpc && !isRepair) {\n      // Get space that file belongs to\n      std::string space_name = FsView::gFsView.GetSpaceNameForFses(mFid, vect_loc);\n      std::string source_space = (space_name.empty() ? \"default\" : space_name);\n      std::string target_space;\n      unsigned long target_layout;\n      auto conversion = Policy::UpdateConversion(path, attrmap, vid, fmdlid,\n                        source_space, *openOpaque,\n                        target_layout, target_space);\n\n      if (conversion == Policy::eFail) {\n        return Emsg(epname, error, EINVAL, \"open file for update - invalid \"\n                    \"update conversion policy found!\", path);\n      }\n\n      if (conversion == Policy::eAsync) {\n        error.setErrCode(0);\n        auto conversionCb = std::make_shared<XrdOucCallBack>();\n        conversionCb->Init(&error);\n        error.setErrInfo(1800, \"delay client up to 30 minutes for update conversion\");\n        auto conversiontag = ConversionTag::Get(\n                               byfid, target_space, target_layout, std::string(\"\"), true);\n        std::string err_msg;\n\n        if (gOFS->mConverterEngine->ScheduleJob(byfid, conversiontag,\n                                                err_msg, conversionCb)) {\n          eos_info(\"msg=\\\"update conversion started\\\" fxid=%08llx conv=\\\"%s\\\"\",\n                   byfid, conversiontag.c_str());\n          return SFS_STARTED;\n        } else {\n          // remove the callback from the error object\n          error.setErrCB(0);\n          error.setErrArg(0);\n          error.setErrInfo(60, \"please retry after 60 seconds for update conversion\");\n          eos_info(\"msg=\\\"stalling client for update conversion\\\" fxid=%08llx \"\n                   \"conv=\\\"%s\\\"\", byfid, conversiontag.c_str());\n          return SFS_STALL;\n        }\n      } else {\n        // no conversion to be run\n      }\n    }\n\n    // Allow updates of 0-size RAIN files so that we are able to write from the\n    // FUSE mount with lazy-open mode enabled.\n    if (!getenv(\"EOS_ALLOW_RAIN_RWM\") && isRewrite && (vid.uid > 3) &&\n        (fmdsize != 0) && (LayoutId::IsRain(fmdlid))) {\n      // Unpriviledged users are not allowed to open RAIN files for update\n      gOFS->MgmStats.Add(\"OpenFailedNoUpdate\", vid.uid, vid.gid, 1, vid.app);\n      return Emsg(epname, error, EPERM, \"update RAIN layout file - \"\n                  \"you have to be a priviledged user for updates\");\n    }\n\n    if (!isInjection && (open_flags & O_TRUNC) && fmd) {\n      // Capture state before truncation\n      {\n        eos::IFileMD::ctime_t cts, mts;\n        fmd->getCTime(cts);\n        fmd->getMTime(mts);\n        truncBefore.set_ctime(cts.tv_sec);\n        truncBefore.set_mtime(mts.tv_sec);\n        truncBefore.set_uid(fmd->getCUid());\n        truncBefore.set_gid(fmd->getCGid());\n        uint32_t m = (fmd->getFlags() & 07777);\n        truncBefore.set_mode(m);\n        char mo[8];\n        snprintf(mo, sizeof(mo), \"0%04o\", m);\n        truncBefore.set_mode_octal(mo);\n        truncPath = path;\n        auditTruncate = true;\n      }\n\n      // check if this directory is write-once for the mapped user\n      if (acl.HasAcl()) {\n        if (acl.CanWriteOnce()) {\n          gOFS->MgmStats.Add(\"OpenFailedNoUpdate\", vid.uid, vid.gid, 1, vid.app);\n          // this is a write once user\n          return Emsg(epname, error, EEXIST,\n                      \"overwrite existing file - you are write-once user\");\n        } else {\n          if ((!stdpermcheck) && (!acl.CanWrite())) {\n            return Emsg(epname, error, EPERM,\n                        \"overwrite existing file - you have no write permission\");\n          }\n        }\n      }\n\n      if (versioning) {\n        if (isAtomicUpload) {\n          // atomic uploads need just to purge version to max-1, the version is created on commit\n          // purge might return an error if the file was not yet existing/versioned\n          gOFS->PurgeVersion(cPath.GetVersionDirectory(), error, versioning - 1);\n          errno = 0;\n        } else {\n          // handle the versioning for a specific file ID\n          if (gOFS->Version(mFid, error, vid, versioning)) {\n            return Emsg(epname, error, errno, \"version file\", path);\n          }\n        }\n      } else {\n        // drop the old file (for non atomic uploads) and create a new truncated one\n        if ((!isAtomicUpload) && gOFS->_rem(path, error, vid, ininfo, false, false)) {\n          return Emsg(epname, error, errno, \"remove file for truncation\", path);\n        }\n      }\n\n      if (!ocUploadUuid.length()) {\n        fmd.reset();\n      } else {\n        eos_info(\"%s\", \"msg=\\\"keep attached to existing fmd in chunked upload\\\"\");\n      }\n\n      gOFS->MgmStats.Add(\"OpenWriteTruncate\", vid.uid, vid.gid, 1, vid.app);\n    } else {\n      if (isInjection && !fmd) {\n        errno = ENOENT;\n        return Emsg(epname, error, errno, \"inject into a non-existing file\", path);\n      }\n\n      if (!fmd && ((open_flags & O_CREAT))) {\n        gOFS->MgmStats.Add(\"OpenWriteCreate\", vid.uid, vid.gid, 1, vid.app);\n      } else {\n        if (acl.HasAcl()) {\n          if (acl.CanWriteOnce()) {\n            // this is a write once user\n            return Emsg(epname, error, EEXIST,\n                        \"overwrite existing file - you are write-once user\");\n          } else {\n            if ((!stdpermcheck) && (!acl.CanWrite()) && (!acl.CanUpdate())) {\n              return Emsg(epname, error, EPERM,\n                          \"overwrite existing file - you have no write permission\");\n            }\n          }\n        }\n\n        gOFS->MgmStats.Add(\"OpenWrite\", vid.uid, vid.gid, 1, vid.app);\n\n        // Emit UPDATE audit for opening existing file for update (non-create, non-truncate)\n        if (gOFS->mAudit) {\n          eos::audit::Stat beforeStat;\n          eos::mgm::auditutil::buildStatFromFileMD(fmd,\n              beforeStat, /*includeSize=*/true, /*includeChecksum=*/true, /*includeNs=*/true);\n          gOFS->mAudit->audit(eos::audit::UPDATE, path, vid, logId, cident, \"mgm\",\n                              std::string(), &beforeStat, nullptr);\n        }\n      }\n    }\n\n    // -------------------------------------------------------------------------\n    // write case\n    // -------------------------------------------------------------------------\n    if (!fmd) {\n      if (!(open_flags & O_CREAT)) {\n        // Open for write for non existing file without creation flag\n        return Emsg(epname, error, ENOENT, \"open file without creation flag\", path);\n      } else {\n        // creation of a new file or isOcUpload\n        COMMONTIMING(\"write::begin\", &tm);\n        {\n          // -------------------------------------------------------------------\n          std::shared_ptr<eos::IFileMD> ref_fmd;\n          eos::common::RWMutexWriteLock ns_wr_lock(gOFS->eosViewRWMutex);\n\n          try {\n            // we create files with the uid/gid of the parent directory\n            if (isAtomicUpload) {\n              creation_path = cPath.GetAtomicPath(versioning, ocUploadUuid);\n              eos_info(\"atomic-path=%s\", creation_path.c_str());\n\n              try {\n                ref_fmd = gOFS->eosView->getFile(path);\n              } catch (eos::MDException& e) {\n                // empty\n              }\n            }\n\n            // Avoid any race condition when opening for creation O_EXCL\n            if (open_flags & O_EXCL) {\n              if (isAtomicUpload) {\n                try {\n                  fmd = gOFS->eosView->getFile(creation_path);\n                } catch (eos::MDException& e1) {\n                  // empty\n                }\n              }\n\n              if (fmd) {\n                gOFS->MgmStats.Add(\"OpenFailedExists\", vid.uid, vid.gid, 1, vid.app);\n                return Emsg(epname, error, EEXIST, \"create file - (O_EXCL)\", path);\n              }\n            }\n\n            {\n              // a faster replacement for createFile view view\n              auto file = gOFS->eosFileService->createFile(0);\n\n              if (!file) {\n                eos_static_crit(\"File creation failed for %s\", creation_path.c_str());\n                throw_mdexception(EIO, \"File creation failed\");\n              }\n\n              eos::common::Path cPath(creation_path.c_str());\n              std::string fileName = cPath.GetName();\n              file->setName(fileName);\n              file->setCUid(vid.uid);\n              file->setCGid(vid.gid);\n              file->setCTimeNow();\n              file->setATimeNow(0);\n              file->setMTimeNow();\n              file->clearChecksum(0);\n              dmd->addFile(file.get());\n              fmd = file;\n            }\n\n            // Emit CREATE or TRUNCATE audit after creation\n            if (gOFS->mAudit) {\n              eos::audit::Stat afterStat;\n              eos::mgm::auditutil::buildStatFromFileMD(fmd,\n                  afterStat, /*includeSize=*/false, /*includeChecksum=*/false, /*includeNs=*/true);\n\n              if (auditTruncate) {\n                gOFS->mAudit->audit(eos::audit::TRUNCATE,\n                                    truncPath.empty() ? path : truncPath,\n                                    vid, logId, cident, \"mgm\",\n                                    std::string(), &truncBefore, &afterStat);\n              } else {\n                gOFS->mAudit->audit(eos::audit::CREATE,\n                                    creation_path.c_str(),\n                                    vid, logId, cident, \"mgm\",\n                                    std::string(), nullptr, &afterStat);\n              }\n            }\n\n            if ((mEosObfuscate > 0) ||\n                (attrmap.count(\"sys.file.obfuscate\") &&\n                 (attrmap[\"sys.file.obfuscate\"] == \"1\"))) {\n              std::string skey = eos::common::SymKey::RandomCipher(mEosKey);\n              // attach an obfucation key\n              fmd->setAttribute(\"user.obfuscate.key\", skey);\n\n              if (mEosKey.length()) {\n                fmd->setAttribute(\"user.encrypted\", \"1\");\n              }\n\n              attrmapF[\"user.obfuscate.key\"] = skey;\n            }\n\n            if (ocUploadUuid.length()) {\n              fmd->setFlags(0);\n            } else {\n              fmd->setFlags(Mode & (S_IRWXU | S_IRWXG | S_IRWXO));\n            }\n\n            // For versions copy xattrs over from the original file\n            if (versioning) {\n              static std::set<std::string> skip_tag {\"sys.eos.btime\", \"sys.fs.tracking\", eos::common::EOS_DTRACE_ATTR, eos::common::EOS_VTRACE_ATTR, \"sys.tmp.atomic\"};\n\n              for (const auto& xattr : attrmapF) {\n                if (skip_tag.find(xattr.first) == skip_tag.end()) {\n                  fmd->setAttribute(xattr.first, xattr.second);\n                }\n              }\n            }\n\n            fmd->setAttribute(\"sys.utrace\", logId);\n            fmd->setAttribute(\"sys.vtrace\", vid.getTrace());\n\n            if (ref_fmd) {\n              // If we have a target file we tag the latest atomic upload name\n              // on a temporary attribute\n              ref_fmd->setAttribute(\"sys.tmp.atomic\", fmd->getName());\n\n              if (acl.EvalUserAttrFile()) {\n                // we inherit existing ACLs during (atomic) versioning\n                ref_fmd->setAttribute(\"user.acl\", acl.UserAttrFile());\n                ref_fmd->setAttribute(\"sys.eval.useracl\", \"1\");\n              }\n            }\n\n            mFid = fmd->getId();\n            fmdlid = fmd->getLayoutId();\n            // oc chunks start with flags=0\n            cid = fmd->getContainerId();\n            auto cmd = dmd; // we have this already\n            cmd->setMTimeNow();\n            eos::ContainerIdentifier cmd_id = cmd->getIdentifier();\n            eos::ContainerIdentifier cmd_pid = cmd->getParentIdentifier();\n            gOFS->mReplicationTracker->Create(fmd);\n            ns_wr_lock.Release();\n            cmd->notifyMTimeChange(gOFS->eosDirectoryService);\n            gOFS->eosView->updateContainerStore(cmd.get());\n            gOFS->eosView->updateFileStore(fmd.get());\n\n            if (ref_fmd) {\n              gOFS->eosView->updateFileStore(ref_fmd.get());\n            }\n\n            gOFS->FuseXCastRefresh(cmd_id, cmd_pid);\n          } catch (eos::MDException& e) {\n            fmd.reset();\n            errno = e.getErrno();\n            eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                      e.getErrno(), e.getMessage().str().c_str());\n          };\n\n          // -------------------------------------------------------------------\n        } // end ns_lock\n        COMMONTIMING(\"write::end\", &tm);\n\n        if (!fmd) {\n          // creation failed\n          gOFS->MgmStats.Add(\"OpenFailedCreate\", vid.uid, vid.gid, 1, vid.app);\n          return Emsg(epname, error, errno, \"create file\", path);\n        }\n\n        isCreation = true;\n        // -------------------------------------------------------------------------\n      }\n    } else {\n      // we attached to an existing file\n      if (open_flags & O_EXCL) {\n        gOFS->MgmStats.Add(\"OpenFailedExists\", vid.uid, vid.gid, 1, vid.app);\n        return Emsg(epname, error, EEXIST, \"create file (O_EXCL)\", path);\n      }\n    }\n  } else {\n    if (!fmd) {\n      // check if there is a redirect or stall for missing entries\n      MAYREDIRECT_ENOENT;\n      MAYSTALL_ENOENT;\n\n      if (auto redirect_kv = attrmap.find(\"sys.redirect.enoent\");\n          redirect_kv != attrmap.end()) {\n        // there is a redirection setting here\n        redirectionhost = \"\";\n        redirectionhost = redirect_kv->second.c_str();\n        int portpos = 0;\n\n        if ((portpos = redirectionhost.find(\":\")) != STR_NPOS) {\n          XrdOucString port = redirectionhost;\n          port.erase(0, portpos + 1);\n          ecode = atoi(port.c_str());\n          redirectionhost.erase(portpos);\n        } else {\n          ecode = 1094;\n        }\n\n        if (!gOFS->SetRedirectionInfo(error, redirectionhost.c_str(), ecode)) {\n          eos_err(\"msg=\\\"failed setting redirection\\\" path=\\\"%s\\\"\", path);\n          return SFS_ERROR;\n        }\n\n        rcode = SFS_REDIRECT;\n        gOFS->MgmStats.Add(\"RedirectENOENT\", vid.uid, vid.gid, 1, vid.app);\n        return rcode;\n      }\n\n      gOFS->MgmStats.Add(\"OpenFailedENOENT\", vid.uid, vid.gid, 1, vid.app);\n      return Emsg(epname, error, errno, \"open file\", path);\n    }\n\n    eos_static_debug(\"has-read-conversion: %d\", Policy::HasReadConversion(attrmap));\n\n    if (Policy::HasReadConversion(attrmap) && (vid.prot != \"https\") &&\n        !isTpc && !isRepair && !isRepairRead && !isPio && !isPioReconstruct) {\n      std::string space_name = FsView::gFsView.GetSpaceNameForFses(mFid, vect_loc);\n      std::string source_space = (space_name.empty() ? \"default\" : space_name);\n      std::string target_space;\n      unsigned long target_layout;\n      auto conversion = Policy::ReadConversion(path, attrmap, vid, fmdlid,\n                        source_space, *openOpaque,\n                        target_layout, target_space);\n\n      if (conversion == Policy::eFail) {\n        return Emsg(\n                 epname, error, EINVAL,\n                 \"open file for read - invalid read conversion policy found!\", path);\n      }\n\n      if (conversion == Policy::eAsync) {\n        error.setErrCode(0);\n        auto conversionCb = std::make_shared<XrdOucCallBack>();\n        conversionCb->Init(&error);\n        error.setErrInfo(1800, \"delay client up to 30 minutes for read conversion\");\n        auto conversiontag = ConversionTag::Get(\n                               byfid, target_space, target_layout, std::string(\"\"), true);\n        std::string err_msg;\n\n        if (gOFS->mConverterEngine->ScheduleJob(byfid, conversiontag,\n                                                err_msg, conversionCb)) {\n          eos_info(\"msg=\\\"read conversion started\\\" fxid=%08llx conv=\\\"%s\\\"\",\n                   byfid, conversiontag.c_str());\n          return SFS_STARTED;\n        } else {\n          // remove the callback from the error object\n          error.setErrCB(0);\n          error.setErrArg(0);\n          error.setErrInfo(60, \"please retry after 60 seconds for read conversion\");\n          eos_info(\"msg=\\\"stalling client for read conversion\\\" fxid=%08llx \"\n                   \"conv=\\\"%s\\\"\", byfid, conversiontag.c_str());\n          return SFS_STALL;\n        }\n      } else {\n        // no conversion to be run\n      }\n    }\n\n    if (isSharedFile) {\n      gOFS->MgmStats.Add(\"OpenShared\", vid.uid, vid.gid, 1, vid.app);\n    } else {\n      gOFS->MgmStats.Add(\"OpenRead\", vid.uid, vid.gid, 1, vid.app);\n    }\n\n    // possibly apply an access conversion policy\n    gOFS->mReplicationTracker->Access(fmd);\n  }\n\n  // ---------------------------------------------------------------------------\n  // flush synchronization logic, don't open a file which is currently flushing\n  // ---------------------------------------------------------------------------\n  if (gOFS->zMQ->gFuseServer.Flushs().hasFlush(eos::common::FileId::FidToInode(\n        mFid))) {\n    // the first 255ms are covered inside hasFlush, otherwise we stall clients for a sec\n    return gOFS->Stall(error, vid, 1, \"file is currently being flushed\");\n  }\n\n  // ---------------------------------------------------------------------------\n  // construct capability\n  // ---------------------------------------------------------------------------\n  XrdOucString capability = \"\";\n\n  if (gOFS->mTapeEnabled) {\n    capability += \"&tapeenabled=1\";\n  }\n\n  if (isPioReconstruct) {\n    capability += \"&mgm.access=update\";\n  } else {\n    if (isRW) {\n      if (isRewrite) {\n        capability += \"&mgm.access=update\";\n      } else {\n        capability += \"&mgm.access=create\";\n      }\n\n      uint64_t cloneId;\n\n      if (fmd && (cloneId = fmd->getCloneId()) != 0) {\n        char sbuff[1024];\n        std::string cloneFST = fmd->getCloneFST();\n\n        if (cloneFST == \"\") {      /* This triggers the copy-on-write */\n          if (int rc = create_cow(cowUpdate, dmd, fmd, vid, error)) {\n            return rc;\n          }\n        }\n\n        eos_debug(\"file %s cloneid %ld cloneFST %s trunc %d\", path, fmd->getCloneId(),\n                  fmd->getCloneFST().c_str(), open_flags & O_TRUNC);\n        snprintf(sbuff, sizeof(sbuff), \"&mgm.cloneid=%ld&mgm.cloneFST=%s\", cloneId,\n                 fmd->getCloneFST().c_str());\n        capability += sbuff;\n      }\n    } else {\n      capability += \"&mgm.access=read\";\n    }\n  }\n\n  if (mEosObfuscate && !isFuse) {\n    // add obfuscation key to redirection capability\n    if (attrmapF.count(\"user.obfuscate.key\")) {\n      capability += \"&mgm.obfuscate.key=\";\n      capability += attrmapF[\"user.obfuscate.key\"].c_str();\n    }\n\n    // add encryption key to redirection capability\n    if (mEosKey.length()) {\n      capability += \"&mgm.encryption.key=\";\n      capability += mEosKey.c_str();\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  // forward some allowed user opaque tags\n  // ---------------------------------------------------------------------------\n  unsigned long layoutId = (isCreation) ? LayoutId::kPlain : fmdlid;\n  // the client can force to read a file on a defined file system\n  unsigned long forcedFsId = 0;\n  // the client can force to place a file in a specified group of a space\n  long forced_group = -1;\n  // this is the filesystem defining the client access point in the selection\n  // vector - for writes it is always 0, for reads it comes out of the\n  // FileAccess function\n  unsigned long fsIndex = 0;\n  std::string space = \"default\";\n  unsigned long new_lid = 0;\n  eos::mgm::Scheduler::tPlctPolicy plctplcy;\n  std::string targetgeotag;\n  std::string bandwidth;\n  std::string ioprio;\n  std::string iotype;\n  bool schedule = false;\n  uint64_t atimeage = 0;\n  std::vector<std::string> altChecksums;\n  bool computeAltXs = false;\n  // select space and layout according to policies\n  COMMONTIMING(\"Policy::begin\", &tm);\n  Policy::GetLayoutAndSpace(path, attrmap, vid, new_lid, space, *openOpaque,\n                            forcedFsId, forced_group, bandwidth, schedule,\n                            ioprio, iotype, isRW, true, &atimeage, &altChecksums, &computeAltXs);\n  COMMONTIMING(\"Policy::end\", &tm);\n  Policy::RedirectStatus rs;\n\n  // do a local redirect here if there is only one replica attached and this is not an HTTPS request\n  if (vid.prot != \"https\" && !isRW && !isFuse && !isPio &&\n      (fmd->getNumLocation() == 1) &&\n      (rs = Policy::RedirectLocal(path, attrmap, vid, layoutId, space,\n                                  *openOpaque)) != Policy::eNever) {\n    XrdCl::URL url(std::string(\"root://localhost//\") + std::string(\n                     path ? path : \"/dummy/\") + std::string(\"?\") + std::string(\n                     ininfo ? ininfo : \"\"));\n    std::string localhost = \"localhost\";\n\n    if (gOFS->Tried(url, localhost, \"*\")) {\n      gOFS->MgmStats.Add(\"OpenFailedRedirectLocal\", vid.uid, vid.gid, 1, vid.app);\n      eos_info(\"msg=\\\"local-redirect disabled - forwarding to FST\\\" path=\\\"%s\\\" info=\\\"%s\\\"\",\n               path, ininfo);\n    } else {\n      eos::common::FileSystem::fs_snapshot_t local_snapshot;\n      unsigned int local_id = fmd->getLocation(0);\n      {\n        eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n        eos::mgm::FileSystem* local_fs = FsView::gFsView.mIdView.lookupByID(local_id);\n        local_fs->SnapShotFileSystem(local_snapshot);\n        eos_info(\"sharedfs='%s'\", local_fs->getSharedFs().c_str());\n      }\n      std::string anchor = \"#\";\n      std::string appanchor = app_name;\n      auto hpos = app_name.find(\"#\");\n\n      if (hpos != std::string::npos) {\n        appanchor = app_name.substr(hpos);\n      }\n\n      anchor += local_snapshot.mSharedFs;\n      eos_debug(\"app-anchor='%s' anchor='%s' redirect-policy=%d\", appanchor.c_str(),\n                anchor.c_str(), rs);\n\n      if ((rs == Policy::eAlways) ||\n          ((rs == Policy::eOptional) && (appanchor == anchor))) {\n        // compute the local path\n        std::string local_path = eos::common::FileId::FidPrefix2FullPath(\n                                   eos::common::FileId::Fid2Hex(fmd->getId()).c_str(),\n                                   local_snapshot.mPath.c_str());\n        eos_info(\"msg=\\\"local-redirect screening - forwarding to local\\\" local-path=\\\"%s\\\" path=\\\"%s\\\" info=\\\"%s\\\"\",\n                 local_path.c_str(), path, ininfo);\n        redirectionhost = \"file://localhost\";\n        redirectionhost += local_path.c_str();\n        ecode = -1;\n\n        if (!gOFS->SetRedirectionInfo(error, redirectionhost.c_str(), ecode)) {\n          eos_err(\"msg=\\\"failed setting redirection\\\" path=\\\"%s\\\"\", path);\n          return SFS_ERROR;\n        }\n\n        rcode = SFS_REDIRECT;\n        gOFS->MgmStats.Add(\"OpenRedirectLocal\", vid.uid, vid.gid, 1, vid.app);\n        eos_info(\"local-redirect=\\\"%s\\\"\", redirectionhost.c_str());\n        return rcode;\n      }\n    }\n  }\n\n  if (ioPriority.length()) {\n    ioprio = ioPriority;\n    capability += \"&mgm.iopriority=\";\n    capability += ioPriority.c_str();\n  } else {\n    if (ioprio.length()) {\n      capability += \"&mgm.iopriority=\";\n      capability += ioprio.c_str();\n    }\n  }\n\n  if (schedule) {\n    capability += \"&mgm.schedule=1\";\n  }\n\n  if (iotype.length()) {\n    capability += \"&mgm.iotype=\";\n    capability += iotype.c_str();\n  }\n\n  if (fmd && atimeage) {\n    static std::set<std::string> skip_tag {\"balancer\", \"groupdrainer\", \"groupbalancer\", \"geobalancer\", \"drainer\", \"converter\", \"fsck\"};\n\n    if (app_name.empty() || (skip_tag.find(app_name) == skip_tag.end())) {\n      // do a potential atime update, we don't need a name\n      try {\n        if (fmd->setATimeNow(atimeage)) {\n          gOFS->eosView->updateFileStore(fmd.get());\n        }\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        std::string errmsg = e.getMessage().str();\n        eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                  e.getErrno(), e.getMessage().str().c_str());\n        gOFS->MgmStats.Add(\"OpenFailedQuota\", vid.uid, vid.gid, 1, vid.app);\n        return Emsg(epname, error, errno, \"open file and update atime for reading\",\n                    errmsg.c_str());\n      }\n    }\n  }\n\n  // get placement policy\n  Policy::GetPlctPolicy(path, attrmap, vid, *openOpaque, plctplcy, targetgeotag);\n  unsigned long long ext_mtime_sec = 0;\n  unsigned long long ext_mtime_nsec = 0;\n  unsigned long long ext_ctime_sec = 0;\n  unsigned long long ext_ctime_nsec = 0;\n  std::string ext_etag;\n  std::map<std::string, std::string> ext_xattr_map;\n\n  if (openOpaque->Get(\"eos.ctime\")) {\n    std::string str_ctime = openOpaque->Get(\"eos.ctime\");\n    size_t pos = str_ctime.find('.');\n\n    if (pos == std::string::npos) {\n      ext_ctime_sec = strtoull(str_ctime.c_str(), 0, 10);\n      ext_ctime_nsec = 0;\n    } else {\n      ext_ctime_sec = strtoull(str_ctime.substr(0, pos).c_str(), 0, 10);\n      ext_ctime_nsec = strtoull(str_ctime.substr(pos + 1).c_str(), 0, 10);\n    }\n  }\n\n  if (openOpaque->Get(\"eos.mtime\")) {\n    std::string str_mtime = openOpaque->Get(\"eos.mtime\");\n    size_t pos = str_mtime.find('.');\n\n    if (pos == std::string::npos) {\n      ext_mtime_sec = strtoull(str_mtime.c_str(), 0, 10);\n      ext_mtime_nsec = 0;\n    } else {\n      ext_mtime_sec = strtoull(str_mtime.substr(0, pos).c_str(), 0, 10);\n      ext_mtime_nsec = strtoull(str_mtime.substr(pos + 1).c_str(), 0, 10);\n    }\n  }\n\n  ext_etag = XrdUtils::GetEnv(*openOpaque, \"eos.etag\");\n\n  if (openOpaque->Get(\"eos.xattr\")) {\n    std::vector<std::string> xattr_keys;\n    eos::common::StringConversion::GetKeyValueMap(openOpaque->Get(\"eos.xattr\"),\n        ext_xattr_map, \"=\", \"#\", &xattr_keys);\n\n    for (auto it = xattr_keys.begin(); it != xattr_keys.end(); ++it) {\n      if (it->substr(0, 5) != \"user.\") {\n        ext_xattr_map.erase(*it);\n      }\n    }\n  }\n\n  if ((!isInjection) && (isCreation || (open_flags & O_TRUNC))) {\n    eos_info(\"blocksize=%llu lid=%x\", LayoutId::GetBlocksize(new_lid), new_lid);\n    layoutId = new_lid;\n    {\n      std::shared_ptr<eos::IFileMD> fmdnew;\n\n      if (!byfid) {\n        try {\n          fmdnew = dmd->findFile(fileName);\n        } catch (eos::MDException& e) {\n          if ((!isAtomicUpload) && (fmdnew != fmd)) {\n            // file has been recreated in the meanwhile\n            return Emsg(epname, error, EEXIST, \"open file (file recreated)\", path);\n          }\n        }\n      }\n\n      // Set the layout and commit new meta data\n      fmd->setLayoutId(layoutId);\n\n      if (isFuse && (open_flags & O_TRUNC)) {\n        std::string s;\n\n        if (fmd->hasAttribute(\"sys.fusex.state\")) {\n          s = fmd->getAttribute(\"sys.fusex.state\");\n        }\n\n        s += \"T\";\n        fmd->setAttribute(\"sys.fusex.state\",\n                          eos::common::StringConversion::ReduceString(s).c_str());\n      }\n\n      // if specified set an external modification/creation time\n      if (ext_mtime_sec) {\n        eos::IFileMD::ctime_t mtime;\n        mtime.tv_sec = ext_mtime_sec;\n        mtime.tv_nsec = ext_mtime_nsec;\n        fmd->setMTime(mtime);\n      } else {\n        fmd->setMTimeNow();\n      }\n\n      if (ext_ctime_sec) {\n        eos::IFileMD::ctime_t ctime;\n        ctime.tv_sec = ext_ctime_sec;\n        ctime.tv_nsec = ext_ctime_nsec;\n        fmd->setCTime(ctime);\n      }\n\n      if (isCreation) {\n        // store the birth time as an extended attribute\n        eos::IFileMD::ctime_t ctime;\n        fmd->getCTime(ctime);\n        char btime[256];\n        snprintf(btime, sizeof(btime), \"%lu.%lu\", ctime.tv_sec, ctime.tv_nsec);\n        fmd->setAttribute(\"sys.eos.btime\", btime);\n      } else {\n        fmd->setATimeNow(0);\n      }\n\n      // if specified set an external temporary ETAG\n      if (ext_etag.length()) {\n        fmd->setAttribute(\"sys.tmp.etag\", ext_etag);\n      }\n\n      for (auto it = ext_xattr_map.begin(); it != ext_xattr_map.end(); ++it) {\n        fmd->setAttribute(it->first, it->second);\n      }\n\n      if (acl.EvalUserAttrFile()) {\n        // we inherit existing ACLs during (atomic) versioning\n        fmd->setAttribute(\"user.acl\", acl.UserAttrFile());\n        fmd->setAttribute(\"sys.eval.useracl\", \"1\");\n      }\n\n      try {\n        eos::common::RWMutexWriteLock ns_wr_lock(gOFS->eosViewRWMutex);\n        eos::FileIdentifier fmd_id = fmd->getIdentifier();\n        std::shared_ptr<eos::IContainerMD> cmd =\n          gOFS->eosDirectoryService->getContainerMD(cid);\n        eos::ContainerIdentifier cmd_id = cmd->getIdentifier();\n        eos::ContainerIdentifier pcmd_id = cmd->getParentIdentifier();\n        cmd->setMTimeNow();\n\n        if (isCreation || (!fmd->getNumLocation())) {\n          eos::IQuotaNode* ns_quota = gOFS->eosView->getQuotaNode(cmd.get());\n\n          if (ns_quota) {\n            ns_quota->addFile(fmd.get());\n          }\n        }\n\n        ns_wr_lock.Release();\n        COMMONTIMING(\"filemd::update\", &tm);\n        gOFS->eosView->updateFileStore(fmd.get());\n        cmd->notifyMTimeChange(gOFS->eosDirectoryService);\n        gOFS->eosView->updateContainerStore(cmd.get());\n        gOFS->FuseXCastRefresh(fmd_id, cmd_id);\n        gOFS->FuseXCastRefresh(cmd_id, pcmd_id);\n        COMMONTIMING(\"fusex::bc\", &tm);\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        std::string errmsg = e.getMessage().str();\n        eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                  e.getErrno(), e.getMessage().str().c_str());\n        gOFS->MgmStats.Add(\"OpenFailedQuota\", vid.uid, vid.gid, 1, vid.app);\n        return Emsg(epname, error, errno, \"open file\", errmsg.c_str());\n      }\n    }\n  }\n\n  // 0-size files can be read from the MGM if this is not FUSE access\n  // atomic files are only served from here and also rain files are skipped\n  if (!isRW && !fmd->getSize() && (!isFuse || isAtomicName)) {\n    if (isAtomicName || (!LayoutId::IsRain(layoutId))) {\n      eos_info(\"msg=\\\"0-size file read from the MGM\\\" path=%s\", path);\n      mIsZeroSize = true;\n\n      // ---------------------------------------------------------------------\n      // Per-directory sys.audit override for READ auditing\n      if (gOFS->AllowAuditRead(path)) {\n        gOFS->mAudit->audit(eos::audit::READ, path, vid, logId, cident, \"mgm\",\n                            std::string(), nullptr, nullptr,\n                            std::string(), std::string(), std::string(),\n                            __FILE__, __LINE__, VERSION);\n      }\n\n      // ---------------------------------------------------------------------\n      return SFS_OK;\n    }\n  }\n\n  // @todo(esindril) the tag is wrong should actually be mgm.uid\n  capability += \"&mgm.ruid=\";\n  capability += (int) vid.uid;\n  capability += \"&mgm.rgid=\";\n  capability += (int) vid.gid;\n  // @todo(esindril) not used and should be removed\n  capability += \"&mgm.uid=99\";\n  capability += \"&mgm.gid=99\";\n  capability += \"&mgm.path=\";\n  {\n    // an '&' will create a failure on the FST\n    XrdOucString safepath = spath.c_str();\n    eos::common::StringConversion::SealXrdPath(safepath);\n    capability += safepath;\n  }\n  capability += \"&mgm.manager=\";\n  capability += gOFS->ManagerId.c_str();\n  capability += \"&mgm.fid=\";\n  std::string hex_fid;\n\n  if (!isRW) {\n    const char* val;\n\n    if ((val = openOpaque->Get(\"eos.clonefst\")) && (strlen(val) < 32)) {\n      hex_fid = fmd->getCloneFST();\n      eos_debug(\"open read eos.clonefst %s hex_fid %s\", val, hex_fid.c_str());\n\n      if (hex_fid != val) {\n        return Emsg(epname, error, EINVAL, \"open - invalid clonefst argument\", path);\n      }\n    }\n  }\n\n  if (hex_fid.empty()) {\n    hex_fid = eos::common::FileId::Fid2Hex(mFid);\n  }\n\n  capability += hex_fid.c_str();\n  XrdOucString sizestring;\n  capability += \"&mgm.cid=\";\n  capability += eos::common::StringConversion::GetSizeString(sizestring, cid);\n  // add the mgm.sec information to the capability\n  capability += \"&mgm.sec=\";\n  capability += eos::common::SecEntity::ToKey(client, app_name.c_str()).c_str();\n  std::string containertag;\n\n  if (attr::getValue(attrmap, \"user.tag\", containertag)) {\n    capability += \"&mgm.container=\";\n    capability += containertag.c_str();\n  }\n\n  // Size which will be reserved with a placement of one replica for the file\n  unsigned long long bookingsize = 0;\n  bool hasClientBookingSize = false;\n  unsigned long long targetsize = 0;\n  unsigned long long minimumsize = 0;\n  unsigned long long maximumsize = 0;\n\n  if (attr::getValue(attrmap, \"sys.forced.bookingsize\", bookingsize)) {\n    // we allow only a system attribute not to get fooled by a user\n  } else {\n    if (attr::getValue(attrmap, \"user.forced.bookingsize\", bookingsize)) {\n      // fallback to user booking size\n    } else {\n      if (XrdUtils::GetEnv(*openOpaque, \"eos.bookingsize\", bookingsize, 1024ull)) {\n        hasClientBookingSize = true;\n      } else {\n        hasClientBookingSize = XrdUtils::GetEnv(*openOpaque, \"oss.asize\", bookingsize, 1024ull);\n      }\n    }\n  }\n  attr::getValue(attrmap, \"sys.forced.minsize\", minimumsize);\n  attr::getValue(attrmap, \"sys.forced.maxsize\", maximumsize);\n\n  XrdUtils::GetEnv(*openOpaque, \"oss.asize\", targetsize);\n  XrdUtils::GetEnv(*openOpaque, \"eos.targetsize\", targetsize);\n\n  std::vector<unsigned int> selectedfs;\n  std::vector<unsigned int> excludefs = GetExcludedFsids();\n  std::vector<std::string> proxys;\n  std::vector<std::string> firewalleps;\n  // file systems which are unavailable during a read operation\n  std::vector<unsigned int> unavailfs;\n  // file systems which have been replaced with a new reconstructed stripe\n  std::vector<unsigned int> replacedfs;\n  int retc = 0;\n  bool isRecreation = false;\n\n  // Place a new file\n  if (isCreation || (!fmd->getNumLocation()) || isInjection) {\n    Scheduler::PlacementArguments plctargs;\n    {\n      unsigned long long _bookingsize;\n\n      if (isRepair) {\n        _bookingsize = bookingsize ? bookingsize : gOFS->getFuseBookingSize();\n      } else {\n        _bookingsize = isFuse ? (isTouch ? 0 : gOFS->getFuseBookingSize()) :\n                       bookingsize;\n      }\n\n      using namespace eos::mgm::scheduler;\n      plctargs.setFileParams(space, Path(path), GroupTag(containertag.c_str()),\n                             Lid(layoutId), (ino64_t)fmd->getId(),\n                             BookingSize(_bookingsize), open_flags & O_TRUNC, vid)\n      .setFsParams(&selectedfs, &excludefs, &selectedfs)\n      .setPlctParams(plctplcy, &targetgeotag, forced_group,\n                     openOpaque->Get(\"eos.schedulingstrategy\"));\n      plctargs.dataproxys = &proxys;\n      plctargs.firewallentpts = &firewalleps;\n\n      if (!plctargs.isValid()) {\n        return Emsg(epname, error, EINVAL, \"open - invalid placement argument\", path);\n      }\n    }\n    COMMONTIMING(\"Scheduler::FilePlacement\", &tm);\n    eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n    retc = Quota::FilePlacement(&plctargs);\n    COMMONTIMING(\"Scheduler::FilePlaced\", &tm);\n    Scheduler::ReshuffleFs(selectedfs);\n  } else {\n    // Access existing file - fill the vector with the existing locations\n    for (unsigned int i = 0; i < fmd->getNumLocation(); i++) {\n      auto loc = fmd->getLocation(i);\n\n      if (loc != 0 && loc != eos::common::TAPE_FS_ID) {\n        selectedfs.push_back(loc);\n        excludefs.push_back(loc);\n      }\n    }\n\n    eos::IFileMD::LocationVector unlinked = fmd->getUnlinkedLocations();\n\n    for (auto loc : unlinked) {\n      excludefs.push_back(loc);\n    }\n\n    if (selectedfs.empty()) {\n      // this file has not a single existing replica\n      gOFS->MgmStats.Add(\"OpenFileOffline\", vid.uid, vid.gid, 1, vid.app);\n      // Fire and forget a sync::offline workflow event\n      errno = 0;\n      workflow.SetFile(path, mFid);\n      std::string workflowErrorMsg;\n      const auto ret_wfe = workflow.Trigger(\"sync::offline\", currentWorkflow,\n                                            vid,\n                                            ininfo, workflowErrorMsg);\n\n      if (ret_wfe < 0 && errno == ENOKEY) {\n        eos_debug(\"msg=\\\"no workflow defined for sync::offline\\\"\");\n      } else {\n        eos_info(\"msg=\\\"workflow trigger returned\\\" retc=%d errno=%d event=\\\"sync::offline\\\"\",\n                 ret_wfe, errno);\n      }\n\n      return Emsg(epname, error, ENODEV, \"open - no disk replica exists\", path);\n    }\n\n    /// ###############\n    // reconstruction opens files in RW mode but we actually need RO mode in this case\n    /// ###############\n    // if the client should go through a firewall entrypoint, try to get it\n    // if the scheduled fs need to be accessed through a dataproxy, try to get it\n    // if any of the two fails, the scheduling operation fails\n    Scheduler::AccessArguments acsargs;\n    acsargs.bookingsize = fmd->getSize();\n    acsargs.dataproxys = &proxys;\n    acsargs.firewallentpts = &firewalleps;\n    acsargs.forcedfsid = forcedFsId;\n    acsargs.forcedspace = space.c_str();\n    acsargs.fsindex = &fsIndex;\n    acsargs.isRW = isPioReconstruct ? false : isRW;\n    acsargs.lid = layoutId;\n    acsargs.inode = (ino64_t) fmd->getId();\n    acsargs.locationsfs = &selectedfs;\n    acsargs.tried_cgi = &tried_cgi;\n    acsargs.unavailfs = &unavailfs;\n    acsargs.vid = &vid;\n\n    if (!acsargs.isValid()) {\n      // there is something wrong in the arguments of file access\n      return Emsg(epname, error, EINVAL, \"open - invalid access argument\", path);\n    }\n\n    {\n      COMMONTIMING(\"Scheduler::FileAccess\", &tm);\n      // TODO future: this doesn't really require a FsView readlock!\n      eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n      retc = Scheduler::FileAccess(&acsargs);\n      COMMONTIMING(\"Scheduler::FileAccessed\", &tm);\n    }\n\n    if (acsargs.isRW) {\n      // If this is an update, we don't have to send the client to cgi\n      // excluded locations, we tell that the file is unreachable\n      for (size_t k = 0; k < selectedfs.size(); k++) {\n        // if the fs is available\n        if (std::find(unavailfs.begin(), unavailfs.end(),\n                      selectedfs[k]) != unavailfs.end()) {\n          eos_info(\"msg=\\\"location %d is excluded as an unavailable filesystem\"\n                   \" - returning ENETUNREACH\\\"\", selectedfs[k]);\n          retc = ENETUNREACH;\n          break;\n        }\n      }\n    }\n\n    if ((retc == ENETUNREACH) || (retc == EROFS) || isRepair) {\n      if (isRW && ((((fmd->getSize() == 0) && (bookingsize == 0)) || isRepair))) {\n        // File-recreation due to offline/full file systems\n        isCreation = true;\n        Scheduler::PlacementArguments plctargs;\n        {\n          using namespace eos::mgm::scheduler;\n          plctargs.setFileParams(space, Path(path), GroupTag(containertag.c_str()),\n                                 Lid(layoutId), (ino64_t)fmd->getId(),\n                                 BookingSize(bookingsize), open_flags & O_TRUNC, vid)\n          .setFsParams(&excludefs, &excludefs, &selectedfs)\n          .setPlctParams(plctplcy, &targetgeotag, forced_group,\n                         openOpaque->Get(\"eos.schedulingstrategy\"));\n          plctargs.dataproxys = &proxys;\n          plctargs.firewallentpts = &firewalleps;\n\n          if (!plctargs.isValid()) {\n            return Emsg(epname, error, EINVAL, \"open - invalid placement argument\", path);\n          }\n        }\n        COMMONTIMING(\"Scheduler::FilePlacement\", &tm);\n        eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n        retc = Quota::FilePlacement(&plctargs);\n        COMMONTIMING(\"Scheduler::FilePlaced\", &tm);\n        eos_info(\"msg=\\\"file-recreation due to offline/full locations\\\" path=%s retc=%d\",\n                 path, retc);\n        isRecreation = true;\n        // end scope fs_rd_lock\n      } else {\n        // Normal read failed, try to reply with the triedrc value if this\n        // exists in the URL otherwise we'll return ENETUNREACH which is a\n        // client recoverable error.\n        if (auto triedrc = XrdUtils::GetEnv(*openOpaque, \"triedrc\");\n            !triedrc.empty()) {\n          int errno_tried = GetTriedrcErrno(triedrc);\n          if (errno_tried) {\n            return Emsg(epname, error, errno_tried, \"open file\", path);\n          }\n        }\n      }\n    }\n\n    if (retc == EXDEV) {\n      // Indicating that the layout requires the replacement of stripes\n      retc = 0; // TODO: we currently don't support repair on the fly mode\n    }\n  }\n\n  LogSchedulingInfo(selectedfs, proxys, firewalleps);\n\n  if (retc) {\n    // If we don't have quota we don't bounce the client back\n    if ((retc != ENOSPC) && (retc != EDQUOT)) {\n      // INLINE Workflows\n      int stalltime = 0;\n      workflow.SetFile(path, fmd->getId());\n      std::string errorMsg;\n\n      if ((stalltime = workflow.Trigger(\"open\", \"enonet\", vid, ininfo,\n                                        errorMsg)) > 0) {\n        eos_info(\"msg=\\\"triggered ENOENT workflow\\\" path=%s\", path);\n        return gOFS->Stall(error, vid, stalltime, \"\"\n                           \"File is currently unavailable - triggered workflow!\");\n      }\n\n      // check if we have a global redirect or stall for offline files\n      MAYREDIRECT_ENONET;\n      MAYSTALL_ENONET;\n      MAYREDIRECT_ENETUNREACH;\n      MAYSTALL_ENETUNREACH;\n\n      // check if the dir attributes tell us to let clients rebounce\n      // stall clients when sys/user attributes are set\n      if (attr::getValue(attrmap, \"sys.stall.unavailable\", stalltime) && stalltime > 0) {\n          gOFS->MgmStats.Add(\"OpenStalled\", vid.uid, vid.gid, 1, vid.app);\n          eos_info(\"attr=sys info=\\\"stalling file since replica's are down\\\" path=%s rw=%d\",\n                   path, isRW);\n          return gOFS->Stall(error, vid, stalltime,\n                             \"Required filesystems are currently unavailable!\");\n      }\n\n      if (attr::getValue(attrmap, \"user.stall.unavailable\", stalltime) && stalltime > 0) {\n          gOFS->MgmStats.Add(\"OpenStalled\", vid.uid, vid.gid, 1, vid.app);\n          eos_info(\"attr=user info=\\\"stalling file since replica's are down\\\" path=%s rw=%d\",\n                   path, isRW);\n          return gOFS->Stall(error, vid, stalltime,\n                             \"Required filesystems are currently unavailable!\");\n      }\n\n      if (std::string _redir;\n          attr::getValue(attrmap, \"sys.redirect.enonet\", _redir) && !_redir.empty()) {\n        // there is a redirection setting here if files are unaccessible\n        redirectionhost = \"\";\n        redirectionhost = _redir.c_str();\n        int portpos = 0;\n\n        if ((portpos = redirectionhost.find(\":\")) != STR_NPOS) {\n          XrdOucString port = redirectionhost;\n          port.erase(0, portpos + 1);\n          ecode = atoi(port.c_str());\n          redirectionhost.erase(portpos);\n        } else {\n          ecode = 1094;\n        }\n\n        if (!gOFS->SetRedirectionInfo(error, redirectionhost.c_str(), ecode)) {\n          eos_err(\"msg=\\\"failed setting redirection\\\" path=\\\"%s\\\"\", path);\n          return SFS_ERROR;\n        }\n\n        rcode = SFS_REDIRECT;\n        gOFS->MgmStats.Add(\"RedirectENONET\", vid.uid, vid.gid, 1, vid.app);\n        return rcode;\n      }\n\n      if (!gOFS->mMaster->IsMaster() && gOFS->mMaster->IsRemoteMasterOk()) {\n        // Redirect ENONET to the actual master\n        int port {0};\n        std::string hostname;\n        std::string master_id = gOFS->mMaster->GetMasterId();\n\n        if (!eos::common::ParseHostNamePort(master_id, hostname, port)) {\n          eos_err(\"msg=\\\"failed parsing remote master info\\\", id=%s\",\n                  master_id.c_str());\n          return Emsg(epname, error, retc, \"open file - failed parsing remote \"\n                      \"master info\", path);\n        }\n\n        redirectionhost = hostname.c_str();\n        ecode = port;\n\n        if (!gOFS->SetRedirectionInfo(error, redirectionhost.c_str(), ecode)) {\n          eos_err(\"msg=\\\"failed setting redirection\\\" path=\\\"%s\\\"\", path);\n          return SFS_ERROR;\n        }\n\n        rcode = SFS_REDIRECT;\n        gOFS->MgmStats.Add(\"RedirectENONET\", vid.uid, vid.gid, 1, vid.app);\n        return rcode;\n      }\n\n      gOFS->MgmStats.Add(\"OpenFileOffline\", vid.uid, vid.gid, 1, vid.app);\n    } else {\n      // Remove the created file from the namespace as root since somebody could\n      // have a no-delete ACL. Do this only if there are no replicas already\n      // attached to the file md entry. If there are, this means the current\n      // thread was blocked in scheduling and a retry of the client went\n      // through successfully. If we delete the entry we end up with data lost.\n      if (isCreation) {\n        bool do_remove = false;\n\n        try {\n          eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, creation_path.c_str());\n          eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n          auto tmp_fmd = gOFS->eosView->getFile(creation_path.c_str());\n\n          if (isAtomicUpload || (tmp_fmd->getNumLocation() == 0)) {\n            do_remove = true;\n          }\n        } catch (eos::MDException& e) {\n          if (isAtomicUpload) {\n            do_remove = true;\n          }\n        }\n\n        if (do_remove) {\n          eos::common::VirtualIdentity vidroot = eos::common::VirtualIdentity::Root();\n          gOFS->_rem(creation_path.c_str(), error, vidroot, 0, false, false, false);\n        }\n      }\n\n      gOFS->MgmStats.Add(\"OpenFailedQuota\", vid.uid, vid.gid, 1, vid.app);\n    }\n\n    if (isRW) {\n      if (retc == ENOSPC) {\n        return Emsg(epname, error, retc, \"get free physical space\", path);\n      }\n\n      if (retc == EDQUOT) {\n        return Emsg(epname, error, retc,\n                    \"get quota space - quota not defined or exhausted\", path);\n      }\n\n      return Emsg(epname, error, retc, \"access quota space\", path);\n    } else {\n      return Emsg(epname, error, retc, \"open file \", path);\n    }\n  } else {\n    if (isRW) {\n      // we want to define the order of chunks during creation, so we attach also rain layouts\n      if (isCreation && hasClientBookingSize && ((bookingsize == 0) ||\n          ocUploadUuid.length() || (LayoutId::IsRain(layoutId)))) {\n        // ---------------------------------------------------------------------\n        // if this is a creation we commit the scheduled replicas NOW\n        // we do the same for chunked/parallel uploads\n        // ---------------------------------------------------------------------\n        {\n          // get an empty file checksum\n          std::string binchecksum = LayoutId::GetEmptyFileBinChecksum(layoutId);\n          eos::Buffer cx;\n          cx.putData(binchecksum.c_str(), binchecksum.size());\n\n          // FUSEX repair access needs to retrieve the file by fid\n          // TODO: Refactor isCreation and isRecreation code paths\n          //eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n          // -------------------------------------------------------------------\n\n          try {\n            std::string locations;\n\n            if (fmd->hasAttribute(\"sys.fs.tracking\")) {\n              locations = fmd->getAttribute(\"sys.fs.tracking\");\n            }\n\n            if (isRecreation) {\n              fmd->unlinkAllLocations();\n              locations += \"=\";\n            }\n\n            if (isRecreation) {\n              std::string s;\n\n              if (fmd->hasAttribute(\"sys.fusex.state\")) {\n                s = fmd->getAttribute(\"sys.fusex.state\");\n              }\n\n              s += \"Z\";\n              fmd->setAttribute(\"sys.fusex.state\",\n                                eos::common::StringConversion::ReduceString(s).c_str());\n            }\n\n            for (auto& fsid : selectedfs) {\n              fmd->addLocation(fsid);\n              locations += \"+\";\n              locations += std::to_string(fsid);\n            }\n\n            fmd->setAttribute(\"sys.fs.tracking\",\n                              eos::common::StringConversion::ReduceString(locations).c_str());\n            fmd->setChecksum(cx);\n            gOFS->eosView->updateFileStore(fmd.get());\n          } catch (eos::MDException& e) {\n            errno = e.getErrno();\n            std::string errmsg = e.getMessage().str();\n            eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                      e.getErrno(), e.getMessage().str().c_str());\n            gOFS->MgmStats.Add(\"OpenFailedQuota\", vid.uid, vid.gid, 1, vid.app);\n            return Emsg(epname, error, errno, \"open file\", errmsg.c_str());\n          }\n\n          // -------------------------------------------------------------------\n        }\n        mIsZeroSize = true;\n      }\n\n      if (isFuse && !isCreation) {\n        // ---------------------------------------------------------------------\n        // if we come from fuse for an update\n        // consistently redirect to the highest fsid having if possible the same\n        // geotag as the client\n        // ---------------------------------------------------------------------\n        if (byfid) {\n          // the new FUSE client needs to have the replicas attached after the\n          // first open call\n          std::string locations;\n\n          try {\n            if (fmd->hasAttribute(\"sys.fs.tracking\")) {\n              locations = fmd->getAttribute(\"sys.fs.tracking\");\n            }\n\n            for (auto& fsid : selectedfs) {\n              fmd->addLocation(fsid);\n              locations += \"+\";\n              locations += std::to_string(fsid);\n            }\n\n            gOFS->eosView->updateFileStore(fmd.get());\n          } catch (eos::MDException& e) {\n            errno = e.getErrno();\n            std::string errmsg = e.getMessage().str();\n            eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                      e.getErrno(), e.getMessage().str().c_str());\n            gOFS->MgmStats.Add(\"OpenFailedQuota\", vid.uid, vid.gid, 1, vid.app);\n            return Emsg(epname, error, errno, \"open file\", errmsg.c_str());\n          }\n        }\n\n        eos::common::FileSystem::fsid_t fsid = 0;\n        fsIndex = 0;\n        std::string fsgeotag;\n        {\n          eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n\n          for (size_t k = 0; k < selectedfs.size(); k++) {\n            auto filesystem = FsView::gFsView.mIdView.lookupByID(selectedfs[k]);\n            fsgeotag = \"\";\n\n            if (filesystem) {\n              fsgeotag = filesystem->GetString(\"stat.geotag\");\n            }\n\n            // if the fs is available\n            if (std::find(unavailfs.begin(), unavailfs.end(), selectedfs[k]) ==\n                unavailfs.end()) {\n              // take the highest fsid with the same geotag if possible\n              if ((vid.geolocation.empty() ||\n                   (fsgeotag.find(vid.geolocation) != std::string::npos)) &&\n                  (selectedfs[k] > fsid)) {\n                fsIndex = k;\n                fsid = selectedfs[k];\n              }\n            }\n          }\n        } // fs_rd_lock scope\n\n        // if the client has a geotag which does not match any of the fs's\n        if (!fsIndex) {\n          fsid = 0;\n\n          for (size_t k = 0; k < selectedfs.size(); k++)\n            if (selectedfs[k] > fsid) {\n              fsIndex = k;\n              fsid = selectedfs[k];\n            }\n        }\n\n        // EOS-2787\n        // reshuffle the selectedfs to set if available the highest with matching geotag in front\n        if (fsid) {\n          std::vector<unsigned int> newselectedfs;\n          newselectedfs.push_back(fsid);\n\n          for (const auto& i : selectedfs) {\n            if (i != newselectedfs.front()) {\n              newselectedfs.push_back(i);\n            }\n          }\n\n          selectedfs.swap(newselectedfs);\n          fsIndex = 0;\n        }\n      }\n    } else {\n      if (!fmd->getSize()) {\n        // 0-size files can be read from the MGM if this is not FUSE access and\n        // also if this is not a rain file\n        if (!isFuse && !LayoutId::IsRain(layoutId)) {\n          mIsZeroSize = true;\n\n          // ----------f---------------------------------------------------------\n          // Per-directory sys.audit override for READ auditing\n          if (gOFS->AllowAuditRead(path)) {\n            gOFS->mAudit->audit(eos::audit::READ, path, vid, logId, cident, \"mgm\",\n                                std::string(), nullptr, nullptr,\n                                std::string(), std::string(), std::string(),\n                                __FILE__, __LINE__, VERSION);\n          }\n\n          // -------------------------------------------------------------------\n          return SFS_OK;\n        }\n      }\n    }\n  }\n\n  // If this is a RAIN layout, we want a nice round-robin for the entry\n  // server since it  has the burden of encoding and traffic fan-out\n  if (isRW && LayoutId::IsRain(layoutId)) {\n    fsIndex = mFid % selectedfs.size();\n    eos_static_info(\"msg=\\\"selecting entry-server\\\" fsIndex=%lu fsid=%lu \"\n                    \"fxid=%08llx mod=%lu\", fsIndex, selectedfs[fsIndex],\n                    mFid, selectedfs.size());\n  }\n\n  // If behaviour enabled then add preference to always select the file system\n  // with the lowest fsid as the server entry point\n  if (LayoutId::IsRain(layoutId) &&\n      gOFS->mBehaviourCfg->Exists(eos::common::BehaviourType::RainMinFsidEntry)) {\n    fsIndex = EnforceRainMinFsidEntry(selectedfs);\n  }\n\n  // Get the redirection host from the selected entry in the vector\n  if (!selectedfs[fsIndex]) {\n    eos_err(\"msg=\\\"0 filesystem in selection\\\" fxid=%08llx\", mFid);\n    return Emsg(epname, error, ENETUNREACH, \"received filesystem id 0\", path);\n  }\n\n  XrdOucString piolist = \"\";\n  XrdOucString infolog = \"\";\n  std::string fs_hostport, fs_host, fs_port, fs_http_port, fs_prefix,\n      fs_host_alias, fs_port_alias;\n  uint32_t fs_id;\n  {\n    eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n    auto filesystem = FsView::gFsView.mIdView.lookupByID(selectedfs[fsIndex]);\n\n    if (!filesystem) {\n      return Emsg(epname, error, ENETUNREACH,\n                  \"received non-existent filesystem\", path);\n    }\n\n    fs_hostport = filesystem->GetString(\"hostport\");\n    fs_host = filesystem->GetString(\"host\");\n    fs_port = filesystem->GetString(\"port\");\n    fs_host_alias = filesystem->GetString(\"stat.alias.host\");\n    fs_port_alias = filesystem->GetString(\"stat.alias.port\");\n\n    // allow FST host alias\n    if (fs_host_alias.length()) {\n      fs_host = fs_host_alias;\n\n      if (fs_port_alias.length()) {\n        fs_port = fs_port_alias;\n      }\n\n      fs_hostport = fs_host + std::string(\":\") + fs_port;\n      eos_info(\"redirection-alias=\\\"%s:%s\\\"\", fs_host_alias.c_str(),\n               fs_port_alias.c_str());\n    }\n\n    fs_http_port = filesystem->GetString(\"stat.http.port\");\n    fs_prefix = filesystem->GetPath();\n    fs_id = filesystem->GetId();\n  } // fs_rd_lock scope\n\n  if (auto result = setProxyFwEntrypoint(firewalleps, proxys, fsIndex,\n                                         fs_hostport, fs_prefix);\n      result.valid()) {\n    targetport = result.targetport;\n    targethttpport = result.targethttpport;\n    targethost = std::move(result.targethost);\n    redirectionhost = result.redirectionhost.c_str();\n  } else {\n    // There is no proxy or firewall entry point to use\n    targethost  = fs_host.c_str();\n    targetport  = atoi(fs_port.c_str());\n    targethttpport  = atoi(fs_http_port.c_str());\n    redirectionhost = targethost.c_str();\n    redirectionhost += \"?\";\n  }\n\n  // ---------------------------------------------------------------------------\n  // Rebuild the layout ID (for read it should indicate only the number of\n  // available stripes for reading);\n  // For 'pio' mode we hand out plain layouts to the client and add the IO\n  // layout as an extra field\n  // ---------------------------------------------------------------------------\n  // Get the unique set of file systems\n  std::set<unsigned int> ufs(selectedfs.begin(), selectedfs.end());\n  ufs.insert(pio_reconstruct_fs.begin(), pio_reconstruct_fs.end());\n  // If file system 0 sentinel is present then it must be removed\n  ufs.erase(0u);\n  new_lid = LayoutId::GetId(\n              isPio ? LayoutId::kPlain :\n              LayoutId::GetLayoutType(layoutId),\n              (isPio ? LayoutId::kNone :\n               LayoutId::GetChecksum(layoutId)),\n              isPioReconstruct ? static_cast<int>(ufs.size()) : static_cast<int>\n              (selectedfs.size()),\n              LayoutId::GetBlocksizeType(layoutId),\n              LayoutId::GetBlockChecksum(layoutId));\n\n  // For RAIN layouts we need to keep the original number of stripes since this\n  // is used to compute the different groups and block sizes in the FSTs\n  if ((LayoutId::IsRain(layoutId))) {\n    LayoutId::SetStripeNumber(new_lid,\n                              LayoutId::GetStripeNumber(layoutId));\n  }\n\n  capability += \"&mgm.lid=\";\n  capability += static_cast<int>(new_lid);\n  // space to be prebooked/allocated\n  capability += \"&mgm.bookingsize=\";\n\n  if (isPioReconstruct) {\n    // For pio reconstruct the booking size needs to be 0,\n    // the recovery will fail on non xfs filesystem otherwise.\n    capability += \"0\";\n  } else {\n    capability +=\n      eos::common::StringConversion::GetSizeString(sizestring, bookingsize);\n  }\n\n  if (minimumsize) {\n    capability += \"&mgm.minsize=\";\n    capability += eos::common::StringConversion::GetSizeString(sizestring,\n                  minimumsize);\n  }\n\n  if (maximumsize) {\n    capability += \"&mgm.maxsize=\";\n    capability += eos::common::StringConversion::GetSizeString(sizestring,\n                  maximumsize);\n  }\n\n  // Expected size of the target file on close\n  if (targetsize) {\n    capability += \"&mgm.targetsize=\";\n    capability += eos::common::StringConversion::GetSizeString(sizestring,\n                  targetsize);\n  }\n\n  if (LayoutId::GetLayoutType(layoutId) == LayoutId::kPlain) {\n    capability += \"&mgm.fsid=\";\n    capability += (int) fs_id;\n  }\n\n  if (isRepairRead) {\n    capability += \"&mgm.repairread=1\";\n  }\n\n  if (mIsZeroSize) {\n    capability += \"&mgm.zerosize=1\";\n  }\n\n  // Add the store flag for RAIN reconstruct jobs\n  if (isPioReconstruct) {\n    capability += \"&mgm.rain.store=1\";\n    // Append also the mgm.rain.size since we can't deduce at the FST during\n    // the recovery step and we need it for the stat information\n    capability += \"&mgm.rain.size=\";\n    capability += std::to_string(fmdsize).c_str();\n  }\n\n  if (bandwidth.length() && (bandwidth != \"0\")) {\n    capability += \"&mgm.iobw=\";\n    capability += bandwidth.c_str();\n  }\n\n  if ((LayoutId::GetLayoutType(layoutId) == LayoutId::kReplica) ||\n      (LayoutId::IsRain(layoutId))) {\n    capability += \"&mgm.fsid=\";\n    capability += (int) fs_id;\n    eos::mgm::FileSystem* repfilesystem = 0;\n    replacedfs.resize(selectedfs.size());\n    // If replacement has been specified try to get new locations for\n    // reconstruction or for missing stripes\n    std::set<unsigned int> add_new_fsid;\n\n    if (isPioReconstruct && !(pio_reconstruct_fs.empty())) {\n      // Get the scheduling group of one of the stripes\n      if (fmd->getNumLocation() == 0) {\n        eos_err(\"msg=\\\"no locations available for file\\\"\");\n        return Emsg(epname, error, EIO, \"get any locations for file\", path);\n      }\n\n      eos::common::FileSystem::fs_snapshot_t orig_snapshot;\n      unsigned int orig_id = fmd->getLocation(0);\n      {\n        eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n        // Note this is a eos::common::filesystem not a mgm one\n        auto orig_fs = FsView::gFsView.mIdView.lookupByID(orig_id);\n\n        if (!orig_fs) {\n          return Emsg(epname, error, EINVAL, \"reconstruct filesystem\", path);\n        }\n\n        orig_fs->SnapShotFileSystem(orig_snapshot);\n      } // fs_rd_lock\n      forced_group = orig_snapshot.mGroupIndex;\n      // Add new stripes if file doesn't have the nomial number\n      auto stripe_diff = (LayoutId::GetStripeNumber(fmd->getLayoutId()) + 1) -\n                         selectedfs.size();\n      // Create a plain layout with the number of replacement stripes to be\n      // scheduled in the file placement routine\n      unsigned long plain_lid = new_lid;\n\n      if (pio_reconstruct_fs.find(0) != pio_reconstruct_fs.end()) {\n        LayoutId::SetStripeNumber(plain_lid, stripe_diff - 1);\n      } else {\n        LayoutId::SetStripeNumber(plain_lid,\n                                  pio_reconstruct_fs.size() - 1 + stripe_diff);\n      }\n\n      eos_info(\"msg=\\\"nominal stripes:%d reconstructed stripes=%d group_idx=%d\\\"\",\n               LayoutId::GetStripeNumber(new_lid) + 1,\n               LayoutId::GetStripeNumber(plain_lid) + 1,\n               forced_group);\n      // Compute the size of the stripes to be placed\n      unsigned long num_data_stripes = LayoutId::GetStripeNumber(layoutId) + 1 -\n                                       LayoutId::GetRedundancyStripeNumber(layoutId);\n      uint64_t plain_book_sz = (uint64_t)std::ceil((float)fmd->getSize() /\n                               LayoutId::GetBlocksize(layoutId));\n      plain_book_sz = std::ceil((float) plain_book_sz / std::pow(num_data_stripes,\n                                2)) *\n                      num_data_stripes * LayoutId::GetBlocksize(layoutId) + LayoutId::OssXsBlockSize;\n      eos_info(\"msg=\\\"plain booking size is %llu\\\"\", plain_book_sz);\n      eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n      Scheduler::PlacementArguments plctargs;\n      {\n        using namespace eos::mgm::scheduler;\n        plctargs.setFileParams(space, Path(path), GroupTag(containertag.c_str()),\n                               Lid(plain_lid), (ino64_t)fmd->getId(),\n                               BookingSize(plain_book_sz), false, rootvid)\n        .setFsParams(&selectedfs, &excludefs, &pio_replacement_fs)\n        .setPlctParams(plctplcy, &targetgeotag, forced_group,\n                       openOpaque->Get(\"eos.schedulingstrategy\"));\n        plctargs.dataproxys = &proxys;\n        plctargs.firewallentpts = &firewalleps;\n\n        if (!plctargs.isValid()) {\n          return Emsg(epname, error, EIO, \"open - invalid placement argument\", path);\n        }\n      }\n      COMMONTIMING(\"Scheduler::FilePlacement\", &tm);\n      {\n        eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n        retc = Quota::FilePlacement(&plctargs);\n      }\n      COMMONTIMING(\"Scheduler::FilePlaced\", &tm);\n      LogSchedulingInfo(selectedfs, proxys, firewalleps);\n\n      if (retc) {\n        gOFS->MgmStats.Add(\"OpenFailedReconstruct\", rootvid.uid, rootvid.gid, 1,\n                           vid.app);\n        return Emsg(epname, error, retc, \"schedule stripes for reconstruction\", path);\n      }\n\n      for (const auto& elem : pio_replacement_fs) {\n        eos_debug(\"msg=\\\"reconstruction scheduled on new fs\\\" fsid=%lu num=%lu\",\n                  elem, pio_replacement_fs.size());\n      }\n\n      auto selection_diff = (LayoutId::GetStripeNumber(fmd->getLayoutId()) + 1)\n                            - selectedfs.size();\n      eos_info(\"msg=\\\"fs selection summary\\\" nominal=%d actual=%d diff=%d\",\n               (LayoutId::GetStripeNumber(fmd->getLayoutId()) + 1),\n               selectedfs.size(), selection_diff);\n\n      // If there are stripes missing then fill them in from the replacements\n      if (pio_replacement_fs.size() < selection_diff) {\n        eos_err(\"msg=\\\"not enough replacement fs\\\" need=%lu have=%lu\",\n                selection_diff, pio_replacement_fs.size());\n        return Emsg(epname, error, retc, \"schedule enough stripes for reconstruction\",\n                    path);\n      }\n\n      for (size_t i = 0; i < selection_diff; ++i) {\n        unsigned int fsid = pio_replacement_fs.back();\n        pio_replacement_fs.pop_back();\n        selectedfs.push_back(fsid);\n        add_new_fsid.insert(fsid);\n      }\n    }\n\n    replacedfs.resize(selectedfs.size());\n    {\n      // Put all the replica urls into the capability,\n      // this is all under a view lock\n      eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n\n      for (unsigned int i = 0; i < selectedfs.size(); ++i) {\n        if (!selectedfs[i]) {\n          eos_err(\"%s\", \"msg=\\\"fsid 0 in replica vector\\\"\");\n        }\n\n        // Logic to discover filesystems to be reconstructed\n        bool replace = false;\n\n        if (isPioReconstruct) {\n          replace = (pio_reconstruct_fs.find(selectedfs[i]) != pio_reconstruct_fs.end());\n        }\n\n        if (replace) {\n          // If we don't find any replacement\n          if (pio_replacement_fs.empty()) {\n            return Emsg(epname, error, EIO, \"get replacement file system\", path);\n          }\n\n          // Take one replacement filesystem from the replacement list\n          replacedfs[i] = selectedfs[i];\n          selectedfs[i] = pio_replacement_fs.back();\n          pio_replacement_fs.pop_back();\n          eos_info(\"msg=\\\"replace fs\\\" old-fsid=%u new-fsid=%u\", replacedfs[i],\n                   selectedfs[i]);\n        } else {\n          // There is no replacement happening\n          replacedfs[i] = 0;\n        }\n\n        repfilesystem = FsView::gFsView.mIdView.lookupByID(selectedfs[i]);\n\n        if (!repfilesystem) {\n          // Don't fail IO on a shadow file system but throw a critical error\n          // message\n          eos_crit(\"msg=\\\"Unable to get replica filesystem information\\\" \"\n                   \"path=\\\"%s\\\" fsid=%d\", path, selectedfs[i]);\n          continue;\n        }\n\n        std::string replicahost;\n        std::string redirectionsuffix;\n        int replicaport{0};\n        bool hasreplaceProxy {false};\n\n        if (replace) {\n          fsIndex = i;\n\n          if (auto result = setProxyFwEntrypoint(firewalleps, proxys,\n                                                 fsIndex,\n                                                 repfilesystem->GetString(\"hostport\"),\n                                                 repfilesystem->GetPath());\n              result.valid()) {\n            targetport = result.targetport;\n            targethttpport = result.targethttpport;\n            targethost = std::move(result.targethost);\n            redirectionhost = result.redirectionhost.c_str();\n            redirectionsuffix = std::move(result.redirectionsuffix);\n            hasreplaceProxy = true;\n          } else {\n            // There is no proxy to use\n            targethost  = repfilesystem->GetString(\"host\").c_str();\n            targetport  = atoi(repfilesystem->GetString(\"port\").c_str());\n            targethttpport  = atoi(repfilesystem->GetString(\"stat.http.port\").c_str());\n            redirectionhost = targethost.c_str();\n            redirectionhost += \"?\";\n            replicahost += repfilesystem->GetString(\"host\").c_str();\n            replicaport = atoi(repfilesystem->GetString(\"port\").c_str());\n          }\n\n          // point at the right vector entry\n          fsIndex = i;\n        }\n\n        capability += \"&mgm.url\";\n        capability += (int) i;\n        capability += \"=root://\";\n\n        // -----------------------------------------------------------------------\n        // Logic to mask 'offline' filesystems\n        // -----------------------------------------------------------------------\n        for (unsigned int k = 0; k < unavailfs.size(); ++k) {\n          if (selectedfs[i] == unavailfs[k]) {\n            replicahost = \"__offline_\";\n            break;\n          }\n        }\n\n        replicahost = repfilesystem->GetString(\"host\");\n        replicaport = atoi(repfilesystem->GetString(\"port\").c_str());\n\n        // if there was a replacement and proxy, copy replica\n        // details from target\n        if (hasreplaceProxy) {\n          replicahost = targethost;\n          replicaport = targetport;\n        } else if (auto result = setProxyFwEntrypoint(firewalleps, proxys,\n                                 i,\n                                 repfilesystem->GetString(\"hostport\"),\n                                 repfilesystem->GetPath());\n                   result.valid()) {\n          replicahost = std::move(result.targethost);\n          replicaport = result.targetport;\n          redirectionsuffix = std::move(result.redirectionsuffix);\n        }\n\n        capability += replicahost.c_str();\n        capability += \":\";\n        capability += replicaport;\n        capability += \"//\";\n        // add replica fsid\n        capability += \"&mgm.fsid\";\n        capability += (int)i;\n        capability += \"=\";\n        capability += (int)repfilesystem->GetId();\n\n        // If there was a proxy redirection add this to the cap\n        if (!redirectionsuffix.empty()) {\n          capability += redirectionsuffix.c_str();\n        }\n\n        if (isPio) {\n          if (replacedfs[i]) {\n            // Add the drop message to the replacement capability\n            capability += \"&mgm.drainfsid\";\n            capability += (int)i;\n            capability += \"=\";\n            capability += (int)replacedfs[i];\n          }\n\n          piolist += \"pio.\";\n          piolist += (int)i;\n          piolist += \"=\";\n          piolist += replicahost.c_str();\n          piolist += \":\";\n          piolist += replicaport;\n          piolist += \"&\";\n        }\n\n        eos_debug(\"msg=\\\"redirection url\\\" %d => %s\", i, replicahost.c_str());\n        infolog += \"target[\";\n        infolog += (int)i;\n        infolog += \"]=(\";\n        infolog += replicahost.c_str();\n        infolog += \",\";\n        infolog += (int)repfilesystem->GetId();\n        infolog += \") \";\n      }\n    } // fs_rd_lock\n\n    if (isPioReconstruct && !add_new_fsid.empty()) {\n      std::string pio_addfs = \"eos.pio.addfs=\";\n\n      for (const auto& elem : add_new_fsid) {\n        pio_addfs += std::to_string(elem).c_str();\n        pio_addfs += \",\";\n      }\n\n      pio_addfs.erase(pio_addfs.length() - 1, 1);\n      redirectionhost += pio_addfs.c_str();\n    }\n  }\n\n  if (!altChecksums.empty()) {\n    capability += \"&mgm.altxs=\";\n    capability += eos::common::StringConversion::Join(altChecksums, \",\").c_str();\n    capability += \"&mgm.altxs.compute=\";\n    capability += computeAltXs ? \"1\" : \"0\";\n  }\n\n  // ---------------------------------------------------------------------------\n  // Encrypt capability\n  // ---------------------------------------------------------------------------\n  XrdOucEnv incapability(capability.c_str());\n  eos::common::SymKey* symkey = eos::common::gSymKeyStore.GetCurrentKey();\n  eos_debug(\"capability=%s\\n\", capability.c_str());\n  int caprc = 0;\n  XrdOucEnv* capabilityenvRaw = nullptr;\n\n  if ((caprc = eos::common::SymKey::CreateCapability(&incapability,\n               capabilityenvRaw,\n               symkey, gOFS->mCapabilityValidity))) {\n    return Emsg(epname, error, caprc, \"sign capability\", path);\n  }\n\n  std::unique_ptr<XrdOucEnv> capabilityenv(capabilityenvRaw);\n  int caplen = 0;\n\n  if (isPio) {\n    redirectionhost = piolist;\n    redirectionhost += \"mgm.lid=\";\n    redirectionhost += static_cast<int>(layoutId);\n    redirectionhost += \"&mgm.logid=\";\n    redirectionhost += this->logId;\n    redirectionhost += capabilityenv->Env(caplen);\n  } else {\n    redirectionhost += capabilityenv->Env(caplen);\n    redirectionhost += \"&mgm.logid=\";\n    redirectionhost += this->logId;\n    if (std::string _blockxs = XrdUtils::GetEnv(*openOpaque, \"eos.blockchecksum\");\n        !_blockxs.empty()) {\n      redirectionhost += \"&eos.blockchecksum=\";\n      redirectionhost += _blockxs.c_str();\n    } else {\n      if (!isRW && LayoutId::GetLayoutType(layoutId) == LayoutId::kReplica) {\n        redirectionhost += \"&mgm.blockchecksum=ignore\";\n      }\n    }\n\n    if (openOpaque->Get(\"eos.checksum\") || openOpaque->Get(\"eos.cloneid\")) {\n      redirectionhost += \"&mgm.checksum=\";\n      redirectionhost += openOpaque->Get(\"eos.checksum\");\n    }\n\n    if (openOpaque->Get(\"eos.mtime\")) {\n      redirectionhost += \"&mgm.mtime=0\";\n    }\n\n    // For the moment we redirect only on storage nodes\n    redirectionhost += \"&mgm.replicaindex=\";\n    redirectionhost += (int) fsIndex;\n    redirectionhost += \"&mgm.replicahead=\";\n    redirectionhost += (int) fsIndex;\n  }\n\n  if (vid.prot == \"https\") {\n    struct stat buf;\n    std::string etag;\n    eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n    // get the current ETAG\n    gOFS->_stat(path, &buf, error, rootvid, \"\", &etag);\n    redirectionhost += \"&mgm.etag=\";\n\n    if (!etag.length()) {\n      redirectionhost += \"undef\";\n    } else {\n      redirectionhost += etag.c_str();\n    }\n  }\n\n  // add the MGM hex id for this file\n  redirectionhost += \"&mgm.id=\";\n  redirectionhost += hex_fid.c_str();\n\n  if (isFuse) {\n    redirectionhost += \"&mgm.mtime=0\";\n  } else {\n    if (!isRW)  {\n      eos::IFileMD::ctime_t mtime;\n\n      try {\n        fmd->getMTime(mtime);\n        redirectionhost += \"&mgm.mtime=\";\n        std::string smtime;\n        smtime += std::to_string(mtime.tv_sec);\n        redirectionhost += smtime.c_str();\n      } catch (eos::MDException& ex) {\n      }\n    }\n  }\n\n  // Also trigger synchronous create workflow event if it's defined\n  if (isCreation) {\n    errno = 0;\n    workflow.SetFile(path, mFid);\n\n    std::string errorMsg;\n    auto ret_wfe = workflow.Trigger(\"sync::create\", currentWorkflow, vid,\n                                    ininfo, errorMsg);\n\n    if (ret_wfe < 0 && errno == ENOKEY) {\n      eos_debug(\"msg=\\\"no workflow defined for sync::create\\\"\");\n    } else {\n      eos_info(\"msg=\\\"workflow trigger returned\\\" retc=%d errno=%d\", ret_wfe, errno);\n\n      if (ret_wfe != 0) {\n        // Remove the file from the namespace in this case\n        try {\n          eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n          gOFS->eosView->removeFile(fmd.get());\n        } catch (eos::MDException& ex) {\n          eos_err(\"Failed to remove file from namespace in case of create workflow error. Reason: %s\",\n                  ex.what());\n        }\n\n        return Emsg(epname, error, ret_wfe, errorMsg.c_str(),\n                    path);\n      }\n    }\n  }\n\n  // add workflow cgis, has to come after create workflow\n  workflow.SetFile(path, mFid);\n\n  if (isRW) {\n    redirectionhost += workflow.getCGICloseW(currentWorkflow, vid).c_str();\n  } else {\n    redirectionhost += workflow.getCGICloseR(currentWorkflow).c_str();\n  }\n\n  // Notify tape garbage collector if tape support is enabled\n  if (gOFS->mTapeEnabled) {\n    try {\n      eos::common::RWMutexReadLock tgc_ns_rd_lock(gOFS->eosViewRWMutex);\n      const auto tgcFmd = gOFS->eosFileService->getFileMD(mFid);\n      const bool isATapeFile = tgcFmd->hasAttribute(\"sys.archive.file_id\");\n      tgc_ns_rd_lock.Release();\n\n      if (isATapeFile) {\n        if (isRW) {\n          const std::string tgcSpace = nullptr != space.c_str() ? space.c_str() : \"\";\n          gOFS->mTapeGc->fileOpenedForWrite(tgcSpace, mFid);\n        } else {\n          const auto fsId = getFirstDiskLocation(selectedfs);\n          const std::string tgcSpace = FsView::gFsView.mIdView.lookupSpaceByID(fsId);\n          gOFS->mTapeGc->fileOpenedForRead(tgcSpace, mFid);\n        }\n      }\n    } catch (...) {\n      // Ignore any garbage collection exceptions\n    }\n  }\n\n  // Always redirect\n  if ((vid.prot == \"https\") || (vid.prot == \"http\")) {\n    ecode = targethttpport;\n  } else {\n    ecode = targetport;\n  }\n\n  rcode = SFS_REDIRECT;\n  XrdOucString predirectionhost = redirectionhost.c_str();\n  eos::common::StringConversion::MaskTag(predirectionhost, \"cap.msg\");\n  eos::common::StringConversion::MaskTag(predirectionhost, \"cap.sym\");\n\n  const char* op = isRW ? \"write\" : \"read\";\n  eos_info(\"op=%s path=%s info=%s %s redirection=%s xrd_port=%d \"\n            \"http_port=%d\", op, path, pinfo.c_str(), infolog.c_str(),\n            predirectionhost.c_str(), targetport, targethttpport);\n\n  EXEC_TIMING_END(\"Open\");\n  COMMONTIMING(\"end\", &tm);\n  char clientinfo[1024];\n  snprintf(clientinfo, sizeof(clientinfo),\n           \"open:rt=%.02f io:bw=%s io:sched=%d io:type=%s io:prio=%s io:redirect=%s:%d\",\n           __exec_time__, bandwidth.length() ? bandwidth.c_str() : \"inf\", schedule,\n           iotype.length() ? iotype.c_str() : \"buffered\",\n           ioprio.length() ? ioprio.c_str() : \"default\", targethost.c_str(), ecode);\n  std::string sclientinfo(clientinfo);\n  std::string zclientinfo;\n  eos::common::SymKey::ZBase64(sclientinfo, zclientinfo);\n  redirectionhost += \"&eos.clientinfo=\";\n  redirectionhost += zclientinfo.c_str();\n\n  if (!gOFS->SetRedirectionInfo(error, redirectionhost.c_str(), ecode)) {\n    eos_err(\"msg=\\\"failed setting redirection\\\" path=\\\"%s\\\"\", path);\n    return SFS_ERROR;\n  }\n\n  eos_info(\"path=%s %s duration=%0.03fms timing=%s\",\n           path, clientinfo, tm.RealTime(), tm.Dump().c_str());\n\n  // Audit READ for successful open if read-only using global or per-dir override\n  if (!isRW && gOFS->AllowAuditRead(path)) {\n    gOFS->mAudit->audit(eos::audit::READ, path, vid, logId, cident, \"mgm\",\n                        std::string(), nullptr, nullptr,\n                        std::string(), std::string(), std::string(),\n                        __FILE__, __LINE__, VERSION);\n  }\n\n  return rcode;\n}\n\n\n//----------------------------------------------------------------------------\n// Read a partial result of a 'proc' interface command\n//----------------------------------------------------------------------------\nXrdSfsXferSize\nXrdMgmOfsFile::read(XrdSfsFileOffset offset,\n                    char* buff,\n                    XrdSfsXferSize blen)\n{\n  static const char* epname = \"read\";\n\n  if (mIsZeroSize) {\n    return 0;\n  }\n\n  if (mProcCmd) {\n    return mProcCmd->read(offset, buff, blen);\n  }\n\n  return Emsg(epname, error, EOPNOTSUPP, \"read\", fileName.c_str());\n}\n\n//------------------------------------------------------------------------------\n// Read file pages into a buffer and return corresponding checksums\n//------------------------------------------------------------------------------\nXrdSfsXferSize\nXrdMgmOfsFile::pgRead(XrdSfsFileOffset offset, char* buffer,\n                      XrdSfsXferSize rdlen, uint32_t* csvec, uint64_t opts)\n{\n  XrdSfsXferSize bytes;\n\n  if ((bytes = read(offset, buffer, rdlen)) <= 0) {\n    return bytes;\n  }\n\n  // Generate the crc's\n  XrdOucPgrwUtils::csCalc(buffer, offset, bytes, csvec);\n  return bytes;\n}\n\n/*----------------------------------------------------------------------------*/\n/*\n * @brief close a file object\n *\n * @return SFS_OK\n *\n * The close on the MGM is called only for files opened using the 'proc' e.g.\n * EOS shell comamnds. By construction failures can happen only during the open\n * of a 'proc' file e.g. the close always succeeds!\n */\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfsFile::close()\n{\n  oh = -1;\n\n  if (mProcCmd) {\n    mProcCmd->close();\n    return SFS_OK;\n  }\n\n  return SFS_OK;\n}\n\n/*----------------------------------------------------------------------------*/\n/*\n * @brief stat the size of an open 'proc' command/file\n *\n * @param buf stat struct where to store information\n * @return SFS_OK if open proc file otherwise SFS_ERROR\n *\n * For 'proc' files the result is created during the file open call.\n * The stat function will fill the size of the created result into the stat\n * buffer.\n */\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfsFile::stat(struct stat* buf)\n{\n  static const char* epname = \"stat\";\n\n  if (mIsZeroSize) {\n    memset(buf, 0, sizeof(struct stat));\n    return 0;\n  }\n\n  if (mProcCmd) {\n    return mProcCmd->stat(buf);\n  }\n\n  return Emsg(epname, error, EOPNOTSUPP, \"stat\", fileName.c_str());\n}\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfsFile::sync()\n/*----------------------------------------------------------------------------*/\n/*\n * sync an open file - no implemented (no use case)\n *\n * @return SFS_ERROR and EOPNOTSUPP\n */\n/*----------------------------------------------------------------------------*/\n{\n  static const char* epname = \"sync\";\n  return Emsg(epname, error, EOPNOTSUPP, \"sync\", fileName.c_str());\n}\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfsFile::sync(XrdSfsAio* aiop)\n/*----------------------------------------------------------------------------*/\n/*\n * aio sync an open file - no implemented (no use case)\n *\n * @return SFS_ERROR and EOPNOTSUPP\n */\n/*----------------------------------------------------------------------------*/\n{\n  static const char* epname = \"sync\";\n  // Execute this request in a synchronous fashion\n  //\n  return Emsg(epname, error, EOPNOTSUPP, \"sync\", fileName.c_str());\n}\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfsFile::truncate(XrdSfsFileOffset flen)\n/*----------------------------------------------------------------------------*/\n/*\n * truncate an open file - no implemented (no use case)\n *\n * @return SFS_ERROR and EOPNOTSUPP\n */\n/*----------------------------------------------------------------------------*/\n{\n  static const char* epname = \"trunc\";\n  return Emsg(epname, error, EOPNOTSUPP, \"truncate\", fileName.c_str());\n}\n\n/*----------------------------------------------------------------------------*/\nXrdMgmOfsFile::~XrdMgmOfsFile()\n/*----------------------------------------------------------------------------*/\n/*\n * @brief destructor\n *\n * Cleans-up the file object on destruction\n */\n/*----------------------------------------------------------------------------*/\n{\n  if (oh > 0) {\n    close();\n  }\n\n  if (openOpaque) {\n    delete openOpaque;\n    openOpaque = 0;\n  }\n}\n\n//------------------------------------------------------------------------------\n/*\n * @brief create an error message for a file object\n *\n * @param pfx message prefix value\n * @param einfo error text/code object\n * @param ecode error code\n * @param op name of the operation performed\n * @param target target of the operation e.g. file name etc.\n *\n * @return SFS_ERROR in all cases\n *\n * This routines prints also an error message into the EOS log.\n */\n//------------------------------------------------------------------------------\nint\nXrdMgmOfsFile::Emsg(const char* pfx,\n                    XrdOucErrInfo& einfo,\n                    int ecode,\n                    const char* op,\n                    const char* target)\n{\n  char etext[128], buffer[4096];\n\n  // Get the reason for the error\n  if (ecode < 0) {\n    ecode = -ecode;\n  }\n\n  if (eos::common::strerror_r(ecode, etext, sizeof(etext))) {\n    sprintf(etext, \"reason unknown (%d)\", ecode);\n  }\n\n  // Format the error message\n  snprintf(buffer, sizeof(buffer), \"Unable to %s %s; %s\", op, target, etext);\n  eos_err(\"Unable to %s %s; %s\", op, target, etext);\n  // Place the error message in the error object and return\n  einfo.setErrInfo(ecode, buffer);\n  return SFS_ERROR;\n}\n\n//------------------------------------------------------------------------------\n// Check if this is a client retry with exclusion of some diskserver. This\n// happens usually for CMS workflows. To distinguish such a scenario from\n// a legitimate retry due to a recoverable error, we need to search for the\n// \"tried=\" opaque tag without a corresponding \"triedrc=\" tag.\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfsFile::IsRainRetryWithExclusion(bool is_rw, unsigned long lid) const\n{\n  if (!is_rw && eos::common::LayoutId::IsRain(lid)) {\n    char* tried_info = openOpaque->Get(\"tried\");\n\n    if ((tried_info == nullptr) || strlen(tried_info) == 0) {\n      return false;\n    }\n\n    // Don't exclude if tried information contains a globally unique cluster\n    // ID which has the form: +<port><host>\n    bool exclude = false;\n    auto endpoints =  eos::common::StringTokenizer::split\n                      <std::list<std::string>>(tried_info, ',');\n\n    for (const auto& ep : endpoints) {\n      if (!ep.empty() && (ep[0] != '+')) {\n        exclude = true;\n        break;\n      }\n    }\n\n    if (openOpaque->Get(\"triedrc\") == nullptr) {\n      return exclude;\n    }\n  }\n\n  return false;\n}\n\n\n//------------------------------------------------------------------------------\n// Parse the triedrc opaque info and return the corresponding error number\n//------------------------------------------------------------------------------\nint\nXrdMgmOfsFile::GetTriedrcErrno(const std::string& input) const\n{\n  if (input.empty()) {\n    return 0;\n  }\n\n  std::vector<std::string> vect_err;\n  eos::common::StringConversion::Tokenize(input, vect_err, \",\");\n\n  for (const auto& elem : vect_err) {\n    if (elem == \"enoent\") {\n      return ENOENT;\n    } else if (elem == \"ioerr\") {\n      return EIO;\n    } else if (elem == \"fserr\") {\n      return EFAULT;\n    } else if (elem == \"srverr\") {\n      return EFAULT;\n    }\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Handle (delegated) TPC redirection\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfsFile::RedirectTpcAccess()\n{\n  if (!gOFS->mTpcRedirect) {\n    return false;\n  }\n\n  const char* tpc_key = openOpaque->Get(\"tpc.key\");\n\n  if (tpc_key == nullptr) {\n    return false;\n  }\n\n  bool is_delegated_tpc = (strncmp(tpc_key, \"delegate\", 8) == 0);\n  // Support the tpc.dlgon=1 marker for XRootD client >= 4.11.2\n  const char* dlg_marker = openOpaque->Get(\"tpc.dlgon\");\n\n  if (dlg_marker) {\n    is_delegated_tpc = is_delegated_tpc || (strncmp(dlg_marker, \"1\", 1) == 0);\n  }\n\n  auto it = gOFS->mTpcRdrInfo.find(is_delegated_tpc);\n\n  // If rdr info not present or if host is empty then skip\n  if ((it == gOFS->mTpcRdrInfo.end()) || (it->second.first.empty())) {\n    return false;\n  }\n\n  error.setErrInfo(it->second.second, it->second.first.c_str());\n  eos_info(\"msg=\\\"tpc %s redirect\\\" rdr_host=%s rdr_port=%i\",\n           is_delegated_tpc ? \"delegated\" : \"undelegated\",\n           it->second.first.c_str(), it->second.second);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Dump scheduling info\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfsFile::LogSchedulingInfo(const std::vector<unsigned int>& selected_fs,\n                                 const std::vector<std::string>& proxy_eps,\n                                 const std::vector<std::string>& fwall_eps) const\n{\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n\n  if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n    std::ostringstream oss;\n    oss << \"selectedfs: \";\n\n    for (const auto& elem : selected_fs) {\n      oss << elem << \"  \";\n    }\n\n    oss << \"proxys: \";\n\n    for (const auto& elem : proxy_eps) {\n      oss << elem << \"  \";\n    }\n\n    oss << \"firewallentrypoints: \";\n\n    for (const auto& elem : fwall_eps) {\n      oss << elem << \"  \";\n    }\n\n    eos_debug(\"msg=\\\"scheduling info %s\\\"\", oss.str().c_str());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get file system ids excluded from scheduling\n//------------------------------------------------------------------------------\nstd::vector<unsigned int>\nXrdMgmOfsFile::GetExcludedFsids() const\n{\n  std::vector<unsigned int> fsids;\n  std::string sfsids;\n\n  if (openOpaque) {\n    sfsids = (openOpaque->Get(\"eos.excludefsid\") ?\n              openOpaque->Get(\"eos.excludefsid\") : \"\");\n  }\n\n  if (sfsids.empty()) {\n    return fsids;\n  }\n\n  auto lst_ids = eos::common::StringTokenizer::split<std::list<std::string>>\n                 (sfsids, ',');\n\n  for (const auto& sid : lst_ids) {\n    try {\n      fsids.push_back(std::stoul(sid));\n    } catch (...) {}\n  }\n\n  return fsids;\n}\n"
  },
  {
    "path": "mgm/ofs/XrdMgmOfsFile.hh",
    "content": "// ----------------------------------------------------------------------\n// File: XrdMgmOfsFile.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file   XrdMgmOfsFile.hh\n *\n * @brief  XRootD OFS plugin class implementing file meta data handling of EOS\n *\n * Many functions in the MgmOfs interface take CGI parameters. The supported\n * CGI parameter are:\n * \"eos.ruid\" - uid role the client wants\n * \"eos.rgid\" - gid role the client wants\n * \"eos.space\" - space a user wants to use for scheduling a write\n * \"eos.checksum\" - checksum a file should have\n * \"eos.lfn\" - use this name as path name not the path parameter (used by prefix\n * redirector MGM's ...\n * \"eos.bookingsize\" - reserve the requested bytes in a file placement\n * \"eos.cli.access=pio\" - ask for a parallel open (changes the response of an\n * open for RAIN layouts)\n * \"eos.app\" - set the application name reported by monitoring\n * \"eos.targetsize\" - expected size of a file to be uploaded\n * \"eos.blockchecksum=ignore\" - disable block checksum verification\n *\n */\n/*----------------------------------------------------------------------------*/\n\n#ifndef __EOSMGM_MGMOFSFILE__HH__\n#define __EOSMGM_MGMOFSFILE__HH__\n\n#include \"common/Mapping.hh\"\n#include \"common/Logging.hh\"\n#include \"mgm/proc/IProcCommand.hh\"\n#include <XrdOuc/XrdOucErrInfo.hh>\n#include <XrdSfs/XrdSfsInterface.hh>\n\nUSE_EOSMGMNAMESPACE\n\n//! Forward declaration\nclass XrdSfsAio;\nclass XrdSecEntity;\n\n//------------------------------------------------------------------------------\n//! Class implementing files and operations\n//------------------------------------------------------------------------------\nclass XrdMgmOfsFile : public XrdSfsFile, eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  XrdMgmOfsFile(char* user = 0, int MonID = 0):\n    XrdSfsFile(user, MonID), eos::common::LogId()\n  {\n    vid = eos::common::VirtualIdentity::Nobody();\n  }\n\n  //----------------------------------------------------------------------------\n  // Destructor\n  //----------------------------------------------------------------------------\n  virtual ~XrdMgmOfsFile();\n\n  static int\n  handleHardlinkDelete(std::shared_ptr<eos::IContainerMD> cmd,\n                       std::shared_ptr<eos::IFileMD> fmd,\n                       eos::common::VirtualIdentity& vid);\n\n  //----------------------------------------------------------------------------\n  //----------------------------------------------------------------------------\n  // utility function: create copy-on-write clone\n  //----------------------------------------------------------------------------\n  static const int cowUpdate = 0;              // do copy, for file updates\n  static const int cowDelete = 1;              // do rename, for file deletes\n  static const int cowUnlink =\n    2;              // create hard link, when name vanishes e.g. Recycle\n  static int create_cow(int cowType,\n                        std::shared_ptr<eos::IContainerMD> dmd, std::shared_ptr<eos::IFileMD> fmd,\n                        eos::common::VirtualIdentity& vid, XrdOucErrInfo& error);\n\n  //----------------------------------------------------------------------------\n  // open a file\n  //----------------------------------------------------------------------------\n  int open(eos::common::VirtualIdentity* vid,\n           const char* fileName,\n           XrdSfsFileOpenMode openMode,\n           mode_t createMode,\n           const XrdSecEntity* client,\n           const char* opaque);\n\n  int open(const char* fileName,\n           XrdSfsFileOpenMode openMode,\n           mode_t createMode,\n           const XrdSecEntity* client = 0,\n           const char* opaque = 0)\n  {\n    return open(0, fileName, openMode, createMode, client, opaque);\n  }\n\n  //----------------------------------------------------------------------------\n  // close a file\n  //----------------------------------------------------------------------------\n  int close();\n\n  //----------------------------------------------------------------------------\n  //! get file name\n  //----------------------------------------------------------------------------\n\n  const char*\n  FName()\n  {\n    return fileName.c_str();\n  }\n\n\n  //----------------------------------------------------------------------------\n  //! return mmap address (we don't need it)\n  //----------------------------------------------------------------------------\n\n  int\n  getMmap(void** Addr, off_t& Size)\n  {\n    if (Addr) {\n      Addr = 0;\n    }\n\n    Size = 0;\n    return SFS_OK;\n  }\n\n  //----------------------------------------------------------------------------\n  //! file pre-read fakes ok (we don't need it)\n  //----------------------------------------------------------------------------\n  int read(XrdSfsFileOffset fileOffset, XrdSfsXferSize preread_sz)\n  {\n    return SFS_OK;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Read a partial result of a 'proc' interface command\n  //!\n  //! @param offset where to read from the result\n  //! @param buff buffer where to place the result\n  //! @param blen maximum size to read\n  //!\n  //! @return number of bytes read upon success or SFS_ERROR\n  //!\n  //! @note This read is only used to stream back 'proc' command results to\n  //! the EOS shell since all normal files get a redirection or error during\n  //! the file open.\n  //----------------------------------------------------------------------------\n  XrdSfsXferSize read(XrdSfsFileOffset offset, char* buff, XrdSfsXferSize blen);\n\n  //----------------------------------------------------------------------------\n  //! File read in async mode - NOT SUPPORTED\n  //----------------------------------------------------------------------------\n  int read(XrdSfsAio* aioparm)\n  {\n    static const char* epname = \"aioread\";\n    return Emsg(epname, error, EOPNOTSUPP, \"read aio - not supported\",\n                fileName.c_str());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Read file pages into a buffer and return corresponding checksums.\n  //!\n  //! @param  offset  - The offset where the read is to start. It may be\n  //!                   unaligned with certain caveats relative to csvec.\n  //! @param  buffer  - pointer to buffer where the bytes are to be placed.\n  //! @param  rdlen   - The number of bytes to read. The amount must be an\n  //!                   integral number of XrdSfsPage::Size bytes.\n  //! @param  csvec   - A vector of entries to be filled with the cooresponding\n  //!                   CRC32C checksum for each page. However, if the offset is\n  //!                   unaligned, then csvec[0] contains the crc for the page\n  //!                   fragment that brings it to alignment for csvec[1].\n  //!                   It must be sized to hold all aligned XrdSys::Pagesize\n  //!                   crc's plus additional ones for leading and ending page\n  //!                   fragments, if any.\n  //! @param  opts    - Processing options (see above).\n  //!\n  //! @return >= 0      The number of bytes that placed in buffer.\n  //! @return SFS_ERROR File could not be read, error holds the reason.\n  //----------------------------------------------------------------------------\n  XrdSfsXferSize pgRead(XrdSfsFileOffset offset, char* buffer,\n                        XrdSfsXferSize rdlen, uint32_t* csvec,\n                        uint64_t opts = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Read file pages and checksums using asynchronous I/O - NOT SUPPORTED\n  //!\n  //! @param  aioparm - Pointer to async I/O object controlling the I/O.\n  //! @param  opts    - Processing options (see above).\n  //!\n  //! @return SFS_OK    Request accepted and will be scheduled.\n  //! @return SFS_ERROR File could not be read, error holds the reason.\n  //-----------------------------------------------------------------------------\n  int pgRead(XrdSfsAio* aioparm, uint64_t opts = 0) override\n  {\n    static const char* epname = \"aioPgRead\";\n    return Emsg(epname, error, EOPNOTSUPP, \"pgRead aio - not supported\",\n                fileName.c_str());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Write to file - NOT SUPPORTED (no use case)\n  //----------------------------------------------------------------------------\n  XrdSfsXferSize write(XrdSfsFileOffset fileOffset, const char* buffer,\n                       XrdSfsXferSize buffer_size) override\n  {\n    static const char* epname = \"write\";\n    return Emsg(epname, error, EOPNOTSUPP, \"write - not supported\",\n                fileName.c_str());\n  }\n\n  //----------------------------------------------------------------------------\n  //! File write in async mode - NOT SUPPORTED\n  //----------------------------------------------------------------------------\n  int write(XrdSfsAio* aioparm)\n  {\n    static const char* epname = \"aiowrite\";\n    return Emsg(epname, error, EOPNOTSUPP, \"write aio - not supported\",\n                fileName.c_str());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Write file pages into a file with corresponding checksums - NOT SUPPORTED\n  //!\n  //! @param  offset  - The offset where the write is to start. It may be\n  //!                   unaligned with certain caveats relative to csvec.\n  //! @param  buffer  - pointer to buffer containing the bytes to write.\n  //! @param  wrlen   - The number of bytes to write. If amount is not an\n  //!                   integral number of XrdSys::PageSize bytes, then this must\n  //!                   be the last write to the file at or above the offset.\n  //! @param  csvec   - A vector which contains the corresponding CRC32 checksum\n  //!                   for each page or page fragment. If offset is unaligned\n  //!                   then csvec[0] is the crc of the leading fragment to\n  //!                   align the subsequent full page who's crc is in csvec[1].\n  //!                   It must be sized to hold all aligned XrdSys::Pagesize\n  //!                   crc's plus additional ones for leading and ending page\n  //!                   fragments, if any.\n  //! @param  opts    - Processing options (see above).\n  //!\n  //! @return >= 0      The number of bytes written.\n  //! @return SFS_ERROR File could not be read, error holds the reason.\n  //----------------------------------------------------------------------------\n  XrdSfsXferSize pgWrite(XrdSfsFileOffset offset, char* buffer,\n                         XrdSfsXferSize wrlen, uint32_t* csvec,\n                         uint64_t opts = 0) override\n  {\n    static const char* epname = \"PgWrite\";\n    return Emsg(epname, error, EOPNOTSUPP, \"pgWrite - not supported\",\n                fileName.c_str());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Write file pages and checksums using asynchronous I/O - NOT SUPPORTED\n  //!\n  //! @param  aioparm - Pointer to async I/O object controlling the I/O.\n  //! @param  opts    - Processing options (see above).\n  //!\n  //! @return SFS_OK    Request accepted and will be scheduled.\n  //! @return SFS_ERROR File could not be read, error holds the reason.\n  //----------------------------------------------------------------------------\n  int pgWrite(XrdSfsAio* aioparm, uint64_t opts = 0) override\n  {\n    static const char* epname = \"aioPgWrite\";\n    return Emsg(epname, error, EOPNOTSUPP, \"pgWrite aio - not supported\",\n                fileName.c_str());\n  }\n\n  //----------------------------------------------------------------------------\n  //! file sync\n  //----------------------------------------------------------------------------\n  int sync();\n\n  //----------------------------------------------------------------------------\n  //! file sync aio\n  //----------------------------------------------------------------------------\n  int sync(XrdSfsAio* aiop);\n\n  //----------------------------------------------------------------------------\n  // file stat\n  //----------------------------------------------------------------------------\n  int stat(struct stat* buf);\n\n  //----------------------------------------------------------------------------\n  // file truncate\n  //----------------------------------------------------------------------------\n  int truncate(XrdSfsFileOffset fileOffset);\n\n  //----------------------------------------------------------------------------\n  //! get checksum info (returns nothing - not supported)\n  //----------------------------------------------------------------------------\n  int\n  getCXinfo(char cxtype[4], int& cxrsz)\n  {\n    return cxrsz = 0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! fctl fakes ok\n  //----------------------------------------------------------------------------\n  int\n  fctl(int, const char*, XrdOucErrInfo&)\n  {\n    return 0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! error message function\n  //----------------------------------------------------------------------------\n  int Emsg(const char*, XrdOucErrInfo&, int, const char* x,\n           const char* y = \"\");\n\n  //----------------------------------------------------------------------------\n  //! Target connection parameters for redirection\n  //----------------------------------------------------------------------------\n  struct targetParams {\n    int          targetport;\n    int          targethttpport;\n    std::string  targethost;\n    std::string  redirectionsuffix;\n    std::string  redirectionhost;\n\n    bool validPort(int port) const {\n      return port > 0 && port < 65536;\n    }\n\n    bool valid() const {\n      return !targethost.empty() &&\n             validPort(targetport) && validPort(targethttpport);\n    }\n  };\n  //----------------------------------------------------------------------------\n  //! Handle Proxy and Firewall Entrypoint scheduling\n  //! This function may be deprecated in future versions\n  static targetParams setProxyFwEntrypoint(\n    const std::vector<std::string>& firewalleps,\n    const std::vector<std::string>& proxys,\n    size_t fsIndex,\n    std::string_view fs_hostport,\n    std::string_view fs_prefix\n  );\n\n#ifdef IN_TEST_HARNESS\npublic:\n#else\nprivate:\n#endif\n  //----------------------------------------------------------------------------\n  //! Check if this is a client retry with exclusion of some diskserver. This\n  //! happens usually for CMS workflows. To distinguish such a scenario from\n  //! a legitimate retry due to a recoverable error, we need to serarch for the\n  //! \"tried=\" opaque tag without a corresponding \"triedrc=\" tag.\n  //!\n  //! @param is_rw true if is RW otherwise false\n  //! @param lid file layout id\n  //!\n  //! @return true if this is a retry for a RAIN file with the user excluding\n  //!         some diskservers, otherwise false.\n  //----------------------------------------------------------------------------\n  bool IsRainRetryWithExclusion(bool is_rw, unsigned long lid) const;\n\n  //----------------------------------------------------------------------------\n  //! Parse the triedrc opaque info and return the corresponding error number\n  //!\n  //! @param input input string in the form of \"enoent,ioerr,fserr,srverr\"\n  //!\n  //! @return error number\n  //----------------------------------------------------------------------------\n  int GetTriedrcErrno(const std::string& input) const;\n\n  //----------------------------------------------------------------------------\n  //! Handle (delegated) TPC redirection\n  //!\n  //! @return true if redirection required (the error object will be properly\n  //!         populated with the redirection host and port info), otherwise\n  //!         false\n  //----------------------------------------------------------------------------\n  bool RedirectTpcAccess();\n\n  //----------------------------------------------------------------------------\n  //! Dump scheduling info\n  //!\n  //! @param selected_fs list of selected file systems\n  //! @param proxys list of data proxy endpoints\n  //! @param fwall_eps firewall entrypoints\n  //----------------------------------------------------------------------------\n  void LogSchedulingInfo(const std::vector<unsigned int>& selected_fs,\n                         const std::vector<std::string>& proxy_eps,\n                         const std::vector<std::string>& fwall_eps) const;\n\n  //----------------------------------------------------------------------------\n  //! Get file system ids excluded from scheduling\n  //!\n  //! return list of file system ids to exclude\n  //----------------------------------------------------------------------------\n  std::vector<unsigned int>\n  GetExcludedFsids() const;\n\n  //----------------------------------------------------------------------------\n  //! Get the application name if specified\n  //!\n  //! @param open_opaque open opaque information\n  //! @param client XrdSecEntity identifying the request\n  //!\n  //! @return application name or empty string if nothing specified\n  //----------------------------------------------------------------------------\n  static const std::string GetClientApplicationName(XrdOucEnv* open_opaque,\n      const XrdSecEntity* client);\n\n  //----------------------------------------------------------------------------\n  //! Get POSIX open flags from the given XRootD open mode\n  //!\n  //! @param open_mode XRootD open mode see XrdSfsInterface.hh\n  //!\n  //! @return POSIX open flags see man 2 open\n  //----------------------------------------------------------------------------\n  static int\n  GetPosixOpenFlags(XrdSfsFileOpenMode open_mode);\n\n  //----------------------------------------------------------------------------\n  //! Get XRootD acceess operation bases on the given open flags\n  //!\n  //! @param open_flags POSIX open flags\n  //!\n  //! @return access opeation type see XrdAccAuthorization.hh\n  //----------------------------------------------------------------------------\n  static Access_Operation\n  GetXrdAccessOperation(int open_flags);\n\n  int oh {0}; //< file handle\n  std::string fileName; //< file name\n  XrdOucEnv* openOpaque {nullptr}; //< opaque info given with 'open'\n  unsigned long long mFid {0ull}; //< Namespace file identifier\n  std::unique_ptr<IProcCommand> mProcCmd {nullptr}; // Proc command object\n  std::shared_ptr<eos::IFileMD> fmd {nullptr}; //< File meta data object\n  eos::common::VirtualIdentity vid; //< virtual ID of the client\n  std::string mEosKey; ///< File specific encryption key\n  //! Flag to toggle obfuscation (-1 take directory default, 0 disable, 1 enable)\n  int mEosObfuscate { -1};\n  bool mIsZeroSize {false}; //< Mark if file is zero size\n};\n\n#endif\n"
  },
  {
    "path": "mgm/ofs/XrdMgmOfsSecurity.hh",
    "content": "// ----------------------------------------------------------------------\n// File: XrdMgmOfsSecurity.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef ___EOSMGM_SECURITY_H__\n#define ___EOSMGM_SECURITY_H__\n\n#include <XrdAcc/XrdAccAuthorize.hh>\n\n#define AUTHORIZE(usr, env, optype, action, pathp, edata)               \\\n  if (usr && gOFS->mExtAuthz                                            \\\n      &&  !gOFS->mExtAuthz->Access(usr, pathp, optype, env))            \\\n    {gOFS->Emsg(epname, edata, EACCES, action, pathp); return SFS_ERROR;}\n\n#define AUTHORIZE2(usr,edata,opt1,act1,path1,env1,opt2,act2,path2,env2) \\\n  {AUTHORIZE(usr, env1, opt1, act1, path1, edata);                      \\\n    AUTHORIZE(usr, env2, opt2, act2, path2, edata);                     \\\n  }\n\n#define OOIDENTENV(usr, env)                                    \\\n  if (usr) {if (usr->name) env.Put(SEC_USER, usr->name);        \\\n    if (usr->host) env.Put(SEC_HOST, usr->host);}\n#endif\n"
  },
  {
    "path": "mgm/ofs/XrdMgmOfsTrace.hh",
    "content": "// ----------------------------------------------------------------------\n// File: XrdMgmOfsTrace.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM__MGMOFSTRACE_H__\n#define __EOSMGM__MGMOFSTRACE_H__\n\n#ifndef NODEBUG\n\n#include <iostream>\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n\n#define GTRACE(act)         gMgmOfsTrace.What & TRACE_ ## act\n\n#define TRACES(x)                                                       \\\n  {gMgmOfsTrace.Beg(epname,tident); std::cerr <<x; gMgmOfsTrace.End();}\n\n#define FTRACE(act, x)                          \\\n  if (GTRACE(act))                              \\\n    TRACES(x <<\" fn=\" << (oh->Name()))\n\n#define XTRACE(act, target, x)                  \\\n  if (GTRACE(act)) TRACES(x <<\" fn=\" <<target)\n\n#define ZTRACE(act, x) if (GTRACE(act)) TRACES(x)\n\n#define DEBUG(x) if (GTRACE(debug)) TRACES(x)\n\n#define EPNAME(x) static const char *epname = x;\n\n#else\n\n#define FTRACE(x, y)\n#define GTRACE(x)    0\n#define TRACES(x)\n#define XTRACE(x, y, a1)\n#define YTRACE(x, y, a1, a2, a3, a4, a5)\n#define ZTRACE(x, y)\n#define DEBUG(x)\n#define EPNAME(x)\n\n#endif\n\n// Trace flags\n//\n#define TRACE_MOST     0x3fcd\n#define TRACE_ALL      0x8ffffff\n#define TRACE_opendir  0x0001\n#define TRACE_readdir  0x0002\n#define TRACE_closedir TRACE_opendir\n#define TRACE_delay    0x0400\n#define TRACE_dir      TRACE_opendir | TRACE_readdir | TRACE_closedir\n#define TRACE_open     0x0004\n#define TRACE_qscan    0x0008\n#define TRACE_close    TRACE_open\n#define TRACE_read     0x0010\n#define TRACE_redirect 0x0800\n#define TRACE_write    0x0020\n#define TRACE_IO       TRACE_read | TRACE_write | TRACE_aio\n#define TRACE_exists   0x0040\n#define TRACE_chmod    TRACE_exists\n#define TRACE_getmode  TRACE_exists\n#define TRACE_getsize  TRACE_exists\n#define TRACE_remove   0x0080\n#define TRACE_rename   TRACE_remove\n#define TRACE_sync     0x0100\n#define TRACE_truncate 0x0200\n#define TRACE_fsctl    0x0400\n#define TRACE_getstats 0x0800\n#define TRACE_mkdir    0x1000\n#define TRACE_stat     0x2000\n#define TRACE_aio      0x4000\n#define TRACE_debug    0x8000\n#define TRACE_authorize 0x10000\n#define TRACE_map      0x20000\n#define TRACE_role     0x40000\n#define TRACE_access   0x80000\n#define TRACE_attributes   0x100000\n#define TRACE_allows   0x200000\n#define TRACE_stager   0x400000\n#define TRACE_prepare  0x800000\n#endif\n"
  },
  {
    "path": "mgm/ofs/cmds/Access.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Access.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n//! Check access permissions for files/directories - XRootD API\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::access(const char* inpath,\n                  int mode,\n                  XrdOucErrInfo& error,\n                  const XrdSecEntity* client,\n                  const char* ininfo)\n{\n  static const char* epname = \"access\";\n  const char* tident = error.getErrUser();\n  NAMESPACEMAP;\n  BOUNCE_ILLEGAL_NAMES;\n  XrdOucEnv access_Env(ininfo);\n  AUTHORIZE(client, &access_Env, AOP_Stat, \"access\", inpath, error);\n  // use a thread private vid\n  eos::common::VirtualIdentity vid;\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, ininfo, tident, vid,\n                              gOFS->mTokenAuthz, AOP_Stat, inpath);\n  EXEC_TIMING_END(\"IdMap\");\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  BOUNCE_NOT_ALLOWED;\n  TOKEN_SCOPE;\n  ACCESSMODE_R;\n  MAYSTALL;\n  MAYREDIRECT;\n  return _access(path, mode, error, vid, ininfo);\n}\n\n//------------------------------------------------------------------------------\n// Check access permissions for file/directories - EOS low-level API\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::_access(const char* path, int mode, XrdOucErrInfo& error,\n                   eos::common::VirtualIdentity& vid, const char* info)\n{\n  static const char* epname = \"_access\";\n  eos_debug(\"path=%s mode=%x uid=%u gid=%u\", path, mode, vid.uid, vid.gid);\n  gOFS->MgmStats.Add(\"Access\", vid.uid, vid.gid, 1);\n  eos::common::Path cPath(path);\n  bool permok = false;\n  std::string attr_path = cPath.GetPath();\n  std::shared_ptr<eos::IFileMD> fh;\n  std::shared_ptr<eos::IContainerMD> dh;\n  mode_t dh_mode {0};\n  bool is_owner=false;\n  eos::Prefetcher::prefetchItemAndWait(gOFS->eosView, cPath.GetPath());\n\n  // Check for existing file\n  try {\n    fh = gOFS->eosView->getFile(cPath.GetPath());\n  } catch (eos::MDException& e) {\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\", e.getErrno(),\n              e.getMessage().str().c_str());\n  }\n\n  try {\n    dh = gOFS->eosView->getContainer(cPath.GetPath());\n  } catch (eos::MDException& e) {\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\", e.getErrno(),\n              e.getMessage().str().c_str());\n  }\n\n  errno = 0;\n\n  try {\n    eos::IContainerMD::XAttrMap attrmap;\n    eos::IContainerMD::XAttrMap fattrmap;\n\n    if (fh || (!dh)) {\n      std::string uri;\n\n      // if this is a file or a not existing directory we check the access on the parent directory\n      if (fh) {\n        // Do not lock the file when calling getUri()!\n        uri = gOFS->eosView->getUri(fh.get());\n        // No need to explicitely lock the file as there is only one operation related to it.\n        fattrmap = fh->getAttributes();\n      } else {\n        uri = cPath.GetPath();\n      }\n\n      eos::common::Path pPath(uri.c_str());\n      dh = gOFS->eosView->getContainer(pPath.GetParentPath());\n      attr_path = pPath.GetParentPath();\n    }\n\n    // ACL and permission check\n    Acl acl(attr_path.c_str(), error, vid, attrmap);\n\n    // Take into account file acls\n    if (fattrmap.size()) {\n      acl.SetFromAttrMap(attrmap, vid, &fattrmap);\n    }\n\n    eos_info(\"acl=%d r=%d w=%d wo=%d x=%d egroup=%d \"\n             \"mutable=%d can_not_delete=%d token-issuer=%d\",\n             acl.HasAcl(), acl.CanRead(), acl.CanWrite(),\n             acl.CanWriteOnce(), acl.CanBrowse(), acl.HasEgroup(),\n             acl.IsMutable(), acl.CanNotDelete(), acl.CanIssueToken());\n    {\n      // In any case, we need to check the container access, read lock it to\n      // check its access and release its lock afterwards\n      eos::MDLocking::ContainerReadLockPtr dhLock = eos::MDLocking::readLock(\n            dh.get());\n      dh_mode = dh->getMode();\n      is_owner = (dh->getCUid() == vid.uid);\n\n      if (fh) {\n\tis_owner = (fh->getCUid() == vid.uid);\n      }\n\n      // check if people who don't own a file or directory can issue token\n      if (!is_owner && (mode & T_OK) && !acl.CanIssueToken()) {\n\terrno = EPERM;\n\treturn Emsg(epname, error, EPERM, \"access - you cannot issue tokens\", path);\n      }\n\n      if (!AccessChecker::checkContainer(dh.get(), acl, mode, vid)) {\n        bool deny = true;\n\n        if ((mode & D_OK) && acl.HasAcl() && acl.CanNotDelete()) {\n          // The container cannot be accessed for deletion, however if the acl has !d, we need\n          // to allow the owner of the file to delete it\n          if (fh) {\n            // Unlock the container before calling the file' getter\n            dhLock.reset();\n\n            if (fh->getCUid() == vid.uid) {\n              deny = false;\n            }\n          }\n        }\n\n        if (deny) {\n          errno = EPERM;\n          return Emsg(epname, error, EPERM, \"access\", path);\n        }\n      }\n    }\n\n    if (fh) {\n      // Check the file access, read lock it before and release the lock afterwards\n      eos::MDLocking::FileReadLock fhLock(fh.get());\n\n      if (!AccessChecker::checkFile(fh.get(), mode, dh_mode, vid)) {\n        errno = EPERM;\n        return Emsg(epname, error, EPERM, \"access\", path);\n      }\n    }\n\n    permok = true;\n  } catch (eos::MDException& e) {\n    dh.reset();\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n              e.getErrno(), e.getMessage().str().c_str());\n  }\n\n  // Check permissions\n  if (!dh) {\n    eos_debug(\"msg=\\\"access\\\" errno=ENOENT\");\n    errno = ENOENT;\n    return Emsg(epname, error, ENOENT, \"access\", path);\n  }\n\n  // root/daemon can always access, daemon only for reading!\n  if ((vid.uid == 0) || ((vid.uid == DAEMONUID) && (!(mode & W_OK)))) {\n    permok = true;\n  }\n\n  if (dh) {\n    eos_debug(\"msg=\\\"access\\\" uid=%d gid=%d retc=%d mode=%o\",\n              vid.uid, vid.gid, permok, dh_mode);\n  }\n\n  if (dh && (mode & F_OK)) {\n    return SFS_OK;\n  }\n\n  if (dh && permok) {\n    return SFS_OK;\n  }\n\n  if (dh && (!permok)) {\n    errno = EACCES;\n    return Emsg(epname, error, EACCES, \"access\", path);\n  }\n\n  // check publicaccess level\n  if (!allow_public_access(path, vid)) {\n    errno = EACCES;\n    return Emsg(epname, error, EACCES, \"access - public access level restriction\",\n                path);\n  }\n\n  errno = EOPNOTSUPP;\n  return Emsg(epname, error, EOPNOTSUPP, \"access\", path);\n}\n\n//------------------------------------------------------------------------------\n// Define access permissions by vid for a file/directory\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::acc_access(const char* path,\n                      XrdOucErrInfo& error,\n                      eos::common::VirtualIdentity& vid,\n                      std::string& accperm)\n{\n  eos_debug(\"path=\\\"%s\\\" mode=%x uid=%u gid=%u\", path, vid.uid, vid.gid);\n  gOFS->MgmStats.Add(\"Access\", vid.uid, vid.gid, 1);\n  eos::common::Path cPath(path);\n  std::shared_ptr<eos::IContainerMD> dh;\n  std::shared_ptr<eos::IFileMD> fh;\n  std::string attr_path = cPath.GetPath();\n  uid_t dhCuid;\n  bool r_ok = false;\n  bool w_ok = false;\n  bool x_ok = false;\n  bool d_ok = false;\n  bool d_perm_ok = false;\n  // ---------------------------------------------------------------------------\n  eos::Prefetcher::prefetchItemAndWait(gOFS->eosView, cPath.GetPath());\n\n  // check for existing file\n  try {\n    fh = gOFS->eosView->getFile(cPath.GetPath());\n  } catch (eos::MDException& e) {\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n              e.getMessage().str().c_str());\n  }\n\n  if (!fh) {\n    // check for existing dir if not a file\n    try {\n      dh = gOFS->eosView->getContainer(cPath.GetPath());\n    } catch (eos::MDException& e) {\n      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                e.getMessage().str().c_str());\n    }\n  }\n\n  try {\n    eos::IContainerMD::XAttrMap attrmap;\n\n    if (fh || (!dh)) {\n      std::string uri;\n\n      // if this is a file or a not existing directory we check the access on the parent directory\n      if (fh) {\n        // Do not lock the file before calling getUri()\n        uri = gOFS->eosView->getUri(fh.get());\n      } else {\n        uri = cPath.GetPath();\n      }\n\n      eos::common::Path pPath(uri.c_str());\n      dh = gOFS->eosView->getContainer(pPath.GetParentPath());\n      attr_path = pPath.GetParentPath();\n    }\n\n    std::set<gid_t> gids;\n\n    if (eos::common::Mapping::gSecondaryGroups) {\n      gids = vid.allowed_gids;\n    } else {\n      gids.insert(vid.gid);\n    }\n\n    std::unique_ptr<Acl> aclPtr;\n    {\n      eos::MDLocking::ContainerReadLock dhLock(dh.get());\n\n      dhCuid = dh->getCUid();\n      if (!vid.token) {\n\tfor (auto g : gids) {\n\t  if (dh->access(vid.uid, g, R_OK)) {\n\t    r_ok = true;\n\t  }\n\n\t  if (dh->access(vid.uid, g, W_OK)) {\n\t    w_ok = true;\n\t    d_ok = true;\n\t    d_perm_ok = true;\n\t  }\n\n\t  if (dh->access(vid.uid, g, X_OK)) {\n\t    x_ok = true;\n\t  }\n\t}\n      }\n\n      //We prevent releasing the directory lock before calling the ACL constructor that will do\n      //an _attr_ls on the directory\n      aclPtr = std::make_unique<Acl>(attr_path.c_str(), error, vid, attrmap);\n    }\n    // ACL and permission check\n    Acl& acl = *aclPtr;\n    eos_debug(\"acl=%d r=%d w=%d wo=%d x=%d egroup=%d mutable=%d path=\\\"%s\\\"\",\n              acl.HasAcl(), acl.CanRead(), acl.CanWrite(), acl.CanWriteOnce(),\n              acl.CanBrowse(), acl.HasEgroup(), acl.IsMutable(), path);\n\n    // browse permission by ACL\n    if (acl.HasAcl()) {\n      if (acl.CanWrite()) {\n        w_ok = true;\n        d_ok = true;\n      }\n\n      // write-once or write is fine for OC write permission\n      if (!(acl.CanWrite() || acl.CanWriteOnce())) {\n        w_ok = false;\n      }\n\n      // deletion might be overwritten/forbidden\n      if (acl.CanNotDelete()) {\n        d_ok = false;\n        // The user was probably allowed to delete from above logic, but the ACL prevents them to do so\n        // however if this user is the owner of the directory, they should\n        // be able to delete if the permissions of the directory allow it\n        if(dhCuid == vid.uid && d_perm_ok) {\n          d_ok = true;\n        }\n      }\n\n      // the r/x are added to the posix permissions already set\n      if (acl.CanRead()) {\n        r_ok |= true;\n      }\n\n      if (acl.CanBrowse()) {\n        x_ok |= true;\n      }\n\n      if (!acl.IsMutable()) {\n        w_ok = d_ok = false;\n      }\n    }\n  } catch (eos::MDException& e) {\n    dh = std::shared_ptr<eos::IContainerMD>((eos::IContainerMD*)0);\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n              e.getErrno(), e.getMessage().str().c_str());\n  }\n\n  // check permissions\n  if (!dh) {\n    accperm = \"\";\n    return SFS_ERROR;\n  }\n\n  // return the OC string;\n  if (r_ok) {\n    accperm += \"R\";\n  }\n\n  if (w_ok) {\n    accperm += \"WCKNV\";\n  }\n\n  if (d_ok) {\n    accperm += \"D\";\n  }\n\n  return SFS_OK;\n}\n\n\n\n//------------------------------------------------------------------------------\n// Test if this is eosnobody accessing a squashfs file\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::is_squashfs_access(const char* path,\n                              eos::common::VirtualIdentity& vid)\n{\n  static int errc = 0;\n  static int eosnobody = eos::common::Mapping::UserNameToUid(\n                           std::string(\"eosnobody\"), errc);\n\n  if ((vid.prot == \"sss\") &&\n      (eosnobody == vid.uid) && !errc) {\n    // eosnobody can access all squash files\n    eos::common::Path cPath(path);\n\n    if (!cPath.isSquashFile()) {\n      errno = EACCES;\n      return 1;\n    }\n\n    return 2;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Test if public access is allowed for a given path\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::allow_public_access(const char* path,\n                               eos::common::VirtualIdentity& vid)\n{\n  int sq = is_squashfs_access(path, vid);\n\n  if (sq == 2) {\n    // eosnobody squashfs file access is allowed\n    return true;\n  }\n\n  if (sq == 1) {\n    // eosnobody access is not allowed in general\n    return false;\n  }\n\n  // Check only for anonymous access\n  // uid=99 for CentOS7\n  // uid=65534 for >= Alma 9\n  if ((vid.uid != 99) && (vid.uid != 65534)) {\n    return true;\n  }\n\n  // check publicaccess level\n  int level = eos::common::Mapping::GetPublicAccessLevel();\n\n  if (level >= 1024) {\n    // short cut\n    return true;\n  }\n\n  eos::common::Path cPath(path);\n\n  if ((int)cPath.GetSubPathSize() >= level) {\n    // forbid everything to nobody in that case\n    errno = EACCES;\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Get the allowed XrdAccPrivs i.e. allowed operations on the given path\n// for the client in the XrdSecEntity\n//------------------------------------------------------------------------------\nXrdAccPrivs\nXrdMgmOfs::GetXrdAccPrivs(const std::string& path, const XrdSecEntity* client,\n                          XrdOucEnv* env)\n{\n  std::string eos_path;\n  eos::common::VirtualIdentity vid;\n  auto basic_checks = [&, this]() -> int {\n    const char* epname = \"access\";\n    char* ininfo = nullptr;\n    XrdOucErrInfo error;\n    const char* inpath = path.c_str();\n    const char* tident = client->tident;\n    NAMESPACEMAP;\n    BOUNCE_ILLEGAL_NAMES;\n    AUTHORIZE(client, env, AOP_Stat, \"access\", inpath, error);\n\n    EXEC_TIMING_BEGIN(\"IdMap\");\n    eos::common::Mapping::IdMap(client, ininfo, tident, vid);\n    EXEC_TIMING_END(\"IdMap\");\n    gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n    BOUNCE_NOT_ALLOWED;\n    ACCESSMODE_R;\n    MAYSTALL;\n    MAYREDIRECT;\n    eos_path = path;\n    return SFS_OK;\n  };\n\n  if (basic_checks()) {\n    eos_err(\"msg=\\\"failed basic checks for access privilege resolution\\\" \"\n            \"path=\\\"%s\\\", user=\\\"%s\\\"\", path.c_str(),\n            (client->name ? client->name : \"\"));\n    return XrdAccPriv_None;\n  }\n\n  return XrdAccPriv_All;\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Attr.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Attr.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n//------------------------------------------------------------------------------\n\n#include \"mgm/ofs/XrdMgmOfsTrace.hh\"\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/misc/AuditHelpers.hh\"\n\n//------------------------------------------------------------------------------\n// List extended attributes for a given file/directory - high-level API.\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::attr_ls(const char* inpath,\n                   XrdOucErrInfo& error,\n                   const XrdSecEntity* client,\n                   const char* ininfo,\n                   eos::IContainerMD::XAttrMap& map)\n\n{\n  static const char* epname = \"attr_ls\";\n  const char* tident = error.getErrUser();\n  eos::common::VirtualIdentity vid;\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz,\n                              AOP_Read, inpath);\n  EXEC_TIMING_END(\"IdMap\");\n  NAMESPACEMAP;\n  BOUNCE_ILLEGAL_NAMES;\n  XrdOucEnv access_Env(ininfo);\n  AUTHORIZE(client, &access_Env, AOP_Stat, \"access\", inpath, error);\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  BOUNCE_NOT_ALLOWED;\n  return _attr_ls(path, error, vid, ininfo, map);\n}\n\n//------------------------------------------------------------------------------\n// List extended attributes for a given file/directory - low-level API.\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::_attr_ls(const char* path, XrdOucErrInfo& error,\n                    const eos::common::VirtualIdentity& vid,\n                    const char* info, eos::IContainerMD::XAttrMap& map,\n                    bool links)\n{\n  static const char* epname = \"attr_ls\";\n  EXEC_TIMING_BEGIN(\"AttrLs\");\n  gOFS->MgmStats.Add(\"AttrLs\", vid.uid, vid.gid, 1);\n  errno = 0;\n  eos::Prefetcher::prefetchItemAndWait(gOFS->eosView, path);\n\n  try {\n    eos::FileOrContainerMD item = gOFS->eosView->getItem(path).get();\n    listAttributes(gOFS->eosView, item, map, links);\n    // we never show obfuscate key\n    map.erase(eos::kAttrObfuscateKey);\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n              e.getMessage().str().c_str());\n  }\n\n  EXEC_TIMING_END(\"AttrLs\");\n\n  if (errno) {\n    return Emsg(epname, error, errno, \"list attributes\", path);\n  }\n\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Get an extended attribute for a given entry by key - high-level API.\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::attr_get(const char* inpath, XrdOucErrInfo& error,\n                    const XrdSecEntity* client, const char* ininfo,\n                    const char* key, std::string& value)\n{\n  static const char* epname = \"attr_get\";\n  const char* tident = error.getErrUser();\n  eos::common::VirtualIdentity vid;\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz,\n                              AOP_Read, inpath);\n  EXEC_TIMING_END(\"IdMap\");\n  NAMESPACEMAP;\n  BOUNCE_ILLEGAL_NAMES;\n  XrdOucEnv access_Env(ininfo);\n  AUTHORIZE(client, &access_Env, AOP_Stat, \"access\", inpath, error);\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  BOUNCE_NOT_ALLOWED;\n  return _attr_get(path, error, vid, ininfo, key, value);\n}\n\n//------------------------------------------------------------------------------\n// Get extended attribute for a given metadata object - low-level API.\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::_attr_get(eos::FileOrContainerMD& item, std::string key,\n                     std::string& rvalue)\n{\n  if (item.file) {\n    return getAttribute(gOFS->eosView, *item.file.get(), key, rvalue);\n  } else if (item.container) {\n    return getAttribute(gOFS->eosView, *item.container.get(), key, rvalue);\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Get extended attribute for a given file - low-level API.\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::_attr_get(eos::IFileMD& fmd, std::string key, std::string& rvalue)\n{\n  return getAttribute(gOFS->eosView, fmd, key, rvalue);\n}\n\n//------------------------------------------------------------------------------\n// Get extended attribute for a given container - low-level API.\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::_attr_get(eos::IContainerMD& cmd, std::string key,\n                     std::string& rvalue)\n{\n  return gOFS->getAttribute(gOFS->eosView, cmd, key, rvalue);\n}\n\n//------------------------------------------------------------------------------\n// Get an extended attribute for a given entry by key - low-level API.\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::_attr_get(const char* path, XrdOucErrInfo& error,\n                     eos::common::VirtualIdentity& vid,\n                     const char* info, const char* key, std::string& value)\n{\n  static const char* epname = \"attr_get\";\n  EXEC_TIMING_BEGIN(\"AttrGet\");\n  gOFS->MgmStats.Add(\"AttrGet\", vid.uid, vid.gid, 1);\n  errno = 0;\n  value.clear();\n\n  if (!key || (strlen(key) == 0)) {\n    errno = EINVAL;\n    return Emsg(epname, error, errno, \"get attribute\", path);\n  }\n\n  const std::string skey = key;\n\n  // Never return the obfuscate key\n  if (skey == eos::kAttrObfuscateKey) {\n    return SFS_OK;\n  }\n\n  eos::Prefetcher::prefetchItemAndWait(gOFS->eosView, path);\n\n  try {\n    eos::FileOrContainerMD item = gOFS->eosView->getItem(path).get();\n\n    if (item.file) {\n      std::shared_ptr<eos::IFileMD> fmd = item.file;\n      eos::MDLocking::FileReadLock fmd_lock(fmd.get());\n\n      if (!_attr_get(*fmd.get(), skey, value)) {\n        errno = ENODATA;\n      }\n    } else {\n      std::shared_ptr<eos::IContainerMD> cmd = item.container;\n      eos::MDLocking::ContainerReadLock cmd_lock(cmd.get());\n\n      if (!_attr_get(*cmd.get(), skey, value)) {\n        errno = ENODATA;\n      }\n    }\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n              e.getMessage().str().c_str());\n  }\n\n  EXEC_TIMING_END(\"AttrGet\");\n\n  if (errno) {\n    return Emsg(epname, error, errno, \"get attributes\", path);\n  }\n\n  // Always decode attribute, even if they are not stores as base64\n  std::string val64 = value;\n  eos::common::SymKey::DeBase64(val64, value);\n\n  if (info) {\n    // Check if base64 encoding is requested\n    XrdOucEnv env(info);\n    const char* ptr = env.Get(\"eos.attr.val.encoding\");\n\n    if (ptr && (strncmp(ptr, \"base64\", 6) == 0)) {\n      std::string nb64 = value;\n      eos::common::SymKey::Base64(nb64, value);\n    }\n  }\n\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Set an extended attribute for a given file/directory - high-level API.\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::attr_set(const char* inpath, XrdOucErrInfo& error,\n                    const XrdSecEntity* client, const char* ininfo,\n                    const char* key, const char* value)\n{\n  static const char* epname = \"attr_set\";\n  const char* tident = error.getErrUser();\n  eos::common::VirtualIdentity vid;\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz,\n                              AOP_Update, inpath);\n  EXEC_TIMING_END(\"IdMap\");\n  NAMESPACEMAP;\n  BOUNCE_ILLEGAL_NAMES;\n  XrdOucEnv access_Env(ininfo);\n  AUTHORIZE(client, &access_Env, AOP_Update, \"update\", inpath, error);\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  BOUNCE_NOT_ALLOWED;\n  return _attr_set(path, error, vid, ininfo, key, value);\n}\n\n//------------------------------------------------------------------------------\n// Set an extended attribute for a given file/directory - low-level API.\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::_attr_set(const char* path, XrdOucErrInfo& error,\n                     eos::common::VirtualIdentity& vid,\n                     const char* info, const char* key, const char* value,\n                     bool exclusive)\n{\n  static const char* epname = \"attr_set\";\n  EXEC_TIMING_BEGIN(\"AttrSet\");\n  gOFS->MgmStats.Add(\"AttrSet\", vid.uid, vid.gid, 1);\n  errno = 0;\n\n  if (!key || (strlen(key) == 0) || !value) {\n    errno = EINVAL;\n    return Emsg(epname, error, errno, \"set attribute (missing/empty)\", path);\n  }\n\n  const std::string skey = key;\n\n  // Never put any attribute on version directories\n  if ((strstr(path, EOS_COMMON_PATH_VERSION_PREFIX) != 0) &&\n      ((skey.find(\"sys.forced\") == 0) ||\n       (skey.find(\"user.forced\") == 0))) {\n    return SFS_OK;\n  }\n\n  // Base64 decode if necessary i.e. input value is prefixed with \"base64:\"\n  std::string raw_val;\n  (void) eos::common::SymKey::DeBase64(value, raw_val);\n\n  // For ACL attr then check validity and convert to numeric format\n  if ((skey.find(\"sys.acl\") == 0) || (skey.find(\"user.acl\") == 0)) {\n    bool is_sys_acl = (skey.find(\"sys.acl\") == 0);\n\n    // Check format of acl\n    if (!Acl::IsValid(raw_val, error, is_sys_acl) &&\n        !Acl::IsValid(raw_val, error, is_sys_acl, true)) {\n      errno = EINVAL;\n      eos_static_err(\"msg=\\\"invalid acl value\\\" value=\\\"%s\\\"\", raw_val.c_str());\n      return Emsg(epname, error, errno, \"set attribute (invalid acl format)\", path);\n    }\n\n    // Convert to numeric representation\n    if (Acl::ConvertIds(raw_val)) {\n      errno = EINVAL;\n      eos_static_err(\"msg=\\\"invalid acl value\\\" value=\\\"%s\\\"\", raw_val.c_str());\n      return Emsg(epname, error, errno, \"set attribute (failed id convert)\", path);\n    }\n  }\n\n  eos::mgm::FusexCastBatch fuse_batch;\n  eos::Prefetcher::prefetchItemAndWait(gOFS->eosView, path);\n\n  try {\n    eos::FileOrContainerMD item = gOFS->eosView->getItem(path).get();\n    // Capture previous value if any\n    std::string prev_value;\n    {\n      if (item.file) {\n        auto amap = item.file->getAttributes();\n\n        if (amap.count(skey)) {\n          prev_value = amap[skey];\n        }\n      } else if (item.container) {\n        auto amap = item.container->getAttributes();\n\n        if (amap.count(skey)) {\n          prev_value = amap[skey];\n        }\n      }\n    }\n    eos::FileOrContWriteLocked item_wlock;\n\n    if (item.file) {\n      item_wlock.fileLock = eos::MDLocking::writeLock(item.file.get());\n    } else {\n      item_wlock.containerLock = eos::MDLocking::writeLock(item.container.get());\n    }\n\n    if (!_attr_set(item, skey, raw_val, exclusive, vid, fuse_batch)) {\n      if (errno == EEXIST) {\n        return Emsg(epname, error, errno, \"set attribute (exclusive set for\"\n                    \" existing attribute)\", path);\n      } else if (errno == EBUSY) {\n        return Emsg(epname, error, errno, \"set attribute (foreign attribute\"\n                    \" lock existing)\", path);\n      }\n    }\n\n    // Fetch new value\n    std::string new_value;\n    {\n      if (item.file) {\n        auto amap = item.file->getAttributes();\n\n        if (amap.count(skey)) {\n          new_value = amap[skey];\n        }\n      } else if (item.container) {\n        auto amap = item.container->getAttributes();\n\n        if (amap.count(skey)) {\n          new_value = amap[skey];\n        }\n      }\n    }\n\n    // Emit audit for attribute set\n    if (mAudit) {\n      if (gOFS->AllowAuditModification(path)) mAudit->audit(eos::audit::SET_XATTR,\n            path, vid, std::string(logId), std::string(cident), \"mgm\",\n            std::string(), nullptr, nullptr, skey, prev_value, new_value, __FILE__,\n            __LINE__, VERSION);\n\n      if ((skey == \"sys.acl\" || skey == \"user.acl\") &&\n          gOFS->AllowAuditModification(path)) {\n        mAudit->audit(eos::audit::SET_ACL, path, vid, std::string(logId),\n                      std::string(cident), \"mgm\",\n                      std::string(), nullptr, nullptr, skey, prev_value, new_value, __FILE__,\n                      __LINE__, VERSION);\n      }\n    }\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n              e.getErrno(), e.getMessage().str().c_str());\n  }\n\n  EXEC_TIMING_END(\"AttrSet\");\n\n  if (errno) {\n    return Emsg(epname, error, errno, \"set attributes\", path);\n  }\n\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Set an extended attribute for a given ContainerMD - low-level API.\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::_attr_set(eos::FileOrContainerMD& item, std::string_view key,\n                     std::string_view value, bool exclusive,\n                     eos::common::VirtualIdentity& vid,\n                     eos::mgm::FusexCastBatch& fuse_batch)\n{\n  eos::IContainerMD::XAttrMap attr_map;\n  bool has_attribute = false;\n  uid_t cuid;\n\n  if (item.file) {\n    cuid = item.file->getCUid();\n    attr_map = item.file->getAttributes();\n    has_attribute = item.file->hasAttribute(key.data());\n  } else {\n    cuid = item.container->getCUid();\n    attr_map = item.container->getAttributes();\n    has_attribute = item.container->hasAttribute(key.data());\n  }\n\n  Acl acl(attr_map, vid);\n\n  if ((vid.uid != cuid) && !acl.AllowXAttrUpdate(key, vid)) {\n    errno = EPERM;\n    return false;\n  }\n\n  if (exclusive && has_attribute) {\n    errno = EEXIST;\n    return false;\n  }\n\n  if (item.file) {\n    // Handle attribute for application locks\n    if (key == eos::common::EOS_APP_LOCK_ATTR) {\n      errno = 0;\n      XattrLock app_lock(attr_map);\n\n      if (app_lock.foreignLock(vid, true)) {\n        errno = EBUSY;\n        return false;\n      }\n    }\n\n    item.file->setAttribute(key.data(), value.data());\n\n    if (key != eos::kAttrTmpEtagKey) {\n      item.file->setCTimeNow();\n    }\n\n    const eos::FileIdentifier f_id(item.file->getIdentifier());\n    const eos::ContainerIdentifier c_id(item.file->getContainerId());\n    eosView->updateFileStore(item.file.get());\n    fuse_batch.Register([&, f_id, c_id]() {\n      gOFS->FuseXCastRefresh(f_id, c_id);\n    });\n  } else {\n    item.container->setAttribute(key.data(), value.data());\n\n    if (key != eos::kAttrTmpEtagKey) {\n      item.container->setCTimeNow();\n    }\n\n    const eos::ContainerIdentifier d_id(item.container->getIdentifier());\n    const eos::ContainerIdentifier d_pid(item.container->getParentIdentifier());\n    eosView->updateContainerStore(item.container.get());\n    fuse_batch.Register([&, d_id, d_pid]() {\n      gOFS->FuseXCastRefresh(d_id, d_pid);\n    });\n  }\n\n  errno = 0;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Remove an extended attribute for a given entry - high-level API.\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::attr_rem(const char* inpath, XrdOucErrInfo& error,\n                    const XrdSecEntity* client, const char* ininfo,\n                    const char* key)\n{\n  static const char* epname = \"attr_rm\";\n  const char* tident = error.getErrUser();\n  eos::common::VirtualIdentity vid;\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz,\n                              AOP_Update, inpath);\n  EXEC_TIMING_END(\"IdMap\");\n  NAMESPACEMAP;\n  BOUNCE_ILLEGAL_NAMES;\n  XrdOucEnv access_Env(ininfo);\n  AUTHORIZE(client, &access_Env, AOP_Delete, \"delete\", inpath, error);\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  BOUNCE_NOT_ALLOWED;\n  return _attr_rem(path, error, vid, ininfo, key);\n}\n\n//------------------------------------------------------------------------------\n// Remove an extended attribute for a given entry - low-level API.\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::_attr_rem(const char* path, XrdOucErrInfo& error,\n                     eos::common::VirtualIdentity& vid,\n                     const char* info, const char* key)\n\n{\n  static const char* epname = \"attr_rm\";\n  EXEC_TIMING_BEGIN(\"AttrRm\");\n  gOFS->MgmStats.Add(\"AttrRm\", vid.uid, vid.gid, 1);\n  errno = 0;\n\n  if (!key || (strlen(key) == 0)) {\n    errno = EINVAL;\n    return Emsg(epname, error, errno, \"delete attribute\", path);\n  }\n\n  const std::string skey = key;\n  eos::Prefetcher::prefetchItemAndWait(gOFS->eosView, path);\n\n  try {\n    eos::FileOrContainerMD item = gOFS->eosView->getItem(path).get();\n\n    if (item.file) { // file\n      std::shared_ptr<eos::IFileMD> fmd = item.file;\n      auto fmd_lock = eos::MDLocking::writeLock(fmd.get());\n      eos::IContainerMD::XAttrMap attr_map = fmd->getAttributes();\n      Acl acl(attr_map, vid);\n\n      if ((vid.uid != fmd->getCUid()) &&\n          !acl.AllowXAttrUpdate(skey, vid)) {\n        errno = EPERM;\n      } else {\n        if (!fmd->hasAttribute(skey)) {\n          errno = ENODATA;\n        } else {\n          // Capture previous value\n          std::string prev;\n          {\n            auto amap = fmd->getAttributes();\n\n            if (amap.count(skey)) {\n              prev = amap[skey];\n            }\n          }\n          fmd->removeAttribute(skey);\n          eosView->updateFileStore(fmd.get());\n          eos::FileIdentifier f_id = fmd->getIdentifier();\n          eos::ContainerIdentifier d_id(fmd->getContainerId());\n          // Release object lock before doing the fuse refresh\n          fmd_lock.reset(nullptr);\n          gOFS->FuseXCastRefresh(f_id, d_id);\n          errno = 0;\n\n          if (mAudit &&\n              gOFS->AllowAuditModification(path)) mAudit->audit(eos::audit::RM_XATTR, path,\n                    vid, std::string(logId), std::string(cident), \"mgm\",\n                    std::string(), nullptr, nullptr, skey, prev, std::string(), __FILE__, __LINE__,\n                    VERSION);\n        }\n      }\n    } else { // container\n      std::shared_ptr<eos::IContainerMD> cmd = item.container;\n      auto cmd_lock = eos::MDLocking::writeLock(cmd.get());\n      eos::IContainerMD::XAttrMap attr_map = cmd->getAttributes();\n      Acl acl(attr_map, vid);\n\n      if (vid.token || (!cmd->access(vid.uid, vid.gid, X_OK | W_OK) &&\n                        !acl.AllowXAttrUpdate(skey, vid))) {\n        errno = EPERM;\n      } else {\n        if (!cmd->hasAttribute(skey)) {\n          errno = ENODATA;\n        } else {\n          std::string prev;\n          {\n            auto amap = cmd->getAttributes();\n\n            if (amap.count(skey)) {\n              prev = amap[skey];\n            }\n          }\n          cmd->removeAttribute(skey);\n          eos::ContainerIdentifier d_id = cmd->getIdentifier();\n          eos::ContainerIdentifier d_pid = cmd->getParentIdentifier();\n          eosView->updateContainerStore(cmd.get());\n          // Release object lock before doing the fuse refresh\n          cmd_lock.reset(nullptr);\n          gOFS->FuseXCastRefresh(d_id, d_pid);\n          errno = 0;\n\n          if (mAudit &&\n              gOFS->AllowAuditModification(path)) mAudit->audit(eos::audit::RM_XATTR, path,\n                    vid, std::string(logId), std::string(cident), \"mgm\",\n                    std::string(), nullptr, nullptr, skey, prev, std::string(), __FILE__, __LINE__,\n                    VERSION);\n        }\n      }\n    }\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n              e.getMessage().str().c_str());\n  }\n\n  EXEC_TIMING_END(\"AttrRm\");\n\n  if (errno) {\n    return Emsg(epname, error, errno, \"remove attribute\", path);\n  }\n\n  return SFS_OK;\n}\n\n//----------------------------------------------------------------------------\n// List attributes high-level function merging space and namespace attributes\n//----------------------------------------------------------------------------\nvoid\nXrdMgmOfs::mergeSpaceAttributes(eos::IContainerMD::XAttrMap& out, bool prefix,\n                                bool existing)\n{\n  std::string space = \"default\";\n\n  if (out.count(\"sys.forced.space\")) {\n    space = out[\"sys.forced.space\"];\n  }\n\n  std::map<std::string, std::string> attr;\n\n  if (gOFS->mSpaceAttributes.count(space)) {\n    // retrieve space arguments\n    std::unique_lock<std::mutex> lock(gOFS->mSpaceAttributesMutex);\n    attr = gOFS->mSpaceAttributes[space];\n  }\n\n  // loop over arguments\n  for (const auto& x : attr) {\n    if (x.first == \"sys.forced.space\") {\n      // we ignore this\n      continue;\n    } else {\n      if (existing && !out.count(x.first)) {\n        // merge only existing attributes\n        continue;\n      }\n\n      std::string inkey = x.first;\n      std::string outkey = (prefix ? (std::string(\"sys.space.\") + x.first) : x.first);\n\n      if (x.first == \"sys.acl\") {\n        // Special meaning of the first char:\n        // > append acl to the existing ones\n        // < prepend acl to the existing ones\n        // | add only if acls not set at all\n        // none of the above means overwrite existing acls\n        char op = '\\0';\n\n        if (!x.second.empty()) {\n          op = x.second[0];\n        }\n\n        std::string old_acls = out[\"sys.acl\"];\n        std::string space_acls = x.second;\n\n        if ((op == '>') || (op == '<') || (op == '|')) {\n          space_acls = space_acls.erase(0, 1);\n        }\n\n        // ACL handling\n        if (((op != '>') && (op != '<') && (op != '|')) || // Full overwrite\n            // Overwrite if empty\n            (old_acls.empty() && ((op == '>') || (op == '<') || (op == '|')))) {\n          out[outkey] = space_acls;\n        } else {\n          // If existing acls already include the space acls then\n          // remove them to avoid duplicates.\n          auto pos = old_acls.find(space_acls);\n\n          if ((pos != std::string::npos) && (op != '|')) {\n            auto del_pos = pos;\n            auto del_len = space_acls.length();\n\n            // Either delete the command before or the comma after\n            if (del_pos && (old_acls[del_pos - 1] == ',')) {\n              --del_pos;\n              ++del_len;\n            } else if ((del_pos + del_len < old_acls.length()) &&\n                       (old_acls[del_pos + del_len] == ',')) {\n              ++del_len;\n            }\n\n            old_acls = old_acls.erase(del_pos, del_len);\n          }\n\n          if (op == '>') { // Append rule\n            out[outkey] = old_acls + std::string(\",\") + space_acls;\n          } else if (op == '<') { // Prepend rule\n            out[outkey] = space_acls + std::string(\",\") + old_acls;\n          }\n        }\n      } else {\n        // Normal attribute handling\n        if (x.second.substr(0, 1) == \"|\") {\n          // if not set rule\n          if (!out[x.first].length()) {\n            out[outkey] = x.second.substr(1);\n          }\n        } else {\n          // overwrite rule\n          out[outkey] = x.second;\n        }\n      }\n    }\n  }\n}\n\n\nvoid\nXrdMgmOfs::listAttributes(eos::IView* view, eos::IContainerMD* target,\n                          eos::IContainerMD::XAttrMap& out, bool prefixLinks)\n{\n  eos::listAttributes(view, target, out, prefixLinks);\n  mergeSpaceAttributes(out);\n}\n\nvoid\nXrdMgmOfs::listAttributes(eos::IView* view, eos::IFileMD* target,\n                          eos::IContainerMD::XAttrMap& out, bool prefixLinks)\n{\n  eos::listAttributes(view, target, out, prefixLinks);\n  mergeSpaceAttributes(out);\n}\n\nvoid\nXrdMgmOfs::listAttributes(eos::IView* view, eos::FileOrContainerMD target,\n                          eos::IContainerMD::XAttrMap& out, bool prefixLinks)\n{\n  eos::listAttributes(view, target, out, prefixLinks);\n  mergeSpaceAttributes(out);\n}\n\ntemplate<typename T>\nbool XrdMgmOfs::getAttribute(eos::IView* view, T& md, std::string key,\n                             std::string& rvalue)\n{\n  auto result = eos::getAttribute(view, md, key, rvalue);\n  eos::IContainerMD::XAttrMap attr;\n\n  if (!result) {\n    attr[key] = \"\";\n  } else {\n    attr[key] = rvalue;\n  }\n\n  mergeSpaceAttributes(attr, false, true);\n  rvalue = attr[key];\n\n  if (!result) {\n    return attr[key].length();\n  } else {\n    return true;\n  }\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Auth.inc",
    "content": "// -----------------------------------------------------------------------------\n// File: Auth.inc\n// Author: Elvin-Alin Sindrilaru - CERN\n// -----------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"auth_plugin/ProtoUtils.hh\"\n\n//------------------------------------------------------------------------------\n// Authentication master thread startup function\n//------------------------------------------------------------------------------\nvoid*\nXrdMgmOfs::StartAuthWorkerThread(void* pp)\n{\n  XrdMgmOfs* ofs = static_cast<XrdMgmOfs*>(pp);\n  ofs->AuthWorkerThread();\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Authentication master thread function - accepts requests from EOS AUTH\n// plugins which he then forwards to worker threads.\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::AuthMasterThread(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"AuthMaster\");\n  // Socket facing clients\n  zmq::socket_t frontend(*mZmqContext, ZMQ_ROUTER);\n  int enable_ipv6 = 1;\n  frontend.set(zmq::sockopt::ipv6, enable_ipv6);\n  std::ostringstream sstr;\n\n  if (mFrontendLocalhost) {\n    sstr << \"tcp://*:\" << mFrontendPort;\n  } else {\n    sstr << \"tcp://127.0.0.1:\" << mFrontendPort;\n  }\n\n  try {\n    frontend.bind(sstr.str().c_str());\n  } catch (zmq::error_t& err) {\n    eos_static_err(\"failed to bind frontend socket\");\n    return;\n  }\n\n  // Socket facing worker threads\n  zmq::socket_t backend(*mZmqContext, ZMQ_DEALER);\n\n  try {\n    backend.bind(\"inproc://authbackend\");\n  } catch (zmq::error_t& err) {\n    eos_static_err(\"failed to bind backend socket\");\n    return;\n  }\n\n  // Start the proxy\n  try {\n    zmq::proxy(frontend, backend);\n  } catch (const zmq::error_t& e) {\n    if (e.num() == ETERM) {\n      eos_warning(\"msg=\\\"master termination requested\\\" tid=%08x\",\n                  XrdSysThread::ID());\n      return;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Reconnect zmq::socket object\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::ConnectToBackend(zmq::socket_t*& socket)\n{\n  if (socket) {\n    delete socket;\n    socket = 0;\n  }\n\n  socket  = new zmq::socket_t(*mZmqContext, ZMQ_REP);\n  // Try to connect to proxy thread - the bind can take a longer time so threfore\n  // keep trying until it is successful\n  bool connected = false;\n  uint8_t tries = 0;\n\n  while (tries <= 5) {\n    try {\n      socket->connect(\"inproc://authbackend\");\n    } catch (const zmq::error_t& e) {\n      if (e.num() == ETERM) {\n        eos_warning(\"msg=\\\"worker termination requested\\\" tid=%08x\",\n                    XrdSysThread::ID());\n        return connected;\n      }\n\n      eos_debug(\"auth worker connection failed - retry\");\n      tries++;\n      sleep(1);\n      continue;\n    }\n\n    connected = true;\n    break;\n  }\n\n  return connected;\n}\n\n//------------------------------------------------------------------------------\n// Authentication worker thread function - accepts requests from the master,\n// executed the proper action and replies with the result.\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::AuthWorkerThread()\n{\n  using namespace eos::auth;\n  int ret;\n  eos_static_info(\"msg=\\\"authentication worker thread starting\\\"\");\n  zmq::socket_t* responder = 0;\n\n  if (!ConnectToBackend(responder)) {\n    eos_err(\"msg=\\\"kill thread as we could not connect to backend socket\\\"\");\n    delete responder;\n    return;\n  }\n\n  std::chrono::steady_clock::time_point time_start, time_end;\n\n  // Main loop of the worker thread\n  while (true) {\n    zmq::message_t request;\n\n    // Wait for next request\n    try {\n      zmq::recv_flags rf = zmq::recv_flags::none;\n      zmq::recv_result_t rr;\n\n      do {\n        rr = responder->recv(request, rf);\n      } while (!rr.has_value());\n    } catch (const zmq::error_t& e) {\n      if (e.num() == ETERM) {\n        eos_warning(\"msg=\\\"worker termination requested\\\" tid=%08x\",\n                    XrdSysThread::ID());\n        delete responder;\n        return;\n      }\n\n      eos_err(\"msg=\\\"socket recv error: %s, trying to reset the socket\\\"\",\n              e.what());\n\n      if (!ConnectToBackend(responder)) {\n        eos_err(\"msg=\\\"kill thread as we could not connect to backend socket\\\"\");\n        delete responder;\n        return;\n      }\n\n      continue;\n    }\n\n    // Read in the ProtocolBuffer object just received\n    time_start = std::chrono::steady_clock::now();\n    std::string msg_recv((char*)request.data(), request.size());\n    RequestProto req_proto;\n    req_proto.ParseFromString(msg_recv);\n    ResponseProto resp;\n    std::shared_ptr<XrdOucErrInfo> error(static_cast<XrdOucErrInfo*>(0));\n    XrdSecEntity* client = 0;\n\n    if (!ValidAuthRequest(&req_proto)) {\n      eos_err(\"message HMAC received is not valid, dropping request\");\n      error.reset(new XrdOucErrInfo(\"admin\"));\n      error.get()->setErrInfo(EKEYREJECTED, \"request HMAC value is wrong\");\n      ret = SFS_ERROR;\n    } else if (req_proto.type() == RequestProto_OperationType_STAT) {\n      // stat request\n      struct stat buf;\n      error.reset(utils::GetXrdOucErrInfo(req_proto.stat().error()));\n      client = utils::GetXrdSecEntity(req_proto.stat().client());\n      ret = gOFS->stat(req_proto.stat().path().c_str(), &buf,\n                       *error.get(), client, req_proto.stat().opaque().c_str());\n      // Fill in particular info for stat request\n      resp.set_message(&buf, sizeof(struct stat));\n      eos_debug(\"stat error msg: %s\", error->getErrText());\n    } else if (req_proto.type() == RequestProto_OperationType_STATM) {\n      // stat mode request\n      mode_t mode;\n      error.reset(utils::GetXrdOucErrInfo(req_proto.stat().error()));\n      client = utils::GetXrdSecEntity(req_proto.stat().client());\n      ret = gOFS->stat(req_proto.stat().path().c_str(), mode,\n                       *error.get(), client, req_proto.stat().opaque().c_str());\n      // Fill in particular info for stat request\n      resp.set_message(&mode, sizeof(mode_t));\n      eos_debug(\"statm error msg: %s\", error->getErrText());\n    } else if (req_proto.type() == RequestProto_OperationType_FSCTL1) {\n      // fsctl request\n      error.reset(utils::GetXrdOucErrInfo(req_proto.fsctl1().error()));\n      client = utils::GetXrdSecEntity(req_proto.fsctl1().client());\n      ret = gOFS->fsctl(req_proto.fsctl1().cmd(), req_proto.fsctl1().args().c_str(),\n                        *error.get(), client);\n      eos_debug(\"fsctl error msg: %s\", error->getErrText());\n    } else if (req_proto.type() == RequestProto_OperationType_FSCTL2) {\n      // FSctl request\n      error.reset(utils::GetXrdOucErrInfo(req_proto.fsctl2().error()));\n      client = utils::GetXrdSecEntity(req_proto.fsctl2().client());\n      XrdSfsFSctl* obj = utils::GetXrdSfsFSctl(req_proto.fsctl2().args());\n      ret = gOFS->FSctl(req_proto.fsctl2().cmd(), *obj, *error.get(), client);\n      eos_debug(\"FSctl error msg: %s\", error->getErrText());\n      // Free memory\n      free(const_cast<char*>(obj->Arg1));\n      free(const_cast<char*>(obj->Arg2));\n      delete obj;\n    } else if (req_proto.type() == RequestProto_OperationType_CHMOD) {\n      // chmod request\n      error.reset(utils::GetXrdOucErrInfo(req_proto.chmod().error()));\n      client = utils::GetXrdSecEntity(req_proto.chmod().client());\n      ret = gOFS->chmod(req_proto.chmod().path().c_str(),\n                        (XrdSfsMode)req_proto.chmod().mode(),\n                        *error.get(), client, req_proto.chmod().opaque().c_str());\n      eos_debug(\"chmod error msg: %s\", error->getErrText());\n    } else if (req_proto.type() == RequestProto_OperationType_CHKSUM) {\n      // chksum request\n      error.reset(utils::GetXrdOucErrInfo(req_proto.chksum().error()));\n      client = utils::GetXrdSecEntity(req_proto.chksum().client());\n      ret = gOFS->chksum((csFunc) req_proto.chksum().func(),\n                         req_proto.chksum().csname().c_str(),\n                         req_proto.chksum().path().c_str(),\n                         *error.get(), client,\n                         req_proto.chksum().opaque().c_str());\n      eos_debug(\"chksum error retc=%i msg: %s\", ret, error->getErrText());\n    } else if (req_proto.type() == RequestProto_OperationType_EXISTS) {\n      // exists request\n      XrdSfsFileExistence exists_flag;\n      error.reset(utils::GetXrdOucErrInfo(req_proto.exists().error()));\n      client = utils::GetXrdSecEntity(req_proto.exists().client());\n      ret = gOFS->exists(req_proto.exists().path().c_str(),\n                         exists_flag, *error.get(), client,\n                         req_proto.exists().opaque().c_str());\n      // Set the status of the exists for the request\n      std::ostringstream sstr;\n      sstr << (int)exists_flag;\n      resp.set_message(sstr.str().c_str());\n      eos_debug(\"exists error msg: %s\", error->getErrText());\n    } else if (req_proto.type() == RequestProto_OperationType_MKDIR) {\n      // mkdir request\n      error.reset(utils::GetXrdOucErrInfo(req_proto.mkdir().error()));\n      client = utils::GetXrdSecEntity(req_proto.mkdir().client());\n      ret = gOFS->mkdir(req_proto.mkdir().path().c_str(),\n                        (XrdSfsMode)req_proto.mkdir().mode(),\n                        *error.get(), client, req_proto.mkdir().opaque().c_str(), 0);\n      eos_debug(\"mkdir error msg: %s\", error->getErrText());\n    } else if (req_proto.type() == RequestProto_OperationType_REMDIR) {\n      // remdir request\n      error.reset(utils::GetXrdOucErrInfo(req_proto.remdir().error()));\n      client = utils::GetXrdSecEntity(req_proto.remdir().client());\n      ret = gOFS->remdir(req_proto.remdir().path().c_str(),\n                         *error.get(), client, req_proto.remdir().opaque().c_str());\n      eos_debug(\"remdir error msg: %s\", error->getErrText());\n    } else if (req_proto.type() == RequestProto_OperationType_REM) {\n      // rem request\n      error.reset(utils::GetXrdOucErrInfo(req_proto.rem().error()));\n      client = utils::GetXrdSecEntity(req_proto.rem().client());\n      ret = gOFS->rem(req_proto.rem().path().c_str(),\n                      *error.get(), client, req_proto.rem().opaque().c_str());\n      eos_debug(\"rem error msg: %s\", error->getErrText());\n    } else if (req_proto.type() == RequestProto_OperationType_RENAME) {\n      // rename request\n      error.reset(utils::GetXrdOucErrInfo(req_proto.rename().error()));\n      client = utils::GetXrdSecEntity(req_proto.rename().client());\n      ret = gOFS->rename(req_proto.rename().oldname().c_str(),\n                         req_proto.rename().newname().c_str(),\n                         *error.get(), client,\n                         req_proto.rename().opaqueo().c_str(),\n                         req_proto.rename().opaquen().c_str());\n      eos_debug(\"rename error msg: %s\", error->getErrText());\n    } else if (req_proto.type() == RequestProto_OperationType_PREPARE) {\n      // prepare request\n      error.reset(utils::GetXrdOucErrInfo(req_proto.prepare().error()));\n      client = utils::GetXrdSecEntity(req_proto.prepare().client());\n      XrdSfsPrep* pargs = utils::GetXrdSfsPrep(req_proto.prepare().pargs());\n      ret = gOFS->prepare(*pargs, *error.get(), client);\n      eos_debug(\"prepare error msg: %s\", error->getErrText());\n      delete pargs;\n    } else if (req_proto.type() == RequestProto_OperationType_TRUNCATE) {\n      // truncate request\n      error.reset(utils::GetXrdOucErrInfo(req_proto.truncate().error()));\n      client = utils::GetXrdSecEntity(req_proto.truncate().client());\n      ret = gOFS->truncate(req_proto.truncate().path().c_str(),\n                           (XrdSfsFileOffset)req_proto.truncate().fileoffset(),\n                           *error.get(), client,\n                           req_proto.truncate().opaque().c_str());\n      eos_debug(\"truncate error msg: %s\", error->getErrText());\n    } else if (req_proto.type() == RequestProto_OperationType_DIROPEN) {\n      // dir open request\n      mMutexDirs.Lock();\n\n      if (mMapDirs.count(req_proto.diropen().uuid())) {\n        mMutexDirs.UnLock();\n        eos_debug(\"dir:%s is already in mapping\", req_proto.diropen().name().c_str());\n        ret = SFS_OK;\n      } else {\n        mMutexDirs.UnLock();\n        XrdMgmOfsDirectory* dir = static_cast<XrdMgmOfsDirectory*>(\n                                    gOFS->newDir((char*)req_proto.diropen().user().c_str(),\n                                        req_proto.diropen().monid()));\n        client = utils::GetXrdSecEntity(req_proto.diropen().client());\n        ret = dir->open(req_proto.diropen().name().c_str(), client,\n                        req_proto.diropen().opaque().c_str());\n\n        if (ret == SFS_OK) {\n          XrdSysMutexHelper scope_lock(mMutexDirs);\n          mMapDirs.insert(std::make_pair(req_proto.diropen().uuid(), dir));\n        } else {\n          delete dir;\n        }\n      }\n    } else if (req_proto.type() == RequestProto_OperationType_DIRFNAME) {\n      // get directory name\n      mMutexDirs.Lock();\n      auto iter = mMapDirs.find(req_proto.dirfname().uuid());\n\n      if (iter == mMapDirs.end()) {\n        mMutexDirs.UnLock();\n        eos_err(\"directory not found in map for reading the name\");\n        ret = SFS_ERROR;\n      } else {\n        // Fill in particular info for the directory name\n        XrdMgmOfsDirectory* dir = iter->second;\n        mMutexDirs.UnLock();\n\n        if (dir->FName()) {\n          resp.set_message(dir->FName(), strlen(dir->FName()));\n        } else {\n          resp.set_message(\"\");\n        }\n\n        ret = SFS_OK;\n      }\n    } else if (req_proto.type() == RequestProto_OperationType_DIRREAD) {\n      // read next entry from directory\n      mMutexDirs.Lock();\n      auto iter =  mMapDirs.find(req_proto.dirread().uuid());\n\n      if (iter == mMapDirs.end()) {\n        mMutexDirs.UnLock();\n        eos_err(\"directory not found in map for reading next entry\");\n        ret = SFS_ERROR;\n      } else {\n        XrdMgmOfsDirectory* dir = iter->second;\n        mMutexDirs.UnLock();\n        const char* entry = dir->nextEntry();\n\n        // Fill in particular info for next entry request\n        if (entry) {\n          resp.set_message(entry, strlen(entry));\n          ret = SFS_OK;\n        } else  {\n          // If no more entries send SFS_ERROR\n          ret = SFS_ERROR;\n        }\n      }\n    } else if (req_proto.type() == RequestProto_OperationType_DIRCLOSE) {\n      // close directory\n      mMutexDirs.Lock();\n      auto iter = mMapDirs.find(req_proto.dirclose().uuid());\n\n      if (iter == mMapDirs.end()) {\n        mMutexDirs.UnLock();\n        eos_err(\"directory not found in map for closing it\");\n        ret = SFS_ERROR;\n      } else {\n        // close directory and remove from mapping\n        XrdMgmOfsDirectory* dir = iter->second;\n        mMapDirs.erase(iter);\n        mMutexDirs.UnLock();\n        dir->close();\n        delete dir;\n        ret = SFS_OK;\n      }\n    } else if (req_proto.type() == RequestProto_OperationType_FILEOPEN) {\n      mMutexFiles.Lock();\n\n      if (mMapFiles.count(req_proto.fileopen().uuid())) {\n        mMutexFiles.UnLock();\n        eos_debug(\"file:%s is already in mapping\", req_proto.fileopen().name().c_str());\n        ret = SFS_OK;\n      } else {\n        // file open request\n        mMutexFiles.UnLock();\n        XrdMgmOfsFile* file = static_cast<XrdMgmOfsFile*>(\n                                gOFS->newFile((char*)req_proto.fileopen().user().c_str(),\n                                              req_proto.fileopen().monid()));\n        client = utils::GetXrdSecEntity(req_proto.fileopen().client());\n        ret = file->open(req_proto.fileopen().name().c_str(),\n                         req_proto.fileopen().openmode(),\n                         (mode_t) req_proto.fileopen().createmode(),\n                         client, req_proto.fileopen().opaque().c_str());\n        error.reset(new XrdOucErrInfo());\n        error->setErrInfo(file->error.getErrInfo(), file->error.getErrText());\n\n        if (ret == SFS_OK) {\n          XrdSysMutexHelper scope_lock(mMutexFiles);\n          mMapFiles.insert(std::make_pair(req_proto.fileopen().uuid(), file));\n        } else {\n          // Drop the file object since we redirected to the FST node or if\n          // there was an error we will not receive a close so we might as well\n          // clean it up now\n          delete file;\n        }\n      }\n    } else if (req_proto.type() == RequestProto_OperationType_FILESTAT) {\n      // file stat request\n      struct stat buf;\n      mMutexFiles.Lock();\n      auto iter =  mMapFiles.find(req_proto.filestat().uuid());\n\n      if (iter == mMapFiles.end()) {\n        mMutexFiles.UnLock();\n        eos_err(\"file not found in map for stat\");\n        memset(&buf, 0, sizeof(struct stat));\n        ret = SFS_ERROR;\n      } else {\n        XrdMgmOfsFile* file = iter->second;\n        mMutexFiles.UnLock();\n        ret = file->stat(&buf);\n\n        if (ret == SFS_ERROR) {\n          error.reset(new XrdOucErrInfo());\n          error->setErrInfo(file->error.getErrInfo(), file->error.getErrText());\n        }\n      }\n\n      resp.set_message(&buf, sizeof(struct stat));\n    } else if (req_proto.type() == RequestProto_OperationType_FILEFNAME) {\n      // file fname request\n      mMutexFiles.Lock();\n      auto iter = mMapFiles.find(req_proto.filefname().uuid());\n\n      if (iter == mMapFiles.end()) {\n        mMutexFiles.UnLock();\n        eos_err(\"file not found in map for fname call\");\n        ret = SFS_ERROR;\n      } else {\n        XrdMgmOfsFile* file = iter->second;\n        mMutexFiles.UnLock();\n\n        if (file->FName()) {\n          resp.set_message(file->FName());\n        } else {\n          resp.set_message(\"\");\n        }\n\n        ret = SFS_OK;\n      }\n    } else if (req_proto.type() == RequestProto_OperationType_FILEREAD) {\n      // file read request\n      mMutexFiles.Lock();\n      auto iter =  mMapFiles.find(req_proto.fileread().uuid());\n\n      if (iter == mMapFiles.end()) {\n        mMutexFiles.UnLock();\n        eos_err(\"file not found in map for read\");\n        ret = SFS_ERROR;\n      } else {\n        XrdMgmOfsFile* file = iter->second;\n        mMutexFiles.UnLock();\n        resp.mutable_message()->resize(req_proto.fileread().length());\n        ret = file->read((XrdSfsFileOffset)req_proto.fileread().offset(),\n                         (char*)resp.mutable_message()->c_str(),\n                         (XrdSfsXferSize)req_proto.fileread().length());\n\n        if (ret == SFS_ERROR) {\n          error.reset(new XrdOucErrInfo());\n          error->setErrInfo(file->error.getErrInfo(), file->error.getErrText());\n        } else {\n          resp.mutable_message()->resize(ret);\n        }\n      }\n    } else if (req_proto.type() == RequestProto_OperationType_FILEWRITE) {\n      // file write request\n      mMutexFiles.Lock();\n      auto iter = mMapFiles.find(req_proto.filewrite().uuid());\n\n      if (iter == mMapFiles.end()) {\n        mMutexFiles.UnLock();\n        eos_err(\"file not found in map for write\");\n        ret = SFS_ERROR;\n      } else {\n        XrdMgmOfsFile* file = iter->second;\n        mMutexFiles.UnLock();\n        ret = file->write(req_proto.filewrite().offset(),\n                          req_proto.filewrite().buff().c_str(),\n                          req_proto.filewrite().length());\n      }\n    } else if (req_proto.type() == RequestProto_OperationType_FILECLOSE) {\n      // close file\n      mMutexFiles.Lock();\n      auto iter = mMapFiles.find(req_proto.fileclose().uuid());\n\n      if (iter == mMapFiles.end()) {\n        mMutexFiles.UnLock();\n        eos_err(\"file not found in map for closing it\");\n        ret = SFS_ERROR;\n      } else {\n        // close file and remove from mapping\n        XrdMgmOfsFile* file = iter->second;\n        mMapFiles.erase(iter);\n        mMutexFiles.UnLock();\n        ret = file->close();\n        delete file;\n      }\n    } else {\n      eos_debug(\"%s\", \"msg=\\\"no such operation supported\\\"\");\n      continue;\n    }\n\n    // Free memory\n    if (client) {\n      utils::DeleteXrdSecEntity(client);\n    }\n\n    // Add error object only if it exists\n    if (error.get()) {\n      XrdOucErrInfoProto* err_proto = resp.mutable_error();\n      utils::ConvertToProtoBuf(error.get(), err_proto);\n    }\n\n    // Construct and send response to the requester\n    resp.set_response(ret);\n#if GOOGLE_PROTOBUF_VERSION < 3004000\n    int reply_size = resp.ByteSize();\n#else\n    int reply_size = resp.ByteSizeLong();\n#endif\n    zmq::message_t reply(reply_size);\n    google::protobuf::io::ArrayOutputStream aos(reply.data(), reply_size);\n    resp.SerializeToZeroCopyStream(&aos);\n    // Try to send out the reply\n    bool reset_socket = false;\n    int num_retries = 40;\n\n    try {\n      zmq::send_result_t sr;\n\n      do {\n        zmq::send_flags sf = zmq::send_flags::dontwait;\n        sr = responder->send(reply, sf);\n        num_retries--;\n      } while (!sr.has_value() && (num_retries > 0));\n    } catch (zmq::error_t& e) {\n      if (e.num() == ETERM) {\n        eos_warning(\"msg=\\\"worker termination requested\\\" tid=%08x\",\n                    XrdSysThread::ID());\n        delete responder;\n        return;\n      }\n\n      eos_err(\"msg=\\\"socket error\\\" err=\\\"%s\\\"\", e.what());\n      reset_socket = true;\n    }\n\n    if ((num_retries <= 0) || reset_socket) {\n      if (!ConnectToBackend(responder)) {\n        eos_err(\"msg=\\\"kill thread as we could not connect to backend socket\\\"\");\n        delete responder;\n        return;\n      }\n    }\n\n    time_end = std::chrono::steady_clock::now();\n    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>\n                    (time_end - time_start);\n    AuthCollectInfo(req_proto.type(), duration.count());\n  }\n\n  delete responder;\n}\n\n//------------------------------------------------------------------------------\n// Check that the ProtocolBuffers message has not been tampered with\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::ValidAuthRequest(eos::auth::RequestProto* reqProto)\n{\n  std::string smsg;\n  std::string recv_hmac = reqProto->hmac();\n  reqProto->set_hmac(\"\");\n\n  // Compute hmac value of the message ignoring the hmac\n  if (!reqProto->SerializeToString(&smsg)) {\n    eos_static_err(\"unable to serialize message to string for HMAC computation\");\n    return false;\n  }\n\n  std::string comp_hmac = eos::common::SymKey::HmacSha1(smsg);\n  XrdOucString base64hmac;\n  bool do_encoding = eos::common::SymKey::Base64Encode((char*)comp_hmac.c_str(),\n                     comp_hmac.length(), base64hmac);\n\n  if (!do_encoding) {\n    eos_err(\"unable to do base64encoding on hmac\");\n    return do_encoding;\n  }\n\n  eos_debug(\"comp_hmac=%s comp_size=%i recv_hmac=%s recv_size=%i key=%s\",\n            base64hmac.c_str(), base64hmac.length(), recv_hmac.c_str(),\n            recv_hmac.length(), eos::common::gSymKeyStore.GetCurrentKey()->GetKey64());\n\n  if (((size_t)base64hmac.length() != recv_hmac.length()) ||\n      strncmp(base64hmac.c_str(), recv_hmac.c_str(), base64hmac.length())) {\n    eos_err(\"computed HMAC different from the received one, this message\"\n            \"has been tampered with ... \");\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Collect statistics for authentication response times\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::AuthCollectInfo(eos::auth::RequestProto_OperationType op,\n                           std::int64_t ms_duration)\n{\n  auto now = std::chrono::steady_clock::now();\n  std::lock_guard<std::mutex> lock(mAuthStatsMutex);\n\n  if (std::chrono::duration_cast<std::chrono::minutes>(now -\n      mLastTimestamp).count() >= 1) {\n    mLastTimestamp = now;\n\n    // Push all accumulated samples\n    for (auto it = mAuthSamples.begin(); it != mAuthSamples.end(); ++it) {\n      if (!it->second.empty()) {\n        AuthUpdateAggregate(mAuthAggregate[op], it->second);\n        it->second.clear();\n      }\n    }\n\n    std::string info = AuthPrintStatistics();\n    eos_info(\"msg=\\\"authentication statistics\\\" data=\\\"%s\\\"\", info.c_str());\n  } else {\n    // Append new measurement\n    mAuthSamples[op].push_back(ms_duration);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Compute stats for the provided samples\n//------------------------------------------------------------------------------\nXrdMgmOfs::AuthStats\nXrdMgmOfs::AuthComputeStats(const std::list<std::int64_t>& lst_samples) const\n{\n  AuthStats stats;\n  stats.mNumSamples = 0;\n  stats.mMax = 0;\n  stats.mMin = std::numeric_limits<std::int64_t>::max();\n  stats.mMean = stats.mVariance = 0;\n  double sum = 0, sq_sum = 0;\n  std::int64_t elem;\n\n  for (auto it = lst_samples.begin(); it != lst_samples.end(); ++it) {\n    elem = *it;\n\n    if (elem > stats.mMax) {\n      stats.mMax = elem;\n    }\n\n    if (elem < stats.mMin) {\n      stats.mMin = elem;\n    }\n\n    sum += elem;\n    sq_sum += elem * elem;\n    ++stats.mNumSamples;\n  }\n\n  if (stats.mNumSamples) {\n    stats.mMean = sum / stats.mNumSamples;\n    stats.mVariance = sq_sum / stats.mNumSamples - stats.mMean * stats.mMean;\n  }\n\n  return stats;\n}\n\n//------------------------------------------------------------------------------\n// Update aggregate info with the latest samples\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::AuthUpdateAggregate(AuthStats& stats,\n                               const std::list<std::int64_t>& lst_samples) const\n{\n  if (stats.mNumSamples == 0) {\n    stats = AuthComputeStats(lst_samples);\n    return;\n  }\n\n  AuthStats tmp = AuthComputeStats(lst_samples);\n  double new_mean = (stats.mNumSamples * stats.mMean + tmp.mNumSamples *\n                     tmp.mMean) /\n                    (stats.mNumSamples + tmp.mNumSamples);\n  stats.mVariance =\n    (stats.mNumSamples * (stats.mVariance + std::pow(stats.mMean, 2)) +\n     tmp.mNumSamples * (tmp.mVariance + std::pow(tmp.mMean, 2))) /\n    (stats.mNumSamples + tmp.mNumSamples) - std::pow(new_mean, 2);\n  stats.mMean = new_mean;\n  stats.mNumSamples += tmp.mNumSamples;\n\n  if (stats.mMax < tmp.mMax) {\n    stats.mMax = tmp.mMax;\n  }\n\n  if (stats.mMin > tmp.mMin) {\n    stats.mMin = tmp.mMin;\n  }\n}\n\n//----------------------------------------------------------------------------\n// Print statistics about authentication performance - needs to be called\n// with the mutex locked\n//----------------------------------------------------------------------------\nstd::string\nXrdMgmOfs::AuthPrintStatistics() const\n{\n  std::ostringstream oss;\n\n  for (auto it = mAuthAggregate.begin(); it != mAuthAggregate.end(); ++it) {\n    oss << \"op=\" << it->first << \"&\"\n        << \"samples=\" << it->second.mNumSamples << \"&\"\n        << \"max=\" << it->second.mMax << \"ms&\"\n        << \"min=\"  << it->second.mMin << \"ms&\"\n        << \"mean=\" << it->second.mMean << \"ms&\"\n        << \"std_dev=\" << std::sqrt(it->second.mVariance) << \"&\";\n  }\n\n  return oss.str();\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Chksum.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Chksum.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::chksum(XrdSfsFileSystem::csFunc Func,\n                  const char* csName,\n                  const char* inpath,\n                  XrdOucErrInfo& error,\n                  const XrdSecEntity* client,\n                  const char* ininfo)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief retrieve a checksum\n *\n * @param func function to be performed 'csCalc','csGet' or 'csSize'\n * @param csName name of the checksum\n * @param error error object\n * @param client XRootD authentication object\n * @param ininfo CGI\n * @return SFS_OK on success otherwise SFS_ERROR\n *\n * We support only checksum type 'eos' which has the maximum length of 20 bytes\n * and returns a checksum based on the defined directory policy (can be adler,\n * md5,sha1 ...). The EOS directory based checksum configuration does not map\n * 1:1 to the XRootD model where a storage system supports only one flavour.\n */\n/*----------------------------------------------------------------------------*/\n{\n  static const char* epname = \"chksum\";\n  const char* tident = error.getErrUser();\n  // use a thread private vid\n  eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Nobody();\n  char buff[MAXPATHLEN + 8];\n  int rc;\n  XrdOucString CheckSumName = csName;\n  // ---------------------------------------------------------------------------\n  // retrieve meta data for <path>\n  // ---------------------------------------------------------------------------\n  // A csSize request is issued usually once to verify everything is working. We\n  // take this opportunity to also verify the checksum name.\n  // ---------------------------------------------------------------------------\n  rc = 0;\n\n  if (Func == XrdSfsFileSystem::csSize) {\n    if (1) {\n      // just return the length\n      error.setErrCode(20);\n      return SFS_OK;\n    } else {\n      eos_static_info(\"not supported\");\n      strcpy(buff, csName);\n      strcat(buff, \" checksum not supported.\");\n      error.setErrInfo(ENOTSUP, buff);\n      return SFS_ERROR;\n    }\n  }\n\n  NAMESPACEMAP;\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz,\n                              AOP_Stat, inpath);\n  EXEC_TIMING_END(\"IdMap\");\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  gOFS->MgmStats.Add(\"Checksum\", vid.uid, vid.gid, 1);\n  XrdOucEnv Open_Env(ininfo);\n  AUTHORIZE(client, &Open_Env, AOP_Stat, \"stat\", inpath, error);\n  BOUNCE_ILLEGAL_NAMES;\n  BOUNCE_NOT_ALLOWED;\n  ACCESSMODE_W;\n  MAYSTALL;\n  MAYREDIRECT;\n  eos_info(\"path=%s\", inpath);\n  // ---------------------------------------------------------------------------\n  errno = 0;\n  std::shared_ptr<eos::IFileMD> fmd;\n  eos::common::Path cPath(path);\n\n  // ---------------------------------------------------------------------------\n  // Everything else requires a path\n  // ---------------------------------------------------------------------------\n\n  if (!path) {\n    strcpy(buff, csName);\n    strcat(buff, \" checksum path not specified.\");\n    error.setErrInfo(EINVAL, buff);\n    return SFS_ERROR;\n  }\n\n  // ---------------------------------------------------------------------------\n  eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, cPath.GetPath());\n  eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n  bool enonet = false;\n\n  try {\n    fmd = gOFS->eosView->getFile(cPath.GetPath());\n    enonet = !fmd->getNumLocation();\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n  }\n\n  if (!fmd) {\n    // file does not exist\n    *buff = 0;\n    rc = ENOENT;\n    MAYREDIRECT_ENOENT;\n    MAYSTALL_ENOENT;\n    error.setErrInfo(rc, \"no such file or directory\");\n    return SFS_ERROR;\n  }\n\n  if (enonet) {\n    // File has no committed replicas, we might bounce to an alive remote master\n    if (!gOFS->mMaster->IsMaster() && gOFS->mMaster->IsRemoteMasterOk()) {\n      lock.Release();\n      // redirect ENONET to an alive remote master\n      int port {0};\n      std::string hostname;\n      std::string master_id = gOFS->mMaster->GetMasterId();\n\n      if (!eos::common::ParseHostNamePort(master_id, hostname, port)) {\n        eos_err(\"msg=\\\"failed parsing remote master info\\\", id=%s\",\n                master_id.c_str());\n        return Emsg(epname, error, ENOENT, \"get checksum - failed parsing \"\n                    \"remote master info\", path);\n      }\n\n      error.setErrInfo(port, hostname.c_str());\n      gOFS->MgmStats.Add(\"RedirectENONET\", vid.uid, vid.gid, 1);\n      return SFS_REDIRECT;\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  // Now determine what to do\n  // ---------------------------------------------------------------------------\n  if ((Func == XrdSfsFileSystem::csCalc) ||\n      (Func == XrdSfsFileSystem::csGet)) {\n  } else {\n    error.setErrInfo(EINVAL, \"Invalid checksum function.\");\n    return SFS_ERROR;\n  }\n\n  // Set the checksum type\n  std::string cksum_type = eos::common::LayoutId::GetChecksumStringReal(\n                             fmd->getLayoutId());\n  sprintf(buff, \"!%s \", cksum_type.c_str());\n  // copy the checksum buffer\n  const char* hv = \"0123456789abcdef\";\n  size_t j = strlen(buff);\n\n  for (size_t i = 0;\n       i < eos::common::LayoutId::GetChecksumLen(fmd->getLayoutId()); i++) {\n    buff[j++] = hv[(fmd->getChecksum().getDataPadded(i) >> 4) & 0x0f];\n    buff[j++] = hv[ fmd->getChecksum().getDataPadded(i) & 0x0f];\n  }\n\n  if (j == 0) {\n    sprintf(buff, \"NONE\");\n  } else {\n    buff[j] = '\\0';\n  }\n\n  eos_info(\"checksum=\\\"%s\\\"\", buff);\n  error.setErrInfo(0, buff);\n  return SFS_OK;\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Chmod.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Chmod.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n#include \"proto/Audit.pb.h\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/misc/AuditHelpers.hh\"\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::chmod(const char* inpath,\n                 XrdSfsMode Mode,\n                 XrdOucErrInfo& error,\n                 const XrdSecEntity* client,\n                 const char* ininfo)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief change the mode of a directory\n *\n * @param inpath path to chmod\n * @param Mode mode to set\n * @param error error object\n * @param client XRootD authentication object\n * @param ininfo CGI\n *\n * Function calls the internal _chmod function. See info there for details.\n */\n/*----------------------------------------------------------------------------*/\n{\n  static const char* epname = \"chmod\";\n  const char* tident = error.getErrUser();\n  //  mode_t acc_mode = Mode & S_IAMB;\n  // use a thread private vid\n  eos::common::VirtualIdentity vid;\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz,\n                              AOP_Chmod, inpath);\n  EXEC_TIMING_END(\"IdMap\");\n  NAMESPACEMAP;\n  BOUNCE_ILLEGAL_NAMES;\n  XrdOucEnv chmod_Env(ininfo);\n  AUTHORIZE(client, &chmod_Env, AOP_Chmod, \"chmod\", inpath, error);\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  BOUNCE_NOT_ALLOWED;\n  ACCESSMODE_W;\n  MAYSTALL;\n  MAYREDIRECT;\n  return _chmod(path, Mode, error, vid, ininfo);\n}\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::_chmod(const char* path,\n                  XrdSfsMode& Mode,\n                  XrdOucErrInfo& error,\n                  eos::common::VirtualIdentity& vid,\n                  const char* ininfo)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief change mode of a directory or file\n *\n * @param path where to chmod\n * @param Mode mode to set (and effective mode returned)\n * @param error error object\n * @param vid virtual identity of the client\n * @param ininfo CGI\n * @return SFS_OK on success otherwise SFS_ERR\n *\n * EOS supports mode bits only on directories, file inherit them from the parent.\n * Only the owner, the admin user, the admin group, root and an ACL chmod granted\n * user are allowed to run this operation on a directory.\n */\n/*----------------------------------------------------------------------------*/\n{\n  static const char* epname = \"chmod\";\n  EXEC_TIMING_BEGIN(\"Chmod\");\n  // ---------------------------------------------------------------------------\n  eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, path);\n  eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, path);\n  eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n  std::shared_ptr<eos::IContainerMD> cmd;\n  std::shared_ptr<eos::IContainerMD> pcmd;\n  std::shared_ptr<eos::IFileMD> fmd;\n  eos::IContainerMD::XAttrMap attrmap;\n  errno = 0;\n  gOFS->MgmStats.Add(\"Chmod\", vid.uid, vid.gid, 1);\n  eos_info(\"path=%s mode=%o\", path, Mode);\n  eos::common::Path cPath(path);\n\n  try {\n    cmd = gOFS->eosView->getContainer(path);\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n  }\n\n  if (!cmd) {\n    errno = 0;\n\n    // Check if this is a file\n    try {\n      fmd = gOFS->eosView->getFile(path);\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n    }\n  }\n\n  if (cmd || fmd)\n    try {\n      std::string uri;\n\n      if (cmd) {\n        uri = gOFS->eosView->getUri(cmd.get());\n      } else {\n        uri = gOFS->eosView->getUri(fmd.get());\n      }\n\n      eos::common::Path pPath(uri.c_str());\n      pcmd = gOFS->eosView->getContainer(pPath.GetParentPath());\n      // ACL and permission check\n      Acl acl(pPath.GetParentPath(), error, vid, attrmap);\n\n      if (vid.uid && !acl.IsMutable()) {\n        // immutable directory\n        errno = EPERM;\n      } else {\n        // If owner without revoked chmod permissions\n        if (((fmd && (fmd->getCUid() == vid.uid)) && (!acl.CanNotChmod())) ||\n            ((cmd && (cmd->getCUid() == vid.uid)) && (!acl.CanNotChmod())) ||\n            (!vid.uid) || // the root user\n            (vid.uid == eos::common::ADM_UID) || // the admin user\n            (vid.gid == eos::common::ADM_GID) || // the admin group\n            (acl.CanChmod())\n           ) { // a pre-defined mask to apply to the desired modbits\n          // the chmod ACL entry\n          // change the permission mask, but make sure it is set to a directory\n          long mask = 07777777;\n\n          if (Mode & S_IFREG) {\n            Mode ^= S_IFREG;\n          }\n\n          if ((Mode & S_ISUID)) {\n            Mode ^= S_ISUID;\n          }\n\n          eosView->updateContainerStore(pcmd.get());\n          eos::ContainerIdentifier pcmd_id = pcmd->getIdentifier();\n          eos::ContainerIdentifier pcmd_pid = pcmd->getParentIdentifier();\n          eos::ContainerIdentifier cmd_id;\n          eos::ContainerIdentifier cmd_pid;\n          eos::FileIdentifier f_id;\n\n  if (cmd) {\n    // Build before stat for directory\n    eos::audit::Stat beforeStat;\n    {\n      eos::mgm::auditutil::buildStatFromContainerMD(cmd, beforeStat, /*includeNs=*/true);\n    }\n            Mode &= mask;\n            cmd->setMode(Mode | S_IFDIR);\n            cmd->setCTimeNow();\n            // store the in-memory modification time for this directory\n            eosView->updateContainerStore(cmd.get());\n            cmd_id = cmd->getIdentifier();\n            cmd_pid = cmd->getParentIdentifier();\n    eos::audit::Stat afterStat;\n    eos::mgm::auditutil::buildStatFromContainerMD(cmd, afterStat, /*includeNs=*/true);\n    if (mAudit && gOFS->AllowAuditModification(path)) mAudit->audit(eos::audit::CHMOD, path, vid, std::string(logId), std::string(cident), \"mgm\", std::string(), &beforeStat, &afterStat, std::string(), std::string(), std::string(), __FILE__, __LINE__, VERSION);\n          }\n\n  if (fmd) {\n    eos::audit::Stat beforeStat;\n    {\n      eos::mgm::auditutil::buildStatFromFileMD(fmd, beforeStat, /*includeSize=*/false, /*includeChecksum=*/false, /*includeNs=*/true);\n    }\n            // we just store 9 bits in flags\n            Mode &= (S_IRWXU | S_IRWXG | S_IRWXO);\n            fmd->setFlags(Mode);\n            eosView->updateFileStore(fmd.get());\n            f_id = fmd->getIdentifier();\n    eos::audit::Stat afterStat;\n    eos::mgm::auditutil::buildStatFromFileMD(fmd, afterStat, /*includeSize=*/false, /*includeChecksum=*/false, /*includeNs=*/true);\n    if (mAudit && gOFS->AllowAuditModification(path)) mAudit->audit(eos::audit::CHMOD, path, vid, std::string(logId), std::string(cident), \"mgm\", std::string(), &beforeStat, &afterStat, std::string(), std::string(), std::string(), __FILE__, __LINE__, VERSION);\n          }\n\n          lock.Release();\n          gOFS->FuseXCastRefresh(pcmd_id, pcmd_pid);\n\n          if (cmd) {\n            gOFS->FuseXCastRefresh(cmd_id, cmd_pid);\n          }\n\n          if (fmd) {\n            gOFS->FuseXCastRefresh(f_id, pcmd_id);\n          }\n\n          errno = 0;\n        } else {\n          errno = EPERM;\n        }\n      }\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n    }\n\n  if (cmd && (!errno)) {\n    EXEC_TIMING_END(\"Chmod\");\n    return SFS_OK;\n  }\n\n  if (fmd && (!errno)) {\n    EXEC_TIMING_END(\"Chmod\");\n    return SFS_OK;\n  }\n\n  return Emsg(epname, error, errno, \"chmod\", path);\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Chown.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Chown.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n#include \"proto/Audit.pb.h\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/misc/AuditHelpers.hh\"\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::_chown(const char* path,\n                  uid_t uid,\n                  gid_t gid,\n                  XrdOucErrInfo& error,\n                  eos::common::VirtualIdentity& vid,\n                  const char* ininfo,\n                  bool nodereference)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief change the owner of a file or directory\n *\n * @param path directory path to change\n * @param uid user id to set\n * @param gid group id to set\n * @param error error object\n * @param vid virtual identity of the client\n * @param ininfo CGI\n * @param specify if we shouldn't follow symlinks\n * @return SFS_OK on success otherwise SFS_ERROR\n *\n * Chown has only an internal implementation because XRootD does not support\n * this operation in the Ofs interface. root can always run the operation.\n * Users with the admin role can run the operation. Normal users can run the operation\n * if they have the 'c' permissions in 'sys.acl'. File ownership can only be changed\n * with the root or admin role. If uid,gid=0xffffffff, we don't set the uid/group\n */\n/*----------------------------------------------------------------------------*/\n\n{\n  static const char* epname = \"chown\";\n  EXEC_TIMING_BEGIN(\"Chown\");\n  // ---------------------------------------------------------------------------\n  std::shared_ptr<eos::IContainerMD> cmd;\n  std::shared_ptr<eos::IFileMD> fmd;\n  errno = 0;\n  gOFS->MgmStats.Add(\"Chown\", vid.uid, vid.gid, 1);\n  eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n\n  // try as a directory\n  try {\n    eos::IContainerMD::XAttrMap attrmap;\n    eos::common::Path cPath(path);\n    cmd = gOFS->eosView->getContainer(path, !nodereference);\n    gOFS->listAttributes(gOFS->eosView, cmd.get(), attrmap, false);\n    eos_info(\"path=%s uid=%u gid=%u old_uid=%u old_gid=%d noderef=%d\",\n             path, uid, gid, cmd->getCUid(), cmd->getCGid(), nodereference);\n    // ACL and permission check\n    Acl acl;\n\n    if (uid != vid.uid) {\n      // if the user is not the owner, user acls are removed\n      attrmap[\"user.acl\"] = \"\";\n    }\n\n    acl.SetFromAttrMap(attrmap, vid);  /* also takes care of eval.useracl */\n    eos_static_debug(\"sys.acl %s acl.CanChown() %d\", attrmap[\"sys.acl\"].c_str(),\n                     acl.CanChown());\n\n    if ((vid.uid && !vid.hasUid(eos::common::ADM_UID) &&\n         !vid.hasGid(eos::common::ADM_GID) && !acl.CanChown()) ||\n        (vid.uid && !acl.IsMutable())) {\n      errno = EPERM;\n    } else {\n      // Prepare before stat for directory\n      eos::audit::Stat beforeStat;\n      {\n        eos::mgm::auditutil::buildStatFromContainerMD(cmd, beforeStat, /*includeNs=*/true);\n      }\n      if ((unsigned int) uid != 0xffffffff) {\n        // Change the owner\n        cmd->setCUid(uid);\n      }\n\n      if (((!vid.uid) || (vid.uid == eos::common::ADM_UID) ||\n           (vid.gid == eos::common::ADM_GID)) &&\n          ((unsigned int)gid != 0xffffffff)) {\n        // Change the group\n        cmd->setCGid(gid);\n      }\n\n      cmd->setCTimeNow();\n      eosView->updateContainerStore(cmd.get());\n      lock.Release();\n      gOFS->FuseXCastRefresh(cmd->getIdentifier(), cmd->getParentIdentifier());\n      errno = 0;\n      if (mAudit) {\n        eos::audit::Stat afterStat;\n        eos::mgm::auditutil::buildStatFromContainerMD(cmd, afterStat, /*includeNs=*/true);\n        if (mAudit && gOFS->AllowAuditModification(path)) mAudit->audit(eos::audit::CHOWN, path, vid, std::string(logId), std::string(cident), \"mgm\", std::string(), &beforeStat, &afterStat, std::string(), std::string(), std::string(), __FILE__, __LINE__, VERSION);\n      }\n    }\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n  }\n\n  if (!cmd) {\n    errno = 0;\n\n    try {\n      // Try as a file\n      eos::common::Path cPath(path);\n      cmd = gOFS->eosView->getContainer(cPath.GetParentPath());\n\n      if (!nodereference) {\n        // Translate to path without symlinks\n        std::string uri_cmd = eosView->getUri(cmd.get());\n        cmd = eosView->getContainer(uri_cmd);\n      }\n\n      eos::IQuotaNode* ns_quota = gOFS->eosView->getQuotaNode(cmd.get());\n      // ACL and permission check\n      eos::IContainerMD::XAttrMap attrmap;\n      gOFS->_attr_ls(cPath.GetParentPath(), error, vid, 0, attrmap, false);\n      Acl acl;\n\n      if (uid != vid.uid) {\n        // if the user is not the owner, user acls are removed\n        attrmap[\"user.acl\"] = \"\";\n      }\n\n      acl.SetFromAttrMap(attrmap, vid);   /* also takes care of eval.useracl */\n      eos_static_debug(\"sys.acl %s acl.CanChown() %d\", attrmap[\"sys.acl\"].c_str(),\n                       acl.CanChown());\n\n      if ((vid.uid) && (!vid.sudoer) && (vid.uid != eos::common::ADM_UID) &&\n          (vid.gid != eos::common::ADM_GID) && !acl.CanChown()) {\n        errno = EPERM;\n      } else {\n        fmd = gOFS->eosView->getFile(path, !nodereference);\n        eos_info(\"path=%s uid=%u gid=%u old_uid=%u old_gid=%d noderef=%d\",\n                 path, uid, gid, fmd->getCUid(), fmd->getCGid(), nodereference);\n        eos::audit::Stat beforeStat;\n        {\n          eos::mgm::auditutil::buildStatFromFileMD(fmd, beforeStat, /*includeSize=*/false, /*includeChecksum=*/false, /*includeNs=*/true);\n        }\n\n        // Subtract the file\n        if (ns_quota) {\n          ns_quota->removeFile(fmd.get());\n        }\n\n        // Change the owner\n        if ((unsigned int) uid != 0xffffffff) {\n          fmd->setCUid(uid);\n        }\n\n        // Change the group\n        if (!vid.uid && ((unsigned int) gid != 0xffffffff)) {\n          fmd->setCGid(gid);\n        }\n\n        // Re-add the file\n        if (ns_quota) {\n          ns_quota->addFile(fmd.get());\n        }\n\n        fmd->setCTimeNow();\n        eosView->updateFileStore(fmd.get());\n        lock.Release();\n        gOFS->FuseXCastRefresh(fmd->getIdentifier(), cmd->getParentIdentifier());\n        if (mAudit) {\n          eos::audit::Stat afterStat;\n          eos::mgm::auditutil::buildStatFromFileMD(fmd, afterStat, /*includeSize=*/false, /*includeChecksum=*/false, /*includeNs=*/true);\n          if (mAudit && gOFS->AllowAuditModification(path)) mAudit->audit(eos::audit::CHOWN, path, vid, std::string(logId), std::string(cident), \"mgm\", std::string(), &beforeStat, &afterStat, std::string(), std::string(), std::string(), __FILE__, __LINE__, VERSION);\n        }\n      }\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  if (cmd && (!errno)) {\n    EXEC_TIMING_END(\"Chmod\");\n    return SFS_OK;\n  }\n\n  return Emsg(epname, error, errno, \"chown\", path);\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Coverage.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Coverage.inc\n// Author: Mihai Patrascoiu - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n\n/*----------------------------------------------------------------------------*/\n/* @brief profiling function flushing coverage data\n *\n * @param sig signal caught\n *\n * Prints the collected gcov data upon receiving the signal.\n * The data should be collected via a tool capable of processing gcov output.\n */\n/*----------------------------------------------------------------------------*/\nvoid\nxrdmgmofs_coverage(int sig)\n{\n#ifdef COVERAGE_BUILD\n  eos_static_notice(\"msg=\\\"printing coverage data\\\"\");\n  __gcov_dump();\n\n  // Get a map of all the loaded dynamic libraries\n  using eos::common::PluginManager;\n  PluginManager& pm = PluginManager::GetInstance();\n  PluginManager::DynamicLibMap dynamicLibMap = pm.GetDynamicLibMap();\n\n  typedef void (*CoverageFunc)();\n\n  // Call the exported coverage function on each dynamic library\n  for (auto& dLib: dynamicLibMap) {\n    CoverageFunc coverageFunc =\n        (CoverageFunc) dLib.second->GetSymbol(\"plugin_coverage\");\n\n    if (coverageFunc != NULL) {\n      eos_static_notice(\"msg=\\\"calling exported coverage function for: %s\\\"\",\n                        dLib.first.c_str());\n      coverageFunc();\n    }\n  }\n\n  return;\n#endif\n\n  eos_static_notice(\"msg=\\\"compiled without coverage support\\\"\");\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/DeleteExternal.inc",
    "content": "// ----------------------------------------------------------------------\n// File: DeleteExternal.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\nbool\nXrdMgmOfs::DeleteExternal(eos::common::FileSystem::fsid_t fsid,\n                          unsigned long long fid, bool is_fsck)\n{\n  using namespace eos::common;\n  std::string fst_queue;\n  std::string fst_host;\n  int fst_port = 1095;\n  XrdOucString capability = \"\";\n  {\n    eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n    auto* fs = FsView::gFsView.mIdView.lookupByID(fsid);\n\n    if (fs) {\n      capability += \"&mgm.access=delete\";\n      capability += \"&mgm.manager=\";\n      capability += gOFS->ManagerId.c_str();\n      capability += \"&mgm.fsid=\";\n      capability += (int) fs->GetId();\n      capability += \"&mgm.fids=\";\n      capability += eos::common::FileId::Fid2Hex(fid).c_str();\n      fst_queue = fs->GetQueue().c_str();\n      fst_host = fs->GetHost();\n      fst_port = fs->getCoreParams().getLocator().getPort();\n    } else {\n      eos_static_err(\"msg=\\\"no such file system object\\\" fsid=%lu\", fsid);\n      return false;\n    }\n  }\n  // Encrypt the capability information\n  XrdOucEnv incapenv(capability.c_str());\n  XrdOucEnv* outcapenv = 0;\n  SymKey* symkey = eos::common::gSymKeyStore.GetCurrentKey();\n  int caprc = 0;\n\n  if ((caprc = SymKey::CreateCapability(&incapenv, outcapenv, symkey,\n                                        mCapabilityValidity))) {\n    eos_static_err(\"msg=\\\"unable to create capability\\\" errno=%u\", caprc);\n    return false;\n  }\n\n  int caplen = 0;\n  bool ok = false;\n  std::string qreq = \"/?fst.pcmd=drop\";\n\n  if (is_fsck) {\n    qreq += \"&fst.drop.type=fsck\";\n  }\n\n  qreq += outcapenv->Env(caplen);\n  std::string qresp;\n\n  if (SendQuery(fst_host, fst_port, qreq, qresp)) {\n    eos_static_err(\"msg=\\\"unable to send deletion message\\\" target=%s\",\n                   fst_queue.c_str());\n  } else {\n    ok = true;\n  }\n\n  delete outcapenv;\n  return ok;\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/DropReplica.inc",
    "content": "//------------------------------------------------------------------------------\n// File: DropReplica.inc\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Drop replica form FST and also update the namespace view for the given\n// file system id\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::DropReplica(eos::IFileMD::id_t fid,\n                       eos::common::FileSystem::fsid_t fsid) const\n{\n  bool retc = true;\n\n  if (fsid == 0ull) {\n    return retc;\n  }\n\n  eos_info(\"msg=\\\"drop replica/stripe\\\" fxid=%08llx fsid=%lu\",\n           fid, fsid);\n\n  // Send external deletion to the FST\n  if (gOFS && !gOFS->DeleteExternal(fsid, fid, true)) {\n    eos_err(\"msg=\\\"failed to send unlink to FST\\\" fxid=%08llx fsid=%lu\",\n            fid, fsid);\n    retc = false;\n  }\n\n  // Drop from the namespace, we don't need the path as root can drop by fid\n  XrdOucErrInfo err;\n  eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n\n  if (gOFS && gOFS->_dropstripe(\"\", fid, err, vid, fsid, true)) {\n    eos_err(\"msg=\\\"failed to drop replicas from ns\\\" fxid=%08llx fsid=%lu\",\n            fid, fsid);\n  }\n\n  return retc;\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/ErrorLogListener.inc",
    "content": "//------------------------------------------------------------------------------\n// File: ErrorLogListener.inc\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n//------------------------------------------------------------------------------\n\nnamespace\n{\n\n//------------------------------------------------------------------------------\n// Make sure the log file exists and can be accessed\n//------------------------------------------------------------------------------\nbool CheckFileExistanceAndPerm(const std::string& log_file, std::string& err)\n{\n  // Check that the log file exists and we're allowed to write to it\n  struct stat info;\n\n  if (::stat(log_file.c_str(), &info)) {\n    if (errno == ENOENT) {\n      // Try to create the log file\n      int fd = open(log_file.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);\n\n      if (fd == -1) {\n        err = \"cannot create log file\";\n        return false;\n      }\n\n      close(fd);\n    } else {\n      err = \"cannot access log file\";\n      return false;\n    }\n  } else {\n    // Check write permissions\n    uid_t euid = geteuid();\n\n    if (info.st_uid != euid) {\n      err = \"wrong owner of the log file\";\n      return false;\n    }\n\n    if ((info.st_mode & (S_IRUSR | S_IWUSR)) == 0) {\n      err = \"wrong permissions for log file\";\n      return false;\n    }\n  }\n\n  return true;\n}\n}\n\n//------------------------------------------------------------------------------\n// Start thread listening for error messages and log them\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::ErrorLogListenerThread(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"ErrorLogListener\");\n  static std::string channel = \"/eos/*/errorreport\";\n  static std::string log_path = \"/var/log/eos/mgm/error.log\";\n  // Tag used when EOS_ZSTD_LOGGING is enabled: produces rotating segments at\n  // <base>/logs/error-YYYYmmdd-HHMMSS.zst with a symlink <base>/error.zstd.\n  static const char* zstd_tag = \"error\";\n  auto& logging = eos::common::Logging::GetInstance();\n  const bool use_zstd = logging.IsZstdEnabled();\n  FILE* file = nullptr;\n\n  if (!use_zstd) {\n    std::string err_msg;\n\n    if (!CheckFileExistanceAndPerm(log_path, err_msg)) {\n      eos_static_err(\"msg=\\\"failed to stat QDB error log listener\\\" \"\n                     \"err_msg=\\\"%s\\\"\", err_msg.c_str());\n      return;\n    }\n\n    // Open log file or create if necessary\n    file = fopen(log_path.c_str(), \"a+\");\n\n    if (file == nullptr) {\n      eos_static_err(\"msg=\\\"failed to open error log file\\\" path=\\\"%s\\\" errno=%d\",\n                     log_path.c_str(), errno);\n      return;\n    }\n\n    XrdSysLogger logger(fileno(file), 1);\n    int retc = logger.Bind(log_path.c_str(), 1);\n    // Disable XRootD log rotation\n    logger.setRotate(0);\n    eos_static_info(\"msg=\\\"starting error report listener\\\" mode=plain \"\n                    \"path=\\\"%s\\\" bind_retc=%d\", log_path.c_str(), retc);\n  } else {\n    eos_static_info(\"msg=\\\"starting error report listener\\\" mode=zstd \"\n                    \"tag=\\\"%s\\\"\", zstd_tag);\n  }\n\n  std::string out;\n  eos::mq::QdbListener err_listener(mQdbContactDetails, channel);\n\n  while (!assistant.terminationRequested()) {\n    if (err_listener.fetch(out, &assistant)) {\n      if (use_zstd) {\n        logging.WriteZstd(zstd_tag, out.c_str());\n      } else {\n        fprintf(file, \"%s\\n\", out.c_str());\n      }\n    }\n  }\n\n  if (file) {\n    (void) fflush(file);\n    (void) fclose(file);\n  }\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Exists.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Exists.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::exists(const char* inpath,\n                  XrdSfsFileExistence& file_exists,\n                  XrdOucErrInfo& error,\n                  const XrdSecEntity* client,\n                  const char* ininfo)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief Check for the existence of a file or directory\n *\n * @param inpath path to check existence\n * @param file_exists return parameter specifying the type (see _exists for details)\n * @param error error object\n * @param client XRootD authentication object\n * @param ininfo CGI\n * @result SFS_OK on success otherwise SFS_ERROR\n *\n * The function calls the internal implementation _exists. See there for details.\n */\n/*----------------------------------------------------------------------------*/\n{\n  static const char* epname = \"exists\";\n  const char* tident = error.getErrUser();\n  eos::common::VirtualIdentity vid;\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz,\n                              AOP_Stat, inpath);\n  EXEC_TIMING_END(\"IdMap\");\n  NAMESPACEMAP;\n  BOUNCE_ILLEGAL_NAMES;\n  XrdOucEnv exists_Env(ininfo);\n  AUTHORIZE(client, &exists_Env, AOP_Stat, \"execute exists\", inpath, error);\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  BOUNCE_NOT_ALLOWED;\n  ACCESSMODE_R;\n  MAYSTALL;\n  MAYREDIRECT;\n  return _exists(path, file_exists, error, vid, ininfo);\n}\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::_exists(const char* path,\n                   XrdSfsFileExistence& file_exists,\n                   XrdOucErrInfo& error,\n                   const XrdSecEntity* client,\n                   const char* ininfo)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief check for the existence of a file or directory\n *\n * @param path path to check\n * @param file_exists return the type of the checked path\n * @param client XRootD authentication object\n * @param ininfo CGI\n * @return SFS_OK if found otherwise SFS_ERROR\n *\n * The values of file_exists are:\n * XrdSfsFileExistIsDirectory - this is a directory\n * XrdSfsFileExistIsFile - this is a file\n * XrdSfsFileExistNo - this is neither a file nor a directory\n *\n * This function may send a redirect response and should not be used as an\n * internal function. The internal function has as a parameter the virtual\n * identity and not the XRootD authentication object.\n */\n/*----------------------------------------------------------------------------*/\n{\n  if ((path == nullptr) || (strlen(path) == 0)) {\n    eos_err(\"%s\", \"msg=\\\"null or empty path\\\"\");\n    return SFS_ERROR;\n  }\n\n  // try if that is directory\n  EXEC_TIMING_BEGIN(\"Exists\");\n  gOFS->MgmStats.Add(\"Exists\", vid.uid, vid.gid, 1);\n  std::shared_ptr<eos::IContainerMD> cmd;\n  {\n    // -------------------------------------------------------------------------\n    eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, path, false);\n\n    try {\n      cmd = gOFS->eosView->getContainer(path, false);\n    } catch (eos::MDException& e) {\n      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                e.getErrno(), e.getMessage().str().c_str());\n    };\n\n    // -------------------------------------------------------------------------\n  }\n\n  if (!cmd) {\n    // -------------------------------------------------------------------------\n    // try if that is a file\n    // -------------------------------------------------------------------------\n    eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, path, false);\n    std::shared_ptr<eos::IFileMD> fmd;\n\n    try {\n      fmd = gOFS->eosView->getFile(path, false);\n    } catch (eos::MDException& e) {\n      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                e.getMessage().str().c_str());\n    }\n\n    // -------------------------------------------------------------------------\n    if (!fmd) {\n      file_exists = XrdSfsFileExistNo;\n    } else {\n      file_exists = XrdSfsFileExistIsFile;\n    }\n  } else {\n    file_exists = XrdSfsFileExistIsDirectory;\n  }\n\n  if (file_exists == XrdSfsFileExistNo) {\n    // get the parent directory\n    eos::common::Path cPath(path);\n    std::shared_ptr<eos::IContainerMD> dir;\n    eos::IContainerMD::XAttrMap attrmap;\n    // -------------------------------------------------------------------------\n    eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView,\n        cPath.GetParentPath(), false);\n\n    try {\n      dir = eosView->getContainer(cPath.GetParentPath(), false);\n      eos::IContainerMD::XAttrMap::const_iterator it;\n      // get attributes, directory will be locked under the _attr_ls() call!\n      gOFS->_attr_ls(cPath.GetParentPath(), error, vid, 0, attrmap);\n    } catch (eos::MDException& e) {\n      dir.reset();\n    }\n\n    // -------------------------------------------------------------------------\n\n    if (dir) {\n      MAYREDIRECT_ENOENT;\n      MAYSTALL_ENOENT;\n      XrdOucString redirectionhost = \"invalid?\";\n      int ecode = 0;\n      int rcode = SFS_OK;\n\n      if (attrmap.count(\"sys.redirect.enoent\")) {\n        // there is a redirection setting here\n        redirectionhost = \"\";\n        redirectionhost = attrmap[\"sys.redirect.enoent\"].c_str();\n        int portpos = 0;\n\n        if ((portpos = redirectionhost.find(\":\")) != STR_NPOS) {\n          XrdOucString port = redirectionhost;\n          port.erase(0, portpos + 1);\n          ecode = atoi(port.c_str());\n          redirectionhost.erase(portpos);\n        } else {\n          ecode = 1094;\n        }\n\n        rcode = SFS_REDIRECT;\n        error.setErrInfo(ecode, redirectionhost.c_str());\n        gOFS->MgmStats.Add(\"RedirectENOENT\", vid.uid, vid.gid, 1);\n        return rcode;\n      }\n    }\n  }\n\n  EXEC_TIMING_END(\"Exists\");\n  return SFS_OK;\n}\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::_exists(const char* path,\n                   XrdSfsFileExistence& file_exists,\n                   XrdOucErrInfo& error,\n                   eos::common::VirtualIdentity& vid,\n                   std::shared_ptr<eos::IContainerMD>& cmd,\n                   std::shared_ptr<eos::IFileMD>& fmd,\n                   const char* ininfo)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief check for the existence of a file or directory\n *\n * @param path path to check\n * @param file_exists return the type of the checked path\n * @param vid virtual identity of the client\n * @param cmd Container MD (out param)\n * @param fmd File MD (out param)\n * @param ininfo CGI\n * @return SFS_OK if found otherwise SFS_ERROR\n *\n * The values of file_exists are:\n * XrdSfsFileExistIsDirectory - this is a directory\n * XrdSfsFileExistIsFile - this is a file\n * XrdSfsFileExistNo - this is neither a file nor a directory\n *\n */\n/*----------------------------------------------------------------------------*/\n{\n  EXEC_TIMING_BEGIN(\"Exists\");\n  gOFS->MgmStats.Add(\"Exists\", vid.uid, vid.gid, 1);\n  // try if that is directory\n  {\n    // -------------------------------------------------------------------------\n    eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, path, false);\n\n    try {\n      cmd = gOFS->eosView->getContainer(path, false);\n    } catch (eos::MDException& e) {\n      cmd.reset();\n      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\", e.getErrno(),\n                e.getMessage().str().c_str());\n    };\n\n    // -------------------------------------------------------------------------\n  }\n\n  if (!cmd) {\n    // try if that is a file\n    // -------------------------------------------------------------------------\n    eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, path, false);\n\n    try {\n      fmd = gOFS->eosView->getFile(path, false);\n    } catch (eos::MDException& e) {\n      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                e.getErrno(), e.getMessage().str().c_str());\n    }\n\n    // -------------------------------------------------------------------------\n\n    if (!fmd) {\n      file_exists = XrdSfsFileExistNo;\n    } else {\n      file_exists = XrdSfsFileExistIsFile;\n    }\n  } else {\n    file_exists = XrdSfsFileExistIsDirectory;\n  }\n\n  EXEC_TIMING_END(\"Exists\");\n  return SFS_OK;\n}\n\n/*----------------------------------------------------------------------------*/\n/*\n * @brief check for the existence of a file or directory by vid\n *\n * @param path path to check\n * @param file_exists return the type of the checked path\n * @param vid virtual identity of the client\n * @param ininfo CGI\n * @return SFS_OK if found otherwise SFS_ERROR\n *\n * The values of file_exists are:\n * XrdSfsFileExistIsDirectory - this is a directory\n * XrdSfsFileExistIsFile - this is a file\n * XrdSfsFileExistNo - this is neither a file nor a directory\n *\n */\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::_exists(const char* fileName,\n                   XrdSfsFileExistence& exists_flag,\n                   XrdOucErrInfo& out_error,\n                   eos::common::VirtualIdentity& vid,\n                   const char* opaque, bool take_lock)\n{\n  std::shared_ptr<eos::IContainerMD> cmd;\n  std::shared_ptr<eos::IFileMD> fmd;\n  return _exists(fileName, exists_flag, out_error, vid, cmd, fmd, opaque);\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/FAttr.inc",
    "content": "//------------------------------------------------------------------------------\n// File: FAttr.inc\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n//------------------------------------------------------------------------------\n\nnamespace\n{\n//----------------------------------------------------------------------------\n//! Helper method to allocate a XrdSfsFABuff structure inside the existing\n//! XrdSfsFACtl object and reserve the given sz for data information\n//----------------------------------------------------------------------------\nbool GetFABuff(XrdSfsFACtl& faCtl, int sz = 0)\n{\n  XrdSfsFABuff* fabP = (XrdSfsFABuff*)malloc(sz + sizeof(XrdSfsFABuff));\n\n  // Check if we allocate a buffer\n  if (!fabP) {\n    return false;\n  }\n\n  // Setup the buffer\n  fabP->next = faCtl.fabP;\n  faCtl.fabP = fabP;\n  fabP->dlen = sz;\n  return true;\n}\n}\n\n//------------------------------------------------------------------------------\n// Perform a filesystem extended attribute function\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::FAttr(XrdSfsFACtl* faReq,\n                 XrdOucErrInfo& error,\n                 const XrdSecEntity* client)\n{\n  static std::map<XrdSfsFACtl::RQST, Access_Operation> s_map {\n    {XrdSfsFACtl::RQST::faDel, AOP_Update},\n    {XrdSfsFACtl::RQST::faGet, AOP_Read},\n    {XrdSfsFACtl::RQST::faLst, AOP_Read},\n    {XrdSfsFACtl::RQST::faSet, AOP_Update}\n  };\n  static const char* epname = \"fattr\";\n\n  // Check if we only need to return support information\n  if (!faReq) {\n    eos_static_info(\"%s\", \"msg=\\\"fattr support info request\\\"\");\n    XrdOucEnv* env = error.getEnv();\n\n    if (!env) {\n      error.setErrInfo(ENOTSUP, \"Not supported\");\n      return SFS_ERROR;\n    }\n\n    env->PutInt(\"usxMaxNsz\", kXR_faMaxNlen);\n    env->PutInt(\"usxMaxVsz\", kXR_faMaxVlen);\n    return SFS_OK;\n  }\n\n  const char* tident = error.getErrUser();\n  const char* inpath = (faReq->path ? faReq->path : \"\");\n  const char* ininfo = (faReq->pcgi ? faReq->pcgi : \"\");\n  const Access_Operation acc_op = s_map[(XrdSfsFACtl::RQST)faReq->rqst];\n  eos::common::VirtualIdentity vid;\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz,\n                              acc_op, inpath);\n  EXEC_TIMING_END(\"IdMap\");\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  NAMESPACEMAP;\n  BOUNCE_ILLEGAL_NAMES;\n  XrdOucEnv access_Env(ininfo);\n  AUTHORIZE(client, &access_Env, acc_op, \"update\", inpath, error);\n  BOUNCE_NOT_ALLOWED;\n  int rc = SFS_OK;\n  char* ptr = nullptr;\n  unsigned int pfx_len = (*faReq->nPfx ? sizeof(faReq->nPfx) : 0u);\n\n  switch (faReq->rqst) {\n  case XrdSfsFACtl::faGet: {\n    eos_info(\"msg=\\\"xattr get\\\" path=\\\"%s\\\" num_attrs=%i\",\n             path, faReq->iNum);\n    unsigned int len_values = 0u;\n    std::string xattr_val;\n    std::map<std::string, std::string> xattrs;\n\n    for (unsigned int i = 0; i < faReq->iNum; ++i) {\n      eos_debug(\"msg=\\\"xattr get\\\" name=\\\"%s\\\"\", faReq->info[i].Name);\n      // Skip any xrootd specific prefix\n      ptr = faReq->info[i].Name;\n      ptr += pfx_len;\n\n      if (_attr_get(path, error, vid, ininfo, ptr, xattr_val) == SFS_OK) {\n        faReq->info[i].faRC = 0;\n      } else {\n        faReq->info[i].faRC = ENOATTR;\n      }\n\n      xattrs[faReq->info[i].Name] = xattr_val;\n      len_values += xattr_val.length();\n    }\n\n    // Get buffer for the attribute values\n    if (!GetFABuff(*faReq, len_values)) {\n      errno = ENOMEM;\n      rc = Emsg(epname, error, errno, \"get fattrs\", faReq->path);\n      break;\n    }\n\n    unsigned int len = 0;\n    unsigned int index = 0;\n    ptr = faReq->fabP->data;\n\n    // Serialize the attribute values\n    for (const auto& xattr : xattrs) {\n      len = xattr.second.length();\n      (void) strncpy(ptr, xattr.second.c_str(), len);\n      faReq->info[index].Value = ptr;\n      faReq->info[index].VLen = len;\n      ptr += len;\n    }\n\n    break;\n  }\n\n  case XrdSfsFACtl::faLst: {\n    eos_debug(\"msg=\\\"xattr list\\\" path=\\\"%s\\\"\", path);\n    eos::IContainerMD::XAttrMap xattrs;\n    rc = _attr_ls(path, error, vid, ininfo, xattrs);\n\n    if ((rc == SFS_OK) && xattrs.size()) {\n      bool get_values = ((faReq->opts & XrdSfsFACtl::retval) ==\n                         XrdSfsFACtl::retval);\n      // Assumed true if get values is true\n      // bool explode = ((faReq->opts & XrdSfsFACtl::xplode) != 0);\n      int len_keys = 0;\n      int len_values = 0;\n      faReq->iNum = 0;\n      faReq->info = 0;\n\n      for (const auto& xattr : xattrs) {\n        ++faReq->iNum;\n        len_keys += xattr.first.length() + 1;\n        len_values += xattr.second.length();\n      }\n\n      // Serialize the attribute keys\n      if (!GetFABuff(*faReq, len_keys)) {\n        errno = ENOMEM;\n        rc = Emsg(epname, error, errno, \"list fattrs\", faReq->path);\n        break;\n      }\n\n      faReq->info = new XrdSfsFAInfo[faReq->iNum];\n      ptr = faReq->fabP->data;\n      int index = 0;\n\n      for (const auto& xattr : xattrs) {\n        (void) strcpy(ptr, xattr.first.c_str());\n        faReq->info[index].Name = ptr;\n        faReq->info[index].NLen = xattr.first.length();\n        faReq->info[index].VLen = 0;\n        ptr += xattr.first.length() + 1;\n        ++index;\n      }\n\n      if (get_values) {\n        index = 0;\n\n        if (!GetFABuff(*faReq, len_values)) {\n          errno = ENOMEM;\n          rc = Emsg(epname, error, errno, \"list fattrs\", faReq->path);\n          break;\n        }\n\n        ptr = faReq->fabP->data;\n        size_t len = 0;\n\n        // Serialize the attribute values\n        for (const auto& xattr : xattrs) {\n          len = xattr.second.length();\n          (void) strncpy(ptr, xattr.second.c_str(), len);\n          faReq->info[index].faRC = 0;\n          faReq->info[index].Value = ptr;\n          faReq->info[index].VLen = len;\n          ptr += len;\n          ++index;\n        }\n      }\n    }\n\n    break;\n  }\n\n  case XrdSfsFACtl::faSet: {\n    eos_info(\"msg=\\\"xattr set\\\" path=\\\"%s\\\" num_attrs=%u\",\n             path, faReq->iNum);\n    bool exclusive = ((faReq->opts & XrdSfsFACtl::newAtr) != 0);\n\n    for (unsigned int i = 0; i < faReq->iNum; ++i) {\n      ptr = faReq->info[i].Name;\n      ptr += pfx_len;\n      std::string xattr_val(faReq->info[i].Value, faReq->info[i].VLen);\n\n      if (_attr_set(path, error, vid, ininfo, ptr,\n                    xattr_val.c_str(), exclusive) == SFS_OK) {\n        faReq->info[i].faRC = 0;\n      } else {\n        faReq->info[i].faRC = errno;\n      }\n    }\n\n    break;\n  }\n\n  case XrdSfsFACtl::faDel: {\n    eos_info(\"msg=\\\"xattr del\\\" path=\\\"%s\\\" num_attrs=%u\",\n             path, faReq->iNum);\n\n    for (unsigned int i = 0; i < faReq->iNum; ++i) {\n      ptr = faReq->info[i].Name;\n      ptr += pfx_len;\n\n      if (_attr_rem(path, error, vid, ininfo, ptr) == SFS_OK) {\n        faReq->info[i].faRC = 0;\n      } else {\n        faReq->info[i].faRC = errno;\n      }\n    }\n\n    break;\n  }\n\n  default:\n    eos_info(\"msg=\\\"unknown xattr request\\\" path=\\\"%s\\\"\", path);\n    error.setErrInfo(ENOTSUP, \"Not supported\");\n    rc = SFS_ERROR;\n    break;\n  }\n\n  return rc;\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Find.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Find.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n\n\n\n//------------------------------------------------------------------------------\n// Low-level recursive namespace clone handling\n//------------------------------------------------------------------------------\n/* cFlag (cloneId defaults to 0):\n * '>' - list files modified after <cloneId>\n * '=' - list files with clone-id <cloneId>\n * '-' - clean up clone-id <cloneId>,\n * '+' - clone if modified after after <cloneId>,\n * '?' - list all files/directories with cloneId/stime detail\n * '!' - list all files where with non-zero cloneId different from <cloneid>\n * */\n#include \"common/FileId.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"json/json.h\"\n#include <string.h>\n\nclass _cloneFoundItem\n{\npublic:\n  eos::IContainerMD::id_t id;\n  int depth;\n  bool isContainer;\n\n  _cloneFoundItem(eos::IContainerMD::id_t i, int d, bool cont) : id(i), depth(d),\n    isContainer(cont) { };\n};\n\n/* curl encode string if needed */\nstatic std::string\n_clone_escape(std::string s)\n{\n  if (strpbrk(s.c_str(), \" %\") == NULL) {\n    return s;  /* only use escape sequences when needed */\n  }\n\n  std::string t = eos::common::StringConversion::curl_default_escaped(s);\n#ifdef notNeededThereAintNoSlashesInFilenames\n  size_t pos = 0;\n\n  while (pos = t.find(\"%2F\", pos)) {\n    t.replace(pos, 3, \"/\");\n    pos += 1;\n  }\n\n#endif\n  return (t);\n}\n\nstatic void\n_cloneResp(XrdOucErrInfo& out_error, XrdOucString& stdErr,\n           eos::common::VirtualIdentity& vid,\n           std::list<_cloneFoundItem>& _found, bool json_output, FILE* fstdout)\n{\n  std::stack<std::string> pp;\n  std::shared_ptr<eos::IContainerMD> cmd;\n  int depth = 0;\n  std::string p;\n  eos::IContainerMD::tmtime_t stime;\n  Json::Value j;\n  Json::StreamWriterBuilder jfw;\n  jfw[\"indentation\"] = \"\";\n\n  if (! _found.empty()) {                                   /* first element is root of tree */\n    p = gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(\n                                _found.front().id).get());\n    pp.push(p.substr(0, p.rfind('/',\n                                p.length() - 2) + 1)); /* \"parent\" path: /eos/a1/a2/ -> /eos/a1/ */\n    pp.push(std::string(\"/eos/a1/dummy/\"));               /* expect 1st element container @ depth 0, here's a dummy */\n  }\n\n  // typedef std::tuple<mode_t/*st_mode*/, id_t/*st_ino*/, int/*st_dev*/, int/*st_nlink*/, uid_t/*st_uid*/, gid_t/*st_gid*/,\n  //        size_t/*st_size*/, double/*st_atime*/, double/*st_mtime*/, double/*st_ctime*/> s_tuple;\n  char sts[2048];\n  const char* sts_format = \"(%d,\" /*st_mode*/\n                           \"%ld,\" /*st_ino*/ \"%d,\" /*st_dev*/ \"%d,\" /*st_nlink*/\n                           \"%ld,\" /*st_uid*/ \"%ld,\"/*st_gid*/ \"%ld,\" /*st_size*/\n                           \"%9.7f,\" /*st_atime*/\n                           \"%9.7f,\" /*st_mtime*/\n                           \"%9.7f)\" /*st_ctime*/;\n\n  for (auto i : _found) {\n    eos::IContainerMD::XAttrMap attrmap;\n\n    if (i.isContainer) {\n      try {\n        cmd = gOFS->eosDirectoryService->getContainerMD(i.id);\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        eos_static_err(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                       e.getMessage().str().c_str());\n        return;\n      }\n\n      while (i.depth <= depth) {            /* pop previous container(s) */\n        pp.pop();\n        depth--;\n      };\n\n      while (i.depth > depth) {\n        pp.push(pp.top() + _clone_escape(cmd->getName()) + \"/\");\n        depth++;\n      }\n\n      cmd->getTMTime(stime);\n\n      if (json_output) {\n        struct timespec ts;\n        j.clear();\n        j[\"n\"] = pp.top();\n        j[\"t\"] = (Json::Value::UInt64) stime.tv_sec;\n        j[\"c\"] = (Json::Value::UInt64) cmd->getCloneId();\n        j[\"T\"] = cmd->getCloneFST();\n        cmd->getMTime(ts);\n        j[\"mt\"] = (Json::Value::UInt64) ts.tv_sec;\n        cmd->getCTime(ts);\n        j[\"ct\"] = (Json::Value::UInt64) ts.tv_sec;\n        gOFS->listAttributes(gOFS->eosView, cmd.get(), attrmap, false);\n        eos::IContainerMD::ctime_t ctime, mtime;\n        cmd->getCTime(ctime);\n        cmd->getMTime(mtime);\n        snprintf(sts, sizeof(sts), sts_format,\n                 cmd->getMode() | S_IFDIR, cmd->getId(), 42,\n                 cmd->getNumFiles(), /*st_mode,st_ino,st_dev,st_nlink*/\n                 cmd->getCUid(), cmd->getCGid(),\n                 cmd->getNumContainers(),        /*st_uid,st_gid,st_size*/\n                 0.0,                                                            /*st_atime*/\n                 mtime.tv_sec + mtime.tv_nsec * 10E-9,\n                 ctime.tv_sec + ctime.tv_nsec * 10E-9);\n      } else {\n        fprintf(fstdout, \"%s %ld:%ld:%s\\n\", pp.top().c_str(), stime.tv_sec,\n                cmd->getCloneId(), cmd->getCloneFST().c_str());\n      }\n    } else {    /* a file */\n      std::shared_ptr<eos::IFileMD> fmd, gmd;\n      uint64_t mdino = 0, hardlinkTgt = 0;\n\n      try {\n        gmd = gOFS->eosFileService->getFileMD(i.id);\n\n        if (gmd->getName().substr(0, 13) == \"...eos.ino...\") {\n          /* This is a zombie hard link target, kept around simply because another file points to it;\n           * drop it from the dump - if that other file is backed up it'll get picked up again.\n           */\n          continue;\n        }\n\n        if (!gmd->hasAttribute(SYS_HARD_LINK)) {\n          fmd = gmd;\n\n          if (fmd->hasAttribute(SYS_HARD_LINK)) {\n            /* a (no-zombie) target for hard link(s), goes into the log */\n            hardlinkTgt = eos::common::FileId::FidToInode(fmd->getId());\n          }\n        } else {                                /* this is a hard link to another file */\n          /*\n           * for hard links:\n           *    name is filled from the named file,\n           *    time stamps, clone id, clone path, attributes from the hard link larget;\n           *\n           * on restore they could be fiddled back together over the clone_path;\n           * from above: we do not report the zombie targets themselves\n           */\n          mdino = std::stoll(gmd->getAttribute(SYS_HARD_LINK));\n          fmd = gOFS->eosFileService->getFileMD(eos::common::FileId::InodeToFid(mdino));\n          eos_static_debug(\"hlnk switched from %s to file %s (%#llx)\",\n                           gmd->getName().c_str(), fmd->getName().c_str(), mdino);\n        }\n      } catch (eos::MDException& e) {\n        eos_static_err(\"exception ec=%d emsg=\\\"%s\\\" dir %s id %#lx\\n\", e.getErrno(),\n                       e.getMessage().str().c_str(), p.c_str());\n        return;\n      }\n\n      gOFS->FuseXCastRefresh(fmd->getIdentifier(), eos::ContainerIdentifier(\n                               fmd->getContainerId()));\n      fmd->getSyncTime(stime);\n\n      if (json_output) {\n        char sbuff[256];\n        struct timespec ts;\n        j.clear();\n        sprintf(sbuff, \"%lx/%lx\", cmd->getId(), fmd->getId());\n        j[\"n\"] = pp.top() + gmd->getName();                 // Name\n        j[\"t\"] = (Json::Value::UInt64) stime.tv_sec;        // time stamp\n        j[\"c\"] = (Json::Value::UInt64) fmd->getCloneId();   // cloneId\n        j[\"T\"] = fmd->getCloneFST();                        // tag\n        j[\"p\"] = sbuff;                                     // clone path\n\n        if (mdino) {\n          j[\"H\"] = (Json::Value::UInt64)\n                   mdino;  // a hard link alias: the mdino can be used to find the peers on restore\n        }\n\n        if (hardlinkTgt) {\n          j[\"L\"] = (Json::Value::UInt64)\n                   hardlinkTgt;  // a hard link target: the inum can be used to find the peers on restore\n        }\n\n        if (fmd->isLink()) {\n          j[\"S\"] = fmd->getLink();                          // the target of the symlink\n        }\n\n        fmd->getMTime(ts);\n        j[\"mt\"] = (Json::Value::UInt64) ts.tv_sec;\n        fmd->getCTime(ts);\n        j[\"ct\"] = (Json::Value::UInt64) ts.tv_sec;\n        gOFS->listAttributes(gOFS->eosView, fmd.get(), attrmap, false);\n        eos::IContainerMD::ctime_t ctime, mtime;\n        cmd->getCTime(ctime);\n        cmd->getMTime(mtime);\n        size_t nlink = (attrmap.count(\"sys.eos.nlink\") > 0) ? std::stol(\n                         attrmap[\"sys.eos.nlink\"]) : 1;\n        snprintf(sts, sizeof(sts), sts_format,\n                 fmd->getFlags() | S_IFREG, fmd->getId(), 42,\n                 nlink,             /*st_mode,st_ino,st_dev,st_nlink*/\n                 fmd->getCUid(), fmd->getCGid(),\n                 fmd->getSize(),                 /*st_uid,st_gid,st_size*/\n                 0.0,                                                            /*st_atime*/\n                 mtime.tv_sec + mtime.tv_nsec * 10E-9,\n                 ctime.tv_sec + ctime.tv_nsec * 10E-9);\n      } else\n        fprintf(fstdout, \"%s%s %ld:%ld/%lx/%lx:%s\\n\", pp.top().c_str(),\n                _clone_escape(gmd->getName()).c_str(),\n                stime.tv_sec, fmd->getCloneId(), cmd->getId(), fmd->getId(),\n                fmd->getCloneFST().c_str());\n    }\n\n    if (json_output) {\n      Json::Value attr;\n\n      for (auto it = attrmap.begin(); it != attrmap.end(); it++) {\n        if ((it->first == \"sys.vtrace\") ||\n            (it->first == SYS_HARD_LINK) ||\n            (it->first == SYS_NUM_LINK)) {\n          continue;\n        }\n\n        attr[it->first] = it->second;\n      }\n\n      j[\"attr\"] = attr;\n      j[\"st\"] = sts;\n      fprintf(fstdout, \"%s\\n\", Json::writeString(jfw, j).c_str());\n    }\n  }\n};\n\nstatic bool\n_cloneMD(std::shared_ptr<eos::IContainerMD>& cloneMd, char cFlag,\n         uint64_t cloneId, std::shared_ptr<eos::IContainerMD>& cmd)\n{\n  char buff[1024];\n  snprintf(buff, sizeof(buff), \"%s/clone/%ld\", gOFS->MgmProcPath.c_str(),\n           cloneId);\n  std::string clonePath(buff);\n\n  try {\n    cloneMd = gOFS->eosView->getContainer(clonePath);\n\n    if (cFlag == '+') {\n      eos_static_err(\"clone directory %s already exists!\", clonePath.c_str());\n      return false;\n    }\n  } catch (eos::MDException& e) {\n    eos_static_debug(\"clonePath %s exception ec=%d emsg=\\\"%s\\\" cFlag '%c'\", buff,\n                     e.getErrno(), e.getMessage().str().c_str(), cFlag);\n\n    if (cFlag == '+' || cFlag == '-') {\n      /* for '-': the clone directory may have been incorrectly removed, this should\n       * not prevent a cleanup */\n      eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n      eos::common::Path mdPath(buff);\n\n      try {\n        std::shared_ptr<eos::IContainerMD> pCloneMd = gOFS->eosView->getContainer(\n              mdPath.GetParentPath());\n        cloneMd = gOFS->eosView->createContainer(clonePath);\n        cloneMd->setMode(S_IFDIR | S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);\n        eos_static_info(\"%s permissions are %#o\", clonePath.c_str(),\n                        cloneMd->getMode());\n        cloneMd->setAttribute(\"sys.clone.root\",  gOFS->eosView->getUri(cmd.get()));\n        gOFS->eosDirectoryService->updateStore(cloneMd.get());\n        gOFS->eosDirectoryService->updateStore(pCloneMd.get());\n        eos::ContainerIdentifier md_id = cloneMd->getIdentifier(); /* see \"mkdir\" */\n        eos::ContainerIdentifier d_id = pCloneMd->getIdentifier();\n        eos::ContainerIdentifier d_pid = pCloneMd->getParentIdentifier();\n        lock.Release();\n        gOFS->FuseXCastRefresh(md_id, d_id);\n        gOFS->FuseXCastRefresh(d_id, d_pid);\n      } catch (eos::MDException& e) {\n        eos_static_err(\"cannot create the %s directory mode 755\", clonePath.c_str());\n        return false;\n      }\n    } else {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nstatic int\n_clone(std::shared_ptr<eos::IContainerMD>& cmd,\n       XrdOucErrInfo& out_error, XrdOucString& stdErr,\n       eos::common::VirtualIdentity& vid,\n       std::list<_cloneFoundItem>& _found,\n       char cFlag, uint64_t cloneId, time_t newId,\n       std::shared_ptr<eos::IContainerMD> cloneMd, int depth)\n{\n  // cmd could almost be passed \"by value\", except for the \"-\" (purge) case; hence the cmd_ref passed by reference\n  std::shared_ptr<eos::IContainerMD>\n  ccmd;                          /* container pointer for recursion */\n  int rc = SFS_OK;\n  std::shared_ptr<eos::IFileMD> fmd;\n  std::string link;\n  eos::IContainerMD::tmtime_t stime;\n  eos::common::RWMutexWriteLock rwlock;\n  eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n\n  /* Only at depth 0: find/create clone anchor directory for operations that require it */\n  if (cloneMd == NULL && cFlag != '?' && cFlag != '>') {\n    if (! _cloneMD(cloneMd, cFlag, (cFlag == '+') ? newId : cloneId, cmd)) {\n      return SFS_ERROR;\n    }\n\n    /* The eosViewRWMutex lock is explicitly grabbed (for '+') only at the \"root\" level of the tree and\n     * \"quickly\" released and re-grabbed at each directory in lower levels. Hence at deeper\n     * recursion levels the lock is already held on entry */\n    if (cFlag == '+') {\n      rwlock.Grab(gOFS->eosViewRWMutex);\n    } else if (cFlag == '-' &&\n               cloneMd->hasAttribute(\"sys.clone.root\")) { /* reset start of purge */\n      std::string rootDir = cloneMd->getAttribute(\"sys.clone.root\");\n\n      try {\n        /* this only happens @ depth 0! */\n        cmd = gOFS->eosView->getContainer(rootDir);\n        eos_static_info(\"clone %ld purge hint %s\", cloneId, rootDir.c_str());\n      } catch (eos::MDException& e) {\n        eos_static_info(\"clone %ld root hint %s ignored ec=%d emsg='%s'\",\n                        cloneId, rootDir.c_str(), e.getErrno(), e.getMessage().str().c_str());\n      }\n    }\n  }\n\n  if (cFlag == '+') {\n    /* cloneId <= 9: special, single level markers\n     * all others: this is a new clone, make the directory part of it\n     */\n    uint64_t thisId = newId, saveId;\n\n    if (cloneId < 10) {\n      thisId = cloneId;\n      saveId = cmd->getCloneId();\n\n      if (saveId >= 10) {\n        saveId = 0;  /* only save an Id serving as marker */\n      }\n\n      cmd->setCloneFST(saveId ? std::to_string(saveId) :\n                       \"\");  /* save this for later restore */\n    }\n\n    cmd->setCloneId(thisId);\n    gOFS->eosDirectoryService->updateStore(cmd.get());\n\n    if (cloneId <= 9) {\n      return 0;\n    }\n  } else if (cFlag == '-' && (uint64_t)cmd->getCloneId() == cloneId) {\n    /* clean the directory flag if it is part of this clone */\n    std::string prev_marker =\n      cmd->getCloneFST();                 /* reset cloneId to a potential previous marker */\n    uint64_t cleanId = 0;\n\n    if (!prev_marker.empty()) {\n      cmd->setCloneFST(\"\");\n      cleanId = std::stol(prev_marker);\n    }\n\n    cmd->setCloneId(cleanId);\n    gOFS->eosDirectoryService->updateStore(cmd.get());\n  }\n\n  if (cFlag != '!' or (cmd->getCloneId() != 0 and\n                       (uint64_t)cmd->getCloneId() != cloneId)) {\n    _found.emplace_back(cmd->getId(), depth, true);  /* log this directory */\n  }\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"_found container %#lx depth %d %s cloneId=%d\", cmd->getId(),\n                     depth, cmd->getName().c_str(), cloneId);\n  }\n\n  for (auto fit = eos::FileMapIterator(cmd); fit.valid(); fit.next()) {\n    if (EOS_LOGS_DEBUG) {\n      eos_static_debug(\"%c depth %d file %s id %#lx\", cFlag, depth, fit.key().c_str(),\n                       fit.value());\n    }\n\n    try {\n      fmd = gOFS->eosFileService->getFileMD(fit.value());\n    } catch (eos::MDException& e) {\n      char sbuff[1024];\n      snprintf(sbuff, sizeof(sbuff), \"msg=\\\"exception\\\" ec=%d fn=%s/%s emsg=\\\"%s\\\"\\n\",\n               e.getErrno(), cmd->getName().c_str(), fit.key().c_str(),\n               e.getMessage().str().c_str());\n      eos_static_info(sbuff);\n      stdErr += sbuff;\n      stdErr += \"\\n\";\n      continue;\n    }\n\n    if (fmd->isLink()) {\n      link = fmd->getLink();\n    }\n\n    fmd->getSyncTime(stime);\n\n    switch (cFlag) {\n    case '>':\n      if ((uint64_t) stime.tv_sec < cloneId) {\n        continue;\n      }\n\n    /* fallthrough */\n\n    case '+':\n      if ((uint64_t) stime.tv_sec < cloneId) {\n        break;\n      }\n\n      fmd->setCloneId((uint64_t) newId);\n      fmd->setCloneFST(\"\");       /* clean clone fid */\n      gOFS->eosFileService->updateStore(fmd.get());\n      break;\n\n    case '=':\n    case '-':\n      if (fmd->getCloneId() != cloneId) {\n        continue;\n      }\n\n      if (cFlag == '-' && cloneId > 9) {\n        eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n        std::string hex_fid = fmd->getCloneFST();\n        fmd->setCloneId(0);         /* clear cloneId */\n        fmd->setCloneFST(\"\");       /* clean up clone fid */\n        gOFS->eosFileService->updateStore(fmd.get());\n        lock.Release();\n\n        if (hex_fid != \"\") {\n          eos::common::FileId::fileid_t clFid = eos::common::FileId::Hex2Fid(\n                                                  hex_fid.c_str());\n\n          try {\n            std::shared_ptr<eos::IFileMD> gmd = gOFS->eosFileService->getFileMD(clFid);\n            gOFS->_rem(gOFS->eosView->getUri(gmd.get()).c_str(), out_error, rootvid, \"\",\n                       false, true, true, true);\n          } catch (eos::MDException& e) {\n            eos_static_info(\"msg=\\\"exception\\\" ec=%d fid=%#lx emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                            clFid, e.getMessage().str().c_str());\n          }\n        }\n\n        continue;\n      }\n\n      break;\n\n    case '!':\n      if (fmd->getCloneId() == 0 || (uint64_t)fmd->getCloneId() == cloneId) {\n        continue;\n      }\n\n      break;\n\n    case '?':\n      break;\n\n    default: /* do something intelligent */\n      ;\n    }\n\n    /* The output is produced in _cloneResp, outside the big lock */\n    _found.emplace_back(fmd->getId(), depth, false);\n  }\n\n  for (auto dit = eos::ContainerMapIterator(cmd); dit.valid(); dit.next()) {\n    if (cFlag == '+') {\n      gOFS->eosViewRWMutex.UnLockWrite();\n    }\n\n    eos::Prefetcher::prefetchContainerMDWithChildrenAndWait(gOFS->eosView,\n        dit.value(), false);\n\n    if (cFlag == '+') {\n      gOFS->eosViewRWMutex.LockWrite();\n    }\n\n    try {\n      ccmd = gOFS->eosDirectoryService->getContainerMD(dit.value());\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      ccmd.reset();\n      eos_static_info(\"msg=\\\"exception\\\" ec=%d cid %#lx emsg=\\\"%s\\\"\\n\",\n                      e.getErrno(), dit.value(), e.getMessage().str().c_str());\n      continue;\n    }\n\n    ccmd->getTMTime(stime);\n    /* if (cFlag == '?' && stime.tv_sec < cloneId) continue;     only if stime reliably percolates down to the root */\n    uint64_t ccId = ccmd->getCloneId();         /* current container's cloneId */\n\n    if (ccId == 0 or cloneId == 0 or cFlag == '+' or cFlag == '!' or\n        ((cFlag == '-' or cFlag == '=') && ccId == cloneId)\n       ) {        /* Only descend for matching subdirs */\n      int rc2 = _clone(ccmd, out_error, stdErr, vid, _found, cFlag, cloneId, newId,\n                       cloneMd, depth + 1);\n\n      if (rc2 > rc) {\n        rc = rc2;\n      }\n    } else eos_static_debug(\"Not descending into did:%lld ccId %lld cFlag '%c'\",\n                              ccmd->getId(), ccId, cFlag);\n  }\n\n  if (cloneMd != NULL && depth == 0 &&\n      cFlag == '-') {         /* clean up clone directory */\n    std::list<std::string> ctrs2remove;\n    std::list<std::string> ctrs2zap;\n\n    for (auto dit = eos::ContainerMapIterator(cloneMd); dit.valid(); dit.next()) {\n      try {\n        ccmd = gOFS->eosDirectoryService->getContainerMD(dit.value());\n        std::list<std::string> files2remove;\n        std::list<std::string> files2zap;\n\n        for (auto fit = eos::FileMapIterator(ccmd); fit.valid(); fit.next()) {\n          try {\n            fmd = gOFS->eosFileService->getFileMD(fit.value());\n            files2remove.push_back(gOFS->eosView->getUri(fmd.get()));\n          } catch (eos::MDException& e) {\n            char sbuff[1024];\n            int sblen = snprintf(sbuff, sizeof(sbuff),\n                                 \"exception ec=%d emsg=\\\"%s\\\" cid %#lx %s fid %#lx %s\\n\",\n                                 e.getErrno(), e.getMessage().str().c_str(), dit.value(),\n                                 ccmd->getName().c_str(), fit.value(), fit.key().c_str());\n            stdErr += sbuff;\n            sbuff[sblen - 1] = '\\0' /* no new-line */;\n            eos_static_info(sbuff);\n            files2zap.push_back(fit.key());\n          }\n        }\n\n        for (auto it = files2remove.begin(); it != files2remove.end(); it++) {\n          try {\n            gOFS->_rem((*it).c_str(), out_error, rootvid, \"\",\n                       false, true, true, true);\n          } catch (eos::MDException& e) {\n            eos_static_err(\"exception ec=%d emsg=\\\"%s\\\" cid %#lx uri %s\\n\", e.getErrno(),\n                           e.getMessage().str().c_str(), dit.value(), (*it).c_str());\n          }\n        }\n\n        for (auto it = files2zap.begin(); it != files2zap.end(); it++) {\n          eos_static_info(\"zapping file %s in %s\", it->c_str(), ccmd->getName().c_str());\n          ccmd->removeFile(*it);\n        }\n\n        ctrs2remove.push_back(gOFS->eosView->getUri(ccmd.get()));\n      } catch (eos::MDException& e) {\n        ccmd.reset();\n        eos_static_info(\"exception ec=%d emsg=\\\"%s\\\" cid %#lx name %s\\n\", e.getErrno(),\n                        e.getMessage().str().c_str(), dit.value(), dit.key().c_str());\n        ctrs2zap.push_back(dit.key());\n        continue;\n      }\n    }\n\n    for (auto it = ctrs2remove.begin(); it != ctrs2remove.end(); it++) {\n      try {\n        gOFS->eosView->removeContainer(*it);\n      } catch (eos::MDException& e) {\n        char sbuff[4096];\n        int sblen = snprintf(sbuff, sizeof(sbuff),\n                             \"exception ec=%d emsg=\\\"%s\\\" name %s\\n\", e.getErrno(),\n                             e.getMessage().str().c_str(), it->c_str());\n        stdErr += sbuff;\n        out_error.setErrInfo(e.getErrno(), sbuff);\n        sbuff[sblen - 1] = '\\0' /* no new-line */;\n        eos_static_info(sbuff);\n        return SFS_ERROR;\n      }\n    }\n\n    for (auto it = ctrs2zap.begin(); it != ctrs2zap.end(); it++) {\n      eos_static_info(\"zapping %s\", (*it).c_str());\n\n      try {\n        cloneMd->removeContainer(*it);\n        gOFS->eosDirectoryService->updateStore(cloneMd.get());\n      } catch (eos::MDException& e) {\n        char sbuff[4096];\n        int sblen = snprintf(sbuff, sizeof(sbuff),\n                             \"exception ec=%d emsg=\\\"%s\\\" name %s\\n\", e.getErrno(),\n                             e.getMessage().str().c_str(), it->c_str());\n        eos_static_debug(sbuff);\n        out_error.setErrInfo(e.getErrno(), sbuff);\n        sbuff[sblen - 1] = '\\0' /* no new-line */;\n        eos_static_info(sbuff);\n        return SFS_ERROR;\n      }\n    }\n\n    try {\n      std::string cname = cloneMd->getName();\n      eos::ContainerIdentifier cloneDir = cloneMd->getParentIdentifier();\n      gOFS->eosView->removeContainer(gOFS->eosView->getUri(cloneMd.get()));\n      gOFS->FuseXCastDeletion(cloneDir, cname);\n    } catch (eos::MDException& e) {\n      char sbuff[4096];\n      int sblen = snprintf(sbuff, sizeof(sbuff),\n                           \"exception ec=%d emsg=\\\"%s\\\" name %s\\n\", e.getErrno(),\n                           e.getMessage().str().c_str(), cloneMd->getName().c_str());\n      out_error.setErrInfo(e.getErrno(), sbuff);\n      sbuff[sblen - 1] = '\\0' /* no new-line */;\n      eos_static_info(sbuff);\n      return SFS_ERROR;\n    }\n  }\n\n  return rc;\n}\n\n\n//------------------------------------------------------------------------------\n// Low-level namespace find command\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::_find(const char* path, XrdOucErrInfo& out_error,\n                 XrdOucString& stdErr, eos::common::VirtualIdentity& vid,\n                 std::map<std::string, std::set<std::string> >& found,\n                 const char* key, const char* val, bool no_files,\n                 time_t millisleep, bool nscounter, int maxdepth,\n                 const char* filematch, bool skip_version_dirs,\n                 bool json_output, FILE* fstdout,\n                 time_t max_ctime_dir, time_t max_ctime_file,\n                 std::map<std::string, time_t>* found_ctime_sec,\n                 ThreadAssistant* assistant)\n{\n  std::vector< std::vector<std::string> > found_dirs;\n  std::shared_ptr<eos::IContainerMD> cmd;\n  std::string Path = path;\n  EXEC_TIMING_BEGIN(\"Find\");\n\n  if (nscounter) {\n    gOFS->MgmStats.Add(\"Find\", vid.uid, vid.gid, 1);\n  }\n\n  if (*Path.rbegin() != '/') {\n    Path += '/';\n  }\n\n  errno = 0;\n  found_dirs.resize(1);\n  found_dirs[0].resize(1);\n  found_dirs[0][0] = Path.c_str();\n  int deepness = 0;\n  // Users cannot return more than 100k files and 50k dirs with one find,\n  // unless there is an access rule allowing deeper queries\n  static uint64_t dir_limit = 50000;\n  static uint64_t file_limit = 100000;\n  Access::GetFindLimits(vid, dir_limit, file_limit);\n  uint64_t files_found = 0;\n  uint64_t dirs_found = 0;\n  bool limitresult = false;\n  bool limited = false;\n  bool fail_if_limited = (out_error.getErrInfo() == E2BIG);\n\n  if ((vid.uid != 0) && (!vid.hasUid(eos::common::ADM_UID)) &&\n      (!vid.hasGid(eos::common::ADM_GID)) && (!vid.sudoer)) {\n    limitresult = true;\n  }\n\n  bool isClone = (key != NULL) && (strcmp(key, \"sys.clone\") == 0);\n\n  if (isClone) {\n    /* sys.clone==<flag><id>, flag ~= [>=?-+], id ~= \\d+ (a numeric timestamp)\n     * flag: '>' list files modified after <id>, '=' list files with clone-id <id>\n     * '-' = clean up clone-id <id>, '+' - reclone after <id>,\n     * '?' = list all with clone-id and stime data */\n    char cFlag = val[0];\n\n    if (strchr(\">=?-+!\", cFlag) == NULL) {\n      return SFS_ERROR;  /* invalid argugment */\n    }\n\n    time_t clone_id = atol(val + 1);                /* could be 0 */\n\n    if (limitresult) {\n      return SFS_ERROR;\n    }\n\n    eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, Path.c_str(), false);\n\n    try {\n      cmd = gOFS->eosView->getContainer(Path.c_str(), false);\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      cmd.reset();\n      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                e.getErrno(), e.getMessage().str().c_str());\n    }\n\n    time_t newId = time(NULL);\n    eos_static_info(\"sys.clone=%c%lld %s >%lld\",\n                    cFlag, clone_id, Path.c_str(), newId);\n    std::list<_cloneFoundItem> _found;\n    int rc = _clone(cmd, out_error, stdErr, vid, _found, cFlag, clone_id, newId,\n                    NULL, 0);  /* clone releases and re-acquires the eosViewRWMutex! */\n\n    if (rc == 0) {\n      _cloneResp(out_error, stdErr, vid, _found, json_output, fstdout);\n    }\n\n    return rc;\n  }\n\n  do {\n    bool permok = false;\n    found_dirs.resize(deepness + 2);\n\n    // Loop over all directories in that deepness\n    for (unsigned int i = 0; i < found_dirs[deepness].size(); ++i) {\n      Path = found_dirs[deepness][i].c_str();\n      eos_static_debug(\"msg=\\\"listing files in directory\\\" path=\\\"%s\\\"\",\n                       Path.c_str());\n\n      // Slow down the find command without holding locks\n      if (millisleep) {\n        std::this_thread::sleep_for(std::chrono::milliseconds(millisleep));\n      }\n\n      eos::IContainerMD::ctime_t ctime;\n      eos::Prefetcher::prefetchContainerMDWithChildrenAndWait(gOFS->eosView,\n          Path.c_str(), false, no_files, limitresult, dir_limit, file_limit);\n\n      try {\n        cmd = gOFS->eosView->getContainer(Path.c_str(), false);\n        eos::MDLocking::ContainerReadLock cmd_lock(cmd.get());\n        permok = (!vid.token) ? cmd->access(vid.uid, vid.gid, R_OK | X_OK) : false;\n        cmd->getCTime(ctime);\n      } catch (eos::MDException& e) {\n        cmd.reset();\n        errno = e.getErrno();\n        eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                  e.getErrno(), e.getMessage().str().c_str());\n      }\n\n      if (!cmd) {\n        continue;\n      }\n\n      if (skip_version_dirs &&\n          (cmd->getName().find(EOS_COMMON_PATH_VERSION_FILE_PREFIX) == 0)) {\n        continue;\n      }\n\n      // Skip directory entries which are newer than max_ctime except\n      // for the top recycle bin directory.\n      if (max_ctime_dir && (ctime.tv_sec > max_ctime_dir)) {\n        if (!Recycle::IsTopRecycleBin(Path)) {\n          continue;\n        }\n      }\n\n      if (!gOFS->allow_public_access(Path.c_str(), vid)) {\n        stdErr += \"error: public access level restriction - no access in  \";\n        stdErr += Path.c_str();\n        stdErr += \"\\n\";\n        continue;\n      }\n\n      if (!permok) {\n        // check-out for ACLs\n        permok = _access(Path.c_str(), R_OK | X_OK, out_error, vid, \"\") ? false : true;\n\n        if (!permok) {\n          stdErr += \"error: no permissions to read directory \";\n          stdErr += Path.c_str();\n          stdErr += \"\\n\";\n          continue;\n        }\n      }\n\n      // Add all children into the 2D vectors\n      for (auto dit = eos::ContainerMapIterator(cmd); dit.valid(); dit.next()) {\n        if (skip_version_dirs &&\n            (dit.key().find(EOS_COMMON_PATH_VERSION_FILE_PREFIX) == 0)) {\n          continue;\n        }\n\n        std::string fpath = Path.c_str();\n        fpath += dit.key();\n        fpath += \"/\";\n\n        // Check if we select by tag\n        if (key) {\n          XrdOucString wkey = key;\n\n          if (wkey.find(\"*\") != STR_NPOS) {\n            // This is a search for 'beginswith' match\n            eos::IContainerMD::XAttrMap attrmap;\n\n            if (!gOFS->_attr_ls(fpath.c_str(), out_error, vid,\n                                (const char*) 0, attrmap)) {\n              for (auto it = attrmap.begin(); it != attrmap.end(); it++) {\n                XrdOucString akey = it->first.c_str();\n\n                if (akey.matches(wkey.c_str())) {\n                  // Trick to add element\n                  (void)found[fpath].size();\n                }\n              }\n            }\n\n            found_dirs[deepness + 1].push_back(fpath.c_str());\n          } else {\n            // This is a search for a full match or a key search\n            std::string sval = val;\n            std::string attr = \"\";\n\n            if (!gOFS->_attr_get(fpath.c_str(), out_error, vid,\n                                 nullptr, key, attr)) {\n              found_dirs[deepness + 1].push_back(fpath.c_str());\n\n              if ((val == std::string(\"*\")) || (attr == val)) {\n                (void)found[fpath].size();\n              }\n            }\n          }\n        } else {\n          if (limitresult) {\n            // Apply  user limits for non root/admin/sudoers\n            if (dirs_found >= dir_limit) {\n              stdErr += \"warning: find results are limited for you to ndirs=\";\n              stdErr += (int) dir_limit;\n              stdErr += \" -  result is truncated!\\n\";\n              limited = true;\n              break;\n            }\n          }\n\n          found_dirs[deepness + 1].push_back(fpath.c_str());\n          (void)found[fpath].size();\n          ++dirs_found;\n        }\n      }\n\n      if (!no_files) {\n        std::string link;\n        std::string fname;\n\n        for (auto fit = eos::FileMapIterator(cmd); fit.valid(); fit.next()) {\n          fname = fit.key();\n          std::shared_ptr<eos::IFileMD> fmd = cmd->findFile(fname);\n          eos::MDLocking::FileReadLockPtr fmd_lock;\n\n          if (fmd) {\n            fmd_lock = eos::MDLocking::readLock(fmd.get());\n          }\n\n          if (fmd) {\n            eos::IContainerMD::ctime_t ctime;\n            fmd->getCTime(ctime);\n\n            // Skip file entries which are newer than max ctime\n            if (max_ctime_file && (ctime.tv_sec > max_ctime_file)) {\n              continue;\n            }\n\n            // Skip symbolic links\n            link = (fmd->isLink() ? fmd->getLink() : \"\");\n\n            if (limitresult) {\n              // Apply user limits for non root/admin/sudoers\n              if (files_found >= file_limit) {\n                stdErr += \"warning: find results are limited for you to nfiles=\";\n                stdErr += (int) file_limit;\n                stdErr += \" -  result is truncated!\\n\";\n                limited = true;\n                break;\n              }\n            }\n\n            if (!filematch) {\n              if (link.length()) {\n                std::string ip = fname;\n                ip += \" -> \";\n                ip += link;\n                found[Path].insert(ip);\n              } else {\n                found[Path].insert(fname);\n              }\n\n              if (found_ctime_sec) {\n                (*found_ctime_sec)[Path] = ctime.tv_sec;\n              }\n\n              ++files_found;\n            } else {\n              XrdOucString name = fname.c_str();\n\n              if (name.matches(filematch)) {\n                found[Path].insert(fname);\n\n                if (found_ctime_sec) {\n                  (*found_ctime_sec)[Path] = ctime.tv_sec;\n                }\n\n                ++files_found;\n              }\n            }\n          }\n        }\n      }\n\n      if (limited) {\n        break;\n      }\n    }\n\n    if (assistant && assistant->terminationRequested()) {\n      break;\n    }\n\n    ++deepness;\n  } while (found_dirs[deepness].size() && !limited &&\n           ((!maxdepth) || (deepness < maxdepth)));\n\n  if (!no_files) {\n    // If the result is empty, maybe this was a find by file\n    if (!found.size()) {\n      XrdSfsFileExistence file_exists;\n\n      if (((_exists(Path.c_str(), file_exists, out_error, vid, 0)) == SFS_OK) &&\n          (file_exists == XrdSfsFileExistIsFile)) {\n        eos::common::Path cPath(Path.c_str());\n        found[cPath.GetParentPath()].insert(cPath.GetName());\n      }\n    }\n  }\n\n  // Include also the directory which was specified in the query if it is\n  // accessible since it can evt. be missing if it is empty\n  XrdSfsFileExistence dir_exists;\n\n  if (((_exists(found_dirs[0][0].c_str(), dir_exists, out_error, vid,\n                0)) == SFS_OK) &&\n      (dir_exists == XrdSfsFileExistIsDirectory)) {\n    eos::common::Path cPath(found_dirs[0][0].c_str());\n    (void) found[found_dirs[0][0].c_str()].size();\n  }\n\n  if (nscounter) {\n    EXEC_TIMING_END(\"Find\");\n  }\n\n  if (fail_if_limited && limited) {\n    errno = E2BIG;\n    return Emsg(\"_find\", out_error, E2BIG,\n                \"query incomplete - too many files/dirs in tree\", path);\n  } else {\n    errno = 0;\n    return SFS_OK;\n  }\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/FsConfigListener.inc",
    "content": "// ----------------------------------------------------------------------\n// File: FsConfigListener.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Process incoming MGM configuration change\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::processIncomingMgmConfigurationChange(const std::string& key)\n{\n  std::string value = FsView::gFsView.GetGlobalConfig(key);\n\n  // Here we might get a change without the namespace, in this\n  // case we add the global namespace\n  if ((key.substr(0, 4) != \"map:\") &&\n      (key.substr(0, 3) != \"fs:\") &&\n      (key.substr(0, 6) != \"quota:\") &&\n      (key.substr(0, 4) != \"vid:\") &&\n      (key.substr(0, 7) != \"policy:\")) {\n    eos_info(\"msg=\\\"apply access config\\\" key=\\\"%s\\\" val=\\\"%s\\\"\",\n             key.c_str(), value.c_str());\n\n    if (key.find(\"iostat:\") == 0) {\n      gOFS->mIoStats->ApplyConfig(&FsView::gFsView);\n    } else {\n      Access::EnforceConfig(false);\n    }\n  } else {\n    eos_info(\"msg=\\\"set config value\\\" key=\\\"%s\\\" val=\\\"%s\\\"\", key.c_str(),\n             value.c_str());\n    gOFS->mConfigEngine->SetConfigValue(0, key.c_str(), value.c_str(), false);\n\n    // For fs modification we need to lock for write the FsView::ViewMutex\n    if (key.find(\"fs:\") == 0) {\n      std::string fs_key = key;\n      fs_key.erase(0, 3);\n      eos::common::RWMutexWriteLock wr_view_lock(FsView::gFsView.ViewMutex);\n      // To avoid issues when applying config changes in the slave we need to\n      // unregister the file system first and then apply the new configuration\n      const bool first_unregister = true;\n      FsView::gFsView.ApplyFsConfig(fs_key.c_str(), value.c_str(),\n                                    first_unregister);\n    } else if (key.find(\"quota:\") == 0) {\n      eos_info(\"%s\", \"msg=\\\"skip quota update as it might mess with \"\n               \"the namespace, will reload once we become master\\\"\");\n    } else {\n      XrdOucString err;\n      gOFS->mConfigEngine->ApplyEachConfig(key.c_str(), value.c_str(), &err);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Process geotag change on the specified filesystem\n//------------------------------------------------------------------------------\nvoid\nXrdMgmOfs::ProcessGeotagChange(const std::string& queue)\n{\n  std::string newgeotag;\n  eos::common::FileSystem::fsid_t fsid = 0;\n  eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n  FileSystem* fs = FsView::gFsView.mIdView.lookupByQueuePath(queue);\n\n  if (fs == nullptr) {\n    return;\n  }\n\n  fsid = (eos::common::FileSystem::fsid_t) fs->GetLongLong(\"id\");\n  newgeotag = fs->GetString(\"stat.geotag\");\n\n  if (fsid == 0 && newgeotag.empty()) {\n    return;\n  }\n\n  std::string oldgeotag = newgeotag;\n\n  if (FsView::gFsView.mNodeView.count(fs->GetQueue())) {\n    // Check if the change notification is an actual change in the geotag\n    FsNode* node = FsView::gFsView.mNodeView[fs->GetQueue()];\n    static_cast<GeoTree*>(node)->getGeoTagInTree(fsid, oldgeotag);\n    oldgeotag.erase(0, 8); // to get rid of the \"<ROOT>::\" prefix\n  }\n\n  if (oldgeotag != newgeotag) {\n    eos_warning(\"msg=\\\"received geotag change\\\" fsid=%lu old_geotag=\\\"%s\\\" \"\n                \"new_geotag=\\\"%s\\\"\", (unsigned long)fsid,\n                oldgeotag.c_str(), newgeotag.c_str());\n    // Release read lock and take write lock\n    fs_rd_lock.Release();\n    eos::common::RWMutexWriteLock fs_rw_lock(FsView::gFsView.ViewMutex);\n    eos::common::FileSystem::fs_snapshot_t snapshot;\n    fs->SnapShotFileSystem(snapshot);\n\n    // Update node view tree structure\n    if (FsView::gFsView.mNodeView.count(snapshot.mQueue)) {\n      FsNode* node = FsView::gFsView.mNodeView[snapshot.mQueue];\n      eos_debug(\"msg=\\\"update geotag of fsid=%lu in node=%s\",\n                (unsigned long)fsid, node->mName.c_str());\n\n      if (!static_cast<GeoTree*>(node)->erase(fsid)) {\n        eos_err(\"msg=\\\"error removing fsid=%lu from node=%s\\\"\",\n                (unsigned long)fsid, node->mName.c_str());\n      }\n\n      if (!static_cast<GeoTree*>(node)->insert(fsid)) {\n        eos_err(\"msg=\\\"error inserting fsid=%lu into node=%s\\\"\",\n                (unsigned long)fsid, node->mName.c_str());\n      }\n    }\n\n    // Update group view tree structure\n    if (FsView::gFsView.mGroupView.count(snapshot.mGroup)) {\n      FsGroup* group = FsView::gFsView.mGroupView[snapshot.mGroup];\n      eos_debug(\"msg=\\\"updating geotag of fsid=%lu in group=%s\\\"\",\n                (unsigned long)fsid, group->mName.c_str());\n\n      if (!static_cast<GeoTree*>(group)->erase(fsid)) {\n        eos_err(\"msg=\\\"error removing fsid=%lu from group=%s\\\"\",\n                (unsigned long)fsid, group->mName.c_str());\n      }\n\n      if (!static_cast<GeoTree*>(group)->insert(fsid)) {\n        eos_err(\"msg=\\\"error inserting fsid=%lu into group=%s\\\"\",\n                (unsigned long)fsid, group->mName.c_str());\n      }\n    }\n\n    // Update space view tree structure\n    if (FsView::gFsView.mSpaceView.count(snapshot.mSpace)) {\n      FsSpace* space = FsView::gFsView.mSpaceView[snapshot.mSpace];\n      eos_debug(\"msg=\\\"updating geotag of fsid=%lu in space=%s\\\"\",\n                (unsigned long)fsid, space->mName.c_str());\n\n      if (!static_cast<GeoTree*>(space)->erase(fsid)) {\n        eos_err(\"msg=\\\"error removing fsid=%lu from space=%s\\\"\",\n                (unsigned long)fsid, space->mName.c_str());\n      }\n\n      if (!static_cast<GeoTree*>(space)->insert(fsid)) {\n        eos_err(\"msg=\\\"error inserting fsid=%lu into space=%s\\\"\",\n                (unsigned long)fsid, space->mName.c_str());\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// A thread monitoring for important key-value changes in filesystems\n//------------------------------------------------------------------------------\nvoid XrdMgmOfs::FileSystemMonitorThread(ThreadAssistant& assistant) noexcept\n{\n  std::shared_ptr<eos::mq::FsChangeListener> fs_listener =\n    mMessagingRealm->GetFsChangeListener(\"filesystem-listener-thread\");\n  const std::set<std::string> interests {\"stat.errc\", \"stat.geotag\",\n                                         \"configstatus\", \"stat.boot\"};\n\n  // Register all the interests for this listener so that new file systems\n  // are properly registered with this listener based on it's interests\n  for (const auto& key : interests) {\n    fs_listener->subscribe(key);\n  }\n\n  FsView::gFsView.AddFsChangeListener(fs_listener, interests);\n\n  while (!assistant.terminationRequested()) {\n    eos::mq::FsChangeListener::Event event;\n\n    if (fs_listener->fetch(assistant, event) && !event.isDeletion()) {\n      if (event.key == \"stat.geotag\") {\n        ProcessGeotagChange(event.fileSystemQueue);\n      } else {\n        // This is a filesystem status error\n        if (gOFS->mMaster->IsMaster()) {\n          // only an MGM master needs to initiate draining\n          eos::common::FileSystem::fsid_t fsid = 0;\n          long long errc = 0;\n          std::string configstatus = \"\";\n          std::string bootstatus = \"\";\n          std::string space;\n          eos::common::ConfigStatus cfgstatus = eos::common::ConfigStatus::kOff;\n          eos::common::BootStatus bstatus = eos::common::BootStatus::kDown;\n          // read the id from the hash and the current error value\n          eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n          FileSystem* fs = FsView::gFsView.mIdView.lookupByQueuePath(\n                             event.fileSystemQueue);\n\n          if (fs) {\n            fsid = (eos::common::FileSystem::fsid_t) fs->GetLongLong(\"id\");\n            errc = (int) fs->GetLongLong(\"stat.errc\");\n            configstatus = fs->GetString(\"configstatus\");\n            bootstatus = fs->GetString(\"stat.boot\");\n            cfgstatus = eos::common::FileSystem::GetConfigStatusFromString(\n                          configstatus.c_str());\n            bstatus = eos::common::FileSystem::GetStatusFromString(bootstatus.c_str());\n            space = fs->getCoreParams().getSpace();\n\n            if (fsid == 0 || space.empty()) {\n              // We are in the initial state where most of the FS params are not populated\n              // skip further processing\n              continue;\n            }\n\n            if (gOFS->mFsScheduler != nullptr && gOFS->mFsScheduler->isRunning()) {\n              bool status = gOFS->mFsScheduler->setDiskStatus(space,\n                            fsid, cfgstatus);\n              bool act_status = gOFS->mFsScheduler->setDiskStatus(space,\n                                fsid, fs->GetActiveStatus(), bstatus);\n\n              if (!status || !act_status) {\n                eos_static_err(\"msg=\\\"Failed to set Disk Status in FsScheduler for disk\\\" %llu\",\n                               fsid);\n              }\n            }\n          }\n\n          if (fs && fsid && errc &&\n              (cfgstatus >= eos::common::ConfigStatus::kRO) &&\n              (bstatus == eos::common::BootStatus::kOpsError)) {\n            // Case when we take action and explicitly ask to start a drain job\n            fs->SetConfigStatus(eos::common::ConfigStatus::kDrain);\n\n            if (gOFS->mFsScheduler != nullptr && gOFS->mFsScheduler->isRunning()) {\n              bool status = gOFS->mFsScheduler->setDiskStatus(fs->getCoreParams().getSpace(),\n                            fsid,\n                            eos::common::ConfigStatus::kDrain);\n\n              if (!status) {\n                eos_static_err(\"msg=\\\"Failed to set Disk Status in FsScheduler for disk\\\" %llu\",\n                               fsid);\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}\n\n/*----------------------------------------------------------------------------*/\n/*\n * @brief file system listener agent starting drain jobs when receving opserror\n * and applying remote master configuration changes to the local configuration\n * object.\n *\n * This thread agent catches 'opserror' states on filesystems and executes the\n * drain job start routine on the referenced filesystem. If a filesystem\n * is removing the error code it also run's a stop drain job routine.\n * Additionally it applies changes in the MGM configuration which have been\n * broadcasted by a remote master MGM.\n */\n/*----------------------------------------------------------------------------*/\nvoid\nXrdMgmOfs::FsConfigListener(ThreadAssistant& assistant) noexcept\n{\n  eos::mq::GlobalConfigChangeListener changeListener(mMessagingRealm.get(),\n      \"fs-config-listener-thread\", MgmConfigQueue.c_str());\n\n  // Thread listening on filesystem errors and configuration changes\n  while (!assistant.terminationRequested()) {\n    eos::mq::GlobalConfigChangeListener::Event event;\n\n    if (changeListener.fetch(assistant, event)) {\n      if (!gOFS->mMaster->IsMaster()) {\n        if (!event.isDeletion()) {\n          // This is an MGM configuration modification -\n          // only an MGM slave needs to apply this.\n          processIncomingMgmConfigurationChange(event.key);\n        } else {\n          eos_static_info(\"msg=\\\"handle deletion event\\\" key=\\\"%s\\\"\",\n                          event.key.c_str());\n          gOFS->mConfigEngine->DeleteConfigValue(0, event.key.c_str(), false);\n          gOFS->mConfigEngine->ApplyKeyDeletion(event.key.c_str());\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Fsctl.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Fsctl.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n\n/*----------------------------------------------------------------------------*/\n/*\n * @brief implements locate and space-ls function\n *\n * @param cmd operation to run\n * @param args arguments for cmd\n * @param error error object\n * @param client XRootD authentication object\n *\n * This function locate's files on the redirector and return's the available\n * space in XRootD fashion.\n */\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::fsctl(const int cmd,\n                 const char* args,\n                 XrdOucErrInfo& error,\n                 const XrdSecEntity* client)\n\n{\n  const char* tident = error.getErrUser();\n  tlLogId.SetSingleShotLogId(tident);\n  eos_thread_info(\"cmd=%d args=%s\", cmd, args);\n  int opcode = cmd & SFS_FSCTL_CMD;\n\n  if (opcode == SFS_FSCTL_LOCATE) {\n    char locResp[4096];\n    char rType[3], *Resp[] = {rType, locResp};\n    rType[0] = 'S';\n    // we don't want to manage writes via global redirection - therefore we mark the files as 'r'\n    rType[1] = 'r'; //(fstat.st_mode & S_IWUSR            ? 'w' : 'r');\n    rType[2] = '\\0';\n\n    if (cmd & SFS_O_HNAME) {\n      sprintf(locResp, \"%s\", (char*) gOFS->ManagerId.c_str());\n    } else {\n      struct sockaddr_in sa;\n      int is_ipv4 = inet_pton(AF_INET, ManagerIp.c_str(), &(sa.sin_addr));\n\n      if (is_ipv4) {\n        sprintf(locResp, \"[::%s]:%d\", (char*)ManagerIp.c_str(), gOFS->ManagerPort);\n      } else {\n        sprintf(locResp, \"[%s]:%d\", (char*)ManagerIp.c_str(), gOFS->ManagerPort);\n      }\n    }\n\n    error.setErrInfo(strlen(locResp) + 3, (const char**) Resp, 2);\n    return SFS_DATA;\n  }\n\n  if (opcode == SFS_FSCTL_STATLS) {\n    int blen = 0;\n    char* buff = error.getMsgBuff(blen);\n    XrdOucString space = \"default\";\n    unsigned long long freebytes = 0;\n    unsigned long long maxbytes = 0;\n    // Take the sum's from all file systems in 'default'\n    std::string path = args;\n    std::string opaque = args;\n\n    if (path.find(\"?\") != std::string::npos) {\n      path.erase(path.find(\"?\"));\n      opaque.erase(0, opaque.find(\"?\") + 1);\n    }\n\n    XrdOucEnv env(opaque.c_str());\n    bool query_space = false;\n\n    if (env.Get(\"eos.space\")) {\n      query_space = true;\n      space = env.Get(\"eos.space\");\n    } else {\n      const char* const defaultSpaceOverride =\n        getenv(\"EOS_MGM_STATVFS_DEFAULT_SPACE\");\n\n      if (nullptr != defaultSpaceOverride && *defaultSpaceOverride != '\\0') {\n        query_space = true;\n        space = defaultSpaceOverride;\n      }\n    }\n\n    eos_thread_info(\"path=%s cgi=%s\", path.c_str(), opaque.c_str());\n\n    if (query_space ||\n        (!getenv(\"EOS_MGM_STATVFS_ONLY_QUOTA\") && ((path == \"/\") || (path == \"\"))) ||\n        (getenv(\"EOS_MGM_STATVFS_ONLY_SPACE\"))) {\n      {\n        eos::common::RWMutexReadLock vlock(FsView::gFsView.ViewMutex);\n\n        if (FsView::gFsView.mSpaceView.count(space.c_str())) {\n          freebytes =\n            FsView::gFsView.mSpaceView[space.c_str()]->SumLongLong(\"stat.statfs.freebytes\",\n                false);\n          maxbytes =\n            FsView::gFsView.mSpaceView[space.c_str()]->SumLongLong(\"stat.statfs.capacity\",\n                false);\n        }\n      } //vlock\n      unsigned long layoutid = Policy::GetSpacePolicyLayout(space.c_str());\n\n      if (layoutid) {\n        // if there is a space policy layout defined we scale values to logical bytes\n        float scalefactor = eos::common::LayoutId::GetSizeFactor(layoutid);\n\n        if (scalefactor) {\n          freebytes /= scalefactor;\n          maxbytes /= scalefactor;\n        }\n      }\n    } else {\n      if (path[path.length() - 1] != '/') {\n        path += '/';\n      }\n\n      // Get quota group values for path and id 0\n      auto map_quotas = Quota::GetGroupStatistics(path, 0);\n\n      if (!map_quotas.empty()) {\n        Quota::GetStatfs(path, maxbytes, freebytes);\n      }\n    }\n\n    static const char* Resp = \"oss.cgroup=%s&oss.space=%lld&oss.free=%lld\"\n                              \"&oss.maxf=%lld&oss.used=%lld&oss.quota=%lld\";\n    blen = snprintf(buff, blen, Resp, space.c_str(), maxbytes,\n                    freebytes, 64 * 1024 * 1024 * 1024LL /* fake 64GB */,\n                    maxbytes - freebytes, maxbytes);\n    error.setErrCode(blen + 1);\n    return SFS_DATA;\n  }\n\n  return Emsg(\"fsctl\", error, EOPNOTSUPP, \"fsctl\", args);\n}\n\n/*----------------------------------------------------------------------------*/\n/*\n * @brief FS control function implementing the locate and plugin call\n *\n * @cmd operation to run (locate or plugin)\n * @args args for the operation\n * @error error object\n * @client XRootD authentication obeject\n *\n * This function locates files on the redirector. Additionally it is used in EOS\n * to implement many stateless operations like commit/drop a replica, stat\n * a file/directory, create a directory listing for FUSE, chmod, chown, access,\n * utimes, get checksum, schedule to delete ...\n */\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::FSctl(const int cmd,\n                 XrdSfsFSctl& args,\n                 XrdOucErrInfo& error,\n                 const XrdSecEntity* client)\n{\n  char ipath[16384];\n  char iopaque[16384];\n  static const char* epname = \"FSctl\";\n  const char* tident = error.getErrUser();\n\n  if (args.Arg1Len) {\n    if (args.Arg1Len < 16384) {\n      strncpy(ipath, args.Arg1, args.Arg1Len);\n      ipath[args.Arg1Len] = 0;\n    } else {\n      return gOFS->Emsg(epname, error, EINVAL,\n                        \"convert path argument - string too long\", \"\");\n    }\n  } else {\n    ipath[0] = 0;\n  }\n\n  bool fusexset = false;\n\n  // check if this is a protocol buffer injection\n  if ((cmd == SFS_FSCTL_PLUGIN) && (args.Arg2Len > 5)) {\n    std::string key;\n    key.assign(args.Arg2, 6);\n    if (key == \"fusex:\") {\n      fusexset = true;\n    }\n  }\n\n  if (!fusexset && args.Arg2Len) {\n    if (args.Arg2Len < 16384) {\n      strncpy(iopaque, args.Arg2, args.Arg2Len);\n      iopaque[args.Arg2Len] = 0;\n    } else {\n      return gOFS->Emsg(epname, error, EINVAL,\n                        \"convert opaque argument - string too long\", \"\");\n    }\n  } else {\n    iopaque[0] = 0;\n  }\n\n  const char* inpath = ipath;\n  const char* ininfo = iopaque;\n  // Do the id mapping with the opaque information\n  eos::common::VirtualIdentity vid;\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  // @todo(esindril) the access operation type should be reviewed depending\n  // on the type of incoming request and adjust accordingly.\n  eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz,\n                              AOP_Stat, inpath, false);\n  EXEC_TIMING_END(\"IdMap\");\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  tlLogId.SetSingleShotLogId(tident);\n  NAMESPACEMAP;\n  BOUNCE_ILLEGAL_NAMES;\n  // ---------------------------------------------------------------------------\n  // from here on we can deal with XrdOucString which is more 'comfortable'\n  // ---------------------------------------------------------------------------\n  XrdOucString spath = path;\n  XrdOucString opaque = iopaque;\n  XrdOucString result = \"\";\n  XrdOucEnv env(opaque.c_str());\n  const char* scmd = env.Get(\"mgm.pcmd\");\n  XrdOucString execmd = scmd ? scmd : \"\";\n\n  // version and is_master is not submitted to access control\n  // so that features of the instance can be retrieved by an authenticated user and\n  // router front-ends can discover the activation state of the mgm\n  if ((execmd != \"is_master\") && (execmd != \"version\") && !fusexset) {\n    BOUNCE_NOT_ALLOWED;\n  }\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"fusexset=%d %s %s\", fusexset, args.Arg1, args.Arg2);\n    eos_thread_debug(\"path=%s opaque=%s\", spath.c_str(), opaque.c_str());\n  }\n\n  // ---------------------------------------------------------------------------\n  // XRootD Locate\n  // ---------------------------------------------------------------------------\n  if ((cmd == SFS_FSCTL_LOCATE)) {\n    ACCESSMODE_R;\n    MAYSTALL;\n    MAYREDIRECT;\n    // check if this file exists\n    XrdSfsFileExistence file_exists;\n\n    if ((_exists(spath.c_str(), file_exists, error, client, 0)) ||\n        (file_exists != XrdSfsFileExistIsFile)) {\n      return SFS_ERROR;\n    }\n\n    char locResp[4096];\n    char rType[3], *Resp[] = {rType, locResp};\n    rType[0] = 'S';\n    // we don't want to manage writes via global redirection - therefore we mark the files as 'r'\n    rType[1] = 'r'; //(fstat.st_mode & S_IWUSR            ? 'w' : 'r');\n    rType[2] = '\\0';\n    sprintf(locResp, \"[::%s]:%d \", (char*) gOFS->ManagerIp.c_str(),\n            gOFS->ManagerPort);\n    error.setErrInfo(strlen(locResp) + 3, (const char**) Resp, 2);\n    ZTRACE(fsctl, \"located at headnode: \" << locResp);\n    return SFS_DATA;\n  }\n\n  if (cmd == SFS_FSCTL_PLUGIO) {\n    return dispatchSFS_FSCTL_PLUGIO(args, error, vid, client);\n  }\n\n  if (cmd != SFS_FSCTL_PLUGIN) {\n    return Emsg(epname, error, EOPNOTSUPP, \"execute FSctl command [EOPNOTSUPP]\",\n                inpath);\n  }\n\n  // Fuse e(x)tension - this we always redirect to the RW master\n  if (fusexset) {\n    std::string protobuf;\n    protobuf.assign(args.Arg2 + 6, args.Arg2Len - 6);\n    vid.app = \"fuse\"; // tag client as fuse\n    return XrdMgmOfs::Fusex(path, ininfo, protobuf, env, error, vid, client);\n  }\n\n  if (scmd) {\n    FsctlCommand command = lookupFsctl(execmd.c_str());\n\n    switch (command) {\n    case FsctlCommand::access: {\n      return XrdMgmOfs::Access(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::adjustreplica: {\n      return XrdMgmOfs::AdjustReplica(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::checksum: {\n      return XrdMgmOfs::Checksum(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::chmod: {\n      return XrdMgmOfs::Chmod(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::chown: {\n      return XrdMgmOfs::Chown(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::commit: {\n      return XrdMgmOfs::Commit(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::drop: {\n      return XrdMgmOfs::Drop(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::event: {\n      return XrdMgmOfs::Event(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::getfmd: {\n      return XrdMgmOfs::Getfmd(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::getfusex: {\n      return XrdMgmOfs::GetFusex(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::is_master: {\n      return XrdMgmOfs::IsMaster(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::mkdir: {\n      return XrdMgmOfs::Mkdir(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::open: {\n      return XrdMgmOfs::Open(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::readlink: {\n      return XrdMgmOfs::Readlink(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::redirect: {\n      return XrdMgmOfs::Redirect(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::stat: {\n      return XrdMgmOfs::FuseStat(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::statvfs: {\n      return XrdMgmOfs::Statvfs(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::symlink: {\n      return XrdMgmOfs::Symlink(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::utimes: {\n      return XrdMgmOfs::Utimes(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::version: {\n      return XrdMgmOfs::Version(path, ininfo, env, error, vid, client);\n    }\n\n    case FsctlCommand::INVALID:\n    default:\n      eos_thread_err(\"No implementation for %s\", execmd.c_str());\n    }\n  }\n\n  return Emsg(epname, error, EINVAL, \"execute FSctl command [EINVAL]\", inpath);\n}\n\n/*----------------------------------------------------------------------------*/\n/*\n * Handle an SFS_FSCTL_PLUGIO command\n *\n */\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::dispatchSFS_FSCTL_PLUGIO(XrdSfsFSctl& args,\n                                    XrdOucErrInfo& error,\n                                    eos::common::VirtualIdentity& vid,\n                                    const XrdSecEntity* client)\n{\n  // args.Arg2 is always set to 0 by XrdXrootdProtocol::do_Qopaque(short qopt)\n  if (0 > args.Arg1Len) {\n    error.setErrInfo(EINVAL, \"Arg1Len of SFS_FSCTL_PLUGIO command is negative\");\n    return SFS_ERROR;\n  }\n\n  if (strnlen(args.Arg1, args.Arg1Len) == (unsigned int)(args.Arg1Len)) {\n    error.setErrInfo(EINVAL,\n                     \"Arg1 of SFS_FSCTL_PLUGIO command is not NULL terminated\");\n    return SFS_ERROR;\n  }\n\n  if (!strcmp(\"tgc\", args.Arg1)) {\n    return mTapeGc->handleFSCTL_PLUGIO_tgc(error, vid, client);\n  }\n\n  // Reaching this point means there was no handler for the SFS_FSCTL_PLUGIO\n  // request\n  static const int maxArgLen = 1024;\n  std::ostringstream errMsg;\n  errMsg << \"Unable to execute cmd=SFS_FSCTL_PLUGIO Arg1=\";\n\n  if (args.Arg1Len > maxArgLen) {\n    errMsg << \"\\\"LARGER THAN \" << maxArgLen << \" BYTES INCLUDING NULL TERMINATOR\\\"\";\n  } else {\n    errMsg << \"\\\"\" << args.Arg1 << \"\\\"\";\n  }\n\n  errMsg << \" [EOPNOTSUPP]\";\n  eos_err(errMsg.str().c_str());\n  error.setErrInfo(EOPNOTSUPP, errMsg.str().c_str());\n  return SFS_ERROR;\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Link.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Link.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/misc/AuditHelpers.hh\"\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::symlink(const char* source_name,\n                   const char* target_name,\n                   XrdOucErrInfo& error,\n                   const XrdSecEntity* client,\n                   const char* infoO,\n                   const char* infoN,\n                   bool overwrite)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief symlink a file or directory\n *\n * @param source_name source name\n * @param target_name target name\n * @param error error object\n * @param client XRootD authentication object\n * @param infoO CGI of the old name\n * @param infoN CGI of the new name\n * @return SFS_OK on success otherwise SFS_ERROR\n *\n */\n/*----------------------------------------------------------------------------*/\n{\n  static const char* epname = \"symlink\";\n  const char* tident = error.getErrUser();\n  errno = 0;\n  XrdOucString source, destination;\n  XrdOucEnv symlinko_Env(infoO);\n  XrdOucEnv symlinkn_Env(infoN);\n  XrdOucString sourcen = source_name;\n  XrdOucString targetn = target_name;\n\n  if (!symlinko_Env.Get(\"eos.encodepath\")) {\n    sourcen.replace(\"#space#\", \" \");\n  }\n\n  if (!symlinkn_Env.Get(\"eos.encodepath\")) {\n    targetn.replace(\"#space#\", \" \");\n  }\n\n  // Use a thread private vid\n  eos::common::VirtualIdentity vid;\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, infoO, tident, vid, gOFS->mTokenAuthz,\n                              AOP_Create, sourcen.c_str());\n  EXEC_TIMING_END(\"IdMap\");\n  eos_info(\"old-name=%s new-name=%s\", source_name, target_name);\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  const char* inpath = 0;\n  const char* ininfo = 0;\n  {\n    inpath = sourcen.c_str();\n    ininfo = infoO;\n    AUTHORIZE(client, &symlinko_Env, AOP_Create, \"link\", inpath, error);\n    NAMESPACEMAP;\n    BOUNCE_ILLEGAL_NAMES;\n    sourcen = path;\n  }\n  {\n    inpath = targetn.c_str();\n    ininfo = infoN;\n    NAMESPACEMAP;\n    BOUNCE_ILLEGAL_NAMES;\n    targetn = path;\n  }\n  BOUNCE_NOT_ALLOWED;\n  ACCESSMODE_W;\n  MAYSTALL;\n  MAYREDIRECT;\n  return symlink(sourcen.c_str(), targetn.c_str(), error, vid, infoO, infoN,\n                 overwrite);\n}\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::symlink(const char* source_name,\n                   const char* target_name,\n                   XrdOucErrInfo& error,\n                   eos::common::VirtualIdentity& vid,\n                   const char* infoO,\n                   const char* infoN,\n                   bool overwrite)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief symlink a file or directory\n *\n * @param source_name source name\n * @param target_name target name\n * @param error error object\n * @param vid virtual identity of the client\n * @param infoO CGI of the source name\n * @param infoN CGI of the target name\n * @return SFS_OK on success otherwise SFS_ERROR\n *\n */\n/*----------------------------------------------------------------------------*/\n{\n  static const char* epname = \"symlink\";\n  errno = 0;\n  eos_info(\"source=%s target=%s\", source_name, target_name);\n  XrdOucString source, destination;\n  XrdOucEnv symlinko_Env(infoO);\n  XrdOucEnv symlinkn_Env(infoN);\n  XrdOucString sourcen = source_name;\n  XrdOucString targetn = target_name;\n  const char* inpath = 0;\n  const char* ininfo = 0;\n  {\n    inpath = source_name;\n    ininfo = infoO;\n    NAMESPACEMAP;\n    BOUNCE_ILLEGAL_NAMES;\n    sourcen = path;\n  }\n  BOUNCE_NOT_ALLOWED;\n  ACCESSMODE_W;\n  MAYSTALL;\n  MAYREDIRECT;\n\n  // check access permissions on source\n  if ((_access(sourcen.c_str(), W_OK, error, vid, infoO) != SFS_OK)) {\n    return SFS_ERROR;\n  }\n\n  return _symlink(sourcen.c_str(), targetn.c_str(), error, vid, infoO, infoN,\n                  overwrite);\n}\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::_symlink(const char* source_name,\n                    const char* target_name,\n                    XrdOucErrInfo& error,\n                    eos::common::VirtualIdentity& vid,\n                    const char* infoO,\n                    const char* infoN,\n                    bool overwrite\n                   )\n/*----------------------------------------------------------------------------*/\n/*\n * @brief symlink a file or directory\n *\n * @param source_name source name\n * @param target_name target name\n * @param error error object\n * @param vid virtual identity of the client\n * @param infoO CGI of the source name\n * @param infoN CGI of the target name\n * @return SFS_OK on success otherwise SFS_ERROR\n *\n */\n/*----------------------------------------------------------------------------*/\n\n{\n  static const char* epname = \"_symlink\";\n  errno = 0;\n  eos_info(\"source=%s target=%s\", source_name, target_name);\n  EXEC_TIMING_BEGIN(\"SymLink\");\n  eos::common::Path oPath(source_name);\n  std::string oP = oPath.GetParentPath();\n\n  if ((!source_name) || (!target_name)) {\n    errno = EINVAL;\n    return Emsg(epname, error, EINVAL, \"symlink - 0 source or target name\");\n  }\n\n  if (!strcmp(source_name, target_name)) {\n    errno = EINVAL;\n    return Emsg(epname, error, EINVAL, \"symlink - source and target are identical\");\n  }\n\n  gOFS->MgmStats.Add(\"Symlink\", vid.uid, vid.gid, 1);\n  XrdSfsFileExistence file_exists = XrdSfsFileExistNo;\n  _exists(oP.c_str(), file_exists, error, vid, infoN);\n\n  if (file_exists != XrdSfsFileExistIsDirectory) {\n    errno = ENOENT;\n    return Emsg(epname, error, ENOENT,\n                \"symlink - parent source dir does not exist\");\n  }\n\n  file_exists = XrdSfsFileExistNo;\n  _exists(source_name, file_exists, error, vid, infoN);\n\n  if (file_exists != XrdSfsFileExistNo) {\n    if (overwrite) {\n      _rem(source_name, error, vid, infoO);\n    } else {\n      errno = EEXIST;\n      return Emsg(epname, error, ENOENT, \"symlink - source exists\");\n    }\n  }\n\n  {\n    eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n\n    try {\n      std::shared_ptr<eos::IContainerMD> dir = eosView->getContainer(\n            oPath.GetParentPath());\n      eosView->createLink(oPath.GetPath(), target_name,\n                          vid.uid, vid.gid);\n      dir->setMTimeNow();\n      dir->notifyMTimeChange(gOFS->eosDirectoryService);\n      eosView->updateContainerStore(dir.get());\n      eos::ContainerIdentifier dir_id = dir->getIdentifier();\n      eos::ContainerIdentifier dir_pid = dir->getParentIdentifier();\n      lock.Release();\n      gOFS->FuseXCastRefresh(dir_id, dir_pid);\n    } catch (eos::MDException& e) {\n      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                e.getErrno(), e.getMessage().str().c_str());\n      errno = e.getErrno();\n      return Emsg(epname, error, errno, e.getMessage().str().c_str());\n    }\n  }\n\n  EXEC_TIMING_END(\"SymLink\");\n  // Audit symlink creation (source_name is the link path, target_name is the target)\n  if (mAudit && gOFS->AllowAuditModification(source_name)) {\n    mAudit->audit(eos::audit::SYMLINK, source_name, vid, std::string(logId), std::string(cident), \"mgm\", target_name, nullptr, nullptr, std::string(), std::string(), std::string(), __FILE__, __LINE__, VERSION);\n  }\n  return SFS_OK;\n}\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::readlink(const char* inpath,\n                    XrdOucErrInfo& error,\n                    XrdOucString& link,\n                    const XrdSecEntity* client,\n                    const char* ininfo)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief read symbolic link target\n *\n * @param name of the link to read\n * @param error error object\n * @param client XRootD authentication object\n * @param link target string if symbolic link otherwise empty\n * @return SFS_OK on success otherwise SFS_ERROR\n *\n */\n/*----------------------------------------------------------------------------*/\n{\n  static const char* epname = \"readlink\";\n  const char* tident = error.getErrUser();\n  // use a thread private vid\n  eos::common::VirtualIdentity vid;\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz,\n                              AOP_Read, inpath);\n  EXEC_TIMING_END(\"IdMap\");\n  eos_info(\"path=%s\", inpath);\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  errno = 0;\n  XrdOucEnv readlink_Env(ininfo);\n  AUTHORIZE(client, &readlink_Env, AOP_Read, \"link\", inpath, error);\n  NAMESPACEMAP;\n  BOUNCE_ILLEGAL_NAMES;\n  TOKEN_SCOPE;\n  BOUNCE_NOT_ALLOWED;\n  ACCESSMODE_R;\n  MAYSTALL;\n  MAYREDIRECT;\n  return _readlink(path, error, vid, link);\n}\n\n// ---------------------------------------------------------------------------\n// read symbolic link\n// ---------------------------------------------------------------------------\nint\nXrdMgmOfs::_readlink(const char* name,\n                     XrdOucErrInfo& error,\n                     eos::common::VirtualIdentity& vid,\n                     XrdOucString& link)\n{\n  static const char* epname = \"_readlink\";\n  errno = 0;\n  eos_info(\"name=%s\", name);\n  std::string linktarget;\n  std::string sname = name;\n  gOFS->MgmStats.Add(\"Symlink\", vid.uid, vid.gid, 1);\n  EXEC_TIMING_BEGIN(\"ReadLink\");\n  eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, sname, false);\n  eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n\n  try {\n    std::shared_ptr<eos::IFileMD> file = eosView->getFile(name, false);\n    std::string slink = file->getLink();\n    link = slink.c_str();\n  } catch (eos::MDException& e) {\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n              e.getErrno(), e.getMessage().str().c_str());\n    errno = e.getErrno();\n    return Emsg(epname, error, errno, e.getMessage().str().c_str());\n  }\n\n  EXEC_TIMING_END(\"ReadLink\");\n  return SFS_OK;\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Mkdir.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Mkdir.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n//------------------------------------------------------------------------------\n#include \"proto/Audit.pb.h\"\n\n//------------------------------------------------------------------------------\n/*\n * @brief create a directory with the given mode\n *\n * @param inpath directory path to create\n * @param Mode mode to set\n * @param error error object\n * @param client XRootD authentication object\n * @param ininfo CGI\n * @param outino return inode number\n * @return SFS_OK if success otherwise SFS_ERROR\n *\n * If mode contains SFS_O_MKPTH the full path is (possibly) created.\n */\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::mkdir(const char* inpath,\n                 XrdSfsMode Mode,\n                 XrdOucErrInfo& error,\n                 const XrdSecEntity* client,\n                 const char* ininfo,\n                 ino_t* outino)\n{\n  static const char* epname = \"mkdir\";\n  const char* tident = error.getErrUser();\n  // use a thread private vid\n  eos::common::VirtualIdentity vid;\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz,\n                              AOP_Mkdir, inpath);\n  EXEC_TIMING_END(\"IdMap\");\n  NAMESPACEMAP;\n  BOUNCE_ILLEGAL_NAMES;\n  TOKEN_SCOPE;\n  XrdOucEnv mkdir_Env(ininfo);\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  eos_info(\"path=%s ininfo=%s\", path, ininfo);\n  BOUNCE_NOT_ALLOWED;\n  ACCESSMODE_W;\n  MAYSTALL;\n  MAYREDIRECT;\n  return _mkdir(path, Mode, error, vid, ininfo, outino);\n}\n\n//------------------------------------------------------------------------------\n/*\n * @brief create a directory with the given mode\n *\n * @param inpath directory path to create\n * @param Mode mode to set\n * @param error error object\n * @param client XRootD authentication object\n * @param ininfo CGI\n * @param outino return inode number\n * @param nopermissioncheck - true: skip it false; do it\n * @return SFS_OK on success otherwise SFS_ERROR\n *\n * If mode contains SFS_O_MKPTH the full path is (possibly) created.\n *\n */\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::_mkdir(const char* path,\n                  XrdSfsMode Mode,\n                  XrdOucErrInfo& error,\n                  eos::common::VirtualIdentity& vid,\n                  const char* ininfo,\n                  ino_t* outino,\n                  bool nopermissioncheck)\n{\n  static const char* epname = \"_mkdir\";\n  //mode_t acc_mode = (Mode & S_IAMB) | S_IFDIR;\n  errno = 0;\n  EXEC_TIMING_BEGIN(\"Mkdir\");\n  gOFS->MgmStats.Add(\"Mkdir\", vid.uid, vid.gid, 1);\n  XrdOucString spath = path;\n  eos_info(\"path=%s\", spath.c_str());\n\n  if (!spath.beginswith(\"/\")) {\n    errno = EINVAL;\n    return Emsg(epname, error, EINVAL, \"create directory - you have to specify\"\n                \" an absolute pathname\", path);\n  }\n\n  bool recurse = false;\n  eos::common::Path cPath(path);\n  bool noParent = false;\n  std::shared_ptr<eos::IContainerMD> dir;\n  eos::IContainerMD::XAttrMap attrmap;\n  {\n    eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView,\n        cPath.GetParentPath());\n    eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n\n    // Check for the parent directory\n    if (spath != \"/\") {\n      try {\n        dir = eosView->getContainer(cPath.GetParentPath());\n      } catch (eos::MDException& e) {\n        eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                  e.getErrno(), e.getMessage().str().c_str());\n        dir.reset();\n        noParent = true;\n      }\n    }\n\n    // Check permission\n    if (dir) {\n      uid_t d_uid = dir->getCUid();\n      gid_t d_gid = dir->getCGid();\n      // ACL and permission check\n      Acl acl(cPath.GetParentPath(), error, vid, attrmap);\n      eos_info(\"path=%s acl=%d r=%d w=%d wo=%d egroup=%d mutable=%d\",\n               cPath.GetParentPath(),\n               acl.HasAcl(), acl.CanRead(), acl.CanWrite(), acl.CanWriteOnce(),\n               acl.HasEgroup(), acl.IsMutable());\n\n      if (!nopermissioncheck) {\n        // Immutable directory\n        if (vid.uid && !acl.IsMutable()) {\n          errno = EPERM;\n          return Emsg(epname, error, EPERM, \"create directory - immutable\",\n                      cPath.GetParentPath());\n        }\n      }\n\n      bool stdpermcheck = false;\n\n      if (acl.HasAcl()) {\n        if ((!acl.CanWrite()) && (!acl.CanWriteOnce())) {\n          // we have to check the standard permissions\n          stdpermcheck = true;\n        }\n      } else {\n        stdpermcheck = true;\n      }\n\n      // Admin can always create a directory\n      if (!nopermissioncheck) {\n        if (stdpermcheck &&\n            (vid.token || (!dir->access(vid.uid, vid.gid, X_OK | W_OK)))) {\n          errno = EPERM;\n          return Emsg(epname, error, EPERM, \"access(XW) parent directory\",\n                      cPath.GetParentPath());\n        }\n      }\n\n      // Check for sys.owner.auth entries, which let users operate as\n      // the owner of the directory\n      bool sticky_owner = false;\n      attr::checkDirOwner(attrmap, d_uid, d_gid, vid, sticky_owner, path);\n\n      if (sticky_owner) {\n        eos_info(\"msg=\\\"client acting as directory owner\\\" path=\\\"%s\\\" \"\n                 \"uid=\\\"%u=>%u\\\" gid=\\\"%u=>%u\\\"\", path, vid.uid,\n                 vid.gid, d_uid, d_gid);\n        vid.uid = d_uid;\n        vid.gid = d_gid;\n      }\n    }\n  }\n\n  // Check if the path exists anyway\n  if (Mode & SFS_O_MKPTH) {\n    recurse = true;\n    eos_debug(\"msg=\\\"SFS_O_MKPATH set\\\" path=\\\"%s\\\"\", path);\n\n    if (dir) {\n      std::shared_ptr<eos::IContainerMD> fulldir;\n      eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, path);\n      eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n\n      // Only if the parent exists, can the full path exist!\n      try {\n        fulldir = eosView->getContainer(path);\n      } catch (eos::MDException& e) {\n        fulldir.reset();\n        eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                  e.getMessage().str().c_str());\n      }\n\n      if (fulldir) {\n        EXEC_TIMING_END(\"Exists\");\n        return SFS_OK;\n      }\n    }\n  }\n\n  eos_debug(\"mkdir path=%s deepness=%d dirname=%s basename=%s\", path,\n            cPath.GetSubPathSize(), cPath.GetParentPath(), cPath.GetName());\n  std::shared_ptr<eos::IContainerMD> newdir;\n\n  if (noParent) {\n    if (recurse) {\n      int i, j;\n      uid_t d_uid = eos::common::VirtualIdentity::kNobodyUid;\n      gid_t d_gid = eos::common::VirtualIdentity::kNobodyGid;\n      std::string found_path;\n\n      // Walk up the paths until one exists\n      for (i = cPath.GetSubPathSize() - 1; i >= 0; i--) {\n        eos_debug(\"msg=\\\"check path existence\\\" path=\\\"%s\\\"\", cPath.GetSubPath(i));\n        errno = 0;\n        eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, cPath.GetSubPath(i));\n        eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n        attrmap.clear();\n\n        try {\n          dir = eosView->getContainer(cPath.GetSubPath(i));\n          found_path = cPath.GetSubPath(i);\n          d_uid = dir->getCUid();\n          d_gid = dir->getCGid();\n        } catch (eos::MDException& e) {\n          dir.reset();\n        }\n\n        if (dir) {\n          break;\n        }\n      }\n\n      // This is really a serious problem!\n      if (!dir) {\n        eos_crit(\"msg=\\\"no parent path traversing the namespace\\\" path=\\\"%s\\\"\",\n                 path);\n        errno = ENODATA;\n        return Emsg(epname, error, ENODATA, \"create directory\", cPath.GetSubPath(i));\n      }\n\n      // ACL and permission check\n      Acl acl(found_path.c_str(), error, vid, attrmap);\n      eos_info(\"path=\\\"%s\\\" acl=%d r=%d w=%d wo=%d egroup=%d mutable=%d\",\n               found_path.c_str(), acl.HasAcl(), acl.CanRead(), acl.CanWrite(),\n               acl.CanWriteOnce(), acl.HasEgroup(), acl.IsMutable());\n\n      if (!nopermissioncheck) {\n        if (vid.uid && !acl.IsMutable()) {\n          errno = EPERM;\n          return Emsg(epname, error, EPERM, \"create directory - immutable\",\n                      cPath.GetParentPath());\n        }\n      }\n\n      bool stdpermcheck = false;\n\n      if (acl.HasAcl()) {\n        if (!acl.CanWrite() && !acl.CanWriteOnce()) {\n          // We have to check the standard permissions\n          stdpermcheck = true;\n        }\n      } else {\n        stdpermcheck = true;\n      }\n\n      if (!nopermissioncheck && stdpermcheck) {\n        if (vid.token || !dir->access(vid.uid, vid.gid, X_OK | W_OK)) {\n          errno = EPERM;\n          return Emsg(epname, error, EPERM, \"create parent directory\",\n                      cPath.GetParentPath());\n        }\n      }\n\n      // Check for sys.owner.auth entries, which let users operate as\n      // the owner of the directory\n      bool sticky_owner = false;\n      attr::checkDirOwner(attrmap, d_uid, d_gid, vid, sticky_owner, path);\n\n      if (sticky_owner) {\n        eos_info(\"msg=\\\"client acting as directory owner\\\" path=\\\"%s\\\" \"\n                 \"uid=\\\"%u=>%u\\\" gid=\\\"%u=>%u\\\"\", found_path.c_str(),\n                 vid.uid, vid.gid, d_uid, d_gid);\n        vid.uid = d_uid;\n        vid.gid = d_gid;\n      }\n\n      eos::common::Path tmp_path(\"\");\n\n      for (j = i + 1; j < (int) cPath.GetSubPathSize(); ++j) {\n        eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n\n        try {\n          errno = 0;\n          eos_debug(\"creating path %s\", cPath.GetSubPath(j));\n          tmp_path.Init(cPath.GetSubPath(j));\n          dir = eosView->getContainer(tmp_path.GetParentPath());\n          newdir = eosView->createContainer(cPath.GetSubPath(j), recurse);\n          newdir->setCUid(vid.uid);\n          newdir->setCGid(vid.gid);\n          newdir->setMode(dir->getMode() & ~(1UL << 9));\n          // Inherit the attributes\n          eos::IFileMD::XAttrMap xattrs = dir->getAttributes();\n\n          for (const auto& elem : xattrs) {\n            newdir->setAttribute(elem.first, elem.second);\n          }\n\n          // Store the in-memory modification time into the parent\n          eos::IContainerMD::ctime_t ctime;\n          newdir->getCTime(ctime);\n          newdir->setMTime(ctime);\n          // Store the birth time\n          char btime[256];\n          snprintf(btime, sizeof(btime), \"%lu.%lu\", ctime.tv_sec, ctime.tv_nsec);\n          newdir->setAttribute(\"sys.eos.btime\", btime);\n          dir->setMTime(ctime);\n          dir->notifyMTimeChange(gOFS->eosDirectoryService);\n          // commit\n          eosView->updateContainerStore(newdir.get());\n          eosView->updateContainerStore(dir.get());\n          dir->notifyMTimeChange(gOFS->eosDirectoryService);\n          newdir->notifyMTimeChange(gOFS->eosDirectoryService);\n          eos::ContainerIdentifier nd_id = newdir->getIdentifier();\n          eos::ContainerIdentifier d_id = dir->getIdentifier();\n          eos::ContainerIdentifier d_pid = dir->getParentIdentifier();\n          lock.Release();\n          gOFS->FuseXCastMD(nd_id, d_id, ctime, true);\n          gOFS->FuseXCastRefresh(d_id, d_pid);\n        } catch (eos::MDException& e) {\n          errno = e.getErrno();\n          eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                    e.getErrno(), e.getMessage().str().c_str());\n          return Emsg(epname, error, errno, \"mkdir\", path);\n        }\n\n        if (!newdir && (errno != EEXIST)) {\n          return Emsg(epname, error, errno, \"mkdir - newdir is 0\", path);\n        }\n\n        dir.swap(newdir);\n      }\n    } else {\n      errno = ENOENT;\n      return Emsg(epname, error, errno, \"mkdir\", path);\n    }\n  }\n\n  // This might not be needed, but it is detected by coverty\n  if (!dir) {\n    return Emsg(epname, error, errno, \"mkdir\", path);\n  }\n\n  eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n\n  try {\n    errno = 0;\n    dir = eosView->getContainer(cPath.GetParentPath());\n    newdir = eosView->createContainer(path);\n    newdir->setCUid(vid.uid);\n    newdir->setCGid(vid.gid);\n    // @note: we always inherit the mode of the parent directory. So far nobody\n    // complained so we'll keep it as it is until someone does. The VTX bit is removed.\n    //newdir->setMode(acc_mode);\n    newdir->setMode(dir->getMode() & ~(1UL << 9));\n    // Store the in-memory modification time\n    eos::IContainerMD::ctime_t ctime;\n    newdir->getCTime(ctime);\n    newdir->setMTime(ctime);\n    // Store the birth time\n    char btime[256];\n    snprintf(btime, sizeof(btime), \"%lu.%lu\", ctime.tv_sec, ctime.tv_nsec);\n    newdir->setAttribute(\"sys.eos.btime\", btime);\n    dir->setMTime(ctime);\n\n    // If not version directory, then inherit attributes\n    if (cPath.GetFullPath().find(EOS_COMMON_PATH_VERSION_PREFIX) == STR_NPOS) {\n      eos::IFileMD::XAttrMap xattrs = dir->getAttributes();\n\n      for (const auto& elem : xattrs) {\n        newdir->setAttribute(elem.first, elem.second);\n      }\n    }\n\n    if (outino) {\n      *outino = newdir->getId();\n    }\n\n    // Commit to backend\n    eosView->updateContainerStore(newdir.get());\n    eosView->updateContainerStore(dir.get());\n    // Notify after attribute inheritance\n    newdir->notifyMTimeChange(gOFS->eosDirectoryService);\n    dir->notifyMTimeChange(gOFS->eosDirectoryService);\n    eos::ContainerIdentifier nd_id = newdir->getIdentifier();\n    eos::ContainerIdentifier d_id = dir->getIdentifier();\n    eos::ContainerIdentifier d_pid = dir->getParentIdentifier();\n    lock.Release();\n    gOFS->FuseXCastMD(nd_id, d_id, ctime, true);\n    gOFS->FuseXCastRefresh(d_id, d_pid);\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\",\n              e.getErrno(), e.getMessage().str().c_str());\n  }\n\n  if (!newdir && errno) {\n    return Emsg(epname, error, errno, \"mkdir\", path);\n  }\n\n  EXEC_TIMING_END(\"Mkdir\");\n  // Emit audit record for successful directory creation (append '/' to denote dir)\n  {\n    std::string apath = path ? path : \"\";\n\n    if (!apath.empty() && apath.back() != '/') {\n      apath.push_back('/');\n    }\n\n    if (!errno && mAudit && gOFS->AllowAuditModification(apath)) {\n      mAudit->audit(eos::audit::MKDIR, apath, vid, std::string(logId),\n                    std::string(cident), \"mgm\", std::string(), nullptr,\n                    nullptr, std::string(), std::string(), std::string(),\n                    __FILE__, __LINE__, VERSION);\n    }\n  }\n  return SFS_OK;\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/PathMap.inc",
    "content": "// ----------------------------------------------------------------------\n// File: PathMap.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n\n/*----------------------------------------------------------------------------*/\nvoid\nXrdMgmOfs::ResetPathMap()\n/*----------------------------------------------------------------------------*/\n/*\n * Reset all the stored entries in the path remapping table\n */\n/*----------------------------------------------------------------------------*/\n{\n  eos::common::RWMutexWriteLock lock(PathMapMutex);\n  PathMap.clear();\n}\n\n//------------------------------------------------------------------------------\n// Add a source/target pair to the path remapping table\n//------------------------------------------------------------------------------\n// @todo (esindril) drop the stop config and store the config when necessary\nbool\nXrdMgmOfs::AddPathMap(const char* source, const char* target,\n                      bool store_config)\n{\n  eos::common::RWMutexWriteLock lock(PathMapMutex);\n\n  if (PathMap.count(source)) {\n    return false;\n  } else {\n    PathMap[source] = target;\n\n    if (store_config) {\n      mConfigEngine->SetConfigValue(\"map\", source, target);\n    }\n\n    return true;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nXrdMgmOfs::PathRemap(const char* inpath,\n                     XrdOucString& outpath)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief translate a path name according to the configured mapping table\n *\n * @param inpath path to map\n * @param outpath remapped path\n *\n * This function does the path translation according to the configured mapping\n * table. It applies the 'longest' matching rule e.g. a rule\n * /eos/instance/store/ => /store/\n * would win over\n * /eos/instance/ = /global/\n * if the given path matches both prefixed like '/eos/instance/store/a'\n */\n/*----------------------------------------------------------------------------*/\n{\n  eos::common::Path cPath(inpath);\n  eos::common::RWMutexReadLock lock(PathMapMutex);\n  eos_debug(\"mappath=%s ndir=%d dirlevel=%d\", inpath, PathMap.size(),\n            cPath.GetSubPathSize() - 1);\n  outpath = inpath;\n\n  // remove double slashes\n  while (outpath.replace(\"//\", \"/\")) {\n  }\n\n  // append a / to the path\n  outpath += \"/\";\n\n  if (!PathMap.size()) {\n    outpath.erase(outpath.length() - 1);\n    return;\n  }\n\n  if (PathMap.count(inpath)) {\n    outpath.replace(inpath, PathMap[inpath].c_str());\n    outpath.erase(outpath.length() - 1);\n    return;\n  }\n\n  if (PathMap.count(outpath.c_str())) {\n    const std::string old_path = outpath.c_str();\n    const std::string new_path = PathMap[outpath.c_str()].c_str();\n    outpath.replace(old_path.c_str(), new_path.c_str());\n    outpath.erase(outpath.length() - 1);\n    return;\n  }\n\n  if (!cPath.GetSubPathSize()) {\n    outpath.erase(outpath.length() - 1);\n    return;\n  }\n\n  for (size_t i = cPath.GetSubPathSize() - 1; i > 0; i--) {\n    if (PathMap.count(cPath.GetSubPath(i))) {\n      outpath.replace(cPath.GetSubPath(i), PathMap[cPath.GetSubPath(i)].c_str(), 0,\n                      strlen(cPath.GetSubPath(i)));\n      outpath.erase(outpath.length() - 1);\n      return;\n    }\n  }\n\n  outpath.erase(outpath.length() - 1);\n  return;\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Remdir.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Remdir.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::remdir(const char* inpath,\n                  XrdOucErrInfo& error,\n                  const XrdSecEntity* client,\n                  const char* ininfo)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief delete a directory from the namespace\n *\n * @param inpath directory to delete\n * @param error error object\n * @param client XRootD authentication object\n * @param ininfo CGI\n * @return SFS_OK on success otherwise SFS_ERROR\n *\n */\n/*----------------------------------------------------------------------------*/\n{\n  static const char* epname = \"remdir\";\n  const char* tident = error.getErrUser();\n  eos::common::VirtualIdentity vid;\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz,\n                              AOP_Delete, inpath);\n  EXEC_TIMING_END(\"IdMap\");\n  NAMESPACEMAP;\n  NAMESPACE_NO_TRAILING_SLASH;\n  BOUNCE_ILLEGAL_NAMES;\n  TOKEN_SCOPE;\n  XrdOucEnv remdir_Env(ininfo);\n  AUTHORIZE(client, &remdir_Env, AOP_Delete, \"remove\", inpath, error);\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  BOUNCE_NOT_ALLOWED;\n  ACCESSMODE_W;\n  MAYSTALL;\n  MAYREDIRECT;\n  return _remdir(path, error, vid, ininfo);\n}\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::_remdir(const char* path,\n                   XrdOucErrInfo& error,\n                   eos::common::VirtualIdentity& vid,\n                   const char* ininfo,\n                   bool simulate)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief delete a directory from the namespace\n *\n * @param inpath directory to delete\n * @param error error object\n * @param vid virtual identity of the client\n * @param ininfo CGI\n * @return SFS_OK on success otherwise SFS_ERROR\n *\n * We support a special ACL to forbid deletion if it would be allowed by the\n * normal POSIX settings (ACL !d flag).\n */\n/*----------------------------------------------------------------------------*/\n\n{\n  static const char* epname = \"remdir\";\n  errno = 0;\n  eos_info(\"path=%s\", path);\n  EXEC_TIMING_BEGIN(\"RmDir\");\n  gOFS->MgmStats.Add(\"RmDir\", vid.uid, vid.gid, 1);\n  std::shared_ptr<eos::IContainerMD> dhpar;\n  std::shared_ptr<eos::IContainerMD> dh;\n  eos::common::Path cPath(path);\n  eos::IContainerMD::XAttrMap attrmap;\n  // Make sure this is not a quota node\n  std::string qpath = path;\n\n  if (Quota::Exists(qpath)) {\n    errno = EBUSY;\n    return Emsg(epname, error, errno, \"rmdir - this is a quota node\",\n                qpath.c_str());\n  }\n\n  eos::common::RWMutexWriteLock viewLock(gOFS->eosViewRWMutex);\n  std::string aclpath;\n  uid_t container_owner_uid = 0;\n  bool container_vtx = false;\n\n  try {\n    dh = gOFS->eosView->getContainer(path);\n    eos::common::Path pPath(gOFS->eosView->getUri(dh.get()).c_str());\n    dhpar = gOFS->eosView->getContainer(pPath.GetParentPath());\n    container_owner_uid = dh->getCUid();\n    container_vtx = dhpar->getMode() & S_ISVTX;\n    aclpath = pPath.GetParentPath();\n  } catch (eos::MDException& e) {\n    dh.reset();\n    dhpar.reset();\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n              e.getErrno(), e.getMessage().str().c_str());\n  }\n\n  eos_info(\"path=\\\"%s\\\" scope=\\\"%s\\\" aclpath=\\\"%s\\\"\", path, vid.scope.c_str(),\n           aclpath.c_str());\n\n  // check existence\n  if (!dh) {\n    errno = ENOENT;\n    return Emsg(epname, error, errno, \"rmdir\", path);\n  }\n\n  // ACL and permission check\n  Acl acl(aclpath.c_str(), error, vid, attrmap);\n\n  if (vid.uid && !acl.IsMutable()) {\n    errno = EPERM;\n    return Emsg(epname, error, EPERM, \"rmdir - immutable\", path);\n  }\n\n  if (!gOFS->allow_public_access(aclpath.c_str(), vid)) {\n    errno = EACCES;\n    return Emsg(epname, error, EACCES, \"access - public access level restriction\",\n                aclpath.c_str());\n  }\n\n  if (ininfo) {\n    XrdOucEnv env_info(ininfo);\n\n    if (env_info.Get(\"mgm.option\")) {\n      XrdOucString option = env_info.Get(\"mgm.option\");\n\n      if (option == \"r\") {\n        // Recursive delete - need to unlock before calling the proc function\n        viewLock.Release();\n        ProcCommand cmd;\n        XrdOucString info = \"mgm.cmd=rm&mgm.option=r&mgm.path=\";\n        info += path;\n        cmd.open(\"/proc/user\", info.c_str(), vid, &error);\n        cmd.close();\n        int rc = cmd.GetRetc();\n\n        if (rc) {\n          return Emsg(epname, error, rc, \"rmdir\", path);\n        }\n\n        return SFS_OK;\n      }\n    }\n  }\n\n  bool stdpermcheck = false;\n  bool aclok = false;\n\n  if (acl.HasAcl() && !container_vtx) {\n    // acls only if this is not a VTX directory\n    if ((dh->getCUid() != vid.uid) &&\n        (vid.uid) && // not the root user\n        (vid.uid != 3) && // not the admin user\n        (vid.gid != 4) && // not the admin group\n        (acl.CanNotDelete())) { // acl does not allow deletion\n      // deletion is explicitly forbidden\n      errno = EPERM;\n      return Emsg(epname, error, EPERM, \"rmdir by ACL\", path);\n    }\n\n    if ((!acl.CanWrite())) {\n      // we have to check the standard permissions\n      stdpermcheck = true;\n    } else {\n      aclok = true;\n    }\n  } else {\n    stdpermcheck = true;\n  }\n\n  // When tokens are used, UNIX permissions are disabled\n  if (vid.token) {\n    stdpermcheck = false;\n  }\n\n    // Check permissions\n  bool permok = stdpermcheck ? (dhpar ? (dhpar->access(vid.uid, vid.gid,\n                                         X_OK | W_OK)) : false) : aclok;\n\n  if (container_vtx) {\n    if (vid.uid) {\n      if (container_owner_uid != vid.uid) {\n        // only the owner can delete\n        errno = EPERM;\n        return Emsg(epname, error, errno, \"rmdir\", path);\n      }\n    }\n  } else {\n    // need for standard perm check\n    if (!permok) {\n      errno = EPERM;\n      return Emsg(epname, error, errno, \"rmdir\", path);\n    }\n  }\n\n  if ((dh->getFlags() && eos::QUOTA_NODE_FLAG) && (vid.uid)) {\n    errno = EADDRINUSE;\n    eos_err(\"%s is a quota node - deletion canceled\", path);\n    return Emsg(epname, error, errno, \"rmdir - this is a quota node\", path);\n  }\n\n  if (!simulate) {\n    try {\n      eos::ContainerIdentifier dhpar_id;\n      eos::ContainerIdentifier dhpar_pid;\n      std::string dh_name;\n\n      // update the in-memory modification time of the parent directory\n      if (dhpar) {\n        dhpar->setMTimeNow();\n        dhpar->notifyMTimeChange(gOFS->eosDirectoryService);\n        eosView->updateContainerStore(dhpar.get());\n        dhpar_id = dhpar->getIdentifier();\n        dhpar_pid = dhpar->getParentIdentifier();\n        dh_name = dh->getName();\n      }\n\n      eosView->removeContainer(path);\n      viewLock.Release();\n\n      if (dhpar) {\n        gOFS->FuseXCastDeletion(dhpar_id, dh_name);\n        gOFS->FuseXCastRefresh(dhpar_id, dhpar_pid);\n      }\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                e.getErrno(), e.getMessage().str().c_str());\n    }\n  }\n\n  viewLock.Release();\n  EXEC_TIMING_END(\"RmDir\");\n\n  if (errno) {\n    if (errno == ENOTEMPTY) {\n      return Emsg(epname, error, errno, \"rmdir - Directory not empty\", path);\n    } else {\n      return Emsg(epname, error, errno, \"rmdir\", path);\n    }\n  } else {\n    // Emit audit record for successful directory deletion (append '/' to denote dir)\n    {\n      std::string apath = path ? path : \"\";\n      if (!apath.empty() && apath.back() != '/') apath.push_back('/');\n      if (mAudit && gOFS->AllowAuditModification(apath)) {\n        mAudit->audit(eos::audit::RMDIR, apath, vid, std::string(logId), std::string(cident), \"mgm\", std::string(), nullptr, nullptr, std::string(), std::string(), std::string(), __FILE__, __LINE__, VERSION);\n      }\n    }\n    return SFS_OK;\n  }\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Rename.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Rename.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Rename file or directory - part of the XRootD API\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::rename(const char* old_name,\n                  const char* new_name,\n                  XrdOucErrInfo& error,\n                  const XrdSecEntity* client,\n                  const char* infoO,\n                  const char* infoN)\n{\n  static const char* epname = \"rename\";\n  const char* tident = error.getErrUser();\n  errno = 0;\n  XrdOucString source, destination;\n  XrdOucEnv renameo_Env(infoO);\n  XrdOucEnv renamen_Env(infoN);\n  XrdOucString oldn = old_name;\n  XrdOucString newn = new_name;\n\n  if (!renameo_Env.Get(\"eos.encodepath\")) {\n    oldn.replace(\"#space#\", \" \");\n  }\n\n  if (!renamen_Env.Get(\"eos.encodepath\")) {\n    newn.replace(\"#space#\", \" \");\n  }\n\n  if ((oldn.find(EOS_COMMON_PATH_VERSION_PREFIX) != STR_NPOS) ||\n      (newn.find(EOS_COMMON_PATH_VERSION_PREFIX) != STR_NPOS)) {\n    errno = EINVAL;\n    return Emsg(epname, error, EINVAL,\n                \"rename version files - use 'file versions' !\");\n  }\n\n  // Use a thread private vid\n  eos::common::VirtualIdentity vid;\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, infoO, tident, vid, gOFS->mTokenAuthz,\n                              AOP_Update, newn.c_str());\n  EXEC_TIMING_END(\"IdMap\");\n  eos_info(\"old-name=%s new-name=%s\", old_name, new_name);\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  const char* inpath = 0;\n  const char* ininfo = 0;\n  {\n    inpath = oldn.c_str();\n    ininfo = infoO;\n    AUTHORIZE(client, &renameo_Env, AOP_Delete, \"rename\", inpath, error);\n    NAMESPACEMAP;\n    BOUNCE_ILLEGAL_NAMES;\n    oldn = path;\n  }\n  {\n    inpath = newn.c_str();\n    ininfo = infoN;\n    AUTHORIZE(client, &renamen_Env, AOP_Update, \"rename\", inpath, error);\n    NAMESPACEMAP;\n    BOUNCE_ILLEGAL_NAMES;\n    newn = path;\n  }\n  BOUNCE_NOT_ALLOWED;\n  ACCESSMODE_W;\n  MAYSTALL;\n  MAYREDIRECT;\n  return rename(oldn.c_str(), newn.c_str(), error, vid, infoO, infoN, true);\n}\n\n//------------------------------------------------------------------------------\n// Rename file or directory - EOS internal API that performs\n// permission checks\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::rename(const char* old_name,\n                  const char* new_name,\n                  XrdOucErrInfo& error,\n                  eos::common::VirtualIdentity& vid,\n                  const char* infoO,\n                  const char* infoN,\n                  bool overwrite)\n{\n  static const char* epname = \"rename\";\n  XrdOucString source, destination;\n  XrdOucEnv renameo_Env(infoO);\n  XrdOucEnv renamen_Env(infoN);\n  XrdOucString oldn = old_name;\n  XrdOucString newn = new_name;\n  const char* inpath = 0;\n  const char* ininfo = 0;\n  errno = 0;\n  {\n    inpath = old_name;\n    ininfo = infoO;\n    NAMESPACEMAP;\n    BOUNCE_ILLEGAL_NAMES;\n    oldn = path;\n  }\n  {\n    inpath = new_name;\n    ininfo = infoN;\n    NAMESPACEMAP;\n    BOUNCE_ILLEGAL_NAMES;\n    newn = path;\n  }\n  BOUNCE_NOT_ALLOWED;\n  ACCESSMODE_W;\n  MAYSTALL;\n  MAYREDIRECT;\n\n  // Check access permissions on source\n  if (_access(oldn.c_str(), W_OK | D_OK, error, vid, infoO) != SFS_OK) {\n    return Emsg(epname, error, errno, \"rename - source access failure\");\n  }\n\n  // Check access permissions on target\n  if (_access(newn.c_str(), W_OK, error, vid, infoN) != SFS_OK) {\n    return Emsg(epname, error, errno, \"rename - destination access failure\");\n  }\n\n  return _rename(oldn.c_str(), newn.c_str(), error, vid, infoO, infoN, true,\n                 false, overwrite);\n}\n\n//------------------------------------------------------------------------------\n// Rename file or directory - EOS internal low-level API\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::_rename(const char* old_name,\n                   const char* new_name,\n                   XrdOucErrInfo& error,\n                   eos::common::VirtualIdentity& vid,\n                   const char* infoO,\n                   const char* infoN,\n                   bool updateCTime,\n                   bool checkQuota,\n                   bool overwrite,\n                   bool fusexcast)\n{\n  static const char* epname = \"_rename\";\n  eos_info(\"source=%s target=%s overwrite=%d\", old_name, new_name, overwrite);\n  errno = 0;\n  EXEC_TIMING_BEGIN(\"Rename\");\n  eos::common::Timing tm(\"_rename\");\n  COMMONTIMING(\"begin\", &tm);\n  eos::common::Path nPath(new_name);\n  eos::common::Path oPath(old_name);\n  std::string oP = oPath.GetParentPath();\n  std::string nP = nPath.GetParentPath();\n\n  if ((!old_name) || (!new_name)) {\n    errno = EINVAL;\n    return Emsg(epname, error, EINVAL, \"rename - 0 source or target name\");\n  }\n\n  // If source and target are the same return success\n  if (!strcmp(old_name, new_name)) {\n    return SFS_OK;\n  }\n\n  gOFS->MgmStats.Add(\"Rename\", vid.uid, vid.gid, 1);\n  std::shared_ptr<eos::IContainerMD> dir;\n  std::shared_ptr<eos::IContainerMD> newdir;\n  std::shared_ptr<eos::IContainerMD> rdir;\n  std::shared_ptr<eos::IFileMD> file;\n  bool renameFile = false;\n  bool renameDir = false;\n  bool renameVersion = false;\n  bool findOk = false;\n  bool quotaMove = false;\n  XrdSfsFileExistence file_exists;\n  std::string new_path = new_name;\n  eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView,\n      nPath.GetParentPath());\n  eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView,\n      oPath.GetParentPath());\n  eos::Prefetcher::prefetchItemAndWait(gOFS->eosView, oPath.GetPath());\n  eos::Prefetcher::prefetchItemAndWait(gOFS->eosView, nPath.GetPath());\n  COMMONTIMING(\"prefetchItems\", &tm);\n\n  if (_exists(old_name, file_exists, error, vid, infoN)) {\n    errno = ENOENT;\n    return Emsg(epname, error, ENOENT, \"rename - source does not exist\");\n  } else {\n    if (file_exists == XrdSfsFileExistNo) {\n      errno = ENOENT;\n      return Emsg(epname, error, ENOENT, \"rename - source does not exist\");\n    }\n\n    if (file_exists == XrdSfsFileExistIsFile) {\n      renameFile = true;\n      XrdSfsFileExistence version_exists;\n      XrdOucString vpath = nPath.GetPath();\n\n      if ((!_exists(oPath.GetVersionDirectory(), version_exists, error, vid,\n                    infoN)) &&\n          (version_exists == XrdSfsFileExistIsDirectory) &&\n          (!vpath.beginswith(oPath.GetVersionDirectory())) &&\n          (!vpath.beginswith(Recycle::gRecyclingPrefix.c_str()))) {\n        renameVersion = true;\n      }\n    }\n\n    if (file_exists == XrdSfsFileExistIsDirectory) {\n      renameDir = true;\n      std::string n_path = nPath.GetPath();\n      std::string o_path = oPath.GetPath();\n\n      if ((n_path.at(n_path.length() - 1) != '/')) {\n        n_path += \"/\";\n      }\n\n      if ((o_path.at(o_path.length() - 1) != '/')) {\n        o_path += \"/\";\n      }\n\n      // Check if old path is a subpath of new path\n      if ((n_path.length() > o_path.length()) &&\n          (!n_path.compare(0, o_path.length(), o_path))) {\n        errno = EINVAL;\n        return Emsg(epname, error, EINVAL, \"rename - old path is subpath of new path\");\n      }\n\n      // Check if old path is a quota node - this is forbidden\n      try {\n        auto rdir = eosView->getContainer(oPath.GetPath());\n        eos::MDLocking::ContainerReadLock rdirLocked(rdir.get());\n\n        if (rdir->getFlags() & eos::QUOTA_NODE_FLAG) {\n          errno = EACCES;\n          return Emsg(epname, error, EACCES, \"rename - source is a quota node\");\n        }\n      } catch (eos::MDException& e) {\n        errno = ENOENT;\n        return Emsg(epname, error, ENOENT, \"rename - source does not exist\");\n      }\n    }\n  }\n\n  if (!_exists(new_name, file_exists, error, vid, infoN)) {\n    if (file_exists == XrdSfsFileExistIsFile) {\n      if (new_path.back() == '/') {\n        errno = ENOTDIR;\n        return Emsg(epname, error, ENOTDIR, \"rename - target is a not directory\");\n      }\n\n      if (overwrite && renameFile) {\n        // Check if we are renaming a version to the primary copy\n        bool keepversion = false;\n        {\n          XrdOucString op = oPath.GetParentPath();\n          XrdOucString vp = nPath.GetVersionDirectory();\n\n          if (op == vp) {\n            keepversion = true;\n          }\n        }\n\n        // Delete the existing target\n        if (gOFS->_rem(new_name, error, vid, infoN, false, keepversion)) {\n          return SFS_ERROR;\n        }\n      } else {\n        errno = EEXIST;\n        return Emsg(epname, error, EEXIST, \"rename - target file name exists\");\n      }\n    }\n\n    if (file_exists == XrdSfsFileExistIsDirectory) {\n      // append the previous last name to the target path\n      if (new_path.back() != '/') {\n        new_path += \"/\";\n      }\n\n      new_path += oPath.GetName();\n      new_name = new_path.c_str();\n      nPath = new_path;\n      nP = nPath.GetParentPath();\n\n      // check if this directory exists already\n      if (!_exists(new_name, file_exists, error, vid, infoN)) {\n        if (file_exists == XrdSfsFileExistIsFile) {\n          errno = EEXIST;\n          return Emsg(epname, error, EEXIST,\n                      \"rename - target directory is an existing file\");\n        }\n\n        if (file_exists == XrdSfsFileExistIsDirectory) {\n          // Delete the existing target, if it empty it will work, otherwise it will fail\n          if (gOFS->_remdir(new_name, error, vid, infoN)) {\n            return SFS_ERROR;\n          }\n        }\n      }\n    }\n  } else {\n    if (!renameDir) {\n      if (new_path.back() == '/') {\n        // append the previous last name to the target path - nevertheless the parent won't exist\n        new_path += oPath.GetName();\n        new_name = new_path.c_str();\n        nPath = new_path;\n        nP = nPath.GetParentPath();\n      }\n    }\n  }\n\n  COMMONTIMING(\"exists\", &tm);\n  // List of source files if a directory is renamed\n  std::map<std::string, std::set<std::string> > found;\n\n  if (renameDir) {\n    {\n      // figure out if this is a move within the same quota node\n      eos::IContainerMD::id_t q1 {0ull};\n      eos::IContainerMD::id_t q2 {0ull};\n      long long avail_files, avail_bytes;\n      Quota::QuotaByPath(oPath.GetParentPath(), 0, 0, avail_files, avail_bytes, q1);\n      Quota::QuotaByPath(nPath.GetParentPath(), 0, 0, avail_files, avail_bytes, q2);\n\n      if (q1 != q2) {\n        quotaMove = true;\n      }\n    }\n\n    if (EOS_LOGS_DEBUG) {\n      eos_debug(\"quotaMove = %d\", quotaMove);\n    }\n\n    // For directory renaming which move into a different directory, we build\n    // the list of files which we are moving if they move between quota nodes\n    if ((oP != nP) && quotaMove) {\n      XrdOucString stdErr;\n\n      if (!gOFS->_find(oPath.GetFullPath().c_str(), error, stdErr, vid, found)) {\n        findOk = true;\n      } else {\n        return Emsg(epname, error, errno,\n                    \"rename - cannot do 'find' inside the source tree\");\n      }\n\n      COMMONTIMING(\"rename::dir_find_files_for_quota_move\", &tm);\n    }\n  }\n\n  {\n    eos::mgm::FusexCastBatch fuse_batch;\n\n    try {\n      dir = eosView->getContainer(oPath.GetParentPath());\n      newdir = eosView->getContainer(nPath.GetParentPath());\n      // Translate to paths without symlinks\n      std::string duri = eosView->getUri(dir.get());\n      std::string newduri = eosView->getUri(newdir.get());\n      // Get symlink-free dir's\n      dir = eosView->getContainer(duri);\n      newdir = eosView->getContainer(newduri);\n      const eos::ContainerIdentifier did = dir->getIdentifier();\n      const eos::ContainerIdentifier pdid = dir->getParentIdentifier();\n      const eos::ContainerIdentifier ndid = newdir->getIdentifier();\n      const eos::ContainerIdentifier pndid = newdir->getParentIdentifier();\n      COMMONTIMING(\"rename::get_old_and_new_containers\", &tm);\n\n      if (renameFile) {\n        if (oP == nP) {\n          file = dir->findFile(oPath.GetName());\n          COMMONTIMING(\"rename::rename_file_within_same_container_find_file\", &tm);\n\n          if (file) {\n            eos::FileIdentifier fid;\n            {\n              eos::MDLocking::BulkMDWriteLock dirFileLocker;\n              dirFileLocker.add(dir.get());\n              dirFileLocker.add(file.get());\n              auto locks = dirFileLocker.lockAll();\n              COMMONTIMING(\"rename::rename_file_within_same_container_dir_file_write_lock\",\n                           &tm);\n              eosView->renameFile(file.get(), nPath.GetName());\n              dir->setMTimeNow();\n              dir->notifyMTimeChange(gOFS->eosDirectoryService);\n              eosView->updateContainerStore(dir.get());\n              COMMONTIMING(\"rename::rename_file_within_same_container_file_rename\", &tm);\n              const std::string old_name = oPath.GetName();\n              fid = file->getIdentifier();\n            }\n\n            if (fusexcast) {\n              fuse_batch.Register([&, did, pdid, fid, old_name]() {\n                gOFS->FuseXCastRefresh(did, pdid);\n                gOFS->FuseXCastDeletion(did, old_name);\n                gOFS->FuseXCastRefresh(fid, did);\n              });\n            }\n          }\n        } else {\n          file = dir->findFile(oPath.GetName());\n          COMMONTIMING(\"rename::move_file_to_different_container_find_file\", &tm);\n\n          if (file) {\n            // Get the quota nodes before locking the directories. Indeed, getting the quota node requires the tree\n            // to be browsed from the directory to all its parent until reaching the quota node (taking read locks on each directory), which\n            // could break the locking order (by directory ID)...\n            eos::IQuotaNode* old_qnode = eosView->getQuotaNode(dir.get());\n            eos::IQuotaNode* new_qnode = eosView->getQuotaNode(newdir.get());\n            // Move to a new directory\n            // TODO: deal with conflicts and proper roll-back in case a file\n            // with the same name already exists in the destination directory\n            eos::MDLocking::BulkMDWriteLock helper;\n            helper.add(dir.get());\n            helper.add(newdir.get());\n            helper.add(file.get());\n            auto locks = helper.lockAll();\n            COMMONTIMING(\"rename::move_file_to_different_container_dirs_file_write_lock\",\n                         &tm);\n            dir->removeFile(oPath.GetName());\n            dir->setMTimeNow();\n            dir->notifyMTimeChange(gOFS->eosDirectoryService);\n            newdir->setMTimeNow();\n            newdir->notifyMTimeChange(gOFS->eosDirectoryService);\n            eosView->updateContainerStore(dir.get());\n            eosView->updateContainerStore(newdir.get());\n\n            if (fusexcast) {\n              const eos::FileIdentifier fid = file->getIdentifier();\n              const std::string old_name = oPath.GetName();\n              fuse_batch.Register([&, did, pdid, ndid, pndid, fid, old_name]() {\n                gOFS->FuseXCastRefresh(did, pdid);\n                gOFS->FuseXCastRefresh(ndid, pndid);\n                gOFS->FuseXCastDeletion(did, old_name);\n                gOFS->FuseXCastRefresh(fid, ndid);\n              });\n            }\n\n            file->setName(nPath.GetName());\n            file->setContainerId(newdir->getId());\n\n            if (updateCTime) {\n              file->setCTimeNow();\n            }\n\n            newdir->addFile(file.get());\n            eosView->updateFileStore(file.get());\n            COMMONTIMING(\"rename::move_file_to_different_container_rename\", &tm);\n            // Adjust the ns quota\n\n            if (old_qnode) {\n              old_qnode->removeFile(file.get());\n            }\n\n            if (new_qnode) {\n              new_qnode->addFile(file.get());\n            }\n\n            COMMONTIMING(\"rename::move_file_to_different_container_adjust_ns_quota\", &tm);\n          }\n        }\n      }\n\n      if (renameDir) {\n        rdir = dir->findContainer(oPath.GetName());\n        COMMONTIMING(\"rename::rename_dir_find_container\", &tm);\n\n        if (rdir) {\n          {\n            eos::MDLocking::BulkMDReadLock containerBulkLocker;\n            containerBulkLocker.add(rdir.get());\n            containerBulkLocker.add(newdir.get());\n            auto containerLocks = containerBulkLocker.lockAll();\n            COMMONTIMING(\"rename::rename_dir_first_is_safe_to_rename_all_dirs_read_lock\",\n                         &tm);\n\n            if (!eos::isSafeToRename(gOFS->eosView, rdir.get(), newdir.get())) {\n              errno = EINVAL;\n              return Emsg(epname, error, EINVAL,\n                          \"rename - old path is subpath of new path\");\n            }\n\n            COMMONTIMING(\"rename::rename_dir_first_is_safe_to_rename\", &tm);\n          }\n          // Remove all the quota from the source node and add to the target node\n          std::map<std::string, std::set<std::string> >::const_reverse_iterator rfoundit;\n          std::set<std::string>::const_iterator fileit;\n\n          // Loop over all the files and subtract them from their quota node\n          if (findOk) {\n            if (checkQuota) {\n              {\n                std::map<uid_t, unsigned long long> user_del_size;\n                std::map<gid_t, unsigned long long> group_del_size;\n\n                // Compute the total quota we need to rename by uid/gid\n                for (rfoundit = found.rbegin(); rfoundit != found.rend();\n                     rfoundit++) {\n                  // To compute the quota, we don't need to read-lock the entire tree as\n                  // it will anyway not be an atomic operation without the big namespace lock taken.\n                  for (fileit = rfoundit->second.begin();\n                       fileit != rfoundit->second.end(); fileit++) {\n                    std::string fspath = rfoundit->first;\n                    fspath += *fileit;\n                    std::shared_ptr<eos::IFileMD> fmd =\n                      std::shared_ptr<eos::IFileMD>((eos::IFileMD*)0);\n\n                    // Stat this file and add to the deletion maps\n                    try {\n                      fmd = gOFS->eosView->getFile(fspath.c_str(), false);\n                    } catch (eos::MDException& e) {\n                      // Check if this is a symbolic link\n                      std::string fname = *fileit;\n                      size_t link_pos = fname.find(\" -> \");\n\n                      if (link_pos != std::string::npos) {\n                        fname.erase(link_pos);\n                        fspath = rfoundit->first;\n                        fspath += fname;\n\n                        try {\n                          fmd = gOFS->eosView->getFile(fspath.c_str(), false);\n                        } catch (eos::MDException& e) {\n                          errno = e.getErrno();\n                          eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\",\n                                    e.getErrno(), e.getMessage().str().c_str());\n                        }\n                      } else {\n                        errno = e.getErrno();\n                        eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\",\n                                  e.getErrno(), e.getMessage().str().c_str());\n                      }\n                    }\n\n                    if (fmd) {\n                      eos::MDLocking::FileReadLock locker(fmd.get());\n\n                      if (!fmd->isLink()) {\n                        // compute quotas to check\n                        user_del_size[fmd->getCUid()] += fmd->getSize();\n                        group_del_size[fmd->getCGid()] += fmd->getSize();\n                      }\n                    } else {\n                      return Emsg(epname, error, errno,\n                                  \"rename - cannot stat file in subtree\",\n                                  fspath.c_str());\n                    }\n                  }\n                }\n\n                COMMONTIMING(\"rename::rename_dir_compute_quotas_to_check\",\n                             &tm);\n                // Verify for each uid/gid that there is enough quota to rename\n                bool userok = true;\n                bool groupok = true;\n\n                // Either all have user quota therefore userok is true\n                for (const auto& [uid, size] : user_del_size) {\n                  if (!Quota::Check(nP, uid, Quota::gProjectId, size, 1)) {\n                    userok = false;\n                    break;\n                  }\n                }\n\n                // or all have group quota therefore groupok is true\n                for (const auto& [gid, size] : group_del_size) {\n                  if (!Quota::Check(nP, Quota::gProjectId, gid, size, 1)) {\n                    groupok = false;\n                    break;\n                  }\n                }\n\n                if ((!userok) || (!groupok)) {\n                  // Deletion will fail as there is not enough quota on the target\n                  return Emsg(epname, error, ENOSPC,\n                              \"rename - cannot get all \"\n                              \"the needed quota for the target directory\");\n                }\n              }\n              COMMONTIMING(\"rename::rename_dir_check_quotas\", &tm);\n            } // if (checkQuota)\n\n            for (rfoundit = found.rbegin(); rfoundit != found.rend();\n                 rfoundit++) {\n              // Loop through every files\n              // To compute the quota, we don't need to read-lock the entire tree as\n              // it will anyway not be an atomic operation without the big namespace lock taken.\n              for (fileit = rfoundit->second.begin();\n                   fileit != rfoundit->second.end(); fileit++) {\n                std::string fspath = rfoundit->first;\n                fspath += *fileit;\n                std::string fname = *fileit;\n\n                if (fname.find(\" -> \") != std::string::npos) {\n                  // Skip symlinks\n                  continue;\n                }\n\n                try {\n                  file = gOFS->eosView->getFile(fspath.c_str());\n                } catch (eos::MDException& e) {\n                  // Check if this is a symbolic link\n                  std::string fname = *fileit;\n                  size_t link_pos = fname.find(\" -> \");\n\n                  if (link_pos != std::string::npos) {\n                    fname.erase(link_pos);\n                    fspath = rfoundit->first;\n                    fspath += fname;\n\n                    try {\n                      file = gOFS->eosView->getFile(fspath.c_str(), false);\n                    } catch (eos::MDException& e) {\n                      errno = e.getErrno();\n                      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\",\n                                e.getErrno(), e.getMessage().str().c_str());\n                    }\n                  } else {\n                    errno = e.getErrno();\n                    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\",\n                              e.getErrno(), e.getMessage().str().c_str());\n                  }\n                }\n\n                if (file) {\n                  eos::MDLocking::FileReadLock locker(file.get());\n\n                  if (!file->isLink()) {\n                    // Get quota nodes from file path and target directory\n                    eos::IQuotaNode* old_qnode = eosView->getQuotaNode(rdir.get());\n                    eos::IQuotaNode* new_qnode = eosView->getQuotaNode(newdir.get());\n\n                    if (old_qnode) {\n                      old_qnode->removeFile(file.get());\n                    }\n\n                    if (new_qnode) {\n                      new_qnode->addFile(file.get());\n                    }\n                  }\n                }\n              }\n            }\n\n            COMMONTIMING(\"rename::rename_dir_apply_quotas\", &tm);\n          }\n\n          if (nP == oP) {\n            // Rename within a container\n            // Lock the containers\n            eos::MDLocking::BulkMDWriteLock bulkContainerLocker;\n            bulkContainerLocker.add(rdir.get());\n            bulkContainerLocker.add(dir.get());\n            auto containerLocks = bulkContainerLocker.lockAll();\n            COMMONTIMING(\"rename::rename_dir_within_same_container_dirs_lock_write\", &tm);\n            eosView->renameContainer(rdir.get(), nPath.GetName());\n\n            if (updateCTime) {\n              rdir->setCTimeNow();\n            }\n\n            const std::string old_name = oPath.GetName();\n            dir->setMTimeNow();\n            dir->notifyMTimeChange(gOFS->eosDirectoryService);\n            eosView->updateContainerStore(rdir.get());\n            eosView->updateContainerStore(dir.get());\n            const eos::ContainerIdentifier rdid = rdir->getIdentifier();\n            fuse_batch.Register([&, rdid, did, pdid, old_name]() {\n              gOFS->FuseXCastRefresh(rdid, did);\n              gOFS->FuseXCastRefresh(did, pdid);\n              gOFS->FuseXCastDeletion(did, old_name);\n            });\n            COMMONTIMING(\"rename::rename_dir_within_same_container\", &tm);\n          } else {\n            {\n              eos::MDLocking::BulkMDReadLock bulkDirLocker;\n              bulkDirLocker.add(rdir.get());\n              bulkDirLocker.add(newdir.get());\n              auto dirLocks = bulkDirLocker.lockAll();\n              COMMONTIMING(\"rename::rename_dir_second_is_safe_to_rename_all_dirs_read_lock\",\n                           &tm);\n\n              // Do the check once again, because we're paranoid\n              if (!eos::isSafeToRename(gOFS->eosView, rdir.get(),\n                                       newdir.get())) {\n                eos_static_crit(\n                  \"%s\", SSTR(\"Unsafe rename of container \"\n                             << rdir->getId() << \" -> \" << newdir->getId()\n                             << \" was prevented at the last resort check\")\n                  .c_str());\n                errno = EINVAL;\n                return Emsg(\n                         epname, error, EINVAL,\n                         \"rename - old path is subpath \"\n                         \"of new path - caught by last resort check, quotanodes \"\n                         \"may have become inconsistent\");\n              }\n\n              COMMONTIMING(\"rename::rename_dir_second_is_safe_to_rename\", &tm);\n            }\n            // Remove from one container to another one\n            eos::MDLocking::BulkMDWriteLock bulkContainerLocker;\n            bulkContainerLocker.add(dir.get());\n            bulkContainerLocker.add(rdir.get());\n            bulkContainerLocker.add(newdir.get());\n            auto containerLocks = bulkContainerLocker.lockAll();\n            COMMONTIMING(\"rename::move_dir_all_dirs_write_lock\", &tm);\n            int64_t tree_size = static_cast<int64_t>(rdir->getTreeSize());\n            int64_t tree_files = static_cast<int64_t>(rdir->getTreeFiles());\n            int64_t tree_cont = static_cast<int64_t>(rdir->getTreeContainers());\n            {\n              // update the source directory - remove the directory\n              dir->removeContainer(oPath.GetName());\n              dir->setMTimeNow();\n              dir->notifyMTimeChange(gOFS->eosDirectoryService);\n\n              if (gOFS->eosContainerAccounting) {\n                gOFS->eosContainerAccounting->RemoveTree(dir.get(), {tree_size, tree_files, tree_cont});\n              }\n\n              eosView->updateContainerStore(dir.get());\n              COMMONTIMING(\"rename::move_dir_remove_source_tree\", &tm);\n              const std::string dir_name = oPath.GetName();\n              fuse_batch.Register([&, did, pdid, dir_name]() {\n                gOFS->FuseXCastDeletion(did, dir_name);\n                gOFS->FuseXCastRefresh(did, pdid);\n              });\n            }\n            {\n              // rename the moved directory and udpate it's parent ID\n              rdir->setName(nPath.GetName());\n              rdir->setParentId(newdir->getId());\n\n              if (updateCTime) {\n                rdir->setCTimeNow();\n              }\n\n              eosView->updateContainerStore(rdir.get());\n              const eos::ContainerIdentifier rdid = rdir->getIdentifier();\n              const eos::ContainerIdentifier prdid = rdir->getParentIdentifier();\n              fuse_batch.Register([&, rdid, prdid]() {\n                gOFS->FuseXCastRefresh(rdid, prdid);\n              });\n              COMMONTIMING(\"rename::move_dir_rename_moved_dir\", &tm);\n            }\n            {\n              // update the target directory - add the directory\n              newdir->addContainer(rdir.get());\n              newdir->setMTimeNow();\n\n              if (gOFS->eosContainerAccounting) {\n                gOFS->eosContainerAccounting->AddTree(newdir.get(), {tree_size, tree_files, tree_cont});\n              }\n\n              const eos::ContainerIdentifier rdid = rdir->getIdentifier();\n              newdir->notifyMTimeChange(gOFS->eosDirectoryService);\n              eosView->updateContainerStore(newdir.get());\n              fuse_batch.Register([&, ndid, pndid, rdid]() {\n                gOFS->FuseXCastRefresh(ndid, pndid);\n                gOFS->FuseXCastRefresh(rdid, ndid);\n              });\n              COMMONTIMING(\"rename::move_dir_update_target_directory_add_old_dir\", &tm);\n            }\n          }\n        }\n\n        file.reset();\n      }\n    } catch (eos::MDException& e) {\n      dir.reset();\n      file.reset();\n      errno = e.getErrno();\n      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                e.getErrno(), e.getMessage().str().c_str());\n    }\n  }\n\n  std::ostringstream oss;\n  oss << \"renamed \" << oPath.GetFullPath() << \" to \" << nPath.GetFullPath() <<\n      \" timing=\" << tm.Dump();\n  eos_static_debug(oss.str().c_str());\n\n  if ((!dir) || ((!file) && (!rdir))) {\n    errno = ENOENT;\n    return Emsg(epname, error, ENOENT, \"rename\", old_name);\n  }\n\n  // check if this was a versioned file\n  if (renameVersion) {\n    // rename also the version directory\n    if (_rename(oPath.GetVersionDirectory(), nPath.GetVersionDirectory(),\n                error, vid, infoO, infoN, false, false, false)) {\n      return SFS_ERROR;\n    }\n  }\n\n  COMMONTIMING(\"end\", &tm);\n  EXEC_TIMING_END(\"Rename\");\n  // Audit RENAME after successful operation\n  if (mAudit) {\n    std::string newPath = nPath.GetFullPath().c_str();\n    std::string oldPath = oPath.GetFullPath().c_str();\n    if (renameDir) {\n      if (!newPath.empty() && newPath.back() != '/') newPath.push_back('/');\n      if (!oldPath.empty() && oldPath.back() != '/') oldPath.push_back('/');\n    }\n    if (mAudit && gOFS->AllowAuditModification(newPath)) mAudit->audit(eos::audit::RENAME, newPath, vid, std::string(logId), std::string(cident), \"mgm\", oldPath, nullptr, nullptr, std::string(), std::string(), std::string(), __FILE__, __LINE__, VERSION);\n  }\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Rename a file by atomically creating a symlink with the same name pointing\n// to the new destination.\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::_rename_with_symlink(const char* old_name, const char* new_name,\n                                XrdOucErrInfo& error,\n                                eos::common::VirtualIdentity& vid,\n                                const char* infoO, const char* infoN,\n                                bool updateCTime, bool checkQuota,\n                                bool fusexcast)\n{\n  static const char* epname = \"_rename_with_symlink\";\n\n  if (!old_name || !new_name) {\n    errno = EINVAL;\n    return Emsg(epname, error, EINVAL,\n                \"rename with symlink - empty source or target\");\n  }\n\n  eos_info(\"msg=\\\"rename with symlink\\\" source_file=\\\"%s\\\" \"\n           \"target_dir=\\\"%s\\\"\", old_name, new_name);\n\n  // If source and target are the same return success\n  if (!strcmp(old_name, new_name)) {\n    return SFS_OK;\n  }\n\n  XrdOucString oldn = old_name;\n  XrdOucString newn = new_name;\n  const char* inpath = 0;\n  const char* ininfo = 0;\n  errno = 0;\n  {\n    inpath = old_name;\n    ininfo = infoO;\n    NAMESPACEMAP;\n    BOUNCE_ILLEGAL_NAMES;\n    oldn = path;\n  }\n  {\n    inpath = new_name;\n    ininfo = infoN;\n    NAMESPACEMAP;\n    BOUNCE_ILLEGAL_NAMES;\n    newn = path;\n  }\n  BOUNCE_NOT_ALLOWED;\n  ACCESSMODE_W;\n  MAYSTALL;\n  MAYREDIRECT;\n\n  // Check access permissions on source\n  if (_access(oldn.c_str(), W_OK | D_OK, error, vid, infoO) != SFS_OK) {\n    return Emsg(epname, error, errno, \"rename with symlink - \"\n                \"source access failure\");\n  }\n\n  // Check access permissions on target\n  if (_access(newn.c_str(), W_OK, error, vid, infoN) != SFS_OK) {\n    return Emsg(epname, error, errno, \"rename with symlink - \"\n                \"destination access failure\");\n  }\n\n  errno = 0;\n  EXEC_TIMING_BEGIN(\"Rename\");\n  eos::common::Timing tm(\"_rename_with_symlink\");\n  COMMONTIMING(\"begin\", &tm);\n  eos::common::Path nPath(newn.c_str());\n  eos::common::Path oPath(oldn.c_str());\n  std::string oP = oPath.GetParentPath();\n  std::string nP = nPath.GetParentPath();\n  gOFS->MgmStats.Add(\"Rename\", vid.uid, vid.gid, 1);\n  XrdSfsFileExistence file_exists;\n  eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, oP);\n  eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, nP);\n  eos::Prefetcher::prefetchItemAndWait(gOFS->eosView, oPath.GetPath());\n  eos::Prefetcher::prefetchItemAndWait(gOFS->eosView, nPath.GetPath());\n  COMMONTIMING(\"prefetchItems\", &tm);\n\n  // Check that source is an existing file\n  if (_exists(old_name, file_exists, error, vid, infoN)) {\n    errno = ENOENT;\n    return Emsg(epname, error, errno,\n                \"rename with symlink - source does not exist\");\n  }\n\n  if (file_exists == XrdSfsFileExistNo) {\n    errno = ENOENT;\n    return Emsg(epname, error, errno,\n                \"rename with symlink - source does not exist\");\n  }\n\n  if (file_exists == XrdSfsFileExistIsDirectory) {\n    errno = EINVAL;\n    return Emsg(epname, error, errno,\n                \"rename with symlink - source is a directory\");\n  }\n\n  if (file_exists == XrdSfsFileExistIsFile) {\n    XrdSfsFileExistence version_exists;\n    XrdOucString vpath = nPath.GetPath();\n\n    if ((!_exists(oPath.GetVersionDirectory(), version_exists, error, vid,\n                  infoN)) &&\n        (version_exists == XrdSfsFileExistIsDirectory) &&\n        (!vpath.beginswith(oPath.GetVersionDirectory())) &&\n        (!vpath.beginswith(Recycle::gRecyclingPrefix.c_str()))) {\n      errno = EINVAL;\n      return Emsg(epname, error, errno, \"rename with symlink - source has versions\");\n    }\n  }\n\n  // Check that destination is a directory and does not contain already\n  // the source file name.\n  if (!_exists(newn.c_str(), file_exists, error, vid, infoN)) {\n    if (file_exists == XrdSfsFileExistIsFile) {\n      errno = ENOTDIR;\n      return Emsg(epname, error, errno,\n                  \"rename with symlink - target is a not directory\");\n    }\n\n    if (file_exists == XrdSfsFileExistNo) {\n      errno = ENOENT;\n      return Emsg(epname, error, errno,\n                  \"rename with symlink - missing target directory\");\n    }\n\n    if (file_exists == XrdSfsFileExistIsDirectory) {\n      // Construct the full destination path and check that\n      // it doesn't exist already.\n      std::string new_path = newn.c_str();\n\n      if (new_path.back() != '/') {\n        new_path += \"/\";\n      }\n\n      if (oP == new_path) {\n        errno = EEXIST;\n        return Emsg(epname, error, errno, \"rename with symlink - \"\n                    \"destination directory must be different from source\");\n      }\n\n      new_path += oPath.GetName();\n      nPath = new_path;\n      nP = nPath.GetParentPath();\n\n      if (!_exists(new_path.c_str(), file_exists, error, vid, infoN)) {\n        if (file_exists != XrdSfsFileExistNo) {\n          errno = EEXIST;\n          return Emsg(epname, error, errno,\n                      \"rename with symlink - name exists in target directory\");\n        }\n      }\n    }\n  } else {\n    errno = ENOENT;\n    return Emsg(epname, error, errno,\n                \"rename with symlink - missing target directory\");\n  }\n\n  COMMONTIMING(\"exists\", &tm);\n\n  try {\n    eos::mgm::FusexCastBatch fuse_batch;\n    std::shared_ptr<eos::IContainerMD> dir = eosView->getContainer(\n          oPath.GetParentPath());\n    std::shared_ptr<eos::IContainerMD> newdir = eosView->getContainer(\n          nPath.GetParentPath());\n    // Translate to paths without symlinks\n    std::string duri = eosView->getUri(dir.get());\n    std::string newduri = eosView->getUri(newdir.get());\n    std::string old_file_uri = duri + oPath.GetName();\n    std::string new_file_uri = newduri + oPath.GetName();\n    // Get symlink-free dir's\n    eos_info(\"msg=\\\"get uri\\\" old=\\\"%s\\\" new=\\\"%s\\\"\", duri.c_str(),\n             newduri.c_str());\n    dir = eosView->getContainer(duri);\n    newdir = eosView->getContainer(newduri);\n    const eos::ContainerIdentifier did = dir->getIdentifier();\n    const eos::ContainerIdentifier pdid = dir->getParentIdentifier();\n    const eos::ContainerIdentifier ndid = newdir->getIdentifier();\n    const eos::ContainerIdentifier pndid = newdir->getParentIdentifier();\n    std::shared_ptr<eos::IFileMD> file = dir->findFile(oPath.GetName());\n\n    if (!file) {\n      errno = ENOENT;\n      return Emsg(epname, error, errno,\n                  \"rename with symlink - source file does not exist\");\n    }\n\n    // Get the quota nodes before locking the directories. Getting the quota\n    // node requires the tree to be browsed from the directory to all its\n    // parents until reaching the quota node (taking read locks on each\n    // directory), which could break the locking order (by directory ID)...\n    eos::IQuotaNode* old_qnode = eosView->getQuotaNode(dir.get());\n    eos::IQuotaNode* new_qnode = eosView->getQuotaNode(newdir.get());\n    // Move to a new directory and create symlink in the old directory\n    // pointing to the new destination\n    eos::MDLocking::BulkMDWriteLock helper;\n    helper.add(dir.get());\n    helper.add(newdir.get());\n    helper.add(file.get());\n    auto locks = helper.lockAll();\n    dir->removeFile(oPath.GetName());\n    // Create the symlink\n    eos_info(\"msg=\\\"create symlink\\\" old=\\\"%s\\\" new=\\\"%s\\\"\",\n             old_file_uri.c_str(), new_file_uri.c_str());\n    eosView->createLink(old_file_uri, new_file_uri, vid.uid, vid.gid);\n    dir->setMTimeNow();\n    dir->notifyMTimeChange(gOFS->eosDirectoryService);\n    newdir->setMTimeNow();\n    newdir->notifyMTimeChange(gOFS->eosDirectoryService);\n    newdir->addFile(file.get());\n    eosView->updateContainerStore(dir.get());\n    eosView->updateContainerStore(newdir.get());\n    file->setName(nPath.GetName());\n    file->setContainerId(newdir->getId());\n\n    if (updateCTime) {\n      file->setCTimeNow();\n    }\n\n    eosView->updateFileStore(file.get());\n    COMMONTIMING(\"rename::move_file_to_different_container_rename\", &tm);\n\n    // Adjust the ns quota\n    if (old_qnode) {\n      old_qnode->removeFile(file.get());\n    }\n\n    if (new_qnode) {\n      new_qnode->addFile(file.get());\n    }\n\n    if (fusexcast) {\n      const eos::FileIdentifier fid = file->getIdentifier();\n      const std::string src_file_name = oPath.GetName();\n      fuse_batch.Register([&, did, pdid, ndid, pndid, fid, src_file_name]() {\n        gOFS->FuseXCastRefresh(did, pdid);\n        gOFS->FuseXCastRefresh(ndid, pndid);\n        gOFS->FuseXCastDeletion(did, src_file_name);\n        gOFS->FuseXCastRefresh(fid, ndid);\n      });\n    }\n\n    COMMONTIMING(\"rename::move_file_to_different_container_adjust_ns_quota\", &tm);\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n              e.getErrno(), e.getMessage().str().c_str());\n    errno = ENOENT;\n    return Emsg(epname, error, errno, \"rename with symlink\", old_name);\n  }\n\n  std::ostringstream oss;\n  oss << \"msg=\\\"rename_with_symlink\\\"\"\n      << \" source= \" << oPath.GetFullPath()\n      << \" destination=\" << nPath.GetFullPath()\n      << \" timing=\" << tm.Dump();\n  eos_static_debug(oss.str().c_str());\n  COMMONTIMING(\"end\", &tm);\n  EXEC_TIMING_END(\"Rename\");\n  // Audit RENAME after successful move with symlink creation\n  if (mAudit) {\n    std::string newPath = nPath.GetFullPath().c_str();\n    std::string oldPath = oPath.GetFullPath().c_str();\n    if (mAudit && gOFS->AllowAuditModification(newPath)) mAudit->audit(eos::audit::RENAME, newPath, vid, std::string(logId), std::string(cident), \"mgm\", oldPath, nullptr, nullptr, std::string(), std::string(), std::string(), __FILE__, __LINE__, VERSION);\n  }\n  return SFS_OK;\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Rm.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Rem.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n#include \"proto/Audit.pb.h\"\n#include <time.h>\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/recycle/RecycleEntry.hh\"\n#include \"mgm/misc/AuditHelpers.hh\"\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::rem(const char* inpath,\n               XrdOucErrInfo& error,\n               const XrdSecEntity* client,\n               const char* ininfo)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief delete a file from the namespace\n *\n * @param inpath file to delete\n * @param error error object\n * @param client XRootD authenticiation object\n * @param ininfo CGI\n * @return SFS_OK if success otherwise SFS_ERROR\n *\n * Deletion supports a recycle bin. See internal implementation of _rem for details.\n */\n/*----------------------------------------------------------------------------*/\n\n{\n  static const char* epname = \"rem\";\n  const char* tident = error.getErrUser();\n  // use a thread private vid\n  eos::common::VirtualIdentity vid;\n  NAMESPACEMAP;\n  BOUNCE_ILLEGAL_NAMES;\n  TOKEN_SCOPE;\n  XrdOucEnv env(ininfo);\n  AUTHORIZE(client, &env, AOP_Delete, \"remove\", inpath, error);\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz,\n                              AOP_Delete, path);\n  EXEC_TIMING_END(\"IdMap\");\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  BOUNCE_NOT_ALLOWED;\n  ACCESSMODE_W;\n  MAYSTALL;\n  MAYREDIRECT;\n  return _rem(path, error, vid, ininfo);\n}\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::_rem(const char* path,\n                XrdOucErrInfo& error,\n                eos::common::VirtualIdentity& vid,\n                const char* ininfo,\n                bool simulate,\n                bool keepversion,\n                bool no_recycling,\n                bool no_quota_enforcement,\n                bool fusexcast,\n                bool no_workflow)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief delete a file from the namespace\n *\n * @param inpath file to delete\n * @param error error object\n * @param vid virtual identity of the client\n * @param ininfo CGI\n * @param simulate indicates 'simulate deletion' e.g. it can be used as a test if a deletion would succeed\n * @param keepversion indicates if the deletion should wipe the version directory\n * @param no_recycling suppresses the recycle bin\n * @param no_quota_enforcment disables quota check on the recycle bin\n * @param fusexcast broadcast deletions if true\n * @param no_workflow skip workflow if true\n * @return SFS_OK if success otherwise SFS_ERROR\n *\n * Deletion supports the recycle bin if configured on the parent directory of\n * the file to be deleted. The simulation mode is used to test if there is\n * enough space in the recycle bin to move the object. If the simulation succeeds\n * the real deletion is executed. 'keepversion' is needed when we want to recover\n * an old version into the current version\n */\n/*----------------------------------------------------------------------------*/\n{\n  static const char* epname = \"rem\";\n  EXEC_TIMING_BEGIN(\"Rm\");\n  eos_info(\"path=%s vid.uid=%u vid.gid=%u\", path, vid.uid, vid.gid);\n\n  if (!simulate) {\n    gOFS->MgmStats.Add(\"Rm\", vid.uid, vid.gid, 1);\n  }\n\n  std::string errMsg = \"remove\";\n  // Perform the actual deletion\n  errno = 0;\n  XrdSfsFileExistence file_exists;\n  vid.scope = path;\n\n  if ((_exists(path, file_exists, error, vid, 0))) {\n    return SFS_ERROR;\n  }\n\n  if (file_exists != XrdSfsFileExistIsFile) {\n    if (file_exists == XrdSfsFileExistIsDirectory) {\n      errno = EISDIR;\n    } else {\n      errno = ENOENT;\n    }\n\n    return Emsg(epname, error, errno, \"remove\", path);\n  }\n\n  // ---------------------------------------------------------------------------\n  eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, path);\n  eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n  // free the booked quota\n  std::shared_ptr<eos::IFileMD> fmd;\n  std::shared_ptr<eos::IContainerMD> container;\n  eos::IContainerMD::XAttrMap attrmap;\n  uid_t owner_uid = 0;\n  gid_t owner_gid = 0;\n  eos::common::FileId::fileid_t fid = 0;\n  bool doRecycle = false; // indicating two-step deletion via recycle-bin\n  std::string aclpath;\n\n  try {\n    fmd = gOFS->eosView->getFile(path, false);\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n              e.getMessage().str().c_str());\n  }\n\n  uid_t container_owner_uid = 0;\n  bool container_vtx = false;\n\n  if (fmd) {\n    owner_uid = fmd->getCUid();\n    owner_gid = fmd->getCGid();\n    fid = fmd->getId();\n\n    try {\n      container = gOFS->eosDirectoryService->getContainerMD(fmd->getContainerId());\n      container_owner_uid = container->getCUid();\n      container_vtx = container->getMode() & S_ISVTX;\n      aclpath = gOFS->eosView->getUri(container.get());\n    } catch (eos::MDException& e) {\n      container.reset();\n    }\n\n    // ACL and permission check\n    Acl acl(aclpath.c_str(), error, vid, attrmap);\n    eos_info(\"acl=%s mutable=%d\", attrmap[\"sys.acl\"].c_str(), acl.IsMutable());\n\n    if (vid.uid && !acl.IsMutable()) {\n      errno = EPERM;\n      return Emsg(epname, error, errno, \"remove file - immutable\", path);\n    }\n\n    // check publicaccess level\n    if (!gOFS->allow_public_access(aclpath.c_str(), vid)) {\n      errno = EACCES;\n      return Emsg(epname, error, EACCES, \"access - public access level restriction\",\n                  aclpath.c_str());\n    }\n\n    bool stdpermcheck = false;\n\n    if (acl.HasAcl() && (!container_vtx)) {\n      eos_info(\"acl=%d r=%d w=%d wo=%d egroup=%d delete=%d not-delete=%d mutable=%d\",\n               acl.HasAcl(), acl.CanRead(), acl.CanWrite(), acl.CanWriteOnce(),\n               acl.HasEgroup(), acl.CanDelete(), acl.CanNotDelete(), acl.IsMutable());\n\n      if ((!acl.CanWrite()) && (!acl.CanWriteOnce())) {\n        // we have to check the standard permissions\n        stdpermcheck = true;\n      }\n    } else {\n      stdpermcheck = true;\n    }\n\n    // when tokens are used UNIX permissions are disabled\n    if (vid.token) {\n      stdpermcheck = false;\n    }\n\n    if (container_vtx) {\n      if (\n        (container_owner_uid == vid.uid) ||\n        (owner_uid == vid.uid)) {\n        // great VTX allows the owner to delete, this is not overruled by a !delete acl\n      } else {\n        if (vid.uid) {\n          // forbidden because of VTX bit\n          errno = EPERM;\n          std::ostringstream oss;\n          oss << path << \" by tident=\" << vid.tident;\n          return Emsg(epname, error, errno, \"remove file\", oss.str().c_str());\n        }\n      }\n    } else {\n      // try other permissions\n      if (container) {\n        if (stdpermcheck && (!container->access(vid.uid, vid.gid, W_OK | X_OK))) {\n          errno = EPERM;\n          std::ostringstream oss;\n          oss << path << \" by tident=\" << vid.tident;\n          return Emsg(epname, error, errno, \"remove file\", oss.str().c_str());\n        }\n\n        // check if this directory is write-once for the mapped user\n        if (acl.CanWriteOnce() && (fmd->getSize())) {\n          errno = EPERM;\n          // this is a write once user\n          return Emsg(epname, error, EPERM,\n                      \"remove existing file - you are write-once user\");\n        }\n\n        // if there is a !d policy we cannot delete files which we don't own\n        if (((vid.uid) && (vid.uid != 3) && (vid.gid != 4) && (acl.CanNotDelete())) &&\n            (container_owner_uid != vid.uid) &&\n            ((fmd->getCUid() != vid.uid))) {\n          errno = EPERM;\n          // deletion is forbidden for not-owner unless we own the parent!\n          return Emsg(epname, error, EPERM,\n                      \"remove existing file - ACL forbids file deletion\");\n        }\n\n        if ((!stdpermcheck) && (!acl.CanWrite())) {\n          errno = EPERM;\n          // this user is not allowed to write\n          return Emsg(epname, error, EPERM,\n                      \"remove existing file - you don't have write permissions\");\n        }\n      }\n\n      // -----------------------------------------------------------------------\n      // check if there is a recycling bin specified and avoid recycling of the\n      // already recycled files/dirs\n      // -----------------------------------------------------------------------\n      XrdOucString sPath = path;\n\n      if (gOFS->mRecycler->IsEnabled() && (no_recycling == false) &&\n          (gOFS->mRecycler->IsEnforced() ||\n           attrmap.count(Recycle::gRecyclingAttribute)) &&\n          (!sPath.beginswith(Recycle::gRecyclingPrefix.c_str()))) {\n        // ---------------------------------------------------------------------\n        // this is two-step deletion via a recyle bin\n        // ---------------------------------------------------------------------\n        if (gOFS->mRecycler->IsEnforced()) {\n          // add the recycle attribute to enable recycling funcionality\n          attrmap[Recycle::gRecyclingAttribute] = Recycle::gRecyclingPrefix;\n        }\n\n        doRecycle = true;\n      } else {\n        // ---------------------------------------------------------------------\n        // this is one-step deletion just removing files 'forever' and now\n        // ---------------------------------------------------------------------\n        if (!simulate) {\n          try {\n            eos::IQuotaNode* ns_quota = gOFS->eosView->getQuotaNode(container.get());\n            eos_info(\"got quota node=%lld\", (unsigned long long) ns_quota);\n\n            if (ns_quota) {\n              ns_quota->removeFile(fmd.get());\n            }\n          } catch (eos::MDException& e) { }\n        }\n      }\n    }\n  } else {      /* file does not exist */\n    errno = ENOENT;\n    return Emsg(epname, error, errno, \"remove\", path);\n  }\n\n  if (!doRecycle) {\n    try {\n      if (!simulate) {\n        eos_info(\"unlinking from view %s\", path);\n\n        if (!no_workflow) {\n          Workflow workflow;\n          // eventually trigger a workflow\n          workflow.Init(&attrmap, path, fid);\n          errno = 0;\n          lock.Release();\n          auto ret_wfe = workflow.Trigger(\"sync::delete\", \"default\", vid, ininfo, errMsg);\n\n          if (ret_wfe < 0 && errno == ENOKEY) {\n            eos_info(\"msg=\\\"no workflow defined for delete\\\"\");\n          } else {\n            eos_info(\"msg=\\\"workflow trigger returned\\\" retc=%d errno=%d\", ret_wfe, errno);\n          }\n\n          if (ret_wfe && errno != ENOKEY) {\n            eos::MDException e(errno);\n            e.getMessage() << \"Deletion workflow failed\";\n            throw e;\n          }\n\n          lock.Grab(gOFS->eosViewRWMutex);\n        }\n\n        /* create a Copy-on-Write clone if needed */\n        XrdMgmOfsFile::create_cow(XrdMgmOfsFile::cowDelete, container, fmd, vid, error);\n\n        if (!XrdMgmOfsFile::handleHardlinkDelete(container, fmd, vid)) {\n          gOFS->eosView->unlinkFile(path);\n          // Reload file object that was modifed in the unlinkFile method\n          // TODO: this can be dropped if you use the unlinkFile which takes\n          // as argument the IFileMD object\n          fmd = gOFS->eosFileService->getFileMD(fmd->getId());\n\n          // Drop the TAPE_FS_ID which otherwise would prevent the\n          // file metadata cleanup\n          if (fmd->hasUnlinkedLocation(eos::common::TAPE_FS_ID)) {\n            fmd->removeLocation(eos::common::TAPE_FS_ID);\n          }\n\n          if ((!fmd->getNumUnlinkedLocation()) && (!fmd->getNumLocation())) {\n            gOFS->eosView->removeFile(fmd.get());\n          }\n\n          gOFS->WriteRmRecord(fmd, path);\n\n          if (container) {\n            container->setMTimeNow();\n            container->notifyMTimeChange(gOFS->eosDirectoryService);\n            eosView->updateContainerStore(container.get());\n            std::string deletion_name = fmd->getName();\n            eos::ContainerIdentifier c_ident = container->getIdentifier();\n            eos::ContainerIdentifier p_ident = container->getParentIdentifier();\n            lock.Release();\n            gOFS->FuseXCastDeletion(c_ident, deletion_name);\n            gOFS->FuseXCastRefresh(c_ident, p_ident);\n          }\n        }\n      }\n\n      errno = 0;\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\",\n                e.getErrno(), e.getMessage().str().c_str());\n    }\n  }\n\n  if (doRecycle && (!simulate)) {\n    // Two-step deletion recycle logic\n    XrdOucString recyclePath;\n    lock.Release();\n    // -------------------------------------------------------------------------\n    std::string recycle_dir, recycle_id;\n    auto it = attrmap.find(Recycle::gRecyclingAttribute);\n\n    if (it != attrmap.end()) {\n      recycle_dir = it->second;\n    }\n\n    it = attrmap.find(Recycle::gRecycleIdXattrKey);\n\n    if (it != attrmap.end()) {\n      recycle_id = it->second;\n    }\n\n    eos_static_info(\"recycle_dir=\\\"%s\\\" recycle_id=\\\"%s\\\"\", recycle_dir.c_str(),\n                    recycle_id.c_str());\n    eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n\n    if (Quota::ExistsResponsible(recycle_dir)) {\n      if (!no_quota_enforcement &&\n          !Quota::Check(recycle_dir, fmd->getCUid(), fmd->getCGid(),\n                        fmd->getSize(), fmd->getNumLocation())) {\n        // This is the very critical case where we have to reject the delete\n        // since the recycle space is full\n        errno = ENOSPC;\n        return Emsg(epname, error, ENOSPC, \"remove existing file - the recycle \"\n                    \"space is full\");\n      } else {\n        // Move the file to the recycle bin\n        int rc = 0;\n        RecycleEntry lRecycle(path, recycle_dir, recycle_id, &vid,\n                              fmd->getCUid(), fmd->getCGid(), fmd->getId());\n        Workflow workflow;\n        // eventually trigger a workflow\n        workflow.Init(&attrmap, path, fmd->getId());\n        errno = 0;\n        auto ret_wfe = workflow.Trigger(\"sync::recycle\", \"default\", vid, ininfo,\n                                        errMsg);\n\n        if (ret_wfe < 0 && errno == ENOKEY) {\n          eos_info(\"msg=\\\"no workflow defined for recycle\\\"\");\n        } else {\n          eos_info(\"msg=\\\"workflow trigger returned\\\" retc=%d errno=%d\", ret_wfe, errno);\n        }\n\n        if ((rc = lRecycle.ToGarbage(epname, error))) {\n          return rc;\n        } else {\n          if (container) {\n            if (XrdMgmOfsFile::create_cow(XrdMgmOfsFile::cowUnlink, container, fmd, vid,\n                                          error) > -1) {\n              eos_info(\"create_cow for recycled %s (fxid:%lx)\", fmd->getName().c_str(),\n                       fmd->getId());\n            }\n          }\n\n          recyclePath = error.getErrText();\n          gOFS->WriteRecycleRecord(fmd);\n        }\n      }\n    } else {\n      // There is no quota defined on that recycle path\n      errno = ENODEV;\n      return Emsg(epname, error, ENODEV, \"remove existing file - the recycle \"\n                  \"space has no quota configuration\");\n    }\n\n    // track who is deleting\n    if (gOFS->_attr_set(recyclePath.c_str(), error, rootvid, \"\",\n                        eos::common::EOS_DTRACE_ATTR, vid.getTrace(true).c_str())) {\n      eos_err(\"msg=\\\"failed to set attribute on recycle path\\\" path=%s\",\n              recyclePath.c_str());\n    }\n\n    if (!keepversion) {\n      // call the version purge function in case there is a version (without gQuota locked)\n      eos::common::Path cPath(path);\n      XrdOucString vdir;\n      vdir += cPath.GetVersionDirectory();\n\n      // tag the version directory key on the garbage file\n      if (recyclePath.length()) {\n        struct stat buf;\n\n        if (!gOFS->_stat(vdir.c_str(), &buf, error, rootvid, 0, 0)) {\n          char sp[256];\n          snprintf(sp, sizeof(sp) - 1, \"%016llx\", (unsigned long long) buf.st_ino);\n\n          if (gOFS->_attr_set(recyclePath.c_str(), error, rootvid, \"\",\n                              Recycle::gRecyclingVersionKey.c_str(), sp)) {\n            eos_err(\"msg=\\\"failed to set attribute on recycle path\\\" path=%s\",\n                    recyclePath.c_str());\n          }\n        }\n      }\n\n      gOFS->PurgeVersion(vdir.c_str(), error, 0);\n      error.clear();\n      errno = 0; // purge might return ENOENT if there was no version\n    }\n  } else {\n    lock.Release();\n\n    if ((!errno) && (!keepversion)) {\n      // call the version purge function in case there is a version (without gQuota locked)\n      eos::common::Path cPath(path);\n      XrdOucString vdir;\n      vdir += cPath.GetVersionDirectory();\n      gOFS->PurgeVersion(vdir.c_str(), error, 0);\n      error.clear();\n      errno = 0; // purge might return ENOENT if there was no version\n    }\n  }\n\n  EXEC_TIMING_END(\"Rm\");\n\n  if (errno) {\n    return Emsg(epname, error, errno, errMsg.c_str(), path);\n  } else {\n    eos_info(\"msg=\\\"deleted\\\" can-recycle=%d path=%s owner.uid=%u owner.gid=%u vid.uid=%u vid.gid=%u\",\n             doRecycle, path, owner_uid, owner_gid, vid.uid, vid.gid);\n\n    // Emit audit record for successful deletion\n    if (mAudit && gOFS->AllowAuditModification(path)) {\n      mAudit->audit(eos::audit::DELETE, path, vid, std::string(logId),\n                    std::string(cident), \"mgm\", std::string(), nullptr, nullptr, std::string(),\n                    std::string(), std::string(), __FILE__, __LINE__, VERSION);\n    }\n\n    return SFS_OK;\n  }\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/SharedPath.inc",
    "content": "// ----------------------------------------------------------------------\n// File: SharedPath.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n\n/*----------------------------------------------------------------------------*/\nstd::string\nXrdMgmOfs::CreateSharePath(const char* inpath,\n                           const char* ininfo,\n                           time_t expires,\n                           XrdOucErrInfo& error,\n                           eos::common::VirtualIdentity& vid)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief create a file sharing path with given liftime\n *\n * @param path file path to share\n * @param info opaque information\n * @param expires unixtimestamp when signature has to expire\n * @param error error object\n * @param vid virtual ID of the caller\n *\n * @return signed path <path>?<signature>\n */\n{\n  NAMESPACEMAP;\n  errno = 0;\n\n  if (_access(path, R_OK, error, vid, \"\")) {\n    errno = EPERM;\n    return std::string(\"\");\n  }\n\n  XrdSfsFileExistence file_exists;\n\n  if ((_exists(path, file_exists, error, vid, 0))) {\n    errno = ENOENT;\n    return std::string(\"\");\n  }\n\n  if (file_exists != XrdSfsFileExistIsFile) {\n    errno = EISDIR;\n    return std::string(\"\");\n  }\n\n  struct stat buf;\n\n  eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n\n  if (_stat(path, &buf, error, rootvid)) {\n    return std::string(\"\");\n  }\n\n  std::string signit = path;\n  signit += \"?\";\n  char sexpires[256];\n  snprintf(sexpires, sizeof(sexpires) - 1, \"%u\", (unsigned int) expires);\n  signit += \"eos.share.expires=\";\n  signit += sexpires;\n  signit += \"&eos.share.fxid=\";\n  const std::string hex_fid = eos::common::FileId::Fid2Hex(buf.st_ino);\n  signit += hex_fid.c_str();\n  signit += \"&eos.share.signature=\";\n  eos::common::SymKey* symkey = eos::common::gSymKeyStore.GetCurrentKey();\n\n  if (!symkey) {\n    errno = ENOKEY;\n    return std::string(\"\");\n  }\n\n  XrdOucString ouc_sign = sexpires;\n  ouc_sign += path;\n  ouc_sign += sexpires;\n  ouc_sign += gOFS->MgmOfsInstanceName;\n  ouc_sign += hex_fid.c_str();\n  XrdOucString ouc_signed;\n\n  if (!eos::common::SymKey::SymmetricStringEncrypt(ouc_sign, ouc_signed,\n      (char*) symkey->GetKey())) {\n    errno = EKEYREJECTED;\n    return std::string(\"\");\n  }\n\n  while (ouc_signed.replace(\"\\n\", \"\")) {\n  }\n\n  signit += ouc_signed.c_str();\n  return signit;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nXrdMgmOfs::VerifySharePath(const char* path,\n                           XrdOucEnv* opaque)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief verify a file sharing path\n *\n * @param path file path to share\n * @param opaque information containing a file share signature\n *\n * @return true if valid otherwise false\n */\n{\n  // check if this is a signed path\n  if (!opaque->Get(\"eos.share.signature\")) {\n    return false;\n  }\n\n  // check if this has a valid expiration date\n  XrdOucString expires = opaque->Get(\"eos.share.expires\");\n\n  if (!expires.length() || expires == \"0\") {\n    return false;\n  }\n\n  // check if this has fid\n  XrdOucString fxid = opaque->Get(\"eos.share.fxid\");\n\n  if (!fxid.length()) {\n    return false;\n  }\n\n  // get the fid\n  struct stat buf;\n  eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n  XrdOucErrInfo error;\n\n  if (_stat(path, &buf, error, rootvid)) {\n    return false;\n  }\n\n  const std::string hex_fid = eos::common::FileId::Fid2Hex(buf.st_ino);\n\n  if (std::string(fxid.c_str()) != hex_fid) {\n    eos_warning(\"msg=\\\"shared file has changed file id - share URL not valid anymore\\\"\");\n    return false;\n  }\n\n  // check that it is not yet expired\n  time_t expired = strtoul(expires.c_str(), 0, 10);\n  time_t now = time(NULL);\n\n  if (!expired || (expired < now)) {\n    int envlen;\n    eos_static_err(\"msg=\\\"shared link expired\\\" path=%s info=%s\\n\", path,\n                   opaque->Env(envlen));\n    return false;\n  }\n\n  eos::common::SymKey* symkey = eos::common::gSymKeyStore.GetCurrentKey();\n\n  if (!symkey) {\n    eos_static_err(\"msg=\\\"failed to retrieve symmetric key to verify shared link\");\n    return false;\n  }\n\n  // verify the signature\n  XrdOucString ouc_sign = expires;\n  ouc_sign += path;\n  ouc_sign += expires;\n  ouc_sign += gOFS->MgmOfsInstanceName;\n  ouc_sign += hex_fid.c_str();\n  XrdOucString ouc_signed;\n\n  if (!eos::common::SymKey::SymmetricStringEncrypt(ouc_sign, ouc_signed,\n      (char*) symkey->GetKey())) {\n    eos_static_err(\"msg=\\\"failed to encrypt to verify shared link\");\n    return false;\n  }\n\n  while (ouc_signed.replace(\"\\n\", \"\")) {\n  }\n\n  XrdOucString ouc_signature = opaque->Get(\"eos.share.signature\");\n\n  if (ouc_signature == ouc_signed) {\n    return true;\n  } else {\n    int envlen;\n    eos_static_err(\"msg=\\\"shared link with invalid signature\\\" path=%s info=%s len=%d len=%d\\n\",\n                   path, opaque->Env(envlen), ouc_signature.length(), ouc_signed.length());\n    return false;\n  }\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/ShouldRedirect.inc",
    "content": "// ----------------------------------------------------------------------\n// File: ShouldRedirect.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Check if a client based on the called function and his identity should be\n// redirected\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::ShouldRedirect(const char* function, int __AccessMode__,\n                          eos::common::VirtualIdentity& vid,\n                          std::string& host, int& port, bool& collapse)\n{\n  eos::common::RWMutexReadLock lock(Access::gAccessMutex);\n\n  if ((vid.host == \"localhost\") || (vid.host == \"localhost.localdomain\") ||\n      (vid.uid == 0)) {\n    if (mMaster->IsMaster() || (IS_ACCESSMODE_R)) {\n      // the slave is redirected to the master for everything which sort of 'writes'\n      return false;\n    }\n  }\n\n  if (!Access::gRedirectionRules.empty()) {\n    bool c1 = Access::gRedirectionRules.count(std::string(\"*\"));\n    bool c3 = (IS_ACCESSMODE_R &&\n               Access::gRedirectionRules.count(std::string(\"r:*\")));\n    bool c2 = (IS_ACCESSMODE_W &&\n               Access::gRedirectionRules.count(std::string(\"w:*\")));\n    bool c4 = (IS_ACCESSMODE_R_MASTER &&\n               Access::gRedirectionRules.count(std::string(\"w:*\")));\n\n    if (c1 || c2 || c3 || c4) {\n      // redirect\n      std::string delimiter = \":\";\n      std::vector<std::string> tokens;\n\n      if (c1) {\n        eos::common::StringConversion::Tokenize(\n          Access::gRedirectionRules[std::string(\"*\")], tokens, delimiter);\n        gOFS->MgmStats.Add(\"Redirect\", vid.uid, vid.gid, 1);\n      } else {\n        if (c2) {\n          eos::common::StringConversion::Tokenize(\n            Access::gRedirectionRules[std::string(\"w:*\")], tokens, delimiter);\n          gOFS->MgmStats.Add(\"RedirectW\", vid.uid, vid.gid, 1);\n        } else {\n          if (c3) {\n            eos::common::StringConversion::Tokenize(\n              Access::gRedirectionRules[std::string(\"r:*\")], tokens, delimiter);\n            gOFS->MgmStats.Add(\"RedirectR\", vid.uid, vid.gid, 1);\n          } else {\n            if (c4) {\n              eos::common::StringConversion::Tokenize(\n                Access::gRedirectionRules[std::string(\"w:*\")], tokens, delimiter);\n              gOFS->MgmStats.Add(\"RedirectR-Master\", vid.uid, vid.gid, 1);\n            }\n          }\n        }\n      }\n\n      if (!tokens.empty()) { // tokens should never be empty but @note fuzz tests showed it could be. Will have to dig deeper\n        if (tokens.size() == 1) {\n          host = tokens[0];\n          port = 1094;\n        } else {\n\t  if (tokens.size() == 2) {\n\t    host = tokens[0];\n\t    port = strtol(tokens[1].c_str(), nullptr,10);\n\t  } else {\n\t    if (tokens.size() == 3) {\n\t      host = tokens[0];\n\t      port = strtol(tokens[1].c_str(), nullptr,10);\n\t      uint64_t delay = ( strtol(tokens[2].c_str(), nullptr,10 ));\n\t      if (delay) {\n\t\tstd::this_thread::sleep_for(std::chrono::milliseconds(delay));\n\t      }\n\t    }\n\t  }\n\t}\n      }\n\n      collapse = true;\n      return true;\n    }\n  }\n\n  return false;\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/ShouldRoute.inc",
    "content": "//------------------------------------------------------------------------------\n// File: ShouldRoute.inc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Test if a client based on the called function and his identity\n// should be re-routed\n//------------------------------------------------------------------------------\nbool\nXrdMgmOfs::ShouldRoute(const char* function, int accessmode,\n                       eos::common::VirtualIdentity& vid,\n                       const char* path, const char* info,\n                       std::string& host, int& port, int& stall_timeout)\n{\n  if ((vid.uid == 0) ||\n      (vid.host == \"localhost\") ||\n      (vid.host == \"localhost.localdomain\")) {\n    return false;\n  }\n\n  // Might happen during shutdown\n  if (mRouting == nullptr) {\n    return false;\n  }\n\n  std::string stat_info;\n  eos::mgm::PathRouting::Status st =\n    mRouting->Reroute(path, info, vid, host, port, stat_info);\n\n  if (st == PathRouting::Status::REROUTE) {\n    gOFS->MgmStats.Add(stat_info.c_str(), vid.uid, vid.gid, 1);\n    return true;\n  } else if (st == PathRouting::Status::STALL) {\n    stall_timeout = 5; // seconds\n    return true;\n  } else {\n    return false;\n  }\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/ShouldStall.inc",
    "content": "// ----------------------------------------------------------------------\n// File: ShouldStall.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include <chrono>\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n\nbool\nXrdMgmOfs::ShouldStall(const char* function,\n                       int __AccessMode__,\n                       eos::common::VirtualIdentity& vid,\n                       int& stalltime, XrdOucString& stallmsg)\n{\n  // Check for user, group or host banning\n  std::string smsg = \"\";\n  stalltime = 0;\n  bool stall = true;\n  std::string functionname = function;\n  bool saturated = false;\n  double limit = 0;\n\n  // After booting don't stall FST nodes\n  if (gOFS->IsNsBooted() && (vid.prot == \"sss\") &&\n      vid.hasUid(DAEMONUID)) {\n    eos_static_debug(\"info=\\\"avoid stalling of the FST node\\\" host=%s\",\n                     vid.host.c_str());\n    stall = false;\n  }\n\n  // Avoid stalling HTTP requests as these translate into errors on the client\n  // except if the adminstrator has set the environment variable allowing\n  // stalling to take place with HTTP\n  if (vid.prot == \"https\" && !getenv(\"EOS_MGM_ALLOW_HTTP_STALL\")) {\n    stall = false;\n  }\n\n  eos::common::RWMutexReadLock lock(Access::gAccessMutex);\n  std::string stallid = \"Stall\";\n  size_t uid_threads = 1;\n\n  if (stall) {\n    if ((vid.uid > 3) && (functionname != \"stat\")  && (vid.app != gOFS->mFuseNoStallApp)) {\n      if ((stalltime = gOFS->mTracker.ShouldStall(vid.uid, saturated, uid_threads))) {\n        smsg = SSTR(\"operate - uid=\" << vid.uid << \" exceeding the \"\n                    \"thread pool limit\");\n        stallid += \"::threads::\";\n        stallid += std::to_string(vid.uid);;\n      } else if (Access::gBannedUsers.count(vid.uid)) {\n        smsg = SSTR(\"operate - uid=\" << vid.uid << \" banned in this instance \"\n                    \"- contact an administrator\");\n\n        // fuse clients don't get stalled by a booted namespace, they get EACCES\n        if (vid.app.substr(0, 4) == \"fuse\") {\n          stallmsg = smsg.c_str();\n          return true;\n        }\n\n        // BANNED USER\n        stalltime = 300;\n      } else if (Access::gBannedGroups.count(vid.gid)) {\n        smsg = SSTR(\"operate - gid=\" << vid.gid << \" banned in this instance \"\n                    \"- contact an administrator\");\n\n        // fuse clients don't get stalled by a booted namespace, they get EACCES\n        if (vid.app.substr(0, 4) == \"fuse\") {\n          stallmsg = smsg.c_str();\n          return true;\n        }\n\n        // BANNED GROUP\n        stalltime = 300;\n      } else if (Access::gBannedHosts.count(vid.host)) {\n        smsg = SSTR(\"operate - client host=\" << vid.host << \" banned in this \"\n                    \"instance - contact an administrator\");\n        // BANNED HOST\n        stalltime = 300;\n      } else if (Access::gBannedDomains.count(vid.domain)) {\n        smsg = SSTR(\"operate -  client domain=\" << vid.domain << \" banned \"\n                    \"in this instance - contact an administrator\");\n        // BANNED DOMAINS\n        stalltime = 300;\n      } else if (vid.token && Access::gBannedTokens.count(vid.token->Voucher())) {\n        smsg = \"operate - your token is banned in this instance - contact an administrator\";\n        // BANNED TOKEN\n        stalltime = 300;\n      } else if (Access::gStallRules.size() && (Access::gStallGlobal)) {\n        // GLOBAL STALL\n        stalltime = atoi(Access::gStallRules[std::string(\"*\")].c_str());\n        smsg = Access::gStallComment[std::string(\"*\")];\n      } else if ((IS_ACCESSMODE_R && (Access::gStallRead)) ||\n                 (IS_ACCESSMODE_R_MASTER && (Access::gStallRead))) {\n        // READ STALL\n        stalltime = atoi(Access::gStallRules[std::string(\"r:*\")].c_str());\n        smsg = Access::gStallComment[std::string(\"r:*\")];\n      } else if (IS_ACCESSMODE_W && (Access::gStallWrite)) {\n        stalltime = atoi(Access::gStallRules[std::string(\"w:*\")].c_str());\n        smsg = Access::gStallComment[std::string(\"w:*\")];\n      } else if (Access::gStallUserGroup) {\n        std::string usermatch = \"rate:user:\";\n        usermatch += vid.uid_string;\n        std::string groupmatch = \"rate:group:\";\n        groupmatch += vid.gid_string;\n        std::string userwildcardmatch = \"rate:user:*\";\n        std::string groupwildcardmatch = \"rate:group:*\";\n        std::map<std::string, std::string>::const_iterator it;\n\n        if ((functionname != \"stat\") &&  // never stall stats\n            (vid.app != gOFS->mFuseNoStallApp)) {\n          for (it = Access::gStallRules.begin();\n               it != Access::gStallRules.end();\n               it++) {\n            stallid = \"Stall\";\n            auto eosxd_pos = it->first.rfind(\"Eosxd\");\n            auto pos = it->first.rfind(\":\");\n            std::string cmd = (eosxd_pos != std::string::npos) ?\n                              it->first.substr(eosxd_pos) : it->first.substr(pos + 1);\n            stallid += \"::\";\n            stallid += cmd;\n            eos_static_debug(\"rule=%s function=%s\", cmd.c_str(), function);\n\n            // only Eosxd rates can be fine-grained by function\n            if (cmd.substr(0, 5) == \"Eosxd\") {\n              if (cmd != function) {\n                continue;\n              }\n            }\n\n            double cutoff = strtod(it->second.c_str(), 0) * 1.33;\n\n            if ((it->first.find(usermatch) == 0)) {\n              // check user rule\n              XrdSysMutexHelper statLock(gOFS->MgmStats.mMutex);\n\n              if ((cutoff == 0) ||\n                  (gOFS->MgmStats.StatAvgUid.count(cmd) &&\n                   gOFS->MgmStats.StatAvgUid[cmd].count(vid.uid) &&\n                   (gOFS->MgmStats.StatAvgUid[cmd][vid.uid].GetAvg5() > cutoff)\n                  )) {\n                // rate exceeded\n                if (!stalltime) {\n                  stalltime = 5;\n                }\n\n                limit = cutoff;\n                smsg = Access::gStallComment[it->first];\n                break;\n              }\n            } else if ((it->first.find(groupmatch) == 0)) {\n              // check group rule\n              XrdSysMutexHelper statLock(gOFS->MgmStats.mMutex);\n\n              if ((cutoff == 0) ||\n                  (gOFS->MgmStats.StatAvgGid.count(cmd) &&\n                   gOFS->MgmStats.StatAvgGid[cmd].count(vid.gid) &&\n                   (gOFS->MgmStats.StatAvgGid[cmd][vid.gid].GetAvg5() > cutoff)\n                  )) {\n                // rate exceeded\n                if (!stalltime) {\n                  stalltime = 5;\n                }\n\n                limit = cutoff;\n                smsg = Access::gStallComment[it->first];\n                break;\n              }\n            }\n\n            if ((it->first.find(userwildcardmatch) == 0)) {\n              // catch all rule = global user rate cut\n              XrdSysMutexHelper statLock(gOFS->MgmStats.mMutex);\n\n              if ((cutoff == 0) ||\n                  (gOFS->MgmStats.StatAvgUid.count(cmd) &&\n                   gOFS->MgmStats.StatAvgUid[cmd].count(vid.uid) &&\n                   (gOFS->MgmStats.StatAvgUid[cmd][vid.uid].GetAvg5() > cutoff)\n                  )) {\n                if (!stalltime) {\n                  stalltime = 5;\n                }\n\n                limit = cutoff;\n                smsg = Access::gStallComment[it->first];\n                break;\n              }\n            } else if ((it->first.find(groupwildcardmatch) == 0)) {\n              // catch all rule = global user rate cut\n              XrdSysMutexHelper statLock(gOFS->MgmStats.mMutex);\n\n              if ((cutoff == 0) ||\n                  (gOFS->MgmStats.StatAvgGid.count(cmd) &&\n                   gOFS->MgmStats.StatAvgGid[cmd].count(vid.gid) &&\n                   (gOFS->MgmStats.StatAvgGid[cmd][vid.gid].GetAvg5() > cutoff)\n                  )) {\n                if (!stalltime) {\n                  stalltime = 5;\n                }\n\n                limit = cutoff;\n                smsg = Access::gStallComment[it->first];\n                break;\n              }\n            }\n          }\n        }\n      }\n\n      if (stalltime && (saturated || ! limit)) {\n        // add random offset between 0 and 5 to stalltime\n        int random_stall = eos::common::getRandom(0, 5);\n        stalltime += random_stall;\n        stallmsg = \"Attention: you are currently hold in this instance and each\"\n                   \" request is stalled for \";\n        stallmsg += (int) stalltime;\n        stallmsg += \" seconds ... \";\n        stallmsg += smsg.c_str();\n        eos_static_info(\"info=\\\"stalling access to\\\" uid=%u gid=%u host=%s stall=%d\",\n                        vid.uid, vid.gid, vid.host.c_str(), stalltime);\n        gOFS->MgmStats.Add(stallid.c_str(), vid.uid, vid.gid, 1);\n        return true;\n      } else {\n        if (limit) {\n          stallid = \"Delay\";\n          stallid += \"::threads::\";\n          stallid += std::to_string(vid.uid);;\n          std::string delayid = stallid;\n          delayid += \"::ms\";\n          size_t ms_to_delay = 1000.0 / limit;\n\n          if (uid_threads) {\n            // renomalize with the curent user thread pool size\n            ms_to_delay *= uid_threads;\n\n            if (ms_to_delay > 40000) {\n              // we should not hang longer than 40s not to trigger timeouts, which are 60s by default for FUSE clients and 5min for XRootD clients\n              ms_to_delay = 40000;\n            }\n          }\n\n          lock.Release();\n          std::this_thread::sleep_for(std::chrono::milliseconds(ms_to_delay));\n          gOFS->MgmStats.Add(stallid.c_str(), vid.uid, vid.gid, 1);\n          gOFS->MgmStats.Add(delayid.c_str(), vid.uid, vid.gid, ms_to_delay);\n          return false;\n        }\n      }\n    } else {\n      if (Access::gStallRules.size() &&\n          Access::gStallRules.count(std::string(\"*\"))) {\n        if ((vid.host != \"localhost.localdomain\") &&\n            (vid.host != \"localhost\")) {\n          // admin/root is only stalled for global stalls not,\n          // for write-only or read-only stalls\n          stalltime = atoi(Access::gStallRules[std::string(\"*\")].c_str());\n          stallmsg = \"Attention: you are currently hold in this instance and each\"\n                     \" request is stalled for \";\n          stallmsg += (int) stalltime;\n          stallmsg += \" seconds ...\";\n          eos_static_info(\"info=\\\"stalling access to\\\" uid=%u gid=%u host=%s\",\n                          vid.uid, vid.gid, vid.host.c_str());\n          gOFS->MgmStats.Add(\"Stall\", vid.uid, vid.gid, 1);\n          return true;\n        } else {\n          // localhost does not get stalled but receives an error during boot when trying to write\n          if (IS_ACCESSMODE_W) {\n            stalltime = 0 ;\n            stallmsg = \"do modifications - writing is currently stalled on the instance\";\n            return true;\n          }\n        }\n      }\n    }\n  }\n\n  eos_static_debug(\"info=\\\"allowing access to\\\" uid=%u gid=%u host=%s\",\n                   vid.uid, vid.gid, vid.host.c_str());\n  return false;\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Shutdown.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Shutdown.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n/*\n * @brief shutdown function cleaning up running threads/objects for a clean exit\n *\n * @param sig signal catched\n *\n * This shutdown function tries to get a write lock before doing the namespace\n * shutdown. Since it is not guaranteed that one can always get a write lock\n * there is a timeout in requiring the write lock and then the shutdown is forced.\n * Depending on the role of the MGM it stop's the running namespace follower\n * and in all cases running sub-services of the MGM.\n */\n//------------------------------------------------------------------------------\nvoid\nxrdmgmofs_shutdown(int sig)\n\n{\n  (void) signal(SIGINT, SIG_IGN);\n  (void) signal(SIGTERM, SIG_IGN);\n  (void) signal(SIGQUIT, SIG_IGN);\n  eos_static_alert(\"msg=\\\"shutdown sequence started\\'\");\n\n  // Avoid shutdown recursions\n  if (gOFS->Shutdown) {\n    return;\n  }\n\n  gOFS->Shutdown = true;\n  gOFS->OrderlyShutdown();\n  eos_static_alert(\"%s\", \"msg=\\\"shutdown complete\\\"\");\n  std::quick_exit(0);\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Stacktrace.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Stacktrace.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n\n/*----------------------------------------------------------------------------*/\nvoid\nxrdmgmofs_stacktrace (int sig)\n/*----------------------------------------------------------------------------*/\n/* @brief static function to print a stack-trace on STDERR\n *\n * @param sig signal catched\n *\n * After catching 'sig' and producing a stack trace the signal handler is put\n * back to the default and the signal is send again ... this is mainly used\n * to create a stack trace and a core dump after a SEGV signal.\n *\n */\n/*----------------------------------------------------------------------------*/\n{\n  (void) signal(SIGINT, SIG_IGN);\n  (void) signal(SIGTERM, SIG_IGN);\n  (void) signal(SIGQUIT, SIG_IGN);\n  void *array[10];\n  size_t size;\n\n  // get void*'s for all entries on the stack\n  size = backtrace(array, 10);\n\n  // print out all the frames to stderr\n  fprintf(stderr, \"error: received signal %d:\\n\", sig);\n\n  backtrace_symbols_fd(array, size, 2);\n\n  eos::common::StackTrace::GdbTrace(0, getpid(), \"thread apply all bt\");\n\n  if (getenv(\"EOS_CORE_DUMP\"))\n  {\n    eos::common::StackTrace::GdbTrace(0, getpid(), \"generate-core-file\");\n  }\n\n  if (getenv(\"EOS_RAISE_SIGNAL_AFTER_SIGV\")) {\n    // now we put back the initial handler ...\n    signal(sig, SIG_DFL);\n\n    // ... and send the signal again\n    kill(getpid(), sig);\n  }\n  else {\n    // No std::abort to prevent abortd from generating a multi-GB corefile\n    std::quick_exit(128 + sig);\n  }\n\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Stat.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Stat.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n\n/*----------------------------------------------------------------------------*/\n\n#include \"namespace/Resolver.hh\"\n\nint\nXrdMgmOfs::stat(const char* inpath,\n                struct stat* buf,\n                XrdOucErrInfo& error,\n                const XrdSecEntity* client,\n                const char* ininfo\n               )\n{\n  return stat(inpath, buf, error, 0, client, ininfo, false);\n}\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::stat(const char* inpath,\n                struct stat* buf,\n                XrdOucErrInfo& error,\n                std::string* etag,\n                const XrdSecEntity* client,\n                const char* ininfo,\n                bool follow,\n                std::string* uri,\n                std::string* cks)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief return stat information for a given path\n *\n * @param inpath path to stat\n * @param buf stat buffer where to store the stat information\n * @param error error object\n * @param client XRootD authentication object\n * @param ininfo CGI\n * @param etag string to return the ETag for that object\n * @param follow to indicate to follow symbolic links on leave nodes\n * @return SFS_OK on success otherwise SFS_ERROR\n *\n * See the internal implemtation _stat for details.\n */\n/*----------------------------------------------------------------------------*/\n\n{\n  static const char* epname = \"stat\";\n  const char* tident = error.getErrUser();\n  // use a thread private vid\n  eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Nobody();\n  NAMESPACEMAP;\n  BOUNCE_ILLEGAL_NAMES;\n  XrdOucEnv Open_Env(ininfo);\n  AUTHORIZE(client, &Open_Env, AOP_Stat, \"stat\", inpath, error);\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz,\n                              AOP_Stat, path, true);\n  EXEC_TIMING_END(\"IdMap\");\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  BOUNCE_NOT_ALLOWED;\n  ACCESSMODE_R;\n  MAYSTALL;\n  eos::common::Path cPath(path);\n\n  // Never redirect stat's for the master mode\n  if (cPath.GetFullPath() != gOFS->MgmProcMasterPath) {\n    MAYREDIRECT;\n  }\n\n  errno = 0;\n  int rc = _stat(path, buf, error, vid, ininfo, etag, follow, uri, cks);\n\n  if (rc) {\n    if (errno == ENOENT) {\n      MAYREDIRECT_ENOENT;\n      MAYSTALL_ENOENT;\n    }\n  } else {\n    _stat_set_flags(buf);\n  }\n\n  return rc;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nXrdMgmOfs::_stat_set_flags(struct stat* buf)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief set XRDSFS_OFFLINE and XRDSFS_HASBKUP flags\n *\n * @param[in,out] buf    Stat structure\n *\n * XRDSFS_HASBKUP is set iff there is a tape copy for the file\n * XRDSFS_OFFLINE is set iff there is no disk copy for the file\n *                (i.e. only a tape copy exists)\n */\n/*----------------------------------------------------------------------------*/\n{\n  // If EOS_TAPE_MODE_T is set, there is a copy on tape\n  if (buf->st_mode & EOS_TAPE_MODE_T) {\n    buf->st_rdev |= XRDSFS_HASBKUP;\n  } else {\n    buf->st_rdev &= ~XRDSFS_HASBKUP;\n  }\n\n  // Number of disk copies = total number of copies - 1 if there is a tape copy\n  auto numDiskCopies = buf->st_nlink - (buf->st_mode & EOS_TAPE_MODE_T ? 1 : 0);\n\n  if (numDiskCopies > 0) {\n    buf->st_rdev &= ~XRDSFS_OFFLINE;\n  } else {\n    if (buf->st_size != 0) {\n      buf->st_rdev |= XRDSFS_OFFLINE;\n    }\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::_stat(const char* path,\n                 struct stat* buf,\n                 XrdOucErrInfo& error,\n                 eos::common::VirtualIdentity& vid,\n                 const char* ininfo,\n                 std::string* etag,\n                 bool follow,\n                 std::string* uri,\n                 std::string* cks)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief return stat information for a given path\n *\n * @param inpath path to stat\n * @param buf stat buffer where to store the stat information\n * @param error error object\n * @param vid virtual identity of the client\n * @param ininfo CGI\n * @param follow to indicate to follow symbolic links on leave nodes\n * @return SFS_OK on success otherwise SFS_ERROR\n *\n * We don't apply any access control on stat calls for performance reasons.\n * Modification times of directories are only emulated and returned from an\n * in-memory map.\n */\n/*----------------------------------------------------------------------------*/\n{\n  static const char* epname = \"_stat\";\n  EXEC_TIMING_BEGIN(\"Stat\");\n  gOFS->MgmStats.Add(\"Stat\", vid.uid, vid.gid, 1);\n  // ---------------------------------------------------------------------------\n  // try if that is a file\n  errno = 0;\n  std::shared_ptr<eos::IFileMD> fmd;\n  eos::common::Path cPath(path);\n\n  // Stat on the master proc entry succeeds only if this MGM is in RW master mode\n  if (cPath.GetFullPath() == gOFS->MgmProcMasterPath) {\n    if (!gOFS->mMaster->IsMaster()) {\n      return Emsg(epname, error, ENODEV, \"stat\", cPath.GetPath());\n    }\n  }\n\n  // public access level restriction\n  if (!gOFS->allow_public_access(path, vid)) {\n    eos_static_err(\"vid.uid=%d\\n\", vid.uid);\n    errno = EACCES;\n    return Emsg(epname, error, EACCES, \"access - public access level restriction\",\n                path);\n  }\n\n  // Prefetch path\n  eos::Prefetcher::prefetchItemAndWait(gOFS->eosView, cPath.GetPath(), follow);\n  eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n\n  try {\n    if (strncmp(cPath.GetPath(), \"/.fxid:\", 7) == 0) {\n      XrdOucString spath = cPath.GetPath();\n      unsigned long long byfid = eos::Resolver::retrieveFileIdentifier(\n                                   spath).getUnderlyingUInt64();\n      fmd = gOFS->eosFileService->getFileMD(byfid);\n      eos_info(\"msg=\\\"access by inode\\\" ino=%s\", cPath.GetPath());\n    } else {\n      fmd = gOFS->eosView->getFile(cPath.GetPath(), follow);\n\n      // if a stat comes with file/ return an error\n      if (std::string(path).back() == '/') {\n        errno = EISDIR;\n        return Emsg(epname, error, errno, \"stat\", cPath.GetPath());\n      }\n    }\n\n    if (uri) {\n      *uri = gOFS->eosView->getUri(fmd.get());\n    }\n\n    if (cks) {\n      eos::appendChecksumOnStringAsHex(fmd.get(), *cks);\n    }\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\", e.getErrno(),\n              e.getMessage().str().c_str());\n\n    if (errno == ELOOP) {\n      return Emsg(epname, error, errno, \"stat\", cPath.GetPath());\n    }\n  }\n\n  if (fmd) {\n    memset(buf, 0, sizeof(struct stat));\n    buf->st_dev = 0xcaff;\n    buf->st_ino = eos::common::FileId::FidToInode(fmd->getId());\n    buf->st_mode = eos::modeFromMetadataEntry(fmd);\n\n    if (fmd->isLink()) {\n      buf->st_nlink = 1;\n    } else {\n      // we have to pass only disk locations to GetRedundancy and correct\n      unsigned long disk_locations = fmd->getNumLocation();\n\n      if (buf->st_mode & EOS_TAPE_MODE_T) {\n        if (disk_locations > 0) {\n          disk_locations--;\n        }\n      }\n\n      buf->st_nlink = eos::common::LayoutId::GetRedundancy(fmd->getLayoutId(),\n                      disk_locations);\n\n      if ((buf->st_mode & EOS_TAPE_MODE_T)) {\n        // file is unavailable on disk, but available on tape e.g. redundancy from disk layout = 0\n        buf->st_nlink++;\n      }\n    }\n\n    buf->st_size = fmd->getSize();\n    buf->st_uid = fmd->getCUid();\n    buf->st_gid = fmd->getCGid();\n    buf->st_rdev = 0; /* device type (if inode device) */\n    buf->st_blksize = 512;\n    buf->st_blocks = (Quota::MapSizeCB(fmd.get()) + 512) /\n                     512; // including layout factor\n    eos::IFileMD::ctime_t atime;\n    // adding also nanosecond to stat struct\n    fmd->getCTime(atime);\n#ifdef __APPLE__\n    buf->st_ctimespec.tv_sec = atime.tv_sec;\n    buf->st_ctimespec.tv_nsec = atime.tv_nsec;\n#else\n    buf->st_ctime = atime.tv_sec;\n    buf->st_ctim.tv_sec = atime.tv_sec;\n    buf->st_ctim.tv_nsec = atime.tv_nsec;\n#endif\n    fmd->getMTime(atime);\n#ifdef __APPLE__\n    buf->st_mtimespec.tv_sec = atime.tv_sec;\n    buf->st_mtimespec.tv_nsec = atime.tv_nsec;\n    fmd->getATime(atime);\n    buf->st_atimespec.tv_sec = atime.tv_sec;\n    buf->st_atimespec.tv_nsec = atime.tv_nsec;\n#else\n    buf->st_mtime = atime.tv_sec;\n    buf->st_mtim.tv_sec = atime.tv_sec;\n    buf->st_mtim.tv_nsec = atime.tv_nsec;\n    fmd->getATime(atime);\n    buf->st_atime = atime.tv_sec;\n    buf->st_atim.tv_sec = atime.tv_sec;\n    buf->st_atim.tv_nsec = atime.tv_nsec;\n#endif\n\n    if (etag) {\n      eos::calculateEtag(fmd.get(), *etag);\n\n      if (fmd->hasAttribute(\"sys.eos.mdino\")) {\n        *etag = \"hardlink\";\n      }\n    }\n\n    EXEC_TIMING_END(\"Stat\");\n    return SFS_OK;\n  }\n\n  // Check if it's a directory\n  std::shared_ptr<eos::IContainerMD> cmd;\n  errno = 0;\n\n  // ---------------------------------------------------------------------------\n  try {\n    cmd = gOFS->eosView->getContainer(cPath.GetPath(), follow);\n\n    if (uri) {\n      *uri = gOFS->eosView->getUri(cmd.get());\n    }\n\n    memset(buf, 0, sizeof(struct stat));\n    buf->st_dev = 0xcaff;\n    buf->st_ino = cmd->getId();\n    buf->st_mode = eos::modeFromMetadataEntry(cmd);\n    buf->st_nlink = 1;\n    buf->st_uid = cmd->getCUid();\n    buf->st_gid = cmd->getCGid();\n    buf->st_rdev = 0; /* device type (if inode device) */\n    buf->st_size = cmd->getTreeSize();\n    buf->st_blksize = cmd->getNumContainers() + cmd->getNumFiles();\n    buf->st_blocks = 0;\n    eos::IContainerMD::ctime_t ctime;\n    eos::IContainerMD::ctime_t mtime;\n    eos::IContainerMD::ctime_t tmtime;\n    cmd->getCTime(ctime);\n    cmd->getMTime(mtime);\n\n    if (gOFS->eosSyncTimeAccounting) {\n      cmd->getTMTime(tmtime);\n    } else\n      // if there is no sync time accounting we just use the normal modification time\n    {\n      tmtime = mtime;\n    }\n\n#ifdef __APPLE__\n    buf->st_atimespec.tv_sec = tmtime.tv_sec;\n    buf->st_mtimespec.tv_sec = mtime.tv_sec;\n    buf->st_ctimespec.tv_sec = ctime.tv_sec;\n    buf->st_atimespec.tv_nsec = tmtime.tv_nsec;\n    buf->st_mtimespec.tv_nsec = mtime.tv_nsec;\n    buf->st_ctimespec.tv_nsec = ctime.tv_nsec;\n#else\n    buf->st_atime = tmtime.tv_sec;\n    buf->st_mtime = mtime.tv_sec;\n    buf->st_ctime = ctime.tv_sec;\n    buf->st_atim.tv_sec = tmtime.tv_sec;\n    buf->st_mtim.tv_sec = mtime.tv_sec;\n    buf->st_ctim.tv_sec = ctime.tv_sec;\n    buf->st_atim.tv_nsec = tmtime.tv_nsec;\n    buf->st_mtim.tv_nsec = mtime.tv_nsec;\n    buf->st_ctim.tv_nsec = ctime.tv_nsec;\n#endif\n\n    if (etag) {\n      eos::calculateEtag(cmd.get(), *etag);\n    }\n\n    if (cks) {\n      *cks = \"\";\n    }\n\n    return SFS_OK;\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\", e.getErrno(),\n              e.getMessage().str().c_str());\n    return Emsg(epname, error, errno, \"stat\", cPath.GetPath());\n  }\n}\n\n// ---------------------------------------------------------------------------\n//  get the checksum info of a file\n// ---------------------------------------------------------------------------\nint\nXrdMgmOfs::_getchecksum(const char* Name,\n                        XrdOucErrInfo& error,\n                        std::string* xstype,\n                        std::string* xs,\n                        const XrdSecEntity* client,\n                        const char* opaque,\n                        bool follow)\n{\n  // ---------------------------------------------------------------------------\n  errno = 0;\n  std::shared_ptr<eos::IFileMD> fmd;\n  eos::common::Path cPath(Name);\n  eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, cPath.GetPath(), follow);\n  eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n\n  try {\n    fmd = gOFS->eosView->getFile(cPath.GetPath(), follow);\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\", e.getErrno(),\n              e.getMessage().str().c_str());\n    return errno;\n  }\n\n  if (fmd) {\n    size_t cxlen = eos::common::LayoutId::GetChecksumLen(fmd->getLayoutId());\n\n    if (cxlen) {\n      *xstype = eos::common::LayoutId::GetChecksumStringReal(fmd->getLayoutId());\n      eos::appendChecksumOnStringAsHex(fmd.get(), *xs);\n    }\n  }\n\n  return 0;\n}\n//------------------------------------------------------------------------------\n// Stat following links (not existing in EOS - behaves like stat)\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::lstat(const char* path,\n                 struct stat* buf,\n                 XrdOucErrInfo& error,\n                 const XrdSecEntity* client,\n                 const char* info)\n\n{\n  return stat(path, buf, error, client, info);\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Stripes.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Stripes.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Send verify stripe request to a certain file system for file path\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::_verifystripe(const char* path,\n                         XrdOucErrInfo& error,\n                         eos::common::VirtualIdentity& vid,\n                         unsigned long fsid,\n                         const std::string& options)\n{\n  static const char* epname = \"verifystripe\";\n  eos::IFileMD::id_t fid = 0ull;\n\n  try {\n    auto fmd = gOFS->eosView->getFile(path);\n    fid = fmd->getId();\n  } catch (eos::MDException& e) {\n    int errc = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n              e.getErrno(), e.getMessage().str().c_str());\n    return Emsg(epname, error, errc,\n                \"verify stripe - not file metadata\", path);\n  }\n\n  return _verifystripe(fid, error, vid, fsid, options, path);\n}\n\n//----------------------------------------------------------------------------\n// Send verify stripe request to a certain file system for file identifier\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::_verifystripe(const eos::IFileMD::id_t fid,\n                         XrdOucErrInfo& error,\n                         eos::common::VirtualIdentity& vid,\n                         unsigned long fsid,\n                         const std::string& options,\n                         const std::string& ns_path)\n{\n  eos_debug(\"verify\");\n  static const char* epname = \"verifystripe\";\n  EXEC_TIMING_BEGIN(\"VerifyStripe\");\n  int lid = 0;\n  int errc = 0;\n  unsigned long long cid = 0;\n  eos::IContainerMD::XAttrMap attrmap;\n  gOFS->MgmStats.Add(\"VerifyStripe\", vid.uid, vid.gid, 1);\n\n  try {\n    auto fmd = gOFS->eosView->getFileMDSvc()->getFileMD(fid);\n    auto fmdLock = eos::MDLocking::readLock(fmd.get());\n    cid = fmd->getContainerId();\n    lid = fmd->getLayoutId();\n  } catch (eos::MDException& e) {\n    errc = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n              e.getMessage().str().c_str());\n    return Emsg(epname, error, errc, \"verify stripe - no file metadata fid=\",\n                std::to_string(fid).c_str());\n  }\n\n  {\n    // Check parent existance and permissions\n    eos::MDLocking::ContainerReadLockPtr cmd_rlock;\n    std::shared_ptr<eos::IContainerMD> cmd;\n\n    try {\n      cmd = gOFS->eosView->getContainerMDSvc()->getContainerMD(cid);\n      cmd_rlock = eos::MDLocking::readLock(cmd.get());\n    } catch (eos::MDException& e) {\n      cmd.reset();\n      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                e.getMessage().str().c_str());\n    }\n\n    // Check permissions\n    errno = 0;\n\n    if (cmd && (vid.token || (!cmd->access(vid.uid, vid.gid, X_OK | W_OK)))) {\n      if (!errno) {\n        errc = EPERM;\n      }\n    } else { // Only root can delete a detached replica\n      if (vid.uid) {\n        errc = EPERM;\n      }\n    }\n\n    if (errc) {\n      return Emsg(epname, error, errc, \"verify stripe fid=\",\n                  std::to_string(fid).c_str());\n    }\n\n    // Get extended attributes if parent container exists\n    if (cmd) {\n      eos::FileOrContainerMD item;\n      item.container = cmd;\n      gOFS->listAttributes(gOFS->eosView, item, attrmap, true);\n    }\n  }\n\n  int fst_port;\n  std::string fst_queue, fst_host;\n  {\n    eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n    auto* verify_fs = FsView::gFsView.mIdView.lookupByID(fsid);\n\n    if (!verify_fs) {\n      errc = EINVAL;\n      return Emsg(epname, error, ENOENT,\n                  \"verify stripe - filesystem does not exist fid=\",\n                  std::to_string(fid).c_str());\n    }\n\n    // @todo(esindril) only issue verify for booted filesystems\n    fst_queue = verify_fs->GetQueue();\n    fst_host = verify_fs->GetHost();\n    fst_port = verify_fs->getCoreParams().getLocator().getPort();\n  }\n  // Build the opaquestring contents\n  XrdOucString opaquestring = \"\";\n  opaquestring += \"&mgm.fid=\";\n  const std::string hex_fid = eos::common::FileId::Fid2Hex(fid);\n  opaquestring += hex_fid.c_str();\n  opaquestring += \"&mgm.manager=\";\n  opaquestring += gOFS->ManagerId.c_str();\n  opaquestring += \"&mgm.access=verify\";\n  opaquestring += \"&mgm.fsid=\";\n  opaquestring += std::to_string(fsid).c_str();\n\n  if (attrmap.count(\"user.tag\")) {\n    opaquestring += \"&mgm.container=\";\n    opaquestring += attrmap[\"user.tag\"].c_str();\n  }\n\n  XrdOucString sizestring = \"\";\n  opaquestring += \"&mgm.cid=\";\n  opaquestring += eos::common::StringConversion::GetSizeString(sizestring, cid);\n  opaquestring += \"&mgm.path=\";\n  XrdOucString safepath = ns_path.c_str();\n  eos::common::StringConversion::SealXrdPath(safepath);\n  opaquestring += safepath;\n  opaquestring += \"&mgm.lid=\";\n  opaquestring += lid;\n\n  if (!options.empty()) {\n    opaquestring += options.c_str();\n  }\n\n  std::string qreq = \"/?fst.pcmd=verify\";\n  qreq += opaquestring.c_str();\n  std::string qresp;\n\n  if (SendQuery(fst_host, fst_port, qreq, qresp)) {\n    eos_static_err(\"msg=\\\"unable to send verification message\\\" target=%s\",\n                   fst_queue.c_str());\n    errc = ECOMM;\n  } else {\n    errc = 0;\n  }\n\n  EXEC_TIMING_END(\"VerifyStripe\");\n\n  if (errc) {\n    return Emsg(epname, error, errc, \"verify stripe fid=\",\n                std::to_string(fid).c_str());\n  }\n\n  return SFS_OK;\n}\n\n/*----------------------------------------------------------------------------*/\n/*\n * @brief send a drop message to a file system for a given file\n *\n * @param path file name to drop stripe\n * #param file id of the file to drop stripe\n * @param error error object\n * @param vid virtual identity of the client\n * @param fsid filesystem id where to run the drop\n * @param forceRemove if true the stripe is immediately dropped\n *\n * @return SFS_OK if success otherwise SFS_ERROR\n *\n * The function requires POSIX W_OK & X_OK on the parent directory to succeed.\n */\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::_dropstripe(const char* path,\n                       eos::common::FileId::fileid_t fid,\n                       XrdOucErrInfo& error,\n                       eos::common::VirtualIdentity& vid,\n                       unsigned long fsid,\n                       bool forceRemove)\n{\n  using eos::common::StringConversion;\n  static const char* epname = \"dropstripe\";\n  eos_debug(\"msg=\\\"drop stripe\\\" path=\\\"%s\\\" fxid=%08llx fsid=%lu\",\n            path, fid, fsid);\n  gOFS->MgmStats.Add(\"DropStripe\", vid.uid, vid.gid, 1);\n  int errc = 0;\n  eos::IContainerMD::id_t cid = 0ull;\n  EXEC_TIMING_BEGIN(\"DropStripe\");\n\n  // Retrieve read locked file\n  try {\n    eos::IFileMDPtr fmd = nullptr;\n    eos::MDLocking::FileReadLockPtr fmd_rlock;\n\n    if (fid) {\n      fmd = gOFS->eosView->getFileMDSvc()->getFileMD(fid);\n    } else {\n      fmd = gOFS->eosView->getFile(path);\n      fmd_rlock = eos::MDLocking::readLock(fmd.get());\n      fid = fmd->getId(); // set in case we were called by path\n    }\n\n    cid = fmd->getContainerId();\n  } catch (eos::MDException& e) {\n    errc = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n              e.getMessage().str().c_str());\n    return Emsg(epname, error, errc, \"drop stripe\", path);\n  }\n\n  // Retrieve parent container and check permissions\n  try {\n    auto cmd = gOFS->eosView->getContainerMDSvc()->getContainerMD(cid);\n    errno = 0;\n\n    // only one operation, no need to readlock!\n    if (vid.token || (!cmd->access(vid.uid, vid.gid, X_OK | W_OK) && !errno)) {\n      errc = EPERM;\n      return Emsg(epname, error, errc, \"drop stripe\", path);\n    }\n  } catch (eos::MDException& e) {\n    // Missing parent container - only root can drop stripes in this case\n    if (vid.uid) {\n      errc = EPERM;\n      return Emsg(epname, error, errc, \"drop detached stripe\", path);\n    }\n  }\n\n  // Retrieve write locked file and modify it\n  try {\n    std::string locations;\n    eos::IFileMDPtr fmd = gOFS->eosView->getFileMDSvc()->getFileMD(fid);\n    eos::MDLocking::FileWriteLockPtr fmd_wlock = eos::MDLocking::writeLock(\n          fmd.get());\n\n    try {\n      locations = fmd->getAttribute(\"sys.fs.tracking\");\n    } catch (...) {\n      // ignore missing attribute\n    }\n\n    if (!forceRemove) {\n      // We only unlink the location\n      if (fmd->hasLocation(fsid)) {\n        fmd->unlinkLocation(fsid);\n        locations += \"-\";\n        locations += std::to_string(fsid);\n        fmd->setAttribute(\"sys.fs.tracking\",\n                          StringConversion::ReduceString(locations).c_str());\n        gOFS->eosView->updateFileStore(fmd.get());\n        eos_debug(\"msg=\\\"unlinking location\\\" fxid=%08llx fsid=%lu\", fid, fsid);\n      } else {\n        errc = ENOENT;\n        return Emsg(epname, error, errc, \"drop stripe\", path);\n      }\n    } else {\n      // Unlink and remove location by force\n      if (fmd->hasLocation(fsid)) {\n        fmd->unlinkLocation(fsid);\n        locations += \"-\";\n        locations += std::to_string(fsid);\n        fmd->setAttribute(\"sys.fs.tracking\",\n                          StringConversion::ReduceString(locations).c_str());\n      }\n\n      fmd->removeLocation(fsid);\n      gOFS->eosView->updateFileStore(fmd.get());\n      eos_debug(\"msg=\\\"unlinking and removing location\\\" fxid=%08llx fsid=%lu\",\n                fid, fsid);\n      fmd_wlock.reset(nullptr);\n      // eraseEntry is only needed if the fsview is inconsistent with the\n      // FileMD. It exists on the selected fsview, but not in the fmd locations.\n      // Very rare case but needs to be done outside the namespace lock as it\n      // might need to load the FileSystem view in memory!\n      gOFS->eosFsView->eraseEntry(fsid, fid);\n    }\n  } catch (eos::MDException& e) {\n    errc = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n              e.getMessage().str().c_str());\n    return Emsg(epname, error, errc, \"drop stripe\", path);\n  }\n\n  EXEC_TIMING_END(\"DropStripe\");\n  return SFS_OK;\n}\n\n/*----------------------------------------------------------------------------*/\n/*\n * @brief send a drop message to all filesystems where given file is located\n *\n * @param path file name to drop stripe\n * @param error error object\n * @param vid virtual identity of the client\n * @param forceRemove if true the stripe is immediately dropped\n *\n * @return SFS_OK if success otherwise SFS_ERROR\n *\n * The function requires POSIX W_OK & X_OK on the parent directory to succeed.\n */\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::_dropallstripes(const char* path,\n                           XrdOucErrInfo& error,\n                           eos::common::VirtualIdentity& vid,\n                           bool forceRemove)\n{\n  static const char* epname = \"dropallstripes\";\n  eos_debug(\"msg=\\\"drop all stripes\\\" path=\\\"%s\\\" force=%d\", path, forceRemove);\n  gOFS->MgmStats.Add(\"DropAllStripes\", vid.uid, vid.gid, 1);\n  int errc = 0;\n  EXEC_TIMING_BEGIN(\"DropAllStripes\");\n\n  // Retrieve parent container and check permissions\n  try {\n    eos::common::Path cpath(path);\n    eos::IContainerMDPtr cont = gOFS->eosView->getContainer(cpath.GetParentPath());\n    auto contLock = eos::MDLocking::readLock(cont.get());\n    errno = 0;\n\n    if (vid.token || (!cont->access(vid.uid, vid.gid, X_OK | W_OK) && !errno)) {\n      errc = EPERM;\n      return Emsg(epname, error, errc, \"drop stripe\", path);\n    }\n  } catch (eos::MDException& e) {\n    // Missing parent container\n    errc = EPERM;\n    return Emsg(epname, error, errc, \"drop detached stripe\", path);\n  }\n\n  // Retrieve write locked file and modify it\n  try {\n    eos::IFileMDPtr fmd = gOFS->eosView->getFile(path);\n    auto fmdLock = eos::MDLocking::writeLock(fmd.get());\n    eos::IFileMD::id_t fid = fmd->getId();\n\n    // If file only on tape then don't touch it\n    if ((fmd->getLocations().size() == 1) &&\n        fmd->hasLocation(eos::common::TAPE_FS_ID)) {\n      return SFS_OK;\n    }\n\n    for (auto location : fmd->getLocations()) {\n      if (location == eos::common::TAPE_FS_ID) {\n        continue;\n      }\n\n      if (!forceRemove) {\n        fmd->unlinkLocation(location);\n        eos_debug(\"msg=\\\"unlinking location\\\" fxid=%08llx fsid=%lu\",\n                  fid, location);\n      } else {\n        fmd->unlinkLocation(location);\n        fmd->removeLocation(location);\n        eos_debug(\"msg=\\\"unlinking and removing location\\\" fxid=%08llx fsid=%lu\",\n                  fid, location);\n      }\n    }\n\n    gOFS->eosView->updateFileStore(fmd.get());\n  } catch (eos::MDException& e) {\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n              e.getErrno(), e.getMessage().str().c_str());\n    return Emsg(epname, error, errc, \"drop all stripes\", path);\n  }\n\n  EXEC_TIMING_END(\"DropAllStripes\");\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Move file replica/stripe from source to target file system\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::_movestripe(const char* path,\n                       XrdOucErrInfo& error,\n                       eos::common::VirtualIdentity& vid,\n                       unsigned long sourcefsid,\n                       unsigned long targetfsid)\n{\n  EXEC_TIMING_BEGIN(\"MoveStripe\");\n  int retc = _replicatestripe(path, error, vid, sourcefsid, targetfsid, true);\n  EXEC_TIMING_END(\"MoveStripe\");\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Copy file replica/stripe from source to target file system\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::_copystripe(const char* path,\n                       XrdOucErrInfo& error,\n                       eos::common::VirtualIdentity& vid,\n                       unsigned long sourcefsid,\n                       unsigned long targetfsid)\n{\n  EXEC_TIMING_BEGIN(\"CopyStripe\");\n  int retc = _replicatestripe(path, error, vid, sourcefsid, targetfsid, false);\n  EXEC_TIMING_END(\"CopyStripe\");\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Copy file replica/stripe from source to target file system - by path\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::_replicatestripe(const char* path,\n                            XrdOucErrInfo& error,\n                            eos::common::VirtualIdentity& vid,\n                            unsigned long sourcefsid,\n                            unsigned long targetfsid,\n                            bool dropsource)\n{\n  static const char* epname = \"replicatestripe\";\n  std::shared_ptr<eos::IContainerMD> dh;\n  std::shared_ptr<eos::IFileMD> fmd;\n  int errc = 0;\n  EXEC_TIMING_BEGIN(\"ReplicateStripe\");\n  eos::common::Path cPath(path);\n  eos_debug(\"msg=\\\"replicate file\\\" path=\\\"%s\\\" src_fsid=%u dst_fsid=%u drop=%d\",\n            path, sourcefsid, targetfsid, dropsource);\n  {\n    eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n\n    try {\n      dh = gOFS->eosView->getContainer(cPath.GetParentPath());\n      dh = gOFS->eosView->getContainer(gOFS->eosView->getUri(dh.get()));\n    } catch (eos::MDException& e) {\n      dh.reset();\n      errc = e.getErrno();\n      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                e.getMessage().str().c_str());\n    }\n\n    // check permissions\n    errno = 0;\n\n    if (dh && (vid.token || (!dh->access(vid.uid, vid.gid, X_OK | W_OK)))) {\n      if (!errno) {\n        errc = EPERM;\n      }\n    }\n\n    if (errc) {\n      return Emsg(epname, error, errc, \"replicate stripe\", path);\n    }\n\n    // get the file\n    try {\n      fmd = gOFS->eosView->getFile(path);\n\n      if (fmd->hasLocation(sourcefsid)) {\n        if (fmd->hasLocation(targetfsid)) {\n          errc = EEXIST;\n        }\n      } else {\n        // this replica does not exist!\n        errc = ENODATA;\n      }\n    } catch (eos::MDException& e) {\n      fmd.reset();\n      errc = e.getErrno();\n      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                e.getMessage().str().c_str());\n    }\n  }\n\n  if (errc) {\n    return Emsg(epname, error, errc, \"replicate stripe\", path);\n  }\n\n  int retc = _replicatestripe(fmd.get(), path, error, vid, sourcefsid,\n                              targetfsid, dropsource);\n  EXEC_TIMING_END(\"ReplicateStripe\");\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Copy file replica/stripe from source to target file system - by FileMD\n//------------------------------------------------------------------------------\nint\nXrdMgmOfs::_replicatestripe(eos::IFileMD* fmd,\n                            const char* path,\n                            XrdOucErrInfo& error,\n                            eos::common::VirtualIdentity& vid,\n                            unsigned long sourcefsid,\n                            unsigned long targetfsid,\n                            bool dropsource)\n{\n  static const char* epname = \"replicatestripe\";\n  uint64_t fid = fmd->getId();\n  std::string app_tag = (dropsource ? \"MoveStripe\" : \"CopyStripe\");;\n  std::shared_ptr<DrainTransferJob> job {\n    new DrainTransferJob(fid, sourcefsid, targetfsid, {}, {}, dropsource, app_tag,\n    false, vid)};\n\n  if (!gOFS->mFidTracker.AddEntry(fid, TrackerType::Drain)) {\n    eos_err(\"msg=\\\"file already tracked\\\" fxid=%08llx\", fid);\n    return Emsg(epname, error, ETXTBSY, \"replicate stripe - file already \"\n                \"tracked \", std::to_string(fid).c_str());\n  } else {\n    gOFS->mDrainEngine.GetThreadPool().PushTask<void>([ = ] {\n      job->UpdateMgmStats();\n      job->DoIt();\n      job->UpdateMgmStats();\n    });\n  }\n\n  return SFS_OK;\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Touch.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Touch.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n\n#include \"fst/checksum/ChecksumPlugins.hh\"\n#include \"common/XattrCompat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/ofs/XrdMgmOfsFile.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"namespace/MDException.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"common/Constants.hh\"\n#include \"namespace/interface/IQuota.hh\"\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::_touch(const char* path,\n                  XrdOucErrInfo& error,\n                  eos::common::VirtualIdentity& vid,\n                  const char* ininfo,\n                  bool doLock,\n                  bool useLayout,\n                  bool truncate,\n                  size_t size,\n                  bool absorb,\n                  const char* linkpath,\n                  const char* xs_hex,\n                  std::string* errmsg)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief create(touch) a no-replica file in the namespace\n *\n * @param path file to touch\n * @param error error object\n * @param vid virtual identity of the client\n * @param ininfo CGI\n * @param doLock take the namespace lock\n * @param useLayout create a file using the layout an space policies\n * @param size to preset on the file\n * @param absorb - if true we will try to move the file into the FST tree without hardlinking it\n * @param link path - file to hardlink/symlink to the FST store - requires shared filesystem access\n * @param xs_hex - checksum value in hex format to register\n *\n * @return SFS_OK or SFS_ERROR\n */\n/*----------------------------------------------------------------------------*/\n{\n  EXEC_TIMING_BEGIN(\"Touch\");\n  eos_info(\"path=%s vid.uid=%u vid.gid=%u\", path, vid.uid, vid.gid);\n  gOFS->MgmStats.Add(\"Touch\", vid.uid, vid.gid, 1);\n  // Perform the actual deletion\n  errno = 0;\n  std::shared_ptr<eos::IFileMD> fmd;\n  bool existedAlready = false;\n\n  if (_access(path, W_OK, error, vid, ininfo)) {\n    return SFS_ERROR;\n  }\n\n  eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, path);\n  eos::common::RWMutexWriteLock lock;\n  std::string fullpath;\n  bool verify = true;\n  std::vector<eos::IFileMD::location_t> locations;\n  int linking = 0;\n  bool create_symlink = false;\n  bool create_hardlink = false;\n\n  if (doLock) {\n    lock.Grab(gOFS->eosViewRWMutex);\n  }\n\n  try {\n    gOFS->eosView->getContainer(path);\n    errno = EISDIR;\n    return SFS_ERROR;\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n              e.getErrno(), e.getMessage().str().c_str());\n  }\n\n  try {\n    fmd = gOFS->eosView->getFile(path);\n    existedAlready = true;\n    errno = 0;\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n              e.getErrno(), e.getMessage().str().c_str());\n  }\n\n  if ((absorb && truncate) || (absorb && !useLayout) ||\n      (linkpath && strlen(linkpath) && !useLayout)) {\n    error.setErrInfo(EINVAL,\n                     \"error: -a can not be combined with -0 and -n - a linkpath can only be combined with -a!\\n\");\n    eos_err(\"-a can not be combined with -0 and -n - a linkpath can only be combined with -a!\\n\");\n\n    if (errmsg) {\n      *errmsg +=\n        \"error: -a can not be combined with -0 and -n - a linkpath can only be combined with -a!\\n\";\n    }\n\n    return SFS_ERROR;\n  }\n\n  if ((absorb || (linkpath && strlen(linkpath))) && vid.uid) {\n    error.setErrInfo(EINVAL,\n                     \"error: external files can only be registered by the root user\\n\");\n    eos_err(\"external files can only be registered by the root user\\n\");\n\n    if (errmsg) {\n      *errmsg += \"error: external files can only be registered by the root user\\n\";\n    }\n\n    return SFS_ERROR;\n  }\n\n  // ----------------------------------------------------------------------------------\n  // for external filesystem registration:\n  // - if this is registration of an existing file, check if this was already adopted\n  // - check if we have write permission to create a hardlink\n  // - fallback to a symlink if we do cross-device registration\n  // ----------------------------------------------------------------------------------\n\n  if (linkpath && strlen(linkpath)) {\n    struct stat buf;\n\n    if (!::stat(linkpath, &buf)) {\n      if (!::access(linkpath, W_OK)) {\n        size = buf.st_size;\n        char xv[4096];\n\n        // check if target has already an EOS lfn\n        if (lgetxattr(linkpath, \"user.eos.lfn\", xv, sizeof(xv)) > 0) {\n          eos_static_err(\"file had already an EOS lfn path='%s'\", linkpath);\n          error.setErrInfo(EEXIST,\n                           \"error: file has already a registered LFN stored on the extended attributes\");\n\n          if (errmsg) {\n            *errmsg +=\n              \"error: file has already a registered LFN stored on the extended attributes\";\n          }\n\n          return SFS_ERROR;\n        }\n      } else {\n        eos_static_err(\"is not writable to us path='%s'\", linkpath);\n        error.setErrInfo(EPERM, \"error: provided path is not writable for the MGM\");\n\n        if (errmsg) {\n          *errmsg += \"error: provided path is not writable for the MGM\";\n        }\n\n        return SFS_ERROR;\n      }\n    } else {\n      eos_err(\"link path does not exist path='%s'\", linkpath);\n      error.setErrInfo(ENOENT,\n                       \"error: provided path is not accessible on the MGM or does not exist\");\n\n      if (errmsg) {\n        *errmsg +=\n          \"error: provided path is not accessible on the MGM or does not exist\";\n      }\n\n      return SFS_ERROR;\n    }\n  } else {\n    if (absorb) {\n      error.setErrInfo(EINVAL,\n                       \"error: link path has to be provided to absorb a file\");\n      eos_err(\"link path has to be provided to absorb a file\");\n\n      if (errmsg) {\n        *errmsg +=\n          \"error: when using -a to absorb a file you have to provide the source path\";\n      }\n\n      return SFS_ERROR;\n    }\n  }\n\n  try {\n    if (!fmd) {\n      if (useLayout) {\n        lock.Release();\n        XrdMgmOfsFile* file = new XrdMgmOfsFile(const_cast<char*>(vid.tident.c_str()));\n        XrdOucString opaque = ininfo;\n\n        if (file) {\n          int rc = file->open(&vid, path, SFS_O_RDWR | SFS_O_CREAT, 0755, nullptr,\n                              \"eos.bookingsize=0&eos.app=touch\");\n          error.setErrInfo(strlen(file->error.getErrText()) + 1,\n                           file->error.getErrText());\n\n          if (rc != SFS_REDIRECT) {\n            error.setErrCode(file->error.getErrInfo());\n            errno = file->error.getErrInfo();\n            eos_static_info(\"open failed\");\n            return SFS_ERROR;\n          }\n\n          delete file;\n        } else {\n          const char* emsg = \"allocate file object\";\n          error.setErrInfo(strlen(emsg) + 1, emsg);\n          error.setErrCode(ENOMEM);\n          return SFS_ERROR;\n        }\n\n        lock.Grab(gOFS->eosViewRWMutex);\n        fmd = gOFS->eosView->getFile(path);\n      } else {\n        fmd = gOFS->eosView->createFile(path, vid.uid, vid.gid);\n      }\n\n      // get the file\n      fmd->setCUid(vid.uid);\n      fmd->setCGid(vid.gid);\n      fmd->setCTimeNow();\n      fmd->setSize(0);\n      fullpath = gOFS->eosView->getUri(fmd.get());\n\n      if (linkpath && strlen(linkpath)) {\n        for (unsigned int i = 0; i < fmd->getNumLocation(); i++) {\n          const auto loc = fmd->getLocation(i);\n          locations.push_back(loc);\n\n          if (loc != 0 && loc != eos::common::TAPE_FS_ID) {\n            eos::common::FileSystem::fs_snapshot_t local_snapshot;\n            eos::mgm::FileSystem* local_fs = FsView::gFsView.mIdView.lookupByID(loc);\n            local_fs->SnapShotFileSystem(local_snapshot);\n            std::string local_path = eos::common::FileId::FidPrefix2FullPath(\n                                       eos::common::FileId::Fid2Hex(fmd->getId()).c_str(),\n                                       local_snapshot.mPath.c_str());\n\n            if (absorb) {\n              // try renaming\n              int rc = ::rename(linkpath, local_path.c_str());\n              fprintf(stderr, \"rename gave %d %d\\n\", rc, errno);\n\n              if (rc) {\n                linking = errno;\n\n                if (errmsg) {\n                  *errmsg += \"error: failed to rename path='\" + std::string(\n                               linkpath) + std::string(\"'\\n\");\n                }\n              } else {\n                eos_info(\"renamed '%s' => '%s'\", linkpath, local_path.c_str());\n\n                if (errmsg) {\n                  *errmsg += \"info: renamed '\";\n                  *errmsg += linkpath;\n                  *errmsg += \"' => '\";\n                  *errmsg += local_path.c_str();\n                  *errmsg += \"'\\n\";\n                }\n\n                linkpath = local_path.c_str();\n              }\n            } else {\n              // try with links\n              int rc = ::link(linkpath, local_path.c_str());\n\n              if (rc && (errno == EXDEV)) {\n                eos_info(\"cross-device registration detected - using symlink for path='%s'\",\n                         linkpath);\n\n                if (::symlink(linkpath, local_path.c_str())) {\n                  linking = errno;\n\n                  if (errmsg) {\n                    *errmsg += \"error: failed to create symlink for path='\" + std::string(\n                                 linkpath) + std::string(\"'\\n\");\n                  }\n                } else {\n                  create_symlink = true;\n                  eos_info(\"created symlink '%s' => '%s'\", local_path.c_str(), linkpath);\n\n                  if (errmsg) {\n                    *errmsg += \"info: created symlink '\";\n                  }\n\n                  if (errmsg) {\n                    *errmsg += local_path.c_str();\n                  }\n\n                  if (errmsg) {\n                    *errmsg += \"' => '\";\n                  }\n\n                  if (errmsg) {\n                    *errmsg += linkpath;\n                  }\n\n                  if (errmsg) {\n                    *errmsg += \"\\n\";\n                  }\n                }\n              } else {\n                create_hardlink = true;\n                eos_info(\"created hardlink '%s' => '%s'\", local_path.c_str(), linkpath);\n\n                if (errmsg) {\n                  *errmsg += \"info: created hardlink '\";\n                }\n\n                if (errmsg) {\n                  *errmsg += local_path.c_str();\n                }\n\n                if (errmsg) {\n                  *errmsg += \"' => '\";\n                }\n\n                if (errmsg) {\n                  *errmsg += linkpath;\n                }\n\n                if (errmsg) {\n                  *errmsg += \"\\n\";\n                }\n              }\n\n              if (create_hardlink) {\n                if (lsetxattr(linkpath, \"user.eos.lfn\",\n                              fullpath.c_str(), fullpath.length(), 0)) {\n                  eos_err(\"can not set user.eos.lfn extended attribute on: '%s'\", linkpath);\n\n                  if (errmsg) {\n                    *errmsg += \"error: cannot set user.eos.lfn extended attribute on :'\";\n                  }\n\n                  if (errmsg) {\n                    *errmsg += linkpath;\n                  }\n\n                  if (errmsg) {\n                    *errmsg += \"'\\n\";\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n\n    if (!linking && xs_hex) {\n      size_t out_sz;\n      unsigned long lid = fmd->getLayoutId();\n      std::string checksum_name = eos::common::LayoutId::GetChecksumString(lid);\n      auto xs_binary = eos::common::StringConversion::Hex2BinDataChar\n                       (std::string(xs_hex), out_sz, SHA256_DIGEST_LENGTH);\n\n      if (xs_binary == nullptr) {\n        if (errmsg) {\n          *errmsg += \"error: failed to store checksum extended attributes on '\";\n          *errmsg += linkpath;\n          *errmsg += \"'\\n\";\n        }\n      } else {\n        if (linkpath) {\n          if (lsetxattr(linkpath, \"user.eos.checksumtype\",\n                        checksum_name.c_str(), checksum_name.length(), 0) ||\n              lsetxattr(linkpath, \"user.eos.checksum\",\n                        xs_binary.get(), out_sz, 0)) {\n            if (errmsg) {\n              *errmsg += \"error: failed to store checksum extended attributes on '\";\n              *errmsg += linkpath;\n              *errmsg += \"'\\n\";\n            }\n          } else {\n            if (errmsg) {\n              *errmsg += \"info: stored checksum '\";\n              *errmsg += checksum_name.c_str();\n              *errmsg += \":\";\n              *errmsg += xs_hex;\n              *errmsg += \"' for linked path '\";\n              *errmsg += linkpath;\n              *errmsg += \"'\\n\";\n            }\n          }\n\n          // Store this checksum\n          eos::Buffer xs_buff;\n          xs_buff.putData(xs_binary.get(), SHA256_DIGEST_LENGTH);\n          fmd->setChecksum(xs_buff);\n          *errmsg += \"info: stored checksum '\";\n          *errmsg += checksum_name.c_str();\n          *errmsg += \":\";\n          *errmsg += xs_hex;\n          *errmsg += \"'\\n\";\n        }\n      }\n    }\n\n    fmd->setMTimeNow();\n    eos::IFileMD::ctime_t mtime;\n    fmd->getMTime(mtime);\n    fmd->setCTime(mtime);\n\n    if (truncate) {\n      fmd->setSize(0);\n    } else {\n      if (size) {\n        fmd->setSize(size);\n      }\n    }\n\n    // Store the birth time as an extended attribute if this is a creation\n    if (!existedAlready) {\n      char btime[256];\n      snprintf(btime, sizeof(btime), \"%lu.%lu\", mtime.tv_sec, mtime.tv_nsec);\n      fmd->setAttribute(\"sys.eos.btime\", btime);\n    }\n\n    if (create_hardlink) {\n      fmd->setAttribute(\"sys.hardlink.path\", linkpath);\n    }\n\n    if (create_symlink) {\n      fmd->setAttribute(\"sys.symlink.path\", linkpath);\n    }\n\n    if (absorb) {\n      fmd->setAttribute(\"sys.absorbed.path\", linkpath);\n    }\n\n    gOFS->eosView->updateFileStore(fmd.get());\n    unsigned long long cid = fmd->getContainerId();\n    std::shared_ptr<eos::IContainerMD> cmd =\n      gOFS->eosDirectoryService->getContainerMD(cid);\n    cmd->setMTime(mtime);\n    cmd->notifyMTimeChange(gOFS->eosDirectoryService);\n\n    // Check if there is any quota node to be updated\n    // Procedure already done if useLayout=true\n    if (!existedAlready && !useLayout) {\n      try {\n        eos::IQuotaNode* ns_quota = gOFS->eosView->getQuotaNode(cmd.get());\n\n        if (ns_quota) {\n          ns_quota->addFile(fmd.get());\n        }\n      } catch (const eos::MDException& eq) {\n        // no quota node\n      }\n    }\n\n    gOFS->eosView->updateContainerStore(cmd.get());\n    const eos::FileIdentifier fid = fmd->getIdentifier();\n    const eos::ContainerIdentifier did = cmd->getIdentifier();\n    const eos::ContainerIdentifier pdid = cmd->getParentIdentifier();\n\n    if (doLock) {\n      lock.Release();\n    }\n\n    gOFS->FuseXCastMD(fid, did, mtime, true);\n    gOFS->FuseXCastRefresh(did, pdid);\n    errno = 0;\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n              e.getErrno(), e.getMessage().str().c_str());\n  }\n\n  if (linking) {\n    errno = linking;\n  } else {\n    if (verify) {\n      XrdOucString options;\n\n      for (auto loc : locations) {\n        if (gOFS->_verifystripe(fullpath.c_str(), error, vid, loc, options.c_str())) {\n          // failed\n        }\n      }\n    }\n  }\n\n  if (errno) {\n    return Emsg(\"utimes\", error, errno, \"touch\", path);\n  }\n\n  EXEC_TIMING_END(\"Touch\");\n  return SFS_OK;\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Utimes.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Utimes.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::utimes(const char* inpath,\n                  struct timespec* tvp,\n                  XrdOucErrInfo& error,\n                  const XrdSecEntity* client,\n                  const char* ininfo)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief set change time for a given file/directory\n *\n * @param inpath path to set\n * @param tvp timespec structure\n * @param error error object\n * @client XRootD authentication object\n * @ininfo CGI\n *\n * @return SFS_OK if success otherwise SFS_ERROR\n */\n/*----------------------------------------------------------------------------*/\n{\n  static const char* epname = \"utimes\";\n  const char* tident = error.getErrUser();\n  // use a thread private vid\n  eos::common::VirtualIdentity vid;\n  EXEC_TIMING_BEGIN(\"IdMap\");\n  eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz,\n                              AOP_Update, inpath);\n  EXEC_TIMING_END(\"IdMap\");\n  NAMESPACEMAP;\n  BOUNCE_ILLEGAL_NAMES;\n  XrdOucEnv utimes_Env(ininfo);\n  AUTHORIZE(client, &utimes_Env, AOP_Update, \"set utimes\", inpath, error);\n  gOFS->MgmStats.Add(\"IdMap\", vid.uid, vid.gid, 1);\n  BOUNCE_NOT_ALLOWED;\n  ACCESSMODE_W;\n  MAYSTALL;\n  MAYREDIRECT;\n  return _utimes(path, tvp, error, vid, ininfo);\n}\n\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::_utimes(const char* path,\n                   struct timespec* tvp,\n                   XrdOucErrInfo& error,\n                   eos::common::VirtualIdentity& vid,\n                   const char* info)\n/*----------------------------------------------------------------------------*/\n/*\n * @brief set change time for a given file/directory\n *\n * @param path path to set\n * @param tvp timespec structure\n * @param error error object\n * @param vid virtual identity of the client\n * @param info CGI\n *\n * @return SFS_OK if success otherwise SFS_ERROR\n *\n * For directories this routine set's the modification\n * time to the specified modification time. For files it\n * set's the modification time.\n */\n/*----------------------------------------------------------------------------*/\n{\n  std::shared_ptr<eos::IContainerMD> cmd;\n  EXEC_TIMING_BEGIN(\"Utimes\");\n  gOFS->MgmStats.Add(\"Utimes\", vid.uid, vid.gid, 1);\n  eos_info(\"calling utimes for path=%s, uid=%i, gid=%i\", path, vid.uid, vid.gid);\n  // ---------------------------------------------------------------------------\n  eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n\n  if (gOFS->_access(path,\n                    W_OK,\n                    error,\n                    vid,\n                    info)) {\n    return SFS_ERROR;\n  }\n\n  try {\n    cmd = gOFS->eosView->getContainer(path, false);\n    cmd->setMTime(tvp[1]);\n    cmd->notifyMTimeChange(gOFS->eosDirectoryService);\n    eosView->updateContainerStore(cmd.get());\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_err(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n            e.getErrno(), e.getMessage().str().c_str());\n  }\n\n  if (!cmd) {\n    std::shared_ptr<eos::IFileMD> fmd;\n\n    try {\n      fmd = gOFS->eosView->getFile(path, false);\n\n      // Set the ctime only if different from 0.0\n      if (tvp[0].tv_sec != 0 || tvp[0].tv_nsec != 0) {\n        fmd->setCTime(tvp[0]);\n      }\n\n      fmd->setMTime(tvp[1]);\n      eosView->updateFileStore(fmd.get());\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                e.getErrno(), e.getMessage().str().c_str());\n    }\n  }\n\n  EXEC_TIMING_END(\"Utimes\");\n  return SFS_OK;\n}\n"
  },
  {
    "path": "mgm/ofs/cmds/Version.inc",
    "content": "// ----------------------------------------------------------------------\n// File: Version.inc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n// -----------------------------------------------------------------------\n// This file is included source code in XrdMgmOfs.cc to make the code more\n// transparent without slowing down the compilation time.\n// -----------------------------------------------------------------------\n\n/*----------------------------------------------------------------------------*/\n/*\n * @brief handles file versioning for fid\n *\n * @param fid id of the file to version\n * @param error object\n * @param vid virtual identity of the caller\n * @param max_versions the maximum number of version to keep\n * @param versionedpath return variable for the full path to the latest version file\n * @return SFS_OK if successfully send otherwise SFS_ERROR\n *\n * Versions are kept in a hidden directory .<name>/<ctime>owned by root\n */\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::Version(eos::common::FileId::fileid_t fid,\n                   XrdOucErrInfo& error,\n                   eos::common::VirtualIdentity& vid,\n                   int max_versions,\n                   XrdOucString* versionedpath,\n                   bool simulate)\n{\n  static const char* epname = \"version\";\n  EXEC_TIMING_BEGIN(\"Version\");\n  gOFS->MgmStats.Add(\"Versioning\", vid.uid, vid.gid, 1);\n  std::shared_ptr<eos::IFileMD> fmd;\n  std::string path;\n  std::string vpath;\n  std::string bname;\n  std::string versionpath;\n  eos::common::VirtualIdentity fidvid = vid;\n  eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n  time_t filectime = 0;\n  {\n    eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n\n    try {\n      fmd = gOFS->eosFileService->getFileMD(fid);\n      path = gOFS->eosView->getUri(fmd.get()).c_str();\n      eos::common::Path cPath(path.c_str());\n      bool noversion;\n      cPath.DecodeAtomicPath(noversion);\n      vpath = cPath.GetParentPath();\n      bname = cPath.GetName();\n      fidvid.uid = fmd->getCUid();\n      fidvid.gid = fmd->getCGid();\n      fidvid.allowed_gids.insert(fidvid.gid);\n      eos::IFileMD::ctime_t ctime;\n      fmd->getCTime(ctime);\n      filectime = (time_t) ctime.tv_sec;\n    } catch (eos::MDException& e) {\n      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                e.getMessage().str().c_str());\n      errno = e.getErrno();\n      std::string errmsg = \"translate file id - \";\n      errmsg = +e.getMessage().str().c_str();\n      return Emsg(epname, error, errno, errmsg.c_str(), path.c_str());\n    }\n  }\n\n  if ((fidvid.uid != vid.uid) && (vid.uid)) {\n    return Emsg(epname, error, EPERM,\n                \"create version - you are not the owner of this file\", path.c_str());\n  }\n\n  vpath += \".sys.v#.\";\n  vpath += bname;\n  versionpath = vpath;\n  versionpath += \"/\";\n  {\n    char vci[128];\n    snprintf(vci, sizeof(vci) - 1, \"%llu.%08llx\", (unsigned long long) filectime,\n             (unsigned long long) fid);\n    versionpath += vci;\n\n    // return the latest version name\n    if (versionedpath) {\n      *versionedpath = versionpath.c_str();\n    }\n  }\n  // Check if .version directory exists, if not create it\n  struct stat buf;\n\n  if (gOFS->_stat(vpath.c_str(), &buf, error, fidvid, 0, 0)) {\n    eos_info(\"msg=\\\"creating version directory\\\" version-directory=\\\"%s\\\"\",\n             vpath.c_str());\n\n    if (gOFS->_mkdir(vpath.c_str(), 0, error, fidvid, (const char*) 0, nullptr,\n                     true)) {\n      return Emsg(epname, error, errno, \"create version directory\", vpath.c_str());\n    }\n\n    if (gOFS->_stat(vpath.c_str(), &buf, error, fidvid, 0, 0)) {\n      return Emsg(epname, error, errno, \"stat version directory\", vpath.c_str());\n    }\n\n    // make sure, the owner can write into the version directory\n    XrdSfsMode chmod_mode = buf.st_mode | S_IRWXU;\n\n    if (gOFS->_chmod(vpath.c_str(), chmod_mode, error, rootvid, (const char*) 0)) {\n      return Emsg(epname, error, errno, \"chmod version directory\", vpath.c_str());\n    }\n  }\n\n  // Rename to the version directory target\n  if ((!gOFS->_stat(vpath.c_str(), &buf, error, fidvid, 0, 0)) && (!simulate) &&\n      gOFS->_rename(path.c_str(), versionpath.c_str(), error, fidvid, 0, 0, false,\n                    false)) {\n    return Emsg(epname, error, errno, \"version file\", path.c_str());\n  }\n\n  // Purge versions according to policy\n  if (max_versions > 0) {\n    if (gOFS->PurgeVersion(vpath.c_str(), error, max_versions)) {\n      return Emsg(epname, error, errno, \"purge versions\", path.c_str());\n    }\n  }\n\n  if (!simulate) {\n    eos_info(\"msg=\\\"new version created\\\" previous-path=\\\"%s\\\" version-path=\\\"%s\\\"\",\n             path.c_str(), versionpath.c_str());\n  } else {\n    eos_info(\"msg=\\\"new version simulated\\\" previous-path=\\\"%s\\\" version-path=\\\"%s\\\"\",\n             path.c_str(), versionpath.c_str());\n  }\n\n  EXEC_TIMING_END(\"Versioning\");\n  return SFS_OK;\n}\n\n/*----------------------------------------------------------------------------*/\n/*\n * @brief purge oldest versions exceeding max_versions\n *\n * @param versiondir directory where versions live\n * @param max_versions maximum number of versions to keep\n *\n * @return SFS_OK if success otherwise SFS_ERROR and might set errno\n *\n * If max_versions=0 it will remove all versions and the version directory!\n * If max_versions=-1 it will read the attribute sys.versioning of the parent\n * directory and apply the setting.\n * If max_versions=-2 it will read the attribute sys.versioning of the parent\n * directory and apply the setting-1 .\n *\n * The caller needs to have the quota mutex read locked (gQuoatMutex).\n */\n/*----------------------------------------------------------------------------*/\nint\nXrdMgmOfs::PurgeVersion(const char* versiondir,\n                        XrdOucErrInfo& error,\n                        int max_versions)\n{\n  eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n  eos_info(\"version-dir=%s max-versions=%d\", versiondir, max_versions);\n\n  if (!versiondir) {\n    errno = EINVAL;\n    return SFS_ERROR;\n  }\n\n  std::string path = versiondir;\n\n  if (max_versions < 0) {\n    // Indicates that we should read the max version depth from the parent attributes\n    eos::common::Path cPath(versiondir);\n    // Get the attributes and call the verify function\n    eos::IContainerMD::XAttrMap map;\n\n    if (gOFS->_attr_ls(cPath.GetParentPath(), error, rootvid, (const char*) 0,\n                       map)) {\n      return SFS_ERROR;\n    }\n\n    if (map.count(\"sys.versioning\")) {\n      max_versions = atoi(map[\"sys.versioning\"].c_str());\n    } else {\n      return SFS_OK;\n    }\n  }\n\n  XrdMgmOfsDirectory directory;\n  int listrc = directory.open(versiondir, rootvid, (const char*) 0);\n  eos_info(\"listrc=%d max-version=%d\", listrc, max_versions);\n\n  if (!listrc && !max_versions) {\n    // We use the rm -r proc function to do the clean-up to have the recycle\n    // functionality involved for version directories\n    ProcCommand Cmd;\n    //info=eos.rgid=0&eos.ruid=0&mgm.cmd=rm&mgm.option=r&mgm.path=\n    XrdOucString info = \"mgm.cmd=rm&mgm.option=r&mgm.path=\";\n    info += path.c_str();\n    Cmd.open(\"/proc/user\", info.c_str(), rootvid, &error);\n    Cmd.close();\n\n    if (Cmd.GetRetc()) {\n      return SFS_ERROR;\n    } else {\n      return SFS_OK;\n    }\n  }\n\n  int success = 0;\n  std::map<uint64_t, std::string> version_by_age;\n  std::vector<std::string> versions;\n\n  if (!listrc) {\n    const char* val = 0;\n    time_t now = time(NULL);\n\n    while ((val = directory.nextEntry())) {\n      std::string entryname = val;\n\n      if ((entryname == \".\") || (entryname == \"..\")) {\n        continue;\n      }\n\n      versions.push_back(entryname);\n      // get age information for this entry\n      std::string mtime, inode;\n      eos::common::StringConversion::SplitKeyValue(entryname, mtime, inode, \".\");\n      uint64_t stmtime = strtoull(mtime.c_str(), 0, 10);\n      ssize_t age = (ssize_t)(now) - stmtime;\n\n      if (age > 0) {\n        version_by_age[age] = entryname;\n      }\n    }\n\n    const uint64_t age_bins[12] = { 0, 86400 * 1, 86400 * 2, 86400 * 3, 86400 * 4, 86400 * 5, 86400 * 6, 86400 * 7,\n                                    86400 * 14, 86400 * 21, 86400 * 28, 0xffffffffffffffff\n                                  };\n    std::map<int, int> age_map;\n    std::set<std::string> keep_set;\n\n    for (auto x = version_by_age.rbegin();  x != version_by_age.rend(); x++) {\n      for (size_t i = 0; i < 12 ; ++i) {\n        if (EOS_LOGS_DEBUG) {\n          eos_static_debug(\"bin %llu\", age_bins[i]);\n        }\n\n        if ((x->first >= age_bins[i]) &&\n            (x->first < age_bins[i + 1])) {\n          if (EOS_LOGS_DEBUG) {\n            eos_static_info(\"map %lu %lu\", x->first, i);\n          }\n\n          if (age_map[i] == 0) {\n            // mark the olderst version in a bin to keep\n            keep_set.insert(x->second);\n          }\n\n          age_map[i]++;\n        }\n      }\n    }\n\n    if (EOS_LOGS_DEBUG) {\n      for (size_t i = 0 ; i < 12 - 1 ; ++i) {\n        eos_static_info(\"age: < %lu days : %lu\", age_bins[i + 1] / 86400, age_map[i]);\n      }\n    }\n\n    // if we have more versions than defined, clean the ones, which can be cleaned\n    if ((int) versions.size() > max_versions) {\n      for (size_t i = 0; i <= (versions.size() - max_versions); ++i) {\n        if (keep_set.count(versions[i])) {\n          continue;\n        }\n\n        std::string deletionpath = path;\n        deletionpath += \"/\";\n        deletionpath += versions[i];\n        success |= gOFS->_rem(deletionpath.c_str(), error, rootvid, (const char*) 0,\n                              false, false);\n      }\n    }\n\n    if (success == SFS_OK) {\n      eos_info(\"dir=\\\"%s\\\" msg=\\\"purging ok\\\" old-versions=%d new-versions=%d\",\n               versiondir, versions.size(), max_versions);\n    } else {\n      eos_err(\"dir=\\\"%s\\\" msg=\\\"purging failed\\\" versions=%d\", versiondir,\n              versions.size());\n    }\n  } else {\n    success = SFS_ERROR;\n  }\n\n  return success;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/Access.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Access.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n\n//----------------------------------------------------------------------------\n// Check access rights\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::Access(const char* path,\n                  const char* ininfo,\n                  XrdOucEnv& env,\n                  XrdOucErrInfo& error,\n                  eos::common::VirtualIdentity& vid,\n                  const XrdSecEntity* client)\n{\n  ACCESSMODE_R;\n  MAYSTALL;\n  const char* inpath = path;\n  MAYREDIRECT;\n  gOFS->MgmStats.Add(\"Fuse-Access\", vid.uid, vid.gid, 1);\n  char* smode = env.Get(\"mode\");\n  int retc = 0;\n\n  if (smode) {\n    int newmode = atoi(smode);\n\n    if (access(path, newmode, error, client, 0)) {\n      retc = error.getErrInfo();\n    }\n  } else {\n    retc = EINVAL;\n  }\n\n  XrdOucString response = \"access: retc=\";\n  response += retc;\n  error.setErrInfo(response.length() + 1, response.c_str());\n  return SFS_DATA;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/AdjustReplica.cc",
    "content": "// ----------------------------------------------------------------------\n// File: AdjustReplica.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n\n//----------------------------------------------------------------------------\n// Adjust replica (repairOnClose from FST)\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::AdjustReplica(const char* path,\n                         const char* ininfo,\n                         XrdOucEnv& env,\n                         XrdOucErrInfo& error,\n                         eos::common::VirtualIdentity& vid,\n                         const XrdSecEntity* client)\n{\n  static const char* epname = \"AdjustReplica\";\n  REQUIRE_SSS_OR_LOCAL_AUTH;\n  ACCESSMODE_W;\n  MAYSTALL;\n  const char* inpath = path;\n  MAYREDIRECT;\n  EXEC_TIMING_BEGIN(\"AdjustReplica\");\n  // TODO(gbitzes): If vid is replaced with root, why is it even a parameter?!\n  vid = eos::common::VirtualIdentity::Root();\n  // Execute a proc command\n  ProcCommand Cmd;\n  XrdOucString info = \"mgm.cmd=file&mgm.subcmd=adjustreplica&mgm.path=\";\n  char* spath = env.Get(\"mgm.path\");\n\n  if (spath) {\n    info += spath;\n    info += \"&mgm.format=fuse\";\n    Cmd.open(\"/proc/user\", info.c_str(), vid, &error);\n    Cmd.close();\n    gOFS->MgmStats.Add(\"AdjustReplica\", 0, 0, 1);\n\n    if (Cmd.GetRetc()) {\n      eos_thread_err(\"msg=\\\"adjustreplica failed\\\" path=\\\"%s\\\"\", spath);\n      return Emsg(epname, error, EIO, \"repair [EIO]\", spath);\n    }\n  } else {\n    eos_thread_err(\"msg=\\\"adjustreplica failed - no given path\\\"\");\n    return Emsg(epname, error, EIO, \"repair [EIO]\", \"no path\");\n  }\n\n  eos_thread_debug(\"msg=\\\"adjustreplica succeeded\\\" path=%s\", spath);\n  const char* ok = \"OK\";\n  error.setErrInfo(strlen(ok) + 1, ok);\n  EXEC_TIMING_END(\"AdjustReplica\");\n  return SFS_DATA;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/Checksum.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Checksum.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"namespace/Resolver.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/utils/Checksum.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n#include <openssl/sha.h>\n\n//----------------------------------------------------------------------------\n// Check access rights\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::Checksum(const char* path,\n                    const char* ininfo,\n                    XrdOucEnv& env,\n                    XrdOucErrInfo& error,\n                    eos::common::VirtualIdentity& vid,\n                    const XrdSecEntity* client)\n{\n  ACCESSMODE_R_MASTER;\n  MAYSTALL;\n  const char* inpath = path;\n  MAYREDIRECT;\n  gOFS->MgmStats.Add(\"Fuse-Checksum\", vid.uid, vid.gid, 1);\n  XrdOucString checksum = \"\";\n  std::shared_ptr<eos::IFileMD> fmd;\n  int retc = 0;\n  bool fuse_readable = env.Get(\"mgm.option\") ? (std::string(\n                         env.Get(\"mgm.option\")) == \"fuse\") ? true : false : false;\n  eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n  XrdOucString spath = path;\n  unsigned long byfid = eos::Resolver::retrieveFileIdentifier(\n                          spath).getUnderlyingUInt64();\n\n  try {\n    if (byfid) {\n      fmd = gOFS->eosFileService->getFileMD(byfid);\n    } else {\n      fmd = gOFS->eosView->getFile(path);\n    }\n\n    size_t xs_length = SHA256_DIGEST_LENGTH;\n\n    if (fuse_readable) {\n      xs_length = eos::common::LayoutId:: GetChecksumLen(fmd->getLayoutId());\n    }\n\n    eos::appendChecksumOnStringAsHex(fmd.get(), checksum, 0x00, xs_length);\n  } catch (eos::MDException& e) {\n    eos_thread_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                     e.getErrno(), e.getMessage().str().c_str());\n    errno = e.getErrno();\n    retc = errno;\n  }\n\n  XrdOucString response = \"checksum: \";\n  response += checksum;\n  response += \" retc=\";\n  response += retc;\n  error.setErrInfo(response.length() + 1, response.c_str());\n  return SFS_DATA;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/Chmod.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Chmod.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n\n//----------------------------------------------------------------------------\n// Chmod of a directory\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::Chmod(const char* path,\n                 const char* ininfo,\n                 XrdOucEnv& env,\n                 XrdOucErrInfo& error,\n                 eos::common::VirtualIdentity& vid,\n                 const XrdSecEntity* client)\n{\n  ACCESSMODE_W;\n  MAYSTALL;\n  const char* inpath = path;\n  MAYREDIRECT;\n  gOFS->MgmStats.Add(\"Fuse-Chmod\", vid.uid, vid.gid, 1);\n  const char* smode = env.Get(\"mode\");\n  int retc = 0;\n\n  if (smode) {\n    XrdSfsMode newmode = atoi(smode);\n\n    if (_chmod(path, newmode, error, vid)) {\n      retc = error.getErrInfo();\n    }\n  } else {\n    retc = EINVAL;\n  }\n\n  XrdOucString response = \"chmod: retc=\";\n  response += retc;\n  error.setErrInfo(response.length() + 1, response.c_str());\n  return SFS_DATA;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/Chown.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Chown.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n\n//----------------------------------------------------------------------------\n// Chown of a file or directory\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::Chown(const char* path,\n                 const char* ininfo,\n                 XrdOucEnv& env,\n                 XrdOucErrInfo& error,\n                 eos::common::VirtualIdentity& vid,\n                 const XrdSecEntity* client)\n{\n  ACCESSMODE_W;\n  MAYSTALL;\n  const char* inpath = path;\n  MAYREDIRECT;\n  gOFS->MgmStats.Add(\"Fuse-Chown\", vid.uid, vid.gid, 1);\n  char* suid = env.Get(\"uid\");\n  char* sgid = env.Get(\"gid\");\n  int retc = 0;\n\n  if (suid && sgid) {\n    uid_t uid = (uid_t) atoi(suid);\n    uid_t gid = (uid_t) atoi(sgid);\n\n    if (_chown(path, uid, gid, error, vid)) {\n      retc = error.getErrInfo();\n    }\n  } else {\n    retc = EINVAL;\n  }\n\n  XrdOucString response = \"chown: retc=\";\n  response += retc;\n  error.setErrInfo(response.length() + 1, response.c_str());\n  return SFS_DATA;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/Commit.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Commit.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"common/LayoutId.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/tracker/ReplicationTracker.hh\"\n#include \"mgm/ofs/fsctl/CommitHelper.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"proto/Audit.pb.h\"\n#include \"namespace/utils/Checksum.hh\"\n#include \"mgm/misc/AuditHelpers.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n#include <openssl/sha.h>\n\n\n//----------------------------------------------------------------------------\n// Commit a replica\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::Commit(const char* path,\n                  const char* ininfo,\n                  XrdOucEnv& env,\n                  XrdOucErrInfo& error,\n                  eos::common::VirtualIdentity& vid,\n                  const XrdSecEntity* client)\n{\n  static const char* epname = \"Commit\";\n  REQUIRE_SSS_OR_LOCAL_AUTH;\n  ACCESSMODE_W;\n  MAYSTALL;\n  const char* inpath = path;\n  MAYREDIRECT;\n  EXEC_TIMING_BEGIN(\"Commit\");\n  // Checksum string\n  char binchecksum[SHA256_DIGEST_LENGTH];\n  // Process CGI parameters\n  CommitHelper::cgi_t cgi;\n  CommitHelper::grab_cgi(env, cgi);\n\n  // Initialize logging\n  if (cgi.count(\"logid\")) {\n    tlLogId.SetLogId(cgi[\"logid\"].c_str(), error.getErrUser());\n  }\n\n  // OC parameters\n  CommitHelper::param_t params;\n  params[\"oc_n\"] = 0;\n  params[\"oc_max\"] = 0;\n  // Selected options\n  CommitHelper::option_t option;\n  CommitHelper::set_options(option, cgi);\n  // Check 'path' parameter\n  CommitHelper::path_t paths;\n  paths[\"atomic\"] = std::string(\"\");\n\n  if (cgi.count(\"path\")) {\n    paths[\"commit\"] = cgi[\"path\"];\n  }\n\n  // Extract all OC upload relevant parameters\n  CommitHelper::init_oc(env, cgi, option, params);\n\n  if (CommitHelper::is_reconstruction(option)) {\n    // Remove checksum in case of a chunk reconstruction\n    // (they have to be ignored)\n    cgi[\"checksum\"] = \"\";\n  }\n\n  if (cgi[\"checksum\"].length()) {\n    // Compute binary checksum\n    CommitHelper::hex2bin_checksum(cgi[\"checksum\"], binchecksum);\n  }\n\n  // Check all commit required parameters are defined\n  if (CommitHelper::check_commit_params(cgi)) {\n    // Convert the main CGI parameters into numbers\n    unsigned long long size = std::stoull(cgi[\"size\"]);\n    unsigned long long fid = strtoull(cgi[\"fid\"].c_str(), 0, 16);\n    unsigned long fsid = std::stoul(cgi[\"fsid\"]);\n    unsigned long mtime = std::stoul(cgi[\"mtime\"]);\n    unsigned long mtimens = std::stoul(cgi[\"mtimensec\"]);\n    std::string emsg;\n    CommitHelper::log_info(vid, tlLogId, cgi, option, params);\n    int rc = CommitHelper::check_filesystem(vid, fsid, cgi, option,\n                                            params, emsg);\n\n    if (rc) {\n      return Emsg(epname, error, rc, emsg.c_str(), \"\");\n    }\n\n    // Create a checksum buffer object\n    eos::Buffer checksumbuffer;\n    checksumbuffer.putData(binchecksum, SHA256_DIGEST_LENGTH);\n    // Attempt file meta data retrieval\n    std::shared_ptr<eos::IFileMD> fmd;\n    eos::IContainerMD::id_t cid = 0;\n    std::string fmdname;\n    {\n      eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, fid);\n      // Keep the lock order View => Namespace => Quota\n      eos::common::RWMutexWriteLock ns_wr_lock(gOFS->eosViewRWMutex);\n      errno = 0;\n\n      try {\n        fmd = gOFS->eosFileService->getFileMD(fid);\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        eos_thread_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\",\n                         e.getErrno(), e.getMessage().str().c_str());\n        emsg = \"retc=\";\n        emsg += e.getErrno();\n        emsg += \" msg=\";\n        emsg += e.getMessage().str().c_str();\n      }\n\n      if (!fmd) {\n        if (errno == ENOENT) {\n          return Emsg(epname, error, ENOENT,\n                      \"commit filesize change - file is already removed [EIDRM]\", \"\");\n        }\n\n        emsg.insert(0, \"commit filesize change [EIO]\");\n        return Emsg(epname, error, errno, emsg.c_str(), cgi[\"path\"].c_str());\n      }\n\n      unsigned long lid = fmd->getLayoutId();\n\n      // Check if fsid and fid are ok\n      if (fmd->getId() != fid) {\n        eos_thread_notice(\"commit for fxid=%08llx != fmd_fxid=%08llx\",\n                          fid, fmd->getId());\n        gOFS->MgmStats.Add(\"CommitFailedFid\", 0, 0, 1);\n        return Emsg(epname, error, EINVAL,\n                    \"commit filesize change - file id is wrong [EINVAL]\",\n                    cgi[\"path\"].c_str());\n      }\n\n      // Check if file is already unlinked from the visible namespace\n      if (!(cid = fmd->getContainerId())) {\n        eos_thread_debug(\"commit for fxid=%08llx but file is disconnected \"\n                         \"from any container\", fmd->getId());\n        gOFS->MgmStats.Add(\"CommitFailedUnlinked\", 0, 0, 1);\n        return Emsg(epname, error, EIDRM,\n                    \"commit filesize change - file is already removed [EIDRM]\", \"\");\n      }\n\n      // Check if we have this replica in the unlink list or not linked list, if yes, the commit has to be suppressed\n      if (option[\"fusex\"] &&\n          (fmd->hasUnlinkedLocation((unsigned int) fsid) ||\n           (!fmd->hasLocation((unsigned int) fsid)))) {\n        eos_thread_err(\"suppressing possible recovery replica for fxid=%08llx \"\n                       \"on unlinked/not linked fsid=%llu - rejecting replica\",\n                       fmd->getId(), fsid);\n        // This happens when a FUSEX recovery has been triggered.\n        // To avoid to reattach replicas, we clean them up here\n        return Emsg(epname, error, EBADE,\n                    \"commit replica - file size is wrong [EBADE] \"\n                    \"- suppressing recovery replica\", \"\");\n      }\n\n      // Check if commit comes from a replication procedure\n      // and if the size/checksum is ok\n      if (option[\"replication\"]) {\n        CommitHelper::remove_scheduler(fid);\n\n        if (eos::common::LayoutId::GetLayoutType(lid) ==\n            eos::common::LayoutId::kReplica) {\n          // We check filesize and the checksum only for replica layouts\n          eos_thread_debug(\"fmd_size=%llu, size=%lli\", fmd->getSize(), size);\n\n          // Validate size parameters\n          if (!CommitHelper::validate_size(vid, fmd, fsid, size, option)) {\n            return Emsg(epname, error, EBADE,\n                        \"commit replica - file size is wrong [EBADE]\", \"\");\n          }\n\n          // Validate checksum parameters\n          if (option[\"verifychecksum\"] &&\n              !CommitHelper::validate_checksum(vid, fmd, checksumbuffer,\n                                               fsid, option)) {\n            return Emsg(epname, error, EBADR,\n                        \"commit replica - file checksum is wrong [EBADR]\", \"\");\n          }\n        }\n      }\n\n      if (option[\"verifysize\"]) {\n        // Check if a file size change was detected\n        if (fmd->getSize() != size) {\n          eos_thread_err(\"commit for fxid=%08llx gave a file size change after \"\n                         \"verification on fsid=%llu\", fmd->getId(), fsid);\n        }\n      }\n\n      if (option[\"verifychecksum\"]) {\n        CommitHelper::log_verifychecksum(vid, fmd, checksumbuffer, fsid,\n                                         cgi, option);\n      }\n\n      if (!CommitHelper::handle_location(vid, cid, fmd, fsid, size,\n                                         cgi, option)) {\n        return Emsg(epname, error, EIDRM,\n                    \"commit file, parent container removed [EIDRM]\", \"\");\n      }\n\n      if (cgi[\"altxs\"].length()) {\n        std::vector<std::string> tkns;\n        std::vector<std::string> name2xs;\n        eos::common::StringConversion::Tokenize(cgi[\"altxs\"], tkns, \",\");\n        fmd->clearAltXs();\n\n        for (const auto& tkn : tkns) {\n          name2xs.clear();\n          eos::common::StringConversion::Tokenize(tkn, name2xs, \":\");\n\n          if (name2xs.size() != 2) {\n            // the entry is not valid\n            continue;\n          }\n\n          auto checksumType = eos::common::LayoutId::GetChecksumFromString(name2xs[0]);\n          auto xs = name2xs[1];\n          fmd->addAltXs(static_cast<eos::common::LayoutId::eChecksum>\n                        (checksumType), xs.c_str(), xs.size());\n        }\n      }\n\n      if (option[\"fusex\"]) {\n        std::string fusexstate;\n\n        try {\n          fusexstate = fmd->getAttribute(\"sys.fusex.state\");\n        } catch (...) {}\n\n        if (option[\"update\"] || option[\"replication\"]) {\n          fusexstate += \"+\";\n          fusexstate += std::to_string(fsid);\n        }\n\n        if (eos::common::LayoutId::GetChecksum(lid) != eos::common::LayoutId::kNone) {\n          if (option[\"commitsize\"]) {\n            fusexstate += \"s|\";\n          }\n        } else {\n          if (option[\"commitsize\"]) {\n            fusexstate += \"s\";\n          }\n        }\n\n        if (option[\"commitchecksum\"]) {\n          fusexstate += \"c|\";\n        }\n\n        if (option[\"verifychecksum\"]) {\n          fusexstate += \"v|\";\n        }\n\n        if (option[\"verifysize\"]) {\n          fusexstate += \"V|\";\n        }\n\n        fmd->setAttribute(\"sys.fusex.state\", fusexstate);\n      }\n\n      // Advance oc upload parameters if concerned\n      CommitHelper::handle_occhunk(vid, fmd, option, params);\n      // Set checksum if concerned\n      CommitHelper::handle_checksum(vid, fmd, option, checksumbuffer);\n      fmdname = fmd->getName();\n      paths[\"atomic\"].Init(fmdname.c_str());\n      paths[\"atomic\"].DecodeAtomicPath(option[\"versioning\"]);\n      option[\"atomic\"] = (paths[\"atomic\"].GetName() != fmdname);\n\n      if (option[\"commitverify\"]) {\n        // disable atomic and versioning functionality for commits originated by \"verify --commitxyz\"\n        option[\"atomic\"] = false;\n        option[\"versioning\"] = false;\n      }\n\n      eos::audit::Stat beforeStat;\n      if (option[\"update\"]) {\n        // Capture before state (size and times)\n        eos::mgm::auditutil::buildStatFromFileMD(fmd, beforeStat, /*includeSize=*/true, /*includeChecksum=*/true, /*includeNs=*/true);\n      }\n\n      if (option[\"update\"] && mtime) {\n        // Update the modification time only if the file contents changed and\n        // mtime != 0\n        // - FUSE clients will commit mtime=0 to indicate they call utimes anyway\n        // - OC clients set the mtime during a commit\n        if (!option[\"atomic\"] || option[\"occhunk\"]) {\n          eos::IFileMD::ctime_t mt;\n          mt.tv_sec = mtime;\n          mt.tv_nsec = mtimens;\n          fmd->setMTime(mt);\n        }\n      }\n\n      eos_thread_debug(\"commit: setting size to %llu\", fmd->getSize());\n      eos::ContainerIdentifier p_ident;\n\n      if (!CommitHelper::commit_fmd(vid, cid, fmd, size, option, emsg, p_ident)) {\n        return Emsg(epname, error, errno, \"commit filesize change\", emsg.c_str());\n      }\n\n      eos::FileIdentifier f_ident = fmd->getIdentifier();\n      eos::ContainerIdentifier c_ident = eos::ContainerIdentifier(\n                                           fmd->getContainerId());\n      ns_wr_lock.Release();\n\n      if (option[\"update\"]) {\n        // broadcast file md\n        gOFS->FuseXCastRefresh(f_ident,\n                               c_ident);\n\n        // Broadcast to the fusex network only if the change has been\n        // triggered outside the fusex client network e.g. xrdcp etc.\n        if (!option[\"fusex\"]) {\n          gOFS->FuseXCastRefresh(c_ident, p_ident);\n        }\n\n        // Emit WRITE audit with before/after size and times\n        if (gOFS->mAudit) {\n          eos::audit::Stat afterStat;\n          eos::mgm::auditutil::buildStatFromFileMD(fmd, afterStat, /*includeSize=*/true, /*includeChecksum=*/true, /*includeNs=*/true);\n          if (gOFS->AllowAuditModification(cgi.count(\"path\") ? cgi[\"path\"] : path)) gOFS->mAudit->audit(eos::audit::WRITE, cgi.count(\"path\") ? cgi[\"path\"] : path,\n                                vid, tlLogId.logId, tlLogId.cident, \"mgm\",\n                                std::string(), &beforeStat, &afterStat);\n        }\n      }\n    }\n    {\n      eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n      // Path of a previous version existing before an atomic/versioning upload\n      std::string delete_path = \"\";\n      eos_thread_info(\"commitsize=%d n1=%s n2=%s occhunk=%d ocdone=%d\",\n                      option[\"commitsize\"],\n                      fmdname.c_str(), paths[\"atomic\"].GetName(),\n                      option[\"occhunk\"], option[\"ocdone\"]);\n\n      // -----------------------------------------------------------------------\n      // We are asked to commit the size and this commit changes the current\n      // atomic name to the final name and we are not an OC upload\n      // -----------------------------------------------------------------------\n      if ((option[\"commitsize\"]) && (fmdname != paths[\"atomic\"].GetName()) &&\n          (!option[\"occhunk\"] || option[\"ocdone\"])) {\n        eos_thread_info(\"commit: de-atomize file %s => %s\",\n                        fmdname.c_str(), paths[\"atomic\"].GetName());\n        unsigned long long vfid =\n          CommitHelper::get_version_fid(vid, fid, paths, option);\n\n        // Check for versioning request\n        if (option[\"versioning\"]) {\n          eos_static_info(\"checked %s%s vfxid=%08llx\",\n                          paths[\"versiondir\"].GetParentPath(),\n                          paths[\"atomic\"].GetPath(), vfid);\n\n          // We purged the versions before during open, so we just simulate\n          // a new one and do the final rename in a transaction\n          if (vfid) {\n            XrdOucString versionedname = \"\";\n\n            if (gOFS->Version(vfid, error, rootvid, 0xffff, &versionedname, true)) {\n              eos_static_crit(\"versioning failed %s/%s vfxid=%08lxx\",\n                              paths[\"versiondir\"].GetParentPath(),\n                              paths[\"atomic\"].GetPath(), vfid);\n              const char* errmsg = \"commit - versioning failed\";\n              return Emsg(epname, error, EREMCHG, errmsg, paths[\"atomic\"].GetName());\n            } else {\n              paths[\"version\"].Init(versionedname.c_str());\n            }\n          }\n        }\n\n        CommitHelper::handle_versioning(vid, fid, paths,\n                                        option, delete_path);\n      }\n\n      gOFS->mReplicationTracker->Commit(fmd);\n\n      // -----------------------------------------------------------------------\n      // If there was a previous target file we have to delete the renamed\n      // atomic left-over\n      // -----------------------------------------------------------------------\n      if (delete_path.length()) {\n        delete_path.insert(0, paths[\"versiondir\"].GetParentPath());\n        eos_thread_info(\"msg=\\\"delete path\\\" path=%s\", delete_path.c_str());\n\n        if (gOFS->_rem(delete_path.c_str(), error, rootvid, \"\")) {\n          eos_thread_err(\"msg=\\\"failed to remove atomic left-over\\\" path=%s\",\n                         delete_path.c_str());\n        }\n      }\n\n      if (option[\"abort\"]) {\n        return Emsg(epname, error, EREMCHG, \"commit replica - overlapping \"\n                    \"atomic upload - discarding atomic upload [EREMCHG]\", \"\");\n      }\n    }\n  } else if (CommitHelper::check_altchecksums_commit_params(cgi)) {\n    unsigned long long fid{};\n\n    try {\n      fid = std::stoull(cgi[\"fid\"], 0, 16);\n    } catch (...) {\n      return Emsg(epname, error, EINVAL, \"commit - bad fid\", \"\");\n    }\n\n    {\n      eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, fid);\n      eos::common::RWMutexWriteLock ns_wr_lock(gOFS->eosViewRWMutex);\n      std::shared_ptr<eos::IFileMD> fmd;\n      std::string emsg;\n\n      try {\n        fmd = gOFS->eosFileService->getFileMD(fid);\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        eos_thread_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\",\n                         e.getErrno(), e.getMessage().str().c_str());\n        emsg = \"retc=\";\n        emsg += e.getErrno();\n        emsg += \" msg=\";\n        emsg += e.getMessage().str().c_str();\n      }\n\n      if (!fmd) {\n        if (errno == ENOENT) {\n          return Emsg(epname, error, ENOENT,\n                      \"commit - file is already removed [EIDRM]\", \"\");\n        }\n\n        emsg.insert(0, \"commit filesize change [EIO]\");\n        return Emsg(epname, error, errno, emsg.c_str(), cgi[\"path\"].c_str());\n      }\n\n      if (cgi[\"altxs\"].length()) {\n        std::vector<std::string> tkns;\n        std::vector<std::string> name2xs;\n        eos::common::StringConversion::Tokenize(cgi[\"altxs\"], tkns, \",\");\n        fmd->clearAltXs();\n\n        for (const auto& tkn : tkns) {\n          name2xs.clear();\n          eos::common::StringConversion::Tokenize(tkn, name2xs, \":\");\n\n          if (name2xs.size() != 2) {\n            // the entry is not valid\n            continue;\n          }\n\n          auto checksumType = eos::common::LayoutId::GetChecksumFromString(name2xs[0]);\n          auto xs = name2xs[1];\n          fmd->addAltXs(static_cast<eos::common::LayoutId::eChecksum>\n                        (checksumType), xs.c_str(), xs.size());\n        }\n      }\n\n      if (cgi[\"altxs_delete\"].length()) {\n        std::vector<std::string> tkns;\n        eos::common::StringConversion::Tokenize(cgi[\"altxs_delete\"], tkns, \",\");\n\n        for (const auto& tkn : tkns) {\n          fmd->removeAltXs(static_cast<eos::common::LayoutId::eChecksum>\n                           (eos::common::LayoutId::GetChecksumFromString(tkn)));\n        }\n      }\n\n      eos::ContainerIdentifier p_ident;\n\n      if (!CommitHelper::commit_fmd(vid, fmd->getContainerId(), fmd, fmd->getSize(),\n                                    option, emsg, p_ident)) {\n        return Emsg(epname, error, errno, \"commit filesize change\", emsg.c_str());\n      }\n\n      eos_info(\"msg=\\\"committed alternative checksums\\\" fid=%08llx\", fid);\n    }\n  } else {\n    int envlen = 0;\n    eos_thread_err(\"commit message does not contain all meta information: %s\",\n                   env.Env(envlen));\n    gOFS->MgmStats.Add(\"CommitFailedParameters\", 0, 0, 1);\n    const char* errtarget = \"unknown\";\n    const char* errmsg =\n      \"commit filesize change - size, fid, fsid, mtime, path not complete\";\n\n    if (cgi.count(\"path\")) {\n      errmsg = \"commit filesize change - size, fid, fsid, mtime not complete\";\n      errtarget = cgi[\"path\"].c_str();\n    }\n\n    return Emsg(epname, error, EINVAL, errmsg, errtarget);\n  }\n\n  gOFS->MgmStats.Add(\"Commit\", 0, 0, 1);\n  const char* ok = \"OK\";\n  error.setErrInfo(strlen(ok) + 1, ok);\n  EXEC_TIMING_END(\"Commit\");\n  return SFS_DATA;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/CommitHelper.cc",
    "content": "// ----------------------------------------------------------------------\n// File: CommitHelper.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/ofs/fsctl/CommitHelper.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/FuseServer/FusexCastBatch.hh\"\n#include \"common/http/OwnCloud.hh\"\n#include \"common/LayoutId.hh\"\n#include \"namespace/interface/IQuota.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/Prefetcher.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n// Initialize static variables\nthread_local eos::common::LogId CommitHelper::tlLogId;\n\n//------------------------------------------------------------------------------\n// Increment the timestamp component of the version file name given as input\n// so as to avoid an existing conflict with an already existing file.\n//------------------------------------------------------------------------------\nstd::string\nCommitHelper::IncrementTsForVersionFn(const std::string& ver_fn)\n{\n  size_t pos = ver_fn.find('.');\n\n  // No . character or at the end of the string\n  if ((pos == std::string::npos) || (pos == ver_fn.length() - 1)) {\n    return ver_fn;\n  }\n\n  std::string sts = ver_fn.substr(0, pos);\n  unsigned long long ts = 0ull;\n\n  try {\n    ts = std::stoull(sts);\n    ++ts;\n  } catch (...) {\n    return ver_fn;\n  }\n\n  std::ostringstream oss;\n  oss << ts << \".\" << ver_fn.substr(pos + 1);\n  return oss.str();\n}\n\n//------------------------------------------------------------------------------\n// convert hex to binary checksum\n//------------------------------------------------------------------------------\nvoid\nCommitHelper::hex2bin_checksum(std::string& checksum, char* binchecksum)\n{\n  // hex2binary conversion\n  memset(binchecksum, 0, 32);\n\n  for (unsigned int i = 0; i < checksum.length(); i += 2) {\n    char hex[3];\n    hex[0] = checksum.at(i);\n    hex[1] = checksum.at(i + 1);\n    hex[2] = 0;\n    binchecksum[i / 2] = strtol(hex, 0, 16);\n  }\n}\n\n//------------------------------------------------------------------------------\n// check if the filesystem to commit to is writable state\n//------------------------------------------------------------------------------\n\nint\nCommitHelper::check_filesystem(eos::common::VirtualIdentity& vid,\n                               unsigned long fsid,\n                               CommitHelper::cgi_t& cgi,\n                               CommitHelper::option_t& option,\n                               CommitHelper::param_t& params,\n                               std::string& emsg)\n{\n  // Check that the file system is still allowed to accept replica's\n  eos::common::RWMutexReadLock vlock(FsView::gFsView.ViewMutex);\n  eos::mgm::FileSystem* fs = FsView::gFsView.mIdView.lookupByID(fsid);\n\n  if ((!fs) || (fs->GetConfigStatus() < eos::common::ConfigStatus::kDrain)) {\n    eos_thread_err(\"msg=\\\"commit suppressed\\\" configstatus=%s subcmd=commit \"\n                   \"path=%s size=%s fxid=%s fsid=%s dropfsid=%s checksum=%s\"\n                   \" mtime=%s mtime.nsec=%s oc-chunk=%d oc-n=%d oc-max=%d \"\n                   \"oc-uuid=%s\",\n                   (fs ? eos::common::FileSystem::GetConfigStatusAsString(\n                      fs->GetConfigStatus())\n                    : \"deleted\"),\n                   cgi[\"path\"].c_str(),\n                   cgi[\"size\"].c_str(),\n                   cgi[\"fid\"].c_str(),\n                   cgi[\"fsid\"].c_str(),\n                   cgi[\"dropfsid\"].c_str(),\n                   cgi[\"checksum\"].c_str(),\n                   cgi[\"mtime\"].c_str(),\n                   cgi[\"mtimensec\"].c_str(),\n                   option[\"occhunk\"], params[\"oc_n\"],\n                   params[\"oc_max\"], cgi[\"oc_uuid\"].c_str());\n    emsg = \"commit file metadata - \"\n           \"filesystem is in non-operational state [EIO]\";\n    return EIO;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// extract all CGI KV pairs\n//------------------------------------------------------------------------------\n\nvoid\nCommitHelper::grab_cgi(XrdOucEnv& env, CommitHelper::cgi_t& cgi)\n{\n  if (env.Get(\"mgm.size\")) {\n    cgi[\"size\"] = env.Get(\"mgm.size\");\n  }\n\n  if (env.Get(\"mgm.path\")) {\n    cgi[\"path\"] = env.Get(\"mgm.path\");\n  }\n\n  if (env.Get(\"mgm.fid\")) {\n    cgi[\"fid\"] = env.Get(\"mgm.fid\");\n  }\n\n  if (env.Get(\"mgm.add.fsid\")) {\n    cgi[\"fsid\"] = env.Get(\"mgm.add.fsid\");\n  }\n\n  if (env.Get(\"mgm.mtime\")) {\n    cgi[\"mtime\"] = env.Get(\"mgm.mtime\");\n  }\n\n  if (env.Get(\"mgm.mtime_ns\")) {\n    cgi[\"mtimensec\"] = env.Get(\"mgm.mtime_ns\");\n  }\n\n  if (env.Get(\"mgm.logid\")) {\n    cgi[\"logid\"] = env.Get(\"mgm.logid\");\n  }\n\n  if (env.Get(\"mgm.verify.checksum\")) {\n    cgi[\"verifychecksum\"] = env.Get(\"mgm.verify.checksum\");\n  }\n\n  if (env.Get(\"mgm.commit.checksum\")) {\n    cgi[\"commitchecksum\"] = env.Get(\"mgm.commit.checksum\");\n  }\n\n  if (env.Get(\"mgm.commit.verify\")) {\n    cgi[\"commitverify\"] = env.Get(\"mgm.commit.verify\");\n  }\n\n  if (env.Get(\"mgm.verify.size\")) {\n    cgi[\"verifysize\"] = env.Get(\"mgm.verify.size\");\n  }\n\n  if (env.Get(\"mgm.commit.size\")) {\n    cgi[\"commitsize\"] = env.Get(\"mgm.commit.size\");\n  }\n\n  if (env.Get(\"mgm.drop.fsid\")) {\n    cgi[\"dropfsid\"] = env.Get(\"mgm.drop.fsid\");\n  }\n\n  if (env.Get(\"mgm.replication\")) {\n    cgi[\"replication\"] = env.Get(\"mgm.replication\");\n  }\n\n  if (env.Get(\"mgm.reconstruction\")) {\n    cgi[\"reconstruction\"] = env.Get(\"mgm.reconstruction\");\n  }\n\n  if (env.Get(\"mgm.modified\")) {\n    cgi[\"ismodified\"] = env.Get(\"mgm.modified\");\n  }\n\n  if (env.Get(\"mgm.fusex\")) {\n    cgi[\"fusex\"] = env.Get(\"mgm.fusex\");\n  }\n\n  if (env.Get(\"mgm.checksum\")) {\n    cgi[\"checksum\"] = env.Get(\"mgm.checksum\");\n  }\n\n  if (env.Get(\"mgm.altxs\")) {\n    cgi[\"altxs\"] = env.Get(\"mgm.altxs\");\n  }\n\n  if (env.Get(\"mgm.commit.altxs\")) {\n    cgi[\"commit_altxs\"] = env.Get(\"mgm.commit.altxs\");\n  }\n\n  if (env.Get(\"mgm.altxs.delete\")) {\n    cgi[\"altxs_delete\"] = env.Get(\"mgm.altxs.delete\");\n  }\n}\n\n//------------------------------------------------------------------------------\n// log commit information\n//------------------------------------------------------------------------------\n\nvoid\nCommitHelper::log_info(eos::common::VirtualIdentity& vid,\n                       const eos::common::LogId& thread_logid,\n                       CommitHelper::cgi_t& cgi,\n                       CommitHelper::option_t& option,\n                       CommitHelper::param_t& params)\n{\n  // Set the thread local variable that will be used when calling eos_thread_*\n  tlLogId = thread_logid;\n\n  if (cgi[\"checksum\"].length()) {\n    eos_thread_info(\"subcmd=commit path=%s size=%s fxid=%s fsid=%s dropfsid=%s \"\n                    \"checksum=%s altxs=%s mtime=%s mtime.nsec=%s oc-chunk=%d oc-n=%d \"\n                    \"oc-max=%d oc-uuid=%s\",\n                    cgi[\"path\"].c_str(),\n                    cgi[\"size\"].c_str(),\n                    cgi[\"fid\"].c_str(),\n                    cgi[\"fsid\"].c_str(),\n                    cgi[\"dropfsid\"].c_str(),\n                    cgi[\"checksum\"].c_str(),\n                    cgi[\"altxs\"].c_str(),\n                    cgi[\"mtime\"].c_str(),\n                    cgi[\"mtimensec\"].c_str(),\n                    option[\"occhunk\"],\n                    params[\"oc_n\"],\n                    params[\"oc_max\"],\n                    cgi[\"ocuuid\"].c_str());\n  } else {\n    eos_thread_info(\"subcmd=commit path=%s size=%s fxid=%s fsid=%s dropfsid=%s \"\n                    \"mtime=%s mtime.nsec=%s oc-chunk=%d oc-n=%d \"\n                    \"oc-max=%d oc-uuid=%s\",\n                    cgi[\"path\"].c_str(),\n                    cgi[\"size\"].c_str(),\n                    cgi[\"fid\"].c_str(),\n                    cgi[\"fsid\"].c_str(),\n                    cgi[\"dropfsid\"].c_str(),\n                    cgi[\"mtime\"].c_str(),\n                    cgi[\"mtimensec\"].c_str(),\n                    option[\"occhunk\"],\n                    params[\"oc_n\"],\n                    params[\"oc_max\"],\n                    cgi[\"ocuuid\"].c_str());\n  }\n}\n\n//------------------------------------------------------------------------------\n// extract options from CGI\n//------------------------------------------------------------------------------\n\nvoid\nCommitHelper::set_options(CommitHelper::option_t& option,\n                          CommitHelper::cgi_t& cgi)\n{\n  option[\"verifychecksum\"] = (cgi[\"verifychecksum\"] == \"1\");\n  option[\"commitchecksum\"] = (cgi[\"commitchecksum\"] == \"1\");\n  option[\"commitsize\"] = (cgi[\"commitsize\"] == \"1\");\n  option[\"commitverify\"] = (cgi[\"commitverify\"] == \"1\");\n  option[\"verifysize\"] = (cgi[\"verifysize\"] == \"1\");\n  option[\"replication\"] = (cgi[\"replication\"] == \"1\");\n  option[\"reconstruction\"] = (cgi[\"reconstruction\"] == \"1\");\n  option[\"modified\"] = (cgi[\"ismodified\"] == \"1\");\n  option[\"fusex\"] = (cgi[\"fusex\"] == \"1\");\n  option[\"abort\"] = false; // indicate to abort a commit\n  option[\"versioning\"] = false; // indicate versioning\n  option[\"atomic\"] = false; // indicate an atomic upload\n  option[\"occhunk\"] = false; // indicate owncloud chunking\n  option[\"ocdone\"] =\n    false; // indicate when the last chunk of a chunked OC upload has been committed\n}\n\n//------------------------------------------------------------------------------\n// initialize OC commit parameters\n//------------------------------------------------------------------------------\n\nvoid\nCommitHelper::init_oc(XrdOucEnv& env, CommitHelper::cgi_t& cgi,\n                      CommitHelper::option_t& option,\n                      CommitHelper::param_t& params)\n{\n  int envlen;\n  int oc_n = 0;\n  int oc_max = 0;\n  XrdOucString oc_uuid = \"\";\n  option[\"occhunk\"] = eos::common::OwnCloud::GetChunkInfo(env.Env(envlen), oc_n,\n                      oc_max, oc_uuid);\n  cgi[\"ocuuid\"] = oc_uuid.c_str();\n  params[\"oc_n\"] = oc_n;\n  params[\"oc_max\"] = oc_max;\n}\n\n//------------------------------------------------------------------------------\n// check for a reconstruction commit\n//------------------------------------------------------------------------------\n\nbool\nCommitHelper::is_reconstruction(CommitHelper::option_t& option)\n{\n  if (option[\"reconstruction\"]) {\n    option[\"verifysize\"] = false;\n    option[\"verifychecksum\"] = false;\n    option[\"commitsize\"] = false;\n    option[\"commitchecksum\"] = false;\n    option[\"commitverify\"] = false;\n    option[\"replication\"] = false;\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// check proper commit parameter\n//------------------------------------------------------------------------------\n\nbool\nCommitHelper::check_commit_params(CommitHelper::cgi_t& cgi)\n{\n  return cgi[\"size\"].length() && cgi[\"fid\"].length() && cgi[\"path\"].length() &&\n         cgi[\"fsid\"].length() && cgi[\"mtime\"].length() && cgi[\"mtimensec\"].length();\n}\n\n//------------------------------------------------------------------------------\n// check proper altchecksums commit parameter\n//------------------------------------------------------------------------------\n\nbool CommitHelper::check_altchecksums_commit_params(CommitHelper::cgi_t& cgi)\n{\n  return cgi[\"commit_altxs\"].length() && (cgi[\"altxs\"].length() ||\n                                          cgi[\"altxs_delete\"].length()) && cgi[\"fid\"].length();\n}\n\n//------------------------------------------------------------------------------\n// Remove fid from the trakced maps\n//------------------------------------------------------------------------------\nvoid\nCommitHelper::remove_scheduler(unsigned long long fid)\n{\n  // Remove tracked entry from balancing\n  gOFS->mFidTracker.RemoveEntry(fid);\n}\n\n//------------------------------------------------------------------------------\n// validate the given size information\n//------------------------------------------------------------------------------\n\nbool\nCommitHelper::validate_size(eos::common::VirtualIdentity& vid,\n                            std::shared_ptr<eos::IFileMD> fmd,\n                            unsigned long fsid,\n                            unsigned long long size,\n                            CommitHelper::option_t& option)\n{\n  if (fmd->getSize() != size) {\n    eos_thread_err(\"replication for fxid=%08llx resulted in a different file \"\n                   \"size on fsid=%llu - %llu vs %llu - rejecting replica\", fmd->getId(), fsid,\n                   fmd->getSize(), size);\n    gOFS->MgmStats.Add(\"ReplicaFailedSize\", 0, 0, 1);\n\n    // -----------------------------------------------------------\n    // if we come via FUSE, we have to remove this replica\n    // -----------------------------------------------------------\n    if (option[\"fusex\"]) {\n      if (fmd->hasLocation((unsigned short) fsid)) {\n        fmd->unlinkLocation((unsigned short) fsid);\n        fmd->removeLocation((unsigned short) fsid);\n\n        try {\n          gOFS->eosView->updateFileStore(fmd.get());\n        } catch (eos::MDException& e) {\n          errno = e.getErrno();\n          std::string errmsg = e.getMessage().str();\n          eos_thread_crit(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                          e.getErrno(), e.getMessage().str().c_str());\n        }\n      }\n    }\n\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// validate the given checksum\n//------------------------------------------------------------------------------\n\nbool\nCommitHelper::validate_checksum(eos::common::VirtualIdentity& vid,\n                                std::shared_ptr<eos::IFileMD> fmd,\n                                eos::Buffer& checksumbuffer,\n                                unsigned long long fsid,\n                                CommitHelper::option_t& option)\n{\n  bool cxError = false;\n  size_t cxlen = eos::common::LayoutId::GetChecksumLen(fmd->getLayoutId());\n\n  for (size_t i = 0; i < cxlen; i++) {\n    if (fmd->getChecksum().getDataPadded(i) != checksumbuffer.getDataPadded(i)) {\n      cxError = true;\n    }\n  }\n\n  if (cxError) {\n    eos_thread_err(\"replication for fxid=%08llx resulted in a different checksum \"\n                   \"on fsid=%llu - rejecting replica\", fmd->getId(), fsid);\n    gOFS->MgmStats.Add(\"ReplicaFailedChecksum\", 0, 0, 1);\n\n    // -----------------------------------------------------------\n    // if we don't come via FUSEX, we have to remove this replica\n    // -----------------------------------------------------------\n    if (!option[\"fusex\"]) {\n      if (fmd->hasLocation((unsigned short) fsid)) {\n        fmd->unlinkLocation((unsigned short) fsid);\n        fmd->removeLocation((unsigned short) fsid);\n        eos_thread_err(\"replication for fxid=%08llx resulted in a different checksum \"\n                       \"on fsid=%llu - dropping replica\", fmd->getId(), fsid);\n\n        try {\n          gOFS->eosView->updateFileStore(fmd.get());\n        } catch (eos::MDException& e) {\n          errno = e.getErrno();\n          std::string errmsg = e.getMessage().str();\n          eos_thread_crit(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                          e.getErrno(), e.getMessage().str().c_str());\n        }\n      }\n    }\n\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// log checksum verification\n//------------------------------------------------------------------------------\n\nvoid\nCommitHelper::log_verifychecksum(eos::common::VirtualIdentity& vid,\n                                 std::shared_ptr<eos::IFileMD>fmd,\n                                 eos::Buffer& checksumbuffer,\n                                 unsigned long fsid,\n                                 CommitHelper::cgi_t& cgi,\n                                 CommitHelper::option_t& option)\n{\n  if (cgi[\"checksum\"].length()) {\n    if (option[\"verifychecksum\"]) {\n      bool cxError = false;\n      size_t cxlen = eos::common::LayoutId::GetChecksumLen(fmd->getLayoutId());\n\n      for (size_t i = 0; i < cxlen; i++) {\n        if (fmd->getChecksum().getDataPadded(i) != checksumbuffer.getDataPadded(i)) {\n          cxError = true;\n        }\n      }\n\n      if (cxError) {\n        eos_thread_err(\"commit for fxid=%08llx gave a different checksum after \"\n                       \"verification on fsid=%llu\", fmd->getId(), fsid);\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// handle replica location updates\n//------------------------------------------------------------------------------\n\nbool\nCommitHelper::handle_location(eos::common::VirtualIdentity& vid,\n                              unsigned long cid,\n                              std::shared_ptr<eos::IFileMD> fmd,\n                              unsigned long fsid,\n                              unsigned long long size,\n                              CommitHelper::cgi_t& cgi,\n                              CommitHelper::option_t& option)\n{\n  // For changing the modification time we have to figure out if we\n  // just attach a new replica or if we have a change of the contents\n  std::shared_ptr<eos::IContainerMD> dir;\n\n  try {\n    dir = gOFS->eosDirectoryService->getContainerMD(cid);\n  } catch (eos::MDException& e) {\n    eos_thread_err(\"parent_id=%llu not found\", cid);\n    gOFS->MgmStats.Add(\"CommitFailedUnlinked\", 0, 0, 1);\n    return false;\n  }\n\n  eos::IQuotaNode* ns_quota = nullptr;\n\n  try {\n    ns_quota = gOFS->eosView->getQuotaNode(dir.get());\n  } catch (const eos::MDException& e) {\n    // empty\n  }\n\n  // Free previous quota\n  if (ns_quota) {\n    ns_quota->removeFile(fmd.get());\n  }\n\n  std::string locations;\n\n  try {\n    locations = fmd->getAttribute(\"sys.fs.tracking\");\n  } catch (...) {}\n\n  if (!fmd->hasLocation(fsid)) {\n    locations += \"+\";\n    locations += std::to_string(fsid);\n  }\n\n  fmd->addLocation(fsid);\n\n  // If fsid is in the deletion list, we try to remove it if there\n  // is something in the deletion list\n  if (fmd->getNumUnlinkedLocation()) {\n    fmd->removeLocation(fsid);\n  }\n\n  if (cgi[\"dropfsid\"].length()) {\n    std::vector<std::string> fsid_tokens;\n    eos::common::StringConversion::Tokenize(cgi[\"dropfsid\"], fsid_tokens, \",\");\n\n    for (auto id : fsid_tokens) {\n      unsigned long drop_fsid = std::stoul(id);\n      eos_thread_info(\"commit: dropping replica on fs %lu\", drop_fsid);\n      fmd->unlinkLocation((unsigned short)drop_fsid);\n      locations += \"-\";\n      locations += std::to_string(drop_fsid);\n    }\n  }\n\n  std::string tracking = eos::common::StringConversion::ReduceString(locations);\n\n  if (tracking.length()) {\n    fmd->setAttribute(\"sys.fs.tracking\", tracking.c_str());\n  }\n\n  option[\"update\"] = false;\n\n  if (option[\"commitsize\"]) {\n    if ((fmd->getSize() != size) || option[\"modified\"]) {\n      eos_thread_debug(\"size difference forces mtime %lld %lld or \"\n                       \"ismodified=%d\", fmd->getSize(), size, option.count(\"modified\"));\n      option[\"update\"] = true;\n    }\n\n    fmd->setSize(size);\n  }\n\n  if (ns_quota) {\n    ns_quota->addFile(fmd.get());\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// handle OC chunk uploads\n//------------------------------------------------------------------------------\n\nvoid\nCommitHelper::handle_occhunk(eos::common::VirtualIdentity& vid,\n                             std::shared_ptr<eos::IFileMD>& fmd,\n                             CommitHelper::option_t& option,\n                             CommitHelper::param_t& params)\n{\n  if (option[\"occhunk\"] && option[\"commitsize\"]) {\n    // store the index in flags;\n    fmd->setFlags(params[\"oc_n\"] + 1);\n    eos_thread_info(\"subcmd=commit max-chunks=%d committed-chunks=%d\",\n                    params[\"oc_max\"],\n                    fmd->getFlags());\n\n    // The last chunk terminates all\n    if (params[\"oc_max\"] == (params[\"oc_n\"] + 1)) {\n      // we are done with chunked upload, remove the flags counter\n      fmd->setFlags((S_IRWXU | S_IRWXG | S_IRWXO));\n      option[\"ocdone\"] = true;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// handle new checksums\n//------------------------------------------------------------------------------\n\nvoid\nCommitHelper::handle_checksum(eos::common::VirtualIdentity& vid,\n                              std::shared_ptr<eos::IFileMD>fmd,\n                              CommitHelper::option_t& option,\n                              eos::Buffer& checksumbuffer)\n{\n  if (option[\"commitchecksum\"]) {\n    if (!option[\"update\"]) {\n      for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {\n        if (fmd->getChecksum().getDataPadded(i) != checksumbuffer.getDataPadded(i)) {\n          eos_thread_debug(\"checksum difference forces mtime\");\n          option[\"update\"] = true;\n        }\n      }\n    }\n\n    fmd->setChecksum(checksumbuffer);\n  }\n}\n\n//------------------------------------------------------------------------------\n// commit new file meta data\n//------------------------------------------------------------------------------\n\nbool\nCommitHelper::commit_fmd(eos::common::VirtualIdentity& vid,\n                         unsigned long cid,\n                         std::shared_ptr<eos::IFileMD>fmd,\n                         unsigned long long replica_size,\n                         CommitHelper::option_t& option,\n                         std::string& errmsg,\n                         eos::ContainerIdentifier& p_ident\n                        )\n{\n  std::shared_ptr<eos::IContainerMD> cmd;\n\n  try {\n    // check for a temporary Etag and remove it\n    std::string tmpEtag = \"sys.tmp.etag\";\n\n    if (fmd->hasAttribute(tmpEtag) && (!option[\"atomic\"] || option[\"occhunk\"])\n        && (option[\"commitsize\"] || option[\"commitchecksum\"])) {\n      // Drop the tmp etag only if this was not a creation of a 0-size file\n      if ((fmd->getSize() != 0) || (replica_size != 0)) {\n        fmd->removeAttribute(tmpEtag);\n      }\n    }\n\n    gOFS->eosView->updateFileStore(fmd.get());\n    cmd = gOFS->eosDirectoryService->getContainerMD(cid);\n    p_ident = cmd->getParentIdentifier();\n\n    if (option[\"update\"]) {\n      if (cmd->hasAttribute(tmpEtag)) {\n        // Drop the tmp etag only if this was not a creation of a 0-size file\n        if ((fmd->getSize() != 0) || (replica_size != 0)) {\n          cmd->removeAttribute(tmpEtag);\n        }\n      }\n\n      // update parent mtime and ctime\n      cmd->setMTimeNow();\n      eos::IContainerMD::mtime_t new_mtime;\n      cmd->getMTime(new_mtime);\n      cmd->setCTime(new_mtime);\n      gOFS->eosView->updateContainerStore(cmd.get());\n      cmd->notifyMTimeChange(gOFS->eosDirectoryService);\n    }\n\n    return true;\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    errmsg = e.getMessage().str();\n    eos_thread_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                     e.getErrno(), e.getMessage().str().c_str());\n    gOFS->MgmStats.Add(\"CommitFailedNamespace\", 0, 0, 1);\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// identify latest version file id\n//------------------------------------------------------------------------------\n\nunsigned long long\nCommitHelper::get_version_fid(eos::common::VirtualIdentity& vid,\n                              unsigned long long fid,\n                              CommitHelper::path_t& paths,\n                              CommitHelper::option_t& option)\n{\n  eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, fid);\n  std::shared_ptr<eos::IFileMD> versionfmd;\n  eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n\n  try {\n    auto fmd = gOFS->eosFileService->getFileMD(fid);\n    paths[\"versiondir\"] = gOFS->eosView->getUri(fmd.get());\n\n    if (option[\"versioning\"]) {\n      versionfmd = gOFS->eosView->getFile(std::string(\n                                            paths[\"versiondir\"].GetParentPath())\n                                          + std::string(paths[\"atomic\"].GetPath()));\n      return versionfmd->getId();\n    }\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_thread_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                     e.getErrno(), e.getMessage().str().c_str());\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// handle creation of a new version during commit\n//------------------------------------------------------------------------------\n\nvoid\nCommitHelper::handle_versioning(eos::common::VirtualIdentity& vid,\n                                unsigned long fid,\n                                CommitHelper::path_t& paths,\n                                CommitHelper::option_t& option,\n                                std::string& delete_path\n                               )\n{\n  eos::mgm::FusexCastBatch fuse_batch;\n  eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n\n  // We have to de-atomize the fmd name here e.g. make the temporary\n  // atomic name a persistent name\n  try {\n    std::shared_ptr<eos::IFileMD> fmd;\n    std::shared_ptr<eos::IContainerMD> dir;\n    std::shared_ptr<eos::IContainerMD> versiondir;\n    std::shared_ptr<eos::IFileMD> versionfmd;\n    eos::IFileMD::XAttrMap attrmapF;\n    dir = gOFS->eosView->getContainer(paths[\"versiondir\"].GetParentPath());\n    fmd = gOFS->eosFileService->getFileMD(fid);\n\n    if (fmd->getName() == paths[\"atomic\"].GetName()) {\n      // defere version handling for an overlapping secondary commit\n      // due to lock-release during commit\n      return;\n    }\n\n    if (option[\"versioning\"] && strlen(paths[\"version\"].GetPath()) &&\n        (std::string(paths[\"version\"].GetPath()) != \"/\")) {\n      try {\n        versiondir = gOFS->eosView->getContainer(paths[\"version\"].GetParentPath());\n        // rename the existing path to the version path\n        versionfmd = gOFS->eosView->getFile(std::string(\n                                              paths[\"versiondir\"].GetParentPath()) + std::string(paths[\"atomic\"].GetPath()));\n        dir->removeFile(paths[\"atomic\"].GetName());\n        // If a file with the same name already exists then we have conflict\n        // and the timestamp of the version file needs to be increased\n        uint16_t attempt = 0;\n\n        while ((versiondir->findFile(paths[\"version\"].GetName()) != nullptr) &&\n               (attempt++ < 5)) {\n          std::string ver_ppath = paths[\"version\"].GetParentPath();\n          std::string ver_fn = paths[\"version\"].GetName();\n          eos_static_info(\"msg=\\\"trigger workaround for file name collision\\\" \"\n                          \"fn=\\\"%s\\\"\", ver_fn.c_str());\n          ver_fn = IncrementTsForVersionFn(ver_fn);\n          std::string full_path = ver_ppath + ver_fn;\n          paths[\"version\"] = eos::common::Path(full_path);\n        }\n\n        versionfmd->setName(paths[\"version\"].GetName());\n        versionfmd->setContainerId(versiondir->getId());\n        attrmapF = versionfmd->getAttributes();\n        versiondir->addFile(versionfmd.get());\n        versiondir->setMTimeNow();\n        gOFS->eosView->updateFileStore(versionfmd.get());\n        const eos::FileIdentifier vfid = versionfmd->getIdentifier();\n        const eos::ContainerIdentifier did = dir->getIdentifier();\n        const eos::ContainerIdentifier vdid = versiondir->getIdentifier();\n        const std::string atomic_name = paths[\"atomic\"].GetName();\n        fuse_batch.Register([&, vfid, did, vdid, atomic_name]() {\n          gOFS->FuseXCastDeletion(did, atomic_name);\n          gOFS->FuseXCastRefresh(vfid, vdid);\n        });\n        // Update the ownership and mode of the new file to the original one\n        fmd->setCUid(versionfmd->getCUid());\n        fmd->setCGid(versionfmd->getCGid());\n        fmd->setFlags(versionfmd->getFlags());\n        static std::set<std::string> skip_tag {\"sys.eos.btime\", \"sys.fs.tracking\", \"sys.utrace\", \"sys.vtrace\", \"sys.tmp.atomic\"};\n\n        for (const auto& xattr : attrmapF) {\n          if (skip_tag.find(xattr.first) == skip_tag.end()) {\n            fmd->setAttribute(xattr.first, xattr.second);\n          }\n        }\n\n        gOFS->eosView->updateFileStore(fmd.get());\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        eos_thread_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                         e.getErrno(), e.getMessage().str().c_str());\n      }\n    }\n\n    std::shared_ptr<eos::IFileMD> pfmd;\n\n    // Rename the temporary upload path to the final path\n    if ((pfmd = dir->findFile(paths[\"atomic\"].GetName()))) {\n      // Check if we are tagged as that 'latest' atomic upload\n      std::string atomic_tag;\n\n      try {\n        atomic_tag = pfmd->getAttribute(\"sys.tmp.atomic\");\n      } catch (eos::MDException& e) {\n      }\n\n      if ((!option[\"ocdone\"]) && (atomic_tag != fmd->getName())) {\n        // this is not our atomic upload, just abort that and delete the temporary artefact\n        delete_path = fmd->getName();\n        eos_thread_err(\"msg=\\\"we are not the last atomic upload - cleaning %s\\\"\",\n                       delete_path.c_str());\n        option[\"abort\"] = true;\n      } else {\n        eos_thread_info(\"msg=\\\"found final path\\\" %s\", paths[\"atomic\"].GetName());\n        // If the target exists we swap the two and then delete the\n        // previous one\n        delete_path = fmd->getName();\n        delete_path += \".delete\";\n        gOFS->eosView->renameFile(pfmd.get(), delete_path);\n      }\n    } else {\n      eos_thread_info(\"msg=\\\"didn't find path\\\" %s\", paths[\"atomic\"].GetName());\n    }\n\n    if (!option[\"abort\"]) {\n      eos_thread_info(\"msg=\\\"de-atomize file\\\" fxid=%08llx atomic-name=%s \"\n                      \"final-name=%s\", fmd->getId(), fmd->getName().c_str(),\n                      paths[\"atomic\"].GetName());\n      gOFS->eosView->renameFile(fmd.get(), paths[\"atomic\"].GetName());\n    }\n  } catch (eos::MDException& e) {\n    delete_path = \"\";\n    errno = e.getErrno();\n    std::string errmsg = e.getMessage().str();\n    eos_thread_err(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                   e.getErrno(), e.getMessage().str().c_str());\n  }\n\n  lock.Release();\n  // the fuse_batch guard will run broadcasts here\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/ofs/fsctl/CommitHelper.hh",
    "content": "// ----------------------------------------------------------------------\n// File: CommitHelper.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_COMMITHELPER__HH__\n#define __EOSMGM_COMMITHELPER__HH__\n\n#include \"common/Logging.hh\"\n#include \"common/Path.hh\"\n#include \"common/Mapping.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include <string>\n#include <map>\n\n#include <XrdOuc/XrdOucEnv.hh>\n\nEOSMGMNAMESPACE_BEGIN\n\nclass CommitHelper\n{\npublic:\n\n  static thread_local eos::common::LogId tlLogId;\n  typedef std::map<std::string, std::string> cgi_t;\n  typedef std::map<std::string, bool> option_t;\n  typedef std::map<std::string, int> param_t;\n  typedef std::map<std::string, eos::common::Path> path_t;\n\n  static void hex2bin_checksum(std::string& checksum, char* binchecksum);\n  static int check_filesystem(eos::common::VirtualIdentity& vid,\n                              unsigned long fsid,\n                              CommitHelper::cgi_t& cgi,\n                              CommitHelper::option_t& option,\n                              CommitHelper::param_t& params,\n                              std::string& emsg);\n\n  static void grab_cgi(XrdOucEnv& env, CommitHelper::cgi_t& cgi);\n\n  static void log_info(eos::common::VirtualIdentity& vid,\n                       const eos::common::LogId& thread_logid,\n                       CommitHelper::cgi_t& cgi,\n                       CommitHelper::option_t& option,\n                       CommitHelper::param_t& params);\n\n  static void set_options(CommitHelper::option_t& option,\n                          CommitHelper::cgi_t& cgi);\n\n  static void init_oc(XrdOucEnv& env,\n                      CommitHelper::cgi_t& cgi,\n                      CommitHelper::option_t& option,\n                      CommitHelper::param_t& params);\n\n  static bool is_reconstruction(CommitHelper::option_t& option);\n\n  static bool check_commit_params(CommitHelper::cgi_t& cgi);\n\n  static bool check_altchecksums_commit_params(CommitHelper::cgi_t& cgi);\n\n  static void remove_scheduler(unsigned long long fid);\n\n  static bool validate_size(eos::common::VirtualIdentity& vid,\n                            std::shared_ptr<eos::IFileMD> fmd,\n                            unsigned long fsid,\n                            unsigned long long size,\n                            CommitHelper::option_t& option);\n\n  static bool validate_checksum(eos::common::VirtualIdentity& vid,\n                                std::shared_ptr<eos::IFileMD> fmd,\n                                eos::Buffer& checksumbuffer,\n                                unsigned long long fsid,\n                                CommitHelper::option_t& option);\n\n  static void log_verifychecksum(eos::common::VirtualIdentity& vid,\n                                 std::shared_ptr<eos::IFileMD>fmd,\n                                 eos::Buffer& checksumbuffer,\n                                 unsigned long fsid,\n                                 CommitHelper::cgi_t& cgi,\n                                 CommitHelper::option_t& option);\n\n  static bool handle_location(eos::common::VirtualIdentity& vid,\n                              unsigned long cid,\n                              std::shared_ptr<eos::IFileMD> fmd,\n                              unsigned long fsid,\n                              unsigned long long size,\n                              CommitHelper::cgi_t& cgi,\n                              CommitHelper::option_t& option);\n\n  static void handle_occhunk(eos::common::VirtualIdentity& vid,\n                             std::shared_ptr<eos::IFileMD>& fmd,\n                             CommitHelper::option_t& option,\n                             std::map<std::string, int>& params);\n\n\n  static void handle_checksum(eos::common::VirtualIdentity& vid,\n                              std::shared_ptr<eos::IFileMD>fmd,\n                              CommitHelper::option_t& option,\n                              eos::Buffer& checksumbuffer);\n\n  static bool commit_fmd(eos::common::VirtualIdentity& vid,\n                         unsigned long cid,\n                         std::shared_ptr<eos::IFileMD>fmd,\n                         unsigned long long replica_size,\n                         CommitHelper::option_t& option,\n                         std::string& errmsg,\n                         eos::ContainerIdentifier& p_ident\n                        );\n\n  static unsigned long long get_version_fid(\n    eos::common::VirtualIdentity& vid,\n    unsigned long long fid,\n    CommitHelper::path_t& paths,\n    CommitHelper::option_t& option);\n\n  static void handle_versioning(eos::common::VirtualIdentity& vid,\n                                unsigned long fid,\n                                CommitHelper::path_t& paths,\n                                CommitHelper::option_t& option,\n                                std::string& delete_path);\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  //----------------------------------------------------------------------------\n  //! Increment the timestamp component of the version file name given as input\n  //! in order to avoid a conflict with an already existing file.\n  //!\n  //! @param ver_fn version file name with format <ctime>.<fxid>\n  //!\n  //! @return new version file name <ctime+1>.<fxid>\n  //----------------------------------------------------------------------------\n  static std::string IncrementTsForVersionFn(const std::string& ver_fn);\n};\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/ofs/fsctl/Drop.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Drop.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"common/SymKeys.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/interface/IQuota.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/IFsView.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/iostat/Iostat.hh\"\n#include <XrdOuc/XrdOucEnv.hh>\n\n//----------------------------------------------------------------------------\n// Drop a replica\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::Drop(const char* path,\n                const char* ininfo,\n                XrdOucEnv& env,\n                XrdOucErrInfo& error,\n                eos::common::VirtualIdentity& vid,\n                const XrdSecEntity* client)\n{\n  static const char* epname = \"Drop\";\n  REQUIRE_SSS_OR_LOCAL_AUTH;\n  ACCESSMODE_W;\n  MAYSTALL;\n  const char* inpath = path;\n  MAYREDIRECT;\n  EXEC_TIMING_BEGIN(\"Drop\");\n  int envlen;\n  eos_thread_info(\"drop request for %s\", env.Env(envlen));\n  char* afid = env.Get(\"mgm.fid\");\n  char* afsid = env.Get(\"mgm.fsid\");\n  char* report = env.Get(\"mgm.report\");\n\n  if (afid && afsid) {\n    eos::IFileMD::id_t fid = eos::common::FileId::Hex2Fid(afid);\n    unsigned long fsid = strtoul(afsid, 0, 10);\n    std::shared_ptr<eos::IContainerMD> container;\n    std::shared_ptr<eos::IFileMD> fmd;\n    eos::IQuotaNode* ns_quota = nullptr;\n    eos::Prefetcher::prefetchFilesystemFileListAndWait(gOFS->eosView,\n        gOFS->eosFsView, fsid);\n    eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, fid);\n    {\n      eos::common::RWMutexWriteLock ns_wr_lock(gOFS->eosViewRWMutex);\n\n      try {\n        fmd = eosFileService->getFileMD(fid);\n      } catch (...) {\n        eos_thread_warning(\"msg=\\\"no meta record exists anymore\\\" fxid=%s\", afid);\n        ns_wr_lock.Release();\n        fmd = nullptr;\n        // Nevertheless drop the file identifier from the file system view\n        gOFS->eosFsView->eraseEntry(fsid, fid);\n      }\n\n      if (fmd) {\n        std::string locations;\n\n        try {\n          locations = fmd->getAttribute(\"sys.fs.tracking\");\n        } catch (...) {}\n\n        try {\n          container =\n            gOFS->eosDirectoryService->getContainerMD(fmd->getContainerId());\n        } catch (eos::MDException& e) {}\n\n        if (container) {\n          try {\n            ns_quota = gOFS->eosView->getQuotaNode(container.get());\n          } catch (eos::MDException& e) {\n            ns_quota = nullptr;\n          }\n        }\n\n        try {\n          std::vector<unsigned int> drop_fsid;\n          bool updatestore = false;\n          // If mgm.dropall flag is set then it means we got a deleteOnClose\n          // at the gateway node and we need to delete all replicas\n          char* drop_all = env.Get(\"mgm.dropall\");\n\n          if (drop_all) {\n            for (unsigned int i = 0; i < fmd->getNumLocation(); i++) {\n              drop_fsid.push_back(fmd->getLocation(i));\n            }\n          } else {\n            drop_fsid.push_back(fsid);\n          }\n\n          // Drop the selected replicas\n          for (const auto& id : drop_fsid) {\n            eos_thread_debug(\"msg=\\\"remove location\\\" fxid=%s fsid=%lu\", afid, id);\n            updatestore = false;\n\n            if (fmd->hasLocation(id)) {\n              fmd->unlinkLocation(id);\n              updatestore = true;\n              locations += \"-\";\n              locations += std::to_string(id);\n            }\n\n            if (fmd->hasUnlinkedLocation(id)) {\n              // Make sure to also send a delete requests for the stripes/\n              // replicas, otherwise we're left with orphans - best effort\n              if (drop_all) {\n                (void) DeleteExternal(id, fid);\n              }\n\n              fmd->removeLocation(id);\n              updatestore = true;\n              locations += \"/\";\n              locations += std::to_string(id);\n            }\n\n            if (updatestore) {\n              fmd->setAttribute(\"sys.fs.tracking\",\n                                eos::common::StringConversion::ReduceString(locations).c_str());\n              gOFS->eosView->updateFileStore(fmd.get());\n              // After update we might have to get the new address\n              fmd = eosFileService->getFileMD(eos::common::FileId::Hex2Fid(afid));\n            } else {\n              // The FileSystem view has a reference for this file but the file\n              // has no replicas registered on the current file system - we need\n              // to force delete the entry from the FileSystem view\n              gOFS->eosFsView->eraseEntry(id, fid);\n            }\n          }\n\n          // Delete the record only if all replicas are dropped\n          if ((!fmd->getNumUnlinkedLocation()) && (!fmd->getNumLocation())\n              && (drop_all || updatestore)) {\n            // However we should only remove the file from the namespace, if\n            // there was indeed a replica to be dropped, otherwise we get\n            // unlinked files if the secondary replica fails to write but\n            // the machine can call the MGM\n            if (ns_quota) {\n              // If we were still attached to a container, we can now detach\n              // and count the file as removed\n              ns_quota->removeFile(fmd.get());\n            }\n\n            gOFS->eosView->removeFile(fmd.get());\n\n            if (container) {\n              container->setMTimeNow();\n              gOFS->eosView->updateContainerStore(container.get());\n              container->notifyMTimeChange(gOFS->eosDirectoryService);\n              eos::ContainerIdentifier container_id = container->getIdentifier();\n              eos::ContainerIdentifier container_pid = container->getParentIdentifier();\n              ns_wr_lock.Release();\n              gOFS->FuseXCastRefresh(container_id, container_pid);\n            }\n          }\n        } catch (...) {\n          eos_thread_warning(\"no meta record exists anymore for fxid=%s\", afid);\n        }\n      }\n    }\n\n    if (report) {\n      // write the report via IoStat\n      std::string deletionreport64 = report;\n      std::string deletionreport;\n\n      if (eos::common::SymKey::ZDeBase64(deletionreport64, deletionreport)) {\n        gOFS->mIoStats->WriteRecord(deletionreport);\n      } else {\n        eos_thread_err(\"failed to decode report '%s'\", deletionreport64.c_str());\n      }\n    }\n  } else {\n    eos_thread_err(\"drop message does not contain all meta information: %s\",\n                   env.Env(envlen));\n    return Emsg(epname, error, EIO, \"drop replica [EIO]\",\n                \"missing meta information\");\n  }\n\n  gOFS->MgmStats.Add(\"Drop\", vid.uid, vid.gid, 1);\n  const char* ok = \"OK\";\n  error.setErrInfo(strlen(ok) + 1, ok);\n  EXEC_TIMING_END(\"Drop\");\n  return SFS_DATA;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/Event.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Event.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"common/Mapping.hh\"\n#include \"common/SecEntity.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/workflow/Workflow.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"common/Definitions.hh\"\n#include <XrdOuc/XrdOucEnv.hh>\n\n\n//----------------------------------------------------------------------------\n// Trigger an event\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::Event(const char* path,\n                 const char* ininfo,\n                 XrdOucEnv& env,\n                 XrdOucErrInfo& error,\n                 eos::common::VirtualIdentity& vid,\n                 const XrdSecEntity* client)\n{\n  static const char* epname = \"Event\";\n  char* auid = env.Get(\"mgm.ruid\");\n  char* agid = env.Get(\"mgm.rgid\");\n  char* asec = env.Get(\"mgm.sec\");\n  char* alogid = env.Get(\"mgm.logid\");\n  char* spath = env.Get(\"mgm.path\");\n  char* afid = env.Get(\"mgm.fid\");\n  char* aevent = env.Get(\"mgm.event\");\n  char* aworkflow = env.Get(\"mgm.workflow\");\n  char* errmsg = env.Get(\"mgm.errmsg\");\n  eos::common::VirtualIdentity localVid = eos::common::VirtualIdentity::Nobody();\n  int errc;\n\n  if (auid) {\n    localVid.uid = strtoul(auid, 0, 10);\n    localVid.uid_string = eos::common::Mapping::UidToUserName(localVid.uid, errc);\n  }\n\n  if (agid) {\n    localVid.gid = strtoul(agid, 0, 10);\n    localVid.gid_string = eos::common::Mapping::GidToGroupName(localVid.gid, errc);\n    localVid.allowed_gids = vid.allowed_gids;\n  }\n\n  if (asec) {\n    std::map<std::string, std::string> secmap =\n      eos::common::SecEntity::KeyToMap(std::string(asec));\n    localVid.prot = secmap[\"prot\"].c_str();\n    localVid.name = secmap[\"name\"].c_str();\n    localVid.host = secmap[\"host\"];\n    localVid.grps = secmap[\"grps\"];\n    localVid.app  = secmap[\"app\"];\n  }\n\n  if (alogid) {\n    tlLogId.SetLogId(alogid, error.getErrUser());\n  }\n\n  eos_thread_debug(\"vid.prot=%s, vid.uid=%u, vid.gid=%u\",\n                   vid.prot.c_str(), vid.uid, vid.gid);\n  eos_thread_debug(\"local.prot=%s, local.uid=%u, local.gid=%u\",\n                   localVid.prot.c_str(), localVid.uid, localVid.gid);\n\n  // Assuming that all workflow actions accept for prepare can modify a file,\n  // check that we have either write or prepare permission as necessary on path\n  bool isPrepare = (aevent != nullptr\n                    && std::string(aevent).find(\"prepare\") != std::string::npos);\n  const int mode = isPrepare  ?  P_OK  :  W_OK;\n  if (vid.prot != \"sss\" && gOFS->_access(spath, mode, error, localVid, \"\")) {\n    const char* emsg =\n      isPrepare ? \"event - you don't have prepare permissions [EPERM]\"\n      : \"event - you don't have write permission [EPERM]\";\n    return Emsg(epname, error, EPERM, emsg, spath);\n  }\n\n  ACCESSMODE_W;\n  MAYSTALL;\n  const char* inpath = path;\n  MAYREDIRECT;\n  EXEC_TIMING_BEGIN(\"Event\");\n  gOFS->MgmStats.Add(\"Event\", 0, 0, 1);\n\n  if (spath && afid && aevent && aworkflow) {\n    eos_thread_info(\"subcmd=event event=%s path=%s fid=%s\",\n                    aevent, spath, afid);\n    unsigned long long fid = strtoull(afid, 0, 16);\n    std::string event = aevent;\n    std::shared_ptr<eos::IFileMD> fmd;\n    std::shared_ptr<eos::IContainerMD> cmd;\n    Workflow workflow;\n    eos::IContainerMD::XAttrMap attrmap;\n    XrdOucString lWorkflow = aworkflow;\n\n    if (lWorkflow.beginswith(\"eos.\")) {\n      // Template workflow defined under the workflow proc directory\n      spath = (char*) gOFS->MgmProcWorkflowPath.c_str();\n      fid = 0;\n    }\n\n    {\n      eos::common::RWMutexReadLock vlock(FsView::gFsView.ViewMutex);\n\n      try {\n        if (fid) {\n          fmd = gOFS->eosFileService->getFileMD(fid);\n        } else {\n          fmd = gOFS->eosView->getFile(spath);\n          fid = fmd->getId();\n        }\n\n        cmd = gOFS->eosDirectoryService->getContainerMD(fmd->getContainerId());\n        eos::IFileMD::XAttrMap xattrs;\n\tgOFS->listAttributes(gOFS->eosView, &(*cmd), xattrs, false);\n\n        for (const auto& elem : xattrs) {\n          attrmap[elem.first] = elem.second;\n        }\n\n        // Check for attribute references\n        if (attrmap.count(\"sys.attr.link\")) {\n          try {\n            cmd = gOFS->eosView->getContainer(attrmap[\"sys.attr.link\"]);\n            eos::IFileMD::XAttrMap xattrs;\n\t    gOFS->listAttributes(gOFS->eosView, &(*cmd), xattrs, false);\n            for (const auto& elem : xattrs) {\n              if (!attrmap.count(elem.first)) {\n                attrmap[elem.first] = elem.second;\n              }\n            }\n          } catch (eos::MDException& e) {\n            cmd.reset();\n            errno = e.getErrno();\n            eos_thread_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\",\n                             e.getErrno(), e.getMessage().str().c_str());\n          }\n\n          attrmap.erase(\"sys.attr.link\");\n        }\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        eos_thread_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\",\n                         e.getErrno(), e.getMessage().str().c_str());\n      }\n    }\n\n    // Load the corresponding workflow\n    std::string strpath = spath;\n    workflow.Init(&attrmap, strpath, fid);\n    std::string decodedErrMessage =\n      \"trigger workflow - synchronous workflow failed\";\n\n    if (errmsg != nullptr) {\n      if (!eos::common::SymKey::Base64Decode(errmsg, decodedErrMessage)) {\n        decodedErrMessage = \"\";\n      }\n    }\n\n    // Trigger the specified event\n    const int rc = workflow.Trigger(event, aworkflow, localVid, ininfo,\n                                    decodedErrMessage);\n\n    if (rc == -1) {\n      int envlen = 0;\n\n      if (errno == ENOKEY) {\n        // No workflow defined\n        return Emsg(epname, error, EINVAL,\n                    \"trigger workflow - no workflow defined for\"\n                    \" <workflow>.<event> [EINVAL]\",\n                    env.Env(envlen));\n      } else {\n        if (!workflow.IsSync()) {\n          return Emsg(epname, error, EIO, \"trigger workflow - internal error [EIO]\",\n                      env.Env(envlen));\n        } else {\n          return Emsg(epname, error, errno, decodedErrMessage.c_str(),\n                      env.Env(envlen));\n        }\n      }\n    }\n\n    if (rc != 0) {\n      std::ostringstream errStr;\n      errStr << \"complete workflow - error while executing \"\n             << event << \" workflow [\" << MacroStringError(rc) << \"]\";\n\n      if (decodedErrMessage.empty()) {\n        return Emsg(epname, error, rc, errStr.str().c_str(), spath);\n      } else {\n        return Emsg(epname, error, rc, decodedErrMessage.c_str(), spath);\n      }\n    }\n  } else {\n    int envlen = 0;\n    const char* env_string = env.Env(envlen);\n    eos_thread_err(\"invalid parameters for event call: %s\", env_string);\n    return Emsg(epname, error, EINVAL,\n                \"notify - invalid parameters for event call: %s [EINVAL]\",\n                env_string);\n  }\n\n  const char* ok = \"OK\";\n  error.setErrInfo(strlen(ok) + 1, ok);\n  EXEC_TIMING_END(\"Event\");\n  return SFS_DATA;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/Fusex.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Fusex.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/zmq/ZMQ.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n\n//----------------------------------------------------------------------------\n// Fuse extension.\n// Will redirect to the RW master.\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::Fusex(const char* path,\n                 const char* ininfo,\n                 std::string protobuf,\n                 XrdOucEnv& env,\n                 XrdOucErrInfo& error,\n                 eos::common::VirtualIdentity& vid,\n                 const XrdSecEntity* client)\n{\n  static const char* epname = \"Fusex\";\n  ACCESSMODE_W;\n  const char* inpath = path;\n  MAYREDIRECT;\n  EXEC_TIMING_BEGIN(\"Eosxd::prot::SET\");\n  eos_static_debug(\"protobuf-len=%d\", protobuf.length());\n  eos::fusex::md md;\n\n  if (!md.ParseFromString(protobuf)) {\n    return Emsg(epname, error, EINVAL, \"parse protocol buffer [EINVAL]\", \"\");\n  }\n\n  // extract the client ID and translate to the app name\n  vid.app = gOFS->zMQ->gFuseServer.Client().client2app(md.clientid());\n\n  gOFS->MgmStats.Add(\"Eosxd::prot::SET\", vid.uid, vid.gid, 1, vid.app);\n\n  FUNCTIONMAYSTALL(\"Eosxd::prot::SET\", vid, error);\n  std::string resultstream;\n  std::string id = std::string(\"Fusex::sync:\") + vid.tident.c_str();\n  int rc = gOFS->zMQ->gFuseServer.HandleMD(id, md, vid, &resultstream, 0);\n\n  if (rc) {\n    return Emsg(epname, error, rc, \"handle request\", \"\");\n  }\n\n  if (resultstream.empty()) {\n    return Emsg(epname, error, EINVAL,\n                \"illegal request - no response [EINVAL]\", \"\");\n  }\n\n  std::string b64response;\n  eos::common::SymKey::Base64(resultstream, b64response);\n  XrdOucString response = \"Fusex:\";\n  response += b64response.c_str();\n  error.setErrInfo(response.length(), response.c_str());\n  EXEC_TIMING_END(\"Eosxd::prot::SET\");\n  return SFS_DATA;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/GetFusex.cc",
    "content": "// ----------------------------------------------------------------------\n// File: GetFusex.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n\n//----------------------------------------------------------------------------\n// Get MD for a fusex client\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::GetFusex(const char* path,\n                    const char* ininfo,\n                    XrdOucEnv& env,\n                    XrdOucErrInfo& error,\n                    eos::common::VirtualIdentity& vid,\n                    const XrdSecEntity* client)\n{\n  static const char* epname = \"GetFusex\";\n  ACCESSMODE_R;\n  FUNCTIONMAYSTALL(\"Eosxd::prot::STAT\", vid, error);\n  const char* inpath = path;\n  MAYREDIRECT;\n  EXEC_TIMING_BEGIN(\"Eosxd::prot::STAT\");\n  gOFS->MgmStats.Add(\"Eosxd::prot::STAT\", vid.uid, vid.gid, 1, vid.app);\n  ProcCommand procCommand;\n  std::string spath = path;\n\n  if (spath != \"/proc/user/\") {\n    return Emsg(epname, error, EINVAL,\n                \"call GetFusex - no proc path given [EINVAL]\", path);\n  }\n\n  int rc = 0;\n\n  if ((rc = procCommand.open(\"/proc/user/\", ininfo, vid, &error))) {\n    return rc;\n  } else {\n    size_t len;\n    const char* result = procCommand.GetResult(len);\n    char* dup_result = (char*) malloc(len);\n\n    if (result && dup_result) {\n      memcpy(dup_result, result, len);\n      XrdOucBuffer* buff = new XrdOucBuffer(dup_result, len);\n      error.setErrInfo(len, buff);\n      EXEC_TIMING_END(\"Eosxd::prot::STAT\");\n      return SFS_DATA;\n    } else {\n      return Emsg(epname, error, ENOMEM, \"call GetFusex - out of memory\", path);\n    }\n  }\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/Getfmd.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Getfmd.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"common/Path.hh\"\n#include \"common/BufferManager.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include <XrdOuc/XrdOucEnv.hh>\n\n//----------------------------------------------------------------------------\n// Return metadata in env representation\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::Getfmd(const char* path,\n                  const char* ininfo,\n                  XrdOucEnv& env,\n                  XrdOucErrInfo& error,\n                  eos::common::VirtualIdentity& vid,\n                  const XrdSecEntity* client)\n{\n  ACCESSMODE_W;\n  MAYSTALL;\n  const char* inpath = path;\n  MAYREDIRECT;\n  gOFS->MgmStats.Add(\"GetMd\", 0, 0, 1);\n  char* afid = env.Get(\"mgm.getfmd.fid\"); // decimal fid\n  eos::common::FileId::fileid_t fid = afid ? strtoull(afid, 0, 10) : 0;\n  XrdOucString response;\n\n  if (fid) {\n    std::string fullpath;\n    std::shared_ptr<eos::IFileMD> fmd;\n    eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, fid);\n    eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n\n    try {\n      fmd = gOFS->eosFileService->getFileMD(fid);\n      fullpath = gOFS->eosView->getUri(fmd.get());\n    } catch (eos::MDException& e) {\n      response = \"getfmd: retc=\";\n      response += e.getErrno();\n      error.setErrInfo(response.length() + 1, response.c_str());\n      return SFS_DATA;\n    }\n\n    eos::common::Path cPath(fullpath.c_str());\n    std::string fmdEnv = \"\";\n    fmd->getEnv(fmdEnv, true);\n    vlock.Release();\n    fmdEnv += \"&container=\";\n    // Patch parent name\n    XrdOucString safepath = cPath.GetParentPath();\n    eos::common::StringConversion::SealXrdPath(safepath);\n    fmdEnv += safepath.c_str();\n    response = \"getfmd: retc=0 \";\n    response += fmdEnv.c_str();\n\n    if (response.find(\"checksum=&\") != STR_NPOS) {\n      // XrdOucEnv does not deal with empty values [... sigh ...]\n      response.replace(\"checksum=&\", \"checksum=none&\");\n    }\n\n    // Patch the file name\n    safepath = cPath.GetName();\n\n    if (safepath.find(\"&\") != STR_NPOS) {\n      XrdOucString initial_name = \"name=\";\n      initial_name += safepath;\n      eos::common::StringConversion::SealXrdPath(safepath);\n      XrdOucString safe_name = \"name=\";\n      safe_name += safepath;\n      response.replace(initial_name, safe_name);\n    }\n  } else {\n    response = \"getfmd: retc=\";\n    response += EINVAL;\n  }\n\n  if (response.length() + 1 > 2 * eos::common::KB) {\n    const uint32_t aligned_sz = eos::common::GetPowerCeil(response.length() + 1,\n                                2 * eos::common::KB);\n    XrdOucBuffer* buff = mXrdBuffPool.Alloc(aligned_sz);\n\n    if (buff == nullptr) {\n      eos_static_err(\"msg=\\\"requested buffer allocation size too big\\\" \"\n                     \"req_sz=%llu max_sz=%i\", response.length(),\n                     mXrdBuffPool.MaxSize());\n      response = \"getfmd: retc=\";\n      response += ENOMEM;\n      error.setErrInfo(response.length() + 1, response.c_str());\n    } else {\n      (void) strncpy(buff->Buffer(), response.c_str(), response.length() + 1);\n      buff->SetLen(response.length() + 1);\n      error.setErrInfo(buff->DataLen(), buff);\n    }\n  } else {\n    error.setErrInfo(response.length() + 1, response.c_str());\n  }\n\n  return SFS_DATA;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/Mkdir.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Mkdir.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n\n//----------------------------------------------------------------------------\n// Make a directory and return its inode\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::Mkdir(const char* path,\n                 const char* ininfo,\n                 XrdOucEnv& env,\n                 XrdOucErrInfo& error,\n                 eos::common::VirtualIdentity& vid,\n                 const XrdSecEntity* client)\n{\n  ACCESSMODE_W;\n  MAYSTALL;\n  const char* inpath = path;\n  MAYREDIRECT;\n  gOFS->MgmStats.Add(\"Fuse-Mkdir\", vid.uid, vid.gid, 1);\n  char* smode = env.Get(\"mode\");\n  int retc = 0;\n\n  if (smode) {\n    struct stat buf;\n    XrdSfsMode mode = atoi(smode);\n    int retc1 = _mkdir(path, mode, error, vid, 0);\n    int retc2 = 0;\n\n    if (retc1 == SFS_OK) {\n      retc2 = lstat(path, &buf, error, client, 0);\n    }\n\n    if ((retc1 == SFS_OK) && (retc2 == SFS_OK)) {\n      char statinfo[16384];\n      // Convert into a char stream\n      sprintf(statinfo,\n              \"mkdir: %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu\\n\",\n              (unsigned long long) buf.st_dev,\n              (unsigned long long) buf.st_ino,\n              (unsigned long long) buf.st_mode,\n              (unsigned long long) buf.st_nlink,\n              (unsigned long long) buf.st_uid,\n              (unsigned long long) buf.st_gid,\n              (unsigned long long) buf.st_rdev,\n              (unsigned long long) buf.st_size,\n              (unsigned long long) buf.st_blksize,\n              (unsigned long long) buf.st_blocks,\n#ifdef __APPLE__\n              (unsigned long long) buf.st_atimespec.tv_sec,\n              (unsigned long long) buf.st_mtimespec.tv_sec,\n              (unsigned long long) buf.st_ctimespec.tv_sec,\n              (unsigned long long) buf.st_atimespec.tv_nsec,\n              (unsigned long long) buf.st_mtimespec.tv_nsec,\n              (unsigned long long) buf.st_ctimespec.tv_nsec\n#else\n              (unsigned long long) buf.st_atime,\n              (unsigned long long) buf.st_mtime,\n              (unsigned long long) buf.st_ctime,\n              (unsigned long long) buf.st_atim.tv_nsec,\n              (unsigned long long) buf.st_mtim.tv_nsec,\n              (unsigned long long) buf.st_ctim.tv_nsec\n#endif\n             );\n      error.setErrInfo(strlen(statinfo) + 1, statinfo);\n      return SFS_DATA;\n    } else {\n      retc = error.getErrInfo();\n    }\n  } else {\n    retc = EINVAL;\n  }\n\n  XrdOucString response = \"mkdir: retc=\";\n  response += retc;\n  error.setErrInfo(response.length() + 1, response.c_str());\n  return SFS_DATA;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/Open.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Open.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/ofs/XrdMgmOfsFile.hh\"\n#include \"mgm/macros/Macros.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n\n//----------------------------------------------------------------------------\n// Parallel IO mode open\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::Open(const char* path,\n                const char* ininfo,\n                XrdOucEnv& env,\n                XrdOucErrInfo& error,\n                eos::common::VirtualIdentity& vid,\n                const XrdSecEntity* client)\n{\n  ACCESSMODE_R;\n  MAYSTALL;\n  const char* inpath = path;\n  MAYREDIRECT;\n  gOFS->MgmStats.Add(\"OpenLayout\", vid.uid, vid.gid, 1);\n  XrdMgmOfsFile* file = new XrdMgmOfsFile(const_cast<char*>(client->tident));\n  XrdOucString opaque = ininfo;\n  int retc = SFS_ERROR;\n\n  if (file) {\n    opaque += \"&eos.cli.access=pio\";\n    int rc = file->open(path, SFS_O_RDONLY, 0, client, opaque.c_str());\n    error = file->error;\n\n    if (rc == SFS_REDIRECT) {\n      // When returning SFS_DATA the ecode represents the length of the data\n      // to be sent to the client.\n      error.setErrCode(strlen(error.getErrText()));\n      retc = SFS_DATA;\n    }\n\n    delete file;\n  } else {\n    const char* emsg = \"allocate file object\";\n    error.setErrInfo(strlen(emsg) + 1, emsg);\n    error.setErrCode(ENOMEM);\n  }\n\n  return retc;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/Readlink.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Readlink.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n\n//----------------------------------------------------------------------------\n// Resolve symbolic link\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::Readlink(const char* path,\n                    const char* ininfo,\n                    XrdOucEnv& env,\n                    XrdOucErrInfo& error,\n                    eos::common::VirtualIdentity& vid,\n                    const XrdSecEntity* client)\n{\n  ACCESSMODE_R;\n  MAYSTALL;\n  const char* inpath = path;\n  MAYREDIRECT;\n  gOFS->MgmStats.Add(\"Fuse-Readlink\", vid.uid, vid.gid, 1);\n  XrdOucString link = \"\";\n  int retc = 0;\n\n  if (readlink(path, error, link, client)) {\n    retc = (error.getErrInfo()) ? error.getErrInfo() : -1;\n  }\n\n  XrdOucString response = \"readlink: retc=\";\n  response += retc;\n\n  if (!retc) {\n    if (env.Get(\"eos.encodepath\")) {\n      link = eos::common::StringConversion::curl_escaped(link.c_str()).c_str();\n    }\n\n    response += \" \";\n    response += link.c_str();\n  }\n\n  error.setErrInfo(response.length() + 1, response.c_str());\n  return SFS_DATA;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/Redirect.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Redirect.cc\n// Author: Geoffray Adde - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/ofs/XrdMgmOfsFile.hh\"\n#include \"mgm/macros/Macros.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n\n//----------------------------------------------------------------------------\n// Get open redirect\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::Redirect(const char* path,\n                    const char* ininfo,\n                    XrdOucEnv& env,\n                    XrdOucErrInfo& error,\n                    eos::common::VirtualIdentity& vid,\n                    const XrdSecEntity* client)\n{\n  gOFS->MgmStats.Add(\"OpenRedirect\", vid.uid, vid.gid, 1);\n  XrdMgmOfsFile* file = new XrdMgmOfsFile(const_cast<char*>(client->tident));\n  int retc = SFS_ERROR;\n\n  if (file) {\n    XrdSfsFileOpenMode oflags = SFS_O_RDONLY;\n    mode_t omode = 0;\n\n    if (env.Get(\"eos.client.openflags\")) {\n      std::string openflags = env.Get(\"eos.client.openflags\");\n\n      if (openflags.find(\"wo\") != std::string::npos) {\n        oflags |= SFS_O_WRONLY;\n      }\n\n      if (openflags.find(\"rw\") != std::string::npos) {\n        oflags |= SFS_O_RDWR;\n      }\n\n      if (openflags.find(\"cr\") != std::string::npos) {\n        oflags |= SFS_O_CREAT;\n      }\n\n      if (openflags.find(\"tr\") != std::string::npos) {\n        oflags |= SFS_O_TRUNC;\n      }\n\n      std::string openmode = env.Get(\"eos.client.openmode\");\n      omode = (mode_t) strtol(openmode.c_str(), NULL, 8);\n    }\n\n    if ((oflags & SFS_O_CREAT) || (oflags & SFS_O_RDWR) ||\n        (oflags & SFS_O_TRUNC)) {\n      ACCESSMODE_W;\n      MAYSTALL;\n      const char* inpath = path;\n      MAYREDIRECT;\n    } else {\n      ACCESSMODE_R;\n      MAYSTALL;\n      const char* inpath = path;\n      MAYREDIRECT;\n    }\n\n    int rc = file->open(path, oflags, omode, client, ininfo);\n    std::string emsg = file->error.getErrText();\n\n    if (rc == SFS_REDIRECT) {\n      eos_thread_debug(\"success redirect=%s\", error.getErrText());\n      char buf[1024];\n      snprintf(buf, 1024, \":%d/%s?\", file->error.getErrInfo(), path);\n      emsg.replace(emsg.find(\"?\"), 1 , buf);\n      error.setErrInfo(emsg.size() + 1, emsg.c_str());\n      retc = SFS_DATA;\n    } else {\n      eos_thread_debug(\"failed redirect=%s\", error.getErrText());\n      error.setErrInfo(emsg.size() + 1, emsg.c_str());\n      error.setErrCode(file->error.getErrInfo());\n    }\n\n    delete file;\n  } else {\n    const char* emsg = \"allocate file object\";\n    error.setErrInfo(strlen(emsg) + 1, emsg);\n    error.setErrCode(ENOMEM);\n  }\n\n  return retc;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/Stat.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Stat.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n\n//----------------------------------------------------------------------------\n// Stat a file or directory.\n// Will redirect to the RW master.\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::FuseStat(const char* path,\n                    const char* ininfo,\n                    XrdOucEnv& env,\n                    XrdOucErrInfo& error,\n                    eos::common::VirtualIdentity& vid,\n                    const XrdSecEntity* client)\n{\n  ACCESSMODE_R_MASTER;\n  MAYSTALL;\n  const char* inpath = path;\n  MAYREDIRECT;\n  gOFS->MgmStats.Add(\"Fuse-Stat\", vid.uid, vid.gid, 1);\n  struct stat buf;\n  int retc = lstat(path, &buf, error, client, ininfo);\n\n  if (retc == SFS_OK) {\n    char* statinfo = static_cast<char*>(malloc(16384));\n    sprintf(statinfo,\n            \"stat: %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu\\n\",\n            (unsigned long long) buf.st_dev,\n            (unsigned long long) buf.st_ino,\n            (unsigned long long) buf.st_mode,\n            (unsigned long long) buf.st_nlink,\n            (unsigned long long) buf.st_uid,\n            (unsigned long long) buf.st_gid,\n            (unsigned long long) buf.st_rdev,\n            (unsigned long long) buf.st_size,\n            (unsigned long long) buf.st_blksize,\n            (unsigned long long) buf.st_blocks,\n#ifdef __APPLE__\n            (unsigned long long) buf.st_atimespec.tv_sec,\n            (unsigned long long) buf.st_mtimespec.tv_sec,\n            (unsigned long long) buf.st_ctimespec.tv_sec,\n            (unsigned long long) buf.st_atimespec.tv_nsec,\n            (unsigned long long) buf.st_mtimespec.tv_nsec,\n            (unsigned long long) buf.st_ctimespec.tv_nsec\n#else\n            (unsigned long long) buf.st_atime,\n            (unsigned long long) buf.st_mtime,\n            (unsigned long long) buf.st_ctime,\n            (unsigned long long) buf.st_atim.tv_nsec,\n            (unsigned long long) buf.st_mtim.tv_nsec,\n            (unsigned long long) buf.st_ctim.tv_nsec\n#endif\n           );\n    // Ownership of statinfo is taken by the xrd_buff object.\n    // Error then takes ownership of the xrd_buff object\n    XrdOucBuffer* xrd_buff = new XrdOucBuffer(statinfo, strlen(statinfo));\n    error.setErrInfo(xrd_buff->BuffSize(), xrd_buff);\n    return SFS_DATA;\n  }\n\n  XrdOucString response = \"stat: retc=\";\n  response += error.getErrInfo();\n  error.setErrInfo(response.length() + 1, response.c_str());\n  return SFS_DATA;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/Statvfs.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Statvfs.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"common/utils/RandUtils.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/fsview/FsView.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n\n//----------------------------------------------------------------------------\n// Virtual filesystem stat\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::Statvfs(const char* path,\n                   const char* ininfo,\n                   XrdOucEnv& env,\n                   XrdOucErrInfo& error,\n                   eos::common::VirtualIdentity& vid,\n                   const XrdSecEntity* client)\n{\n  ACCESSMODE_R;\n  MAYSTALL;\n  const char* inpath = path;\n  MAYREDIRECT;\n  gOFS->MgmStats.Add(\"Fuse-Statvfs\", vid.uid, vid.gid, 1);\n  XrdOucString space = env.Get(\"path\");\n\n  if (env.Get(\"eos.encodepath\")) {\n    space = eos::common::StringConversion::curl_unescaped(space.c_str()).c_str();\n  }\n\n  static XrdSysMutex statvfsmutex;\n  static time_t laststat = 0;\n  static long long freebytes = 0;\n  static long long freefiles = 0;\n  static long long maxbytes = 0;\n  static long long maxfiles = 0;\n  long long l_freebytes = 0;\n  long long l_freefiles = 0;\n  long long l_maxbytes = 0;\n  long long l_maxfiles = 0;\n  int retc = 0;\n\n  if (space.length()) {\n    int deepness = 0;\n    int spos = 0;\n\n    while ((spos = space.find(\"/\", spos)) != STR_NPOS) {\n      deepness++;\n      spos++;\n    }\n\n    if ((!getenv(\"EOS_MGM_STATVFS_ONLY_QUOTA\") && (deepness < 4)) ||\n        (getenv(\"EOS_MGM_STATVFS_ONLY_SPACE\"))) {\n      statvfsmutex.Lock();\n      time_t now = time(NULL);\n\n      // Use caching to avoid often expensive space recomputations\n      if ((now - laststat) > eos::common::getRandom(5, 15)) {\n        // Take the sums from all file systems in 'default' space\n        eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n\n        if (FsView::gFsView.mSpaceView.count(\"default\")) {\n          freebytes =\n            FsView::gFsView.mSpaceView[\"default\"]->SumLongLong(\"stat.statfs.freebytes\",\n                false);\n          freefiles =\n            FsView::gFsView.mSpaceView[\"default\"]->SumLongLong(\"stat.statfs.ffree\", false);\n          maxbytes =\n            FsView::gFsView.mSpaceView[\"default\"]->SumLongLong(\"stat.statfs.capacity\",\n                false);\n          maxfiles =\n            FsView::gFsView.mSpaceView[\"default\"]->SumLongLong(\"stat.statfs.files\", false);\n        }\n\n        laststat = now;\n      }\n\n      l_freebytes = freebytes;\n      l_freefiles = freefiles;\n      l_maxbytes  = maxbytes;\n      l_maxfiles  = maxfiles;\n      statvfsmutex.UnLock();\n    } else {\n      const std::string sspace = space.c_str();\n      Quota::GetIndividualQuota(vid, sspace, l_maxbytes, l_freebytes,\n                                l_maxfiles, l_freefiles, true);\n    }\n  } else {\n    retc = EINVAL;\n  }\n\n  XrdOucString response = \"statvfs: retc=\";\n  response += retc;\n\n  if (!retc) {\n    char val[1025];\n    snprintf(val, 1024, \"%lld\", l_freebytes);\n    response += \" f_avail_bytes=\";\n    response += val;\n    snprintf(val, 1024, \"%lld\", l_freefiles);\n    response += \" f_avail_files=\";\n    response += val;\n    snprintf(val, 1024, \"%lld\", l_maxbytes);\n    response += \" f_max_bytes=\";\n    response += val;\n    snprintf(val, 1024, \"%lld\", l_maxfiles);\n    response += \" f_max_files=\";\n    response += val;\n  }\n\n  error.setErrInfo(response.length() + 1, response.c_str());\n  return SFS_DATA;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/Symlink.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Symlink.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n\n//----------------------------------------------------------------------------\n// Create symbolic link\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::Symlink(const char* path,\n                   const char* ininfo,\n                   XrdOucEnv& env,\n                   XrdOucErrInfo& error,\n                   eos::common::VirtualIdentity& vid,\n                   const XrdSecEntity* client)\n{\n  ACCESSMODE_W;\n  MAYSTALL;\n  const char* inpath = path;\n  MAYREDIRECT;\n  gOFS->MgmStats.Add(\"Fuse-Symlink\", vid.uid, vid.gid, 1);\n  char* starget = env.Get(\"target\");\n  int retc = 0;\n\n  if (starget) {\n    XrdOucString target = starget;\n\n    if (env.Get(\"eos.encodepath\")) {\n      target = eos::common::StringConversion::curl_unescaped(starget).c_str();\n    } else {\n      eos::common::StringConversion::UnsealXrdPath(target);\n    }\n\n    if (symlink(path, target.c_str(), error, client, 0, 0)) {\n      retc = error.getErrInfo();\n    }\n  } else {\n    retc = EINVAL;\n  }\n\n  XrdOucString response = \"symlink: retc=\";\n  response += retc;\n  error.setErrInfo(response.length() + 1, response.c_str());\n  return SFS_DATA;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/Utimes.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Utimes.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n\n//----------------------------------------------------------------------------\n// Set utimes\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::Utimes(const char* path,\n                  const char* ininfo,\n                  XrdOucEnv& env,\n                  XrdOucErrInfo& error,\n                  eos::common::VirtualIdentity& vid,\n                  const XrdSecEntity* client)\n{\n  ACCESSMODE_W;\n  MAYSTALL;\n  const char* inpath = path;\n  MAYREDIRECT;\n  gOFS->MgmStats.Add(\"Fuse-Utimes\", vid.uid, vid.gid, 1);\n  char* tv1_sec = env.Get(\"tv1_sec\");\n  char* tv1_nsec = env.Get(\"tv1_nsec\");\n  char* tv2_sec = env.Get(\"tv2_sec\");\n  char* tv2_nsec = env.Get(\"tv2_nsec\");\n  int retc = 0;\n\n  if (tv1_sec && tv1_nsec && tv2_sec && tv2_nsec) {\n    struct timespec tvp[2];\n    // ctime\n    tvp[0].tv_sec = strtol(tv1_sec, 0, 10);\n    tvp[0].tv_nsec = strtol(tv1_nsec, 0, 10);\n    // mtime\n    tvp[1].tv_sec = strtol(tv2_sec, 0, 10);\n    tvp[1].tv_nsec = strtol(tv2_nsec, 0, 10);\n\n    if (_utimes(path, tvp, error, vid, ininfo)) {\n      retc = error.getErrInfo();\n    }\n  } else {\n    retc = EINVAL;\n  }\n\n  XrdOucString response = \"utimes: retc=\";\n  response += retc;\n  error.setErrInfo(response.length() + 1, response.c_str());\n  return SFS_DATA;\n}\n"
  },
  {
    "path": "mgm/ofs/fsctl/Version.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Version.cc\n// Author: Geoffray Adde - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n\n//----------------------------------------------------------------------------\n// Get EOS version and features\n//----------------------------------------------------------------------------\nint\nXrdMgmOfs::Version(const char* path,\n                   const char* ininfo,\n                   XrdOucEnv& env,\n                   XrdOucErrInfo& error,\n                   eos::common::VirtualIdentity& vid,\n                   const XrdSecEntity* client)\n{\n  ACCESSMODE_R;\n  MAYSTALL;\n  const char* inpath = path;\n  MAYREDIRECT;\n  gOFS->MgmStats.Add(\"Version\", 0, 0, 1);\n  bool features = env.Get(\"mgm.version.features\");\n  XrdOucString response = \"version: retc=\";\n  int retc = 0;\n  XrdOucErrInfo errInfo;\n  ProcCommand procCommand;\n  const char* cmdInfo = features ? \"mgm.cmd=version&mgm.option=f\"\n                        : \"mgm.cmd=version\";\n\n  if (procCommand.open(\"/proc/user\", cmdInfo, vid, &errInfo)) {\n    retc = EINVAL;\n  }\n\n  response += retc;\n\n  if (!retc) {\n    char buff[4096];\n    response += \" \";\n\n    while (int nread = procCommand.read(0, buff, 4095)) {\n      buff[nread] = '\\0';\n      response += buff;\n\n      if (nread != 4095) {\n        break;\n      }\n    }\n  }\n\n  error.setErrInfo(response.length() + 1, response.c_str());\n  return SFS_DATA;\n}\n"
  },
  {
    "path": "mgm/pathrouting/PathRouting.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file PathRouting.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/pathrouting/PathRouting.hh\"\n#include \"common/Path.hh\"\n#include \"common/StringConversion.hh\"\n#include <XrdCl/XrdClURL.hh>\n#include <sstream>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nPathRouting::~PathRouting()\n{\n  mThread.join();\n}\n\n//------------------------------------------------------------------------------\n// Clear the routing table\n//------------------------------------------------------------------------------\nvoid\nPathRouting::Clear()\n{\n  eos::common::RWMutexWriteLock lock(mPathRouteMutex);\n  mPathRoute.clear();\n}\n\n//------------------------------------------------------------------------------\n// Add path endpoint pair to the routing table\n//------------------------------------------------------------------------------\nbool\nPathRouting::Add(const std::string& path, RouteEndpoint&& endpoint)\n{\n  std::string string_rep = endpoint.ToString();\n  eos::common::RWMutexWriteLock route_wr_lock(mPathRouteMutex);\n  auto it = mPathRoute.find(path);\n\n  if (it == mPathRoute.end()) {\n    auto it_emplace = mPathRoute.emplace(path, std::list<RouteEndpoint>());\n    it_emplace.first->second.emplace_back(std::move(endpoint));\n  } else {\n    bool found = false;\n\n    for (const auto& ep : it->second) {\n      if (ep == endpoint) {\n        found = true;\n        break;\n      }\n    }\n\n    if (found) {\n      return false;\n    }\n\n    it->second.emplace_back(std::move(endpoint));\n  }\n\n  eos_debug(\"added route %s => %s\", path.c_str(), string_rep.c_str());\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Remove routing for the corresponding path\n//------------------------------------------------------------------------------\nbool\nPathRouting::Remove(const std::string& path)\n{\n  eos::common::RWMutexWriteLock route_wr_lock(mPathRouteMutex);\n  auto it = mPathRoute.find(path);\n\n  if (path.empty() || (it == mPathRoute.end())) {\n    return false;\n  }\n\n  mPathRoute.erase(it);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Route a path according to the configured routing table\n//------------------------------------------------------------------------------\nPathRouting::Status\nPathRouting::Reroute(const char* inpath, const char* ininfo,\n                     eos::common::VirtualIdentity& vid,\n                     std::string& host, int& port, std::string& stat_info)\n{\n  // Process and extract the path for which we need to do the routing\n  std::string path = (inpath ? inpath : \"\");\n  std::string surl = path;\n\n  if (ininfo) {\n    surl += \"?\";\n    surl += ininfo;\n  }\n\n  XrdCl::URL url(surl);\n  XrdCl::URL::ParamsMap param = url.GetParams();\n  // If there is a routing tag in the CGI, we use that one to map. Also note the\n  // tags have priorities.\n  std::list<std::string> tags {\"eos.route\", \"mgm.path\", \"mgm.quota.space\"};\n\n  for (const auto& tag : tags) {\n    auto it = param.find(tag);\n\n    if (it != param.end() && it->second.length()) {\n      path = it->second;\n      break;\n    }\n  }\n\n  // Make sure path is not empty and is '/' terminated\n  if (path.empty()) {\n    eos_debug(\"%s\", \"msg=\\\"input path is empty\\\"\");\n    return Status::NOROUTING;\n  }\n\n  path = eos::common::StringConversion::curl_unescaped(path.c_str()).c_str();\n  eos::common::Path cPath(path.c_str());\n  path = cPath.GetPath();\n\n  // Make sure path is / terminated\n  if (path.back() != '/') {\n    path += '/';\n  }\n\n  eos_debug(\"path=%s map_route_size=%d\", path.c_str(), mPathRoute.size());\n  eos::common::RWMutexReadLock route_rd_lock(mPathRouteMutex);\n\n  if (mPathRoute.empty()) {\n    eos_debug(\"%s\", \"msg=\\\"no routes defined\\\"\");\n    return Status::NOROUTING;\n  }\n\n  auto it = mPathRoute.find(path);\n\n  if (it == mPathRoute.end()) {\n    // Try to find the longest possible match\n    if (!cPath.GetSubPathSize()) {\n      eos_debug(\"path=%s has no subpath\", path.c_str());\n      return Status::NOROUTING;\n    }\n\n    for (size_t i = cPath.GetSubPathSize() - 1; i > 0; i--) {\n      eos_debug(\"[route] %s => %s\\n\", path.c_str(), cPath.GetSubPath(i));\n      it = mPathRoute.find(cPath.GetSubPath(i));\n\n      if (it != mPathRoute.end()) {\n        break;\n      }\n    }\n\n    // If no match found then return\n    if (it == mPathRoute.end()) {\n      return Status::NOROUTING;\n    }\n  }\n\n  std::ostringstream oss;\n  oss << \"Rt:\";\n  // Try to find the master endpoint, if none exists then just redirect to the\n  // first endpoint in the list if reachable\n  auto* master_ep = &it->second.front();\n\n  for (auto& endpoint : it->second) {\n    if (endpoint.mIsOnline.load() && endpoint.mIsMaster.load()) {\n      master_ep = &endpoint;\n      break;\n    }\n  }\n\n  if (!master_ep->mIsOnline.load()) {\n    eos_warning(\"msg=\\\"no online endpoints for route\\\" path=%s\", it->first.c_str());\n    return Status::STALL;\n  }\n\n  // Http redirection\n  if (vid.prot == \"http\" || vid.prot == \"https\") {\n    port = master_ep->GetHttpPort();\n    oss << vid.prot.c_str();\n  } else {\n    // XRootD redirection\n    port = master_ep->GetXrdPort();\n    oss << \"xrd\";\n  }\n\n  host = master_ep->GetHostname();\n  oss << \":\" << host;\n  stat_info = oss.str();\n  eos_debug(\"re-routing path=%s using match_path=%s to host=%s port=%d\",\n            path.c_str(), it->first.c_str(), host.c_str(), port);\n  return Status::REROUTE;\n}\n\n//------------------------------------------------------------------------------\n// Get routes listing\n//------------------------------------------------------------------------------\nbool\nPathRouting::GetListing(const std::string& path, std::string& out) const\n{\n  std::ostringstream oss;\n  eos::common::RWMutexReadLock route_rd_lock(mPathRouteMutex);\n  auto printRoute =\n  [&oss](const std::pair<const std::string, std::list<RouteEndpoint>>& route) {\n    bool first = true;\n    oss << route.first << \" => \";\n\n    for (const auto& endpoint : route.second) {\n      if (!first) {\n        oss << \",\";\n      }\n\n      if (!endpoint.mIsOnline.load()) {\n        oss << \"_\";\n      } else if (endpoint.mIsMaster.load()) {\n        oss << \"*\";\n      }\n\n      oss << endpoint.ToString();\n      first = false;\n    }\n\n    oss << std::endl;\n  };\n\n  // List all paths\n  if (path.empty()) {\n    for (const auto& elem : mPathRoute) {\n      printRoute(elem);\n    }\n  } else {\n    auto it = mPathRoute.find(path);\n\n    if (it == mPathRoute.end()) {\n      return false;\n    }\n\n    printRoute(*it);\n  }\n\n  out = oss.str();\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Method executed by an async thread which is updating the current master\n// endpoint for each routing\n//------------------------------------------------------------------------------\nvoid\nPathRouting::UpdateEndpointsStatus(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"PathRouter\");\n  while (!assistant.terminationRequested()) {\n    {\n      eos::common::RWMutexReadLock route_rd_lock(mPathRouteMutex);\n\n      for (auto& route : mPathRoute) {\n        int num_masters = 0;\n        eos_debug(\"checking route='%s'\", route.first.c_str());\n\n        for (auto& endpoint : route.second) {\n          endpoint.UpdateStatus();\n\n          if (endpoint.mIsOnline.load() && endpoint.mIsMaster.load()) {\n            ++num_masters;\n          }\n        }\n\n        // There is smth awfully wrong if we have more than two masters ...\n        if (num_masters >= 2) {\n          eos_warning(\"there is more than one master for route path=%s\",\n                      route.first.c_str());\n\n          // Mark them all as offline so that we stall the clients\n          for (auto& endpoint : route.second) {\n            endpoint.mIsOnline.store(false);\n            endpoint.mIsMaster.store(false);\n          }\n        }\n      }\n    }\n    assistant.wait_for(mTimeout);\n  }\n}\n\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/pathrouting/PathRouting.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file PathRouting.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/Mapping.hh\"\n#include \"common/Logging.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"mgm/routeendpoint/RouteEndpoint.hh\"\n#include <map>\n#include <list>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class PathRouting\n//------------------------------------------------------------------------------\nclass PathRouting: public eos::common::LogId\n{\npublic:\n\n  //! Reroute response type\n  enum class Status {\n    REROUTE,   ///! Route was found and available\n    NOROUTING, ///! No route found\n    STALL      ///! Route found but no endpoint available\n  };\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param upd_timeout async thread update interval\n  //----------------------------------------------------------------------------\n  PathRouting(std::chrono::seconds upd_timeout = std::chrono::seconds(5)):\n    mTimeout(upd_timeout)\n  {\n    if (mTimeout.count()) {\n      mThread.reset(&PathRouting::UpdateEndpointsStatus, this);\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~PathRouting();\n\n  //----------------------------------------------------------------------------\n  //! @brief Route a path according to the configured routing table. This\n  //! function does the path translation according to the configured routing\n  //! table. It applies the 'longest' matching rule.\n  //!\n  //! @param inpath path to route\n  //! @param ininfo opaque information\n  //! @param vid user virtual identity\n  //! @param host redirection host\n  //! @param port redirection port\n  //! @param stat_info stat info string to be aggregated by MgmStats\n  //!\n  //! @return Status enum representing the state of the routing\n  //----------------------------------------------------------------------------\n  Status Reroute(const char* inpath, const char* ininfo,\n                 eos::common::VirtualIdentity& vid,\n                 std::string& host, int& port, std::string& stat_info);\n\n  //----------------------------------------------------------------------------\n  //! Add a source/target pair to the path routing table\n  //!\n  //! @param path prefix path to route\n  //! @param endpoint endpoint for the routing\n  //!\n  //! @return true if route added, otherwise false\n  //----------------------------------------------------------------------------\n  bool Add(const std::string& path, RouteEndpoint&& endpoint);\n\n  //----------------------------------------------------------------------------\n  //! Remove routing for the corresponding path\n  //!\n  //! @param path routing path to be removed\n  //!\n  //! @return true if successfully removed, otherwise false\n  //----------------------------------------------------------------------------\n  bool Remove(const std::string& path);\n\n  //----------------------------------------------------------------------------\n  //! Clear all the stored entries in the path routing table\n  //----------------------------------------------------------------------------\n  void Clear();\n\n  //----------------------------------------------------------------------------\n  //! Get routes listing\n  //!\n  //! @param path get listing for a particular path, if empty then all routes\n  //!        will be returned\n  //! @param out output string\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool GetListing(const std::string& path, std::string& out) const;\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Method executed by an async thread which is updating the current master\n  //! endpoint for each routing\n  //!\n  //! @param assistant thread executing the method\n  //----------------------------------------------------------------------------\n  void UpdateEndpointsStatus(ThreadAssistant& assistant) noexcept;\n\n  std::map<std::string, std::list<RouteEndpoint>> mPathRoute;\n  mutable eos::common::RWMutex mPathRouteMutex;\n  AssistedThread mThread; ///< Thread updating the master endpoints\n  std::chrono::seconds mTimeout; ///< Update timeout\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/placement/ClusterDataTypes.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ClusterDataTypes\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_CLUSTERDATATYPES_HH\n#define EOS_CLUSTERDATATYPES_HH\n\n#include \"common/FileSystem.hh\"\n#include <array>\n#include <unordered_map>\n\nnamespace eos::mgm::placement\n{\n\nusing fsid_t = eos::common::FileSystem::fsid_t;\n\n// We use a item_id to represent a storage element, negative numbers represent\n// storage elements in the hierarchy, ie. groups/racks/room/site etc.\nusing item_id_t = int32_t;\nusing epoch_id_t = uint64_t;\nusing ConfigStatus = eos::common::ConfigStatus;\nusing ActiveStatus = eos::common::ActiveStatus;\n// A struct representing a disk, this is the lowest level of the hierarchy,\n// disk ids map 1:1 to fsids, however it is necessary that the last bit of fsid_t\n// is not used, as we use a int32_t for the rest of the placement hierarchy.\n// the struct is packed to 8 bytes, so upto 8192 disks\n// can fit in a single 64kB cache, it is recommended to keep this struct aligned\ninline ActiveStatus getActiveStatus(ActiveStatus status,\n                                    eos::common::BootStatus bstatus)\n{\n  if (status == ActiveStatus::kOnline) {\n    if (bstatus != eos::common::BootStatus::kBooted) {\n      return ActiveStatus::kOffline;\n    }\n  }\n\n  return status;\n}\n\nstruct Disk {\n  fsid_t id;\n  mutable std::atomic<ConfigStatus> config_status {ConfigStatus::kUnknown};\n  mutable std::atomic<ActiveStatus> active_status {ActiveStatus::kUndefined};\n\n  mutable std::atomic<uint8_t> weight{0}; // we really don't need floating point precision\n  mutable std::atomic<uint8_t> percent_used{0};\n\n  Disk() : id(0) {}\n\n  explicit Disk(fsid_t _id) : id(_id) {}\n\n  Disk(fsid_t _id, ConfigStatus _config_status,\n       ActiveStatus _active_status, uint8_t _weight, uint8_t _percent_used = 0)\n    : id(_id), config_status(_config_status), active_status(_active_status),\n      weight(_weight), percent_used(_percent_used)\n  {}\n\n  // TODO future: these copy constructors must only be used at construction time\n  // explicit copy constructor as atomic types are not copyable\n  Disk(const Disk& other)\n    : Disk(other.id, other.config_status.load(std::memory_order_relaxed),\n           other.active_status.load(std::memory_order_relaxed),\n           other.weight.load(std::memory_order_relaxed),\n           other.percent_used.load(std::memory_order_relaxed))\n  {\n  }\n\n  Disk& operator=(const Disk& other)\n  {\n    id = other.id;\n    config_status.store(other.config_status.load(std::memory_order_relaxed),\n                        std::memory_order_relaxed);\n    active_status.store(other.active_status.load(std::memory_order_relaxed),\n                        std::memory_order_relaxed);\n    weight.store(other.weight.load(std::memory_order_relaxed),\n                 std::memory_order_relaxed);\n    percent_used.store(other.percent_used.load(std::memory_order_relaxed),\n                       std::memory_order_relaxed);\n    return *this;\n  }\n\n  friend bool\n  operator<(const Disk& l, const Disk& r)\n  {\n    return l.id < r.id;\n  }\n\n  std::string to_string() const\n  {\n    std::stringstream ss;\n    ss << \"id: \" << id << \"\\n\"\n       << \"ConfigStatus: \"\n       << common::FileSystem::GetConfigStatusAsString(config_status.load(\n             std::memory_order_relaxed))\n       << \"\\n\"\n       << \"ActiveStatus: \"\n       << common::FileSystem::GetActiveStatusAsString(active_status.load(\n             std::memory_order_relaxed))\n       << \"\\n\"\n       << \"Weight: \" << static_cast<uint16_t>(weight.load(std::memory_order_relaxed))\n       << \"\\n\"\n       << \"UsedPercent: \" << static_cast<uint16_t>(percent_used.load(\n             std::memory_order_relaxed));\n    return ss.str();\n  }\n};\n\nstatic_assert(sizeof(Disk) == 8, \"Disk data type not aligned to 8 bytes!\");\n\n// some common storage elements, these could be user defined in the future\nenum class StdBucketType : uint8_t {\n  GROUP = 0,\n  RACK,\n  ROOM,\n  SITE,\n  ROOT,\n  COUNT\n};\n\nconstexpr uint8_t\nget_bucket_type(StdBucketType t)\n{\n  return static_cast<uint8_t>(t);\n}\n\ninline std::string BucketTypeToStr(StdBucketType t)\n{\n  switch (t) {\n  case StdBucketType::GROUP:\n    return \"group\";\n\n  case StdBucketType::RACK:\n    return \"rack\";\n\n  case StdBucketType::ROOM:\n    return \"room\";\n\n  case StdBucketType::SITE:\n    return \"site\";\n\n  case StdBucketType::ROOT:\n    return \"root\";\n\n  default:\n    return \"unknown\";\n  }\n}\n\n// Constant to offset the group id, so group ids would be starting from this offset\n// in memory they'd be stored at -group_id\nconstexpr int kBaseGroupOffset = -10;\n\n\n// Return bucket index from group id, guaranteed to be -ve\n// If we have groups > INT_MAX we're in UB land, but this is mostly not possible\ninline constexpr item_id_t\nGroupIDtoBucketID(unsigned int group_index)\n{\n  return kBaseGroupOffset - group_index;\n}\n\ninline constexpr unsigned int\nBucketIDtoGroupID(item_id_t bucket_id)\n{\n  return kBaseGroupOffset - bucket_id;\n}\n\nstruct Bucket {\n  item_id_t id;\n  uint32_t total_weight;\n  uint8_t bucket_type;\n  std::vector<item_id_t> items;\n  std::string location;\n  std::string full_geotag;\n\n  Bucket() = default;\n\n  Bucket(item_id_t _id, uint8_t type)\n    : id(_id), total_weight(0), bucket_type(type)\n  {\n  }\n\n  friend bool\n  operator<(const Bucket& l, const Bucket& r)\n  {\n    return l.id < r.id;\n  }\n\n  std::string to_string() const\n  {\n    std::string group_str;\n\n    if (bucket_type == get_bucket_type(StdBucketType::GROUP)) {\n      group_str = \"Group Index: \" +\n                  std::to_string(BucketIDtoGroupID(id)) + \"\\n\";\n    }\n\n    std::stringstream ss;\n    ss << \"id: \" << id << \"\\n\"\n       << group_str\n       << \"Total Weight: \" << total_weight << \"\\n\"\n       << \"Bucket Type: \"\n       << BucketTypeToStr(static_cast<StdBucketType>(bucket_type))\n       << \"\\nItem List: \";\n\n    for (const auto& it : items) {\n      ss << it << \", \";\n    }\n\n    return ss.str();\n  }\n};\n\nstruct ClusterData {\n  std::vector<Disk> disks;\n  std::vector<Bucket> buckets;\n  std::vector<std::vector<uint64_t>> disk_tags;\n\n  // Diagnostic data structures. Not used in hot path\n  std::unordered_map<fsid_t, std::string> disk_tag_map;\n  std::unordered_map<uint64_t, std::string> geo_hash_registry;\n\n  bool setDiskStatus(fsid_t id, ConfigStatus status)\n  {\n    if (id > disks.size()) {\n      return false;\n    }\n\n    disks[id - 1].config_status.store(status, std::memory_order_release);\n    return true;\n  }\n\n  bool setDiskStatus(fsid_t id, ActiveStatus status)\n  {\n    if (id > disks.size()) {\n      return false;\n    }\n\n    disks[id - 1].active_status.store(status, std::memory_order_release);\n    return true;\n  }\n\n  bool setDiskWeight(fsid_t id, uint8_t weight)\n  {\n    if (id > disks.size()) {\n      return false;\n    }\n\n    disks[id - 1].weight.store(weight, std::memory_order_release);\n    return true;\n  }\n\n  std::string getDisksAsString() const\n  {\n    std::string result_str;\n    result_str.append(\"Total Disks: \");\n    result_str.append(std::to_string(disks.size()));\n    result_str.append(\"\\n\");\n\n    for (const auto& d : disks) {\n      result_str.append(d.to_string());\n      result_str.append(\"\\n\");\n      if (auto kv= disk_tag_map.find(d.id); kv != disk_tag_map.end()) {\n        result_str.append(\"geotag: \");\n        result_str.append(kv->second);\n        result_str.append(\"\\n\");\n      }\n\n    }\n\n    return result_str;\n  }\n\n  std::string getBucketsAsString() const\n  {\n    std::string result_str;\n\n    for (const auto& b : buckets) {\n      if (b.id == 0 && b.bucket_type == 0) {\n        continue;\n      }\n\n      result_str.append(b.to_string());\n      result_str.append(\"\\n\");\n    }\n\n    return result_str;\n  }\n\n};\n\ninline bool isValidBucketId(item_id_t id, const ClusterData& data)\n{\n  return id < 0 && (-id < (int)data.buckets.size());\n}\n\n\n} // eos::mgm::placement\n\n#endif // EOS_CLUSTERDATATYPES_HH\n"
  },
  {
    "path": "mgm/placement/ClusterMap.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ClusterMap.cc\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#include \"mgm/placement/ClusterMap.hh\"\n\n#include <xxhash.h>\n\nnamespace eos::mgm::placement\n{\n\nStorageHandler\nClusterMgr::getStorageHandler(size_t max_buckets)\n{\n  return StorageHandler(*this, max_buckets);\n}\n\nClusterMgr::ClusterDataPtr\nClusterMgr::getClusterData()\n{\n  return {mClusterData.get(), cluster_mgr_rcu};\n}\n\nvoid\nClusterMgr::addClusterData(ClusterData&& data)\n{\n  // mClusterData is an atomic unique ptr, so reset returns a ptr\n  // whose deletion we need to do outside the lock\n  ClusterData* old_ptr {nullptr};\n  {\n    std::unique_lock l(cluster_mgr_rcu);\n    old_ptr = mClusterData.reset(new ClusterData(std::move(data)));\n    mCurrentEpoch.fetch_add(1, std::memory_order_release);\n  }\n  delete old_ptr;\n}\n\n\nbool\nClusterMgr::setDiskStatus(fsid_t disk_id, ConfigStatus status)\n{\n  eos::common::RCUReadLock rlock(cluster_mgr_rcu);\n  return mClusterData->setDiskStatus(disk_id, status);\n}\n\nbool\nClusterMgr::setDiskStatus(fsid_t disk_id, ActiveStatus status)\n{\n  eos::common::RCUReadLock rlock(cluster_mgr_rcu);\n  return mClusterData->setDiskStatus(disk_id, status);\n}\n\nbool\nClusterMgr::setDiskWeight(fsid_t disk_id, uint8_t weight)\n{\n  eos::common::RCUReadLock rlock(cluster_mgr_rcu);\n  if (mClusterData->setDiskWeight(disk_id, weight)) {\n    mCurrentEpoch.fetch_add(1, std::memory_order_release);\n    return true;\n  }\n\n  return false;\n}\n\nStorageHandler\nClusterMgr::getStorageHandlerWithData()\n{\n  if (!mClusterData) {\n    return getStorageHandler();\n  }\n\n  auto cluster_data = getClusterData();\n  ClusterData cluster_data_copy(cluster_data());\n  return StorageHandler(*this, std::move(cluster_data_copy));\n}\n\nstd::string\nClusterMgr::getStateStr(std::string_view type)\n{\n  using namespace std::string_view_literals;\n  std::stringstream ss;\n  eos::common::RCUReadLock rlock(cluster_mgr_rcu);\n\n  if (type == \"bucket\"sv || type == \"all\"sv) {\n    ss << mClusterData->getBucketsAsString();\n  }\n  if (type == \"disk\"sv || type == \"all\"sv) {\n    ss << mClusterData->getDisksAsString();\n  }\n\n  return ss.str();\n}\n\nbool\nStorageHandler::isValidBucketID(item_id_t bucket_id) const\n{\n  return bucket_id < 0 &&\n         (size_t(-bucket_id) < mData.buckets.size());\n}\n\nbool\nStorageHandler::addBucket(uint8_t bucket_type, item_id_t bucket_id,\n                          item_id_t parent_bucket_id)\n{\n  if (bucket_id > 0 || parent_bucket_id > 0) {\n    return false;\n  }\n\n  int32_t index = -bucket_id;\n  int32_t parent_index = -parent_bucket_id;\n\n  // This cast is safe, we'd already checked that the value is +ve\n  if ((size_t)index >= mData.buckets.size()) {\n    mData.buckets.resize(index + 1);\n  }\n\n  mData.buckets.at(index) = Bucket(bucket_id, bucket_type);\n\n  // Handle special case when the parent is the root && we're adding root\n  if (parent_index != bucket_id) {\n    mData.buckets[parent_index].items.push_back(bucket_id);\n  }\n\n  return true;\n}\n\nbool\nStorageHandler::addDisk(Disk disk, item_id_t bucket_id, std::string_view tag)\n{\n  if (disk.id == mData.disks.size() + 1)  {\n    return addDiskSequential(disk, bucket_id);\n  }\n\n  if (!isValidBucketID(bucket_id) || disk.id == 0) {\n    return false;\n  }\n\n  size_t insert_pos = disk.id - 1;\n\n  if (disk.id > mData.disks.size()) {\n    mData.disks.resize(disk.id);\n  }\n\n  mData.disks[insert_pos] = disk;\n  mData.buckets[-bucket_id].items.push_back(disk.id);\n  mData.buckets[-bucket_id].total_weight += disk.weight;\n  addGeoTag(disk.id, tag);\n  return true;\n}\n\nbool\nStorageHandler::addDiskSequential(Disk disk, item_id_t bucket_id, std::string_view tag)\n{\n  if (!isValidBucketID(bucket_id) || disk.id == 0) {\n    return false;\n  }\n\n  mData.disks.push_back(disk);\n  mData.buckets[-bucket_id].items.push_back(disk.id);\n  mData.buckets[-bucket_id].total_weight += disk.weight;\n\n  addGeoTag(disk.id, tag);\n  return true;\n}\n\nuint64_t\nStorageHandler::getUniqueHash(std::string_view tag) {\n  uint64_t h = XXH3_64bits(tag.data(), tag.size());\n  auto [it, inserted] = mData.geo_hash_registry.try_emplace(h, tag);\n\n  // check if we have inserted a new tag or re-inserted the same tag\n  if (inserted || it->second == tag) {\n    return h;\n  }\n\n  // hash collision detected, we need to resolve it\n  // eos_static_warn(\"msg=\\\"Hash collision detected for geotag\\\" tag=\\\"%s\\\" hash=%lu\",\n  //                 tag.data(), h);\n  uint64_t nonce = 0;\n  std::string new_tag;\n\n  // This is very very very rare to enter,\n  // but in case for some reason 2 distinct strings hash onto the same value,\n  // add nonce to the tag and re-hash until we find a unique one\n  while (true) {\n    ++nonce;\n    new_tag = std::string(tag) + \"[\" + std::to_string(nonce) + \"]\";\n    h = XXH3_64bits(new_tag.data(), new_tag.size());\n    auto [_it, inserted] = mData.geo_hash_registry.try_emplace(h, new_tag);\n    if (inserted || _it->second == new_tag) {\n      return h;\n    }\n  }\n}\n\n\nvoid StorageHandler::addGeoTag(item_id_t item_id, std::string_view tag)\n{\n  // TODO future: implement geotags at bucket level making a natural hierarchy\n  if (tag.empty() || item_id <= 0) {\n    return;\n  }\n\n  if ((size_t)item_id > mData.disk_tags.size()) {\n    mData.disk_tags.resize(item_id);\n  }\n\n  std::vector<uint64_t> location_hash;\n  size_t start = 0;\n  size_t end = 0;\n  while ((end = tag.find(\"::\", start)) != std::string_view::npos) {\n    std::string_view storage_element = tag.substr(start, end - start);\n    location_hash.push_back(getUniqueHash(storage_element));\n    start = end + 2;\n  }\n  mData.disk_tags[item_id - 1] = std::move(location_hash);\n  mData.disk_tag_map.insert_or_assign(item_id, tag);\n\n}\n} // namespace eos::mgm::placement\n"
  },
  {
    "path": "mgm/placement/ClusterMap.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ClusterMap.hh\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n#include <vector>\n#include <memory>\n#include \"mgm/placement/ClusterDataTypes.hh\"\n#include \"common/concurrency/RCULite.hh\"\n#include \"common/concurrency/AtomicUniquePtr.h\"\n\nnamespace eos::mgm::placement {\n\n\nclass StorageHandler;\n// cluster_rcu_mutex_t is compatible with std::shared_mutex ap\n// and hence can be used with std::shared_lock and std::unique_lock\n// It is possible to just change cluster_rcu_mutex_t to anything\n// that conforms to std::shared_mutex api\nusing RCUMutexT = eos::common::RCUMutexT<>;\n\n\nclass ClusterMgr {\npublic:\n\n  struct ClusterDataPtr {\n    ClusterDataPtr(ClusterData* data_,\n                   RCUMutexT& rcu_domain_):\n      rlock(rcu_domain_),\n      data(data_)\n    {}\n\n    ~ClusterDataPtr() = default;\n\n    const ClusterData& operator()() const {\n      return *data;\n    }\n\n    ClusterData* operator->() const {\n      return data;\n    }\n\n    operator bool() const {\n      return data != nullptr;\n    }\n\n  private:\n    eos::common::RCUReadLock<RCUMutexT> rlock;\n    ClusterData* data;\n  };\n\n  ClusterMgr() = default;\n\n  StorageHandler getStorageHandler(size_t max_buckets=256);\n  StorageHandler getStorageHandlerWithData();\n  epoch_id_t getCurrentEpoch() const { return mCurrentEpoch; }\n\n  ClusterDataPtr getClusterData();\n\n  bool setDiskStatus(fsid_t disk_id, ConfigStatus status);\n  bool setDiskStatus(fsid_t disk_id, ActiveStatus status);\n  bool setDiskWeight(fsid_t disk_id, uint8_t weight);\n  // Not meant to be called directly! use storage handler, we might consider\n  // making this private and friending if this is abused\n  void addClusterData(ClusterData&& data);\n  std::string getStateStr(std::string_view type);\nprivate:\n  eos::common::atomic_unique_ptr<ClusterData> mClusterData;\n  std::atomic<epoch_id_t> mCurrentEpoch {0};\n  RCUMutexT cluster_mgr_rcu;\n};\n\nclass StorageHandler {\npublic:\n  StorageHandler(ClusterMgr& mgr, size_t max_buckets=256) :\n      mClusterMgr(mgr)\n  { mData.buckets.resize(max_buckets); }\n\n  StorageHandler(ClusterMgr& mgr, ClusterData&& data) :\n      mClusterMgr(mgr), mData(std::move(data))\n  {}\n\n  bool addBucket(uint8_t bucket_type, item_id_t bucket_id,\n                 item_id_t parent_bucket_id=0);\n\n  bool addDisk(Disk d, item_id_t bucket_id, std::string_view tag=\"\");\n\n  // We store disks sequentially with index as fsid - 1;\n  bool addDiskSequential(Disk d, item_id_t bucket_id, std::string_view tag=\"\");\n\n  bool isValidBucketID(item_id_t bucket_id) const;\n\n  ~StorageHandler() {\n    mClusterMgr.addClusterData(std::move(mData));\n  }\n\n  // helpers for storing geotag data\n  void addGeoTag(item_id_t item_id, std::string_view tag);\n  uint64_t getUniqueHash(std::string_view tag);\nprivate:\n  ClusterMgr& mClusterMgr;\n  ClusterData mData;\n};\n\n} // namespace eos::mgm::placement\n"
  },
  {
    "path": "mgm/placement/FlatScheduler.cc",
    "content": "#include \"mgm/placement/FlatScheduler.hh\"\n#include \"mgm/placement/RoundRobinPlacementStrategy.hh\"\n#include \"mgm/placement/WeightedRandomStrategy.hh\"\n#include \"mgm/placement/WeightedRoundRobinStrategy.hh\"\n#include <queue>\n\nnamespace eos::mgm::placement {\n\nstd::unique_ptr<PlacementStrategy>\nmakePlacementStrategy(PlacementStrategyT type, size_t max_buckets)\n{\n  switch (type) {\n  case PlacementStrategyT::kRoundRobin: [[fallthrough]];\n  case PlacementStrategyT::kThreadLocalRoundRobin: [[fallthrough]];\n  case PlacementStrategyT::kRandom: [[fallthrough]];\n  case PlacementStrategyT::kFidRandom:\n    return std::make_unique<RoundRobinPlacement>(type, max_buckets);\n  case PlacementStrategyT::kWeightedRandom:\n    return std::make_unique<WeightedRandomPlacement>(type, max_buckets);\n  case PlacementStrategyT::kWeightedRoundRobin:\n    return std::make_unique<WeightedRoundRobinPlacement>(type, max_buckets);\n  default:\n    return nullptr;\n  }\n\n}\n\nFlatScheduler::FlatScheduler(size_t max_buckets)\n{\n  for (size_t i = 0; i < TOTAL_PLACEMENT_STRATEGIES; i++) {\n    mPlacementStrategy[i] = makePlacementStrategy(\n        static_cast<PlacementStrategyT>(i), max_buckets);\n  }\n}\n\nFlatScheduler::FlatScheduler(PlacementStrategyT strategy, size_t max_buckets)\n: mDefaultStrategy(strategy)\n{\n  mPlacementStrategy[static_cast<int>(strategy)] =\n      makePlacementStrategy(strategy, max_buckets);\n}\n\nPlacementResult\nFlatScheduler::schedule(const ClusterData& cluster_data,\n                        PlacementArguments args)\n{\n\n  PlacementResult result;\n  if (args.n_replicas == 0) {\n    result.err_msg = \"Zero replicas requested\";\n    return result;\n  } else if (isValidBucketId(args.bucket_id, cluster_data)) {\n    result.err_msg = \"Bucket id out of range\";\n    return result;\n  }\n\n  if (! is_valid_placement_strategy(args.strategy)) {\n    args.strategy = mDefaultStrategy;\n  }\n\n\n  if (args.default_placement) {\n    return scheduleDefault(cluster_data, args);\n  }\n\n\n  // classical BFS\n  std::queue<item_id_t> item_queue;\n  item_queue.push(args.bucket_id);\n  int result_index = 0;\n  uint8_t n_final_replicas = args.n_replicas;\n\n  while (!item_queue.empty()) {\n    item_id_t bucket_id = item_queue.front();\n    item_queue.pop();\n    if (!isValidBucketId(bucket_id, cluster_data)) {\n      result.err_msg = \"Invalid bucket id\";\n      return result;\n    }\n\n    auto bucket = cluster_data.buckets.at(-bucket_id);\n    auto items_to_place = args.rules.at(bucket.bucket_type);\n    if (items_to_place == -1) {\n      items_to_place = n_final_replicas;\n    }\n\n    args.bucket_id = bucket_id;\n    args.n_replicas = items_to_place;\n\n    auto result = mPlacementStrategy[strategy_index(args.strategy)]->placeFiles(cluster_data, args);\n    if (!result) {\n      return result;\n    } else {\n      for (int i=0; i < result.n_replicas; ++i) {\n        auto _id = result.ids[i];\n        if (_id < 0) {\n          item_queue.push(_id);\n        } else {\n          result.ids[result_index++] = _id;\n        }\n      }\n    }\n  }\n  return result;\n}\n\nPlacementResult\nFlatScheduler::scheduleDefault(const ClusterData& cluster_data,\n                               PlacementArguments args)\n{\n  uint8_t n_final_replicas = args.n_replicas;\n  if (!is_valid_placement_strategy(args.strategy) ||\n      mPlacementStrategy[strategy_index(args.strategy)] == nullptr) {\n    PlacementResult result;\n    result.err_msg = \"Not a valid PlacementStrategy\";\n    result.ret_code = EINVAL;\n    return result;\n  }\n\n  do {\n    const auto& bucket = cluster_data.buckets.at(-args.bucket_id);\n    uint8_t n_replicas = 1;\n    if (bucket.bucket_type == static_cast<uint8_t>(StdBucketType::GROUP)) {\n      n_replicas = n_final_replicas;\n\n      // Check if there is a forced group, reset the bucket_id in that case\n      // TODO: determine from the previous level in the hierarchy whether we're\n      // choosing groups and force groups in those cases\n      if (args.forced_group_index >= 0) {\n        args.bucket_id = kBaseGroupOffset - args.forced_group_index;\n        if (!isValidBucketId(args.bucket_id, cluster_data)) {\n          PlacementResult result;\n          result.err_msg = \"Invalid forced group index\";\n          result.ret_code = EINVAL;\n          return result;\n        }\n      }\n    }\n\n    args.n_replicas = n_replicas;\n    //PlacementStrategy::Args plct_args{args.bucket_id, n_replicas, args.status};\n    auto result = mPlacementStrategy[strategy_index(args.strategy)]->placeFiles(cluster_data, args);\n    if (!result || result.ids.empty()) {\n      return result;\n    }\n\n    if (result.is_valid_placement(n_replicas)) {\n      return result;\n    }\n\n    args.bucket_id = result.ids.front();\n  } while(args.bucket_id < 0);\n\n  return {};\n}\n\nconstexpr size_t FlatScheduler::accessStategyIndex(PlacementStrategyT strategy) {\n  size_t s_index = strategy_index(PlacementStrategyT::kGeoScheduler);\n  switch (strategy) {\n    case PlacementStrategyT::kThreadLocalRoundRobin: [[fallthrough]];\n    case PlacementStrategyT::kRandom: [[fallthrough]];\n    case PlacementStrategyT::kFidRandom: [[fallthrough]];\n    case PlacementStrategyT::kRoundRobin:\n      s_index = strategy_index(PlacementStrategyT::kRoundRobin);\n      break;\n    case PlacementStrategyT::kWeightedRoundRobin: [[fallthrough]];\n    case PlacementStrategyT::kWeightedRandom:\n      s_index = strategy_index(PlacementStrategyT::kWeightedRandom);\n      break;\n    default:\n      break;\n  }\n  return s_index;\n}\n\nint\nFlatScheduler::access(const ClusterData& cluster_data,\n                      AccessArguments& args)\n  {\n  if (!is_valid_placement_strategy(args.strategy) ||\n      mPlacementStrategy[strategy_index(args.strategy)] == nullptr) {\n    return EINVAL;\n  }\n\n  auto s_index = accessStategyIndex(args.strategy);\n  mPlacementStrategy[s_index]->access(cluster_data, args);\n\n\n  return 0;\n\n}\n} // namespace eos::mgm::placement\n"
  },
  {
    "path": "mgm/placement/FlatScheduler.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Scheduler\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include \"mgm/placement/ClusterDataTypes.hh\"\n#include \"mgm/placement/PlacementStrategy.hh\"\n#include <algorithm>\n#include <optional>\n\nnamespace eos::mgm::placement {\n\nstd::unique_ptr<PlacementStrategy> makePlacementStrategy(PlacementStrategyT type,\n                      size_t max_buckets);\n// We really need a more creative name?\nclass FlatScheduler {\npublic:\n  FlatScheduler(size_t max_buckets);\n  FlatScheduler(PlacementStrategyT strategy, size_t max_buckets);\n\n  PlacementResult schedule(const ClusterData& cluster_data,\n                           PlacementArguments args);\n\n  int access(const ClusterData& cluster_data,\n             AccessArguments& args);\n\n  static constexpr size_t\n  accessStategyIndex(PlacementStrategyT strategy);\nprivate:\n  PlacementResult scheduleDefault(const ClusterData& cluster_data,\n                                  PlacementArguments args);\n\n  std::array<std::unique_ptr<PlacementStrategy>, TOTAL_PLACEMENT_STRATEGIES>\n      mPlacementStrategy;\n  PlacementStrategyT mDefaultStrategy{PlacementStrategyT::Count};\n};\n} // namespace eos::mgm::placement\n\n\n\n\n\n"
  },
  {
    "path": "mgm/placement/FsScheduler.cc",
    "content": "#include \"common/utils/ContainerUtils.hh\"\n#include \"common/Logging.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/placement/FsScheduler.hh\"\n\nnamespace eos::mgm::placement\n{\n\nstatic constexpr int MAX_GROUPS_TO_TRY = 10;\n\nstd::map<std::string, std::unique_ptr<ClusterMgr>>\n    EosClusterMgrHandler::make_cluster_mgr()\n{\n  std::map<std::string, std::unique_ptr<ClusterMgr>> space_cluster_map;\n  {\n    eos::common::RWMutexReadLock vlock(FsView::gFsView.ViewMutex);\n\n    for (auto space_group_kv : FsView::gFsView.mSpaceGroupView) {\n      space_cluster_map.insert_or_assign(space_group_kv.first,\n                                         std::make_unique<ClusterMgr>());\n      auto total_groups = space_group_kv.second.size();\n      eos_static_info(\"msg=\\\"Creating FSScheduler with \\\" total_groups=%llu\",\n                      total_groups);\n      auto storage_handler =\n        space_cluster_map[space_group_kv.first]->getStorageHandler(\n          common::next_power2(total_groups + 10));\n      bool status =\n        storage_handler.addBucket(get_bucket_type(StdBucketType::ROOT), 0);\n\n      if (!status) {\n        eos_static_crit(\"msg=\\\"Failed to add root bucket!\\\"\");\n      }\n\n      for (auto group_iter : space_group_kv.second) {\n        auto group_index = group_iter->GetIndex();\n        item_id_t group_id = GroupIDtoBucketID(group_index);\n        eos_static_info(\"msg=\\\"Adding group \\\" Group ID=%d, Internal bucket ID=%d\",\n                        group_index, group_id);\n        bool status = storage_handler.addBucket(\n                        get_bucket_type(StdBucketType::GROUP), group_id, 0);\n\n        if (!status) {\n          eos_static_crit(\"msg=\\\"Failed to add group bucket!\\\" group_id=%d\",\n                          group_id);\n        }\n\n        for (auto it_fs = group_iter->begin(); it_fs != group_iter->end();\n             ++it_fs) {\n          auto fs = FsView::gFsView.mIdView.lookupByID(*it_fs);\n          auto capacity = fs->GetLongLong(\"stat.statfs.capacity\");\n          uint8_t used = static_cast<uint8_t>(\n                           fs->GetDouble(\"stat.statfs.filled\")); // filled is supposed to be between 0 & 100\n          uint8_t weight = 1;\n\n          if (capacity > (1LL << 40)) {\n            weight = capacity / (1LL << 40);\n          }\n\n          auto active_status = getActiveStatus(fs->GetActiveStatus(),\n                                               fs->GetStatus());\n          auto add_status = storage_handler.addDisk(Disk(fs->GetId(),\n                            fs->GetConfigStatus(),\n                            active_status, weight, used),\n                            group_id,\n                            fs->GetString(\"stat.geotag\"));\n\n          eos_static_info(\"msg=\\\"Adding disk at \\\" ID=%d group_id=%d status=%d\",\n                          fs->GetId(), group_id, add_status);\n        }\n      }\n    }\n  }\n  return space_cluster_map;\n}\n\nstd::unique_ptr<ClusterMgr>\nEosClusterMgrHandler::make_cluster_mgr(const std::string& spaceName)\n{\n  auto cluster_mgr = std::make_unique<ClusterMgr>();\n  eos::common::RWMutexReadLock vlock(FsView::gFsView.ViewMutex);\n\n  if (auto space_group_kv = FsView::gFsView.mSpaceGroupView.find(spaceName);\n      space_group_kv != FsView::gFsView.mSpaceGroupView.end()) {\n    auto total_groups = space_group_kv->second.size();\n    auto storage_handler =\n      cluster_mgr->getStorageHandler(common::next_power2(total_groups + 1));\n    storage_handler.addBucket(get_bucket_type(StdBucketType::ROOT), 0);\n\n    for (auto group_iter : space_group_kv->second) {\n      auto group_index = group_iter->GetIndex();\n      item_id_t group_id = GroupIDtoBucketID(group_index);\n      eos_static_info(\"msg=\\\"Adding group \\\" Group ID=%d, Internal bucket ID=%d\",\n                      group_index, group_id);\n      bool status = storage_handler.addBucket(get_bucket_type(StdBucketType::GROUP),\n                                              group_id);\n\n      if (!status) {\n        eos_static_crit(\"msg=\\\"Failed to add group bucket!\\\" group_id=%d\",\n                        group_id);\n      }\n\n      for (auto it_fs = group_iter->begin(); it_fs != group_iter->end();\n           ++it_fs) {\n        auto fs = FsView::gFsView.mIdView.lookupByID(*it_fs);\n        auto capacity = fs->GetLongLong(\"stat.statfs.capacity\");\n        uint8_t used = static_cast<uint8_t>(fs->GetDouble(\n                                              \"stat.statfs.filled\")); // filled is supposed to be between 0 & 100\n        uint8_t weight = 1;\n\n        if (capacity > (1LL << 40)) {\n          weight = capacity / (1LL << 40);\n        }\n\n        auto active_status = getActiveStatus(fs->GetActiveStatus(),\n                                             fs->GetStatus());\n        storage_handler.addDisk(Disk(fs->GetId(), fs->GetConfigStatus(),\n                                     active_status, weight, used),\n                                group_id, fs->GetString(\"stat.geotag\"));\n      }\n    }\n  }\n\n  return cluster_mgr;\n}\n\nClusterMgr*\nFSScheduler::get_cluster_mgr(const std::string& spaceName)\n{\n  if (!cluster_mgr_map) {\n    return nullptr;\n  }\n\n  if (auto kv = cluster_mgr_map->find(spaceName);\n      kv != cluster_mgr_map->end()) {\n    return kv->second.get();\n  }\n\n  return nullptr;\n}\n\nvoid\nFSScheduler::updateClusterData()\n{\n  if (!cluster_handler) {\n    eos_static_crit(\"msg=\\\"Cluster handler is not yet initialized!\\\"\");\n    //Throw an exception? There is no api to set a cluster handler currently!\n    return;\n  }\n\n  auto cluster_map = cluster_handler->make_cluster_mgr();\n  eos::common::ScopedRCUWrite(cluster_rcu_mutex, cluster_mgr_map,\n                              new ClusterMapT(std::move(cluster_map)));\n  mIsRunning.store(true, std::memory_order_release);\n}\n\nPlacementResult\nFSScheduler::schedule(const std::string& spaceName,\n                      PlacementArguments args)\n{\n  if (!is_valid_placement_strategy(args.strategy)) {\n    args.strategy = getPlacementStrategy(spaceName);\n    eos_static_info(\"msg=\\\"Overriding scheduling strategy to space default\\\": %s\",\n                    strategy_to_str(args.strategy).c_str());\n  }\n\n  eos::common::RCUReadLock rlock(cluster_rcu_mutex);\n  auto cluster_mgr = get_cluster_mgr(spaceName);\n\n  if (!cluster_mgr) {\n    eos_static_crit(\"msg=\\\"Scheduler is not yet initialized for space=%s\\\"\",\n                    spaceName.c_str());\n    return {};\n  }\n\n  PlacementResult result;\n  auto cluster_data_ptr = cluster_mgr->getClusterData();\n\n  for (int i = 0; i < MAX_GROUPS_TO_TRY; i++) {\n    result = scheduler->schedule(cluster_data_ptr(), args);\n\n    if (result.is_valid_placement(args.n_replicas)) {\n      return result;\n    } else {\n      eos_static_debug(\"msg=\\\"Scheduler failed to place %d replicas\\\" err=%s\",\n                       result.n_replicas, result.error_string().c_str());\n    }\n  }\n\n  return result;\n}\n\nPlacementResult\nFSScheduler::schedule(const std::string& spaceName, uint8_t n_replicas)\n{\n  return schedule(spaceName, PlacementArguments(n_replicas, ConfigStatus::kRW,\n                  getPlacementStrategy(spaceName)));\n}\n\nint\nFSScheduler::access(const std::string &spaceName, AccessArguments &args)\n{\n  if (!is_valid_placement_strategy(args.strategy)) {\n    args.strategy = getPlacementStrategy(spaceName);\n    eos_static_info(\"msg=\\\"Overriding access strategy to space default\\\": %s\",\n                    strategy_to_str(args.strategy).c_str());\n  }\n\n  eos::common::RCUReadLock rlock(cluster_rcu_mutex);\n  auto cluster_mgr = get_cluster_mgr(spaceName);\n\n  if (!cluster_mgr) {\n    eos_static_crit(\"msg=\\\"Scheduler is not yet initialized for space=%s\\\"\",\n                    spaceName.c_str());\n    return EINVAL;\n  }\n\n  auto cluster_data_ptr = cluster_mgr->getClusterData();\n  return scheduler->access(cluster_data_ptr(), args);\n\n}\n\nbool\nFSScheduler::setDiskStatus(const std::string& spaceName, fsid_t disk_id,\n                           ConfigStatus status)\n{\n  if (spaceName.empty() || disk_id == 0) {\n    return false;\n  }\n\n  eos::common::RCUReadLock rlock(cluster_rcu_mutex);\n  auto* cluster_mgr = get_cluster_mgr(spaceName);\n\n  if (!cluster_mgr) {\n    eos_static_crit(\"msg=\\\"Scheduler is not yet initialized for space=%s\\\"\",\n                    spaceName.c_str());\n    return false;\n  }\n\n  return cluster_mgr->setDiskStatus(disk_id, status);\n}\n\nbool\nFSScheduler::setDiskStatus(const std::string& spaceName, fsid_t disk_id,\n                           ActiveStatus status,\n                           eos::common::BootStatus bstatus)\n{\n  if (spaceName.empty() || disk_id == 0) {\n    return false;\n  }\n\n  eos::common::RCUReadLock rlock(cluster_rcu_mutex);\n  auto* cluster_mgr = get_cluster_mgr(spaceName);\n\n  if (!cluster_mgr) {\n    eos_static_crit(\"msg=\\\"Scheduler is not yet initialized for space=%s\\\"\",\n                    spaceName.c_str());\n    return false;\n  }\n\n  auto _status = getActiveStatus(status, bstatus);\n  return cluster_mgr->setDiskStatus(disk_id, _status);\n}\n\nbool\nFSScheduler::setDiskWeight(const std::string& spaceName, fsid_t disk_id,\n                           uint8_t weight)\n{\n  if (spaceName.empty() || disk_id == 0) {\n    return false;\n  }\n\n  eos::common::RCUReadLock rlock(cluster_rcu_mutex);\n  auto* cluster_mgr = get_cluster_mgr(spaceName);\n\n  if (!cluster_mgr) {\n    eos_static_crit(\"msg=\\\"Scheduler is not yet initialized for\\\" space=%s\",\n                    spaceName.c_str());\n    return false;\n  }\n\n  return cluster_mgr->setDiskWeight(disk_id, weight);\n}\n\nvoid\nFSScheduler::setPlacementStrategy(std::string_view strategy_sv)\n{\n  placement_strategy.store(strategy_from_str(strategy_sv),\n                           std::memory_order_release);\n}\n\nPlacementStrategyT\nFSScheduler::getPlacementStrategy()\n{\n  return placement_strategy.load(std::memory_order_acquire);\n}\nvoid\nFSScheduler::setPlacementStrategy(const std::string& spacename,\n                                  std::string_view strategy_sv)\n{\n  std::map<std::string, PlacementStrategyT> strategy_map;\n\n  if (space_strategy_map && !space_strategy_map->empty()) {\n    eos::common::RCUReadLock rlock(cluster_rcu_mutex);\n    strategy_map.insert(space_strategy_map->begin(),\n                        space_strategy_map->end());\n  }\n\n  strategy_map.insert_or_assign(spacename, strategy_from_str(strategy_sv));\n  eos::common::ScopedRCUWrite(cluster_rcu_mutex, space_strategy_map,\n                              new SpaceStrategyMapT(std::move(strategy_map)));\n  eos_static_info(\"msg=\\\"Configured default scheduler type for\\\" space=%s, strategy=%s\",\n                  spacename.c_str(), strategy_sv.data());\n}\n\nPlacementStrategyT\nFSScheduler::getPlacementStrategy(const std::string& spacename)\n{\n  eos::common::RCUReadLock rlock(cluster_rcu_mutex);\n\n  if (space_strategy_map && !space_strategy_map->empty()) {\n    if (auto kv = space_strategy_map->find(spacename);\n        kv != space_strategy_map->end()) {\n      return kv->second;\n    }\n  }\n\n  return getPlacementStrategy();\n}\n\nstd::string\nFSScheduler::getStateStr(const std::string& spacename, std::string_view type_sv)\n{\n  eos::common::RCUReadLock rlock(cluster_rcu_mutex);\n  auto* cluster_mgr = get_cluster_mgr(spacename);\n\n  if (!cluster_mgr) {\n    eos_static_crit(\"msg=\\\"Scheduler is not yet initialized for\\\" space=%s\",\n                    spacename.c_str());\n    return {};\n  }\n\n  return cluster_mgr->getStateStr(type_sv);\n}\n\nbool\nFSScheduler::isRunning() const\n{\n  return mIsRunning.load(std::memory_order_acquire);\n}\n\n}// eos::mgm::placement\n"
  },
  {
    "path": "mgm/placement/FsScheduler.hh",
    "content": "// ----------------------------------------------------------------------\n// File: FsViewUpdater.cc\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/placement/ClusterMap.hh\"\n#include \"mgm/placement/FlatScheduler.hh\"\n\nnamespace eos::mgm::placement {\n\nusing ClusterMapT = std::map<std::string, std::unique_ptr<ClusterMgr>>;\n\nstruct ClusterMgrHandler {\n  virtual ClusterMapT make_cluster_mgr()=0;\n  virtual std::unique_ptr<ClusterMgr> make_cluster_mgr(const std::string& spaceName)=0;\n  virtual ~ClusterMgrHandler() = default;\n};\n\nstruct EosClusterMgrHandler : public ClusterMgrHandler\n{\n  ClusterMapT make_cluster_mgr() override;\n  std::unique_ptr<ClusterMgr> make_cluster_mgr(const std::string& spaceName) override;\n};\n\n\nclass FSScheduler {\npublic:\n  using ClusterMapPtrT = eos::common::atomic_unique_ptr<ClusterMapT>;\n  using SpaceStrategyMapT = std::map<std::string, PlacementStrategyT>;\n  using SpaceStrategyMapPtrT = eos::common::atomic_unique_ptr<SpaceStrategyMapT>;\n\n  FSScheduler(size_t max_buckets,\n              std::unique_ptr<ClusterMgrHandler>&& _handler) :\n    scheduler(std::make_unique<FlatScheduler>(max_buckets)),\n    cluster_handler(std::move(_handler)),\n    placement_strategy(placement::PlacementStrategyT::kGeoScheduler)\n  {}\n\n  FSScheduler() : FSScheduler(1024,\n                              std::make_unique<EosClusterMgrHandler>()) {}\n\n\n  PlacementResult schedule(const std::string& spaceName, uint8_t n_replicas);\n  PlacementResult schedule(const std::string& spaceName, PlacementArguments args);\n\n  int access(const std::string& spaceName, AccessArguments& args);\n  void updateClusterData();\n  bool setDiskStatus(const std::string& spaceName, fsid_t disk_id,\n                     ConfigStatus status);\n  bool setDiskStatus(const std::string& spaceName, fsid_t disk_id,\n                     ActiveStatus status, eos::common::BootStatus bstatus);\n\n  bool setDiskWeight(const std::string& spaceName, fsid_t disk_id,\n                     uint8_t weight);\n\n  bool isRunning() const;\n\n  void setPlacementStrategy(std::string_view strategy_sv);\n  void setPlacementStrategy(const std::string& spacename,\n                            std::string_view strategy_sv);\n\n  PlacementStrategyT getPlacementStrategy();\n  PlacementStrategyT getPlacementStrategy(const std::string& spacename);\n  std::string getStateStr(const std::string& spacename, std::string_view type_sv);\nprivate:\n\n  ClusterMgr* get_cluster_mgr(const std::string& spaceName);\n\n  std::atomic<bool> mIsRunning {false};\n  std::unique_ptr<FlatScheduler> scheduler;\n  std::unique_ptr<ClusterMgrHandler> cluster_handler;\n  ClusterMapPtrT cluster_mgr_map;\n  std::atomic<PlacementStrategyT> placement_strategy;\n  SpaceStrategyMapPtrT space_strategy_map;\n  RCUMutexT cluster_rcu_mutex;\n};\n\n\n\n} // eos::mgm::placement\n"
  },
  {
    "path": "mgm/placement/PlacementStrategy.cc",
    "content": "// ----------------------------------------------------------------------\n// File: PlacementStrategy\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2026 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"PlacementStrategy.hh\"\n#include \"mgm/placement/PlacementStrategy.hh\"\n\nnamespace eos::mgm::placement {\nsize_t\nPlacementStrategy::calculateMaxGeoOverlap(item_id_t candidate_id,\n                                          const ClusterData &data,\n                                          const PlacementResult &current_result,\n                                          int items_added) const {\n    if (candidate_id <= 0 || data.disk_tags.empty()) {\n        return 0; // No penalty if no topology data exists\n    }\n\n    if ((size_t)candidate_id > data.disk_tags.size()) {\n        return std::numeric_limits<size_t>::max();\n    }\n\n    const auto& candidate_path = data.disk_tags[candidate_id - 1];\n    size_t max_overlap_found = 0;\n\n    // Compare against ALL currently selected replicas\n    for (int i = 0; i < items_added; ++i) {\n        item_id_t existing_id = current_result.ids[i];\n\n        // Skip invalid existing IDs (shouldn't happen, but doublecheck)\n        if (existing_id <= 0 || (size_t)existing_id > data.disk_tags.size()) {\n            continue;\n        }\n\n        const auto& existing_path = data.disk_tags[existing_id - 1];\n\n        // 3. Calculate overlap depth for this pair\n        // (e.g., DC::Room matches = 2)\n        size_t current_overlap = 0;\n        size_t len = std::min(candidate_path.size(), existing_path.size());\n\n        for (size_t d = 0; d < len; ++d) {\n            if (candidate_path[d] != existing_path[d]) break;\n            current_overlap++;\n        }\n\n        // 4. Track the WORST overlap (closest proximity)\n        if (current_overlap > max_overlap_found) {\n            max_overlap_found = current_overlap;\n        }\n    }\n\n    return max_overlap_found;\n}\n\nPlacementResult\nPlacementStrategy::placeWithGeoFilter(const ClusterData &cluster_data,\n                                      const Args &args,\n                                      const std::vector<item_id_t> &sorted_candidates)\n{\n\n    PlacementResult result;\n    int replicas_selected = 0;\n\n    for (size_t i = 0; i < sorted_candidates.size(); ++i) {\n        if (replicas_selected >= args.n_replicas) break;\n\n        item_id_t candidate_id = sorted_candidates[i];\n\n        // Caller might have validated, but we double-check for safety)\n        // Do this only with disks for now! A future version will handle\n        // buckets here\n        if (candidate_id <= 0) continue;\n        if (result.contains(candidate_id)) continue;\n\n        // Calculate overlap with ALL currently selected replicas\n        size_t overlap = calculateMaxGeoOverlap(candidate_id, cluster_data, result, replicas_selected);\n\n        // We want to skip this candidate if it overlaps, BUT not if it causes failure.\n        bool skip_candidate = false;\n\n        if (replicas_selected > 0 && overlap > 0) {\n            // Heuristic: Do we have enough candidates left to afford skipping this one?\n            // We look ahead to see if there are other options.\n            size_t remaining_candidates = sorted_candidates.size() - i;\n            size_t needed = args.n_replicas - replicas_selected;\n\n            // \"Buffer Factor\" of 2: We only skip if we have 2x more candidates than we need.\n            // This ensures we don't aggressively filter ourselves into ENOSPC.\n            if (remaining_candidates > needed * 2) {\n                skip_candidate = true;\n            }\n        }\n\n        if (!skip_candidate) {\n            result.ids[replicas_selected++] = candidate_id;\n        }\n    }\n\n    // 5. Finalize\n    if (replicas_selected < args.n_replicas) {\n        result.ret_code = ENOSPC;\n        result.err_msg = \"Could not find enough suitable replicas\";\n    } else {\n        result.ret_code = 0;\n    }\n\n    return result;\n}\n\n\n\n}// namespace eos::mgm::placement\n"
  },
  {
    "path": "mgm/placement/PlacementStrategy.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file PlacementStrategy.hh\n//! @author Abhishek Lekshmanan <abhishek.lekshmanan@cern.ch>\n//-----------------------------------------------------------------------------\n\n/************************************************************************\n  * EOS - the CERN Disk Storage System                                   *\n  * Copyright (C) 2023 CERN/Switzerland                           *\n  *                                                                      *\n  * This program is free software: you can redistribute it and/or modify *\n  * it under the terms of the GNU General Public License as published by *\n  * the Free Software Foundation, either version 3 of the License, or    *\n  * (at your option) any later version.                                  *\n  *                                                                      *\n  * This program is distributed in the hope that it will be useful,      *\n  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n  * GNU General Public License for more details.                         *\n  *                                                                      *\n  * You should have received a copy of the GNU General Public License    *\n  * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n  ************************************************************************/\n\n#pragma once\n\n#include \"mgm/placement/ClusterDataTypes.hh\"\n#include \"mgm/placement/RRSeed.hh\"\n#include <algorithm>\n#include <optional>\n#include <xxhash.h>\n\nnamespace eos::mgm::placement\n{\n\nstruct PlacementResult {\n  std::array<item_id_t, 32> ids {0};\n  int ret_code;\n  int n_replicas;\n  std::optional<std::string> err_msg;\n\n  PlacementResult() :  ret_code(-1), n_replicas(0) {}\n  PlacementResult(int n_rep):  ret_code(-1), n_replicas(n_rep) {}\n\n  operator bool() const\n  {\n    return ret_code == 0;\n  }\n\n\n  bool is_valid_placement(uint8_t _n_replicas) const\n  {\n    return _n_replicas == n_replicas &&\n           (std::all_of(ids.cbegin(), ids.cbegin() + n_replicas,\n    [](item_id_t id) {\n      return id > 0;\n    }));\n  }\n\n  friend std::ostream& operator<< (std::ostream& os, const PlacementResult r)\n  {\n    for (int i = 0; i < r.n_replicas; ++i) {\n      os << r.ids[i] << \" \";\n    }\n\n    return os;\n  }\n\n  // Simple helper to convert to string\n  std::string result_string() const\n  {\n    std::stringstream ss;\n    ss << *this;\n    return ss.str();\n  }\n\n  std::string error_string() const\n  {\n    return err_msg.value_or(\"\");\n  }\n\n  bool contains(item_id_t item) const\n  {\n    return std::find(ids.cbegin(),\n                     ids.cbegin() + n_replicas,\n                     item) != ids.cbegin() + n_replicas;\n  }\n};\n\nenum class PlacementStrategyT : uint8_t {\n  kRoundRobin = 0,\n  kThreadLocalRoundRobin,\n  kRandom,\n  kFidRandom,\n  kWeightedRandom,\n  kWeightedRoundRobin,\n  kGeoScheduler, // Any flat scheduler strategies must be above this line!\n  Count\n};\n\n// Determining placement of replicas for a file\n// We need to understand how many storage elements we select at each level\n// of the hierarchy, for example for a 2 replica file, with 2 sites,\n// we'd select 1 per site, and then going further down the hierarchy, we'd have\n// to select 1 per room etc. until we reach our last abstraction at the group\n// where we'd need to select as many replicas as we have left, in this case 2.\n// we really don't want a tree that's more than 16 levels deep?\nconstexpr uint8_t MAX_PLACEMENT_HEIGHT = 16;\nusing selection_rules_t = std::array<int8_t, MAX_PLACEMENT_HEIGHT>;\nstatic selection_rules_t kDefault2Replica {-1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};\n\nconstexpr size_t TOTAL_PLACEMENT_STRATEGIES = static_cast<size_t>\n    (PlacementStrategyT::Count);\nconstexpr uint8_t MAX_PLACEMENT_ATTEMPTS = 100;\n\ninline constexpr bool is_valid_placement_strategy(PlacementStrategyT strategy)\n{\n  return strategy != PlacementStrategyT::Count;\n}\n\ninline constexpr size_t strategy_index(PlacementStrategyT strategy)\n{\n  return static_cast<size_t>(strategy);\n}\n\nconstexpr PlacementStrategyT strategy_from_str(std::string_view strategy_sv)\n{\n  using namespace std::string_view_literals;\n\n  if (strategy_sv == \"roundrobin\"sv ||\n      strategy_sv == \"rr\"sv) {\n    return PlacementStrategyT::kRoundRobin;\n  } else if (strategy_sv == \"threadlocalroundrobin\"sv ||\n             strategy_sv == \"threadlocalrr\"sv ||\n             strategy_sv == \"tlrr\"sv) {\n    return PlacementStrategyT::kThreadLocalRoundRobin;\n  } else if (strategy_sv == \"random\"sv) {\n    return PlacementStrategyT::kRandom;\n  } else if (strategy_sv == \"fid\"sv ||\n             strategy_sv == \"fidrandom\"sv) {\n    return PlacementStrategyT::kFidRandom;\n  } else if (strategy_sv == \"weightedrandom\"sv) {\n    return PlacementStrategyT::kWeightedRandom;\n  } else if (strategy_sv == \"weightedroundrobin\"sv ||\n             strategy_sv == \"weightedrr\"sv) {\n    return PlacementStrategyT::kWeightedRoundRobin;\n  } else if (strategy_sv == \"geoscheduler\"sv ||\n             strategy_sv == \"geo\"sv) {\n    return PlacementStrategyT::kGeoScheduler;\n  }\n\n  // default to geoscheduler!\n  return PlacementStrategyT::kGeoScheduler;\n}\n\ninline std::string strategy_to_str(PlacementStrategyT strategy)\n{\n  switch (strategy) {\n  case PlacementStrategyT::kRoundRobin:\n    return \"roundrobin\";\n\n  case PlacementStrategyT::kThreadLocalRoundRobin:\n    return \"threadlocalroundrobin\";\n\n  case PlacementStrategyT::kRandom:\n    return \"random\";\n\n  case PlacementStrategyT::kFidRandom:\n    return \"fidrandom\";\n\n  case PlacementStrategyT::kWeightedRandom:\n    return \"weightedrandom\";\n\n  case PlacementStrategyT::kWeightedRoundRobin:\n    return \"weightedroundrobin\";\n\n  case PlacementStrategyT::kGeoScheduler:\n    return \"geoscheduler\";\n\n  default:\n    return \"unknown\";\n  }\n}\n\nstruct PlacementArguments {\n  item_id_t bucket_id = 0;\n  uint8_t n_replicas;\n  ConfigStatus status = ConfigStatus::kRW;\n  uint64_t fid;\n  bool default_placement = true;\n  selection_rules_t rules = kDefault2Replica;\n  PlacementStrategyT strategy = PlacementStrategyT::Count;\n  std::vector<uint32_t> excludefs;\n  int64_t forced_group_index = -1;\n\n  PlacementArguments(item_id_t bucket_id, uint8_t n_replicas,\n                     ConfigStatus status, uint64_t fid,\n                     selection_rules_t rules)\n    : bucket_id(bucket_id), n_replicas(n_replicas), status(status),\n      fid(fid), default_placement(false), rules(rules)\n  {\n  }\n\n  PlacementArguments(item_id_t bucket_id, uint8_t n_replicas,\n                     ConfigStatus status, uint64_t fid)\n    : bucket_id(bucket_id), n_replicas(n_replicas), status(status),\n      fid(fid)\n  {\n  }\n\n  PlacementArguments(uint8_t n_replicas, ConfigStatus _status,\n                     PlacementStrategyT _strategy)\n    : bucket_id(0), n_replicas(n_replicas), status(_status), fid(0),\n      default_placement(true), rules(kDefault2Replica), strategy(_strategy)\n  {\n  }\n\n\n  PlacementArguments(uint8_t n_replicas, ConfigStatus _status)\n    : PlacementArguments(0, n_replicas, _status, 0)\n  {\n  }\n\n\n\n  PlacementArguments(uint8_t n_replicas)\n    : PlacementArguments(n_replicas, ConfigStatus::kRW)\n  {\n  }\n\n\n\n  PlacementArguments(item_id_t bucket_id, uint8_t n_replicas, ConfigStatus status)\n    : bucket_id(bucket_id), n_replicas(n_replicas), status(status),\n      fid(0), default_placement(true), rules(kDefault2Replica)\n  {\n  }\n\n  PlacementArguments(item_id_t bucket_id, uint8_t n_replicas) :\n    PlacementArguments(bucket_id, n_replicas, ConfigStatus::kRW)\n  {\n  }\n\n};\n\n  struct AccessArguments {\n    size_t& selectedIndex;\n    ino64_t inode {0};\n    PlacementStrategyT strategy {PlacementStrategyT::Count};\n\n    std::string_view geolocation;\n    std::vector<uint32_t>* unavailfs {nullptr};\n    const std::vector<uint32_t>& selectedfs;\n\n    AccessArguments(size_t& _selectedIndex, ino64_t _inode,\n                    PlacementStrategyT _strategy,\n                    std::string_view _geolocation,\n                    std::vector<uint32_t>* _unavailfs,\n                    const std::vector<uint32_t>& _selectedfs) :\n      selectedIndex(_selectedIndex), inode(_inode),\n      strategy(_strategy), geolocation(_geolocation),\n      unavailfs(_unavailfs), selectedfs(_selectedfs)\n    {\n    }\n\n    AccessArguments(size_t& _selectedIndex, PlacementStrategyT _strategy, const std::vector<uint32_t>& _selectedfs) :\n      selectedIndex(_selectedIndex),\n      strategy(_strategy),\n      selectedfs(_selectedfs)\n    {\n    }\n  };\n\nstruct PlacementStrategy {\n  using Args = PlacementArguments;\n\n  virtual PlacementResult placeFiles(const ClusterData& cluster_data,\n                                     Args args) = 0;\n\n  virtual int access(const ClusterData& cluster_data,\n                     AccessArguments& args) = 0;\n\n  bool validateArgs(const ClusterData& cluster_data, const Args& args,\n                    PlacementResult& result) const\n  {\n    if (args.n_replicas == 0) {\n      result.ret_code = EINVAL;\n      result.err_msg = \"Zero replicas requested\";\n      return false;\n    }\n\n    int32_t bucket_index = -args.bucket_id;\n    auto bucket_sz = cluster_data.buckets.size();\n\n    if (bucket_sz < args.n_replicas) {\n      result.err_msg = \"More replicas than bucket size!\";\n      result.ret_code = ERANGE;\n      return false;\n    }\n\n    try {\n      const auto& bucket = cluster_data.buckets.at(bucket_index);\n\n      if (bucket.items.size() < args.n_replicas) {\n        result.err_msg = \"Bucket \" + std::to_string(bucket.id) +\n                         \"does not contain enough elements!\";\n        result.ret_code = ENOENT;\n        return false;\n      }\n    } catch (std::out_of_range& e) {\n      result.err_msg = \"Bucket ID\" + std::to_string(bucket_index) + \"is invalid!\";\n      result.ret_code = ERANGE;\n      return false;\n    }\n\n    return true;\n  }\n\n  static bool validDiskPlct(item_id_t disk_id,\n                            const ClusterData& cluster_data,\n                            const std::vector<uint32_t>& excludefs,\n                            eos::common::ConfigStatus status)\n  {\n    if (disk_id <= 0) {\n      return false;\n    }\n\n    if (std::find(excludefs.begin(),\n                  excludefs.end(),\n                  disk_id) != excludefs.end()) {\n      return false;\n    }\n\n    auto disk_config_status = cluster_data.disks[disk_id - 1].config_status.load(\n                                std::memory_order_acquire);\n    auto disk_active_status = cluster_data.disks[disk_id - 1].active_status.load(\n                                std::memory_order_acquire);\n    return disk_active_status == eos::common::ActiveStatus::kOnline &&\n           disk_config_status >= status;\n  }\n\n  virtual ~PlacementStrategy() = default;\n\n  /**\n   * Calculates the maximum topological overlap between a candidate and existing replicas.\n   * Lower score is better.\n   * * @param candidate_id The disk ID we are considering adding.\n   * @param data The cluster data containing the GeoTag vectors.\n   * @param current_result The list of replicas already selected for this file.\n   * @param items_added How many items in current_result are valid.\n   * @return The number of shared hierarchy levels with the NEAREST existing replica.\n   */\n  size_t calculateMaxGeoOverlap(item_id_t candidate_id,\n                                const ClusterData& data,\n                                const PlacementResult& current_result,\n                                int items_added) const;\n\n  PlacementResult placeWithGeoFilter(const ClusterData& cluster_data,\n                                     const Args& args,\n                                     const std::vector<item_id_t>& sorted_candidates);\n};\n\nstatic inline uint64_t hashFid(uint64_t fid, uint64_t fsid, uint64_t salt=0) {\n// Using XXH3 as it provides good distribution and performance\n// ensure little-endian encoding for cross platform consistency\n  uint64_t buf[3] = {\n    htole64(fid),\n    htole64(fsid),\n    htole64(salt)\n  };\n  return XXH3_64bits(buf, sizeof(buf));\n}\n// Simple helper struct to help sort items based on score\nstruct RankedItem {\n  item_id_t id;\n  uint64_t score;\n\n  bool operator<(const RankedItem& other) const\n  {\n    return score < other.score;\n  }\n};\n\n} // namespace eos::mgm::placement\n"
  },
  {
    "path": "mgm/placement/RRSeed.hh",
    "content": "// ----------------------------------------------------------------------\n// File: RRSeed\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_RRSEED_HH\n#define EOS_RRSEED_HH\n\n#include <atomic>\n#include <vector>\n#include <cstddef>\n\nnamespace eos::mgm::placement {\n\n// A copyable atomic type! DO NOT use this as a synchronization primitive!\n// This is only meant for storing a seed value for a random number generator\n// wherein we only use this at the initialization phase to put onto a vector!\n// Do not copy atomic types when you are using them to synchronize values!\ntemplate <typename T>\nstruct AtomicWrapper {\n  AtomicWrapper(T t): value(t) {}\n  AtomicWrapper(const AtomicWrapper<T>& other): value(other.value.load()) {}\n\n  std::atomic<T> value;\n};\n\n// A simple round robin seed generator, stored as a list of atomic values,\n// the use case of the list is when you'd need a 2-D round robin and you'd\n// need to RR over the 2nd dimension. Under the hood this is nothing but just\n// a 1-D counter incremented to a given size.\ntemplate <typename T=uint64_t>\nclass RRSeed {\npublic:\n\n  // Currently the counter will wrap around to 0 if it reaches the max value\n  // of type T, as is defined for unsigned integers. If you need to -ve values\n  // rewrite carefully considering overflows!\n  static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value,\n                \"We expect only unsigned integer types, \"\n                \"otherwise overflow would be Undefined Behaviour\");\n\n  // Initialization is not TS: we assume that this is only called once!!!\n  explicit RRSeed(size_t max_items) : mSeeds(max_items, 0) {}\n\n  // Get a seed at an index, also reserve n_items, so that the next seed is n_items\n  // away. Please ensure the index is within range!\n  T get(size_t index, size_t n_items) {\n    return mSeeds.at(index).value.fetch_add(n_items, std::memory_order_relaxed);\n  }\n\n  size_t getNumSeeds() const { return mSeeds.size(); }\nprivate:\n  std::vector<AtomicWrapper<T>> mSeeds;\n};\n\n\n}\n\n#endif // EOS_RRSEED_HH\n"
  },
  {
    "path": "mgm/placement/RoundRobinPlacementStrategy.cc",
    "content": "#include \"mgm/placement/RoundRobinPlacementStrategy.hh\"\n#include \"common/utils/ContainerUtils.hh\"\n#include \"common/utils/RandUtils.hh\"\n\nnamespace eos::mgm::placement {\n\nstd::unique_ptr<RRSeeder>\nmakeRRSeeder(PlacementStrategyT strategy, size_t max_buckets)\n{\n  if (strategy == PlacementStrategyT::kThreadLocalRoundRobin) {\n    return std::make_unique<ThreadLocalRRSeeder>(max_buckets);\n  } else if (strategy == PlacementStrategyT::kRandom) {\n    return std::make_unique<RandomSeeder>(max_buckets);\n  } else if (strategy == PlacementStrategyT::kFidRandom) {\n    return std::make_unique<FidSeeder>(max_buckets);\n  }\n  return std::make_unique<GlobalRRSeeder>(max_buckets);\n}\n\n\nPlacementResult\nRoundRobinPlacement::placeFiles(const ClusterData& cluster_data, Args args)\n{\n\n  PlacementResult result(args.n_replicas);\n  if (!validateArgs(cluster_data, args, result)) {\n    return result;\n  }\n\n  int32_t bucket_index = -args.bucket_id;\n  auto bucket_sz = cluster_data.buckets.size();\n\n  if (bucket_sz > mSeed->getNumSeeds()) {\n      result.err_msg = \"More buckets than random seeds! seeds=\" +\n                       std::to_string(mSeed->getNumSeeds()) +\n                       \" buckets=\" + std::to_string(bucket_sz);\n    result.ret_code = ERANGE;\n    return result;\n  }\n\n  const auto& bucket = cluster_data.buckets[bucket_index];\n  auto rr_seed = mSeed->get(bucket_index, args.n_replicas, args.fid);\n  int items_added = 0;\n  for (int i = 0;\n       (items_added < args.n_replicas) && (i < MAX_PLACEMENT_ATTEMPTS); i++) {\n\n    auto id = eos::common::pickIndexRR(bucket.items, rr_seed + i);\n\n    // While it is highly unlikely that we'll get a duplicate with RR placement,\n    // random seed gen can still generate the same seed twice.\n    if (result.contains(id)) {\n      continue;\n    }\n\n    item_id_t item_id = id;\n    if (id > 0) {\n      // we are dealing with a disk! check if it is usable\n      if ((size_t)id > cluster_data.disks.size()) {\n        result.err_msg = \"Disk ID unknown!\";\n        result.ret_code = ERANGE;\n        return result;\n      }\n\n      if (!PlacementStrategy::validDiskPlct(item_id, cluster_data, args.excludefs, args.status)) {\n        continue;\n      }\n    }\n    result.ids[items_added++] = item_id;\n  }\n\n  if (items_added != args.n_replicas) {\n    result.err_msg = \"Could not find enough items to place replicas\";\n    result.ret_code = ENOSPC;\n    return result;\n  }\n\n  result.ret_code = 0;\n  return result;\n}\n\n\nint\nRoundRobinPlacement::access(const ClusterData& cluster_data, AccessArguments& args)\n{\n  args.selectedIndex = common::getRandom((size_t)0, args.selectedfs.size()-1);\n  return 0;\n}\n\n\n\n} // namespace eos::mgm::placement\n"
  },
  {
    "path": "mgm/placement/RoundRobinPlacementStrategy.hh",
    "content": "// ----------------------------------------------------------------------\n//! @file: RoundRobinPlacementStrategy.hh\n//! @author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include \"common/Logging.hh\"\n#include \"mgm/placement/ClusterDataTypes.hh\"\n#include \"mgm/placement/PlacementStrategy.hh\"\n#include \"mgm/placement/RRSeed.hh\"\n#include \"mgm/placement/ThreadLocalRRSeed.hh\"\n#include \"utils/RandUtils.hh\"\n\nnamespace eos::mgm::placement {\n\nstruct RRSeeder {\n  virtual ~RRSeeder() = default;\n  virtual size_t get(size_t index, size_t num_items, size_t fid) = 0;\n  virtual size_t getNumSeeds() = 0;\n};\n\nstruct GlobalRRSeeder : public RRSeeder {\n  explicit GlobalRRSeeder(size_t max_buckets) : mSeed(max_buckets) {}\n\n  size_t\n  get(size_t index, size_t num_items, size_t) override\n  {\n    return mSeed.get(index, num_items);\n  }\n\n  size_t\n  getNumSeeds() override\n  {\n    return mSeed.getNumSeeds();\n  }\n\nprivate:\n  RRSeed<size_t> mSeed;\n};\n\nstruct ThreadLocalRRSeeder : public RRSeeder {\n  explicit ThreadLocalRRSeeder(size_t max_buckets)\n  {\n    ThreadLocalRRSeed::init(max_buckets);\n  }\n\n  size_t\n  get(size_t index, size_t num_items, size_t) override\n  {\n    return ThreadLocalRRSeed::get(index, num_items);\n  }\n\n  size_t\n  getNumSeeds() override\n  {\n    return ThreadLocalRRSeed::getNumSeeds();\n  }\n};\n\nstruct RandomSeeder: public RRSeeder {\n  explicit RandomSeeder(size_t max_buckets)\n      : mMaxBuckets(max_buckets)\n  {\n  }\n\n  size_t\n  get(size_t index, size_t, size_t) override\n  {\n    if (index > mMaxBuckets) {\n      eos_static_err(\"msg=\\\"RandomSeeder index > MaxBuckets\\\" index=%lu mMaxBuckets=%lu\",\n                     index, mMaxBuckets);\n      return eos::common::getRandom(0ul, mMaxBuckets - 1) + index - mMaxBuckets;\n    }\n    return eos::common::getRandom(0ul, mMaxBuckets - 1);\n  }\n\n  size_t\n  getNumSeeds() override\n  {\n    return mMaxBuckets;\n  }\n\nprivate:\n  size_t mMaxBuckets;\n};\n\nstruct FidSeeder: public RRSeeder {\n  explicit FidSeeder(size_t _max_buckets) : max_buckets(_max_buckets) {}\n\n  size_t get(size_t index, size_t replicas, size_t fid) {\n    return index ^ replicas ^ fid;\n  }\n\n  size_t getNumSeeds() override {\n    return max_buckets;\n  }\nprivate:\n  size_t max_buckets;\n};\n\nstd::unique_ptr<RRSeeder>\nmakeRRSeeder(PlacementStrategyT strategy, size_t max_buckets);\n\n\nclass RoundRobinPlacement : public PlacementStrategy {\npublic:\n  explicit RoundRobinPlacement(PlacementStrategyT strategy, size_t max_buckets)\n      : mSeed(makeRRSeeder(strategy, max_buckets))\n  {\n  }\n\n  PlacementResult placeFiles(const ClusterData& cluster_data,\n                             Args args) override;\n\n  int access(const ClusterData& cluster_data,\n             AccessArguments& args) override;\n\nprivate:\n  std::unique_ptr<RRSeeder> mSeed;\n};\n} // namespace eos::mgm::placementq\n"
  },
  {
    "path": "mgm/placement/ThreadLocalRRSeed.cc",
    "content": "\n#include \"ThreadLocalRRSeed.hh\"\n#include \"common/Logging.hh\"\n#include \"utils/RandUtils.hh\"\n\nnamespace eos::mgm::placement {\n\nthread_local std::vector<uint64_t> ThreadLocalRRSeed::gRRSeeds(kDefaultMaxRRSeeds,0);\n\nvoid\nThreadLocalRRSeed::init(size_t max_items, bool randomize)\n{\n  gRRSeeds.resize(max_items, 0);\n  if (randomize) {\n    for (size_t i = 0; i < max_items; i++) {\n      gRRSeeds[i] = eos::common::getRandom(0ul, max_items);\n    }\n  }\n\n}\n\nvoid\nThreadLocalRRSeed::resize(size_t max_items, bool randomize)\n{\n  auto old_size = gRRSeeds.size();\n  gRRSeeds.resize(max_items, 0);\n  if (randomize) {\n    for (size_t i = old_size; i < max_items; i++) {\n      gRRSeeds[i] = eos::common::getRandom(0ul, max_items);\n    }\n  }\n}\n\nuint64_t\nThreadLocalRRSeed::get(size_t index, size_t n_items)\n{\n  if (index >= gRRSeeds.size()) {\n    eos_static_crit(\"index %lu is out of range %lu\", index, gRRSeeds.size());\n    return 0;\n  }\n  uint64_t ret = gRRSeeds[index];\n  gRRSeeds[index] += n_items;\n  return ret;\n}\n\n} // namespace eos::mgm::placement\n"
  },
  {
    "path": "mgm/placement/ThreadLocalRRSeed.hh",
    "content": "#ifndef EOS_THREADLOCALRRSEED_HH\n#define EOS_THREADLOCALRRSEED_HH\n\n#include <vector>\n#include <cstdint>\n#include <cstddef>\n\nnamespace eos::mgm::placement {\n\nconstexpr size_t kDefaultMaxRRSeeds = 1024;\n\n\n/*A thread local version of RRSeed, in the scheduler context, we don't want\n * the seeds to all start at 0, we initialize with random numbers at first */\nstruct ThreadLocalRRSeed {\n  static uint64_t get(size_t index, size_t n_items);\n\n  static void init(size_t max_items, bool randomize = true);\n\n  static void resize(size_t max_items, bool randomize = true);\n\n  static size_t getNumSeeds()  {\n    return gRRSeeds.size();\n  }\n\n  static thread_local std::vector<uint64_t> gRRSeeds;\n};\n\n\n\n} // namespace eos::mgm::placement\n#endif // EOS_THREADLOCALRRSEED_HH\n"
  },
  {
    "path": "mgm/placement/WeightedRandomStrategy.cc",
    "content": "#include \"mgm/placement/WeightedRandomStrategy.hh\"\n#include \"common/Logging.hh\"\n#include <random>\n#include <shared_mutex>\n\nnamespace eos::mgm::placement\n{\n\nstruct WeightedRandomPlacement::Impl {\n  PlacementResult placeFiles(const ClusterData& data,\n                             Args args);\n\n  void populateWeights(const ClusterData& data);\n  std::shared_mutex mtx;\n  std::discrete_distribution<> mBucketWeights;\n  std::map<item_id_t, std::discrete_distribution<>> mDiskWeights;\n};\n\nvoid WeightedRandomPlacement::Impl::populateWeights(const ClusterData& data)\n{\n  std::vector<int> weights(data.buckets.size());\n  std::vector<int> item_weights;\n\n  // TODO optimize single element lists! no need to use a random distrib!\n  for (const auto& bucket : data.buckets) {\n    weights.at(-bucket.id) = bucket.total_weight;\n\n    for (const auto& item_id : bucket.items) {\n      if (item_id > 0) {\n        item_weights.push_back(data.disks.at(item_id - 1).weight);\n      } else {\n        item_weights.push_back(data.buckets.at(-item_id).total_weight);\n      }\n    }\n\n    mDiskWeights.emplace(bucket.id,\n                         std::discrete_distribution<>(item_weights.begin(),\n                             item_weights.end()));\n    item_weights.clear();\n  }\n\n  mBucketWeights = std::discrete_distribution<>(weights.begin(), weights.end());\n}\n\nPlacementResult WeightedRandomPlacement::Impl::placeFiles(\n  const ClusterData& data,\n  Args args)\n{\n  PlacementResult result(args.n_replicas);\n  static thread_local std::random_device rd;\n  static thread_local std::mt19937 gen(rd());\n  std::shared_lock rlock(mtx);\n\n  // This is only called at initialization\n  if (mBucketWeights.max() == 0) {\n    rlock.unlock();\n    std::unique_lock wlock(mtx);\n\n    if (mBucketWeights.max() == 0) {\n      try {\n        populateWeights(data);\n      } catch (std::exception& e) {\n        eos_static_crit(\"msg=\\\"exception while populating weights\\\" ec=%d emsg=\\\"%s\\\"\",\n                        EINVAL, e.what());\n        result.err_msg = e.what();\n        result.ret_code = EINVAL;\n        return result;\n      }\n    }\n  }\n\n  int32_t bucket_index = -args.bucket_id;\n  int items_added = 0;\n\n  for (int i = 0; items_added < args.n_replicas && i < MAX_PLACEMENT_ATTEMPTS;\n       i++) {\n    auto item_index = mDiskWeights[args.bucket_id](gen);\n    item_id_t item_id = data.buckets[bucket_index].items[item_index];\n    eos_static_debug(\"Got item_index=%d item_id=%d\",\n                     item_index, item_id);\n\n    if (result.contains(item_id)) {\n      eos_static_info(\"msg=\\\"Skipping duplicate result\\\" item_id=%d\", item_id);\n      continue;\n    }\n\n    if (item_id > 0) {\n      if ((size_t)item_id > data.disks.size()) {\n        result.err_msg = \"Disk ID out of range\";\n        result.ret_code = ERANGE;\n        return result;\n      }\n\n      if (!PlacementStrategy::validDiskPlct(item_id, data, args.excludefs, args.status)) {\n        continue;\n      }\n    }\n\n    result.ids[items_added++] = item_id;\n  }\n\n  result.ret_code = 0;\n  return result;\n}\n\nWeightedRandomPlacement::WeightedRandomPlacement(PlacementStrategyT strategy,\n    size_t max_buckets) :\n  mImpl(std::make_unique<Impl>())\n{\n}\n\nPlacementResult WeightedRandomPlacement::placeFiles(const ClusterData& data,\n    Args args)\n{\n  PlacementResult result(args.n_replicas);\n\n  if (!validateArgs(data, args, result)) {\n    return result;\n  }\n\n  return mImpl->placeFiles(data, std::move(args));\n}\n\nint WeightedRandomPlacement::access(const ClusterData &data, AccessArguments& args)\n{\n\n  //TODO move all of the common validation to base class!\n  if (args.selectedfs.empty()) {\n    return ENOENT;\n  }\n\n  uint64_t best_score = 0;\n  size_t best_index = std::numeric_limits<size_t>::max();\n\n  for (const auto& fsid: args.selectedfs) {\n    if ((size_t)fsid > data.disks.size()) {\n      eos_static_info(\"msg=\\\"FlatScheduler Access - Skipping invalid fsid\\\" fsid=%u\", fsid);\n      continue;\n    }\n    const auto& disk = data.disks[fsid - 1];\n\n    if (!validDiskPlct(fsid, data, args.unavailfs ? *args.unavailfs : std::vector<uint32_t>{},\n                        eos::common::ConfigStatus::kRO)) {\n      continue;\n    }\n\n    auto h = hashFid(args.inode, fsid);\n    auto wt = disk.weight.load(std::memory_order_relaxed);\n    uint64_t score = h/wt;\n    if (best_index == std::numeric_limits<size_t>::max() ||\n        score < best_score) {\n      best_score = score;\n      best_index = fsid;\n    }\n\n  }\n\n  if (best_index <= args.selectedfs.size()) {\n    args.selectedIndex = best_index;\n    return 0;\n  }\n\n  return ENOENT;\n}\n\nWeightedRandomPlacement::~WeightedRandomPlacement() = default;\n\n} // namespace eos::mgm::placement\n"
  },
  {
    "path": "mgm/placement/WeightedRandomStrategy.hh",
    "content": "// ----------------------------------------------------------------------\n//! @file: WeightedPlacementStrategy.hh\n//! @author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/placement/PlacementStrategy.hh\"\nnamespace eos::mgm::placement {\n\n/**\n * A placement strategy that places files on nodes based on a weighted\n * random distribution. The weights are currently based on the disk sizes\n */\nclass WeightedRandomPlacement : public PlacementStrategy {\npublic:\n  WeightedRandomPlacement(PlacementStrategyT strategy, size_t max_buckets);\n  virtual PlacementResult placeFiles(const ClusterData& data,\n                                     Args args) override;\n  virtual int access(const ClusterData& data, AccessArguments& args) override;\n  ~WeightedRandomPlacement();\nprivate:\n  struct Impl;\n  std::unique_ptr<Impl> mImpl;\n};\n\n} // namespace eos::mgm::placement"
  },
  {
    "path": "mgm/placement/WeightedRoundRobinStrategy.cc",
    "content": "#include \"mgm/placement/WeightedRoundRobinStrategy.hh\"\n#include \"common/utils/ContainerUtils.hh\"\n#include \"common/Logging.hh\"\n#include <mutex>\n\nnamespace eos::mgm::placement {\n\n\nstruct WeightedRoundRobinPlacement::Impl {\n  PlacementResult placeFiles(const ClusterData& data,\n                             Args args);\n\n  void fill_weights(const ClusterData& data)\n  {\n    total_wt = 0;\n    std::for_each(data.buckets.begin(),\n                  data.buckets.end(),\n                  [this](const Bucket& bucket) {\n                    mItemWeights[bucket.id] = bucket.total_weight;\n                    total_wt += bucket.total_weight;\n                  });\n    total_disk_wt = 0;\n    std::for_each(data.disks.begin(),\n                  data.disks.end(),\n                  [this](const Disk& disk) {\n                    auto disk_wt = disk.weight.load(std::memory_order_acquire);\n                    mItemWeights[disk.id] = disk_wt;\n                    total_disk_wt += disk_wt;\n                  });\n  }\n\n  std::map<item_id_t, int> mItemWeights;\n  std::atomic<epoch_id_t> mCurrentEpoch;\n  std::map<item_id_t, int> mBucketIndex;\n  std::atomic<int64_t> total_wt {0};\n  std::atomic<int64_t> total_disk_wt{0};\n  std::mutex wt_mtx;\n};\n\n\n\nPlacementResult\nWeightedRoundRobinPlacement::Impl::placeFiles(const ClusterData& cluster_data,\n                                              Args args)\n{\n  std::scoped_lock lock(wt_mtx);\n  //NOTE: when 2 requests reach at the same point when near 0, we'll end up\n  //granting all of them... in spite of near 0 weights.. this is fine as the\n  //weighting is still an approximate means and there is no need for exactness,\n  //the next cycle should refresh the weights correctly\n\n  if (total_wt < (args.n_replicas)) {\n    eos_static_info(\"%s\",\"msg=\\\"Refilling weights\\\"\");\n    fill_weights(cluster_data);\n  }\n\n  PlacementResult result(args.n_replicas);\n\n  auto bucket_index_kv = mBucketIndex[args.bucket_id]++;\n  int32_t bucket_index = -args.bucket_id;\n  const auto& bucket = cluster_data.buckets[bucket_index];\n  int items_added = 0;\n  for (int i = 0;\n       (items_added < args.n_replicas) && (i < MAX_PLACEMENT_ATTEMPTS); i++) {\n    item_id_t item_id = eos::common::pickIndexRR(bucket.items, bucket_index_kv++);\n\n    if (result.contains(item_id)) {\n      continue;\n    }\n\n    if (item_id > 0) {\n      if ((mItemWeights[args.bucket_id] < args.n_replicas)\n          || mItemWeights[args.bucket_id] == mItemWeights[item_id]) {\n        fill_weights(cluster_data);\n      }\n\n      if (--mItemWeights[item_id] < 0) {\n        eos_static_debug(\"msg=\\\"Skipping scheduling 0 wt item at\\\" item_id=%d total_wt=%llu\",\n                         item_id, total_wt.load(std::memory_order_relaxed));\n        continue;\n      }\n\n      if (std::find(args.excludefs.begin(),\n                    args.excludefs.end(), item_id) != args.excludefs.end()) {\n        continue;\n      }\n\n      if ((size_t)item_id > cluster_data.disks.size()) {\n        result.err_msg = \"Disk ID unknown!\";\n        result.ret_code = ERANGE;\n        return result;\n      }\n\n      const auto& disk = cluster_data.disks[item_id - 1];\n      if (disk.active_status.load(std::memory_order_acquire) !=\n          eos::common::ActiveStatus::kOnline) {\n        continue;\n      }\n\n      auto disk_status = disk.config_status.load(std::memory_order_relaxed);\n      if (disk_status < args.status) {\n        continue;\n      }\n\n\n      item_id = disk.id;\n      --total_wt;\n      --mItemWeights[args.bucket_id];\n\n\n    } else {\n      // We're dealing with a bucket, make sure we've enough wt left!\n      if (mItemWeights[item_id] < args.n_replicas) {\n        continue;\n      }\n    }\n    result.ids[items_added++] = item_id;\n  }\n  if (items_added != args.n_replicas) {\n    result.err_msg = \"Failed to place files!\";\n    result.ret_code = ENOSPC;\n    return result;\n  }\n  result.ret_code = 0;\n  return result;\n}\n\nWeightedRoundRobinPlacement::WeightedRoundRobinPlacement(PlacementStrategyT strategy,\n                                                         size_t max_buckets):\n  mImpl(std::make_unique<Impl>())\n{}\n\nPlacementResult WeightedRoundRobinPlacement::placeFiles(const ClusterData& data,\n                                                        Args args)\n{\n  PlacementResult result(args.n_replicas);\n  if (!validateArgs(data, args, result)) {\n    return result;\n  }\n\n  return mImpl->placeFiles(data, std::move(args));\n}\n\nint WeightedRoundRobinPlacement::access(const ClusterData &data, AccessArguments& args)\n{\n  return EINVAL;\n}\n\nWeightedRoundRobinPlacement::~WeightedRoundRobinPlacement() = default;\n} // eos::mgm::placement\n"
  },
  {
    "path": "mgm/placement/WeightedRoundRobinStrategy.hh",
    "content": "// ----------------------------------------------------------------------\n//! @file: WeightedRoundRobinPlacementStrategy.hh\n//! @author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/placement/PlacementStrategy.hh\"\nnamespace eos::mgm::placement {\n\n/**\n * A placement strategy that places files on nodes based on a weighted\n * random distribution. The weights are currently based on the disk sizes\n */\nclass WeightedRoundRobinPlacement : public PlacementStrategy {\npublic:\n  WeightedRoundRobinPlacement(PlacementStrategyT strategy, size_t max_buckets);\n  virtual PlacementResult placeFiles(const ClusterData& data,\n                                     Args args) override;\n  virtual int access(const ClusterData& data, AccessArguments& args) override;\n  ~WeightedRoundRobinPlacement();\nprivate:\n  struct Impl;\n  std::unique_ptr<Impl> mImpl;\n};\n\n} // namespace eos::mgm::placement\n"
  },
  {
    "path": "mgm/policy/Policy.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Policy.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"mgm/policy/Policy.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Mapping.hh\"\n#include \"common/Utils.hh\"\n#include \"common/utils/ContainerUtils.hh\"\n#include \"common/utils/XrdUtils.hh\"\n#include \"mgm/misc/Constants.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n/*----------------------------------------------------------------------------*/\n\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\nconst std::vector<std::string> Policy::gBasePolicyKeys = {\n  \"policy.space\",         \"policy.layout\",           \"policy.nstripes\",\n  \"policy.checksum\",      \"policy.blocksize\",        \"policy.blockchecksum\",\n  \"policy.localredirect\", \"policy.updateconversion\", \"policy.readconversion\",\n  \"policy.altspaces\"\n};\n\nconst std::vector<std::string> Policy::gBasePolicyRWKeys = {\n  \"policy.bandwidth\", \"policy.iopriority\", \"policy.iotype\",\n  \"policy.schedule\"\n};\n\n/*----------------------------------------------------------------------------*/\ndouble\nPolicy::GetDefaultSizeFactor(std::shared_ptr<eos::IContainerMD> cmd)\n{\n  XrdOucEnv env(\"\");\n  unsigned long forcedfsid;\n  long forcedgroup;\n  unsigned long layoutid = 0;\n  std::string ret_space;\n  std::string bandwidth;\n  bool schedule = 0;\n  std::string iopriority;\n  std::string iotype;\n  bool isrw = false;     // does not matter\n  uint64_t atimeage = 0; // does not matter\n  eos::IContainerMD::XAttrMap attrmap = cmd->getAttributes();\n  eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n  GetLayoutAndSpace(\"/\", attrmap, rootvid, layoutid, ret_space, env, forcedfsid,\n                    forcedgroup, bandwidth, schedule, iopriority, iotype, isrw,\n                    false, &atimeage);\n  double f = eos::common::LayoutId::GetSizeFactor(layoutid);\n  return f ? f : 1.0;\n}\n\n/*----------------------------------------------------------------------------*/\nunsigned long\nPolicy::GetSpacePolicyLayout(const char* space)\n{\n  std::string space_env = \"eos.space=\";\n  space_env += space;\n  XrdOucEnv env(space_env.c_str());\n  unsigned long forcedfsid;\n  long forcedgroup;\n  unsigned long layoutid = 0;\n  std::string ret_space;\n  std::string bandwidth;\n  bool schedule = 0;\n  std::string iopriority;\n  std::string iotype;\n  bool isrw = false;     // does not matter\n  uint64_t atimeage = 0; // does not matter\n  eos::IContainerMD::XAttrMap attrmap;\n  eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n  GetLayoutAndSpace(\"/\", attrmap, rootvid, layoutid, ret_space, env, forcedfsid,\n                    forcedgroup, bandwidth, schedule, iopriority, iotype, isrw,\n                    true, &atimeage, nullptr, nullptr);\n  return layoutid;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nPolicy::GetLayoutAndSpace(const char* path,\n                          eos::IContainerMD::XAttrMap& attrmap,\n                          const eos::common::VirtualIdentity& vid,\n                          unsigned long& layoutId, std::string& space,\n                          XrdOucEnv& env, unsigned long& forcedfsid,\n                          long& forcedgroup, std::string& bandwidth,\n                          bool& schedule, std::string& iopriority,\n                          std::string& iotype, bool rw, bool lockview,\n                          uint64_t* atimeage, std::vector<std::string>* altChecksums, bool* computeAltXs)\n{\n  eos::common::RWMutexReadLock lock;\n  // this is for the moment only defaulting or manual selection\n  unsigned long layout = eos::common::LayoutId::GetLayoutFromEnv(env);\n  unsigned long xsum = eos::common::LayoutId::GetChecksumFromEnv(env);\n  unsigned long bxsum = eos::common::LayoutId::GetBlockChecksumFromEnv(env);\n  unsigned long stripes = eos::common::LayoutId::GetStripeNumberFromEnv(env);\n  unsigned long blocksize = eos::common::LayoutId::GetBlocksizeFromEnv(env);\n  bandwidth = eos::common::LayoutId::GetBandwidthFromEnv(env);\n  iotype = eos::common::LayoutId::GetIotypeFromEnv(env);\n  bool noforcedchecksum = false;\n  const char* val = 0;\n  bool conversion = IsProcConversion(path);\n  std::map<std::string, std::string> spacepolicies;\n  std::map<std::string, std::string> spacerwpolicies;\n  std::string satime;\n  std::string altxsopt;\n  RWParams rwparams{vid.uid_string, vid.gid_string,\n                    eos::common::XrdUtils::GetEnv(env, \"eos.app\", \"default\"),\n                    rw};\n  auto policy_keys = GetConfigKeys();\n  auto policy_rw_keys = GetRWConfigKeys(rwparams);\n\n  if (!conversion) {\n    // don't apply space policies to conversion paths\n    if (lockview) {\n      lock.Grab(FsView::gFsView.ViewMutex);\n    }\n\n    auto it = FsView::gFsView.mSpaceView.find(\"default\");\n\n    if (it != FsView::gFsView.mSpaceView.end()) {\n      it->second->GetConfigMembers(policy_keys, spacepolicies);\n      it->second->GetConfigMembers(policy_rw_keys, spacerwpolicies);\n      satime = it->second->GetConfigMember(\"atime\");\n      altxsopt = it->second->GetConfigMember(\"altxs\");\n    } // FSView default\n\n    if (lockview) {\n      lock.Release();\n    }\n  } // if !conversion\n\n  std::string schedule_str;\n  GetRWValue(spacerwpolicies, POLICY_SCHEDULE, rwparams, schedule_str);\n  GetRWValue(spacerwpolicies, POLICY_IOPRIORITY, rwparams, iopriority);\n  GetRWValue(spacerwpolicies, POLICY_IOTYPE, rwparams, iotype);\n  GetRWValue(spacerwpolicies, POLICY_BANDWIDTH, rwparams, bandwidth);\n  schedule = schedule_str.length() ? schedule_str == \"1\" : schedule;\n\n  if ((val = env.Get(\"eos.space\"))) {\n    space = val;\n  } else {\n    space = \"default\";\n\n    if (!conversion) {\n      std::string space_key = \"policy.space\";\n\n      if (auto kv = spacepolicies.find(space_key);\n          kv != spacepolicies.end() && (!kv->second.empty())) {\n        // if there is no explicit space given, we preset with the policy one\n        space = kv->second.c_str();\n      }\n    }\n  }\n\n  // Replace the non empty settings from the default space have been already\n  // defined before\n  if (!conversion && space != \"default\") {\n    std::map<std::string, std::string> nondefault_policies;\n    spacerwpolicies.clear();\n\n    if (lockview) {\n      lock.Grab(FsView::gFsView.ViewMutex);\n    }\n\n    auto it = FsView::gFsView.mSpaceView.find(space.c_str());\n\n    if (it != FsView::gFsView.mSpaceView.end()) {\n      it->second->GetConfigMembers(policy_keys, nondefault_policies);\n      it->second->GetConfigMembers(policy_rw_keys, spacerwpolicies);\n      satime = it->second->GetConfigMember(\"atime\");\n      altxsopt = it->second->GetConfigMember(\"altxs\");\n    } // FsView;\n\n    if (lockview) {\n      lock.Release();\n    }\n\n    // Since this map only contains keys that are already populated, we can be\n    // sure that we'll be only replacing non empty keys\n    for (auto&& kv : nondefault_policies) {\n      if (!kv.second.empty()) {\n        spacepolicies.insert_or_assign(kv.first, std::move(kv.second));\n      }\n    }\n\n    std::string schedule_str;\n    GetRWValue(spacerwpolicies, POLICY_SCHEDULE, rwparams, schedule_str);\n    GetRWValue(spacerwpolicies, POLICY_IOPRIORITY, rwparams, iopriority);\n    GetRWValue(spacerwpolicies, POLICY_IOTYPE, rwparams, iotype);\n    GetRWValue(spacerwpolicies, POLICY_BANDWIDTH, rwparams, bandwidth);\n    schedule = schedule_str.length() ? schedule_str == \"1\" : schedule;\n  } // !conversion && space != default\n\n  // look if we have to inject the default space policies\n  for (const auto& it : spacepolicies) {\n    std::string key_name = it.first.substr(7);\n\n    if (key_name == \"space\") {\n      continue;\n    }\n\n    std::string sys_key = \"sys.forced.\";\n    std::string user_key = \"user.forced.\";\n    sys_key += key_name;\n    user_key += key_name;\n\n    if ((!attrmap.count(sys_key)) && (!attrmap.count(user_key)) &&\n        !it.second.empty()) {\n      attrmap[sys_key] = it.second;\n    }\n  }\n\n  common::XrdUtils::GetEnv(env, \"eos.group\", forcedgroup,  -1l);\n\n  if ((xsum != eos::common::LayoutId::kNone) &&\n      (val = env.Get(\"eos.checksum.noforce\"))) {\n    // we don't force *.forced.checksum settings\n    // we need this flag to be able to force MD5 checksums for S3 uploads\n    noforcedchecksum = true;\n  }\n\n  if ((vid.uid == 0) && (val = env.Get(\"eos.layout.noforce\"))) {\n    // root can request not to apply any forced settings\n  } else {\n    if (auto space_kv = attrmap.find(SYS_FORCED_SPACE);\n        space_kv != attrmap.end()) {\n      // we force to use a certain space in this directory even if the user\n      // wants something else\n      space = space_kv->second.c_str();\n      eos_static_debug(\"sys.forced.space in %s\", path);\n    }\n\n    // Check if the given space is under the nominal value, otherwise loop\n    // through\n    // altspaces and take the first having capacity\n    if (rw) {\n      std::vector<std::string> alt_spaces;\n      std::string altspaces_key = \"policy.altspaces\";\n\n      if (auto kv = spacepolicies.find(altspaces_key);\n          kv != spacepolicies.end() && (!kv->second.empty())) {\n        if (FsView::gFsView.UnderNominalQuota(space, (vid.uid == 0))) {\n          eos::common::StringConversion::Tokenize(kv->second, alt_spaces, \",\");\n\n          for (auto aspace : alt_spaces) {\n            if (FsView::gFsView.UnderNominalQuota(aspace, (vid.uid == 0))) {\n              eos_static_info(\"msg=\\\"space '%s' is under nominal quota - \"\n                              \"selected alternative space '%s'\",\n                              space.c_str(), aspace.c_str());\n              space = aspace;\n              // select this one and refersh all the attrmap for\n              // the following section!\n              std::map<std::string, std::string> altspacepolicies;\n              spacerwpolicies.clear();\n\n              if (lockview) {\n                lock.Grab(FsView::gFsView.ViewMutex);\n              }\n\n              auto it = FsView::gFsView.mSpaceView.find(space.c_str());\n\n              if (it != FsView::gFsView.mSpaceView.end()) {\n                it->second->GetConfigMembers(policy_keys, altspacepolicies);\n                it->second->GetConfigMembers(policy_rw_keys, spacerwpolicies);\n                satime = it->second->GetConfigMember(\"atime\");\n              } // FsView;\n\n              if (lockview) {\n                lock.Release();\n              }\n\n              std::string schedule_str;\n              GetRWValue(spacerwpolicies, POLICY_SCHEDULE, rwparams,\n                         schedule_str);\n              GetRWValue(spacerwpolicies, POLICY_IOPRIORITY, rwparams,\n                         iopriority);\n              GetRWValue(spacerwpolicies, POLICY_IOTYPE, rwparams, iotype);\n              GetRWValue(spacerwpolicies, POLICY_BANDWIDTH, rwparams,\n                         bandwidth);\n              schedule = schedule_str.length() ? schedule_str == \"1\" : schedule;\n\n              // overwrite everything from the space policy settings for the alternative space!\n              for (const auto& it : altspacepolicies) {\n                std::string key_name = it.first.substr(7);\n\n                if (key_name == \"space\") {\n                  continue;\n                }\n\n                if (it.second.empty()) {\n                  continue;\n                }\n\n                std::string sys_key = \"sys.forced.\";\n                sys_key += key_name;\n                attrmap[sys_key] = it.second;\n                eos_static_info(\"msg=\\\"setting alternative space policy\\\" attr=\\\"%s\\\" value=\\\"%s\\\"\",\n                                sys_key.c_str(), it.second.c_str());\n              }\n\n              break;\n            }\n          }\n        }\n      }\n    }\n\n    if (auto forcedgroup_kv = attrmap.find(SYS_FORCED_GROUP);\n        forcedgroup_kv != attrmap.end()) {\n      // we force to use a certain group in this directory even if the user\n      // wants something else\n      eos::common::StringToNumeric(forcedgroup_kv->second, forcedgroup);\n      eos_static_debug(\"sys.forced.group in %s\", path);\n    }\n\n    if (auto kv = attrmap.find(SYS_FORCED_LAYOUT); kv != attrmap.end()) {\n      // we force to use a specified layout in this directory even if the user\n      // wants something else\n      layout = eos::common::LayoutId::GetLayoutFromString(kv->second);\n      eos_static_debug(\"sys.forced.layout in %s\", path);\n    }\n\n    if (!noforcedchecksum) {\n      if (auto kv = attrmap.find(SYS_FORCED_CHECKSUM); kv != attrmap.end()) {\n        // we force to use a specified checksumming in this directory even if\n        // the user wants something else\n        xsum = eos::common::LayoutId::GetChecksumFromString(kv->second);\n        eos_static_debug(\"sys.forced.checksum in %s\", path);\n      }\n    }\n\n    if (auto kv = attrmap.find(SYS_FORCED_BLOCKCHECKSUM); kv != attrmap.end()) {\n      bxsum = eos::common::LayoutId::GetBlockChecksumFromString(kv->second);\n      eos_static_debug(\"sys.forced.blockchecksum in %s %x\", path, bxsum);\n    }\n\n    if (attrmap.count(SYS_FORCED_NSTRIPES)) {\n      XrdOucString layoutstring = \"eos.layout.nstripes=\";\n      layoutstring += attrmap[\"sys.forced.nstripes\"].c_str();\n      XrdOucEnv layoutenv(layoutstring.c_str());\n      // we force to use a specified stripe number in this directory even if the\n      // user wants something else\n      stripes = eos::common::LayoutId::GetStripeNumberFromEnv(layoutenv);\n      eos_static_debug(\"sys.forced.nstripes in %s\", path);\n    }\n\n    if (attrmap.count(SYS_FORCED_BLOCKSIZE)) {\n      XrdOucString layoutstring = \"eos.layout.blocksize=\";\n      layoutstring += attrmap[\"sys.forced.blocksize\"].c_str();\n      XrdOucEnv layoutenv(layoutstring.c_str());\n      // we force to use a specified stripe width in this directory even if the\n      // user wants something else\n      blocksize = eos::common::LayoutId::GetBlocksizeFromEnv(layoutenv);\n      eos_static_debug(\"sys.forced.blocksize in %s : %llu\", path, blocksize);\n    }\n\n    std::string iotypeattr = rw ? \"sys.forced.iotype:w\" : \"sys.forced.iotype:r\";\n\n    if (attrmap.count(iotypeattr)) {\n      iotype = attrmap[iotypeattr];\n      eos_static_debug(\"sys.forced.iotype i %s : %s\", path, iotype.c_str());\n    }\n\n    std::string iopriorityattr =\n      rw ? \"sys.forced.iopriority:w\" : \"sys.forced.iopriority:r\";\n\n    if (attrmap.count(iopriorityattr)) {\n      iopriority = attrmap[iopriorityattr];\n      eos_static_debug(\"sys.forced.iopriority i %s : %s\", path,\n                       iopriority.c_str());\n    }\n\n    std::string bandwidthattr =\n      rw ? \"sys.forced.bandwidth:w\" : \"sys.forced.bandwidth:r\";\n\n    if (attrmap.count(bandwidthattr)) {\n      bandwidth = attrmap[bandwidthattr];\n      eos_static_debug(\"sys.forced.bandwidth i %s : %s\", path,\n                       bandwidth.c_str());\n    }\n\n    std::string scheduleattr =\n      rw ? \"sys.forced.schedule:w\" : \"sys.forced.schedule:r\";\n\n    if (attrmap.count(scheduleattr)) {\n      schedule = (attrmap[scheduleattr] == \"1\");\n      eos_static_debug(\"sys.forced.schedule i %s : %d\", path);\n    }\n\n    if (((!attrmap.count(\"sys.forced.nouserlayout\")) ||\n         (attrmap[\"sys.forced.nouserlayout\"] != \"1\")) &&\n        ((!attrmap.count(\"user.forced.nouserlayout\")) ||\n         (attrmap[\"user.forced.nouserlayout\"] != \"1\"))) {\n      if (attrmap.count(\"user.forced.space\")) {\n        // we force to use a certain space in this directory even if the user\n        // wants something else\n        space = attrmap[\"user.forced.space\"].c_str();\n        eos_static_debug(\"user.forced.space in %s\", path);\n      }\n\n      if (auto kv = attrmap.find(USER_FORCED_LAYOUT); kv != attrmap.end()) {\n        // we force to use a specified layout in this directory even if the user\n        // wants something else\n        layout = eos::common::LayoutId::GetLayoutFromString(kv->second);\n        eos_static_debug(\"user.forced.layout in %s\", path);\n      }\n\n      if (!noforcedchecksum) {\n        if (auto kv = attrmap.find(USER_FORCED_CHECKSUM); kv != attrmap.end()) {\n          // we force to use a specified checksumming in this directory even if\n          // the user wants something else\n          xsum = eos::common::LayoutId::GetChecksumFromString(kv->second);\n          eos_static_debug(\"user.forced.checksum in %s\", path);\n        }\n      }\n\n      if (auto kv = attrmap.find(USER_FORCED_BLOCKCHECKSUM);\n          kv != attrmap.end()) {\n        // we force to use a specified checksumming in this directory even if\n        // the user wants something else\n        bxsum = eos::common::LayoutId::GetBlockChecksumFromString(kv->second);\n        eos_static_debug(\"user.forced.blockchecksum in %s\", path);\n      }\n\n      if (attrmap.count(USER_FORCED_NSTRIPES)) {\n        XrdOucString layoutstring = \"eos.layout.nstripes=\";\n        layoutstring += attrmap[\"user.forced.nstripes\"].c_str();\n        XrdOucEnv layoutenv(layoutstring.c_str());\n        // we force to use a specified stripe number in this directory even if\n        // the user wants something else\n        stripes = eos::common::LayoutId::GetStripeNumberFromEnv(layoutenv);\n        eos_static_debug(\"user.forced.nstripes in %s\", path);\n      }\n\n      if (attrmap.count(USER_FORCED_BLOCKSIZE)) {\n        XrdOucString layoutstring = \"eos.layout.blocksize=\";\n        layoutstring += attrmap[\"user.forced.blocksize\"].c_str();\n        XrdOucEnv layoutenv(layoutstring.c_str());\n        // we force to use a specified stripe width in this directory even if\n        // the user wants something else\n        blocksize = eos::common::LayoutId::GetBlocksizeFromEnv(layoutenv);\n        eos_static_debug(\"user.forced.blocksize in %s\", path);\n      }\n    }\n\n    if ((attrmap.count(\"sys.forced.nofsselection\") &&\n         (attrmap[\"sys.forced.nofsselection\"] == \"1\")) ||\n        (attrmap.count(\"user.forced.nofsselection\") &&\n         (attrmap[\"user.forced.nofsselection\"] == \"1\"))) {\n      eos_static_debug(\"<sys|user>.forced.nofsselection in %s\", path);\n      forcedfsid = 0;\n    } else {\n      common::XrdUtils::GetEnv(env, \"eos.force.fsid\", forcedfsid, 0ul);\n    }\n  }\n\n  // populate list of alternative checksums\n  if (altChecksums && attrmap.count(SYS_ALTCHECKSUMS)) {\n    altChecksums->clear();\n    std::vector<std::string> xs;\n    eos::common::StringConversion::Tokenize(attrmap[SYS_ALTCHECKSUMS], xs, \",\");\n    *altChecksums = std::move(xs);\n\n    if (computeAltXs && altxsopt == \"on\") {\n      *computeAltXs = true;\n    }\n  }\n\n  if (satime.length() && atimeage) {\n    *atimeage = std::stoull(satime.c_str(), 0, 10);\n  }\n\n  layoutId =\n    eos::common::LayoutId::GetId(layout, xsum, stripes, blocksize, bxsum);\n  eos_static_info(\"layoutId=%lxl layout=%ld xsum=%ld stripes=%ld blocksize=%ld\",\n                  layout, layoutId, xsum, stripes, blocksize);\n  return;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nPolicy::GetPlctPolicy(const char* path, eos::IContainerMD::XAttrMap& attrmap,\n                      const eos::common::VirtualIdentity& vid, XrdOucEnv& env,\n                      eos::mgm::Scheduler::tPlctPolicy& plctpol,\n                      std::string& targetgeotag)\n{\n  // default to save\n  plctpol = eos::mgm::Scheduler::kScattered;\n  std::string policyString;\n  const char* val = 0;\n\n  if ((val = env.Get(\"eos.placementpolicy\"))) {\n    // we force an explicit placement policy\n    policyString = val;\n  }\n\n  if ((vid.uid == 0) && (val = env.Get(\"eos.placementpolicy.noforce\"))) {\n    // root can request not to apply any forced settings\n  } else if (attrmap.count(\"sys.forced.placementpolicy\")) {\n    // we force to use a certain placement policy even if the user wants\n    // something else\n    policyString = attrmap[\"sys.forced.placementpolicy\"].c_str();\n    eos_static_debug(\"sys.forced.placementpolicy in %s\", path);\n  } else {\n    // check there are no user placement restrictions\n    if (((!attrmap.count(\"sys.forced.nouserplacementpolicy\")) ||\n         (attrmap[\"sys.forced.nouserplacementpolicy\"] != \"1\")) &&\n        ((!attrmap.count(\"user.forced.nouserplacementpolicy\")) ||\n         (attrmap[\"user.forced.nouserplacementpolicy\"] != \"1\"))) {\n      if (attrmap.count(\"user.forced.placementpolicy\")) {\n        // we use the user defined placement policy\n        policyString = attrmap[\"user.forced.placementpolicy\"].c_str();\n        eos_static_debug(\"user.forced.placementpolicy in %s\", path);\n      }\n    }\n  }\n\n  if (policyString.empty() || policyString == \"scattered\") {\n    plctpol = eos::mgm::Scheduler::kScattered;\n    return;\n  }\n\n  std::string::size_type seppos = policyString.find(':');\n\n  // if no target geotag is provided, it's not a valid placement policy\n  if (seppos == std::string::npos || seppos == policyString.length() - 1) {\n    eos_static_warning(\n      \"no geotag given in placement policy for path %s : \\\"%s\\\"\", path,\n      policyString.c_str());\n    return;\n  }\n\n  targetgeotag = policyString.substr(seppos + 1);\n  // Check if geotag is valid\n  std::string tmp_geotag = eos::common::SanitizeGeoTag(targetgeotag);\n\n  if (tmp_geotag != targetgeotag) {\n    eos_static_warning(\"%s\", tmp_geotag.c_str());\n    return;\n  }\n\n  if (!policyString.compare(0, seppos, \"hybrid\")) {\n    plctpol = eos::mgm::Scheduler::kHybrid;\n  } else if (!policyString.compare(0, seppos, \"gathered\")) {\n    plctpol = eos::mgm::Scheduler::kGathered;\n  } else {\n    eos_static_warning(\"unknown placement policy for path %s : \\\"%s\\\"\", path,\n                       policyString.c_str());\n  }\n\n  return;\n}\n\n/*----------------------------------------------------------------------------*/\nPolicy::RedirectStatus\nPolicy::RedirectLocal(const char* path, eos::IContainerMD::XAttrMap& map,\n                      const eos::common::VirtualIdentity& vid,\n                      unsigned long& layoutId, const std::string& space,\n                      XrdOucEnv& env)\n{\n  std::string rkey = \"sys.forced.localredirect\";\n\n  if (map.count(rkey) &&\n      ((map[rkey] == \"true\") || (map[rkey] == \"1\") ||\n       (map[rkey] == \"always\")) &&\n      ((eos::common::LayoutId::GetLayoutType(layoutId) ==\n        eos::common::LayoutId::kReplica) ||\n       (eos::common::LayoutId::GetLayoutType(layoutId) ==\n        eos::common::LayoutId::kPlain))) {\n    if (env.Get(\"eos.localredirect\") &&\n        (std::string(env.Get(\"eos.localredirect\")) == \"0\")) {\n      return eNever;\n    } else {\n      return eAlways;\n    }\n  }\n\n  if (map.count(rkey) && ((map[rkey] == \"optional\") || (map[rkey] == \"2\")) &&\n      ((eos::common::LayoutId::GetLayoutType(layoutId) ==\n        eos::common::LayoutId::kReplica) ||\n       (eos::common::LayoutId::GetLayoutType(layoutId) ==\n        eos::common::LayoutId::kPlain))) {\n    if (env.Get(\"eos.localredirect\") &&\n        (std::string(env.Get(\"eos.localredirect\")) == \"0\")) {\n      return eNever;\n    } else {\n      return eOptional;\n    }\n  }\n\n  if (env.Get(\"eos.localredirect\") &&\n      (std::string(env.Get(\"eos.localredirect\")) == \"1\")) {\n    return eAlways;\n  } else {\n    return eNever;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check if update conversion is configured for the current entry\n//------------------------------------------------------------------------------\nbool\nPolicy::HasUpdConversion(eos::IContainerMD::XAttrMap& map)\n{\n  static const std::string key_upd_conv = \"sys.forced.updateconversion\";\n  return (map.find(key_upd_conv) != map.end());\n}\n\n//------------------------------------------------------------------------------\n// Check if read conversion is configured for the current entry\n//------------------------------------------------------------------------------\nbool\nPolicy::HasReadConversion(eos::IContainerMD::XAttrMap& map)\n{\n  static const std::string key_rd_conv = \"sys.forced.readconversion\";\n  return (map.find(key_rd_conv) != map.end());\n}\n\n/*----------------------------------------------------------------------------*/\nPolicy::ConversionPolicy\nPolicy::UpdateConversion(const char* path, eos::IContainerMD::XAttrMap& map,\n                         const eos::common::VirtualIdentity& vid,\n                         unsigned long& layoutId, const std::string& space,\n                         XrdOucEnv& env, unsigned long& targetLayoutId,\n                         std::string& target_space)\n{\n  static const std::string rkey = \"sys.forced.updateconversion\";\n\n  if (map.count(rkey)) {\n    std::string tspace;\n    std::string tlayout;\n    bool split = eos::common::StringConversion::SplitKeyValue(map[rkey], tspace,\n                 tlayout);\n\n    if (!split) {\n      return Policy::eFail;\n    }\n\n    // return space and layout id\n    target_space = tspace;\n    targetLayoutId = std::stoi(tlayout, nullptr, 16);\n\n    if ((space == target_space) && (layoutId == targetLayoutId)) {\n      // this is already with the desired layout and space\n      return eNone;\n    }\n\n    return Policy::eAsync; // for the moment we don't want anything synchronous\n    // happening in the MGM\n  } else {\n    // nothing to convert\n    return Policy::eNone;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nPolicy::ConversionPolicy\nPolicy::ReadConversion(const char* path, eos::IContainerMD::XAttrMap& map,\n                       const eos::common::VirtualIdentity& vid,\n                       unsigned long& layoutId, const std::string& space,\n                       XrdOucEnv& env, unsigned long& targetLayoutId,\n                       std::string& target_space)\n{\n  static const std::string rkey = \"sys.forced.readconversion\";\n\n  if (map.count(rkey)) {\n    std::string tspace;\n    std::string tlayout;\n    bool split = eos::common::StringConversion::SplitKeyValue(map[rkey], tspace,\n                 tlayout);\n\n    if (!split) {\n      return Policy::eFail;\n    }\n\n    // return space and layout id\n    target_space = tspace;\n    targetLayoutId = std::stoi(tlayout, nullptr, 16);\n\n    if ((space == target_space) && (layoutId == targetLayoutId)) {\n      // this is already with the desired layout and space\n      return eNone;\n    }\n\n    if (!FsView::gFsView.UnderNominalQuota(target_space, (vid.uid == 0))) {\n      // there is no space in the target space left, just don't convert it\n      eos_static_info(\"msg=\\\"target space '%s' over nominal size - suppresing \"\n                      \"read conversion policy\\\"\", target_space.c_str());\n      return Policy::eNone;\n    }\n\n    return Policy::eAsync; // for the moment we don't want anything synchronous\n    // happening in the MGM\n  } else {\n    // nothing to convert\n    return Policy::eNone;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nPolicy::Set(const char* value)\n{\n  XrdOucEnv env(value);\n  XrdOucString policy = env.Get(\"mgm.policy\");\n  XrdOucString skey = env.Get(\"mgm.policy.key\");\n  XrdOucString policycmd = env.Get(\"mgm.policy.cmd\");\n\n  if (!skey.length()) {\n    return false;\n  }\n\n  bool set = false;\n\n  if (!value) {\n    return false;\n  }\n\n  //  gOFS->ConfigEngine->SetConfigValue(\"policy\",skey.c_str(), svalue.c_str());\n  return set;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nPolicy::Set(XrdOucEnv& env, int& retc, XrdOucString& stdOut,\n            XrdOucString& stdErr)\n{\n  int envlen;\n  // no '&' are allowed into stdOut !\n  XrdOucString inenv = env.Env(envlen);\n\n  while (inenv.replace(\"&\", \" \")) {\n  };\n\n  bool rc = Set(env.Env(envlen));\n\n  if (rc == true) {\n    stdOut += \"success: set policy [ \";\n    stdOut += inenv;\n    stdOut += \"]\\n\";\n    errno = 0;\n    retc = 0;\n    return true;\n  } else {\n    stdErr += \"error: failed to set policy [ \";\n    stdErr += inenv;\n    stdErr += \"]\\n\";\n    errno = EINVAL;\n    retc = EINVAL;\n    return false;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nPolicy::Ls(XrdOucEnv& env, int& retc, XrdOucString& stdOut,\n           XrdOucString& stdErr)\n{\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nPolicy::Rm(XrdOucEnv& env, int& retc, XrdOucString& stdOut,\n           XrdOucString& stdErr)\n{\n  return true;\n}\n\n/*----------------------------------------------------------------------------*/\nconst char*\nPolicy::Get(const char* key)\n{\n  return 0;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nPolicy::IsProcConversion(const char* path)\n{\n  XrdOucString spath = path;\n\n  if (spath.beginswith(gOFS->MgmProcConversionPath.c_str())) {\n    return true;\n  } else {\n    return false;\n  }\n}\n\nvoid\nPolicy::GetRWValue(const std::map<std::string, std::string>& conf_map,\n                   const std::string& key_name, const RWParams& params,\n                   std::string& value)\n{\n  for (auto&& k : params.getKeys(key_name)) {\n    if (const auto& kv = conf_map.find(k);\n        kv != conf_map.end() && !kv->second.empty()) {\n      value = kv->second;\n    }\n  }\n}\n\nstd::vector<std::string>\nPolicy::GetRWConfigKeys(const RWParams& params)\n{\n  std::vector<std::string> config_keys;\n  config_keys.reserve(16);\n\n  for (const auto& _key : gBasePolicyRWKeys) {\n    eos::common::splice(config_keys, params.getKeys(_key));\n  }\n\n  return config_keys;\n}\n\nstd::vector<std::string>\nPolicy::RWParams::getKeys(const std::string& key) const\n{\n  auto key_name = getKey(key);\n  return {key_name + app_key, key_name + user_key, key_name + group_key,\n          key_name};\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/policy/Policy.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Policy.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_POLICY__HH__\n#define __EOSMGM_POLICY__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/Mapping.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"mgm/scheduler/Scheduler.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucEnv.hh>\n#include <XrdOuc/XrdOucString.hh>\n/*----------------------------------------------------------------------------*/\n#include <sys/types.h>\n\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\nclass Policy\n{\npublic:\n  Policy() {};\n\n  ~Policy() {};\n\n  static void GetLayoutAndSpace(const char* path,\n                                eos::IContainerMD::XAttrMap& map,\n                                const eos::common::VirtualIdentity& vid,\n                                unsigned long& layoutId, std::string& space,\n                                XrdOucEnv& env, unsigned long& forcedfsid,\n                                long& forcedgroup, std::string& bandwidth,\n                                bool& schedul, std::string& iopriority,\n                                std::string& ioptype, bool isrw,\n                                bool lock_view = false, uint64_t* atimeage = 0,\n                                std::vector<std::string>* altChecksums = nullptr,\n                                bool* computeAltXs = nullptr);\n\n  static void GetPlctPolicy(const char* path, eos::IContainerMD::XAttrMap& map,\n                            const eos::common::VirtualIdentity& vid,\n                            XrdOucEnv& env,\n                            eos::mgm::Scheduler::tPlctPolicy& plctpo,\n                            std::string& targetgeotag);\n  enum RedirectStatus { eNever, eAlways, eOptional };\n\n  static RedirectStatus RedirectLocal(const char* path,\n                                      eos::IContainerMD::XAttrMap& map,\n                                      const eos::common::VirtualIdentity& vid,\n                                      unsigned long& layoutId,\n                                      const std::string& space, XrdOucEnv& env);\n\n  enum ConversionPolicy { eSync, eAsync, eNone, eFail };\n\n  //----------------------------------------------------------------------------\n  //! Check if update conversion is configured for the current entry\n  //!\n  //! @return true if configured, otherwise false\n  //----------------------------------------------------------------------------\n  static bool HasUpdConversion(eos::IContainerMD::XAttrMap& map);\n\n  //----------------------------------------------------------------------------\n  //! Check if read conversion is configured for the current entry\n  //!\n  //! @return true if configured, otherwise false\n  //----------------------------------------------------------------------------\n  static bool HasReadConversion(eos::IContainerMD::XAttrMap& map);\n\n  static ConversionPolicy\n  UpdateConversion(const char* path, eos::IContainerMD::XAttrMap& map,\n                   const eos::common::VirtualIdentity& vid,\n                   unsigned long& layoutId, const std::string& space,\n                   XrdOucEnv& env, unsigned long& targetLayoutId,\n                   std::string& target_space);\n\n  static ConversionPolicy\n  ReadConversion(const char* path, eos::IContainerMD::XAttrMap& map,\n                 const eos::common::VirtualIdentity& vid,\n                 unsigned long& layoutId, const std::string& space,\n                 XrdOucEnv& env, unsigned long& targetLayoutId,\n                 std::string& target_space);\n\n  static unsigned long GetSpacePolicyLayout(const char* space);\n\n  static bool Set(const char* value);\n  static bool Set(XrdOucEnv& env, int& retc, XrdOucString& stdOut,\n                  XrdOucString& stdErr);\n  static void Ls(XrdOucEnv& env, int& retc, XrdOucString& stdOut,\n                 XrdOucString& stdErr);\n  static bool Rm(XrdOucEnv& env, int& retc, XrdOucString& stdOut,\n                 XrdOucString& stdErr);\n\n  static bool IsProcConversion(const char* path);\n\n  static const char* Get(const char* key);\n\n  struct RWParams;\n\n  static inline std::vector<std::string>\n  GetConfigKeys()\n  {\n    return gBasePolicyKeys;\n  }\n\n  static std::vector<std::string> GetRWConfigKeys(const RWParams& params);\n\n  static void GetRWValue(const std::map<std::string, std::string>& conf_map,\n                         const std::string& key_name, const RWParams& params,\n                         std::string& value);\n\n  static const std::vector<std::string> gBasePolicyKeys;\n  static const std::vector<std::string> gBaseLocalPolicyKeys;\n  static const std::vector<std::string> gBasePolicyRWKeys;\n\n  static double GetDefaultSizeFactor(std::shared_ptr<eos::IContainerMD> cmd);\n\n  struct RWParams {\n    std::string user_key;\n    std::string group_key;\n    std::string app_key;\n    std::string rw_marker;\n\n    RWParams(const std::string& user_str, const std::string& group_str,\n             const std::string& app_str, bool is_rw)\n      : user_key(\".user:\" + user_str), group_key(\".group:\" + group_str),\n        app_key(\".app:\" + app_str), rw_marker(is_rw ? \":w\" : \":r\")\n    {\n    }\n\n    std::string\n    getKey(const std::string& key) const\n    {\n      return key + rw_marker;\n    }\n\n    std::vector<std::string> getKeys(const std::string& key) const;\n  };\n};\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/proc/IProcCommand.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file IProcCommand.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Path.hh\"\n#include \"common/CommentLog.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/proc/IProcCommand.hh\"\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"json/json.h\"\n#include <google/protobuf/util/json_util.h>\n\nEOSMGMNAMESPACE_BEGIN\n\nstd::atomic_uint_least64_t IProcCommand::uuid{0};\nstd::mutex IProcCommand::mMapCmdsMutex;\nstd::map<eos::console::RequestProto::CommandCase, uint64_t>\nIProcCommand::mCmdsExecuting;\n\n//------------------------------------------------------------------------------\n// Open a proc command e.g. call the appropriate user or admin command and\n// store the output in a resultstream or in case of find in a temporary output\n// file.\n//------------------------------------------------------------------------------\nint\nIProcCommand::open(const char* path, const char* info,\n                   eos::common::VirtualIdentity& vid,\n                   XrdOucErrInfo* error)\n{\n  // @todo (esindril): configure delay based on the type of command\n  int delay = 5;\n\n  if (!mExecRequest) {\n    if (HasSlot()) {\n      LaunchJob();\n      mExecRequest = true;\n    } else {\n      eos_notice(\"%s\", SSTR(\"cmd_type=\" << mReqProto.command_case() <<\n                            \" no more slots, stall client 3 seconds\").c_str());\n      return delay - 2;\n    }\n  }\n\n  if (mFuture.wait_for(std::chrono::seconds(delay)) !=\n      std::future_status::ready) {\n    // Stall the client\n    std::string msg = \"command not ready, stall the client 5 seconds\";\n    eos_notice(\"%s\", msg.c_str());\n    error->setErrInfo(0, msg.c_str());\n    return delay;\n  } else {\n    eos::console::ReplyProto reply = mFuture.get();\n\n    // Routing redirect encountered\n    if (reply.retc() == SFS_REDIRECT) {\n      eos_notice(\"msg=\\\"routing redirect\\\" path=%s hostport=%s:%d \"\n                 \"stall_timeout=%d\", mRoutingInfo.path.c_str(),\n                 mRoutingInfo.host.c_str(), mRoutingInfo.port,\n                 mRoutingInfo.stall_timeout);\n\n      if (mRoutingInfo.stall_timeout) {\n        // Force re-execution of the command upon return from stall\n        mExecRequest = false;\n        std::string stall_msg = \"No master MGM available\";\n        return gOFS->Stall(*error, vid, mRoutingInfo.stall_timeout,\n                           stall_msg.c_str());\n      }\n\n      return gOFS->Redirect(*error, mRoutingInfo.host.c_str(),\n                            mRoutingInfo.port);\n    }\n\n    // Output is written in file\n    if (!mOfsOutStreamFilename.empty() && !mOfsErrStreamFilename.empty()) {\n      ifstdoutStream.open(mOfsOutStreamFilename, std::ifstream::in);\n      ifstderrStream.open(mOfsErrStreamFilename, std::ifstream::in);\n      iretcStream.str(std::string(\"&mgm.proc.retc=\") + std::to_string(reply.retc()));\n      readStdOutStream = true;\n    } else {\n      std::ostringstream oss;\n\n      if (mReqProto.format() == eos::console::RequestProto::FUSE) {\n        // The proto dumpmd issued by the FST uses the FUSE format\n        // (resync metadata, background Fsck and standalone Fsck)\n        // @todo This format should be dropped once Quarkdb migration is complete\n        //       and the NS will be queried directly\n        oss << reply.std_out();\n      } else {\n        oss << \"mgm.proc.stdout=\" << reply.std_out().c_str()\n            << \"&mgm.proc.stderr=\" << reply.std_err().c_str()\n            << \"&mgm.proc.retc=\" << reply.retc();\n      }\n\n      mTmpResp = oss.str();\n    }\n\n    // Store the client's command comment in the comments logbook\n    if ((vid.uid <= 2) || (vid.sudoer)) {\n      // Only instance users or sudoers can add to the logbook\n      if (mComment.length() && gOFS->mCommentLog) {\n        std::string argsJson;\n        (void) google::protobuf::util::MessageToJsonString(mReqProto, &argsJson);\n\n        if (!gOFS->mCommentLog->Add(mTimestamp, \"\", \"\", argsJson.c_str(),\n                                    mComment.c_str(), reply.std_err().c_str(),\n                                    reply.retc())) {\n          eos_err(\"failed to log to comments logbook\");\n        }\n      }\n    }\n  }\n\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Read a part of the result stream created during open\n//------------------------------------------------------------------------------\nsize_t\nIProcCommand::read(XrdSfsFileOffset offset, char* buff, XrdSfsXferSize blen)\n{\n  size_t cpy_len = 0;\n\n  if (readStdOutStream && ifstdoutStream.is_open() && ifstderrStream.is_open()) {\n    ifstdoutStream.read(buff, blen);\n    cpy_len = (size_t)ifstdoutStream.gcount();\n\n    if (cpy_len < (size_t)blen) {\n      readStdOutStream = false;\n      readStdErrStream = true;\n      ifstderrStream.read(buff + cpy_len, blen - cpy_len);\n      cpy_len += (size_t)ifstderrStream.gcount();\n    }\n  } else if (readStdErrStream && ifstderrStream.is_open()) {\n    ifstderrStream.read(buff, blen);\n    cpy_len = (size_t)ifstderrStream.gcount();\n\n    if (cpy_len < (size_t)blen) {\n      readStdErrStream = false;\n      readRetcStream = true;\n      iretcStream.read(buff + cpy_len, blen - cpy_len);\n      cpy_len += (size_t)iretcStream.gcount();\n    }\n  } else if (readRetcStream) {\n    iretcStream.read(buff, blen);\n    cpy_len = (size_t)iretcStream.gcount();\n\n    if (cpy_len < (size_t)blen) {\n      readRetcStream = false;\n    }\n  } else if ((size_t)offset < mTmpResp.length()) {\n    cpy_len = std::min((size_t)(mTmpResp.size() - offset), (size_t)blen);\n    memcpy(buff, mTmpResp.data() + offset, cpy_len);\n  }\n\n  return cpy_len;\n}\n\n//------------------------------------------------------------------------------\n// Launch command asynchronously, creating the corresponding promise and future\n//------------------------------------------------------------------------------\nvoid\nIProcCommand::LaunchJob()\n{\n  if (mDoAsync) {\n    mFuture = ProcInterface::sProcThreads.PushTask<eos::console::ReplyProto>\n    ([this]() -> eos::console::ReplyProto {\n      return ProcessRequest();\n    });\n\n    if (EOS_LOGS_DEBUG) {\n      eos_debug(\"%s\", ProcInterface::sProcThreads.GetInfo().c_str());\n    }\n  } else {\n    std::promise<eos::console::ReplyProto> promise;\n    mFuture = promise.get_future();\n    promise.set_value(ProcessRequest());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check if we can safely delete the current object as there is no async\n// thread executing the ProcessResponse method\n//------------------------------------------------------------------------------\nbool\nIProcCommand::KillJob()\n{\n  bool is_killed = true;\n\n  if (mDoAsync) {\n    mForceKill.store(true);\n\n    if (mFuture.valid()) {\n      is_killed = (mFuture.wait_for(std::chrono::seconds(0)) ==\n                   std::future_status::ready);\n    }\n  }\n\n  return is_killed;\n}\n\n//------------------------------------------------------------------------------\n// Open temporary output files for file based results\n//------------------------------------------------------------------------------\nbool\nIProcCommand::OpenTemporaryOutputFiles()\n{\n  std::ostringstream tmpdir;\n  tmpdir << \"/var/tmp/eos/mgm/\";\n  tmpdir << uuid++;\n  mOfsOutStreamFilename = tmpdir.str();\n  mOfsOutStreamFilename += \".stdout\";\n  mOfsErrStreamFilename = tmpdir.str();\n  mOfsErrStreamFilename += \".stderr\";\n  eos::common::Path cPath(mOfsOutStreamFilename.c_str());\n\n  if (!cPath.MakeParentPath(S_IRWXU)) {\n    eos_err(\"Unable to create temporary outputfile directory %s\",\n            tmpdir.str().c_str());\n    return false;\n  }\n\n  // own the directory by daemon\n  if (::chown(cPath.GetParentPath(), 2, 2)) {\n    eos_err(\"Unable to own temporary outputfile directory %s\",\n            cPath.GetParentPath());\n  }\n\n  mOfsOutStream.open(mOfsOutStreamFilename, std::ofstream::out);\n  mOfsErrStream.open(mOfsErrStreamFilename, std::ofstream::out);\n\n  if ((!mOfsOutStream) || (!mOfsErrStream)) {\n    if (mOfsOutStream.is_open()) {\n      mOfsOutStream.close();\n    }\n\n    if (mOfsErrStream.is_open()) {\n      mOfsErrStream.close();\n    }\n\n    return false;\n  }\n\n  mOfsOutStream << \"mgm.proc.stdout=\";\n  mOfsErrStream << \"&mgm.proc.stderr=\";\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Open temporary output files for file based results\n//------------------------------------------------------------------------------\nbool\nIProcCommand::CloseTemporaryOutputFiles()\n{\n  mOfsOutStream.close();\n  mOfsErrStream.close();\n  return !(mOfsOutStream.is_open() || mOfsErrStream.is_open());\n}\n\n//------------------------------------------------------------------------------\n// Format console output string as json\n//------------------------------------------------------------------------------\nJson::Value\nIProcCommand::ConvertOutputToJsonFormat(const std::string& stdOut)\n{\n  using eos::common::StringConversion;\n  std::stringstream ss(stdOut);\n  Json::Value jsonOut;\n  std::string line;\n\n  do {\n    Json::Value jsonEntry;\n    line.clear();\n\n    if (!std::getline(ss, line)) {\n      break;\n    }\n\n    if (line.empty()) {\n      continue;\n    }\n\n    XrdOucString sline = line.c_str();\n\n    while (sline.replace(\"<n>\", \"n\")) {}\n\n    while (sline.replace(\"?configstatus@rw\", \"_rw\")) {}\n\n    line = sline.c_str();\n    std::map <std::string, std::string> map;\n    StringConversion::GetKeyValueMap(line.c_str(), map, \"=\", \" \");\n    // These values violate the JSON hierarchy and have to be rewritten\n    StringConversion::ReplaceMapKey(map, \"cfg.balancer\",\n                                    \"cfg.balancer.status\");\n    StringConversion::ReplaceMapKey(map, \"cfg.geotagbalancer\",\n                                    \"cfg.geotagbalancer.status\");\n    StringConversion::ReplaceMapKey(map, \"cfg.geobalancer\",\n                                    \"cfg.geobalancer.status\");\n    StringConversion::ReplaceMapKey(map, \"cfg.groupbalancer\",\n                                    \"cfg.groupbalancer.status\");\n    StringConversion::ReplaceMapKey(map, \"geotagbalancer\",\n                                    \"geotagbalancer.status\");\n    StringConversion::ReplaceMapKey(map, \"geobalancer\",\n                                    \"geobalancer.status\");\n    StringConversion::ReplaceMapKey(map, \"groupbalancer\",\n                                    \"groupbalancer.status\");\n    StringConversion::ReplaceMapKey(map, \"cfg.wfe\", \"cfg.wfe.status\");\n    StringConversion::ReplaceMapKey(map, \"cfg.lru\", \"cfg.lru.status\");\n    StringConversion::ReplaceMapKey(map, \"local.drain\", \"local.drain.status\");\n    StringConversion::ReplaceMapKey(map, \"stat.health\", \"stat.health.status\");\n    StringConversion::ReplaceMapKey(map, \"wfe\", \"wfe.status\");\n    StringConversion::ReplaceMapKey(map, \"lru\", \"lru.status\");\n    StringConversion::ReplaceMapKey(map, \"balancer\", \"balancer.status\");\n    StringConversion::ReplaceMapKey(map, \"converter\", \"converter.status\");\n    StringConversion::ReplaceMapKey(map, \"inspector\", \"inspector.status\");\n    StringConversion::ReplaceMapKey(map, \"groupdrainer\", \"groupdrainer.status\");\n\n    for (const auto& it : map) {\n      std::vector<std::string> token;\n      char* conv;\n      errno = 0;\n      StringConversion::Tokenize(it.first, token, \".\");\n      double val = strtod(it.second.c_str(), &conv);\n      std::string value;\n\n      if (token.empty()) {\n        continue;\n      }\n\n      if (!it.second.empty()) {\n        value = it.second;\n      } else {\n        value = \"NULL\";\n      }\n\n      auto* jep = &(jsonEntry[token[0]]);\n\n      for (int i = 1; i < (int)token.size(); i++) {\n        jep = &((*jep)[token[i]]);\n      }\n      // Seal value\n      XrdOucString svalue = value.c_str();\n      eos::common::StringConversion::Seal(svalue);\n      value = svalue.c_str();\n\n      if (errno || (!val && (conv  == it.second.c_str())) ||\n          ((conv - it.second.c_str()) != (long long)it.second.length())) {\n        // non numeric\n        (*jep) = value;\n      } else {\n        // Check if this is an integer value otherwise use double\n        if (it.second.find('.') == std::string::npos) {\n          try {\n            std::uint64_t int_val = std::stoull(it.second);\n            (*jep) = static_cast<Json::Value::UInt64>(int_val);\n          } catch (...) {\n            eos_static_err(\"msg=\\\"failed to convert to integer use double\\\" \"\n                           \"data=\\\"%s\\\"\", it.second.c_str());\n            (*jep) = val;\n          }\n        } else { // It's double\n          (*jep) = val;\n        }\n      }\n    }\n\n    jsonOut.append(jsonEntry);\n  } while (true);\n\n  return jsonOut;\n}\n\n//------------------------------------------------------------------------------\n// Create a JSON string from the command output, error and return code\n//------------------------------------------------------------------------------\nstd::string\nIProcCommand::ResponseToJsonString(const std::string& out,\n                                   const std::string& err, int rc)\n{\n  Json::Value json;\n\n  try {\n    json[\"result\"] = ConvertOutputToJsonFormat(out);\n    json[\"errormsg\"] = err;\n    json[\"retc\"] = std::to_string(rc);\n  } catch (Json::Exception& e) {\n    eos_err(\"Json conversion exception cmd_type=%s emsg=\\\"%s\\\"\",\n            SSTR(mReqProto.command_case()).c_str(), e.what());\n    json[\"errormsg\"] = \"illegal string in json conversion\";\n    json[\"retc\"] = std::to_string(EFAULT);\n  }\n\n  return SSTR(json);\n}\n\n//------------------------------------------------------------------------------\n// Retrieve the file's full path given its numeric id\n//------------------------------------------------------------------------------\nint\nIProcCommand::GetPathFromFid(std::string& path, unsigned long long fid,\n                             std::string& err_msg)\n{\n  if (path.empty()) {\n    if (fid == 0ULL) {\n      err_msg += \"error: fid is 0\";\n      return EINVAL;\n    }\n\n    try {\n      eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n      std::string temp = gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(\n                           fid).get());\n      path = temp;\n      return 0;\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      eos_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                e.getMessage().str().c_str());\n      err_msg = \"error: \" + e.getMessage().str() + '\\n';\n      return errno;\n    }\n  }\n\n  return EINVAL;\n}\n\n//------------------------------------------------------------------------------\n// Retrieve the container's full path given its numeric id\n//------------------------------------------------------------------------------\nint\nIProcCommand::GetPathFromCid(std::string& path, unsigned long long cid,\n                             std::string& err_msg)\n{\n  if (path.empty()) {\n    if (cid == 0ULL) {\n      err_msg += \"error: cid is 0\";\n      return EINVAL;\n    }\n\n    try {\n      eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n      std::string temp = gOFS->eosView->getUri\n                         (gOFS->eosDirectoryService->getContainerMD(cid).get());\n      path = temp;\n      return 0;\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      eos_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                e.getMessage().str().c_str());\n      err_msg = \"error: \" + e.getMessage().str() + '\\n';\n      return errno;\n    }\n  }\n\n  return EINVAL;\n}\n\n//------------------------------------------------------------------------------\n// Check if operation forbidden\n//------------------------------------------------------------------------------\nbool\nIProcCommand::IsOperationForbidden(const std::string& path,\n                                   const eos::common::VirtualIdentity& vid,\n                                   std::string& err_check, int& errno_check) const\n{\n  if (eos::mgm::ProcBounceIllegalNames(path, err_check, errno_check) ||\n      eos::mgm::ProcBounceNotAllowed(path, mVid, err_check, errno_check)) {\n    return true;\n  }\n\n  return false;\n}\n\n//----------------------------------------------------------------------------\n// Fill routing information if a routing redirect should happen\n//----------------------------------------------------------------------------\nbool\nIProcCommand::ShouldRoute(const std::string& path,\n                          eos::console::ReplyProto& reply)\n{\n  eos_debug(\"msg=\\\"applying routing\\\" path=%s is_redirect=%d\",\n            path.c_str(), gOFS->IsRedirect);\n\n  if (gOFS->IsRedirect) {\n    if (gOFS->ShouldRoute(__FUNCTION__, 0, mVid, path.c_str(), 0,\n                          mRoutingInfo.host, mRoutingInfo.port,\n                          mRoutingInfo.stall_timeout)) {\n      mRoutingInfo.path = path;\n      reply.set_retc(SFS_REDIRECT);\n      return true;\n    }\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Check if there is still an available slot for the current type of command\n//------------------------------------------------------------------------------\nbool\nIProcCommand::HasSlot()\n{\n  static const uint64_t slot_limit {50};\n  std::unique_lock<std::mutex> lock(mMapCmdsMutex);\n  auto it = mCmdsExecuting.find(mReqProto.command_case());\n\n  if (it == mCmdsExecuting.end()) {\n    mCmdsExecuting[mReqProto.command_case()] = 1;\n    mHasSlot = true;\n  } else {\n    if (it->second >= slot_limit) {\n      return false;\n    } else {\n      ++it->second;\n      mHasSlot = true;\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Resolve an identifier (fid/fxid/cid/cxid) to its corresponding EOS path.\n//------------------------------------------------------------------------------\nbool\nIProcCommand::ResolveIdentifierToPath(XrdOucString& identifier,\n                                      XrdOucString& resolvedPath, XrdOucString& errorMsg,\n                                      int& retc)\n{\n  auto fail = [&](const char* msg, int err = EINVAL) {\n    errorMsg = msg;\n    errorMsg += \" '\";\n    errorMsg += identifier.c_str();\n    errorMsg += \"'\";\n    retc = err;\n    return false;\n  };\n\n  const char* str = identifier.c_str();\n\n  bool is_container = false;\n  int base = 10;\n  const char* num = nullptr;\n\n  if (!strncmp(str, \"fid:\", 4)) {\n    num = str + 4;\n  } else if (!strncmp(str, \"cid:\", 4)) {\n    is_container = true;\n    num = str + 4;\n  } else if (!strncmp(str, \"fxid:\", 5)) {\n    base = 16;\n    num = str + 5;\n  } else if (!strncmp(str, \"cxid:\", 5)) {\n    is_container = true;\n    base = 16;\n    num = str + 5;\n  } else {\n    resolvedPath = identifier;\n    return true;\n  }\n\n  if (*num == '\\0') {\n    return fail(\"error: empty identifier\");\n  }\n\n  char* endptr = nullptr;\n  errno = 0;\n  unsigned long long id = strtoull(num, &endptr, base);\n\n  if (errno != 0 || endptr == num || *endptr != '\\0') {\n    return fail(base == 10 ? \"error: invalid decimal identifier\"\n                           : \"error: invalid hex identifier\");\n  }\n\n  if (id == 0) {\n    return fail(\"error: identifier cannot be zero\");\n  }\n\n  try {\n    if (is_container) {\n      auto dmd = gOFS->eosDirectoryService->getContainerMD(id);\n      resolvedPath = gOFS->eosView->getUri(dmd.get()).c_str();\n      eos_info(\"msg=\\\"resolved container id to path\\\" cid=%llu cxid=%08llx path=%s\", id,\n               id, resolvedPath.c_str());\n    } else {\n      auto fmd = gOFS->eosFileService->getFileMD(id);\n      resolvedPath = gOFS->eosView->getUri(fmd.get()).c_str();\n      eos_info(\"msg=\\\"resolved file id to path\\\" fid=%llu fxid=%08llx path=%s\", id, id,\n               resolvedPath.c_str());\n    }\n\n    return true;\n  } catch (eos::MDException& e) {\n    errorMsg = \"error: cannot resolve \";\n    errorMsg += (is_container ? \"container\" : \"file\");\n    errorMsg += \" identifier - \";\n    errorMsg += e.getMessage().str().c_str();\n    retc = e.getErrno();\n    return false;\n  } catch (std::exception& e) {\n    errorMsg = \"error: exception resolving identifier: \";\n    errorMsg += e.what();\n    retc = EIO;\n    return false;\n  } catch (...) {\n    errorMsg = \"error: unknown exception resolving identifier\";\n    retc = EIO;\n    return false;\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/IProcCommand.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file IProcCommand.hh\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/Mapping.hh\"\n#include \"common/Logging.hh\"\n#include \"proto/ConsoleReply.pb.h\"\n#include \"proto/ConsoleRequest.pb.h\"\n#include <XrdSfs/XrdSfsInterface.hh>\n#include <future>\n#include <sstream>\n\n//! Forward declarations\nclass XrdOucErrInfo;\n\nnamespace Json\n{\nclass Value;\n}\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class IProcCommand - interface that needs to be implemented by all types\n//! of commands executed by the MGM.\n//------------------------------------------------------------------------------\nclass IProcCommand: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  IProcCommand():\n    mHasSlot(false), mExecRequest(false), mReqProto(), mDoAsync(false),\n    mForceKill(false), mVid(), mComment(), mRoutingInfo(), mTmpResp()\n  {\n    mTimestamp = time(NULL);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client protobuf request\n  //! @param vid client virtual identity\n  //! @param async if true then use thread pool to execute the command\n  //----------------------------------------------------------------------------\n  IProcCommand(eos::console::RequestProto&& req,\n               eos::common::VirtualIdentity& vid, bool async):\n    IProcCommand()\n  {\n    mReqProto = req;\n    mDoAsync = async;\n    mVid = vid;\n    mComment = req.comment().c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~IProcCommand()\n  {\n    mForceKill.store(true);\n\n    if (mOfsOutStream.is_open()) {\n      mOfsOutStream.close();\n    }\n\n    unlink(mOfsOutStreamFilename.c_str());\n\n    if (mOfsErrStream.is_open()) {\n      mOfsErrStream.close();\n    }\n\n    unlink(mOfsErrStreamFilename.c_str());\n\n    if (mHasSlot) {\n      std::unique_lock<std::mutex> lock(mMapCmdsMutex);\n      --mCmdsExecuting[mReqProto.command_case()];\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Open a proc command e.g. call the appropriate user or admin command and\n  //! store the output in a resultstream of in case of find in temporary output\n  //! files.\n  //!\n  //! @param inpath path indicating user or admin command\n  //! @param info CGI describing the proc command\n  //! @param vid_in virtual identity of the user requesting a command\n  //! @param error object to store errors\n  //!\n  //! @return SFS_OK in any case\n  //----------------------------------------------------------------------------\n  virtual int open(const char* path, const char* info,\n                   eos::common::VirtualIdentity& vid,\n                   XrdOucErrInfo* error);\n\n  //----------------------------------------------------------------------------\n  //! Read a part of the result stream created during open\n  //!\n  //! @param boff offset where to start\n  //! @param buff buffer to store stream\n  //! @param blen len to return\n  //!\n  //! @return number of bytes read\n  //----------------------------------------------------------------------------\n  virtual size_t read(XrdSfsFileOffset offset, char* buff, XrdSfsXferSize blen);\n\n  //----------------------------------------------------------------------------\n  //! Get the size of the result stream\n  //!\n  //! @param buf stat structure to fill\n  //!\n  //! @return SFS_OK in any case\n  //----------------------------------------------------------------------------\n  virtual int stat(struct stat* buf)\n  {\n    off_t size = 0;\n\n    if (readStdOutStream) {\n      ifstdoutStream.seekg(0, ifstdoutStream.end);\n      size += ifstdoutStream.tellg();\n      ifstdoutStream.seekg(0, ifstdoutStream.beg);\n      ifstderrStream.seekg(0, ifstderrStream.end);\n      size += ifstderrStream.tellg();\n      ifstderrStream.seekg(0, ifstderrStream.beg);\n      iretcStream.seekg(0, iretcStream.end);\n      size += iretcStream.tellg();\n      iretcStream.seekg(0, iretcStream.beg);\n    } else {\n      size = mTmpResp.length();\n    }\n\n    memset(buf, 0, sizeof(struct stat));\n    buf->st_size = size;\n    return SFS_OK;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Close the proc stream\n  //!\n  //! @return 0 if comment has been successfully stored otherwise != 0\n  //----------------------------------------------------------------------------\n  virtual int close()\n  {\n    if (ifstdoutStream.is_open()) {\n      ifstdoutStream.close();\n    }\n\n    if (ifstderrStream.is_open()) {\n      ifstderrStream.close();\n    }\n\n    return SFS_OK;\n  }\n\n  virtual std::string GetCmd(const char* cgi = 0)\n  {\n    return \"proto\";\n  }\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behavior of the command executed\n  //----------------------------------------------------------------------------\n  virtual eos::console::ReplyProto ProcessRequest() noexcept = 0;\n\n  //----------------------------------------------------------------------------\n  //! Launch command asynchronously, creating the corresponding promise and\n  //! future\n  //----------------------------------------------------------------------------\n  virtual void LaunchJob() final;\n\n  //----------------------------------------------------------------------------\n  //! Check if we can safely delete the current object as there is no async\n  //! thread executing the ProcessResponse method\n  //!\n  //! @return true if deletion if safe, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool KillJob() final;\n\n  //----------------------------------------------------------------------------\n  //! Return the result\n  //----------------------------------------------------------------------------\n  virtual const char* GetResult(size_t& size) const\n  {\n    return \"bla\";\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get result file name\n  //----------------------------------------------------------------------------\n  inline const char* GetResultFn() const\n  {\n    return mOfsOutStreamFilename.c_str();\n  }\n\n  virtual void SetError(XrdOucErrInfo* error) {};\n\nprotected:\n  virtual bool OpenTemporaryOutputFiles();\n  virtual bool CloseTemporaryOutputFiles();\n\n  //----------------------------------------------------------------------------\n  //! Retrieve the file's full path given its numeric id.\n  //! This method executes under the NamespaceView lock.\n  //!\n  //! @param path full path of the file\n  //! @param fid file numeric id\n  //! @param err_msg_prefix error message to be displayed in case of exception\n  //! @return retc return code\n  //----------------------------------------------------------------------------\n  int GetPathFromFid(std::string& path, unsigned long long fid,\n                     std::string& err_msg);\n\n  //----------------------------------------------------------------------------\n  //! Retrieve the container's full path given its numeric id.\n  //! This method executes under the NamespaceView lock.\n  //!\n  //! @param path full path of the container\n  //! @param cid container numeric id\n  //! @param err_msg_prefix error message to be displayed in case of exception\n  //----------------------------------------------------------------------------\n  int GetPathFromCid(std::string& path, unsigned long long cid,\n                     std::string& err_msg);\n\n  //----------------------------------------------------------------------------\n  //! Format console output string as json.\n  //!\n  //! @note\n  //! This will work only if the given output follows <key>=<value> format.\n  //! Also, provided values must follow a proper JSON hierarchy !\n  //!\n  //! Although the function tries to correct some values,\n  //! the correction is not exhaustive.\n  //!\n  //! Valid example:\n  //! stat.drain.status=<value> / stat.drain.otherkey=<value>\n  //!\n  //! Invalid example:\n  //! stat.drain=<value> / stat.drain.status=<value> (will throw an exception)\n  //!\n  //! @param stdOut console output string\n  //!\n  //! @return jsonOut json formatted output\n  //----------------------------------------------------------------------------\n  static Json::Value ConvertOutputToJsonFormat(const std::string& stdOut);\n\n  //----------------------------------------------------------------------------\n  //! Create a JSON string from the command output, error and return code.\n  //!\n  //! @param out console output string\n  //! @param err console error string\n  //! @param retc console return code\n  //!\n  //! @return jsonOut json response string containing output, error\n  //!                 and return code\n  //----------------------------------------------------------------------------\n  std::string ResponseToJsonString(const std::string& out,\n                                   const std::string& err = \"\",\n                                   int rc = 0);\n\n  //----------------------------------------------------------------------------\n  //! Indicate whether output should be in JSON format\n  //----------------------------------------------------------------------------\n  inline bool WantsJsonOutput()\n  {\n    return mReqProto.format() == eos::console::RequestProto::JSON;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if operation forbidden\n  //!\n  //! @param path path of the request\n  //! @param vid client virtual identity\n  //! @param err_check output error message\n  //! @param errno_check output errno in case of errors\n  //!\n  //! @return true if operation forbidden, false otherwise\n  //----------------------------------------------------------------------------\n  bool IsOperationForbidden(const std::string& path,\n                            const eos::common::VirtualIdentity& vid,\n                            std::string& err_check, int& errno_check) const;\n\n  //----------------------------------------------------------------------------\n  //! Check if a routing redirect should happen.\n  //!\n  //! @note\n  //! In case routing is needed, fills the routing info object\n  //! and sets the reply return code to SFS_REDIRECT.\n  //!\n  //! @param path path to route\n  //! @param reply the reply proto object\n  //!\n  //! @return true if should route, false otherwise\n  //----------------------------------------------------------------------------\n  bool ShouldRoute(const std::string& path,\n                   eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Check if there is still an available slot for the current type of command\n  //! in the queue served by the thread pool\n  //!\n  //! @return true if command can be queued, otherwise false\n  //----------------------------------------------------------------------------\n  bool HasSlot();\n\n  //----------------------------------------------------------------------------\n  //! Resolve an identifier (fid/fxid/cid/cxid) to its corresponding EOS path.\n  //! If the input is not a recognized identifier, it is returned unchanged.\n  //! In case of failure, sets the return code and error message accordingly.\n  //!\n  //! Supported formats:\n  //!   - fid:<decimal>   : file identifier (decimal)\n  //!   - fxid:<hex>      : file identifier (hexadecimal)\n  //!   - cid:<decimal>   : container identifier (decimal)\n  //!   - cxid:<hex>      : container identifier (hexadecimal)\n  //!\n  //! @param identifier   input identifier or path\n  //! @param resolvedPath output resolved EOS path\n  //! @param errorMsg     output error message in case of failure\n  //! @param retc         output return code (errno style)\n  //!\n  //! @return true on success, false on failure\n  //----------------------------------------------------------------------------\n  bool ResolveIdentifierToPath(XrdOucString& identifier, XrdOucString& resolvedPath,\n                               XrdOucString& errorMsg, int& retc);\n\n  //----------------------------------------------------------------------------\n  //! Store routing information\n  //----------------------------------------------------------------------------\n  struct RoutingInfo {\n    std::string path;\n    std::string host;\n    int port;\n    int stall_timeout;\n  };\n\n  static std::atomic_uint_least64_t uuid;\n  static std::mutex mMapCmdsMutex; ///< Mutex protecting the cmds map\n  //! Map of command types to number of commands actually queued\n  static std::map<eos::console::RequestProto::CommandCase, uint64_t>\n  mCmdsExecuting;\n\n  //! Indicate if current command has taken a slot in the queue\n  std::atomic<bool> mHasSlot;\n  bool mExecRequest; ///< Indicate if request is launched asynchronously\n  eos::console::RequestProto mReqProto; ///< Client request protobuf object\n  std::future<eos::console::ReplyProto> mFuture; ///< Response future\n  bool mDoAsync; ///< If true use thread pool to do the work\n  std::atomic<bool> mForceKill; ///< Flag to notify worker thread\n  eos::common::VirtualIdentity mVid; ///< Copy of original vid\n  time_t mTimestamp; ///< Timestamp of the proc command\n  XrdOucString mComment; ///< Comment issued by the user for the proc command\n  RoutingInfo mRoutingInfo; ///< Routing information of the proc command\n  std::string mTmpResp; ///< String used for streaming the response\n  std::ofstream mOfsOutStream;\n  std::ofstream mOfsErrStream;\n  std::string mOfsOutStreamFilename;\n  std::string mOfsErrStreamFilename;\n  std::ifstream ifstdoutStream;\n  std::ifstream ifstderrStream;\n  std::istringstream iretcStream;\n  bool readStdOutStream {false};\n  bool readStdErrStream {false};\n  bool readRetcStream {false};\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/ProcCommand.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file ProcCommand.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"ProcCommand.hh\"\n#include \"common/Path.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"common/CommentLog.hh\"\n#include <XrdOuc/XrdOucTokenizer.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"json/json.h\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nProcCommand::ProcCommand():\n  pVid(0), mPath(\"\"),  mCmd(\"\"), mSubCmd(\"\"), mArgs(\"\"), mResultStream(\"\"),\n  pOpaque(0), ininfo(0),  mDoSort(false), mSelection(0), mOutFormat(\"\"),\n  mOutDepth(0), fstdout(0), fstderr(0), fresultStream(0), fstdoutfilename(\"\"),\n  fstderrfilename(\"\"), fresultStreamfilename(\"\"), mError(0),\n  mLen(0), mAdminCmd(false), mUserCmd(false), mFuseFormat(false),\n  mJsonFormat(false), mHttpFormat(false), mClosed(false),  mSendRetc(false),\n  mJsonCallback(\"\") {}\n\n//------------------------------------------------------------------------------\n// Constructor with parameter\n//------------------------------------------------------------------------------\nProcCommand::ProcCommand(eos::common::VirtualIdentity& vid):\n  ProcCommand()\n{\n  pVid = &vid;\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nProcCommand::~ProcCommand()\n{\n  if (fstdout) {\n    fclose(fstdout);\n    fstdout = 0;\n    unlink(fstdoutfilename.c_str());\n  }\n\n  if (fstderr) {\n    fclose(fstderr);\n    fstderr = 0;\n    unlink(fstderrfilename.c_str());\n  }\n\n  if (fresultStream) {\n    fclose(fresultStream);\n    fresultStream = 0;\n    unlink(fresultStreamfilename.c_str());\n  }\n\n  if (pOpaque) {\n    delete pOpaque;\n    pOpaque = 0;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Open temporary output files for results of find commands\n//------------------------------------------------------------------------------\nbool\nProcCommand::OpenTemporaryOutputFiles()\n{\n  char tmpdir [4096];\n  snprintf(tmpdir, sizeof(tmpdir) - 1, \"%s/%llu\",\n           gOFS->TmpStorePath.c_str(),\n           (unsigned long long) XrdSysThread::ID());\n  fstdoutfilename = tmpdir;\n  fstdoutfilename += \".stdout\";\n  fstderrfilename = tmpdir;\n  fstderrfilename += \".stderr\";\n  fresultStreamfilename = tmpdir;\n  fresultStreamfilename += \".mResultstream\";\n  eos::common::Path cPath(fstdoutfilename.c_str());\n\n  if (!cPath.MakeParentPath(S_IRWXU)) {\n    eos_err(\"Unable to create temporary outputfile directory %s\", tmpdir);\n    return false;\n  }\n\n  // own the directory by daemon\n  if (::chown(cPath.GetParentPath(), 2, 2)) {\n    eos_err(\"Unable to own temporary outputfile directory %s\",\n            cPath.GetParentPath());\n  }\n\n  fstdout = fopen(fstdoutfilename.c_str(), \"w\");\n  fstderr = fopen(fstderrfilename.c_str(), \"w\");\n  fresultStream = fopen(fresultStreamfilename.c_str(), \"w+\");\n\n  if ((!fstdout) || (!fstderr) || (!fresultStream)) {\n    if (fstdout) {\n      fclose(fstdout);\n    }\n\n    if (fstderr) {\n      fclose(fstderr);\n    }\n\n    if (fresultStream) {\n      fclose(fresultStream);\n    }\n\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Open a proc command e.g. call the appropriate user or admin command and\n// store the output in a resultstream of in case of find in temporary output\n// files.\n//------------------------------------------------------------------------------\nint\nProcCommand::open(const char* inpath, const char* info,\n                  eos::common::VirtualIdentity& vid_in,\n                  XrdOucErrInfo* error)\n{\n  pVid = &vid_in;\n  mClosed = false;\n  mPath = inpath;\n  mDoSort = false;\n  mError = error;\n  ininfo = info;\n\n  if ((mPath.beginswith(\"/proc/admin\"))) {\n    mAdminCmd = true;\n  }\n\n  if (mPath.beginswith(\"/proc/user\")) {\n    mUserCmd = true;\n  }\n\n  // Deal with '&' ... sigh\n  XrdOucString sinfo = ininfo;\n\n  for (int i = 0; i < sinfo.length(); i++) {\n    if (sinfo[i] == '&') {\n      // figure out if this is a real separator or\n      XrdOucString follow = sinfo.c_str() + i + 1;\n\n      if (!follow.beginswith(\"mgm.\") && (!follow.beginswith(\"eos.\")) &&\n          (!follow.beginswith(\"xrd.\")) && (!follow.beginswith(\"callback\")) &&\n          (!follow.beginswith(\"authz\"))) {\n        sinfo.erase(i, 1);\n        sinfo.insert(\"#AND#\", i);\n      }\n    }\n  }\n\n  pOpaque = new XrdOucEnv(sinfo.c_str());\n\n  if (!pOpaque) {\n    // alloc failed\n    return SFS_ERROR;\n  }\n\n  mOutFormat = \"\";\n  mOutDepth = 0;\n  mCmd = pOpaque->Get(\"mgm.cmd\");\n  mSubCmd = pOpaque->Get(\"mgm.subcmd\");\n  mOutFormat = pOpaque->Get(\"mgm.outformat\");\n  long depth = pOpaque->GetInt(\"mgm.outdepth\");\n\n  if (depth > 0) {\n    mOutDepth = (unsigned)depth;\n  }\n\n  mSelection = pOpaque->Get(\"mgm.selection\");\n  mComment = pOpaque->Get(\"mgm.comment\") ? pOpaque->Get(\"mgm.comment\") : \"\";\n  mJsonCallback = pOpaque->Get(\"callback\") ? pOpaque->Get(\"callback\") : \"\";\n  mSendRetc = pOpaque->Get(\"mgm.retc\") ? true : false;\n  eos_static_debug(\"json-callback=%s opaque=%s\", mJsonCallback.c_str(),\n                   sinfo.c_str());\n  int envlen = 0;\n  mArgs = pOpaque->Env(envlen);\n  mFuseFormat = false;\n  mJsonFormat = false;\n  mHttpFormat = false;\n  // If set to FUSE, don't print the stdout,stderr tags and we guarantee a line\n  // feed in the end\n  XrdOucString format = pOpaque->Get(\"mgm.format\");\n\n  if (format == \"fuse\") {\n    mFuseFormat = true;\n  }\n\n  if (format == \"json\") {\n    mJsonFormat = true;\n  }\n\n  if (format == \"http\") {\n    mHttpFormat = true;\n  }\n\n  stdOut = \"\";\n  stdErr = \"\";\n  retc = 0;\n  mResultStream = \"\";\n  mLen = 0;\n  mDoSort = true;\n\n  if (mJsonCallback.length()) {\n    mJsonFormat = true;\n  }\n\n  // Admin command section\n  if (mAdminCmd) {\n    if (mCmd == \"archive\") {\n      Archive();\n      mDoSort = false;\n    } else if (mCmd == \"backup\") {\n      Backup();\n      mDoSort = false;\n    } else if (mCmd == \"geosched\") {\n      GeoSched();\n      mDoSort = false;\n    } else if (mCmd == \"fusex\") {\n      Fusex();\n      mDoSort = false;\n    } else if (mCmd == \"vid\") {\n      Vid();\n    } else if (mCmd == \"rtlog\") {\n      Rtlog();\n      mDoSort = false;\n    } else if (mCmd == \"access\") { // @todo (faluchet) drop when move to 5.0.0\n      Access();\n      mDoSort = false;\n    } else if (mCmd == \"quota\") { // @todo (faluchet) drop when move to 5.0.0\n      AdminQuota();\n      mDoSort = false;\n    } else {\n      // command is not implemented\n      stdErr += \"error: no such admin command '\";\n      stdErr += mCmd;\n      stdErr += \"'\";\n      retc = EINVAL;\n    }\n\n    MakeResult();\n    return SFS_OK;\n  }\n\n  // User command section\n  if (mUserCmd) {\n    if (mCmd == \"accounting\") {\n      Accounting();\n      mDoSort = false;\n    } else if (mCmd == \"archive\") {\n      Archive();\n      mDoSort = false;\n    } else if (mCmd == \"motd\") {\n      Motd();\n      mDoSort = false;\n    } else if (mCmd == \"version\") {\n      Version();\n      mDoSort = false;\n    } else if (mCmd == \"who\") {\n      Who();\n      mDoSort = false;\n    } else if (mCmd == \"fuse\") {\n      return Fuse();\n    } else if (mCmd == \"fuseX\") {\n      return FuseX();\n    } else if (mCmd == \"file\") {\n      File();\n      mDoSort = false;\n    } else if (mCmd == \"fileinfo\") {\n      Fileinfo();\n      mDoSort = false;\n    } else if (mCmd == \"mkdir\") {\n      Mkdir();\n    } else if (mCmd == \"rmdir\") {\n      Rmdir();\n    } else if (mCmd == \"cd\") {\n      Cd();\n      mDoSort = false;\n    } else if (mCmd == \"chown\") {\n      Chown();\n    } else if (mCmd == \"ls\") {\n      Ls();\n      mDoSort = false;\n    } else if (mCmd == \"rm\") {\n      Rm();\n    } else if (mCmd == \"whoami\") {\n      Whoami();\n      mDoSort = false;\n    } else if (mCmd == \"find\") {\n      Find();\n    } else if (mCmd == \"map\") {\n      Map();\n    } else if (mCmd == \"member\") {\n      Member();\n    } else if (mCmd == \"attr\") {\n      Attr();\n      mDoSort = false;\n    } else if (mCmd == \"chmod\") {\n      Chmod();\n    } else if (mCmd == \"quota\") { // @todo (faluchet) drop when move to 5.0.0\n      UserQuota();\n      mDoSort = false;\n    } else {\n      // Command not implemented\n      stdErr += \"error: no such user command '\";\n      stdErr += mCmd;\n      stdErr += \"'\";\n      retc = ENOTSUP;\n    }\n\n    if (mSendRetc) {\n      // client wants return code on open\n      if (retc)\n        return gOFS->Emsg((const char*) \"open\", *error, retc,\n                          \"execute command\", ininfo);\n      else {\n        return SFS_OK;\n      }\n    } else {\n      // client gets result stream\n      MakeResult();\n      return SFS_OK;\n    }\n  }\n\n  // If neither admin nor proc command\n  return gOFS->Emsg((const char*) \"open\", *error, EINVAL,\n                    \"execute command - not implemented \", ininfo);\n}\n\n//------------------------------------------------------------------------------\n// Read a part of the result stream produced during open\n//------------------------------------------------------------------------------\nsize_t\nProcCommand::read(XrdSfsFileOffset boff, char* buff, XrdSfsXferSize blen)\n{\n  if (fresultStream) {\n    // file based results go here ...\n    if ((fseek(fresultStream, boff, 0)) == 0) {\n      size_t nread = fread(buff, 1, blen, fresultStream);\n\n      if (nread > 0) {\n        return nread;\n      }\n    } else {\n      eos_err(\"seek to %llu failed\\n\", boff);\n    }\n\n    return 0;\n  } else {\n    if (mLen - boff <= 0) {\n      return 0;\n    }\n\n    // Memory based results go here ...\n    if (((unsigned int) blen <= (mLen - boff))) {\n      memcpy(buff, mResultStream.c_str() + boff, blen);\n      return blen;\n    } else {\n      memcpy(buff, mResultStream.c_str() + boff, (mLen - boff));\n      return (mLen - boff);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Return stat information for the result stream to tell the client the size\n// of the proc output.\n//------------------------------------------------------------------------------\nint\nProcCommand::stat(struct stat* buf)\n{\n  memset(buf, 0, sizeof(struct stat));\n  buf->st_size = mLen;\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Close the proc stream and store the client's command comment\n// in the comments logbook.\n//------------------------------------------------------------------------------\nint\nProcCommand::close()\n{\n  if (!mClosed) {\n    // Only instance users or sudoers can add to the logbook\n    if ((pVid->uid <= 2) || (pVid->sudoer)) {\n      if (mComment.length() && gOFS->mCommentLog) {\n        if (!gOFS->mCommentLog->Add(mTimestamp, mCmd.c_str(), mSubCmd.c_str(),\n                                    mArgs.c_str(), mComment.c_str(), stdErr.c_str(), retc)) {\n          eos_err(\"failed to log to comments logbook\");\n        }\n      }\n    }\n\n    mClosed = true;\n  }\n\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Build the in-memory result of the stdout, stderr & retc of the proc command.\n// Depending on the output format the key-value CGI returned changes => see\n// implementation.\n//------------------------------------------------------------------------------\nvoid\nProcCommand::MakeResult()\n{\n  using eos::common::StringConversion;\n  mResultStream = \"\";\n\n  if (!fstdout) {\n    if (mDoSort) {\n      eos::common::StringConversion::SortLines(stdOut);\n    }\n\n    if ((!mFuseFormat && !mJsonFormat && !mHttpFormat)) {\n      // The default format\n      mResultStream = \"mgm.proc.stdout=\";\n      mResultStream += StringConversion::Seal(stdOut);\n      mResultStream += \"&mgm.proc.stderr=\";\n      mResultStream += StringConversion::Seal(stdErr);\n      mResultStream += \"&mgm.proc.retc=\";\n      mResultStream += std::to_string(retc);\n    }\n\n    if (mFuseFormat || mHttpFormat) {\n      if (mFuseFormat) {\n        mResultStream += stdOut.c_str();\n      } else {\n        mResultStream +=\n          \"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD XHTML 1.1//EN\\\" \\\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\\\">\\n\";\n        mResultStream += \"<html>\\n\";\n        mResultStream +=\n          \"<TITLE>EOS-HTTP</TITLE> <link rel=\\\"stylesheet\\\" href=\\\"http://www.w3.org/StyleSheets/Core/Midnight\\\"> \\n\";\n        mResultStream += \"<meta charset=\\\"utf-8\\\"> \\n\";\n\n        // block cross-site scripting in responses\n        if (stdErr.length()) {\n          mResultStream +=\n            \"<meta http-equiv=\\\"Content-Security-Policy\\\" content=\\\"script-src https://code.jquery.com 'self';\\\">\\n\";\n        }\n\n        mResultStream += \"<div class=\\\"httptable\\\" id=\\\"\";\n        mResultStream += mCmd.c_str();\n        mResultStream += \"_\";\n        mResultStream += mSubCmd.c_str();\n        mResultStream += \"\\\">\\n\";\n\n        // FUSE format contains only STDOUT\n        if (stdOut.length() && KeyValToHttpTable(stdOut)) {\n          mResultStream += stdOut.c_str();\n        } else {\n          if (stdErr.length() || retc) {\n            mResultStream += stdOut.c_str();\n            mResultStream += \"<h3>&#9888;&nbsp;<font color=\\\"red\\\">\";\n            mResultStream += stdErr.c_str();\n            mResultStream += \"</font></h3>\";\n          } else {\n            if (!stdOut.length()) {\n              mResultStream += \"<h3>&#10004;&nbsp;\";\n              mResultStream += \"Success!\";\n              mResultStream += \"</h3>\";\n            } else {\n              mResultStream += stdOut.c_str();\n            }\n          }\n        }\n\n        mResultStream += \"</div>\";\n      }\n    }\n\n    if (mJsonFormat) {\n      if (!stdJson.length()) {\n        Json::Value json;\n\n        try {\n          Json::Value jsonOut;\n          json[\"errormsg\"] = stdErr.c_str();\n          json[\"retc\"] = std::to_string(retc);\n          jsonOut = IProcCommand::ConvertOutputToJsonFormat(stdOut.c_str());\n\n          if (mCmd.length()) {\n            if (mSubCmd.length()) {\n              json[mCmd.c_str()][mSubCmd.c_str()] = jsonOut;\n            } else {\n              json[mCmd.c_str()] = jsonOut;\n            }\n          } else {\n            json[\"result\"] = jsonOut;\n          }\n        } catch (Json::Exception& e) {\n          eos_static_err(\"Json conversion exception cmd=%s subcmd=%s \"\n                         \"emsg=\\\"%s\\\"\", mCmd.c_str(), mSubCmd.c_str(), e.what());\n          json[\"errormsg\"] = \"illegal string in json conversion\";\n          json[\"retc\"] = std::to_string(EFAULT);\n        }\n\n        stdJson = SSTR(json).c_str();\n      }\n\n      if (mJsonCallback.length()) {\n        // JSONP\n        mResultStream = mJsonCallback.c_str();\n        mResultStream += \"([\\n\";\n        mResultStream += stdJson.c_str();\n        mResultStream += \"\\n]);\";\n      } else {\n        // JSON\n        if (vid.prot.beginswith(\"http\")) {\n          mResultStream = stdJson.c_str();\n        } else {\n          mResultStream = \"mgm.proc.json=\";\n          mResultStream += StringConversion::Seal(stdJson);\n        }\n      }\n    }\n\n    if (mResultStream.length() && (*(mResultStream.rbegin()) != '\\n')) {\n      mResultStream += \"\\n\";\n    }\n\n    if (retc) {\n      eos_static_err(\"%s (errno=%u)\", stdErr.c_str(), retc);\n    }\n\n    mLen = mResultStream.length();\n  } else {\n    // File based results CANNOT be sorted and don't have mFuseFormat\n    if (!mFuseFormat) {\n      // Create the stdout result\n      if (!fseek(fstdout, 0, 0) &&\n          !fseek(fstderr, 0, 0) &&\n          !fseek(fresultStream, 0, 0)) {\n        fprintf(fresultStream, \"&mgm.proc.stdout=\");\n        std::ifstream inStdout(fstdoutfilename.c_str());\n        std::ifstream inStderr(fstderrfilename.c_str());\n        std::string entry;\n\n        while (std::getline(inStdout, entry)) {\n          XrdOucString sentry = entry.c_str();\n          sentry += \"\\n\";\n\n          if (!mFuseFormat) {\n            StringConversion::Seal(sentry);\n          }\n\n          fprintf(fresultStream, \"%s\", sentry.c_str());\n        }\n\n        // Close and remove - if this fails there is nothing to recover anyway\n        fclose(fstdout);\n        fstdout = 0;\n        unlink(fstdoutfilename.c_str());\n        // Create the stderr result\n        fprintf(fresultStream, \"&mgm.proc.stderr=\");\n\n        while (std::getline(inStderr, entry)) {\n          XrdOucString sentry = entry.c_str();\n          sentry += \"\\n\";\n          StringConversion::Seal(sentry);\n          fprintf(fresultStream, \"%s\", sentry.c_str());\n        }\n\n        // Close and remove - if this fails there is nothing to recover anyway\n        fclose(fstderr);\n        fstderr = 0;\n        unlink(fstderrfilename.c_str());\n        fprintf(fresultStream, \"&mgm.proc.retc=%d\", retc);\n        mLen = ftell(fresultStream);\n        // Spool the resultstream to the beginning\n        fseek(fresultStream, 0, 0);\n      } else {\n        eos_static_err(\"cannot seek to position 0 in result files\");\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Try to detect and convert a monitor output format and convert it into a\n// nice http table\n//------------------------------------------------------------------------------\nbool\nProcCommand::KeyValToHttpTable(XrdOucString& stdOut)\n{\n  while (stdOut.replace(\"= \", \"=\\\"\\\"\")) {\n  }\n\n  std::string stmp = stdOut.c_str();\n  XrdOucTokenizer tokenizer((char*) stmp.c_str());\n  const char* line;\n  bool ok = true;\n  std::vector<std::string> keys;\n  std::vector < std::map < std::string, std::string >> keyvaluetable;\n  std::string table;\n\n  while ((line = tokenizer.GetLine())) {\n    if (strlen(line) <= 1) {\n      continue;\n    }\n\n    std::map<std::string, std::string> keyval;\n\n    if (eos::common::StringConversion::GetKeyValueMap(line,\n        keyval,\n        \"=\",\n        \" \",\n        &keys)) {\n      keyvaluetable.push_back(keyval);\n    } else {\n      ok = false;\n      break;\n    }\n  }\n\n  if (ok) {\n    table +=\n      R\"literal(<style>\ntable\n{\n  table-layout:auto;\n}\n</style>\n)literal\";\n\n    table += \"<table border=\\\"8\\\" cellspacing=\\\"10\\\" cellpadding=\\\"20\\\">\\n\";\n    // build the header\n    table += \"<tr>\\n\";\n    for (size_t i = 0; i < keys.size(); i++)\n    {\n      table += \"<th>\";\n      table += \"<font size=\\\"2\\\">\";\n      // for keys don't print lengthy strings like a.b.c.d ... just print d\n      std::string dotkeys = keys[i];\n      size_t pos = dotkeys.rfind(\".\");\n      if (pos != std::string::npos)\n        dotkeys.erase(0, pos + 1);\n      //table += dotkeys;\n      table += keys[i];\n      table += \"</font>\";\n      table += \"</th>\";\n      table += \"\\n\";\n    }\n    table += \"</tr>\\n\";\n\n    // build the rows\n\n    for (size_t i = 0; i < keyvaluetable.size(); i++)\n    {\n      table += \"<tr>\\n\";\n      for (size_t j = 0; j < keys.size(); j++)\n      {\n        table += \"<td nowrap=\\\"nowrap\\\">\";\n        table += \"<font size=\\\"2\\\">\";\n        XrdOucString sizestring = keyvaluetable[i][keys[j]].c_str();\n        unsigned long long val = eos::common::StringConversion::GetSizeFromString(sizestring);\n        if (errno || val == 0 || (!sizestring.isdigit()))\n        {\n          XrdOucString decodeURI = keyvaluetable[i][keys[j]].c_str();\n          // we need to remove URI encoded spaces now\n          while (decodeURI.replace(\"%20\", \" \"))\n          {\n          }\n          table += decodeURI.c_str();\n        }\n        else\n        {\n          eos::common::StringConversion::GetReadableSizeString(sizestring, val, \"\");\n          table += sizestring.c_str();\n        }\n        table += \"</font>\";\n        table += \"</td>\";\n      }\n      table += \"</tr>\\n\";\n      table += \"\\n\";\n    }\n\n\n    table += \"</table>\\n\";\n    stdOut = table.c_str();\n  }\n  return ok;\n}\n\nJson::Value ProcCommand::CallJsonFormatter(const std::string& output)\n{\n  return IProcCommand::ConvertOutputToJsonFormat(output);\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/ProcCommand.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file ProcCommand.hh\n//! @param Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"IProcCommand.hh\"\n#include <XrdOuc/XrdOucEnv.hh>\n#include \"namespace/interface/IView.hh\"\n#include <iomanip>\n\n//! Forward declaration\nnamespace Json\n{\nclass Value;\n}\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! @todo (esindril): This needs to be moved to the archive command\n//! Class IFilter used as interface to implement various types of filters\n//! for the archive and backup operations.\n//------------------------------------------------------------------------------\nclass IFilter\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~IFilter() {};\n\n  //----------------------------------------------------------------------------\n  //! Filter the file entry\n  //!\n  //! @param entry_info entry information on which the filter is applied\n  //!\n  //! @return true if entry should be filtered out, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool FilterOutFile(const std::map<std::string,\n                             std::string>& entry_info) = 0 ;\n\n  //----------------------------------------------------------------------------\n  //! Filter the directory entry\n  //!\n  //! @param path current directory path\n  //!\n  //! @return true if entry should be filtered out, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool FilterOutDir(const std::string& path) = 0;\n};\n\n//------------------------------------------------------------------------------\n//! Class handling proc command execution\n//------------------------------------------------------------------------------\nclass ProcCommand: public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ProcCommand();\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param vid identity of the user executing the current command\n  //----------------------------------------------------------------------------\n  ProcCommand(eos::common::VirtualIdentity& vid);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~ProcCommand();\n\n  //----------------------------------------------------------------------------\n  //! Open a proc command e.g. call the appropriate user or admin command and\n  //! store the output in a resultstream of in case of find in temporary output\n  //! files.\n  //!\n  //! @param inpath path indicating user or admin command\n  //! @param info CGI describing the proc command\n  //! @param vid_in virtual identity of the user requesting a command\n  //! @param error object to store errors\n  //!\n  //! @return SFS_OK or client stall interval in seconds\n  //----------------------------------------------------------------------------\n  virtual int open(const char* path, const char* info,\n                   eos::common::VirtualIdentity& vid,\n                   XrdOucErrInfo* error) override;\n\n  //----------------------------------------------------------------------------\n  //! Read a part of the result stream created during open\n  //!\n  //! @param boff offset where to start\n  //! @param buff buffer to store stream\n  //! @param blen len to return\n  //!\n  //! @return number of bytes read\n  //----------------------------------------------------------------------------\n  virtual size_t read(XrdSfsFileOffset offset, char* buff,\n                      XrdSfsXferSize blen) override;\n\n  //----------------------------------------------------------------------------\n  //! Get the size of the result stream\n  //!\n  //! @param buf stat structure to fill\n  //!\n  //! @return SFS_OK in any case\n  //----------------------------------------------------------------------------\n  virtual int stat(struct stat* buf) override;\n\n  //----------------------------------------------------------------------------\n  //! Close the proc stream and store the client's command comment\n  //! in the comments logbook\n  //!\n  //! @return 0 if comment has been successfully stored otherwise != 0\n  //----------------------------------------------------------------------------\n  virtual int close() override;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behavior of the command executed by the\n  //! asynchronous thread - used only for protobuf commands\n  //----------------------------------------------------------------------------\n  virtual eos::console::ReplyProto ProcessRequest() noexcept override\n  {\n    // Default behavior for old (raw) style commands\n    return eos::console::ReplyProto();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Add stdout, stderr to an external stdout, stderr variable\n  //----------------------------------------------------------------------------\n  void\n  AddOutput(XrdOucString& lStdOut, XrdOucString& lStdErr)\n  {\n    lStdOut += stdOut;\n    lStdErr += stdErr;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Add stdout, stderr to an external stdout, stderr variable\n  //----------------------------------------------------------------------------\n  void\n  AddOutput(std::string& lStdOut, std::string& lStdErr)\n  {\n    lStdOut += stdOut.c_str();\n    lStdErr += stdErr.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Open temporary output files for find commands\n  //!\n  //! @return true if successful otherwise false\n  //----------------------------------------------------------------------------\n  bool OpenTemporaryOutputFiles() override;\n\n  //----------------------------------------------------------------------------\n  //! Get the return code of a proc command\n  //----------------------------------------------------------------------------\n  inline int GetRetc() const\n  {\n    return retc;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get stdErr of a proc command\n  //----------------------------------------------------------------------------\n  inline const char* GetStdErr() const\n  {\n    return stdErr.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get stdJson of a proc command\n  //----------------------------------------------------------------------------\n  inline const char* GetStdJson() const\n  {\n    return stdJson.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the result stream  of a proc command\n  //----------------------------------------------------------------------------\n  inline const char* GetResult(size_t& size) const override\n  {\n    if (mClosed) {\n      size = 0;\n      return 0;\n    }\n\n    size = mResultStream.size();\n    return mResultStream.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get result file name\n  //----------------------------------------------------------------------------\n  inline const char* GetResultFn() const\n  {\n    return fresultStreamfilename.c_str();\n  }\n\n  //----------------------------------------------------------------------------\n  //! List of user proc commands\n  //----------------------------------------------------------------------------\n  int Accounting();\n  int Attr();\n  int Archive();\n  int Backup();\n  int Cd();\n  int Chmod();\n  int DirInfo(const char* path);\n  int DirJSON(uint64_t id, Json::Value* json, bool dolock = true);\n  int File();\n  int Find();\n  int Fileinfo();\n  int FileInfo(const char* path);\n  int FileJSON(uint64_t id, Json::Value* json, bool dolock = true);\n  int Fuse();\n  int FuseX();\n  int Ls();\n  int Map();\n  int Member();\n  int Mkdir();\n  int Motd();\n  int Recycle();\n  int Rm();\n  int Rmdir();\n  int Version();\n  int Who();\n  int Whoami();\n  int UserQuota();\n\n  //----------------------------------------------------------------------------\n  //! List of admin proc commands\n  //----------------------------------------------------------------------------\n  int Chown();\n  int Drain();\n  int Fusex();\n  int GeoSched();\n  int Ns();\n  int Rtlog();\n  int Vid();\n  int Access(); // @todo (faluchet) drop when move to 5.0.0\n  int AdminQuota(); // @todo (faluchet) drop when move to 5.0.0\n\n  //----------------------------------------------------------------------------\n  //! Send command to archive daemon and collect the response\n  //!\n  //! @param cmd archive command in JSON format\n  //!\n  //! @return 0 is successful, otherwise errno. The output of the command or\n  //!         any possible error messages are saved in stdOut and stdErr.\n  //----------------------------------------------------------------------------\n  int ArchiveExecuteCmd(const std::string& cmd);\n\n  //----------------------------------------------------------------------------\n  //! Response structure holding information about the status of an archived dir\n  //----------------------------------------------------------------------------\n  struct ArchDirStatus {\n    std::string mTime;\n    std::string mUuid;\n    std::string mPath;\n    std::string mOp;\n    std::string mStatus;\n\n    //--------------------------------------------------------------------------\n    //! Constructor\n    //--------------------------------------------------------------------------\n    ArchDirStatus(const std::string& xtime, const std::string& uuid,\n                  const std::string& path, const std::string& op,\n                  const std::string& st):\n      mTime(xtime), mUuid(uuid), mPath(path), mOp(op), mStatus(st)\n    {};\n\n    //--------------------------------------------------------------------------\n    //! Destructor\n    //--------------------------------------------------------------------------\n    ~ArchDirStatus() {};\n  };\n\n  //----------------------------------------------------------------------------\n  //! Create a result stream from stdOut, stdErr & retc\n  //----------------------------------------------------------------------------\n  void MakeResult();\n\n  //----------------------------------------------------------------------------\n  //! Helper function able to detect key value pair output and convert to http\n  //! table format\n  //----------------------------------------------------------------------------\n  bool KeyValToHttpTable(XrdOucString& stdOut);\n\n  //----------------------------------------------------------------------------\n  //! Helper function to classify the current file state\n  //----------------------------------------------------------------------------\n\n  std::string  FileMDToStatus(std::shared_ptr<eos::IFileMD> fmd);\n\n  //----------------------------------------------------------------------------\n  //! Return the name of the command\n  //----------------------------------------------------------------------------\n  std::string GetCmd(const char* cgi) override\n  {\n    if (cgi) {\n      XrdOucEnv env(cgi);\n      return env.Get(\"mgm.cmd\") ? env.Get(\"mgm.cmd\") : \"none\";\n    }\n\n    return \"none\";\n  }\n\n  void SetError(XrdOucErrInfo* error) override\n  {\n    mError = error;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Convert output to json format for EOS-wnc\n  //----------------------------------------------------------------------------\n  static Json::Value CallJsonFormatter(const std::string& output);\n\nprotected:\n  eos::common::VirtualIdentity* pVid; ///< Pointer to virtual identity\n\nprivate:\n  XrdOucString stdOut; ///< stdOut returned by proc command\n  XrdOucString stdErr; ///< stdErr returned by proc command\n  XrdOucString stdJson; ///< JSON output returned by proc command\n  int retc; ///< Return code from the proc command\n  XrdOucString mPath; ///< path argument for the proc command\n  XrdOucString mCmd; ///< proc command name\n  XrdOucString mSubCmd; ///< proc sub command name\n  XrdOucString mArgs; ///< full args from opaque input\n  std::string mResultStream; ///< string containing the assembled stream\n  XrdOucEnv* pOpaque; ///< pointer to the opaque information object\n  const char* ininfo; ///< original opaque info string\n  bool mDoSort; ///< sort flag (true = sorting)\n  const char* mSelection; ///< selection argument from the opaque request\n  XrdOucString mOutFormat; ///< output format type e.g. fuse or json\n  unsigned mOutDepth; ///< depth of aggregation along the topology tree\n\n  //----------------------------------------------------------------------------\n  //! The 'find' command does not keep results in memory but writes to\n  //! a temporary output file which is streamed to the client\n  //----------------------------------------------------------------------------\n  FILE* fstdout;\n  FILE* fstderr;\n  FILE* fresultStream;\n  XrdOucString fstdoutfilename;\n  XrdOucString fstderrfilename;\n  XrdOucString fresultStreamfilename;\n  XrdOucErrInfo* mError;\n\n  ssize_t mLen; ///< len of the result stream\n  bool mAdminCmd; ///< indicates an admin command\n  bool mUserCmd; ///< indicates a user command\n  bool mFuseFormat; ///< indicates FUSE format\n  bool mJsonFormat; ///< indicates JSON format\n  bool mHttpFormat; ///< indicates HTTP format\n  bool mClosed; ///< indicates the proc command has been closed already\n  bool mSendRetc; ///< indicates to return the return code to the open call\n  XrdOucString mJsonCallback; ///< sets the JSONP callback name in a response\n\n  //----------------------------------------------------------------------------\n  //! Create archive file. If successful then the archive file is copied to the\n  //! arch_dir location. If not it sets the retc and stdErr string accordingly.\n  //!\n  //! @param arch_dir directory for which the archive file is created\n  //! @param dst_url archive destination URL (i.e. CASTOR location)\n  //! @param fid inode number of the archive root directory used for fast find\n  //!        functionality of archived directories through .../proc/archive/\n  //!\n  //! @return void, it sets the global retc in case of error\n  //----------------------------------------------------------------------------\n  void ArchiveCreate(const std::string& arch_dir,\n                     const std::string& dst_url, uint64_t fid);\n\n  //----------------------------------------------------------------------------\n  //! Get list of archived files from the proc/archive directory\n  //!\n  //! @param root root of subtree for which we collect archived entries\n  //!\n  //! @return vector containing the full path of the directories currently\n  //!         archived\n  //----------------------------------------------------------------------------\n  std::vector<ArchDirStatus> ArchiveGetDirs(const std::string& root) const;\n\n  //----------------------------------------------------------------------------\n  //! Update the status of the archived directories depending on the information\n  //! that we got from the archiver daemon. All ongoing transfers will be in\n  //! status \"transferring\" while the rest will display the status of the\n  //! archive.\n  //!\n  //! @param dirs vector of archived directories\n  //! @param tx_dirs vector of ongoing transfers\n  //! @param max_path_len max path length of the entries in the dirs vector\n  //----------------------------------------------------------------------------\n  void ArchiveUpdateStatus(std::vector<ArchDirStatus>& dirs,\n                           std::vector<ArchDirStatus>& tx_dirs,\n                           size_t& max_path_len);\n\n  //----------------------------------------------------------------------------\n  //! Get fileinfo for all files/dirs in the subtree and add it to the\n  //! archive i.e.  do\n  //! \"find -d --fileinfo /dir/\" for directories or\n  //! \"find -f --fileinfo /dir/ for files.\n  //!\n  //! @param arch_dir EOS directory being archived\n  //! @param arch_ofs local archive file stream object\n  //! @param num number of entries added\n  //! @param is_file if true add file entries to the archive, otherwise\n  //!                directories\n  //! @param filter filter to be applied to the entries\n  //!\n  //! @return 0 if successful, otherwise errno\n  //----------------------------------------------------------------------------\n  int ArchiveAddEntries(const std::string& arch_dir, std::fstream& arch_ofs,\n                        int& num, bool is_file, IFilter* filter = NULL);\n\n  //----------------------------------------------------------------------------\n  //! Make EOS sub-tree immutable by adding the sys.acl=z:i rule to all of the\n  //! directories in the sub-tree.\n  //!\n  //! @param arch_dir EOS directory\n  //! @param vect_files vector of special archive filenames\n  //!\n  //! @return 0 is successful, otherwise errno. It sets the global retc in case\n  //!         of error.\n  //----------------------------------------------------------------------------\n  int MakeSubTreeImmutable(const std::string& arch_dir,\n                           const std::vector<std::string>& vect_files);\n\n  //----------------------------------------------------------------------------\n  //! Make EOS sub-tree mutable by removing the sys.acl=z:i rule from all of the\n  //! directories in the sub-tree.\n  //!\n  //! @param arch_dir EOS directory\n  //!\n  //! @return 0 is successful, otherwise errno. It sets the global retc in case\n  //!         of error.\n  //----------------------------------------------------------------------------\n  int MakeSubTreeMutable(const std::string& arch_dir);\n\n  //----------------------------------------------------------------------------\n  //! Check that the user has the necessary permissions to do an archiving\n  //! operation.\n  //!\n  //! @param arch_dir archive directory\n  //!\n  //! @return true if user is allowed, otherwise False\n  //----------------------------------------------------------------------------\n  bool ArchiveCheckAcl(const std::string& arch_dir) const;\n\n  //----------------------------------------------------------------------------\n  //! Format listing output. Includes combining the information that we get\n  //! from the archiver daemon with the list of pending transfers at the MGM.\n  //!\n  //! @param cmd_json command to be sent to the archive daemon\n  //----------------------------------------------------------------------------\n  void ArchiveFormatListing(const std::string& cmd_json);\n\n  //----------------------------------------------------------------------------\n  //! Create backup file. If successful then the backup file is copied to the\n  //! backup_dir location. If not it sets the retc and stdErr string accordingly.\n  //!\n  //! @param backup_dir directory for which the backup file is created\n  //! @param dst_url backup destination URL ending with '/'\n  //! @param twindow_type time window type which can refer either to the\n  //!        mtime or the ctime\n  //! @param twindow_val time window timestamp\n  //! @param excl_xattr set of extended attributes which are not enforced and\n  //!        also not checked during the verification step\n  //!\n  //! @return 0 if successful, otherwise errno. It sets the global retc in case\n  //!         of error\n  //----------------------------------------------------------------------------\n  int BackupCreate(const std::string& backup_dir,\n                   const std::string& dst_url,\n                   const std::string& twindow_type,\n                   const std::string& twindow_val,\n                   const std::set<std::string>& excl_xattr);\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/ProcInterface.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file ProcInterface.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"ProcInterface.hh\"\n#include <XrdOuc/XrdOucEnv.hh>\n#include \"common/Constants.hh\"\n#include \"mgm/proc/admin/AccessCmd.hh\"\n#include \"mgm/proc/admin/ConfigCmd.hh\"\n#include \"mgm/proc/admin/ConvertCmd.hh\"\n#include \"mgm/proc/admin/DebugCmd.hh\"\n#include \"mgm/proc/admin/DevicesCmd.hh\"\n#include \"mgm/proc/admin/FsCmd.hh\"\n#include \"mgm/proc/admin/FsckCmd.hh\"\n#include \"mgm/proc/admin/GroupCmd.hh\"\n#include \"mgm/proc/admin/IoCmd.hh\"\n#include \"mgm/proc/admin/NodeCmd.hh\"\n#include \"mgm/proc/admin/NsCmd.hh\"\n#include \"mgm/proc/admin/QuotaCmd.hh\"\n#include \"mgm/proc/admin/SchedCmd.hh\"\n#include \"mgm/proc/admin/SpaceCmd.hh\"\n#include \"mgm/proc/admin/EvictCmd.hh\"\n#include \"mgm/proc/admin/FileRegisterCmd.hh\"\n#include \"mgm/proc/user/AclCmd.hh\"\n#include \"mgm/proc/user/DfCmd.hh\"\n#include \"mgm/proc/user/NewfindCmd.hh\"\n#include \"mgm/proc/user/RecycleCmd.hh\"\n#include \"mgm/proc/user/RmCmd.hh\"\n#include \"mgm/proc/user/RouteCmd.hh\"\n#include \"mgm/proc/user/TokenCmd.hh\"\n#include <google/protobuf/util/json_util.h>\n\nEOSMGMNAMESPACE_BEGIN\nthread_local eos::common::LogId ProcInterface::tlLogId;\nstd::mutex ProcInterface::mMutexCmds;\nstd::list<std::unique_ptr<IProcCommand>> ProcInterface::mCmdToDel;\nstd::unordered_map<std::string, std::unique_ptr<IProcCommand>>\n    ProcInterface::mMapCmds;\neos::common::ThreadPool ProcInterface::sProcThreads(\n  std::max(std::thread::hardware_concurrency() / 10, 64u),\n  std::max(std::thread::hardware_concurrency() / 4, 256u),\n  3, 2, 2, \"proc_pool\");\n\n//------------------------------------------------------------------------------\n// Factory method to get a ProcCommand object\n//------------------------------------------------------------------------------\nstd::unique_ptr<IProcCommand>\nProcInterface::GetProcCommand(const char* tident,\n                              eos::common::VirtualIdentity& vid,\n                              const char* path, const char* opaque,\n                              const char* log_id)\n{\n  tlLogId.SetLogId((log_id ? log_id : \"\"), vid, tident);\n  // Check if this is an already submitted command\n  std::unique_ptr<IProcCommand> pcmd = GetSubmittedCmd(tident);\n\n  if (pcmd) {\n    return pcmd;\n  }\n\n  if (!path || !opaque) {\n    // Return old style proc command which is populated in the open\n    pcmd.reset(new ProcCommand());\n  } else {\n    XrdOucEnv env(opaque);\n\n    // New proc command implementation using ProtocolBuffer objects\n    if (env.Get(\"mgm.cmd.proto\")) {\n      pcmd = HandleProtobufRequest(opaque, vid);\n    } else {\n      pcmd.reset(new ProcCommand());\n    }\n  }\n\n  return pcmd;\n}\n\n//------------------------------------------------------------------------------\n// Get asynchronous executing command, submitted earlier by the same client\n//------------------------------------------------------------------------------\nstd::unique_ptr<IProcCommand>\nProcInterface::GetSubmittedCmd(const char* tident)\n{\n  std::unique_ptr<IProcCommand> pcmd;\n  std::lock_guard<std::mutex> lock(mMutexCmds);\n  auto it = mMapCmds.find(tident);\n\n  if (it != mMapCmds.end()) {\n    pcmd.swap(it->second);\n    mMapCmds.erase(it);\n  }\n\n  return pcmd;\n}\n\n//------------------------------------------------------------------------------\n// Save asynchronous executing command, so we can stall the client and\n// return later on the result.\n//------------------------------------------------------------------------------\nbool\nProcInterface::SaveSubmittedCmd(const char* tident,\n                                std::unique_ptr<IProcCommand>&& pcmd)\n{\n  std::lock_guard<std::mutex> lock(mMutexCmds);\n\n  if (mMapCmds.count(tident)) {\n    return false;\n  }\n\n  mMapCmds.insert(std::make_pair(std::string(tident), std::move(pcmd)));\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Drop asynchronous executing command since the client disconnected\n//------------------------------------------------------------------------------\nvoid\nProcInterface::DropSubmittedCmd(const char* tident)\n{\n  std::lock_guard<std::mutex> lock(mMutexCmds);\n\n  // Drop any long running commands without connected clients\n  for (auto it = mCmdToDel.begin(); it != mCmdToDel.end(); /* empty */) {\n    if ((*it)->KillJob()) {\n      mCmdToDel.erase(it++);\n    } else {\n      ++it;\n    }\n  }\n\n  // Check if this client has any executing command\n  auto it = mMapCmds.find(tident);\n\n  if (it != mMapCmds.end()) {\n    std::unique_ptr<IProcCommand> tmp_cmd;\n    tmp_cmd.swap(it->second);\n    mMapCmds.erase(it);\n\n    if (!tmp_cmd->KillJob()) {\n      mCmdToDel.push_back(std::move(tmp_cmd));\n    }\n  }\n}\n\n//----------------------------------------------------------------------------\n// Handle protobuf request\n//----------------------------------------------------------------------------\nstd::unique_ptr<IProcCommand>\nProcInterface::HandleProtobufRequest(const char* opaque,\n                                     eos::common::VirtualIdentity& vid)\n{\n  using eos::console::RequestProto;\n  std::unique_ptr<IProcCommand> cmd;\n  std::ostringstream oss;\n  std::string raw_pb;\n  XrdOucEnv env(opaque);\n  const char* b64data = env.Get(\"mgm.cmd.proto\");\n\n  if (!eos::common::SymKey::Base64Decode(b64data, raw_pb)) {\n    oss << \"error: failed to base64decode request\";\n    eos_thread_err(\"%s\", oss.str().c_str());\n    return cmd;\n  }\n\n  eos::console::RequestProto req;\n\n  if (!req.ParseFromString(raw_pb)) {\n    oss << \"error: failed to deserialize ProtocolBuffer object: \"\n        << raw_pb;\n    eos_thread_err(\"%s\", oss.str().c_str());\n    return cmd;\n  }\n\n  return HandleProtobufRequest(req, vid);\n}\n\n\nstd::unique_ptr<IProcCommand>\nProcInterface::HandleProtobufRequest(eos::console::RequestProto& req,\n                                     eos::common::VirtualIdentity& vid)\n{\n  using eos::console::RequestProto;\n  std::unique_ptr<IProcCommand> cmd;\n  // Log the type of command that we received\n  std::string json_out;\n  (void) google::protobuf::util::MessageToJsonString(req, &json_out);\n  eos_thread_info(\"cmd_proto=%s\", json_out.c_str());\n\n  switch (req.command_case()) {\n  case RequestProto::kAcl:\n    cmd.reset(new AclCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kNs:\n    cmd.reset(new NsCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kFind:\n    cmd.reset(new NewfindCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kFs:\n    cmd.reset(new FsCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kRm:\n    cmd.reset(new RmCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kDf:\n    cmd.reset(new DfCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kDevices:\n    cmd.reset(new DevicesCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kToken:\n    cmd.reset(new TokenCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kEvict:\n    cmd.reset(new EvictCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kRoute:\n    cmd.reset(new RouteCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kRecycle:\n    cmd.reset(new RecycleCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kIo:\n    cmd.reset(new IoCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kGroup:\n    cmd.reset(new GroupCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kDebug:\n    cmd.reset(new DebugCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kNode:\n    cmd.reset(new NodeCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kFsck:\n    cmd.reset(new FsckCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kQuota:\n    cmd.reset(new QuotaCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kSched:\n    cmd.reset(new SchedCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kSpace:\n    cmd.reset(new SpaceCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kConfig:\n    cmd.reset(new ConfigCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kAccess:\n    cmd.reset(new AccessCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kConvert:\n    cmd.reset(new ConvertCmd(std::move(req), vid));\n    break;\n\n  case RequestProto::kRecord:\n    cmd.reset(new FileRegisterCmd(std::move(req), vid));\n    break;\n\n  default:\n    eos_static_err(\"error: unknown request type\");\n    break;\n  }\n\n  return cmd;\n}\n\n//----------------------------------------------------------------------------\n// Inspect protobuf request if this modifies the namespace\n//----------------------------------------------------------------------------\nbool\nProcInterface::ProtoIsWriteAccess(const char* opaque)\n{\n  using eos::console::RequestProto;\n  std::unique_ptr<IProcCommand> cmd;\n  std::ostringstream oss;\n  std::string raw_pb;\n  XrdOucEnv env(opaque);\n  const char* b64data = env.Get(\"mgm.cmd.proto\");\n\n  if (!eos::common::SymKey::Base64Decode(b64data, raw_pb)) {\n    oss << \"error: failed to base64decode request\";\n    eos_static_err(\"%s\", oss.str().c_str());\n    return false;\n  }\n\n  eos::console::RequestProto req;\n\n  if (!req.ParseFromString(raw_pb)) {\n    oss << \"error: failed to deserialize ProtocolBuffer object: \" << raw_pb;\n    eos_static_err(\"%s\", oss.str().c_str());\n    return false;\n  }\n\n  // Log the type of command that we received\n  std::string json_out;\n  (void)google::protobuf::util::MessageToJsonString(req, &json_out);\n\n  /* being conservative, true by default. Add false clauses explicitly */\n  switch (req.command_case()) {\n  case RequestProto::kNs:\n    switch (req.ns().subcmd_case()) {\n    case eos::console::NsProto::kQuota:\n      return true;\n\n    default:\n      return false;\n    }\n\n  case RequestProto::kFind: // @todo could perhaps, check --purge\n  case RequestProto::kIo:\n  case RequestProto::kDebug:\n  case RequestProto::kConfig:\n  case RequestProto::kToken:\n    return false;\n\n  // conditional on the subcommand\n  case RequestProto::kAcl:\n    switch (req.acl().op()) {\n    case eos::console::AclProto::NONE:\n    case eos::console::AclProto::LIST:\n      return false;\n\n    default:\n      return true;\n    }\n\n  case RequestProto::kRecycle:\n    switch (req.recycle().subcmd_case()) {\n    case eos::console::RecycleProto::kLs:\n      return false;\n\n    default:\n      return true;\n    }\n\n  case RequestProto::kFs:\n    switch (req.fs().subcmd_case()) {\n    case eos::console::FsProto::kClone:\n    case eos::console::FsProto::kCompare:\n    case eos::console::FsProto::kDumpmd:\n    case eos::console::FsProto::kLs:\n    case eos::console::FsProto::kStatus:\n      return false;\n\n    default:\n      return true;\n    }\n\n  case RequestProto::kRoute:\n    switch (req.route().subcmd_case()) {\n    case eos::console::RouteProto::kList:\n      return false;\n\n    default:\n      return true;\n    }\n\n  case RequestProto::kGroup:\n    switch (req.group().subcmd_case()) {\n    case eos::console::GroupProto::kLs:\n      return false;\n\n    default:\n      return true;\n    }\n\n  case RequestProto::kNode:\n    switch (req.node().subcmd_case()) {\n    case eos::console::NodeProto::kLs:\n    case eos::console::NodeProto::kStatus:\n      return false;\n\n    default:\n      return true;\n    }\n\n  case RequestProto::kQuota:\n    switch (req.quota().subcmd_case()) {\n    case eos::console::QuotaProto::kLs:\n    case eos::console::QuotaProto::kLsuser:\n      return false;\n\n    default:\n      return true;\n    }\n\n  case RequestProto::kSpace:\n    switch (req.space().subcmd_case()) {\n    case eos::console::SpaceProto::kLs:\n    case eos::console::SpaceProto::kStatus:\n    case eos::console::SpaceProto::kNodeGet:\n    case eos::console::SpaceProto::kTracker:\n    case eos::console::SpaceProto::kInspector:\n      return false;\n\n    case eos::console::SpaceProto::kGroupbalancer:\n      switch (req.space().groupbalancer().cmd_case()) {\n      case eos::console::SpaceProto::GroupBalancerProto::kStatus:\n        return false;\n\n      default:\n        return true;\n      }\n\n    case eos::console::SpaceProto::kGroupdrainer:\n      switch (req.space().groupdrainer().cmd_case()) {\n      case eos::console::SpaceProto::GroupDrainerProto::kStatus:\n        return false;\n\n      default:\n        return true;\n      }\n\n    default:\n      return true;\n    }\n\n  case RequestProto::kAccess:\n    switch (req.access().subcmd_case()) {\n    // we have always to allow to modify the access settings, otherwise we cannot remove write or global stalls\n    default:\n      return false;\n    }\n\n  // always true\n  case RequestProto::kRm:\n  case RequestProto::kEvict:\n  default:\n    return true;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check if a path indicates a proc command\n//------------------------------------------------------------------------------\nbool\nProcInterface::IsProcAccess(const char* path)\n{\n  return (strstr(path, \"/proc/\") == path);\n}\n\n//------------------------------------------------------------------------------\n// Check if a proc command is a 'write' command modifying state of an MGM\n//------------------------------------------------------------------------------\nbool\nProcInterface::IsWriteAccess(const char* path, const char* info)\n{\n  XrdOucString inpath = (path ? path : \"\");\n  XrdOucString ininfo = (info ? info : \"\");\n\n  if (!inpath.beginswith(\"/proc/\")) {\n    return false;\n  }\n\n  XrdOucEnv procEnv(ininfo.c_str());\n\n  // Filter protobuf requests\n  // @TODO: avoid parsing proto buf requests twice (here and later when running the request)\n  if (procEnv.Get(\"mgm.cmd.proto\")) {\n    return ProtoIsWriteAccess(ininfo.c_str());\n  }\n\n  XrdOucString cmd = procEnv.Get(\"mgm.cmd\");\n  XrdOucString subcmd = procEnv.Get(\"mgm.subcmd\");\n\n  // Filter here all namespace modifying proc messages\n  if (((cmd == \"file\") &&\n       ((subcmd == \"adjustreplica\") ||\n        (subcmd == \"drop\") ||\n        (subcmd == \"layout\") ||\n        (subcmd == \"touch\") ||\n        (subcmd == \"verify\") ||\n        (subcmd == \"version\") ||\n        (subcmd == \"versions\") ||\n        (subcmd == \"move\") ||\n        (subcmd == \"rename\"))) ||\n      ((cmd == \"attr\") &&\n       ((subcmd == \"set\") ||\n        (subcmd == \"rm\"))) ||\n      ((cmd == \"archive\") &&\n       ((subcmd == \"create\") ||\n        (subcmd == \"get\")  ||\n        (subcmd == \"purge\")  ||\n        (subcmd == \"delete\"))) ||\n      ((cmd == \"backup\")) ||\n      ((cmd == \"mkdir\")) ||\n      ((cmd == \"rmdir\")) ||\n      ((cmd == \"rm\")) ||\n      ((cmd == \"chown\")) ||\n      ((cmd == \"chmod\")) ||\n      ((cmd == \"fuseX\")) ||\n      ((cmd == \"fusex\")) ||\n      ((cmd == \"fs\") &&\n       ((subcmd == \"config\") ||\n        (subcmd == \"boot\") ||\n        (subcmd == \"dropdeletion\") ||\n        (subcmd == \"add\") ||\n        (subcmd == \"mv\") ||\n        (subcmd == \"rm\"))) ||\n      ((cmd == \"space\") &&\n       ((subcmd == \"config\") ||\n        (subcmd == \"define\") ||\n        (subcmd == \"set\") ||\n        (subcmd == \"rm\") ||\n        (subcmd == \"quota\"))) ||\n      ((cmd == \"node\") &&\n       ((subcmd == \"rm\") ||\n        (subcmd == \"config\") ||\n        (subcmd == \"set\") ||\n        (subcmd == \"register\") ||\n        (subcmd == \"gw\"))) ||\n      ((cmd == \"group\") &&\n       ((subcmd == \"set\") ||\n        (subcmd == \"rm\"))) ||\n      ((cmd == \"map\") &&\n       ((subcmd == \"link\") ||\n        (subcmd == \"unlink\"))) ||\n      ((cmd == \"quota\") &&\n       ((subcmd != \"ls\"))) ||\n      ((cmd == \"vid\") &&\n       ((subcmd != \"ls\"))) ||\n      ((cmd == \"transfer\") &&\n       ((subcmd != \"\"))) ||\n      ((cmd == \"recycle\") &&\n       ((subcmd != \"ls\")))) {\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Authorize a proc command based on the client's VID\n//------------------------------------------------------------------------------\nbool\nProcInterface::Authorize(const char* path, const char* info,\n                         eos::common::VirtualIdentity& vid,\n                         const XrdSecEntity* entity)\n{\n  XrdOucString inpath = path;\n\n  // Administrator access\n  if (inpath.beginswith(\"/proc/admin/\")) {\n    // Hosts with 'sss' authentication can run 'admin' commands\n    std::string protocol = entity ? entity->prot : \"\";\n\n    // We allow sss only with the daemon login is admin\n    if ((protocol == \"sss\") &&\n        (vid.hasUid(DAEMONUID))) {\n      return true;\n    }\n\n    // Root can do it\n    if (!vid.uid) {\n      return true;\n    }\n\n    // One has to be part of the virtual users 2(daemon)/3(adm)/4(adm)\n    return ((vid.hasUid(DAEMONUID)) ||\n            (vid.hasUid(eos::common::ADM_UID)) ||\n            (vid.hasGid(eos::common::ADM_GID)));\n  }\n\n  // User access\n  if (inpath.beginswith(\"/proc/user/\")) {\n    return true;\n  }\n\n  return false;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/ProcInterface.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file ProcInterface.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"mgm/proc/IProcCommand.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Mapping.hh\"\n#include \"common/ThreadPool.hh\"\n#include \"proc_fs.hh\"\n#include <unordered_map>\n\n//! Forward declarations\nclass XrdSecEntity;\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! @file   ProcInterface.hh\n//!\n//! @brief  ProcCommand class handling proc commands\n//!\n//! A proc command is identified by a user requesting to read a path like\n//! '/proc/user' or '/proc/admin'. These two options specify either user or\n//! admin commands. Admin commands can only be executed if a VID indicates\n//! membership in the admin group, root or in some cases 'sss' authenticated\n//! clients. A proc command is usually referenced with the tag 'mgm.cmd'.\n//! In some cases there are sub commands defined by 'mgm.subcmd'.\n//! Proc commands are executed in the 'open' function and the results\n//! are provided as stdOut,stdErr and a return code which is assembled in an\n//! opaque output stream with 3 keys indicating the three return objects.\n//! The resultstream is streamed by a client like a file read using 'xrdcp'\n//! issuing several read requests. On close the resultstream is freed.\n//!\n//! The implementations of user commands are found under mgm/proc/user/X.cc\n//! The implementations of admin commands are found under mgm/proc/admin/X.cc\n//! A new command has to be added to the if-else construct in the open function.\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n//! Class ProcInterface\n//------------------------------------------------------------------------------\nclass ProcInterface: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Factory method to get ProcCommand object\n  //!\n  //! @param tident client connection unique identifier\n  //! @param vid virtual id of the client\n  //! @param path input path for proc command\n  //! @param opaque input opaque information\n  //! @param log_id log id passed from the calling method\n  //!\n  //! @return ProcCommand object\n  //----------------------------------------------------------------------------\n  static std::unique_ptr<IProcCommand>\n  GetProcCommand(const char* tident, eos::common::VirtualIdentity& vid,\n                 const char* path = 0, const char* opaque = 0,\n                 const char* log_id = 0);\n\n  //----------------------------------------------------------------------------\n  //! Check if a path is requesting a proc command\n  //!\n  //! @param path input path for a proc command\n  //!\n  //! @return true if proc command otherwise false\n  //----------------------------------------------------------------------------\n  static bool IsProcAccess(const char* path);\n\n  //----------------------------------------------------------------------------\n  //! Check if a proto proc command contains a 'write' action on the instance\n  //----------------------------------------------------------------------------\n  static bool ProtoIsWriteAccess(const char* opaque);\n\n  //----------------------------------------------------------------------------\n  //! Check if a proc command contains a 'write' action on the instance\n  //!\n  //! @param path input arguments for proc command\n  //! @param info CGI for proc command\n  //!\n  //! @return true if write access otherwise false\n  //----------------------------------------------------------------------------\n  static bool IsWriteAccess(const char* path, const char* info);\n\n  //----------------------------------------------------------------------------\n  //! Authorize if the virtual ID can execute the requested command\n  //!\n  //! @param path specifies user or admin command path\n  //! @param info CGI providing proc arguments\n  //! @param vid virtual id of the client\n  //! @param entity security entity object\n  //!\n  //! @return true if authorized otherwise false\n  //----------------------------------------------------------------------------\n  static bool Authorize(const char* path, const char* info,\n                        eos::common::VirtualIdentity& vid,\n                        const XrdSecEntity* entity);\n\n  //----------------------------------------------------------------------------\n  //! Get asynchronous executing command, submitted earlier by the same client\n  //! who cames to pick up the result.\n  //!\n  //! @param tident unique client connection identifier\n  //!\n  //! @return proc client command object or nullptr\n  //----------------------------------------------------------------------------\n  static std::unique_ptr<IProcCommand> GetSubmittedCmd(const char* tident);\n\n  //----------------------------------------------------------------------------\n  //! Save asynchronous executing command, so we can stall the client and\n  //! return later on the result.\n  //!\n  //! @param tident unique client connection identifier\n  //! @param pcmd proc command object\n  //!\n  //! @return true if command saved, otherwise false\n  //----------------------------------------------------------------------------\n  static bool SaveSubmittedCmd(const char* tident,\n                               std::unique_ptr<IProcCommand>&& pcmd);\n\n  //----------------------------------------------------------------------------\n  //! Drop asynchronously executing command since the client disconnected\n  //!\n  //! @param tident unique client connection identifier\n  //----------------------------------------------------------------------------\n  static void DropSubmittedCmd(const char* tident);\n\n  ///! Pool of threads executing asynchronously long-running client commands\n  static eos::common::ThreadPool sProcThreads;\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Handle protobuf request\n  //!\n  //! @parm path input path of a proc command\n  //! @param opaque full opaque info containing the base64 protocol request\n  //! @param vid virtual identity of the client\n  //!\n  //! @return unique pointer to ProcCommand object or null otherwise\n  //----------------------------------------------------------------------------\n  static std::unique_ptr<IProcCommand>\n  HandleProtobufRequest(const char* opaque, eos::common::VirtualIdentity& vid);\n\n  //----------------------------------------------------------------------------\n  //! Handle protobuf request\n  //!\n  //! @parm protobuf request object\n  //! @param vid virtual identity of the client\n  //!\n  //! @return unique pointer to ProcCommand object or null otherwise\n  //----------------------------------------------------------------------------\n  static std::unique_ptr<IProcCommand>\n  HandleProtobufRequest(eos::console::RequestProto& req,\n                        eos::common::VirtualIdentity& vid);\n\n  //! Map of command id to async proc commands\n  static std::unordered_map<std::string, std::unique_ptr<IProcCommand>> mMapCmds;\n  //! List of running command without an associated client waiting for their\n  //! response\n  static std::list<std::unique_ptr<IProcCommand>> mCmdToDel;\n  static std::mutex mMutexCmds; ///< Mutex protecting access to the map above\n  static thread_local eos::common::LogId tlLogId; ///< Thread local log id\n};\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/Access.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/admin/Access.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <XrdOuc/XrdOucEnv.hh>\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/stat/Stat.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Access()\n{\n  gOFS->MgmStats.Add(\"AccessControl\", pVid->uid, pVid->gid, 1);\n  std::string user = \"\";\n  std::string group = \"\";\n  std::string host = \"\";\n  std::string domain = \"\";\n  std::string option = \"\";\n  std::string redirect = \"\";\n  std::string stall = \"\";\n  std::string type = \"\";\n  bool monitoring = false;\n  bool translate = true;\n  user = pOpaque->Get(\"mgm.access.user\") ? pOpaque->Get(\"mgm.access.user\") : \"\";\n  group = pOpaque->Get(\"mgm.access.group\") ? pOpaque->Get(\"mgm.access.group\") :\n          \"\";\n  host = pOpaque->Get(\"mgm.access.host\") ? pOpaque->Get(\"mgm.access.host\") : \"\";\n  option = pOpaque->Get(\"mgm.access.option\") ? pOpaque->Get(\"mgm.access.option\") :\n           \"\";\n  domain = pOpaque->Get(\"mgm.access.domain\") ? pOpaque->Get(\"mgm.access.domain\") :\n           \"\";\n  redirect = pOpaque->Get(\"mgm.access.redirect\") ?\n             pOpaque->Get(\"mgm.access.redirect\") : \"\";\n  stall = pOpaque->Get(\"mgm.access.stall\") ? pOpaque->Get(\"mgm.access.stall\") :\n          \"\";\n  type = pOpaque->Get(\"mgm.access.type\") ? pOpaque->Get(\"mgm.access.type\") : \"\";\n\n  if ((option.find(\"m\")) != std::string::npos) {\n    monitoring = true;\n  }\n\n  if ((option.find(\"n\")) != std::string::npos) {\n    translate = false;\n  }\n\n  if (mSubCmd == \"ban\") {\n    eos::common::RWMutexWriteLock lock(Access::gAccessMutex);\n\n    if (user.length()) {\n      int errc = 0;\n      uid_t uid = eos::common::Mapping::UserNameToUid(user, errc);\n\n      if (!errc) {\n        Access::gBannedUsers.insert(uid);\n\n        if (Access::StoreAccessConfig()) {\n          stdOut = \"success: ban user '\", stdOut += user.c_str();\n          stdOut += \"'\";\n          retc = 0;\n        } else {\n          stdErr = \"error: unable to store access configuration\";\n          retc = EIO;\n        }\n      } else {\n        stdErr = \"error: no such user - cannot ban '\";\n        stdErr += user.c_str();\n        stdErr += \"'\";\n        retc = EINVAL;\n      }\n    }\n\n    if (group.length()) {\n      int errc = 0;\n      gid_t gid = eos::common::Mapping::GroupNameToGid(group, errc);\n\n      if (!errc) {\n        Access::gBannedGroups.insert(gid);\n\n        if (Access::StoreAccessConfig()) {\n          stdOut = \"success: ban group '\", stdOut += group.c_str();\n          stdOut += \"'\";\n          retc = 0;\n        } else {\n          stdErr = \"error: unable to store access configuration\";\n          retc = EIO;\n        }\n      } else {\n        stdErr = \"error: no such group - cannot ban '\";\n        stdErr += group.c_str();\n        stdErr += \"'\";\n        retc = EINVAL;\n      }\n    }\n\n    if (host.length()) {\n      Access::gBannedHosts.insert(host);\n\n      if (Access::StoreAccessConfig()) {\n        stdOut = \"success: ban host '\";\n        stdOut += host.c_str();\n        stdOut += \"'\";\n        retc = 0;\n      } else {\n        stdErr = \"error: unable to store access configuration\";\n        retc = EIO;\n      }\n    }\n\n    if (domain.length()) {\n      Access::gBannedDomains.insert(domain);\n\n      if (Access::StoreAccessConfig()) {\n        stdOut = \"success: ban domain '\";\n        stdOut += domain.c_str();\n        stdOut += \"'\";\n        retc = 0;\n      } else {\n        stdErr = \"error: unable to store access configuration\";\n        retc = EIO;\n      }\n    }\n  }\n\n  if (mSubCmd == \"unban\") {\n    eos::common::RWMutexWriteLock lock(Access::gAccessMutex);\n\n    if (user.length()) {\n      int errc = 0;\n      uid_t uid = eos::common::Mapping::UserNameToUid(user, errc);\n\n      if (!errc) {\n        if (Access::gBannedUsers.count(uid)) {\n          Access::gBannedUsers.erase(uid);\n\n          if (Access::StoreAccessConfig()) {\n            stdOut = \"success: unban user '\", stdOut += user.c_str();\n            stdOut += \"'\";\n            retc = 0;\n          } else {\n            stdErr = \"error: unable to store access configuration\";\n            retc = EIO;\n          }\n        } else {\n          stdErr = \"error: user '\";\n          stdErr += user.c_str();\n          stdErr += \"' is not banned anyway!\";\n          retc = ENOENT;\n        }\n      } else {\n        stdErr = \"error: no such user - cannot ban '\";\n        stdErr += user.c_str();\n        stdErr += \"'\";\n        retc = EINVAL;\n      }\n    }\n\n    if (group.length()) {\n      int errc = 0;\n      gid_t gid = eos::common::Mapping::GroupNameToGid(group, errc);\n\n      if (!errc) {\n        if (Access::gBannedGroups.count(gid)) {\n          Access::gBannedGroups.erase(gid);\n\n          if (Access::StoreAccessConfig()) {\n            stdOut = \"success: unban group '\", stdOut += group.c_str();\n            stdOut += \"'\";\n            retc = 0;\n          } else {\n            stdErr = \"error: unable to store access configuration\";\n            retc = EIO;\n          }\n        } else {\n          stdErr = \"error: group '\";\n          stdErr += group.c_str();\n          stdErr += \"' is not banned anyway!\";\n          retc = ENOENT;\n        }\n      } else {\n        stdErr = \"error: no such group - cannot unban '\";\n        stdErr += group.c_str();\n        stdErr += \"'\";\n        retc = EINVAL;\n      }\n    }\n\n    if (host.length()) {\n      if (Access::gBannedHosts.count(host)) {\n        Access::gBannedHosts.erase(host);\n\n        if (Access::StoreAccessConfig()) {\n          stdOut = \"success: unban host '\";\n          stdOut += host.c_str();\n          stdOut += \"'\";\n          retc = 0;\n        } else {\n          stdErr = \"error: unable to store access configuration\";\n          retc = EIO;\n        }\n      } else {\n        stdErr = \"error: host '\";\n        stdErr += host.c_str();\n        stdErr += \"' is not banned anyway!\";\n        retc = ENOENT;\n      }\n    }\n\n    if (domain.length()) {\n      if (Access::gBannedDomains.count(domain)) {\n        Access::gBannedDomains.erase(domain);\n\n        if (Access::StoreAccessConfig()) {\n          stdOut = \"success: unban domain '\";\n          stdOut += domain.c_str();\n          stdOut += \"'\";\n          retc = 0;\n        } else {\n          stdErr = \"error: unable to store access configuration\";\n          retc = EIO;\n        }\n      } else {\n        stdErr = \"error: domain '\";\n        stdErr += domain.c_str();\n        stdErr += \"' is not banned anyway!\";\n        retc = ENOENT;\n      }\n    }\n  }\n\n  if (mSubCmd == \"allow\") {\n    eos::common::RWMutexWriteLock lock(Access::gAccessMutex);\n\n    if (user.length()) {\n      int errc = 0;\n      uid_t uid = eos::common::Mapping::UserNameToUid(user, errc);\n\n      if (!errc) {\n        Access::gAllowedUsers.insert(uid);\n\n        if (Access::StoreAccessConfig()) {\n          stdOut = \"success: allow user '\", stdOut += user.c_str();\n          stdOut += \"'\";\n          retc = 0;\n        } else {\n          stdErr = \"error: unable to store access configuration\";\n          retc = EIO;\n        }\n      } else {\n        stdErr = \"error: no such user - cannot allow '\";\n        stdErr += user.c_str();\n        stdErr += \"'\";\n        retc = EINVAL;\n      }\n    }\n\n    if (group.length()) {\n      int errc = 0;\n      gid_t gid = eos::common::Mapping::GroupNameToGid(group, errc);\n\n      if (!errc) {\n        Access::gAllowedGroups.insert(gid);\n\n        if (Access::StoreAccessConfig()) {\n          stdOut = \"success: allow group '\", stdOut += group.c_str();\n          stdOut += \"'\";\n          retc = 0;\n        } else {\n          stdErr = \"error: unable to store access configuration\";\n          retc = EIO;\n        }\n      } else {\n        stdErr = \"error: no such group - cannot allow '\";\n        stdErr += group.c_str();\n        stdErr += \"'\";\n        retc = EINVAL;\n      }\n    }\n\n    if (host.length()) {\n      Access::gAllowedHosts.insert(host);\n\n      if (Access::StoreAccessConfig())  {\n        stdOut = \"success: allow host '\";\n        stdOut += host.c_str();\n        stdOut += \"'\";\n        retc = 0;\n      } else {\n        stdErr = \"error: unable to store access configuration\";\n        retc = EIO;\n      }\n    }\n\n    if (domain.length()) {\n      Access::gAllowedDomains.insert(domain);\n\n      if (Access::StoreAccessConfig()) {\n        stdOut = \"success: allow domain '\";\n        stdOut += domain.c_str();\n        stdOut += \"'\";\n        retc = 0;\n      } else {\n        stdErr = \"error: unable to store access configuration\";\n        retc = EIO;\n      }\n    }\n  }\n\n  if (mSubCmd == \"unallow\") {\n    eos::common::RWMutexWriteLock lock(Access::gAccessMutex);\n\n    if (user.length()) {\n      int errc = 0;\n      uid_t uid = eos::common::Mapping::UserNameToUid(user, errc);\n\n      if (!errc) {\n        if (Access::gAllowedUsers.count(uid)) {\n          Access::gAllowedUsers.erase(uid);\n\n          if (Access::StoreAccessConfig()) {\n            stdOut = \"success: unallow user '\", stdOut += user.c_str();\n            stdOut += \"'\";\n            retc = 0;\n          } else {\n            stdErr = \"error: unable to store access configuration\";\n            retc = EIO;\n          }\n        } else {\n          stdErr = \"error: user '\";\n          stdErr += user.c_str();\n          stdErr += \"' is not allowed anyway!\";\n          retc = ENOENT;\n        }\n      } else {\n        stdErr = \"error: no such user - cannot unallow '\";\n        stdErr += user.c_str();\n        stdErr += \"'\";\n        retc = EINVAL;\n      }\n    }\n\n    if (group.length()) {\n      int errc = 0;\n      gid_t gid = eos::common::Mapping::GroupNameToGid(group, errc);\n\n      if (!errc) {\n        if (Access::gAllowedGroups.count(gid)) {\n          Access::gAllowedGroups.erase(gid);\n\n          if (Access::StoreAccessConfig()) {\n            stdOut = \"success: unallow group '\", stdOut += group.c_str();\n            stdOut += \"'\";\n            retc = 0;\n          } else {\n            stdErr = \"error: unable to store access configuration\";\n            retc = EIO;\n          }\n        } else {\n          stdErr = \"error: group '\";\n          stdErr += group.c_str();\n          stdErr += \"' is not allowed anyway!\";\n          retc = ENOENT;\n        }\n      } else {\n        stdErr = \"error: no such group - cannot unallow '\";\n        stdErr += group.c_str();\n        stdErr += \"'\";\n        retc = EINVAL;\n      }\n    }\n\n    if (host.length()) {\n      if (Access::gAllowedHosts.count(host)) {\n        Access::gAllowedHosts.erase(host);\n\n        if (Access::StoreAccessConfig()) {\n          stdOut = \"success: unallow host '\";\n          stdOut += host.c_str();\n          stdOut += \"'\";\n          retc = 0;\n        } else {\n          stdErr = \"error: unable to store access configuration\";\n          retc = EIO;\n        }\n      } else {\n        stdErr = \"error: host '\";\n        stdErr += host.c_str();\n        stdErr += \"' is not banned anyway!\";\n        retc = ENOENT;\n      }\n    }\n\n    if (domain.length()) {\n      if (Access::gAllowedDomains.count(domain)) {\n        Access::gAllowedDomains.erase(domain);\n\n        if (Access::StoreAccessConfig()) {\n          stdOut = \"success: unallow domain '\";\n          stdOut += domain.c_str();\n          stdOut += \"'\";\n          retc = 0;\n        } else {\n          stdErr = \"error: unable to store access configuration\";\n          retc = EIO;\n        }\n      } else {\n        stdErr = \"error: domain '\";\n        stdErr += domain.c_str();\n        stdErr += \"' is not banned anyway!\";\n        retc = ENOENT;\n      }\n    }\n  }\n\n  if (mSubCmd == \"set\") {\n    eos::common::RWMutexWriteLock lock(Access::gAccessMutex);\n\n    if (redirect.length() &&\n        ((type.length() == 0) || (type == \"r\") || (type == \"w\") ||\n         (type == \"ENONET\") || (type == \"ENOENT\") || (type == \"ENETUNREACH\"))) {\n      if (type == \"r\") {\n        Access::gRedirectionRules[std::string(\"r:*\")] = redirect;\n      } else {\n        if (type == \"w\") {\n          Access::gRedirectionRules[std::string(\"w:*\")] = redirect;\n        } else {\n          if (type == \"ENOENT\") {\n            Access::gRedirectionRules[std::string(\"ENOENT:*\")] = redirect;\n          } else {\n            if (type == \"ENONET\") {\n              Access::gRedirectionRules[std::string(\"ENONET:*\")] = redirect;\n            } else {\n              if (type == \"ENETUNREACH\") {\n                Access::gRedirectionRules[std::string(\"ENETUNREACH:*\")] = redirect;\n              } else {\n                Access::gRedirectionRules[std::string(\"*\")] = redirect;\n              }\n            }\n          }\n        }\n      }\n\n      if (Access::StoreAccessConfig()) {\n        stdOut = \"success: setting global redirection to '\";\n        stdOut += redirect.c_str();\n        stdOut += \"'\";\n\n        if (type.length()) {\n          stdOut += \" for <\";\n          stdOut += type.c_str();\n          stdOut += \">\";\n        }\n\n        retc = 0;\n      } else {\n        stdErr = \"error: unable to store access configuration\";\n        retc = EIO;\n      }\n    } else {\n      if (stall.length()) {\n        int desiredRate = atoi(stall.c_str());\n        bool rateIsValid = ((type.find(\"rate:\") == 0 && desiredRate >= 0) ||\n                            (desiredRate > 0));\n\n        if (rateIsValid &&\n            ((type.length() == 0) || (type == \"r\") || (type == \"w\") ||\n             ((type.find(\"rate:\") == 0)) || (type == \"ENONET\") ||\n             (type == \"ENOENT\") || (type == \"ENETUNREACH\"))) {\n          if (type == \"r\") {\n            Access::gStallRules[std::string(\"r:*\")] = stall;\n            Access::gStallComment[std::string(\"r:*\")] = mComment.c_str();\n          } else {\n            if (type == \"w\") {\n              Access::gStallRules[std::string(\"w:*\")] = stall;\n              Access::gStallComment[std::string(\"w:*\")] = mComment.c_str();\n            } else {\n              if ((type.find(\"rate:user:\") == 0) || (type.find(\"rate:group:\") == 0)) {\n                Access::gStallRules[std::string(type.c_str())] = stall;\n                Access::gStallComment[std::string(type.c_str())] = mComment.c_str();\n              } else {\n                if (type == \"ENONET\") {\n                  Access::gStallRules[std::string(\"ENONET:*\")] = stall;\n                  Access::gStallComment[std::string(\"ENONET:*\")] = mComment.c_str();\n                } else {\n                  if (type == \"ENOENT\") {\n                    Access::gStallRules[std::string(\"ENOENT:*\")] = stall;\n                    Access::gStallComment[std::string(\"ENOENT:*\")] = mComment.c_str();\n                  } else {\n                    if (type == \"ENETUNREACH\") {\n                      Access::gStallRules[std::string(\"ENETUNREACH:*\")] = stall;\n                      Access::gStallComment[std::string(\"ENETUNREACH:*\")] = mComment.c_str();\n                    } else {\n                      Access::gStallRules[std::string(\"*\")] = stall;\n                      Access::gStallComment[std::string(\"*\")] = mComment.c_str();\n                    }\n                  }\n                }\n              }\n            }\n          }\n\n          if (Access::StoreAccessConfig()) {\n            if (type.find(\"rate:\") == 0) {\n              stdOut += \"success: setting rate cutoff at \";\n              stdOut += stall.c_str();\n              stdOut += \" Hz for rate:<user|group>:<operation>=\";\n              stdOut += type.c_str();\n            } else {\n              stdOut += \"success: setting global stall to \";\n              stdOut += stall.c_str();\n              stdOut += \" seconds\";\n\n              if (type.length()) {\n                stdOut += \" for <\";\n                stdOut += type.c_str();\n                stdOut += \">\";\n              }\n            }\n\n            retc = 0;\n          } else {\n            stdErr = \"error: unable to store access configuration\";\n            retc = EIO;\n          }\n        } else {\n          stdErr = \"error: limit has to be >= 0 (value zero allowed just for 'rate:' limit)\";\n          retc = EINVAL;\n        }\n      } else {\n        stdErr = \"error: redirect or stall has to be defined\";\n        retc = EINVAL;\n      }\n    }\n  }\n\n  if (mSubCmd == \"rm\") {\n    eos::common::RWMutexWriteLock lock(Access::gAccessMutex);\n\n    if (redirect.length()) {\n      if ((Access::gRedirectionRules.count(std::string(\"*\")) &&\n           ((type.length() == 0))) ||\n          (Access::gRedirectionRules.count(std::string(\"r:*\")) && (type == \"r\")) ||\n          (Access::gRedirectionRules.count(std::string(\"w:*\")) && (type == \"w\")) ||\n          (Access::gRedirectionRules.count(std::string(\"ENONET:*\")) &&\n           (type == \"ENONET\")) ||\n          (Access::gRedirectionRules.count(std::string(\"ENOENT:*\")) &&\n           (type == \"ENOENT\")) ||\n          (Access::gRedirectionRules.count(std::string(\"ENETUNREACH:*\")) &&\n           (type == \"ENETUNREACH\"))) {\n        stdOut = \"success: removing global redirection\";\n\n        if (type.length()) {\n          stdOut += \" for <\";\n          stdOut += type.c_str();\n          stdOut += \">\";\n        }\n\n        if (type == \"r\") {\n          Access::gRedirectionRules.erase(std::string(\"r:*\"));\n        } else {\n          if (type == \"w\") {\n            Access::gRedirectionRules.erase(std::string(\"w:*\"));\n          } else {\n            if (type == \"ENONET\") {\n              Access::gRedirectionRules.erase(std::string(\"ENONET:*\"));\n            } else {\n              if (type == \"ENOENT\") {\n                Access::gRedirectionRules.erase(std::string(\"ENOENT:*\"));\n              } else {\n                if (type == \"ENETUNREACH\") {\n                  Access::gRedirectionRules.erase(std::string(\"ENETUNREACH:*\"));\n                } else {\n                  Access::gRedirectionRules.erase(std::string(\"*\"));\n                }\n              }\n            }\n          }\n        }\n\n        if (Access::StoreAccessConfig()) {\n          stdOut = \"success: removing redirection \";\n\n          if (type.length()) {\n            stdOut += \" for <\";\n            stdOut += type.c_str();\n            stdOut += \">\";\n          }\n\n          retc = 0;\n        } else {\n          stdErr = \"error: unable to store access configuration\";\n          retc = EIO;\n        }\n      } else {\n        stdErr = \"error: there is no global redirection defined\";\n        retc = EINVAL;\n      }\n    } else {\n      if ((Access::gStallRules.count(std::string(\"*\")) && ((type.length() == 0))) ||\n          (Access::gStallRules.count(std::string(\"r:*\")) && (type == \"r\")) ||\n          (Access::gStallRules.count(std::string(\"w:*\")) && (type == \"w\")) ||\n          (Access::gStallRules.count(std::string(\"ENONET:*\")) && (type == \"ENONET\")) ||\n          (Access::gStallRules.count(std::string(\"ENOENT:*\")) && (type == \"ENOENT\")) ||\n          (Access::gStallRules.count(std::string(\"ENETUNREACH:*\")) &&\n           (type == \"ENETUNREACH\")) ||\n          type.length()) {\n        stdOut = \"success: removing global stall time\";\n\n        if (type.length()) {\n          stdOut += \" for <\";\n          stdOut += type.c_str();\n          stdOut += \">\";\n        }\n\n        if (type == \"r\") {\n          Access::gStallRules.erase(std::string(\"r:*\"));\n          Access::gStallComment.erase(std::string(\"r:*\"));\n        } else {\n          if (type == \"w\") {\n            Access::gStallRules.erase(std::string(\"w:*\"));\n            Access::gStallComment.erase(std::string(\"w:*\"));\n          } else if (type == \"ENONET\") {\n            Access::gStallRules.erase(std::string(\"ENONET:*\"));\n            Access::gStallComment.erase(std::string(\"ENONET:*\"));\n          } else if (type == \"ENOENT\") {\n            Access::gStallRules.erase(std::string(\"ENOENT:*\"));\n            Access::gStallComment.erase(std::string(\"ENOENT:*\"));\n          } else if (type == \"ENETUNREACH\") {\n            Access::gStallRules.erase(std::string(\"ENETUNREACH:*\"));\n            Access::gStallComment.erase(std::string(\"ENETUNREACH:*\"));\n          } else {\n            if ((type.find(\"rate:user:\") == 0) || (type.find(\"rate:group:\") == 0)) {\n              Access::gStallRules.erase(std::string(type.c_str()));\n              Access::gStallComment.erase(std::string(type.c_str()));\n            } else {\n              Access::gStallRules.erase(std::string(\"*\"));\n              Access::gStallComment.erase(std::string(\"*\"));\n            }\n          }\n        }\n\n        if (Access::StoreAccessConfig()) {\n          if ((type.find(\"rate:user:\") == 0) || (type.find(\"rate:group:\") == 0)) {\n            stdOut = \"success: removing limit \";\n\n            if (type.length()) {\n              stdOut += \" for <\";\n              stdOut += type.c_str();\n              stdOut += \">\";\n            }\n          } else {\n            stdOut = \"success: removing stall \";\n\n            if (type.length()) {\n              stdOut += \"for <\";\n              stdOut += type.c_str();\n              stdOut += \">\";\n            }\n          }\n\n          retc = 0;\n        } else {\n          stdErr = \"error: unable to store access configuration\";\n          retc = EIO;\n        }\n      } else {\n        stdErr = \"error: redirect or stall has to be defined\";\n        retc = EINVAL;\n      }\n    }\n  }\n\n  if (mSubCmd == \"ls\") {\n    eos::common::RWMutexReadLock lock(Access::gAccessMutex);\n    std::set<uid_t>::const_iterator ituid;\n    std::set<gid_t>::const_iterator itgid;\n    std::set<std::string>::const_iterator ithost;\n    std::set<std::string>::const_iterator itdomain;\n    std::map<std::string, std::string>::const_iterator itred;\n    int cnt;\n\n    if (Access::gBannedUsers.size()) {\n      if (!monitoring) {\n        stdOut += \"# ....................................................................................\\n\";\n        stdOut += \"# Banned Users ...\\n\";\n        stdOut += \"# ....................................................................................\\n\";\n      }\n\n      cnt = 0;\n\n      for (ituid = Access::gBannedUsers.begin(); ituid != Access::gBannedUsers.end();\n           ituid++) {\n        cnt++;\n\n        if (monitoring) {\n          stdOut += \"user.banned=\";\n        } else {\n          char counter[16];\n          snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n          stdOut += \"[ \";\n          stdOut += counter;\n          stdOut += \" ] \";\n        }\n\n        if (!translate) {\n          stdOut += eos::common::Mapping::UidAsString(*ituid).c_str();\n        } else {\n          int terrc = 0;\n          stdOut += eos::common::Mapping::UidToUserName(*ituid, terrc).c_str();\n        }\n\n        stdOut += \"\\n\";\n      }\n    }\n\n    if (Access::gBannedGroups.size()) {\n      if (!monitoring) {\n        stdOut += \"# ....................................................................................\\n\";\n        stdOut += \"# Banned Groups...\\n\";\n        stdOut += \"# ....................................................................................\\n\";\n      }\n\n      cnt = 0;\n\n      for (itgid = Access::gBannedGroups.begin();\n           itgid != Access::gBannedGroups.end(); itgid++) {\n        cnt++;\n\n        if (monitoring) {\n          stdOut += \"group.banned=\";\n        } else {\n          char counter[16];\n          snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n          stdOut += \"[ \";\n          stdOut += counter;\n          stdOut += \" ] \";\n        }\n\n        if (!translate) {\n          stdOut += eos::common::Mapping::GidAsString(*itgid).c_str();\n        } else {\n          int terrc = 0;\n          stdOut += eos::common::Mapping::GidToGroupName(*itgid, terrc).c_str();\n        }\n\n        stdOut += \"\\n\";\n      }\n    }\n\n    if (Access::gBannedHosts.size()) {\n      if (!monitoring) {\n        stdOut += \"# ....................................................................................\\n\";\n        stdOut += \"# Banned Hosts ...\\n\";\n        stdOut += \"# ....................................................................................\\n\";\n      }\n\n      cnt = 0;\n\n      for (ithost = Access::gBannedHosts.begin();\n           ithost != Access::gBannedHosts.end(); ithost++) {\n        cnt++;\n\n        if (monitoring) {\n          stdOut += \"host.banned=\";\n        } else {\n          char counter[16];\n          snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n          stdOut += \"[ \";\n          stdOut += counter;\n          stdOut += \" ] \";\n        }\n\n        stdOut += ithost->c_str();\n        stdOut += \"\\n\";\n      }\n    }\n\n    if (Access::gBannedDomains.size()) {\n      if (!monitoring) {\n        stdOut += \"# ....................................................................................\\n\";\n        stdOut += \"# Banned Domains ...\\n\";\n        stdOut += \"# ....................................................................................\\n\";\n      }\n\n      cnt = 0;\n\n      for (itdomain = Access::gBannedDomains.begin();\n           itdomain != Access::gBannedDomains.end(); itdomain++) {\n        cnt++;\n\n        if (monitoring) {\n          stdOut += \"domain.banned=\";\n        } else {\n          char counter[16];\n          snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n          stdOut += \"[ \";\n          stdOut += counter;\n          stdOut += \" ] \";\n        }\n\n        stdOut += itdomain->c_str();\n        stdOut += \"\\n\";\n      }\n    }\n\n    if (Access::gAllowedUsers.size()) {\n      if (!monitoring) {\n        stdOut += \"# ....................................................................................\\n\";\n        stdOut += \"# Allowd Users ...\\n\";\n        stdOut += \"# ....................................................................................\\n\";\n      }\n\n      cnt = 0;\n\n      for (ituid = Access::gAllowedUsers.begin();\n           ituid != Access::gAllowedUsers.end(); ituid++) {\n        cnt++;\n\n        if (monitoring) {\n          stdOut += \"user.allowed=\";\n        } else {\n          char counter[16];\n          snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n          stdOut += \"[ \";\n          stdOut += counter;\n          stdOut += \" ] \";\n        }\n\n        if (!translate) {\n          stdOut += eos::common::Mapping::UidAsString(*ituid).c_str();\n        } else {\n          int terrc = 0;\n          stdOut += eos::common::Mapping::UidToUserName(*ituid, terrc).c_str();\n        }\n\n        stdOut += \"\\n\";\n      }\n    }\n\n    if (Access::gAllowedGroups.size()) {\n      if (!monitoring) {\n        stdOut += \"# ....................................................................................\\n\";\n        stdOut += \"# Allowed Groups...\\n\";\n        stdOut += \"# ....................................................................................\\n\";\n      }\n\n      cnt = 0;\n\n      for (itgid = Access::gAllowedGroups.begin();\n           itgid != Access::gAllowedGroups.end(); itgid++) {\n        cnt++;\n\n        if (monitoring) {\n          stdOut += \"group.allowed=\";\n        } else {\n          char counter[16];\n          snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n          stdOut += \"[ \";\n          stdOut += counter;\n          stdOut += \" ] \";\n        }\n\n        if (!translate) {\n          stdOut += eos::common::Mapping::GidAsString(*itgid).c_str();\n        } else {\n          int terrc = 0;\n          stdOut += eos::common::Mapping::GidToGroupName(*itgid, terrc).c_str();\n        }\n\n        stdOut += \"\\n\";\n      }\n    }\n\n    if (Access::gAllowedHosts.size()) {\n      if (!monitoring) {\n        stdOut += \"# ....................................................................................\\n\";\n        stdOut += \"# Allowed Hosts ...\\n\";\n        stdOut += \"# ....................................................................................\\n\";\n      }\n\n      cnt = 0;\n\n      for (ithost = Access::gAllowedHosts.begin();\n           ithost != Access::gAllowedHosts.end(); ithost++) {\n        cnt++;\n\n        if (monitoring) {\n          stdOut += \"host.allowed=\";\n        } else {\n          char counter[16];\n          snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n          stdOut += \"[ \";\n          stdOut += counter;\n          stdOut += \" ] \";\n        }\n\n        stdOut += ithost->c_str();\n        stdOut += \"\\n\";\n      }\n    }\n\n    if (Access::gAllowedDomains.size()) {\n      if (!monitoring) {\n        stdOut += \"# ....................................................................................\\n\";\n        stdOut += \"# Allowed Domains ...\\n\";\n        stdOut += \"# ....................................................................................\\n\";\n      }\n\n      cnt = 0;\n\n      for (itdomain = Access::gAllowedDomains.begin();\n           itdomain != Access::gAllowedDomains.end(); itdomain++) {\n        cnt++;\n\n        if (monitoring) {\n          stdOut += \"domain.allowed=\";\n        } else {\n          char counter[16];\n          snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n          stdOut += \"[ \";\n          stdOut += counter;\n          stdOut += \" ] \";\n        }\n\n        stdOut += itdomain->c_str();\n        stdOut += \"\\n\";\n      }\n    }\n\n    if (Access::gRedirectionRules.size()) {\n      if (!monitoring) {\n        stdOut += \"# ....................................................................................\\n\";\n        stdOut += \"# Redirection Rules ...\\n\";\n        stdOut += \"# ....................................................................................\\n\";\n      }\n\n      cnt = 0;\n\n      for (itred = Access::gRedirectionRules.begin();\n           itred != Access::gRedirectionRules.end(); itred++) {\n        cnt++;\n\n        if (monitoring) {\n          stdOut += \"redirect.\";\n          stdOut += itred->first.c_str();\n          stdOut += \"=\";\n        } else {\n          char counter[1024];\n          snprintf(counter, sizeof(counter) - 1, \"[ %02d ] %32s => \", cnt,\n                   itred->first.c_str());\n          stdOut += counter;\n        }\n\n        stdOut += itred->second.c_str();\n        stdOut += \"\\n\";\n      }\n    }\n\n    if (Access::gStallRules.size()) {\n      if (!monitoring) {\n        stdOut += \"# ....................................................................................\\n\";\n        stdOut += \"# Stall Rules ...\\n\";\n        stdOut += \"# ....................................................................................\\n\";\n      }\n\n      cnt = 0;\n\n      for (itred = Access::gStallRules.begin(); itred != Access::gStallRules.end();\n           itred++) {\n        cnt++;\n\n        if (monitoring) {\n          stdOut += \"stall.\";\n          stdOut += itred->first.c_str();\n          stdOut += \"=\";\n        } else {\n          char counter[1024];\n          snprintf(counter, sizeof(counter) - 1, \"[ %02d ] %32s => \", cnt,\n                   itred->first.c_str());\n          stdOut += counter;\n        }\n\n        stdOut += itred->second.c_str();\n\n        if (monitoring) {\n          stdOut += \" mComment=\\\"\";\n          stdOut += Access::gStallComment[itred->first].c_str();\n          stdOut += \"\\\"\";\n        } else {\n          stdOut += \"\\t\";\n          stdOut += Access::gStallComment[itred->first].c_str();\n        }\n\n        stdOut += \"\\n\";\n      }\n    }\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/AccessCmd.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: AccessCmd.cc\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#include \"AccessCmd.hh\"\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/Constants.hh\"\n#include <XrdOuc/XrdOucEnv.hh>\n#include <cctype>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Process a rule key by converting the given username to a uid if necessary\n//------------------------------------------------------------------------------\nstd::string\nProcessRuleKey(const std::string& key)\n{\n  std::string new_key = key;\n\n  if (new_key.find(\"threads:\") == 0) {\n    size_t pos = new_key.find(\":\");\n\n    if (pos + 1 == new_key.length()) {\n      return std::string();\n    }\n\n    std::string target = new_key.substr(pos + 1);\n    eos::common::trim(target);\n\n    if (target.empty()) {\n      return std::string();\n    }\n\n    if ((target != \"max\") && (target != \"*\")) {\n      // Check if target is a username and then try to convert it\n      if (std::find_if(begin(target), end(target), [](unsigned char c) {\n      return std::isalpha(c);\n      }) != target.end()) {\n        int errc = 0;\n        uid_t uid_target = eos::common::Mapping::UserNameToUid(target, errc);\n\n        if (errc) {\n          return std::string();\n        }\n\n        new_key = \"threads:\" + std::to_string(uid_target);\n      }\n    }\n  }\n\n  return new_key;\n}\n\n//------------------------------------------------------------------------------\n// Method implementing the specific behavior of the command executed by the\n// asynchronous thread\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nAccessCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  eos::console::AccessProto access = mReqProto.access();\n\n  if ((mVid.uid != 0) && (!mVid.hasUid(eos::common::ADM_UID)) && (!mVid.hasGid(eos::common::ADM_GID)) &&\n      (!mVid.sudoer)) {\n    // root and admins only\n    reply.set_std_out(\"\");\n    reply.set_std_err(\"error: you are not an access administrator!\\\"\");\n    reply.set_retc(EPERM);\n    return reply;\n  }\n\n  switch (access.subcmd_case()) {\n  case eos::console::AccessProto::kLs :\n    LsSubcmd(access.ls(), reply);\n    break;\n\n  case eos::console::AccessProto::kRm :\n    RmSubcmd(access.rm(), reply);\n    break;\n\n  case eos::console::AccessProto::kSet :\n    SetSubcmd(access.set(), reply);\n    break;\n\n  case eos::console::AccessProto::kBan :\n    BanSubcmd(access.ban(), reply);\n    break;\n\n  case eos::console::AccessProto::kUnban :\n    UnbanSubcmd(access.unban(), reply);\n    break;\n\n  case eos::console::AccessProto::kAllow :\n    AllowSubcmd(access.allow(), reply);\n    break;\n\n  case eos::console::AccessProto::kUnallow :\n    UnallowSubcmd(access.unallow(), reply);\n    break;\n\n  case eos::console::AccessProto::kStallhosts :\n    StallhostsSubcmd(access.stallhosts(), reply);\n    break;\n\n  default :\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: not supported\");\n  }\n\n  return reply;\n}\n\n//------------------------------------------------------------------------------\n// Execute ls subcommand\n//------------------------------------------------------------------------------\nvoid AccessCmd::LsSubcmd(const eos::console::AccessProto_LsProto& ls,\n                         eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out {\"\"};\n  std::ostringstream std_err {\"\"};\n  int ret_c {0};\n  gOFS->MgmStats.Add(\"AccessControl\", mVid.uid, mVid.gid, 1);\n  eos::common::RWMutexReadLock lock(Access::gAccessMutex);\n  int cnt {0};\n\n  if (!Access::gBannedUsers.empty()) {\n    if (!ls.monitoring()) {\n      std_out <<\n              \"# ....................................................................................\\n\";\n      std_out << \"# Banned Users ...\\n\";\n      std_out <<\n              \"# ....................................................................................\\n\";\n    }\n\n    cnt = 0;\n\n    for (auto ituid = Access::gBannedUsers.begin();\n         ituid != Access::gBannedUsers.end(); ituid++) {\n      cnt++;\n\n      if (!ls.monitoring()) {\n        char counter[16];\n        snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n        std_out << \"[ \" << counter << \" ] \";\n      } else {\n        std_out << \"user.banned=\";\n      }\n\n      if (ls.id2name()) {\n        std_out << eos::common::Mapping::UidAsString(*ituid).c_str();\n      } else {\n        int terrc = 0;\n        std_out << eos::common::Mapping::UidToUserName(*ituid, terrc).c_str();\n      }\n\n      std_out << '\\n';\n    }\n  }\n\n  if (!Access::gBannedGroups.empty()) {\n    if (!ls.monitoring()) {\n      std_out <<\n              \"# ....................................................................................\\n\";\n      std_out << \"# Banned Groups...\\n\";\n      std_out <<\n              \"# ....................................................................................\\n\";\n    }\n\n    cnt = 0;\n\n    for (auto itgid = Access::gBannedGroups.begin();\n         itgid != Access::gBannedGroups.end(); itgid++) {\n      ++cnt;\n\n      if (!ls.monitoring()) {\n        char counter[16];\n        snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n        std_out << \"[ \" << counter << \" ] \";\n      } else {\n        std_out << \"group.banned=\";\n      }\n\n      if (ls.id2name()) {\n        std_out << eos::common::Mapping::GidAsString(*itgid).c_str();\n      } else {\n        int terrc = 0;\n        std_out << eos::common::Mapping::GidToGroupName(*itgid, terrc).c_str();\n      }\n\n      std_out << '\\n';\n    }\n  }\n\n  if (!Access::gBannedHosts.empty()) {\n    if (!ls.monitoring()) {\n      std_out <<\n              \"# ....................................................................................\\n\";\n      std_out << \"# Banned Hosts ...\\n\";\n      std_out <<\n              \"# ....................................................................................\\n\";\n    }\n\n    cnt = 0;\n\n    for (auto ithost = Access::gBannedHosts.begin();\n         ithost != Access::gBannedHosts.end(); ithost++) {\n      cnt++;\n\n      if (!ls.monitoring()) {\n        char counter[16];\n        snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n        std_out << \"[ \" << counter << \" ] \";\n      } else {\n        std_out << \"host.banned=\";\n      }\n\n      std_out << ithost->c_str() << '\\n';\n    }\n  }\n\n  if (!Access::gBannedDomains.empty()) {\n    if (!ls.monitoring()) {\n      std_out <<\n              \"# ....................................................................................\\n\";\n      std_out << \"# Banned Domains ...\\n\";\n      std_out <<\n              \"# ....................................................................................\\n\";\n    }\n\n    cnt = 0;\n\n    for (auto itdomain = Access::gBannedDomains.begin();\n         itdomain != Access::gBannedDomains.end(); ++itdomain) {\n      ++cnt;\n\n      if (!ls.monitoring()) {\n        char counter[16];\n        snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n        std_out << \"[ \" << counter << \" ] \";\n      } else {\n        std_out << \"domain.banned=\";\n      }\n\n      std_out << itdomain->c_str() << '\\n';\n    }\n  }\n\n  if (!Access::gBannedTokens.empty()) {\n    if (!ls.monitoring()) {\n      std_out <<\n              \"# ....................................................................................\\n\";\n      std_out << \"# Banned Tokens ...\\n\";\n      std_out <<\n              \"# ....................................................................................\\n\";\n    }\n\n    cnt = 0;\n\n    for (auto ittoken = Access::gBannedTokens.begin();\n         ittoken != Access::gBannedTokens.end(); ++ittoken) {\n      ++cnt;\n\n      if (!ls.monitoring()) {\n        char counter[16];\n        snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n        std_out << \"[ \" << counter << \" ] \";\n      } else {\n        std_out << \"tokens.banned=\";\n      }\n\n      std_out << ittoken->c_str() << '\\n';\n    }\n  }\n\n  if (!Access::gAllowedUsers.empty()) {\n    if (!ls.monitoring()) {\n      std_out <<\n              \"# ....................................................................................\\n\";\n      std_out << \"# Allowd Users ...\\n\";\n      std_out <<\n              \"# ....................................................................................\\n\";\n    }\n\n    cnt = 0;\n\n    for (auto ituid = Access::gAllowedUsers.begin();\n         ituid != Access::gAllowedUsers.end(); ++ituid) {\n      ++cnt;\n\n      if (!ls.monitoring()) {\n        char counter[16];\n        snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n        std_out << \"[ \" << counter << \" ] \";\n      } else {\n        std_out << \"user.allowed=\";\n      }\n\n      if (ls.id2name()) {\n        std_out << eos::common::Mapping::UidAsString(*ituid).c_str();\n      } else {\n        int terrc = 0;\n        std_out << eos::common::Mapping::UidToUserName(*ituid, terrc).c_str();\n      }\n\n      std_out << '\\n';\n    }\n  }\n\n  if (!Access::gAllowedGroups.empty()) {\n    if (!ls.monitoring()) {\n      std_out <<\n              \"# ....................................................................................\\n\";\n      std_out << \"# Allowed Groups...\\n\";\n      std_out <<\n              \"# ....................................................................................\\n\";\n    }\n\n    cnt = 0;\n\n    for (auto itgid = Access::gAllowedGroups.begin();\n         itgid != Access::gAllowedGroups.end(); itgid++) {\n      ++cnt;\n\n      if (!ls.monitoring()) {\n        char counter[16];\n        snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n        std_out << \"[ \" << counter << \" ] \";\n      } else {\n        std_out << \"group.allowed=\";\n      }\n\n      if (ls.id2name()) {\n        std_out << eos::common::Mapping::GidAsString(*itgid).c_str();\n      } else {\n        int terrc = 0;\n        std_out << eos::common::Mapping::GidToGroupName(*itgid, terrc).c_str();\n      }\n\n      std_out << '\\n';\n    }\n  }\n\n  if (!Access::gAllowedHosts.empty()) {\n    if (!ls.monitoring()) {\n      std_out <<\n              \"# ....................................................................................\\n\";\n      std_out << \"# Allowed Hosts ...\\n\";\n      std_out <<\n              \"# ....................................................................................\\n\";\n    }\n\n    cnt = 0;\n\n    for (auto ithost = Access::gAllowedHosts.begin();\n         ithost != Access::gAllowedHosts.end(); ithost++) {\n      cnt++;\n\n      if (!ls.monitoring()) {\n        char counter[16];\n        snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n        std_out << \"[ \" << counter << \" ] \";\n      } else {\n        std_out << \"host.allowed=\";\n      }\n\n      std_out << ithost->c_str() << '\\n';\n    }\n  }\n\n  if (!Access::gAllowedDomains.empty()) {\n    if (!ls.monitoring()) {\n      std_out <<\n              \"# ....................................................................................\\n\";\n      std_out << \"# Allowed Domains ...\\n\";\n      std_out <<\n              \"# ....................................................................................\\n\";\n    }\n\n    cnt = 0;\n\n    for (auto itdomain = Access::gAllowedDomains.begin();\n         itdomain != Access::gAllowedDomains.end(); ++itdomain) {\n      ++cnt;\n\n      if (!ls.monitoring()) {\n        char counter[16];\n        snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n        std_out << \"[ \" << counter << \" ] \";\n      } else {\n        std_out << \"domain.allowed=\";\n      }\n\n      std_out << itdomain->c_str() << '\\n';\n    }\n  }\n\n  if (!Access::gAllowedTokens.empty()) {\n    if (!ls.monitoring()) {\n      std_out <<\n              \"# ....................................................................................\\n\";\n      std_out << \"# Allowed Tokens ...\\n\";\n      std_out <<\n              \"# ....................................................................................\\n\";\n    }\n\n    cnt = 0;\n\n    for (auto ittoken = Access::gAllowedTokens.begin();\n         ittoken != Access::gAllowedTokens.end(); ++ittoken) {\n      ++cnt;\n\n      if (!ls.monitoring()) {\n        char counter[16];\n        snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n        std_out << \"[ \" << counter << \" ] \";\n      } else {\n        std_out << \"tokens.allowed=\";\n      }\n\n      std_out << ittoken->c_str() << '\\n';\n    }\n  }\n\n  if (!Access::gRedirectionRules.empty()) {\n    if (!ls.monitoring()) {\n      std_out <<\n              \"# ....................................................................................\\n\";\n      std_out << \"# Redirection Rules ...\\n\";\n      std_out <<\n              \"# ....................................................................................\\n\";\n    }\n\n    cnt = 0;\n\n    for (auto itred = Access::gRedirectionRules.begin();\n         itred != Access::gRedirectionRules.end(); ++itred) {\n      ++cnt;\n\n      if (!ls.monitoring()) {\n        char counter[1024];\n        snprintf(counter, sizeof(counter) - 1, \"[ %02d ] %32s => \", cnt,\n                 itred->first.c_str());\n        std_out << counter;\n      } else {\n        std_out << \"redirect.\" << itred->first.c_str() << \"=\";\n      }\n\n      std_out << itred->second.c_str() << '\\n';\n    }\n  }\n\n  if (!Access::gStallRules.empty()) {\n    if (!ls.monitoring()) {\n      std_out <<\n              \"# ....................................................................................\\n\";\n      std_out << \"# Stall Rules ...\\n\";\n      std_out <<\n              \"# ....................................................................................\\n\";\n    }\n\n    cnt = 0;\n\n    for (auto itred = Access::gStallRules.begin();\n         itred != Access::gStallRules.end(); ++itred) {\n      ++cnt;\n\n      if (!ls.monitoring()) {\n        char counter[1024];\n        snprintf(counter, sizeof(counter) - 1, \"[ %02d ] %32s => \", cnt,\n                 itred->first.c_str());\n        std_out << counter;\n      } else {\n        std_out << \"stall.\" << itred->first.c_str() << \"=\";\n      }\n\n      std_out << itred->second.c_str();\n\n      if (!ls.monitoring()) {\n        std_out << \"\\t\" << Access::gStallComment[itred->first].c_str();\n      } else {\n        std_out << \" mComment=\\\"\" << Access::gStallComment[itred->first].c_str() <<\n                \"\\\"\";\n      }\n\n      std_out << '\\n';\n    }\n  }\n\n  if (!Access::gStallHosts.empty()) {\n    if (!ls.monitoring()) {\n      std_out <<\n              \"# ....................................................................................\\n\";\n      std_out << \"# Hosts in the stall white list ...\\n\";\n      std_out <<\n              \"# ....................................................................................\\n\";\n    }\n\n    cnt = 0;\n\n    for (auto ithost = Access::gStallHosts.begin();\n         ithost != Access::gStallHosts.end(); ithost++) {\n      cnt++;\n\n      if (!ls.monitoring()) {\n        char counter[16];\n        snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n        std_out << \"[ \" << counter << \" ] \";\n      } else {\n        std_out << \"host.stallhosts=\";\n      }\n\n      std_out << ithost->c_str() << '\\n';\n    }\n  }\n\n  if (!Access::gNoStallHosts.empty()) {\n    if (!ls.monitoring()) {\n      std_out <<\n              \"# ....................................................................................\\n\";\n      std_out << \"# Hosts in the no-stall black list ...\\n\";\n      std_out <<\n              \"# ....................................................................................\\n\";\n    }\n\n    cnt = 0;\n\n    for (auto ithost = Access::gNoStallHosts.begin();\n         ithost != Access::gNoStallHosts.end(); ithost++) {\n      cnt++;\n\n      if (!ls.monitoring()) {\n        char counter[16];\n        snprintf(counter, sizeof(counter) - 1, \"%02d\", cnt);\n        std_out << \"[ \" << counter << \" ] \";\n      } else {\n        std_out << \"host.nostallhosts=\";\n      }\n\n      std_out << ithost->c_str() << '\\n';\n    }\n  }\n\n  reply.set_std_out(std_out.str());\n  reply.set_std_err(std_err.str());\n  reply.set_retc(ret_c);\n}\n\n//------------------------------------------------------------------------------\n// Execute rm subcommand\n//------------------------------------------------------------------------------\nvoid AccessCmd::RmSubcmd(const eos::console::AccessProto_RmProto& rm,\n                         eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out {\"\"};\n  std::ostringstream std_err {\"\"};\n  int ret_c = 0;\n  gOFS->MgmStats.Add(\"AccessControl\", mVid.uid, mVid.gid, 1);\n  eos::common::RWMutexWriteLock lock(Access::gAccessMutex);\n\n  switch (rm.rule()) {\n  case eos::console::AccessProto_RmProto::REDIRECT : {\n    if (!((Access::gRedirectionRules.count(\"*\") && rm.key().empty()) ||\n          (Access::gRedirectionRules.count(\"r:*\") && rm.key() == \"r\") ||\n          (Access::gRedirectionRules.count(\"w:*\") && rm.key() == \"w\") ||\n          (Access::gRedirectionRules.count(\"ENONET:*\") && rm.key() == \"ENONET\") ||\n          (Access::gRedirectionRules.count(\"ENOENT:*\") && rm.key() == \"ENOENT\") ||\n          (Access::gRedirectionRules.count(\"ENETUNREACH:*\") &&\n           rm.key() == \"ENETUNREACH\"))) {\n      reply.set_std_err(\"error: there is no global redirection defined with \"\n                        \"such key: '\" + rm.key() + '\\'');\n      reply.set_retc(EINVAL);\n      return;\n    } else {\n      std_out << \"success: removing global redirection\";\n\n      if (!rm.key().empty()) {\n        std_out << \" for <\" << rm.key() << \">\";\n      }\n\n      if (rm.key().empty()) {\n        Access::gRedirectionRules.erase(\"*\");\n      } else {\n        Access::gRedirectionRules.erase(rm.key() + \":*\");\n      }\n\n      lock.Release();\n      eos::common::RWMutexReadLock lock(Access::gAccessMutex);\n\n      if (!Access::StoreAccessConfig()) {\n        reply.set_std_err(\"error: unable to store access configuration\");\n        reply.set_retc(EIO);\n        return;\n      }\n    }\n  }\n  break;\n\n  case eos::console::AccessProto_RmProto::STALL :\n  case eos::console::AccessProto_RmProto::LIMIT : {\n    if (!((Access::gStallRules.count(\"*\") && rm.key().empty()) ||\n          (Access::gStallRules.count(\"r:*\") && (rm.key() == \"r\")) ||\n          (Access::gStallRules.count(\"w:*\") && (rm.key() == \"w\")) ||\n          (Access::gStallRules.count(\"ENONET:*\") && (rm.key() == \"ENONET\")) ||\n          (Access::gStallRules.count(\"ENOENT:*\") && (rm.key() == \"ENOENT\")) ||\n          (Access::gStallRules.count(\"ENETUNREACH:*\") && (rm.key() == \"ENETUNREACH\")) ||\n          !rm.key().empty())) {\n      reply.set_std_err(\"error: there is no global redirection defined with \"\n                        \"such key: '\" + rm.key() + '\\'');\n      reply.set_retc(EINVAL);\n      return;\n    } else {\n      std_out << \"success: removing global \";\n\n      if (!rm.key().empty()) {\n        if ((rm.key().find(\"rate:user:\") == 0) ||\n            (rm.key().find(\"rate:group:\") == 0) ||\n            (rm.key().find(\"threads:\") == 0)) {\n          std_out << \"limit\";\n        } else {\n          std_out << \"stall\";\n        }\n\n        std_out << \" for <\" << rm.key() << \">\";\n      }\n\n      if ((rm.key().find(\"rate:user:\") == 0) ||\n          (rm.key().find(\"rate:group:\") == 0) ||\n          (rm.key().find(\"threads:\") == 0)) {\n        const std::string rule_key = ProcessRuleKey(rm.key());\n\n        if (rule_key.empty()) {\n          reply.set_std_err(\"error: malformed access rule\");\n          reply.set_retc(EINVAL);\n          return;\n        }\n\n        // Remove by uid\n        Access::gStallRules.erase(rule_key);\n        Access::gStallComment.erase(rule_key);\n        // Also remove by username to cover old bug\n        Access::gStallRules.erase(rm.key());\n        Access::gStallComment.erase(rm.key());\n      } else if (rm.key().empty()) {\n        Access::gStallRules.erase(\"*\");\n        Access::gStallComment.erase(\"*\");\n      } else {\n        Access::gStallRules.erase(rm.key() + \":*\");\n        Access::gStallComment.erase(rm.key() + \":*\");\n      }\n\n      lock.Release();\n      eos::common::RWMutexReadLock rlock(Access::gAccessMutex);\n\n      if (!Access::StoreAccessConfig()) {\n        reply.set_std_err(\"error: unable to store access configuration\");\n        reply.set_retc(EIO);\n        return;\n      }\n    }\n  }\n  break;\n\n  default : // should never happen\n    reply.set_std_err(\"error: rule not found, it should be one of redirect|stall|limit\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  reply.set_std_out(std_out.str());\n  reply.set_std_err(std_err.str());\n  reply.set_retc(ret_c);\n}\n\n//------------------------------------------------------------------------------\n// Execute set subcommand\n//------------------------------------------------------------------------------\nvoid AccessCmd::SetSubcmd(const eos::console::AccessProto_SetProto& set,\n                          eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out {\"\"};\n  std::ostringstream std_err {\"\"};\n  int ret_c = 0;\n  gOFS->MgmStats.Add(\"AccessControl\", mVid.uid, mVid.gid, 1);\n  eos::common::RWMutexWriteLock lock(Access::gAccessMutex);\n\n  switch (set.rule()) {\n  case eos::console::AccessProto_SetProto::REDIRECT : {\n    if (!(set.key().empty() || (set.key() == \"r\") || (set.key() == \"w\") ||\n          (set.key() == \"ENONET\") || (set.key() == \"ENOENT\") ||\n          (set.key() == \"ENETUNREACH\"))) {\n      reply.set_std_err(\"error: there is no redirection to set with such \"\n                        \"key: '\" + set.key() + '\\'');\n      reply.set_retc(EINVAL);\n      return;\n    } else {\n      std_out << \"success: setting global redirection to '\" << set.target() << '\\'';\n\n      if (!set.key().empty()) {\n        std_out << \" for <\" << set.key() << \">\";\n      }\n\n      if (set.key().empty()) {\n        Access::gRedirectionRules[\"*\"] = set.target();\n      } else {\n        Access::gRedirectionRules[set.key() + \":*\"] = set.target();\n      }\n\n      lock.Release();\n      eos::common::RWMutexReadLock rlock(Access::gAccessMutex);\n\n      if (!Access::StoreAccessConfig()) {\n        reply.set_std_err(\"error: unable to store access configuration\");\n        reply.set_retc(EIO);\n        return;\n      }\n    }\n  }\n  break;\n\n  case eos::console::AccessProto_SetProto::STALL :\n  case eos::console::AccessProto_SetProto::LIMIT : {\n    int target;\n\n    try {\n      target = (std::stoi(set.target()));\n    } catch (const std::exception& e) {\n      reply.set_std_err(\"error: target must be an integer equal or greater than 0 (value zero allowed just for 'rate:' limit)\");\n      reply.set_retc(EINVAL);\n      return;\n    }\n\n    if (!((set.key().find(\"rate:\") == 0 && target >= 0) || (target > 0))) {\n      reply.set_std_err(\"error: target must be an integer equal or greater than 0 (value zero allowed just for 'rate:' limit)\");\n      reply.set_retc(EINVAL);\n      return;\n    }\n\n    if (set.key().find(\"rate:\") == 0) {\n      std_out << \"success: setting rate cutoff at \" << set.target()\n              << \" Hz for rate:<user|group>:<operation>=\" << set.key();\n    }  else if (set.key().find(\"threads:\") == 0) {\n      std_out << \"success: setting thread limit at \" << set.target()\n              << \" for \" << set.key();\n    } else {\n      if (!(set.key().empty() || (set.key() == \"r\") || (set.key() == \"w\") ||\n            (set.key() == \"ENONET\") || (set.key() == \"ENOENT\") ||\n            (set.key() == \"ENETUNREACH\"))) {\n        reply.set_std_err(\"error: there is no stall to set with such \"\n                          \"key: '\" + set.key() + '\\'');\n        reply.set_retc(EINVAL);\n        return;\n      }\n\n      std_out << \"success: setting global stall to \" << set.target() << \" seconds\";\n\n      if (!set.key().empty()) {\n        std_out << \" for <\" << set.key() << \">\";\n      }\n    }\n\n    if ((set.key().find(\"rate:user:\") == 0) ||\n        (set.key().find(\"rate:group:\") == 0) ||\n        (set.key().find(\"threads:\") == 0)) {\n      const std::string rule_key = ProcessRuleKey(set.key());\n\n      if (rule_key.empty()) {\n        reply.set_std_err(\"error: malformed access rule\");\n        reply.set_retc(EINVAL);\n        return;\n      }\n\n      Access::gStallRules[rule_key] = set.target();\n      Access::gStallComment[rule_key] = mReqProto.comment();\n    } else if (set.key().empty()) {\n      Access::gStallRules[\"*\"] = set.target();\n      Access::gStallComment[\"*\"] = mReqProto.comment();\n    } else {\n      Access::gStallRules[set.key() + \":*\"] = set.target();\n      Access::gStallComment[set.key() + \":*\"] = mReqProto.comment();\n    }\n\n    lock.Release();\n    eos::common::RWMutexReadLock rlock(Access::gAccessMutex);\n\n    if (!Access::StoreAccessConfig()) {\n      reply.set_std_err(\"error: unable to store access configuration\");\n      reply.set_retc(EIO);\n      return;\n    }\n  }\n  break;\n\n  default : // should never happen\n    reply.set_std_err(\"error: rule not found, it should be one of redirect|stall|limit\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  reply.set_std_out(std_out.str());\n  reply.set_std_err(std_err.str());\n  reply.set_retc(ret_c);\n}\n\n\n// @note (faluchet) all this machinery (and more) should be done with templates and funct programming... Later\n\nvoid AccessCmd::aux(const std::string& sid, std::ostringstream& std_out,\n                    std::ostringstream& std_err, int& ret_c)\n{\n  std::string saction;\n\n  switch (mReqProto.access().subcmd_case()) {\n  case eos::console::AccessProto::kBan:\n    saction = \"ban\";\n    break;\n\n  case eos::console::AccessProto::kUnban:\n    saction = \"unban\";\n    break;\n\n  case eos::console::AccessProto::kAllow:\n    saction = \"allow\";\n    break;\n\n  case eos::console::AccessProto::kUnallow:\n    saction = \"unallow\";\n    break;\n\n  case eos::console::AccessProto::kStallhosts:\n    saction = \"(un-)stallhosts\";\n    break;\n\n  default :\n    ;\n  }\n\n  eos::common::RWMutexReadLock rlock(Access::gAccessMutex);\n\n  if (Access::StoreAccessConfig()) {\n    std_out << \"success: \" << saction << \" '\" << sid << '\\'';\n    ret_c = 0;\n  } else {\n    std_err << \"error: unable to store access configuration\";\n    ret_c = EIO;\n  }\n}\n\n//----------------------------------------------------------------------------\n// Execute ban subcommand\n//----------------------------------------------------------------------------\nvoid AccessCmd::BanSubcmd(const eos::console::AccessProto_BanProto& ban,\n                          eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out{\"\"};\n  std::ostringstream std_err{\"\"};\n  int ret_c = 0;\n  int errc = 0;\n  gOFS->MgmStats.Add(\"AccessControl\", mVid.uid, mVid.gid, 1);\n  eos::common::RWMutexWriteLock lock(Access::gAccessMutex);\n\n  switch (ban.idtype()) {\n  case eos::console::AccessProto_BanProto::USER\n      : {\n      uid_t uid = eos::common::Mapping::UserNameToUid(ban.id(), errc);\n\n      if (!errc) {\n        Access::gBannedUsers.insert(uid);\n        lock.Release();\n        aux(ban.id(), std_out, std_err, ret_c);\n      } else {\n        std_err << \"error: no such user - cannot ban '\" << ban.id() << '\\'';\n        ret_c = EINVAL;\n      }\n    }\n    break;\n\n  case eos::console::AccessProto_BanProto::GROUP\n      : {\n      gid_t gid = eos::common::Mapping::GroupNameToGid(ban.id(), errc);\n\n      if (!errc) {\n        Access::gBannedGroups.insert(gid);\n        lock.Release();\n        aux(ban.id(), std_out, std_err, ret_c);\n      } else {\n        std_err << \"error: no such group - cannot ban '\" << ban.id() << '\\'';\n        ret_c = EINVAL;\n      }\n    }\n    break;\n\n  case eos::console::AccessProto_BanProto::HOST\n      : {\n      Access::gBannedHosts.insert(ban.id());\n      lock.Release();\n      aux(ban.id(), std_out, std_err, ret_c);\n    }\n    break;\n\n  case eos::console::AccessProto_BanProto::DOMAINNAME\n      : {\n      Access::gBannedDomains.insert(ban.id());\n      lock.Release();\n      aux(ban.id(), std_out, std_err, ret_c);\n    }\n    break;\n\n  case eos::console::AccessProto_BanProto::TOKEN\n      : {\n      Access::gBannedTokens.insert(ban.id());\n      lock.Release();\n      aux(ban.id(), std_out, std_err, ret_c);\n    }\n    break;\n\n  default:\n    ;\n  }\n\n  reply.set_std_out(std_out.str());\n  reply.set_std_err(std_err.str());\n  reply.set_retc(ret_c);\n}\n\n//------------------------------------------------------------------------------\n// Execute unban subcommand\n//------------------------------------------------------------------------------\nvoid AccessCmd::UnbanSubcmd(const eos::console::AccessProto_UnbanProto& unban,\n                            eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out{\"\"};\n  std::ostringstream std_err{\"\"};\n  int ret_c = 0;\n  int errc = 0;\n  gOFS->MgmStats.Add(\"AccessControl\", mVid.uid, mVid.gid, 1);\n  eos::common::RWMutexWriteLock lock(Access::gAccessMutex);\n\n  switch (unban.idtype()) {\n  case eos::console::AccessProto_UnbanProto::USER\n      : {\n      uid_t uid = eos::common::Mapping::UserNameToUid(unban.id(), errc);\n\n      if (!errc) {\n        if (Access::gBannedUsers.count(uid)) {\n          Access::gBannedUsers.erase(uid);\n          lock.Release();\n          aux(unban.id(), std_out, std_err, ret_c);\n        } else {\n          std_err << \"error: user '\" << unban.id() << \"' is not banned anyway\";\n          ret_c = ENOENT;\n        }\n      } else {\n        std_err << \"error: no such user - cannot unban '\" << unban.id() << '\\'';\n        ret_c = EINVAL;\n      }\n    }\n    break;\n\n  case eos::console::AccessProto_UnbanProto::GROUP\n      : {\n      gid_t gid = eos::common::Mapping::GroupNameToGid(unban.id(), errc);\n\n      if (!errc) {\n        if (Access::gBannedGroups.count(gid)) {\n          Access::gBannedGroups.erase(gid);\n          lock.Release();\n          aux(unban.id(), std_out, std_err, ret_c);\n        } else {\n          std_err << \"error: group '\" << unban.id() << \"' is not banned anyway\";\n          ret_c = ENOENT;\n        }\n      } else {\n        std_err << \"error: no such group - cannot unban '\" << unban.id() << '\\'';\n        ret_c = EINVAL;\n      }\n    }\n    break;\n\n  case eos::console::AccessProto_UnbanProto::HOST\n      : {\n      if (Access::gBannedHosts.count(unban.id())) {\n        Access::gBannedHosts.erase(unban.id());\n        lock.Release();\n        aux(unban.id(), std_out, std_err, ret_c);\n        lock.Grab(Access::gAccessMutex);\n      } else {\n        std_err << \"error: host '\" << unban.id() << \"' is not banned anyway\";\n        ret_c = ENOENT;\n      }\n    }\n    break;\n\n  case eos::console::AccessProto_UnbanProto::DOMAINNAME\n      : {\n      if (Access::gBannedDomains.count(unban.id())) {\n        Access::gBannedDomains.erase(unban.id());\n        lock.Release();\n        aux(unban.id(), std_out, std_err, ret_c);\n        lock.Grab(Access::gAccessMutex);\n      } else {\n        std_err << \"error: domain '\" << unban.id() << \"' is not banned anyway\";\n        ret_c = ENOENT;\n      }\n    }\n    break;\n\n  case eos::console::AccessProto_UnbanProto::TOKEN\n      : {\n      if (Access::gBannedTokens.count(unban.id())) {\n        Access::gBannedTokens.erase(unban.id());\n        lock.Release();\n        aux(unban.id(), std_out, std_err, ret_c);\n        lock.Grab(Access::gAccessMutex);\n      } else {\n        std_err << \"error: token '\" << unban.id() << \"' is not banned anyway\";\n        ret_c = ENOENT;\n      }\n    }\n    break;\n\n  default:\n    ;\n  }\n\n  reply.set_std_out(std_out.str());\n  reply.set_std_err(std_err.str());\n  reply.set_retc(ret_c);\n}\n\n//------------------------------------------------------------------------------\n// Execute allow subcommand\n//------------------------------------------------------------------------------\nvoid AccessCmd::AllowSubcmd(const eos::console::AccessProto_AllowProto& allow,\n                            eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out{\"\"};\n  std::ostringstream std_err{\"\"};\n  int ret_c = 0;\n  int errc = 0;\n  gOFS->MgmStats.Add(\"AccessControl\", mVid.uid, mVid.gid, 1);\n  eos::common::RWMutexWriteLock lock(Access::gAccessMutex);\n\n  switch (allow.idtype()) {\n  case eos::console::AccessProto_AllowProto::USER\n      : {\n      uid_t uid = eos::common::Mapping::UserNameToUid(allow.id(), errc);\n\n      if (!errc) {\n        Access::gAllowedUsers.insert(uid);\n        lock.Release();\n        aux(allow.id(), std_out, std_err, ret_c);\n      } else {\n        std_err << \"error: no such user - cannot allow '\" << allow.id() << '\\'';\n        ret_c = EINVAL;\n      }\n    }\n    break;\n\n  case eos::console::AccessProto_AllowProto::GROUP\n      : {\n      gid_t gid = eos::common::Mapping::GroupNameToGid(allow.id(), errc);\n\n      if (!errc) {\n        Access::gAllowedGroups.insert(gid);\n        lock.Release();\n        aux(allow.id(), std_out, std_err, ret_c);\n      } else {\n        std_err << \"error: no such group - cannot allow '\" << allow.id() << '\\'';\n        ret_c = EINVAL;\n      }\n    }\n    break;\n\n  case eos::console::AccessProto_AllowProto::HOST\n      : {\n      Access::gAllowedHosts.insert(allow.id());\n      lock.Release();\n      aux(allow.id(), std_out, std_err, ret_c);\n    }\n    break;\n\n  case eos::console::AccessProto_AllowProto::DOMAINNAME\n      : {\n      Access::gAllowedDomains.insert(allow.id());\n      lock.Release();\n      aux(allow.id(), std_out, std_err, ret_c);\n    }\n    break;\n\n  case eos::console::AccessProto_AllowProto::TOKEN\n      : {\n      Access::gAllowedTokens.insert(allow.id());\n      lock.Release();\n      aux(allow.id(), std_out, std_err, ret_c);\n    }\n    break;\n\n  default:\n    ;\n  }\n\n  reply.set_std_out(std_out.str());\n  reply.set_std_err(std_err.str());\n  reply.set_retc(ret_c);\n}\n\n//------------------------------------------------------------------------------\n// Execute unallow subcommand\n//------------------------------------------------------------------------------\nvoid\nAccessCmd::UnallowSubcmd(const eos::console::AccessProto_UnallowProto& unallow,\n                         eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out{\"\"};\n  std::ostringstream std_err{\"\"};\n  int ret_c = 0;\n  int errc = 0;\n  gOFS->MgmStats.Add(\"AccessControl\", mVid.uid, mVid.gid, 1);\n  eos::common::RWMutexWriteLock lock(Access::gAccessMutex);\n\n  switch (unallow.idtype()) {\n  case eos::console::AccessProto_UnallowProto::USER\n      : {\n      uid_t uid = eos::common::Mapping::UserNameToUid(unallow.id(), errc);\n\n      if (!errc) {\n        if (Access::gAllowedUsers.count(uid)) {\n          Access::gAllowedUsers.erase(uid);\n          lock.Release();\n          aux(unallow.id(), std_out, std_err, ret_c);\n        } else {\n          std_err << \"error: user '\" << unallow.id() << \"' is not allowed anyway\";\n          ret_c = ENOENT;\n        }\n      } else {\n        std_err << \"error: no such user - cannot unallow '\" << unallow.id() << '\\'';\n        ret_c = EINVAL;\n      }\n    }\n    break;\n\n  case eos::console::AccessProto_UnallowProto::GROUP\n      : {\n      gid_t gid = eos::common::Mapping::GroupNameToGid(unallow.id(), errc);\n\n      if (!errc) {\n        if (Access::gAllowedGroups.count(gid)) {\n          Access::gAllowedGroups.erase(gid);\n          lock.Release();\n          aux(unallow.id(), std_out, std_err, ret_c);\n        } else {\n          std_err << \"error: group '\" << unallow.id() << \"' is not allowed anyway\";\n          ret_c = ENOENT;\n        }\n      } else {\n        std_err << \"error: no such group - cannot unallow '\" << unallow.id() << '\\'';\n        ret_c = EINVAL;\n      }\n    }\n    break;\n\n  case eos::console::AccessProto_UnallowProto::HOST\n      : {\n      if (Access::gAllowedHosts.count(unallow.id())) {\n        Access::gAllowedHosts.erase(unallow.id());\n        lock.Release();\n        aux(unallow.id(), std_out, std_err, ret_c);\n      } else {\n        std_err << \"error: host '\" << unallow.id() << \"' is not allowed anyway\";\n        ret_c = ENOENT;\n      }\n    }\n    break;\n\n  case eos::console::AccessProto_UnallowProto::DOMAINNAME\n      : {\n      if (Access::gAllowedDomains.count(unallow.id())) {\n        Access::gAllowedDomains.erase(unallow.id());\n        lock.Release();\n        aux(unallow.id(), std_out, std_err, ret_c);\n      } else {\n        std_err << \"error: domain '\" << unallow.id() << \"' is not allowed anyway\";\n        ret_c = ENOENT;\n      }\n    }\n    break;\n\n  case eos::console::AccessProto_UnallowProto::TOKEN\n      : {\n      if (Access::gAllowedTokens.count(unallow.id())) {\n        Access::gAllowedTokens.erase(unallow.id());\n        lock.Release();\n        aux(unallow.id(), std_out, std_err, ret_c);\n      } else {\n        std_err << \"error: domain '\" << unallow.id() << \"' is not allowed anyway\";\n        ret_c = ENOENT;\n      }\n    }\n    break;\n\n  default:\n    ;\n  }\n\n  reply.set_std_out(std_out.str());\n  reply.set_std_err(std_err.str());\n  reply.set_retc(ret_c);\n}\n\n//----------------------------------------------------------------------------\n// Execute stallhostssubcommand\n//----------------------------------------------------------------------------\nvoid AccessCmd::StallhostsSubcmd(const\n                                 eos::console::AccessProto_StallHostsProto& stall,\n                                 eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out{\"\"};\n  std::ostringstream std_err{\"\"};\n  int ret_c = 0;\n  gOFS->MgmStats.Add(\"AccessControl\", mVid.uid, mVid.gid, 1);\n  eos::common::RWMutexWriteLock lock(Access::gAccessMutex);\n\n  switch (stall.type()) {\n  case eos::console::AccessProto_StallHostsProto::STALL :\n    switch (stall.op()) {\n    case eos::console::AccessProto_StallHostsProto::ADD: {\n      if (Access::gStallHosts.count(stall.hostpattern())) {\n        ret_c = EEXIST;\n        std_err << \"error: entry exists already in the stall list\\n\";\n      } else {\n        if (Access::gNoStallHosts.count(stall.hostpattern())) {\n          ret_c = EEXIST;\n          std_err << \"error: this pattern is in the no-stall list!\\n\";\n        } else {\n          Access::gStallHosts.insert(stall.hostpattern());\n        }\n      }\n\n      lock.Release();\n\n      if (!ret_c) {\n        aux(stall.hostpattern(), std_out, std_err, ret_c);\n      }\n\n      break;\n    }\n\n    case eos::console::AccessProto_StallHostsProto::REMOVE: {\n      if (!Access::gStallHosts.count(stall.hostpattern())) {\n        ret_c = ENOENT;\n        std_err << \"error: this pattern is not in the stall list\\n\";\n      } else {\n        Access::gStallHosts.erase(stall.hostpattern());\n      }\n\n      lock.Release();\n\n      if (!ret_c) {\n        aux(stall.hostpattern(), std_out, std_err, ret_c);\n      }\n\n      break;\n    }\n\n    default:\n      lock.Release();\n      ret_c = EINVAL;\n    }\n\n    break;\n\n  case eos::console::AccessProto_StallHostsProto::NOSTALL :\n    switch (stall.op()) {\n    case eos::console::AccessProto_StallHostsProto::ADD: {\n      if (Access::gNoStallHosts.count(stall.hostpattern())) {\n        ret_c = EEXIST;\n        std_err << \"error: entry exists already in the no-stall list\\n\";\n      } else {\n        if (Access::gStallHosts.count(stall.hostpattern())) {\n          ret_c = EEXIST;\n          std_err << \"error: this pattern is in the stall list!\\n\";\n        } else {\n          Access::gNoStallHosts.insert(stall.hostpattern());\n        }\n      }\n\n      lock.Release();\n      aux(stall.hostpattern(), std_out, std_err, ret_c);\n      break;\n    }\n\n    case eos::console::AccessProto_StallHostsProto::REMOVE: {\n      if (!Access::gNoStallHosts.count(stall.hostpattern())) {\n        ret_c = ENOENT;\n        std_err << \"error: this pattern is not in the nostall list\\n\";\n      } else {\n        Access::gNoStallHosts.erase(stall.hostpattern());\n      }\n\n      lock.Release();\n      aux(stall.hostpattern(), std_out, std_err, ret_c);\n      break;\n    }\n\n    default:\n      lock.Release();\n      ret_c = EINVAL;\n      aux(stall.hostpattern(), std_out, std_err, ret_c);\n    }\n\n  default:\n    break;\n  }\n\n  reply.set_std_out(std_out.str());\n  reply.set_std_err(std_err.str());\n  reply.set_retc(ret_c);\n}\n\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/AccessCmd.hh",
    "content": "//------------------------------------------------------------------------------\n// @file: AccessCmd.hh\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"proto/Access.pb.h\"\n#include \"mgm/proc/ProcCommand.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n\n//------------------------------------------------------------------------------\n//! Process a rule key by converting the given username to a uid if necessary\n//!\n//! @param key input key\n//!\n//! @return processed key to be used internally by the MGM\n//------------------------------------------------------------------------------\nstd::string ProcessRuleKey(const std::string& key);\n\n\n//------------------------------------------------------------------------------\n//! Class AccessCmd - class handling config commands\n//------------------------------------------------------------------------------\nclass AccessCmd: public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit AccessCmd(eos::console::RequestProto&& req,\n                     eos::common::VirtualIdentity& vid)\n    : IProcCommand(std::move(req), vid, false)\n  {\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~AccessCmd() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Execute ls subcommand\n  //!\n  //! @param ls ls subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void LsSubcmd(const eos::console::AccessProto_LsProto& ls,\n                eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute rm subcommand\n  //!\n  //! @param rm rm subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void RmSubcmd(const eos::console::AccessProto_RmProto& rm,\n                eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute set subcommand\n  //!\n  //! @param set set subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void SetSubcmd(const eos::console::AccessProto_SetProto& set,\n                 eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute ban subcommand\n  //!\n  //! @param ban ban subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void BanSubcmd(const eos::console::AccessProto_BanProto& ban,\n                 eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute unban subcommand\n  //!\n  //! @param unban unban subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void UnbanSubcmd(const eos::console::AccessProto_UnbanProto& unban,\n                   eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute allow subcommand\n  //!\n  //! @param allow allow subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void AllowSubcmd(const eos::console::AccessProto_AllowProto& allow,\n                   eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute unallow subcommand\n  //!\n  //! @param unallow unallow subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void UnallowSubcmd(const eos::console::AccessProto_UnallowProto& unallow,\n                     eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute stallhost subcommand\n  //!\n  //! @param stallhosts subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void StallhostsSubcmd(const eos::console::AccessProto_StallHostsProto& stall,\n                        eos::console::ReplyProto& reply);\n\n  void aux(const std::string& sid,\n           std::ostringstream& std_out, std::ostringstream& std_err,\n           int& ret_c);\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/Backup.cc",
    "content": "//------------------------------------------------------------------------------\n// File: Backup.cc\n// Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2014 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Path.hh\"\n#include \"mgm/proc/admin/Backup.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include <XrdCl/XrdClCopyProcess.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n#include <string>\n#include <iomanip>\n#include <fstream>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//                            *** TwindowFilter ***\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// File/dir filter constructor\n//------------------------------------------------------------------------------\nTwindowFilter::TwindowFilter(const std::string& twindow_type,\n                             const std::string& twindow_val):\n  mTwindowType(twindow_type),\n  mTwindowVal(twindow_val)\n{}\n\n//----------------------------------------------------------------------------\n// Filter file entry if it is a version file i.e. contains \".sys.v#.\" or if\n// it is ouside the timewindow\n//----------------------------------------------------------------------------\nbool\nTwindowFilter::FilterOutFile(\n  const std::map<std::string, std::string>& entry_info)\n{\n  if (mTwindowType.empty() || mTwindowVal.empty()) {\n    return false;\n  }\n\n  std::string path = entry_info.find(\"file\")->second;\n\n  // Filter if this is a version file\n  if (path.find(\".sys.v#.\") != std::string::npos) {\n    return true;\n  }\n\n  const auto iter = entry_info.find(mTwindowType);\n\n  if (iter == entry_info.end()) {\n    return false;\n  }\n\n  char* end;\n  std::string svalue = iter->second;\n  float value = strtof(svalue.c_str(), &end);\n  float ref_value = strtof(mTwindowVal.c_str(), &end);\n\n  // Filter if ouside the timewindow\n  if (value < ref_value) {\n    return true;\n  }\n\n  // Extract directory/ies that need to stay - we try to remove empty dirs\n  size_t pos = 0;\n\n  while ((pos = path.rfind('/')) != std::string::npos) {\n    path = path.substr(0, pos + 1);\n    mSetDirs.insert(path);\n    path = path.substr(0, pos);\n  }\n\n  // Always add root directory\n  mSetDirs.insert(\"./\");\n  return false;\n}\n\n//----------------------------------------------------------------------------\n// Filter the current dir entry\n//----------------------------------------------------------------------------\nbool\nTwindowFilter::FilterOutDir(const std::string& path)\n{\n  if (mTwindowType.empty() || mTwindowVal.empty()) {\n    return false;\n  }\n\n  if (mSetDirs.find(path) != mSetDirs.end()) {\n    return false;\n  }\n\n  eos_debug(\"filter out directory=%s\", path.c_str());\n  return true;\n}\n\n//------------------------------------------------------------------------------\n//                            *** ProcCommand ***\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Backup command\n//------------------------------------------------------------------------------\nint ProcCommand::Backup()\n{\n  std::string src_surl = (pOpaque->Get(\"mgm.backup.src\") ?\n                          pOpaque->Get(\"mgm.backup.src\") : \"\");\n  std::string dst_surl = (pOpaque->Get(\"mgm.backup.dst\") ?\n                          pOpaque->Get(\"mgm.backup.dst\") : \"\");\n\n  // Make sure the source and destiantion directories end with \"/\"\n  if (*src_surl.rbegin() != '/') {\n    src_surl += '/';\n  }\n\n  if (*dst_surl.rbegin() != '/') {\n    dst_surl += '/';\n  }\n\n  XrdCl::URL src_url(src_surl);\n  XrdCl::URL dst_url(dst_surl);\n  std::ostringstream oss;\n\n  if (!src_url.IsValid() || !dst_url.IsValid()) {\n    stdErr = \"error: both backup source and destination must be valid XRootD URLs\";\n    retc = EINVAL;\n    return SFS_OK;\n  }\n\n  // If local path we assume local EOS instance\n  if (src_url.GetProtocol() == \"file\") {\n    if (*src_surl.rbegin() != '/') {\n      src_surl += '/';\n    }\n\n    oss << \"root://\" << gOFS->ManagerId << \"/\" << src_surl;\n    src_url.FromString(oss.str());\n    src_surl = src_url.GetURL();\n  }\n\n  if (dst_url.GetProtocol() == \"file\") {\n    if (*dst_surl.rbegin() != '/') {\n      dst_surl += '/';\n    }\n\n    oss.clear();\n    oss.str(\"\");\n    oss << \"root://\" << gOFS->ManagerId << \"/\" << dst_surl;\n    dst_url.FromString(oss.str());\n    dst_surl = dst_url.GetURL();\n  }\n\n  // Create backup file and copy it to the destination location\n  std::string twindow_type = (pOpaque->Get(\"mgm.backup.ttime\") ?\n                              pOpaque->Get(\"mgm.backup.ttime\") : \"\");\n  std::string twindow_val = (pOpaque->Get(\"mgm.backup.vtime\") ?\n                             pOpaque->Get(\"mgm.backup.vtime\") : \"\");\n\n  if (!twindow_type.empty()  && (twindow_type != \"ctime\")\n      && (twindow_type != \"mtime\")) {\n    stdErr = \"error: unknown time window type, should be ctime/mtime\";\n    retc = EINVAL;\n    return SFS_OK;\n  }\n\n  // Get the list of excluded extended attribute values which are not enforced\n  // and not checked during the verification step\n  std::string token;\n  std::string str_xattr = (pOpaque->Get(\"mgm.backup.excl_xattr\") ?\n                           pOpaque->Get(\"mgm.backup.excl_xattr\") : \"\");\n  std::set<std::string> set_xattrs;\n  std::istringstream iss(str_xattr);\n\n  while (std::getline(iss, token, ',')) {\n    set_xattrs.insert(token);\n  }\n\n  // If create flag is not present then put job in the queue\n  if (!pOpaque->Get(\"mgm.backup.create\")) {\n    int envlen;\n    std::string job_spec = pOpaque->Env(envlen);\n\n    if (!gOFS->SubmitBackupJob(job_spec)) {\n      eos_err(\"error=\\\"backup job already pending\\\"\");\n      stdErr = \"error: identic backup job already pending\";\n      retc = EINVAL;\n    }\n\n    return SFS_OK;\n  }\n\n  // Do the actual tree scan and build the backup file\n  retc = BackupCreate(src_surl, dst_surl, twindow_type, twindow_val, set_xattrs);\n\n  if (!retc) {\n    std::string bfile_url = src_url.GetURL();\n    bfile_url += EOS_COMMON_PATH_BACKUP_FILE_PREFIX;\n    bfile_url += \"backup.file\";\n    std::ostringstream cmd_json;\n    // @todo (esindril): The force flag should be passed on from the console\n    cmd_json << \"{\\\"cmd\\\": \\\"backup\\\", \"\n             << \"\\\"src\\\": \\\"\" << bfile_url.c_str() << \"\\\", \"\n             << \"\\\"opt\\\": \\\"force\\\", \"\n             << \"\\\"uid\\\": \\\"\" << pVid->uid << \"\\\", \"\n             << \"\\\"gid\\\": \\\"\" << pVid->gid << \"\\\" \"\n             << \"}\";\n    retc = ArchiveExecuteCmd(cmd_json.str());\n    eos_debug(\"sending command: %s\", cmd_json.str().c_str());\n  }\n\n  eos_debug(\"retc=%i, stdOut=%s, stdErr=%s\", retc, stdOut.c_str(),\n            stdErr.c_str());\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Create backup file which uses functionality from the archive mechanism\n//------------------------------------------------------------------------------\nint\nProcCommand::BackupCreate(const std::string& src_surl,\n                          const std::string& dst_surl,\n                          const std::string& twindow_type,\n                          const std::string& twindow_val,\n                          const std::set<std::string>& excl_xattr)\n{\n  int num_dirs = 0;\n  int num_files = 0;\n  XrdCl::URL src_url(src_surl);\n  // Create the output directory if necessary and open the temporary file in\n  // which we construct the backup file\n  std::ostringstream oss;\n  oss << \"/tmp/eos.mgm/backup.\" << XrdSysThread::ID();\n  std::string backup_fn = oss.str();\n  eos::common::Path cPath(backup_fn.c_str());\n\n  if (!cPath.MakeParentPath(S_IRWXU)) {\n    eos_err(\"Unable to create temporary outputfile directory /tmp/eos.mgm/\");\n    stdErr = \"unable to create temporary output directory /tmp/eos.mgm/\";\n    retc = EIO;\n    return retc;\n  }\n\n  // Own the directory by daemon\n  if (::chown(cPath.GetParentPath(), 2, 2)) {\n    eos_err(\"Unable to own temporary outputfile directory %s\",\n            cPath.GetParentPath());\n    stdErr = \"unable to own temporary output directory /tmp/eos.mgm/\";\n    retc = EIO;\n    return retc;\n  }\n\n  // Create tmp file holding information about the file entries. If twindow\n  // specified then also use a filter object.\n  std::string files_fn = backup_fn + \"_files\";\n  std::fstream files_ofs(files_fn.c_str(),\n                         std::fstream::out | std::fstream::trunc);\n\n  if (!files_ofs.is_open()) {\n    eos_err(\"Failed to create files backup file=%s\", files_fn.c_str());\n    stdErr = \"failed to create backup file at MGM \";\n    retc = EIO;\n    return retc;\n  }\n\n  files_ofs.clear();\n  files_ofs.close();\n  files_ofs.open(files_fn.c_str(), std::fstream::in | std::fstream::out);\n\n  if (!files_ofs.is_open()) {\n    unlink(files_fn.c_str());\n    eos_err(\"Failed to open files backup file=%s\", files_fn.c_str());\n    stdErr = \"failed to open backup file at MGM \";\n    retc = EIO;\n    return retc;\n  }\n\n  // Create filter\n  std::unique_ptr<IFilter> filter(new TwindowFilter(twindow_type, twindow_val));\n\n  // Add files info\n  if (ArchiveAddEntries(src_url.GetPath(), files_ofs, num_files,\n                        true, filter.get()) || (num_files == 0)) {\n    files_ofs.close();\n    unlink(files_fn.c_str());\n    return retc;\n  }\n\n  // Create tmp file holding information about the dir entries\n  std::string dirs_fn = backup_fn + \"_dirs\";\n  std::fstream dirs_ofs(dirs_fn.c_str(),\n                        std::fstream::out | std::fstream::trunc);\n\n  if (!dirs_ofs.is_open()) {\n    files_ofs.close();\n    unlink(files_fn.c_str());\n    eos_err(\"Failed to create local backup file=%s\", dirs_fn.c_str());\n    stdErr = \"failed to create backup file at MGM \";\n    retc = EIO;\n    return retc;\n  }\n\n  dirs_ofs.clear();\n  dirs_ofs.close();\n  dirs_ofs.open(dirs_fn.c_str(), std::fstream::in | std::fstream::out);\n\n  if (!dirs_ofs.is_open()) {\n    files_ofs.close();\n    unlink(files_fn.c_str());\n    unlink(dirs_fn.c_str());\n    eos_err(\"Failed to open dirs backup file=%s\", dirs_fn.c_str());\n    stdErr = \"failed to open backup file at MGM \";\n    retc = EIO;\n    return retc;\n  }\n\n  // Add dirs info\n  if (ArchiveAddEntries(src_url.GetPath(), dirs_ofs, num_dirs, false,\n                        filter.get())) {\n    files_ofs.close();\n    unlink(files_fn.c_str());\n    dirs_ofs.close();\n    unlink(dirs_fn.c_str());\n    return retc;\n  }\n\n  // Create the final backup file, write JSON header, append dir and file info\n  std::fstream backup_ofs(backup_fn.c_str(), std::fstream::out);\n\n  if (!backup_ofs.is_open()) {\n    eos_err(\"Failed to open local backup file=%s\", backup_fn.c_str());\n    stdErr = \"failed to open backup file at MGM \";\n    retc = EIO;\n    files_ofs.close();\n    unlink(files_fn.c_str());\n    dirs_ofs.close();\n    unlink(dirs_fn.c_str());\n    return retc;\n  }\n\n  // Note: we treat backups as archive get operations from tape to disk\n  // therefore we need to swap the src and destination in the header\n  num_dirs--; // don't count current dir\n  backup_ofs.seekp(0);\n  backup_ofs << \"{\"\n             << \"\\\"src\\\": \\\"\" << dst_surl << \"\\\", \"\n             << \"\\\"dst\\\": \\\"\" << src_surl << \"\\\", \"\n             << \"\\\"svc_class\\\": \\\"\\\", \"\n             << \"\\\"dir_meta\\\": [\\\"uid\\\", \\\"gid\\\", \\\"mode\\\", \\\"attr\\\"], \"\n             << \"\\\"file_meta\\\": [\\\"size\\\", \\\"mtime\\\", \\\"ctime\\\", \\\"uid\\\", \\\"gid\\\", \"\n             << \"\\\"mode\\\", \\\"xstype\\\", \\\"xs\\\"], \"\n             << \"\\\"excl_xattr\\\": [\";\n\n  // Add the list of excluded xattrs\n  for (auto it = excl_xattr.begin(); it != excl_xattr.end(); /*empty*/) {\n    backup_ofs << \"\\\"\" << *it << \"\\\"\";\n    ++it;\n\n    if (it != excl_xattr.end()) {\n      backup_ofs << \", \";\n    }\n  }\n\n  backup_ofs << \"], \"\n             << \"\\\"uid\\\": \\\"\" << pVid->uid << \"\\\", \"\n             << \"\\\"gid\\\": \\\"\" << pVid->gid << \"\\\", \"\n             << \"\\\"twindow_type\\\": \\\"\" << twindow_type << \"\\\", \"\n             << \"\\\"twindow_val\\\": \\\"\" << twindow_val << \"\\\", \"\n             << \"\\\"timestamp\\\": \" << std::setw(10) << time(static_cast<time_t*>(0)) << \", \"\n             << \"\\\"num_dirs\\\": \" << std::setw(10) << num_dirs << \", \"\n             << \"\\\"num_files\\\": \" << std::setw(10) << num_files\n             << \"}\" << std::endl;\n  // Append the directory entries\n  dirs_ofs.seekp(0);\n  backup_ofs << dirs_ofs.rdbuf();\n  // Append the file entries\n  files_ofs.seekp(0);\n  backup_ofs << files_ofs.rdbuf();\n  // Close all files\n  files_ofs.close();\n  dirs_ofs.close();\n  backup_ofs.close();\n  // Copy local backup file to backup source\n  XrdCl::PropertyList properties;\n  XrdCl::PropertyList result;\n  XrdCl::URL url_src, url_dst;\n  XrdCl::URL tmp_url(src_surl);\n  std::string dst_path = tmp_url.GetPath();\n  dst_path += EOS_COMMON_PATH_BACKUP_FILE_PREFIX;\n  dst_path += \"backup.file\";\n  url_src.SetProtocol(\"file\");\n  url_src.SetPath(backup_fn.c_str());\n  url_dst.SetProtocol(tmp_url.GetProtocol());\n  url_dst.SetHostName(tmp_url.GetHostName());\n  url_dst.SetPort(tmp_url.GetPort());\n  url_dst.SetUserName(\"root\");\n  url_dst.SetParams(\"eos.ruid=0&eos.rgid=0\");\n  url_dst.SetPath(dst_path);\n  properties.Set(\"source\", url_src);\n  properties.Set(\"target\", url_dst);\n  XrdCl::CopyProcess copy_proc;\n  copy_proc.AddJob(properties, &result);\n  XrdCl::XRootDStatus status_prep = copy_proc.Prepare();\n\n  if (status_prep.IsOK()) {\n    XrdCl::XRootDStatus status_run = copy_proc.Run(0);\n\n    if (!status_run.IsOK()) {\n      eos_err(\"Failed run for copy process, msg=%s\", status_run.ToStr().c_str());\n      stdErr = \"error: failed run for copy process, msg=\";\n      stdErr += status_run.ToStr().c_str();\n      retc = EIO;\n    }\n  } else {\n    eos_err(\"Failed prepare for copy process, msg=%s\", status_prep.ToStr().c_str());\n    stdErr = \"error: failed prepare for copy process, msg=\";\n    stdErr += status_prep.ToStr().c_str();\n    retc = EIO;\n  }\n\n  // Remove local files\n  unlink(files_fn.c_str());\n  unlink(dirs_fn.c_str());\n  unlink(backup_fn.c_str());\n  return retc;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/Backup.hh",
    "content": "//------------------------------------------------------------------------------\n// File: Backup.hh\n// Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_BACKUP_HH__\n#define __EOSMGM_BACKUP_HH__\n\n#include \"mgm/proc/ProcCommand.hh\"\n#include <set>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class TwindowFilter to exclude older entries\n//------------------------------------------------------------------------------\nclass TwindowFilter: public IFilter, public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param twindow_type time window type\n  //! @param twindow_val time window value\n  //----------------------------------------------------------------------------\n  TwindowFilter(const std::string& twindow_type, const std::string& twindow_val);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~TwindowFilter() {};\n\n  //----------------------------------------------------------------------------\n  //! Filter file entry if it is a version file i.e. contains \".sys.v#.\" or if\n  //! it is ouside the timewindow\n  //!\n  //! @param entry_info entry information on which the filter is applied\n  //!\n  //! @return true if entry should be filtered out, otherwise false\n  //----------------------------------------------------------------------------\n  bool FilterOutFile(const std::map<std::string, std::string>& entry_info);\n\n  //----------------------------------------------------------------------------\n  //! Filter the directory entry\n  //!\n  //! @param path current directory path\n  //!\n  //! @return true if entry should be filtered out, otherwise false\n  //----------------------------------------------------------------------------\n  bool FilterOutDir(const std::string& path);\n\nprivate:\n  std::string mTwindowType; ///< Time window type\n  std::string mTwindowVal; ///< Time window value\n  std::set<std::string> mSetDirs; ///< Set of directories to keep\n};\n\nEOSMGMNAMESPACE_END\n\n#endif // __EOS_MGM_BACKUP_HH__\n"
  },
  {
    "path": "mgm/proc/admin/ConfigCmd.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: ConfigCmd.cc\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"ConfigCmd.hh\"\n#include \"mgm/proc/ProcInterface.hh\"\n\n#include <XrdOuc/XrdOucEnv.hh>\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/config/IConfigEngine.hh\"\n\n\nEOSMGMNAMESPACE_BEGIN\n\n\n//------------------------------------------------------------------------------\n// Method implementing the specific behavior of the command executed by the\n// asynchronous thread\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nConfigCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n\n  if ((mVid.uid != 0)) {\n    reply.set_std_err(\"error: you have to take role 'root' to execute this command\");\n    reply.set_retc(EPERM);\n    return reply;\n  }\n\n  eos::console::ConfigProto config = mReqProto.config();\n\n  switch (mReqProto.config().subcmd_case()) {\n  case eos::console::ConfigProto::kLs:\n    LsSubcmd(config.ls(), reply);\n    break;\n\n  case eos::console::ConfigProto::kDump:\n    DumpSubcmd(config.dump(), reply);\n    break;\n\n  case eos::console::ConfigProto::kReset:\n    ResetSubcmd(reply);\n    break;\n\n  case eos::console::ConfigProto::kExp:\n    ExportSubcmd(config.exp(), reply);\n    break;\n\n  case eos::console::ConfigProto::kSave:\n    SaveSubcmd(config.save(), reply);\n    break;\n\n  case eos::console::ConfigProto::kLoad:\n    LoadSubcmd(config.load(), reply);\n    break;\n\n  case eos::console::ConfigProto::kChangelog:\n    ChangelogSubcmd(config.changelog(), reply);\n    break;\n\n  default:\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: not supported\");\n  }\n\n  return reply;\n}\n\n//----------------------------------------------------------------------------\n// Execute ls subcommand\n//----------------------------------------------------------------------------\nvoid ConfigCmd::LsSubcmd(const eos::console::ConfigProto_LsProto& ls,\n                         eos::console::ReplyProto& reply)\n{\n  XrdOucString listing = \"\";\n\n  if (!(gOFS->mConfigEngine->ListConfigs(listing, ls.showbackup()))) {\n    reply.set_std_err(\"error: listing of existing configs failed!\");\n    reply.set_retc(errno);\n  } else {\n    reply.set_std_out(listing.c_str());\n  }\n}\n\n//----------------------------------------------------------------------------\n// Execute dump subcommand\n//----------------------------------------------------------------------------\nvoid ConfigCmd::DumpSubcmd(const eos::console::ConfigProto_DumpProto& dump,\n                           eos::console::ReplyProto& reply)\n{\n  XrdOucString sdump = \"\";\n\n  if (!gOFS->mConfigEngine->DumpConfig(sdump, dump.file())) {\n    reply.set_std_err(\"error: failed to dump configuration\");\n    reply.set_retc(errno);\n  } else {\n    reply.set_std_out(sdump.c_str());\n  }\n}\n\n//----------------------------------------------------------------------------\n// Execute reset subcommand\n//----------------------------------------------------------------------------\nvoid ConfigCmd::ResetSubcmd(eos::console::ReplyProto& reply)\n{\n  gOFS->mConfigEngine->ResetConfig();\n  reply.set_std_out(\"success: configuration has been reset(cleaned)!\");\n}\n\n//----------------------------------------------------------------------------\n// Execute export subcommand\n//----------------------------------------------------------------------------\nvoid ConfigCmd::ExportSubcmd(const eos::console::ConfigProto_ExportProto& exp,\n                             eos::console::ReplyProto& reply)\n{\n  reply.set_std_err(\"error: export command has been deprecated\");\n  reply.set_retc(EINVAL);\n}\n\n//----------------------------------------------------------------------------\n// Execute save subcommand\n//----------------------------------------------------------------------------\nvoid ConfigCmd::SaveSubcmd(const eos::console::ConfigProto_SaveProto& save,\n                           eos::console::ReplyProto& reply)\n{\n  eos_notice(\"config save: %s\", save.ShortDebugString().c_str());\n  XrdOucString std_err;\n\n  if (!gOFS->mConfigEngine->SaveConfig(save.file(), save.force(),\n                                       mReqProto.comment(), std_err)) {\n    reply.set_std_err(std_err.c_str());\n    reply.set_retc(errno);\n  } else {\n    reply.set_std_out(\"success: configuration successfully saved!\");\n  }\n}\n\n//----------------------------------------------------------------------------\n// Execute load subcommand\n//----------------------------------------------------------------------------\nvoid ConfigCmd::LoadSubcmd(const eos::console::ConfigProto_LoadProto& load,\n                           eos::console::ReplyProto& reply)\n{\n  eos_notice(\"config load: %s\", load.ShortDebugString().c_str());\n  eos::mgm::ConfigResetMonitor fsview_cfg_reset_monitor;\n  XrdOucString std_err;\n\n  if (!gOFS->mConfigEngine->LoadConfig(load.file(), std_err)) {\n    reply.set_std_err(std_err.c_str());\n    reply.set_retc(errno);\n  } else {\n    reply.set_std_out(\"success: configuration successfully loaded!\");\n  }\n}\n\n//----------------------------------------------------------------------------\n// Execute changelog subcommand\n//----------------------------------------------------------------------------\nvoid ConfigCmd::ChangelogSubcmd(const eos::console::ConfigProto_ChangelogProto&\n                                changelog,\n                                eos::console::ReplyProto& reply)\n{\n  std::string std_out;\n  gOFS->mConfigEngine->Tail(changelog.lines(), std_out);\n  eos_notice(\"config changelog\");\n  reply.set_std_out(std_out);\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/ConfigCmd.hh",
    "content": "//------------------------------------------------------------------------------\n// @file: ConfigCmd.hh\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"proto/Config.pb.h\"\n#include \"mgm/proc/ProcCommand.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class ConfigCmd - class handling config commands\n//------------------------------------------------------------------------------\nclass ConfigCmd: public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit ConfigCmd(eos::console::RequestProto&& req,\n                     eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, false)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~ConfigCmd() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Execute ls subcommand\n  //!\n  //! @param ls ls subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void LsSubcmd(const eos::console::ConfigProto_LsProto& ls,\n                eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute dump subcommand\n  //!\n  //! @param dump dump subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void DumpSubcmd(const eos::console::ConfigProto_DumpProto& dump,\n                  eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute reset subcommand\n  //!\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void ResetSubcmd(eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute export subcommand\n  //!\n  //! @param exp exp subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void ExportSubcmd(const eos::console::ConfigProto_ExportProto& exp,\n                    eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute save subcommand\n  //!\n  //! @param save save subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void SaveSubcmd(const eos::console::ConfigProto_SaveProto& save,\n                  eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute load subcommand\n  //!\n  //! @param load load subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void LoadSubcmd(const eos::console::ConfigProto_LoadProto& load,\n                  eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute changelog subcommand\n  //!\n  //! @param changelog changelog subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void ChangelogSubcmd(const eos::console::ConfigProto_ChangelogProto& changelog,\n                       eos::console::ReplyProto& reply);\n\n\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/ConvertCmd.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ConvertCmd.cc\n// Author: Mihai Patrascoiu - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"ConvertCmd.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/scheduler/Scheduler.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/convert/ConverterEngine.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"common/table_formatter/TableFormatterBase.hh\"\n#include \"common/Constants.hh\"\n#include <json/json.h>\n\nEOSMGMNAMESPACE_BEGIN\n\n// Helper function forward declaration\nstatic int CheckValidPath(const char*, eos::common::VirtualIdentity&,\n                          std::string&, XrdSfsFileExistence = XrdSfsFileExistNo);\n\nstatic std::string BuildConversionId(const std::string&,\n                                     eos::common::LayoutId::eChecksum,\n                                     int,\n                                     eos::common::FileId::fileid_t file_id,\n                                     const std::string&,\n                                     const std::string& = \"\");\n\n//------------------------------------------------------------------------------\n// Method implementing the specific behaviour of the command executed\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nConvertCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  const eos::console::ConvertProto& convert = mReqProto.convert();\n  const auto& subcmd = convert.subcmd_case();\n  bool json =\n    (mReqProto.format() == eos::console::RequestProto::JSON);\n\n  if (!gOFS->mConverterEngine) {\n    reply.set_std_err(\"error: converter engine is not initialized\");\n    reply.set_retc(ENOTSUP);\n    return reply;\n  }\n\n  if (subcmd == eos::console::ConvertProto::kConfig) {\n    ConfigSubcmd(convert.config(), reply, json);\n  } else if (subcmd == eos::console::ConvertProto::kFile) {\n    FileSubcmd(convert.file(), reply, json);\n  } else if (subcmd == eos::console::ConvertProto::kList) {\n    ListSubcmd(convert.list(), reply, json);\n  } else if (subcmd == eos::console::ConvertProto::kClear) {\n    // check if vid has admin permissions (root, sudoer, admin user, admin group)\n    if (!vid.uid || vid.sudoer || vid.hasUid(eos::common::ADM_UID) ||\n        vid.hasGid(eos::common::ADM_GID)) {\n      ClearSubcmd(convert.clear(), reply);\n    } else {\n      reply.set_retc(EPERM);\n      reply.set_std_err(\"error: you have to take role 'root' to execute this command\");\n    }\n  } else {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: command not supported\");\n  }\n\n  return reply;\n}\n\n//------------------------------------------------------------------------------\n// List current converter configuration\n//------------------------------------------------------------------------------\nstd::string\nConvertCmd::ConfigList(bool json) const\n{\n  std::ostringstream out;\n  // Lambda function to parse threadpool information\n  auto parseKeyValueString = [](std::string skeyvalue) -> Json::Value {\n    using eos::common::StringConversion;\n    std::map<std::string, std::string> map;\n    Json::Value json;\n\n    if (StringConversion::GetKeyValueMap(skeyvalue.c_str(),\n                                         map, \"=\", \" \"))\n    {\n      for (const auto& it : map) {\n        json[it.first] = map[it.first];\n      }\n    }\n\n    return json;\n  };\n  // Extract Converter parameters\n  std::string threadpool = gOFS->mConverterEngine->GetThreadPoolInfo();\n  std::string config = gOFS->mConverterEngine->SerializeConfig();\n  uint64_t running = gOFS->mConverterEngine->NumRunningJobs();\n  uint64_t failed = gOFS->mConverterEngine->NumFailedJobs();\n  int64_t pending = gOFS->mConverterEngine->NumPendingJobs();\n\n  if (json) {\n    Json::Value json;\n    json[\"threadpool\"] = parseKeyValueString(threadpool.c_str());\n    json[\"config\"] = parseKeyValueString(config.c_str());\n    json[\"running\"] = (Json::Value::UInt64) running;\n    json[\"pending\"] = (Json::Value::UInt64) pending;\n    json[\"failed\"] = (Json::Value::UInt64) failed;\n    Json::StreamWriterBuilder builder;\n    std::unique_ptr<Json::StreamWriter> jsonwriter(\n      builder.newStreamWriter());\n    jsonwriter->write(json, &out);\n  } else {\n    out << \"Configuration: \" << config << std::endl\n        << \"Threadpool   : \" << threadpool << std::endl\n        << \"Running jobs : \" << running << std::endl\n        << \"Pending jobs : \" << pending << std::endl\n        << \"Failed jobs  : \" << failed << std::endl;\n  }\n\n  return out.str();\n}\n\n//------------------------------------------------------------------------------\n// Execute config subcommand\n//------------------------------------------------------------------------------\nvoid\nConvertCmd::ConfigSubcmd(const eos::console::ConvertProto_ConfigProto& config,\n                         eos::console::ReplyProto& reply, bool json)\n{\n  using eos::console::ConvertProto_ConfigProto;\n\n  if (config.op() == ConvertProto_ConfigProto::LIST) {\n    if (gOFS) {\n      reply.set_std_out(ConfigList(json));\n    }\n  } else if (config.op() == ConvertProto_ConfigProto::SET) {\n    if (gOFS) {\n      if (!gOFS->mConverterEngine->SetConfig(config.key(), config.value())) {\n        reply.set_std_err(\"error: failed applying converter configuration\");\n        reply.set_retc(EINVAL);\n        return;\n      }\n    }\n  } else {\n    reply.set_std_err(\"error: unknown converter operation\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  reply.set_retc(0);\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Execute file subcommand\n//------------------------------------------------------------------------------\nvoid ConvertCmd::FileSubcmd(const eos::console::ConvertProto_FileProto& file,\n                            eos::console::ReplyProto& reply,\n                            bool json)\n{\n  using eos::common::LayoutId;\n  std::ostringstream out;\n  std::ostringstream err;\n  std::string errmsg;\n  int retc = 0;\n  auto enforce_file = XrdSfsFileExistence::XrdSfsFileExistIsFile;\n  std::string path = PathFromIdentifierProto(file.identifier(), errmsg);\n\n  if (!path.length()) {\n    reply.set_std_err(errmsg);\n    reply.set_retc(errno);\n    return;\n  }\n\n  if ((retc = CheckValidPath(path.c_str(), mVid, errmsg, enforce_file))) {\n    reply.set_std_err(errmsg);\n    reply.set_retc(retc);\n    return;\n  }\n\n  // Extract file id, layout id and replica location\n  eos::IFileMD::id_t file_id = 0;\n  eos::IFileMD::layoutId_t file_layoutid = 0;\n  eos::IFileMD::location_t replica_location = 0;\n\n  try {\n    eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosView->getFile(path).get();\n    file_id = fmd->getId();\n    file_layoutid = fmd->getLayoutId();\n    replica_location = fmd->getLocations().at(0);\n  } catch (eos::MDException& e) {\n    eos_debug(\"msg=\\\"exception retrieving file metadata\\\" path=%s \"\n              \"ec=%d emsg=\\\"%s\\\"\", path.c_str(), e.getErrno(),\n              e.getMessage().str().c_str());\n    err << \"error: failed to retrieve file metadata '\" << path << \"'\";\n    reply.set_std_err(err.str());\n    reply.set_retc(e.getErrno());\n    return;\n  } catch (const std::out_of_range& e) {\n    eos_static_err(\"msg=\\\"exception getting replica locations\\\" path=%s \"\n                   \"emsg=\\\"%s\\\"\", path.c_str(), e.what());\n    err << \"error: files without replicas can not be converted\";\n    reply.set_std_err(err.str());\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  // Handle conversion space\n  const auto& conversion = file.conversion();\n\n  if ((retc = CheckConversionProto(conversion, errmsg))) {\n    reply.set_std_err(errmsg);\n    reply.set_retc(retc);\n    return;\n  }\n\n  std::string space = conversion.space();\n\n  if (space.empty()) {\n    eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n    auto filesystem = FsView::gFsView.mIdView.lookupByID(replica_location);\n\n    if (filesystem) {\n      space = filesystem->GetString(\"schedgroup\");\n    } else {\n      err << \"error: unable to retrieve \"\n          << \"filesystem location for '\" << path << \"'\";\n      reply.set_std_err(err.str());\n      reply.set_retc(EINVAL);\n      return;\n    }\n  }\n\n  // Handle checksum\n  LayoutId::eChecksum echecksum;\n\n  if (conversion.checksum().length()) {\n    echecksum = static_cast<LayoutId::eChecksum>\n                (LayoutId::GetChecksumFromString(conversion.checksum()));\n  } else {\n    echecksum = static_cast<LayoutId::eChecksum>\n                (LayoutId::GetChecksum(file_layoutid));\n  }\n\n  // Schedule conversion job\n  std::string err_msg;\n  std::string conversion_id = BuildConversionId(conversion.layout(), echecksum,\n                              conversion.replica(), file_id,\n                              space, conversion.placement());\n  eos_info(\"msg=\\\"scheduling conversion job\\\" path=%s conversion_id=%s\",\n           path.c_str(), conversion_id.c_str());\n\n  if (!gOFS->mConverterEngine->ScheduleJob(file_id, conversion_id, err_msg)) {\n    err << \"error: failed to schedule conversion '\"\n        << conversion_id.c_str();\n\n    if (!err_msg.empty()) {\n      err << \" msg=\\\"\" << err_msg << \"\\\"\";\n    }\n\n    reply.set_std_err(err.str());\n    reply.set_retc(EIO);\n    return;\n  }\n\n  if (json) {\n    Json::Value json;\n    json[\"conversion_id\"] = conversion_id;\n    json[\"path\"] = path;\n    json[\"space\"] = space;\n    json[\"checksum\"] = LayoutId::GetChecksumString(echecksum);\n    Json::StreamWriterBuilder builder;\n    std::unique_ptr<Json::StreamWriter> jsonwriter(\n      builder.newStreamWriter());\n    jsonwriter->write(json, &out);\n  } else {\n    out << \"Scheduled conversion job: \" << conversion_id;\n  }\n\n  reply.set_std_out(out.str());\n}\n\n//------------------------------------------------------------------------------\n// List jobs subcommand\n//------------------------------------------------------------------------------\nvoid\nConvertCmd::ListSubcmd(const eos::console::ConvertProto_ListProto& list,\n                       eos::console::ReplyProto& reply, bool json)\n{\n  std::ostringstream oss;\n  auto pending = gOFS->mConverterEngine->GetPendingJobs();\n\n  if (pending.empty()) {\n    reply.set_std_out(\"info: no pending conversions\");\n    return;\n  }\n\n  if (json) {\n    Json::Value json;\n\n    for (const auto& elem : pending) {\n      json[std::to_string(std::get<0>(elem))] = std::get<1>(elem);\n    }\n\n    Json::StreamWriterBuilder builder;\n    std::unique_ptr<Json::StreamWriter> jsonwriter(\n      builder.newStreamWriter());\n    jsonwriter->write(json, &oss);\n  } else {\n    TableFormatterBase table;\n    TableHeader header;\n    TableData body;\n    header.push_back(std::make_tuple(\"Fxid\", 10, \"-s\"));\n    header.push_back(std::make_tuple(\"Conversion string\", 0, \"-s\"));\n    table.SetHeader(header);\n\n    for (const auto& elem : pending) {\n      TableRow row;\n      row.emplace_back(eos::common::FileId::Fid2Hex(std::get<0>(elem)), \"-s\");\n      row.emplace_back(std::get<1>(elem), \"-s\");\n      body.push_back(row);\n    }\n\n    table.AddRows(body);\n    oss << table.GenerateTable();\n  }\n\n  if (!oss.str().empty()) {\n    reply.set_std_out(oss.str());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Clear jobs subcommand\n//------------------------------------------------------------------------------\nvoid\nConvertCmd::ClearSubcmd(const eos::console::ConvertProto_ClearProto& clear,\n                        eos::console::ReplyProto& reply)\n{\n  gOFS->mConverterEngine->ClearPendingJobs();\n  reply.set_std_out(\"info: list cleared\");\n}\n\n//------------------------------------------------------------------------------\n// Translate the identifier proto into a namespace path\n//------------------------------------------------------------------------------\nstd::string ConvertCmd::PathFromIdentifierProto(\n  const eos::console::ConvertProto_IdentifierProto& identifier,\n  std::string& err_msg)\n{\n  using eos::console::ConvertProto;\n  const auto& type = identifier.Identifier_case();\n  std::string path = \"\";\n\n  if (type == ConvertProto::IdentifierProto::kPath) {\n    path = identifier.path().c_str();\n  } else if (type == ConvertProto::IdentifierProto::kFileId) {\n    GetPathFromFid(path, identifier.fileid(), err_msg);\n  } else if (type == ConvertProto::IdentifierProto::kContainerId) {\n    GetPathFromCid(path, identifier.containerid(), err_msg);\n  } else {\n    err_msg = \"error: received empty string path\";\n  }\n\n  return path;\n}\n\n//------------------------------------------------------------------------------\n// Check that the given proto conversion is valid\n//------------------------------------------------------------------------------\nint ConvertCmd::CheckConversionProto(\n  const eos::console::ConvertProto_ConversionProto& conversion,\n  std::string& err_msg)\n{\n  using eos::common::LayoutId;\n\n  if (LayoutId::GetLayoutFromString(conversion.layout()) == -1) {\n    err_msg = \"error: invalid conversion layout\";\n    return EINVAL;\n  }\n\n  if (conversion.replica() < 1 || conversion.replica() > 32) {\n    err_msg = \"error: invalid replica number (must be between 1 and 32)\";\n    return EINVAL;\n  }\n\n  if (conversion.checksum().length()) {\n    auto xs_id = LayoutId::GetChecksumFromString(conversion.checksum());\n\n    if ((xs_id == -1) || (xs_id == LayoutId::eChecksum::kNone)) {\n      err_msg = \"error: invalid conversion checksum\";\n      return EINVAL;\n    }\n  }\n\n  if (conversion.placement().length() &&\n      eos::mgm::Scheduler::PlctPolicyFromString(conversion.placement()) == -1) {\n    err_msg = \"error: invalid conversion placement policy\";\n    return EINVAL;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n//! Check that the given path points to a valid entry\n//!\n//! @param path the path to check\n//! @param vid virtual identity of the client\n//! @param err_msg string to place error message\n//! @param enforce_type expected entry type (file or directory)\n//!\n//! @return 0 if path is valid, error code otherwise\n//------------------------------------------------------------------------------\nstatic int CheckValidPath(const char* path,\n                          eos::common::VirtualIdentity& vid,\n                          std::string& err_msg,\n                          XrdSfsFileExistence enforce_type)\n{\n  XrdSfsFileExistence fileExists;\n  XrdOucErrInfo errInfo;\n\n  // Check for path existence\n  if (gOFS->_exists(path, fileExists, errInfo, vid)) {\n    err_msg = \"error: unable to check for path existence\";\n    return errInfo.getErrInfo();\n  }\n\n  if (fileExists == XrdSfsFileExistNo) {\n    err_msg = \"error: path does not point to a valid entry\";\n    return EINVAL;\n  } else if ((fileExists != XrdSfsFileExistIsFile) &&\n             (fileExists != XrdSfsFileExistIsDirectory)) {\n    err_msg = \"error: path does not point to a file or container\";\n    return EINVAL;\n  }\n\n  if ((enforce_type != XrdSfsFileExistNo) && (fileExists != enforce_type)) {\n    std::string type = (fileExists == XrdSfsFileExistIsFile) ?\n                       \"file\" : \"directory\";\n    err_msg = \"error: path must point to a \" + type;\n    return EINVAL;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n//! Build and return a conversion id from the provided arguments\n//!\n//! @param layout the conversion layout\n//! @param echecksum the conversion checksum\n//! @param stripes the conversion number of stripes\n//! @param file_id the conversion file id\n//! @paran space the conversion target space\n//! @param placement the conversion placement type\n//!\n//! @return 0 if path is valid, error code otherwise\n//------------------------------------------------------------------------------\nstatic std::string BuildConversionId(const std::string& layout,\n                                     eos::common::LayoutId::eChecksum echecksum,\n                                     int stripes,\n                                     eos::common::FileId::fileid_t file_id,\n                                     const std::string& space,\n                                     const std::string& placement)\n{\n  using eos::common::LayoutId;\n  unsigned long layoutid = 0;\n  layoutid = LayoutId::GetId(LayoutId::GetLayoutFromString(layout),\n                             echecksum,\n                             stripes,\n                             LayoutId::eBlockSize::k4M,\n                             LayoutId::eChecksum::kCRC32C,\n                             0, // excess replicas\n                             LayoutId::GetRedundancyFromLayoutString(layout));\n  char buff[4096];\n  snprintf(buff, std::size(buff), \"%016llx:%s#%08lx\",\n           file_id, space.c_str(), layoutid);\n  std::string conversion {buff};\n\n  if (!placement.empty()) { // ~<placement_policy>\n    conversion += \"~\";\n    conversion += placement;\n  }\n\n  return conversion;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/ConvertCmd.hh",
    "content": "//------------------------------------------------------------------------------\n// File: ConvertCmd.hh\n// Author: Mihai Patrascoiu - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"proto/Convert.pb.h\"\n#include \"mgm/Namespace.hh\"\n#include \"mgm/proc/IProcCommand.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class ConvertCmd - class handling convert commands\n//------------------------------------------------------------------------------\nclass ConvertCmd: public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit ConvertCmd(eos::console::RequestProto&& req,\n                      eos::common::VirtualIdentity& vid) :\n    IProcCommand(std::move(req), vid, false)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~ConvertCmd() = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! List current converter configuration\n  //!\n  //! @param jons if true return json string representation\n  //!\n  //! @return string represetation of the converter configuration\n  //----------------------------------------------------------------------------\n  std::string ConfigList(bool json = false) const;\n\n  //----------------------------------------------------------------------------\n  //! Execute config subcommand\n  //!\n  //! @param config config subcommand proto object\n  //! @param reply reply proto object\n  //! @param json flag to print output in JSON format\n  //----------------------------------------------------------------------------\n  void ConfigSubcmd(const eos::console::ConvertProto_ConfigProto& config,\n                    eos::console::ReplyProto& reply, bool json = false);\n\n  //----------------------------------------------------------------------------\n  //! Execute file subcommand\n  //!\n  //! @param file file subcommand proto object\n  //! @param reply reply proto object\n  //! @param json flag to print output in JSON format\n  //----------------------------------------------------------------------------\n  void FileSubcmd(const eos::console::ConvertProto_FileProto& file,\n                  eos::console::ReplyProto& reply, bool json = false);\n\n  //----------------------------------------------------------------------------\n  //! List jobs subcommand\n  //!\n  //! @param list list subcommand proto object\n  //! @param reply reply proto object\n  //! @param json flag to print output in JSON format\n  //----------------------------------------------------------------------------\n  void ListSubcmd(const eos::console::ConvertProto_ListProto& list,\n                  eos::console::ReplyProto& reply, bool json = false);\n\n  //----------------------------------------------------------------------------\n  //! Clear jobs subcommand\n  //!\n  //! @param clear clear subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void ClearSubcmd(const eos::console::ConvertProto_ClearProto& clear,\n                   eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Translate the proto identifier into a namespace path\n  //!\n  //! @param identifier identifier proto object\n  //! @param err_msg string to place error message\n  //!\n  //! @return path of identifier or empty string if translation failed\n  //----------------------------------------------------------------------------\n  std::string PathFromIdentifierProto(\n    const eos::console::ConvertProto_IdentifierProto& identifier,\n    std::string& err_msg);\n\n  //------------------------------------------------------------------------------\n  //! Check that the given proto conversion is valid\n  //!\n  //! @param conversion conversion proto object\n  //! @param err_msg string to place error message\n  //!\n  //! @return 0 if conversion is valid, error code otherwise\n  //------------------------------------------------------------------------------\n  int CheckConversionProto(\n    const eos::console::ConvertProto_ConversionProto& conversion,\n    std::string& err_msg);\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/DebugCmd.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: DebugCmd.cc\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"DebugCmd.hh\"\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mq/MessagingRealm.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Method implementing the specific behavior of the command\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nDebugCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  eos::console::DebugProto debug = mReqProto.debug();\n\n  switch (mReqProto.debug().subcmd_case()) {\n  case eos::console::DebugProto::kGet:\n    GetSubcmd(debug.get(), reply);\n    break;\n\n  case eos::console::DebugProto::kSet:\n    SetSubcmd(debug.set(), reply);\n    break;\n\n  default:\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: not supported\");\n  }\n\n  return reply;\n}\n\n//------------------------------------------------------------------------------\n// Execute get subcommand\n//------------------------------------------------------------------------------\nvoid DebugCmd::GetSubcmd(const eos::console::DebugProto_GetProto& get,\n                         eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out;\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n  std_out <<\n          \"# ------------------------------------------------------------------------------------\\n\"\n          << \"# Debug log level\\n\"\n          << \"# ....................................................................................\\n\";\n  std::string priority = g_logging.GetPriorityString(g_logging.gPriorityLevel);\n  std::for_each(priority.begin(), priority.end(), [](char& c) {\n    c = ::tolower(static_cast<unsigned char>(c));\n  });\n  std_out << \"/eos/\" << gOFS->HostName << ':' << gOFS->ManagerPort << \"/mgm := \"\n          << priority.c_str() << std::endl;\n  auto nodes = FsView::gFsView.mNodeView;\n\n  for (auto& node : nodes) {\n    std_out << node.first << \" := \"\n            << FsView::gFsView.mNodeView[node.first]->GetConfigMember(\"debug.state\")\n            << std::endl;\n  }\n\n  reply.set_std_out(std_out.str());\n  reply.set_retc(0);\n}\n\n//------------------------------------------------------------------------------\n// Build string that is put into a message sent to the FSTs or slaves with the\n// new log level\n//------------------------------------------------------------------------------\nstd::string PrepareMsg(const eos::console::DebugProto_SetProto& set)\n{\n  std::string in = \"mgm.cmd=debug\";\n\n  if (set.debuglevel().length()) {\n    in += \"&mgm.debuglevel=\" + set.debuglevel();\n  }\n\n  if (set.nodename().length()) {\n    in += \"&mgm.nodename=\" + set.nodename();\n  }\n\n  if (set.filter().length()) {\n    in += \"&mgm.filter=\" + set.filter();\n  }\n\n  return in;\n}\n\n//------------------------------------------------------------------------------\n// Build query string to be sent to the FSTs to change the debug level\n//------------------------------------------------------------------------------\nstd::string PrepareQuery(const eos::console::DebugProto_SetProto& set)\n{\n  std::ostringstream oss;\n  oss << \"/?fst.pcmd=debug\"\n      << \"&fst.debug.level=\" << set.debuglevel();\n\n  if (!set.filter().empty()) {\n    oss << \"&fst.debug.filter=\" << set.filter();\n  }\n\n  return oss.str();\n}\n\n//------------------------------------------------------------------------------\n// Execute set subcommand\n//------------------------------------------------------------------------------\nvoid DebugCmd::SetSubcmd(const eos::console::DebugProto_SetProto& set,\n                         eos::console::ReplyProto& reply)\n{\n  if (mVid.uid != 0) {\n    reply.set_std_err(\"error: only role 'root' can execute this command\");\n    reply.set_retc(EPERM);\n    return;\n  }\n\n  // Always check debug level exists first\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n  int debugval = g_logging.GetPriorityByString(set.debuglevel().c_str());\n\n  if (debugval < 0) {\n    reply.set_std_err(SSTR(\"error: unknown log level <\" + set.debuglevel() + \">\"));\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  // Filter out several *'s ...\n  int nstars = 0;\n  int npos = 0;\n\n  while ((npos = set.nodename().find('*', npos)) != STR_NPOS) {\n    npos++;\n    nstars++;\n  }\n\n  if (nstars > 1) {\n    reply.set_std_err(\"error: debug level node can only contain one wildcard \"\n                      \"character (*)!\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  int ret_c = 0;\n  std::ostringstream out, err;\n  std::string body = PrepareMsg(set);\n  std::string query = PrepareQuery(set);\n\n  if ((set.nodename() == \"*\") || (set.nodename().empty()) ||\n      (XrdOucString(set.nodename().c_str()) == gOFS->MgmOfsQueue) ||\n      (set.nodename() == \"/eos/*/mgm\")) {\n    g_logging.SetLogPriority(debugval);\n    out << \"success: log level is now <\" + set.debuglevel() + '>';\n    eos_static_notice(\"msg=\\\"setting log level to <%s>\\\"\",\n                      set.debuglevel().c_str());\n\n    if (set.filter().length()) {\n      g_logging.SetFilter(set.filter().c_str());\n      out << \" filter=\" + set.filter();\n      eos_static_notice(\"msg=\\\"setting message logid filter to <%s>\\\"\",\n                        set.filter().c_str());\n    }\n  }\n\n  if ((set.nodename() == \"/eos/*/mgm\") || set.nodename().empty()) {\n    reply.set_retc(ret_c);\n    return;\n  }\n\n  std::set<std::string> endpoints = FsView::gFsView.CollectEndpoints(\n                                      set.nodename());\n\n  if (endpoints.empty()) {\n    reply.set_std_err(\"error: requested endpoint(s) not existing or not online\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  std::map<std::string, std::pair<int, std::string>> responses;\n  int query_retc = gOFS->BroadcastQuery(query, endpoints, responses);\n\n  if (query_retc == 0) {\n    out.str(\"\");\n    out.clear();\n    out << (\"success: log level=\" + set.debuglevel() +\n            \" on nodename=\" + set.nodename() + \"\\n\").c_str();\n    eos_static_notice(\"msg=\\\"forwarding log level <%s> to nodename=%s\\\"\",\n                      set.debuglevel().c_str(), set.nodename().c_str());\n  } else {\n    err << (\"error: could not send log level to nodename=\" +\n            set.nodename() + \"\\n\").c_str();\n    eos_static_err(\"msg=\\\"failed log level broadcast\\\" nodename=\\\"%s\\\"\",\n                   set.nodename().c_str());\n    ret_c = EINVAL;\n  }\n\n  reply.set_std_out(out.str());\n  reply.set_std_err(err.str());\n  reply.set_retc(ret_c);\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/DebugCmd.hh",
    "content": "//------------------------------------------------------------------------------\n// @file: DebugCmd.hh\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"proto/Debug.pb.h\"\n#include \"mgm/proc/ProcCommand.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class DebugCmd - class handling debug commands\n//------------------------------------------------------------------------------\nclass DebugCmd: public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit DebugCmd(eos::console::RequestProto&& req,\n                    eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, false)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~DebugCmd() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Execute get subcommand\n  //!\n  //! @param get get subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  static void GetSubcmd(const eos::console::DebugProto_GetProto& get,\n                 eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute set subcommand\n  //!\n  //! @param set set subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void SetSubcmd(const eos::console::DebugProto_SetProto& set,\n                 eos::console::ReplyProto& reply);\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/DevicesCmd.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: DevicesCmd.cc\n// @author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"DevicesCmd.hh\"\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/tgc/Constants.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/devices/Devices.hh\"\n#include \"common/Path.hh\"\n#include \"mgm/config/IConfigEngine.hh\"\n#include \"common/Constants.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/table_formatter/TableFormatterBase.hh\"\n#include \"common/table_formatter/TableFormatting.hh\"\n#include \"common/json/Json.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n//------------------------------------------------------------------------------\n// Method implementing the specific behavior of the command executed by the\n// asynchronous thread\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nDevicesCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  eos::console::DevicesProto devices = mReqProto.devices();\n\n  switch (mReqProto.devices().subcmd_case()) {\n  case eos::console::DevicesProto::kLs:\n    LsSubcmd(devices.ls(), reply);\n    break;\n\n  default:\n    reply.set_std_err(\"error: not supported\");\n    reply.set_retc(EINVAL);\n  }\n\n  return reply;\n}\n\n//----------------------------------------------------------------------------\n// Execute ls subcommand\n//----------------------------------------------------------------------------\nvoid DevicesCmd::LsSubcmd(const eos::console::DevicesProto_LsProto& ls,\n                          eos::console::ReplyProto& reply)\n{\n  using eos::console::DevicesProto;\n  std::string list_format;\n  std::string format;\n  auto format_case = ls.outformat();\n  Json::Value gjson;\n\n  if ((format_case == DevicesProto::LsProto::NONE) && WantsJsonOutput()) {\n    format_case = DevicesProto::LsProto::MONITORING;\n  }\n\n  switch (format_case) {\n  case DevicesProto::LsProto::LISTING:\n    break;\n\n  case DevicesProto::LsProto::MONITORING:\n    break;\n\n  default : // NONE\n    break;\n  }\n\n  std::string std_out;\n\n  if (ls.refresh()) {\n    //  force a new extraction\n    gOFS->mDeviceTracker->Extract();\n  }\n\n  auto extractionTime = gOFS->mDeviceTracker->getExtractionTime();\n  auto extractionLocalTime = gOFS->mDeviceTracker->getLocalExtractionTime();\n\n  if (format_case != DevicesProto::LsProto::MONITORING) {\n    std_out += \"# \";\n    std_out += extractionLocalTime;\n    std_out += \"\\n\";\n  }\n\n  for (auto it = FsView::gFsView.mSpaceView.begin();\n       it != FsView::gFsView.mSpaceView.end(); ++it) {\n    std::map<std::string, std::map<std::string, uint64_t>> driveModelStats;\n    std::map<std::string, std::map<std::string, uint64_t>> driveModelSmartStats;\n    double totalcapacity = 0;\n    double totalhours = 0;\n    double totaltbhours = 0;\n    uint64_t totaldrivecount = 0;\n    TableFormatterBase table;\n    std::string space = it->first;\n\n    if (format_case == DevicesProto::LsProto::MONITORING) {\n      table.SetHeader({\n        {\"key\", 0, \"os\"},\n        {\"space\", 5, \"os\"},\n        {\"id\", 5, \"l\"},\n        {\"model\", 0, \"-s\"},\n        {\"serial\", 0, \"-s\"},\n        {\"type\", 0, \"-s\"},\n        {\"capacity\", 0, \"l\"},\n        {\"rpms\", 0, \"l\"},\n        {\"poweronhours\", 0, \"l\"},\n        {\"temp\", 0, \"l\"},\n        {\"smart\", 0, \"s\"},\n        {\"if\", 0, \"-s\"},\n        {\"rla\", 0, \"-s\"},\n        {\"wc\", 0, \"-s\"}\n      });\n    } else {\n      table.SetHeader({\n        {it->first, 12, \"+l\"},\n        {\"model\", 0, \"-s\"},\n        {\"serial\", 0, \"-s\"},\n        {\"type\", 0, \"-s\"},\n        {\"capacity\", 0, \"+l\"},\n        {\"rpms\", 0, \"l\"},\n        {\"poweron[h]\", 0, \"l\"},\n        {\"temp[degrees]\", 0, \"l\"},\n        {\"S.M.A.R.T\", 0, \"s\"},\n        {\"if\", 0, \"-s\"},\n        {\"rla\", 0, \"-s\"},\n        {\"wc\", 0, \"-s\"}\n      });\n    }\n\n    auto jinfo = gOFS->mDeviceTracker->getJson();\n    auto spinfo = gOFS->mDeviceTracker->getSpaceMap();\n    auto sminfo = gOFS->mDeviceTracker->getSmartMap();\n    std::vector<std::string> smStatus {\"OK\", \"no smartctl\", \"N/A\", \"FAILING\", \"Check\", \"invalid\", \"unknown\"};\n    std::vector<std::string> smHuman {\"ok\", \"noctl\", \"na\", \"failing\", \"check\", \"inval\", \"unknown\"};\n\n    if (!jinfo || !spinfo) {\n      if (!WantsJsonOutput()) {\n        reply.set_std_err(\"error: not yet availabe - try again\");\n        reply.set_retc(EAGAIN);\n      } else {\n        reply.set_std_err(\"{ \\\"errmsg\\\" : \\\"not yet available -try again\\\", \\\"errc\\\" : 11 }\");\n        reply.set_retc(EAGAIN);\n      }\n\n      return ;\n    }\n\n    for (auto it = jinfo->begin(); it != jinfo->end(); ++it) {\n      std::string smartstatus = \"unknown\";\n      auto id = it->first;\n\n      if (!spinfo->count(id)) {\n        // fs not mapped to a space\n        continue;\n      }\n\n      if (space != (*spinfo)[id]) {\n        // no in this printout\n        continue;\n      }\n\n      auto sm = sminfo->find(id);\n\n      if (sm != sminfo->end()) {\n        smartstatus = sm->second;\n      }\n\n      Json::Value root;\n      std::string errs;\n      Json::CharReaderBuilder jsonReaderBuilder;\n      std::unique_ptr<Json::CharReader> const reader(\n        jsonReaderBuilder.newCharReader());\n      const std::string& ojson = it->second;\n\n      if (reader->parse(ojson.c_str(), ojson.c_str() + ojson.size(), &root, &errs)) {\n        try {\n          std::string model     = root.isMember(\"model_name\") ?\n                                  root[\"model_name\"].asString() : \"unknown\";\n          std::replace(model.begin(), model.end(), ' ', ':');\n          std::string serial     = root.isMember(\"serial_number\") ?\n                                   root[\"serial_number\"].asString() : \"unknown\";\n          std::string dtype     = (root.isMember(\"device\") &&\n                                   root[\"device\"].isMember(\"type\")) ? root[\"device\"][\"type\"].asString() :\n                                  \"unknown\";\n          uint64_t capacity     = (root.isMember(\"user_capacity\") &&\n                                   root[\"user_capacity\"].isMember(\"bytes\")) ?\n                                  root[\"user_capacity\"][\"bytes\"].asUInt64() : 0;\n          uint64_t rpms         = (root.isMember(\"rotation_rate\")) ?\n                                  root[\"rotation_rate\"].asUInt64() : 0;\n          uint64_t powerhours   = (root.isMember(\"power_on_time\") &&\n                                   root[\"power_on_time\"].isMember(\"hours\")) ?\n                                  root[\"power_on_time\"][\"hours\"].asUInt64() : 0;\n          uint64_t temperature  = (root.isMember(\"temperature\") &&\n                                   root[\"temperature\"].isMember(\"current\")) ?\n                                  root[\"temperature\"][\"current\"].asUInt64() : 0;\n          std::string ifspeed   = (root.isMember(\"interface_speed\")\n                                   && root[\"interface_speed\"].isMember(\"max\")\n                                   && root[\"interface_speed\"][\"max\"].isMember(\"string\")) ?\n                                  root[\"interface_speed\"][\"max\"][\"string\"].asString() : \"unknown\";\n          std::replace(ifspeed.begin(), ifspeed.end(), ' ', ':');\n          std::string read_lookahead = (root.isMember(\"read_lookahead\")) ?\n                                       (root[\"read_lookahead\"][\"enabled\"].asBool() ? \"true\" : \"false\") : \"unknown\";\n          std::string write_cache    = (root.isMember(\"write_cache\")) ?\n                                       (root[\"write_cache\"][\"enabled\"].asBool() ? \"true\" : \"false\") : \"unknown\";\n\n          if (model.length()) {\n            driveModelStats[model][\"count\"]++;\n            totaldrivecount++;\n            driveModelStats[model][\"bytes\"] += capacity;\n            driveModelStats[model][\"hours\"] += powerhours;\n            totalcapacity += capacity;\n            totalhours += powerhours;\n            totaltbhours += (capacity * powerhours / 1000000000000.0);\n\n            if (!driveModelSmartStats.count(model)) {\n              // make sure we have each smart status in the smart stats map\n              for (size_t i = 0; i < smStatus.size(); ++i) {\n                driveModelSmartStats[model][smHuman[i]] = 0;\n              }\n            }\n\n            for (size_t i = 0; i < smStatus.size(); ++i) {\n              if (sm->second == smStatus[i]) {\n                smartstatus = smHuman[i];\n                driveModelSmartStats[model][smartstatus]++;\n                break;\n              }\n            }\n          }\n\n          TableData body;\n          TableRow row;\n\n          if (format_case == DevicesProto::LsProto::MONITORING) {\n            row.emplace_back(\"deviceinfo\", \"os\");\n            row.emplace_back(space, \"s\");\n          }\n\n          row.emplace_back((unsigned long long)id, \"l\");\n          row.emplace_back(model, \"-s\");\n          row.emplace_back(serial, \"-s\");\n          row.emplace_back(dtype, \"-s\");\n\n          if (format_case == DevicesProto::LsProto::MONITORING) {\n            row.emplace_back((unsigned long long)capacity, \"l\", \"B\");\n          } else {\n            row.emplace_back((unsigned long long)capacity, \"+l\", \"B\");\n          }\n\n          row.emplace_back((unsigned long long)rpms, \"l\");\n\n          if (format_case == DevicesProto::LsProto::MONITORING) {\n            row.emplace_back((unsigned long long)powerhours, \"l\");\n          } else {\n            row.emplace_back((unsigned long long)powerhours, \"+l\", \"h\");\n          }\n\n          row.emplace_back((unsigned long long)temperature, \"l\");\n          row.emplace_back(smartstatus, \"s\");\n          row.emplace_back(ifspeed, \"-s\");\n          row.emplace_back(read_lookahead, \"-s\");\n          row.emplace_back(write_cache, \"-s\");\n          body.push_back(row);\n          table.AddRows(body);\n        } catch (Json::Exception const&) {\n          std_out += \"fatal: json exception has been thrown\\n\";\n          eos_static_crit(\"err=\\\"catched JSON exception\\\"\");\n        }\n      }\n\n      gjson[\"extractiontime\"][\"timestamp\"] = (Json::Value::UInt64)extractionTime;\n      gjson[\"extractiontime\"][\"localtime\"] = extractionLocalTime;\n      gjson[\"space\"][space][\"filesystem\"][std::to_string(id)] = root;\n    }\n\n    if (format_case == DevicesProto::LsProto::LISTING) {\n      std_out += table.GenerateTable();\n    }\n\n    if (format_case == DevicesProto::LsProto::MONITORING) {\n      std_out += table.GenerateTable();\n    }\n\n    if (true) { // we might add a switch to suppress this output later\n      TableFormatterBase table;\n      TableHeader header;\n      TableData body;\n\n      if (format_case == DevicesProto::LsProto::MONITORING) {\n        header.push_back(std::make_tuple(\"key\", 0, \"os\"));\n        header.push_back(std::make_tuple(\"model\", 0, \"os\"));\n      } else {\n        header.push_back(std::make_tuple(\"space\", 0, \"+s\"));\n        header.push_back(std::make_tuple(\"model\", 0, \"-s\"));\n      }\n\n      auto fst = driveModelStats.begin();\n\n      if (fst != driveModelStats.end()) {\n        if (format_case == DevicesProto::LsProto::MONITORING) {\n          header.push_back(std::make_tuple(\"avg:age:years\", 0, \"f\"));\n        } else {\n          header.push_back(std::make_tuple(\"avg:age[years]\", 0, \"f\"));\n        }\n\n        for (auto it = fst->second.begin(); it != fst->second.end(); ++it) {\n          if (format_case == DevicesProto::LsProto::MONITORING) {\n            header.push_back(std::make_tuple(it->first, 0, \"l\"));\n          } else {\n            header.push_back(std::make_tuple(it->first, 0, \"+l\"));\n          }\n        }\n      }\n\n      for (auto i = smHuman.begin(); i != smHuman.end(); ++i) {\n        header.push_back(std::make_tuple(std::string(\"smrt:\") + *i, 0, \"os\"));\n      }\n\n      table.SetHeader(header);\n\n      for (auto it = driveModelStats.begin(); it != driveModelStats.end(); ++it) {\n        double avgage = driveModelStats[it->first][\"hours\"] /\n                        driveModelStats[it->first][\"count\"] / 24.0 / 365.0;\n        TableRow row;\n\n        if (format_case == DevicesProto::LsProto::MONITORING) {\n          row.emplace_back(\"devicestats\", \"os\");\n        } else {\n          row.emplace_back(space.c_str(), \"s\");\n        }\n\n        row.emplace_back(it->first, \"-s\");\n        row.emplace_back(avgage, \"f\");\n        gjson[\"statistics\"][it->first][\"avg:age:years\"] = avgage;\n\n        for (auto iit = it->second.begin(); iit != it->second.end(); ++iit) {\n          if (format_case == DevicesProto::LsProto::MONITORING) {\n            row.emplace_back((unsigned long long)iit->second, \"l\");\n          } else {\n            if (iit->first == \"bytes\") {\n              row.emplace_back((unsigned long long)iit->second, \"+l\", \"B\");\n            } else if (iit->first == \"hours\") {\n              row.emplace_back((unsigned long long)iit->second, \"+l\", \"h\");\n            } else {\n              row.emplace_back((unsigned long long)iit->second, \"+l\");\n            }\n          }\n\n          gjson[\"statistics\"][it->first][iit->first] = (Json::Value::UInt64)iit->second;\n        }\n\n        for (size_t i = 0; i < smHuman.size(); ++i) {\n          row.emplace_back((unsigned long long)\n                           driveModelSmartStats[it->first][smHuman[i]], \"l\");\n        }\n\n        body.push_back(row);\n      }\n\n      table.AddRows(body);\n\n      if (!WantsJsonOutput()) {\n        std_out += table.GenerateTable();\n      }\n    }\n\n    if (true) { // we might add a switch to suppress this output later\n      TableFormatterBase table;\n      TableHeader header;\n      TableData body;\n\n      if (format_case == DevicesProto::LsProto::MONITORING) {\n        header.push_back(std::make_tuple(\"key\", 0, \"os\"));\n        header.push_back(std::make_tuple(\"tbyears\", 0, \"of\"));\n        header.push_back(std::make_tuple(\"driveage\", 0, \"of\"));\n        header.push_back(std::make_tuple(\"drivehours\", 0, \"ol\"));\n        header.push_back(std::make_tuple(\"clouddollar-replica\", 0, \"ol\"));\n        header.push_back(std::make_tuple(\"clouddollar-erasure\", 0, \"ol\"));\n      } else {\n        header.push_back(std::make_tuple(\"Cost-Matrix\", 0, \"+s\"));\n        header.push_back(std::make_tuple(\"TB*Years\", 0, \"+l\"));\n        header.push_back(std::make_tuple(\"Avg-Drive-Hours\", 6, \"+l\"));\n        header.push_back(std::make_tuple(\"Tot-Drive-Hours\", 0, \"+l\"));\n        header.push_back(std::make_tuple(\"Cloud$-Replica\", 0, \"+l\"));\n        header.push_back(std::make_tuple(\"Cloud$-Erasure\", 0, \"+l\"));\n      }\n\n      table.SetHeader(header);\n      double volyears = totalcapacity * totalhours / 24.0 / 365.0;\n      double tbyears = totaltbhours / 24.0 / 365.0;\n      double tage = totalhours / (totaldrivecount ? totaldrivecount : 1000000);\n      double cloudinstancecost = tbyears * 250.0; // assume 250 cloud$ per tb/year\n      gjson[\"cost\"][\"vol:years\"] = volyears;\n      gjson[\"cost\"][\"tb:years\"] = tbyears;\n      gjson[\"cost\"][\"avg-drive-hours\"] = tage;\n      gjson[\"cost\"][\"tot-drive-hours\"] = totalhours;\n      gjson[\"cost\"][\"cloud-dollar-replica\"] = cloudinstancecost / 2.0;\n      gjson[\"cost\"][\"cloud-dollar-erasure\"] = cloudinstancecost / 1.2;\n      TableRow row;\n\n      if (format_case == DevicesProto::LsProto::MONITORING) {\n        row.emplace_back(tbyears, \"f\");\n        row.emplace_back(tage, \"l\");\n        row.emplace_back(totalhours, \"l\");\n        row.emplace_back(cloudinstancecost / 2.0, \"l\");\n        row.emplace_back(cloudinstancecost / 1.2, \"l\");\n      } else {\n        row.emplace_back(gOFS->MgmOfsInstanceName.c_str(), \"s\");\n        row.emplace_back(tbyears, \"+l\");\n        row.emplace_back(tage, \"+l\");\n        row.emplace_back(totalhours, \"+l\");\n        row.emplace_back(cloudinstancecost / 2.0, \"+l\", \"$\");\n        row.emplace_back(cloudinstancecost / 1.2, \"+l\", \"$\");\n      }\n\n      body.push_back(row);\n      table.AddRows(body);\n\n      if (!WantsJsonOutput()) {\n        std_out += table.GenerateTable();\n      }\n    }\n  }\n\n  if (WantsJsonOutput()) {\n    std_out = SSTR(gjson).c_str();\n  }\n\n  reply.set_std_out(std_out);\n  reply.set_retc(0);\n}\nEOSMGMNAMESPACE_END\n\n"
  },
  {
    "path": "mgm/proc/admin/DevicesCmd.hh",
    "content": "//------------------------------------------------------------------------------\n// @file: DevicesCmd.hh\n// @author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"proto/Devices.pb.h\"\n#include \"mgm/proc/ProcCommand.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n//------------------------------------------------------------------------------\n//! Class DevicesCmd - class handling devices commands\n//------------------------------------------------------------------------------\nclass DevicesCmd: public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit DevicesCmd(eos::console::RequestProto&& req,\n                    eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, false)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~DevicesCmd() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Execute ls subcommand\n  //!\n  //! @param ls ls subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void LsSubcmd(const eos::console::DevicesProto_LsProto& ls,\n                eos::console::ReplyProto& reply);\n\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/EvictCmd.cc",
    "content": "//------------------------------------------------------------------------------\n// File: EvictCmd.cc\n// Author: Jozsef Makai - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Path.hh\"\n#include \"common/Timing.hh\"\n#include \"EvictCmd.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/EosCtaReporter.hh\"\n#include \"common/Definitions.hh\"\n#include \"common/Constants.hh\"\n#include \"namespace/interface/IView.hh\"\n#include <optional>\n\nEOSMGMNAMESPACE_BEGIN\n\neos::console::ReplyProto\neos::mgm::EvictCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  std::ostringstream errStream;\n  std::ostringstream outStream;\n  bool allReplicasRemoved = false;\n  int ret_c = 0;\n  eos::console::RequestProto req = mReqProto;\n\n  const auto& evict = req.evict();\n  // TODO: Replace the code removed above by this line\n  // const auto& evict = mReqProto.evict();\n  XrdOucErrInfo errInfo;\n  eos::common::VirtualIdentity root_vid = eos::common::VirtualIdentity::Root();\n  struct timespec ts_now;\n  eos::common::Timing::GetTimeSpec(ts_now);\n  std::optional<uint64_t> fsid =\n    evict.has_evictsinglereplica() ? std::optional(\n      evict.evictsinglereplica().fsid()) : std::nullopt;\n  bool ignoreEvictCounter = evict.ignoreevictcounter();\n  bool ignoreRemovalOnFst = evict.ignoreremovalonfst();\n\n  if (fsid.has_value() && !ignoreEvictCounter) {\n    reply.set_retc(EINVAL);\n    errStream << \"error: Parameter 'fsid' can only be used with 'ignore-evict-counter'\" <<\n              std::endl;\n    reply.set_std_err(errStream.str());\n    reply.set_std_out(outStream.str());\n    return reply;\n  }\n\n  if(ignoreRemovalOnFst && !fsid.has_value()) {\n    reply.set_retc(EINVAL);\n    errStream << \"error: Parameter 'ignore-removal-on-fst' can only be used with 'fsid'\" <<\n        std::endl;\n    reply.set_std_err(errStream.str());\n    reply.set_std_out(outStream.str());\n    return reply;\n  }\n\n  int count_some_disk_replicas_removed = 0;\n  int count_all_disk_replicas_removed = 0;\n  int count_evict_counter_not_zero = 0;\n\n  for (int i = 0; i < evict.file_size(); i++) {\n    EosCtaReporterEvict eosLog;\n    eosLog\n    .addParam(EosCtaReportParam::SEC_APP, \"tape_evict\")\n    .addParam(EosCtaReportParam::LOG, std::string(gOFS->logId))\n    .addParam(EosCtaReportParam::RUID, mVid.uid)\n    .addParam(EosCtaReportParam::RGID, mVid.gid)\n    .addParam(EosCtaReportParam::TD, mVid.tident.c_str())\n    .addParam(EosCtaReportParam::TS, ts_now.tv_sec)\n    .addParam(EosCtaReportParam::TNS, ts_now.tv_nsec);\n    const auto& file = evict.file(i);\n    std::string path;\n    std::string err;\n\n    switch (file.File_case()) {\n    case eos::console::EvictProto::FileProto::kPath:\n      path = file.path();\n\n      if (0 == path.length()) {\n        errStream << \"error: Received an empty string path\";\n        ret_c = EINVAL;\n        eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());\n        continue;\n      }\n\n      eosLog.addParam(EosCtaReportParam::PATH, path);\n      break;\n\n    case eos::console::EvictProto::FileProto::kFid:\n      GetPathFromFid(path, file.fid(), err);\n\n      if (0 == path.length()) {\n        errStream << \"error: Received an unknown fid: value=\" << file.fid();\n        ret_c = EINVAL;\n        eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());\n        continue;\n      }\n\n      eosLog.addParam(EosCtaReportParam::PATH, path);\n      break;\n\n    default:\n      errStream << \"error: Received a file with neither a path nor an fid\";\n      ret_c = EINVAL;\n      eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());\n      continue;\n    }\n\n    // check that we have the correct permission\n    eos::common::Path cPath(path.c_str());\n    errInfo.clear();\n\n    if (gOFS->_access(cPath.GetParentPath(), P_OK, errInfo, mVid, \"\") != 0) {\n      errStream << \"error: you don't have 'p' acl flag permission on path '\"\n                << cPath.GetParentPath() << \"'\";\n      ret_c = EPERM;\n      eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());\n      continue;\n    }\n\n    // check if this file exists\n    XrdSfsFileExistence file_exists;\n    errInfo.clear();\n\n    if (gOFS->_exists(path.c_str(), file_exists, errInfo, mVid, nullptr)) {\n      errStream << \"error: unable to run exists on path '\" << path << \"'\";\n      ret_c = errno;\n      eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());\n      continue;\n    }\n\n    if (file_exists == XrdSfsFileExistNo) {\n      errStream << \"error: no such file with path '\" << path << \"'\";\n      ret_c = ENODATA;\n      eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());\n      continue;\n    } else if (file_exists == XrdSfsFileExistIsDirectory) {\n      errStream << \"error: given path is a directory '\" << path << \"'\";\n      ret_c = EINVAL;\n      eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());\n      continue;\n    }\n\n    struct stat buf;\n\n    if (gOFS->_stat(path.c_str(), &buf, errInfo, mVid, nullptr, nullptr,\n                    false) != 0) {\n      errStream << \"error: unable to run stat for replicas on path '\" << path << \"'\";\n      ret_c = EINVAL;\n      eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());\n      continue;\n    }\n\n    // we don't remove anything if it's not on tape\n    if ((buf.st_mode & EOS_TAPE_MODE_T) == 0) {\n      errStream << \"error: no tape replicas for file '\" << path << \"'\";\n      ret_c = EINVAL;\n      eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());\n      continue;\n    }\n\n    int diskReplicaCount = 0;\n    XrdOucString options;\n\n    if (fsid.has_value()) {\n      auto fmd = gOFS->eosView->getFile(path.c_str());\n      bool diskReplicaFound = false;\n\n      for (auto location : fmd->getLocations()) {\n        // Ignore tape replica\n        if (location == eos::common::TAPE_FS_ID) {\n          continue;\n        }\n\n        if (location == fsid.value()) {\n          diskReplicaFound = true;\n        }\n\n        ++diskReplicaCount;\n      }\n\n      if (!diskReplicaFound) {\n        eos_static_err(\"msg=\\\"unable to find disk replica of %s\\\" fsid=\\\"%u\\\" reason=\\\"%s\\\"\",\n                       path.c_str(), fsid.value(), errInfo.getErrText());\n        errStream << \"error: unable to find disk replica of '\" << path << \"'\";\n        eosLog.addParam(EosCtaReportParam::EVICTCMD_FSID,  fsid.value());\n        eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());\n        ret_c = SFS_ERROR;\n        continue;\n      }\n    } else {\n      auto fmd = gOFS->eosView->getFile(path.c_str());\n\n      for (auto location : fmd->getLocations()) {\n        // Ignore tape replica\n        if (location == eos::common::TAPE_FS_ID) {\n          continue;\n        }\n\n        ++diskReplicaCount;\n      }\n\n      if (diskReplicaCount == 0) {\n        eos_static_err(\"msg=\\\"unable to find any disk replica of %s\\\" reason=\\\"%s\\\"\",\n                       path.c_str(), errInfo.getErrText());\n        errStream << \"error: unable to find any disk replica of '\" << path << \"'\";\n        eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());\n        ret_c = SFS_ERROR;\n        continue;\n      }\n    }\n\n    errInfo.clear();\n\n    if (fsid.has_value() && ignoreEvictCounter) {\n      // Drop single stripe\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wmaybe-uninitialized\"\n      if (gOFS->_dropstripe(path.c_str(), 0, errInfo, root_vid, fsid.value(),\n                            ignoreRemovalOnFst) != 0) {\n        eos_static_err(\"msg=\\\"could not delete replica of %s\\\" fsid=\\\"%u\\\" reason=\\\"%s\\\"\",\n                       path.c_str(), fsid.value(), errInfo.getErrText());\n        errStream << \"error: could not delete replica of '\" << path << \"'\";\n        eosLog.addParam(EosCtaReportParam::EVICTCMD_FSID, fsid.value());\n        eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());\n        ret_c = SFS_ERROR;\n      } else {\n        if (diskReplicaCount <= 1) {\n          allReplicasRemoved = true;\n          count_all_disk_replicas_removed++;\n        } else {\n          count_some_disk_replicas_removed++;\n        }\n      }\n#pragma GCC diagnostic pop\n    } else {\n      // May drop all stripes\n      if (!ignoreEvictCounter) {\n        // Check the eviction counter first, if not force\n        int evictionCounter = 0;\n\n        try {\n          eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n          auto fmd = gOFS->eosView->getFile(path.c_str());\n\n          if (fmd->hasAttribute(eos::common::RETRIEVE_EVICT_COUNTER_NAME)) {\n            evictionCounter = std::stoi(fmd->getAttribute(\n                                          eos::common::RETRIEVE_EVICT_COUNTER_NAME));\n          }\n\n          eosLog.addParam(EosCtaReportParam::EVICTCMD_EVICTCOUNTER, evictionCounter);\n          evictionCounter = std::max(0, evictionCounter - 1);\n          fmd->setAttribute(eos::common::RETRIEVE_EVICT_COUNTER_NAME,\n                            std::to_string(evictionCounter));\n          gOFS->eosView->updateFileStore(fmd.get());\n        } catch (eos::MDException& ex) {\n          eos_static_err(\"msg=\\\"could not update eviction counter for file %s\\\"\",\n                         path.c_str());\n        }\n\n        if (evictionCounter > 0) {\n          // Do not remove if eviction counter not zero\n          eosLog.addParam(EosCtaReportParam::EVICTCMD_FILEREMOVED, false);\n          count_evict_counter_not_zero++;\n          continue;\n        }\n      }\n\n      // Drop all stripes\n      if (gOFS->_dropallstripes(path.c_str(), errInfo, root_vid) != 0) {\n        eos_static_err(\"msg=\\\"could not delete all disk replicas of %s\\\" reason=\\\"%s\\\"\",\n                       path.c_str(), errInfo.getErrText());\n        errStream << \"error: could not delete all disk replicas of '\" << path << \"'\";\n        eosLog.addParam(EosCtaReportParam::EVICTCMD_ERROR, errStream.str());\n        ret_c = SFS_ERROR;\n      } else {\n        count_all_disk_replicas_removed++;\n        allReplicasRemoved = true;\n      }\n    }\n\n    if (allReplicasRemoved) {\n      // reset the retrieves counter in case of success\n      eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n\n      try {\n        auto fmd = gOFS->eosView->getFile(path.c_str());\n        fmd->setAttribute(eos::common::RETRIEVE_REQID_ATTR_NAME, \"\");\n        fmd->setAttribute(eos::common::RETRIEVE_REQTIME_ATTR_NAME, \"\");\n        fmd->removeAttribute(eos::common::RETRIEVE_EVICT_COUNTER_NAME);\n        gOFS->eosView->updateFileStore(fmd.get());\n      } catch (eos::MDException& ex) {\n        eos_static_err(\"msg=\\\"could not reset Prepare request ID list or eviction counter for \"\n                       \"file %s. Try removing the %s, %s or %s attributes\\\"\",\n                       path.c_str(), eos::common::RETRIEVE_REQID_ATTR_NAME,\n                       eos::common::RETRIEVE_REQTIME_ATTR_NAME,\n                       eos::common::RETRIEVE_EVICT_COUNTER_NAME);\n      }\n\n      if (fsid.has_value()) {\n        eosLog.addParam(EosCtaReportParam::EVICTCMD_FSID, fsid.value());\n      }\n\n      eosLog.addParam(EosCtaReportParam::EVICTCMD_FILEREMOVED, true);\n    }\n  }\n\n  reply.set_retc(ret_c);\n  reply.set_std_err(errStream.str());\n  std::string stdout_reply_s;\n\n  if ((count_all_disk_replicas_removed + count_some_disk_replicas_removed +\n       count_evict_counter_not_zero) > 0) {\n    if (fsid.has_value()) {\n      outStream << \"found and removed the fsid=\" << fsid.value() <<\n                \" disk replica for \"\n                << (count_all_disk_replicas_removed + count_some_disk_replicas_removed)\n                << \"/\" << evict.file_size() << \" files\";\n    } else  {\n      outStream << \"removed all disk replicas for \"\n                << count_all_disk_replicas_removed << \"/\" << evict.file_size() << \" files\";\n\n      if (!ignoreEvictCounter) {\n        outStream << \"; reduced evict counter for \" << count_evict_counter_not_zero <<\n                  \"/\"\n                  << evict.file_size() << \" files\";\n      }\n    }\n  }\n\n  reply.set_std_out(outStream.str());\n  return reply;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/EvictCmd.hh",
    "content": "//------------------------------------------------------------------------------\n// File: EvictCmd.hh\n// Author: Jozsef Makai - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n\n#include \"mgm/proc/IProcCommand.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"proto/ConsoleRequest.pb.h\"\n\nEOSMGMNAMESPACE_BEGIN\n\nclass EvictCmd : public IProcCommand\n{\npublic:\n//----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  EvictCmd(eos::console::RequestProto&& req,\n              eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, true) {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~EvictCmd() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/FileRegisterCmd.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: QuotaCmd.cc\n// @author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"QuotaCmd.hh\"\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/policy/Policy.hh\"\n#include \"common/Path.hh\"\n#include \"common/Constants.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"namespace/interface/IQuota.hh\"\n#include \"mgm/proc/admin/FileRegisterCmd.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Method implementing the specific behavior of the command executed by the\n// asynchronous thread\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nFileRegisterCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  eos::console::FileRegisterProto reg = mReqProto.record();\n  eos::common::Path cPath(reg.path());\n  std::shared_ptr<eos::IContainerMD> dir;\n  std::shared_ptr<eos::IFileMD> fmd;\n  eos::IContainerMD::XAttrMap attrmap;\n  {\n    eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView,\n        cPath.GetParentPath());\n    eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n\n    // Check for the parent directory\n    try {\n      dir = gOFS->eosView->getContainer(cPath.GetParentPath());\n      attrmap = dir->getAttributes();\n      std::string name = cPath.GetName();\n      auto exists = dir->findItem(name);\n\n      if (exists.value().file || exists.value().container) {\n        if (reg.update() && exists.value().file) {\n          fmd = dir->findFile(name.c_str());\n        } else {\n          reply.set_retc(EEXIST);\n          reply.set_std_err(\"file already exists\");\n          return reply;\n        }\n      } else {\n        if (reg.update()) {\n          reply.set_retc(ENOENT);\n          reply.set_std_err(\"no suche file\");\n          return reply;\n        }\n      }\n\n      uid_t uid = reg.owner().uid();\n      uid_t gid = reg.owner().gid();\n\n      if (reg.owner().username().length()) {\n        int errc = 0;\n        uid = eos::common::Mapping::UserNameToUid(reg.owner().username().c_str(), errc);\n      }\n\n      if (reg.owner().groupname().length()) {\n        int errc = 0;\n        gid = eos::common::Mapping::GroupNameToGid(reg.owner().groupname().c_str(),\n              errc);\n      }\n\n      if (!reg.update()) {\n        // create with given uid/gid\n        fmd = gOFS->eosView->createFile(cPath.GetFullPath().c_str(), uid, gid);\n      } else {\n        if (uid) {\n          fmd->setCUid(uid);\n        }\n\n        if (gid) {\n          fmd->setCGid(gid);\n        }\n      }\n\n      if (reg.mode()) {\n        //store mode\n        fmd->setFlags(reg.mode());\n      }\n\n      if (reg.checksum().length()) {\n        //store checksum\n        size_t out_sz = 0;\n        auto xs_binary = eos::common::StringConversion::Hex2BinDataChar\n                         (reg.checksum(), out_sz, SHA256_DIGEST_LENGTH);\n        eos::Buffer xs_buff;\n        xs_buff.putData(xs_binary.get(), SHA256_DIGEST_LENGTH);\n        fmd->setChecksum(xs_buff);\n      }\n\n      if (reg.ctime().sec()) {\n        // add ctime\n        struct timespec tvp;\n        tvp.tv_sec = reg.ctime().sec();\n        tvp.tv_nsec = reg.ctime().nsec();\n        fmd->setCTime(tvp);\n      }\n\n      if (reg.mtime().sec()) {\n        // add mtime\n        struct timespec tvp;\n        tvp.tv_sec = reg.mtime().sec();\n        tvp.tv_nsec = reg.mtime().nsec();\n        fmd->setMTime(tvp);\n      }\n\n      if (reg.atime().sec()) {\n\t// add atime\n\tstruct timespec tvp;\n\tstruct timespec tvnow;\n\ttvp.tv_sec = reg.atime().sec();\n\ttvp.tv_nsec = reg.atime().nsec();\n\n\tif (reg.atimeifnewer()) {\n\t  // only update if the input atime is actually newer than the existing one\n\t  fmd->getATime(tvnow);\n\t  if ( (tvp.tv_sec > tvnow.tv_sec) ||\n\t       ( (tvp.tv_sec == tvnow.tv_sec) && (tvp.tv_nsec > tvnow.tv_nsec) ) ) {\n\t    fmd->setATime(tvp);\n\t  } else {\n\t    reply.set_std_out(\"warning: atime is not newer than existing one\");\n\t  }\n\t} else {\n\t  fmd->setATime(tvp);\n\t}\n      }\n\n      if (reg.btime().sec()) {\n        // add btime\n        char btime[256];\n        snprintf(btime, sizeof(btime), \"%lu.%lu\", reg.btime().sec(),\n                 reg.btime().nsec());\n        fmd->setAttribute(\"sys.eos.btime\", btime);\n      } else {\n        // add btime\n        char btime[256];\n        snprintf(btime, sizeof(btime), \"%lu.%lu\", time(NULL), 0l);\n        fmd->setAttribute(\"sys.eos.btime\", btime);\n      }\n\n      // add locations\n      for (const auto& fsid : reg.locations()) {\n        if ((fsid > 0) && (fsid <= eos::common::TAPE_FS_ID)) {\n          fmd->addLocation(fsid);\n        }\n      }\n\n      // add xattr\n      for (const auto& elem : reg.attr()) {\n        fmd->setAttribute(elem.first, elem.second);\n      }\n\n      if (reg.layoutid()) {\n        fmd->setLayoutId(reg.layoutid());\n      } else {\n        // automatically get a layout id for this registration\n        unsigned long layoutId;\n        std::string space;\n        XrdOucEnv env;\n        unsigned long forcedfsid = 0;\n        long forcedgroup = 0;\n        std::string bandwidth;\n        bool schedule = false;\n        std::string iopriority;\n        std::string iotype;\n        Policy::GetLayoutAndSpace(cPath.GetFullPath().c_str(),\n                                  attrmap,\n                                  vid,\n                                  layoutId,\n                                  space,\n                                  env,\n                                  forcedfsid,\n                                  forcedgroup,\n                                  bandwidth,\n                                  schedule,\n                                  iopriority,\n                                  iotype,\n                                  true,\n                                  false);\n        fmd->setLayoutId(layoutId);\n      }\n\n      try {\n        eos::IQuotaNode* ns_quota = gOFS->eosView->getQuotaNode(dir.get());\n\n        if (ns_quota) {\n          if (!reg.update()) {\n            fmd->setSize(reg.size());\n            ns_quota->addFile(fmd.get());\n          } else {\n            ns_quota->removeFile(fmd.get());\n            fmd->setSize(reg.size());\n          }\n        } else {\n          fmd->setSize(reg.size());\n        }\n      } catch (const eos::MDException& eq) {\n        // no quota node\n      }\n\n      gOFS->eosView->updateFileStore(fmd.get());\n      dir->setMTimeNow();\n      gOFS->eosView->updateContainerStore(dir.get());\n      lock.Release();\n      dir->notifyMTimeChange(gOFS->eosDirectoryService);\n      return reply;\n    } catch (eos::MDException& e) {\n      eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                e.getErrno(), e.getMessage().str().c_str());\n      dir.reset();\n      reply.set_retc(e.getErrno());\n      reply.set_std_err(e.getMessage().str().c_str());\n      return reply;\n    }\n  }\n  reply.set_retc(EINVAL);\n  reply.set_std_err(\"error: not supported\");\n  return reply;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/FileRegisterCmd.hh",
    "content": "//------------------------------------------------------------------------------\n// @file: FileRegisterCmd.hh\n// @author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"proto/File.pb.h\"\n#include \"mgm/proc/ProcCommand.hh\"\n\n\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class FileRegisterCmd - class handling file registration\n//------------------------------------------------------------------------------\nclass FileRegisterCmd: public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit FileRegisterCmd(eos::console::RequestProto&& req,\n                    eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, false)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~FileRegisterCmd() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\nprivate:\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/FsCmd.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FsCmd.cc\n// Author: Jozsef Makai - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n* EOS - the CERN Disk Storage System                                   *\n* Copyright (C) 2017 CERN/Switzerland                                  *\n*                                                                      *\n* This program is free software: you can redistribute it and/or modify *\n* it under the terms of the GNU General Public License as published by *\n* the Free Software Foundation, either version 3 of the License, or    *\n* (at your option) any later version.                                  *\n*                                                                      *\n* This program is distributed in the hope that it will be useful,      *\n* but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n* GNU General Public License for more details.                         *\n*                                                                      *\n* You should have received a copy of the GNU General Public License    *\n* along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n************************************************************************/\n\n#include \"FsCmd.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/proc/proc_fs.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/placement/FsScheduler.hh\"\n#include \"common/LayoutId.hh\"\n#include \"namespace/interface/IFsView.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include <XrdOuc/XrdOucTokenizer.hh>\n#include <unordered_set>\n\nEOSMGMNAMESPACE_BEGIN\n\nstatic const uint64_t FS_DUMPMD_MAX_NUM_ENTRIES = 100000;\nXrdSysSemaphore eos::mgm::FsCmd::mSemaphore{5};\n\n//------------------------------------------------------------------------------\n// Method implementing the specific behaviour of the command executed by the\n// asynchronous thread\n//------------------------------------------------------------------------------\neos::console::ReplyProto\neos::mgm::FsCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  eos::console::FsProto fs = mReqProto.fs();\n  const auto& subCmdCase = fs.subcmd_case();\n\n  if (subCmdCase == eos::console::FsProto::SubcmdCase::kAdd) {\n    reply.set_retc(Add(fs.add()));\n  } else if (subCmdCase == eos::console::FsProto::SubcmdCase::kBoot) {\n    reply.set_retc(Boot(fs.boot()));\n  } else if (subCmdCase == eos::console::FsProto::SubcmdCase::kClone) {\n    reply.set_retc(Clone(fs.clone()));\n  } else if (subCmdCase == eos::console::FsProto::SubcmdCase::kCompare) {\n    reply.set_retc(Compare(fs.compare()));\n  } else if (subCmdCase == eos::console::FsProto::SubcmdCase::kConfig) {\n    reply.set_retc(Config(fs.config()));\n  } else if (subCmdCase == eos::console::FsProto::SubcmdCase::kDropdel) {\n    reply.set_retc(DropDeletion(fs.dropdel()));\n  } else if (subCmdCase == eos::console::FsProto::SubcmdCase::kDropghosts) {\n    reply.set_retc(DropGhosts(fs.dropghosts()));\n  } else if (subCmdCase == eos::console::FsProto::SubcmdCase::kDropfiles) {\n    reply.set_retc(DropFiles(fs.dropfiles()));\n  } else if (subCmdCase == eos::console::FsProto::SubcmdCase::kDumpmd) {\n    reply.set_retc(DumpMd(fs.dumpmd()));\n  } else if (subCmdCase == eos::console::FsProto::SubcmdCase::kLs) {\n    mOut = List(fs.ls());\n    reply.set_retc(0);\n  } else if (subCmdCase == eos::console::FsProto::SubcmdCase::kMv) {\n    reply.set_retc(Mv(fs.mv()));\n  } else if (subCmdCase == eos::console::FsProto::SubcmdCase::kRm) {\n    reply.set_retc(Rm(fs.rm()));\n  } else if (subCmdCase == eos::console::FsProto::SubcmdCase::kStatus) {\n    reply.set_retc(Status(fs.status()));\n  } else {\n    reply.set_retc(EINVAL);\n    mErr = \"error: not supported\";\n  }\n\n  reply.set_std_out(mOut);\n  reply.set_std_err(mErr);\n  return reply;\n}\n\n//------------------------------------------------------------------------------\n// Add subcommand\n//------------------------------------------------------------------------------\nint\nFsCmd::Add(const eos::console::FsProto::AddProto& addProto)\n{\n  std::string sfsid = addProto.manual() ? std::to_string(addProto.fsid()) : \"0\";\n  std::string uuid = addProto.uuid();\n  std::string nodequeue = addProto.nodequeue();\n\n  // If nodequeue is empty then we have the host or even the host and the port\n  if (nodequeue.empty()) {\n    if (addProto.hostport().empty()) {\n      mErr = \"error: no nodequeue or or hostport specified\";\n      return EINVAL;\n    }\n\n    nodequeue = \"/eos/\";\n    nodequeue += addProto.hostport();\n\n    // If only hostname present then append default FST port number 1095\n    if (nodequeue.find(':') == std::string::npos) {\n      nodequeue += \":1095\";\n    }\n\n    nodequeue += \"/fst\";\n  }\n\n  std::string mountpoint = addProto.mountpoint();\n  std::string space = addProto.schedgroup();\n  std::string configstatus = addProto.status();\n  std::string sharedfs = addProto.sharedfs();\n  XrdOucString out, err;\n  mRetc = proc_fs_add(gOFS->mMessagingRealm.get(), sfsid, uuid, nodequeue,\n                      mountpoint, space, configstatus, sharedfs, out, err, mVid);\n  gOFS->mFsScheduler->updateClusterData();\n  mOut = out.c_str() != nullptr ? out.c_str() : \"\";\n  mErr = err.c_str() != nullptr ? err.c_str() : \"\";\n  return mRetc;\n}\n\n//------------------------------------------------------------------------------\n// Boot subcommand\n//------------------------------------------------------------------------------\nint\nFsCmd::Boot(const eos::console::FsProto::BootProto& bootProto)\n{\n  std::ostringstream outStream, errStream;\n\n  if ((mVid.uid == 0) || (mVid.prot == \"sss\")) {\n    std::string node = (bootProto.id_case() ==\n                        eos::console::FsProto::BootProto::kNodeQueue ?\n                        bootProto.nodequeue() : \"\");\n    std::string sfsid = (bootProto.id_case() ==\n                         eos::console::FsProto::BootProto::kFsid ?\n                         std::to_string(bootProto.fsid()) : \"0\");\n    std::string fsuuid = (bootProto.id_case() ==\n                          eos::console::FsProto::BootProto::kUuid ?\n                          bootProto.uuid() : \"\");\n    eos::common::FileSystem::eBootConfig bootConfig =\n      eos::common::FileSystem::kBootOptional;\n\n    if (bootProto.syncmgm()) {\n      bootConfig = eos::common::FileSystem::kBootMgm;\n    } else if (bootProto.syncdisk()) {\n      bootConfig = eos::common::FileSystem::kBootDisk;\n    }\n\n    // @note it would be nicer if the method get refactored\n    eos::common::FileSystem::fsid_t fsid = 0;\n\n    try {\n      fsid = std::stoi(sfsid);\n    } catch (const std::exception& e) {\n      fsid = 0;\n    }\n\n    if (node == \"*\") {\n      // boot all filesystems\n      if (mVid.uid == 0) {\n        eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n        outStream << \"success: boot message sent to\";\n\n        for (const auto id : FsView::gFsView.mIdView) {\n          if ((id.second->GetConfigStatus() > eos::common::ConfigStatus::kOff)) {\n            auto now = time(nullptr);\n            id.second->SetLongLong(\"bootcheck\", bootConfig);\n            id.second->SetLongLong(\"bootsenttime\", (unsigned long long) now);\n            outStream << \" \";\n            outStream << id.second->GetString(\"host\").c_str();\n            outStream << \":\";\n            outStream << id.second->GetString(\"path\").c_str();\n          }\n        }\n      } else {\n        mRetc = EPERM;\n        errStream << \"error: you have to take role 'root' to execute this command\";\n      }\n    } else if (node.length()) {\n      // boot all filesystems on node queue\n      eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n\n      if (!FsView::gFsView.mNodeView.count(node)) {\n        errStream << \"error: cannot boot node - no node with name=\";\n        errStream << node.c_str();\n        mRetc = ENOENT;\n      } else {\n        outStream << \"success: boot message sent to\";\n\n        for (auto it = FsView::gFsView.mNodeView[node]->begin();\n             it != FsView::gFsView.mNodeView[node]->end(); ++it) {\n          FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it);\n\n          if (fs != nullptr) {\n            auto now = time(nullptr);\n            fs->SetLongLong(\"bootcheck\", bootConfig);\n            fs->SetLongLong(\"bootsenttime\", ((now > 0) ? now : 0));\n            outStream << \" \";\n            outStream << fs->GetString(\"host\").c_str();\n            outStream << \":\";\n            outStream << fs->GetString(\"path\").c_str();\n          }\n        }\n      }\n    } else {\n      // boot filesystem by fsid or uuid\n      FileSystem* fs = nullptr;\n      eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n\n      if (fsid) {\n        fs = FsView::gFsView.mIdView.lookupByID(fsid);\n      } else if (fsuuid.length()) {\n        if (FsView::gFsView.GetMapping(fsuuid)) {\n          fs = FsView::gFsView.mIdView.lookupByID(FsView::gFsView.GetMapping(fsuuid));\n        }\n      }\n\n      if (fs != nullptr) {\n        fs->SetLongLong(\"bootcheck\", bootConfig);\n        fs->SetLongLong(\"bootsenttime\", (unsigned long long) time(nullptr));\n        outStream << \"success: boot message sent to \";\n        outStream << fs->GetString(\"host\").c_str();\n        outStream << \":\";\n        outStream << fs->GetString(\"path\").c_str();\n      } else {\n        // Should not get here\n        errStream << \"error: fail boot, could not retrieve filesystem with \"\n                  << \"identifier \" << (sfsid.empty() ? fsuuid : sfsid);\n        mRetc = ENOENT;\n      }\n    }\n  } else {\n    mRetc = EPERM;\n    errStream << \"error: you have to take role 'root' or connect via 'sss' \"\n              \"to execute this command\";\n  }\n\n  mOut = outStream.str();\n  mErr = errStream.str();\n  return mRetc;\n}\n\n//------------------------------------------------------------------------------\n// Config subcommand\n//------------------------------------------------------------------------------\nint\nFsCmd::Config(const eos::console::FsProto::ConfigProto& configProto)\n{\n  auto key = configProto.key();\n  auto value = configProto.value();\n  std::string identifier = std::to_string(configProto.fsid());\n  XrdOucString out, err;\n  mRetc = proc_fs_config(identifier, key, value, out, err,\n                         mVid, mComment.c_str());\n  mOut = (out.c_str() != nullptr ? out.c_str() : \"\");\n  mErr = (err.c_str() != nullptr ? err.c_str() : \"\");\n  return mRetc;\n}\n\n//------------------------------------------------------------------------------\n// Dropdeletion subcommand\n//------------------------------------------------------------------------------\nint\nFsCmd::DropDeletion(const eos::console::FsProto::DropDeletionProto& drop_del)\n{\n  std::string out, err;\n  eos::common::RWMutexReadLock rd_lock(FsView::gFsView.ViewMutex);\n  mRetc = proc_fs_dropdeletion(drop_del.fsid(), mVid, out, err);\n  mOut = out;\n  mErr = err;\n  return mRetc;\n}\n\n//------------------------------------------------------------------------------\n// DropGhosts subcommand\n//------------------------------------------------------------------------------\nint\nFsCmd::DropGhosts(const eos::console::FsProto::DropGhostsProto& drop_ghosts)\n{\n  std::string out, err;\n  std::set<eos::IFileMD::id_t> fids;\n  fids.insert(drop_ghosts.fids().cbegin(), drop_ghosts.fids().cend());\n  eos::common::RWMutexReadLock rd_lock(FsView::gFsView.ViewMutex);\n  mRetc = proc_fs_dropghosts(drop_ghosts.fsid(), fids, mVid, out, err);\n  mOut = out;\n  mErr = err;\n  return mRetc;\n}\n\n//------------------------------------------------------------------------------\n// Dumpmd subcommand\n//------------------------------------------------------------------------------\nint\nFsCmd::DumpMd(const eos::console::FsProto::DumpMdProto& dumpmd_proto)\n{\n  XrdOucString out, err;\n\n  if ((mVid.uid == 0) || (mVid.prot == \"sss\")) {\n    // Stall if the namespace is still booting\n    while (!gOFS->IsNsBooted()) {\n      std::this_thread::sleep_for(std::chrono::seconds(2));\n    }\n\n    std::string sfsid = std::to_string(dumpmd_proto.fsid());\n\n    if (dumpmd_proto.showcount()) {\n      unsigned long long num_files =\n        gOFS->eosFsView->getNumFilesOnFs(dumpmd_proto.fsid());\n      out = \"num_files=\";\n      out += std::to_string(num_files).c_str();\n    } else {\n      XrdOucString option = dumpmd_proto.display() ==\n                            eos::console::FsProto::DumpMdProto::MONITOR ? \"m\" : \"\";\n      XrdOucString dp = dumpmd_proto.showpath() ? \"1\" : \"0\";\n      XrdOucString df = dumpmd_proto.showfid() ? \"1\" : \"0\";\n      XrdOucString ds = dumpmd_proto.showsize() ? \"1\" : \"0\";\n      size_t entries = 0;\n      mRetc = SemaphoreProtectedProcDumpmd(sfsid, option, dumpmd_proto.showpath(),\n                                           dumpmd_proto.showfid(),\n                                           dumpmd_proto.showfxid(),\n                                           dumpmd_proto.showsize(),\n                                           out, err, entries);\n\n      if (!mRetc) {\n        gOFS->MgmStats.Add(\"DumpMd\", mVid.uid, mVid.gid, entries);\n      }\n    }\n  } else {\n    mRetc = EPERM;\n    err = \"error: you have to take role 'root' or connect via 'sss' \"\n          \"to execute this command\";\n  }\n\n  mOut = out.c_str() != nullptr ? out.c_str() : \"\";\n  mErr = err.c_str() != nullptr ? err.c_str() : \"\";\n  return mRetc;\n}\n\n//------------------------------------------------------------------------------\n// List subcommand\n//------------------------------------------------------------------------------\nstd::string\neos::mgm::FsCmd::List(const eos::console::FsProto::LsProto& lsProto)\n{\n  using eos::console::FsProto;\n  bool json_output = false;\n  std::string output;\n\n  // Handle listing of drain jobs\n  if ((lsProto.display() == FsProto::LsProto::RUNNING_DRAIN_JOBS) ||\n      (lsProto.display() == FsProto::LsProto::FAILED_DRAIN_JOBS)) {\n    bool only_failed =\n      (lsProto.display() == FsProto::LsProto::FAILED_DRAIN_JOBS);\n    eos::mgm::Drainer::DrainHdrInfo hdr_info;\n\n    if (only_failed) {\n      hdr_info = {{\"File id\",    \"fid\"},\n        {\"Drain fsid\", \"fs_src\"},\n        {\"Dst fsid\",   \"fs_dst\"},\n        {\"Error info\", \"err_msg\"}\n      };\n    } else {\n      hdr_info = {{\"File id\",     \"fid\"},\n        {\"Drain fsid\",  \"fs_src\"},\n        {\"Src fsid\",    \"tx_fs_src\"},\n        {\"Dst fsid\",    \"fs_dst\"},\n        {\"Start times\", \"start_timestamp\"},\n        {\"Progress\",    \"progress\"},\n        {\"Avg.(MB/s)\",  \"speed\"}\n      };\n    }\n\n    unsigned int fsid {0};\n\n    // If matchlist is present then it must be an fsid\n    if (!lsProto.matchlist().empty()) {\n      try {\n        fsid = std::stoul(lsProto.matchlist());\n      } catch (...) {\n        // ignore\n      }\n    }\n\n    if (!gOFS->mDrainEngine.GetJobsInfo(output, hdr_info, fsid, only_failed)) {\n      output = \"error: failed while collecting drain jobs info\";\n    }\n\n    return output;\n  }\n\n  auto display = lsProto.display();\n\n  if ((display == FsProto::LsProto::DEFAULT) && WantsJsonOutput()) {\n    display = FsProto::LsProto::MONITOR;\n  }\n\n  if (display == FsProto::LsProto::MONITOR) {\n    json_output = WantsJsonOutput();\n  }\n\n  auto display_string = DisplayModeToString(display);\n  auto format = FsView::GetFileSystemFormat(display_string);\n\n  if (!lsProto.brief()) {\n    if (format.find('S') != std::string::npos) {\n      format.replace(format.find('S'), 1, \"s\");\n    }\n  }\n\n  {\n    eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n    FsView::gFsView.PrintSpaces(output, \"\", format, 0,\n                                lsProto.matchlist().c_str(),\n                                display_string, mReqProto.dontcolor());\n  }\n\n  if (json_output) {\n    output = ResponseToJsonString(output);\n  }\n\n  return output;\n}\n\n//------------------------------------------------------------------------------\n// Mv subcommand\n//------------------------------------------------------------------------------\nint\nFsCmd::Mv(const eos::console::FsProto::MvProto& mvProto)\n{\n  if (mVid.uid == 0) {\n    std::string source = mvProto.src();\n    std::string dest = mvProto.dst();\n    bool force = mvProto.force();\n    XrdOucString out, err;\n    mRetc = proc_fs_mv(source, dest, out, err, mVid, force,\n                       gOFS->mMessagingRealm.get());\n    // do a blanket refresh of internal state\n    // TODO fine grain to group/disk status here\n    gOFS->mFsScheduler->updateClusterData();\n    mOut = out.c_str() != nullptr ? out.c_str() : \"\";\n    mErr = err.c_str() != nullptr ? err.c_str() : \"\";\n  } else {\n    mRetc = EPERM;\n    mErr = \"error: you have to take role 'root' to execute this command\";\n  }\n\n  return mRetc;\n}\n\n//------------------------------------------------------------------------------\n// Rm subcommand\n//------------------------------------------------------------------------------\nint\nFsCmd::Rm(const eos::console::FsProto::RmProto& rmProto)\n{\n  std::string nodequeue;\n  std::string mountpoint;\n  std::string id = (rmProto.id_case() == eos::console::FsProto::RmProto::kFsid ?\n                    std::to_string(rmProto.fsid()) : \"\");\n\n  if (rmProto.id_case() == eos::console::FsProto::RmProto::kNodeQueue) {\n    const auto& hostmountpoint = rmProto.nodequeue();\n    auto splitAt = hostmountpoint.find(\"/fst\");\n\n    try { // @note quick patch against std::out_of_range, could be nicer\n      nodequeue = hostmountpoint.substr(0, splitAt + 4);\n      mountpoint = hostmountpoint.substr(splitAt + 4);\n    } catch (std::out_of_range& e) {\n      mOut = \"\";\n      mErr = \"error: there is no such nodequeue (check format): '\" +\n             rmProto.nodequeue() + \"' \" + id + \"\\n\";\n      mRetc = EINVAL;\n      return mRetc;\n    }\n  }\n\n  XrdOucString out, err;\n  {\n    eos::common::RWMutexWriteLock wr_lock(FsView::gFsView.ViewMutex);\n    mRetc = proc_fs_rm(nodequeue, mountpoint, id, out, err, mVid);\n  }\n  gOFS->mFsScheduler->updateClusterData();\n  mOut = out.c_str() != nullptr ? out.c_str() : \"\";\n  mErr = err.c_str() != nullptr ? err.c_str() : \"\";\n  return mRetc;\n}\n\n//------------------------------------------------------------------------------\n// Status subcommand\n//------------------------------------------------------------------------------\nint\nFsCmd::Status(const eos::console::FsProto::StatusProto& statusProto)\n{\n  std::ostringstream outStream, errStream;\n\n  if ((mVid.uid == 0) || (mVid.prot == \"sss\")) {\n    eos::common::FileSystem::fsid_t fsid = 0;\n    XrdOucString filelisting = \"\";\n    bool listfile = false;\n    bool riskanalysis = false;\n\n    if (statusProto.longformat()) {\n      listfile = true;\n      riskanalysis = true;\n    }\n\n    if (statusProto.riskassessment()) {\n      riskanalysis = true;\n    }\n\n    if (statusProto.id_case() == eos::console::FsProto::StatusProto::kNodeQueue) {\n      eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n      const std::string& queuepath = statusProto.nodequeue();\n      auto pos = queuepath.find(\"/fst\");\n      const std::string queue = queuepath.substr(0, pos + 4);\n      const std::string mount = queuepath.substr(pos + 4);\n\n      if (FsView::gFsView.mNodeView.count(queue)) {\n        for (auto it = FsView::gFsView.mNodeView[queue]->begin();\n             it != FsView::gFsView.mNodeView[queue]->end(); ++it) {\n          FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it);\n\n          if (fs && fs->GetPath() == mount) {\n            // this is the filesystem\n            fsid = *it;\n          }\n        }\n      }\n\n      if (!fsid) {\n        errStream << \"error: no such filesystem \" << queuepath;\n        mErr = errStream.str();\n        mRetc = ENOENT;\n        return mRetc;\n      }\n    } else {\n      fsid = statusProto.fsid();\n    }\n\n    bool fs_found = false;\n    const std::string dotted_line =\n      \"# ------------------------------------------------------------------------------------\\n\";\n    {\n      eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n      FileSystem* fs = FsView::gFsView.mIdView.lookupByID(fsid);\n\n      if (fs) {\n        fs_found = true;\n        outStream << dotted_line.c_str();\n        outStream << \"# FileSystem Variables\\n\";\n        outStream << dotted_line.c_str();\n        std::vector<std::string> keylist;\n        fs->GetKeys(keylist);\n        std::sort(keylist.begin(), keylist.end());\n\n        for (auto& key : keylist) {\n          char line[1024];\n          snprintf(line, sizeof(line) - 1, \"%-32s := %s\\n\", key.c_str(),\n                   fs->GetString(key.c_str()).c_str());\n          outStream << line;\n        }\n      } else {\n        errStream << \"error: cannot find filesystem - no filesystem with fsid=\";\n        errStream << fsid;\n        mRetc = ENOENT;\n        return mRetc;\n      }\n    }\n\n    if (fs_found && riskanalysis) {\n      outStream << dotted_line.c_str();\n      outStream << \"# Risk Analysis\\n\";\n      outStream << dotted_line.c_str();\n      // get some statistics about the filesystem\n      //-------------------------------------------\n      unsigned long long nfids = 0;\n      unsigned long long nfids_healthy = 0;\n      unsigned long long nfids_risky = 0;\n      unsigned long long nfids_inaccessible = 0;\n      unsigned long long nfids_todelete = 0;\n      eos::Prefetcher::prefetchFilesystemFileListWithFileMDsAndParentsAndWait(\n        gOFS->eosView, gOFS->eosFsView, fsid);\n      eos::common::RWMutexReadLock viewLock(gOFS->eosViewRWMutex);\n\n      try {\n        nfids_todelete = gOFS->eosFsView->getNumUnlinkedFilesOnFs(fsid);\n        nfids = gOFS->eosFsView->getNumFilesOnFs(fsid);\n\n        for (auto it_fid = gOFS->eosFsView->getFileList(fsid);\n             (it_fid && it_fid->valid()); it_fid->next()) {\n          std::shared_ptr<eos::IFileMD> fmd =\n            gOFS->eosFileService->getFileMD(it_fid->getElement());\n\n          if (fmd) {\n            size_t nloc_ok = 0;\n            size_t nloc = fmd->getNumLocation();\n\n            for (auto& loc : fmd->getLocations()) {\n              if (loc) {\n                FileSystem* repfs = FsView::gFsView.mIdView.lookupByID(loc);\n\n                if (repfs) {\n                  eos::common::FileSystem::fs_snapshot_t snapshot;\n                  repfs->SnapShotFileSystem(snapshot, false);\n\n                  if ((snapshot.mStatus == eos::common::BootStatus::kBooted) &&\n                      (snapshot.mConfigStatus == eos::common::ConfigStatus::kRW) &&\n                      (snapshot.mErrCode == 0) && // this we probably don't need\n                      (snapshot.mActiveStatus == eos::common::ActiveStatus::kOnline)) {\n                    nloc_ok++;\n                  }\n                }\n              }\n            }\n\n            if (eos::common::LayoutId::GetLayoutType(fmd->getLayoutId()) ==\n                eos::common::LayoutId::kReplica) {\n              if (nloc_ok == nloc) {\n                nfids_healthy++;\n              } else {\n                if (nloc_ok == 0) {\n                  nfids_inaccessible++;\n\n                  if (listfile) {\n                    filelisting += \"status=offline path=\";\n                    filelisting += gOFS->eosView->getUri(fmd.get()).c_str();\n                    filelisting += \"\\n\";\n                  }\n                } else {\n                  if (nloc_ok < nloc) {\n                    nfids_risky++;\n\n                    if (listfile) {\n                      filelisting += \"status=atrisk  path=\";\n                      filelisting += gOFS->eosView->getUri(fmd.get()).c_str();\n                      filelisting += \"\\n\";\n                    }\n                  }\n                }\n              }\n            }\n\n            if (eos::common::LayoutId::GetLayoutType(fmd->getLayoutId()) ==\n                eos::common::LayoutId::kPlain) {\n              if (nloc_ok != nloc) {\n                nfids_inaccessible++;\n\n                if (listfile) {\n                  filelisting += \"status=offline path=\";\n                  filelisting += gOFS->eosView->getUri(fmd.get()).c_str();\n                  filelisting += \"\\n\";\n                }\n              }\n            }\n          }\n        }\n\n        XrdOucString sizestring;\n        char line[1024];\n        snprintf(line, sizeof(line) - 1, \"%-32s := %10s (%.02f%%)\\n\", \"number of files\",\n                 eos::common::StringConversion::GetSizeString(sizestring, nfids), 100.0);\n        outStream << line;\n        snprintf(line, sizeof(line) - 1, \"%-32s := %10s (%.02f%%)\\n\", \"files healthy\",\n                 eos::common::StringConversion::GetSizeString(sizestring, nfids_healthy),\n                 nfids ? (100.0 * nfids_healthy) / nfids : 100.0);\n        outStream << line;\n        snprintf(line, sizeof(line) - 1, \"%-32s := %10s (%.02f%%)\\n\", \"files at risk\",\n                 eos::common::StringConversion::GetSizeString(sizestring, nfids_risky),\n                 nfids ? (100.0 * nfids_risky) / nfids : 100.0);\n        outStream << line;\n        snprintf(line, sizeof(line) - 1, \"%-32s := %10s (%.02f%%)\\n\",\n                 \"files inaccessible\", eos::common::StringConversion::GetSizeString(sizestring,\n                     nfids_inaccessible), nfids ? (100.0 * nfids_inaccessible) / nfids : 100.0);\n        outStream << line;\n        snprintf(line, sizeof(line) - 1, \"%-32s := %10s\\n\", \"files pending deletion\",\n                 eos::common::StringConversion::GetSizeString(sizestring, nfids_todelete));\n        outStream << line;\n        outStream << dotted_line.c_str();\n\n        if (listfile) {\n          outStream << filelisting;\n        }\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        eos_static_err(\"caught exception %d %s\\n\", e.getErrno(),\n                       e.getMessage().str().c_str());\n      }\n    }\n\n    mRetc = 0;\n  } else {\n    mRetc = EPERM;\n    errStream << \"error: you have to take role 'root' to execute this command \"\n              \"or connect via sss\";\n  }\n\n  mOut = outStream.str();\n  mErr = errStream.str();\n  return mRetc;\n}\n\n//------------------------------------------------------------------------------\n// Drop files attached to a file system by file id\n//------------------------------------------------------------------------------\nint\nFsCmd::DropFiles(const eos::console::FsProto::DropFilesProto& dropfilesProto)\n{\n  XrdOucErrInfo errInfo;\n  auto filesDeleted = 0u;\n  // Create a snapshot to avoid deadlock with dropstripe\n  std::vector<eos::common::FileId::fileid_t> fileids;\n  {\n    eos::common::RWMutexReadLock rlock(gOFS->eosViewRWMutex);\n\n    for (auto it_fid = gOFS->eosFsView->getFileList(dropfilesProto.fsid());\n         (it_fid && it_fid->valid()); it_fid->next()) {\n      try {\n        fileids.push_back(it_fid->getElement());\n      } catch (eos::MDException& e) {\n        eos_err(\"msg=\\\"failed to get metadata, ignore it\\\" fxid=%08llx\",\n                it_fid->getElement());\n      }\n    }\n  }\n\n  for (const auto& fid : fileids) {\n    errInfo.clear();\n\n    if (gOFS->_dropstripe(\"\", fid, errInfo, mVid, dropfilesProto.fsid(),\n                          dropfilesProto.force()) != 0) {\n      eos_err(\"msg=\\\"failed to  delete replica\\\" fxid=%08llx fsid=%lu\",\n              fid, dropfilesProto.fsid());\n    } else {\n      filesDeleted++;\n    }\n  }\n\n  std::ostringstream oss;\n  oss << \"Deleted \" << filesDeleted << \" replicas on filesystem \" <<\n      dropfilesProto.fsid() << std::endl;\n  mOut = oss.str();\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Compare two file systemd in therm of the files they contain\n//------------------------------------------------------------------------------\nint\nFsCmd::Compare(const eos::console::FsProto::CompareProto& compareProto)\n{\n  std::string filePath;\n  std::unordered_set<std::string, Murmur3::MurmurHasher<std::string>>\n      sourceHash, targetHash;\n  {\n    eos::common::RWMutexReadLock rlock(gOFS->eosViewRWMutex);\n\n    for (auto it_fid = gOFS->eosFsView->getFileList(compareProto.sourceid());\n         (it_fid && it_fid->valid()); it_fid->next()) {\n      try {\n        auto fmd = gOFS->eosFileService->getFileMD(it_fid->getElement());\n        filePath = gOFS->eosView->getUri(fmd.get());\n        sourceHash.insert(filePath);\n      } catch (eos::MDException& e) {}\n    }\n\n    for (auto it_fid = gOFS->eosFsView->getFileList(compareProto.targetid());\n         (it_fid && it_fid->valid()); it_fid->next()) {\n      try {\n        auto fmd = gOFS->eosFileService->getFileMD(it_fid->getElement());\n        filePath = gOFS->eosView->getUri(fmd.get());\n        targetHash.insert(filePath);\n      } catch (eos::MDException& e) {}\n    }\n  }\n  std::ostringstream resultStream;\n\n  for (const auto& source : sourceHash) {\n    if (targetHash.find(source) == targetHash.end()) {\n      resultStream << \"path=\" << source << \" => found in \" << compareProto.sourceid()\n                   << \" - missing in \" << compareProto.targetid() << std::endl;\n    }\n  }\n\n  for (const auto& target : targetHash) {\n    if (sourceHash.find(target) == sourceHash.end()) {\n      resultStream << \"path=\" << target << \" => found in \" << compareProto.targetid()\n                   << \" - missing in \" << compareProto.sourceid() << std::endl;\n    }\n  }\n\n  mOut = resultStream.str();\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Clone the contents of one file system to another\n//------------------------------------------------------------------------------\nint\nFsCmd::Clone(const eos::console::FsProto::CloneProto& cloneProto)\n{\n  std::string filePath;\n  XrdOucErrInfo errInfo;\n  auto success = 0u;\n  eos::common::RWMutexReadLock rlock(gOFS->eosViewRWMutex);\n\n  for (auto it_fid = gOFS->eosFsView->getFileList(cloneProto.sourceid());\n       (it_fid && it_fid->valid()); it_fid->next()) {\n    try {\n      auto fmd = gOFS->eosFileService->getFileMD(it_fid->getElement());\n      filePath = gOFS->eosView->getUri(fmd.get());\n      errInfo.clear();\n\n      if (gOFS->_copystripe(filePath.c_str(), errInfo, mVid,\n                            cloneProto.sourceid(), cloneProto.targetid()) == 0) {\n        success++;\n      }\n    } catch (eos::MDException& e) {\n      eos_err(\"Could not get metadata, could not clone file replica %ul on filesystem\",\n              it_fid->getElement());\n    }\n  }\n\n  std::ostringstream oss;\n  oss << \"Successfully replicated \" << success << \" files.\" << std::endl;\n  mOut = oss.str();\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Convert display mode flags to strings\n//------------------------------------------------------------------------------\nstd::string\neos::mgm::FsCmd::DisplayModeToString(eos::console::FsProto::LsProto::DisplayMode\n                                     mode)\n{\n  switch (mode) {\n  case eos::console::FsProto::LsProto::LONG:\n    return \"l\";\n\n  case eos::console::FsProto::LsProto::MONITOR:\n    return \"m\";\n\n  case eos::console::FsProto::LsProto::DRAIN:\n    return \"d\";\n\n  case eos::console::FsProto::LsProto::ERROR:\n    return \"e\";\n\n  case eos::console::FsProto::LsProto::FSCK:\n    return \"fsck\";\n\n  case eos::console::FsProto::LsProto::IO:\n    return \"io\";\n\n  default:\n    return \"\";\n  }\n}\n\nint\nFsCmd::SemaphoreProtectedProcDumpmd(std::string& sfsid, XrdOucString& option,\n                                    bool show_path, bool show_fid,\n                                    bool show_fxid, bool show_size,\n                                    XrdOucString& out, XrdOucString& err,\n                                    size_t& entries)\n{\n  try {\n    mSemaphore.Wait();\n  } catch (...) {\n    err += \"error: failed while waiting on semaphore, cannot dumpmd\";\n    return EAGAIN;\n  }\n\n  eos::IFileMD::location_t fsid = 0ul;\n\n  try {\n    fsid = std::stoul(sfsid);\n  } catch (...) {\n    eos_static_err(\"msg=\\\"failed numeric conversion of file system id\\\" \"\n                   \"sfsid=\\\"%s\\\"\", sfsid.c_str());\n    err = \"error: non-numeric file system id\";\n    return EINVAL;\n  }\n\n  // Get number of file entries on the give file systems\n  unsigned long long num_files = gOFS->eosFsView->getNumFilesOnFs(fsid);\n\n  if (num_files > FS_DUMPMD_MAX_NUM_ENTRIES) {\n    eos_static_err(\"msg=\\\"forbid dumpmd on file system with too many files\\\" \"\n                   \"fsid=%lu\", fsid);\n    err = \"error: too many entries (>100k) on file system to dump them all\";\n    return EFBIG;\n  }\n\n  int lretc = proc_fs_dumpmd(sfsid, option, show_path, show_fid, show_fxid,\n                             show_size, out, err, mVid, entries);\n\n  try {\n    mSemaphore.Post();\n  } catch (...) {}\n\n  return lretc;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/FsCmd.hh",
    "content": "//------------------------------------------------------------------------------\n// File: FsCmd.hh\n// Author: Jozsef Makai - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n* EOS - the CERN Disk Storage System                                   *\n* Copyright (C) 2017 CERN/Switzerland                                  *\n*                                                                      *\n* This program is free software: you can redistribute it and/or modify *\n* it under the terms of the GNU General Public License as published by *\n* the Free Software Foundation, either version 3 of the License, or    *\n* (at your option) any later version.                                  *\n*                                                                      *\n* This program is distributed in the hope that it will be useful,      *\n* but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n* GNU General Public License for more details.                         *\n*                                                                      *\n* You should have received a copy of the GNU General Public License    *\n* along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n************************************************************************/\n\n#pragma once\n#include \"mgm/proc/IProcCommand.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"proto/ConsoleRequest.pb.h\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class FsCmd\n//------------------------------------------------------------------------------\nclass FsCmd : public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  FsCmd(eos::console::RequestProto&& req,\n        eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, true), mRetc(0) {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~FsCmd() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\nprivate:\n  //! Methods implementing individual subcommands\n  std::string List(const eos::console::FsProto::LsProto& lsProto);\n\n  int Config(const eos::console::FsProto::ConfigProto& configProto);\n\n  int Mv(const eos::console::FsProto::MvProto& mvProto);\n\n  int Rm(const eos::console::FsProto::RmProto& rmProto);\n\n  int DropDeletion(const eos::console::FsProto::DropDeletionProto& drop_del);\n\n  int DropGhosts(const eos::console::FsProto::DropGhostsProto& drop_ghosts);\n\n  int Add(const eos::console::FsProto::AddProto& addProto);\n\n  int Boot(const eos::console::FsProto::BootProto& bootProto);\n\n  int DumpMd(const eos::console::FsProto::DumpMdProto& dumpmdProto);\n\n  int Status(const eos::console::FsProto::StatusProto& statusProto);\n\n  int DropFiles(const eos::console::FsProto::DropFilesProto& dropfilesProto);\n\n  int Compare(const eos::console::FsProto::CompareProto& compareProto);\n\n  int Clone(const eos::console::FsProto::CloneProto& cloneProto);\n\n  std::string DisplayModeToString(eos::console::FsProto::LsProto::DisplayMode\n                                  mode);\n\n  int SemaphoreProtectedProcDumpmd(std::string& fsid, XrdOucString& option,\n                                   bool show_path, bool show_fid,\n                                   bool show_fxid, bool show_size,\n                                   XrdOucString& out, XrdOucString& err,\n                                   size_t& entries);\n\n  template <class T, std::size_t N>\n  static constexpr std::size_t SizeOfArray(const T(&array)[N]) noexcept\n  {\n    return N;\n  }\n\n  static XrdSysSemaphore mSemaphore;\n  std::string mOut; ///< Command output string\n  std::string mErr; ///< Command error output string\n  int mRetc; ///< Command return code\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/FsckCmd.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file FsckCmd.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"FsckCmd.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/fsck/Fsck.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Method implementing the specific behavior of the command executed\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nFsckCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  eos::console::FsckProto fsck = mReqProto.fsck();\n  eos::console::FsckProto::SubcmdCase subcmd = fsck.subcmd_case();\n\n  if ((subcmd != eos::console::FsckProto::kReport) && (mVid.uid != 0)) {\n    reply.set_retc(EPERM);\n    reply.set_std_err(\"error: only admin can execute this command\");\n    return reply;\n  }\n\n  if (subcmd == eos::console::FsckProto::kStat) {\n    const bool monitor_fmt = (mReqProto.format() ==\n                              eos::console::RequestProto_FormatType_FUSE);\n    std::string output;\n    gOFS->mFsckEngine->PrintOut(output, monitor_fmt);\n    reply.set_std_out(std::move(output));\n  } else if (subcmd == eos::console::FsckProto::kConfig) {\n    const eos::console::FsckProto::ConfigProto& config = fsck.config();\n    std::string msg;\n\n    if (!gOFS->mFsckEngine->Config(config.key(), config.value(), msg)) {\n      reply.set_retc(EINVAL);\n\n      if (msg.empty()) {\n        reply.set_std_err(SSTR(\"error: failed to set \" << config.key()\n                               << \"=\" << config.value()).c_str());\n      } else {\n        reply.set_std_err(msg);\n      }\n    }\n  } else if (subcmd == eos::console::FsckProto::kReport) {\n    const eos::console::FsckProto::ReportProto& report = fsck.report();\n    std::set<std::string> tags;\n\n    // Collect all the tags\n    for (const auto& elem : report.tags()) {\n      tags.insert(elem);\n    }\n\n    std::string out;\n\n    if (gOFS->mFsckEngine->Report(out, tags, report.display_per_fs(),\n                                  report.display_fxid(), report.display_lfn(),\n                                  report.display_json())) {\n      reply.set_std_out(out);\n    } else {\n      reply.set_retc(EINVAL);\n      reply.set_std_err(out);\n    }\n  } else if (subcmd == eos::console::FsckProto::kRepair) {\n    std::string out;\n    const eos::console::FsckProto::RepairProto& repair = fsck.repair();\n    const eos::common::FileSystem::fsid_t fsid_err = repair.fsid_err();\n\n    if (gOFS->mFsckEngine->RepairEntry(repair.fid(), {fsid_err},\n                                       repair.error(), repair.async(),\n                                       out)) {\n      reply.set_std_out(out);\n    } else {\n      reply.set_std_err(out);\n      reply.set_retc(EINVAL);\n    }\n  } else if (subcmd == eos::console::FsckProto::kCleanOrphans) {\n    const eos::console::FsckProto::CleanOrphansProto& clean = fsck.clean_orphans();\n    eos::common::FileSystem::fsid_t fsid = clean.fsid();\n    std::string query = \"/?fst.pcmd=clean_orphans&fst.fsid=\" + std::to_string(fsid);\n    std::set<std::string> endpoints;\n\n    if (fsid == 0ul) {\n      // Send command to all FSTs (nodes)\n      eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n\n      for (const auto& elem : FsView::gFsView.mNodeView) {\n        if (elem.second->GetActiveStatus() == eos::common::ActiveStatus::kOnline) {\n          eos_static_debug(\"msg=\\\"fsck clean_orphans\\\" hostport=\\\"%s\\\"\",\n                           elem.second->GetMember(\"hostport\").c_str());\n          endpoints.insert(elem.second->GetMember(\"hostport\"));\n        }\n      }\n\n      // Force clean QDB orphans irrespective of the actual cleanup on disk\n      if (clean.force_qdb_cleanup()) {\n        gOFS->mFsckEngine->ForceCleanQdbOrphans();\n      }\n    } else {\n      // Send command only to the corresponding FST (node)\n      eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n      auto* fs = FsView::gFsView.mIdView.lookupByID(fsid);\n\n      if (!fs) {\n        reply.set_retc(EINVAL);\n        reply.set_std_err(\"error: given file system does not exist\");\n        return reply;\n      }\n\n      std::ostringstream endpoint;\n      endpoint << fs->GetHost() << \":\" << fs->getCoreParams().getLocator().getPort();\n      endpoints.insert(endpoint.str());\n    }\n\n    // Map of responses from each individual endpoint\n    std::map<std::string, std::pair<int, std::string>> responses;\n\n    if (gOFS->BroadcastQuery(query, endpoints, responses)) {\n      std::ostringstream err_msg;\n      err_msg << \"error: failed orphans clean for the following endpoints\\n\";\n\n      for (const auto& elem : responses) {\n        if (elem.second.first) {\n          err_msg << \"node: \" << elem.first\n                  << \" errc: \" << elem.second.first\n                  << \" msg: \" << elem.second.second;\n        }\n      }\n\n      reply.set_std_err(err_msg.str());\n      reply.set_retc(EINVAL);\n    } else {\n      reply.set_std_out(\"info: orphans successfully cleaned\");\n    }\n  } else {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: not supported\");\n  }\n\n  return reply;\n}\n\n#ifdef EOS_GRPC_GATEWAY\n//------------------------------------------------------------------------------\n// Method implementing the specific behaviour of the command executed and\n// streaming the response via the grpc::ServerWriter\n//------------------------------------------------------------------------------\nvoid\nFsckCmd::ProcessRequest(grpc::ServerWriter<eos::console::ReplyProto>* writer)\n{\n  eos::console::ReplyProto StreamReply;\n  eos::console::FsckProto fsck = mReqProto.fsck();\n  eos::console::FsckProto::SubcmdCase subcmd = fsck.subcmd_case();\n\n  // Check for admin privileges\n  if ((subcmd != eos::console::FsckProto::kReport) && (mVid.uid != 0)) {\n    StreamReply.set_retc(EPERM);\n    StreamReply.set_std_err(\"error: only admin can execute this command\");\n    writer->Write(StreamReply);\n    return;\n  }\n\n  if (subcmd == eos::console::FsckProto::kStat) {\n    const bool monitor_fmt = (mReqProto.format() ==\n                              eos::console::RequestProto_FormatType_FUSE);\n    std::string output;\n    gOFS->mFsckEngine->PrintOut(output, monitor_fmt);\n    StreamReply.set_std_out(std::move(output));\n    writer->Write(StreamReply);\n  } else if (subcmd == eos::console::FsckProto::kConfig) {\n    const eos::console::FsckProto::ConfigProto& config = fsck.config();\n    std::string msg;\n\n    if (!gOFS->mFsckEngine->Config(config.key(), config.value(), msg)) {\n      StreamReply.set_retc(EINVAL);\n\n      if (msg.empty()) {\n        StreamReply.set_std_err(SSTR(\"error: failed to set \" << config.key()\n                                     << \"=\" << config.value()).c_str());\n      } else {\n        StreamReply.set_std_err(msg);\n      }\n    } else {\n      StreamReply.set_std_out(\"info: configuration applied successfully\");\n    }\n\n    writer->Write(StreamReply);\n  } else if (subcmd == eos::console::FsckProto::kReport) {\n    const eos::console::FsckProto::ReportProto& report = fsck.report();\n    std::set<std::string> tags;\n\n    // Collect all the tags\n    for (const auto& elem : report.tags()) {\n      tags.insert(elem);\n    }\n\n    std::string out;\n\n    if (gOFS->mFsckEngine->Report(out, tags, report.display_per_fs(),\n                                  report.display_fxid(), report.display_lfn(),\n                                  report.display_json())) {\n      StreamReply.set_std_out(out);\n      writer->Write(StreamReply);\n    } else {\n      StreamReply.set_retc(EINVAL);\n      StreamReply.set_std_err(out);\n      writer->Write(StreamReply);\n    }\n  } else if (subcmd == eos::console::FsckProto::kRepair) {\n    std::string out;\n    const eos::console::FsckProto::RepairProto& repair = fsck.repair();\n    const eos::common::FileSystem::fsid_t fsid_err = repair.fsid_err();\n\n    if (gOFS->mFsckEngine->RepairEntry(repair.fid(), {fsid_err},\n                                       repair.error(), repair.async(),\n                                       out)) {\n      StreamReply.set_std_out(out);\n    } else {\n      StreamReply.set_std_err(out);\n      StreamReply.set_retc(EINVAL);\n    }\n\n    writer->Write(StreamReply);\n  } else if (subcmd == eos::console::FsckProto::kCleanOrphans) {\n    const eos::console::FsckProto::CleanOrphansProto& clean = fsck.clean_orphans();\n    eos::common::FileSystem::fsid_t fsid = clean.fsid();\n    std::string query = \"/?fst.pcmd=clean_orphans&fst.fsid=\" + std::to_string(fsid);\n    std::set<std::string> endpoints;\n\n    if (fsid == 0ul) {\n      // Send command to all FSTs (nodes)\n      eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n\n      for (const auto& elem : FsView::gFsView.mNodeView) {\n        if (elem.second->GetActiveStatus() == eos::common::ActiveStatus::kOnline) {\n          eos_static_debug(\"msg=\\\"fsck clean_orphans\\\" hostport=\\\"%s\\\"\",\n                           elem.second->GetMember(\"hostport\").c_str());\n          endpoints.insert(elem.second->GetMember(\"hostport\"));\n        }\n      }\n\n      // Force clean QDB orphans irrespective of the actual cleanup on disk\n      if (clean.force_qdb_cleanup()) {\n        gOFS->mFsckEngine->ForceCleanQdbOrphans();\n      }\n    } else {\n      // Send command only to the corresponding FST (node)\n      eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n      auto* fs = FsView::gFsView.mIdView.lookupByID(fsid);\n\n      if (!fs) {\n        StreamReply.set_retc(EINVAL);\n        StreamReply.set_std_err(\"error: given file system does not exist\");\n        writer->Write(StreamReply);\n        return;\n      }\n\n      std::ostringstream endpoint;\n      endpoint << fs->GetHost() << \":\" << fs->getCoreParams().getLocator().getPort();\n      endpoints.insert(endpoint.str());\n    }\n\n    // Map of responses from each individual endpoint\n    std::map<std::string, std::pair<int, std::string>> responses;\n\n    if (gOFS->BroadcastQuery(query, endpoints, responses)) {\n      std::ostringstream err_msg;\n      err_msg << \"error: failed orphans clean for the following endpoints\\n\";\n\n      for (const auto& elem : responses) {\n        if (elem.second.first) {\n          err_msg << \"node: \" << elem.first\n                  << \" errc: \" << elem.second.first\n                  << \" msg: \" << elem.second.second;\n        }\n      }\n\n      StreamReply.set_std_err(err_msg.str());\n      StreamReply.set_retc(EINVAL);\n    } else {\n      StreamReply.set_std_out(\"info: orphans successfully cleaned\");\n    }\n\n    writer->Write(StreamReply);\n  } else {\n    StreamReply.set_retc(EINVAL);\n    StreamReply.set_std_err(\"error: not supported\");\n    writer->Write(StreamReply);\n  }\n}\n\n#endif\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/FsckCmd.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file FsckCmd.hh\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"proto/Fsck.pb.h\"\n#include \"mgm/proc/IProcCommand.hh\"\n\n#ifdef EOS_GRPC_GATEWAY\n#include \"proto/eos_rest_gateway/eos_rest_gateway_service.grpc.pb.h\"\n#endif\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class FsckCmd - class handling ns commands\n//------------------------------------------------------------------------------\nclass FsckCmd: public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit FsckCmd(eos::console::RequestProto&& req,\n                   eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, false)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~FsckCmd() = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed\n  //!\n  //! @return ReplyProto object which contains the full response\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\n#ifdef EOS_GRPC_GATEWAY\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed and\n  //! streaming the response via the grpc::ServerWriter\n  //!\n  //! @writer object used for streaming back the response\n  //----------------------------------------------------------------------------\n  void ProcessRequest(grpc::ServerWriter<eos::console::ReplyProto>* writer);\n#endif\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/Fusex.cc",
    "content": "// -----------------------------------------------------------------------------\n// File: proc/admin/Fusex.cc\n// Author: Andreas-Joachim Peters - CERN\n// -----------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/zmq/ZMQ.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Fusex()\n{\n  if (pVid->uid == 0) {\n    if (mSubCmd == \"ls\") {\n      std::string option = pOpaque->Get(\"mgm.option\") ? pOpaque->Get(\"mgm.option\") :\n                           \"\";\n      std::string out;\n      gOFS->zMQ->gFuseServer.Print(out, option);\n      stdOut += out.c_str();\n      retc = 0;\n    } else if (mSubCmd == \"conf\") {\n      std::string hb = pOpaque->Get(\"mgm.fusex.hb\") ? pOpaque->Get(\"mgm.fusex.hb\") :\n                       \"\";\n      std::string qc = pOpaque->Get(\"mgm.fusex.qc\") ? pOpaque->Get(\"mgm.fusex.qc\") :\n                       \"\";\n      std::string bc = pOpaque->Get(\"mgm.fusex.bc.max\") ?\n                       pOpaque->Get(\"mgm.fusex.bc.max\") :\n                       \"\";\n      std::string bc_match = pOpaque->Get(\"mgm.fusex.bc.match\") ?\n                             pOpaque->Get(\"mgm.fusex.bc.match\") :\n                             \"\";\n      int i_hb = atoi(hb.c_str());\n      int i_qc = atoi(qc.c_str());\n      int i_bc = atoi(bc.c_str());\n\n      if (bc.length()) {\n        gOFS->zMQ->gFuseServer.Client().SetBroadCastMaxAudience(i_bc);\n        stdOut += \"info: configure FUSEX broadcast max. client audience to \";\n        stdOut += bc.c_str();\n        stdOut += \" listeners\";\n        stdOut += \"\\n\";\n        FsView::gFsView.mSpaceView[\"default\"]->SetConfigMember(\"fusex.bca\",\n            bc.c_str());\n        retc = 0;\n      }\n\n      if (bc_match.length()) {\n        gOFS->zMQ->gFuseServer.Client().SetBroadCastAudienceSuppressMatch(bc_match);\n        stdOut += \"info: configure FUSEX broadcast audience to suppress match to \";\n        stdOut += bc_match.c_str();\n        stdOut += \"\\n\";\n        FsView::gFsView.mSpaceView[\"default\"]->SetConfigMember(\"fusex.bca_match\",\n            bc_match.c_str());\n        retc = 0;\n      }\n\n      if (!i_bc) {\n        bc = std::to_string(gOFS->zMQ->gFuseServer.Client().BroadCastMaxAudience());\n        stdOut += \"info: configured FUSEX broadcast max. client audience \";\n        stdOut += bc.c_str();\n        stdOut += \" listeners\";\n        stdOut += \"\\n\";\n        retc = 0;\n      }\n\n      if (!bc_match.length()) {\n        bc_match = gOFS->zMQ->gFuseServer.Client().BroadCastAudienceSuppressMatch();\n        stdOut += \"info: configured FUSEX broadcast audience to suppress match is '\";\n        stdOut += bc_match.c_str();\n        stdOut += \"'\";\n        stdOut += \"\\n\";\n        retc = 0;\n      }\n\n      if (!i_hb) {\n        i_hb = gOFS->zMQ->gFuseServer.Client().HeartbeatInterval();\n        char shb[16];\n        snprintf(shb, sizeof(shb), \"%d\", i_hb);\n        hb = shb;\n      }\n\n      if (!i_qc) {\n        i_qc = gOFS->zMQ->gFuseServer.Client().QuotaCheckInterval();\n        char sqc[16];\n        snprintf(sqc, sizeof(sqc), \"%d\", i_qc);\n        qc = sqc;\n      }\n\n      if ((i_hb > 0) && (i_hb <= 15)) {\n        gOFS->zMQ->gFuseServer.Client().SetHeartbeatInterval(i_hb);\n        stdOut += \"info: configured FUSEX heartbeat interval is \";\n        stdOut += hb.c_str();\n        stdOut += \" seconds\\n\";\n        FsView::gFsView.mSpaceView[\"default\"]->SetConfigMember(\"fusex.hbi\",\n            hb.c_str());\n        retc = 0;\n      } else {\n        stdErr += \"error: hearbeat interval must be [1..15] seconds\\n\";\n        retc = EINVAL;\n      }\n\n      if ((i_qc > 0) && (i_qc <= 60)) {\n        gOFS->zMQ->gFuseServer.Client().SetQuotaCheckInterval(i_qc);\n        stdOut += \"info: configured FUSEX quota check interval is \";\n        stdOut += qc.c_str();\n        stdOut += \" seconds\\n\";\n        FsView::gFsView.mSpaceView[\"default\"]->SetConfigMember(\"fusex.qti\",\n            qc.c_str());\n        retc = 0;\n      } else {\n        if (i_qc < 0) {\n          stdErr += \"error: quota check interval must be [1..60] seconds\\n\";\n          retc = EINVAL;\n        }\n      }\n    } else if (mSubCmd == \"evict\") {\n      std::string s_reason;\n      std::string uuid = pOpaque->Get(\"mgm.fusex.uuid\") ?\n                         pOpaque->Get(\"mgm.fusex.uuid\") : \"\";\n      XrdOucString reason64 = pOpaque->Get(\"mgm.fusex.reason\") ?\n                              pOpaque->Get(\"mgm.fusex.reason\") : \"evicted via EOS shell\";\n      XrdOucString reason;\n      eos::common::SymKey::DeBase64(reason64, reason);\n      s_reason = reason.c_str();\n      std::vector<std::string> evicted_out;\n\n      if (gOFS->zMQ->gFuseServer.Client().Evict(uuid, s_reason,\n          &evicted_out) == ENOENT) {\n        stdErr += \"error: no such client '\";\n        stdErr += uuid.c_str();\n        retc = ENOENT;\n        stdErr += \"'\";\n      } else {\n        if (evicted_out.size() == 1) {\n          stdOut += \"info: evicted client '\";\n          stdOut += evicted_out[0].c_str();\n          stdOut += \"'\";\n        } else {\n          if (evicted_out.size() == 0) {\n            stdOut += \"info: no client has been evicted!\";\n          } else {\n            stdOut += \"info: evicted clients:\\n\";\n\n            for (auto it : evicted_out) {\n              stdOut += it.c_str();\n              stdOut += \"\\n\";\n            }\n          }\n        }\n\n        retc = 0;\n      }\n    } else if (mSubCmd == \"droplocks\") {\n      std::string sinode = pOpaque->Get(\"mgm.inode\") ?\n                           pOpaque->Get(\"mgm.inode\") : \"\";\n      std::string spid = pOpaque->Get(\"mgm.fusex.pid\") ?\n                         pOpaque->Get(\"mgm.fusex.pid\") : \"\";\n      uint64_t inode = strtoull(sinode.c_str(), 0, 16);\n      uint64_t pid   = strtoull(spid.c_str(), 0, 10);\n\n      if (gOFS->zMQ->gFuseServer.Locks().dropLocks(inode, pid)) {\n        stdErr += \"error: no such lock for inode '\";\n        stdErr += sinode.c_str();\n        stdErr += \"'\";\n        stdErr += \" and process '\";\n        stdErr += spid.c_str();\n        stdErr += \"'\";\n        retc = ENOENT;\n      } else {\n        retc = 0;\n        stdOut += \"success: removed locks for inode '\";\n        stdOut += sinode.c_str();\n        stdOut += \"'\";\n        stdOut += \" and process '\";\n        stdOut += spid.c_str();\n        stdOut += \"'\";\n      }\n    } else if (mSubCmd == \"caps\") {\n      std::string option = pOpaque->Get(\"mgm.option\") ? pOpaque->Get(\"mgm.option\") :\n                           \"t\";\n      std::string filter = pOpaque->Get(\"mgm.filter\") ? pOpaque->Get(\"mgm.filter\") :\n                           \"\";\n      filter = eos::common::StringConversion::curl_unescaped(filter.c_str());\n      stdOut += gOFS->zMQ->gFuseServer.Cap().Print(option, filter).c_str();\n      retc = 0;\n    } else {\n      stdErr += \"error: subcmd not implemented\";\n      retc = EINVAL;\n    }\n  } else {\n    stdErr += \"error: you have to be root to list VSTs\";\n    retc = EPERM;\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/GeoSched.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/admin/GeoSched.cc\n// Author: Geoffray Adde - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/geotreeengine/GeoTreeEngine.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::GeoSched()\n{\n  if (pVid->uid == 0) {\n    retc = SFS_ERROR;\n\n    if (mSubCmd == \"showtree\" || mSubCmd == \"showsnapshot\" ||\n        mSubCmd == \"showstate\" || mSubCmd == \"showparam\") {\n      XrdOucString schedgroup = \"\";\n      XrdOucString optype = \"\";\n      schedgroup = pOpaque->Get(\"mgm.schedgroup\");\n      optype = pOpaque->Get(\"mgm.optype\");\n      bool btree = (mSubCmd == \"showtree\");\n      bool bsnapsh = (mSubCmd == \"showsnapshot\");\n      bool bprm = (mSubCmd == \"showparam\");\n      bool bst = (mSubCmd == \"showstate\");\n      bool useColors = false;\n      bool monitoring = pOpaque->Get(\"mgm.monitoring\");\n\n      if (pOpaque->Get(\"mgm.usecolors\")) {\n        useColors = (bool)XrdOucString(pOpaque->Get(\"mgm.usecolors\")).atoi();\n      }\n\n      std::string info;\n      gOFS->mGeoTreeEngine->printInfo(info, btree, bsnapsh, bprm, bst, schedgroup.c_str(),\n                               optype.c_str(), useColors, monitoring);\n      stdOut += info.c_str();\n      retc = SFS_OK;\n    }\n\n    if (mSubCmd == \"set\") {\n      XrdOucString param = pOpaque->Get(\"mgm.param\");\n      XrdOucString paramidx = pOpaque->Get(\"mgm.paramidx\");\n      XrdOucString value = pOpaque->Get(\"mgm.value\");\n      int iparamidx = paramidx.atoi();\n      bool ok = false;\n      // -> save it to the config\n      ok = gOFS->mGeoTreeEngine->setParameter(param.c_str(), value.c_str(), iparamidx, true);\n      retc = ok ? SFS_OK : SFS_ERROR;\n    }\n\n    if (mSubCmd == \"updtpause\") {\n      if (gOFS->mGeoTreeEngine->PauseUpdater()) {\n        stdOut += \"GeoTreeEngine has been paused\\n\";\n      } else {\n        stdOut += \"GeoTreeEngine could not be paused at the moment\\n\";\n      }\n\n      retc = SFS_OK;\n    }\n\n    if (mSubCmd == \"updtresume\") {\n      gOFS->mGeoTreeEngine->ResumeUpdater();\n      stdOut += \"GeoTreeEngine has been resumed\\n\";\n      retc = SFS_OK;\n    }\n\n    if (mSubCmd == \"forcerefresh\") {\n      gOFS->mGeoTreeEngine->forceRefresh();\n      stdOut += \"GeoTreeEngine has been refreshed\\n\";\n      retc = SFS_OK;\n    }\n\n    if (mSubCmd.beginswith(\"disabled\")) {\n      XrdOucString geotag = pOpaque->Get(\"mgm.geotag\");\n      XrdOucString group = pOpaque->Get(\"mgm.schedgroup\");\n      XrdOucString optype = pOpaque->Get(\"mgm.optype\");\n\n      if (mSubCmd == \"disabledadd\") {\n        gOFS->mGeoTreeEngine->addDisabledBranch(group.c_str(), optype.c_str(), geotag.c_str(),\n                                         &stdOut, true); // -> save it to the config\n        retc = SFS_OK;\n      }\n\n      if (mSubCmd == \"disabledrm\") {\n        gOFS->mGeoTreeEngine->rmDisabledBranch(group.c_str(), optype.c_str(), geotag.c_str(),\n                                        &stdOut, true); // -> save it to the config\n        retc = SFS_OK;\n      }\n\n      if (mSubCmd == \"disabledshow\") {\n        gOFS->mGeoTreeEngine->showDisabledBranches(group.c_str(), optype.c_str(),\n                                            geotag.c_str(), &stdOut);\n        retc = SFS_OK;\n      }\n    }\n\n    if (mSubCmd.beginswith(\"access\")) {\n      XrdOucString geotag = pOpaque->Get(\"mgm.geotag\");\n      XrdOucString geotag_list = pOpaque->Get(\"mgm.geotaglist\");\n      bool monitoring = pOpaque->Get(\"mgm.monitoring\");\n\n      if (mSubCmd == \"accesssetdirect\") {\n        gOFS->mGeoTreeEngine->setAccessGeotagMapping(&stdOut, geotag.c_str(),\n                                              geotag_list.c_str(), true);\n        retc = SFS_OK;\n      }\n\n      if (mSubCmd == \"accesscleardirect\") {\n        gOFS->mGeoTreeEngine->clearAccessGeotagMapping(&stdOut,\n                                                geotag == \"all\" ? \"\" : geotag.c_str(), true);\n        retc = SFS_OK;\n      }\n\n      if (mSubCmd == \"accessshowdirect\") {\n        gOFS->mGeoTreeEngine->showAccessGeotagMapping(&stdOut, monitoring);\n        retc = SFS_OK;\n      }\n\n      if (mSubCmd == \"accesssetproxygroup\") {\n        gOFS->mGeoTreeEngine->setAccessProxygroup(&stdOut, geotag.c_str(), geotag_list.c_str(),\n                                           true);\n        retc = SFS_OK;\n      }\n\n      if (mSubCmd == \"accessclearproxygroup\") {\n        gOFS->mGeoTreeEngine->clearAccessProxygroup(&stdOut,\n                                             geotag == \"all\" ? \"\" : geotag.c_str(), true);\n        retc = SFS_OK;\n      }\n\n      if (mSubCmd == \"accessshowproxygroup\") {\n        gOFS->mGeoTreeEngine->showAccessProxygroup(&stdOut, monitoring);\n        retc = SFS_OK;\n      }\n    }\n  } else {\n    retc = EPERM;\n    stdErr = \"error: you have to take role 'root' to execute this command\";\n  }\n\n  return retc;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/GroupCmd.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: GroupCmd.cc\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"GroupCmd.hh\"\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/geotreeengine/GeoTreeEngine.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nstatic constexpr auto GEOTAG_KEY = \"stat.geotag\";\nstatic constexpr auto GEOTAG_PLCT_KEY = \"plct\";\n//------------------------------------------------------------------------------\n// Method implementing the specific behavior of the command executed by the\n// asynchronous thread\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nGroupCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  eos::console::GroupProto group = mReqProto.group();\n\n  switch (mReqProto.group().subcmd_case()) {\n  case eos::console::GroupProto::kLs:\n    LsSubcmd(group.ls(), reply);\n    break;\n\n  case eos::console::GroupProto::kRm:\n    RmSubcmd(group.rm(), reply);\n    break;\n\n  case eos::console::GroupProto::kSet:\n    SetSubcmd(group.set(), reply);\n    break;\n\n  default:\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: not supported\");\n  }\n\n  return reply;\n}\n\n//------------------------------------------------------------------------------\n// Execute ls subcommand\n//------------------------------------------------------------------------------\nvoid\nGroupCmd::LsSubcmd(const eos::console::GroupProto_LsProto& ls,\n                   eos::console::ReplyProto& reply)\n{\n  using eos::console::GroupProto;\n  bool json_output = false;\n  std::string list_format;\n  std::string format;\n  auto format_case = ls.outformat();\n\n  if ((format_case == GroupProto::LsProto::NONE) && WantsJsonOutput()) {\n    format_case = GroupProto::LsProto::MONITORING;\n  }\n\n  switch (format_case) {\n  case GroupProto::LsProto::MONITORING:\n    format = FsView::GetGroupFormat(\"m\");\n    json_output = WantsJsonOutput();\n    break;\n\n  case GroupProto::LsProto::IOGROUP:\n    format = FsView::GetGroupFormat(\"io\");\n    break;\n\n  case GroupProto::LsProto::IOFS:\n    format = FsView::GetGroupFormat(\"IO\");\n    list_format = FsView::GetFileSystemFormat(\"io\");\n    // @note in the old implementation was mOutFormat=\"io\", but then mOutFormat\n    // never used again apparently\n    // ls.set_outformat(eos::console::GroupProto_LsProto::IOGROUP);\n    break;\n\n  case GroupProto::LsProto::LISTING:\n    format = FsView::GetGroupFormat(\"l\");\n    list_format = FsView::GetFileSystemFormat(\"l\");\n    break;\n\n  default : // NONE\n    format = FsView::GetGroupFormat(\"\");\n    break;\n  }\n\n  if (!ls.outhost()) {\n    if (format.find('S') != std::string::npos) {\n      format.replace(format.find('S'), 1, \"s\");\n    }\n\n    if (list_format.find('S') != std::string::npos) {\n      list_format.replace(list_format.find('S'), 1, \"s\");\n    }\n  }\n\n  std::string output;\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n  FsView::gFsView.PrintGroups(output, format, list_format, ls.outdepth(),\n                              ls.selection().c_str(), mReqProto.dontcolor());\n\n  if (json_output) {\n    output = ResponseToJsonString(output);\n  }\n\n  reply.set_std_out(output.c_str());\n  reply.set_retc(0);\n}\n\n//------------------------------------------------------------------------------\n// Execute rm subcommand\n//------------------------------------------------------------------------------\nvoid\nGroupCmd::RmSubcmd(const eos::console::GroupProto_RmProto& rm,\n                   eos::console::ReplyProto& reply)\n{\n  if (mVid.uid != 0) {\n    reply.set_std_err(\"error: you have to take role 'root' to execute this command\");\n    reply.set_retc(EPERM);\n    return;\n  }\n\n  if (!rm.group().length()) {\n    reply.set_std_err(\"error: illegal parameter 'group'\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  eos::common::RWMutexWriteLock lock(FsView::gFsView.ViewMutex);\n\n  if (!FsView::gFsView.mGroupView.count(rm.group())) {\n    reply.set_std_err((\"error: no such group '\" + rm.group() + \"'\").c_str());\n    reply.set_retc(ENOENT);\n    return;\n  }\n\n  for (auto it = FsView::gFsView.mGroupView[rm.group()]->begin();\n       it != FsView::gFsView.mGroupView[rm.group()]->end(); ++it) {\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it);\n\n    if (fs) {\n      // Check that all filesystems are empty\n      if ((fs->GetConfigStatus(false) != eos::common::ConfigStatus::kEmpty)) {\n        reply.set_std_err(\"error: unable to remove group '\" + rm.group() +\n                          \"' - filesystems are not all in empty state - \"\n                          \"try list the group and drain them or set: fs \"\n                          \"config <fsid> configstatus=empty\\n\");\n        reply.set_retc(EBUSY);\n        return;\n      }\n    }\n  }\n\n  common::SharedHashLocator groupLocator =\n    common::SharedHashLocator::makeForGroup(rm.group());\n\n  if (!mq::SharedHashWrapper::deleteHash(gOFS->mMessagingRealm.get(),\n                                         groupLocator)) {\n    reply.set_std_err((\"error: unable to remove config of group '\" +\n                       rm.group() + \"'\").c_str());\n    reply.set_retc(EIO);\n    return;\n  } else {\n    if (FsView::gFsView.UnRegisterGroup(rm.group().c_str())) {\n      reply.set_std_out((\"success: removed group '\" + rm.group() + \"'\").c_str());\n      reply.set_retc(0);\n    } else {\n      reply.set_retc(EINVAL);\n      reply.set_std_err((\"error: unable to unregister group '\" +\n                         rm.group() + \"'\").c_str());\n    }\n\n    return;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Execute set subcommand\n//------------------------------------------------------------------------------\nvoid\nGroupCmd::SetSubcmd(const eos::console::GroupProto_SetProto& set,\n                    eos::console::ReplyProto& reply)\n{\n  if (mVid.uid != 0) {\n    reply.set_std_err(\"error: you have to take role 'root' to execute this command\");\n    reply.set_retc(EPERM);\n    return;\n  }\n\n  if (set.group().empty() || set.group_state().empty()) {\n    reply.set_std_err(\"error: empty group name or group state\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  const std::string key = \"status\";\n  eos::common::RWMutexWriteLock lock(FsView::gFsView.ViewMutex);\n  auto git = FsView::gFsView.mGroupView.find(set.group());\n\n  if (git == FsView::gFsView.mGroupView.end()) {\n    if (set.group_state() == \"drain\") {\n      reply.set_std_err(\"error: group does not exist!\");\n      reply.set_retc(EINVAL);\n      return;\n    }\n\n    reply.set_std_out((\"info: creating group '\" + set.group() + \"'\").c_str());\n\n    if (!FsView::gFsView.RegisterGroup(set.group().c_str())) {\n      std::string groupconfigname =\n        common::SharedHashLocator::makeForGroup(set.group()).getConfigQueue();\n      reply.set_std_err((\"error: cannot register group <\" +\n                         set.group() + \">\").c_str());\n      reply.set_retc(EIO);\n      return;\n    }\n\n    git = FsView::gFsView.mGroupView.find(set.group());\n\n    if (git == FsView::gFsView.mGroupView.end()) {\n      reply.set_std_err(\"error: missing newly created group\");\n      reply.set_retc(EINVAL);\n      return;\n    }\n  }\n\n  eos::mgm::FsGroup* group = git->second;\n\n  // Set the current group status\n  if (!group->SetConfigMember(key, set.group_state())) {\n    reply.set_std_err(\"error: cannot set config status\");\n    reply.set_retc(EIO);\n    return;\n  }\n\n  if (set.group_state() == \"on\") {\n    // Recompute the drain status in this group\n    bool setactive = false;\n\n    for (auto fs_it = group->cbegin(); fs_it != group->cend(); ++fs_it) {\n      auto fs = FsView::gFsView.mIdView.lookupByID(*fs_it);\n\n      if (fs) {\n        common::DrainStatus drainstatus =\n          (eos::common::FileSystem::GetDrainStatusFromString\n           (fs->GetString(\"local.drain\").c_str()));\n\n        if ((drainstatus == eos::common::DrainStatus::kDraining) ||\n            (drainstatus == eos::common::DrainStatus::kDrainStalling)) {\n          // If any group filesystem is draining, all the others have\n          // to enable the pull for draining!\n          setactive = true;\n        }\n      }\n    }\n\n    // Collect all geotags to be enabled for placement\n    XrdOucString output;\n    std::unordered_set<std::string> geotags;\n\n    for (auto fs_it = group->cbegin(); fs_it != group->cend(); ++fs_it) {\n      auto fs = FsView::gFsView.mIdView.lookupByID(*fs_it);\n\n      if (fs) {\n        geotags.emplace(fs->GetString(GEOTAG_KEY));\n\n        if (setactive) {\n          if (fs->GetString(\"local.drainer\") != \"on\") {\n            fs->SetString(\"local.drainer\", \"on\");\n          }\n        } else {\n          if (fs->GetString(\"local.drainer\") != \"off\") {\n            fs->SetString(\"local.drainer\", \"off\");\n          }\n        }\n      }\n    }\n\n    for (const auto& geo_tag : geotags) {\n      (void) gOFS->mGeoTreeEngine->rmDisabledBranch(set.group().c_str(),\n          GEOTAG_PLCT_KEY,\n          geo_tag.c_str(),\n          &output, true);\n    }\n  }\n\n  if (set.group_state() == \"off\") {\n    // Disable all draining in this group\n    for (auto fs_it = group->cbegin(); fs_it != group->cend(); ++fs_it) {\n      auto fs = FsView::gFsView.mIdView.lookupByID(*fs_it);\n\n      if (fs) {\n        fs->SetString(\"local.drainer\", \"off\");\n      }\n    }\n  }\n\n  if (set.group_state() == \"drain\") {\n    // Collect all geotags to be disabled for placement\n    std::unordered_set<std::string> geotags;\n\n    for (auto fs_it = group->cbegin(); fs_it != group->cend(); ++fs_it) {\n      auto fs = FsView::gFsView.mIdView.lookupByID(*fs_it);\n\n      if (fs) {\n        geotags.emplace(fs->GetString(GEOTAG_KEY));\n      }\n    }\n\n    XrdOucString output;\n\n    for (const auto& geo_tag : geotags) {\n      bool status = gOFS->mGeoTreeEngine->addDisabledBranch(set.group().c_str(),\n                    GEOTAG_PLCT_KEY,\n                    geo_tag.c_str(),\n                    &output, true);\n\n      if (!status) {\n        reply.set_retc(EIO);\n        reply.set_std_err(output.c_str());\n        return;\n      }\n    }\n  }\n\n  reply.set_retc(0);\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/GroupCmd.hh",
    "content": "//------------------------------------------------------------------------------\n// @file: GroupCmd.hh\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"proto/Group.pb.h\"\n#include \"mgm/proc/ProcCommand.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class GroupCmd - class handling group commands\n//------------------------------------------------------------------------------\nclass GroupCmd: public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit GroupCmd(eos::console::RequestProto&& req,\n                    eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, false)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~GroupCmd() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Execute ls subcommand\n  //!\n  //! @param ls ls subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void LsSubcmd(const eos::console::GroupProto_LsProto& ls,\n               eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute rm subcommand\n  //!\n  //! @param rm rm subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void RmSubcmd(const eos::console::GroupProto_RmProto& rm,\n                eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute set subcommand\n  //!\n  //! @param set set subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void SetSubcmd(const eos::console::GroupProto_SetProto& set,\n                 eos::console::ReplyProto& reply);\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/IoCmd.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: IoCmd.cc\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"IoCmd.hh\"\n#include \"mgm/iostat/Iostat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"proto/Io.pb.h\"\n#include \"zmq.hpp\"\n\n#include <iomanip>\n#include <string>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Method implementing the specific behavior of the command executed\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nIoCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  const eos::console::IoProto io = mReqProto.io();\n\n  switch (mReqProto.io().subcmd_case()) {\n  case eos::console::IoProto::kStat:\n    StatSubcmd(io.stat(), reply);\n    break;\n\n  case eos::console::IoProto::kEnable:\n    EnableSubcmd(io.enable(), reply);\n    break;\n\n  case eos::console::IoProto::kReport:\n    ReportSubcmd(io.report(), reply);\n    break;\n\n  case eos::console::IoProto::kNs:\n    NsSubcmd(io.ns(), reply);\n    break;\n\n  case eos::console::IoProto::kShaping:\n    ShapingSubcommand(io.shaping(), reply);\n    break;\n\n  default:\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: not supported\");\n  }\n\n  return reply;\n}\n\n//------------------------------------------------------------------------------\n// Execute stat subcommand\n//------------------------------------------------------------------------------\nvoid\nIoCmd::StatSubcmd(const eos::console::IoProto_StatProto& stat, eos::console::ReplyProto& reply)\n{\n  XrdOucString out = \"\";\n  bool monitoring = stat.monitoring() || WantsJsonOutput();\n\n  // If nothing is selected, we show the summary information\n  if (!(stat.apps() || stat.domain() || stat.top() || stat.details())) {\n    gOFS->mIoStats->PrintOut(out,\n                             true,\n                             stat.details(),\n                             monitoring,\n                             stat.numerical(),\n                             stat.top(),\n                             stat.domain(),\n                             stat.apps(),\n                             stat.sample_stat(),\n                             stat.time_ago(),\n                             stat.time_interval());\n  } else {\n    gOFS->mIoStats->PrintOut(out,\n                             stat.summary(),\n                             stat.details(),\n                             monitoring,\n                             stat.numerical(),\n                             stat.top(),\n                             stat.domain(),\n                             stat.apps(),\n                             stat.sample_stat(),\n                             stat.time_ago(),\n                             stat.time_interval());\n  }\n\n  if (WantsJsonOutput()) {\n    out = ResponseToJsonString(out.c_str()).c_str();\n  }\n\n  reply.set_std_out(out.c_str());\n  reply.set_retc(0);\n}\n\n//------------------------------------------------------------------------------\n// Execute enable subcommand\n//------------------------------------------------------------------------------\nvoid\nIoCmd::EnableSubcmd(const eos::console::IoProto_EnableProto& enable, eos::console::ReplyProto& reply)\n{\n  std::ostringstream out, err;\n  int ret_c = 0;\n\n  if (enable.switchx()) {\n    // enable\n    if ((!enable.reports()) && (!enable.namespacex())) {\n      if (enable.upd_address().length()) {\n        if (gOFS->mIoStats->AddUdpTarget(enable.upd_address().c_str())) {\n          out << \"success: enabled IO udp target \" << enable.upd_address();\n        } else {\n          err << \"error: IO udp target was not configured \" << enable.upd_address();\n          ret_c = EINVAL;\n        }\n      } else {\n        if (enable.popularity()) {\n          // Always enable collection otherwise we don't get anything for\n          // popularity reporting\n          gOFS->mIoStats->StartCollection();\n\n          if (gOFS->mIoStats->StartPopularity()) {\n            out << \"success: enabled IO popularity collection\";\n          } else {\n            err << \"error: IO popularity collection already enabled\";\n            ret_c = EINVAL;\n          }\n        } else {\n          if (gOFS->mIoStats->StartCollection()) {\n            out << \"success: enabled IO report collection\";\n          } else {\n            err << \"error: IO report collection already enabled\";\n            ret_c = EINVAL;\n          }\n        }\n      }\n    } else {\n      // disable\n      if (enable.reports()) {\n        if (gOFS->mIoStats->StartReport()) {\n          out << \"success: enabled IO report store\";\n        } else {\n          err << \"error: IO report store already enabled\";\n          ret_c = EINVAL;\n        }\n      }\n\n      if (enable.namespacex()) {\n        if (gOFS->mIoStats->StartReportNamespace()) {\n          out << \"success: enabled IO report namespace\";\n        } else {\n          err << \"error: IO report namespace already enabled\";\n          ret_c = EINVAL;\n        }\n      }\n    }\n  } else {\n    if ((!enable.reports()) && (!enable.namespacex())) {\n      if (enable.upd_address().length()) {\n        if (gOFS->mIoStats->RemoveUdpTarget(enable.upd_address().c_str())) {\n          out << \"success: disabled IO udp target \" << enable.upd_address();\n        } else {\n          err << \"error: IO udp target was not configured \" << enable.upd_address();\n          ret_c = EINVAL;\n        }\n      } else {\n        if (enable.popularity()) {\n          if (gOFS->mIoStats->StopPopularity()) {\n            out << \"success: disabled IO popularity collection\";\n          } else {\n            err << \"error: IO popularity collection already disabled\";\n            ret_c = EINVAL;\n          }\n        } else {\n          if (gOFS->mIoStats->StopCollection()) {\n            out << \"success: disabled IO report collection\";\n          } else {\n            err << \"error: IO report collection already disabled\";\n            ret_c = EINVAL;\n          }\n        }\n      }\n    } else {\n      if (enable.reports()) {\n        if (gOFS->mIoStats->StopReport()) {\n          out << \"success: disabled IO report store\";\n        } else {\n          err << \"error: IO report store already disabled\";\n          ret_c = EINVAL;\n        }\n      }\n\n      if (enable.namespacex()) {\n        if (gOFS->mIoStats->StopReportNamespace()) {\n          out << \"success: disabled IO report namespace\";\n        } else {\n          err << \"error: IO report namespace already disabled\";\n          ret_c = EINVAL;\n        }\n      }\n    }\n  }\n\n  reply.set_std_out(out.str());\n  reply.set_std_err(err.str());\n  reply.set_retc(ret_c);\n}\n\n//------------------------------------------------------------------------------\n// Execute report subcommand\n//------------------------------------------------------------------------------\nvoid\nIoCmd::ReportSubcmd(const eos::console::IoProto_ReportProto& report, eos::console::ReplyProto& reply)\n{\n  XrdOucString out{\"\"};\n  XrdOucString err{\"\"};\n\n  if (mVid.uid != 0) {\n    reply.set_std_err(\"error: you have to take role 'root' to execute this command\");\n    reply.set_retc(EPERM);\n    return;\n  }\n\n  if (gOFS->mIoStats) {\n    gOFS->mIoStats->PrintNsReport(report.path().c_str(), out);\n  }\n\n  reply.set_std_out(out.c_str());\n  reply.set_std_err(err.c_str());\n  reply.set_retc(0);\n}\n\n//------------------------------------------------------------------------------\n// Execute ns subcommand\n//------------------------------------------------------------------------------\nvoid\nIoCmd::NsSubcmd(const eos::console::IoProto_NsProto& ns, eos::console::ReplyProto& reply)\n{\n  std::string option;\n\n  if (ns.monitoring() || WantsJsonOutput()) {\n    option += \"-m\";\n  }\n\n  if (ns.rank_by_byte()) {\n    option += \"-b\";\n  }\n\n  if (ns.rank_by_access()) {\n    option += \"-n\";\n  }\n\n  if (ns.last_week()) {\n    option += \"-w\";\n  }\n\n  if (ns.hotfiles()) {\n    option += \"-f\";\n  }\n\n  switch (ns.count()) {\n  case eos::console::IoProto_NsProto::ONEHUNDRED:\n    option += \"-100\";\n    break;\n\n  case eos::console::IoProto_NsProto::ONETHOUSAND:\n    option += \"-1000\";\n    break;\n\n  case eos::console::IoProto_NsProto::TENTHOUSAND:\n    option += \"-10000\";\n    break;\n\n  case eos::console::IoProto_NsProto::ALL:\n    option += \"-a\";\n    break;\n\n  default: // NONE\n    break;\n  }\n\n  XrdOucString out = \"\";\n  gOFS->mIoStats->PrintNsPopularity(out, option.c_str());\n\n  if (WantsJsonOutput()) {\n    out = ResponseToJsonString(out.c_str()).c_str();\n  }\n\n  reply.set_std_out(out.c_str());\n  reply.set_retc(0);\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/IoCmd.hh",
    "content": "//------------------------------------------------------------------------------\n// @file: IoCmd.hh\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"mgm/proc/ProcCommand.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class IoCmd - class handling io commands\n//------------------------------------------------------------------------------\nclass IoCmd : public IProcCommand {\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit IoCmd(eos::console::RequestProto&& req, eos::common::VirtualIdentity& vid)\n      : IProcCommand(std::move(req), vid, false)\n  {\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~IoCmd() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Execute stat subcommand\n  //!\n  //! @param stat stat subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void StatSubcmd(const eos::console::IoProto_StatProto& stat, eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute enable subcommand\n  //!\n  //! @param enable enable subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  static void EnableSubcmd(const eos::console::IoProto_EnableProto& enable, eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute report subcommand\n  //!\n  //! @param report report subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void ReportSubcmd(const eos::console::IoProto_ReportProto& report, eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute ns subcommand\n  //!\n  //! @param ns ns subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void NsSubcmd(const eos::console::IoProto_NsProto& ns, eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Manage shaping subcommand to manage all the commands of io shaping\n  //!\n  //! @param ms ms subcommand proto object\n  //! @param reply the reply of the mgm\n  //----------------------------------------------------------------------------\n  void ShapingSubcommand(const eos::console::IoProto_ShapingProto& ms,\n                         eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Monitor command to set the limits\n  //!\n  //! @param mn mn subcommand proto object\n  //! @param reply the reply of the mgm\n  //----------------------------------------------------------------------------\n  void MonitorSet(const eos::console::IoProto_ShapingProto& mn,\n                  eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Monitor command to display the limits\n  //!\n  //! @param reply the reply of the mgm\n  //! @param stringstream & the options of teh commands\n  //----------------------------------------------------------------------------\n  void MonitorSetLs(eos::console::ReplyProto& reply, std::stringstream&);\n\n  //----------------------------------------------------------------------------\n  //! Monitor command to remove a limits\n  //!\n  //! @param reply the reply of the mgm\n  //! @param stringstream & the options of teh commands\n  //----------------------------------------------------------------------------\n  void MonitorSetRm(eos::console::ReplyProto& reply, std::stringstream&);\n\n  //----------------------------------------------------------------------------\n  //! Monitor command to add a window\n  //!\n  //! @param mn mn subcommand proto object\n  //! @param reply the reply of the mgm\n  //----------------------------------------------------------------------------\n  void MonitorAdd(const eos::console::IoProto_ShapingProto& mn,\n                  eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Monitor command to remove a window\n  //!\n  //! @param mn mn subcommand proto object\n  //! @param reply the reply of the mgm\n  //----------------------------------------------------------------------------\n  void MonitorRm(const eos::console::IoProto_ShapingProto& mn,\n                 eos::console::ReplyProto& reply);\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/IoShapingCmd.cc",
    "content": "#include \"IoCmd.hh\"\n#include \"fsview/FsView.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n\n#include <common/CLI11.hpp>\n\n#include \"proto/ConsoleReply.pb.h\"\n\nstd::string\nformat_rate(const double bytes_per_sec)\n{\n  const char* units[] = {\"B/s\", \"kB/s\", \"MB/s\", \"GB/s\", \"TB/s\", \"PB/s\"};\n  int unit_idx = 0;\n  double val = bytes_per_sec;\n  while (val >= 1000.0 && unit_idx < 5) {\n    val /= 1000.0;\n    unit_idx++;\n  }\n  std::ostringstream ss;\n  ss << std::fixed << std::setprecision(2) << val << \" \" << units[unit_idx];\n  return ss.str();\n};\n\nnamespace eos::mgm {\n\nvoid\nShapingPolicySet(const eos::console::IoProto_ShapingProto_PolicyAction_SetAction& set_req,\n                 eos::console::ReplyProto& reply)\n{\n  const auto& engine = gOFS->mTrafficShapingEngine;\n  const std::shared_ptr<traffic_shaping::TrafficShapingManager> manager =\n      engine.GetManager();\n  if (!manager) {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: Traffic shaping engine is not initialized.\\n\");\n    return;\n  }\n\n  traffic_shaping::TrafficShapingPolicy policy;\n  std::string target_desc;\n\n  if (set_req.has_app()) {\n    target_desc = \"App '\" + set_req.app() + \"'\";\n    policy = manager->GetAppPolicy(set_req.app())\n                 .value_or(traffic_shaping::TrafficShapingPolicy{});\n  } else if (set_req.has_uid()) {\n    target_desc = \"UID \" + std::to_string(set_req.uid());\n    policy = manager->GetUidPolicy(set_req.uid())\n                 .value_or(traffic_shaping::TrafficShapingPolicy{});\n  } else if (set_req.has_gid()) {\n    target_desc = \"GID \" + std::to_string(set_req.gid());\n    policy = manager->GetGidPolicy(set_req.gid())\n                 .value_or(traffic_shaping::TrafficShapingPolicy{});\n  } else {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: You must specify a target (--app, --uid, or --gid).\\n\");\n    return;\n  }\n\n  // --- Parse User Limits ---\n  if (set_req.has_limit_read_bytes_per_sec()) {\n    policy.limit_read_bytes_per_sec = set_req.limit_read_bytes_per_sec();\n  }\n  if (set_req.has_limit_write_bytes_per_sec()) {\n    policy.limit_write_bytes_per_sec = set_req.limit_write_bytes_per_sec();\n  }\n\n  // --- Parse Reservations ---\n  if (set_req.has_reservation_read_bytes_per_sec()) {\n    policy.reservation_read_bytes_per_sec = set_req.reservation_read_bytes_per_sec();\n  }\n  if (set_req.has_reservation_write_bytes_per_sec()) {\n    policy.reservation_write_bytes_per_sec = set_req.reservation_write_bytes_per_sec();\n  }\n\n  // --- Parse Ephemeral Controller Limits ---\n  if (set_req.has_controller_limit_read_bytes_per_sec()) {\n    policy.controller_limit_read_bytes_per_sec =\n        set_req.controller_limit_read_bytes_per_sec();\n  }\n  if (set_req.has_controller_limit_write_bytes_per_sec()) {\n    policy.controller_limit_write_bytes_per_sec =\n        set_req.controller_limit_write_bytes_per_sec();\n  }\n\n  if (set_req.has_is_enabled()) {\n    policy.is_enabled = set_req.is_enabled();\n  }\n\n  if (set_req.has_app()) {\n    manager->SetAppPolicy(set_req.app(), policy);\n  } else if (set_req.has_uid()) {\n    manager->SetUidPolicy(set_req.uid(), policy);\n  } else if (set_req.has_gid()) {\n    manager->SetGidPolicy(set_req.gid(), policy);\n  }\n\n  std::string status_str = policy.is_enabled ? \"Enabled\" : \"Disabled\";\n\n  reply.set_retc(0);\n  reply.set_std_out(\"success: Updated shaping policy for \" + target_desc +\n                    \" (Status: \" + status_str + \")\\n\");\n}\n\nvoid\nShapingPolicyDelete(\n    const eos::console::IoProto_ShapingProto_PolicyAction_RemoveAction& rm_req,\n    eos::console::ReplyProto& reply)\n{\n  auto& engine = gOFS->mTrafficShapingEngine;\n  const std::shared_ptr<traffic_shaping::TrafficShapingManager> manager =\n      engine.GetManager();\n  if (!manager) {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: Traffic shaping engine is not initialized.\\n\");\n    return;\n  }\n\n  std::string target_desc;\n\n  if (rm_req.has_app()) {\n    manager->RemoveAppPolicy(rm_req.app());\n    target_desc = \"App '\" + rm_req.app() + \"'\";\n  } else if (rm_req.has_uid()) {\n    manager->RemoveUidPolicy(rm_req.uid());\n    target_desc = \"UID \" + std::to_string(rm_req.uid());\n  } else if (rm_req.has_gid()) {\n    manager->RemoveGidPolicy(rm_req.gid());\n    target_desc = \"GID \" + std::to_string(rm_req.gid());\n  } else {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\n        \"error: You must specify a target to delete (--app, --uid, or --gid).\\n\");\n    return;\n  }\n\n  reply.set_retc(0);\n  reply.set_std_out(\"success: Deleted shaping policy for \" + target_desc + \"\\n\");\n}\n\nvoid\nShapingTrafficEnable(eos::console::ReplyProto& reply)\n{\n  auto& engine = gOFS->mTrafficShapingEngine;\n  engine.Enable();\n  reply.set_retc(0);\n  reply.set_std_out(\"success: Traffic Shaping enabled.\\n\");\n}\n\nvoid\nShapingTrafficDisable(eos::console::ReplyProto& reply)\n{\n  auto& engine = gOFS->mTrafficShapingEngine;\n  engine.Disable();\n  reply.set_retc(0);\n  reply.set_std_out(\"success: Traffic Shaping disabled.\\n\");\n}\n\nvoid\nShapingList(const eos::console::IoProto_ShapingProto_ListAction& list_req,\n            eos::console::ReplyProto& reply)\n{\n  const auto& engine = gOFS->mTrafficShapingEngine;\n  const std::shared_ptr<traffic_shaping::TrafficShapingManager> manager =\n      engine.GetManager();\n  if (!manager) {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: Traffic Shaping Engine is not initialized.\\n\");\n    return;\n  }\n\n  auto global_stats = manager->GetGlobalStats();\n  auto node_stats = manager->GetNodeStats();\n  auto total_stats = manager->GetTotalStats();\n\n  struct AggregatedStats {\n    double read_rate = 0.0;\n    double write_rate = 0.0;\n    double read_iops = 0.0;\n    double write_iops = 0.0;\n  };\n\n  std::map<std::string, AggregatedStats> agg_stats;\n\n  traffic_shaping::SmaIdx sma_idx = traffic_shaping::Sma1m;\n  uint32_t window_sec = list_req.time_window_seconds();\n\n  switch (window_sec) {\n  case 1:\n    sma_idx = traffic_shaping::Sma1s;\n    break;\n  case 5:\n    sma_idx = traffic_shaping::Sma5s;\n    break;\n  case 15:\n    sma_idx = traffic_shaping::Sma15s;\n    break;\n  case 300:\n    sma_idx = traffic_shaping::Sma5m;\n    break;\n  case 60:\n  default:\n    sma_idx = traffic_shaping::Sma1m;\n    window_sec = 60; // Enforce default for output formatting\n    break;\n  }\n\n  if (list_req.show_nodes()) {\n    for (const auto& [node_id, snapshot] : node_stats) {\n      std::string group_key = node_id.empty() ? \"<unknown>\" : node_id;\n      auto& entry = agg_stats[group_key];\n\n      const auto& sma_metrics = snapshot.sma[sma_idx];\n      entry.read_rate += sma_metrics.read_rate_bps;\n      entry.write_rate += sma_metrics.write_rate_bps;\n      entry.read_iops += sma_metrics.read_iops;\n      entry.write_iops += sma_metrics.write_iops;\n    }\n  } else {\n    for (const auto& [key, snapshot] : global_stats) {\n      std::string group_key;\n\n      if (list_req.show_apps()) {\n        group_key = key.app.empty() ? \"<unknown>\" : key.app;\n      } else if (list_req.show_users()) {\n        group_key = std::to_string(key.uid);\n      } else if (list_req.show_groups()) {\n        group_key = std::to_string(key.gid);\n      } else {\n        group_key = \"app:\" + key.app; // Default fallback\n      }\n\n      auto& entry = agg_stats[group_key];\n\n      const auto& sma_metrics = snapshot.sma[sma_idx];\n      entry.read_rate += sma_metrics.read_rate_bps;\n      entry.write_rate += sma_metrics.write_rate_bps;\n      entry.read_iops += sma_metrics.read_iops;\n      entry.write_iops += sma_metrics.write_iops;\n    }\n  }\n\n  const auto& total_sma_metrics = total_stats.sma[sma_idx];\n\n  std::ostringstream oss;\n\n  if (list_req.json_output()) {\n    std::string type_str = \"unknown\";\n    if (list_req.show_apps()) {\n      type_str = \"app\";\n    } else if (list_req.show_users()) {\n      type_str = \"uid\";\n    } else if (list_req.show_groups()) {\n      type_str = \"gid\";\n    } else if (list_req.show_nodes()) {\n      type_str = \"node\";\n    }\n\n    oss << \"[\\n\";\n    bool first = true;\n    for (const auto& [name, stat] : agg_stats) {\n      if (!first) {\n        oss << \",\\n\";\n      }\n      first = false;\n      oss << \"  {\\n\"\n          << \"    \\\"id\\\": \\\"\" << name << \"\\\",\\n\"\n          << \"    \\\"type\\\": \\\"\" << type_str << \"\\\",\\n\"\n          << \"    \\\"window_sec\\\": \" << window_sec << \",\\n\"\n          << \"    \\\"read_rate_bps\\\": \" << std::fixed << std::setprecision(2)\n          << stat.read_rate << \",\\n\"\n          << \"    \\\"write_rate_bps\\\": \" << stat.write_rate << \",\\n\"\n          << \"    \\\"read_iops\\\": \" << stat.read_iops << \",\\n\"\n          << \"    \\\"write_iops\\\": \" << stat.write_iops << \"\\n\"\n          << \"  }\";\n    }\n\n    if (list_req.system_stats()) {\n      const auto [estimator_median, estimator_min, estimator_max] =\n          manager->GetEstimatorsUpdateLoopMicroSecStats();\n      const auto [fst_limits_median, fst_limits_min, fst_limits_max] =\n          manager->GetFstLimitsUpdateLoopMicroSecStats();\n      const auto reports_processed_mean = manager->GetFstReportsProcessedPerSecondMean();\n      const auto system_stats_window_seconds = manager->GetSystemStatsWindowSeconds();\n\n      if (!first) {\n        oss << \",\\n\";\n      }\n      oss << \"  {\\n\"\n          << \"    \\\"id\\\": \\\"engine_meta\\\",\\n\"\n          << \"    \\\"type\\\": \\\"system\\\",\\n\"\n          << \"    \\\"estimators_loop_median_us\\\": \" << std::fixed << std::setprecision(2)\n          << estimator_median << \",\\n\"\n          << \"    \\\"estimators_loop_min_us\\\": \" << estimator_min << \",\\n\"\n          << \"    \\\"estimators_loop_max_us\\\": \" << estimator_max << \",\\n\"\n          << \"    \\\"fst_limits_loop_median_us\\\": \" << std::fixed << std::setprecision(2)\n          << fst_limits_median << \",\\n\"\n          << \"    \\\"fst_limits_loop_min_us\\\": \" << fst_limits_min << \",\\n\"\n          << \"    \\\"fst_limits_loop_max_us\\\": \" << fst_limits_max << \",\\n\"\n          << \"    \\\"reports_processed_per_sec_mean\\\": \" << std::fixed\n          << std::setprecision(2) << reports_processed_mean << \",\\n\"\n          << \"    \\\"system_stats_window_seconds\\\": \" << system_stats_window_seconds\n          << \"\\n\"\n          << \"  }\";\n    }\n\n    oss << \"\\n]\\n\";\n  } else {\n    std::string header_name = \"ID\";\n    if (list_req.show_apps()) {\n      header_name = \"Application\";\n    } else if (list_req.show_users()) {\n      header_name = \"UID\";\n    } else if (list_req.show_groups()) {\n      header_name = \"GID\";\n    } else if (list_req.show_nodes()) {\n      header_name = \"Storage Node\";\n    }\n\n    oss << \"--- IO Rates (\" << window_sec << \"s simple moving average) ---\\n\";\n\n    oss << std::left << std::setw(40) << header_name << std::right << std::setw(15)\n        << \"Read Rate\" << std::setw(15) << \"Write Rate\" << std::setw(12) << \"Read IOPS\"\n        << std::setw(12) << \"Write IOPS\" << \"\\n\";\n\n    oss << std::string(94, '-') << \"\\n\";\n\n    for (const auto& [name, stat] : agg_stats) {\n      oss << std::left << std::setw(40) << name << std::right << std::setw(15)\n          << format_rate(stat.read_rate) << std::setw(15) << format_rate(stat.write_rate)\n          << std::fixed << std::setprecision(2) << std::setw(12) << stat.read_iops\n          << std::setw(12) << stat.write_iops << \"\\n\";\n    }\n\n    oss << std::string(94, '-') << \"\\n\";\n    oss << std::left << std::setw(40) << \"Total\" << std::right << std::setw(15)\n        << format_rate(total_sma_metrics.read_rate_bps) << std::setw(15)\n        << format_rate(total_sma_metrics.write_rate_bps) << std::fixed\n        << std::setprecision(2) << std::setw(12) << total_sma_metrics.read_iops\n        << std::setw(12) << total_sma_metrics.write_iops << \"\\n\";\n\n    if (list_req.system_stats()) {\n      const auto [estimator_median, estimator_min, estimator_max] =\n          manager->GetEstimatorsUpdateLoopMicroSecStats();\n      const auto [fst_limits_median, fst_limits_min, fst_limits_max] =\n          manager->GetFstLimitsUpdateLoopMicroSecStats();\n      const auto reports_processed_mean = manager->GetFstReportsProcessedPerSecondMean();\n      const auto system_stats_window_seconds = manager->GetSystemStatsWindowSeconds();\n\n      oss << \"\\n--- System Statistics (averaged over last \" << system_stats_window_seconds\n          << \" seconds) ---\\n\";\n      oss << std::left << std::setw(30) << \"Estimators Update:\"\n          << \"Median = \" << std::fixed << std::setprecision(2) << estimator_median\n          << \" us | \"\n          << \"Min = \" << estimator_min << \" us | \"\n          << \"Max = \" << estimator_max << \" us\\n\";\n\n      oss << std::left << std::setw(30) << \"FST Policy Update:\"\n          << \"Median = \" << std::fixed << std::setprecision(2) << fst_limits_median\n          << \" us | \"\n          << \"Min = \" << fst_limits_min << \" us | \"\n          << \"Max = \" << fst_limits_max << \" us\\n\";\n\n      oss << std::left << std::setw(30) << \"FST Reports Per Second:\"\n          << \"Mean = \" << std::fixed << std::setprecision(2) << reports_processed_mean\n          << \"\\n\";\n    }\n  }\n\n  reply.set_retc(0);\n  reply.set_std_out(oss.str());\n}\n\nvoid\nShapingPolicyList(\n    const eos::console::IoProto_ShapingProto_PolicyAction_ListAction& list_req,\n    eos::console::ReplyProto& reply)\n{\n  const auto& engine = gOFS->mTrafficShapingEngine;\n  const std::shared_ptr<traffic_shaping::TrafficShapingManager> manager =\n      engine.GetManager();\n  if (!manager) {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: Traffic shaping engine is not initialized.\\n\");\n    return;\n  }\n\n  const bool show_all =\n      !list_req.filter_apps() && !list_req.filter_users() && !list_req.filter_groups();\n  std::ostringstream oss;\n\n  if (list_req.json_output()) {\n    oss << \"[\\n\";\n    bool first = true;\n\n    auto print_json_entry = [&](const std::string& type_str, const std::string& id_str,\n                                const auto& policy) {\n      if (!first) {\n        oss << \",\\n\";\n      }\n      first = false;\n      oss << \"  {\\n\"\n          << \"    \\\"id\\\": \\\"\" << id_str << \"\\\",\\n\"\n          << \"    \\\"type\\\": \\\"\" << type_str << \"\\\",\\n\"\n          << \"    \\\"is_enabled\\\": \" << (policy.is_enabled ? \"true\" : \"false\") << \",\\n\"\n          << \"    \\\"limit_read_bytes_per_sec\\\": \" << policy.limit_read_bytes_per_sec\n          << \",\\n\"\n          << \"    \\\"limit_write_bytes_per_sec\\\": \" << policy.limit_write_bytes_per_sec\n          << \",\\n\"\n          << \"    \\\"reservation_read_bytes_per_sec\\\": \"\n          << policy.reservation_read_bytes_per_sec << \",\\n\"\n          << \"    \\\"reservation_write_bytes_per_sec\\\": \"\n          << policy.reservation_write_bytes_per_sec << \",\\n\"\n          << \"    \\\"controller_limit_read_bytes_per_sec\\\": \"\n          << policy.controller_limit_read_bytes_per_sec << \",\\n\"\n          << \"    \\\"controller_limit_write_bytes_per_sec\\\": \"\n          << policy.controller_limit_write_bytes_per_sec << \"\\n\"\n          << \"  }\";\n    };\n\n    if (show_all || list_req.filter_apps()) {\n      for (const auto& [app, policy] : manager->GetAppPolicies()) {\n        print_json_entry(\"app\", app, policy);\n      }\n    }\n\n    if (show_all || list_req.filter_users()) {\n      for (const auto& [uid, policy] : manager->GetUidPolicies()) {\n        print_json_entry(\"uid\", std::to_string(uid), policy);\n      }\n    }\n\n    if (show_all || list_req.filter_groups()) {\n      for (const auto& [gid, policy] : manager->GetGidPolicies()) {\n        print_json_entry(\"gid\", std::to_string(gid), policy);\n      }\n    }\n\n    if (!first) {\n      oss << \"\\n\";\n    }\n    oss << \"]\\n\";\n\n  } else {\n    const bool show_ctrl = list_req.show_controller_limits();\n\n    auto print_header = [&oss, show_ctrl](const std::string& title,\n                                          const std::string& id_col) {\n      oss << \"--- \" << title << \" ---\\n\";\n      oss << std::left << std::setw(40) << id_col << std::setw(10) << \"Status\"\n          << std::right << std::setw(15) << \"Read Limit\" << std::setw(15)\n          << \"Write Limit\";\n\n      if (show_ctrl) {\n        oss << std::setw(15) << \"Ctrl Rd Lim\" << std::setw(15) << \"Ctrl Wr Lim\";\n      }\n\n      oss << std::setw(15) << \"Read Rsv.\" << std::setw(15) << \"Write Rsv.\" << \"\\n\";\n      oss << std::string(show_ctrl ? 140 : 110, '-') << \"\\n\";\n    };\n\n    auto print_row = [&oss,\n                      show_ctrl](const std::string& id,\n                                 const traffic_shaping::TrafficShapingPolicy& policy) {\n      oss << std::left << std::setw(40) << id << std::setw(10)\n          << (policy.is_enabled ? \"Enabled\" : \"Disabled\") << std::right << std::setw(15)\n          << format_rate(policy.limit_read_bytes_per_sec) << std::setw(15)\n          << format_rate(policy.limit_write_bytes_per_sec);\n\n      if (show_ctrl) {\n        oss << std::setw(15) << format_rate(policy.controller_limit_read_bytes_per_sec)\n            << std::setw(15) << format_rate(policy.controller_limit_write_bytes_per_sec);\n      }\n\n      oss << std::setw(15) << format_rate(policy.reservation_read_bytes_per_sec)\n          << std::setw(15) << format_rate(policy.reservation_write_bytes_per_sec) << \"\\n\";\n    };\n\n    bool has_output = false;\n\n    if (show_all || list_req.filter_apps()) {\n      if (auto policies = manager->GetAppPolicies(); !policies.empty()) {\n        print_header(\"Application Policies\", \"Application\");\n        for (const auto& [app, policy] : policies) {\n          print_row(app, policy);\n        }\n        oss << \"\\n\";\n        has_output = true;\n      }\n    }\n\n    if (show_all || list_req.filter_users()) {\n      if (auto policies = manager->GetUidPolicies(); !policies.empty()) {\n        print_header(\"User (UID) Policies\", \"UID\");\n        for (const auto& [uid, policy] : policies) {\n          print_row(std::to_string(uid), policy);\n        }\n        oss << \"\\n\";\n        has_output = true;\n      }\n    }\n\n    if (show_all || list_req.filter_groups()) {\n      if (auto policies = manager->GetGidPolicies(); !policies.empty()) {\n        print_header(\"Group (GID) Policies\", \"GID\");\n        for (const auto& [gid, policy] : policies) {\n          print_row(std::to_string(gid), policy);\n        }\n        oss << \"\\n\";\n        has_output = true;\n      }\n    }\n\n    if (!has_output) {\n      oss << \"No traffic shaping policies configured.\\n\";\n    }\n  }\n\n  reply.set_retc(0);\n  reply.set_std_out(oss.str());\n}\n\nvoid\nShapingConfig(const eos::console::IoProto_ShapingProto_ConfigAction& config_req,\n              eos::console::ReplyProto& reply)\n{\n  auto& engine = gOFS->mTrafficShapingEngine;\n\n  switch (config_req.subcmd_case()) {\n  case eos::console::IoProto_ShapingProto_ConfigAction::kList: {\n    std::ostringstream oss;\n    oss << \"--- Traffic Shaping Thread Configuration ---\\n\"\n        << std::left << std::setw(45) << \"Estimators Update Period:\"\n        << engine.GetEstimatorsUpdateThreadPeriodMilliseconds() << \" ms\\n\"\n        << std::setw(45) << \"FST IO Policy Update Period:\"\n        << engine.GetFstIoPolicyUpdateThreadPeriodMilliseconds() << \" ms\\n\"\n        << std::setw(45) << \"FST IO Stats Reporting Period:\"\n        << engine.GetFstIoStatsReportThreadPeriodMilliseconds() << \" ms\\n\"\n        << std::setw(45)\n        << \"System Stats Time Window:\" << engine.GetSystemStatsWindowSeconds() << \" s\\n\";\n\n    reply.set_retc(0);\n    reply.set_std_out(oss.str());\n    break;\n  }\n\n  case eos::console::IoProto_ShapingProto_ConfigAction::kSet: {\n    const auto& set_req = config_req.set();\n    std::ostringstream oss;\n\n    if (set_req.has_update_estimators_thread_period_ms()) {\n      engine.SetEstimatorsUpdateThreadPeriodMilliseconds(\n          set_req.update_estimators_thread_period_ms());\n      oss << \"success: Set estimators update period to \"\n          << engine.GetEstimatorsUpdateThreadPeriodMilliseconds() << \" ms\\n\";\n    }\n\n    if (set_req.has_fst_io_policy_update_thread_period_ms()) {\n      engine.SetFstIoPolicyUpdateThreadPeriodMilliseconds(\n          set_req.fst_io_policy_update_thread_period_ms());\n      oss << \"success: Set FST IO policy update period to \"\n          << engine.GetFstIoPolicyUpdateThreadPeriodMilliseconds() << \" ms\\n\";\n    }\n\n    if (set_req.has_fst_io_stats_reporting_thread_period_ms()) {\n      engine.SetFstIoStatsReportThreadPeriodMilliseconds(\n          set_req.fst_io_stats_reporting_thread_period_ms());\n      oss << \"success: Set FST IO stats reporting period to \"\n          << engine.GetFstIoStatsReportThreadPeriodMilliseconds() << \" ms\\n\";\n    }\n\n    if (set_req.has_system_stats_time_window_seconds()) {\n      engine.SetSystemStatsWindowSeconds(set_req.system_stats_time_window_seconds());\n      oss << \"success: Set system stats time window to \"\n          << engine.GetSystemStatsWindowSeconds() << \" s\\n\";\n    }\n\n    if (oss.str().empty()) {\n      reply.set_retc(EINVAL);\n      reply.set_std_err(\"error: No configuration parameters provided to set.\\n\");\n    } else {\n      reply.set_retc(0);\n      reply.set_std_out(oss.str());\n    }\n    break;\n  }\n\n  case eos::console::IoProto_ShapingProto_ConfigAction::SUBCMD_NOT_SET:\n  default:\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: Shaping config: invalid or missing subcommand (ls/set).\\n\");\n    break;\n  }\n}\n\nvoid\nIoCmd::ShapingSubcommand(const eos::console::IoProto_ShapingProto& shaping,\n                         eos::console::ReplyProto& reply)\n{\n  switch (shaping.subcmd_case()) {\n\n  case eos::console::IoProto_ShapingProto::kList: {\n    ShapingList(shaping.list(), reply);\n    break;\n  }\n\n  case eos::console::IoProto_ShapingProto::kEnable: {\n    ShapingTrafficEnable(reply);\n    break;\n  }\n\n  case eos::console::IoProto_ShapingProto::kDisable: {\n    ShapingTrafficDisable(reply);\n    break;\n  }\n\n  case eos::console::IoProto_ShapingProto::kPolicy: {\n\n    switch (const auto& policy = shaping.policy(); policy.subcmd_case()) {\n    case eos::console::IoProto_ShapingProto_PolicyAction::kList:\n      ShapingPolicyList(policy.list(), reply);\n      break;\n\n    case eos::console::IoProto_ShapingProto_PolicyAction::kSet:\n      ShapingPolicySet(policy.set(), reply);\n      break;\n\n    case eos::console::IoProto_ShapingProto_PolicyAction::kRemove:\n      ShapingPolicyDelete(policy.remove(), reply); // Note the trailing underscore!\n      break;\n\n    default:\n      reply.set_retc(EINVAL);\n      reply.set_std_err(\n          \"error: Shaping policy: invalid or missing subcommand (list/set/delete).\\n\");\n      break;\n    }\n\n    break;\n  }\n\n  case eos::console::IoProto_ShapingProto::kConfig: {\n    ShapingConfig(shaping.config(), reply);\n    break;\n  }\n  case eos::console::IoProto_ShapingProto::SUBCMD_NOT_SET:\n  default:\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\n        \"error: Shaping command: sub-command (traffic/policy) not specified.\\n\");\n    break;\n  }\n}\n} // namespace eos::mgm\n"
  },
  {
    "path": "mgm/proc/admin/NodeCmd.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: NodeCmd.cc\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"NodeCmd.hh\"\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/config/IConfigEngine.hh\"\n#include \"mq/MessagingRealm.hh\"\n#include \"namespace/interface/IFsView.hh\"\n\n#include <algorithm>\n#include <cctype>\n\nEOSMGMNAMESPACE_BEGIN\n\nnamespace {\n\nstd::string\nFormatNodeStatusValue(std::string value)\n{\n  if ((value.substr(0, 7) == \"base64:\") || (value.substr(0, 8) == \"zbase64:\")) {\n    return value.substr(0, value.find(':') + 1) + \"...\";\n  }\n\n  if (value.length() > 1024) {\n    return \"...\";\n  }\n\n  for (const unsigned char c : value) {\n    if (!std::isprint(c)) {\n      return \"<binary:\" + std::to_string(value.length()) + \" bytes>\";\n    }\n  }\n\n  return value;\n}\n\n} // namespace\n\n//------------------------------------------------------------------------------\n// Method implementing the specific behavior of the command executed by the\n// asynchronous thread\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nNodeCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  eos::console::NodeProto node = mReqProto.node();\n\n  switch (mReqProto.node().subcmd_case()) {\n  case eos::console::NodeProto::kLs:\n    LsSubcmd(node.ls(), reply);\n    break;\n\n  case eos::console::NodeProto::kRm:\n    RmSubcmd(node.rm(), reply);\n    break;\n\n  case eos::console::NodeProto::kStatus:\n    StatusSubcmd(node.status(), reply);\n    break;\n\n  case eos::console::NodeProto::kConfig:\n    ConfigSubcmd(node.config(), reply);\n    break;\n\n  case eos::console::NodeProto::kSet:\n    SetSubcmd(node.set(), reply);\n    break;\n\n  case eos::console::NodeProto::kProxygroup:\n    ProxygroupSubcmd(node.proxygroup(), reply);\n    break;\n\n  default:\n    reply.set_std_err(\"error: not supported\");\n    reply.set_retc(EINVAL);\n  }\n\n  return reply;\n}\n\n//------------------------------------------------------------------------------\n// Execute ls subcommand\n//------------------------------------------------------------------------------\nvoid NodeCmd::LsSubcmd(const eos::console::NodeProto_LsProto& ls,\n                       eos::console::ReplyProto& reply)\n{\n  using eos::console::NodeProto;\n  bool json_output = false;\n  std::string list_format;\n  std::string format;\n  auto format_case = ls.outformat();\n\n  if ((format_case == NodeProto::LsProto::NONE) && WantsJsonOutput()) {\n    format_case = NodeProto::LsProto::MONITORING;\n  }\n\n  switch (format_case) {\n  case NodeProto::LsProto::LISTING:\n    format = FsView::GetNodeFormat(\"l\");\n    list_format = FsView::GetFileSystemFormat(\"l\");\n    break;\n\n  case NodeProto::LsProto::MONITORING:\n    format = FsView::GetNodeFormat(\"m\");\n    json_output = WantsJsonOutput();\n    break;\n\n  case NodeProto::LsProto::IO:\n    format = FsView::GetNodeFormat(\"io\");\n    break;\n\n  case NodeProto::LsProto::SYS:\n    format = FsView::GetNodeFormat(\"sys\");\n    break;\n\n  case NodeProto::LsProto::FSCK:\n    format = FsView::GetNodeFormat(\"fsck\");\n    break;\n\n  default : // NONE\n    format = FsView::GetNodeFormat(\"\");\n    break;\n  }\n\n  if (!ls.outhost()) {\n    if (format.find('S') != std::string::npos) {\n      format.replace(format.find('S'), 1, \"s\");\n    }\n\n    if (list_format.find('S') != std::string::npos) {\n      list_format.replace(list_format.find('S'), 1, \"s\");\n    }\n  }\n\n  std::string output;\n  eos::common::RWMutexReadLock rd_lock(FsView::gFsView.ViewMutex);\n  FsView::gFsView.PrintNodes(output, format, list_format, 0,\n                             ls.selection().c_str(), mReqProto.dontcolor());\n\n  if (json_output) {\n    output = ResponseToJsonString(output);\n  }\n\n  reply.set_std_out(output);\n  reply.set_retc(0);\n}\n\n//------------------------------------------------------------------------------\n// Execute rm subcommand\n//------------------------------------------------------------------------------\nvoid NodeCmd::RmSubcmd(const eos::console::NodeProto_RmProto& rm,\n                       eos::console::ReplyProto& reply)\n{\n  if (mVid.uid != 0 && mVid.prot != \"sss\") {\n    reply.set_std_err(\"error: you have to take role 'root' to execute this command\");\n    reply.set_retc(EPERM);\n    return;\n  }\n\n  if (rm.node().empty()) {\n    reply.set_std_err(\"error: illegal parameter 'node'\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  std::string nodename = rm.node();\n\n  if ((nodename.find(':') == std::string::npos)) {\n    nodename += \":1095\"; // default eos fst port\n  }\n\n  if ((nodename.find(\"/eos/\") == std::string::npos)) {\n    nodename.insert(0, \"/eos/\");\n    nodename.append(\"/fst\");\n  }\n\n  eos::common::RWMutexWriteLock wr_lock(FsView::gFsView.ViewMutex);\n\n  if (!FsView::gFsView.mNodeView.count(nodename)) {\n    reply.set_std_err(\"error: no such node '\" + nodename + \"'\");\n    reply.set_retc(ENOENT);\n    return;\n  }\n\n  // Remove a node only if it has no heartbeat anymore\n  if ((time(nullptr) - FsView::gFsView.mNodeView[nodename]->GetHeartBeat()) < 5) {\n    reply.set_std_err(\"error: this node was still sending a heartbeat < 5 \"\n                      \"seconds ago - stop the FST daemon first!\");\n    reply.set_retc(EBUSY);\n    return;\n  }\n\n  // Remove a node only if all filesystems are in empty state\n  for (auto it = FsView::gFsView.mNodeView[nodename]->begin();\n       it != FsView::gFsView.mNodeView[nodename]->end(); ++it) {\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it);\n\n    if (fs) {\n      // check the empty state\n      if ((fs->GetConfigStatus(false) != eos::common::ConfigStatus::kEmpty)) {\n        reply.set_std_err(\"error: unable to remove node '\" + nodename +\n                          \"' - filesystems are not all in empty state\");\n        reply.set_retc(EBUSY);\n        return;\n      }\n    }\n  }\n\n  common::SharedHashLocator nodeLocator = common::SharedHashLocator::makeForNode(\n      nodename);\n\n  if (!mq::SharedHashWrapper::deleteHash(gOFS->mMessagingRealm.get(),\n                                         nodeLocator)) {\n    reply.set_std_err(\"error: unable to remove config of node '\" + nodename + \"'\");\n    reply.set_retc(EIO);\n  } else {\n    if (FsView::gFsView.UnRegisterNode(nodename.c_str())) {\n      reply.set_std_out(\"success: removed node '\" + nodename + \"'\");\n    } else {\n      reply.set_std_err(\"error: unable to unregister node '\" + nodename + \"'\");\n    }\n  }\n\n  // Delete also the entry from the configuration\n  eos_info(\"msg=\\\"delete from configuration\\\" node_name=%s\",\n           nodeLocator.getConfigQueue().c_str());\n  gOFS->mConfigEngine->DeleteConfigValueByMatch(\"global\",\n      nodeLocator.getConfigQueue().c_str());\n  gOFS->mConfigEngine->AutoSave();\n}\n\n//------------------------------------------------------------------------------\n// Execute status subcommand\n//------------------------------------------------------------------------------\nvoid NodeCmd::StatusSubcmd(const eos::console::NodeProto_StatusProto& status,\n                           eos::console::ReplyProto& reply)\n{\n  std::string nodename = status.node();\n\n  if ((nodename.find(':') == std::string::npos)) {\n    nodename += \":1095\"; // default eos fst port\n  }\n\n  if ((nodename.find(\"/eos/\") == std::string::npos)) {\n    nodename.insert(0, \"/eos/\");\n    nodename.append(\"/fst\");\n  }\n\n  if (!FsView::gFsView.mNodeView.count(nodename)) {\n    reply.set_std_err(\"error: cannot find node - no node with name '\" + nodename +\n                      \"'\");\n    reply.set_retc(ENOENT);\n    return;\n  }\n\n  eos::common::RWMutexWriteLock wr_lock(FsView::gFsView.ViewMutex);\n  std::string std_out;\n  std::vector<std::string> keylist;\n  std_out +=\n    \"# ------------------------------------------------------------------------------------\\n\";\n  std_out += \"# Node Variables\\n\";\n  std_out +=\n    \"# ....................................................................................\\n\";\n  FsView::gFsView.mNodeView[nodename]->GetConfigKeys(keylist);\n  std::sort(keylist.begin(), keylist.end());\n  size_t key_width = 32;\n\n  for (const auto& key : keylist) {\n    key_width = std::max(key_width, key.length());\n  }\n\n  for (auto& i : keylist) {\n    char line[2048];\n    std::string val =\n        FormatNodeStatusValue(FsView::gFsView.mNodeView[nodename]->GetConfigMember(i));\n\n    snprintf(line, sizeof(line) - 1, \"%-*s := %s\\n\", static_cast<int>(key_width),\n             i.c_str(), val.c_str());\n    std_out += line;\n  }\n\n  reply.set_std_out(std_out);\n  reply.set_retc(0);\n}\n\n//------------------------------------------------------------------------------\n// Execute config subcommand\n//------------------------------------------------------------------------------\nvoid NodeCmd::ConfigSubcmd(const eos::console::NodeProto_ConfigProto& config,\n                           eos::console::ReplyProto& reply)\n{\n  if (mVid.uid != 0 && mVid.prot != \"sss\") {\n    reply.set_std_err(\"error: you have to take role 'root' to execute this command\");\n    reply.set_retc(EPERM);\n    return;\n  }\n\n  if (!config.node_name().length() ||\n      !config.node_key().length() ||\n      !config.node_value().length()) {\n    reply.set_std_err(\"error: invalid parameters\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  std::set<std::string> set_nodes;\n  {\n    // Collect the path of the nodes concerned\n    eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n\n    if ((config.node_name().find('*') != std::string::npos)) {\n      for (auto& it : FsView::gFsView.mNodeView) {\n        set_nodes.insert(it.first);\n      }\n    } else {\n      // by host:port name\n      std::string path = config.node_name();\n\n      if ((path.find(':') == std::string::npos)) {\n        path += \":1095\"; // default eos fst port\n      }\n\n      if ((path.find(\"/eos/\") == std::string::npos)) {\n        path.insert(0, \"/eos/\");\n        path.append(\"/fst\");\n      }\n\n      if (FsView::gFsView.mNodeView.count(path)) {\n        set_nodes.insert(path);\n      }\n    }\n  }\n\n  if (set_nodes.empty()) {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: cannot find node <\" + config.node_name() + \">\");\n    return;\n  }\n\n  // Handle file system specific configurations\n  if (config.node_key() == \"configstatus\") {\n    return ConfigFsSpecific(set_nodes, config.node_key(), config.node_value(),\n                            reply);\n  }\n\n  // Handle note specific configurations\n  for (auto& node_path : set_nodes) {\n    eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n    auto it = FsView::gFsView.mNodeView.find(node_path);\n\n    if (it == FsView::gFsView.mNodeView.end()) {\n      continue;\n    }\n\n    FsNode* node = it->second;\n\n    if (config.node_key() == \"error.simulation\") {\n      if (node->SetConfigMember(config.node_key(), config.node_value(), false)) {\n        reply.set_std_out(\"success: setting error simulation tag '\" +\n                          config.node_value() += \"'\");\n      } else {\n        reply.set_std_err(\"error: failed to store the error simulation tag\");\n        reply.set_retc(EFAULT);\n      }\n    } else if (config.node_key() == \"publish.interval\") {\n      if (node->SetConfigMember(config.node_key(), config.node_value(), false)) {\n        reply.set_std_out(\"success: setting publish interval to '\" +\n                          config.node_value() + \"'\");\n      } else {\n        reply.set_std_err(\"error: failed to store publish interval\");\n        reply.set_retc(EFAULT);\n      }\n    } else if (config.node_key() == \"debug.level\") {\n      if (node->SetConfigMember(config.node_key(), config.node_value(), false)) {\n        reply.set_std_out(\"success: setting debug level to '\" +\n                          config.node_value() + \"'\");\n      } else {\n        reply.set_std_err(\"error: failed to store debug level interval\");\n        reply.set_retc(EFAULT);\n      }\n    } else if (config.node_key() == \"stripexs\") {\n      // the value can be only 'on' or 'off'\n      if (config.node_value() != \"on\" && config.node_value() != \"off\") {\n        reply.set_std_err(\"error: stripexs value can be either \\\"on\\\" or \\\"off\\\"\");\n        reply.set_retc(EINVAL);\n      } else {\n        if (node->SetConfigMember(config.node_key(), config.node_value(), false)) {\n          reply.set_std_out(\"success: setting stripexs to '\" + config.node_value() + \"'\");\n        } else {\n          reply.set_std_err(\"error: failed to store stripexs config\");\n          reply.set_retc(EFAULT);\n        }\n      }\n    } else {\n      reply.set_std_err(\"error: the specified key is not known - consult the \"\n                        \"usage information of the command\");\n      reply.set_retc(EINVAL);\n    }\n  }\n}\n\n//----------------------------------------------------------------------------\n// Execute config operation affecting the file system parameters\n//----------------------------------------------------------------------------\nvoid\nNodeCmd::ConfigFsSpecific(const std::set<std::string>& nodes,\n                          const std::string& key, const std::string& value,\n                          eos::console::ReplyProto& reply)\n{\n  using eos::common::FileSystem;\n\n  if ((FileSystem::GetConfigStatusFromString(value.c_str()) ==\n       eos::common::ConfigStatus::kUnknown)) {\n    reply.set_std_err(\"error: not an allowed parameter <\" + value + \">\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  for (const auto& node_path : nodes) {\n    std::set<eos::common::FileSystem::fsid_t> fsids;\n    {\n      // Collect the list of file systems concerned\n      eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n      auto it = FsView::gFsView.mNodeView.find(node_path);\n\n      if (it == FsView::gFsView.mNodeView.end()) {\n        continue;\n      }\n\n      for (eos::common::FileSystem::fsid_t fsid : *it->second) {\n        fsids.insert(fsid);\n      }\n    }\n\n    for (auto fsid : fsids) {\n      eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n      auto* fs = FsView::gFsView.mIdView.lookupByID(fsid);\n\n      if (!fs) {\n        continue;\n      }\n\n      if (value == \"empty\") {\n        // Check if the file system is really empty\n        if (gOFS->eosFsView->getNumFilesOnFs(fs->GetId())) {\n          eos_static_info(\"msg=\\\"trying to set a file system that still \"\n                          \"contains files to empty state\\\" fsid=%lu\",\n                          fs->GetId());\n          reply.set_std_err(\"error: some file systems are not empty\");\n          reply.set_retc(EINVAL);\n          break;\n        }\n      }\n\n      fs->SetString(key.c_str(), value.c_str());\n      FsView::gFsView.StoreFsConfig(fs, false);\n    }\n\n    gOFS->mConfigEngine->AutoSave();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Execute set subcommand\n//------------------------------------------------------------------------------\nvoid NodeCmd::SetSubcmd(const eos::console::NodeProto_SetProto& set,\n                        eos::console::ReplyProto& reply)\n{\n  std::string nodename = set.node();\n  const std::string& status = set.node_state_switch();\n  std::string key = \"status\";\n\n  if (!nodename.length() || !status.length()) {\n    reply.set_std_err(\"error: illegal parameter\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  if ((nodename.find(':') == std::string::npos)) {\n    nodename += \":1095\"; // default eos fst port\n  }\n\n  if ((nodename.find(\"/eos/\") == std::string::npos)) {\n    nodename.insert(0, \"/eos/\");\n    nodename.append(\"/fst\");\n  }\n\n  std::string tident = mVid.tident.c_str();\n  std::string rnodename = nodename;\n  {\n    // for sss + node identification\n    rnodename.erase(0, 5);\n    size_t dpos;\n\n    if ((dpos = rnodename.find(':')) != std::string::npos) {\n      rnodename.erase(dpos);\n    }\n\n    if ((dpos = rnodename.find('.')) != std::string::npos) {\n      rnodename.erase(dpos);\n    }\n\n    size_t addpos = 0;\n\n    if ((addpos = tident.find('@')) != std::string::npos) {\n      tident.erase(0, addpos + 1);\n    }\n  }\n  eos::common::RWMutexWriteLock lock(FsView::gFsView.ViewMutex);\n  // If EOS_SKIP_SSS_HOSTNAME_MATCH env variable is set then we skip\n  // the check below as this currently breaks the Kubernetes setup.\n  bool skip_hostname_match = getenv(\"EOS_SKIP_SSS_HOSTNAME_MATCH\") ? true : false;\n\n  if (mVid.uid == 0 || mVid.prot == \"sss\") {\n    if (mVid.uid != 0 && mVid.prot == \"sss\") {\n      if (!skip_hostname_match &&\n          tident.compare(0, tident.length(), rnodename, 0, tident.length())) {\n        std::ostringstream err;\n        err << \"error: hostname mismatch '\" << tident << \"'!='\" << rnodename\n            << \"'; nodes can only be configured as 'root' \"\n            << \"or by connecting from the node itself using the sss protocol (1).\";\n        reply.set_std_err(err.str());\n        reply.set_retc(EPERM);\n        return;\n      }\n    }\n  } else {\n    reply.set_std_err(\"error: nodes can only be configured as 'root' or by \"\n                      \"connecting from the node itself using the sss protocol(2)\");\n    reply.set_retc(EPERM);\n    return;\n  }\n\n  if (!FsView::gFsView.mNodeView.count(nodename)) {\n    reply.set_std_out(\"info: creating node '\" + nodename + \"'\");\n\n    // reply.set_std_err(\"error: no such node '\" + nodename + \"'\");\n    // reply.set_retc(ENOENT);\n    if (!FsView::gFsView.RegisterNode(nodename.c_str())) {\n      reply.set_std_err(\"error: cannot register node <\" + nodename + \">\");\n      reply.set_retc(EIO);\n      return;\n    }\n  }\n\n  if (!FsView::gFsView.mNodeView[nodename]->SetConfigMember(key, status)) {\n    reply.set_std_err(\"error: cannot set node config value\");\n    reply.set_retc(EIO);\n    return;\n  }\n\n  // set also the manager name\n  if (!FsView::gFsView.mNodeView[nodename]->SetConfigMember(\"manager\",\n      gOFS->mMaster->GetMasterId(), true)) {\n    reply.set_std_err(\"error: cannot set the manager name\");\n    reply.set_retc(EIO);\n    return;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Execute proxygroup subcommand\n//------------------------------------------------------------------------------\nvoid NodeCmd::ProxygroupSubcmd(const eos::console::NodeProto_ProxygroupProto&\n                               proxygroup, eos::console::ReplyProto& reply)\n{\n  std::string nodename = proxygroup.node();\n  std::string status = (proxygroup.node_proxygroup().length()) ?\n                       proxygroup.node_proxygroup() : \"clear\";\n  std::string key = \"proxygroup\";\n  eos::console::NodeProto_ProxygroupProto::Action action =\n    proxygroup.node_action();\n\n  if (status.find_first_not_of(\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890._-\")\n      != std::string::npos) {\n    status.clear();\n  }\n\n  if (!nodename.length() || !status.length()) {\n    reply.set_std_err(\"error: illegal parameter\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  if ((nodename.find(':') == std::string::npos)) {\n    nodename += \":1095\"; // default eos fst port\n  }\n\n  if ((nodename.find(\"/eos/\") == std::string::npos)) {\n    nodename.insert(0, \"/eos/\");\n    nodename.append(\"/fst\");\n  }\n\n  std::string tident = mVid.tident.c_str();\n  std::string rnodename = nodename;\n  {\n    // for sss + node identification\n    rnodename.erase(0, 5);\n    size_t dpos;\n\n    if ((dpos = rnodename.find(':')) != std::string::npos) {\n      rnodename.erase(dpos);\n    }\n\n    if ((dpos = rnodename.find('.')) != std::string::npos) {\n      rnodename.erase(dpos);\n    }\n\n    size_t addpos = 0;\n\n    if ((addpos = tident.find('@')) != std::string::npos) {\n      tident.erase(0, addpos + 1);\n    }\n  }\n  eos::common::RWMutexWriteLock lock(FsView::gFsView.ViewMutex);\n  // If EOS_SKIP_SSS_HOSTNAME_MATCH env variable is set then we skip\n  // the check below as this currently breaks the Kubernetes setup.\n  bool skip_hostname_match = (getenv(\"EOS_SKIP_SSS_HOSTNAME_MATCH\")) ? true :\n                             false;\n\n  if (mVid.uid == 0 || mVid.prot == \"sss\") {\n    if (mVid.uid != 0 && mVid.prot == \"sss\") {\n      if (!skip_hostname_match &&\n          tident.compare(0, tident.length(), rnodename, 0, tident.length())) {\n        std::ostringstream err;\n        err << \"error: hostname mismatch '\" << tident << \"'!='\" << rnodename\n            << \"'; nodes can only be configured as 'root' \"\n            << \"or by connecting from the node itself using the sss protocol (1).\";\n        reply.set_std_err(err.str());\n        reply.set_retc(EPERM);\n        return;\n      }\n    }\n  } else {\n    reply.set_std_err(\"error: nodes can only be configured as 'root' or by \"\n                      \"connecting from the node itself using the sss protocol(2)\");\n    reply.set_retc(EPERM);\n    return;\n  }\n\n  if (!FsView::gFsView.mNodeView.count(nodename)) {\n    reply.set_std_out(\"info: creating node '\" + nodename + \"'\");\n\n    // reply.set_std_err(\"error: no such node '\" + nodename + \"'\");\n    // reply.set_retc(ENOENT);\n    if (!FsView::gFsView.RegisterNode(nodename.c_str())) {\n      reply.set_std_err(\"error: cannot register node <\" + nodename + \">\");\n      reply.set_retc(EIO);\n      return;\n    }\n  }\n\n  // we need to take the previous version of groupproxys to update it\n  std::string proxygroups = FsView::gFsView.mNodeView[nodename]->GetConfigMember(\n                              key);\n  eos_static_debug(\" old proxygroups value %s\", proxygroups.c_str());\n  // find a previous occurence\n  std::set<std::string> groups;\n  std::string::size_type pos1 = 0, pos2 = 0;\n\n  if (!proxygroups.empty()) {\n    do {\n      pos2 = proxygroups.find(',', pos1);\n      groups.insert(proxygroups.substr(pos1,\n                                       pos2 == std::string::npos ? std::string::npos : pos2 - pos1));\n      pos1 = pos2;\n\n      if (pos1 != std::string::npos) {\n        pos1++;\n      }\n    } while (pos2 != std::string::npos);\n  }\n\n  if (action == eos::console::NodeProto_ProxygroupProto::CLEAR) {\n    proxygroups = \"\";\n  } else {\n    if (action == eos::console::NodeProto_ProxygroupProto::ADD) {\n      groups.insert(status);\n    } else if (action == eos::console::NodeProto_ProxygroupProto::RM) {\n      groups.erase(status);\n    }\n\n    proxygroups.clear();\n\n    for (const auto& group : groups) {\n      proxygroups.append(group + \",\");\n    }\n\n    if (!proxygroups.empty()) {\n      proxygroups.resize(proxygroups.size() - 1);\n    }\n  }\n\n  eos_static_debug(\" new proxygroups value %s\", proxygroups.c_str());\n  status = proxygroups;\n\n  if (!FsView::gFsView.mNodeView[nodename]->SetConfigMember(key, status)) {\n    reply.set_std_err(\"error: cannot set node config value\");\n    reply.set_retc(EIO);\n    return;\n  }\n\n  // set also the manager name\n  if (!FsView::gFsView.mNodeView[nodename]->SetConfigMember(\"manager\",\n      gOFS->mMaster->GetMasterId(), true)) {\n    reply.set_std_err(\"error: cannot set the manager name\");\n    reply.set_retc(EIO);\n    return;\n  }\n}\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/NodeCmd.hh",
    "content": "//------------------------------------------------------------------------------\n// @file: NodeCmd.hh\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"proto/Node.pb.h\"\n#include \"mgm/proc/ProcCommand.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class NodeCmd - class handling node commands\n//------------------------------------------------------------------------------\nclass NodeCmd: public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit NodeCmd(eos::console::RequestProto&& req,\n                   eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, false)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~NodeCmd() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Execute ls subcommand\n  //!\n  //! @param ls ls subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void LsSubcmd(const eos::console::NodeProto_LsProto& ls,\n                eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute rm subcommand\n  //!\n  //! @param rm rm subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void RmSubcmd(const eos::console::NodeProto_RmProto& rm,\n                eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute status subcommand\n  //!\n  //! @param status status subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  static void StatusSubcmd(const eos::console::NodeProto_StatusProto& status,\n                           eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute config subcommand\n  //!\n  //! @param config config subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void ConfigSubcmd(const eos::console::NodeProto_ConfigProto& config,\n                    eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute config operation affecting the file system parameters\n  //!\n  //! @param nodes set of nodes concerned\n  //! @param key configuration key\n  //! @param value configuation value\n  //! @param reply reply protobuf object\n  //----------------------------------------------------------------------------\n  void ConfigFsSpecific(const std::set<std::string>& nodes,\n                        const std::string& key, const std::string& value,\n                        eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute set subcommand\n  //!\n  //! @param set set subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void SetSubcmd(const eos::console::NodeProto_SetProto& set,\n                 eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute proxygroup subcommand\n  //!\n  //! @param proxygroup proxygroup subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void ProxygroupSubcmd(const eos::console::NodeProto_ProxygroupProto& proxygroup,\n                        eos::console::ReplyProto& reply);\n\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/NsCmd.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file NsCmd.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"NsCmd.hh\"\n#include \"common/BehaviourConfig.hh\"\n#include \"common/LinuxFds.hh\"\n#include \"common/LinuxMemConsumption.hh\"\n#include \"common/LinuxStat.hh\"\n#include \"common/ParseUtils.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/StringUtils.hh\"\n#include \"mgm/config/IConfigEngine.hh\"\n#include \"mgm/convert/ConverterEngine.hh\"\n#include \"mgm/fsck/Fsck.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/ofs/XrdMgmOfsFile.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/tgc/MultiSpaceTapeGc.hh\"\n#include \"mgm/zmq/ZMQ.hh\"\n#include \"namespace/Constants.hh\"\n#include \"namespace/Resolver.hh\"\n#include \"namespace/interface/ContainerIterators.hh\"\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"namespace/ns_quarkdb/NamespaceGroup.hh\"\n#include \"namespace/ns_quarkdb/QClPerformance.hh\"\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n#include \"namespace/ns_quarkdb/utils/QuotaRecomputer.hh\"\n#include <algorithm>\n#include <exception>\n#include <memory>\n#include <qclient/QClient.hh>\n#include <sstream>\n#include <vector>\n\nnamespace {\nstruct HaClusterStatus {\n  std::string local;\n  std::string role;\n  std::string leader;\n  std::vector<std::string> followers;\n  std::vector<std::string> nodes;\n  bool available = false;\n};\n\nvoid\nAppendUnique(std::vector<std::string>& values, const std::string& value)\n{\n  if (value.empty()) {\n    return;\n  }\n\n  if (std::find(values.begin(), values.end(), value) == values.end()) {\n    values.push_back(value);\n  }\n}\n\nstd::string\nJoinValuesOrNone(const std::vector<std::string>& values)\n{\n  return values.empty() ? std::string(\"none\")\n                        : eos::common::StringConversion::Join(values, \",\");\n}\n\nstd::string\nExtractEndpointHost(const std::string& endpoint)\n{\n  std::string host;\n  int port = 0;\n\n  if (eos::common::ParseHostNamePort(endpoint, host, port)) {\n    return host;\n  }\n\n  return endpoint;\n}\n\nstd::string\nBuildEndpoint(const std::string& host, int port)\n{\n  if (host.empty() || (port <= 0)) {\n    return \"\";\n  }\n\n  std::ostringstream oss;\n  oss << host << \":\" << port;\n  return oss.str();\n}\n\nvoid\nDeriveFollowersFromNodes(const std::vector<std::string>& nodes, const std::string& leader,\n                         std::vector<std::string>& followers)\n{\n  for (const auto& node : nodes) {\n    if (node != leader) {\n      AppendUnique(followers, node);\n    }\n  }\n}\n\nbool\nParseRaftInfoReply(qclient::redisReplyPtr reply, HaClusterStatus& status,\n                   std::string& err)\n{\n  if (!reply) {\n    err = \"raft-info returned an empty reply\";\n    return false;\n  }\n\n  if (reply->type != REDIS_REPLY_ARRAY) {\n    std::ostringstream oss;\n    oss << \"unexpected raft-info reply: \" << qclient::describeRedisReply(reply);\n    err = oss.str();\n    return false;\n  }\n\n  for (size_t i = 0; i < reply->elements; ++i) {\n    redisReply* element = reply->element[i];\n\n    if (!element) {\n      continue;\n    }\n\n    if ((element->type != REDIS_REPLY_STRING) && (element->type != REDIS_REPLY_STATUS)) {\n      continue;\n    }\n\n    std::string entry(element->str ? element->str : \"\", element->len);\n\n    if (eos::common::startsWith(entry, \"LEADER \")) {\n      status.leader = entry.substr(7);\n    } else if (eos::common::startsWith(entry, \"MYSELF \")) {\n      status.local = entry.substr(7);\n    } else if (eos::common::startsWith(entry, \"STATUS \")) {\n      status.role = entry.substr(7);\n    } else if (eos::common::startsWith(entry, \"NODES \")) {\n      eos::common::StringConversion::Tokenize(entry.substr(6), status.nodes, \",\");\n    } else if (eos::common::startsWith(entry, \"REPLICA \")) {\n      const auto endpoint_end = entry.find_first_of(\" |\", 8);\n      AppendUnique(status.followers,\n                   entry.substr(8, endpoint_end == std::string::npos ? std::string::npos\n                                                                     : endpoint_end - 8));\n    }\n  }\n\n  if (status.followers.empty() && !status.nodes.empty()) {\n    DeriveFollowersFromNodes(status.nodes, status.leader, status.followers);\n  }\n\n  status.available = !(status.local.empty() && status.role.empty() &&\n                       status.leader.empty() && status.followers.empty());\n  return status.available;\n}\n\nHaClusterStatus\nCollectQdbHaStatus(XrdMgmOfs* ofs, std::string& err)\n{\n  HaClusterStatus status;\n\n  if (ofs->namespaceGroup->isInMemory()) {\n    return status;\n  }\n\n  try {\n    auto qdb_status_client = std::make_unique<qclient::QClient>(\n        ofs->mQdbContactDetails.members, ofs->mQdbContactDetails.constructOptions());\n    qclient::redisReplyPtr raft_reply = qdb_status_client->exec(\"raft-info\").get();\n    ParseRaftInfoReply(raft_reply, status, err);\n  } catch (const std::exception& ex) {\n    err = ex.what();\n  }\n\n  return status;\n}\n\nHaClusterStatus\nBuildMgmHaStatus(const std::string& local, bool is_master, const std::string& master_id,\n                 int mgm_port, const HaClusterStatus* qdb_status)\n{\n  HaClusterStatus status;\n  status.local = local;\n  status.role = is_master ? \"leader\" : \"follower\";\n  status.leader = master_id;\n\n  int master_port = mgm_port;\n  std::string master_host;\n  const bool have_master_endpoint =\n      eos::common::ParseHostNamePort(master_id, master_host, master_port);\n\n  if (qdb_status && have_master_endpoint && !master_id.empty() && (master_port > 0)) {\n    std::vector<std::string> qdb_nodes = qdb_status->nodes;\n\n    if (qdb_nodes.empty()) {\n      AppendUnique(qdb_nodes, qdb_status->leader);\n\n      for (const auto& follower : qdb_status->followers) {\n        AppendUnique(qdb_nodes, follower);\n      }\n    }\n\n    for (const auto& qdb_node : qdb_nodes) {\n      const std::string host = ExtractEndpointHost(qdb_node);\n      const std::string endpoint = BuildEndpoint(host, master_port);\n\n      if (!endpoint.empty() && (endpoint != master_id)) {\n        AppendUnique(status.followers, endpoint);\n      }\n    }\n  }\n\n  status.available = !(status.local.empty() && status.role.empty() &&\n                       status.leader.empty() && status.followers.empty());\n  return status;\n}\n} // namespace\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Method implementing the specific behaviour of the command executed by the\n// asynchronous thread\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nNsCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  eos::console::NsProto ns = mReqProto.ns();\n  eos::console::NsProto::SubcmdCase subcmd = ns.subcmd_case();\n\n  if (subcmd == eos::console::NsProto::kStat) {\n    StatSubcmd(ns.stat(), reply);\n  } else if (subcmd == eos::console::NsProto::kMutex) {\n    MutexSubcmd(ns.mutex(), reply);\n  } else if (subcmd == eos::console::NsProto::kCompact) {\n    CompactSubcmd(ns.compact(), reply);\n  } else if (subcmd == eos::console::NsProto::kMaster) {\n    MasterSubcmd(ns.master(), reply);\n  } else if (subcmd == eos::console::NsProto::kTree) {\n    TreeSizeSubcmd(ns.tree(), reply);\n  } else if (subcmd == eos::console::NsProto::kCache) {\n    CacheSubcmd(ns.cache(), reply);\n  } else if (subcmd == eos::console::NsProto::kQuota) {\n    QuotaSizeSubcmd(ns.quota(), reply);\n  } else if (subcmd == eos::console::NsProto::kDrain) {\n    DrainSubcmd(ns.drain(), reply);\n  } else if (subcmd == eos::console::NsProto::kReserve) {\n    ReserveIdsSubCmd(ns.reserve(), reply);\n  } else if (subcmd == eos::console::NsProto::kBenchmark) {\n    BenchmarkSubCmd(ns.benchmark(), reply);\n  } else if (subcmd == eos::console::NsProto::kTracker) {\n    TrackerSubCmd(ns.tracker(), reply);\n  } else if (subcmd == eos::console::NsProto::kBehaviour) {\n    BehaviourSubCmd(ns.behaviour(), reply);\n  } else {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: not supported\");\n  }\n\n  return reply;\n}\n\n//------------------------------------------------------------------------------\n// Execute mutex subcommand\n//------------------------------------------------------------------------------\nvoid\nNsCmd::MutexSubcmd(const eos::console::NsProto_MutexProto& mutex,\n                   eos::console::ReplyProto& reply)\n{\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n\n  if (mVid.uid == 0) {\n    bool no_option = true;\n    std::ostringstream oss;\n\n    if (mutex.sample_rate1() || mutex.sample_rate10() ||\n        mutex.sample_rate100() || mutex.toggle_timing() ||\n        mutex.toggle_order() || mutex.blockedtime()) {\n      no_option = false;\n    }\n\n    eos::common::RWMutex* fs_mtx = &FsView::gFsView.ViewMutex;\n    eos::common::RWMutex* quota_mtx = &Quota::pMapMutex;\n    eos::common::RWMutex* ns_mtx = &gOFS->eosViewRWMutex;\n    eos::common::RWMutex* fusex_client_mtx = &gOFS->zMQ->gFuseServer.Client();\n    //eos::common::RWMutex* fusex_cap_mtx = &gOFS->zMQ->gFuseServer.Cap();\n\n    if (no_option) {\n      size_t cycleperiod = eos::common::RWMutex::GetLockUnlockDuration();\n      std::string line = \"# ------------------------------------------------------\"\n                         \"------------------------------\";\n      oss << line << std::endl\n          << \"# Mutex Monitoring Management\" << std::endl\n          << line << std::endl\n          << \"order checking is : \"\n          << (eos::common::RWMutex::GetOrderCheckingGlobal() ? \"on \" : \"off\")\n          << \" (estimated order checking latency for 1 rule \";\n      size_t orderlatency = eos::common::RWMutex::GetOrderCheckingLatency();\n      oss <<  orderlatency << \" nsec / \"\n          << int(double(orderlatency) / cycleperiod * 100)\n          << \"% of the mutex lock/unlock cycle duration)\" << std::endl\n          << \"deadlock checking is : \"\n          << (eos::common::RWMutex::GetDeadlockCheckingGlobal() ? \"on\" : \"off\")\n          << std::endl\n          << \"timing         is : \"\n          << (fs_mtx->GetTiming() ? \"on \" : \"off\")\n          << \" (estimated timing latency for 1 lock \";\n      size_t timinglatency = eos::common::RWMutex::GetTimingLatency();\n      oss << timinglatency << \" nsec / \"\n          << int(double(timinglatency) / cycleperiod * 100)\n          << \"% of the mutex lock/unlock cycle duration)\" << std::endl\n          << \"sampling rate  is : \";\n      float sr = fs_mtx->GetSampling();\n      char ssr[32];\n      sprintf(ssr, \"%f\", sr);\n      oss << (sr < 0 ? \"NA\" : ssr);\n\n      if (sr > 0) {\n        oss << \" (estimated average timing latency \"\n            << int(double(timinglatency) * sr) << \" nsec / \"\n            << int((timinglatency * sr) / cycleperiod * 100)\n            << \"% of the mutex lock/unlock cycle duration)\";\n      }\n\n      oss << std::endl;\n      oss << \"blockedtiming  is : \";\n      oss << ns_mtx->BlockedForMsInterval() << \" ms\" << std::endl;\n    }\n\n    if (mutex.toggle_timing()) {\n      if (fs_mtx->GetTiming()) {\n        fs_mtx->SetTiming(false);\n        quota_mtx->SetTiming(false);\n        ns_mtx->SetTiming(false);\n        oss << \"mutex timing is off\" << std::endl;\n      } else {\n        fs_mtx->SetTiming(true);\n        quota_mtx->SetTiming(true);\n        ns_mtx->SetTiming(true);\n        oss << \"mutex timing is on\" << std::endl;\n      }\n    }\n\n    if (mutex.toggle_order()) {\n      if (eos::common::RWMutex::GetOrderCheckingGlobal()) {\n        eos::common::RWMutex::SetOrderCheckingGlobal(false);\n        oss << \"mutex order checking is off\" << std::endl;\n      } else {\n        eos::common::RWMutex::SetOrderCheckingGlobal(true);\n        oss << \"mutex order checking is on\" << std::endl;\n      }\n    }\n\n    if (mutex.toggle_deadlock()) {\n      if (eos::common::RWMutex::GetDeadlockCheckingGlobal()) {\n        eos::common::RWMutex::SetDeadlockCheckingGlobal(false);\n        oss << \"mutex deadlock checking is off\" << std::endl;\n      } else {\n        eos::common::RWMutex::SetDeadlockCheckingGlobal(true);\n        oss << \"mutex deadlock checking is on\" << std::endl;\n      }\n    }\n\n    if (mutex.blockedtime()) {\n      fs_mtx->SetBlockedForMsInterval(mutex.blockedtime());\n      ns_mtx->SetBlockedForMsInterval(mutex.blockedtime());\n      quota_mtx->SetBlockedForMsInterval(mutex.blockedtime());\n      fusex_client_mtx->SetBlockedForMsInterval(mutex.blockedtime());\n      //fusex_cap_mtx->SetBlockedForMsInterval(mutex.blockedtime());\n      oss << \"blockedtiming set to \" << ns_mtx->BlockedForMsInterval() << \" ms\" <<\n          std::endl;\n    }\n\n    if (mutex.sample_rate1() || mutex.sample_rate10() ||\n        mutex.sample_rate100()) {\n      float rate = 0.0;\n\n      if (mutex.sample_rate1()) {\n        rate = 0.01;\n      } else if (mutex.sample_rate10()) {\n        rate = 0.1;\n      } else if (mutex.sample_rate100()) {\n        rate = 1.0;\n      }\n\n      fs_mtx->SetSampling(true, rate);\n      quota_mtx->SetSampling(true, rate);\n      ns_mtx->SetSampling(true, rate);\n    }\n\n    reply.set_std_out(oss.str());\n  } else {\n    reply.set_std_err(\"error: you have to take role 'root' to execute this\"\n                      \" command\");\n    reply.set_retc(EPERM);\n  }\n\n#endif\n}\n\n//------------------------------------------------------------------------------\n// Execute stat command\n//------------------------------------------------------------------------------\nvoid\nNsCmd::StatSubcmd(const eos::console::NsProto_StatProto& stat,\n                  eos::console::ReplyProto& reply)\n{\n  using eos::common::StringConversion;\n  std::ostringstream oss;\n  std::ostringstream err;\n  int retc = 0;\n\n  if (stat.reset()) {\n    gOFS->MgmStats.Clear();\n    oss << \"success: all counters have been reset\" << std::endl;\n  }\n\n  uint64_t f = gOFS->eosFileService->getNumFiles();\n  uint64_t d = gOFS->eosDirectoryService->getNumContainers();\n  eos::common::FileId::fileid_t fid_now = gOFS->eosFileService->getFirstFreeId();\n  eos::common::FileId::fileid_t cid_now =\n    gOFS->eosDirectoryService->getFirstFreeId();\n  struct stat statf;\n  struct stat statd;\n  memset(&statf, 0, sizeof(struct stat));\n  memset(&statd, 0, sizeof(struct stat));\n  XrdOucString clfsize = \"\";\n  XrdOucString cldsize = \"\";\n  XrdOucString clfratio = \"\";\n  XrdOucString cldratio = \"\";\n  XrdOucString sizestring = \"\";\n\n  // Statistics for the changelog files if they exist\n  if ((gOFS->MgmNsFileChangeLogFile.length() != 0) &&\n      (gOFS->MgmNsDirChangeLogFile.length() != 0) &&\n      (!::stat(gOFS->MgmNsFileChangeLogFile.c_str(), &statf)) &&\n      (!::stat(gOFS->MgmNsDirChangeLogFile.c_str(), &statd))) {\n    StringConversion::GetReadableSizeString(clfsize,\n                                            (unsigned long long) statf.st_size, \"B\");\n    StringConversion::GetReadableSizeString(cldsize,\n                                            (unsigned long long) statd.st_size, \"B\");\n    StringConversion::GetReadableSizeString(clfratio,\n                                            (unsigned long long) f ?\n                                            (1.0 * statf.st_size) / f : 0, \"B\");\n    StringConversion::GetReadableSizeString(cldratio,\n                                            (unsigned long long) d ?\n                                            (1.0 * statd.st_size) / d : 0, \"B\");\n  }\n\n  time_t fboot_time = 0;\n  time_t boot_time = 0;\n  std::string bootstring = namespaceStateToString(gOFS->mNamespaceState);\n\n  if (bootstring == \"booting\") {\n    fboot_time = time(nullptr) - gOFS->mFileInitTime;\n    boot_time = time(nullptr) - gOFS->mStartTime;\n  } else {\n    fboot_time = gOFS->mFileInitTime;\n    boot_time = gOFS->mTotalInitTime;\n  }\n\n  // Statistics for memory usage\n  eos::common::LinuxMemConsumption::linux_mem_t mem;\n\n  if (!eos::common::LinuxMemConsumption::GetMemoryFootprint(mem)) {\n    err << \"error: failed to get the memory usage information\" << std::endl;\n    retc = errno;\n  }\n\n  eos::common::LinuxStat::linux_stat_t pstat;\n\n  if (!eos::common::LinuxStat::GetStat(pstat)) {\n    err << \"error: failed to get the process stat information\" << std::endl;\n    retc = errno;\n  }\n\n  eos::common::LinuxFds::linux_fds_t fds;\n\n  if (!eos::common::LinuxFds::GetFdUsage(fds)) {\n    err << \"error: failed to get the process fd information\" << std::endl;\n    retc = errno;\n  }\n\n  std::string master_status = gOFS->mMaster->PrintOut();\n  const std::string mgm_master_id = gOFS->mMaster->GetMasterId();\n  const bool is_mgm_master = gOFS->mMaster->IsMaster();\n  const std::string mgm_local_id = gOFS->ManagerId.c_str();\n  HaClusterStatus qdb_ha_status;\n  std::string qdb_ha_err;\n  XrdOucString compact_status = \"\";\n  size_t eosxd_nclients = 0;\n  size_t eosxd_active_clients = 0;\n  size_t eosxd_locked_clients = 0;\n  gOFS->zMQ->gFuseServer.Client().ClientStats(eosxd_nclients,\n      eosxd_active_clients, eosxd_locked_clients);\n  bool monitoring = stat.monitor() || WantsJsonOutput();\n  CacheStatistics fileCacheStats = gOFS->eosFileService->getCacheStatistics();\n  CacheStatistics containerCacheStats =\n    gOFS->eosDirectoryService->getCacheStatistics();\n  common::MutexLatencyWatcher::LatencySpikes viewLatency =\n    gOFS->mViewMutexWatcher.getLatencySpikes();\n  double eosViewMutexPenultimateSecWriteLockTimePercentage =\n    (gOFS->eosViewRWMutex.getNbMsMutexWriteLockedPenultimateSecond().count() /\n     1000.0) * 100.0;\n\n  if (eosViewMutexPenultimateSecWriteLockTimePercentage > 100) {\n    eosViewMutexPenultimateSecWriteLockTimePercentage = 100;\n  }\n\n  double readcontention = gOFS->MgmStats.GetReadContention();\n  double writecontention = gOFS->MgmStats.GetWriteContention();\n\n  qdb_ha_status = CollectQdbHaStatus(gOFS, qdb_ha_err);\n\n  if (!qdb_ha_err.empty()) {\n    eos_debug(\"msg=\\\"failed to collect qdb raft status for eos ns\\\" err=\\\"%s\\\"\",\n              qdb_ha_err.c_str());\n  }\n\n  HaClusterStatus mgm_ha_status =\n      BuildMgmHaStatus(mgm_local_id, is_mgm_master, mgm_master_id, gOFS->ManagerPort,\n                       qdb_ha_status.available ? &qdb_ha_status : nullptr);\n\n  if (monitoring) {\n    oss << \"uid=all gid=all ns.total.files=\" << f\n        << \"\\nuid=all gid=all ns.total.directories=\" << d\n        << \"\\nuid=all gid=all ns.current.fid=\" << fid_now\n        << \"\\nuid=all gid=all ns.current.cid=\" << cid_now\n        << \"\\nuid=all gid=all ns.generated.fid=\" << (int)(fid_now - gOFS->mBootFileId)\n        << \"\\nuid=all gid=all ns.generated.cid=\"\n        << (int)(cid_now - gOFS->mBootContainerId)\n        << \"\\nuid=all gid=all ns.contention.read=\" << readcontention\n        << \"\\nuid=all gid=all ns.contention.write=\" << writecontention\n        << \"\\nuid=all gid=all ns.cache.files.maxsize=\" << fileCacheStats.maxNum\n        << \"\\nuid=all gid=all ns.cache.files.occupancy=\" << fileCacheStats.occupancy\n        << \"\\nuid=all gid=all ns.cache.files.requests=\" << fileCacheStats.numRequests\n        << \"\\nuid=all gid=all ns.cache.files.hits=\" << fileCacheStats.numHits\n        << \"\\nuid=all gid=all ns.cache.containers.maxsize=\" << containerCacheStats.maxNum\n        << \"\\nuid=all gid=all ns.cache.containers.occupancy=\"\n        << containerCacheStats.occupancy\n        << \"\\nuid=all gid=all ns.cache.containers.requests=\"\n        << containerCacheStats.numRequests\n        << \"\\nuid=all gid=all ns.cache.containers.hits=\" << containerCacheStats.numHits\n        << \"\\nuid=all gid=all ns.total.files.changelog.size=\"\n        << StringConversion::GetSizeString(clfsize, (unsigned long long)statf.st_size)\n        << std::endl\n        << \"uid=all gid=all ns.total.directories.changelog.size=\"\n        << StringConversion::GetSizeString(cldsize, (unsigned long long)statd.st_size)\n        << std::endl\n        << \"uid=all gid=all ns.total.files.changelog.avg_entry_size=\"\n        << StringConversion::GetSizeString(\n               clfratio, (unsigned long long)f ? (1.0 * statf.st_size) / f : 0)\n        << std::endl\n        << \"uid=all gid=all ns.total.directories.changelog.avg_entry_size=\"\n        << StringConversion::GetSizeString(\n               cldratio, (unsigned long long)d ? (1.0 * statd.st_size) / d : 0)\n        << std::endl\n        << \"uid=all gid=all \" << compact_status.c_str() << std::endl\n        << \"uid=all gid=all ns.boot.status=\" << bootstring << std::endl\n        << \"uid=all gid=all ns.boot.time=\" << boot_time << std::endl\n        << \"uid=all gid=all ns.boot.file.time=\" << fboot_time\n        << std::endl\n        // Backwards compatibility for callers that still parse the legacy\n        // is_master/master_id fields. Keep this for now, but remove it once\n        // callers have switched to the structured ns.mgm.* fields.\n        << \"uid=all gid=all \" << master_status.c_str() << std::endl\n        << \"uid=all gid=all ns.mgm.local=\" << mgm_ha_status.local << std::endl\n        << \"uid=all gid=all ns.mgm.role=\" << mgm_ha_status.role << std::endl\n        << \"uid=all gid=all ns.mgm.leader=\" << mgm_ha_status.leader << std::endl\n        << \"uid=all gid=all ns.mgm.followers=\"\n        << JoinValuesOrNone(mgm_ha_status.followers) << std::endl\n        << \"uid=all gid=all ns.memory.virtual=\" << mem.vmsize << std::endl\n        << \"uid=all gid=all ns.memory.resident=\" << mem.resident << std::endl\n        << \"uid=all gid=all ns.memory.share=\" << mem.share << std::endl\n        << \"uid=all gid=all ns.stat.threads=\" << pstat.threads << std::endl\n        << \"uid=all gid=all ns.fds.all=\" << fds.all << std::endl\n        << \"uid=all gid=all ns.fusex.caps=\" << gOFS->zMQ->gFuseServer.Cap().ncaps()\n        << std::endl\n        << \"uid=all gid=all ns.fusex.clients=\" << eosxd_nclients << std::endl\n        << \"uid=all gid=all ns.fusex.activeclients=\" << eosxd_active_clients << std::endl\n        << \"uid=all gid=all ns.fusex.lockedclients=\" << eosxd_locked_clients << std::endl\n        << \"uid=all gid=all ns.hanging=\" << gOFS->mViewMutexWatcher.isLockedUp()\n        << std::endl\n        << \"uid=all gid=all ns.hanging.since=\" << gOFS->mViewMutexWatcher.hangingSince()\n        << std::endl\n        << \"uid=all gid=all ns.latencypeak.eosviewmutex.last=\" << viewLatency.last.count()\n        << std::endl\n        << \"uid=all gid=all ns.latencypeak.eosviewmutex.1min=\"\n        << viewLatency.lastMinute.count() << std::endl\n        << \"uid=all gid=all ns.latencypeak.eosviewmutex.2min=\"\n        << viewLatency.last2Minutes.count() << std::endl\n        << \"uid=all gid=all ns.latencypeak.eosviewmutex.5min=\"\n        << viewLatency.last5Minutes.count() << std::endl\n        << \"uid=all gid=all ns.eosviewmutex.penultimateseclocktimepercent=\"\n        << eosViewMutexPenultimateSecWriteLockTimePercentage << std::endl;\n\n    if (!gOFS->namespaceGroup->isInMemory()) {\n      auto* qdb_group = dynamic_cast<eos::QuarkNamespaceGroup*>\n                        (gOFS->namespaceGroup.get());\n      auto* perf_monitor = dynamic_cast<eos::QClPerfMonitor*>\n                           (qdb_group->getPerformanceMonitor().get());\n      std::map<std::string, unsigned long long> info = perf_monitor->GetPerfMarkers();\n      oss << \"uid=all gid=all ns.qclient.persistency_type=\"\n          << qdb_group->getMetadataFlusher()->getPersistencyType() << \"\\n\";\n      oss << \"uid=all gid=all ns.qdb.leader=\" << qdb_ha_status.leader << \"\\n\"\n          << \"uid=all gid=all ns.qdb.followers=\"\n          << JoinValuesOrNone(qdb_ha_status.followers) << \"\\n\";\n\n      if (info.find(\"rtt_min\") != info.end()) {\n        oss << \"uid=all gid=all ns.qclient.rtt_ms.min=\"\n            << info[\"rtt_min\"] / 1000 << std::endl\n            << \"uid=all gid=all ns.qclient.rtt_ms.avg=\"\n            << info[\"rtt_avg\"] / 1000 << std::endl\n            << \"uid=all gid=all ns.qclient.rtt_ms.max=\"\n            << info[\"rtt_max\"] / 1000 << std::endl\n            << \"uid=all gid=all ns.qclient.rtt_ms_peak.1min=\"\n            << info[\"rtt_peak_1m\"] / 1000 << std::endl\n            << \"uid=all gid=all ns.qclient.rtt_ms_peak.2min=\"\n            << info[\"rtt_peak_2m\"] / 1000 << std::endl\n            << \"uid=all gid=all ns.qclient.rtt_ms_peak.5min=\"\n            << info[\"rtt_peak_5m\"] / 1000 << std::endl;\n      }\n    }\n\n    if (pstat.vsize > gOFS->LinuxStatsStartup.vsize) {\n      oss << \"uid=all gid=all ns.memory.growth=\" << (unsigned long long)\n          (pstat.vsize - gOFS->LinuxStatsStartup.vsize) << std::endl;\n    } else {\n      oss << \"uid=all gid=all ns.memory.growth=-\" << (unsigned long long)\n          (-pstat.vsize + gOFS->LinuxStatsStartup.vsize) << std::endl;\n    }\n\n    oss << \"uid=all gid=all ns.uptime=\"\n        << (int)(time(NULL) - gOFS->mStartTime) << std::endl\n        << \"uid=all gid=all \"\n        << gOFS->mDrainEngine.GetThreadPoolInfo() << std::endl\n        << \"uid=all gid=all \"\n        << gOFS->mFsckEngine->GetThreadPoolInfo() << std::endl\n        << \"uid=all gid=all \"\n        << (gOFS->mConverterEngine ?\n            gOFS->mConverterEngine->GetThreadPoolInfo() :\n            \"info=\\\"converter driver not running\\\"\")\n        << std::endl;\n    FsView::gFsView.DumpBalancerPoolInfo(oss, \"uid=all gid=all \");\n\n    // Only display the tape enabled state if it is set to true in order to\n    // simplify the disk-only use of EOS\n    if (gOFS->mTapeEnabled) {\n      oss << \"uid=all gid=all ns.tapeenabled=true\" << std::endl;\n      // GC should only be active on the master MGM node\n      oss << \"uid=all gid=all tgc.is_active=\"\n          << (gOFS->mTapeGc->isGcActive() ? \"true\" : \"false\")\n          << std::endl;\n      // Tape GC stats are only displayed if enabled for at least one EOS space\n      const auto tgcStats = gOFS->mTapeGc->getStats();\n\n      if (!tgcStats.empty()) {\n        oss << \"uid=all gid=all tgc.stats=evicts\";\n\n        for (auto itor = tgcStats.begin(); itor != tgcStats.end(); itor++) {\n          const std::string& tgcSpace = itor->first;\n          const tgc::TapeGcStats& tgcSpaceStats = itor->second;\n          oss << \" \" << tgcSpace << \"=\" << tgcSpaceStats.nbEvicts;\n        }\n\n        oss << std::endl;\n        oss << \"uid=all gid=all tgc.stats=queuesize\";\n\n        for (auto itor = tgcStats.begin(); itor != tgcStats.end(); itor++) {\n          const std::string& tgcSpace = itor->first;\n          const tgc::TapeGcStats& tgcSpaceStats = itor->second;\n          oss << \" \" << tgcSpace << \"=\" << tgcSpaceStats.lruQueueSize;\n        }\n\n        oss << std::endl;\n        oss << \"uid=all gid=all tgc.stats=totalbytes\";\n\n        for (auto itor = tgcStats.begin(); itor != tgcStats.end(); itor++) {\n          const std::string& tgcSpace = itor->first;\n          const tgc::TapeGcStats& tgcStats = itor->second;\n          oss << \" \" << tgcSpace << \"=\" << tgcStats.spaceStats.totalBytes;\n        }\n\n        oss << std::endl;\n        oss << \"uid=all gid=all tgc.stats=availbytes\";\n\n        for (auto itor = tgcStats.begin(); itor != tgcStats.end(); itor++) {\n          const std::string& tgcSpace = itor->first;\n          const tgc::TapeGcStats& tgcStats = itor->second;\n          oss << \" \" << tgcSpace << \"=\" << tgcStats.spaceStats.availBytes;\n        }\n\n        oss << std::endl;\n        oss << \"uid=all gid=all tgc.stats=qrytimestamp\";\n\n        for (auto itor = tgcStats.begin(); itor != tgcStats.end(); itor++) {\n          const std::string& tgcSpace = itor->first;\n          const tgc::TapeGcStats& tgcSpaceStats = itor->second;\n          oss << \" \" << tgcSpace << \"=\" << tgcSpaceStats.queryTimestamp;\n        }\n\n        oss << std::endl;\n      }\n    }\n  } else {\n    std::string line = \"# ------------------------------------------------------\"\n                       \"------------------------------\";\n    oss << line << std::endl\n        << \"# Namespace Statistics\" << std::endl\n        << line << std::endl\n        << \"ALL      Files                            \" << f << \" [\" << bootstring\n        << \"] (\" << fboot_time << \"s)\" << std::endl\n        << \"ALL      Directories                      \" << d << std::endl\n        << \"ALL      Total boot time                  \" << boot_time << \" s\" << std::endl\n        << \"ALL      Contention                       write: \" << std::fixed\n        << std::setprecision(2) << writecontention << \" % read:\" << std::fixed\n        << std::setprecision(2) << readcontention << \" %\" << std::endl\n        << line << std::endl;\n\n    if (compact_status.length()) {\n      oss << \"ALL      Compactification                 \"\n          << compact_status.c_str() << std::endl\n          << line << std::endl;\n    }\n\n    oss << \"ALL      Replication                      \" << master_status.c_str()\n        << std::endl\n        << \"ALL      MGM Leadership                   \"\n        << \"leader=\" << (mgm_ha_status.leader.empty() ? \"unknown\" : mgm_ha_status.leader)\n        << \" role=\" << mgm_ha_status.role << std::endl\n        << \"ALL      MGM followers                    \"\n        << JoinValuesOrNone(mgm_ha_status.followers) << std::endl;\n\n    if (qdb_ha_status.available) {\n      oss << \"ALL      QDB Leadership                   \"\n          << \"leader=\"\n          << (qdb_ha_status.leader.empty() ? \"unknown\" : qdb_ha_status.leader)\n          << std::endl\n          << \"ALL      QDB followers                    \"\n          << JoinValuesOrNone(qdb_ha_status.followers) << std::endl;\n    }\n\n    oss << line << std::endl;\n\n    if (clfsize.length() && cldsize.length()) {\n      oss << \"ALL      File Changelog Size              \" << clfsize << std::endl\n          << \"ALL      Dir  Changelog Size              \" << cldsize << std::endl\n          << line << std::endl\n          << \"ALL      avg. File Entry Size             \" << clfratio << std::endl\n          << \"ALL      avg. Dir  Entry Size             \" << cldratio << std::endl\n          << line << std::endl;\n    }\n\n    oss << \"ALL      files created since boot         \"\n        << (int)(fid_now - gOFS->mBootFileId) << std::endl\n        << \"ALL      container created since boot     \"\n        << (int)(cid_now - gOFS->mBootContainerId) << std::endl\n        << line << std::endl\n        << \"ALL      current file id                  \" << fid_now\n        << std::endl\n        << \"ALL      current container id             \" << cid_now\n        << std::endl\n        << line << std::endl\n        << \"ALL      eosxd caps                       \"\n        << gOFS->zMQ->gFuseServer.Cap().Dump() << std::endl\n        << \"ALL      eosxd clients                    \" <<\n        eosxd_nclients << std::endl\n        << \"ALL      eosxd active clients             \" <<\n        eosxd_active_clients << std::endl\n        << \"ALL      eosxd locked clients             \" <<\n        eosxd_locked_clients << std::endl\n        << line << std::endl;\n\n    if (fileCacheStats.enabled || containerCacheStats.enabled) {\n      oss << \"ALL      File cache max num               \" << fileCacheStats.maxNum\n          << std::endl\n          << \"ALL      File cache occupancy             \" << fileCacheStats.occupancy\n          << std::endl\n          << \"ALL      In-flight FileMD                 \" << fileCacheStats.inFlight\n          << std::endl\n          << \"ALL      Container cache max num          \" << containerCacheStats.maxNum\n          << std::endl\n          << \"ALL      Container cache occupancy        \" << containerCacheStats.occupancy\n          << std::endl\n          << \"ALL      In-flight ContainerMD            \" << containerCacheStats.inFlight\n          << std::endl\n          << line << std::endl;\n    }\n\n    oss << \"ALL      eosViewRWMutex status            \" <<\n        (gOFS->mViewMutexWatcher.isLockedUp() ? \"locked-up\" : \"available\")\n        << \" (\" << gOFS->mViewMutexWatcher.hangingSince() << \"s) \" << std::endl;\n    oss << \"ALL      eosViewRWMutex peak-latency      \" << viewLatency.last.count()\n        << \"ms (last) \"\n        << viewLatency.lastMinute.count() << \"ms (1 min) \" <<\n        viewLatency.last2Minutes.count() << \"ms (2 min) \" <<\n        viewLatency.last5Minutes.count() << \"ms (5 min)\"\n        << std::endl;\n    oss << \"ALL      eosViewRWMutex locked for \" <<\n        eosViewMutexPenultimateSecWriteLockTimePercentage\n        << \"% of the penultimate second\" << std::endl << line << std::endl;\n\n    if (!gOFS->namespaceGroup->isInMemory()) {\n      auto* qdb_group = dynamic_cast<eos::QuarkNamespaceGroup*>\n                        (gOFS->namespaceGroup.get());\n      auto* perf_monitor = dynamic_cast<eos::QClPerfMonitor*>\n                           (qdb_group->getPerformanceMonitor().get());\n      std::map<std::string, unsigned long long> info = perf_monitor->GetPerfMarkers();\n      oss << \"ALL      QClient Persistency              \"\n          << qdb_group->getMetadataFlusher()->getPersistencyType() << \"\\n\";\n\n      if (info.find(\"rtt_min\") != info.end()) {\n        oss << \"ALL      QClient overall RTT              \"\n            << info[\"rtt_min\"] / 1000 << \"ms (min)  \"\n            << info[\"rtt_avg\"] / 1000 << \"ms (avg)  \"\n            << info[\"rtt_max\"] / 1000 << \"ms (max)  \"\n            << std::endl\n            << \"ALL      QClient recent peak RTT          \"\n            << info[\"rtt_peak_1m\"] / 1000 << \"ms (1 min) \"\n            << info[\"rtt_peak_2m\"] / 1000 << \"ms (2 min) \"\n            << info[\"rtt_peak_5m\"] / 1000 << \"ms (5 min)\"\n            << std::endl  << line << std::endl;\n      }\n    }\n\n    // Do them one at a time otherwise sizestring is saved only the first time\n    oss << \"ALL      memory virtual                   \"\n        << StringConversion::GetReadableSizeString(sizestring, (unsigned long long)\n            mem.vmsize, \"B\")\n        << std::endl;\n    oss << \"ALL      memory resident                  \"\n        << StringConversion::GetReadableSizeString(sizestring, (unsigned long long)\n            mem.resident, \"B\")\n        << std::endl;\n    oss << \"ALL      memory share                     \"\n        << StringConversion::GetReadableSizeString(sizestring, (unsigned long long)\n            mem.share, \"B\")\n        << std::endl\n        << \"ALL      memory growths                   \";\n\n    if (pstat.vsize > gOFS->LinuxStatsStartup.vsize) {\n      oss << StringConversion::GetReadableSizeString\n          (sizestring, (unsigned long long)(pstat.vsize - gOFS->LinuxStatsStartup.vsize),\n           \"B\")\n          << std::endl;\n    } else {\n      oss << StringConversion::GetReadableSizeString\n          (sizestring, (unsigned long long)(-pstat.vsize + gOFS->LinuxStatsStartup.vsize),\n           \"B\")\n          << std::endl;\n    }\n\n    oss << \"ALL      threads                          \" <<  pstat.threads\n        << std::endl\n        << \"ALL      fds                              \" << fds.all\n        << std::endl\n        << \"ALL      uptime                           \"\n        << (int)(time(NULL) - gOFS->mStartTime) << std::endl\n        << line << std::endl\n        << \"ALL      drain info                       \"\n        << gOFS->mDrainEngine.GetThreadPoolInfo() << std::endl\n        << \"ALL      fsck info                        \"\n        << gOFS->mFsckEngine->GetThreadPoolInfo() << std::endl\n        << \"ALL      converter info                   \"\n        << (gOFS->mConverterEngine ?\n            gOFS->mConverterEngine->GetThreadPoolInfo() :\n            \"info=\\\"converter driver not running\\\"\")\n        << std::endl;\n    std::string_view prefix {\"ALL      balancer info                    \"};\n    FsView::gFsView.DumpBalancerPoolInfo(oss, prefix);\n    oss << \"ALL      traffic shaping info             \"\n        << \"is_enabled=\" << (gOFS->mTrafficShapingEngine.IsEnabled() ? \"true\" : \"false\")\n        << std::endl;\n    oss << line << std::endl\n        << gOFS->mFidTracker.PrintStats() << std::endl\n        << line << std::endl;\n\n    // Only display the tape enabled state if it is set to true in order to\n    // simplify the disk-only use of EOS\n    if (gOFS->mTapeEnabled) {\n      oss << \"ALL      tapeenabled                      true\" << std::endl;\n      // GC should only be active on the master MGM node\n      oss << \"ALL      tgc is active                    \"\n          << (gOFS->mTapeGc->isGcActive() ? \"true\" : \"false\")\n          << std::endl;\n      // Tape GC stats are only displayed if enabled for at least one EOS space\n      const auto tgcStats = gOFS->mTapeGc->getStats();\n\n      if (!tgcStats.empty()) {\n        oss << \"ALL      tgc.stats=evicts             \";\n\n        for (auto itor = tgcStats.begin(); itor != tgcStats.end(); itor++) {\n          const std::string& tgcSpace = itor->first;\n          const tgc::TapeGcStats& tgcSpaceStats = itor->second;\n          oss << \" \" << tgcSpace << \"=\" << tgcSpaceStats.nbEvicts;\n        }\n\n        oss << std::endl;\n        oss << \"ALL      tgc.stats=queuesize             \";\n\n        for (auto itor = tgcStats.begin(); itor != tgcStats.end(); itor++) {\n          const std::string& tgcSpace = itor->first;\n          const tgc::TapeGcStats& tgcSpaceStats = itor->second;\n          oss << \" \" << tgcSpace << \"=\" << tgcSpaceStats.lruQueueSize;\n        }\n\n        oss << std::endl;\n        oss << \"ALL      tgc.stats=totalbytes            \";\n\n        for (auto itor = tgcStats.begin(); itor != tgcStats.end(); itor++) {\n          const std::string& tgcSpace = itor->first;\n          const tgc::TapeGcStats& tgcStats = itor->second;\n          oss << \" \" << tgcSpace << \"=\" << tgcStats.spaceStats.totalBytes;\n        }\n\n        oss << std::endl;\n        oss << \"ALL      tgc.stats=availbytes            \";\n\n        for (auto itor = tgcStats.begin(); itor != tgcStats.end(); itor++) {\n          const std::string& tgcSpace = itor->first;\n          const tgc::TapeGcStats& tgcStats = itor->second;\n          oss << \" \" << tgcSpace << \"=\" << tgcStats.spaceStats.availBytes;\n        }\n\n        oss << std::endl;\n        oss << \"ALL      tgc.stats=qrytimestamp          \";\n\n        for (auto itor = tgcStats.begin(); itor != tgcStats.end(); itor++) {\n          const std::string& tgcSpace = itor->first;\n          const tgc::TapeGcStats& tgcSpaceStats = itor->second;\n          oss << \" \" << tgcSpace << \"=\" << tgcSpaceStats.queryTimestamp;\n        }\n\n        oss << std::endl;\n      }\n\n      oss << line << std::endl;\n    }\n  }\n\n  if (!stat.summary()) {\n    XrdOucString stats_out;\n    gOFS->MgmStats.PrintOutTotal(stats_out, stat.groupids(), monitoring,\n                                 stat.numericids(), stat.apps());\n    oss << stats_out.c_str();\n  }\n\n  oss << gOFS->mTracker.PrintOut(monitoring);\n\n  if (WantsJsonOutput()) {\n    std::string out = ResponseToJsonString(oss.str(), err.str(), retc);\n    oss.clear(), oss.str(out);\n  } else {\n    if (!monitoring && (mReqProto.dontcolor() == false)) {\n      std::string out = oss.str();\n      oss.clear();\n      TextHighlight(out);\n      oss.str(out);\n    }\n  }\n\n  reply.set_retc(retc);\n  reply.set_std_out(oss.str());\n  reply.set_std_err(err.str());\n}\n\n//------------------------------------------------------------------------------\n// Execute master command\n//------------------------------------------------------------------------------\nvoid\nNsCmd::MasterSubcmd(const eos::console::NsProto_MasterProto& master,\n                    eos::console::ReplyProto& reply)\n{\n  using eos::console::NsProto_MasterProto;\n\n  if (master.op() == NsProto_MasterProto::DISABLE) {\n    reply.set_std_err(\"error: operation deprecated\");\n    reply.set_retc(ENOTSUP);\n  } else if (master.op() == NsProto_MasterProto::ENABLE) {\n    reply.set_std_err(\"error: operation deprecated\");\n    reply.set_retc(ENOTSUP);\n  } else if (master.op() == NsProto_MasterProto::LOG) {\n    std::string out;\n    gOFS->mMaster->GetLog(out);\n    reply.set_std_out(out.c_str());\n  } else if (master.op() == NsProto_MasterProto::LOG_CLEAR) {\n    gOFS->mMaster->ResetLog();\n    reply.set_std_out(\"success: cleaned the master log\");\n  } else if (master.host().length()) {\n    std::string out, err;\n\n    if (!gOFS->mMaster->SetMasterId(master.host(), 1094, err)) {\n      reply.set_std_err(err.c_str());\n      reply.set_retc(EIO);\n    } else {\n      out += \"success: current master will step down\\n\";\n      reply.set_std_out(out.c_str());\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Execute compact command\n//------------------------------------------------------------------------------\nvoid\nNsCmd::CompactSubcmd(const eos::console::NsProto_CompactProto& compact,\n                     eos::console::ReplyProto& reply)\n{\n  reply.set_std_err(\"error: operation supported by master object\");\n  reply.set_retc(ENOTSUP);\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Execute tree size recompute command\n//------------------------------------------------------------------------------\nvoid\nNsCmd::TreeSizeSubcmd(const eos::console::NsProto_TreeSizeProto& tree,\n                      eos::console::ReplyProto& reply)\n{\n  std::shared_ptr<IContainerMD> cont;\n\n  try {\n    cont = eos::Resolver::resolveContainer(gOFS->eosView, tree.container());\n  } catch (const eos::MDException& e) {\n    reply.set_std_err(SSTR(e.what()));\n    reply.set_retc(e.getErrno());\n    return;\n  }\n\n  if (cont == nullptr) {\n    reply.set_std_err(\"error: container not found\");\n    reply.set_retc(ENOENT);\n    return;\n  }\n\n  std::shared_ptr<eos::IContainerMD> tmp_cont {nullptr};\n  std::list< std::list<eos::IContainerMD::id_t> > bfs =\n    BreadthFirstSearchContainers(cont.get(), tree.depth());\n\n  for (auto it_level = bfs.crbegin(); it_level != bfs.crend(); ++it_level) {\n    for (const auto& id : *it_level) {\n      try {\n        tmp_cont = gOFS->eosDirectoryService->getContainerMD(id);\n      } catch (const eos::MDException& e) {\n        eos_err(\"error=\\\"%s\\\"\", e.what());\n        continue;\n      }\n\n      UpdateTreeSize(tmp_cont);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Execute quota size recompute command\n//------------------------------------------------------------------------------\nvoid\nNsCmd::QuotaSizeSubcmd(const eos::console::NsProto_QuotaSizeProto& tree,\n                       eos::console::ReplyProto& reply)\n{\n  std::string cont_uri {\"\"};\n  eos::IContainerMD::id_t cont_id {0ull};\n  {\n    eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n    std::shared_ptr<IContainerMD> cont {nullptr};\n\n    try {\n      cont = eos::Resolver::resolveContainer(gOFS->eosView, tree.container());\n    } catch (const eos::MDException& e) {\n      reply.set_std_err(SSTR(e.what()));\n      reply.set_retc(e.getErrno());\n      return;\n    }\n\n    if ((cont->getFlags() & eos::QUOTA_NODE_FLAG) == 0) {\n      reply.set_std_err(\"error: directory is not a quota node\");\n      reply.set_retc(EINVAL);\n      return;\n    }\n\n    cont_uri = gOFS->eosView->getUri(cont.get());\n    cont_id = cont->getId();\n  }\n  // Recompute the quota node\n  QuotaNodeCore qnc;\n  bool update = false;\n\n  if (tree.used_bytes() || tree.used_inodes()) {\n    QuotaNodeCore::UsageInfo usage;\n    usage.space = tree.used_bytes();\n    usage.physicalSpace = tree.physical_bytes();\n    usage.files = tree.used_inodes();\n\n    if (tree.uid().size() && !tree.gid().size()) {\n      // set by user\n      qnc.setByUid(strtoul(tree.uid().c_str(), 0, 10), usage);\n    } else if (tree.gid().size() && !tree.uid().size())  {\n      // set by group\n      qnc.setByGid(strtoul(tree.gid().c_str(), 0, 10), usage);\n    } else {\n      reply.set_std_err(\"error: to overwrite quota you have to set a user or group id - never both\");\n      reply.set_retc(EINVAL);\n      return;\n    }\n\n    update = true;\n  } else {\n    if (gOFS->eosView->inMemory()) {\n      reply.set_std_err(\"error: quota recomputation is only available for \"\n                        \"QDB namespace\");\n      reply.set_retc(EINVAL);\n      return;\n    }\n\n    std::unique_ptr<qclient::QClient> qcl =\n      std::make_unique<qclient::QClient>(gOFS->mQdbContactDetails.members,\n                                         gOFS->mQdbContactDetails.constructOptions());\n    eos::QuotaRecomputer recomputer(qcl.get(),\n                                    static_cast<QuarkNamespaceGroup*>(gOFS->namespaceGroup.get())->getExecutor());\n    eos::MDStatus status = recomputer.recompute(cont_uri, cont_id, qnc);\n\n    if (!status.ok()) {\n      reply.set_std_err(status.getError());\n      reply.set_retc(status.getErrno());\n      return;\n    }\n\n    // Remove all the entries, which should not be updated if any uid/gid\n    // specified.\n    if (tree.uid().size() || tree.gid().size()) {\n      qnc.filterByUid(strtoul(tree.uid().c_str(), 0, 10));\n      qnc.filterByGid(strtoul(tree.gid().c_str(), 0, 10));\n      update = true;\n    }\n  }\n\n  // Update the quota note\n  try {\n    eos::common::RWMutexWriteLock ns_wr_lock(gOFS->eosViewRWMutex);\n    auto cont = gOFS->eosDirectoryService->getContainerMD(cont_id);\n\n    if ((cont->getFlags() & eos::QUOTA_NODE_FLAG) == 0) {\n      eos_err(\"msg=\\\"quota recomputation failed, directory is not (anymore) a \"\n              \"quota node\\\" cxid=%08llx path=\\\"%s\\\"\", cont_id, cont_uri.c_str());\n      reply.set_std_err(\"error: directory is not a quota node (anymore)\");\n      reply.set_retc(EINVAL);\n      return;\n    }\n\n    eos::IQuotaNode* quotaNode = gOFS->eosView->getQuotaNode(cont.get());\n\n    if (update) {\n      quotaNode->updateCore(qnc);\n      eos_info(\"msg=\\\"quota update successful\\\" cxid=%08llx path=\\\"%s\\\"\",\n               cont_id, cont_uri.c_str());\n    } else {\n      quotaNode->replaceCore(qnc);\n      if (Quota::RefreshFromNsQuota(cont_uri)) {\n        eos_info(\"msg=\\\"quota recomputation successful\\\" cxid=%08llx path=\\\"%s\\\"\",\n                 cont_id, cont_uri.c_str());\n      } else {\n        eos_warning(\"msg=\\\"quota recomputation failed to refresh SpaceQuota\\\" \"\n                    \"cxid=%08llx path=\\\"%s\\\"\",\n                    cont_id, cont_uri.c_str());\n      }\n    }\n  } catch (const eos::MDException& e) {\n    eos_err(\"msg=\\\"quota recomputation failed, directory removed\\\" \"\n            \"cxid=%08llx path=\\\"%s\\\"\", cont_id, cont_uri.c_str());\n    reply.set_std_err(SSTR(e.what()));\n    reply.set_retc(e.getErrno());\n    return;\n  }\n\n  reply.set_retc(0);\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Recompute and update tree size of the given container assuming its\n// subcontainers tree size values are correct and adding the size of files\n// attached directly to the current container\n//------------------------------------------------------------------------------\nvoid\nNsCmd::UpdateTreeSize(eos::IContainerMDPtr cont) const\n{\n  eos_debug(\"cont name=%s, id=%llu\", cont->getName().c_str(), cont->getId());\n  std::shared_ptr<eos::IFileMD> tmp_fmd {nullptr};\n  std::shared_ptr<eos::IContainerMD> tmp_cont {nullptr};\n  uint64_t tree_size = 0u;\n  uint64_t tree_containers = 0u;\n  uint64_t tree_files = 0u;\n\n  for (auto fit = FileMapIterator(cont); fit.valid(); fit.next()) {\n    try {\n      tmp_fmd = gOFS->eosFileService->getFileMD(fit.value());\n    } catch (const eos::MDException& e) {\n      eos_err(\"error=\\\"%s\\\"\", e.what());\n      continue;\n    }\n\n    // No need to lock the file here as it's only one operation to be done\n    tree_size += tmp_fmd->getSize();\n    tree_files += 1;\n  }\n\n  for (auto cit = ContainerMapIterator(cont); cit.valid(); cit.next()) {\n    try {\n      tmp_cont = gOFS->eosDirectoryService->getContainerMD(cit.value());\n    } catch (const eos::MDException& e) {\n      eos_err(\"error=\\\"%s\\\"\", e.what());\n      continue;\n    }\n\n    // Read lock the container here\n    eos::MDLocking::ContainerReadLock readLock(tmp_cont.get());\n    tree_size += tmp_cont->getTreeSize();\n    tree_containers += tmp_cont->getTreeContainers() +\n                       1; //Count the current cont' children + the subChildren (getDirCount())\n    tree_files += tmp_cont->getTreeFiles();\n  }\n\n  eos::ContainerIdentifier id;\n  eos::ContainerIdentifier parentId;\n  {\n    eos::MDLocking::ContainerWriteLock writeLock(cont.get());\n    id = cont->getIdentifier();\n    parentId = cont->getParentIdentifier();\n    cont->setTreeSize(tree_size);\n    cont->setTreeFiles(tree_files);\n    cont->setTreeContainers(tree_containers);\n    gOFS->eosDirectoryService->updateStore(cont.get());\n  }\n  gOFS->FuseXCastRefresh(id, parentId);\n}\n\n//------------------------------------------------------------------------------\n// Execute cache update command\n//------------------------------------------------------------------------------\nvoid\nNsCmd::CacheSubcmd(const eos::console::NsProto_CacheProto& cache,\n                   eos::console::ReplyProto& reply)\n{\n  using namespace eos::constants;\n  using eos::console::NsProto_CacheProto;\n  std::map<std::string, std::string> map_cfg;\n\n  if (cache.op() == NsProto_CacheProto::SET_FILE) {\n    if (cache.max_num() > 100) {\n      map_cfg[sMaxNumCacheFiles] = std::to_string(cache.max_num());\n      map_cfg[sMaxSizeCacheFiles] = std::to_string(cache.max_size());\n      gOFS->mConfigEngine->SetConfigValue(\"ns\", \"cache-size-nfiles\",\n                                          std::to_string(cache.max_num()).c_str());\n      gOFS->eosFileService->configure(map_cfg);\n    }\n  } else if (cache.op() == NsProto_CacheProto::SET_DIR) {\n    if (cache.max_num() > 100) {\n      map_cfg[sMaxNumCacheDirs] = std::to_string(cache.max_num());\n      map_cfg[sMaxSizeCacheDirs] = std::to_string(cache.max_size());\n      gOFS->mConfigEngine->SetConfigValue(\"ns\", \"cache-size-ndirs\",\n                                          std::to_string(cache.max_num()).c_str());\n      gOFS->eosDirectoryService->configure(map_cfg);\n    }\n  } else if (cache.op() == NsProto_CacheProto::DROP_FILE) {\n    map_cfg[sMaxNumCacheFiles] = std::to_string(UINT64_MAX);\n    map_cfg[sMaxSizeCacheFiles] = std::to_string(UINT64_MAX);\n    gOFS->eosFileService->configure(map_cfg);\n  } else if (cache.op() == NsProto_CacheProto::DROP_DIR) {\n    map_cfg[sMaxNumCacheDirs] = std::to_string(UINT64_MAX);\n    map_cfg[sMaxSizeCacheDirs] = std::to_string(UINT64_MAX);\n    gOFS->eosDirectoryService->configure(map_cfg);\n  } else if (cache.op() == NsProto_CacheProto::DROP_ALL) {\n    map_cfg[sMaxNumCacheFiles] = std::to_string(UINT64_MAX);\n    map_cfg[sMaxSizeCacheFiles] = std::to_string(UINT64_MAX);\n    map_cfg[sMaxNumCacheDirs] = std::to_string(UINT64_MAX);\n    map_cfg[sMaxSizeCacheDirs] = std::to_string(UINT64_MAX);\n    gOFS->eosFileService->configure(map_cfg);\n    gOFS->eosDirectoryService->configure(map_cfg);\n  } else if (cache.op() == NsProto_CacheProto::DROP_SINGLE_FILE) {\n    bool found = gOFS->eosFileService->dropCachedFileMD(FileIdentifier(\n                   cache.single_to_drop()));\n    reply.set_retc(!found);\n  } else if (cache.op() == NsProto_CacheProto::DROP_SINGLE_CONTAINER) {\n    bool found = gOFS->eosDirectoryService->dropCachedContainerMD(\n                   ContainerIdentifier(cache.single_to_drop()));\n    reply.set_retc(!found);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Do a breadth first search of all the subcontainers under the given\n// container\n//------------------------------------------------------------------------------\nstd::list< std::list<eos::IContainerMD::id_t> >\nNsCmd::BreadthFirstSearchContainers(eos::IContainerMD* cont,\n                                    uint32_t max_depth) const\n{\n  uint32_t num_levels = 0u;\n  std::shared_ptr<eos::IContainerMD> tmp_cont;\n  std::list< std::list<eos::IContainerMD::id_t> > depth(256);\n  auto it_lvl = depth.begin();\n  it_lvl->push_back(cont->getId());\n\n  while (it_lvl->size() && (it_lvl != depth.end())) {\n    auto it_next_lvl = it_lvl;\n    ++it_next_lvl;\n\n    for (const auto& cid : *it_lvl) {\n      try {\n        tmp_cont = gOFS->eosDirectoryService->getContainerMD(cid);\n      } catch (const eos::MDException& e) {\n        // ignore error\n        eos_static_err(\"error=\\\"%s\\\"\", e.what());\n        continue;\n      }\n\n      for (auto subcont_it = ContainerMapIterator(tmp_cont); subcont_it.valid();\n           subcont_it.next()) {\n        if (it_next_lvl != depth.end()) {\n          it_next_lvl->push_back(subcont_it.value());\n        } else {\n          eos_static_notice(\"msg=\\\"reached maximum hierarchy depth\\\" \"\n                            \"cxid=%08llx\", cid);\n        }\n      }\n    }\n\n    it_lvl = it_next_lvl;\n    ++num_levels;\n\n    if (max_depth && (num_levels == max_depth)) {\n      break;\n    }\n  }\n\n  depth.resize(num_levels);\n  return depth;\n}\n\n//------------------------------------------------------------------------------\n// Update the maximum size of the thread pool used for drain jobs\n//------------------------------------------------------------------------------\nvoid\nNsCmd::DrainSubcmd(const eos::console::NsProto_DrainProto& drain,\n                   eos::console::ReplyProto& reply)\n{\n  using eos::console::NsProto_DrainProto;\n\n  if (drain.op() == NsProto_DrainProto::LIST) {\n    if (gOFS) {\n      reply.set_std_out(gOFS->mDrainEngine.SerializeConfig());\n    }\n  } else if (drain.op() == NsProto_DrainProto::SET) {\n    if (drain.key().empty() || drain.value().empty()) {\n      reply.set_std_err(\"error: both key and value need to be specified\");\n      reply.set_retc(EINVAL);\n      return;\n    } else {\n      if (gOFS) {\n        if (!gOFS->mDrainEngine.SetConfig(drain.key(), drain.value())) {\n          reply.set_std_err(\"error: failed applying drainer configuration\");\n          reply.set_retc(EINVAL);\n          return;\n        }\n      }\n    }\n  } else {\n    reply.set_std_err(\"error: unknown drainer operation\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  reply.set_retc(0);\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Execute reserve ids command\n//------------------------------------------------------------------------------\nvoid\nNsCmd::ReserveIdsSubCmd(const eos::console::NsProto_ReserveIdsProto& reserve,\n                        eos::console::ReplyProto& reply)\n{\n  if (reserve.fileid() > 0) {\n    gOFS->eosFileService->blacklistBelow(FileIdentifier(reserve.fileid()));\n  }\n\n  if (reserve.containerid() > 0) {\n    gOFS->eosDirectoryService->blacklistBelow(ContainerIdentifier(\n          reserve.containerid()));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Execute benchmark command\n//------------------------------------------------------------------------------\nvoid\nNsCmd::BenchmarkSubCmd(const eos::console::NsProto_BenchmarkProto& benchmark,\n                       eos::console::ReplyProto& reply)\n{\n  size_t n_threads = (benchmark.threads() < 1024) ? benchmark.threads() : 1024;\n  size_t n_subdirs = benchmark.subdirs();\n  size_t n_subfiles = benchmark.subfiles();\n  std::string bench_prefix = benchmark.prefix();\n  eos_static_info(\"msg=\\\"runing benchmark\\\" nthreads=%lu ndirs=%lu nfiles=%lu\",\n                  n_threads,\n                  n_subdirs,\n                  n_subfiles);\n  eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n  XrdOucErrInfo error;\n  std::string prefix = bench_prefix + \"/benchmark/\";\n  std::stringstream oss;\n  {\n    eos::common::Timing bench(\"Benchmark\");\n    COMMONTIMING(\"START\", &bench);\n    std::vector<std::thread> workers;\n    //pass 1 - create dir structure\n    gOFS->_mkdir(prefix.c_str(), 0777,  error, vid, \"\", 0, false);\n\n    for (size_t i = 0; i < n_threads; i++) {\n      workers.push_back(std::thread([i, n_subdirs, n_subfiles, &vid, prefix]() {\n        eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n        XrdOucErrInfo error;\n        std::string wdir = prefix + std::string(\"worker.\") + std::to_string(i);\n        XrdSecEntity client(\"sss\");\n        client.tident = \"benchmark\";\n        gOFS->_mkdir(wdir.c_str(), 0777, error, vid, \"\", 0, false);\n\n        for (size_t d = 0 ; d < n_subdirs ; d++) {\n          std::string sdir = wdir + std::string(\"/d.\") + std::to_string(\n                               d) + std::string(\"/\");\n          gOFS->_mkdir(sdir.c_str(), 0777,  error, vid, \"\", 0, false);\n\n          for (size_t f = 0 ; f < n_subfiles ; f++) {\n            std::string fname = sdir + std::string(\"f.\") + std::to_string(f);\n          }\n        }\n      }));\n    }\n\n    std::for_each(workers.begin(), workers.end(), [](std::thread & t) {\n      t.join();\n    });\n    COMMONTIMING(\"STOP\", &bench);\n    double rt = bench.RealTime() / 1000.0;\n    const char* l =  eos_static_log(LOG_SILENT,\n                                    \"[   mkdir     ] dirs=%lu time=%.02f dir-rate=%.02f\", n_threads * n_subdirs, rt,\n                                    n_threads * n_subdirs / rt);\n    oss << l;\n    oss << std::endl;\n    eos_static_notice(l);\n  }\n  {\n    eos::common::Timing bench(\"Benchmark\");\n    COMMONTIMING(\"START\", &bench);\n    std::vector<std::thread> workers;\n\n    //pass 2 - create files\n    for (size_t i = 0; i < n_threads; i++) {\n      workers.push_back(std::thread([i, n_subdirs, n_subfiles, &vid, prefix]() {\n        eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n        XrdOucErrInfo error;\n        std::string wdir = prefix + std::string(\"worker.\") + std::to_string(i);\n        XrdSecEntity client(\"sss\");\n        client.tident = \"benchmark\";\n\n        for (size_t d = 0 ; d < n_subdirs ; d++) {\n          std::string sdir = wdir + std::string(\"/d.\") + std::to_string(\n                               d) + std::string(\"/\");\n\n          for (size_t f = 0 ; f < n_subfiles ; f++) {\n            std::string fname = sdir + std::string(\"f.\") + std::to_string(f);\n            XrdOucEnv env;\n            XrdMgmOfsFile* file = new XrdMgmOfsFile((char*)\"bench\");\n\n            if (file) {\n              file->open(&vid, fname.c_str(), SFS_O_CREAT | SFS_O_RDWR, 0777, 0,\n                         \"eos.app=fuse&eos.bookingsize=0\");\n              delete file;\n            }\n          }\n        }\n      }));\n    }\n\n    std::for_each(workers.begin(), workers.end(), [](std::thread & t) {\n      t.join();\n    });\n    COMMONTIMING(\"STOP\", &bench);\n    double rt = bench.RealTime() / 1000.0;\n    const char* l =  eos_static_log(LOG_SILENT,\n                                    \"[   create    ] files=%lu time=%.02f file-rate=%.02f Hz\",\n                                    n_threads * n_subdirs * n_subfiles, rt,\n                                    1.0 * n_threads * n_subdirs * n_subfiles / rt);\n    oss << l;\n    oss << std::endl;\n    eos_static_notice(l);\n  }\n  {\n    eos::common::Timing bench(\"Benchmark\");\n    COMMONTIMING(\"START\", &bench);\n    std::vector<std::thread> workers;\n    //pass 3 - exists structure\n    gOFS->_mkdir(prefix.c_str(), 0777,  error, vid, \"\", 0, false);\n\n    for (size_t i = 0; i < n_threads; i++) {\n      workers.push_back(std::thread([i, n_subdirs, n_subfiles, &vid, prefix]() {\n        eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n        XrdOucErrInfo error;\n        std::string wdir = prefix + std::string(\"worker.\") + std::to_string(i);\n        XrdSecEntity client(\"sss\");\n        client.tident = \"benchmark\";\n        gOFS->_mkdir(wdir.c_str(), 0777, error, vid, \"\", 0, false);\n\n        for (size_t d = 0 ; d < n_subdirs ; d++) {\n          std::string sdir = wdir + std::string(\"/d.\") + std::to_string(\n                               d) + std::string(\"/\");\n          gOFS->_mkdir(sdir.c_str(), 0777,  error, vid, \"\", 0, false);\n\n          for (size_t f = 0 ; f < n_subfiles ; f++) {\n            std::string fname = sdir + std::string(\"f.\") + std::to_string(f);\n            XrdOucEnv env;\n            XrdMgmOfsFile* file = new XrdMgmOfsFile((char*)\"bench\");\n\n            if (file) {\n              file->open(&vid, fname.c_str(), SFS_O_CREAT | SFS_O_RDWR, 0777, 0, 0);\n              delete file;\n            }\n          }\n        }\n      }));\n    }\n\n    std::for_each(workers.begin(), workers.end(), [](std::thread & t) {\n      t.join();\n    });\n    COMMONTIMING(\"STOP\", &bench);\n    double rt = bench.RealTime() / 1000.0;\n    const char* l =  eos_static_log(LOG_SILENT,\n                                    \"[   exists    ] files=%lu dirs=%lu time=%.02f dir-rate=%.02f file-rate=%.02f Hz\",\n                                    n_threads * n_subdirs * n_subfiles, n_threads * n_subdirs, rt,\n                                    1.0 * n_threads * n_subdirs / rt,\n                                    1.0 * n_threads * n_subdirs * n_subfiles / rt);\n    oss << l;\n    oss << std::endl;\n    eos_static_notice(l);\n  }\n  {\n    eos::common::Timing bench(\"Benchmark\");\n    COMMONTIMING(\"START\", &bench);\n    std::vector<std::thread> workers;\n\n    //pass 4 - open files for reading\n    for (size_t i = 0; i < n_threads; i++) {\n      workers.push_back(std::thread([i, n_subdirs, n_subfiles, &vid, prefix]() {\n        eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n        XrdOucErrInfo error;\n        std::string wdir = prefix + std::string(\"worker.\") + std::to_string(i);\n        XrdSecEntity client(\"sss\");\n        client.tident = \"benchmark\";\n\n        for (size_t d = 0 ; d < n_subdirs ; d++) {\n          std::string sdir = wdir + std::string(\"/d.\") + std::to_string(\n                               d) + std::string(\"/\");\n\n          for (size_t f = 0 ; f < n_subfiles ; f++) {\n            std::string fname = sdir + std::string(\"f.\") + std::to_string(f);\n            XrdOucEnv env;\n            XrdMgmOfsFile* file = new XrdMgmOfsFile((char*)\"bench\");\n\n            if (file) {\n              file->open(&vid, fname.c_str(), 0, 0, 0, \"eos.app=fuse\");\n              delete file;\n            }\n          }\n        }\n      }));\n    }\n\n    std::for_each(workers.begin(), workers.end(), [](std::thread & t) {\n      t.join();\n    });\n    COMMONTIMING(\"STOP\", &bench);\n    double rt = bench.RealTime() / 1000.0;\n    const char* l =  eos_static_log(LOG_SILENT,\n                                    \"[   read      ] files=%lu time=%.02f file-rate=%.02f Hz\",\n                                    n_threads * n_subdirs * n_subfiles, rt,\n                                    1.0 * n_threads * n_subdirs * n_subfiles / rt);\n    oss << l;\n    oss << std::endl;\n    eos_static_notice(l);\n  }\n  {\n    eos::common::Timing bench(\"Benchmark\");\n    COMMONTIMING(\"START\", &bench);\n    std::vector<std::thread> workers;\n\n    //pass 5 - open files for writing\n    for (size_t i = 0; i < n_threads; i++) {\n      workers.push_back(std::thread([i, n_subdirs, n_subfiles, &vid, prefix]() {\n        eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n        XrdOucErrInfo error;\n        std::string wdir = prefix + std::string(\"worker.\") + std::to_string(i);\n        XrdSecEntity client(\"sss\");\n        client.tident = \"benchmark\";\n\n        for (size_t d = 0 ; d < n_subdirs ; d++) {\n          std::string sdir = wdir + std::string(\"/d.\") + std::to_string(\n                               d) + std::string(\"/\");\n\n          for (size_t f = 0 ; f < n_subfiles ; f++) {\n            std::string fname = sdir + std::string(\"f.\") + std::to_string(f);\n            XrdOucEnv env;\n            XrdMgmOfsFile* file = new XrdMgmOfsFile((char*)\"bench\");\n\n            if (file) {\n              file->open(&vid, fname.c_str(), SFS_O_RDWR, 0777, 0,\n                         \"eos.app=fuse&eos.bookingsize=0\");\n              delete file;\n            }\n          }\n        }\n      }));\n    }\n\n    std::for_each(workers.begin(), workers.end(), [](std::thread & t) {\n      t.join();\n    });\n    COMMONTIMING(\"STOP\", &bench);\n    double rt = bench.RealTime() / 1000.0;\n    const char* l =  eos_static_log(LOG_SILENT,\n                                    \"[   write     ] files=%lu time=%.02f file-rate=%.02f Hz\",\n                                    n_threads * n_subdirs * n_subfiles, rt,\n                                    1.0 * n_threads * n_subdirs * n_subfiles / rt);\n    oss << l;\n    oss << std::endl;\n    eos_static_notice(l);\n  }\n  {\n    eos::common::Timing bench(\"Benchmark\");\n    COMMONTIMING(\"START\", &bench);\n    std::vector<std::thread> workers;\n\n    //pass 6 - delete structure\n    for (size_t i = 0; i < n_threads; i++) {\n      workers.push_back(std::thread([i, n_subdirs, n_subfiles, &vid, prefix]() {\n        eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n        XrdOucErrInfo error;\n        std::string wdir = prefix + std::string(\"worker.\") + std::to_string(i);\n        XrdSecEntity client(\"sss\");\n        client.tident = \"benchmark\";\n\n        for (size_t d = 0 ; d < n_subdirs ; d++) {\n          std::string sdir = wdir + std::string(\"/d.\") + std::to_string(\n                               d) + std::string(\"/\");\n\n          for (size_t f = 0 ; f < n_subfiles ; f++) {\n            std::string fname = sdir + std::string(\"f.\") + std::to_string(f);\n            gOFS->_rem(fname.c_str(), error, vid, \"\");\n          }\n\n          gOFS->_remdir(sdir.c_str(), error, vid);\n        }\n\n        gOFS->_remdir(wdir.c_str(), error, vid);\n      }));\n    }\n\n    std::for_each(workers.begin(), workers.end(), [](std::thread & t) {\n      t.join();\n    });\n    gOFS->_remdir(prefix.c_str(), error, vid);\n    COMMONTIMING(\"STOP\", &bench);\n    double rt = bench.RealTime() / 1000.0;\n    const char* l =  eos_static_log(LOG_SILENT,\n                                    \"[   deletion  ] files=%lu dirs=%lu time=%.02f dir-rate=%.02f file-rate=%.02f Hz\",\n                                    n_threads * n_subdirs * n_subfiles, n_threads * n_subdirs, rt,\n                                    1.0 * n_threads * n_subdirs / rt,\n                                    1.0 * n_threads * n_subdirs * n_subfiles / rt);\n    oss << l;\n    oss << std::endl;\n    eos_static_notice(l);\n  }\n  reply.set_retc(0);\n  reply.set_std_out(oss.str().c_str());\n}\n\n\n//------------------------------------------------------------------------------\n// Execute tracker command\n//----------------------------------------------------------------------------\nvoid\nNsCmd::TrackerSubCmd(const eos::console::NsProto_TrackerProto& tracker,\n                     eos::console::ReplyProto& reply)\n{\n  using eos::console::NsProto_TrackerProto;\n\n  if (tracker.op() == NsProto_TrackerProto::NONE) {\n    reply.set_std_err(\"error: no tracker operation specified\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  const eos::mgm::TrackerType tt =\n    gOFS->mFidTracker.StringToTrackerType(tracker.name());\n  std::string output;\n\n  if (tracker.op() == NsProto_TrackerProto::LIST) {\n    output = gOFS->mFidTracker.PrintStats(true, true, tt);\n  } else if (tracker.op() == NsProto_TrackerProto::CLEAR) {\n    gOFS->mFidTracker.Clear(tt);\n    output = \"info: tracker successfully cleaned\";\n  } else {\n    reply.set_std_err(\"error: unknown operation type\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  reply.set_std_out(output);\n  reply.set_retc(0);\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Execute behaviour command\n//------------------------------------------------------------------------------\nvoid\nNsCmd::BehaviourSubCmd(const eos::console::NsProto_BehaviourProto& behaviour,\n                       eos::console::ReplyProto& reply)\n{\n  using eos::common::BehaviourConfig;\n  using eos::console::NsProto_BehaviourProto;\n\n  switch (behaviour.op()) {\n  case NsProto_BehaviourProto::LIST: {\n    auto map_behaviours = gOFS->mBehaviourCfg->List();\n    std::ostringstream oss;\n\n    for (const auto& elem : map_behaviours) {\n      oss << elem.first << \" => \" << elem.second;\n    }\n\n    reply.set_std_out(oss.str());\n    break;\n  }\n\n  case NsProto_BehaviourProto::SET: {\n    auto btype = BehaviourConfig::ConvertStringToBehaviour(behaviour.name());\n\n    if ((btype == eos::common::BehaviourType::None) ||\n        (btype == eos::common::BehaviourType::All)) {\n      reply.set_std_err(\"error: unkown behaviour type\");\n      reply.set_retc(EINVAL);\n    } else {\n      if (gOFS->mBehaviourCfg->Set(btype, behaviour.value())) {\n        reply.set_std_out(\"info: behaviour set successfully\");\n      } else {\n        reply.set_std_err(\"error: operation failed, check accepted \"\n                          \"config values\");\n        reply.set_retc(EINVAL);\n      }\n    }\n\n    break;\n  }\n\n  case NsProto_BehaviourProto::GET: {\n    auto btype = BehaviourConfig::ConvertStringToBehaviour(behaviour.name());\n\n    if ((btype == eos::common::BehaviourType::None) ||\n        (btype == eos::common::BehaviourType::All)) {\n      reply.set_std_err(\"error: unkown behaviour type\");\n      reply.set_retc(EINVAL);\n    } else {\n      if (gOFS->mBehaviourCfg->Exists(btype)) {\n        std::string val = gOFS->mBehaviourCfg->Get(btype);\n        reply.set_std_out(SSTR(\"behaviour=\\\"\" << behaviour.name() << \"\\\"\"\n                               << \" value=\\\"\" << val << \"\\\"\"));\n      } else {\n        reply.set_std_err(\"error: no such behaviour configured\");\n        reply.set_retc(EINVAL);\n      }\n    }\n\n    break;\n  }\n\n  case NsProto_BehaviourProto::CLEAR: {\n    auto btype = BehaviourConfig::ConvertStringToBehaviour(behaviour.name());\n\n    if (btype == eos::common::BehaviourType::None) {\n      reply.set_std_err(\"error: unkown behaviour type\");\n      reply.set_retc(EINVAL);\n    } else {\n      gOFS->mBehaviourCfg->Clear(btype);\n      reply.set_std_out(\"info: behaviour(s) cleared successfully\");\n    }\n\n    break;\n  }\n\n  default: {\n    reply.set_std_err(\"error: unknown behaviour subcommand\");\n    reply.set_retc(EINVAL);\n    break;\n  }\n  }\n\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Apply text highlighting to ns output\n//------------------------------------------------------------------------------\nvoid\nNsCmd::TextHighlight(std::string& text) const\n{\n  XrdOucString tmp = text.c_str();\n  // Color replacements\n  tmp.replace(\"[booted]\", \"\\033[1m[booted]\\033[0m\");\n  tmp.replace(\"[down]\", \"\\033[49;31m[down]\\033[0m\");\n  tmp.replace(\"[failed]\", \"\\033[49;31m[failed]\\033[0m\");\n  tmp.replace(\"[booting]\", \"\\033[49;32m[booting]\\033[0m\");\n  tmp.replace(\"[compacting]\", \"\\033[49;34m[compacting]\\033[0m\");\n  // Replication highlighting\n  tmp.replace(\"master-rw\", \"\\033[49;31mmaster-rw\\033[0m\");\n  tmp.replace(\"master-ro\", \"\\033[49;34mmaster-ro\\033[0m\");\n  tmp.replace(\"slave-ro\", \"\\033[1mslave-ro\\033[0m\");\n  tmp.replace(\"=ok\", \"=\\033[49;32mok\\033[0m\");\n  tmp.replace(\"=compacting\", \"=\\033[49;32mcompacting\\033[0m\");\n  tmp.replace(\"=off\", \"=\\033[49;34moff\\033[0m\");\n  tmp.replace(\"=blocked\", \"=\\033[49;34mblocked\\033[0m\");\n  tmp.replace(\"=wait\", \"=\\033[49;34mwait\\033[0m\");\n  tmp.replace(\"=starting\", \"=\\033[49;34mstarting\\033[0m\");\n  tmp.replace(\"=true\", \"=\\033[49;32mtrue\\033[0m\");\n  tmp.replace(\"=false\", \"=\\033[49;31mfalse\\033[0m\");\n  text = tmp.c_str();\n}\n\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/NsCmd.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file NsCmd.hh\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"proto/Ns.pb.h\"\n#include \"mgm/proc/ProcCommand.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include <list>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class NsCmd - class handling ns commands\n//------------------------------------------------------------------------------\nclass NsCmd: public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit NsCmd(eos::console::RequestProto&& req,\n                 eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, false)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~NsCmd() = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Execute mutex subcommand\n  //!\n  //! @param mutex mutex subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void MutexSubcmd(const eos::console::NsProto_MutexProto& mutex,\n                   eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute stat command\n  //!\n  //! @param stat stat subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void StatSubcmd(const eos::console::NsProto_StatProto& stat,\n                  eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute master command\n  //!\n  //! @param master master subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void MasterSubcmd(const eos::console::NsProto_MasterProto& master,\n                    eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute compact command\n  //!\n  //! @param compact compact subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void CompactSubcmd(const eos::console::NsProto_CompactProto& master,\n                     eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute tree size recompute\n  //!\n  //! @param tree tree size subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void TreeSizeSubcmd(const eos::console::NsProto_TreeSizeProto& tree,\n                      eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute quota size recompute\n  //!\n  //! @param quota size subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void QuotaSizeSubcmd(const eos::console::NsProto_QuotaSizeProto& tree,\n                       eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute cache update command\n  //!\n  //! @param cache cache subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void CacheSubcmd(const eos::console::NsProto_CacheProto& cache,\n                   eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Handle ns drain subcommand that can modify global drain parameters\n  //!\n  //! @param drain drain subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void DrainSubcmd(const eos::console::NsProto_DrainProto& drain,\n                   eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute reserve ids command\n  //!\n  //! @param cache cache subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void ReserveIdsSubCmd(const eos::console::NsProto_ReserveIdsProto& reserve,\n                        eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute benchmark command\n  //!\n  //! @param benchmark subcommand proto object\n  //!\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void BenchmarkSubCmd(const eos::console::NsProto_BenchmarkProto& benchmark,\n                       eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute tracker command\n  //!\n  //! @param tracker subcommand proto object\n  //!\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void TrackerSubCmd(const eos::console::NsProto_TrackerProto& tracker,\n                     eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute behaviour command\n  //!\n  //! @param behaviour subcommand proto object\n  //!\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void\n  BehaviourSubCmd(const eos::console::NsProto_BehaviourProto& behaviour,\n                  eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Do a breadth first search of all the subcontainers under the given\n  //! container\n  //!\n  //! @param cont container object\n  //! @param max_depth maximum depth scanned for updating the tree size, 0\n  //!        means no limit\n  //!\n  //! @return list containing lists of subcontainers at each depth level\n  //!         starting with level 0 in front representing the given container\n  //! @note this function assumes a write lock on eosViewRWMutex\n  //----------------------------------------------------------------------------\n  std::list< std::list<eos::IContainerMD::id_t> >\n  BreadthFirstSearchContainers(eos::IContainerMD* cont,\n                               uint32_t max_depth = 0) const;\n\n  //----------------------------------------------------------------------------\n  //! Recompute and update tree size of the given container assuming its\n  //! subcontainers tree size values are correct and adding the size of files\n  //! attached directly to the current container\n  //!\n  //! @param cont container object\n  //----------------------------------------------------------------------------\n  void UpdateTreeSize(eos::IContainerMDPtr cont) const;\n\n  //----------------------------------------------------------------------------\n  //! Apply text highlighting to ns output\n  //!\n  //! @param text to be highlighted\n  //----------------------------------------------------------------------------\n  void TextHighlight(std::string& text) const;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/Quota.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/admin/Quota.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/quota/Quota.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::AdminQuota()\n{\n  if (mSubCmd == \"rmnode\") {\n    eos_notice(\"quota rmnode\");\n\n    if (pVid->uid == 0) {\n      std::string msg = \"\";\n      std::string tag = \"mgm.quota.space\";\n      std::string path = (pOpaque->Get(tag.c_str()) ?\n                          pOpaque->Get(tag.c_str()) : \"\");\n\n      if (path.empty()) {\n        retc = EINVAL;\n        stdErr = \"error: no quota path specified\";\n        return SFS_OK;\n      }\n\n      if (Quota::RmSpaceQuota(path, msg, retc)) {\n        stdOut = msg.c_str();\n      } else {\n        stdErr = msg.c_str();\n      }\n    } else {\n      retc = EPERM;\n      stdErr = \"error: you cannot remove quota nodes without having the root role!\";\n    }\n  } else {\n    stdErr = \"error: unknown subcommand <\";\n    stdErr += mSubCmd;\n    stdErr += \">\";\n    retc = EINVAL;\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/QuotaCmd.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: QuotaCmd.cc\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"QuotaCmd.hh\"\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/acl/Acl.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/recycle/Recycle.hh\"\n#include \"common/Path.hh\"\n#include \"common/Constants.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Method implementing the specific behavior of the command executed by the\n// asynchronous thread\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nQuotaCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  eos::console::QuotaProto quota = mReqProto.quota();\n\n  switch (mReqProto.quota().subcmd_case()) {\n  case eos::console::QuotaProto::kLsuser:\n    LsuserSubcmd(quota.lsuser(), reply);\n    break;\n\n  case eos::console::QuotaProto::kLs:\n    LsSubcmd(quota.ls(), reply);\n    break;\n\n  case eos::console::QuotaProto::kSet:\n    SetSubcmd(quota.set(), reply);\n    break;\n\n  case eos::console::QuotaProto::kRm:\n    RmSubcmd(quota.rm(), reply);\n    break;\n\n  case eos::console::QuotaProto::kRmnode:\n    RmnodeSubcmd(quota.rmnode(), reply);\n    break;\n\n  default:\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: not supported\");\n  }\n\n  return reply;\n}\n\n//------------------------------------------------------------------------------\n// Execute lsuser subcommand\n//------------------------------------------------------------------------------\nvoid QuotaCmd::LsuserSubcmd(const eos::console::QuotaProto_LsuserProto& lsuser,\n                            eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out, std_err;\n  int ret_c = 0;\n  gOFS->MgmStats.Add(\"Quota\", mVid.uid, mVid.gid, 1);\n  std::string space = lsuser.space();\n  bool exists = false;\n\n  if (!space.empty()) {\n    XrdOucErrInfo mError;\n    // evt. correct the space variable to be a directory path (+/)\n    struct stat buf {};\n    eos::common::Path cPath(space.c_str());\n    std::string sspace = cPath.GetPath();\n\n    if (sspace != \"/\" && sspace.back() != '/') {\n      sspace += \"/\";\n    }\n\n    if (!gOFS->_stat(sspace.c_str(), &buf, mError, mVid,\n                     nullptr)) { // @note no.01 Where is the info in mError is going?\n      space = sspace;\n      exists = true;\n    }\n  }\n\n  eos_notice(\"msg=\\\"quota ls (user)\\\" space=%s\", space.c_str());\n\n  // Early return if routing should happen\n  if (ShouldRoute(space, reply)) {\n    return;\n  }\n\n  if (!exists && lsuser.exists()) {\n    reply.set_retc(ENOENT);\n    reply.set_std_err(\"error: the given path does not exist!\");\n    return;\n  }\n\n  if (lsuser.quotanode()) {\n    eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n    // check if this is a quotanode\n    std::string quota_node_path = Quota::GetResponsibleSpaceQuotaPath(space);\n    eos::common::Path qPath(quota_node_path.c_str());\n    eos::common::Path sPath(space.c_str());\n\n    if (std::string(qPath.GetPath()) != std::string(sPath.GetPath())) {\n      reply.set_retc(ENOENT);\n      reply.set_std_err(\"error: the given path is not a quotanode!\");\n      return;\n    }\n  }\n\n  XrdOucString out {\"\"};\n  auto monitoring = lsuser.format() || WantsJsonOutput();\n  bool is_ok = Quota::PrintOut(space, out, mVid.uid, -1, monitoring, true);\n\n  if (is_ok && out.length()) {\n    if (!monitoring) {\n      std_out << (\"\\nBy user:\" + out).c_str();\n    } else {\n      std_out << out.c_str();\n    }\n  } else {\n    if (!is_ok) {\n      std_err << out.c_str() << std::endl;\n      ret_c = EINVAL;\n    }\n  }\n\n  out = \"\";\n  is_ok = Quota::PrintOut(space, out, -1, mVid.gid, monitoring, true);\n  // mDoSort = false; @note no.02 was there in the old implementation, but looks like it is not actually needed anymore\n\n  if (is_ok && out != \"\") {\n    if (!monitoring) {\n      std_out << (\"\\nBy group:\" + out).c_str();\n    } else {\n      std_out << out.c_str();\n    }\n  } else {\n    if (!is_ok) {\n      std_err << out.c_str();\n      ret_c = EINVAL;\n    }\n  }\n\n  if (WantsJsonOutput()) {\n    std_out.str(ResponseToJsonString(std_out.str(), std_err.str(), ret_c));\n  }\n\n  reply.set_std_out(std_out.str());\n  reply.set_std_err(std_err.str());\n  reply.set_retc(ret_c);\n}\n\n//------------------------------------------------------------------------------\n// Execute ls subcommand\n//------------------------------------------------------------------------------\nvoid QuotaCmd::LsSubcmd(const eos::console::QuotaProto_LsProto& ls,\n                        eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out, std_err;\n  int ret_c = 0;\n  XrdOucErrInfo mError;\n  int errc;\n  gOFS->MgmStats.Add(\"Quota\", mVid.uid, mVid.gid, 1);\n  std::string space = ls.space();\n  auto monitoring = ls.format() || WantsJsonOutput();\n\n  if (!space.empty()) {\n    // evt. correct the space variable to be a directory path (+/)\n    struct stat buf {};\n    eos::common::Path cPath(space.c_str());\n    std::string sspace = cPath.GetPath();\n\n    if (sspace != \"/\" && sspace.back() != '/') {\n      sspace += \"/\";\n    }\n\n    if (!gOFS->_stat(sspace.c_str(), &buf, mError, mVid, nullptr)) {\n      space = sspace;\n    } else {\n      if (ls.exists()) {\n        reply.set_retc(ENOENT);\n        reply.set_std_err(\"error: the given path does not exist!\");\n        return;\n      }\n    }\n  }\n\n  if (ls.quotanode()) {\n    eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n    // check if this is a quotanode\n    std::string quota_node_path = Quota::GetResponsibleSpaceQuotaPath(space);\n    eos::common::Path qPath(quota_node_path.c_str());\n    eos::common::Path sPath(space.c_str());\n\n    if (std::string(qPath.GetPath()) != std::string(sPath.GetPath())) {\n      reply.set_retc(ENOENT);\n      reply.set_std_err(\"error: the given path is not a quotanode!\");\n      return;\n    }\n  }\n\n  eos_notice(\"msg=\\\"quota ls\\\" space=%s\", space.c_str());\n  XrdOucString out1 {\"\"};\n  XrdOucString out2 {\"\"};\n  long long int uid = (ls.uid().empty() ? -1LL :\n                       eos::common::Mapping::UserNameToUid(ls.uid(), errc));\n  long long int gid = (ls.gid().empty() ? -1LL :\n                       eos::common::Mapping::GroupNameToGid(ls.gid(), errc));\n\n  if ((uid != -1LL) && (gid != -1LL)) {\n    // Print both uid and gid info\n    if (!Quota::PrintOut(space, out1, uid, -1LL, monitoring, !ls.printid())) {\n      std_err.str(out1.c_str());\n      ret_c = EINVAL;\n    } else {\n      if (!Quota::PrintOut(space, out2, -1LL, gid, monitoring, !ls.printid())) {\n        std_err.str(out2.c_str());\n        ret_c = EINVAL;\n      } else {\n        std_out.str((out1 + out2).c_str());\n      }\n    }\n  } else {\n    // Either uid or gid is printed\n    if (Quota::PrintOut(space, out1, uid, gid, monitoring, !ls.printid())) {\n      std_out.str(out1.c_str());\n    } else {\n      std_err.str(out1.c_str());\n      ret_c = EINVAL;\n    }\n  }\n\n  if (WantsJsonOutput()) {\n    std_out.str(ResponseToJsonString(std_out.str(), std_err.str(), ret_c));\n  }\n\n  reply.set_std_out(std_out.str());\n  reply.set_std_err(std_err.str());\n  reply.set_retc(ret_c);\n}\n\n//------------------------------------------------------------------------------\n// Execute set subcommand\n//------------------------------------------------------------------------------\nvoid QuotaCmd::SetSubcmd(const eos::console::QuotaProto_SetProto& set,\n                         eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out, std_err;\n  int ret_c = 0;\n  XrdOucErrInfo mError;\n  int errc;\n  long id = 0;\n  Quota::IdT id_type;\n  gOFS->MgmStats.Add(\"Quota\", mVid.uid, mVid.gid, 1);\n  std::string space = set.space();\n\n  if (!space.empty()) {\n    // evt. correct the space variable to be a directory path (+/)\n    struct stat buf {};\n    eos::common::Path cPath(space.c_str());\n    std::string sspace = cPath.GetPath();\n\n    if (sspace != \"/\" && sspace.back() != '/') {\n      sspace += \"/\";\n    }\n\n    if (!gOFS->_stat(sspace.c_str(), &buf, mError, mVid, nullptr)) { // @note no.01\n      space = sspace;\n    }\n  }\n\n  bool canQuota;\n\n  if ((!mVid.uid) || mVid.hasUid(eos::common::ADM_UID) ||\n      mVid.hasGid(eos::common::ADM_GID)) { // @note no.03\n    // root and admin can set quota\n    canQuota = true;\n  } else {\n    // figure out if the authenticated user is a quota admin\n    eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n    eos::IContainerMD::XAttrMap attrmap;\n\n    if (space[0] != '/') {\n      // take the proc directory\n      space = gOFS->MgmProcPath.c_str();\n    } else {\n      // effectively check ACLs on the quota node directory if it can be retrieved\n      std::string quota_node_path = Quota::GetResponsibleSpaceQuotaPath(space);\n\n      if (quota_node_path.length()) {\n        space = quota_node_path;\n      }\n    }\n\n    // ACL and permission check\n    Acl acl(space.c_str(), mError, mVid, attrmap); // @note no.01\n    canQuota = acl.CanSetQuota();\n  }\n\n  if (!canQuota) {\n    reply.set_retc(EPERM);\n    reply.set_std_err(\"error: you are not a quota administrator!\");\n    return;\n  }\n\n  if (!(mVid.prot != \"sss\") && !mVid.isLocalhost()) {\n    reply.set_retc(EPERM);\n    reply.set_std_err(\"error: you cannot set quota from storage node with 'sss' authentication!\");\n    return;\n  }\n\n  eos_notice(\"quota set\");\n  std::string msg;\n  struct stat buf {};\n\n  if (space.empty()) {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: command not properly formatted\");\n    return;\n  }\n\n  if (gOFS->_stat(space.c_str(), &buf, mError, mVid, nullptr)) {\n    reply.set_retc(ENOENT);\n    reply.set_std_err(\"error: quota directory does not exist\");\n    return;\n  }\n\n  if (set.uid().length() && set.gid().length()) {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: you need specify either a uid or a gid\");\n    return;\n  }\n\n  if (set.uid().length()) {\n    id_type = Quota::IdT::kUid;\n    id = eos::common::Mapping::UserNameToUid(set.uid(), errc);\n\n    if (errc == EINVAL) {\n      reply.set_retc(EINVAL);\n      reply.set_std_err(\"error: unable to translate uid=\" + set.uid());\n      return;\n    }\n  } else if (set.gid().length()) {\n    id_type = Quota::IdT::kGid;\n    id = eos::common::Mapping::GroupNameToGid(set.gid(), errc);\n\n    if (errc == EINVAL) {\n      reply.set_retc(EINVAL);\n      reply.set_std_err(\"error: unable to translate gid=\" + set.gid());\n      return;\n    }\n  } else {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: no uid/gid specified for quota set\");\n    return;\n  }\n\n  // Deal with quota on recycle bin\n  {\n    XrdOucString spath =\n      space.c_str();\n\n    if (spath.beginswith(Recycle::gRecyclingPrefix.c_str())) {\n      if (mVid.isLocalhost()) {\n        std_err <<\n                \"warning: better use 'recycle config --size' to modify the size of the recycle bin!\\n\";\n\n        if ((id_type != Quota::IdT::kGid) ||\n            (id != Quota::gProjectId)) {\n          std_err <<\n                  \"warning: you have modified a user or group quota in the recycle bin, which is not the project quota - hopefully you know\\\n what you are doing!\\n\";\n        }\n      } else {\n        std_err <<\n                \"error: please use 'recycle config --size' to modify the size of the recycle bin!\";\n        reply.set_retc(EINVAL);\n        reply.set_std_err(std_err.str());\n        return;\n      }\n    }\n  }\n  // Deal with volume quota\n  unsigned long long size = eos::common::StringConversion::GetDataSizeFromString(\n                              set.maxbytes());\n\n  if (set.maxbytes().length() && ((errno == EINVAL) || (errno == ERANGE))) {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: the volume quota you specified is not a valid number\");\n    return;\n  } else if (set.maxbytes().length()) {\n    if (size < gOFS->getFuseBookingSize()) {\n      reply.set_retc(EINVAL);\n      std_err <<\n              \"error: the volume quota has to be offset by the minimum booking size for mounted filesystem acccess : min(quota) >> \"\n              << gOFS->getFuseBookingSize() << \" bytes\";\n      reply.set_std_err(std_err.str());\n      return ;\n    }\n\n    // Set volume quota\n    if (!Quota::SetQuotaTypeForId(space, id, id_type, Quota::Type::kVolume, size,\n                                  msg, ret_c)) {\n      std_err.str(msg);\n      return;\n    } else {\n      std_out << msg;\n    }\n  }\n\n  // Deal with inode quota\n  unsigned long long inodes = eos::common::StringConversion::GetSizeFromString(\n                                set.maxinodes());\n\n  if (set.maxinodes().length() && (errno == EINVAL)) {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: the inode quota you specified is not a valid number\");\n    return;\n  } else if (set.maxinodes().length()) {\n    // Set inode quota\n    if (!Quota::SetQuotaTypeForId(space, id, id_type, Quota::Type::kInode, inodes,\n                                  msg, ret_c)) {\n      std_err << msg;\n      return;\n    } else {\n      std_out << msg;\n    }\n  }\n\n  if ((!set.maxbytes().length()) && (!set.maxinodes().length())) {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: max. bytes or max. inodes values have to be defined\");\n    return;\n  }\n\n  reply.set_std_out(std_out.str());\n  reply.set_std_err(std_err.str());\n  reply.set_retc(ret_c);\n}\n\n//------------------------------------------------------------------------------\n// Execute rm subcommand\n//------------------------------------------------------------------------------\nvoid QuotaCmd::RmSubcmd(const eos::console::QuotaProto_RmProto& rm,\n                        eos::console::ReplyProto& reply)\n{\n  int ret_c = 0;\n  XrdOucErrInfo mError;\n  int errc;\n  long id = 0;\n  Quota::IdT id_type;// = Quota::IdT::kUid;\n  gOFS->MgmStats.Add(\"Quota\", mVid.uid, mVid.gid, 1);\n  std::string space = rm.space();\n\n  if (!space.empty()) {\n    // evt. correct the space variable to be a directory path (+/)\n    struct stat buf {};\n    eos::common::Path cPath(space.c_str());\n    std::string sspace = cPath.GetPath();\n\n    if (sspace != \"/\" && sspace.back() != '/') {\n      sspace += \"/\";\n    }\n\n    if (!gOFS->_stat(sspace.c_str(), &buf, mError, mVid, nullptr)) { // @note no.01\n      space = sspace;\n    }\n  }\n\n  bool canQuota;\n\n  if ((!mVid.uid) || mVid.hasUid(eos::common::ADM_UID) ||\n      mVid.hasGid(eos::common::ADM_GID)) { // @note no.02\n    // root and admin can set quota\n    canQuota = true;\n  } else {\n    // figure out if the authenticated user is a quota admin\n    eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n    eos::IContainerMD::XAttrMap attrmap;\n\n    if (space[0] != '/') {\n      // take the proc directory\n      space = gOFS->MgmProcPath.c_str();\n    } else {\n      // effectively check ACLs on the quota node directory if it can be retrieved\n      std::string quota_node_path = Quota::GetResponsibleSpaceQuotaPath(space);\n\n      if (quota_node_path.length()) {\n        space = quota_node_path;\n      }\n    }\n\n    // ACL and permission check\n    Acl acl(space.c_str(), mError, mVid, attrmap); // @note no.01\n    canQuota = acl.CanSetQuota();\n  }\n\n  if (!canQuota) {\n    reply.set_retc(EPERM);\n    reply.set_std_err(\"error: you are not a quota administrator!\");\n    return;\n  }\n\n  if (!(mVid.prot != \"sss\") && !mVid.isLocalhost()) {\n    reply.set_retc(EPERM);\n    reply.set_std_err(\"error: you cannot set quota from storage node with 'sss' authentication!\");\n    return;\n  }\n\n  if (space.empty()) {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: command not properly formatted\");\n    return;\n  }\n\n  if (rm.uid().length() && rm.gid().length()) {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: you need specify either a uid or a gid\");\n    return;\n  }\n\n  if (rm.uid().length()) {\n    id_type = Quota::IdT::kUid;\n    id = eos::common::Mapping::UserNameToUid(rm.uid(), errc);\n\n    if (errc == EINVAL) {\n      reply.set_std_err(\"error: unable to translate uid=\" + rm.uid());\n      reply.set_retc(EINVAL);\n      return;\n    }\n  } else if (rm.gid().length()) {\n    id_type = Quota::IdT::kGid;\n    id = eos::common::Mapping::GroupNameToGid(rm.gid(), errc);\n\n    if (errc == EINVAL) {\n      reply.set_std_err(\"error: unable to translate gid=\" + rm.gid());\n      reply.set_retc(EINVAL);\n      return;\n    }\n  } else {\n    reply.set_std_err(\"error: no uid/gid specified for quota remove\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  std::string ret_msg;\n\n  if (rm.type() == eos::console::QuotaProto::RmProto::NONE) {\n    if (Quota::RmQuotaForId(space, id, id_type, ret_msg, ret_c)) {\n      reply.set_std_out(ret_msg);  //std_out = ret_msg;\n    } else {\n      reply.set_std_err(ret_msg);  //std_err = ret_msg;\n    }\n  } else if (rm.type() == eos::console::QuotaProto::RmProto::VOLUME) {\n    if (Quota::RmQuotaTypeForId(space, id, id_type, Quota::Type::kVolume, ret_msg,\n                                ret_c)) {\n      reply.set_std_out(ret_msg);  //std_out = ret_msg;\n    } else {\n      reply.set_std_err(ret_msg);  //std_err = ret_msg;\n    }\n  } else if (rm.type() == eos::console::QuotaProto::RmProto::INODE) {\n    if (Quota::RmQuotaTypeForId(space, id, id_type, Quota::Type::kInode, ret_msg,\n                                ret_c)) {\n      reply.set_std_out(ret_msg);  //std_out = ret_msg;\n    } else {\n      reply.set_std_err(ret_msg);  //std_err = ret_msg;\n    }\n  }\n\n//  reply.set_std_out(ret_msg);\n//  reply.set_std_err(ret_msg);\n  reply.set_retc(ret_c);\n}\n\n//------------------------------------------------------------------------------\n// Execute rmnode subcommand\n//------------------------------------------------------------------------------\nvoid QuotaCmd::RmnodeSubcmd(const eos::console::QuotaProto_RmnodeProto& rmnode,\n                            eos::console::ReplyProto& reply)\n{\n  eos_notice(\"quota rmnode\");\n\n  if ((mVid.uid != 0) && (mVid.uid != 3)) {\n    reply.set_retc(EPERM);\n    reply.set_std_err(\"error: you cannot remove quota nodes without having the root or adm role!\");\n    return;\n  }\n\n  if (rmnode.space().empty()) {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: no quota path specified\");\n    return;\n  }\n\n  int ret_c;\n  std::string ret_msg;\n\n  if (Quota::RmSpaceQuota(rmnode.space(), ret_msg, ret_c)) {\n    reply.set_retc(ret_c);\n    reply.set_std_out(ret_msg);\n  } else {\n    reply.set_retc(ret_c);\n    reply.set_std_err(ret_msg);\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/QuotaCmd.hh",
    "content": "//------------------------------------------------------------------------------\n// @file: QuotaCmd.hh\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"proto/Quota.pb.h\"\n#include \"mgm/proc/ProcCommand.hh\"\n\n\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class QuotaCmd - class handling quota commands\n//------------------------------------------------------------------------------\nclass QuotaCmd: public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit QuotaCmd(eos::console::RequestProto&& req,\n                    eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, false)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~QuotaCmd() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Execute lsuser subcommand\n  //!\n  //! @param lsuser lsuser subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void LsuserSubcmd(const eos::console::QuotaProto_LsuserProto& lsuser,\n                    eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute ls subcommand\n  //!\n  //! @param ls ls subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void LsSubcmd(const eos::console::QuotaProto_LsProto& ls,\n                    eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute set subcommand\n  //!\n  //! @param set set subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void SetSubcmd(const eos::console::QuotaProto_SetProto& set,\n                    eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute rm subcommand\n  //!\n  //! @param rm rm subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void RmSubcmd(const eos::console::QuotaProto_RmProto& rm,\n                    eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute rmnode subcommand\n  //!\n  //! @param rmnode rmnode subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void RmnodeSubcmd(const eos::console::QuotaProto_RmnodeProto& rmnode,\n                    eos::console::ReplyProto& reply);\n\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/Rtlog.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/admin/Rtlog.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Rtlog()\n{\n  if (pVid->uid) {\n    retc = EPERM;\n    stdErr = \"error: you have to take role 'root' to execute this command\";\n    return SFS_OK;\n  }\n\n  mDoSort = 1;\n  // this is just to identify a new queue for reach request\n  static int bccount = 0;\n  bccount++;\n  XrdOucString queue = pOpaque->Get(\"mgm.rtlog.queue\");\n  XrdOucString lines = pOpaque->Get(\"mgm.rtlog.lines\");\n  XrdOucString tag = pOpaque->Get(\"mgm.rtlog.tag\");\n  XrdOucString filter = pOpaque->Get(\"mgm.rtlog.filter\");\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n\n  if (!filter.length()) {\n    filter = \" \";\n  }\n\n  if ((!queue.length()) || (!lines.length()) || (!tag.length())) {\n    stdErr = \"error: mgm.rtlog.queue, mgm.rtlog.lines, mgm.rtlog.tag have to be given as input paramters!\";\n    retc = EINVAL;\n    return SFS_OK;\n  }\n\n  if ((g_logging.GetPriorityByString(tag.c_str())) == -1) {\n    stdErr = \"error: mgm.rtlog.tag must be info, debug, err, emerg, alert, crit, warning or notice\";\n    retc = EINVAL;\n    return SFS_OK;\n  }\n\n  // Grab the logs from the current MGM\n  if ((queue == \".\") || (queue == \"*\") || (queue == gOFS->MgmOfsQueue)) {\n    int logtagindex = g_logging.GetPriorityByString(tag.c_str());\n\n    for (int j = 0; j <= logtagindex; j++) {\n      g_logging.gMutex.Lock();\n\n      for (int i = 1; i <= atoi(lines.c_str()); i++) {\n        XrdOucString logline = g_logging.gLogMemory[j][(g_logging.gLogCircularIndex[j] -\n                               i + g_logging.gCircularIndexSize) % g_logging.gCircularIndexSize].c_str();\n\n        if (logline.length() && ((logline.find(filter.c_str())) != STR_NPOS)) {\n          stdOut += logline;\n          stdOut += \"\\n\";\n        }\n\n        if (!logline.length()) {\n          break;\n        }\n      }\n\n      g_logging.gMutex.UnLock();\n    }\n  }\n\n  // Grab the logs from the FSTs\n  if ((queue == \"*\") || ((queue != gOFS->MgmOfsQueue) && (queue != \".\"))) {\n    std::set<std::string> endpoints = FsView::gFsView.CollectEndpoints(\n                                        queue.c_str());\n\n    if (endpoints.empty()) {\n      eos_static_err(\"msg=\\\"no matching endpoints\\\" queue=\\\"%s\\\"\", queue.c_str());\n      stdErr = \"error: not matching endpoints for given queue\";\n      retc = EINVAL;\n    } else {\n      std::ostringstream oss;\n      oss << \"/?fst.pcmd=rtlog\"\n          << \"&mgm.rtlog.lines=\" << lines\n          << \"&mgm.rtlog.tag=\" << tag;\n\n      if (filter != \" \") {\n        oss << \"&mgm.rtlog.filter=\" << filter;\n      }\n\n      std::string request = oss.str();\n      std::map<std::string, std::pair<int, std::string>> responses;\n      int query_retc = gOFS->BroadcastQuery(request, endpoints, responses, 10);\n\n      if (query_retc == 0) {\n        for (const auto& resp : responses) {\n          stdOut += resp.second.second.c_str();\n        }\n      } else {\n        eos_err(\"msg=\\\"request for rtlogs failed\\\" endpoints=\\\"%s\\\"\",\n                queue.c_str());\n        stdErr = \"error: request for rtlogs failed\";\n        retc = EFAULT;\n      }\n    }\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/SchedCmd.cc",
    "content": "// ----------------------------------------------------------------------\n// File: SchedCmd.cc\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#include \"mgm/proc/admin/SchedCmd.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/placement/PlacementStrategy.hh\"\n#include \"mgm/placement/FsScheduler.hh\"\n\nnamespace eos::mgm {\n\neos::console::ReplyProto\neos::mgm::SchedCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  eos::console::SchedProto sched = mReqProto.sched();\n  switch (sched.subcmd_case()) {\n  case eos::console::SchedProto::kConfig:\n    return ConfigureSubcmd(sched.config());\n  case eos::console::SchedProto::kLs:\n    return LsSubcmd(sched.ls());\n  default:\n    reply.set_std_err(\"error: not supported\");\n    reply.set_retc(EINVAL);\n  }\n\n  return reply;\n}\n\neos::console::ReplyProto\neos::mgm::SchedCmd::ConfigureSubcmd(const eos::console::SchedProto_ConfigureProto& config)\n{\n  eos::console::ReplyProto reply;\n  switch (config.subopt_case()) {\n  case eos::console::SchedProto_ConfigureProto::kType:\n    return SchedulerTypeSubcmd(config.type());\n  case eos::console::SchedProto_ConfigureProto::kWeight:\n    return WeightSubCmd(config.weight());\n  case eos::console::SchedProto_ConfigureProto::kShow:\n    return ShowSubCmd(config.show());\n  case eos::console::SchedProto_ConfigureProto::kRefresh:\n    return RefreshSubCmd(config.refresh());\n  default:\n    reply.set_std_err(\"error: not supported\");\n    reply.set_retc(EINVAL);\n  }\n  return reply;\n}\n\neos::console::ReplyProto\neos::mgm::SchedCmd::SchedulerTypeSubcmd(const eos::console::SchedProto_TypeProto& type)\n{\n  eos::console::ReplyProto reply;\n  std::ostringstream stdout;\n\n  gOFS->mFsScheduler->setPlacementStrategy(type.schedtype());\n  stdout << \"info: configured default scheduler type as : \"\n         << placement::strategy_to_str(gOFS->mFsScheduler->getPlacementStrategy());\n\n  reply.set_std_out(stdout.str());\n  reply.set_retc(0);\n  return reply;\n}\n\neos::console::ReplyProto\neos::mgm::SchedCmd::WeightSubCmd(const eos::console::SchedProto_WeightProto& weight)\n{\n  eos::console::ReplyProto reply;\n  std::ostringstream oss;\n\n  bool status = gOFS->mFsScheduler->setDiskWeight(weight.spacename(),\n                                                  weight.id(), weight.weight());\n  if (!status) {\n    oss << \"Failed setting disk weight for fsid=\" << weight.id();\n    reply.set_retc(EINVAL);\n    reply.set_std_err(oss.str());\n    return reply;\n  }\n\n\n  oss << \"Success, configured fsid=\"<< weight.id() << \" weight=\" << weight.weight();\n  reply.set_retc(0);\n  reply.set_std_out(oss.str());\n  return reply;\n}\n\neos::console::ReplyProto\nSchedCmd::LsSubcmd(const eos::console::SchedProto_LsProto& ls)\n{\n  eos::console::ReplyProto reply;\n  std::string status;\n  std::string type;\n  switch (ls.option()) {\n  case eos::console::SchedProto_LsProto::BUCKET:\n    type = \"bucket\";\n    break;\n  case eos::console::SchedProto_LsProto::DISK:\n    type = \"disk\";\n    break;\n  default:\n    type = \"all\";\n  }\n\n  status = gOFS->mFsScheduler->getStateStr(ls.spacename(),type);\n  reply.set_std_out(status);\n  reply.set_retc(0);\n  return reply;\n}\n\neos::console::ReplyProto\nSchedCmd::ShowSubCmd(const eos::console::SchedProto_ShowProto& show)\n{\n  eos::console::ReplyProto reply;\n  if (show.option() == eos::console::SchedProto_ShowProto::TYPE) {\n    auto strategy = gOFS->mFsScheduler->getPlacementStrategy();\n    if (!show.spacename().empty()) {\n      strategy = gOFS->mFsScheduler->getPlacementStrategy(show.spacename());\n    }\n    std::ostringstream oss;\n    oss << \"Scheduler Type:\"\n        << placement::strategy_to_str(strategy)\n        << std::endl;\n    reply.set_std_out(oss.str());\n    reply.set_retc(0);\n\n  }\n  return reply;\n}\n\neos::console::ReplyProto\nSchedCmd::RefreshSubCmd(const eos::console::SchedProto_RefreshProto& refresh)\n{\n  eos::console::ReplyProto reply;\n  gOFS->mFsScheduler->updateClusterData();\n  reply.set_std_out(\"Refreshed Cluster Data for all spaces!\");\n  reply.set_retc(0);\n  return reply;\n}\n\n} // namespace eos::mgm\n"
  },
  {
    "path": "mgm/proc/admin/SchedCmd.hh",
    "content": "// ----------------------------------------------------------------------\n// File: SchedCmd.hh\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include \"proto/Sched.pb.h\"\n#include \"mgm/proc/IProcCommand.hh\"\n\nnamespace eos::mgm {\n\nclass SchedCmd: public IProcCommand\n{\npublic:\n  explicit SchedCmd(eos::console::RequestProto&& req,\n                        eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, false)\n  {}\n\n  ~SchedCmd() override = default;\n\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\nprivate:\n  eos::console::ReplyProto\n  ConfigureSubcmd(const eos::console::SchedProto_ConfigureProto& config);\n\n  eos::console::ReplyProto\n  SchedulerTypeSubcmd(const eos::console::SchedProto_TypeProto& type);\n\n  eos::console::ReplyProto\n  WeightSubCmd(const eos::console::SchedProto_WeightProto& weight);\n\n  eos::console::ReplyProto\n  LsSubcmd(const eos::console::SchedProto_LsProto& ls);\n\n  eos::console::ReplyProto\n  ShowSubCmd(const eos::console::SchedProto_ShowProto& show);\n\n  eos::console::ReplyProto\n  RefreshSubCmd(const eos::console::SchedProto_RefreshProto& refresh);\n};\n\n\n} // namespace eos::mgm\n"
  },
  {
    "path": "mgm/proc/admin/SpaceCmd.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: SpaceCmd.cc\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"SpaceCmd.hh\"\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/tgc/Constants.hh\"\n#include \"mgm/http/rest-api/Constants.hh\"\n#include \"mgm/http/rest-api/manager/RestApiManager.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/lru/LRU.hh\"\n#include \"mgm/acl/Acl.hh\"\n#include \"common/Path.hh\"\n#include \"mgm/tracker/ReplicationTracker.hh\"\n#include \"mgm/inspector/FileInspector.hh\"\n#include \"mgm/egroup/Egroup.hh\"\n#include \"mgm/config/IConfigEngine.hh\"\n#include \"mgm/groupbalancer/GroupBalancer.hh\"\n#include \"mgm/groupdrainer/GroupDrainer.hh\"\n#include \"mgm/balancer/FsBalancer.hh\"\n#include \"mgm/placement/FsScheduler.hh\"\n#include \"namespace/interface/IFsView.hh\"\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"common/Constants.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/token/EosTok.hh\"\n\nEOSMGMNAMESPACE_BEGIN\nstatic const std::string BALANCER_KEY_PREFIX = \"balancer\";\nstatic const std::string GROUPBALANCER_KEY_PREFIX = \"groupbalancer\";\nstatic const std::string GROUPDRAINER_KEY_PREFIX = \"groupdrainer\";\n\n//------------------------------------------------------------------------------\n// Method implementing the specific behavior of the command executed by the\n// asynchronous thread\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nSpaceCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  eos::console::SpaceProto space = mReqProto.space();\n\n  switch (mReqProto.space().subcmd_case()) {\n  case eos::console::SpaceProto::kLs:\n    LsSubcmd(space.ls(), reply);\n    break;\n\n  case eos::console::SpaceProto::kSet:\n    SetSubcmd(space.set(), reply);\n    break;\n\n  case eos::console::SpaceProto::kStatus:\n    StatusSubcmd(space.status(), reply);\n    break;\n\n  case eos::console::SpaceProto::kNodeSet:\n    NodeSetSubcmd(space.nodeset(), reply);\n    break;\n\n  case eos::console::SpaceProto::kNodeGet:\n    NodeGetSubcmd(space.nodeget(), reply);\n    break;\n\n  case eos::console::SpaceProto::kReset:\n    ResetSubcmd(space.reset(), reply);\n    break;\n\n  case eos::console::SpaceProto::kDefine:\n    DefineSubcmd(space.define(), reply);\n    break;\n\n  case eos::console::SpaceProto::kConfig:\n    ConfigSubcmd(space.config(), reply);\n    break;\n\n  case eos::console::SpaceProto::kQuota:\n    QuotaSubcmd(space.quota(), reply);\n    break;\n\n  case eos::console::SpaceProto::kRm:\n    RmSubcmd(space.rm(), reply);\n    break;\n\n  case eos::console::SpaceProto::kTracker:\n    TrackerSubcmd(space.tracker(), reply);\n    break;\n\n  case eos::console::SpaceProto::kInspector:\n    InspectorSubcmd(space.inspector(), reply);\n    break;\n\n  case eos::console::SpaceProto::kGroupbalancer:\n    GroupBalancerSubCmd(space.groupbalancer(), reply);\n    break;\n\n  case eos::console::SpaceProto::kGroupdrainer:\n    GroupDrainerSubCmd(space.groupdrainer(), reply);\n    break;\n\n  default:\n    reply.set_std_err(\"error: not supported\");\n    reply.set_retc(EINVAL);\n  }\n\n  return reply;\n}\n\n//----------------------------------------------------------------------------\n// Execute ls subcommand\n//----------------------------------------------------------------------------\nvoid SpaceCmd::LsSubcmd(const eos::console::SpaceProto_LsProto& ls,\n                        eos::console::ReplyProto& reply)\n{\n  using eos::console::SpaceProto;\n  bool json_output = false;\n  std::string list_format;\n  std::string format;\n  auto format_case = ls.outformat();\n\n  if ((format_case == SpaceProto::LsProto::NONE) && WantsJsonOutput()) {\n    format_case = SpaceProto::LsProto::MONITORING;\n  }\n\n  switch (format_case) {\n  case SpaceProto::LsProto::LISTING:\n    format = FsView::GetSpaceFormat(\"l\");\n    list_format = FsView::GetFileSystemFormat(\"l\");\n    break;\n\n  case SpaceProto::LsProto::MONITORING:\n    format = FsView::GetSpaceFormat(\"m\");\n    json_output = WantsJsonOutput();\n    break;\n\n  case SpaceProto::LsProto::IO:\n    format = FsView::GetSpaceFormat(\"io\");\n    break;\n\n  case SpaceProto::LsProto::FSCK:\n    format = FsView::GetSpaceFormat(\"fsck\");\n    break;\n\n  default : // NONE\n    format = FsView::GetSpaceFormat(\"\");\n    break;\n  }\n\n  std::string std_out;\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n  FsView::gFsView.PrintSpaces(std_out, format, list_format, ls.outdepth(),\n                              ls.selection().c_str(), \"\", mReqProto.dontcolor());\n\n  if (json_output) {\n    std_out = ResponseToJsonString(std_out);\n  }\n\n  reply.set_std_out(std_out);\n  reply.set_retc(0);\n}\n\n//----------------------------------------------------------------------------\n// Execute status subcommand\n//----------------------------------------------------------------------------\nvoid SpaceCmd::StatusSubcmd(const eos::console::SpaceProto_StatusProto& status,\n                            eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out;\n  bool monitoring = status.outformat_m() || WantsJsonOutput();\n  const char* fmtstr = (monitoring) ? \"%s=%s \" : \"%-32s := %s\\n\";\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n\n  if (!FsView::gFsView.mSpaceView.count(status.mgmspace())) {\n    reply.set_std_err(\"error: cannot find space - no space with name=\" +\n                      status.mgmspace());\n    reply.set_retc(ENOENT);\n    return;\n  }\n\n  if (!monitoring) {\n    std_out <<\n            \"# ------------------------------------------------------------------------------------\\n\";\n    std_out << \"# Space Variables\\n\";\n    std_out <<\n            \"# ....................................................................................\\n\";\n  }\n\n  std::vector <std::string> keylist;\n  FsView::gFsView.mSpaceView[status.mgmspace()]->GetConfigKeys(keylist);\n  std::sort(keylist.begin(), keylist.end());\n\n  for (auto& i : keylist) {\n    char line[32678];\n\n    if (((i == \"nominalsize\") || (i == \"headroom\")) && !monitoring) {\n      XrdOucString sizestring;\n      // size printout\n      snprintf(line, sizeof(line) - 1, fmtstr, i.c_str(),\n               eos::common::StringConversion::GetReadableSizeString(\n                 sizestring,\n                 strtoull(FsView::gFsView.mSpaceView[status.mgmspace()]\n                          ->GetConfigMember(i).c_str(), nullptr, 10),\n                 \"B\"));\n    } else {\n      snprintf(line, sizeof(line) - 1, fmtstr, i.c_str(),\n               FsView::gFsView.mSpaceView[status.mgmspace()]\n               ->GetConfigMember(i).c_str());\n    }\n\n    std_out << line;\n  }\n\n  if (WantsJsonOutput()) {\n    std_out.str(ResponseToJsonString(std_out.str()));\n  }\n\n  reply.set_std_out(std_out.str());\n  reply.set_retc(0);\n}\n\n//----------------------------------------------------------------------------\n// Execute set subcommand\n//----------------------------------------------------------------------------\nvoid SpaceCmd::SetSubcmd(const eos::console::SpaceProto_SetProto& set,\n                         eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out, std_err;\n  int ret_c = 0;\n\n  if (mVid.uid != 0) {\n    reply.set_std_err(\"error: you have to take role 'root' to execute this command\");\n    reply.set_retc(EPERM);\n    return;\n  }\n\n  if (set.mgmspace().empty()) {\n    reply.set_std_err(\"error: illegal parameters\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n\n  if (!FsView::gFsView.mSpaceView.count(set.mgmspace())) {\n    reply.set_std_err(\"error: no such space - define one using 'space define' or add a filesystem under that space!\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  std::string key = \"status\";\n  std::string status = (set.state_switch()) ? \"on\" : \"off\";\n\n  // Loop over all groups within this space\n  if (FsView::gFsView.mSpaceGroupView.count(set.mgmspace())) {\n    for (auto& group : FsView::gFsView.mSpaceGroupView.at(set.mgmspace())) {\n      if (!group->SetConfigMember(key, status)) {\n        std_err << \"error: cannot set status in group <\" << group->mName << \">\\n\";\n        ret_c = EIO;\n      }\n    }\n  }\n\n  // Enable all nodes if 'on' request\n  if (set.state_switch()) {\n    for (auto& node : FsView::gFsView.mNodeView) {\n      if (!node.second->SetConfigMember(key, status)) {\n        std_err << \"error: cannot set status=on in node <\"\n                << node.second->mName << \">\\n\";\n        ret_c = EIO;\n      }\n    }\n  }\n\n  reply.set_std_out(std_out.str());\n  reply.set_std_err(std_err.str());\n  reply.set_retc(ret_c);\n}\n\n//----------------------------------------------------------------------------\n// Execute node-set subcommand\n//----------------------------------------------------------------------------\nvoid SpaceCmd::NodeSetSubcmd(const eos::console::SpaceProto_NodeSetProto&\n                             nodeset, eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out, std_err;\n  int ret_c = 0;\n  std::string val = nodeset.nodeset_value();\n\n  if (mVid.uid != 0) {\n    reply.set_std_err(\"error: you have to take role 'root' to execute this command\");\n    reply.set_retc(EPERM);\n    return;\n  }\n\n  if (nodeset.mgmspace().empty() || nodeset.nodeset_key().empty() ||\n      nodeset.nodeset_value().empty()) {\n    reply.set_std_err(\"error: illegal parameters\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  eos::common::RWMutexWriteLock lock(FsView::gFsView.ViewMutex);\n\n  if (!FsView::gFsView.mSpaceView.count(nodeset.mgmspace())) {\n    reply.set_std_err(\"error: no such space - define one using 'space define' or add a filesystem under that space!\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  {\n    // loop over all nodes\n    std::map<std::string, FsNode*>::const_iterator it;\n\n    for (it = FsView::gFsView.mNodeView.begin();\n         it != FsView::gFsView.mNodeView.end(); it++) {\n      XrdOucString file = val.c_str();\n\n      if (file.beginswith(\"file:/\")) {\n        // load the file on the MGM\n        file.erase(0, 5);\n        eos::common::Path iPath(file.c_str());\n        XrdOucString fpath = iPath.GetPath();\n\n        if (!fpath.beginswith(\"/var/eos/\")) {\n          std_err.str((\"error: cannot load requested file=\" + file +\n                       \" - only files under /var/eos/ can bo loaded\\n\").c_str());\n          ret_c = EINVAL;\n        } else {\n          std::ifstream ifs(file.c_str(), std::ios::in | std::ios::binary);\n\n          if (!ifs) {\n            std_err.str((\"error: cannot load requested file=\" + file).c_str());\n            ret_c = EINVAL;\n          } else {\n            val = std::string((std::istreambuf_iterator<char>(ifs)),\n                              std::istreambuf_iterator<char>());\n            // store the value b64 encoded\n            XrdOucString val64;\n            eos::common::SymKey::Base64Encode((char*) val.c_str(), val.length(), val64);\n            val = (\"base64:\" + val64).c_str();\n            std_out << \"success: loaded contents \\n\" + val;\n          }\n        }\n      }\n\n      if (!ret_c && !it->second->SetConfigMember(nodeset.nodeset_key(), val)) {\n        std_err << \"error: cannot set node-set for node <\" + it->first + \">\\n\";\n        ret_c = EIO;\n      }\n    }\n  }\n\n  reply.set_std_out(std_out.str());\n  reply.set_std_err(std_err.str());\n  reply.set_retc(ret_c);\n}\n\n//----------------------------------------------------------------------------\n// Execute node-get subcommand\n//----------------------------------------------------------------------------\nvoid SpaceCmd::NodeGetSubcmd(const eos::console::SpaceProto_NodeGetProto&\n                             nodeget, eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out;\n\n  if (mVid.uid != 0) {\n    reply.set_std_err(\"error: you have to take role 'root' to execute this command\");\n    reply.set_retc(EPERM);\n    return;\n  }\n\n  if (nodeget.mgmspace().empty() || nodeget.nodeget_key().empty()) {\n    reply.set_std_err(\"error: illegal parameters\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n\n  if (!FsView::gFsView.mSpaceView.count(nodeget.mgmspace())) {\n    reply.set_std_err(\"error: no such space - define one using 'space define' or add a filesystem under that space!\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  {\n    std::string val;\n    std::string new_val;\n    bool identical = true;\n    // loop over all nodes\n    std::map<std::string, FsNode*>::const_iterator it;\n\n    for (it = FsView::gFsView.mNodeView.begin();\n         it != FsView::gFsView.mNodeView.end(); it++) {\n      new_val = it->second->GetConfigMember(nodeget.nodeget_key());\n\n      if (val.length() && new_val != val) {\n        identical = false;\n      }\n\n      val = new_val;\n      std_out << \"# [ \" + (it->first).substr(0,\n                                             it->first.find(':')) + \" ]\\n\" + new_val + '\\n';\n    }\n\n    if (identical) {\n      std_out.str(\"*:=\" + val + '\\n');\n    }\n  }\n\n  reply.set_std_out(std_out.str());\n}\n\n//----------------------------------------------------------------------------\n// Execute reset subcommand\n//----------------------------------------------------------------------------\nvoid SpaceCmd::ResetSubcmd(const eos::console::SpaceProto_ResetProto& reset,\n                           eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out, std_err;\n  int ret_c = 0;\n  eos::common::RWMutexReadLock fsViewLock(FsView::gFsView.ViewMutex);\n\n  switch (reset.option()) {\n  case eos::console::SpaceProto_ResetProto::DRAIN: {\n    if (FsView::gFsView.mSpaceView.count(reset.mgmspace())) {\n      FsView::gFsView.mSpaceView[reset.mgmspace()]->ResetDraining();\n      std_out << \"info: reset draining in space '\" + reset.mgmspace() + \"'\";\n    } else {\n      std_err << \"error: illegal space name\";\n      ret_c = EINVAL;\n    }\n  }\n  break;\n\n  case eos::console::SpaceProto_ResetProto::EGROUP: {\n    gOFS->EgroupRefresh->Reset();\n    std_out << \"\\ninfo: clear cached EGroup information ...\";\n  }\n  break;\n\n  case eos::console::SpaceProto_ResetProto::NSFILESISTEMVIEW: {\n    eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n    gOFS->eosFsView->shrink();\n    std_out << \"\\ninfo: resized namespace filesystem view ...\";\n  }\n  break;\n\n  case eos::console::SpaceProto_ResetProto::NSFILEMAP: {\n    std_out << \"\\n info: ns does not support file map resizing\";\n  }\n  break;\n\n  case eos::console::SpaceProto_ResetProto::NSDIRECTORYMAP: {\n    std_out << \"\\ninfo: ns does not support directory map resizing\";\n  }\n  break;\n\n  case eos::console::SpaceProto_ResetProto::NS: {\n    eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n    gOFS->eosFsView->shrink();\n    std_out << \"\\ninfo: ns does not support map resizing\";\n  }\n  break;\n\n  case eos::console::SpaceProto_ResetProto::MAPPING: {\n    eos::common::Mapping::Reset();\n    std_out << \"\\ninfo: clear all user/group uid/gid caches ...\\n\";\n  }\n  break;\n\n  case eos::console::SpaceProto_ResetProto::SCHEDULEDRAIN: {\n    gOFS->mFidTracker.Clear(eos::mgm::TrackerType::Drain);\n    std_out.str(\"info: reset drain scheduling map in space '\" + reset.mgmspace() +\n                '\\'');\n  }\n  break;\n\n  case eos::console::SpaceProto_ResetProto::SCHEDULEBALANCE: {\n    gOFS->mFidTracker.Clear(eos::mgm::TrackerType::Balance);\n    std_out.str(\"info: reset balance scheduling map in space '\" + reset.mgmspace() +\n                '\\'');\n  }\n  break;\n\n  default: { // NONE - when NONE, do cases DRAIN and EGROUP and MAPPING\n    if (FsView::gFsView.mSpaceView.count(reset.mgmspace())) {\n      FsView::gFsView.mSpaceView[reset.mgmspace()]->ResetDraining();\n      std_out << \"info: reset draining in space '\" + reset.mgmspace() + \"'\";\n    } else {\n      std_err << \"error: illegal space name\";\n      ret_c = EINVAL;\n    }\n\n    gOFS->EgroupRefresh->Reset();\n    std_out << \"\\ninfo: clear cached EGroup information ...\";\n    eos::common::Mapping::Reset();\n    std_out << \"\\ninfo: clear all user/group uid/gid caches ...\\n\";\n  }\n  break;\n  }\n\n  reply.set_std_out(std_out.str());\n  reply.set_std_err(std_err.str());\n  reply.set_retc(ret_c);\n}\n\n//----------------------------------------------------------------------------\n// Execute define subcommand\n//----------------------------------------------------------------------------\nvoid SpaceCmd::DefineSubcmd(const eos::console::SpaceProto_DefineProto& define,\n                            eos::console::ReplyProto& reply)\n{\n  if (mVid.uid != 0) {\n    reply.set_std_err(\"error: you have to take role 'root' to execute this command\");\n    reply.set_retc(EPERM);\n    return;\n  }\n\n  if (define.mgmspace().empty()) {\n    reply.set_std_err(\"error: illegal parameters <space-name>\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  if ((define.groupsize() * define.groupmod()) > 65536) {\n    reply.set_std_err(\"error: the product of <groupsize>*<groupsize> must be a positive integer (<=65536)!\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  eos::common::RWMutexWriteLock lock(FsView::gFsView.ViewMutex);\n\n  if (!FsView::gFsView.mSpaceView.count(define.mgmspace())) {\n    reply.set_std_out(\"info: creating space '\" + define.mgmspace() + \"'\");\n\n    if (!FsView::gFsView.RegisterSpace(define.mgmspace().c_str())) {\n      reply.set_std_err(\"error: cannot register space <\" + define.mgmspace() + \">\");\n      reply.set_retc(EIO);\n      return;\n    }\n  }\n\n  // Set the new space parameters\n  auto space = FsView::gFsView.mSpaceView[define.mgmspace()];\n\n  if ((!space->SetConfigMember(\"groupsize\",\n                               std::to_string(define.groupsize()))) ||\n      (!space->SetConfigMember(\"groupmod\", std::to_string(define.groupmod())))) {\n    reply.set_std_err(\"error: cannot set space config value\");\n    reply.set_retc(EIO);\n  }\n}\n\n//----------------------------------------------------------------------------\n// Execute config subcommand\n//----------------------------------------------------------------------------\nvoid SpaceCmd::ConfigSubcmd(const eos::console::SpaceProto_ConfigProto& config,\n                            eos::console::ReplyProto& reply)\n{\n  if (mVid.uid != 0) {\n    reply.set_std_err(\"error: you have to take role 'root' to execute this command\");\n    reply.set_retc(EPERM);\n    return;\n  }\n\n  int ret_c = 0;\n  std::ostringstream std_out, std_err;\n  const std::string space_name = config.mgmspace_name();\n  std::string key = config.mgmspace_key();\n  std::string value = config.mgmspace_value();\n\n  if (space_name.empty() || key.empty() ||\n      (!config.remove() && value.empty())) {\n    reply.set_std_err(\"error: illegal parameters\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  bool applied = false;\n  FileSystem* fs = nullptr;\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n  auto it_space = FsView::gFsView.mSpaceView.find(space_name);\n\n  if ((it_space == FsView::gFsView.mSpaceView.end()) ||\n      (it_space->second == nullptr)) {\n    ret_c = EINVAL;\n    std_err.str(\"error: cannot find space <\" + space_name + \">\");\n    reply.set_std_err(std_err.str());\n    reply.set_retc(ret_c);\n    return;\n  }\n\n  FsSpace* space = it_space->second;\n\n  if (!strcmp(mgm::rest::TAPE_REST_API_SWITCH_ON_OFF, key.c_str())) {\n    applied = true;\n\n    //REST API activation\n    if ((value != \"on\") && (value != \"off\")) {\n      ret_c = EINVAL;\n      std_err.str(\"error: value has to either on or off\");\n    } else {\n      if (space_name != \"default\") {\n        ret_c = EIO;\n        std_err.str(\"error: the tape REST API can only be enabled or disabled on the default space\");\n      } else {\n        if (!space->SetConfigMember(key, value)) {\n          ret_c = EIO;\n          std_err.str(\"error: cannot set space config value\");\n        } else {\n          auto config = gOFS->mRestApiManager->getTapeRestApiConfig();\n\n          if (value == \"on\") {\n            if (!config->isActivated()) {\n              // Stage should be deactivated by default\n              if (!space->SetConfigMember(rest::TAPE_REST_API_STAGE_SWITCH_ON_OFF, \"off\")) {\n                ret_c = EIO;\n                std_err.str(\"error: cannot set space config value\");\n              } else {\n                config->setActivated(true);\n                config->setStageEnabled(false);\n                std_out << \"success: Tape REST API enabled\";\n              }\n            } else {\n              std_out << \"The tape REST API is already enabled\";\n            }\n          } else {\n            //Switch off the tape REST API\n            //Also switch off the STAGE resource\n            if (!space->SetConfigMember(\n                  rest::TAPE_REST_API_STAGE_SWITCH_ON_OFF, \"off\")) {\n              ret_c = EIO;\n              std_err.str(\"error: cannot set space config value\");\n            } else {\n              config->setActivated(false);\n              config->setStageEnabled(false);\n              std_out << \"success: Tape REST API disabled\";\n            }\n          }\n        }\n      }\n    }\n  }\n\n  if (!strcmp(mgm::rest::TAPE_REST_API_STAGE_SWITCH_ON_OFF, key.c_str())) {\n    applied = true;\n\n    //REST API activation\n    if ((value != \"on\") && (value != \"off\")) {\n      ret_c = EINVAL;\n      std_err.str(\"error: value has to either on or off\");\n    } else {\n      if (space_name != \"default\") {\n        ret_c = EIO;\n        std_err.str(\"error: the tape REST API STAGE resource can only be enabled or disabled on the default space\");\n      } else {\n        if (!space\n            ->SetConfigMember(key, value)) {\n          ret_c = EIO;\n          std_err.str(\"error: cannot set space config value\");\n        } else {\n          if (value == \"on\") {\n            gOFS->mRestApiManager->getTapeRestApiConfig()->setStageEnabled(true);\n            std_out << \"success: Tape REST API STAGE resource enabled\";\n          } else {\n            gOFS->mRestApiManager->getTapeRestApiConfig()->setStageEnabled(false);\n            std_out << \"success: Tape REST API STAGE resource disabled\";\n          }\n        }\n      }\n    }\n  }\n\n  // set a space related parameter\n  if (!key.compare(0, 6, \"space.\")) {\n    key.erase(0, 6);\n\n    if (config.remove()) {\n      if (!space->DeleteConfigMember(key)) {\n        ret_c = ENOENT;\n        std_err.str(\"error: key has not been deleted\");\n      } else {\n        std_out.str(\"success: removed space config '\" + key + \"'\\n\");\n      }\n\n      if (key.substr(0, 9) == std::string(\"attr.sys.\")) {\n        // remove attribute in gOFS map\n        std::unique_lock<std::mutex> lock(gOFS->mSpaceAttributesMutex);\n        std::string mkey = key.substr(5);\n        gOFS->mSpaceAttributes[space_name].erase(mkey);\n      }\n\n      reply.set_std_out(std_out.str());\n      reply.set_std_err(std_err.str());\n      reply.set_retc(ret_c);\n      return;\n    }\n\n    if (eos::common::startsWith(key, \"policy.\") ||\n        eos::common::startsWith(key, \"local.policy.\")) {\n      if (value == \"remove\") {\n        applied = true;\n\n        if (!space->DeleteConfigMember(key)) {\n          ret_c = ENOENT;\n          std_err.str(\"error: key has not been deleted\");\n        } else {\n          std_out.str(\"success: removed space policy '\" + key + \"'\\n\");\n        }\n      } else {\n        applied = true;\n\n        // set a space policy parameters e.g. default placement attributes\n        if (!space->SetConfigMember(key, value)) {\n          std_err.str(\"error: cannot set space config value\");\n          ret_c = EIO;\n        } else {\n          std_out.str(\"success: configured policy in space='\" + space_name +\n                      \"' as \" + key + \"='\" + value + \"'\\n\");\n          ret_c = 0;\n        }\n      }\n    } else if (key == eos::mgm::tgc::TGC_NAME_FREE_BYTES_SCRIPT) {\n      applied = true;\n\n      if (!space->SetConfigMember(key, value)) {\n        std_err.str(\"error: cannot set space config value\");\n        ret_c = EIO;\n      } else {\n        std_out.str(\"success: configured policy in space='\" + space_name +\n                    \"' as \" + key + \"='\" + value + \"'\\n\");\n        ret_c = 0;\n      }\n    } else if (key == \"groupbalancer.engine\") {\n      applied = true;\n\n      if (GroupBalancer::is_valid_engine(value)) {\n        if (!space->SetConfigMember(key, value)) {\n          std_err.str(\"error: cannot set space config value\");\n          ret_c = EIO;\n        } else {\n          std_out.str(\"success: configured groupbalancer.engine in space='\" +\n                      space_name + \"' as \" + key + \"='\" + value + \"'\\n\");\n          ret_c = 0;\n        }\n      } else {\n        std_err.str(\"error: invalid groupbalancer engine name\");\n        ret_c = EINVAL;\n      }\n    } else if (key == \"groupbalancer.blocklist\") {\n      if (!space->SetConfigMember(key, value)) {\n        std_err.str(\"error: cannot set space config value\");\n        ret_c = EIO;\n      } else {\n        space->mGroupBalancer->reconfigure();\n        applied = true;\n        std_out.str(\"success: updated \" + key + \"in space='\" +\n                    space_name + \"' as \" + value + \"'\\n\");\n        ret_c = 0;\n      }\n    } else if (key == \"scheduler.type\") {\n      if (!space->SetConfigMember(key, value)) {\n        std_err.str(\"error: cannot set space config value\");\n        ret_c = EIO;\n      } else {\n        applied = true;\n        gOFS->mFsScheduler->setPlacementStrategy(space->mName, value);\n        std_out.str(\"success: configured scheduler.type in space='\" +\n                    space_name + \"' as \" + value + \"\\n\");\n        ret_c = 0;\n      }\n    } else if (!key.compare(0, 5, \"atime\")) {\n      applied = true;\n\n      if (!space->SetConfigMember(key, value)) {\n        ret_c = EIO;\n        std_err.str(\"error: cannot set spbpace config value\");\n      } else {\n        std_out.str(\"success: defining space acces time tracking: \" + key + \"=\" +\n                    value);\n      }\n    } else {\n      if ((key == \"nominalsize\") ||\n          (key == \"headroom\") ||\n          (key == \"graceperiod\") ||\n          (key == \"drainperiod\") ||\n          (key == \"balancer\") ||\n          (key == \"balancer.threshold\") ||\n          (key == \"balancer.node.rate\") ||\n          (key == \"balancer.node.ntx\") ||\n          (key == \"balancer.max-queue-jobs\") ||\n          (key == \"balancer.max-thread-pool-size\") ||\n          (key == \"balancer.update.interval\") ||\n          (key == \"drainer.tx.minrate\") ||\n          (key == \"drainer.retries\") ||\n          (key == \"drainer.fs.ntx\") ||\n          (key == \"tracker\") ||\n          (key == \"inspector\") ||\n          (key == \"inspector.interval\") ||\n          (key == \"inspector.price.disk.tbyear\") ||\n          (key == \"inspector.price.tape.tbyear\") ||\n          (key == \"inspector.price.currency\") ||\n          (key == \"lru\") ||\n          (key == \"lru.interval\") ||\n          (key == \"wfe\") ||\n          (key == \"wfe.interval\") ||\n          (key == \"wfe.ntx\") ||\n          (key == \"groupbalancer\") ||\n          (key == \"groupbalancer.ntx\") ||\n          (key == \"groupbalancer.threshold\") ||\n          (key == \"groupbalancer.min_threshold\") ||\n          (key == \"groupbalancer.max_threshold\") ||\n          (key == \"groupbalancer.min_file_size\") ||\n          (key == \"groupbalancer.max_file_size\") ||\n          (key == \"groupbalancer.file_attempts\") ||\n          (key == \"geobalancer\") ||\n          (key == \"geobalancer.ntx\") ||\n          (key == \"geobalancer.threshold\") ||\n          (key == \"groupdrainer\") ||\n          (key == \"groupdrainer.threshold\") ||\n          (key == \"groupdrainer.group_refresh_interval\") ||\n          (key == \"groupdrainer.retry_interval\") ||\n          (key == \"groupdrainer.retry_count\") ||\n          (key == \"groupdrainer.ntx\") ||\n          (key == \"geo.access.policy.read.exact\") ||\n          (key == \"geo.access.policy.write.exact\") ||\n          (key == \"filearchivedgc\") ||\n          (key == \"max.ropen\") ||\n          (key == \"max.wopen\") ||\n          (key == \"altxs\") ||\n          (key == eos::mgm::tgc::TGC_NAME_QRY_PERIOD_SECS) ||\n          (key == eos::mgm::tgc::TGC_NAME_AVAIL_BYTES) ||\n          (key == eos::mgm::tgc::TGC_NAME_TOTAL_BYTES) ||\n          (key == \"token.generation\") ||\n          (key == eos::common::SCAN_IO_RATE_NAME) ||\n          (key == eos::common::SCAN_ENTRY_INTERVAL_NAME) ||\n          (key == eos::common::SCAN_RAIN_ENTRY_INTERVAL_NAME) ||\n          (key == eos::common::SCAN_DISK_INTERVAL_NAME) ||\n          (key == eos::common::SCAN_NS_INTERVAL_NAME) ||\n          (key == eos::common::SCAN_NS_RATE_NAME) ||\n          (key.substr(0, 9) == std::string(\"attr.sys.\"))) {\n        if ((key == \"balancer\") ||\n            (key == \"tracker\") ||\n            (key == \"inspector\") ||\n            (key == \"lru\") ||\n            (key == \"groupbalancer\") ||\n            (key == \"geobalancer\") ||\n            (key == \"geo.access.policy.read.exact\") ||\n            (key == \"geo.access.policy.write.exact\") ||\n            (key == \"filearchivedgc\") ||\n            (key == \"groupdrainer\") ||\n            (key == \"altxs\")) {\n          applied = true;\n\n          if ((value != \"on\") && (value != \"off\")) {\n            ret_c = EINVAL;\n            std_err.str(\"error: value has to either on or off\");\n          } else {\n            if (!space->SetConfigMember(key, value)) {\n              ret_c = EIO;\n              std_err.str(\"error: cannot set space config value\");\n            } else {\n              if (key == \"balancer\") {\n                if (space->mFsBalancer) {\n                  if (value == \"on\") {\n                    std_out << \"success: (fs) balancer is enabled!\";\n                  } else {\n                    std_out << \"success: (fs) balancer is disabled!\";\n                  }\n\n                  if (space->mFsBalancer) {\n                    space->mFsBalancer->SignalConfigUpdate();\n                  }\n                } else {\n                  std_err.str(\"error: (fs) balancer not initialized for space\");\n                  ret_c = EIO;\n                }\n              }\n\n              if (key == \"tracker\") {\n                if (value == \"on\") {\n                  gOFS->mReplicationTracker->enable();\n                  std_out << \"success: tracker is enabled!\";\n                } else {\n                  gOFS->mReplicationTracker->disable();\n                  std_out << \"success: tracker is disabled!\";\n                }\n              }\n\n              if (key == \"inspector\") {\n                if (space->mFileInspector) {\n                  if (value == \"on\") {\n                    space->mFileInspector->enable();\n                    std_out << \"success: file inspector is enabled!\";\n                  } else {\n                    space->mFileInspector->disable();\n                    std_out << \"success: file inspector is disabled!\";\n                  }\n                } else {\n                  std_err.str(\"error: no inspector for space\");\n                  ret_c = EINVAL;\n                }\n              }\n\n              if (key == \"groupbalancer\") {\n                if (space->mGroupBalancer) {\n                  if (value == \"on\") {\n                    std_out << \"success: groupbalancer is enabled!\";\n                  } else {\n                    std_out << \"success: groupbalancer is disabled!\";\n                  }\n\n                  space->mGroupBalancer->reconfigure();\n                } else {\n                  std_err.str(\"error: group balancer not initialized for space\");\n                  ret_c = EIO;\n                }\n              }\n\n              if (key == \"geobalancer\") {\n                if (space->mGeoBalancer) {\n                  if (value == \"on\") {\n                    std_out << \"success: geobalancer is enabled!\";\n                  } else {\n                    std_out << \"success: geobalancer is disabled!\";\n                  }\n                } else {\n                  std_err.str(\"error: geo balancer not initialized for space\");\n                  ret_c = EIO;\n                }\n              }\n\n              if (key == \"groupdrainer\") {\n                if (space->mGroupDrainer) {\n                  if (value == \"on\") {\n                    std_out << \"success: groupdrainer is enabled!\";\n                  } else {\n                    std_out << \"success: groupdrainer is disabled!\";\n                  }\n\n                  space->mGroupDrainer->reconfigure();\n                } else {\n                  std_err.str(\"error: group drainer not initialized for space\");\n                  ret_c = EIO;\n                }\n              }\n\n              if (key == \"geo.access.policy.read.exact\") {\n                if (value == \"on\") {\n                  std_out <<\n                          \"success: geo access policy prefers the exact geo matching replica for reading!\";\n                } else {\n                  std_out <<\n                          \"success: geo access policy prefers with a weight the geo matching replica for reading!\";\n                }\n              }\n\n              if (key == \"geo.access.policy.write.exact\") {\n                if (value == \"on\") {\n                  std_out <<\n                          \"success: geo access policy prefers the exact geo matching replica for placements!\";\n                } else {\n                  std_out <<\n                          \"success: geo access policy prefers with a weight the geo matching replica for placements!\";\n                }\n              }\n\n              if (key == \"scheduler.skip.overloaded\") {\n                if (value == \"on\") {\n                  std_out << \"success: scheduler skips overloaded eth-out nodes!\";\n                } else {\n                  std_out << \"success: scheduler does not skip overloaded eth-out nodes!\";\n                }\n              }\n\n              if (key == \"filearchivedgc\") {\n                if (value == \"on\") {\n                  std_out << \"success: 'file archived' garbage collector is enabled\";\n                } else {\n                  std_out << \"success: 'file archived' garbage collector is disabled\";\n                }\n              }\n\n              if (key == \"lru\") {\n                std_out << ((value == \"on\") ? \"success: LRU is enabled\" :\n                            \"success: LRU is disabled\");\n                gOFS->mLRUEngine->RefreshOptions();\n              }\n            }\n          }\n        } else if (key == \"wfe\") {\n          applied = true;\n\n          if ((value != \"on\") && (value != \"off\") && (value != \"paused\")) {\n            ret_c = EINVAL;\n            std_err.str(\"error: value has to either on, paused or off\");\n          } else {\n            if (!space->SetConfigMember(key, value)) {\n              ret_c = EIO;\n              std_err.str(\"error: cannot set space config value\");\n            } else {\n              std::string status = (value == \"on\") ? \"enabled\" :\n                                   (value == \"off\" ? \"disabled\" : \"paused\");\n              std_out << \"success: wfe is \" << status << \"!\";\n            }\n          }\n        } else if (value == \"remove\") {\n          applied = true;\n\n          if (key.substr(0, 9) == std::string(\"attr.sys.\")) {\n            // remove attribute in gOFS map\n            std::unique_lock<std::mutex> lock(gOFS->mSpaceAttributesMutex);\n            std::string mkey = key.substr(5);\n            gOFS->mSpaceAttributes[space_name].erase(mkey);\n          }\n\n          if (!space->DeleteConfigMember(key)) {\n            ret_c = ENOENT;\n            std_err.str(\"error: key has not been deleted\");\n          } else {\n            std_out.str(\"success: deleted space config : \" + key);\n          }\n        } else if (key.substr(0, 9) == std::string(\"attr.sys.\")) {\n          if (key == \"attr.sys.acl\") {\n            // screen if this is a valid ACL\n            Acl acl;\n            std::string scal = value;\n            bool replace = true;\n\n            if (value.front() == '>' ||\n                value.front() == '<' ||\n                value.front() == '|') {\n              scal.erase(0, 1);\n              replace = false;\n            }\n\n            XrdOucErrInfo error;\n\n            if (!acl.IsValid(scal, error, true, false) &&\n                !acl.IsValid(scal, error, true, true)) {\n              ret_c = EINVAL;\n              std_err.str(\"error: the ACL is not valid\");\n              reply.set_std_out(std_out.str());\n              reply.set_std_err(std_err.str());\n              reply.set_retc(ret_c);\n              return;\n            } else {\n              if (Acl::ConvertIds(scal)) {\n                ret_c = EINVAL;\n                std_err.str(\"error: cannot convert to numerical IDs\");\n                reply.set_std_out(std_out.str());\n                reply.set_std_err(std_err.str());\n                reply.set_retc(ret_c);\n                return;\n              }\n\n              if (!replace) {\n                value.erase(1);\n                value += scal;\n              } else {\n                value = scal;\n              }\n\n              std_out.str(\"success: setting \" + key + \"=\" + value);\n            }\n          }\n\n          {\n            // set attribute in gOFS map\n            std::unique_lock<std::mutex> lock(gOFS->mSpaceAttributesMutex);\n            std::string mkey = key.substr(5);\n            gOFS->mSpaceAttributes[space_name][mkey] = value;\n          }\n\n          applied = true;\n\n          // setting space attributes\n          if (!space->SetConfigMember(key, value)) {\n            ret_c = EIO;\n            std_err.str(\"error: cannot set space config value\");\n          } else {\n            std_out.str(\"success: setting \" + key + \"=\" + value);\n          }\n        } else {\n          errno = 0;\n          applied = true;\n          unsigned long long size = eos::common::StringConversion::GetSizeFromString(\n                                      value.c_str());\n\n          if (!errno) {\n            if ((key != \"balancer.threshold\") &&\n                (key != \"geobalancer.threshold\") &&\n                (key != \"groupbalancer.threshold\") &&\n                (key != \"groupbalancer.min_threshold\") &&\n                (key != \"groupbalancer.max_threshold\") &&\n                (key != \"groupdrainer.threshold\")) {\n              // Threshold is allowed to be decimal!\n              char ssize[1024];\n              snprintf(ssize, sizeof(ssize) - 1, \"%llu\", size);\n              value = ssize;\n            }\n\n            if (!space->SetConfigMember(key, value)) {\n              ret_c = EIO;\n              std_err.str(\"error: cannot set space config value\");\n            } else {\n              std_out.str(\"success: setting \" + key + \"=\" + value);\n\n              if ((key == \"token.generation\")) {\n                eos::common::EosTok::sTokenGeneration = strtoull(value.c_str(), 0, 0);\n              }\n\n              if (key == \"lru.interval\") {\n                gOFS->mLRUEngine->RefreshOptions();\n              }\n\n              if (eos::common::startsWith(key, GROUPBALANCER_KEY_PREFIX)) {\n                space->mGroupBalancer->reconfigure();\n              } else if (eos::common::startsWith(key, GROUPDRAINER_KEY_PREFIX)) {\n                space->mGroupDrainer->reconfigure();\n              } else if (eos::common::startsWith(key, BALANCER_KEY_PREFIX)) {\n                if (space->mFsBalancer) {\n                  space->mFsBalancer->SignalConfigUpdate();\n                }\n              }\n            }\n          } else {\n            ret_c = EINVAL;\n            std_err.str(\"error: value has to be a positive number\");\n          }\n        }\n      }\n    }\n  }\n\n  // Set a filesystem related parameter\n  if (!key.compare(0, 3, \"fs.\")) {\n    applied = true;\n    key.erase(0, 3);\n    // we disable the autosave, do all the updates and then switch back\n    // to autosave and evt. save all changes\n    gOFS->mConfigEngine->SetAutoSave(false);\n\n    // Store these as a global parameters of the space\n    if ((key == \"headroom\") || (key == \"graceperiod\") || (key == \"drainperiod\") ||\n        (key == \"max.ropen\") || (key == \"max.wopen\") ||\n        (key == eos::common::SCAN_IO_RATE_NAME) ||\n        (key == eos::common::SCAN_ENTRY_INTERVAL_NAME) ||\n        (key == eos::common::SCAN_RAIN_ENTRY_INTERVAL_NAME) ||\n        (key == eos::common::SCAN_DISK_INTERVAL_NAME) ||\n        (key == eos::common::SCAN_NS_INTERVAL_NAME) ||\n        (key == eos::common::SCAN_NS_RATE_NAME) ||\n        (key == eos::common::ALTXS_SYNC) ||\n        (key == eos::common::ALTXS_SYNC_INTERVAL) ||\n        (key == eos::common::ALTXS_SYNC_RATE)) {\n      unsigned long long size = eos::common::StringConversion::GetSizeFromString(\n                                  value.c_str());\n      char ssize[1024];\n      snprintf(ssize, sizeof(ssize) - 1, \"%llu\", size);\n\n      if (value == \"remove\") {\n        if (!space->DeleteConfigMember(key)) {\n          ret_c = ENOENT;\n        } else {\n          std_out.str(\"success: deleting \" + key);\n        }\n      } else {\n        if ((!space->SetConfigMember(key, ssize))) {\n          std_err << \"error: failed to set space parameter <\" + key + \">\\n\";\n          ret_c = EINVAL;\n        } else {\n          std_out.str(\"success: setting \" + key + \"=\" + value);\n        }\n      }\n    } else {\n      if (key != \"configstatus\") {\n        std_err << \"error: not an allowed parameter <\" + key + \">\\n\";\n        ret_c = EINVAL;\n      }\n    }\n\n    for (auto it = space->begin(); it != space->end(); ++it) {\n      fs = FsView::gFsView.mIdView.lookupByID(*it);\n\n      if (fs) {\n        // check the allowed strings\n        if (((key == \"configstatus\") &&\n             (eos::common::FileSystem::GetConfigStatusFromString(value.c_str()) !=\n              eos::common::ConfigStatus::kUnknown))) {\n          fs->SetString(key.c_str(), value.c_str());\n          FsView::gFsView.StoreFsConfig(fs, false);\n        } else {\n          errno = 0;\n          eos::common::StringConversion::GetSizeFromString(value.c_str());\n\n          if (((key == \"headroom\") || (key == \"graceperiod\") || (key == \"drainperiod\") ||\n               (key == \"max.ropen\") || (key == \"max.wopen\") ||\n               (key == eos::common::SCAN_IO_RATE_NAME) ||\n               (key == eos::common::SCAN_ENTRY_INTERVAL_NAME) ||\n               (key == eos::common::SCAN_RAIN_ENTRY_INTERVAL_NAME) ||\n               (key == eos::common::SCAN_DISK_INTERVAL_NAME) ||\n               (key == eos::common::SCAN_NS_INTERVAL_NAME) ||\n               (key == eos::common::SCAN_NS_RATE_NAME) ||\n               (key == eos::common::ALTXS_SYNC) ||\n               (key == eos::common::ALTXS_SYNC_INTERVAL) ||\n               (key == eos::common::ALTXS_SYNC_RATE)) && (!errno)) {\n            if (value == \"remove\") {\n              fs->RemoveKey(key.c_str());\n            } else {\n              fs->SetLongLong(key.c_str(),\n                              eos::common::StringConversion::GetSizeFromString(value.c_str()));\n            }\n\n            FsView::gFsView.StoreFsConfig(fs, false);\n          } else {\n            std_err << \"error: not an allowed parameter <\" + key + \">\\n\";\n            ret_c = EINVAL;\n            break;\n          }\n        }\n      } else {\n        std_err << \"error: cannot identify the filesystem by <\" + space_name\n                + \">\\n\";\n        ret_c = EINVAL;\n      }\n    }\n\n    gOFS->mConfigEngine->SetAutoSave(true);\n    gOFS->mConfigEngine->AutoSave();\n  }\n\n  if (!applied) {\n    ret_c = EINVAL;\n    std_err.str(\"error: unknown parameter <\" + key +\n                \"> - probably need to prefix with 'space.' or 'fs.'\\n\");\n  }\n\n  reply.set_std_out(std_out.str());\n  reply.set_std_err(std_err.str());\n  reply.set_retc(ret_c);\n}\n\n//----------------------------------------------------------------------------\n// Execute quota subcommand\n//----------------------------------------------------------------------------\nvoid SpaceCmd::QuotaSubcmd(const eos::console::SpaceProto_QuotaProto& quota,\n                           eos::console::ReplyProto& reply)\n{\n  std::string key = \"quota\";\n  std::string onoff = (quota.quota_switch()) ? \"on\" : \"off\" ;\n\n  if (mVid.uid != 0) {\n    reply.set_std_err(\"error: you have to take role 'root' to execute this command\");\n    reply.set_retc(EPERM);\n    return;\n  }\n\n  if (quota.mgmspace().empty()) {\n    reply.set_std_err(\"error: illegal parameters\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n\n  if (FsView::gFsView.mSpaceView.count(quota.mgmspace())) {\n    if (!FsView::gFsView.mSpaceView[quota.mgmspace()]->SetConfigMember(key,\n        onoff)) {\n      reply.set_std_err(\"error: cannot set space config value\");\n      reply.set_retc(EIO);\n    }\n  } else {\n    reply.set_std_err(\"error: no such space defined\");\n    reply.set_retc(EINVAL);\n  }\n}\n\n//----------------------------------------------------------------------------\n// Execute rm subcommand\n//----------------------------------------------------------------------------\nvoid SpaceCmd::RmSubcmd(const eos::console::SpaceProto_RmProto& rm,\n                        eos::console::ReplyProto& reply)\n{\n  if (mVid.uid != 0) {\n    reply.set_std_err(\"error: you have to take role 'root' to execute this command\");\n    reply.set_retc(EPERM);\n    return;\n  }\n\n  if (rm.mgmspace().empty()) {\n    reply.set_std_err(\"error: illegal parameters\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  eos::common::RWMutexWriteLock lock(FsView::gFsView.ViewMutex);\n\n  if (!FsView::gFsView.mSpaceView.count(rm.mgmspace())) {\n    reply.set_std_err(\"error: no such space '\" + rm.mgmspace() + \"'\");\n    reply.set_retc(ENOENT);\n    return;\n  }\n\n  for (auto it = FsView::gFsView.mSpaceView[rm.mgmspace()]->begin();\n       it != FsView::gFsView.mSpaceView[rm.mgmspace()]->end(); it++) {\n    FileSystem* fs = FsView::gFsView.mIdView.lookupByID(*it);\n\n    if (fs) {\n      // check that filesystems are empty\n      if ((fs->GetConfigStatus(false) != eos::common::ConfigStatus::kEmpty)) {\n        reply.set_std_err(\"error: unable to remove space '\" + rm.mgmspace() +\n                          \"' - filesystems are not all in empty state - try to drain them or: space config <name> configstatus=empty\\n\");\n        reply.set_retc(EBUSY);\n        return;\n      }\n    }\n  }\n\n  common::SharedHashLocator spaceLocator =\n    common::SharedHashLocator::makeForSpace(rm.mgmspace());\n\n  if (!mq::SharedHashWrapper::deleteHash(gOFS->mMessagingRealm.get(),\n                                         spaceLocator)) {\n    reply.set_std_err(\"error: unable to remove config of space '\" + rm.mgmspace() +\n                      \"'\");\n    reply.set_retc(EIO);\n  } else {\n    if (FsView::gFsView.UnRegisterSpace(rm.mgmspace().c_str())) {\n      reply.set_std_out(\"success: removed space '\" + rm.mgmspace() + \"'\");\n    } else {\n      reply.set_std_err(\"error: unable to unregister space '\" + rm.mgmspace() + \"'\");\n    }\n  }\n}\n\n//----------------------------------------------------------------------------\n// Execute tracker subcommand\n//----------------------------------------------------------------------------\nvoid SpaceCmd::TrackerSubcmd(const eos::console::SpaceProto_TrackerProto&\n                             tracker, eos::console::ReplyProto& reply)\n{\n  std::ostringstream std_out;\n  std::string tmp;\n  gOFS->mReplicationTracker->Scan(2 * 86400, false, &tmp);\n  std_out <<\n          \"# ------------------------------------------------------------------------------------\\n\";\n  std_out << tmp;\n  std_out <<\n          \"# ------------------------------------------------------------------------------------\\n\";\n  reply.set_std_out(std_out.str());\n  reply.set_retc(0);\n}\n\n//----------------------------------------------------------------------------\n// Execute inspector subcommand\n//----------------------------------------------------------------------------\nvoid SpaceCmd::InspectorSubcmd(const eos::console::SpaceProto_InspectorProto&\n                               inspector, eos::console::ReplyProto& reply)\n{\n  std::string_view options = inspector.options();\n  std::string std_out;\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n  auto space_it = FsView::gFsView.mSpaceView.find(inspector.mgmspace());\n\n  if (space_it != FsView::gFsView.mSpaceView.end()) {\n    space_it->second->mFileInspector->Dump(std_out, options,\n                                           FileInspector::LockFsView::Off);\n    reply.set_std_out(std_out);\n    reply.set_retc(0);\n  } else {\n    reply.set_std_err(\"error: no such space\");\n    reply.set_retc(EINVAL);\n  }\n}\n\n//----------------------------------------------------------------------------\n// Execute group balancer subcommand\n//----------------------------------------------------------------------------\nvoid\nSpaceCmd::GroupBalancerSubCmd(const eos::console::SpaceProto_GroupBalancerProto&\n                              groupbalancer,\n                              eos::console::ReplyProto& reply)\n{\n  if (groupbalancer.mgmspace().empty()) {\n    reply.set_std_err(\"error: A spacename is needed for this cmd\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n  auto space_it = FsView::gFsView.mSpaceView.find(groupbalancer.mgmspace());\n\n  if (space_it == FsView::gFsView.mSpaceView.end()) {\n    reply.set_std_err(\"error: No such space exists!\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  const auto fs_space = space_it->second;\n\n  switch (groupbalancer.cmd_case()) {\n  case eos::console::SpaceProto_GroupBalancerProto::kStatus:\n    GroupBalancerStatusCmd(groupbalancer.status(), reply, fs_space);\n    break;\n\n  default:\n    reply.set_std_err(\"error: not supported\");\n    reply.set_retc(EINVAL);\n  }\n}\n\n//----------------------------------------------------------------------------\n// Execute group balancer status subcommand\n//----------------------------------------------------------------------------\nvoid SpaceCmd::GroupBalancerStatusCmd(const\n                                      eos::console::SpaceProto_GroupBalancerStatusProto& status,\n                                      eos::console::ReplyProto& reply,\n                                      FsSpace* const fs_space)\n{\n  if (fs_space == nullptr || fs_space->mGroupBalancer == nullptr) {\n    reply.set_std_err(\"Invalid space/GroupBalancer config\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  bool monitoring = status.options().find('m') != std::string::npos;\n  bool detail = status.options().find('d') != std::string::npos;\n  reply.set_std_out(fs_space->mGroupBalancer->Status(detail, monitoring));\n  reply.set_retc(0);\n}\n\n//----------------------------------------------------------------------------\n// Execute group drainer status subcommand\n//----------------------------------------------------------------------------\nvoid\nSpaceCmd::GroupDrainerSubCmd(const eos::console::SpaceProto_GroupDrainerProto&\n                             groupdrainer,\n                             console::ReplyProto& reply)\n{\n  if (groupdrainer.mgmspace().empty()) {\n    reply.set_std_err(\"error: A spacename is needed for this cmd\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n  auto space_it = FsView::gFsView.mSpaceView.find(groupdrainer.mgmspace());\n\n  if (space_it == FsView::gFsView.mSpaceView.end()) {\n    reply.set_std_err(\"error: No such space exists!\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  const auto fs_space = space_it->second;\n\n  if (!fs_space->mGroupDrainer) {\n    reply.set_std_out(\"GroupDrainer not enabled or is configuring!\");\n    reply.set_retc(EIO);\n    return;\n  }\n\n  switch (groupdrainer.cmd_case()) {\n  case eos::console::SpaceProto_GroupDrainerProto::kStatus:\n    switch (groupdrainer.status().outformat()) {\n    case eos::console::SpaceProto::GroupDrainerStatusProto::MONITORING:\n      reply.set_std_out(fs_space->mGroupDrainer->getStatus(\n                          GroupDrainer::StatusFormat::MONITORING));\n      break;\n\n    case eos::console::SpaceProto::GroupDrainerStatusProto::DETAIL:\n      reply.set_std_out(fs_space->mGroupDrainer->getStatus(\n                          GroupDrainer::StatusFormat::DETAIL));\n      break;\n\n    default:\n      reply.set_std_out(fs_space->mGroupDrainer->getStatus(\n                          GroupDrainer::StatusFormat::NONE));\n    }\n\n    break;\n\n  case eos::console::SpaceProto_GroupDrainerProto::kReset:\n    switch (groupdrainer.reset().option()) {\n    case eos::console::SpaceProto::GroupDrainerResetProto::FAILED:\n      fs_space->mGroupDrainer->resetFailedTransfers();\n      reply.set_std_out(\"Done resetting all failed transfers!\");\n      break;\n\n    case eos::console::SpaceProto::GroupDrainerResetProto::ALL:\n      fs_space->mGroupDrainer->resetCaches();\n      reply.set_std_out(\"Done clearing all GroupDrainer caches!\");\n      break;\n\n    default:\n      reply.set_std_out(\"Unknown option!\");\n      reply.set_retc(EINVAL);\n      return;\n    }\n\n    break;\n\n  default:\n    reply.set_std_err(\"Unknown option!\");\n    reply.set_retc(EINVAL);\n    return;\n  }\n\n  reply.set_retc(0);\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/SpaceCmd.hh",
    "content": "//------------------------------------------------------------------------------\n// @file: SpaceCmd.hh\n// @author: Fabio Luchetti - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"proto/Space.pb.h\"\n#include \"mgm/proc/ProcCommand.hh\"\n\nEOSMGMNAMESPACE_BEGIN\nclass FsSpace;\n//------------------------------------------------------------------------------\n//! Class SpaceCmd - class handling space commands\n//------------------------------------------------------------------------------\nclass SpaceCmd: public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit SpaceCmd(eos::console::RequestProto&& req,\n                    eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, false)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~SpaceCmd() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Execute ls subcommand\n  //!\n  //! @param ls ls subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void LsSubcmd(const eos::console::SpaceProto_LsProto& ls,\n                eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute tracker subcommand\n  //!\n  //! @param tracker tracker subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  static void TrackerSubcmd(const eos::console::SpaceProto_TrackerProto& tracker,\n                            eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute status subcommand\n  //!\n  //! @param status status subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void StatusSubcmd(const eos::console::SpaceProto_StatusProto& status,\n                    eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute set subcommand\n  //!\n  //! @param set set subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void SetSubcmd(const eos::console::SpaceProto_SetProto& set,\n                 eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute node-set subcommand\n  //!\n  //! @param nodeset nodeset subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void NodeSetSubcmd(const eos::console::SpaceProto_NodeSetProto& nodeset,\n                     eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute node-get subcommand\n  //!\n  //! @param nodeget nodeget subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void NodeGetSubcmd(const eos::console::SpaceProto_NodeGetProto& nodeget,\n                     eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute reset subcommand\n  //!\n  //! @param reset reset subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  static void ResetSubcmd(const eos::console::SpaceProto_ResetProto& reset,\n                          eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute define subcommand\n  //!\n  //! @param define define subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void DefineSubcmd(const eos::console::SpaceProto_DefineProto& define,\n                    eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute config subcommand\n  //!\n  //! @param config config subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void ConfigSubcmd(const eos::console::SpaceProto_ConfigProto& config,\n                    eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute quota subcommand\n  //!\n  //! @param quota quota subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void QuotaSubcmd(const eos::console::SpaceProto_QuotaProto& quota,\n                   eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute rm subcommand\n  //!\n  //! @param rm rm subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void RmSubcmd(const eos::console::SpaceProto_RmProto& rm,\n                eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute inspector subcommand\n  //!\n  //! @param inspector inspector subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  static void InspectorSubcmd(const eos::console::SpaceProto_InspectorProto&\n                              inspector,\n                              eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Execute groupbalancer subcommand\n  //!\n  //! @param groupbalaner groupbalaner subcommand proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void GroupBalancerSubCmd(const eos::console::SpaceProto_GroupBalancerProto&\n                           groupbalancer,\n                           eos::console::ReplyProto& reply);\n\n\n  void GroupBalancerStatusCmd(const\n                              eos::console::SpaceProto_GroupBalancerStatusProto& status,\n                              eos::console::ReplyProto& reply,\n                              FsSpace* const fs_space);\n\n  void GroupDrainerSubCmd(const eos::console::SpaceProto_GroupDrainerProto&\n                          groupdrainer,\n                          eos::console::ReplyProto& reply);\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/admin/Vid.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/admin/Vid.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/vid/Vid.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Vid()\n{\n  if (mSubCmd == \"ls\") {\n    eos_notice(\"vid ls\");\n    Vid::Ls(*pOpaque, retc, stdOut, stdErr);\n    mDoSort = true;\n  }\n\n  if ((mSubCmd == \"set\") || (mSubCmd == \"rm\")) {\n    if (pVid->uid == 0) {\n      if (mSubCmd == \"set\") {\n        eos_notice(\"vid set\");\n        Vid::Set(*pOpaque, retc, stdOut, stdErr);\n      }\n\n      if (mSubCmd == \"rm\") {\n        eos_notice(\"vid rm\");\n        Vid::Rm(*pOpaque, retc, stdOut, stdErr);\n      }\n    } else {\n      retc = EPERM;\n      stdErr = \"error: you have to take role 'root' to execute this command\";\n    }\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/proc_fs.cc",
    "content": "//------------------------------------------------------------------------------\n// File: proc_fs.cc\n// Author: Andreas-Joachim Peters - CERN & Ivan Arizanovic - Comtrade\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/proc/proc_fs.hh\"\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/imaster/IMaster.hh\"\n#include \"namespace/interface/IFsView.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/Path.hh\"\n#include \"common/Constants.hh\"\n#include \"common/Utils.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Get type of entity used in a fs mv operation.\n//------------------------------------------------------------------------------\nEntityType get_entity_type(const std::string& input, XrdOucString& stdOut,\n                           XrdOucString& stdErr)\n{\n  std::ostringstream oss;\n  EntityType ret = EntityType::UNKNOWN;\n  // check for nodes\n  size_t ppos = input.find(\":\");\n\n  if (ppos != std::string::npos) {\n    // this is a node with port\n    ret = EntityType::NODE;\n    return ret;\n  }\n\n  // check for fs,group,space\n  size_t pos = input.find('.');\n\n  if (pos == std::string::npos) {\n    if (input.find_first_not_of(\"0123456789\") == std::string::npos) {\n      // This is an fs id\n      errno = 0;\n      (void) strtol(input.c_str(), nullptr, 10);\n\n      if (errno) {\n        eos_static_err(\"input fsid: %s must be a numeric value\", input.c_str());\n        oss << \"fsid: \" << input << \" must be a numeric value\";\n        stdErr = oss.str().c_str();\n      } else {\n        ret = EntityType::FS;\n      }\n    } else {\n      // This is a space\n      ret = EntityType::SPACE;\n    }\n  } else {\n    // This is group definition, make sure the space and group tokens are correct\n    std::string space = input.substr(0, pos);\n    std::string group = input.substr(pos + 1);\n\n    if (space.find_first_not_of(\"0123456789\") == std::string::npos) {\n      eos_static_err(\"input space.group: %s must contain a string value for space\",\n                     input.c_str());\n      oss << \"space.group: \" << input << \" must contain a string value for space\";\n      stdErr = oss.str().c_str();\n    } else {\n      // Group must be a numeric value\n      if (group.find_first_not_of(\"0123456789\") != std::string::npos) {\n        eos_static_err(\"input space.group: %s must contain a numeric value for group\",\n                       input.c_str());\n        oss << \"space.group: \" << input << \" must contain a numeric value for group\";\n        stdErr = oss.str().c_str();\n      } else {\n        ret = EntityType::GROUP;\n      }\n    }\n  }\n\n  return ret;\n}\n\n//------------------------------------------------------------------------------\n// Get operation type based on the input entity types\n//------------------------------------------------------------------------------\nMvOpType get_operation_type(const std::string& in1, const std::string& in2,\n                            XrdOucString& stdOut, XrdOucString& stdErr)\n{\n  // Do them individually to get proper error messages\n  EntityType in1_type = get_entity_type(in1, stdOut, stdErr);\n\n  if (in1_type == EntityType::UNKNOWN) {\n    return MvOpType::UNKNOWN;\n  }\n\n  EntityType in2_type = get_entity_type(in2, stdOut, stdErr);\n\n  if (in2_type == EntityType::UNKNOWN) {\n    return MvOpType::UNKNOWN;\n  }\n\n  if (((in1_type == EntityType::FS) && (in2_type == EntityType::SPACE)) ||\n      ((in1_type == EntityType::FS) && (in2_type == EntityType::GROUP)) ||\n      ((in1_type == EntityType::GROUP) && (in2_type == EntityType::SPACE)) ||\n      ((in1_type == EntityType::SPACE) && (in2_type == EntityType::SPACE)) ||\n      ((in1_type == EntityType::FS) && (in2_type == EntityType::NODE))) {\n    return static_cast<MvOpType>((in1_type << 2) | in2_type);\n  }\n\n  return MvOpType::UNKNOWN;\n}\n\n//------------------------------------------------------------------------------\n// Check for hostname matching with SSS protocol\n//------------------------------------------------------------------------------\nstatic int\ncheck_sss_hostname_match(const eos::common::VirtualIdentity& vid,\n                         const std::string& target_host, XrdOucString& stdErr)\n{\n  const std::string vid_hostname = vid.host;\n  const std::string vid_prot = vid.prot.c_str();\n\n  // If EOS_SKIP_SSS_HOSTNAME_MATCH env variable is set (e.g. possibly to avoid\n  // complications in Kubernetes environments), then skip the hostname check.\n  bool skip_hostname_match = false;\n  if (getenv(\"EOS_SKIP_SSS_HOSTNAME_MATCH\")) {\n    skip_hostname_match = true;\n  }\n\n  // Rough check that the filesystem is added from a host with the same\n  // hostname ... anyway we should have configured 'sss' security\n  if ((vid.uid == 0) || (vid_prot == \"sss\")) {\n    if ((vid_prot == \"sss\") && (vid.uid != 0)) {\n      if (!skip_hostname_match &&\n          vid_hostname.compare(0, target_host.length(), target_host, 0,\n                               target_host.length())) {\n        std::ostringstream err;\n        err << \"error: hostname mismatch '\" << vid_hostname << \"'!='\" << target_host\n            << \"'; filesystems can only be configured as 'root'\"\n            << \" or from the server mounting them using sss protocol (1)\\n.\";\n        stdErr = err.str().c_str();\n        return EPERM;\n      }\n    }\n  } else {\n    stdErr = \"error: filesystems can only be configured as 'root' or \"\n             \"from the server mounting them using sss protocol (2)\\n\";\n    return EPERM;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Dump metadata information\n//------------------------------------------------------------------------------\nint\nproc_fs_dumpmd(std::string& sfsid, XrdOucString& option, bool show_path,\n               bool show_fid, bool show_fxid, bool show_size,\n               XrdOucString& stdOut, XrdOucString& stdErr,\n               eos::common::VirtualIdentity& vid_in, size_t& entries)\n{\n  entries = 0;\n  int retc = 0;\n  bool monitor = false;\n  bool processPath = false;\n  std::ostringstream out;\n  std::ostringstream err;\n  std::ostringstream warn;\n  out << std::setfill('0');\n  warn << std::setfill('0') << std::hex;\n\n  if (option == \"m\") {\n    monitor = true;\n    show_path = false;\n    show_fid = false;\n    show_fxid = false;\n    show_size = false;\n  }\n\n  processPath = monitor || show_path;\n\n  if (!sfsid.length()) {\n    err << \"error: no <fsid> provided\";\n    retc = EINVAL;\n  } else {\n    int fsid = atoi(sfsid.c_str());\n    eos::Prefetcher::prefetchFilesystemFileListWithFileMDsAndParentsAndWait(\n      gOFS->eosView, gOFS->eosFsView, fsid);\n\n    if (monitor) {\n      eos::Prefetcher::prefetchFilesystemUnlinkedFileListWithFileMDsAndWait(\n        gOFS->eosView, gOFS->eosFsView, fsid);\n    }\n\n    eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n\n    for (auto it_fid = gOFS->eosFsView->getFileList(fsid);\n         (it_fid && it_fid->valid()); it_fid->next()) {\n      std::shared_ptr<eos::IFileMD> fmd;\n      std::string containerpath;\n      std::string fullpath;\n\n      try {\n        fmd = gOFS->eosFileService->getFileMD(it_fid->getElement());\n\n        if (fmd) {\n          entries++;\n\n          if (processPath) {\n            try {\n              std::string spath = gOFS->eosView->getUri(fmd.get());\n              XrdOucString safepath = spath.c_str();\n              eos::common::StringConversion::SealXrdPath(safepath);\n              fullpath = safepath.c_str();\n              safepath = eos::common::Path{spath.c_str()} .GetParentPath();\n              eos::common::StringConversion::SealXrdPath(safepath);\n              containerpath = safepath.c_str();\n            } catch (eos::MDException& e) {\n              errno = e.getErrno();\n              eos_static_err(\"Couldn't retrieve path for fxid=%08llx \"\n                             \"errc=%d emsg=\\\"%s\\\"\", it_fid->getElement(),\n                             e.getErrno(), e.getMessage().str().c_str());\n            }\n          }\n\n          if ((!show_path) && (!show_fid) && (!show_fxid)  && (!show_size)) {\n            std::string env;\n            fmd->getEnv(env, true);\n            XrdOucString senv = env.c_str();\n\n            if (senv.endswith(\"checksum=\")) {\n              senv.replace(\"checksum=\", \"checksum=none\");\n            }\n\n            out << senv.c_str();\n\n            if (monitor) {\n              out << \"&container=\"\n                  << (containerpath.size() ? containerpath.c_str() : \"(null)\");\n            }\n          } else {\n            bool has_info = false;\n\n            if (show_path) {\n              out << \"path=\"\n                  << (fullpath.size() ? fullpath.c_str() : \"(null)\");\n              has_info = true;\n            }\n\n            if (show_fid) {\n              out << (has_info ? \" \" : \"\") << \"fid=\" << fmd->getId();\n              has_info = true;\n            }\n\n            if (show_fxid) {\n              out << (has_info ? \" \" : \"\") << \"fxid=\" << std::setw(8)\n                  << std::hex << fmd->getId() << std::dec;\n              has_info = true;\n            }\n\n            if (show_size) {\n              out << (has_info ? \" \" : \"\") << \"size=\" << fmd->getSize();\n            }\n          }\n\n          out << std::endl;\n        }\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        eos_static_err(\"Couldn't retrieve meta data for fxid=%llx errc=%d \"\n                       \"emsg=\\\"%s\\\"\", (unsigned long long) it_fid->getElement(),\n                       e.getErrno(), e.getMessage().str().c_str());\n      }\n\n      if (!fmd) {\n        warn << \"# warning: ghost entry fxid=\" << std::setw(8) << std::hex\n             << it_fid->getElement() << std::dec << std::endl;\n        retc = EIDRM;\n      } else if (processPath && containerpath.empty()) {\n        warn << \"# warning: missing container for fxid=\" << std::setw(8)\n             << std::hex << fmd->getId() << std::dec << std::endl;\n        retc = EIDRM;\n      }\n    }\n\n    if (monitor) {\n      // Also add files which have yet to be unlinked\n      for (auto it_fid = gOFS->eosFsView->getUnlinkedFileList(fsid);\n           (it_fid && it_fid->valid()); it_fid->next()) {\n        std::shared_ptr<eos::IFileMD> fmd;\n\n        try {\n          fmd = gOFS->eosFileService->getFileMD(it_fid->getElement());\n\n          if (fmd) {\n            entries++;\n            std::string env;\n            fmd->getEnv(env, true);\n            XrdOucString senv = env.c_str();\n            senv.replace(\"checksum=&\", \"checksum=none&\");\n            out << senv.c_str() << \"&container=(null)\" << std::endl;\n          }\n        } catch (eos::MDException& e) {\n          errno = e.getErrno();\n          eos_static_err(\"Couldn't retrieve meta data for fxid=%08llx errc=%d \"\n                         \"emsg=\\\"%s\\\"\", it_fid->getElement(), e.getErrno(),\n                         e.getMessage().str().c_str());\n        }\n      }\n    }\n  }\n\n  if (retc == EIDRM) {\n    out << warn.str().c_str();\n    err << \"# error: filesystem contains problematic entries\" << std::endl;\n  }\n\n  stdOut += out.str().c_str();\n  stdErr = err.str().c_str();\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Configure filesystem\n//------------------------------------------------------------------------------\nint\nproc_fs_config(std::string& identifier, std::string& key, std::string& value,\n               XrdOucString& stdOut, XrdOucString& stdErr,\n               eos::common::VirtualIdentity& vid_in,\n               const std::string& statusComment)\n{\n  int retc = 0;\n  eos::common::FileSystem::fsid_t fsid = 0;\n\n  // Check if identifier is fsid (must be pure numeric)\n  if (identifier.find_first_not_of(\"0123456789\") == std::string::npos) {\n    fsid = atoi(identifier.c_str());\n  }\n\n  if (!identifier.length() || !key.length() || !value.length()) {\n    stdErr = \"error: illegal parameters\";\n    retc = EINVAL;\n  } else {\n    FileSystem* fs = nullptr;\n    eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n\n    if (fsid) {\n      // by filesystem id\n      fs = FsView::gFsView.mIdView.lookupByID(fsid);\n    }\n\n    if (!fs && FsView::gFsView.GetMapping(identifier)) {\n      // by filesystem uuid\n      fs = FsView::gFsView.mIdView.lookupByID(FsView::gFsView.GetMapping(identifier));\n    }\n\n    if (!fs) {\n      // by host:port:data name\n      std::string path = identifier;\n      size_t slashpos = identifier.find('/');\n\n      if (slashpos != std::string::npos) {\n        path.erase(0, slashpos);\n        identifier.erase(slashpos);\n\n        if ((identifier.find(':') == std::string::npos)) {\n          identifier += \":1095\"; // default eos fst port\n        }\n\n        if ((identifier.find(\"/eos/\") == std::string::npos)) {\n          identifier.insert(0, \"/eos/\");\n          identifier.append(\"/fst\");\n        }\n\n        if (FsView::gFsView.mNodeView.count(identifier)) {\n          for (auto it = FsView::gFsView.mNodeView[identifier]->begin();\n               it != FsView::gFsView.mNodeView[identifier]->end(); ++it) {\n            FileSystem* candidate = FsView::gFsView.mIdView.lookupByID(*it);\n\n            if (candidate && candidate->GetPath() == path) {\n              fs = candidate;\n            }\n          }\n        }\n      }\n    }\n\n    if (fs) {\n      // Check the allowed strings\n      if (((key == \"configstatus\") &&\n           (eos::common::FileSystem::GetConfigStatusFromString(value.c_str()) !=\n            eos::common::ConfigStatus::kUnknown)) ||\n          (((key == eos::common::SCAN_IO_RATE_NAME) ||\n            (key == eos::common::SCAN_ENTRY_INTERVAL_NAME) ||\n            (key == eos::common::SCAN_RAIN_ENTRY_INTERVAL_NAME) ||\n            (key == eos::common::SCAN_DISK_INTERVAL_NAME) ||\n            (key == eos::common::SCAN_NS_INTERVAL_NAME) ||\n            (key == eos::common::SCAN_NS_RATE_NAME) ||\n            (key == eos::common::SCAN_ALTXS_INTERVAL_NAME) ||\n            (key == eos::common::ALTXS_SYNC) ||\n            (key == eos::common::ALTXS_SYNC_INTERVAL) ||\n            (key == \"max.ropen\" || (key == \"max.wopen\")) ||\n            (key == \"headroom\") || (key == \"graceperiod\") ||\n            (key == \"drainperiod\") || (key == \"proxygroup\") ||\n            (key == \"filestickyproxydepth\") || (key == \"forcegeotag\") ||\n            (key == \"sharedfs\") ||\n            (key == \"s3credentials\")))) {\n        // Check permissions\n        size_t dpos = 0;\n        std::string nodename = fs->GetString(\"host\");\n\n        if ((dpos = nodename.find('.')) != std::string::npos) {\n          nodename.erase(dpos);\n        }\n\n        int rc = check_sss_hostname_match(vid_in, nodename, stdErr);\n        if (rc) {\n          return rc;\n        }\n\n        if ((key == eos::common::SCAN_IO_RATE_NAME) ||\n            (key == eos::common::SCAN_ENTRY_INTERVAL_NAME) ||\n            (key == eos::common::SCAN_RAIN_ENTRY_INTERVAL_NAME) ||\n            (key == eos::common::SCAN_DISK_INTERVAL_NAME) ||\n            (key == eos::common::SCAN_NS_INTERVAL_NAME) ||\n            (key == eos::common::SCAN_NS_RATE_NAME) ||\n            (key == eos::common::SCAN_ALTXS_INTERVAL_NAME) ||\n            (key == eos::common::ALTXS_SYNC) ||\n            (key == eos::common::ALTXS_SYNC_INTERVAL) ||\n            (key == \"headroom\") || (key == \"graceperiod\") ||\n            (key == \"drainperiod\")) {\n          fs->SetLongLong(key.c_str(),\n                          eos::common::StringConversion::GetSizeFromString(value.c_str()));\n          FsView::gFsView.StoreFsConfig(fs);\n        } else if (key == \"configstatus\") {\n          if (value == \"empty\") {\n            // Check if this filesystem is really empty\n            if (gOFS->eosFsView->getNumFilesOnFs(fs->GetId()) != 0) {\n              std::ostringstream oss;\n              oss << \"error: the filesystem is not empty, therefore it can't be removed\\n\"\n                  << \"# -------------------------------------------------------------------\\n\"\n                  << \"# You can inspect the registered files via the command:\\n\"\n                  << \"# [eos] fs dumpmd \" << fs->GetId() << \" --path\\n\"\n                  << \"# -------------------------------------------------------------------\\n\"\n                  << \"# You can drain the filesystem if it is still operational via the command:\\n\"\n                  << \"# [eos] fs config \" << fs->GetId() << \" configstatus=drain\\n\"\n                  << \"# -------------------------------------------------------------------\\n\"\n                  << \"# You can force to remove these files via the command:\\n\"\n                  << \"# [eos] fs dropfiles \" << fs->GetId() << \"\\n\"\n                  << \"# -------------------------------------------------------------------\\n\"\n                  << \"# You can force to drop these files (brute force) via the command:\\n\"\n                  << \"# [eos] fs dropfiles \" << fs->GetId() << \" -f \\n\"\n                  << \"# -------------------------------------------------------------------\\n\"\n                  << \"# [eos] = 'eos -b' on MGM or 'eosadmin' on storage nodes\\n\";\n              stdErr = oss.str().c_str();\n              retc = EPERM;\n              return retc;\n            }\n          }\n\n          if (!fs->SetString(key.c_str(), value.c_str())) {\n            stdErr = \"error: failed to apply configuration change\";\n            retc = EINVAL;\n            return retc;\n          }\n\n          std::string operation;\n          bool success;\n\n          if (statusComment.empty()) {\n            success = fs->RemoveKey(\"statuscomment\");\n            operation = \"remove\";\n          } else {\n            success = fs->SetString(\"statuscomment\", statusComment.c_str());\n            operation = \"save\";\n          }\n\n          if (!success) {\n            eos_static_warning(\"failed to %s config status comment \"\n                               \"fs_identifier=%s comment=%s\", operation.c_str(),\n                               identifier.c_str(), statusComment.c_str());\n          }\n\n          FsView::gFsView.StoreFsConfig(fs);\n        } else if (key == \"s3credentials\") {\n          // Validate S3 credentials string\n          if (std::count(value.begin(), value.end(), ':') != 1) {\n            stdErr += \"error: invalid S3 credentials string\";\n            retc = EINVAL;\n            return retc;\n          } else {\n            size_t pos = value.find(':');\n\n            if (pos == 0 || (pos + 1) == value.length()) {\n              stdErr += \"error: S3 credentials string is missing \";\n              stdErr += (pos == 0)  ? \"<accesskey>\" : \"<secretkey>\";\n              retc = EINVAL;\n              return retc;\n            }\n          }\n\n          fs->SetString(key.c_str(), value.c_str());\n          FsView::gFsView.StoreFsConfig(fs);\n        } else if (key == \"forcegeotag\") {\n          std::string geotag = eos::common::SanitizeGeoTag(value);\n\n          if (geotag != value) {\n            stdErr += geotag.c_str();\n            retc = EINVAL;\n            return retc;\n          }\n\n          fs->SetString(key.c_str(), value.c_str());\n          FsView::gFsView.StoreFsConfig(fs);\n        } else {\n          // Other proxy* key set\n          fs->SetString(key.c_str(), value.c_str());\n          FsView::gFsView.StoreFsConfig(fs);\n        }\n      } else {\n        stdErr += \"error: not an allowed parameter <\";\n        stdErr += key.c_str();\n        stdErr += \">\";\n        retc = EINVAL;\n      }\n    } else {\n      stdErr += \"error: cannot identify the filesystem by <\";\n      stdErr += identifier.c_str();\n      stdErr += \">\";\n      retc = EINVAL;\n    }\n  }\n\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Add filesystem\n//------------------------------------------------------------------------------\nint\nproc_fs_add(mq::MessagingRealm* realm, std::string& sfsid, std::string& uuid,\n            std::string& nodename, std::string& mountpoint, std::string& space,\n            std::string& configstatusStr, std::string& sharedfs, XrdOucString& stdOut,\n            XrdOucString& stdErr, eos::common::VirtualIdentity& vid_in, bool force)\n{\n  using eos::common::StringConversion;\n  common::FileSystem::fsid_t fsid = atoi(sfsid.c_str());\n  common::ConfigStatus configStatus =\n    common::FileSystem::GetConfigStatusFromString(configstatusStr.c_str());\n\n  if ((!nodename.length()) || (!mountpoint.length()) || (!space.length()) ||\n      (!configstatusStr.length()) ||\n      (configStatus < eos::common::ConfigStatus::kOff)) {\n    stdErr += \"error: illegal parameters\";\n    return EINVAL;\n  }\n\n  // Node name comes as /eos/<host>..../, we just need <host> without domain\n  std::string rnodename = nodename;\n  rnodename.erase(0, 5);\n  size_t dpos;\n\n  if ((dpos = rnodename.find('.')) != std::string::npos) {\n    rnodename.erase(dpos);\n  }\n\n  int rc = check_sss_hostname_match(vid_in, rnodename, stdErr);\n  if (rc) {\n    return rc;\n  }\n\n  eos::common::RWMutexWriteLock fs_wr_lock(FsView::gFsView.ViewMutex);\n  // queuepath = /eos/<host:port>/<path>\n  std::string queuepath = nodename;\n  queuepath += mountpoint;\n  common::FileSystemLocator locator;\n\n  if (!common::FileSystemLocator::fromQueuePath(queuepath, locator)) {\n    eos_static_err(\"msg=\\\"could not parse queue path\\\" queue=\\\"%s\\\"\",\n                   queuepath.c_str());\n    stdErr += \"error: could not parse queue path qeueue='\";\n    stdErr += queuepath.c_str();\n    stdErr += \"'\";\n    return EINVAL;\n  }\n\n  // Check if this filesystem exists already ....\n  if (FsView::gFsView.ExistsQueue(nodename, queuepath)) {\n    eos_static_err(\"msg=\\\"file system already registered\\\" queue=\\\"%s\\\"\",\n                   queuepath.c_str());\n    stdErr += \"error: cannot register filesystem - it already exists!\";\n    return EEXIST;\n  }\n\n  // Check if there is a mapping for 'uuid'\n  if (FsView::gFsView.GetMapping(uuid) ||\n      ((fsid > 0) && (FsView::gFsView.HasMapping(fsid)))) {\n    eos_static_err(\"msg=\\\"file system already registered\\\" uuid=%s fsid=%lu\",\n                   uuid.c_str(), fsid);\n    stdErr = SSTR(\"error: file system identified by uuid=\" <<\n                  uuid << \" id=\" << sfsid << \" already exists\").c_str();\n    return EEXIST;\n  }\n\n  // We want one atomic update with all the parameters defined\n  std::string splitspace;\n  std::string splitgroup;\n  unsigned int groupsize = 0;\n  unsigned int groupmod = 0;\n  // Logic to automatically adjust scheduling subgroups\n  StringConversion::SplitByPoint(space, splitspace, splitgroup);\n\n  if (FsView::gFsView.mSpaceView.count(splitspace)) {\n    groupsize = atoi(FsView::gFsView.mSpaceView[splitspace]->GetMember\n                     (std::string(\"cfg.groupsize\")).c_str());\n    groupmod = atoi(FsView::gFsView.mSpaceView[splitspace]->GetMember\n                    (std::string(\"cfg.groupmod\")).c_str());\n  } else {\n    if (splitspace != eos::common::EOS_SPARE_GROUP) {\n      eos_static_err(\"msg=\\\"no such space\\\" space=%s\", splitspace.c_str());\n      stdErr = SSTR(\"error: not such space \\\"\" << splitspace << \"\\\"\").c_str();\n      return EINVAL;\n    }\n  }\n\n  // Groups where we attempt to insert the current file system\n  std::set<int> target_grps;\n\n  // Case when specific group is requested by the user\n  if (splitgroup.length()) {\n    try {\n      int id = std::stoi(splitgroup);\n\n      if (!force && (id >= (int)groupmod)) {\n        stdErr = SSTR(\"error: requested group \" << id\n                      << \" bigger than groupmod\\n\").c_str();\n        return EINVAL;\n      }\n\n      target_grps.insert(id);\n    } catch (...) {\n      eos_static_err(\"msg=\\\"invalid group requested\\\" group=\\\"%s\\\"\",\n                     splitgroup.c_str());\n      stdErr = SSTR(\"error: invalid group requested \\\"\"\n                    << splitgroup << \"\\\"\").c_str();\n      return EINVAL;\n    }\n  } else {\n    // Otherwise, try out all the groups in the space\n    for (int i = 0; i < (int)groupmod; ++i) {\n      target_grps.insert(i);\n    }\n  }\n\n  // Final group will be decided later on\n  splitgroup.clear();\n\n  // Handle special case for \"spare\" space that does not have any groups\n  if (splitspace == eos::common::EOS_SPARE_GROUP) {\n    target_grps.clear();\n  }\n\n  for (auto grp_id : target_grps) {\n    std::string schedgroup = SSTR(splitspace << \".\" << grp_id);\n\n    // All good if group is not yet created\n    if (FsView::gFsView.mGroupView.count(schedgroup) == 0) {\n      splitgroup = std::to_string(grp_id);\n      break;\n    }\n\n    FsGroup* group = FsView::gFsView.mGroupView[schedgroup];\n\n    if (!force) {\n      // Skip if group is already full\n      if (group->size() > groupsize) {\n        if (target_grps.size() == 1) {\n          eos_static_err(\"msg=\\\"scheduling group is full\\\" group=\\\"%s.%i\\\"\",\n                         splitspace.c_str(), grp_id);\n          stdErr += SSTR(\"error: scheduling group \" << splitspace << \".\" << grp_id\n                         << \" is full\" << std::endl).c_str();\n        }\n\n        continue;\n      }\n    }\n\n    // Skip if group already contains an fs from the current node.\n    // Allow disabling this check in development clusters through the envvar\n    bool exists = false;\n\n    if (!getenv(\"EOS_ALLOW_SAME_HOST_IN_GROUP\") && !force) {\n      for (auto it = group->begin(); it != group->end(); ++it) {\n        FileSystem* entry = FsView::gFsView.mIdView.lookupByID(*it);\n\n        if (entry && (entry->GetString(\"host\") == locator.getHost())) {\n          exists = true;\n          break;\n        }\n      }\n    }\n\n    if (exists) {\n      continue;\n    }\n\n    splitgroup = std::to_string(grp_id);\n    break;\n  }\n\n  if ((splitspace != eos::common::EOS_SPARE_GROUP) && splitgroup.empty()) {\n    eos_static_err(\"msg=\\\"no group available for file system\\\" fsid=%lu\"\n                   \" queue=%s space=\\\"%s\\\" group=\\\"%s\\\"\",\n                   fsid, queuepath.c_str(), splitspace.c_str(),\n                   splitgroup.c_str());\n    stdErr += \"error: no group available for file system\";\n    return EINVAL;\n  }\n\n  FileSystem* fs = nullptr;\n\n  if (fsid) {\n    if (!FsView::gFsView.ProvideMapping(uuid, fsid)) {\n      eos_static_err(\"msg=\\\"conflict registering file system id/uuid\\\"\"\n                     \"fsid=%lu uuid=%s\", fsid, uuid.c_str());\n      stdErr += \"error: conflict adding your uuid/fsid mapping\";\n      return EINVAL;\n    } else {\n      fs = new FileSystem(locator, realm);\n    }\n  } else {\n    fsid = FsView::gFsView.CreateMapping(uuid);\n    fs = new FileSystem(locator, realm);\n  }\n\n  stdOut += SSTR(\"success: mapped '\" << uuid <<  \"' <=> fsid=\" << fsid).c_str();\n  common::GroupLocator groupLocator;\n  std::string description = ((splitspace == eos::common::EOS_SPARE_GROUP) ?\n                             splitspace : SSTR(splitspace << \".\" << splitgroup));\n  common::GroupLocator::parseGroup(description, groupLocator);\n  common::FileSystemCoreParams coreParams(fsid, locator, groupLocator, uuid,\n                                          configStatus, sharedfs);\n\n  if (FsView::gFsView.Register(fs, coreParams)) {\n    // Set all the space related default parameters\n    if (FsView::gFsView.mSpaceView.count(space)) {\n      if (FsView::gFsView.mSpaceView[space]->ApplySpaceDefaultParameters(fs)) {\n        // Store the modifications\n        FsView::gFsView.StoreFsConfig(fs);\n      }\n    }\n  } else {\n    // Remove mapping\n    if (FsView::gFsView.RemoveMapping(fsid, uuid)) {\n      stdErr += SSTR(\"\\ninfo: unmapped '\" << uuid << \"' <!> fsid=\"\n                     << fsid << std::endl).c_str();\n    } else {\n      stdErr += \"error: cannot remove mapping - this can be fatal!\";\n    }\n\n    // Remove filesystem object\n    stdErr += \"error: cannot register filesystem - check for path duplication!\";\n    return EINVAL;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Move a filesystem/group/space to a group/space\n//------------------------------------------------------------------------------\nint\nproc_fs_mv(std::string& src, std::string& dst, XrdOucString& stdOut,\n           XrdOucString& stdErr, eos::common::VirtualIdentity& vid_in,\n           bool force, mq::MessagingRealm* realm)\n{\n  int retc = 0;\n  MvOpType operation = get_operation_type(src, dst, stdOut, stdErr);\n  eos::common::RWMutexWriteLock fs_wr_lock(FsView::gFsView.ViewMutex);\n\n  switch (operation) {\n  case MvOpType::FS_2_GROUP:\n    retc = proc_mv_fs_group(FsView::gFsView, src, dst, stdOut, stdErr, force);\n    break;\n\n  case MvOpType::FS_2_SPACE:\n    retc = proc_mv_fs_space(FsView::gFsView, src, dst, stdOut, stdErr, force);\n    break;\n\n  case MvOpType::GRP_2_SPACE:\n    retc = proc_mv_grp_space(FsView::gFsView, src, dst, stdOut, stdErr, force);\n    break;\n\n  case MvOpType::SPC_2_SPACE:\n    retc = proc_mv_space_space(FsView::gFsView, src, dst, stdOut, stdErr, force);\n    break;\n\n  case MvOpType::FS_2_NODE:\n    retc = proc_mv_fs_node(FsView::gFsView, src, dst, stdOut, stdErr, force, vid_in,\n                           realm);\n    break;\n\n  default:\n    stdErr = \"error: operation not supported\";\n    retc = EINVAL;\n    break;\n  }\n\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Check if a file system can be moved. It needs to be active and in RW mode.\n//------------------------------------------------------------------------------\nbool proc_fs_can_mv(eos::mgm::FileSystem* fs, const std::string& dst,\n                    XrdOucString& stdOut, XrdOucString& stdErr, bool force)\n{\n  std::ostringstream oss;\n  FileSystem::fs_snapshot_t snapshot;\n\n  if (fs->SnapShotFileSystem(snapshot)) {\n    if (force) {\n      return true;\n    }\n\n    if (dst.find('.') != std::string::npos) {\n      if (snapshot.mGroup == dst) {\n        oss << \"error: file system \" << snapshot.mId << \" is already in \"\n            << \"group \"  << dst << std::endl;\n        stdOut = oss.str().c_str();\n        return false;\n      }\n    } else {\n      if (snapshot.mSpace == dst) {\n        oss << \"error:: file system \" << snapshot.mId << \" is already in \"\n            << \"space \" << dst << std::endl;\n        stdOut = oss.str().c_str();\n        return false;\n      }\n    }\n\n    // File system must be in RW mode and active for the move to work\n    bool is_empty = (fs->GetConfigStatus() == eos::common::ConfigStatus::kEmpty);\n    bool is_active = (fs->GetActiveStatus() == eos::common::ActiveStatus::kOnline);\n\n    if (!(is_empty && is_active)) {\n      eos_static_err(\"msg=\\\"file system is not empty or is not active\\\" \"\n                     \"fsid=%lu\", snapshot.mId);\n      oss << \"error: file system \" << snapshot.mId << \" is not empty or \"\n          << \"is not active\" << std::endl;\n      stdErr = oss.str().c_str();\n      return false;\n    }\n  } else {\n    eos_static_err(\"%s\", \"msg=\\\"failed to snapshot file system\\\"\");\n    oss << \"error: failed to snapshot files system\" << std::endl;\n    stdErr = oss.str().c_str();\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Move a filesystem to a group\n//------------------------------------------------------------------------------\nint proc_mv_fs_group(FsView& fs_view, const std::string& src,\n                     const std::string& dst, XrdOucString& stdOut,\n                     XrdOucString& stdErr, bool force)\n{\n  using eos::mgm::FsView;\n  using eos::mgm::FileSystem;\n  using eos::common::StringConversion;\n  int pos = dst.find('.');\n  eos::common::FileSystem::fsid_t fsid = atoi(src.c_str());\n  std::string space = dst.substr(0, pos);\n  std::string group = dst.substr(pos + 1);\n  size_t grp_size = 0;\n  size_t grp_mod = 0;\n  std::ostringstream oss;\n  // Check if space exists and get groupsize and groupmod\n  auto it_space = fs_view.mSpaceView.find(space);\n\n  if (it_space != fs_view.mSpaceView.end()) {\n    grp_size = std::strtoul(it_space->second->GetConfigMember(\"groupsize\").c_str(),\n                            nullptr, 10);\n    grp_mod = std::strtoul(it_space->second->GetConfigMember(\"groupmod\").c_str(),\n                           nullptr, 10);\n  } else {\n    eos_static_err(\"msg=\\\"requested space %s does not exist\\\"\", space.c_str());\n    oss << \"error: space \" << space << \" does not exist\" << std::endl;\n    stdErr = oss.str().c_str();\n    return EINVAL;\n  }\n\n  // Get this filesystem\n  FileSystem* fs = fs_view.mIdView.lookupByID(fsid);\n\n  if (fs) {\n    if (!proc_fs_can_mv(fs, dst, stdOut, stdErr, force)) {\n      return EINVAL;\n    }\n  } else {\n    eos_static_err(\"no such fsid: %i\", fsid);\n    oss << \"error: no such fsid: \" << fsid << std::endl;\n    stdErr = oss.str().c_str();\n    return EINVAL;\n  }\n\n  // Get the target group\n  if (dst != eos::common::EOS_SPARE_GROUP) {\n    auto const iter = fs_view.mGroupView.find(dst);\n\n    if (iter != fs_view.mGroupView.end()) {\n      FsGroup* grp = iter->second;\n      size_t grp_num_fs = grp->size();\n\n      // Check that we can still add file systems to this group\n      if (!force && (grp_num_fs > grp_size)) {\n        eos_static_err(\"msg=\\\"reached maximum number of fs for group %s\\\"\",\n                       dst.c_str());\n        oss << \"error: reached maximum number of file systems for group \"\n            << dst.c_str() << std::endl;\n        stdErr = oss.str().c_str();\n        return EINVAL;\n      }\n\n      // Check that there is no other file system from the same node\n      // in this group\n      bool is_forbidden = false;\n      std::string host;\n      std::string fs_host = fs->GetHost();\n\n      for (auto it = grp->begin(); it != grp->end(); ++it) {\n        FileSystem* entry = fs_view.mIdView.lookupByID(*it);\n\n        if (entry) {\n          host = entry->GetHost();\n\n          if (fs_host == host) {\n            is_forbidden = true;\n            break;\n          }\n        }\n      }\n\n      if (!force && is_forbidden) {\n        eos_static_err(\"msg=\\\"group %s already contains an fs from the \"\n                       \"same node\\\"\", dst.c_str());\n        oss << \"error: group \" << dst\n            << \" already contains a file system from the same node\"\n            << std::endl;\n        stdErr = oss.str().c_str();\n        return EINVAL;\n      }\n    } else {\n      // A new group will be created, check that it respects the groupmod param\n      size_t grp_indx = std::strtoul(group.c_str(), nullptr, 10);\n\n      if (!force && (grp_indx >= grp_mod)) {\n        eos_static_err(\"group %s is not respecting the groupmod value of %u\",\n                       dst.c_str(), grp_mod);\n        oss << \"error: group \" << dst.c_str() << \" is not respecting the groupmod\"\n            << \" value of \" << grp_mod << \" for this space\" << std::endl;\n        stdErr = oss.str().c_str();\n        return EINVAL;\n      }\n\n      eos_static_debug(\"group %s will be created\", dst.c_str());\n    }\n  } else {\n    // This is a special case when we \"park\" file systems in the spare space\n    eos_static_debug(\"fsid %s will be \\\"parked\\\" in space spare\", src.c_str());\n  }\n\n  if (fs_view.MoveGroup(fs, dst)) {\n    // Apply defaults from the new space\n    std::set<std::string> paramlist;\n    paramlist.insert(\"scaninterval\");\n    paramlist.insert(\"scan_rain_interval\");\n    paramlist.insert(\"scanrate\");\n    paramlist.insert(\"headroom\");\n    paramlist.insert(\"drainperiod\");\n    paramlist.insert(\"graceperiod\");\n    paramlist.insert(\"max.ropen\");\n    paramlist.insert(\"max.wopen\");\n\n    for (auto it = paramlist.begin(); it != paramlist.end(); ++it) {\n      std::string value = it_space->second->GetConfigMember(*it);\n\n      if (value.length()) {\n        int64_t uvalue = StringConversion::GetSizeFromString(value.c_str());\n        fs->SetLongLong(it->c_str(), uvalue);\n        oss << \"info: applying space config \" << *it << \"=\" << value << std::endl;\n      }\n    }\n\n    FsView::gFsView.StoreFsConfig(fs);\n    oss << \"success: filesystem \" << (int) fs->GetId() << \" moved to group \"\n        << dst << std::endl;\n    stdOut = oss.str().c_str();\n  } else {\n    eos_static_err(\"failed to move fsid: %i to group: %s\", fsid, dst.c_str());\n    oss << \"error: failed to move filesystem \" << fsid << \" to group \"\n        << dst << std::endl;\n    stdErr = oss.str().c_str();\n    return EINVAL;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Move a filesystem to a space\n//------------------------------------------------------------------------------\nint proc_mv_fs_space(FsView& fs_view, const std::string& src,\n                     const std::string& dst, XrdOucString& stdOut,\n                     XrdOucString& stdErr, bool force)\n{\n  using eos::mgm::FsView;\n  std::ostringstream oss;\n  // Check if file system is not already in this space\n  FileSystem::fsid_t fsid = strtol(src.c_str(), nullptr, 10);\n  FileSystem* fs = fs_view.mIdView.lookupByID(fsid);\n\n  if (fs) {\n    if (!proc_fs_can_mv(fs, dst, stdOut, stdErr, force)) {\n      return EINVAL;\n    }\n  } else {\n    eos_static_err(\"msg=\\\"no such file system\\\" fsid=%lu\", fsid);\n    oss << \"error: no such fsid: \" << fsid << std::endl;\n    stdErr = oss.str().c_str();\n    return EINVAL;\n  }\n\n  auto it_space = fs_view.mSpaceView.find(dst);\n\n  if (it_space == fs_view.mSpaceView.end()) {\n    eos_static_info(\"msg=\\\"creating space %s\\\"\", dst.c_str());\n    FsSpace* new_space = new FsSpace(dst.c_str());\n    fs_view.mSpaceView[dst] = new_space;\n    it_space = fs_view.mSpaceView.find(dst);\n  }\n\n  int grp_size = std::atoi(it_space->second->GetConfigMember\n                           (\"groupsize\").c_str());\n  int grp_mod = std::atoi(it_space->second->GetConfigMember\n                          (\"groupmod\").c_str());\n\n  if ((dst == eos::common::EOS_SPARE_GROUP) && grp_mod) {\n    eos_static_err(\"%s\", \"msg=\\\"space spare must have groupmod 0\\\"\");\n    oss << \"error: space \\\"spare\\\" must have groupmod 0. Please update the \"\n        << \"space configuration using \\\"eos space define <space> <size> <mod>\"\n        << std::endl;\n    stdErr = oss.str().c_str();\n    stdOut.erase();\n    return EINVAL;\n  }\n\n  std::list<std::string> sorted_grps;\n\n  if (grp_mod) {\n    sorted_grps = proc_sort_groups_by_priority(fs_view, dst, grp_size, grp_mod);\n  } else {\n    // Special case for spare space which doesn't have groups\n    sorted_grps.emplace_back(eos::common::EOS_SPARE_GROUP);\n  }\n\n  bool done = false;\n\n  for (const auto& grp : sorted_grps) {\n    if (proc_mv_fs_group(fs_view, src, grp, stdOut, stdErr, force) == 0) {\n      stdErr = \"\";\n      done = true;\n      break;\n    }\n  }\n\n  if (!done) {\n    eos_static_err(\"msg=\\\"failed to add fs %s to space %s\\\"\",\n                   src.c_str(), dst.c_str());\n    std::ostringstream oss;\n    oss << \"error: failed to add file system \" << src.c_str() << \" to space \"\n        << dst.c_str() << \" - no suitable group found\" << std::endl;\n    stdOut.erase();\n    stdErr = oss.str().c_str();\n    return EINVAL;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Sort the groups in a space by priority - the first ones are the ones that\n// are most suitable to add a new file system to them.\n//------------------------------------------------------------------------------\nstd::list<std::string>\nproc_sort_groups_by_priority(FsView& fs_view, const std::string& space,\n                             size_t grp_size, size_t grp_mod)\n{\n  using eos::mgm::FsView;\n  struct cmp_grp {\n    bool operator()(FsGroup* a, FsGroup* b)\n    {\n      return (a->size() < b->size());\n    }\n  };\n  // All groups in space are possible candidates\n  std::list<FsGroup*> grps; // first - highest priority, last - lowest\n  std::set<std::string> set_grps;\n  std::string node;\n\n  for (std::uint32_t  i = 0; i < grp_mod; ++i) {\n    node = space;\n    node += \".\";\n    node += std::to_string(i);\n    set_grps.insert(node);\n  }\n\n  // Get all groups belonging to the current space and that have less than the\n  // max number of file systems\n  for (auto it = fs_view.mGroupView.begin();\n       it != fs_view.mGroupView.end(); ++it) {\n    if (it->first.find(space) == 0) {\n      set_grps.erase(it->first);\n\n      if (it->second->size() < grp_size) {\n        if (it->second->GetConfigMember(\"status\") == \"on\") {\n          grps.push_back(it->second);\n        }\n      }\n    }\n  }\n\n  grps.sort(cmp_grp());\n  // Any groups left in the initial set represent completely new groups\n  // without any file systems i.e highest priority\n  std::list<std::string> ret_grps(set_grps.begin(), set_grps.end());\n\n  for (auto&& grp : grps) {\n    ret_grps.push_back(grp->mName);\n  }\n\n  return ret_grps;\n}\n\n//------------------------------------------------------------------------------\n// Move a group to a space\n//------------------------------------------------------------------------------\nint proc_mv_grp_space(FsView& fs_view, const std::string& src,\n                      const std::string& dst, XrdOucString& stdOut,\n                      XrdOucString& stdErr, bool force)\n{\n  using eos::mgm::FsView;\n  std::ostringstream oss;\n  std::list<std::string> failed_fs; // file systems that couldn't be moved\n  auto it_grp = fs_view.mGroupView.find(src);\n\n  if (it_grp == fs_view.mGroupView.end()) {\n    eos_static_err(\"group %s does not exist\", src.c_str());\n    oss << \"error: group \" << src << \" does not exist\";\n    stdErr = oss.str().c_str();\n    return EINVAL;\n  }\n\n  // Get all file systems from the group since iterators will be invalidated\n  // when we start moving them to the new destination\n  FsGroup* grp = it_grp->second;\n  std::list<std::string> lst_fsids;\n\n  for (auto it = grp->begin(); it != grp->end(); ++it) {\n    lst_fsids.push_back(std::to_string(*it));\n  }\n\n  for (const auto& sfsid : lst_fsids) {\n    if (proc_mv_fs_space(fs_view, sfsid, dst, stdOut, stdErr, force)) {\n      failed_fs.push_back(sfsid);\n    }\n  }\n\n  if (!failed_fs.empty()) {\n    oss << \"warning: the following file systems could not be moved \";\n\n    for (auto&& elem : failed_fs) {\n      oss << elem << \" \";\n    }\n\n    oss << std::endl;\n    stdOut.erase();\n    stdErr = oss.str().c_str();;\n    return EINVAL;\n  } else {\n    oss << \"success: all file systems in group \" << src << \" have been \"\n        << \"moved to space \" << dst << std::endl;\n    stdOut = oss.str().c_str();\n    stdErr.erase();\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Move space to space\n//------------------------------------------------------------------------------\nint proc_mv_space_space(FsView& fs_view, const std::string& src,\n                        const std::string& dst, XrdOucString& stdOut,\n                        XrdOucString& stdErr, bool force)\n{\n  using eos::mgm::FsView;\n  std::ostringstream oss;\n  std::list<std::string> failed_fs; // file systems that couldn't be moved\n  auto it_space1 = fs_view.mSpaceView.find(src);\n\n  if (it_space1 == fs_view.mSpaceView.end()) {\n    eos_static_err(\"space %s does not exist\", src.c_str());\n    oss << \"error: space \" << src << \" does not exist\";\n    stdErr = oss.str().c_str();\n    return EINVAL;\n  }\n\n  auto it_space2 = fs_view.mSpaceView.find(dst);\n\n  if (it_space2 == fs_view.mSpaceView.end()) {\n    eos_static_err(\"space %s does not exist\", dst.c_str());\n    oss << \"error: space \" << dst << \" does not exist\";\n    stdErr = oss.str().c_str();\n    return EINVAL;\n  }\n\n  // Get all file systems from the space since iterators will be invalidated\n  // when we start moving them to the new destination\n  FsSpace* space1 = it_space1->second;\n  std::list<std::string> lst_fsids;\n\n  for (auto it = space1->begin(); it != space1->end(); ++it) {\n    lst_fsids.push_back(std::to_string(*it));\n  }\n\n  for (const auto& sfsid : lst_fsids) {\n    if (proc_mv_fs_space(fs_view, sfsid, dst, stdOut, stdErr, force)) {\n      failed_fs.push_back(sfsid);\n    }\n  }\n\n  if (!failed_fs.empty()) {\n    oss << \"warning: the following file systems could not be moved \";\n\n    for (auto&& elem : failed_fs) {\n      oss << elem << \" \";\n    }\n\n    oss << std::endl;\n    stdOut.erase();\n    stdErr = oss.str().c_str();;\n    return EINVAL;\n  } else {\n    oss << \"success: all file systems in space \" << src << \" have been \"\n        << \" moved to space \" << dst << std::endl;\n    stdOut = oss.str().c_str();\n    stdErr.erase();\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Move FS to a new node\n//------------------------------------------------------------------------------\nint\nproc_mv_fs_node(FsView& fs_view, const std::string& src,\n                const std::string& dst, XrdOucString& stdOut,\n                XrdOucString& stdErr, bool force,\n                eos::common::VirtualIdentity& vid_in,\n                mq::MessagingRealm* realm)\n{\n  std::ostringstream oss;\n  eos::common::FileSystem::fsid_t fsid = 0;\n  int rc = EINVAL;\n\n  try {\n    fsid = std::stoi(src.c_str());\n  } catch (...) {\n    eos_static_err(\"msg=\\\"failed to convert source fsid\\\" data=\\\"%s\\\"\",\n                   src.c_str());\n    return rc;\n  }\n\n  FileSystem* fs = fs_view.mIdView.lookupByID(fsid);\n\n  if (fs) {\n    if (!proc_fs_can_mv(fs, dst, stdOut, stdErr, force)) {\n      return EINVAL;\n    }\n\n    FileSystem::fs_snapshot_t snapshot;\n\n    if (fs->SnapShotFileSystem(snapshot)) {\n      // pretend this is empty\n      fs->SetString(\"configstatus\", \"empty\");\n      std::string a;\n      std::string b;\n      std::string id = src;\n      std::string uuid = snapshot.mUuid;\n      std::string hostport = snapshot.mHostPort;\n      std::string path = snapshot.mPath;\n      std::string space = snapshot.mSpace;\n      std::string group = snapshot.mGroup;\n      std::string sharedfs = snapshot.mSharedFs;\n      std::string configstatus = eos::common::FileSystem::GetConfigStatusAsString(\n                                   snapshot.mConfigStatus);\n      rc = proc_fs_rm(a, b, id, stdOut, stdErr, vid_in);\n      FsView::gFsView.ViewMutex.UnLockWrite();\n\n      if (!rc) {\n        std::string nodename = \"/eos/\";\n        nodename += dst;\n        nodename += \"/fst\";\n        rc = proc_fs_add(realm, id, uuid, nodename, path,\n                         ((getenv(\"EOS_ALLOW_SAME_HOST_IN_GROUP\") || force) ?\n                          group : space),\n                         configstatus, sharedfs, stdOut, stdErr, vid_in, true);\n\n        if (rc) {\n          oss << \"error: failed to reinsert filesystem with id='\" << fsid <<\n              \"' - this is really really bad!!!\" << std::endl;\n          stdErr += oss.str().c_str();\n          stdOut.erase();\n        }\n      } else {\n        oss << \"error: failed to remove file system id='\"\n            << fsid << \"'\" << std::endl;\n        stdErr = oss.str().c_str();\n        stdOut.erase();\n      }\n\n      FsView::gFsView.ViewMutex.LockWrite();\n    } else {\n      oss << \"error: failed ot snapshot filesystem with id='\" << fsid << \"'\" <<\n          std::endl;\n      stdErr = oss.str().c_str();\n      stdOut.erase();\n      rc = EINVAL;\n    }\n  } else {\n    oss << \"error: no such filesystem with id='\" << fsid << \"'\" << std::endl;\n    stdErr = oss.str().c_str();\n    stdOut.erase();\n    rc = ENOENT;\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Remove filesystem\n//------------------------------------------------------------------------------\nint\nproc_fs_rm(std::string& nodename, std::string& mountpoint, std::string& id,\n           XrdOucString& stdOut, XrdOucString& stdErr,\n           eos::common::VirtualIdentity& vid_in)\n{\n  int retc = 0;\n  eos::common::FileSystem::fsid_t fsid = 0;\n\n  if (id.length()) {\n    fsid = stoi(id);\n  }\n\n  eos_static_info(\"msg=\\\"file system removal\\\" id=\\\"%s\\\", node=\\\"%s\\\" \"\n                  \"mountpoint=\\\"%s\\\"\", id.c_str(), nodename.c_str(),\n                  mountpoint.c_str());\n  FileSystem* fs = 0;\n\n  if (id.length()) {\n    // find by id\n    fs = FsView::gFsView.mIdView.lookupByID(fsid);\n  } else {\n    if (mountpoint.length() && nodename.length()) {\n      std::string queuepath = nodename;\n      queuepath += mountpoint;\n      fs = FsView::gFsView.FindByQueuePath(queuepath);\n    }\n  }\n\n  if (fs) {\n    std::string nodename = fs->GetString(\"host\");\n    std::string cstate = fs->GetString(\"configstatus\");\n    size_t dpos = 0;\n\n    if ((dpos = nodename.find('.')) != std::string::npos) {\n      nodename.erase(dpos);\n    }\n\n    int rc = check_sss_hostname_match(vid_in, nodename, stdErr);\n    if (rc) {\n      return rc;\n    }\n\n    // @note We can only remove a file system only if it's empty and\n    // exceptionally if we are a slave MGM and the fs is in drain mode\n    // and we got a request to remove it. The empty state from the\n    // master MGM is never propagated to the slaves.\n    if ((cstate != \"empty\") &&\n        !((cstate == \"drain\") && !gOFS->mMaster->IsMaster())) {\n      eos_static_err(\"msg=\\\"only empty file systems can be removed\\\" \"\n                     \"cstate=\\\"%s\\\"\", cstate.c_str());\n      stdErr = \"error: you can only remove file systems which are in 'empty' status\";\n      retc = EINVAL;\n    } else {\n      if (!FsView::gFsView.UnRegister(fs, true, true)) {\n        stdErr = \"error: couldn't unregister the filesystem \";\n        stdErr += nodename.c_str();\n        stdErr += \" \";\n        stdErr += mountpoint.c_str();\n        stdErr += \" \";\n        stdErr += id.c_str();\n        stdErr += \"from the FsView\";\n        retc = EFAULT;\n      } else {\n        stdOut = \"success: unregistered \";\n        stdOut += nodename.c_str();\n        stdOut += \" \";\n        stdOut += mountpoint.c_str();\n        stdOut += \" \";\n        stdOut += id.c_str();\n        stdOut += \" from the FsView\\n\";\n      }\n    }\n  } else {\n    eos_static_err(\"msg=\\\"no file system found\\\" node=\\\"%s\\\" \"\n                   \"mountpoint=\\\"%s\\\"\", nodename.c_str(),\n                   mountpoint.c_str());\n    stdErr = \"error: there is no filesystem defined by \";\n    stdErr += nodename.c_str();\n    stdErr += \" \";\n    stdErr += mountpoint.c_str();\n    stdErr += \" \";\n    stdErr += id.c_str();\n    stdErr += \" \";\n    retc = EINVAL;\n  }\n\n  return retc;\n}\n\n//-------------------------------------------------------------------------------\n// Clean unlinked files from filesystem\n//-------------------------------------------------------------------------------\nint\nproc_fs_dropdeletion(const eos::common::FileSystem::fsid_t& fsid,\n                     const eos::common::VirtualIdentity& vid_in,\n                     std::string& out, std::string& err)\n{\n  if (fsid == 0ul) {\n    err = \"error: no such filesystem fsid=0\";\n    return EINVAL;\n  }\n\n  if ((vid_in.uid != 0)) {\n    err = \"error: command can only be executed by 'root'\";\n    return EPERM;\n  }\n\n  eos::common::RWMutexWriteLock ns_wr_lock(gOFS->eosViewRWMutex);\n\n  if (gOFS->eosFsView->clearUnlinkedFileList(fsid)) {\n    out = SSTR(\"success: dropped deletions on fsid=\" << fsid).c_str();\n  } else {\n    out = SSTR(\"note: there is no deletion list for fsid=\" << fsid).c_str();\n  }\n\n  return 0;\n}\n\n//-------------------------------------------------------------------------------\n// Clean ghost entries in a filesystem view\n//-------------------------------------------------------------------------------\nint\nproc_fs_dropghosts(const eos::common::FileSystem::fsid_t& fsid,\n                   const std::set<eos::IFileMD::id_t>& set_fids,\n                   const eos::common::VirtualIdentity& vid_in,\n                   std::string& out, std::string& err)\n{\n  if (fsid == 0ul) {\n    err = \"error: no such filesystem fsid=0\";\n    return EINVAL;\n  }\n\n  if ((vid_in.uid != 0)) {\n    err = \"error: command can only be executed by 'root'\";\n    return EPERM;\n  }\n\n  std::set<eos::IFileMD::id_t> to_delete;\n\n  if (set_fids.empty()) {\n    // We check all the files on that filesystem\n    eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n\n    for (auto it_fid = gOFS->eosFsView->getFileList(fsid);\n         (it_fid && it_fid->valid()); it_fid->next()) {\n      try {\n        auto fmd = gOFS->eosFileService->getFileMD(it_fid->getElement());\n      } catch (eos::MDException& e) {\n        if (e.getErrno() == ENOENT) {\n          out += SSTR(\"# removing id: \" << it_fid->getElement() << \"\\n\").c_str();\n          to_delete.insert(it_fid->getElement());\n        }\n      }\n    }\n  } else {\n    eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n\n    for (const auto& fid : set_fids) {\n      try {\n        auto fmd = gOFS->eosFileService->getFileMD(fid);\n      } catch (eos::MDException& e) {\n        if (e.getErrno() == ENOENT) {\n          out += SSTR(\"# removing id: \" << fid << \"\\n\").c_str();\n          to_delete.insert(fid);\n        }\n      }\n    }\n  }\n\n  eos::common::RWMutexWriteLock ns_wr_lock(gOFS->eosViewRWMutex);\n\n  for (const auto& fid : to_delete) {\n    gOFS->eosFsView->eraseEntry(fsid, fid);\n  }\n\n  out += SSTR(\"success: dropped \" << to_delete.size()\n              << \" ghost entries from fsid=\" << fsid).c_str();\n  return 0;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/proc_fs.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file proc_fs.hh\n//! @author Andreas-Joachim Peters - CERN & Ivan Arizanovic - Comtrade\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_PROC_FS__HH__\n#define __EOSMGM_PROC_FS__HH__\n\n#include \"mgm/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Mapping.hh\"\n#include \"mgm/filesystem/FileSystem.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include <XrdSec/XrdSecEntity.hh>\n#include <set>\n#include <list>\n\nnamespace eos::mq\n{\nclass MessagingRealm;\n}\n\n\nEOSMGMNAMESPACE_BEGIN\n\n//! Type of entity we are dealing with during an fs operation\nenum EntityType {\n  UNKNOWN = 0x00, // unknown entity\n  FS      = 0x01, // file system\n  GROUP   = 0x10, // eos space\n  SPACE   = 0x11, // eos group\n  NODE    = 0x1000 // node\n};\n\n//! Type of accepted operations for fs mv command\nenum class MvOpType {\n  UNKNOWN     = 0x0000,\n  FS_2_GROUP  = (EntityType::FS << 2) | EntityType::GROUP,\n  FS_2_SPACE  = (EntityType::FS << 2) | EntityType::SPACE,\n  GRP_2_SPACE = (EntityType::GROUP << 2) | EntityType::SPACE,\n  SPC_2_SPACE = (EntityType::SPACE << 2) | EntityType::SPACE,\n  FS_2_NODE   = (EntityType::FS << 2) | EntityType::NODE\n};\n\n//------------------------------------------------------------------------------\n//! Dump metadata held on filesystem\n//!\n//! @param sfsid id of the filesystem\n//! @param option output format option (can be default or monitor)\n//! @param show_path display path flag\n//! @param show_fid display fid flag\n//! @param show_fxid display fxid flag\n//! @param show_size display size flag\n//! @param stdOut normal output string\n//! @param stdErr error output string\n//! @param vid_in virtual identity of the client\n//! @param entries counts the number of entries\n//!\n//! @return 0 if successful, otherwise error code value\n//------------------------------------------------------------------------------\nint proc_fs_dumpmd(std::string& sfsid, XrdOucString& option, bool show_path,\n                   bool show_fid, bool show_fxid, bool show_size,\n                   XrdOucString& stdOut, XrdOucString& stdErr,\n                   eos::common::VirtualIdentity& vid_in, size_t& entries);\n\n//------------------------------------------------------------------------------\n//! Set filesystem configuration parameter\n//!\n//! @param identifier filesystem identifier (can be id, uuid)\n//! @param key parameter identifier\n//! @param value parameter value\n//! @param stdOut normal output string\n//! @param stdErr error output string\n//! @param vid_in virtual identity of the client\n//! @param statusComment comment detailing last config status change\n//!\n//! @return 0 if successful, otherwise error code value\n//------------------------------------------------------------------------------\nint proc_fs_config(std::string& identifier, std::string& key,\n                   std::string& value,\n                   XrdOucString& stdOut, XrdOucString& stdErr,\n                   eos::common::VirtualIdentity& vid_in,\n                   const std::string& statusComment = \"\");\n\n//------------------------------------------------------------------------------\n//! Add a new filesystem\n//!\n//! @param realm messaging realm\n//! @param sfsid id of the filesystem\n//! @param uuid uuid of the filesystem\n//! @param nodename node identifier in the node queue\n//! @param mountpoint location where to mount the filesystem\n//! @param space assigned scheduling space\n//! @param configstatus assigned config status\n//! @param stdOut normal output string\n//! @param stdErr error output string\n//! @param vid_in virtual identity of the client\n//!\n//! @return 0 if successful, otherwise error code value\n//------------------------------------------------------------------------------\nint proc_fs_add(mq::MessagingRealm* realm, std::string& sfsid,\n                std::string& uuid,\n                std::string& nodename, std::string& mountpoint, std::string& space,\n                std::string& configstatus,\n                std::string& sharedfs,\n                XrdOucString& stdOut,\n                XrdOucString& stdErr,\n                eos::common::VirtualIdentity& vid_in,\n                bool force = false);\n\n//------------------------------------------------------------------------------\n//! Remove a filesystem\n//!\n//! @param nodename node identifier in the node queue\n//! @param mountpoint location where to mount the filesystem\n//! @param id id of the filesystem\n//! @param stdOut normal output string\n//! @param stdErr error output string\n//! @param vid_in virtual identify of the client\n//!\n//! @return 0 if successful, otherwise error code value\n//------------------------------------------------------------------------------\nint proc_fs_rm(std::string& nodename, std::string& mountpoint, std::string& id,\n               XrdOucString& stdOut, XrdOucString& stdErr,\n               eos::common::VirtualIdentity& vid_in);\n\n//------------------------------------------------------------------------------\n//! Clear unlinked files from the filesystem\n//!\n//! @param fsid filesystem id\n//! @param out normal output string\n//! @param err error output string\n//! @param vid_in virtual identify of the client\n//!\n//! @return 0 if successful, otherwise error code value\n//------------------------------------------------------------------------------\nint proc_fs_dropdeletion(const eos::common::FileSystem::fsid_t& id,\n                         const eos::common::VirtualIdentity& vid_in,\n                         std::string& out, std::string& err);\n\n//------------------------------------------------------------------------------\n//! Drop ghost entries from a filesystem view (file ids without meta data objects)\n//!\n//! @param fsid filesystem id\n//! @param fids explicit set of fids to be checked and dropped if they are\n//!        ghosts entries\n//! @param out normal output string\n//! @param err error output string\n//! @param vid_in virtual identify of the client\n//!\n//! @return 0 if successful, otherwise error code value\n//------------------------------------------------------------------------------\nint proc_fs_dropghosts(const eos::common::FileSystem::fsid_t& fsid,\n                       const std::set<eos::IFileMD::id_t>& fids,\n                       const eos::common::VirtualIdentity& vid_in,\n                       std::string& out, std::string& err);\n\n\n//------------------------------------------------------------------------------\n//! Get type of entity. It can either be a filesystem, an eos group or an eos\n//! space.\n//!\n//! @param input string given as input\n//! @param stdOut normal output string\n//! @param stdErr error output string\n//!\n//! @return type of entity\n//------------------------------------------------------------------------------\nEntityType get_entity_type(const std::string& input, XrdOucString& stdOut,\n                           XrdOucString& stdErr);\n\n//------------------------------------------------------------------------------\n//! Get operation type based on input entity types\n//!\n//! @param input1 first input\n//! @param input2 second input\n//! @param stdOut normal output string\n//! @param stdErr error output string\n//!\n//! @return operation type\n//------------------------------------------------------------------------------\nMvOpType get_operation_type(const std::string& in1, const std::string& in2,\n                            XrdOucString& stdOut, XrdOucString& stdErr);\n\n//------------------------------------------------------------------------------\n//! Move a filesystem operation\n//!\n//! @param src file system/group/space to move\n//! @param dst destination\n//! @param stdOut output string\n//! @param stdErr error output string\n//! @param force allows to move non-empty filesystems\n//! @param realm messaging realm\n//!\n//! @return 0 if successful, otherwise error code\n//------------------------------------------------------------------------------\nint proc_fs_mv(std::string& src, std::string& dst, XrdOucString& stdOut,\n               XrdOucString& stdErr,\n               eos::common::VirtualIdentity& vid_in,\n               bool force,\n               mq::MessagingRealm* realm = 0);\n\n\n//------------------------------------------------------------------------------\n//! Check if a file system can be moved. It needs to be active and in RW mode.\n//! @note needs to be called with FsView::ViewMutex locked\n//!\n//! @param fs file system object\n//! @param dst destination\n//! @param stdOut output string\n//! @param stdErr error output string\n//! @param force allows to move non-empty filesystems\n//!\n//! @return true if can be moved, otherwise false\n//------------------------------------------------------------------------------\nbool proc_fs_can_mv(eos::mgm::FileSystem* fs, const std::string& dst,\n                    XrdOucString& stdOut, XrdOucString& stdErr, bool force);\n\n//------------------------------------------------------------------------------\n//! Move a filesystem to a group\n//! @note needs to be called with FsView::ViewMutex locked\n//!\n//! @param fs_view file system view handler\n//! @param src file system to move\n//! @param dst destination group\n//! @param stdOut output string\n//! @param stdErr error output string\n//! @param force allows to move non-empty filesystems\n//!\n//! @return 0 if successful, otherwise error code\n//------------------------------------------------------------------------------\nint proc_mv_fs_group(FsView& fs_view, const std::string& src,\n                     const std::string& dst, XrdOucString& stdOut,\n                     XrdOucString& stdErr, bool force);\n\n//------------------------------------------------------------------------------\n//! Move a filesystem to a space\n//! @note needs to be called with FsView::ViewMutex locked\n//!\n//! @param fs_view file system view handler\n//! @param src file system to move\n//! @param dst destination space\n//! @param stdOut output string\n//! @param stdErr error output string\n//! @param force allows to move non-empty filesystems\n//!\n//! @return 0 if successful, otherwise error code\n//------------------------------------------------------------------------------\nint proc_mv_fs_space(FsView& fs_view, const std::string& src,\n                     const std::string& dst, XrdOucString& stdOut,\n                     XrdOucString& stdErr, bool force);\n\n//------------------------------------------------------------------------------\n//! Move a group to a space\n//! @note needs to be called with FsView::ViewMutex locked\n//\n//! @param fs_view file system view handler\n//! @param src group to move\n//! @param dst destination space\n//! @param stdOut output string\n//! @param stdErr error output string\n//! @param force allows to move non-empty filesystems\n//!\n//! @return 0 if successful, otherwise error code\n//------------------------------------------------------------------------------\nint proc_mv_grp_space(FsView& fs_view, const std::string& src,\n                      const std::string& dst, XrdOucString& stdOut,\n                      XrdOucString& stdErr, bool force);\n\n//------------------------------------------------------------------------------\n//! Move a space to a space\n//! @note needs to be called with FsView::ViewMutex locked\n//!\n//! @param fs_view file system view handler\n//! @param src group to move\n//! @param dst destination space\n//! @param stdOut output string\n//! @param stdErr error output string\n//! @param force allows to move non-empty filesystems\n//!\n//! @return 0 if successful, otherwise error code\n//------------------------------------------------------------------------------\nint proc_mv_space_space(FsView& fs_view, const std::string& src,\n                        const std::string& dst, XrdOucString& stdOut,\n                        XrdOucString& stdErr, bool force);\n\n\n//------------------------------------------------------------------------------\n//! Move a filesystem between nodes\n//! @note needs to be called with FsView::ViewMutex locked\n//!\n//! @param fs_view file system view handler\n//! @param src filesyste to move\n//! @param dst destination node\n//! @param stdOut output string\n//! @param stdErr error output string\n//! @param force currently unused\n//!\n//! @return 0 if successful, otherwise error code\n//------------------------------------------------------------------------------\nint proc_mv_fs_node(FsView& fs_view, const std::string& src,\n                    const std::string& dst, XrdOucString& stdOut,\n                    XrdOucString& stdErr, bool force,\n                    eos::common::VirtualIdentity& vid_in,\n                    mq::MessagingRealm* realm);\n\n//------------------------------------------------------------------------------\n//! Sort the groups in a space by priority - the first ones are the ones that\n//! are most suitable to add a new file system to them.\n//!\n//! @param fs_view file system view handler\n//! @param space space from which to sort the groups\n//! @param grp_size maximum number of file systems per group\n//! @param grp_mod maximum number of groups in current space\n//!\n//! @return sorted list of groups with the most desirable one in the beginning\n//------------------------------------------------------------------------------\nstd::list<std::string>\nproc_sort_groups_by_priority(FsView& fs_view, const std::string& space,\n                             size_t grp_size, size_t grp_mod);\n\nEOSMGMNAMESPACE_END\n#endif\n"
  },
  {
    "path": "mgm/proc/user/Accounting.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/Accounting.cc\n// Author: Jozsef Makai - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/StringTokenizer.hh\"\n#include \"common/ExpiryCache.hh\"\n#include \"common/Logging.hh\"\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/proc/ProcCommand.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"json/json.h\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Accounting()\n{\n  static eos::common::ExpiryCache<std::string> accountingCache(\n    std::chrono::seconds(600));\n  static const auto generateAccountingJson = [](\n  eos::common::VirtualIdentity & vid) {\n    static const auto processAccountingAttribute = [](\n    std::pair<std::string, std::string> attr, Json::Value & storageShare) {\n      static auto accountingAttrPrefix = \"sys.accounting\";\n\n      if (attr.first.find(accountingAttrPrefix) == 0) {\n        auto objectPath =\n          eos::common::StringTokenizer::split<std::vector<std::string>>(attr.first, '.');\n        Json::Value* keyEndpointPtr = nullptr;\n\n        // Creating a new json object following the path in this case\n        if (objectPath.size() > 3) {\n          // we keep a pointer to the previous json object along the path\n          auto* prevObject = &(storageShare[objectPath[2]]);\n\n          for (size_t i = 3; i < objectPath.size(); ++i) {\n            try {\n              auto number = std::stoul(objectPath[i]);\n              prevObject = &((*prevObject)[Json::ArrayIndex(number)]);\n            } catch (std::invalid_argument& err) {\n              prevObject = &((*prevObject)[objectPath[i]]);\n            }\n          }\n\n          // finally set the value of the attribute\n          keyEndpointPtr = prevObject;\n        } else {\n          keyEndpointPtr = &(storageShare[objectPath[objectPath.size() - 1]]);\n        }\n\n        // This value is interpreted as a list of elements separated by comma\n        if (attr.second.find(',') != std::string::npos) {\n          auto attrs = eos::common::StringTokenizer::split<std::list<std::string>>\n                       (attr.second, ',');\n\n          for (auto && attrValue : attrs) {\n            (*keyEndpointPtr).append(attrValue);\n          }\n        } else {\n          *keyEndpointPtr = attr.second;\n        }\n      }\n    };\n    Json::Value root;\n    Json::Value storageShare;\n    eos::IContainerMD::XAttrMap attributes;\n    XrdOucErrInfo errInfo;\n    // start with extended attributes so they can't overwrite fields\n    gOFS->_attr_ls(gOFS->MgmProcPath.c_str(), errInfo, vid, nullptr,\n                   attributes);\n\n    for (const auto& attr : attributes) {\n      processAccountingAttribute(attr, storageShare);\n    }\n\n    for (const auto& member : storageShare.getMemberNames()) {\n      root[\"storageservice\"][member] = storageShare[member];\n    }\n\n    root[\"storageservice\"][\"name\"] = gOFS->MgmOfsInstanceName.c_str();\n    std::ostringstream version;\n    version << VERSION << \"-\" << RELEASE;\n    root[\"storageservice\"][\"implementation\"] = \"EOS\";\n    root[\"storageservice\"][\"implementationversion\"] = version.str().c_str();\n    root[\"storageservice\"][\"latestupdate\"] = Json::Int64{std::time(nullptr)};\n    auto capacityOnline = Json::UInt64{0};\n    auto usedOnline = Json::UInt64{0};\n\n    for (const auto& quota : Quota::GetAllGroupsLogicalQuotaValues()) {\n      storageShare.clear();\n      attributes.clear();\n      errInfo.clear();\n      gOFS->_attr_ls(quota.first.c_str(), errInfo, vid, nullptr, attributes);\n\n      for (const auto& attr : attributes) {\n        processAccountingAttribute(attr, storageShare);\n      }\n\n      auto usedSizeofShare = Json::UInt64{std::get<0>(quota.second)};\n      auto totalSizeofShare = Json::UInt64{std::get<1>(quota.second)};\n      capacityOnline += totalSizeofShare;\n      usedOnline += usedSizeofShare;\n      storageShare[\"path\"].append(quota.first);\n      storageShare[\"usedsize\"] = usedSizeofShare;\n      storageShare[\"totalsize\"] = totalSizeofShare;\n      storageShare[\"numberoffiles\"] = Json::UInt64{std::get<2>(quota.second)};\n      storageShare[\"timestamp\"] = Json::Int64{std::time(nullptr)};\n      root[\"storageservice\"][\"storageshares\"].append(storageShare);\n    }\n\n    root[\"storageservice\"][\"storagecapacity\"][\"online\"][\"totalsize\"] =\n      capacityOnline;\n    root[\"storageservice\"][\"storagecapacity\"][\"online\"][\"usedsize\"] = usedOnline;\n    root[\"storageservice\"][\"storagecapacity\"][\"offline\"][\"totalsize\"] = Json::UInt64{0};\n    root[\"storageservice\"][\"storagecapacity\"][\"offline\"][\"usedsize\"] = Json::UInt64{0};\n    return new std::string(SSTR(root));\n  };\n  retc = SFS_OK;\n\n  if (mSubCmd == \"config\") {\n    if (!pVid->sudoer) {\n      stdErr += \"error: only sudoers are allowed to change cache configuration\";\n      retc = EPERM;\n      return retc;\n    }\n\n    if (pOpaque->Get(\"mgm.accounting.expired\")) {\n      try {\n        auto minutes = std::stoi(pOpaque->Get(\"mgm.accounting.expired\"));\n        accountingCache.SetExpiredAfter(\n          std::chrono::duration_cast<std::chrono::seconds>(\n            std::chrono::minutes(std::max(minutes, 1))\n          )\n        );\n        stdOut += \"success: expired time frame set to \";\n        stdOut += minutes;\n        stdOut += \"\\n\";\n      } catch (std::invalid_argument& err) {\n        stdErr += \"error: provided number is not configurable\";\n        retc = EINVAL;\n      }\n    }\n\n    if (pOpaque->Get(\"mgm.accounting.invalid\")) {\n      try {\n        auto minutes = std::stoi(pOpaque->Get(\"mgm.accounting.invalid\"));\n        accountingCache.SetInvalidAfter(\n          std::chrono::duration_cast<std::chrono::seconds>(\n            std::chrono::minutes(std::max(minutes, 5))\n          )\n        );\n        stdOut += \"success: invalid time frame set to \";\n        stdOut += minutes;\n        stdOut += \"\\n\";\n      } catch (std::invalid_argument& err) {\n        stdErr += \"error: provided number is not configurable\";\n        retc = EINVAL;\n      }\n    }\n  } else if (mSubCmd == \"report\") {\n    auto options = std::string(pOpaque->Get(\"mgm.option\") ?\n                               pOpaque->Get(\"mgm.option\") : \"\");\n    bool forceUpdate = options.find('f') != std::string::npos;\n\n    try {\n      auto json = accountingCache.getCachedObject(forceUpdate, generateAccountingJson,\n                  std::ref(*pVid));\n      stdOut += json.c_str();\n    } catch (eos::common::UpdateException& err) {\n      stdErr += err.what();\n      retc = EAGAIN;\n    }\n  } else {\n    stdErr += \"error: command is not supported\";\n    retc = ENOTSUP;\n  }\n\n  return retc;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/AclCmd.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file AclCmd.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"AclCmd.hh\"\n#include \"mgm/acl/Acl.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/ErrnoToString.hh\"\n#include \"common/Path.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include <unistd.h>\n#include <functional>\n#include <queue>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Process request\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nAclCmd::ProcessRequest() noexcept\n{\n  using eos::console::AclProto;\n  eos::console::ReplyProto reply;\n  eos::console::AclProto acl = mReqProto.acl();\n  std::string err_msg;\n\n  if (acl.op() == AclProto::LIST) {\n    std::string acl_val;\n\n    try {\n      eos::Prefetcher::prefetchItemAndWait(gOFS->eosView, acl.path());\n      eos::FileOrContainerMD item = gOFS->eosView->getItem(acl.path()).get();\n      eos::FileOrContReadLocked item_rlock;\n\n      if (item.file) {\n        item_rlock.fileLock = eos::MDLocking::readLock(item.file.get());\n      } else {\n        item_rlock.containerLock = eos::MDLocking::readLock(item.container.get());\n      }\n\n      GetAcls(item, acl_val, acl.sys_acl(), acl.user_acl());\n\n      if (acl_val.empty()) {\n        reply.set_std_err(SSTR(\"error: \" <<\n                               eos::common::ErrnoToString(ENODATA)));\n        reply.set_retc(ENODATA);\n      } else {\n        // Convert to username if possible, ignore errors\n        (void) Acl::ConvertIds(acl_val, true);\n        reply.set_std_out(acl_val);\n        reply.set_retc(0);\n      }\n    } catch (const eos::MDException& e) {\n      reply.set_std_err(SSTR(\"error: \" <<\n                             eos::common::ErrnoToString(e.getErrno())));\n      reply.set_retc(e.getErrno());\n    }\n  } else if (acl.op() == AclProto::MODIFY) {\n    int retc = ModifyAcls(acl);\n    reply.set_retc(retc);\n    reply.set_std_out(\"\");\n\n    if (retc) {\n      reply.set_std_err(mErr);\n    }\n  } else {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: not supported\");\n  }\n\n  return reply;\n}\n\n//------------------------------------------------------------------------------\n// Get sys.acl and user.acl for a given path\n//------------------------------------------------------------------------------\nvoid\nAclCmd::GetAcls(eos::FileOrContainerMD& item, std::string& acl, bool sys,\n                bool user)\n{\n  bool header = sys && user;\n\n  if (sys) {\n    std::string sys_acl;\n\n    if (gOFS->_attr_get(item, \"sys.acl\", sys_acl)) {\n      if (!sys_acl.empty()) {\n        if (header) {\n          acl += \"# sys.acl\\n\";\n        }\n\n        acl += sys_acl;\n      }\n    }\n  }\n\n  if (user) {\n    std::string user_acl;\n\n    if (gOFS->_attr_get(item, \"user.acl\", user_acl)) {\n      if (!user_acl.empty()) {\n        if (header) {\n          std::string eval_acl;\n          gOFS->_attr_get(item, \"sys.eval.useracl\", eval_acl);\n          acl += \"\\n# user.acl\";\n\n          if (eval_acl != \"1\") {\n            acl += \" (ignored)\";\n          }\n\n          acl += \"\\n\";\n        }\n\n        acl += user_acl;\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Modify acls\n//------------------------------------------------------------------------------\nint\nAclCmd::ModifyAcls(const eos::console::AclProto& acl)\n{\n  // Parse acl modification command into bitmask rule format\n  if (!ParseRule(acl.rule())) {\n    eos_static_err(\"msg=\\\"%s\\\"\", mErr.c_str());\n    mErr = \"error: failed to parse ACL input rule or unknown id\";\n    return EINVAL;\n  }\n\n  XrdOucErrInfo error;\n  XrdOucString m_err = \"\";\n  const std::string acl_key = (acl.sys_acl() ? \"sys.acl\" : \"user.acl\");\n\n  if (acl_key == \"user.acl\") {\n    // If user.acl to be modified and the tag sys.eval.useracl not set then fail\n    std::string eval_acl;\n\n    if ((mVid.uid != 0) &&\n        gOFS->_attr_get(acl.path().c_str(), error, mVid,\n                        (const char*) 0, \"sys.eval.useracl\", eval_acl)) {\n      mErr = \"error: unable to set user.acl, missing sys.eval.useracl\";\n      return EINVAL;\n    }\n  }\n\n  std::list<std::string> paths;\n  eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, acl.path(), false);\n\n  if (acl.recursive()) {\n    std::map<std::string, std::set<std::string>> dirs;\n    m_err.erase();\n    (void) gOFS->_find(acl.path().c_str(), error, m_err, mVid, dirs, nullptr,\n                       nullptr, true, 0, false, 0, nullptr);\n\n    if (m_err.length()) {\n      mErr = m_err.c_str();\n      return EINVAL;\n    }\n\n    if (!dirs.size()) {\n      paths.push_back(acl.path());\n    }\n\n    // Save all the directories in the current subtree skipping version dirs\n    for (const auto& elem : dirs) {\n      if (elem.first.find(EOS_COMMON_PATH_VERSION_PREFIX) ==\n          std::string::npos) {\n        paths.push_back(elem.first);\n      }\n    }\n  } else {\n    paths.push_back(acl.path());\n  }\n\n  RuleMap rule_map;\n  std::string old_acls, new_acl;\n  int ret = 0;\n\n  // If we only have one path this can be either a file or a container\n  if (paths.size() == 1) {\n    eos::Prefetcher::prefetchItemAndWait(gOFS->eosView, paths.front());\n  }\n\n  for (const auto& dpath : paths) {\n    eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, dpath);\n    // Clear the old_acls variable for each path\n    old_acls.clear();\n    // Fuse notifications must be sent after the metadata lock is released\n    eos::mgm::FusexCastBatch fuse_batch;\n\n    try {\n      eos::FileOrContainerMD item;\n      eos::FileOrContWriteLocked item_wlock;\n\n      // This could be either a file or a container\n      if (paths.size() == 1) {\n        item = gOFS->eosView->getItem(dpath).get();\n\n        if (item.file) {\n          item_wlock.fileLock = eos::MDLocking::writeLock(item.file.get());\n        } else {\n          item_wlock.containerLock = eos::MDLocking::writeLock(item.container.get());\n        }\n      } else {\n        // This is for sure a container\n        item.container = gOFS->eosView->getContainer(dpath);\n        item_wlock.containerLock = eos::MDLocking::writeLock(item.container.get());\n      }\n\n      GetAcls(item, old_acls, acl.sys_acl(), acl.user_acl());\n      GenerateRuleMap(old_acls, rule_map);\n      // ACL position is 1-indexed as 0 is the default numeric protobuf val\n      auto [err, acl_pos] = GetRulePosition(rule_map.size(), acl.position());\n\n      if (err) {\n        mErr = \"error: rule position cannot be met!\";\n        return err;\n      }\n\n      ApplyRule(rule_map, acl_pos);\n      new_acl = GenerateAclString(rule_map);\n      eos_info(\"msg=\\\"ACL update\\\" old_acl=\\\"%s\\\" new_acl=\\\"%s\\\" path=\\\"%s\\\"\",\n               old_acls.c_str(), new_acl.c_str(), acl.path().c_str());\n\n      if (!gOFS->_attr_set(item, acl_key, new_acl, false, mVid, fuse_batch)) {\n        mErr = \"error: failed to set new acl for path=\";\n        mErr += dpath.c_str();\n        eos_err(\"msg=\\\"failed to set acl\\\" path=\\\"%s\", dpath.c_str());\n        // The returned errno will correspond to the first errno encountered\n        // during the application of the ACL recursively\n        ret = errno;\n        return ret;\n      }\n    } catch (const eos::MDException& e) {\n      if (acl.recursive() && (e.getErrno() == ENOENT) && (paths.size() > 1)) {\n        eos_err(\"msg=\\\"skip acl update for missing directoy\\\" path=\\\"%s\\\"\",\n                dpath.c_str());\n        continue;\n      }\n\n      mErr = \"error: failed to set new acl for path=\";\n      mErr += dpath.c_str();\n      eos_err(\"msg=\\\"failed to set acl\\\" path=\\\"%s\", dpath.c_str());\n      ret = e.getErrno();\n      return ret;\n    }\n  }\n\n  return ret;\n}\n\n//------------------------------------------------------------------------------\n// Get ACL rule from string by creating a pair of identifier for the ACL and\n// the bitmask representation\n//------------------------------------------------------------------------------\nstd::optional<Rule> AclCmd::GetRuleFromString(const std::string& single_acl)\n{\n  Rule ret;\n  auto acl_delimiter = single_acl.rfind(':');\n\n  if (acl_delimiter == std::string::npos) {\n    eos_static_err(\"msg=\\\"invalid acl string\\\" acl=\\\"%s\\\"\",\n                   single_acl.c_str());\n    return std::nullopt;\n  }\n\n  ret.first = std::string(single_acl.begin(),\n                          single_acl.begin() + acl_delimiter);\n  unsigned long rule_int = 0;\n\n  for (auto i = acl_delimiter + 1, size = single_acl.length(); i < size; ++i) {\n    switch (single_acl.at(i)) {\n    case 'r' :\n      rule_int = rule_int | AclCmd::R;\n      break;\n\n    case 'w' :\n\n      // Check for wo case\n      if ((i + 1 < size) && single_acl.at(i + 1) == 'o') {\n        i++;\n        rule_int = rule_int | AclCmd::WO;\n      } else {\n        rule_int = rule_int | AclCmd::W;\n      }\n\n      break;\n\n    case 'x' :\n      rule_int = rule_int | AclCmd::X;\n      break;\n\n    case 'm' :\n      rule_int = rule_int | AclCmd::M;\n      break;\n\n    case 'q' :\n      rule_int = rule_int | AclCmd::Q;\n      break;\n\n    case 'c' :\n      rule_int = rule_int | AclCmd::C;\n      break;\n\n    case 'a':\n      rule_int = rule_int | AclCmd::A;\n      break;\n\n    case 'A':\n      rule_int = rule_int | AclCmd::SysAcl;\n      break;\n\n    case 'X':\n      rule_int = rule_int | AclCmd::SysAttr;\n      break;\n\n    case 't':\n      rule_int = rule_int | AclCmd::Token;\n      break;\n\n    case '+' :\n      // There are only two + flags in current acl permissions +d and +u\n      i++;\n\n      if (single_acl.at(i) == 'd') {\n        rule_int = rule_int | AclCmd::pD;\n      } else {\n        rule_int = rule_int | AclCmd::pU;\n      }\n\n      break;\n\n    case '!' :\n      i++;\n\n      if (single_acl.at(i) == 'd') {\n        rule_int = rule_int | AclCmd::nD;\n      }\n\n      if (single_acl.at(i) == 'u') {\n        rule_int = rule_int | AclCmd::nU;\n      }\n\n      if (single_acl.at(i) == 'm') {\n        rule_int = rule_int | AclCmd::nM;\n      }\n\n      if (single_acl.at(i) == 'r') {\n        rule_int = rule_int | AclCmd::nR;\n      }\n\n      if (single_acl.at(i) == 'w') {\n        rule_int = rule_int | AclCmd::nW;\n      }\n\n      if (single_acl.at(i) == 'x') {\n        rule_int = rule_int | AclCmd::nX;\n      }\n\n      break;\n\n    default:\n      break;\n    }\n  }\n\n  ret.second = rule_int;\n  return ret;\n}\n\n\n//------------------------------------------------------------------------------\n// Generate rule map from the string representation of the acls\n//------------------------------------------------------------------------------\nvoid\nAclCmd::GenerateRuleMap(const std::string& acl_string, RuleMap& rmap)\n{\n  if (acl_string.empty()) {\n    return;\n  }\n\n  rmap.clear();\n  size_t curr_pos = 0, pos = 0;\n\n  while (true) {\n    pos = acl_string.find(',', curr_pos);\n\n    if (pos == std::string::npos) {\n      pos = acl_string.length();\n    }\n\n    std::string single_acl = std::string(acl_string.begin() + curr_pos,\n                                         acl_string.begin() + pos);\n    auto rule = GetRuleFromString(single_acl);\n\n    if (rule) {\n      insert_or_assign(rmap, std::move(*rule));\n    } else {\n      eos_static_err(\"msg=\\\"failed to parse ACL rule\\\" rule=\\\"%s\\\"\",\n                     single_acl.c_str());\n    }\n\n    curr_pos = pos + 1;\n\n\n    if (curr_pos > acl_string.length()) {\n      break;\n    }\n  }\n\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Convert acl modification command into bitmask rule format\n//------------------------------------------------------------------------------\nbool AclCmd::GetRuleBitmask(const std::string& input, bool set)\n{\n  bool lambda_happen = false;\n  unsigned long ret = 0, add_ret = 0, rm_ret = 0;\n  auto add_lambda = [&](AclCmd::ACLPos pos) {\n    add_ret = add_ret | pos;\n    ret = ret | pos;\n  };\n  auto remove_lambda = [&](AclCmd::ACLPos pos) {\n    rm_ret = rm_ret | pos;\n    ret = ret & (~pos);\n  };\n  std::function<void(AclCmd::ACLPos)> curr_lambda = add_lambda;\n\n  for (auto flag = input.begin(); flag != input.end(); ++flag) {\n    // Check for add/rm rules\n    if ((*flag == '-') || (*flag == '+')) {\n      auto temp_iter = flag;\n      ++temp_iter;\n\n      if (temp_iter == input.end()) {\n        continue;\n      }\n\n      if ((*flag == '-') && (*temp_iter == '-')) {\n        goto error_label;\n      }\n\n      if (*flag == '-') {\n        lambda_happen = true;\n        curr_lambda = remove_lambda;\n\n        if (*temp_iter == '+') {\n          ++flag;\n        }\n      } else if (*flag == '+') {\n        lambda_happen = true;\n        curr_lambda = add_lambda;\n\n        if (*temp_iter == '+') {\n          ++flag;\n        }\n      }\n\n      if ((*temp_iter != 'd') &&\n          (*temp_iter != 'u') &&\n          (*temp_iter != '+')) {\n        continue;\n      }\n    }\n\n    // If there is no +/- character non-\"set\" mode\n    if (!set && !lambda_happen) {\n      goto error_label;\n    }\n\n    // Check for flags\n    if (*flag == 'r') {\n      curr_lambda(AclCmd::R);\n      continue;\n    }\n\n    if (*flag == 'w') {\n      auto temp_iter = flag;\n      ++temp_iter;\n\n      if ((temp_iter != input.end()) && (*temp_iter == 'o')) {\n        curr_lambda(AclCmd::WO);\n        ++flag;\n      } else {\n        curr_lambda(AclCmd::W);\n      }\n\n      continue;\n    }\n\n    if (*flag == 'x') {\n      curr_lambda(AclCmd::X);\n      continue;\n    }\n\n    if (*flag == 'm') {\n      curr_lambda(AclCmd::M);\n      continue;\n    }\n\n    if (*flag == 'q') {\n      curr_lambda(AclCmd::Q);\n      continue;\n    }\n\n    if (*flag == 'a') {\n      curr_lambda(AclCmd::A);\n      continue;\n    }\n\n    if (*flag == 'A') {\n      curr_lambda(AclCmd::SysAcl);\n      continue;\n    }\n\n    if (*flag == 'X') {\n      curr_lambda(AclCmd::SysAttr);\n      continue;\n    }\n\n    if (*flag == 't') {\n      curr_lambda(AclCmd::Token);\n      continue;\n    }\n\n    if (*flag == 'c') {\n      curr_lambda(AclCmd::C);\n      continue;\n    }\n\n    if (*flag == '!') {\n      ++flag;\n\n      if (flag == input.end()) {\n        goto error_label;\n      }\n\n      if (*flag == 'd') {\n        curr_lambda(AclCmd::nD);\n        continue;\n      }\n\n      if (*flag == 'u') {\n        curr_lambda(AclCmd::nU);\n        continue;\n      }\n\n      if (*flag == 'm') {\n        curr_lambda(AclCmd::nM);\n        continue;\n      }\n\n      if (*flag == 'r') {\n        curr_lambda(AclCmd::nR);\n        continue;\n      }\n\n      if (*flag == 'w') {\n        curr_lambda(AclCmd::nW);\n        continue;\n      }\n\n      if (*flag == 'x') {\n        curr_lambda(AclCmd::nX);\n        continue;\n      }\n\n      goto error_label;\n    }\n\n    if (*flag == '+') {\n      ++flag;\n\n      if (*flag == 'd') {\n        curr_lambda(AclCmd::pD);\n        continue;\n      }\n\n      if (*flag == 'u') {\n        curr_lambda(AclCmd::pU);\n        continue;\n      }\n    }\n\n    goto error_label;\n  }\n\n  // Set the mask of flags which are going to be added or removed\n  mAddRule = ((add_ret == 0) ? 0 : ret & add_ret);\n  mRmRule  = ((rm_ret == 0) ? 0 : ~ret & rm_ret);\n  return true;\nerror_label:\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Parse command line (modification) rule given by the client\n//------------------------------------------------------------------------------\nbool AclCmd::ParseRule(const std::string& input)\n{\n  size_t pos_del_first, pos_del_last, pos_equal;\n  pos_del_first = input.find(\":\");\n  pos_del_last  = input.rfind(\":\");\n  pos_equal     = input.find(\"=\");\n  std::string id, srule;\n\n  if ((pos_del_first == pos_del_last) && (pos_equal != std::string::npos)) {\n    // u:id=rw+x | g:id=rw+x\n    mSet = true;\n    // Check if id and rule are correct\n    id = std::string(input.begin(), input.begin() + pos_equal);\n\n    if (!CheckCorrectId(id)) {\n      return false;\n    }\n\n    // Convert it to numeric format, add dummy \":r\" and then remove it so that\n    // the format is what ConvertIds expects\n    id += \":r\";\n\n    if (Acl::ConvertIds(id, false)) {\n      return false;\n    }\n\n    id = id.erase(id.rfind(':'));\n    mId = id;\n    eos_info(\"mId=%s\", mId.c_str());\n    srule = std::string(input.begin() + pos_equal + 1, input.end());\n\n    if (!GetRuleBitmask(srule, mSet)) {\n      mErr = \"error: failed to get input rule as bitmask\";\n      return false;\n    }\n  } else {\n    if ((pos_del_first != pos_del_last) &&\n        (pos_del_first != std::string::npos) &&\n        (pos_del_last  != std::string::npos)) {\n      mSet = false;\n      // u:id:+rw  | g:id:rw+x\n      // Check if id and rule are correct\n      id = std::string(input.begin(), input.begin() + pos_del_last);\n\n      if (!CheckCorrectId(id)) {\n        mErr = \"error: input rule has incorrect format for id\";\n        return false;\n      }\n\n      // Convert it to numeric format, add dummy \":r\" and then remove it so that\n      // the format is what ConvertIds expects\n      id += \":r\";\n\n      if (Acl::ConvertIds(id, false)) {\n        return false;\n      }\n\n      id = id.erase(id.rfind(':'));\n      mId = id;\n      srule = std::string(input.begin() + pos_del_last + 1, input.end());\n\n      if (!GetRuleBitmask(srule, mSet)) {\n        mErr = \"error: failed to get input rule as bitmask\";\n        return false;\n      }\n    } else {\n      return false;\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Check if id has the correct format\n//------------------------------------------------------------------------------\nbool\nAclCmd::CheckCorrectId(const std::string& id) const\n{\n  std::string allowed_chars =\n    \"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_-\";\n\n  if ((id.at(0) == 'u' && id.at(1) == ':') ||\n      (id.at(0) == 'k' && id.at(1) == ':') ||\n      (id.at(0) == 'g' && id.at(1) == ':')) {\n    return id.find_first_not_of(allowed_chars, 2) == std::string::npos;\n  }\n\n  if (id.find(\"egroup\") == 0 && id.at(6) == ':') {\n    return id.find_first_not_of(allowed_chars, 7) == std::string::npos;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Apply client modification rule(s) to the acls of the current entry\n//------------------------------------------------------------------------------\nvoid AclCmd::ApplyRule(RuleMap& rules, size_t pos)\n{\n  unsigned long temp_rule = 0;\n\n  if (!mSet) {\n    auto it = std::find_if(rules.begin(),\n                           rules.end(),\n    [&](const Rule & rule) -> bool {\n      return rule.first == mId;\n    });\n\n    if (it != rules.end()) {\n      temp_rule = it->second;\n    }\n  }\n\n  if (mAddRule != 0) {\n    temp_rule = temp_rule | mAddRule;\n  }\n\n  if (mRmRule != 0) {\n    temp_rule = temp_rule & (~mRmRule);\n  }\n\n  if (pos != 0) {\n    auto [it, err] = get_iterator(rules, pos);\n\n    if (err != 0) {\n      mErr = \"Invalid position of rule, errc=\" + std::to_string(err);\n    }\n\n    insert_or_assign(rules, mId, temp_rule, it, true);\n    return;\n  }\n\n  insert_or_assign(rules, mId, temp_rule);\n}\n\n//------------------------------------------------------------------------------\n// Generate acl string representation from a rule map\n//------------------------------------------------------------------------------\nstd::string\nAclCmd::GenerateAclString(const RuleMap& rmap)\n{\n  std::string ret = \"\";\n\n  for (const auto& elem : rmap) {\n    if (elem.second != 0) {\n      ret += elem.first + \":\" + AclCmd::AclBitmaskToString(elem.second) + \",\";\n    }\n  }\n\n  // Remove last ','\n  if (ret != \"\") {\n    ret = ret.substr(0, ret.size() - 1);\n  }\n\n  return ret;\n}\n\n//------------------------------------------------------------------------------\n// Convert ACL bitmask to string representation\n//------------------------------------------------------------------------------\nstd::string\nAclCmd::AclBitmaskToString(const unsigned long int in)\n{\n  std::string ret = \"\";\n\n  if (in & AclCmd::R) {\n    ret.append(\"r\");\n  }\n\n  if (in & AclCmd::W) {\n    ret.append(\"w\");\n  }\n\n  if (in & AclCmd::WO) {\n    ret.append(\"wo\");\n  }\n\n  if (in & AclCmd::X) {\n    ret.append(\"x\");\n  }\n\n  if (in & AclCmd::SysAcl) {\n    ret.append(\"A\");\n  }\n\n  if (in & AclCmd::SysAttr) {\n    ret.append(\"X\");\n  }\n\n  if (in & AclCmd::Token) {\n    ret.append(\"t\");\n  }\n\n  if (in & AclCmd::M) {\n    ret.append(\"m\");\n  }\n\n  if (in & AclCmd::nM) {\n    ret.append(\"!m\");\n  }\n\n  if (in & AclCmd::nD) {\n    ret.append(\"!d\");\n  }\n\n  if (in & AclCmd::pD) {\n    ret.append(\"+d\");\n  }\n\n  if (in & AclCmd::nU) {\n    ret.append(\"!u\");\n  }\n\n  if (in & AclCmd::pU) {\n    ret.append(\"+u\");\n  }\n\n  if (in & AclCmd::Q) {\n    ret.append(\"q\");\n  }\n\n  if (in & AclCmd::C) {\n    ret.append(\"c\");\n  }\n\n  if (in & AclCmd::A) {\n    ret.append(\"a\");\n  }\n\n  if (in & AclCmd::nR) {\n    ret.append(\"!r\");\n  }\n\n  if (in & AclCmd::nW) {\n    ret.append(\"!w\");\n  }\n\n  if (in & AclCmd::nX) {\n    ret.append(\"!x\");\n  }\n\n  return ret;\n}\n\nstd::pair<int, size_t>\nAclCmd::GetRulePosition(size_t rule_map_sz, size_t rule_pos)\n{\n  std::pair<int, size_t> result {0, 0};\n\n  // Trivial case, nothing is set\n  if (!rule_map_sz && !rule_pos) {\n    return result;\n  }\n\n  if (!rule_map_sz) {\n    // Only valid case here is that the client asks the first position!\n    if (rule_pos != 1) {\n      result.first = EINVAL;\n    }\n  }\n\n  if (rule_map_sz && rule_pos) {\n    if (rule_pos > rule_map_sz) {\n      result.first = EINVAL;\n    }\n\n    result.second = rule_pos;\n  }\n\n  return result;\n}\n\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/AclCmd.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file AclCmd.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"mgm/proc/ProcCommand.hh\"\n#include \"proto/Acl.pb.h\"\n#include <list>\n#include <optional>\n\n\nEOSMGMNAMESPACE_BEGIN\n\ntypedef std::pair<std::string, unsigned long> Rule;\n//typedef std::unordered_map<std::string, unsigned long> RuleMap;\n// We use a list as we need to be able to insert at any position\ntypedef std::list<Rule> RuleMap;\n\ntemplate <typename C, typename K>\ntypename C::iterator key_position(C& c, const K& k)\n{\n  return std::find_if(c.begin(),\n                      c.end(),\n  [&k](const typename C::value_type & val)->bool {\n    return k == val.first;\n  });\n}\n\ntemplate <typename C, typename K, typename V>\nvoid insert_or_assign(C& c, K&& k, V&& v)\n{\n  auto it = key_position(c, k);\n\n  if (it != c.end()) {\n    it->second = v;\n    return;\n  }\n\n  c.emplace_back(std::make_pair(std::forward<K>(k),\n                                std::forward<V>(v)));\n}\n\ntemplate <typename C, typename K, typename V,\n          typename It = typename C::iterator>\nvoid insert_or_assign(C& c, K && k, V && v, It &&\n                      pos, bool move_existing = false)\n{\n  auto it = key_position(c, k);\n\n  if (it != c.end()) {\n    if (!move_existing || it == pos) {\n      it->second = v;\n      return;\n    }\n\n    // This function currently moves an existing key to a given position too, if\n    // this is not needed, we could skip the following erase! Since Iterator is at a\n    // different position, erase this, we'll readd the key back at the last step\n    auto next_it = c.erase(it);\n\n    // In case we're demoting an element the erase would've dropped an element\n    // so the index position should be incremented!\n    if (pos != c.end() &&\n        (std::distance(c.begin(), next_it) <= std::distance(c.begin(), pos))) {\n      ++pos;\n    }\n  }\n\n  c.insert(pos, std::make_pair(std::forward<K>(k),\n                               std::forward<V>(v)));\n}\n\ntemplate <typename C>\nvoid insert_or_assign(C& c, typename C::value_type&& value)\n{\n  using first_type = typename C::value_type::first_type;\n  using second_type = typename C::value_type::second_type;\n  insert_or_assign(c,\n                   std::forward<first_type>(value.first),\n                   std::forward<second_type>(value.second));\n}\n\ntemplate <typename C>\nstd::pair<typename C::iterator, int>\nget_iterator(C& c, size_t pos)\n{\n  if (pos == 0 || pos > c.size()) {\n    return std::make_pair(c.end(), EINVAL);\n  }\n\n  auto it = c.begin();\n  std::advance(it, pos - 1);\n  return std::make_pair(it, 0);\n}\n\n//------------------------------------------------------------------------------\n//! Class AclCmd - class handling acl command from a client\n//------------------------------------------------------------------------------\nclass AclCmd: public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit AclCmd(eos::console::RequestProto&& req,\n                  eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, true), mId(), mAddRule(0),\n    mRmRule(0), mSet(false)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~AclCmd() = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behavior of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\n  //----------------------------------------------------------------------------\n  //! Generate rule map from the string representation of the acls. If there\n  //! are no acls then the rmap will be empty.\n  //!\n  //! @note Public only for testing.\n  //!\n  //! @param acl_string string containing acl\n  //! @param rmap map to be filled with acl rules\n  //----------------------------------------------------------------------------\n  static void\n  GenerateRuleMap(const std::string& acl_string, RuleMap& rmap);\n\n  //----------------------------------------------------------------------------\n  //! Check if id has the correct format i.e u:user_id or g:group_id\n  //!\n  //! @param id string containing id\n  //!\n  //! @return bool true if correct, otherwise false\n  //----------------------------------------------------------------------------\n  bool CheckCorrectId(const std::string& id) const;\n\n  //----------------------------------------------------------------------------\n  //! Get ACL rule from string by creating a pair of identifier for the ACL and\n  //! the bitmask representation.\n  //!\n  //! @param in ACL string\n  //!\n  //! @return std::optional containing ACL identifier (ie. u:user1 or g:group1)\n  //! and the bitmask representation\n  //----------------------------------------------------------------------------\n  static std::optional<Rule> GetRuleFromString(const std::string& in);\n\n  //----------------------------------------------------------------------------\n  //! Convert acl modification command into bitmask rule format\n  //!\n  //! @param input string containing the modifications of the acls\n  //! @param set if true \"set\" mode is active, otherwise false\n  //!\n  //! @return bool true if conversion successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool GetRuleBitmask(const std::string& input, bool set = false);\n\n  //----------------------------------------------------------------------------\n  //! Generate the position to place the rule\n  //!\n  //! @param input rule_map size of the current rule_map\n  //! @param input rule_pos position to be inserted\n  //!\n  //! @return a pair of error, insert position\n  //----------------------------------------------------------------------------\n  static std::pair<int, size_t> GetRulePosition(size_t rule_map_sz,\n      size_t rule_pos);\n\n  //----------------------------------------------------------------------------\n  //! Return mAddRule result after GetRuleBitmask call.\n  //----------------------------------------------------------------------------\n  unsigned long GetAddRule()\n  {\n    return mAddRule;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return mRmRule result after GetRuleBitmask call.\n  //----------------------------------------------------------------------------\n  unsigned long  GetRmRule()\n  {\n    return mRmRule;\n  }\n\nprivate:\n  //! Enumerator defining which bit represents which acl flag.\n  enum ACLPos {\n    R  = 1 << 0,       // 1    -  r\n    W  = 1 << 1,       // 2    -  w\n    X  = 1 << 2,       // 4    -  x\n    M  = 1 << 3,       // 8    -  m\n    nM = 1 << 4,       // 16   - !m\n    nD = 1 << 5,       // 32   - !d\n    pD = 1 << 6,       // 64   - +d\n    nU = 1 << 7,       // 128  - !u\n    pU = 1 << 8,       // 256  - +u\n    Q  = 1 << 9,       // 512  -  q\n    C  = 1 << 10,      // 1024 -  c\n    WO = 1 << 11,      // 2048 - wo\n    nR = 1 << 12,      // 4096 - !r\n    nW = 1 << 13,      // 8192 - !w\n    nX = 1 << 14,      //16384 - !x\n    A  = 1 << 15,      //32768 -  a\n    SysAcl  = 1 << 16, //65536  - A\n    SysAttr = 1 << 17, //131072 - X\n    Token   = 1 << 18  //262144 - t\n  };\n\n  std::string mId; ///< Rule identifier extracted from command line\n  ///< ACL rule bitmasks for adding and removing\n  unsigned long mAddRule, mRmRule;\n  bool mSet; ///< Rule is set operations i.e contains =\n\n  //----------------------------------------------------------------------------\n  //! Get sys.acl and user.acl for a given namespace entry\n  //!\n  //! @param item file or container metadata object\n  //! @param acls ACL VALUE\n  //! @param sys if true return sys.acls\n  //! @param user if true return user.acls\n  //----------------------------------------------------------------------------\n  void GetAcls(eos::FileOrContainerMD& item, std::string& acls,\n               bool sys = true, bool user = true);\n\n  //----------------------------------------------------------------------------\n  //! Modify the acls for a path\n  //!\n  //! @param acl acl ProtoBuf object\n  //!\n  //! @return 0 if successful, otherwise errno\n  //----------------------------------------------------------------------------\n  int ModifyAcls(const eos::console::AclProto& acl);\n\n  //----------------------------------------------------------------------------\n  //! Generate acl string representation from a rule map\n  //!\n  //! @param rmap map of rules to be used for conversion\n  //!\n  //! @return true if conversion successful, otherwise false\n  //----------------------------------------------------------------------------\n  static std::string GenerateAclString(const RuleMap& rmap);\n\n  //----------------------------------------------------------------------------\n  //! Parse command line (modification) rule given by the client. This specifies\n  //! the modifications to be operated on the current acls of the dir(s).\n  //!\n  //! @param input string rule from command line\n  //!\n  //! @return bool true if rule is correct, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseRule(const std::string& input);\n\n  //----------------------------------------------------------------------------\n  //! Apply client modification rule(s) to the acls of the current entry\n  //!\n  //! @param rules map of acl rules for the current entry (directory)\n  //----------------------------------------------------------------------------\n  void ApplyRule(RuleMap& rules, size_t pos = 0);\n\n  //----------------------------------------------------------------------------\n  //! Convert ACL bitmask to string representation\n  //!\n  //! @param in ACL bitmask\n  //!\n  //! @return std::string representation of ACL\n  //----------------------------------------------------------------------------\n  static std::string AclBitmaskToString(const unsigned long in);\n\n  std::string mErr; ///< Command error output string\n\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Archive.cc",
    "content": "//------------------------------------------------------------------------------\n// File: Archive.cc\n// Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2014 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcCommand.hh\"\n#include \"mgm/proc/user/NewfindCmd.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/ofs/XrdMgmOfsDirectory.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/acl/Acl.hh\"\n#include \"common/SymKeys.hh\"\n#include \"common/Path.hh\"\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"mgm/zmq/ZMQ.hh\"\n#include <XrdCl/XrdClCopyProcess.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n#include <iomanip>\n\nEOSMGMNAMESPACE_BEGIN\n\nstatic const std::string ARCH_INIT = \".archive.init\";\nstatic const std::string ARCH_PUT_DONE = \".archive.put.done\";\nstatic const std::string ARCH_PUT_ERR = \".archive.put.err\";\nstatic const std::string ARCH_GET_DONE = \".archive.get.done\";\nstatic const std::string ARCH_GET_ERR = \".archive.get.err\";\nstatic const std::string ARCH_PURGE_DONE = \".archive.purge.done\";\nstatic const std::string ARCH_PURGE_ERR = \".archive.purge.err\";\nstatic const std::string ARCH_DELETE_ERR = \".archive.delete.err\";\nstatic const std::string ARCH_LOG = \".archive.log\";\n\n\n//------------------------------------------------------------------------------\n// Archive command\n//------------------------------------------------------------------------------\nint\nProcCommand::Archive()\n{\n  struct stat statinfo;\n  std::ostringstream cmd_json;\n  std::string option = (pOpaque->Get(\"mgm.archive.option\") ?\n                        pOpaque->Get(\"mgm.archive.option\") : \"\");\n\n  // For listing we don't need an EOS path\n  if (mSubCmd == \"transfers\") {\n    if (option.empty()) {\n      stdErr = \"error: need to provide the archive listing type\";\n      retc = EINVAL;\n    } else {\n      cmd_json << \"{\\\"cmd\\\": \" << \"\\\"\" << mSubCmd.c_str() << \"\\\", \"\n               << \"\\\"opt\\\": \" <<  \"\\\"\" << option << \"\\\", \"\n               << \"\\\"uid\\\": \" << \"\\\"\" << pVid->uid << \"\\\", \"\n               << \"\\\"gid\\\": \" << \"\\\"\" << pVid->gid << \"\\\" \"\n               << \"}\";\n    }\n  } else if (mSubCmd == \"kill\") {\n    if (option.empty()) {\n      stdErr = \"error: need to provide a job_uuid for kill\";\n      retc = EINVAL;\n    } else {\n      cmd_json << \"{\\\"cmd\\\": \" << \"\\\"\" << mSubCmd.c_str() << \"\\\", \"\n               << \"\\\"opt\\\": \" << \"\\\"\" << option << \"\\\", \"\n               << \"\\\"uid\\\": \" << \"\\\"\" << pVid->uid << \"\\\", \"\n               << \"\\\"gid\\\": \" << \"\\\"\" << pVid->gid << \"\\\" \"\n               << \"}\";\n    }\n  } else if (mSubCmd == \"list\") {\n    XrdOucString spath = pOpaque->Get(\"mgm.archive.path\");\n    const char* inpath = spath.c_str();\n    NAMESPACEMAP;\n    PROC_BOUNCE_ILLEGAL_NAMES;\n    PROC_BOUNCE_NOT_ALLOWED;\n    eos::common::Path cPath(path);\n    spath = cPath.GetPath();\n\n    // Make sure the EOS directory path ends with '/'\n    if (spath[spath.length() - 1] != '/') {\n      spath += '/';\n    }\n\n    // First get the list of the ongoing transfers\n    cmd_json << \"{\\\"cmd\\\": \\\"transfers\\\", \"\n             << \"\\\"opt\\\": \\\"all\\\", \"\n             << \"\\\"uid\\\": \\\"\" << pVid->uid << \"\\\", \"\n             << \"\\\"gid\\\": \\\"\" << pVid->gid << \"\\\" \"\n             << \"}\";\n  } else {\n    // Archive/backup transfer operation\n    XrdOucString spath = pOpaque->Get(\"mgm.archive.path\");\n    const char* inpath = spath.c_str();\n    NAMESPACEMAP;\n    PROC_BOUNCE_ILLEGAL_NAMES;\n    PROC_BOUNCE_NOT_ALLOWED;\n    eos::common::Path cPath(path);\n    spath = cPath.GetPath();\n\n    // Make sure the EOS directory path ends with '/'\n    if (spath[spath.length() - 1] != '/') {\n      spath += '/';\n    }\n\n    // Check archive permissions\n    if (!ArchiveCheckAcl(spath.c_str())) {\n      stdErr = \"error: failed archive ACL check\";\n      retc = EPERM;\n      return SFS_OK;\n    }\n\n    std::ostringstream dir_stream;\n    dir_stream << \"root://\" << gOFS->MgmOfsAlias.c_str() << \"/\" << spath.c_str();\n    std::string dir_url = dir_stream.str();\n\n    // Check that the requested path exists and is a directory\n    if (gOFS->_stat(spath.c_str(), &statinfo, *mError, *pVid)) {\n      stdErr = \"error: requested path does not exit\";\n      retc = EINVAL;\n      return SFS_OK;\n    }\n\n    if (!S_ISDIR(statinfo.st_mode)) {\n      stdErr = \"error:archive path is not a directory\";\n      retc = EINVAL;\n      return SFS_OK;\n    }\n\n    // Used for creating/deleting a file in /eos/.../proc/archive with the same\n    // name as the inode value. Used to provide archive fast find functionality.\n    uint64_t fid = statinfo.st_ino;\n    // Create vector containing the paths to all the possible special files\n    std::ostringstream oss;\n    std::vector<std::string> vect_paths;\n    std::vector<std::string> vect_files = {\n      ARCH_INIT, ARCH_PUT_DONE, ARCH_PUT_ERR, ARCH_GET_DONE, ARCH_GET_ERR,\n      ARCH_PURGE_DONE, ARCH_PURGE_ERR, ARCH_DELETE_ERR, ARCH_LOG\n    };\n\n    for (auto it = vect_files.begin(); it != vect_files.end(); ++it) {\n      oss.str(\"\");\n      oss.clear();\n      oss << spath.c_str() << *it;\n      vect_paths.push_back(oss.str());\n    }\n\n    if (mSubCmd == \"create\") {\n      if (!gOFS->MgmArchiveDstUrl.length()) {\n        eos_err(\"archive destination not configured for this EOS instance\");\n        stdErr = \"error: archive destination not configured for this EOS instance\";\n        retc = EINVAL;\n        return SFS_OK;\n      }\n\n      if (!gOFS->MgmOfsAlias.length() || (gOFS->MgmOfsAlias == \"localhost\")) {\n        eos_err(\"EOS_MGM_ALIAS is empty or points to localhost\");\n        stdErr = (\"error: EOS_MGM_ALIAS needs to be set to a FQDN for the \"\n                  \"archive command to work\");\n        retc = EINVAL;\n        return SFS_OK;\n      }\n\n      // Build the destination dir by using the uid/gid of the user triggering\n      // the archive operation e.g root:// ... //some/dir/gid1/uid1/\n      std::string dir_sha256 = eos::common::SymKey::HexSha256(spath.c_str());\n      std::ostringstream dst_oss;\n      dst_oss << gOFS->MgmArchiveDstUrl.c_str() << dir_sha256 << '/';\n      std::string surl = dst_oss.str();\n      // Make sure the destination directory does not exist\n      XrdCl::URL url(surl);\n      XrdCl::FileSystem fs(url);\n      XrdCl::StatInfo* st_info = 0;\n      XrdCl::XRootDStatus status = fs.Stat(url.GetPath(), st_info);\n\n      if (status.IsOK()) {\n        stdErr = \"error: archive dst=\";\n        stdErr += surl.c_str();\n        stdErr += \" already exists\";\n        eos_err(\"%s\", stdErr.c_str());\n        retc = EIO;\n        return SFS_OK;\n      }\n\n      if (MakeSubTreeImmutable(spath.c_str(), vect_files)) {\n        return retc;\n      }\n\n      ArchiveCreate(spath.c_str(), surl, fid);\n      return SFS_OK;\n    } else if ((mSubCmd == \"put\") ||\n               (mSubCmd == \"get\") ||\n               (mSubCmd == \"purge\") ||\n               (mSubCmd == \"delete\")) {\n      std::string arch_url = dir_url;\n\n      if (option == \"r\") {\n        // Retry failed operation\n        option = \"retry\";\n        std::string arch_err = spath.c_str();\n\n        if (mSubCmd == \"put\") {\n          arch_err += ARCH_PUT_ERR;\n          arch_url += ARCH_PUT_ERR;\n        } else if (mSubCmd == \"get\") { // get retry\n          arch_err += ARCH_GET_ERR;\n          arch_url += ARCH_GET_ERR;\n        } else if (mSubCmd == \"purge\") {\n          arch_err += ARCH_PURGE_ERR;\n          arch_url += ARCH_PURGE_ERR;\n        } else if (mSubCmd == \"delete\") {\n          arch_err += ARCH_DELETE_ERR;\n          arch_url += ARCH_DELETE_ERR;\n        }\n\n        if (gOFS->_stat(arch_err.c_str(), &statinfo, *mError, *pVid)) {\n          stdErr = \"error: no failed \";\n          stdErr += mSubCmd;\n          stdErr += \" file in directory: \";\n          stdErr += spath.c_str();\n          retc = EINVAL;\n        }\n      } else {\n        // Check that the init/put archive file exists\n        option = \"\";\n        std::string arch_path = spath.c_str();\n\n        if (mSubCmd == \"put\") { // put\n          arch_path += ARCH_INIT;\n          arch_url += ARCH_INIT;\n\n          if (gOFS->_stat(arch_path.c_str(), &statinfo, *mError, *pVid)) {\n            stdErr = \"error: no archive init file in directory: \";\n            stdErr += spath.c_str();\n            retc = EINVAL;\n          }\n        } else if (mSubCmd == \"get\") { // get\n          arch_path += ARCH_PURGE_DONE;\n          arch_url += ARCH_PURGE_DONE;\n\n          if (gOFS->_stat(arch_path.c_str(), &statinfo, *mError, *pVid)) {\n            stdErr = \"error: no archive purge file in directory: \";\n            stdErr += spath.c_str();\n            retc = EINVAL;\n          }\n        } else if (mSubCmd == \"purge\") { // purge\n          arch_path += ARCH_PUT_DONE;\n\n          if (gOFS->_stat(arch_path.c_str(), &statinfo, *mError, *pVid)) {\n            arch_path = spath.c_str();\n            arch_path += ARCH_GET_DONE;\n\n            if (gOFS->_stat(arch_path.c_str(), &statinfo, *mError, *pVid)) {\n              stdErr = \"error: purge can be done only after a successful \" \\\n                       \"get or put operation\";\n              retc = EINVAL;\n            } else {\n              arch_url += ARCH_GET_DONE;\n            }\n          } else {\n            arch_url += ARCH_PUT_DONE;\n          }\n        } else if (mSubCmd == \"delete\") { // delete\n          if (pVid->uid == 0 && ((pVid->prot == \"unix\") || (pVid->prot == \"sss\"))) {\n            bool found = false;\n            std::string arch_fn;\n\n            // Check that archive exists in the current directory\n            for (auto it = vect_files.begin(); it != vect_files.end(); ++it) {\n              arch_fn = spath.c_str();\n              arch_fn += *it;\n\n              if ((*it != ARCH_LOG) &&\n                  (!gOFS->_stat(arch_fn.c_str(), &statinfo, *mError, *pVid))) {\n                arch_url += *it;\n                found = true;\n                break;\n              }\n            }\n\n            if (!found) {\n              stdErr = \"error: current directory is not archived\";\n              retc = EINVAL;\n            } else {\n              // Delete the entry in /eos/.../proc/archive/\n              std::ostringstream proc_fn;\n              proc_fn << gOFS->MgmProcArchivePath << '/' << fid;\n\n              if (gOFS->_rem(proc_fn.str().c_str(), *mError, *pVid)) {\n                stdErr = \"warning: unable to remove archive id from /proc fast find\";\n              }\n            }\n          } else {\n            stdErr = \"error: permission denied, only admin can delete an archive\";\n            retc = EPERM;\n          }\n        }\n      }\n\n      cmd_json << \"{\\\"cmd\\\": \" << \"\\\"\" << mSubCmd.c_str() << \"\\\", \"\n               << \"\\\"src\\\": \" << \"\\\"\" << arch_url << \"\\\", \"\n               << \"\\\"opt\\\": \" << \"\\\"\" << option  << \"\\\", \"\n               << \"\\\"uid\\\": \" << \"\\\"\" << pVid->uid << \"\\\", \"\n               << \"\\\"gid\\\": \" << \"\\\"\" << pVid->gid << \"\\\" \"\n               << \"}\";\n    } else {\n      stdErr = \"error: operation not supported, needs to be one of the following: \"\n               \"create, put, get or list\";\n      retc = EINVAL;\n    }\n  }\n\n  // Send request to archiver process if no error occured\n  if (!retc) {\n    // Do formatting if this is a listing command\n    if ((mSubCmd == \"list\") || (mSubCmd == \"transfers\")) {\n      ArchiveFormatListing(cmd_json.str());\n    } else {\n      retc = ArchiveExecuteCmd(cmd_json.str());\n    }\n  }\n\n  eos_debug(\"retc=%i, stdOut=%s, stdErr=%s\", retc, stdOut.c_str(),\n            stdErr.c_str());\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Format listing output. Includes combining the information that we get\n// from the archiver daemon with the list of pending transfers at the MGM.\n//------------------------------------------------------------------------------\nvoid\nProcCommand::ArchiveFormatListing(const std::string& cmd_json)\n{\n  // Parse response from the archiver regarding ongoing transfers\n  size_t pos;\n  size_t max_path_len = 64;\n  std::string entry, token, key, value;\n  std::map<std::string, std::string> map_info;\n  std::vector<ArchDirStatus> tx_dirs;\n  std::vector<ArchDirStatus> bkps;\n\n  // For \"transfers\" command now list of pending backups to avoid false reporting\n  if (mSubCmd == \"transfers\") {\n    bkps = gOFS->GetPendingBkps();\n  }\n\n  // Get list of ongoing transfers from the archiver daemon\n  if (ArchiveExecuteCmd(cmd_json)) {\n    return;\n  }\n\n  std::istringstream iss(stdOut.c_str());\n  stdOut = \"\";\n\n  while (std::getline(iss, entry, '\\n')) {\n    // Entry has the following format:\n    //date=%s,uuid=%s,path=%s,op=%s,status=%s\n    entry += ','; // for easier parsing\n\n    while ((pos = entry.find(',')) != std::string::npos) {\n      token = entry.substr(0, pos);\n      entry = entry.substr(pos + 1);\n      pos = token.find('=');\n\n      if (pos == std::string::npos) {\n        stdErr = \"error: unexpected archive response format\";\n        retc = EINVAL;\n        return;\n      }\n\n      key = token.substr(0, pos);\n      value = token.substr(pos + 1);\n      map_info[key] = value;\n    }\n\n    if (map_info.size() != 5) {\n      stdErr = \"error: incomplete archive response information\";\n      retc = EINVAL;\n      return;\n    }\n\n    tx_dirs.emplace_back(map_info[\"date\"], map_info[\"uuid\"],\n                         map_info[\"path\"], map_info[\"op\"],\n                         map_info[\"status\"]);\n\n    // Save max path lenght for formatting purposes\n    if (max_path_len < map_info[\"path\"].length()) {\n      max_path_len = map_info[\"path\"].length();\n    }\n\n    map_info.clear();\n  }\n\n  // For the list command print only information about the existing archived\n  // directories and their status\n  if (mSubCmd == \"list\") {\n    // Create the table for displaying archive status informations\n    std::string spath = (pOpaque->Get(\"mgm.archive.path\") ?\n                         pOpaque->Get(\"mgm.archive.path\") : \"/\");\n    std::vector<ArchDirStatus> archive_dirs = ArchiveGetDirs(spath.c_str());\n    ArchiveUpdateStatus(archive_dirs, tx_dirs, max_path_len);\n    std::vector<size_t> col_size = {30, max_path_len + 5, 16};\n    std::ostringstream oss;\n    oss << '|' << std::setfill('-') << std::setw(col_size[0] + 1)\n        << '|' << std::setw(col_size[1] + 1)\n        << '|' << std::setw(col_size[2] + 1)\n        << '|' << std::setfill(' ');\n    std::string line = oss.str();\n    oss.str(\"\");\n    oss.clear();\n    // Add table header\n    oss << line << std::endl\n        << '|' << std::setw(col_size[0]) << std::setiosflags(std::ios_base::left)\n        << \"Creation date\"\n        << '|' << std::setw(col_size[1]) << std::setiosflags(std::ios_base::left)\n        << \"Path\"\n        << '|' << std::setw(col_size[2]) << std::setiosflags(std::ios_base::left)\n        << \"Status\"\n        << '|'\n        << std::endl << line << std::endl;\n\n    for (auto dir = archive_dirs.begin(); dir != archive_dirs.end(); ++dir) {\n      oss << '|' << std::setw(col_size[0]) << std::setiosflags(std::ios_base::left)\n          << dir->mTime\n          << '|' << std::setw(col_size[1]) << std::setiosflags(std::ios_base::left)\n          << dir->mPath\n          << '|' << std::setw(col_size[2]) << std::setiosflags(std::ios_base::left)\n          << dir->mStatus\n          << '|'\n          << std::endl << line << std::endl;\n    }\n\n    stdOut = oss.str().c_str();\n  } else if (mSubCmd == \"transfers\") {\n    // Remove those pending backup transfers that were submitted to the archive\n    // daemon in the meantime. We need this as getting the pending backups and\n    // the ongoing transfers from the archiver is not atomic.\n    bool skip;\n\n    for (auto pending = bkps.begin(); pending != bkps.end(); /*empty*/) {\n      skip = false;\n\n      for (auto tx = tx_dirs.begin(); tx != tx_dirs.end(); ++tx) {\n        if (tx->mPath == pending->mPath) {\n          bkps.erase(pending++);\n          skip = true;\n          break;\n        }\n      }\n\n      if (!skip) {\n        if (max_path_len < pending->mPath.length()) {\n          max_path_len = pending->mPath.length();\n        }\n\n        // Advance iterator\n        pending++;\n      }\n    }\n\n    // For \"transfers\" command print status of onging transfers based on the reply\n    // from the archive daemon\n    std::vector<size_t> col_size = {26, max_path_len + 7, 16, 24};\n    std::ostringstream oss;\n    oss << '|' << std::setfill('-') << std::setw(col_size[0] + 1)\n        << '|' << std::setw(col_size[1] + 1)\n        << '|' << std::setw(col_size[2] + 1)\n        << '|' << std::setw(col_size[3] + 1)\n        << '|' << std::setfill(' ');\n    std::string line = oss.str();\n    oss.str(\"\");\n    oss.clear();\n    // Add table header\n    oss << line << std::endl\n        << '|' << std::setw(col_size[0]) << std::setiosflags(std::ios_base::left)\n        << \"Start time\"\n        << '|' << std::setw(col_size[1]) << std::setiosflags(std::ios_base::left)\n        << \"Transfer info\"\n        << '|' << std::setw(col_size[2]) << std::setiosflags(std::ios_base::left)\n        << \"Operation\"\n        << '|' << std::setw(col_size[3]) << std::setiosflags(std::ios_base::left)\n        << \"Status\"\n        << '|'\n        << std::endl << line << std::endl;\n\n    for (auto dir = tx_dirs.begin(); dir != tx_dirs.end(); ++dir) {\n      oss << '|' << std::setw(col_size[0]) << std::setiosflags(std::ios_base::left)\n          << dir->mTime\n          << '|' << std::setw(col_size[1]) << std::setiosflags(std::ios_base::left)\n          << (\"Uuid: \" + dir->mUuid)\n          << '|' << std::setw(col_size[2]) << std::setiosflags(std::ios_base::left)\n          << dir->mOp\n          << '|' << std::setw(col_size[3]) << std::setiosflags(std::ios_base::left)\n          << dir->mStatus\n          << '|'\n          << std::endl\n          << '|' << std::setw(col_size[0]) << std::setiosflags(std::ios_base::left)\n          << ' '\n          << '|' << std::setw(col_size[1]) << std::setiosflags(std::ios_base::left)\n          << (\"Path: \" + dir->mPath)\n          << '|' << std::setw(col_size[2]) << std::setiosflags(std::ios_base::left)\n          << ' '\n          << '|' << std::setw(col_size[3]) << std::setiosflags(std::ios_base::left)\n          << ' '\n          << '|'\n          << std::endl << line << std::endl;\n    }\n\n    // Append set of pending backup transfers at the MGM\n    for (auto it = bkps.begin(); it != bkps.end(); ++it) {\n      oss << '|' << std::setw(col_size[0]) << std::setiosflags(std::ios_base::left)\n          << it->mTime\n          << '|'  << std::setw(col_size[1]) << std::setiosflags(std::ios_base::left)\n          << (\"Uuid: \" + it->mUuid)\n          << '|' << std::setw(col_size[2]) << std::setiosflags(std::ios_base::left)\n          << it->mOp\n          << '|' << std::setw(col_size[3]) << std::setiosflags(std::ios_base::left)\n          << it->mStatus\n          << '|'\n          << std::endl\n          << '|' << std::setw(col_size[0]) << std::setiosflags(std::ios_base::left)\n          << ' '\n          << '|' << std::setw(col_size[1]) << std::setiosflags(std::ios_base::left)\n          << (\"Path: \" + it->mPath)\n          << '|' << std::setw(col_size[2]) << std::setiosflags(std::ios_base::left)\n          << ' '\n          << '|' << std::setw(col_size[3]) << std::setiosflags(std::ios_base::left)\n          << ' '\n          << '|'\n          << std::endl << line << std::endl;\n    }\n\n    stdOut = oss.str().c_str();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get archive status for both already archived directories as well as for dirs\n// that have ongoing transfers\n//------------------------------------------------------------------------------\nvoid\nProcCommand::ArchiveUpdateStatus(std::vector<ProcCommand::ArchDirStatus>& dirs,\n                                 std::vector<ProcCommand::ArchDirStatus>& tx_dirs,\n                                 size_t& max_path_length)\n{\n  max_path_length = 0;\n  bool found = false;\n  std::string path;\n  std::vector<std::string> vect_files = {ARCH_INIT, ARCH_PUT_DONE, ARCH_PUT_ERR,\n                                         ARCH_GET_DONE, ARCH_GET_ERR, ARCH_PURGE_ERR,\n                                         ARCH_PURGE_DONE, ARCH_DELETE_ERR\n                                        };\n  XrdSfsFileExistence exists_flag;\n  XrdOucErrInfo out_error;\n\n  for (auto dir = dirs.begin(); dir != dirs.end(); ++dir) {\n    if (max_path_length < dir->mPath.length()) {\n      max_path_length = dir->mPath.length();\n    }\n\n    found = false;\n\n    for (auto it = tx_dirs.begin(); it != tx_dirs.end(); ++it) {\n      if (dir->mPath == it->mPath) {\n        found = true;\n        break;\n      }\n    }\n\n    if (found) {\n      dir->mStatus = \"transferring\";\n    } else {\n      XrdCl::URL url(dir->mPath);\n\n      for (auto st_file = vect_files.begin(); st_file != vect_files.end();\n           ++st_file) {\n        path = url.GetPath() + \"/\" + *st_file;\n\n        if ((gOFS->_exists(path.c_str(), exists_flag, out_error) == SFS_OK) &&\n            ((exists_flag & XrdSfsFileExistIsFile) == true)) {\n          if (*st_file == ARCH_INIT) {\n            dir->mStatus = \"created\";\n          } else if (*st_file == ARCH_PUT_DONE) {\n            dir->mStatus = \"put done\";\n          } else if (*st_file == ARCH_PUT_ERR) {\n            dir->mStatus = \"put failed\";\n          } else if (*st_file == ARCH_GET_DONE) {\n            dir->mStatus = \"get done\";\n          } else if (*st_file == ARCH_GET_ERR) {\n            dir->mStatus = \"get failed\";\n          } else if (*st_file == ARCH_PURGE_DONE) {\n            dir->mStatus = \"purge done\";\n          } else if (*st_file == ARCH_PURGE_ERR) {\n            dir->mStatus = \"purge failed\";\n          } else if (*st_file == ARCH_DELETE_ERR) {\n            dir->mStatus = \"delete failed\";\n          }\n\n          break;\n        }\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get the list of files in proc/arhive whose name represents the fid of the\n// archived directory\n//------------------------------------------------------------------------------\nstd::vector<ProcCommand::ArchDirStatus>\nProcCommand::ArchiveGetDirs(const std::string& root) const\n{\n  const char* dname;\n  std::string full_path;\n  std::set<std::string> fids;\n  eos::common::VirtualIdentity root_ident = eos::common::VirtualIdentity::Root();\n  std::vector<ArchDirStatus> dirs;\n  XrdMgmOfsDirectory proc_dir;\n  int retc = proc_dir._open(gOFS->MgmProcArchivePath.c_str(),\n                            root_ident, static_cast<const char*>(0));\n\n  if (retc) {\n    return dirs;\n  }\n\n  while ((dname = proc_dir.nextEntry())) {\n    if (dname[0] != '.') {\n      fids.insert(dname);\n    }\n  }\n\n  proc_dir.close();\n  struct timespec mtime;\n  std::string sdate;\n  std::shared_ptr<eos::IContainerMD> cmd;\n  eos::IContainerMD::id_t id;\n  {\n    eos::common::RWMutexReadLock nsLock(gOFS->eosViewRWMutex);\n\n    for (auto fid = fids.begin(); fid != fids.end(); ++fid) {\n      // Convert string id to ContainerMD:id_t\n      id = std::stoull(*fid);\n\n      try {\n        cmd = gOFS->eosDirectoryService->getContainerMD(id);\n        full_path = gOFS->eosView->getUri(cmd.get());\n\n        // If archive directory is in the currently searched subtree\n        if (full_path.find(root) == 0) {\n          cmd->getMTime(mtime);\n          sdate = asctime(localtime(&mtime.tv_sec));\n          sdate.erase(sdate.find('\\n')); // trim string\n          dirs.emplace_back(sdate, \"N/A\", full_path, \"N/A\", \"unknown\");\n        }\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        eos_static_err(\"fxid=%08llx errno=%d msg=\\\"%s\\\"\\n\",\n                       id, e.getErrno(), e.getMessage().str().c_str());\n      }\n    }\n  }\n  return dirs;\n}\n\n//------------------------------------------------------------------------------\n// Send command to archive daemon and collect the response\n//------------------------------------------------------------------------------\nint\nProcCommand::ArchiveExecuteCmd(const std::string& cmd)\n{\n  int retc = 0;\n  int sock_linger = 0;\n  zmq::socket_t socket(*(gOFS->mZmqContext), ZMQ_REQ);\n  int sock_timeout = 1500; // 1,5s\n  socket.set(zmq::sockopt::rcvtimeo, sock_timeout);\n  socket.set(zmq::sockopt::linger, sock_linger);\n\n  try {\n    socket.connect(gOFS->mArchiveEndpoint.c_str());\n  } catch (zmq::error_t& zmq_err) {\n    eos_static_err(\"connect to archiver failed:% \", zmq_err.what());\n    stdErr = \"error: connect to archiver failed\";\n    retc = EINVAL;\n  }\n\n  if (!retc) {\n    zmq::message_t msg((void*)cmd.c_str(), cmd.length(), NULL);\n    zmq::send_flags sf = zmq::send_flags::none;\n    zmq::recv_flags rf = zmq::recv_flags::none;\n\n    try {\n      if (!socket.send(msg, sf)) {\n        stdErr = \"error: send request to archiver\";\n        retc = EINVAL;\n      } else {\n        zmq::recv_result_t rc = socket.recv(msg, rf);\n\n        if (!rc.has_value()) {\n          stdErr = \"error: no response from archiver\";\n          retc = EINVAL;\n        } else {\n          // Parse response from the archiver\n          XrdOucString msg_str((const char*) msg.data(), msg.size());\n          //eos_info(\"Msg_str:%s\", msg_str.c_str());\n          std::istringstream iss(msg_str.c_str());\n          std::string status, line, response;\n          iss >> status;\n\n          // Discard whitespaces from the beginning\n          while (getline(iss >> std::ws, line)) {\n            response += line;\n\n            if (iss.good()) {\n              response += '\\n';\n            }\n          }\n\n          if (status == \"OK\") {\n            stdOut = response.c_str();\n          } else if (status == \"ERROR\") {\n            stdErr = response.c_str();\n            retc = EINVAL;\n          } else {\n            stdErr = \"error: unknown response format from archiver\";\n            retc = EINVAL;\n          }\n        }\n      }\n    } catch (zmq::error_t& zmq_err) {\n      stdErr = \"error: timeout getting response from archiver, msg: \";\n      stdErr += zmq_err.what();\n      retc = EINVAL;\n    }\n  }\n\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Check if the current user has the necessary permissions to do an archiving\n// operation\n//------------------------------------------------------------------------------\nbool\nProcCommand::ArchiveCheckAcl(const std::string& arch_dir) const\n{\n  bool is_allowed = false;\n  eos::IContainerMD::XAttrMap attrmap;\n  // Load evt. the attributes\n  gOFS->_attr_ls(arch_dir.c_str(), *mError, *pVid, 0, attrmap);\n  // ACL and permission check\n  Acl acl(arch_dir.c_str(), *mError, *pVid, attrmap);\n  eos_info(\"acl=%d a=%d egroup=%d mutable=%d\", acl.HasAcl(), acl.CanArchive(),\n           acl.HasEgroup(), acl.IsMutable());\n\n  if (pVid->uid) {\n    is_allowed = acl.CanArchive();\n  } else {\n    is_allowed = true;\n  }\n\n  return is_allowed;\n}\n\n\n//------------------------------------------------------------------------------\n// Create archive file.\n//------------------------------------------------------------------------------\nvoid\nProcCommand::ArchiveCreate(const std::string& arch_dir,\n                           const std::string& dst_url, uint64_t fid)\n{\n  int num_dirs = 0;\n  int num_files = 0;\n  // Create the output directory if necessary and open the temporary file in\n  // which we construct the archive file\n  std::ostringstream oss;\n  oss << gOFS->TmpStorePath << \"/archive.\" << XrdSysThread::ID();\n  std::string arch_fn = oss.str();\n  std::fstream arch_ofs(arch_fn.c_str(), std::fstream::out);\n\n  if (!arch_ofs.is_open()) {\n    eos_static_err(\"msg=\\\"failed to open local archive file\\\" path=\\\"%s\\\"\",\n                   arch_fn.c_str());\n    stdErr = \"failed to open archive file at MGM\";\n    retc = EIO;\n    return;\n  }\n\n  // Write archive JSON header leaving blank the fields for the number of\n  // files/dirs and timestamp which will be filled in later on\n  arch_ofs << \"{\"\n           << \"\\\"src\\\": \\\"\" << \"root://\" << gOFS->MgmOfsAlias << \"/\" << arch_dir << \"\\\", \"\n           << \"\\\"dst\\\": \\\"\" << dst_url << \"\\\", \"\n           << \"\\\"svc_class\\\": \\\"\" << gOFS->MgmArchiveSvcClass << \"\\\", \"\n           << \"\\\"dir_meta\\\": [\\\"uid\\\", \\\"gid\\\", \\\"mode\\\", \\\"attr\\\"], \"\n           << \"\\\"file_meta\\\": [\\\"size\\\", \\\"mtime\\\", \\\"ctime\\\", \\\"uid\\\", \\\"gid\\\", \"\n           << \"\\\"mode\\\", \\\"xstype\\\", \\\"xs\\\"], \"\n           << \"\\\"uid\\\": \\\"\" << pVid->uid << \"\\\", \"\n           << \"\\\"gid\\\": \\\"\" << pVid->gid << \"\\\", \"\n           << \"\\\"timestamp\\\": \" << std::setw(10) << \"\" << \", \"\n           << \"\\\"num_dirs\\\": \" << std::setw(10) << \"\" << \", \"\n           << \"\\\"num_files\\\": \" << std::setw(10) << \"\"\n           << \"}\" << std::endl;\n\n  // Add directories info\n  if (ArchiveAddEntries(arch_dir, arch_ofs, num_dirs, false)) {\n    MakeSubTreeMutable(arch_dir);\n    arch_ofs.close();\n    unlink(arch_fn.c_str());\n    return;\n  }\n\n  // Add files info\n  if (ArchiveAddEntries(arch_dir, arch_ofs, num_files, true) ||\n      (num_files == 0)) {\n    MakeSubTreeMutable(arch_dir);\n    arch_ofs.close();\n    unlink(arch_fn.c_str());\n    return;\n  }\n\n  // Rewind the stream and update the header with the number of files and dirs\n  num_dirs--; // don't count current dir\n  arch_ofs.seekp(0);\n  arch_ofs << \"{\"\n           << \"\\\"src\\\": \\\"\" << \"root://\" << gOFS->MgmOfsAlias << \"/\" << arch_dir << \"\\\", \"\n           << \"\\\"dst\\\": \\\"\" << dst_url << \"\\\", \"\n           << \"\\\"svc_class\\\": \\\"\" << gOFS->MgmArchiveSvcClass << \"\\\", \"\n           << \"\\\"dir_meta\\\": [\\\"uid\\\", \\\"gid\\\", \\\"mode\\\", \\\"attr\\\"], \"\n           << \"\\\"file_meta\\\": [\\\"size\\\", \\\"mtime\\\", \\\"ctime\\\", \\\"uid\\\", \\\"gid\\\", \"\n           << \"\\\"mode\\\", \\\"xstype\\\", \\\"xs\\\"], \"\n           << \"\\\"uid\\\": \\\"\" << pVid->uid << \"\\\", \"\n           << \"\\\"gid\\\": \\\"\" << pVid->gid << \"\\\", \"\n           << \"\\\"timestamp\\\": \" << std::setw(10) << time(static_cast<time_t*>(0)) << \", \"\n           << \"\\\"num_dirs\\\": \" << std::setw(10) << num_dirs << \", \"\n           << \"\\\"num_files\\\": \" << std::setw(10) << num_files\n           << \"}\" << std::endl;\n  arch_ofs.close();\n  // Copy local archive file to archive directory in EOS\n  std::string dst_path = arch_dir;\n  dst_path += ARCH_INIT;\n  XrdCl::PropertyList properties;\n  XrdCl::PropertyList result;\n  XrdCl::URL url_src;\n  url_src.SetProtocol(\"file\");\n  url_src.SetPath(arch_fn.c_str());\n  XrdCl::URL url_dst;\n  url_dst.SetProtocol(\"root\");\n  url_dst.SetHostName(\"localhost\");\n  url_dst.SetUserName(\"root\");\n  url_dst.SetParams(\"eos.ruid=0&eos.rgid=0\");\n  url_dst.SetPath(dst_path);\n  properties.Set(\"source\", url_src);\n  properties.Set(\"target\", url_dst);\n  XrdCl::CopyProcess copy_proc;\n  copy_proc.AddJob(properties, &result);\n  XrdCl::XRootDStatus status_prep = copy_proc.Prepare();\n\n  if (status_prep.IsOK()) {\n    XrdCl::XRootDStatus status_run = copy_proc.Run(0);\n\n    if (!status_run.IsOK()) {\n      stdErr = \"error: failed run for copy process, msg=\";\n      stdErr += status_run.ToStr().c_str();\n      retc = EIO;\n    }\n  } else {\n    stdErr = \"error: failed prepare for copy process, msg=\";\n    stdErr += status_prep.ToStr().c_str();\n    retc = EIO;\n  }\n\n  // Remove local archive file\n  unlink(arch_fn.c_str());\n  // Change the permissions on the archive file to 644\n  eos::common::VirtualIdentity root_ident = eos::common::VirtualIdentity::Root();\n  XrdSfsMode mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;\n\n  if (gOFS->_chmod(dst_path.c_str(), mode, *mError, root_ident)) {\n    stdErr = \"error: setting permisions on the archive file\";\n    retc = EIO;\n  }\n\n  // Add the dir inode to /proc/archive/ for fast find\n  if (!retc) {\n    oss.clear();\n    oss.str(\"\");\n    oss << gOFS->MgmProcArchivePath << \"/\" << fid;\n\n    if (gOFS->_touch(oss.str().c_str(), *mError, root_ident)) {\n      stdOut = \"warning: failed to create file in /eos/.../proc/archive/\";\n      return;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Make EOS sub-tree immutable/mutable by adding/removing the sys.acl=z:i from\n// all of the directories in the subtree.\n//------------------------------------------------------------------------------\nint\nProcCommand::MakeSubTreeImmutable(const std::string& arch_dir,\n                                  const std::vector<std::string>& vect_files)\n{\n  bool found_archive = false;\n  // Map of directories to set of files\n  std::map< std::string, std::set<std::string> > found;\n  eos::common::VirtualIdentity root_vid = eos::common::VirtualIdentity::Root();\n\n  // Check for already archived directories in the current sub-tree\n  if (gOFS->_find(arch_dir.c_str(), *mError, stdErr, root_vid, found,\n                  (const char*) 0, (const char*) 0)) {\n    eos_err(\"dir=%s list all err=%s\", arch_dir.c_str(), stdErr.c_str());\n    retc = errno;\n    return retc;\n  }\n\n  for (auto it = found.begin(); it != found.end(); ++it) {\n    for (auto itf = vect_files.begin(); itf != vect_files.end(); ++itf) {\n      if (it->second.find(*itf) != it->second.end()) {\n        found_archive = true;\n        stdErr = \"error: another archive found in current sub-tree in \";\n        stdErr += it->first.c_str();\n        stdErr += itf->c_str();\n        break;\n      }\n    }\n\n    if (found_archive) {\n      break;\n    }\n  }\n\n  if (found_archive) {\n    // Forbit archiving of archives\n    retc = EPERM;\n    return retc;\n  }\n\n  // Make the EOS sub-tree immutable e.g.: add sys.acl=z:i\n  eos::common::VirtualIdentity root_ident = eos::common::VirtualIdentity::Root();\n  const char* acl_key = \"sys.acl\";\n  std::string acl_val;\n\n  for (auto it = found.begin(); it != found.end(); ++it) {\n    acl_val = \"\";\n\n    if (!gOFS->_attr_get(it->first.c_str(), *mError, *pVid,\n                         (const char*) 0, acl_key, acl_val)) {\n      // Add immutable only if not already present\n      size_t pos_z = acl_val.find(\"z:\");\n\n      if (pos_z != std::string::npos) {\n        if (acl_val.find('i', pos_z + 2) == std::string::npos) {\n          acl_val.insert(pos_z + 2, \"i\");\n        }\n      } else {\n        acl_val += \",z:i\";\n      }\n    } else {\n      acl_val = \"z:i\";\n    }\n\n    eos_debug(\"acl_key=%s, acl_val=%s\", acl_key, acl_val.c_str());\n\n    if (gOFS->_attr_set(it->first.c_str(), *mError, root_ident,\n                        (const char*) 0, acl_key, acl_val.c_str())) {\n      stdErr = \"error: making EOS subtree immutable, dir=\";\n      stdErr += arch_dir.c_str();\n      retc = mError->getErrInfo();\n      break;\n    }\n  }\n\n  return retc;\n}\n\n//----------------------------------------------------------------------------\n// Make EOS sub-tree mutable by removing the sys.acl=z:i rule from all of the\n// directories in the sub-tree.\n//----------------------------------------------------------------------------\nint\nProcCommand::MakeSubTreeMutable(const std::string& arch_dir)\n{\n  std::map< std::string, std::set<std::string> > found;\n  eos::common::VirtualIdentity root_vid = eos::common::VirtualIdentity::Root();\n\n  // Get all dirs in current subtree\n  if (gOFS->_find(arch_dir.c_str(), *mError, stdErr, root_vid, found,\n                  (const char*) 0, (const char*) 0)) {\n    eos_err(\"dir=%s list all err=%s\", arch_dir.c_str(), stdErr.c_str());\n    retc = errno;\n    return retc;\n  }\n\n  // Make the EOS sub-tree mutable e.g.: remove sys.acl=z:i\n  const char* acl_key = \"sys.acl\";\n  std::string acl_val;\n  std::string new_acl;\n\n  for (auto it = found.begin(); it != found.end(); ++it) {\n    acl_val = \"\";\n\n    if (!gOFS->_attr_get(it->first.c_str(), *mError, *pVid,\n                         (const char*) 0, acl_key, acl_val)) {\n      std::istringstream iss(acl_val.c_str());\n      std::string rule;\n      new_acl = \"\";\n\n      while (std::getline(iss, rule, ',')) {\n        if (rule.find(\"z:\") == 0) {\n          rule.erase(rule.find('i'), 1);\n\n          if (rule.length() > 2) {\n            new_acl += rule;\n            new_acl += ',';\n          }\n        } else {\n          // Don' modify the rest of the rules\n          new_acl += rule;\n          new_acl += ',';\n        }\n      }\n\n      // Remove last comma\n      if (new_acl.length()) {\n        new_acl.erase(new_acl.length() - 1);\n      }\n\n      acl_val = new_acl.c_str();\n    } else {\n      eos_warning(\"Dir=%s no xattrs\", it->first.c_str());\n      continue;\n    }\n\n    eos_debug(\"acl_key=%s, acl_val=%s\", acl_key, acl_val.c_str());\n\n    // Update the new sys.acl xattr\n    if (acl_val.length()) {\n      if (gOFS->_attr_set(it->first.c_str(), *mError, root_vid,\n                          (const char*) 0, acl_key, acl_val.c_str())) {\n        stdErr = \"error: making EOS subtree mutable (update sys.acl), dir=\";\n        stdErr += arch_dir.c_str();\n        retc = mError->getErrInfo();\n        break;\n      }\n    } else {\n      // Completely remove the sys.acl xattr\n      if (gOFS->_attr_rem(it->first.c_str(), *mError, root_vid,\n                          (const char*) 0, acl_key)) {\n        stdErr = \"error: making EOS subtree mutable (rm sys.acl), dir=\";\n        stdErr += arch_dir.c_str();\n        retc = mError->getErrInfo();\n        break;\n      }\n    }\n  }\n\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Get fileinfo for all files/dirs in the subtree and add it to the archive\n//------------------------------------------------------------------------------\nint\nProcCommand::ArchiveAddEntries(const std::string& arch_dir,\n                               std::fstream& ofs, int& num, bool is_file,\n                               IFilter* filter)\n{\n  num = 0;\n  std::map<std::string, std::string> info_map;\n  std::map<std::string, std::string> attr_map; // only for dirs\n\n  // These keys should match the ones in the header dictionary\n  if (is_file) {\n    info_map = { {\"file\", \"\"}, {\"size\", \"\"}, {\"mtime\", \"\"}, {\"ctime\", \"\"},\n      {\"uid\", \"\"}, {\"gid\", \"\"}, {\"mode\", \"\"}, {\"xstype\", \"\"},\n      {\"xs\", \"\"}\n    };\n  } else { // dir\n    info_map = {{\"file\", \"\"}, {\"uid\", \"\"}, {\"gid\", \"\"}, {\"mode\", \"\"},\n      {\"xattrn\", \"\"}, {\"xattrv\", \"\"}\n    };\n  }\n\n  eos::common::VirtualIdentity root_vid = eos::common::VirtualIdentity::Root();\n  eos::console::RequestProto req;\n  auto* find_req = req.mutable_find();\n  find_req->set_path(arch_dir);\n  find_req->set_fileinfo(true);\n\n  if (is_file) {\n    find_req->set_files(true);\n  } else {\n    find_req->set_directories(true);\n  }\n\n  XrdOucErrInfo cmd_error;\n  std::unique_ptr<IProcCommand> cmd_find(new NewfindCmd(std::move(req),\n                                         root_vid));\n  int cmd_retc = cmd_find->open(nullptr, nullptr, root_vid, &cmd_error);\n\n  while (cmd_retc > 0) {\n    eos_static_info(\"msg=\\\"waiting for find reply\\\" path=\\\"%s\\\"\",\n                    arch_dir.c_str());\n    std::this_thread::sleep_for(std::chrono::seconds(cmd_retc));\n    cmd_retc = cmd_find->open(nullptr, nullptr, root_vid, &cmd_error);\n  }\n\n  if (cmd_retc < 0) {\n    eos_static_err(\"msg=\\\"failed archive find\\\" path=\\\"%s\\\"\", arch_dir.c_str());\n    stdErr = \"failed archive find operation\";\n    retc = EINVAL;\n    return retc;\n  }\n\n  (void) cmd_find->close();\n  size_t spos = 0;\n  size_t key_length = 0; // lenght of file/dir name - it could have spaces\n  std::string rel_path;\n  std::string key, value, pair;\n  std::string line;\n  std::istringstream line_iss;\n  std::ifstream result_ifs(cmd_find->GetResultFn());\n  XrdOucString unseal_str;\n\n  if (!result_ifs.good()) {\n    eos_static_err(\"msg=\\\"failed to open find fileinfo result file on MGM\\\" \"\n                   \"path=\\\"%s\\\"\", cmd_find->GetResultFn());\n    stdErr = \"failed to open find fileinfo result file on MGM\";\n    retc = EIO;\n    return retc;\n  }\n\n  char* tmp_buff = new char[4096 * 4];\n\n  while (std::getline(result_ifs, line)) {\n    if (line.find(\"&mgm.proc.stderr=\") == 0) {\n      continue;\n    }\n\n    if (line.find(\"&mgm.proc.stdout=\") == 0) {\n      line.erase(0, 17);\n    } else {\n      if (line.find(\"mgm.proc.stdout=\") == 0) {\n        line.erase(0, 16);\n      }\n    }\n\n    unseal_str = XrdOucString(line.c_str());\n    line = eos::common::StringConversion::UnSeal(unseal_str);\n    line_iss.clear();\n    line_iss.str(line);\n\n    // We assume that the keylength.file parameter is always first in the\n    // output of fileinfo -m command\n    while (line_iss.good()) {\n      line_iss >> pair;\n      spos = pair.find('=');\n\n      if ((spos == std::string::npos) || (!line_iss.good())) {\n        continue;  // not in key=value format\n      }\n\n      key = pair.substr(0, spos);\n      value = pair.substr(spos + 1);\n\n      if (key == \"keylength.file\") {\n        key_length = static_cast<size_t>(atoi(value.c_str()));\n        // Read in the file/dir path using the previously read key_length\n        int full_length = key_length + 5; // 5 stands for \"file=\"\n        line_iss.read(tmp_buff, 1); // read the empty space before \"file=...\"\n        line_iss.read(tmp_buff, full_length);\n        tmp_buff[full_length] = '\\0';\n        pair = tmp_buff;\n        spos = pair.find('=');\n        key = pair.substr(0, spos);\n        value = pair.substr(spos + 1);\n      }\n\n      if (info_map.find(key) == info_map.end()) {\n        continue;  // not what we are looking for\n      }\n\n      if (key == \"xattrn\") {\n        // The next token must be an xattrv\n        std::string xattrn = value;\n        line_iss >> pair;\n        spos = pair.find('=');\n\n        if ((spos == std::string::npos) || (!line_iss.good())) {\n          delete[] tmp_buff;\n          eos_static_err(\"%s\", \"msg=\\\"malformed xattr pair format\\\"\");\n          stdErr = \"malformed xattr pair format\";\n          retc = EINVAL;\n          return retc;\n        }\n\n        key = pair.substr(0, spos);\n        value = pair.substr(spos + 1);\n\n        if (key != \"xattrv\") {\n          delete[] tmp_buff;\n          eos_static_err(\"%s\", \",msg=\\\"not found expected xattrv\\\"\");\n          stdErr = \"not found expected xattrv\";\n          retc = EINVAL;\n          return retc;\n        }\n\n        attr_map[xattrn] = value;\n      } else {\n        info_map[key] = value;\n        eos_debug(\"key=%s, value=%s\", key.c_str(), value.c_str());\n      }\n    }\n\n    // Add entry info to the archive file with the path names relative to the\n    // current archive directory\n    rel_path = info_map[\"file\"];\n    rel_path.erase(0, arch_dir.length());\n\n    if (rel_path.empty()) {\n      rel_path = \"./\";\n    }\n\n    info_map[\"file\"] = rel_path;\n    // TODO(esindril): The file path should be base64 encoded to avoid any surprises\n\n    if (eos::common::Path::IsVersion(info_map[\"file\"])) {\n      eos_static_err(\"msg=\\\"failed archive contains a version entry\\\" \"\n                     \"arch_path=\\\"%s\\\" is_file=%u entry_path=\\\"%s\\\"\",\n                     arch_dir.c_str(), is_file, info_map[\"file\"].c_str());\n      stdErr = \"archive contains version entry: \";\n      stdErr += info_map[\"file\"].c_str();\n      retc = EINVAL;\n      break;\n    }\n\n    if (is_file) {\n      // Filter out file entries if necessary\n      if (filter && filter->FilterOutFile(info_map)) {\n        continue;\n      }\n\n      // Return an error if the archive contains 0-size files or symlinks\n      if (info_map[\"size\"] == \"0\") {\n        eos_static_err(\"msg=\\\"failed archive contains 0-size file\\\" \"\n                       \"arch_path=\\\"%s\\\" file_path=\\\"%s\\\"\",\n                       arch_dir.c_str(), info_map[\"file\"].c_str());\n        stdErr = \"archive contains 0-size file: \";\n        stdErr += info_map[\"file\"].c_str();\n        retc = EINVAL;\n        break;\n      }\n\n      if (info_map[\"xstype\"] == \"none\") {\n        eos_static_err(\"msg=\\\"failed archive contains symlink file\\\" \"\n                       \"arch_path=\\\"%s\\\" file_path=\\\"%s\\\"\",\n                       arch_dir.c_str(), info_map[\"file\"].c_str());\n        stdErr = \"archive contains symlink file: \";\n        stdErr += info_map[\"file\"].c_str();\n        retc = EINVAL;\n        break;\n      }\n\n      if (eos::common::Path::IsAtomic(info_map[\"file\"])) {\n        eos_static_err(\"msg=\\\"failed archive contains an atomic file\\\" \"\n                       \"arch_path=\\\"%s\\\" file_path=\\\"%s\\\"\",\n                       arch_dir.c_str(), info_map[\"file\"].c_str());\n        stdErr = \"archive contains atomic file: \";\n        stdErr += info_map[\"file\"].c_str();\n        retc = EINVAL;\n        break;\n      }\n\n      ofs << \"[\\\"f\\\", \\\"\" << info_map[\"file\"] << \"\\\", \"\n          << \"\\\"\" << info_map[\"size\"] << \"\\\", \"\n          << \"\\\"\" << info_map[\"mtime\"] << \"\\\", \"\n          << \"\\\"\" << info_map[\"ctime\"] << \"\\\", \"\n          << \"\\\"\" << info_map[\"uid\"] << \"\\\", \"\n          << \"\\\"\" << info_map[\"gid\"] << \"\\\", \"\n          << \"\\\"\" << info_map[\"mode\"] << \"\\\", \"\n          << \"\\\"\" << info_map[\"xstype\"] << \"\\\", \"\n          << \"\\\"\" << info_map[\"xs\"] << \"\\\"]\"\n          << std::endl;\n    } else {\n      // Filter out directory entries if necessary\n      if (filter && filter->FilterOutDir(info_map[\"file\"])) {\n        continue;\n      }\n\n      eos_info(\"msg=\\\"writing to ofs stream\\\" path=%s\\\"\", info_map[\"file\"].c_str());\n      ofs << \"[\\\"d\\\", \\\"\" << info_map[\"file\"] << \"\\\", \"\n          << \"\\\"\" << info_map[\"uid\"] << \"\\\", \"\n          << \"\\\"\" << info_map[\"gid\"] << \"\\\", \"\n          << \"\\\"\" << info_map[\"mode\"] << \"\\\", \"\n          << \"{\";\n\n      for (auto it = attr_map.begin(); it != attr_map.end(); /*empty*/) {\n        ofs << \"\\\"\" << it->first << \"\\\": \\\"\" << it->second << \"\\\"\";\n        ++it;\n\n        if (it != attr_map.end()) {\n          ofs << \", \";\n        }\n      }\n\n      ofs << \"}]\" << std::endl;\n      attr_map.clear();\n    }\n\n    num++;\n  }\n\n  delete[] tmp_buff;\n  return retc;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Attr.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/Attr.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"common/Path.hh\"\n#include \"common/LayoutId.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/Resolver.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Make sure the input given by the client makes sense\n//!\n//! @return true if successful, otherwise false\n//------------------------------------------------------------------------------\nbool SanitizeXattr(const std::string& key, const std::string& value)\n{\n  if ((key == \"sys.forced.blocksize\") || (key == \"user.forced.blocksize\")) {\n    std::string out_val;\n    (void)eos::common::SymKey::DeBase64(value, out_val);\n    return eos::common::LayoutId::IsValidBlocksize(out_val);\n  }\n\n  return true;\n}\n\nint\nProcCommand::Attr()\n{\n  XrdOucString spath = pOpaque->Get(\"mgm.path\");\n  XrdOucString option = pOpaque->Get(\"mgm.option\");\n  const char* inpath = spath.c_str();\n  uint64_t identifier = 0;\n  bool exclusive = false;\n  ACCESSMODE_R;\n  NAMESPACEMAP;\n  PROC_BOUNCE_ILLEGAL_NAMES;\n  PROC_BOUNCE_NOT_ALLOWED;\n  WAIT_BOOT;\n\n  if ((spath.beginswith(\"fid:\") || spath.beginswith(\"fxid:\"))) {\n    identifier = Resolver::retrieveFileIdentifier(spath).getUnderlyingUInt64();\n    spath = \"\";\n    std::string lpath;\n    std::string err_msg;\n\n    if (GetPathFromFid(lpath, identifier, err_msg)) {\n      stdErr = err_msg.c_str();\n    }\n\n    spath = lpath.c_str();\n  } else if (spath.beginswith(\"pid:\") || spath.beginswith(\"pxid:\") ||\n             spath.beginswith(\"cid:\") || spath.beginswith(\"cxid:\")) {\n    if (spath.beginswith(\"pid:\") || spath.beginswith(\"pxid:\")) {\n      spath.replace('p', 'f', 0, 1);\n    } else {\n      spath.replace('c', 'f', 0, 1);\n    }\n\n    identifier = Resolver::retrieveFileIdentifier(spath).getUnderlyingUInt64();\n    spath = \"\";\n    std::string lpath;\n    std::string err_msg;\n\n    if (GetPathFromCid(lpath, identifier, err_msg)) {\n      stdErr = err_msg.c_str();\n    }\n\n    spath = lpath.c_str();\n  } else {\n    spath = eos::common::Path(path).GetPath();\n    eos::common::StringConversion::UnsealXrdPath(spath);\n  }\n\n  path = spath.c_str();\n  PROC_TOKEN_SCOPE;\n\n  if ((!spath.length()) && (!identifier)) {\n    // Empty path or invalid numeric identifier\n    stdErr = \"error: please give a valid identifier (<path>|fid:<fid-dec>\"\n             \"|fxid:<fid-hex>|cid:<cid-dec>|cxid:<cid-hex>)\";\n    retc = EINVAL;\n  } else if ((!spath.length())) {\n    // Retrieval of path from numeric identifier failed\n    retc = errno;\n  } else if ((mSubCmd != \"set\") && (mSubCmd != \"get\") && (mSubCmd != \"ls\") &&\n             (mSubCmd != \"rm\") && (mSubCmd != \"fold\")) {\n    // Unrecognized subcommand\n    stdErr = \"error: the subcommand must be one of 'ls', 'get', 'set', 'rm' or 'fold'!\";\n    retc = EINVAL;\n  } else {\n    if (((mSubCmd == \"set\") && ((!pOpaque->Get(\"mgm.attr.key\")) ||\n                                ((!pOpaque->Get(\"mgm.attr.value\"))))) ||\n        ((mSubCmd == \"get\") && ((!pOpaque->Get(\"mgm.attr.key\")))) ||\n        ((mSubCmd == \"rm\") && ((!pOpaque->Get(\"mgm.attr.key\"))))) {\n      stdErr = \"error: you have to provide 'mgm.attr.key' for set,get,rm and 'mgm.attr.value' for set commands!\";\n      retc = EINVAL;\n    } else {\n      retc = 0;\n      const char* ptr = pOpaque->Get(\"mgm.attr.key\");\n      std::string key = (ptr ? ptr : \"\");\n      ptr = pOpaque->Get(\"mgm.attr.value\");\n      std::string val = (ptr ? ptr : \"\");\n      eos::common::StringConversion::ReplaceStringInPlace(val, \"\\\"\", \"\");\n\n      if (val.length() && !SanitizeXattr(key.c_str(), val.c_str())) {\n        stdErr = \"error: invalid input\";\n        retc = EINVAL;\n        return SFS_OK;\n      }\n\n      // Find everything to be modified i.e. directories only\n      std::map<std::string, std::set<std::string> > found;\n\n      if (option.find(\"r\") != STR_NPOS) {\n        if (gOFS->_find(spath.c_str(), *mError, stdErr, *pVid, found, nullptr,\n                        nullptr, true)) {\n          stdErr += \"error: unable to search in path\";\n          retc = errno;\n        } else {\n          // Path may be a file, so add it to the list\n          if (found.empty()) {\n            (void) found[spath.c_str()].size();\n          }\n        }\n      } else {\n        // the single dir case\n        (void) found[spath.c_str()].size();\n      }\n\n      if (option.find(\"c\") != STR_NPOS) {\n        exclusive = true;\n      }\n\n      if ((mSubCmd == \"set\") || (mSubCmd == \"rm\")) {\n        SET_ACCESSMODE_W;\n      }\n\n      if (!retc) {\n        // apply to  directories starting at the highest level\n        for (auto foundit = found.begin(); foundit != found.end(); foundit++) {\n          {\n            eos::IContainerMD::XAttrMap map;\n            eos::IContainerMD::XAttrMap linkmap;\n\n            if ((mSubCmd == \"ls\")) {\n              RECURSIVE_STALL(\"AttrLs\", (*pVid));\n\n              if (gOFS->_access(foundit->first.c_str(), R_OK, *mError, *pVid, 0)) {\n                stdErr += \"error: unable to get attributes  \";\n                stdErr += foundit->first.c_str();\n                retc = errno;\n                return SFS_OK;\n              }\n\n              XrdOucString partialStdOut = \"\";\n\n              if (gOFS->_attr_ls(foundit->first.c_str(), *mError, *pVid, (const char*) 0, map,\n                                 true)) {\n                stdErr += \"error: unable to list attributes of \";\n                stdErr += foundit->first.c_str();\n                stdErr += \"\\n\";\n                retc = errno;\n              } else {\n                eos::IContainerMD::XAttrMap::const_iterator it;\n\n                if (option == \"r\") {\n                  stdOut += foundit->first.c_str();\n                  stdOut += \":\\n\";\n                }\n\n                for (it = map.begin(); it != map.end(); ++it) {\n                  partialStdOut += (it->first).c_str();\n\n                  if (option.find(\"V\") == STR_NPOS) {\n                    if (it->first != \"sys.file.buffer\") {\n                      partialStdOut += \"=\";\n                      partialStdOut += \"\\\"\";\n                      partialStdOut += (it->second).c_str();\n                    } else {\n                      partialStdOut += \"=\\\"[\";\n                      partialStdOut += (std::to_string(it->second.size()).c_str());\n                      partialStdOut += \"] bytes\";\n                    }\n\n                    partialStdOut += \"\\\"\";\n                    partialStdOut += \"\\n\";\n                  } else {\n                    partialStdOut += \"\\n\";\n                  }\n                }\n\n                stdOut += partialStdOut;\n\n                if (option == \"r\") {\n                  stdOut += \"\\n\";\n                }\n              }\n            }\n\n            if (mSubCmd == \"set\") {\n              RECURSIVE_STALL(\"AttrSet\", (*pVid));\n\n              if (key == \"user.acl\") {\n                std::string evalacl;\n\n                // If someone wants to set a user.acl and the tag sys.eval.useracl\n                // is not there, we return an error ...\n                if ((pVid->uid != 0) && gOFS->_attr_get(foundit->first.c_str(),\n                                                        *mError, *pVid,\n                                                        (const char*) 0,\n                                                        \"sys.eval.useracl\", evalacl)) {\n                  stdErr += \"error: unable to set user.acl - the file/directory does not \"\n                            \"evaluate user acls (sys.eval.useracl is undefined)!\\n\";\n                  retc = EINVAL;\n                  return SFS_OK;\n                }\n              }\n\n              // Check if the origin exists and is a directory\n              if (key == \"sys.attr.link\") {\n                eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n\n                try {\n                  auto cmd = gOFS->eosView->getContainer(val.c_str());\n                } catch (eos::MDException& e) {\n                  std::ostringstream oss;\n                  oss << \"error: \" << val.c_str()\n                      << \" must be an existing directory\" << std::endl;\n                  stdErr = oss.str().c_str();\n                  retc = EINVAL;\n                  return SFS_OK;\n                }\n              }\n\n              if (gOFS->_attr_set(foundit->first.c_str(), *mError, *pVid, (const char*) 0,\n                                  key.c_str(), val.c_str(), exclusive)) {\n                stdErr += \"error: unable to set attribute in file/directory \";\n                stdErr += foundit->first.c_str();\n\n                if (mError != 0) {\n                  stdErr += \": \";\n                  stdErr += mError->getErrText();\n                }\n\n                stdErr += \"\\n\";\n                retc = errno;\n              }\n            }\n\n            if (mSubCmd == \"get\") {\n              RECURSIVE_STALL(\"AttrGet\", (*pVid));\n\n              if (gOFS->_access(foundit->first.c_str(), R_OK, *mError, *pVid, 0)) {\n                stdErr += \"error: unable to get attributes of \";\n                stdErr += foundit->first.c_str();\n                retc = errno;\n                return SFS_OK;\n              }\n\n              if (gOFS->_attr_get(foundit->first.c_str(), *mError, *pVid, (const char*) 0,\n                                  key.c_str(), val)) {\n                stdErr += \"error: unable to get attribute \";\n                stdErr += key.c_str();\n                stdErr += \" in file/directory \";\n                stdErr += foundit->first.c_str();\n                stdErr += \"\\n\";\n                retc = errno;\n              } else {\n                if (option.find(\"V\") != STR_NPOS) {\n                  stdOut += val.c_str();\n                } else {\n                  stdOut += key.c_str();\n                  stdOut += \"=\\\"\";\n                  stdOut += val.c_str();\n                  stdOut += \"\\\"\\n\";\n                }\n              }\n            }\n\n            if (mSubCmd == \"rm\") {\n              RECURSIVE_STALL(\"AttrRm\", (*pVid));\n\n              if (gOFS->_attr_rem(foundit->first.c_str(), *mError, *pVid, (const char*) 0,\n                                  key.c_str())) {\n                stdErr += \"error: unable to remove attribute '\";\n                stdErr += key.c_str();\n                stdErr += \"' in file/directory \";\n                stdErr += foundit->first.c_str();\n                stdErr += \"\\n\";\n                retc = errno;\n              } else {\n                stdOut += \"success: removed attribute '\";\n                stdOut += key.c_str();\n                stdOut += \"' from file/directory \";\n                stdOut += foundit->first.c_str();\n                stdOut += \"\\n\";\n              }\n            }\n\n            if (mSubCmd == \"fold\") {\n              RECURSIVE_STALL(\"AttrLs\", (*pVid));\n              int retc = gOFS->_attr_ls(foundit->first.c_str(), *mError, *pVid,\n                                        (const char*) 0, map, false);\n\n              if ((!retc) && map.count(\"sys.attr.link\")) {\n                retc |= gOFS->_attr_ls(map[\"sys.attr.link\"].c_str(), *mError, *pVid,\n                                       (const char*) 0, linkmap, true);\n              }\n\n              if (retc) {\n                stdErr += \"error: unable to list attributes in file/directory \";\n                stdErr += foundit->first.c_str();\n                stdErr += \"\\n\";\n                retc = errno;\n              } else {\n                XrdOucString partialStdOut;\n                eos::IContainerMD::XAttrMap::const_iterator it;\n\n                if (option == \"r\") {\n                  stdOut += foundit->first.c_str();\n                  stdOut += \":\\n\";\n                }\n\n                for (it = map.begin(); it != map.end(); ++it) {\n                  if (linkmap.count(it->first) &&\n                      linkmap[it->first] == map[it->first]) {\n                    if (gOFS->_attr_rem(foundit->first.c_str(), *mError, *pVid, (const char*) 0,\n                                        it->first.c_str())) {\n                      stdErr += \"error [ attr fold ] : unable to remove local attribute \";\n                      stdErr += it->first.c_str();\n                      stdErr += \"\\n\";\n                      retc = errno;\n                    } else {\n                      stdOut += \"info [ attr fold ] : removing local attribute \";\n                      stdOut += (it->first).c_str();\n                      stdOut += \"=\";\n                      stdOut += \"\\\"\";\n                      stdOut += (it->second).c_str();\n                      stdOut += \"\\\"\";\n                      stdOut += \"\\n\";\n                    }\n                  }\n                }\n\n                if (option == \"r\") {\n                  stdOut += \"\\n\";\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Cd.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/Cd.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/stat/Stat.hh\"\n\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Cd()\n{\n  gOFS->MgmStats.Add(\"Cd\", pVid->uid, pVid->gid, 1);\n  XrdOucString spath = pOpaque->Get(\"mgm.path\");\n  XrdOucString option = pOpaque->Get(\"mgm.option\");\n  const char* inpath = spath.c_str();\n  NAMESPACEMAP;\n  spath = path;\n\n  if (!spath.length()) {\n    stdErr = \"error: you have to give a path name to call 'cd'\";\n    retc = EINVAL;\n  } else {\n    PROC_TOKEN_SCOPE;\n    struct stat buf;\n\n    if (gOFS->_stat(spath.c_str(), &buf, *mError, *pVid, (const char*) 0)) {\n      stdErr = mError->getErrText();\n      retc = errno;\n    } else {\n      // if this is a directory open it and list\n      if (S_ISDIR(buf.st_mode)) {\n        retc = 0;\n      } else {\n        stdErr += \"error: this is not a directory\";\n        retc = ENOTDIR;\n      }\n    }\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Chmod.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/Chmod.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <XrdOuc/XrdOucEnv.hh>\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/macros/Macros.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Chmod()\n{\n  XrdOucString spath = pOpaque->Get(\"mgm.path\");\n  XrdOucString option = pOpaque->Get(\"mgm.option\");\n  XrdOucString mode = pOpaque->Get(\"mgm.chmod.mode\");\n  const char* inpath = spath.c_str();\n  NAMESPACEMAP;\n  PROC_BOUNCE_ILLEGAL_NAMES;\n  PROC_BOUNCE_NOT_ALLOWED;\n  spath = path;\n  PROC_TOKEN_SCOPE;\n\n  if ((!spath.length()) || (!mode.length())) {\n    stdErr = \"error: you have to provide a path and the mode to set!\\n\";\n    retc = EINVAL;\n  } else {\n    // find everything to be modified\n    std::map<std::string, std::set<std::string> > found;\n    std::map<std::string, std::set<std::string> >::const_iterator foundit;\n    std::set<std::string>::const_iterator fileit;\n\n    if (option == \"r\") {\n      if (gOFS->_find(spath.c_str(), *mError, stdErr, *pVid, found, 0, 0, true)) {\n        stdErr += \"error: unable to search in path\";\n        retc = errno;\n      }\n    } else {\n      // the single dir case\n      (void)found[spath.c_str()].size();\n    }\n\n    char modecheck[1024];\n    snprintf(modecheck, sizeof(modecheck) - 1, \"%llu\",\n             (unsigned long long) strtoul(mode.c_str(), 0, 10));\n    XrdOucString ModeCheck = modecheck;\n\n    if (ModeCheck != mode) {\n      stdErr = \"error: mode has to be an octal number like 777, 2777, 755, 644 ...\";\n      retc = EINVAL;\n    } else {\n      ACCESSMODE_W;\n      XrdSfsMode Mode = (XrdSfsMode) strtoul(mode.c_str(), 0, 8);\n\n      for (foundit = found.begin(); foundit != found.end(); foundit++) {\n        {\n          RECURSIVE_STALL(\"Chmod\", (*pVid));\n\n          if (gOFS->_chmod(foundit->first.c_str(), Mode, *mError, *pVid, (char*) 0)) {\n            stdErr += \"error: unable to chmod of directory \";\n            stdErr += foundit->first.c_str();\n            stdErr += \"\\n\";\n            retc = errno;\n          } else {\n            char omode[64];\n            snprintf(omode, sizeof(omode), \"%o\", (int)Mode);\n\n            if (pVid->uid) {\n              stdOut += \"success: mode of file/directory \";\n              stdOut += foundit->first.c_str();\n              stdOut += \" is now '2\";\n              stdOut += omode;\n              stdOut += \"'\\n\";\n            } else {\n              stdOut += \"success: mode of file/directory \";\n              stdOut += foundit->first.c_str();\n              stdOut += \" is now '\";\n              stdOut += omode;\n              stdOut += \"'\\n\";\n            }\n          }\n        }\n      }\n    }\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Chown.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/admin/Chown.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <XrdOuc/XrdOucEnv.hh>\n#include \"common/Path.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/macros/Macros.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Chown()\n{\n  XrdOucString spath = pOpaque->Get(\"mgm.path\");\n  XrdOucString option = pOpaque->Get(\"mgm.chown.option\");\n  XrdOucString owner = pOpaque->Get(\"mgm.chown.owner\");\n  const char* inpath = spath.c_str();\n  NAMESPACEMAP;\n  PROC_BOUNCE_ILLEGAL_NAMES;\n  PROC_BOUNCE_NOT_ALLOWED;\n  spath = path;\n  PROC_TOKEN_SCOPE;\n  bool nodereference = (option.find(\"h\") != STR_NPOS) ? true : false;\n  bool singlefile = false;\n\n  if ((!spath.length()) || (!owner.length())) {\n    stdErr = \"error: you have to provide a path and the owner to set!\\n\";\n    retc = EINVAL;\n  } else {\n    // find everything to be modified\n    std::map<std::string, std::set<std::string> > found;\n    std::map<std::string, std::set<std::string> >::const_iterator foundit;\n    std::set<std::string>::const_iterator fileit;\n\n    if ((option.find(\"r\") != STR_NPOS)) {\n      if (gOFS->_find(spath.c_str(), *mError, stdErr, *pVid, found)) {\n        stdErr += \"error: unable to search in path\";\n        retc = errno;\n      }\n    } else {\n      struct stat buf;\n\n      if (!gOFS->_stat(spath.c_str(), &buf, *mError, *pVid, 0, 0, false)) {\n        if (S_ISDIR(buf.st_mode)) {\n          (void) found[spath.c_str()].size();\n        } else {\n          eos::common::Path cPath(spath.c_str());\n          found[cPath.GetParentPath()].insert(cPath.GetName());\n          singlefile = true;\n        }\n      } else {\n        (void) found[spath.c_str()].size();\n      }\n    }\n\n    std::string uid = owner.c_str();\n    std::string gid = owner.c_str();\n    bool failure = false;\n    uid_t uidt = eos::common::VirtualIdentity::kNobodyUid;\n    gid_t gidt = eos::common::VirtualIdentity::kNobodyGid;;\n    int dpos = 0;\n\n    if ((dpos = owner.find(\":\")) != STR_NPOS) {\n      uid.erase(dpos);\n      gid.erase(0, dpos + 1);\n\n      if (uid == \"\") {\n        uidt = 0xffffffff;\n      }\n\n      if ((gid != \"0\")) {\n        // try to translate with password database\n        int terrc = 0;\n        gidt = eos::common::Mapping::GroupNameToGid(gid, terrc);\n\n        if (terrc) {\n          // cannot translate this name\n          stdErr = \"error: I cannot translate your gid string using the pwd database\";\n          retc = terrc;\n          failure = true;\n        }\n      } else {\n        gidt = 0;\n      }\n    } else {\n      gid = \"0\";\n      gidt = 0xffffffff;\n    }\n\n    if (uid != \"0\") {\n      if (uid.length()) {\n        int terrc = 0;\n        uidt = eos::common::Mapping::UserNameToUid(uid, terrc);\n\n        if (terrc) {\n          stdErr = \"error: I cannot translate your uid string using the pwd database\";\n          retc = terrc;\n          failure = true;\n        }\n      }\n    } else {\n      uidt = 0;\n    }\n\n    if (pVid->uid && ((!uidt) || (!gidt))) {\n      stdErr = \"error: you are changing to uid/gid=0 but you are not root!\";\n      retc = EPERM;\n      failure = true;\n    }\n\n    ACCESSMODE_W;\n\n    if (!failure) {\n      if (!singlefile) {\n        // for directories\n        for (foundit = found.begin(); foundit != found.end(); foundit++) {\n          RECURSIVE_STALL(\"Chown\", (*pVid));\n          std::string dirname = foundit->first;\n          size_t linkpos = (dirname.find(\" -> \"));\n\n          if (linkpos != std::string::npos) {\n            dirname = foundit->first.substr(0, linkpos);\n          }\n\n          if (gOFS->_chown(dirname.c_str(), uidt, gidt, *mError, *pVid,\n                           (char*) 0, nodereference)) {\n            stdErr += \"error: unable to chown of directory \";\n            stdErr += dirname.c_str();\n            stdErr += \"\\n\";\n            retc = errno;\n          } else {\n            stdOut += \"success: owner of directory \";\n            stdOut += dirname.c_str();\n            stdOut += \" is now \";\n            stdOut += \"uid=\";\n            stdOut += uid.c_str();\n\n            if (!pVid->uid) {\n              if (gidt) {\n                stdOut += \" gid=\";\n                stdOut += gid.c_str();\n              }\n            }\n\n            stdOut += \"\\n\";\n          }\n        }\n      }\n\n      // for files\n      for (foundit = found.begin(); foundit != found.end(); foundit++) {\n        for (fileit = foundit->second.begin(); fileit != foundit->second.end();\n             fileit++) {\n          std::string fpath = foundit->first;\n          std::string filename = *fileit;\n          size_t linkpos = (filename.find(\" -> \"));\n\n          if (linkpos != std::string::npos) {\n            filename = filename.substr(0, linkpos);\n          }\n\n          fpath += filename;\n\n          if (gOFS->_chown(fpath.c_str(), uidt, gidt, *mError, *pVid, (char*) 0,\n                           nodereference)) {\n            stdErr += \"error: unable to chown of file \";\n            stdErr += fpath.c_str();\n            stdErr += \"\\n\";\n            retc = errno;\n          } else {\n            stdOut += \"success: owner of file \";\n            stdOut += fpath.c_str();\n            stdOut += \" is now \";\n            stdOut += \"uid=\";\n            stdOut += uid.c_str();\n\n            if (!pVid->uid) {\n              if (gidt) {\n                stdOut += \" gid=\";\n                stdOut += gid.c_str();\n              }\n\n              stdOut += \"\\n\";\n            }\n          }\n        }\n      }\n    }\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/DfCmd.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file DfCmd.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"DfCmd.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/config/IConfigEngine.hh\"\n#include <sstream>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Method implementing the specific behavior of the command\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nDfCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  eos::console::DfProto df = mReqProto.df();\n\n  reply.set_std_out(FsView::gFsView.Df(df.monitoring(),df.si(),df.readable(), df.path(), WantsJsonOutput()));\n  reply.set_retc(0);\n\n  return reply;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/DfCmd.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file DfCmd.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"proto/Df.pb.h\"\n#include \"mgm/proc/ProcCommand.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class DfCmd - class handling df commands\n//------------------------------------------------------------------------------\nclass DfCmd: public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit DfCmd(eos::console::RequestProto&& req,\n                    eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, false)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~DfCmd() = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\nprivate:\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/File.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/File.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/acl/Acl.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/policy/Policy.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/convert/ConverterEngine.hh\"\n#include \"mgm/convert/ConversionTag.hh\"\n#include \"mgm/xattr/XattrLock.hh\"\n#include \"mgm/misc/Constants.hh\"\n#include \"common/Utils.hh\"\n#include \"common/Path.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/SecEntity.hh\"\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/utils/Checksum.hh\"\n#include \"namespace/utils/Attributes.hh\"\n#include \"namespace/Resolver.hh\"\n#include <XrdCl/XrdClCopyProcess.hh>\n#include <math.h>\n#include <memory>\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::File()\n{\n  XrdOucString spath = \"\";\n  XrdOucString spathid = pOpaque->Get(\"mgm.file.id\");\n  eos::IFileMD::id_t fid = 0ull;\n\n  if (spathid.length()) {\n    try {\n      fid = std::strtoull(spathid.c_str(), nullptr, 10);\n    } catch (...) {\n      stdErr = \"error: given file identifier is not numeric\";\n      retc = EINVAL;\n      return SFS_OK;\n    }\n\n    std::string err_msg;\n    std::string lpath;\n\n    if (GetPathFromFid(lpath, fid, err_msg)) {\n      stdErr = err_msg.c_str();\n    }\n\n    spath = lpath.c_str();\n  } else {\n    spath = pOpaque->Get(\"mgm.path\");\n  }\n\n  const char* inpath = spath.c_str();\n\n  if (!inpath) {\n    inpath = \"\";\n  }\n\n  NAMESPACEMAP;\n  PROC_BOUNCE_ILLEGAL_NAMES;\n  PROC_BOUNCE_NOT_ALLOWED;\n  spath = path;\n  bool cmdok = false;\n\n  if (!spath.length() && !fid && (mSubCmd != \"drop\")) {\n    stdErr = \"error: you have to give a path name to call 'file'\";\n    retc = EINVAL;\n    return SFS_OK;\n  } else {\n    // --------------------------------------------------------------------------\n    // drop a replica referenced by filesystem id\n    // --------------------------------------------------------------------------\n    if (mSubCmd == \"drop\") {\n      cmdok = true;\n      XrdOucString sfsid = pOpaque->Get(\"mgm.file.fsid\");\n      XrdOucString sforce = pOpaque->Get(\"mgm.file.force\");\n      bool forceRemove = false;\n\n      if (sforce.length() && (sforce == \"1\")) {\n        forceRemove = true;\n      }\n\n      unsigned long fsid = (sfsid.length()) ? strtoul(sfsid.c_str(), 0, 10) : 0;\n      unsigned long fid = (spathid.length()) ? strtoul(spathid.c_str(), 0, 10) : 0;\n\n      if (gOFS->_dropstripe(spath.c_str(), fid, *mError, *pVid, fsid, forceRemove)) {\n        stdErr += \"error: unable to drop stripe\";\n        retc = errno;\n      } else {\n        stdOut += \"success: dropped stripe on fs=\";\n        stdOut += (int) fsid;\n      }\n    }\n\n    // -------------------------------------------------------------------------\n    // change the number of stripes for files with replica layout\n    // -------------------------------------------------------------------------\n    if (mSubCmd == \"layout\") {\n      cmdok = true;\n      XrdOucString stripes = pOpaque->Get(\"mgm.file.layout.stripes\");\n      XrdOucString cksum = pOpaque->Get(\"mgm.file.layout.checksum\");\n      XrdOucString layout = pOpaque->Get(\"mgm.file.layout.type\");\n      int checksum_type = eos::common::LayoutId::kNone;\n      XrdOucString ne = \"eos.layout.checksum=\";\n      ne += cksum;\n      XrdOucEnv env(ne.c_str());\n      int newstripenumber = 0;\n      std::string newlayoutstring;\n\n      if (stripes.length()) {\n        newstripenumber = atoi(stripes.c_str());\n      }\n\n      if (layout.length()) {\n        newlayoutstring = layout.c_str();\n      }\n\n      if (!stripes.length() && !cksum.length() && !newlayoutstring.length()) {\n        stdErr = \"error: you have to give a valid number of stripes\"\n                 \" as an argument to call 'file layout' or a valid checksum or a layout id\";\n        retc = EINVAL;\n      } else if (stripes.length() &&\n                 ((newstripenumber < 1) ||\n                  (newstripenumber > 255))) {\n        stdErr = \"error: you have to give a valid number of stripes\"\n                 \" as an argument to call 'file layout'\";\n        retc = EINVAL;\n      } else if (cksum.length() &&\n                 ((checksum_type = eos::common::LayoutId::GetChecksumFromEnv(\n                                     env)) == eos::common::LayoutId::kNone)) {\n        stdErr = \"error: you have to give a valid checksum typ0e\"\n                 \" as an argument to call 'file layout'\";\n        retc = EINVAL;\n      } else {\n        // only root can do that\n        if (pVid->uid == 0) {\n          std::shared_ptr<eos::IFileMD> fmd;\n          eos::common::RWMutexWriteLock viewWriteLock;\n\n          if ((spath.beginswith(\"fid:\") || (spath.beginswith(\"fxid:\")))) {\n            WAIT_BOOT;\n            unsigned long long fid = Resolver::retrieveFileIdentifier(\n                                       spath).getUnderlyingUInt64();\n            // reference by fid+fsid\n            //-------------------------------------------\n            viewWriteLock.Grab(gOFS->eosViewRWMutex);\n\n            try {\n              fmd = gOFS->eosFileService->getFileMD(fid);\n            } catch (eos::MDException& e) {\n              errno = e.getErrno();\n              stdErr = \"error: cannot retrieve file meta data - \";\n              stdErr += e.getMessage().str().c_str();\n              eos_debug(\"caught exception %d %s\\n\",\n                        e.getErrno(),\n                        e.getMessage().str().c_str());\n            }\n          } else {\n            // -----------------------------------------------------------------\n            // reference by path\n            // ----------------------------------------------------------------\n            viewWriteLock.Grab(gOFS->eosViewRWMutex);\n\n            try {\n              fmd = gOFS->eosView->getFile(spath.c_str());\n            } catch (eos::MDException& e) {\n              errno = e.getErrno();\n              stdErr = \"error: cannot retrieve file meta data - \";\n              stdErr += e.getMessage().str().c_str();\n              eos_debug(\"caught exception %d %s\\n\",\n                        e.getErrno(),\n                        e.getMessage().str().c_str());\n            }\n          }\n\n          if (fmd) {\n            bool only_replica = false;\n            bool only_tape = false;\n            bool any_layout = false;\n\n            if (fmd->getNumLocation() > 0) {\n              only_replica = true;\n            } else {\n              any_layout = true;\n            }\n\n            if (fmd->getNumLocation() == 1) {\n              if (fmd->hasLocation(EOS_TAPE_FSID)) {\n                only_tape = true;\n              }\n            }\n\n            if (!cksum.length()) {\n              checksum_type = eos::common::LayoutId::GetChecksum(fmd->getLayoutId());\n            }\n\n            if (!newstripenumber) {\n              newstripenumber = eos::common::LayoutId::GetStripeNumber(\n                                  fmd->getLayoutId()) + 1;\n            }\n\n            int lid = eos::common::LayoutId::kReplica;\n            unsigned long newlayout =\n              eos::common::LayoutId::GetId(lid,\n                                           checksum_type,\n                                           newstripenumber,\n                                           eos::common::LayoutId::GetBlocksizeType(fmd->getLayoutId())\n                                          );\n\n            if (newlayoutstring.length()) {\n              newlayout = strtol(newlayoutstring.c_str(), 0, 16);\n            }\n\n            if ((only_replica &&\n                 (((eos::common::LayoutId::GetLayoutType(fmd->getLayoutId()) ==\n                    eos::common::LayoutId::kReplica) ||\n                   (eos::common::LayoutId::GetLayoutType(fmd->getLayoutId()) ==\n                    eos::common::LayoutId::kPlain)) &&\n                  (eos::common::LayoutId::GetLayoutType(newlayout) ==\n                   eos::common::LayoutId::kReplica))) || only_tape || any_layout) {\n              fmd->setLayoutId(newlayout);\n              stdOut += \"success: setting layout to \";\n              stdOut += eos::common::LayoutId::PrintLayoutString(newlayout).c_str();\n              stdOut += \" for path=\";\n              stdOut += spath;\n              // commit new layout\n              gOFS->eosView->updateFileStore(fmd.get());\n            } else {\n              retc = EPERM;\n              stdErr = \"error: you can only change the number of \"\n                       \"stripes for files with replica layout or files without locations\";\n            }\n          } else {\n            retc = errno;\n            stdErr += \"error: no such file\";\n          }\n\n          viewWriteLock.Release();\n          //-------------------------------------------\n        } else {\n          retc = EPERM;\n          stdErr = \"error: you have to take role 'root' to execute this command\";\n        }\n      }\n    }\n\n    // -------------------------------------------------------------------------\n    // verify checksum, size for files issuing an asynchronous verification req\n    // -------------------------------------------------------------------------\n    if (mSubCmd == \"verify\") {\n      cmdok = true;\n      XrdOucString option = \"\";\n      XrdOucString computechecksum = pOpaque->Get(\"mgm.file.compute.checksum\");\n      XrdOucString commitchecksum = pOpaque->Get(\"mgm.file.commit.checksum\");\n      XrdOucString commitsize = pOpaque->Get(\"mgm.file.commit.size\");\n      XrdOucString commitfmd = pOpaque->Get(\"mgm.file.commit.fmd\");\n      XrdOucString verifyrate = pOpaque->Get(\"mgm.file.verify.rate\");\n      XrdOucString sendresync = pOpaque->Get(\"mgm.file.resync\");\n      bool doresync = false;\n\n      if (computechecksum == \"1\") {\n        option += \"&mgm.verify.compute.checksum=1\";\n      }\n\n      if (commitchecksum == \"1\") {\n        option += \"&mgm.verify.commit.checksum=1\";\n      }\n\n      if (commitsize == \"1\") {\n        option += \"&mgm.verify.commit.size=1\";\n      }\n\n      if (commitfmd == \"1\") {\n        option += \"&mgm.verify.commit.fmd=1\";\n      }\n\n      if (verifyrate.length()) {\n        option += \"&mgm.verify.rate=\";\n        option += verifyrate;\n      }\n\n      if (sendresync == \"1\") {\n        doresync = true;\n      }\n\n      XrdOucString fsidfilter = pOpaque->Get(\"mgm.file.verify.filterid\");\n      int acceptfsid = 0;\n\n      if (fsidfilter.length()) {\n        acceptfsid = atoi(pOpaque->Get(\"mgm.file.verify.filterid\"));\n      }\n\n      // only root can do that\n      if (pVid->uid == 0) {\n        eos::common::RWMutexReadLock viewReadLock(gOFS->eosViewRWMutex);\n        std::shared_ptr<eos::IFileMD> fmd;\n\n        if ((spath.beginswith(\"fid:\") || (spath.beginswith(\"fxid:\")))) {\n          WAIT_BOOT;\n          unsigned long long fid = Resolver::retrieveFileIdentifier(\n                                     spath).getUnderlyingUInt64();\n\n          // -------------------------------------------------------------------\n          // reference by fid+fsid\n          // -------------------------------------------------------------------\n\n          try {\n            fmd = gOFS->eosFileService->getFileMD(fid);\n            std::string fullpath = gOFS->eosView->getUri(fmd.get());\n            spath = fullpath.c_str();\n          } catch (eos::MDException& e) {\n            errno = e.getErrno();\n            stdErr = \"error: cannot retrieve file meta data - \";\n            stdErr += e.getMessage().str().c_str();\n            eos_debug(\"caught exception %d %s\\n\",\n                      e.getErrno(),\n                      e.getMessage().str().c_str());\n          }\n        } else {\n          // -------------------------------------------------------------------\n          // reference by path\n          // -------------------------------------------------------------------\n          try {\n            fmd = gOFS->eosView->getFile(spath.c_str());\n          } catch (eos::MDException& e) {\n            errno = e.getErrno();\n            stdErr = \"error: cannot retrieve file meta data - \";\n            stdErr += e.getMessage().str().c_str();\n            eos_debug(\"caught exception %d %s\\n\",\n                      e.getErrno(),\n                      e.getMessage().str().c_str());\n          }\n        }\n\n        if (fmd) {\n          // -------------------------------------------------------------------\n          // copy out the locations vector\n          // -------------------------------------------------------------------\n          eos::IFileMD::LocationVector::const_iterator it;\n          bool isRAIN = false;\n          eos::IFileMD::LocationVector locations = fmd->getLocations();\n          eos::common::LayoutId::layoutid_t fmdlid = fmd->getLayoutId();\n          eos::common::FileId::fileid_t fileid = fmd->getId();\n\n          if ((eos::common::LayoutId::GetLayoutType(fmdlid) ==\n               eos::common::LayoutId::kRaidDP) ||\n              (eos::common::LayoutId::GetLayoutType(fmdlid) ==\n               eos::common::LayoutId::kArchive) ||\n              (eos::common::LayoutId::GetLayoutType(fmdlid) ==\n               eos::common::LayoutId::kRaid6)\n             ) {\n            isRAIN = true;\n          }\n\n          if (computechecksum == \"1\" && commitchecksum == \"1\") {\n            auto dmd = gOFS->eosDirectoryService->getContainerMD(fmd->getContainerId());\n            eos::IContainerMD::XAttrMap attrmap;\n            eos::listAttributes(gOFS->eosView, dmd.get(), attrmap, false);\n\n            if (attrmap.count(SYS_ALTCHECKSUMS)) {\n              option += \"&mgm.verify.compute.altchecksum=\";\n              option += attrmap[SYS_ALTCHECKSUMS].c_str();\n            }\n          }\n\n          viewReadLock.Release();\n          retc = 0;\n          bool acceptfound = false;\n\n          for (it = locations.begin(); it != locations.end(); ++it) {\n            if (acceptfsid && (acceptfsid != (int) *it)) {\n              continue;\n            }\n\n            if (acceptfsid) {\n              acceptfound = true;\n            }\n\n            if (doresync) {\n              int lretc = gOFS->QueryResync(fileid, (int) * it, true);\n\n              if (!lretc) {\n                stdOut += \"success: sending FMD resync to fsid=\";\n                stdOut += (int) * it;\n                stdOut += \" for path=\";\n                stdOut += spath;\n                stdOut += \"\\n\";\n              } else {\n                stdErr = \"error: failed to send FMD resync to fsid=\";\n                stdErr += (int) * it;\n                stdErr += \"\\n\";\n                retc = errno;\n              }\n            } else {\n              if (isRAIN) {\n                int lretc = gOFS->QueryResync(fileid, (int) * it);\n\n                if (!lretc) {\n                  stdOut += \"success: sending resync for RAIN layout to fsid=\";\n                  stdOut += (int) * it;\n                  stdOut += \" for path=\";\n                  stdOut += spath;\n                  stdOut += \"\\n\";\n                } else {\n                  retc = errno;\n                }\n              } else {\n                // rain layouts only resync meta data records\n                int lretc = gOFS->_verifystripe(spath.c_str(), *mError, vid,\n                                                (unsigned long) * it, option.c_str());\n\n                if (!lretc) {\n                  stdOut += \"success: sending verify to fsid=\";\n                  stdOut += (int) * it;\n                  stdOut += \" for path=\";\n                  stdOut += spath;\n                  stdOut += \"\\n\";\n                } else {\n                  retc = errno;\n                }\n              }\n            }\n\n            // -------------------------------------------------------------------\n            // we want to be able to force the registration and verification of a\n            // not registered replica\n            // -------------------------------------------------------------------\n            if (acceptfsid && (!acceptfound)) {\n              int lretc = gOFS->_verifystripe(spath.c_str(), *mError, vid,\n                                              (unsigned long) acceptfsid, option.c_str());\n\n              if (!lretc) {\n                stdOut += \"success: sending forced verify to fsid=\";\n                stdOut += acceptfsid;\n                stdOut += \" for path=\";\n                stdOut += spath;\n                stdOut += \"\\n\";\n              } else {\n                retc = errno;\n              }\n            }\n          }\n        }\n\n        //-------------------------------------------\n      } else {\n        retc = EPERM;\n        stdErr = \"error: you have to take role 'root' to execute this command\";\n      }\n    }\n\n    // -------------------------------------------------------------------------\n    // move a replica/stripe from source fs to target fs\n    // -------------------------------------------------------------------------\n    if (mSubCmd == \"move\") {\n      cmdok = true;\n      XrdOucString sfsidsource = pOpaque->Get(\"mgm.file.sourcefsid\");\n      unsigned long sourcefsid = (sfsidsource.length()) ?\n                                 strtoul(sfsidsource.c_str(), 0, 10) : 0;\n      XrdOucString sfsidtarget = pOpaque->Get(\"mgm.file.targetfsid\");\n      unsigned long targetfsid = (sfsidsource.length()) ?\n                                 strtoul(sfsidtarget.c_str(), 0, 10) : 0;\n\n      if (gOFS->_movestripe(spath.c_str(),\n                            *mError,\n                            *pVid,\n                            sourcefsid,\n                            targetfsid)\n         ) {\n        stdErr += \"error: unable to move stripe\";\n        retc = errno;\n      } else {\n        stdOut += \"success: scheduled move from source fs=\";\n        stdOut += sfsidsource;\n        stdOut += \" => target fs=\";\n        stdOut += sfsidtarget;\n      }\n    }\n\n    // -------------------------------------------------------------------------\n    // replicate a replica/stripe from source fs to target fs\n    // -------------------------------------------------------------------------\n    if (mSubCmd == \"replicate\") {\n      cmdok = true;\n      XrdOucString sfsidsource = pOpaque->Get(\"mgm.file.sourcefsid\");\n      unsigned long sourcefsid = (sfsidsource.length()) ?\n                                 strtoul(sfsidsource.c_str(), 0, 10) : 0;\n      XrdOucString sfsidtarget = pOpaque->Get(\"mgm.file.targetfsid\");\n      unsigned long targetfsid = (sfsidtarget.length()) ?\n                                 strtoul(sfsidtarget.c_str(), 0, 10) : 0;\n\n      if (gOFS->_copystripe(spath.c_str(), *mError, *pVid, sourcefsid, targetfsid)) {\n        stdErr += \"error: unable to replicate stripe\";\n        retc = errno;\n      } else {\n        stdOut += \"success: scheduled replication from source fs=\";\n        stdOut += sfsidsource;\n        stdOut += \" => target fs=\";\n        stdOut += sfsidtarget;\n      }\n    }\n\n    // -------------------------------------------------------------------------\n    // create URLs to share a file without authentication\n    // -------------------------------------------------------------------------\n    if (mSubCmd == \"share\") {\n      cmdok = true;\n      XrdOucString sexpires = pOpaque->Get(\"mgm.file.expires\");\n      time_t expires = (sexpires.length()) ?\n                       (time_t) strtoul(sexpires.c_str(), 0, 10) : 0;\n\n      if (!expires) {\n        // default is 30 days\n        expires = (time_t)(time(NULL) + (30 * 86400));\n      }\n\n      std::string sharepath;\n      sharepath = gOFS->CreateSharePath(spath.c_str(), \"\", expires, *mError, *pVid);\n\n      if (vid.uid != 0) {\n        // non-root users cannot create shared URLs with validity > 90 days\n        if ((expires - time(NULL)) > (90 * 86400)) {\n          stdErr += \"error: you cannot request shared URLs with a validity longer than 90 days!\\n\";\n          errno = EINVAL;\n          retc = EINVAL;\n          sharepath = \"\";\n        }\n      }\n\n      if (!sharepath.length()) {\n        stdErr += \"error: unable to create URLs for file sharing\";\n        retc = errno;\n      } else {\n        XrdOucString httppath = \"http://\";\n        httppath += gOFS->HostName;\n        httppath += \":\";\n        httppath += gOFS->mHttpdPort;\n        httppath += \"/\";\n        size_t qpos = sharepath.find(\"?\");\n        std::string httpunenc = sharepath;\n        httpunenc.erase(qpos);\n        std::string httpenc = eos::common::StringConversion::curl_escaped(httpunenc);\n        // remove /#curl#\n        httpenc.erase(0, 7);\n        httppath += httpenc.c_str();\n        httppath += httpenc.c_str();\n        XrdOucString cgi = sharepath.c_str();\n        cgi.erase(0, qpos);\n\n        while (cgi.replace(\"+\", \"%2B\", qpos)) {\n        }\n\n        httppath += cgi.c_str();\n        XrdOucString rootUrl = \"root://\";\n        rootUrl += gOFS->ManagerId;\n        rootUrl += \"/\";\n        rootUrl += sharepath.c_str();\n\n        if (mHttpFormat) {\n          stdOut += \"<h4 id=\\\"sharevalidity\\\" >File Sharing Links: [ valid until  \";\n          struct tm* newtime;\n          newtime = localtime(&expires);\n          stdOut += asctime(newtime);\n          stdOut.erase(stdOut.length() - 1);\n          stdOut += \" ]</h4>\\n\";\n          stdOut += path;\n          stdOut += \"<table border=\\\"0\\\"><tr><td>\";\n          stdOut += \"<img alt=\\\"\\\" src=\\\"data:image/gif;base64,R0lGODlhEAANAJEAAAJ6xv///wAAAAAAACH5BAkAAAEALAAAAAAQAA0AAAg0AAMIHEiwoMGDCBMqFAigIYCFDBsadPgwAMWJBB1axBix4kGPEhN6HDgyI8eTJBFSvEgwIAA7\\\">\";\n          stdOut += \"<a id=\\\"httpshare\\\" href=\\\"\";\n          stdOut += httppath.c_str();\n          stdOut += \"\\\">HTTP</a></td>\";\n          stdOut += \"</tr><tr><td>\";\n          stdOut += \"<img alt=\\\"\\\" src=\\\"data:image/gif;base64,R0lGODlhEAANAJEAAAJ6xv///wAAAAAAACH5BAkAAAEALAAAAAAQAA0AAAg0AAMIHEiwoMGDCBMqFAigIYCFDBsadPgwAMWJBB1axBix4kGPEhN6HDgyI8eTJBFSvEgwIAA7\\\">\";\n          stdOut += \"<a id=\\\"rootshare\\\" href=\\\"\";\n          stdOut += rootUrl.c_str();\n          stdOut += \"\\\">ROOT</a></td>\";\n          stdOut += \"</tr></table>\\n\";\n        } else {\n          stdOut += \"[ root ]: \";\n          stdOut += rootUrl;\n          stdOut += \"\\n\";\n          stdOut += \"[ http ]: \";\n          stdOut += httppath;\n          stdOut += \"\\n\";\n        }\n      }\n    }\n\n    // -------------------------------------------------------------------------\n    // rename a file or directory from source to target path\n    // -------------------------------------------------------------------------\n    if (mSubCmd == \"rename\") {\n      cmdok = true;\n      XrdOucString source = pOpaque->Get(\"mgm.file.source\");\n      XrdOucString target = pOpaque->Get(\"mgm.file.target\");\n      if (!source.length()) {\n        source = spath;\n      }\n      if (!ResolveIdentifierToPath(source, source, stdErr, retc)) {\n        return SFS_OK;\n      }\n      if (!ResolveIdentifierToPath(target, target, stdErr, retc)) {\n        return SFS_OK;\n      }\n\n      PROC_MOVE_TOKENSCOPE(source.c_str(), target.c_str());\n\n      if (gOFS->rename(source.c_str(), target.c_str(), *mError, *pVid, 0, 0, true)) {\n        stdErr += \"error: \";\n        stdErr += mError->getErrText();\n        retc = errno;\n      } else {\n        stdOut += \"success: renamed '\";\n        stdOut += source.c_str();\n        stdOut += \"' to '\";\n        stdOut += target.c_str();\n        stdOut += \"'\";\n      }\n    }\n\n    //--------------------------------------------------------------------------\n    // Rename a file by atomically creting a symlink in the old location that\n    // will point to the new destination.\n    //--------------------------------------------------------------------------\n    if (mSubCmd == \"rename_with_symlink\") {\n      cmdok = true;\n      XrdOucString source = pOpaque->Get(\"mgm.file.source\");\n      XrdOucString target = pOpaque->Get(\"mgm.file.target\");\n\n      if (!ResolveIdentifierToPath(source, source, stdErr, retc)) {\n        return SFS_OK;\n      }\n      if (!ResolveIdentifierToPath(target, target, stdErr, retc)) {\n        return SFS_OK;\n      }\n\n      PROC_MOVE_TOKENSCOPE(source.c_str(), target.c_str());\n\n      if (gOFS->_rename_with_symlink(source.c_str(), target.c_str(),\n                                     *mError, *pVid, 0, 0, true, true)) {\n        stdErr += \"error: \";\n        stdErr += mError->getErrText();\n        retc = errno;\n      } else {\n        stdOut += \"success: renamed '\";\n        stdOut += source.c_str();\n        stdOut += \"' to '\";\n        stdOut += target.c_str();\n        stdOut += \"'\";\n      }\n    }\n\n    //--------------------------------------------------------------------------\n    // Link a file or directory from source to target path\n    //--------------------------------------------------------------------------\n    if (mSubCmd == \"symlink\") {\n      cmdok = true;\n      XrdOucString source = pOpaque->Get(\"mgm.file.source\");\n      XrdOucString target = pOpaque->Get(\"mgm.file.target\");\n      XrdOucString forceS = pOpaque->Get(\"mgm.file.force\");\n      bool force = (forceS == \"1\");\n\n      if (!ResolveIdentifierToPath(source, source, stdErr, retc)) {\n        return SFS_OK;\n      }\n      if (!ResolveIdentifierToPath(target, target, stdErr, retc)) {\n        return SFS_OK;\n      }\n\n      if (gOFS->symlink(source.c_str(), target.c_str(), *mError, *pVid, 0, 0,\n                        force)) {\n        stdErr += \"error: unable to link\";\n        retc = errno;\n      } else {\n        stdOut += \"success: linked '\";\n        stdOut += source.c_str();\n        stdOut += \"' to '\";\n        stdOut += target.c_str();\n        stdOut += \"'\";\n      }\n    }\n\n    // -------------------------------------------------------------------------\n    // trigger a workflow on a given file\n    // -------------------------------------------------------------------------\n    if (mSubCmd == \"workflow\") {\n      cmdok = true;\n      XrdOucString event = pOpaque->Get(\"mgm.event\");\n      XrdOucString workflow = pOpaque->Get(\"mgm.workflow\");\n      unsigned long long fid = 0;\n\n      if (!event.length() || !workflow.length()) {\n        stdErr = \"error: you have to specify a workflow and an event!\\n\";\n        retc = EINVAL;\n        return SFS_OK;\n      }\n\n      if ((spath.beginswith(\"fid:\") || (spath.beginswith(\"fxid:\")))) {\n        //-------------------------------------------\n        // reference by fid+fsid\n        //-------------------------------------------\n        unsigned long long fid = Resolver::retrieveFileIdentifier(\n                                   spath).getUnderlyingUInt64();\n        eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n        std::shared_ptr<eos::IFileMD> fmd;\n\n        try {\n          fmd = gOFS->eosFileService->getFileMD(fid);\n          spath = gOFS->eosView->getUri(fmd.get()).c_str();\n        } catch (eos::MDException& e) {\n          eos_debug(\"caught exception %d %s\\n\",\n                    e.getErrno(),\n                    e.getMessage().str().c_str());\n          stdErr += \"error: \";\n          stdErr += mError->getErrText();\n          retc = errno;\n          return SFS_OK;\n        }\n      } else {\n        eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n        std::shared_ptr<eos::IFileMD> fmd;\n\n        try {\n          fmd = gOFS->eosView->getFile(spath.c_str());\n          fid = fmd->getId();\n        } catch (eos::MDException& e) {\n          eos_debug(\"caught exception %d %s\\n\",\n                    e.getErrno(),\n                    e.getMessage().str().c_str());\n          stdErr += \"error: \";\n          stdErr += mError->getErrText();\n          retc = errno;\n          return SFS_OK;\n        }\n      }\n\n      XrdSfsFSctl args;\n      XrdOucString opaque = \"mgm.pcmd=event&mgm.fid=\";\n      XrdOucString lSec;\n      opaque += eos::common::FileId::Fid2Hex(fid).c_str();\n      opaque += \"&mgm.logid=\";\n      opaque += logId;\n      opaque += \"&mgm.event=\";\n      opaque += event.c_str();\n      opaque += \"&mgm.workflow=\";\n      opaque += workflow.c_str();\n      opaque += \"&mgm.path=\";\n      opaque += spath.c_str();\n      opaque += \"&mgm.ruid=\";\n      opaque += (int) vid.uid;\n      opaque += \"&mgm.rgid=\";\n      opaque += (int) vid.gid;\n      XrdSecEntity lClient(pVid->prot.c_str());\n      lClient.name = (char*) pVid->name.c_str();\n      lClient.tident = (char*) pVid->tident.c_str();\n      lClient.host = (char*) pVid->host.c_str();\n      lSec = \"&mgm.sec=\";\n      lSec += eos::common::SecEntity::ToKey(&lClient, \"eos\").c_str();\n      opaque += lSec;\n      args.Arg1 = spath.c_str();\n      args.Arg1Len = spath.length();\n      args.Arg2 = opaque.c_str();\n      args.Arg2Len = opaque.length();\n\n      if (gOFS->FSctl(SFS_FSCTL_PLUGIN,\n                      args,\n                      *mError,\n                      &lClient) != SFS_DATA) {\n        stdErr += \"error: unable to run workflow '\";\n        stdErr += event.c_str();\n        stdErr += \"' : \";\n        stdErr += mError->getErrText();\n        retc = errno;\n      } else {\n        stdOut += \"success: triggered workflow  '\";\n        stdOut += event.c_str();\n        stdOut += \"' on '\";\n        stdOut += spath.c_str();\n        stdOut += \"'\";\n      }\n    }\n\n    // -------------------------------------------------------------------------\n    // tag/untag a file to be located on a certain file system\n    // -------------------------------------------------------------------------\n    if (mSubCmd == \"tag\") {\n      cmdok = true;\n\n      if (!((vid.prot == \"sss\") && vid.hasUid(DAEMONUID)) && (vid.uid != 0)) {\n        stdErr = \"error: permission denied - you have to be root to \"\n                 \"run the 'tag' command\";\n        retc = EPERM;\n        return SFS_OK;\n      }\n\n      bool do_add = false;\n      bool do_rm = false;\n      bool do_unlink = false;\n      XrdOucString sfsid = pOpaque->Get(\"mgm.file.tag.fsid\");\n\n      if (sfsid.beginswith(\"+\")) {\n        do_add = true;\n      }\n\n      if (sfsid.beginswith(\"-\")) {\n        do_rm = true;\n      }\n\n      if (sfsid.beginswith(\"~\")) {\n        do_unlink = true;\n      }\n\n      sfsid.erase(0, 1);\n      errno = 0;\n      int fsid = (sfsid.c_str()) ? (int) strtol(sfsid.c_str(), 0, 10) : 0;\n\n      if (errno || (fsid == 0) || (!do_add && !do_rm && !do_unlink)) {\n        stdErr = \"error: no valid filesystem id and/or operation (+/-/~) \"\n                 \"provided e.g. 'file tag /myfile +1000'\\n\";\n        stdErr += sfsid;\n        retc = EINVAL;\n        return SFS_OK;\n      } else {\n        std::shared_ptr<eos::IFileMD> fmd = nullptr;\n\n        try {\n          if (fid) {\n            fmd = gOFS->eosFileService->getFileMD(fid);\n          } else {\n            fmd = gOFS->eosView->getFile(spath.c_str());\n          }\n\n          eos::MDLocking::FileWriteLock fwLock(fmd.get());\n\n          if (do_add && fmd->hasLocation(fsid)) {\n            stdErr += \"error: file '\";\n            stdErr += spath.c_str();\n            stdErr += \"' is already located on fs=\";\n            stdErr += (int) fsid;\n            retc = EINVAL;\n            return SFS_OK;\n          } else if ((do_rm || do_unlink) &&\n                     (!fmd->hasLocation(fsid) &&\n                      !fmd->hasUnlinkedLocation(fsid))) {\n            stdErr += \"error: file '\";\n            stdErr += spath.c_str();\n            stdErr += \"' is not located on fs=\";\n            stdErr += (int) fsid;\n            retc = EINVAL;\n            return SFS_OK;\n          } else {\n            if (do_add) {\n              fmd->addLocation(fsid);\n              stdOut += \"success: added location to file '\";\n            }\n\n            if (do_rm || do_unlink) {\n              fmd->unlinkLocation(fsid);\n\n              if (do_rm) {\n                stdOut += \"success: removed location from file '\";\n                fmd->removeLocation(fsid);\n              } else {\n                stdOut += \"success: unlinked location from file '\";\n              }\n            }\n\n            gOFS->eosView->updateFileStore(fmd.get());\n            stdOut += spath.c_str();\n            stdOut += \"' on fs=\";\n            stdOut += (int) fsid;\n          }\n        } catch (eos::MDException& e) {\n          errno = e.getErrno();\n          eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                    e.getMessage().str().c_str());\n        }\n\n        if (!fmd) {\n          stdErr += \"error: unable to get file meta data of file '\";\n          stdErr += spath.c_str();\n          stdErr += \"'\";\n          retc = errno;\n          return SFS_OK;\n        }\n      }\n    }\n\n    // Third-party copy files/directories\n    if (mSubCmd == \"copy\") {\n      cmdok = true;\n      XrdOucString src = spath;\n      XrdOucString dst = pOpaque->Get(\"mgm.file.target\");\n\n      if (!dst.length()) {\n        stdErr += \"error: missing destination argument\";\n        retc = EINVAL;\n      } else {\n        struct stat srcbuf;\n        struct stat dstbuf;\n\n        // check that we can access source and destination\n        if (gOFS->_stat(src.c_str(), &srcbuf, *mError, *pVid, \"\")) {\n          stdErr += \"error: \";\n          stdErr += mError->getErrText();\n          retc = errno;\n        } else {\n          XrdOucString option = pOpaque->Get(\"mgm.file.option\");\n          bool silent = false;\n\n          if ((option.find(\"s\")) != STR_NPOS) {\n            silent = true;\n          } else {\n            if ((option.find(\"c\")) != STR_NPOS) {\n              stdOut += \"info: cloning '\";\n            } else {\n              stdOut += \"info: copying '\";\n            }\n\n            stdOut += spath;\n            stdOut += \"' => '\";\n            stdOut += dst;\n            stdOut += \"' ...\\n\";\n          }\n\n          int dstat = gOFS->_stat(dst.c_str(), &dstbuf, *mError, *pVid, \"\");\n\n          if ((option.find(\"f\") == STR_NPOS) && !dstat) {\n            // there is no force flag and the target exists\n            stdErr += \"error: the target file exists - use '-f' to force the copy\";\n            retc = EEXIST;\n          } else {\n            // check source and destination access\n            if (gOFS->_access(src.c_str(), R_OK, *mError, *pVid, \"\") ||\n                gOFS->_access(dst.c_str(), W_OK, *mError, *pVid, \"\")) {\n              stdErr += \"error: \";\n              stdErr += mError->getErrText();\n              retc = errno;\n            } else {\n              std::vector<std::string> lCopySourceList;\n              std::vector<std::string> lCopyTargetList;\n              // If this is a directory create a list of files to copy\n              std::map < std::string, std::set < std::string >> found;\n\n              if (S_ISDIR(srcbuf.st_mode) && S_ISDIR(dstbuf.st_mode)) {\n                if (!gOFS->_find(src.c_str(), *mError, stdErr, *pVid, found)) {\n                  // Add all to the copy source,target list ...\n                  for (auto dirit = found.begin(); dirit != found.end(); dirit++) {\n                    // Loop over dirs and add all the files\n                    for (auto fileit = dirit->second.begin(); fileit != dirit->second.end();\n                         fileit++) {\n                      std::string src_path = dirit->first;\n                      std::string end_path = src_path;\n                      end_path.erase(0, src.length());\n                      src_path += *fileit;\n                      std::string dst_path = dst.c_str();\n                      dst_path += end_path;\n                      dst_path += *fileit;\n                      lCopySourceList.push_back(src_path.c_str());\n                      lCopyTargetList.push_back(dst_path.c_str());\n                      stdOut += \"info: copying '\";\n                      stdOut += src_path.c_str();\n                      stdOut += \"' => '\";\n                      stdOut += dst_path.c_str();\n                      stdOut += \"' ... \\n\";\n                    }\n                  }\n                } else {\n                  stdErr += \"error: find failed\";\n                }\n              } else {\n                // Add a single file to the copy list\n                lCopySourceList.push_back(src.c_str());\n                lCopyTargetList.push_back(dst.c_str());\n              }\n\n              for (size_t i = 0; i < lCopySourceList.size(); i++) {\n                // Setup a TPC job\n                XrdCl::PropertyList properties;\n                XrdCl::PropertyList result;\n\n                if (srcbuf.st_size) {\n                  // TPC for non-empty files\n                  properties.Set(\"thirdParty\", \"only\");\n                }\n\n                properties.Set(\"force\", true);\n                properties.Set(\"posc\", false);\n                properties.Set(\"coerce\", false);\n                std::string source = lCopySourceList[i];\n                std::string target = lCopyTargetList[i];\n                std::string sizestring;\n                std::string cgi = \"eos.ruid=\";\n                cgi += eos::common::StringConversion::GetSizeString(sizestring,\n                       (unsigned long long) pVid->uid);\n                cgi += \"&eos.rgid=\";\n                cgi += eos::common::StringConversion::GetSizeString(sizestring,\n                       (unsigned long long) pVid->gid);\n                cgi += \"&eos.app=filecopy\";\n\n                if ((option.find(\"c\")) != STR_NPOS) {\n                  char clonetime[256];\n                  snprintf(clonetime, sizeof(clonetime) - 1, \"&eos.ctime=%lu&eos.mtime=%lu\",\n                           srcbuf.st_ctime, srcbuf.st_mtime);\n                  cgi += clonetime;\n                }\n\n                XrdCl::URL url_src;\n                url_src.SetProtocol(\"root\");\n                url_src.SetHostName(\"localhost\");\n                url_src.SetUserName(\"root\");\n                url_src.SetParams(cgi);\n                url_src.SetPath(source);\n                XrdCl::URL url_trg;\n                url_trg.SetProtocol(\"root\");\n                url_trg.SetHostName(\"localhost\");\n                url_trg.SetUserName(\"root\");\n                url_trg.SetParams(cgi);\n                url_trg.SetPath(target);\n                properties.Set(\"source\", url_src);\n                properties.Set(\"target\", url_trg);\n                properties.Set(\"sourceLimit\", (uint16_t) 1);\n                properties.Set(\"chunkSize\", (uint32_t)(4 * 1024 * 1024));\n                properties.Set(\"parallelChunks\", (uint8_t) 1);\n                XrdCl::CopyProcess lCopyProcess;\n                lCopyProcess.AddJob(properties, &result);\n                XrdCl::XRootDStatus lTpcPrepareStatus = lCopyProcess.Prepare();\n                eos_static_info(\"[tpc]: %s=>%s %s\",\n                                url_src.GetURL().c_str(),\n                                url_trg.GetURL().c_str(),\n                                lTpcPrepareStatus.ToStr().c_str());\n\n                if (lTpcPrepareStatus.IsOK()) {\n                  XrdCl::XRootDStatus lTpcStatus = lCopyProcess.Run(0);\n                  eos_static_info(\"[tpc]: %s %d\",\n                                  lTpcStatus.ToStr().c_str(),\n                                  lTpcStatus.IsOK());\n\n                  if (lTpcStatus.IsOK()) {\n                    if (!silent) {\n                      stdOut += \"success: copy done '\";\n                      stdOut += source.c_str();\n                      stdOut += \"'\\n\";\n                    }\n                  } else {\n                    stdErr += \"error: copy failed ' \";\n                    stdErr += source.c_str();\n                    stdErr += \"' - \";\n                    stdErr += lTpcStatus.ToStr().c_str();\n                    retc = EIO;\n                  }\n                } else {\n                  stdErr += \"error: copy failed - \";\n                  stdErr += lTpcPrepareStatus.ToStr().c_str();\n                  retc = EIO;\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n\n    if (mSubCmd == \"convert\") {\n      cmdok = true;\n\n      // -----------------------------------------------------------------------\n      // check access permissions on source\n      // -----------------------------------------------------------------------\n      if ((gOFS->_access(spath.c_str(), W_OK, *mError, *pVid, \"\") != SFS_OK)) {\n        stdErr += \"error: you have no write permission on '\";\n        stdErr += spath.c_str();\n        stdErr += \"'\";\n        retc = EPERM;\n      } else {\n        while (1) {\n          using eos::common::LayoutId;\n          LayoutId::eChecksum echecksum{LayoutId::eChecksum::kNone};\n          XrdOucString layout = pOpaque->Get(\"mgm.convert.layout\");\n          XrdOucString space = pOpaque->Get(\"mgm.convert.space\");\n          XrdOucString plctplcy = pOpaque->Get(\"mgm.convert.placementpolicy\");\n          XrdOucString checksum = pOpaque->Get(\"mgm.convert.checksum\");\n          XrdOucString option = pOpaque->Get(\"mgm.option\");\n\n          //stdOut += (\"Placement Policy is: \" + plctplcy);\n          if (plctplcy.length()) {\n            // -------------------------------------------------------------------\n            // check that the placement policy is valid\n            // i.e. scattered, hybrid:<geotag> or gathered:<geotag>\n            // -------------------------------------------------------------------\n            if (plctplcy != \"scattered\" &&\n                !plctplcy.beginswith(\"hybrid:\") &&\n                !plctplcy.beginswith(\"gathered:\")) {\n              stdErr += \"error: placement policy is invalid\";\n              retc = EINVAL;\n              return SFS_OK;\n            }\n\n            // Check geotag in case of hybrid or gathered policy\n            if (plctplcy != \"scattered\") {\n              std::string policy = plctplcy.c_str();\n              std::string targetgeotag = policy.substr(policy.find(':') + 1);\n              std::string tmp_geotag = eos::common::SanitizeGeoTag(targetgeotag);\n\n              if (tmp_geotag != targetgeotag) {\n                stdErr += tmp_geotag.c_str();\n                retc = EINVAL;\n                return SFS_OK;\n              }\n            }\n\n            plctplcy = \"~\" + plctplcy;\n          } else {\n            plctplcy = \"\";\n          }\n\n          if (checksum.length()) {\n            int xs = LayoutId::GetChecksumFromString(checksum.c_str());\n\n            if (xs != -1) {\n              echecksum = static_cast<LayoutId::eChecksum>(xs);\n            }\n          }\n\n          if (!space.length()) {\n            // Get target space from the layout settings\n            eos::common::Path cPath(spath.c_str());\n            eos::IContainerMD::XAttrMap map;\n            int rc = gOFS->_attr_ls(cPath.GetParentPath(), *mError, *pVid, (const char*) 0,\n                                    map);\n\n            if (rc || (!map.count(\"sys.forced.space\") && !map.count(\"user.forced.space\"))) {\n              stdErr += \"error: cannot get default space settings from parent \"\n                        \"directory attributes\";\n              retc = EINVAL;\n            } else {\n              if (map.count(\"sys.forced.space\")) {\n                space = map[\"sys.forced.space\"].c_str();\n              } else {\n                space = map[\"user.forced.space\"].c_str();\n              }\n            }\n          }\n\n          if (space.length()) {\n            if (!layout.length() && (option != \"rewrite\")) {\n              stdErr += \"error: conversion layout has to be defined\";\n              retc = EINVAL;\n            } else {\n              // get the file meta data\n              std::shared_ptr<eos::IFileMD> fmd;\n              int fsid = 0;\n              eos::common::LayoutId::layoutid_t layoutid = 0;\n              eos::common::FileId::fileid_t fileid = 0;\n              {\n                eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n\n                try {\n                  fmd = gOFS->eosView->getFile(spath.c_str());\n                  layoutid = fmd->getLayoutId();\n                  fileid = fmd->getId();\n\n                  if (fmd->getNumLocation()) {\n                    eos::IFileMD::LocationVector loc_vect = fmd->getLocations();\n                    fsid = *(loc_vect.begin());\n                  }\n                } catch (eos::MDException& e) {\n                  errno = e.getErrno();\n                  eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                            e.getMessage().str().c_str());\n                }\n              }\n\n              if (!fmd) {\n                stdErr += \"error: unable to get file meta data of file \";\n                stdErr += spath.c_str();\n                retc = errno;\n              } else {\n                std::string conversiontag;\n\n                if (option == \"rewrite\") {\n                  if (layout.length() == 0) {\n                    stdOut += \"info: rewriting file with identical layout id\\n\";\n                    char hexlayout[17];\n                    snprintf(hexlayout, sizeof(hexlayout) - 1, \"%08llx\",\n                             (long long) layoutid);\n                    layout = hexlayout;\n                  }\n\n                  // get the space this file is currently hosted\n                  if (!fsid) {\n                    // bummer, this file has not even a single replica\n                    stdErr += \"error: file has no replica attached\\n\";\n                    retc = ENODEV;\n                    break;\n                  }\n\n                  // figure out which space this fsid is in ...\n                  {\n                    eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n                    FileSystem* filesystem = FsView::gFsView.mIdView.lookupByID(fsid);\n\n                    if (!filesystem) {\n                      stdErr += \"error: couldn't find filesystem in view\\n\";\n                      retc = EINVAL;\n                      break;\n                    }\n\n                    // get the space of that filesystem\n                    space = filesystem->GetString(\"schedgroup\").c_str();\n                    space.erase(space.find(\".\"));\n                    stdOut += \"info:: rewriting into space '\";\n                    stdOut += space;\n                    stdOut += \"'\\n\";\n                  }\n                }\n\n                if (eos::common::StringConversion::IsHexNumber(layout.c_str(), \"%08x\")) {\n                  conversiontag = ConversionTag::Get(fileid, space.c_str(), layout.c_str(),\n                                                     std::string(\"\"), false);\n                  stdOut += \"info: conversion based on hexadecimal layout id\\n\";\n                } else {\n                  unsigned long layout_type = 0;\n                  unsigned long layout_stripes = 0;\n                  // check if it was provided as <layout>:<stripes>\n                  std::string lLayout = layout.c_str();\n                  std::string lLayoutName;\n                  std::string lLayoutStripes;\n\n                  if (eos::common::StringConversion::SplitKeyValue(lLayout,\n                      lLayoutName,\n                      lLayoutStripes)) {\n                    XrdOucString lLayoutString = \"eos.layout.type=\";\n                    lLayoutString += lLayoutName.c_str();\n                    lLayoutString += \"&eos.layout.nstripes=\";\n                    lLayoutString += lLayoutStripes.c_str();\n                    // ---------------------------------------------------------------\n                    // add block checksumming and the default blocksize of 4 M\n                    // ---------------------------------------------------------------\n\n                    // ---------------------------------------------------------------\n                    // unless explicitely stated, use the layout checksum\n                    // ---------------------------------------------------------------\n                    if (echecksum == eos::common::LayoutId::eChecksum::kNone) {\n                      echecksum = static_cast<eos::common::LayoutId::eChecksum>(\n                                    eos::common::LayoutId::GetChecksum(layoutid));\n                    }\n\n                    XrdOucEnv lLayoutEnv(lLayoutString.c_str());\n                    layout_type =\n                      eos::common::LayoutId::GetLayoutFromEnv(lLayoutEnv);\n                    layout_stripes =\n                      eos::common::LayoutId::GetStripeNumberFromEnv(lLayoutEnv);\n                    // ---------------------------------------------------------------\n                    // re-create layout id by merging in the layout stripes, type & checksum\n                    // ---------------------------------------------------------------\n                    layoutid =\n                      eos::common::LayoutId::GetId(layout_type,\n                                                   echecksum,\n                                                   layout_stripes,\n                                                   eos::common::LayoutId::k4M,\n                                                   eos::common::LayoutId::kCRC32C,\n                                                   eos::common::LayoutId::GetRedundancyStripeNumber(layoutid));\n                    conversiontag = ConversionTag::Get(fileid, space.c_str(), layoutid,\n                                                       plctplcy.c_str(), false);\n                    stdOut += \"info: conversion based layout+stripe arguments\\n\";\n                  } else {\n                    // assume this is the name of an attribute\n                    conversiontag = ConversionTag::Get(fileid, space.c_str(), layout.c_str(),\n                                                       plctplcy.c_str(), false);\n                    stdOut += \"info: conversion based conversion attribute name\\n\";\n                  }\n                }\n\n                std::string err_msg;\n\n                // Push conversion job to QuarkDB\n                if (gOFS->mConverterEngine->ScheduleJob(fmd->getId(),\n                                                        conversiontag, err_msg)) {\n                  stdOut += \"success: pushed conversion job '\";\n                  stdOut += conversiontag.c_str();\n                  stdOut += \"' to QuarkDB\";\n                } else {\n                  stdErr += \"error: failed to schedule conversion '\";\n                  stdErr += conversiontag.c_str();\n\n                  if (err_msg.empty()) {\n                    stdErr += \" msg=\\\"\";\n                    stdErr += err_msg.c_str();\n                    stdErr += \"\\\"\";\n                  }\n\n                  retc = EINVAL;\n                  break;\n                }\n              }\n            }\n          }\n\n          break; // while 1\n        }\n      }\n    }\n\n    // -------------------------------------------------------------------------\n    // touch a file\n    // -------------------------------------------------------------------------\n    if (mSubCmd == \"touch\") {\n      cmdok = true;\n      bool useLayout = true;\n      bool truncate = false;\n      bool absorb = false;\n      bool lock = false;\n      bool unlock = false;\n      time_t lifetime = 86400;\n      size_t size = 0;\n      const char* hardlinkpath = 0;\n      const char* checksuminfo = 0;\n      std::string errmsg;\n\n      if (pOpaque->Get(\"mgm.file.touch.nolayout\")) {\n        useLayout = false;\n      }\n\n      if (pOpaque->Get(\"mgm.file.touch.truncate\")) {\n        truncate = true;\n      }\n\n      if (pOpaque->Get(\"mgm.file.touch.size\")) {\n        size = strtoull(pOpaque->Get(\"mgm.file.touch.size\"), 0, 10);\n      }\n\n      if (pOpaque->Get(\"mgm.file.touch.absorb\")) {\n        absorb = true;\n      }\n\n      char* lockop = 0;\n      char* wildcard = pOpaque->Get(\"mgm.file.touch.wildcard\");\n      bool userwildcard = false;\n      bool appwildcard = false;\n\n      if (wildcard && (std::string(wildcard) != \"user\") &&\n          (std::string(wildcard) != \"app\")) {\n        stdErr = \"error: invalid wildcard type specified, can be only 'user' or 'app'\\n\";\n        retc = EINVAL;\n        return SFS_OK;\n      } else {\n        if (wildcard) {\n          if (std::string(wildcard) == \"user\") {\n            userwildcard = true;\n          } else {\n            appwildcard = true;\n          }\n        }\n      }\n\n      if ((lockop = pOpaque->Get(\"mgm.file.touch.lockop\"))) {\n        if (std::string(lockop) == \"lock\") {\n          lock = true;\n          unlock = false;\n        } else if (std::string(lockop) == \"unlock\") {\n          unlock = true;\n          lock = false;\n        } else {\n          stdErr = \"error: invalid lock operation specified - can be either 'lock' or 'unlock' '\";\n          stdErr += lockop;\n          stdErr += \"'\";\n          retc = EINVAL;\n          return SFS_OK;\n        }\n      }\n\n      char* lock_lifetime = 0;\n\n      if ((lock_lifetime = pOpaque->Get(\"mgm.file.touch.lockop.lifetime\"))) {\n        lifetime = atoi(lock_lifetime);\n      }\n\n      hardlinkpath = pOpaque->Get(\"mgm.file.touch.hardlinkpath\");\n      checksuminfo = pOpaque->Get(\"mgm.file.touch.checksuminfo\");\n\n      if (!spath.length()) {\n        stdErr = \"error: There is no file with given id! '\";\n        stdErr += spathid;\n        stdErr += \"'\";\n        retc = ENOENT;\n      } else {\n        if (gOFS->_touch(spath.c_str(), *mError, *pVid, 0, true, useLayout, truncate,\n                         size, absorb, hardlinkpath, checksuminfo, &errmsg)) {\n          stdErr = \"error: unable to touch '\";\n          stdErr += spath.c_str();\n          stdErr += \"'\";\n\n          if (errmsg.length()) {\n            stdErr += \"\\n\";\n            stdErr += errmsg.c_str();\n          }\n\n          retc = errno;\n        } else {\n          if (lock) {\n            // try to set a xattr lock\n            XattrLock applock;\n            errno = 0;\n\n            if (applock.Lock(spath.c_str(), false, lifetime, *pVid, userwildcard,\n                             appwildcard)) {\n              stdOut += \"success: created exclusive lock for '\";\n              stdOut += spath.c_str();\n              stdOut += \"'\\n\";\n              stdOut += applock.Dump().c_str();\n            } else {\n              stdErr += \"error: cannot get exclusive lock for '\";\n              stdErr += spath.c_str();\n              stdErr += \"'\\n\";\n              stdErr += applock.Dump().c_str();\n              retc = errno;\n              return SFS_OK;\n            }\n          }\n\n          if (unlock) {\n            // try to unlock an xattr lock\n            XattrLock applock;\n\n            if (applock.Unlock(spath.c_str(), *pVid)) {\n              stdOut += \"success: removed exclusive lock for '\";\n              stdOut += spath.c_str();\n              stdOut += \"'\\n\";\n              stdOut += applock.Dump().c_str();\n            } else {\n              if (errno == ENODATA) {\n                stdOut += \"info: there was no exclusive lock for '\";\n                stdOut += spath.c_str();\n                stdOut += \"'\\n\";\n              } else {\n                stdErr += \"error: failed to remove exclusive lock for '\";\n                stdErr += spath.c_str();\n                stdErr += \"'\\n\";\n                stdErr += applock.Dump().c_str();\n                retc = errno;\n                return SFS_OK;\n              }\n            }\n          }\n\n          stdOut += \"success: touched '\";\n          stdOut += spath.c_str();\n          stdOut += \"'\";\n\n          if (errmsg.length()) {\n            stdOut += \"\\n\";\n            stdOut += errmsg.c_str();\n          }\n        }\n      }\n    }\n\n    //--------------------------------------------------------------------------\n    // Fix file by removing/repairing or adding replicas/stripes\n    // -------------------------------------------------------------------------\n    if (mSubCmd == \"adjustreplica\") {\n      // Only root can do that\n      cmdok = true;\n\n      if (pVid->uid) {\n        retc = EPERM;\n        stdErr = \"error: you have to take role 'root' to execute this command\";\n        return SFS_OK;\n      }\n\n      uint32_t lid = 0ul;\n      uint64_t size = 0ull;\n      unsigned long long fid = 0ull;\n      std::shared_ptr<eos::IFileMD> fmd {nullptr};\n      eos::IFileMD::LocationVector loc_vect;\n      XrdOucString file_option = pOpaque->Get(\"mgm.file.option\");\n      bool nodrop = false;\n\n      if (file_option == \"nodrop\") {\n        nodrop = true;\n      }\n\n      int icreationsubgroup = -1;\n      std::string creationspace = (pOpaque->Get(\"mgm.file.desiredspace\") ?\n                                   pOpaque->Get(\"mgm.file.desiredspace\") : \"\");\n\n      if (pOpaque->Get(\"mgm.file.desiredsubgroup\")) {\n        icreationsubgroup = atoi(pOpaque->Get(\"mgm.file.desiredsubgroup\"));\n      }\n\n      {\n        eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n\n        // Reference by fid+fsid\n        if ((spath.beginswith(\"fid:\") || (spath.beginswith(\"fxid:\")))) {\n          WAIT_BOOT;\n          unsigned long long fid = Resolver::retrieveFileIdentifier(\n                                     spath).getUnderlyingUInt64();\n\n          try {\n            fmd = gOFS->eosFileService->getFileMD(fid);\n          } catch (eos::MDException& e) {\n            errno = e.getErrno();\n            stdErr = \"error: cannot retrieve file meta data - \";\n            stdErr += e.getMessage().str().c_str();\n            eos_debug(\"caught exception %d %s\\n\",\n                      e.getErrno(),\n                      e.getMessage().str().c_str());\n          }\n        } else {\n          // Reference by path\n          try {\n            fmd = gOFS->eosView->getFile(spath.c_str());\n          } catch (eos::MDException& e) {\n            errno = e.getErrno();\n            stdErr = \"error: cannot retrieve file meta data - \";\n            stdErr += e.getMessage().str().c_str();\n            eos_debug(\"caught exception %d %s\\n\",\n                      e.getErrno(),\n                      e.getMessage().str().c_str());\n          }\n        }\n\n        if (fmd) {\n          fid = fmd->getId();\n          lid = fmd->getLayoutId();\n          loc_vect = fmd->getLocations();\n          size = fmd->getSize();\n        } else {\n          retc = errno ? errno : EINVAL;\n          return SFS_OK;\n        }\n      }\n\n      std::string refspace = \"\";\n      std::string space = \"default\";\n      unsigned int forcedsubgroup = 0;\n\n      if (eos::common::LayoutId::GetLayoutType(lid) ==\n          eos::common::LayoutId::kReplica) {\n        // Check the configured and available replicas\n        unsigned int nrep_online = 0;\n        unsigned int nrep = loc_vect.size();\n        unsigned int nrep_layout = eos::common::LayoutId::GetStripeNumber(lid) + 1;\n        // Give priority to healthy file systems during scheduling\n        std::vector<unsigned int> src_fs;\n        eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n\n        for (auto loc_it = loc_vect.begin();\n             loc_it != loc_vect.end(); ++loc_it) {\n          if (*loc_it == 0) {\n            eos_err(\"msg=\\\"skip file system with id 0\\\" fxid=%08llx\", fid);\n            continue;\n          }\n\n          FileSystem* filesystem = FsView::gFsView.mIdView.lookupByID(*loc_it);\n\n          if (filesystem) {\n            eos::common::FileSystem::fs_snapshot_t snapshot;\n            filesystem->SnapShotFileSystem(snapshot, true);\n            // Remember the spacename\n            space = snapshot.mSpace;\n\n            if (!refspace.length()) {\n              refspace = space;\n            } else {\n              if (space != refspace) {\n                eos_warning(\"msg=\\\"replicas are in different spaces\\\" \"\n                            \"fxid=%08llx space=%s req_space=%s\", fid,\n                            space.c_str(), refspace.c_str());\n                continue;\n              }\n            }\n\n            forcedsubgroup = snapshot.mGroupIndex;\n\n            if ((snapshot.mConfigStatus > eos::common::ConfigStatus::kDrain) &&\n                (snapshot.mStatus == eos::common::BootStatus::kBooted)) {\n              // This is an accessible replica\n              ++nrep_online;\n              src_fs.insert(src_fs.begin(), *loc_it);\n            } else {\n              // Give less priority to unhealthy file systems\n              src_fs.push_back(*loc_it);\n            }\n          } else {\n            eos_err(\"msg=\\\"skip unknown file system\\\" fsid=%lu fxid=%08llx\",\n                    *loc_it, fid);\n          }\n        }\n\n        eos_debug(\"path=%s nrep=%lu nrep-layout=%lu nrep-online=%lu\",\n                  spath.c_str(), nrep, nrep_layout, nrep_online);\n\n        if (nrep_layout > nrep_online) {\n          // Set the desired space & subgroup if provided\n          if (creationspace.length()) {\n            space = creationspace;\n          }\n\n          if (icreationsubgroup != -1) {\n            forcedsubgroup = icreationsubgroup;\n          }\n\n          // If space explicitly set, don't force a particular subgroup\n          if (creationspace.length()) {\n            forcedsubgroup = -1;\n          }\n\n          // Trigger async replication if not enough replicas online\n          int nrep_new = nrep_layout - nrep_online;\n          // Get the location where we can read that file\n          eos_debug(\"msg=\\\"creating %d new replicas\\\" fxid=%08llx space=%s \"\n                    \"forcedsubgroup=%d icreationsubgroup=%d\", nrep_new,\n                    fid, space.c_str(), forcedsubgroup, icreationsubgroup);\n          // This defines the fs to use in the selectedfs vector\n          unsigned long fs_indx;\n          std::vector<unsigned int> selectedfs;\n          std::vector<unsigned int> unavailfs;\n          std::vector<unsigned int> excludefs;\n\n          if (pOpaque->Get(\"mgm.file.excludefs\")) {\n            unsigned int exclude_fsid = strtoul(pOpaque->Get(\"mgm.file.excludefs\"), 0, 10);\n\n            if (exclude_fsid) {\n              excludefs.push_back(exclude_fsid);\n              src_fs.erase(std::remove(src_fs.begin(), src_fs.end(), exclude_fsid),\n                           src_fs.end());\n            }\n          }\n\n          std::string tried_cgi;\n          // Now we just need to ask for <n> targets\n          int layoutId = eos::common::LayoutId::GetId(eos::common::LayoutId::kReplica,\n                         eos::common::LayoutId::kNone, nrep_new);\n          eos::common::Path cPath(spath.c_str());\n          eos::IContainerMD::XAttrMap attrmap;\n          gOFS->_attr_ls(cPath.GetParentPath(), *mError, *pVid, (const char*) 0, attrmap);\n          eos::mgm::Scheduler::tPlctPolicy plctplcy;\n          std::string targetgeotag;\n          // Get placement policy\n          Policy::GetPlctPolicy(spath.c_str(), attrmap, *pVid, *pOpaque,\n                                plctplcy, targetgeotag);\n          // We don't know the container tag here, but we don't really\n          // care since we are scheduled as root\n          Scheduler::PlacementArguments plctargs;\n          plctargs.alreadyused_filesystems = &src_fs;\n          plctargs.bookingsize = size;\n          plctargs.forced_scheduling_group_index = forcedsubgroup;\n          plctargs.lid = layoutId;\n          plctargs.inode = (ino64_t) fid;\n          plctargs.path = spath.c_str();\n          plctargs.plctTrgGeotag = &targetgeotag;\n          plctargs.plctpolicy = plctplcy;\n          plctargs.exclude_filesystems = &excludefs;\n          plctargs.selected_filesystems = &selectedfs;\n          plctargs.spacename = &space;\n          plctargs.truncate = true;\n          plctargs.vid = pVid;\n\n          if (!plctargs.isValid()) {\n            stdErr += \"error: invalid argument for file placement\";\n            retc = EINVAL;\n          } else {\n            {\n              errno = retc = Quota::FilePlacement(&plctargs);\n            }\n\n            if (!errno) {\n              Scheduler::AccessArguments acsargs;\n              acsargs.bookingsize = 0;\n              acsargs.forcedspace = space.c_str();\n              acsargs.fsindex = &fs_indx;\n              acsargs.isRW = false;\n              acsargs.lid = (unsigned long) lid;\n              acsargs.inode = (ino64_t) fid;\n              acsargs.locationsfs = &src_fs;\n              acsargs.tried_cgi = &tried_cgi;\n              acsargs.unavailfs = &unavailfs;\n              acsargs.vid = pVid;\n\n              if (!acsargs.isValid()) {\n                stdErr += \"error: invalid argument for file access\";\n                retc = EINVAL;\n              } else {\n                // We got a new replication vector\n                for (unsigned int i = 0; i < selectedfs.size(); ++i) {\n                  errno = Scheduler::FileAccess(&acsargs);\n\n                  if (!errno) {\n                    // This is now our source filesystem\n                    unsigned int src_fsid = src_fs[fs_indx];\n\n                    if (gOFS->_replicatestripe(fmd.get(), spath.c_str(),\n                                               *mError, *pVid, src_fsid,\n                                               selectedfs[i], false)) {\n                      retc = mError->getErrInfo();\n                      stdErr = SSTR(\"error: unable to replicate stripe \"\n                                    << src_fsid << \" => \" << selectedfs[i]\n                                    << \" msg=\" << mError->getErrText()\n                                    << std::endl).c_str();\n\n                      // Add message from previous successful replication job\n                      if (stdOut.length()) {\n                        stdErr = stdOut + stdErr;\n                      }\n                    } else {\n                      stdOut = SSTR(\"success: scheduled replication from source fs=\"\n                                    << src_fsid << \" => target fs=\"\n                                    << selectedfs[i] << std::endl).c_str();\n                    }\n                  } else {\n                    retc = ENOSPC;\n                    stdErr = \"error: create new replicas => no source available: \";\n                    stdErr += spath;\n                    stdErr += \"\\n\";\n                  }\n                }\n              }\n            } else {\n              stdErr = \"error: create new replicas => cannot place replicas: \";\n              stdErr += spath;\n              stdErr += \"\\n\";\n            }\n          }\n        } else {\n          // Run this in case of over-replication\n          if ((nrep_layout < nrep) && (nodrop == false)) {\n            unsigned int n2delete = nrep - nrep_layout;\n            std::multimap <common::ConfigStatus, int /*fsid*/> statemap;\n            std::multimap <std::string /*schedgroup*/, int /*fsid*/> groupmap;\n            // We have too many replica's online, we drop (nrep_online - nrep_layout)\n            // replicas starting with the lowest configuration state\n            eos_debug(\"msg=\\\"drop %d replicas\\\" space=%s group=%d fxid=%08llx\",\n                      n2delete, creationspace.c_str(), icreationsubgroup, fid);\n            {\n              for (auto loc_it = loc_vect.begin();\n                   loc_it != loc_vect.end(); ++loc_it) {\n                if (!(*loc_it)) {\n                  eos_err(\"msg=\\\"skip file system with id 0\\\" fxid=%08llx\", fid);\n                  continue;\n                }\n\n                eos::common::FileSystem::fsid_t fsid = *loc_it;\n                FileSystem* filesystem = FsView::gFsView.mIdView.lookupByID(fsid);\n                eos::common::FileSystem::fs_snapshot_t fs;\n\n                if (filesystem && filesystem->SnapShotFileSystem(fs, true)) {\n                  statemap.insert(std::pair<common::ConfigStatus, int>(fs.mConfigStatus, fsid));\n                  groupmap.insert(std::pair<std::string, int>(fs.mGroup, fsid));\n                }\n              }\n            }\n            std::string cspace = creationspace.c_str();\n\n            if (!cspace.empty() && (icreationsubgroup > 0)) {\n              cspace += SSTR(\".\" << icreationsubgroup);\n            }\n\n            std::multimap <common::ConfigStatus, int> limitedstatemap;\n\n            for (auto sit = groupmap.begin(); sit != groupmap.end(); ++sit) {\n              // Use fsid only if they match the space and/or group req\n              if (sit->first.find(cspace) != 0) {\n                continue;\n              }\n\n              // Default to the highest state for safety reasons\n              common::ConfigStatus state = eos::common::ConfigStatus::kRW;\n\n              // get the state for each fsid matching\n              for (auto state_it = statemap.begin();\n                   state_it != statemap.end(); ++state_it) {\n                if (state_it->second == sit->second) {\n                  state = state_it->first;\n                  break;\n                }\n              }\n\n              // fill the map containing only the candidates\n              limitedstatemap.insert(std::pair<common::ConfigStatus, int>\n                                     (state, sit->second));\n            }\n\n            std::vector<unsigned long> fsid2delete;\n\n            for (auto lit = limitedstatemap.begin(); lit != limitedstatemap.end(); ++lit) {\n              fsid2delete.push_back(lit->second);\n\n              if (fsid2delete.size() == n2delete) {\n                break;\n              }\n            }\n\n            if (fsid2delete.size() != n2delete) {\n              // add a warning that something does not work as requested ....\n              stdErr = SSTR(\"warning: cannot adjust replicas according to \"\n                            \"your requirement:\"\n                            << \" space=\" << creationspace\n                            << \" subgroup=\" << icreationsubgroup\n                            << std::endl).c_str();\n            }\n\n            eos::common::RWMutexWriteLock ns_wr_lock(gOFS->eosViewRWMutex);\n\n            try { // Get again the original file meta data\n              auto fmd = gOFS->eosFileService->getFileMD(fid);\n\n              for (unsigned int i = 0; i < fsid2delete.size(); i++) {\n                if (fmd->hasLocation(fsid2delete[i])) {\n                  fmd->unlinkLocation(fsid2delete[i]);\n                  eos_debug(\"msg=\\\"removing location\\\" fsid=%lu fxid=%08llx\",\n                            fsid2delete[i], fid);\n                  stdOut += \"success: dropping replica on fsid=\";\n                  stdOut += (int) fsid2delete[i];\n                  stdOut += \"\\n\";\n                }\n              }\n\n              gOFS->eosView->updateFileStore(fmd.get());\n            } catch (eos::MDException& e) {\n              errno = e.getErrno();\n              eos_debug(\"msg=\\\"caught exception\\\" errno=%d msg=\\\"%s\\\"\",\n                        e.getErrno(), e.getMessage().str().c_str());\n              stdErr = SSTR(\"error: drop excess replicas => cannot unlink \"\n                            \"location - \" << e.getMessage().str().c_str()\n                            << std::endl).c_str();\n            }\n          }\n        }\n      } else {\n        // This is a rain layout, we try to rewrite the file using the converter\n        if (eos::common::LayoutId::IsRain(lid)) {\n          ProcCommand Cmd;\n          // rewrite the file asynchronous using the converter\n          XrdOucString option = pOpaque->Get(\"mgm.option\");\n          XrdOucString info;\n          info += \"&mgm.cmd=file&mgm.subcmd=convert&mgm.option=rewrite&mgm.path=\";\n          info += spath.c_str();\n          retc = Cmd.open(\"/proc/user\", info.c_str(), *pVid, mError);\n          Cmd.AddOutput(stdOut, stdErr);\n          Cmd.close();\n          retc = Cmd.GetRetc();\n        } else {\n          retc = EINVAL;\n          stdOut += \"warning: no action for this layout type (neither replica nor rain)\\n\";\n        }\n      }\n    }\n\n    // -------------------------------------------------------------------------\n    // return meta data for a particular file\n    // -------------------------------------------------------------------------\n    if (mSubCmd == \"getmdlocation\") {\n      cmdok = true;\n      gOFS->MgmStats.Add(\"GetMdLocation\", pVid->uid, pVid->gid, 1);\n      // this returns the access urls to query local metadata information\n      XrdOucString spath = pOpaque->Get(\"mgm.path\");\n      const char* inpath = spath.c_str();\n      NAMESPACEMAP;\n      PROC_BOUNCE_ILLEGAL_NAMES;\n      PROC_BOUNCE_NOT_ALLOWED;\n      spath = path;\n\n      if (!spath.length()) {\n        stdErr = \"error: you have to give a path name to call 'fileinfo'\";\n        retc = EINVAL;\n      } else {\n        std::shared_ptr<eos::IFileMD> fmd;\n        //-------------------------------------------\n        std::string ns_path {};\n        eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n        eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n\n        try {\n          if ((spath.beginswith(\"fid:\") || (spath.beginswith(\"fxid:\")))) {\n            WAIT_BOOT;\n            unsigned long long fid = Resolver::retrieveFileIdentifier(\n                                       spath).getUnderlyingUInt64();\n            // reference by fid+fsid\n            //-------------------------------------------\n            fmd = gOFS->eosFileService->getFileMD(fid);\n          } else {\n            fmd = gOFS->eosView->getFile(spath.c_str());\n            ns_path = spath.c_str();\n          }\n        } catch (eos::MDException& e) {\n          errno = e.getErrno();\n          stdErr = \"error: cannot retrieve file meta data - \";\n          stdErr += e.getMessage().str().c_str();\n          eos_debug(\"caught exception %d %s\\n\",\n                    e.getErrno(),\n                    e.getMessage().str().c_str());\n        }\n\n        if (!fmd) {\n          retc = errno;\n        } else {\n          if (ns_path.empty()) {\n            try {\n              ns_path = gOFS->eosView->getUri(fmd.get());\n            } catch (const eos::MDException& e) {\n              // File is no longer attached to a cointainer put only the name\n              ns_path = fmd->getName();\n            }\n          }\n\n          XrdOucString sizestring;\n          int i = 0;\n          stdOut += \"&\";\n          stdOut += \"mgm.nrep=\";\n          stdOut += (int) fmd->getNumLocation();\n          stdOut += \"&\";\n          stdOut += \"mgm.checksumtype=\";\n          stdOut += eos::common::LayoutId::GetChecksumString(fmd->getLayoutId());\n          stdOut += \"&\";\n          stdOut += \"mgm.size=\";\n          stdOut +=\n            eos::common::StringConversion::GetSizeString(sizestring,\n                (unsigned long long) fmd->getSize());\n          stdOut += \"&\";\n          stdOut += \"mgm.checksum=\";\n          eos::appendChecksumOnStringAsHex(fmd.get(), stdOut, 0x00, SHA256_DIGEST_LENGTH);\n          stdOut += \"&\";\n          stdOut += \"mgm.stripes=\";\n          stdOut += (int)(eos::common::LayoutId::GetStripeNumber(fmd->getLayoutId()) + 1);\n          stdOut += \"&\";\n          eos::IFileMD::LocationVector loc_vect = fmd->getLocations();\n\n          for (auto loc_it = loc_vect.begin();\n               loc_it != loc_vect.end(); ++loc_it) {\n            // ignore filesystem id 0\n            if (!(*loc_it)) {\n              eos_err(\"fsid 0 found fxid=%08llx\", fmd->getId());\n              continue;\n            }\n\n            eos::common::FileSystem* filesystem = FsView::gFsView.mIdView.lookupByID(\n                                                    *loc_it);\n\n            if (filesystem) {\n              XrdOucString host;\n              std::string hostport = filesystem->GetString(\"hostport\");\n              stdOut += \"mgm.replica.url\";\n              stdOut += i;\n              stdOut += \"=\";\n              stdOut += hostport.c_str();\n              stdOut += \"&\";\n              const std::string hex_fid = eos::common::FileId::Fid2Hex(fmd->getId());\n              stdOut += \"mgm.fid\";\n              stdOut += i;\n              stdOut += \"=\";\n              stdOut += hex_fid.c_str();\n              stdOut += \"&\";\n              stdOut += \"mgm.fsid\";\n              stdOut += i;\n              stdOut += \"=\";\n              stdOut += (int) * loc_it;\n              stdOut += \"&\";\n              stdOut += \"mgm.fsbootstat\";\n              stdOut += i;\n              stdOut += \"=\";\n              stdOut += filesystem->GetString(\"stat.boot\").c_str();\n              stdOut += \"&\";\n              stdOut += \"mgm.fstpath\";\n              stdOut += i;\n              stdOut += \"=\";\n              stdOut +=\n                eos::common::FileId::FidPrefix2FullPath(hex_fid.c_str(),\n                    filesystem->GetPath().c_str()).c_str();\n              stdOut += \"&\";\n              stdOut += \"mgm.nspath=\";\n              stdOut += ns_path.c_str();\n              stdOut += \"&\";\n            } else {\n              stdOut += \"NA&\";\n            }\n\n            i++;\n          }\n        }\n      }\n    }\n\n    // Purge versions of a file\n    if (mSubCmd == \"purge\") {\n      cmdok = true;\n      int max_versions = 0;\n      const char* ptr = pOpaque->Get(\"mgm.purge.version\");\n      std::string smax_versions = (ptr ? ptr : \"\");\n      ProcCommand Cmd;\n      XrdOucString info;\n\n      if (smax_versions.empty()) {\n        max_versions = -1; // read the max version from the parent xattr\n      } else {\n        try {\n          max_versions = std::stoi(smax_versions);\n        } catch (...) {\n          stdErr = \"error: illegal version count specified\";\n          retc = EINVAL;\n          return SFS_OK;\n        }\n      }\n\n      // Check that the requests file exists\n      struct stat buf;\n\n      if (gOFS->_stat(spath.c_str(), &buf, *mError, *pVid, \"\")) {\n        stdErr = \"error: unable to stat path=\";\n        stdErr += spath.c_str();\n        retc = errno;\n        return SFS_OK;\n      }\n\n      XrdOucString version_dir;\n      eos::common::Path cPath(spath.c_str());\n      version_dir += cPath.GetParentPath();\n      version_dir += \"/.sys.v#.\";\n      version_dir += cPath.GetName();\n      version_dir += \"/\";\n\n      if (gOFS->PurgeVersion(version_dir.c_str(), *mError, max_versions)) {\n        if (mError->getErrInfo()) {\n          retc = mError->getErrInfo();\n          stdErr += SSTR(\"error: unable to purge versions for path=\" << spath\n                         << \"\\nerror: \" << mError->getErrText()).c_str();\n        } else {\n          stdErr += SSTR(\"info: no versions to purge for path=\" << spath).c_str();\n        }\n\n        return SFS_OK;\n      }\n    }\n\n    // Create a new version of a file\n    if (mSubCmd == \"version\") {\n      cmdok = true;\n      XrdOucString max_count = pOpaque->Get(\"mgm.purge.version\");\n      int maxversion = 0;\n\n      if (!max_count.length()) {\n        maxversion = -1;\n      } else {\n        maxversion = atoi(max_count.c_str());\n\n        if (!maxversion) {\n          stdErr = \"error: illegal version count specified version-cnt=\";\n          stdErr += max_count.c_str();\n          retc = EINVAL;\n          return SFS_OK;\n        }\n      }\n\n      struct stat buf;\n\n      if (gOFS->_stat(spath.c_str(), &buf, *mError, *pVid, \"\")) {\n        stdErr = \"error; unable to stat path=\";\n        stdErr += spath.c_str();\n        retc = errno;\n        return SFS_OK;\n      }\n\n      // Third party copy the file to a temporary name\n      ProcCommand Cmd;\n      eos::common::Path atomicPath(spath.c_str());\n      XrdOucString info;\n      info += \"&mgm.cmd=file&mgm.subcmd=copy&mgm.file.target=\";\n      info += atomicPath.GetAtomicPath(true);\n      info += \"&mgm.path=\";\n      info += spath.c_str();\n      retc = Cmd.open(\"/proc/user\", info.c_str(), *pVid, mError);\n      Cmd.AddOutput(stdOut, stdErr);\n      Cmd.close();\n\n      if ((!Cmd.GetRetc())) {\n        if (maxversion > 0) {\n          XrdOucString versiondir;\n          eos::common::Path cPath(spath.c_str());\n          versiondir += cPath.GetParentPath();\n          versiondir += \"/.sys.v#.\";\n          versiondir += cPath.GetName();\n          versiondir += \"/\";\n\n          if (gOFS->PurgeVersion(versiondir.c_str(), *mError, maxversion)) {\n            stdErr += \"error: unable to purge versions of path=\";\n            stdErr += spath.c_str();\n            stdErr += \"\\n\";\n            stdErr += \"error: \";\n            stdErr += mError->getErrText();\n            retc = mError->getErrInfo();\n            return SFS_OK;\n          }\n        }\n\n        // Everything worked well\n        stdOut = \"info: created new version of '\";\n        stdOut += spath.c_str();\n        stdOut += \"'\";\n\n        if (maxversion > 0) {\n          stdOut += \" keeping \";\n          stdOut += (int) maxversion;\n          stdOut += \" versions!\";\n        }\n      }\n    }\n\n    // List or grab version(s) of a file\n    if (mSubCmd == \"versions\") {\n      cmdok = true;\n      XrdOucString grab = pOpaque->Get(\"mgm.grab.version\");\n\n      if (grab == \"-1\") {\n        ProcCommand Cmd;\n        // list versions\n        eos::common::Path vpath(spath.c_str());\n        XrdOucString info;\n        info += \"&mgm.cmd=ls&mgm.option=-l\";\n        info += \"&mgm.path=\";\n        info += vpath.GetVersionDirectory();\n        Cmd.open(\"/proc/user\", info.c_str(), *pVid, mError);\n        Cmd.AddOutput(stdOut, stdErr);\n        Cmd.close();\n        retc = Cmd.GetRetc();\n\n        if (retc && (retc == ENOENT)) {\n          stdOut = \"\";\n          stdErr = \"error: no version exists for '\";\n          stdErr += spath.c_str();\n          stdErr += \"'\";\n          return SFS_OK;\n        }\n      } else {\n        eos::common::Path vpath(spath.c_str());\n        struct stat buf;\n        struct stat vbuf;\n\n        if (gOFS->_stat(spath.c_str(), &buf, *mError, *pVid, \"\")) {\n          stdErr = \"error; unable to stat path=\";\n          stdErr += spath.c_str();\n          retc = errno;\n          return SFS_OK;\n        }\n\n        // grab version\n        XrdOucString versionname = pOpaque->Get(\"mgm.grab.version\");\n\n        if (!versionname.length()) {\n          stdErr = \"error: you have to provide the version you want to stage!\";\n          retc = EINVAL;\n          return SFS_OK;\n        }\n\n        XrdOucString versionpath = vpath.GetVersionDirectory();\n        versionpath += versionname;\n\n        if (gOFS->_stat(versionpath.c_str(), &vbuf, *mError, *pVid, \"\")) {\n          stdErr = \"error: failed to stat your provided version path='\";\n          stdErr += versionpath;\n          stdErr += \"'\";\n          retc = errno;\n          return SFS_OK;\n        }\n\n        // now stage a new version of the existing file\n        XrdOucString versionedpath;\n\n        if (gOFS->Version(eos::common::FileId::InodeToFid(buf.st_ino), *mError,\n                          *pVid, -1, &versionedpath)) {\n          stdErr += \"error: unable to create a version of path=\";\n          stdErr += spath.c_str();\n          stdErr += \"\\n\";\n          stdErr += \"error: \";\n          stdErr += mError->getErrText();\n          retc = mError->getErrInfo();\n          return SFS_OK;\n        }\n\n        // and stage back the desired version\n        if (gOFS->rename(versionpath.c_str(), spath.c_str(), *mError, *pVid)) {\n          stdErr += \"error: unable to stage\";\n          stdErr += \" '\";\n          stdErr += versionpath.c_str();\n          stdErr += \"' back to '\";\n          stdErr += spath.c_str();\n          stdErr += \"'\";\n          retc = errno;\n          return SFS_OK;\n        } else {\n          {\n            // Copy the xattrs of the current file to the newly restored one\n            std::set<std::string> exclude_xattrs {\"sys.utrace\", \"sys.vtrace\"};\n            eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n            auto versioned_fmd = gOFS->eosView->getFile(versionedpath.c_str());\n            auto restored_fmd = gOFS->eosView->getFile(spath.c_str());\n\n            if (!versioned_fmd || !restored_fmd) {\n              stdErr = \"error: failed to copy xattrs\";\n              retc = EINVAL;\n              return SFS_OK;\n            }\n\n            eos::IFileMD::XAttrMap map_xattrs = versioned_fmd->getAttributes();\n\n            for (const auto& xattr : map_xattrs) {\n              if (exclude_xattrs.find(xattr.first) == exclude_xattrs.end()) {\n                restored_fmd->setAttribute(xattr.first, xattr.second);\n              }\n            }\n\n            gOFS->eosView->updateFileStore(restored_fmd.get());\n          }\n          stdOut += \"success: staged '\";\n          stdOut += versionpath;\n          stdOut += \"' back to '\";\n          stdOut += spath.c_str();\n          stdOut += \"'\";\n          stdOut += \" - the previous file is now '\";\n          stdOut += versionedpath;\n          stdOut += \";\";\n        }\n      }\n    }\n  }\n\n  if (!cmdok) {\n    stdErr = \"error: don't know subcmd=\";\n    stdErr += mSubCmd;\n    retc = EINVAL;\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Fileinfo.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/File.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/misc/Constants.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"common/table_formatter/TableFormatterBase.hh\"\n#include \"common/table_formatter/TableCell.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/Timing.hh\"\n#include \"common/Path.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/interface/ContainerIterators.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"namespace/Resolver.hh\"\n#include \"namespace/utils/Etag.hh\"\n#include \"namespace/utils/Checksum.hh\"\n#include <json/json.h>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Fileinfo method\n//------------------------------------------------------------------------------\nstd::string\nProcCommand::FileMDToStatus(std::shared_ptr<eos::IFileMD> fmd)\n{\n  int tape_copy = 0;\n\n  if (fmd->hasAttribute(SYS_HARD_LINK)) {\n    return \"hardlink\";\n  }\n\n  if (fmd->isLink()) {\n    return \"symlink\";\n  }\n\n  if (fmd->hasLocation(EOS_TAPE_FSID)) {\n    tape_copy++;\n  }\n\n  if (fmd->getNumLocation() == 0) {\n    if (fmd->getSize() == 0) {\n      return \"healthy\";\n    }\n\n    if (fmd->getNumUnlinkedLocation()) {\n      return \"pending_deletion\";\n    }\n\n    return \"locations::uncommitted\";\n  }\n\n  if (fmd->getNumLocation() < (eos::common::LayoutId::GetStripeNumber(\n                                 fmd->getLayoutId()) + 1 + tape_copy)) {\n    return \"locations::incomplete\";\n  }\n\n  if (fmd->getNumLocation() > (eos::common::LayoutId::GetStripeNumber(\n                                 fmd->getLayoutId()) + 1 + tape_copy)) {\n    return \"locations::overreplicated\";\n  }\n\n  eos::IFileMD::XAttrMap xattrs = fmd->getAttributes();\n  // check sys.fusex.state\n  std::string fs = xattrs[\"sys.fusex.state\"];\n\n  if (fs.length()) {\n    if (fs.length() > 1) {\n      std::string b2 = fs.substr(fs.length() - 2);\n\n      if (b2 == \"±\") {\n        return \"fuse::needsflush\";\n      }\n    }\n\n    if (fs.back() == 'Z') {\n      return \"fuse::repairing\";\n    }\n\n    // scan from the back\n    if (fs.back() == '|') {\n      size_t spos = fs.rfind(\"±\", fs.length() - 1);\n      size_t ncommits = 0;\n\n      if (spos != std::string::npos) {\n        spos++; // multichar !\n\n        for (size_t i = spos; i < fs.length(); ++i) {\n          if (fs.at(i) == '+') {\n            ncommits++;\n          }\n        }\n      }\n\n      if (eos::common::LayoutId::GetLayoutType(fmd->getLayoutId()) ==\n          eos::common::LayoutId::kReplica) {\n        if (fmd->getSize() && (ncommits < fmd->getNumLocation())) {\n          return \"fuse::missingcommits\";\n        }\n      }\n    }\n  }\n\n  return \"healthy\";\n}\n\nint\nProcCommand::Fileinfo()\n{\n  gOFS->MgmStats.Add(\"FileInfo\", pVid->uid, pVid->gid, 1);\n  XrdOucString spath = pOpaque->Get(\"mgm.path\");\n  const char* inpath = spath.c_str();\n  NAMESPACEMAP;\n  PROC_BOUNCE_ILLEGAL_NAMES;\n  PROC_BOUNCE_NOT_ALLOWED;\n  struct stat buf;\n  unsigned long long fid = 0;\n\n  if ((!spath.beginswith(\"fid:\")) && (!spath.beginswith(\"fxid:\")) &&\n      (!spath.beginswith(\"pid:\")) && (!spath.beginswith(\"pxid:\")) &&\n      (!spath.beginswith(\"inode:\"))) {\n    if (gOFS->_stat(path, &buf, *mError, *pVid, (char*) 0, (std::string*)0,\n                    false)) {\n      stdErr = \"error: cannot stat '\";\n      stdErr += path;\n      stdErr += \"'\\n\";\n      retc = ENOENT;\n      return SFS_OK;\n    }\n\n    if (S_ISDIR(buf.st_mode)) {\n      fid = buf.st_ino;\n    } else {\n      fid = eos::common::FileId::InodeToFid(buf.st_ino);\n    }\n  } else {\n    XrdOucString sfid = spath;\n\n    if ((sfid.replace(\"inode:\", \"\"))) {\n      size_t pos = 0;\n\n      try {\n        fid = std::stoull(sfid.c_str(), &pos, 10);\n      } catch (...) {\n        stdErr = \"error: inode option takes a fuse inode decimal value\";\n        retc = EINVAL;\n        return SFS_OK;\n      }\n\n      if (pos != (size_t)sfid.length()) {\n        stdErr = \"error: inode option takes a fuse inode decimal value - some \"\n                 \"characters were not converted\";\n        retc = EINVAL;\n        return SFS_OK;\n      }\n\n      if (eos::common::FileId::IsFileInode(fid)) {\n        buf.st_mode = S_IFREG;\n        fid = eos::common::FileId::InodeToFid(fid);\n        spath = \"fid:\";\n        spath += eos::common::StringConversion::GetSizeString(sfid, fid);\n        path = spath.c_str();\n      } else {\n        buf.st_mode = S_IFDIR;\n        spath.replace(\"inode:\", \"pid:\");\n        path = spath.c_str();\n      }\n    } else { // one of fid, fxid, pid, pxid\n      buf.st_mode = (sfid.beginswith(\"f\")) ? S_IFREG : S_IFDIR;\n      sfid.replace('p', 'f', 0, 1);\n      fid = Resolver::retrieveFileIdentifier(sfid).getUnderlyingUInt64();\n    }\n  }\n\n  if (mJsonFormat) {\n    if (S_ISDIR(buf.st_mode)) {\n      return DirJSON(fid, 0);\n    } else {\n      return FileJSON(fid, 0);\n    }\n  } else {\n    if (S_ISDIR(buf.st_mode)) {\n      return DirInfo(path);\n    } else {\n      return FileInfo(path);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Fileinfo given path\n//------------------------------------------------------------------------------\nint\nProcCommand::FileInfo(const char* path)\n{\n  XrdOucString option = pOpaque->Get(\"mgm.file.info.option\");\n  XrdOucString spath = path;\n  bool detached = false;\n  uint64_t clock = 0;\n  {\n    eos::common::RWMutexReadLock viewReadLock;\n    std::shared_ptr<eos::IFileMD> fmd;\n\n    if ((spath.beginswith(\"fid:\") || (spath.beginswith(\"fxid:\")))) {\n      unsigned long long fid = Resolver::retrieveFileIdentifier(\n                                 spath).getUnderlyingUInt64();\n      // Reference by fid+fxid\n      eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, fid);\n      viewReadLock.Grab(gOFS->eosViewRWMutex);\n      std::string nspath;\n\n      try {\n        fmd = gOFS->eosFileService->getFileMD(fid, &clock);\n        nspath = gOFS->eosView->getUri(fmd.get());\n\n        if (fmd->isLink()) {\n          try {\n            spath = gOFS->eosView->getRealPath(nspath).c_str();\n          } catch (const eos::MDException& e) {\n            // The link points to a location outside the EOS namespace\n            // therefore we return info about the symlink object\n            spath = nspath.c_str();\n          }\n        } else {\n          spath = nspath.c_str();\n        }\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        stdErr = \"error: cannot retrieve file meta data - \";\n        stdErr += e.getMessage().str().c_str();\n        eos_debug(\"msg=\\\"exception retrieving file metadata\\\" ec=%d \"\n                  \"emsg=\\\"%s\\\"\\n\", e.getErrno(), e.getMessage().str().c_str());\n      }\n\n      // Detect detached state for fid/fxid reference\n      detached = nspath.empty();\n    } else {\n      // Reference by path\n      eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, spath.c_str());\n      viewReadLock.Grab(gOFS->eosViewRWMutex);\n\n      try {\n        fmd = gOFS->eosView->getFile(spath.c_str(), false);\n      } catch (eos::MDException& e) {\n        try {\n          // Maybe this is a symlink pointing outside the EOS namespace\n          fmd = gOFS->eosView->getFile(spath.c_str(), false);\n        } catch (eos::MDException& ee) {\n          errno = ee.getErrno();\n          stdErr = \"error: cannot retrieve file meta data - \";\n          stdErr += ee.getMessage().str().c_str();\n          eos_debug(\"msg=\\\"exception retrieving file metadata\\\" ec=%d \"\n                    \"emsg=\\\"%s\\\"\\n\", ee.getErrno(), ee.getMessage().str().c_str());\n        }\n      }\n\n      if (fmd) {\n        try {\n          std::string nspath = gOFS->eosView->getUri(fmd.get());\n\n          if (fmd->isLink()) {\n            spath = gOFS->eosView->getRealPath(nspath).c_str();\n          } else {\n            spath = nspath.c_str();\n          }\n        } catch (eos::MDException& ee) {\n          fmd.reset();\n          errno = ee.getErrno();\n          stdErr = \"error: cannot retrieve file meta data - \";\n          stdErr += ee.getMessage().str().c_str();\n          eos_debug(\"msg=\\\"exception retrieving file metadata\\\" ec=%d \"\n                    \"emsg=\\\"%s\\\"\\n\", ee.getErrno(), ee.getMessage().str().c_str());\n        }\n      }\n    }\n\n    if (!fmd) {\n      retc = errno;\n      viewReadLock.Release();\n      //-------------------------------------------\n    } else {\n      using eos::common::Timing;\n      using eos::common::FileId;\n      using eos::common::LayoutId;\n      using eos::common::StringConversion;\n      // Make a copy of the file metadata object\n      std::shared_ptr<eos::IFileMD> fmd_copy(fmd->clone());\n      fmd.reset();\n      // TODO (esindril): All this copying should be reviewed\n      viewReadLock.Release();\n      //-------------------------------------------\n      uint32_t lid = fmd_copy->getLayoutId();\n      XrdOucString sizestring;\n      bool Monitoring = false;\n      bool Envformat = false;\n      bool outputFilter = false;\n      std::ostringstream out;\n      const std::string hex_fid = FileId::Fid2Hex(fmd_copy->getId());\n      const std::string hex_pid = FileId::Fid2Hex(fmd_copy->getContainerId());\n\n      if ((option.find(\"-m\")) != STR_NPOS) {\n        Monitoring = true;\n      }\n\n      if ((option.find(\"-env\")) != STR_NPOS) {\n        Envformat = true;\n        Monitoring = false;\n      }\n\n      if (Envformat) {\n        std::string env;\n        fmd_copy->getEnv(env);\n        eos::common::Path cPath(spath.c_str());\n        out << env << \"&container=\" << cPath.GetParentPath() << std::endl;\n      } else {\n        // Filter output according to requested filters\n        // Note: filters affect only non-monitoring output\n        if (!Monitoring) {\n          if ((option.find(\"-path\")) != STR_NPOS) {\n            out << \"path:   \" << spath << std::endl;\n          }\n\n          if ((option.find(\"-fxid\")) != STR_NPOS) {\n            out << \"fxid:   \" << hex_fid << std::endl;\n          }\n\n          if ((option.find(\"-fid\")) != STR_NPOS) {\n            out << \"fid:    \" << fmd_copy->getId() << std::endl;\n          }\n\n          if ((option.find(\"-size\")) != STR_NPOS) {\n            out << \"size:   \" << fmd_copy->getSize() << std::endl;\n          }\n\n          if ((option.find(\"-checksum\")) != STR_NPOS) {\n            std::string xs;\n            eos::appendChecksumOnStringAsHex(fmd_copy.get(), xs);\n            out << \"xstype: \" << LayoutId::GetChecksumString(lid)\n                << std::endl\n                << \"xs:     \" << xs << std::endl;\n          }\n\n          // Mark filter flag if out is not empty\n          outputFilter = (out.tellp() != std::streampos(0));\n        }\n\n        if (Monitoring || !outputFilter) {\n          eos::IFileMD::XAttrMap xattrs = fmd_copy->getAttributes();\n          bool showFullpath = (option.find(\"-fullpath\") != STR_NPOS);\n          bool showProxygroup = (option.find(\"-proxy\") != STR_NPOS);\n          eos::IFileMD::ctime_t mtime;\n          eos::IFileMD::ctime_t ctime;\n          eos::IFileMD::ctime_t btime {0, 0};\n          eos::IFileMD::ctime_t atime {0, 0};\n          fmd_copy->getCTime(ctime);\n          fmd_copy->getMTime(mtime);\n          fmd_copy->getATime(atime);\n\n          if (xattrs.count(\"sys.eos.btime\")) {\n            Timing::Timespec_from_TimespecStr(xattrs[\"sys.eos.btime\"], btime);\n          }\n\n          time_t filectime = (time_t) ctime.tv_sec;\n          time_t filemtime = (time_t) mtime.tv_sec;\n          time_t filebtime = (time_t) btime.tv_sec;\n          time_t fileatime = (time_t) atime.tv_sec;\n          std::string etag, xs;\n          eos::calculateEtag(fmd_copy.get(), etag);\n          eos::appendChecksumOnStringAsHex(fmd_copy.get(), xs);\n          std::string redundancy = eos::common::LayoutId::GetRedundancySymbol(\n                                     fmd_copy->hasLocation(EOS_TAPE_FSID),\n                                     eos::common::LayoutId::GetRedundancy(lid,\n                                         fmd_copy->getNumLocation()),\n                                     fmd_copy->getSize());\n\n          if (!Monitoring) {\n            out << \"  File: '\" << spath << \"'\"\n                << \"  Flags: \" << StringConversion::IntToOctal((int) fmd_copy->getFlags(), 4);\n\n            if (clock) {\n              out << \"  Clock: \" << FileId::Fid2Hex(clock);\n            }\n\n            out << std::endl;\n            out << \"  Size: \" << fmd_copy->getSize() << std::endl\n                << \"Status: \" << FileMDToStatus(fmd_copy) << std::endl\n                << \"Modify: \" << eos::common::Timing::ltime(filemtime)\n                << \" Timestamp: \" << eos::common::Timing::TimespecToString(mtime)\n                << std::endl\n                << \"Change: \" << eos::common::Timing::ltime(filectime)\n                << \" Timestamp: \" << eos::common::Timing::TimespecToString(ctime)\n                << std::endl\n                << \"Access: \" << eos::common::Timing::ltime(fileatime)\n                << \" Timestamp: \" << eos::common::Timing::TimespecToString(atime)\n                << std::endl\n                << \" Birth: \" << eos::common::Timing::ltime(filebtime)\n                << \" Timestamp: \" << eos::common::Timing::TimespecToString(btime)\n                << std::endl\n                << \"  CUid: \" << fmd_copy->getCUid()\n                << \" CGid: \" << fmd_copy->getCGid()\n                << \" Fxid: \" << hex_fid\n                << \" Fid: \" << fmd_copy->getId()\n                << \" Pid: \" << fmd_copy->getContainerId()\n                << \" Pxid: \" << hex_pid\n                << std::endl\n                << \" ETAGs: \" << etag << std::endl\n                << \"XStype: \" << std::left << std::setw(9) << LayoutId::GetChecksumString(\n                  fmd_copy->getLayoutId())\n                << \"XS: \" << xs\n                << std::endl;\n            auto altchecksums = fmd_copy->getAltXs();\n\n            for (auto [type, xs] : altchecksums) {\n              out << \"XStype: \" << std::left << std::setw(9) << LayoutId::GetChecksumString(\n                    type)\n                  << \"XS: \" << xs << std::endl;\n            }\n\n            out << \"Layout: \" << LayoutId::GetLayoutTypeString(fmd_copy->getLayoutId())\n                << \" Stripes: \" << (LayoutId::GetStripeNumber(fmd_copy->getLayoutId()) + 1)\n                << \" Blocksize: \" << LayoutId::GetBlockSizeString(fmd_copy->getLayoutId())\n                << \" LayoutId: \" << FileId::Fid2Hex(fmd_copy->getLayoutId())\n                << \" Redundancy: \" << redundancy\n                << std::endl;\n            out << \"  #Rep: \" << fmd_copy->getNumLocation() << std::endl;\n\n            if (fmd_copy->hasLocation(EOS_TAPE_FSID)) {\n              std::string storage_class = xattrs[\"sys.archive.storage_class\"];\n              std::string archive_id = xattrs[\"sys.archive.file_id\"];\n              out << \"TapeID: \" << (archive_id.length() ? archive_id : \"undef\")\n                  << \" StorageClass: \" << (storage_class.length() ? storage_class : \"none\");\n            }\n\n            if (xattrs.count(\"user.obfuscate.key\")) {\n              if (xattrs.count(\"user.encrypted\")) {\n                out << \" Crypt: encrypted\" << std::endl;\n              } else {\n                out << \" Crypt: obfuscated\" << std::endl;\n              }\n            }\n          } else {\n            std::string xs;\n\n            if (LayoutId::GetChecksumLen(lid)) {\n              eos::appendChecksumOnStringAsHex(fmd_copy.get(), xs);\n            } else {\n              xs = \"0\";\n            }\n\n            out << \"keylength.file=\" << spath.length()\n                << \" file=\" << spath\n                << \" size=\" << fmd_copy->getSize()\n                << \" status=\" << FileMDToStatus(fmd_copy);\n\n            if (fmd_copy->isLink()) {\n              out << \" target=\" << fmd_copy->getLink();\n            } else if (xattrs.count(SYS_HARD_LINK)) {\n              out << \" target= \" << fmd_copy->getAttribute(SYS_HARD_LINK);\n            }\n\n            out << \" mtime=\" << mtime.tv_sec << \".\" << mtime.tv_nsec\n                << \" ctime=\" << ctime.tv_sec << \".\" << ctime.tv_nsec\n                << \" btime=\" << btime.tv_sec << \".\" << btime.tv_nsec\n                << \" atime=\" << atime.tv_sec << \".\" << atime.tv_nsec\n                << \" clock=\" << clock\n                << \" mode=\" << StringConversion::IntToOctal((int) fmd_copy->getFlags(), 4)\n                << \" uid=\" << fmd_copy->getCUid()\n                << \" gid=\" << fmd_copy->getCGid()\n                << \" fxid=\" << hex_fid\n                << \" fid=\" << fmd_copy->getId()\n                << \" ino=\" << FileId::FidToInode(fmd_copy->getId())\n                << \" pid=\" << fmd_copy->getContainerId()\n                << \" pxid=\" << hex_pid\n                << \" xstype=\" << LayoutId::GetChecksumString(lid)\n                << \" xs=\" << xs\n                << \" etag=\" << etag\n                << \" detached=\" << detached\n                << \" layout=\" << LayoutId::GetLayoutTypeString(lid)\n                << \" nstripes=\" << (LayoutId::GetStripeNumber(lid) + 1)\n                << \" lid=\" << FileId::Fid2Hex(lid)\n                << \" nrep=\" << fmd_copy->getNumLocation()\n                << \" \";\n\n            for (const auto& elem : xattrs) {\n              out << \"xattrn=\" << elem.first\n                  << \" xattrv=\" << elem.second << \" \";\n            }\n\n            auto altchecksums = fmd_copy->getAltXs();\n\n            for (auto [type, xs] : altchecksums) {\n              out << \"altxsn=\" << eos::common::LayoutId::GetChecksumString(type)\n                  << \" altxsv=\" << xs << \" \";\n            }\n          }\n\n          eos::IFileMD::LocationVector::const_iterator lociter;\n          eos::IFileMD::LocationVector loc_vect = fmd_copy->getLocations();\n          std::vector<unsigned int> selectedfs;\n          std::vector<std::string> proxys;\n          std::vector<std::string> firewalleps;\n          std::vector<unsigned int> unavailfs;\n          std::vector<unsigned int> replacedfs;\n          size_t fsIndex;\n          Scheduler::AccessArguments acsargs;\n          int i = 0;\n          int schedretc = -1;\n          TableHeader table_mq_header;\n          TableData table_mq_data;\n          TableFormatterBase table_mq;\n          bool table_mq_header_exist = false;\n\n          for (lociter = loc_vect.begin(); lociter != loc_vect.end(); ++lociter) {\n            // Ignore filesystem id 0\n            if (!(*lociter)) {\n              eos_err(\"msg=\\\"found file on fsid=0\\\" fxid=%08llx\",\n                      fmd_copy->getId());\n              continue;\n            }\n\n            XrdOucString location = \"\";\n            location += (int) * lociter;\n            eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n            eos::common::FileSystem* filesystem = FsView::gFsView.mIdView.lookupByID(\n                                                    *lociter);\n\n            if (filesystem) {\n              // For the fullpath option we output the physical location of the\n              // replicas\n              std::string fullpath;\n\n              if (showFullpath) {\n                fullpath = FileId::FidPrefix2FullPath(hex_fid.c_str(),\n                                                      filesystem->GetPath().c_str());\n              }\n\n              if (!Monitoring) {\n                std::string format =\n                  \"header=1|key=host:width=24:format=s|key=schedgroup:width=16:format=s|key=path:width=16:format=s|key=stat.boot:width=10:format=s|key=configstatus:width=14:format=s|key=local.drain:width=12:format=s|key=stat.active:width=8:format=s|key=stat.geotag:width=24:format=s\";\n\n                if (showProxygroup) {\n                  format += \"|key=proxygroup:width=24:format=s\";\n                }\n\n                filesystem->Print(table_mq_header, table_mq_data, format);\n\n                // Build header\n                if (!table_mq_header.empty()) {\n                  TableHeader table_mq_header_temp;\n                  table_mq_header_temp.push_back(std::make_tuple(\"no.\", 3, \"-l\"));\n                  table_mq_header_temp.push_back(std::make_tuple(\"fs-id\", 6, \"l\"));\n                  std::copy(table_mq_header.begin(), table_mq_header.end(),\n                            std::back_inserter(table_mq_header_temp));\n\n                  if (showFullpath) {\n                    table_mq_header_temp.push_back(std::make_tuple(\"physical location\", 18, \"s\"));\n                  }\n\n                  table_mq.SetHeader(table_mq_header_temp);\n                  table_mq_header_exist = true;\n                }\n\n                // Build body\n                if (table_mq_header_exist) {\n                  TableData table_mq_data_temp;\n\n                  for (auto& row : table_mq_data) {\n                    if (!row.empty()) {\n                      table_mq_data_temp.emplace_back();\n                      table_mq_data_temp.back().push_back(TableCell(i, \"l\"));\n                      table_mq_data_temp.back().push_back(TableCell(*lociter, \"l\"));\n\n                      for (auto& cell : row) {\n                        table_mq_data_temp.back().push_back(cell);\n                      }\n\n                      if (showFullpath) {\n                        table_mq_data_temp.back().push_back(TableCell(fullpath.c_str(), \"s\"));\n                      }\n                    }\n                  }\n\n                  table_mq.AddRows(table_mq_data_temp);\n                  table_mq_data.clear();\n                  table_mq_data_temp.clear();\n                }\n\n                if ((filesystem->GetString(\"proxygroup\").size()) &&\n                    (filesystem->GetString(\"proxygroup\") != \"<none>\") &&\n                    filesystem->GetString(\"filestickyproxydepth\").size() &&\n                    filesystem->GetLongLong(\"filestickyproxydepth\") >= 0) {\n                  // we do the scheduling only once when we meet a filesystem that requires it\n                  if (schedretc == -1) {\n                    acsargs.bookingsize = fmd_copy->getSize();\n                    acsargs.dataproxys = &proxys;\n                    acsargs.firewallentpts = NULL;\n                    acsargs.forcedfsid = 0;\n                    std::string space = filesystem->GetString(\"schedgroup\");\n                    space.resize(space.rfind(\".\"));\n                    acsargs.forcedspace = space.c_str();\n                    acsargs.fsindex = &fsIndex;\n                    acsargs.isRW = false;\n                    acsargs.lid = lid;\n                    acsargs.inode = (ino64_t) fmd_copy->getId();\n                    acsargs.locationsfs = &selectedfs;\n\n                    for (auto it = loc_vect.begin(); it != loc_vect.end(); it++) {\n                      selectedfs.push_back(*it);\n                    }\n\n                    std::string stried_cgi = \"\";\n                    acsargs.tried_cgi = &stried_cgi;\n                    acsargs.unavailfs = &unavailfs;\n                    acsargs.vid = &vid;\n\n                    if (!acsargs.isValid()) {\n                      // there is something wrong in the arguments of file access\n                      eos_static_err(\"msg=\\\"open - invalid access argument\\\"\");\n                    }\n\n                    schedretc = Scheduler::FileAccess(&acsargs);\n\n                    if (schedretc) {\n                      eos_static_warning(\"msg=\\\"cannot schedule the proxy\\\"\");\n                    }\n                  }\n\n                  if (schedretc) {\n                    out << \"     sticky to undefined\";\n                  } else {\n                    size_t k;\n\n                    for (k = 0; k < loc_vect.size() && selectedfs[k] != loc_vect[i]; k++);\n\n                    out << \"sticky to \" << proxys[k];\n                  }\n                }\n              } else {\n                out << \"fsid=\" << location << \" \";\n\n                if (showFullpath) {\n                  out << \"fullpath=\" << fullpath << \" \";\n                }\n              }\n            } else {\n              if (!Monitoring) {\n                if (location != EOS_TAPE_FSID) {\n                  out << std::setw(3) << i << std::setw(8) << location\n                      << \" NA\" << std::endl;\n                }\n              } else {\n                out << \"fsid=\" << location << \" \";\n              }\n            }\n\n            i++;\n          }\n\n          out << table_mq.GenerateTable(HEADER);\n          eos::IFileMD::LocationVector unlink_vect = fmd_copy->getUnlinkedLocations();\n\n          for (lociter = unlink_vect.begin(); lociter != unlink_vect.end(); ++lociter) {\n            if (!Monitoring) {\n              out << \"(undeleted) $ \" << *lociter << std::endl;\n            } else {\n              out << \"fsdel=\" << *lociter << \" \";\n            }\n          }\n\n          if (!Monitoring) {\n            out << \"*******\";\n          }\n        }\n      }\n\n      stdOut += out.str().c_str();\n    }\n  }\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// DirInfo by path\n//------------------------------------------------------------------------------\nint\nProcCommand::DirInfo(const char* path)\n{\n  XrdOucString option = pOpaque->Get(\"mgm.file.info.option\");\n  XrdOucString spath = path;\n  bool detached = false;\n  uint64_t clock = 0;\n  {\n    eos::common::RWMutexReadLock viewReadLock;\n    std::shared_ptr<eos::IContainerMD> dmd;\n\n    if ((spath.beginswith(\"pid:\") || (spath.beginswith(\"pxid:\")))) {\n      unsigned long long fid = 0;\n\n      if (spath.beginswith(\"pid:\")) {\n        spath.replace(\"pid:\", \"\");\n        fid = strtoull(spath.c_str(), 0, 10);\n      }\n\n      if (spath.beginswith(\"pxid:\")) {\n        spath.replace(\"pxid:\", \"\");\n        fid = strtoull(spath.c_str(), 0, 16);\n      }\n\n      // reference by pid+pxid\n      //-------------------------------------------\n      eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, fid);\n      viewReadLock.Grab(gOFS->eosViewRWMutex);\n      std::string nspath;\n\n      try {\n        dmd = gOFS->eosDirectoryService->getContainerMD(fid, &clock);\n        nspath = gOFS->eosView->getUri(dmd.get());\n        spath = nspath.c_str();\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        stdErr = \"error: cannot retrieve directory meta data - \";\n        stdErr += e.getMessage().str().c_str();\n        eos_debug(\"msg=\\\"exception retrieving container metadata\\\" ec=%d \"\n                  \"emsg=\\\"%s\\\"\\n\", e.getErrno(), e.getMessage().str().c_str());\n      }\n\n      // Detect detached state for pid/pxid reference\n      detached = nspath.empty();\n    } else {\n      eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, spath.c_str());\n      // reference by path\n      //-------------------------------------------\n      viewReadLock.Grab(gOFS->eosViewRWMutex);\n\n      try {\n        dmd = gOFS->eosView->getContainer(spath.c_str());\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        stdErr = \"error: cannot retrieve directory meta data - \";\n        stdErr += e.getMessage().str().c_str();\n        eos_debug(\"msg=\\\"exception retrieving container metadata\\\" ec=%d \"\n                  \"emsg=\\\"%s\\\"\\n\", e.getErrno(), e.getMessage().str().c_str());\n      }\n    }\n\n    if (!dmd) {\n      retc = errno;\n      viewReadLock.Release();\n      //-------------------------------------------\n    } else {\n      using eos::common::Timing;\n      using eos::common::FileId;\n      using eos::common::LayoutId;\n      using eos::common::StringConversion;\n      size_t num_containers = dmd->getNumContainers();\n      size_t num_files = dmd->getNumFiles();\n      size_t tree_size = dmd->getTreeSize();\n      uint64_t tree_containers = dmd->getTreeContainers();\n      uint64_t tree_files = dmd->getTreeFiles();\n      std::shared_ptr<eos::IContainerMD> dmd_copy(dmd->clone());\n      dmd.reset();\n      viewReadLock.Release();\n      //-------------------------------------------\n      XrdOucString sizestring;\n      bool Monitoring = false;\n      bool outputFilter = false;\n      std::ostringstream out;\n      const std::string hex_fid = FileId::Fid2Hex(dmd_copy->getId());\n      const std::string hex_pid = FileId::Fid2Hex(dmd_copy->getParentId());\n\n      if ((option.find(\"-m\")) != STR_NPOS) {\n        Monitoring = true;\n      }\n\n      // Filter output according to requested filters\n      // Note: filters affect only non-monitoring output\n      if (!Monitoring) {\n        if ((option.find(\"-path\")) != STR_NPOS) {\n          out << \"path:   \" << spath << std::endl;\n        }\n\n        if ((option.find(\"-fxid\")) != STR_NPOS) {\n          out << \"fxid:   \" << hex_fid << std::endl;\n        }\n\n        if ((option.find(\"-fid\")) != STR_NPOS) {\n          out << \"fid:    \" << dmd_copy->getId() << std::endl;\n        }\n\n        if ((option.find(\"-size\")) != STR_NPOS) {\n          out << \"size:   \" << (num_containers + num_files) << std::endl;\n        }\n\n        outputFilter = ((out.tellp() != std::streampos(0)) ||\n                        (option.find(\"-checksum\") != STR_NPOS));\n      }\n\n      if (Monitoring || !outputFilter) {\n        eos::IFileMD::XAttrMap xattrs = dmd_copy->getAttributes();\n        eos::IContainerMD::ctime_t ctime;\n        eos::IContainerMD::mtime_t mtime;\n        eos::IContainerMD::tmtime_t tmtime;\n        eos::IContainerMD::ctime_t btime {0, 0};\n        dmd_copy->getCTime(ctime);\n        dmd_copy->getMTime(mtime);\n        dmd_copy->getTMTime(tmtime);\n\n        if (xattrs.count(\"sys.eos.btime\")) {\n          Timing::Timespec_from_TimespecStr(xattrs[\"sys.eos.btime\"], btime);\n        }\n\n        time_t filectime = (time_t) ctime.tv_sec;\n        time_t filemtime = (time_t) mtime.tv_sec;\n        time_t filetmtime = (time_t) tmtime.tv_sec;\n        time_t filebtime = (time_t) btime.tv_sec;\n        char fid[32];\n        snprintf(fid, 32, \"%llu\", (unsigned long long) dmd_copy->getId());\n        std::string etag;\n        eos::calculateEtag(dmd_copy.get(), etag);\n\n        if (!Monitoring) {\n          out << \"  Directory: '\" << spath << \"'\"\n              << \"  Treesize: \" << tree_size\n              << \"  Treecontainers: \" << tree_containers\n              << \"  Treefiles: \"  << tree_files << std::endl;\n          out << \"  Container: \" << num_containers\n              << \"  Files: \" << num_files\n              << \"  Flags: \" << StringConversion::IntToOctal(dmd_copy->getMode(), 4);\n\n          if (clock) {\n            out << \"  Clock: \" << FileId::Fid2Hex(clock);\n          }\n\n          out << std::endl;\n          out << \"Modify: \" << Timing::ltime(filemtime)\n              << \" Timestamp: \" << Timing::TimespecToString(mtime)\n              << std::endl\n              << \"Change: \" << Timing::ltime(filectime)\n              << \" Timestamp: \" << Timing::TimespecToString(ctime)\n              << std::endl\n              << \"Sync  : \" << Timing::ltime(filetmtime)\n              << \" Timestamp: \" << Timing::TimespecToString(tmtime)\n              << std::endl\n              << \"Birth : \" << Timing::ltime(filebtime)\n              << \" Timestamp: \" << Timing::TimespecToString(btime)\n              << std::endl\n              << \"  CUid: \" << dmd_copy->getCUid()\n              << \" CGid: \" << dmd_copy->getCGid()\n              << \" Fxid: \" << hex_fid\n              << \" Fid: \" << dmd_copy->getId()\n              << \" Pid: \" << dmd_copy->getParentId()\n              << \" Pxid: \" << hex_pid\n              << std::endl\n              << \"  ETAG: \" << etag\n              << std::endl;\n        } else {\n          out << \"keylength.file=\" << spath.length()\n              << \" file=\" << spath\n              << \" treesize=\" << tree_size\n              << \" treecontainers=\" << tree_containers\n              << \" treefiles=\" << tree_files\n              << \" container=\" << num_containers\n              << \" files=\" << num_files\n              << \" mtime=\" << mtime.tv_sec << \".\" << mtime.tv_nsec\n              << \" ctime=\" << ctime.tv_sec << \".\" << ctime.tv_nsec\n              << \" btime=\" << btime.tv_sec << \".\" << btime.tv_nsec\n              << \" stime=\" << tmtime.tv_sec << \".\" << tmtime.tv_nsec\n              << \" clock=\" << clock\n              << \" mode=\" << StringConversion::IntToOctal((int) dmd_copy->getMode(), 4)\n              << \" uid=\" << dmd_copy->getCUid()\n              << \" gid=\" << dmd_copy->getCGid()\n              << \" fxid=\" << hex_fid\n              << \" fid=\" << dmd_copy->getId()\n              << \" ino=\" << dmd_copy->getId()\n              << \" pid=\" << dmd_copy->getParentId()\n              << \" pxid=\" << hex_pid\n              << \" etag=\" << etag\n              << \" detached=\" << detached\n              << \" \";\n\n          for (const auto& elem : xattrs) {\n            out << \"xattrn=\" << elem.first\n                << \" xattrv=\" << elem.second << \" \";\n          }\n        }\n      }\n\n      stdOut += out.str().c_str();\n    }\n  }\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// File info in JSON format\n//------------------------------------------------------------------------------\nint\nProcCommand::FileJSON(uint64_t fid, Json::Value* ret_json, bool dolock)\n{\n  using eos::common::LayoutId;\n  eos::IFileMD::ctime_t ctime;\n  eos::IFileMD::ctime_t mtime;\n  eos::IFileMD::ctime_t atime {0, 0};\n  eos::IFileMD::ctime_t btime {0, 0};\n  eos_static_debug(\"msg=\\\"JSON fileinfo\\\" fxid=%08llx\", fid);\n  Json::Value json;\n  json[\"id\"] = (Json::Value::UInt64) fid;\n  const std::string hex_fid = eos::common::FileId::Fid2Hex(fid);\n\n  try {\n    eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, fid);\n    eos::common::RWMutexReadLock viewReadLock;\n    std::shared_ptr<eos::IFileMD> fmd;\n    std::string path;\n    bool detached;\n\n    if (dolock) {\n      viewReadLock.Grab(gOFS->eosViewRWMutex);\n    }\n\n    try {\n      fmd = gOFS->eosFileService->getFileMD(fid);\n      path = gOFS->eosView->getUri(fmd.get());\n    } catch (eos::MDException& e) {\n      eos_static_debug(\"msg=\\\"exception retrieving file metadata\\\" ec=%d \"\n                       \"emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                       e.getMessage().str().c_str());\n\n      if (!fmd) {\n        if (dolock) {\n          viewReadLock.Release();\n        }\n\n        std::rethrow_exception(std::current_exception());\n      }\n    }\n\n    std::shared_ptr<eos::IFileMD> fmd_copy(fmd->clone());\n    fmd.reset();\n\n    if (dolock) {\n      viewReadLock.Release();\n    }\n\n    // TODO (esindril): All this copying should be reviewed\n    //--------------------------------------------------------------------------\n    fmd_copy->getCTime(ctime);\n    fmd_copy->getMTime(mtime);\n    fmd_copy->getATime(atime);\n    unsigned long long nlink = (fmd_copy->isLink()) ? 1 :\n                               fmd_copy->getNumLocation();\n    eos::IFileMD::XAttrMap xattrs = fmd_copy->getAttributes();\n\n    if (xattrs.count(\"sys.eos.btime\")) {\n      eos::common::Timing::Timespec_from_TimespecStr(xattrs[\"sys.eos.btime\"],\n          btime);\n    }\n\n    if ((detached = path.empty())) {\n      path = SSTR(\"fid:\" << fid);\n    }\n\n    json[\"fxid\"] = hex_fid.c_str();\n    json[\"inode\"] = (Json::Value::UInt64) eos::common::FileId::FidToInode(fid);\n    json[\"ctime\"] = (Json::Value::UInt64) ctime.tv_sec;\n    json[\"ctime_ns\"] = (Json::Value::UInt64) ctime.tv_nsec;\n    json[\"atime\"] = (Json::Value::UInt64) atime.tv_sec;\n    json[\"atime_ns\"] = (Json::Value::UInt64) atime.tv_nsec;\n    json[\"mtime\"] = (Json::Value::UInt64) mtime.tv_sec;\n    json[\"mtime_ns\"] = (Json::Value::UInt64) mtime.tv_nsec;\n    json[\"btime\"] = (Json::Value::UInt64) btime.tv_sec;\n    json[\"btime_ns\"] = (Json::Value::UInt64) btime.tv_nsec;\n    json[\"size\"] = (Json::Value::UInt64) fmd_copy->getSize();\n    json[\"uid\"] = fmd_copy->getCUid();\n    json[\"gid\"] = fmd_copy->getCGid();\n    json[\"mode\"] = fmd_copy->getFlags();\n    json[\"nlink\"] = (Json::Value::UInt64) nlink;\n    json[\"name\"] = fmd_copy->getName();\n    json[\"path\"] = path;\n    json[\"detached\"] = detached;\n    json[\"pid\"] = (Json::Value::UInt64) fmd_copy->getContainerId();\n    json[\"layout\"] = LayoutId::GetLayoutTypeString(fmd_copy->getLayoutId());\n    json[\"nstripes\"] = (int)(LayoutId::GetStripeNumber(fmd_copy->getLayoutId())\n                             + 1);\n    json[\"checksumtype\"] = LayoutId::GetChecksumString(fmd_copy->getLayoutId());\n    json[\"status\"] = FileMDToStatus(fmd_copy);\n\n    if (fmd_copy->isLink()) {\n      json[\"target\"] = fmd_copy->getLink();\n    } else if (fmd_copy->hasAttribute(SYS_HARD_LINK)) {\n      json[\"target\"] = fmd_copy->getAttribute(SYS_HARD_LINK);\n    }\n\n    Json::Value jsonxattr;\n\n    for (const auto& elem : xattrs) {\n      jsonxattr[elem.first] = elem.second;\n    }\n\n    if (fmd_copy->numAttributes()) {\n      json[\"xattr\"] = jsonxattr;\n    }\n\n    Json::Value jsonfsids;\n    eos::IFileMD::LocationVector loc_vect = fmd_copy->getLocations();\n\n    // Get host name for the fs ids\n    for (auto loc_it = loc_vect.begin(); loc_it != loc_vect.end(); ++loc_it) {\n      eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n      Json::Value jsonfsinfo;\n      eos::common::FileSystem* filesystem = FsView::gFsView.mIdView.lookupByID(\n                                              *loc_it);\n\n      if (filesystem) {\n        eos::common::FileSystem::fs_snapshot_t fs;\n\n        if (filesystem->SnapShotFileSystem(fs, true)) {\n          std::string fstpath =\n            eos::common::FileId::FidPrefix2FullPath(hex_fid.c_str(),\n                fs.mPath.c_str());\n          jsonfsinfo[\"fsid\"] = fs.mId;\n          jsonfsinfo[\"geotag\"] = filesystem->GetString(\"stat.geotag\");\n          jsonfsinfo[\"host\"] = fs.mHost;\n          jsonfsinfo[\"mountpoint\"] = fs.mPath;\n          jsonfsinfo[\"fstpath\"] = fstpath.c_str();\n          jsonfsinfo[\"schedgroup\"] = fs.mGroup;\n          jsonfsinfo[\"status\"] = eos::common::FileSystem::GetStatusAsString(fs.mStatus);\n\n          if (!fs.mForceGeoTag.empty()) {\n            jsonfsinfo[\"forcegeotag\"] = fs.mForceGeoTag;\n          }\n        }\n      } else {\n        jsonfsinfo[\"fsid\"] = *loc_it;\n      }\n\n      jsonfsids.append(jsonfsinfo);\n    }\n\n    json[\"locations\"] = jsonfsids;\n    json[\"checksumtype\"] = eos::common::LayoutId::GetChecksumString(\n                             fmd_copy->getLayoutId());\n    std::string cks;\n    eos::appendChecksumOnStringAsHex(fmd_copy.get(), cks);\n    json[\"checksumvalue\"] = cks;\n    // Add alternative checksums if available\n    auto altchecksums = fmd_copy->getAltXs();\n\n    if (!altchecksums.empty()) {\n      Json::Value jsonaltxs;\n\n      for (auto [type, xs] : altchecksums) {\n        Json::Value elem;\n        elem[\"type\"] = eos::common::LayoutId::GetChecksumString(type);\n        elem[\"value\"] = xs;\n        jsonaltxs.append(elem);\n      }\n\n      json[\"altchecksums\"] = jsonaltxs;\n    }\n\n    std::string etag;\n    eos::calculateEtag(fmd_copy.get(), etag);\n    json[\"etag\"] = etag;\n    json[\"status\"] = FileMDToStatus(fmd_copy);\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_static_debug(\"msg=\\\"exception during JSON fileinfo\\\" ec=%d \"\n                     \"emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                     e.getMessage().str().c_str());\n    json[\"errc\"] = errno;\n    json[\"errmsg\"] = e.getMessage().str().c_str();\n  }\n\n  if (!ret_json) {\n    std::stringstream r;\n    r << json;\n    stdJson += r.str().c_str();\n  } else {\n    *ret_json = json;\n  }\n\n  retc = 0;\n  return SFS_OK;\n}\n\n//------------------------------------------------------------------------------\n// Get directory info in JSON format\n//------------------------------------------------------------------------------\nint\nProcCommand::DirJSON(uint64_t fid, Json::Value* ret_json, bool dolock)\n{\n  eos::IFileMD::ctime_t ctime;\n  eos::IFileMD::ctime_t mtime;\n  eos::IFileMD::ctime_t tmtime;\n  eos::IFileMD::ctime_t btime {0, 0};\n  eos_static_debug(\"msg=\\\"JSON dirinfo\\\" fxid=%08llx\", fid);\n  Json::Value json;\n  json[\"id\"] = (Json::Value::UInt64) fid;\n\n  try {\n    eos::common::RWMutexReadLock viewReadLock;\n    std::shared_ptr<eos::IContainerMD> cmd;\n    std::string path;\n    bool detached;\n    eos::Prefetcher::prefetchContainerMDWithParentsAndWait(gOFS->eosView, fid);\n\n    if (dolock) {\n      viewReadLock.Grab(gOFS->eosViewRWMutex);\n    }\n\n    try {\n      cmd = gOFS->eosDirectoryService->getContainerMD(fid);\n      path = gOFS->eosView->getUri(cmd.get());\n    } catch (eos::MDException& e) {\n      eos_static_debug(\"msg=\\\"exception retrieving container metadata\\\" \"\n                       \"ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                       e.getMessage().str().c_str());\n\n      if (!cmd) {\n        viewReadLock.Release();\n        std::rethrow_exception(std::current_exception());\n      }\n    }\n\n    eos::IFileMD::XAttrMap xattrs = cmd->getAttributes();\n    cmd->getCTime(ctime);\n    cmd->getMTime(mtime);\n    cmd->getTMTime(tmtime);\n\n    if (xattrs.count(\"sys.eos.btime\")) {\n      eos::common::Timing::Timespec_from_TimespecStr(xattrs[\"sys.eos.btime\"],\n          btime);\n    }\n\n    if ((detached = path.empty())) {\n      path = SSTR(\"pid:\" << fid);\n    }\n\n    json[\"inode\"] = (Json::Value::UInt64) fid;\n    json[\"ctime\"] = (Json::Value::UInt64) ctime.tv_sec;\n    json[\"ctime_ns\"] = (Json::Value::UInt64) ctime.tv_nsec;\n    json[\"atime\"] = (Json::Value::UInt64) ctime.tv_sec;\n    json[\"atime_ns\"] = (Json::Value::UInt64) ctime.tv_nsec;\n    json[\"mtime\"] = (Json::Value::UInt64) mtime.tv_sec;\n    json[\"mtime_ns\"] = (Json::Value::UInt64) mtime.tv_nsec;\n    json[\"tmtime\"] = (Json::Value::UInt64) tmtime.tv_sec;\n    json[\"tmtime_ns\"] = (Json::Value::UInt64) tmtime.tv_nsec;\n    json[\"btime\"] = (Json::Value::UInt64) btime.tv_sec;\n    json[\"btime_ns\"] = (Json::Value::UInt64) btime.tv_nsec;\n    json[\"treesize\"] = (Json::Value::UInt64) cmd->getTreeSize();\n    json[\"treecontainers\"] = (Json::Value::UInt64) cmd->getTreeContainers();\n    json[\"treefiles\"] = (Json::Value::UInt64) cmd->getTreeFiles();\n    json[\"uid\"] = cmd->getCUid();\n    json[\"gid\"] = cmd->getCGid();\n    json[\"flags\"] = cmd->getFlags();\n    json[\"mode\"] = cmd->getMode();\n    json[\"nlink\"] = 1;\n    json[\"name\"] = cmd->getName();\n    json[\"path\"] = path;\n    json[\"detached\"] = detached;\n    json[\"pid\"] = (Json::Value::UInt64) cmd->getParentId();\n    json[\"nndirectories\"] = (int)cmd->getNumContainers();\n    json[\"nfiles\"] = (int)cmd->getNumFiles();\n    Json::Value chld;\n    std::shared_ptr<eos::IContainerMD> cmd_copy(cmd->clone());\n    cmd_copy->InheritChildren(*(cmd.get()));\n    cmd.reset();\n    viewReadLock.Release();\n\n    if (!ret_json) {\n      for (auto it = FileMapIterator(cmd_copy); it.valid(); it.next()) {\n        Json::Value fjson;\n        FileJSON(it.value(), &fjson, true);\n        chld.append(fjson);\n      }\n\n      // Loop through all subcontainers\n      for (auto dit = ContainerMapIterator(cmd_copy); dit.valid(); dit.next()) {\n        Json::Value djson;\n        DirJSON(dit.value(), &djson, true);\n        chld.append(djson);\n      }\n    }\n\n    if ((cmd_copy->getNumFiles() + cmd_copy->getNumContainers()) != 0) {\n      json[\"children\"] = chld;\n    }\n\n    Json::Value jsonxattr;\n\n    for (const auto& elem : xattrs) {\n      jsonxattr[elem.first] = elem.second;\n    }\n\n    if (cmd_copy->numAttributes()) {\n      json[\"xattr\"] = jsonxattr;\n    }\n\n    std::string etag;\n    eos::calculateEtag(cmd_copy.get(), etag);\n    json[\"etag\"] = etag;\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_static_debug(\"msg=\\\"exception during JSON dirinfo\\\" ec=%d \"\n                     \"emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                     e.getMessage().str().c_str());\n    json[\"errc\"] = errno;\n    json[\"errmsg\"] = e.getMessage().str().c_str();\n  }\n\n  if (!ret_json) {\n    std::stringstream r;\n    r << json;\n    stdJson += r.str().c_str();\n    retc = 0;\n  } else {\n    *ret_json = json;\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Find.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/Find.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/acl/Acl.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/utils/Checksum.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/Path.hh\"\n\n#ifdef __APPLE__\n#define ENONET 64\n#endif\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Find()\n{\n  mDoSort = true;\n  XrdOucString spath = pOpaque->Get(\"mgm.path\");\n  XrdOucString filematch = pOpaque->Get(\"mgm.find.match\");\n  XrdOucString option = pOpaque->Get(\"mgm.option\");\n  XrdOucString attribute = pOpaque->Get(\"mgm.find.attribute\");\n  XrdOucString maxdepth = pOpaque->Get(\"mgm.find.maxdepth\");\n  XrdOucString olderthan = pOpaque->Get(\"mgm.find.olderthan\");\n  XrdOucString youngerthan = pOpaque->Get(\"mgm.find.youngerthan\");\n  XrdOucString purgeversion = pOpaque->Get(\"mgm.find.purge.versions\");\n  XrdOucString key = attribute;\n  XrdOucString val = attribute;\n  XrdOucString printkey = pOpaque->Get(\"mgm.find.printkey\");\n  const char* inpath = spath.c_str();\n  bool deepquery = false;\n  static XrdSysMutex deepQueryMutex;\n  static std::map<std::string, std::set<std::string> >* globalfound = 0;\n  NAMESPACEMAP;\n  PROC_BOUNCE_ILLEGAL_NAMES;\n  PROC_BOUNCE_NOT_ALLOWED;\n  spath = path;\n  PROC_TOKEN_SCOPE;\n\n  if (!OpenTemporaryOutputFiles()) {\n    stdErr += \"error: cannot write find result files on MGM\\n\";\n    retc = EIO;\n    return SFS_OK;\n  }\n\n  eos::common::Path cPath(spath.c_str());\n\n  if (cPath.GetSubPathSize() < 5) {\n    if ((((option.find(\"d\")) != STR_NPOS) && ((option.find(\"f\")) == STR_NPOS))) {\n      // directory queries are fine even for the complete namespace\n      deepquery = false;\n    } else {\n      // deep queries are serialized by a mutex and use a single the output hashmap !\n      deepquery = true;\n    }\n  }\n\n  // this hash is used to calculate the balance of the found files over the filesystems involved\n  google::dense_hash_map<unsigned long, unsigned long long> filesystembalance;\n  google::dense_hash_map<std::string, unsigned long long> spacebalance;\n  google::dense_hash_map<std::string, unsigned long long> schedulinggroupbalance;\n  google::dense_hash_map<int, unsigned long long> sizedistribution;\n  google::dense_hash_map<int, unsigned long long> sizedistributionn;\n  filesystembalance.set_empty_key(0);\n  spacebalance.set_empty_key(\"\");\n  schedulinggroupbalance.set_empty_key(\"\");\n  sizedistribution.set_empty_key(-1);\n  sizedistributionn.set_empty_key(-1);\n  bool calcbalance = false;\n  bool findzero = false;\n  bool findgroupmix = false;\n  bool printsize = false;\n  bool printfid = false;\n  bool printuid = false;\n  bool printgid = false;\n  bool printfs = false;\n  bool printchecksum = false;\n  bool printctime = false;\n  bool printmtime = false;\n  bool printrep = false;\n  bool selectrepdiff = false;\n  bool selectonehour = false;\n  bool printunlink = false;\n  bool printcounter = false;\n  bool printchildcount = false;\n  bool printhosts = false;\n  bool printpartition = false;\n  bool selectonline = false;\n  bool printfileinfo = false;\n  bool selectfaultyacl = false;\n  bool purge = false;\n  bool purge_atomic = false;\n  bool printxurl = false;\n  XrdOucString url = \"root://\";\n  url += gOFS->MgmOfsAlias;\n  url += \"/\";\n  int  max_version = 999999;\n  int  finddepth = 0;\n  time_t selectoldertime = 0;\n  time_t selectyoungertime = 0;\n\n  if (olderthan.c_str()) {\n    selectoldertime = (time_t) strtoull(olderthan.c_str(), 0, 10);\n  }\n\n  if (youngerthan.c_str()) {\n    selectyoungertime = (time_t) strtoull(youngerthan.c_str(), 0, 10);\n  }\n\n  if (option.find(\"b\") != STR_NPOS) {\n    calcbalance = true;\n  }\n\n  if (option.find(\"0\") != STR_NPOS) {\n    findzero = true;\n  }\n\n  if (option.find(\"G\") != STR_NPOS) {\n    findgroupmix = true;\n  }\n\n  if (option.find(\"S\") != STR_NPOS) {\n    printsize = true;\n  }\n\n  if (option.find(\"F\") != STR_NPOS) {\n    printfid = true;\n  }\n\n  if (option.find(\"L\") != STR_NPOS) {\n    printfs = true;\n  }\n\n  if (option.find(\"X\") != STR_NPOS) {\n    printchecksum = true;\n  }\n\n  if (option.find(\"u\") != STR_NPOS) {\n    printuid = true;\n  }\n\n  if (option.find(\"g\") != STR_NPOS) {\n    printgid = true;\n  }\n\n  if (option.find(\"C\") != STR_NPOS) {\n    printctime = true;\n  }\n\n  if (option.find(\"M\") != STR_NPOS) {\n    printmtime = true;\n  }\n\n  if (option.find(\"R\") != STR_NPOS) {\n    printrep = true;\n  }\n\n  if (option.find(\"U\") != STR_NPOS) {\n    printunlink = true;\n  }\n\n  if (option.find(\"D\") != STR_NPOS) {\n    selectrepdiff = true;\n  }\n\n  if (option.find(\"1\") != STR_NPOS) {\n    selectonehour = true;\n  }\n\n  if (option.find(\"Z\") != STR_NPOS) {\n    printcounter = true;\n  }\n\n  if (option.find(\"l\") != STR_NPOS) {\n    printchildcount = true;\n  }\n\n  if (option.find(\"x\") != STR_NPOS) {\n    printxurl = true;\n  }\n\n  if (option.find(\"H\") != STR_NPOS) {\n    printhosts = true;\n  }\n\n  if (option.find(\"P\") != STR_NPOS) {\n    printpartition = true;\n  }\n\n  if (option.find(\"O\") != STR_NPOS) {\n    selectonline = true;\n  }\n\n  if (option.find(\"I\") != STR_NPOS) {\n    printfileinfo = true;\n\n    if ((option.find(\"d\") == STR_NPOS) && (option.find(\"f\") == STR_NPOS)) {\n      option += \"df\";\n    }\n\n    printchildcount = false;\n  }\n\n  if (option.find(\"A\") != STR_NPOS) {\n    selectfaultyacl = true;\n    option += \"d\";\n  }\n\n  if (purgeversion.length()) {\n    if (purgeversion == \"atomic\") {\n      purge_atomic = true;\n      option += \"f\";\n    } else {\n      if ((atoi(purgeversion.c_str()) == 0) && (purgeversion != \"0\")) {\n        fprintf(fstderr,\n                \"error: the max. version given to --purge has to be a valid number >=0\");\n        retc = EINVAL;\n        return SFS_OK;\n      }\n\n      max_version = atoi(purgeversion.c_str());\n      purge = true;\n      option += \"d\";\n    }\n  }\n\n  if ((option.find('f') == STR_NPOS) && (option.find('d') == STR_NPOS)) {\n    if (!printcounter) {\n      option += \"df\";\n    }\n  }\n\n  if (attribute.length()) {\n    key.erase(attribute.find(\"=\"));\n    val.erase(0, attribute.find(\"=\") + 1);\n  }\n\n  if (maxdepth.length()) {\n    finddepth = atoi(maxdepth.c_str());\n\n    if (finddepth > 0) {\n      deepquery = false;\n    }\n  }\n\n  if (!spath.length()) {\n    fprintf(fstderr, \"error: you have to give a path name to call 'find'\");\n    retc = EINVAL;\n  } else {\n    std::map<std::string, std::set<std::string> >* found = 0;\n\n    if (deepquery) {\n      // we use a single once allocated map for deep searches to store the results to avoid memory explosion\n      deepQueryMutex.Lock();\n\n      if (!globalfound) {\n        globalfound = new std::map<std::string, std::set<std::string> >;\n      }\n\n      found = globalfound;\n    } else {\n      found = new std::map<std::string, std::set<std::string> >;\n    }\n\n    std::map<std::string, std::set<std::string> >::const_iterator foundit;\n    std::set<std::string>::const_iterator fileit;\n    bool nofiles = false;\n\n    if (((option.find(\"d\")) != STR_NPOS) && ((option.find(\"f\")) == STR_NPOS)) {\n      nofiles = true;\n    }\n\n    // check what <path> actually is ...\n    XrdSfsFileExistence file_exists;\n\n    if ((gOFS->_exists(spath.c_str(), file_exists, *mError, *pVid, 0))) {\n      stdErr += \"error: failed to run exists on '\";\n      stdErr += spath.c_str();\n      stdErr += \"'\";\n      fprintf(fstderr, \"%s\", stdErr.c_str());\n      retc = errno;\n\n      if (deepquery) {\n        deepQueryMutex.UnLock();\n      } else {\n        delete found;\n      }\n\n      return SFS_OK;\n    } else {\n      if (file_exists == XrdSfsFileExistIsFile) {\n        // if this is already a file name, we switch off to find directories\n        option += \"f\";\n      }\n\n      if (file_exists == XrdSfsFileExistNo) {\n        stdErr += \"error: no such file or directory\";\n        fprintf(fstderr, \"%s\", stdErr.c_str());\n        retc = ENOENT;\n\n        if (deepquery) {\n          deepQueryMutex.UnLock();\n        } else {\n          delete found;\n        }\n\n        return SFS_OK;\n      }\n    }\n\n    if (gOFS->_find(spath.c_str(), *mError, stdErr, *pVid, (*found),\n                    key.c_str(), val.c_str(), nofiles, 0, true, finddepth,\n                    filematch.length() ? filematch.c_str() : 0, false,\n                    option.find(\"j\") != STR_NPOS, fstdout)) {\n      fprintf(fstderr, \"%s\", stdErr.c_str());\n      fprintf(fstderr, \"error: unable to run find in directory\");\n      retc = errno;\n\n      if (deepquery) {\n        deepQueryMutex.UnLock();\n      } else {\n        delete found;\n      }\n\n      return SFS_OK;\n    } else {\n      if (stdErr.length()) {\n        fprintf(fstderr, \"%s\", stdErr.c_str());\n        retc = E2BIG;\n      }\n    }\n\n    int cnt = 0;\n    unsigned long long filecounter = 0;\n    unsigned long long dircounter = 0;\n\n    if (((option.find(\"f\")) != STR_NPOS) || ((option.find(\"d\")) == STR_NPOS)) {\n      for (foundit = (*found).begin(); foundit != (*found).end(); foundit++) {\n        if ((option.find(\"d\")) == STR_NPOS) {\n          if (option.find(\"f\") == STR_NPOS) {\n            if (!printcounter) {\n              if (printxurl) {\n                fprintf(fstdout, \"%s\", url.c_str());\n              }\n\n              fprintf(fstdout, \"%s\\n\", foundit->first.c_str());\n            }\n\n            dircounter++;\n          }\n        }\n\n        for (fileit = foundit->second.begin(); fileit != foundit->second.end();\n             fileit++) {\n          cnt++;\n          std::string fspath = foundit->first;\n          fspath += *fileit;\n\n          if (!calcbalance) {\n            if (findgroupmix || findzero || printsize || printfid || printuid ||\n                printgid || printfileinfo || printchecksum || printctime ||\n                printmtime || printrep || printunlink || printhosts ||\n                printpartition || selectrepdiff || selectonehour ||\n                selectoldertime || selectyoungertime || purge_atomic) {\n              //-------------------------------------------\n              eos::common::RWMutexReadLock viewReadLock(gOFS->eosViewRWMutex);\n              std::shared_ptr<eos::IFileMD> fmd;\n\n              try {\n                bool selected = true;\n                fmd = gOFS->eosView->getFile(fspath.c_str());\n                viewReadLock.Release();\n                //-------------------------------------------\n\n                if (selectonehour) {\n                  eos::IFileMD::ctime_t mtime;\n                  fmd->getMTime(mtime);\n\n                  if (mtime.tv_sec > (time(NULL) - 3600)) {\n                    selected = false;\n                  }\n                }\n\n                if (selectoldertime) {\n                  eos::IFileMD::ctime_t xtime;\n\n                  if (printctime) {\n                    fmd->getCTime(xtime);\n                  } else {\n                    fmd->getMTime(xtime);\n                  }\n\n                  if (xtime.tv_sec > selectoldertime) {\n                    selected = false;\n                  }\n                }\n\n                if (selectyoungertime) {\n                  eos::IFileMD::ctime_t xtime;\n\n                  if (printctime) {\n                    fmd->getCTime(xtime);\n                  } else {\n                    fmd->getMTime(xtime);\n                  }\n\n                  if (xtime.tv_sec < selectyoungertime) {\n                    selected = false;\n                  }\n                }\n\n                if (selected && findgroupmix) {\n                  if (findzero && !fmd->getSize()) {\n                    if (!printcounter) {\n                      if (printxurl) {\n                        fprintf(fstdout, \"%s\", url.c_str());\n                      }\n\n                      fprintf(fstdout, \"%s\\n\", fspath.c_str());\n                    }\n                  }\n\n                  // find files which have replicas on mixed scheduling groups\n                  XrdOucString sGroupRef = \"\";\n                  XrdOucString sGroup = \"\";\n                  bool mixed = false;\n                  eos::IFileMD::LocationVector loc_vect = fmd->getLocations();\n                  eos::IFileMD::LocationVector::const_iterator lociter;\n\n                  for (lociter = loc_vect.begin(); lociter != loc_vect.end(); ++lociter) {\n                    // ignore filesystem id 0\n                    if (!(*lociter)) {\n                      eos_err(\"fsid 0 found fxid=%08llx\", fmd->getId());\n                      continue;\n                    }\n\n                    eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n                    eos::common::FileSystem* filesystem = FsView::gFsView.mIdView.lookupByID(\n                                                            *lociter);\n\n                    if (filesystem) {\n                      sGroup = filesystem->GetString(\"schedgroup\").c_str();\n                    } else {\n                      sGroup = \"none\";\n                    }\n\n                    if (sGroupRef.length()) {\n                      if (sGroup != sGroupRef) {\n                        mixed = true;\n                        break;\n                      }\n                    } else {\n                      sGroupRef = sGroup;\n                    }\n                  }\n\n                  if (mixed) {\n                    if (!printcounter) {\n                      if (printxurl) {\n                        fprintf(fstdout, \"%s\", url.c_str());\n                      }\n\n                      fprintf(fstdout, \"%s\\n\", fspath.c_str());\n                    }\n                  }\n                } else {\n                  if (selected &&\n                      (selectonehour || selectoldertime || selectyoungertime ||\n                       findzero || printsize || printfid || printuid || printgid ||\n                       printchecksum || printfileinfo || printfs || printctime ||\n                       printmtime || printrep || printunlink || printhosts ||\n                       printpartition || selectrepdiff || purge_atomic)) {\n                    XrdOucString sizestring;\n                    bool printed = true;\n\n                    if (selectrepdiff) {\n                      if (fmd->getNumLocation() != (eos::common::LayoutId::GetStripeNumber(\n                                                      fmd->getLayoutId()) + 1)) {\n                        printed = true;\n                      } else {\n                        printed = false;\n                      }\n                    }\n\n                    if (findzero) {\n                      printed = (fmd->getSize() == 0);\n                    }\n\n                    if (purge_atomic) {\n                      printed = false;\n                    }\n\n                    if (printed) {\n                      if (!printfileinfo) {\n                        if (!printcounter) {\n                          fprintf(fstdout, \"path=\");\n\n                          if (printxurl) {\n                            fprintf(fstdout, \"%s\", url.c_str());\n                          }\n\n                          fprintf(fstdout, \"%s\", fspath.c_str());\n                        }\n\n                        if (printsize) {\n                          if (!printcounter) {\n                            fprintf(fstdout, \" size=%llu\", (unsigned long long) fmd->getSize());\n                          }\n                        }\n\n                        if (printfid) {\n                          if (!printcounter) {\n                            // @todo (esindril) this should be printed using the fxid tag\n                            // But to avoid breaking scripts we won't change this for the\n                            // moment.\n                            fprintf(fstdout, \" fid=%08llx\", (unsigned long long) fmd->getId());\n                          }\n                        }\n\n                        if (printuid) {\n                          if (!printcounter) {\n                            fprintf(fstdout, \" uid=%u\", (unsigned int) fmd->getCUid());\n                          }\n                        }\n\n                        if (printgid) {\n                          if (!printcounter) {\n                            fprintf(fstdout, \" gid=%u\", (unsigned int) fmd->getCGid());\n                          }\n                        }\n\n                        if (printfs) {\n                          if (!printcounter) {\n                            fprintf(fstdout, \" fsid=\");\n                          }\n\n                          eos::IFileMD::LocationVector loc_vect = fmd->getLocations();\n                          eos::IFileMD::LocationVector::const_iterator lociter;\n\n                          for (lociter = loc_vect.begin(); lociter != loc_vect.end(); ++lociter) {\n                            if (lociter != loc_vect.begin()) {\n                              if (!printcounter) {\n                                fprintf(fstdout, \",\");\n                              }\n                            }\n\n                            if (!printcounter) {\n                              fprintf(fstdout, \"%d\", (int) *lociter);\n                            }\n                          }\n                        }\n\n                        if ((printpartition) && (!printcounter)) {\n                          fprintf(fstdout, \" partition=\");\n                          std::set<std::string> fsPartition;\n                          eos::IFileMD::LocationVector loc_vect = fmd->getLocations();\n                          eos::IFileMD::LocationVector::const_iterator lociter;\n\n                          for (lociter = loc_vect.begin(); lociter != loc_vect.end(); ++lociter) {\n                            // get host name for fs id\n                            eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n                            eos::common::FileSystem* filesystem = FsView::gFsView.mIdView.lookupByID(\n                                                                    *lociter);\n\n                            if (filesystem) {\n                              eos::common::FileSystem::fs_snapshot_t fs;\n\n                              if (filesystem->SnapShotFileSystem(fs, true)) {\n                                std::string partition = fs.mHost;\n                                partition += \":\";\n                                partition += fs.mPath;\n\n                                if ((!selectonline) ||\n                                    (filesystem->GetActiveStatus() == eos::common::ActiveStatus::kOnline)) {\n                                  fsPartition.insert(partition);\n                                }\n                              }\n                            }\n                          }\n\n                          for (auto partitionit = fsPartition.begin(); partitionit != fsPartition.end();\n                               partitionit++) {\n                            if (partitionit != fsPartition.begin()) {\n                              fprintf(fstdout, \",\");\n                            }\n\n                            fprintf(fstdout, \"%s\", partitionit->c_str());\n                          }\n                        }\n\n                        if ((printhosts) && (!printcounter)) {\n                          fprintf(fstdout, \" hosts=\");\n                          std::set<std::string> fsHosts;\n                          eos::IFileMD::LocationVector loc_vect = fmd->getLocations();\n                          eos::IFileMD::LocationVector::const_iterator lociter;\n\n                          for (lociter = loc_vect.begin(); lociter != loc_vect.end(); ++lociter) {\n                            // get host name for fs id\n                            eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n                            eos::common::FileSystem* filesystem = FsView::gFsView.mIdView.lookupByID(\n                                                                    *lociter);\n\n                            if (filesystem) {\n                              eos::common::FileSystem::fs_snapshot_t fs;\n\n                              if (filesystem->SnapShotFileSystem(fs, true)) {\n                                fsHosts.insert(fs.mHost);\n                              }\n                            }\n                          }\n\n                          for (auto hostit = fsHosts.begin(); hostit != fsHosts.end(); hostit++) {\n                            if (hostit != fsHosts.begin()) {\n                              fprintf(fstdout, \",\");\n                            }\n\n                            fprintf(fstdout, \"%s\", hostit->c_str());\n                          }\n                        }\n\n                        if (printchecksum) {\n                          if (!printcounter) {\n                            fprintf(fstdout, \" checksum=\");\n                            std::string str;\n                            eos::appendChecksumOnStringAsHex(fmd.get(), str);\n\n                            if (!str.empty()) {\n                              fprintf(fstdout, \"%s\", str.c_str());\n                            }\n                          }\n                        }\n\n                        if (printctime) {\n                          eos::IFileMD::ctime_t ctime;\n                          fmd->getCTime(ctime);\n\n                          if (!printcounter)\n                            fprintf(fstdout, \" ctime=%llu.%llu\", (unsigned long long)\n                                    ctime.tv_sec, (unsigned long long) ctime.tv_nsec);\n                        }\n\n                        if (printmtime) {\n                          eos::IFileMD::ctime_t mtime;\n                          fmd->getMTime(mtime);\n\n                          if (!printcounter)\n                            fprintf(fstdout, \" mtime=%llu.%llu\", (unsigned long long)\n                                    mtime.tv_sec, (unsigned long long) mtime.tv_nsec);\n                        }\n\n                        if (printrep) {\n                          if (!printcounter) {\n                            fprintf(fstdout, \" nrep=%d\", (int) fmd->getNumLocation());\n                          }\n                        }\n\n                        if (printunlink) {\n                          if (!printcounter) {\n                            fprintf(fstdout, \" nunlink=%d\", (int) fmd->getNumUnlinkedLocation());\n                          }\n                        }\n                      } else {\n                        // print fileinfo -m\n                        ProcCommand Cmd;\n                        XrdOucString lStdOut = \"\";\n                        XrdOucString lStdErr = \"\";\n                        XrdOucString info = \"&mgm.cmd=fileinfo&mgm.path=\";\n                        info += fspath.c_str();\n                        info += \"&mgm.file.info.option=-m\";\n                        Cmd.open(\"/proc/user\", info.c_str(), *pVid, mError);\n                        Cmd.AddOutput(lStdOut, lStdErr);\n\n                        if (lStdOut.length()) {\n                          fprintf(fstdout, \"%s\", lStdOut.c_str());\n                        }\n\n                        if (lStdErr.length()) {\n                          fprintf(fstderr, \"%s\", lStdErr.c_str());\n                        }\n\n                        Cmd.close();\n                      }\n\n                      if (!printcounter) {\n                        fprintf(fstdout, \"\\n\");\n                      }\n                    }\n\n                    if (purge_atomic &&\n                        (fspath.find(EOS_COMMON_PATH_ATOMIC_FILE_PREFIX) != std::string::npos)) {\n                      fprintf(fstdout, \"# found atomic %s\\n\", fspath.c_str());\n                      struct stat buf;\n\n                      if ((!gOFS->_stat(fspath.c_str(), &buf, *mError, *pVid, (const char*) 0, 0)) &&\n                          ((pVid->uid == 0) || (pVid->uid == buf.st_uid))) {\n                        time_t now = time(NULL);\n\n                        if ((now - buf.st_ctime) > 86400) {\n                          if (!gOFS->_rem(fspath.c_str(), *mError, *pVid, (const char*) 0)) {\n                            fprintf(fstdout, \"# purging atomic %s\", fspath.c_str());\n                          }\n                        } else {\n                          fprintf(fstdout, \"# skipping atomic %s [< 1d old ]\\n\", fspath.c_str());\n                        }\n                      }\n                    }\n                  }\n                }\n\n                if (selected) {\n                  filecounter++;\n                }\n              } catch (eos::MDException& e) {\n                eos_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                          e.getMessage().str().c_str());\n                viewReadLock.Release();\n                //-------------------------------------------\n              }\n            } else {\n              if ((!printcounter) && (!purge_atomic)) {\n                if (printxurl) {\n                  fprintf(fstdout, \"%s\", url.c_str());\n                }\n\n                fprintf(fstdout, \"%s\\n\", fspath.c_str());\n              }\n\n              filecounter++;\n            }\n          } else {\n            // get location\n            //-------------------------------------------\n            eos::common::RWMutexReadLock viewReadLock(gOFS->eosViewRWMutex);\n            std::shared_ptr<eos::IFileMD> fmd;\n\n            try {\n              fmd = gOFS->eosView->getFile(fspath.c_str());\n            } catch (eos::MDException& e) {\n              eos_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                        e.getMessage().str().c_str());\n            }\n\n            if (fmd) {\n              viewReadLock.Release();\n              //-------------------------------------------\n\n              for (unsigned int i = 0; i < fmd->getNumLocation(); i++) {\n                int loc = fmd->getLocation(i);\n                uint64_t size = eos::common::LayoutId::GetStripeFileSize(fmd->getLayoutId(),\n                                fmd->getSize());\n\n                if (!loc) {\n                  eos_err(\"fsid 0 found %s %llu\", fmd->getName().c_str(), fmd->getId());\n                  continue;\n                }\n\n                filesystembalance[loc] += size;\n\n                if ((i == 0) && (size)) {\n                  int bin = (int) log10((double) size);\n                  sizedistribution[ bin ] += size;\n                  sizedistributionn[ bin ]++;\n                }\n\n                eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n                eos::common::FileSystem* filesystem = FsView::gFsView.mIdView.lookupByID(loc);\n\n                if (filesystem) {\n                  eos::common::FileSystem::fs_snapshot_t fs;\n\n                  if (filesystem->SnapShotFileSystem(fs, true)) {\n                    spacebalance[fs.mSpace.c_str()] += size;\n                    schedulinggroupbalance[fs.mGroup.c_str()] += size;\n                  }\n                }\n              }\n            } else {\n              viewReadLock.Release();\n              //-------------------------------------------\n            }\n          }\n        }\n      }\n\n      gOFS->MgmStats.Add(\"FindEntries\", pVid->uid, pVid->gid, cnt);\n    }\n\n    eos_debug(\"Listing directories\");\n\n    if ((option.find(\"d\")) != STR_NPOS) {\n      for (foundit = (*found).begin(); foundit != (*found).end(); foundit++) {\n        // eventually call the version purge function if we own this version dir or we are root\n        if (purge &&\n            (foundit->first.find(EOS_COMMON_PATH_VERSION_PREFIX) != std::string::npos)) {\n          struct stat buf;\n\n          if ((!gOFS->_stat(foundit->first.c_str(), &buf, *mError, *pVid, (const char*) 0,\n                            0)) &&\n              ((pVid->uid == 0) || (pVid->uid == buf.st_uid))) {\n            fprintf(fstdout, \"# purging %s\", foundit->first.c_str());\n            gOFS->PurgeVersion(foundit->first.c_str(), *mError, max_version);\n          }\n        }\n\n        if (selectfaultyacl) {\n          // get the attributes and call the verify function\n          eos::IContainerMD::XAttrMap map;\n\n          if (!gOFS->_attr_ls(foundit->first.c_str(),\n                              *mError,\n                              *pVid,\n                              (const char*) 0,\n                              map)\n             ) {\n            if ((map.count(\"sys.acl\") || map.count(\"user.acl\"))) {\n              if (map.count(\"sys.acl\")) {\n                if (Acl::IsValid(map[\"sys.acl\"].c_str(), *mError)) {\n                  continue;\n                }\n              }\n\n              if (map.count(\"user.acl\")) {\n                if (Acl::IsValid(map[\"user.acl\"].c_str(), *mError)) {\n                  continue;\n                }\n              }\n            } else {\n              continue;\n            }\n          }\n        }\n\n        // print directories\n        std::string attr = \"\";\n\n        if (printkey.length()) {\n          gOFS->_attr_get(foundit->first.c_str(), *mError, vid, (const char*) 0,\n                          printkey.c_str(), attr);\n\n          if (printkey.length()) {\n            if (!attr.length()) {\n              attr = \"undef\";\n            }\n\n            if (!printcounter) {\n              fprintf(fstdout, \"%s=%-32s path=\", printkey.c_str(), attr.c_str());\n            }\n          }\n        }\n\n        if (!purge && !printcounter) {\n          if (printchildcount) {\n            //-------------------------------------------\n            eos::common::RWMutexReadLock nLock(gOFS->eosViewRWMutex);\n            std::shared_ptr<eos::IContainerMD> cmd;\n            unsigned long long childfiles = 0;\n            unsigned long long childdirs = 0;\n\n            try {\n              cmd = gOFS->eosView->getContainer(foundit->first.c_str());\n              childfiles = cmd->getNumFiles();\n              childdirs = cmd->getNumContainers();\n              fprintf(fstdout, \"%s ndir=%llu nfiles=%llu\\n\", foundit->first.c_str(),\n                      childdirs, childfiles);\n            } catch (eos::MDException& e) {\n              eos_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                        e.getMessage().str().c_str());\n            }\n          } else {\n            if (!printfileinfo) {\n              if (printxurl) {\n                fprintf(fstdout, \"%s\", url.c_str());\n              }\n\n              fprintf(fstdout, \"path=%s\", foundit->first.c_str());\n\n              if (printuid || printgid) {\n                eos::common::RWMutexReadLock nLock(gOFS->eosViewRWMutex);\n                std::shared_ptr<eos::IContainerMD> cmd;\n\n                try {\n                  cmd = gOFS->eosView->getContainer(foundit->first.c_str());\n\n                  if (printuid) {\n                    fprintf(fstdout, \" uid=%u\", (unsigned int) cmd->getCUid());\n                  }\n\n                  if (printgid) {\n                    fprintf(fstdout, \" gid=%u\", (unsigned int) cmd->getCGid());\n                  }\n                } catch (eos::MDException& e) {\n                  eos_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                            e.getMessage().str().c_str());\n                }\n              }\n            } else {\n              // print fileinfo -m\n              ProcCommand Cmd;\n              XrdOucString lStdOut = \"\";\n              XrdOucString lStdErr = \"\";\n              XrdOucString info = \"&mgm.cmd=fileinfo&mgm.path=\";\n              info += foundit->first.c_str();\n              info += \"&mgm.file.info.option=-m\";\n              Cmd.open(\"/proc/user\", info.c_str(), *pVid, mError);\n              Cmd.AddOutput(lStdOut, lStdErr);\n\n              if (lStdOut.length()) {\n                fprintf(fstdout, \"%s\", lStdOut.c_str());\n              }\n\n              if (lStdErr.length()) {\n                fprintf(fstderr, \"%s\", lStdErr.c_str());\n              }\n\n              Cmd.close();\n            }\n\n            fprintf(fstdout, \"\\n\");\n          }\n        }\n      }\n\n      dircounter++;\n    }\n\n    if (deepquery) {\n      globalfound->clear();\n      deepQueryMutex.UnLock();\n    } else {\n      delete found;\n    }\n\n    if (printcounter) {\n      fprintf(fstdout, \"nfiles=%llu ndirectories=%llu\\n\", filecounter, dircounter);\n    }\n  }\n\n  if (calcbalance) {\n    XrdOucString sizestring = \"\";\n    google::dense_hash_map<unsigned long, unsigned long long>::iterator it;\n\n    for (it = filesystembalance.begin(); it != filesystembalance.end(); it++) {\n      fprintf(fstdout, \"fsid=%lu \\tvolume=%-12s \\tnbytes=%llu\\n\", it->first,\n              eos::common::StringConversion::GetReadableSizeString(sizestring, it->second,\n                  \"B\"), it->second);\n    }\n\n    google::dense_hash_map<std::string, unsigned long long>::iterator its;\n\n    for (its = spacebalance.begin(); its != spacebalance.end(); its++) {\n      fprintf(fstdout, \"space=%s \\tvolume=%-12s \\tnbytes=%llu\\n\", its->first.c_str(),\n              eos::common::StringConversion::GetReadableSizeString(sizestring, its->second,\n                  \"B\"), its->second);\n    }\n\n    google::dense_hash_map<std::string, unsigned long long>::iterator itg;\n\n    for (itg = schedulinggroupbalance.begin(); itg != schedulinggroupbalance.end();\n         itg++) {\n      fprintf(fstdout, \"sched=%s \\tvolume=%-12s \\tnbytes=%llu\\n\", itg->first.c_str(),\n              eos::common::StringConversion::GetReadableSizeString(sizestring, itg->second,\n                  \"B\"), itg->second);\n    }\n\n    google::dense_hash_map<int, unsigned long long>::iterator itsd;\n\n    for (itsd = sizedistribution.begin(); itsd != sizedistribution.end(); itsd++) {\n      unsigned long long lowerlimit = 0;\n      unsigned long long upperlimit = 0;\n\n      if (((itsd->first) - 1) > 0) {\n        lowerlimit = pow(10, (itsd->first));\n      }\n\n      if ((itsd->first) > 0) {\n        upperlimit = pow(10, (itsd->first) + 1);\n      }\n\n      XrdOucString sizestring1;\n      XrdOucString sizestring2;\n      XrdOucString sizestring3;\n      XrdOucString sizestring4;\n      unsigned long long avgsize = (unsigned long long)(sizedistributionn[itsd->first]\n                                   ? itsd->second / sizedistributionn[itsd->first] : 0);\n      fprintf(fstdout,\n              \"sizeorder=%02d \\trange=[ %-12s ... %-12s ] volume=%-12s \\tavgsize=%-12s \\tnbytes=%llu \\t avgnbytes=%llu \\t nfiles=%llu\\n\",\n              itsd->first\n              , eos::common::StringConversion::GetReadableSizeString(sizestring1, lowerlimit,\n                  \"B\")\n              , eos::common::StringConversion::GetReadableSizeString(sizestring2, upperlimit,\n                  \"B\")\n              , eos::common::StringConversion::GetReadableSizeString(sizestring3,\n                  itsd->second, \"B\")\n              , eos::common::StringConversion::GetReadableSizeString(sizestring4, avgsize,\n                  \"B\")\n              , itsd->second\n              , avgsize\n              , sizedistributionn[itsd->first]\n             );\n    }\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Fuse.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/Fuse.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/ofs/XrdMgmOfsDirectory.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"common/Path.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Fuse()\n{\n  gOFS->MgmStats.Add(\"Fuse-Dirlist\", pVid->uid, pVid->gid, 1);\n  XrdOucString spath = pOpaque->Get(\"mgm.path\");\n  bool statentries = pOpaque->GetInt(\"mgm.statentries\") == -999999999 ? false :\n                     (bool)pOpaque->GetInt(\"mgm.statentries\");\n  bool encodepath  = pOpaque->Get(\"eos.encodepath\");\n  const char* inpath = spath.c_str();\n  NAMESPACEMAP;\n  PROC_BOUNCE_ILLEGAL_NAMES;\n  PROC_BOUNCE_NOT_ALLOWED;\n  spath = path;\n\n  if (encodepath) {\n    mResultStream = \"inodirlist_pathencode: retc=\";\n  } else {\n    mResultStream = \"inodirlist: retc=\";\n  }\n\n  if (!spath.length()) {\n    mResultStream += EINVAL;\n  } else {\n    XrdMgmOfsDirectory* inodir = (XrdMgmOfsDirectory*) gOFS->newDir((char*) \"\");\n\n    if (!inodir) {\n      mResultStream += ENOMEM;\n      return SFS_ERROR;\n    }\n\n    if ((retc = inodir->_open(path, *pVid, 0)) != SFS_OK) {\n      delete inodir;\n      retc = -retc;\n      mResultStream += retc;\n      mLen = mResultStream.length();\n      return SFS_OK;\n    }\n\n    const char* entry;\n    mResultStream += \"0 \";\n    unsigned long long inode = 0;\n    char inodestr[256];\n    size_t dotend = 0;\n    size_t dotstart = mResultStream.length();\n\n    while ((entry = inodir->nextEntry())) {\n      bool isdot = false;\n      bool isdotdot = false;\n      XrdOucString whitespaceentry = entry;\n\n      if (whitespaceentry == \".\") {\n        isdot = true;\n      }\n\n      if (whitespaceentry == \"..\") {\n        isdotdot = true;\n      }\n\n      if (encodepath) {\n        whitespaceentry = eos::common::StringConversion::curl_escaped(\n                            whitespaceentry.c_str()).c_str();\n      } else {\n        // encode spaces\n        whitespaceentry.replace(\" \", \"%20\");\n        // encode \\n\n        whitespaceentry.replace(\"\\n\", \"%0A\");\n      }\n\n      if ((!isdot) && (!isdotdot)) {\n        mResultStream += whitespaceentry.c_str();\n        mResultStream += \" \";\n      }\n\n      if (isdot) {\n        // the . and .. has to be streamed as first entries\n        mResultStream.insert(dotstart, \". \");\n      }\n\n      if (isdotdot) {\n        mResultStream.insert(dotend, \".. \");\n      }\n\n      XrdOucString statpath = path;\n      statpath += \"/\";\n      statpath += entry;\n      eos::common::Path cPath(statpath.c_str());\n      // attach MD to get inode number\n      std::shared_ptr<eos::IFileMD> fmd;\n      std::shared_ptr<eos::IContainerMD> dir;\n      inode = 0;\n      //-------------------------------------------\n      {\n        eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n\n        try {\n          fmd = gOFS->eosView->getFile(cPath.GetPath(), false);\n          inode = eos::common::FileId::FidToInode(fmd->getId());\n        } catch (eos::MDException& e) {\n          errno = e.getErrno();\n          eos_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                    e.getMessage().str().c_str());\n        }\n      }\n      //-------------------------------------------\n\n      // check if that is a directory in case\n      if (!fmd) {\n        eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n\n        try {\n          dir = gOFS->eosView->getContainer(cPath.GetPath(), false);\n          inode = dir->getId();\n        } catch (eos::MDException& e) {\n          dir = std::shared_ptr<IContainerMD>((IContainerMD*)0);\n          eos_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                    e.getMessage().str().c_str());\n        }\n      }\n\n      sprintf(inodestr, \"%llu\", inode);\n\n      if ((!isdot) && (!isdotdot) && inode) {\n        mResultStream += inodestr;\n        mResultStream += \" \";\n\n        if (statentries && (fmd || dir)) {\n          struct stat buf;\n          std::string uri;\n\n          if (!gOFS->_stat(cPath.GetPath(), &buf, *mError, *pVid, (const char*) 0, 0,\n                           false, &uri)) {\n            char cbuf[1024];\n            char* ss = cbuf;\n            (*(ss++)) = '{';\n            ss = eos::common::StringConversion::FastUnsignedToAsciiHex(buf.st_atim.tv_nsec,\n                 ss);\n            (*(ss++)) = ',';\n            ss = eos::common::StringConversion::FastUnsignedToAsciiHex(buf.st_atim.tv_sec,\n                 ss);\n            (*(ss++)) = ',';\n            ss = eos::common::StringConversion::FastUnsignedToAsciiHex(buf.st_blksize, ss);\n            (*(ss++)) = ',';\n            ss = eos::common::StringConversion::FastUnsignedToAsciiHex(buf.st_blocks, ss);\n            (*(ss++)) = ',';\n            ss = eos::common::StringConversion::FastUnsignedToAsciiHex(buf.st_ctim.tv_nsec,\n                 ss);\n            (*(ss++)) = ',';\n            ss = eos::common::StringConversion::FastUnsignedToAsciiHex(buf.st_ctim.tv_sec,\n                 ss);\n            (*(ss++)) = ',';\n            ss = eos::common::StringConversion::FastUnsignedToAsciiHex(buf.st_dev, ss);\n            (*(ss++)) = ',';\n            ss = eos::common::StringConversion::FastUnsignedToAsciiHex(buf.st_gid, ss);\n            (*(ss++)) = ',';\n            ss = eos::common::StringConversion::FastUnsignedToAsciiHex(buf.st_ino, ss);\n            (*(ss++)) = ',';\n            ss = eos::common::StringConversion::FastUnsignedToAsciiHex(buf.st_mode, ss);\n            (*(ss++)) = ',';\n            ss = eos::common::StringConversion::FastUnsignedToAsciiHex(buf.st_mtim.tv_nsec,\n                 ss);\n            (*(ss++)) = ',';\n            ss = eos::common::StringConversion::FastUnsignedToAsciiHex(buf.st_mtim.tv_sec,\n                 ss);\n            (*(ss++)) = ',';\n            ss = eos::common::StringConversion::FastUnsignedToAsciiHex(buf.st_nlink, ss);\n            (*(ss++)) = ',';\n            ss = eos::common::StringConversion::FastUnsignedToAsciiHex(buf.st_rdev, ss);\n            (*(ss++)) = ',';\n            ss = eos::common::StringConversion::FastUnsignedToAsciiHex(buf.st_size, ss);\n            (*(ss++)) = ',';\n            ss = eos::common::StringConversion::FastUnsignedToAsciiHex(buf.st_uid, ss);\n            (*ss++) = '}';\n            (*ss++) = ' ';\n            (*ss++) = 0;\n            mResultStream += cbuf;\n          }\n        }\n      } else {\n        if (isdot) {\n          mResultStream.insert(dotstart + 2, inodestr);\n          mResultStream.insert(dotstart + 2 + strlen(inodestr), \" \");\n          dotend = dotstart + 2 + strlen(inodestr) + 1;\n        } else if (isdotdot) {\n          mResultStream.insert(dotend + 3, inodestr);\n          mResultStream.insert(dotend + strlen(inodestr) + 3, \" \");\n        } else {\n          eos_debug(\"null inode and not . or ..\");\n        }\n      }\n    }\n\n    inodir->close();\n    delete inodir;\n    eos_debug(\"returning resultstream %s\", mResultStream.c_str());\n    mLen = mResultStream.length();\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/FuseX.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/FuseX.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <XrdOuc/XrdOucEnv.hh>\n#include \"common/SymKeys.hh\"\n#include \"mgm/zmq/ZMQ.hh\"\n#include \"mgm/FuseServer/Server.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/Prefetcher.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n\n#define FUSEXMAXCHILDREN 64\n\nint\nProcCommand::FuseX()\n{\n  ACCESSMODE_R;\n  FUNCTIONMAYSTALL(\"Eosxd::prot::LS\", *pVid, *mError);\n  {\n    FUNCTIONMAYSTALL(\"Eosxd::ext::LS\", *pVid, *mError);\n  }\n  {\n    FUNCTIONMAYSTALL(\"Eosxd::ext::LS-Entry\", *pVid, *mError);\n  }\n  gOFS->MgmStats.Add(\"Eosxd::prot::LS\", pVid->uid, pVid->gid, 1, pVid->app);\n  EXEC_TIMING_BEGIN(\"Eosxd::prot::LS\");\n  // -------------------------------------------------------------------------------------------------------\n  // This function returns meta data by inode or if provided first translates a path into an inode.\n  // The client can provide the meta-data clock. If it is equivalent to the stored clock, this function\n  // return EEXIST and no result stream.\n  // If a path cannot be translated the function returns ENOENT or a relevant errno for namespace failures.\n  // If mgm.op is equal to 'GETCAP' it does not return meta data but a capability.\n  // -------------------------------------------------------------------------------------------------------\n  XrdOucString sinode = pOpaque->Get(\"mgm.inode\") ? pOpaque->Get(\"mgm.inode\") :\n                        \"0\";\n  XrdOucString sclock = pOpaque->Get(\"mgm.clock\") ? pOpaque->Get(\"mgm.clock\") :\n                        \"0\";\n  XrdOucString spath  = pOpaque->Get(\"mgm.path\") ? pOpaque->Get(\"mgm.path\") : \"\";\n  XrdOucString schild = pOpaque->Get(\"mgm.child\") ? pOpaque->Get(\"mgm.child\") :\n                        \"\";\n  XrdOucString sop    = pOpaque->Get(\"mgm.op\") ? pOpaque->Get(\"mgm.op\") : \"GET\";\n  XrdOucString suuid  = pOpaque->Get(\"mgm.uuid\") ? pOpaque->Get(\"mgm.uuid\") : \"\";\n  XrdOucString cid    = pOpaque->Get(\"mgm.cid\") ? pOpaque->Get(\"mgm.cid\") : \"\";\n  XrdOucString authid = pOpaque->Get(\"mgm.authid\") ? pOpaque->Get(\"mgm.authid\") :\n                        \"\";\n  bool inlined = pOpaque->Get(\"mgm.inline\") ? true :\n                 false; // clients supports inlined responses in error messages\n\n  if (spath.length()) {\n    // decode escaped path name\n    spath = eos::common::StringConversion::curl_unescaped(spath.c_str()).c_str();\n  }\n\n  const char* inpath = spath.length() ? spath.c_str() : sinode.c_str();\n  uint64_t inode = strtoull(sinode.c_str(), 0, 16);\n  uint64_t clock = strtoull(sclock.c_str(), 0, 10);\n  uint64_t parentinode = 0;\n\n  if (EOS_LOGS_DEBUG) {\n    eos_static_debug(\"vid(%d,%d,%s)\", vid.uid, vid.gid, vid.host.c_str());\n  }\n\n  PROC_BOUNCE_NOT_ALLOWED;\n  eos::fusex::md md;\n  md.set_clientuuid(suuid.c_str());\n  md.set_clientid(cid.c_str());\n  md.set_authid(authid.c_str());\n  errno = 0;\n\n  if (spath.length()) {\n    // translate spath into an inode number\n    std::shared_ptr<eos::IFileMD> fmd;\n    std::shared_ptr<eos::IContainerMD> cmd;\n    eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, spath.c_str());\n    errno = 0;\n    eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n    std::string emsg = \"none\";\n\n    try {\n      fmd = gOFS->eosView->getFile(spath.c_str(), true);\n      inode = eos::common::FileId::FidToInode(fmd->getId());\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      emsg = e.getMessage().str().c_str();\n    }\n\n    if (!fmd) {\n      lock.Release();\n      eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, spath.c_str());\n      errno = 0 ;\n      lock.Grab(gOFS->eosViewRWMutex);\n\n      try {\n        cmd = gOFS->eosView->getContainer(spath.c_str(), true);\n        inode = cmd->getId();\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        emsg = e.getMessage().str().c_str();\n      }\n    }\n\n    if (errno) {\n      if (errno != ENOENT) {\n        eos_err(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\",\n                errno, emsg.c_str());\n      } else {\n        eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\",\n                  errno, emsg.c_str());\n      }\n\n      return gOFS->Emsg(\"FuseX\", *mError, errno, \"get-if-clock\",\n                        emsg.c_str());\n    }\n  }\n\n  if (schild.length()) {\n    // decode escaped child name\n    schild = eos::common::StringConversion::curl_unescaped(schild.c_str()).c_str();\n    std::shared_ptr<eos::IContainerMD> cmd;\n    eos::Prefetcher::prefetchContainerMDWithChildrenAndWait(gOFS->eosView, inode);\n    errno = 0;\n    eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n    std::string emsg = \"none\";\n\n    // lookup by parent dir + name\n    try {\n      cmd = gOFS->eosDirectoryService->getContainerMD(inode);\n      inode = 0;\n\n      if ((cmd->getNumContainers() + cmd->getNumFiles()) < FUSEXMAXCHILDREN) {\n        parentinode = inode;\n      }\n\n      std::shared_ptr<eos::IFileMD> fmd = cmd->findFile(schild.c_str());\n\n      if (fmd) {\n        inode = eos::common::FileId::FidToInode(fmd->getId());\n      } else {\n        std::shared_ptr<eos::IContainerMD> ccmd =\n          cmd->findContainer(schild.c_str());\n\n        if (ccmd) {\n          inode = ccmd->getId();\n        }\n      }\n\n      if (!inode) {\n        errno = ENOENT;\n        emsg = schild.c_str();\n        emsg += \" - no such file or directory\";\n      } else {\n        errno = 0;\n      }\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      emsg = e.getMessage().str().c_str();\n    }\n\n    if (errno) {\n      if (errno != ENOENT) {\n        eos_err(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\",\n                errno, emsg.c_str());\n      } else {\n        eos_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\",\n                  errno, emsg.c_str());\n      }\n\n      return gOFS->Emsg(\"FuseX\", *mError, errno, \"get-if-clock\",\n                        emsg.c_str());\n    }\n  }\n\n  uint64_t md_clock = 0;\n  md.set_md_ino(inode);\n\n  if (parentinode) {\n    // if we have a small response, we return the listing of the parent instead\n    // the 'name' MD only, that saves us future roundtrips\n    if (sop == \"GET\") {\n      md.set_operation(md.LS);\n      md.set_md_ino(parentinode);\n    }\n  } else {\n    if (sop == \"GET\") {\n      md.set_operation(md.GET);\n    }\n\n    if (sop == \"LS\") {\n      md.set_operation(md.LS);\n    }\n\n    if (sop == \"GETCAP\") {\n      md.set_operation(md.GETCAP);\n    }\n  }\n\n  if (clock) {\n    // if a clock is given, we only retrieve the MD clock without calling the FillXXX functions\n    if (!eos::common::FileId::IsFileInode(md.md_ino())) {\n      try {\n        uint64_t md_clock;\n        gOFS->eosDirectoryService->getContainerMD(md.md_ino(), &md_clock);\n      } catch (eos::MDException& e) {\n        try {\n          gOFS->eosFileService->getFileMD(eos::common::FileId::InodeToFid(inode),\n                                          &md_clock);\n        } catch (eos::MDException& e) {\n          return gOFS->Emsg(\"FuseX\", *mError, e.getErrno(),\n                            e.getMessage().str().c_str());\n        }\n      }\n    } else {\n    }\n\n    if (EOS_LOGS_DEBUG) {\n      eos_debug(\"c1=%llu c2=%llu\", md_clock, clock);\n    }\n\n    if ((sop == \"GET\") || (sop == \"LS\")) {\n      if (md_clock == clock) {\n        // if the given clock is ok, we return EEXIST\n        return gOFS->Emsg(\"FuseX\", *mError, EEXIST, \"get-if-clock\", inpath);\n      }\n    }\n  }\n\n  std::string result;\n  std::string id = std::string(\"Fusex::sync:\") + vid.tident.c_str();\n  mResultStream = \"\";\n  int rc = gOFS->zMQ->gFuseServer.HandleMD(id, md, *pVid, &result, &md_clock);\n\n  if (rc) {\n    return gOFS->Emsg(\"FuseX\", *mError, rc, \"handle request\", \"\");\n  }\n\n  if (EOS_LOGS_DEBUG) {\n    eos_debug(\"c1=%llu c2=%llu\", md_clock, clock);\n  }\n\n  if (sop == \"GETCAP\") {\n    // check clock synchronization\n    // the client is supposed to send his current time when requesting a CAP\n    time_t now = time(NULL);\n\n    if ((uint64_t)now > clock + 2) {\n      eos_err(\"client-clock %lu %s server-clock %lu\", clock, sclock.c_str(), now);\n      return gOFS->Emsg(\"FuseX\", *mError, EL2NSYNC, \"get-cap-clock-out-of-sync\",\n                        inpath);\n    }\n  }\n\n  if (inlined) {\n    if (result.size() < 2048) {\n      if (EOS_LOGS_DEBUG) {\n        eos_debug(\"returning in-line result - len=%u\", result.size());\n      }\n\n      std::string b64response;\n      eos::common::SymKey::Base64(result, b64response);\n      XrdOucBuffer* buff = new XrdOucBuffer(strndup(b64response.c_str(),\n                                            b64response.size()), b64response.size());\n      mError->setErrInfo(EIDRM, buff);\n      return SFS_ERROR;\n    }\n  }\n\n  mResultStream = result;\n\n  if (EOS_LOGS_DEBUG)\n    eos_debug(\"returning resultstream len=%u %s\", mResultStream.size(),\n              mResultStream.c_str());\n\n  mLen = mResultStream.size();\n\n  if (EOS_LOGS_DEBUG)\n    eos_debug(\"result-dump=%s\",\n              eos::common::StringConversion::string_to_hex(result.c_str()).c_str());\n\n  EXEC_TIMING_END(\"Eosxd::prot::LS\");\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Ls.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/Ls.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/ofs/XrdMgmOfsDirectory.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"common/Glob.hh\"\n#include \"common/Path.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/Timing.hh\"\n#include \"common/LayoutId.hh\"\n#include \"namespace/utils/Mode.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n\n\n\nint\nProcCommand::Ls()\n{\n  struct result {\n    std::string out;\n    std::string err;\n    int retc;\n\n    std::string getCacheName(uint64_t ino, uint64_t mtime_sec, uint64_t mtime_nsec,\n                             std::string options)\n    {\n      std::string cacheentry;\n      cacheentry = std::to_string(ino);\n      cacheentry += \":\";\n      cacheentry += std::to_string(mtime_sec);\n      cacheentry += \".\";\n      cacheentry += std::to_string(mtime_nsec);\n\n      if (options.length()) {\n        cacheentry += \":\";\n        cacheentry += options;\n      }\n\n      return cacheentry;\n    }\n  };\n  static eos::common::LRU::Cache<std::string, struct result, std::mutex> dirCache;\n  static bool use_cache = (getenv(\"EOS_MGM_LISTING_CACHE\") &&\n                           (dirCache.setMaxSize(atoi(getenv(\"EOS_MGM_LISTING_CACHE\")))));\n  std::ostringstream oss;\n  gOFS->MgmStats.Add(\"Ls\", pVid->uid, pVid->gid, 1);\n  XrdOucString spath = pOpaque->Get(\"mgm.path\");\n\n  if (pOpaque->Get(\"eos.encodepath\")) {\n    spath = eos::common::StringConversion::curl_unescaped(spath.c_str()).c_str();\n  }\n\n  if (spath.length() >= FILENAME_MAX) {\n    oss << \"error: path length longer than \" << FILENAME_MAX << \" bytes\";\n    eos_err(\"msg=\\\"%s\\\"\", oss.str().c_str());\n    stdErr = oss.str().c_str();\n    retc = E2BIG;\n    return SFS_OK;\n  }\n\n  eos::common::Path cPath(spath.c_str());\n\n  // Check for globbing, we support a maximum depth of 255\n  if (cPath.GetSubPathSize() > eos::common::Path::MAX_LEVELS) {\n    oss << \"error: path has more than \" << eos::common::Path::MAX_LEVELS\n        << \" levels\";\n    eos_err(\"msg=\\\"%s\\\"\", oss.str().c_str());\n    stdErr = oss.str().c_str();\n    retc = E2BIG;\n    return SFS_OK;\n  }\n\n  const char* inpath = cPath.GetPath();\n  NAMESPACEMAP;\n  PROC_BOUNCE_ILLEGAL_NAMES;\n  PROC_BOUNCE_NOT_ALLOWED;\n  PROC_TOKEN_SCOPE;\n  eos_info(\"mapped to %s\", path);\n  spath = path;\n  XrdOucString option = pOpaque->Get(\"mgm.option\");\n  bool showbackendstatus = false;\n  bool longlisting = false;\n  bool noglobbing = false;\n  XrdOucString filter = \"\";\n\n  if (!spath.length()) {\n    stdErr = \"error: you have to give a path name to call 'ls'\";\n    retc = EINVAL;\n  } else {\n    XrdMgmOfsDirectory dir;\n    struct stat buf;\n    int listrc = 0;\n    eos::common::Glob glob;\n    eos::common::Path cPath(spath.c_str());\n    // Allow users to pass `--no-globbing` with ls.\n    option.replace(\"no-globbing\", 'N');\n\n    // Deactivate globbing\n    if (option.find(\"N\") != STR_NPOS) {\n      noglobbing = true;\n    }\n\n    if (!noglobbing && cPath.Globbing()) {\n      // always use globbing\n      spath = cPath.GetParentPath();\n      filter = cPath.GetName();\n    }\n\n    XrdOucString ls_file;\n    std::string uri;\n    std::string cacheentry;\n    struct result cachedresult;\n\n    if (gOFS->_stat(spath.c_str(), &buf, *mError, *pVid, (const char*) 0, 0, true,\n                    &uri)) {\n      stdErr = mError->getErrText();\n      retc = errno;\n    } else {\n      // put the resolved uri path\n      spath = uri.c_str();\n      cacheentry = cachedresult.getCacheName(buf.st_ino, buf.st_mtim.tv_sec,\n                                             buf.st_mtim.tv_nsec, std::string(option.length() ? option.c_str() : \"\"));\n\n      if (use_cache && dirCache.tryGet(cacheentry, cachedresult)) {\n        if (!gOFS->_access(spath.c_str(), R_OK | X_OK, *mError, *pVid, 0)) {\n          // return from cache\n          retc = cachedresult.retc;\n          stdOut = cachedresult.out.c_str();\n          stdErr = cachedresult.err.c_str();\n          // reinsert LRU\n          dirCache.insert(cacheentry, cachedresult);\n          return SFS_OK;\n        }\n\n        // fall through to report permission errors\n      }\n\n      // if this is a directory open it and list\n      if (S_ISDIR(buf.st_mode) && ((option.find(\"d\")) == STR_NPOS)) {\n        listrc = dir.open(spath.c_str(), *pVid, (const char*) 0);\n      } else {\n        // if this is a file, open the parent and set the filter\n        if ((spath.length() > 1) && spath.endswith(\"/\")) {\n          spath.erase(spath.length() - 1);\n        }\n\n        int rpos = spath.rfind(\"/\");\n\n        if (rpos == STR_NPOS) {\n          listrc = SFS_ERROR;\n          retc = ENOENT;\n        } else {\n          // this is an 'ls <file>' command which has to return only one entry!\n          ls_file.assign(spath, rpos + 1);\n          spath.erase(rpos);\n          listrc = SFS_OK;\n        }\n      }\n\n      bool translateids = true;\n\n      if ((option.find(\"n\")) != STR_NPOS) {\n        translateids = false;\n      }\n\n      if ((option.find(\"s\")) != STR_NPOS) {\n        // just return '0' if this is a directory\n        return SFS_OK;\n      }\n\n      if ((option.find(\"y\")) != STR_NPOS) {\n        showbackendstatus = true;\n        option += \"l\";\n      }\n\n      if ((option.find(\"l\") != STR_NPOS)) {\n        longlisting = true;\n      }\n\n      if (!listrc) {\n        const char* val;\n\n        while ((ls_file.length() && (val = ls_file.c_str())) ||\n               (val = dir.nextEntry())) {\n          // this return's a single file or a (filtered) directory list\n          XrdOucString entryname = val;\n\n          if (((option.find(\"a\")) == STR_NPOS) && entryname.beginswith(\".\")) {\n            // quit if we list a hidden file without 'a' flag\n            if (ls_file.length()) {\n              break;\n            }\n\n            // skip over . .. and hidden files\n            continue;\n          }\n\n          if ((filter.length()) && !glob.Match(filter.c_str(), entryname.c_str())) {\n            // apply filter & skip single file/directories\n            if (ls_file.length()) {\n              break;\n            }\n\n            continue;\n          }\n\n          if ((((option.find(\"l\")) == STR_NPOS)) && ((option.find(\"F\")) == STR_NPOS)) {\n            stdOut += val;\n            stdOut += \"\\n\";\n          } else {\n            std::string backendstatus;\n            // return full information\n            XrdOucString statpath = spath;\n            statpath += \"/\";\n            statpath += val;\n\n            while (statpath.replace(\"//\", \"/\")) {\n            }\n\n            struct stat buf;\n\n            std::string cks;\n\n            if (gOFS->_stat(statpath.c_str(), &buf, *mError, *pVid, (const char*) 0, 0,\n                            false, 0, &cks)) {\n              if (errno != ENOENT) {\n                stdErr += \"error: unable to stat path \";\n                stdErr += statpath;\n                stdErr += \"\\n\";\n                retc = errno;\n              }\n            } else {\n              // TODO: convert virtual IDs back\n              XrdOucString suid = \"\";\n              suid += (int) buf.st_uid;\n              XrdOucString sgid = \"\";\n              sgid += (int) buf.st_gid;\n              XrdOucString sizestring = \"\";\n              struct tm* t_tm;\n              struct tm t_tm_local;\n              t_tm = localtime_r(&buf.st_mtime, &t_tm_local);\n              char modestr[11];\n              eos::modeToBuffer(buf.st_mode, modestr);\n\n              if (showbackendstatus) {\n                std::string rsymbol = eos::common::LayoutId::GetRedundancySymbol\n                                      (buf.st_mode & EOS_TAPE_MODE_T, buf.st_nlink, buf.st_size);\n                char sbsts[256];\n                snprintf(sbsts, sizeof(sbsts), \"%-9s\", rsymbol.c_str());\n                backendstatus = sbsts;\n              }\n\n              if (translateids) {\n                {\n                  // try to translate with password database\n                  int terrc = 0;\n                  std::string username = \"\";\n                  username = eos::common::Mapping::UidToUserName(buf.st_uid, terrc);\n\n                  if (!terrc) {\n                    char uidlimit[16];\n                    snprintf(uidlimit, 12, \"%s\", username.c_str());\n                    suid = uidlimit;\n                  }\n                }\n                {\n                  // try to translate with password database\n                  std::string groupname = \"\";\n                  int terrc = 0;\n                  groupname = eos::common::Mapping::GidToGroupName(buf.st_gid, terrc);\n\n                  if (!terrc) {\n                    char gidlimit[16];\n                    snprintf(gidlimit, 12, \"%s\", groupname.c_str());\n                    sgid = gidlimit;\n                  }\n                }\n              }\n\n              std::string t_creat = eos::common::Timing::ToLsFormat(t_tm);\n              constexpr size_t lsline_sz = 4096;\n              char lsline[lsline_sz];\n              XrdOucString dirmarker = \"\";\n\n              if ((option.find(\"F\")) != STR_NPOS) {\n                dirmarker = \"/\";\n              }\n\n              if (modestr[0] != 'd') {\n                dirmarker = \"\";\n              }\n\n              if ((option.find(\"i\")) != STR_NPOS) {\n                // add inode information\n                char sinode[16];\n                bool isfile = (modestr[0] != 'd');\n                snprintf(sinode, 16, \"%llu\",\n                         (unsigned long long)(isfile ? eos::common::FileId::InodeToFid(\n                                                buf.st_ino) : buf.st_ino));\n                snprintf(lsline, lsline_sz, \"%-16s\", sinode);\n                stdOut += lsline;\n              }\n\n              if ((option.find(\"c\")) != STR_NPOS) {\n                // add checksum information\n                constexpr size_t checksum_sz = 36;\n                char checksum[checksum_sz];\n                snprintf(checksum, checksum_sz, \"%-34s\", cks.c_str());\n                stdOut += checksum;\n              }\n\n              if ((option.find(\"h\")) == STR_NPOS) {\n                snprintf(lsline, lsline_sz, \"%s%s %3d %-8.8s %-8.8s %12s %s %s%s\",\n                         backendstatus.c_str(), modestr, (int) buf.st_nlink,\n                         suid.c_str(), sgid.c_str(),\n                         eos::common::StringConversion::GetSizeString(sizestring,\n                             (unsigned long long) buf.st_size),\n                         t_creat.c_str(), val, dirmarker.c_str());\n              } else {\n                snprintf(lsline, lsline_sz, \"%s%s %3d %-8.8s %-8.8s %12s %s %s%s\",\n                         backendstatus.c_str(), modestr, (int) buf.st_nlink,\n                         suid.c_str(), sgid.c_str(),\n                         eos::common::StringConversion::GetReadableSizeString(sizestring,\n                             (unsigned long long) buf.st_size, \"\"),\n                         t_creat.c_str(), val, dirmarker.c_str());\n              }\n\n              if ((option.find(\"l\")) != STR_NPOS) {\n                stdOut += lsline;\n\n                if (S_ISLNK(buf.st_mode)) {\n                  stdOut += \" -> \";\n                  XrdOucString link;\n\n                  if (!gOFS->_readlink(statpath.c_str(), *mError, *pVid, link)) {\n                    stdOut += link.c_str();\n                  } else {\n                    stdOut += \"( error )\\n\";\n                  }\n                }\n\n                stdOut += \"\\n\";\n              } else {\n                stdOut += val;\n                stdOut += dirmarker;\n                stdOut += \"\\n\";\n              }\n            }\n          }\n\n          if (stdOut.length() > 1 * 1024 * 1024 * 1024) {\n            stdOut += \"... (truncated after 1G of output)\\n\";\n            retc = E2BIG;\n            stdErr += \"warning: list too long - truncated after 1GB of output!\\n\";\n            break;\n          }\n\n          if (ls_file.length()) {\n            // this was a single file to be listed\n            break;\n          }\n        }\n\n        if (!ls_file.length()) {\n          dir.close();\n        }\n      } else {\n        stdErr += \"error: unable to open directory\";\n\n        if (retc == 0) {\n          retc = errno;\n        }\n      }\n    }\n\n    if (!retc && !showbackendstatus && !longlisting) {\n      // we cannot cache listing where people ask for dynamicinformation of children like folder size, Y option ...\n      cachedresult.retc = retc;\n      cachedresult.out = stdOut.c_str();\n      cachedresult.err = stdErr.c_str();\n\n      if (use_cache) {\n        dirCache.insert(cacheentry, cachedresult);\n      }\n    }\n  }\n\n  if (filter.length() && !stdOut.length() && !stdErr.length()) {\n    // Globbing characters were provided in user request, globbing was NOT disabled,\n    // stdout is empty and stdErr is empty --> no entry will be returned to the user,\n    // return ENOENT instead of silently return OK with no result\n    retc = ENOENT;\n    stdErr = \"error: No such file or directory\";\n    return SFS_ERROR;\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Map.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/Map.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/config/IConfigEngine.hh\"\n#include \"common/Constants.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Map()\n{\n  if (mSubCmd == \"ls\") {\n    eos::common::RWMutexReadLock lock(gOFS->PathMapMutex);\n    std::map<std::string, std::string>::const_iterator it;\n\n    for (it = gOFS->PathMap.begin(); it != gOFS->PathMap.end(); it++) {\n      char mapline[16384];\n      snprintf(mapline, sizeof(mapline) - 1, \"%-64s => %s\\n\", it->first.c_str(),\n               it->second.c_str());\n      stdOut += mapline;\n    }\n\n    return SFS_OK;\n  }\n\n  if (mSubCmd == \"link\") {\n    if ((!pVid->uid) || vid.hasUid(eos::common::ADM_UID) ||\n        vid.hasGid(eos::common::ADM_GID)) {\n      XrdOucString srcpath = pOpaque->Get(\"mgm.map.src\");\n      XrdOucString dstpath = pOpaque->Get(\"mgm.map.dest\");\n\n      if ((!srcpath.length()) || ((srcpath.find(\"..\") != STR_NPOS))\n          || ((srcpath.find(\"/../\") != STR_NPOS))\n          || ((srcpath.find(\" \") != STR_NPOS))\n          || ((srcpath.find(\"\\\\\") != STR_NPOS))\n          || ((srcpath.find(\"/./\") != STR_NPOS))\n          || ((!srcpath.beginswith(\"/\")))\n          || ((!srcpath.endswith(\"/\")))\n          || (!dstpath.length()) || ((dstpath.find(\"..\") != STR_NPOS))\n          || ((dstpath.find(\"/../\") != STR_NPOS))\n          || ((dstpath.find(\" \") != STR_NPOS))\n          || ((dstpath.find(\"\\\\\") != STR_NPOS))\n          || ((dstpath.find(\"/./\") != STR_NPOS))\n          || ((!dstpath.beginswith(\"/\")))\n          || ((!dstpath.endswith(\"/\")))) {\n        retc = EPERM;\n        stdErr = \"error: source and destination path has to start and end with '/', shouldn't contain spaces, '/./' or '/../' or backslash characters!\";\n      } else {\n        if (gOFS->PathMap.count(srcpath.c_str())) {\n          retc = EEXIST;\n          stdErr = \"error: there is already a mapping defined for '\";\n          stdErr += srcpath.c_str();\n          stdErr += \"' - remove the existing mapping using 'map unlink'!\";\n        } else {\n          gOFS->PathMap[srcpath.c_str()] = dstpath.c_str();\n          gOFS->mConfigEngine->SetConfigValue(\"map\", srcpath.c_str(), dstpath.c_str());\n          stdOut = \"success: added mapping '\";\n          stdOut += srcpath.c_str();\n          stdOut += \"'=>'\";\n          stdOut += dstpath.c_str();\n          stdOut += \"'\";\n        }\n      }\n    } else {\n      // permission denied\n      retc = EPERM;\n      stdErr = \"error: you don't have the required priviledges to execute 'map link'!\";\n    }\n\n    return SFS_OK;\n  }\n\n  if (mSubCmd == \"unlink\") {\n    XrdOucString path = pOpaque->Get(\"mgm.map.src\");\n\n    if ((!pVid->uid) ||\n        vid.hasUid(eos::common::ADM_UID) ||\n        vid.hasGid(eos::common::ADM_GID)) {\n      eos::common::RWMutexWriteLock lock(gOFS->PathMapMutex);\n\n      if ((!path.length()) || (!gOFS->PathMap.count(path.c_str()))) {\n        retc = EINVAL;\n        stdErr = \"error: path '\";\n        stdErr += path.c_str();\n        stdErr += \"' is not in the path map!\";\n      } else {\n        gOFS->PathMap.erase(path.c_str());\n        gOFS->mConfigEngine->DeleteConfigValue(\"map\", path.c_str());\n        stdOut = \"success: removed mapping of path '\";\n        stdOut += path.c_str();\n        stdOut += \"'\";\n      }\n    } else {\n      // permission denied\n      retc = EPERM;\n      stdErr = \"error: you don't have the required priviledges to execute 'map unlink'!\";\n    }\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Member.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/Map.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/egroup/Egroup.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Member()\n{\n  XrdOucString smember = pOpaque->Get(\"mgm.egroup\");\n  XrdOucString update = pOpaque->Get(\"mgm.egroupupdate\");\n\n  if (smember.length()) {\n    std::string egroup = smember.c_str();\n\n    if (update == \"true\") {\n      gOFS->EgroupRefresh->refresh(vid.uid_string, egroup);\n    }\n\n    std::string rs = gOFS->EgroupRefresh->DumpMember(vid.uid_string, egroup);\n    stdOut += rs.c_str();\n  } else {\n    std::string rs = gOFS->EgroupRefresh->DumpMembers();\n    stdOut += rs.c_str();\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Mkdir.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/Mkdir.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/macros/Macros.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Mkdir()\n{\n  XrdOucString spath = pOpaque->Get(\"mgm.path\");\n  XrdOucString option = pOpaque->Get(\"mgm.option\");\n  const char* inpath = spath.c_str();\n  NAMESPACEMAP;\n  PROC_BOUNCE_ILLEGAL_NAMES;\n  PROC_BOUNCE_NOT_ALLOWED;\n  spath = path;\n  PROC_TOKEN_SCOPE;\n\n  if (!spath.length()) {\n    stdErr = \"error: you have to give a path name to call 'mkdir'\";\n    retc = EINVAL;\n  } else {\n    XrdSfsMode mode = 0;\n\n    if (option == \"p\") {\n      mode |= SFS_O_MKPTH;\n    }\n\n    if (gOFS->_mkdir(spath.c_str(), mode, *mError, *pVid, (const char*) 0)) {\n      stdErr += \"error: unable to create directory\";\n      retc = errno;\n    }\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Motd.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/Motd.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"common/Constants.hh\"\n#include <fcntl.h>\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Motd()\n{\n  XrdOucString motdupload = pOpaque->Get(\"mgm.motd\") ? pOpaque->Get(\"mgm.motd\") :\n                            \"\";\n  gOFS->MgmStats.Add(\"Motd\", pVid->uid, pVid->gid, 1);\n  eos_info(\"motd\");\n  XrdOucString motdfile = gOFS->MgmMetaLogDir;\n  motdfile += \"/motd\";\n\n  if (motdupload.length() &&\n      ((!pVid->uid) || vid.hasUid(eos::common::ADM_UID) || vid.hasGid(eos::common::ADM_GID))) {\n    // root + admins can set the MOTD\n    ssize_t motdlen = 0;\n    char* motdout = 0;\n    eos_info(\"decoding motd\\n\");\n\n    if (eos::common::SymKey::Base64Decode(motdupload, motdout, motdlen)) {\n      if (motdlen) {\n        int fd = ::open(motdfile.c_str(), O_WRONLY);\n\n        if (fd >= 0) {\n          size_t nwrite = ::write(fd, motdout, motdlen);\n\n          if (!nwrite) {\n            stdErr += \"error: error writing motd file\\n\";\n          }\n\n          ::close(fd);\n        }\n\n        free(motdout);\n      }\n    } else {\n      stdErr += \"error: unabile to decode motd message\\n\";\n    }\n  }\n\n  int fd = ::open(motdfile.c_str(), O_RDONLY);\n\n  if (fd >= 0) {\n    size_t nread;\n    char buffer[65536];\n    nread = ::read(fd, buffer, sizeof(buffer));\n\n    if (nread > 0) {\n      buffer[65535] = 0;\n      stdOut += buffer;\n    }\n\n    ::close(fd);\n  }\n\n  return SFS_OK;\n}\n\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/NewfindCmd.cc",
    "content": "//------------------------------------------------------------------------------\n// @file: NewfindCmd.cc\n// @author: Fabio Luchetti, Georgios Bitzes, Jozsef Makai - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#include \"NewfindCmd.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/Path.hh\"\n#include \"common/Timing.hh\"\n#include \"common/Constants.hh\"\n#include \"common/RegexWrapper.hh\"\n#include \"mgm/acl/Acl.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/auth/AccessChecker.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/ns_quarkdb/ContainerMD.hh\"\n#include \"namespace/ns_quarkdb/FileMD.hh\"\n#include \"namespace/ns_quarkdb/NamespaceGroup.hh\"\n#include \"namespace/ns_quarkdb/explorer/NamespaceExplorer.hh\"\n#include \"namespace/utils/BalanceCalculator.hh\"\n#include \"namespace/utils/Checksum.hh\"\n#include \"namespace/utils/Stat.hh\"\n#include \"namespace/utils/Etag.hh\"\n#include <mgm/access/Access.hh>\n#include <namespace/Prefetcher.hh>\n\nEOSMGMNAMESPACE_BEGIN\n\n\ntemplate<typename T>\nstatic bool eliminateBasedOnFileMatch(const eos::console::FindProto& req,\n                                      const T& md)\n{\n  const std::string sregex = req.name();\n\n  if (sregex.empty()) {\n    return false;\n  }\n\n  const std::string to_filter = md->getName();\n  return (!eos::common::eos_regex_search(to_filter, sregex));\n}\n\n//------------------------------------------------------------------------------\n// Based on the Uid/Gid of given FileMd / ContainerMd, should it be included\n// in the search results?\n//------------------------------------------------------------------------------\ntemplate<typename T>\nstatic bool eliminateBasedOnUidGid(const eos::console::FindProto& req,\n                                   const T& md)\n{\n  if (req.searchuid() && md->getCUid() != req.uid()) {\n    return true;\n  }\n\n  if (req.searchnotuid() && md->getCUid() == req.notuid()) {\n    return true;\n  }\n\n  if (req.searchgid() && md->getCGid() != req.gid()) {\n    return true;\n  }\n\n  if (req.searchnotgid() && md->getCGid() == req.notgid()) {\n    return true;\n  }\n\n  return false;\n}\n\n//----------------------------------------------------------------------------------\n// Check whether to eliminate depending on modification time and options passed to NewfindCmd.\n// @note Assume ctime_t is always of type \"struct timespec\" for both containers and files\n//----------------------------------------------------------------------------------\ntemplate<typename T>\nstatic bool eliminateBasedOnTime(const eos::console::FindProto& req,\n                                 const T& md)\n{\n  struct timespec xtime;\n\n  if (req.ctime()) {\n    md->getCTime(xtime);\n  } else {\n    md->getMTime(xtime);\n  }\n\n  if (req.onehourold()) {\n    if (xtime.tv_sec > (time(nullptr) - 3600)) {\n      return true;\n    }\n  }\n\n  time_t selectoldertime = (time_t) req.olderthan();\n  time_t selectyoungertime = (time_t) req.youngerthan();\n\n  if (selectoldertime > 0) {\n    if (xtime.tv_sec > selectoldertime) {\n      return true;\n    }\n  }\n\n  if (selectyoungertime > 0) {\n    if (xtime.tv_sec < selectyoungertime) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Check whether to select depending on permissions.\n//------------------------------------------------------------------------------\ntemplate<typename T>\nstatic bool eliminateBasedOnPermissions(const eos::console::FindProto& req,\n                                        const T& md)\n{\n  if (!req.searchpermission() && !req.searchnotpermission()) {\n    return false;\n  }\n\n  mode_t st_mode = eos::modeFromMetadataEntry(md);\n  std::ostringstream flagOstr;\n  flagOstr << std::oct << st_mode;\n  std::string flagStr = flagOstr.str();\n  std::string permString = flagStr.substr(flagStr.length() - 3);\n\n  if (req.searchpermission() && permString != req.permission()) {\n    return true;\n  }\n\n  if (req.searchnotpermission() && permString == req.notpermission()) {\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Check whether to select depending on attributes.\n//------------------------------------------------------------------------------\ntemplate<typename T>\nstatic bool eliminateBasedOnAttr(const eos::console::FindProto& req,\n                                 const T& md)\n{\n  if (req.attributekey().empty() || req.attributevalue().empty()) {\n    return false;\n  }\n\n  std::string attr;\n\n  if (!gOFS->_attr_get(*md.get(), req.attributekey(), attr)) {\n    return true;\n  }\n\n  return (attr != req.attributevalue());\n}\n\n//------------------------------------------------------------------------------\n// Check whether to select depending on the file/container having faulty ACLs.\n//------------------------------------------------------------------------------\ntemplate<typename T>\nstatic bool eliminateBasedOnFaultyAcl(const eos::console::FindProto& req,\n                                      const T& md)\n{\n  // if not asking for faulty acls, just don't eliminate it\n  if (!req.faultyacl()) {\n    return false;\n  } else {\n    XrdOucErrInfo errInfo;\n    std::string sysacl;\n    std::string useracl;\n\n    // return jumps makes it convoluted, sight...\n    if (gOFS->_attr_get(*md.get(), \"sys.acl\", sysacl)) {\n      if (!Acl::IsValid(sysacl.c_str(), errInfo)) {\n        return false;\n      }\n    }\n\n    if (gOFS->_attr_get(*md.get(), \"user.acl\", useracl)) {\n      if (!Acl::IsValid(useracl.c_str(), errInfo)) {\n        return false;\n      }\n    }\n\n    return true;\n  }\n}\n\ntemplate<typename T>\nstatic bool FilterOut(const eos::console::FindProto& req,\n                      const T& md)\n{\n  return (\n           eliminateBasedOnFileMatch(req, md) ||\n           eliminateBasedOnUidGid(req, md) ||\n           eliminateBasedOnTime(req, md) ||\n           eliminateBasedOnPermissions(req, md) ||\n           eliminateBasedOnAttr(req, md) ||\n           eliminateBasedOnFaultyAcl(req, md)\n         );\n}\n\n// For files only. Check whether file replicas belong to different scheduling groups\nstatic bool hasSizeZero(std::shared_ptr<eos::IFileMD>& fmd)\n{\n  return (fmd->getSize() == 0);\n}\n\n// For files only. Check whether file replicas belong to different scheduling groups\nstatic bool hasMixedSchedGroups(std::shared_ptr<eos::IFileMD>& fmd)\n{\n  // find files which have replicas on mixed scheduling groups\n  std::string sGroupRef = \"\";\n  std::string sGroup = \"\";\n\n  for (auto lociter : fmd->getLocations()) {\n    // ignore filesystem id 0\n    if (!lociter) {\n      eos_static_err(\"fsid 0 found fxid=%08llx\", fmd->getId());\n      continue;\n    }\n\n    eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n    eos::common::FileSystem* filesystem = FsView::gFsView.mIdView.lookupByID(\n                                            lociter);\n\n    if (filesystem != nullptr) {\n      sGroup = filesystem->GetString(\"schedgroup\");\n    } else {\n      sGroup = \"none\";\n    }\n\n    if (!sGroupRef.empty()) {\n      if (sGroup != sGroupRef) {\n        return true;\n      }\n    } else {\n      sGroupRef = sGroup;\n    }\n  }\n\n  return false;\n}\n\n// For files only. Check whether a file has not the nominal number of stripes(replicas)\nstatic bool hasStripeDiff(std::shared_ptr<eos::IFileMD>& fmd)\n{\n  return (fmd->getNumLocation() == eos::common::LayoutId::GetStripeNumber(\n            fmd->getLayoutId()) + 1);\n}\n\n\n//------------------------------------------------------------------------------\n// Print path.\n//------------------------------------------------------------------------------\ntemplate<typename S>   // std::ofstream or std::stringstream\nstatic void printPath(S& ss, const eos::console::FindProto& req,\n                      const std::string& path)\n{\n  if (req.format().length() || req.treecount()) {\n    ss << \"path=\\\"\";\n  }\n\n  if (req.xurl()) {\n    ss << \"root://\" << gOFS->MgmOfsAlias << \"/\";\n  }\n\n  ss << path;\n\n  if (req.format().length() || req.treecount()) {\n    ss << \"\\\"\";\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print target.\n//------------------------------------------------------------------------------\ntemplate<typename S>   // std::ofstream or std::stringstream\nstatic void printTarget(S& ss, const eos::console::FindProto& req,\n                        const std::string& path)\n{\n  if (!path.empty()) {\n    ss << \" target=\\\"\" << path << \"\\\"\";\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print uid / gid of a FileMD or ContainerMD, if requested by req.\n//------------------------------------------------------------------------------\ntemplate<typename S, typename T>\nstatic void printUidGid(S& ss, const eos::console::FindProto& req, const T& md)\n{\n  if (req.format().length()) {\n    return;\n  }\n\n  if (req.printuid()) {\n    ss << \" uid=\" << md->getCUid();\n  }\n\n  if (req.printgid()) {\n    ss << \" gid=\" << md->getCGid();\n  }\n}\n\n\ntemplate<typename S, typename T>\nstatic void printAttributes(S& ss,\n                            const eos::console::FindProto& req, const T& md)\n{\n  if (req.format().length()) {\n    return;\n  }\n\n  if (!req.printkey().empty()) {\n    std::string attr;\n\n    if (!gOFS->_attr_get(*md.get(), req.printkey(), attr)) {\n      attr = \"undef\";\n    }\n\n    ss << \" \" << req.printkey() << \"=\" << attr;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print directories and files count of a ContainerMD, if requested by req.\n//------------------------------------------------------------------------------\nstatic void printChildCount(std::ofstream& ss,\n                            const eos::console::FindProto& req,\n                            const std::shared_ptr<eos::IContainerMD>& cmd, size_t ndirs, size_t nfiles)\n{\n  if (req.format().length()) {\n    return;\n  }\n\n  if (req.childcount()) {\n    ss << \" ndirs=\" << ndirs\n       << \" nfiles=\" << nfiles;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print du information\n//------------------------------------------------------------------------------\nstatic void printDu(std::ofstream& ss, const eos::console::FindProto& req,\n                    const std::shared_ptr<eos::IContainerMD>& cmd, size_t ndirs, size_t nfiles)\n{\n  if (req.du()) {\n    bool si = req.dusi();\n    bool readable = req.dureadable();\n    size_t treesize = cmd->getTreeSize();\n    std::string size = readable ?\n                       eos::common::StringConversion::GetReadableSizeString(treesize,\n                           si ? ((treesize > (10 * si)) ? \"iB\" : \"B\") : \"B\",\n                           si ? 1024 : 1000) : std::to_string(treesize);\n    ss << std::left << std::setw(16) << size << \" \";\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print user defined format\n//------------------------------------------------------------------------------\nstatic void printFormat(std::ofstream& ss, const eos::console::FindProto& req,\n                        const std::shared_ptr<eos::IContainerMD>& cmd, size_t ndirs, size_t nfiles)\n{\n  if (req.format().length()) {\n    std::vector<std::string> tokens;\n    eos::common::StringConversion::Tokenize(req.format(),\n                                            tokens, \",\");\n\n    for (auto i : tokens) {\n      if (i == \"type\") {\n        ss << \" type=directory \";\n      }\n\n      if (i == \"size\") {\n        ss << \" size=\" << std::to_string(cmd->getTreeSize());\n      }\n\n      if (i == \"cxid\") {\n        ss << \" cxid=\"  << std::hex << cmd->getId() << std::dec;\n      }\n\n      if (i == \"pxid\") {\n        ss << \" pxid=\"  << std::hex << cmd->getParentId() << std::dec;\n      }\n\n      if (i == \"cid\") {\n        ss << \" cid=\"  << cmd->getId();\n      }\n\n      if (i == \"pid\") {\n        ss << \" pid=\"  << cmd->getParentId();\n      }\n\n      if (i == \"uid\") {\n        ss << \" uid=\" << cmd->getCUid();\n      }\n\n      if (i == \"gid\") {\n        ss << \" gid=\" << cmd->getCGid();\n      }\n\n      if (i == \"mode\") {\n        ss << \" mode=\" << std::oct << cmd->getMode() << std::dec;\n      }\n\n      if (i == \"files\") {\n        ss << \" files=\" << ndirs;\n      }\n\n      if (i == \"directories\") {\n        ss << \" directories=\" << nfiles;\n      }\n\n      if (i == \"mtime\") {\n        eos::IFileMD::ctime_t mtime {0, 0};\n        cmd->getMTime(mtime);\n        ss << \" mtime=\" << eos::common::Timing::TimespecToString(mtime);\n      }\n\n      if (i == \"btime\") {\n        eos::IFileMD::ctime_t btime {0, 0};\n\n        if (cmd->getAttributes().count(\"sys.eos.btime\")) {\n          eos::common::Timing::Timespec_from_TimespecStr(\n            cmd->getAttributes()[\"sys.eos.btime\"], btime);\n        }\n\n        ss << \" btime=\" << eos::common::Timing::TimespecToString(btime);\n      }\n\n      if (i == \"ctime\") {\n        eos::IFileMD::ctime_t ctime {0, 0};\n        cmd->getCTime(ctime);\n        ss << \" ctime=\" << eos::common::Timing::TimespecToString(ctime);\n      }\n\n      if (i == \"etag\") {\n        std::string etag;\n        eos::calculateEtag(cmd.get(), etag);\n        ss << \" etag=\" << etag;\n      }\n\n      if (i.substr(0, 5) == \"attr.\") {\n        std::string attr = i.substr(5);\n\n        if (attr == \"*\") {\n          for (const auto& elem : cmd->getAttributes()) {\n            ss << \" attr.\" << elem.first << \"=\" << \"\\\"\" << elem.second << \"\\\"\";\n          }\n        } else {\n          if (cmd->getAttributes().count(attr)) {\n            ss << \" \" << i << \"=\\\"\" << cmd->getAttributes()[attr] << \"\\\"\";\n          }\n        }\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print du information\n//------------------------------------------------------------------------------\nstatic void printDu(std::ofstream& ss, const eos::console::FindProto& req,\n                    const std::shared_ptr<eos::IFileMD>& fmd)\n{\n  if (req.du()) {\n    bool si = req.dusi();\n    bool readable = req.dureadable();\n    size_t filesize = fmd->getSize();\n    std::string size = readable ?\n                       eos::common::StringConversion::GetReadableSizeString(filesize,\n                           si ? ((filesize > (10 * si)) ? \"iB\" : \"B\") : \"B\",\n                           si ? 1024 : 1000) : std::to_string(filesize);\n    ss << std::left << std::setw(16) << size << \" \";\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print user defined format\n//------------------------------------------------------------------------------\nstatic void printFormat(std::ofstream& ss, const eos::console::FindProto& req,\n                        const std::shared_ptr<eos::IFileMD>& fmd)\n{\n  if (req.format().length()) {\n    std::vector<std::string> tokens;\n    eos::common::StringConversion::Tokenize(req.format(),\n                                            tokens, \",\");\n\n    for (auto i : tokens) {\n      if (i == \"type\") {\n        if (fmd->isLink()) {\n          ss << \" type=symlink \";\n        } else {\n          ss << \" type=file \";\n        }\n      }\n\n      if (i == \"link\") {\n        printTarget(ss, req, fmd->getLink());\n      }\n\n      if (i == \"size\") {\n        ss << \" size=\" << std::to_string(fmd->getSize());\n      }\n\n      if (i == \"fxid\") {\n        ss << \" fxid=\"  << std::hex << fmd->getId() << std::dec;\n      }\n\n      if (i == \"cxid\") {\n        ss << \" cxid=\"  << std::hex << fmd->getContainerId() << std::dec;\n      }\n\n      if (i == \"fid\") {\n        ss << \" fid=\"  << fmd->getId();\n      }\n\n      if (i == \"cid\") {\n        ss << \" cid=\"  << fmd->getContainerId();\n      }\n\n      if (i == \"uid\") {\n        ss << \" uid=\" << fmd->getCUid();\n      }\n\n      if (i == \"gid\") {\n        ss << \" gid=\" << fmd->getCGid();\n      }\n\n      if (i == \"flags\") {\n        ss << \" flags=\" << std::oct << fmd->getFlags() << std::dec;\n      }\n\n      if (i == \"atime\") {\n        eos::IFileMD::ctime_t atime {0, 0};\n        fmd->getCTime(atime);\n        ss << \" atime=\" << eos::common::Timing::TimespecToString(atime);\n      }\n\n      if (i == \"mtime\") {\n        eos::IFileMD::ctime_t mtime {0, 0};\n        fmd->getMTime(mtime);\n        ss << \" mtime=\" << eos::common::Timing::TimespecToString(mtime);\n      }\n\n      if (i == \"btime\") {\n        eos::IFileMD::ctime_t btime {0, 0};\n\n        if (fmd->getAttributes().count(\"sys.eos.btime\")) {\n          eos::common::Timing::Timespec_from_TimespecStr(\n            fmd->getAttributes()[\"sys.eos.btime\"], btime);\n        }\n\n        ss << \" btime=\" << eos::common::Timing::TimespecToString(btime);\n      }\n\n      if (i == \"ctime\") {\n        eos::IFileMD::ctime_t ctime {0, 0};\n        fmd->getCTime(ctime);\n        ss << \" ctime=\" << eos::common::Timing::TimespecToString(ctime);\n      }\n\n      if (i == \"etag\") {\n        std::string etag;\n        eos::calculateEtag(fmd.get(), etag);\n        ss << \" etag=\" << etag;\n      }\n\n      if (i == \"checksum\") {\n        std::string xs;\n        eos::appendChecksumOnStringAsHex(fmd.get(), xs);\n        ss << \" checksum=\" << xs;\n      }\n\n      if (i == \"checksumtype\") {\n        ss << \" checksumtype=\" << eos::common::LayoutId::GetChecksumString(\n             fmd->getLayoutId());\n      }\n\n      if (i.substr(0, 5) == \"attr.\") {\n        std::string attr = i.substr(5);\n\n        if (attr == \"*\") {\n          for (const auto& elem : fmd->getAttributes()) {\n            ss << \" attr.\" << elem.first << \"=\" << \"\\\"\" << elem.second << \"\\\"\";\n          }\n        } else {\n          if (fmd->getAttributes().count(attr)) {\n            ss << \" \" << i << \"=\\\"\" << fmd->getAttributes()[attr] << \"\\\"\";\n          }\n        }\n      }\n    }\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Print hex checksum of given fmd, if requested by req.\n//------------------------------------------------------------------------------\ntemplate<typename S>   // std::ofstream or std::stringstream\nstatic void printChecksum(S& ss, const eos::console::FindProto& req,\n                          const std::shared_ptr<eos::IFileMD>& fmd)\n{\n  if (req.format().length()) {\n    return;\n  }\n\n  if (req.checksum()) {\n    ss << \" checksum=\";\n    std::string checksum;\n    eos::appendChecksumOnStringAsHex(fmd.get(), checksum);\n    ss << checksum;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print replica location of an fmd.\n//------------------------------------------------------------------------------\ntemplate<typename S>   // std::ofstream or std::stringstream\nstatic void printReplicas(S& ss, const std::shared_ptr<eos::IFileMD>& fmd,\n                          bool onlyhost, bool selectonline)\n{\n  if (onlyhost) {\n    ss << \" hosts=\";\n  } else {\n    ss << \" partition=\";\n  }\n\n  std::set<std::string> results;\n\n  for (auto lociter : fmd->getLocations()) {\n    // lookup filesystem\n    eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n    eos::common::FileSystem* filesystem = FsView::gFsView.mIdView.lookupByID(\n                                            lociter);\n\n    if (!filesystem) {\n      continue;\n    }\n\n    eos::common::FileSystem::fs_snapshot_t fs;\n\n    if (filesystem->SnapShotFileSystem(fs, true)) {\n      if (selectonline && filesystem->GetActiveStatus() !=\n          eos::common::ActiveStatus::kOnline) {\n        continue;\n      }\n\n      std::string item;\n\n      if (onlyhost) {\n        item = fs.mHost;\n      } else {\n        item = fs.mHost;\n        item += \":\";\n        item += fs.mPath;\n      }\n\n      results.insert(item);\n    }\n  }\n\n  for (auto it = results.begin(); it != results.end(); it++) {\n    if (it != results.begin()) {\n      ss << \",\";\n    }\n\n    ss << it->c_str();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print fs of a FileMD.\n//------------------------------------------------------------------------------\ntemplate<typename S>   // std::ofstream or std::stringstream\nstatic void printFs(S& ss, const std::shared_ptr<eos::IFileMD>& fmd)\n{\n  ss << \" fsid=\";\n  eos::IFileMD::LocationVector loc_vect = fmd->getLocations();\n  eos::IFileMD::LocationVector::const_iterator lociter;\n\n  for (lociter = loc_vect.begin(); lociter != loc_vect.end(); ++lociter) {\n    if (lociter != loc_vect.begin()) {\n      ss << ',';\n    }\n\n    ss << *lociter;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print a selected FileMD, according to formatting settings in req.\n//------------------------------------------------------------------------------\ntemplate<typename S>   // std::ofstream or std::stringstream\nstatic void printFMD(S& ss, const eos::console::FindProto& req,\n                     const std::shared_ptr<eos::IFileMD>& fmd)\n{\n  if (req.format().length()) {\n    return;\n  }\n\n  if (req.size()) {\n    ss << \" size=\" << fmd->getSize();\n  }\n\n  if (req.fid()) {\n    ss << \" fid=\" << fmd->getId();\n  }\n\n  if (req.fs()) {\n    printFs(ss, fmd);\n  }\n\n  if (req.partition()) {\n    printReplicas(ss, fmd, false, req.online());\n  }\n\n  if (req.hosts()) {\n    printReplicas(ss, fmd, true, req.online());\n  }\n\n  printChecksum(ss, req, fmd);\n\n  if (req.ctime()) {\n    eos::IFileMD::ctime_t ctime;\n    fmd->getCTime(ctime);\n    ss << \" ctime=\" << (unsigned long long) ctime.tv_sec;\n    ss << '.' << (unsigned long long) ctime.tv_nsec;\n  }\n\n  if (req.mtime()) {\n    eos::IFileMD::ctime_t mtime;\n    fmd->getMTime(mtime);\n    ss << \" mtime=\" << (unsigned long long) mtime.tv_sec;\n    ss << '.' << (unsigned long long) mtime.tv_nsec;\n  }\n\n  if (req.nrep()) {\n    ss << \" nrep=\" << fmd->getNumLocation();\n  }\n\n  if (req.nunlink()) {\n    ss << \" nunlink=\" << fmd->getNumUnlinkedLocation();\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Purge atomic files\n//------------------------------------------------------------------------------\ntemplate<typename S>   // std::ofstream or std::stringstream\nvoid\nNewfindCmd::ProcessAtomicFilePurge(S& ss,\n                                   const std::string& fspath,\n                                   eos::IFileMD& fmd)\n{\n  if (fspath.find(EOS_COMMON_PATH_ATOMIC_FILE_PREFIX) == std::string::npos) {\n    return;\n  }\n\n  ss << \"# found atomic \" << fspath << std::endl;\n\n  if (!(mVid.uid == 0 || mVid.uid == fmd.getCUid())) {\n    // Not allowed to remove file\n    ss << \"# skipping atomic \" << fspath << \" [no permission to remove]\" <<\n       std::endl;\n    return;\n  }\n\n  time_t now = time(nullptr);\n  eos::IFileMD::ctime_t atime;\n  fmd.getCTime(atime);\n\n  // Is the file older than 1 day?\n  if (now - atime.tv_sec <= 86400) {\n    ss << \"# skipping atomic \" << fspath << \" [< 1d old ]\" << std::endl;\n    return;\n  }\n\n  // Perform the rm\n  XrdOucErrInfo errInfo;\n\n  if (!gOFS->_rem(fspath.c_str(), errInfo, mVid, (const char*) nullptr,\n                  false, false, true)) {\n    ss << \"# purging atomic \" << fspath;\n  } else {\n    ss << \"# could not purge atomic \" << fspath;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Purge version directory.\n//------------------------------------------------------------------------------\ntemplate<typename S>   // std::ofstream or std::stringstream\nvoid\nNewfindCmd::PurgeVersions(S& ss, int64_t maxVersion,\n                          const std::string& dirpath)\n{\n  if (dirpath.find(EOS_COMMON_PATH_VERSION_PREFIX) == std::string::npos) {\n    return;\n  }\n\n  struct stat buf;\n\n  XrdOucErrInfo errInfo;\n\n  if ((!gOFS->_stat(dirpath.c_str(), &buf, errInfo, mVid, nullptr,\n                    nullptr)) && ((mVid.uid == 0) || (mVid.uid == buf.st_uid))) {\n    ss << \"# purging \" << dirpath;\n    gOFS->PurgeVersion(dirpath.c_str(), errInfo, maxVersion);\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Filter-out directories which we have no permission to access\n//------------------------------------------------------------------------------\nclass TraversalFilter : public ExpansionDecider\n{\npublic:\n  TraversalFilter(const eos::common::VirtualIdentity& v,\n                  bool skip_version_dirs) :\n    mSkipVersionDirs(skip_version_dirs), mVid(v) {}\n\n  virtual bool shouldExpandContainer(const eos::ns::ContainerMdProto& proto,\n                                     const eos::IContainerMD::XAttrMap& attrs,\n                                     const std::string& fullPath) override\n  {\n    // If skip version dirs enabled and container start with the version\n    // prefix then we skip expanding the current directory\n    if (mSkipVersionDirs &&\n        (proto.name().find(EOS_COMMON_PATH_VERSION_FILE_PREFIX) == 0)) {\n      return false;\n    }\n\n    eos::QuarkContainerMD cmd;\n    cmd.initializeWithoutChildren(eos::ns::ContainerMdProto(proto));\n    return AccessChecker::checkContainer(&cmd, attrs, R_OK | X_OK, mVid)\n           && AccessChecker::checkPublicAccess(fullPath, mVid);\n  }\nprivate:\n  bool mSkipVersionDirs;\n  const eos::common::VirtualIdentity& mVid;\n};\n\n\n//------------------------------------------------------------------------------\n// Find result struct\n//------------------------------------------------------------------------------\nstruct FindResult {\n  std::string path;\n  bool isdir;\n  bool iscache;\n  bool expansionFilteredOut;\n  // Filled out as long as populateLinkedAttributes set\n  eos::IContainerMD::XAttrMap attrs;\n  uint64_t numFiles = 0;\n  uint64_t numContainers = 0;\n  NamespaceItem item;\n\n  //----------------------------------------------------------------------------\n  // Convert FindResult of IContainerMD object\n  //----------------------------------------------------------------------------\n  std::shared_ptr<eos::IContainerMD> toContainerMD()\n  {\n    if (iscache) {\n      try {\n        auto cmd = gOFS->eosView->getContainer(path);\n        numFiles = cmd->getNumFiles();\n        numContainers = cmd->getNumContainers();\n        return cmd;\n      } catch (eos::MDException& e) {}\n\n      return {};\n    } else {\n      if (item.isFile) {\n        return {};\n      } else {\n        auto p = std::make_shared<eos::QuarkContainerMD>();\n        p->initializeWithoutChildren(eos::ns::ContainerMdProto(item.containerMd));\n\n        // copy xattributes\n        for (auto i : item.attrs) {\n          p->setAttribute(i.first, i.second);\n        }\n\n        return p;\n      }\n    }\n  }\n\n  //--------------------------------------------------------------------------\n  //! Convert FindResult of IFileMD object\n  //--------------------------------------------------------------------------\n  std::shared_ptr<eos::IFileMD> toFileMD()\n  {\n    if (iscache) {\n      try {\n        return gOFS->eosView->getFile(path);\n      } catch (eos::MDException& e) {}\n\n      return {};\n    } else {\n      if (item.isFile) {\n        auto p = std::make_shared<eos::QuarkFileMD>();\n        p->initialize(eos::ns::FileMdProto(item.fileMd));\n\n        // copy xattributes\n        for (auto i : item.attrs) {\n          p->setAttribute(i.first, i.second);\n        }\n\n        return p;\n      } else {\n        return {};\n      }\n    }\n  }\n};\n\n\n//------------------------------------------------------------------------------\n// Find result provider class\n//------------------------------------------------------------------------------\nclass FindResultProvider\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  // QDB: Initialize NamespaceExplorer\n  //----------------------------------------------------------------------------\n  FindResultProvider(qclient::QClient* qc, const std::string& target,\n                     const uint32_t depthlimit, const bool ignore_files,\n                     bool skip_version_dirs, const eos::common::VirtualIdentity& v)\n    : qcl(qc), path(target), depthlimit(depthlimit), ignore_files(ignore_files),\n      mSkipVersionDirs(skip_version_dirs), mVid(v)\n  {\n    restart();\n  }\n\n  //----------------------------------------------------------------------------\n  // Restart\n  //----------------------------------------------------------------------------\n  void restart()\n  {\n    if (!found) {\n      ExplorationOptions options;\n      options.populateLinkedAttributes = true;\n      options.view = gOFS->eosView;\n      options.depthLimit = depthlimit;\n      options.expansionDecider.reset(new TraversalFilter(mVid, mSkipVersionDirs));\n      options.ignoreFiles = ignore_files;\n      explorer.reset(new NamespaceExplorer(path, options, *qcl,\n                                           static_cast<QuarkNamespaceGroup*>(gOFS->namespaceGroup.get())->getExecutor()));\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  // In-memory: Check whether we need to take deep query mutex lock\n  //----------------------------------------------------------------------------\n  FindResultProvider()\n  {\n    localfound.reset(new std::map<std::string, std::set<std::string>>());\n    found = localfound.get();\n  }\n\n  ~FindResultProvider()\n  {\n    if (found) {\n      found->clear();\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  // In-memory: Get map for holding content results\n  //----------------------------------------------------------------------------\n  std::map<std::string, std::set<std::string>>* getFoundMap()\n  {\n    return found;\n  }\n\n  bool nextInMemory(FindResult& res)\n  {\n    res.expansionFilteredOut = false;\n    res.iscache = true;\n\n    // Search not started yet?\n    if (!inMemStarted) {\n      dirIterator = found->begin();\n      targetFileSet = &dirIterator->second;\n      fileIterator = targetFileSet->begin();\n      inMemStarted = true;\n      res.path = dirIterator->first;\n      res.isdir = true;\n      return true;\n    }\n\n    // At the end of a file iterator? Give out next directory.\n    if (fileIterator == targetFileSet->end()) {\n      dirIterator++;\n\n      if (dirIterator == found->end()) {\n        // Search has ended.\n        return false;\n      }\n\n      targetFileSet = &dirIterator->second;\n      fileIterator = targetFileSet->begin();\n      res.path = dirIterator->first;\n      res.isdir = true;\n      return true;\n    }\n\n    // Nope, just give out another file.\n    res.path = dirIterator->first + *fileIterator;\n    res.isdir = false;\n    fileIterator++;\n    return true;\n  }\n\n  bool nextInQDB(FindResult& res)\n  {\n    res.iscache = false;\n\n    // Just copy the result given by namespace explorer\n    if (!explorer->fetch(res.item)) {\n      return false;\n    }\n\n    res.path = res.item.fullPath;\n    res.isdir = !res.item.isFile;\n    res.expansionFilteredOut = res.item.expansionFilteredOut;\n    res.attrs = res.item.attrs;\n\n    if (res.item.isFile) {\n      res.numFiles = 0;\n      res.numContainers = 0;\n    } else {\n      res.numFiles = res.item.numFiles;\n      res.numContainers = res.item.numContainers;\n    }\n\n    return true;\n  }\n\n  bool next(FindResult& res)\n  {\n    if (found) {\n      // In-memory case\n      return nextInMemory(res);\n    }\n\n    // QDB case\n    return nextInQDB(res);\n  }\n\nprivate:\n  //----------------------------------------------------------------------------\n  // In-memory: Map holding results\n  //----------------------------------------------------------------------------\n  std::unique_ptr<std::map<std::string, std::set<std::string>>> localfound;\n  std::map<std::string, std::set<std::string>>* found = nullptr;\n  bool inMemStarted = false;\n\n  std::map<std::string, std::set<std::string>>::iterator dirIterator;\n  std::set<std::string>* targetFileSet = nullptr;\n  std::set<std::string>::iterator fileIterator;\n\n  //----------------------------------------------------------------------------\n  // QDB: NamespaceExplorer and QClient\n  //----------------------------------------------------------------------------\n  qclient::QClient* qcl = nullptr;\n  std::string path;\n  uint32_t depthlimit;\n  bool ignore_files;\n  bool mSkipVersionDirs;\n  std::unique_ptr<NamespaceExplorer> explorer;\n  eos::common::VirtualIdentity mVid;\n};\n\n//------------------------------------------------------------------------------\n// Method implementing the specific behaviour of the command executed\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nNewfindCmd::ProcessRequest() noexcept\n{\n  XrdOucString m_err {\"\"};\n  eos::console::ReplyProto reply;\n  const eos::console::FindProto& findRequest = mReqProto.find();\n\n  // Early return if routing should happen\n  if (ShouldRoute(findRequest.path(), reply)) {\n    return reply;\n  }\n\n  // Shortcut with bad input --name regex filters\n  if (!findRequest.name().empty()) {\n    if (!eos::common::eos_regex_valid(findRequest.name())) {\n      eos_static_err(\"msg=\\\"find filter not an accepted regex\\\" filter=\\\"%s\\\"\",\n                     findRequest.name().c_str());\n      reply.set_std_err(\"error: find filter not an accepted regex. \"\n                        \"Note that -name filters using 'egrep' style \"\n                        \"regex match!\\n\");\n      reply.set_retc(EINVAL);\n      return reply;\n    }\n  }\n\n  if (!OpenTemporaryOutputFiles()) {\n    reply.set_retc(EIO);\n    reply.set_std_err(SSTR(\"error: cannot write find result files on MGM\" <<\n                           std::endl));\n    return reply;\n  }\n\n  auto& purgeversion = findRequest.purge();\n  bool purge = false;\n  bool purge_atomic = (purgeversion == \"atomic\");\n  auto max_version = 999999ul;\n\n  if (!purge_atomic) {\n    try {\n      max_version = std::stoul(purgeversion);\n      purge = true;\n    } catch (std::logic_error& err) {\n      // this error is handled at client side, should not receive bad input from client\n    }\n  }\n\n  // This hash is used to calculate the balance of the found files over the\n  // filesystems involved\n  eos::BalanceCalculator balanceCalculator;\n  eos::common::Path cPath(findRequest.path().c_str());\n  XrdOucErrInfo errInfo;\n  // check what <path> actually is ...\n  XrdSfsFileExistence file_exists;\n  std::string real_path = findRequest.path();\n\n  if (gOFS->_exists(real_path.c_str(), file_exists, errInfo, mVid, nullptr)) {\n    mOfsErrStream << \"error: failed to run exists on '\"\n                  << findRequest.path() << \"'\" << std::endl;\n    reply.set_retc(errno);\n    return reply;\n  } else {\n    if (file_exists == XrdSfsFileExistNo) {\n      mOfsErrStream << \"error: no such file or directory\" << std::endl;\n      reply.set_retc(ENOENT);\n      return reply;\n    }\n\n    if (findRequest.path() != \"/\") {\n      try {\n        eos::common::RWMutexReadLock ns_rd_lock(gOFS->eosViewRWMutex);\n        real_path = gOFS->eosView->getRealPath(findRequest.path());\n        eos_static_info(\"msg=\\\"real path resolved\\\" rpath=\\\"%s\\\"\",\n                        real_path.c_str());\n      } catch (eos::MDException& e) {\n        mOfsErrStream << \"error: could not resolve real path\" << std::endl;\n        reply.set_retc(ENOENT);\n        return reply;\n      }\n    }\n  }\n\n  //set scope for this request\n  {\n    if (mVid.token && !mVid.token->TreeToken()) {\n      std::string token;\n      vid.token->Dump(token, false,true);\n      eos_static_err(\"msg=\\\"token has no tree permissions\\\" token=\\\"%s\\\"\", token.c_str());\n      mOfsErrStream << \"error: token has no tree permission\" << std::endl;\n      reply.set_retc(EPERM);\n      reply.set_std_out(\"\");\n      reply.set_std_err(SSTR(\"error: token has no tree permission\" <<\n\t\t\t     std::endl));\n\n      return reply;\n    }\n    std::string rp = real_path.c_str();\n    rp += \"/\";\n    const char* path = rp.c_str();\n    PROC_MVID_TOKEN_SCOPE \n  }\n  \n  errInfo.clear();\n  std::unique_ptr<qclient::QClient> qcl =\n    std::make_unique<qclient::QClient>(gOFS->mQdbContactDetails.members,\n                                       gOFS->mQdbContactDetails.constructOptions());\n  std::unique_ptr<FindResultProvider> findResultProvider;\n  int depthlimit = ((findRequest.Maxdepth__case() ==\n                     eos::console::FindProto::MAXDEPTH__NOT_SET) ?\n                    eos::common::Path::MAX_LEVELS :\n                    cPath.GetSubPathSize() + findRequest.maxdepth());\n  bool onlydirs = (findRequest.directories() &&\n                   !findRequest.files()) | findRequest.count() |\n                  findRequest.treecount() | findRequest.childcount();\n\n  if (findRequest.cache()) {\n    // read via our in-memory cache using _find\n    findResultProvider.reset(new FindResultProvider());\n    std::map<std::string, std::set<std::string>>* found =\n          findResultProvider->getFoundMap();\n\n    if (gOFS->_find(real_path.c_str(), errInfo, m_err, mVid, (*found),\n                    findRequest.attributekey().length() ?\n                    findRequest.attributekey().c_str() : nullptr,\n                    findRequest.attributevalue().length() ?\n                    findRequest.attributevalue().c_str() : nullptr,\n                    onlydirs, 0, true, findRequest.maxdepth(),\n                    findRequest.name().empty() ? nullptr : findRequest.name().c_str(),\n                    findRequest.skipversiondirs())) {\n      mOfsErrStream << \"error: unable to run find in directory\" << std::endl;\n      reply.set_retc(errno);\n      return reply;\n    } else {\n      if (m_err.length()) {\n        mOfsErrStream << m_err;\n        reply.set_retc(E2BIG);\n        return reply;\n      }\n    }\n  } else {\n    // read from the QDB backend\n    try {\n      findResultProvider.reset\n      (new FindResultProvider(qcl.get(), real_path, depthlimit, onlydirs,\n                              findRequest.skipversiondirs(), mVid));\n    } catch (eos::MDException& e) {\n      eos_static_info(\"msg=\\\"caught newfind exception\\\" orig_path=\\\"%s\\\" \"\n                      \"rpath=\\\"%s\\\" errno=%d what=\\\"%s\\\"\",\n                      findRequest.path().c_str(), real_path.c_str(),\n                      e.getErrno(), e.what());\n\n      if (e.getErrno() == ENOENT) {\n        mOfsErrStream << \"error: no such file or directory\" << std::endl;\n      } else {\n        mOfsErrStream << \"error: unable to start find\" << std::endl;\n      }\n\n      reply.set_retc(e.getErrno());\n      return reply;\n    }\n  }\n\n  uint64_t treecount_aggregate_dircounter = 0;\n  uint64_t treecount_aggregate_filecounter = 0;\n  uint64_t dircounter = 0;\n  uint64_t filecounter = 0;\n  // For general users, cannot return more than 50k dirs and 100k files with one find,\n  // unless there is an access rule allowing deeper queries.\n  // Special users (like root) have the limit lifted by default.\n  const bool limit_result = ((mVid.uid != 0) &&\n                             (!mVid.hasUid(eos::common::ADM_UID)) &&\n                             (!mVid.hasGid(eos::common::ADM_GID)) && (!mVid.sudoer));\n  static uint64_t dir_limit = 50000;\n  static uint64_t file_limit = 100000;\n  Access::GetFindLimits(mVid, dir_limit, file_limit);\n  // @note assume that findResultProvider will serve results DFS-ordered\n  FindResult findResult;\n  std::shared_ptr<eos::IContainerMD> cMD;\n  std::shared_ptr<eos::IFileMD> fMD;\n//  EXEC_TIMING_BEGIN(\"Newfind\");\n  std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();\n\n  while (findResultProvider->next(findResult)) {\n    if (limit_result) {\n      if (dircounter >= dir_limit || filecounter >= file_limit) {\n        mOfsErrStream << \"warning(\" << E2BIG\n                      << \"): find results are limited for you to \" << dir_limit\n                      << \" directories and \" << file_limit << \" files.\\n\"\n                      << \"Result is truncated! (found \" << dircounter\n                      << \" directories and \" << filecounter\n                      << \" files so far)\\n\";\n        reply.set_retc(E2BIG);\n        break;\n      }\n    }\n\n    if (findResult.isdir) {\n      if (!findRequest.directories() && findRequest.files() &&\n          !findRequest.count() && !findRequest.treecount()) {\n        continue;\n      }\n\n      if (findResult.expansionFilteredOut) {\n        // Returns a meaningful error message. Mirrors the checks in shouldExpandContainer\n        if (findRequest.skipversiondirs() &&\n            (findResult.path.find(EOS_COMMON_PATH_VERSION_PREFIX) != std::string::npos)) {\n          eos_static_debug(\"msg=\\\"entry filtered out\\\" path=\\\"%s\\\"\",\n                           findResult.path.c_str());\n          continue;\n        } else if (!AccessChecker::checkContainer(findResult.toContainerMD().get(),\n                   findResult.attrs, R_OK | X_OK, mVid)) {\n          mOfsErrStream << \"error(\" << EACCES << \"): no permissions to read directory \";\n          mOfsErrStream << findResult.path << std::endl;\n          reply.set_retc(EACCES);\n          continue;\n        } else if (!AccessChecker::checkPublicAccess(findResult.path,\n                   const_cast<common::VirtualIdentity&>(mVid))) {\n          mOfsErrStream << \"error(\" << EACCES <<\n                        \"): public access level restriction on directory \";\n          mOfsErrStream << findResult.path << std::endl;\n          reply.set_retc(EACCES);\n          continue;\n        } else {\n          // @note Empty branch, will be cut out from the compiler anyway.\n          // Either the findResult container can't be expanded further as it reaches maxdepth\n          // (this is not an error), either there is something fundamentally wrong. Should never happen.\n        }\n      }\n\n      // Selection\n      cMD = findResult.toContainerMD();\n\n      if (!cMD) {\n        continue;  // Process next item if we don't have a cMD\n      }\n\n      // --treecount nullify the filters, we don't want to bias the count\n      // because of intermediate filtered-out result, also update the total\n      // counter while traversing\n      if (!findRequest.treecount()) {\n        if (FilterOut(findRequest, cMD)) {\n          continue;\n        }\n      } else {\n        treecount_aggregate_dircounter += findResult.numContainers;\n        treecount_aggregate_filecounter += findResult.numFiles;\n      }\n\n      dircounter++;\n      filecounter += findResult.numFiles;\n\n      if (findRequest.count() || findRequest.treecount()) {\n        continue;\n      }\n\n      // Purge version directory?\n      if (purge) {\n        this->PurgeVersions(mOfsOutStream, max_version, findResult.path);\n        continue;\n      }\n\n      // Printing\n\n      // Are we printing fileinfo -m? Then, that's it\n      if (findRequest.fileinfo()) {\n        this->PrintFileInfoMinusM(findResult, errInfo);\n        continue;\n      }\n\n      printDu(mOfsOutStream, findRequest, cMD, findResult.numContainers,\n              findResult.numFiles);\n      printPath(mOfsOutStream, findRequest, findResult.path);\n      printChildCount(mOfsOutStream, findRequest, cMD, findResult.numContainers,\n                      findResult.numFiles);\n      printFormat(mOfsOutStream, findRequest, cMD, findResult.numContainers,\n                  findResult.numFiles);\n      printUidGid(mOfsOutStream, findRequest, cMD);\n      printAttributes(mOfsOutStream, findRequest, cMD);\n      mOfsOutStream << std::endl;\n    } else {\n      if (!findRequest.files() && findRequest.directories()) {\n        continue;\n      }\n\n      // Selection\n      fMD = findResult.toFileMD();\n\n      if (!fMD) {\n        continue;  // Process next item if we don't have a fMD\n      }\n\n      // Balance calculation? If yes,\n      // ignore selection criteria (TODO: Change this?)\n      // and simply account all fMD's.\n      if (findRequest.balance()) {\n        balanceCalculator.account(fMD);\n        continue;\n      }\n\n      if (FilterOut(findRequest, fMD)) {\n        continue;\n      }\n\n      if (findRequest.zerosizefiles() && !hasSizeZero(fMD)) {\n        continue;\n      }\n\n      if (findRequest.mixedgroups() && !hasMixedSchedGroups(fMD)) {\n        continue;\n      }\n\n      if (findRequest.stripediff() && hasStripeDiff(fMD)) {\n        continue;  // @note or the opposite?\n      }\n\n      filecounter++;\n\n      if (findRequest.count() || findRequest.treecount()) {\n        continue;\n      }\n\n      // Purge atomic files?\n      if (purge_atomic) {\n        this->ProcessAtomicFilePurge(mOfsOutStream, findResult.path, *fMD.get());\n        continue;\n      }\n\n      // Modify layout stripes?\n      if (findRequest.dolayoutstripes()) {\n        this->ModifyLayoutStripes(findRequest, findResult.path);\n        continue;\n      }\n\n      // Printing\n\n      // Are we printing fileinfo -m? Then, that's it\n      if (findRequest.fileinfo()) {\n        this->PrintFileInfoMinusM(findResult, errInfo);\n        continue;\n      }\n\n      printDu(mOfsOutStream, findRequest, fMD);\n\n      if (findRequest.format().length()) {\n        // format printing for symlinks\n        printPath(mOfsOutStream, findRequest, findResult.path);\n      } else {\n        // standard represenation for symlinks\n        printPath(mOfsOutStream, findRequest,\n                  fMD->isLink() ? findResult.path + \" -> \" + fMD->getLink() : findResult.path);\n      }\n\n      printFormat(mOfsOutStream, findRequest, fMD);\n      printUidGid(mOfsOutStream, findRequest, fMD);\n      printAttributes(mOfsOutStream, findRequest, fMD);\n      printFMD(mOfsOutStream, findRequest, fMD);\n      mOfsOutStream << std::endl;\n    }\n  }\n\n//  EXEC_TIMING_END(\"Newfind\");\n  std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();\n  gOFS->MgmStats.AddExec(\"Newfind\",\n                         std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count());\n  gOFS->MgmStats.Add(\"Newfind\", mVid.uid, mVid.gid, 1);\n  gOFS->MgmStats.Add(\"NewfindEntries\", mVid.uid, mVid.gid, filecounter);\n\n  if (findRequest.treecount()) {\n    printPath(mOfsOutStream, findRequest, findResult.path);\n    mOfsOutStream << \" sum.nfiles=\" << treecount_aggregate_filecounter\n                  << \" sum.ndirectories=\" << treecount_aggregate_dircounter <<\n                  std::endl;\n  }\n\n  if (findRequest.count()) {\n    mOfsOutStream << \"nfiles=\" << filecounter << \" ndirectories=\" << dircounter <<\n                  std::endl;\n  }\n\n  if (findRequest.balance()) {\n    balanceCalculator.printSummary(mOfsOutStream);\n  }\n\n  if (!CloseTemporaryOutputFiles()) {\n    reply.set_retc(EIO);\n    reply.set_std_err(\"error: cannot save find result files on MGM\\n\");\n    return reply;\n  }\n\n  return reply;\n}\n\n#ifdef EOS_GRPC\n//------------------------------------------------------------------------------\n// Method implementing the specific behaviour of the command executed for gRPC\n//------------------------------------------------------------------------------\nvoid\nNewfindCmd::ProcessRequest(grpc::ServerWriter<eos::console::ReplyProto>* writer)\n{\n  XrdOucString m_err {\"\"};\n  eos::console::ReplyProto StreamReply;\n  const eos::console::FindProto& findRequest = mReqProto.find();\n  auto& purgeversion = findRequest.purge();\n  bool purge = false;\n  bool purge_atomic = (purgeversion == \"atomic\");\n  auto max_version = 999999ul;\n\n  if (!purge_atomic) {\n    try {\n      max_version = std::stoul(purgeversion);\n      purge = true;\n    } catch (std::logic_error& err) {\n      // this error is handled at client side, should not receive bad input from client\n    }\n  }\n\n  // Shortcut with bad input --name regex filters\n  if (!findRequest.name().empty()) {\n    if (!eos::common::eos_regex_valid(findRequest.name())) {\n      eos_static_err(\"msg=\\\"find filter not an accepted regex\\\" filter=\\\"%s\\\"\",\n                     findRequest.name().c_str());\n      StreamReply.set_std_out(\"\");\n      StreamReply.set_std_err(\"error: find filter not an accepted regex. \"\n                              \"Note that -name filters using 'egrep' style \"\n                              \"regex match!\\n\");\n      StreamReply.set_retc(EINVAL);\n      writer->Write(StreamReply);\n      return;\n    }\n  }\n\n  // This hash is used to calculate the balance of the found files over the\n  // filesystems involved\n  eos::BalanceCalculator balanceCalculator;\n  eos::common::Path cPath(findRequest.path().c_str());\n  XrdOucErrInfo errInfo;\n  // check what <path> actually is ...\n  XrdSfsFileExistence file_exists;\n  std::string real_path = findRequest.path();\n\n  if ((gOFS->_exists(real_path.c_str(), file_exists, errInfo, mVid,\n                     nullptr))) {\n    StreamReply.set_std_out(\"\");\n    StreamReply.set_std_err(\n      \"error: failed to run exists on '\" + real_path + \"'\\n\");\n    StreamReply.set_retc(errno);\n    writer->Write(StreamReply);\n    return;\n  } else if (file_exists == XrdSfsFileExistNo) {\n    StreamReply.set_std_out(\"\");\n    StreamReply.set_std_err(\"error: no such file or directory\\n\");\n    StreamReply.set_retc(ENOENT);\n    writer->Write(StreamReply);\n    return;\n  }\n\n  errInfo.clear();\n  std::unique_ptr<qclient::QClient> qcl =\n    std::make_unique<qclient::QClient>(gOFS->mQdbContactDetails.members,\n                                       gOFS->mQdbContactDetails.constructOptions());\n  std::unique_ptr<FindResultProvider> findResultProvider;\n  int depthlimit = findRequest.Maxdepth__case() ==\n                   eos::console::FindProto::MAXDEPTH__NOT_SET ?\n                   eos::common::Path::MAX_LEVELS : cPath.GetSubPathSize() + findRequest.maxdepth();\n  bool onlydirs = (findRequest.directories() && !findRequest.files()) ||\n                  findRequest.treecount();\n\n  if (findRequest.cache()) {\n    // read via our in-memory cache using _find\n    // TODO: check, may need to reset findResultProvider\n    std::map<std::string, std::set<std::string>>* found =\n          findResultProvider->getFoundMap();\n\n    if (gOFS->_find(real_path.c_str(), errInfo, m_err, mVid, (*found),\n                    findRequest.attributekey().length() ? findRequest.attributekey().c_str() :\n                    nullptr,\n                    findRequest.attributevalue().length() ? findRequest.attributevalue().c_str() :\n                    nullptr,\n                    findRequest.directories(), 0, true, findRequest.maxdepth(),\n                    findRequest.name().empty() ? nullptr : findRequest.name().c_str())) {\n      mOfsErrStream << \"error: unable to run find in directory\" << std::endl;\n      StreamReply.set_retc(errno);\n      return;\n    } else {\n      if (m_err.length()) {\n        mOfsErrStream << m_err;\n        StreamReply.set_retc(E2BIG);\n        return;\n      }\n    }\n  } else {\n    // read from the back-end\n    try {\n      findResultProvider.reset\n      (new FindResultProvider(qcl.get(), real_path, depthlimit, onlydirs,\n                              findRequest.skipversiondirs(), mVid));\n    } catch (eos::MDException& e) {\n      eos_static_info(\"msg=\\\"caught newfind exception\\\" orig_path=\\\"%s\\\" \"\n                      \"rpath=\\\"%s\\\" errno=%d what=\\\"%s\\\"\",\n                      findRequest.path().c_str(), real_path.c_str(),\n                      e.getErrno(), e.what());\n      StreamReply.set_std_out(\"\");\n\n      if (e.getErrno() == ENOENT) {\n        StreamReply.set_std_err(\"error: no such file or directory\\n\");\n      } else {\n        StreamReply.set_std_err(\"error: unable to start find\\n\");\n      }\n\n      StreamReply.set_retc(e.getErrno());\n      writer->Write(StreamReply);\n      return;\n    }\n  }\n\n  uint64_t treecount_aggregate_dircounter = 0;\n  uint64_t treecount_aggregate_filecounter = 0;\n  uint64_t dircounter = 0;\n  uint64_t filecounter = 0;\n  // For general users, cannot return more than 50k dirs and 100k files with one find,\n  // unless there is an access rule allowing deeper queries.\n  // Special users (like root) have the limit lifted by default.\n  const bool limit_result = ((mVid.uid != 0) &&\n                             (!mVid.hasUid(eos::common::ADM_UID)) &&\n                             (!mVid.hasGid(eos::common::ADM_GID)) && (!mVid.sudoer));\n  static uint64_t dir_limit = 50000;\n  static uint64_t file_limit = 100000;\n  Access::GetFindLimits(mVid, dir_limit, file_limit);\n  // @note assume that findResultProvider will serve results DFS-ordered\n  FindResult findResult;\n  std::shared_ptr<eos::IContainerMD> cMD;\n  std::shared_ptr<eos::IFileMD> fMD;\n//  EXEC_TIMING_BEGIN(\"Newfind\");\n  std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();\n  std::string output_str(\"\");\n  int counter = 0;\n\n  while (findResultProvider->next(findResult)) {\n    if (limit_result) {\n      if (dircounter >= dir_limit || filecounter >= file_limit) {\n        output_str += \"warning(\" + std::to_string(E2BIG) +\n                      \"): find results are limited for you to \" + std::to_string(dir_limit) +\n                      \" directories and \" + std::to_string(file_limit) +\n                      \" files.\\nResult is truncated! (found \" +\n                      std::to_string(dircounter) + \" directories and \" + std::to_string(\n                        filecounter) + \" files so far)\\n\";\n        break;\n      }\n    }\n\n    std::ostringstream output;\n\n    if (findResult.isdir) {\n      if (!findRequest.directories() && findRequest.files() && !findRequest.count()) {\n        continue;\n      }\n\n      if (findResult.expansionFilteredOut) {\n        // Returns a meaningful error message. Mirrors the checks in shouldExpandContainer\n        if (!AccessChecker::checkContainer(findResult.toContainerMD().get(),\n                                           findResult.attrs, R_OK | X_OK, mVid)) {\n          output_str += \"error(\" + std::to_string(EACCES) +\n                        \"): no permissions to read directory \" + findResult.path + \"\\n\";\n          continue;\n        } else if (!AccessChecker::checkPublicAccess(findResult.path,\n                   const_cast<common::VirtualIdentity&>(mVid))) {\n          output_str += \"error(\" + std::to_string(EACCES) +\n                        \"): public access level restriction on directory \" + findResult.path + \"\\n\";\n          continue;\n        } else {\n          // @note Empty branch, will be cut out from the compiler anyway.\n          // Either the findResult container can't be expanded further as it reaches maxdepth\n          // (this is not an error), either there is something fundamentally wrong. Should never happen.\n        }\n      }\n\n      // Selection\n      cMD = findResult.toContainerMD();\n\n      if (!cMD) {\n        continue;  // Process next item if we don't have a cMD\n      }\n\n      // --treecount nullify the filters, we don't want to bias the count because of intermediate filtered-out result\n      // Alse, take the chance to update the total counter while traversing\n      if (!findRequest.treecount()) {\n        if (FilterOut(findRequest, cMD)) {\n          continue;\n        }\n      } else {\n        treecount_aggregate_dircounter += findResult.numContainers;\n        treecount_aggregate_filecounter += findResult.numFiles;\n      }\n\n      dircounter++;\n      filecounter += findResult.numFiles;\n\n      if (findRequest.count() || findRequest.treecount()) {\n        continue;\n      }\n\n      // Purge version directory?\n      if (purge) {\n        this->PurgeVersions(output, max_version, findResult.path);\n      }\n      // Printing\n      // Are we printing fileinfo -m? Then, that's it\n      else if (findRequest.fileinfo()) {\n        this->PrintFileInfoMinusM(output, findResult, errInfo);\n      } else {\n        printDu(mOfsOutStream, findRequest, cMD, findResult.numContainers,\n                findResult.numFiles);\n        printPath(output, findRequest, findResult.path);\n        printChildCount(mOfsOutStream, findRequest, cMD, findResult.numContainers,\n                        findResult.numFiles);\n        printFormat(mOfsOutStream, findRequest, cMD, findResult.numContainers,\n                    findResult.numFiles);\n        printUidGid(output, findRequest, cMD);\n        printAttributes(output, findRequest, cMD);\n\n        // Print times for directory\n        if (findRequest.ctime()) {\n          eos::IFileMD::ctime_t ctime;\n          cMD->getCTime(ctime);\n          output << \" ctime=\" << (unsigned long long) ctime.tv_sec;\n          output << '.' << (unsigned long long) ctime.tv_nsec;\n        }\n\n        if (findRequest.mtime()) {\n          eos::IFileMD::ctime_t mtime;\n          cMD->getMTime(mtime);\n          output << \" mtime=\" << (unsigned long long) mtime.tv_sec;\n          output << '.' << (unsigned long long) mtime.tv_nsec;\n        }\n      }\n    } else if (!findResult.isdir) { // redundant, no problem\n      if (!findRequest.files() && findRequest.directories()) {\n        continue;\n      }\n\n      // Selection\n      fMD = findResult.toFileMD();\n\n      if (!fMD) {\n        continue;  // Process next item if we don't have a fMD\n      }\n\n      // Balance calculation? If yes,\n      // ignore selection criteria (TODO: Change this?)\n      // and simply account all fMD's.\n      if (findRequest.balance()) {\n        balanceCalculator.account(fMD);\n        continue;\n      }\n\n      if (FilterOut(findRequest, fMD)) {\n        continue;\n      }\n\n      if (findRequest.zerosizefiles() && !hasSizeZero(fMD)) {\n        continue;\n      }\n\n      if (findRequest.mixedgroups() && !hasMixedSchedGroups(fMD)) {\n        continue;\n      }\n\n      if (findRequest.stripediff() && hasStripeDiff(fMD)) {\n        continue;  // @note or the opposite?\n      }\n\n      if (findRequest.count() || findRequest.treecount()) {\n        continue;\n      }\n\n      // Purge atomic files?\n      if (purge_atomic) {\n        this->ProcessAtomicFilePurge(output, findResult.path, *fMD.get());\n      }\n      // Modify layout stripes?\n      else if (findRequest.dolayoutstripes()) {\n        this->ModifyLayoutStripes(output, findRequest, findResult.path);\n      }\n      // Printing\n      // Are we printing fileinfo -m? Then, that's it\n      else if (findRequest.fileinfo()) {\n        this->PrintFileInfoMinusM(output, findResult, errInfo);\n      } else {\n        printDu(mOfsOutStream, findRequest, fMD);\n        printPath(output, findRequest,\n                  fMD->isLink() ? findResult.path + \" -> \" + fMD->getLink() : findResult.path);\n        printFormat(mOfsOutStream, findRequest, fMD);\n        printUidGid(output, findRequest, fMD);\n        printAttributes(output, findRequest, fMD);\n        printFMD(output, findRequest, fMD);\n      }\n    }\n\n    output_str += output.str();\n    counter++;\n\n    // Erase \"\\t\" if it is on the end of entry\n    if (!output_str.empty() && output_str.substr(output_str.size() - 1) == \"\\t\") {\n      output_str.erase(output_str.size() - 1);\n    }\n\n    // Add \"\\n\" if doesn't exist\n    if (!output_str.empty() && output_str.substr(output_str.size() - 1) != \"\\n\") {\n      output_str += \"\\n\";\n    }\n\n    // Write every 100 lines separately to gRPC\n    if (counter >= 100) {\n      StreamReply.set_std_out(output_str);\n      StreamReply.set_std_err(\"\");\n      StreamReply.set_retc(0);\n      writer->Write(StreamReply);\n      counter = 0;\n      output_str.clear();\n    }\n  }\n\n  // Write last part to gRPC, if exists\n  if (!output_str.empty()) {\n    StreamReply.set_std_out(output_str);\n    StreamReply.set_std_err(\"\");\n    StreamReply.set_retc(0);\n    writer->Write(StreamReply);\n  }\n\n//  EXEC_TIMING_END(\"Newfind\");\n  std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();\n  gOFS->MgmStats.AddExec(\"Newfind\",\n                         std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count());\n  gOFS->MgmStats.Add(\"Newfind\", mVid.uid, mVid.gid, 1);\n  gOFS->MgmStats.Add(\"NewfindEntries\", mVid.uid, mVid.gid, filecounter);\n\n  if (findRequest.treecount()) {\n    StreamReply.set_std_out(\n      \"path=\\\"\" + findRequest.path() +\n      \"\\\" sum.nfiles=\" + std::to_string(treecount_aggregate_filecounter) +\n      \" sum.ndirectories=\" + std::to_string(treecount_aggregate_dircounter) + \"\\n\");\n    StreamReply.set_std_err(\"\");\n    StreamReply.set_retc(0);\n    writer->Write(StreamReply);\n  }\n\n  if (findRequest.count()) {\n    StreamReply.set_std_out(\n      \"nfiles=\" + std::to_string(filecounter) + \" ndirectories=\" + std::to_string(\n        dircounter) + \"\\n\");\n    StreamReply.set_std_err(\"\");\n    StreamReply.set_retc(0);\n    writer->Write(StreamReply);\n  }\n\n  if (findRequest.balance()) {\n    std::stringstream output;\n    balanceCalculator.printSummary(output);\n    StreamReply.set_std_out(output.str() + \"\\n\");\n    StreamReply.set_std_err(\"\");\n    StreamReply.set_retc(0);\n    writer->Write(StreamReply);\n  }\n}\n#endif\n\n//------------------------------------------------------------------------------\n// Get fileinfo about path in monitoring format\n//------------------------------------------------------------------------------\nvoid\nNewfindCmd::PrintFileInfoMinusM(std::ostream& ss, const FindResult& find_obj,\n                                XrdOucErrInfo& errInfo)\n{\n  ProcCommand Cmd;\n  std::string output_stdout(\"\"), output_stderr(\"\");\n  std::string info = \"&mgm.cmd=fileinfo&mgm.file.info.option=-m\";\n\n  try {\n    if (find_obj.isdir) {\n      if (find_obj.item.containerMd.id()) {\n        info += \"&mgm.path=pid:\";\n        info += std::to_string(find_obj.item.containerMd.id());\n      } else {\n        info += \"&mgm.path=\";\n        info += find_obj.path;\n      }\n    } else {\n      if (find_obj.item.fileMd.id()) {\n        info += \"&mgm.path=fid:\";\n        info += std::to_string(find_obj.item.fileMd.id());\n      } else {\n        info += \"&mgm.path=\";\n        info += find_obj.path;\n      }\n    }\n  } catch (...) {\n    eos_static_err(\"msg=\\\"failed to convert metadata id\\\" path=\\\"%s\\\"\",\n                   find_obj.path.c_str());\n    return;\n  }\n\n  Cmd.open(\"/proc/user\", info.c_str(), mVid, &errInfo);\n  Cmd.AddOutput(output_stdout, output_stderr);\n  Cmd.close();\n\n  if (Cmd.GetRetc() == 0) {\n    ss << output_stdout;\n  } else {\n    ss << output_stderr;\n  }\n\n  ss << std::endl;\n}\n\n//------------------------------------------------------------------------------\n// Modify layout stripes for gRPC\n//------------------------------------------------------------------------------\nvoid\nNewfindCmd::ModifyLayoutStripes(std::ostream& ss,\n                                const eos::console::FindProto& req,\n                                const std::string& fspath)\n{\n  XrdOucErrInfo errInfo;\n  ProcCommand fileCmd;\n  std::string info = \"mgm.cmd=file&mgm.subcmd=layout&mgm.path=\";\n  info += fspath;\n  info += \"&mgm.file.layout.stripes=\";\n  info += std::to_string(req.layoutstripes());\n\n  if (fileCmd.open(\"/proc/user\", info.c_str(), mVid, &errInfo) == 0) {\n    std::ostringstream outputStream;\n    XrdSfsFileOffset offset = 0;\n    constexpr uint32_t size = 512;\n    auto bytesRead = 0ul;\n    char buffer[size];\n\n    do {\n      bytesRead = fileCmd.read(offset, buffer, size);\n\n      for (auto i = 0u; i < bytesRead; i++) {\n        outputStream << buffer[i];\n      }\n\n      offset += bytesRead;\n    } while (bytesRead == size);\n\n    fileCmd.close();\n    XrdOucEnv env(outputStream.str().c_str());\n\n    if (std::stoi(env.Get(\"mgm.proc.retc\")) == 0) {\n      if (!req.silent()) {\n        ss << env.Get(\"mgm.proc.stdout\");\n      }\n    } else {\n      ss << env.Get(\"mgm.proc.stderr\");\n    }\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/NewfindCmd.hh",
    "content": "//------------------------------------------------------------------------------\n// File: NewfindCmd.hh\n// Author: Jozsef Makai - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/proc/IProcCommand.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"proto/ConsoleRequest.pb.h\"\n\n#ifdef EOS_GRPC\n#include \"proto/EosWnc.grpc.pb.h\"\n#endif\n\nnamespace eos\n{\nclass IFileMD;\n}\n\nEOSMGMNAMESPACE_BEGIN\n\nclass FindResult;\n\nclass NewfindCmd : public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit NewfindCmd(eos::console::RequestProto&& req,\n                      eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, true)\n  {}\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~NewfindCmd() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\n#ifdef EOS_GRPC\n  void ProcessRequest(grpc::ServerWriter<eos::console::ReplyProto>* writer);\n#endif\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Print fileinfo data in monitoring format to the given output stream\n  //!\n  //! @param ss output stream\n  //! @param find_obj file/container obj\n  //! @param errInfo error info object\n  //----------------------------------------------------------------------------\n  void PrintFileInfoMinusM(std::ostream& ss, const FindResult& find_obj,\n                           XrdOucErrInfo& errInfo);\n\n  //----------------------------------------------------------------------------\n  //! Print fileinfo data in monitoring format to default output stream\n  //! @note uses the above implementation\n  //----------------------------------------------------------------------------\n  void PrintFileInfoMinusM(const FindResult& find_obj, XrdOucErrInfo& errInfo)\n  {\n    PrintFileInfoMinusM(mOfsOutStream, find_obj, errInfo);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Trigger a file layout command to modify the number of stripes\n  //!\n  //! @param ss output stream\n  //! @param req find request object\n  //! @param fspath file identifier\n  //----------------------------------------------------------------------------\n  void ModifyLayoutStripes(std::ostream& ss,\n                           const eos::console::FindProto& req,\n                           const std::string& fspath);\n\n  //----------------------------------------------------------------------------\n  //! Trigger a file layout command to modify the number of stripes\n  //! @note uses the above implementation\n  //----------------------------------------------------------------------------\n  void ModifyLayoutStripes(const eos::console::FindProto& req,\n                           const std::string& fspath)\n  {\n    ModifyLayoutStripes(mOfsOutStream, req, fspath);\n  }\n\n  template<typename S>   // std::ofstream or std::stringstream\n  void ProcessAtomicFilePurge(S& ss, const std::string& fspath,\n                              eos::IFileMD& fmd);\n\n  template<typename S>   // std::ofstream or std::stringstream\n  void PurgeVersions(S& ss, int64_t maxVersion, const std::string& dirpath);\n\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Quota.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/Quota.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/acl/Acl.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"common/Constants.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::UserQuota()\n{\n  using eos::common::Mapping;\n  using eos::common::StringConversion;\n  int errc = 0;\n  long id = 0;\n  Quota::IdT id_type = Quota::IdT::kUid;\n  Quota::Type quota_type = Quota::Type::kUnknown;\n  std::string space = \"\";\n\n  if (pOpaque->Get(\"mgm.quota.space\")) {\n    space = pOpaque->Get(\"mgm.quota.space\");\n  }\n\n  gOFS->MgmStats.Add(\"Quota\", pVid->uid, pVid->gid, 1);\n\n  if (!space.empty()) {\n    // evt. correct the space variable to be a directory path (+/)\n    struct stat buf;\n    std::string sspace = space;\n\n    if (sspace[sspace.length() - 1] != '/') {\n      sspace += '/';\n    }\n\n    if (!gOFS->_stat(sspace.c_str(), &buf, *mError, *pVid, 0)) {\n      space = sspace;\n    }\n  }\n\n  if (mSubCmd == \"lsuser\") {\n    XrdOucString monitoring = pOpaque->Get(\"mgm.quota.format\");\n    bool monitor = false;\n\n    if (monitoring == \"m\") {\n      monitor = true;\n    }\n\n    eos_notice(\"quota ls (user)\");\n    XrdOucString out = \"\";\n    bool is_ok = Quota::PrintOut(space, out, pVid->uid, -1, monitor, true);\n\n    if (is_ok && out != \"\") {\n      if (!monitor) {\n        stdOut += \"\\nBy user:\";\n        stdOut += out;\n      } else {\n        stdOut += out;\n      }\n    } else {\n      if (!is_ok) {\n        stdErr += out;\n        retc = EINVAL;\n      }\n    }\n\n    out = \"\";\n    is_ok = Quota::PrintOut(space, out, -1, pVid->gid, monitor, true);\n    mDoSort = false;\n\n    if (is_ok && out != \"\") {\n      if (!monitor) {\n        stdOut += \"\\nBy group:\";\n        stdOut += out;\n      } else {\n        stdOut += out;\n      }\n    } else {\n      if (!is_ok) {\n        stdErr += out;\n        retc = EINVAL;\n      }\n    }\n\n    return SFS_OK;\n  }\n\n  bool canQuota = false;\n\n  if ((!vid.uid) || vid.hasUid(eos::common::ADM_UID) || vid.hasGid(eos::common::ADM_GID)) {\n    // root and admin can set quota\n    canQuota = true;\n  } else {\n    // figure out if the authenticated user is a quota admin\n    eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n    eos::IContainerMD::XAttrMap attrmap;\n\n    if (space[0] != '/') {\n      // take the proc directory\n      space = gOFS->MgmProcPath.c_str();\n    } else {\n      // effectively check ACLs on the quota node directory if it can be retrieved\n      std::string quota_node_path = Quota::GetResponsibleSpaceQuotaPath(space);\n\n      if (quota_node_path.length()) {\n        space = quota_node_path;\n      }\n    }\n\n    // ACL and permission check\n    Acl acl(space.c_str(), *mError, *pVid, attrmap);\n    canQuota = acl.CanSetQuota();\n  }\n\n  if (canQuota) {\n    std::string uid_sel = (pOpaque->Get(\"mgm.quota.uid\") ?\n                           pOpaque->Get(\"mgm.quota.uid\") : \"\");\n    std::string gid_sel = (pOpaque->Get(\"mgm.quota.gid\") ?\n                           pOpaque->Get(\"mgm.quota.gid\") : \"\");\n\n    if (mSubCmd == \"ls\") {\n      eos_notice(\"quota ls\");\n      XrdOucString monitoring = pOpaque->Get(\"mgm.quota.format\");\n      XrdOucString printid = pOpaque->Get(\"mgm.quota.printid\");\n      long long int uid = (uid_sel.empty() ? -1LL :\n                           Mapping::UserNameToUid(uid_sel, errc));\n      long long int gid = (gid_sel.empty() ? -1LL :\n                           Mapping::GroupNameToGid(gid_sel, errc));\n      bool monitor = false;\n      bool translate = true;\n\n      if (monitoring == \"m\") {\n        monitor = true;\n      }\n\n      if (printid == \"n\") {\n        translate = false;\n      }\n\n      XrdOucString out1 = \"\";\n\n      if ((uid != -1LL) && (gid != -1LL)) {\n        // Print both uid and gid info\n        if (!Quota::PrintOut(space, out1, uid, -1LL, monitor, translate)) {\n          stdOut = \"\";\n          stdErr = out1.c_str();\n          retc = EINVAL;\n        } else {\n          XrdOucString out2 = \"\";\n\n          if (!Quota::PrintOut(space, out2, -1LL, gid, monitor, translate)) {\n            stdOut = \"\";\n            stdErr = out2.c_str();\n            retc = EINVAL;\n          } else {\n            out1 += out2;\n            stdOut = out1.c_str();\n          }\n        }\n      } else {\n        // Either uid or gid is printed\n        if (Quota::PrintOut(space, out1, uid, gid, monitor, translate)) {\n          stdOut = out1;\n        } else {\n          stdOut = \"\";\n          stdErr = out1.c_str();\n          retc = EINVAL;\n        }\n      }\n    }\n\n    if (mSubCmd == \"set\") {\n      eos_notice(\"quota set\");\n      std::string msg {\"\"};\n      XrdOucString svolume = pOpaque->Get(\"mgm.quota.maxbytes\");\n      XrdOucString sinodes = pOpaque->Get(\"mgm.quota.maxinodes\");\n\n      if (space.empty()) {\n\tstdErr = \"error: command not properly formatted\";\n\tretc = EINVAL;\n\treturn SFS_OK;\n      }\n\n      if (uid_sel.length() && gid_sel.length()) {\n\tstdErr = \"error: you need specify either a uid or a gid\";\n\tretc = EINVAL;\n\treturn SFS_OK;\n      }\n\n      if (uid_sel.length()) {\n\tid_type = Quota::IdT::kUid;\n\tid = Mapping::UserNameToUid(uid_sel.c_str(), errc);\n\n\tif (errc == EINVAL) {\n\t  stdErr = \"error: unable to translate uid=\";\n\t  stdErr += uid_sel.c_str();\n\t  retc = EINVAL;\n\t  return SFS_OK;\n\t}\n      } else if (gid_sel.length()) {\n\tid_type = Quota::IdT::kGid;\n\tid = Mapping::GroupNameToGid(gid_sel.c_str(), errc);\n\t\n\tif (errc == EINVAL) {\n\t  stdErr = \"error: unable to translate gid=\";\n\t  stdErr += gid_sel.c_str();\n\t  retc = EINVAL;\n\t  return SFS_OK;\n\t}\n      } else {\n\tstdErr = \"error: no uid/gid specified for quota set\";\n\tretc = EINVAL;\n\treturn SFS_OK;\n      }\n\n      // Deal with volume quota\n      unsigned long long size = StringConversion::GetDataSizeFromString(svolume);\n\n      if (svolume.length() && ((errno == EINVAL) || (errno == ERANGE))) {\n\tstdErr = \"error: the volume quota you specified is not a valid number\";\n\tretc = EINVAL;\n\treturn SFS_OK;\n      } else if (svolume.length()) {\n\t// Set volume quota\n\tif (Quota::SetQuotaTypeForId(space, id, id_type, Quota::Type::kVolume,\n\t\t\t\t     size, msg, retc)) {\n\t  stdOut = msg.c_str();\n\t} else {\n\t  stdErr = msg.c_str();\n\t  return SFS_OK;\n\t}\n      }\n\n      // Deal with inode quota\n      unsigned long long inodes = StringConversion::GetSizeFromString(sinodes);\n\n      if (sinodes.length() && (errno == EINVAL)) {\n\tstdErr = \"error: the inode quota you specified are not a valid number\";\n\tretc = EINVAL;\n\treturn SFS_OK;\n      } else if (sinodes.length()) {\n\t// Set inode quota\n\tif (Quota::SetQuotaTypeForId(space, id, id_type, Quota::Type::kInode,\n\t\t\t\t     inodes, msg, retc)) {\n\t  stdOut += msg.c_str();\n\t} else {\n\t  stdErr += msg.c_str();\n\t  return SFS_OK;\n\t}\n      }\n\n      if ((!svolume.length()) && (!sinodes.length())) {\n\tstdErr = \"error: max. bytes or max. inodes values have to be defined\";\n\tretc = EINVAL;\n\treturn SFS_OK;\n      }\n    } else {\n      retc = EPERM;\n      stdErr = \"error: you cannot set quota from storage node with 'sss' \"\n\t\"authentication!\";\n    }\n\n    if (mSubCmd == \"rm\") {\n      eos_notice(\"quota rm\");\n\n      if ((pVid->prot != \"sss\") || pVid->isLocalhost()) {\n        int errc;\n\n        if (space.empty()) {\n          stdErr = \"error: command not properly formatted\";\n          retc = EINVAL;\n          return SFS_OK;\n        }\n\n        if (uid_sel.length() && gid_sel.length()) {\n          stdErr = \"error: you need specify either a uid or a gid\";\n          retc = EINVAL;\n          return SFS_OK;\n        }\n\n        if (uid_sel.length()) {\n          id_type = Quota::IdT::kUid;\n          id = Mapping::UserNameToUid(uid_sel.c_str(), errc);\n\n          if (errc == EINVAL) {\n            stdErr = \"error: unable to translate uid=\";\n            stdErr += uid_sel.c_str();\n            retc = EINVAL;\n            return SFS_OK;\n          }\n        } else if (gid_sel.length()) {\n          id_type = Quota::IdT::kGid;\n          id = Mapping::GroupNameToGid(gid_sel.c_str(), errc);\n\n          if (errc == EINVAL) {\n            stdErr = \"error: unable to translate gid=\";\n            stdErr += gid_sel.c_str();\n            retc = EINVAL;\n            return SFS_OK;\n          }\n        } else {\n          stdErr = \"error: no uid/gid specified for quota remove\";\n          retc = EINVAL;\n          return SFS_OK;\n        }\n\n        std::string msg{\"\"};\n        XrdOucString qtype  = pOpaque->Get(\"mgm.quota.type\");\n\n        // Get type of quota\n        if (qtype.length() == 0) {\n          quota_type = Quota::Type::kAll;\n        } else if (qtype == \"inode\") {\n          quota_type = Quota::Type::kInode;\n        } else if (qtype == \"volume\") {\n          quota_type = Quota::Type::kVolume;\n        }\n\n        if (quota_type == Quota::Type::kUnknown) {\n          retc = EINVAL;\n          stdErr = \"error: unknown quota type \";\n          stdErr += qtype.c_str();\n        } else if (quota_type == Quota::Type::kAll) {\n          if (Quota::RmQuotaForId(space, id, id_type, msg, retc)) {\n            stdOut = msg.c_str();\n          } else {\n            stdErr = msg.c_str();\n          }\n        } else {\n          if (Quota::RmQuotaTypeForId(space, id, id_type, quota_type, msg, retc)) {\n            stdOut = msg.c_str();\n          } else {\n            stdErr = msg.c_str();\n          }\n        }\n      } else {\n        retc = EPERM;\n        stdErr = \"error: you cannot remove quota from a storage node using \"\n                 \"'sss' authentication!\";\n      }\n    }\n  } else {\n    retc = EPERM;\n    stdErr = \"error: you are not a quota administrator!\";\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/RecycleCmd.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file RecycleCmd.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"RecycleCmd.hh\"\n#include \"mgm/recycle/Recycle.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Process request\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nRecycleCmd::ProcessRequest() noexcept\n{\n  using eos::mgm::Recycle;\n  using eos::console::RecycleProto;\n  std::string err_msg;\n  eos::console::ReplyProto reply;\n  RecycleProto recycle = mReqProto.recycle();\n  RecycleProto::SubcmdCase subcmd = recycle.subcmd_case();\n  std::string std_out, std_err;\n  int rc = 0;\n\n  if (subcmd == RecycleProto::kLs) {\n    eos_static_info(\"%s\", \"msg=\\\"handling recycle ls command\\\"\");\n    const eos::console::RecycleProto_LsProto& ls = recycle.ls();\n    std::string rtype = \"uid\";\n\n    if (ls.type() == eos::console::RecycleProto::ALL) {\n      rtype = \"all\";\n    } else if (ls.type() == eos::console::RecycleProto::RID) {\n      rtype = \"rid\";\n    }\n\n    rc = Recycle::Print(std_out, std_err, mVid, ls.monitorfmt(),\n                        !ls.numericids(), ls.fulldetails(), rtype,\n                        ls.recycleid(), ls.date(), nullptr, true,\n                        ls.maxentries());\n\n    if (std_out.length()) {\n      reply.set_std_out(std_out.c_str());\n    }\n\n    if (std_err.length()) {\n      reply.set_std_err(std_err.c_str());\n    }\n\n    reply.set_retc(rc);\n  } else if (subcmd == RecycleProto::kPurge) {\n    const eos::console::RecycleProto_PurgeProto& purge = recycle.purge();\n    std::string rtype = \"uid\";\n\n    if (purge.type() == eos::console::RecycleProto::ALL) {\n      rtype = \"all\";\n    } else if (purge.type() == eos::console::RecycleProto::RID) {\n      rtype = \"rid\";\n    }\n\n    reply.set_retc(Recycle::Purge(std_out, std_err, mVid, purge.key(),\n                                  purge.date(), rtype, purge.recycleid()));\n\n    if (reply.retc()) {\n      reply.set_std_err(std_err.c_str());\n    } else {\n      reply.set_std_out(std_out.c_str());\n    }\n  } else if (subcmd == RecycleProto::kRestore) {\n    const eos::console::RecycleProto_RestoreProto& restore = recycle.restore();\n    reply.set_retc(Recycle::Restore(std_out, std_err, mVid, restore.key(),\n                                    restore.forceorigname(),\n                                    restore.restoreversions(),\n                                    restore.makepath()));\n\n    if (reply.retc()) {\n      reply.set_std_err(std_err.c_str());\n    } else {\n      reply.set_std_out(std_out.c_str());\n    }\n  } else if (subcmd == RecycleProto::kConfig) {\n    using eos::mgm::Quota;\n    int retc = 0;\n    const eos::console::RecycleProto_ConfigProto& config = recycle.config();\n\n    if (mVid.uid != 0) {\n      reply.set_std_err(\"error: you need to be root to configure the recycle bin\"\n                        \" and/or recycle policies\");\n      reply.set_retc(EPERM);\n      return reply;\n    }\n\n    if (config.op() == eos::console::RecycleProto_ConfigProto::ADD_BIN) {\n      retc = Recycle::Config(std_out, std_err, mVid, config.op(), config.subtree());\n    } else if (config.op() == eos::console::RecycleProto_ConfigProto::RM_BIN) {\n      retc = Recycle::Config(std_out, std_err, mVid, config.op(), config.subtree());\n    } else if (config.op() == eos::console::RecycleProto_ConfigProto::LIFETIME) {\n      retc = Recycle::Config(std_out, std_err, mVid, config.op(),\n                             std::to_string(config.lifetimesec()));\n    } else if (config.op() == eos::console::RecycleProto_ConfigProto::RATIO) {\n      retc = Recycle::Config(std_out, std_err, mVid, config.op(),\n                             std::to_string(config.ratio()));\n    } else if (config.op() == eos::console::RecycleProto_ConfigProto::SIZE) {\n      std::string msg;\n\n      if (!Quota::SetQuotaTypeForId(Recycle::gRecyclingPrefix, Quota::gProjectId,\n                                    Quota::IdT::kGid, Quota::Type::kVolume, config.size(),\n                                    msg, retc)) {\n        reply.set_std_err(msg);\n        reply.set_retc(retc);\n        return reply;\n      }\n    } else if (config.op() == eos::console::RecycleProto_ConfigProto::INODES) {\n      std::string msg;\n\n      if (!Quota::SetQuotaTypeForId(Recycle::gRecyclingPrefix, Quota::gProjectId,\n                                    Quota::IdT::kGid, Quota::Type::kInode, config.size(),\n                                    msg, retc)) {\n        reply.set_std_err(msg);\n        reply.set_retc(retc);\n        return reply;\n      }\n    } else if (config.op() ==\n               eos::console::RecycleProto_ConfigProto::COLLECT_INTERVAL) {\n      retc = Recycle::Config(std_out, std_err, mVid, config.op(),\n                             std::to_string(config.size()));\n    } else if (config.op() ==\n               eos::console::RecycleProto_ConfigProto::REMOVE_INTERVAL) {\n      retc = Recycle::Config(std_out, std_err, mVid, config.op(),\n                             std::to_string(config.size()));\n    } else if (config.op() == eos::console::RecycleProto_ConfigProto::DRY_RUN) {\n      retc = Recycle::Config(std_out, std_err, mVid, config.op(), config.value());\n    } else if (config.op() == eos::console::RecycleProto_ConfigProto::ENFORCE) {\n      retc = Recycle::Config(std_out, std_err, mVid, config.op(), config.value());\n    } else if (config.op() == eos::console::RecycleProto_ConfigProto::ENABLE) {\n      retc = Recycle::Config(std_out, std_err, mVid, config.op(), config.value());\n    } else if (config.op() == eos::console::RecycleProto_ConfigProto::DUMP) {\n      retc = 0;\n      std_out = gOFS->mRecycler->Dump();\n    }\n\n    reply.set_retc(retc);\n\n    if (retc == 0) {\n      reply.set_std_out(std_out.c_str());\n    } else {\n      reply.set_std_err(std_err.c_str());\n    }\n  } else if (subcmd == RecycleProto::kProject) {\n    if (mVid.uid != 0) {\n      std_err = \"error: you need to be root to setup recycle ids\\n\";\n      reply.set_std_err(std_err.c_str());\n      reply.set_retc(EPERM);\n    }\n\n    const eos::console::RecycleProto_ProjectProto& project = recycle.project();\n    int retc = Recycle::RecycleIdSetup(project.path(), project.acl(), std_err);\n    reply.set_retc(retc);\n\n    if (retc) {\n      reply.set_std_err(std_err);\n    }\n  } else {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: not supported\");\n  }\n\n  return reply;\n}\n\n//------------------------------------------------------------------------------\n// Process request\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nRecycleCmd::ProcessRequest(std::vector<std::map<std::string, std::string>>*\n                           rvec) noexcept\n{\n  using eos::mgm::Recycle;\n  using eos::console::RecycleProto;\n  std::string err_msg;\n  eos::console::ReplyProto reply;\n  RecycleProto recycle = mReqProto.recycle();\n  RecycleProto::SubcmdCase subcmd = recycle.subcmd_case();\n  std::string std_out, std_err;\n  int rc = 0;\n\n  if (subcmd == RecycleProto::kLs) {\n    eos_static_info(\"%s\", \"msg=\\\"handling recycle ls command\\\"\");\n    const eos::console::RecycleProto_LsProto& ls = recycle.ls();\n    std::string rtype = \"uid\";\n\n    if (ls.type() == eos::console::RecycleProto::ALL) {\n      rtype = \"all\";\n    } else if (ls.type() == eos::console::RecycleProto::RID) {\n      rtype = \"rid\";\n    }\n\n    rc = Recycle::Print(std_out, std_err, mVid, ls.monitorfmt(),\n                        !ls.numericids(), ls.fulldetails(), rtype,\n                        ls.recycleid(), ls.date(), rvec, true,\n                        ls.maxentries());\n\n    if (std_out.length()) {\n      reply.set_std_out(std_out.c_str());\n    }\n\n    if (std_err.length()) {\n      reply.set_std_err(std_err.c_str());\n    }\n\n    reply.set_retc(rc);\n  } else {\n    return ProcessRequest();\n  }\n\n  return reply;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/RecycleCmd.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file RecycleCmd.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"proto/Recycle.pb.h\"\n#include \"mgm/proc/ProcCommand.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class RecycleCmd - class handling recycle commands\n//------------------------------------------------------------------------------\nclass RecycleCmd: public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit RecycleCmd(eos::console::RequestProto&& req,\n                      eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, true)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~RecycleCmd() = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //!\n  //! @param rvec vector to hold the listing results\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto\n  ProcessRequest(std::vector<std::map<std::string, std::string>>* rvec)\n  noexcept;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Rm.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/Rm.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/ofs/XrdMgmOfsDirectory.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/recycle/Recycle.hh\"\n#include \"mgm/recycle/RecycleEntry.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"common/Path.hh\"\n#include <regex.h>\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Rm()\n{\n  XrdOucString spath = \"\";\n  XrdOucString spathid = pOpaque->Get(\"mgm.file.id\");\n  XrdOucString scontainerid = pOpaque->Get(\"mgm.container.id\");\n\n  if (spathid.length()) {\n    std::string lpath;\n    std::string err_msg;\n\n    if (GetPathFromFid(lpath, std::strtoull(spathid.c_str(), nullptr, 10),\n                       err_msg)) {\n      stdErr = err_msg.c_str();\n    }\n\n    spath = lpath.c_str();\n  } else {\n    if (scontainerid.length()) {\n      std::string lpath;\n      std::string err_msg;\n\n      if (GetPathFromCid(lpath, std::strtoull(scontainerid.c_str(), nullptr, 10),\n                         err_msg)) {\n        stdErr = err_msg.c_str();\n      }\n\n      spath.c_str();\n    } else {\n      spath = pOpaque->Get(\"mgm.path\");\n    }\n  }\n\n  const char* inpath = spath.c_str();\n  XrdOucString option = pOpaque->Get(\"mgm.option\");\n  XrdOucString deep = pOpaque->Get(\"mgm.deletion\");\n  eos::common::Path cPath(inpath);\n  bool force = (option.find(\"f\") != STR_NPOS);\n  XrdOucString filter = \"\";\n  std::set<std::string> rmList;\n  NAMESPACEMAP;\n  PROC_BOUNCE_ILLEGAL_NAMES;\n  PROC_BOUNCE_NOT_ALLOWED;\n  spath = path;\n  PROC_TOKEN_SCOPE;\n\n  if (force && (vid.uid)) {\n    stdErr = \"warning: removing the force flag - this is only allowed for the 'root' role!\\n\";\n    force = false;\n  }\n\n  if (!spath.length()) {\n    stdErr = \"error: you have to give a path name to call 'rm'\";\n    retc = EINVAL;\n  } else {\n    if (spath.find(\"*\") != STR_NPOS) {\n      // this is wildcard deletion\n      eos::common::Path cPath(spath.c_str());\n      spath = cPath.GetParentPath();\n      filter = cPath.GetName();\n    }\n\n    // check if this file exists\n    XrdSfsFileExistence file_exists;\n\n    if (gOFS->_exists(spath.c_str(), file_exists, *mError, *pVid, nullptr)) {\n      stdErr += \"error: unable to run exists on path '\";\n      stdErr += spath.c_str();\n      stdErr += \"'\";\n      retc = errno;\n      return SFS_OK;\n    }\n\n    if (file_exists == XrdSfsFileExistNo) {\n      stdErr += \"error: no such file or directory with path '\";\n      stdErr += spath.c_str();\n      stdErr += \"'\";\n      retc = ENOENT;\n      return SFS_OK;\n    }\n\n    if (file_exists == XrdSfsFileExistIsFile) {\n      // if we have rm -r <file> we remove the -r flag\n      option.replace(\"r\", \"\");\n    }\n\n    if ((file_exists == XrdSfsFileExistIsDirectory) && filter.length()) {\n      regex_t regex_filter;\n      // Adding regex anchors for beginning and end of string\n      XrdOucString filter_temp = \"^\";\n      // Changing wildcard * into regex syntax\n      filter.replace(\"*\", \".*\");\n      filter_temp += filter;\n      filter_temp += \"$\";\n      int reg_rc = regcomp(&(regex_filter), filter_temp.c_str(),\n                           REG_EXTENDED | REG_NEWLINE);\n\n      if (reg_rc) {\n        stdErr += \"error: failed to compile filter regex \";\n        stdErr += filter_temp.c_str();\n        retc = EINVAL;\n        return SFS_OK;\n      }\n\n      XrdMgmOfsDirectory dir;\n      // list the path and match against filter\n      int listrc = dir.open(spath.c_str(), *pVid, nullptr);\n\n      if (!listrc) {\n        const char* val;\n\n        while ((val = dir.nextEntry())) {\n          XrdOucString mpath = spath;\n          XrdOucString entry = val;\n          mpath += val;\n\n          if ((entry == \".\") ||\n              (entry == \"..\")) {\n            continue;\n          }\n\n          if (!regexec(&regex_filter, entry.c_str(), 0, nullptr, 0)) {\n            rmList.insert(mpath.c_str());\n          }\n        }\n      }\n\n      regfree(&regex_filter);\n      // if we have rm * (whatever wildcard) we remove the -r flag\n      option.replace(\"r\", \"\");\n    } else {\n      rmList.insert(spath.c_str());\n    }\n\n    // find everything to be deleted\n    if (option.find(\"r\") != STR_NPOS) {\n      std::map<std::string, std::set<std::string> > found;\n      std::map<std::string, std::set<std::string> >::const_reverse_iterator rfoundit;\n      std::set<std::string>::const_iterator fileit;\n\n      if (((cPath.GetSubPathSize() < 4) && (deep != \"deep\")) ||\n          (gOFS->_find(spath.c_str(), *mError, stdErr, *pVid, found))) {\n        if ((cPath.GetSubPathSize() < 4) && (deep != \"deep\")) {\n          stdErr += \"error: deep recursive deletes are forbidden without shell confirmation code!\";\n          retc = EPERM;\n        } else {\n          stdErr += \"error: unable to remove file/directory\";\n          retc = errno;\n        }\n      } else {\n        std::string recycle_dir;\n        std::string recycle_id;\n\n        if (!force) {\n          // Only recycle if there is no '-f' flag\n          std::string lpath = spath.c_str();\n          size_t pos = lpath.find(\"/.sys.v#.\");\n\n          if (pos != std::string::npos) {\n            lpath.erase(pos);\n          }\n\n          // Get recycle directory and eventually the recycle id\n          gOFS->_attr_get(lpath.c_str(), *mError, *pVid, \"\",\n                          Recycle::gRecyclingAttribute.c_str(), recycle_dir);\n          gOFS->_attr_get(lpath.c_str(), *mError, *pVid, \"\",\n                          Recycle::gRecycleIdXattrKey.c_str(), recycle_id);\n        }\n\n        // Check if we have a recycle policy set\n        if (recycle_dir.length() &&\n            (!spath.beginswith(Recycle::gRecyclingPrefix.c_str()))) {\n          //.....................................................................\n          // two step deletion via recycle bin\n          //.....................................................................\n          // delete files in simulation mode\n          std::map<uid_t, unsigned long long> user_deletion_size;\n          std::map<gid_t, unsigned long long> group_deletion_size;\n\n          for (rfoundit = found.rbegin(); rfoundit != found.rend(); rfoundit++) {\n            int rpos = 0;\n\n            if ((rpos = rfoundit->first.find(\"/.sys.v#.\")) == STR_NPOS) {\n              // skip to check version files\n              for (fileit = rfoundit->second.begin(); fileit != rfoundit->second.end();\n                   fileit++) {\n                std::string fspath = rfoundit->first;\n                size_t l_pos;\n                std::string entry = *fileit;\n\n                if ((l_pos = entry.find(\" ->\")) != std::string::npos) {\n                  entry.erase(l_pos);\n                }\n\n                fspath += entry;\n\n                if (gOFS->_rem(fspath.c_str(), *mError, *pVid, nullptr, true)) {\n                  stdErr += \"error: unable to remove file - bulk deletion aborted\\n\";\n                  retc = errno;\n                  return SFS_OK;\n                }\n              }\n            }\n          }\n\n          // delete directories in simulation mode\n          for (rfoundit = found.rbegin(); rfoundit != found.rend(); rfoundit++) {\n            // don't even try to delete the root directory\n            std::string fspath = rfoundit->first;\n\n            if (fspath == \"/\") {\n              continue;\n            }\n\n            if (gOFS->_remdir(rfoundit->first.c_str(), *mError, *pVid, nullptr,\n                              true) && (errno != ENOENT)) {\n              stdErr += \"error: unable to remove directory - bulk deletion aborted\\n\";\n              retc = errno;\n              return SFS_OK;\n            }\n          }\n\n          struct stat buf;\n\n          if (gOFS->_stat(spath.c_str(), &buf, *mError, *pVid, \"\")) {\n            stdErr = \"error: failed to stat bulk deletion directory: \";\n            stdErr += spath.c_str();\n            retc = errno;\n            return SFS_OK;\n          }\n\n          spath += \"/\";\n          RecycleEntry lRecycle(spath.c_str(), recycle_dir, recycle_id,\n                                pVid, buf.st_uid, buf.st_gid,\n                                (unsigned long long) buf.st_ino);\n          int rc = 0;\n\n          if ((rc = lRecycle.ToGarbage(\"rm-r\", *mError))) {\n            stdErr = \"error: failed to recycle path \";\n            stdErr += path;\n            stdErr += \"\\n\";\n            stdErr += mError->getErrText();\n            retc = mError->getErrInfo();\n            return SFS_OK;\n          } else {\n            stdOut += \"success: you can recycle this deletion using 'recycle restore \";\n            char sp[256];\n            snprintf(sp, sizeof(sp) - 1, \"%016llx\", (unsigned long long) buf.st_ino);\n            stdOut += sp;\n            stdOut += \"'\\n\";\n            retc = 0;\n            return SFS_OK;\n          }\n        } else {\n          //.....................................................................\n          // standard way to delete files recursively\n          //.....................................................................\n          // delete files starting at the deepest level\n          for (rfoundit = found.rbegin(); rfoundit != found.rend(); rfoundit++) {\n            for (fileit = rfoundit->second.begin(); fileit != rfoundit->second.end();\n                 fileit++) {\n              std::string fspath = rfoundit->first;\n              size_t l_pos;\n              std::string entry = *fileit;\n\n              if ((l_pos = entry.find(\" ->\")) != std::string::npos) {\n                entry.erase(l_pos);\n              }\n\n              fspath += entry;\n\n              if (gOFS->_rem(fspath.c_str(), *mError, *pVid, (const char*) 0, false, false,\n                             force)) {\n                stdErr += \"error: unable to remove file : \\n\";\n                stdErr += fspath.c_str();\n                retc = errno;\n              }\n            }\n          }\n\n          // delete directories starting at the deepest level\n          for (rfoundit = found.rbegin(); rfoundit != found.rend(); rfoundit++) {\n            // don't even try to delete the root directory\n            std::string fspath = rfoundit->first;\n\n            if (fspath == \"/\") {\n              continue;\n            }\n\n            if (gOFS->_remdir(rfoundit->first.c_str(), *mError, *pVid, nullptr)) {\n              if (errno != ENOENT) {\n                stdErr += \"error: unable to remove directory : \";\n                stdErr += rfoundit->first.c_str();\n                stdErr += \"; reason: \";\n                stdErr += mError->getErrText();\n                retc = errno;\n              }\n            }\n          }\n        }\n      }\n    } else {\n      for (auto it = rmList.begin(); it != rmList.end(); ++it) {\n        if (gOFS->_rem(it->c_str(), *mError, *pVid, nullptr, false, false,\n                       force) && (errno != ENOENT)) {\n          stdErr += \"error: unable to remove file/directory '\";\n          stdErr += it->c_str();\n          stdErr += \"'\";\n          retc |= errno;\n        }\n      }\n    }\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/RmCmd.cc",
    "content": "//------------------------------------------------------------------------------\n// File: RmCmd.cc\n// Author: Jozsef Makai - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"RmCmd.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/ofs/XrdMgmOfsDirectory.hh\"\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/recycle/Recycle.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/recycle/RecycleEntry.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"common/Path.hh\"\n#include \"common/Glob.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\neos::console::ReplyProto\neos::mgm::RmCmd::ProcessRequest() noexcept\n{\n  ACCESSMODE_W;\n  eos::console::ReplyProto reply;\n  std::ostringstream outStream;\n  std::ostringstream errStream;\n  XrdOucString m_err {\"\"};\n  int ret_c = 0;\n  eos::console::RmProto rm = mReqProto.rm();\n  auto recursive = rm.recursive();\n  auto noworkflow = rm.noworkflow();\n  auto force = rm.bypassrecycle();\n  auto noglobbing = rm.noglobbing();\n  std::string spath;\n\n  if (rm.path().empty()) {\n    std::string full_path;\n    std::string err_msg;\n\n    if (rm.fileid()) {\n      GetPathFromFid(full_path, rm.fileid(), err_msg);\n    } else  if (rm.containerid()) {\n      GetPathFromCid(full_path, rm.containerid(), err_msg);\n    }\n\n    spath = full_path;\n\n    if (spath.empty() && (rm.fileid() || rm.containerid())) {\n      if (vid.uid) {\n        reply.set_std_err(\"warning: removing a pending deletion is allowed \"\n                          \"only for the 'root' role\");\n        reply.set_retc(EPERM);\n      } else {\n        // Try to remove a file/container metadata object pending deletion\n        // which is blocked in the namespace in a detached state\n        bool is_dir = (rm.containerid() != 0ull);\n\n        // If -F is given then we try to force remove the file without waiting\n        // for the confirmation from the diskservers\n        if (gOFS->RemoveDetached((is_dir ? rm.containerid() : rm.fileid()),\n                                 is_dir, force, err_msg)) {\n          reply.set_std_out(err_msg);\n          reply.set_retc(0);\n        } else {\n          reply.set_std_err(err_msg);\n          reply.set_retc(errno);\n        }\n      }\n\n      return reply;\n    }\n  } else {\n    spath = rm.path();\n  }\n\n  eos::mgm::NamespaceMap(spath, nullptr, mVid);\n  std::string err_check;\n  int errno_check = 0;\n  const char* path = spath.c_str();\n  PROC_MVID_TOKEN_SCOPE;\n\n  // Enforce path checks and identity access rights\n  if (IsOperationForbidden(spath, mVid, err_check, errno_check)) {\n    eos_err(\"msg=\\\"operation forbidden\\\" path=\\\"%s\\\" serr_msg=\\\"%s\\\" errno=%i\",\n            spath.c_str(), err_check.c_str(), errno_check);\n    reply.set_std_err(err_check);\n    reply.set_retc(errno_check);\n    return reply;\n  }\n\n  eos::common::Path cPath(spath.c_str());\n  XrdOucString filter = \"\";\n  std::set<std::string> rmList;\n\n  if (force && (vid.uid)) {\n    errStream << \"warning: removing the force flag - this is only allowed \"\n              << \"for the 'root' role!\" << std::endl;\n    force = false;\n  }\n\n  if (spath.empty()) {\n    errStream << \"error: you have to give a path name to call 'rm'\";\n    ret_c = EINVAL;\n  } else {\n    if (!noglobbing) {\n      // Globbing was not disabled by the user\n      eos::common::Path objPath(spath.c_str());\n\n      if (objPath.Globbing()) {\n        spath = objPath.GetParentPath();\n        filter = objPath.GetName();\n      }\n    }\n\n    // Check file existence\n    XrdSfsFileExistence file_exists;\n    XrdOucErrInfo errInfo;\n\n    if (gOFS->_exists(spath.c_str(), file_exists, errInfo, mVid, nullptr)) {\n      errStream << \"error: unable to run exists on path '\" << spath << \"'\";\n      reply.set_std_err(errStream.str());\n      reply.set_retc(errno);\n      return reply;\n    }\n\n    if (file_exists == XrdSfsFileExistNo) {\n      errStream << \"error: no such file or directory with path '\" << spath << \"'\";\n      reply.set_std_err(errStream.str());\n      reply.set_retc(ENOENT);\n      return reply;\n    }\n\n    if (file_exists == XrdSfsFileExistIsFile) {\n      // if we have rm -r <file> we remove the -r flag\n      recursive = false;\n    }\n\n    if ((file_exists == XrdSfsFileExistIsDirectory) && filter.length()) {\n      // List the path and match against filter\n      XrdMgmOfsDirectory dir;\n      eos::common::Glob glob;\n      int listrc = dir.open(spath.c_str(), mVid, nullptr);\n\n      if (!listrc) {\n        const char* val;\n\n        while ((val = dir.nextEntry())) {\n          XrdOucString mpath = spath.c_str();\n          XrdOucString entry = val;\n          mpath += val;\n\n          if ((entry == \".\") ||\n              (entry == \"..\")) {\n            continue;\n          }\n\n          if (glob.Match(filter.c_str(), entry.c_str())) {\n            rmList.insert(mpath.c_str());\n          }\n        }\n\n        // No file matched the globbing, don't silently fail nor succeed,\n        // return ENOENT instead\n        if (rmList.empty()) {\n          errStream << \"error: no such file or directory with path '\" << spath << filter\n                    << \"', globbing did not match anything. You may disable the globbing by using --no-globbing\";\n          reply.set_std_err(errStream.str());\n          reply.set_retc(ENOENT);\n          return reply;\n        }\n      }\n\n      // if we have rm * (whatever wildcard) we remove the -r flag\n      recursive = false;\n    } else {\n      rmList.insert(spath);\n    }\n\n    // Find everything to be deleted\n    if (recursive) {\n      std::map<std::string, std::set<std::string>> found;\n      std::map<std::string, std::set<std::string>>::const_reverse_iterator rfoundit;\n      std::set<std::string>::const_iterator fileit;\n      errInfo.clear();\n      errInfo.setErrCode(E2BIG);// ask to fail E2BIG\n\n      if (gOFS->_find(spath.c_str(), errInfo, m_err, mVid, found)) {\n        if (errno == E2BIG) {\n          errStream <<\n                    \"error: the directory tree exceeds the configured query limit for you - ask an administrator to increase your query limit or split the operation into several deletions\";\n        } else {\n          errStream << \"error: unable to list directory '\" << spath << \"'\";\n        }\n\n        ret_c = errno;\n      } else {\n        std::string recycle_dir;\n        std::string recycle_id;\n\n        if (!force) {\n          // Only recycle if there is no '-f' flag\n          std::string lpath = spath.c_str();\n          size_t pos = lpath.find(\"/.sys.v#.\");\n\n          if (pos != std::string::npos) {\n            lpath.erase(pos);\n          }\n\n          // Get recycle directory and eventually the recycle id\n          gOFS->_attr_get(lpath.c_str(), errInfo, mVid, \"\",\n                          Recycle::gRecyclingAttribute.c_str(), recycle_dir);\n          gOFS->_attr_get(lpath.c_str(), errInfo, mVid, \"\",\n                          Recycle::gRecycleIdXattrKey.c_str(), recycle_id);\n        }\n\n        // See if we have a recycle policy set\n        if (recycle_dir.length() &&\n            (spath.find(Recycle::gRecyclingPrefix) != 0)) {\n          // Two step deletion via recycle bin\n          // delete files in simulation mode\n          std::map<uid_t, unsigned long long> user_deletion_size;\n          std::map<gid_t, unsigned long long> group_deletion_size;\n\n          for (rfoundit = found.rbegin(); rfoundit != found.rend(); rfoundit++) {\n            int rpos = 0;\n            RECURSIVE_STALL(\"Rm\", mVid);\n\n            if ((rpos = rfoundit->first.find(\"/.sys.v#.\")) == STR_NPOS) {\n              for (fileit = rfoundit->second.begin(); fileit != rfoundit->second.end();\n                   fileit++) {\n                std::string fspath = rfoundit->first;\n                std::string entry = *fileit;\n                size_t l_pos;\n\n                if ((l_pos = entry.find(\" ->\")) != std::string::npos) {\n                  entry.erase(l_pos);\n                }\n\n                fspath += entry;\n                errInfo.clear();\n\n                if (gOFS->_rem(fspath.c_str(), errInfo, mVid, nullptr, true)) {\n                  errStream << \"error: unable to remove file '\" << fspath << \"'\"\n                            << \" - bulk deletion aborted\" << std::endl;\n                  reply.set_std_err(errStream.str());\n                  reply.set_retc(errno);\n                  return reply;\n                }\n              }\n            }\n          }\n\n          // Delete directories in simulation mode\n          for (rfoundit = found.rbegin(); rfoundit != found.rend(); rfoundit++) {\n            RECURSIVE_STALL(\"RmDir\", mVid);\n            // don't even try to delete the root directory\n            std::string fspath = rfoundit->first;\n\n            if (fspath == \"/\") {\n              continue;\n            }\n\n            errInfo.clear();\n\n            if (gOFS->_remdir(rfoundit->first.c_str(), errInfo, mVid, nullptr, true)\n                && (errno != ENOENT)) {\n              errStream << \"error: unable to remove directory '\" << rfoundit->first << \"'\"\n                        << \" - bulk deletion aborted\" << std::endl;\n              reply.set_std_err(errStream.str());\n              reply.set_retc(errno);\n              return reply;\n            }\n          }\n\n          struct stat buf;\n\n          errInfo.clear();\n\n          if (gOFS->_stat(spath.c_str(), &buf, errInfo, mVid, \"\")) {\n            errStream << \"error: failed to stat bulk deletion directory '\" << spath << \"'\";\n            reply.set_std_err(errStream.str());\n            reply.set_retc(errno);\n            return reply;\n          }\n\n          spath += \"/\";\n          RecycleEntry lRecycle(spath.c_str(), recycle_dir, recycle_id,\n                                &mVid, buf.st_uid, buf.st_gid,\n                                (unsigned long long) buf.st_ino);\n          errInfo.clear();\n\n          if (lRecycle.ToGarbage(\"rm-r\", errInfo)) {\n            errStream << \"error: failed to recycle path '\" << spath << \"'\" << std::endl\n                      << \"reason: \" << errInfo.getErrText() << std::endl;\n            reply.set_std_err(errStream.str());\n            reply.set_retc(errInfo.getErrInfo());\n            return reply;\n          } else {\n            outStream << \"success: you can recycle this deletion using 'recycle restore \"\n                      << \"pxid:\" << std::setw(16) << std::setfill('0') << std::hex\n                      << buf.st_ino << \"'\" << std::endl;\n            reply.set_std_out(outStream.str());\n            reply.set_retc(SFS_OK);\n            return reply;\n          }\n        } else {\n          // Standard way to delete files recursively\n          // delete files starting at the deepest level\n          for (rfoundit = found.rbegin(); rfoundit != found.rend(); rfoundit++) {\n            for (fileit = rfoundit->second.begin(); fileit != rfoundit->second.end();\n                 fileit++) {\n              std::string fspath = rfoundit->first;\n              size_t l_pos;\n              std::string entry = *fileit;\n\n              if ((l_pos = entry.find(\" ->\")) != std::string::npos) {\n                entry.erase(l_pos);\n              }\n\n              fspath += entry;\n              errInfo.clear();\n\n              if (gOFS->_rem(fspath.c_str(), errInfo, mVid, nullptr, false, false,\n                             force, false, true, noworkflow)) {\n                errStream << \"error: unable to remove file '\" << fspath.c_str() << \"'\"\n                          << \" reason: \" << errInfo.getErrText() << std::endl;\n                ret_c = errno;\n              }\n            }\n          }\n\n          // Delete directories starting at the deepest level\n          for (rfoundit = found.rbegin(); rfoundit != found.rend(); rfoundit++) {\n            // don't even try to delete the root directory\n            std::string fspath = rfoundit->first;\n\n            if (fspath == \"/\") {\n              continue;\n            }\n\n            errInfo.clear();\n\n            if (gOFS->_remdir(rfoundit->first.c_str(), errInfo, mVid, nullptr)) {\n              if (errno != ENOENT) {\n                errStream << \"error: unable to remove directory \"\n                          << \"'\" << rfoundit->first.c_str() << \"'\" << std::endl\n                          << \" reason: \" << errInfo.getErrText() << std::endl;\n                ret_c = errno;\n              }\n            }\n          }\n        }\n      }\n    } else {\n      for (const auto& it : rmList) {\n        errInfo.clear();\n\n        if (gOFS->_rem(it.c_str(), errInfo, mVid, nullptr, false, false,\n                       force, false, true, noworkflow) && (errno != ENOENT)) {\n          errStream << \"error: unable to remove file/directory '\" << it << \"'\"\n                    << std::endl;\n          ret_c |= errno;\n        }\n      }\n    }\n  }\n\n  reply.set_retc(ret_c);\n  reply.set_std_out(outStream.str());\n  reply.set_std_err(errStream.str());\n  return reply;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/RmCmd.hh",
    "content": "//------------------------------------------------------------------------------\n// File: RmCmd.hh\n// Author: Jozsef Makai - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n\n#include \"mgm/proc/IProcCommand.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"proto/ConsoleRequest.pb.h\"\n\nEOSMGMNAMESPACE_BEGIN\n\nclass RmCmd : public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  RmCmd(eos::console::RequestProto&& req,\n        eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, true) {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~RmCmd() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Rmdir.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/Rmdir.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/macros/Macros.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Rmdir()\n{\n  XrdOucString spath = pOpaque->Get(\"mgm.path\");\n  const char* inpath = spath.c_str();\n  NAMESPACEMAP;\n  NAMESPACE_NO_TRAILING_SLASH;\n  PROC_BOUNCE_ILLEGAL_NAMES;\n  PROC_BOUNCE_NOT_ALLOWED;\n  spath = path;\n  PROC_TOKEN_SCOPE;\n\n  if (!spath.length()) {\n    stdErr = \"error: you have to give a path name to call 'rmdir'\";\n    retc = EINVAL;\n  } else {\n    if (gOFS->_remdir(spath.c_str(), *mError, *pVid, (const char*) 0)) {\n      stdErr += \"error: unable to remove directory \\\"\";\n      stdErr += spath.c_str();\n      stdErr +=\"\\\"\";\n      retc = errno;\n    }\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/RouteCmd.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file RouteCmd.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"RouteCmd.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/config/IConfigEngine.hh\"\n#include \"mgm/routeendpoint/RouteEndpoint.hh\"\n#include \"mgm/pathrouting/PathRouting.hh\"\n#include \"common/Constants.hh\"\n#include <sstream>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Method implementing the specific behavior of the command\n//------------------------------------------------------------------------------\neos::console::ReplyProto\nRouteCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  eos::console::RouteProto route = mReqProto.route();\n  eos::console::RouteProto::SubcmdCase subcmd = route.subcmd_case();\n\n  if (subcmd == eos::console::RouteProto::kList) {\n    ListSubcmd(route.list(), reply);\n  } else if (subcmd == eos::console::RouteProto::kLink) {\n    LinkSubcmd(route.link(), reply);\n  } else if (subcmd == eos::console::RouteProto::kUnlink) {\n    UnlinkSubcmd(route.unlink(), reply);\n  } else {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(\"error: not supported\");\n  }\n\n  return reply;\n}\n\n//------------------------------------------------------------------------------\n// List redirection routing\n//------------------------------------------------------------------------------\nvoid\nRouteCmd::ListSubcmd(const eos::console::RouteProto_ListProto& list,\n                     eos::console::ReplyProto& reply)\n{\n  std::string out;\n\n  if (!gOFS->mRouting->GetListing(list.path(), out)) {\n    reply.set_retc(ENOENT);\n    reply.set_std_err(\"error: no matching route\");\n  } else {\n    reply.set_std_out(out);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Add routing for a given path\n//------------------------------------------------------------------------------\nvoid\nRouteCmd::LinkSubcmd(const eos::console::RouteProto_LinkProto& link,\n                     eos::console::ReplyProto& reply)\n{\n  if ((mVid.uid != 0) && !mVid.hasUid(eos::common::ADM_UID) &&\n      !mVid.hasGid(eos::common::ADM_GID)) {\n    reply.set_retc(EPERM);\n    reply.set_std_err(\"error: you don't have the required priviledges to \"\n                      \"execute this command\");\n    return;\n  }\n\n  for (const auto& ep_proto : link.endpoints()) {\n    RouteEndpoint endpoint(ep_proto.fqdn(), ep_proto.xrd_port(),\n                           ep_proto.http_port());\n    std::string str_rep = endpoint.ToString();\n\n    if (gOFS->mRouting->Add(link.path(), std::move(endpoint))) {\n      gOFS->mConfigEngine->SetConfigValue(\"route\", link.path().c_str(),\n                                          str_rep.c_str());\n    } else {\n      reply.set_retc(EINVAL);\n      reply.set_std_err(SSTR(\"error: routing to \" << str_rep\n                             << \" already exists\"));\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Remove routing for given path\n//------------------------------------------------------------------------------\nvoid\nRouteCmd::UnlinkSubcmd(const eos::console::RouteProto_UnlinkProto& unlink,\n                       eos::console::ReplyProto& reply)\n{\n  if ((mVid.uid != 0) && !mVid.hasUid(eos::common::ADM_UID) &&\n      !mVid.hasGid(eos::common::ADM_GID)) {\n    reply.set_retc(EPERM);\n    reply.set_std_err(\"error: you don't have the required priviledges to \"\n                      \"execute this command\");\n    return;\n  }\n\n  std::string path = unlink.path();\n\n  if (gOFS->mRouting->Remove(path)) {\n    gOFS->mConfigEngine->DeleteConfigValue(\"route\", path.c_str());\n  } else {\n    reply.set_retc(EINVAL);\n    reply.set_std_err(SSTR(\"error: path \\\"\" << path\n                           << \"\\\" not in the routing table\"));\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/RouteCmd.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file RouteCmd.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"proto/Route.pb.h\"\n#include \"mgm/proc/ProcCommand.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include <list>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class RouteCmd - class handling route commands\n//------------------------------------------------------------------------------\nclass RouteCmd: public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  explicit RouteCmd(eos::console::RequestProto&& req,\n                    eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, false)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~RouteCmd() = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! List redirection routing\n  //!\n  //! @param list list subcmd proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void ListSubcmd(const eos::console::RouteProto_ListProto& list,\n                  eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Add routing for a given path\n  //!\n  //! @param link link subcmd proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void LinkSubcmd(const eos::console::RouteProto_LinkProto& link,\n                  eos::console::ReplyProto& reply);\n\n  //----------------------------------------------------------------------------\n  //! Remove routing for given path\n  //!\n  //! @param unlink unlink subcmd proto object\n  //! @param reply reply proto object\n  //----------------------------------------------------------------------------\n  void UnlinkSubcmd(const eos::console::RouteProto_UnlinkProto& unlink,\n                    eos::console::ReplyProto& reply);\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/TokenCmd.cc",
    "content": "//------------------------------------------------------------------------------\n// File: TokenCmd.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"TokenCmd.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/ofs/XrdMgmOfsDirectory.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/recycle/Recycle.hh\"\n#include \"mgm/macros/Macros.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"common/Path.hh\"\n#include \"common/Definitions.hh\"\n#include \"common/token/EosTok.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\neos::mgm::TokenCmd::StoreToken(const std::string& token,\n                               const std::string& voucherid, std::string& tokenpath, uid_t uid, gid_t gid)\n{\n  XrdOucErrInfo info;\n  std::shared_ptr<eos::IFileMD> fmd;\n\n  if (!GetTokenPrefix(info, uid, gid, tokenpath)) {\n    tokenpath += voucherid;\n\n    // create file with voucherid name\n    try {\n      fmd.reset();\n      fmd = gOFS->eosView->getFile(tokenpath.c_str(), 0, 0);\n      return EEXIST;\n    } catch (eos::MDException& e) {\n      fmd = gOFS->eosView->createFile(tokenpath, 0, 0);\n      fmd->setSize(0);\n      fmd->setCUid(uid);\n      fmd->setCGid(gid);\n      // store token as extended attribute\n      fmd->setAttribute(\"sys.token\", token);\n      gOFS->eosView->updateFileStore(fmd.get());\n    }\n\n    return 0;\n  }\n\n  return EIO;\n}\n\n/*----------------------------------------------------------------------------*/\nint\neos::mgm::TokenCmd::GetTokenPrefix(XrdOucErrInfo& error, uid_t uid, gid_t gid,\n                                   std::string& tokenpath)\n/*----------------------------------------------------------------------------*/\n{\n  const char* epname = \"GetTokenPrefix\";\n  eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n  char stokenuser[4096];\n  time_t now = time(NULL);\n  struct tm nowtm;\n  localtime_r(&now, &nowtm);\n\n  do {\n    snprintf(stokenuser, sizeof(stokenuser) - 1, \"%s/uid:%u/%04u/%02u/%02u/\",\n             gOFS->MgmProcTokenPath.c_str(),\n             uid,\n             1900 + nowtm.tm_year,\n             nowtm.tm_mon + 1,\n             nowtm.tm_mday);\n    struct stat buf;\n\n    if (!gOFS->_stat(stokenuser, &buf, error, rootvid, \"\")) {\n      tokenpath = stokenuser;\n      return SFS_OK;\n    }\n\n    // Verify/create group/user directory\n    if (gOFS->_mkdir(stokenuser, S_IRUSR | S_IXUSR | SFS_O_MKPTH, error, rootvid,\n                     \"\")) {\n      return gOFS->Emsg(epname, error, EIO, \"remove existing file - the \"\n                        \"token user directory couldn't be created\");\n    }\n\n    // Check the user token directory\n\n    if (gOFS->_stat(stokenuser, &buf, error, rootvid, \"\")) {\n      return gOFS->Emsg(epname, error, EIO, \"remove existing file - could not \"\n                        \"determine ownership of the token user directory\",\n                        stokenuser);\n    }\n\n    // Check the ownership of the user directory\n    if ((buf.st_uid != uid) || (buf.st_gid != gid)) {\n      // Set the correct ownership\n      if (gOFS->_chown(stokenuser, uid, gid, error, rootvid, \"\")) {\n        return gOFS->Emsg(epname, error, EIO, \"remove existing file - could not \"\n                          \"change ownership of the token user directory\",\n                          stokenuser);\n      }\n    }\n\n    tokenpath = stokenuser;\n    return SFS_OK;\n  } while (1);\n}\n\n\n\n\neos::console::ReplyProto\neos::mgm::TokenCmd::ProcessRequest() noexcept\n{\n  eos::console::ReplyProto reply;\n  std::ostringstream outStream;\n  std::ostringstream errStream;\n  XrdOucString m_err {\"\"};\n  int ret_c = 0;\n  eos::console::TokenProto token = mReqProto.token();\n\n  // ----------------------------------------------------------------------------------------------\n  // security barrier for token issuing\n  // ----------------------------------------------------------------------------------------------\n  // a regular user can only issue tokens for files/paths he owns\n  // - if a token asks for a directory path, the user has to own that directory\n  // - if a oktne assk for a file path, the user has to own that file or if there is no file he has\n  //   to own the parent directory\n  // a sudoer or root can ask for any token\n  // ----------------------------------------------------------------------------------------------\n\n  if (!eos::common::EosTok::sTokenGeneration) {\n    reply.set_retc(EPERM);\n    reply.set_std_err(\"error: change the generation value != 0 e.g. using eos space config default space.token.generation=1 to enable token creation\");\n    return reply;\n  }\n\n  if (mVid.token) {\n    // a token authenticated user cannot issue another token\n    reply.set_retc(EPERM);\n    reply.set_std_err(\"error: a token authorized user cannot issue another token\");\n    return reply;\n  }\n\n  eos_static_info(\"root=%d sudoer=%d uid=%u gid=%u\", mVid.hasUid(0), mVid.sudoer,\n                  mVid.uid, mVid.gid);\n  int mode = R_OK | T_OK;\n\n  if (token.permission().find(\"x\") != std::string::npos) {\n    mode |= X_OK;\n  }\n\n  if (token.permission().find(\"w\") != std::string::npos) {\n    mode |= W_OK;\n  }\n\n  if (token.vtoken().empty()) {\n    eos_static_info(\"%s\\n\", token.vtoken().c_str());\n\n    // check who asks for a token\n    if ((mVid.hasUid(0))) {\n      // we issue all the token in the world for them\n    } else {\n      // for user token, we only allow rwxd, nothing else;\n      for (char const& c : token.permission()) {\n        if ((c != 'r') &&\n            (c != 'x') &&\n            (c != 'w') &&\n            (c != 'd') &&\n            (c != '!') &&\n            (c != '+')) {\n          reply.set_retc(EINVAL);\n          reply.set_std_err(\"error: you can only use rwx[+1]d in your permission set!\");\n          return reply;\n        }\n      }\n\n      if (token.expires() > ((uint64_t)time(NULL) + (365 * 86400))) {\n        reply.set_retc(EINVAL);\n        reply.set_std_err(\"error: the maximum lifetime for a user token is one year!\");\n        return reply;\n      }\n\n      XrdOucErrInfo error;\n      // we restrict only on-behalf of the requestor tokens\n      token.set_owner(mVid.uid_string);\n      token.set_group(mVid.gid_string);\n\n      // deal with multiple paths\n      std::vector<std::string> paths;\n      eos::common::StringConversion::MulticharTokenize(token.path(),\n                                              paths, \"://:\");\n\n      for ( auto p: paths) {\n\t// we verify that mVid owns the path in the token\n\tif (p.back() == '/') {\n\t  if (token.allowtree()) {\n\t    if (gOFS->_access(p.c_str(), mode, error, mVid, \"\")) {\n\t      if (error.getErrInfo()) {\n\t\t// stat error\n              reply.set_retc(error.getErrInfo());\n              reply.set_std_err(error.getErrText());\n              return reply;\n\t      }\n\t    }\n\t  } else {\n\t    // directory token\n\t    if (gOFS->_access(p.c_str(), mode, error, mVid, \"\")) {\n\t      if (errno) {\n\t\t// return errno\n\t\treply.set_retc(errno);\n\t\tif (errno == ENOENT) {\n\t\t  reply.set_std_err(\"error: path does not exist!\");\n\t\t} else {\n\t\t  reply.set_std_err(\"error: no permission!\");\n\t\t}\n\t\treturn reply;\n\t      }\n\t    }\n\t  }\n\t} else {\n\t  // file path\n\t  mode |= F_OK;\n\t  // now tree permission for files\n\t  token.set_allowtree(false);\n\t  eos::common::Path cPath(p.c_str());\n\t  errno = 0;\n\t  if (gOFS->_access(p.c_str(), mode, error, mVid, \"\")) {\n\t    if (errno) {\n\t      // return errno\n\t      reply.set_retc(errno);\n\t      if (errno == ENOENT) {\n\t\treply.set_std_err(\"error: path does not exist!\");\n\t      } else {\n\t\treply.set_std_err(\"error: no permission!\");\n\t      }\n\t      return reply;\n\t    }\n\t  }\n        }\n      }\n    }\n  }\n\n  eos::common::EosTok eostoken;\n  eos::common::SymKey* symkey = eos::common::gSymKeyStore.GetCurrentKey();\n  std::string key = symkey ? symkey->GetKey64() : \"0123456789defaultkey\";\n\n  if (getenv(\"EOS_MGM_TOKEN_KEYFILE\")) {\n    struct stat buf;\n\n    if (::stat(getenv(\"EOS_MGM_TOKEN_KEYFILE\"), &buf)) {\n      reply.set_retc(-ENOKEY);\n      reply.set_std_err(\"error: unable to load token keyfile\");\n      return reply;\n    } else {\n      if ((buf.st_uid != DAEMONUID) ||\n          (buf.st_mode != 0100400)) {\n        reply.set_retc(-ENOKEY);\n        eos_static_err(\"mode bit is %o\", buf.st_mode);\n        reply.set_std_err(\"error: unable to load token keyfile - wrong ownership (must be daemon:400)\");\n        return reply;\n      }\n    }\n\n    key = eos::common::StringConversion::LoadFileIntoString(\n            getenv(\"EOS_MGM_TOKEN_KEYFILE\"), key);\n  }\n\n  if (token.vtoken().empty()) {\n    if (token.permission().find(\":\") != std::string::npos) {\n      // someone could try to inject more acl entries here\n      reply.set_retc(-EPERM);\n      reply.set_std_err(\"error: illegal permission requested\");\n      return reply;\n    }\n\n    // create a token\n    eostoken.SetPath(token.path(), token.allowtree());\n    eostoken.SetPermission(token.permission());\n    eostoken.SetExpires(token.expires());\n    eostoken.SetOwner(token.owner());\n    eostoken.SetGroup(token.group());\n    eostoken.SetGeneration(eos::common::EosTok::sTokenGeneration);\n    eostoken.SetRequester(mVid.getTrace());\n\n    for (int i = 0; i < token.origins_size(); ++i) {\n      const eos::console::TokenAuth& auth = token.origins(i);\n      eostoken.AddOrigin(auth.host(), auth.name(), auth.prot());\n    }\n\n    if (eostoken.VerifyOrigin(vid.host, vid.uid_string,\n                              std::string(vid.prot.c_str())) == -EBADE) {\n      errStream << \"error: one or several origin regexp's are invalid\" << std::endl;\n      ret_c = -EBADE;\n    } else {\n      std::string token = eostoken.Write(key) ;\n      outStream << token;\n      std::string dump;\n      eostoken.Dump(dump, true, true);\n      std::string voucherid = eostoken.Voucher();\n      {\n        eos::common::RWMutexReadLock lock(Access::gAccessMutex);\n\n        if (Access::gAllowedTokens.size()) {\n          outStream << std::endl;\n          outStream <<\n                    \"warning: the token will not be usuable without approval of an administrator!\"\n                    << std::endl;\n          outStream << \"         ask for token approval of voucher:id=\" << voucherid <<\n                    std::endl;\n        }\n      }\n      std::string token_path;\n\n      if ((ret_c = StoreToken(dump, voucherid, token_path, vid.uid, vid.gid))) {\n        errStream << \"error: could not store the token: \" << ret_c << std::endl;\n      } else {\n        eos_warning(\"creating voucher=%s path=%s owner=%s group=%s perm=%s expires=%lu store=%s token:'%s'\\n\"\n                    ,\n                    eostoken.Voucher().c_str(),\n                    eostoken.Path().c_str(),\n                    eostoken.Owner().c_str(),\n                    eostoken.Group().c_str(),\n                    eostoken.Permission().c_str(),\n                    eostoken.Expires(),\n                    token_path.c_str(),\n                    dump.c_str());\n      }\n    }\n  } else {\n    if (!(ret_c = eostoken.Read(token.vtoken(), key,\n                                eos::common::EosTok::sTokenGeneration.load(), true))) {\n      std::string dump;\n      eostoken.Dump(dump);\n      outStream << dump;\n    } else {\n      errStream << \"error: cannot read token\" << std::endl;\n    }\n\n    if (eostoken.VerifyOrigin(vid.host, vid.uid_string,\n                              std::string(vid.prot.c_str())) == -EBADE) {\n      errStream << \"error: one or several origin regexp's are invalid\" << std::endl;\n      ret_c = -EBADE;\n    }\n  }\n\n  reply.set_retc(ret_c);\n  reply.set_std_out(outStream.str());\n  reply.set_std_err(errStream.str());\n  return reply;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/TokenCmd.hh",
    "content": "//------------------------------------------------------------------------------\n// File: TokenCmd.hh\n// Author: Andreas-Joachim Peteres - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n\n#include \"mgm/proc/IProcCommand.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"proto/ConsoleRequest.pb.h\"\n\nEOSMGMNAMESPACE_BEGIN\n\nclass TokenCmd : public IProcCommand\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param req client ProtocolBuffer request\n  //! @param vid client virtual identity\n  //----------------------------------------------------------------------------\n  TokenCmd(eos::console::RequestProto&& req,\n        eos::common::VirtualIdentity& vid):\n    IProcCommand(std::move(req), vid, true) {\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~TokenCmd() override = default;\n\n  //----------------------------------------------------------------------------\n  //! Method implementing the specific behaviour of the command executed by the\n  //! asynchronous thread\n  //----------------------------------------------------------------------------\n  eos::console::ReplyProto ProcessRequest() noexcept override;\n\n  //----------------------------------------------------------------------------\n  //! Method storing a token\n  //----------------------------------------------------------------------------\n  int StoreToken(const std::string& token, const std::string& voucherid, std::string& token_path, uid_t uid, gid_t gid);\n\n  //----------------------------------------------------------------------------\n  //! Method getting a token storage prefix path\n  //----------------------------------------------------------------------------\n  int GetTokenPrefix(XrdOucErrInfo& error, uid_t uid, gid_t gid, std::string& tokenpath);\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Version.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/Version.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/features/Features.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include <XrdVersion.hh>\n\nXrdVERSIONINFOREF( XrdgetProtocol );\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Version()\n{\n  gOFS->MgmStats.Add(\"Version\", pVid->uid, pVid->gid, 1);\n  eos_info(\"version\");\n  XrdOucString option = pOpaque->Get(\"mgm.option\");\n\n  if (option.find(\"m\") != STR_NPOS) {\n    // Get XrdVersion information from existing installation\n    std::string XrdVersion = XrdVERSIONINFOVAR(XrdgetProtocol).vStr;\n    // Parse XrdVersion [ has format: component vNumber ]\n    size_t pos = XrdVersion.find(\" \");\n    if (pos != std::string::npos) {\n      XrdVersion = XrdVersion.substr(pos + 1);\n    }\n\n    stdOut += \"eos.instance.name=\";\n    stdOut +=  gOFS->MgmOfsInstanceName;\n    stdOut += \" eos.instance.version=\";\n    stdOut += VERSION;\n    stdOut += \" eos.instance.release=\";\n    stdOut += RELEASE;\n    stdOut += \" xrootd.version=\";\n    stdOut += XrdVersion.c_str();\n    stdOut += \" \";\n\n    for (auto it = Features::sMap.begin(); it != Features::sMap.end(); it++) {\n      stdOut += it->first.c_str();\n      stdOut += \"=\";\n      stdOut += it->second.c_str();\n      stdOut += \" \";\n    }\n  } else {\n    stdOut += \"EOS_INSTANCE=\";\n    stdOut += gOFS->MgmOfsInstanceName;\n    stdOut += \"\\nEOS_SERVER_VERSION=\";\n    stdOut += VERSION;\n    stdOut += \" EOS_SERVER_RELEASE=\";\n    stdOut += RELEASE;\n\n    if (option.find(\"f\") != STR_NPOS) {\n      stdOut += \"\\nEOS_SERVER_FEATURES=\";\n\n      for (auto it = Features::sMap.begin(); it != Features::sMap.end(); it++) {\n        stdOut += \"\\n\";\n        stdOut += it->first.c_str();\n        stdOut += \"  =>  \";\n        stdOut += it->second.c_str();\n      }\n    }\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Who.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/Who.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/******************A******************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include <json/json.h>\n#include \"common/Mapping.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Who()\n{\n  gOFS->MgmStats.Add(\"Who\", pVid->uid, pVid->gid, 1);\n  std::map<std::string, int> usernamecount;\n  std::map<std::string, int> authcount;\n  std::vector<std::string> tokens;\n  std::unordered_map<std::string, time_t> active_tidents;\n  std::string delimiter = \"^\";\n  std::string option;\n  bool json_format = false;\n  bool monitoring = false;\n  bool showclients = false;\n  bool showall = false;\n  bool showauth = false;\n  bool showsummary = false;\n  bool numericids = false;\n  \n  Json::Value json;\n\n  if (pOpaque->Get(\"mgm.option\")) {\n    option = pOpaque->Get(\"mgm.option\");\n  }\n\n  if (pOpaque->Get(\"mgm.format\")) {\n    std::string format = pOpaque->Get(\"mgm.format\");\n    json_format = (format == \"json\");\n  }\n\n  if ((option.find(\"m\")) != std::string::npos) {\n    monitoring = true;\n  }\n\n  if ((option.find(\"c\")) != std::string::npos) {\n    showclients = true;\n  }\n\n  if ((option.find(\"z\")) != std::string::npos) {\n    showauth = true;\n  }\n\n  if ((option.find(\"a\")) != std::string::npos) {\n    showall = true;\n  }\n\n  if ((option.find(\"s\")) != std::string::npos) {\n    showsummary = true;\n  }\n\n  if ((option.find(\"n\")) != std::string::npos) {\n    numericids = true;\n  }\n\n  for (size_t i = 0; i < eos::common::Mapping::ActiveTidentsSharded.num_shards();\n       ++i) {\n    for (auto&& it:  eos::common::Mapping::ActiveTidentsSharded.get_shard(i)) {\n      std::string username = \"\";\n      tokens.clear();\n      eos::common::StringConversion::Tokenize(it.first, tokens, delimiter);\n      uid_t uid = atoi(tokens[0].c_str());\n      int terrc = 0;\n      if (numericids) {\n\tusername = std::to_string(uid);\n      } else {\n\tusername = eos::common::Mapping::UidToUserName(uid, terrc);\n      }\n      usernamecount[username]++;\n      authcount[tokens[2]]++;\n      active_tidents.emplace(std::move(it.first), std::move(it.second));\n    }\n  }\n\n\n  if (showauth || showall) {\n    std::map<std::string, int>::const_iterator it;\n\n    for (it = authcount.begin(); it != authcount.end(); it++) {\n      char formatline[1024];\n\n      if (monitoring) {\n        snprintf(formatline, sizeof(formatline) - 1, \"auth=%s nsessions=%d\\n\",\n                 it->first.c_str(), it->second);\n        stdOut += formatline;\n      } else if (json_format) {\n        Json::Value json_auth;\n        json_auth[\"auth\"] = it->first;\n        json_auth[\"nsessions\"] = it->second;\n        json.append(json_auth);\n      } else {\n        snprintf(formatline, sizeof(formatline) - 1, \"auth   : %-24s := %d sessions\\n\",\n                 it->first.c_str(), it->second);\n        stdOut += formatline;\n      }\n    }\n  }\n\n  if (!showclients || showall) {\n    std::map<std::string, int>::const_iterator ituname;\n    std::map<uid_t, int>::const_iterator ituid;\n\n    for (ituname = usernamecount.begin(); ituname != usernamecount.end();\n         ituname++) {\n      char formatline[1024];\n\n      if (monitoring) {\n        snprintf(formatline, sizeof(formatline) - 1, \"uid=%s nsessions=%d\\n\",\n                 ituname->first.c_str(), ituname->second);\n        stdOut += formatline;\n      } else if (json_format) {\n        Json::Value json_user;\n        json_user[\"uid\"] = ituname->first;\n        json_user[\"nsessions\"] = ituname->second;\n        json.append(json_user);\n      } else {\n        snprintf(formatline, sizeof(formatline) - 1, \"user   : %-24s := %d sessions\\n\",\n                 ituname->first.c_str(), ituname->second);\n        stdOut += formatline;\n      }\n    }\n  }\n\n  unsigned long long cnt = 0;\n\n  if (showclients || showall || showsummary) {\n    for (const auto& it : active_tidents) {\n\n      cnt++;\n      std::string username = \"\";\n      tokens.clear();\n      // std::string intoken = it->first.c_str();\n      eos::common::StringConversion::Tokenize(it.first, tokens, delimiter);\n      uid_t uid = atoi(tokens[0].c_str());\n      int terrc = 0;\n      if (numericids) {\n\tusername = std::to_string(uid);\n      } else {\n\tusername = eos::common::Mapping::UidToUserName(uid, terrc);\n      }\n      char formatline[1024];\n      time_t now = time(NULL);\n\n      if (monitoring) {\n        snprintf(formatline, sizeof(formatline) - 1,\n                 \"client=%s uid=%s auth=%s idle=%ld gateway=\\\"%s\\\" app=%s\\n\",\n                 tokens[1].c_str(), username.c_str(), tokens[2].c_str(),\n                 now - it.second, tokens[3].c_str(),\n                 (tokens.size() > 4) ? tokens[4].c_str() : \"XRoot\");\n        stdOut += formatline;\n      } else if (json_format) {\n        if (!showsummary) {\n          Json::Value json_client;\n          json_client[\"client\"] = tokens[1];\n          json_client[\"uid\"] = username;\n          json_client[\"auth\"] = tokens[2];\n          json_client[\"idle\"] = (Json::UInt64)(now - it.second);\n          json_client[\"gateway\"] = tokens[3];\n          json_client[\"app\"] =\n            (tokens.size() > 4) ? tokens[4].c_str() : \"XRoot\";\n          json.append(json_client);\n        }\n      } else {\n        snprintf(formatline, sizeof(formatline) - 1,\n                 \"client : %-10s               := %-40s (%5s) [ %-40s ] { %-8s } %lds idle time \\n\",\n                 username.c_str(), tokens[1].c_str(), tokens[2].c_str(),\n                 tokens[3].c_str(),\n                 ((tokens.size() > 4) && tokens[4].length())\n                 ? tokens[4].c_str()\n                 : \"XRoot\",\n                 now - it.second);\n\n        if (!showsummary) {\n          stdOut += formatline;\n        }\n      }\n    }\n  }\n\n  if (showsummary) {\n    char formatline[1024];\n\n    if (monitoring) {\n      snprintf(formatline, sizeof(formatline) - 1, \"nclients=%llu\\n\", cnt);\n      stdOut += formatline;\n    } else if (json_format) {\n      Json::Value json_count;\n      json_count[\"nclients\"] = Json::UInt64(cnt);\n      json.append(json_count);\n    } else {\n      snprintf(formatline, sizeof(formatline) - 1, \"sum(clients) : %llu\\n\", cnt);\n      stdOut += formatline;\n    }\n  }\n\n  if (json_format) {\n    stdOut = \"\";\n    Json::StreamWriterBuilder builder;\n    std::unique_ptr<Json::StreamWriter> jsonwriter(\n\t\t\t\t\t\t   builder.newStreamWriter());\n    std::stringstream ss;\n    jsonwriter->write(json, &ss);\n    stdJson += ss.str().c_str();\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/proc/user/Whoami.cc",
    "content": "// ----------------------------------------------------------------------\n// File: proc/user/Whoami.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/proc/ProcInterface.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/stat/Stat.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nint\nProcCommand::Whoami()\n{\n  gOFS->MgmStats.Add(\"WhoAmI\", pVid->uid, pVid->gid, 1);\n  std::string option = (pOpaque->Get(\"mgm.option\")) ? pOpaque->Get(\"mgm.option\") :\n                       \"\";\n\n  if ((option.find(\"m\")) != std::string::npos) {\n    stdOut += \"uid=\";\n    stdOut += (int) pVid->uid;\n    stdOut += \" uids=\";\n\n    for (const auto& uid : pVid->allowed_uids) {\n      stdOut += (int)uid;\n      stdOut += \",\";\n    }\n\n    if (!pVid->allowed_uids.empty()) {\n      stdOut.erase(stdOut.length() - 1);\n    }\n\n    stdOut += \" gid=\";\n    stdOut += (int) pVid->gid;\n    stdOut += \" gids=\";\n\n    for (const auto& gid : pVid->allowed_gids) {\n      stdOut += (int)gid;\n      stdOut += \",\";\n    }\n\n    if (!pVid->allowed_gids.empty()) {\n      stdOut.erase(stdOut.length() - 1);\n    }\n\n    stdOut += \" authz=\";\n    stdOut += pVid->prot;\n    stdOut += \" sudo=\";\n\n    if (pVid->sudoer) {\n      stdOut += \"true\";\n    } else {\n      stdOut += \"false\";\n    }\n\n    //! the host/geo location is not reported\n  } else {\n    stdOut += \"Virtual Identity: uid=\";\n    stdOut += (int) pVid->uid;\n    stdOut += \" (\";\n\n    for (const auto& uid : pVid->allowed_uids) {\n      stdOut += (int)uid;\n      stdOut += \",\";\n    }\n\n    if (!pVid->allowed_uids.empty()) {\n      stdOut.erase(stdOut.length() - 1);\n    }\n\n    stdOut += \") gid=\";\n    stdOut += (int) pVid->gid;\n    stdOut += \" (\";\n\n    for (const auto& gid : pVid->allowed_gids) {\n      stdOut += (int)gid;\n      stdOut += \",\";\n    }\n\n    if (!pVid->allowed_gids.empty()) {\n      stdOut.erase(stdOut.length() - 1);\n    }\n\n    stdOut += \")\";\n    stdOut += \" [authz:\";\n    stdOut += pVid->prot;\n    stdOut += \"]\";\n\n    if (pVid->sudoer) {\n      stdOut += \" sudo*\";\n    }\n\n    stdOut += \" host=\";\n    stdOut += pVid->host.c_str();\n    stdOut += \" domain=\";\n    stdOut += pVid->domain.c_str();\n\n    if (pVid->geolocation.length()) {\n      stdOut += \" geo-location=\";\n      stdOut += pVid->geolocation.c_str();\n    }\n\n    if (pVid->key.length()) {\n      if (pVid->prot == \"sss\") {\n        stdOut += \" key=\";\n        stdOut += pVid->key.c_str();\n      } else {\n        stdOut += \" key=<oauth2>\";\n      }\n    }\n\n    if (pVid->fullname.length()) {\n      stdOut += \" fullname='\";\n      stdOut += pVid->fullname.c_str();\n      stdOut += \"'\";\n    }\n\n    if (pVid->federation.length()) {\n      stdOut += \" federation='\";\n      stdOut += pVid->federation.c_str();\n      stdOut += \"'\";\n    }\n\n    if (pVid->email.length()) {\n      stdOut += \" email='\";\n      stdOut += pVid->email.c_str();\n      stdOut += \"'\";\n    }\n\n    std::string tokenDump;\n\n    if (pVid->token) {\n      pVid->token->Dump(tokenDump, true, false);\n\n      if (tokenDump.length() > 4) {\n        stdOut += \"\\n\";\n        stdOut += tokenDump.c_str();\n      }\n    }\n  }\n\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/qdbmaster/QdbMaster.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file QdbMaster.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/qdbmaster/QdbMaster.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/wfe/WFE.hh\"\n#include \"mgm/fsck/Fsck.hh\"\n#include \"mgm/lru/LRU.hh\"\n#include \"mgm/recycle/Recycle.hh\"\n#include \"mgm/devices/Devices.hh\"\n#include \"mgm/geotreeengine/GeoTreeEngine.hh\"\n#include \"mgm/convert/ConverterEngine.hh\"\n#include \"mgm/config/IConfigEngine.hh\"\n#include \"mgm/tgc/MultiSpaceTapeGc.hh\"\n#include \"mq/MessagingRealm.hh\"\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"namespace/interface/IFsView.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/interface/IQuota.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"namespace/interface/INamespaceGroup.hh\"\n#include \"common/plugin_manager/PluginManager.hh\"\n#include \"common/IntervalStopwatch.hh\"\n#include <qclient/QClient.hh>\n#include \"common/ShellCmd.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nstd::string QdbMaster::sLeaseKey {\"master_lease\"};\nstd::chrono::seconds QdbMaster::sMasterDelaySec = std::chrono::seconds(10);\nstatic constexpr auto QDBMASTER_THREAD_NAME = \"QdbMasterSup\";\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nQdbMaster::QdbMaster(const eos::QdbContactDetails& qdb_info,\n                     const std::string& host_port):\n  mOneOff(true), mIdentity(host_port), mMasterIdentity(),\n  mIsMaster(false),  mConfigLoaded(false),\n  mAcquireDelay(0)\n{\n  mQcl = std::make_unique<qclient::QClient>(qdb_info.members,\n         qdb_info.constructOptions());\n}\n\n//------------------------------------------------------------------------------\n//! Destructor\n//------------------------------------------------------------------------------\nQdbMaster::~QdbMaster()\n{\n  mThread.join();\n}\n\n//------------------------------------------------------------------------------\n// Init method to determine the current master/slave state\n//------------------------------------------------------------------------------\nbool\nQdbMaster::Init()\n{\n  gOFS->mNamespaceState = NamespaceState::kBooting;\n  mThread.reset(&QdbMaster::Supervisor, this);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Boot namespace\n//------------------------------------------------------------------------------\nbool\nQdbMaster::BootNamespace()\n{\n  using eos::common::PluginManager;\n  // Load the namepace implementation from external library\n  PluginManager& pm = PluginManager::GetInstance();\n  PF_PlatformServices& pm_svc = pm.GetPlatformServices();\n  pm_svc.invokeService = &XrdMgmOfs::DiscoverPlatformServices;\n  gOFS->namespaceGroup.reset(static_cast<INamespaceGroup*>\n                             (pm.CreateObject(\"NamespaceGroup\")));\n  // Collect namespace options, and initialize namespace group\n  std::map<std::string, std::string> namespaceConfig;\n  std::string err;\n  std::string instance_id =\n    SSTR(gOFS->MgmOfsInstanceName << \":\" << gOFS->ManagerPort);\n  namespaceConfig[\"queue_path\"] = gOFS->mQClientDir;\n  namespaceConfig[\"qdb_cluster\"] = gOFS->mQdbCluster;\n  namespaceConfig[\"qdb_password\"] = gOFS->mQdbPassword;\n  namespaceConfig[\"qdb_flusher_md\"] = SSTR(instance_id << \"_md\");\n  namespaceConfig[\"qdb_flusher_quota\"] = SSTR(instance_id << \"_quota\");\n\n  if (!gOFS->mQClientFlusherType.empty()) {\n    namespaceConfig[\"qclient_flusher_type\"] = gOFS->mQClientFlusherType;\n  }\n\n  if (!gOFS->mQClientRocksDBOptions.empty()) {\n    namespaceConfig[\"qclient_rocksdb_options\"] = gOFS->mQClientRocksDBOptions;\n  }\n\n  FillNsCacheConfig(gOFS->mConfigEngine, namespaceConfig);\n\n  if (!gOFS->namespaceGroup->initialize(&gOFS->eosViewRWMutex, namespaceConfig,\n                                        err, &gOFS->mNamespaceStats)) {\n    eos_err(\"msg=\\\"could not initialize namespace group, err: %s\\\"\", err.c_str());\n    return false;\n  }\n\n  // Fetch all required services out of namespace group\n  gOFS->eosDirectoryService = gOFS->namespaceGroup->getContainerService();\n  gOFS->eosFileService = gOFS->namespaceGroup->getFileService();\n  gOFS->eosView = gOFS->namespaceGroup->getHierarchicalView();\n  gOFS->eosFsView = gOFS->namespaceGroup->getFilesystemView();\n  gOFS->eosContainerAccounting =\n    gOFS->namespaceGroup->getContainerAccountingView();\n  gOFS->eosSyncTimeAccounting = gOFS->namespaceGroup->getSyncTimeAccountingView();\n\n  if (!gOFS->eosDirectoryService || !gOFS->eosFileService || !gOFS->eosView ||\n      !gOFS->eosFsView || !gOFS->eosContainerAccounting ||\n      !gOFS->eosSyncTimeAccounting) {\n    MasterLog(eos_log(LOG_ERR, \"%s\", \"msg=\\\"namespace implementation could not \"\n                      \"be loaded using the provided library plugin - one of \"\n                      \"the required namespace views could not be created\\\"\"));\n    gOFS->mNamespaceState = NamespaceState::kFailed;\n    return false;\n  }\n\n  time_t tstart = time(nullptr);\n\n  try {\n    gOFS->eosDirectoryService->configure(namespaceConfig);\n    gOFS->eosFileService->configure(namespaceConfig);\n    gOFS->eosFsView->configure(namespaceConfig);\n    gOFS->eosView->configure(namespaceConfig);\n    gOFS->eosFileService->setQuotaStats(gOFS->eosView->getQuotaStats());\n    gOFS->eosDirectoryService->setQuotaStats(gOFS->eosView->getQuotaStats());\n    gOFS->eosView->getQuotaStats()->registerSizeMapper(Quota::MapSizeCB);\n    gOFS->eosView->initialize1();\n    gOFS->mBootContainerId = gOFS->eosDirectoryService->getFirstFreeId();\n    MasterLog(eos_log(LOG_NOTICE, \"msg=\\\"container initialization done\\\" \"\n                      \"duration=%ds\", (time(nullptr) - tstart)));\n  } catch (eos::MDException& e) {\n    MasterLog(eos_log(LOG_NOTICE, \"msg=\\\"container initialization failed\\\" \"\n                      \"duration=%ds errc=%d  reason=\\\"%s\\\"\",\n                      (time(nullptr) - tstart), e.getErrno(),\n                      e.getMessage().str().c_str()));\n    gOFS->mNamespaceState = NamespaceState::kFailed;\n    return false;\n  } catch (const std::runtime_error& qdb_err) {\n    MasterLog(eos_log(LOG_NOTICE, \"msg=\\\"container initialization failed \"\n                      \"unable to connect to QuarkDB cluster\\\" reason=\\\"%s\\\"\",\n                      qdb_err.what()));\n    gOFS->mNamespaceState = NamespaceState::kFailed;\n    return false;\n  }\n\n  // Initialize the file view\n  gOFS->mFileInitTime = time(nullptr);\n\n  try {\n    MasterLog(eos_log(LOG_NOTICE, \"%s\",\n                      \"msg=\\\"eos file view initialize2 starting ...\\\"\"));\n    eos::common::RWMutexWriteLock wr_view_lock(gOFS->eosViewRWMutex);\n    gOFS->eosView->initialize2();\n    MasterLog(eos_log(LOG_NOTICE, \"msg=\\\"file view initialize2 done\\\" duration=%ds\",\n                      time(nullptr) - gOFS->mFileInitTime));\n    gOFS->mBootFileId = gOFS->eosFileService->getFirstFreeId();\n  } catch (eos::MDException& e) {\n    MasterLog(eos_log(LOG_CRIT, \"msg=\\\"file view initialize2 failed\\\" duration=%ds \"\n                      \"errc=%d reason=\\\"%s\\\"\", (time(nullptr) - gOFS->mFileInitTime),\n                      e.getErrno(), e.getMessage().str().c_str()));\n    gOFS->mNamespaceState = NamespaceState::kFailed;\n    return false;;\n  }\n\n  gOFS->namespaceGroup->startCacheRefreshListener();\n  gOFS->mFileInitTime = time(nullptr) - gOFS->mFileInitTime;\n  gOFS->mTotalInitTime = time(nullptr) - gOFS->mTotalInitTime;\n  gOFS->mNamespaceState = NamespaceState::kBooted;\n  MasterLog(eos_log(LOG_ALERT, \"%s\", \"msg=\\\"QDB namespace booted\\\"\"));\n\n  // Get process status after boot\n  if (!eos::common::LinuxStat::GetStat(gOFS->LinuxStatsStartup)) {\n    MasterLog(eos_log(LOG_ERR, \"%s\",\n                      \"msg=\\\"failed to grab /proc/self/stat information\\\"\"));\n  }\n\n  while (mOneOff) {\n    std::this_thread::sleep_for(std::chrono::seconds(1));\n    MasterLog(eos_log(LOG_INFO,\n                      \"%s\", \"msg=\\\"wait for the supervisor to run once\\\"\"));\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Configure QDB lease timeouts\n//------------------------------------------------------------------------------\nvoid\nQdbMaster::ConfigureTimeouts(uint64_t& master_init_lease)\n{\n  if (getenv(\"EOS_QDB_MASTER_INIT_LEASE_MS\")) {\n    master_init_lease = std::stoull(getenv(\"EOS_QDB_MASTER_INIT_LEASE_MS\"));\n  }\n\n  if (getenv(\"EOS_QDB_MASTER_LEASE_MS\")) {\n    mLeaseValidity = std::chrono::milliseconds\n                     (std::stoull(getenv(\"EOS_QDB_MASTER_LEASE_MS\")));\n\n    if (mLeaseValidity > std::chrono::minutes(5)) {\n      MasterLog(eos_log(LOG_WARNING, \"%s\", \"msg=\\\"QDB master lease validity set \"\n                        \"to the maximum of 5 minutes\\\"\"));\n      mLeaseValidity = std::chrono::minutes(5);\n    }\n\n    if (master_init_lease < (uint64_t)mLeaseValidity.count()) {\n      MasterLog(eos_log(LOG_WARNING, \"%s\", \"msg=\\\"QDB master init lease validity \"\n                        \"modified to the value of the QDB master lease\\\"\"));\n      master_init_lease = mLeaseValidity.count();\n    }\n  }\n\n  MasterLog(eos_log(LOG_INFO, \"msg=\\\"QDB master lease vailidy configured \"\n                    \"as %u ms\\\"\",  mLeaseValidity.count()));\n}\n\n//------------------------------------------------------------------------------\n// Thread supervising the master/slave status\n//------------------------------------------------------------------------------\nvoid\nQdbMaster::Supervisor(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(QDBMASTER_THREAD_NAME);\n  bool new_is_master = false;\n  std::string old_master_id;\n  uint64_t master_init_lease = 60000; // 60 seconds\n  ConfigureTimeouts(master_init_lease);\n  eos_notice(\"%s\", \"msg=\\\"set up booting stall rule\\\"\");\n  RemoveStatusFile(EOSMGMMASTER_SUBSYS_RW_LOCKFILE);\n  Access::StallInfo old_stall;\n  Access::StallInfo new_stall(\"*\", \"100\", \"namespace is booting\", true);\n  Access::SetStallRule(new_stall, old_stall);\n  // @todo (esindril) handle case when config contains stall rules\n\n  // Wait for the namespace to boot and the config to load\n  while ((gOFS->mNamespaceState != NamespaceState::kBooted) &&\n         !assistant.terminationRequested()) {\n    assistant.wait_for(std::chrono::seconds(1));\n    MasterLog(eos_log(LOG_INFO,\n                      \"msg=\\\"waiting for namespace boot\\\" mNamespaceState=%s\",\n                      namespaceStateToString(gOFS->mNamespaceState).c_str()));\n  }\n\n  // Loop updating the master status\n  while (!assistant.terminationRequested()) {\n    old_master_id = GetMasterId();\n    new_is_master = AcquireLeaseWithDelay();\n    UpdateMasterId(GetLeaseHolder());\n    MasterLog(eos_log(LOG_DEBUG, \"old_is_master=%s, is_master=%s, old_master_id=%s,\"\n                      \" master_id=%s\", mIsMaster.load() ? \"true\" : \"false\",\n                      new_is_master ? \"true\" : \"false\",\n                      old_master_id.c_str(), GetMasterId().c_str()));\n\n    // Run one-off after boot\n    if (mOneOff) {\n      if (new_is_master) {\n        // Increase the lease validity for the transition\n        if (!AcquireLease(master_init_lease)) {\n          MasterLog(eos_log(LOG_ERR, \"%s\", \"msg=\\\"failed to renew lease during\"\n                            \" transition\\\"\"));\n          continue;\n        }\n\n        SlaveToMaster();\n        mDoMasterDelay = false;\n        gOFS->mTracker.SetAcceptingRequests(true);\n      } else {\n        MasterToSlave();\n      }\n\n      MasterLog(eos_log(LOG_NOTICE, \"%s\", \"msg=\\\"remove booting stall rule\\\"\"));\n      Access::StallInfo dummy_stall;\n      Access::SetStallRule(old_stall, dummy_stall);\n      mOneOff = false;\n    } else {\n      // There was a master-slave transition\n      if (mIsMaster != new_is_master) {\n        if (mIsMaster) {\n          MasterLog(eos_log(LOG_ERR, \"%s\", \"msg=\\\"lost the master lease\\\"\"));\n          MasterToSlave();\n        } else {\n          // Increase the lease validity for the transition\n          if (!AcquireLease(master_init_lease)) {\n            MasterLog(eos_log(LOG_ERR, \"%s\", \"msg=\\\"failed to renew lease \"\n                              \"during transition\\\"\"));\n            continue;\n          }\n\n          MasterLog(eos_log(LOG_ERR, \"%s\", \"msg=\\\"acquired the master lease\\\"\"));\n          SlaveToMaster();\n          PostSlaveToMaster(old_master_id, GetMasterId());\n        }\n      } else {\n        std::string new_master_id = GetMasterId();\n\n        // Update new master if we released the lease on purpose\n        if (!new_is_master && (new_master_id == mIdentity)) {\n          new_master_id.clear();\n        }\n\n        // There was a change in the master identity or the current master\n        // could not update the lease\n        if (!new_master_id.empty()) {\n          if ((new_master_id != old_master_id) &&\n              (new_master_id != mIdentity)) {\n            // Follow up on the master to slave transition\n            eos_static_info(\"%s\", \"msg=\\\"follow up master-to-slave transition\\\"\");\n            Access::SetMasterToSlaveRules(new_master_id);\n            gOFS->mTracker.SetAcceptingRequests(true);\n          } else {\n            if (new_master_id == mIdentity) {\n              if (mDoMasterDelay) {\n                if (mMasterDelayDeadline < std::chrono::system_clock::now()) {\n                  eos_static_info(\"msg=\\\"stop delaying new requests\\\" master=\\\"%s\\\"\",\n                                  new_master_id.c_str());\n                  mDoMasterDelay = false;\n                  gOFS->mTracker.SetAcceptingRequests(true);\n                } else {\n                  eos_static_info(\"msg=\\\"delay accepting requests\\\" master=\\\"%s\\\"\",\n                                  new_master_id.c_str());\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n\n    // If there is a master then wait a bit\n    if (!GetMasterId().empty()) {\n      std::chrono::milliseconds wait_ms(mLeaseValidity.count() / 2);\n      assistant.wait_for(wait_ms);\n    }\n  }\n\n  RemoveStatusFile(EOSMGMMASTER_SUBSYS_RW_LOCKFILE);\n}\n\n//------------------------------------------------------------------------------\n// PostSlaveToMaster runs a custom hook. It is invoked after the SlaveToMaster\n// transition. It only runs if configured in the mgmofs.postslavetomaster option.\n//------------------------------------------------------------------------------\nvoid QdbMaster::PostSlaveToMaster(std::string old_master,\n                                  std::string new_master)\n{\n  if (gOFS->mPostSlaveToMaster.length() == 0) {\n    return;\n  }\n\n  eos_static_info(\"msg=\\\"running post slave to master script\\\" path=\\\"%s\\\"\",\n                  gOFS->mPostSlaveToMaster.c_str());\n  std::string script = std::string(gOFS->mPostSlaveToMaster.c_str())\n                       + \" '\" + old_master + \"'\"\n                       + \" '\" + new_master + \"'\";\n  eos::common::ShellCmd cmd(script);\n  auto status = cmd.wait(POST_SLAVE_TO_MASTER_TIMEOUT);\n\n  if (status.exit_code) {\n    eos_static_warning(\"msg=\\\"post slave to master script failed\\\" \"\n                       \"script=\\\"%s\\\" retcode=%d\", script.c_str(),\n                       status.exit_code);\n  } else {\n    eos_static_info(\"msg=\\\"post slave to master script run successfully\\\"\");\n  }\n}\n\n//------------------------------------------------------------------------------\n// Slave to master transition\n//------------------------------------------------------------------------------\nvoid\nQdbMaster::SlaveToMaster()\n{\n  MasterLog(eos_log(LOG_INFO, \"%s\", \"msg=\\\"start slave to master transition\\\"\"));\n  Access::StallInfo old_stall; // to be discarded\n  Access::StallInfo new_stall(\"*\", \"5\", \"slave->master transition\", true);\n  Access::SetStallRule(new_stall, old_stall);\n  gOFS->mTracker.SetAcceptingRequests(false);\n  gOFS->mTracker.SpinUntilNoRequestsInFlight(true,\n      std::chrono::milliseconds(100));\n  // Force refresh the inode provider to get the latest inode values from QDB\n  gOFS->eosFileService->configure({{constants::sKeyInodeRefresh, \"true\"}});\n  gOFS->eosFileService->initialize();\n  gOFS->eosDirectoryService->initialize();\n  std::string std_out, std_err;\n  // We are the master and we broadcast every configuration change\n  gOFS->mMessagingRealm->EnableBroadcast();\n\n  if (!ApplyMasterConfig(std_out, std_err, true)) {\n    eos_err(\"%s\", \"msg=\\\"failed to apply master configuration\\\"\");\n    std::abort();\n  }\n\n  Quota::LoadNodes();\n  EnableNsCaching();\n  WFE::MoveFromRBackToQ();\n  // Notify all the nodes about the new master identity\n  FsView::gFsView.BroadcastMasterId(GetMasterId());\n  mIsMaster = true;\n  gOFS->mLRUEngine->Start();\n  gOFS->mRecycler->Start();\n  gOFS->mDeviceTracker->Start();\n  // Trigger a geotree refresh to make sure all the file systems are\n  // marked as available in the GeoTree after the failover.\n  gOFS->mGeoTreeEngine->forceRefresh();\n  Access::RemoveStallRule(\"*\");\n  Access::SetSlaveToMasterRules();\n  CreateStatusFile(EOSMGMMASTER_SUBSYS_RW_LOCKFILE);\n\n  // Start tape garbage collector, only if tape is configured and enabled\n  if (gOFS->mTapeEnabled) {\n    try {\n      gOFS->mTapeGc->start();\n    } catch (std::exception& ex) {\n      std::ostringstream msg;\n      msg << \"msg=\\\"Failed to start tape-aware garbage collection: \" << ex.what() <<\n          \"\\\"\";\n      eos_crit(msg.str().c_str());\n      std::abort();\n    } catch (...) {\n      eos_crit(\"msg=\\\"Failed to start tape-aware garbage collection: Caught an unknown exception\\\"\");\n      std::abort();\n    }\n  }\n\n  mDoMasterDelay = true;\n  mMasterDelayDeadline = std::chrono::system_clock::now() + sMasterDelaySec;\n  MasterLog(eos_log(LOG_INFO, \"%s\",\n                    \"msg=\\\"finished slave to master transition\\\"\"));\n}\n\n//------------------------------------------------------------------------------\n// Master to slave transition\n//------------------------------------------------------------------------------\nvoid\nQdbMaster::MasterToSlave()\n{\n  MasterLog(eos_log(LOG_INFO, \"%s\", \"msg=\\\"start master to slave transition\\\"\"));\n  RemoveStatusFile(EOSMGMMASTER_SUBSYS_RW_LOCKFILE);\n  mIsMaster = false;\n  UpdateMasterId(\"\");\n  gOFS->mDeviceTracker->Stop();\n  gOFS->mRecycler->Stop();\n  gOFS->mDrainEngine.Stop();\n  gOFS->mFsckEngine->Stop();\n  gOFS->mLRUEngine->Stop();\n\n  if (gOFS->mConverterEngine) {\n    gOFS->mConverterEngine->Stop();\n  }\n\n  Access::StallInfo old_stall; // to be discarded\n  Access::StallInfo new_stall(\"*\", \"5\", \"master->slave transition\", true);\n  Access::SetStallRule(new_stall, old_stall);\n  gOFS->mTracker.SetAcceptingRequests(false);\n  gOFS->mTracker.SpinUntilNoRequestsInFlight(true,\n      std::chrono::milliseconds(100));\n  // We are the slave, we just listen and don't broadcast anything\n  gOFS->mMessagingRealm->DisableBroadcast();\n\n  // When we boot the first time also load the config\n  if (mOneOff) {\n    std::string std_out, std_err;\n\n    if (!ApplyMasterConfig(std_out, std_err, false)) {\n      eos_err(\"%s\", \"msg=\\\"failed to apply configuration\\\"\");\n      std::abort();\n    }\n  }\n\n  // Disable NS cahcing after the configuration is applied otherwise\n  // a namespace cache size override might apply and we end up the\n  // slave caching information.\n  DisableNsCaching();\n\n  // Stop the tape garbage collector if tape is configured and enabled\n  if (gOFS->mTapeEnabled) {\n    try {\n      gOFS->mTapeGc->stop();\n    } catch (std::exception& ex) {\n      std::ostringstream msg;\n      msg << \"msg=\\\"Failed to stop tape-aware garbage collection: \" << ex.what() <<\n          \"\\\"\";\n      eos_err(msg.str().c_str());\n    } catch (...) {\n      eos_err(\"msg=\\\"Failed to stop tape-aware garbage collection: Caught an unknown exception\\\"\");\n    }\n  }\n\n  MasterLog(eos_log(LOG_INFO, \"%s\",\n                    \"msg=\\\"finished master to slave transition\\\"\"));\n}\n\n//------------------------------------------------------------------------------\n// Apply configuration setting\n//------------------------------------------------------------------------------\nbool\nQdbMaster::ApplyMasterConfig(std::string& stdOut, std::string& stdErr,\n                             bool apply_stall_rdr)\n{\n  static std::mutex sequential_mutex;\n  std::unique_lock<std::mutex> lock(sequential_mutex);\n  gOFS->mFsckEngine->Stop();\n  gOFS->mDrainEngine.Stop();\n  gOFS->mDrainEngine.Start();\n  // Take care of setting the config engine for FsView to null while applying\n  // the config otherwise we deadlock since the FsView will try to set config\n  // keys\n  eos::mgm::ConfigResetMonitor fsview_cfg_reset_monitor;\n\n  if (gOFS->MgmConfigAutoLoad.length()) {\n    eos_static_info(\"msg=\\\"autoload config\\\" cfg_name=\\\"%s\\\"\",\n                    gOFS->MgmConfigAutoLoad.c_str());\n    std::string configenv = gOFS->MgmConfigAutoLoad.c_str();\n    XrdOucString stdErr = \"\";\n\n    if (!gOFS->mConfigEngine->LoadConfig(configenv, stdErr, apply_stall_rdr)) {\n      eos_crit(\"msg=\\\"failed config autoload\\\" config=\\\"%s\\\" err=\\\"%s\\\"\",\n               gOFS->MgmConfigAutoLoad.c_str(), stdErr.c_str());\n    } else {\n      mConfigLoaded = true;\n      eos_static_info(\"msg=\\\"successful config autoload\\\" config=\\\"%s\\\"\",\n                      gOFS->MgmConfigAutoLoad.c_str());\n    }\n  }\n\n  return mConfigLoaded;\n}\n\n//------------------------------------------------------------------------------\n// Try to acquire lease\n//------------------------------------------------------------------------------\nbool\nQdbMaster::AcquireLease(uint64_t validity_msec)\n{\n  using eos::common::StringConversion;\n  std::string timeout = std::to_string(validity_msec ? validity_msec :\n                                       mLeaseValidity.count());\n  eos::common::IntervalStopwatch stop_watch;\n  std::future<qclient::redisReplyPtr> f =\n    mQcl->exec(\"lease-acquire\", sLeaseKey, mIdentity, timeout);\n  qclient::redisReplyPtr reply = f.get();\n\n  if (stop_watch.timeIntoCycle().count() > 5) {\n    eos_info(\"msg=\\\"qclient acquire lease call took %llums\\\"\",\n             stop_watch.timeIntoCycle().count());\n  }\n\n  if (reply == nullptr) {\n    return false;\n  }\n\n  std::string reply_msg(reply->str, reply->len);\n\n  if ((reply_msg == \"ACQUIRED\") ||\n      (reply_msg == \"RENEWED\")) {\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Try to acquire lease with delay. If the mAcquireDelay timestamp is set\n// then we skip trying to acquire the lease until the delay has expired.\n//------------------------------------------------------------------------------\nbool\nQdbMaster::AcquireLeaseWithDelay()\n{\n  bool is_master = false;\n\n  if (mAcquireDelay != 0) {\n    if (mAcquireDelay >= time(nullptr)) {\n      std::this_thread::sleep_for(std::chrono::seconds(1));\n      eos_info(\"%s\", \"msg=\\\"enforce lease acquire delay\\\"\");\n    } else {\n      mAcquireDelay = 0;\n      is_master = AcquireLease();\n    }\n  } else {\n    is_master = AcquireLease();\n  }\n\n  return is_master;\n}\n\n\n//----------------------------------------------------------------------------\n// Release lease\n//----------------------------------------------------------------------------\nvoid\nQdbMaster::ReleaseLease()\n{\n  std::future<qclient::redisReplyPtr> f = mQcl->exec(\"lease-release\", sLeaseKey);\n  qclient::redisReplyPtr reply = f.get();\n  (void) reply;\n}\n\n//----------------------------------------------------------------------------\n// Get the identity of the current lease holder\n//----------------------------------------------------------------------------\nstd::string\nQdbMaster::GetLeaseHolder()\n{\n  std::string holder;\n  std::future<qclient::redisReplyPtr> f = mQcl->exec(\"lease-get\", sLeaseKey);\n  qclient::redisReplyPtr reply = f.get();\n\n  if ((reply == nullptr) || (reply->type == REDIS_REPLY_NIL)) {\n    eos_err(\"%s\", \"msg=\\\"lease-get is NULL\\\"\");\n    return holder;\n  }\n\n  std::string reply_msg = std::string(reply->element[0]->str,\n                                      reply->element[0]->len);\n  eos_debug(\"lease-get reply: %s\", reply_msg.c_str());\n  std::string tag {\"HOLDER: \"};\n  size_t pos = reply_msg.find(tag);\n\n  if (pos == std::string::npos) {\n    return holder;\n  }\n\n  pos += tag.length();\n  size_t pos_end = reply_msg.find('\\n', pos);\n\n  if (pos_end == std::string::npos) {\n    holder = reply_msg.substr(pos);\n  } else {\n    holder = reply_msg.substr(pos, pos_end - pos + 1);\n  }\n\n  return holder;\n}\n\n//------------------------------------------------------------------------------\n// Set the new master hostname\n//------------------------------------------------------------------------------\nbool\nQdbMaster::SetMasterId(const std::string& hostname, int port,\n                       std::string& err_msg)\n{\n  using namespace std::chrono;\n  std::string new_id = hostname + std::to_string(port);\n\n  if (mIsMaster) {\n    if (new_id != mIdentity) {\n      // Introduce delay in acquiring the lease so that we give the opportunity\n      // to other nodes to become the master\n      mAcquireDelay = time(nullptr) + 2 *\n                      duration_cast<seconds>(mLeaseValidity).count();\n    }\n  } else {\n    err_msg = \"error: currently this node is not acting as a master\";\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Check if remove master is OK\n//----------------------------------------------------------------------------\nbool\nQdbMaster::IsRemoteMasterOk() const\n{\n  std::string master_id = GetMasterId();\n\n  // If we're master or remote master id is empty then fail\n  if ((mIsMaster && (master_id == mIdentity)) || master_id.empty()) {\n    return false;\n  }\n\n  std::ostringstream oss;\n  oss << \"root://\" << master_id << \"//dummy?xrd.wantprot=sss,unix\";\n  XrdCl::URL url(oss.str());\n\n  if (!url.IsValid()) {\n    eos_err(\"msg=\\\"invalid remote master\\\" id=%s\", master_id.c_str());\n    return false;\n  }\n\n  // Check if node is reachable\n  XrdCl::FileSystem fs(url);\n  XrdCl::XRootDStatus st = fs.Ping(1);\n\n  if (!st.IsOK()) {\n    eos_err(\"msg=\\\"remote master not reachable\\\" id=%s\", master_id.c_str());\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Show the current master/slave run configuration (used by ns stat)\n//------------------------------------------------------------------------------\nstd::string\nQdbMaster::PrintOut()\n{\n  std::ostringstream oss;\n  oss << \"is_master=\" << (mIsMaster ? \"true\" : \"false\")\n      << \" master_id=\" << GetMasterId();\n  return oss.str();\n}\n\n//------------------------------------------------------------------------------\n// Disable namespace caching\n//------------------------------------------------------------------------------\nvoid\nQdbMaster::DisableNsCaching()\n{\n  std::map<std::string, std::string> map_cfg;\n  map_cfg[constants::sMaxNumCacheFiles] = \"0\";\n  map_cfg[constants::sMaxNumCacheDirs] = \"0\";\n  gOFS->eosFileService->configure(map_cfg);\n  gOFS->eosDirectoryService->configure(map_cfg);\n}\n\n//------------------------------------------------------------------------------\n// Enable namespace caching with default values\n//------------------------------------------------------------------------------\nvoid\nQdbMaster::EnableNsCaching()\n{\n  std::map<std::string, std::string> map_cfg;\n  FillNsCacheConfig(gOFS->mConfigEngine, map_cfg);\n  gOFS->eosFileService->configure(map_cfg);\n  gOFS->eosDirectoryService->configure(map_cfg);\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/qdbmaster/QdbMaster.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file QdbMaster.hh\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @brief Master interface for Qdb implementation\n//------------------------------------------------------------------------------\n#pragma once\n#include \"mgm/imaster/IMaster.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n\n//! Forward declaration\nnamespace qclient\n{\nclass QClient;\n}\n\nEOSMGMNAMESPACE_BEGIN\n\n#define POST_SLAVE_TO_MASTER_TIMEOUT 60\n\n//------------------------------------------------------------------------------\n//! Class IMaster\n//------------------------------------------------------------------------------\nclass QdbMaster: public IMaster\n{\npublic:\n  static std::string sLeaseKey;\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param qdb_info contact details for QDB cluster\n  //! @param host_port hostname:port of the current mgm\n  //----------------------------------------------------------------------------\n  QdbMaster(const eos::QdbContactDetails& qdb_info,\n            const std::string& host_port);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~QdbMaster();\n\n  //----------------------------------------------------------------------------\n  //! Copy constructor\n  //----------------------------------------------------------------------------\n  QdbMaster(const QdbMaster& other) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Copy assignment operator\n  //----------------------------------------------------------------------------\n  QdbMaster& operator=(const QdbMaster&) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Init method to determine the current master/slave state\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool Init() override;\n\n  //----------------------------------------------------------------------------\n  //! Boot namespace\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool BootNamespace() override;\n\n  //----------------------------------------------------------------------------\n  //! Apply configuration setting\n  //!\n  //! @param stdOut output string\n  //! @param stdErr output error string\n  //! @param apply_stall_rdr if true then apply the stall and redirection\n  //!        rules from the configuration\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ApplyMasterConfig(std::string& stdOut, std::string& stdErr,\n                         bool apply_stall_rdr) override;\n\n  //----------------------------------------------------------------------------\n  //! Check if we are the master host\n  //!\n  //! @return true if master, otherwise false\n  //----------------------------------------------------------------------------\n  bool IsMaster() override\n  {\n    return mIsMaster;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if remove master is OK\n  //!\n  //! @return true if OK, otherwise false\n  //----------------------------------------------------------------------------\n  bool IsRemoteMasterOk() const override;\n\n  //----------------------------------------------------------------------------\n  //! Get current master hostname\n  //----------------------------------------------------------------------------\n  const std::string GetMasterId() const override\n  {\n    std::unique_lock<std::mutex> lock(mMutexId);\n    return mMasterIdentity;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set the new master hostname\n  //!\n  //! @param hostname new master hostname\n  //! @param port new master port, default 1094\n  //! @param err_msg error message\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool SetMasterId(const std::string& hostname, int port,\n                   std::string& err_msg) override;\n\n  //----------------------------------------------------------------------------\n  //! Return a delay time for balancing & draining since after a transition\n  //! we don't know the maps of already scheduled ID's and we have to make\n  //! sure not to reissue a transfer too early!\n  //----------------------------------------------------------------------------\n  size_t GetServiceDelay() override\n  {\n    // @todo (esindril): this needs to be properly implemented\n    return 0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get master log\n  //----------------------------------------------------------------------------\n  void GetLog(std::string& stdOut) override\n  {\n    stdOut = mLog;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Show the current master/slave run configuration (used by ns stat)\n  //!\n  //! @return string describing the status\n  //----------------------------------------------------------------------------\n  std::string PrintOut() override;\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Update the current master id\n  //!\n  //! @param master_id current master id\n  //----------------------------------------------------------------------------\n  inline void UpdateMasterId(const std::string& master_id)\n  {\n    std::unique_lock<std::mutex> lock(mMutexId);\n    mMasterIdentity = master_id;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Method supervising the master/slave status\n  //!\n  //! @param assistant thread executing the method\n  //----------------------------------------------------------------------------\n  void Supervisor(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Try to acquire lease\n  //!\n  //! @param validity_msec validity in milliseconds of the lease\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool AcquireLease(uint64_t validity_msec = 0ull);\n\n  //----------------------------------------------------------------------------\n  //! Try to acquire lease with delay. If the mAcquireDelay timestamp is set\n  //! then we skip trying to acquire the lease until the delay has expired.\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool AcquireLeaseWithDelay();\n\n  //----------------------------------------------------------------------------\n  //! Release lease\n  //----------------------------------------------------------------------------\n  void ReleaseLease();\n\n  //----------------------------------------------------------------------------\n  //! Get the identity of the current lease holder\n  //!\n  //! @return identity string or empty string if noone holds the leas\n  //----------------------------------------------------------------------------\n  std::string GetLeaseHolder();\n\n  //----------------------------------------------------------------------------\n  //! Slave to master transition\n  //----------------------------------------------------------------------------\n  void SlaveToMaster();\n\n  //----------------------------------------------------------------------------\n  //! Run a post slave to master hook, if configured\n  //----------------------------------------------------------------------------\n  void PostSlaveToMaster(std::string old_master, std::string new_master);\n\n  //----------------------------------------------------------------------------\n  //! Master to slave transition\n  //----------------------------------------------------------------------------\n  void MasterToSlave();\n\n  //----------------------------------------------------------------------------\n  //! Disable namespace caching\n  //----------------------------------------------------------------------------\n  void DisableNsCaching();\n\n  //----------------------------------------------------------------------------\n  //! Enable namespace caching with default values\n  //----------------------------------------------------------------------------\n  void EnableNsCaching();\n\n  //----------------------------------------------------------------------------\n  //! Configure QDB lease timeouts/validity\n  //!\n  //! @param master_init_lease lease timeout used during a transition\n  //----------------------------------------------------------------------------\n  void ConfigureTimeouts(uint64_t& master_init_lease);\n\n  std::atomic<bool> mOneOff; ///< Flag to mark that supervisor ran once\n  std::string mIdentity; ///< MGM identity hostname:port\n  mutable std::mutex mMutexId; ///< Mutex for the master identity\n  std::string mMasterIdentity; ///< Current master host\n  std::atomic<bool> mIsMaster; ///< Mark if current instance is master\n  std::atomic<bool> mConfigLoaded; ///< Mark if configuration is loaded\n  //! Timepoint until when to delay the acquiring of the lease - so that we\n  //! give the chance to other MGMs to become masters\n  std::atomic<time_t> mAcquireDelay;\n  AssistedThread mThread; ///< Supervisor thread updating master/slave state\n  std::unique_ptr<qclient::QClient>\n  mQcl; ///< qclient for talking to the QDB cluster\n  //! Time for which a lease is aquired\n  std::chrono::milliseconds mLeaseValidity {10000};\n  //! Flag to mark that the master transition needs to apply a delay before\n  //! accepting new requests\n  std::atomic<bool> mDoMasterDelay = false;\n  //! Point in time until new requests are stalled after a transition to\n  //! master role.\n  std::chrono::time_point<std::chrono::system_clock> mMasterDelayDeadline;\n  //! Delay in seconds before a master accepts new requests\n  static std::chrono::seconds sMasterDelaySec;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/quota/#Quota.cc#",
    "content": "// ----------------------------------------------------------------------\n// File: Quota.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/policy/Policy.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/table_formatter/TableFormatterBase.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/ns_quarkdb/NamespaceGroup.hh\"\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n#include \"mgm/config/IConfigEngine.hh\"\n#include <errno.h>\n\nEOSMGMNAMESPACE_BEGIN\n\nstd::map<std::string, SpaceQuota*> Quota::pMapQuota;\nstd::map<eos::IContainerMD::id_t, SpaceQuota*> Quota::pMapInodeQuota;\neos::common::RWMutex Quota::pMapMutex;\ngid_t Quota::gProjectId = 99;\n\n#ifdef __APPLE__\n#define ENONET 64\n#endif\n\n//------------------------------------------------------------------------------\n// *** Class SpaceQuota implementaion ***\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Constructor - requires the eosViewRWMutex write-lock\n//------------------------------------------------------------------------------\nSpaceQuota::SpaceQuota(const char* path):\n  eos::common::LogId(),\n  pPath(path),\n  mQuotaNode(nullptr),\n  mLastEnableCheck(0),\n  mLastRefresh(0),\n  mLayoutSizeFactor(1.0),\n  mDirtyTarget(true)\n{\n  std::shared_ptr<eos::IContainerMD> quotadir;\n\n  try {\n    quotadir = gOFS->eosView->getContainer(path);\n  } catch (const eos::MDException& e) {\n    eos_err(\"No such path=%s\", path);\n  }\n\n  if (quotadir == nullptr) {\n    try {\n      quotadir = gOFS->eosView->createContainer(path, true);\n      quotadir->setMode(S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | S_IFDIR);\n      gOFS->eosView->updateContainerStore(quotadir.get());\n    } catch (eos::MDException& e) {\n      eos_crit(\"Cannot create quota directory %s\", path);\n      throw;\n    }\n  }\n\n  if (quotadir) {\n    try {\n      mQuotaNode = gOFS->eosView->getQuotaNode(quotadir.get(), false);\n\n      if (mQuotaNode) {\n        eos_info(\"Found ns quota node for path=%s\", path);\n      } else {\n        eos_info(\"No ns quota found for path=%s\", path);\n      }\n    } catch (const eos::MDException& e) {\n      mQuotaNode = nullptr;\n    }\n\n    if (!mQuotaNode) {\n      try {\n        mQuotaNode = gOFS->eosView->registerQuotaNode(quotadir.get());\n      } catch (eos::MDException& e) {\n        mQuotaNode = nullptr;\n        eos_crit(\"Cannot register quota node %s, errmsg=%s\",\n                 path, e.what());\n        throw;\n      }\n    }\n\n    UpdateLogicalSizeFactor();\n\n  } else {\n    eos_crit(\"Failed to create quota dir=%s\", path);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get quota status\n//------------------------------------------------------------------------------\nconst char*\nSpaceQuota::GetQuotaStatus(unsigned long long is, unsigned long long avail)\n{\n  if (!avail) {\n    return \"ignored\";\n  }\n\n  double p = (100.0 * is / avail);\n\n  if (p < 90) {\n    return \"ok\";\n  } else if (p < 99) {\n    return \"warning\";\n  } else {\n    return \"exceeded\";\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get current quota value as percentage of the available one\n//------------------------------------------------------------------------------\nfloat\nSpaceQuota::GetQuotaPercentage(unsigned long long is, unsigned long long avail)\n{\n  float fp = avail ? (100.0 * is / avail) : 100.0;\n\n  if (fp > 100.0) {\n    fp = 100.0;\n  }\n\n  if (fp < 0) {\n    fp = 0;\n  }\n\n  return fp;\n}\n\n//------------------------------------------------------------------------------\n// Update ns quota node address referred to by current space quota\n//------------------------------------------------------------------------------\nbool\nSpaceQuota::UpdateQuotaNodeAddress()\n{\n  try {\n    std::shared_ptr<eos::IContainerMD> quotadir =\n      gOFS->eosView->getContainer(pPath.c_str());\n    mQuotaNode = gOFS->eosView->getQuotaNode(quotadir.get(), false);\n\n    if (!mQuotaNode) {\n      return false;\n    }\n  } catch (eos::MDException& e) {\n    mQuotaNode = nullptr;\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Calculate the size factor used to estimate the logical available bytes\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::UpdateLogicalSizeFactor()\n{\n  XrdOucErrInfo error;\n  eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n  vid.sudoer = 1;\n  eos::IContainerMD::XAttrMap map;\n  int retc = gOFS->_attr_ls(pPath.c_str(), error, vid, 0, map);\n\n  if (!retc) {\n    unsigned long layoutId;\n    XrdOucEnv env;\n    unsigned long forcedfsid;\n    long forcedgroup;\n    std::string bandwidth;\n    std::string spn = pPath; // Is this necessary?\n    bool schedule = false;\n    std::string iopriority;\n    std::string iotype;\n    // get the layout in this quota node\n    Policy::GetLayoutAndSpace(pPath.c_str(), map, vid, layoutId, spn, env,\n                              forcedfsid, forcedgroup, bandwidth, schedule, iopriority, iotype, false);\n    mLayoutSizeFactor = eos::common::LayoutId::GetSizeFactor(layoutId);\n  } else {\n    mLayoutSizeFactor = 1.0;\n  }\n\n  // Protect for division by 0\n  if (mLayoutSizeFactor < 1.0) {\n    mLayoutSizeFactor = 1.0;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Remove quota\n//------------------------------------------------------------------------------\nbool\nSpaceQuota::RmQuota(unsigned long tag, unsigned long id)\n{\n  eos_debug(\"rm quota tag=%lu id=%lu\", tag, id);\n  XrdSysMutexHelper scope_lock(mMutex);\n  bool erased = mMapIdQuota.erase(Index(tag, id));\n\n  if (erased) {\n    mDirtyTarget = true;\n  }\n\n  return erased;\n}\n\n//------------------------------------------------------------------------------\n// Get quota value\n//------------------------------------------------------------------------------\nlong long\nSpaceQuota::GetQuota(unsigned long tag, unsigned long id)\n{\n  XrdSysMutexHelper scope_lock(mMutex);\n  auto it = mMapIdQuota.find(Index(tag, id));\n\n  if (it != mMapIdQuota.end()) {\n    return static_cast<long long>(it->second);\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Set quota\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::SetQuota(unsigned long tag, unsigned long id,\n                     unsigned long long value)\n{\n  eos_debug(\"set quota tag=%lu id=%lu value=%llu\", tag, id, value);\n  XrdSysMutexHelper scope_lock(mMutex);\n  mMapIdQuota[Index(tag, id)] = value;\n\n  switch(tag) {\n    case kUserBytesTarget:\n    mMapIdQuota.try_emplace(Index(kUserLogicalBytesTarget, id), value / mLayoutSizeFactor);\n    break;\n\n    case kGroupBytesTarget:\n    mMapIdQuota.try_emplace(Index(kGroupLogicalBytesTarget, id), value / mLayoutSizeFactor);\n    break;\n  }\n\n  if ((tag == kUserBytesTarget) ||\n      (tag == kGroupBytesTarget) ||\n      (tag == kUserFilesTarget) ||\n      (tag == kGroupFilesTarget) ||\n      (tag == kUserLogicalBytesTarget) ||\n      (tag == kGroupLogicalBytesTarget)) {\n    mDirtyTarget = true;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Reset quota\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::ResetQuota(unsigned long tag, unsigned long id)\n{\n  mMapIdQuota[Index(tag, id)] = 0;\n\n  if ((tag == kUserBytesTarget) ||\n      (tag == kGroupBytesTarget) ||\n      (tag == kUserFilesTarget) ||\n      (tag == kGroupFilesTarget) ||\n      (tag == kUserLogicalBytesTarget) ||\n      (tag == kGroupLogicalBytesTarget)) {\n    mDirtyTarget = true;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Add quota\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::AddQuota(unsigned long tag, unsigned long id, long long value)\n{\n  eos_debug(\"add quota tag=%lu id=%lu value=%llu\", tag, id, value);\n\n  // Avoid negative numbers\n  if (((long long) mMapIdQuota[Index(tag, id)] + value) >= 0) {\n    mMapIdQuota[Index(tag, id)] += value;\n  }\n\n  eos_debug(\"sum quota tag=%lu id=%lu value=%llu\", tag, id,\n            mMapIdQuota[Index(tag, id)]);\n}\n\n//------------------------------------------------------------------------------\n// Update\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::UpdateTargetSums()\n{\n  if (!mDirtyTarget) {\n    return;\n  }\n\n  eos_debug(\"%s\", \"updating targets\");\n  XrdSysMutexHelper scope_lock(mMutex);\n  mDirtyTarget = false;\n  mMapIdQuota[Index(kAllUserBytesTarget, 0)] = 0;\n  mMapIdQuota[Index(kAllUserFilesTarget, 0)] = 0;\n  mMapIdQuota[Index(kAllGroupBytesTarget, 0)] = 0;\n  mMapIdQuota[Index(kAllGroupFilesTarget, 0)] = 0;\n  mMapIdQuota[Index(kAllUserLogicalBytesTarget, 0)] = 0;\n  mMapIdQuota[Index(kAllGroupLogicalBytesTarget, 0)] = 0;\n\n  for (const auto& [tag, value] : mMapIdQuota) {\n    switch(UnIndex(tag)) {\n      case kUserBytesTarget:\n        AddQuota(kAllUserBytesTarget, 0, value);\n        break;\n      case kUserLogicalBytesTarget:\n        AddQuota(kAllUserLogicalBytesTarget, 0, value);\n        break;\n      case kUserFilesTarget:\n        AddQuota(kAllUserFilesTarget, 0, value);\n        break;\n      case kGroupBytesTarget:\n        AddQuota(kAllGroupBytesTarget, 0, value);\n        break;\n      case kGroupLogicalBytesTarget:\n        AddQuota(kAllGroupLogicalBytesTarget, 0, value);\n        break;\n      case kGroupFilesTarget:\n        AddQuota(kAllGroupFilesTarget, 0, value);\n        break;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::UpdateIsSums()\n{\n  eos_debug(\"%s\", \"updating IS values\");\n  XrdSysMutexHelper scope_lock(mMutex);\n  mMapIdQuota[Index(kAllUserBytesIs, 0)] = 0;\n  mMapIdQuota[Index(kAllUserLogicalBytesIs, 0)] = 0;\n  mMapIdQuota[Index(kAllUserFilesIs, 0)] = 0;\n  mMapIdQuota[Index(kAllGroupBytesIs, 0)] = 0;\n  mMapIdQuota[Index(kAllGroupFilesIs, 0)] = 0;\n  mMapIdQuota[Index(kAllGroupLogicalBytesIs, 0)] = 0;\n  bool has_project_quota = false;\n  auto it = mMapIdQuota.find(Index(kGroupLogicalBytesTarget, Quota::gProjectId));\n\n  if ((it != mMapIdQuota.end()) && it->second) {\n    has_project_quota = true;\n  }\n\n  // If project quota is defined for the current quota node then use that\n  // value to avoid possible double counting\n  if (has_project_quota) {\n    AddQuota(kAllGroupFilesIs, 0,\n             mMapIdQuota[Index(kGroupFilesIs, Quota::gProjectId)]);\n    AddQuota(kAllGroupBytesIs, 0,\n             mMapIdQuota[Index(kGroupBytesIs, Quota::gProjectId)]);\n    AddQuota(kAllGroupLogicalBytesIs, 0,\n             mMapIdQuota[Index(kGroupLogicalBytesIs, Quota::gProjectId)]);\n  } else {\n    for (auto it = mMapIdQuota.begin(); it != mMapIdQuota.end(); it++) {\n      if ((UnIndex(it->first) == kUserBytesIs)) {\n        AddQuota(kAllUserBytesIs, 0, it->second);\n      }\n\n      if ((UnIndex(it->first) == kUserLogicalBytesIs)) {\n        AddQuota(kAllUserLogicalBytesIs, 0, it->second);\n      }\n\n      if ((UnIndex(it->first) == kUserFilesIs)) {\n        AddQuota(kAllUserFilesIs, 0, it->second);\n      }\n\n      if ((UnIndex(it->first) == kGroupFilesIs)) {\n        AddQuota(kAllGroupFilesIs, 0, it->second);\n      }\n\n      if ((UnIndex(it->first) == kGroupBytesIs)) {\n        AddQuota(kAllGroupBytesIs, 0, it->second);\n      }\n\n      if ((UnIndex(it->first) == kGroupLogicalBytesIs)) {\n        AddQuota(kAllGroupLogicalBytesIs, 0, it->second);\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Update uid/gid values from quota node\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::UpdateFromQuotaNode(uid_t uid, gid_t gid, bool upd_proj_quota)\n{\n  eos_debug(\"%s\", \"updating uid/gid values from quota node\");\n  XrdSysMutexHelper scope_lock(mMutex);\n\n  if (mQuotaNode) {\n    mMapIdQuota[Index(kUserBytesIs, uid)] = 0;\n    mMapIdQuota[Index(kUserLogicalBytesIs, uid)] = 0;\n    mMapIdQuota[Index(kUserFilesIs, uid)] = 0;\n    mMapIdQuota[Index(kGroupBytesIs, gid)] = 0;\n    mMapIdQuota[Index(kGroupFilesIs, gid)] = 0;\n    mMapIdQuota[Index(kGroupLogicalBytesIs, gid)] = 0;\n    AddQuota(kUserBytesIs, uid, mQuotaNode->getPhysicalSpaceByUser(uid));\n    AddQuota(kUserLogicalBytesIs, uid, mQuotaNode->getUsedSpaceByUser(uid));\n    AddQuota(kUserFilesIs, uid, mQuotaNode->getNumFilesByUser(uid));\n    AddQuota(kGroupBytesIs, gid, mQuotaNode->getPhysicalSpaceByGroup(gid));\n    AddQuota(kGroupLogicalBytesIs, gid, mQuotaNode->getUsedSpaceByGroup(gid));\n    AddQuota(kGroupFilesIs, gid, mQuotaNode->getNumFilesByGroup(gid));\n    mMapIdQuota[Index(kUserBytesIs, Quota::gProjectId)] = 0;\n    mMapIdQuota[Index(kUserLogicalBytesIs, Quota::gProjectId)] = 0;\n    mMapIdQuota[Index(kUserFilesIs, Quota::gProjectId)] = 0;\n\n    if (upd_proj_quota) {\n      // Recalculate the project quota only every 5 seconds to boost perf.\n      static XrdSysMutex lMutex;\n      static time_t lUpdateTime = 0;\n      bool docalc = false;\n      {\n        XrdSysMutexHelper lock(lMutex);\n        time_t now = time(NULL);\n\n        if (lUpdateTime < now) {\n          // Next recalculation in 5 second\n          docalc = true;\n          lUpdateTime = now + 5;\n        }\n      }\n\n      if (docalc || (gid == Quota::gProjectId)) {\n        mMapIdQuota[Index(kGroupBytesIs, Quota::gProjectId)] = 0;\n        mMapIdQuota[Index(kGroupFilesIs, Quota::gProjectId)] = 0;\n        mMapIdQuota[Index(kGroupLogicalBytesIs, Quota::gProjectId)] = 0;\n        // Loop over users and fill project quota\n        auto uids = mQuotaNode->getUids();\n\n        for (auto itu = uids.begin(); itu != uids.end(); ++itu) {\n          AddQuota(kGroupBytesIs, Quota::gProjectId,\n                   mQuotaNode->getPhysicalSpaceByUser(*itu));\n          AddQuota(kGroupLogicalBytesIs, Quota::gProjectId,\n                   mQuotaNode->getUsedSpaceByUser(*itu));\n          AddQuota(kGroupFilesIs, Quota::gProjectId, mQuotaNode->getNumFilesByUser(*itu));\n        }\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Refresh counters\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::Refresh(time_t age)\n{\n  time_t now = time(NULL);\n\n  // since this loads all quota node info all the time, we don't do this for GetIndividualQuota in realtime all the time\n  if (age) {\n    if ((now - age) < mLastRefresh) {\n      return;\n    }\n  }\n\n  mLastRefresh = now;\n  AccountNsToSpace();\n  UpdateLogicalSizeFactor();\n  UpdateIsSums();\n  UpdateTargetSums();\n}\n\n\n//------------------------------------------------------------------------------\n// Print quota information\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::PrintOut(XrdOucString& output, long long int uid_sel,\n                     long long int gid_sel, bool monitoring, bool translate_ids)\n{\n  using eos::common::StringConversion;\n  // Make a map containing once all the defined uid's and gid's\n  std::vector<std::pair<std::string, unsigned>> uids, gids;\n  {\n    XrdSysMutexHelper scope_lock(mMutex);\n\n    // For project space we just print the user/group entry gProjectId\n    if (mMapIdQuota[Index(kGroupBytesTarget, Quota::gProjectId)] > 0) {\n      gid_sel = Quota::gProjectId;\n    }\n\n    for (auto it = mMapIdQuota.begin(); it != mMapIdQuota.end(); ++it) {\n      if ((UnIndex(it->first) >= kUserBytesIs) &&\n          (UnIndex(it->first) <= kUserFilesTarget)) {\n        long long int uid = (long long int)((it->first) & 0xffffffff);\n\n        // uid selection filter\n        if ((uid_sel >= 0LL) && (uid != uid_sel)) {\n          continue;\n        }\n\n        // we don't print the users if a gid is selected\n        if (gid_sel >= 0LL) {\n          continue;\n        }\n\n        // Translate IDs\n        std::string name = std::to_string(uid).c_str();\n        uids.push_back(std::make_pair(name, uid));\n      }\n\n      if ((UnIndex(it->first) >= kGroupBytesIs) &&\n          (UnIndex(it->first) <= kGroupFilesTarget)) {\n        long long int gid = (it->first) & 0xfffffff;\n\n        // uid selection filter\n        if ((gid_sel >= 0LL) && (gid != gid_sel)) {\n          continue;\n        }\n\n        // We don't print the group if a uid is selected\n        if (uid_sel >= 0LL) {\n          continue;\n        }\n\n        // Translate IDs\n        std::string name = std::to_string(gid).c_str();\n        gids.push_back(std::make_pair(name, gid));\n      }\n    }\n  }\n\n  // translate ids without mutex held\n  if (translate_ids) {\n    std::string name;\n    std::vector<std::pair<std::string, unsigned>> tuids, tgids;\n\n    for (auto u : uids) {\n      if (gid_sel == Quota::gProjectId) {\n        name = \"project\";\n      } else {\n        int errc = 0;\n        name = eos::common::Mapping::UidToUserName(u.second, errc);\n      }\n\n      tuids.push_back(std::make_pair(name, u.second));\n    }\n\n    for (auto g : gids) {\n      if (gid_sel == Quota::gProjectId) {\n        name = \"project\";\n      } else {\n        int errc = 0;\n        name = eos::common::Mapping::GidToGroupName(g.second, errc);\n      }\n\n      tgids.push_back(std::make_pair(name, g.second));\n    }\n\n    uids = tuids;\n    gids = tgids;\n  }\n\n  // Sort and erase duplicated uids and gids\n  eos_info(\"uids_size=%i, gids_size=%i\", uids.size(), gids.size());\n  std::sort(uids.begin(), uids.end());\n  uids.erase(std::unique(uids.begin(), uids.end()), uids.end());\n  std::sort(gids.begin(), gids.end());\n  gids.erase(std::unique(gids.begin(), gids.end()), gids.end());\n\n  if (EOS_LOGS_DEBUG) {\n    eos_debug(\"%s\",\"sorted\");\n\n    for (unsigned int i = 0; i < uids.size(); ++i) {\n      eos_debug(\"sort %d %d\", i, uids[i].second);\n    }\n\n    for (unsigned int i = 0; i < gids.size(); ++i) {\n      eos_debug(\"sort %d %d\", i, gids[i].second);\n    }\n  }\n\n  // Print the header for selected uid/gid's only if there is something to print\n  // If we have a full listing we print even empty quota nodes (=header only)\n  if (!monitoring) {\n    if (((uid_sel < 0) && (gid_sel < 0)) || !uids.empty() || !gids.empty()) {\n      output += \"\\n┏━> Quota Node: \";\n      output += pPath.c_str();\n      output += \"\\n\";\n    }\n  }\n\n  //! Quota node - Users\n  TableFormatterBase table_user;\n\n  if (!uids.empty()) {\n    // Table header\n    if (!monitoring) {\n      table_user.SetHeader({\n        std::make_tuple(GetTagCategory(kUserBytesIs), 10, \"-s\"),\n        std::make_tuple(GetTagName(kUserBytesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kUserLogicalBytesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kUserFilesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kUserBytesTarget), 10, \"+l\"),\n        std::make_tuple(GetTagName(kUserLogicalBytesTarget), 10, \"+l\"),\n        std::make_tuple(GetTagName(kUserFilesTarget), 10, \"+l\"),\n        std::make_tuple(\"filled[%]\", 10, \"f\"),\n        std::make_tuple(\"vol-status\", 10, \"s\"),\n        std::make_tuple(\"ino-status\", 10, \"s\")\n      });\n    } else {\n      table_user.SetHeader({\n        std::make_tuple(\"quota\", 0, \"os\"),\n        std::make_tuple(\"uid\", 0, \"os\"),\n        std::make_tuple(\"space\", 0, \"os\"),\n        std::make_tuple(\"usedbytes\", 0, \"ol\"),\n        std::make_tuple(\"usedlogicalbytes\", 0, \"ol\"),\n        std::make_tuple(\"usedfiles\", 0, \"ol\"),\n        std::make_tuple(\"maxbytes\", 0, \"ol\"),\n        std::make_tuple(\"maxlogicalbytes\", 0, \"ol\"),\n        std::make_tuple(\"maxfiles\", 0, \"ol\"),\n        std::make_tuple(\"percentageusedbytes\", 0, \"of\"),\n        std::make_tuple(\"statusbytes\", 0, \"os\"),\n        std::make_tuple(\"statusfiles\", 0, \"os\")\n      });\n    }\n\n    for (const auto& [name, id] : uids) {\n      eos_debug(\"loop with id=%d\", id);\n      TableData table_data;\n      table_data.emplace_back();\n\n      if (!monitoring) {\n        table_data.back().push_back(TableCell(name.c_str(), \"-s\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserBytesIs, id), \"+l\", \"B\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserLogicalBytesIs, id), \"+l\", \"B\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserFilesIs, id), \"+l\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserBytesTarget, id), \"+l\", \"B\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserLogicalBytesTarget, id), \"+l\", \"B\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserFilesTarget, id), \"+l\"));\n        table_data.back().push_back(TableCell(GetQuotaPercentage(\n                                                GetQuota(kUserLogicalBytesIs, id), GetQuota(kUserLogicalBytesTarget, id)), \"f\", \"%\"));\n        table_data.back().push_back(TableCell(GetQuotaStatus(\n                                                GetQuota(kUserLogicalBytesIs, id), GetQuota(kUserLogicalBytesTarget, id)), \"s\"));\n        table_data.back().push_back(TableCell(GetQuotaStatus(\n                                                GetQuota(kUserFilesIs, id), GetQuota(kUserFilesTarget, id)), \"s\"));\n      } else {\n        table_data.back().push_back(TableCell(\"node\", \"os\"));\n        table_data.back().push_back(TableCell(name.c_str(), \"os\"));\n        table_data.back().push_back(TableCell(pPath.c_str(), \"os\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserBytesIs, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserLogicalBytesIs, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserFilesIs, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserBytesTarget, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserLogicalBytesTarget, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserFilesTarget, id), \"ol\"));\n        table_data.back().push_back(TableCell(GetQuotaPercentage(\n                                                GetQuota(kUserLogicalBytesIs, id), GetQuota(kUserLogicalBytesTarget, id)), \"of\"));\n        table_data.back().push_back(TableCell(GetQuotaStatus(\n                                                GetQuota(kUserLogicalBytesIs, id), GetQuota(kUserLogicalBytesTarget, id)), \"os\"));\n        table_data.back().push_back(TableCell(GetQuotaStatus(\n                                                GetQuota(kUserFilesIs, id), GetQuota(kUserFilesTarget, id)), \"os\"));\n      }\n\n      table_user.AddRows(table_data);\n    }\n  }\n\n  if ((uid_sel < 0) && (gid_sel < 0)) {\n    output += table_user.GenerateTable(HEADER).c_str();\n  } else {\n    output += table_user.GenerateTable(HEADER2).c_str();\n  }\n\n  //! Quota node - Group\n  TableFormatterBase table_group;\n\n  if (!gids.empty()) {\n    // group loop\n    if (!monitoring) {\n      table_group.SetHeader({\n        std::make_tuple(GetTagCategory(kGroupBytesIs), 10, \"-s\"),\n        std::make_tuple(GetTagName(kGroupBytesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kGroupLogicalBytesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kGroupFilesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kGroupBytesTarget), 10, \"+l\"),\n        std::make_tuple(GetTagName(kGroupLogicalBytesTarget), 10, \"+l\"),\n        std::make_tuple(GetTagName(kGroupFilesTarget), 10, \"+l\"),\n        std::make_tuple(\"filled[%]\", 10, \"f\"),\n        std::make_tuple(\"vol-status\", 10, \"s\"),\n        std::make_tuple(\"ino-status\", 10, \"s\")\n      });\n    } else {\n      table_group.SetHeader({\n        std::make_tuple(\"quota\", 0, \"os\"),\n        std::make_tuple(\"gid\", 0, \"os\"),\n        std::make_tuple(\"space\", 0, \"os\"),\n        std::make_tuple(\"usedbytes\", 0, \"ol\"),\n        std::make_tuple(\"usedlogicalbytes\", 0, \"ol\"),\n        std::make_tuple(\"usedfiles\", 0, \"ol\"),\n        std::make_tuple(\"maxbytes\", 0, \"ol\"),\n        std::make_tuple(\"maxlogicalbytes\", 0, \"ol\"),\n        std::make_tuple(\"maxfiles\", 0, \"ol\"),\n        std::make_tuple(\"percentageusedbytes\", 0, \"of\"),\n        std::make_tuple(\"statusbytes\", 0, \"os\"),\n        std::make_tuple(\"statusfiles\", 0, \"os\")\n      });\n    }\n\n    for (const auto& [name, id] : gids) {\n      eos_debug(\"loop with id=%d\", id);\n      TableData table_data;\n      table_data.emplace_back();\n\n      if (!monitoring) {\n        table_data.back().push_back(TableCell(name.c_str(), \"-s\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupBytesIs, id), \"+l\", \"B\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupLogicalBytesIs, id), \"+l\", \"B\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupFilesIs, id), \"+l\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupBytesTarget, id), \"+l\", \"B\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupLogicalBytesTarget, id), \"+l\", \"B\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupFilesTarget, id), \"+l\"));\n        table_data.back().push_back(TableCell(GetQuotaPercentage(\n                                                GetQuota(kGroupLogicalBytesIs, id), GetQuota(kGroupLogicalBytesTarget, id)), \"f\", \"%\"));\n        table_data.back().push_back(TableCell(GetQuotaStatus(\n                                                GetQuota(kGroupLogicalBytesIs, id), GetQuota(kGroupLogicalBytesTarget, id)), \"s\"));\n        table_data.back().push_back(TableCell(GetQuotaStatus(\n                                                GetQuota(kGroupFilesIs, id), GetQuota(kGroupFilesTarget, id)), \"s\"));\n      } else {\n        table_data.back().push_back(TableCell(\"node\", \"os\"));\n        table_data.back().push_back(TableCell(name.c_str(), \"os\"));\n        table_data.back().push_back(TableCell(pPath.c_str(), \"os\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupBytesIs, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupLogicalBytesIs, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupFilesIs, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupBytesTarget, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupLogicalBytesTarget, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupFilesTarget, id), \"ol\"));\n        table_data.back().push_back(TableCell(GetQuotaPercentage(\n                                                GetQuota(kGroupLogicalBytesIs, id), GetQuota(kGroupLogicalBytesTarget, id)), \"of\"));\n        table_data.back().push_back(TableCell(GetQuotaStatus(\n                                                GetQuota(kGroupLogicalBytesIs, id), GetQuota(kGroupLogicalBytesTarget, id)), \"os\"));\n        table_data.back().push_back(TableCell(GetQuotaStatus(\n                                                GetQuota(kGroupFilesIs, id), GetQuota(kGroupFilesTarget, id)), \"os\"));\n      }\n\n      table_group.AddRows(table_data);\n    }\n  }\n\n  if ((uid_sel < 0) && (gid_sel < 0)) {\n    output += table_group.GenerateTable(HEADER).c_str();\n  } else {\n    output += table_group.GenerateTable(HEADER2).c_str();\n  }\n\n  //! Quota node - Summary\n  if ((uid_sel < 0) && (gid_sel < 0)) {\n    if (!monitoring) {\n      TableFormatterBase table_summary;\n      table_summary.SetHeader({\n        std::make_tuple(\"summary\", 10, \"-s\"),\n        std::make_tuple(GetTagName(kAllUserBytesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kAllUserLogicalBytesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kAllUserFilesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kAllUserBytesTarget), 10, \"+l\"),\n        std::make_tuple(GetTagName(kAllUserLogicalBytesTarget), 10, \"+l\"),\n        std::make_tuple(GetTagName(kAllUserFilesTarget), 10, \"+l\"),\n        std::make_tuple(\"filled[%]\", 10, \"f\"),\n        std::make_tuple(\"vol-status\", 10, \"s\"),\n        std::make_tuple(\"ino-status\", 10, \"s\")\n      });\n      TableData table_data;\n      table_data.emplace_back();\n      table_data.back().push_back(TableCell(\"All users\", \"-s\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserBytesIs, 0), \"+l\", \"B\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserLogicalBytesIs, 0), \"+l\", \"B\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserFilesIs, 0), \"+l\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserBytesTarget, 0), \"+l\", \"B\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserLogicalBytesTarget, 0), \"+l\", \"B\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserFilesTarget, 0), \"+l\"));\n      table_data.back().push_back(TableCell(GetQuotaPercentage(\n                                              GetQuota(kAllUserLogicalBytesIs, 0), GetQuota(kAllUserLogicalBytesTarget, 0)), \"f\", \"%\"));\n      table_data.back().push_back(TableCell(GetQuotaStatus(\n                                              GetQuota(kAllUserLogicalBytesIs, 0), GetQuota(kAllUserLogicalBytesTarget, 0)), \"s\"));\n      table_data.back().push_back(TableCell(GetQuotaStatus(\n                                              GetQuota(kAllUserFilesIs, 0), GetQuota(kAllUserFilesTarget, 0)), \"s\"));\n      table_data.emplace_back();\n      table_data.back().push_back(TableCell(\"All groups\", \"-ls\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupBytesIs, 0), \"+l\", \"B\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupLogicalBytesIs, 0), \"+l\", \"B\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupFilesIs, 0), \"+l\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupBytesTarget, 0), \"+l\", \"B\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupLogicalBytesTarget, 0), \"+l\", \"B\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupFilesTarget, 0), \"+l\"));\n      table_data.back().push_back(TableCell(GetQuotaPercentage(\n                                              GetQuota(kAllGroupLogicalBytesIs, 0), GetQuota(kAllGroupLogicalBytesTarget, 0)), \"f\", \"%\"));\n      table_data.back().push_back(TableCell(GetQuotaStatus(\n                                              GetQuota(kAllGroupLogicalBytesIs, 0), GetQuota(kAllGroupLogicalBytesTarget, 0)), \"s\"));\n      table_data.back().push_back(TableCell(GetQuotaStatus(\n                                              GetQuota(kAllGroupFilesIs, 0), GetQuota(kAllGroupFilesTarget, 0)), \"s\"));\n      table_summary.AddRows(table_data);\n      output += table_summary.GenerateTable(HEADER2).c_str();\n    } else {\n      TableFormatterBase table_summary_user;\n      table_summary_user.SetHeader({\n        std::make_tuple(\"quota\", 0, \"os\"),\n        std::make_tuple(\"uid\", 0, \"os\"),\n        std::make_tuple(\"space\", 0, \"os\"),\n        std::make_tuple(\"usedbytes\", 0, \"ol\"),\n        std::make_tuple(\"usedlogicalbytes\", 0, \"ol\"),\n        std::make_tuple(\"usedfiles\", 0, \"ol\"),\n        std::make_tuple(\"maxbytes\", 0, \"ol\"),\n        std::make_tuple(\"maxlogicalbytes\", 0, \"ol\"),\n        std::make_tuple(\"maxfiles\", 0, \"ol\"),\n        std::make_tuple(\"percentageusedbytes\", 0, \"of\"),\n        std::make_tuple(\"statusbytes\", 0, \"os\"),\n        std::make_tuple(\"statusfiles\", 0, \"os\")\n      });\n      TableData table_data;\n      table_data.emplace_back();\n      table_data.back().push_back(TableCell(\"node\", \"os\"));\n      table_data.back().push_back(TableCell(\"ALL\", \"os\"));\n      table_data.back().push_back(TableCell(pPath.c_str(), \"os\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserBytesIs, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserLogicalBytesIs, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserFilesIs, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserBytesTarget, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserLogicalBytesTarget, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserFilesTarget, 0), \"ol\"));\n      table_data.back().push_back(TableCell(GetQuotaPercentage(\n                                              GetQuota(kAllUserLogicalBytesIs, 0), GetQuota(kAllUserLogicalBytesTarget, 0)), \"of\"));\n      table_data.back().push_back(TableCell(GetQuotaStatus(\n                                              GetQuota(kAllUserLogicalBytesIs, 0), GetQuota(kAllUserLogicalBytesTarget, 0)), \"os\"));\n      table_data.back().push_back(TableCell(GetQuotaStatus(\n                                              GetQuota(kAllUserFilesIs, 0), GetQuota(kAllUserFilesTarget, 0)), \"os\"));\n      table_summary_user.AddRows(table_data);\n      output += table_summary_user.GenerateTable().c_str();\n      TableFormatterBase table_summary_group;\n      table_summary_group.SetHeader({\n        std::make_tuple(\"quota\", 0, \"os\"),\n        std::make_tuple(\"gid\", 0, \"os\"),\n        std::make_tuple(\"space\", 0, \"os\"),\n        std::make_tuple(\"usedbytes\", 0, \"ol\"),\n        std::make_tuple(\"usedlogicalbytes\", 0, \"ol\"),\n        std::make_tuple(\"usedfiles\", 0, \"ol\"),\n        std::make_tuple(\"maxbytes\", 0, \"ol\"),\n        std::make_tuple(\"maxlogicalbytes\", 0, \"ol\"),\n        std::make_tuple(\"maxfiles\", 0, \"ol\"),\n        std::make_tuple(\"percentageusedbytes\", 0, \"of\"),\n        std::make_tuple(\"statusbytes\", 0, \"os\"),\n        std::make_tuple(\"statusfiles\", 0, \"os\")\n      });\n      table_data.clear();\n      table_data.emplace_back();\n      table_data.back().push_back(TableCell(\"node\", \"os\"));\n      table_data.back().push_back(TableCell(\"ALL\", \"os\"));\n      table_data.back().push_back(TableCell(pPath.c_str(), \"os\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupBytesIs, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupLogicalBytesIs, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupFilesIs, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupBytesTarget, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupLogicalBytesTarget, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupFilesTarget, 0), \"ol\"));\n      table_data.back().push_back(TableCell(GetQuotaPercentage(\n                                              GetQuota(kAllGroupLogicalBytesIs, 0), GetQuota(kAllGroupLogicalBytesTarget, 0)), \"of\"));\n      table_data.back().push_back(TableCell(GetQuotaStatus(\n                                              GetQuota(kAllGroupLogicalBytesIs, 0), GetQuota(kAllGroupLogicalBytesTarget, 0)), \"os\"));\n      table_data.back().push_back(TableCell(GetQuotaStatus(\n                                              GetQuota(kAllGroupFilesIs, 0), GetQuota(kAllGroupFilesTarget, 0)), \"os\"));\n      table_summary_group.AddRows(table_data);\n      output += table_summary_group.GenerateTable().c_str();\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// User/group/project quota checks. If both user and group quotas are defined,\n// then both need to be satisfied.\n//------------------------------------------------------------------------------\nbool\nSpaceQuota::CheckWriteQuota(uid_t uid, gid_t gid, long long desired_vol,\n                            unsigned int inodes)\n{\n  bool hasquota = false;\n  // Update info from the ns quota node - user, group and project quotas\n  UpdateFromQuotaNode(uid, gid, GetQuota(kGroupLogicalBytesTarget, Quota::gProjectId)\n                      ? true : false);\n  eos_info(\"uid=%d gid=%d size=%llu quota=%llu\", uid, gid, desired_vol,\n           GetQuota(kUserLogicalBytesTarget, uid));\n  bool userquota = false;\n  bool groupquota = false;\n  bool projectquota = false;\n  bool hasuserquota = false;\n  bool hasgroupquota = false;\n  bool hasprojectquota = false;\n  bool uservolumequota = false;\n  bool userinodequota = false;\n  bool groupvolumequota = false;\n  bool groupinodequota = false;\n\n  if (GetQuota(kUserLogicalBytesTarget, uid) > 0) {\n    userquota = true;\n    uservolumequota = true;\n  }\n\n  if (GetQuota(kGroupLogicalBytesTarget, gid) > 0) {\n    groupquota = true;\n    groupvolumequota = true;\n  }\n\n  if (GetQuota(kUserFilesTarget, uid) > 0) {\n    userquota = true;\n    userinodequota = true;\n  }\n\n  if (GetQuota(kGroupFilesTarget, gid) > 0) {\n    groupquota = true;\n    groupinodequota = true;\n  }\n\n  if (uservolumequota) {\n    if ((GetQuota(kUserLogicalBytesTarget, uid) - GetQuota(kUserLogicalBytesIs,\n         uid)) > (long long)desired_vol) {\n      hasuserquota = true;\n    } else {\n      hasuserquota = false;\n    }\n  }\n\n  if (userinodequota) {\n    // The +1 comes from the fact the the current file is already accounted to\n    // the ns quota by doing ns_quota->addFile previously in the open function.\n    if ((GetQuota(kUserFilesTarget, uid) - GetQuota(kUserFilesIs,\n         uid) + 1) >= inodes) {\n      if (!uservolumequota) {\n        hasuserquota = true;\n      }\n    } else {\n      hasuserquota = false;\n    }\n  }\n\n  if (groupvolumequota) {\n    if ((GetQuota(kGroupLogicalBytesTarget, gid) - GetQuota(kGroupLogicalBytesIs,\n         gid)) > desired_vol) {\n      hasgroupquota = true;\n    } else {\n      hasgroupquota = false;\n    }\n  }\n\n  if (groupinodequota) {\n    if ((GetQuota(kGroupFilesTarget, gid) - GetQuota(kGroupFilesIs,\n         gid)) > inodes) {\n      if (!groupvolumequota) {\n        hasgroupquota = true;\n      }\n    } else {\n      hasgroupquota = false;\n    }\n  }\n\n  if (((GetQuota(kGroupLogicalBytesTarget, Quota::gProjectId) -\n        GetQuota(kGroupLogicalBytesIs, Quota::gProjectId)) > desired_vol)) {\n    hasprojectquota = true;\n\n    if ((GetQuota(kGroupFilesTarget, Quota::gProjectId)) &&\n        ((GetQuota(kGroupFilesTarget, Quota::gProjectId) <\n          (GetQuota(kGroupFilesIs, Quota::gProjectId) + inodes)))) {\n      hasprojectquota = false;\n    }\n  }\n\n  if (!userquota && !groupquota) {\n    projectquota = true;\n  }\n\n  eos_info(\"userquota=%d groupquota=%d hasuserquota=%d hasgroupquota=%d \"\n           \"userinodequota=%d uservolumequota=%d projectquota=%d \"\n           \"hasprojectquota=%d\", userquota, groupquota, hasuserquota,\n           hasgroupquota, userinodequota, uservolumequota, projectquota,\n           hasprojectquota);\n\n  // If both quotas are defined we need to have both\n  if (userquota && groupquota) {\n    hasquota = hasuserquota & hasgroupquota;\n  } else {\n    hasquota = hasuserquota || hasgroupquota;\n  }\n\n  if (projectquota && hasprojectquota) {\n    hasquota = true;\n  }\n\n  // Root does not need any quota\n  if (uid == 0) {\n    hasquota = true;\n  }\n\n  return hasquota;\n}\n\n//------------------------------------------------------------------------------\n// Import ns quota values into current space quota\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::AccountNsToSpace()\n{\n  if (UpdateQuotaNodeAddress()) {\n    XrdSysMutexHelper scope_lock(mMutex);\n    // Insert current state of a single quota node into a SpaceQuota\n    ResetQuota(kGroupBytesIs, Quota::gProjectId);\n    ResetQuota(kGroupFilesIs, Quota::gProjectId);\n    ResetQuota(kGroupLogicalBytesIs, Quota::gProjectId);\n    // Loop over users\n    auto uids = mQuotaNode->getUids();\n\n    for (auto itu = uids.begin(); itu != uids.end(); ++itu) {\n      ResetQuota(kUserBytesIs, *itu);\n      AddQuota(kUserBytesIs, *itu, mQuotaNode->getPhysicalSpaceByUser(*itu));\n      ResetQuota(kUserFilesIs, *itu);\n      AddQuota(kUserFilesIs, *itu, mQuotaNode->getNumFilesByUser(*itu));\n      ResetQuota(kUserLogicalBytesIs, *itu);\n      AddQuota(kUserLogicalBytesIs, *itu, mQuotaNode->getUsedSpaceByUser(*itu));\n\n      if (mMapIdQuota[Index(kGroupBytesTarget, Quota::gProjectId)] > 0) {\n        // Only account in project quota nodes\n        AddQuota(kGroupBytesIs, Quota::gProjectId,\n                 mQuotaNode->getPhysicalSpaceByUser(*itu));\n        AddQuota(kGroupLogicalBytesIs, Quota::gProjectId,\n                 mQuotaNode->getUsedSpaceByUser(*itu));\n        AddQuota(kGroupFilesIs, Quota::gProjectId, mQuotaNode->getNumFilesByUser(*itu));\n      }\n    }\n\n    auto gids = mQuotaNode->getGids();\n\n    for (auto itg = gids.begin(); itg != gids.end(); ++itg) {\n      // Don't update the project quota directory from the quota\n      if (*itg == Quota::gProjectId) {\n        continue;\n      }\n\n      ResetQuota(kGroupBytesIs, *itg);\n      AddQuota(kGroupBytesIs, *itg, mQuotaNode->getPhysicalSpaceByGroup(*itg));\n      ResetQuota(kGroupFilesIs, *itg);\n      AddQuota(kGroupFilesIs, *itg, mQuotaNode->getNumFilesByGroup(*itg));\n      ResetQuota(kGroupLogicalBytesIs, *itg);\n      AddQuota(kGroupLogicalBytesIs, *itg, mQuotaNode->getUsedSpaceByGroup(*itg));\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Convert int tag to string representation\n//------------------------------------------------------------------------------\nconst char* SpaceQuota::GetTagAsString(int tag)\n{\n  if (tag == kUserBytesTarget) {\n    return \"userbytes\";\n  }\n\n  if (tag == kUserLogicalBytesTarget) {\n    return \"userlogicalbytes\";\n  }\n\n  if (tag == kUserFilesTarget) {\n    return \"userfiles\";\n  }\n\n  if (tag == kGroupBytesTarget) {\n    return \"groupbytes\";\n  }\n\n  if (tag == kGroupLogicalBytesTarget) {\n    return \"grouplogicalbytes\";\n  }\n\n  if (tag == kGroupFilesTarget) {\n    return \"groupfiles\";\n  }\n\n  if (tag == kAllUserBytesTarget) {\n    return \"alluserbytes\";\n  }\n\n  if (tag == kAllUserLogicalBytesTarget) {\n    return \"alluserlogicalbytes\";\n  }\n\n  if (tag == kAllUserFilesTarget) {\n    return \"alluserfiles\";\n  }\n\n  if (tag == kAllGroupBytesTarget) {\n    return \"allgroupbytes\";\n  }\n\n  if (tag == kAllGroupLogicalBytesTarget) {\n    return \"allgrouplogicalbytes\";\n  }\n\n  if (tag == kAllGroupFilesTarget) {\n    return \"allgroupfiles\";\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Convert string tag to int representation\n//------------------------------------------------------------------------------\nunsigned long SpaceQuota::GetTagFromString(const std::string& tag)\n{\n  if (tag == \"userbytes\") {\n    return kUserBytesTarget;\n  }\n\n  if (tag == \"userlogicalbytes\") {\n    return kUserLogicalBytesTarget;\n  }\n\n  if (tag == \"userfiles\") {\n    return kUserFilesTarget;\n  }\n\n  if (tag == \"groupbytes\") {\n    return kGroupBytesTarget;\n  }\n\n  if (tag == \"grouplogicalbytes\") {\n    return kGroupLogicalBytesTarget;\n  }\n\n  if (tag == \"groupfiles\") {\n    return kGroupFilesTarget;\n  }\n\n  if (tag == \"alluserbytes\") {\n    return kAllUserBytesTarget;\n  }\n\n  if (tag == \"alluserlogicalbytes\") {\n    return kAllUserLogicalBytesTarget;\n  }\n\n  if (tag == \"alluserfiles\") {\n    return kAllUserFilesTarget;\n  }\n\n  if (tag == \"allgroupbytes\") {\n    return kAllGroupBytesTarget;\n  }\n\n  if (tag == \"allgrouplogicalbytes\") {\n    return kAllGroupLogicalBytesTarget;\n  }\n\n  if (tag == \"allgroupfiles\") {\n    return kAllGroupFilesTarget;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Convert int tag to user or group category\n//------------------------------------------------------------------------------\nconst char* SpaceQuota::GetTagCategory(int tag)\n{\n  if ((tag == kUserBytesIs) || (tag == kUserBytesTarget) ||\n      (tag == kUserLogicalBytesIs) || (tag == kUserLogicalBytesTarget) ||\n      (tag == kUserFilesIs) || (tag == kUserFilesTarget) ||\n      (tag == kAllUserBytesIs) || (tag == kAllUserBytesTarget) ||\n      (tag == kAllUserFilesIs) || (tag == kAllUserFilesTarget)) {\n    return \"user\";\n  }\n\n  if ((tag == kGroupBytesIs) || (tag == kGroupBytesTarget) ||\n      (tag == kGroupLogicalBytesIs) || (tag == kGroupLogicalBytesTarget) ||\n      (tag == kGroupFilesIs) || (tag == kGroupFilesTarget) ||\n      (tag == kAllGroupBytesIs) || (tag == kAllGroupBytesTarget) ||\n      (tag == kAllGroupFilesIs) || (tag == kAllGroupFilesTarget)) {\n    return \"group\";\n  }\n\n  return \"-----\";\n}\n\n//------------------------------------------------------------------------------\n// Convert int tag to string description\n//------------------------------------------------------------------------------\nconst char* SpaceQuota::GetTagName(int tag)\n{\n  if (tag == kUserBytesIs) {\n    return \"used bytes\";\n  }\n\n  if (tag == kUserLogicalBytesIs) {\n    return \"logi bytes\";\n  }\n\n  if (tag == kUserBytesTarget) {\n    return \"aval bytes\";\n  }\n\n  if (tag == kUserFilesIs) {\n    return \"used files\";\n  }\n\n  if (tag == kUserFilesTarget) {\n    return \"aval files\";\n  }\n\n  if (tag == kUserLogicalBytesTarget) {\n    return \"aval logib\";\n  }\n\n  if (tag == kGroupBytesIs) {\n    return \"used bytes\";\n  }\n\n  if (tag == kGroupLogicalBytesIs) {\n    return \"logi bytes\";\n  }\n\n  if (tag == kGroupBytesTarget) {\n    return \"aval bytes\";\n  }\n\n  if (tag == kGroupFilesIs) {\n    return \"used files\";\n  }\n\n  if (tag == kGroupFilesTarget) {\n    return \"aval files\";\n  }\n\n  if (tag == kGroupLogicalBytesTarget) {\n    return \"aval logib\";\n  }\n\n  if (tag == kAllUserBytesIs) {\n    return \"used bytes\";\n  }\n\n  if (tag == kAllUserLogicalBytesIs) {\n    return \"logi bytes\";\n  }\n\n  if (tag == kAllUserBytesTarget) {\n    return \"aval bytes\";\n  }\n\n  if (tag == kAllUserFilesIs) {\n    return \"used files\";\n  }\n\n  if (tag == kAllUserFilesTarget) {\n    return \"aval files\";\n  }\n\n  if (tag == kAllUserLogicalBytesTarget) {\n    return \"aval logib\";\n  }\n\n  if (tag == kAllGroupBytesIs) {\n    return \"used bytes\";\n  }\n\n  if (tag == kAllGroupLogicalBytesIs) {\n    return \"logi bytes\";\n  }\n\n  if (tag == kAllGroupBytesTarget) {\n    return \"aval bytes\";\n  }\n\n  if (tag == kAllGroupFilesIs) {\n    return \"used files\";\n  }\n\n  if (tag == kAllGroupFilesTarget) {\n    return \"aval files\";\n  }\n\n  if (tag == kAllGroupLogicalBytesTarget) {\n    return \"aval logib\";\n  }\n\n  return \"---- -----\";\n}\n\n//------------------------------------------------------------------------------\n// *** Class Quota implementaion ***\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Get space quota object for exact path - caller has to have a read lock on\n// pMapMutex.\n//------------------------------------------------------------------------------\nSpaceQuota*\nQuota::GetSpaceQuota(const std::string& qpath)\n{\n  std::string path = NormalizePath(qpath);\n\n  if (pMapQuota.count(path)) {\n    return pMapQuota[path];\n  } else {\n    return nullptr;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get space quota object responsible for path (find best match) - caller has\n// to have a read lock on pMapMutex.\n//------------------------------------------------------------------------------\nSpaceQuota*\nQuota::GetResponsibleSpaceQuota(const std::string& path)\n{\n  SpaceQuota* squota = nullptr;\n\n  for (auto it = pMapQuota.begin(); it != pMapQuota.end(); ++it) {\n    if (common::startsWith(path, it->second->GetSpaceName())) {\n      if (squota == nullptr) {\n        squota = it->second;\n      }\n\n      // Save if it's a better match\n      if (it->second->GetSpaceNameStr().size() > squota->GetSpaceNameStr().size()) {\n        squota = it->second;\n      }\n    }\n  }\n\n  return squota;\n}\n\n//----------------------------------------------------------------------------\n//  Get space quota node path\n//----------------------------------------------------------------------------\nstd::string\nQuota::GetResponsibleSpaceQuotaPath(const std::string& path)\n{\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetResponsibleSpaceQuota(path);\n\n  if (squota) {\n    return squota->GetSpaceNameStr();\n  } else {\n    return \"\";\n  }\n}\n\n\n\n//------------------------------------------------------------------------------\n// Check if space quota exists\n//------------------------------------------------------------------------------\nbool\nQuota::Exists(const std::string& qpath)\n{\n  std::string path = NormalizePath(qpath);\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  return (pMapQuota.count(path) != 0);\n}\n\n//------------------------------------------------------------------------------\n// Check if there is a SpaceQuota responsible for the given path\n//------------------------------------------------------------------------------\nbool\nQuota::ExistsResponsible(const std::string& path)\n{\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  return (GetResponsibleSpaceQuota(path) != 0);\n}\n\n//------------------------------------------------------------------------------\n// Get individual quota - called only from mgm/http/webdav/PropFindResponse\n//------------------------------------------------------------------------------\nvoid\nQuota::GetIndividualQuota(eos::common::VirtualIdentity& vid,\n                          const std::string& path,\n                          long long& max_bytes,\n                          long long& free_bytes,\n                          long long& max_files,\n                          long long& free_files,\n                          bool logical)\n{\n  // Check for sys.auth='*'\n  eos::common::VirtualIdentity m_vid = vid;\n  std::string ownerauth;\n  XrdOucErrInfo error;\n  struct stat buf;\n\n  if (!gOFS->_stat(path.c_str(), &buf, error, vid, \"\")) {\n    gOFS->_attr_get(path.c_str(), error, vid, \"\", \"sys.owner.auth\", ownerauth);\n\n    if (ownerauth.length()) {\n      if (ownerauth == \"*\") {\n        eos_static_info(\"msg=\\\"client authenticated as directory owner\\\" \"\n                        \"path=\\\"%s\\\"uid=\\\"%u=>%u\\\" gid=\\\"%u=>%u\\\"\", path.c_str(),\n                        vid.uid, vid.gid, buf.st_uid, buf.st_gid);\n        // The client can operate as the owner, we rewrite the virtual id\n        m_vid.uid = buf.st_uid;\n        m_vid.gid = buf.st_gid;\n      } else {\n        ownerauth += \",\";\n        std::string ownerkey = vid.prot.c_str();\n        ownerkey += \":\";\n\n        if (vid.prot == \"gsi\") {\n          ownerkey += vid.dn.c_str();\n        } else {\n          ownerkey += vid.uid_string.c_str();\n        }\n\n        if ((ownerauth.find(ownerkey)) != std::string::npos) {\n          eos_static_info(\"msg=\\\"client authenticated as directory owner\\\" \"\n                          \"path=\\\"%s\\\"uid=\\\"%u=>%u\\\" gid=\\\"%u=>%u\\\"\", path.c_str(),\n                          vid.uid, vid.gid, buf.st_uid, buf.st_gid);\n          // The client can operate as the owner, we rewrite the virtual id\n          m_vid.uid = buf.st_uid;\n          m_vid.gid = buf.st_gid;\n        }\n      }\n    }\n  }\n\n  eos::common::RWMutexReadLock rd_ns_lock(gOFS->eosViewRWMutex);\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* space = GetResponsibleSpaceQuota(path);\n\n  if (space) {\n    space->Refresh(60);\n    long long max_bytes_usr, max_bytes_grp, max_bytes_prj;\n    long long free_bytes_usr, free_bytes_grp, free_bytes_prj;\n    long long max_files_usr, max_files_grp, max_files_prj;\n    long long free_files_usr, free_files_grp, free_files_prj;\n    free_bytes_usr = free_bytes_grp = free_bytes_prj = 0;\n    max_bytes_usr = max_bytes_grp = max_bytes_prj = 0;\n    free_files_usr = free_files_grp = free_files_prj = 0;\n    (void) free_files_usr; // not used - avoid compile warning\n    max_files_usr = max_files_grp = max_files_prj = 0;\n    (void) max_files_usr; // not used -avoid compile warning\n\n    if(logical) {\n      max_bytes_usr  = space->GetQuota(SpaceQuota::kUserLogicalBytesTarget, m_vid.uid);\n      max_bytes_grp = space->GetQuota(SpaceQuota::kGroupLogicalBytesTarget, m_vid.gid);\n      max_bytes_prj = space->GetQuota(SpaceQuota::kGroupLogicalBytesTarget, Quota::gProjectId);\n\n      free_bytes_usr = max_bytes_usr -\n        space->GetQuota(SpaceQuota::kUserLogicalBytesIs, m_vid.uid);\n      free_bytes_grp = max_bytes_grp -\n        space->GetQuota(SpaceQuota::kGroupLogicalBytesIs, m_vid.gid);\n      free_bytes_prj = max_bytes_prj -\n        space->GetQuota(SpaceQuota::kGroupLogicalBytesIs, Quota::gProjectId);\n    } else {\n      max_bytes_usr = space->GetQuota(SpaceQuota::kUserBytesTarget, m_vid.uid);\n      max_bytes_grp = space->GetQuota(SpaceQuota::kGroupBytesTarget, m_vid.gid);\n      max_bytes_prj = space->GetQuota(SpaceQuota::kGroupBytesTarget, Quota::gProjectId);\n\n      free_bytes_usr = max_bytes_usr -\n        space->GetQuota(SpaceQuota::kUserBytesIs, m_vid.uid);\n      free_bytes_grp = max_bytes_grp -\n        space->GetQuota(SpaceQuota::kGroupBytesIs, m_vid.gid);\n      free_bytes_prj = max_bytes_prj -\n        space->GetQuota(SpaceQuota::kGroupBytesIs, Quota::gProjectId);\n    }\n\n    if (free_bytes_usr > free_bytes) {\n      free_bytes = free_bytes_usr;\n    }\n\n    if (free_bytes_grp > free_bytes) {\n      free_bytes = free_bytes_grp;\n    }\n\n    if (free_bytes_prj > free_bytes) {\n      free_bytes = free_bytes_prj;\n    }\n\n    if (max_bytes_usr > max_bytes) {\n      max_bytes = max_bytes_usr;\n    }\n\n    if (max_bytes_grp > max_bytes) {\n      max_bytes = max_bytes_grp;\n    }\n\n    if (max_bytes_prj > max_bytes) {\n      max_bytes = max_bytes_prj;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Set quota type for id\n//------------------------------------------------------------------------------\nbool\nQuota::SetQuotaTypeForId(const std::string& qpath, long id, Quota::IdT id_type,\n                         Quota::Type quota_type, unsigned long long value,\n                         std::string& msg, int& retc)\n{\n  std::ostringstream oss_msg;\n  std::string path = NormalizePath(qpath);\n  retc = EINVAL;\n\n  // If no path use \"/eos/\"\n  if (path.empty()) {\n    path = \"/eos/\";\n  }\n\n  // Make sure the quota node exist\n  if (!Create(path)) {\n    oss_msg << \"error: failed to create quota node: \" << path;\n    msg = oss_msg.str();\n    return false;\n  }\n\n  // Get type of quota to set and construct config entry\n  std::ostringstream oss_config;\n  SpaceQuota::eQuotaTag quota_tag;\n  oss_config << path << \":\";\n\n  if (id_type == IdT::kUid) {\n    oss_config << \"uid=\";\n\n    if (quota_type == Type::kVolume) {\n      quota_tag = SpaceQuota::kUserLogicalBytesTarget;\n    } else {\n      quota_tag = SpaceQuota::kUserFilesTarget;\n    }\n  } else {\n    oss_config << \"gid=\";\n\n    if (quota_type == Type::kVolume) {\n      quota_tag = SpaceQuota::kGroupLogicalBytesTarget;\n    } else {\n      quota_tag = SpaceQuota::kGroupFilesTarget;\n    }\n  }\n\n  oss_config << id << \":\" << SpaceQuota::GetTagAsString(quota_tag);\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetSpaceQuota(path);\n\n  if (!squota) {\n    oss_msg << \"error: no quota space defined for node \" << path;\n    msg = oss_msg.str();\n    return false;\n  }\n\n  // When setting logical bytes quota, set also raw bytes for backward compatibility\n  if (quota_type == Type::kVolume) {\n    long long raw_bytes, log_bytes;\n\n    std::string raw_config = oss_config.str();\n    raw_config.erase(raw_config.find(\"logical\"), 7);\n\n    SpaceQuota::eQuotaTag quota_raw = (id_type == IdT::kUid) ? SpaceQuota::kUserBytesTarget\n                                                             : SpaceQuota::kGroupBytesTarget;\n\n    if (getenv(\"EOS_MGM_QUOTA_SET_BY_LOGICAL\")) {\n      log_bytes = value;\n      raw_bytes = value * squota->GetLayoutSizeFactor();\n    } else {\n      log_bytes = value / squota->GetLayoutSizeFactor();\n      raw_bytes = value;\n    }\n\n    std::string log_value = std::to_string(log_bytes);\n    std::string raw_value = std::to_string(raw_bytes);\n\n    oss_msg << \"updating quota using \" << log_bytes << \" bytes (\" << raw_bytes << \" raw bytes)\\n\";\n\n    squota->SetQuota(quota_tag, id, log_bytes);\n    squota->SetQuota(quota_raw, id, raw_bytes);\n    gOFS->mConfigEngine->SetConfigValue(\"quota\", oss_config.str().c_str(), log_value.c_str());\n    gOFS->mConfigEngine->SetConfigValue(\"quota\", raw_config.c_str(), raw_value.c_str());\n  } else {\n    std::string svalue = std::to_string(value);\n    squota->SetQuota(quota_tag, id, value);\n    gOFS->mConfigEngine->SetConfigValue(\"quota\", oss_config.str().c_str(), svalue.c_str());\n  }\n\n  oss_msg << \"success: updated \"\n          << ((quota_type == Type::kVolume) ? \"volume\" : \"inode\")\n          << \" quota for \"\n          << ((id_type == IdT::kUid) ? \"uid=\" : \"gid=\") << id\n          << \" for node \" << path << std::endl;\n  msg = oss_msg.str();\n  retc = 0;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Set quota depending on the quota tag.\n//------------------------------------------------------------------------------\nbool\nQuota::SetQuotaForTag(const std::string& qpath,\n                      const std::string& quota_stag,\n                      long id, unsigned long long value)\n{\n  unsigned long spaceq_type = SpaceQuota::GetTagFromString(quota_stag);\n  // Make sure the quota node exists\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetSpaceQuota(qpath);\n\n  if (squota) {\n    squota->SetQuota(spaceq_type, id, value);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Remove quota type for id\n//------------------------------------------------------------------------------\nbool\nQuota::RmQuotaTypeForId(const std::string& qpath, long id, Quota::IdT id_type,\n                        Quota::Type quota_type, std::string& msg, int& retc)\n{\n  std::ostringstream oss_msg;\n  std::string path = NormalizePath(qpath);\n  retc = EINVAL;\n\n  // If no path use \"/eos/\"\n  if (path.empty()) {\n    path = \"/eos/\";\n  }\n\n  // Get type of quota to remove and construct config entry\n  std::ostringstream oss_config;\n  SpaceQuota::eQuotaTag quota_tag;\n  oss_config << path << \":\";\n\n  if (id_type == IdT::kUid) {\n    oss_config << \"uid=\";\n\n    if (quota_type == Type::kVolume) {\n      quota_tag = SpaceQuota::kUserBytesTarget;\n    } else {\n      quota_tag = SpaceQuota::kUserFilesTarget;\n    }\n  } else {\n    oss_config << \"gid=\";\n\n    if (quota_type == Type::kVolume) {\n      quota_tag = SpaceQuota::kGroupBytesTarget;\n    } else {\n      quota_tag = SpaceQuota::kGroupFilesTarget;\n    }\n  }\n\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetSpaceQuota(path);\n\n  if (!squota) {\n    oss_msg << \"error: no quota space defined for node \" << path;\n    msg = oss_msg.str();\n    return false;\n  }\n\n  if (squota->RmQuota(quota_tag, id)) {\n    oss_config << id << \":\" << SpaceQuota::GetTagAsString(quota_tag);\n    gOFS->mConfigEngine->DeleteConfigValue(\"quota\", oss_config.str().c_str());\n\n    // If this is a volume quota, remove also the equivalent logical quota if set\n    if (quota_type == Type::kVolume) {\n      std::string log_config = oss_config.str();\n      log_config.replace(log_config.find(\"bytes\"), 5, \"logicalbytes\");\n      SpaceQuota::eQuotaTag tag = (id_type == IdT::kUid) ? SpaceQuota::kUserLogicalBytesTarget\n                                                         : SpaceQuota::kGroupLogicalBytesTarget;\n      if (squota->RmQuota(tag, id))\n        gOFS->mConfigEngine->DeleteConfigValue(\"quota\", log_config.c_str());\n    }\n\n    oss_msg << \"success: removed \"\n            << ((quota_type == Type::kVolume) ? \"volume\" : \"inode\")\n            << \" quota for \"\n            << ((id_type == IdT::kUid) ? \"uid=\" : \"gid=\") << id\n            << \" from node \" << path;\n    msg = oss_msg.str();\n    retc = 0;\n    return true;\n  } else {\n    oss_msg << \"error: no \"\n            << ((quota_type == Type::kVolume) ? \"volume\" : \"inode\")\n            << \" quota defined on node \" << path << \" for \"\n            << ((id_type == IdT::kUid) ? \"user id\" : \"group id\");\n    msg = oss_msg.str();\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Remove all quota types for an id\n//------------------------------------------------------------------------------\nbool\nQuota::RmQuotaForId(const std::string& path, long id, Quota::IdT id_type,\n                    std::string& msg, int& retc)\n{\n  eos_static_debug(\"path=%s\", path.c_str());\n  std::string msg_vol, msg_inode;\n  bool rm_vol = RmQuotaTypeForId(path, id, id_type, Type::kVolume, msg_vol, retc);\n  bool rm_inode = RmQuotaTypeForId(path, id, id_type, Type::kInode,\n                                   msg_inode, retc);\n\n  if (rm_vol || rm_inode) {\n    if (rm_vol) {\n      msg += msg_vol;\n    }\n\n    if (rm_inode) {\n      msg += msg_inode;\n    }\n\n    return true;\n  } else {\n    msg = \"error: no quota defined for node \";\n    msg += path;\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Remove space quota\n//------------------------------------------------------------------------------\nbool\nQuota::RmSpaceQuota(const std::string& qpath, std::string& msg, int& retc)\n{\n  std::string path = NormalizePath(qpath);\n  eos_static_debug(\"qpath=%s, path=%s\", qpath.c_str(), path.c_str());\n  eos::common::RWMutexWriteLock wr_ns_lock(gOFS->eosViewRWMutex);\n  eos::common::RWMutexWriteLock wr_quota_lock(pMapMutex);\n  std::unique_ptr<SpaceQuota> squota(GetSpaceQuota(path));\n\n  if (!squota) {\n    retc = EINVAL;\n    msg = \"error: there is no quota node under path \";\n    msg += path;\n    return false;\n  } else {\n    // Remove space quota from map\n    pMapQuota.erase(path);\n    // Delete also from the pMapInodeQuota\n    (void) pMapInodeQuota.erase(squota->GetQuotaNode()->getId());\n\n    // Remove ns quota node\n    try {\n      std::shared_ptr<eos::IContainerMD> qcont = gOFS->eosView->getContainer(path);\n      gOFS->eosView->removeQuotaNode(qcont.get());\n      retc = 0;\n    } catch (eos::MDException& e) {\n      retc = e.getErrno();\n      msg = e.getMessage().str().c_str();\n    }\n\n    // Remove all configuration entries\n    std::string match = path;\n    match += \":\";\n    gOFS->mConfigEngine->DeleteConfigValueByMatch(\"quota\", match.c_str());\n    msg = \"success: removed space quota for \";\n    msg += path;\n\n    if (!gOFS->mConfigEngine->AutoSave()) {\n      return false;\n    }\n\n    return true;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Remove quota depending on the quota tag. Convenience wrapper around the\n// default RmQuotaTypeForId.\n//------------------------------------------------------------------------------\nbool\nQuota::RmQuotaForTag(const std::string& path, const std::string& quota_stag,\n                     long id)\n{\n  unsigned long spaceq_type = SpaceQuota::GetTagFromString(quota_stag);\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetSpaceQuota(path);\n\n  if (squota) {\n    squota->RmQuota(spaceq_type, id);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Callback function to calculate how much pyhisical space a file occupies\n//------------------------------------------------------------------------------\nuint64_t\nQuota::MapSizeCB(const eos::IFileMD* file)\n{\n  if (!file) {\n    return 0;\n  }\n\n  eos::IFileMD::layoutId_t lid = file->getLayoutId();\n  return (uint64_t) file->getSize() * eos::common::LayoutId::GetSizeFactor(lid);\n}\n\n//------------------------------------------------------------------------------\n// Load nodes\n//------------------------------------------------------------------------------\nvoid\nQuota::LoadNodes()\n{\n  std::vector<std::string> create_quota;\n  // Load all known nodes\n  {\n    std::string quota_path;\n    std::shared_ptr<eos::IContainerMD> container;\n    eos::common::RWMutexReadLock rd_ns_lock(gOFS->eosViewRWMutex);\n    auto set_ids = gOFS->eosView->getQuotaStats()->getAllIds();\n\n    for (const auto elem : set_ids) {\n      try {\n        container = gOFS->eosDirectoryService->getContainerMD(elem);\n        quota_path = gOFS->eosView->getUri(container.get());\n\n        // Make sure directories are '/' terminated\n        if (quota_path.back() != '/') {\n          quota_path += '/';\n        }\n\n        if (!Exists(quota_path)) {\n          create_quota.push_back(quota_path);\n        }\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        eos_static_err(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                       e.getErrno(), e.getMessage().str().c_str());\n      }\n    }\n  }\n\n  // Create all the necessary space quota nodes\n  for (auto it = create_quota.begin(); it != create_quota.end(); ++it) {\n    eos_static_notice(\"msg=\\\"create quota node\\\" path=\\\"%s\\\"\", it->c_str());\n    (void) Create(it->c_str());\n  }\n\n  // Refresh the space quota objects\n  {\n    // loop over pMapQuota releasing locks each time in the iteration\n    eos::common::RWMutexReadLock rd_ns_lock(gOFS->eosViewRWMutex);\n    eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n    bool first = true;\n    size_t n = 0;\n\n    do {\n      auto it = pMapQuota.begin();\n      std::advance(it, n);\n\n      if (!first) {\n        rd_ns_lock.Grab(gOFS->eosViewRWMutex);\n        rd_quota_lock.Grab(pMapMutex);\n        first = false;\n      }\n\n      if (it == pMapQuota.end()) {\n        break;\n      }\n\n      it->second->Refresh(5);\n      n++;\n      rd_quota_lock.Release();\n      rd_ns_lock.Release();\n    } while (1);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print out quota information\n//------------------------------------------------------------------------------\nbool\nQuota::PrintOut(const std::string& path, XrdOucString& output,\n                long long int uid_sel, long long int gid_sel, bool monitoring,\n                bool translate_ids)\n{\n  output = \"\";\n  // Add this to have all quota nodes visible even if they are not in\n  // the configuration file\n  LoadNodes();\n  eos::common::RWMutexReadLock rd_fs_lock(FsView::gFsView.ViewMutex);\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n\n  if (path.empty()) {\n    for (auto it = pMapQuota.begin(); it != pMapQuota.end(); ++it) {\n      it->second->PrintOut(output, uid_sel, gid_sel, monitoring, translate_ids);\n    }\n  } else {\n    SpaceQuota* squota = GetResponsibleSpaceQuota(path);\n\n    if (squota) {\n      squota->PrintOut(output, uid_sel, gid_sel, monitoring, translate_ids);\n    } else {\n      output = \"error: no quota for path \";\n      output += path.c_str();\n      return false;\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Get group quota values for a particular path and id\n//------------------------------------------------------------------------------\nstd::map<int, unsigned long long>\nQuota::GetGroupStatistics(const std::string& qpath, long id)\n{\n  std::string path = NormalizePath(qpath);\n  std::map<int, unsigned long long> map;\n  eos::common::RWMutexReadLock rd_ns_lock(gOFS->eosViewRWMutex);\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetResponsibleSpaceQuota(path);\n\n  if (!squota) {\n    return map;\n  }\n\n  squota->Refresh(60);\n  unsigned long long value;\n  // Set of all group related quota keys\n  std::set<int> set_keys = {SpaceQuota::kGroupLogicalBytesIs, SpaceQuota::kGroupLogicalBytesTarget,\n                            SpaceQuota::kGroupFilesIs, SpaceQuota::kGroupFilesTarget,\n                            SpaceQuota::kAllGroupLogicalBytesTarget,\n                            SpaceQuota::kAllGroupLogicalBytesIs\n                           };\n\n  for (auto it = set_keys.begin(); it != set_keys.end(); ++it) {\n    value = squota->GetQuota(*it, id);\n    map.insert(std::make_pair(*it, value));\n  }\n\n  return map;\n}\n\n//------------------------------------------------------------------------------\n// Update quota from the namespace quota only if the requested path is actually\n// a ns quota node.\n//------------------------------------------------------------------------------\nbool\nQuota::UpdateFromNsQuota(const std::string& path, uid_t uid, gid_t gid)\n{\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetResponsibleSpaceQuota(path);\n\n  // No quota or this is not the space quota itself - do nothing\n  if (!squota || squota->GetSpaceNameStr().compare(path)) {\n    return false;\n  }\n\n  squota->UpdateFromQuotaNode(uid, gid, true);\n  return true;\n}\n\n//----------------------------------------------------------------------------\n// Check if the requested volume and inode values respect the quota\n//----------------------------------------------------------------------------\nbool\nQuota::Check(const std::string& path, uid_t uid, gid_t gid,\n             long long desired_vol, unsigned int desired_inodes)\n{\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetResponsibleSpaceQuota(path);\n\n  if (!squota) {\n    return true;\n  }\n\n  return squota->CheckWriteQuota(uid, gid, desired_vol, desired_inodes);\n}\n\n//------------------------------------------------------------------------------\n// Clean-up all space quotas by deleting them and clearing the maps\n//------------------------------------------------------------------------------\nvoid\nQuota::CleanUp()\n{\n  eos::common::RWMutexWriteLock wr_lock(pMapMutex);\n\n  for (auto it = pMapQuota.begin(); it != pMapQuota.end(); ++it) {\n    delete it->second;\n  }\n\n  pMapQuota.clear();\n  pMapInodeQuota.clear();\n}\n\n//------------------------------------------------------------------------------\n// Take the decision where to place a new file in the system. The core of the\n// implementation is in the Scheduler and GeoTreeEngine.\n//------------------------------------------------------------------------------\nint\nQuota::FilePlacement(Scheduler::PlacementArguments* args)\n{\n  if (FsView::gFsView.mSpaceGroupView.count(*args->spacename) == 0) {\n    eos_static_err(\"msg=\\\"no filesystem in space\\\" space=\\\"%s\\\"\",\n                   args->spacename->c_str());\n    args->selected_filesystems->clear();\n    return ENOSPC;\n  }\n\n  // Check if quota enabled for current space\n  if (FsView::gFsView.IsQuotaEnabled(*args->spacename)) {\n    eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n    if (SpaceQuota* squota = GetResponsibleSpaceQuota(args->path)) {\n      if (!squota->CheckWriteQuota(args->vid->uid, args->vid->gid, args->bookingsize, 1)) {\n        eos_static_debug(\"not enough quota for uid=%u gid=%u grouptag=%s bookingsize=%lu at path=%s\",\n                         args->vid->uid, args->vid->gid, args->grouptag, args->bookingsize, args->path);\n=======\n        eos_static_debug(\"uid=%u gid=%u grouptag=%s place filesystems=%u has no quota left!\",\n                         args->vid->uid, args->vid->gid, args->grouptag, nfilesystems);\n>>>>>>> 04b82be1e (MGM: Quota: Do not multiply bookingsize by number of filesystems)\n        return EDQUOT;\n      }\n    }\n  } else {\n    eos_static_debug(\"quota is disabled for space=%s\", args->spacename->c_str());\n  }\n\n  if (!FsView::gFsView.UnderNominalQuota(*args->spacename, args->vid->sudoer)) {\n    eos_static_err(\"msg=\\\"over physical quota limit (nominal space setting)\\\" space=\\\"%s\\\"\",\n                   args->spacename->c_str());\n    return ENOSPC;\n  }\n\n  eos_static_debug(\"%s\", \"nominal quota ok\");\n\n  // Call the scheduler implementation\n  return Scheduler::FilePlacement(args);\n}\n\n//------------------------------------------------------------------------------\n// Create quota node for path\n//------------------------------------------------------------------------------\nbool\nQuota::Create(const std::string& path)\n{\n  // Check if path is correct\n  if (path.empty() || path[0] != '/' || (*path.rbegin()) != '/') {\n    return false;\n  }\n\n  eos::common::RWMutexWriteLock wr_ns_lock(gOFS->eosViewRWMutex);\n  eos::common::RWMutexWriteLock wr_quota_lock(pMapMutex);\n\n  if (pMapQuota.count(path) == 0) {\n    try {\n      SpaceQuota* squota = new SpaceQuota(path.c_str());\n      pMapQuota[path] = squota;\n      pMapInodeQuota[squota->GetQuotaNode()->getId()] = squota;\n    } catch (const eos::MDException& e) {\n      eos_static_crit(\"Failed to create quota node %s\", path.c_str());\n      return false;\n    }\n  }\n\n  // Synchronize the flusher to avoid a race condition with the slave creating\n  // the same directory when applying the quota\n  auto* qdb_ns_grp = dynamic_cast<eos::QuarkNamespaceGroup*>\n                     (gOFS->namespaceGroup.get());\n\n  if (qdb_ns_grp) {\n    qdb_ns_grp->getMetadataFlusher()->synchronize();\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Retrieve the kAllGroupLogicalBytesIs and kAllGroupLogicalBytesTarget\n// values for the quota nodes.\n//------------------------------------------------------------------------------\nstd::map<std::string, std::tuple<unsigned long long,\n    unsigned long long,\n    unsigned long long>>\n    Quota::GetAllGroupsLogicalQuotaValues()\n{\n  std::map<std::string, std::tuple<unsigned long long,\n      unsigned long long,\n      unsigned long long>> allGroupLogicalByteValues;\n  // Add this to have all quota nodes visible even if they are not in\n  // the configuration file\n  LoadNodes();\n  eos::common::RWMutexReadLock rd_fs_lock(FsView::gFsView.ViewMutex);\n  eos::common::RWMutexReadLock rd_ns_lock(gOFS->eosViewRWMutex);\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n\n  for (const auto& quotaNode : pMapQuota) {\n    // quotaNode.second->Refresh();\n    allGroupLogicalByteValues[quotaNode.first] = std::make_tuple\n        (quotaNode.second->GetQuota(SpaceQuota::eQuotaTag::kAllGroupLogicalBytesIs, 0),\n         quotaNode.second->GetQuota(SpaceQuota::eQuotaTag::kAllGroupLogicalBytesTarget,\n                                    0),\n         quotaNode.second->GetQuota(SpaceQuota::eQuotaTag::kAllGroupFilesIs, 0));\n  }\n\n  return allGroupLogicalByteValues;\n}\n\n//------------------------------------------------------------------------------\n// Get quota for requested user and group by path\n//------------------------------------------------------------------------------\nint\nQuota::QuotaByPath(const char* path, uid_t uid, gid_t gid,\n                   long long& avail_files, long long& avail_bytes,\n                   eos::IContainerMD::id_t& quota_inode)\n{\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetResponsibleSpaceQuota(path);\n\n  if (squota) {\n    quota_inode = squota->GetQuotaNode()->getId();\n    return GetQuotaInfo(squota, uid, gid, avail_files, avail_bytes);\n  }\n\n  return -1;\n}\n\n//------------------------------------------------------------------------------\n// Get quota for requested user and group by quota inode\n//------------------------------------------------------------------------------\nint\nQuota::QuotaBySpace(const eos::IContainerMD::id_t qino, uid_t uid, gid_t gid,\n                    long long& avail_files, long long& avail_bytes)\n{\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  auto it = pMapInodeQuota.find(qino);\n\n  if (it != pMapInodeQuota.end()) {\n    return GetQuotaInfo(it->second, uid, gid, avail_files, avail_bytes);\n  }\n\n  return -1;\n}\n\n//------------------------------------------------------------------------------\n// Private method to collect desired info from a quota node\n// returns std::numeric_limits<long>::max() / 2; in avail_files or avail_bytes if not quota is set\n//------------------------------------------------------------------------------\nint\nQuota::GetQuotaInfo(SpaceQuota* squota, uid_t uid, gid_t gid,\n                    long long& avail_files, long long& avail_bytes)\n{\n  long long maxbytes_user, maxbytes_group, maxbytes_project;\n  long long freebytes_user, freebytes_group, freebytes_project;\n  long long freebytes = 0 ;\n  long long maxbytes = 0;\n  freebytes_user = freebytes_group = freebytes_project = 0;\n  maxbytes_user = maxbytes_group = maxbytes_project = 0;\n  squota->UpdateFromQuotaNode(uid, gid,\n                              squota->GetQuota(SpaceQuota::kGroupBytesTarget, Quota::gProjectId)\n                              ? true : false);\n  maxbytes_user  = squota->GetQuota(SpaceQuota::kUserLogicalBytesTarget, uid);\n  maxbytes_group = squota->GetQuota(SpaceQuota::kGroupLogicalBytesTarget, gid);\n  maxbytes_project = squota->GetQuota(SpaceQuota::kGroupLogicalBytesTarget,\n                                      Quota::gProjectId);\n  freebytes_user = maxbytes_user - squota->GetQuota(\n                     SpaceQuota::kUserLogicalBytesIs, uid);\n  freebytes_group = maxbytes_group - squota->GetQuota(\n                      SpaceQuota::kGroupLogicalBytesIs, gid);\n  freebytes_project = maxbytes_project - squota->GetQuota(\n                        SpaceQuota::kGroupLogicalBytesIs, Quota::gProjectId);\n\n  if (freebytes_user > freebytes) {\n    freebytes = freebytes_user;\n  }\n\n  if (freebytes_group > freebytes) {\n    freebytes = freebytes_group;\n  }\n\n  if (freebytes_project > freebytes) {\n    freebytes = freebytes_project;\n  }\n\n  if (maxbytes_user > maxbytes) {\n    maxbytes = maxbytes_user;\n  }\n\n  if (maxbytes_group > maxbytes) {\n    maxbytes = maxbytes_group;\n  }\n\n  if (maxbytes_project > maxbytes) {\n    maxbytes = maxbytes_project;\n  }\n\n  if (!freebytes && (maxbytes == 0)) {\n    // this is no quota set\n    freebytes = std::numeric_limits<long>::max() / 2;\n  }\n\n  long long maxfiles_user, maxfiles_group, maxfiles_project;\n  long long freefiles_user, freefiles_group, freefiles_project;\n  long long freefiles = 0;\n  long long maxfiles = 0;\n  freefiles_user = freefiles_group = freefiles_project = 0;\n  maxfiles_user = maxfiles_group = maxfiles_project = 0;\n  maxfiles_user  = squota->GetQuota(SpaceQuota::kUserFilesTarget, uid);\n  maxfiles_group = squota->GetQuota(SpaceQuota::kGroupFilesTarget, gid);\n  maxfiles_project = squota->GetQuota(SpaceQuota::kGroupFilesTarget,\n                                      Quota::gProjectId);\n  freefiles_user = maxfiles_user - squota->GetQuota(SpaceQuota::kUserFilesIs,\n                   uid);\n  freefiles_group = maxfiles_group - squota->GetQuota(SpaceQuota::kGroupFilesIs,\n                    gid);\n  freefiles_project = maxfiles_project - squota->GetQuota(\n                        SpaceQuota::kGroupFilesIs, Quota::gProjectId);\n\n  if (freefiles_user > freefiles) {\n    freefiles = freefiles_user;\n  }\n\n  if (freefiles_group > freefiles) {\n    freefiles = freefiles_group;\n  }\n\n  if (freefiles_project > freefiles) {\n    freefiles = freefiles_project;\n  }\n\n  if (maxfiles_user > maxfiles) {\n    maxfiles = maxfiles_user;\n  }\n\n  if (maxfiles_group > maxfiles) {\n    maxfiles = maxfiles_group;\n  }\n\n  if (maxfiles_project > maxfiles) {\n    maxfiles = maxfiles_project;\n  }\n\n  if (!freefiles && (maxfiles == 0)) {\n    // this is no quota set\n    freefiles = std::numeric_limits<long>::max() / 2;\n  }\n\n  avail_files = freefiles;\n  avail_bytes = freebytes;\n  return 0;\n}\n\n\n//------------------------------------------------------------------------------\n// Get logical max and free bytes for the given space\n//------------------------------------------------------------------------------\nvoid\nQuota::GetStatfs(const std::string& path, unsigned long long& maxbytes,\n                 unsigned long long& freebytes)\n{\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* space = GetResponsibleSpaceQuota(path);\n\n  if (space) {\n    space->Refresh(60);\n    maxbytes = space->GetQuota(SpaceQuota::kAllGroupLogicalBytesTarget, 0);\n    freebytes = maxbytes - space->GetQuota(SpaceQuota::kAllGroupLogicalBytesIs, 0);\n  } else {\n    maxbytes = freebytes = 0;\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Remove file from corresponding quota node\n//------------------------------------------------------------------------------\nbool\nQuota::RemoveFile(eos::IFileMD::id_t fid)\n{\n  std::shared_ptr<eos::IFileMD> fmd {nullptr};\n  std::shared_ptr<eos::IContainerMD> cmd {nullptr};\n  eos::IQuotaNode* ns_quota {nullptr};\n  eos::common::RWMutexWriteLock ns_wr_lock(gOFS->eosViewRWMutex);\n\n  try {\n    fmd = gOFS->eosFileService->getFileMD(fid);\n    cmd = gOFS->eosDirectoryService->getContainerMD(fmd->getContainerId());\n    ns_quota = gOFS->eosView->getQuotaNode(cmd.get());\n  } catch (const eos::MDException& e) {\n    return false;\n  }\n\n  if (ns_quota && fmd) {\n    ns_quota->removeFile(fmd.get());\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Remove file from corresponding quota node\n//------------------------------------------------------------------------------\nbool\nQuota::AddFile(eos::IFileMD::id_t fid)\n{\n  std::shared_ptr<eos::IFileMD> fmd {nullptr};\n  std::shared_ptr<eos::IContainerMD> cmd {nullptr};\n  eos::IQuotaNode* ns_quota {nullptr};\n  eos::common::RWMutexWriteLock ns_wr_lock(gOFS->eosViewRWMutex);\n\n  try {\n    fmd = gOFS->eosFileService->getFileMD(fid);\n    cmd = gOFS->eosDirectoryService->getContainerMD(fmd->getContainerId());\n    ns_quota = gOFS->eosView->getQuotaNode(cmd.get());\n  } catch (const eos::MDException& e) {\n    return false;\n  }\n\n  if (ns_quota && fmd) {\n    ns_quota->addFile(fmd.get());\n    return true;\n  }\n\n  return false;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/quota/Quota.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Quota.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/policy/Policy.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/table_formatter/TableFormatterBase.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/ns_quarkdb/NamespaceGroup.hh\"\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n#include \"mgm/config/IConfigEngine.hh\"\n#include <errno.h>\n\nEOSMGMNAMESPACE_BEGIN\n\nstd::map<std::string, SpaceQuota*> Quota::pMapQuota;\nstd::map<eos::IContainerMD::id_t, SpaceQuota*> Quota::pMapInodeQuota;\neos::common::RWMutex Quota::pMapMutex;\ngid_t Quota::gProjectId = 99;\n\n#ifdef __APPLE__\n#define ENONET 64\n#endif\n\n//------------------------------------------------------------------------------\n// *** Class SpaceQuota implementaion ***\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Constructor - requires the eosViewRWMutex write-lock\n//------------------------------------------------------------------------------\nSpaceQuota::SpaceQuota(const char* path):\n  eos::common::LogId(),\n  pPath(path),\n  mQuotaNode(nullptr),\n  mLastEnableCheck(0),\n  mLastRefresh(0),\n  mLayoutSizeFactor(1.0),\n  mDirtyTarget(true)\n{\n  std::shared_ptr<eos::IContainerMD> quotadir;\n\n  try {\n    quotadir = gOFS->eosView->getContainer(path);\n  } catch (const eos::MDException& e) {\n    eos_err(\"No such path=%s\", path);\n  }\n\n  if (quotadir == nullptr) {\n    try {\n      quotadir = gOFS->eosView->createContainer(path, true);\n      quotadir->setMode(S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | S_IFDIR);\n      gOFS->eosView->updateContainerStore(quotadir.get());\n    } catch (eos::MDException& e) {\n      eos_crit(\"Cannot create quota directory %s\", path);\n      throw;\n    }\n  }\n\n  if (quotadir) {\n    try {\n      mQuotaNode = gOFS->eosView->getQuotaNode(quotadir.get(), false);\n\n      if (mQuotaNode) {\n        eos_info(\"Found ns quota node for path=%s\", path);\n      } else {\n        eos_info(\"No ns quota found for path=%s\", path);\n      }\n    } catch (const eos::MDException& e) {\n      mQuotaNode = nullptr;\n    }\n\n    if (!mQuotaNode) {\n      try {\n        mQuotaNode = gOFS->eosView->registerQuotaNode(quotadir.get());\n      } catch (eos::MDException& e) {\n        mQuotaNode = nullptr;\n        eos_crit(\"Cannot register quota node %s, errmsg=%s\",\n                 path, e.what());\n        throw;\n      }\n    }\n\n    UpdateLogicalSizeFactor();\n\n  } else {\n    eos_crit(\"Failed to create quota dir=%s\", path);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get quota status\n//------------------------------------------------------------------------------\nconst char*\nSpaceQuota::GetQuotaStatus(unsigned long long is, unsigned long long avail)\n{\n  if (!avail) {\n    return \"ignored\";\n  }\n\n  double p = (100.0 * is / avail);\n\n  if (p < 90) {\n    return \"ok\";\n  } else if (p < 99) {\n    return \"warning\";\n  } else {\n    return \"exceeded\";\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get current quota value as percentage of the available one\n//------------------------------------------------------------------------------\nfloat\nSpaceQuota::GetQuotaPercentage(unsigned long long is, unsigned long long avail)\n{\n  float fp = avail ? (100.0 * is / avail) : 100.0;\n\n  if (fp > 100.0) {\n    fp = 100.0;\n  }\n\n  if (fp < 0) {\n    fp = 0;\n  }\n\n  return fp;\n}\n\n//------------------------------------------------------------------------------\n// Update ns quota node address referred to by current space quota\n//------------------------------------------------------------------------------\nbool\nSpaceQuota::UpdateQuotaNodeAddress()\n{\n  try {\n    std::shared_ptr<eos::IContainerMD> quotadir =\n      gOFS->eosView->getContainer(pPath.c_str());\n    mQuotaNode = gOFS->eosView->getQuotaNode(quotadir.get(), false);\n\n    if (!mQuotaNode) {\n      return false;\n    }\n  } catch (eos::MDException& e) {\n    mQuotaNode = nullptr;\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Calculate the size factor used to estimate the logical available bytes\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::UpdateLogicalSizeFactor()\n{\n  XrdOucErrInfo error;\n  eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n  vid.sudoer = 1;\n  eos::IContainerMD::XAttrMap map;\n  int retc = gOFS->_attr_ls(pPath.c_str(), error, vid, 0, map);\n\n  if (!retc) {\n    unsigned long layoutId;\n    XrdOucEnv env;\n    unsigned long forcedfsid;\n    long forcedgroup;\n    std::string bandwidth;\n    std::string spn = pPath; // Is this necessary?\n    bool schedule = false;\n    std::string iopriority;\n    std::string iotype;\n    // get the layout in this quota node\n    Policy::GetLayoutAndSpace(pPath.c_str(), map, vid, layoutId, spn, env,\n                              forcedfsid, forcedgroup, bandwidth, schedule, iopriority, iotype, false);\n    mLayoutSizeFactor = eos::common::LayoutId::GetSizeFactor(layoutId);\n  } else {\n    mLayoutSizeFactor = 1.0;\n  }\n\n  // Protect for division by 0\n  if (mLayoutSizeFactor < 1.0) {\n    mLayoutSizeFactor = 1.0;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Remove quota\n//------------------------------------------------------------------------------\nbool\nSpaceQuota::RmQuota(unsigned long tag, unsigned long id)\n{\n  eos_debug(\"rm quota tag=%lu id=%lu\", tag, id);\n  XrdSysMutexHelper scope_lock(mMutex);\n  bool erased = mMapIdQuota.erase(Index(tag, id));\n\n  if (erased) {\n    mDirtyTarget = true;\n  }\n\n  return erased;\n}\n\n//------------------------------------------------------------------------------\n// Get quota value\n//------------------------------------------------------------------------------\nlong long\nSpaceQuota::GetQuota(unsigned long tag, unsigned long id)\n{\n  XrdSysMutexHelper scope_lock(mMutex);\n  auto it = mMapIdQuota.find(Index(tag, id));\n\n  if (it != mMapIdQuota.end()) {\n    return static_cast<long long>(it->second);\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Set quota\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::SetQuota(unsigned long tag, unsigned long id,\n                     unsigned long long value)\n{\n  eos_debug(\"set quota tag=%lu id=%lu value=%llu\", tag, id, value);\n  XrdSysMutexHelper scope_lock(mMutex);\n  mMapIdQuota[Index(tag, id)] = value;\n\n  switch(tag) {\n    case kUserBytesTarget:\n    mMapIdQuota.try_emplace(Index(kUserLogicalBytesTarget, id), value / mLayoutSizeFactor);\n    break;\n\n    case kGroupBytesTarget:\n    mMapIdQuota.try_emplace(Index(kGroupLogicalBytesTarget, id), value / mLayoutSizeFactor);\n    break;\n  }\n\n  if ((tag == kUserBytesTarget) ||\n      (tag == kGroupBytesTarget) ||\n      (tag == kUserFilesTarget) ||\n      (tag == kGroupFilesTarget) ||\n      (tag == kUserLogicalBytesTarget) ||\n      (tag == kGroupLogicalBytesTarget)) {\n    mDirtyTarget = true;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Reset quota\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::ResetQuota(unsigned long tag, unsigned long id)\n{\n  mMapIdQuota[Index(tag, id)] = 0;\n\n  if ((tag == kUserBytesTarget) ||\n      (tag == kGroupBytesTarget) ||\n      (tag == kUserFilesTarget) ||\n      (tag == kGroupFilesTarget) ||\n      (tag == kUserLogicalBytesTarget) ||\n      (tag == kGroupLogicalBytesTarget)) {\n    mDirtyTarget = true;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Add quota\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::AddQuota(unsigned long tag, unsigned long id, long long value)\n{\n  eos_debug(\"add quota tag=%lu id=%lu value=%llu\", tag, id, value);\n\n  // Avoid negative numbers\n  if (((long long) mMapIdQuota[Index(tag, id)] + value) >= 0) {\n    mMapIdQuota[Index(tag, id)] += value;\n  }\n\n  eos_debug(\"sum quota tag=%lu id=%lu value=%llu\", tag, id,\n            mMapIdQuota[Index(tag, id)]);\n}\n\n//------------------------------------------------------------------------------\n// Update\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::UpdateTargetSums()\n{\n  if (!mDirtyTarget) {\n    return;\n  }\n\n  eos_debug(\"%s\", \"updating targets\");\n  XrdSysMutexHelper scope_lock(mMutex);\n  mDirtyTarget = false;\n  mMapIdQuota[Index(kAllUserBytesTarget, 0)] = 0;\n  mMapIdQuota[Index(kAllUserFilesTarget, 0)] = 0;\n  mMapIdQuota[Index(kAllGroupBytesTarget, 0)] = 0;\n  mMapIdQuota[Index(kAllGroupFilesTarget, 0)] = 0;\n  mMapIdQuota[Index(kAllUserLogicalBytesTarget, 0)] = 0;\n  mMapIdQuota[Index(kAllGroupLogicalBytesTarget, 0)] = 0;\n\n  for (const auto& [tag, value] : mMapIdQuota) {\n    switch(UnIndex(tag)) {\n      case kUserBytesTarget:\n        AddQuota(kAllUserBytesTarget, 0, value);\n        break;\n      case kUserLogicalBytesTarget:\n        AddQuota(kAllUserLogicalBytesTarget, 0, value);\n        break;\n      case kUserFilesTarget:\n        AddQuota(kAllUserFilesTarget, 0, value);\n        break;\n      case kGroupBytesTarget:\n        AddQuota(kAllGroupBytesTarget, 0, value);\n        break;\n      case kGroupLogicalBytesTarget:\n        AddQuota(kAllGroupLogicalBytesTarget, 0, value);\n        break;\n      case kGroupFilesTarget:\n        AddQuota(kAllGroupFilesTarget, 0, value);\n        break;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::UpdateIsSums()\n{\n  eos_debug(\"%s\", \"updating IS values\");\n  XrdSysMutexHelper scope_lock(mMutex);\n  mMapIdQuota[Index(kAllUserBytesIs, 0)] = 0;\n  mMapIdQuota[Index(kAllUserLogicalBytesIs, 0)] = 0;\n  mMapIdQuota[Index(kAllUserFilesIs, 0)] = 0;\n  mMapIdQuota[Index(kAllGroupBytesIs, 0)] = 0;\n  mMapIdQuota[Index(kAllGroupFilesIs, 0)] = 0;\n  mMapIdQuota[Index(kAllGroupLogicalBytesIs, 0)] = 0;\n  bool has_project_quota = false;\n  auto it = mMapIdQuota.find(Index(kGroupLogicalBytesTarget, Quota::gProjectId));\n\n  if ((it != mMapIdQuota.end()) && it->second) {\n    has_project_quota = true;\n  }\n\n  // If project quota is defined for the current quota node then use that\n  // value to avoid possible double counting\n  if (has_project_quota) {\n    AddQuota(kAllGroupFilesIs, 0,\n             mMapIdQuota[Index(kGroupFilesIs, Quota::gProjectId)]);\n    AddQuota(kAllGroupBytesIs, 0,\n             mMapIdQuota[Index(kGroupBytesIs, Quota::gProjectId)]);\n    AddQuota(kAllGroupLogicalBytesIs, 0,\n             mMapIdQuota[Index(kGroupLogicalBytesIs, Quota::gProjectId)]);\n  } else {\n    for (auto it = mMapIdQuota.begin(); it != mMapIdQuota.end(); it++) {\n      if ((UnIndex(it->first) == kUserBytesIs)) {\n        AddQuota(kAllUserBytesIs, 0, it->second);\n      }\n\n      if ((UnIndex(it->first) == kUserLogicalBytesIs)) {\n        AddQuota(kAllUserLogicalBytesIs, 0, it->second);\n      }\n\n      if ((UnIndex(it->first) == kUserFilesIs)) {\n        AddQuota(kAllUserFilesIs, 0, it->second);\n      }\n\n      if ((UnIndex(it->first) == kGroupFilesIs)) {\n        AddQuota(kAllGroupFilesIs, 0, it->second);\n      }\n\n      if ((UnIndex(it->first) == kGroupBytesIs)) {\n        AddQuota(kAllGroupBytesIs, 0, it->second);\n      }\n\n      if ((UnIndex(it->first) == kGroupLogicalBytesIs)) {\n        AddQuota(kAllGroupLogicalBytesIs, 0, it->second);\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Update uid/gid values from quota node\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::UpdateFromQuotaNode(uid_t uid, gid_t gid, bool upd_proj_quota)\n{\n  eos_debug(\"%s\", \"updating uid/gid values from quota node\");\n  XrdSysMutexHelper scope_lock(mMutex);\n\n  if (mQuotaNode) {\n    mMapIdQuota[Index(kUserBytesIs, uid)] = 0;\n    mMapIdQuota[Index(kUserLogicalBytesIs, uid)] = 0;\n    mMapIdQuota[Index(kUserFilesIs, uid)] = 0;\n    mMapIdQuota[Index(kGroupBytesIs, gid)] = 0;\n    mMapIdQuota[Index(kGroupFilesIs, gid)] = 0;\n    mMapIdQuota[Index(kGroupLogicalBytesIs, gid)] = 0;\n    AddQuota(kUserBytesIs, uid, mQuotaNode->getPhysicalSpaceByUser(uid));\n    AddQuota(kUserLogicalBytesIs, uid, mQuotaNode->getUsedSpaceByUser(uid));\n    AddQuota(kUserFilesIs, uid, mQuotaNode->getNumFilesByUser(uid));\n    AddQuota(kGroupBytesIs, gid, mQuotaNode->getPhysicalSpaceByGroup(gid));\n    AddQuota(kGroupLogicalBytesIs, gid, mQuotaNode->getUsedSpaceByGroup(gid));\n    AddQuota(kGroupFilesIs, gid, mQuotaNode->getNumFilesByGroup(gid));\n    mMapIdQuota[Index(kUserBytesIs, Quota::gProjectId)] = 0;\n    mMapIdQuota[Index(kUserLogicalBytesIs, Quota::gProjectId)] = 0;\n    mMapIdQuota[Index(kUserFilesIs, Quota::gProjectId)] = 0;\n\n    if (upd_proj_quota) {\n      // Recalculate the project quota only every 5 seconds to boost perf.\n      static XrdSysMutex lMutex;\n      static time_t lUpdateTime = 0;\n      bool docalc = false;\n      {\n        XrdSysMutexHelper lock(lMutex);\n        time_t now = time(NULL);\n\n        if (lUpdateTime < now) {\n          // Next recalculation in 5 second\n          docalc = true;\n          lUpdateTime = now + 5;\n        }\n      }\n\n      if (docalc || (gid == Quota::gProjectId)) {\n        mMapIdQuota[Index(kGroupBytesIs, Quota::gProjectId)] = 0;\n        mMapIdQuota[Index(kGroupFilesIs, Quota::gProjectId)] = 0;\n        mMapIdQuota[Index(kGroupLogicalBytesIs, Quota::gProjectId)] = 0;\n        // Loop over users and fill project quota\n        auto uids = mQuotaNode->getUids();\n\n        for (auto itu = uids.begin(); itu != uids.end(); ++itu) {\n          AddQuota(kGroupBytesIs, Quota::gProjectId,\n                   mQuotaNode->getPhysicalSpaceByUser(*itu));\n          AddQuota(kGroupLogicalBytesIs, Quota::gProjectId,\n                   mQuotaNode->getUsedSpaceByUser(*itu));\n          AddQuota(kGroupFilesIs, Quota::gProjectId, mQuotaNode->getNumFilesByUser(*itu));\n        }\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Refresh counters\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::Refresh(time_t age)\n{\n  time_t now = time(NULL);\n\n  // since this loads all quota node info all the time, we don't do this for GetIndividualQuota in realtime all the time\n  if (age) {\n    if ((now - age) < mLastRefresh) {\n      return;\n    }\n  }\n\n  mLastRefresh = now;\n  AccountNsToSpace();\n  UpdateLogicalSizeFactor();\n  UpdateIsSums();\n  UpdateTargetSums();\n}\n\n\n//------------------------------------------------------------------------------\n// Print quota information\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::PrintOut(XrdOucString& output, long long int uid_sel,\n                     long long int gid_sel, bool monitoring, bool translate_ids)\n{\n  using eos::common::StringConversion;\n  // Make a map containing once all the defined uid's and gid's\n  std::vector<std::pair<std::string, unsigned>> uids, gids;\n  {\n    XrdSysMutexHelper scope_lock(mMutex);\n\n    // For project space we just print the user/group entry gProjectId\n    if (mMapIdQuota[Index(kGroupBytesTarget, Quota::gProjectId)] > 0) {\n      gid_sel = Quota::gProjectId;\n    }\n\n    for (auto it = mMapIdQuota.begin(); it != mMapIdQuota.end(); ++it) {\n      if ((UnIndex(it->first) >= kUserBytesIs) &&\n          (UnIndex(it->first) <= kUserFilesTarget)) {\n        long long int uid = (long long int)((it->first) & 0xffffffff);\n\n        // uid selection filter\n        if ((uid_sel >= 0LL) && (uid != uid_sel)) {\n          continue;\n        }\n\n        // we don't print the users if a gid is selected\n        if (gid_sel >= 0LL) {\n          continue;\n        }\n\n        // Translate IDs\n        std::string name = std::to_string(uid).c_str();\n        uids.push_back(std::make_pair(name, uid));\n      }\n\n      if ((UnIndex(it->first) >= kGroupBytesIs) &&\n          (UnIndex(it->first) <= kGroupFilesTarget)) {\n        long long int gid = (it->first) & 0xfffffff;\n\n        // uid selection filter\n        if ((gid_sel >= 0LL) && (gid != gid_sel)) {\n          continue;\n        }\n\n        // We don't print the group if a uid is selected\n        if (uid_sel >= 0LL) {\n          continue;\n        }\n\n        // Translate IDs\n        std::string name = std::to_string(gid).c_str();\n        gids.push_back(std::make_pair(name, gid));\n      }\n    }\n  }\n\n  // translate ids without mutex held\n  if (translate_ids) {\n    std::string name;\n    std::vector<std::pair<std::string, unsigned>> tuids, tgids;\n\n    for (auto u : uids) {\n      if (gid_sel == Quota::gProjectId) {\n        name = \"project\";\n      } else {\n        int errc = 0;\n        name = eos::common::Mapping::UidToUserName(u.second, errc);\n      }\n\n      tuids.push_back(std::make_pair(name, u.second));\n    }\n\n    for (auto g : gids) {\n      if (gid_sel == Quota::gProjectId) {\n        name = \"project\";\n      } else {\n        int errc = 0;\n        name = eos::common::Mapping::GidToGroupName(g.second, errc);\n      }\n\n      tgids.push_back(std::make_pair(name, g.second));\n    }\n\n    uids = tuids;\n    gids = tgids;\n  }\n\n  // Sort and erase duplicated uids and gids\n  eos_info(\"uids_size=%i, gids_size=%i\", uids.size(), gids.size());\n  std::sort(uids.begin(), uids.end());\n  uids.erase(std::unique(uids.begin(), uids.end()), uids.end());\n  std::sort(gids.begin(), gids.end());\n  gids.erase(std::unique(gids.begin(), gids.end()), gids.end());\n\n  if (EOS_LOGS_DEBUG) {\n    eos_debug(\"%s\",\"sorted\");\n\n    for (unsigned int i = 0; i < uids.size(); ++i) {\n      eos_debug(\"sort %d %d\", i, uids[i].second);\n    }\n\n    for (unsigned int i = 0; i < gids.size(); ++i) {\n      eos_debug(\"sort %d %d\", i, gids[i].second);\n    }\n  }\n\n  // Print the header for selected uid/gid's only if there is something to print\n  // If we have a full listing we print even empty quota nodes (=header only)\n  if (!monitoring) {\n    if (((uid_sel < 0) && (gid_sel < 0)) || !uids.empty() || !gids.empty()) {\n      output += \"\\n┏━> Quota Node: \";\n      output += pPath.c_str();\n      output += \"\\n\";\n    }\n  }\n\n  //! Quota node - Users\n  TableFormatterBase table_user;\n\n  if (!uids.empty()) {\n    // Table header\n    if (!monitoring) {\n      table_user.SetHeader({\n        std::make_tuple(GetTagCategory(kUserBytesIs), 10, \"-s\"),\n        std::make_tuple(GetTagName(kUserBytesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kUserLogicalBytesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kUserFilesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kUserBytesTarget), 10, \"+l\"),\n        std::make_tuple(GetTagName(kUserLogicalBytesTarget), 10, \"+l\"),\n        std::make_tuple(GetTagName(kUserFilesTarget), 10, \"+l\"),\n        std::make_tuple(\"filled[%]\", 10, \"f\"),\n        std::make_tuple(\"vol-status\", 10, \"s\"),\n        std::make_tuple(\"ino-status\", 10, \"s\")\n      });\n    } else {\n      table_user.SetHeader({\n        std::make_tuple(\"quota\", 0, \"os\"),\n        std::make_tuple(\"uid\", 0, \"os\"),\n        std::make_tuple(\"space\", 0, \"os\"),\n        std::make_tuple(\"usedbytes\", 0, \"ol\"),\n        std::make_tuple(\"usedlogicalbytes\", 0, \"ol\"),\n        std::make_tuple(\"usedfiles\", 0, \"ol\"),\n        std::make_tuple(\"maxbytes\", 0, \"ol\"),\n        std::make_tuple(\"maxlogicalbytes\", 0, \"ol\"),\n        std::make_tuple(\"maxfiles\", 0, \"ol\"),\n        std::make_tuple(\"percentageusedbytes\", 0, \"of\"),\n        std::make_tuple(\"statusbytes\", 0, \"os\"),\n        std::make_tuple(\"statusfiles\", 0, \"os\")\n      });\n    }\n\n    for (const auto& [name, id] : uids) {\n      eos_debug(\"loop with id=%d\", id);\n      TableData table_data;\n      table_data.emplace_back();\n\n      if (!monitoring) {\n        table_data.back().push_back(TableCell(name.c_str(), \"-s\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserBytesIs, id), \"+l\", \"B\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserLogicalBytesIs, id), \"+l\", \"B\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserFilesIs, id), \"+l\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserBytesTarget, id), \"+l\", \"B\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserLogicalBytesTarget, id), \"+l\", \"B\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserFilesTarget, id), \"+l\"));\n        table_data.back().push_back(TableCell(GetQuotaPercentage(\n                                                GetQuota(kUserLogicalBytesIs, id), GetQuota(kUserLogicalBytesTarget, id)), \"f\", \"%\"));\n        table_data.back().push_back(TableCell(GetQuotaStatus(\n                                                GetQuota(kUserLogicalBytesIs, id), GetQuota(kUserLogicalBytesTarget, id)), \"s\"));\n        table_data.back().push_back(TableCell(GetQuotaStatus(\n                                                GetQuota(kUserFilesIs, id), GetQuota(kUserFilesTarget, id)), \"s\"));\n      } else {\n        table_data.back().push_back(TableCell(\"node\", \"os\"));\n        table_data.back().push_back(TableCell(name.c_str(), \"os\"));\n        table_data.back().push_back(TableCell(pPath.c_str(), \"os\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserBytesIs, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserLogicalBytesIs, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserFilesIs, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserBytesTarget, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserLogicalBytesTarget, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kUserFilesTarget, id), \"ol\"));\n        table_data.back().push_back(TableCell(GetQuotaPercentage(\n                                                GetQuota(kUserLogicalBytesIs, id), GetQuota(kUserLogicalBytesTarget, id)), \"of\"));\n        table_data.back().push_back(TableCell(GetQuotaStatus(\n                                                GetQuota(kUserLogicalBytesIs, id), GetQuota(kUserLogicalBytesTarget, id)), \"os\"));\n        table_data.back().push_back(TableCell(GetQuotaStatus(\n                                                GetQuota(kUserFilesIs, id), GetQuota(kUserFilesTarget, id)), \"os\"));\n      }\n\n      table_user.AddRows(table_data);\n    }\n  }\n\n  if ((uid_sel < 0) && (gid_sel < 0)) {\n    output += table_user.GenerateTable(HEADER).c_str();\n  } else {\n    output += table_user.GenerateTable(HEADER2).c_str();\n  }\n\n  //! Quota node - Group\n  TableFormatterBase table_group;\n\n  if (!gids.empty()) {\n    // group loop\n    if (!monitoring) {\n      table_group.SetHeader({\n        std::make_tuple(GetTagCategory(kGroupBytesIs), 10, \"-s\"),\n        std::make_tuple(GetTagName(kGroupBytesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kGroupLogicalBytesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kGroupFilesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kGroupBytesTarget), 10, \"+l\"),\n        std::make_tuple(GetTagName(kGroupLogicalBytesTarget), 10, \"+l\"),\n        std::make_tuple(GetTagName(kGroupFilesTarget), 10, \"+l\"),\n        std::make_tuple(\"filled[%]\", 10, \"f\"),\n        std::make_tuple(\"vol-status\", 10, \"s\"),\n        std::make_tuple(\"ino-status\", 10, \"s\")\n      });\n    } else {\n      table_group.SetHeader({\n        std::make_tuple(\"quota\", 0, \"os\"),\n        std::make_tuple(\"gid\", 0, \"os\"),\n        std::make_tuple(\"space\", 0, \"os\"),\n        std::make_tuple(\"usedbytes\", 0, \"ol\"),\n        std::make_tuple(\"usedlogicalbytes\", 0, \"ol\"),\n        std::make_tuple(\"usedfiles\", 0, \"ol\"),\n        std::make_tuple(\"maxbytes\", 0, \"ol\"),\n        std::make_tuple(\"maxlogicalbytes\", 0, \"ol\"),\n        std::make_tuple(\"maxfiles\", 0, \"ol\"),\n        std::make_tuple(\"percentageusedbytes\", 0, \"of\"),\n        std::make_tuple(\"statusbytes\", 0, \"os\"),\n        std::make_tuple(\"statusfiles\", 0, \"os\")\n      });\n    }\n\n    for (const auto& [name, id] : gids) {\n      eos_debug(\"loop with id=%d\", id);\n      TableData table_data;\n      table_data.emplace_back();\n\n      if (!monitoring) {\n        table_data.back().push_back(TableCell(name.c_str(), \"-s\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupBytesIs, id), \"+l\", \"B\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupLogicalBytesIs, id), \"+l\", \"B\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupFilesIs, id), \"+l\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupBytesTarget, id), \"+l\", \"B\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupLogicalBytesTarget, id), \"+l\", \"B\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupFilesTarget, id), \"+l\"));\n        table_data.back().push_back(TableCell(GetQuotaPercentage(\n                                                GetQuota(kGroupLogicalBytesIs, id), GetQuota(kGroupLogicalBytesTarget, id)), \"f\", \"%\"));\n        table_data.back().push_back(TableCell(GetQuotaStatus(\n                                                GetQuota(kGroupLogicalBytesIs, id), GetQuota(kGroupLogicalBytesTarget, id)), \"s\"));\n        table_data.back().push_back(TableCell(GetQuotaStatus(\n                                                GetQuota(kGroupFilesIs, id), GetQuota(kGroupFilesTarget, id)), \"s\"));\n      } else {\n        table_data.back().push_back(TableCell(\"node\", \"os\"));\n        table_data.back().push_back(TableCell(name.c_str(), \"os\"));\n        table_data.back().push_back(TableCell(pPath.c_str(), \"os\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupBytesIs, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupLogicalBytesIs, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupFilesIs, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupBytesTarget, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupLogicalBytesTarget, id), \"ol\"));\n        table_data.back().push_back(TableCell(\n                                      GetQuota(kGroupFilesTarget, id), \"ol\"));\n        table_data.back().push_back(TableCell(GetQuotaPercentage(\n                                                GetQuota(kGroupLogicalBytesIs, id), GetQuota(kGroupLogicalBytesTarget, id)), \"of\"));\n        table_data.back().push_back(TableCell(GetQuotaStatus(\n                                                GetQuota(kGroupLogicalBytesIs, id), GetQuota(kGroupLogicalBytesTarget, id)), \"os\"));\n        table_data.back().push_back(TableCell(GetQuotaStatus(\n                                                GetQuota(kGroupFilesIs, id), GetQuota(kGroupFilesTarget, id)), \"os\"));\n      }\n\n      table_group.AddRows(table_data);\n    }\n  }\n\n  if ((uid_sel < 0) && (gid_sel < 0)) {\n    output += table_group.GenerateTable(HEADER).c_str();\n  } else {\n    output += table_group.GenerateTable(HEADER2).c_str();\n  }\n\n  //! Quota node - Summary\n  if ((uid_sel < 0) && (gid_sel < 0)) {\n    if (!monitoring) {\n      TableFormatterBase table_summary;\n      table_summary.SetHeader({\n        std::make_tuple(\"summary\", 10, \"-s\"),\n        std::make_tuple(GetTagName(kAllUserBytesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kAllUserLogicalBytesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kAllUserFilesIs), 10, \"+l\"),\n        std::make_tuple(GetTagName(kAllUserBytesTarget), 10, \"+l\"),\n        std::make_tuple(GetTagName(kAllUserLogicalBytesTarget), 10, \"+l\"),\n        std::make_tuple(GetTagName(kAllUserFilesTarget), 10, \"+l\"),\n        std::make_tuple(\"filled[%]\", 10, \"f\"),\n        std::make_tuple(\"vol-status\", 10, \"s\"),\n        std::make_tuple(\"ino-status\", 10, \"s\")\n      });\n      TableData table_data;\n      table_data.emplace_back();\n      table_data.back().push_back(TableCell(\"All users\", \"-s\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserBytesIs, 0), \"+l\", \"B\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserLogicalBytesIs, 0), \"+l\", \"B\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserFilesIs, 0), \"+l\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserBytesTarget, 0), \"+l\", \"B\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserLogicalBytesTarget, 0), \"+l\", \"B\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserFilesTarget, 0), \"+l\"));\n      table_data.back().push_back(TableCell(GetQuotaPercentage(\n                                              GetQuota(kAllUserLogicalBytesIs, 0), GetQuota(kAllUserLogicalBytesTarget, 0)), \"f\", \"%\"));\n      table_data.back().push_back(TableCell(GetQuotaStatus(\n                                              GetQuota(kAllUserLogicalBytesIs, 0), GetQuota(kAllUserLogicalBytesTarget, 0)), \"s\"));\n      table_data.back().push_back(TableCell(GetQuotaStatus(\n                                              GetQuota(kAllUserFilesIs, 0), GetQuota(kAllUserFilesTarget, 0)), \"s\"));\n      table_data.emplace_back();\n      table_data.back().push_back(TableCell(\"All groups\", \"-ls\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupBytesIs, 0), \"+l\", \"B\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupLogicalBytesIs, 0), \"+l\", \"B\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupFilesIs, 0), \"+l\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupBytesTarget, 0), \"+l\", \"B\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupLogicalBytesTarget, 0), \"+l\", \"B\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupFilesTarget, 0), \"+l\"));\n      table_data.back().push_back(TableCell(GetQuotaPercentage(\n                                              GetQuota(kAllGroupLogicalBytesIs, 0), GetQuota(kAllGroupLogicalBytesTarget, 0)), \"f\", \"%\"));\n      table_data.back().push_back(TableCell(GetQuotaStatus(\n                                              GetQuota(kAllGroupLogicalBytesIs, 0), GetQuota(kAllGroupLogicalBytesTarget, 0)), \"s\"));\n      table_data.back().push_back(TableCell(GetQuotaStatus(\n                                              GetQuota(kAllGroupFilesIs, 0), GetQuota(kAllGroupFilesTarget, 0)), \"s\"));\n      table_summary.AddRows(table_data);\n      output += table_summary.GenerateTable(HEADER2).c_str();\n    } else {\n      TableFormatterBase table_summary_user;\n      table_summary_user.SetHeader({\n        std::make_tuple(\"quota\", 0, \"os\"),\n        std::make_tuple(\"uid\", 0, \"os\"),\n        std::make_tuple(\"space\", 0, \"os\"),\n        std::make_tuple(\"usedbytes\", 0, \"ol\"),\n        std::make_tuple(\"usedlogicalbytes\", 0, \"ol\"),\n        std::make_tuple(\"usedfiles\", 0, \"ol\"),\n        std::make_tuple(\"maxbytes\", 0, \"ol\"),\n        std::make_tuple(\"maxlogicalbytes\", 0, \"ol\"),\n        std::make_tuple(\"maxfiles\", 0, \"ol\"),\n        std::make_tuple(\"percentageusedbytes\", 0, \"of\"),\n        std::make_tuple(\"statusbytes\", 0, \"os\"),\n        std::make_tuple(\"statusfiles\", 0, \"os\")\n      });\n      TableData table_data;\n      table_data.emplace_back();\n      table_data.back().push_back(TableCell(\"node\", \"os\"));\n      table_data.back().push_back(TableCell(\"ALL\", \"os\"));\n      table_data.back().push_back(TableCell(pPath.c_str(), \"os\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserBytesIs, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserLogicalBytesIs, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserFilesIs, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserBytesTarget, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserLogicalBytesTarget, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllUserFilesTarget, 0), \"ol\"));\n      table_data.back().push_back(TableCell(GetQuotaPercentage(\n                                              GetQuota(kAllUserLogicalBytesIs, 0), GetQuota(kAllUserLogicalBytesTarget, 0)), \"of\"));\n      table_data.back().push_back(TableCell(GetQuotaStatus(\n                                              GetQuota(kAllUserLogicalBytesIs, 0), GetQuota(kAllUserLogicalBytesTarget, 0)), \"os\"));\n      table_data.back().push_back(TableCell(GetQuotaStatus(\n                                              GetQuota(kAllUserFilesIs, 0), GetQuota(kAllUserFilesTarget, 0)), \"os\"));\n      table_summary_user.AddRows(table_data);\n      output += table_summary_user.GenerateTable().c_str();\n      TableFormatterBase table_summary_group;\n      table_summary_group.SetHeader({\n        std::make_tuple(\"quota\", 0, \"os\"),\n        std::make_tuple(\"gid\", 0, \"os\"),\n        std::make_tuple(\"space\", 0, \"os\"),\n        std::make_tuple(\"usedbytes\", 0, \"ol\"),\n        std::make_tuple(\"usedlogicalbytes\", 0, \"ol\"),\n        std::make_tuple(\"usedfiles\", 0, \"ol\"),\n        std::make_tuple(\"maxbytes\", 0, \"ol\"),\n        std::make_tuple(\"maxlogicalbytes\", 0, \"ol\"),\n        std::make_tuple(\"maxfiles\", 0, \"ol\"),\n        std::make_tuple(\"percentageusedbytes\", 0, \"of\"),\n        std::make_tuple(\"statusbytes\", 0, \"os\"),\n        std::make_tuple(\"statusfiles\", 0, \"os\")\n      });\n      table_data.clear();\n      table_data.emplace_back();\n      table_data.back().push_back(TableCell(\"node\", \"os\"));\n      table_data.back().push_back(TableCell(\"ALL\", \"os\"));\n      table_data.back().push_back(TableCell(pPath.c_str(), \"os\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupBytesIs, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupLogicalBytesIs, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupFilesIs, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupBytesTarget, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupLogicalBytesTarget, 0), \"ol\"));\n      table_data.back().push_back(TableCell(\n                                    GetQuota(kAllGroupFilesTarget, 0), \"ol\"));\n      table_data.back().push_back(TableCell(GetQuotaPercentage(\n                                              GetQuota(kAllGroupLogicalBytesIs, 0), GetQuota(kAllGroupLogicalBytesTarget, 0)), \"of\"));\n      table_data.back().push_back(TableCell(GetQuotaStatus(\n                                              GetQuota(kAllGroupLogicalBytesIs, 0), GetQuota(kAllGroupLogicalBytesTarget, 0)), \"os\"));\n      table_data.back().push_back(TableCell(GetQuotaStatus(\n                                              GetQuota(kAllGroupFilesIs, 0), GetQuota(kAllGroupFilesTarget, 0)), \"os\"));\n      table_summary_group.AddRows(table_data);\n      output += table_summary_group.GenerateTable().c_str();\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// User/group/project quota checks. If both user and group quotas are defined,\n// then both need to be satisfied.\n//------------------------------------------------------------------------------\nbool\nSpaceQuota::CheckWriteQuota(uid_t uid, gid_t gid, long long desired_vol,\n                            unsigned int inodes)\n{\n  bool hasquota = false;\n  // Update info from the ns quota node - user, group and project quotas\n  UpdateFromQuotaNode(uid, gid, GetQuota(kGroupLogicalBytesTarget, Quota::gProjectId)\n                      ? true : false);\n  eos_info(\"uid=%d gid=%d size=%llu quota=%llu\", uid, gid, desired_vol,\n           GetQuota(kUserLogicalBytesTarget, uid));\n  bool userquota = false;\n  bool groupquota = false;\n  bool projectquota = false;\n  bool hasuserquota = false;\n  bool hasgroupquota = false;\n  bool hasprojectquota = false;\n  bool uservolumequota = false;\n  bool userinodequota = false;\n  bool groupvolumequota = false;\n  bool groupinodequota = false;\n\n  if (GetQuota(kUserLogicalBytesTarget, uid) > 0) {\n    userquota = true;\n    uservolumequota = true;\n  }\n\n  if (GetQuota(kGroupLogicalBytesTarget, gid) > 0) {\n    groupquota = true;\n    groupvolumequota = true;\n  }\n\n  if (GetQuota(kUserFilesTarget, uid) > 0) {\n    userquota = true;\n    userinodequota = true;\n  }\n\n  if (GetQuota(kGroupFilesTarget, gid) > 0) {\n    groupquota = true;\n    groupinodequota = true;\n  }\n\n  if (uservolumequota) {\n    if ((GetQuota(kUserLogicalBytesTarget, uid) - GetQuota(kUserLogicalBytesIs,\n         uid)) > (long long)desired_vol) {\n      hasuserquota = true;\n    } else {\n      hasuserquota = false;\n    }\n  }\n\n  if (userinodequota) {\n    // The +1 comes from the fact the the current file is already accounted to\n    // the ns quota by doing ns_quota->addFile previously in the open function.\n    if ((GetQuota(kUserFilesTarget, uid) - GetQuota(kUserFilesIs,\n         uid) + 1) >= inodes) {\n      if (!uservolumequota) {\n        hasuserquota = true;\n      }\n    } else {\n      hasuserquota = false;\n    }\n  }\n\n  if (groupvolumequota) {\n    if ((GetQuota(kGroupLogicalBytesTarget, gid) - GetQuota(kGroupLogicalBytesIs,\n         gid)) > desired_vol) {\n      hasgroupquota = true;\n    } else {\n      hasgroupquota = false;\n    }\n  }\n\n  if (groupinodequota) {\n    if ((GetQuota(kGroupFilesTarget, gid) - GetQuota(kGroupFilesIs,\n         gid)) > inodes) {\n      if (!groupvolumequota) {\n        hasgroupquota = true;\n      }\n    } else {\n      hasgroupquota = false;\n    }\n  }\n\n  if (((GetQuota(kGroupLogicalBytesTarget, Quota::gProjectId) -\n        GetQuota(kGroupLogicalBytesIs, Quota::gProjectId)) > desired_vol)) {\n    hasprojectquota = true;\n\n    if ((GetQuota(kGroupFilesTarget, Quota::gProjectId)) &&\n        ((GetQuota(kGroupFilesTarget, Quota::gProjectId) <\n          (GetQuota(kGroupFilesIs, Quota::gProjectId) + inodes)))) {\n      hasprojectquota = false;\n    }\n  }\n\n  if (!userquota && !groupquota) {\n    projectquota = true;\n  }\n\n  eos_info(\"userquota=%d groupquota=%d hasuserquota=%d hasgroupquota=%d \"\n           \"userinodequota=%d uservolumequota=%d projectquota=%d \"\n           \"hasprojectquota=%d\", userquota, groupquota, hasuserquota,\n           hasgroupquota, userinodequota, uservolumequota, projectquota,\n           hasprojectquota);\n\n  // If both quotas are defined we need to have both\n  if (userquota && groupquota) {\n    hasquota = hasuserquota & hasgroupquota;\n  } else {\n    hasquota = hasuserquota || hasgroupquota;\n  }\n\n  if (projectquota && hasprojectquota) {\n    hasquota = true;\n  }\n\n  // Root does not need any quota\n  if (uid == 0) {\n    hasquota = true;\n  }\n\n  return hasquota;\n}\n\n//------------------------------------------------------------------------------\n// Import ns quota values into current space quota\n//------------------------------------------------------------------------------\nvoid\nSpaceQuota::AccountNsToSpace()\n{\n  if (UpdateQuotaNodeAddress()) {\n    XrdSysMutexHelper scope_lock(mMutex);\n    // Insert current state of a single quota node into a SpaceQuota\n    ResetQuota(kGroupBytesIs, Quota::gProjectId);\n    ResetQuota(kGroupFilesIs, Quota::gProjectId);\n    ResetQuota(kGroupLogicalBytesIs, Quota::gProjectId);\n    // Loop over users\n    auto uids = mQuotaNode->getUids();\n\n    for (auto itu = uids.begin(); itu != uids.end(); ++itu) {\n      ResetQuota(kUserBytesIs, *itu);\n      AddQuota(kUserBytesIs, *itu, mQuotaNode->getPhysicalSpaceByUser(*itu));\n      ResetQuota(kUserFilesIs, *itu);\n      AddQuota(kUserFilesIs, *itu, mQuotaNode->getNumFilesByUser(*itu));\n      ResetQuota(kUserLogicalBytesIs, *itu);\n      AddQuota(kUserLogicalBytesIs, *itu, mQuotaNode->getUsedSpaceByUser(*itu));\n\n      if (mMapIdQuota[Index(kGroupBytesTarget, Quota::gProjectId)] > 0) {\n        // Only account in project quota nodes\n        AddQuota(kGroupBytesIs, Quota::gProjectId,\n                 mQuotaNode->getPhysicalSpaceByUser(*itu));\n        AddQuota(kGroupLogicalBytesIs, Quota::gProjectId,\n                 mQuotaNode->getUsedSpaceByUser(*itu));\n        AddQuota(kGroupFilesIs, Quota::gProjectId, mQuotaNode->getNumFilesByUser(*itu));\n      }\n    }\n\n    auto gids = mQuotaNode->getGids();\n\n    for (auto itg = gids.begin(); itg != gids.end(); ++itg) {\n      // Don't update the project quota directory from the quota\n      if (*itg == Quota::gProjectId) {\n        continue;\n      }\n\n      ResetQuota(kGroupBytesIs, *itg);\n      AddQuota(kGroupBytesIs, *itg, mQuotaNode->getPhysicalSpaceByGroup(*itg));\n      ResetQuota(kGroupFilesIs, *itg);\n      AddQuota(kGroupFilesIs, *itg, mQuotaNode->getNumFilesByGroup(*itg));\n      ResetQuota(kGroupLogicalBytesIs, *itg);\n      AddQuota(kGroupLogicalBytesIs, *itg, mQuotaNode->getUsedSpaceByGroup(*itg));\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Convert int tag to string representation\n//------------------------------------------------------------------------------\nconst char* SpaceQuota::GetTagAsString(int tag)\n{\n  if (tag == kUserBytesTarget) {\n    return \"userbytes\";\n  }\n\n  if (tag == kUserLogicalBytesTarget) {\n    return \"userlogicalbytes\";\n  }\n\n  if (tag == kUserFilesTarget) {\n    return \"userfiles\";\n  }\n\n  if (tag == kGroupBytesTarget) {\n    return \"groupbytes\";\n  }\n\n  if (tag == kGroupLogicalBytesTarget) {\n    return \"grouplogicalbytes\";\n  }\n\n  if (tag == kGroupFilesTarget) {\n    return \"groupfiles\";\n  }\n\n  if (tag == kAllUserBytesTarget) {\n    return \"alluserbytes\";\n  }\n\n  if (tag == kAllUserLogicalBytesTarget) {\n    return \"alluserlogicalbytes\";\n  }\n\n  if (tag == kAllUserFilesTarget) {\n    return \"alluserfiles\";\n  }\n\n  if (tag == kAllGroupBytesTarget) {\n    return \"allgroupbytes\";\n  }\n\n  if (tag == kAllGroupLogicalBytesTarget) {\n    return \"allgrouplogicalbytes\";\n  }\n\n  if (tag == kAllGroupFilesTarget) {\n    return \"allgroupfiles\";\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Convert string tag to int representation\n//------------------------------------------------------------------------------\nunsigned long SpaceQuota::GetTagFromString(const std::string& tag)\n{\n  if (tag == \"userbytes\") {\n    return kUserBytesTarget;\n  }\n\n  if (tag == \"userlogicalbytes\") {\n    return kUserLogicalBytesTarget;\n  }\n\n  if (tag == \"userfiles\") {\n    return kUserFilesTarget;\n  }\n\n  if (tag == \"groupbytes\") {\n    return kGroupBytesTarget;\n  }\n\n  if (tag == \"grouplogicalbytes\") {\n    return kGroupLogicalBytesTarget;\n  }\n\n  if (tag == \"groupfiles\") {\n    return kGroupFilesTarget;\n  }\n\n  if (tag == \"alluserbytes\") {\n    return kAllUserBytesTarget;\n  }\n\n  if (tag == \"alluserlogicalbytes\") {\n    return kAllUserLogicalBytesTarget;\n  }\n\n  if (tag == \"alluserfiles\") {\n    return kAllUserFilesTarget;\n  }\n\n  if (tag == \"allgroupbytes\") {\n    return kAllGroupBytesTarget;\n  }\n\n  if (tag == \"allgrouplogicalbytes\") {\n    return kAllGroupLogicalBytesTarget;\n  }\n\n  if (tag == \"allgroupfiles\") {\n    return kAllGroupFilesTarget;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Convert int tag to user or group category\n//------------------------------------------------------------------------------\nconst char* SpaceQuota::GetTagCategory(int tag)\n{\n  if ((tag == kUserBytesIs) || (tag == kUserBytesTarget) ||\n      (tag == kUserLogicalBytesIs) || (tag == kUserLogicalBytesTarget) ||\n      (tag == kUserFilesIs) || (tag == kUserFilesTarget) ||\n      (tag == kAllUserBytesIs) || (tag == kAllUserBytesTarget) ||\n      (tag == kAllUserFilesIs) || (tag == kAllUserFilesTarget)) {\n    return \"user\";\n  }\n\n  if ((tag == kGroupBytesIs) || (tag == kGroupBytesTarget) ||\n      (tag == kGroupLogicalBytesIs) || (tag == kGroupLogicalBytesTarget) ||\n      (tag == kGroupFilesIs) || (tag == kGroupFilesTarget) ||\n      (tag == kAllGroupBytesIs) || (tag == kAllGroupBytesTarget) ||\n      (tag == kAllGroupFilesIs) || (tag == kAllGroupFilesTarget)) {\n    return \"group\";\n  }\n\n  return \"-----\";\n}\n\n//------------------------------------------------------------------------------\n// Convert int tag to string description\n//------------------------------------------------------------------------------\nconst char* SpaceQuota::GetTagName(int tag)\n{\n  if (tag == kUserBytesIs) {\n    return \"used bytes\";\n  }\n\n  if (tag == kUserLogicalBytesIs) {\n    return \"logi bytes\";\n  }\n\n  if (tag == kUserBytesTarget) {\n    return \"aval bytes\";\n  }\n\n  if (tag == kUserFilesIs) {\n    return \"used files\";\n  }\n\n  if (tag == kUserFilesTarget) {\n    return \"aval files\";\n  }\n\n  if (tag == kUserLogicalBytesTarget) {\n    return \"aval logib\";\n  }\n\n  if (tag == kGroupBytesIs) {\n    return \"used bytes\";\n  }\n\n  if (tag == kGroupLogicalBytesIs) {\n    return \"logi bytes\";\n  }\n\n  if (tag == kGroupBytesTarget) {\n    return \"aval bytes\";\n  }\n\n  if (tag == kGroupFilesIs) {\n    return \"used files\";\n  }\n\n  if (tag == kGroupFilesTarget) {\n    return \"aval files\";\n  }\n\n  if (tag == kGroupLogicalBytesTarget) {\n    return \"aval logib\";\n  }\n\n  if (tag == kAllUserBytesIs) {\n    return \"used bytes\";\n  }\n\n  if (tag == kAllUserLogicalBytesIs) {\n    return \"logi bytes\";\n  }\n\n  if (tag == kAllUserBytesTarget) {\n    return \"aval bytes\";\n  }\n\n  if (tag == kAllUserFilesIs) {\n    return \"used files\";\n  }\n\n  if (tag == kAllUserFilesTarget) {\n    return \"aval files\";\n  }\n\n  if (tag == kAllUserLogicalBytesTarget) {\n    return \"aval logib\";\n  }\n\n  if (tag == kAllGroupBytesIs) {\n    return \"used bytes\";\n  }\n\n  if (tag == kAllGroupLogicalBytesIs) {\n    return \"logi bytes\";\n  }\n\n  if (tag == kAllGroupBytesTarget) {\n    return \"aval bytes\";\n  }\n\n  if (tag == kAllGroupFilesIs) {\n    return \"used files\";\n  }\n\n  if (tag == kAllGroupFilesTarget) {\n    return \"aval files\";\n  }\n\n  if (tag == kAllGroupLogicalBytesTarget) {\n    return \"aval logib\";\n  }\n\n  return \"---- -----\";\n}\n\n//------------------------------------------------------------------------------\n// *** Class Quota implementaion ***\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Get space quota object for exact path - caller has to have a read lock on\n// pMapMutex.\n//------------------------------------------------------------------------------\nSpaceQuota*\nQuota::GetSpaceQuota(const std::string& qpath)\n{\n  std::string path = NormalizePath(qpath);\n\n  if (pMapQuota.count(path)) {\n    return pMapQuota[path];\n  } else {\n    return nullptr;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get space quota object responsible for path (find best match) - caller has\n// to have a read lock on pMapMutex.\n//------------------------------------------------------------------------------\nSpaceQuota*\nQuota::GetResponsibleSpaceQuota(const std::string& path)\n{\n  SpaceQuota* squota = nullptr;\n\n  for (auto it = pMapQuota.begin(); it != pMapQuota.end(); ++it) {\n    if (common::startsWith(path, it->second->GetSpaceName())) {\n      if (squota == nullptr) {\n        squota = it->second;\n      }\n\n      // Save if it's a better match\n      if (it->second->GetSpaceNameStr().size() > squota->GetSpaceNameStr().size()) {\n        squota = it->second;\n      }\n    }\n  }\n\n  return squota;\n}\n\n//----------------------------------------------------------------------------\n//  Get space quota node path\n//----------------------------------------------------------------------------\nstd::string\nQuota::GetResponsibleSpaceQuotaPath(const std::string& path)\n{\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetResponsibleSpaceQuota(path);\n\n  if (squota) {\n    return squota->GetSpaceNameStr();\n  } else {\n    return \"\";\n  }\n}\n\n\n\n//------------------------------------------------------------------------------\n// Check if space quota exists\n//------------------------------------------------------------------------------\nbool\nQuota::Exists(const std::string& qpath)\n{\n  std::string path = NormalizePath(qpath);\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  return (pMapQuota.count(path) != 0);\n}\n\n//------------------------------------------------------------------------------\n// Check if there is a SpaceQuota responsible for the given path\n//------------------------------------------------------------------------------\nbool\nQuota::ExistsResponsible(const std::string& path)\n{\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  return (GetResponsibleSpaceQuota(path) != 0);\n}\n\n//------------------------------------------------------------------------------\n// Get individual quota - called only from mgm/http/webdav/PropFindResponse\n//------------------------------------------------------------------------------\nvoid\nQuota::GetIndividualQuota(eos::common::VirtualIdentity& vid,\n                          const std::string& path,\n                          long long& max_bytes,\n                          long long& free_bytes,\n                          long long& max_files,\n                          long long& free_files,\n                          bool logical)\n{\n  // Check for sys.auth='*'\n  eos::common::VirtualIdentity m_vid = vid;\n  std::string ownerauth;\n  XrdOucErrInfo error;\n  struct stat buf;\n\n  if (!gOFS->_stat(path.c_str(), &buf, error, vid, \"\")) {\n    gOFS->_attr_get(path.c_str(), error, vid, \"\", \"sys.owner.auth\", ownerauth);\n\n    if (ownerauth.length()) {\n      if (ownerauth == \"*\") {\n        eos_static_info(\"msg=\\\"client authenticated as directory owner\\\" \"\n                        \"path=\\\"%s\\\"uid=\\\"%u=>%u\\\" gid=\\\"%u=>%u\\\"\", path.c_str(),\n                        vid.uid, vid.gid, buf.st_uid, buf.st_gid);\n        // The client can operate as the owner, we rewrite the virtual id\n        m_vid.uid = buf.st_uid;\n        m_vid.gid = buf.st_gid;\n      } else {\n        ownerauth += \",\";\n        std::string ownerkey = vid.prot.c_str();\n        ownerkey += \":\";\n\n        if (vid.prot == \"gsi\") {\n          ownerkey += vid.dn.c_str();\n        } else {\n          ownerkey += vid.uid_string.c_str();\n        }\n\n        if ((ownerauth.find(ownerkey)) != std::string::npos) {\n          eos_static_info(\"msg=\\\"client authenticated as directory owner\\\" \"\n                          \"path=\\\"%s\\\"uid=\\\"%u=>%u\\\" gid=\\\"%u=>%u\\\"\", path.c_str(),\n                          vid.uid, vid.gid, buf.st_uid, buf.st_gid);\n          // The client can operate as the owner, we rewrite the virtual id\n          m_vid.uid = buf.st_uid;\n          m_vid.gid = buf.st_gid;\n        }\n      }\n    }\n  }\n\n  eos::common::RWMutexReadLock rd_ns_lock(gOFS->eosViewRWMutex);\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* space = GetResponsibleSpaceQuota(path);\n\n  if (space) {\n    space->Refresh(60);\n    long long max_bytes_usr, max_bytes_grp, max_bytes_prj;\n    long long free_bytes_usr, free_bytes_grp, free_bytes_prj;\n    long long max_files_usr, max_files_grp, max_files_prj;\n    long long free_files_usr, free_files_grp, free_files_prj;\n    free_bytes_usr = free_bytes_grp = free_bytes_prj = 0;\n    max_bytes_usr = max_bytes_grp = max_bytes_prj = 0;\n    free_files_usr = free_files_grp = free_files_prj = 0;\n    (void) free_files_usr; // not used - avoid compile warning\n    max_files_usr = max_files_grp = max_files_prj = 0;\n    (void) max_files_usr; // not used -avoid compile warning\n\n    if(logical) {\n      max_bytes_usr  = space->GetQuota(SpaceQuota::kUserLogicalBytesTarget, m_vid.uid);\n      max_bytes_grp = space->GetQuota(SpaceQuota::kGroupLogicalBytesTarget, m_vid.gid);\n      max_bytes_prj = space->GetQuota(SpaceQuota::kGroupLogicalBytesTarget, Quota::gProjectId);\n\n      free_bytes_usr = max_bytes_usr -\n        space->GetQuota(SpaceQuota::kUserLogicalBytesIs, m_vid.uid);\n      free_bytes_grp = max_bytes_grp -\n        space->GetQuota(SpaceQuota::kGroupLogicalBytesIs, m_vid.gid);\n      free_bytes_prj = max_bytes_prj -\n        space->GetQuota(SpaceQuota::kGroupLogicalBytesIs, Quota::gProjectId);\n    } else {\n      max_bytes_usr = space->GetQuota(SpaceQuota::kUserBytesTarget, m_vid.uid);\n      max_bytes_grp = space->GetQuota(SpaceQuota::kGroupBytesTarget, m_vid.gid);\n      max_bytes_prj = space->GetQuota(SpaceQuota::kGroupBytesTarget, Quota::gProjectId);\n\n      free_bytes_usr = max_bytes_usr -\n        space->GetQuota(SpaceQuota::kUserBytesIs, m_vid.uid);\n      free_bytes_grp = max_bytes_grp -\n        space->GetQuota(SpaceQuota::kGroupBytesIs, m_vid.gid);\n      free_bytes_prj = max_bytes_prj -\n        space->GetQuota(SpaceQuota::kGroupBytesIs, Quota::gProjectId);\n    }\n\n    if (free_bytes_usr > free_bytes) {\n      free_bytes = free_bytes_usr;\n    }\n\n    if (free_bytes_grp > free_bytes) {\n      free_bytes = free_bytes_grp;\n    }\n\n    if (free_bytes_prj > free_bytes) {\n      free_bytes = free_bytes_prj;\n    }\n\n    if (max_bytes_usr > max_bytes) {\n      max_bytes = max_bytes_usr;\n    }\n\n    if (max_bytes_grp > max_bytes) {\n      max_bytes = max_bytes_grp;\n    }\n\n    if (max_bytes_prj > max_bytes) {\n      max_bytes = max_bytes_prj;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Set quota type for id\n//------------------------------------------------------------------------------\nbool\nQuota::SetQuotaTypeForId(const std::string& qpath, long id, Quota::IdT id_type,\n                         Quota::Type quota_type, unsigned long long value,\n                         std::string& msg, int& retc)\n{\n  std::ostringstream oss_msg;\n  std::string path = NormalizePath(qpath);\n  retc = EINVAL;\n\n  // If no path use \"/eos/\"\n  if (path.empty()) {\n    path = \"/eos/\";\n  }\n\n  // Make sure the quota node exist\n  if (!Create(path)) {\n    oss_msg << \"error: failed to create quota node: \" << path;\n    msg = oss_msg.str();\n    return false;\n  }\n\n  // Get type of quota to set and construct config entry\n  std::ostringstream oss_config;\n  SpaceQuota::eQuotaTag quota_tag;\n  oss_config << path << \":\";\n\n  if (id_type == IdT::kUid) {\n    oss_config << \"uid=\";\n\n    if (quota_type == Type::kVolume) {\n      quota_tag = SpaceQuota::kUserLogicalBytesTarget;\n    } else {\n      quota_tag = SpaceQuota::kUserFilesTarget;\n    }\n  } else {\n    oss_config << \"gid=\";\n\n    if (quota_type == Type::kVolume) {\n      quota_tag = SpaceQuota::kGroupLogicalBytesTarget;\n    } else {\n      quota_tag = SpaceQuota::kGroupFilesTarget;\n    }\n  }\n\n  oss_config << id << \":\" << SpaceQuota::GetTagAsString(quota_tag);\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetSpaceQuota(path);\n\n  if (!squota) {\n    oss_msg << \"error: no quota space defined for node \" << path;\n    msg = oss_msg.str();\n    return false;\n  }\n\n  // When setting logical bytes quota, set also raw bytes for backward compatibility\n  if (quota_type == Type::kVolume) {\n    long long raw_bytes, log_bytes;\n\n    std::string raw_config = oss_config.str();\n    raw_config.erase(raw_config.find(\"logical\"), 7);\n\n    SpaceQuota::eQuotaTag quota_raw = (id_type == IdT::kUid) ? SpaceQuota::kUserBytesTarget\n                                                             : SpaceQuota::kGroupBytesTarget;\n\n    if (getenv(\"EOS_MGM_QUOTA_SET_BY_LOGICAL\")) {\n      log_bytes = value;\n      raw_bytes = value * squota->GetLayoutSizeFactor();\n    } else {\n      log_bytes = value / squota->GetLayoutSizeFactor();\n      raw_bytes = value;\n    }\n\n    std::string log_value = std::to_string(log_bytes);\n    std::string raw_value = std::to_string(raw_bytes);\n\n    oss_msg << \"updating quota using \" << log_bytes << \" bytes (\" << raw_bytes << \" raw bytes)\\n\";\n\n    squota->SetQuota(quota_tag, id, log_bytes);\n    squota->SetQuota(quota_raw, id, raw_bytes);\n    gOFS->mConfigEngine->SetConfigValue(\"quota\", oss_config.str().c_str(), log_value.c_str());\n    gOFS->mConfigEngine->SetConfigValue(\"quota\", raw_config.c_str(), raw_value.c_str());\n  } else {\n    std::string svalue = std::to_string(value);\n    squota->SetQuota(quota_tag, id, value);\n    gOFS->mConfigEngine->SetConfigValue(\"quota\", oss_config.str().c_str(), svalue.c_str());\n  }\n\n  oss_msg << \"success: updated \"\n          << ((quota_type == Type::kVolume) ? \"volume\" : \"inode\")\n          << \" quota for \"\n          << ((id_type == IdT::kUid) ? \"uid=\" : \"gid=\") << id\n          << \" for node \" << path << std::endl;\n  msg = oss_msg.str();\n  retc = 0;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Set quota depending on the quota tag.\n//------------------------------------------------------------------------------\nbool\nQuota::SetQuotaForTag(const std::string& qpath,\n                      const std::string& quota_stag,\n                      long id, unsigned long long value)\n{\n  unsigned long spaceq_type = SpaceQuota::GetTagFromString(quota_stag);\n  // Make sure the quota node exists\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetSpaceQuota(qpath);\n\n  if (squota) {\n    squota->SetQuota(spaceq_type, id, value);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Remove quota type for id\n//------------------------------------------------------------------------------\nbool\nQuota::RmQuotaTypeForId(const std::string& qpath, long id, Quota::IdT id_type,\n                        Quota::Type quota_type, std::string& msg, int& retc)\n{\n  std::ostringstream oss_msg;\n  std::string path = NormalizePath(qpath);\n  retc = EINVAL;\n\n  // If no path use \"/eos/\"\n  if (path.empty()) {\n    path = \"/eos/\";\n  }\n\n  // Get type of quota to remove and construct config entry\n  std::ostringstream oss_config;\n  SpaceQuota::eQuotaTag quota_tag;\n  oss_config << path << \":\";\n\n  if (id_type == IdT::kUid) {\n    oss_config << \"uid=\";\n\n    if (quota_type == Type::kVolume) {\n      quota_tag = SpaceQuota::kUserBytesTarget;\n    } else {\n      quota_tag = SpaceQuota::kUserFilesTarget;\n    }\n  } else {\n    oss_config << \"gid=\";\n\n    if (quota_type == Type::kVolume) {\n      quota_tag = SpaceQuota::kGroupBytesTarget;\n    } else {\n      quota_tag = SpaceQuota::kGroupFilesTarget;\n    }\n  }\n\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetSpaceQuota(path);\n\n  if (!squota) {\n    oss_msg << \"error: no quota space defined for node \" << path;\n    msg = oss_msg.str();\n    return false;\n  }\n\n  if (squota->RmQuota(quota_tag, id)) {\n    oss_config << id << \":\" << SpaceQuota::GetTagAsString(quota_tag);\n    gOFS->mConfigEngine->DeleteConfigValue(\"quota\", oss_config.str().c_str());\n\n    // If this is a volume quota, remove also the equivalent logical quota if set\n    if (quota_type == Type::kVolume) {\n      std::string log_config = oss_config.str();\n      log_config.replace(log_config.find(\"bytes\"), 5, \"logicalbytes\");\n      SpaceQuota::eQuotaTag tag = (id_type == IdT::kUid) ? SpaceQuota::kUserLogicalBytesTarget\n                                                         : SpaceQuota::kGroupLogicalBytesTarget;\n      if (squota->RmQuota(tag, id))\n        gOFS->mConfigEngine->DeleteConfigValue(\"quota\", log_config.c_str());\n    }\n\n    oss_msg << \"success: removed \"\n            << ((quota_type == Type::kVolume) ? \"volume\" : \"inode\")\n            << \" quota for \"\n            << ((id_type == IdT::kUid) ? \"uid=\" : \"gid=\") << id\n            << \" from node \" << path;\n    msg = oss_msg.str();\n    retc = 0;\n    return true;\n  } else {\n    oss_msg << \"error: no \"\n            << ((quota_type == Type::kVolume) ? \"volume\" : \"inode\")\n            << \" quota defined on node \" << path << \" for \"\n            << ((id_type == IdT::kUid) ? \"user id\" : \"group id\");\n    msg = oss_msg.str();\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Remove all quota types for an id\n//------------------------------------------------------------------------------\nbool\nQuota::RmQuotaForId(const std::string& path, long id, Quota::IdT id_type,\n                    std::string& msg, int& retc)\n{\n  eos_static_debug(\"path=%s\", path.c_str());\n  std::string msg_vol, msg_inode;\n  bool rm_vol = RmQuotaTypeForId(path, id, id_type, Type::kVolume, msg_vol, retc);\n  bool rm_inode = RmQuotaTypeForId(path, id, id_type, Type::kInode,\n                                   msg_inode, retc);\n\n  if (rm_vol || rm_inode) {\n    if (rm_vol) {\n      msg += msg_vol;\n    }\n\n    if (rm_inode) {\n      msg += msg_inode;\n    }\n\n    return true;\n  } else {\n    msg = \"error: no quota defined for node \";\n    msg += path;\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Remove space quota\n//------------------------------------------------------------------------------\nbool\nQuota::RmSpaceQuota(const std::string& qpath, std::string& msg, int& retc)\n{\n  std::string path = NormalizePath(qpath);\n  eos_static_debug(\"qpath=%s, path=%s\", qpath.c_str(), path.c_str());\n  eos::common::RWMutexWriteLock wr_ns_lock(gOFS->eosViewRWMutex);\n  eos::common::RWMutexWriteLock wr_quota_lock(pMapMutex);\n  std::unique_ptr<SpaceQuota> squota(GetSpaceQuota(path));\n\n  if (!squota) {\n    retc = EINVAL;\n    msg = \"error: there is no quota node under path \";\n    msg += path;\n    return false;\n  } else {\n    // Remove space quota from map\n    pMapQuota.erase(path);\n    // Delete also from the pMapInodeQuota\n    (void) pMapInodeQuota.erase(squota->GetQuotaNode()->getId());\n\n    // Remove ns quota node\n    try {\n      std::shared_ptr<eos::IContainerMD> qcont = gOFS->eosView->getContainer(path);\n      gOFS->eosView->removeQuotaNode(qcont.get());\n      retc = 0;\n    } catch (eos::MDException& e) {\n      retc = e.getErrno();\n      msg = e.getMessage().str().c_str();\n    }\n\n    // Remove all configuration entries\n    std::string match = path;\n    match += \":\";\n    gOFS->mConfigEngine->DeleteConfigValueByMatch(\"quota\", match.c_str());\n    msg = \"success: removed space quota for \";\n    msg += path;\n\n    if (!gOFS->mConfigEngine->AutoSave()) {\n      return false;\n    }\n\n    return true;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Remove quota depending on the quota tag. Convenience wrapper around the\n// default RmQuotaTypeForId.\n//------------------------------------------------------------------------------\nbool\nQuota::RmQuotaForTag(const std::string& path, const std::string& quota_stag,\n                     long id)\n{\n  unsigned long spaceq_type = SpaceQuota::GetTagFromString(quota_stag);\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetSpaceQuota(path);\n\n  if (squota) {\n    squota->RmQuota(spaceq_type, id);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Callback function to calculate how much pyhisical space a file occupies\n//------------------------------------------------------------------------------\nuint64_t\nQuota::MapSizeCB(const eos::IFileMD* file)\n{\n  if (!file) {\n    return 0;\n  }\n\n  eos::IFileMD::layoutId_t lid = file->getLayoutId();\n  return (uint64_t) file->getSize() * eos::common::LayoutId::GetSizeFactor(lid);\n}\n\n//------------------------------------------------------------------------------\n// Load nodes\n//------------------------------------------------------------------------------\nvoid\nQuota::LoadNodes()\n{\n  std::vector<std::string> create_quota;\n  // Load all known nodes\n  {\n    std::string quota_path;\n    std::shared_ptr<eos::IContainerMD> container;\n    eos::common::RWMutexReadLock rd_ns_lock(gOFS->eosViewRWMutex);\n    auto set_ids = gOFS->eosView->getQuotaStats()->getAllIds();\n\n    for (const auto elem : set_ids) {\n      try {\n        container = gOFS->eosDirectoryService->getContainerMD(elem);\n        quota_path = gOFS->eosView->getUri(container.get());\n\n        // Make sure directories are '/' terminated\n        if (quota_path.back() != '/') {\n          quota_path += '/';\n        }\n\n        if (!Exists(quota_path)) {\n          create_quota.push_back(quota_path);\n        }\n      } catch (eos::MDException& e) {\n        errno = e.getErrno();\n        eos_static_err(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                       e.getErrno(), e.getMessage().str().c_str());\n      }\n    }\n  }\n\n  // Create all the necessary space quota nodes\n  for (auto it = create_quota.begin(); it != create_quota.end(); ++it) {\n    eos_static_notice(\"msg=\\\"create quota node\\\" path=\\\"%s\\\"\", it->c_str());\n    (void) Create(it->c_str());\n  }\n\n  // Refresh the space quota objects\n  {\n    // loop over pMapQuota releasing locks each time in the iteration\n    eos::common::RWMutexReadLock rd_ns_lock(gOFS->eosViewRWMutex);\n    eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n    bool first = true;\n    size_t n = 0;\n\n    do {\n      auto it = pMapQuota.begin();\n      std::advance(it, n);\n\n      if (!first) {\n        rd_ns_lock.Grab(gOFS->eosViewRWMutex);\n        rd_quota_lock.Grab(pMapMutex);\n        first = false;\n      }\n\n      if (it == pMapQuota.end()) {\n        break;\n      }\n\n      it->second->Refresh(5);\n      n++;\n      rd_quota_lock.Release();\n      rd_ns_lock.Release();\n    } while (1);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Print out quota information\n//------------------------------------------------------------------------------\nbool\nQuota::PrintOut(const std::string& path, XrdOucString& output,\n                long long int uid_sel, long long int gid_sel, bool monitoring,\n                bool translate_ids)\n{\n  output = \"\";\n  // Add this to have all quota nodes visible even if they are not in\n  // the configuration file\n  LoadNodes();\n  eos::common::RWMutexReadLock rd_fs_lock(FsView::gFsView.ViewMutex);\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n\n  if (path.empty()) {\n    for (auto it = pMapQuota.begin(); it != pMapQuota.end(); ++it) {\n      it->second->PrintOut(output, uid_sel, gid_sel, monitoring, translate_ids);\n    }\n  } else {\n    SpaceQuota* squota = GetResponsibleSpaceQuota(path);\n\n    if (squota) {\n      squota->PrintOut(output, uid_sel, gid_sel, monitoring, translate_ids);\n    } else {\n      output = \"error: no quota for path \";\n      output += path.c_str();\n      return false;\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Get group quota values for a particular path and id\n//------------------------------------------------------------------------------\nstd::map<int, unsigned long long>\nQuota::GetGroupStatistics(const std::string& qpath, long id)\n{\n  std::string path = NormalizePath(qpath);\n  std::map<int, unsigned long long> map;\n  eos::common::RWMutexReadLock rd_ns_lock(gOFS->eosViewRWMutex);\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetResponsibleSpaceQuota(path);\n\n  if (!squota) {\n    return map;\n  }\n\n  squota->Refresh(60);\n  unsigned long long value;\n  // Set of all group related quota keys\n  std::set<int> set_keys = {SpaceQuota::kGroupLogicalBytesIs, SpaceQuota::kGroupLogicalBytesTarget,\n                            SpaceQuota::kGroupFilesIs, SpaceQuota::kGroupFilesTarget,\n                            SpaceQuota::kAllGroupLogicalBytesTarget,\n                            SpaceQuota::kAllGroupLogicalBytesIs\n                           };\n\n  for (auto it = set_keys.begin(); it != set_keys.end(); ++it) {\n    value = squota->GetQuota(*it, id);\n    map.insert(std::make_pair(*it, value));\n  }\n\n  return map;\n}\n\n//------------------------------------------------------------------------------\n// Update quota from the namespace quota only if the requested path is actually\n// a ns quota node.\n//------------------------------------------------------------------------------\nbool\nQuota::UpdateFromNsQuota(const std::string& path, uid_t uid, gid_t gid)\n{\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetResponsibleSpaceQuota(path);\n\n  // No quota or this is not the space quota itself - do nothing\n  if (!squota || squota->GetSpaceNameStr().compare(path)) {\n    return false;\n  }\n\n  squota->UpdateFromQuotaNode(uid, gid, true);\n  return true;\n}\n\n//----------------------------------------------------------------------------\n// Check if the requested volume and inode values respect the quota\n//----------------------------------------------------------------------------\nbool\nQuota::Check(const std::string& path, uid_t uid, gid_t gid,\n             long long desired_vol, unsigned int desired_inodes)\n{\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetResponsibleSpaceQuota(path);\n\n  if (!squota) {\n    return true;\n  }\n\n  return squota->CheckWriteQuota(uid, gid, desired_vol, desired_inodes);\n}\n\n//------------------------------------------------------------------------------\n// Clean-up all space quotas by deleting them and clearing the maps\n//------------------------------------------------------------------------------\nvoid\nQuota::CleanUp()\n{\n  eos::common::RWMutexWriteLock wr_lock(pMapMutex);\n\n  for (auto it = pMapQuota.begin(); it != pMapQuota.end(); ++it) {\n    delete it->second;\n  }\n\n  pMapQuota.clear();\n  pMapInodeQuota.clear();\n}\n\n//------------------------------------------------------------------------------\n// Take the decision where to place a new file in the system. The core of the\n// implementation is in the Scheduler and GeoTreeEngine.\n//------------------------------------------------------------------------------\nint\nQuota::FilePlacement(Scheduler::PlacementArguments* args)\n{\n  if (FsView::gFsView.mSpaceGroupView.count(*args->spacename) == 0) {\n    eos_static_err(\"msg=\\\"no filesystem in space\\\" space=\\\"%s\\\"\",\n                   args->spacename->c_str());\n    args->selected_filesystems->clear();\n    return ENOSPC;\n  }\n\n  // Check if quota enabled for current space\n  if (FsView::gFsView.IsQuotaEnabled(*args->spacename)) {\n    eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n    if (SpaceQuota* squota = GetResponsibleSpaceQuota(args->path)) {\n      if (!squota->CheckWriteQuota(args->vid->uid, args->vid->gid, args->bookingsize, 1)) {\n        eos_static_debug(\"not enough quota for uid=%u gid=%u grouptag=%s bookingsize=%lu at path=%s\",\n                         args->vid->uid, args->vid->gid, args->grouptag, args->bookingsize, args->path);\n        return EDQUOT;\n      }\n    }\n  } else {\n    eos_static_debug(\"quota is disabled for space=%s\", args->spacename->c_str());\n  }\n\n  if (!FsView::gFsView.UnderNominalQuota(*args->spacename, args->vid->sudoer)) {\n    eos_static_err(\"msg=\\\"over physical quota limit (nominal space setting)\\\" space=\\\"%s\\\"\",\n                   args->spacename->c_str());\n    return ENOSPC;\n  }\n\n  eos_static_debug(\"%s\", \"nominal quota ok\");\n\n  // Call the scheduler implementation\n  return Scheduler::FilePlacement(args);\n}\n\n//------------------------------------------------------------------------------\n// Create quota node for path\n//------------------------------------------------------------------------------\nbool\nQuota::Create(const std::string& path)\n{\n  // Check if path is correct\n  if (path.empty() || path[0] != '/' || (*path.rbegin()) != '/') {\n    return false;\n  }\n\n  eos::common::RWMutexWriteLock wr_ns_lock(gOFS->eosViewRWMutex);\n  eos::common::RWMutexWriteLock wr_quota_lock(pMapMutex);\n\n  if (pMapQuota.count(path) == 0) {\n    try {\n      SpaceQuota* squota = new SpaceQuota(path.c_str());\n      pMapQuota[path] = squota;\n      pMapInodeQuota[squota->GetQuotaNode()->getId()] = squota;\n    } catch (const eos::MDException& e) {\n      eos_static_crit(\"Failed to create quota node %s\", path.c_str());\n      return false;\n    }\n  }\n\n  // Synchronize the flusher to avoid a race condition with the slave creating\n  // the same directory when applying the quota\n  auto* qdb_ns_grp = dynamic_cast<eos::QuarkNamespaceGroup*>\n                     (gOFS->namespaceGroup.get());\n\n  if (qdb_ns_grp) {\n    qdb_ns_grp->getMetadataFlusher()->synchronize();\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Retrieve the kAllGroupLogicalBytesIs and kAllGroupLogicalBytesTarget\n// values for the quota nodes.\n//------------------------------------------------------------------------------\nstd::map<std::string, std::tuple<unsigned long long,\n    unsigned long long,\n    unsigned long long>>\n    Quota::GetAllGroupsLogicalQuotaValues()\n{\n  std::map<std::string, std::tuple<unsigned long long,\n      unsigned long long,\n      unsigned long long>> allGroupLogicalByteValues;\n  // Add this to have all quota nodes visible even if they are not in\n  // the configuration file\n  LoadNodes();\n  eos::common::RWMutexReadLock rd_fs_lock(FsView::gFsView.ViewMutex);\n  eos::common::RWMutexReadLock rd_ns_lock(gOFS->eosViewRWMutex);\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n\n  for (const auto& quotaNode : pMapQuota) {\n    // quotaNode.second->Refresh();\n    allGroupLogicalByteValues[quotaNode.first] = std::make_tuple\n        (quotaNode.second->GetQuota(SpaceQuota::eQuotaTag::kAllGroupLogicalBytesIs, 0),\n         quotaNode.second->GetQuota(SpaceQuota::eQuotaTag::kAllGroupLogicalBytesTarget,\n                                    0),\n         quotaNode.second->GetQuota(SpaceQuota::eQuotaTag::kAllGroupFilesIs, 0));\n  }\n\n  return allGroupLogicalByteValues;\n}\n\n//------------------------------------------------------------------------------\n// Get quota for requested user and group by path\n//------------------------------------------------------------------------------\nint\nQuota::QuotaByPath(const char* path, uid_t uid, gid_t gid,\n                   long long& avail_files, long long& avail_bytes,\n                   eos::IContainerMD::id_t& quota_inode)\n{\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetResponsibleSpaceQuota(path);\n\n  if (squota) {\n    quota_inode = squota->GetQuotaNode()->getId();\n    return GetQuotaInfo(squota, uid, gid, avail_files, avail_bytes);\n  }\n\n  return -1;\n}\n\n//------------------------------------------------------------------------------\n// Get quota for requested user and group by quota inode\n//------------------------------------------------------------------------------\nint\nQuota::QuotaBySpace(const eos::IContainerMD::id_t qino, uid_t uid, gid_t gid,\n                    long long& avail_files, long long& avail_bytes)\n{\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  auto it = pMapInodeQuota.find(qino);\n\n  if (it != pMapInodeQuota.end()) {\n    return GetQuotaInfo(it->second, uid, gid, avail_files, avail_bytes);\n  }\n\n  return -1;\n}\n\n//------------------------------------------------------------------------------\n// Private method to collect desired info from a quota node\n// returns std::numeric_limits<long>::max() / 2; in avail_files or avail_bytes if not quota is set\n//------------------------------------------------------------------------------\nint\nQuota::GetQuotaInfo(SpaceQuota* squota, uid_t uid, gid_t gid,\n                    long long& avail_files, long long& avail_bytes)\n{\n  long long maxbytes_user, maxbytes_group, maxbytes_project;\n  long long freebytes_user, freebytes_group, freebytes_project;\n  long long freebytes = 0 ;\n  long long maxbytes = 0;\n  freebytes_user = freebytes_group = freebytes_project = 0;\n  maxbytes_user = maxbytes_group = maxbytes_project = 0;\n  squota->UpdateFromQuotaNode(uid, gid,\n                              squota->GetQuota(SpaceQuota::kGroupBytesTarget, Quota::gProjectId)\n                              ? true : false);\n  maxbytes_user  = squota->GetQuota(SpaceQuota::kUserLogicalBytesTarget, uid);\n  maxbytes_group = squota->GetQuota(SpaceQuota::kGroupLogicalBytesTarget, gid);\n  maxbytes_project = squota->GetQuota(SpaceQuota::kGroupLogicalBytesTarget,\n                                      Quota::gProjectId);\n  freebytes_user = maxbytes_user - squota->GetQuota(\n                     SpaceQuota::kUserLogicalBytesIs, uid);\n  freebytes_group = maxbytes_group - squota->GetQuota(\n                      SpaceQuota::kGroupLogicalBytesIs, gid);\n  freebytes_project = maxbytes_project - squota->GetQuota(\n                        SpaceQuota::kGroupLogicalBytesIs, Quota::gProjectId);\n\n  if (freebytes_user > freebytes) {\n    freebytes = freebytes_user;\n  }\n\n  if (freebytes_group > freebytes) {\n    freebytes = freebytes_group;\n  }\n\n  if (freebytes_project > freebytes) {\n    freebytes = freebytes_project;\n  }\n\n  if (maxbytes_user > maxbytes) {\n    maxbytes = maxbytes_user;\n  }\n\n  if (maxbytes_group > maxbytes) {\n    maxbytes = maxbytes_group;\n  }\n\n  if (maxbytes_project > maxbytes) {\n    maxbytes = maxbytes_project;\n  }\n\n  if (!freebytes && (maxbytes == 0)) {\n    // this is no quota set\n    freebytes = std::numeric_limits<long>::max() / 2;\n  }\n\n  long long maxfiles_user, maxfiles_group, maxfiles_project;\n  long long freefiles_user, freefiles_group, freefiles_project;\n  long long freefiles = 0;\n  long long maxfiles = 0;\n  freefiles_user = freefiles_group = freefiles_project = 0;\n  maxfiles_user = maxfiles_group = maxfiles_project = 0;\n  maxfiles_user  = squota->GetQuota(SpaceQuota::kUserFilesTarget, uid);\n  maxfiles_group = squota->GetQuota(SpaceQuota::kGroupFilesTarget, gid);\n  maxfiles_project = squota->GetQuota(SpaceQuota::kGroupFilesTarget,\n                                      Quota::gProjectId);\n  freefiles_user = maxfiles_user - squota->GetQuota(SpaceQuota::kUserFilesIs,\n                   uid);\n  freefiles_group = maxfiles_group - squota->GetQuota(SpaceQuota::kGroupFilesIs,\n                    gid);\n  freefiles_project = maxfiles_project - squota->GetQuota(\n                        SpaceQuota::kGroupFilesIs, Quota::gProjectId);\n\n  if (freefiles_user > freefiles) {\n    freefiles = freefiles_user;\n  }\n\n  if (freefiles_group > freefiles) {\n    freefiles = freefiles_group;\n  }\n\n  if (freefiles_project > freefiles) {\n    freefiles = freefiles_project;\n  }\n\n  if (maxfiles_user > maxfiles) {\n    maxfiles = maxfiles_user;\n  }\n\n  if (maxfiles_group > maxfiles) {\n    maxfiles = maxfiles_group;\n  }\n\n  if (maxfiles_project > maxfiles) {\n    maxfiles = maxfiles_project;\n  }\n\n  if (!freefiles && (maxfiles == 0)) {\n    // this is no quota set\n    freefiles = std::numeric_limits<long>::max() / 2;\n  }\n\n  avail_files = freefiles;\n  avail_bytes = freebytes;\n  return 0;\n}\n\n\n//------------------------------------------------------------------------------\n// Get logical max and free bytes for the given space\n//------------------------------------------------------------------------------\nvoid\nQuota::GetStatfs(const std::string& path, unsigned long long& maxbytes,\n                 unsigned long long& freebytes)\n{\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* space = GetResponsibleSpaceQuota(path);\n\n  if (space) {\n    space->Refresh(60);\n    maxbytes = space->GetQuota(SpaceQuota::kAllGroupLogicalBytesTarget, 0);\n    freebytes = maxbytes - space->GetQuota(SpaceQuota::kAllGroupLogicalBytesIs, 0);\n  } else {\n    maxbytes = freebytes = 0;\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Remove file from corresponding quota node\n//------------------------------------------------------------------------------\nbool\nQuota::RemoveFile(eos::IFileMD::id_t fid)\n{\n  std::shared_ptr<eos::IFileMD> fmd {nullptr};\n  std::shared_ptr<eos::IContainerMD> cmd {nullptr};\n  eos::IQuotaNode* ns_quota {nullptr};\n  eos::common::RWMutexWriteLock ns_wr_lock(gOFS->eosViewRWMutex);\n\n  try {\n    fmd = gOFS->eosFileService->getFileMD(fid);\n    cmd = gOFS->eosDirectoryService->getContainerMD(fmd->getContainerId());\n    ns_quota = gOFS->eosView->getQuotaNode(cmd.get());\n  } catch (const eos::MDException& e) {\n    return false;\n  }\n\n  if (ns_quota && fmd) {\n    ns_quota->removeFile(fmd.get());\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Remove file from corresponding quota node\n//------------------------------------------------------------------------------\nbool\nQuota::AddFile(eos::IFileMD::id_t fid)\n{\n  std::shared_ptr<eos::IFileMD> fmd {nullptr};\n  std::shared_ptr<eos::IContainerMD> cmd {nullptr};\n  eos::IQuotaNode* ns_quota {nullptr};\n  eos::common::RWMutexWriteLock ns_wr_lock(gOFS->eosViewRWMutex);\n\n  try {\n    fmd = gOFS->eosFileService->getFileMD(fid);\n    cmd = gOFS->eosDirectoryService->getContainerMD(fmd->getContainerId());\n    ns_quota = gOFS->eosView->getQuotaNode(cmd.get());\n  } catch (const eos::MDException& e) {\n    return false;\n  }\n\n  if (ns_quota && fmd) {\n    ns_quota->addFile(fmd.get());\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Force refresh of space quota from namespace quota node\n//------------------------------------------------------------------------------\nbool\nQuota::RefreshFromNsQuota(const std::string& path)\n{\n  eos::common::RWMutexReadLock rd_quota_lock(pMapMutex);\n  SpaceQuota* squota = GetResponsibleSpaceQuota(path);\n\n  if (!squota || squota->GetSpaceNameStr().compare(path)) {\n    return false;\n  }\n\n  {\n    XrdSysMutexHelper scope_lock(squota->mMutex);\n    auto it = squota->mMapIdQuota.begin();\n    while (it != squota->mMapIdQuota.end()) {\n      unsigned long tag = squota->UnIndex(it->first);\n\n      // Only remove \"Is\" tags\n      if (tag == SpaceQuota::kUserBytesIs || tag == SpaceQuota::kUserLogicalBytesIs ||\n          tag == SpaceQuota::kUserFilesIs || tag == SpaceQuota::kGroupBytesIs ||\n          tag == SpaceQuota::kGroupLogicalBytesIs || tag == SpaceQuota::kGroupFilesIs ||\n          tag == SpaceQuota::kAllUserBytesIs ||\n          tag == SpaceQuota::kAllUserLogicalBytesIs ||\n          tag == SpaceQuota::kAllUserFilesIs || tag == SpaceQuota::kAllGroupBytesIs ||\n          tag == SpaceQuota::kAllGroupLogicalBytesIs ||\n          tag == SpaceQuota::kAllGroupFilesIs) {\n        it = squota->mMapIdQuota.erase(it);\n      } else {\n        ++it;\n      }\n    }\n  }\n  squota->Refresh(0);\n\n  return true;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/quota/Quota.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Quota.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_QUOTA__HH__\n#define __EOSMGM_QUOTA__HH__\n\n// this is needed because of some openssl definition conflict!\n#undef des_set_key\n#include <google/dense_hash_map>\n#include <google/sparsehash/densehashtable.h>\n#include \"mgm/scheduler/Scheduler.hh\"\n#include \"common/Logging.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/Mapping.hh\"\n#include \"common/RWMutex.hh\"\n#include \"namespace/interface/IQuota.hh\"\n#include <XrdOuc/XrdOucString.hh>\n\nEOSMGMNAMESPACE_BEGIN\n\nclass Quota;\n\n//------------------------------------------------------------------------------\n//! Class SpaceQuota\n//------------------------------------------------------------------------------\nclass SpaceQuota : public eos::common::LogId\n{\n  friend class Quota;\n\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  SpaceQuota(const char* path);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~SpaceQuota() = default;\n\n  //----------------------------------------------------------------------------\n  //! Get space name\n  //----------------------------------------------------------------------------\n  inline const char* GetSpaceName() const\n  {\n    return pPath.c_str();\n  }\n\n  inline const std::string& GetSpaceNameStr() const\n  {\n    return pPath;\n  }\n  //----------------------------------------------------------------------------\n  //! Get namespace quota node\n  //----------------------------------------------------------------------------\n  inline eos::IQuotaNode* GetQuotaNode()\n  {\n    return mQuotaNode;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get space layout size factor\n  //----------------------------------------------------------------------------\n  double GetLayoutSizeFactor()\n  {\n    return mLayoutSizeFactor;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check user and/or group quota. If both are present, they both have to be\n  //! fullfilled.\n  //!\n  //! @param uid user id\n  //! @param gid group id\n  //! @param desired_vol desired volume (size)\n  //! @param desired_inodes desired number of inodes\n  //!\n  //! @return true if user has enough quota, otherwise false\n  //----------------------------------------------------------------------------\n  bool CheckWriteQuota(uid_t uid, gid_t gid, long long desired_vol,\n                       unsigned int desired_inodes);\n\n  //----------------------------------------------------------------------------\n  //! Print quota information\n  //----------------------------------------------------------------------------\n  void PrintOut(XrdOucString& output, long long int uid_sel = -1,\n                long long int gid_sel = -1, bool monitoring = false,\n                bool translate_ids = false);\n\n  //----------------------------------------------------------------------------\n  //! Quota type tags\n  //----------------------------------------------------------------------------\n  enum eQuotaTag {\n    kUserBytesIs = 1,                 kUserLogicalBytesIs = 2,\n    kUserLogicalBytesTarget = 3,      kUserBytesTarget = 4,\n    kUserFilesIs = 5,                 kUserFilesTarget = 6,\n    kGroupBytesIs = 7,                kGroupLogicalBytesIs = 8,\n    kGroupLogicalBytesTarget = 9,     kGroupBytesTarget = 10,\n    kGroupFilesIs = 11,               kGroupFilesTarget = 12,\n    kAllUserBytesIs = 13,             kAllUserLogicalBytesIs = 14,\n    kAllUserLogicalBytesTarget = 15,  kAllUserBytesTarget = 16,\n    kAllGroupBytesIs = 17,            kAllGroupLogicalBytesIs = 18,\n    kAllGroupLogicalBytesTarget = 19, kAllGroupBytesTarget = 20,\n    kAllUserFilesIs = 21,             kAllUserFilesTarget = 22,\n    kAllGroupFilesIs = 23,            kAllGroupFilesTarget = 24\n  };\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Get quota\n  //!\n  //! @param tag quota type tag (eQuotaTag)\n  //! @param id uid/gid/project id\n  //!\n  //! @return requested quota value\n  //----------------------------------------------------------------------------\n  long long GetQuota(unsigned long tag, unsigned long id);\n\n  //----------------------------------------------------------------------------\n  //! Set quota\n  //!\n  //! @param tag quota type tag (eQuotaTag)\n  //! @param id user/group id\n  //! @param value quota value to set\n  //----------------------------------------------------------------------------\n  void SetQuota(unsigned long tag, unsigned long id, unsigned long long value);\n\n  //----------------------------------------------------------------------------\n  //! Reset quota\n  //!\n  //! @param tag quota type tag (eQuotaTag)\n  //! @param uid uid/gid/project id\n  //!\n  //! @warning Caller needs to hold a lock on mMutex\n  //----------------------------------------------------------------------------\n  void ResetQuota(unsigned long tag, unsigned long id);\n\n  //----------------------------------------------------------------------------\n  //! Add quota\n  //!\n  //! @param tag quota type tag (eQuotaTag)\n  //! @param id user/group id\n  //! @param value quota value to be added\n  //!\n  //! @warning Caller needs to hold a lock on mMutex.\n  //----------------------------------------------------------------------------\n  void AddQuota(unsigned long tag, unsigned long id, long long value);\n\n  //----------------------------------------------------------------------------\n  //! Account ns quota values into the space quota view.\n  //----------------------------------------------------------------------------\n  void AccountNsToSpace();\n\n  //----------------------------------------------------------------------------\n  //! Update quota from the ns quota node for the given identity only if the\n  //! requested path is actually a ns quota node.\n  //!\n  //! @param uid user id\n  //! @param gid group id\n  //! @param upd_proj_quota if true then update also the project quota\n  //----------------------------------------------------------------------------\n  void UpdateFromQuotaNode(uid_t uid, gid_t, bool upd_proj_quota);\n\n  //----------------------------------------------------------------------------\n  //! Refresh quota all quota values for current space\n  //!\n  //! @warning Caller needs to hold a read-lock on both eosViewRWMutex and\n  //!          pMapMutex\n  //----------------------------------------------------------------------------\n  void Refresh(time_t age = 0);\n\n  //----------------------------------------------------------------------------\n  //! Calculate the size factor used to estimate the logical available bytes\n  //----------------------------------------------------------------------------\n  void UpdateLogicalSizeFactor();\n\n  //----------------------------------------------------------------------------\n  //! Update target quota values\n  //----------------------------------------------------------------------------\n  void UpdateTargetSums();\n\n  //----------------------------------------------------------------------------\n  //! Update current quota values\n  //----------------------------------------------------------------------------\n  void UpdateIsSums();\n\n  //----------------------------------------------------------------------------\n  //! Update ns quota node address referred to by current space quota\n  //!\n  //! @return true if update successful, otherwise false\n  //! @warning Caller needs to hold a read-lock on eosViewRWMutex\n  //----------------------------------------------------------------------------\n  bool UpdateQuotaNodeAddress();\n\n  //----------------------------------------------------------------------------\n  //! Serialize index\n  //----------------------------------------------------------------------------\n  inline unsigned long long Index(unsigned long tag, unsigned long id)\n  {\n    return ((tag << 32) | id);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Deserialize index\n  //----------------------------------------------------------------------------\n  inline unsigned long UnIndex(unsigned long long reindex)\n  {\n    return (reindex >> 32) & 0xffffffff;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if quota is enabled - needs a lock on the FsView\n  //!\n  //! @return true if quota enabled, otherwise false\n  //! @warning Caller needs to hold a read-lock on gFsView::ViewMutex\n  //----------------------------------------------------------------------------\n  bool IsEnabled();\n\n  //----------------------------------------------------------------------------\n  //! Remove quota\n  //!\n  //! @param tag quota type tag\n  //! @param uid uid/gid/project id\n  //!\n  //! @return true if quota deleted, false if quota not found\n  //----------------------------------------------------------------------------\n  bool RmQuota(unsigned long tag, unsigned long id);\n\n  //----------------------------------------------------------------------------\n  //! Get current quota value as percentage of the available one\n  //!\n  //! @param is current quota value\n  //! @param avail available quota value\n  //!\n  //! @return string representation of the percentage value\n  //----------------------------------------------------------------------------\n  float\n  GetQuotaPercentage(unsigned long long is, unsigned long long avail);\n\n  //----------------------------------------------------------------------------\n  //! Get quota status\n  //!\n  //! @para is current quota value\n  //! @param avail available quota value\n  //!\n  //! @return string representing the status i.e. ignored/ok/warning/exceeded\n  //----------------------------------------------------------------------------\n  const char* GetQuotaStatus(unsigned long long is, unsigned long long avail);\n\n  //----------------------------------------------------------------------------\n  //! Convert int tag to string representation\n  //!\n  //! @param tag int tag value\n  //!\n  //! @return string representation of the tag\n  //----------------------------------------------------------------------------\n  static const char* GetTagAsString(int tag);\n\n  //----------------------------------------------------------------------------\n  //! Convert int tag to string description\n  //!\n  //! @param tag int tag value\n  //!\n  //! @return string tag description\n  //----------------------------------------------------------------------------\n  static const char* GetTagName(int tag);\n\n  //----------------------------------------------------------------------------\n  //! Convert string tag to int representation\n  //!\n  //! @param tag string tag\n  //!\n  //! @return int representation of the tag\n  //----------------------------------------------------------------------------\n  static unsigned long GetTagFromString(const std::string& tag);\n\n  //----------------------------------------------------------------------------\n  //! Convert int tag to user or group category\n  //!\n  //! @param tag int tag value\n  //!\n  //! @return user/group category\n  //----------------------------------------------------------------------------\n  static const char* GetTagCategory(int tag);\n\n  std::string pPath; ///< quota node path\n  eos::IQuotaNode* mQuotaNode; ///< corresponding ns quota node\n  XrdSysMutex mMutex; ///< mutex to protect access to mMapIdQuota\n  time_t mLastEnableCheck; ///< timestamp of the last check\n  time_t mLastRefresh; ///< timestamp of last refresh call\n  double mLayoutSizeFactor; ///< layout dependent size factor\n  bool mDirtyTarget; ///< mark to recompute target values\n\n  //! Map for user view, depending on eQuota and uid/gid\n  std::map<long long, unsigned long long> mMapIdQuota;\n};\n\n\n//------------------------------------------------------------------------------\n//! Class Quota\n//------------------------------------------------------------------------------\nclass Quota: eos::common::LogId\n{\npublic:\n  enum IdT { kUid, kGid }; ///< Id type enum\n  enum Type { kUnknown, kVolume, kInode, kAll }; ///< Quota types\n\n  //----------------------------------------------------------------------------\n  //! Create space quota\n  //!\n  //! @param path quota node path which needs to be '/' terminated\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  static bool Create(const std::string& path);\n\n  //----------------------------------------------------------------------------\n  //! Check if quota node for path exists\n  //!\n  //! @param qpath path to search for\n  //!\n  //! @return true if quota node exists, otherwise false\n  //----------------------------------------------------------------------------\n  static bool Exists(const std::string& qpath);\n\n  //----------------------------------------------------------------------------\n  //! Check if there is a quota node responsible for the given path\n  //!\n  //! @param path path for which a quota node is searched, which needs to be\n  //!             '/' terminated\n  //!\n  //! @return true if quota node exists, otherwise false\n  //----------------------------------------------------------------------------\n  static bool ExistsResponsible(const std::string& path);\n\n  //----------------------------------------------------------------------------\n  //! Get individual quota values\n  //!\n  //! @param vid client virtual identity\n  //! @param path path\n  //! @param max_bytes max bytes value\n  //! @param free_bytes free bytes value\n  //! @param logical request to return the layout corrected quota values\n  //!\n  //----------------------------------------------------------------------------\n  static void GetIndividualQuota(eos::common::VirtualIdentity& vid,\n                                 const std::string& path,\n                                 long long& max_bytes,\n                                 long long& free_bytes,\n                                 long long& max_files,\n                                 long long& free_files,\n                                 bool logical = false);\n\n\n  //----------------------------------------------------------------------------\n  //! Set quota type of id (uid/gid)\n  //!\n  //! @param qpath quota path\n  //! @param id uid or gid value depending on the id_type\n  //! @param id_type type of id, can be uid or gid\n  //! @param quota_type type of quota to remove, can be inode or volume\n  //! @param value quota value to be set\n  //! @param msg message returned to the client\n  //! @param retc error number returned to the client\n  //!\n  //! @return true if quota set successful, otherwise false\n  //----------------------------------------------------------------------------\n  static bool SetQuotaTypeForId(const std::string& qpath, long id,\n                                Quota::IdT id_type, Quota::Type quota_type,\n                                unsigned long long value, std::string& msg,\n                                int& retc);\n\n\n  //----------------------------------------------------------------------------\n  //! Set quota specified by the quota tag.\n  //!\n  //! @param qpath quota path\n  //! @param quota_tag string representation of the SpaceQuota::eQuotaTag. From\n  //!                  this we can deduce the quota type and the id type.\n  //! @param id uid or gid value depending on the space_tag\n  //! @param value quota value to be set\n  //!\n  //! @return true if quota set successful, otherwise false\n  //----------------------------------------------------------------------------\n  static bool SetQuotaForTag(const std::string& qpath,\n                             const std::string& quota_tag,\n                             long id, unsigned long long value);\n\n  //----------------------------------------------------------------------------\n  //! Remove all quota types for an id\n  //!\n  //! @param path quota node path\n  //! @param id uid or gid value depending on the id_type\n  //! @param id_type type of id, can be uid or gid\n  //! @param msg message returned to the client\n  //! @param retc error number returned to the client\n  //!\n  //! @return true if operation successful, otherwise false\n  //----------------------------------------------------------------------------\n  static bool RmQuotaForId(const std::string& path, long id,\n                           Quota::IdT id_type, std::string& msg, int& retc);\n\n  //----------------------------------------------------------------------------\n  //! Remove quota type for id\n  //!\n  //! @param qpath quota node path\n  //! @param id uid or gid value depending on the id_type\n  //! @param id_type type of id, can be uid or gid\n  //! @param quota_type type of quota to remove, can be inode or volume\n  //! @param msg message returned to the client\n  //! @param retc error number returned to the client\n  //!\n  //! @return true if operation successful, otherwise false\n  //----------------------------------------------------------------------------\n  static bool RmQuotaTypeForId(const std::string& qpath, long id,\n                               Quota::IdT id_type, Quota::Type quota_type,\n                               std::string& msg, int& retc);\n\n  //------------------------------------------------------------------------------\n  //! Remove quota specified by the quota tag\n  //!\n  //! @param qpath quota node path\n  //! @param quota_tag string representation of the SpaceQuota::eQuotaTag. From\n  //!                  this we can deduce the quota type and the id type.\n  //! @param id uid or gid value depending on the space_tag\n  //!\n  //! @return true if quota set successful, otherwise false\n  //------------------------------------------------------------------------------\n  static bool RmQuotaForTag(const std::string& space,\n                            const std::string& quota_stag,\n                            long id);\n\n  //----------------------------------------------------------------------------\n  //! Removes a quota node\n  //!\n  //! @param qpath quota node path to be removed\n  //! @param msg message returned to the client\n  //! @param retc error number returned to the client\n  //!\n  //! @return true if operation successful, otherwise false\n  //----------------------------------------------------------------------------\n  static bool RmSpaceQuota(const std::string& qpath, std::string& msg, int& retc);\n\n  //----------------------------------------------------------------------------\n  //! Get group quota values for a particular path and id\n  //!\n  //! @param qpath quota node path\n  //! @param id uid/gid/projectid\n  //!\n  //! @return map between quota types and values. The map contains 4 entries\n  //!         corresponding to the following keys: kGroupBytesIs,\n  //!         kGroupBytesTarget, kGroupFilesIs and kGroupFilesTarget\n  //----------------------------------------------------------------------------\n  static std::map<int, unsigned long long>\n  GetGroupStatistics(const std::string& qpath, long id);\n\n  //----------------------------------------------------------------------------\n  //! Update SpaceQuota from the namespace quota only if the requested path is\n  //! actually a ns quota node. This also performs an update for the project\n  //! quota.\n  //!\n  //! @param path path\n  //! @param uid user id\n  //! @param gid group id\n  //!\n  //! @return true if update successful, otherwise it means that current path\n  //!         doesn't point to a ns quota node and return false.\n  //----------------------------------------------------------------------------\n  static bool UpdateFromNsQuota(const std::string& path, uid_t uid, gid_t gid);\n\n  //----------------------------------------------------------------------------\n  //! Check if the requested volume and inode values respect the quota\n  //!\n  //! @param path path\n  //! @param uid user id\n  //! @param gid group id\n  //! @param desired_vol desired space\n  //! @param desired_inodes desired number of inondes\n  //!\n  //! @return true if quota is respected, otherwise false\n  //----------------------------------------------------------------------------\n  static bool Check(const std::string& path, uid_t uid, gid_t gid,\n                    long long desired_vol, unsigned int desired_inodes);\n\n  //----------------------------------------------------------------------------\n  //! Callback function to calculate how much pyhisical space a file occupies\n  //!\n  //! @param file file MD object\n  //!\n  //! @return physical size depending on file layout type\n  //----------------------------------------------------------------------------\n  static uint64_t MapSizeCB(const eos::IFileMD* file);\n\n  //----------------------------------------------------------------------------\n  //! Load function to initialize all SpaceQuota's with the quota node\n  //! definition from the namespace\n  //----------------------------------------------------------------------------\n  static void LoadNodes();\n\n  //----------------------------------------------------------------------------\n  //! Clean-up all space quotas by deleting them and clearing the map\n  //----------------------------------------------------------------------------\n  static void CleanUp();\n\n  //----------------------------------------------------------------------------\n  //! Print out quota information\n  //!\n  //! @return true if operation successful, otherwise false and populate the\n  //!         output string with the error messsage\n  //----------------------------------------------------------------------------\n  static bool PrintOut(const std::string& path, XrdOucString& output,\n                       long long int uid_sel = -1, long long int gid_sel = -1,\n                       bool monitoring = false, bool translate_ids = false);\n\n  //----------------------------------------------------------------------------\n  //! Take the decision where to place a new file in the system. The core of the\n  //! implementation is in the Scheduler and GeoTreeEngine.\n  //!\n  //! @param space quota space name\n  //! @param path file path\n  //! @param vid virtual id of client\n  //! @param grouptag group tag for placement\n  //! @param lid layout to be placed\n  //! @param alreadyused_filsystems filesystems to avoid\n  //! @param selected_filesystems filesystems selected by scheduler\n  //! @param dataproxys if non null, schedule dataproxys for each fs\n  //! @param firewallentpts if non null, schedule firewall entry points for each fs\n  //! @param plctpolicy indicates if placement should be local/spread/hybrid\n  //! @param plctTrgGeotag indicates close to which Geotag collocated stripes\n  //!                      should be placed\n  //! @param truncate indicates placement with truncation\n  //! @param forched_scheduling_group_index forced index for the scheduling\n  //!                      subgroup to be used\n  //! @param bookingsize size to book for the placement\n  //!\n  //! @return 0 if placement successful, otherwise a non-zero value\n  //!         ENOSPC - no space quota defined for current space\n  //!         EDQUOT - no quota node found or not enough quota to place\n  //! @warning Must be called with a lock on the FsView::gFsView::ViewMutex\n  //----------------------------------------------------------------------------\n  static\n  int FilePlacement(Scheduler::PlacementArguments* args);\n\n  //----------------------------------------------------------------------------\n  //! @brief Retrieve the kAllGroupLogicalBytesIs and kAllGroupLogicalBytesTarget\n  //! values for the quota nodes.\n  //!\n  //! @return a map with the paths of the quota nodes and the corresponding\n  //! values\n  //----------------------------------------------------------------------------\n  static std::map<std::string, std::tuple<unsigned long long,\n         unsigned long long,\n         unsigned long long>>\n         GetAllGroupsLogicalQuotaValues();\n\n  //----------------------------------------------------------------------------\n  //! Get quota for requested user and group by path\n  //!\n  //! @param path path for which to search for a quota node\n  //! @param uid user id\n  //! @param gid group id\n  //! @param avail_files inode quota left\n  //! @param avail_bytes size quota left\n  //! @param quota_inode inode of the quota node\n  //!\n  //! @return 0 if successful\n  //----------------------------------------------------------------------------\n  static int QuotaByPath(const char* path, uid_t uid, gid_t gid,\n                         long long& avail_files, long long& avail_bytes,\n                         eos::IContainerMD::id_t& quota_inode);\n\n\n  //----------------------------------------------------------------------------\n  //! Get quota for requested user and group by quota inode\n  //!\n  //! @param qino inode of quota node\n  //! @param uid user id\n  //! @param gid group id\n  //! @param avail_files inode quota left\n  //! @param avail_bytes size quota left\n  //!\n  //! @return 0 if successful\n  //----------------------------------------------------------------------------\n  static int QuotaBySpace(const eos::IContainerMD::id_t, uid_t uid, gid_t gid,\n                          long long& avail_files, long long& avail_bytes);\n\n  //----------------------------------------------------------------------------\n  //! Get space quota node path looking for the most specific\n  //! match.\n  //!\n  //! @param path path for which we search for a responsible space quota\n  //!\n  //! @return path name to space quota\n  //----------------------------------------------------------------------------\n  static std::string GetResponsibleSpaceQuotaPath(const std::string& path);\n\n\n  //----------------------------------------------------------------------------\n  //! Get logical free und max bytes for this space\n  //----------------------------------------------------------------------------\n  static void GetStatfs(const std::string& path, unsigned long long& maxbytes,\n                        unsigned long long& freebytes);\n\n  //----------------------------------------------------------------------------\n  //! Remove file from corresponding quota node\n  //\n  //! @param fid file identifier\n  //!\n  //! @return true if file remove successfully and quota node exists\n  //----------------------------------------------------------------------------\n  static bool RemoveFile(eos::IFileMD::id_t fid);\n\n  //----------------------------------------------------------------------------\n  //! Remove file from corresponding quota node\n  //\n  //! @param fid file identifier\n  //!\n  //! @return true if file added successful and quota node exists\n  //----------------------------------------------------------------------------\n  static bool AddFile(eos::IFileMD::id_t fid);\n\n  //----------------------------------------------------------------------------\n  //! Force refresh of space quota from namespace quota node\n  //!\n  //! @param path quota node path\n  //!\n  //! @return true if refresh successful, otherwise false\n  //----------------------------------------------------------------------------\n  static bool RefreshFromNsQuota(const std::string& path);\n\n  static gid_t gProjectId; ///< gid indicating project quota\n  static eos::common::RWMutex pMapMutex; ///< Protect access to pMapQuota\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Private method to collect desired info from a quota node\n  //!\n  //! @param squota quota object\n  //! @param uid user id\n  //! @param gid group id\n  //! @param avail_files inode quota left\n  //! @param avail_bytes size quota left\n  //!\n  //! @return 0 if successful\n  //! @note locks should be taken outside this method\n  //----------------------------------------------------------------------------\n  static int GetQuotaInfo(SpaceQuota* squota, uid_t uid, gid_t gid,\n                          long long& avail_files, long long& avail_bytes);\n\n  //----------------------------------------------------------------------------\n  //! Get space quota object for exact path\n  //!\n  //! @param qpath path of the quota node\n  //!\n  //! @return SpaceQuota object\n  //----------------------------------------------------------------------------\n  static SpaceQuota* GetSpaceQuota(const std::string& qpath);\n\n  //----------------------------------------------------------------------------\n  //! Get space quota node responsible for path looking for the most specific\n  //! match.\n  //!\n  //! @param path path for which we search for a responsible space quotap\n  //!\n  //! @return SpaceQuota object\n  //----------------------------------------------------------------------------\n  static SpaceQuota* GetResponsibleSpaceQuota(const std::string& path);\n\n\n  //----------------------------------------------------------------------------\n  //! Make sure the path ends with a /\n  //!\n  //! @param path input path\n  //!\n  //! @return / terminated path\n  //----------------------------------------------------------------------------\n  static inline std::string NormalizePath(const std::string& ipath)\n  {\n    std::string path = ipath;\n\n    if (!path.empty() && (path.back() != '/')) {\n      path += '/';\n    }\n\n    return path;\n  }\n\n  //! Map from path to SpaceQuota object\n  static std::map<std::string, SpaceQuota*> pMapQuota;\n  //! Map from container id to SpaceQuota object\n  static std::map<eos::IContainerMD::id_t, SpaceQuota*> pMapInodeQuota;\n};\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/recycle/Recycle.cc",
    "content": "//------------------------------------------------------------------------------\n// File: Recycle.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/recycle/Recycle.hh\"\n#include \"mgm/recycle/RecyclePolicy.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"common/Constants.hh\"\n#include \"common/Logging.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/utils/BackOffInvoker.hh\"\n#include \"common/RWMutex.hh\"\n#include \"common/Path.hh\"\n#include \"common/StringUtils.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/acl/Acl.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/qdbmaster/QdbMaster.hh\"\n#include \"mgm/ofs/XrdMgmOfsDirectory.hh\"\n#include \"mgm/proc/user/AclCmd.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"namespace/interface/ContainerIterators.hh\"\n#include <XrdOuc/XrdOucErrInfo.hh>\n\nnamespace\n{\n//----------------------------------------------------------------------------\n//! Check that all directories in the given path hierarchy contain the given\n//! xattr key.\n//!\n//! @param path path hierarchy pointing to a directory\n//! @param xattr_key extended attribute key\n//! @param xattr_val extended attribute value\n//!\n//! @return true if successful, otherwise false\n//----------------------------------------------------------------------------\nbool AllHierarchyHasXattr(std::string_view path, std::string_view xattr_key,\n                          std::string_view xattr_val)\n{\n  // Find all the directories that contain the given xattr\n  XrdOucString lout;\n  XrdOucErrInfo lerror;\n  std::map<std::string, std::set<std::string>> found;\n  // Get the total number of sub-dirs in the hierarchy. Make sure we skip\n  // version directories as these should not have any xattrs set on them.\n  if (gOFS->_find(path.data(), lerror, lout, Recycle::mRootVid, found,      //\n                  nullptr, nullptr, true, 0ull, false, 0, nullptr, true)) { //\n    eos_static_err(\"msg=\\\"failed computing number of sub-dirs in hierarchy\\\" \"\n                   \"path=\\\"%s\\\"\", path.data());\n    return false;\n  }\n\n  uint64_t tree_num_dirs = found.size();\n  found.clear();\n\n  // Get the sub-dirs that contain the requested xattr key-value combination\n  if (gOFS->_find(path.data(), lerror, lout, Recycle::mRootVid,    //\n                  found, xattr_key.data(), xattr_val.data(), true, //\n                  0ull, false, 0, nullptr, true)) {\n    eos_static_err(\"msg=\\\"failed running find in hierarchy\\\" path=\\\"%s\\\"\",\n                   path.data());\n    return false;\n  }\n\n  if (found.size() == tree_num_dirs) {\n    return true;\n  }\n\n  return false;\n}\n}\n\nEOSMGMNAMESPACE_BEGIN\n\n// MgmOfsConfigure prepends the proc directory path e.g. the bin is\n// /eos/<instance>/proc/recycle/\nstd::string Recycle::gRecyclingPrefix = \"/recycle/\";\nstd::string Recycle::gRecyclingAttribute = \"sys.recycle\";\nstd::string Recycle::gRecyclingPostFix = \".d\";\nstd::string Recycle::gRecyclingVersionKey = \"sys.recycle.version.key\";\nstd::string Recycle::gRecycleIdXattrKey = \"sys.forced.recycleid\";\neos::common::VirtualIdentity Recycle::mRootVid =\n  eos::common::VirtualIdentity::Root();\nstd::chrono::seconds Recycle::mLastRemoveTs = std::chrono::seconds(0);\n\n//------------------------------------------------------------------------------\n// Collect entries to recycle based on the current policy\n//------------------------------------------------------------------------------\nvoid\nRecycle::CollectEntries(ThreadAssistant& assistant)\n{\n  auto now_ts = eos::common::SystemClock::SecondsSinceEpoch(mClock.GetTime());\n  static std::chrono::seconds s_last_ts = now_ts;\n  eos_static_debug(\"msg=\\\"recycle start collection\\\" ts=%llu\", now_ts.count());\n\n  // Run collection once every mCollectInterval\n  if (now_ts - s_last_ts < mPolicy.mCollectInterval.load()) {\n    eos_static_debug(\"msg=\\\"recycle skip collection\\\" last_ts=%llu \"\n                     \"collect_interval_sec=%llu\", s_last_ts.count(),\n                     mPolicy.mCollectInterval.load().count());\n    return;\n  }\n\n  // Clear the old list of deletions as we'll repopulate it\n  mPendingDeletions.clear();\n  s_last_ts = now_ts;\n  int depth = 4;\n  XrdOucErrInfo err_obj;\n  XrdOucString err_msg;\n  std::map<std::string, std::set<std::string>> find_map;\n  // /eos/<instance>/proc/recycle/uid:<val>/year/month/day\n  (void) gOFS->_find(Recycle::gRecyclingPrefix.c_str(),\n                     err_obj, err_msg, mRootVid, find_map,\n                     0, 0, true, 0, true, depth, nullptr, false,\n                     false, nullptr, 0, 0, nullptr, &assistant);\n  std::string cutoff_date = GetCutOffDate();\n  eos_static_notice(\"msg=\\\"recycle find query\\\" cutoff_date=\\\"%s\\\"\",\n                    cutoff_date.c_str())\n\n  for (auto it_dir = find_map.begin(); it_dir != find_map.end(); ++it_dir) {\n    // Select all the directories with depth 8\n    const std::string dir_path = it_dir->first;\n    eos::common::Path cpath(dir_path);\n    unsigned int path_levels = cpath.GetSubPathSize();\n\n    if (path_levels == std::clamp(path_levels, 5u, 8u)) {\n      bool exceeds_cutoff = false; // old directory to be removed\n      bool top_dir = false; // top directory to be removed if empty\n\n      if (path_levels == 8) {\n        std::string dir_date = cpath.GetFullPath().c_str();\n        dir_date.erase(0, strlen(cpath.GetSubPath(5)));\n        eos_static_debug(\"dir_date=\\\"%s\\\" cutoff_date=\\\"%s\\\"\",\n                         dir_date.c_str(), cutoff_date.c_str());\n        exceeds_cutoff = (cutoff_date.compare(dir_date) > 0);\n      } else {\n        top_dir = true;\n      }\n\n      // Select directories which are older than the cut off date\n      if (exceeds_cutoff || top_dir) {\n        try {\n          eos::IContainerMDPtr cmd = gOFS->eosView->getContainer(dir_path);\n          auto cmd_rd_lock = eos::MDLocking::readLock(cmd.get());\n\n          // If no more children then add it to the list for deletion\n          if (cmd->getNumContainers() == 0) {\n            mPendingDeletions.emplace(cmd->getId(), dir_path);\n          } else if (exceeds_cutoff) {\n            // Otherwise add all the subcontainers used for sharding\n            // .../year/month/day/[0,1,2, ... max_shard]/\n            for (auto it = eos::ContainerMapIterator(cmd); it.valid(); it.next()) {\n              std::string full_path = dir_path + it.key();\n              mPendingDeletions.emplace(it.value(), full_path);\n            }\n          }\n        } catch (const eos::MDException& e) {\n          // skip missing directory\n        }\n      }\n    }\n  }\n\n  if (EOS_LOGS_DEBUG) {\n    for (const auto& pair : mPendingDeletions) {\n      eos_static_debug(\"msg=\\\"recycle entry\\\" cxid=%08llx path=\\\"%s\\\"\",\n                       pair.first, pair.second.c_str());\n    }\n  }\n\n  auto duration = std::chrono::duration_cast<std::chrono::seconds>\n                  (eos::common::SystemClock::SecondsSinceEpoch(mClock.GetTime()) - now_ts);\n  eos_static_notice(\"msg=\\\"recycle done collection\\\" num_entries=%llu \"\n                    \"duration_sec=%llu\", mPendingDeletions.size(),\n                    duration.count());\n}\n\n//------------------------------------------------------------------------------\n// Remove the pending deletions\n//------------------------------------------------------------------------------\nvoid\nRecycle::RemoveEntries()\n{\n  auto now_ts = eos::common::SystemClock::SecondsSinceEpoch(mClock.GetTime());\n\n  // Run removal every mRemoveInterval\n  if (now_ts - mLastRemoveTs < mPolicy.mRemoveInterval.load()) {\n    eos_static_debug(\"msg=\\\"recycle skip removal\\\" last_ts=%llu \"\n                     \"removal_interval_sec=%llu\", mLastRemoveTs.count(),\n                     mPolicy.mRemoveInterval.load().count());\n    return;\n  }\n\n  mLastRemoveTs = now_ts;\n\n  if (mPendingDeletions.empty()) {\n    return;\n  }\n\n  // Compute the index of the containers to be removed in the current slot\n  int total_slots = mPolicy.mCollectInterval.load().count() /\n                    mPolicy.mRemoveInterval.load().count();\n  int current_slot = (now_ts.count() % mPolicy.mCollectInterval.load().count()) /\n                     mPolicy.mRemoveInterval.load().count();\n\n  // Catch all config in case the remove interval >= collect interval\n  if (total_slots == 0) {\n    total_slots = 1;\n    current_slot = 0;\n  }\n\n  auto it = mPendingDeletions.begin();\n  uint64_t count = 0;\n\n  while (it != mPendingDeletions.end()) {\n    // Keep ratio and watermarks already respected\n    if ((count % 10 == 0) && mPolicy.IsWithinLimits()) {\n      break;\n    }\n\n    ++count;\n    // Decide if the current directory should be handled at this moment -\n    // try to spread out the deletions throughout the day!\n    eos::IContainerMD::id_t cid = it->first;\n\n    if (cid % total_slots != current_slot) {\n      eos_static_debug(\"msg=\\\"recycle skip directory removal\\\" cxid=%08llx\"\n                       \" current_slot=%i slots=%i\", cid, current_slot,\n                       total_slots);\n      ++it;\n      continue;\n    }\n\n    if (mPolicy.mDryRun) {\n      eos_static_info(\"msg=\\\"recycle skip removing entries in dry-run\\\" \"\n                      \"cxid=%08llx\", cid);\n      ++it;\n    } else {\n      // Handle deletion\n      RemoveSubtree(it->second);\n      it = mPendingDeletions.erase(it);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Remove all the entries in the given subtree\n//------------------------------------------------------------------------------\nvoid\nRecycle::RemoveSubtree(std::string_view dpath)\n{\n  std::map<std::string, std::set<std::string> > found;\n  XrdOucString err_msg;\n  XrdOucErrInfo lerror;\n\n  if (gOFS->_find(dpath.data(), lerror, err_msg, mRootVid, found)) {\n    eos_static_err(\"msg=\\\"failed doing find in subtree\\\" path=%s stderr=\\\"%s\\\"\",\n                   dpath.data(), err_msg.c_str());\n  } else {\n    // Delete files starting at the deepest level\n    for (auto dit = found.rbegin(); dit != found.rend(); ++dit) {\n      for (auto fit = dit->second.begin(); fit != dit->second.end(); ++fit) {\n        const std::string fname = HandlePotentialSymlink(dit->first, *fit);\n        eos_static_debug(\"orig_fname=\\\"%s\\\" new_fname=\\\"%s\\\"\",\n                         fit->c_str(), fname.c_str());\n        std::string fpath = dit->first;\n        fpath += fname;\n\n        if (gOFS->_rem(fpath.c_str(), lerror, mRootVid, nullptr)) {\n          eos_static_err(\"msg=\\\"unable to remove file\\\" path=%s\", fpath.c_str());\n        } else {\n          eos_static_info(\"msg=\\\"permanently deleted file from recycle bin\\\" \"\n                          \"path=%s\", fpath.c_str());\n        }\n      }\n    }\n\n    // Delete directories starting at the deepest level\n    for (auto dit = found.rbegin(); dit != found.rend(); ++dit) {\n      eos_static_info(\"msg=\\\"handling directory\\\" path=%s\", dit->first.c_str());\n      std::string ldpath = dit->first.c_str();\n\n      // Don't even try to delete the root directory or\n      // something outside the recycle bin\n      if ((ldpath == \"/\") || (ldpath.find(Recycle::gRecyclingPrefix) != 0)) {\n        continue;\n      }\n\n      if (!gOFS->_remdir(ldpath.c_str(), lerror, mRootVid, (const char*) 0)) {\n        eos_static_info(\"msg=\\\"permanently deleted directory from \"\n                        \"recycle bin\\\" path=%s\", ldpath.c_str());\n      } else {\n        eos_static_err(\"msg=\\\"unable to remove directory\\\" path=%s\",\n                       ldpath.c_str());\n      }\n    }\n\n    // Delete parent directories if empty and still within the recycle bin.\n    if (dpath.find(Recycle::gRecyclingPrefix) == 0) {\n      eos_static_info(\"msg=\\\"delete parent directory\\\" path=%s\", dpath.data());\n      eos::common::Path cpath(std::string(dpath.data()));\n\n      for (auto level = cpath.GetSubPathSize() - 1; level > 4; --level) {\n        std::string sub_path = cpath.GetSubPath(level);\n\n        if (!gOFS->_remdir(sub_path.c_str(), lerror, mRootVid, (const char*) 0)) {\n          eos_static_info(\"msg=\\\"permanently deleted directory from \"\n                          \"recycle bin\\\" path=%s\", sub_path.c_str());\n        } else {\n          // Failed removal means directory is not empty so there is\n          // no point in continuing.\n          break;\n        }\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get cut-off date based on the configured retention policy with respect\n// to the current timestamp.\n//------------------------------------------------------------------------------\nstd::string\nRecycle::GetCutOffDate()\n{\n  uint64_t now = eos::common::SystemClock::SecondsSinceEpoch(\n                   mClock.GetTime()).count();\n  // Add one extra day\n  std::time_t cut_off_ts = now - mPolicy.mKeepTimeSec - 86400;\n  auto tm = *std::localtime(&cut_off_ts);\n  std::ostringstream oss;\n  oss << std::put_time(&tm, \"%Y/%m/%d\");\n  return oss.str();\n}\n\n//------------------------------------------------------------------------------\n// Recycle method doing the clean-up\n//------------------------------------------------------------------------------\nvoid\nRecycle::Recycler(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"Recycler\");\n  eos_static_info(\"%s\", \"\\\"msg = \\\"recycle thread started\\\"\");\n  gOFS->WaitUntilNamespaceIsBooted(assistant);\n  mLastRemoveTs = eos::common::SystemClock::SecondsSinceEpoch(mClock.GetTime());\n  // Lambda computing the wait time for the configuration update condition\n  // variable. We need to wait at most the remove interval time.\n  auto getCvWaitFor = [&]() -> std::chrono::seconds {\n    auto now_ts = eos::common::SystemClock::SecondsSinceEpoch(mClock.GetTime());\n    std::chrono::seconds wait_for = now_ts - mLastRemoveTs;\n\n    if (wait_for.count() > 5)\n    {\n      wait_for -= std::chrono::seconds(5);\n    }\n\n    return wait_for;\n  };\n\n  if (assistant.terminationRequested()) {\n    return;\n  }\n\n  assistant.wait_for(std::chrono::seconds(10));\n  eos::common::BackOffInvoker backoff_logger;\n  mPolicy.ApplyConfig(&FsView::gFsView);\n\n  while (!assistant.terminationRequested()) {\n    // Every now and then we wake up\n    backoff_logger.invoke([this]() {\n      eos_static_info(\"msg=\\\"recycle thread\\\" snooze-time=%llusec\",\n                      mPolicy.mRemoveInterval.load().count());\n    });\n    {\n      // Wait for configuration update request or timeout\n      std::unique_lock<std::mutex> lock(mCvMutex);\n      mCvCfgUpdate.wait_for(lock, getCvWaitFor());\n    }\n\n    if (!gOFS->mMaster->IsMaster() || (IsEnabled() == false)) {\n      continue;\n    }\n\n    if (mPolicy.mSpaceKeepRatio) {\n      mPolicy.RefreshWatermarks();\n    }\n\n    if (mPolicy.mKeepTimeSec && !mPolicy.IsWithinLimits()) {\n      CollectEntries(assistant);\n      RemoveEntries();\n    }\n  }\n\n  eos_static_info(\"%s\", \"msg=\\\"recycler thread exiting\\\"\");\n}\n\n//------------------------------------------------------------------------------\n// Print the recycle bin contents\n//------------------------------------------------------------------------------\nint\nRecycle::Print(std::string& std_out, std::string& std_err,\n               eos::common::VirtualIdentity& vid, bool monitoring,\n               bool translateids, bool details,\n               std::string_view display_type,\n               std::string_view display_val,\n               std::string_view date, Recycle::RecycleListing* rvec,\n               bool whodeleted, int32_t maxentries)\n{\n  using namespace eos::common;\n  XrdOucString uids;\n  XrdOucString gids;\n  std::map<std::string, bool> printmap;\n  std::ostringstream oss_out;\n\n  // Sanitize user input\n  if (!date.empty()) {\n    for (const auto& ch : date) {\n      if (!isdigit(ch) && (ch != '/')) {\n        std_err = \"error: invalid date format\";\n        return EINVAL;\n      }\n    }\n  }\n\n  if ((display_type == \"all\") &&\n      ((!vid.uid) ||\n       (vid.hasUid(eos::common::ADM_UID)) ||\n       (vid.hasGid(eos::common::ADM_GID)))) {\n    // Add everything found in the recycle directory structure to the printmap\n    std::string subdirs;\n    XrdMgmOfsDirectory dirl;\n    int listrc = dirl.open(Recycle::gRecyclingPrefix.c_str(), mRootVid,\n                           (const char*) 0);\n\n    if (listrc) {\n      eos_static_err(\"msg=\\\"unable to list the garbage directory level-1\\\" \"\n                     \"recycle-path=%s\", Recycle::gRecyclingPrefix.c_str());\n    } else {\n      // loop over all directories = group directories\n      const char* dname1;\n\n      while ((dname1 = dirl.nextEntry())) {\n        std::string sdname = dname1;\n\n        if ((sdname == \".\") || (sdname == \"..\")) {\n          continue;\n        }\n\n        if ((sdname.substr(0, 4) == \"uid:\") ||\n            (sdname.substr(0, 4) == \"rid:\")) {\n          printmap[sdname] = true;\n        }\n      }\n\n      dirl.close();\n    }\n  } else if (display_type == \"rid\") {\n    // Check that a recycle id value was given\n    if (display_val.empty()) {\n      std_err = \"error: no recycle id value given\";\n      return EINVAL;\n    }\n\n    // Basic checks about the recyle id\n    try {\n      (void) std::stoull(display_val.data());\n    } catch (...) {\n      std_err = \"error: recycle id must be numeric\";\n      return EINVAL;\n    }\n\n    // Add only the requested recycle id\n    printmap[SSTR(\"rid:\" << display_val.data())] = true;\n  } else {\n    // Add only the virtual user to the printmap\n    printmap[SSTR(\"uid:\" << vid.uid)] = true;\n  }\n\n  eos::common::Path dPath(std::string(\"/\") + date.data());\n\n  if (details) {\n    size_t count = 0;\n\n    for (auto it = printmap.begin(); it != printmap.end(); it++) {\n      char sdir[4096];\n      snprintf(sdir, sizeof(sdir) - 1, \"%s/%s/%s\",\n               Recycle::gRecyclingPrefix.c_str(),\n               it->first.c_str(), date.data());\n      int depth = 5 ;\n\n      if (dPath.GetSubPathSize()) {\n        if (depth > (int) dPath.GetSubPathSize()) {\n          depth -= dPath.GetSubPathSize();\n        }\n      }\n\n      XrdOucString err_msg;\n      XrdOucErrInfo lerror;\n      std::map<std::string, std::set < std::string>> find_map;\n      int retc = gOFS->_find(sdir, lerror, err_msg, mRootVid, find_map,\n                             0, 0, false, 0, true, depth);\n\n      if (retc) {\n        if (errno != ENOENT) {\n          std_err = err_msg.c_str();\n          eos_static_err(\"msg=\\\"failed find command\\\" dir=\\\"%s\\\"\", sdir);\n        } else {\n          continue;\n        }\n      }\n\n      for (auto it_dir = find_map.begin(); it_dir != find_map.end(); ++it_dir) {\n        XrdOucString dirname = it_dir->first.c_str();\n\n        if (dirname.endswith(\".d/\")) {\n          dirname.erase(dirname.length() - 1);\n          eos::common::Path cpath(dirname.c_str());\n          dirname = cpath.GetParentPath();\n          it_dir->second.insert(cpath.GetName());\n        }\n\n        eos_static_debug(\"dir=%s\", it_dir->first.c_str());\n\n        for (auto it_file = it_dir->second.begin();\n             it_file != it_dir->second.end(); ++it_file) {\n          if (maxentries && (count >= (size_t)maxentries)) {\n            retc = E2BIG;\n            std_out += oss_out.str();\n            return E2BIG;;\n          }\n\n          const std::string fname = HandlePotentialSymlink(dirname.c_str(), *it_file);\n          eos_static_debug(\"orig_fname=\\\"%s\\\" new_fname=\\\"%s\\\"\",\n                           it_file->c_str(), fname.c_str());\n\n          if ((fname != \"/\") && (fname.find('#') != 0)) {\n            eos_static_debug(\"msg=\\\"skip unexpected entry\\\" fname=\\\"%s\\\"\",\n                             fname.c_str());\n            continue;\n          }\n\n          std::string fullpath = dirname.c_str();\n          fullpath += fname;\n          XrdOucString originode;\n          XrdOucString origpath = fname.c_str();\n\n          // Demangle the original pathname\n          while (origpath.replace(\"#:#\", \"/\")) {\n          }\n\n          XrdOucErrInfo error;\n          XrdOucString type = \"file\";\n          std::string deleter;\n          struct stat buf;\n\n          if (!gOFS->_stat(fullpath.c_str(), &buf, error, vid, \"\", nullptr, false)) {\n            if (translateids) {\n              int errc = 0;\n              uids = eos::common::Mapping::UidToUserName(buf.st_uid, errc).c_str();\n\n              if (errc) {\n                uids = eos::common::Mapping::UidAsString(buf.st_uid).c_str();\n              }\n\n              gids = eos::common::Mapping::GidToGroupName(buf.st_gid, errc).c_str();\n\n              if (errc) {\n                gids = eos::common::Mapping::GidAsString(buf.st_gid).c_str();\n              }\n            } else {\n              uids = eos::common::Mapping::UidAsString(buf.st_uid).c_str();\n              gids = eos::common::Mapping::GidAsString(buf.st_gid).c_str();\n            }\n\n            if (origpath.endswith(Recycle::gRecyclingPostFix.c_str())) {\n              type = \"recursive-dir\";\n              origpath.erase(origpath.length() - Recycle::gRecyclingPostFix.length());\n            }\n\n            originode = origpath;\n            originode.erase(0, origpath.length() - 16);\n            origpath.erase(origpath.length() - 17);\n\n            // put the key prefixes\n            if (type == \"file\") {\n              originode.insert(\"fxid:\", 0);\n            } else {\n              originode.insert(\"pxid:\", 0);\n            }\n\n            if (whodeleted) {\n              if (!gOFS->_attr_get(fullpath.c_str(), error, vid, \"\",\n                                   eos::common::EOS_DTRACE_ATTR,\n                                   deleter)) {\n              } else {\n                deleter = \"{}\";\n              }\n            }\n\n            if (monitoring) {\n              oss_out << \"recycle=ls recycle-bin=\" << Recycle::gRecyclingPrefix\n                      << \" uid=\" << uids.c_str() << \" gid=\" << gids.c_str()\n                      << \" size=\" << std::to_string(buf.st_size)\n                      << \" deletion-time=\" << std::to_string(buf.st_ctime)\n                      << \" type=\" << type.c_str()\n                      << \" keylength.restore-path=\" << origpath.length()\n                      << \" restore-path=\" << origpath.c_str()\n                      << \" restore-key=\" << originode.c_str()\n                      << \" dtrace=\\\"\" << deleter.c_str() << \"\\\"\"\n                      << std::endl;\n\n              if (rvec) {\n                std::map<std::string, std::string> rmap;\n                rmap[\"uid\"] = std::to_string(buf.st_uid);\n                rmap[\"gid\"] = std::to_string(buf.st_gid);\n                rmap[\"username\"] = uids.c_str();\n                rmap[\"groupname\"] = gids.c_str();\n                rmap[\"size\"] = std::to_string(buf.st_size);\n                rmap[\"dtime\"] = std::to_string(buf.st_ctime);\n                rmap[\"type\"] = type.c_str();\n                rmap[\"path\"] = origpath.c_str();\n                rmap[\"key\"] = originode.c_str();\n                rmap[\"dtrace\"] = deleter.c_str();\n                rvec->push_back(rmap);\n              }\n            } else {\n              char sline[4096];\n\n              if (count == 0) {\n                // print a header\n                snprintf(sline, sizeof(sline) - 1,\n                         \"# %-24s %-8s %-8s %-12s %-13s %-21s %-64s %-32s\\n\", \"Deletion Time\", \"UID\",\n                         \"GID\",\n                         \"SIZE\", \"TYPE\", \"RESTORE-KEY\", \"RESTORE-PATH\", \"DTRACE\");\n                oss_out << sline\n                        << \"# ================================================\"\n                        << \"==================================================\"\n                        << \"=========================================================\"\n                        << \"=============================\"\n                        << std::endl;\n              }\n\n              char tdeltime[4096];\n              std::string deltime = ctime_r(&buf.st_ctime, tdeltime);\n              deltime.erase(deltime.length() - 1);\n              snprintf(sline, sizeof(sline) - 1,\n                       \"%-26s %-8s %-8s %-12s %-13s %-16s %-64s %-32s\",\n                       deltime.c_str(), uids.c_str(), gids.c_str(),\n                       StringConversion::GetSizeString((unsigned long long) buf.st_size).c_str(),\n                       type.c_str(), originode.c_str(), origpath.c_str(), deleter.c_str());\n\n              if (oss_out.tellp() > 1 * 1024 * 1024 * 1024) {\n                retc = E2BIG;\n                oss_out << \"... (truncated after 1G of output)\" << std::endl;\n                std_out += oss_out.str();\n                std_err += \"warning: list too long - truncated after 1GB of output!\\n\";\n                return E2BIG;\n              }\n\n              oss_out << sline << std::endl;\n            }\n\n            count++;\n\n            if ((vid.uid) && (!vid.sudoer) && (count > 100000)) {\n              retc = E2BIG;\n              oss_out << \"... (truncated)\" << std::endl;\n              std_out += oss_out.str();\n              std_err += \"warning: list too long - truncated after 100000 entries!\\n\";\n              return E2BIG;\n            }\n          }\n        }\n      }\n    }\n  } else {\n    auto map_quotas = Quota::GetGroupStatistics(Recycle::gRecyclingPrefix,\n                      Quota::gProjectId);\n\n    if (!map_quotas.empty()) {\n      unsigned long long used_bytes = map_quotas[SpaceQuota::kGroupLogicalBytesIs];\n      unsigned long long max_bytes = map_quotas[SpaceQuota::kGroupLogicalBytesTarget];\n      unsigned long long used_inodes = map_quotas[SpaceQuota::kGroupFilesIs];\n      unsigned long long max_inodes = map_quotas[SpaceQuota::kGroupFilesTarget];\n      char sline[4096];\n\n      if (!monitoring) {\n        oss_out << \"# _________________________________________________________\"\n                << \"___________________________________________________________\"\n                << \"___________________________\" << std::endl;\n        snprintf(sline, sizeof(sline) - 1, \"# used %s out of %s (%.02f%% volume) \"\n                 \"used %llu out of %llu (%.02f%% inodes used) Object-Lifetime %lu [s] Keep-Ratio %.02f\",\n                 StringConversion::GetReadableSizeString(used_bytes, \"B\").c_str(),\n                 StringConversion::GetReadableSizeString(max_bytes, \"B\").c_str(),\n                 used_bytes * 100.0 / max_bytes,\n                 used_inodes, max_inodes, used_inodes * 100.0 / max_inodes,\n                 gOFS->mRecycler->GetKeepTime(),\n                 gOFS->mRecycler->GetKeepRatio());\n        oss_out << sline << std::endl\n                << \"# _________________________________________________________\"\n                << \"___________________________________________________________\"\n                << \"___________________________\" << std::endl;\n      } else {\n        snprintf(sline, sizeof(sline) - 1, \"recycle-bin=%s usedbytes=%llu \"\n                 \"maxbytes=%llu volumeusage=%.02f%% usedinodes=%llu \"\n                 \"maxinodes=%llu inodeusage=%.02f%% lifetime=%lu ratio=%.02f\",\n                 Recycle::gRecyclingPrefix.c_str(),\n                 used_bytes, max_bytes, used_bytes * 100.0 / max_bytes,\n                 used_inodes, max_inodes, used_inodes * 100.0 / max_inodes,\n                 gOFS->mRecycler->GetKeepTime(),\n                 gOFS->mRecycler->GetKeepRatio());\n        oss_out << sline << std::endl;\n      }\n    }\n  }\n\n  std_out += oss_out.str();\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Check if client is allowed to restore the given recyle path\n//------------------------------------------------------------------------------\nint\nRecycle::IsAllowedToRestore(std::string_view recycle_path,\n                            const eos::common::VirtualIdentity& vid)\n{\n  static const std::string sUserRecyclePrefix =\n    SSTR(Recycle::gRecyclingPrefix << \"uid:\");\n  eos_static_debug(\"msg=\\\"attempt file restore\\\" path=\\\"%s\\\"\",\n                   recycle_path.data());\n\n  // Root is allowed to restore anything\n  // if (vid.uid == 0) {\n  //   return 0;\n  // }\n\n  // If this is a user area then restore is allowed only for the owner\n  if (recycle_path.find(sUserRecyclePrefix) == 0) {\n    std::string usr_recycle = SSTR(sUserRecyclePrefix << vid.uid);\n\n    if (recycle_path.find(usr_recycle) != 0) {\n      return EPERM;\n    }\n  }\n\n  struct stat buf;\n\n  XrdOucErrInfo lerror;\n\n  if (gOFS->_stat(recycle_path.data(), &buf, lerror,\n                  mRootVid, \"\", nullptr, false)) {\n    return EIO;\n  }\n\n  // If not owner of the recycle entry\n  if (vid.uid != buf.st_uid) {\n    // Evalue the parent ACLs for right to read\n    eos::common::Path cpath(recycle_path.data());\n    std::string parent_dir = cpath.GetParentPath();\n\n    try {\n      auto cmd = gOFS->eosView->getContainer(parent_dir);\n      auto cmd_rlock = eos::MDLocking::readLock(cmd.get());\n      auto xattrs = cmd->getAttributes();\n      Acl acl(xattrs, vid);\n\n      if (acl.CanRead()) {\n        return 0;\n      }\n\n      return EPERM;\n    } catch (const eos::MDException& e) {\n      eos_static_err(\"msg=\\\"missing parent directory for restore check\\\" \"\n                     \"path=\\\"%s\\\"\", parent_dir.c_str());\n      return ENOENT;\n    }\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Get recycle bin path from the given restore key information\n//------------------------------------------------------------------------------\nint\nRecycle::GetPathFromRestoreKey(std::string_view key,\n                               const eos::common::VirtualIdentity& vid,\n                               std::string& std_err, std::string& recycle_path)\n{\n  if (key.empty()) {\n    std_err += \"error: invalid argument as recycle key\";\n    return EINVAL;\n  }\n\n  bool force_file = false;\n  bool force_dir = false;\n  std::string skey(key);\n\n  if (skey.find(\"fxid:\") == 0) {\n    skey.erase(0, 5);\n    force_file = true;\n  } else if (skey.find(\"pxid:\") == 0) {\n    skey.erase(0, 5);\n    force_dir = true;\n  } else {\n    std_err = SSTR(\"error: unknow recycle key format \\\"\"\n                   << skey << \"\\\"\");\n    return EINVAL;\n  }\n\n  // Make sure the value is a hex\n  unsigned long long id = 0ull;\n\n  try {\n    id = std::stoull(skey, 0, 16);\n  } catch (...) {\n    std_err = \"error: recycle key must containe a hex value\";\n    return EINVAL;\n  }\n\n  // Full path of the target inside the recycle bin\n  {\n    std::shared_ptr<eos::IFileMD> fmd;\n\n    if (!force_dir) {\n      try {\n        eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, id);\n        auto fmd = gOFS->eosFileService->getFileMD(id);\n        recycle_path = gOFS->eosView->getUri(fmd.get());\n      } catch (const eos::MDException& e) {\n        // empty\n      }\n    }\n\n    if (!force_file && !fmd) {\n      try {\n        eos::Prefetcher::prefetchContainerMDWithParentsAndWait(gOFS->eosView, id);\n        auto cmd = gOFS->eosDirectoryService->getContainerMD(id);\n        recycle_path = gOFS->eosView->getUri(cmd.get());\n      } catch (const eos::MDException& e) {\n        // empty\n      }\n    }\n\n    if (recycle_path.empty()) {\n      std_err = SSTR(\"error: cannot find object referenced by recycle-key=\"\n                     << key);\n      return ENOENT;\n    }\n  }\n\n  // Check that this is a path to recycle\n  if (recycle_path.find(Recycle::gRecyclingPrefix.c_str()) != 0) {\n    std_err = \"error: referenced object is not in the recycle bin\";\n    return EINVAL;\n  }\n\n  return 0;\n}\n\n//----------------------------------------------------------------------------\n// Demangle path from recycle bin to obtain the original path\n//----------------------------------------------------------------------------\nstd::string\nRecycle::DemanglePath(std::string_view recycle_path)\n{\n  std::string orig_path(recycle_path);\n\n  // This should not contain any '/'\n  if (orig_path.empty() || orig_path.find('/') != std::string::npos) {\n    return std::string();\n  }\n\n  eos::common::replace_all(orig_path, \"#:#\", \"/\");\n  // The recycle path also contains a dot and the hex value of the\n  // namespace object it references - written with 16 padding. For directories\n  // it also contains a \".d\" at the end that needs to be removed\n  size_t olen = orig_path.length();\n\n  if ((olen > 2) && (orig_path.rfind(Recycle::gRecyclingPostFix) == olen - 2)) {\n    orig_path.erase(olen - 2);\n    olen -= 2;\n  }\n\n  if (olen >= 17) {\n    orig_path.erase(olen - 17);\n  }\n\n  return orig_path;\n}\n\n//------------------------------------------------------------------------------\n// Restore an entry from the recycle bin to the original location\n//------------------------------------------------------------------------------\nint\nRecycle::Restore(std::string& std_out, std::string& std_err,\n                 eos::common::VirtualIdentity& vid, std::string_view key,\n                 bool force_orig_name, bool restore_versions, bool make_path)\n{\n  std::string recycle_path;\n  int retc = GetPathFromRestoreKey(key, vid, std_err, recycle_path);\n\n  if (retc) {\n    return retc;\n  }\n\n  // Check if client is allowed to restore the given entry\n  retc = IsAllowedToRestore(recycle_path, vid);\n\n  if (retc) {\n    std_err = \"error: client not allowed to restore given path\";\n    return retc;\n  }\n\n  // Reconstruct original file name\n  eos::common::Path cPath(recycle_path.c_str());\n  std::string orig_path = DemanglePath(cPath.GetName());\n\n  if (orig_path.empty()) {\n    std_err = \"error: failed to demangle recycle path\";\n    return EINVAL;\n  }\n\n  eos::common::Path oPath(orig_path.c_str());\n  XrdOucErrInfo lerror;\n  struct stat buf;\n\n  // Check if original parent path exists\n  if (gOFS->_stat(oPath.GetParentPath(), &buf, lerror, mRootVid, \"\")) {\n    if (make_path) {\n      ProcCommand cmd;\n      XrdOucString info = \"mgm.cmd=mkdir&mgm.option=p&mgm.path=\";\n      info += oPath.GetParentPath();\n      cmd.open(\"/proc/user\", info.c_str(), vid, &lerror);\n      cmd.close();\n      int rc = cmd.GetRetc();\n\n      if (rc) {\n        std_err = SSTR(\"error: creation failed: \" << cmd.GetStdErr());\n        return rc;\n      }\n    } else {\n      std_err = SSTR(\"error: you have to recreate the restore directory path=\"\n                     << oPath.GetParentPath()\n                     << \" to be able to restore this file/tree\\n\"\n                     << \"hint: retry after creating the mentioned directory\");\n      return ENOENT;\n    }\n  }\n\n  // Check if original path exists\n  if (!gOFS->_stat(oPath.GetPath(), &buf, lerror, mRootVid, \"\", nullptr, false)) {\n    if (force_orig_name == false) {\n      std_err = \"error: the original path already exists, use \"\n                \"'-f|--force-original-name' to put the deleted file/tree\\n\"\n                \" back and rename the file/tree in place to <name>.<inode>\";\n      return EEXIST;\n    } else {\n      char sp[256];\n      snprintf(sp, sizeof(sp) - 1, \"%016llx\",\n               (unsigned long long)(S_ISDIR(buf.st_mode) ? buf.st_ino :\n                                    eos::common::FileId::InodeToFid(buf.st_ino)));\n      std::string newold = SSTR(oPath.GetPath() << \".\" << sp);\n\n      if (gOFS->_rename(oPath.GetPath(), newold.c_str(), lerror, mRootVid, \"\", \"\",\n                        true, true)) {\n        std_err = SSTR(\"error: failed to rename the existing file/tree where we \"\n                       \"need to restore path=\" << oPath.GetPath() << std::endl\n                       << lerror.getErrText());\n        return EIO;\n      } else {\n        std_out = SSTR(\"warning: renamed restore path=\" << oPath.GetPath()\n                       << \" to backup-path=\" << newold.c_str());\n      }\n    }\n  }\n\n  // Do the 'undelete' aka rename\n  if (gOFS->_rename(cPath.GetPath(), oPath.GetPath(), lerror, mRootVid,\n                    \"\", \"\", true)) {\n    std_err = SSTR(\"error: failed to undelete path=\" << oPath.GetPath());\n    return EIO;\n  } else {\n    std_out = SSTR(\"success: restored path=\" << oPath.GetPath());\n  }\n\n  // Skip version restore if not requested\n  if (restore_versions == false) {\n    return 0;\n  }\n\n  // Attempt to restore versions\n  std::string vkey;\n\n  if (gOFS->_attr_get(oPath.GetPath(), lerror, mRootVid, \"\",\n                      Recycle::gRecyclingVersionKey.c_str(), vkey)) {\n    // No version directory to restore\n    return 0;\n  }\n\n  retc = Restore(std_out, std_err, vid, vkey.c_str(),\n                 force_orig_name, restore_versions);\n\n  // Mask an non existent version reference\n  if (retc == ENOENT) {\n    return 0;\n  }\n\n  return retc;\n}\n\n//------------------------------------------------------------------------------\n// Purge files in the recycle bin\n//------------------------------------------------------------------------------\nint\nRecycle::Purge(std::string& std_out, std::string& std_err,\n               eos::common::VirtualIdentity& vid,\n               std::string key, std::string_view date,\n               std::string_view type, std::string_view recycle_id)\n{\n  // Basic permission checks\n  if (vid.uid && !vid.sudoer &&\n      !(vid.hasUid(eos::common::ADM_UID)) &&\n      !(vid.hasGid(eos::common::ADM_GID))) {\n    std_err = \"error: you cannot purge your recycle bin without being a \"\n              \"sudo or having an admin role\";\n    return EPERM;\n  }\n\n  if (!key.empty() && !date.empty()) {\n    std_err = \"error: recycle key and date can not be used together\";\n    return EINVAL;\n  }\n\n  // Sanitize user input\n  if (!date.empty()) {\n    for (const auto& ch : date) {\n      if (!isdigit(ch) && (ch != '/')) {\n        std_err = \"error: invalid date format\";\n        return EINVAL;\n      }\n    }\n  }\n\n  // Path that needs to be purged\n  std::string recycle_path;\n\n  if (!key.empty()) {\n    int retc = GetPathFromRestoreKey(key, vid, std_err, recycle_path);\n\n    if (retc) {\n      return retc;\n    }\n  } else if (!date.empty()) {\n    char sdir[4096];\n\n    if ((type == \"all\") && (vid.uid == 0)) {\n      snprintf(sdir, sizeof(sdir) - 1, \"%s/\", Recycle::gRecyclingPrefix.c_str());\n    } else if ((type == \"rid\") && !recycle_id.empty()) {\n      snprintf(sdir, sizeof(sdir) - 1, \"%s/rid:%s/%s\",\n               Recycle::gRecyclingPrefix.c_str(),\n               recycle_id.data(), date.data());\n    } else {\n      snprintf(sdir, sizeof(sdir) - 1, \"%s/uid:%u/%s\",\n               Recycle::gRecyclingPrefix.c_str(),\n               (unsigned int) vid.uid, date.data());\n    }\n\n    recycle_path = sdir;\n  }\n\n  if (recycle_path.empty()) {\n    std_err = \"error: empty recycle path\";\n    return EINVAL;\n  }\n\n  // Make sure the path to purge is inside the recycle bine\n  if (recycle_path.find(Recycle::gRecyclingPrefix) != 0) {\n    std_err = SSTR(\"error: purge path \\\"\" << recycle_path\n                   << \"\\\" is not in the recyle bin \");\n    return EINVAL;\n  }\n\n  // Determine if we need to remove a subtree or a file\n  struct stat buf;\n  XrdOucErrInfo lerror;\n\n  if (gOFS->_stat(recycle_path.c_str(), &buf, lerror, mRootVid, \"\",\n                  nullptr, false)) {\n    std_err = SSTR(\"error: recycle path \" << recycle_path\n                   << \" does not exist\");\n    return ENOENT;\n  }\n\n  if (S_ISDIR(buf.st_mode)) {\n    RemoveSubtree(recycle_path.data());\n  } else {\n    (void) gOFS->_rem(recycle_path.c_str(), lerror, mRootVid, nullptr);\n  }\n\n  std_out = SSTR(\"success: purged path \" << recycle_path\n                 << \" from recycle bin!\");\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Configure the recycle bin parameters\n//------------------------------------------------------------------------------\nint\nRecycle::Config(std::string& std_out, std::string& std_err,\n                eos::common::VirtualIdentity& vid,\n                eos::console::RecycleProto_ConfigProto_OpType op,\n                const std::string& value)\n{\n  XrdOucErrInfo lerror;\n\n  if (vid.uid != 0) {\n    std_err = \"error: you need to be root to configure the recycle bin\"\n              \" and/or recycle polcies\\n\";\n    return EPERM;\n  }\n\n  if (op == eos::console::RecycleProto_ConfigProto::ADD_BIN) {\n    if (value.empty()) {\n      std_err = \"error: missing subtree argument\\n\";\n      return EINVAL;\n    }\n\n    // execute a proc command\n    ProcCommand Cmd;\n    XrdOucString info;\n    info = \"eos.rgid=0&eos.ruid=0&mgm.cmd=attr&mgm.subcmd=set&mgm.option=r&mgm.path=\";\n    info += value.c_str();\n    info += \"&mgm.attr.key=\";\n    info += Recycle::gRecyclingAttribute.c_str();\n    info += \"&mgm.attr.value=\";\n    info += Recycle::gRecyclingPrefix.c_str();\n    int result = Cmd.open(\"/proc/user\", info.c_str(), mRootVid, &lerror);\n    Cmd.AddOutput(std_out, std_err);\n    Cmd.close();\n    return result;\n  }\n\n  if (op == eos::console::RecycleProto_ConfigProto::RM_BIN) {\n    if (value.empty()) {\n      std_err = \"error: missing subtree argument\\n\";\n      return EINVAL;\n    }\n\n    // execute a proc command\n    ProcCommand Cmd;\n    XrdOucString info;\n    info = \"eos.rgid=0&eos.ruid=0&mgm.cmd=attr&mgm.subcmd=rm&mgm.option=r&mgm.path=\";\n    info += value.c_str();\n    info += \"&mgm.attr.key=\";\n    info += Recycle::gRecyclingAttribute.c_str();\n    int result = Cmd.open(\"/proc/user\", info.c_str(), mRootVid, &lerror);\n    Cmd.AddOutput(std_out, std_err);\n    Cmd.close();\n    return result;\n  }\n\n  if (op == eos::console::RecycleProto_ConfigProto::LIFETIME) {\n    if (value.empty()) {\n      std_err = \"error: missing lifetime argument\";\n      return EINVAL;\n    }\n\n    uint64_t size = 0ull;\n\n    try {\n      size = std::stoull(value);\n    } catch (...) {\n      size = 0ull;\n    }\n\n    if (!size) {\n      std_err = \"error: lifetime has been converted to 0 seconds - probably you made a typo!\";\n      return EINVAL;\n    }\n\n    if (size < 60) {\n      std_err = \"error: a recycle bin lifetime less than 60s is not accepted!\";\n      return EINVAL;\n    }\n\n    if (!gOFS->mRecycler->mPolicy.Config(RecyclePolicy::sKeepTimeKey, value,\n                                         std_err)) {\n      return EINVAL;\n    } else {\n      std_out += \"success: recycle bin lifetime configured!\\n\";\n    }\n  } else if (op == eos::console::RecycleProto_ConfigProto::RATIO) {\n    if (value.empty()) {\n      std_err = \"error: missing ratio argument\\n\";\n      return EINVAL;\n    }\n\n    double ratio = 0.0;\n\n    try {\n      ratio = std::stod(value);\n    } catch (...) {\n      ratio = 0.0;\n    }\n\n    if ((ratio <= 0) || (ratio > 0.99)) {\n      std_err = \"error: a recycle bin ratio has to be 0 < ratio < 1.0!\";\n      return EINVAL;\n    }\n\n    if (!gOFS->mRecycler->mPolicy.Config(RecyclePolicy::sRatioKey, value,\n                                         std_err)) {\n      return EINVAL;\n    } else {\n      std_out += \"success: recycle bin ratio configured!\";\n    }\n  } else if (op == eos::console::RecycleProto_ConfigProto::COLLECT_INTERVAL) {\n    if (value.empty()) {\n      std_err = \"error: missing collect interval value\\n\";\n      return EINVAL;\n    }\n\n    try {\n      uint64_t collect_interval = std::stoull(value);\n\n      // Make sure the collect interval is never less than 10 sec\n      if (collect_interval < 10) {\n        std_err = \"error: recycle collect interval has to be > 10\";\n        return EINVAL;\n      }\n    } catch (...) {\n      std_err = \"error: recycle collect interval not numeric\";\n      return EINVAL;\n    }\n\n    if (!gOFS->mRecycler->mPolicy.Config(RecyclePolicy::sCollectKey, value,\n                                         std_err)) {\n      return EINVAL;\n    } else {\n      std_out += \"success: recycle bin update collect interval\";\n    }\n  } else if (op == eos::console::RecycleProto_ConfigProto::REMOVE_INTERVAL) {\n    if (value.empty()) {\n      std_err = \"error: missing remove interval value\\n\";\n      return EINVAL;\n    }\n\n    try {\n      uint64_t remove_interval = std::stoull(value);\n\n      // Make sure the collect interval is never less than 10 sec\n      if (remove_interval < 10) {\n        std_err = \"error: recycle remove interval has to be > 10\";\n        return EINVAL;\n      }\n\n      if (remove_interval >= gOFS->mRecycler->GetCollectInterval()) {\n        std_err = \"erorr: remove interval needs to be smaller than \"\n                  \"the collect interval\";\n        return EINVAL;\n      }\n    } catch (...) {\n      std_err = \"error: recycle remove interval not numeric\";\n      return EINVAL;\n    }\n\n    if (!gOFS->mRecycler->mPolicy.Config(RecyclePolicy::sRemoveKey, value,\n                                         std_err)) {\n      return EINVAL;\n    } else {\n      std_out += \"success: recycle bin update remove interval\";\n    }\n  } else if (op == eos::console::RecycleProto_ConfigProto::DRY_RUN) {\n    if (value.empty() || ((value != \"yes\") && (value != \"no\"))) {\n      std_err = \"error: missing/wrong dry-run value\\n\";\n      return EINVAL;\n    }\n\n    if (!gOFS->mRecycler->mPolicy.Config(RecyclePolicy::sDryRunKey, value,\n                                         std_err)) {\n      return EINVAL;\n    } else {\n      std_out += \"success: recycle bin update dry-run option\";\n    }\n  } else if (op == eos::console::RecycleProto_ConfigProto::ENFORCE) {\n    if (value.empty() || ((value != \"on\") && (value != \"off\"))) {\n      std_err = \"error: missing/wrong enforce value\\n\";\n      return EINVAL;\n    }\n\n    if (!gOFS->mRecycler->mPolicy.Config(RecyclePolicy::sEnforceKey, value,\n                                         std_err)) {\n      return EINVAL;\n    } else {\n      if (value == \"on\") {\n        std_out += \"success: recycle bin enforced\";\n      } else {\n        std_out += \"success: recycle bin not enforced\";\n      }\n    }\n  } else if (op == eos::console::RecycleProto_ConfigProto::ENABLE) {\n    if (value.empty() || ((value != \"on\") && (value != \"off\"))) {\n      std_err = \"error: missing/wrong ennable value\\n\";\n      return EINVAL;\n    }\n\n    if (!gOFS->mRecycler->mPolicy.Config(RecyclePolicy::sEnableKey, value, std_err)) {\n      return EINVAL;\n    } else {\n      if (value == \"on\") {\n        std_out += \"success: recycle bin enabled\";\n      } else {\n        std_out += \"success: recycle bin disabled\";\n      }\n    }\n  } else {\n    std_err = \"error: unknown configuration operation\";\n    return EINVAL;\n  }\n\n  gOFS->mRecycler->NotifyConfigUpdate();\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Configure a recycle id for the given path\n//------------------------------------------------------------------------------\nint\nRecycle::RecycleIdSetup(std::string_view path, std::string_view acl,\n                        std::string& std_err)\n{\n  // Check that the given path exist and save the container id\n  eos::ContainerIdentifier cid {0ull};\n  std::string recycle_id_val;\n\n  try {\n    auto cmd = gOFS->eosView->getContainer(path.data());\n    auto cmd_lock = eos::MDLocking::readLock(cmd.get());\n    cid = cmd->getIdentifier();\n\n    if (cmd->hasAttribute(Recycle::gRecycleIdXattrKey)) {\n      recycle_id_val = cmd->getAttribute(Recycle::gRecycleIdXattrKey);\n    }\n  } catch (eos::MDException& e) {\n    std_err = SSTR(\"error: path does not exist \" << path.data()\n                   << \" msg=\" << e.what()).c_str();\n    return ENOENT;\n  }\n\n  // Don't allow if path is inside the \"proc\" hierarchy\n  if (path.find(gOFS->MgmProcPath.c_str()) == 0) {\n    std_err = \"error: path can not be inside the proc hierarchy\";\n    return EPERM;\n  }\n\n  // If there is already a recycle id attribute then skip applying it\n  if (recycle_id_val.empty()) {\n    recycle_id_val = std::to_string(cid.getUnderlyingUInt64());\n  }\n\n  // Create the recycle directory for the given recycle id\n  XrdOucErrInfo lerror;\n  std::string proj_recycle_path = Recycle::gRecyclingPrefix + \"rid:\" +\n                                  recycle_id_val;\n\n  // @todo(esindril) consider either to avoid inheriting sys.recycle xattrs or\n  // to move the Recycler configuration inside the EOS config and drop the\n  // configuration via xattrs!\n  if (gOFS->_mkdir(proj_recycle_path.c_str(), S_IRUSR | S_IXUSR | SFS_O_MKPTH,\n                   lerror, mRootVid, \"\")) {\n    std_err = \"error: failed to create recycle project directory\";\n    return EINVAL;\n  }\n\n  // Add requested ACLs to the recycle bin top project directory\n  if (!acl.empty()) {\n    eos::console::RequestProto req;\n    eos::console::AclProto* acl_req = req.mutable_acl();\n    acl_req->set_recursive(true);\n    acl_req->set_sys_acl(true);\n    acl_req->set_op(eos::console::AclProto::MODIFY);\n    acl_req->set_rule(acl.data());\n    acl_req->set_path(proj_recycle_path);\n    eos::mgm::AclCmd acl_cmd(std::move(req), mRootVid);\n    eos::console::ReplyProto reply = acl_cmd.ProcessRequest();\n\n    if (reply.retc()) {\n      std_err = reply.std_err();\n      return reply.retc();\n    }\n  }\n\n  // Apply recursively the sys.forced.recycleid=<cid_val> to the entire\n  // original subtree and make sure no sub-dir was skipped\n  unsigned int attempts = 5;\n\n  do {\n    XrdOucString lerr;\n    bool exclusive = false;\n    std::map<std::string, std::set<std::string>> found;\n    // Make sure we skip version directories as these should not have\n    // any xattrs set on them.\n    if (gOFS->_find(path.data(), lerror, lerr, mRootVid, found, nullptr, //\n                    nullptr, true, 0, false, 0, nullptr, true)) {        //\n      std_err = \"error: failed to search in given path\";\n      return errno;\n    }\n\n    for (auto find_it = found.cbegin(); find_it != found.cend(); ++find_it) {\n      if (gOFS->_attr_set(find_it->first.c_str(), lerror, mRootVid,\n                          (const char*) 0, Recycle::gRecycleIdXattrKey.c_str(),\n                          recycle_id_val.c_str(), exclusive)) {\n        if (lerror.getErrInfo() != ENOENT) {\n          std_err = \"error: failed to set xattr on path \";\n          std_err += find_it->first;\n          return errno;\n        }\n      }\n    }\n  } while ((--attempts > 0) &&\n           !AllHierarchyHasXattr(path, Recycle::gRecycleIdXattrKey,\n                                 recycle_id_val));\n\n  if ((attempts == 0) &&\n      !AllHierarchyHasXattr(path, Recycle::gRecycleIdXattrKey, recycle_id_val)) {\n    std_err += \"error: failed to propagate sys.forced.recycleid in the hiearchy \";\n    std_err += path;\n    return EINVAL;\n  }\n\n  return 0;\n}\n\n//----------------------------------------------------------------------------\n// Handle symlink or symlink like file names during recycle operations\n//----------------------------------------------------------------------------\nstd::string\nRecycle::HandlePotentialSymlink(const std::string& ppath,\n                                const std::string& fn)\n{\n  size_t pos = fn.find(\" -> \");\n\n  if (pos == std::string::npos) {\n    return fn;\n  }\n\n  // Check if this file name actually exists\n  std::string fpath = ppath + fn;\n  struct stat buf;\n  XrdOucErrInfo lerror;\n\n  if (gOFS->_stat(fpath.c_str(), &buf, lerror, mRootVid,\n                  \"\", nullptr, false) == SFS_OK) {\n    return fn;\n  }\n\n  // This means we are dealing with a symlink file so we need to remove the\n  // target from the filename so that we actually work with it\n  return fn.substr(0, pos);\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/recycle/Recycle.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Recycle.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/****************************(********************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"mgm/recycle/RecyclePolicy.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/SystemClock.hh\"\n#include \"proto/Recycle.pb.h\"\n#include <XrdOuc/XrdOucString.hh>\n#include <sys/types.h>\n\nclass XrdOucErrInfo;\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! @brief  This class implements the thread cleaning up a recycling bin and the\n//! movement of a file or bulk directory to the recycling bin\n//!\n//! If the class is called with an empty constructor the Start function starts a\n//! which is cleaning up under gRecyclingPrefix according to the attribute\n//! \"sys.recycle.keep\" which defines the time in second how long files are kept\n//! in the recycling bin.\n//! If the class is called with the complex constructor it is used with the ToGarbage\n//! method to move a deleted file or a bulk deletion into the recycling bin.\n//! The Recycling bin has the substructure <instance-proc>/recycle/<gid>/<uid>/\n//! <constracted-path>.<08x:inode>\n//! The constrcated path is the full path of the file where all '/' are replaced\n//! with a '#:#'\n//------------------------------------------------------------------------------\nclass Recycle\n{\npublic:\n  typedef std::vector<std::map<std::string, std::string>> RecycleListing;\n  //! Prefix for all recycle bins\n  static std::string gRecyclingPrefix;\n  //! Attribute key defining a recycling location\n  static std::string gRecyclingAttribute;\n  static std::string gRecyclingPostFix;\n  //! Attribute key storing the recycling key of the version directory\n  //! belonging to a given file\n  static std::string gRecyclingVersionKey;\n  //! Recycle id extended attribute value used to store the container id\n  //! for which the corresponding recycle directory belong to\n  static std::string gRecycleIdXattrKey;\n  //! Root virtual identity used internally for unrestricted operations\n  static eos::common::VirtualIdentity mRootVid;\n  //! Timestamp of the last remove operation done by the recycler\n  static std::chrono::seconds mLastRemoveTs;\n\n  //----------------------------------------------------------------------------\n  //! Configure the recycle bin\n  //!\n  //! @param std_out where to print\n  //! @param std_err where to print\n  //! @param vid of the client\n  //! @param op operation type according to Recycle.proto\n  //! @param value configuration value for the given operation type\n  //!\n  //! @return 0 if successful, otherwise errno\n  //----------------------------------------------------------------------------\n  static int Config(std::string& std_out, std::string& std_err,\n                    eos::common::VirtualIdentity& vid,\n                    eos::console::RecycleProto_ConfigProto_OpType op,\n                    const std::string& value);\n\n  //----------------------------------------------------------------------------\n  //! Configure a recycle id for the given path and optionally set the given\n  //! ACL on the computed top level recycle directory to control access with\n  //! respect to restoring recycled entries\n  //!\n  //! @param path path to top level directory that is to be labeled with a\n  //!             recycle id. The recycle id will match the top container id.\n  //! @param acl string representation of ACLs to be appended to the top\n  //!             recycle directory\n  //! @param std_err output string holding any potential error message\n  //!\n  //! @return 0 if succsessful, otherwise errno\n  //----------------------------------------------------------------------------\n  static int RecycleIdSetup(std::string_view path, std::string_view acl,\n                            std::string& std_err);\n\n  //----------------------------------------------------------------------------\n  //! Print the recycle bin contents\n  //!\n  //! @param std_out where to print\n  //! @param std_err where to print\n  //! @param vid of the client\n  //! @param monitoring selects monitoring key-value output format\n  //! @param translateids selects to display uid/gid as number or string\n  //! @param details display detailed info\n  //! @param display_type e.g. all, by uid, by recycle id\n  //! @param display_value\n  //! @param date filter recycle bin for given date <year> or <year>/<month>\n  //!        or <year>/<month>/<day>\n  //! @param rvec a vector of maps with all recycle informations requested\n  //! @param whodeleted - show who exectued a deletion\n  //! @param maxentries - maximum number of entries to report\n  //!\n  //! @return 0 if success, E2BIG if return list is limited\n  //----------------------------------------------------------------------------\n  static int Print(std::string& std_out, std::string& std_err,\n                   eos::common::VirtualIdentity& vid, bool monitoring,\n                   bool transalteids, bool details,\n                   std::string_view display_type,\n                   std::string_view display_val,\n                   std::string_view date = \"\", RecycleListing* rvec = 0,\n                   bool whodeleted = true, int32_t maxentries = 0);\n\n  //----------------------------------------------------------------------------\n  //! Restore an entry from the recycle bin to the original location\n  //!\n  //! @param std_out stdout message\n  //! @param std_err stderr error message\n  //! @param vid client virtual identity\n  //! @param key (==inode) identifier to restore (undelete)\n  //! @param force_orig_name flag to force restore to the original name\n  //! @param restore_versions flag to restore all versions\n  //! @param make_path flag to recreate all missing parent directories\n  //!\n  //! @return 0 if successful, otherwise errno\n  //----------------------------------------------------------------------------\n  static int Restore(std::string& std_out, std::string& std_err,\n                     eos::common::VirtualIdentity& vid, std::string_view key,\n                     bool force_orig_name, bool restore_versions,\n                     bool make_path = false);\n\n  //----------------------------------------------------------------------------\n  //! Get the configured keep time\n  //!\n  //! @return keep time in seconds\n  //----------------------------------------------------------------------------\n  inline uint64_t GetKeepTime() const\n  {\n    return mPolicy.mKeepTimeSec.load();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the configured keep ratio\n  //!\n  //! @return keep ratio\n  //----------------------------------------------------------------------------\n  inline double GetKeepRatio() const\n  {\n    return mPolicy.mSpaceKeepRatio.load();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get recycle bin path from the given restore key information\n  //!\n  //! @param key restore key fxid:<val> or pxid:<val>\n  //! @param vid client virtual identity\n  //! @param std_err error message\n  //! @param recycle_path computed recycle path\n  //!\n  //! @return 0 if successful, otherwise errno\n  //----------------------------------------------------------------------------\n  static int\n  GetPathFromRestoreKey(std::string_view key,\n                        const eos::common::VirtualIdentity& vid,\n                        std::string& std_err, std::string& recycle_path);\n\n  //----------------------------------------------------------------------------\n  //! Demangle path from recycle bin to obtain the original path\n  //!\n  //! @param recyle_path recycle path using the #:# encoding. This only\n  //!        contains the flattened structure without the path location\n  //!        inside the recycle bin.\n  //!\n  //! @return original path or empty string if there was an error\n  //----------------------------------------------------------------------------\n  static std::string\n  DemanglePath(std::string_view recycle_path);\n\n  //----------------------------------------------------------------------------\n  //! Purge files in the recycle bin\n  //!\n  //! @param std_out where to print\n  //! @param std_err where to print\n  //! @param vid client virtual identity\n  //! @param key (==inode) identifier to purge (undelete)\n  //! @param date can be empty, <year>, <year>/<month> or <year>/<month>/<day>\n  //! @param type glocal, user or request id type of operation\n  //! @param recycle_id recycle project identifier for scoping this request\n  //!\n  //! @return 0 if successful, otherwise errno\n  //----------------------------------------------------------------------------\n  static int Purge(std::string& std_out, std::string& std_err,\n                   eos::common::VirtualIdentity& vid,\n                   std::string key, std::string_view date,\n                   std::string_view type = \"\",\n                   std::string_view recycle_id = \"\");\n\n  //----------------------------------------------------------------------------\n  //! Check if given path is inside the recycle bin\n  //!\n  //! @param path searched path\n  //!\n  //! @return true if path inside the recycle bin, otherwise false\n  //----------------------------------------------------------------------------\n  static bool InRecycleBin(const std::string& path)\n  {\n    return (path.substr(0, Recycle::gRecyclingPrefix.length()) ==\n            Recycle::gRecyclingPrefix);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if given path matches the top recycle bin directory\n  //!\n  //! @param path searched path\n  //!\n  //! @return true if path inside the recycle bin, otherwise false\n  //----------------------------------------------------------------------------\n  static bool IsTopRecycleBin(std::string path)\n  {\n    if (*(path.rbegin()) != '/') {\n      path += '/';\n    }\n\n    return (path == Recycle::gRecyclingPrefix);\n  }\n\n\n  //----------------------------------------------------------------------------\n  //! Default constructor\n  //----------------------------------------------------------------------------\n  Recycle(bool fake_clock = false):\n    mClock(fake_clock)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~Recycle()\n  {\n    Stop();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Start the recycle thread cleaning up the recycle bin\n  //----------------------------------------------------------------------------\n  void Start()\n  {\n    mThread.reset(&Recycle::Recycler, this);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Stop the recycle thread\n  //----------------------------------------------------------------------------\n  void Stop()\n  {\n    // Unblock thread waiting for timeout of config update\n    NotifyConfigUpdate();\n    mThread.join();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Recycle method doing the clean-up. One should define the\n  //! 'sys.recycle.keeptime' on the recycle directory which is the\n  //! time in seconds of how long files stay in the recycle bin.\n  //!\n  //! @param assistant thread information\n  //----------------------------------------------------------------------------\n  void Recycler(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Notify the recycle that the configuration was updated\n  //----------------------------------------------------------------------------\n  inline void NotifyConfigUpdate()\n  {\n    mCvCfgUpdate.notify_all();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get collect interval value\n  //----------------------------------------------------------------------------\n  inline uint64_t GetCollectInterval() const\n  {\n    return mPolicy.mCollectInterval.load().count();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Dump recycler configutation\n  //!\n  //! @return string representation of the recycler configuration\n  //----------------------------------------------------------------------------\n  inline std::string Dump() const\n  {\n    return mPolicy.Dump();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if the recycling is enforced instance wide\n  //!\n  //! @return true if enforced, otherwise false\n  //----------------------------------------------------------------------------\n  inline bool IsEnforced() const\n  {\n    return mPolicy.mEnforced.load();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if the recycle bin is enabled\n  //!\n  //! @return true if enabled, otherwise false\n  //----------------------------------------------------------------------------\n  inline bool IsEnabled() const\n  {\n    return mPolicy.mEnabled.load();\n  }\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  AssistedThread mThread; ///< Thread doing the recycling\n  RecyclePolicy mPolicy;\n  //! Map holding the container identifier and the full path of the directories\n  //! to be deleted.\n  std::map<eos::IContainerMD::id_t, std::string> mPendingDeletions;\n  eos::common::SystemClock mClock;\n  //! Condition variable to notify a configuration update is needed\n  std::mutex mCvMutex;\n  std::condition_variable mCvCfgUpdate;\n\n  //----------------------------------------------------------------------------\n  //! Check if client is allowed to restore the given recyle path. There are\n  //! two situations when restore is allowed:\n  //! * client is the owner of the entry\n  //! * directory ACLs allow the client to read the entry i.e. restore\n  //!\n  //! @param recycle_path path in the recycle bin to restore\n  //! @param vid client virtual identity\n  //!\n  //! @return 0 if allowe, otherwise errno\n  //----------------------------------------------------------------------------\n  static int\n  IsAllowedToRestore(std::string_view recycle_path,\n                     const eos::common::VirtualIdentity& vid);\n\n  //----------------------------------------------------------------------------\n  //! Remove all the entries in the given subtree\n  //!\n  //! @param fullpath full path to directory\n  //----------------------------------------------------------------------------\n  static void RemoveSubtree(std::string_view fullpath);\n\n  //----------------------------------------------------------------------------\n  //! Handle symlink or symlink like file names. Three scenarios:\n  //! - file does not contain the ' -> ' string so it's returned as it is\n  //! - file is not a symlink but contains the ' -> ' string in its name then\n  //!   we return it as it is\n  //! - file is a symlink so the name contains the ' -> ' string and we need\n  //!   to remove everything after this occurence so that we can properly\n  //!   target the symlink file and not the target of the symlink\n  //!\n  //! @param ppath full parent directory path\n  //! @param fn file name\n  //!\n  //! @return output file name that we can act on\n  //----------------------------------------------------------------------------\n  static std::string\n  HandlePotentialSymlink(const std::string& ppath, const std::string& fn);\n\n  //----------------------------------------------------------------------------\n  //! Collect entries to recycle based on current policy\n  //!\n  //! @param assistant thread assistant\n  //----------------------------------------------------------------------------\n  void CollectEntries(ThreadAssistant& assistant);\n\n  //----------------------------------------------------------------------------\n  //! Remove the pending deletions\n  //----------------------------------------------------------------------------\n  void RemoveEntries();\n\n  //----------------------------------------------------------------------------\n  //! Get cut-off date based on the configured retention policy with respect\n  //! to the current timestamp.\n  //!\n  //! @return a string representing a date <year>/<month>/day numeric format\n  //----------------------------------------------------------------------------\n  std::string GetCutOffDate();\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/recycle/RecycleEntry.cc",
    "content": "//------------------------------------------------------------------------------\n// File: RecycleEntry.cc\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/****************************(********************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/recycle/RecycleEntry.hh\"\n#include \"mgm/recycle/Recycle.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"common/VirtualIdentity.hh\"\n#include <sys/stat.h>\n\n//! Static members\nuint32_t RecycleEntry::sMaxEntriesPerDir = 100000;\neos::common::VirtualIdentity RecycleEntry::mRootVid =\n  eos::common::VirtualIdentity::Root();\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nRecycleEntry::RecycleEntry(std::string_view path, std::string_view recycle_dir,\n                           std::string_view rid,\n                           eos::common::VirtualIdentity* vid, uid_t uid,\n                           gid_t gid, unsigned long long id):\n  mPath(path), mRecycleDir(recycle_dir),\n  mOwnerUid(uid), mOwnerGid(gid), mId(id)\n{\n  if (rid.empty()) {\n    mRecycleId = SSTR(\"uid:\" << mOwnerUid);\n  } else {\n    mRecycleId = SSTR(\"rid:\" << rid.data());\n  }\n\n  // Make sure the recycle dir path does not have ending '/'\n  if (!mRecycleDir.empty() && (*mRecycleDir.rbegin() == '/')) {\n    mRecycleDir.erase(mRecycleDir.length() - 1);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Compute recycle path directory for given user and timestamp\n//------------------------------------------------------------------------------\nint\nRecycleEntry::GetRecyclePrefix(const char* epname, XrdOucErrInfo& error,\n                               std::string& recycle_prefix)\n{\n  char prefix[4096];\n  time_t now = time(NULL);\n  struct tm nowtm;\n  localtime_r(&now, &nowtm);\n  size_t index = 0;\n\n  do {\n    snprintf(prefix, sizeof(prefix) - 1, \"%s/%s/%04u/%02u/%02u/%lu\",\n             mRecycleDir.c_str(), mRecycleId.c_str(), 1900 + nowtm.tm_year,\n             nowtm.tm_mon + 1, nowtm.tm_mday, index);\n    struct stat buf;\n\n    // If index directory exists and it has more than 100k enties then\n    // move on to the next index.\n    if (!gOFS->_stat(prefix, &buf, error, mRootVid, \"\")) {\n      if (buf.st_blksize > sMaxEntriesPerDir) {\n        ++index;\n        continue;\n      }\n    } else {\n      // Create recycle directory\n      if (gOFS->_mkdir(prefix, S_IRUSR | S_IXUSR | SFS_O_MKPTH,\n                       error, mRootVid, \"\")) {\n        return gOFS->Emsg(epname, error, EIO, \"remove existing file - the \"\n                          \"recycle space user directory couldn't be created\");\n      }\n\n      // Check recycle directory ownership\n      if (gOFS->_stat(prefix, &buf, error, mRootVid, \"\")) {\n        return gOFS->Emsg(epname, error, EIO, \"remove existing file - could \"\n                          \"not determine ownership of the recycle space \"\n                          \"user directory\", prefix);\n      }\n\n      // Update the ownership of the user directory\n      if ((buf.st_uid != mOwnerUid) || (buf.st_gid != mOwnerGid)) {\n        if (gOFS->_chown(prefix, mOwnerUid, mOwnerGid, error, mRootVid, \"\")) {\n          return gOFS->Emsg(epname, error, EIO, \"remove existing file - could \"\n                            \"not change ownership of the recycle space user \"\n                            \"directory\", prefix);\n        }\n      }\n    }\n\n    recycle_prefix = prefix;\n    return SFS_OK;\n  } while (true);\n}\n\n\n//------------------------------------------------------------------------------\n// Recycle the given object (file or subtree)\n//------------------------------------------------------------------------------\nint\nRecycleEntry::ToGarbage(const char* epname, XrdOucErrInfo& error)\n{\n  char recycle_path[4096];\n  // If path ends with '/' we recycle a full directory tree aka directory\n  bool is_dir = false;\n  // rewrite the file name /a/b/c as #:#a#:#b#:#c\n  XrdOucString contractedpath = mPath.c_str();\n\n  if (contractedpath.endswith(\"/\")) {\n    is_dir = true;\n    mPath.erase(mPath.length() - 1);\n    // remove the '/' indicating a recursive directory recycling\n    contractedpath.erase(contractedpath.length() - 1);\n  }\n\n  while (contractedpath.replace(\"/\", \"#:#\")) {\n  }\n\n  // For dir's we add a '.d' in the end of the recycle path\n  std::string lPostFix = \"\";\n\n  if (is_dir) {\n    lPostFix = Recycle::gRecyclingPostFix;\n  }\n\n  std::string rpath;\n  int rc = 0;\n\n  // retrieve the current valid index directory\n  if ((rc = GetRecyclePrefix(epname, error, rpath))) {\n    return rc;\n  }\n\n  snprintf(recycle_path, sizeof(recycle_path) - 1, \"%s/%s.%016llx%s\",\n           rpath.c_str(), contractedpath.c_str(), mId, lPostFix.c_str());\n\n  // Finally do the rename\n  if (gOFS->_rename(mPath.c_str(), recycle_path, error, mRootVid,\n                    \"\", \"\", true, true, false, true)) {\n    return gOFS->Emsg(epname, error, EIO, \"rename file/directory\",\n                      recycle_path);\n  }\n\n  // Store the recycle path in the error object\n  error.setErrInfo(0, recycle_path);\n  return SFS_OK;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/recycle/RecycleEntry.hh",
    "content": "//------------------------------------------------------------------------------\n// File: RecycleEntry.hh\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/****************************(********************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"XrdOuc/XrdOucErrInfo.hh\"\n#include <string>\n#include <string_view>\n\n//! Forward declarations\nnamespace eos\n{\nnamespace common\n{\nclass VirtualIdentity;\n}\n}\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class RecycleEntry - modelling an entry that is to be handled by the\n//! recycling functionality\n//------------------------------------------------------------------------------\nclass RecycleEntry\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param path path to recycle\n  //! @param recycle_dir recycle bin directory\n  //! @param rid empty for user based recycling, rid value for projects\n  //! @param owner_uid user id\n  //! @param owner_gid group id\n  //! @param id of the container or file\n  //----------------------------------------------------------------------------\n  RecycleEntry(std::string_view path, std::string_view recycle_dir,\n               std::string_view rid, eos::common::VirtualIdentity* vid,\n               uid_t owner_uid, gid_t owner_gid, unsigned long long id);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~RecycleEntry() = default;\n\n  //----------------------------------------------------------------------------\n  //! Recycle the given object (file or subtree)\n  //!\n  //! @param epname error tag\n  //! @param error object\n  //!\n  //! @return SFS_OK if ok, otherwise SFS_ERR + errno + error object set\n  //----------------------------------------------------------------------------\n  int ToGarbage(const char* epname, XrdOucErrInfo& error);\n\nprivate:\n  static uint32_t sMaxEntriesPerDir;\n  std::string mPath; ///< Path for the entry to recycle\n  std::string mRecycleDir; ///< Path for the top recycle directory\n  std::string mRecycleId; ///< Either uid:<val> or rid:<val> for projects\n  uid_t mOwnerUid; ///< Original uid owner of the entry\n  gid_t mOwnerGid; ///< Origianl gid owner of the entry\n  unsigned long long mId; ///< File or container identifier\n  //! Root virtual identity used internally for unrestricted operations\n  static eos::common::VirtualIdentity mRootVid;\n\n  //----------------------------------------------------------------------------\n  //! Compute recycle path directory for given user and timestamp\n  //!\n  //! epname error printing name\n  //! error error object\n  //! recyclepath computed by this function\n  //!\n  //! SFS_OK if ok, otherwise SFS_ERR + errno + error object set\n  //----------------------------------------------------------------------------\n  int GetRecyclePrefix(const char* epname, XrdOucErrInfo& error,\n                       std::string& recyclepath);\n\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/recycle/RecyclePolicy.cc",
    "content": "//------------------------------------------------------------------------------\n// File: RecyclePolicy.cc\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/****************************(********************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/recycle/RecyclePolicy.hh\"\n#include \"mgm/recycle/Recycle.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"common/StringTokenizer.hh\"\n#include <XrdOuc/XrdOucErrInfo.hh>\n\nEOSMGMNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n// Apply the recycle configuration stored in the configuration engine\n//----------------------------------------------------------------------------\nvoid RecyclePolicy::ApplyConfig(eos::mgm::FsView* fsview)\n{\n  using eos::common::StringTokenizer;\n  std::string config = fsview->GetGlobalConfig(\"recycle\");\n  std::map<std::string, std::string> kv_map;\n  auto pairs = StringTokenizer::split<std::list<std::string>>(config, ' ');\n\n  for (const auto& pair : pairs) {\n    auto kv = StringTokenizer::split<std::vector<std::string>>(pair, '=');\n\n    if (kv.empty()) {\n      eos_err(\"msg=\\\"unknown recycle config data\\\" data=\\\"%s\\\"\", config.c_str());\n      continue;\n    }\n\n    if (kv.size() == 1) {\n      kv.emplace_back(\"\");\n    }\n\n    kv_map.emplace(kv[0], kv[1]);\n  }\n\n  std::string msg;\n\n  for (const auto& [key, value] : kv_map) {\n    if (!Config(key, value, msg)) {\n      eos_err(\"msg=\\\"failed to apply recycle config\\\" key=\\\"%s\\\" value=\\\"%s\\\" \"\n              \"error=\\\"%s\\\"\", key.c_str(), value.c_str(), msg.c_str());\n    }\n  }\n}\n\n//----------------------------------------------------------------------------\n// Store the current running recycle configuration in the config engine\n//----------------------------------------------------------------------------\nbool RecyclePolicy::StoreConfig()\n{\n  std::ostringstream oss;\n  oss << sEnforceKey << \"=\" << (mEnforced.load() ? \"on\" : \"off\") << \" \" //\n      << sEnableKey << \"=\" << (mEnabled.load() ? \"on\" : \"off\") << \" \"   //\n      << sKeepTimeKey << \"=\" << mKeepTimeSec.load() << \" \"              //\n      << sRatioKey << \"=\" << mSpaceKeepRatio.load() << \" \"              //\n      << sCollectKey << \"=\" << mCollectInterval.load().count() << \" \"   //\n      << sRemoveKey << \"=\" << mRemoveInterval.load().count() << \" \"     //\n      << sDryRunKey << \"=\" << (mDryRun.load() ? \"yes\" : \"no\");\n  return FsView::gFsView.SetGlobalConfig(\"recycle\", oss.str());\n}\n\n//----------------------------------------------------------------------------\n// Apply configuration options to the recycle mechanism\n//----------------------------------------------------------------------------\nbool RecyclePolicy::Config(const std::string& key, const std::string& value,\n                           std::string& msg)\n{\n  if (value.empty()) {\n    return true;\n  }\n\n  if (key == sKeepTimeKey) {\n    try {\n      mKeepTimeSec = std::stoull(value);\n    } catch (...) {\n      msg = \"error: recycle keep time conversion to ull failed\";\n      return false;\n    }\n  } else if (key == sRatioKey) {\n    try {\n      mSpaceKeepRatio = std::stod(value);\n    } catch (...) {\n      msg = \"error: recycle keep ratio conversion to double failed\";\n      return false;\n    }\n  } else if (key == sCollectKey) {\n    try {\n      mCollectInterval = std::chrono::seconds(std::stoull(value));\n    } catch (...) {\n      msg = \"error: recycle collect interval conversion failed\";\n      return false;\n    }\n  } else if (key == sRemoveKey) {\n    try {\n      mRemoveInterval = std::chrono::seconds(std::stoull(value));\n    } catch (...) {\n      msg = \"error: recycle remove interval conversion failed\";\n      return false;\n    }\n  } else if (key == sDryRunKey) {\n    mDryRun = (value == \"yes\");\n  } else if (key == sEnforceKey) {\n    if (value == \"on\") {\n      mEnforced = true;\n    } else if (value == \"off\") {\n      mEnforced = false;\n    } else {\n      msg = \"error: unknown value for recycle-enforce - expected on|off\";\n      return false;\n    }\n  } else if (key == sEnableKey) {\n    if (value == \"on\") {\n      mEnabled = true;\n    } else if (value == \"off\") {\n      mEnabled = false;\n    } else {\n      msg = \"error: unknown value for recycle-enable - expected on|off\";\n      return false;\n    }\n  } else {\n    // Ignore unknown keys\n    return true;\n  }\n\n  eos_static_info(\"msg=\\\"recycle config updated\\\" %s\", Dump(\" \").c_str());\n  return StoreConfig();\n}\n\n//----------------------------------------------------------------------------\n// Dump current active recycle policy\n//----------------------------------------------------------------------------\nstd::string\nRecyclePolicy::Dump(const std::string& delim) const\n{\n  std::ostringstream oss;\n  oss << \"enforced=\" << (mEnforced.load() ? \"on\" : \"off\") << delim\n      << \"enabled=\" << (mEnabled.load() ? \"on\" : \"off\") << delim\n      << \"dry_run=\" << (mDryRun.load() ? \"yes\" : \"no\") << delim\n      << \"keep_time_sec=\" << mKeepTimeSec.load() << delim\n      << \"space_keep_ratio=\" << mSpaceKeepRatio.load() << delim\n      << \"low_space_watermark=\" << mLowSpaceWatermark.load() << delim\n      << \"low_inode_watermark=\" << mLowInodeWatermark.load() << delim\n      << \"collect_interval_sec=\" << mCollectInterval.load().count() << delim\n      << \"remove_interval_sec=\" << mRemoveInterval.load().count() << delim;\n  return oss.str();\n}\n\n//----------------------------------------------------------------------------\n// Get quota statistics for the recycle bin\n//----------------------------------------------------------------------------\nstd::map<int, unsigned long long>\nRecyclePolicy::GetQuotaStats()\n{\n  return Quota::GetGroupStatistics(Recycle::gRecyclingPrefix,\n                                   Quota::gProjectId);\n}\n\n//------------------------------------------------------------------------------\n// Refresh watermark values based on the configured quota\n//------------------------------------------------------------------------------\nvoid\nRecyclePolicy::RefreshWatermarks()\n{\n  auto map_quotas = GetQuotaStats();\n\n  if (!map_quotas.empty()) {\n    unsigned long long usedbytes = map_quotas[SpaceQuota::kGroupLogicalBytesIs];\n    unsigned long long maxbytes = map_quotas[SpaceQuota::kGroupLogicalBytesTarget];\n    unsigned long long usedfiles = map_quotas[SpaceQuota::kGroupFilesIs];\n    unsigned long long maxfiles = map_quotas[SpaceQuota::kGroupFilesTarget];\n\n    if ((mSpaceKeepRatio.load() > (1.0 * usedbytes / (maxbytes ? maxbytes :\n                                   999999999))) &&\n        (mSpaceKeepRatio.load() > (1.0 * usedfiles / (maxfiles ? maxfiles :\n                                   999999999)))) {\n      eos_static_debug(\"msg=\\\"skip recycle watermark update - ratio still low\\\" \"\n                       \"space-ratio=%.02f inode-ratio=%.02f ratio=%.02f\",\n                       1.0 * usedbytes / (maxbytes ? maxbytes : 999999999),\n                       1.0 * usedfiles / (maxfiles ? maxfiles : 999999999),\n                       mSpaceKeepRatio.load());\n      return;\n    } else {\n      // Make local copy to avoid modifying the original space ratio\n      double space_ratio = mSpaceKeepRatio.load();\n\n      if (space_ratio - 0.1 > 0) {\n        space_ratio -= 0.1;\n      }\n\n      mLowInodeWatermark = (maxfiles * space_ratio);\n      mLowSpaceWatermark = (maxbytes * space_ratio);\n      eos_static_info(\"msg=\\\"cleaning by ratio policy\\\" low-inodes-mark=%lld \"\n                      \"low-space-mark=%lld ratio=%.02f\", mLowInodeWatermark.load(),\n                      mLowSpaceWatermark.load(), mSpaceKeepRatio.load());\n    }\n  } else {\n    mLowInodeWatermark = 0ull;\n    mLowSpaceWatermark = 0ull;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check based on the quota information if we are within the watermark\n//------------------------------------------------------------------------------\nbool\nRecyclePolicy::IsWithinLimits()\n{\n  if (mSpaceKeepRatio.load()) {\n    auto map_quotas = GetQuotaStats();\n\n    if (!map_quotas.empty()) {\n      unsigned long long usedbytes = map_quotas[SpaceQuota::kGroupLogicalBytesIs];\n      unsigned long long usedfiles = map_quotas[SpaceQuota::kGroupFilesIs];\n      eos_static_debug(\"volume=%lld volume_low_wm=%lld \"\n                       \"inodes=%lld inodes_low_wm=%lld\",\n                       usedbytes, mLowSpaceWatermark.load(),\n                       usedfiles, mLowInodeWatermark.load());\n\n      if ((mLowInodeWatermark.load() && (mLowInodeWatermark.load() > usedfiles)) ||\n          (mLowSpaceWatermark.load() && (mLowSpaceWatermark.load() > usedbytes))) {\n        return true;\n      }\n    }\n  }\n\n  eos_static_debug(\"%s\", \"msg=\\\"do cleanup, space ratio not configured or \"\n                   \"above watermark limits\\\"\");\n  return false;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/recycle/RecyclePolicy.hh",
    "content": "//------------------------------------------------------------------------------\n// File: RecyclePolicy.hh\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/****************************(********************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Mapping.hh\"\n#include \"namespace/interface/IView.hh\"\n#include <atomic>\n\nEOSMGMNAMESPACE_BEGIN\n\n//! Forward declarations\nclass FsView;\n\n//------------------------------------------------------------------------------\n//! Class RecyclePolicy\n//------------------------------------------------------------------------------\nclass RecyclePolicy: public eos::common::LogId\n{\npublic:\n  friend class Recycle;\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  RecyclePolicy() = default;\n\n  //----------------------------------------------------------------------------\n  //! Apply the recycle configuration stored in the configuration engine\n  //!\n  //! @param fsview file system view info\n  //----------------------------------------------------------------------------\n  void ApplyConfig(eos::mgm::FsView* fsview);\n\n  //----------------------------------------------------------------------------\n  //! Store the current running recycle configuration in the config engine\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool StoreConfig();\n\n  //----------------------------------------------------------------------------\n  //! Apply configuration options to the recycle mechanism\n  //!\n  //! @param key configuration key\n  //! @param value configuration value\n  //! @param msg output message/error\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool Config(const std::string& key, const std::string& value, std::string& msg);\n\n  //----------------------------------------------------------------------------\n  //! Refresh watermark values based on the configured quota\n  //----------------------------------------------------------------------------\n  void RefreshWatermarks();\n\n  //----------------------------------------------------------------------------\n  //! Check based on the quota information if we are within the watermark\n  //! limits. If no space keep ratio set then we consider this condition false\n  //! so that time based cleanup can still continue.\n  //!\n  //! @return true if within watermark limits, false otherwise\n  //----------------------------------------------------------------------------\n  bool IsWithinLimits();\n\n  static constexpr auto sKeepTimeKey = \"recycle-keep-time\";\n  static constexpr auto sRatioKey = \"recycle-ratio\";\n  static constexpr auto sCollectKey = \"recycle-collect-time\";\n  static constexpr auto sRemoveKey = \"recycle-remove-time\";\n  static constexpr auto sDryRunKey = \"recycle-dry-run\";\n  static constexpr auto sEnforceKey = \"recycle-enforce\";\n  static constexpr auto sEnableKey = \"recycle-enable\";\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  //! Mark that recycle is enabled\n  std::atomic<bool> mEnabled {true};\n  //! Mark if recycle is enforced instance wide\n  std::atomic<bool> mEnforced {false};\n  std::atomic<uint64_t> mKeepTimeSec {0};\n  std::atomic<double> mSpaceKeepRatio {0.0};\n  //! Flag if we are in dry-run mode or not\n  std::atomic<bool> mDryRun {false};\n  //! How often the collection of entries is happening, default 1 day\n  std::atomic<std::chrono::seconds> mCollectInterval =\n    std::chrono::seconds(24 * 3600);\n  //! How often the removal of entries is happening, default 1 hour\n  std::atomic<std::chrono::seconds> mRemoveInterval =\n    std::chrono::seconds(3600);\n\n  std::atomic<unsigned long long> mLowSpaceWatermark {0ull};\n  std::atomic<unsigned long long> mLowInodeWatermark {0ull};\n\n  //----------------------------------------------------------------------------\n  //! Get quota statistics for the recycle bin\n  //!\n  //! return map storing the quota information or empty if not quota\n  //----------------------------------------------------------------------------\n  virtual std::map<int, unsigned long long> GetQuotaStats();\n\n  //----------------------------------------------------------------------------\n  //! Dump current active recycle policy\n  //!\n  //! @param delim delimiter between output entries\n  //----------------------------------------------------------------------------\n  std::string Dump(const std::string& delim = \"\\n\") const;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/routeendpoint/RouteEndpoint.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file RouteEndpoint.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/routeendpoint/RouteEndpoint.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/ParseUtils.hh\"\n#include <XrdCl/XrdClURL.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n#include <sstream>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Move assignment operator\n//------------------------------------------------------------------------------\nRouteEndpoint&\nRouteEndpoint::operator =(RouteEndpoint&& other) noexcept\n{\n  if (this != &other) {\n    std::swap(mFqdn, other.mFqdn);\n    mXrdPort = other.mXrdPort;\n    mHttpPort = other.mHttpPort;\n    mIsOnline.store(other.mIsOnline.load());\n    mIsMaster.store(other.mIsMaster.load());\n  }\n\n  return *this;\n}\n\n//------------------------------------------------------------------------------\n// Parse route endpoint specification from string\n//------------------------------------------------------------------------------\nbool\nRouteEndpoint::ParseFromString(const std::string& input)\n{\n  std::vector<std::string> tokens;\n  eos::common::StringConversion::Tokenize(input, tokens, \":\");\n\n  if (tokens.size() != 3) {\n    return false;\n  }\n\n  mFqdn = tokens[0];\n\n  try {\n    mXrdPort = std::stoul(tokens[1]);\n    mHttpPort = std::stoul(tokens[2]);\n  } catch (const std::exception& e) {\n    return false;\n  }\n\n  return eos::common::ValidHostnameOrIP(mFqdn);\n}\n\n//------------------------------------------------------------------------------\n// Get string representation of the endpoint\n//------------------------------------------------------------------------------\nstd::string\nRouteEndpoint::ToString() const\n{\n  std::ostringstream oss;\n  oss << mFqdn << \":\" << mXrdPort << \":\" << mHttpPort;\n  return oss.str();\n}\n\n//------------------------------------------------------------------------------\n// Update master status\n//------------------------------------------------------------------------------\nvoid\nRouteEndpoint::UpdateStatus()\n{\n  std::ostringstream oss;\n  oss << \"root://\" << mFqdn << \":\" << mXrdPort << \"//dummy?xrd.wantprot=sss,unix\";\n  XrdCl::URL url(oss.str());\n\n  if (!url.IsValid()) {\n    mIsOnline = false;\n    mIsMaster = false;\n    eos_static_crit(\"invalid url host='%s'\", mFqdn.c_str());\n    return;\n  }\n\n  // Check if node is online\n  XrdCl::FileSystem fs(url);\n  XrdCl::XRootDStatus st = fs.Ping(1);\n\n  if (!st.IsOK()) {\n    mIsOnline = false;\n    mIsMaster = false;\n    eos_static_debug(\"failed to ping host='%s'\", mFqdn.c_str());\n    return;\n  }\n\n  mIsOnline.store(true);\n  /* TODO: review if we want to have this policy by hostname or not ... currently disabled\n  // If the host names is not starting with eos, we assume that is just a plain XrootD service\n  if (mFqdn.substr(0,3) != \"eos\") {\n    mIsMaster = true;\n    eos_static_debug(\"disabling EOS master check host='%s' - assuming standard \"\n                     \"XRootD service (hostname does not start with eos...)\",\n                     mFqdn.c_str());\n    return ;\n  }\n  */\n  // Check if node is master\n  XrdCl::Buffer* response {nullptr};\n  XrdCl::Buffer request;\n  request.FromString(\"/?mgm.pcmd=is_master\");\n\n  if (!fs.Query(XrdCl::QueryCode::OpaqueFile, request, response).IsOK()) {\n    eos_static_debug(\"host='%s' is running as 'master'\", mFqdn.c_str());\n    mIsMaster = false;\n  } else {\n    eos_static_debug(\"host='%s' is NOT running as 'master'\", mFqdn.c_str());\n    mIsMaster = true;\n  }\n\n  delete response;\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/routeendpoint/RouteEndpoint.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file RouteEndpoint.hh\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/Logging.hh\"\n#include <string>\n#include <stdint.h>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class RouteEndpoint\n//------------------------------------------------------------------------------\nclass RouteEndpoint: public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  RouteEndpoint():\n    mIsOnline(false), mIsMaster(false), mXrdPort(0), mHttpPort(0)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Constructor with parameters\n  //!\n  //! @param host redirection host\n  //! @param xrd_port xrootd redirection port\n  //! @param http_port http redirection port\n  //----------------------------------------------------------------------------\n  RouteEndpoint(const std::string& fqdn, uint32_t xrd_port, uint32_t http_port):\n    mIsOnline(false), mIsMaster(false), mFqdn(fqdn), mXrdPort(xrd_port),\n    mHttpPort(http_port)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~RouteEndpoint() = default;\n\n  //----------------------------------------------------------------------------\n  //! Move assignment operator\n  //----------------------------------------------------------------------------\n  RouteEndpoint& operator =(RouteEndpoint&& other) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Move constructor\n  //----------------------------------------------------------------------------\n  RouteEndpoint(RouteEndpoint&& other) noexcept\n  {\n    *this = std::move(other);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Parse route endpoint specification from string\n  //!\n  //! @param string route endpoint specification in the form of:\n  //!        <host_fqdn>:<xrd_port>:<http_port>\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  bool ParseFromString(const std::string& input);\n\n  //----------------------------------------------------------------------------\n  //! Get string representation in the form of:\n  //! <host_fqdn>:<xrd_port>:<http_port>\n  //----------------------------------------------------------------------------\n  std::string ToString() const;\n\n  //----------------------------------------------------------------------------\n  //! Get redirection host\n  //----------------------------------------------------------------------------\n  inline std::string GetHostname() const\n  {\n    return mFqdn;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get Xrd redirection port\n  //----------------------------------------------------------------------------\n  inline int GetXrdPort() const\n  {\n    return mXrdPort;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get http redirection port\n  //----------------------------------------------------------------------------\n  inline int GetHttpPort() const\n  {\n    return mHttpPort;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Update status - both the online and master status\n  //------------------------------------------------------------------------------\n  void UpdateStatus();\n\n  //----------------------------------------------------------------------------\n  //! Operator ==\n  //----------------------------------------------------------------------------\n  bool operator ==(const RouteEndpoint& rhs) const\n  {\n    return ((mFqdn == rhs.mFqdn) &&\n            (mXrdPort == rhs.mXrdPort) &&\n            (mHttpPort == rhs.mHttpPort));\n  }\n\n  //----------------------------------------------------------------------------\n  //! Operator !=\n  //----------------------------------------------------------------------------\n  bool operator !=(const RouteEndpoint& rhs) const\n  {\n    return !(*this == rhs);\n  }\n\n  std::atomic<bool> mIsOnline; ///< Mark if node is online\n  std::atomic<bool> mIsMaster; ///< Mark if node is master\n\nprivate:\n  std::string mFqdn; ///< Redirection host fqdn\n  uint32_t mXrdPort; ///< Redirection xrootd port\n  uint32_t mHttpPort; ///< Redirection http port\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/scheduler/Scheduler.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Scheduler.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/scheduler/Scheduler.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"mgm/geotreeengine/GeoTreeEngine.hh\"\n#include \"mgm/placement/FsScheduler.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n\nXrdSysMutex Scheduler::pMapMutex;\nstd::map<std::string, FsGroup*> Scheduler::schedulingGroup;\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nScheduler::Scheduler() { }\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nScheduler::~Scheduler() { }\n\n//------------------------------------------------------------------------------\n// Write placement routine - the caller routine has to lock via =>\n// eos::common::RWMutexReadLock(FsView::gFsView.ViewMutex)\n//------------------------------------------------------------------------------\nuint8_t Scheduler::getRequiredReplicas(unsigned long lid)\n{\n  uint64_t n_replicas = eos::common::LayoutId::GetStripeNumber(lid) + 1;\n  return static_cast<uint8_t>(n_replicas);\n}\n\n\nint Scheduler::FlatSchedulerFilePlacement(PlacementArguments *args)\n{\n  auto strategy = gOFS->mFsScheduler->getPlacementStrategy(*args->spacename);\n  if (args->sched_strategy_cstr != nullptr) {\n    strategy = placement::strategy_from_str(args->sched_strategy_cstr);\n  }\n  if (strategy == placement::PlacementStrategyT::kGeoScheduler) {\n    return EINVAL;\n  }\n\n  uint8_t n_replicas = getRequiredReplicas(args->lid);\n  placement::PlacementArguments plct_args{n_replicas, placement::ConfigStatus::kRW, strategy};\n\n  plct_args.excludefs.assign(args->exclude_filesystems->begin(),\n                             args->exclude_filesystems->end());\n  plct_args.forced_group_index = args->forced_scheduling_group_index;\n\n  auto ret = gOFS->mFsScheduler->schedule(*args->spacename, plct_args);\n  if (!ret) {\n    eos_static_err(\"unable to place files with FlatScheduler err=%s\",\n                   ret.error_string().c_str());\n    return ENOSPC;\n  }\n\n  args->selected_filesystems->assign(ret.ids.begin(), ret.ids.begin()+n_replicas);\n  return 0;\n}\n\n\nint\nScheduler::FilePlacement(PlacementArguments* args)\n{\n  if (!FlatSchedulerFilePlacement(args)) {\n    return 0;\n  }\n\n  eos_static_debug(\"requesting file placement from geolocation %s\",\n                   args->vid->geolocation.c_str());\n  // The caller routine has to lock via =>\n  //  eos::common::RWMutexReadLock(FsView::gFsView.ViewMutex)\n  std::map<eos::common::FileSystem::fsid_t, float> availablefs;\n  std::map<eos::common::FileSystem::fsid_t, std::string> availablefsgeolocation;\n  std::list<eos::common::FileSystem::fsid_t> availablevector;\n  // fill the avoid list from the selected_filesystems input vector\n  unsigned int nfilesystems = eos::common::LayoutId::GetStripeNumber(\n                                args->lid) + 1;\n  unsigned int ncollocatedfs = 0;\n\n  switch (args->plctpolicy) {\n  case kScattered:\n    if (!(args->vid->geolocation.empty())) {\n      ncollocatedfs = 1;\n    } else {\n      ncollocatedfs = 0;\n    }\n\n    break;\n\n  case kHybrid:\n    switch (eos::common::LayoutId::GetLayoutType(args->lid)) {\n    case eos::common::LayoutId::kPlain:\n      ncollocatedfs = 1;\n      break;\n\n    case eos::common::LayoutId::kReplica:\n      ncollocatedfs = nfilesystems - 1;\n      break;\n\n    default:\n      ncollocatedfs = nfilesystems - eos::common::LayoutId::GetRedundancyStripeNumber(\n                        args->lid);\n      break;\n    }\n\n    break;\n\n  // we only do geolocations for replica layouts\n  case kGathered:\n    ncollocatedfs = nfilesystems;\n  }\n\n  eos_static_debug(\"checking placement policy : policy is %d, nfilesystems is\"\n                   \" %d and ncollocated is %d\", (int)args->plctpolicy, (int)nfilesystems,\n                   (int)ncollocatedfs);\n  uid_t uid = args->vid->uid;\n  gid_t gid = args->vid->gid;\n  XrdOucString lindextag = \"\";\n\n  if (args->grouptag) {\n    lindextag = args->grouptag;\n  } else {\n    lindextag += (int) uid;\n    lindextag += \":\";\n    lindextag += (int) gid;\n  }\n\n  std::string indextag = lindextag.c_str();\n  std::set<FsGroup*>::const_iterator git;\n  std::vector<std::string> fsidsgeotags;\n  std::vector<FsGroup*> groupsToTry;\n\n  // place the group iterator\n  if (!args->alreadyused_filesystems->empty()) {\n    if (!gOFS->mGeoTreeEngine->getInfosFromFsIds(*args->alreadyused_filesystems,\n        &fsidsgeotags,\n        0, &groupsToTry)) {\n      eos_static_debug(\"could not retrieve scheduling group for all avoid fsids\");\n    } else {\n      eos_static_debug(\"succesfully retrieved scheduling groups for all avoid fsids\");\n    }\n  }\n\n  if (args->forced_scheduling_group_index >= 0) {\n    eos_static_debug(\"searching for forced scheduling group=%i\",\n                     args->forced_scheduling_group_index);\n\n    for (git = FsView::gFsView.mSpaceGroupView[*args->spacename].begin();\n         git != FsView::gFsView.mSpaceGroupView[*args->spacename].end(); ++git) {\n      if ((*git)->GetIndex() == (unsigned int) args->forced_scheduling_group_index) {\n        break;\n      }\n    }\n\n    if ((git != FsView::gFsView.mSpaceGroupView[*args->spacename].end()) &&\n        ((*git)->GetIndex() != (unsigned int) args->forced_scheduling_group_index)) {\n      args->selected_filesystems->clear();\n      return ENOSPC;\n    }\n\n    if (git == FsView::gFsView.mSpaceGroupView[*args->spacename].end()) {\n      args->selected_filesystems->clear();\n      return ENOSPC;\n    }\n\n    eos_static_debug(\"forced scheduling group index %d\",\n                     args->forced_scheduling_group_index);\n  } else {\n    XrdSysMutexHelper scope_lock(pMapMutex);\n\n    if (schedulingGroup.count(indextag)) {\n      git = FsView::gFsView.mSpaceGroupView[*args->spacename].find(\n              schedulingGroup[indextag]);\n      schedulingGroup[indextag] = *git;\n    } else {\n      git = FsView::gFsView.mSpaceGroupView[*args->spacename].begin();\n      schedulingGroup[indextag] = *git;\n    }\n\n    if (git ==  FsView::gFsView.mSpaceGroupView[*args->spacename].end()) {\n      git = FsView::gFsView.mSpaceGroupView[*args->spacename].begin();\n    }\n  }\n\n  // Rotate scheduling view ptr,updating schedulingGroup map\n  // if groupsToTry is not empty we try to first use the same scheduling groups of the already used filesystems\n  for (unsigned int groupindex = 0;\n       groupindex < FsView::gFsView.mSpaceGroupView[*args->spacename].size() +\n       groupsToTry.size(); groupindex++) {\n    FsGroup* group = nullptr;\n\n    // Try first the forced scheduling group and fail if we cannot schedule there\n    if (args->forced_scheduling_group_index >= 0) {\n      group = *git;\n    } else {\n      // Rotate scheduling view ptr -  we select a random one\n      group = (groupindex < groupsToTry.size() ? groupsToTry[groupindex] :\n               *git);\n    }\n\n    eos_static_debug(\"Trying GeoTree Placement on group: %s, total groups: %d, groupsToTry: %d \",\n                     group->mName.c_str(), FsView::gFsView.mSpaceGroupView[*args->spacename].size(),\n                     groupsToTry.size());\n    bool placeRes = gOFS->mGeoTreeEngine->placeNewReplicasOneGroup(\n                      group, nfilesystems,\n                      args->selected_filesystems,\n                      args->inode,\n                      args->dataproxys,\n                      args->firewallentpts,\n                      GeoTreeEngine::regularRW,\n                      // file systems to avoid are assumed to already host a replica\n                      args->alreadyused_filesystems,\n                      &fsidsgeotags,\n                      args->bookingsize,\n                      args->plctTrgGeotag ? *args->plctTrgGeotag : \"\",\n                      args->vid->geolocation,\n                      ncollocatedfs,\n                      args->exclude_filesystems,\n                      NULL);\n    eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n\n    if (g_logging.gLogMask & LOG_MASK(LOG_DEBUG)) {\n      char buffer[1024];\n      buffer[0] = 0;\n      char* buf = buffer;\n\n      for (auto it = args->selected_filesystems->begin();\n           it != args->selected_filesystems->end(); ++it) {\n        buf += sprintf(buf, \"%lu  \", (unsigned long)(*it));\n      }\n\n      eos_static_debug(\"GeoTree Placement returned %d with fs id's -> %s\",\n                       (int)placeRes, buffer);\n    }\n\n    if (placeRes) {\n      eos_static_debug(\"placing replicas for %s in subgroup %s\", args->path,\n                       group->mName.c_str());\n    } else {\n      if (args->forced_scheduling_group_index >= 0) {\n        eos_static_debug(\"msg=\\\"could not place all replica(s) for %s in the \"\n                         \"forced subgroup %s\\\"\", args->path, group->mName.c_str());\n        args->selected_filesystems->clear();\n        return ENOSPC;\n      } else {\n        eos_static_debug(\"msg=\\\"could not place all replica(s) for %s in subgroup %s, \"\n                         \"checking next group\\\"\", args->path, group->mName.c_str());\n      }\n    }\n\n    if (groupindex >= groupsToTry.size()) {\n      if ((git == FsView::gFsView.mSpaceGroupView[*args->spacename].end()) ||\n          (++git == FsView::gFsView.mSpaceGroupView[*args->spacename].end())) {\n        git = FsView::gFsView.mSpaceGroupView[*args->spacename].begin();\n      }\n\n      // remember the last group for that indextag\n      pMapMutex.Lock();\n      schedulingGroup[indextag] = *git;\n      pMapMutex.UnLock();\n    }\n\n    if (placeRes) {\n      return 0;\n    } else {\n      continue;\n    }\n  }\n\n  // Check if we are in any kind of no-update mode\n  args->selected_filesystems->clear();\n  return ENOSPC;\n}\n\nstatic GeoTreeEngine::SchedType toGeoTreeSchedtype(Scheduler::tSchedType in_type, bool isRW) {\n  GeoTreeEngine::SchedType out_type = GeoTreeEngine::regularRO;\n  if (in_type == Scheduler::tSchedType::regular) {\n    out_type = isRW ? GeoTreeEngine::regularRW : GeoTreeEngine::regularRO;\n  } else if (in_type == Scheduler::tSchedType::draining) {\n    out_type = GeoTreeEngine::draining;\n  }\n  return out_type;\n}\n\nint Scheduler::FlatSchedulerFileAccess(AccessArguments *args) {\n  std::string spaceName(args->forcedspace);\n  auto strategy = gOFS->mFsScheduler->getPlacementStrategy(spaceName);\n  if (strategy == placement::PlacementStrategyT::kGeoScheduler) {\n    return EINVAL;\n  }\n\n  placement::AccessArguments access_args {\n    *args->fsindex,\n    args->inode,\n    strategy,\n    args->vid->geolocation,\n    args->unavailfs,\n    *args->locationsfs\n  };\n\n  return gOFS->mFsScheduler->access(spaceName, access_args);\n}\n\n//------------------------------------------------------------------------------\n// File access method\n//------------------------------------------------------------------------------\nint Scheduler::FileAccess(AccessArguments* args)\n{\n  size_t nReqStripes = (args->isRW ?\n                        eos::common::LayoutId::GetOnlineStripeNumber(args->lid) :\n                        eos::common::LayoutId::GetMinOnlineReplica(args->lid));\n  // pre-checks before deciding the scheduler\n\n  if (nReqStripes > args->locationsfs->size()) {\n    eos_static_debug(\"not enough filesystems available for access: \"\n                     \"required=%zu, available=%zu\", nReqStripes,\n                     args->locationsfs->size());\n    return EROFS;\n  }\n\n  if (args->forcedfsid > 0 &&\n      std::find(args->locationsfs->begin(),\n                args->locationsfs->end(),\n                args->forcedfsid) == args->locationsfs->end()) {\n    eos_static_debug(\"forced filesystem %lu not in available locations\",\n                     (unsigned long)args->forcedfsid);\n    return ENODATA;\n  }\n\n  if (!FlatSchedulerFileAccess(args)) {\n    eos_static_debug(\"msg=\\\"successfully accessed file via FlatScheduler\\\" index=%zu\",\n                     *args->fsindex);\n    return 0;\n  } else {\n    eos_static_info(\"%s\", \"msg=\\\"Failed access via FlatScheduler, falling back to geotree\\\"\");\n  }\n\n  eos_static_debug(\"requesting file access from geolocation %s\",\n                   args->vid->geolocation.c_str());\n  GeoTreeEngine::SchedType st = toGeoTreeSchedtype(args->schedtype, args->isRW);\n\n\n  // make sure we have the matching geo location before the not matching one\n  if (!args->tried_cgi->empty()) {\n    std::vector<std::string> hosts;\n\n    if (!gOFS->mGeoTreeEngine->getInfosFromFsIds(*args->locationsfs, 0,\n        &hosts, 0)) {\n      eos_static_debug(\"could not retrieve host for all the avoided fsids\");\n    }\n\n    size_t idx = 0;\n\n    // we store unavailable filesystems in the unavail vector\n    for (auto it = hosts.begin(); it != hosts.end(); it++) {\n      if ((!it->empty()) && args->tried_cgi->find((*it) + \",\") != std::string::npos) {\n        // - this matters for RAID layouts because we have to remove there URLs\n        // to let the RAID driver use only online stripes\n        args->unavailfs->push_back((*args->locationsfs)[idx]);\n      }\n\n      idx++;\n    }\n  }\n\n  return gOFS->mGeoTreeEngine->accessHeadReplicaMultipleGroup(nReqStripes,\n         *args->fsindex,\n         *args->locationsfs,\n         args->inode,\n         args->dataproxys,\n         args->firewallentpts,\n         st,\n         args->vid->geolocation,\n         args->forcedfsid, args->unavailfs);\n}\n\nvoid Scheduler::ReshuffleFs(std::vector<unsigned int> &selectedfs)\n{\n  if (selectedfs.size() > 0) {\n    std::vector<unsigned int> newselectedfs;\n    auto result = std::minmax_element(selectedfs.begin(), selectedfs.end());\n    int sum = std::accumulate(selectedfs.begin(), selectedfs.end(), 0);\n\n    if ((sum % 2) == 0) {\n      newselectedfs.push_back(*result.second);\n    } else {\n      newselectedfs.push_back(*result.first);\n    }\n\n    for (const auto& i : selectedfs) {\n      if (i != newselectedfs.front()) {\n        newselectedfs.push_back(i);\n      }\n    }\n\n    selectedfs.swap(newselectedfs);\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/scheduler/Scheduler.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Scheduler.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_SCHEDULER__HH__\n#define __EOSMGM_SCHEDULER__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"common/Logging.hh\"\n#include \"common/FileSystem.hh\"\n#include \"common/LayoutId.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"mgm/fsview/FsView.hh\"\n/*----------------------------------------------------------------------------*/\n/*----------------------------------------------------------------------------*/\n\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class implementing file scheduling e.g. access and placement\n//------------------------------------------------------------------------------\nclass Scheduler\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  Scheduler();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~Scheduler();\n\n  //! Types of placement policy\n  enum tPlctPolicy\n  { kScattered, kHybrid, kGathered };\n\n  //! Types of scheduling\n  enum tSchedType\n  { regular, draining };\n\n  //! Arguments to place a file placement\n  struct PlacementArguments {\n    /// INPUT\n    //! space name\n    const std::string* spacename;\n    //! file path\n    const char* path;\n    //! group tag for placement\n    const char* grouptag;\n    //! layout to be placed\n    unsigned long lid;\n    //! file inode\n    ino64_t inode;\n    //! indicates if placement should be local/spread/hybrid\n    tPlctPolicy plctpolicy;\n    //! indicates close to which Geotag collocated stripes should be placed\n    const std::string* plctTrgGeotag;\n    //! indicates placement with truncation\n    bool truncate;\n    //! forced index for the scheduling group to be used\n    int forced_scheduling_group_index;\n    //! size to book for the placement\n    unsigned long long bookingsize;\n    //! indicate if this is a request for regular, draining or balancing placement\n    tSchedType schedtype;\n    //! scheduling strategy to be used (if not set, use space default)\n    //! scheduling strategy if set from opaque string\n    const char* sched_strategy_cstr;\n    //! virtual identity of the client\n    const eos::common::VirtualIdentity* vid;\n    /// INPUT/OUTPUT\n    //! filesystems to avoid\n    std::vector<unsigned int>* alreadyused_filesystems;\n    //! selected_filesystems filesystems selected by the scheduler\n    std::vector<unsigned int>* selected_filesystems;\n    //! file systems not to be used by the scheduler\n    std::vector<unsigned int>* exclude_filesystems;\n    //! if non NULL, schedule dataproxys for each fs if proxygroups are defined (empty string if not defined)\n    std::vector<std::string>* dataproxys;\n    //! if non NULL, schedule a firewall entry point for each fs\n    std::vector<std::string>* firewallentpts;\n\n    PlacementArguments() :\n      spacename(0),\n      path(0),\n      grouptag(0),\n      lid(0),\n      inode(0),\n      plctpolicy(kScattered),\n      plctTrgGeotag(),\n      truncate(false),\n      forced_scheduling_group_index(-1),\n      bookingsize(1024 * 1024 * 1024ll),\n      schedtype(regular),\n      sched_strategy_cstr(nullptr),\n      vid(0),\n      alreadyused_filesystems(0),\n      selected_filesystems(0),\n      exclude_filesystems(0),\n      dataproxys(0),\n      firewallentpts(0)\n    {}\n\n    bool isValid() const\n    {\n      return\n        spacename && spacename->size()\n        && path\n        && lid\n        && isValidReplicas(lid)\n        && vid\n        && alreadyused_filesystems\n        && exclude_filesystems\n        && selected_filesystems;\n    }\n\n    bool isValidReplicas(unsigned long lid) const {\n      return common::LayoutId::GetStripeNumber(lid) + 1 < std::numeric_limits<uint8_t>::max();\n    }\n\n    // Strong Types to avoid misplaced function calls\n    struct Path { const char* value; };\n    struct GroupTag { const char* value; };\n    struct Lid { unsigned long value; }; // LayoutId would conflict with eos::common\n    struct BookingSize { unsigned long long value; };\n\n    PlacementArguments& setFileParams(const std::string& p_space,\n                                      Path p_path,\n                                      GroupTag p_grouptag,\n                                      Lid p_lid,\n                                      ino64_t p_inode,\n                                      BookingSize p_bookingsize,\n                                      bool p_truncate,\n                                      const common::VirtualIdentity& p_vid) {\n      spacename = &p_space;\n      path = p_path.value;\n      grouptag = p_grouptag.value;\n      lid = p_lid.value;\n      inode = p_inode;\n      vid = &p_vid;\n      bookingsize = p_bookingsize.value;\n      truncate = p_truncate;\n      return *this;\n    }\n\n    PlacementArguments& setFsParams(std::vector<unsigned int>* p_alreadyused_filesystems,\n                                    std::vector<unsigned int>* p_exclude_filesystems,\n                                    std::vector<unsigned int>* p_selected_filesystems) {\n      alreadyused_filesystems = p_alreadyused_filesystems;\n      exclude_filesystems = p_exclude_filesystems;\n      selected_filesystems = p_selected_filesystems;\n      return *this;\n    }\n\n    PlacementArguments& setPlctParams(tPlctPolicy p_plctpolicy,\n                                     const std::string* p_plctTrgGeotag,\n                                     int p_forced_group_index,\n                                     const char* p_sched_strategy_cstr) {\n      plctpolicy = p_plctpolicy;\n      plctTrgGeotag = p_plctTrgGeotag;\n      forced_scheduling_group_index = p_forced_group_index;\n      sched_strategy_cstr = p_sched_strategy_cstr;\n      return *this;\n    }\n\n  };\n\n  //----------------------------------------------------------------------------\n  //! Take the decision where to place a new file in the system.\n  //!\n  //! @param args the structure holding all the input and output arguments\n  //!\n  //! @return 0 if placement successful, otherwise a non-zero value\n  //!         ENOSPC - no space quota defined for current space\n  //!\n  //! NOTE: Has to be called with a lock on the FsView::gFsView::ViewMutex\n  //----------------------------------------------------------------------------\n  static int FilePlacement(PlacementArguments* args);\n\n  static uint8_t getRequiredReplicas(unsigned long lid);\n\n  static int FlatSchedulerFilePlacement(PlacementArguments* args);\n\n  struct AccessArguments {\n    /// INPUT\n    //! forced filesystem for access\n    unsigned long forcedfsid;\n    //! forced space for access\n    const char* forcedspace;\n    //! cgi containing already tried hosts\n    const std::string* tried_cgi;\n    //! layout of the file\n    unsigned long lid;\n    //! file inode\n    ino64_t inode;\n    //! indicate pure read or rd/wr access\n    bool isRW;\n    //! size to book additionally for rd/wr access\n    unsigned long long bookingsize;\n    //! indicate if this is a request for regular, draining or balancing access\n    tSchedType schedtype;\n    //! virtual identity of the client\n    const eos::common::VirtualIdentity* vid;\n    //!filesystem ids where layout is stored\n    const std::vector<unsigned int>* locationsfs;\n    /// INPUT/OUTPUT\n    //! if non NULL, schedule dataproxys for each fs if proxygroups are defined (empty string if not defined)\n    std::vector<std::string>* dataproxys;\n    //! firewallentpts if non NULL, schedule a firewall entry point for each fs\n    std::vector<std::string>* firewallentpts;\n    //! return index pointing to layout entry filesystem\n    unsigned long* fsindex;\n    //! return filesystems currently unavailable\n    std::vector<unsigned int>* unavailfs;\n\n    AccessArguments() :\n      forcedfsid(0),\n      forcedspace(0),\n      tried_cgi(),\n      lid(0),\n      inode(0),\n      isRW(false),\n      bookingsize(0),\n      schedtype(regular),\n      vid(nullptr),\n      locationsfs(nullptr),\n      dataproxys(nullptr),\n      firewallentpts(nullptr),\n      fsindex(nullptr),\n      unavailfs(nullptr)\n    {}\n\n    bool isValid() const\n    {\n      return\n        lid\n        && vid\n        && locationsfs\n        && fsindex\n        && unavailfs;\n    }\n\n  };\n\n  //----------------------------------------------------------------------------\n  //! Take the decision from where to access a file.\n  //!\n  //! @param args the structure holding all the input and output arguments\n  //!\n  //! @return 0 if successful, otherwise a non-zero value\n  //!\n  //! NOTE: Has to be called with a lock on the FsView::gFsView::ViewMutex\n  //----------------------------------------------------------------------------\n  static int FileAccess(AccessArguments* args);\n\n  static int FlatSchedulerFileAccess(AccessArguments* args);\n  //----------------------------------------------------------------------------\n  //! Translate placement policy type to string\n  //----------------------------------------------------------------------------\n  static const char* PlctPolicyString(tPlctPolicy plctPolicy)\n  {\n    if (plctPolicy == kScattered) {\n      return \"scattered\";\n    } else if (plctPolicy == kHybrid) {\n      return \"hybrid\";\n    } else if (plctPolicy == kGathered) {\n      return \"gathered\";\n    } else {\n      return \"none\";\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return placement policy from string representation\n  //----------------------------------------------------------------------------\n  static int PlctPolicyFromString(const std::string& placement)\n  {\n    if (placement == \"scattered\") {\n      return kScattered;\n    } else if (placement == \"hybrid\") {\n      return kHybrid;\n    } else if (placement == \"gathered\") {\n      return kGathered;\n    }\n\n    return -1;\n  }\n\n  // reshuffle the selectedfs by returning as first entry the lowest if the\n  // sum of the fsid is odd the highest if the sum is even\n  static void ReshuffleFs(std::vector<unsigned int>& selectedfs);\n\nprotected:\n\n  static XrdSysMutex pMapMutex; //< protect the following scheduling state maps\n\n  //! Points to the current scheduling group where to start scheduling =>\n  //! std::string = <grouptag>|<uid>:<gid>\n  static std::map<std::string, FsGroup*> schedulingGroup;\n};\n\nnamespace scheduler {\n\ninline Scheduler::PlacementArguments::Path Path(const char* v) {\n  return Scheduler::PlacementArguments::Path{v};\n}\n\ninline Scheduler::PlacementArguments::GroupTag GroupTag(const char* v) {\n  return Scheduler::PlacementArguments::GroupTag{v};\n}\n\ninline Scheduler::PlacementArguments::Lid Lid(unsigned long v) {\n  return Scheduler::PlacementArguments::Lid{v};\n}\n\ninline Scheduler::PlacementArguments::BookingSize BookingSize(unsigned long long v) {\n  return Scheduler::PlacementArguments::BookingSize{v};\n}\n\n\n} // namespace scheduler\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/shaping/TrafficShaping.cc",
    "content": "#include \"mgm/shaping/TrafficShaping.hh\"\n#include \"Constants.hh\"\n#include \"common/Logging.hh\"\n#include \"common/SymKeys.hh\"\n#include \"config/IConfigEngine.hh\"\n#include \"fsview/FsView.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"namespace/interface/IFsView.hh\"\n\n#include \"proto/TrafficShaping.pb.h\"\n\n#include <dlfcn.h>\n#include <google/protobuf/util/json_util.h>\n#include <set>\n#include <sstream>\n#include <sys/stat.h>\n\nnamespace eos::mgm::traffic_shaping {\n\nuint64_t\nTrafficShapingPolicy::GetEffectiveWriteLimit() const\n{\n  uint64_t active_user_limit = is_enabled ? limit_write_bytes_per_sec : 0;\n  if (active_user_limit > 0 && controller_limit_write_bytes_per_sec > 0) {\n    return std::min(active_user_limit, controller_limit_write_bytes_per_sec);\n  }\n  return active_user_limit > 0 ? active_user_limit : controller_limit_write_bytes_per_sec;\n}\n\nuint64_t\nTrafficShapingPolicy::GetEffectiveReadLimit() const\n{\n  uint64_t active_user_limit = is_enabled ? limit_read_bytes_per_sec : 0;\n  if (active_user_limit > 0 && controller_limit_read_bytes_per_sec > 0) {\n    return std::min(active_user_limit, controller_limit_read_bytes_per_sec);\n  }\n  return active_user_limit > 0 ? active_user_limit : controller_limit_read_bytes_per_sec;\n}\n\nbool\nTrafficShapingPolicy::IsEmpty() const\n{\n  return limit_write_bytes_per_sec == 0 && limit_read_bytes_per_sec == 0 &&\n         reservation_write_bytes_per_sec == 0 && reservation_read_bytes_per_sec == 0 &&\n         controller_limit_write_bytes_per_sec == 0 &&\n         controller_limit_read_bytes_per_sec == 0;\n}\n\nbool\nTrafficShapingPolicy::IsActive() const\n{\n  const bool has_user_rules =\n      limit_write_bytes_per_sec > 0 || limit_read_bytes_per_sec > 0 ||\n      reservation_write_bytes_per_sec > 0 || reservation_read_bytes_per_sec > 0;\n  const bool has_controller_rules =\n      controller_limit_write_bytes_per_sec > 0 || controller_limit_read_bytes_per_sec > 0;\n  return has_controller_rules || (is_enabled && has_user_rules);\n}\n\nbool\nTrafficShapingPolicy::operator==(const TrafficShapingPolicy& policy) const\n{\n  return limit_write_bytes_per_sec == policy.limit_write_bytes_per_sec &&\n         limit_read_bytes_per_sec == policy.limit_read_bytes_per_sec &&\n         reservation_write_bytes_per_sec == policy.reservation_write_bytes_per_sec &&\n         reservation_read_bytes_per_sec == policy.reservation_read_bytes_per_sec &&\n         is_enabled == policy.is_enabled;\n}\n\nbool\nTrafficShapingPolicy::operator!=(const TrafficShapingPolicy& policy) const\n{\n  return !(*this == policy);\n}\n\nstd::string\nTrafficShapingPolicy::ToString() const\n{\n  std::ostringstream oss;\n  oss << (is_enabled ? \"Enabled\" : \"Disabled\") << \", \"\n      << \"Read Limit: \" << limit_read_bytes_per_sec << \" Bps, \"\n      << \"Write Limit: \" << limit_write_bytes_per_sec << \" Bps, \"\n      << \"Read Reservation: \" << reservation_read_bytes_per_sec << \" Bps, \"\n      << \"Write Reservation: \" << reservation_write_bytes_per_sec << \" Bps, \"\n      << \"Controller Read Limit: \" << controller_limit_read_bytes_per_sec << \" Bps, \"\n      << \"Controller Write Limit: \" << controller_limit_write_bytes_per_sec << \" Bps\";\n  return oss.str();\n}\n\nTrafficShapingManager::TrafficShapingManager() = default;\n\nTrafficShapingManager::~TrafficShapingManager() { Clear(); }\n\nvoid\nTrafficShapingManager::ApplyThreadConfig(const uint32_t estimators_period_ms,\n                                         const uint32_t fst_policy_period_ms,\n                                         const uint32_t window_seconds)\n{\n  std::unique_lock lock(mMutex);\n\n  mSystemStatsWindowSeconds = window_seconds;\n  mEstimatorsTickIntervalSec = estimators_period_ms * 0.001;\n\n  for (auto& [key, stats] : mGlobalStats) {\n    stats.ResetWindows(mEstimatorsTickIntervalSec);\n  }\n  for (auto& [node, stats] : mNodeStats) {\n    stats.ResetWindows(mEstimatorsTickIntervalSec);\n  }\n  mTotalStats.ResetWindows(mEstimatorsTickIntervalSec);\n\n  estimators_update_loop_micro_sec.emplace(mSystemStatsWindowSeconds,\n                                           mEstimatorsTickIntervalSec);\n  fst_reports_processed_per_second.emplace(mSystemStatsWindowSeconds,\n                                           mEstimatorsTickIntervalSec);\n  fst_limits_update_loop_micro_sec.emplace(mSystemStatsWindowSeconds,\n                                           fst_policy_period_ms * 0.001);\n}\n\ndouble\nTrafficShapingManager::CalculateEma(const double current_val, const double prev_ema,\n                                    const double alpha)\n{\n  return (alpha * current_val) + ((1.0 - alpha) * prev_ema);\n}\n\nstd::tuple<std::unordered_map<std::string, double>,\n           std::unordered_map<std::string, double>, std::unordered_map<uint32_t, double>,\n           std::unordered_map<uint32_t, double>, std::unordered_map<uint32_t, double>,\n           std::unordered_map<uint32_t, double>>\nTrafficShapingManager::GetCurrentReadAndWriteRates() const\n{\n  std::shared_lock lock(mMutex);\n\n  std::unordered_map<std::string, double> app_read_rates, app_write_rates;\n  std::unordered_map<uint32_t, double> uid_read_rates, uid_write_rates;\n  std::unordered_map<uint32_t, double> gid_read_rates, gid_write_rates;\n\n  for (const auto& [key, stats] : mGlobalStats) {\n    const double read_bps = stats.ema[Ema5s].read_rate_bps;\n    const double write_bps = stats.ema[Ema5s].write_rate_bps;\n\n    app_read_rates[key.app] += read_bps;\n    app_write_rates[key.app] += write_bps;\n\n    uid_read_rates[key.uid] += read_bps;\n    uid_write_rates[key.uid] += write_bps;\n\n    gid_read_rates[key.gid] += read_bps;\n    gid_write_rates[key.gid] += write_bps;\n  }\n\n  return {app_read_rates,  app_write_rates, uid_read_rates,\n          uid_write_rates, gid_read_rates,  gid_write_rates};\n}\n\nvoid\nTrafficShapingManager::ProcessReport(const eos::traffic_shaping::FstIoReport& report)\n{\n  const std::string& node_id = report.node_id();\n\n  const auto now_steady = std::chrono::steady_clock::now();\n  const time_t now_unix = time(nullptr);\n\n  std::unique_lock lock(mMutex);\n\n  NodeData& node_data = mNodeStates[node_id];\n  NodeStateMap& node_map = node_data.streams;\n\n  bool is_first_node_contact =\n      (node_data.last_report_time == std::chrono::steady_clock::time_point{});\n\n  double node_elapsed_sec = 0.0;\n  if (!is_first_node_contact) {\n    node_elapsed_sec =\n        std::chrono::duration<double>(now_steady - node_data.last_report_time).count();\n  }\n  node_data.last_report_time = now_steady;\n\n  bool is_node_delayed =\n      (!is_first_node_contact && node_elapsed_sec > kMaxThreadPeriodMs * 0.001);\n\n  if (is_first_node_contact && !report.entries().empty()) {\n    eos_static_info(\n        \"Received first FST report from node '%s'. We will treat this as a baseline and \"\n        \"not calculate deltas to prevent spikes. node_elapsed_sec=%.3f\",\n        node_id.c_str(), node_elapsed_sec);\n  }\n\n  if (is_node_delayed && !report.entries().empty()) {\n    eos_static_warning(\n        \"msg=\\\"Large delay in FST report, dropping deltas to prevent rate spike\\\" \"\n        \"node=%s node_elapsed_sec=%.3f\",\n        node_id.c_str(), node_elapsed_sec);\n  }\n\n  if (!report.entries().empty()) {\n    eos_static_debug(\"Received FST IO Report from node '%s'. Report: \"\n                     \"%s\",\n                     node_id.c_str(), report.DebugString().c_str());\n  }\n\n  uint64_t total_node_delta_bytes_read = 0;\n  uint64_t total_node_delta_bytes_written = 0;\n  uint64_t total_node_delta_read_iops = 0;\n  uint64_t total_node_delta_write_iops = 0;\n\n  for (const auto& entry : report.entries()) {\n    StreamKey key{entry.app_name(), entry.uid(), entry.gid()};\n\n    StreamState& state = node_map[key];\n\n    uint64_t delta_bytes_read = 0;\n    uint64_t delta_bytes_written = 0;\n    uint64_t delta_read_iops = 0;\n    uint64_t delta_write_iops = 0;\n    const bool is_first_stream_contact =\n        state.last_update_time == std::chrono::steady_clock::time_point{};\n\n    // Handle New Streams, MGM Restarts, and FST Restarts\n    if (is_first_stream_contact || state.generation_id != entry.generation_id()) {\n      state.generation_id = entry.generation_id();\n\n      const uint64_t now_sys_ms = std::chrono::duration_cast<std::chrono::milliseconds>(\n                                      std::chrono::system_clock::now().time_since_epoch())\n                                      .count();\n\n      // If the stream was created more than 3 seconds ago, it is a ghost from the past\n      bool is_old_stream = false;\n      if (now_sys_ms > entry.generation_id() &&\n          (now_sys_ms - entry.generation_id() > 3000)) {\n        is_old_stream = true;\n      }\n\n      if (is_first_node_contact || (is_first_stream_contact && is_old_stream)) {\n        delta_bytes_read = 0;\n        delta_bytes_written = 0;\n        delta_read_iops = 0;\n        delta_write_iops = 0;\n\n        eos_static_info(\"msg=\\\"Issue detected with IO Stats report, we will not \"\n                        \"calculate deltas for this report.\\\" \"\n                        \"node=%s app=%s uid=%u gid=%u is_old_stream=%d \"\n                        \"is_first_stream_contact=%d is_first_node_contact=%d\",\n                        node_id.c_str(), entry.app_name().c_str(), entry.uid(),\n                        entry.gid(), is_old_stream, is_first_stream_contact,\n                        is_first_node_contact);\n\n      } else {\n        delta_bytes_read = entry.total_bytes_read();\n        delta_bytes_written = entry.total_bytes_written();\n        delta_read_iops = entry.total_read_ops();\n        delta_write_iops = entry.total_write_ops();\n      }\n    } else {\n      if (entry.total_bytes_read() >= state.last_bytes_read) {\n        delta_bytes_read = entry.total_bytes_read() - state.last_bytes_read;\n      }\n      if (entry.total_bytes_written() >= state.last_bytes_written) {\n        delta_bytes_written = entry.total_bytes_written() - state.last_bytes_written;\n      }\n      if (entry.total_read_ops() >= state.last_iops_read) {\n        delta_read_iops = entry.total_read_ops() - state.last_iops_read;\n      }\n      if (entry.total_write_ops() >= state.last_iops_write) {\n        delta_write_iops = entry.total_write_ops() - state.last_iops_write;\n      }\n    }\n\n    // Prevent huge spikes due to delayed reports or restarts by zeroing out deltas if we\n    // detect a delay\n    if (is_node_delayed) {\n      delta_bytes_read = 0;\n      delta_bytes_written = 0;\n      delta_read_iops = 0;\n      delta_write_iops = 0;\n    }\n\n    total_node_delta_bytes_read += delta_bytes_read;\n    total_node_delta_bytes_written += delta_bytes_written;\n    total_node_delta_read_iops += delta_read_iops;\n    total_node_delta_write_iops += delta_write_iops;\n\n    state.last_bytes_read = entry.total_bytes_read();\n    state.last_bytes_written = entry.total_bytes_written();\n    state.last_iops_read = entry.total_read_ops();\n    state.last_iops_write = entry.total_write_ops();\n    state.last_update_time = now_steady;\n\n    if (delta_bytes_read > 0 || delta_bytes_written > 0 || delta_read_iops > 0 ||\n        delta_write_iops > 0) {\n\n      auto [it, inserted] = mGlobalStats.try_emplace(key, mEstimatorsTickIntervalSec);\n      MultiWindowRate& global = it->second;\n\n      global.bytes_read_accumulator += delta_bytes_read;\n      global.bytes_written_accumulator += delta_bytes_written;\n      global.read_iops_accumulator += delta_read_iops;\n      global.write_iops_accumulator += delta_write_iops;\n      global.last_activity_time = now_unix;\n    }\n  }\n\n  if (total_node_delta_bytes_read > 0 || total_node_delta_bytes_written > 0 ||\n      total_node_delta_read_iops > 0 || total_node_delta_write_iops > 0) {\n\n    auto [it, inserted] = mNodeStats.try_emplace(node_id, mEstimatorsTickIntervalSec);\n    MultiWindowRate& node_stat = it->second;\n\n    node_stat.bytes_read_accumulator += total_node_delta_bytes_read;\n    node_stat.bytes_written_accumulator += total_node_delta_bytes_written;\n    node_stat.read_iops_accumulator += total_node_delta_read_iops;\n    node_stat.write_iops_accumulator += total_node_delta_write_iops;\n    node_stat.last_activity_time = now_unix;\n\n    mTotalStats.bytes_read_accumulator += total_node_delta_bytes_read;\n    mTotalStats.bytes_written_accumulator += total_node_delta_bytes_written;\n    mTotalStats.read_iops_accumulator += total_node_delta_read_iops;\n    mTotalStats.write_iops_accumulator += total_node_delta_write_iops;\n    mTotalStats.last_activity_time = now_unix;\n  }\n}\n\ndouble\nComputeEmaAlpha(const double window_seconds, const double time_delta_seconds)\n{\n  if (time_delta_seconds <= 0.0 || window_seconds <= 0.0) {\n    return 1.0;\n  }\n  return (2.0 * time_delta_seconds) / (window_seconds + time_delta_seconds);\n}\n\nvoid\nTrafficShapingManager::UpdateEstimators(const double time_delta_seconds)\n{\n  if (time_delta_seconds <= 0.001 /* 1 ms */) {\n    eos_static_err(\n        \"msg=\\\"Skipping estimator update due to problem time delta seconds: %f\\\"\",\n        time_delta_seconds);\n    return;\n  }\n\n  std::unique_lock lock(mMutex);\n\n  std::array<double, EmaWindowSec.size()> ema_alphas{};\n  for (size_t i = 0; i < EmaWindowSec.size(); ++i) {\n    ema_alphas[i] = ComputeEmaAlpha(EmaWindowSec[i], time_delta_seconds);\n  }\n\n  auto process_rate = [&](MultiWindowRate& stats) {\n    const uint64_t bytes_read_now = stats.bytes_read_accumulator.exchange(0);\n    const uint64_t bytes_written_now = stats.bytes_written_accumulator.exchange(0);\n    const uint64_t read_iops_now = stats.read_iops_accumulator.exchange(0);\n    const uint64_t write_iops_now = stats.write_iops_accumulator.exchange(0);\n\n    const double current_read_bps =\n        static_cast<double>(bytes_read_now) / time_delta_seconds;\n    const double current_write_bps =\n        static_cast<double>(bytes_written_now) / time_delta_seconds;\n    const double current_read_iops =\n        static_cast<double>(read_iops_now) / time_delta_seconds;\n    const double current_write_iops =\n        static_cast<double>(write_iops_now) / time_delta_seconds;\n\n    for (size_t i = 0; i < EmaWindowSec.size(); ++i) {\n      stats.ema[i].read_rate_bps =\n          CalculateEma(current_read_bps, stats.ema[i].read_rate_bps, ema_alphas[i]);\n      stats.ema[i].write_rate_bps =\n          CalculateEma(current_write_bps, stats.ema[i].write_rate_bps, ema_alphas[i]);\n      stats.ema[i].read_iops =\n          CalculateEma(current_read_iops, stats.ema[i].read_iops, ema_alphas[i]);\n      stats.ema[i].write_iops =\n          CalculateEma(current_write_iops, stats.ema[i].write_iops, ema_alphas[i]);\n    }\n\n    stats.bytes_read_window.Add(bytes_read_now);\n    stats.bytes_written_window.Add(bytes_written_now);\n    stats.iops_read_window.Add(read_iops_now);\n    stats.iops_write_window.Add(write_iops_now);\n\n    stats.bytes_read_window.Tick();\n    stats.bytes_written_window.Tick();\n    stats.iops_read_window.Tick();\n    stats.iops_write_window.Tick();\n\n    for (size_t i = 0; i < SmaWindowSec.size(); ++i) {\n      const int window_sec = SmaWindowSec[i];\n      stats.sma[i].read_rate_bps = stats.bytes_read_window.GetRate(window_sec);\n      stats.sma[i].write_rate_bps = stats.bytes_written_window.GetRate(window_sec);\n      stats.sma[i].read_iops = stats.iops_read_window.GetRate(window_sec);\n      stats.sma[i].write_iops = stats.iops_write_window.GetRate(window_sec);\n    }\n  };\n\n  for (auto& [key, stats] : mGlobalStats) {\n    process_rate(stats);\n  }\n  for (auto& [node_id, stats] : mNodeStats) {\n    process_rate(stats);\n  }\n  process_rate(mTotalStats);\n}\n\nuint64_t\nTrafficShapingManager::CalculateDelayUs(const double limit_bps,\n                                        const double current_rate_bps,\n                                        const uint64_t current_delay_us)\n{\n  if (limit_bps <= 0.0) {\n    return 0;\n  }\n\n  // --- Tuning Constants ---\n  constexpr uint64_t kMaxDelayUs = 2000000;          // 2 seconds max artificial delay\n  constexpr int64_t kMaxStepUpUs = kMaxDelayUs / 25; // Max delta to ADD per tick (80ms)\n  // Allow faster recovery: shed delay 2.5x faster than we add it.\n  constexpr int64_t kMaxStepDownUs =\n      kMaxDelayUs / 10;                   // Max delta to REMOVE per tick (200ms)\n  constexpr double kIdleThreshold = 0.01; // < 1% of limit => no meaningful traffic\n\n  const double ratio = current_rate_bps / limit_bps;\n  uint64_t delay_us = current_delay_us;\n\n  // 0. Idle freeze: preserve the current delay when traffic is essentially absent.\n  if (current_rate_bps < limit_bps * kIdleThreshold) {\n    return delay_us;\n  }\n\n  // 1. Kickstart the delay if we breach the limit for the first time\n  if (delay_us == 0 && ratio > 1.0) {\n    delay_us = 100;\n  }\n  // 2. Adjust the delay using a proportional feedback loop\n  else if (delay_us > 0) {\n    // Asymmetric Proportional Gain (kp):\n    // Bumped the recovery gain from 0.05 to 0.10 so it bounces back from undershoots\n    // faster.\n    const double kp = (ratio > 1.0) ? 0.15 : 0.10;\n    const double damped_ratio = 1.0 + ((ratio - 1.0) * kp);\n\n    const auto current_delay_signed = static_cast<int64_t>(delay_us);\n    const auto target_delay =\n        static_cast<int64_t>(static_cast<double>(current_delay_signed) * damped_ratio);\n\n    // Apply the asymmetric step limits\n    int64_t delta_us = target_delay - current_delay_signed;\n    if (delta_us > kMaxStepUpUs) {\n      delta_us = kMaxStepUpUs;\n    } else if (delta_us < -kMaxStepDownUs) {\n      delta_us = -kMaxStepDownUs;\n    }\n\n    delay_us =\n        static_cast<uint64_t>(std::max(int64_t{0}, current_delay_signed + delta_us));\n  }\n\n  // 3. Clamp to absolute maximum\n  delay_us = std::min<uint64_t>(kMaxDelayUs, delay_us);\n\n  // 4. Snap to 0 if the delay drops too low, and we are under the limit\n  if (delay_us < 10 && ratio < 1.0) {\n    delay_us = 0;\n  }\n\n  return delay_us;\n}\n\nvoid\nTrafficShapingManager::UpdateTrafficShapingController()\n{\n  std::shared_lock read_lock(mPluginMutex);\n\n  if (!mCustomControllerAlgo) {\n    return;\n  }\n\n  // 1. Gather all unique active apps and defined policies.\n  // Snapshot mAppPolicies under mMutex to avoid racing with Set/RemoveAppPolicy.\n  std::set<std::string> unique_apps;\n  auto rates = GetCurrentReadAndWriteRates();\n  auto& app_read_map = std::get<0>(rates);\n  auto& app_write_map = std::get<1>(rates);\n\n  for (const auto& [app, _] : app_read_map) {\n    unique_apps.insert(app);\n  }\n  for (const auto& [app, _] : app_write_map) {\n    unique_apps.insert(app);\n  }\n\n  std::unordered_map<std::string, TrafficShapingPolicy> policies_snapshot;\n  {\n    std::shared_lock policies_lock(mMutex);\n    policies_snapshot = mAppPolicies;\n  }\n  for (const auto& [app, _] : policies_snapshot) {\n    unique_apps.insert(app);\n  }\n\n  if (unique_apps.empty()) {\n    return;\n  }\n\n  // 2. Prepare the flat array for the plugin\n  std::vector<AppState> app_array;\n  app_array.reserve(unique_apps.size());\n\n  for (const auto& app : unique_apps) {\n    AppState st{}; // Zero-initialize\n    strncpy(st.app_name, app.c_str(), sizeof(st.app_name) - 1);\n\n    // Telemetry\n    if (auto it = app_read_map.find(app); it != app_read_map.end()) {\n      st.current_read_bps = it->second;\n    }\n    if (auto it = app_write_map.find(app); it != app_write_map.end()) {\n      st.current_write_bps = it->second;\n    }\n\n    // Policy (from snapshot)\n    if (auto pol_it = policies_snapshot.find(app); pol_it != policies_snapshot.end()) {\n      st.reservation_write_bps = pol_it->second.reservation_write_bytes_per_sec;\n      st.reservation_read_bps = pol_it->second.reservation_read_bytes_per_sec;\n      st.controller_limit_write_bps = pol_it->second.controller_limit_write_bytes_per_sec;\n      st.controller_limit_read_bps = pol_it->second.controller_limit_read_bytes_per_sec;\n    }\n\n    app_array.push_back(st);\n  }\n\n  // 3. INJECT THE DATA INTO THE PLUGIN\n  mCustomControllerAlgo(app_array.data(), app_array.size());\n\n  // 4. Read the results back and apply them\n  for (const auto& st : app_array) {\n    if (st.update_write || st.update_read) {\n      TrafficShapingPolicy policy;\n\n      // Preserve existing user configuration from snapshot.\n      // SetAppPolicy acquires mMutex internally; do not hold it here.\n      if (auto pol_it = policies_snapshot.find(st.app_name);\n          pol_it != policies_snapshot.end()) {\n        policy = pol_it->second;\n      }\n\n      if (st.update_write) {\n        policy.controller_limit_write_bytes_per_sec = st.new_controller_limit_write_bps;\n      }\n      if (st.update_read) {\n        policy.controller_limit_read_bytes_per_sec = st.new_controller_limit_read_bps;\n      }\n\n      // Automatically syncs to FSTs because SetAppPolicy bumps the config version\n      SetAppPolicy(st.app_name, policy);\n\n      eos_static_info(\"msg=\\\"Plugin applied dynamic controller limits\\\" app=\\\"%s\\\" \"\n                      \"controller_limit_write_bps=%lu controller_limit_read_bps=%lu\",\n                      st.app_name, policy.controller_limit_write_bytes_per_sec,\n                      policy.controller_limit_read_bytes_per_sec);\n    }\n  }\n}\n\nvoid\nTrafficShapingManager::UpdateLimits()\n{\n  // 1. Evaluate hot-reload status at the start of every tick\n  LoadPluginIfModified();\n\n  eos::traffic_shaping::TrafficShapingFstIoDelayConfig fst_io_delay_config;\n\n  auto* app_write_map = fst_io_delay_config.mutable_app_write_delay();\n  auto* app_read_map = fst_io_delay_config.mutable_app_read_delay();\n  auto* uid_write_map = fst_io_delay_config.mutable_uid_write_delay();\n  auto* uid_read_map = fst_io_delay_config.mutable_uid_read_delay();\n  auto* gid_write_map = fst_io_delay_config.mutable_gid_write_delay();\n  auto* gid_read_map = fst_io_delay_config.mutable_gid_read_delay();\n\n  auto adjust_delay = [&](const double limit_bps, const double current_rate,\n                          uint64_t& delay_us, auto* output_map, const auto& entity_key,\n                          const char* entity_type, const std::string& entity_id,\n                          const char* op_type) {\n    if (limit_bps <= 0) {\n      return;\n    }\n\n    const uint64_t old_delay = delay_us;\n    const double ratio = current_rate / limit_bps;\n    {\n      std::shared_lock read_lock(mPluginMutex);\n      if (mCustomAlgo) {\n        delay_us = mCustomAlgo(limit_bps, current_rate, old_delay);\n      } else {\n        delay_us = CalculateDelayUs(limit_bps, current_rate, old_delay);\n      }\n    }\n\n    if (delay_us > 0) {\n      (*output_map)[entity_key] = delay_us;\n    }\n\n    eos_static_debug(\"msg=\\\"throttle evaluation\\\" type=\\\"%s\\\" id=\\\"%s\\\" op=\\\"%s\\\" \"\n                     \"limit_bps=%.0f current_rate_bps=%.0f ratio=%.3f \"\n                     \"old_delay_us=%lu new_delay_us=%lu\",\n                     entity_type, entity_id.c_str(), op_type, limit_bps, current_rate,\n                     ratio, old_delay, delay_us);\n  };\n\n  const auto [app_read_rates, app_write_rates, uid_read_rates, uid_write_rates,\n              gid_read_rates, gid_write_rates] = GetCurrentReadAndWriteRates();\n\n  // Helper lambda to do a single-pass map lookup\n  auto get_rate = [](const auto& map, const auto& key) {\n    if (auto it = map.find(key); it != map.end()) {\n      return it->second;\n    }\n    return 0.0;\n  };\n  {\n    std::shared_lock lock(mMutex);\n\n    for (const auto& [app, policy] : mAppPolicies) {\n      if (!policy.IsActive()) {\n        eos_static_debug(\"msg=\\\"skipping inactive policy\\\" type=\\\"app\\\" id=\\\"%s\\\"\",\n                         app.c_str());\n        continue;\n      }\n\n      adjust_delay(static_cast<double>(policy.GetEffectiveWriteLimit()),\n                   get_rate(app_write_rates, app),\n                   (*mFstIoDelayConfig.mutable_app_write_delay())[app], app_write_map,\n                   app, \"app\", app, \"write\");\n\n      adjust_delay(static_cast<double>(policy.GetEffectiveReadLimit()),\n                   get_rate(app_read_rates, app),\n                   (*mFstIoDelayConfig.mutable_app_read_delay())[app], app_read_map, app,\n                   \"app\", app, \"read\");\n    }\n\n    for (const auto& [uid, policy] : mUidPolicies) {\n      if (!policy.IsActive()) {\n        eos_static_debug(\"msg=\\\"skipping inactive policy\\\" type=\\\"uid\\\" id=\\\"%u\\\"\", uid);\n        continue;\n      }\n\n      adjust_delay(static_cast<double>(policy.GetEffectiveWriteLimit()),\n                   get_rate(uid_write_rates, uid),\n                   (*mFstIoDelayConfig.mutable_uid_write_delay())[uid], uid_write_map,\n                   uid, \"uid\", std::to_string(uid), \"write\");\n\n      adjust_delay(static_cast<double>(policy.GetEffectiveReadLimit()),\n                   get_rate(uid_read_rates, uid),\n                   (*mFstIoDelayConfig.mutable_uid_read_delay())[uid], uid_read_map, uid,\n                   \"uid\", std::to_string(uid), \"read\");\n    }\n\n    for (const auto& [gid, policy] : mGidPolicies) {\n      if (!policy.IsActive()) {\n        eos_static_debug(\"msg=\\\"skipping inactive policy\\\" type=\\\"gid\\\" id=\\\"%u\\\"\", gid);\n        continue;\n      }\n\n      adjust_delay(static_cast<double>(policy.GetEffectiveWriteLimit()),\n                   get_rate(gid_write_rates, gid),\n                   (*mFstIoDelayConfig.mutable_gid_write_delay())[gid], gid_write_map,\n                   gid, \"gid\", std::to_string(gid), \"write\");\n\n      adjust_delay(static_cast<double>(policy.GetEffectiveReadLimit()),\n                   get_rate(gid_read_rates, gid),\n                   (*mFstIoDelayConfig.mutable_gid_read_delay())[gid], gid_read_map, gid,\n                   \"gid\", std::to_string(gid), \"read\");\n    }\n  }\n\n  std::vector<std::string> online_nodes;\n  {\n    eos::common::RWMutexReadLock viewlock(FsView::gFsView.ViewMutex);\n    for (const auto& [node_name, node_view] : FsView::gFsView.mNodeView) {\n      if (node_view->GetStatus() == \"online\") {\n        online_nodes.push_back(node_name);\n      }\n    }\n  }\n\n  std::string serialized = fst_io_delay_config.SerializeAsString();\n  std::string encoded;\n\n  if (!eos::common::SymKey::Base64(serialized, encoded)) {\n    eos_static_warning(\"%s\", \"msg=\\\"failed to base64-encode FST IO limits config\\\"\");\n    return;\n  }\n\n  for (const auto& node_name : online_nodes) {\n    eos::common::RWMutexReadLock viewlock(FsView::gFsView.ViewMutex);\n    auto it = FsView::gFsView.mNodeView.find(node_name);\n    if (it != FsView::gFsView.mNodeView.end()) {\n      it->second->SetConfigMember(eos::common::FST_TRAFFIC_SHAPING_IO_LIMITS, encoded,\n                                  true);\n    }\n  }\n}\n\nstd::unordered_map<StreamKey, RateSnapshot, StreamKeyHash>\nTrafficShapingManager::GetGlobalStats() const\n{\n  std::shared_lock lock(mMutex);\n\n  std::unordered_map<StreamKey, RateSnapshot, StreamKeyHash> snapshot_map;\n  snapshot_map.reserve(mGlobalStats.size());\n\n  for (const auto& [key, internal_stat] : mGlobalStats) {\n    RateSnapshot& snap = snapshot_map[key];\n\n    snap.last_activity_time = internal_stat.last_activity_time;\n    snap.active_stream_count = internal_stat.active_stream_count;\n    snap.ema = internal_stat.ema;\n    snap.sma = internal_stat.sma;\n  }\n\n  return snapshot_map;\n}\n\nstd::unordered_map<std::string, RateSnapshot>\nTrafficShapingManager::GetNodeStats() const\n{\n  std::shared_lock lock(mMutex);\n  std::unordered_map<std::string, RateSnapshot> snapshot_map;\n  snapshot_map.reserve(mNodeStats.size());\n\n  for (const auto& [node_id, internal_stat] : mNodeStats) {\n    RateSnapshot& snap = snapshot_map[node_id];\n    snap.last_activity_time = internal_stat.last_activity_time;\n    snap.active_stream_count = internal_stat.active_stream_count;\n    snap.ema = internal_stat.ema;\n    snap.sma = internal_stat.sma;\n  }\n\n  return snapshot_map;\n}\n\nTrafficShapingManager::GarbageCollectionStats\nTrafficShapingManager::GarbageCollect(const int max_idle_seconds)\n{\n  std::unique_lock lock(mMutex);\n\n  const auto now_steady = std::chrono::steady_clock::now();\n  const time_t now_unix = time(nullptr);\n\n  GarbageCollectionStats stats = {0, 0, 0};\n\n  for (auto node_it = mNodeStates.begin(); node_it != mNodeStates.end();) {\n    NodeStateMap& map = node_it->second.streams;\n\n    for (auto stream_it = map.begin(); stream_it != map.end();) {\n\n      auto elapsed_sec = std::chrono::duration_cast<std::chrono::seconds>(\n                             now_steady - stream_it->second.last_update_time)\n                             .count();\n\n      if (elapsed_sec > max_idle_seconds) {\n        stream_it = map.erase(stream_it);\n        stats.removed_node_streams++;\n      } else {\n        ++stream_it;\n      }\n    }\n\n    // Calculate how long it has been since the Node itself sent a heartbeat\n    auto node_idle_sec = std::chrono::duration_cast<std::chrono::seconds>(\n                             now_steady - node_it->second.last_report_time)\n                             .count();\n\n    // Only erase the node if it has no streams AND the node is actually offline/silent\n    if (map.empty() && node_idle_sec > max_idle_seconds) {\n      mNodeStats.erase(node_it->first);\n      node_it = mNodeStates.erase(node_it);\n      stats.removed_nodes++;\n    } else {\n      ++node_it;\n    }\n  }\n\n  for (auto it = mGlobalStats.begin(); it != mGlobalStats.end();) {\n    if (now_unix - it->second.last_activity_time > max_idle_seconds) {\n      it = mGlobalStats.erase(it);\n      stats.removed_global_streams++;\n    } else {\n      ++it;\n    }\n  }\n\n  return stats;\n}\n\nvoid\nTrafficShapingManager::UpdateFstLimitsLoopMicroSec(const uint64_t time_microseconds)\n{\n  std::unique_lock lock(mMutex);\n  if (fst_limits_update_loop_micro_sec) {\n    fst_limits_update_loop_micro_sec->Add(time_microseconds);\n    fst_limits_update_loop_micro_sec->Tick();\n  }\n}\n\nvoid\nTrafficShapingManager::UpdateEstimatorsLoopMicroSec(const uint64_t time_microseconds)\n{\n  std::unique_lock lock(mMutex);\n  if (estimators_update_loop_micro_sec) {\n    estimators_update_loop_micro_sec->Add(time_microseconds);\n    estimators_update_loop_micro_sec->Tick();\n  }\n  if (fst_reports_processed_per_second) {\n    fst_reports_processed_per_second->Tick();\n  }\n}\n\nstd::tuple<uint64_t, uint64_t, uint64_t>\nTrafficShapingManager::GetEstimatorsUpdateLoopMicroSecStats() const\n{\n  std::shared_lock lock(mMutex);\n  if (estimators_update_loop_micro_sec) {\n    return {\n        estimators_update_loop_micro_sec->GetMedian(true), // Ignore zeroes correctly\n        estimators_update_loop_micro_sec->GetMin(true),\n        estimators_update_loop_micro_sec->GetMax(true),\n    };\n  }\n  return {0, 0, 0};\n}\n\nstd::tuple<uint64_t, uint64_t, uint64_t>\nTrafficShapingManager::GetFstLimitsUpdateLoopMicroSecStats() const\n{\n  std::shared_lock lock(mMutex);\n  if (fst_limits_update_loop_micro_sec) {\n    return {\n        fst_limits_update_loop_micro_sec->GetMedian(true),\n        fst_limits_update_loop_micro_sec->GetMin(true),\n        fst_limits_update_loop_micro_sec->GetMax(true),\n    };\n  }\n  return {0, 0, 0};\n}\n\nvoid\nTrafficShapingManager::UpdateFstReportsProcessed(const uint64_t count)\n{\n  std::unique_lock lock(mMutex);\n  if (fst_reports_processed_per_second) {\n    fst_reports_processed_per_second->Add(count);\n  }\n}\n\ndouble\nTrafficShapingManager::GetFstReportsProcessedPerSecondMean() const\n{\n  std::shared_lock lock(mMutex);\n  if (fst_reports_processed_per_second) {\n    double multiplier = 1.0;\n    if (mEstimatorsTickIntervalSec > 0.0) {\n      multiplier = 1.0 / mEstimatorsTickIntervalSec;\n    }\n\n    // 0 counts in a tick are valid measurements for processing speed.\n    return fst_reports_processed_per_second->GetMean(false) * multiplier;\n  }\n  return 0.0;\n}\n\nvoid\nTrafficShapingManager::Clear()\n{\n  std::unique_lock lock(mMutex);\n  mNodeStates.clear();\n  mGlobalStats.clear();\n  mNodeStats.clear();\n  mTotalStats.clear();\n\n  estimators_update_loop_micro_sec.reset();\n  fst_limits_update_loop_micro_sec.reset();\n  fst_reports_processed_per_second.reset();\n\n  if (mPluginHandle) {\n    dlclose(mPluginHandle);\n    mPluginHandle = nullptr;\n    mCustomAlgo = nullptr;\n    mCustomControllerAlgo = nullptr;\n    mPluginLastModified = 0;\n  }\n}\n\nRateSnapshot\nTrafficShapingManager::GetTotalStats() const\n{\n  std::shared_lock lock(mMutex);\n  RateSnapshot snap;\n  snap.last_activity_time = mTotalStats.last_activity_time;\n  snap.active_stream_count = mTotalStats.active_stream_count;\n  snap.ema = mTotalStats.ema;\n  snap.sma = mTotalStats.sma;\n  return snap;\n}\n\nTrafficShapingEngine::TrafficShapingEngine()\n    : mRunning(false)\n    , mEstimatorsUpdateThreadPeriodMilliseconds(200)\n    , mFstIoPolicyUpdateThreadPeriodMilliseconds(200)\n    , mFstIoStatsReportThreadPeriodMilliseconds(200)\n    , mSystemStatsWindowSeconds(15)\n{\n  mManager = std::make_shared<TrafficShapingManager>();\n}\n\nTrafficShapingEngine::~TrafficShapingEngine() { Stop(); }\n\nvoid\nTrafficShapingEngine::ApplyThreadConfig(uint32_t est_ms, uint32_t pol_ms, uint32_t rep_ms,\n                                        uint32_t win_s, const bool save_to_config_engine)\n{\n  if (est_ms < kMinThreadPeriodMs) {\n    est_ms = kMinThreadPeriodMs;\n  } else if (est_ms > kMaxThreadPeriodMs) {\n    est_ms = kMaxThreadPeriodMs;\n  }\n\n  if (pol_ms < kMinThreadPeriodMs) {\n    pol_ms = kMinThreadPeriodMs;\n  } else if (pol_ms > kMaxThreadPeriodMs) {\n    pol_ms = kMaxThreadPeriodMs;\n  }\n\n  if (rep_ms < kMinThreadPeriodMs) {\n    rep_ms = kMinThreadPeriodMs;\n  } else if (rep_ms > kMaxThreadPeriodMs) {\n    rep_ms = kMaxThreadPeriodMs;\n  }\n\n  if (win_s < kMinSystemStatsWindowSec) {\n    win_s = kMinSystemStatsWindowSec;\n  } else if (win_s > kMaxSystemStatsWindowSec) {\n    win_s = kMaxSystemStatsWindowSec;\n  }\n\n  bool changed = false;\n  if (mEstimatorsUpdateThreadPeriodMilliseconds.load() != est_ms) {\n    changed = true;\n  }\n  if (mFstIoPolicyUpdateThreadPeriodMilliseconds.load() != pol_ms) {\n    changed = true;\n  }\n  if (mFstIoStatsReportThreadPeriodMilliseconds.load() != rep_ms) {\n    changed = true;\n  }\n  if (mSystemStatsWindowSeconds.load() != win_s) {\n    changed = true;\n  }\n\n  mEstimatorsUpdateThreadPeriodMilliseconds = est_ms;\n  mFstIoPolicyUpdateThreadPeriodMilliseconds = pol_ms;\n  mFstIoStatsReportThreadPeriodMilliseconds = rep_ms;\n  mSystemStatsWindowSeconds = win_s;\n\n  if (mManager != nullptr) {\n    mManager->ApplyThreadConfig(est_ms, pol_ms, win_s);\n  }\n\n  if (save_to_config_engine && changed) {\n    UpdateThreadConfigs();\n    SyncTrafficShapingEnabledWithFst();\n  }\n}\n\nvoid\nTrafficShapingEngine::SetEstimatorsUpdateThreadPeriodMilliseconds(\n    const uint32_t period_ms)\n{\n  ApplyThreadConfig(period_ms, mFstIoPolicyUpdateThreadPeriodMilliseconds,\n                    mFstIoStatsReportThreadPeriodMilliseconds, mSystemStatsWindowSeconds,\n                    true);\n}\n\nvoid\nTrafficShapingEngine::SetFstIoPolicyUpdateThreadPeriodMilliseconds(\n    const uint32_t period_ms)\n{\n  ApplyThreadConfig(mEstimatorsUpdateThreadPeriodMilliseconds, period_ms,\n                    mFstIoStatsReportThreadPeriodMilliseconds, mSystemStatsWindowSeconds,\n                    true);\n}\n\nvoid\nTrafficShapingEngine::SetFstIoStatsReportThreadPeriodMilliseconds(uint32_t period_ms)\n{\n  ApplyThreadConfig(mEstimatorsUpdateThreadPeriodMilliseconds,\n                    mFstIoPolicyUpdateThreadPeriodMilliseconds, period_ms,\n                    mSystemStatsWindowSeconds, true);\n}\n\nvoid\nTrafficShapingEngine::SetSystemStatsWindowSeconds(uint32_t window_seconds)\n{\n  ApplyThreadConfig(mEstimatorsUpdateThreadPeriodMilliseconds,\n                    mFstIoPolicyUpdateThreadPeriodMilliseconds,\n                    mFstIoStatsReportThreadPeriodMilliseconds, window_seconds, true);\n}\n\nvoid\nTrafficShapingEngine::UpdateThreadConfigs()\n{\n  eos::traffic_shaping::ThreadConfig thread_loop_stats;\n\n  thread_loop_stats.set_update_estimators_period_millis(\n      mEstimatorsUpdateThreadPeriodMilliseconds);\n  thread_loop_stats.set_fst_policy_update_period_millis(\n      mFstIoPolicyUpdateThreadPeriodMilliseconds);\n  thread_loop_stats.set_fst_io_stats_report_period_millis(\n      mFstIoStatsReportThreadPeriodMilliseconds);\n  thread_loop_stats.set_system_stats_time_window_seconds(mSystemStatsWindowSeconds);\n\n  std::string serialized = thread_loop_stats.SerializeAsString();\n  std::string encoded;\n\n  if (!eos::common::SymKey::Base64(serialized, encoded)) {\n    eos_static_warning(\"%s\", \"msg=\\\"failed to base64-encode thread periods config\\\"\");\n    return;\n  }\n\n  FsView::gFsView.SetGlobalConfig(common::TRAFFIC_SHAPING_THREAD_PERIODS, encoded);\n  gOFS->mConfigEngine->AutoSave();\n}\n\nvoid\nTrafficShapingEngine::ApplyConfig()\n{\n  const bool is_enabled =\n      FsView::gFsView.GetBoolGlobalConfig(common::TRAFFIC_SHAPING_ENABLE_CONFIG);\n  eos_static_info(\"msg=\\\"Applying Traffic Shaping Config\\\" enabled=%s\",\n                  is_enabled ? \"true\" : \"false\");\n  if (is_enabled) {\n    Enable();\n  } else {\n    Disable();\n  }\n\n  const std::string config =\n      FsView::gFsView.GetGlobalConfig(common::TRAFFIC_SHAPING_POLICIES_CONFIG);\n\n  if (const auto manager = GetManager(); manager != nullptr) {\n    const bool result = manager->LoadPoliciesFromString(config);\n    if (!result) {\n      eos_static_err(\"%s\", \"msg=\\\"Failed to load Traffic Shaping policies from config\\\"\");\n    }\n  }\n\n  uint32_t est_ms = mEstimatorsUpdateThreadPeriodMilliseconds;\n  uint32_t pol_ms = mFstIoPolicyUpdateThreadPeriodMilliseconds;\n  uint32_t rep_ms = mFstIoStatsReportThreadPeriodMilliseconds;\n  uint32_t win_s = mSystemStatsWindowSeconds;\n\n  const std::string thread_periods =\n      FsView::gFsView.GetGlobalConfig(common::TRAFFIC_SHAPING_THREAD_PERIODS);\n  if (!thread_periods.empty()) {\n    try {\n      std::string serialized_thread_periods;\n\n      if (!eos::common::SymKey::DeBase64(thread_periods, serialized_thread_periods)) {\n        eos_static_err(\"%s\", \"msg=\\\"failed to base64-decode thread periods config\\\"\");\n        serialized_thread_periods.clear();\n      }\n\n      eos::traffic_shaping::ThreadConfig thread_config;\n\n      if (!serialized_thread_periods.empty() &&\n          thread_config.ParseFromString(serialized_thread_periods)) {\n        est_ms = thread_config.update_estimators_period_millis();\n        pol_ms = thread_config.fst_policy_update_period_millis();\n        rep_ms = thread_config.fst_io_stats_report_period_millis();\n        win_s = thread_config.system_stats_time_window_seconds();\n      } else if (!serialized_thread_periods.empty()) {\n        eos_static_err(\"%s\", \"msg=\\\"failed to parse thread periods config\\\"\");\n      }\n    } catch (const std::exception& e) {\n      eos_static_err(\"msg=\\\"failed to parse thread periods config\\\" error=%s\", e.what());\n    }\n  }\n\n  ApplyThreadConfig(est_ms, pol_ms, rep_ms, win_s, false);\n\n  SyncTrafficShapingEnabledWithFst();\n}\n\nvoid\nTrafficShapingEngine::Start()\n{\n  if (mRunning) {\n    return;\n  }\n  mRunning = true;\n\n  mEstimatorsUpdateThread.reset(&TrafficShapingEngine::EstimatorsUpdate, this);\n  mEstimatorsUpdateThread.setName(\"Traffic Shaping Estimators Update\");\n\n  mFstIoPolicyUpdateThread.reset(&TrafficShapingEngine::FstIoPolicyUpdate, this);\n  mFstIoPolicyUpdateThread.setName(\"Traffic Shaping FST Policy Update\");\n\n  mFstTrafficShapingConfigUpdateThread.reset(\n      &TrafficShapingEngine::FstTrafficShapingConfigUpdate, this);\n  mFstTrafficShapingConfigUpdateThread.setName(\"Traffic Shaping FST Config Update\");\n\n  ApplyThreadConfig(mEstimatorsUpdateThreadPeriodMilliseconds,\n                    mFstIoPolicyUpdateThreadPeriodMilliseconds,\n                    mFstIoStatsReportThreadPeriodMilliseconds, mSystemStatsWindowSeconds,\n                    false);\n\n  // NOTE: Do NOT call SyncTrafficShapingEnabledWithFst() here.\n  // Start() can be invoked from Enable() which is called from\n  // FsView::ApplyGlobalConfig() while the ViewMutex WRITE lock is held.\n  // SyncTrafficShapingEnabledWithFst() acquires ViewMutex READ lock, which\n  // would deadlock the same thread.  The periodic FstTrafficShapingConfigUpdate\n  // thread provides the heartbeat sync, and TrafficShapingEngine::ApplyConfig()\n  // (called after the write lock is released) provides the immediate sync.\n  eos_static_info(\"msg=\\\"Traffic Shaping Engine Started\\\"\");\n}\n\nvoid\nTrafficShapingEngine::Stop()\n{\n  if (!mRunning) {\n    return;\n  }\n  mRunning = false;\n\n  mEstimatorsUpdateThread.join();\n  mFstIoPolicyUpdateThread.join();\n  mFstTrafficShapingConfigUpdateThread.join();\n  {\n    std::lock_guard lock(mReportQueueMutex);\n    mReportQueue.clear();\n  }\n\n  if (mManager != nullptr) {\n    mManager->Clear();\n  }\n\n  // NOTE: Do NOT call SyncTrafficShapingEnabledWithFst() here for the same\n  // reason as in Start(): Stop() can be called from Disable() which is called\n  // from FsView::ApplyGlobalConfig() while ViewMutex write lock is held.\n  eos_static_info(\"msg=\\\"Traffic Shaping Engine Stopped\\\"\");\n}\n\nstd::shared_ptr<TrafficShapingManager>\nTrafficShapingEngine::GetManager() const\n{\n  return mManager;\n}\n\nvoid\nTrafficShapingEngine::ProcessSerializedFstIoReportNonBlocking(\n    const std::string& serialized_report)\n{\n  if (!mRunning) {\n    return;\n  }\n\n  eos::traffic_shaping::FstIoReport report;\n  if (!report.ParseFromString(serialized_report)) {\n    eos_static_warning(\"%s\", \"msg=\\\"failed to parse FstIoReport from string\\\"\");\n    return;\n  }\n  AddReportToQueue(report);\n}\n\nvoid\nTrafficShapingEngine::AddReportToQueue(const eos::traffic_shaping::FstIoReport& report)\n{\n  static constexpr std::size_t kMaxReports = 500;\n\n  std::lock_guard lock(mReportQueueMutex);\n\n  if (mReportQueue.size() >= kMaxReports) {\n    eos_static_warning(\n        \"msg=\\\"Traffic Shaping report queue full, dropping oldest report\\\" \"\n        \"size=%zu\",\n        mReportQueue.size());\n    mReportQueue.erase(mReportQueue.begin());\n  }\n\n  mReportQueue.emplace_back(report);\n}\n\nvoid\nTrafficShapingEngine::ProcessAllQueuedReports()\n{\n  std::vector<eos::traffic_shaping::FstIoReport> local_queue;\n  {\n    std::lock_guard lock(mReportQueueMutex);\n    std::swap(mReportQueue, local_queue);\n  }\n\n  if (mManager == nullptr) {\n    return;\n  }\n\n  for (const auto& report : local_queue) {\n    mManager->ProcessReport(report);\n  }\n\n  mManager->UpdateFstReportsProcessed(local_queue.size());\n}\n\nvoid\nTrafficShapingEngine::EstimatorsUpdate(ThreadAssistant& assistant)\n{\n  eos_static_info(\"%s\", \"msg=\\\"Starting Traffic Shaping estimators update thread\\\"\");\n\n  auto last_run = std::chrono::steady_clock::now();\n\n  int infrequent_action_counter = 0;\n  constexpr int infrequent_action_threshold = 100;\n\n  while (!assistant.terminationRequested()) {\n    assistant.wait_for(\n        std::chrono::milliseconds(mEstimatorsUpdateThreadPeriodMilliseconds));\n\n    if (assistant.terminationRequested()) {\n      break;\n    }\n\n    ProcessAllQueuedReports();\n\n    auto now = std::chrono::steady_clock::now();\n    const std::chrono::duration<double> elapsed = now - last_run;\n    const double time_delta_seconds = elapsed.count();\n    last_run = now;\n\n    mManager->UpdateEstimators(time_delta_seconds);\n\n    if (++infrequent_action_counter >= infrequent_action_threshold) {\n      infrequent_action_counter = 0;\n      const auto [removed_nodes, removed_node_streams, removed_global_streams] =\n          mManager->GarbageCollect(900);\n\n      if (removed_node_streams > 0 || removed_global_streams > 0) {\n        eos_static_debug(\"msg=\\\"Traffic Shaping Garbage Collection\\\" removed_nodes=%lu \"\n                         \"removed_node_streams=%lu \"\n                         \"removed_global_streams=%lu\",\n                         removed_nodes, removed_node_streams, removed_global_streams);\n      }\n    }\n\n    auto work_done = std::chrono::steady_clock::now();\n    const auto work_duration_micro_sec =\n        std::chrono::duration_cast<std::chrono::microseconds>(work_done - now).count();\n\n    if (static_cast<double>(work_duration_micro_sec) >\n        static_cast<double>(mEstimatorsUpdateThreadPeriodMilliseconds) * 0.1 * 1000.0) {\n      eos_static_warning(\n          \"msg=\\\"Traffic Shaping Estimators Update loop is slow\\\" work_duration_ms=%.2f\",\n          static_cast<double>(work_duration_micro_sec) / 1000.0);\n    }\n\n    mManager->UpdateEstimatorsLoopMicroSec(work_duration_micro_sec);\n  }\n}\n\nvoid\nTrafficShapingEngine::FstIoPolicyUpdate(ThreadAssistant& assistant) const\n{\n  eos_static_info(\"%s\", \"msg=\\\"Starting FstIoPolicyUpdate thread\\\"\");\n\n  while (!assistant.terminationRequested()) {\n    assistant.wait_for(\n        std::chrono::milliseconds(mFstIoPolicyUpdateThreadPeriodMilliseconds));\n\n    if (assistant.terminationRequested()) {\n      break;\n    }\n\n    auto work_start_time = std::chrono::steady_clock::now();\n\n    mManager->UpdateTrafficShapingController();\n\n    mManager->UpdateLimits();\n\n    auto work_end_time = std::chrono::steady_clock::now();\n    const auto compute_duration_us =\n        std::chrono::duration_cast<std::chrono::microseconds>(work_end_time -\n                                                              work_start_time)\n            .count();\n\n    mManager->UpdateFstLimitsLoopMicroSec(compute_duration_us);\n  }\n}\n\nvoid\nTrafficShapingEngine::FstTrafficShapingConfigUpdate(ThreadAssistant& assistant)\n{\n  while (!assistant.terminationRequested()) {\n    assistant.wait_for(std::chrono::seconds(5));\n\n    if (assistant.terminationRequested()) {\n      break;\n    }\n\n    SyncTrafficShapingEnabledWithFst();\n  }\n}\n\nvoid\nTrafficShapingEngine::Enable()\n{\n  FsView::gFsView.SetGlobalConfig(common::TRAFFIC_SHAPING_ENABLE_CONFIG, true);\n  gOFS->mConfigEngine->AutoSave();\n  Start();\n}\n\nvoid\nTrafficShapingEngine::Disable()\n{\n  FsView::gFsView.SetGlobalConfig(common::TRAFFIC_SHAPING_ENABLE_CONFIG, false);\n  gOFS->mConfigEngine->AutoSave();\n  Stop();\n}\n\nvoid\nTrafficShapingEngine::SyncTrafficShapingEnabledWithFst()\n{\n  const bool enabled = mRunning;\n  const std::string enabled_str = enabled ? \"true\" : \"false\";\n  const std::string period_str =\n      std::to_string(mFstIoStatsReportThreadPeriodMilliseconds);\n\n  std::vector<std::string> online_nodes;\n  {\n    eos::common::RWMutexReadLock viewlock(FsView::gFsView.ViewMutex);\n    for (const auto& [node_name, node_view] : FsView::gFsView.mNodeView) {\n      if (node_view->GetStatus() == \"online\") {\n        online_nodes.push_back(node_name);\n      }\n    }\n  }\n\n  for (const auto& node_name : online_nodes) {\n    eos::common::RWMutexReadLock viewlock(FsView::gFsView.ViewMutex);\n    auto it = FsView::gFsView.mNodeView.find(node_name);\n    if (it != FsView::gFsView.mNodeView.end()) {\n      it->second->SetConfigMember(eos::common::FST_TRAFFIC_SHAPING_ENABLE_TOGGLE,\n                                  enabled_str, true);\n      it->second->SetConfigMember(eos::common::FST_TRAFFIC_SHAPING_STATS_THREAD_PERIOD,\n                                  period_str, true);\n    }\n  }\n}\n\nvoid\nTrafficShapingManager::SetUidPolicy(const uint32_t uid,\n                                    const TrafficShapingPolicy& policy)\n{\n  bool config_changed = false;\n  std::string serialized;\n  {\n    std::unique_lock lock(mMutex);\n    const auto it = mUidPolicies.find(uid);\n\n    if (policy.IsEmpty()) {\n      if (it != mUidPolicies.end()) {\n        mUidPolicies.erase(it);\n        config_changed = true;\n        eos_static_info(\"msg=\\\"Removed empty UID Traffic Shaping Policy\\\" uid=%u\", uid);\n      }\n    } else {\n      if (it == mUidPolicies.end()) {\n        mUidPolicies[uid] = policy;\n        config_changed = true;\n        eos_static_info(\"msg=\\\"Set UID Traffic Shaping Policy\\\" uid=%u policy=%s\", uid,\n                        policy.ToString().c_str());\n      } else {\n        // operator!= ignores controller limits, so it only flags true user config changes\n        if (it->second != policy) {\n          config_changed = true;\n        }\n        // Always update in-memory to reflect any potential ephemeral controller limit\n        // changes\n        it->second = policy;\n        eos_static_info(\"msg=\\\"Updated UID Traffic Shaping Policy\\\" uid=%u policy=%s \"\n                        \"persistent_changed=%d\",\n                        uid, policy.ToString().c_str(), config_changed);\n      }\n    }\n\n    if (config_changed) {\n      serialized = SerializePoliciesUnlocked();\n    }\n  }\n\n  if (config_changed) {\n    FsView::gFsView.SetGlobalConfig(common::TRAFFIC_SHAPING_POLICIES_CONFIG, serialized);\n    gOFS->mConfigEngine->AutoSave();\n  }\n}\n\nvoid\nTrafficShapingManager::SetGidPolicy(const uint32_t gid,\n                                    const TrafficShapingPolicy& policy)\n{\n  bool config_changed = false;\n  std::string serialized;\n  {\n    std::unique_lock lock(mMutex);\n    auto it = mGidPolicies.find(gid);\n\n    if (policy.IsEmpty()) {\n      if (it != mGidPolicies.end()) {\n        mGidPolicies.erase(it);\n        config_changed = true;\n        eos_static_info(\"msg=\\\"Removed empty GID Traffic Shaping Policy\\\" gid=%u\", gid);\n      }\n    } else {\n      if (it == mGidPolicies.end()) {\n        mGidPolicies[gid] = policy;\n        config_changed = true;\n        eos_static_info(\"msg=\\\"Set GID Traffic Shaping Policy\\\" gid=%u policy=%s\", gid,\n                        policy.ToString().c_str());\n      } else {\n        if (it->second != policy) {\n          config_changed = true;\n        }\n        it->second = policy;\n        eos_static_info(\"msg=\\\"Updated GID Traffic Shaping Policy\\\" gid=%u policy=%s \"\n                        \"persistent_changed=%d\",\n                        gid, policy.ToString().c_str(), config_changed);\n      }\n    }\n\n    if (config_changed) {\n      serialized = SerializePoliciesUnlocked();\n    }\n  }\n\n  if (config_changed) {\n    FsView::gFsView.SetGlobalConfig(common::TRAFFIC_SHAPING_POLICIES_CONFIG, serialized);\n    gOFS->mConfigEngine->AutoSave();\n  }\n}\n\nvoid\nTrafficShapingManager::SetAppPolicy(const std::string& app,\n                                    const TrafficShapingPolicy& policy)\n{\n  bool config_changed = false;\n  std::string serialized;\n  {\n    std::unique_lock lock(mMutex);\n    const auto it = mAppPolicies.find(app);\n\n    if (policy.IsEmpty()) {\n      if (it != mAppPolicies.end()) {\n        mAppPolicies.erase(it);\n        config_changed = true;\n        eos_static_info(\"msg=\\\"Removed empty App Traffic Shaping Policy\\\" app=%s\",\n                        app.c_str());\n      }\n    } else {\n      if (it == mAppPolicies.end()) {\n        mAppPolicies[app] = policy;\n        config_changed = true;\n        eos_static_info(\"msg=\\\"Set App Traffic Shaping Policy\\\" app=%s policy=%s\",\n                        app.c_str(), policy.ToString().c_str());\n      } else {\n        if (it->second != policy) {\n          config_changed = true;\n        }\n        it->second = policy;\n        eos_static_info(\"msg=\\\"Updated App Traffic Shaping Policy\\\" app=%s policy=%s \"\n                        \"persistent_changed=%d\",\n                        app.c_str(), policy.ToString().c_str(), config_changed);\n      }\n    }\n\n    if (config_changed) {\n      serialized = SerializePoliciesUnlocked();\n    }\n  }\n\n  if (config_changed) {\n    FsView::gFsView.SetGlobalConfig(common::TRAFFIC_SHAPING_POLICIES_CONFIG, serialized);\n    gOFS->mConfigEngine->AutoSave();\n  }\n}\n\nvoid\nTrafficShapingManager::RemoveUidPolicy(const uint32_t uid)\n{\n  std::string serialized;\n  {\n    std::unique_lock lock(mMutex);\n    if (!mUidPolicies.erase(uid)) {\n      return;\n    }\n    eos_static_info(\"msg=\\\"Removed UID Traffic Shaping Policy\\\" uid=%u\", uid);\n    serialized = SerializePoliciesUnlocked();\n  }\n  FsView::gFsView.SetGlobalConfig(common::TRAFFIC_SHAPING_POLICIES_CONFIG, serialized);\n  gOFS->mConfigEngine->AutoSave();\n}\n\nvoid\nTrafficShapingManager::RemoveGidPolicy(const uint32_t gid)\n{\n  std::string serialized;\n  {\n    std::unique_lock lock(mMutex);\n    if (!mGidPolicies.erase(gid)) {\n      return;\n    }\n    eos_static_info(\"msg=\\\"Removed GID Traffic Shaping Policy\\\" gid=%u\", gid);\n    serialized = SerializePoliciesUnlocked();\n  }\n  FsView::gFsView.SetGlobalConfig(common::TRAFFIC_SHAPING_POLICIES_CONFIG, serialized);\n  gOFS->mConfigEngine->AutoSave();\n}\n\nvoid\nTrafficShapingManager::RemoveAppPolicy(const std::string& app)\n{\n  std::string serialized;\n  {\n    std::unique_lock lock(mMutex);\n    if (!mAppPolicies.erase(app)) {\n      return;\n    }\n    eos_static_info(\"msg=\\\"Removed App Traffic Shaping Policy\\\" app=%s\", app.c_str());\n    serialized = SerializePoliciesUnlocked();\n  }\n  FsView::gFsView.SetGlobalConfig(common::TRAFFIC_SHAPING_POLICIES_CONFIG, serialized);\n  gOFS->mConfigEngine->AutoSave();\n}\n\nstd::unordered_map<uint32_t, TrafficShapingPolicy>\nTrafficShapingManager::GetUidPolicies() const\n{\n  std::shared_lock lock(mMutex);\n  return mUidPolicies;\n}\n\nstd::unordered_map<uint32_t, TrafficShapingPolicy>\nTrafficShapingManager::GetGidPolicies() const\n{\n  std::shared_lock lock(mMutex);\n  return mGidPolicies;\n}\n\nstd::unordered_map<std::string, TrafficShapingPolicy>\nTrafficShapingManager::GetAppPolicies() const\n{\n  std::shared_lock lock(mMutex);\n  return mAppPolicies;\n}\n\nstd::optional<TrafficShapingPolicy>\nTrafficShapingManager::GetUidPolicy(const uint32_t uid) const\n{\n  std::shared_lock lock(mMutex);\n  if (const auto it = mUidPolicies.find(uid); it != mUidPolicies.end()) {\n    return it->second;\n  }\n  return std::nullopt;\n}\n\nstd::optional<TrafficShapingPolicy>\nTrafficShapingManager::GetGidPolicy(const uint32_t gid) const\n{\n  std::shared_lock lock(mMutex);\n  if (const auto it = mGidPolicies.find(gid); it != mGidPolicies.end()) {\n    return it->second;\n  }\n  return std::nullopt;\n}\n\nstd::optional<TrafficShapingPolicy>\nTrafficShapingManager::GetAppPolicy(const std::string& app) const\n{\n  std::shared_lock lock(mMutex);\n  if (const auto it = mAppPolicies.find(app); it != mAppPolicies.end()) {\n    return it->second;\n  }\n  return std::nullopt;\n}\n\nstd::string\nTrafficShapingManager::SerializePoliciesUnlocked() const\n{\n  eos::traffic_shaping::TrafficShapingPolicyConfig proto_config;\n\n  auto copy_to_proto = [](const TrafficShapingPolicy& cpp_pol,\n                          eos::traffic_shaping::TrafficShapingPolicy& proto_pol) {\n    proto_pol.set_limit_write_bytes_per_sec(cpp_pol.limit_write_bytes_per_sec);\n    proto_pol.set_limit_read_bytes_per_sec(cpp_pol.limit_read_bytes_per_sec);\n    proto_pol.set_reservation_write_bytes_per_sec(\n        cpp_pol.reservation_write_bytes_per_sec);\n    proto_pol.set_reservation_read_bytes_per_sec(cpp_pol.reservation_read_bytes_per_sec);\n    proto_pol.set_is_enabled(cpp_pol.is_enabled);\n  };\n\n  for (const auto& [uid, pol] : mUidPolicies) {\n    copy_to_proto(pol, (*proto_config.mutable_uid_policies())[uid]);\n  }\n  for (const auto& [gid, pol] : mGidPolicies) {\n    copy_to_proto(pol, (*proto_config.mutable_gid_policies())[gid]);\n  }\n  for (const auto& [app, pol] : mAppPolicies) {\n    copy_to_proto(pol, (*proto_config.mutable_app_policies())[app]);\n  }\n\n  std::string json_data;\n  google::protobuf::util::JsonPrintOptions options;\n  options.add_whitespace = false;\n\n  if (const auto status =\n          google::protobuf::util::MessageToJsonString(proto_config, &json_data, options);\n      !status.ok()) {\n    eos_static_err(\"msg=\\\"Failed to serialize policies to JSON\\\"\");\n  }\n\n  return json_data;\n}\n\nbool\nTrafficShapingManager::LoadPoliciesFromString(const std::string& serialized_policies)\n{\n  if (serialized_policies.empty()) {\n    return true;\n  }\n\n  std::unordered_map<uint32_t, TrafficShapingPolicy> new_uid_policies;\n  std::unordered_map<uint32_t, TrafficShapingPolicy> new_gid_policies;\n  std::unordered_map<std::string, TrafficShapingPolicy> new_app_policies;\n\n  std::unique_lock lock(mMutex);\n\n  // Parse under mMutex so that concurrent Set/RemovePolicy calls cannot land\n  // between parsing and map replacement, which would cause their updates to be lost.\n  eos::traffic_shaping::TrafficShapingPolicyConfig proto_config;\n\n  google::protobuf::util::JsonParseOptions options;\n  options.ignore_unknown_fields = true;\n\n  const auto status = google::protobuf::util::JsonStringToMessage(serialized_policies,\n                                                                  &proto_config, options);\n  if (!status.ok()) {\n    eos_static_err(\"msg=\\\"Failed to parse policies from JSON string\\\"\");\n    return false;\n  }\n\n  auto merge_policies = [](auto& current_map, const auto& proto_map, auto& new_map) {\n    auto copy_to_cpp = [](const auto& proto_pol) -> TrafficShapingPolicy {\n      TrafficShapingPolicy cpp_pol;\n      cpp_pol.limit_write_bytes_per_sec = proto_pol.limit_write_bytes_per_sec();\n      cpp_pol.limit_read_bytes_per_sec = proto_pol.limit_read_bytes_per_sec();\n      cpp_pol.reservation_write_bytes_per_sec =\n          proto_pol.reservation_write_bytes_per_sec();\n      cpp_pol.reservation_read_bytes_per_sec = proto_pol.reservation_read_bytes_per_sec();\n      cpp_pol.is_enabled = proto_pol.is_enabled();\n      return cpp_pol;\n    };\n\n    for (const auto& [id, pol] : proto_map) {\n      auto cpp_pol = copy_to_cpp(pol);\n      if (auto it = current_map.find(id); it != current_map.end()) {\n        cpp_pol.controller_limit_read_bytes_per_sec =\n            it->second.controller_limit_read_bytes_per_sec;\n        cpp_pol.controller_limit_write_bytes_per_sec =\n            it->second.controller_limit_write_bytes_per_sec;\n      }\n      new_map[id] = cpp_pol;\n    }\n\n    // 2. Retain Python-generated ephemeral policies (policies with ONLY controller\n    // limits) that might have been skipped by the DB serialization\n    TrafficShapingPolicy empty_user_pol; // All user fields are 0/disabled\n    for (const auto& [id, pol] : current_map) {\n      if (new_map.find(id) == new_map.end()) {\n        // We use operator== because it deliberately ignores controller fields.\n        // If this evaluates to true, it means NO user settings exist for this policy.\n        if (pol == empty_user_pol && (pol.controller_limit_read_bytes_per_sec > 0 ||\n                                      pol.controller_limit_write_bytes_per_sec > 0)) {\n          new_map[id] = pol;\n        }\n      }\n    }\n  };\n\n  merge_policies(mUidPolicies, proto_config.uid_policies(), new_uid_policies);\n  merge_policies(mGidPolicies, proto_config.gid_policies(), new_gid_policies);\n  merge_policies(mAppPolicies, proto_config.app_policies(), new_app_policies);\n\n  mUidPolicies = std::move(new_uid_policies);\n  mGidPolicies = std::move(new_gid_policies);\n  mAppPolicies = std::move(new_app_policies);\n\n  return true;\n}\n\nvoid\nTrafficShapingManager::LoadPluginIfModified()\n{\n  const char* plugin_path = \"/etc/eos/traffic_shaping_plugin.so\";\n  struct stat file_stat;\n\n  // 1. Check if the hot-reload file exists on disk\n  if (stat(plugin_path, &file_stat) == 0) {\n    // 2. Check if the file has been updated since our last load\n    if (file_stat.st_mtime > mPluginLastModified) {\n      eos_static_info(\"msg=\\\"Detected new or modified traffic shaping plugin. \"\n                      \"Loading...\\\" path=\\\"%s\\\"\",\n                      plugin_path);\n\n      // ACQUIRE EXCLUSIVE WRITE LOCK: Wait for any active evaluation threads to finish\n      std::unique_lock write_lock(mPluginMutex);\n\n      // Close the old plugin if it exists\n      if (mPluginHandle) {\n        dlclose(mPluginHandle);\n        mPluginHandle = nullptr;\n        mCustomAlgo = nullptr;\n        mCustomControllerAlgo = nullptr;\n      }\n\n      // Open the new plugin\n      mPluginHandle = dlopen(plugin_path, RTLD_NOW | RTLD_LOCAL);\n      if (mPluginHandle) {\n        dlerror(); // Clear any existing errors\n\n        // Find our specific C functions\n        mCustomAlgo = (DelayAlgoFunc)dlsym(mPluginHandle, \"calculate_delay\");\n        mCustomControllerAlgo =\n            (ControllerAlgoFunc)dlsym(mPluginHandle, \"update_controller\");\n\n        // We require at least one symbol to be present to consider the load a success\n        if (!mCustomAlgo && !mCustomControllerAlgo) {\n          eos_static_err(\"msg=\\\"Failed to find any matching symbols ('calculate_delay' \"\n                         \"or 'update_controller') in plugin! \"\n                         \"Falling back to built-in.\\\" error=\\\"%s\\\"\",\n                         dlerror());\n          dlclose(mPluginHandle);\n          mPluginHandle = nullptr;\n          mCustomAlgo = nullptr;\n          mCustomControllerAlgo = nullptr;\n        } else {\n          eos_static_info(\"msg=\\\"Successfully loaded and activated custom traffic \"\n                          \"shaping plugin\\\"\");\n        }\n      } else {\n        eos_static_err(\"msg=\\\"Failed to load traffic shaping plugin. Falling back to \"\n                       \"built-in.\\\" error=\\\"%s\\\"\",\n                       dlerror());\n      }\n\n      // Update our timestamp so we don't reload it again until it changes\n      mPluginLastModified = file_stat.st_mtime;\n    }\n  } else {\n    // 3. File doesn't exist. If we have one loaded, the user deleted it. Revert to\n    // built-in.\n    if (mPluginHandle) {\n      eos_static_info(\"msg=\\\"Traffic shaping plugin file removed. Reverting to \"\n                      \"built-in algorithm.\\\"\");\n\n      std::unique_lock write_lock(mPluginMutex);\n\n      dlclose(mPluginHandle);\n      mPluginHandle = nullptr;\n      mCustomAlgo = nullptr;\n      mCustomControllerAlgo = nullptr;\n      mPluginLastModified = 0;\n    }\n  }\n}\n\n} // namespace eos::mgm::traffic_shaping\n"
  },
  {
    "path": "mgm/shaping/TrafficShaping.hh",
    "content": "#pragma once\n\n#include \"common/AssistedThread.hh\"\n#include \"common/shaping/IoStatsKey.hh\"\n#include \"common/shaping/SlidingWindowStats.hh\"\n#include \"proto/TrafficShaping.pb.h\"\n\n#include <atomic>\n#include <memory>\n#include <optional>\n#include <shared_mutex>\n#include <string>\n#include <thread>\n#include <unordered_map>\n#include <vector>\n\nnamespace eos::mgm::traffic_shaping {\n\nextern \"C\" {\n// Function signatures for the hot-reloaded plugin\ntypedef uint64_t (*DelayAlgoFunc)(double limit_bps, double current_rate_bps,\n                                  uint64_t current_delay_us);\n\n// A flat, simple struct containing all inputs and outputs for ONE app\nstruct AppState {\n  char app_name[128]; // Fixed size so we don't mess with pointers\n\n  // Inputs from MGM -> Plugin\n  double current_read_bps;\n  double current_write_bps;\n  uint64_t reservation_write_bps;\n  uint64_t reservation_read_bps;\n  uint64_t controller_limit_write_bps;\n  uint64_t controller_limit_read_bps;\n\n  // Outputs from Plugin -> MGM\n  uint64_t new_controller_limit_write_bps;\n  uint64_t new_controller_limit_read_bps;\n  bool update_write; // Set to true if the plugin wants to apply the new write limit\n  bool update_read;  // Set to true if the plugin wants to apply the new read limit\n};\n\n// Pass the pure data array to avoid C++ ABI name mangling and linking issues\ntypedef void (*ControllerAlgoFunc)(AppState* apps, size_t num_apps);\n}\n\nstruct StreamState {\n  uint64_t last_bytes_read = 0;\n  uint64_t last_bytes_written = 0;\n  uint64_t last_iops_read = 0;\n  uint64_t last_iops_write = 0;\n\n  uint64_t generation_id = 0;\n\n  std::chrono::steady_clock::time_point last_update_time{};\n};\n\nstruct RateMetrics {\n  double read_rate_bps = 0.0;\n  double write_rate_bps = 0.0;\n  double read_iops = 0.0;\n  double write_iops = 0.0;\n};\n\nconstexpr std::array<int, 2> EmaWindowSec = {1, 5};\nconstexpr std::array<int, 5> SmaWindowSec = {1, 5, 15, 60, 300};\n\nenum EmaIdx : size_t { Ema1s = 0, Ema5s = 1 };\n\nenum SmaIdx : size_t { Sma1s = 0, Sma5s = 1, Sma15s = 2, Sma1m = 3, Sma5m = 4 };\n\nconstexpr uint32_t kMinThreadPeriodMs = 50;\nconstexpr uint32_t kMaxThreadPeriodMs = 3000;\nconstexpr uint32_t kMinSystemStatsWindowSec = 5;\nconstexpr uint32_t kMaxSystemStatsWindowSec = 300;\n\nstruct MultiWindowRate {\n  double tick_interval_seconds;\n  const double sma_max_history_seconds =\n      *std::max_element(SmaWindowSec.begin(), SmaWindowSec.end());\n\n  std::atomic<uint64_t> bytes_read_accumulator{0};\n  std::atomic<uint64_t> bytes_written_accumulator{0};\n  std::atomic<uint64_t> read_iops_accumulator{0};\n  std::atomic<uint64_t> write_iops_accumulator{0};\n\n  std::array<RateMetrics, EmaWindowSec.size()> ema{};\n  std::array<RateMetrics, SmaWindowSec.size()> sma{};\n\n  eos::common::traffic_shaping::SlidingWindowStats bytes_read_window;\n  eos::common::traffic_shaping::SlidingWindowStats bytes_written_window;\n  eos::common::traffic_shaping::SlidingWindowStats iops_read_window;\n  eos::common::traffic_shaping::SlidingWindowStats iops_write_window;\n\n  uint32_t active_stream_count = 0;\n  time_t last_activity_time = 0;\n\n  explicit MultiWindowRate(const double initial_tick_interval)\n      : tick_interval_seconds(initial_tick_interval)\n      , bytes_read_window(sma_max_history_seconds, initial_tick_interval)\n      , bytes_written_window(sma_max_history_seconds, initial_tick_interval)\n      , iops_read_window(sma_max_history_seconds, initial_tick_interval)\n      , iops_write_window(sma_max_history_seconds, initial_tick_interval)\n  {\n  }\n\n  void\n  clear()\n  {\n    bytes_read_accumulator.store(0, std::memory_order_relaxed);\n    bytes_written_accumulator.store(0, std::memory_order_relaxed);\n    read_iops_accumulator.store(0, std::memory_order_relaxed);\n    write_iops_accumulator.store(0, std::memory_order_relaxed);\n\n    for (auto& m : ema) {\n      m = RateMetrics{};\n    }\n\n    for (auto& m : sma) {\n      m = RateMetrics{};\n    }\n\n    ResetWindows(tick_interval_seconds);\n\n    active_stream_count = 0;\n    last_activity_time = 0;\n  }\n\n  void\n  ResetWindows(double new_tick_interval)\n  {\n    tick_interval_seconds = new_tick_interval;\n    bytes_read_window = eos::common::traffic_shaping::SlidingWindowStats(\n        sma_max_history_seconds, tick_interval_seconds);\n    bytes_written_window = eos::common::traffic_shaping::SlidingWindowStats(\n        sma_max_history_seconds, tick_interval_seconds);\n    iops_read_window = eos::common::traffic_shaping::SlidingWindowStats(\n        sma_max_history_seconds, tick_interval_seconds);\n    iops_write_window = eos::common::traffic_shaping::SlidingWindowStats(\n        sma_max_history_seconds, tick_interval_seconds);\n\n    for (auto& s : sma) {\n      s = RateMetrics{};\n    }\n  }\n};\n\nstruct RateSnapshot {\n  uint64_t bytes_read_accumulator = 0;\n  uint64_t bytes_written_accumulator = 0;\n\n  std::array<RateMetrics, EmaWindowSec.size()> ema{};\n  std::array<RateMetrics, SmaWindowSec.size()> sma{};\n\n  uint32_t active_stream_count = 0;\n  time_t last_activity_time = 0;\n};\n\nusing StreamKey = eos::common::traffic_shaping::IoStatsKey;\nusing StreamKeyHash = eos::common::traffic_shaping::IoStatsKeyHash;\n\nstruct TrafficShapingPolicy {\n  // --- Persistent User Configuration ---\n  uint64_t limit_write_bytes_per_sec = 0;\n  uint64_t limit_read_bytes_per_sec = 0;\n  uint64_t reservation_write_bytes_per_sec = 0;\n  uint64_t reservation_read_bytes_per_sec = 0;\n\n  bool is_enabled = true;\n\n  // --- Ephemeral Controller Configuration ---\n  uint64_t controller_limit_write_bytes_per_sec = 0;\n  uint64_t controller_limit_read_bytes_per_sec = 0;\n\n  uint64_t GetEffectiveWriteLimit() const;\n\n  uint64_t GetEffectiveReadLimit() const;\n\n  bool IsEmpty() const;\n\n  bool IsActive() const;\n\n  bool operator==(const TrafficShapingPolicy& policy) const;\n\n  bool operator!=(const TrafficShapingPolicy& policy) const;\n\n  std::string ToString() const;\n};\n\nclass TrafficShapingManager {\npublic:\n  TrafficShapingManager();\n\n  ~TrafficShapingManager();\n\n  void ProcessReport(const eos::traffic_shaping::FstIoReport& report);\n\n  void UpdateEstimators(double time_delta_seconds);\n\n  void UpdateLimits();\n\n  void UpdateTrafficShapingController();\n\n  void ApplyThreadConfig(uint32_t estimators_period_ms, uint32_t fst_policy_period_ms,\n                         uint32_t window_seconds);\n\n  std::unordered_map<StreamKey, RateSnapshot, StreamKeyHash> GetGlobalStats() const;\n\n  std::unordered_map<std::string, RateSnapshot> GetNodeStats() const;\n\n  RateSnapshot GetTotalStats() const;\n\n  struct GarbageCollectionStats {\n    size_t removed_nodes;\n    size_t removed_node_streams;\n    size_t removed_global_streams;\n  };\n\n  GarbageCollectionStats GarbageCollect(int max_idle_seconds);\n\n  void SetUidPolicy(uint32_t uid, const TrafficShapingPolicy& policy);\n\n  void SetGidPolicy(uint32_t gid, const TrafficShapingPolicy& policy);\n\n  void SetAppPolicy(const std::string& app, const TrafficShapingPolicy& policy);\n\n  void RemoveUidPolicy(uint32_t uid);\n\n  void RemoveGidPolicy(uint32_t gid);\n\n  void RemoveAppPolicy(const std::string& app);\n\n  std::string SerializePoliciesUnlocked() const;\n\n  bool LoadPoliciesFromString(const std::string& serialized_policies);\n\n  std::unordered_map<uint32_t, TrafficShapingPolicy> GetUidPolicies() const;\n\n  std::unordered_map<uint32_t, TrafficShapingPolicy> GetGidPolicies() const;\n\n  std::unordered_map<std::string, TrafficShapingPolicy> GetAppPolicies() const;\n\n  std::optional<TrafficShapingPolicy> GetUidPolicy(uint32_t uid) const;\n\n  std::optional<TrafficShapingPolicy> GetGidPolicy(uint32_t gid) const;\n\n  std::optional<TrafficShapingPolicy> GetAppPolicy(const std::string& app) const;\n\n  void UpdateFstLimitsLoopMicroSec(uint64_t time_microseconds);\n\n  void UpdateEstimatorsLoopMicroSec(uint64_t time_microseconds);\n\n  void UpdateFstReportsProcessed(uint64_t count);\n\n  double GetFstReportsProcessedPerSecondMean() const;\n\n  std::tuple<uint64_t, uint64_t, uint64_t> GetEstimatorsUpdateLoopMicroSecStats() const;\n\n  std::tuple<uint64_t, uint64_t, uint64_t> GetFstLimitsUpdateLoopMicroSecStats() const;\n\n  uint32_t\n  GetSystemStatsWindowSeconds() const\n  {\n    std::shared_lock lock(mMutex);\n    return mSystemStatsWindowSeconds;\n  }\n\n  std::tuple<std::unordered_map<std::string, double>, // app read\n             std::unordered_map<std::string, double>, // app write\n             std::unordered_map<uint32_t, double>,    // uid read\n             std::unordered_map<uint32_t, double>,    // uid write\n             std::unordered_map<uint32_t, double>,    // gid read\n             std::unordered_map<uint32_t, double>>    // gid write\n  GetCurrentReadAndWriteRates() const;\n\n  void Clear();\n\nprivate:\n  using NodeStateMap = std::unordered_map<StreamKey, StreamState, StreamKeyHash>;\n\n  struct NodeData {\n    std::chrono::steady_clock::time_point last_report_time{};\n    NodeStateMap streams;\n  };\n\n  std::unordered_map<std::string, NodeData> mNodeStates;\n  std::unordered_map<StreamKey, MultiWindowRate, StreamKeyHash> mGlobalStats;\n  std::unordered_map<std::string, MultiWindowRate> mNodeStats;\n  // We provide an initial tick interval but this will be refreshed on initialization\n  MultiWindowRate mTotalStats{0.5};\n\n  std::unordered_map<uint32_t, TrafficShapingPolicy> mUidPolicies;\n  std::unordered_map<uint32_t, TrafficShapingPolicy> mGidPolicies;\n  std::unordered_map<std::string, TrafficShapingPolicy> mAppPolicies;\n\n  eos::traffic_shaping::TrafficShapingFstIoDelayConfig mFstIoDelayConfig;\n\n  std::optional<eos::common::traffic_shaping::SlidingWindowStats>\n      estimators_update_loop_micro_sec;\n  std::optional<eos::common::traffic_shaping::SlidingWindowStats>\n      fst_limits_update_loop_micro_sec;\n  std::optional<eos::common::traffic_shaping::SlidingWindowStats>\n      fst_reports_processed_per_second;\n\n  double mEstimatorsTickIntervalSec{0.5};\n  uint32_t mSystemStatsWindowSeconds{15};\n\n  mutable std::shared_mutex mMutex;\n\n  static double CalculateEma(double current_val, double prev_ema, double alpha);\n\n  // Calculates the new FST delay microsecond value given the current rate and limit\n  static uint64_t CalculateDelayUs(double limit_bps, double current_rate_bps,\n                                   uint64_t current_delay_us);\n\n  // --- Plugin Hot-Reload State ---\n  void* mPluginHandle = nullptr;\n  DelayAlgoFunc mCustomAlgo = nullptr;\n  ControllerAlgoFunc mCustomControllerAlgo = nullptr;\n  time_t mPluginLastModified = 0;\n  std::shared_mutex mPluginMutex;\n\n  void LoadPluginIfModified();\n};\n\nclass TrafficShapingEngine {\npublic:\n  TrafficShapingEngine();\n\n  ~TrafficShapingEngine();\n\n  void ApplyConfig();\n\n  void Start();\n\n  void Stop();\n\n  void Enable();\n\n  void Disable();\n\n  bool\n  IsEnabled() const\n  {\n    return mRunning;\n  }\n\n  void SyncTrafficShapingEnabledWithFst();\n\n  std::shared_ptr<TrafficShapingManager> GetManager() const;\n\n  void ProcessSerializedFstIoReportNonBlocking(const std::string& serialized_report);\n\n  void ApplyThreadConfig(uint32_t est_ms, uint32_t pol_ms, uint32_t rep_ms,\n                         uint32_t win_s, bool save_to_config_engine = true);\n\n  uint32_t\n  GetEstimatorsUpdateThreadPeriodMilliseconds() const\n  {\n    return mEstimatorsUpdateThreadPeriodMilliseconds.load();\n  }\n\n  uint32_t\n  GetFstIoPolicyUpdateThreadPeriodMilliseconds() const\n  {\n    return mFstIoPolicyUpdateThreadPeriodMilliseconds.load();\n  }\n\n  uint32_t\n  GetFstIoStatsReportThreadPeriodMilliseconds() const\n  {\n    return mFstIoStatsReportThreadPeriodMilliseconds.load();\n  }\n\n  uint32_t\n  GetSystemStatsWindowSeconds() const\n  {\n    return mSystemStatsWindowSeconds.load();\n  }\n\n  void SetEstimatorsUpdateThreadPeriodMilliseconds(uint32_t period_ms);\n\n  void SetFstIoPolicyUpdateThreadPeriodMilliseconds(uint32_t period_ms);\n\n  void SetFstIoStatsReportThreadPeriodMilliseconds(uint32_t period_ms);\n\n  void SetSystemStatsWindowSeconds(uint32_t window_seconds);\n\nprivate:\n  void EstimatorsUpdate(ThreadAssistant&);\n\n  void FstIoPolicyUpdate(ThreadAssistant&) const;\n\n  void FstTrafficShapingConfigUpdate(ThreadAssistant&);\n\n  void AddReportToQueue(const eos::traffic_shaping::FstIoReport& report);\n\n  void ProcessAllQueuedReports();\n\n  void UpdateThreadConfigs();\n\n  std::shared_ptr<TrafficShapingManager> mManager{};\n\n  AssistedThread mEstimatorsUpdateThread;\n  AssistedThread mFstIoPolicyUpdateThread;\n  AssistedThread mFstTrafficShapingConfigUpdateThread;\n\n  std::atomic<bool> mRunning{};\n\n  std::atomic<uint32_t> mEstimatorsUpdateThreadPeriodMilliseconds{};\n  std::atomic<uint32_t> mFstIoPolicyUpdateThreadPeriodMilliseconds{};\n  std::atomic<uint32_t> mFstIoStatsReportThreadPeriodMilliseconds{};\n  std::atomic<uint32_t> mSystemStatsWindowSeconds{};\n\n  std::vector<eos::traffic_shaping::FstIoReport> mReportQueue{};\n  std::mutex mReportQueueMutex{};\n};\n\n} // namespace eos::mgm::traffic_shaping\n"
  },
  {
    "path": "mgm/stat/Stat.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Stat.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Mapping.hh\"\n#include \"common/table_formatter/TableFormatterBase.hh\"\n#include \"common/Statistics.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include <XrdOuc/XrdOucString.hh>\n\nEOSMGMNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\nvoid\nStat::Add(const char* tag, uid_t uid, gid_t gid, unsigned long val,\n          const std::string app)\n{\n  XrdSysMutexHelper lock(mMutex);\n  StatsUid[tag][uid] += val;\n  StatsGid[tag][gid] += val;\n  StatAvgUid[tag][uid].Add(val);\n  StatAvgGid[tag][gid].Add(val);\n\n  if (!app.empty()) {\n    StatsApp[tag][app] += val;\n    StatAvgApp[tag][app].Add(val);\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nStat::AddExt(const char* tag, uid_t uid, gid_t gid, unsigned long nsample,\n             const double& avgv, const double& minv, const double& maxv,\n             const std::string app)\n{\n  XrdSysMutexHelper lock(mMutex);\n  StatExtUid[tag][uid].Insert(nsample, avgv, minv, maxv);\n  StatExtGid[tag][gid].Insert(nsample, avgv, minv, maxv);\n\n  if (!app.empty()) {\n    StatExtApp[tag][app].Insert(nsample, avgv, minv, maxv);\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nStat::AddExec(const char* tag, float exectime)\n{\n  XrdSysMutexHelper lock(mMutex);\n  StatExec[tag].push_back(exectime);\n  CumulativeTimeExec[tag] += exectime;\n\n  // we average over 100 entries\n  if (StatExec[tag].size() > 100) {\n    StatExec[tag].pop_front();\n  }\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\nunsigned long long\nStat::GetTotal(const char* tag)\n{\n  google::sparse_hash_map<uid_t, unsigned long long>::const_iterator it;\n  unsigned long long val = 0;\n\n  if (!StatsUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatsUid[tag].begin(); it != StatsUid[tag].end(); ++it) {\n    val += it->second;\n  }\n\n  return val;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\ndouble\nStat::GetCumulativeExecTime(const char* tag)\n{\n  if (!CumulativeTimeExec.count(tag)) {\n    return 0.0;\n  }\n\n  return CumulativeTimeExec[tag];\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalAvg3600(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatAvg>::iterator it;\n  double val = 0;\n\n  if (!StatAvgUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatAvgUid[tag].begin(); it != StatAvgUid[tag].end(); ++it) {\n    val += it->second.GetAvg3600();\n  }\n\n  return val;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalNExt3600(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  unsigned long n = 0;\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    for (int i = 0; i < 3600; i++) {\n      n += it->second.n3600[i];\n    }\n  }\n\n  return (double) n;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalAvgExt3600(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double val = 0;\n  double totw = 0;\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    double w = 0;\n\n    for (int i = 0; i < 3600; i++) {\n      w += it->second.n3600[i];\n    }\n\n    totw += w;\n    val += it->second.GetAvg3600() * w;\n  }\n\n  return val / totw;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalMinExt3600(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double minval = std::numeric_limits<unsigned long>::max();\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    minval = std::min(minval, it->second.GetMin3600());\n  }\n\n  return minval;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalMaxExt3600(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double maxval = std::numeric_limits<unsigned long>::min();\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    maxval = std::max(maxval, it->second.GetMax3600());\n  }\n\n  return maxval;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalAvg300(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatAvg>::iterator it;\n  double val = 0;\n\n  if (!StatAvgUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatAvgUid[tag].begin(); it != StatAvgUid[tag].end(); ++it) {\n    val += it->second.GetAvg300();\n  }\n\n  return val;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalNExt300(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  unsigned long n = 0;\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    for (int i = 0; i < 300; i++) {\n      n += it->second.n300[i];\n    }\n  }\n\n  return (double) n;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalAvgExt300(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double val = 0;\n  double totw = 0;\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    double w = 0;\n\n    for (int i = 0; i < 300; i++) {\n      w += it->second.n300[i];\n    }\n\n    totw += w;\n    val += it->second.GetAvg300() * w;\n  }\n\n  return val / totw;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalMinExt300(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double minval = std::numeric_limits<unsigned long>::max();\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    minval = std::min(minval, it->second.GetMin300());\n  }\n\n  return minval;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalMaxExt300(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double maxval = std::numeric_limits<unsigned long>::min();\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    maxval = std::max(maxval, it->second.GetMax300());\n  }\n\n  return maxval;\n}\n\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalAvg60(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatAvg>::iterator it;\n  double val = 0;\n\n  if (!StatAvgUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatAvgUid[tag].begin(); it != StatAvgUid[tag].end(); ++it) {\n    val += it->second.GetAvg60();\n  }\n\n  return val;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalNExt60(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  unsigned long n = 0;\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    for (int i = 0; i < 60; i++) {\n      n += it->second.n60[i];\n    }\n  }\n\n  return (double) n;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalAvgExt60(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double val = 0;\n  double totw = 0;\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    double w = 0;\n\n    for (int i = 0; i < 60; i++) {\n      w += it->second.n60[i];\n    }\n\n    totw += w;\n    val += it->second.GetAvg60() * w;\n  }\n\n  return val / totw;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalMinExt60(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double minval = std::numeric_limits<unsigned long>::max();\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    minval = std::min(minval, it->second.GetMin60());\n  }\n\n  return minval;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalMaxExt60(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double maxval = std::numeric_limits<unsigned long>::min();\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    maxval = std::max(maxval, it->second.GetMax60());\n  }\n\n  return maxval;\n}\n\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalAvg5(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatAvg>::iterator it;\n  double val = 0;\n\n  if (!StatAvgUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatAvgUid[tag].begin(); it != StatAvgUid[tag].end(); ++it) {\n    val += it->second.GetAvg5();\n  }\n\n  return val;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalNExt5(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  unsigned long n = 0;\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    for (int i = 0; i < 5; i++) {\n      n += it->second.n5[i];\n    }\n  }\n\n  return (double) n;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalAvgExt5(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double val = 0;\n  double totw = 0;\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    double w = 0;\n\n    for (int i = 0; i < 5; i++) {\n      w += it->second.n5[i];\n    }\n\n    totw += w;\n    val += it->second.GetAvg5() * w;\n  }\n\n  return val / totw;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalMinExt5(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double minval = std::numeric_limits<unsigned long>::max();\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    minval = std::min(minval, it->second.GetMin5());\n  }\n\n  return minval;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalMaxExt5(const char* tag)\n{\n  google::sparse_hash_map<uid_t, StatExt>::iterator it;\n  double maxval = std::numeric_limits<unsigned long>::min();\n\n  if (!StatExtUid.count(tag)) {\n    return 0;\n  }\n\n  for (it = StatExtUid[tag].begin(); it != StatExtUid[tag].end(); ++it) {\n    maxval = std::max(maxval, it->second.GetMax5());\n  }\n\n  return maxval;\n}\n\n\n//------------------------------------------------------------------------------\n// Calculate the average execution time for 'tag'\n// warning: you have to lock the mutex if directly used\n//------------------------------------------------------------------------------\ndouble\nStat::GetExec(const char* tag, double& deviation, double& percentile,\n              double& max)\n{\n  double avg = 0;\n  deviation = 0;\n  max = 0;\n  percentile = 0;\n  std::multiset<float> m;\n\n  if (StatExec.count(tag)) {\n    std::deque<float>::const_iterator it;\n\n    for (it = StatExec[tag].begin(); it != StatExec[tag].end(); ++it) {\n      m.insert(*it);\n    }\n\n    avg = eos::common::Statistics::avg(m);\n    deviation = eos::common::Statistics::sig(m);\n    percentile = eos::common::Statistics::nperc(m, 99);\n    max = eos::common::Statistics::max(m);\n  }\n\n  return avg;\n}\n\n/*----------------------------------------------------------------------------*/\n// warning: you have to lock the mutex if directly used\n\ndouble\nStat::GetTotalExec(double& deviation)\n{\n  // calculates average execution time for all commands\n  google::sparse_hash_map<std::string, std::deque<float> >::const_iterator ittag;\n  double sum = 0;\n  double avg = 0;\n  deviation = 0;\n  int cnt = 0;\n\n  for (ittag = StatExec.begin(); ittag != StatExec.end(); ittag++) {\n    std::deque<float>::const_iterator it;\n\n    for (it = ittag->second.begin(); it != ittag->second.end(); it++) {\n      cnt++;\n      sum += *it;\n    }\n  }\n\n  if (cnt) {\n    avg = sum / cnt;\n  }\n\n  for (ittag = StatExec.begin(); ittag != StatExec.end(); ittag++) {\n    std::deque<float>::const_iterator it;\n\n    for (it = ittag->second.begin(); it != ittag->second.end(); it++) {\n      deviation += pow((*it - avg), 2);\n    }\n  }\n\n  if (cnt) {\n    deviation = sqrt(deviation / cnt);\n  }\n\n  return avg;\n}\n\n//------------------------------------------------------------------------------\n// Get read contention approximation\n//------------------------------------------------------------------------------\ndouble\nStat::GetReadContention() const\n{\n  XrdSysMutexHelper lock(mMutex);\n  double rnorm = gOFS->MgmStats.GetTotalAvg5(\"NsUsedR\");\n  return ((rnorm) ?\n          (100.0 * gOFS->MgmStats.GetTotalAvg5(\"NsLeadR\") / rnorm) : 0.0);\n}\n\n//----------------------------------------------------------------------------\n// Get write contention approximation\n//----------------------------------------------------------------------------\ndouble\nStat::GetWriteContention() const\n{\n  XrdSysMutexHelper lock(mMutex);\n  double wnorm = gOFS->MgmStats.GetTotalAvg5(\"NsUsedW\");\n  return ((wnorm) ?\n          (100.0 * gOFS->MgmStats.GetTotalAvg5(\"NsLeadW\") / wnorm) : 0.0);\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nStat::Clear()\n{\n  XrdSysMutexHelper lock(mMutex);\n\n  for (auto ittag = StatsUid.begin(); ittag != StatsUid.end(); ittag++) {\n    StatsUid[ittag->first].clear();\n    StatsUid[ittag->first].resize(1000);\n    StatsGid[ittag->first].clear();\n    StatsGid[ittag->first].resize(1000);\n    StatsApp[ittag->first].clear();\n    StatsApp[ittag->first].resize(1000);\n    StatAvgUid[ittag->first].clear();\n    StatAvgUid[ittag->first].resize(1000);\n    StatAvgGid[ittag->first].clear();\n    StatAvgGid[ittag->first].resize(1000);\n    StatAvgApp[ittag->first].clear();\n    StatAvgApp[ittag->first].resize(1000);\n    StatExec[ittag->first].clear();\n    StatExec[ittag->first].resize(1000);\n    CumulativeTimeExec[ittag->first] = 0.0;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nStat::PrintOutTotal(XrdOucString& out, bool details, bool monitoring,\n                    bool numerical, bool apps)\n{\n  if (apps) {\n    details = true;\n  }\n\n  mMutex.Lock();\n  std::vector<std::string> tags, tags_ext;\n  std::vector<std::string>::iterator it;\n  google::sparse_hash_map < std::string,\n         google::sparse_hash_map<uid_t, unsigned long long> >::iterator tit;\n  google::sparse_hash_map < std::string,\n         google::sparse_hash_map<uid_t, StatExt > >::iterator tit_ext;\n\n  for (tit = StatsUid.begin(); tit != StatsUid.end(); ++tit) {\n    tags.push_back(tit->first);\n  }\n\n  for (tit_ext = StatExtUid.begin(); tit_ext != StatExtUid.end(); ++tit_ext) {\n    tags_ext.push_back(tit_ext->first);\n  }\n\n  std::sort(tags.begin(), tags.end());\n  std::sort(tags_ext.begin(), tags_ext.end());\n  char outline[1024];\n  double avg = 0;\n  double sig = 0;\n  avg = GetTotalExec(sig);\n\n  if (!monitoring) {\n    sprintf(outline, \"%-8s %-32s %3.02f ± %3.02f\\n\", \"ALL\", \"Execution Time\", avg,\n            sig);\n  } else {\n    sprintf(outline,\n            \"uid=all gid=all total.exec.avg=%.02f total.exec.sigma=%.02f\\n\", avg, sig);\n  }\n\n  out += outline;\n  std::string na = !monitoring ? \"-NA-\" : \"NA\";\n  std::string format_cmd = !monitoring ? \"-s\" : \"os\";\n  std::string format_s = !monitoring ? \"s\" : \"os\";\n  std::string format_ss = !monitoring ? \"-s\" : \"os\";\n  std::string format_l = !monitoring ? \"+l\" : \"ol\";\n  std::string format_f = !monitoring ? \"f\" : \"of\";\n  std::string format_ff = !monitoring ? \"±f\" : \"of\";\n  // Specification for all users and groups\n  TableFormatterBase table_all;\n\n  if (!monitoring) {\n    table_all.SetHeader({\n      std::make_tuple(\"who\", 3, format_ss),\n      std::make_tuple(\"command\", 24, format_cmd),\n      std::make_tuple(\"sum\", 8, format_l),\n      std::make_tuple(\"5s\", 8, format_f),\n      std::make_tuple(\"1min\", 8, format_f),\n      std::make_tuple(\"5min\", 8, format_f),\n      std::make_tuple(\"1h\", 8, format_f),\n      std::make_tuple(\"exec(ms)\", 8, format_f),\n      std::make_tuple(\"sigma(ms)\", 8, format_ff),\n      std::make_tuple(\"99p(ms)\", 8, format_f),\n      std::make_tuple(\"max(ms)\", 8, format_f),\n      std::make_tuple(\"cumul\", 8, format_f)\n    });\n  } else {\n    table_all.SetHeader({\n      std::make_tuple(\"uid\", 0, format_ss),\n      std::make_tuple(\"gid\", 0, format_s),\n      std::make_tuple(\"cmd\", 0, format_s),\n      std::make_tuple(\"total\", 0, format_l),\n      std::make_tuple(\"5s\", 0, format_f),\n      std::make_tuple(\"60s\", 0, format_f),\n      std::make_tuple(\"300s\", 0, format_f),\n      std::make_tuple(\"3600s\", 0, format_f),\n      std::make_tuple(\"exec\", 0, format_f),\n      std::make_tuple(\"execsig\", 0, format_ff),\n      std::make_tuple(\"exec99\", 8, format_f),\n      std::make_tuple(\"execmax\", 8, format_f),\n      std::make_tuple(\"cumulexecms\", 8, format_f)\n    });\n  }\n\n  for (it = tags.begin(); it != tags.end(); ++it) {\n    const char* tag = it->c_str();\n    double avg = 0, sig = 0;\n    double max = 0, perc = 0;\n    double cumulativeExecTimeMs = 0;\n    avg = GetExec(tag, sig, max, perc);\n    cumulativeExecTimeMs = GetCumulativeExecTime(tag);\n    TableData table_data;\n    table_data.emplace_back();\n    table_data.back().push_back(TableCell(\"all\", format_ss));\n\n    if (monitoring) {\n      table_data.back().push_back(TableCell(\"all\", format_s));\n    }\n\n    table_data.back().push_back(TableCell(tag, format_cmd));\n    table_data.back().push_back(TableCell(GetTotal(tag), format_l));\n    table_data.back().push_back(TableCell(GetTotalAvg5(tag), format_f));\n    table_data.back().push_back(TableCell(GetTotalAvg60(tag), format_f));\n    table_data.back().push_back(TableCell(GetTotalAvg300(tag), format_f));\n    table_data.back().push_back(TableCell(GetTotalAvg3600(tag), format_f));\n\n    if (avg || monitoring) {\n      table_data.back().push_back(TableCell(avg, format_f));\n      table_data.back().push_back(TableCell(sig, format_f));\n      table_data.back().push_back(TableCell(perc, format_f));\n      table_data.back().push_back(TableCell(max, format_f));\n\n      if (!monitoring) {\n        //We want the output to be displayed in day/hours/min/seconds in the table output\n        XrdOucString cumulativeExecTimeStr;\n        common::StringConversion::GetReadableAgeString(cumulativeExecTimeStr,\n            llround(cumulativeExecTimeMs) / 1000);\n        table_data.back().push_back(\n          TableCell(cumulativeExecTimeStr.c_str(), format_ss));\n      } else {\n        table_data.back().push_back(TableCell(cumulativeExecTimeMs, format_f));\n      }\n    } else {\n      table_data.back().push_back(TableCell(na, format_s));\n      table_data.back().push_back(TableCell(na, format_s));\n      table_data.back().push_back(TableCell(na, format_s));\n      table_data.back().push_back(TableCell(na, format_s));\n      table_data.back().push_back(TableCell(na, format_s));\n    }\n\n    table_all.AddRows(table_data);\n  }\n\n  if (details) {\n    for (it = tags_ext.begin(); it != tags_ext.end(); ++it) {\n      const char* tag = it->c_str();\n      TableData table_data_spl, table_data_min, table_data_avg, table_data_max;\n      table_data_spl.emplace_back();\n      table_data_min.emplace_back();\n      table_data_avg.emplace_back();\n      table_data_max.emplace_back();\n      table_data_spl.back().push_back(TableCell(\"all\", format_ss));\n      table_data_min.back().push_back(TableCell(\"all\", format_ss));\n      table_data_avg.back().push_back(TableCell(\"all\", format_ss));\n      table_data_max.back().push_back(TableCell(\"all\", format_ss));\n\n      if (monitoring) {\n        table_data_spl.back().push_back(TableCell(\"all\", format_s));\n        table_data_min.back().push_back(TableCell(\"all\", format_s));\n        table_data_avg.back().push_back(TableCell(\"all\", format_s));\n        table_data_max.back().push_back(TableCell(\"all\", format_s));\n      }\n\n      std::string tag_spl = tag, tag_min = tag, tag_avg = tag, tag_max = tag;\n      tag_spl += \":spl\";\n      tag_min += \":min\";\n      tag_avg += \":avg\";\n      tag_max += \":max\";\n      table_data_spl.back().push_back(TableCell(tag_spl, format_s));\n      table_data_min.back().push_back(TableCell(tag_min, format_s));\n      table_data_avg.back().push_back(TableCell(tag_avg, format_s));\n      table_data_max.back().push_back(TableCell(tag_max, format_s));\n      table_data_spl.back().push_back(TableCell(\"\", \"\", \"\", true));\n      table_data_min.back().push_back(TableCell(\"\", \"\", \"\", true));\n      table_data_avg.back().push_back(TableCell(\"\", \"\", \"\", true));\n      table_data_max.back().push_back(TableCell(\"\", \"\", \"\", true));\n      table_data_spl.back().push_back(TableCell(GetTotalNExt5(tag), format_f));\n\n      if (GetTotalNExt5(tag) < 1) {\n        table_data_min.back().push_back(TableCell(na, format_s));\n        table_data_avg.back().push_back(TableCell(na, format_s));\n        table_data_max.back().push_back(TableCell(na, format_s));\n      } else {\n        table_data_min.back().push_back(TableCell(GetTotalMinExt5(tag), format_f));\n        table_data_avg.back().push_back(TableCell(GetTotalAvgExt5(tag), format_f));\n        table_data_max.back().push_back(TableCell(GetTotalMaxExt5(tag), format_f));\n      }\n\n      table_data_spl.back().push_back(TableCell(GetTotalNExt60(tag), format_f));\n\n      if (GetTotalNExt60(tag) < 1) {\n        table_data_min.back().push_back(TableCell(na, format_s));\n        table_data_avg.back().push_back(TableCell(na, format_s));\n        table_data_max.back().push_back(TableCell(na, format_s));\n      } else {\n        table_data_min.back().push_back(TableCell(GetTotalMinExt60(tag), format_f));\n        table_data_avg.back().push_back(TableCell(GetTotalAvgExt60(tag), format_f));\n        table_data_max.back().push_back(TableCell(GetTotalMaxExt60(tag), format_f));\n      }\n\n      table_data_spl.back().push_back(TableCell(GetTotalNExt300(tag), format_f));\n\n      if (GetTotalNExt300(tag) < 1) {\n        table_data_min.back().push_back(TableCell(na, format_s));\n        table_data_avg.back().push_back(TableCell(na, format_s));\n        table_data_max.back().push_back(TableCell(na, format_s));\n      } else {\n        table_data_min.back().push_back(TableCell(GetTotalMinExt300(tag), format_f));\n        table_data_avg.back().push_back(TableCell(GetTotalAvgExt300(tag), format_f));\n        table_data_max.back().push_back(TableCell(GetTotalMaxExt300(tag), format_f));\n      }\n\n      table_data_spl.back().push_back(TableCell(GetTotalNExt3600(tag), format_f));\n\n      if (GetTotalNExt3600(tag) < 1) {\n        table_data_min.back().push_back(TableCell(na, format_s));\n        table_data_avg.back().push_back(TableCell(na, format_s));\n        table_data_max.back().push_back(TableCell(na, format_s));\n      } else {\n        table_data_min.back().push_back(TableCell(GetTotalMinExt3600(tag), format_f));\n        table_data_avg.back().push_back(TableCell(GetTotalAvgExt3600(tag), format_f));\n        table_data_max.back().push_back(TableCell(GetTotalMaxExt3600(tag), format_f));\n      }\n\n      table_all.AddRows(table_data_spl);\n      table_all.AddRows(table_data_min);\n      table_all.AddRows(table_data_avg);\n      table_all.AddRows(table_data_max);\n    }\n  }\n\n  if (!apps) {\n    out += table_all.GenerateTable(HEADER).c_str();\n  }\n\n  if (details) {\n    // Collect uids and gids inside the lock and the do the translation outside\n    // the lock\n    std::set<uid_t> set_uids;\n    std::set<gid_t> set_gids;\n    std::set<std::string> set_apps;\n\n    for (auto tuit = StatAvgUid.begin(); tuit != StatAvgUid.end(); tuit++) {\n      for (auto it = tuit->second.begin(); it != tuit->second.end(); ++it) {\n        set_uids.insert(it->first);\n      }\n    }\n\n    for (auto tuit_ext = StatExtUid.begin(); tuit_ext != StatExtUid.end();\n         tuit_ext++) {\n      for (auto it = tuit_ext->second.begin(); it != tuit_ext->second.end(); ++it) {\n        set_uids.insert(it->first);\n      }\n    }\n\n    for (auto tgit = StatAvgGid.begin(); tgit != StatAvgGid.end(); tgit++) {\n      for (auto it = tgit->second.begin(); it != tgit->second.end(); ++it) {\n        set_gids.insert(it->first);\n      }\n    }\n\n    for (auto tgit_ext = StatExtGid.begin(); tgit_ext != StatExtGid.end();\n         tgit_ext++) {\n      for (auto it = tgit_ext->second.begin(); it != tgit_ext->second.end(); ++it) {\n        set_gids.insert(it->first);\n      }\n    }\n\n    for (auto tgit = StatAvgApp.begin(); tgit != StatAvgApp.end(); tgit++) {\n      for (auto it = tgit->second.begin(); it != tgit->second.end(); ++it) {\n        set_apps.insert(it->first);\n      }\n    }\n\n    for (auto tgit_ext = StatExtApp.begin(); tgit_ext != StatExtApp.end();\n         tgit_ext++) {\n      for (auto it = tgit_ext->second.begin(); it != tgit_ext->second.end(); ++it) {\n        set_apps.insert(it->first);\n      }\n    }\n\n    mMutex.UnLock();\n    std::map<uid_t, std::string> umap;\n    std::map<gid_t, std::string> gmap;\n\n    for (const auto numeric_uid : set_uids) {\n      int terrc = 0;\n      std::string username = eos::common::Mapping::UidToUserName(numeric_uid, terrc);\n      umap[numeric_uid] = username;\n    }\n\n    for (const auto numeric_gid : set_gids) {\n      int terrc = 0;\n      std::string groupname = eos::common::Mapping::GidToGroupName(numeric_gid,\n                              terrc);\n      gmap[numeric_gid] = groupname;\n    }\n\n    mMutex.Lock();\n    //! User statistic\n    TableFormatterBase table_user;\n\n    if (!monitoring) {\n      table_user.SetHeader({\n        std::make_tuple(\"user\", 5, format_ss),\n        std::make_tuple(\"command\", 24, format_cmd),\n        std::make_tuple(\"sum\", 8, format_l),\n        std::make_tuple(\"5s\", 8, format_f),\n        std::make_tuple(\"1min\", 8, format_f),\n        std::make_tuple(\"5min\", 8, format_f),\n        std::make_tuple(\"1h\", 8, format_f)\n      });\n    } else {\n      table_user.SetHeader({\n        std::make_tuple(\"uid\", 0, format_ss),\n        std::make_tuple(\"cmd\", 0, format_s),\n        std::make_tuple(\"total\", 0, format_l),\n        std::make_tuple(\"5s\", 0, format_f),\n        std::make_tuple(\"60s\", 0, format_f),\n        std::make_tuple(\"300s\", 0, format_f),\n        std::make_tuple(\"3600s\", 0, format_f)\n      });\n    }\n\n    std::vector<std::tuple<int, std::string, std::string, unsigned long long,\n        double, double, double, double>> table_data;\n    std::vector<std::tuple<int, std::string, std::string, double, double,\n        double, double, double, double, double, double, double, double,\n        double, double, double, double, double, double>> table_data_ext;\n\n    for (auto tuit = StatAvgUid.begin(); tuit != StatAvgUid.end(); tuit++) {\n      for (auto it = tuit->second.begin(); it != tuit->second.end(); ++it) {\n        std::string username;\n\n        if (numerical) {\n          username = std::to_string(it->first);\n        } else {\n          username = umap.count(it->first) ? umap[it->first] :\n                     eos::common::StringConversion::GetSizeString(username,\n                         (unsigned long long)it->first);\n        }\n\n        table_data.push_back(std::make_tuple(0, username, tuit->first.c_str(),\n                                             StatsUid[tuit->first.c_str()][it->first],\n                                             it->second.GetAvg5(), it->second.GetAvg60(),\n                                             it->second.GetAvg300(), it->second.GetAvg3600()));\n      }\n    }\n\n    for (auto tuit_ext = StatExtUid.begin(); tuit_ext != StatExtUid.end();\n         tuit_ext++) {\n      for (auto it = tuit_ext->second.begin(); it != tuit_ext->second.end(); ++it) {\n        std::string username;\n\n        if (numerical) {\n          username = std::to_string(it->first);\n        } else {\n          username = umap.count(it->first) ? umap[it->first] :\n                     eos::common::StringConversion::GetSizeString(username,\n                         (unsigned long long)it->first);\n        }\n\n        table_data_ext.push_back(std::make_tuple(\n                                   0, username, tuit_ext->first.c_str(),\n                                   it->second.GetN5(), it->second.GetAvg5(),\n                                   it->second.GetMin5(), it->second.GetMax5(),\n                                   it->second.GetN60(), it->second.GetAvg60(),\n                                   it->second.GetMin60(), it->second.GetMax60(),\n                                   it->second.GetN300(), it->second.GetAvg300(),\n                                   it->second.GetMin300(), it->second.GetMax300(),\n                                   it->second.GetN3600(), it->second.GetAvg3600(),\n                                   it->second.GetMin3600(), it->second.GetMax3600()));\n      }\n    }\n\n    //! Group statistic\n    TableFormatterBase table_group;\n\n    if (!monitoring) {\n      table_group.SetHeader({\n        std::make_tuple(\"group\", 5, format_ss),\n        std::make_tuple(\"command\", 24, format_cmd),\n        std::make_tuple(\"sum\", 8, format_l),\n        std::make_tuple(\"5s\", 8, format_f),\n        std::make_tuple(\"1min\", 8, format_f),\n        std::make_tuple(\"5min\", 8, format_f),\n        std::make_tuple(\"1h\", 8, format_f)\n      });\n    } else {\n      table_group.SetHeader({\n        std::make_tuple(\"gid\", 0, format_ss),\n        std::make_tuple(\"cmd\", 0, format_s),\n        std::make_tuple(\"total\", 0, format_l),\n        std::make_tuple(\"5s\", 0, format_f),\n        std::make_tuple(\"60s\", 0, format_f),\n        std::make_tuple(\"300s\", 0, format_f),\n        std::make_tuple(\"3600s\", 0, format_f)\n      });\n    }\n\n    for (auto tgit = StatAvgGid.begin(); tgit != StatAvgGid.end(); tgit++) {\n      for (auto it = tgit->second.begin(); it != tgit->second.end(); ++it) {\n        std::string groupname;\n\n        if (numerical) {\n          groupname = std::to_string(it->first);\n        } else {\n          groupname = gmap.count(it->first) ? gmap[it->first] :\n                      eos::common::StringConversion::GetSizeString(groupname,\n                          (unsigned long long)it->first);\n        }\n\n        table_data.push_back(std::make_tuple(1, groupname, tgit->first.c_str(),\n                                             StatsGid[tgit->first.c_str()][it->first],\n                                             it->second.GetAvg5(), it->second.GetAvg60(),\n                                             it->second.GetAvg300(), it->second.GetAvg3600()));\n      }\n    }\n\n    for (auto tgit_ext = StatExtGid.begin(); tgit_ext != StatExtGid.end();\n         tgit_ext++) {\n      for (auto it = tgit_ext->second.begin(); it != tgit_ext->second.end(); ++it) {\n        std::string groupname;\n\n        if (numerical) {\n          groupname = std::to_string(it->first);\n        } else {\n          groupname = gmap.count(it->first) ? gmap[it->first] :\n                      eos::common::StringConversion::GetSizeString(groupname,\n                          (unsigned long long)it->first);\n        }\n\n        table_data_ext.push_back(std::make_tuple(\n                                   1, groupname, tgit_ext->first.c_str(),\n                                   it->second.GetN5(), it->second.GetAvg5(),\n                                   it->second.GetMin5(), it->second.GetMax5(),\n                                   it->second.GetN60(), it->second.GetAvg60(),\n                                   it->second.GetMin60(), it->second.GetMax60(),\n                                   it->second.GetN300(), it->second.GetAvg300(),\n                                   it->second.GetMin300(), it->second.GetMax300(),\n                                   it->second.GetN3600(), it->second.GetAvg3600(),\n                                   it->second.GetMin3600(), it->second.GetMax3600()));\n      }\n    }\n\n    //! App statistic-\n    TableFormatterBase table_app;\n\n    if (!monitoring) {\n      table_app.SetHeader({\n        std::make_tuple(\"fuse-app\", 5, format_ss),\n        std::make_tuple(\"command\", 24, format_cmd),\n        std::make_tuple(\"sum\", 8, format_l),\n        std::make_tuple(\"5s\", 8, format_f),\n        std::make_tuple(\"1min\", 8, format_f),\n        std::make_tuple(\"5min\", 8, format_f),\n        std::make_tuple(\"1h\", 8, format_f)\n      });\n    } else {\n      table_app.SetHeader({\n        std::make_tuple(\"fuse-app\", 0, format_ss),\n        std::make_tuple(\"cmd\", 0, format_s),\n        std::make_tuple(\"total\", 0, format_l),\n        std::make_tuple(\"5s\", 0, format_f),\n        std::make_tuple(\"60s\", 0, format_f),\n        std::make_tuple(\"300s\", 0, format_f),\n        std::make_tuple(\"3600s\", 0, format_f)\n      });\n    }\n\n    for (auto tapp = StatAvgApp.begin(); tapp != StatAvgApp.end(); tapp++) {\n      for (auto it = tapp->second.begin(); it != tapp->second.end(); ++it) {\n        std::string appname;\n        appname = it->first;\n        table_data.push_back(std::make_tuple(2, appname, tapp->first.c_str(),\n                                             StatsApp[tapp->first.c_str()][it->first],\n                                             it->second.GetAvg5(), it->second.GetAvg60(),\n                                             it->second.GetAvg300(), it->second.GetAvg3600()));\n      }\n    }\n\n    for (auto tapp_ext = StatExtApp.begin(); tapp_ext != StatExtApp.end();\n         tapp_ext++) {\n      for (auto it = tapp_ext->second.begin(); it != tapp_ext->second.end(); ++it) {\n        std::string appname;\n        appname = it->first;\n        table_data_ext.push_back(std::make_tuple(\n                                   2, appname, tapp_ext->first.c_str(),\n                                   it->second.GetN5(), it->second.GetAvg5(),\n                                   it->second.GetMin5(), it->second.GetMax5(),\n                                   it->second.GetN60(), it->second.GetAvg60(),\n                                   it->second.GetMin60(), it->second.GetMax60(),\n                                   it->second.GetN300(), it->second.GetAvg300(),\n                                   it->second.GetMin300(), it->second.GetMax300(),\n                                   it->second.GetN3600(), it->second.GetAvg3600(),\n                                   it->second.GetMin3600(), it->second.GetMax3600()));\n      }\n    }\n\n    // Data sorting\n    std::sort(table_data.begin(), table_data.end());\n    std::sort(table_data_ext.begin(), table_data_ext.end());\n\n    // Output user and group statistic\n    for (int i = 0; i <= 2; i++) {\n      for (auto it : table_data) {\n        if (std::get<0>(it) == i) {\n          TableData table_data_sorted;\n          table_data_sorted.emplace_back();\n          table_data_sorted.back().push_back(TableCell(std::get<1>(it), format_ss));\n          table_data_sorted.back().push_back(TableCell(std::get<2>(it), format_s));\n          table_data_sorted.back().push_back(TableCell(std::get<3>(it), format_l));\n          table_data_sorted.back().push_back(TableCell(std::get<4>(it), format_f));\n          table_data_sorted.back().push_back(TableCell(std::get<5>(it), format_f));\n          table_data_sorted.back().push_back(TableCell(std::get<6>(it), format_f));\n          table_data_sorted.back().push_back(TableCell(std::get<7>(it), format_f));\n\n          if (i == 0) {\n            table_user.AddRows(table_data_sorted);\n          } else if (i == 1) {\n            table_group.AddRows(table_data_sorted);\n          } else if (i == 2) {\n            table_app.AddRows(table_data_sorted);\n          }\n        }\n      }\n\n      for (auto it : table_data_ext) {\n        if (std::get<0>(it) == i) {\n          TableData table_data_spl, table_data_min, table_data_avg, table_data_max;\n          table_data_spl.emplace_back();\n          table_data_min.emplace_back();\n          table_data_avg.emplace_back();\n          table_data_max.emplace_back();\n          table_data_spl.back().push_back(TableCell(std::get<1>(it), format_ss));\n          table_data_min.back().push_back(TableCell(std::get<1>(it), format_ss));\n          table_data_avg.back().push_back(TableCell(std::get<1>(it), format_ss));\n          table_data_max.back().push_back(TableCell(std::get<1>(it), format_ss));\n          std::string tag = std::get<2>(it);\n          std::string tag_spl = tag, tag_min = tag, tag_avg = tag, tag_max = tag;\n          tag_spl += \":spl\";\n          tag_min += \":min\";\n          tag_avg += \":avg\";\n          tag_max += \":max\";\n          table_data_spl.back().push_back(TableCell(tag_spl, format_s));\n          table_data_min.back().push_back(TableCell(tag_min, format_s));\n          table_data_avg.back().push_back(TableCell(tag_avg, format_s));\n          table_data_max.back().push_back(TableCell(tag_max, format_s));\n          table_data_spl.back().push_back(TableCell(\"\", \"\", \"\", true));\n          table_data_min.back().push_back(TableCell(\"\", \"\", \"\", true));\n          table_data_avg.back().push_back(TableCell(\"\", \"\", \"\", true));\n          table_data_max.back().push_back(TableCell(\"\", \"\", \"\", true));\n          table_data_spl.back().push_back(TableCell(std::get<3>(it), format_f));\n\n          if (std::get<3>(it) < 1) {\n            table_data_min.back().push_back(TableCell(na, format_s));\n            table_data_avg.back().push_back(TableCell(na, format_s));\n            table_data_max.back().push_back(TableCell(na, format_s));\n          } else {\n            table_data_min.back().push_back(TableCell(std::get<4>(it), format_f));\n            table_data_avg.back().push_back(TableCell(std::get<5>(it), format_f));\n            table_data_max.back().push_back(TableCell(std::get<6>(it), format_f));\n          }\n\n          table_data_spl.back().push_back(TableCell(std::get<7>(it), format_f));\n\n          if (std::get<7>(it) < 1) {\n            table_data_min.back().push_back(TableCell(na, format_s));\n            table_data_avg.back().push_back(TableCell(na, format_s));\n            table_data_max.back().push_back(TableCell(na, format_s));\n          } else {\n            table_data_min.back().push_back(TableCell(std::get<8>(it), format_f));\n            table_data_avg.back().push_back(TableCell(std::get<9>(it), format_f));\n            table_data_max.back().push_back(TableCell(std::get<10>(it), format_f));\n          }\n\n          table_data_spl.back().push_back(TableCell(std::get<11>(it), format_f));\n\n          if (std::get<11>(it) < 1) {\n            table_data_min.back().push_back(TableCell(na, format_s));\n            table_data_avg.back().push_back(TableCell(na, format_s));\n            table_data_max.back().push_back(TableCell(na, format_s));\n          } else {\n            table_data_min.back().push_back(TableCell(std::get<12>(it), format_f));\n            table_data_avg.back().push_back(TableCell(std::get<13>(it), format_f));\n            table_data_max.back().push_back(TableCell(std::get<14>(it), format_f));\n          }\n\n          table_data_spl.back().push_back(TableCell(std::get<15>(it), format_f));\n\n          if (std::get<15>(it) < 1) {\n            table_data_min.back().push_back(TableCell(na, format_s));\n            table_data_avg.back().push_back(TableCell(na, format_s));\n            table_data_max.back().push_back(TableCell(na, format_s));\n          } else {\n            table_data_min.back().push_back(TableCell(std::get<16>(it), format_f));\n            table_data_avg.back().push_back(TableCell(std::get<17>(it), format_f));\n            table_data_max.back().push_back(TableCell(std::get<18>(it), format_f));\n          }\n\n          if (i == 0) {\n            table_user.AddRows(table_data_spl);\n            table_user.AddRows(table_data_min);\n            table_user.AddRows(table_data_avg);\n            table_user.AddRows(table_data_max);\n          } else if (i == 1) {\n            table_group.AddRows(table_data_spl);\n            table_group.AddRows(table_data_min);\n            table_group.AddRows(table_data_avg);\n            table_group.AddRows(table_data_max);\n          } else if (i == 2) {\n            table_app.AddRows(table_data_spl);\n            table_app.AddRows(table_data_min);\n            table_app.AddRows(table_data_avg);\n            table_app.AddRows(table_data_max);\n          }\n        }\n      }\n    }\n\n    if (apps) {\n      out += table_app.GenerateTable(HEADER).c_str();\n    } else {\n      out += table_user.GenerateTable(HEADER).c_str();\n      out += table_group.GenerateTable(HEADER).c_str();\n    }\n  }\n\n  mMutex.UnLock();\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nStat::Circulate(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(\"StatCirculate\");\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n  unsigned long long qu1 = 0;\n  unsigned long long qu2 = 0;\n  unsigned long long ns1 = 0;\n  unsigned long long ns2 = 0;\n  unsigned long long view1 = 0;\n  unsigned long long view2 = 0;\n  eos::common::RWMutex::TimingStats qu12stmp, ns12stmp, view12stmp;\n  unsigned long long ns1tmp = 0ull, ns2tmp = 0ull, view1tmp = 0ull,\n                     view2tmp = 0ull, qu1tmp = 0ull, qu2tmp = 0ull;\n#endif\n  auto chrononow = std::chrono::system_clock::now();\n  auto chronolast = chrononow;\n\n  // Empty the circular buffer and extract some Mq statistic values\n  while (!assistant.terminationRequested()) {\n    assistant.wait_for(std::chrono::milliseconds(512));\n    chrononow = std::chrono::system_clock::now();\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n    eos::common::RWMutex* fs_mtx = &FsView::gFsView.ViewMutex;\n    eos::common::RWMutex* quota_mtx = &Quota::pMapMutex;\n    eos::common::RWMutex* ns_mtx = &gOFS->eosViewRWMutex;\n    // fsview statistics extraction\n    view1tmp = fs_mtx->GetReadLockCounter();\n    view2tmp = fs_mtx->GetWriteLockCounter();\n    fs_mtx->GetTimingStatistics(view12stmp);\n    fs_mtx->ResetTimingStatistics();\n    // namespace lock statistics extraction\n    ns1tmp = ns_mtx->GetReadLockCounter();\n    ns2tmp = ns_mtx->GetWriteLockCounter();\n    ns_mtx->GetTimingStatistics(ns12stmp);\n    ns_mtx->ResetTimingStatistics();\n    // quota lock statistics extraction\n    qu1tmp = quota_mtx->GetReadLockCounter();\n    qu2tmp = quota_mtx->GetWriteLockCounter();\n    quota_mtx->GetTimingStatistics(qu12stmp);\n    quota_mtx->ResetTimingStatistics();\n    Add(\"ViewLockR\", 0, 0, view1tmp - view1);\n    Add(\"ViewLockW\", 0, 0, view2tmp - view2);\n    Add(\"NsLockR\", 0, 0, ns1tmp - ns1);\n    Add(\"NsLockW\", 0, 0, ns2tmp - ns2);\n    Add(\"QuotaLockR\", 0, 0, qu1tmp - qu1);\n    Add(\"QuotaLockW\", 0, 0, qu2tmp - qu2);\n    AddExt(\"ViewLockRWait\", 0, 0, (unsigned long) view12stmp.readLockCounterSample,\n           view12stmp.averagewaitread, view12stmp.minwaitread, view12stmp.maxwaitread);\n    AddExt(\"ViewLockWWait\", 0, 0, (unsigned long) view12stmp.writeLockCounterSample,\n           view12stmp.averagewaitwrite, view12stmp.minwaitwrite, view12stmp.maxwaitwrite);\n    AddExt(\"NsLockRWait\", 0, 0, (unsigned long) ns12stmp.readLockCounterSample,\n           ns12stmp.averagewaitread, ns12stmp.minwaitread, ns12stmp.maxwaitread);\n    AddExt(\"NsLockWWait\", 0, 0, (unsigned long) ns12stmp.writeLockCounterSample,\n           ns12stmp.averagewaitwrite, ns12stmp.minwaitwrite, ns12stmp.maxwaitwrite);\n    AddExt(\"QuotaLockRWait\", 0, 0, (unsigned long) qu12stmp.readLockCounterSample,\n           qu12stmp.averagewaitread, ns12stmp.minwaitread, ns12stmp.maxwaitread);\n    AddExt(\"QuotaLockWWait\", 0, 0, (unsigned long) qu12stmp.writeLockCounterSample,\n           qu12stmp.averagewaitwrite, ns12stmp.minwaitwrite, ns12stmp.maxwaitwrite);\n    view1 = view1tmp;\n    view2 = view2tmp;\n    ns1 = ns1tmp;\n    ns2 = ns2tmp;\n    qu1 = qu1tmp;\n    qu2 = qu2tmp;\n    std::chrono::milliseconds elapsed =\n      std::chrono::duration_cast<std::chrono::milliseconds> (chrononow - chronolast);\n    Add(\"NsUsedR\", 0, 0, ns_mtx->GetReadLockTime() / elapsed.count());\n    Add(\"NsUsedW\", 0, 0, ns_mtx->GetWriteLockTime() / elapsed.count());\n    Add(\"NsLeadR\", 0, 0, ns_mtx->GetReadLockLeadTime() / elapsed.count());\n    Add(\"NsLeadW\", 0, 0, ns_mtx->GetWriteLockLeadTime() / elapsed.count());\n#endif\n    XrdSysMutexHelper lock(mMutex);\n    time_t now = time(NULL);\n\n    // loop over tags\n    for (auto tit = StatAvgUid.begin(); tit != StatAvgUid.end(); ++tit) {\n      // loop over vids\n      for (auto it = tit->second.begin(); it != tit->second.end(); ++it) {\n        it->second.StampZero(now);\n      }\n    }\n\n    for (auto tit = StatAvgGid.begin(); tit != StatAvgGid.end(); ++tit) {\n      // loop over vids\n      for (auto it = tit->second.begin(); it != tit->second.end(); ++it) {\n        it->second.StampZero(now);\n      }\n    }\n\n    for (auto tit = StatAvgApp.begin(); tit != StatAvgApp.end(); ++tit) {\n      // loop over vids\n      for (auto it = tit->second.begin(); it != tit->second.end(); ++it) {\n        it->second.StampZero(now);\n      }\n    }\n\n    for (auto tit_ext = StatExtGid.begin(); tit_ext != StatExtGid.end();\n         ++tit_ext) {\n      // loop over vids\n      for (auto it = tit_ext->second.begin(); it != tit_ext->second.end(); ++it) {\n        it->second.StampZero(now);\n      }\n    }\n\n    for (auto tit_ext = StatExtGid.begin(); tit_ext != StatExtGid.end();\n         ++tit_ext) {\n      // loop over vids\n      for (auto it = tit_ext->second.begin(); it != tit_ext->second.end(); ++it) {\n        it->second.StampZero(now);\n      }\n    }\n\n    for (auto tit_ext = StatExtApp.begin(); tit_ext != StatExtApp.end();\n         ++tit_ext) {\n      // loop over vids\n      for (auto it = tit_ext->second.begin(); it != tit_ext->second.end(); ++it) {\n        it->second.StampZero(now);\n      }\n    }\n\n    chronolast = chrononow;\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/stat/Stat.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Stat.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"mgm/Namespace.hh\"\n#include \"common/AssistedThread.hh\"\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdSys/XrdSysPthread.hh>\n#include <google/sparse_hash_map>\n#include <vector>\n#include <map>\n#include <string>\n#include <deque>\n#include <math.h>\n#include <sys/time.h>\n\nEOSMGMNAMESPACE_BEGIN\n\nclass StatAvg\n{\npublic:\n  unsigned long avg3600[3600];\n  unsigned long avg300[300];\n  unsigned long avg60[60];\n  unsigned long avg5[5];\n\n  StatAvg()\n  {\n    memset(avg3600, 0, sizeof(avg3600));\n    memset(avg300, 0, sizeof(avg300));\n    memset(avg60, 0, sizeof(avg60));\n    memset(avg5, 0, sizeof(avg5));\n  }\n\n  ~StatAvg() { };\n\n  void\n  Add(unsigned long val)\n  {\n    int64_t time_val = time(0);\n\n    if (time_val < 0) {\n      time_val = 0;\n    }\n\n    unsigned int bin3600 = time_val % 3600;\n    unsigned int bin300 = time_val % 300;\n    unsigned int bin60 = time_val % 60;\n    unsigned int bin5 = time_val % 5;\n    avg3600[(bin3600 + 1) % 3600] = 0;\n    avg3600[bin3600] += val;\n    avg300[(bin300 + 1) % 300] = 0;\n    avg300[bin300] += val;\n    avg60[(bin60 + 1) % 60] = 0;\n    avg60[bin60] += val;\n    avg5[(bin5 + 1) % 5] = 0;\n    avg5[bin5] += val;\n  }\n\n  void\n  StampZero(time_t& time_val)\n  {\n    if (time_val < 0) {\n      time_val = 0;\n    }\n\n    unsigned int bin3600 = time_val % 3600;\n    unsigned int bin300 = time_val % 300;\n    unsigned int bin60 = time_val % 60;\n    unsigned int bin5 = time_val % 5;\n    avg3600[(bin3600 + 1) % 3600] = 0;\n    avg300[(bin300 + 1) % 300] = 0;\n    avg60[(bin60 + 1) % 60] = 0;\n    avg5[(bin5 + 1) % 5] = 0;\n  }\n\n  double\n  GetAvg3600()\n  {\n    double sum = 0;\n\n    for (int i = 0; i < 3600; i++) {\n      sum += avg3600[i];\n    }\n\n    return (sum / 3599);\n  }\n\n  double\n  GetAvg300()\n  {\n    double sum = 0;\n\n    for (int i = 0; i < 300; i++) {\n      sum += avg300[i];\n    }\n\n    return (sum / 299);\n  }\n\n  double\n  GetAvg60()\n  {\n    double sum = 0;\n\n    for (int i = 0; i < 60; i++) {\n      sum += avg60[i];\n    }\n\n    return (sum / 59);\n  }\n\n  double\n  GetAvg5()\n  {\n    double sum = 0;\n\n    for (int i = 0; i < 5; i++) {\n      sum += avg5[i];\n    }\n\n    return (sum / 4);\n  }\n};\n\nclass StatExt\n{\npublic:\n  unsigned long n3600[3600];\n  unsigned long n300[300];\n  unsigned long n60[60];\n  unsigned long n5[5];\n  double sum3600[3600];\n  double sum300[300];\n  double sum60[60];\n  double sum5[5];\n  double min3600[3600];\n  double min300[300];\n  double min60[60];\n  double min5[5];\n  double max3600[3600];\n  double max300[300];\n  double max60[60];\n  double max5[5];\n\n  StatExt()\n  {\n    memset(n3600, 0, sizeof(n3600));\n    memset(n300, 0, sizeof(n300));\n    memset(n60, 0, sizeof(n60));\n    memset(n5, 0, sizeof(n5));\n\n    for (int k = 0; k < 3600; k++) {\n      min3600[k] = std::numeric_limits<long long>::max();\n      max3600[k] = std::numeric_limits<size_t>::min();\n      sum3600[k] = 0;\n    }\n\n    for (int k = 0; k < 300; k++) {\n      min300[k] = std::numeric_limits<long long>::max();\n      max300[k] = std::numeric_limits<size_t>::min();\n      sum300[k] = 0;\n    }\n\n    for (int k = 0; k < 60; k++) {\n      min60[k] = std::numeric_limits<long long>::max();\n      max60[k] = std::numeric_limits<size_t>::min();\n      sum60[k] = 0;\n    }\n\n    for (int k = 0; k < 5; k++) {\n      min5[k] = std::numeric_limits<long long>::max();\n      max5[k] = std::numeric_limits<size_t>::min();\n      sum5[k] = 0;\n    }\n  }\n\n  ~StatExt() { };\n\n  void\n  Insert(unsigned long nsample, const double& avgv, const double& minv,\n         const double& maxv)\n  {\n    int64_t time_val = time(0);\n\n    if (time_val < 0) {\n      time_val = 0;\n    }\n\n    unsigned int bin3600 = time_val % 3600;\n    unsigned int bin300 = time_val % 300;\n    unsigned int bin60 = time_val % 60;\n    unsigned int bin5 = time_val % 5;\n    n3600[(bin3600 + 1) % 3600] = 0;\n    n3600[bin3600] += nsample;\n    sum3600[(bin3600 + 1) % 3600] = 0;\n    sum3600[bin3600] += avgv * nsample;\n    min3600[(bin3600 + 1) % 3600] = std::numeric_limits<long long>::max();\n    min3600[bin3600] = std::min(min3600[bin3600], minv);\n    max3600[(bin3600 + 1) % 3600] = std::numeric_limits<size_t>::min();\n    max3600[bin3600] = std::max(max3600[bin3600], maxv);\n    n300[(bin300 + 1) % 300] = 0;\n    n300[bin300] += nsample;\n    sum300[(bin300 + 1) % 300] = 0;\n    sum300[bin300] += avgv * nsample;\n    min300[(bin300 + 1) % 300] = std::numeric_limits<long long>::max();\n    min300[bin300] = std::min(min300[bin300], minv);\n    max300[(bin300 + 1) % 300] = std::numeric_limits<size_t>::min();\n    max300[bin300] = std::max(max300[bin300], maxv);\n    n60[(bin60 + 1) % 60] = 0;\n    n60[bin60] += nsample;\n    sum60[(bin60 + 1) % 60] = 0;\n    sum60[bin60] += avgv * nsample;\n    min60[(bin60 + 1) % 60] = std::numeric_limits<long long>::max();\n    min60[bin60] = std::min(min60[bin60], minv);\n    max60[(bin60 + 1) % 60] = std::numeric_limits<size_t>::min();\n    max60[bin60] = std::max(max60[bin60], maxv);\n    n5[(bin5 + 1) % 5] = 0;\n    n5[bin5] += nsample;\n    sum5[(bin5 + 1) % 5] = 0;\n    sum5[bin5] += avgv * nsample;\n    min5[(bin5 + 1) % 5] = std::numeric_limits<long long>::max();\n    min5[bin5] = std::min(min5[bin5], minv);\n    max5[(bin5 + 1) % 5] = std::numeric_limits<size_t>::min();\n    max5[bin5] = std::max(max5[bin5], maxv);\n  }\n\n  void\n  StampZero(time_t& time_val)\n  {\n    if (time_val < 0) {\n      time_val = 0;\n    }\n\n    unsigned int bin3600 = time_val % 3600;\n    unsigned int bin300 = time_val % 300;\n    unsigned int bin60 = time_val % 60;\n    unsigned int bin5 = time_val % 5;\n    n3600[(bin3600 + 1) % 3600] = 0;\n    n300[(bin300 + 1) % 300] = 0;\n    n60[(bin60 + 1) % 60] = 0;\n    n5[(bin5 + 1) % 5] = 0;\n    sum3600[(bin3600 + 1) % 3600] = 0;\n    sum300[(bin300 + 1) % 300] = 0;\n    sum60[(bin60 + 1) % 60] = 0;\n    sum5[(bin5 + 1) % 5] = 0;\n    min3600[(bin3600 + 1) % 3600] = std::numeric_limits<long long>::max();\n    min300[(bin300 + 1) % 300] = std::numeric_limits<long long>::max();\n    min60[(bin60 + 1) % 60] = std::numeric_limits<long long>::max();\n    min5[(bin5 + 1) % 5] = std::numeric_limits<long long>::max();\n    max3600[(bin3600 + 1) % 3600] = std::numeric_limits<size_t>::min();\n    max300[(bin300 + 1) % 300] = std::numeric_limits<size_t>::min();\n    max60[(bin60 + 1) % 60] = std::numeric_limits<size_t>::min();\n    max5[(bin5 + 1) % 5] = std::numeric_limits<size_t>::min();\n  }\n\n  double\n  GetN3600()\n  {\n    unsigned long sum = 0;\n\n    for (int i = 0; i < 3600; i++) {\n      sum += n3600[i];\n    }\n\n    return (double) sum;\n  }\n\n  double\n  GetAvg3600()\n  {\n    double sum = 0;\n    double n = 0;\n\n    for (int i = 0; i < 3600; i++) {\n      n += n3600[i];\n      sum += sum3600[i];\n    }\n\n    return (sum / n);\n  }\n\n  double\n  GetMin3600()\n  {\n    double minval = std::numeric_limits<long long>::max();\n\n    for (int i = 0; i < 3600; i++) {\n      minval = std::min(min3600[i], minval);\n    }\n\n    return double(minval);\n  }\n\n  double\n  GetMax3600()\n  {\n    double maxval = std::numeric_limits<size_t>::min();\n\n    for (int i = 0; i < 3600; i++) {\n      maxval = std::max(max3600[i], maxval);\n    }\n\n    return double(maxval);\n  }\n\n  double\n  GetN300()\n  {\n    unsigned long sum = 0;\n\n    for (int i = 0; i < 300; i++) {\n      sum += n300[i];\n    }\n\n    return (double) sum;\n  }\n\n  double\n  GetAvg300()\n  {\n    double sum = 0;\n    double n = 0;\n\n    for (int i = 0; i < 300; i++) {\n      n += n300[i];\n      sum += sum300[i];\n    }\n\n    return (sum / n);\n  }\n\n  double\n  GetMin300()\n  {\n    double minval = std::numeric_limits<long long>::max();\n\n    for (int i = 0; i < 300; i++) {\n      minval = std::min(min300[i], minval);\n    }\n\n    return double(minval);\n  }\n\n  double\n  GetMax300()\n  {\n    double maxval = std::numeric_limits<size_t>::min();\n\n    for (int i = 0; i < 300; i++) {\n      maxval = std::max(max300[i], maxval);\n    }\n\n    return double(maxval);\n  }\n\n  double\n  GetN60()\n  {\n    unsigned long sum = 0;\n\n    for (int i = 0; i < 60; i++) {\n      sum += n60[i];\n    }\n\n    return (double) sum;\n  }\n\n  double\n  GetAvg60()\n  {\n    double sum = 0;\n    double n = 0;\n\n    for (int i = 0; i < 60; i++) {\n      n += n60[i];\n      sum += sum60[i];\n    }\n\n    return (sum / n);\n  }\n\n  double\n  GetMin60()\n  {\n    double minval = std::numeric_limits<long long>::max();\n\n    for (int i = 0; i < 60; i++) {\n      minval = std::min(min60[i], minval);\n    }\n\n    return double(minval);\n  }\n\n  double\n  GetMax60()\n  {\n    double maxval = std::numeric_limits<size_t>::min();\n\n    for (int i = 0; i < 60; i++) {\n      maxval = std::max(max60[i], maxval);\n    }\n\n    return double(maxval);\n  }\n\n  double\n  GetN5()\n  {\n    unsigned long sum = 0;\n\n    for (int i = 0; i < 5; i++) {\n      sum += n5[i];\n    }\n\n    return (double) sum;\n  }\n\n  double\n  GetAvg5()\n  {\n    double sum = 0;\n    double n = 0;\n\n    for (int i = 0; i < 5; i++) {\n      n += n5[i];\n      sum += sum5[i];\n    }\n\n    return (sum / n);\n  }\n\n  double\n  GetMin5()\n  {\n    double minval = std::numeric_limits<long long>::max();\n\n    for (int i = 0; i < 5; i++) {\n      minval = std::min(min5[i], minval);\n    }\n\n    return double(minval);\n  }\n\n  double\n  GetMax5()\n  {\n    double maxval = std::numeric_limits<size_t>::min();\n\n    for (int i = 0; i < 5; i++) {\n      maxval = std::max(max5[i], maxval);\n    }\n\n    return double(maxval);\n  }\n\n};\n\n\n#define EXEC_TIMING_BEGIN(__ID__)               \\\n  struct timeval start__ID__;                   \\\n  struct timeval stop__ID__;                    \\\n  struct timezone tz__ID__;                     \\\n  gettimeofday(&start__ID__, &tz__ID__);\n\n#define EXEC_TIMING_END(__ID__)                                         \\\n  gettimeofday(&stop__ID__, &tz__ID__);                                 \\\n  double __exec_time__ = ((stop__ID__.tv_sec-start__ID__.tv_sec)*1000.0) + ((stop__ID__.tv_usec-start__ID__.tv_usec)/1000.0); \\\n  if (gOFS != nullptr) {                                                 \\\n    gOFS->MgmStats.AddExec(__ID__, __exec_time__);                     \\\n  }\n\nclass Stat\n{\npublic:\n  mutable XrdSysMutex mMutex;\n\n  // first is name of value, then the map\n  google::sparse_hash_map<std::string, google::sparse_hash_map<uid_t, unsigned long long> >\n  StatsUid;\n  google::sparse_hash_map<std::string, google::sparse_hash_map<gid_t, unsigned long long> >\n  StatsGid;\n  google::sparse_hash_map<std::string, google::sparse_hash_map<std::string, unsigned long long> >\n  StatsApp;\n  google::sparse_hash_map<std::string, google::sparse_hash_map<uid_t, StatAvg> >\n  StatAvgUid;\n  google::sparse_hash_map<std::string, google::sparse_hash_map<gid_t, StatAvg> >\n  StatAvgGid;\n  google::sparse_hash_map<std::string, google::sparse_hash_map<std::string, StatAvg> >\n  StatAvgApp;\n  google::sparse_hash_map<std::string, google::sparse_hash_map<uid_t, StatExt> >\n  StatExtUid;\n  google::sparse_hash_map<std::string, google::sparse_hash_map<gid_t, StatExt> >\n  StatExtGid;\n  google::sparse_hash_map<std::string, google::sparse_hash_map<std::string, StatExt> >\n  StatExtApp;\n  google::sparse_hash_map<std::string, std::deque<float> > StatExec;\n  google::sparse_hash_map<std::string, double> CumulativeTimeExec;\n\n  void Add(const char* tag, uid_t uid, gid_t gid, unsigned long val, const std::string app=\"\");\n\n  void AddExt(const char* tag, uid_t uid, gid_t gid, unsigned long nsample,\n              const double& avgv, const double& minv, const double& maxv, const std::string app=\"\");\n\n  void AddExec(const char* tag, float exectime);\n\n  // warning: you have to lock the mutex if directly used\n  unsigned long long GetTotal(const char* tag);\n  double GetCumulativeExecTime(const char* tag);\n\n  // warning: you have to lock the mutex if directly used\n  double GetTotalAvg3600(const char* tag);\n  double GetTotalNExt3600(const char* tag);\n  double GetTotalAvgExt3600(const char* tag);\n  double GetTotalMinExt3600(const char* tag);\n  double GetTotalMaxExt3600(const char* tag);\n\n  // warning: you have to lock the mutex if directly used\n  double GetTotalAvg300(const char* tag);\n  double GetTotalNExt300(const char* tag);\n  double GetTotalAvgExt300(const char* tag);\n  double GetTotalMinExt300(const char* tag);\n  double GetTotalMaxExt300(const char* tag);\n\n  // warning: you have to lock the mutex if directly used\n  double GetTotalAvg60(const char* tag);\n  double GetTotalNExt60(const char* tag);\n  double GetTotalAvgExt60(const char* tag);\n  double GetTotalMinExt60(const char* tag);\n  double GetTotalMaxExt60(const char* tag);\n\n  // warning: you have to lock the mutex if directly used\n  double GetTotalAvg5(const char* tag);\n  double GetTotalNExt5(const char* tag);\n  double GetTotalAvgExt5(const char* tag);\n  double GetTotalMinExt5(const char* tag);\n  double GetTotalMaxExt5(const char* tag);\n\n  // warning: you have to lock the mutex if directly used\n  double GetExec(const char* tag, double& deviation, double& percential,\n                 double& max);\n\n  // warning: you have to lock the mutex if directly used\n  double GetTotalExec(double& deviation);\n\n  void Clear();\n\n  //----------------------------------------------------------------------------\n  //! Get read contention approximation\n  //----------------------------------------------------------------------------\n  double GetReadContention() const;\n\n  //----------------------------------------------------------------------------\n  //! Get write contention approximation\n  //----------------------------------------------------------------------------\n  double GetWriteContention() const;\n\n\n  void PrintOutTotal(XrdOucString& out, bool details = false,\n                     bool monitoring = false, bool numerical = false, bool apps = false);\n\n  void Circulate(ThreadAssistant& assistant) noexcept;\n\n  ~Stat() = default;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/tgc/AsyncResult.hh",
    "content": "// ----------------------------------------------------------------------\n// File: AsyncResult.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGC_ASYNCRESULT_HH__\n#define __EOSMGMTGC_ASYNCRESULT_HH__\n\n#include \"mgm/Namespace.hh\"\n\n#include <string>\n#include <optional>\n\n/**\n * @file AsyncResult.hh\n *\n * @brief Class representing the result of polling an asynchronous task which\n * may still be running.  In addition this class can store the result of a\n * previous execution of the task.\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class representing the result of polling an asynchronous task which\n//! may still be running.  In addition this class can store the result of a\n//! previous execution of the task.\n//------------------------------------------------------------------------------\ntemplate<typename Value> class AsyncResult {\nprivate:\n  //----------------------------------------------------------------------------\n  //! Private constructor to force the use of the factory methods\n  //----------------------------------------------------------------------------\n  AsyncResult(): m_state(State::PENDING_AND_NO_PREVIOUS_VALUE) {}\n\npublic:\n\n  enum class State {\n    PENDING_AND_NO_PREVIOUS_VALUE, //! Task still running and there is no result from a previous task\n    PENDING_AND_PREVIOUS_VALUE, //! Task still running and there is a result from a previous task\n    VALUE, //! Task has completed successfully and has written a syntactically valid value to its standard out\n    ERROR //! Task has failed with an error\n  };\n\n  static const char* stateToStr(const State &state) {\n    switch(state) {\n    case State::PENDING_AND_NO_PREVIOUS_VALUE: return \"PENDING_AND_NO_PREVIOUS_VALUE\";\n    case State::PENDING_AND_PREVIOUS_VALUE: return \"PENDING_AND_PREVIOUS_VALUE\";\n    case State::VALUE: return \"VALUE\";\n    case State::ERROR: return \"ERROR\";\n    default: return \"UNKNOWN\";\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Create a PENDING_AND_NO_PREVIOUS_VALUE result\n  //----------------------------------------------------------------------------\n  static AsyncResult createPendingAndNoPreviousValue() {\n    AsyncResult result;\n    result.m_state = State::PENDING_AND_NO_PREVIOUS_VALUE;\n    return result;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Create a PENDING_AND_PREVIOUS_VALUE result\n  //----------------------------------------------------------------------------\n  static AsyncResult createPendingAndPreviousValue(const Value &previousValue) {\n    AsyncResult result;\n    result.m_state = State::PENDING_AND_PREVIOUS_VALUE;\n    result.m_previousValue = previousValue;\n    return result;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Create a VALUE result\n  //----------------------------------------------------------------------------\n  static AsyncResult createValue(const Value &value) {\n    AsyncResult result;\n    result.m_state = State::VALUE;\n    result.m_value = value;\n    return result;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Create an ERROR result\n  //----------------------------------------------------------------------------\n  static AsyncResult createError(const std::string &error) {\n    AsyncResult result;\n    result.m_state = State::ERROR;\n    result.m_error = error;\n    return result;\n  }\n\n  //----------------------------------------------------------------------------\n  //! @return state of the result\n  //----------------------------------------------------------------------------\n  State getState() const {\n    return m_state;\n  }\n\n  //----------------------------------------------------------------------------\n  //! @return optional previous value\n  //----------------------------------------------------------------------------\n  std::optional<Value> getPreviousValue() const {\n    return m_previousValue;\n  }\n\n  //----------------------------------------------------------------------------\n  //! @return optional value\n  //----------------------------------------------------------------------------\n  std::optional<Value> getValue() const {\n    return m_value;\n  }\n\n  //----------------------------------------------------------------------------\n  //! @return optional error\n  //----------------------------------------------------------------------------\n  std::optional<std::string> getError() const {\n    return m_error;\n  }\n\nprivate:\n\n  State m_state;\n  std::optional<Value> m_previousValue;\n  std::optional<Value> m_value;\n  std::optional<std::string> m_error;\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/AsyncUint64ShellCmd.cc",
    "content": "// ----------------------------------------------------------------------\n// File: AsyncUint64ShellCmd.cc\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/tgc/AsyncUint64ShellCmd.hh\"\n#include \"mgm/tgc/SmartSpaceStats.hh\"\n#include \"mgm/CtaUtils.hh\"\n\n#include <chrono>\n#include <thread>\n\nEOSTGCNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n// Constructor\nAsyncUint64ShellCmd::AsyncUint64ShellCmd(ITapeGcMgm& mgm):\n  m_mgm(mgm)\n{\n}\n\n//----------------------------------------------------------------------------\n//! Return the current result of the shell command\n//----------------------------------------------------------------------------\nAsyncUint64ShellCmd::Uint64AsyncResult\nAsyncUint64ShellCmd::getUint64FromShellCmdStdOut(const std::string& cmdStr)\n{\n  try {\n    std::lock_guard<std::mutex> lock(m_mutex);\n\n    // Invalid means this is either the very first call to get() or it is\n    // after the successful reading of a previous async result\n    if (!m_future.valid()) {\n      m_future = std::async(std::launch::async,\n                            &AsyncUint64ShellCmd::runShellCmdAndParseStdOut, this, cmdStr);\n    }\n\n    if (!m_future.valid()) {\n      throw std::runtime_error(\"Failed to create a valid std::future\");\n    }\n\n    const auto futureStatus = m_future.wait_for(std::chrono::seconds(0));\n\n    switch (futureStatus) {\n    case std::future_status::deferred: { // Should never happen\n      throw std::runtime_error(\"futureStatus is deferred\");\n    }\n\n    case std::future_status::ready: {\n      const std::uint64_t uint64Result = m_future.get();\n      const auto result = Uint64AsyncResult::createValue(uint64Result);\n      m_previousResult = uint64Result;\n      m_future = {};\n      return result;\n    }\n\n    case std::future_status::timeout: {\n      const auto result = m_previousResult ?\n                          Uint64AsyncResult::createPendingAndPreviousValue(m_previousResult.value()) :\n                          Uint64AsyncResult::createPendingAndNoPreviousValue();\n      return result;\n    }\n\n    default: // Should never happen\n      throw std::runtime_error(\"Unknown std::future_status value\");\n    }\n  } catch (std::exception& ex) {\n    m_previousResult = std::nullopt;\n    return Uint64AsyncResult::createError(ex.what());\n  } catch (...) {\n    m_previousResult = std::nullopt;\n    return Uint64AsyncResult::createError(\"Caught an unknown exception\");\n  }\n}\n\n//------------------------------------------------------------------------------\n//! Run the specified shell command and parse its standard out as a uint64\n//! @param cmdStr The shell command string to be executed\n//------------------------------------------------------------------------------\nstd::uint64_t\nAsyncUint64ShellCmd::runShellCmdAndParseStdOut(const std::string cmdStr)\n{\n  const ssize_t outputMaxLen = 256;\n  const std::string cmdOut = m_mgm.getStdoutFromShellCmd(cmdStr, outputMaxLen);\n  return CtaUtils::toUint64(cmdOut);\n}\n\nEOSTGCNAMESPACE_END\n"
  },
  {
    "path": "mgm/tgc/AsyncUint64ShellCmd.hh",
    "content": "// ----------------------------------------------------------------------\n// File: AsyncUint64ShellCmd.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGC_ASYNCUINT64SHELLCMD_HH__\n#define __EOSMGMTGC_ASYNCUINT64SHELLCMD_HH__\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/tgc/AsyncResult.hh\"\n#include \"mgm/tgc/ITapeGcMgm.hh\"\n\n#include <cstdint>\n#include <future>\n#include <mutex>\n#include <string>\n\n/**\n * @file AsyncUint64ShellCmd.hh\n *\n * @brief Class used to asynchronously run no more than one shell command at a\n * time and allow the uint64 result printed on its standard out to be polled.\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\nclass SmartSpaceStats;\n\n//------------------------------------------------------------------------------\n//! Class used to asynchronously run no more than one shell command at a time\n//! and allow the uint64 result printed on its standard out to be polled.\n//------------------------------------------------------------------------------\nclass AsyncUint64ShellCmd {\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //! @param mgm reference to the object providing the interface to the MGM\n  //----------------------------------------------------------------------------\n  AsyncUint64ShellCmd(ITapeGcMgm &mgm);\n\n  typedef AsyncResult<std::uint64_t> Uint64AsyncResult;\n\n  //----------------------------------------------------------------------------\n  //! @cmdStr The shell command string to execute\n  //! @return the current result of the shell command\n  //! @note this method will automatically launch a shell command if necessary\n  //----------------------------------------------------------------------------\n  Uint64AsyncResult getUint64FromShellCmdStdOut(const std::string &cmdStr);\n\nprivate:\n\n  std::mutex m_mutex;\n  ITapeGcMgm &m_mgm;\n  std::optional<std::uint64_t> m_previousResult;\n  std::future<std::uint64_t> m_future;\n\n  //----------------------------------------------------------------------------\n  //! Run the specified shell command and parse its standard out as a uint64\n  //! @param cmdStr the shell command string to execute\n  //! @return the parsed standard out as a uint64\n  //----------------------------------------------------------------------------\n  std::uint64_t runShellCmdAndParseStdOut(std::string cmdStr);\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/BlockingFlag.hh",
    "content": "// ----------------------------------------------------------------------\n// File: BlockingFlag.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_BLOCKINGFLAG_HH__\n#define __EOSMGM_BLOCKINGFLAG_HH__\n\n#include <condition_variable>\n#include <mutex>\n#include <stdexcept>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file BlockingFlag.hh\n *\n * @brief Boolean flag that starts with a value of false and can have timed\n * waits on its value becoming true.\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n//! Boolean flag that starts with a value of false and can have timed waits on\n//! its value becoming true.\n//----------------------------------------------------------------------------\nclass BlockingFlag {\npublic:\n\n  //--------------------------------------------------------------------------\n  //! Constructor\n  //--------------------------------------------------------------------------\n  BlockingFlag(): m_flag(false) {\n  }\n\n  //--------------------------------------------------------------------------\n  //! Boolean operator\n  //--------------------------------------------------------------------------\n  operator bool() const {\n    std::unique_lock<std::mutex> lock(m_mutex);\n    return m_flag;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Waits the specified duration for the flag to become true\n  //!\n  //! @param duration The amount of time to wait\n  //! @return True if the flag has been set to true, else false if a timeout\n  //! has occurred\n  //--------------------------------------------------------------------------\n  template<class Duration> bool waitForTrue(Duration duration) noexcept {\n    try {\n      std::unique_lock<std::mutex> lock(m_mutex);\n      return m_cond.wait_for(lock, duration, [&]{return m_flag;});\n    } catch(std::exception &ex) {\n      eos_static_err(\"msg=\\\"%s\\\"\", ex.what());\n    } catch(...) {\n      eos_static_err(\"msg=\\\"Caught an unknown exception\\\"\");\n    }\n    return false;\n  }\n\n  //--------------------------------------------------------------------------\n  //! Sets the flag to true and wakes all threads waiting on waitForTrue()\n  //--------------------------------------------------------------------------\n  void setToTrue() {\n    std::unique_lock<std::mutex> lock(m_mutex);\n    m_flag = true;\n    m_cond.notify_all();\n  }\n\nprivate:\n\n  //--------------------------------------------------------------------------\n  //! Mutex protecting the flag\n  //--------------------------------------------------------------------------\n  mutable std::mutex m_mutex;\n\n  //--------------------------------------------------------------------------\n  //! The condition variable of the flag\n  //--------------------------------------------------------------------------\n  std::condition_variable m_cond;\n\n  //--------------------------------------------------------------------------\n  //! The flag\n  //--------------------------------------------------------------------------\n  bool m_flag;\n\n}; // class BlockingFlag\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/CachedValue.hh",
    "content": "// ----------------------------------------------------------------------\n// File: CachedValue.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGC_CACHEDVALUE_HH__\n#define __EOSMGMTGC_CACHEDVALUE_HH__\n\n#include \"mgm/Namespace.hh\"\n\n#include <ctime>\n#include <functional>\n#include <mutex>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file CachedValue.hh\n *\n * @brief Templated class for creating a time based cache for a single\n * variable.\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n//! Templated class for creating a time based cache for a single variable.\n//!\n//! @tparam ValueType The type of the value to be cached.\n//----------------------------------------------------------------------------\ntemplate <typename ValueType> class CachedValue {\npublic:\n  //--------------------------------------------------------------------------\n  //! Constructor\n  //! @param valueGetter callable responsible for getting a new value\n  //! @param maxAgeSecs age at which a call to get() will renew the cache. A\n  //! value of zero means the a call to get() will always renew the cache.\n  //--------------------------------------------------------------------------\n  CachedValue(std::function<ValueType()> valueGetter, const std::time_t maxAgeSecs):\n    m_valueHasNeverBeenSet(true),\n    m_valueGetter(valueGetter),\n    m_maxAgeSecs(maxAgeSecs),\n    m_timestamp(time(nullptr))\n  {\n  }\n\n  //--------------------------------------------------------------------------\n  //! @return the cached value\n  //--------------------------------------------------------------------------\n  ValueType\n  get()\n  {\n    std::lock_guard<std::mutex> lock(m_mutex);\n\n    const std::time_t now = time(nullptr);\n    const std::time_t age = now - m_timestamp;\n\n    if(m_valueHasNeverBeenSet || age >= m_maxAgeSecs) {\n      m_valueHasNeverBeenSet = false;\n      m_timestamp = now;\n      m_value = m_valueGetter();\n    }\n\n    return m_value;\n  }\n\nprivate:\n  /// Mutex used to protect the cached value\n  std::mutex m_mutex;\n\n  /// True if the cached value has never been set\n  bool m_valueHasNeverBeenSet;\n\n  /// The cached value\n  ValueType m_value;\n\n  /// Callable responsible for getting a new value\n  std::function<ValueType()> m_valueGetter;\n\n  /// Age at which a call to get() will renew the cache\n  std::time_t m_maxAgeSecs;\n\n  /// The timestamp of when the value was last updated\n  std::time_t m_timestamp;\n}; // class CachedValue\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/Constants.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Constants.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGC_CONSTANTS_HH__\n#define __EOSMGMTGC_CONSTANTS_HH__\n\n#include \"mgm/Namespace.hh\"\n\n#include <cstdint>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file Constants.hh\n *\n * @brief Constants specific to the implementation of the tape aware garbage\n * collector.\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n/// Default maximum age in seconds of a tape-ware garbage collector's\n/// cached configuration\nconst std::uint64_t TGC_DEFAULT_MAX_CONFIG_CACHE_AGE_SECS = 10;\n\n// Maximum number of bins within a histogram of freed bytes over time\nconst std::uint32_t TGC_FREED_BYTES_HISTOGRAM_MAX_NB_BINS = 65535;\n\n/// Fixed number of bins within a histogram of freed bytes over time\nconst std::uint32_t TGC_FREED_BYTES_HISTOGRAM_NB_BINS = 600;\n\n// Maximum bin width in seconds of a histogram of freed bytes over time\nconst std::uint32_t TGC_FREED_BYTES_HISTOGRAM_MAX_BIN_WIDTH_SECS = 65535;\n\n/// Default bin width in seconds of a histogram of freed bytes over time\nconst std::uint32_t TGC_DEFAULT_FREED_BYTES_HISTOGRAM_BIN_WIDTH_SECS = 1;\n\n/// Name of a space configuration member\nconstexpr const char * TGC_NAME_QRY_PERIOD_SECS = \"tgc.qryperiodsecs\";\n\n/// Maximum delay in seconds between EOS space queries for the tape-aware GC\nconst std::uint64_t TGC_MAX_QRY_PERIOD_SECS = TGC_FREED_BYTES_HISTOGRAM_NB_BINS * TGC_FREED_BYTES_HISTOGRAM_MAX_BIN_WIDTH_SECS;\n\n/// Default delay in seconds between EOS space queries for the tape-aware GC\nconst std::uint64_t TGC_DEFAULT_QRY_PERIOD_SECS = 320;\n\n/// Name of a space configuration member\nconstexpr const char * TGC_NAME_AVAIL_BYTES = \"tgc.availbytes\";\n\n/// Default number of available bytes the garbage collector is targeting\nconst std::uint64_t TGC_DEFAULT_AVAIL_BYTES = 0;\n\n/// Name of a space configuration member\nconstexpr const char * TGC_NAME_FREE_BYTES_SCRIPT = \"tgc.freebytesscript\";\n\n/// Default path of optional script used to determine the number of free bytes in a given EOS space\nconstexpr const char * TGC_DEFAULT_FREE_BYTES_SCRIPT = \"\";\n\n/// Name of a space configuration member\nconstexpr const char * TGC_NAME_TOTAL_BYTES = \"tgc.totalbytes\";\n\n/// Default total number of bytes before garbage collection can begin\nconst std::uint64_t TGC_DEFAULT_TOTAL_BYTES = 1000000000000000000UL; // 1 Exabyte\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/DummyClock.hh",
    "content": "// ----------------------------------------------------------------------\n// File: DummyClock.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGC_DUMMYCLOCK_HH__\n#define __EOSMGMTGC_DUMMYCLOCK_HH__\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/tgc/IClock.hh\"\n\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file DummyClock.hh\n *\n * @brief A dummy clock to be used for unit testing.\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Provides the current time using std::time()\n//------------------------------------------------------------------------------\nclass DummyClock: public IClock {\npublic:\n\n  //------------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param initialTime Initial value for the current (dummy) time in seconds\n  //! since the Epoch, 1970-01-01 00:00:00 +0000 (UTC)\n  //------------------------------------------------------------------------------\n  DummyClock(std::time_t initialTime): m_currentTime(initialTime) {\n  }\n\n  //------------------------------------------------------------------------------\n  //! @return Number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC)\n  //------------------------------------------------------------------------------\n  std::time_t getTime() override {\n    return m_currentTime;\n  }\n\n  //------------------------------------------------------------------------------\n  //! Set the current (dummy) time in seconds since the Epoch,\n  //! 1970-01-01 00:00:00 +0000 (UTC)\n  //!\n  //! @param currentTime Current (dummy) time in seconds since the Epoch,\n  //! 1970-01-01 00:00:00 +0000 (UTC)\n  //------------------------------------------------------------------------------\n  void setTime(const std::time_t currentTime) {\n    m_currentTime = currentTime;\n  }\n\nprivate:\n\n  //------------------------------------------------------------------------------\n  //! Current (dummy) time in seconds since the Epoch,\n  //! 1970-01-01 00:00:00 +0000 (UTC)\n  //------------------------------------------------------------------------------\n  std::time_t m_currentTime;\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/DummyTapeGcMgm.cc",
    "content": "// ----------------------------------------------------------------------\n// File: TapeGc.cc\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/tgc/Constants.hh\"\n#include \"mgm/tgc/DummyTapeGcMgm.hh\"\n\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nDummyTapeGcMgm::DummyTapeGcMgm():\nm_nbCallsToGetTapeGcSpaceConfig(0),\nm_nbCallsToGetSpaceStats(0),\nm_nbCallsToFileInNamespaceAndNotScheduledForDeletion(0),\nm_nbCallsToGetFileSizeBytes(0), m_nbCallsToEvictAsRoot(0)\n{\n}\n\n//----------------------------------------------------------------------------\n//! @return The configuration of a tape-aware garbage collector for the\n//! specified space.\n//! @param spaceName The name of the space\n//----------------------------------------------------------------------------\nSpaceConfig\nDummyTapeGcMgm::getTapeGcSpaceConfig(const std::string &spaceName) {\n  const SpaceConfig defaultConfig;\n\n  try {\n    std::lock_guard<std::mutex> lock(m_mutex);\n    m_nbCallsToGetTapeGcSpaceConfig++;\n\n    auto itor = m_spaceToTapeGcConfig.find(spaceName);\n    if(itor == m_spaceToTapeGcConfig.end()) {\n      return defaultConfig;\n    } else {\n      return itor->second;\n    }\n  } catch(...) {\n    return defaultConfig;\n  }\n}\n\n//----------------------------------------------------------------------------\n// Determine if the specified file exists and is not scheduled for deletion\n//----------------------------------------------------------------------------\nbool\nDummyTapeGcMgm::fileInNamespaceAndNotScheduledForDeletion(const IFileMD::id_t /* fid */)\n{\n  std::lock_guard<std::mutex> lock(m_mutex);\n  m_nbCallsToFileInNamespaceAndNotScheduledForDeletion++;\n  return true;\n}\n\n//----------------------------------------------------------------------------\n// Return statistics about the specified space\n//----------------------------------------------------------------------------\nSpaceStats\nDummyTapeGcMgm::getSpaceStats(const std::string &space) const {\n  const SpaceStats defaultStats;\n\n  try {\n    std::lock_guard<std::mutex> lock(m_mutex);\n    m_nbCallsToGetSpaceStats++;\n\n    auto itor = m_spaceToStats.find(space);\n    if(itor == m_spaceToStats.end()) {\n      return defaultStats;\n    } else {\n      return itor->second;\n    }\n  } catch(...) {\n    return defaultStats;\n  }\n}\n\n//----------------------------------------------------------------------------\n// Return size of the specified file\n//----------------------------------------------------------------------------\nuint64_t\nDummyTapeGcMgm::getFileSizeBytes(const IFileMD::id_t /* fid */)\n{\n  std::lock_guard<std::mutex> lock(m_mutex);\n  m_nbCallsToGetFileSizeBytes++;\n  return 1;\n}\n\n//----------------------------------------------------------------------------\n// Execute evict as user root\n//----------------------------------------------------------------------------\nvoid\nDummyTapeGcMgm::evictAsRoot(const IFileMD::id_t /* fid */)\n{\n  std::lock_guard<std::mutex> lock(m_mutex);\n  m_nbCallsToEvictAsRoot++;\n}\n\n//----------------------------------------------------------------------------\n// Return map from file system ID to EOS space name\n//----------------------------------------------------------------------------\nstd::map<common::FileSystem::fsid_t, std::string>\nDummyTapeGcMgm::getFsIdToSpaceMap()\n{\n  return std::map<common::FileSystem::fsid_t, std::string> ();\n}\n\n//----------------------------------------------------------------------------\n// Return map from EOS space name to disk replicas within that space\n//----------------------------------------------------------------------------\nstd::map<std::string, std::set<ITapeGcMgm::FileIdAndCtime> >\nDummyTapeGcMgm::getSpaceToDiskReplicasMap(const std::set<std::string> &spacesToMap, std::atomic<bool> &stop,\n  uint64_t &nbfilesScanned)\n{\n  nbfilesScanned = 0;\n  return std::map<std::string, std::set<FileIdAndCtime> >();\n}\n\n//----------------------------------------------------------------------------\n// Get the stdout of the specified shell cmd as a string\n//----------------------------------------------------------------------------\nstd::string\nDummyTapeGcMgm::getStdoutFromShellCmd(const std::string &cmdStr, const ssize_t maxLen) const\n{\n  std::lock_guard<std::mutex> lock(m_mutex);\n\n  return m_stdoutFromShellCmd;\n}\n\n//----------------------------------------------------------------------------\n// Set the configuration of the tape-aware garbage collector\n//----------------------------------------------------------------------------\nvoid\nDummyTapeGcMgm::setTapeGcSpaceConfig(const std::string &space,\n  const SpaceConfig &config) {\n  std::lock_guard<std::mutex> lock(m_mutex);\n\n  m_spaceToTapeGcConfig[space] = config;\n}\n\n//----------------------------------------------------------------------------\n// Set the statistics of the specified EOS space.\n//----------------------------------------------------------------------------\nvoid\nDummyTapeGcMgm::setSpaceStats(const std::string &space,\n  const SpaceStats &spaceStats) {\n  std::lock_guard<std::mutex> lock(m_mutex);\n\n  m_spaceToStats[space] = spaceStats;\n}\n\n//------------------------------------------------------------------------------\n// Return number of times getTapeGcSpaceConfig() has been called\n//------------------------------------------------------------------------------\nuint64_t\nDummyTapeGcMgm::getNbCallsToGetTapeGcSpaceConfig() const {\n  std::lock_guard<std::mutex> lock(m_mutex);\n\n  return m_nbCallsToGetTapeGcSpaceConfig;\n}\n\n//------------------------------------------------------------------------------\n// Return number of times getSpaceStats() has been called\n//------------------------------------------------------------------------------\nuint64_t\nDummyTapeGcMgm::getNbCallsToGetSpaceStats() const {\n  std::lock_guard<std::mutex> lock(m_mutex);\n\n  return m_nbCallsToGetSpaceStats;\n}\n\n//------------------------------------------------------------------------------\n// Return number of times fileInNamespaceAndNotScheduledForDeletion() has been\n// called\n//------------------------------------------------------------------------------\nuint64_t\nDummyTapeGcMgm::getNbCallsToFileInNamespaceAndNotScheduledForDeletion() const {\n  std::lock_guard<std::mutex> lock(m_mutex);\n\n  return m_nbCallsToFileInNamespaceAndNotScheduledForDeletion;\n}\n\n//------------------------------------------------------------------------------\n// Return number of times getFileSizeBytes() has been called\n//------------------------------------------------------------------------------\nuint64_t\nDummyTapeGcMgm::getNbCallsToGetFileSizeBytes() const {\n  std::lock_guard<std::mutex> lock(m_mutex);\n\n  return m_nbCallsToGetFileSizeBytes;\n}\n\n//------------------------------------------------------------------------------\n// Return number of times evictAsRoot() has been called\n//------------------------------------------------------------------------------\nuint64_t\nDummyTapeGcMgm::getNbCallsToEvictAsRoot() const {\n  std::lock_guard<std::mutex> lock(m_mutex);\n\n  return m_nbCallsToEvictAsRoot;\n}\n\n//----------------------------------------------------------------------------\n// Set the stdout of the specified shell cmd as a string\n//----------------------------------------------------------------------------\nvoid\nDummyTapeGcMgm::setStdoutFromShellCmd(const std::string &stdoutFromShellCmd)\n{\n  std::lock_guard<std::mutex> lock(m_mutex);\n\n  m_stdoutFromShellCmd = stdoutFromShellCmd;\n}\n\nEOSTGCNAMESPACE_END\n"
  },
  {
    "path": "mgm/tgc/DummyTapeGcMgm.hh",
    "content": "// ----------------------------------------------------------------------\n// File: DummyTapeGcMgm.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGC_DUMMYTAPEGCMGM_HH__\n#define __EOSMGMTGC_DUMMYTAPEGCMGM_HH__\n\n#include \"mgm/tgc/ITapeGcMgm.hh\"\n\n#include <map>\n#include <mutex>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file DummyTapeGcMgm.hh\n *\n * @brief A dummy implementation of access to the EOS MGM.  The main purpose of\n * this class is to facilitate unit testing.\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! A dummy implementation of access to the EOS MGM.  The main purpose of this\n//! class is to facilitate unit testing.\n//------------------------------------------------------------------------------\nclass DummyTapeGcMgm: public ITapeGcMgm {\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  DummyTapeGcMgm();\n\n  //----------------------------------------------------------------------------\n  //! Delete copy constructor\n  //----------------------------------------------------------------------------\n  DummyTapeGcMgm(const DummyTapeGcMgm &) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Delete move constructor\n  //----------------------------------------------------------------------------\n  DummyTapeGcMgm(const DummyTapeGcMgm &&) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Delete assignment operator\n  //----------------------------------------------------------------------------\n  DummyTapeGcMgm &operator=(const DummyTapeGcMgm &) = delete;\n\n  //----------------------------------------------------------------------------\n  //! @return The configuration of a tape-aware garbage collector for the\n  //! specified space.\n  //! @param spaceName The name of the space\n  //----------------------------------------------------------------------------\n  SpaceConfig getTapeGcSpaceConfig(const std::string &spaceName) override;\n\n  //----------------------------------------------------------------------------\n  //! @return Statistics about the specified space\n  //! @param space The name of the EOS space to be queried\n  //! @throw TapeAwareGcSpaceNotFound when the EOS space named m_spaceName\n  //! cannot be found\n  //----------------------------------------------------------------------------\n  SpaceStats getSpaceStats(const std::string &space) const override;\n\n  //----------------------------------------------------------------------------\n  //! @param fid The file identifier\n  //! @return The size of the specified file in bytes.  If the file cannot be\n  //! found in the EOS namespace then a file size of 0 is returned.\n  //----------------------------------------------------------------------------\n  std::uint64_t getFileSizeBytes(IFileMD::id_t fid) override;\n\n  //----------------------------------------------------------------------------\n  //! Determine if the specified file exists and is not scheduled for deletion\n  //!\n  //! @param fid The file identifier\n  //! @return True if the file exists in the EOS namespace and is not scheduled\n  //! for deletion\n  //----------------------------------------------------------------------------\n  bool fileInNamespaceAndNotScheduledForDeletion(IFileMD::id_t fid) override;\n\n  //----------------------------------------------------------------------------\n  //! Execute evict as user root\n  //!\n  //! @param fid The file identifier\n  //----------------------------------------------------------------------------\n  void evictAsRoot(const IFileMD::id_t fid) override;\n\n  //----------------------------------------------------------------------------\n  //! @return Map from file system ID to EOS space name\n  //----------------------------------------------------------------------------\n  std::map<common::FileSystem::fsid_t, std::string> getFsIdToSpaceMap() override;\n\n  //----------------------------------------------------------------------------\n  //! @return map from EOS space name to disk replicas within that space - the\n  //! disk replicas are ordered from oldest first to youngest last\n  //! @param spaces names of the EOS spaces to be mapped\n  //! @param stop reference to a shared atomic boolean that if set to true will\n  //! cause this method to stop and return\n  //! @param nbFilesScanned reference to a counter which this method will set to\n  //! the total number of files scanned\n  //----------------------------------------------------------------------------\n  std::map<std::string, std::set<FileIdAndCtime> > getSpaceToDiskReplicasMap(\n    const std::set<std::string> &spacesToMap, std::atomic<bool> &stop, uint64_t &nbFilesScanned) override;\n\n  //----------------------------------------------------------------------------\n  //! @return The stdout of the specified shell cmd as a string\n  //! @param cmdStr The shell command string to be executed\n  //! @param maxLen The maximum length of the result\n  //----------------------------------------------------------------------------\n  std::string getStdoutFromShellCmd(const std::string &cmdStr, const ssize_t maxLen) const override;\n\n  //----------------------------------------------------------------------------\n  //! Set the tape-aware garbage collector configuration for the specified EOS\n  //! space\n  //!\n  //! @param space Name of the space.\n  //! @param config The configuration\n  //----------------------------------------------------------------------------\n  void setTapeGcSpaceConfig(const std::string &space, const SpaceConfig &config);\n\n  //----------------------------------------------------------------------------\n  //! Set the statistics of the specified EOS space.\n  //!\n  //! @param space Name of the space.\n  //! @param spaceStats The statistics.\n  //----------------------------------------------------------------------------\n  void setSpaceStats(const std::string &space, const SpaceStats &spaceStats);\n\n  //----------------------------------------------------------------------------\n  //! @return number of times getTapeGcSpaceConfig() has been called\n  //----------------------------------------------------------------------------\n  std::uint64_t getNbCallsToGetTapeGcSpaceConfig() const;\n\n  //----------------------------------------------------------------------------\n  //! @return number of times getSpaceStats() has been called\n  //----------------------------------------------------------------------------\n  std::uint64_t getNbCallsToGetSpaceStats() const;\n\n  //----------------------------------------------------------------------------\n  //! @return number of times fileInNamespaceAndNotScheduledForDeletion() has\n  //! been called\n  //----------------------------------------------------------------------------\n  std::uint64_t getNbCallsToFileInNamespaceAndNotScheduledForDeletion() const;\n\n  //----------------------------------------------------------------------------\n  //! @return number of times getFileSizeBytes() has been called\n  //----------------------------------------------------------------------------\n  std::uint64_t getNbCallsToGetFileSizeBytes() const;\n\n  //------------------------------------------------------------------------------\n  //! @return number of times evictAsRoot() has been called\n  //------------------------------------------------------------------------------\n  std::uint64_t getNbCallsToEvictAsRoot() const;\n\n  //----------------------------------------------------------------------------\n  //! Set the standard out from the shell command\n  //! @param stdoutFromShellCmd Standard out from the shell command as a string\n  //----------------------------------------------------------------------------\n  void setStdoutFromShellCmd(const std::string &stdoutFromShellCmd);\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Mutex protecting this dummy object representing access to the MGM\n  //----------------------------------------------------------------------------\n  mutable std::mutex m_mutex;\n\n  //----------------------------------------------------------------------------\n  //! Map from EOS space name to the tape-aware garbage collector configuration\n  //----------------------------------------------------------------------------\n  std::map<std::string, SpaceConfig> m_spaceToTapeGcConfig;\n\n  //----------------------------------------------------------------------------\n  //! Map from the name of an EOS space to its statistics\n  //----------------------------------------------------------------------------\n  std::map<std::string, SpaceStats> m_spaceToStats;\n\n  //----------------------------------------------------------------------------\n  //! Number of times getTapeGcSpaceConfig() has been called\n  //----------------------------------------------------------------------------\n  std::uint64_t m_nbCallsToGetTapeGcSpaceConfig;\n\n  //----------------------------------------------------------------------------\n  //! Number of times getSpaceStats() has been called\n  //----------------------------------------------------------------------------\n  mutable std::uint64_t m_nbCallsToGetSpaceStats;\n\n  //----------------------------------------------------------------------------\n  //! Number of times fileInNamespaceAndNotScheduledForDeletion() has been\n  //! called\n  //----------------------------------------------------------------------------\n  std::uint64_t m_nbCallsToFileInNamespaceAndNotScheduledForDeletion;\n\n  //----------------------------------------------------------------------------\n  //! Number of times getFileSizeBytes() has been called\n  //----------------------------------------------------------------------------\n  std::uint64_t m_nbCallsToGetFileSizeBytes;\n\n  //----------------------------------------------------------------------------\n  //! Number of times evictAsRoot() has been called\n  //----------------------------------------------------------------------------\n  std::uint64_t m_nbCallsToEvictAsRoot;\n\n  //----------------------------------------------------------------------------\n  //! Standard out from the shel command as a string.\n  //----------------------------------------------------------------------------\n  std::string m_stdoutFromShellCmd;\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/FreedBytesHistogram.cc",
    "content": "// ----------------------------------------------------------------------\n// File: FreedBytesHistogram.cc\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/tgc/Constants.hh\"\n#include \"mgm/tgc/FreedBytesHistogram.hh\"\n#include \"mgm/CtaUtils.hh\"\n\n#include <algorithm>\n#include <sstream>\n\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFreedBytesHistogram::FreedBytesHistogram(\n  const std::uint32_t nbBins,\n  const std::uint32_t binWidthSecs,\n  IClock& clock):\n  m_histogram(nbBins, 0),\n  m_startIndex(0),\n  m_binWidthSecs(binWidthSecs),\n  m_clock(clock)\n{\n  m_lastUpdateTimestamp = m_clock.getTime();\n\n  if (0 == nbBins || TGC_FREED_BYTES_HISTOGRAM_MAX_NB_BINS < nbBins) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \" failed: nbBins is invalid. Value must be > 0 and <= \"\n        <<\n        TGC_FREED_BYTES_HISTOGRAM_MAX_NB_BINS;\n    throw InvalidNbBins(msg.str());\n  }\n\n  if (0 == binWidthSecs ||\n      TGC_FREED_BYTES_HISTOGRAM_MAX_BIN_WIDTH_SECS < binWidthSecs) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ <<\n        \" failed: binWidthSecs is invalid. Value must be > 0 and <= \" <<\n        TGC_FREED_BYTES_HISTOGRAM_MAX_BIN_WIDTH_SECS;\n    throw InvalidBinWidth(msg.str());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Notify cache that bytes were freed\n//------------------------------------------------------------------------------\nvoid\nFreedBytesHistogram::bytesFreed(const uint64_t nbBytes)\n{\n  std::lock_guard<std::mutex> lock(m_mutex);\n  alignHistogramWithNow();\n  // Update youngest bin\n  m_histogram.at(m_startIndex) += nbBytes;\n}\n\n//------------------------------------------------------------------------------\n// Return number of bytes freed in the specified last number of seconds\n//------------------------------------------------------------------------------\nstd::uint64_t\nFreedBytesHistogram::getNbBytesFreedInLastNbSecs(const std::uint32_t lastNbSecs)\n{\n  std::lock_guard<std::mutex> lock(m_mutex);\n  const std::uint32_t nbBins = m_histogram.size();\n  const std::uint32_t historicalDepth = nbBins * m_binWidthSecs;\n\n  if (lastNbSecs > m_histogram.size() * m_binWidthSecs) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \" failed: Cannot go back more than \" << historicalDepth\n        << \" seconds\"\n        \": requested=\" << lastNbSecs << \": Try reducing \" << TGC_NAME_QRY_PERIOD_SECS;\n    throw TooFarBackInTime(msg.str());\n  }\n\n  const size_t nbBinsToTotal = CtaUtils::divideAndRoundUp(lastNbSecs,\n                               m_binWidthSecs);\n  alignHistogramWithNow();\n  uint64_t total = 0;\n\n  for (size_t binIndexOffset = 0; binIndexOffset < nbBinsToTotal;\n       binIndexOffset++) {\n    const size_t binIndex = (m_startIndex + binIndexOffset) % nbBins;\n    total += m_histogram.at(binIndex);\n  }\n\n  return total;\n}\n\n//------------------------------------------------------------------------------\n// Return the total number of freed bytes\n//------------------------------------------------------------------------------\nstd::uint64_t\nFreedBytesHistogram::getTotalBytesFreed()\n{\n  std::lock_guard<std::mutex> lock(m_mutex);\n  alignHistogramWithNow();\n  std::uint64_t total = 0;\n\n  for (size_t i = 0; i < m_histogram.size(); i++) {\n    total += m_histogram.at(i);\n  }\n\n  return total;\n}\n\n//----------------------------------------------------------------------------\n//! Return the number of bytes freed in the specified bin\n//----------------------------------------------------------------------------\nstd::uint64_t\nFreedBytesHistogram::getFreedBytesInBin(const std::uint32_t binIndex) const\n{\n  std::lock_guard<std::mutex> lock(m_mutex);\n  const std::uint32_t maxBinIndex = m_histogram.size() - 1;\n\n  if (binIndex > maxBinIndex) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \" failed: binIndex is too large: binIndex=\" << binIndex\n        << \" maxBinIndex=\" << maxBinIndex;\n    throw InvalidBinIndex(msg.str());\n  }\n\n  const std::uint32_t circularBinIndex = (m_startIndex + binIndex) %\n                                         m_histogram.size();\n  return m_histogram.at(circularBinIndex);\n}\n\n\n//------------------------------------------------------------------------------\n// Slide histogram to the right until the first bin is aligned with now\n//------------------------------------------------------------------------------\nvoid\nFreedBytesHistogram::alignHistogramWithNow()\n{\n  const time_t now = m_clock.getTime();\n  const time_t ageSecs = now - m_lastUpdateTimestamp;\n  const size_t rawNbBinsToMove = CtaUtils::divideAndRoundToNearest(ageSecs,\n                                 m_binWidthSecs);\n  const size_t nbBinsToMove = std::min(m_histogram.size(), rawNbBinsToMove);\n  // Move start index backwards in order to slide histigram to the the right\n  m_startIndex = (m_startIndex + m_histogram.size() - nbBinsToMove) %\n                 m_histogram.size();\n\n  // Zero off out-of-date bins\n  for (size_t i = 0; i < nbBinsToMove; i++) {\n    const size_t binIndex = (m_startIndex + i) % m_histogram.size();\n    m_histogram.at(binIndex) = 0;\n  }\n\n  // Update histogram timestamp\n  m_lastUpdateTimestamp = now;\n}\n\n//------------------------------------------------------------------------------\n// Set the bin width\n//------------------------------------------------------------------------------\nvoid\nFreedBytesHistogram::setBinWidthSecs(const std::uint32_t newBinWidthSecs)\n{\n  if (0 == newBinWidthSecs ||\n      TGC_FREED_BYTES_HISTOGRAM_MAX_BIN_WIDTH_SECS < newBinWidthSecs) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ <<\n        \" failed: newBinWidthSecs is invalid. Value must be > 0 and <= \" <<\n        TGC_FREED_BYTES_HISTOGRAM_MAX_BIN_WIDTH_SECS;\n    throw InvalidBinWidth(msg.str());\n  }\n\n  std::lock_guard<std::mutex> lock(m_mutex);\n  const std::uint32_t nbBins = m_histogram.size();\n  std::vector tempHistogram(m_histogram.size(), 0);\n  const std::uint32_t newHistoricalDepthSecs = nbBins * newBinWidthSecs;\n\n  for (std::uint32_t secsAgo = 1; secsAgo <= newHistoricalDepthSecs; secsAgo++) {\n    const std::uint32_t binIndex = (secsAgo - 1) / newBinWidthSecs;\n    std::uint64_t bytesFreedPerSec = 0;\n\n    try {\n      bytesFreedPerSec = getFreedBytesPerSec(secsAgo);\n    } catch (TooFarBackInTime&) {\n      break;\n    }\n\n    tempHistogram.at(binIndex) += bytesFreedPerSec;\n  }\n\n  for (std::uint32_t binIndex = 0; binIndex < nbBins; binIndex++) {\n    m_histogram.at(binIndex) = tempHistogram.at(binIndex);\n  }\n\n  m_startIndex = 0;\n  m_binWidthSecs = newBinWidthSecs;\n}\n\n//------------------------------------------------------------------------------\n// Return bin width in seconds\n//------------------------------------------------------------------------------\nstd::uint32_t\nFreedBytesHistogram::getBinWidthSecs() const\n{\n  std::lock_guard<std::mutex> lock(m_mutex);\n  return m_binWidthSecs;\n}\n\n//------------------------------------------------------------------------------\n// Return number of bins\n//------------------------------------------------------------------------------\nstd::uint32_t\nFreedBytesHistogram::getNbBins() const\n{\n  std::lock_guard<std::mutex> lock(m_mutex);\n  return m_histogram.size();\n}\n\n//------------------------------------------------------------------------------\n// Return bytes freed per second during the specified second\n//------------------------------------------------------------------------------\nstd::uint64_t\nFreedBytesHistogram::getFreedBytesPerSec(const std::uint32_t secsAgo) const\n{\n  if (secsAgo > m_histogram.size() * m_binWidthSecs) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \" failed: Cannot go back more than \" <<\n        m_histogram.size() * m_binWidthSecs << \" seconds\"\n        \": requested=\" << secsAgo;\n    throw TooFarBackInTime(msg.str());\n  }\n\n  if (0 == secsAgo) {\n    return 0;\n  }\n\n  const size_t binIndexOffset = (secsAgo - 1) / m_binWidthSecs;\n  const size_t binIndex = (m_startIndex + binIndexOffset) % m_histogram.size();\n  const uint64_t freedBytes = m_histogram.at(binIndex);\n  const uint64_t bytesPerSec = CtaUtils::divideAndRoundToNearest(freedBytes,\n                               m_binWidthSecs);\n  return bytesPerSec;\n}\n\nEOSTGCNAMESPACE_END\n"
  },
  {
    "path": "mgm/tgc/FreedBytesHistogram.hh",
    "content": "// ----------------------------------------------------------------------\n// File: FreedBytesHistogram.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGCFREEDBYTESHISTOGRAM_HH__\n#define __EOSMGMTGCFREEDBYTESHISTOGRAM_HH__\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/tgc/IClock.hh\"\n\n#include <cstdint>\n#include <ctime>\n#include <mutex>\n#include <stdexcept>\n#include <vector>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file FreedBytesHistogram.hh\n *\n * @brief Histogram of freed bytes over time.\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Histogram of freed bytes over time.\n//------------------------------------------------------------------------------\nclass FreedBytesHistogram {\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Thrown when an invalid number of bins has been specified\n  //----------------------------------------------------------------------------\n  struct InvalidNbBins: public std::runtime_error {\n    InvalidNbBins(const std::string &what): runtime_error(what) {}\n  };\n\n  //----------------------------------------------------------------------------\n  //! Thrown when an invalid bin width has been specified\n  //----------------------------------------------------------------------------\n  struct InvalidBinWidth: public std::runtime_error {\n    InvalidBinWidth(const std::string &what): runtime_error(what) {}\n  };\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param nbBins The number of bins in the histogram\n  //! @param binWidthSecs The width of a bin in seconds\n  //! @param clock Object responsible for giving the current time\n  //! @throw InvalidNbBins If nbBins is invalid\n  //! @throw InvalidBinWidth If binWidthSecs is invalid\n  //----------------------------------------------------------------------------\n  FreedBytesHistogram(std::uint32_t nbBins, std::uint32_t binWidthSecs, IClock &clock);\n\n  //----------------------------------------------------------------------------\n  //! Notify cache that bytes were freed\n  //----------------------------------------------------------------------------\n  void bytesFreed(std::uint64_t nbBytes);\n\n  //----------------------------------------------------------------------------\n  //! Thrown when a request for historic data goes too far back in time\n  //----------------------------------------------------------------------------\n  struct TooFarBackInTime: public std::runtime_error {\n    TooFarBackInTime(const std::string &what): runtime_error(what) {}\n  };\n\n  //----------------------------------------------------------------------------\n  //! @return number of bytes freed in the specified last number of seconds\n  //! @param lastNbSecs The last number of seconds.  A value of 0 seconds will\n  //! always return a value of 0 freed bytes.\n  //! @throw TooFarBackInTime when lastNbSecs goes back in time more than the\n  //! finite capacity of the underlying histogram, in other words if more than\n  //! nbBins * binWidthSecs\n  //----------------------------------------------------------------------------\n  std::uint64_t getNbBytesFreedInLastNbSecs(std::uint32_t lastNbSecs);\n\n  //----------------------------------------------------------------------------\n  //! @return The total number of bytes freed that the histogram in its finite\n  //! capacity knows about\n  //----------------------------------------------------------------------------\n  std::uint64_t getTotalBytesFreed();\n\n  //----------------------------------------------------------------------------\n  //! Thrown when an invalid bin index has been specified\n  //----------------------------------------------------------------------------\n  struct InvalidBinIndex: public std::runtime_error {\n    InvalidBinIndex(const std::string &what): runtime_error(what) {}\n  };\n\n  //----------------------------------------------------------------------------\n  //! @return Number of bytes freed in the specified histogram bin\n  //! @param binIndex Bin index in the range 0 to nbBind - 1 inclusive\n  //! @throw InvalidBinIndex If binIndex is invalid\n  //----------------------------------------------------------------------------\n  std::uint64_t getFreedBytesInBin(std::uint32_t binIndex) const;\n\n  //----------------------------------------------------------------------------\n  //! Set the bin width\n  //! @param newBinWidthSecs The new bin width in seconds\n  //! @throw InvalidBinWidth If newBinWidthSecs is invalid\n  //----------------------------------------------------------------------------\n  void setBinWidthSecs(std::uint32_t newBinWidthSecs);\n\n  //----------------------------------------------------------------------------\n  //! @return Bin width in seconds\n  //----------------------------------------------------------------------------\n  uint32_t getBinWidthSecs() const;\n\n  //----------------------------------------------------------------------------\n  //! @return number of bins\n  //----------------------------------------------------------------------------\n  uint32_t getNbBins() const;\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Mutex used to protect the contents of this object\n  //----------------------------------------------------------------------------\n  mutable std::mutex m_mutex;\n\n  //----------------------------------------------------------------------------\n  //! Circular histogram of freed bytes over time.  The time/x-axis starts at\n  //! 0 seconds since now and goes to nbBins * binWidthSecs seconds since now.\n  //----------------------------------------------------------------------------\n  std::vector<std::uint64_t> m_histogram;\n\n  //----------------------------------------------------------------------------\n  //! Current start index of histogram\n  //----------------------------------------------------------------------------\n  size_t m_startIndex;\n\n  //----------------------------------------------------------------------------\n  //! Width of a histogram bin in seconds\n  //----------------------------------------------------------------------------\n  std::uint32_t m_binWidthSecs;\n\n  //----------------------------------------------------------------------------\n  //! Object responsible for giving the cUrrent time\n  //----------------------------------------------------------------------------\n  IClock &m_clock;\n\n  //----------------------------------------------------------------------------\n  //! Timestamp of last update\n  //----------------------------------------------------------------------------\n  std::time_t m_lastUpdateTimestamp;\n\n  //----------------------------------------------------------------------------\n  //! Slide histogram to the right until the first bin is aligned with now\n  //!\n  //! Please note that this method assumes a lock has been taken on m_mutex\n  //----------------------------------------------------------------------------\n  void alignHistogramWithNow();\n\n  //----------------------------------------------------------------------------\n  //! @return Number bytes freed per second during the specified second\n  //! @param secsAgo The number of seconds ago.  A value of 0 seconds will\n  //! always return a value of 0 freed bytes.\n  //!\n  //! Please note that this method assumes a lock has been taken on m_mutex\n  //----------------------------------------------------------------------------\n  std::uint64_t getFreedBytesPerSec(std::uint32_t secsAgo) const;\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/IClock.cc",
    "content": "// ----------------------------------------------------------------------\n// File: IClock.cc\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/tgc/IClock.hh\"\n\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Destructor\n//------------------------------------------------------------------------------\nIClock::~IClock() {\n}\n\nEOSTGCNAMESPACE_END\n"
  },
  {
    "path": "mgm/tgc/IClock.hh",
    "content": "// ----------------------------------------------------------------------\n// File: IClock.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGC_ICLOCK_HH__\n#define __EOSMGMTGC_ICLOCK_HH__\n\n#include \"mgm/Namespace.hh\"\n\n#include <ctime>\n#include <stdexcept>\n\n/**\n * @file IClock.hh\n *\n * @brief Specifies the interface to an object providing the current time.\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Specifies the interface to an object providing the current time.\n//------------------------------------------------------------------------------\nclass IClock {\npublic:\n\n  //------------------------------------------------------------------------------\n  //! Destructor\n  //------------------------------------------------------------------------------\n  virtual ~IClock();\n\n  //------------------------------------------------------------------------------\n  //! @return Number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC)\n  //------------------------------------------------------------------------------\n  virtual std::time_t getTime() = 0;\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/ITapeGcMgm.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ITapeGcMgm.cc\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/tgc/ITapeGcMgm.hh\"\n\nEOSTGCNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n// Destructor\n//----------------------------------------------------------------------------\nITapeGcMgm::~ITapeGcMgm() {\n}\n\nEOSTGCNAMESPACE_END\n"
  },
  {
    "path": "mgm/tgc/ITapeGcMgm.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ITapeGcMgm.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGC_ITAPEGCMGM_HH__\n#define __EOSMGMTGC_ITAPEGCMGM_HH__\n\n#include \"common/FileSystem.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"mgm/tgc/SpaceStats.hh\"\n#include \"mgm/tgc/SpaceConfig.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n\n#include <atomic>\n#include <cstdint>\n#include <set>\n#include <stdexcept>\n#include <string>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file ITapeGcMgm.hh\n *\n * @brief Specifies the tape-aware garbage collector's interface to the EOS MGM\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Specifies the tape-aware garbage collector's interface to the EOS MGM\n//------------------------------------------------------------------------------\nclass ITapeGcMgm {\npublic:\n  //----------------------------------------------------------------------------\n  //! Default constructor\n  //----------------------------------------------------------------------------\n  ITapeGcMgm() = default;\n\n  //----------------------------------------------------------------------------\n  //! Virtual destructor\n  //----------------------------------------------------------------------------\n  virtual ~ITapeGcMgm() = 0;\n\n  //----------------------------------------------------------------------------\n  //! @return The configuration of a tape-aware garbage collector for the\n  //! specified space.\n  //! @param spaceName The name of the space\n  //----------------------------------------------------------------------------\n  virtual SpaceConfig getTapeGcSpaceConfig(const std::string &spaceName) = 0;\n\n  //----------------------------------------------------------------------------\n  //! @return Statistics about the specified space\n  //! @param space The name of the EOS space to be queried\n  //! @throw TapeAwareGcSpaceNotFound when the EOS space named m_spaceName\n  //! cannot be found\n  //----------------------------------------------------------------------------\n  [[nodiscard]] virtual SpaceStats getSpaceStats(const std::string &spaceName) const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Thrown when there is a failure to get the size of a file\n  //----------------------------------------------------------------------------\n  struct FailedToGetFileSize: public std::runtime_error {\n    FailedToGetFileSize(const std::string &msg): std::runtime_error(msg) {}\n  };\n\n  //----------------------------------------------------------------------------\n  //! @param fid The file identifier\n  //! @return The size of the specified file in bytes.\n  //! @throw FailedToGetFileSize When there is a failure to get the size of the\n  //! file\n  //----------------------------------------------------------------------------\n  virtual std::uint64_t getFileSizeBytes(IFileMD::id_t fid) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Determine if the specified file exists and is not scheduled for deletion\n  //!\n  //! @param fid The file identifier\n  //! @return True if the file exists in the EOS namespace and is not scheduled\n  //! for deletion\n  //----------------------------------------------------------------------------\n  virtual bool fileInNamespaceAndNotScheduledForDeletion(IFileMD::id_t fid) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Execute evict as user root\n  //!\n  //! @param fid The file identifier\n  //----------------------------------------------------------------------------\n  virtual void evictAsRoot(const IFileMD::id_t fid) = 0;\n\n  //----------------------------------------------------------------------------\n  //! @return Map from file system ID to EOS space name\n  //----------------------------------------------------------------------------\n  virtual std::map<common::FileSystem::fsid_t, std::string> getFsIdToSpaceMap() = 0;\n\n  //------------------------------------------------------------------------------\n  //! Structure containing the identifier and ctime of an EOS file which can\n  //! be ordered by ctime within an std container.\n  //------------------------------------------------------------------------------\n  struct FileIdAndCtime\n  {\n    //----------------------------------------------------------------------------\n    //! The EOS identifier\n    //----------------------------------------------------------------------------\n    IFileMD::id_t id;\n\n    //----------------------------------------------------------------------------\n    //! The ctime\n    //----------------------------------------------------------------------------\n    timespec ctime;\n\n    //----------------------------------------------------------------------------\n    //! Constructor\n    //----------------------------------------------------------------------------\n    FileIdAndCtime(): id(0) {\n      ctime.tv_sec = 0;\n      ctime.tv_nsec = 0;\n    }\n\n    //----------------------------------------------------------------------------\n    //! Constructor\n    //----------------------------------------------------------------------------\n    FileIdAndCtime(const IFileMD::id_t i, const timespec c): id(i), ctime(c) {}\n\n    //----------------------------------------------------------------------------\n    //! Less than operator\n    //----------------------------------------------------------------------------\n    bool operator<(const FileIdAndCtime &rhs) const {\n      if (ctime.tv_sec == rhs.ctime.tv_sec) {\n        return ctime.tv_nsec < rhs.ctime.tv_nsec;\n      } else {\n        return ctime.tv_sec < rhs.ctime.tv_sec;\n      }\n    }\n  };\n\n  //----------------------------------------------------------------------------\n  //! @return map from EOS space name to disk replicas within that space - the\n  //! disk replicas are ordered from oldest first to youngest last\n  //! @param spaces names of the EOS spaces to be mapped\n  //! @param stop reference to a shared atomic boolean that if set to true will\n  //! cause this method to stop and return\n  //! @param nbFilesScanned reference to a counter which this method will set to\n  //! the total number of files scanned\n  //----------------------------------------------------------------------------\n  virtual std::map<std::string, std::set<FileIdAndCtime> > getSpaceToDiskReplicasMap(\n    const std::set<std::string> &spacesToMap, std::atomic<bool> &stop, uint64_t &nbFilesScanned) = 0;\n\n  //----------------------------------------------------------------------------\n  //! @return The stdout of the specified shell cmd as a string\n  //! @param cmdStr The shell command string to be executed\n  //! @param maxLen The maximum length of the result\n  //----------------------------------------------------------------------------\n  virtual std::string getStdoutFromShellCmd(const std::string &cmdStr, const ssize_t maxLen) const = 0;\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/Lru.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Lru.cc\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/tgc/Lru.hh\"\n#include \"mgm/tgc/MaxLenExceeded.hh\"\n\n#include <iomanip>\n#include <stdexcept>\n\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Constructor\n//------------------------------------------------------------------------------\nLru::Lru(const FidQueue::size_type maxQueueSize):\n  mMaxQueueSize(maxQueueSize), mMaxQueueSizeExceeded(false)\n{\n  if(0 == maxQueueSize) {\n    throw MaxQueueSizeIsZero(std::string(__FUNCTION__) +\n      \" failed: maxQueueSize must be greater than 0\");\n  }\n}\n\n//------------------------------------------------------------------------------\n//! Notify the queue a file has been accessed\n//------------------------------------------------------------------------------\nvoid Lru::fileAccessed(const IFileMD::id_t fid)\n{\n  const auto mapEntry = mFidToQueueEntry.find(fid);\n\n  // If a new file has been accessed\n  if(mFidToQueueEntry.end() == mapEntry) {\n    newFileHasBeenAccessed(fid);\n  } else {\n    queuedFileHasBeenAccessed(fid, mapEntry.value());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Handle the fact a new file has been accessed\n//------------------------------------------------------------------------------\nvoid\nLru::newFileHasBeenAccessed(const IFileMD::id_t fid)\n{\n  // Ignore the new file if the maximum queue size has been reached\n  // IMPORTANT: This should be a rare situation\n  if(mFidToQueueEntry.size() == mMaxQueueSize) {\n    mMaxQueueSizeExceeded = true;\n  } else {\n    // Add file to the front of the LRU queue\n    mQueue.push_front(fid);\n    mFidToQueueEntry[fid] = mQueue.begin();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Handle the fact that a file already in the queue has been accessed\n//------------------------------------------------------------------------------\nvoid\nLru::queuedFileHasBeenAccessed(const IFileMD::id_t fid,\n  FidQueue::iterator &queueItor)\n{\n  // Erase the existing file from the LRU queue\n  mQueue.erase(queueItor);\n\n  // Push the identifier of the file onto the front of the LRU queue\n  mQueue.push_front(fid);\n  mFidToQueueEntry[fid] = mQueue.begin();\n}\n\n//------------------------------------------------------------------------------\n//! Notify the queue a file has been deleted from the EOS namespace\n//------------------------------------------------------------------------------\nvoid\nLru::fileDeletedFromNamespace(const IFileMD::id_t fid)\n{\n  const auto mapEntry = mFidToQueueEntry.find(fid);\n\n  if(mFidToQueueEntry.end() != mapEntry) {\n    mQueue.erase(mapEntry.value());\n    mFidToQueueEntry.erase(mapEntry);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Return true if the queue is empty\n//------------------------------------------------------------------------------\nbool\nLru::empty() const\n{\n  return mQueue.empty();\n}\n\n//------------------------------------------------------------------------------\n// Return queue size\n//------------------------------------------------------------------------------\nLru::FidQueue::size_type\nLru::size() const\n{\n  return mFidToQueueEntry.size();\n}\n\n//------------------------------------------------------------------------------\n// Pop and return the identifier of the least used file\n//------------------------------------------------------------------------------\nIFileMD::id_t\nLru::getAndPopFidOfLeastUsedFile()\n{\n  if(mQueue.empty()) {\n    throw QueueIsEmpty(std::string(__FUNCTION__) +\n      \" failed: The queue is empty\");\n  } else {\n    mMaxQueueSizeExceeded = false;\n\n    const auto lruFid = mQueue.back();\n    mQueue.pop_back();\n    mFidToQueueEntry.erase(lruFid);\n    return lruFid;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Return true if the maximum queue size has been exceeded\n//------------------------------------------------------------------------------\nbool\nLru::maxQueueSizeExceeded() const noexcept {\n  return mMaxQueueSizeExceeded;\n}\n\n//----------------------------------------------------------------------------\n// Return A JSON string representation of the LRU queue\n//----------------------------------------------------------------------------\nvoid\nLru::toJson(std::ostringstream &os, const std::uint64_t maxLen) const {\n  os << \"{\\\"size\\\":\\\"\" << size() << \"\\\",\\\"fids_from_MRU_to_LRU\\\":\";\n  os << std::setfill('0') << std::hex << \"[\";\n  bool isFirstFid = true;\n  for (const auto fid: mQueue) {\n    if (isFirstFid) {\n      isFirstFid = false;\n    } else {\n      os << \",\";\n    }\n    os << \"\\\"0x\" << std::setw(16) << fid << \"\\\"\";\n\n    {\n      const auto osSize = os.tellp();\n      if (0 > osSize) throw std::runtime_error(std::string(__FUNCTION__) + \": os.tellp() returned a negative number\");\n      if (maxLen && maxLen < (std::string::size_type)osSize) {\n        std::ostringstream msg;\n        msg << __FUNCTION__ << \": maxLen exceeded: maxLen=\" << maxLen;\n        throw MaxLenExceeded(msg.str());\n      }\n    }\n  }\n  os << \"]}\";\n\n  {\n    const auto osSize = os.tellp();\n    if (0 > osSize) throw std::runtime_error(std::string(__FUNCTION__) + \": os.tellp() returned a negative number\");\n    if (maxLen && maxLen < (std::string::size_type)osSize) {\n      std::ostringstream msg;\n      msg << __FUNCTION__ << \": maxLen exceeded: maxLen=\" << maxLen;\n      throw MaxLenExceeded(msg.str());\n    }\n  }\n}\n\nEOSTGCNAMESPACE_END\n"
  },
  {
    "path": "mgm/tgc/Lru.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Lru.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGCLRU_HH__\n#define __EOSMGMTGCLRU_HH__\n\n#include \"common/Murmur3.hh\"\n#include \"common/hopscotch_map.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n\n#include <list>\n#include <stdexcept>\n#include <unordered_map>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file Lru.hh\n *\n * @brief Class implementing a Least Recenting Used (LRU) queue\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class implementing a Least Recenting Used (LRU) queue\n//------------------------------------------------------------------------------\nclass Lru {\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Data type for storing a queue of file identifiers\n  //----------------------------------------------------------------------------\n  typedef std::list<IFileMD::id_t> FidQueue;\n\n  //----------------------------------------------------------------------------\n  //! Exception thrown when maxQueueSize has been incorrectly set to zero.\n  //----------------------------------------------------------------------------\n  struct MaxQueueSizeIsZero: public std::runtime_error {\n    MaxQueueSizeIsZero(const std::string &msg): std::runtime_error(msg) {}\n  };\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param maxQueueSize The maximum number of entries permitted in the LRU\n  //!                     queue.  This value must be greater than 0.\n  //!\n  //! @throw MaxQueueSizeIsZero If maxQueueSize is equal to 0.\n  //----------------------------------------------------------------------------\n  Lru(const FidQueue::size_type maxQueueSize = 10000000);\n\n  //----------------------------------------------------------------------------\n  //! Notify the queue a file has been accessed\n  //!\n  //! @param fid The file identifier\n  //----------------------------------------------------------------------------\n  void fileAccessed(const IFileMD::id_t fid);\n\n  //----------------------------------------------------------------------------\n  //! Notify the queue a file has been deleted from the EOS namespace\n  //!\n  //! @param fid The file identifier\n  //----------------------------------------------------------------------------\n  void fileDeletedFromNamespace(const IFileMD::id_t fid);\n\n  //----------------------------------------------------------------------------\n  //! @return true if the queue is empty\n  //----------------------------------------------------------------------------\n  bool empty() const;\n\n  //----------------------------------------------------------------------------\n  //! @return queue size\n  //----------------------------------------------------------------------------\n  FidQueue::size_type size() const;\n\n  //----------------------------------------------------------------------------\n  //! Exception thrown when the queue is empty\n  //----------------------------------------------------------------------------\n  struct QueueIsEmpty: public std::runtime_error {\n    QueueIsEmpty(const std::string &msg): std::runtime_error(msg) {}\n  };\n\n  //----------------------------------------------------------------------------\n  //! Pop and return the identifier of the least used file\n  //!\n  //! @return the file identifier\n  //!\n  //! @throw QueueIsEmpty if the queue is empty\n  //----------------------------------------------------------------------------\n  IFileMD::id_t getAndPopFidOfLeastUsedFile();\n\n  //----------------------------------------------------------------------------\n  //! @return True if the maximum queue size has been exceeded\n  //----------------------------------------------------------------------------\n  bool maxQueueSizeExceeded() const noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Writes the JSON representation of this object to the specified stream.\n  //!\n  //! @param os Input/Output parameter specifying the stream to write to.\n  //! @param maxLen The maximum length the stream should be.  A value of 0 means\n  //! unlimited.  This method can go over the maxLen limit but it MUST throw\n  //! a MaxLenExceeded exception if it does.\n  //!\n  //! @throw MaxLenExceeded if the length of the JSON string has exceeded maxLen\n  //----------------------------------------------------------------------------\n  void toJson(std::ostringstream &os, std::uint64_t maxLen = 0) const;\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! The maximum number of entries permitted in the LRU queue\n  //----------------------------------------------------------------------------\n  FidQueue::size_type mMaxQueueSize;\n\n  //----------------------------------------------------------------------------\n  //! True if the maximum size of the LRU queue has been exceeded.  This member\n  //! variable is used to reduce the number of warning messages sent to the\n  //! logger.\n  //----------------------------------------------------------------------------\n  bool mMaxQueueSizeExceeded;\n\n  //----------------------------------------------------------------------------\n  //! The queue of files from the most used at the front to the least used at\n  //! the back\n  //----------------------------------------------------------------------------\n  FidQueue mQueue;\n\n  //----------------------------------------------------------------------------\n  //! Data type for a map from file ID to entry within the LRU queue\n  //----------------------------------------------------------------------------\n  typedef tsl::hopscotch_map < IFileMD::id_t, FidQueue::iterator,\n    Murmur3::MurmurHasher<IFileMD::id_t> > FidToQueueEntryMap;\n\n  //----------------------------------------------------------------------------\n  //! Map from file ID to entry within the LRU queue\n  //----------------------------------------------------------------------------\n  FidToQueueEntryMap mFidToQueueEntry;\n\n  //----------------------------------------------------------------------------\n  //! Handle the fact a new file has been accessed\n  //!\n  //! @param fid The file identifier\n  //----------------------------------------------------------------------------\n  void newFileHasBeenAccessed(const IFileMD::id_t fid);\n\n  //----------------------------------------------------------------------------\n  //! Handle the fact that a file already in the queue has been accessed\n  //!\n  //! @param fid The file identifier\n  //! @param queueItor The queue entry\n  //----------------------------------------------------------------------------\n  void queuedFileHasBeenAccessed(const IFileMD::id_t fid, FidQueue::iterator &queueItor);\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/MaxLenExceeded.cc",
    "content": "// ----------------------------------------------------------------------\n// File: MaxLenExceeded.cc\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/tgc/MaxLenExceeded.hh\"\n\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Thrown when a maximum length has been exceeded\n//------------------------------------------------------------------------------\nMaxLenExceeded::MaxLenExceeded(const std::string &msg): std::runtime_error(msg) {\n}\n\nEOSTGCNAMESPACE_END\n"
  },
  {
    "path": "mgm/tgc/MaxLenExceeded.hh",
    "content": "// ----------------------------------------------------------------------\n// File: MaxLenExceeded.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGC_MAXLENEXCEEDED_HH__\n#define __EOSMGMTGC_MAXLENEXCEEDED_HH__\n\n#include \"mgm/Namespace.hh\"\n\n#include <stdexcept>\n\n/**\n * @file MaxLenExceeded.hh\n *\n * @brief Exception thrown when a maximum length has been exceeded\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Thrown when a maximum length has been exceeded\n//------------------------------------------------------------------------------\nstruct MaxLenExceeded: public std::runtime_error {\n  MaxLenExceeded(const std::string &msg);\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/MultiSpaceTapeGc.cc",
    "content": "// ----------------------------------------------------------------------\n// File: MultiSpaceTapeGc.cc\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"mgm/tgc/MaxLenExceeded.hh\"\n#include \"mgm/tgc/MultiSpaceTapeGc.hh\"\n#include \"mgm/CtaUtils.hh\"\n\n#include <iomanip>\n#include <sstream>\n#include <stdexcept>\n#include <time.h>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file MultiSpaceTapeGc.cc\n *\n * @brief Class implementing a tape aware garbage collector that can work over\n * multiple EOS spaces\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nMultiSpaceTapeGc::MultiSpaceTapeGc(ITapeGcMgm& mgm):\n  m_tapeEnabled(false), m_gcIsActive(false), m_mgm(mgm), m_gcs(mgm)\n{\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nMultiSpaceTapeGc::~MultiSpaceTapeGc()\n{\n  try {\n    stop();\n  } catch (std::exception& ex) {\n    eos_static_err(\"msg=\\\"%s\\\"\", ex.what());\n  } catch (...) {\n    eos_static_err(\"msg=\\\"Caught an unknown exception\\\"\");\n  }\n}\n\n//------------------------------------------------------------------------------\n// Notify GC the specified file has been opened for write\n//------------------------------------------------------------------------------\nvoid\nMultiSpaceTapeGc::fileOpenedForWrite(const std::string& space,\n                                     const eos::IFileMD::id_t fid)\n{\n  if (!m_tapeEnabled || !m_gcsPopulatedUsingQdb) {\n    return;\n  }\n\n  try {\n    dispatchFileAccessedToGc(\"file opened for write\", space, fid);\n  } catch (SpaceToTapeGcMap::UnknownEOSSpace&) {\n    // Ignore events for EOS spaces that do not have a tape-aware GC\n  } catch (std::exception& ex) {\n    eos_static_err(\"%s failed: %s\", __FUNCTION__, ex.what());\n  } catch (...) {\n    eos_static_err(\"%s failed: Caught an unknown exception\", __FUNCTION__);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Notify GC the specified file has been opened for read\n//------------------------------------------------------------------------------\nvoid\nMultiSpaceTapeGc::fileOpenedForRead(const std::string& space,\n                                    const eos::IFileMD::id_t fid)\n{\n  if (!m_tapeEnabled && !m_gcsPopulatedUsingQdb) {\n    return;\n  }\n\n  try {\n    dispatchFileAccessedToGc(\"file opened for read\", space, fid);\n  } catch (std::exception& ex) {\n    eos_static_err(\"%s failed: %s\", __FUNCTION__, ex.what());\n  } catch (...) {\n    eos_static_err(\"%s failed: Caught an unknown exception\", __FUNCTION__);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Notify GC the specified file has been converted\n//------------------------------------------------------------------------------\nvoid\nMultiSpaceTapeGc::fileConverted(const std::string& space,\n                                const eos::IFileMD::id_t fid)\n{\n  if (!m_tapeEnabled && !m_gcsPopulatedUsingQdb) {\n    return;\n  }\n\n  try {\n    dispatchFileAccessedToGc(\"file converted\", space, fid);\n  } catch (std::exception& ex) {\n    eos_static_err(\"%s failed: %s\", __FUNCTION__, ex.what());\n  } catch (...) {\n    eos_static_err(\"%s failed: Caught an unknown exception\", __FUNCTION__);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Dispatch file accessed event to the space specific tape garbage collector\n//------------------------------------------------------------------------------\nvoid\nMultiSpaceTapeGc::dispatchFileAccessedToGc(const std::string& event,\n    const std::string& space,\n    const IFileMD::id_t fileId)\n{\n  const char* const msgFormat =\n    \"event=\\\"%s\\\" space=\\\"%s\\\" fxid=%08llx msg=\\\"%s failed: %s\\\"\";\n\n  try {\n    auto& gc = m_gcs.getGc(space);\n    gc.fileAccessed(fileId);\n  } catch (SpaceToTapeGcMap::UnknownEOSSpace&) {\n    // Ignore events for EOS spaces that do not have a tape-aware GC\n  } catch (std::exception& ex) {\n    eos_static_err(msgFormat, event.c_str(), space.c_str(), fileId, __FUNCTION__,\n                   ex.what());\n  } catch (...) {\n    eos_static_err(msgFormat, event.c_str(), space.c_str(), fileId, __FUNCTION__,\n                   \"Caught an unknown exception\");\n  }\n}\n\n//------------------------------------------------------------------------------\n// Return map from EOS space name to tape-aware GC statistics\n//------------------------------------------------------------------------------\nstd::map<std::string, TapeGcStats>\nMultiSpaceTapeGc::getStats() const\n{\n  const char* const msgFormat =\n    \"msg=\\\"Unable to get statistics about tape-aware garbage collectors: %s\\\"\";\n\n  try {\n    if (!m_tapeEnabled) {\n      return std::map<std::string, TapeGcStats>();\n    }\n\n    return m_gcs.getStats();\n  } catch (std::exception& ex) {\n    eos_static_err(msgFormat, ex.what());\n  } catch (...) {\n    eos_static_err(msgFormat, \"Caught an unknown exception\");\n  }\n\n  return std::map<std::string, TapeGcStats>();\n}\n\n//----------------------------------------------------------------------------\n// Handles a cmd=SFS_FSCTL_PLUGIO arg1=tgc request\n//----------------------------------------------------------------------------\nint\nMultiSpaceTapeGc::handleFSCTL_PLUGIO_tgc(XrdOucErrInfo& error,\n    eos::common::VirtualIdentity& vid,\n    const XrdSecEntity* client)\n{\n  try {\n    if (vid.host != \"localhost\" && vid.host != \"localhost.localdomain\") {\n      std::ostringstream replyMsg, logMsg;\n      replyMsg << __FUNCTION__ <<\n               \": System access restricted - unauthorized identity used\";\n      logMsg << \"msg=\\\"\" << replyMsg.str() << \"\\\"\";\n      eos_static_err(logMsg.str().c_str());\n      error.setErrInfo(EACCES, replyMsg.str().c_str());\n      return SFS_ERROR;\n    }\n\n    if (!m_tapeEnabled) {\n      std::ostringstream replyMsg, logMsg;\n      replyMsg << __FUNCTION__ << \": Support for tape is not enabled\";\n      logMsg << \"msg=\\\"\" << replyMsg.str() << \"\\\"\";\n      eos_static_err(logMsg.str().c_str());\n      error.setErrInfo(ENOTSUP, replyMsg.str().c_str());\n      return SFS_ERROR;\n    }\n\n    const uint64_t replySize = 1048576; // 1 MiB\n    char* const reply = static_cast<char*>(malloc(replySize));\n\n    if (!reply) {\n      std::ostringstream replyMsg, logMsg;\n      replyMsg << __FUNCTION__ << \": Failed to allocate memory for reply: replySize=\"\n               << replySize;\n      logMsg << \"msg=\\\"\" << replyMsg.str() << \"\\\"\";\n      eos_static_err(logMsg.str().c_str());\n      error.setErrInfo(ENOMEM, replyMsg.str().c_str());\n      return SFS_ERROR;\n    }\n\n    std::ostringstream json;\n\n    try {\n      m_gcs.toJson(json, replySize - 1);\n    } catch (MaxLenExceeded& ml) {\n      std::ostringstream msg;\n      msg << \"msg=\\\"\" << ml.what() << \"\\\"\";\n      eos_static_err(msg.str().c_str());\n      error.setErrInfo(ERANGE, ml.what());\n      return SFS_ERROR;\n    }\n\n    std::strncpy(reply, json.str().c_str(), replySize - 1);\n    reply[replySize - 1] = '\\0';\n    // Ownership of reply is taken by the xrd_buff object.\n    // Error then takes ownership of the xrd_buff object\n    XrdOucBuffer* const xrd_buff = new XrdOucBuffer(reply, replySize);\n    xrd_buff->SetLen(strlen(reply + 1));\n    error.setErrInfo(xrd_buff->BuffSize(), xrd_buff);\n    return SFS_DATA;\n  } catch (std::exception& ex) {\n    eos_static_err(\"msg=\\\"handleFSCTL_PLUGIO_tgc failed: %s\\\"\", ex.what());\n  } catch (...) {\n    eos_static_err(\"msg=\\\"handleFSCTL_PLUGIO_tgc failed: Caught an unknown exception\\\"\");\n  }\n\n  error.setErrInfo(ECANCELED, \"handleFSCTL_PLUGIO_tgc failed\");\n  return SFS_ERROR;\n}\n\n//------------------------------------------------------------------------------\n// Enable garbage collection\n//------------------------------------------------------------------------------\nvoid\nMultiSpaceTapeGc::setTapeEnabled(const std::set<std::string>& spaces)\n{\n  std::lock_guard<std::mutex> workerLock(m_gcStartupMutex);\n  m_tapeEnabled = true;\n  m_spaces.insert(spaces.begin(), spaces.end());\n}\n\n//------------------------------------------------------------------------------\n// Start garbage collection for the specified EOS spaces\n//------------------------------------------------------------------------------\nvoid\nMultiSpaceTapeGc::start()\n{\n  std::lock_guard<std::mutex> workerLock(m_gcStartupMutex);\n\n  // Starting garbage collecton requires it to have been enabled\n  if (!m_tapeEnabled) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ <<\n        \" failed: Trying to start garbage collection without enabling first\";\n    throw GcIsNotEnabled(msg.str());\n  }\n\n  if (m_gcIsActive) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \" failed: Garbage collection has already been started\";\n    throw GcAlreadyStarted(msg.str());\n  }\n\n  for (const auto& space : m_spaces) {\n    m_gcs.createGc(space);\n  }\n\n  std::function<void()> entryPoint = std::bind(\n                                       &MultiSpaceTapeGc::workerThreadEntryPoint, this);\n  m_worker = std::make_unique<std::thread>(entryPoint);\n  m_gcIsActive = true;\n}\n\n//------------------------------------------------------------------------------\n// Stop garbage collection for all previously specified EOS spaces\n//------------------------------------------------------------------------------\nvoid\nMultiSpaceTapeGc::stop()\n{\n  std::lock_guard<std::mutex> workerLock(m_gcStartupMutex);\n\n  try {\n    if (m_worker) {\n      m_stop = true;\n      m_worker->join();\n      m_worker.reset();\n    }\n  } catch (std::exception& ex) {\n    eos_static_err(\"msg=\\\"%s\\\"\", ex.what());\n  } catch (...) {\n    eos_static_err(\"msg=\\\"Caught an unknown exception\\\"\");\n  }\n\n  m_gcs.destroyAllGc();\n  m_gcsPopulatedUsingQdb = false;\n  m_gcIsActive = false;\n}\n\n//----------------------------------------------------------------------------\n// Check if garbage collection is active\n//----------------------------------------------------------------------------\n\nbool\nMultiSpaceTapeGc::isGcActive()\n{\n  return m_gcIsActive;\n}\n\n//------------------------------------------------------------------------------\n// Entry point for the worker thread of this object\n//------------------------------------------------------------------------------\nvoid\nMultiSpaceTapeGc::workerThreadEntryPoint() noexcept\n{\n  try {\n    populateGcsUsingQdb();\n    m_gcsPopulatedUsingQdb = true;\n    m_gcs.startGcWorkerThreads();\n  } catch (std::exception& ex) {\n    eos_static_crit(\"msg=\\\"Worker thread of the multi-space tape-aware garbage collector failed: %s\\\"\",\n                    ex.what());\n  } catch (...) {\n    eos_static_crit(\"msg=\\\"Worker thread of the multi-space tape-aware garbage collector failed:\"\n                    \" Caught an unknown exception\\\"\");\n  }\n}\n\n//----------------------------------------------------------------------------\n// Populate the in-memory LRUs of the tape garbage collectors using Quark DB\n//----------------------------------------------------------------------------\nvoid\nMultiSpaceTapeGc::populateGcsUsingQdb()\n{\n  eos_static_info(\"msg=\\\"Starting to populate the meta-data of the tape-aware garbage collectors\\\"\");\n  const auto startTgcPopulation = time(nullptr);\n  const auto gcSpaces = m_gcs.getSpaces();\n  uint64_t nbFilesScanned = 0;\n  auto gcSpaceToFiles = m_mgm.getSpaceToDiskReplicasMap(gcSpaces, m_stop,\n                        nbFilesScanned);\n\n  // Build up space GC LRU structures whilst reducing space file lists\n  for (auto& spaceAndFiles : gcSpaceToFiles) {\n    const auto& space = spaceAndFiles.first;\n    auto& files = spaceAndFiles.second;\n    auto& gc = m_gcs.getGc(space);\n    {\n      std::ostringstream msg;\n      msg << \"msg=\\\"About to populate the tape-aware GC meta-data for an EOS space\\\" space=\\\"\"\n          << space << \"\\\" nbFiles=\"\n          << files.size();\n      eos_static_info(msg.str().c_str());\n    }\n\n    for (auto fileItor = files.begin(); fileItor != files.end();) {\n      if (m_stop) {\n        eos_static_info(\"msg=\\\"Requested to stop populating the meta-data of the tape-aware garbage collectors\\\"\");\n        return;\n      }\n\n      gc.fileAccessed(fileItor->id);\n      fileItor = files.erase(fileItor);\n    }\n  }\n\n  {\n    const auto populationDurationSecs = time(nullptr) - startTgcPopulation;\n    std::ostringstream msg;\n    msg << \"msg=\\\"Finished populating the meta-data of the tape-aware garbage collectors\\\" nbFilesScanned=\"\n        << nbFilesScanned << \" durationSecs=\" <<\n        populationDurationSecs;\n    eos_static_info(msg.str().c_str());\n  }\n}\n\nEOSTGCNAMESPACE_END\n"
  },
  {
    "path": "mgm/tgc/MultiSpaceTapeGc.hh",
    "content": "// ----------------------------------------------------------------------\n// File: MultiSpaceTapeGc.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_MULTISPACETAPEGC_HH__\n#define __EOSMGM_MULTISPACETAPEGC_HH__\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/tgc/ITapeGcMgm.hh\"\n#include \"mgm/tgc/Lru.hh\"\n#include \"mgm/tgc/SpaceToTapeGcMap.hh\"\n#include \"mgm/tgc/TapeGcStats.hh\"\n\n#include <atomic>\n#include <map>\n#include <set>\n#include <stdexcept>\n#include <string>\n#include <XrdSfs/XrdSfsInterface.hh>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file MultiSpaceTapeGc.hh\n *\n * @brief Class implementing a tape aware garbage collector that can work over\n * multiple EOS spaces\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! A tape aware garbage collector that can work over multiple EOS spaces\n//------------------------------------------------------------------------------\nclass MultiSpaceTapeGc\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param mgm the interface to the EOS MGM\n  //----------------------------------------------------------------------------\n  explicit MultiSpaceTapeGc(ITapeGcMgm &mgm);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~MultiSpaceTapeGc();\n\n  //----------------------------------------------------------------------------\n  //! Delete copy constructor\n  //----------------------------------------------------------------------------\n  MultiSpaceTapeGc(const MultiSpaceTapeGc &) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Delete assignment operator\n  //----------------------------------------------------------------------------\n  MultiSpaceTapeGc &operator=(const MultiSpaceTapeGc &) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Thrown if garbage collection has already started\n  //----------------------------------------------------------------------------\n  struct GcAlreadyStarted: public std::runtime_error {using std::runtime_error::runtime_error;};\n\n  //----------------------------------------------------------------------------\n  //! Thrown if garbage collection is started without being enabled\n  //----------------------------------------------------------------------------\n  struct GcIsNotEnabled: public std::runtime_error {using std::runtime_error::runtime_error;};\n\n  //----------------------------------------------------------------------------\n  //! Enables garbage collection for the specified EOS spaces\n  //!\n  //! Please note that calling this method tells this object that support for\n  //! tape is enabled\n  //----------------------------------------------------------------------------\n  void setTapeEnabled(const std::set<std::string>& spaces);\n\n  //----------------------------------------------------------------------------\n  //! Start garbage collection for the previously specified EOS spaces\n  //!\n  //! Please note that calling this method tells this object requires support\n  //! for tape to be enabled\n  //!\n  //! @throw GCAlreadyStarted if garbage collection has already been started\n  //! @throw GcIsNotEnabled if garbage colletion has not been enabled\n  //----------------------------------------------------------------------------\n  void start();\n\n  //----------------------------------------------------------------------------\n  //! Stop garbage collection for all specified EOS spacesm_worker\n  //!\n  //! @throw GCAlreadyStarted if garbage collection has already been started\n  //! @throw GcIsNotEnabled if garbage colletion has not been enabled\n  //----------------------------------------------------------------------------\n  void stop();\n\n  //----------------------------------------------------------------------------\n  //! Check if garbage collection is active\n  //----------------------------------------------------------------------------\n  bool isGcActive();\n\n  //----------------------------------------------------------------------------\n  //! Notify GC the specified file has been opened for write\n  //! @note This method does nothing and returns immediately if the GC has not\n  //! been enabled\n  //!\n  //! @param space where the file will be written to\n  //! @param fid file identifier\n  //----------------------------------------------------------------------------\n  void fileOpenedForWrite(const std::string &space, const eos::IFileMD::id_t fid);\n\n  //----------------------------------------------------------------------------\n  //! Notify GC the specified file has been opened for read\n  //! @note This method does nothing and returns immediately if the GC has not\n  //! been enabled\n  //!\n  //! @param space where the file resides\n  //! @param fid file identifier\n  //----------------------------------------------------------------------------\n  void fileOpenedForRead(const std::string &space, const eos::IFileMD::id_t fid);\n\n  //----------------------------------------------------------------------------\n  //! Notify GC the specified file has been converted\n  //! @note This method does nothing and returns immediately if the GC has not\n  //! been enabled\n  //!\n  //! @param space where the destination converted file resides\n  //! @param fid file identifier\n  //----------------------------------------------------------------------------\n  void fileConverted(const std::string &space, const eos::IFileMD::id_t fid);\n\n  //----------------------------------------------------------------------------\n  //! @return map from EOS space name to tape-aware GC statistics\n  //----------------------------------------------------------------------------\n  std::map<std::string, TapeGcStats> getStats() const;\n\n  //----------------------------------------------------------------------------\n  //! Handles a cmd=SFS_FSCTL_PLUGIO arg1=tgc request\n  //----------------------------------------------------------------------------\n  int handleFSCTL_PLUGIO_tgc(XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const XrdSecEntity* client);\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! True if tape support is enabled\n  //----------------------------------------------------------------------------\n  std::atomic<bool> m_tapeEnabled;\n\n  //----------------------------------------------------------------------------\n  //! True if garbage collector is active in current node\n  //----------------------------------------------------------------------------\n  std::atomic<bool> m_gcIsActive;\n\n  //----------------------------------------------------------------------------\n  //! The interface to the EOS MGM\n  //----------------------------------------------------------------------------\n  ITapeGcMgm &m_mgm;\n\n  //----------------------------------------------------------------------------\n  //! Thread safe map from EOS space name to tape aware garbage collector\n  //----------------------------------------------------------------------------\n  SpaceToTapeGcMap m_gcs;\n\n  //----------------------------------------------------------------------------\n  //! True if the worker thread of this object should stop\n  //----------------------------------------------------------------------------\n  std::atomic<bool> m_stop = false;\n\n  //----------------------------------------------------------------------------\n  //! Mutex ensuring that calls to start()/stop() are consistent\n  //----------------------------------------------------------------------------\n  std::mutex m_gcStartupMutex;\n\n  //----------------------------------------------------------------------------\n  //! The worker thread for this object (each TapeGc also has its own separate\n  //! worker thread)\n  //----------------------------------------------------------------------------\n  std::unique_ptr<std::thread> m_worker;\n\n  //----------------------------------------------------------------------------\n  //! Becomes true when the metadata of the tape-aware GCs has been fully\n  //! populated using Quark DB\n  //----------------------------------------------------------------------------\n  std::atomic<bool> m_gcsPopulatedUsingQdb = false;\n\n  //----------------------------------------------------------------------------\n  //! The names of the EOS spaces that are to be garbage collected\n  //----------------------------------------------------------------------------\n  std::set<std::string> m_spaces;\n\n  //----------------------------------------------------------------------------\n  //! Entry point for the worker thread of this object\n  //----------------------------------------------------------------------------\n  void workerThreadEntryPoint() noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Populate the in-memory LRU data structures of the tape aware garbage\n  //! collectors using Quark DB\n  //----------------------------------------------------------------------------\n  void populateGcsUsingQdb();\n\n  //----------------------------------------------------------------------------\n  //! Thrown if an EOS file system cannot determined\n  //----------------------------------------------------------------------------\n  struct FileSystemNotFound: public std::runtime_error {using std::runtime_error::runtime_error;};\n\n  //----------------------------------------------------------------------------\n  //! Dispach file accessed event to the space specific tape garbage collector\n  // \n  //! @param event human readable string describing the event\n  //! @param space the name of the EOS space where the file resides\n  //! @param fileId ID of the file\n  //----------------------------------------------------------------------------\n  void dispatchFileAccessedToGc(const std::string &event, const std::string &space, const IFileMD::id_t fileId);\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/RealClock.cc",
    "content": "// ----------------------------------------------------------------------\n// File: RealClock.cc\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/tgc/RealClock.hh\"\n\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Destructor\n//------------------------------------------------------------------------------\nstd::time_t RealClock::getTime() {\n  return std::time(nullptr);\n}\n\nEOSTGCNAMESPACE_END\n"
  },
  {
    "path": "mgm/tgc/RealClock.hh",
    "content": "// ----------------------------------------------------------------------\n// File: RealClock.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGC_REALCLOCK_HH__\n#define __EOSMGMTGC_REALCLOCK_HH__\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/tgc/IClock.hh\"\n\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file RealClock.hh\n *\n * @brief Provides the current time using std::time().\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Provides the current time using std::time()\n//------------------------------------------------------------------------------\nclass RealClock: public IClock {\npublic:\n\n  //------------------------------------------------------------------------------\n  //! @return Number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC)\n  //------------------------------------------------------------------------------\n  std::time_t getTime() override;\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/RealTapeGcMgm.cc",
    "content": "// ----------------------------------------------------------------------\n// File: TapeGc.cc\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/ShellCmd.hh\"\n#include \"mgm/proc/admin/EvictCmd.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/policy/Policy.hh\"\n#include \"mgm/tgc/Constants.hh\"\n#include \"mgm/tgc/RealTapeGcMgm.hh\"\n#include \"mgm/tgc/SpaceNotFound.hh\"\n#include \"mgm/CtaUtils.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"namespace/ns_quarkdb/inspector/FileScanner.hh\"\n#include \"namespace/ns_quarkdb/qclient/include/qclient/QClient.hh\"\n#include \"namespace/Prefetcher.hh\"\n\n#include <sstream>\n#include <stdexcept>\n\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nRealTapeGcMgm::RealTapeGcMgm(XrdMgmOfs& ofs): m_ofs(ofs)\n{\n}\n\n//----------------------------------------------------------------------------\n// Return the configuration of a tape-aware garbage collector\n//----------------------------------------------------------------------------\nSpaceConfig\nRealTapeGcMgm::getTapeGcSpaceConfig(const std::string& spaceName)\n{\n  SpaceConfig config;\n  config.queryPeriodSecs = getSpaceConfigMemberUint64(spaceName,\n                           TGC_NAME_QRY_PERIOD_SECS, TGC_DEFAULT_QRY_PERIOD_SECS);\n  config.availBytes = getSpaceConfigMemberUint64(spaceName, TGC_NAME_AVAIL_BYTES,\n                      TGC_DEFAULT_AVAIL_BYTES);\n  config.freeBytesScript = getSpaceConfigMemberString(spaceName,\n                           TGC_NAME_FREE_BYTES_SCRIPT, TGC_DEFAULT_FREE_BYTES_SCRIPT);\n  config.totalBytes = getSpaceConfigMemberUint64(spaceName, TGC_NAME_TOTAL_BYTES,\n                      TGC_DEFAULT_TOTAL_BYTES);\n  return config;\n}\n\n//----------------------------------------------------------------------------\n// Return the value of the specified space configuration variable\n//----------------------------------------------------------------------------\nstd::string\nRealTapeGcMgm::getSpaceConfigMemberString(\n  const std::string& spaceName,\n  const std::string& memberName,\n  const std::string defaultValue) noexcept\n{\n  try {\n    std::string valueStr;\n    {\n      eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n      const auto spaceItor = FsView::gFsView.mSpaceView.find(spaceName);\n\n      if (FsView::gFsView.mSpaceView.end() == spaceItor) {\n        throw std::exception();\n      }\n\n      if (nullptr == spaceItor->second) {\n        throw std::exception();\n      }\n\n      const auto& space = *(spaceItor->second);\n      valueStr = space.GetConfigMember(memberName);\n    }\n\n    if (valueStr.empty()) {\n      throw std::exception();\n    } else {\n      return valueStr;\n    }\n  } catch (...) {\n    return defaultValue;\n  }\n}\n\n//----------------------------------------------------------------------------\n// Return the value of the specified space configuration variable\n//----------------------------------------------------------------------------\nstd::uint64_t\nRealTapeGcMgm::getSpaceConfigMemberUint64(\n  const std::string& spaceName,\n  const std::string& memberName,\n  const std::uint64_t defaultValue) noexcept\n{\n  try {\n    std::string valueStr;\n    {\n      eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n      const auto spaceItor = FsView::gFsView.mSpaceView.find(spaceName);\n\n      if (FsView::gFsView.mSpaceView.end() == spaceItor) {\n        throw std::exception();\n      }\n\n      if (nullptr == spaceItor->second) {\n        throw std::exception();\n      }\n\n      const auto& space = *(spaceItor->second);\n      valueStr = space.GetConfigMember(memberName);\n    }\n\n    if (valueStr.empty()) {\n      throw std::exception();\n    } else {\n      return CtaUtils::toUint64(valueStr);\n    }\n  } catch (...) {\n    return defaultValue;\n  }\n}\n\n//----------------------------------------------------------------------------\n// Determine if the specified file exists and is not scheduled for deletion\n//----------------------------------------------------------------------------\nbool RealTapeGcMgm::fileInNamespaceAndNotScheduledForDeletion(\n  const IFileMD::id_t fid)\n{\n  // Prefetch before taking lock because metadata may not be in memory\n  Prefetcher::prefetchFileMDAndWait(m_ofs.eosView, fid);\n  common::RWMutexReadLock lock(m_ofs.eosViewRWMutex);\n  const auto fmd = m_ofs.eosFileService->getFileMD(fid);\n  // A file scheduled for deletion has a container ID of 0\n  return nullptr != fmd && 0 != fmd->getContainerId();\n}\n\n//----------------------------------------------------------------------------\n// Return statistics about the specified EOS space\n//----------------------------------------------------------------------------\nSpaceStats\nRealTapeGcMgm::getSpaceStats(const std::string& space) const\n{\n  SpaceStats stats;\n  // Policy::GetSpacePolicyLayout() implicitly takes a lock on FsView::gFsView.ViewMutex\n  const auto layoutId = Policy::GetSpacePolicyLayout(space.c_str());\n  {\n    eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n\n    if (FsView::gFsView.mSpaceView.count(space.c_str())) {\n      stats.availBytes =\n        FsView::gFsView.mSpaceView[space.c_str()]->SumLongLong(\"stat.statfs.freebytes?configstatus@rw\",\n            false);\n      stats.totalBytes =\n        FsView::gFsView.mSpaceView[space.c_str()]->SumLongLong(\"stat.statfs.capacity\",\n            false);\n    }\n  }\n\n  // if there is a space policy layout defined we scale values to logical bytes\n  if (layoutId) {\n    const auto scalefactor = eos::common::LayoutId::GetSizeFactor(layoutId);\n\n    if (scalefactor) {\n      stats.availBytes /= scalefactor;\n      stats.totalBytes /= scalefactor;\n    }\n  }\n\n  return stats;\n}\n\n//----------------------------------------------------------------------------\n// Return the size of the specified file\n//----------------------------------------------------------------------------\nstd::uint64_t RealTapeGcMgm::getFileSizeBytes(const IFileMD::id_t fid)\n{\n  try {\n    // Prefetch before taking lock because metadata may not be in memory\n    Prefetcher::prefetchFileMDAndWait(m_ofs.eosView, fid);\n  } catch (std::exception& ex) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \": fid=\" << fid << \": prefetchFileMDAndWait() failed: \"\n        << ex.what();\n    throw FailedToGetFileSize(msg.str());\n  } catch (...) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \": fid=\" << fid <<\n        \": prefetchFileMDAndWait() failed: Unknown exception\";\n    throw FailedToGetFileSize(msg.str());\n  }\n\n  common::RWMutexReadLock lock(m_ofs.eosViewRWMutex);\n  std::shared_ptr<eos::IFileMD> fmd;\n\n  try {\n    fmd = m_ofs.eosFileService->getFileMD(fid);\n  } catch (std::exception& ex) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \": fid=\" << fid << \": getFileMD() failed: \" << ex.what();\n    throw FailedToGetFileSize(msg.str());\n  } catch (...) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \": fid=\" << fid <<\n        \": getFileMD() failed: Unknown exception\";\n    throw FailedToGetFileSize(msg.str());\n  }\n\n  if (nullptr == fmd) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \": fid=\" << fid << \": getFileMD() returned nullptr\";\n    throw FailedToGetFileSize(msg.str());\n  }\n\n  std::uint64_t fileSizeBytes = 0;\n\n  try {\n    fileSizeBytes = fmd->getSize();\n  } catch (std::exception& ex) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \": fid=\" << fid << \": getSize() failed: \" << ex.what();\n    throw FailedToGetFileSize(msg.str());\n  } catch (...) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \": fid=\" << fid <<\n        \": getSize() failed: Unknown exception\";\n    throw FailedToGetFileSize(msg.str());\n  }\n\n  IContainerMD::id_t containerId = 0;\n\n  try {\n    containerId = fmd->getContainerId();\n  } catch (std::exception& ex) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \": fid=\" << fid << \": getContainerId() failed: \" <<\n        ex.what();\n    throw FailedToGetFileSize(msg.str());\n  } catch (...) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \": fid=\" << fid <<\n        \": getContainerId() failed: Unknown exception\";\n    throw FailedToGetFileSize(msg.str());\n  }\n\n  // A file scheduled for deletion has a container ID of 0\n  if (0 == containerId) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \": fid=\" << fid <<\n        \": File has been scheduled for deletion\";\n    throw FailedToGetFileSize(msg.str());\n  }\n\n  return fileSizeBytes;\n}\n\n//----------------------------------------------------------------------------\n// Execute evict as user root\n//----------------------------------------------------------------------------\nvoid\nRealTapeGcMgm::evictAsRoot(const IFileMD::id_t fid)\n{\n  eos::common::VirtualIdentity rootVid = eos::common::VirtualIdentity::Root();\n  eos::console::RequestProto req;\n  eos::console::EvictProto* evict = req.mutable_evict();\n  evict->set_ignoreevictcounter(true);\n  auto file = evict->add_file();\n  file->set_fid(fid);\n  EvictCmd cmd(std::move(req), rootVid);\n  auto const result = cmd.ProcessRequest();\n\n  if (result.retc()) {\n    throw std::runtime_error(result.std_err());\n  }\n}\n\n//----------------------------------------------------------------------------\n// Return map from file system ID to EOS space name\n//----------------------------------------------------------------------------\nstd::map<common::FileSystem::fsid_t, std::string>\nRealTapeGcMgm::getFsIdToSpaceMap()\n{\n  std::map<common::FileSystem::fsid_t, std::string> fsIdToSpace;\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n  const auto spaces = getSpaces();\n\n  for (const auto& space : spaces) {\n    const auto spaceItor = FsView::gFsView.mSpaceView.find(space);\n\n    if (FsView::gFsView.mSpaceView.end() == spaceItor) {\n      throw SpaceNotFound(std::string(__FUNCTION__) + \": Cannot find space \" +\n                          space + \": FsView does not know the space name\");\n    }\n\n    if (nullptr == spaceItor->second) {\n      throw SpaceNotFound(std::string(__FUNCTION__) + \": Cannot find space \" +\n                          space + \": Pointer to FsSpace is nullptr\");\n    }\n\n    const FsSpace& fsSpace = *(spaceItor->second);\n\n    for (const auto fsId : fsSpace) {\n      const auto itor = fsIdToSpace.find(fsId);\n\n      if (fsIdToSpace.end() != itor) {\n        std::ostringstream msg;\n        msg << __FUNCTION__ <<\n            \" failed: Found a filesystem in more than one EOS space: fsId=\" << fsId <<\n            \" firstSpace=\" << itor->second << \" secondSpace=\" << space;\n        throw std::runtime_error(msg.str());\n      }\n\n      fsIdToSpace[fsId] = space;\n    }\n  }\n\n  return fsIdToSpace;\n}\n\n//----------------------------------------------------------------------------\n// Return a list of the names of all the EOS spaces\n//----------------------------------------------------------------------------\nstd::set<std::string>\nRealTapeGcMgm::getSpaces() const\n{\n  std::set<std::string> spaces;\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n\n  for (const auto& nameAndSpace : FsView::gFsView.mSpaceView) {\n    if (0 != spaces.count(nameAndSpace.first)) {\n      std::ostringstream msg;\n      msg << __FUNCTION__ <<\n          \" failed: Detected two EOS spaces with the same name: space=\" <<\n          nameAndSpace.first;\n      throw std::runtime_error(msg.str());\n    }\n\n    spaces.insert(nameAndSpace.first);\n  }\n\n  return spaces;\n}\n\n//----------------------------------------------------------------------------\n// Return map from EOS space name to disk replicas within that space\n//----------------------------------------------------------------------------\nstd::map<std::string, std::set<ITapeGcMgm::FileIdAndCtime> >\nRealTapeGcMgm::getSpaceToDiskReplicasMap(const std::set<std::string>&\n    spacesToMap, std::atomic<bool>& stop,\n    uint64_t& nbFilesScanned)\n{\n  nbFilesScanned = 0;\n\n  if (m_ofs.mQdbContactDetails.members.empty()) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \" failed: QdbContactDetails.members is empty\";\n    eos_static_warning(msg.str().c_str());\n    throw std::runtime_error(msg.str());\n  }\n\n  std::map<std::string, std::set<FileIdAndCtime> > spaceToReplicas;\n  const auto fsIdToSpace = getFsIdToSpaceMap();\n  qclient::QClient qdbClient(m_ofs.mQdbContactDetails.members,\n                             m_ofs.mQdbContactDetails.constructOptions());\n  FileScanner fileScanner(qdbClient);\n  std::set<int> fsIdsWithNoSpace;\n\n  while (fileScanner.valid()) {\n    eos::ns::FileMdProto file;\n\n    if (stop) {\n      eos_static_info(\"The creation of the EOS space name to files map has been requested to stop\");\n      break;\n    }\n\n    if (!fileScanner.getItem(file)) {\n      eos_static_warning(\"msg=\\\"fileScanner stopped iterating early\\\"\");\n      break;\n    }\n\n    const auto ctime = CtaUtils::bufToTimespec(file.ctime());\n    const int locationsSize = file.locations_size();\n\n    for (int locationIndex = 0; locationIndex < locationsSize; locationIndex++) {\n      const int fsId = file.locations(locationIndex);\n      const auto itor = fsIdToSpace.find(fsId);\n\n      if (fsIdToSpace.end() == itor) {\n        fsIdsWithNoSpace.insert(fsId);\n      } else {\n        const std::string& space = itor->second;\n\n        if (spacesToMap.count(space)) {\n          spaceToReplicas[space].emplace(file.id(), ctime);\n        }\n      }\n    }\n\n    nbFilesScanned++;\n    fileScanner.next();\n  }\n\n  if (!fsIdsWithNoSpace.empty()) {\n    std::ostringstream msg;\n    msg << \"msg=\\\"Found file system IDs with no EOS space\\\" fsIds=\\\"\";\n    bool isFirstFsId = true;\n\n    for (const auto fsId : fsIdsWithNoSpace) {\n      if (isFirstFsId) {\n        isFirstFsId = false;\n      } else {\n        msg << \",\";\n      }\n\n      msg << fsId;\n    }\n\n    eos_static_warning(msg.str().c_str());\n  }\n\n  return spaceToReplicas;\n}\n\n//----------------------------------------------------------------------------\n// Get the stdout of the specified shell cmd as a string\n//----------------------------------------------------------------------------\nstd::string\nRealTapeGcMgm::getStdoutFromShellCmd(const std::string& cmdStr,\n                                     const ssize_t maxLen) const\n{\n  common::ShellCmd cmd(cmdStr);\n  const size_t timeoutSecs = 5;\n  const auto cmdRc = cmd.wait(timeoutSecs);\n\n  if (cmdRc.timed_out) {\n    std::ostringstream msg;\n    msg << \"Execution of shell command timed out after \" << timeoutSecs <<\n        \" seconds\";\n    throw std::runtime_error(msg.str());\n  } else if (cmdRc.signaled) {\n    std::ostringstream msg;\n    msg << \"Shell command received signal \" << cmdRc.signo;\n    throw std::runtime_error(msg.str());\n  } else if (cmdRc.exited && cmdRc.exit_code) {\n    std::ostringstream msg;\n    msg << \"Shell command exited with non-zero exit code \" << cmdRc.exit_code;\n    throw std::runtime_error(msg.str());\n  } else if (cmdRc.exited && 0 == cmdRc.exit_code) {\n    try {\n      return CtaUtils::readFdIntoStr(cmd.outfd, maxLen);\n    } catch (std::exception& ex) {\n      std::ostringstream msg;\n      msg << \"Failed to read stdout from shell command: \" << ex.what();\n      throw std::runtime_error(msg.str());\n    }\n  }\n\n  std::ostringstream msg;\n  msg << \"Shell command failed for unknown reason\";\n  throw std::runtime_error(msg.str());\n}\n\nEOSTGCNAMESPACE_END\n"
  },
  {
    "path": "mgm/tgc/RealTapeGcMgm.hh",
    "content": "// ----------------------------------------------------------------------\n// File: RealTapeGcMgm.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGC_REALTAPEGCMGM_HH__\n#define __EOSMGMTGC_REALTAPEGCMGM_HH__\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/tgc/ITapeGcMgm.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file RealTapeGcMgm.hh\n *\n * @brief Implements access to the real EOS MGM\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Implements access to the real EOS MGM\n//------------------------------------------------------------------------------\nclass RealTapeGcMgm: public ITapeGcMgm {\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param ofs The XRootD OFS plugin implementing the metadata handling of EOS\n  //----------------------------------------------------------------------------\n  explicit RealTapeGcMgm(XrdMgmOfs &ofs);\n\n  //----------------------------------------------------------------------------\n  //! Delete copy constructor\n  //----------------------------------------------------------------------------\n  RealTapeGcMgm(const RealTapeGcMgm &) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Delete move constructor\n  //----------------------------------------------------------------------------\n  RealTapeGcMgm(const RealTapeGcMgm &&) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Delete assignment operator\n  //----------------------------------------------------------------------------\n  RealTapeGcMgm &operator=(const RealTapeGcMgm &) = delete;\n\n  //----------------------------------------------------------------------------\n  //! @return The configuration of a tape-aware garbage collector for the\n  //! specified space.\n  //! @param spaceName The name of the space\n  //----------------------------------------------------------------------------\n  SpaceConfig getTapeGcSpaceConfig(const std::string &spaceName) override;\n\n  //----------------------------------------------------------------------------\n  //! @return Statistics about the specified space\n  //! @param space The name of the EOS space to be queried\n  //! @throw TapeAwareGcSpaceNotFound when the EOS space named m_spaceName\n  //! cannot be found\n  //----------------------------------------------------------------------------\n  [[nodiscard]] SpaceStats getSpaceStats(const std::string &space) const override;\n\n  //----------------------------------------------------------------------------\n  //! @param fid The file identifier\n  //! @return The size of the specified file in bytes.  If the file cannot be\n  //! found in the EOS namespace then a file size of 0 is returned.\n  //----------------------------------------------------------------------------\n  std::uint64_t getFileSizeBytes(IFileMD::id_t fid) override;\n\n  //----------------------------------------------------------------------------\n  //! Determine if the specified file exists and is not scheduled for deletion\n  //!\n  //! @param fid The file identifier\n  //! @return True if the file exists in the EOS namespace and is not scheduled\n  //! for deletion\n  //----------------------------------------------------------------------------\n  bool fileInNamespaceAndNotScheduledForDeletion(IFileMD::id_t fid) override;\n\n  //----------------------------------------------------------------------------\n  //! Execute evict as user root\n  //!\n  //! @param fid The file identifier\n  //----------------------------------------------------------------------------\n  void evictAsRoot(const IFileMD::id_t fid) override;\n\n  //----------------------------------------------------------------------------\n  //! @return Map from file system ID to EOS space name\n  //----------------------------------------------------------------------------\n  std::map<common::FileSystem::fsid_t, std::string> getFsIdToSpaceMap() override;\n\n  //----------------------------------------------------------------------------\n  //! @return map from EOS space name to disk replicas within that space - the\n  //! disk replicas are ordered from oldest first to youngest last\n  //! @param spaces names of the EOS spaces to be mapped\n  //! @param stop reference to a shared atomic boolean that if set to true will\n  //! cause this method to stop and return\n  //! @param nbFilesScanned reference to a counter which this method will set to\n  //! the total number of files scanned\n  //----------------------------------------------------------------------------\n  std::map<std::string, std::set<FileIdAndCtime> > getSpaceToDiskReplicasMap(\n    const std::set<std::string> &spacesToMap, std::atomic<bool> &stop, uint64_t &nbFilesScanned) override;\n\n  //----------------------------------------------------------------------------\n  //! @return The stdout of the specified shell cmd as a string\n  //! @param cmdStr The shell command string to be executed\n  //! @param maxLen The maximum length of the result\n  //----------------------------------------------------------------------------\n  std::string getStdoutFromShellCmd(const std::string &cmdStr, const ssize_t maxLen) const override;\n\nprivate:\n\n  /// The XRootD OFS plugin implementing the metadata handling of EOS\n  XrdMgmOfs &m_ofs;\n\n  //----------------------------------------------------------------------------\n  //! @return The string value of the specified space configuration variable.\n  //! If the value cannot be determined for whatever reason then the specified\n  //! default is returned.\n  //!\n  //! @param spaceName The name of the space\n  //! @param memberName The name of the space configuration member.\n  //! @param defaultValue The default value of the space configuration member.\n  //----------------------------------------------------------------------------\n  static std::string getSpaceConfigMemberString(const std::string &spaceName, const std::string &memberName,\n                                                std::string defaultValue) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! @return The unit64_t value of the specified space configuration variable.\n  //! If the value cannot be determined for whatever reason then the specified\n  //! default is returned.\n  //!\n  //! @param spaceName The name of the space\n  //! @param memberName The name of the space configuration member.\n  //! @param defaultValue The default value of the space configuration member.\n  //----------------------------------------------------------------------------\n  static std::uint64_t getSpaceConfigMemberUint64(const std::string &spaceName, const std::string &memberName,\n    std::uint64_t defaultValue) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! @return a list of the names of all the EOS spaces\n  //----------------------------------------------------------------------------\n  std::set<std::string> getSpaces() const;\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/SmartSpaceStats.cc",
    "content": "// ----------------------------------------------------------------------\n// File: SmartSpaceStats.cc\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"common/ShellCmd.hh\"\n#include \"common/StringUtils.hh\"\n#include \"mgm/tgc/SmartSpaceStats.hh\"\n#include \"mgm/CtaUtils.hh\"\n\n#include <sstream>\n\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Constructor\n//------------------------------------------------------------------------------\nSmartSpaceStats::SmartSpaceStats(const std::string& spaceName, ITapeGcMgm& mgm,\n                                 CachedValue<SpaceConfig>& config):\n  m_singleAsyncFreeBytesScript(mgm),\n  m_spaceName(spaceName),\n  m_mgm(mgm),\n  m_queryMgmTimestamp(0),\n  m_freedBytesHistogram(TGC_FREED_BYTES_HISTOGRAM_NB_BINS,\n                        TGC_DEFAULT_FREED_BYTES_HISTOGRAM_BIN_WIDTH_SECS, m_clock),\n  m_config(config)\n{\n}\n\n//------------------------------------------------------------------------------\n// Return statistics about the EOS space being managed\n//------------------------------------------------------------------------------\nSmartSpaceStats::SpaceStatsAndAvailBytesSrc\nSmartSpaceStats::get()\n{\n  const std::time_t now = time(nullptr);\n  const auto spaceConfig = m_config.get();\n  std::lock_guard<std::mutex> lock(m_mutex);\n  const std::time_t secsSinceLastQuery = now - m_queryMgmTimestamp;\n\n  if (secsSinceLastQuery >= spaceConfig.queryPeriodSecs) {\n    m_mgmStats = SpaceStatsAndAvailBytesSrc();\n\n    try {\n      m_mgmStats.stats = m_mgm.getSpaceStats(m_spaceName);\n\n      if (spaceConfig.freeBytesScript.empty()) {\n        m_mgmStats.availBytesSrc = Src::INTERNAL_BECAUSE_SCRIPT_PATH_EMPTY;\n      } else {\n        // Try to overwrite m_mgmStats.availBytes if tgc.freebytesscript is set\n        if (!spaceConfig.freeBytesScript.empty()) {\n          try {\n            std::ostringstream cmd;\n            cmd << spaceConfig.freeBytesScript << \" \" << m_spaceName;\n            const auto asyncResult =\n              m_singleAsyncFreeBytesScript.getUint64FromShellCmdStdOut(cmd.str());\n\n            switch (asyncResult.getState()) {\n            case AsyncResult<std::uint64_t>::State::PENDING_AND_NO_PREVIOUS_VALUE:\n              // Don't overwrite m_mgmStats.availBytes\n              m_mgmStats.availBytesSrc =\n                Src::INTERNAL_BECAUSE_SCRIPT_PENDING_AND_NO_PREVIOUS_VALUE;\n              break;\n\n            case AsyncResult<std::uint64_t>::State::PENDING_AND_PREVIOUS_VALUE:\n              if (asyncResult.getPreviousValue()) {\n                // Use the previous value for now\n                m_mgmStats.stats.availBytes = asyncResult.getPreviousValue().value();\n                m_mgmStats.availBytesSrc = Src::SCRIPT_PREVIOUS_VALUE_BECAUSE_SCRIPT_PENDING;\n              } else {\n                throw std::runtime_error(\n                  \"State of AsyncResult is PENDING_AND_PREVIOUS_VALUE but it does not contain a previous value\");\n              }\n\n              // fallthrough\n\n            case AsyncResult<std::uint64_t>::State::VALUE:\n              if (asyncResult.getValue()) {\n                m_mgmStats.stats.availBytes = asyncResult.getValue().value();\n                m_mgmStats.availBytesSrc = Src::SCRIPT_VALUE_BECAUSE_SCRIPT_JUST_FINISHED;\n              }\n\n              break;\n\n            case AsyncResult<std::uint64_t>::State::ERROR:\n              if (asyncResult.getError()) {\n                std::stringstream msg;\n                msg << \"Execution of script failed with an error: \" <<\n                    asyncResult.getError().value();\n                throw std::runtime_error(msg.str());\n              } else {\n                throw std::runtime_error(\"State of AsyncResult is EXCEPTION but it does not contain an exception\");\n              }\n\n            default:\n              throw std::runtime_error(\"Unknown AsyncResult::State\");\n            }\n          } catch (std::exception& ex) {\n            m_mgmStats.availBytesSrc = Src::INTERNAL_BECAUSE_SCRIPT_ERROR;\n            eos_static_err(\n              \"msg=\\\"Failed to get and parse output of tgc.freebytesscript. Falling back to internal filesystem stats\\\"\"\n              \" space=\\\"%s\\\" tgc.freebytesscript=\\\"%s\\\" error=\\\"%s\\\"\", m_spaceName.c_str(),\n              spaceConfig.freeBytesScript.c_str(), ex.what());\n          }\n        }\n      }\n    } catch (std::exception& se) {\n      eos_static_err(\"spaceName=\\\"%s\\\" msg=\\\"Failed to get space stats\\\" error=\\\"%s\\\"\",\n                     m_spaceName.c_str(), se.what());\n    } catch (...) {\n      eos_static_err(\"spaceName=\\\"%s\\\" msg=\\\"Failed to get space stats\\\" error=\\\"Caught unknown exception\\\"\",\n                     m_spaceName.c_str());\n    }\n\n    m_queryMgmTimestamp = now;\n  }\n\n  if (0 == spaceConfig.queryPeriodSecs ||\n      TGC_MAX_QRY_PERIOD_SECS < ((std::uint64_t)spaceConfig.queryPeriodSecs)) {\n    std::ostringstream msg;\n    msg << \"spaceName=\\\"\" << m_spaceName << \"\\\" msg=\\\"Ignoring new value of \" <<\n        TGC_NAME_QRY_PERIOD_SECS <<\n        \" : Value must be > 0 and <= \" << TGC_MAX_QRY_PERIOD_SECS << \": Value=\" <<\n        spaceConfig.queryPeriodSecs << \"\\\"\";\n    eos_static_err(msg.str().c_str());\n  } else {\n    const std::uint32_t oldBinWidthSecs = m_freedBytesHistogram.getBinWidthSecs();\n    const std::uint32_t newBinWidthSecs =\n      CtaUtils::divideAndRoundUp(spaceConfig.queryPeriodSecs,\n                                 m_freedBytesHistogram.getNbBins());\n\n    if (0 == newBinWidthSecs) {\n      std::ostringstream msg;\n      msg << \"spaceName=\\\"\" << m_spaceName <<\n          \"\\\" msg=\\\"The newBinWidthSecs value of 0 will be ignored.\"\n          \" Value must be greater than 0.\\\"\";\n      eos_static_err(msg.str().c_str());\n    } else if (newBinWidthSecs != oldBinWidthSecs) {\n      m_freedBytesHistogram.setBinWidthSecs(newBinWidthSecs);\n      std::ostringstream msg;\n      msg << \"spaceName=\\\"\" << m_spaceName <<\n          \"\\\" msg=\\\"Changed bin width of freed bytes histogram:\"\n          \" oldValue=\" << oldBinWidthSecs << \" newValue=\" << newBinWidthSecs << \"\\\"\";\n      eos_static_info(msg.str().c_str());\n    }\n  }\n\n  // Space statistics from the MGM are not timestamped and therefore may\n  // themselves be out of data by as much as spaceConfig.queryPeriodSecs\n  //\n  // Add the count of bytes the garbage collector has freed in the last\n  // spaceConfig.queryPeriodSecs even if this may cause a temporary double\n  // count\n  uint64_t nbBytesFreed = 0;\n\n  try {\n    nbBytesFreed = m_freedBytesHistogram.getNbBytesFreedInLastNbSecs(\n                     spaceConfig.queryPeriodSecs);\n  } catch (FreedBytesHistogram::TooFarBackInTime& ex) {\n    nbBytesFreed = m_freedBytesHistogram.getTotalBytesFreed();\n    std::ostringstream msg;\n    msg << \"msg=\\\"\" << ex.what() << \"\\\"\";\n    eos_static_err(msg.str().c_str());\n  }\n\n  m_mgmStats.stats.availBytes += nbBytesFreed;\n  return m_mgmStats;\n}\n\n//----------------------------------------------------------------------------\n// Return timestamp at which the last query was made\n//----------------------------------------------------------------------------\nstd::time_t\nSmartSpaceStats::getQueryTimestamp()\n{\n  std::lock_guard<std::mutex> lock(m_mutex);\n  return m_queryMgmTimestamp;\n}\n\n//------------------------------------------------------------------------------\n// Notify this object that a disk replica has been queued for deletion\n//------------------------------------------------------------------------------\nvoid\nSmartSpaceStats::diskReplicaQueuedForDeletion(const size_t fileSizeBytes)\n{\n  std::lock_guard<std::mutex> lock(m_mutex);\n  m_freedBytesHistogram.bytesFreed(fileSizeBytes);\n}\n\nEOSTGCNAMESPACE_END\n"
  },
  {
    "path": "mgm/tgc/SmartSpaceStats.hh",
    "content": "// ----------------------------------------------------------------------\n// File: SmartSpaceStats.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMSMARTSPACESTATS_HH__\n#define __EOSMGMSMARTSPACESTATS_HH__\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/tgc/AsyncUint64ShellCmd.hh\"\n#include \"mgm/tgc/CachedValue.hh\"\n#include \"mgm/tgc/FreedBytesHistogram.hh\"\n#include \"mgm/tgc/ITapeGcMgm.hh\"\n#include \"mgm/tgc/RealClock.hh\"\n#include \"mgm/tgc/SpaceStats.hh\"\n\n#include <ctime>\n#include <mutex>\n#include <string>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file SmartSpaceStats.hh\n *\n * @brief Class encapsulating how the tape-aware GC updates its internal\n * statistics about the EOS space it is managing.\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class encapsulating how the tape-aware GC updates its internal statistics\n//! about the EOS space it is managing\n//------------------------------------------------------------------------------\nclass SmartSpaceStats {\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param spaceName Name of the EOS space being managed\n  //! @param mgm Interface to the EOS MGM\n  //! @param config Configuration of the tape-aware garbage collector\n  //----------------------------------------------------------------------------\n  SmartSpaceStats(const std::string &spaceName, ITapeGcMgm &mgm, CachedValue<SpaceConfig> &config);\n\n  //----------------------------------------------------------------------------\n  //! Notify this object that a disk replica has been queued for deletion\n  //!\n  //! @param fileSizeBytes File size in bytes\n  //----------------------------------------------------------------------------\n  void diskReplicaQueuedForDeletion(size_t fileSizeBytes);\n\n  enum class Src {\n    NONE,\n    INTERNAL_BECAUSE_SCRIPT_PATH_EMPTY,\n    INTERNAL_BECAUSE_SCRIPT_PENDING_AND_NO_PREVIOUS_VALUE,\n    INTERNAL_BECAUSE_SCRIPT_ERROR,\n    SCRIPT_VALUE_BECAUSE_SCRIPT_JUST_FINISHED,\n    SCRIPT_PREVIOUS_VALUE_BECAUSE_SCRIPT_PENDING\n  };\n\n  static const char *srcToStr(const Src src) {\n    switch(src) {\n    case Src::NONE: return \"NONE\";\n    case Src::INTERNAL_BECAUSE_SCRIPT_PATH_EMPTY: return \"INTERNAL_BECAUSE_SCRIPT_PATH_EMPTY\";\n    case Src::INTERNAL_BECAUSE_SCRIPT_PENDING_AND_NO_PREVIOUS_VALUE:\n      return \"INTERNAL_BECAUSE_SCRIPT_PENDING_AND_NO_PREVIOUS_VALUE\";\n    case Src::INTERNAL_BECAUSE_SCRIPT_ERROR: return \"INTERNAL_BECAUSE_SCRIPT_ERROR\";\n    case Src::SCRIPT_VALUE_BECAUSE_SCRIPT_JUST_FINISHED: return \"SCRIPT_VALUE_BECAUSE_SCRIPT_JUST_FINISHED\";\n    case Src::SCRIPT_PREVIOUS_VALUE_BECAUSE_SCRIPT_PENDING: return \"SCRIPT_PREVIOUS_VALUE_BECAUSE_SCRIPT_PENDING\";\n    default: return \"UNKNOWN\";\n    }\n  }\n\n  struct SpaceStatsAndAvailBytesSrc {\n    SpaceStats stats;\n    Src availBytesSrc = Src::NONE;\n  };\n\n  //----------------------------------------------------------------------------\n  //! @return statistics about the EOS space being managed\n  //----------------------------------------------------------------------------\n  SpaceStatsAndAvailBytesSrc get();\n\n  //----------------------------------------------------------------------------\n  //! @return timestamp at which the last query was made\n  //----------------------------------------------------------------------------\n  std::time_t getQueryTimestamp();\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Object used to asynchronously run no more than one tgc.freebytesscript at\n  //! a time.\n  //!\n  //! When the tgc.freebytesscript configuration variable is set there are\n  //! effectively two sources from which the total number of free bytes can be\n  //! obtained.  The internal filesystem statistics of the MGM that it receives\n  //! from its FSTs and the external script whose path is stored in the\n  //! tgc.freebytesscript parameter.  There are two cases in which the MGM TGC\n  //! will fall back to using its internal statistics.  The MGM will use its\n  //! internal statistics until the script has been successfully executed for\n  //! the very first time.  The MGM will use its internal statistics if the\n  //! script fails where failure can include timing out.  Once the script has\n  //! been executed successfully the MGM will cache the returned value until the\n  //! next time the script successfully completes.  If at any point the script\n  //! fails then the MGM will revert back to using its internal statistics.\n  //----------------------------------------------------------------------------\n  AsyncUint64ShellCmd m_singleAsyncFreeBytesScript;\n\n  //----------------------------------------------------------------------------\n  //! Name of the EOS space being managed\n  //----------------------------------------------------------------------------\n  std::string m_spaceName;\n\n  //----------------------------------------------------------------------------\n  //! Interface to the EOS MGM\n  //----------------------------------------------------------------------------\n  ITapeGcMgm &m_mgm;\n\n  //----------------------------------------------------------------------------\n  //! Mutex to protect the member variables of this object\n  //----------------------------------------------------------------------------\n  mutable std::mutex m_mutex;\n\n  //----------------------------------------------------------------------------\n  //! The timestamp at which the last query to the MGM was made\n  //----------------------------------------------------------------------------\n  std::time_t m_queryMgmTimestamp;\n\n  //----------------------------------------------------------------------------\n  //! MGM statistics about the EOS space being managed\n  //----------------------------------------------------------------------------\n  SpaceStatsAndAvailBytesSrc m_mgmStats;\n\n  //----------------------------------------------------------------------------\n  //! Object responsible for providing the current time\n  //!\n  //! This member variable MUST be declared before m_freedBytesHistogram\n  //----------------------------------------------------------------------------\n  RealClock m_clock;\n\n  //----------------------------------------------------------------------------\n  //! Histogram of freed bytes over time\n  //----------------------------------------------------------------------------\n  FreedBytesHistogram m_freedBytesHistogram;\n\n  //----------------------------------------------------------------------------\n  //! The configuration of the tape-aware garbage collector\n  //----------------------------------------------------------------------------\n  CachedValue<SpaceConfig> &m_config;\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/SpaceConfig.hh",
    "content": "// ----------------------------------------------------------------------\n// File: SpaceConfig.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGC_SPACECONFIG_HH__\n#define __EOSMGMTGC_SPACECONFIG_HH__\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/tgc/Constants.hh\"\n\n#include <cstdint>\n#include <ctime>\n#include <string>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file SpaceConfig.hh\n *\n * @brief The configuration of a tape-aware garbage collector for a specific EOS\n * space.\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! The configuration of a tape-aware garbage collector for a specific EOS\n//! space.\n//------------------------------------------------------------------------------\nstruct SpaceConfig {\n  std::time_t queryPeriodSecs;\n  std::uint64_t availBytes;\n  std::string freeBytesScript;\n  std::uint64_t totalBytes;\n\n  SpaceConfig():\n    queryPeriodSecs(TGC_DEFAULT_QRY_PERIOD_SECS),\n    availBytes(TGC_DEFAULT_AVAIL_BYTES),\n    freeBytesScript(TGC_DEFAULT_FREE_BYTES_SCRIPT),\n    totalBytes(TGC_DEFAULT_TOTAL_BYTES)\n  {\n  }\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/SpaceNotFound.cc",
    "content": "// ----------------------------------------------------------------------\n// File: SpaceNotFound.cc\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/tgc/SpaceNotFound.hh\"\n\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Thrown when a given EOS space cannot be found\n//------------------------------------------------------------------------------\nSpaceNotFound::SpaceNotFound(const std::string &msg): std::runtime_error(msg) {\n}\n\nEOSTGCNAMESPACE_END\n"
  },
  {
    "path": "mgm/tgc/SpaceNotFound.hh",
    "content": "// ----------------------------------------------------------------------\n// File: SpaceNotFound.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGC_SPACENOTFOUND_HH__\n#define __EOSMGMTGC_SPACENOTFOUND_HH__\n\n#include <stdexcept>\n\n/**\n * @file TapeAwareGcSpaceNotFound.hh\n *\n * @brief Exception thrown when a given EOS space cannot be found\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Thrown when a given EOS space cannot be found\n//------------------------------------------------------------------------------\nstruct SpaceNotFound: public std::runtime_error {\n  SpaceNotFound(const std::string &msg);\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/SpaceStats.hh",
    "content": "// ----------------------------------------------------------------------\n// File: SpaceStats.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGC_SPACESTATS_HH__\n#define __EOSMGMTGC_SPACESTATS_HH__\n\n#include \"mgm/Namespace.hh\"\n\n#include <cstdint>\n#include <string>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file FreeAndUsedBytes.hh\n *\n * @brief Structure to store statistics about an EOS space\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\n//! Structure to store the statistics about an EOS space\n/*----------------------------------------------------------------------------*/\nstruct SpaceStats {\n  std::uint64_t totalBytes;\n  std::uint64_t availBytes;\n\n  SpaceStats(): totalBytes(0), availBytes(0) {}\n\n  bool operator==(const SpaceStats &rhs) const {\n    return totalBytes == rhs.totalBytes && availBytes == rhs.availBytes;\n  }\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/SpaceToTapeGcMap.cc",
    "content": "// ----------------------------------------------------------------------\n// File: SpaceToTapeGcMap.cc\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/tgc/MaxLenExceeded.hh\"\n#include \"mgm/tgc/SpaceToTapeGcMap.hh\"\n\n#include <memory>\n#include <sstream>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file SpaceToTapeAwareGcMap.cc\n *\n * @brief Class implementing a thread safe map from EOS space name to tape\n * ware garbage collector\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n//! Constructor\n//----------------------------------------------------------------------------\nSpaceToTapeGcMap::SpaceToTapeGcMap(ITapeGcMgm &mgm): m_mgm(mgm)\n{\n}\n\n//----------------------------------------------------------------------------\n//! Create a tape aware garbage collector for the specified EOS space.\n//----------------------------------------------------------------------------\nTapeGc&\nSpaceToTapeGcMap::createGc(const std::string &space)\n{\n  if(space.empty()) {\n    std::ostringstream msg;\n    msg << \"EOS space passed to \" << __FUNCTION__ << \" is an empty string\";\n    throw std::runtime_error(msg.str());\n  }\n\n  std::lock_guard<std::mutex> lock(m_mutex);\n\n  auto itor = m_gcs.find(space);\n  if(m_gcs.end() != itor) {\n    std::ostringstream msg;\n    msg << \"A tape aware garbage collector already exists for EOS space \" << space;\n    throw GcAlreadyExists(msg.str());\n  }\n\n  const auto result = m_gcs.emplace(space, std::make_unique<TapeGc>(m_mgm, space));\n  if(!result.second) {\n    std::ostringstream msg;\n    msg << \"Failed to insert new TapeGC for EOS space \" << space << \" into internal map\";\n    throw std::runtime_error(msg.str());\n  }\n  return *(result.first->second);\n}\n\n//----------------------------------------------------------------------------\n//! Destroys the tape aware garbage collectors for all EOS spaces.\n//----------------------------------------------------------------------------\nvoid\nSpaceToTapeGcMap::destroyAllGc()\n{\n  std::lock_guard<std::mutex> lock(m_mutex);\n  m_gcs.clear();\n}\n\n//----------------------------------------------------------------------------\n//! Returns the garbage collector associated with the specified EOS space.\n//----------------------------------------------------------------------------\nTapeGc\n&SpaceToTapeGcMap::getGc(const std::string &space) const\n{\n  if(space.empty()) {\n    std::ostringstream msg;\n    msg << \"EOS space passed to \" << __FUNCTION__ << \" is an empty string\";\n    throw std::runtime_error(msg.str());\n  }\n\n  std::lock_guard<std::mutex> lock(m_mutex);\n\n  auto itor = m_gcs.find(space);\n  if(m_gcs.end() == itor) {\n    std::ostringstream msg;\n    msg << \"EOS space \" << space << \" is unknown to \" << __FUNCTION__;\n    throw UnknownEOSSpace(msg.str());\n  }\n\n  auto &gc = itor->second;\n  if(!gc) {\n    std::stringstream msg;\n    msg << \"Encountered unexpected nullptr to TapeGc for EOS space \" << space;\n    throw std::runtime_error(msg.str());\n  } \n\n  return *gc;\n}\n\n//----------------------------------------------------------------------------\n//! @return map from EOS space name to tape-aware GC statistics\n//----------------------------------------------------------------------------\nstd::map<std::string, TapeGcStats>\nSpaceToTapeGcMap::getStats() const\n{\n  std::map<std::string, TapeGcStats> stats;\n\n  std::lock_guard<std::mutex> lock(m_mutex);\n\n  for(auto &spaceAndTapeGc : m_gcs) {\n    if(nullptr != spaceAndTapeGc.second) {\n      stats[spaceAndTapeGc.first] = spaceAndTapeGc.second->getStats();\n    }\n  }\n\n  return stats;\n}\n\n//----------------------------------------------------------------------------\n// Return the names of the EOS spaces being garbage collected\n//----------------------------------------------------------------------------\nstd::set<std::string>\nSpaceToTapeGcMap::getSpaces() const\n{\n  std::set<std::string> spaces;\n\n  std::lock_guard<std::mutex> lock(m_mutex);\n\n  for (auto &spaceAndTapeGc : m_gcs) {\n    if (nullptr != spaceAndTapeGc.second) {\n      if (0 != spaces.count(spaceAndTapeGc.first)) {\n        std::ostringstream msg;\n        msg << __FUNCTION__ << \" failed: Detected two garbage collectors working on the same EOS space: space=\" <<\n          spaceAndTapeGc.first;\n        throw std::runtime_error(msg.str());\n      }\n      spaces.insert(spaceAndTapeGc.first);\n    }\n  }\n\n  return spaces;\n}\n\n//----------------------------------------------------------------------------\n// Return A JSON string representation of this map\n//----------------------------------------------------------------------------\nvoid\nSpaceToTapeGcMap::toJson(std::ostringstream &os, std::uint64_t maxLen) const\n{\n  os << \"{\";\n  {\n    bool isFirstGc = true;\n    std::lock_guard<std::mutex> lock(m_mutex);\n    for (auto &spaceAndTapeGc : m_gcs) {\n      if (isFirstGc) {\n        isFirstGc = false;\n      } else {\n        os << \",\";\n      }\n\n      if (nullptr != spaceAndTapeGc.second) {\n        os << \"\\\"\" << spaceAndTapeGc.first << \"\\\":\";\n        spaceAndTapeGc.second->toJson(os, maxLen);\n\n        const auto osSize = os.tellp();\n        if (0 > osSize) throw std::runtime_error(std::string(__FUNCTION__) + \": os.tellp() returned a negative number\");\n        if (maxLen && maxLen < (std::string::size_type)osSize) {\n          std::ostringstream msg;\n          msg << __FUNCTION__ << \": maxLen exceeded: maxLen=\" << maxLen;\n          throw MaxLenExceeded(msg.str());\n        }\n      }\n    }\n  }\n  os << \"}\";\n\n  {\n    const auto osSize = os.tellp();\n    if (0 > osSize) throw std::runtime_error(std::string(__FUNCTION__) + \": os.tellp() returned a negative number\");\n    if (maxLen && maxLen < (std::string::size_type)osSize) {\n      std::ostringstream msg;\n      msg << __FUNCTION__ << \": maxLen exceeded: maxLen=\" << maxLen;\n      throw MaxLenExceeded(msg.str());\n    }\n  }\n}\n\n//--------------------------------------------------------------------------\n// Start the worker thread of each garbage collector\n//--------------------------------------------------------------------------\nvoid\nSpaceToTapeGcMap::startGcWorkerThreads()\n{\n  std::lock_guard<std::mutex> lock(m_mutex);\n\n  for(auto &spaceAndTapeGc : m_gcs) {\n    if(nullptr != spaceAndTapeGc.second) {\n      spaceAndTapeGc.second->startWorkerThread();\n    }\n  }\n}\n\nEOSTGCNAMESPACE_END\n"
  },
  {
    "path": "mgm/tgc/SpaceToTapeGcMap.hh",
    "content": "// ----------------------------------------------------------------------\n// File: SpaceToTapeGcMap.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_SPACETOTAPEAGCMAP_HH__\n#define __EOSMGM_SPACETOTAPEAGCMAP_HH__\n\n#include \"mgm/tgc/ITapeGcMgm.hh\"\n#include \"mgm/tgc/TapeGc.hh\"\n#include \"mgm/tgc/TapeGcStats.hh\"\n\n#include <map>\n#include <set>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file SpaceToTapeGcMap.hh\n *\n * @brief Class implementing a thread safe map from EOS space name to tape aware\n * garbage collector\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class implementing a thread safe map from EOS space name to tape aware\n//! garbage collector\n//------------------------------------------------------------------------------\nclass SpaceToTapeGcMap {\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor.\n  //!\n  //! @param mgm the interface to the EOS MGM\n  //----------------------------------------------------------------------------\n  SpaceToTapeGcMap(ITapeGcMgm &mgm);\n\n  //----------------------------------------------------------------------------\n  //! Deletion of copy constructor.\n  //----------------------------------------------------------------------------\n  SpaceToTapeGcMap(const SpaceToTapeGcMap &) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Deletion of move constructor.\n  //----------------------------------------------------------------------------\n  SpaceToTapeGcMap(const SpaceToTapeGcMap &&) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Exception thrown when a tape aware garbage collector already exists.\n  //----------------------------------------------------------------------------\n  struct GcAlreadyExists: public std::runtime_error {\n    GcAlreadyExists(const std::string &msg): std::runtime_error(msg) {}\n  };\n\n  //----------------------------------------------------------------------------\n  //! Thread safe method that creates a tape-aware garbage collector for the\n  //! specified EOS space.\n  //!\n  //! @param space The name of the EOS space.\n  //! @preturn A reference to the newly created tape-aware garbage collector.\n  //! @throw GcAlreadyExists If a tape aware garbage collector already exists\n  //! for the specified EOS space.\n  //----------------------------------------------------------------------------\n  TapeGc &createGc(const std::string &space);\n\n  //----------------------------------------------------------------------------\n  //! Thread safe method that destroys all tape-aware garbage collectors.\n  //----------------------------------------------------------------------------\n  void destroyAllGc();\n\n  //----------------------------------------------------------------------------\n  //! Exception thrown when an unknown EOS space is encountered.\n  //----------------------------------------------------------------------------\n  struct UnknownEOSSpace: public std::runtime_error {\n    UnknownEOSSpace(const std::string &msg): std::runtime_error(msg) {}\n  };\n\n  //----------------------------------------------------------------------------\n  //! Thread safe method that returns the garbage collector associated with the\n  //! specified EOS space.\n  //!\n  //! @param space The name of the EOS space.\n  //! @return The tape aware garbage collector associated with the specified EOS\n  //! space.\n  //! @throw UnknownEOSSpace If the specified EOS space is unknown.\n  //----------------------------------------------------------------------------\n  TapeGc &getGc(const std::string &space) const;\n\n  //----------------------------------------------------------------------------\n  //! @return map from EOS space name to tape-aware GC statistics\n  //----------------------------------------------------------------------------\n  std::map<std::string, TapeGcStats> getStats() const;\n\n  //----------------------------------------------------------------------------\n  //! @return a list of the names of the EOS spaces being garbage collected\n  //----------------------------------------------------------------------------\n  std::set<std::string> getSpaces() const;\n\n  //----------------------------------------------------------------------------\n  //! Writes the JSON representation of this object to the specified stream.\n  //!\n  //! @param os Input/Output parameter specifying the stream to write to.\n  //! @param maxLen The maximum length the stream should be.  A value of 0 means\n  //! unlimited.  This method can go over the maxLen limit but it MUST throw\n  //! a MaxLenExceeded exception if it does.\n  //!\n  //! @throw MaxLenExceeded if the length of the JSON string has exceeded maxLen\n  //----------------------------------------------------------------------------\n  void toJson(std::ostringstream &os, std::uint64_t maxLen = 0) const;\n\n  //--------------------------------------------------------------------------\n  //! Start the worker thread of each garbage collector\n  //--------------------------------------------------------------------------\n  void startGcWorkerThreads();\n\nprivate:\n\n  //--------------------------------------------------------------------------\n  //! The interface to the EOS MGM\n  //--------------------------------------------------------------------------\n  ITapeGcMgm &m_mgm;\n\n  //--------------------------------------------------------------------------\n  //! Mutex protecting the map\n  //--------------------------------------------------------------------------\n  mutable std::mutex m_mutex;\n\n  //----------------------------------------------------------------------------\n  //! Map from space name to tape aware garbage collector\n  //----------------------------------------------------------------------------\n  std::map<std::string, std::unique_ptr<TapeGc> > m_gcs;\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/TapeGc.cc",
    "content": "// ----------------------------------------------------------------------\n// File: TapeGc.cc\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/tgc/Constants.hh\"\n#include \"mgm/tgc/MaxLenExceeded.hh\"\n#include \"mgm/tgc/TapeGc.hh\"\n#include \"mgm/tgc/SpaceNotFound.hh\"\n#include \"mgm/CtaUtils.hh\"\n\n#include <cstring>\n#include <functional>\n#include <iomanip>\n#include <ios>\n#include <sstream>\n\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nTapeGc::TapeGc(ITapeGcMgm& mgm, const std::string& spaceName,\n               const std::time_t maxConfigCacheAgeSecs):\n  m_mgm(mgm),\n  m_spaceName(spaceName),\n  m_config(std::bind(&ITapeGcMgm::getTapeGcSpaceConfig, &mgm, spaceName),\n           maxConfigCacheAgeSecs),\n  m_spaceStats(spaceName, mgm, m_config), m_nbEvicts(0)\n{\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nTapeGc::~TapeGc()\n{\n  try {\n    std::lock_guard<std::mutex> workerLock(m_workerMutex);\n\n    if (m_worker) {\n      m_stop.setToTrue();\n      m_worker->join();\n    }\n  } catch (std::exception& ex) {\n    eos_static_err(\"msg=\\\"%s\\\"\", ex.what());\n  } catch (...) {\n    eos_static_err(\"msg=\\\"Caught an unknown exception\\\"\");\n  }\n}\n\n//------------------------------------------------------------------------------\n// Idempotent method to start the worker thread of the tape-aware GC\n//------------------------------------------------------------------------------\nvoid\nTapeGc::startWorkerThread()\n{\n  try {\n    // Do nothing if calling thread is not the first to call startWorkerThread()\n    if (m_startWorkerThreadMethodCalled.test_and_set()) {\n      return;\n    }\n\n    std::function<void()> entryPoint = std::bind(&TapeGc::workerThreadEntryPoint,\n                                       this);\n    {\n      std::lock_guard<std::mutex> workerLock(m_workerMutex);\n      m_worker = std::make_unique<std::thread>(entryPoint);\n    }\n  } catch (std::exception& ex) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \" failed: \" << ex.what();\n    throw std::runtime_error(msg.str());\n  } catch (...) {\n    std::ostringstream msg;\n    msg << __FUNCTION__ << \" failed: Caught an unknown exception\";\n    throw std::runtime_error(msg.str());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Entry point for the GC worker thread\n//------------------------------------------------------------------------------\nvoid\nTapeGc::workerThreadEntryPoint() noexcept\n{\n  do {\n    while (!m_stop && tryToGarbageCollectASingleFile()) {\n    }\n  } while (!m_stop.waitForTrue(std::chrono::seconds(1)));\n}\n\n//------------------------------------------------------------------------------\n// Notify GC the specified file has been accessed\n//------------------------------------------------------------------------------\nvoid\nTapeGc::fileAccessed(const IFileMD::id_t fid) noexcept\n{\n  try {\n    std::lock_guard<std::mutex> lruQueueLock(m_lruQueueMutex);\n    const bool exceededBefore = m_lruQueue.maxQueueSizeExceeded();\n    m_lruQueue.fileAccessed(fid);\n\n    // Only log crossing the max queue size threshold - don't log each access\n    if (!exceededBefore && m_lruQueue.maxQueueSizeExceeded()) {\n      std::ostringstream msg;\n      msg << \"space=\\\"\" << m_spaceName << \"\\\" fxid=\" << std::hex << fid <<\n          \" msg=\\\"Max queue size of tape-aware GC has been passed - new files will be ignored\\\"\";\n      eos_static_warning(msg.str().c_str());\n    }\n  } catch (std::exception& ex) {\n    eos_static_err(\"msg=\\\"%s\\\"\", ex.what());\n  } catch (...) {\n    eos_static_err(\"msg=\\\"Caught an unknown exception\\\"\");\n  }\n}\n\n//------------------------------------------------------------------------------\n// Try to garage collect a single file if necessary and possible\n//------------------------------------------------------------------------------\nbool\nTapeGc::tryToGarbageCollectASingleFile() noexcept\n{\n  try {\n    const auto config = m_config.get();\n\n    try {\n      const auto spaceStats = m_spaceStats.get().stats;\n\n      // Return no file was garbage collected if there is still enough available\n      // space or if the total amount of space is not enough (not all disk\n      // systems are on-line)\n      if (spaceStats.availBytes >= config.availBytes ||\n          spaceStats.totalBytes < config.totalBytes) {\n        return false;\n      }\n    } catch (SpaceNotFound&) {\n      // Return no file was garbage collected if the space was not found\n      return false;\n    }\n\n    IFileMD::id_t fid = 0;\n    {\n      std::lock_guard<std::mutex> lruQueueLock(m_lruQueueMutex);\n\n      if (m_lruQueue.empty()) {\n        return false; // No file was garbage collected\n      }\n\n      fid = m_lruQueue.getAndPopFidOfLeastUsedFile();\n    }\n    std::uint64_t diskReplicaToBeDeletedSizeBytes = 0;\n\n    try {\n      diskReplicaToBeDeletedSizeBytes = m_mgm.getFileSizeBytes(fid);\n    } catch (std::exception& ex) {\n      std::ostringstream msg;\n      msg << \"fxid=\" << std::hex << fid <<\n          \" msg=\\\"Unable to garbage collect disk replica: \"\n          << ex.what() << \"\\\"\";\n      eos_static_info(msg.str().c_str());\n      // Please note that a file is considered successfully garbage collected\n      // if its size cannot be determined\n      return true;\n    } catch (...) {\n      std::ostringstream msg;\n      msg << \"fxid=\" << std::hex << fid <<\n          \" msg=\\\"Unable to garbage collect disk replica: Unknown exception\";\n      eos_static_info(msg.str().c_str());\n      // Please note that a file is considered successfully garbage collected\n      // if its size cannot be determined\n      return true;\n    }\n\n    // The garbage collector should explicitly ignore zero length files by\n    // returning success\n    if (0 == diskReplicaToBeDeletedSizeBytes) {\n      std::ostringstream msg;\n      msg << \"fxid=\" << std::hex << fid <<\n          \" msg=\\\"Garbage collector ignoring zero length file\\\"\";\n      eos_static_info(msg.str().c_str());\n      return true;\n    }\n\n    try {\n      m_mgm.evictAsRoot(fid);\n    } catch (std::exception& ex) {\n      std::ostringstream msg;\n      msg << \"fxid=\" << std::hex << fid <<\n          \" msg=\\\"Putting file back in GC queue after failing to garbage collect its disk replica: \"\n          << ex.what();\n      eos_static_info(msg.str().c_str());\n      std::lock_guard<std::mutex> lruQueueLock(m_lruQueueMutex);\n      m_lruQueue.fileAccessed(fid);\n      return false; // No disk replica was garbage collected\n    } catch (...) {\n      std::ostringstream msg;\n      msg << \"fxid=\" << std::hex << fid <<\n          \" msg=\\\"Putting file back in GC queue after failing to garbage collect its disk replica: Unknown exception\";\n      eos_static_info(msg.str().c_str());\n      std::lock_guard<std::mutex> lruQueueLock(m_lruQueueMutex);\n      m_lruQueue.fileAccessed(fid);\n      return false; // No disk replica was garbage collected\n    }\n\n    m_nbEvicts++;\n    diskReplicaQueuedForDeletion(diskReplicaToBeDeletedSizeBytes);\n    std::ostringstream msg;\n    msg << \"fxid=\" << std::hex << fid <<\n        \" msg=\\\"Garbage collected disk replica using evict\\\"\";\n    eos_static_info(msg.str().c_str());\n    return true; // A disk replica was garbage collected\n  } catch (std::exception& ex) {\n    eos_static_err(\"msg=\\\"%s\\\"\", ex.what());\n  } catch (...) {\n    eos_static_err(\"msg=\\\"Caught an unknown exception\\\"\");\n  }\n\n  return false; // No disk replica was garbage collected\n}\n\n//----------------------------------------------------------------------------\n// Return statistics\n//----------------------------------------------------------------------------\nTapeGcStats\nTapeGc::getStats() noexcept\n{\n  try {\n    TapeGcStats tgcStats;\n    tgcStats.nbEvicts = m_nbEvicts;\n    tgcStats.lruQueueSize = getLruQueueSize();\n    tgcStats.spaceStats = m_spaceStats.get().stats;\n    tgcStats.queryTimestamp = m_spaceStats.getQueryTimestamp();\n    return tgcStats;\n  } catch (...) {\n    return TapeGcStats();\n  }\n}\n\n//----------------------------------------------------------------------------\n// Return the size of the LRU queue\n//----------------------------------------------------------------------------\nLru::FidQueue::size_type\nTapeGc::getLruQueueSize() const noexcept\n{\n  const char* const msgFormat =\n    \"TapeGc::getLruQueueSize() failed space=%s: %s\";\n\n  try {\n    std::lock_guard<std::mutex> lruQueueLock(m_lruQueueMutex);\n    return m_lruQueue.size();\n  } catch (std::exception& ex) {\n    eos_static_err(msgFormat, m_spaceName.c_str(), ex.what());\n  } catch (...) {\n    eos_static_err(msgFormat, m_spaceName.c_str(), \"Caught an unknown exception\");\n  }\n\n  return 0;\n}\n\n//----------------------------------------------------------------------------\n// Return A JSON string representation of the GC\n//----------------------------------------------------------------------------\nvoid\nTapeGc::toJson(std::ostringstream& os, const std::uint64_t maxLen) const\n{\n  {\n    std::lock_guard<std::mutex> lruQueueLock(m_lruQueueMutex);\n    os <<\n       \"{\"\n       \"\\\"spaceName\\\":\\\"\" << m_spaceName << \"\\\",\"\n       \"\\\"lruQueue\\\":\";\n    m_lruQueue.toJson(os, maxLen);\n    os << \"}\";\n  }\n  {\n    const auto osSize = os.tellp();\n\n    if (0 > osSize) {\n      throw std::runtime_error(std::string(__FUNCTION__) +\n                               \": os.tellp() returned a negative number\");\n    }\n\n    if (maxLen && maxLen < (std::string::size_type)osSize) {\n      std::ostringstream msg;\n      msg << __FUNCTION__ << \": maxLen exceeded: maxLen=\" << maxLen;\n      throw MaxLenExceeded(msg.str());\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Notify this object that a disk replica has been queued for deletion\n//------------------------------------------------------------------------------\nvoid\nTapeGc::diskReplicaQueuedForDeletion(const size_t fileSizeBytes)\n{\n  m_spaceStats.diskReplicaQueuedForDeletion(fileSizeBytes);\n}\n\nEOSTGCNAMESPACE_END\n"
  },
  {
    "path": "mgm/tgc/TapeGc.hh",
    "content": "// ----------------------------------------------------------------------\n// File: TapeGc.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_TAPEGC_HH__\n#define __EOSMGM_TAPEGC_HH__\n\n#include \"common/Logging.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"mgm/tgc/BlockingFlag.hh\"\n#include \"mgm/tgc/CachedValue.hh\"\n#include \"mgm/tgc/Constants.hh\"\n#include \"mgm/tgc/ITapeGcMgm.hh\"\n#include \"mgm/tgc/Lru.hh\"\n#include \"mgm/tgc/SmartSpaceStats.hh\"\n#include \"mgm/tgc/SpaceConfig.hh\"\n#include \"mgm/tgc/TapeGcStats.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"proto/ConsoleReply.pb.h\"\n#include \"proto/ConsoleRequest.pb.h\"\n\n#include <atomic>\n#include <cstdint>\n#include <ctime>\n#include <mutex>\n#include <stdexcept>\n#include <thread>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file TapeGc.hh\n *\n * @brief Class implementing a tape aware garbage collector\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! A tape aware garbage collector\n//------------------------------------------------------------------------------\nclass TapeGc\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param mgm interface to the EOS MGM\n  //! @param spaceName name of the EOS space that this garbage collector will\n  //! manage\n  //! @param maxConfigCacheAgeSecs maximum age in seconds of a tape-ware garbage\n  //! collector's cached configuration\n  //----------------------------------------------------------------------------\n  TapeGc(\n    ITapeGcMgm &mgm,\n    const std::string &spaceName,\n    std::time_t maxConfigCacheAgeSecs = TGC_DEFAULT_MAX_CONFIG_CACHE_AGE_SECS\n  );\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~TapeGc();\n\n  //----------------------------------------------------------------------------\n  //! Delete copy constructor\n  //----------------------------------------------------------------------------\n  TapeGc(const TapeGc &) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Delete move constructor\n  //----------------------------------------------------------------------------\n  TapeGc(const TapeGc &&) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Delete assignment operator\n  //----------------------------------------------------------------------------\n  TapeGc &operator=(const TapeGc &) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Idempotent method to start the worker thread of the tape-aware GC\n  //----------------------------------------------------------------------------\n  void startWorkerThread();\n\n  //----------------------------------------------------------------------------\n  //! Notify GC the specified file has been accessed\n  //!\n  //! @param fid file identifier\n  //----------------------------------------------------------------------------\n  void fileAccessed(IFileMD::id_t fid) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! @return statistics\n  //----------------------------------------------------------------------------\n  TapeGcStats getStats() noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Writes the JSON representation of this object to the specified stream.\n  //!\n  //! @param os Input/Output parameter specifying the stream to write to.\n  //! @param maxLen The maximum length the stream should be.  A value of 0 means\n  //! unlimited.  This method can go over the maxLen limit but it MUST throw\n  //! a MaxLenExceeded exception if it does.\n  //!\n  //! @throw MaxLenExceeded if the length of the JSON string has exceeded maxLen\n  //----------------------------------------------------------------------------\n  void toJson(std::ostringstream &os, std::uint64_t maxLen = 0) const;\n\nprotected:\n\n  /// The interface to the EOS MGM\n  ITapeGcMgm &m_mgm;\n\n  /// The name of the EOS space managed by this garbage collector\n  std::string m_spaceName;\n\n  /// Ensures startWorkerThread() only starts the worker thread once\n  std::atomic_flag m_startWorkerThreadMethodCalled = ATOMIC_FLAG_INIT;\n\n  /// True if the worker thread should stop\n  BlockingFlag m_stop;\n\n  /// Mutex dedicated to protecting the m_worker member variable\n  std::mutex m_workerMutex;\n\n  /// The one and only GC worker thread\n  std::unique_ptr<std::thread> m_worker;\n\n  /// Mutex protecting mLruQueue\n  mutable std::mutex m_lruQueueMutex;\n\n  /// Queue of Least Recently Used (LRU) files\n  Lru m_lruQueue;\n\n  //----------------------------------------------------------------------------\n  //! Entry point for the GC worker thread\n  //----------------------------------------------------------------------------\n  void workerThreadEntryPoint() noexcept;\n\n  //----------------------------------------------------------------------------\n  //! @return the size of the LRU queue.  Zero is returned in the case of error.\n  //----------------------------------------------------------------------------\n  Lru::FidQueue::size_type getLruQueueSize() const noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Try to garbage collect a single file if necessary and possible.\n  //!\n  //! Please note that a file is considered successfully garbage collected if\n  //! it does not exists in the EOS namespace when it is popped from the LRU\n  //! data structure.\n  //!\n  //! @return True if a file was garbage collected\n  //----------------------------------------------------------------------------\n  bool tryToGarbageCollectASingleFile() noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Configuration\n  //----------------------------------------------------------------------------\n  CachedValue<SpaceConfig> m_config;\n\n  //----------------------------------------------------------------------------\n  //! Statistics about the EOS space being managed\n  //----------------------------------------------------------------------------\n  SmartSpaceStats m_spaceStats;\n\n  //----------------------------------------------------------------------------\n  //! Counter that is incremented each time a file is successfully evicted\n  //----------------------------------------------------------------------------\n  std::atomic<std::uint64_t> m_nbEvicts;\n\n  //----------------------------------------------------------------------------\n  //! Take note of a disk replica queued for deletion so that the amount of free\n  //! space can be updated without having to wait for the next query to the EOS\n  //! MGM\n  //!\n  //! @param fileSizeBytes File size in bytes\n  //----------------------------------------------------------------------------\n  void diskReplicaQueuedForDeletion(size_t fileSizeBytes);\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/TapeGcStats.hh",
    "content": "// ----------------------------------------------------------------------\n// File: TapeGcStats.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGMTGCTAPEGCSTATS_HH__\n#define __EOSMGMTGCTAPEGCSTATS_HH__\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/tgc/SpaceStats.hh\"\n\n#include <cstdint>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file TapeGcStats.hh\n *\n * @brief Statistics about a tape-aware GC\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Statistics about a tape-aware GC\n//------------------------------------------------------------------------------\nstruct TapeGcStats {\n  //----------------------------------------------------------------------------\n  //! Constructor.\n  //----------------------------------------------------------------------------\n  TapeGcStats(): nbEvicts(0),\n    lruQueueSize(0),\n    queryTimestamp(0) {\n  }\n\n  //----------------------------------------------------------------------------\n  //! Number of files successfully evicted since TapeGc started.\n  //! This value is Zero in the case of an error.\n  //----------------------------------------------------------------------------\n  std::uint64_t nbEvicts;\n\n  //----------------------------------------------------------------------------\n  //! Size of the LRU queue.  This value is Zero in the case of an error.\n  //----------------------------------------------------------------------------\n  Lru::FidQueue::size_type lruQueueSize;\n\n  //----------------------------------------------------------------------------\n  //! Statistics about the EOS space being managed by the tape-aware garbage\n  //! collector\n  //----------------------------------------------------------------------------\n  SpaceStats spaceStats;\n\n  //----------------------------------------------------------------------------\n  //! Timestamp at which the EOS space was queried.  This value is zero in the\n  //! case of error.\n  //----------------------------------------------------------------------------\n  time_t queryTimestamp;\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tgc/TestingTapeGc.hh",
    "content": "// ----------------------------------------------------------------------\n// File: TestingTapeGc.hh\n// Author: Steven Murray - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_TESTINGTAPEGC_HH__\n#define __EOSMGM_TESTINGTAPEGC_HH__\n\n#include \"mgm/tgc/TapeGc.hh\"\n\n#include <atomic>\n#include <ctime>\n#include <mutex>\n#include <stdexcept>\n#include <thread>\n\n/*----------------------------------------------------------------------------*/\n/**\n * @file TestingTapeGc.hh\n *\n * @brief Facilitates the unit testing of the TapeGc class\n *\n */\n/*----------------------------------------------------------------------------*/\nEOSTGCNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Facilitates the unit testing of the TapeGc class\n//------------------------------------------------------------------------------\nclass TestingTapeGc: public TapeGc\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param mgm interface to the EOS MGM\n  //! @param space name of the EOS space that this garbage collector will work\n  //! on\n  //! @param maxConfigCacheAgeSecs maximum age in seconds of a tape-ware garbage\n  //! collector's cached configuration\n  //----------------------------------------------------------------------------\n  TestingTapeGc(\n    ITapeGcMgm &mgm,\n    const std::string &space,\n    const std::time_t maxConfigCacheAgeSecs\n  ): TapeGc(mgm, space, maxConfigCacheAgeSecs)\n  {\n  }\n\n  //----------------------------------------------------------------------------\n  //! Make tryToGarbageCollectASingleFile() public so it can be unit tested\n  //----------------------------------------------------------------------------\n  using TapeGc::tryToGarbageCollectASingleFile;\n};\n\nEOSTGCNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/tracker/ReplicationTracker.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ReplicationTracker.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/tracker/ReplicationTracker.hh\"\n#include \"common/Constants.hh\"\n#include \"common/FileId.hh\"\n#include \"common/IntervalStopwatch.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/Path.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/proc/ProcCommand.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"namespace/Resolver.hh\"\n#include \"namespace/interface/IView.hh\"\n\nnamespace\n{\n//----------------------------------------------------------------------------\n//! Get a file system location that belongs to the file. Make sure thi is\n//! not a TAPE_FS_ID as it's used to determine the space that the file\n//! belongs to\n//!\n//! @return file systemd id holding the file\n//----------------------------------------------------------------------------\neos::common::FileSystem::fsid_t\nGetValidLocation(std::shared_ptr<eos::IFileMD> fmd)\n{\n  int fsid = 0;\n  unsigned int index = 0;\n  unsigned int num_loc = fmd->getNumLocation();\n\n  while (num_loc && (index < num_loc)) {\n    fsid = fmd->getLocation(index);\n\n    if (fsid && (fsid != eos::common::TAPE_FS_ID)) {\n      break;\n    }\n\n    fsid = 0;\n    ++index;\n  }\n\n  return fsid;\n}\n}\n\nEOSMGMNAMESPACE_BEGIN\n\nusing namespace eos::common;\n\nstatic constexpr auto REPTRACKER_THREAD_NAME = \"RepTracker\";\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nReplicationTracker::ReplicationTracker(const char* path) : mPath(path)\n{\n  mVid = eos::common::VirtualIdentity::Root();\n  mConversionEnabled.store(0, std::memory_order_seq_cst);\n  mThread.reset(&ReplicationTracker::backgroundThread, this);\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nReplicationTracker::~ReplicationTracker()\n{\n  mThread.join();\n}\n\n//------------------------------------------------------------------------------\n// Create a new file\n//------------------------------------------------------------------------------\nvoid\nReplicationTracker::Create(std::shared_ptr<eos::IFileMD> fmd)\n{\n  if (!enabled()) {\n    return;\n  }\n\n  std::string prefix = Prefix(fmd);\n  std::string tag = prefix + eos::common::FileId::Fid2Hex(fmd->getId());\n  std::shared_ptr<eos::IContainerMD> dmd;\n\n  try {\n    gOFS->eosView->createContainer(prefix, true);\n    dmd = gOFS->eosView->getContainer(prefix);\n    dmd->setCTimeNow();\n    gOFS->eosView->updateContainerStore(dmd.get());\n  } catch (const MDException& e) {\n  }\n\n  try {\n    fmd = gOFS->eosView->createFile(tag.c_str(), 0, 0);\n  } catch (const MDException& e) {\n    eos_static_crit(\"failed to create tag file='%s'\", tag.c_str());\n    return;\n  }\n\n  std::string uri = gOFS->eosView->getUri(fmd.get());\n  eos_static_info(\"op=created tag='%s' uri='%s'\", tag.c_str(), uri.c_str());\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Access an existing file\n//------------------------------------------------------------------------------\nvoid\nReplicationTracker::Access(std::shared_ptr<eos::IFileMD> fmd)\n{\n  if (conversion_enabled()) {\n    // determine the space from the first filesystem ID stored\n    eos::common::FileSystem::fsid_t fsid = GetValidLocation(fmd);\n\n    if (fsid) {\n      std::string policy = ConversionPolicy(OperationMode::eAccess, fsid);\n\n      if (policy.length()) {\n        size_t cutoff_size = 0;\n        bool do_conversion = true;\n        std::string size_policy =\n          ConversionSizePolicy(OperationMode::eAccess, fsid);\n\n        if (size_policy.length()) {\n          switch ((size_policy.at(0))) {\n          case '<':\n            // max size policy\n            cutoff_size = std::stol(size_policy.substr(1));\n\n            if (fmd->getSize() >= cutoff_size) {\n              if (EOS_LOGS_DEBUG) {\n                eos_static_debug(\n                  \"suppressing conversion because of minimum size \"\n                  \"policy '%s' fxid:%08llx\",\n                  policy.c_str(), fmd->getId());\n              }\n\n              do_conversion = false;\n            }\n\n            break;\n\n          case '>':\n            // min size policy\n            cutoff_size = std::stol(size_policy.substr(1));\n\n            if (fmd->getSize() <= cutoff_size) {\n              if (EOS_LOGS_DEBUG) {\n                eos_static_debug(\n                  \"suppressing conversion because of maximum size \"\n                  \"policy '%s' fxid:%08llx\",\n                  policy.c_str(), fmd->getId());\n              }\n\n              do_conversion = false;\n            }\n\n          default:\n            eos_static_warning(\n              \"illegal space conversion policy size: should be \"\n              \"empty '', <size '<1000', >size '>1000\");\n            break;\n          }\n        }\n\n        if (do_conversion) {\n          // create a conversion job for this file according to the policy\n          // definition\n          eos_static_info(\"triggering conversion policy '%s' for fxid:%08llx\",\n                          policy.c_str(), fmd->getId());\n          std::string layout;\n          std::string space;\n\n          if (eos::common::StringConversion::SplitKeyValue(policy, layout,\n              space, \"@\")) {\n            std::string info =\n              \"mgm.cmd=file&mgm.subcmd=convert&mgm.convert.layout=\";\n            info += layout;\n            info += \"&mgm.convert.space=\";\n            info += space;\n            info += \"&mgm.file.id=\";\n            info += std::to_string(fmd->getId());\n            XrdOucErrInfo error;\n            eos::common::VirtualIdentity rootvid =\n              eos::common::VirtualIdentity::Root();\n            ProcCommand cmd;\n            cmd.open(\"/proc/user\", info.c_str(), rootvid, &error);\n            cmd.close();\n            int rc = cmd.GetRetc();\n\n            if (rc) {\n              eos_static_err(\n                \"converions-hook failed with rc=%d for fxid:%08llx\", rc,\n                fmd->getId());\n            }\n          }\n        }\n      }\n    }\n  }\n}\n\nstd::string\nReplicationTracker::ConversionPolicy(OperationMode mode, int fsid)\n{\n  std::string space = FsView::gFsView.mIdView.lookupSpaceByID(fsid);\n  eos_static_debug(\"%s %d\", space.c_str(), fsid);\n\n  if (space.length()) {\n    eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n    auto it = FsView::gFsView.mSpaceView.find(space);\n\n    if (it != FsView::gFsView.mSpaceView.end()) {\n      switch (mode) {\n      case OperationMode::eInjection:\n        return it->second->GetConfigMember(\"policy.conversion.injection\");\n\n      case OperationMode::eCreation:\n        return it->second->GetConfigMember(\"policy.conversion.creation\");\n\n      case OperationMode::eAccess:\n        return it->second->GetConfigMember(\"policy.conversion.access\");\n      }\n    }\n  }\n  return \"\";\n}\n\nstd::string\nReplicationTracker::ConversionSizePolicy(OperationMode mode, int fsid)\n{\n  std::string space = FsView::gFsView.mIdView.lookupSpaceByID(fsid);\n  eos_static_debug(\"%s %d\", space.c_str(), fsid);\n\n  if (space.length()) {\n    eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n    auto it = FsView::gFsView.mSpaceView.find(space);\n\n    if (it != FsView::gFsView.mSpaceView.end()) {\n      switch (mode) {\n      case OperationMode::eInjection:\n        return it->second->GetConfigMember(\"policy.conversion.injection.size\");\n\n      case OperationMode::eCreation:\n        return it->second->GetConfigMember(\"policy.conversion.creation.size\");\n\n      case OperationMode::eAccess:\n        return it->second->GetConfigMember(\"policy.conversion.access.size\");\n      }\n    }\n  }\n  return \"\";\n}\n\n//------------------------------------------------------------------------------\n// Commit a file\n//------------------------------------------------------------------------------\nvoid\nReplicationTracker::Commit(std::shared_ptr<eos::IFileMD> fmd)\n{\n  // check if this is still a 'temporary' name\n  if (fmd->getName().substr(0, strlen(EOS_COMMON_PATH_ATOMIC_FILE_PREFIX)) ==\n      EOS_COMMON_PATH_ATOMIC_FILE_PREFIX) {\n    // check if this is still a 'temporary' name\n    return;\n  }\n\n  bool tapecopy = fmd->hasLocation(TAPE_FS_ID);\n\n  // check replica count\n  if ((fmd->getNumLocation() - tapecopy) ==\n      (eos::common::LayoutId::GetStripeNumber(fmd->getLayoutId()) + 1)) {\n    if (conversion_enabled()) {\n      // determine the space from the first filesystem ID stored\n      eos::common::FileSystem::fsid_t fsid = GetValidLocation(fmd);\n\n      if (fsid) {\n        std::string policy = ConversionPolicy(\n                               tapecopy ? OperationMode::eInjection : OperationMode::eCreation,\n                               fsid);\n\n        if (policy.length()) {\n          size_t cutoff_size = 0;\n          bool do_conversion = true;\n          std::string size_policy = ConversionSizePolicy(\n                                      tapecopy ? OperationMode::eInjection : OperationMode::eCreation,\n                                      fsid);\n\n          if (size_policy.length()) {\n            switch ((size_policy.at(0))) {\n            case '<':\n              // max size policy\n              cutoff_size = std::stol(size_policy.substr(1));\n\n              if (fmd->getSize() >= cutoff_size) {\n                if (EOS_LOGS_DEBUG) {\n                  eos_static_debug(\"suppressing conversion because of minimum \"\n                                   \"size policy '%s' fxid:%08llx\",\n                                   policy.c_str(), fmd->getId());\n                }\n\n                do_conversion = false;\n              }\n\n              break;\n\n            case '>':\n              // min size policy\n              cutoff_size = std::stol(size_policy.substr(1));\n\n              if (fmd->getSize() <= cutoff_size) {\n                if (EOS_LOGS_DEBUG) {\n                  eos_static_debug(\"suppressing conversion because of maximum \"\n                                   \"size policy '%s' fxid:%08llx\",\n                                   policy.c_str(), fmd->getId());\n                }\n\n                do_conversion = false;\n              }\n\n              break;\n\n            default:\n              eos_static_warning(\n                \"illegal space conversion policy size: should be \"\n                \"empty '', <size '<1000', >size '>1000\");\n              break;\n            }\n          }\n\n          if (do_conversion) {\n            // create a conversion job for this file according to the\n            // policy definition\n            eos_static_info(\"triggering conversion policy '%s' for fxid:%08llx\",\n                            policy.c_str(), fmd->getId());\n            std::string layout;\n            std::string space;\n\n            if (eos::common::StringConversion::SplitKeyValue(policy, layout,\n                space, \"@\")) {\n              std::string info = \"mgm.cmd=file&mgm.subcmd=convert&\"\n                                 \"mgm.convert.layout=\";\n              info += layout;\n              info += \"&mgm.convert.space=\";\n              info += space;\n              info += \"&mgm.file.id=\";\n              info += std::to_string(fmd->getId());\n              XrdOucErrInfo error;\n              eos::common::VirtualIdentity rootvid =\n                eos::common::VirtualIdentity::Root();\n              ProcCommand cmd;\n              cmd.open(\"/proc/user\", info.c_str(), rootvid, &error);\n              cmd.close();\n              int rc = cmd.GetRetc();\n\n              if (rc) {\n                eos_static_err(\"converions-hook failed with rc=%d \"\n                               \"for fxid:%08llx\",\n                               rc, fmd->getId());\n              }\n            }\n          }\n        }\n      }\n    }\n\n    if (!enabled()) {\n      return;\n    }\n\n    std::string prefix = Prefix(fmd);\n    std::string tag = prefix + eos::common::FileId::Fid2Hex(fmd->getId());\n    std::string uri = gOFS->eosView->getUri(fmd.get());\n    std::shared_ptr<eos::IFileMD> entry_fmd;\n    eos::common::RWMutexWriteLock nslock(gOFS->eosViewRWMutex);\n\n    try {\n      entry_fmd = gOFS->eosView->getFile(tag);\n      gOFS->eosView->unlinkFile(entry_fmd.get());\n    } catch (const MDException& e) {\n      if (e.getErrno() != ENOENT) {\n        eos_static_crit(\"failed to remove tag file='%s' error='%s'\",\n                        tag.c_str(), e.what());\n      }\n\n      return;\n    }\n\n    eos_static_info(\"op=removed tag='%s' uri='%s'\", tag.c_str(), uri.c_str());\n  }\n\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Validate a file\n//------------------------------------------------------------------------------\nvoid\nReplicationTracker::Validate(std::shared_ptr<eos::IFileMD> fmd)\n{\n}\n\n//------------------------------------------------------------------------------\n// Get Tag file prefix\n//------------------------------------------------------------------------------\nstd::string\nReplicationTracker::Prefix(std::shared_ptr<eos::IFileMD> fmd)\n{\n  char strackerfile[4096];\n  eos::IFileMD::ctime_t ctime;\n  fmd->getCTime(ctime);\n  time_t now = ctime.tv_sec;\n  struct tm nowtm;\n  localtime_r(&now, &nowtm);\n  snprintf(strackerfile, sizeof(strackerfile), \"%s/%04u/%02u/%02u/\",\n           mPath.c_str(), 1900 + nowtm.tm_year, nowtm.tm_mon + 1,\n           nowtm.tm_mday);\n  return strackerfile;\n}\n\n//------------------------------------------------------------------------------\n// Retrieve current LRU configuration options\n//------------------------------------------------------------------------------\nReplicationTracker::Options\nReplicationTracker::getOptions()\n{\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n  ReplicationTracker::Options opts;\n  // Default options\n  opts.enabled = false;\n  opts.interval = std::chrono::minutes(60);\n\n  if (FsView::gFsView.mSpaceView.count(\"default\") &&\n      (FsView::gFsView.mSpaceView[\"default\"]->GetConfigMember(\"tracker\") ==\n       \"on\")) {\n    opts.enabled = true;\n  }\n\n  if (opts.enabled) {\n    enable();\n    eos_static_debug(\"creation tracker is enabled\");\n  } else {\n    disable();\n  }\n\n  // this is hardcoded to 2 days, it could be 'dangerous' to make this\n  // really configurable\n  opts.atomic_cleanup_age = 2 * 86400;\n  return opts;\n}\n\n//------------------------------------------------------------------------------\n// Background Thread cleaning up left-over atomic uploads\n//------------------------------------------------------------------------------\nvoid\nReplicationTracker::backgroundThread(ThreadAssistant& assistant) noexcept\n{\n  ThreadAssistant::setSelfThreadName(REPTRACKER_THREAD_NAME);\n  gOFS->WaitUntilNamespaceIsBooted(assistant);\n  // set the initial state after boot\n  Options opts = getOptions();\n\n  if (opts.enabled) {\n    enable();\n  } else {\n    disable();\n  }\n\n  assistant.wait_for(std::chrono::seconds(10));\n  eos_static_info(\"%s\", \"msg=\\\"async thread started\\\"\");\n\n  while (!assistant.terminationRequested()) {\n    // every now and then we wake up\n    Options opts = getOptions();\n\n    // Only a master needs to run a ReplicationTracker\n    if (opts.enabled) {\n      enable();\n    } else {\n      disable();\n    }\n\n    common::IntervalStopwatch stopwatch(enabled() ? opts.interval\n                                        : std::chrono::seconds(10));\n\n    if (gOFS->mMaster->IsMaster()) {\n      eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n      auto it = FsView::gFsView.mSpaceView.find(\"default\");\n\n      if (it != FsView::gFsView.mSpaceView.end()) {\n        if (it->second->GetConfigMember(\"policy.conversion\") == \"on\") {\n          if (!conversion_enabled()) {\n            conversion_enable();\n            eos_static_info(\"enabling space conversion hooks\");\n          }\n        } else {\n          if (conversion_enabled()) {\n            conversion_disable();\n            eos_static_info(\"disabling space conversion hooks\");\n          }\n        }\n      }\n    }\n\n    if (opts.enabled && gOFS->mMaster->IsMaster()) {\n      eos_static_info(\"msg=\\\"scan started!\\\"\");\n      Scan(opts.atomic_cleanup_age, true, 0);\n      eos_static_info(\"msg=\\\"scan finished!\\\"\");\n    }\n\n    assistant.wait_for(stopwatch.timeRemainingInCycle());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Scan entries in creation tracker - opt cleanup or output\n//------------------------------------------------------------------------------\nvoid\nReplicationTracker::Scan(uint64_t atomic_age, bool cleanup, std::string* out)\n{\n  eos::common::RWMutexReadLock viewReadLock;\n  time_t now = time(NULL);\n  std::map<std::string, std::set<std::string>> found;\n  XrdOucString stdErr;\n\n  if (!enabled()) {\n    *out += \"# tracker is disabled - use 'eos space config default \"\n            \"space.tracker=on'\\n\";\n  }\n\n  if (!gOFS->_find(mPath.c_str(), mError, stdErr, mVid, found, 0, 0, false,\n                   10)) {\n    for (auto rfoundit = found.rbegin(); rfoundit != found.rend(); rfoundit++) {\n      if (!rfoundit->second.size()) {\n        std::string creationpath = mPath + \"/\";\n\n        if (rfoundit->first == creationpath) {\n          // don't delete the creation proc entry\n          continue;\n        }\n\n        std::shared_ptr<eos::IContainerMD> dmd;\n        eos::IContainerMD::ctime_t ctime;\n        // delete this directory if it is older than atomic_age\n        eos::common::RWMutexWriteLock viewWriteLock(gOFS->eosViewRWMutex);\n\n        try {\n          dmd = gOFS->eosView->getContainer(rfoundit->first);\n          dmd->getCTime(ctime);\n          uint64_t age = now - ctime.tv_sec;\n\n          if (age > atomic_age && !dmd->getNumFiles() &&\n              !dmd->getNumContainers()) {\n            gOFS->eosView->removeContainer(rfoundit->first);\n          }\n        } catch (const MDException& e) {\n          eos_static_crit(\"failed to remove directory='%s'\",\n                          rfoundit->first.c_str());\n        }\n\n        viewWriteLock.Release();\n      } else {\n        for (auto fileit = rfoundit->second.begin();\n             fileit != rfoundit->second.end(); fileit++) {\n          std::string fspath = rfoundit->first;\n          std::string entry = *fileit;\n          std::string entry_path = fspath + \"/\" + entry;\n          XrdOucString fxid = \"fxid:\";\n          std::string fullpath;\n          bool flag_deletion = false;\n          bool is_atomic = false;\n          std::string reason = \"KEEPIT\";\n          fxid += entry.c_str();\n          std::shared_ptr<eos::IFileMD> fmd;\n          std::shared_ptr<eos::IFileMD> entry_fmd;\n          size_t n_rep = 0;\n          size_t n_layout_rep = 0;\n          unsigned long long fid =\n            Resolver::retrieveFileIdentifier(fxid).getUnderlyingUInt64();\n          eos::IFileMD::ctime_t ctime;\n          // reference by fxid\n          eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, fid);\n          viewReadLock.Grab(gOFS->eosViewRWMutex);\n\n          try {\n            fmd = gOFS->eosFileService->getFileMD(fid);\n            fmd->getCTime(ctime);\n            fullpath = gOFS->eosView->getUri(fmd.get());\n\n            if (fmd->getName().substr(\n                  0, strlen(EOS_COMMON_PATH_ATOMIC_FILE_PREFIX)) ==\n                EOS_COMMON_PATH_ATOMIC_FILE_PREFIX) {\n              is_atomic = true;\n            }\n\n            n_rep = fmd->getNumLocation();\n            n_layout_rep =\n              (eos::common::LayoutId::GetStripeNumber(fmd->getLayoutId()) +\n               1);\n\n            if (n_rep < n_layout_rep) {\n              reason = \"REPLOW\";\n            } else {\n              reason = \"REP-OK\";\n              flag_deletion = true;\n            }\n          } catch (eos::MDException& e) {\n            errno = e.getErrno();\n            stdErr = \"error: cannot retrieve file meta data - \";\n            stdErr += e.getMessage().str().c_str();\n            eos_static_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                             e.getMessage().str().c_str());\n            reason = \"ENOENT\";\n            flag_deletion = true;\n            ctime.tv_sec = now - atomic_age - 1;\n          }\n\n          viewReadLock.Release();\n          uint64_t age = now - ctime.tv_sec;\n\n          if (is_atomic && (age > atomic_age)) {\n            flag_deletion = true;\n            reason = \"ATOMIC\";\n          }\n\n          if (out) {\n            if (reason == \"ENOENT\") {\n              // don't show files which had been deleted\n              continue;\n            }\n\n            char outline[16384];\n            snprintf(outline, sizeof(outline),\n                     \"key=%s age=%lu (s) delete=%d rep=%lu/%lu \"\n                     \"atomic=%d reason=%s uri='%s'\\n\",\n                     entry.c_str(), age, flag_deletion, n_rep, n_layout_rep,\n                     is_atomic, reason.c_str(), fullpath.c_str());\n            *out += outline;\n\n            if (out->size() > (128 * 1024 * 1024)) {\n              *out += \"# ... list has been truncated\\n\";\n              return;\n            }\n          } else {\n            if (reason == \"ENOENT\") {\n              // mark for tag deletion\n              flag_deletion = 1;\n            }\n\n            eos_static_info(\n              \"key=%s age=%lu (s) delete=%d rep=%lu/%lu atomic=%d \"\n              \"reason=%s uri='%s'\",\n              entry.c_str(), age, flag_deletion, n_rep, n_layout_rep,\n              is_atomic, reason.c_str(), fullpath.c_str());\n          }\n\n          if (cleanup && flag_deletion) {\n            eos::common::RWMutexWriteLock viewWriteLock(gOFS->eosViewRWMutex);\n\n            // cleanup the tag entry\n            try {\n              entry_fmd = gOFS->eosView->getFile(entry_path);\n              gOFS->eosView->unlinkFile(entry_fmd.get());\n            } catch (const MDException& e) {\n              eos_static_crit(\"failed to remove tag file='%s'\",\n                              entry_path.c_str());\n            }\n\n            if (reason == \"ATOMIC\") {\n              // cleanup the atomic left-over\n              try {\n                fmd = gOFS->eosFileService->getFileMD(fid);\n                gOFS->eosView->unlinkFile(fmd.get());\n              } catch (const MDException& e) {\n                eos_static_crit(\"failed to cleanup atomic target file='%s'\",\n                                fullpath.c_str());\n              }\n            }\n\n            viewWriteLock.Release();\n          }\n        }\n      }\n    }\n  } else {\n    eos_static_err(\"find failed in path='%s' errmsg='%s'\", mPath.c_str(),\n                   stdErr.c_str());\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/tracker/ReplicationTracker.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ReplicationTracker.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include \"common/AssistedThread.hh\"\n#include \"common/VirtualIdentity.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include <XrdOuc/XrdOucErrInfo.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <atomic>\n#include <memory>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class tracking the sanity of created files\n//------------------------------------------------------------------------------\n\nclass ReplicationTracker {\npublic:\n  // Define the enum class\n  enum class OperationMode { eInjection, eCreation, eAccess };\n\n  struct Options {\n    bool enabled;                //< Is CreationTracking even enabled?\n    uint64_t atomic_cleanup_age; //< Age when atomic files will be auto-cleaned\n    std::chrono::seconds\n        interval; //< Run CreationTracking cleanup every this many seconds\n  };\n\n  ReplicationTracker(const char* path);\n  virtual ~ReplicationTracker();\n\n  void Create(std::shared_ptr<eos::IFileMD> fmd);\n  void Access(std::shared_ptr<eos::IFileMD> fmd);\n  std::string ConversionPolicy(OperationMode mode, int fsid);\n  std::string ConversionSizePolicy(OperationMode mode, int fsid);\n  void Commit(std::shared_ptr<eos::IFileMD> fmd);\n  void Validate(std::shared_ptr<eos::IFileMD> fmd);\n\n  void Scan(uint64_t atomic_age, bool cleanup, std::string* out = 0);\n\n  std::string Prefix(std::shared_ptr<eos::IFileMD> fmd);\n\n  static ReplicationTracker*\n  Create(const char* path)\n  {\n    return new ReplicationTracker(path);\n  }\n\n  bool\n  enabled()\n  {\n    return (mEnabled.load()) ? true : false;\n  }\n  bool\n  disable()\n  {\n    if (!enabled()) {\n      return false;\n    } else {\n      mEnabled.store(0, std::memory_order_seq_cst);\n      return true;\n    }\n  }\n  bool\n  enable()\n  {\n    if (enabled()) {\n      return false;\n    } else {\n      mEnabled.store(1, std::memory_order_seq_cst);\n      return true;\n    }\n  }\n\n  bool\n  conversion_enabled()\n  {\n    return (mConversionEnabled.load()) ? true : false;\n  }\n  bool\n  conversion_disable()\n  {\n    if (!conversion_enabled()) {\n      return false;\n    } else {\n      mConversionEnabled.store(0, std::memory_order_seq_cst);\n      return true;\n    }\n  }\n  bool\n  conversion_enable()\n  {\n    if (conversion_enabled()) {\n      return false;\n    } else {\n      mConversionEnabled.store(1, std::memory_order_seq_cst);\n      return true;\n    }\n  }\n\n  Options getOptions();\n\nprivate:\n  AssistedThread mThread; ///< thread id of the creation background tracker\n\n  void backgroundThread(ThreadAssistant& assistant) noexcept;\n\n  std::atomic<int> mEnabled;\n  std::atomic<int> mConversionEnabled;\n  XrdOucErrInfo mError;\n  eos::common::VirtualIdentity mVid;\n  std::string mPath;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/utils/AttrHelper.cc",
    "content": "#include \"mgm/utils/AttrHelper.hh\"\n#include \"mgm/misc/Constants.hh\"\n#include \"common/Logging.hh\"\n#include \"common/StringUtils.hh\"\n\nnamespace eos::mgm::attr\n{\n\nbool\ncheckDirOwner(const eos::IContainerMD::XAttrMap& attrmap, uid_t d_uid,\n              gid_t d_gid, eos::common::VirtualIdentity& vid,\n              bool& sticky_owner, const char* path)\n{\n  // -------------------------------------------------------------------------\n  // Check for sys.ownerauth entries, which let people operate as the owner of\n  // the directory\n  // -------------------------------------------------------------------------\n  sticky_owner = false;\n\n  if (auto kv = attrmap.find(SYS_OWNER_AUTH);\n      kv != attrmap.end()) {\n    if (kv->second == \"*\") {\n      sticky_owner = true;\n    } else {\n      std::string ownerkey = vid.prot.c_str();\n      ownerkey += \":\";\n\n      if (vid.prot == \"gsi\") {\n        ownerkey += vid.dn;\n      } else {\n        ownerkey += vid.uid_string;\n      }\n\n      if (kv->second.find(ownerkey) != std::string::npos) {\n        eos_static_info(\"msg=\\\"client authenticated as directory owner\\\" path=\\\"%s\\\"uid=\\\"%u=>%u\\\" gid=\\\"%u=>%u\\\"\",\n                        path, vid.uid, vid.gid, d_uid, d_gid);\n        // yes the client can operate as the owner, we rewrite the virtual\n        // identity to the directory uid/gid pair\n        vid.uid = d_uid;\n        vid.gid = d_gid;\n        return true;\n      }\n    }\n  }\n\n  return sticky_owner;\n}\n\nbool checkAtomicUpload(const eos::IContainerMD::XAttrMap& attrmap,\n                       const char* atomic_cgi)\n{\n  int isAtomic(0);\n\n  if (const auto& kv = attrmap.find(SYS_FORCED_ATOMIC);\n      kv != attrmap.end()) {\n    // This should return true for values > 0\n    eos::common::StringToNumeric(kv->second, isAtomic);\n  } else if (const auto& kv = attrmap.find(USER_FORCED_ATOMIC);\n             kv != attrmap.end()) {\n    eos::common::StringToNumeric(kv->second, isAtomic);\n  } else if (atomic_cgi) {\n    return true;\n  }\n\n  return isAtomic;\n}\n\nint getVersioning(const eos::IContainerMD::XAttrMap& attrmap,\n                  std::string_view versioning_cgi)\n{\n  int versioning {0};\n\n  if (versioning_cgi.length()) {\n    eos::common::StringToNumeric(versioning_cgi, versioning);\n    return versioning;\n  }\n\n  if (const auto& kv = attrmap.find(SYS_VERSIONING);\n      kv != attrmap.end()) {\n    eos::common::StringToNumeric(kv->second, versioning, (int)0);\n  } else if (const auto& kv = attrmap.find(USER_VERSIONING);\n             kv != attrmap.end()) {\n    eos::common::StringToNumeric(kv->second, versioning, (int)0);\n  }\n\n  return versioning;\n}\n\nbool getValue(const eos::IContainerMD::XAttrMap &attrmap,\n              const std::string &key, std::string &out)\n{\n  if (auto kv = attrmap.find(key);\n      kv != attrmap.end()) {\n    out = kv->second;\n    return true;\n  }\n  return false;\n}\n\n\n} // namespace eos::mgm::attr\n"
  },
  {
    "path": "mgm/utils/AttrHelper.hh",
    "content": "//------------------------------------------------------------------------------\n// File: AttrHelper.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include <type_traits>\n\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"common/VirtualIdentity.hh\"\n#include \"common/StringUtils.hh\"\n\nnamespace eos::mgm::attr\n{\n\n/*!\n * check and set directory owner based on \"sys.auth.owner\" param\n * @param attrmap map of xattrs\n * @param d_uid directory uid\n * @param d_gid directory gid\n * @param vid Vid of the user, may be modified\n * @param sticky_owner true if directory has \"sys.auth.owner=*\"\n * @param path path of directory, only used for logging\n * @return true if permissions, vid will be modified to directory uid/gid\n *         only for the non sticky owner case\n *\n * The current usages of sticky_owner are special in comparision to\n * sys.owner.auth, so we don't yet reset the vid in case of sticky_owner\n * if there is an auth.owner match\n */\nbool checkDirOwner(const eos::IContainerMD::XAttrMap& attrmap, uid_t d_uid,\n                   gid_t d_gid, eos::common::VirtualIdentity& vid,\n                   bool& sticky_owner, const char* path);\n/*!\n * Check for Atomic Uploads\n * Atomic uploads follow the evaluation order sys.attribute > user.attribute > cgi\n * The CGI is evaluated only if both sys and user attributes are not present\n * @param attrmap map of xattrs\n * @param atomic_cgi cgi from env\n * @return bool status of atomic upload\n */\nbool checkAtomicUpload(const eos::IContainerMD::XAttrMap& attrmap,\n                       const char* atomic_cgi = nullptr);\n\n/*!\n * Check for versioing attribute\n * Versioning follows the evaluation order cgi > sys.attribute > user.attribute\n * @param attrmap map of xattrs\n * @param versioning_cgi string_view of versioning cgi\n * @return versioning int\n */\nint getVersioning(const eos::IContainerMD::XAttrMap& attrmap,\n                  std::string_view versioning_cgi = {});\n\n/*!\n * Get string value from xattr map\n * @param attrmap map of xattrs\n * @param key key to search\n * @param out output string\n * @return true if key found and out populated\n */\nbool getValue(const eos::IContainerMD::XAttrMap& attrmap,\n              const std::string& key, std::string& out);\n\n/*!\n * Get numeric value from xattr map\n * @param attrmap map of xattrs\n * @param key key to search\n * @param out output numeric value\n * @return true if key found and out populated\n */\ntemplate <typename T>\nauto getValue(const eos::IContainerMD::XAttrMap&  attrmap,\n              const std::string& key, T& out)\n    -> std::enable_if_t<std::is_arithmetic_v<T>, bool>\n{\n    auto kv = attrmap.find(key);\n    if (kv != attrmap.end()) {\n        return common::StringToNumeric(kv->second, out);\n    }\n    return false;\n}\n\n}// eos::mgm::attr\n"
  },
  {
    "path": "mgm/utils/FileSystemRegistry.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FileSystemRegistry.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/utils/FileSystemRegistry.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Assert.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFileSystemRegistry::FileSystemRegistry()\n{\n}\n\n//------------------------------------------------------------------------------\n// Lookup a FileSystem object by ID - return nullptr if none exists.\n//------------------------------------------------------------------------------\nmgm::FileSystem*\nFileSystemRegistry::lookupByID(eos::common::FileSystem::fsid_t id) const\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  auto it = mById.find(id);\n\n  if (it == mById.end()) {\n    return nullptr;\n  }\n\n  return it->second;\n}\n\n//----------------------------------------------------------------------------\n//! Lookup a FileSystem space - return \"\" if it does not exist\n//----------------------------------------------------------------------------\nstd::string\nFileSystemRegistry::lookupSpaceByID(eos::common::FileSystem::fsid_t id) const\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  auto fs = lookupByID(id) ;\n  if (fs) {\n    return fs->getCoreParams().getSpace();\n  } else {\n    return \"\";\n  }\n}\n\n\n\n//------------------------------------------------------------------------------\n// Lookup a FileSystem object by queuepath - return nullptr if none exists\n//------------------------------------------------------------------------------\nmgm::FileSystem*\nFileSystemRegistry::lookupByQueuePath(const std::string& queuepath) const\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  auto it = mByQueuePath.find(queuepath);\n\n  if (it == mByQueuePath.end()) {\n    return nullptr;\n  }\n\n  return it->second;\n}\n\n//------------------------------------------------------------------------------\n// Lookup a FileSystem id by FileSystem pointer - return 0 if none exists\n//------------------------------------------------------------------------------\neos::common::FileSystem::fsid_t\nFileSystemRegistry::lookupByPtr(mgm::FileSystem* fs) const\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  auto it = mByFsPtr.find(fs);\n\n  if (it == mByFsPtr.end()) {\n    return 0;\n  }\n\n  return it->second.mId;\n}\n\n//----------------------------------------------------------------------------\n// Register new FileSystem with the given ID.\n//\n// Refuse if either the FileSystem pointer already exists, or another\n// FileSystem has the same ID.\n//----------------------------------------------------------------------------\nbool FileSystemRegistry::registerFileSystem(const common::FileSystemLocator&\n    locator,\n    common::FileSystem::fsid_t fsid, FileSystem* fs)\n{\n  eos::common::RWMutexWriteLock wr_lock(mMutex);\n\n  // fsid collision?\n  if (mById.count(fsid) > 0) {\n    eos_static_crit(\"Could not insert fsid=%llu to FileSystemRegistry - \"\n                    \"fsid already exists!\", fsid);\n    return false;\n  }\n\n  // fs pointer collision?\n  if (mByFsPtr.count(fs) > 0) {\n    eos_static_crit(\"Could not insert fsid=%llu to FileSystemRegistry - \"\n                    \"fs pointer %x already exists!\", fsid, fs);\n    return false;\n  }\n\n  // queuepath collision?\n  if (mByQueuePath.count(locator.getQueuePath()) > 0) {\n    eos_static_crit(\"Could not insert fsid=%llu to FileSystemRegistry - \"\n                    \"queuepath %s already exists!\", fsid,\n                    locator.getQueuePath().c_str());\n    return false;\n  }\n\n  // Attempting to insert fsid=0?\n  if (fsid == 0) {\n    eos_static_crit(\"Attempted to insert fsid=0 into FileSystemRegistry\");\n    return false;\n  }\n\n  // Attempting to insert fs=nullptr?\n  if (fs == nullptr) {\n    eos_static_crit(\"Attempted to insert fs=nullptr into FileSystemRegistry\");\n    return false;\n  }\n\n  // Attempting to insert queuepath=\"\"?\n  if (locator.getQueuePath().empty()) {\n    eos_static_crit(\"Attempted to insert queuepath=empty into FileSystemRegistry\");\n    return false;\n  }\n\n  // All good, insert.\n  mById.emplace(fsid, fs);\n  mByFsPtr.emplace(fs, IdAndQueuePath(fsid, locator.getQueuePath()));\n  mByQueuePath.emplace(locator.getQueuePath(), fs);\n  eos_assert(mById.size() == mByFsPtr.size());\n  eos_assert(mById.size() == mByQueuePath.size());\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Erase by fsid - return true if found and erased, false otherwise\n//------------------------------------------------------------------------------\nbool FileSystemRegistry::eraseById(eos::common::FileSystem::fsid_t id)\n{\n  eos::common::RWMutexWriteLock wr_locK(mMutex);\n  // id exists?\n  auto it = mById.find(id);\n\n  if (it == mById.end()) {\n    return false;\n  }\n\n  // Lookup corresponding mByFsPtr entry, which MUST exist\n  auto it2 = mByFsPtr.find(it->second);\n  eos_assert(it2 != mByFsPtr.end());\n  // Lookup corresponding mByQueuePath entry, which MUST exist\n  auto it3 = mByQueuePath.find(it2->second.mQueuePath);\n  eos_assert(it3 != mByQueuePath.end());\n  mById.erase(it);\n  mByFsPtr.erase(it2);\n  mByQueuePath.erase(it3);\n  eos_assert(mById.size() == mByFsPtr.size());\n  eos_assert(mById.size() == mByQueuePath.size());\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Erase by ptr - return true if found and erased, false otherwise\n//------------------------- ----------------------------------------------------\nbool FileSystemRegistry::eraseByPtr(mgm::FileSystem* fs)\n{\n  eos::common::RWMutexWriteLock wr_lock(mMutex);\n  // ptr exists?\n  auto it = mByFsPtr.find(fs);\n\n  if (it == mByFsPtr.end()) {\n    return false;\n  }\n\n  // Lookup corresponding mById entry, which MUST exist\n  auto it2 = mById.find(it->second.mId);\n  eos_assert(it2 != mById.end());\n  // Lookup corresponding mByQueuePath entry, which MUST exist\n  auto it3 = mByQueuePath.find(it->second.mQueuePath);\n  eos_assert(it3 != mByQueuePath.end());\n  mByFsPtr.erase(it);\n  mById.erase(it2);\n  mByQueuePath.erase(it3);\n  eos_assert(mById.size() == mByFsPtr.size());\n  eos_assert(mById.size() == mByQueuePath.size());\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Does a FileSystem with the given id exist?\n//------------------------------------------------------------------------------\nbool FileSystemRegistry::exists(eos::common::FileSystem::fsid_t id) const\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  return mById.count(id) > 0;\n}\n\n//------------------------------------------------------------------------------\n// Return number of registered filesystems\n//------------------------------------------------------------------------------\nsize_t FileSystemRegistry::size() const\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  eos_assert(mById.size() == mByFsPtr.size());\n  eos_assert(mById.size() == mByQueuePath.size());\n  return mById.size();\n}\n\n//------------------------------------------------------------------------------\n// Entirely clear registry contents\n//------------------------------------------------------------------------------\nvoid FileSystemRegistry::clear()\n{\n  eos::common::RWMutexWriteLock wr_lock(mMutex);\n  mById.clear();\n  mByFsPtr.clear();\n  mByQueuePath.clear();\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/utils/FileSystemRegistry.hh",
    "content": "//------------------------------------------------------------------------------\n// File: FileSystemRegistry.hh\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/filesystem/FileSystem.hh\"\n#include \"common/RWMutex.hh\"\n#include <map>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class to keep track of currently registered filesystems. For compatibility\n//! purposes with what existed before, initially this class will behave exactly\n//! like an id -> FileSystem* map.\n//!\n//! The API (together with users of this class) will be improved incrementally.\n//------------------------------------------------------------------------------\n\nclass FileSystemRegistry\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Types\n  //----------------------------------------------------------------------------\n  using const_iterator =\n    std::map<eos::common::FileSystem::fsid_t, mgm::FileSystem*>::const_iterator;\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  FileSystemRegistry();\n\n  //----------------------------------------------------------------------------\n  //! Map compatibility: begin()\n  //----------------------------------------------------------------------------\n  const_iterator begin() const\n  {\n    return mById.cbegin();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Map compatibility: cbegin()\n  //----------------------------------------------------------------------------\n  const_iterator cbegin() const\n  {\n    return mById.cbegin();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Map compatibility: end()\n  //----------------------------------------------------------------------------\n  const_iterator end() const\n  {\n    return mById.cend();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Map compatibility: cend()\n  //----------------------------------------------------------------------------\n  const_iterator cend() const\n  {\n    return mById.cend();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Lookup a FileSystem object by ID - return nullptr if none exists.\n  //----------------------------------------------------------------------------\n  mgm::FileSystem* lookupByID(eos::common::FileSystem::fsid_t id) const;\n\n  //----------------------------------------------------------------------------\n  //! Lookup a FileSystem space - return \"\" if it does not exist\n  //----------------------------------------------------------------------------\n  std::string lookupSpaceByID(eos::common::FileSystem::fsid_t id) const;\n\n  //----------------------------------------------------------------------------\n  //! Lookup a FileSystem id by FileSystem pointer - return 0 if none exists\n  //----------------------------------------------------------------------------\n  eos::common::FileSystem::fsid_t lookupByPtr(mgm::FileSystem* fs) const;\n\n  //----------------------------------------------------------------------------\n  //! Lookup a FileSystem object by queuepath - return nullptr if none exists\n  //----------------------------------------------------------------------------\n  mgm::FileSystem* lookupByQueuePath(const std::string& queuepath) const;\n\n  //----------------------------------------------------------------------------\n  //! Does a FileSystem with the given id exist?\n  //----------------------------------------------------------------------------\n  bool exists(eos::common::FileSystem::fsid_t id) const;\n\n  //----------------------------------------------------------------------------\n  //! Register new FileSystem with the given ID.\n  //!\n  //! Refuse if either the FileSystem pointer already exists, or another\n  //! FileSystem has the same ID.\n  //----------------------------------------------------------------------------\n  bool registerFileSystem(const common::FileSystemLocator& locator,\n                          common::FileSystem::fsid_t fsid, mgm::FileSystem* fs);\n\n  //----------------------------------------------------------------------------\n  //! Return number of registered filesystems\n  //----------------------------------------------------------------------------\n  size_t size() const;\n\n  //----------------------------------------------------------------------------\n  //! Erase by fsid - return true if found and erased, false otherwise\n  //----------------------------------------------------------------------------\n  bool eraseById(eos::common::FileSystem::fsid_t id);\n\n  //----------------------------------------------------------------------------\n  //! Erase by ptr - return true if found and erased, false otherwise\n  //------------------------- ---------------------------------------------------\n  bool eraseByPtr(mgm::FileSystem* fs);\n\n  //------------------------------------------------------------------------------\n  //! Entirely clear registry contents\n  //------------------------------------------------------------------------------\n  void clear();\n\nprivate:\n  struct IdAndQueuePath {\n    eos::common::FileSystem::fsid_t mId;\n    std::string mQueuePath;\n\n    IdAndQueuePath(eos::common::FileSystem::fsid_t id,\n                   const std::string& queuepath) : mId(id), mQueuePath(queuepath) {}\n  };\n\n  mutable eos::common::RWMutex mMutex;\n  std::map<eos::common::FileSystem::fsid_t, mgm::FileSystem*> mById;\n  std::map<mgm::FileSystem*, IdAndQueuePath> mByFsPtr;\n  std::map<std::string, mgm::FileSystem*> mByQueuePath;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/utils/FileSystemStatusUtils.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FileSystemStatusUtils.cc\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"FileSystemStatusUtils.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"common/Logging.hh\"\n\nnamespace eos::mgm::fsutils\n{\n\nvoid ApplyDrainedStatus(eos::common::FileSystem::fsid_t fsid)\n{\n  eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n  auto fs = FsView::gFsView.mIdView.lookupByID(fsid);\n  eos_static_notice(\"msg=\\\"Drain complete\\\" fsid=%d\", fsid);\n\n  if (fs) {\n    auto status = eos::common::DrainStatus::kDrained;\n    eos::common::FileSystemUpdateBatch batch;\n    batch.setDrainStatusLocal(status);\n    batch.setLongLongLocal(\"local.drain.bytesleft\", 0);\n    batch.setLongLongLocal(\"local.drain.timeleft\", 0);\n    batch.setLongLongLocal(\"local.drain.failed\", 0);\n    batch.setLongLongLocal(\"local.drain.files\", 0);\n\n    if (!gOFS->Shutdown) {\n      // If drain done and the system is not shutting down then set the\n      // file system to \"empty\" state\n      batch.setLongLongLocal(\"local.drain.progress\", 100);\n      batch.setLongLongLocal(\"local.drain.failed\", 0);\n      batch.setStringDurable(\"configstatus\", \"empty\");\n      FsView::gFsView.StoreFsConfig(fs);\n    }\n\n    fs->applyBatch(batch);\n  }\n}\n\nvoid\nApplyFailedDrainStatus(eos::common::FileSystem::fsid_t fsid,\n                       uint64_t numFailedJobs)\n{\n  eos_static_notice(\"msg=\\\"failed drain\\\" fsid=%d\", fsid);\n  eos::common::RWMutexReadLock fs_rd_lock(FsView::gFsView.ViewMutex);\n  auto fs = FsView::gFsView.mIdView.lookupByID(fsid);\n\n  if (fs) {\n    auto drain_status = eos::common::DrainStatus::kDrainFailed;\n    eos::common::FileSystemUpdateBatch batch;\n    batch.setDrainStatusLocal(drain_status);\n    batch.setLongLongLocal(\"local.drain.timeleft\", 0);\n    batch.setLongLongLocal(\"local.drain.progress\", 100);\n    batch.setLongLongLocal(\"local.drain.failed\", numFailedJobs);\n    fs->applyBatch(batch);\n  }\n}\n\nstd::vector<eos::common::FileSystem::fsid_t>\nFsidsinGroup(const std::string& groupname,\n             eos::common::ActiveStatus active_status,\n             eos::common::DrainStatus drain_status)\n{\n  std::vector<eos::common::FileSystem::fsid_t> result;\n  eos::common::RWMutexReadLock rlock(FsView::gFsView.ViewMutex);\n  auto group_it = FsView::gFsView.mGroupView.find(groupname);\n\n  if (group_it == FsView::gFsView.mGroupView.end()) {\n    eos_static_err(\"msg=\\\"group not found: \\\" %s\", groupname.c_str());\n    return {};\n  }\n\n  for (auto fs_it = group_it->second->begin();\n       fs_it != group_it->second->end();\n       ++fs_it) {\n    auto target = FsView::gFsView.mIdView.lookupByID(*fs_it);\n\n    if (target &&\n        target->GetActiveStatus() == eos::common::ActiveStatus::kOnline &&\n        target->GetDrainStatus() == eos::common::DrainStatus::kNoDrain) {\n      result.emplace_back(*fs_it);\n    }\n  }\n\n  return result;\n}\n\nstd::map<eos::common::FileSystem::fsid_t, FsidStatus>\nGetGroupFsStatus(const std::string& groupname)\n{\n  eos::common::RWMutexReadLock rlock(FsView::gFsView.ViewMutex);\n  auto group_it = FsView::gFsView.mGroupView.find(groupname);\n\n  if (group_it == FsView::gFsView.mGroupView.end()) {\n    eos_static_err(\"msg=\\\"group not found: \\\" %s\", groupname.c_str());\n    return {};\n  }\n\n  fs_status_map_t result;\n\n  for (auto fs_it = group_it->second->begin();\n       fs_it != group_it->second->end();\n       ++fs_it) {\n    auto target = FsView::gFsView.mIdView.lookupByID(*fs_it);\n\n    if (target) {\n      result.emplace(*fs_it, FsidStatus{target->GetActiveStatus(),\n                                        target->GetDrainStatus()});\n    }\n  }\n\n  return result;\n}\n\n} // eos::mgm::fsutils\n"
  },
  {
    "path": "mgm/utils/FileSystemStatusUtils.hh",
    "content": "//------------------------------------------------------------------------------\n// File: FileSystemStatusUtils.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include \"common/FileSystem.hh\"\n#include <vector>\n#include <map>\n\nnamespace eos::mgm::fsutils\n{\n\n/*!\n * Set the filesystem as drained, and if we're not shutting down, to empty as well\n * @param fsid the FSID to set the drained status to\n */\nvoid ApplyDrainedStatus(eos::common::FileSystem::fsid_t fsid);\n\n/*!\n * Set the filesystem to  drain fail\n * @param fsid the FSID for setting failed drain\n * @param numFailedJobs number of failed file entries\n */\nvoid ApplyFailedDrainStatus(eos::common::FileSystem::fsid_t fsid,\n                            uint64_t numFailedJobs);\n\n/*!\n * Get a list of Filesystems with the supplied active/drain status\n * @param groupname\n * @param active_status defaults to Online\n * @param drain_status defaults to No Drain\n * @return a vector a filesystem IDs\n */\nstd::vector<eos::common::FileSystem::fsid_t>\nFsidsinGroup(const std::string& groupname,\n             eos::common::ActiveStatus active_status = eos::common::ActiveStatus::kOnline,\n             eos::common::DrainStatus drain_status = eos::common::DrainStatus::kNoDrain);\n\nstruct FsidStatus {\n  eos::common::ActiveStatus active_status;\n  eos::common::DrainStatus drain_status;\n  explicit FsidStatus(eos::common::ActiveStatus _active_status,\n                      eos::common::DrainStatus _drain_status) : active_status(_active_status),\n    drain_status(_drain_status) {}\n};\n\nusing fs_status_map_t = std::map<eos::common::FileSystem::fsid_t, FsidStatus>;\n\nfs_status_map_t\nGetGroupFsStatus(const std::string& groupname);\n\n} // eos::mgm::fsutils\n"
  },
  {
    "path": "mgm/utils/FilesystemUuidMapper.cc",
    "content": "// ----------------------------------------------------------------------\n// File: FilesystemUuidMapper.cc\n// Author: Georgios Bitzes - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/utils/FilesystemUuidMapper.hh\"\n#include \"common/Assert.hh\"\n#include \"common/Logging.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFilesystemUuidMapper::FilesystemUuidMapper() {}\n\n//------------------------------------------------------------------------------\n// Inject mapping. If the given id and / or uuid are already occupied, we\n// refuse and return false.\n//\n// Otherwise, we return true.\n//------------------------------------------------------------------------------\nbool FilesystemUuidMapper::injectMapping(eos::common::FileSystem::fsid_t id,\n    const std::string& uuid)\n{\n  // Valid id?\n  if (id <= 0) {\n    return false;\n  }\n\n  // Valid uuid?\n  if (uuid.empty()) {\n    return false;\n  }\n\n  eos::common::RWMutexWriteLock wr_lock(mMutex);\n  // Do we have an entry with the given uuid already?\n  auto it1 = uuid2fs.find(uuid);\n\n  if (it1 != uuid2fs.end()) {\n    if (it1->second != id) {\n      // We already have an entry for the given uuid, and it's assigned to\n      // a different id.. reject.\n      return false;\n    }\n  }\n\n  // Do we have an entry with the given fsid already?\n  auto it2 = fs2uuid.find(id);\n\n  if (it2 != fs2uuid.end()) {\n    if (it2->second != uuid) {\n      // We already have an entry for the given fsid, and it's assigned to\n      // a different uuid.. reject.\n      return false;\n    }\n  }\n\n  // No conflicts, insert.\n  uuid2fs[uuid] = id;\n  fs2uuid[id] = uuid;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Retrieve size of the map\n//------------------------------------------------------------------------------\nsize_t FilesystemUuidMapper::size() const\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  eos_assert(uuid2fs.size() == fs2uuid.size());\n  return uuid2fs.size();\n}\n\n//------------------------------------------------------------------------------\n// Clear contents\n//------------------------------------------------------------------------------\nvoid FilesystemUuidMapper::clear()\n{\n  eos::common::RWMutexWriteLock wr_lock(mMutex);\n  uuid2fs.clear();\n  fs2uuid.clear();\n}\n\n//------------------------------------------------------------------------------\n// Is there any entry with the given fsid?\n//------------------------------------------------------------------------------\nbool FilesystemUuidMapper::hasFsid(eos::common::FileSystem::fsid_t id) const\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  return fs2uuid.find(id) != fs2uuid.end();\n}\n\n//------------------------------------------------------------------------------\n// Is there any entry with the given uuid?\n//------------------------------------------------------------------------------\nbool FilesystemUuidMapper::hasUuid(const std::string& uuid) const\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  return uuid2fs.find(uuid) != uuid2fs.end();\n}\n\n//------------------------------------------------------------------------------\n// Retrieve the fsid that corresponds to the given uuid. Return 0 if none\n// exists.\n//------------------------------------------------------------------------------\neos::common::FileSystem::fsid_t\nFilesystemUuidMapper::lookup(const std::string& uuid) const\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  auto it = uuid2fs.find(uuid);\n\n  if (it == uuid2fs.end()) {\n    return 0;\n  }\n\n  return it->second;\n}\n\n//------------------------------------------------------------------------------\n//! Retrieve the uuid that corresponds to the given fsid. Return \"\" if none\n//! exists.\n//------------------------------------------------------------------------------\nstd::string\nFilesystemUuidMapper::lookup(eos::common::FileSystem::fsid_t id) const\n{\n  eos::common::RWMutexReadLock rd_lock(mMutex);\n  auto it = fs2uuid.find(id);\n\n  if (it == fs2uuid.end()) {\n    return \"\";\n  }\n\n  return it->second;\n}\n\n//------------------------------------------------------------------------------\n//! Remove a mapping, given the fsid. Returns true if the element was found\n//! and removed, and false if not found.\n//------------------------------------------------------------------------------\nbool FilesystemUuidMapper::remove(eos::common::FileSystem::fsid_t id)\n{\n  eos::common::RWMutexWriteLock wr_lock(mMutex);\n  auto it = fs2uuid.find(id);\n\n  if (it == fs2uuid.end()) {\n    return false;\n  }\n\n  // Find the reverse relationship, which _must_ exist\n  auto it2 = uuid2fs.find(it->second);\n  eos_assert(it2 != uuid2fs.end());\n  // Drop both\n  fs2uuid.erase(it);\n  uuid2fs.erase(it2);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n//! Remove a mapping, given the uuid. Returns true if the element was found\n//! and removed, and false if not found.\n//------------------------------------------------------------------------------\nbool FilesystemUuidMapper::remove(const std::string& uuid)\n{\n  eos::common::RWMutexWriteLock wr_lock(mMutex);\n  auto it = uuid2fs.find(uuid);\n\n  if (it == uuid2fs.end()) {\n    return false;\n  }\n\n  // Find the reverse relationship, which _must_ exist\n  auto it2 = fs2uuid.find(it->second);\n  eos_assert(it2 != fs2uuid.end());\n  // Drop both\n  uuid2fs.erase(it);\n  fs2uuid.erase(it2);\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Allocate a new fsid for the given uuid.\n// - If the given uuid is registered already, simply map to the existing\n//   one, don't modify anything.\n// - If not, allocate a brand new, currently-unused fsid.\n// - This map cannot hold more than 64k filesystems - legacy limitation from\n//   original implementation in FsView, not sure if we can remove it.\n//------------------------------------------------------------------------------\neos::common::FileSystem::fsid_t\nFilesystemUuidMapper::allocate(const std::string& uuid)\n{\n  eos::common::RWMutexWriteLock wr_lock(mMutex);\n  // Given uuid exists already?\n  auto it = uuid2fs.find(uuid);\n\n  if (it != uuid2fs.end()) {\n    return it->second; // nothing more to do\n  }\n\n  // Does not exist, need to allocate..\n  if (uuid2fs.empty()) {\n    // Entire structure is empty, start from 1.\n    eos::common::FileSystem::fsid_t id = 1;\n    uuid2fs[uuid] = id;\n    fs2uuid[id] = uuid;\n    return id;\n  }\n\n  // Find largest fsid currently in use\n  eos::common::FileSystem::fsid_t maxInUse = fs2uuid.rbegin()->first;\n\n  if (maxInUse < 64000) {\n    // Allocate maxInUse+1\n    eos::common::FileSystem::fsid_t id = maxInUse + 1;\n    uuid2fs[uuid] = id;\n    fs2uuid[id] = uuid;\n    return id;\n  }\n\n  // We don't allow values larger than 64k.. linear search from 1 to 64k\n  // to find an open spot.\n  for (eos::common::FileSystem::fsid_t id = 1; id < 64000; id++) {\n    if (fs2uuid.count(id) == 0) {\n      // Found an empty spot\n      uuid2fs[uuid] = id;\n      fs2uuid[id] = uuid;\n      return id;\n    }\n  }\n\n  // Unable to allocate, something is wrong, abort\n  eos_static_crit(\"all filesystem id's exhausted (64.000) - aborting the program\");\n  exit(-1);\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/utils/FilesystemUuidMapper.hh",
    "content": "//------------------------------------------------------------------------------\n// File: FilesystemUuidMapper.hh\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_MGM_UTILS_FILESYSTEM_UUID_MAPPER_HH\n#define EOS_MGM_UTILS_FILESYSTEM_UUID_MAPPER_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"common/FileSystem.hh\"\n#include \"common/RWMutex.hh\"\n\n//------------------------------------------------------------------------------\n//! @file  FilesystemUuidMapper.hh\n//! @brief Utility class for uuid <-> fs id mapping of filesystems,\n//!        and vice-versa\n//------------------------------------------------------------------------------\nEOSMGMNAMESPACE_BEGIN\n\nclass FilesystemUuidMapper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  FilesystemUuidMapper();\n\n  //----------------------------------------------------------------------------\n  //! Inject mapping. If the given id and / or uuid are already occupied, we\n  //! refuse and return false.\n  //!\n  //! Otherwise, we return true.\n  //----------------------------------------------------------------------------\n  bool injectMapping(eos::common::FileSystem::fsid_t id,\n                     const std::string& uuid);\n\n  //----------------------------------------------------------------------------\n  //! Is there any entry with the given fsid?\n  //----------------------------------------------------------------------------\n  bool hasFsid(eos::common::FileSystem::fsid_t id) const;\n\n  //----------------------------------------------------------------------------\n  //! Is there any entry with the given uuid?\n  //----------------------------------------------------------------------------\n  bool hasUuid(const std::string& uuid) const;\n\n  //----------------------------------------------------------------------------\n  //! Retrieve size of the map\n  //----------------------------------------------------------------------------\n  size_t size() const;\n\n  //----------------------------------------------------------------------------\n  //! Retrieve the fsid that corresponds to the given uuid. Return 0 if none\n  //! exists.\n  //----------------------------------------------------------------------------\n  eos::common::FileSystem::fsid_t lookup(const std::string& uuid) const;\n\n  //----------------------------------------------------------------------------\n  //! Retrieve the uuid that corresponds to the given fsid. Return \"\" if none\n  //! exists.\n  //----------------------------------------------------------------------------\n  std::string lookup(eos::common::FileSystem::fsid_t id) const;\n\n  //----------------------------------------------------------------------------\n  //! Remove a mapping, given the fsid. Returns true if the element was found\n  //! and removed, and false if not found.\n  //----------------------------------------------------------------------------\n  bool remove(eos::common::FileSystem::fsid_t id);\n\n  //----------------------------------------------------------------------------\n  //! Remove a mapping, given the uuid. Returns true if the element was found\n  //! and removed, and false if not found.\n  //----------------------------------------------------------------------------\n  bool remove(const std::string& uuid);\n\n  //----------------------------------------------------------------------------\n  //! Clear contents\n  //----------------------------------------------------------------------------\n  void clear();\n\n  //----------------------------------------------------------------------------\n  //! Allocate a new fsid for the given uuid.\n  //! - If the given uuid is registered already, simply map to the existing\n  //!   one, don't modify anything.\n  //! - If not, allocate a brand new, currently-unused fsid.\n  //! - This map cannot hold more than 64k filesystems - legacy limitation from\n  //!   original implementation in FsView, not sure if we can remove it.\n  //----------------------------------------------------------------------------\n  eos::common::FileSystem::fsid_t allocate(const std::string& uuid);\n\nprivate:\n  mutable eos::common::RWMutex mMutex;\n  //! Map translating a file system ID to a unique ID\n  std::map<eos::common::FileSystem::fsid_t, std::string> fs2uuid;\n  //! Map translating a unique ID to a filesystem ID\n  std::map<std::string, eos::common::FileSystem::fsid_t> uuid2fs;\n};\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/vid/Vid.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Vid.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/Mapping.hh\"\n#include \"mgm/vid/Vid.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/config/IConfigEngine.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Set vid values\n//------------------------------------------------------------------------------\nbool\nVid::Set(const char* value, bool storeConfig)\n{\n  eos::common::RWMutexWriteLock lock(eos::common::Mapping::gMapMutex);\n  XrdOucEnv env(value);\n  XrdOucString skey = env.Get(\"mgm.vid.key\");\n  XrdOucString svalue = value;\n  XrdOucString vidcmd = env.Get(\"mgm.vid.cmd\");\n  const char* val = 0;\n\n  if (!skey.length()) {\n    return false;\n  }\n\n  bool set = false;\n\n  if (!value) {\n    return false;\n  }\n\n  if (vidcmd == \"publicaccesslevel\") {\n    if (storeConfig) {\n      gOFS->mConfigEngine->SetConfigValue(\"vid\", skey.c_str(), value);\n    }\n\n    if ((val = env.Get(\"mgm.vid.level\"))) {\n      eos::common::Mapping::gNobodyAccessTreeDeepness = (int) strtol(val, 0, 10);\n      set = true;\n    }\n  }\n\n  if (vidcmd == \"tokensudo\") {\n    if (storeConfig) {\n      gOFS->mConfigEngine->SetConfigValue(\"vid\", skey.c_str(), value);\n    }\n\n    if ((val = env.Get(\"mgm.vid.tokensudo\"))) {\n      if (std::string(val) == \"always\") {\n        val = \"0\";\n        set = true;\n      } else if (std::string(val) == \"never\") {\n        val = \"3\";\n        set = true;\n      } else if (std::string(val) == \"strong\") {\n        val = \"2\";\n        set = true;\n      } else if (std::string(val) == \"encrypted\") {\n        val = \"1\";\n        set = true;\n      } else if ((std::string(val) == \"0\") ||\n                 (std::string(val) == \"1\") ||\n                 (std::string(val) == \"2\") ||\n                 (std::string(val) == \"3\")) {\n        set = true;\n      }\n\n      if (set) {\n        eos::common::Mapping::gTokenSudo = (int) strtol(val, 0, 10);\n      }\n    }\n  }\n\n  if (vidcmd == \"geotag\") {\n    if ((val = env.Get(\"mgm.vid.geotag\"))) {\n      XrdOucString gkey = skey;\n      gkey.erase(\"geotag:\");\n      eos::common::Mapping::gGeoMap[gkey.c_str()] = val;\n\n      if (storeConfig) {\n        gOFS->mConfigEngine->SetConfigValue(\"vid\", skey.c_str(), value);\n      }\n\n      set = true;\n    }\n  }\n\n  if (vidcmd == \"membership\") {\n    uid_t uid = eos::common::VirtualIdentity::kNobodyUid;\n\n    if (env.Get(\"mgm.vid.source.uid\")) {\n      // rule for a certain user id\n      int errc = 0;\n      std::string username = env.Get(\"mgm.vid.source.uid\");\n\n      if (std::find_if(username.begin(), username.end(),\n      [](unsigned char c) {\n      return std::isalpha(c);\n      }) !=\n      username.end()) {\n        uid = eos::common::Mapping::UserNameToUid(username, errc);\n      } else {\n        try {\n          uid = std::stoul(username);\n        } catch (const std::exception& e) {\n          uid = eos::common::VirtualIdentity::kNobodyUid;\n        }\n      }\n\n      XrdOucString suid;\n      suid += (int) uid;\n      skey.replace(username.c_str(), suid);\n\n      if (errc) {\n        eos_static_err(\"msg=\\\"failed username translation\\\" user=%s\", username.c_str());\n      }\n    }\n\n    const char* val = 0;\n\n    if ((val = env.Get(\"mgm.vid.target.uid\"))) {\n      // fill uid target list\n      eos::common::Mapping::gUserRoleVector[uid].clear();\n      eos::common::Mapping::CommaListToUidSet(val,\n                                              eos::common::Mapping::gUserRoleVector[uid]);\n\n      if (storeConfig) {\n        gOFS->mConfigEngine->SetConfigValue(\"vid\", skey.c_str(), svalue.c_str());\n      }\n\n      set = true;\n    }\n\n    if ((val = env.Get(\"mgm.vid.target.gid\"))) {\n      // fill gid target list\n      eos::common::Mapping::gGroupRoleVector[uid].clear();\n      eos::common::Mapping::CommaListToGidSet(val,\n                                              eos::common::Mapping::gGroupRoleVector[uid]);\n\n      if (storeConfig) {\n        gOFS->mConfigEngine->SetConfigValue(\"vid\", skey.c_str(), svalue.c_str());\n      }\n\n      set = true;\n    }\n\n    if ((val = env.Get(\"mgm.vid.target.sudo\"))) {\n      // fill sudoer list\n      XrdOucString setting = val;\n\n      if (setting == \"true\") {\n        eos::common::Mapping::gSudoerMap[uid] = 1;\n\n        if (storeConfig) {\n          gOFS->mConfigEngine->SetConfigValue(\"vid\", skey.c_str(), value);\n        }\n\n        return true;\n      } else {\n        // this in fact is deletion of the right\n        eos::common::Mapping::gSudoerMap.erase(uid);\n\n        if (storeConfig) {\n          gOFS->mConfigEngine->DeleteConfigValue(\"vid\", skey.c_str());\n        }\n\n        return true;\n      }\n    }\n  }\n\n  if (vidcmd == \"map\") {\n    XrdOucString auth = env.Get(\"mgm.vid.auth\");\n\n    if ((auth != \"voms\") && (auth != \"krb5\") && (auth != \"sss\") &&\n        (auth != \"unix\") && (auth != \"tident\") && (auth != \"gsi\") &&\n        (auth != \"https\") && (auth != \"grpc\") && (auth != \"oauth2\") &&\n        (auth != \"ztn\")) {\n      eos_static_err(\"%s\", \"msg=\\\"invalid auth mode\\\"\");\n      return false;\n    }\n\n    XrdOucString pattern = env.Get(\"mgm.vid.pattern\");\n\n    if (!pattern.length()) {\n      eos_static_err(\"%s\", \"msg=\\\"missing pattern\\\"\");\n      return false;\n    }\n\n    if (!pattern.beginswith(\"\\\"\")) {\n      pattern.insert(\"\\\"\", 0);\n    }\n\n    if (!pattern.endswith(\"\\\"\")) {\n      pattern += \"\\\"\";\n    }\n\n    skey = auth;\n    skey += \":\";\n    skey += pattern;\n\n    if ((!env.Get(\"mgm.vid.uid\")) && (!env.Get(\"mgm.vid.gid\"))) {\n      eos_static_err(\"%s\", \"msg=\\\"missing uid|gid\\\"\");\n      return false;\n    }\n\n    XrdOucString newuid = env.Get(\"mgm.vid.uid\");\n    XrdOucString newgid = env.Get(\"mgm.vid.gid\");\n\n    // check for valid arguments first before setting any config\n    if (newuid.length()) {\n      uid_t muid = (uid_t) atoi(newuid.c_str());\n      XrdOucString cx = \"\";\n      cx += (int) muid;\n\n      if (cx != newuid) {\n        eos_static_err(\"msg=\\\"strings differ\\\" old=\\\"%s\\\" new=\\\"%s\\\"\",\n                       cx.c_str(), newuid.c_str());\n        return false;\n      }\n    }\n\n    if (newgid.length()) {\n      gid_t mgid = (gid_t) atoi(newgid.c_str());\n      XrdOucString cx = \"\";\n      cx += (int) mgid;\n\n      if (cx != newgid) {\n        eos_static_err(\"strings differ %s %s\", cx.c_str(), newgid.c_str());\n        return false;\n      }\n    }\n\n    if (newuid.length()) {\n      uid_t muid = (uid_t) atoi(newuid.c_str());\n      XrdOucString cx = \"\";\n      cx += (int) muid;\n\n      if (cx != newuid) {\n        eos_static_err(\"strings differ %s %s\", cx.c_str(), newuid.c_str());\n        return false;\n      }\n\n      skey += \":\";\n      skey += \"uid\";\n      eos::common::Mapping::gVirtualUidMap[skey.c_str()] = muid;\n\n      // extract a hostname pattern and add it to the to allowed tident match set\n      if (auth == \"tident\" && (pattern.find(\"*\", pattern.find(\"@\")) != STR_NPOS)) {\n        XrdOucString hostmatch = pattern.c_str();\n        XrdOucString protocol = pattern.c_str();\n\n        while (hostmatch.replace(\"\\\"\", \"\")) {}\n\n        while (protocol.replace(\"\\\"\", \"\")) {}\n\n        hostmatch.erase(0, hostmatch.find(\"@\") + 1);\n        protocol.erase(protocol.find(\"@\"));\n        eos::common::Mapping::gAllowedTidentMatches.insert(std::make_pair(std::string(\n              protocol.c_str()), std::string(hostmatch.c_str())));\n      }\n\n      set = true;\n      // no '&' are allowed here\n      XrdOucString svalue = value;\n\n      while (svalue.replace(\"&\", \" \")) {\n      };\n\n      if (storeConfig) {\n        gOFS->mConfigEngine->SetConfigValue(\"vid\", skey.c_str(), svalue.c_str());\n      }\n    }\n\n    skey = auth;\n    skey += \":\";\n    skey += pattern;\n\n    if (newgid.length()) {\n      gid_t mgid = (gid_t) atoi(newgid.c_str());\n      XrdOucString cx = \"\";\n      cx += (int) mgid;\n\n      if (cx != newgid) {\n        eos_static_err(\"strings differ %s %s\", cx.c_str(), newgid.c_str());\n        return false;\n      }\n\n      skey += \":\";\n      skey += \"gid\";\n      eos::common::Mapping::gVirtualGidMap[skey.c_str()] = mgid;\n      set = true;\n      // no '&' are allowed here\n      XrdOucString svalue = value;\n\n      while (svalue.replace(\"&\", \" \")) {\n      };\n\n      if (storeConfig) {\n        gOFS->mConfigEngine->SetConfigValue(\"vid\", skey.c_str(), svalue.c_str());\n      }\n    }\n  }\n\n  return set;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nVid::Set(XrdOucEnv& env,\n         int& retc,\n         XrdOucString& stdOut,\n         XrdOucString& stdErr,\n         bool storeConfig)\n{\n  int envlen;\n  // no '&' are allowed into stdOut !\n  XrdOucString inenv = env.Env(envlen);\n\n  while (inenv.replace(\"&\", \" \")) {\n  };\n\n  bool rc = Set(env.Env(envlen), storeConfig);\n\n  if (rc == true) {\n    stdOut += \"success: set vid [ \";\n    stdOut += inenv;\n    stdOut += \" ]\\n\";\n    errno = 0;\n    retc = 0;\n    return true;\n  } else {\n    stdErr += \"error: failed to set vid [ \";\n    stdErr += inenv;\n    stdErr += \" ]\\n\";\n    errno = EINVAL;\n    retc = EINVAL;\n    return false;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nVid::Ls(XrdOucEnv& env,\n        int& retc,\n        XrdOucString& stdOut,\n        XrdOucString& stdErr)\n{\n  eos::common::RWMutexReadLock lock(eos::common::Mapping::gMapMutex);\n  eos::common::Mapping::Print(stdOut, env.Get(\"mgm.vid.option\"));\n  retc = 0;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nVid::Rm(XrdOucEnv& env,\n        int& retc,\n        XrdOucString& stdOut,\n        XrdOucString& stdErr,\n        bool storeConfig)\n{\n  eos::common::RWMutexWriteLock lock(eos::common::Mapping::gMapMutex);\n  XrdOucString skey = env.Get(\"mgm.vid.key\");\n  XrdOucString vidcmd = env.Get(\"mgm.vid.cmd\");\n  int envlen = 0;\n  XrdOucString inenv = env.Env(envlen);\n\n  while (inenv.replace(\"&\", \" \")) {\n  };\n\n  if (skey.beginswith(\"vid:\")) {\n    skey.erase(0, 4);\n  }\n\n  if (!skey.length()) {\n    stdErr += \"error: failed to rm vid [ \";\n    stdErr += inenv;\n    stdErr += \"] - key missing\";\n    errno = EINVAL;\n    retc = EINVAL;\n    return false;\n  }\n\n  int errc = 0;\n  int nerased = 0;\n\n  // Depending on the key we have to modify the eos::common::Mapping maps\n  if (skey.endswith(\":uids\")) {\n    XrdOucString lkey = skey;\n    lkey.replace(\":uids\", \"\");\n    std::string usrname = lkey.c_str();\n    uid_t uid = eos::common::VirtualIdentity::kNobodyUid;\n\n    if (std::find_if(usrname.begin(), usrname.end(),\n    [](unsigned char c) {\n    return std::isalpha(c);\n    }) !=\n    usrname.end()) {\n      uid = eos::common::Mapping::UserNameToUid(usrname, errc);\n\n      if (errc) {\n        uid = eos::common::VirtualIdentity::kNobodyUid;\n      }\n    } else {\n      try {\n        uid = std::stoul(usrname);\n      } catch (const std::exception& e) {\n        uid = eos::common::VirtualIdentity::kNobodyUid;\n      }\n    }\n\n    skey.replace(usrname.c_str(), std::to_string(uid).c_str());\n    nerased += eos::common::Mapping::gUserRoleVector.erase(uid);\n  }\n\n  if (skey.endswith(\":gids\")) {\n    XrdOucString lkey = skey;\n    lkey.replace(\":gids\", \"\");\n    // This is not a mistake, the skey embeds the uid/username for the\n    // current mapping rule!\n    std::string usrname = lkey.c_str();\n    uid_t uid = eos::common::VirtualIdentity::kNobodyUid;\n\n    if (std::find_if(usrname.begin(), usrname.end(),\n    [](unsigned char c) {\n    return std::isalpha(c);\n    }) !=\n    usrname.end()) {\n      uid = eos::common::Mapping::UserNameToUid(usrname, errc);\n\n      if (errc) {\n        uid = eos::common::VirtualIdentity::kNobodyUid;\n      }\n    } else {\n      try {\n        uid = std::stoul(usrname);\n      } catch (const std::exception& e) {\n        uid = eos::common::VirtualIdentity::kNobodyUid;\n      }\n    }\n\n    skey.replace(usrname.c_str(), std::to_string(uid).c_str());\n    nerased += eos::common::Mapping::gGroupRoleVector.erase(uid);\n  }\n\n  if (skey.beginswith(\"geotag:\")) {\n    // remove from geo tag map\n    XrdOucString gkey = skey;\n    gkey.erase(\"geotag:\");\n    nerased += eos::common::Mapping::gGeoMap.erase(gkey.c_str());\n  } else {\n    nerased += eos::common::Mapping::gVirtualUidMap.erase(skey.c_str());\n    nerased += eos::common::Mapping::gVirtualGidMap.erase(skey.c_str());\n  }\n\n  if (skey.beginswith(\"tident:\")) {\n    XrdOucString saveskey = skey;\n    skey.replace(\"tident:\\\"\", \"\");\n\n    if (skey.find(\"*\", skey.find(\"@\")) != STR_NPOS) {\n      XrdOucString hostmatch = skey.c_str();\n      XrdOucString protocol = skey.c_str();\n\n      while (hostmatch.replace(\"\\\"\", \"\")) {}\n\n      while (hostmatch.replace(\":uid\", \"\")) {}\n\n      while (hostmatch.replace(\":gid\", \"\")) {}\n\n      while (protocol.replace(\"\\\"\", \"\")) {}\n\n      hostmatch.erase(0, hostmatch.find(\"@\") + 1);\n      protocol.erase(protocol.find(\"@\"));\n\n      for (auto it = eos::common::Mapping::gAllowedTidentMatches.begin();\n           it != eos::common::Mapping::gAllowedTidentMatches.end(); ++it) {\n        XrdOucString auth = it->first.c_str();\n        XrdOucString pattern = it->second.c_str();\n\n        if ((auth == protocol) && (pattern == hostmatch)) {\n          eos::common::Mapping::gAllowedTidentMatches.erase(it);\n          break;\n        }\n      }\n    }\n\n    skey = saveskey;\n  }\n\n  if (vidcmd == \"map\") {\n    while (true) {\n      XrdOucString auth = env.Get(\"mgm.vid.auth\");\n\n      if ((auth != \"voms\") && (auth != \"krb5\") && (auth != \"sss\") &&\n          (auth != \"unix\") && (auth != \"tident\") && (auth != \"gsi\") &&\n          (auth != \"https\") && (auth != \"grpc\") && (auth != \"oauth2\")) {\n        eos_static_err(\"%s\", \"msg=\\\"invalid auth mode\\\"\");\n        break;\n      }\n\n      XrdOucString pattern = env.Get(\"mgm.vid.pattern\");\n\n      if (!pattern.length()) {\n        eos_static_err(\"%s\", \"msg=\\\"missing pattern\\\"\");\n        break;\n      }\n\n      if (!pattern.beginswith(\"\\\"\")) {\n        pattern.insert(\"\\\"\", 0);\n      }\n\n      if (!pattern.endswith(\"\\\"\")) {\n        pattern += \"\\\"\";\n      }\n\n      skey = auth;\n      skey += \":\";\n      skey += pattern;\n\n      if ((!env.Get(\"mgm.vid.uid\")) && (!env.Get(\"mgm.vid.gid\"))) {\n        eos_static_err(\"msg=\\\"missing uid|gid\\\"\");\n        break;\n      }\n\n      XrdOucString newuid = env.Get(\"mgm.vid.uid\");\n      XrdOucString newgid = env.Get(\"mgm.vid.gid\");\n\n      if (newuid.length()) {\n        uid_t muid = (uid_t) atoi(newuid.c_str());\n        XrdOucString cx = \"\";\n        cx += (int) muid;\n\n        if (cx != newuid) {\n          eos_static_err(\"msg=\\\"strings differ\\\" old=\\\"%s\\\" new=\\\"%s\\\"\",\n                         cx.c_str(), newuid.c_str());\n          break;\n        }\n\n        skey += \":\";\n        skey += \"uid\";\n        eos::common::Mapping::gVirtualUidMap[skey.c_str()] = muid;\n\n        if (storeConfig) {\n          gOFS->mConfigEngine->DeleteConfigValue(\"vid\", skey.c_str());\n        }\n\n        nerased++;\n      }\n\n      skey = auth;\n      skey += \":\";\n      skey += pattern;\n\n      if (newgid.length()) {\n        gid_t mgid = (gid_t) atoi(newgid.c_str());\n        XrdOucString cx = \"\";\n        cx += (int) mgid;\n\n        if (cx != newgid) {\n          eos_static_err(\"msg=\\\"strings differ\\\" old=\\\"%s\\\" new=\\\"%s\\\"\",\n                         cx.c_str(), newgid.c_str());\n          break;\n        }\n\n        skey += \":\";\n        skey += \"gid\";\n        eos::common::Mapping::gVirtualGidMap[skey.c_str()] = mgid;\n\n        if (storeConfig) {\n          gOFS->mConfigEngine->DeleteConfigValue(\"vid\", skey.c_str());\n        }\n\n        nerased++;\n      }\n\n      skey = \"\";\n      break;\n    }\n  }\n\n  // Delete the entry from the config engine\n  if (storeConfig && skey.length()) {\n    gOFS->mConfigEngine->DeleteConfigValue(\"vid\", skey.c_str());\n  }\n\n  if (nerased) {\n    stdOut += \"success: rm vid [ \";\n    stdOut += inenv;\n    stdOut += \"]\";\n    errno = 0;\n    retc = 0;\n    return true;\n  } else {\n    stdErr += \"error: nothing has been removed\";\n    errno = EINVAL;\n    retc = EINVAL;\n    return false;\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/vid/Vid.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Vid.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_VID__HH__\n#define __EOSMGM_VID__HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"mgm/Namespace.hh\"\n/*----------------------------------------------------------------------------*/\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdOuc/XrdOucEnv.hh>\n/*----------------------------------------------------------------------------*/\n#include <sys/types.h>\n\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\nclass Vid\n{\npublic:\n\n  Vid () { };\n\n  ~Vid () { };\n\n  static bool Set (const char* value, bool storeConfig = true);\n  static bool Set (XrdOucEnv &env, int &retc, XrdOucString &stdOut, XrdOucString &stdErr, bool storeConfig = true);\n  static void Ls (XrdOucEnv &env, int &retc, XrdOucString &stdOut, XrdOucString &stdErr);\n  static bool Rm (XrdOucEnv &env, int &retc, XrdOucString &stdOut, XrdOucString &stdErr, bool storeConfig = true);\n};\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/wfe/WFE.cc",
    "content": "// ----------------------------------------------------------------------\n// File: WFE.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Path.hh\"\n#include \"common/Logging.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/ShellCmd.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/Constants.hh\"\n#include \"common/CtaCommon.hh\"\n#include \"mgm/quota/Quota.hh\"\n#include \"common/eos_cta_pb/EosCtaAlertHandler.hh\"\n#include \"common/WebNotify.hh\"\n#include \"mgm/xattr/XattrSet.hh\"\n#include \"mgm/wfe/WFE.hh\"\n#include \"mgm/stat/Stat.hh\"\n#include \"mgm/ofs/XrdMgmOfsDirectory.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/EosCtaReporter.hh\"\n#include \"mgm/CtaUtils.hh\"\n#include \"mgm/proc/admin/EvictCmd.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"namespace/utils/Checksum.hh\"\n#include \"namespace/utils/Etag.hh\"\n#include \"namespace/utils/Attributes.hh\"\n#include <Xrd/XrdScheduler.hh>\n#include \"proto/Rpc.grpc.pb.h\"\n#include \"common/WFEClient.hh\"\n#include <google/protobuf/util/json_util.h>\n#define EOS_WFE_BASH_PREFIX \"/var/eos/wfe/bash/\"\n\nXrdSysMutex eos::mgm::WFE::gSchedulerMutex;\nXrdScheduler* eos::mgm::WFE::gScheduler;\n\n/*----------------------------------------------------------------------------*/\nextern XrdSysError gMgmOfsEroute;\nextern XrdOucTrace gMgmOfsTrace;\n\nEOSMGMNAMESPACE_BEGIN\n\nusing namespace eos::common;\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Constructor of the work flow engine\n */\n/*----------------------------------------------------------------------------*/\nWFE::WFE()\n{\n  mMs = 0;\n  mActiveJobs = 0;\n  mRootVid = eos::common::VirtualIdentity::Root();\n  XrdSysMutexHelper sLock(gSchedulerMutex);\n  gScheduler = new XrdScheduler(&gMgmOfsEroute, &gMgmOfsTrace, 10, 500, 100);\n  gScheduler->Start();\n}\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief asynchronous WFE thread startup function\n */\n/*----------------------------------------------------------------------------*/\nbool\nWFE::Start()\n{\n  try {\n    mThread.reset(&WFE::WFEr, this);\n  } catch (const std::system_error& e) {\n    return false;\n  }\n\n  return true;\n}\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief asynchronous WFE thread stop function\n */\n/*----------------------------------------------------------------------------*/\nvoid\nWFE::Stop()\n{\n  mThread.join();\n}\n\n//------------------------------------------------------------------------------\n// @brief WFE method doing the actual workflow\n//\n// This thread method loops in regular intervals over all workflow jobs in the\n// workflow directory /eos/<instance>/proc/workflow/\n//------------------------------------------------------------------------------/\nvoid\nWFE::WFEr(ThreadAssistant& assistant) noexcept\n{\n  // Eternal thread doing WFE scans\n  time_t snoozetime = 10;\n  size_t lWFEntx = 0;\n  time_t cleanuptime = 0;\n  gOFS->WaitUntilNamespaceIsBooted(assistant);\n\n  if (assistant.terminationRequested()) {\n    return;\n  }\n\n  assistant.wait_for(std::chrono::seconds(10));\n  eos_static_info(\"msg=\\\"async WFE thread started\\\"\");\n\n  while (!assistant.terminationRequested()) {\n    bool IsEnabledWFE;\n    time_t lWFEInterval;\n    time_t lStartTime = time(NULL);\n    time_t lStopTime;\n    time_t lKeepTime = 7 * 86400;\n    std::map<std::string, std::set<std::string> > wfedirs;\n    XrdOucString stdErr;\n    {\n      eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n\n      if (FsView::gFsView.mSpaceView.count(\"default\") &&\n          (FsView::gFsView.mSpaceView[\"default\"]->GetConfigMember(\"wfe\") == \"on\")) {\n        IsEnabledWFE = true;\n      } else {\n        IsEnabledWFE = false;\n      }\n\n      if (FsView::gFsView.mSpaceView.count(\"default\")) {\n        lWFEInterval =\n          atoi(FsView::gFsView.mSpaceView[\"default\"]->GetConfigMember(\"wfe.interval\").c_str());\n        lWFEntx =\n          atoi(FsView::gFsView.mSpaceView[\"default\"]->GetConfigMember(\"wfe.ntx\").c_str());\n        lKeepTime = atoi(\n                      FsView::gFsView.mSpaceView[\"default\"]->GetConfigMember(\"wfe.keepTIME\").c_str());\n\n        if (!lKeepTime) {\n          lKeepTime = 7 * 86400;\n        }\n      } else {\n        lWFEInterval = 0;\n        lWFEntx = 0;\n      }\n    }\n\n    // Only a master needs to run WFE\n    if (gOFS->mMaster->IsMaster() && IsEnabledWFE) {\n      eos_static_debug(\"msg=\\\"start WFE scan\\\"\");\n      // Find all directories defining an WFE policy\n      gOFS->MgmStats.Add(\"WFEFind\", 0, 0, 1);\n      EXEC_TIMING_BEGIN(\"WFEFind\");\n      // prepare four queries today, yesterday for queued and error jobs\n      std::string queries[4];\n\n      for (size_t i = 0; i < 4; ++i) {\n        queries[i] = gOFS->MgmProcWorkflowPath.c_str();\n        queries[i] += \"/\";\n      }\n\n      {\n        // today\n        time_t when = time(NULL);\n        std::string day = eos::common::Timing::UnixTimestamp_to_Day(when);\n        queries[0] += day;\n        queries[0] += \"/q/\";\n        queries[1] += day;\n        queries[1] += \"/e/\";\n        //yesterday\n        when -= (24 * 3600);\n        day = eos::common::Timing::UnixTimestamp_to_Day(when);\n        queries[2] += day;\n        queries[2] += \"/q/\";\n        queries[3] += day;\n        queries[3] += \"/e/\";\n      }\n\n      for (size_t i = 0; i < 4; ++i) {\n        eos_static_debug(\"query-path=%s\", queries[i].c_str());\n        gOFS->_find(queries[i].c_str(), mError, stdErr, mRootVid, wfedirs, 0,\n                    0, false, 0, false, 0);\n      }\n\n      {\n        eos_static_debug(\"msg=\\\"finished WFE find\\\" WFE-dirs=%llu %s\",\n                         wfedirs.size(), stdErr.c_str()\n                        );\n        time_t now = time(NULL);\n\n        for (auto it = wfedirs.begin(); it != wfedirs.end(); it++) {\n          // -------------------------------------------------------------------\n          // get workflows\n          // -------------------------------------------------------------------\n          if (it->second.size()) {\n            for (auto wit = it->second.begin(); wit != it->second.end(); ++wit) {\n              eos_static_debug(\"wfe-dir=\\\"%s\\\" wfe-job=\\\"%s\\\"\", it->first.c_str(),\n                               wit->c_str());\n              std::string f = it->first;\n              f += *wit;\n              Job* job = new Job();\n\n              if (!job || job->Load(f)) {\n                eos_static_err(\"msg=\\\"cannot load workflow entry\\\" value=\\\"%s\\\"\", f.c_str());\n\n                if (job) {\n                  delete job;\n                }\n              } else {\n                // don't schedule jobs for the future\n                if ((!job->mActions.size()) || (now < job->mActions[0].mTime)) {\n                  delete job;\n                  continue;\n                }\n\n                // stop scheduling if there are too many jobs running\n                if (lWFEntx <= GetActiveJobs()) {\n                  if (lWFEntx > 0) {\n                    mDoneSignal.WaitMS(100);\n\n                    if (lWFEntx <= GetActiveJobs()) {\n                      delete job;\n                      break;\n                    }\n                  }\n                }\n\n                if (!job->IsSync()) {\n                  // use the shared scheduler for asynchronous jobs\n                  XrdSysMutexHelper sLock(gSchedulerMutex);\n                  time_t storetime = 0;\n                  // move job into the scheduled queue\n                  job->Move(job->mActions[0].mQueue, \"r\", storetime);\n                  job->mActions[0].mQueue = \"r\";\n                  job->mActions[0].mTime = storetime;\n                  XrdOucString tst;\n                  job->mActions[0].mWhen = eos::common::StringConversion::GetSizeString(tst,\n                                           (unsigned long long) storetime);\n                  gScheduler->Schedule((XrdJob*) job);\n                  IncActiveJobs();\n                  eos_static_info(\"msg=\\\"scheduled workflow\\\" job=\\\"%s\\\"\",\n                                  job->mDescription.c_str());\n                } else {\n                  delete job;\n                }\n              }\n            }\n          }\n        }\n      }\n\n      EXEC_TIMING_END(\"WFEFind\");\n      eos_static_debug(\"msg=\\\"finished WFE application\\\" WFE-dirs=%llu\",\n                       wfedirs.size());\n    }\n\n    lStopTime = time(NULL);\n\n    if ((lStopTime - lStartTime) < lWFEInterval) {\n      snoozetime = lWFEInterval - (lStopTime - lStartTime);\n    }\n\n    if (!IsEnabledWFE) {\n      snoozetime = 6000;\n    }\n\n    eos_static_debug(\"snooze-time=%llu enabled=%d\", snoozetime, IsEnabledWFE);\n    size_t snoozeloop = snoozetime * 10;\n\n    for (size_t i = 0; i < snoozeloop; i++) {\n      assistant.wait_for(std::chrono::milliseconds(100));\n\n      if (assistant.terminationRequested()) {\n        break;\n      }\n\n      // Check if the setting changed\n      eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n\n      if (FsView::gFsView.mSpaceView.count(\"default\") &&\n          (FsView::gFsView.mSpaceView[\"default\"]->GetConfigMember(\"wfe\") == \"on\")) {\n        if (!IsEnabledWFE) {\n          break;\n        }\n      } else {\n        if (IsEnabledWFE) {\n          break;\n        }\n      }\n    }\n\n    if (gOFS->mMaster->IsMaster() &&\n        (!cleanuptime || (cleanuptime < time(NULL)))) {\n      time_t now = time(NULL);\n      eos_static_info(\"msg=\\\"clean old workflows\\\"\");\n      XrdMgmOfsDirectory dir;\n\n      if (dir.open(gOFS->MgmProcWorkflowPath.c_str(), mRootVid, \"\") != SFS_OK) {\n        eos_static_err(\"msg=\\\"failed to open proc workflow directory\\\"\");\n        continue;\n      }\n\n      const char* entry;\n\n      while ((entry = dir.nextEntry())) {\n        std::string when = entry;\n\n        if ((when == \".\") ||\n            (when == \"..\")) {\n          continue;\n        }\n\n        time_t tst = eos::common::Timing::Day_to_UnixTimestamp(when);\n\n        if (!tst || (tst < (now - lKeepTime))) {\n          eos_static_info(\"msg=\\\"cleaning\\\" dir=\\\"%s\\\"\", entry);\n          ProcCommand Cmd;\n          XrdOucString info;\n          XrdOucString out;\n          XrdOucString err;\n          info = \"mgm.cmd=rm&eos.ruid=0&eos.rgid=0&mgm.deletion=deep&mgm.option=r&mgm.path=\";\n          info += gOFS->MgmProcWorkflowPath;\n          info += \"/\";\n          info += entry;\n          Cmd.open(\"/proc/user\", info.c_str(), mRootVid, &mError);\n          Cmd.AddOutput(out, err);\n\n          if (err.length()) {\n            eos_static_err(\"msg=\\\"cleaning failed\\\" errmsg=\\\"%s\\\"\", err.c_str());\n          } else {\n            eos_static_info(\"msg=\\\"cleaned\\\" dri=\\\"%s\\\"\");\n          }\n\n          Cmd.close();\n        }\n      }\n\n      cleanuptime = now + 3600;\n    }\n  }\n}\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief store a workflow jobs in the workflow queue\n * @return SFS_OK if success\n */\n/*----------------------------------------------------------------------------*/\nint\nWFE::Job::Save(std::string queue, time_t& when, int action, int retry)\n{\n  if (mActions.size() != 1) {\n    return -1;\n  }\n\n  std::string workflowdir = gOFS->MgmProcWorkflowPath.c_str();\n  workflowdir += \"/\";\n  workflowdir += mActions[action].mDay;\n  workflowdir += \"/\";\n  workflowdir += queue;\n  workflowdir += \"/\";\n  workflowdir += mActions[action].mWorkflow;\n  workflowdir += \"/\";\n  std::string entry = eos::common::FileId::Fid2Hex(mFid);\n  eos_static_info(\"workflowdir=\\\"%s\\\" retry=%d when=%u job-time=%s\",\n                  workflowdir.c_str(),\n                  retry, when, mActions[action].mWhen.c_str());\n  XrdOucErrInfo lError;\n  eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n  // check that the workflow directory exists\n  struct stat buf;\n\n  if (gOFS->_stat(workflowdir.c_str(),\n                  &buf,\n                  lError,\n                  rootvid,\n                  \"\")) {\n    // create the workflow sub directory\n    if (gOFS->_mkdir(workflowdir.c_str(),\n                     S_IRWXU | SFS_O_MKPTH,\n                     lError,\n                     rootvid,\n                     \"\")) {\n      // check if it has been created in the meanwhile as the stat and mkdir are not atomic together\n      if (gOFS->_stat(workflowdir.c_str(),\n                      &buf,\n                      lError,\n                      rootvid,\n                      \"\")) {\n        eos_static_err(\"msg=\\\"failed to create workflow directory\\\" path=\\\"%s\\\"\",\n                       workflowdir.c_str());\n        return -1;\n      }\n    }\n  }\n\n  // write a workflow file\n  std::string workflowpath = workflowdir;\n\n  // evt. store with the current time\n\n  if (!when) {\n    when = time(nullptr);\n  }\n\n  XrdOucString tst;\n  workflowpath += eos::common::StringConversion::GetSizeString(tst,\n                  (unsigned long long) when);\n  workflowpath += \":\";\n  workflowpath += entry;\n  workflowpath += \":\";\n  workflowpath += mActions[action].mEvent;\n  mWorkflowPath = workflowpath;\n  //Store which day it is stored for\n  mActions[action].mSavedOnDay = mActions[action].mDay;\n  std::string vids = eos::common::Mapping::VidToString(mVid);\n\n  try {\n    // The point of prefetching here is to get the chunks preceeding the final\n    // one, so that createFile is guaranteed not to wait on network requests.\n    eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, workflowpath);\n    eos::common::RWMutexWriteLock wLock {gOFS->eosViewRWMutex};\n    auto fmd = gOFS->eosView->createFile(workflowpath, 0, 0);\n    auto cid = fmd->getContainerId();\n    auto cmd = gOFS->eosDirectoryService->getContainerMD(cid);\n    cmd->setMTimeNow();\n    cmd->notifyMTimeChange(gOFS->eosDirectoryService);\n    gOFS->eosView->updateContainerStore(cmd.get());\n    fmd->setAttribute(\"sys.action\", mActions[0].mAction);\n    fmd->setAttribute(\"sys.vid\", vids);\n    fmd->setAttribute(\"sys.wfe.errmsg\", mErrorMessage);\n    fmd->setAttribute(\"sys.wfe.retry\", std::to_string(retry));\n    gOFS->eosView->updateFileStore(fmd.get());\n  } catch (eos::MDException& ex) {\n    eos_static_err(\"msg=\\\"failed to save workflow entry\\\" path=\\\"%s\\\" error=\\\"%s\\\"\",\n                   workflowpath.c_str(),\n                   ex.what());\n    return -1;\n  }\n\n  return SFS_OK;\n}\n\n/*----------------------------------------------------------------------------*/\nint\nWFE::Job::Load(std::string path2entry)\n/*----------------------------------------------------------------------------*/\n/**\n * @brief load a workflow job from the given path\n * @return SFS_OK if success\n */\n/*----------------------------------------------------------------------------*/\n{\n  XrdOucErrInfo lError;\n  eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n  std::string f = path2entry;\n  f.erase(0, path2entry.rfind('/') + 1);\n  std::string workflow = path2entry;\n  workflow.erase(path2entry.rfind('/'));\n  workflow.erase(0, workflow.rfind('/') + 1);\n  std::string q = path2entry;\n  q.erase(q.rfind('/'));\n  q.erase(q.rfind('/'));\n  q.erase(0, q.rfind('/') + 1);\n  std::string savedAtDay = path2entry;\n  savedAtDay.erase(savedAtDay.rfind('/'));\n  savedAtDay.erase(savedAtDay.rfind('/'));\n  savedAtDay.erase(savedAtDay.rfind('/'));\n  savedAtDay.erase(0, savedAtDay.rfind('/') + 1);\n  std::string when;\n  std::string idevent;\n  std::string id;\n  std::string event;\n  bool s1 = eos::common::StringConversion::SplitKeyValue(f, when, idevent, \":\");\n  bool s2 = eos::common::StringConversion::SplitKeyValue(idevent, id, event, \":\");\n  mWorkflowPath = path2entry;\n\n  if (s1 && s2) {\n    mFid = eos::common::FileId::Hex2Fid(id.c_str());\n    eos_static_info(\"workflow=\\\"%s\\\" fxid=%08llx\", workflow.c_str(), mFid);\n    {\n      eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, path2entry);\n      eos::common::RWMutexReadLock rLock(gOFS->eosViewRWMutex);\n      auto fmd = gOFS->eosView->getFile(path2entry);\n\n      try {\n        time_t t_when = strtoull(when.c_str(), 0, 10);\n        AddAction(fmd->getAttribute(\"sys.action\"), event, t_when, savedAtDay, workflow,\n                  q);\n      } catch (eos::MDException& ex) {\n        eos_static_err(\"msg=\\\"no action stored\\\" path=\\\"%s\\\"\", f.c_str());\n      }\n\n      try {\n        auto vidstring = fmd->getAttribute(\"sys.vid\").c_str();\n\n        if (!eos::common::Mapping::VidFromString(mVid, vidstring)) {\n          eos_static_crit(\"parsing of %s failed - setting nobody\\n\", vidstring);\n          mVid = eos::common::VirtualIdentity::Nobody();\n        }\n      } catch (eos::MDException& ex) {\n        mVid = eos::common::VirtualIdentity::Nobody();\n        eos_static_err(\"msg=\\\"no vid stored\\\" path=\\\"%s\\\"\", f.c_str());\n      }\n\n      try {\n        mRetry = (int)strtoul(fmd->getAttribute(\"sys.wfe.retry\").c_str(), nullptr, 10);\n      } catch (eos::MDException& ex) {\n        eos_static_err(\"msg=\\\"no retry stored\\\" path=\\\"%s\\\"\", f.c_str());\n      }\n\n      try {\n        mErrorMessage = fmd->getAttribute(\"sys.wfe.errmsg\");\n      } catch (eos::MDException& ex) {\n        eos_static_info(\"msg=\\\"no error message stored\\\" path=\\\"%s\\\"\", f.c_str());\n      }\n    }\n  } else {\n    eos_static_err(\"msg=\\\"illegal workflow entry\\\" key=\\\"%s\\\"\", f.c_str());\n    return SFS_ERROR;\n  }\n\n  return SFS_OK;\n}\n\n/*----------------------------------------------------------------------------*/\nint\nWFE::Job::Move(std::string from_queue, std::string to_queue, time_t& when,\n               int retry)\n/*----------------------------------------------------------------------------*/\n/**\n * @brief move workflow jobs between queues\n * @return SFS_OK if success\n */\n/*----------------------------------------------------------------------------*/\n{\n  auto fromDay = mActions[0].mSavedOnDay;\n\n  if (Save(to_queue, when, 0, retry) == SFS_OK) {\n    mActions[0].mQueue = to_queue;\n\n    if ((from_queue != to_queue) && (Delete(from_queue, fromDay) == SFS_ERROR)) {\n      eos_static_err(\"msg=\\\"failed to remove for move from queue=\\\"%s\\\" to queue=\\\"%s\\\"\",\n                     from_queue.c_str(), to_queue.c_str());\n    }\n  } else {\n    eos_static_err(\"msg=\\\"failed to save for move to queue\\\" queue=\\\"%s\\\"\",\n                   to_queue.c_str());\n    return SFS_ERROR;\n  }\n\n  return SFS_OK;\n}\n\n/*----------------------------------------------------------------------------*/\nint\n/*----------------------------------------------------------------------------*/\nWFE::Job::Results(std::string queue, int retc, XrdOucString log, time_t when)\n/*----------------------------------------------------------------------------*/\n{\n  std::string workflowdir = gOFS->MgmProcWorkflowPath.c_str();\n  workflowdir += \"/\";\n  workflowdir += mActions[0].mDay;\n  workflowdir += \"/\";\n  workflowdir += queue;\n  workflowdir += \"/\";\n  workflowdir += mActions[0].mWorkflow;\n  workflowdir += \"/\";\n  std::string entry = eos::common::FileId::Fid2Hex(mFid);\n  eos_static_info(\"workflowdir=\\\"%s\\\" entry=%s\", workflowdir.c_str(),\n                  entry.c_str());\n  XrdOucErrInfo lError;\n  eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n  // check that the workflow directory exists\n  struct stat buf;\n\n  if (gOFS->_stat(workflowdir.c_str(),\n                  &buf,\n                  lError,\n                  rootvid,\n                  \"\")) {\n    eos_static_err(\"msg=\\\"failed to find the workflow dir\\\" path=\\\"%s\\\"\",\n                   workflowdir.c_str());\n    return -1;\n  }\n\n  // write a workflow file\n  std::string workflowpath = workflowdir;\n  XrdOucString tst;\n  workflowpath += eos::common::StringConversion::GetSizeString(tst,\n                  (unsigned long long) when);\n  workflowpath += \":\";\n  workflowpath += entry;\n  workflowpath += \":\";\n  workflowpath += mActions[0].mEvent;\n  mWorkflowPath = workflowpath;\n  XrdOucString sretc;\n  sretc += retc;\n\n  if (gOFS->_attr_set(workflowpath.c_str(),\n                      lError,\n                      rootvid,\n                      nullptr,\n                      \"sys.wfe.retc\",\n                      sretc.c_str())) {\n    eos_static_err(\"msg=\\\"failed to store workflow return code\\\" path=\\\"%s\\\" retc=\\\"%s\\\"\",\n                   workflowpath.c_str(),\n                   sretc.c_str());\n    return -1;\n  }\n\n  if (gOFS->_attr_set(workflowpath.c_str(),\n                      lError,\n                      rootvid,\n                      nullptr,\n                      \"sys.wfe.log\",\n                      log.c_str())) {\n    eos_static_err(\"msg=\\\"failed to store workflow log\\\" path=\\\"%s\\\" log=\\\"%s\\\"\",\n                   workflowpath.c_str(),\n                   log.c_str());\n    return -1;\n  }\n\n  if (!retc) {\n    // we remote the ugly error message, which sticks around even if retc=0\n    if (gOFS->_attr_set(workflowpath.c_str(),\n\t\t\tlError,\n\t\t\trootvid,\n\t\t\tnullptr,\n\t\t\t\"sys.wfe.errmsg\",\n\t\t\t\"\")) {\n      eos_static_err(\"msg=\\\"failed to store workflow return code\\\" path=\\\"%s\\\" retc=\\\"%s\\\"\",\n\t\t     workflowpath.c_str(),\n                   sretc.c_str());\n      return -1;\n    }\n  }\n\n  return SFS_OK;\n}\n\n\n\n/*----------------------------------------------------------------------------*/\nint\nWFE::Job::Delete(std::string queue, std::string fromDay)\n/*----------------------------------------------------------------------------*/\n/**\n * @brief delete a workflow job from a queue\n * @return SFS_OK if success\n */\n/*----------------------------------------------------------------------------*/\n{\n  if (mActions.size() != 1) {\n    return SFS_ERROR;\n  }\n\n  std::string workflowdir = gOFS->MgmProcWorkflowPath.c_str();\n  workflowdir += \"/\";\n  // We have to remove from the day when it was saved\n  workflowdir += fromDay;\n  workflowdir += \"/\";\n  workflowdir += queue;\n  workflowdir += \"/\";\n  workflowdir += mActions[0].mWorkflow;\n  workflowdir += \"/\";\n  std::string entry = eos::common::FileId::Fid2Hex(mFid);\n  eos_static_info(\"workflowdir=\\\"%s\\\"\", workflowdir.c_str());\n  XrdOucErrInfo lError;\n  eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n  // write a workflow file\n  std::string workflowpath = workflowdir;\n  workflowpath += mActions[0].mWhen;\n  workflowpath += \":\";\n  workflowpath += entry;\n  workflowpath += \":\";\n  workflowpath += mActions[0].mEvent;\n\n  if (!gOFS->_rem(workflowpath.c_str(),\n                  lError,\n                  rootvid,\n                  \"\",\n                  false,\n                  false,\n                  true)) {\n    return SFS_OK;\n  } else {\n    eos_static_err(\"msg=\\\"failed to delete job\\\" job=\\\"%s\\\"\", mDescription.c_str());\n    return SFS_ERROR;\n  }\n}\n\n/*----------------------------------------------------------------------------*/\nint\nWFE::Job::DoIt(bool issync, std::string& errorMsg, const char* const ininfo)\n/*----------------------------------------------------------------------------*/\n/**\n * @brief execute a workflow\n * @return\n *  */\n/*----------------------------------------------------------------------------*/\n{\n  // RAII: Async jobs reduce counter on all paths\n  auto decrementJobs = [this](void*) {\n    if (!IsSync()) {\n      gOFS->WFEd.DecActiveJobs();\n      gOFS->WFEd.GetSignal()->Signal();\n    }\n  };\n  std::unique_ptr<void, decltype(decrementJobs)> activeJobsGuard {\n    static_cast<void*>(this),\n    decrementJobs\n  };\n  std::string method;\n  std::string args;\n  eos::common::VirtualIdentity lRootVid = eos::common::VirtualIdentity::Root();\n  XrdOucErrInfo lError;\n  int retc = 0;\n  time_t storetime = 0;\n\n  if (mActions[0].mQueue == \"r\" || mActions[0].mQueue == \"e\") {\n    bool actionParsed = false;\n\n    if (mActions[0].mAction.find(':') == std::string::npos) {\n      method = mActions[0].mAction;\n      actionParsed = true;\n    } else {\n      actionParsed = eos::common::StringConversion::SplitKeyValue(mActions[0].mAction,\n                     method,\n                     args, \":\");\n    }\n\n    if (actionParsed) {\n      if (method == \"mail\") {\n        std::string recipient;\n        std::string freetext;\n\n        if (!eos::common::StringConversion::SplitKeyValue(args, recipient, freetext,\n            \":\")) {\n          recipient = args;\n          freetext = \"EOS workflow notification\";\n        }\n\n        std::string topic = gOFS->MgmOfsInstanceName.c_str();\n        topic += \" ( \";\n        topic += gOFS->HostName;\n        topic += \" ) \";\n        topic += \" \";\n        topic += \" event=\";\n        topic += mActions[0].mEvent;\n        topic += \" fid=\";\n        topic += eos::common::FileId::Fid2Hex(mFid).c_str();\n        std::string do_mail = \"echo \";\n        do_mail += \"\\\"\";\n        do_mail += freetext;\n        do_mail += \"\\\"\";\n        do_mail += \"| mail -s \\\"\";\n        do_mail += topic;\n        do_mail += \"\\\" \";\n        do_mail += recipient;\n        eos_static_info(\"shell-cmd=\\\"%s\\\"\", do_mail.c_str());\n        eos::common::ShellCmd cmd(do_mail);\n        eos::common::cmd_status rc = cmd.wait(5);\n\n        if (rc.exit_code) {\n          eos_static_err(\"msg=\\\"failed to send workflow notification mail\\\" job=\\\"%s\\\"\",\n                         mDescription.c_str());\n          storetime = 0;\n          Move(mActions[0].mQueue, \"f\", storetime);\n          XrdOucString log = \"failed to send workflow notification mail\";\n          Results(\"f\", -1, log, storetime);\n        } else {\n          eos_static_info(\"msg=\\\"done notification\\\" job=\\\"%s\\\"\",\n                          mDescription.c_str());\n          storetime = 0;\n          Move(mActions[0].mQueue, \"d\", storetime);\n          XrdOucString log = \"notified by email\";\n          Results(\"d\", 0, log, storetime);\n        }\n      } else if (method == \"bash\") {\n        std::string executable;\n        std::string executableargs;\n\n        if (!eos::common::StringConversion::SplitKeyValue(args, executable,\n            executableargs, \":\")) {\n          executable = args;\n          executableargs = \"\";\n        }\n\n        XrdOucString execargs = executableargs.c_str();\n        std::string fullpath;\n        bool format_error = false;\n\n        if (executable.find('/') == std::string::npos) {\n          std::shared_ptr<eos::IFileMD> fmd ;\n          std::shared_ptr<eos::IContainerMD> cmd ;\n          // do meta replacement\n          eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, mFid);\n          eos::common::RWMutexReadLock viewReadLock(gOFS->eosViewRWMutex);\n\n          try {\n            fmd = gOFS->eosFileService->getFileMD(mFid);\n            cmd = gOFS->eosDirectoryService->getContainerMD(fmd->getContainerId());\n            fullpath = gOFS->eosView->getUri(fmd.get());\n          } catch (eos::MDException& e) {\n            eos_static_debug(\"caught exception %d %s\\n\", e.getErrno(),\n                             e.getMessage().str().c_str());\n          }\n\n          if (fmd.get() && cmd.get()) {\n            std::shared_ptr<eos::IFileMD> cfmd  = fmd;\n            std::shared_ptr<eos::IContainerMD> ccmd = cmd;\n            viewReadLock.Release();\n            std::string cv;\n            eos::IFileMD::ctime_t ctime;\n            eos::IFileMD::ctime_t mtime;\n            cfmd->getCTime(ctime);\n            cfmd->getMTime(mtime);\n            std::string checksum;\n            eos::appendChecksumOnStringAsHex(cfmd.get(), checksum);\n            // translate uid/gid to username/groupname\n            std::string user_name;\n            std::string group_name;\n            int errc;\n            errc = 0;\n            user_name  = Mapping::UidToUserName(cfmd->getCUid(), errc);\n\n            if (errc) {\n              user_name = \"nobody\";\n            }\n\n            errc = 0;\n            group_name = Mapping::GidToGroupName(cfmd->getCGid(), errc);\n\n            if (errc) {\n              group_name = \"nobody\";\n            }\n\n            XrdOucString unbase64;\n            XrdOucString base64;\n            unbase64 = fullpath.c_str();\n            eos::common::SymKey::Base64(unbase64, base64);\n            int cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::path>\", unbase64.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::base64:path>\", base64.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::uid>\",\n                                    eos::common::StringConversion::GetSizeString(cv,\n                                        (unsigned long long) cfmd->getCUid()))) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::gid>\",\n                                    eos::common::StringConversion::GetSizeString(cv,\n                                        (unsigned long long) cfmd->getCGid()))) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::ruid>\",\n                                    eos::common::StringConversion::GetSizeString(cv,\n                                        (unsigned long long) mVid.uid))) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::rgid>\",\n                                    eos::common::StringConversion::GetSizeString(cv,\n                                        (unsigned long long) mVid.gid))) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::username>\",\n                                    user_name.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::groupname>\",\n                                    group_name.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::rusername>\",\n                                    mVid.uid_string.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::rgroupname>\",\n                                    mVid.gid_string.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::host>\",\n                                    mVid.host.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::sec.app>\",\n                                    mVid.app.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::sec.name>\",\n                                    mVid.name.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::sec.prot>\",\n                                    mVid.prot.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::sec.grps>\",\n                                    mVid.grps.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::instance>\",\n                                    gOFS->MgmOfsInstanceName)) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::ctime.s>\",\n                                    eos::common::StringConversion::GetSizeString(cv,\n                                        (unsigned long long) ctime.tv_sec))) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::mtime.s>\",\n                                    eos::common::StringConversion::GetSizeString(cv,\n                                        (unsigned long long) ctime.tv_sec))) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::ctime.ns>\",\n                                    eos::common::StringConversion::GetSizeString(cv,\n                                        (unsigned long long) ctime.tv_nsec))) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::mtime.ns>\",\n                                    eos::common::StringConversion::GetSizeString(cv,\n                                        (unsigned long long) ctime.tv_nsec))) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::ctime>\",\n                                    eos::common::StringConversion::GetSizeString(cv,\n                                        (unsigned long long) ctime.tv_sec))) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::mtime>\",\n                                    eos::common::StringConversion::GetSizeString(cv,\n                                        (unsigned long long) ctime.tv_sec))) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::size>\",\n                                    eos::common::StringConversion::GetSizeString(cv,\n                                        (unsigned long long) cfmd->getSize()))) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::cid>\",\n                                    eos::common::StringConversion::GetSizeString(cv,\n                                        (unsigned long long) cfmd->getContainerId()))) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::fid>\",\n                                    eos::common::StringConversion::GetSizeString(cv, (unsigned long long) mFid))) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            const std::string hex_fid = eos::common::FileId::Fid2Hex(mFid);\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::fxid>\", hex_fid.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            std::string turl = \"root://\";\n            turl += gOFS->MgmOfsAlias.c_str();\n            turl += \"/\";\n            turl += fullpath;\n            turl += \"?eos.lfn=fxid:\";\n            turl += hex_fid.c_str();\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::turl>\",\n                                    turl.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            unbase64 = cfmd->getName().c_str();\n            eos::common::SymKey::Base64(unbase64, base64);\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::name>\", unbase64.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::base64:name>\", base64.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            unbase64 = cfmd->getLink().c_str();\n            eos::common::SymKey::Base64(unbase64, base64);\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::link>\", unbase64.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::base64:link>\", base64.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::checksum>\", checksum.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::checksumtype>\",\n                                    eos::common::LayoutId::GetChecksumString(cfmd->getLayoutId()))) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::event>\", mActions[0].mEvent.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::queue>\", mActions[0].mQueue.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::workflow>\",\n                                    mActions[0].mWorkflow.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::vpath>\", mWorkflowPath.c_str())) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            time_t now = time(NULL);\n            cnt = 0;\n\n            while (execargs.replace(\"<eos::wfe::now>\",\n                                    eos::common::StringConversion::GetSizeString(cv, (unsigned long long) now))) {\n              if (++cnt > 16) {\n                break;\n              }\n            }\n\n            int xstart = 0;\n            cnt = 0;\n\n            while ((xstart = execargs.find(\"<eos::wfe::fxattr:\")) != STR_NPOS) {\n              if (++cnt > 256) {\n                break;\n              }\n\n              int xend = execargs.find(\">\", xstart);\n\n              if (xend == STR_NPOS) {\n                format_error = true;\n                break;\n              } else {\n                bool b64encode = false;\n                std::string key;\n                std::string value;\n                key.assign(execargs.c_str() + xstart + 18, xend - xstart - 18);\n                execargs.erase(xstart, xend + 1 - xstart);\n                XrdOucString skey = key.c_str();\n\n                if (skey.beginswith(\"base64:\")) {\n                  key.erase(0, 7);\n                  b64encode = true;\n                }\n\n                if (gOFS->_attr_get(*cfmd, key, value)) {\n                  if (b64encode) {\n                    unbase64 = value.c_str();\n                    eos::common::SymKey::Base64(unbase64, base64);\n                    value = base64.c_str();\n                  }\n\n                  if (xstart == execargs.length()) {\n                    execargs += value.c_str();\n                  } else {\n                    execargs.insert(value.c_str(), xstart);\n                  }\n                } else {\n                  execargs.insert(\"UNDEF\", xstart);\n                }\n              }\n            }\n\n            xstart = 0;\n            cnt = 0;\n\n            while ((xstart = execargs.find(\"<eos::wfe::cxattr:\")) != STR_NPOS) {\n              if (++cnt > 256) {\n                break;\n              }\n\n              int xend = execargs.find(\">\", xstart);\n\n              if (xend == STR_NPOS) {\n                format_error = true;\n                break;\n              } else {\n                bool b64encode = false;\n                std::string key;\n                std::string value;\n                key.assign(execargs.c_str() + xstart + 18, xend - xstart - 18);\n                execargs.erase(xstart, xend + 1 - xstart);\n                XrdOucString skey = key.c_str();\n\n                if (skey.beginswith(\"base64:\")) {\n                  key.erase(0, 7);\n                  b64encode = true;\n                }\n\n                if (gOFS->_attr_get(*ccmd, key, value)) {\n                  if (b64encode) {\n                    unbase64 = value.c_str();\n                    eos::common::SymKey::Base64(unbase64, base64);\n                    value = base64.c_str();\n                  }\n\n                  if (xstart == execargs.length()) {\n                    execargs += value.c_str();\n                  } else {\n                    execargs.insert(value.c_str(), xstart);\n                  }\n                } else {\n                  execargs.insert(\"UNDEF\", xstart);\n                }\n              }\n            }\n\n            if (execargs.find(\"<eos::wfe::base64:metadata>\") != STR_NPOS) {\n              XrdOucString out = \"\";\n              XrdOucString err = \"\";\n              // ---------------------------------------------------------------------------------\n              // run file info to get file md\n              // ---------------------------------------------------------------------------------\n              XrdOucString file_metadata;\n              ProcCommand Cmd;\n              XrdOucString info;\n              info = \"mgm.cmd=fileinfo&mgm.path=fid:\";\n              info += eos::common::StringConversion::GetSizeString(cv,\n                      (unsigned long long) mFid);\n              info += \"&mgm.file.info.option=-m\";\n              Cmd.open(\"/proc/user\", info.c_str(), lRootVid, &lError);\n              Cmd.AddOutput(out, err);\n              Cmd.close();\n              file_metadata = out;\n\n              if (err.length()) {\n                eos_static_err(\"msg=\\\"file info returned error\\\" err=\\\"%s\\\"\", err.c_str());\n              }\n\n              cnt = 0;\n\n              while (file_metadata.replace(\"\\\"\", \"'\")) {\n                if (++cnt > 16) {\n                  break;\n                }\n              }\n\n              out = err = \"\";\n              // ---------------------------------------------------------------------------------\n              // run container info to get container md\n              // ---------------------------------------------------------------------------------\n              XrdOucString container_metadata;\n              info = \"mgm.cmd=fileinfo&mgm.path=pid:\";\n              info += eos::common::StringConversion::GetSizeString(cv,\n                      (unsigned long long) cfmd->getContainerId());\n              info += \"&mgm.file.info.option=-m\";\n              Cmd.open(\"/proc/user\", info.c_str(), lRootVid, &lError);\n              Cmd.AddOutput(out, err);\n              Cmd.close();\n              container_metadata = out;\n\n              if (err.length()) {\n                eos_static_err(\"msg=\\\"container info returned error\\\" err=\\\"%s\\\"\", err.c_str());\n              }\n\n              cnt = 0;\n\n              while (container_metadata.replace(\"\\\"\", \"'\")) {\n                if (++cnt > 16) {\n                  break;\n                }\n              }\n\n              std::string metadata = \"\\\"fmd={ \";\n              metadata += file_metadata.c_str();\n              metadata += \"} dmd={ \";\n              metadata += container_metadata.c_str();\n              metadata += \"}\\\"\";\n              unbase64 = metadata.c_str();\n              eos::common::SymKey::Base64(unbase64, base64);\n              execargs.replace(\"<eos::wfe::base64:metadata>\", base64.c_str());\n            }\n\n            execargs.replace(\"<eos::wfe::action>\", mActions[0].mAction.c_str());\n            std::string bashcmd = EOS_WFE_BASH_PREFIX + executable + \" \" + execargs.c_str();\n\n            if (!format_error) {\n              eos::common::ShellCmd cmd(bashcmd);\n              eos_static_info(\"shell-cmd=\\\"%s\\\"\", bashcmd.c_str());\n              eos::common::cmd_status rc = cmd.wait(1800);\n              // retrieve the stderr of this command\n              XrdOucString outerr;\n              char buff[65536];\n              int end;\n              memset(buff, 0, sizeof(buff));\n\n              while ((end = ::read(cmd.errfd, buff, sizeof(buff))) > 0) {\n                outerr += buff;\n                memset(buff, 0, sizeof(buff));\n              }\n\n              eos_static_info(\"shell-cmd-stderr=%s\", outerr.c_str());\n              // scan for result tags referencing the trigger path\n              xstart = 0;\n              cnt = 0;\n\n              while ((xstart = outerr.find(\"<eos::wfe::path::fxattr:\", xstart)) != STR_NPOS) {\n                if (++cnt > 256) {\n                  break;\n                }\n\n                int xend = outerr.find(\">\", xstart);\n\n                if (xend == STR_NPOS) {\n                  eos_static_err(\"malformed shell stderr tag\");\n                  break;\n                } else {\n                  std::string key;\n                  std::string value;\n                  key.assign(outerr.c_str() + xstart + 24, xend - xstart - 24);\n                  int vend = outerr.find(\" \", xend + 1);\n\n                  if (vend > 0) {\n                    value.assign(outerr.c_str(), xend + 1, vend - (xend + 1));\n                  } else {\n                    value.assign(outerr.c_str(), xend + 1, std::string::npos);\n                  }\n\n                  // remove a possible line feed from the value\n                  while (value.length() && (value[value.length() - 1] == '\\n')) {\n                    value.erase(value.length() - 1);\n                  }\n\n                  eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, mFid);\n                  eos::common::RWMutexWriteLock nsLock(gOFS->eosViewRWMutex);\n\n                  try {\n                    fmd = gOFS->eosFileService->getFileMD(mFid);\n                    base64 = value.c_str();\n                    eos::common::SymKey::DeBase64(base64, unbase64);\n                    fmd->setAttribute(key, unbase64.c_str());\n                    fmd->setMTimeNow();\n                    gOFS->eosView->updateFileStore(fmd.get());\n                    errno = 0;\n                    eos_static_info(\"msg=\\\"stored extended attribute\\\" key=%s value=%s\",\n                                    key.c_str(), value.c_str());\n                  } catch (eos::MDException& e) {\n                    eos_static_err(\"msg=\\\"failed set extended attribute\\\" key=%s value=%s\",\n                                   key.c_str(), value.c_str());\n                  }\n                }\n\n                xstart++;\n              }\n\n              retc = rc.exit_code;\n\n              if (rc.exit_code) {\n                eos_static_err(\"msg=\\\"failed to run bash workflow\\\" job=\\\"%s\\\" retc=%d\",\n                               mDescription.c_str(), rc.exit_code);\n                int retry = 0;\n                time_t delay = 0;\n\n                if (rc.exit_code == EAGAIN) {\n                  try {\n                    std::string retryattr = \"sys.workflow.\" + mActions[0].mEvent + \".\" +\n                                            mActions[0].mWorkflow + \".retry.max\";\n                    std::string delayattr = \"sys.workflow.\" + mActions[0].mEvent + \".\" +\n                                            mActions[0].mWorkflow + \".retry.delay\";\n                    eos_static_info(\"%s %s\", retryattr.c_str(), delayattr.c_str());\n                    std::string value = ccmd->getAttribute(retryattr);\n                    retry = (int)strtoul(value.c_str(), 0, 10);\n                    value = ccmd->getAttribute(delayattr);\n                    delay = (int)strtoul(value.c_str(), 0, 10);\n                  } catch (eos::MDException& e) {\n                    execargs.insert(\"UNDEF\", xstart);\n                  }\n\n                  if (!IsSync() && (mRetry < retry)) {\n                    storetime = (time_t) mActions[0].mTime + delay;\n                    // can retry\n                    Move(\"r\", \"e\", storetime, ++mRetry);\n                    XrdOucString log = \"scheduled for retry\";\n                    Results(\"e\", EAGAIN, log, storetime);\n                  } else {\n                    storetime = (time_t) mActions[0].mTime;\n                    // can not retry\n                    Move(\"r\", \"f\", storetime, mRetry);\n                    XrdOucString log = \"workflow failed without possibility to retry\";\n                    Results(\"f\", rc.exit_code, log, storetime);\n                  }\n                } else {\n                  storetime = 0;\n                  // can not retry\n                  Move(\"r\", \"f\", storetime);\n                  XrdOucString log = \"workflow failed without possibility to retry\";\n                  Results(\"f\", rc.exit_code, log, storetime);\n                }\n              } else {\n                eos_static_info(\"msg=\\\"done bash workflow\\\" job=\\\"%s\\\"\",\n                                mDescription.c_str());\n                storetime = 0;\n                Move(\"r\", \"d\", storetime);\n                XrdOucString log = \"workflow succeeded\";\n                Results(\"d\", rc.exit_code, log, storetime);\n              }\n\n              // scan for result tags referencing the workflow path\n              xstart = 0;\n              cnt = 0;\n\n              while ((xstart = outerr.find(\"<eos::wfe::vpath::fxattr:\",\n                                           xstart)) != STR_NPOS) {\n                if (++cnt > 256) {\n                  break;\n                }\n\n                int xend = outerr.find(\">\", xstart);\n\n                if (xend == STR_NPOS) {\n                  eos_static_err(\"malformed shell stderr tag\");\n                  break;\n                } else {\n                  std::string key;\n                  std::string value;\n                  key.assign(outerr.c_str() + xstart + 25, xend - xstart - 25);\n                  int vend = outerr.find(\" \", xend + 1);\n\n                  if (vend > 0) {\n                    value.assign(outerr.c_str(), xend + 1, vend - (xend + 1));\n                  } else {\n                    value.assign(outerr.c_str(), xend + 1, std::string::npos);\n                  }\n\n                  eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, mWorkflowPath);\n                  eos::common::RWMutexWriteLock nsLock(gOFS->eosViewRWMutex);\n\n                  try {\n                    fmd = gOFS->eosView->getFile(mWorkflowPath);\n                    base64 = value.c_str();\n                    eos::common::SymKey::DeBase64(base64, unbase64);\n                    fmd->setAttribute(key, unbase64.c_str());\n                    fmd->setMTimeNow();\n                    gOFS->eosView->updateFileStore(fmd.get());\n                    errno = 0;\n                    eos_static_info(\"msg=\\\"stored extended attribute on vpath\\\" vpath=%s key=%s value=%s\",\n                                    mWorkflowPath.c_str(), key.c_str(), value.c_str());\n                  } catch (eos::MDException& e) {\n                    eos_static_err(\"msg=\\\"failed set extended attribute\\\" key=%s value=%s\",\n                                   key.c_str(), value.c_str());\n                  }\n                }\n\n                xstart++;\n              }\n            } else {\n              retc = EINVAL;\n              storetime = 0;\n              // cannot retry\n              Move(mActions[0].mQueue, \"f\", storetime);\n              XrdOucString log = \"workflow failed to invalid arguments\";\n              Results(\"f\", retc, log, storetime);\n            }\n          } else {\n            storetime = 0;\n            retc = EINVAL;\n            viewReadLock.Release();\n            eos_static_err(\"msg=\\\"failed to run bash workflow - file gone\\\" job=\\\"%s\\\"\",\n                           mDescription.c_str());\n            Move(mActions[0].mQueue, \"g\", storetime);\n            XrdOucString log = \"workflow failed to invalid arguments - file is gone\";\n            Results(\"g\", retc, log, storetime);\n          }\n        } else {\n          storetime = 0;\n          retc = EINVAL;\n          eos_static_err(\"msg=\\\"failed to run bash workflow - executable name modifies path\\\" job=\\\"%s\\\"\",\n                         mDescription.c_str());\n          Move(mActions[0].mQueue, \"g\", storetime);\n        }\n      } else if (method == \"notify\") {\n\treturn HandleNotifyEvents(errorMsg, ininfo, args);\n      } else if (gOFS->mTapeEnabled && method == \"proto\") {\n        return HandleProtoMethodEvents(errorMsg, ininfo);\n      } else {\n        storetime = 0;\n        eos_static_err(\"msg=\\\"moving unknown workflow\\\" job=\\\"%s\\\"\",\n                       mDescription.c_str());\n        Move(mActions[0].mQueue, \"g\", storetime);\n        XrdOucString log = \"workflow is not known\";\n        Results(\"g\", EINVAL, log, storetime);\n      }\n    } else {\n      storetime = 0;\n      retc = EINVAL;\n      eos_static_err(\"msg=\\\"moving illegal workflow\\\" job=\\\"%s\\\"\",\n                     mDescription.c_str());\n      Move(mActions[0].mQueue, \"g\", storetime);\n      XrdOucString log = \"workflow illegal\";\n      Results(\"g\", retc, log, storetime);\n    }\n  } else {\n    //Delete(mActions[0].mQueue);\n  }\n\n  return retc;\n}\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Handles \"notify\" method events\n * @param errorMsg out parameter giving the text of any error\n * @args notification arguments\n * @return SFS_OK if success\n */\n/*----------------------------------------------------------------------------*/\nint WFE::Job::HandleNotifyEvents(std::string& errorMsg,\n\t\t\t\t const char* const ininfo,\n\t\t\t\t const std::string &args)\n{\n  const auto event = mActions[0].mEvent;\n  std::string fullPath;\n  std::vector<std::string> tokens;\n\n  // the syntax is: protocol:uri:port:channel:timeout\n  // undefined have to be set empty, we always expect 5 tokens = ::::\n\n  eos::common::StringConversion::EmptyTokenize(args,\n\t\t\t\t\t       tokens,\n\t\t\t\t\t       \"|\");\n\n  // check notification configuration\n  if (tokens.size() != 5) {\n    eos_static_err(\"msg=\\\"invalid notificaiton\\\" args='%s'\", args.c_str());\n    MoveWithResults(EINVAL);\n    return EINVAL;\n  }\n\n  std::string json;\n\n  try {\n    eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, mFid);\n    eos::common::RWMutexReadLock rlock(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosFileService->getFileMD(mFid);\n    fullPath = gOFS->eosView->getUri(fmd.get());\n\n    eos::rpc::MDNotification rpcresponse;\n    rpcresponse.set_type(eos::rpc::FILE);\n\n    eos::rpc::FileMdProto* rpcfmd = rpcresponse.mutable_fmd();\n    eos::rpc::RoleId* rpcrole     = rpcresponse.mutable_role();\n\n    rpcrole->set_uid(mVid.uid);\n    rpcrole->set_gid(mVid.gid);\n    rpcrole->set_username(mVid.uid_string);\n    rpcrole->set_groupname(mVid.gid_string);\n    rpcrole->set_app(mVid.app);\n\n    rpcfmd->set_name(fmd->getName());\n    rpcfmd->set_id(fmd->getId());\n    rpcfmd->set_inode(eos::common::FileId::FidToInode(fmd->getId()));\n    rpcfmd->set_cont_id(fmd->getContainerId());\n    rpcfmd->set_uid(fmd->getCUid());\n    rpcfmd->set_gid(fmd->getCGid());\n    rpcfmd->set_size(fmd->getSize());\n    rpcfmd->set_layout_id(fmd->getLayoutId());\n    rpcfmd->set_flags(fmd->getFlags());\n    rpcfmd->set_link_name(fmd->getLink());\n    eos::IFileMD::ctime_t ctime;\n    eos::IFileMD::ctime_t mtime;\n    fmd->getCTime(ctime);\n    fmd->getMTime(mtime);\n    rpcfmd->mutable_ctime()->set_sec(ctime.tv_sec);\n    rpcfmd->mutable_ctime()->set_n_sec(ctime.tv_nsec);\n    rpcfmd->mutable_mtime()->set_sec(mtime.tv_sec);\n    rpcfmd->mutable_mtime()->set_n_sec(mtime.tv_nsec);\n    rpcfmd->mutable_checksum()->set_value(\n\t\t\t\t\t fmd->getChecksum().getDataPtr(), fmd->getChecksum().size());\n    rpcfmd->mutable_checksum()->set_type(\n\t\t\t\t\teos::common::LayoutId::GetChecksumStringReal(fmd->getLayoutId()));\n    for (const auto& elem : fmd->getAttributes()) {\n      (*rpcfmd->mutable_xattrs())[elem.first] = elem.second;\n    }\n\n    // store the event type as an extended attribute\n    (*rpcfmd->mutable_xattrs())[\"notify::event\"] = event;\n\n    std::string etag;\n    eos::calculateEtag(fmd.get(), etag);\n\n    if (fmd->hasAttribute(\"sys.eos.mdino\")) {\n      etag = \"hardlink\";\n    }\n\n    rpcfmd->set_etag(etag);\n    rpcfmd->set_path(fullPath);\n\n    google::protobuf::util::JsonPrintOptions options;\n    //   options.add_whitespace = true;         // Pretty print\n    //    options.preserve_proto_field_names = true; // Use proto field names instead of camelCase\n    //    options.always_print_primitive_fields = true;\n    auto status = google::protobuf::util::MessageToJsonString(rpcresponse, &json, options);\n\n    if (!status.ok()) {\n      eos_static_err(\"msg=\\\"failed to convert GRPC to JSON\\\"\");\n      MoveWithResults(EFAULT);\n      return EFAULT;\n    }\n  } catch (eos::MDException& e) {\n    eos_static_err(\"msg=\\\"could not get metadata for file %u\\\" reason='%s'\", mFid,\n                   e.getMessage().str().c_str());\n    MoveWithResults(ENOENT);\n    return ENOENT;\n  }\n\n  eos_static_info(\"msg=\\\"web-notify\\\" method=%s uri=%s port=%s channel=%s json='%s' timeout=%s\",\n\t\t  tokens[0].c_str(),\n\t\t  tokens[1].c_str(),\n\t\t  tokens[2].c_str(),\n\t\t  tokens[3].c_str(),\n\t\t  json.c_str(),\n\t\t  tokens[4].c_str());\n  bool notified = eos::common::WebNotify::Notify(tokens[0],\n\t\t\t\t\t\t tokens[1],\n\t\t\t\t\t\t tokens[2],\n\t\t\t\t\t\t tokens[3],\n\t\t\t\t\t\t json,\n\t\t\t\t\t\t tokens[4]);\n\n  if (notified) {\n    MoveWithResults(SFS_OK);\n    return 0;\n  } else {\n    MoveWithResults(EFAULT);\n    return EFAULT;\n  }\n}\n\n\n/*----------------------------------------------------------------------------*/\n/**\n * @brief Handles \"proto\" method events\n * @param errorMsg out parameter giving the text of any error\n * @return SFS_OK if success\n */\n/*----------------------------------------------------------------------------*/\nint WFE::Job::HandleProtoMethodEvents(std::string& errorMsg,\n                                      const char* const ininfo)\n{\n  const auto event = mActions[0].mEvent;\n  std::string fullPath;\n\n  try {\n    eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, mFid);\n    eos::common::RWMutexReadLock rlock(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosFileService->getFileMD(mFid);\n    fullPath = gOFS->eosView->getUri(fmd.get());\n  } catch (eos::MDException& e) {\n    eos_static_err(\"Could not get metadata for file %u. Reason: %s\", mFid,\n                   e.getMessage().str().c_str());\n    MoveWithResults(ENOENT);\n    return ENOENT;\n  }\n\n  {\n    std::string opaqueRequestIdStr;\n\n    if (nullptr != ininfo) {\n      XrdOucEnv opaque(ininfo);\n      const char* const opaqueRequestId = opaque.Get(\"mgm.reqid\");\n\n      if (nullptr != opaqueRequestId) {\n        opaqueRequestIdStr = opaqueRequestId;\n      }\n    }\n\n    auto eventUpperCase = event;\n    std::transform(eventUpperCase.begin(), eventUpperCase.end(),\n                   eventUpperCase.begin(),\n    [](unsigned char c) {\n      return std::toupper(c);\n    }\n                  );\n    eos_static_info(\"%s %s %s %s fxid=%08llx mgm.reqid=\\\"%s\\\"\",\n                    mActions[0].mWorkflow.c_str(),\n                    eventUpperCase.c_str(),\n                    fullPath.c_str(), gOFS->ProtoWFEndPoint.c_str(), mFid,\n                    opaqueRequestIdStr.c_str());\n  }\n\n  if (event == \"sync::prepare\" || event == \"prepare\") {\n    return HandleProtoMethodPrepareEvent(fullPath, ininfo, errorMsg);\n  } else if (event == \"sync::abort_prepare\" || event == \"abort_prepare\") {\n    return HandleProtoMethodAbortPrepareEvent(fullPath, ininfo, errorMsg);\n  } else if (event == \"sync::evict_prepare\" || event == \"evict_prepare\") {\n    return HandleProtoMethodEvictPrepareEvent(fullPath, ininfo, errorMsg);\n  } else if (event == \"sync::create\" || event == \"create\") {\n    return HandleProtoMethodCreateEvent(fullPath, ininfo, errorMsg);\n  } else if (event == \"sync::delete\" || event == \"delete\") {\n    return HandleProtoMethodDeleteEvent(fullPath, errorMsg);\n  } else if (event == \"sync::closew\" || event == \"closew\") {\n    return HandleProtoMethodCloseEvent(event, fullPath, ininfo);\n  } else if (event == \"sync::archived\" || event == \"archived\") {\n    return HandleProtoMethodArchivedEvent(event, fullPath, ininfo);\n  } else if (event == RETRIEVE_FAILED_WORKFLOW_NAME) {\n    return HandleProtoMethodRetrieveFailedEvent(fullPath);\n  } else if (event == ARCHIVE_FAILED_WORKFLOW_NAME) {\n    return HandleProtoMethodArchiveFailedEvent(fullPath);\n  } else if (event == \"sync::offline\" || event == \"offline\") {\n    return HandleProtoMethodOfflineEvent(fullPath, ininfo, errorMsg);\n  } else if (event == \"sync::update_fid\" || event == \"update_fid\") {\n    return HandleProtoMethodUpdateFidEvent(fullPath, errorMsg);\n  } else {\n    eos_static_err(\"Unknown event %s for proto workflow\", event.c_str());\n    MoveWithResults(SFS_ERROR);\n    return SFS_ERROR;\n  }\n}\n\n\nint\nWFE::Job::HandleProtoMethodPrepareEvent(const std::string& fullPath,\n                                        const char* const ininfo,\n                                        std::string& errorMsg)\n{\n  EXEC_TIMING_BEGIN(\"Proto::Prepare\");\n  gOFS->MgmStats.Add(\"Proto::Prepare\", 0, 0, 1);\n  const std::string prepareRequestId = GetPrepareRequestIdFromOpaqueData(ininfo);\n  const std::string prepareActivity = GetPrepareActivityFromOpaqueData(ininfo);\n  const int prepareRc = IdempotentPrepare(fullPath, prepareRequestId,\n                                          prepareActivity, errorMsg);\n  EXEC_TIMING_END(\"Proto::Prepare\");\n  return prepareRc;\n}\n\n\nstd::string\nWFE::Job::GetPrepareRequestIdFromOpaqueData(const char* const ininfo)\n{\n  // Get the new request ID from the opaque data and add it to the list\n  XrdOucEnv opaque(ininfo);\n  const char* const prepareRequestId = opaque.Get(\"mgm.reqid\");\n\n  if (prepareRequestId == nullptr) {\n    throw_mdexception(EINVAL, \"mgm.reqid does not exist in opaque data.\");\n  } else if (*prepareRequestId == '\\0') {\n    throw_mdexception(EINVAL, \"mgm.reqid has no value set in opaque data.\");\n  }\n\n  return prepareRequestId;\n}\n\n\nstd::string\nWFE::Job::GetPrepareActivityFromOpaqueData(const char* const ininfo)\n{\n  // Get the activity from the opaque data and add it to the list\n  XrdOucEnv opaque(ininfo);\n  const char* const prepareActivity = opaque.Get(\"activity\");\n\n  if (prepareActivity == nullptr) {\n    return \"\";\n  }\n\n  return prepareActivity;\n}\n\nstd::string\nWFE::Job::GetArchiveMetadataFromOpaqueData(const char* const ininfo)\n{\n  // Get the activity from the opaque data and add it to the list\n  XrdOucEnv opaque(ininfo);\n  const char* const archiveMetadata = opaque.Get(\"archivemetadata\");\n\n  if (archiveMetadata == nullptr) {\n    return \"\";\n  }\n\n  return archiveMetadata;\n}\n\n\nint\nWFE::Job::IdempotentPrepare(const std::string& fullPath,\n                            const std::string& prepareRequestId,\n                            const std::string& prepareActivity, std::string& errorMsg)\n{\n  using namespace std::chrono;\n  struct stat buf;\n  XrdOucErrInfo errInfo;\n  bool onDisk;\n  bool onTape;\n  struct timespec ts_now;\n  eos::common::Timing::GetTimeSpec(ts_now);\n  EosCtaReporterPrepareWfe eosLog;\n  eosLog\n  .addParam(EosCtaReportParam::SEC_APP, \"tape_wfe\")\n  .addParam(EosCtaReportParam::LOG, std::string(gOFS->logId))\n  .addParam(EosCtaReportParam::PATH, fullPath)\n  .addParam(EosCtaReportParam::RUID, mVid.uid)\n  .addParam(EosCtaReportParam::RGID, mVid.gid)\n  .addParam(EosCtaReportParam::TD, mVid.tident.c_str())\n  .addParam(EosCtaReportParam::PREP_WFE_EVENT, \"stage\")\n  .addParam(EosCtaReportParam::PREP_WFE_ACTIVITY, prepareActivity)\n  .addParam(EosCtaReportParam::TS, ts_now.tv_sec)\n  .addParam(EosCtaReportParam::TNS, ts_now.tv_nsec);\n\n  // Check if we have a disk replica and if not, whether it's on tape\n  if (gOFS->_stat(fullPath.c_str(), &buf, errInfo, mVid, nullptr, nullptr,\n                  false) == 0) {\n    // Note that buf.st_mode is an unsigned integer\n    onDisk = (buf.st_mode & EOS_TAPE_MODE_T) ? buf.st_nlink > 1 : buf.st_nlink > 0;\n    onTape = (buf.st_mode & EOS_TAPE_MODE_T) != 0;\n    eosLog\n    .addParam(EosCtaReportParam::PREP_WFE_ONDISK, onDisk)\n    .addParam(EosCtaReportParam::PREP_WFE_ONTAPE, onTape);\n  } else {\n    std::stringstream err_message;\n    err_message <<\n                \"Cannot determine file and disk replicas, not doing the prepare. Reason: \" <<\n                errInfo.getErrText();\n    eos_static_err(err_message.str().c_str());\n    eosLog\n    .addParam(EosCtaReportParam::PREP_WFE_SENTTOCTA, false)\n    .addParam(EosCtaReportParam::PREP_WFE_ERROR, err_message.str());\n    MoveWithResults(EAGAIN);\n    return EAGAIN;\n  }\n\n  uid_t cuid = eos::common::VirtualIdentity::kNobodyUid;\n  gid_t cgid = eos::common::VirtualIdentity::kNobodyGid;\n\n  if (onDisk) {\n    eos_static_info(\"File %s is already on disk, nothing to prepare. Eviction counter will be incremented.\",\n                    fullPath.c_str());\n\n    try {\n      int evictionCounter = 0;\n      eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n      auto fmd = gOFS->eosFileService->getFileMD(mFid);\n\n      if (fmd->hasAttribute(RETRIEVE_EVICT_COUNTER_NAME)) {\n        evictionCounter = std::stoi(fmd->getAttribute(RETRIEVE_EVICT_COUNTER_NAME));\n      }\n\n      fmd->setAttribute(RETRIEVE_EVICT_COUNTER_NAME,\n                        std::to_string(++evictionCounter));\n      gOFS->eosView->updateFileStore(fmd.get());\n      eosLog.addParam(EosCtaReportParam::PREP_WFE_EVICTCOUNTER, evictionCounter);\n    } catch (eos::MDException& ex) {\n      std::stringstream err_message;\n      err_message << \"msg=\\\"could not update eviction counter for file \" << fullPath;\n      eos_static_err(err_message.str().c_str());\n      eosLog.addParam(EosCtaReportParam::PREP_WFE_ERROR, err_message.str());\n    }\n\n    eosLog.addParam(EosCtaReportParam::PREP_WFE_SENTTOCTA, false);\n    MoveWithResults(SFS_OK);\n    return SFS_OK;\n  } else if (!onTape) {\n    std::stringstream err_message;\n    err_message << \"File \" << fullPath <<\n                \"  is not on disk nor on tape, cannot prepare it.\";\n    eos_static_err(err_message.str().c_str());\n    eosLog\n    .addParam(EosCtaReportParam::PREP_WFE_SENTTOCTA, false)\n    .addParam(EosCtaReportParam::PREP_WFE_ERROR, err_message.str());\n    MoveWithResults(ENODATA);\n    return ENODATA;\n  } else {\n    eos::common::RWMutexWriteLock lock;\n    lock.Grab(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosFileService->getFileMD(mFid);\n    // Get the list of in-flight Prepare requests for this file (if any)\n    XattrSet prepareReqIds;\n\n    if (fmd->hasAttribute(RETRIEVE_REQID_ATTR_NAME)) {\n      prepareReqIds.deserialize(fmd->getAttribute(RETRIEVE_REQID_ATTR_NAME));\n    }\n\n    try {\n      bool isFirstPrepare = prepareReqIds.values.empty();\n\n      if (prepareReqIds.values.count(prepareRequestId.c_str()) == 0) {\n        prepareReqIds.values.insert(prepareRequestId.c_str());\n        fmd->setAttribute(RETRIEVE_REQID_ATTR_NAME, prepareReqIds.serialize());\n      }\n\n      eosLog\n      .addParam(EosCtaReportParam::PREP_WFE_FIRSTPREPARE, isFirstPrepare)\n      .addParam(EosCtaReportParam::PREP_WFE_REQID, prepareRequestId)\n      .addParam(EosCtaReportParam::PREP_WFE_REQCOUNT, prepareReqIds.values.size());\n\n      // if we are the first to retrieve the file\n      if (isFirstPrepare) {\n        fmd->setAttribute(RETRIEVE_ERROR_ATTR_NAME, \"\");\n        // Read these attributes here to optimize locking\n        cuid = fmd->getCUid();\n        cgid = fmd->getCGid();\n        gOFS->eosView->updateFileStore(fmd.get());\n      } else {\n        eos_static_info(\"File %s is already being retrieved by %u clients.\",\n                        fullPath.c_str(), prepareReqIds.values.size() - 1);\n        eosLog.addParam(EosCtaReportParam::PREP_WFE_SENTTOCTA, false);\n        MoveWithResults(SFS_OK);\n        return SFS_OK;\n      }\n    } catch (eos::MDException& ex) {\n      lock.Release();\n      std::stringstream err_message;\n      err_message << \"Could not write attributes \" << RETRIEVE_REQID_ATTR_NAME <<\n                  \" and \" << RETRIEVE_ERROR_ATTR_NAME\n                  << \" for file \" << fullPath.c_str() << \". Not doing the retrieve.\";\n      eos_static_err(err_message.str().c_str());\n      eosLog\n      .addParam(EosCtaReportParam::PREP_WFE_SENTTOCTA, false)\n      .addParam(EosCtaReportParam::PREP_WFE_ERROR, err_message.str());\n      MoveWithResults(EAGAIN);\n      return EAGAIN;\n    }\n  }\n\n  // If we reached this point: the file is not on disk, it is on tape, and this is the first Prepare\n  // request for this file. Proceed with issuing the Prepare request to the tape back-end.\n  cta::xrd::Request request;\n  auto notification = request.mutable_notification();\n  notification->mutable_cli()->mutable_user()->set_username(GetUserName(\n        mVid.uid));\n  notification->mutable_cli()->mutable_user()->set_groupname(GetGroupName(\n        mVid.gid));\n  auto xAttrs = CollectAttributes(fullPath);\n\n  for (const auto& attribute : xAttrs) {\n    google::protobuf::MapPair<std::string, std::string> attr(attribute.first,\n        attribute.second);\n    notification->mutable_file()->mutable_xattr()->insert(attr);\n\n    // need to set the storage_class and archive_file_id attributes (not just the extended ones)\n    if (attribute.first == ARCHIVE_STORAGE_CLASS_ATTR_NAME) {\n      notification->mutable_file()->set_storage_class(attribute.second);\n    }\n    if (attribute.first == ARCHIVE_FILE_ID_ATTR_NAME) {\n      notification->mutable_file()->set_archive_file_id(std::strtoul(attribute.second.c_str(), nullptr, 10));\n    }\n  }\n\n  if (prepareActivity.length()) {\n    google::protobuf::MapPair<std::string, std::string> attr(\"activity\",\n        prepareActivity);\n    notification->mutable_file()->mutable_xattr()->insert(attr);\n  }\n\n  notification->mutable_wf()->set_event(cta::eos::Workflow::PREPARE);\n  notification->mutable_file()->set_lpath(fullPath);\n  notification->mutable_wf()->mutable_instance()->set_name(\n    gOFS->MgmOfsInstanceName.c_str());\n  notification->mutable_file()->mutable_owner()->set_uid(cuid);\n  notification->mutable_file()->mutable_owner()->set_gid(cgid);\n  notification->mutable_file()->set_disk_file_id(std::to_string(mFid));\n\n  if (xAttrs.count(ARCHIVE_FILE_ID_ATTR_NAME)) {\n    notification->mutable_file()->set_archive_file_id\n    (CtaUtils::toUint64(xAttrs[ARCHIVE_FILE_ID_ATTR_NAME]));\n  }\n\n  if (xAttrs.count(ARCHIVE_STORAGE_CLASS_ATTR_NAME)) {\n    notification->mutable_file()->set_storage_class(\n      xAttrs[ARCHIVE_STORAGE_CLASS_ATTR_NAME]);\n  }\n\n  if (xAttrs.count(EOS_BTIME)) {\n    eos::IFileMD::ctime_t btime {0, 0};\n    Timing::Timespec_from_TimespecStr(xAttrs[EOS_BTIME], btime);\n    notification->mutable_file()->mutable_btime()->set_sec(btime.tv_sec);\n    notification->mutable_file()->mutable_btime()->set_nsec(btime.tv_nsec);\n  }\n\n  auto fxidString = StringConversion::FastUnsignedToAsciiHex(mFid);\n  std::ostringstream destStream;\n  std::string mgmHostName;\n\n  if (gOFS->MgmOfsAlias.length()) {\n    mgmHostName = gOFS->MgmOfsAlias.c_str();\n  } else if (gOFS->HostName != nullptr) {\n    mgmHostName = gOFS->HostName;\n  } else {\n    std::stringstream err_message;\n    err_message <<\n                \"IdempotentPrepare() failed: Could not determine the value of mgmHostName\";\n    eos_static_err(err_message.str().c_str());\n    eosLog\n    .addParam(EosCtaReportParam::PREP_WFE_SENTTOCTA, false)\n    .addParam(EosCtaReportParam::PREP_WFE_ERROR, err_message.str());\n    MoveWithResults(ENODATA);\n    return ENODATA;\n  }\n\n  eosLog.addParam(EosCtaReportParam::HOST, mgmHostName);\n  destStream << \"root://\" << mgmHostName << \"/\" << fullPath << \"?eos.lfn=fxid:\"\n             << fxidString;\n  destStream << \"&eos.ruid=0&eos.rgid=0&eos.injection=1&eos.workflow=\" <<\n             RETRIEVE_WRITTEN_WORKFLOW_NAME <<\n             \"&eos.space=\" << gOFS->mPrepareDestSpace;\n  notification->mutable_transport()->set_dst_url(destStream.str());\n  std::ostringstream errorReportStream;\n  errorReportStream << \"eosQuery://\" << mgmHostName\n                    << \"//eos/wfe/passwd?mgm.pcmd=event&mgm.fid=\" << fxidString\n                    << \"&mgm.logid=cta&mgm.event=\" << RETRIEVE_FAILED_WORKFLOW_NAME\n                    << \"&mgm.workflow=default&mgm.path=/dummy_path&mgm.ruid=0&mgm.rgid=0&mgm.errmsg=\";\n  notification->mutable_transport()->set_error_report_url(\n    errorReportStream.str());\n  auto sendResult = SendProtoWFRequest(this, fullPath, request, errorMsg);\n  // Create timestamp\n  std::string ctimestr(std::to_string(system_clock::to_time_t(\n                                        system_clock::now())));\n\n  if (sendResult == 0) {\n    // Update the timestamp of the last Prepare request that was successfully sent\n    eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosFileService->getFileMD(mFid);\n\n    try {\n      fmd->setAttribute(RETRIEVE_REQTIME_ATTR_NAME, ctimestr);\n      gOFS->eosView->updateFileStore(fmd.get());\n    } catch (eos::MDException& ex) {\n      // fail silently if we couldn't update the timestamp\n    }\n  } else {\n    if (errorMsg.empty()) {\n      errorMsg = \"Prepare handshake failed\";\n    }\n\n    std::string errorMsgAttr = ctimestr + \" -> \" + errorMsg;\n    eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosFileService->getFileMD(mFid);\n\n    try {\n      // Delete the request ID from the extended attributes so it can be retried\n      fmd->setAttribute(RETRIEVE_REQID_ATTR_NAME, \"\");\n      fmd->setAttribute(RETRIEVE_REQTIME_ATTR_NAME, \"\");\n      fmd->setAttribute(RETRIEVE_ERROR_ATTR_NAME, errorMsgAttr);\n      gOFS->eosView->updateFileStore(fmd.get());\n    } catch (eos::MDException& ex) {}\n  }\n\n  eosLog.addParam(EosCtaReportParam::PREP_WFE_SENTTOCTA, true);\n  return sendResult;\n}\n\n\nint\nWFE::Job::HandleProtoMethodAbortPrepareEvent(const std::string& fullPath,\n    const char* const ininfo,\n    std::string& errorMsg)\n{\n  EXEC_TIMING_BEGIN(\"Proto::Prepare::Abort\");\n  gOFS->MgmStats.Add(\"Proto::Prepare::Abort\", 0, 0, 1);\n  EosCtaReporterPrepareWfe eosLog;\n  eosLog\n  .addParam(EosCtaReportParam::SEC_APP, \"tape_wfe\")\n  .addParam(EosCtaReportParam::LOG, std::string(gOFS->logId))\n  .addParam(EosCtaReportParam::PATH, fullPath)\n  .addParam(EosCtaReportParam::RUID, mVid.uid)\n  .addParam(EosCtaReportParam::RGID, mVid.gid)\n  .addParam(EosCtaReportParam::TD, mVid.tident.c_str())\n  .addParam(EosCtaReportParam::PREP_WFE_EVENT, \"abort\");\n  XattrSet prepareReqIds;\n  {\n    eos::common::RWMutexWriteLock lock;\n    lock.Grab(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosFileService->getFileMD(mFid);\n\n    try {\n      if (fmd->hasAttribute(RETRIEVE_REQID_ATTR_NAME)) {\n        prepareReqIds.deserialize(fmd->getAttribute(RETRIEVE_REQID_ATTR_NAME));\n      }\n\n      eosLog.addParam(EosCtaReportParam::PREP_WFE_REQCOUNT, prepareReqIds.values.size());\n    } catch (...) {\n      lock.Release();\n      std::stringstream err_message;\n      err_message << \"Could not determine ongoing retrieves for file \" <<\n                  fullPath.c_str() << \". Check the \"\n                  << RETRIEVE_REQID_ATTR_NAME << \" extended attribute\";\n      eos_static_err(err_message.str().c_str());\n      eosLog.addParam(EosCtaReportParam::PREP_WFE_ERROR, err_message.str());\n      MoveWithResults(EAGAIN);\n      return EAGAIN;\n    }\n\n    std::string opaqueRequestIdStr;\n\n    try {\n      // Remove the request ID from the list in the Xattr\n      XrdOucEnv opaque(ininfo);\n      const char* const opaqueRequestId = opaque.Get(\"mgm.reqid\");\n\n      if (nullptr != opaqueRequestId) {\n        opaqueRequestIdStr = opaqueRequestId;\n      }\n\n      if (opaqueRequestId == nullptr) {\n        throw_mdexception(EINVAL, \"mgm.reqid does not exist in opaque data.\");\n      } else if (*opaqueRequestId == '\\0') {\n        throw_mdexception(EINVAL, \"mgm.reqid has no value set in opaque data.\");\n      }\n\n      eosLog.addParam(EosCtaReportParam::PREP_WFE_REQID, opaqueRequestId);\n\n      if (prepareReqIds.values.erase(opaqueRequestId) != 1) {\n        throw_mdexception(EINVAL, \"Request ID not found in extended attributes\");\n      }\n\n      fmd->setAttribute(RETRIEVE_REQID_ATTR_NAME, prepareReqIds.serialize());\n      gOFS->eosView->updateFileStore(fmd.get());\n    } catch (eos::MDException& ex) {\n      lock.Release();\n      std::stringstream err_message;\n      err_message << \"Error accessing attribute \" << RETRIEVE_REQID_ATTR_NAME <<\n                  \" for file \" << fullPath.c_str() << \". \"\n                  << \"Not doing the abort retrieve.\";\n      eos_static_err(err_message.str().c_str());\n      eosLog.addParam(EosCtaReportParam::PREP_WFE_ERROR, err_message.str());\n      MoveWithResults(EAGAIN);\n      return EAGAIN;\n    }\n  }\n\n  if (!prepareReqIds.values.empty()) {\n    // There are other pending Prepare requests on this file, just return OK\n    eosLog.addParam(EosCtaReportParam::PREP_WFE_SENTTOCTA, false);\n    MoveWithResults(SFS_OK);\n    return SFS_OK;\n  }\n\n  // optimization for reduced memory IO during write lock\n  cta::xrd::Request request;\n  auto notification = request.mutable_notification();\n  notification->mutable_cli()->mutable_user()->set_username(GetUserName(\n        mVid.uid));\n  notification->mutable_cli()->mutable_user()->set_groupname(GetGroupName(\n        mVid.gid));\n  auto xAttrs = CollectAttributes(fullPath);\n\n  for (const auto& attribute : xAttrs) {\n    google::protobuf::MapPair<std::string, std::string> attr(attribute.first,\n        attribute.second);\n    notification->mutable_file()->mutable_xattr()->insert(attr);\n  }\n\n  uid_t cuid = eos::common::VirtualIdentity::kNobodyUid;\n  gid_t cgid = eos::common::VirtualIdentity::kNobodyGid;\n  {\n    eos::common::RWMutexReadLock rlock(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosFileService->getFileMD(mFid);\n    cuid = fmd->getCUid();\n    cgid = fmd->getCGid();\n  }\n  notification->mutable_file()->mutable_owner()->set_uid(cuid);\n  notification->mutable_file()->mutable_owner()->set_gid(cgid);\n  notification->mutable_wf()->set_event(cta::eos::Workflow::ABORT_PREPARE);\n  notification->mutable_file()->set_lpath(fullPath);\n  notification->mutable_wf()->mutable_instance()->set_name(\n    gOFS->MgmOfsInstanceName.c_str());\n  notification->mutable_file()->set_disk_file_id(std::to_string(mFid));\n\n  if (xAttrs.count(ARCHIVE_FILE_ID_ATTR_NAME)) {\n    notification->mutable_file()->set_archive_file_id\n    (CtaUtils::toUint64(xAttrs[ARCHIVE_FILE_ID_ATTR_NAME]));\n  }\n\n  if (xAttrs.count(ARCHIVE_STORAGE_CLASS_ATTR_NAME)) {\n    notification->mutable_file()->set_storage_class(\n      xAttrs[ARCHIVE_STORAGE_CLASS_ATTR_NAME]);\n  }\n\n  if (xAttrs.count(EOS_BTIME)) {\n    eos::IFileMD::ctime_t btime {0, 0};\n    Timing::Timespec_from_TimespecStr(xAttrs[EOS_BTIME], btime);\n    notification->mutable_file()->mutable_btime()->set_sec(btime.tv_sec);\n    notification->mutable_file()->mutable_btime()->set_nsec(btime.tv_nsec);\n  }\n\n  auto s_ret = SendProtoWFRequest(this, fullPath, request, errorMsg);\n\n  if (s_ret == 0) {\n    eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosFileService->getFileMD(mFid);\n\n    try {\n      // All Prepare requests cancelled by the user:\n      // Delete the request time and error message from the extended attributes\n      fmd->setAttribute(RETRIEVE_REQTIME_ATTR_NAME, \"\");\n      fmd->setAttribute(RETRIEVE_ERROR_ATTR_NAME, \"\");\n      gOFS->eosView->updateFileStore(fmd.get());\n    } catch (eos::MDException& ex) {}\n  }\n\n  eosLog.addParam(EosCtaReportParam::PREP_WFE_SENTTOCTA, true);\n  EXEC_TIMING_END(\"Proto::Prepare::Abort\");\n  return s_ret;\n}\n\nint\nWFE::Job::HandleProtoMethodEvictPrepareEvent(const std::string& fullPath,\n    const char* const ininfo,\n    std::string& errorMsg)\n{\n  using namespace std::chrono;\n  struct stat buf;\n  XrdOucErrInfo errInfo;\n  bool onDisk;\n  bool onTape;\n  EosCtaReporterPrepareWfe eosLog;\n  eosLog\n  .addParam(EosCtaReportParam::SEC_APP, \"tape_wfe\")\n  .addParam(EosCtaReportParam::LOG, std::string(gOFS->logId))\n  .addParam(EosCtaReportParam::PATH, fullPath)\n  .addParam(EosCtaReportParam::RUID, mVid.uid)\n  .addParam(EosCtaReportParam::RGID, mVid.gid)\n  .addParam(EosCtaReportParam::TD, mVid.tident.c_str())\n  .addParam(EosCtaReportParam::PREP_WFE_EVENT, \"evict\");\n  EXEC_TIMING_BEGIN(\"Proto::EvictPrepare\");\n  gOFS->MgmStats.Add(\"Proto::EvictPrepare\", 0, 0, 1);\n  std::ostringstream preamble;\n  preamble << \"fxid=\" << std::hex << mFid << \" file=\" << fullPath;\n\n  // Check if we have a disk replica and if not, whether it's on tape\n  if (gOFS->_stat(fullPath.c_str(), &buf, errInfo, mVid, nullptr, nullptr,\n                  false) == 0) {\n    onDisk = ((buf.st_mode & EOS_TAPE_MODE_T) ? buf.st_nlink - 1 : buf.st_nlink) >\n             0;\n    onTape = (buf.st_mode & EOS_TAPE_MODE_T) != 0;\n    eosLog\n    .addParam(EosCtaReportParam::PREP_WFE_ONDISK, onDisk)\n    .addParam(EosCtaReportParam::PREP_WFE_ONTAPE, onTape);\n  } else {\n    std::ostringstream msg;\n    msg << preamble.str() <<\n        \" msg=\\\"Cannot determine file and disk replicas, not doing the evict. Reason: \"\n        << errInfo.getErrText() << \"\\\"\";\n    eos_static_err(msg.str().c_str());\n    eosLog.addParam(EosCtaReportParam::PREP_WFE_ERROR, msg.str());\n    MoveWithResults(EAGAIN);\n    return EAGAIN;\n  }\n\n  if (!onDisk) {\n    std::ostringstream msg;\n    msg << preamble.str() << \" msg=\\\"File is not on disk, nothing to evict.\\\"\";\n    eos_static_info(msg.str().c_str());\n  } else if (!onTape) {\n    std::ostringstream msg;\n    msg << preamble.str() << \" msg=\\\"File is not on tape, cannot evict it.\\\"\";\n    eos_static_err(msg.str().c_str());\n    eosLog.addParam(EosCtaReportParam::PREP_WFE_ERROR, msg.str());\n    MoveWithResults(ENODATA);\n    return ENODATA;\n  } else {\n    const auto result = EvictAsRoot(mFid);\n\n    if (0 == result.retc()) {\n      std::ostringstream msg;\n      msg << preamble.str() <<\n          \" msg=\\\"Successfully issued evict for evict_prepare event\\\"\";\n      eos_static_info(msg.str().c_str());\n    } else {\n      std::ostringstream msg;\n      msg << preamble.str() <<\n          \" msg=\\\"Failed to issue evict for evict_prepare event\\\"\";\n      eos_static_info(msg.str().c_str());\n      eosLog.addParam(EosCtaReportParam::PREP_WFE_ERROR, msg.str());\n      MoveWithResults(EAGAIN);\n      return EAGAIN;\n    }\n  }\n\n  MoveWithResults(SFS_OK);\n  EXEC_TIMING_END(\"Proto::EvictPrepare\");\n  return SFS_OK;\n}\n\nint\nWFE::Job::HandleProtoMethodCreateEvent(const std::string& fullPath,\n                                       const char* const ininfo,\n                                       std::string& errorMsg)\n{\n  EXEC_TIMING_BEGIN(\"Proto::Create\");\n  gOFS->MgmStats.Add(\"Proto::Create\", 0, 0, 1);\n  cta::xrd::Request request;\n  EosCtaReporterFileCreation eosLog;\n  struct timespec ts_now;\n  eos::common::Timing::GetTimeSpec(ts_now);\n  auto notification = request.mutable_notification();\n  notification->mutable_cli()->mutable_user()->set_username(GetUserName(\n        mVid.uid));\n  notification->mutable_cli()->mutable_user()->set_groupname(GetGroupName(\n        mVid.gid));\n  auto xAttrs = CollectAttributes(fullPath);\n\n  const std::string archiveMetadata = GetArchiveMetadataFromOpaqueData(ininfo);\n  eosLog\n      .addParam(EosCtaReportParam::SEC_APP, \"tape_create\")\n      .addParam(EosCtaReportParam::LOG, std::string(gOFS->logId))\n      .addParam(EosCtaReportParam::PATH, fullPath)\n      .addParam(EosCtaReportParam::RUID, mVid.uid)\n      .addParam(EosCtaReportParam::RGID, mVid.gid)\n      .addParam(EosCtaReportParam::TD, mVid.tident.c_str())\n      .addParam(EosCtaReportParam::TS, ts_now.tv_sec)\n      .addParam(EosCtaReportParam::TNS, ts_now.tv_nsec)\n      .addParam(EosCtaReportParam::FILE_CREATE_ARCHIVE_METADATA, archiveMetadata);\n\n  for (const auto& attribute : xAttrs) {\n    google::protobuf::MapPair<std::string, std::string> attr(attribute.first,\n        attribute.second);\n    notification->mutable_file()->mutable_xattr()->insert(attr);\n  }\n\n  uid_t cuid = eos::common::VirtualIdentity::kNobodyUid;\n  gid_t cgid = eos::common::VirtualIdentity::kNobodyGid;\n  {\n    eos::common::RWMutexReadLock rlock(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosFileService->getFileMD(mFid);\n    cuid = fmd->getCUid();\n    cgid = fmd->getCGid();\n    eosLog\n        .addParam(EosCtaReportParam::FILE_CREATE_FID, fmd->getId())\n        .addParam(EosCtaReportParam::FILE_CREATE_FXID,\n                  eos::common::FileId::Fid2Hex(fmd->getId()).c_str());\n  }\n  notification->mutable_file()->mutable_owner()->set_uid(cuid);\n  notification->mutable_file()->mutable_owner()->set_gid(cgid);\n  notification->mutable_wf()->set_event(cta::eos::Workflow::CREATE);\n  notification->mutable_wf()->mutable_instance()->set_name(\n    gOFS->MgmOfsInstanceName.c_str());\n  notification->mutable_file()->set_lpath(fullPath);\n  notification->mutable_file()->set_disk_file_id(std::to_string(mFid));\n\n  if (xAttrs.count(ARCHIVE_FILE_ID_ATTR_NAME)) {\n    notification->mutable_file()->set_archive_file_id\n    (CtaUtils::toUint64(xAttrs[ARCHIVE_FILE_ID_ATTR_NAME]));\n  }\n\n  if (xAttrs.count(ARCHIVE_STORAGE_CLASS_ATTR_NAME)) {\n    notification->mutable_file()->set_storage_class(\n      xAttrs[ARCHIVE_STORAGE_CLASS_ATTR_NAME]);\n  }\n\n  if (xAttrs.count(EOS_BTIME)) {\n    eos::IFileMD::ctime_t btime {0, 0};\n    Timing::Timespec_from_TimespecStr(xAttrs[EOS_BTIME], btime);\n    notification->mutable_file()->mutable_btime()->set_sec(btime.tv_sec);\n    notification->mutable_file()->mutable_btime()->set_nsec(btime.tv_nsec);\n    eosLog.addParam(EosCtaReportParam::FILE_CREATE_EOS_BTIME, xAttrs.count(EOS_BTIME));\n  }\n\n  auto s_ret = SendProtoWFRequest(this, fullPath, request, errorMsg);\n  EXEC_TIMING_END(\"Proto::Create\");\n  return s_ret;\n}\n\nint\nWFE::Job::HandleProtoMethodDeleteEvent(const std::string& fullPath,\n                                       std::string& errorMsg)\n{\n  EXEC_TIMING_BEGIN(\"Proto::Delete\");\n  gOFS->MgmStats.Add(\"Proto::Delete\", 0, 0, 1);\n  cta::xrd::Request request;\n  auto notification = request.mutable_notification();\n  notification->mutable_cli()->mutable_user()->set_username(GetUserName(\n        mVid.uid));\n  notification->mutable_cli()->mutable_user()->set_groupname(GetGroupName(\n        mVid.gid));\n  auto xAttrs = CollectAttributes(fullPath);\n\n  for (const auto& attribute : xAttrs) {\n    google::protobuf::MapPair<std::string, std::string> attr(attribute.first,\n        attribute.second);\n    notification->mutable_file()->mutable_xattr()->insert(attr);\n  }\n\n  notification->mutable_wf()->set_event(cta::eos::Workflow::DELETE);\n  notification->mutable_wf()->mutable_instance()->set_name(\n    gOFS->MgmOfsInstanceName.c_str());\n  notification->mutable_file()->set_lpath(fullPath);\n  notification->mutable_file()->set_disk_file_id(std::to_string(mFid));\n\n  if (xAttrs.count(ARCHIVE_FILE_ID_ATTR_NAME)) {\n    notification->mutable_file()->set_archive_file_id\n    (CtaUtils::toUint64(xAttrs[ARCHIVE_FILE_ID_ATTR_NAME]));\n  }\n\n  if (xAttrs.count(ARCHIVE_STORAGE_CLASS_ATTR_NAME)) {\n    notification->mutable_file()->set_storage_class(\n      xAttrs[ARCHIVE_STORAGE_CLASS_ATTR_NAME]);\n  }\n\n  if (xAttrs.count(EOS_BTIME)) {\n    eos::IFileMD::ctime_t btime {0, 0};\n    Timing::Timespec_from_TimespecStr(xAttrs[EOS_BTIME], btime);\n    notification->mutable_file()->mutable_btime()->set_sec(btime.tv_sec);\n    notification->mutable_file()->mutable_btime()->set_nsec(btime.tv_nsec);\n  }\n\n  // IMPORTANT\n  // Remove the tape location from the EOS namespace before actually deleting\n  // the tape file(s). Doing these operations the other way around could result\n  // in failing to remove the tape location of the file from the EOS namespace\n  // after successfully deleting the actual tape files.  This would give the end\n  // user a false sense of security that their tape file still exists when they\n  // list it. This would be considered as data loss by the end user.\n  // However, before deleting the file from the EOS namespace, we should log its contents.\n  // This provides a way to recover the namespace after an unintended deletion of a file on tape.\n  uint64_t file_size;\n  {\n    struct timespec ts_now;\n    eos::common::Timing::GetTimeSpec(ts_now);\n    EosCtaReporterFileDeletion eosLog;\n    auto fmd = gOFS->eosFileService->getFileMD(mFid);\n    auto locations = fmd->getLocations();\n    std::ostringstream locationsOStream;\n    bool streamEmpty = true;\n\n    for (auto& location : fmd->getLocations()) {\n      locationsOStream << (streamEmpty ? \"\" : \",\") << location;\n      streamEmpty = false;\n    }\n\n    file_size = fmd->getSize();\n    // Add File Size to notification\n    notification->mutable_file()->set_size(file_size);\n    std::string checksum;\n    eos::appendChecksumOnStringAsHex(fmd.get(), checksum);\n    eosLog.addParam(EosCtaReportParam::SEC_APP, \"tape_delete\")\n    .addParam(EosCtaReportParam::LOG, std::string(gOFS->logId))\n    .addParam(EosCtaReportParam::PATH, fullPath)\n    .addParam(EosCtaReportParam::RUID, mVid.uid)\n    .addParam(EosCtaReportParam::RGID, mVid.gid)\n    .addParam(EosCtaReportParam::TD, mVid.tident.c_str())\n    .addParam(EosCtaReportParam::TS, ts_now.tv_sec)\n    .addParam(EosCtaReportParam::TNS, ts_now.tv_nsec)\n    .addParam(EosCtaReportParam::FILE_DEL_FID, fmd->getId())\n    .addParam(EosCtaReportParam::FILE_DEL_FXID,\n              eos::common::FileId::Fid2Hex(fmd->getId()).c_str())\n    .addParam(EosCtaReportParam::FILE_DEL_EOS_BTIME,\n              xAttrs.count(EOS_BTIME) ? xAttrs[EOS_BTIME] : \"\")\n    .addParam(EosCtaReportParam::FILE_DEL_ARCHIVE_FILE_ID,\n              xAttrs.count(ARCHIVE_FILE_ID_ATTR_NAME) ? xAttrs[ARCHIVE_FILE_ID_ATTR_NAME] :\n              \"\")\n    .addParam(EosCtaReportParam::FILE_DEL_ARCHIVE_STORAGE_CLASS,\n              xAttrs.count(ARCHIVE_STORAGE_CLASS_ATTR_NAME) ?\n              xAttrs[ARCHIVE_STORAGE_CLASS_ATTR_NAME] : \"\")\n    .addParam(EosCtaReportParam::FILE_DEL_LOCATIONS, locationsOStream.str())\n    .addParam(EosCtaReportParam::FILE_DEL_CHECKSUMTYPE,\n              eos::common::LayoutId::GetChecksumString(fmd->getLayoutId()))\n    .addParam(EosCtaReportParam::FILE_DEL_CHECKSUMVALUE, checksum)\n    .addParam(EosCtaReportParam::FILE_DEL_SIZE, fmd->getSize());\n    // Add checksum to the notification\n    CtaCommon::SetChecksum(notification->mutable_file()->mutable_csb()->add_cs(),\n                           fmd->getLayoutId(), checksum);\n  }\n  bool tapeLocationWasRemoved = false;\n\n  try {\n    // remove tape location\n    eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosFileService->getFileMD(mFid);\n    fmd->unlinkLocation(TAPE_FS_ID);\n    fmd->removeLocation(TAPE_FS_ID);\n    gOFS->eosView->updateFileStore(fmd.get());\n    tapeLocationWasRemoved = true;\n  } catch (eos::MDException& ex) {\n    eos_static_err(\"msg=\\\"Failed to unlink tape location for file %s\",\n                   fullPath.c_str());\n  }\n\n  if (tapeLocationWasRemoved) {\n    if (xAttrs.count(ARCHIVE_FILE_ID_ATTR_NAME)) {\n      const int sendRc = SendProtoWFRequest(this, fullPath, request, errorMsg);\n\n      if (SFS_OK != sendRc) {\n        // The EOS namespace can ignore the failed deletion of the tape file(s) as this only generates dark data tape which will be picked up later\n        eos_static_err(\"msg=\\\"Failed to notify protocol buffer endpoint about the deletion of file %s: %s\\\" sendRc=%d\",\n                       fullPath.c_str(), errorMsg.c_str(), sendRc);\n      }\n    } else {\n      if (file_size == 0) {\n        eos_static_warning(\n          \"msg=\\\"File size is zero and attribute sys.archive.file_id not found. Not sending deletion request to CTA.\\\"\");\n      } else {\n        eos_static_err(\n          \"msg=\\\"File size is %d but attribute sys.archive.file_id not found. Not sending deletion request to CTA.\\\"\",\n          file_size);\n      }\n    }\n  }\n\n  EXEC_TIMING_END(\"Proto::Delete\");\n  return SFS_OK; // Ignore any failure in notifying the protocol buffer endpoint\n}\n\nint\nWFE::Job::HandleProtoMethodCloseEvent(const std::string& event,\n                                      const std::string& fullPath,\n                                      const char* const ininfo)\n{\n  EXEC_TIMING_BEGIN(\"Proto::Close\");\n  gOFS->MgmStats.Add(\"Proto::Close\", 0, 0, 1);\n\n  if (mActions[0].mWorkflow == RETRIEVE_WRITTEN_WORKFLOW_NAME) {\n    resetRetrieveIdListAndErrorMsg(fullPath);\n  }\n\n  {\n    XrdOucEnv opaque(ininfo);\n    const char* const archive_req_id = opaque.Get(\"mgm.archive_req_id\");\n\n    if (archive_req_id != nullptr && *archive_req_id != '\\0') {\n      try {\n        eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n        auto fmd = gOFS->eosFileService->getFileMD(mFid);\n        fmd->setAttribute(CTA_OBJECTSTORE_ARCHIVE_REQ_ID_NAME, archive_req_id);\n        gOFS->eosView->updateFileStore(fmd.get());\n      } catch (std::exception& se) {\n        eos_static_err(\"msg=\\\"Failed to set xattr: %s\\\" path=\\\"%s\\\" xattr_name=\\\"%s\\\" xattr_value=\\\"%s\\\"\",\n                       se.what(), fullPath.c_str(), CTA_OBJECTSTORE_ARCHIVE_REQ_ID_NAME,\n                       archive_req_id);\n      } catch (...) {\n        eos_static_err(\"msg=\\\"Failed to set xattr: Caught an unknown exception\\\" path=\\\"%s\\\" xattr_name=\\\"%s\\\"\"\n                       \" xattr_value=\\\"%s\\\"\", fullPath.c_str(), CTA_OBJECTSTORE_ARCHIVE_REQ_ID_NAME,\n                       archive_req_id);\n      }\n    }\n  }\n\n  MoveWithResults(SFS_OK);\n  EXEC_TIMING_END(\"Proto::Close\");\n  return SFS_OK;\n}\n\nvoid\nWFE::Job::resetRetrieveIdListAndErrorMsg(const std::string& fullPath)\n{\n  std::string errorMsg;\n\n  try {\n    eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n    XattrSet prepareReqIds;\n    auto fmd = gOFS->eosFileService->getFileMD(mFid);\n\n    if (fmd->hasAttribute(RETRIEVE_REQID_ATTR_NAME)) {\n      prepareReqIds.deserialize(fmd->getAttribute(RETRIEVE_REQID_ATTR_NAME));\n    }\n\n    int evictionCounter = prepareReqIds.values.size();\n    fmd->setAttribute(RETRIEVE_REQID_ATTR_NAME, \"\");\n    fmd->setAttribute(RETRIEVE_REQTIME_ATTR_NAME, \"\");\n    fmd->setAttribute(RETRIEVE_ERROR_ATTR_NAME, \"\");\n    fmd->setAttribute(RETRIEVE_EVICT_COUNTER_NAME, std::to_string(evictionCounter));\n    fmd->removeAttribute(CTA_OBJECTSTORE_REQ_ID_NAME);\n    gOFS->eosView->updateFileStore(fmd.get());\n    return;\n  } catch (std::exception& se) {\n    errorMsg = se.what();\n  } catch (...) {\n    errorMsg = \"Caught an unknown exception\";\n  }\n\n  // Reaching this point means an exception was thrown and caught\n  eos_static_err(\"Could not reset retrieves counter and error attribute for file %s: %s\",\n                 fullPath.c_str(), errorMsg.c_str());\n}\n\nint\nWFE::Job::HandleProtoMethodArchivedEvent(const std::string& event,\n    const std::string& fullPath,\n    const char* const ininfo)\n{\n  EXEC_TIMING_BEGIN(\"Proto::Archive\");\n  gOFS->MgmStats.Add(\"Proto::Archive\", 0, 0, 1);\n  std::string xattrCtaArchiveFileId;\n  bool hasXattrCtaArchiveFileId = false;\n  {\n    eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosFileService->getFileMD(mFid);\n    hasXattrCtaArchiveFileId = fmd->hasAttribute(ARCHIVE_FILE_ID_ATTR_NAME);\n\n    if (hasXattrCtaArchiveFileId) {\n      xattrCtaArchiveFileId = fmd->getAttribute(ARCHIVE_FILE_ID_ATTR_NAME);\n    }\n  }\n  bool onlyTapeCopy = false;\n  {\n    eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosFileService->getFileMD(mFid);\n    onlyTapeCopy = fmd->hasLocation(TAPE_FS_ID) && fmd->getLocations().size() == 1;\n  }\n  XrdOucEnv opaque(ininfo);\n  const char* const opaqueCtaArchiveFileId = opaque.Get(\"cta_archive_file_id\");\n\n  if (event == \"archived\") {\n    eos_static_err(\"The archived message for file %s is asynchronous when it should be synchronous.\"\n                   \" Ignoring request\", fullPath.c_str());\n  } else if (onlyTapeCopy) {\n    eos_static_info(\"File %s already has a tape copy. Ignoring request.\",\n                    fullPath.c_str());\n  } else if (!hasXattrCtaArchiveFileId) {\n    eos_static_err(\"File %s does not have the %s attribute. Ignoring request.\",\n                   fullPath.c_str(), ARCHIVE_FILE_ID_ATTR_NAME);\n  } else if (xattrCtaArchiveFileId.empty()) {\n    eos_static_err(\"The %s attribute of file %s is an empty string. Ignoring request.\",\n                   ARCHIVE_FILE_ID_ATTR_NAME, fullPath.c_str());\n  } else if (nullptr == opaqueCtaArchiveFileId) {\n    eos_static_err(\"The opaque data of the archived message for file %s does not contain cta_archive_file_id.\"\n                   \" Ignoring request.\", fullPath.c_str());\n  } else if (xattrCtaArchiveFileId != opaqueCtaArchiveFileId) {\n    eos_static_err(\"The %s attribute of file %s does not match cta_archive_file_id in the\"\n                   \" opaque data of the archived message. xattrCtaArchiveFileId=%s opaqueCtaArchiveFileId=%s.\"\n                   \" Ignoring request.\",\n                   ARCHIVE_FILE_ID_ATTR_NAME, fullPath.c_str(), xattrCtaArchiveFileId.c_str(),\n                   opaqueCtaArchiveFileId);\n  } else {\n    eos::common::VirtualIdentity root_vid = eos::common::VirtualIdentity::Root();\n    {\n      eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n      auto fmd = gOFS->eosFileService->getFileMD(mFid);\n\n      try {\n        fmd->addLocation(TAPE_FS_ID);\n        // Reset the error message\n        fmd->setAttribute(ARCHIVE_ERROR_ATTR_NAME, \"\");\n        //Reset the CTA archive request objectstore ID\n        fmd->setAttribute(CTA_OBJECTSTORE_ARCHIVE_REQ_ID_NAME, \"\");\n        gOFS->eosView->updateFileStore(fmd.get());\n      } catch (eos::MDException& ex) {\n        // fail silently - file could have been removed already\n      }\n    }\n    std::string tgcSpace = \"default\";\n    {\n      eos::common::RWMutexReadLock fsLock(FsView::gFsView.ViewMutex);\n      eos::common::RWMutexReadLock eosLock(gOFS->eosViewRWMutex);\n      auto fmd = gOFS->eosFileService->getFileMD(mFid);\n      unsigned int firstDiskLocation = 0;\n\n      for (unsigned int i = 0; i < fmd->getNumLocation(); i++) {\n        const auto loc = fmd->getLocation(i);\n\n        if (loc != 0 && loc != eos::common::TAPE_FS_ID) {\n          firstDiskLocation = loc;\n          break;\n        }\n      }\n\n      if (0 != firstDiskLocation) {\n        tgcSpace = FsView::gFsView.mIdView.lookupSpaceByID(firstDiskLocation);\n      }\n    }\n\n    if (GetFileArchivedGCEnabled(tgcSpace)) {\n      bool dropAllStripes = true;\n      IContainerMD::XAttrMap parentDirAttributes;\n      XrdOucErrInfo errInfo;\n\n      if (gOFS->_attr_ls(eos::common::Path{fullPath.c_str()} .GetParentPath(),\n                         errInfo, root_vid, nullptr, parentDirAttributes, true) == 0) {\n        for (const auto& attrPair : parentDirAttributes) {\n          if (attrPair.first == \"sys.wfe.archived.dropdiskreplicas\" &&\n              attrPair.second == \"0\") {\n            dropAllStripes = false;\n          }\n        }\n      }\n      errInfo.clear();\n\n      if (dropAllStripes &&\n          gOFS->_dropallstripes(fullPath.c_str(), errInfo, root_vid) != 0) {\n        eos_static_err(\"Could not delete all file replicas of %s. Reason: %s\",\n                       fullPath.c_str(), errInfo.getErrText());\n        MoveToRetry(fullPath);\n        return EAGAIN;\n      }\n    }\n  }\n\n  MoveWithResults(SFS_OK);\n  EXEC_TIMING_END(\"Proto::Archive\");\n  return SFS_OK;\n}\n\nbool\nWFE::Job::GetFileArchivedGCEnabled(const std::string& space)\n{\n  const bool defaultValue =\n    false; // The default value of filearchivedgc is 'false'\n  std::string valueStr;\n\n  try {\n    eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n    const auto spaceItor = FsView::gFsView.mSpaceView.find(space);\n\n    if (FsView::gFsView.mSpaceView.end() == spaceItor) {\n      return defaultValue;\n    }\n\n    if (nullptr == spaceItor->second) {\n      return defaultValue;\n    }\n\n    const auto& space = *(spaceItor->second);\n    valueStr = space.GetConfigMember(\"filearchivedgc\");\n  } catch (...) {\n    return defaultValue;\n  }\n\n  if (valueStr.empty()) {\n    return defaultValue;\n  } else {\n    return valueStr == \"on\";\n  }\n}\n\nint\nWFE::Job::HandleProtoMethodRetrieveFailedEvent(const std::string& fullPath)\n{\n  EXEC_TIMING_BEGIN(\"Proto::Retrieve::Failed\");\n  gOFS->MgmStats.Add(\"Proto::Retrieve::Failed\", 0, 0, 1);\n\n  try {\n    eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosFileService->getFileMD(mFid);\n    fmd->setAttribute(RETRIEVE_REQID_ATTR_NAME, \"\");\n    fmd->setAttribute(RETRIEVE_REQTIME_ATTR_NAME, \"\");\n    fmd->setAttribute(RETRIEVE_ERROR_ATTR_NAME, mErrorMessage);\n    gOFS->eosView->updateFileStore(fmd.get());\n  } catch (eos::MDException& ex) {\n    eos_static_err(\"Could not reset retrieves counter and set retrieve error attribute for file %s.\",\n                   fullPath.c_str());\n    MoveWithResults(SFS_ERROR);\n    return SFS_ERROR;\n  }\n\n  MoveWithResults(SFS_OK);\n  EXEC_TIMING_END(\"Proto::Retrieve::Failed\");\n  return SFS_OK;\n}\n\nint\nWFE::Job::HandleProtoMethodArchiveFailedEvent(const std::string& fullPath)\n{\n  EXEC_TIMING_BEGIN(\"Proto::Archive::Failed\");\n  gOFS->MgmStats.Add(\"Proto::Archive::Failed\", 0, 0, 1);\n\n  try {\n    eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);\n    auto fmd = gOFS->eosFileService->getFileMD(mFid);\n    fmd->setAttribute(ARCHIVE_ERROR_ATTR_NAME, mErrorMessage);\n    gOFS->eosView->updateFileStore(fmd.get());\n  } catch (eos::MDException& ex) {\n    eos_static_err(\"Could not set archive error attribute for file %s.\",\n                   fullPath.c_str());\n    MoveWithResults(SFS_ERROR);\n    return SFS_ERROR;\n  }\n\n  MoveWithResults(SFS_OK);\n  EXEC_TIMING_END(\"Proto::Archive::Failed\");\n  return SFS_OK;\n}\n\nint\nWFE::Job::HandleProtoMethodOfflineEvent(const std::string& fullPath,\n                                        const char* const ininfo, std::string& errorMsg)\n{\n  EXEC_TIMING_BEGIN(\"Proto::Offline\");\n  gOFS->MgmStats.Add(\"Proto::Offline\", 0, 0, 1);\n  const std::string prepareRequestId = \"eos:implicit-prepare\";\n  const std::string prepareActivity = \"\";\n  const int prepareRc = IdempotentPrepare(fullPath, prepareRequestId,\n                                          prepareActivity, errorMsg);\n  EXEC_TIMING_END(\"Proto::Offline\");\n  return prepareRc;\n}\n\nint\nWFE::Job::HandleProtoMethodUpdateFidEvent(const std::string& fullPath,\n    std::string& errorMsg)\n{\n  EXEC_TIMING_BEGIN(\"Proto::UpdateFid\");\n  gOFS->MgmStats.Add(\"Proto::UpdateFid\", 0, 0, 1);\n  cta::xrd::Request request;\n  auto notification = request.mutable_notification();\n  notification->mutable_cli()->mutable_user()->set_username(GetUserName(\n        mVid.uid));\n  notification->mutable_cli()->mutable_user()->set_groupname(GetGroupName(\n        mVid.gid));\n  auto xAttrs = CollectAttributes(fullPath);\n\n  for (const auto& attribute : xAttrs) {\n    google::protobuf::MapPair<std::string, std::string> attr(attribute.first,\n        attribute.second);\n    notification->mutable_file()->mutable_xattr()->insert(attr);\n  }\n\n  notification->mutable_wf()->set_event(cta::eos::Workflow::UPDATE_FID);\n  notification->mutable_wf()->mutable_instance()->set_name(\n    gOFS->MgmOfsInstanceName.c_str());\n  notification->mutable_file()->set_lpath(fullPath);\n  notification->mutable_file()->set_disk_file_id(std::to_string(mFid));\n\n  if (xAttrs.count(ARCHIVE_FILE_ID_ATTR_NAME)) {\n    notification->mutable_file()->set_archive_file_id\n    (CtaUtils::toUint64(xAttrs[ARCHIVE_FILE_ID_ATTR_NAME]));\n  }\n\n  if (xAttrs.count(ARCHIVE_STORAGE_CLASS_ATTR_NAME)) {\n    notification->mutable_file()->set_storage_class(\n      xAttrs[ARCHIVE_STORAGE_CLASS_ATTR_NAME]);\n  }\n\n  if (xAttrs.count(EOS_BTIME)) {\n    eos::IFileMD::ctime_t btime {0, 0};\n    Timing::Timespec_from_TimespecStr(xAttrs[EOS_BTIME], btime);\n    notification->mutable_file()->mutable_btime()->set_sec(btime.tv_sec);\n    notification->mutable_file()->mutable_btime()->set_nsec(btime.tv_nsec);\n  }\n\n  const int sendRc = SendProtoWFRequest(this, fullPath, request, errorMsg);\n\n  if (SFS_OK != sendRc) {\n    eos_static_err(\"msg=\\\"Failed to notify protocol buffer endpoint about file ID update %s: %s\\\" sendRc=%d \"\n                   \"newFxid=%08llx\", fullPath.c_str(), errorMsg.c_str(), sendRc, mFid);\n  }\n\n  EXEC_TIMING_END(\"Proto::UpdateFid\");\n  return sendRc;\n}\n\nint\nWFE::Job::SendProtoWFRequest(Job* jobPtr, const std::string& fullPath,\n                             const cta::xrd::Request& request, std::string& errorMsg, bool retry)\n{\n  const std::string& event = jobPtr->mActions[0].mEvent;\n  std::string exec_tag = \"Proto::Send::\";\n  exec_tag += event;\n  EXEC_TIMING_BEGIN(exec_tag.c_str());\n  gOFS->MgmStats.Add(exec_tag.c_str(), 0, 0, 1);\n\n  if (gOFS->ProtoWFEndPoint.empty() || gOFS->ProtoWFResource.empty()) {\n    eos_static_err(\"protoWFEndPoint=\\\"%s\\\" protoWFResource=\\\"%s\\\" fullPath=\\\"%s\\\" event=\\\"%s\\\" \"\n                   \"msg=\\\"You are running proto wf jobs without specifying mgmofs.protowfendpoint or \"\n                   \"mgmofs.protowfresource in the MGM config file.\\\"\",\n                   gOFS->ProtoWFEndPoint.c_str(), gOFS->ProtoWFResource.c_str(), fullPath.c_str(),\n                   event.c_str());\n    jobPtr->MoveWithResults(ENOTCONN);\n    return ENOTCONN;\n  }\n\n  cta::xrd::Response response;\n  auto root_certs = gOFS->ConcatenatedServerRootCA;\n  // Instantiate service object only once, static is thread-safe\n  static std::unique_ptr<WFEClient> request_sender = CreateRequestSender(gOFS->protowfusegrpc, gOFS->ProtoWFEndPoint, gOFS->ProtoWFResource, root_certs, gOFS->JwtTokenPath, gOFS->protowfusegrpctls);\n  cta::xrd::Response::ResponseType response_type = cta::xrd::Response::RSP_INVALID;\n  // Send the request\n  try {\n    const auto sentAt = std::chrono::steady_clock::now();\n    response_type = request_sender->send(request, response);\n    const auto receivedAt = std::chrono::steady_clock::now();\n    const auto timeSpentMilliseconds =\n      std::chrono::duration_cast<std::chrono::milliseconds> (receivedAt - sentAt);\n    eos_static_info(\"protoWFEndPoint=\\\"%s\\\" protoWFResource=\\\"%s\\\" fullPath=\\\"%s\\\" event=\\\"%s\\\" timeSpentMs=%ld \"\n                    \"msg=\\\"Sent protocol buffer request\\\"\",\n                    gOFS->ProtoWFEndPoint.c_str(), gOFS->ProtoWFResource.c_str(), fullPath.c_str(),\n                    event.c_str(),\n                    timeSpentMilliseconds.count());\n  } catch (std::runtime_error& error) {\n    eos_static_err(\"protoWFEndPoint=\\\"%s\\\" protoWFResource=\\\"%s\\\" fullPath=\\\"%s\\\" event=\\\"%s\\\" \"\n                  \"msg=\\\"Could not send protocol buffer request to outside service.\\\" reason=\\\"%s\\\"\",\n                  gOFS->ProtoWFEndPoint.c_str(), gOFS->ProtoWFResource.c_str(), fullPath.c_str(),\n                  event.c_str(),\n                  error.what());\n    errorMsg = error.what();\n    retry ? jobPtr->MoveToRetry(fullPath) : jobPtr->MoveWithResults(ENOTCONN);\n    return ENOTCONN;\n  }\n  // note: the gRPC frontend only returns the following error types:\n  // RSP_ERR_PROTOBUF, RSP_ERR_USER, RSP_ERR_CTA\n  // Handle the response\n  int retval = EPROTO;\n\n  switch (response_type) {\n  case cta::xrd::Response::RSP_SUCCESS: {\n    // Set all attributes for file from response\n    eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n    XrdOucErrInfo errInfo;\n\n    for (const auto& attrPair : response.xattr()) {\n      errInfo.clear();\n\n      if (gOFS->_attr_set(fullPath.c_str(), errInfo, rootvid,\n                          nullptr, attrPair.first.c_str(), attrPair.second.c_str()) != 0) {\n        eos_static_err(\"protoWFEndPoint=\\\"%s\\\" protoWFResource=\\\"%s\\\" fullPath=\\\"%s\\\" event=\\\"%s\\\" \"\n                       \"msg=\\\"Could not set attribute\\\" attrName=\\\"%s\\\" attrValue=\\\"%s\\\" reason=\\\"%s\\\"\",\n                       gOFS->ProtoWFEndPoint.c_str(), gOFS->ProtoWFResource.c_str(), fullPath.c_str(),\n                       event.c_str(),\n                       attrPair.first.c_str(), attrPair.second.c_str(), errInfo.getErrText());\n      }\n    }\n\n    jobPtr->MoveWithResults(SFS_OK);\n    EXEC_TIMING_END(exec_tag.c_str());\n    return SFS_OK;\n  }\n\n  // CTA Frontend error; return operation cancelled\n  case cta::xrd::Response::RSP_ERR_CTA:\n    retval = ECANCELED;\n    break;\n\n  // User error; return operation not permitted\n  case cta::xrd::Response::RSP_ERR_USER:\n    retval = EPERM;\n    break;\n\n  // Error in Google Protocol buffers; return protocol error\n  case cta::xrd::Response::RSP_ERR_PROTOBUF:\n    retval = EPROTO;\n    break;\n\n  // Response type was not set or was set to an unknown value; return not a data message\n  case cta::xrd::Response::RSP_INVALID:\n  default:\n    retval = EBADMSG;\n    eos_static_err(\"protoWFEndPoint=\\\"%s\\\" protoWFResource=\\\"%s\\\" fullPath=\\\"%s\\\" event=\\\"%s\\\" \"\n                   \"msg=\\\"Invalid or unknown response\\\" response=\\\"%s\\\"\",\n                   gOFS->ProtoWFEndPoint.c_str(), gOFS->ProtoWFResource.c_str(), fullPath.c_str(),\n                   event.c_str(),\n                   response.DebugString().c_str());\n  }\n\n  eos_static_err(\"protoWFEndPoint=\\\"%s\\\" protoWFResource=\\\"%s\\\" fullPath=\\\"%s\\\" event=\\\"%s\\\" \"\n                 \"msg=\\\"Received an error response\\\" response=\\\"%s\\\" reason=\\\"%s\\\"\",\n                 gOFS->ProtoWFEndPoint.c_str(), gOFS->ProtoWFResource.c_str(), fullPath.c_str(),\n                 event.c_str(),\n                 CtaCommon::ctaResponseCodeToString(response_type).c_str(),\n                 response.message_txt().c_str());\n  retry ? jobPtr->MoveToRetry(fullPath) : jobPtr->MoveWithResults(retval);\n  errorMsg = response.message_txt();\n  return retval;\n}\n\nconsole::ReplyProto\nWFE::Job::EvictAsRoot(const eos::IFileMD::id_t fid)\n{\n  eos::common::VirtualIdentity rootVid = eos::common::VirtualIdentity::Root();\n  eos::console::RequestProto req;\n  eos::console::EvictProto* evict = req.mutable_evict();\n  auto file = evict->add_file();\n  file->set_fid(fid);\n  EvictCmd cmd(std::move(req), rootVid);\n  return cmd.ProcessRequest();\n}\n\nvoid\nWFE::Job::MoveToRetry(const std::string& filePath)\n{\n  if (!IsSync()) {\n    int retry = 0, delay = 0;\n    std::string retryattr = \"sys.workflow.\" + mActions[0].mEvent + \".\" +\n                            mActions[0].mWorkflow + \".retry.max\";\n    std::string delayattr = \"sys.workflow.\" + mActions[0].mEvent + \".\" +\n                            mActions[0].mWorkflow + \".retry.delay\";\n    eos_static_info(\"%s %s\", retryattr.c_str(), delayattr.c_str());\n    {\n      eos::common::Path cPath(filePath.c_str());\n      auto parentPath = cPath.GetParentPath();\n      eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, parentPath);\n      eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);\n      auto cmd = gOFS->eosView->getContainer(parentPath);\n\n      try {\n        retry = std::stoi(cmd->getAttribute(retryattr));\n      } catch (...) {\n        // retry 25 times by default\n        retry = 25;\n      }\n\n      try {\n        delay = std::stoi(cmd->getAttribute(delayattr));\n      } catch (...) {\n        // retry after 1 hour by default and one final longer wait\n        delay = mRetry == retry - 1 ? 7200 : 3600;\n      }\n    }\n\n    if (mRetry < retry) {\n      time_t storetime = (time_t) mActions[0].mTime + delay;\n      Move(\"r\", \"e\", storetime, ++mRetry);\n      Results(\"e\", EAGAIN, \"scheduled for retry\", storetime);\n    } else {\n      eos_static_err(\"WF event finally failed for %s event of %s file after %d retries.\",\n                     mActions[0].mEvent.c_str(), filePath.c_str(), mRetry);\n      MoveWithResults(SFS_ERROR, \"e\");\n    }\n  }\n}\n\nvoid\nWFE::Job::MoveWithResults(int rcode, std::string fromQueue)\n{\n  if (!IsSync()) {\n    time_t storetime = 0;\n\n    if (rcode == 0) {\n      Move(fromQueue, \"d\", storetime);\n      Results(\"d\", rcode, \"moved to done\", storetime);\n    } else {\n      Move(fromQueue, \"f\", storetime);\n      Results(\"f\", rcode, \"moved to failed\", storetime);\n    }\n  }\n}\n\nstd::string\nWFE::GetGroupName(gid_t gid)\n{\n  int errc = 0;\n  auto group_name  = Mapping::GidToGroupName(gid, errc);\n\n  if (errc) {\n    group_name = \"nobody\";\n  }\n\n  return group_name;\n}\n\nstd::string\nWFE::GetUserName(uid_t uid)\n{\n  int errc = 0;\n  auto user_name  = Mapping::UidToUserName(uid, errc);\n\n  if (errc) {\n    user_name = \"nobody\";\n  }\n\n  return user_name;\n}\n\n/*----------------------------------------------------------------------------*/\nvoid\nWFE::PublishActiveJobs()\n/*----------------------------------------------------------------------------*/\n/**\n * @brief publish the active job number in the space view\n *\n */\n/*----------------------------------------------------------------------------*/\n{\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n  char sactive[256];\n  snprintf(sactive, sizeof(sactive) - 1, \"%u\", GetActiveJobs());\n  FsView::gFsView.mSpaceView[\"default\"]->SetConfigMember(\"stat.wfe.active\",\n      sactive, true);\n}\n\nIContainerMD::XAttrMap\nWFE::CollectAttributes(const std::string& fullPath)\n{\n  eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n  XrdOucErrInfo errInfo;\n  IContainerMD::XAttrMap fileAttributes, parentDirAttributes, result;\n\n  if (gOFS->_attr_ls(fullPath.c_str(),\n                     errInfo, rootvid, nullptr, fileAttributes, true) == 0) {\n    for (const auto& fileAttrPair : fileAttributes) {\n      // sys.archive.file_id and sys.archive.storage_class are set in the MGM and need to be\n      // communicated to the CTA Frontend.\n      // sys.cta.* is set by the CTA Frontend for internal use, e.g. tracking requests in the\n      // objectstore.\n      if (fileAttrPair.first.find(EOS_BTIME) == 0 ||\n          fileAttrPair.first.find(\"sys.archive.\") == 0 ||\n          fileAttrPair.first.find(\"sys.cta.\") == 0 ||\n          fileAttrPair.first.find(\"CTA_\") == 0) {\n        result.insert(fileAttrPair);\n      }\n    }\n  }\n\n  errInfo.clear();\n\n  if (gOFS->_attr_ls(eos::common::Path{fullPath.c_str()} .GetParentPath(),\n                     errInfo, rootvid, nullptr, parentDirAttributes, true) == 0) {\n    for (const auto& dirAttrPair : parentDirAttributes) {\n      if (dirAttrPair.first.find(\"sys.archive.\") == 0 ||\n          dirAttrPair.first.find(\"CTA_\") == 0) {\n        result.insert(dirAttrPair);\n      }\n    }\n  }\n  return result;\n}\n\nvoid\nWFE::MoveFromRBackToQ()\n{\n  std::string queries[2];\n\n  for (auto& query : queries) {\n    query = gOFS->MgmProcWorkflowPath.c_str();\n    query += \"/\";\n  }\n\n  {\n    // today\n    time_t when = time(nullptr);\n    std::string day = eos::common::Timing::UnixTimestamp_to_Day(when);\n    queries[0] += day;\n    queries[0] += \"/r/\";\n    //yesterday\n    when -= (24 * 3600);\n    day = eos::common::Timing::UnixTimestamp_to_Day(when);\n    queries[1] += day;\n    queries[1] += \"/r/\";\n  }\n\n  std::map<std::string, std::set<std::string>> wfedirs;\n  XrdOucErrInfo errInfo;\n  XrdOucString stdErr;\n  eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();\n\n  for (const auto& query : queries) {\n    gOFS->_find(query.c_str(), errInfo, stdErr, rootvid, wfedirs,\n                nullptr, nullptr, false, 0, false, 0);\n  }\n\n  for (const auto& wfedir : wfedirs) {\n    auto wfEntry = wfedir.first;\n\n    for (const auto& entry : wfedir.second) {\n      wfEntry += entry;\n      Job job;\n\n      if (job.Load(wfEntry) == 0) {\n        if (!job.IsSync()) {\n          job.Move(\"r\", \"q\", job.mActions[0].mTime);\n        }\n      } else {\n        eos_static_err(\"msg=\\\"cannot load workflow entry during recycling from r queue\\\" value=\\\"%s\\\"\",\n                       wfEntry.c_str());\n      }\n    }\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/wfe/WFE.hh",
    "content": "// ----------------------------------------------------------------------\n// File: WFE.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_WFE__HH__\n#define __EOSMGM_WFE__HH__\n\n#include \"mgm/Namespace.hh\"\n#include \"common/Mapping.hh\"\n#include \"common/Timing.hh\"\n#include \"common/FileId.hh\"\n#include \"common/ThreadPool.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"common/xrootd-ssi-protobuf-interface/eos_cta/include/CtaFrontendApi.hpp\"\n#include \"proto/ConsoleReply.pb.h\"\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdOuc/XrdOucErrInfo.hh>\n#include <Xrd/XrdJob.hh>\n#include <sys/types.h>\n\n//! Forward declaration\nclass XrdScheduler;\nclass WFEClient;\n\nEOSMGMNAMESPACE_BEGIN\n\n/**\n * @file   WFE.hh\n *\n * @brief  This class implements an WFE engine\n *\n */\n\nclass WFE\n{\nprivate:\n  //............................................................................\n  // variables for the WFE thread\n  //............................................................................\n  AssistedThread mThread; //< thread id of the WFE thread\n  time_t mMs; //< forced sleep time used for find / scans\n\n  eos::common::VirtualIdentity mRootVid; //< we operate with the root vid\n  XrdOucErrInfo mError; //< XRootD error object\n\n  /// number of all jobs which are queued and didn't run yet\n  std::atomic_uint_least32_t mActiveJobs;\n\n  /// condition variable to get signalled for a done job\n  XrdSysCondVar mDoneSignal;\n\npublic:\n\n  /* Default Constructor - use it to run the WFE thread by calling Start\n   */\n  WFE();\n\n  /**\n   * @brief get the millisecond sleep time for find\n   * @return configured sleep time\n   */\n  time_t GetMs()\n  {\n    return mMs;\n  }\n\n  /**\n   * @brief set the millisecond sleep time for find\n   * @param ms sleep time in milliseconds to enforce\n   */\n  void SetMs(time_t ms)\n  {\n    mMs = ms;\n  }\n\n  /* Start the WFE thread engine\n   */\n  bool Start();\n\n  /* Stop the WFE thread engine\n   */\n  void Stop();\n\n  /* WFE method doing the actual policy scrubbing\n   */\n  void WFEr(ThreadAssistant& assistant) noexcept;\n\n  /**\n   * @brief Destructor\n   *\n   */\n  ~WFE()\n  {\n    Stop();\n    std::cerr << __FUNCTION__ << \":: end of destructor\" << std::endl;\n  }\n\n  class Job : XrdJob\n  {\n  public:\n    struct Action {\n\n      Action(std::string a, std::string e, time_t when, std::string workflow,\n             std::string queue)\n      {\n        mAction = std::move(a);\n        mEvent = std::move(e);\n        mTime = when;\n        mWorkflow = std::move(workflow);\n        mQueue = std::move(queue);\n        XrdOucString tst;\n        mWhen = eos::common::StringConversion::GetSizeString(tst,\n                (unsigned long long) when);\n        mDay = eos::common::Timing::UnixTimestamp_to_Day(when);\n      }\n\n      Action(std::string a, std::string e, time_t when, std::string savedOnDay,\n             std::string workflow,\n             std::string queue) : Action(std::move(a), std::move(e), when,\n                                           std::move(workflow), std::move(queue))\n      {\n        mSavedOnDay = std::move(savedOnDay);\n      }\n\n      std::string mAction;\n      std::string mEvent;\n      time_t mTime; //! unix timestamp\n      std::string mWhen; //! string with unix timestamp\n      std::string mDay; //! string with yearmonthday\n      std::string mSavedOnDay; //! string with yearmonthday\n      std::string mWorkflow;\n      std::string mQueue;\n    };\n\n    Job()\n    {\n      mFid = 0;\n      mRetry = 0;\n    }\n\n    Job(eos::common::FileId::fileid_t fid,\n        const eos::common::VirtualIdentity& vid,\n        const std::string& errorMessage = \"\")\n    {\n      mFid = fid;\n      mRetry = 0;\n      mVid = vid;\n      mErrorMessage = errorMessage;\n    }\n\n    ~Job() override = default;\n\n    Job(const Job& other)\n    {\n      mActions = other.mActions;\n      mFid = other.mFid;\n      mDescription = other.mDescription;\n      mRetry = other.mRetry;\n      mErrorMessage = other.mErrorMessage;\n    }\n    // ---------------------------------------------------------------------------\n    // Job execution function\n    // ---------------------------------------------------------------------------\n    void DoIt() override\n    {\n      std::string errorMsg;\n      DoIt(false, errorMsg);\n    }\n\n    //! @ininfo original opaque information of the URL that triggered the event\n    int  DoIt(bool issync, std::string& errorMsg, const char * const ininfo = nullptr);\n\n    //! @brief Handles \"notify\" method events\n    //! @param errorMsg out parameter giving the text of any error\n    //! @ininfo original opaque information of the URL that triggered the event\n    //! @args notification args\n    //! @return SFS_OK if success\n    int HandleNotifyEvents(std::string& errorMsg, const char * const ininfo, const std::string& args);\n\n    //! @brief Handles \"proto\" method events\n    //! @param errorMsg out parameter giving the text of any error\n    //! @ininfo original opaque information of the URL that triggered the event\n    //! @return SFS_OK if success\n    int HandleProtoMethodEvents(std::string& errorMsg, const char * const ininfo);\n\n    //! @brief Handles a \"proto\" method \"prepare\" event\n    //! @param fullPath the full path of the file\n    //! @ininfo original opaque information of the URL that triggered the event\n    //! @param errorMsg out parameter giving the text of any error response\n    int HandleProtoMethodPrepareEvent(const std::string &fullPath, const char * const ininfo, std::string& errorMsg);\n\n    //! @brief Handles a \"proto\" method \"abort_prepare\" event\n    //! @param fullPath the full path of the file\n    //! @ininfo original opaque information of the URL that triggered the event\n    //! @param errorMsg out parameter giving the text of any error response\n    int HandleProtoMethodAbortPrepareEvent(const std::string &fullPath, const char * const ininfo, std::string &errorMsg);\n\n    //! @brief Handles a \"proto\" method \"evict_prepare\" event\n    //! @param fullPath the full path of the file\n    //! @ininfo original opaque information of the URL that triggered the event\n    //! @param errorMsg out parameter giving the text of any error response\n    int HandleProtoMethodEvictPrepareEvent(const std::string &fullPath, const char * const ininfo, std::string& errorMsg);\n\n    //! @brief Handles a \"proto\" method \"create\" event\n    //! @param fullPath the full path of the file\n    //! @ininfo original opaque information of the URL that triggered the event\n    //! @param errorMsg out parameter giving the text of any error response\n    int HandleProtoMethodCreateEvent(const std::string &fullPath, const char* const ininfo, std::string &errorMsg);\n\n    //! @brief Handles a \"proto\" method \"delete\" event\n    //! @param fullPath the full path of the file\n    //! @param errorMsg out parameter giving the text of any error response\n    int HandleProtoMethodDeleteEvent(const std::string &fullPath, std::string &errorMsg);\n\n    //! @brief Handles a \"proto\" method \"close\" event\n    //! @param event the precise name of the event\n    //! @param fullPath the full path of the file\n    //! @ininfo original opaque information of the URL that triggered the event\n    int HandleProtoMethodCloseEvent(const std::string &event, const std::string &fullPath,\n      const char * const ininfo);\n\n    //! @brief Handles a \"proto\" method \"archived\" event\n    //! @param event the precise name of the event\n    //! @param fullPath the full path of the file\n    //! @ininfo original opaque information of the URL that triggered the event\n    int HandleProtoMethodArchivedEvent(const std::string &event, const std::string &fullPath,\n      const char * const ininfo);\n\n    //! @brief Handles a \"proto\" method \"retrieve_failed\" event\n    //! @param fullPath the full path of the file\n    int HandleProtoMethodRetrieveFailedEvent(const std::string &fullPath);\n\n    //! @brief Handles a \"proto\" method \"archive_failed\" event\n    //! @param fullPath the full path of the file\n    int HandleProtoMethodArchiveFailedEvent(const std::string &fullPath);\n\n    //! @brief Handles a \"proto\" method \"offline\" event\n    //! @param fullPath the full path of the file\n    //! @param ininfo original opaque information of the URL that triggered the event\n    //! @param errorMsg out parameter giving the text of any error response\n    int HandleProtoMethodOfflineEvent(const std::string &fullPath, const char * const ininfo, std::string& errorMsg);\n\n    //! @brief Handles a \"proto\" method \"update_fid\" event\n    //! @param fullPath the full path of the file\n    //! @param errorMsg out parameter giving the text of any error response\n    int HandleProtoMethodUpdateFidEvent(const std::string &fullPath, std::string &errorMsg);\n\n    //! @brief Resets the extended attributes for tracking retrieve requests\n    //! @param fullPath the full path of the file\n    void resetRetrieveIdListAndErrorMsg(const std::string &fullPath);\n\n    //! @brief This method is used for communicating proto event requests\n    //! @param jobPtr pointer to the job of the event\n    //! @param fullPath path of the file which trigered the event\n    //! @param request the protobuf request object\n    //! @param retry should retry the request\n    //! @param errorMsg out parameter giving the text of any error response\n    //! @return whether it was successful or not\n    static int SendProtoWFRequest(Job* jobPtr, const std::string& fullPath,\n                                  const cta::xrd::Request& request, std::string& errorMsg,\n                                  bool retry = false);\n    \n\n    //! @brief Execute evict as user root\n    //!\n    //! @param fid The file identifier\n    //! @return evict result\n    static console::ReplyProto EvictAsRoot(const eos::IFileMD::id_t fid);\n\n    //! @brief Returns true if the 'file archived' garbage collector should be\n    //! enabled\n    //! @param space name of the space in which to look for the value of the\n    //! corresponding configuration variable\n    //! @return true if the 'file archived' garbage collector should be enabled\n    bool GetFileArchivedGCEnabled(const std::string &space);\n\n    // -------------------------------------------------------------------------\n    // persistency related methods\n    // -------------------------------------------------------------------------\n    int Save(std::string queue, time_t& when, int action = 0, int retry = 0);\n\n    int Load(std::string path2entry);\n\n    int Move(std::string from_queue, std::string to_queue, time_t& when,\n             int retry = 0);\n\n    int Results(std::string queue, int retc, XrdOucString log, time_t when);\n\n    int Delete(std::string queue, std::string fromDay);\n\n    // -------------------------------------------------------------------------\n\n    void AddAction(const std::string& action,\n                   const std::string& event,\n                   time_t when,\n                   const std::string& workflow,\n                   const std::string& queue)\n    {\n      Action thisaction(action, event, when, workflow, queue);\n      mActions.push_back(thisaction);\n      mDescription += action;\n      mDescription += \" \";\n      mDescription += \"/\";\n      mDescription += event;\n      mDescription += \"/\";\n      std::string tst;\n      mDescription += eos::common::StringConversion::GetSizeString(tst,\n                      (unsigned long long) when);\n      mDescription += \"/\";\n      mDescription += workflow;\n      mDescription += \"/\";\n      mDescription += queue;\n      mDescription += \"/\";\n      mDescription += eos::common::StringConversion::GetSizeString(tst,\n                      (unsigned long long) mFid);\n    }\n\n    void AddAction(const std::string& action,\n                   const std::string& event,\n                   time_t when,\n                   const std::string& savedOnDay,\n                   const std::string& workflow,\n                   const std::string& queue)\n    {\n      AddAction(action, event, when, workflow, queue);\n      mActions[mActions.size() - 1].mSavedOnDay = savedOnDay;\n    }\n\n    bool IsSync(const std::string& event = \"\")\n    {\n      return (event.length() ? event.substr(0, 6) : mActions[0].mEvent.substr(0,\n              6)) == \"sync::\";\n    }\n\n    std::vector<Action> mActions;\n    eos::common::FileId::fileid_t mFid;\n    std::string mDescription;\n    eos::common::VirtualIdentity mVid;\n    std::string mWorkflowPath;\n    std::string mErrorMessage;\n    int mRetry;///! number of retries\n\n  private:\n    //! @brief moving proto wf event jobs to retry queue\n    //! @param filePath the path of the file concerned\n    void MoveToRetry(const std::string& filePath);\n\n    //! @brief move to queues based on the results\n    //! @param rcode the return code of the job\n    void MoveWithResults(int rcode, std::string fromQueue = \"r\");\n\n    //! @brief get prepare request identifier from specified opaque information\n    //! @param ininfo opaque information\n    //! @return prepare request identifier\n    //! @throw MDException if the prepare request identifier does not exist in the\n    //! opaque information or if it has no value\n    std::string GetPrepareRequestIdFromOpaqueData(const char* const ininfo);\n\n    //! @brief get prepare request activity from specified opaque information\n    //! @param ininfo opaque information\n    //! @return prepare request activity, or empty string if no activity is found\n    std::string GetPrepareActivityFromOpaqueData(const char* const ininfo);\n\n    //! @brief get archive metadata from specified opaque information\n    //! @param ininfo opaque information\n    //! @return archive metadata, in base64, or empty string if no archive metadata is found\n    std::string GetArchiveMetadataFromOpaqueData(const char* const ininfo);\n\n    //! @brief Queues a prepare request if necessary\n    //! @param fullPath the full path of the file\n    //! @param prepareRequestId prepare request identifier\n    //! @param prepareActivity prepare request activity, can be empty string\n    //! @param errorMsg out parameter giving the text of any error response\n    int IdempotentPrepare(const std::string& fullPath, const std::string &prepareRequestId, const std::string& prepareActivity, std::string& errorMsg);\n\n  };\n\n  XrdSysCondVar* GetSignal()\n  {\n    return &mDoneSignal;\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Decrement the number of active jobs in the workflow engine\n  // ---------------------------------------------------------------------------\n\n  void DecActiveJobs()\n  {\n    mActiveJobs--;\n    PublishActiveJobs();\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Increment the number of active jobs in this converter\n  // ---------------------------------------------------------------------------\n\n  void IncActiveJobs()\n  {\n    mActiveJobs++;\n    PublishActiveJobs();\n  }\n\n  // ---------------------------------------------------------------------------\n  //! Publish the number of active jobs in the workflow engine\n  // ---------------------------------------------------------------------------\n  void PublishActiveJobs();\n\n  // ---------------------------------------------------------------------------\n  //! Return active jobs\n  // ---------------------------------------------------------------------------\n\n  inline auto GetActiveJobs() -> decltype(mActiveJobs.load()) const\n  {\n    return mActiveJobs.load();\n  }\n\n  static std::string GetUserName(uid_t uid);\n\n  static std::string GetGroupName(gid_t gid);\n\n  static IContainerMD::XAttrMap CollectAttributes(const std::string& fullPath);\n\n  static void MoveFromRBackToQ();\n\n  /// the scheduler class is providing a destructor-less object,\n  /// so we have to create once a singleton of this and keep/share it\n  static XrdSysMutex gSchedulerMutex;\n\n  /// singleton object of a scheduler\n  static XrdScheduler* gScheduler;\n};\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "mgm/wfe.proto",
    "content": "syntax = \"proto3\";\npackage eos.wfe;\n\nmessage id {\n  sfixed n = 1;         // identity number\n  string name = 2;       // identity name \n}\n\nmessage checksum {\n  string value = 1;      //< checksum value\n  string name = 2;       //< checksum name\n}\n\nmessage clock {\n  fixed64 sec = 1;       //< seconds of a clock\n  fixed64 nsec = 2;      //< nanoseconds of a clock\n}\n\nmessage md {\n  fixed64 fid = 1;       //< file/container id\n  fixed64 pid = 2;       //< parent id\n  clock ctime = 3;       //< change time\n  clock mtime = 4;       //< modification time\n  clock btime = 5;       //< birth time\n  clock ttime = 6;       //< tree modification time\n  id owner = 7;          //< ownership \n  fixed64 size = 8;      //< size \n  checksum cks = 9;      //< checksum information\n  sfixed32 mode = 10;    //< mode\n  string lpath = 11;     //< logical path\n  map<string, string> xattr = 12; //< xattribute map\n};\n\nmessage security {\n  string host = 1;       //< client host \n  string app = 2;        //< app string\n  string name = 3;       //< sec name\n  string prot = 4;       //< security protocol\n  string grps = 5;       //< security grps\n}\n\nmessage client {\n  id user = 1;           //< acting client \n  security sec = 2;      //< client security information\n}\n\nmessage service {\n  string name = 1;       //< name of the service\n  string url = 2;        //< access url of the service\n}\n\nmessage workflow {\n  string event = 1;      //< event\n  string queue = 2;      //< queue\n  string workflow = 3;   //< workflow\n  string vpath = 4;      //< vpath\n  service instance = 5;  //< instance information\n  fixed64 timestamp = 6; //< event timestamp\n}\n\nmessage notification {\n  workflow wf = 1;      //< workflow\n  string turl = 2;      //< transport URL\n  client cli = 3;       //< client information\n  md file = 4;          //< file meta data\n  md directory = 5;     //< directory meta data\n}\n"
  },
  {
    "path": "mgm/workflow/Workflow.cc",
    "content": "// ----------------------------------------------------------------------\n// File: Workflow.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"common/Logging.hh\"\n#include \"common/LayoutId.hh\"\n#include \"mgm/workflow/Workflow.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/wfe/WFE.hh\"\n#include \"mgm/fsview/FsView.hh\"\n#include \"common/Constants.hh\"\n#include \"namespace/Prefetcher.hh\"\n/*----------------------------------------------------------------------------*/\n\nEOSMGMNAMESPACE_BEGIN\n\n/*----------------------------------------------------------------------------*/\n\nint\nWorkflow::Trigger(const std::string& event, std::string workflow,\n                  eos::common::VirtualIdentity& vid,\n                  const char* const ininfo, std::string& errorMessage)\n{\n  errno = 0;\n\n  if (workflow == \"none\" && vid.sudoer) {\n    eos_static_info(\"\\\"none\\\" workflow has been called by sudoer, ignoring the event\");\n    return 0;\n  }\n\n  if ((workflow == eos::common::RETRIEVE_WRITTEN_WORKFLOW_NAME &&\n       vid.prot != \"sss\")\n      || (workflow == \"none\" && !vid.sudoer)) {\n    workflow = \"default\";\n  }\n\n  if ((event == \"open\")) {\n    std::string key = \"sys.workflow.\";\n    key += event;\n    key += \".\";\n    key += workflow;\n\n    if (mAttr && (*mAttr).count(key)) {\n      eos_static_info(\"key=%s %d %d\", key.c_str(), mAttr, (*mAttr).count(key));\n      mEvent = event;\n      mWorkflow = workflow;\n      mAction = (*mAttr)[key];\n      int retc = Create(vid, ininfo, errorMessage);\n\n      if (!retc) {\n        if ((workflow == \"enonet\")) {\n          std::string stallkey = key + \".stall\";\n\n          if ((*mAttr).count(stallkey)) {\n            int stalltime = eos::common::StringConversion::GetSizeFromString((\n                              *mAttr)[stallkey]);\n            return stalltime;\n          }\n        }\n\n        return 0;\n      }\n\n      errno = retc;\n      return retc;\n    } else {\n      errno = ENOKEY;\n    }\n  } else {\n    std::string key = \"sys.workflow.\" + event + \".\" + workflow;\n\n    if (mAttr && (*mAttr).count(key)) {\n      mEvent = event;\n      mWorkflow = workflow;\n      mAction = (*mAttr)[key];\n      int retc = Create(vid, ininfo, errorMessage);\n\n      if (retc != 0) {\n        return retc;\n      }\n\n      return 0;\n    } else {\n      errno = ENOKEY;\n    }\n  }\n\n  // not defined\n  return -1;\n}\n\n/*----------------------------------------------------------------------------*/\nstd::string\nWorkflow::getCGICloseW(const std::string& workflow,\n                       const eos::common::VirtualIdentity& vid)\n{\n  std::string cgi;\n  std::string key = \"sys.workflow.closew.\" + workflow;\n  std::string syncKey = \"sys.workflow.sync::closew.\" + workflow;\n\n  // synchronous closew has priority\n  if (mAttr && (*mAttr).count(syncKey)) {\n    std::string fullPath;\n    decltype(gOFS->eosFileService->getFileMD(mFid)->getCUid()) cuid =\n      eos::common::VirtualIdentity::kNobodyUid;\n    decltype(gOFS->eosFileService->getFileMD(mFid)->getCGid()) cgid =\n      eos::common::VirtualIdentity::kNobodyGid;\n\n    try {\n      eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, mFid);\n      eos::common::RWMutexReadLock rlock(gOFS->eosViewRWMutex);\n      auto fmd = gOFS->eosFileService->getFileMD(mFid);\n      fullPath = gOFS->eosView->getUri(fmd.get());\n      cuid = fmd->getCUid();\n      cgid = fmd->getCGid();\n    } catch (eos::MDException& e) {\n      eos_static_err(\"Not creating workflow URL because cannot get meta data. Reason: %s\",\n                     e.what());\n      return \"\";\n    }\n\n    std::ostringstream attrStream;\n    std::string separator;\n\n    for (const auto& attribute : WFE::CollectAttributes(fullPath)) {\n      attrStream << separator << attribute.first <<\n                 eos::common::WF_CUSTOM_ATTRIBUTES_TO_FST_EQUALS << attribute.second;\n      separator = eos::common::WF_CUSTOM_ATTRIBUTES_TO_FST_SEPARATOR;\n    }\n\n    auto attrStr = attrStream.str();\n    std::string attrEncoded;\n    eos::common::SymKey::Base64Encode(attrStr.c_str(), attrStr.length(),\n                                      attrEncoded);\n    cgi = \"&mgm.event=sync::closew&mgm.workflow=\";\n    cgi += workflow;\n    cgi += \"&mgm.instance=\";\n    cgi += gOFS->MgmOfsInstanceName.c_str();\n    cgi += \"&mgm.owner_uid=\";\n    cgi += std::to_string(cuid);\n    cgi += \"&mgm.owner_gid=\";\n    cgi += std::to_string(cgid);\n    cgi += \"&mgm.requestor=\";\n    cgi += WFE::GetUserName(vid.uid);\n    cgi += \"&mgm.requestorgroup=\";\n    cgi += WFE::GetGroupName(vid.gid);\n    cgi += \"&mgm.attributes=\";\n    cgi += attrEncoded;\n  } else if (mAttr && (*mAttr).count(key)) {\n    cgi = \"&mgm.event=closew&mgm.workflow=\";\n    cgi += workflow;\n  }\n\n  return cgi;\n}\n\n/*----------------------------------------------------------------------------*/\nstd::string\nWorkflow::getCGICloseR(const std::string& workflow)\n{\n  std::string cgi;\n  std::string key = \"sys.workflow.closer.\" + workflow;\n  std::string syncKey = \"sys.workflow.sync::closer.\" + workflow;\n\n  // synchronous closer has priority\n  if (mAttr && (*mAttr).count(syncKey)) {\n    cgi = \"&mgm.event=sync::close&mgm.workflow=\";\n    cgi += workflow;\n  } else if (mAttr && (*mAttr).count(key)) {\n    cgi = \"&mgm.event=close&mgm.workflow=\";\n    cgi += workflow;\n  }\n\n  return cgi;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nWorkflow::Attach(const char* path)\n{\n  return false;\n}\n\n/*----------------------------------------------------------------------------*/\nint\nWorkflow::Create(eos::common::VirtualIdentity& vid,\n                 const char* const ininfo, std::string& errorMessage)\n{\n  try {\n    return ExceptionThrowingCreate(vid, ininfo, errorMessage);\n  } catch (std::exception& se) {\n    errorMessage = se.what();\n  } catch (...) {\n    errorMessage = \"Caught an unknown exception\";\n  }\n\n  // Reaching here means that an exception was thrown\n  eos_static_err(\"msg =\\\"Caught an unexpected exception: %s\\\"\",\n                 errorMessage.c_str());\n  return ECANCELED;\n}\n\n/*----------------------------------------------------------------------------*/\nint\nWorkflow::ExceptionThrowingCreate(eos::common::VirtualIdentity& vid,\n                                  const char* const ininfo, std::string& errorMessage)\n{\n  int retc = 0;\n  WFE::Job job(mFid, vid, errorMessage);\n  time_t t = time(nullptr);\n\n  if (job.IsSync(mEvent)) {\n    if (WfeEnabled()) {\n      job.AddAction(mAction, mEvent, t, mWorkflow, \"r\");\n      return job.DoIt(true, errorMessage, ininfo);\n    }\n  } else {\n    if (WfeRecordingEnabled()) {\n      job.AddAction(mAction, mEvent, t, mWorkflow, \"q\");\n      retc = job.Save(\"q\", t);\n\n      if (retc) {\n        eos_static_err(\"failed to save\");\n        return retc;\n      }\n    }\n  }\n\n  return 0;\n}\n\n/*----------------------------------------------------------------------------*/\nbool\nWorkflow::Delete()\n{\n  return false;\n}\n\nbool\nWorkflow::WfeRecordingEnabled()\n{\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n  return FsView::gFsView.mSpaceView.count(\"default\") &&\n         (FsView::gFsView.mSpaceView[\"default\"]->GetConfigMember(\"wfe\") != \"off\");\n}\n\nbool\nWorkflow::WfeEnabled()\n{\n  eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n  return FsView::gFsView.mSpaceView.count(\"default\") &&\n         (FsView::gFsView.mSpaceView[\"default\"]->GetConfigMember(\"wfe\") == \"on\");\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/workflow/Workflow.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Workflow.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_WORKFLOW__HH__\n#define __EOSMGM_WORKFLOW__HH__\n\n#include \"common/FileId.hh\"\n#include \"common/Mapping.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"namespace/interface/IView.hh\"\n#include <sys/types.h>\n\nEOSMGMNAMESPACE_BEGIN\n\nclass Workflow\n{\npublic:\n  Workflow():\n    mAttr(nullptr), mPath(\"\"), mFid(0), mEvent(\"\"), mWorkflow(\"\"), mAction(\"\")\n  {}\n\n  ~Workflow() = default;\n\n  void Init(eos::IContainerMD::XAttrMap* attr, std::string path = \"\",\n            eos::common::FileId::fileid_t fid = 0)\n  {\n    mAttr = attr;\n    mPath = std::move(path);\n    mFid = fid;\n  }\n\n  void SetFile(const std::string& path = \"\",\n               eos::common::FileId::fileid_t fid = 0)\n  {\n    if (path.length()) {\n      mPath = path;\n    }\n\n    if (fid) {\n      mFid = fid;\n    }\n  }\n\n  int Trigger(const std::string& event, std::string workflow,\n              eos::common::VirtualIdentity& vid,\n              const char * const ininfo, std::string& errorMessage);\n\n  std::string getCGICloseW(const std::string& workflow,\n                           const eos::common::VirtualIdentity& vid);\n\n  std::string getCGICloseR(const std::string& workflow);\n\n\n  bool IsSync()\n  {\n    return (mEvent.substr(0, 6) == \"sync::\");\n  }\n\n  void Reset()\n  {\n    mPath = \"\";\n    mFid = 0;\n    mEvent = \"\";\n    mWorkflow = \"\";\n    mAttr = nullptr;\n    mAction = \"\";\n  }\n\n  int Create(eos::common::VirtualIdentity& vid,\n             const char * const ininfo, std::string& errorMessage);\n\n  int ExceptionThrowingCreate(eos::common::VirtualIdentity& vid,\n    const char * const ininfo, std::string& errorMessage);\n\n  bool Attach(const char* path);\n\n  bool Delete();\n\nprivate:\n  eos::IContainerMD::XAttrMap* mAttr;\n  std::string mPath;\n  eos::common::FileId::fileid_t mFid;\n  std::string mEvent;\n  std::string mWorkflow;\n  std::string mAction;\n\n  inline static bool WfeRecordingEnabled();\n\n  inline static bool WfeEnabled();\n};\n\nEOSMGMNAMESPACE_END\n#endif\n"
  },
  {
    "path": "mgm/xattr/XattrLock.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/************************************************************************\n * @file  XattrLock.hh                                                  *\n * @brief Class to handle locks in extended attributes                  *\n ************************************************************************/\n\n#pragma once\n\n#include <set>\n#include <string>\n#include <iostream>\n\n#include \"mgm/Namespace.hh\"\n#include \"common/Constants.hh\"\n#include \"namespace/Prefetcher.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nstruct XattrLock {\npublic:\n  XattrLock() :\n    valid(false), isfuseopen(false), isshared(false), expires(0)\n  {}\n\n  XattrLock(eos::IFileMD::XAttrMap& attr) :\n    xattr(attr), valid(false), isfuseopen(false),\n    isshared(false), expires(0)\n\n  {\n    auto l = xattr.find(eos::common::EOS_APP_LOCK_ATTR);\n\n    if (l != xattr.end()) {\n      Parse(l->second.c_str());\n    }\n\n    // check fuse commit state\n    std::string fs = attr[\"sys.fusex.state\"];\n\n    if (!fs.empty()) {\n      isfuseopen = fs.back() != '|';\n    }\n  }\n  virtual ~XattrLock() {}\n\n  void Parse(const char* l)\n  {\n    valid = false;\n    std::map<std::string, std::string> kvmap;\n\n    if (eos::common::StringConversion::GetKeyValueMap(l,\n        kvmap)) {\n      if (!kvmap.count(\"expires\") ||\n          !kvmap.count(\"type\") ||\n          !kvmap.count(\"owner\")) {\n        valid = false;\n      } else {\n        expires = atoi(kvmap[\"expires\"].c_str());\n        isshared = (kvmap[\"type\"] == \"shared\");\n        owner = (kvmap[\"owner\"]);\n        valid = true;\n      }\n    }\n  }\n\n  bool foreignLock(eos::common::VirtualIdentity& vid, bool isrw)\n  {\n    time_t now = time(NULL);\n\n    if (!valid) {\n      return false;\n    }\n\n    ssize_t lifetime = expires - now;\n\n    if (isfuseopen) {\n      // file is in open state\n      return false;\n    }\n\n    if (lifetime > 0) {\n      // check the lock is still within its lifetime\n      if (lifetime > 604800) {\n        // that is an illegal attribute and we ignore it\n        return false;\n      }\n\n      if (!isrw && isshared) {\n        // if this is read access on a shared lock, we let it pass\n        return false;\n      }\n\n      // we can have full match or a wildcard ownership for the user or app name, both with wildcard does not make sense\n      std::string lockowner   = std::string(vid.uid_string.c_str()) + std::string(\":\")\n                                + std::string(vid.app.c_str());\n      std::string lockownerwn = std::string(\"*\") + std::string(\":\") + std::string(\n                                  vid.app.c_str());\n      std::string lockownerwa = std::string(vid.uid_string.c_str()) + std::string(\":\")\n                                + std::string(\"*\");\n\n      if ((lockowner != owner) &&\n          (lockownerwn != owner) &&\n          (lockownerwa != owner)) {\n        // this is not us - this is the only case we prevent access\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  bool Lock(const char* path, bool shrd, time_t lifetime,\n            eos::common::VirtualIdentity& vid, bool userwildcard, bool appwildcard)\n  {\n    errno = 0;\n    XrdOucErrInfo error;\n    std::string value;\n    eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, path);\n    eos::IFileMDPtr fmd = nullptr;\n    eos::MDLocking::FileWriteLockPtr fdLock;\n\n    try {\n      fmd = gOFS->eosView->getFile(path);\n      fdLock = eos::MDLocking::writeLock(fmd.get());\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      eos_static_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                       e.getMessage().str().c_str());\n      return false;\n    }\n\n    if (!gOFS->_attr_get(path, error, vid, \"\", eos::common::EOS_APP_LOCK_ATTR,\n                         value)) {\n      // parse the lock\n      Parse(value.c_str());\n\n      if (foreignLock(vid, true)) {\n        errno = EBUSY;\n        // we cannot lock this\n        return false;\n      }\n    }\n\n    if (lifetime > (604800)) {\n      // application locks no longer than a week\n      errno = EINVAL;\n      return false;\n    }\n\n    if (userwildcard && appwildcard) {\n      // cannot have both with wildcard\n      errno = EINVAL;\n      return false;\n    }\n\n    // define expires\n    expires = time(NULL) + lifetime;\n    // define the owner\n    owner = (userwildcard ? std::string(\"*\") : std::string(vid.uid_string.c_str()))\n            + std::string(\":\") + (appwildcard ? std::string(\"*\") : std::string(\n                                    vid.app.c_str()));\n    // define shared\n    isshared = shrd;\n\n    // store the lock attribute\n    if (gOFS->_attr_set(path, error, vid, \"\", eos::common::EOS_APP_LOCK_ATTR,\n                        Value().c_str())) {\n      return false;\n    }\n\n    return true;\n  }\n\n  bool Unlock(const char* path, eos::common::VirtualIdentity& vid)\n  {\n    errno = 0;\n    XrdOucErrInfo error;\n    std::string value;\n    eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, path);\n    eos::IFileMDPtr fmd = nullptr;\n    eos::MDLocking::FileWriteLockPtr fdLock;\n\n    try {\n      fmd = gOFS->eosView->getFile(path);\n      fdLock = eos::MDLocking::writeLock(fmd.get());\n    } catch (eos::MDException& e) {\n      errno = e.getErrno();\n      eos_static_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                       e.getMessage().str().c_str());\n      return false;\n    }\n\n    if (!gOFS->_attr_get(path, error, vid, \"\", eos::common::EOS_APP_LOCK_ATTR,\n                         value)) {\n      // parse the lock\n      Parse(value.c_str());\n\n      if (foreignLock(vid, true)) {\n        errno = EBUSY;\n        // we cannot unlock this\n        return false;\n      }\n    }\n\n    // remove the lock attribute\n    if (gOFS->_attr_rem(path, error, vid, \"\", eos::common::EOS_APP_LOCK_ATTR)) {\n      return false;\n    }\n\n    return true;\n  }\n\n  std::string Dump()\n  {\n    std::ostringstream ss;\n    ss << \"valid:\" << valid << \" expires:\" << expires << \" shared:\" << isshared <<\n       std::endl;\n    return ss.str();\n  }\n\n  std::string Value()\n  {\n    std::ostringstream ss;\n    ss << \"expires:\" << expires;\n\n    if (isshared) {\n      ss << \",type:\" << \"shared\";\n    } else {\n      ss << \",type:\" << \"exclusive\";\n    }\n\n    ss << \",owner:\" << owner;\n    return ss.str();\n  }\n\nprivate:\n  eos::IFileMD::XAttrMap xattr;\n  bool valid;\n  bool isfuseopen;\n  bool isshared;\n  time_t expires;\n  std::string owner;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/xattr/XattrSet.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/************************************************************************\n * @file  XattrSet.hh                                                   *\n * @brief Serialize/deserialize a set to allow it to be stored as an    *\n *        extended attribute value (string) but manipulated as a set    *\n ************************************************************************/\n\n#pragma once\n\n#include <set>\n#include <string>\n#include <iostream>\n\n#include \"mgm/Namespace.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\nstruct XattrSet {\n  XattrSet() {}\n\n  XattrSet(const char *str) { deserialize(str); }\n\n  // Convert a string of space-separated values into a set\n  void deserialize(const char *str) {\n    values.clear();\n    do {\n      const char *begin;\n\n      for(begin = str; *str != ' ' && *str != '\\0'; ++str) ;\n\n      if(str-begin > 1) {\n        values.insert(std::string(begin, str));\n      }\n    } while(*str++ != '\\0');\n  }\n\n  void deserialize(const std::string &str) { deserialize(str.c_str()); }\n\n  // Convert a set into a string\n  std::string serialize() const {\n    std::string xattr_str;\n    if(!values.empty()) {\n      for(auto &str : values) {\n        xattr_str += str + ' ';\n      }\n      xattr_str.resize(xattr_str.length()-1);\n    }\n    return xattr_str;\n  }\n\n  std::set<std::string> values;\n};\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/zmq/ZMQ.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ZMQ.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <thread>\n#include \"common/Logging.hh\"\n#include <common/StringUtils.hh>\n#include \"mgm/fusex.pb.h\"\n#include \"mgm/FuseServer/Server.hh\"\n#include \"mgm/zmq/ZMQ.hh\"\n\nEOSMGMNAMESPACE_BEGIN\n\n//int ZMQ::Task::sMaxThreads = 16;\nFuseServer::Server ZMQ::gFuseServer;\n\n//------------------------------------------------------------------------------\n// Start thread handling fuse server proxying\n//------------------------------------------------------------------------------\nvoid\nZMQ::ServeFuse()\n{\n  mTask.reset(new Task(mBindUrl));\n  std::thread t1(&Task::run, mTask.get());\n  t1.detach();\n}\n\n//------------------------------------------------------------------------------\n// Task destructor\n//------------------------------------------------------------------------------\nZMQ::Task::~Task()\n{\n  // Closing the ZMQ context will cause an execption to be thrown in the worker\n  // thread with ETERM as the error number\n  mZmqCtx.close();\n\n  for (const auto& th : mWorkerThreads) {\n    delete th;\n  }\n\n  mWorkerThreads.clear();\n}\n\n\n//------------------------------------------------------------------------------\n// Start proxy service\n//------------------------------------------------------------------------------\nvoid\nZMQ::Task::run() noexcept\n{\n  int enable_ipv6 = 1;\n  mFrontend.set(zmq::sockopt::ipv6, enable_ipv6);\n  {\n    // set keepalive options\n    int32_t keep_alive = 1;\n    int32_t keep_alive_idle = 30;\n    int32_t keep_alive_cnt = 2;\n    int32_t keep_alive_intvl = 30;\n    mFrontend.set(zmq::sockopt::tcp_keepalive, keep_alive);\n    mFrontend.set(zmq::sockopt::tcp_keepalive_idle,keep_alive_idle);\n    mFrontend.set(zmq::sockopt::tcp_keepalive_cnt, keep_alive_cnt);\n    mFrontend.set(zmq::sockopt::tcp_keepalive_intvl, keep_alive_intvl);\n  }\n  mFrontend.bind(mBindUrl.c_str());\n  mBackend.bind(\"inproc://backend\");\n  mInjector.connect(\"inproc://backend\");\n\n  for (int i = 0; i < sMaxThreads; ++i) {\n    mWorkerThreads.push_back(new std::thread(&Worker::work,\n                             new Worker(mZmqCtx, ZMQ_DEALER)));\n    mWorkerThreads.back()->detach();\n  }\n\n  try {\n    zmq::proxy(mFrontend, mBackend);\n  } catch (const zmq::error_t& e) {\n    if (e.num() == ETERM) {\n      // Shutdown\n      return;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n//  Reply to a client identifier with a piece of data\n//------------------------------------------------------------------------------\nvoid\nZMQ::Task::reply(const std::string& id, const std::string& data)\n{\n  static XrdSysMutex sMutex;\n  XrdSysMutexHelper lLock(sMutex);\n  zmq::message_t id_msg(id.c_str(), id.size());\n  zmq::message_t data_msg(data.c_str(), data.size());\n  zmq::send_flags sfm = zmq::send_flags::sndmore;\n  zmq::send_flags sf  = zmq::send_flags::none;\n  try {\n    mInjector.send(id_msg, sfm);\n    mInjector.send(data_msg, sf);\n  } catch (const zmq::error_t& e) {\n    if (e.num() == ETERM) {\n      return;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n//\n//------------------------------------------------------------------------------\nvoid\nZMQ::Worker::work()\n{\n  worker_.connect(\"inproc://backend\");\n  eos::fusex::container hb;\n\n  try {\n    while (true) {\n      zmq::message_t identity;\n      zmq::message_t msg;\n      zmq::message_t copied_id;\n      zmq::message_t copied_msg;\n      auto rc = worker_.recv(identity);\n      auto more = worker_.get(zmq::sockopt::rcvmore);\n\n      if (!more) {\n        eos_static_warning(\"discarding illegal message\");\n        continue;\n      }\n\n      rc = worker_.recv(msg);\n      std::string id(static_cast<const char*>(identity.data()), identity.size());\n      std::string s(static_cast<const char*>(msg.data()), msg.size());\n      hb.Clear();\n\n      if (hb.ParseFromString(s)) {\n        switch (hb.type()) {\n        case eos::fusex::container::HEARTBEAT: {\n          struct timespec tsnow {};\n          eos::common::Timing::GetTimeSpec(tsnow);\n          hb.mutable_heartbeat_()->set_delta(tsnow.tv_sec - hb.heartbeat_().clock() +\n                                             (((int64_t) tsnow.tv_nsec - (int64_t) hb.heartbeat_().clock_ns()) * 1.0 /\n                                              1000000000.0));\n\n          if (gFuseServer.Client().Dispatch(id, *(hb.mutable_heartbeat_()))) {\n            if (EOS_LOGS_DEBUG) {\n              eos_static_debug(\"msg=\\\"received new heartbeat\\\" identity=%s type=%d\",\n                               (id.length() < 256) ? id.c_str() : \"-illegal-\", hb.type());\n            }\n          } else {\n            if (EOS_LOGS_DEBUG) {\n              eos_static_debug(\"msg=\\\"received heartbeat\\\" identity=%s type=%d\",\n                               (id.length() < 256) ? id.c_str() : \"-illegal-\",\n                               hb.type());\n            }\n          }\n\n          if (hb.statistics_().vsize_mb() != 0.0f) {\n            gFuseServer.Client().HandleStatistics(id, hb.statistics_());\n          }\n        }\n        break;\n\n        default:\n          eos_static_err(\"%s\", \"msg=\\\"message type unknown\");\n        }\n      } else {\n        eos_static_debug(\"msg=\\\"unable to parse message\\\": \"\n                         \"id.c_str()=%s, id.length()=%d, id:hex=%s, s.c_str()=%s, s.length()=%d, s:hex=%s\",\n                         id.c_str(), id.length(), eos::common::stringToHex(id).c_str(), s.c_str(),\n                         s.length(), eos::common::stringToHex(s).c_str());\n      }\n    }\n  } catch (const zmq::error_t& e) {\n    // Shutdown\n    if (e.num() == ETERM) {\n      eos_static_debug(\"%s\", \"msg=\\\"shutdown ZMQ worker ...\\\"\");\n      delete this;\n    }\n  }\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "mgm/zmq/ZMQ.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ZMQ.hh\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSMGM_ZMQ__HH__\n#define __EOSMGM_ZMQ__HH__\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/FuseServer/Server.hh\"\n#include <thread>\n#include <vector>\n#include <zmq.hpp>\n#include <unistd.h>\n\nEOSMGMNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class ZMQ\n//------------------------------------------------------------------------------\nclass ZMQ\n{\npublic:\n  class Task;\n  static FuseServer::Server gFuseServer; ///< Fuse server object\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  explicit ZMQ(const char* URL): mBindUrl(URL)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~ZMQ()\n  {\n    std::cerr << __FUNCTION__ << \":: end of destructor\\n\";\n  }\n\n  //----------------------------------------------------------------------------\n  //! Start thread handling fuse server proxying\n  //----------------------------------------------------------------------------\n  void ServeFuse();\n\n  std::unique_ptr<Task> mTask; ///< Task associated to the ZMQ object\n\n  //----------------------------------------------------------------------------\n  //! Class Worker\n  //----------------------------------------------------------------------------\n  class Worker\n  {\n  public:\n    Worker(zmq::context_t& ctx, int sock_type)\n      : mZmqCtx(ctx), worker_(mZmqCtx, sock_type) {}\n\n    void work();\n\n  private:\n    zmq::context_t& mZmqCtx;\n    zmq::socket_t worker_;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Class Task\n  //----------------------------------------------------------------------------\n  class Task\n  {\n  public:\n    const static int sMaxThreads = 16; ///< Max number of worker threads\n\n    //----------------------------------------------------------------------------\n    //! Constructor\n    //----------------------------------------------------------------------------\n    explicit Task(std::string& url)\n      : mZmqCtx(1), mFrontend(mZmqCtx, ZMQ_ROUTER),\n        mBackend(mZmqCtx, ZMQ_DEALER), mInjector(mZmqCtx, ZMQ_DEALER),\n        mBindUrl(url)\n    {}\n\n    //----------------------------------------------------------------------------\n    //! Destructor\n    //----------------------------------------------------------------------------\n    ~Task();\n\n    //----------------------------------------------------------------------------\n    //! Start proxy service\n    //----------------------------------------------------------------------------\n    void run() noexcept;\n\n    //----------------------------------------------------------------------------\n    //! Reply to a client identifier which a pice of data\n    //!\n    //! @param id cilent idnetifier\n    //! @param data data buffer\n    //----------------------------------------------------------------------------\n    void reply(const std::string& id, const std::string& data);\n\n  private:\n    zmq::context_t mZmqCtx; ///< ZMQ context for task\n    zmq::socket_t mFrontend; ///< Frontend socket\n    zmq::socket_t mBackend; ///< Backend socket\n    zmq::socket_t mInjector; ///< Injector socket connected to the backedn\n    std::string mBindUrl; ///< URL\n    std::list<std::thread*> mWorkerThreads; ///< List of worker threads\n  };\n\nprivate:\n  std::string mBindUrl; ///< URL\n};\n\nEOSMGMNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "misc/CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2016 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nif (NOT APPLE)\nadd_subdirectory(etc)\nadd_subdirectory(sbin)\nadd_subdirectory(selinux)\nadd_subdirectory(var)\nadd_subdirectory(egi)\nendif()\n\nadd_subdirectory(usr)"
  },
  {
    "path": "misc/egi/CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Elvin-Alin Sindrilaru - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2012 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ninstall(FILES eos-star-accounting.py eos-info-provider.py\n        DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n        PERMISSIONS OWNER_READ OWNER_EXECUTE\n                    GROUP_READ GROUP_EXECUTE\n                    WORLD_READ WORLD_EXECUTE)\n"
  },
  {
    "path": "misc/egi/eos-info-provider.py",
    "content": "#!/usr/bin/python3\n#-------------------------------------------------------------------------------\n# @file eos-info-provider.py\n# @author: Elvin-Alin Sindrilaru - CERN 2022\n#-------------------------------------------------------------------------------\n#\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2022 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\nimport argparse\nimport socket\nimport json\nimport subprocess\nimport os\nimport sys\nimport ldap3\nimport traceback\nfrom datetime import datetime\n\n\nclass SystemInfo(object):\n    \"\"\"All necessary info on EOS and the system\"\"\"\n\n    def __init__(self, host, cert, key):\n        self.host = host\n        self.cert = cert\n        self.key = key\n        self.capath = '/etc/grid-security/certificates/'\n        self.ports = set([])\n        self.getlisteningports()\n\n    def getlisteningports(self):\n        \"\"\"Check which ports are being listened on\"\"\"\n        try:\n            devnull = os.open(os.devnull, os.O_RDWR) # subprocess.DEVNULL only since python 3.3\n            pipe_out, _ = subprocess.Popen(\"ss -tln\", shell=True, stdout=subprocess.PIPE, stderr=devnull).communicate()\n            for listen in pipe_out.split('\\n'):\n                m = re.search(r\":([0-9]+)\\s\", listen)\n                if m != None:\n                    self.ports.add(int(m.group(1)))\n        except:\n            pass\n\n    def rpmversion(self, package):\n        \"\"\"Get version number from rpm package\"\"\"\n        rpm_proc = subprocess.Popen([\"rpm -qa | grep {0} | awk -F '-' '{{print $3;}}'\".format(package)],\n                                    stdout=subprocess.PIPE,\n                                    stderr=subprocess.PIPE, shell=True)\n        rpm_out, __ = rpm_proc.communicate()\n\n        if rpm_proc.returncode:\n            return str(\"none\")\n\n        return str(rpm_out)\n\n    def getprotinfo(self, prot):\n        \"\"\"Return relevant info for each protocol\"\"\"\n        if prot == \"https\":\n            if 9000 in self.ports:\n                interfacename = \"https\"\n                interfaceversion = self.rpmversion(\"eos-xrootd\")\n                return interfacename, interfaceversion, interfacename, interfaceversion\n            else:\n                return None\n        if prot == \"root\":\n            if 1094 in self.ports:\n                interfacename = \"xroot\"\n                interfaceversion = self.rpmversion(\"eos-xrootd\")\n                return interfacename, interfaceversion, interfacename, interfaceversion\n            else:\n                return None\n        return None\n\n\n    def get_site_statistics(self):\n        \"\"\"\n        Get site statistics concerning EOS instance\"\n        \"\"\"\n        info_dict = {}\n        # Get information about used and free bytes\n        space_proc = subprocess.Popen([\"sudo eos space ls -m\"],\n                                      stdout=subprocess.PIPE,\n                                      stderr=subprocess.PIPE, shell=True)\n        space_out, __ = space_proc.communicate()\n\n        if space_proc.returncode:\n            _log.error(\"failed eos space ls command\")\n            raise RuntimeException(\"failed space ls command\")\n\n        tags = [\"sum.stat.statfs.usedbytes=\", \"sum.stat.statfs.freebytes\"]\n\n        for token in space_out.split():\n            for tag in tags:\n                if token.startswith(tag):\n                    if \"usedbytes\" in token:\n                        info_dict[\"usedbytes\"] = token.split('=')[1]\n                    elif \"freebytes\" in token:\n                        info_dict[\"freebytes\"] = token.split('=')[1]\n\n        # Get information about number of files\n        ns_proc = subprocess.Popen([\"sudo eos ns stat -m | grep \\\"ns.total.files=\\\"\"],\n                                   stdout=subprocess.PIPE,\n                                   stderr=subprocess.PIPE, shell=True)\n        ns_out, __ = ns_proc.communicate()\n\n        if ns_proc.returncode:\n            _log.error(\"failed eos ns stat command\")\n            raise RuntimeException(\"failed eos ns stat command\")\n\n        for token in ns_out.split():\n            if token.startswith(\"ns.total.files=\"):\n                info_dict[\"filecount\"] = token.split('=')[1]\n                break\n\n        return info_dict\n\n\n\nclass Entry(object):\n    \"\"\"Base class for all GLUE2 entries\"\"\"\n\n    def __init__(self):\n        # An array of child objects in the ldap hierarchy\n        self.Children = []\n        # This will be the attribute taken for the DN\n        self.name = \"\"\n        self.Attributes = {}\n        self.Attributes[\"GLUE2EntityCreationTime\"] = [datetime.utcnow().replace(microsecond=0).isoformat() + 'Z']\n        if sys.version_info[:2] < (2, 7):\n            self.ldif_writer = ldif.LDIFWriter(sys.stdout, cols=1000000)\n        else:\n            self.ldif_writer = ldap3.Connection(None, client_strategy='LDIF')\n\n    def add_child(self, entry):\n        self.Children.append(entry)\n\n    def convert(self, data):\n        ret = {}\n        for k, v in data.items():\n            ret[k] = list(map(lambda x: x.encode('utf-8'), v))\n\n    def print_out(self, parent=None):\n        \"\"\" Recursively print out with all children\"\"\"\n        if parent == None:\n            dn = self.name + \"=\" + self.Attributes[self.name][0]\n        else:\n            dn = self.name + \"=\" + self.Attributes[self.name][0] + \",\" + parent\n        bAttributes = {}\n        if sys.version_info[:2] < (2, 7):\n            # python3 LDIFWriter works only with string dictionary keys\n            # and list of bytes as dictionary attribute values\n            for k, v in self.Attributes.items():\n                bAttributes[k] = list(map(lambda x: x.encode('utf-8'), v))\n            self.ldif_writer.unparse(dn, bAttributes)\n        else:\n            bClasses = None\n            for k, v in self.Attributes.items():\n                if k.lower() == 'objectclass':\n                    bClasses = list(map(lambda x: x.encode('utf-8'), v))\n                else:\n                    bAttributes[k] = list(map(lambda x: x.encode('utf-8'), v))\n            sys.stdout.write(self.ldif_writer.add(dn, bClasses, bAttributes))\n            sys.stdout.write('\\n')\n\n        for e in self.Children:\n            e.print_out(dn)\n\n    def set_foreign_key(self):\n        \"\"\"To be implemented by subclasses\"\"\"\n        pass\n\n    def getname(self):\n        return self.Attributes[self.name][0]\n\n\nclass Service(Entry):\n    \"\"\"A GLUE2Service\"\"\"\n\n    def __init__(self, hostname, sitename):\n        Entry.__init__(self)\n        self.name = \"GLUE2ServiceID\"\n        self.Attributes[\"GLUE2ServiceID\"] = [hostname + \"/Service\"]\n        self.Attributes[\"GLUE2ServiceType\"] = [\"eos\"]\n        self.Attributes[\"GLUE2ServiceQualityLevel\"] = [\"production\"]\n        self.Attributes[\"GLUE2ServiceCapability\"] = [\n            'data.access.flatfiles',\n            'data.transfer',\n            'data.management.replica',\n            'data.management.storage',\n            'data.management.transfer',\n            'security.authentication',\n            'security.authorization',\n        ]\n        self.Attributes[\"GLUE2ServiceAdminDomainForeignKey\"] = [sitename]\n        self.Attributes[\"ObjectClass\"] = [\"GLUE2Service\", \"GLUE2StorageService\"]\n\n\nclass Endpoint(Entry):\n    \"\"\"A GLUE2Endpoint\"\"\"\n\n    def __init__(self, epprot, ephost, epport, eppath, epcert, interfacename, interfaceversion, implname, implversion):\n        Entry.__init__(self)\n        self.name = \"GLUE2EndpointID\"\n        self.Attributes[\"GLUE2EndpointID\"] = [ephost + \"/Endpoint/\" + epprot]\n        self.Attributes[\"GLUE2EndpointURL\"] = [epprot + \"://\" + ephost + \":\" + str(epport) + eppath]\n        if epcert != None: self.Attributes[\"GLUE2EndpointIssuerCA\"] = [epcert]\n        self.Attributes[\"GLUE2EndpointInterfaceName\"] = [interfacename]\n        self.Attributes[\"GLUE2EndpointInterfaceVersion\"] = [interfaceversion]\n        self.Attributes[\"GLUE2EndpointImplementationName\"] = [implname]\n        self.Attributes[\"GLUE2EndpointImplementationVersion\"] = [implversion]\n        self.Attributes[\"GLUE2EndpointQualityLevel\"] = [\"production\"]\n        self.Attributes[\"GLUE2EndpointHealthState\"] = [\"ok\"]\n        self.Attributes[\"GLUE2EndpointServingState\"] = [\"production\"]\n        self.Attributes[\"GLUE2EndpointCapability\"] = [\n            'data.access.flatfiles',\n            'data.transfer',\n            'data.management.replica',\n            'data.management.storage',\n            'data.management.transfer',\n            'security.authentication',\n            'security.authorization',\n        ]\n        self.Attributes[\"GLUE2EndpointServiceForeignKey\"] = [\"Undefined\"]\n        self.Attributes[\"ObjectClass\"] = [\"GLUE2StorageEndpoint\", \"GLUE2Endpoint\"]\n\n    def set_foreign_key(self, value):\n        self.Attributes[\"GLUE2EndpointServiceForeignKey\"] = [value]\n\n\nclass AccessProtocol(Entry):\n    \"\"\"A Glue2AccessProtocol\"\"\"\n\n    def __init__(self, ephost, interfacename, interfaceversion, implname, implversion):\n        Entry.__init__(self)\n        self.name = \"GLUE2StorageAccessProtocolID\"\n        self.Attributes[\"GLUE2StorageAccessProtocolID\"] = [ephost + \"/AccessProtocol/\" + interfacename]\n        self.Attributes[\"GLUE2StorageAccessProtocolVersion\"] = [interfaceversion]\n        self.Attributes[\"GLUE2StorageAccessProtocolType\"] = [interfacename]\n        # self.Attributes[\"GLUE2StorageAccessProtocolEndpoint\"]=[endpointurl]\n        self.Attributes[\"GLUE2StorageAccessProtocolStorageServiceForeignKey\"] = [\"Undefined\"]\n        self.Attributes[\"ObjectClass\"] = [\"GLUE2StorageAccessProtocol\"]\n\n    def set_foreign_key(self, value):\n        self.Attributes[\"GLUE2StorageAccessProtocolStorageServiceForeignKey\"] = [value]\n\n\nclass AccessPolicy(Entry):\n    \"\"\"A GLUE2AccessPolicy\"\"\"\n\n    def __init__(self, groups, hostname):\n        Entry.__init__(self)\n        self.name = \"GLUE2PolicyID\"\n        self.Attributes[\"GLUE2PolicyID\"] = [hostname + \"/AccessPolicy\"]\n        self.Attributes[\"GLUE2PolicyRule\"] = groups\n        self.Attributes[\"GLUE2PolicyScheme\"] = [\"org.glite.standard\"]\n        self.Attributes[\"GLUE2AccessPolicyEndpointForeignKey\"] = [\"Undefined\"]\n        self.Attributes[\"ObjectClass\"] = [\"GLUE2AccessPolicy\"]\n\n    def set_foreign_key(self, value):\n        self.Attributes[\"GLUE2AccessPolicyEndpointForeignKey\"] = [value]\n\n\nclass StorageServiceCapacity(Entry):\n    \"\"\"A GLUE2StorageServiceCapacity\"\"\"\n\n    def __init__(self, hostname, tot, used):\n        Entry.__init__(self)\n        self.name = \"GLUE2StorageServiceCapacityID\"\n        self.Attributes[\"GLUE2StorageServiceCapacityID\"] = [hostname + \"/StorageServiceCapacity\"]\n        self.Attributes[\"GLUE2StorageServiceCapacityType\"] = [\"online\"]\n        self.Attributes[\"GLUE2StorageServiceCapacityFreeSize\"] = [str((tot - used) // 1024**3)]\n        self.Attributes[\"GLUE2StorageServiceCapacityTotalSize\"] = [str(tot // 1024**3)]\n        self.Attributes[\"GLUE2StorageServiceCapacityUsedSize\"] = [str(used // 1024**3)]\n        self.Attributes[\"GLUE2StorageServiceCapacityStorageServiceForeignKey\"] = [\"Undefined\"]\n        self.Attributes[\"ObjectClass\"] = [\"GLUE2StorageServiceCapacity\"]\n\n    def set_foreign_key(self, value):\n        self.Attributes[\"GLUE2StorageServiceCapacityStorageServiceForeignKey\"] = [value]\n\n\nclass DataStore(Entry):\n    \"\"\"A GLUE2DataStore\"\"\"\n\n    def __init__(self, hostname, tot, used):\n        Entry.__init__(self)\n        self.name = \"GLUE2ResourceID\"\n        self.Attributes[\"GLUE2ResourceID\"] = [hostname + \"/DataStore\"]\n        self.Attributes[\"GLUE2DataStoreTotalSize\"] = [str(tot // 1024**3)]\n        self.Attributes[\"GLUE2DataStoreUsedSize\"] = [str(used // 1024**3)]\n        self.Attributes[\"GLUE2DataStoreFreeSize\"] = [str((tot - used) // 1024**3)]\n        self.Attributes[\"GLUE2DataStoreType\"] = [\"disk\"]\n        self.Attributes[\"GLUE2DataStoreLatency\"] = [\"online\"]\n        self.Attributes[\"GLUE2DataStoreStorageManagerForeignKey\"] = [\"Undefined\"]\n        self.Attributes[\"GLUE2ResourceManagerForeignKey\"] = [\"Undefined\"]\n        self.Attributes[\"ObjectClass\"] = [\"GLUE2DataStore\"]\n\n    def set_foreign_key(self, value):\n        self.Attributes[\"GLUE2DataStoreStorageManagerForeignKey\"] = [value]\n        self.Attributes[\"GLUE2ResourceManagerForeignKey\"] = [value]\n\n\nclass MappingPolicy(Entry):\n    \"\"\"A GLUE2MappingPolicy\"\"\"\n\n    def __init__(self, qtname, groups, hostname):\n        Entry.__init__(self)\n        self.name = \"GLUE2PolicyID\"\n        self.Attributes[\"GLUE2PolicyID\"] = [hostname + \"/MappingPolicy/\" + qtname]\n        self.Attributes[\"GLUE2PolicyRule\"] = groups\n        self.Attributes[\"GLUE2PolicyScheme\"] = [\"org.glite.standard\"]\n        self.Attributes[\"GLUE2MappingPolicyShareForeignKey\"] = [\"Undefined\"]\n        self.Attributes[\"ObjectClass\"] = [\"GLUE2MappingPolicy\"]\n\n    def set_foreign_key(self, value):\n        self.Attributes[\"GLUE2MappingPolicyShareForeignKey\"] = [value]\n\n\nclass Manager(Entry):\n    \"\"\"A GLUE2Policy\"\"\"\n\n    def __init__(self, eosversion, hostname):\n        Entry.__init__(self)\n        self.name = \"GLUE2ManagerID\"\n        self.Attributes[\"GLUE2ManagerID\"] = [hostname + \"/Manager\"]\n        self.Attributes[\"GLUE2ManagerProductName\"] = [\"EOS\"]\n        self.Attributes[\"GLUE2ManagerProductVersion\"] = [eosversion]\n        self.Attributes[\"GLUE2StorageManagerStorageServiceForeignKey\"] = [\"Undefined\"]\n        self.Attributes[\"GLUE2ManagerServiceForeignKey\"] = [\"Undefined\"]\n        self.Attributes[\"ObjectClass\"] = [\"GLUE2StorageManager\", \"GLUE2Manager\"]\n\n    def set_foreign_key(self, value):\n        self.Attributes[\"GLUE2StorageManagerStorageServiceForeignKey\"] = [value]\n        self.Attributes[\"GLUE2ManagerServiceForeignKey\"] = [value]\n\n\ndef create_endpoints(sysinfo):\n    \"\"\"Find all the StorageEndpoints on this DPM\n    and return an array of them\"\"\"\n    cert_subject = None\n    try:\n        from M2Crypto import X509\n        x509 = X509.load_cert(sysinfo.cert, X509.FORMAT_PEM)\n        cert_subject = \"/%s\" % '/'.join(x509.get_issuer().as_text().split(', '))\n    except:\n        pass\n\n    ret = []\n    if 9000 in sysinfo.ports:\n        ret.append(Endpoint(\"https\", sysinfo.host, 9000, \"\", cert_subject, *sysinfo.getprotinfo(\"https\")))\n    if 1094 in sysinfo.ports:\n        ret.append(Endpoint(\"xroot\", sysinfo.host, 1094, \"/\", cert_subject, *sysinfo.getprotinfo(\"root\")))\n\n    return ret\n\n\ndef create_accessprotocols(sysinfo):\n    \"\"\"Find all the StorageEndpoints and return an array of them\"\"\"\n    ret = []\n    if 9000 in sysinfo.ports:\n        ret.append(AccessProtocol(sysinfo.host, *sysinfo.getprotinfo(\"https\")))\n    if 1094 in sysinfo.ports:\n        ret.append(AccessProtocol(sysinfo.host, *sysinfo.getprotinfo(\"root\")))\n    return ret\n\n\ndef create_manager(sysinfo):\n    \"\"\"Create a StorageManager\"\"\"\n    info_dict = sysinfo.get_site_statistics()\n    mgr = Manager(sysinfo.rpmversion(\"eos-server\"), sysinfo.host)\n    totalcapacity = int(info_dict[\"usedbytes\"]) + int(info_dict[\"freebytes\"])\n    ds = DataStore(sysinfo.host, totalcapacity, int(info_dict[\"usedbytes\"]))\n    ds.set_foreign_key(mgr.getname())\n    mgr.add_child(ds)\n    return mgr\n\n\ndef main():\n    # Arguments\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"--cert\", help=\"Path to host certificate\", default=\"/etc/grid-security/hostcert.pem\")\n    parser.add_argument(\"--key\", help=\"Path to host key\", default=\"/etc/grid-security/hostkey.pem\")\n    parser.add_argument(\"--host\", help=\"fqdn\", default=socket.getfqdn())\n    parser.add_argument(\"--sitename\", help=\"site name\", required=True)\n    args = parser.parse_args()\n\n    # Create the top of the tree\n    top = Service(args.host, args.sitename)\n    sysinfo = SystemInfo(args.host, args.cert, args.key)\n    info_dict = sysinfo.get_site_statistics()\n    totalcapacity = int(info_dict[\"usedbytes\"]) + int(info_dict[\"freebytes\"])\n    ssc = StorageServiceCapacity(args.host, totalcapacity, int(info_dict[\"usedbytes\"]))\n    ssc.set_foreign_key(top.getname())\n    top.add_child(ssc)\n\n    for endpoint in create_endpoints(sysinfo):\n        endpoint.set_foreign_key(top.getname())\n        ap = AccessPolicy(totalgroups, args.host)\n        ap.set_foreign_key(endpoint.getname())\n        endpoint.add_child(ap)\n        top.add_child(endpoint)\n\n    for accessprotocol in create_accessprotocols(sysinfo):\n        accessprotocol.set_foreign_key(top.getname())\n        top.add_child(accessprotocol)\n\n    manager = create_manager(sysinfo)\n    manager.set_foreign_key(top.getname())\n    top.add_child(manager)\n\n    # Print everything out\n    prefix = \"GLUE2GroupID=resource,o=glue\"\n    top.print_out(prefix)\n\n\n# Excecute\nif __name__ == '__main__':\n    try:\n        main()\n    except Exception as err:\n        print(\"Got an exception: {0} in {1}\".format(err, traceback.print_exc()))\n"
  },
  {
    "path": "misc/egi/eos-star-accounting.py",
    "content": "#!/usr/bin/python3\n#-------------------------------------------------------------------------------\n# @file eos-star-accounting.py\n# @author Elvin Sindrilaru - CERN 2022\n#------------------------------------------------------------------------------\n\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2022 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\n# Module to generate an accounting record following the EMI StAR specs\n# in the version 1.2, for details see\n# * http://cds.cern.ch/record/1452920/files/GFD.201.pdf\n# * https://wiki.egi.eu/wiki/APEL/Storage\n#\n# Syntax:\n#\n# eos-star-accounting [-h] [--help]\n# .. to get the help screen\n#\n# Dependencies:\n#  yum install python-lxml python-uuid\n#\n\nfrom __future__ import absolute_import\nfrom __future__ import print_function\nfrom __future__ import division\n\nimport os\nimport sys\nimport socket\nimport base64\nimport lxml.builder as lb\nfrom lxml import etree\nimport uuid\nimport datetime\nimport logging\nimport subprocess\nimport optparse\nfrom io import BytesIO\n\ntry:\n    from urllib.parse import urlparse, urljoin\n    import http.client as http_client\nexcept ImportError:\n    from urlparse import urlparse, urljoin\n    import httplib as http_client\n\n__version__ = '0.0.1'\n__author__ = 'Elvin Sindrilaru'\n\n_log = logging.getLogger('eos-star')\n\nSR_NAMESPACE = \"http://eu-emi.eu/namespaces/2011/02/storagerecord\"\nSR = \"{%s}\" % SR_NAMESPACE\nNSMAP = {\"sr\": SR_NAMESPACE}\n\n\ndef addrecord(xmlroot, hostname, group, user, site, filecount, resourcecapacityused, logicalcapacityused, validduration, recordid=None):\n    # Update XML\n    rec = etree.SubElement(xmlroot, SR+'StorageUsageRecord')\n    rid = etree.SubElement(rec, SR+'RecordIdentity')\n    rid.set(SR+\"createTime\",\n            datetime.datetime.utcnow().strftime(\"%Y-%m-%dT%H:%M:%SZ\"))\n\n    if hostname:\n        ssys = etree.SubElement(rec, SR+\"StorageSystem\")\n        ssys.text = hostname\n\n    recid = recordid\n\n    if not recid:\n        recid = hostname+\"-\"+str(uuid.uuid1())\n\n    rid.set(SR+\"recordId\", recid)\n    subjid = etree.SubElement(rec, SR+'SubjectIdentity')\n\n    if group:\n        grp = etree.SubElement(subjid, SR+\"Group\")\n        grp.text = group\n\n    if user:\n        usr = etree.SubElement(subjid, SR+\"User\")\n        usr.text = user\n\n    if site:\n        st = etree.SubElement(subjid, SR+\"Site\")\n        st.text = site\n\n    e = etree.SubElement(rec, SR+\"StorageMedia\")\n    e.text = \"disk\"\n\n    if validduration:\n        e = etree.SubElement(rec, SR+\"StartTime\")\n        d = datetime.datetime.utcnow() - datetime.timedelta(seconds=validduration)\n        e.text = d.strftime(\"%Y-%m-%dT%H:%M:%SZ\")\n\n    e = etree.SubElement(rec, SR+\"EndTime\")\n    e.text = datetime.datetime.utcnow().strftime(\"%Y-%m-%dT%H:%M:%SZ\")\n\n    if filecount:\n        e = etree.SubElement(rec, SR+\"FileCount\")\n        e.text = str(filecount)\n\n    if not resourcecapacityused:\n        resourcecapacityused = 0\n\n    e1 = etree.SubElement(rec, SR+\"ResourceCapacityUsed\")\n    e1.text = str(resourcecapacityused)\n\n    e3 = etree.SubElement(rec, SR+\"ResourceCapacityAllocated\")\n    e3.text = str(resourcecapacityused)\n\n    if not logicalcapacityused:\n        logicalcapacityused = 0\n\n    e2 = etree.SubElement(rec, SR+\"LogicalCapacityUsed\")\n    e2.text = str(logicalcapacityused)\n\n\ndef get_site_statistics():\n    \"\"\"\n       Get site statistics concerning EOS instance\"\n    \"\"\"\n    info_dict = {}\n    # Get information about used and free bytes\n    space_proc = subprocess.Popen([\"sudo eos space ls -m\"],\n                                  stdout=subprocess.PIPE,\n                                  stderr=subprocess.PIPE, shell=True)\n    space_out, __ = space_proc.communicate()\n\n    if space_proc.returncode:\n        _log.error(\"failed eos space ls command\")\n        raise RuntimeException(\"failed space ls command\")\n\n    tags = [\"sum.stat.statfs.usedbytes=\", \"sum.stat.statfs.freebytes\"]\n\n    for token in space_out.split():\n        for tag in tags:\n            if token.startswith(tag):\n                if \"usedbytes\" in token:\n                    info_dict[\"usedbytes\"] = token.split('=')[1]\n                elif \"freebytes\" in token:\n                    info_dict[\"freebytes\"] = token.split('=')[1]\n\n    # Get information about number of files\n    ns_proc = subprocess.Popen([\"sudo eos ns stat -m | grep \\\"ns.total.files=\\\"\"],\n                               stdout=subprocess.PIPE,\n                               stderr=subprocess.PIPE, shell=True)\n    ns_out, __ = ns_proc.communicate()\n\n    if ns_proc.returncode:\n        _log.error(\"failed eos ns stat command\")\n        raise RuntimeException(\"failed eos ns stat command\")\n\n    for token in ns_out.split():\n        if token.startswith(\"ns.total.files=\"):\n            info_dict[\"filecount\"] = token.split('=')[1]\n            break\n\n    return info_dict\n\n\ndef star(reportgroups, reportusers, record_id, site, hostname, validduration):\n    # Init the xml generator\n    xmlroot = etree.Element(SR+\"StorageUsageRecords\", nsmap=NSMAP)\n\n    if not site:\n        # Grab the site name from the EOS_INSTANCE_NAME env variable\n        site_proc = subprocess.Popen([\"cat /etc/sysconfig/eos_env | grep EOS_INSTANCE_NAME | awk -F '=' '{print $2;}'\"],\n                                     stdout=subprocess.PIPE,\n                                     stderr=subprocess.PIPE,\n                                     shell=True)\n        site_out, __ = site_proc.communicate()\n\n        if len(site_out) == 0:\n            _log.error(\"could not determine site name\")\n            sys.exit(EINVAL)\n        else:\n            site = site_out.strip('\\0\\n')\n            _log.debug(\"using \\\"{0}\\\" as site name\".format(site))\n\n    if site:\n        # Report about site\n        _log.debug(\"Site reporting: starting\")\n        info_dict = get_site_statistics()\n        _log.debug(\"Site stats: {0}\".format(info_dict))\n        addrecord(xmlroot, hostname, None, None, site, info_dict[\"filecount\"],\n                  info_dict[\"usedbytes\"], info_dict[\"usedbytes\"],\n                  validduration)\n\n    if reportgroups:\n        # Report about groups\n        _log.debug(\"Groups reporting: starting\")\n        pass\n\n    if reportusers:\n        #\n        # Report about users\n        #\n        _log.debug(\"Users reporting: starting\")\n        pass\n\n    out = BytesIO()\n    et = etree.ElementTree(xmlroot)\n    et.write(out, pretty_print=True, encoding=\"utf-8\")\n    return out.getvalue().decode('utf-8')\n\n\ndef main():\n    # basic logging configuration\n    streamHandler = logging.StreamHandler(sys.stderr)\n    streamHandler.setFormatter(logging.Formatter(\"%(asctime)s [%(levelname)s](%(module)s:%(lineno)d) %(message)s\", \"%d %b %H:%M:%S\"))\n    _log.addHandler(streamHandler)\n    _log.setLevel(logging.WARN)\n\n    parser = optparse.OptionParser()\n    parser.add_option('--reportgroups', dest='reportgroups', action='store_true', default=False, help=\"Report about groups\")\n    parser.add_option('--reportusers', dest='reportusers', action='store_true', default=False, help=\"Report about users\")\n    parser.add_option('-v', '--debug', dest='verbose', action='count', default=0, help='Increase verbosity level for debugging (on stderr)')\n    parser.add_option('--hostname', dest='hostname', default=socket.getfqdn(), help=\"The hostname string to use in the record. Default: this host.\")\n    parser.add_option('--site', dest='site', default=\"\", help=\"The site string to use in the record. Default: none.\")\n    parser.add_option('--recordid', dest='recordid', default=None, help=\"The recordid string to use in the record. Default: a newly computed unique string.\")\n    parser.add_option('--validduration', dest='validduration', default=86400, help=\"Valid duration of this record, in seconds (default: 1 day)\")\n\n    options, args = parser.parse_args(sys.argv[1:])\n    if options.verbose == 0: _log.setLevel(logging.ERROR)\n    elif options.verbose == 1: _log.setLevel(logging.WARN)\n    elif options.verbose == 2: _log.setLevel(logging.INFO)\n    else: _log.setLevel(logging.DEBUG)\n\n    data = star(options.reportgroups, options.reportusers, options.recordid, options.site, options.hostname, options.validduration)\n    sys.stdout.write(data)\n    _log.debug('done')\n\n    return os.EX_OK\n\nif __name__ == '__main__':\n    try:\n        main()\n    except Exception as err:\n        print(\"Got an exception: {0}\".format(err), file=sys.stderr)\n"
  },
  {
    "path": "misc/etc/CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ninstall(DIRECTORY systemd USE_SOURCE_PERMISSIONS DESTINATION lib)\n\ninstall(DIRECTORY bash_completion.d cron.d eos logrotate.d sysconfig auto.master.d profile.d\n  USE_SOURCE_PERMISSIONS DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR})\n\ninstall(FILES\n  bash_completion.d/eos\n  DESTINATION ${CMAKE_INSTALL_DATADIR}/bash-completion/completions)\n\ninstall(FILES\n  zsh/site-functions/_eos\n  DESTINATION ${CMAKE_INSTALL_DATADIR}/zsh/site-functions)\n\ninstall(FILES\n  eos.keytab\n  eos.client.keytab\n  DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}\n  PERMISSIONS OWNER_READ)\n\ninstall(FILES\n  fuse.conf.eos\n  xrd.cf.auth\n  xrd.cf.fed\n  xrd.cf.fst\n  xrd.cf.mgm\n  xrd.cf.prefix\n  xrd.cf.quarkdb\n  xrd.cf.sync\n  auto.cfsd\n  DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR})\n"
  },
  {
    "path": "misc/etc/auto.cfsd",
    "content": "# -> syntax is '<mountname> -fstype=eoscf :<mountname>'\n# -> e.g.\n# cernhome -fstype=eoscfs :cernhome\n"
  },
  {
    "path": "misc/etc/auto.master.d/cfsd.autofs",
    "content": "# change to the auto mount point for eoscfsd mounts\n/cern /etc/auto.cfsd\n"
  },
  {
    "path": "misc/etc/bash_completion.d/eos",
    "content": "#!/usr/bin/env bash\n\nif [[ -n \"${_EOS_BASH_COMPLETION_LOADED:-}\" ]]; then\n  return 0 2>/dev/null || exit 0\nfi\n_EOS_BASH_COMPLETION_LOADED=1\n\n_eos_comp()\n{\n  COMPREPLY=()\n\n  local reply\n  while IFS= read -r reply; do\n    [[ -z \"${reply}\" ]] && continue\n    [[ ${reply} != \"${word}\"* ]] && continue\n    COMPREPLY+=(\"${reply}\")\n  done <<< \"$1\"\n\n  for reply in \"${COMPREPLY[@]}\"; do\n    if [[ ${reply} == *= || ${reply} == */ ]]; then\n      compopt -o nospace -o filenames 2>/dev/null\n      break\n    fi\n  done\n}\n\n_eos_dynamic_complete()\n{\n  local args=()\n  local i\n\n  for ((i = 1; i < COMP_CWORD; ++i)); do\n    args+=(\"${COMP_WORDS[i]}\")\n  done\n\n  args+=(\"${word}\")\n\n  local old_ifs=\"$IFS\"\n  IFS=$'\\n'\n  local opts\n  opts=$(eos __complete \"${args[@]}\" 2>/dev/null)\n  IFS=\"$old_ifs\"\n\n  if [[ -n \"${opts}\" ]]; then\n    _eos_comp \"${opts}\"\n  else\n    COMPREPLY=()\n  fi\n}\n\n_eos_find_command_index()\n{\n  local i token\n\n  for ((i = 1; i < COMP_CWORD; ++i)); do\n    token=\"${COMP_WORDS[i]}\"\n\n    case \"${token}\" in\n      -a|--app)\n        ((++i))\n        continue\n        ;;\n      -r|--role)\n        ((i += 2))\n        continue\n        ;;\n      -b|--batch|-h|--help|-j|--json|-s|-v|--version)\n        continue\n        ;;\n      root://*|ipc://*)\n        continue\n        ;;\n      -*)\n        continue\n        ;;\n      *)\n        printf '%s\\n' \"${i}\"\n        return 0\n        ;;\n    esac\n  done\n\n  printf '0\\n'\n}\n\n_eos_path_capable_command()\n{\n  local command_index command\n  command_index=$(_eos_find_command_index)\n\n  if [[ \"${command_index}\" == \"0\" ]]; then\n    return 1\n  fi\n\n  command=\"${COMP_WORDS[command_index]}\"\n\n  case \"${command}\" in\n    acl|attr|cat|cd|chmod|chown|cp|du|file|fileinfo|find|info|ln|ls|mkdir|mv|rm|rmdir|stat|touch)\n      return 0\n      ;;\n  esac\n\n  return 1\n}\n\n_eos_local_path_complete()\n{\n  COMPREPLY=()\n\n  local reply\n  while IFS= read -r reply; do\n    [[ -z \"${reply}\" ]] && continue\n    COMPREPLY+=(\"${reply}\")\n  done < <(compgen -f -- \"${word}\")\n\n  [[ ${#COMPREPLY[@]} -gt 0 ]] || return 1\n  compopt -o filenames 2>/dev/null\n  return 0\n}\n\n_eos()\n{\n  COMPREPLY=()\n  compopt +o default +o bashdefault 2>/dev/null\n\n  local word=\"${COMP_WORDS[COMP_CWORD]}\"\n\n  if [[ ${word} == ./* || ${word} == ../* || ${word} == ~/* ]]; then\n    _eos_local_path_complete && return 0\n  fi\n\n  _eos_dynamic_complete\n\n  if [[ ${#COMPREPLY[@]} -eq 0 && ${word} != -* && ${word} != /eos* ]] &&\n     _eos_path_capable_command; then\n    _eos_local_path_complete && return 0\n  fi\n}\n\ncomplete -F _eos eos\n"
  },
  {
    "path": "misc/etc/cron.d/eos-health",
    "content": "# ----------------------------------------------------------------------\n# File: eos-health\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n*/5 * * * * root /etc/init.d/eoshealth status >& /dev/null\n"
  },
  {
    "path": "misc/etc/cron.d/eos-logs",
    "content": "# ----------------------------------------------------------------------\n# File: eos-logs\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n30 0 * * * root  find /var/log/eos  -name \"*-????????.gz\" -mtime +200 -exec rm \\{\\} \\;\n\n"
  },
  {
    "path": "misc/etc/cron.d/eos-mgm-monitoring",
    "content": "# ----------------------------------------------------------------------\n# File: eos-mgm-monitoring\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2024 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n*/5 * * * * root timeout 250s /sbin/eos-mdreport >& /var/eos/md/.eos-mdreport\n*/5 * * * * root timeout 250s /sbin/eos-mdstat >& /var/eos/md/.eos-mdstat\n*/5 * * * * root timeout 250s /sbin/eos-reportstat >& /var/eos/md/.eos-reportstat\n0 */8 * * * root timeout 250s /sbin/eos-inspectorreport >& /var/eos/md/.eos-inspectorreport\n5 */8 *\t* * root timeout 250s /sbin/eos-inspectorstat\t>& /var/eos/md/.eos-inspectorstat\n2-59/5 * * * * root timeout 250s /sbin/eos-prom-push >& /var/eos/md/.eos-prom-push\n\n"
  },
  {
    "path": "misc/etc/cron.d/eos-reports",
    "content": "# ----------------------------------------------------------------------\n# File: eos-reports\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n30 0 * * * root  test -d /var/eos/report/ && find /var/eos/report/ -name \"*.eosreport\" -daystart -mtime +0 -exec gzip -9 \\{\\} \\;\n"
  },
  {
    "path": "misc/etc/cron.d/xrd-alive",
    "content": "# ----------------------------------------------------------------------\n# File: xrd-alive\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n*/5 * * * * root [ -f \"/etc/sysconfig/xrd\" ] && source /etc/sysconfig/xrd ; [ \"$XRD_ALIVE\" = \"1\" ] && for i in ${XRD_ROLES}; do service xrd status $i >& /dev/null || ( date; service xrd condrestart $i ; echo \"`date` restarted $i on `hostname -f`\" | $XRD_NOTIFY ; ) >> /var/log/xroot/xrd-alive.log;  done\n\n\n\n"
  },
  {
    "path": "misc/etc/eos/cfsd/eoscfsd.conf",
    "content": "{\n  \"auth\" : {\n     \"k5domain\" : \"CERN.CH\"\n  }\n}\n"
  },
  {
    "path": "misc/etc/eos/config/fst/fst",
    "content": "# ------------------------------------------------------------ #\n[sysconfig]\n# ------------------------------------------------------------ #\n\nEOS_XRDCP=${EOS_XROOTD}/bin/xrdcp\nEOS_MGM_ALIAS=${SERVER_HOST}\nEOS_GEOTAG=local::geo\nQDB_HOST=${SERVER_HOST}\nQDB_PORT=7777\n\n#-------------------------------------------------------------------------------\n# LOGGING\n#-------------------------------------------------------------------------------\n# ZSTD-compressed rotating logs (replacement for fan-out files). Disabled by default.\n# When enabled, 'eos daemon run' starts xrootd without '-l <logfile>' so its\n# STDERR flows into the EOS Logging ZSTD pipeline.\n#EOS_ZSTD_LOGGING=0\n# Rotate logging segments every N seconds (default: 3600 = 1 hour)\n#EOS_ZSTD_LOGGING_ROTATION=3600\n# ZSTD compression level for logging (1..19), default 1\n#EOS_ZSTD_LOGGING_LEVEL=1\n\n# ------------------------------------------------------------ #\n# create a private mount namespace for the process\n# ------------------------------------------------------------ #\n[unshare]\n# ------------------------------------------------------------ #\n\n# ------------------------------------------------------------ #\n[fst:xrootd:fst]\n# ------------------------------------------------------------ #\n\n###########################################################\nxrd.network keepalive\nxrd.port 1095\n###########################################################\nxrootd.fslib -2 libXrdEosFst.so\nxrootd.async off nosf\nxrootd.redirect ${EOS_MGM_ALIAS}:1094 chksum\n###########################################################\nxrootd.seclib libXrdSec.so\nsec.protocol unix\nsec.protocol sss -c /etc/eos.keytab -s /etc/eos.keytab\nsec.protbind * only unix sss\n###########################################################\nall.export / nolock\nall.trace none\nall.manager localhost 2131\n###########################################################\nofs.persist off\nofs.osslib libEosFstOss.so\nofs.tpc pgm ${EOS_XRDCP}\n###########################################################\nfstofs.broker root://localhost:1097//eos/\nfstofs.autoboot true\nfstofs.quotainterval 10\nfstofs.metalog /var/eos/md/\nfstofs.filemd_handler attr\nfstofs.qdbcluster ${QDB_HOST}:${QDB_PORT}\nfstofs.qdbpassword_file  /etc/eos.keytab\n\n###########################################################\n#-------------------------------------------------------------------------------\n# Configuration for XrdHttp http(s) service on port 11000\n#-------------------------------------------------------------------------------\n#if exec xrootd\n#   xrd.protocol XrdHttp:11000 /usr/lib64/libXrdHttp-4.so\n#   http.exthandler EosFstHttp /usr/lib64/libEosFstHttp.so none\n#   http.cert /etc/grid-security/daemon/host.cert\n#   http.key /etc/grid-security/daemon/privkey.pem\n#   http.cafile /etc/grid-security/daemon/ca.cert\n#fi"
  },
  {
    "path": "misc/etc/eos/config/generic/all",
    "content": "# ------------------------------------------------------------ #\n[init]\n# ------------------------------------------------------------ #\n\nmkdir -p /var/run/eos/\nchown daemon:root /var/run/eos/\nmkdir -p /var/cache/eos/\nchown daemon:root /var/cache/eos/\nif [ -e /etc/eos.keytab ]; then chown daemon /etc/eos.keytab ; chmod 400 /etc/eos.keytab ; fi\nmkdir -p /var/eos/md /var/eos/report\nchmod 755 /var/eos /var/eos/report\nmkdir -p /var/spool/eos/core/mgm /var/spool/eos/core/mq /var/spool/eos/core/fst /var/spool/eos/core/qdb /var/spool/eos/admin\nmkdir -p /var/log/eos\nchown -R daemon /var/spool/eos\nfind /var/log/eos -maxdepth 1 -type d -exec chown daemon {} \\;\nfind /var/eos/ -maxdepth 1 -mindepth 1 -not -path \"/var/eos/fs\" -not -path \"/var/eos/fusex\" -type d -exec chown -R daemon {} \\;\nchmod -R 775 /var/spool/eos\nmkdir -p /var/eos/auth /var/eos/stage\nchown daemon /var/eos/auth /var/eos/stage\nsetfacl -m default:u:daemon:r /var/eos/auth/\n\n\n# ------------------------------------------------------------ #\n[sysconfig]\n# ------------------------------------------------------------ #\n\n# EOSHOST is replaced by the eos CLI with the current hostname\nSERVER_HOST=${EOSHOST}\nINSTANCE_NAME=eosdev\nGEO_TAG=local\nEOS_USE_MQ_ON_QDB=1\n\nEOS_XROOTD=/opt/eos/xrootd/\nLD_LIBRARY_PATH=${EOS_XROOTD}/lib64:/opt/eos/grpc/lib64\nLD_PRELOAD=/usr/lib64/libjemalloc.so\n"
  },
  {
    "path": "misc/etc/eos/config/mgm/auth",
    "content": "# ------------------------------------------------------------ #\n[mgm:xrootd:auth]\n# ------------------------------------------------------------ #\nxrd.port 2094\nall.export /\neosauth.mgm localhost:15555\n\nxrootd.fslib /usr/lib64/libEosAuthOfs.so\nxrootd.seclib libXrdSec.so\neosauth.numsockets 10\neosauth.loglevel info\n\nxrootd.chksum adler\n\n# UNIX authentication + any other type of authentication\nsec.protocol unix\nsec.protbind localhost.localdomain unix\nsec.protbind localhost unix \nsec.protbind * only unix\n\n"
  },
  {
    "path": "misc/etc/eos/config/mgm/mgm",
    "content": "# ------------------------------------------------------------ #\n[mgm:xrootd:mgm]\n# ------------------------------------------------------------ #\n\n###########################################################\nxrootd.fslib libXrdEosMgm.so\nxrootd.seclib libXrdSec.so\nxrootd.async off nosf\nxrootd.chksum adler32\n###########################################################\nxrd.sched mint 8 maxt 256 idle 64\n###########################################################\nall.export / nolock\nall.role manager\n###########################################################\noss.fdlimit 16384 32768\n###########################################################\n# UNIX authentication\nsec.protocol unix\n\n# SSS authentication\nsec.protocol sss -c /etc/eos.keytab -s /etc/eos.keytab\n\n###########################################################\nsec.protbind [::ffff:127.0.0.1] unix sss\nsec.protbind localhost.localdomain unix sss\nsec.protbind localhost unix sss\nsec.protbind * only ${KRB5} ${GSI} sss unix\n###########################################################\nmgmofs.fs /\nmgmofs.targetport 1095\n#mgmofs.authlib libXrdAliceTokenAcc.so\n#mgmofs.authorize 1\n###########################################################\n#mgmofs.trace all debug\n# this URL can be overwritten by EOS_BROKER_URL in [sysconfig]\nmgmofs.broker ${EOS_BROKER_URL}\n# this name can be overwritten by EOS_INSTANCE_NAME defined in [sysconfig]\nmgmofs.instance ${EOS_INSTANCE_NAME}\n\n# namespace, transfer and authentication export directory\nmgmofs.metalog /var/eos/md\nmgmofs.txdir /var/eos/tx\nmgmofs.authdir /var/eos/auth\nmgmofs.archivedir /var/eos/archive\n\n# report store path\nmgmofs.reportstorepath /var/eos/report\n\n# this defines the default config to load\nmgmofs.autoloadconfig default\n\n# Post slave to master transition script\n# mgmofs.postslavetomaster /usr/bin/true\n\n#-------------------------------------------------------------------------------\n# Configuration for the authentication plugin EosAuth\n#-------------------------------------------------------------------------------\n# Set the number of authentication worker threads running on the MGM\nmgmofs.auththreads 64\n\n# Set the front end port number for incoming authentication requests\nmgmofs.authport 15555\n\n# Only listen on localhost connections\nmgmofs.authlocal 1\n\n###########################################################\n# Set the FST gateway host and port\nmgmofs.fstgw someproxy.cern.ch:3001\n\n#-------------------------------------------------------------------------------\n# Configuration for the authentication plugin EosAuth\n#-------------------------------------------------------------------------------\n# Set the number of authentication worker threads running on the MGM\n#mgmofs.auththreads 10\n\n# Set the front end port number for incoming authentication requests\n#mgmofs.authport 15555\n\n#-------------------------------------------------------------------------------\n# Set the namespace plugin implementation\n#-------------------------------------------------------------------------------\nmgmofs.nslib /usr/lib64/libEosNsQuarkdb.so\n\n# Quarkdb custer configuration used for the namespace\nmgmofs.qdbcluster localhost:7777\nmgmofs.qdbpassword_file /etc/eos.keytab\n\n#-------------------------------------------------------------------------------\n# Configuration for the MGM workflow engine\n#-------------------------------------------------------------------------------\n\n# The SSI protocol buffer endpoint for notification messages from \"proto\" workflow actions\n#mgmofs.protowfendpoint HOSTNAME.2NDLEVEL.TOPLEVEL:10955\n#mgmofs.protowfresource /SSI_RESOURCE\n\n#-------------------------------------------------------------------------------\n# Confguration parameters for tape\n#-------------------------------------------------------------------------------\n\n#mgmofs.tapeenabled false\n#mgmofs.prepare.dest.space default\n#mgmofs.prepare.reqid.max 64\n\n#-------------------------------------------------------------------------------\n# Configuration for the tape aware garbage collector\n#-------------------------------------------------------------------------------\n\n# EOS spaces for which the tape aware garbage collector should be enabled\n#mgmofs.tgc.enablespace space1 space2 ...\n\n\n[sysconfig]\n# Should we run with another limit on the core file size other than the default\nDAEMON_COREFILE_LIMIT=unlimited\n\n# Preload jemalloc\nLD_PRELOAD=/usr/lib64/libjemalloc.so\n\n# Disable the KRB5 replay cache\nKRB5RCACHETYPE=none\n\n# These two authentication methods are initialized by modules\nKRB5=\nGSI=\n#-------------------------------------------------------------------------------\n# EOS MGM Configuration\n#-------------------------------------------------------------------------------\n\n# The fully qualified hostname of current MGM\nEOS_MGM_HOST=${SERVER_HOST}\n\n# The fully qualified hostname of target MGM\nEOS_MGM_HOST_TARGET=${SERVER_HOST}\n\n# Don't manage sync service\nEOS_START_SYNC_SEPARATELY=1\n\n# The EOS instance name\nEOS_INSTANCE_NAME=${INSTANCE_NAME}\n\n# The EOS configuration to load after daemon start\nEOS_AUTOLOAD_CONFIG=default\n\n# The EOS broker URL\nEOS_BROKER_URL=root://localhost:1097//eos/\n\n# The EOS host geo location tag used to sort hosts into geographical (rack) locations\nEOS_GEOTAG=${GEO_TAG}\n\n# The fully qualified hostname of MGM master1\nEOS_MGM_MASTER1=${SERVER_HOST}\n\n# The fully qualified hostname of MGM master2\nEOS_MGM_MASTER2=${SERVER_HOST}\n\n# The alias which selects master 1 or 2\nEOS_MGM_ALIAS=${SERVER_HOST}\n\n# Disable stack trace via GDB\nEOS_NO_STACKTRACE=1\n\n# Enable core dumps initiated internally\n#EOS_CORE_DUMP\n\n# Disable shutdown/signal handlers for debugging\n#EOS_NO_SHUTDOWN\n\n# Add secondary group information from database/LDAP (set to 1 to enable)\n#EOS_SECONDARY_GROUPS=0\n\n# Do subtree accounting on directories (set to 1 to enable)\nEOS_NS_ACCOUNTING=1\n\n# Do sync time propagation (set to 1 to enable)\nEOS_SYNCTIME_ACCOUNTING=1\n\n# Use std::shared_timed_mutex for the RWMutex implementation - uncomment to\n# enable.\n# EOS_USE_SHARED_MUTEX=1\n\n# By default statvfs reports the total space if the path deepness is < 4\n# If you want to report only quota accouting you can define\n# EOS_MGM_STATVFS_ONLY_QUOTA=1\n\n# GRPC PORT (set to 0 to disable)\n# EOS_MGM_GRPC_PORT=50051\n\n# directories stored in the listing cache used by 'eos ls' and 'xrd ls'\nEOS_MGM_LISTING_CACHE=0\n\n# allow to have two FSTs on the same physical node in the ame group\nEOS_ALLOW_SAME_HOST_IN_GROUP=1\n\n#-------------------------------------------------------------------------------\n# MGM Audit Configuration\n#-------------------------------------------------------------------------------\n# EOS_MGM_AUDIT controls what is audited at the MGM:\n#   \"none\"|\"false\"|\"no\"|\"off\" or empty => disable all auditing\n#   \"default\"   => audit modifications and READ for default document suffixes\n#   \"modifications\" => audit all modifications (no LIST, no READ)\n#   \"detail\"    => audit modifications and READ for all files (no LIST)\n#   \"all\"       => audit everything including LIST and READ for all files\n# Default example below sets auditing OFF by default.\nEOS_MGM_AUDIT=off\n\n# Configure READ audit suffix filter: comma-separated list of extensions (case-insensitive),\n# use '*' to audit READ for all files. If unset, a built-in document suffix list is used:\n# txt,pdf,doc,docx,ppt,pptx,xls,xlsx,odt,ods,odp,rtf,csv,json,xml,yaml,yml,md,html,htm\n# EOS_MGM_AUDIT_READ_SUFFIX=\"pdf,docx\"\n\n#-------------------------------------------------------------------------------\n# LOGGING+AUDITING\n#-------------------------------------------------------------------------------\n# ZSTD-compressed rotating logs (replacement for fan-out files). Disabled by default.\n# When enabled, 'eos daemon run' starts xrootd without '-l <logfile>' so its\n# STDERR flows into the EOS Logging ZSTD pipeline.\n#EOS_ZSTD_LOGGING=0\n# Rotate logging segments every N seconds (default: 3600 = 1 hour)\n#EOS_ZSTD_LOGGING_ROTATION=3600\n# ZSTD compression level for logging (1..19), default 1\n#EOS_ZSTD_LOGGING_LEVEL=1\n#\n# ZSTD-compressed iostat reports. Disabled by default.\n# When enabled, iostat reports are written as compressed, time-rotated segments.\n#EOS_ZSTD_REPORTS=0\n# Rotate iostat report segments every N seconds (default: 86400 = 1 day)\n#EOS_ZSTD_REPORTS_ROTATION=86400\n# ZSTD compression level for iostat reports (1..19), default 1\n#EOS_ZSTD_REPORTS_LEVEL=1\n#\n# Audit log rotation interval in seconds (default 3600 = 1 hour)\n# Override only if you need a different rotation cadence\n#EOS_AUDIT_ROTATION=3600\n\n#-------------------------------------------------------------------------------\n# HTTPD Configuration\n#-------------------------------------------------------------------------------\n# HTTP server ports\n\n# MGM\nEOS_MGM_HTTP_PORT=8000\n\n#-------------------------------------------------------------------------------\n# FUSEX Configuration\n#-------------------------------------------------------------------------------\n\n# Listener port of the ZMQ server used by FUSEx)\n# EOS_MGM_FUSEX_PORT=1100\n\n# max number of children listed via FUSEX\nEOS_MGM_FUSEX_MAX_CHILDREN=262144\n\n#-------------------------------------------------------------------------------\n# Archive configuration\n#-------------------------------------------------------------------------------\n\n# Set the root destination for all archives beloging to this instance\n# EOS_ARCHIVE_URL=root://castorpps.cern.ch//user/cern.ch/c3/archive/\n\n# Set the CASTOR service class (svcClass) for all file transfers to CASTOR\n# EOS_ARCHIVE_SVCCLASS=default\n\n#-------------------------------------------------------------------------------\n# MGM TTY Console Broadcast Configuration\n#-------------------------------------------------------------------------------\n\n# define the log file where you want to grep\n# EOS_TTY_BROADCAST_LISTEN_LOGFILE=\"/var/log/eos/mgm/xrdlog.mgm\"\n\n# define the log file regex you want to broad cast to all consoles\n# EOS_TTY_BROACAST_EGREP=\"\\\"CRIT|ALERT|EMERG|PROGRESS\\\"\"\n\n#-------------------------------------------------------------------------------\n# MGM SciToken Cache Directory\n#-------------------------------------------------------------------------------\n\nXDG_CACHE_HOME=/var/cache/eos/\n"
  },
  {
    "path": "misc/etc/eos/config/mgm/mgm.modules",
    "content": "[modules]\n# alice\n# http\n# krb5\n# gsi\n"
  },
  {
    "path": "misc/etc/eos/config/modules/alice",
    "content": "# ------------------------------------------------------------ #\n[mgm:xrootd:mgm]\n# ------------------------------------------------------------ #\n\nmgmofs.authlib  /usr/lib64/libXrdAliceTokenAcc.so\nmgmofs.authorize  1\nalicetokenacc.multiprocess  8\nalicetokenacc.noauthzhost  eosmon01\nalicetokenacc.noauthzhost  eosmon01.cern.ch\nalicetokenacc.noauthzhost  eosmon02\nalicetokenacc.noauthzhost  eosmon02.cern.ch\nalicetokenacc.noauthzhost  localhost\nalicetokenacc.noauthzhost  localhost.localdomain\nalicetokenacc.truncateprefix  /eos/${INSTANCE_NAME}/grid\n\n# ------------------------------------------------------------ #\n[fst:xrootd:fst]\n# ------------------------------------------------------------ #\n\nxrootd.monitor  all flush 60s window 30s dest files info user aliendb2.cern.ch:9930\n\n# ------------------------------------------------------------ #\n[mgm:init]\n# ------------------------------------------------------------ #\nset -x\nyum install alicetokenacc -y --nogpgcheck\n\nALICE_UID=${ALICEUID-16437}\nALICE_GID=${ALICEGID-1395}\n\nid -G z2 >& /dev/null\nif [ $? -ne 0 ]; then\n  groupadd --gid ${ALICE_GID} z2\nfi\n\nid alice >& /dev/null\nif [ $? -ne 0 ]; then\n  adduser --uid ${ALICE_UID}  alice\nfi\n\neos vid set map \\<pwd\\> -unix vuid:${ALICE_UID}\neos vid set map \\<pwd\\> -unix vgid:${ALICE_GID}\neos mkdir -p /eos/${INSTANCE_NAME:3}/grid/\neos chown ${ALICE_UID}:${ALICE_GID} /eos/${INSTANCE_NAME:3}/grid/\neos chmod 700 /eos/${INSTANCE_NAME:3}/grid/\n\nfor name in 01 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 ; do\n  eos map link /$name/ /eos/${INSTANCE_NAME:3}/grid/$name/\n  eos mkdir -p /eos/${INSTANCE_NAME:3}/grid/$name/\ndone\n\n# ------------------------------------------------------------ #\n[fst:init]\n# ------------------------------------------------------------ #\nyum install eos-apmon -y --nogpgcheck\n\n[fst:sysconfig]\nAPMON_INSTANCE_NAME=${ALICE_SE_NAME}\nAPMON_STORAGEPATH=\"data\"\n"
  },
  {
    "path": "misc/etc/eos/config/qdb/qdb",
    "content": "[sysconfig]\n\n# ----------------------\n# name of this QDB node\n# ----------------------\nQDB_HOST=${SERVER_HOST}\n\n# defaults\nQDB_PORT=7777\nQDB_CLUSTER_ID=${INSTANCE_NAME}\nQDB_NODE=${QDB_HOST}:${QDB_PORT}\nQDB_NODES=${QDB_HOST}:${QDB_PORT}\nQDB_PATH=/var/lib/qdb\n\n\n[init]\ntest -d ${QDB_PATH} || quarkdb-create --path ${QDB_PATH} --clusterID ${QDB_CLUSTER_ID} --nodes ${QDB_NODES}\nchown -R daemon:daemon ${QDB_PATH}\n\n[qdb:xrootd:qdb]\nxrd.port ${QDB_PORT}\nxrd.protocol redis:${QDB_PORT} libXrdQuarkDB.so\n\nredis.database /var/lib/qdb\nredis.mode raft\nredis.myself ${QDB_NODE}\nredis.password_file /etc/eos.keytab"
  },
  {
    "path": "misc/etc/eos.client.keytab",
    "content": "0 u:daemon g:daemon n:eos-test+ N:6927582626958016513 c:1612953522 e:0 f:0 k:4d6faa5829d44b32a19c74e2915d94dd86125bfe7dfffb7c2badcb000f9a8327\n"
  },
  {
    "path": "misc/etc/eos.keytab",
    "content": "0 u:eosdev g:eosdev n:eosdev-test+ N:7189944157387882498 c:1674039326 e:0 f:0 k:9703d33635fe95364bc50b76bcf3dadf9c73f7f30c8dcb74e15fa89677cd4f28\n0 u:eosnobody g:eosnobody n:eosnobody-test+ N:6964297016721539074 c:1621501757 e:0 f:0 k:f3b1aa51cba0367f77892e1754385fcc0dfed1f5c19a4122519d2e6cf0dd2a0d\n0 u:daemon g:daemon n:eos-test+ N:6927582626958016513 c:1612953522 e:0 f:0 k:4d6faa5829d44b32a19c74e2915d94dd86125bfe7dfffb7c2badcb000f9a8327\n"
  },
  {
    "path": "misc/etc/fuse.conf",
    "content": "user_allow_other\n"
  },
  {
    "path": "misc/etc/fuse.conf.eos",
    "content": "user_allow_other\n"
  },
  {
    "path": "misc/etc/logrotate.d/eos-fuse-logs",
    "content": "/var/log/eos/fuse/*.log {\n\tmissingok\n\tdaily\n        copytruncate\n        create 755 root root\n        dateext\n        rotate 200\n        compress\n}\n"
  },
  {
    "path": "misc/etc/logrotate.d/eos-fusex-logs",
    "content": "/var/log/eos/fusex/*.log {\n\tmissingok\n\tdaily\n        copytruncate\n        create 755 root root\n        dateext\n        rotate 200\n        compress\n}\n"
  },
  {
    "path": "misc/etc/logrotate.d/eos-logs",
    "content": "# aequivalent to the original config, without duplicating for every file\n# rotation of MGM logs\n/var/log/eos/mgm/xrdlog.mgm /var/log/eos/mgm/*.log /var/log/eos/mq/xrdlog.mq /var/log/eos/tx/transfer-archive.log {\n  missingok\n  daily\n  copytruncate\n  create 644 daemon daemon\n  dateext\n  rotate 200\n  compress\n}\n\n# rotation of FST logs\n/var/log/eos/fst/xrdlog.fst /var/log/eos/fst/*.log {\n  missingok\n  daily\n  copytruncate\n  create 644 daemon daemon\n  dateext\n  rotate 200\n  compress\n}\n\n# rotation for frontend authentication daemons\n/var/log/eos/auth[0-9]/xrdlog.auth[0-9] {\n  missingok\n  daily\n  copytruncate\n  create 644 daemon daemon\n  dateext\n  rotate 200\n  compress\n}\n"
  },
  {
    "path": "misc/etc/profile.d/eos-completion.sh",
    "content": "case $- in\n  *i*) ;;\n  *)\n    return 0 2>/dev/null || exit 0\n    ;;\nesac\n\nif [[ -n \"${BASH_VERSION:-}\" ]] && shopt -q progcomp; then\n  if [[ -r /etc/bash_completion.d/eos ]]; then\n    . /etc/bash_completion.d/eos\n  fi\nfi\n"
  },
  {
    "path": "misc/etc/sysconfig/eos_env.example",
    "content": "#-------------------------------------------------------------------------------\n# File: eos_env.example\n#-------------------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2025 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n# Allow unlimited core files sizes.\nDAEMON_COREFILE_LIMIT=unlimited\n\n# Preload jemalloc\nLD_PRELOAD=/usr/lib64/libjemalloc.so.1\n\n# Disable the KRB5 replay cache\nKRB5RCACHETYPE=none\n\n# What roles should the xroot daemon run for. For each role you can overwrite\n# the default options using a dedicate sysconfig file\n# e.g. /etc/sysconfig/xrd.<role>. The role based mechanism allows for\n# multiple xrd's running with different options to be controlled via\n# the same systemd script\n\n#-------------------------------------------------------------------------------\n# EOS roles - Systemd Services\n#-------------------------------------------------------------------------------\nXRD_ROLES=\"mq mgm fst\"\n\n#-------------------------------------------------------------------------------\n# EOS Configuration\n#-------------------------------------------------------------------------------\n\n# The EOS instance name N.B.! It MUST start with \"eos\" and then some\n# alphanumeric characters. This is important since there are some special\n# files and directories created inside the /eos/<instance_name> subtree\n# indispensable for the proper functioning of the instance.\nEOS_INSTANCE_NAME=eosdev\n\n# EOS host geo location used to sort hosts into geographical (rack) locations\n# !!! REQUIRED !!! list of \":\" separated tokens of up to 8 chars\nEOS_GEOTAG=\"\"\n\n# The EOS configuration to load after daemon start\nEOS_AUTOLOAD_CONFIG=default\n\n# The alias for machines running MGMs\nEOS_MGM_ALIAS=eosdev.cern.ch\n\n# In HA mode, presence of this env enables the redirection of the\n# read traffic from the slaves to the master\n# EOS_HA_REDIRECT_READS=1\n\n# The mail notification in case of fail-over\n# EOS_MAIL_CC=\"apeters@mail.cern.ch\"\n# EOS_NOTIFY=\"mail -s `date +%s`-`hostname`-eos-notify $EOS_MAIL_CC\"\n\n# Add secondary group information from database/LDAP (set to 1 to enable)\n# EOS_SECONDARY_GROUPS=0\n\n# By default statvfs reports the total space if the path deepness is < 4\n# If you want to report only quota accouting you can define\n# EOS_MGM_STATVFS_ONLY_QUOTA=1\n\n# If you only want to report the space acacounting you can define\n# EOS_MGM_STATVFS_ONLY_SPACE=1\n\n# Set to 1 if you want eos quota set by logical space (default is by raw)\n# EOS_MGM_QUOTA_SET_BY_LOGICAL=1\n\n#-------------------------------------------------------------------------------\n# Archive configuration\n#-------------------------------------------------------------------------------\n\n# Set the root destination for all archives beloging to this instance\n# EOS_ARCHIVE_URL=root://castorpps.cern.ch//user/cern.ch/c3/archive/\n\n# Set the service class (svcClass) for all file transfers to CTA\n# EOS_ARCHIVE_SVCCLASS=default\n\n#-------------------------------------------------------------------------------\n# MGM TTY Console Broadcast Configuration\n#-------------------------------------------------------------------------------\n\n# define the log file where you want to grep\nEOS_TTY_BROADCAST_LISTEN_LOGFILE=\"/var/log/eos/mgm/xrdlog.mgm\"\n\n# define the log file regex you want to broad cast to all consoles\nEOS_TTY_BROACAST_EGREP=\"\\\"CRIT|ALERT|EMERG|PROGRESS\\\"\"\n\n#-------------------------------------------------------------------------------\n# MGM FUSE configuration\n#-------------------------------------------------------------------------------\n\n# uncomment to change the minimum needed size available to create a new file\n# EOS_MGM_FUSE_BOOKING_SIZE=5368709120\n\n# define the name of the FUSE application, which can by-pass throttling & stalling\n# EOS_MGM_FUSEX_NOSTALL_APP=\"fuse::restic\"\n\n#-------------------------------------------------------------------------------\n# MGM 'xrdfs query space' configuration\n#-------------------------------------------------------------------------------\n\n# uncoment to set the EOS space name to be used by 'xrdfs query space' commands\n# that do not explicitly specify an EOS space name\n# EOS_MGM_STATVFS_DEFAULT_SPACE=\"default\"\n\n#-------------------------------------------------------------------------------\n# MGM Directory Listing Cache configuration\n#-------------------------------------------------------------------------------\n\n# Set to 0 to disable listing cache for 'xrdfs ls' and 'eos ls', or a number\n# with the number of dirs to cache. If not defined the caching is disabled.\n# EOS_MGM_LISTING_CACHE=1024\n\n#-------------------------------------------------------------------------------\n# MGM OIDC configuration\n#-------------------------------------------------------------------------------\n# By default the sub field is mapped from OIDC tokens\n# EOS_MGM_OIDC_MAP_FIELD=sub\n# By default (undefined) the server certificate and hostname are verified,\n# to skip this define the followin env variable.\n# EOS_MGM_OIDC_INSECURE=1\n\n#-------------------------------------------------------------------------------\n# MGM token generation configuration\n#-------------------------------------------------------------------------------\n# By default the token generation key is derived from an sss key\n# EOS_MGM_TOKEN_KEYFILE=/etc/eos/token.key\n\n#-------------------------------------------------------------------------------\n# MGM device tracking\n#-------------------------------------------------------------------------------\n# Interval at which the MGM takes out compressed JSON S.M.A.R.T info and publishes them\n# EOS_MGM_DEVICES_PUBLISHING_INTERVAL=900\n\n#-------------------------------------------------------------------------------\n# MGM SciTokens cache directory\n#-------------------------------------------------------------------------------\nXDG_CACHE_HOME=/var/tmp/\n\n#-------------------------------------------------------------------------------\n# MGM Audit Configuration\n#-------------------------------------------------------------------------------\n# EOS_MGM_AUDIT controls what is audited at the MGM:\n#   \"none\"|\"false\"|\"no\"|\"off\" or empty => disable all auditing\n#   \"default\"   => audit modifications and READ for default document suffixes\n#   \"modifications\" => audit all modifications (no LIST, no READ)\n#   \"detail\"    => audit modifications and READ for all files (no LIST)\n#   \"all\"       => audit everything including LIST and READ for all files\n# Default example below sets auditing OFF by default.\nEOS_MGM_AUDIT=off\n\n# Audit log rotation interval in seconds (default is 3600 = 1 hour)\n# Override only if you need a different rotation cadence\nEOS_AUDIT_ROTATION=3600\n\n# Configure READ audit suffix filter: comma-separated list of extensions (case-insensitive),\n# use '*' to audit READ for all files. If unset, a built-in document suffix list is used:\n# txt,pdf,doc,docx,ppt,pptx,xls,xlsx,odt,ods,odp,rtf,csv,json,xml,yaml,yml,md,html,htm\n# EOS_MGM_AUDIT_READ_SUFFIX=\"pdf,docx\"\n\n#-------------------------------------------------------------------------------\n# ZSTD-compressed Logging and Reports\n#-------------------------------------------------------------------------------\n# Enable ZSTD-compressed rotating logs (replaces fan-out files). Disabled by default.\n# When enabled, xrootd is started without '-l <logfile>' so its STDERR flows\n# into the EOS Logging ZSTD pipeline (handled by eos_start.sh / 'eos daemon run').\n#EOS_ZSTD_LOGGING=0\n# Rotation interval for ZSTD logging in seconds (default 3600 = 1 hour)\n#EOS_ZSTD_LOGGING_ROTATION=3600\n# ZSTD compression level for logging (1..19), default 1\n#EOS_ZSTD_LOGGING_LEVEL=1\n#\n# Enable ZSTD-compressed iostat reports. Disabled by default.\n# When enabled, reports are written as compressed, time-rotated segments instead of plain .eosreport files.\n#EOS_ZSTD_REPORTS=0\n# Rotation interval for compressed reports in seconds (default 86400 = 1 day)\n#EOS_ZSTD_REPORTS_ROTATION=86400\n# ZSTD compression level for reports (1..19), default 1\n#EOS_ZSTD_REPORTS_LEVEL=1\n\n#-------------------------------------------------------------------------------\n# FST Configuration\n#-------------------------------------------------------------------------------\n\n# Disable 'sss' enforcement to allow generic TPC\n#EOS_FST_NO_SSS_ENFORCEMENT=1\n\n# Network interface to monitor (default eth0)\n#EOS_FST_NETWORK_INTERFACE=\"eth0\"\n\n# Specify in seconds how often FSTs should query for new delete operations\n# EOS_FST_DELETE_QUERY_INTERVAL=300\n\n# If variable defined then enable the use of xrootd connection pool i.e.\n# create/share different physical connections for queries done from the FST\n# to the MGM in the CallManager method. By default this is disabled.\n# EOS_FST_CALL_MANAGER_XRD_POOL=1\n\n# If CallManager xrootd connection pool is enabled one can set the maxium size\n# of the pool of connections. The min value is 1, the max value is 32. By default\n# the value is 10.\n# EOS_FST_CALL_MANAGER_XRD_POOL_SIZE=10\n\n# If variable defined use asynchronous (double-buffered) reading in TPCs - By default\n# it is undefined = disabled\n# EOS_FST_TPC_READASYNC=1\n\n# Modify the TPC key validity which by default is 120 seconds\n# EOS_FST_TPC_KEY_VALIDITY_SEC=120\n\n# Control the asynchronous callback on close, if undefined or 0 then disabled,\n# else if 1 then enabled. This option is applied only for write operations.\n# EOS_FST_ASYNC_CLOSE=0\n\n# When asynchronous callback on close is enabled, one can use the following env\n# variable to control the minimum size of files for which this gets triggered.\n# If not specified then the values is 0 bytes.\n# EOS_FST_ASYNC_CLOSE_MIN_SIZE_BYTES=0\n\n# Enable internal stacktrace printing in the logs - this is useful especially\n# for container environments where abrtd is not running\n# EOS_FST_ENABLE_STACKTRACE=1\n\n# Enable async writes between replicas - this can improve the performance for\n# FSTs with long latency.\n# EOS_FST_REPLICA_ASYNC_WRITE=1\n\n# If this variable is present then deletion requests coming from the Fsck\n# engine are actually performed as a move on the file system mount in a special\n# directory called .eosdeletions. By default disabled.\n# EOS_FST_FSCK_DELETE_BY_MOVE=1\n\n# This variable overwrites the FST hostname in MGM redirection\n# - use it when the internal name is different from the external name\n# EOS_FST_ALIAS=\"\"\n\n# This variable overwrites the FST port in MGM redirection\n# - use it when the internal name is different from the external name\n# EOS_FST_PORT_ALIAS=1094\n\n# Enable XrdIo read-ahead functionality. By default disabled ie. 0.\n# EOS_FST_XRDIO_READAHEAD=0\n\n# Force disable XrdIo read-ahead even if this is enabled by using the above\n# env variable or through the fst.readahead opaque information. By default\n# disabled ie. 0 This can be useful in case read-ahead needs to be disabled\n# instance wide.\n# EOS_FST_XRDIO_READAHEAD_FORCE_DISABLE=0\n\n# In case XrdIo read-ahead is enabled this can control the number of blocks that\n# are pre-fetched. By default this is set to 2.\n# EOS_FST_XRDIO_READAHEAD_BLOCKS=2\n\n# In case XrdIo read-ahead is enabled this controls the block size of requests\n# that are pre-fetched. By default this is set to 1024*1024 (1MB).\n# EOS_FST_XRDIO_READAHEAD_BLOCK_SIZE=1024*1024\n\n# XFS filesystems will use file allocation by default unless explicitly disabled.\n#\n# XFS on zoned storage (e.g Host managed SMR drives) does not support fallocate\n# and requires the following variable to be defined to avoid write errors.\n# (the value is not considered)\n# EOS_FST_DISABLE_XFS_FALLOCATE=1\n\n# Other filesystems like EXT4 and BTRFS will not use fallocation by default\n# unless the following variable is defined (the value is not considered)\n# EOS_FST_POSIX_FALLOCATE=1\n\n# Enable automatic draining of file systems when S.M.A.R.T errors are detected.\n# The condition for this is that smartctl -q silent -a /dev/... returns a value\n# that has the 4th bit set to 1, which translated to \"FAILING\" status. This is\n# disabled by default. To enable it must be set to \"1\".\n# EOS_FST_DRAIN_ON_SMART_ERROR=0\n\n# EOS does not allow adding/booting a file system storing data on the root\n# partition without the .eosfsuuid file existing already. This protection is\n# in place in case the partition holding /data01 can not be mounted, then the\n# directory /data01 will sit on the root partition. This check can be disabled\n# by setting the following env variable (value is not considered).\n# EOS_FST_DISABLE_ROOT_PARTITION_CHECK=1\n\n#-------------------------------------------------------------------------------\n# GRPC Configuration\n#-------------------------------------------------------------------------------\n\n# GRPC port - set to 0 toi disable GRPC\n# EOS_MGM_GRPC_PORT=50051\n\n# GRPC security - define to enable SSL server\n# EOS_MGM_GRPC_SSL_CERT\n# EOS_MGM_GRPC_SSL_KEY\n# EOS_MGM_GRPC_SSL_CA\n\n# If this is set then the GRPC server does not request a client certificate\n# from the user contacting it (the value is not considered).\n# EOS_MGM_GRPC_DONT_REQUEST_CLIENT_CERTIFICATE=1\n\n#-------------------------------------------------------------------------------\n# Configure GRPC server dedicated for access via Windows native client (EOS-wnc)\n#-------------------------------------------------------------------------------\n\n# GRPC Wnc port - set to 0 to disable EOS-wnc server\n# EOS_MGM_WNC_PORT=50052\n\n# Define to enable SSL authentication on EOS-wnc server\n# EOS_MGM_WNC_SSL_CERT\n# EOS_MGM_WNC_SSL_KEY\n# EOS_MGM_WNC_SSL_CA\n\n#-------------------------------------------------------------------------------\n# REST API dedicated GRPC service\n#-------------------------------------------------------------------------------\n\n# Enable the REST API support. The effect of this env variable depends if the\n# code has been built with grpc-gateway (eos-grpc-gateway) support or not. To\n# have a fully functional REST API both conditions (built-in support and env\n# variable set to 1) need to be satisfied. Disabled by default i.e. 0.\n# EOS_MGM_ENABLE_REST_API=0\n\n# Set the port for the internal GRPC server handling the REST API requests.\n# Default value is 500054.\n# EOS_MGM_REST_GRPC_PORT=50054\n\n#-------------------------------------------------------------------------------\n# FUSEX Configuration\n#-------------------------------------------------------------------------------\n\n# Listener port of the ZMQ server used by FUSEx)\n# EOS_MGM_FUSEX_PORT=1100\n\n# Maximum number of 'listable' children\n# EOS_MGM_FUSEX_MAX_CHILDREN=32768\n\n#-------------------------------------------------------------------------------\n# Federation Configuration\n#-------------------------------------------------------------------------------\n\n# The host[:port] name of the meta manager (global redirector)\n# EOS_FED_MANAGER=eos.cern.ch:1094\n\n# The port of the PSS xrootd server\n# EOS_PSS_PORT=1098\n\n# The hostname[:port] of the EOS MGM service\n# EOS_PSS_MGM=$EOS_MGM_ALIAS:1094\n\n# The path which should be proxied (/ for all)\n# EOS_PSS_PATH=/\n\n#-------------------------------------------------------------------------------\n# Common configuration for MGM and FST\n#-------------------------------------------------------------------------------\n\n# Enable core dumps initiated internally (the value is not considered)\n# EOS_CORE_DUMP=1\n\n# Disable shutdown/signal handlers for debugging (the value is not considered)\n# EOS_NO_SHUTDOWN=1\n\n# Enable coverage report signal handler (the value is not considered)\n# EOS_COVERAGE_REPORT=1\n\n# If variable defined then enable the use of xrootd connection pool i.e.\n# create/share different physical connections for transfers to the same\n# destination xrootd server. By default this is disabled.\n# This applies both in context of the MGM server when it comes to TPC jobs and\n# also on the FST server for FST to FST transfers.\n# EOS_XRD_USE_CONNECTION_POOL=1\n\n# When xrootd connection pool is enabled, one can control the maximum number\n# of physical connection that can be established with the destination server.\n# The min value is 1 and the max 1024. By default this 1024.\n# EOS_XRD_CONNECTION_POOL_SIZE=128\n\n#-------------------------------------------------------------------------------\n# Test Configuration\n#-------------------------------------------------------------------------------\n\n# Mail notification for failed tests\n# EOS_TEST_MAILNOTIFY=apeters@mail.cern.ch\n\n# SMS notification for failed tests\n# EOS_TEST_GSMNOTIFY=\"0041764875002@mail2sms.cern.ch\"\n\n# Instance name = name of directory at deepness 2 /eos/<instance>/\n# EOS_TEST_INSTANCE=\"dev\"\n\n# MGM host redirector\n# EOS_TEST_REDIRECTOR=localhost\n\n# Local test output directory\n# EOS_TEST_TESTSYS=/tmp/eos-instance-test\n\n# Time to lock re-sending of SMS for consecutively failing tests\n# EOS_TEST_GSMLOCKTIME=3600\n\n# Max. time given to the test to finish\n# EOS_TEST_TESTTIMESLICE=600\n\n#-------------------------------------------------------------------------------\n# QuarkDB Configuration - used for testing\n#-------------------------------------------------------------------------------\n# QuarkDB Hostport\n# EOS_QUARKDB_HOSTPORT=localhost:9999\n\n# QuarkDB Password\n# EOS_QUARKDB_PASSWD=password_must_be_atleast_32_characters\n"
  },
  {
    "path": "misc/etc/systemd/system/eos.service",
    "content": "# ----------------------------------------------------------------------\n# File: eos.service\n# Author: Ivan Arizanovic - ComTrade Solutions Engineering\n# ----------------------------------------------------------------------\n#\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2016 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n[Unit]\nDescription=EOS All Services\n\n[Service]\nExecStartPre=/bin/sh -c \"/usr/sbin/eos_start_pre.sh eos-all\"\n\nExecStart=/bin/echo For status of daemons, \\\nrun \\'journalctl -e\\' or \\'systemctl status eos@*\\'.\n\nType=oneshot\nKillMode=none\n\n[Install]\nRequiredBy=multi-user.target\n"
  },
  {
    "path": "misc/etc/systemd/system/eos.target",
    "content": "[Unit]\nDescription=eos target allowing to start/stop all eos@*.service instances at once\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "misc/etc/systemd/system/eos5-fst@.service",
    "content": "[Unit]\nDescription=%I EOS5 FST service instance\n# When systemd stops or restarts the app.service, the action is propagated to this unit\nPartOf=eos5.service\n# Start this unit after the app.service start\nAfter=eos5-mgm.service\n\n[Service]\n# Pretend that the component is running\nExecStart=/usr/bin/eos daemon run fst %i\n# Restart the service on non-zero exit code when terminated by a signal other than SIGHUP, SIGINT, SIGTERM or SIGPIPE\nRestart=on-failure\n\n[Install]\n# This unit should start when app.service is starting\nWantedBy=eos5.service\n"
  },
  {
    "path": "misc/etc/systemd/system/eos5-mgm@.service",
    "content": "[Unit]\nDescription=%I EOS5 MGM service instance\n# When systemd stops or restarts the app.service, the action is propagated to this unit\nPartOf=eos5.service\n# Start this unit after the app.service start\nAfter=eos5-mq.service\n\n[Service]\n# Pretend that the component is running\nExecStart=/usr/bin/eos daemon run mgm %i\n# Restart the service on non-zero exit code when terminated by a signal other than SIGHUP, SIGINT, SIGTERM or SIGPIPE\nRestart=on-failure\n\n[Install]\n# This unit should start when app.service is starting\nWantedBy=eos5.service\n"
  },
  {
    "path": "misc/etc/systemd/system/eos5-qdb@.service",
    "content": "[Unit]\nDescription=%I EOS5 QDB service instance\n# When systemd stops or restarts the app.service, the action is propagated to this unit\nPartOf=eos5.service\n# Start this unit after the app.service start\nAfter=eos5.service\n\n[Service]\n# Pretend that the component is running\nExecStart=/usr/bin/eos daemon run qdb %i\n# Restart the service on non-zero exit code when terminated by a signal other than SIGHUP, SIGINT, SIGTERM or SIGPIPE\nRestart=on-failure\n\n[Install]\n# This unit should start when app.service is starting\nWantedBy=eos5.service\n"
  },
  {
    "path": "misc/etc/systemd/system/eos5.service",
    "content": "[Unit]\nDescription=EOS5 top level service\n\n[Service]\n# The dummy program will exit\nType=oneshot\n# Execute a dummy program\nExecStart=/bin/true\n# This service shall be considered active after start\nRemainAfterExit=yes\n\n[Install]\n# Components of this application should be started at boot time\nWantedBy=multi-user.target\n"
  },
  {
    "path": "misc/etc/systemd/system/eos@.service",
    "content": "# ----------------------------------------------------------------------\n# File: eos@.service\n# Author: Ivan Arizanovic - ComTrade Solutions Engineering\n# ----------------------------------------------------------------------\n#\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2016 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n[Unit]\nDescription=EOS %i\nAfter=network-online.target local-fs.target\nWants=network-online.target local-fs.target\nPartOf=eos.target\n\n[Service]\nWorkingDirectory=/var/eos\nEnvironmentFile=/etc/sysconfig/eos_env\n\nExecStartPre=/bin/sh -c \"/usr/sbin/eos_start_pre.sh eos-start-pre %i\"\nExecStart=/usr/sbin/eos_start.sh -n %i -c /etc/xrd.cf.%i -l /var/log/eos/xrdlog.%i -Rdaemon\nExecStop=/bin/sh -c \"/usr/sbin/eos_start_pre.sh eos-stop %i\"\n\nType=simple\nUser=root\nGroup=root\nRestart=on-failure\nRestartSec=5\nLimitNOFILE=65000\nTimeoutSec=120\n\n# Leverage 'soft-stop' feature of FST (waits for writes to complete || timeout)\nKillMode=mixed\n# ExitStatus is KILL because FST daemon. Stop-signal from FST daemon is 9/KILL.\nSuccessExitStatus=KILL\n\n[Install]\nWantedBy=eos.target\n"
  },
  {
    "path": "misc/etc/systemd/system/eos@.socket",
    "content": "[Unit]\nDescription=Eos daemon socket\n\n[Sockets]\n# Set the xrd port\nListenStream=1094\n# Set the http port\nListenStream=900\nService=eos@%i.service\n"
  },
  {
    "path": "misc/etc/systemd/system/eos@master.service",
    "content": "#-------------------------------------------------------------------------------\n# File: eos@master.service\n# Author: Ivan Arizanovic - ComTrade Solutions Engineering\n#-------------------------------------------------------------------------------\n#\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2016 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n[Unit]\nDescription=EOS Master\n\n[Service]\nEnvironmentFile=/etc/sysconfig/eos_env\n\nExecStartPre=/bin/sh -c \"/usr/sbin/eos_start_pre.sh eos-master\"\n\nExecStart=/usr/bin/echo \"Configured ${mq} ${mgm} on localhost as master\"\n\nType=oneshot\nKillMode=none\n\n[Install]\nRequiredBy=multi-user.target\n"
  },
  {
    "path": "misc/etc/systemd/system/eos@slave.service",
    "content": "#-------------------------------------------------------------------------------\n# File: eos@slave.service\n# Author: Ivan Arizanovic - ComTrade Solutions Engineering\n#-------------------------------------------------------------------------------\n#\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2016 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n[Unit]\nDescription=EOS Slave\n\n[Service]\nEnvironmentFile=/etc/sysconfig/eos_env\n\nExecStartPre=/bin/sh -c \"/usr/sbin/eos_start_pre.sh eos-slave\"\n\nExecStart=/usr/bin/echo \"Configured ${mq} ${mgm} on localhost as slave.\"\n\nType=oneshot\nKillMode=none\n\n[Install]\nRequiredBy=multi-user.target\n"
  },
  {
    "path": "misc/etc/xrd.cf.auth",
    "content": "# EOS Authentication Plugin configuration which can run on the same box as the \n# MGM instance or on a different one. Do not forget to open the MGM port in the firewall.\nxrd.port 1099\nxrootd.fslib /usr/lib64/libEosAuthOfs.so\nxrootd.seclib libXrdSec.so\n# Set the hostname where to connect\neosauth.mgm localhost:15555\neosauth.numsockets 10\neosauth.loglevel info\nxrootd.chksum adler\n# UNIX authentication + any other type of authentication\nsec.protocol unix\nsec.protbind localhost.localdomain unix\nsec.protbind localhost unix \nsec.protbind * only unix \n\nall.adminpath /var/spool/eos/core/\nall.export /\nxrootd.trace off\n"
  },
  {
    "path": "misc/etc/xrd.cf.fed",
    "content": "###########################################################################\n# This is a very simple sample configuration file sufficient to start an  #\n# xrootd data server using the default port 1094 and its companion cmsd.  #\n# Trying to use the xrootd will cause the client to simply wait because   #\n# there is no redirector and this configuration file is insufficient to   #\n# start one. Consult the reference manuals on how to create a usable      #\n# configuration file to completely describe a functional xrootd cluster.  #\n#                                                                         #\n# On start-up the xrootd will complain about not connecting to the pipe   #\n# named '/var/spool/xrootd/.olb/olbd.admin'. This will continue until the #\n# cmsd starts. When the cmsd start is will say ' Waiting for primary      #\n# server to login.' Once xrootd is started and connects to the cmsd, the  #\n# cmsd will complain 'Unable to connect socket to localhost' because      #\n# there is no redirector. However, this shows that xrootd and cmsd have   #\n# been correctly installed.                                               #\n#                                                                         #\n# Note: You should always create a *single* configuration file and use it #\n# when starting each daemon that you need to run in the cluster!          #\n###########################################################################\n\nset eospssport    = ${EOS_PSS_PORT}\nset eospssmgm     = ${EOS_PSS_MGM}\nset eospsspath    = ${EOS_PSS_PATH}\nset eosfedmanager = ${EOS_FED_MANAGER}\n\nxrd.port $eospssport\nofs.osslib libXrdPss.so\npss.origin $eospssmgm\nxrootd.redirect $eospssmgm $eospsspath\n\n\n# The export directive indicates which paths are to be exported. While the\n# default is '/tmp', we indicate it anyway and add the 'stages attribute\n# to allow you to start the frm_xfrd to bring in missing files into '/tmp'.\n# Remove this attribute if you don't want to enable this feature.\n#\nall.export $eospsspath \n\n# The role directive tells xrootd to run as a data server as part of a\n# cluster. The causes the xrootd to try to contact the local cmsd which\n# needs to started as part of of initialization. As a side note, a\n# redirector would have a manager role.\n#\nall.role server\n\n# The cmsd running on a data server node needs to know where the redirector\n# (i.e. manager) is running. In this generic config we simply say that it\n# is on this host to allow initialization to succeed. However, the final\n# result is not practically usable.\n#\nall.manager $eosfedmanager\n\n# The copycmd directive tells the frm_xfrd what to use to copy files into\n# an exported path with the 'stage' attribute. Here we just say this will\n# be '/bin/cp' to allow the frm_xfrd to actual start to show that it works.\n# Here missing files are created in /tmp as zero-length files.\n#\n#frm.xfr.copycmd /bin/cp /dev/null $PFN\n\n# The adminpath and pidpath variables indicate where the pid and various\n# IPC files should be placed\n#\nall.adminpath /var/spool/xrootd\nall.pidpath /var/run/xrootd\n"
  },
  {
    "path": "misc/etc/xrd.cf.fst",
    "content": "\t###########################################################\nset MGM=$EOS_MGM_ALIAS\n###########################################################\n\nxrootd.fslib -2 libXrdEosFst.so\nxrootd.async off nosf\nxrd.network keepalive\nxrootd.redirect $(MGM):1094 chksum\n\n###########################################################\nxrootd.seclib libXrdSec.so\nsec.protocol unix\nsec.protocol sss -c /etc/eos.client.keytab -s /etc/eos.client.keytab\nsec.protbind * only unix sss\n###########################################################\nall.export / nolock\nall.trace none\nall.manager localhost 2131\n#ofs.trace open\n###########################################################\nxrd.port 1095\nofs.persist off\nofs.osslib libEosFstOss.so\nofs.tpc pgm /usr/bin/xrdcp\n###########################################################\n# this URL can be overwritten by EOS_BROKER_URL defined /etc/sysconfig/xrd\nfstofs.broker root://localhost:1097//eos/\nfstofs.autoboot true\nfstofs.quotainterval 10\nfstofs.metalog /var/eos/md/\n#fstofs.authdir /var/eos/auth/\n#fstofs.trace client\n###########################################################\n# QuarkDB cluster info needed by FSCK to perform the namespace scan\n#fstofs.qdbcluster localhost:777\n#fstofs.qdbpassword_file /etc/eos.keytab\n# Use gRPC?\n#fstofs.protowfusegrpc true\n#fstofs.jwttokenpath /etc/grid-security/jwt-token-grpc\n#fstofs.protowfusegrpctls true\n\n#-------------------------------------------------------------------------------\n# Configuration for XrdHttp http(s) service on port 11000\n#-------------------------------------------------------------------------------\n#if exec xrootd\n#   xrd.protocol XrdHttp:11000 /usr/lib64/libXrdHttp-4.so\n#   http.exthandler EosFstHttp /usr/lib64/libEosFstHttp.so none\n#   http.cert /etc/grid-security/daemon/host.cert\n#   http.key /etc/grid-security/daemon/privkey.pem\n#   http.cafile /etc/grid-security/daemon/ca.cert\n#fi\n"
  },
  {
    "path": "misc/etc/xrd.cf.mgm",
    "content": "###########################################################\nxrootd.fslib libXrdEosMgm.so\nxrootd.seclib libXrdSec.so\nxrootd.async off nosf\nxrootd.chksum adler32\n###########################################################\n\nxrd.sched mint 8 maxt 256 idle 64\n###########################################################\nall.export / nolock\nall.role manager\n###########################################################\noss.fdlimit 16384 32768\n###########################################################\n# UNIX authentication\nsec.protocol unix\n# SSS authentication\nsec.protocol sss -c /etc/eos.client.keytab -s /etc/eos.keytab\n# KRB  authentication\n#sec.protocol krb5 -exptkn:/var/eos/auth/krb5#<uid> host/<host>@CERN.CH\nsec.protocol krb5 host/<host>@CERN.CH\n\n# GSI authentication\n#sec.protocol gsi -crl:0 -cert:/etc/grid-security/daemon/hostcert.pem -key:/etc/grid-security/daemon/hostkey.pem -gridmap:/etc/grid-security/grid-mapfile -d:0 -gmapopt:2 -vomsat:1 -moninfo:1 -exppxy:/var/eos/auth/gsi#<uid>\n\nsec.protocol gsi -crl:0 -cert:/etc/grid-security/daemon/hostcert.pem -key:/etc/grid-security/daemon/hostkey.pem -gridmap:/etc/grid-security/grid-mapfile -d:0 -gmapopt:2 -vomsat:1 -moninfo:1\n\n###########################################################\nsec.protbind localhost.localdomain unix sss\nsec.protbind localhost unix sss\nsec.protbind * only krb5 gsi sss unix\n###########################################################\nmgmofs.fs /\nmgmofs.targetport 1095\n#mgmofs.authlib libXrdAliceTokenAcc.so\n#mgmofs.authorize 1\n###########################################################\n#mgmofs.trace all debug\n# this URL can be overwritten by EOS_BROKER_URL defined in /etc/sysconfig/eos\n\nmgmofs.broker root://localhost:1097//eos/\n# this name can be overwritten by EOS_INSTANCE_NAME defined in /etc/sysconfig/eos\n\nmgmofs.instance eosdev\n\n# namespace, transfer and authentication export directory\nmgmofs.metalog /var/eos/md\nmgmofs.txdir /var/eos/tx\nmgmofs.authdir /var/eos/auth\nmgmofs.archivedir /var/eos/archive\n\n# report store path\nmgmofs.reportstorepath /var/eos/report\n\n# this defines the default config to load\nmgmofs.autoloadconfig default\n\n#-------------------------------------------------------------------------------\n# Config Engine Configuration\n#-------------------------------------------------------------------------------\nmgmofs.cfgtype file\n\n# this has to be defined if we have a failover configuration via alias - can be overwritten by EOS_MGM_ALIAS in /etc/sysconfig/eos\n#mgmofs.alias eosdev.cern.ch\n\n#-------------------------------------------------------------------------------\n# Configuration for the authentication plugin EosAuth\n#-------------------------------------------------------------------------------\n# Set the number of authentication worker threads running on the MGM\n#mgmofs.auththreads 10\n\n# Set the front end port number for incoming authentication requests\n#mgmofs.authport 15555\n\n# By default we listen only on localhost connections - set to 0 if you want to allow remote access\n#mgmofs.authlocal 1\n\n\n###########################################################\n# Set the FST gateway host and port\nmgmofs.fstgw someproxy.cern.ch:3001\n\n#-------------------------------------------------------------------------------\n# Configuration for the authentication plugin EosAuth\n#-------------------------------------------------------------------------------\n# Set the number of authentication worker threads running on the MGM\n#mgmofs.auththreads 10\n\n# Set the front end port number for incoming authentication requests\n#mgmofs.authport 15555\n\n#-------------------------------------------------------------------------------\n# Set the namespace plugin implementation\n#-------------------------------------------------------------------------------\nmgmofs.nslib /usr/lib64/libEosNsQuarkdb.so\n\n# Quarkdb custer configuration used for the namespace\n#mgmofs.qdbcluster localhost:7777\n#mgmofs.qdbpassword_file /etc/eos.keytab\n\n#-------------------------------------------------------------------------------\n# Configuration for the MGM workflow engine\n#-------------------------------------------------------------------------------\n\n# The SSI protocol buffer endpoint for notification messages from \"proto\" workflow actions\n#mgmofs.protowfendpoint HOSTNAME.2NDLEVEL.TOPLEVEL:10955\n#mgmofs.protowfresource /SSI_RESOURCE\n\n#-------------------------------------------------------------------------------\n# Confguration parameters for tape\n#-------------------------------------------------------------------------------\n\n#mgmofs.tapeenabled false\n#mgmofs.prepare.dest.space default\n#mgmofs.prepare.reqid.max 64\n\n#-------------------------------------------------------------------------------\n# Configuration for the tape aware garbage collector\n#-------------------------------------------------------------------------------\n\n# EOS spaces for which the tape aware garbage collector should be enabled\n#mgmofs.tgc.enablespace space1 space2 ...\n\n# Use gRPC?\n#mgmofs.protowfusegrpc true\n#mgmofs.protowfusegrpctls true\n#mgmofs.jwttokenpath /etc/grid-security/jwt-token-grpc\n"
  },
  {
    "path": "misc/etc/xrd.cf.prefix",
    "content": "###########################################################\n# --------------------------------------------------------\n# prefix redirector configuration on port 2000\n# 'eos -b set access redirect eosdev.cern.ch?eos.prefix=/eos/dev/'\n# --------------------------------------------------------\n###########################################################\nxrootd.fslib libXrdEosMgm.so\nxrootd.seclib libXrdSec.so\nxrootd.async off nosf\nxrootd.chksum eos\nxrd.port 2000\n###########################################################\n\nxrd.sched mint 8 maxt 256 idle 64\n###########################################################\nall.export /\nall.role manager\n###########################################################\noss.fdlimit 16384 32768\n###########################################################\n# UNIX authentication\nsec.protocol unix\n\n###########################################################\nsec.protbind * only unix\n###########################################################\nmgmofs.fs /\nmgmofs.targetport 1095\n#mgmofs.authlib libXrdAliceTokenAcc.so\n#mgmofs.authorize 1\n###########################################################\n#mgmofs.trace all debug\n# this URL can be overwritten by EOS_BROKER_URL defined in /etc/sysconfig/eos\n\nmgmofs.broker root://localhost:2000//eos/\n# this name can be overwritten by EOS_INSTANCE_NAME defined in /etc/sysconfig/eos\n\nmgmofs.instance eosdevpf1\n\n# startup with reduced functinoality if we are just a request redirecto\nmgmofs.redirector true\n\n# configuration and namespace location\nmgmofs.configdir /var/eos/prefix/config\nmgmofs.metalog /var/eos/prefix/md\nmgmofs.txdir /var/eos/prefix/tx/\nmgmofs.authdir /var/eos/prefix/auth/\n\n# report store path\nmgmofs.reportstorepath /var/eos/prefix/report\n\n# this defines the default config to load\nmgmofs.autoloadconfig default\n\n# this has to be defined if we have a failover configuration via alias - can be overwritten by EOS_MGM_ALIAS in /etc/sysconfig/eos\n#mgmofs.alias eosdev.cern.ch\n\n\n###########################################################\n"
  },
  {
    "path": "misc/etc/xrd.cf.quarkdb",
    "content": "xrd.port 9999\nxrd.protocol redis:9999 libXrdQuarkDB.so\n\nredis.mode raft\nredis.database /var/lib/quarkdb/node-1\n\n#----------------------------------------------------------\n# $EOS_QUARKDB_HOSTPORT environment variable must be set\n# with the same value used for redis.myself\n#----------------------------------------------------------\nredis.myself localhost:9999\n\n#----------------------------------------------------------\n# $EOS_QUARKDB_PASSWD environment variable must be set\n# with the same value used for redis.password\n#----------------------------------------------------------\n#redis.password password_must_be_atleast_32_characters\n"
  },
  {
    "path": "misc/etc/xrd.cf.sync",
    "content": "###########################################################\nxrootd.async off nosf\nxrd.network keepalive\n###########################################################\nxrootd.seclib libXrdSec.so\nsec.protocol sss -c /etc/eos.client.keytab -s /etc/eos.keytab\nsec.protocol sss\n###########################################################\nall.export /var/eos/ nolock\nall.trace none\nofs.trace open close\n###########################################################\nxrd.port 1096\n"
  },
  {
    "path": "misc/etc/zsh/site-functions/_eos",
    "content": "#compdef eos\n\nlocal current_word=\"${words[CURRENT]}\"\n\nif [[ ${current_word} == ./* || ${current_word} == ../* || ${current_word} == ~/* ]]; then\n  _path_files\n  return $?\nfi\n\ninteger eos_command_index=0\nlocal token\n\nfor (( eos_command_index = 2; eos_command_index < CURRENT; ++eos_command_index )); do\n  token=\"${words[eos_command_index]}\"\n\n  case \"${token}\" in\n    -a|--app)\n      (( ++eos_command_index ))\n      continue\n      ;;\n    -r|--role)\n      (( eos_command_index += 2 ))\n      continue\n      ;;\n    -b|--batch|-h|--help|-j|--json|-s|-v|--version)\n      continue\n      ;;\n    root://*|ipc://*)\n      continue\n      ;;\n    -*)\n      continue\n      ;;\n    *)\n      break\n      ;;\n  esac\ndone\n\nlocal -a eos_args completions\n\neos_args=(\"${(@)words[2,CURRENT-1]}\")\neos_args+=(\"${current_word}\")\n\ncompletions=(\"${(@f)$(eos __complete \"${eos_args[@]}\" 2>/dev/null)}\")\n\nif (( ${#completions[@]} == 0 )); then\n  if [[ ${current_word} != -* && ${current_word} != /eos* ]] &&\n     (( eos_command_index < CURRENT )) &&\n     [[ \"${words[eos_command_index]}\" == (acl|attr|cat|cd|chmod|chown|cp|du|file|fileinfo|find|info|ln|ls|mkdir|mv|rm|rmdir|stat|touch) ]]; then\n    _path_files\n    return $?\n  fi\n  return 1\nfi\n\nif (( ${#${(M)completions:#*/}} > 0 )); then\n  compadd -Q -f -S '' -- \"${completions[@]}\"\nelse\n  compadd -Q -- \"${completions[@]}\"\nfi\n"
  },
  {
    "path": "misc/sbin/CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2016 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ninstall(PROGRAMS\n  eos-status\n  eos_start.sh\n  eos_start_pre.sh\n  eos-reportstat\n  eos-mdstat\n  eos-mdreport\n  eos-inspectorreport\n  eos-inspectorstat\n  eos-prom-push\n  eos-jwk-https\n  eos-diagnostic-tool\n  DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR})\n\nif(TARGET eosxd)\n  install(PROGRAMS mount.eosx DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR})\nendif()\n\nif(TARGET eosxd3)\n  install(PROGRAMS umount.fuse mount.eosx3 mount.eoscfs DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR})\n\nendif()\n"
  },
  {
    "path": "misc/sbin/eos-diagnostic-tool",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: eos-diagnostic-tool\n# Author: Abhishek Lekshmanan - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2025 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nEXPORT_BASE_DIR=\"${EXPORT_BASE_DIR:-/tmp/eos-diagnostics}\"\nDUMP_DIR=\"${DUMP_DIR:-${EXPORT_BASE_DIR}/dump-$(date +\"%Y%m%dT%H%M%S\")}\"\nEXPORT_DIR=\"${DUMP_DIR}\"\nTARBALL_DIR=\"${EXPORT_BASE_DIR}/tarballs\"\n\n# directory tree within the export dir for various info\nSTATS_DIR=\"${EXPORT_DIR}/stats\"\nSTACKDUMP_DIR=\"${EXPORT_DIR}/stacktraces\"\nPROC_STATS_DIR=\"${EXPORT_DIR}/stacktraces/proc\"\nCONFIG_DIR=\"${EXPORT_DIR}/config\"\nCONFIG_ETC_DIR=\"${EXPORT_DIR}/config/etc\"\n\nEOS_MGM_URL=\"${EOS_MGM_URL:-ipc://}\"\n\nechoerr() { echo \"$@\" 1>&2; }\n\nlog_msg() {\n    local level=\"$1\"\n    local message=\"$2\"\n    local timestamp=$(date -Iseconds)\n    local log_file=\"${EXPORT_DIR}/eos-diagnostic.log\"\n\n    # Log to stderr only for interactive use cases\n    if [[ -t 1 ]]; then\n        echo \"${timestamp} [${level}] ${message}\" 1>&2\n    fi\n    echo \"${timestamp} [${level}] ${message}\" >> \"$log_file\"\n}\n\nlog_info() { log_msg \"INFO\" \"$*\"; }\nlog_debug() { log_msg \"DEBUG\" \"$*\"; }\nlog_error() { log_msg \"ERROR\" \"$*\"; }\n\nerror_exit() {\n    log_error \"$*\";\n    exit 1\n}\n\nget_timestamp() {\n  date +\"%Y%m%dT%H%M%S\"\n}\n\nsetup_dir_tree()\n{\n    mkdir -p \"$EXPORT_DIR\"\n    mkdir -p \"$STATS_DIR\"\n    mkdir -p \"$STACKDUMP_DIR\"\n    mkdir -p \"$PROC_STATS_DIR\"\n    mkdir -p \"$CONFIG_DIR\"\n    mkdir -p \"$CONFIG_ETC_DIR\"\n    mkdir -p \"$TARBALL_DIR\"\n}\n\ncollect_eos_stats()\n{\n    log_info \"Collecting EOS stats for ${EOS_MGM_URL}\"\n\n    ns_hang=$(timeout 30 eos ns stat -a -m 2>&1 | tee \"${STATS_DIR}/ns_all.info\" | grep ns.hanging)\n\n    if [[ -z \"${ns_hang}\" || \"${ns_hang}\" =~ ns.hanging=1 ]]; then\n        log_error \"MGM is stuck, skipping extraction of MGM stats in this run!\"\n        return 125\n    fi\n\n    eos who -a -m > \"${STATS_DIR}/who.info\"\n    eos io stat -x -m > \"${STATS_DIR}/iostat.info\"\n    eos fusex ls -m > \"${STATS_DIR}/fusex.info\"\n    eos node ls -m > \"${STATS_DIR}/node.info\"\n    eos space ls -m > \"${STATS_DIR}/space.info\"\n    eos config dump > \"${CONFIG_DIR}/eos.conf.dump\"\n}\n\n\nget_mgm_pid()\n{\n    local service_name=\"${1:-eos@mgm}\"\n    if systemctl -q is-active \"$service_name\"; then\n\t      log_debug \"getting mgm pid from systemd\"\n        MGM_PID=$(systemctl show -P MainPID \"${service_name}\")\n    fi\n\n    if [[ -z \"$MGM_PID\" ]]; then\n\t      log_debug \"trying xrootd mgm pid\"\n    \t  MGM_PID=$(pgrep -f 'xrootd.*mgm' -P 1)\n    fi\n\n    if [[ -z \"$MGM_PID\" ]]; then\n\t      log_debug \"tying eos-mgm pid\"\n\t      MGM_PID=$(pgrep -f 'eos-mgm')\n    fi\n\n    if [[ -z \"$MGM_PID\" ]]; then\n        # Time to bailout now!\n        error_exit \"Unable to determine MGM PID, exiting!\"\n    fi\n\n}\n\n\ngen_eu_stack()\n{\n    local pid=$1\n    log_info \"Dumping thread names\"\n    ps -T -p $pid -o tid,comm > \"${STACKDUMP_DIR}/thread-names-$(get_timestamp)\"\n    log_info \"Generating eu-stack for PID: $pid\"\n    eu-stack -p $pid > \"${STACKDUMP_DIR}/eu-stack-$(get_timestamp)\"\n}\n\ngen_stacks()\n{\n    local pid=$1\n\t  gen_eu_stack \"$pid\"\n\t  kill -SIGUSR2 \"$pid\"\n\t  sleep 5 && gen_eu_stack \"$pid\"\n}\n\ngen_proc_stats()\n{\n    local pid=$1\n    log_info \"Collecting /proc stats for PID: $pid\"\n\n    cp \"/proc/$pid/limits\" \"${PROC_STATS_DIR}/limits\"\n    cp \"/proc/$pid/status\" \"${PROC_STATS_DIR}/status\"\n    cp \"/proc/$pid/stack\" \"${PROC_STATS_DIR}/stack\"\n    cp \"/proc/$pid/maps\" \"${PROC_STATS_DIR}/maps\"\n    cp \"/proc/$pid/smaps\" \"${PROC_STATS_DIR}/smaps\"\n    cp \"/proc/$pid/stat\" \"${PROC_STATS_DIR}/stat\"\n    cp \"/proc/$pid/statm\" \"${PROC_STATS_DIR}/statm\"\n}\n\nget_config()\n{\n    cp /etc/syconfig/eos_env \"${CONFIG_ETC_DIR}/\"\n    cp /etc/xrd.cf.* \"${CONFIG_ETC_DIR}/\"\n    cp -r /etc/eos/ \"${CONFIG_ETC_DIR}/\"\n}\n\nmake_tarball()\n{\n    local dump_dir_name=\"$(basename \"${EXPORT_DIR}\")\"\n    DUMP_TARBALL=\"${TARBALL_DIR}/${dump_dir_name}.tar.xz\"\n    pushd \"$(dirname \"${EXPORT_DIR}\")\" > /dev/null\n    tar -cJf \"${DUMP_TARBALL}\" \"${dump_dir_name}\"\n    log_info \"Tarballs at ${DUMP_TARBALL}\"\n    popd > /dev/null\n}\n\nsetup_dir_tree\nget_mgm_pid\nlog_info \"Found MGM PID as ${MGM_PID}\"\ngen_stacks \"$MGM_PID\" &\ngen_proc_stats \"$MGM_PID\" &\ncollect_eos_stats &\nget_config &\nlog_info \"Dumping diagnostics at ${EXPORT_DIR}\"\nwait\nmake_tarball\n"
  },
  {
    "path": "misc/sbin/eos-inspectorreport",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: eos-inspectorreport\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2024 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n# just check if we have an MGM locally, otherwise we don't do anything\ntimeout 5 eos root://localhost whoami >& /dev/null || exit 0\neos root://localhost inspector -a -m > /var/eos/md/inspectorstats.tmp\nif [ -e \"/var/eos/md/inspectorstats.0\" ]; then\n    cp /var/eos/md/inspectorstats.0 /var/eos/md/inspectorstats.1\nfi\nmv /var/eos/md/inspectorstats.tmp /var/eos/md/inspectorstats.0\n\n\n"
  },
  {
    "path": "misc/sbin/eos-inspectorstat",
    "content": "#!/usr/bin/env python3\n\n# ----------------------------------------------------------------------\n# File: eos-inspectorstat\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2024 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nimport sys\nimport time\nimport json\nimport os\n\ncostinspector = {}\n\nnow = int(time.time())\n\ndef dict2prom(data_dict):\n    prefix = \"eos\"\n    category = \"inspector\"\n    subcategory = \"user\"\n    metric_name = \"_\".join([prefix, category, subcategory])\n    help_text = \"User storage costs derived from inspector output\"\n\n    prom_output = f\"# HELP {metric_name} {help_text}\\n\"\n    prom_output += f\"# TYPE {metric_name} gauge\\n\"\n\n    for uid, metrics in data_dict.items():\n        if uid == \"uid\":\n            continue\n\n        for metric, value in metrics.items():\n            if metric == \"uid\":\n                continue\n\n            prom_output += f\"{metric_name}{{uid=\\\"{uid}\\\", measure=\\\"{metric}\\\"}}\" f\" {value}\\n\"\n\n    return prom_output\n\nIGNORED_KEYS = {\"tag\", \"username\", \"key\"}\n\ndef read_textfile(file_path, cost):\n    with open(file_path, 'r') as file:\n        for line in file:\n            data = dict(item.split('=', 1) for item in line.strip().split())\n            uid = data.get('uid')\n            if uid:\n                cost.setdefault(uid, {})\n                for key, value in data.items():\n                    if key not in IGNORED_KEYS:\n                        cost[uid][key] = value\n                cost[uid][\"unixtime\"] = int(time.time())\n    return cost\n\narguments = sys.argv\n\nf = \"/var/eos/md/inspectorstats.0\"\n\ntofile=\"\"\ntofiletmp=\"\"\npromfile=\"\"\npromfiletmp=\"\"\n\nif len(arguments) > 1:\n    f = arguments[1]\nelse:\n    tofile=\"/var/eos/md/inspector-report.json\"\n    tofiletmp = tofile + \".tmp\"\n    promfile=\"/var/eos/md/inspector-report.prom\"\n    promfiletmp = promfile + \".tmp\"\n    \nread_textfile(f, costinspector)\n\njsondump = json.dumps(costinspector, indent=4)\nif len(tofile):\n    with open(tofiletmp, \"w\") as file:\n        file.write(jsondump)\n        os.rename(tofiletmp,tofile)\n\nif len(promfile):\n    with open(promfiletmp, \"w\") as file:\n        file.write(dict2prom(costinspector))\n        os.rename(promfiletmp, promfile)\nelse:\n    print(jsondump)\n"
  },
  {
    "path": "misc/sbin/eos-jwk-https",
    "content": "#!/usr/bin/env python3\n\n# ----------------------------------------------------------------------\n# File: eos-jwk-https\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2024 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nfrom http.server import HTTPServer, BaseHTTPRequestHandler\nimport ssl\nimport socket\nimport os\n\nclass SimpleHTTPRequestHandler(BaseHTTPRequestHandler):\n    def do_GET(self):\n        wellknown = '{\"jwks_uri\":\"https://';\n        wellknown += socket.getfqdn()\n        wellknown += \":4443\"\n        wellknown += '/jwk\"}'\n        if self.path == \"/.well-known/openid-configuration\":\n           self.send_response(200)\n           self.end_headers()\n           self.wfile.write(bytes(wellknown,'UTF-8'))\n        else:\n           if self.path == \"/jwk\":\n              self.send_response(200)\n              self.end_headers()\n              self.wfile.write(bytes(os.environ[\"EOS_JWK\"],'UTF-8'))\n           else:\n              self.send_error(404, \"File not found\")\n              return\n\nhttpd = HTTPServer(('', 4443), SimpleHTTPRequestHandler)\n\nhttpd.socket = ssl.wrap_socket (httpd.socket, \n        keyfile='/etc/grid-security/daemon/hostkey.pem', \n        certfile='/etc/grid-security/daemon/hostcert.pem', server_side=True)\n\nhttpd.serve_forever()\n"
  },
  {
    "path": "misc/sbin/eos-jwker.readme",
    "content": "eos-jwker is a GO application as a binary for ALMA9\n- to update do:\nyum install -y go\ngo install github.com/jphastings/jwker/cmd/jwker@latest\n- rename the GO binary jwker to ./misc/sbin/eos-jwker\n"
  },
  {
    "path": "misc/sbin/eos-mdreport",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: eos-mdreport\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2024 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n# just check if we have an MGM locally, otherwise we don't do anything\ntimeout 5 eos root://localhost whoami >& /dev/null || exit 0\neos root://localhost ns stat -a -n -m > /var/eos/md/mdstats.tmp\nif [ -e \"/var/eos/md/mdstats.0\" ]; then\n    cp /var/eos/md/mdstats.0 /var/eos/md/mdstats.1\nfi\nmv /var/eos/md/mdstats.tmp /var/eos/md/mdstats.0\n\n\n"
  },
  {
    "path": "misc/sbin/eos-mdstat",
    "content": "#!/usr/bin/env python3\n\n# ----------------------------------------------------------------------\n# File: eos-mdstat\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2024 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nimport sys\nimport time\nimport json\nimport os\n\nmdcost1 = {}\nmdcost2 = {}\n\nshortnames = {\"OpenRead\":\"open:r\",\"OpenWrite\":\"open:w\",\"OpenWriteTruncate\":\"open:t\", \"OpenWriteCreate\":\"open:c\",\"OpenProc\":\"cli\",\"Eosxd::prot::LS\":\"fuse:ls\",\"Eosxd::prot::STAT\":\"fuse:stat\",\"Eosxd::prot::SET\":\"fuse:set\",\"Eosxd::ext::LS-Entry\":\"fuse:entry\",\"IdMap\":\"generic:op\",\"Delay\":\"delay\",\"Stall\":\"stall\"}\n\nnow = int(time.time())\n\ndef dict2prom(data_dict):\n    prom = \"\"\n    prefix = \"eos\"\n    category = \"md\"\n    subcategory = \"user\"\n    metric = f\"{prefix}_{category}_{subcategory}\"\n\n    prom += f\"# HELP {metric} User meta-data service usage derived from eos ns stat\\n\"\n    prom += f\"# TYPE {metric} gauge\\n\"\n\n    for key, value in data_dict.items():\n        if key == \"uid\":\n            continue\n\n        for tag, val in value.items():\n            if tag == \"uid\":\n                continue\n\n            prom += f\"{prefix}_{category}_{subcategory}{{uid=\\\"{key}\\\", measure=\\\"{tag}\\\"}} {val}\\n\"\n\n    return prom\n\ndef read_textfile(file_path, mdcost):\n    with open(file_path, 'r') as file:\n        for line in file:\n            dict = {}\n            parts = line.strip().split()\n            for item in parts:\n                key, value = item.split('=', 1)\n                key = key.strip()\n                value = value.strip()\n                # Store key-value pair in the dictionary\n                dict[key] = value\n            if 'uid' in dict:\n                if dict['uid'] == \"all\":\n                    continue;\n\n                if dict['uid'] not in mdcost:\n                    mdcost[dict['uid']] = {}\n\n                if dict['uid'] == \"all\":\n                    if dict['gid'] == \"all\":\n                        if 'cmd' in dict:\n                            if dict['cmd'].startswith((\"Stall::threads::\",\"Delay::threads::\")):\n                                if dict['cmd'].endswith((\"ms\")):\n                                    continue\n                                n=dict['total']\n                                elements = dict['cmd'].split(\"::\")\n                                user = elements[-1]\n                                if user not in mdcost:\n                                    mdcost[user] = {}\n                                mdcost[user][elements[0]]=n\n                    continue\n                if 'cmd' in dict:\n                    user=dict['uid']\n                    if dict['cmd'].startswith((\"OpenRead\",\"OpenWrite\",\"OpenWriteCreate\",\"OpenProc\",\"Eosxd::prot::LS\",\"Eosxd::prot::STAT\",\"Eosxd::prot::SET\",\"Eosxd::ext::LS-Entry\",\"IdMap\")):\n                        cmd=dict['cmd']\n                        n=dict['total']\n                        mdcost[user][cmd]=n\n    return\n\narguments = sys.argv\n\nf1 = \"/var/eos/md/mdstats.1\"\nf2 = \"/var/eos/md/mdstats.0\"\n\ntofile=\"\"\ntofiletmp=\"\"\npromfile=\"\"\npromfiletmp=\"\"\n\nif len(arguments) > 2:\n    f1 = arguments[1]\n    f2 = arguments[2]\nelse:\n    tofile=\"/var/eos/md/md-report.json\"\n    tofiletmp = tofile + \".tmp\"\n    promfile=\"/var/eos/md/md-report.prom\"\n    promfiletmp= promfile + \".tmp\"\n\n    read_textfile(f1, mdcost1)\nread_textfile(f2, mdcost2)\n\nmddiff = {}\nfor user,cost in mdcost2.items():\n    mddiff[user] = {}\n    for key,value in cost.items():\n        if user in mdcost1:\n            if key in mdcost1[user]:\n                if int(value) >= int(mdcost1[user][key]):\n                   newvalue=int(value)-int(mdcost1[user][key])\n            else:\n                newvalue=int(value)\n        else:\n            newvalue=int(value)\n        mddiff[user][shortnames[key]]=newvalue\n    for item in shortnames:\n        if shortnames[item] not in mddiff[user]:\n            mddiff[user][shortnames[item]]=0\n        mddiff[user][\"unixtime\"]=now\n\njsondump = json.dumps(mddiff, indent=4)\nif len(tofile):\n    with open(tofiletmp, \"w\") as file:\n        file.write(jsondump)\n        os.rename(tofiletmp,tofile)\nelse:\n    print(jsondump)\n\nif len(promfile):\n    with open(promfiletmp, \"w\") as file:\n        file.write(dict2prom(mddiff))\n        os.rename(promfiletmp, promfile)\n"
  },
  {
    "path": "misc/sbin/eos-prom-push",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: eos-prom-push\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2024 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n# ************************************************************************\n# the push endpoint can be overwritten in /etc/sysconfig/eos_prom\n# EOS_PROM_URL=...\n# the push can be disabled in /etc/sysconfig/eos_prom\n# EOS_PROM_DISABLE=1\n# ************************************************************************\n# By default the push is enabled!\n# ************************************************************************\n\n# ************************************************************************\n# just check if we have an MGM locally, otherwise we don't do anything\nconfig=$(timeout 5 eos root://localhost version 2>/dev/null)\ntest $? -ne 0 && exit 0\nexport $config\nEOS_PROM_URL=\"http://eos-prometheus.cern.ch:9091/metrics/job/user_inspector/instance\"\nEOS_PROM_CLIENT=`hostname -f`\ntest -e \"/etc/sysconfig/eos_prom\" && . /etc/sysconfig/eos_prom\n\nif [ \"${EOS_PROM_DISABLE}\" == \"1\" ]; then\n    exit 0;\nfi    \n\n# ************************************************************************\n# Publish only files created in the last 5 minutes window\nfor name in `find /var/eos/md -type f -cmin -5 -name \"*.prom\"`; do\n    cat $name | timeout 10 curl --data-binary @- ${EOS_PROM_URL}/${EOS_PROM_CLIENT}/cluster/${EOS_INSTANCE} \ndone\n\n\n"
  },
  {
    "path": "misc/sbin/eos-reportstat",
    "content": "#!/usr/bin/env python3\n# ----------------------------------------------------------------------\n# File: eos-reportstat\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2024 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nimport sys;\nimport time;\nimport re;\nimport os;\nimport json;\nimport datetime;\n\narguments = sys.argv\n\nuserstats = {}\n\ndef dict2prom(data_dict):\n    \"\"\"\n    Converts a dictionary to a Prometheus metrics format string.\n    \"\"\"\n    prefix = \"eos\"\n    category = \"report\"\n    subcategory = \"user\"\n    metric_name = \"_\".join([prefix, category, subcategory])\n    help_text = \"User data service usage derived from eos io reports\"\n\n    prom_output = f\"# HELP {metric_name} {help_text}\\n\"\n    prom_output += f\"# TYPE {metric_name} gauge\\n\"\n\n    for uid, metrics in data_dict.items():\n        if uid == \"uid\":\n            continue\n\n        for metric, value in metrics.items():\n            if metric == \"uid\":\n                continue\n\n            prom_output += f'{metric_name}{{uid=\"{uid}\", measure=\"{metric}\"}} {value}\\n'\n\n    return prom_output\n\n\ndef read_file_backwards(file_path):\n    with open(file_path, 'rb') as file:\n        file.seek(0, os.SEEK_END)  # Move the pointer to the end of the file\n        file_size = file.tell()  # Get the current position, which is the size of the file\n\n        lines = []\n        while file.tell() > 2:\n            file.seek(-2, os.SEEK_CUR)  # Move the pointer back by 2 bytes\n            char = file.read(1)\n            if char == b'\\n':\n                lines.reverse()\n                yield b''.join(lines).decode()\n                lines = []\n            else:\n                lines.append(char)\n        if lines:\n            lines.reverse()\n            yield b''.join(lines).decode()\n\nnow = int(time.time())\nyear = datetime.datetime.now().year\nmonth = datetime.datetime.now().strftime('%m')\nday = datetime.datetime.now().strftime('%d')\ntofile=\"\"\ntofiletmp=\"\"\npromfile=\"\"\npromfiletmp=\"\"\n\nif len(arguments) == 1:\n    file_path = \"/var/eos/report/\" + str(year) + \"/\" + str(month) + \"/\" + str(year) + str(month) + str(day) + \".eosreport\"\n    tofile=\"/var/eos/md/data-report.json\"\n    tofiletmp=tofile + \".tmp\"\n    promfile=\"/var/eos/md/data-report.prom\"\n    promfiletmp=promfile + \".tmp\"\nelse:\n    file_path = arguments[1]\nif len(arguments) > 2:\n    age = int(arguments[2])\nelse:\n    age = 300\n\nif not os.path.isfile(file_path):\n    sys.exit()\n\nkeys = [\"nrc\",\"nwc\",\"rb\",\"wb\",\"rv_op\",\"rvb_sum\"];\nfor line in read_file_backwards(file_path):\n    closetime = r'&cts=(\\d+)&'\n    cts= re.search(closetime, line);\n    rline=line.replace('&', \" \")\n    if cts:\n        if (now-int(cts.group(1))) < age:\n            # get all the things we want here\n            keyval = r'\\b(\\w+)=(\\d+)\\b'\n            pairs = dict(re.findall(keyval, rline))\n            for name in keys:\n                if pairs['ruid'] not in userstats:\n                    userstats[pairs['ruid']] = {}\n                if name not in userstats[pairs['ruid']]:\n                    userstats[pairs['ruid']][name] = 0;\n                else:\n                    userstats[pairs['ruid']][name]+=int(pairs[name])\n        else:\n            break\nfor user,stats in userstats.items():\n    userstats[user]['unixtime']=now\n\njsondump = json.dumps(userstats, indent=4)\n\nif len(tofile):\n    with open(tofiletmp, \"w\") as file:\n        file.write(jsondump)\n        os.rename(tofiletmp,tofile)\nelse:\n    print(jsondump)\n\nif len(promfile):\n    with open(promfiletmp, \"w\") as file:\n        file.write(dict2prom(userstats))\n        os.rename(promfiletmp, promfile)\n"
  },
  {
    "path": "misc/sbin/eos-status",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: eos-status\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2016 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nTMPDIR=\"/tmp/.eos.s.$RANDOM\"\nmkdir -p $TMPDIR\n\n# INFO GATEHERING\nexport EOS_MGM_URL=ipc://\nQDB=`cat /etc/xrd.cf.mgm | grep qdbcluster | awk '{print $2}' | cut -d \":\" -f 1` QDBPWFILE=`cat /etc/xrd.cf.mgm | grep qdbpassword_file | awk '{print $2}'` QDBPW=`cat ${QDBPWFILE}` && redis-cli -c -p 7777 -h \"${QDB}\" -a \"${QDBPW}\" raft-leader-info >& $TMPDIR/qdb.info\n\neos version > $TMPDIR/version.info\neos ns > $TMPDIR/ns.info\neos ns stat -m > $TMPDIR/nsstat.info\neos node ls -m > $TMPDIR/node.info\neos space ls -m > $TMPDIR/space.info\neos fs ls -m > $TMPDIR/fs.info\neos quota ls -m > $TMPDIR/quota.info\neos group ls -m > $TMPDIR/group.info\neos fusex ls -m > $TMPDIR/fusex.info\neos who -a -m > $TMPDIR/who.info\neos vid ls > $TMPDIR/vid.info\neos health -m > $TMPDIR/health.info\neos io stat -x -m > $TMPDIR/iostat.info\ninstance=`cat $TMPDIR/version.info  | grep EOS_INSTANCE | cut -d\"=\" -f 2`\nhealth=\"OK\";\ncat $TMPDIR/node.info | awk '{print $3,$4}' | sed s/=/\\ /g | awk '{print $2,$4}' | sort | uniq -c > $TMPDIR/nodes.info\ncat $TMPDIR/node.info | sed s/\\ /\\\\n/g | grep cfg.stat.sys.eos.version  | cut -d \"=\" -f 2 | sort | uniq -c > $TMPDIR/versions.info\ncat $TMPDIR/version.info  | grep EOS_SERVER_VERSION | sed s/=/\\ /g | awk '{printf(\"%s-%s\\n\", $2,$4);}' | sort | uniq -c > $TMPDIR/mgmversion.info\ncat $TMPDIR/qdb.info  | grep VERSION  | awk '{ print $NF }' | sort | uniq -c > $TMPDIR/qdbversion.info\ncat $TMPDIR/space.info | awk '{print $2, $15, $17, $18,$22}' | sed s/=/\\ /g | awk '{printf(\"%-10s (%.02f PB total / %.02f PB used %.02f PB free / %.02f PB avail )\\n\", $2, ($8+1)/1000000000000000.0,($4+1)/1000000000000000.0, ($6+1)/1000000000000000.0, ($10+1)/1000000000000000.0);}' > $TMPDIR/spaces.info\ncat $TMPDIR/group.info | awk '{print $2, $3'} | sed s/=/\\ /g | awk '{print $2,$4}' | cut -d \".\" -f 1 | awk '{print $2,$1}' | sort | uniq -c > $TMPDIR/groups.info\ncat $TMPDIR/fs.info | awk '{print $6,$7,$8}' | sed s/=/\\ /g | awk '{print $6, $4,$2}' | cut -d \".\" -f 1 | awk '{print $3,$2,$1}' | sort  | uniq -c > $TMPDIR/fss.info\ncat $TMPDIR/who.info | awk '{print $3,$6}' | sort | grep auth| sed s/auth=// | sed s/app=// | sort | uniq -c | awk '{printf(\"%7s %-5s (%-s)\\n\",$1,$2,$3);}' > $TMPDIR/whos.info\ncat $TMPDIR/iostat.info | grep app_io_out | sed s/\\ /\\\\n/g  | grep 60s | sed s/60s// | awk '{sum+=$1; printf(\"%.02f GB/s\\n\", (sum+1)/1000000000.0/60.0);}' | tail -1 > $TMPDIR/ioout.info\ncat $TMPDIR/iostat.info | grep app_io_in | sed s/\\ /\\\\n/g  | grep 60s | sed s/60s// | awk '{sum+=$1; printf(\"%.02f GB/s\\n\", (sum+1)/1000000000.0/60.0);}' | tail -1 > $TMPDIR/ioin.info\ncat $TMPDIR/ns.info | grep eosxd | awk '{ print $NF }' | awk '{printf(\"%s \",$1);}' | awk '{printf(\"%s clients (%s active) caps %s locked %s\\n\", $2,$3,$1,$4);}' > $TMPDIR/eosxd.info\ncat $TMPDIR/fusex.info | awk '{print $3,$4 }' | sed s/=/\\ /g | awk '{print $2,$4}' | sort | uniq -c | awk '{printf(\"%7s %-16s %-s\\n\", $1,$2,$3);}' > $TMPDIR/eosxdv.info\ncat $TMPDIR/fusex.info | awk '{print $17}' | sed s/=/\\ /g | awk '{print $4,$2}' | sort | uniq -c | awk '{printf(\"%7s %-16s %-s\\n\", $1,$2,$3);}' > $TMPDIR/eosxdm.info\ncat $TMPDIR/fusex.info | grep \"/eos/home-\" | awk '{print $17,$18}' | sed s/=/\\ /g | awk '{print $4,$2}' | sort | uniq -c | awk '{printf(\"%7s %-16s %-s\\n\", $1,$2,$3);}' > $TMPDIR/eosxdh.info\ncat $TMPDIR/fusex.info | awk '{print $28,$38,$39,$40}' | sed s/=/\\ /g  | awk '{s1+=$2; s2+=$4; s3+=$6; s4+=$8;n++; printf(\"%.02f MB/s IN %.02f MB/s OUT %d kIOPS %d MB RSS\\n\", s2,s3,s4,s1/n);}' | tail -1 > $TMPDIR/eosxdio.info\nqdbstatus=`cat $TMPDIR/qdb.info  | grep NODE-HEALTH | awk '{print $2}'`\n\nmasterid=`cat $TMPDIR/ns.info | grep master_id | awk '{ print $NF }' | cut -d \"=\" -f 2`\ntest -z $masterid && masterid=\"[no lease support]\"\nboottime=`cat $TMPDIR/ns.info  | grep \"Total boot\" | awk '{print $5,$6}'`\nbootstatus=`cat $TMPDIR/ns.info  | grep Files | awk '{print $4}'`;\nfiles=`cat $TMPDIR/ns.info | grep Files | awk '{print $3}'`\ndirectories=`cat $TMPDIR/ns.info | grep Directories | awk '{print $3}'`\nioin=`cat $TMPDIR/ioin.info`\nioout=`cat $TMPDIR/ioout.info`\nnclients=`cat $TMPDIR/who.info | wc -l`;\nfillratio=`eos geosched show param | grep fillRatioLimit | awk '{print $3}'`\ncriticalgroups=`cat $TMPDIR/health.info | grep critical_group | awk '{print $6}' | cut -d \"=\" -f 2`\nncriticalgroups=`cat $TMPDIR/health.info | grep critical_group | wc -l`\nplacementcontention=`cat $TMPDIR/health.info | grep PlacementContentionCheck | grep -v \"full_fs=0\"| wc -l`\nnfstoffline=`cat $TMPDIR/nodes.info | grep -v \"online on\" |wc -l`\nngroupoffline=`cat $TMPDIR/group.info | grep -v \"cfg.status=on\" |wc -l`\nnfsdown=`cat $TMPDIR/fs.info | grep -v booted | wc -l`\n\nhealthmsg=\"\"\n\nif ( test $ngroupoffline -gt 0 ); then\n  if ( test \"$health\" = \"OK\" ); then\n    health=\"WARN\"\n  fi\n  healthmsg=\"$healthmsg group-offline:$ngroupoffline\"\nfi\n\nif ( test $nfsdown -gt 0 ) ; then\n  if ( test \"$health\" = \"OK\" ); then\n    health=\"WARN\"\n  fi\n  healthmsg=\"$healthmsg fs-down:$nfsdown\"\nfi\n\nif ( test $placementcontention -gt 0 ); then\n  health=\"CRIT\"\n  healthmsg=\"$healthmsg crit-contention:$placementcontention\"\n  if ( test $ncriticalgroups -gt 0 ); then\n    healthmsg=\"$healthmsg crit-groups:$ncriticalgroups\"\n  fi\nfi\n\nif ( test $nfstoffline -gt 0 ); then\n  if ( test \"$health\" = \"OK\" ); then\n    health=\"WARN\"\n  fi\n  healthmsg=\"$healthmsg node-offline:$nfstoffline\"\nfi\n\nif ( test $fillratio -gt 98 ); then\n  if ( test \"$health\" = \"OK\" ); then\n    health=\"WARN\"\n  fi\n  healthmsg=\"$healthmsg sched-ratio:$fillratio\"\nfi\n\nfunction indent()\n{\n(\n    first=\"$1\"\n      IFS=\n      read -r line\n      printf \"%*s %s\\n\" ${#first} \"$first\" \"$line\"\n      while read -r line; do\n          printf \"%*s %s\\n\" ${#first} \"\" \"$line\"\n      done\n) < $TMPDIR/$2\n}\n\necho    \"instance: $instance\"\necho    \"          health:    \" \"$health       $healthmsg\"\nindent  \"          nodes:      fst\" \"nodes.info\"\nindent  \"          versions:   mgm\" \"mgmversion.info\"\nindent  \"                      qdb\" \"qdbversion.info\"\nindent  \"                      fst\" \"versions.info\"\necho\necho    \"services:          \"\necho    \"                      $masterid (active)\"\necho    \"                      namespace $bootstatus [$boottime]\"\necho    \"                      qdb [$qdbstatus]\"\necho\nindent  \"storage:  data:      \"  \"spaces.info\"\necho    \"          meta-data: \"  \"$files files $directories directories\"\nindent  \"          groups:        \" \"groups.info\"\nindent  \"          filesystems:   \" \"fss.info\"\necho    \"          scheduler:     \" \"$fillratio% (fill limit)\"\necho\necho   \"clients:  $nclients clients\"\nindent  \"          auth:          \" \"whos.info\"\necho    \"          io:        \"  \"$ioin IN $ioout OUT\"\necho\nindent  \"          fuse :     \"  \"eosxd.info\"\nindent  \"                     \"  \"eosxdio.info\"\nindent  \"                      v: \"  \"eosxdv.info\"\nindent  \"                      t: \"  \"eosxdm.info\"\nindent  \"                      h: \"  \"eosxdh.info\"\n\n# CLEANUP\nrm -rf $TMPDIR\n"
  },
  {
    "path": "misc/sbin/eos_start.sh",
    "content": "#!/usr/bin/env sh\n\n# Decide which xrootd binary to use\nXROOTD_BINARY=/usr/bin/xrootd\n\nif [ -x /opt/eos/xrootd/bin/xrootd ]; then\n  XROOTD_BINARY=/opt/eos/xrootd/bin/xrootd\nfi\n\n# When EOS_ZSTD_LOGGING is enabled (via /etc/sysconfig/eos_env, imported by the\n# systemd unit as EnvironmentFile=), the EOS Logging class ingests xrootd\n# diagnostics from STDERR and writes them as ZSTD-compressed rotating segments.\n# In that case we must NOT pass '-l <logfile>' to xrootd, or it would redirect\n# stdout/stderr to that file and the Logging pipeline would see nothing.\ncase \"$(echo \"${EOS_ZSTD_LOGGING:-}\" | tr '[:upper:]' '[:lower:]')\" in\n  1|true|yes|on) USE_STDERR=1 ;;\n  *)            USE_STDERR=0 ;;\nesac\n\nARGS='\"$@\"'\nLOGGING_MODE=\"\"\n\nif [ \"${USE_STDERR}\" = \"1\" ]; then\n  # Strip any '-l <path>' pair from the argument list so xrootd logs to stderr.\n  ARGS=\"\"\n  LOGGING_MODE=\" (EOS_ZSTD_LOGGING=${EOS_ZSTD_LOGGING} : logging to stderr)\"\n  SKIP=0\n  for a in \"$@\"; do\n    if [ \"${SKIP}\" = \"1\" ]; then\n      SKIP=0\n      continue\n    fi\n    case \"${a}\" in\n      -l)\n        SKIP=1\n        ;;\n      -l*)\n        # '-l/path' form (no space) - drop it as well\n        ;;\n      *)\n        ARGS=\"${ARGS} \\\"${a}\\\"\"\n        ;;\n    esac\n  done\nfi\n\necho \"Using xrootd binary: ${XROOTD_BINARY}${LOGGING_MODE}\"\neval \"exec \\\"\\${XROOTD_BINARY}\\\" ${ARGS}\"\n"
  },
  {
    "path": "misc/sbin/eos_start_pre.sh",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: eos_start_pre.sh\n# Author: Ivan Arizanovic - ComTrade Solutions Engineering\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2016 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n. /etc/sysconfig/eos_env\n\n# Start All EOS daemons (all required daemons from config file)\nif [ \"$1\" = \"eos-all\" ]; then\n  if [[ -z \"$XRD_ROLES\" ]]; then\n    echo \"<3>Error: No XRD_ROLES variable declared in \\\"/etc/sysconfig/eos_env\\\"\"\n    exit 1\n  fi\n\n  for i in ${XRD_ROLES}; do\n    systemctl start eos@${i} &\n  done\n\n  # Wait for all the daemons to start\n  FAIL=0\n  for job in `jobs -p`;do\n    echo \"<5>Waiting for $job ...\"\n    wait $job || let \"FAIL+=1\"\n  done\n\n  if [ \"$FAIL\" == \"0\" ]; then\n    exit 0\n  else\n    exit 1\n  fi\nfi\n\n# StartPre EOS daemons\nif [ \"$1\" = \"eos-start-pre\" ]; then\n  if [[ \"$XRD_ROLES\" == *\"$2\"* ]]; then\n    if [ -e /etc/eos.keytab ]; then\n      chown daemon /etc/eos.keytab\n      chmod 400 /etc/eos.keytab\n    fi\n    mkdir -p /var/eos/md /var/eos/report\n    chmod 755 /var/eos /var/eos/report\n    mkdir -p /var/spool/eos/core/${2} /var/spool/eos/admin\n    mkdir -p /var/log/eos\n    chown -R daemon /var/spool/eos\n    find /var/log/eos -maxdepth 1 -type d -exec chown daemon {} \\;\n    find /var/eos/ -maxdepth 1 -mindepth 1 -not -path \"/var/eos/fs\" -not -path \"/var/eos/fusex\" -type d -exec chown -R daemon {} \\;\n    chmod -R 775 /var/spool/eos\n\n    if [ -f /var/log/eos/mgm/error.log ]; then\n      chown daemon:daemon /var/log/eos/mgm/error.log\n    fi\n\n    mkdir -p /var/eos/auth /var/eos/stage\n    chown daemon /var/eos/auth /var/eos/stage\n    setfacl -m default:u:daemon:r /var/eos/auth/\n\n    # Require cmsd for fed daemon\n    if [ \"$2\" = \"fed\" ]; then\n      systemctl start cmsd@clustered\n    fi\n  else\n    echo \"<3>Error: Service $2 not in the XRD_ROLES in \\\"/etc/sysconfig/eos_env\\\"\"\n    exit 1\n  fi\nfi\n\n# Stop EOS daemons\nif [ \"$1\" = \"eos-stop\" ]; then\n  if [ \"$2\" = \"fed\" ]; then\n    systemctl stop cmsd@clustered\n  fi\nfi\n\n# Start EOS Master\nif [ \"$1\" = \"eos-master\" ]; then\n  if [[ \"$XRD_ROLES\" == *\"mq\"* ]]; then\n    touch /var/eos/eos.mq.master\n  fi\n\n  if [[ \"$XRD_ROLES\" == *\"mgm\"* ]]; then\n    touch /var/eos/eos.mgm.rw\n  fi\nfi\n\n# Start EOS Slave\nif [ \"$1\" = \"eos-slave\" ]; then\n  if [[ \"$XRD_ROLES\" == *\"mq\"* ]]; then\n    unlink /var/eos/eos.mq.master\n  fi\n\n  if [[ \"$XRD_ROLES\" == *\"mgm\"* ]]; then\n    unlink /var/eos/eos.mgm.rw\n  fi\nfi\n\n# Start EOS fuse daemons\nif [ \"$1\" = \"eosd-start\" ]; then\n  mkdir -p /var/run/eosd/ /var/run/eosd/credentials/store ${EOS_FUSE_MOUNTDIR}\n  chmod 1777 /var/run/eosd/credentials /var/run/eosd/credentials/store\n  chmod 755 ${EOS_FUSE_MOUNTDIR}\nfi\n\n# Stop EOS fuse daemons\nif [ \"$1\" = \"eosd-stop\" ]; then\n  umount -f ${EOS_FUSE_MOUNTDIR}\nfi\n"
  },
  {
    "path": "misc/sbin/mount.eoscfs",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: mount.eoscfsx\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2022 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ncase $1 in\n  -h|--help|-?)\n      echo \"mount.eoscfsx is a private mount(8) wrapper for eoscfsd/EOS.\"\n      echo \"Don't use it directly!\"\n      exit 1\n      ;;\nesac\n\nrestricted=1\n\nif [ $UID -eq 0 ] && [ $UID -eq $EUID ]; then\n  restricted=0\nfi\n\n# mount(8) in restricted mode (for non-root users) does not allow to use any\n# mount options,\n# we fail for users\n\nif [ $restricted -eq 1 ]; then\n  echo \"error: non-root mount attempted !\"\n  exit -1;\nfi\n\nif [ -z \"$1\" ]; then\n    echo \"error: please provide the name of your FUSE mount !\"\n    exit -1;\nfi\n\nname=$1\nshift\n\noptions=()\neoo=0 \n\n# filter out '-n ' which is passwd by automount if /etc/mtab is a symlink into /proc\nwhile [[ $1 ]]\ndo\n    if ! ((eoo)); then\n\tcase \"$1\" in\n\t    -n)\n\t\tshift\n\t\t;;\n\t    *)\n\t\toptions+=(\"$1\")\n\t\tshift\n\t\t;;\n\tesac\n    else\n\toptions+=(\"$1\")\n\t\n\t# Another (worse) way of doing the same thing:\n\t# options=(\"${options[@]}\" \"$1\")\n\tshift\n    fi\ndone\n\n/usr/bin/eoscfsd ${options[@]} $name >& /dev/null < /dev/zero\n"
  },
  {
    "path": "misc/sbin/mount.eosx",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: mount.eosx\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2017 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ncase $1 in\n  -h|--help|-?)\n      echo \"mount.eosxd is a private mount(8) wrapper for eosxd/EOS.\"\n      echo \"Don't use it directly!\"\n      exit 1\n      ;;\nesac\n\nrestricted=1\n\nif [ $UID -eq 0 ] && [ $UID -eq $EUID ]; then\n  restricted=0\nfi\n\nEOSXD=\"/usr/bin/eosxd\"\n\nif [ -e \"/etc/sysconfig/eosxd\" ]; then\n    . /etc/sysconfig/eosxd\nfi\n\n# mount(8) in restricted mode (for non-root users) does not allow to use any\n# mount options,\n# we fail for users\n\nif [ $restricted -eq 1 ]; then\n  echo \"error: non-root mount attempted !\"\n  exit -1;\nfi\n\nif [ -z \"$1\" ]; then\n    echo \"error: please provide the name of your FUSE mount !\"\n    exit -1;\nfi\n\nshift\n\noptions=()\neoo=0 \n\n# filter out '-n ' which is passwd by automount if /etc/mtab is a symlink into /proc\nwhile [[ $1 ]]\ndo\n    if ! ((eoo)); then\n\tcase \"$1\" in\n\t    -n)\n\t\tshift\n\t\t;;\n\t    *)\n\t\toptions+=(\"$1\")\n\t\tshift\n\t\t;;\n\tesac\n    else\n\toptions+=(\"$1\")\n\t\n\t# Another (worse) way of doing the same thing:\n\t# options=(\"${options[@]}\" \"$1\")\n\tshift\n    fi\ndone\n\necho ${options[@]}\n\n$EOSXD ${options[@]} -oautofs >& /dev/null < /dev/zero\n"
  },
  {
    "path": "misc/sbin/mount.eosx3",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: mount.eosx3\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2017 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ncase $1 in\n  -h|--help|-?)\n      echo \"mount.eosx3 is a private mount(8) wrapper for eosxd3/EOS.\"\n      echo \"Don't use it directly!\"\n      exit 1\n      ;;\nesac\n\nrestricted=1\n\nif [ $UID -eq 0 ] && [ $UID -eq $EUID ]; then\n  restricted=0\nfi\n\n# mount(8) in restricted mode (for non-root users) does not allow to use any\n# mount options,\n# we fail for users\n\nif [ $restricted -eq 1 ]; then\n  echo \"error: non-root mount attempted !\"\n  exit -1;\nfi\n\nif [ -z \"$1\" ]; then\n    echo \"error: please provide the name of your FUSE mount !\"\n    exit -1;\nfi\n\nshift\n\noptions=()\neoo=0 \n\n# filter out '-n ' which is passwd by automount if /etc/mtab is a symlink into /proc\nwhile [[ $1 ]]\ndo\n    if ! ((eoo)); then\n\tcase \"$1\" in\n\t    -n)\n\t\tshift\n\t\t;;\n\t    *)\n\t\toptions+=(\"$1\")\n\t\tshift\n\t\t;;\n\tesac\n    else\n\toptions+=(\"$1\")\n\t\n\t# Another (worse) way of doing the same thing:\n\t# options=(\"${options[@]}\" \"$1\")\n\tshift\n    fi\ndone\n\necho ${options[@]}\n\n/usr/bin/eosxd3 ${options[@]} -oautofs >& /dev/null < /dev/zero\n"
  },
  {
    "path": "misc/sbin/umount.fuse",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: umount.fuse\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2024 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nif [ \"$1\" != \"\" ]; then\n    pkill -f -1 \"eoscfsd $1\"\n    fusermount -u $1\nfi\n\n\n\n\n\n"
  },
  {
    "path": "misc/selinux/CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2016 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ninstall(DIRECTORY DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/selinux/targeted)\ninstall(DIRECTORY DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/selinux/mls)\ninstall(DIRECTORY DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/selinux/strict)\n\nadd_custom_target(\n  eosfuse-selinux ALL\n  COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/choose_selinux.sh ${CMAKE_CURRENT_BINARY_DIR})\n\ninstall(\n  FILES ${CMAKE_CURRENT_BINARY_DIR}/eosfuse.pp\n  DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/selinux/targeted/\n  PERMISSIONS OWNER_READ OWNER_EXECUTE\n\t      GROUP_READ GROUP_EXECUTE\n\t      WORLD_READ WORLD_EXECUTE)\n\ninstall(\n  FILES ${CMAKE_CURRENT_BINARY_DIR}/eosfuse.pp\n  DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/selinux/mls/\n  PERMISSIONS OWNER_READ OWNER_EXECUTE\n\t      GROUP_READ GROUP_EXECUTE\n\t      WORLD_READ WORLD_EXECUTE)\n\ninstall(\n  FILES ${CMAKE_CURRENT_BINARY_DIR}/eosfuse.pp\n  DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/selinux/strict/\n  PERMISSIONS OWNER_READ OWNER_EXECUTE\n\t      GROUP_READ GROUP_EXECUTE\n\t      WORLD_READ WORLD_EXECUTE)\n"
  },
  {
    "path": "misc/selinux/README.md",
    "content": "eosfuse SELinux\n===============\n\nThe eosfuse SELinux policy is built via the following steps:\n1. Establish the platform distribution `dist` (e.g.: CC7)\n2. Write the new `eosfuse.te` file -- Remember to increase the version!\n2. Run `checkmodule -M -m -o eosfuse.mod eosfuse.te`\n3. Run `semodule_package -o eosfuse-${dist}.pp -m eosfuse.mod`\n4. Test the policy: `semodule -i eosfuse-${dist}.pp`\n\neosfuse and the build process\n-----------------------------\n\nThe eosfuse policy files for the supported platforms are version controlled.\n\nIn the build process, the `eosfuse-selinux` target runs the `choose_selinux.sh`\nto decide which policy to pick depending on the platform (defaults to CC7). \nIn the end, the chosen file will be installed as `eosfuse.pp`.\n\n#### Example\n\n```bash\n# Choosing the version\nCC7  --> eosfuse-7.pp\nrest --> eosfuse-7.pp \n\n# Actual installation\n/usr/share/selinux/targeted/eosfuse.pp\n/usr/share/selinux/mls/eosfuse.pp\n/usr/share/selinux/strict/eosfuse.pp\n```\n"
  },
  {
    "path": "misc/selinux/choose_selinux.sh",
    "content": "#!/bin/bash\nset -e\n\n############################################################\n# Choose the EOS fuse SELinux policy\n# Program receives as argument the target directory location\n############################################################\n\nWORKDIR=$(dirname \"$0\")\nTARGET_DIR=$1\n\nif [[ ! -d \"${TARGET_DIR}\" ]]; then\n  echo \"error: could not find target directory ${TARGET_DIR}\"\n  exit 1\nfi\n\n# Set default distribution as CC7\nDIST=7\n\nif [[ -f \"/usr/lib/rpm/redhat/dist.sh\" ]]; then\n  DIST=$(/usr/lib/rpm/redhat/dist.sh --distnum)\nfi\n\nif [[ -f \"${WORKDIR}/eosfuse-${DIST}.pp\" ]]; then\n  cp \"${WORKDIR}/eosfuse-${DIST}.pp\" \"${TARGET_DIR}/eosfuse.pp\"\nelse\n  cp \"${WORKDIR}/eosfuse-7.pp\" \"${TARGET_DIR}/eosfuse.pp\"\nfi\n"
  },
  {
    "path": "misc/selinux/eosfuse.te",
    "content": "module eosfuse 1.6;\n\nrequire {\n\tattribute domain;\n\ttype initrc_tmp_t;\n\ttype var_log_t;\n\ttype var_run_t;\n\ttype automount_t;\n\ttype automount_var_run_t;\n\ttype mount_t;\n\ttype sshd_t;\n\ttype su_exec_t;\n\ttype tmp_t;\n\ttype var_spool_t;\n\ttype user_tmp_t;\n\ttype device_t;\n\tclass lnk_file { read getattr create };\n\tclass dir add_name;\n\tclass file { write read create setattr getattr unlink ioctl open };\n\tclass process { getpgid noatsecure rlimitinh siginh ptrace };\n\tclass fifo_file { create unlink open };\n\tclass capability { net_admin sys_ptrace };\n}\n\n#============= automount_t ==============\nallow automount_t initrc_tmp_t:file write;\nallow automount_t automount_var_run_t:lnk_file create;\nallow automount_t user_tmp_t:file { create unlink };\nallow automount_t var_spool_t:lnk_file { create read };\nallow automount_t mount_t:process { noatsecure rlimitinh siginh };\nallow automount_t self:process ptrace;\nallow automount_t device_t:file { open read write ioctl };\nallow automount_t self:capability { net_admin sys_ptrace };\n\n#============= mount_t ==============\nallow mount_t var_log_t:dir add_name;\nallow mount_t var_log_t:file { create setattr };\nallow mount_t var_run_t:lnk_file { read getattr };\nallow mount_t domain:process getpgid;\nallow mount_t self:process signull;\nallow mount_t self:process ptrace;\nallow mount_t tmp_t:fifo_file { create unlink open };\n\n#============= sshd_t ==============\nallow sshd_t su_exec_t:file getattr;\n"
  },
  {
    "path": "misc/usr/CMakeLists.txt",
    "content": "find_program(SYSTEMD_TMPFILES NAMES systemd-tmpfiles)\n\nif(NOT SYSTEMD_TMPFILES)\n  return()\nendif()\n\ninstall(FILES eosd.conf eos-fusex-core.conf DESTINATION lib/tmpfiles.d)\n"
  },
  {
    "path": "misc/usr/eos-fusex-core.conf",
    "content": "d /run/eos/ 0755 root root\nd /run/eos/credentials/ 1777 root root\nd /run/eos/credentials/store 1777 root root\n"
  },
  {
    "path": "misc/usr/eosd.conf",
    "content": "d /run/eosd 755 root root\nd /run/eosd/credentials 1777 root root\nd /run/eosd/credentials/store 1777 root root\n"
  },
  {
    "path": "misc/var/CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ninstall(DIRECTORY eos USE_SOURCE_PERMISSIONS\n  DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR})\n\n# Empty directories are not tracked in git, install them manually\ninstall(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/cache/eos/fusex)\ninstall(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/cache/eos/cfsd)\ninstall(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/eos/archive)\ninstall(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/eos/ns-queue/default)\ninstall(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/eos/archive)\ninstall(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/eos/cfsd)\ninstall(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/eos/fuse)\ninstall(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/eos/fusex)\ninstall(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/eos/httpd)\ninstall(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/eos/tx)\ninstall(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/share/eos)\ninstall(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/www/eos/cgi-bin)\ninstall(DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_RUNSTATEDIR}/eos/credentials/store)\n"
  },
  {
    "path": "misc/var/eos/test/LeakSanitizer.supp",
    "content": "# Suppress known leak in XrdOfs object\nleak:XrdOfs::XrdOfs()\n\n# Suppress malloc leak in OpenSSL\nleak:CRYPTO_malloc\n"
  },
  {
    "path": "misc/var/eos/wfe/bash/shell",
    "content": "#!/bin/bash\nbn=`basename $0`\ntag=$1\nshift\nlogfile=\"/var/log/eos/wfe/$tag.log\"\ntmplogfile=`mktemp`\nmkdir -p /var/log/eos/wfe/\necho \"----------------------------------------------------------------------------------------------------------------------\" >> $tmplogfile\necho -n  `date +%s` `date`  >> $tmplogfile\necho \" $bn $*\" >> $tmplogfile\n\nexec > >(tee -a $tmplogfile)\nexec 2> >(tee -a $tmplogfile >&2)\n\neval $* \nretc=$?\necho \"retc=$retc\" >> $tmplogfile\ncat $tmplogfile >> $logfile\nunlink $tmplogfile\n\nexit $retc\n\n\n"
  },
  {
    "path": "namespace/CMakeLists.txt",
    "content": "#-------------------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Elvin Sindrilaru <esindril@cern.ch>\n#-------------------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2015 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n#-------------------------------------------------------------------------------\n# EosNsCommon-Sources library\n#-------------------------------------------------------------------------------\ninclude_directories(\n  ${CMAKE_BINARY_DIR}\n  ${CMAKE_SOURCE_DIR}/namespace/ns_quarkdb/qclient/include)\n\nadd_library(\n  EosNsCommon-Objects OBJECT\n\n  Constants.cc\n  MDException.cc\n  MDException.hh\n\n  PermissionHandler.cc                PermissionHandler.hh\n  Prefetcher.cc                       Prefetcher.hh\n  Resolver.cc                         Resolver.hh\n  MDLocking.cc                        MDLocking.hh\n  # Namespace interface\n  interface/IView.hh\n  interface/IFsView.hh\n  interface/IFileMDSvc.hh\n  interface/IContainerMDSvc.hh\n  interface/Identifiers.hh\n  interface/IFileMD.hh\n  interface/IContainerMD.hh\n\n  # Namespace utils\n  utils/DataHelper.cc\n  utils/Descriptor.cc\n  utils/FileListRandomPicker.cc\n  utils/Buffer.hh\n  utils/Etag.cc                       utils/Etag.hh\n\n  # non-loadable classes used in QDB namespace\n  ns_quarkdb/accounting/ContainerAccounting.cc            ns_quarkdb/accounting/ContainerAccounting.hh\n  ns_quarkdb/accounting/SyncTimeAccounting.cc             ns_quarkdb/accounting/SyncTimeAccounting.hh\n  ns_quarkdb/accounting/FileSystemHandler.cc              ns_quarkdb/accounting/FileSystemHandler.hh\n  ns_quarkdb/accounting/FileSystemView.cc                 ns_quarkdb/accounting/FileSystemView.hh\n  ns_quarkdb/accounting/QuotaStats.cc                     ns_quarkdb/accounting/QuotaStats.hh\n  ns_quarkdb/accounting/QuotaNodeCore.cc                  ns_quarkdb/accounting/QuotaNodeCore.hh\n                                                          ns_quarkdb/accounting/SetChangeList.hh\n\n  ns_quarkdb/explorer/NamespaceExplorer.cc                ns_quarkdb/explorer/NamespaceExplorer.hh\n  ns_quarkdb/flusher/MetadataFlusher.cc                   ns_quarkdb/flusher/MetadataFlusher.hh\n\n  ns_quarkdb/inspector/AttributeExtraction.cc             ns_quarkdb/inspector/AttributeExtraction.hh\n  ns_quarkdb/inspector/ContainerScanner.cc                ns_quarkdb/inspector/ContainerScanner.hh\n  ns_quarkdb/inspector/FileMetadataFilter.cc              ns_quarkdb/inspector/FileMetadataFilter.hh\n  ns_quarkdb/inspector/FileScanner.cc                     ns_quarkdb/inspector/FileScanner.hh\n  ns_quarkdb/inspector/Inspector.cc                       ns_quarkdb/inspector/Inspector.hh\n  ns_quarkdb/inspector/OutputSink.cc                      ns_quarkdb/inspector/OutputSink.hh\n  ns_quarkdb/inspector/Printing.cc                        ns_quarkdb/inspector/Printing.hh\n\n  ns_quarkdb/persistency/ContainerMDSvc.cc                ns_quarkdb/persistency/ContainerMDSvc.hh\n  ns_quarkdb/persistency/FileMDSvc.cc                     ns_quarkdb/persistency/FileMDSvc.hh\n  ns_quarkdb/persistency/FileSystemIterator.cc            ns_quarkdb/persistency/FileSystemIterator.hh\n  ns_quarkdb/persistency/MetadataFetcher.cc               ns_quarkdb/persistency/MetadataFetcher.hh\n  ns_quarkdb/persistency/MetadataProvider.cc              ns_quarkdb/persistency/MetadataProvider.hh\n  ns_quarkdb/persistency/MetadataProviderShard.cc         ns_quarkdb/persistency/MetadataProviderShard.hh\n  ns_quarkdb/persistency/NextInodeProvider.cc             ns_quarkdb/persistency/NextInodeProvider.hh\n  ns_quarkdb/persistency/RequestBuilder.cc                ns_quarkdb/persistency/RequestBuilder.hh\n  ns_quarkdb/persistency/Serialization.cc                 ns_quarkdb/persistency/Serialization.hh\n  ns_quarkdb/persistency/UnifiedInodeProvider.cc          ns_quarkdb/persistency/UnifiedInodeProvider.hh\n\n  ns_quarkdb/utils/QuotaRecomputer.cc                     ns_quarkdb/utils/QuotaRecomputer.hh\n\n  ns_quarkdb/views/HierarchicalView.cc                    ns_quarkdb/views/HierarchicalView.hh\n\n  ns_quarkdb/CacheRefreshListener.cc                      ns_quarkdb/CacheRefreshListener.hh\n  ns_quarkdb/ContainerMD.cc                               ns_quarkdb/ContainerMD.hh\n  ns_quarkdb/FileMD.cc                                    ns_quarkdb/FileMD.hh\n                                                          ns_quarkdb/LRU.hh\n  ns_quarkdb/NamespaceGroup.cc                            ns_quarkdb/NamespaceGroup.hh\n  ns_quarkdb/VersionEnforcement.cc                        ns_quarkdb/VersionEnforcement.hh\n  ns_quarkdb/QClPerformance.cc                            ns_quarkdb/QClPerformance.hh)\n\ntarget_link_libraries(EosNsCommon-Objects PUBLIC\n  qclient\n  EosCliProto-Objects\n  EosCommon\n  XROOTD::UTILS\n  ROCKSDB::ROCKSDB\n  JSONCPP::JSONCPP)\n\nset_target_properties(EosNsCommon-Objects\n  PROPERTIES POSITION_INDEPENDENT_CODE True)\n\nadd_library(EosNsCommon SHARED $<TARGET_OBJECTS:EosNsCommon-Objects>)\n\ntarget_include_directories(\n  EosNsCommon PUBLIC\n  $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>\n  $<INSTALL_INTERFACE:include>)\n\ntarget_link_libraries(EosNsCommon PUBLIC\n  EosNsCommon-Objects\n  EosCrc32c-Objects\n  qclient\n  ROCKSDB::ROCKSDB)\n\nset_target_properties(\n  EosNsCommon\n  PROPERTIES\n  VERSION ${VERSION}\n  SOVERSION ${VERSION_MAJOR})\n\ninstall(\n  TARGETS EosNsCommon\n  LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n  ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\n\n#-------------------------------------------------------------------------------\n# EosNsCommon-Static library\n#-------------------------------------------------------------------------------\nif (Linux)\n  add_library(EosNsCommon-Static STATIC $<TARGET_OBJECTS:EosNsCommon-Objects>)\n\n  target_include_directories(EosNsCommon-Static PUBLIC\n    $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>\n    $<INSTALL_INTERFACE:include>)\n\n  target_link_libraries(EosNsCommon-Static PUBLIC\n    EosNsCommon-Objects\n    EosCrc32c-Objects\n    EosCommon-Static\n    qclient\n    ROCKSDB::ROCKSDB)\n\n  set_target_properties(EosNsCommon-Static PROPERTIES\n    POSITION_INDEPENDENT_CODE True)\nendif ()\n\nadd_subdirectory(ns_quarkdb)\n"
  },
  {
    "path": "namespace/Constants.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Lukasz Janyst <ljanyst@cern.ch>\n// desc:   Namespace constants\n//------------------------------------------------------------------------------\n#include \"namespace/Constants.hh\"\n\nnamespace eos\n{\n  const uint16_t QUOTA_NODE_FLAG = 0x0001;\n}\n"
  },
  {
    "path": "namespace/Constants.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Lukasz Janyst <ljanyst@cern.ch>\n// desc:   Namespace constants\n//------------------------------------------------------------------------------\n\n#pragma once\n#include <stdint.h>\n\nnamespace eos\n{\nextern const uint16_t QUOTA_NODE_FLAG;\n}\n"
  },
  {
    "path": "namespace/MDException.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Georgios Bitzes <georgios.bitzes@cern.ch>\n// desc:   Metadata exception\n//------------------------------------------------------------------------------\n\n#include \"MDException.hh\"\n#include \"common/Logging.hh\"\n#include <folly/ExceptionWrapper.h>\n\nnamespace eos\n{\n\nMDStatus::MDStatus(int localerrn, const std::string& error)\n  : localerrno(localerrn), err(error)\n{\n  if (localerrno != ENOENT) {\n    eos_static_crit(\"MDStatus (%d): %s\", localerrn, error.c_str());\n  } else {\n    eos_static_debug(\"MDStatus (%d): %s\", localerrn, error.c_str());\n  }\n}\n}\n"
  },
  {
    "path": "namespace/MDException.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Lukasz Janyst <ljanyst@cern.ch>\n// desc:   Metadata exception\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_MD_EXCEPTION_HH\n#define EOS_NS_MD_EXCEPTION_HH\n\n#include <stdexcept>\n#include <sstream>\n#include <cerrno>\n#include <cstring>\n#include <folly/ExceptionWrapper.h>\n\n#define SSTR(message) static_cast<std::ostringstream&>(std::ostringstream().flush() << message).str()\n#define throw_mdexception(err, msg) { eos::MDException __md___exception____(err); __md___exception____.getMessage() << msg; throw __md___exception____; }\n#define make_mdexception(err, msg) folly::make_exception_wrapper<eos::MDException>(err, std::move(SSTR(msg)))\n\nnamespace eos\n{\n//----------------------------------------------------------------------------\n//! Metadata exception\n//----------------------------------------------------------------------------\nclass MDException: public std::exception\n{\npublic:\n  //------------------------------------------------------------------------\n  // Constructor taking an error code and string message\n  //------------------------------------------------------------------------\n  MDException(int errorNo = ENODATA, const std::string& msg = \"\"):\n    pErrorNo(errorNo), pTmpMessage(0)\n  {\n    if (!msg.empty()) {\n      getMessage() << msg;\n    }\n  }\n\n  MDException(int errno, std::string&& msg):\n    pErrorNo(errno), pTmpMessage(0)\n  {\n    getMessage() << msg;\n  }\n  //------------------------------------------------------------------------\n  //! Destructor\n  //------------------------------------------------------------------------\n  virtual ~MDException() throw()\n  {\n    delete [] pTmpMessage;\n  }\n\n  //------------------------------------------------------------------------\n  //! Copy constructor - this is actually required because we cannot copy\n  //! stringstreams\n  //------------------------------------------------------------------------\n  MDException(const MDException& e)\n  {\n    pMessage << e.pMessage.str();\n    pErrorNo = e.getErrno();\n    pTmpMessage = 0;\n  }\n\n  //------------------------------------------------------------------------\n  //! Get errno associated with the exception\n  //------------------------------------------------------------------------\n  int getErrno() const\n  {\n    return pErrorNo;\n  }\n\n  //------------------------------------------------------------------------\n  //! Get the message stream\n  //------------------------------------------------------------------------\n  std::ostringstream& getMessage()\n  {\n    return pMessage;\n  }\n\n  //------------------------------------------------------------------------\n  // Get the message\n  //------------------------------------------------------------------------\n  virtual const char* what() const noexcept override\n  {\n    // we could to that instead: return (pMessage.str()+\" \").c_str();\n    // but it's ugly and probably not portable\n    if (pTmpMessage) {\n      delete [] pTmpMessage;\n    }\n\n    std::string msg = pMessage.str();\n    pTmpMessage = new char[msg.length() + 1];\n    pTmpMessage[msg.length()] = 0;\n    pTmpMessage = strcpy(pTmpMessage, msg.c_str());\n    return pTmpMessage;\n  }\n\n  void wrapAndRethrow(const std::string& prefix) const\n  {\n    throw_mdexception(pErrorNo, SSTR(prefix << pMessage.str()));\n  }\n\nprivate:\n  //------------------------------------------------------------------------\n  // Data members\n  //------------------------------------------------------------------------\n  std::ostringstream  pMessage;\n  int                 pErrorNo;\n  mutable char*       pTmpMessage;\n};\n\ninline MDException makeMDException(int err, std::string&& msg)\n{\n  MDException exc(err);\n  exc.getMessage() << msg;\n  return exc;\n}\n\n//----------------------------------------------------------------------------\n//! Metadata operation status\n//----------------------------------------------------------------------------\nclass MDStatus\n{\npublic:\n  MDStatus() : localerrno(0) {}\n\n  MDStatus(int localerrn, const std::string& error);\n  bool ok() const\n  {\n    return err.empty();\n  }\n  std::string getError() const\n  {\n    return err;\n  }\n  int getErrno() const\n  {\n    return localerrno;\n  }\n\n  void throwIfNotOk(const std::string& prefix = {})\n  {\n    if (!ok()) {\n      throw_mdexception(localerrno, SSTR(prefix << err));\n    }\n  }\nprivate:\n  int localerrno;\n  std::string err;\n};\n\n}\n\n#endif // EOS_NS_MD_EXCEPTION_HH\n"
  },
  {
    "path": "namespace/MDLocking.cc",
    "content": "/************************************************************************\n* EOS - the CERN Disk Storage System                                   *\n* Copyright (C) 2024 CERN/Switzerland                                  *\n*                                                                      *\n* This program is free software: you can redistribute it and/or modify *\n* it under the terms of the GNU General Public License as published by *\n* the Free Software Foundation, either version 3 of the License, or    *\n* (at your option) any later version.                                  *\n*                                                                      *\n* This program is distributed in the hope that it will be useful,      *\n* but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n* GNU General Public License for more details.                         *\n*                                                                      *\n* You should have received a copy of the GNU General Public License    *\n* along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n************************************************************************/\n\n#include \"namespace/MDLocking.hh\"\n#include \"namespace/locking/NSObjectLocker.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\nMDLocking::FileReadLockPtr MDLocking::readLock(FileMDPtr fmd){\n  return std::make_unique<FileReadLock>(fmd);\n}\n\nMDLocking::FileWriteLockPtr MDLocking::writeLock(FileMDPtr fmd) {\n  return std::make_unique<FileWriteLock>(fmd);\n}\nMDLocking::ContainerReadLockPtr MDLocking::readLock(ContainerMDPtr cmd) {\n  return std::make_unique<ContainerReadLock>(cmd);\n}\n\nMDLocking::ContainerWriteLockPtr MDLocking::writeLock(ContainerMDPtr cmd) {\n  return std::make_unique<ContainerWriteLock>(cmd);\n}\n\nEOSNSNAMESPACE_END"
  },
  {
    "path": "namespace/MDLocking.hh",
    "content": "/************************************************************************\n* EOS - the CERN Disk Storage System                                   *\n* Copyright (C) 2024 CERN/Switzerland                                  *\n*                                                                      *\n* This program is free software: you can redistribute it and/or modify *\n* it under the terms of the GNU General Public License as published by *\n* the Free Software Foundation, either version 3 of the License, or    *\n* (at your option) any later version.                                  *\n*                                                                      *\n* This program is distributed in the hope that it will be useful,      *\n* but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n* GNU General Public License for more details.                         *\n*                                                                      *\n* You should have received a copy of the GNU General Public License    *\n* along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n************************************************************************/\n\n#ifndef EOS_MDLOCKING_HH\n#define EOS_MDLOCKING_HH\n\n\n#include <memory>\n\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/LockableNSObject.hh\"\n#include \"namespace/locking/RawPtr.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\nclass IContainerMD;\nusing IContainerMDPtr = std::shared_ptr<IContainerMD>;\nusing IContainerMDRawPtr = raw_ptr<IContainerMD>;\n\nstruct FileOrContainerMD;\n\nclass IFileMD;\nusing IFileMDPtr = std::shared_ptr<IFileMD>;\nusing IFileMDRawPtr = raw_ptr<IFileMD>;\n\ntemplate<typename ObjectMDPtr, typename LockType>\nclass NSObjectMDLock;\n\ntemplate<typename ObjectMDPtr, typename LockType>\nclass NSObjectMDTryLock;\n\ntemplate<typename TryLockerType>\nclass BulkNsObjectLocker;\n\ntemplate<typename ContainerTryLockerType, typename FileTryLockerType>\nclass BulkMultiNsObjectLocker;\n\n/**\n * Convenient class where developers can use the type extracted from it to perform NS file\n * or container locking\n */\nclass MDLocking\n{\nprivate:\n  // Pointer type of ContainerMD\n  using ContainerMDPtr = IContainerMDRawPtr;\n  using FileMDPtr = IFileMDRawPtr;\n  using FileReadTryLock = NSObjectMDTryLock<FileMDPtr, MDReadLock>;\n  using FileWriteTryLock = NSObjectMDTryLock<FileMDPtr, MDWriteLock>;\n  using ContainerReadTryLock = NSObjectMDTryLock<ContainerMDPtr, MDReadLock>;\n  using ContainerWriteTryLock = NSObjectMDTryLock<ContainerMDPtr, MDWriteLock>;\n\npublic:\n  // Read lock a container\n  using ContainerReadLock = NSObjectMDLock<ContainerMDPtr, MDReadLock>;\n  // Write lock a container\n  using ContainerWriteLock = NSObjectMDLock<ContainerMDPtr, MDWriteLock>;\n\n  // Pointer holding container read/write locks\n  using ContainerReadLockPtr = std::unique_ptr<ContainerReadLock>;\n  using ContainerWriteLockPtr = std::unique_ptr<ContainerWriteLock>;\n\n  //Read lock a file\n  using FileReadLock = NSObjectMDLock<FileMDPtr, MDReadLock>;\n  // Write lock a file\n  using FileWriteLock = NSObjectMDLock<FileMDPtr, MDWriteLock>;\n\n  // Pointers holding file read/write lock\n  using FileReadLockPtr = std::unique_ptr<FileReadLock>;\n  using FileWriteLockPtr = std::unique_ptr<FileWriteLock>;\n\n  // Bulk container read/write locks\n  using BulkContainerReadLock = BulkNsObjectLocker<ContainerReadTryLock>;\n  using BulkContainerWriteLock = BulkNsObjectLocker<ContainerWriteTryLock>;\n\n  // Bulk file read/write locks\n  using BulkFileReadLock = BulkNsObjectLocker<FileReadTryLock>;\n  using BulkFileWriteLock = BulkNsObjectLocker<FileWriteTryLock>;\n\n  // Bulk MD object (file and container) read/write locks\n  using BulkMDReadLock =\n    BulkMultiNsObjectLocker<ContainerReadTryLock, FileReadTryLock>;\n  using BulkMDWriteLock =\n    BulkMultiNsObjectLocker<ContainerWriteTryLock, FileWriteTryLock>;\n\n  /**\n   * Read locks a file\n   * @param fmd the shared_ptr of the file to read lock\n   * @return the pointer to the lock\n   */\n  static FileReadLockPtr readLock(FileMDPtr fmd);\n  /**\n   * Write locks a file\n   * @param fmd the shared_ptr of the file to write lock\n   * @return the pointer to the lock\n   */\n  static FileWriteLockPtr writeLock(FileMDPtr fmd);\n  /**\n   * Read lock a container\n   * @param cmd the shared_ptr of the container to lock\n   * @return the pointer to the lock\n   */\n  static ContainerReadLockPtr readLock(ContainerMDPtr cmd);\n  /**\n   * Write locks a container\n   * @param cmd the shared_ptr of the container to lock\n   * @return the pointer to the lock\n   */\n  static ContainerWriteLockPtr writeLock(ContainerMDPtr cmd);\n};\n\n//------------------------------------------------------------------------------\n//! Holds either a FileMD locked or a ContainerMD locked. Only one of these are ever filled,\n//! the other will be nullptr. Both might be nullptr as well.\n//------------------------------------------------------------------------------\ntemplate<typename ContainerMDLock, typename FileMDLock>\nstruct FileOrContainerMDLocked {\n  std::unique_ptr<ContainerMDLock> containerLock = nullptr;\n  std::unique_ptr<FileMDLock> fileLock = nullptr;\n};\n\nusing FileOrContReadLocked =\n  FileOrContainerMDLocked<eos::MDLocking::ContainerReadLock,\n  eos::MDLocking::FileReadLock>;\nusing FileOrContWriteLocked =\n  FileOrContainerMDLocked<eos::MDLocking::ContainerWriteLock,\n  eos::MDLocking::FileWriteLock>;\nEOSNSNAMESPACE_END\n\n#endif // EOS_MDLOCKING_HH\n"
  },
  {
    "path": "namespace/Namespace.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Namespace.hh\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#pragma once\n#define USE_EOSNSNAMESPACE using namespace eos;\n#define EOSNSNAMESPACE_BEGIN namespace eos {\n#define EOSNSNAMESPACE_END }\n"
  },
  {
    "path": "namespace/PermissionHandler.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author: Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief  Collection of functions to do permission checking\n//------------------------------------------------------------------------------\n\n#include \"namespace/PermissionHandler.hh\"\n#include <sys/stat.h>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Convert \"user\" mode_t permission bits to internally-used representation.\n//------------------------------------------------------------------------------\nchar PermissionHandler::convertModetUser(mode_t mode)\n{\n  char perms = 0;\n\n  if ((mode & S_IRUSR) != 0u) {\n    perms |= CANREAD;\n  }\n\n  if ((mode & S_IWUSR) != 0u) {\n    perms |= CANWRITE;\n  }\n\n  if ((mode & S_IXUSR) != 0u) {\n    perms |= CANENTER;\n  }\n\n  return perms;\n}\n\n//------------------------------------------------------------------------------\n//! Convert \"group\" mode_t permission bits to internally-used representation.\n//------------------------------------------------------------------------------\nchar PermissionHandler::convertModetGroup(mode_t mode)\n{\n  char perms = 0;\n\n  if ((mode & S_IRGRP) != 0u) {\n    perms |= CANREAD;\n  }\n\n  if ((mode & S_IWGRP) != 0u) {\n    perms |= CANWRITE;\n  }\n\n  if ((mode & S_IXGRP) != 0u) {\n    perms |= CANENTER;\n  }\n\n  return perms;\n}\n\n//------------------------------------------------------------------------------\n//! Convert \"other\" mode_t permission bits to internally-used representation.\n//------------------------------------------------------------------------------\nchar PermissionHandler::convertModetOther(mode_t mode)\n{\n  char perms = 0;\n\n  if ((mode & S_IROTH) != 0u) {\n    perms |= CANREAD;\n  }\n\n  if ((mode & S_IWOTH) != 0u) {\n    perms |= CANWRITE;\n  }\n\n  if ((mode & S_IXOTH) != 0u) {\n    perms |= CANENTER;\n  }\n\n  return perms;\n}\n\n//------------------------------------------------------------------------------\n//! Check permissions and decide whether to allow or not.\n//------------------------------------------------------------------------------\nbool PermissionHandler::checkPerms(char actual, char requested)\n{\n  for (int i = 0; i < 3; ++i) {\n    if ((requested & (1 << i)) != 0) {\n      if ((actual & (1 << i)) == 0) {\n        return false;\n      }\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n//! Convert requested permissions to internal representation. Ready to pass\n//! onto checkPerms then.\n//------------------------------------------------------------------------------\nchar PermissionHandler::convertRequested(mode_t requested)\n{\n  char convFlags = 0;\n\n  if ((requested & R_OK) != 0) {\n    convFlags |= CANREAD;\n  }\n\n  if ((requested & W_OK) != 0) {\n    convFlags |= CANWRITE;\n  }\n\n  if ((requested & X_OK) != 0) {\n    convFlags |= CANENTER;\n  }\n\n  return convFlags;\n}\n\n//------------------------------------------------------------------------------\n//! Parse octal mask\n//------------------------------------------------------------------------------\nbool PermissionHandler::parseOctalMask(const std::string& str, mode_t& out)\n{\n  try {\n    size_t pos = 0;\n    out = std::stol(str, &pos, 8);\n\n    if (pos != str.length()) {\n      return false;\n    }\n\n    return true;\n  } catch (...) {\n    return false;\n  }\n}\n\n//------------------------------------------------------------------------------\n//! Convert requested permissions to internal representation. Ready to pass\n//! onto checkPerms then.\n//------------------------------------------------------------------------------\nmode_t PermissionHandler::filterWithSysMask(const std::string& sysmask,\n    mode_t mode)\n{\n  if (sysmask.empty()) {\n    return mode;\n  }\n\n  mode_t mask;\n\n  if (!parseOctalMask(sysmask, mask)) {\n    // un-parseable mask, ignore\n    return mode;\n  }\n\n  return mode & mask;\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/PermissionHandler.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief  Collection of functions to do permission checking\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n#define CANREAD 0x01\n#define CANWRITE 0x02\n#define CANENTER 0x04\n\nclass PermissionHandler\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Convert \"user\" mode_t permission bits to internally-used representation.\n  //----------------------------------------------------------------------------\n  static char convertModetUser(mode_t mode);\n\n  //----------------------------------------------------------------------------\n  //! Convert \"group\" mode_t permission bits to internally-used representation.\n  //----------------------------------------------------------------------------\n  static char convertModetGroup(mode_t mode);\n\n  //----------------------------------------------------------------------------\n  //! Convert \"other\" mode_t permission bits to internally-used representation.\n  //----------------------------------------------------------------------------\n  static char convertModetOther(mode_t mode);\n\n  //----------------------------------------------------------------------------\n  //! Check permissions and decide whether to allow or not.\n  //----------------------------------------------------------------------------\n  static bool checkPerms(char actual, char requested);\n\n  //----------------------------------------------------------------------------\n  //! Convert requested permissions to internal representation. Ready to pass\n  //! onto checkPerms then.\n  //----------------------------------------------------------------------------\n  static char convertRequested(mode_t requested);\n\n  //----------------------------------------------------------------------------\n  //! Parse octal mask\n  //----------------------------------------------------------------------------\n  static bool parseOctalMask(const std::string& str, mode_t& out);\n\n  //----------------------------------------------------------------------------\n  //! Filter mode based a given mask, passed as string\n  //----------------------------------------------------------------------------\n  static mode_t filterWithSysMask(const std::string& sysmask, mode_t mode);\n\n  //------------------------------------------------------------------------------\n  //! Filter mode based on sys.mask, as given in xattrs.\n  //! Template, so we can handle both std::map and protobuf map.\n  //------------------------------------------------------------------------------\n  template<typename MapType>\n  static mode_t filterWithSysMask(const MapType& xattr, mode_t mode)\n  {\n    auto it = xattr.find(\"sys.mask\");\n\n    if (it == xattr.end()) {\n      return mode;\n    }\n\n    return filterWithSysMask(it->second, mode);\n  }\n\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/Prefetcher.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Metadata prefetching engine\n//------------------------------------------------------------------------------\n\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/interface/IFsView.hh\"\n#include \"namespace/interface/ContainerIterators.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"common/FileId.hh\"\n#include \"common/Logging.hh\"\n\nusing std::placeholders::_1;\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nPrefetcher::Prefetcher(IView* view)\n{\n  pView = view;\n  pFileMDSvc = pView->getFileMDSvc();\n  pContainerMDSvc = pView->getContainerMDSvc();\n}\n\n//------------------------------------------------------------------------------\n// Declare an intent to access FileMD with the given id soon\n//------------------------------------------------------------------------------\nvoid Prefetcher::stageFileMD(IFileMD::id_t id)\n{\n  if (pView->inMemory()) {\n    return;\n  }\n\n  mFileMDs.emplace_back(pFileMDSvc->getFileMDFut(id));\n}\n\n//------------------------------------------------------------------------------\n// Prefetch Uri of IFileMDPtr\n//------------------------------------------------------------------------------\nfolly::Future<std::string> Prefetcher::prefetchFileUri(IFileMDPtr file) {\n  if(file) {\n    return this->pView->getUriFut(file->getIdentifier());\n  }\n\n  return \"\";\n}\n\n//------------------------------------------------------------------------------\n// Prefetch Uri of IContainerMDPtr\n//------------------------------------------------------------------------------\nfolly::Future<std::string> Prefetcher::prefetchContUri(IContainerMDPtr cont) {\n  if(cont) {\n    return this->pView->getUriFut(cont->getIdentifier());\n  }\n\n  return \"\";\n}\n\n//------------------------------------------------------------------------------\n// Declare an intent to access FileMD with the given id soon, along with\n// its parents\n//------------------------------------------------------------------------------\nvoid Prefetcher::stageFileMDWithParents(IFileMD::id_t id)\n{\n  if (pView->inMemory()) {\n    return;\n  }\n\n  mUris.emplace_back(pFileMDSvc->getFileMDFut(id).thenValue(std::bind(&Prefetcher::prefetchFileUri, this, _1)));\n}\n\n//------------------------------------------------------------------------------\n// Declare an intent to access ContainerMD with the given id soon, along with\n// its parents\n//------------------------------------------------------------------------------\nvoid Prefetcher::stageContainerMDWithParents(IContainerMD::id_t id)\n{\n  if (pView->inMemory()) {\n    return;\n  }\n\n  mUris.emplace_back(pContainerMDSvc->getContainerMDFut(id).thenValue(std::bind(&Prefetcher::prefetchContUri, this, _1)));\n}\n\n//----------------------------------------------------------------------------\n// Declare an intent to access FileMD with the given path soon\n//----------------------------------------------------------------------------\nvoid Prefetcher::stageFileMD(const std::string& path, bool follow)\n{\n  if (pView->inMemory()) {\n    return;\n  }\n\n  try {\n    mFileMDs.emplace_back(pView->getFileFut(path, follow));\n  }\n  catch(MDException &exc) {\n    eos_static_warning(\"Exception in Prefetcher while looking up FileMD path %s: %s, benign race condition?\", path.c_str(), exc.getMessage().str().c_str());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Declare an intent to access ContainerMD with the given id soon\n//------------------------------------------------------------------------------\nvoid Prefetcher::stageContainerMD(IContainerMD::id_t id)\n{\n  if (pView->inMemory()) {\n    return;\n  }\n\n  mContainerMDs.emplace_back(pContainerMDSvc->getContainerMDFut(id));\n}\n\n//----------------------------------------------------------------------------\n// Declare an intent to access ContainerMD with the given path soon\n//----------------------------------------------------------------------------\nvoid Prefetcher::stageContainerMD(const std::string& path, bool follow)\n{\n  if (pView->inMemory()) {\n    return;\n  }\n\n  try {\n    mContainerMDs.emplace_back(pView->getContainerFut(path, follow));\n  }\n  catch(MDException &exc) {\n    eos_static_warning(\"Exception in Prefetcher while looking up ContainerMD path %s: %s, benign race condition?\", path.c_str(), exc.getMessage().str().c_str());\n  }\n}\n\n//------------------------------------------------------------------------------\n//! Declare an intent to access the given path soon. We don't know if there's\n//! a file, or directory there.\n//------------------------------------------------------------------------------\nvoid Prefetcher::stageItem(const std::string& path, bool follow)\n{\n  if (pView->inMemory()) {\n    return;\n  }\n\n  mItems.emplace_back(pView->getItem(path, follow));\n}\n\n//------------------------------------------------------------------------------\n// Wait until all staged requests have been loaded in cache.\n//------------------------------------------------------------------------------\nvoid Prefetcher::wait()\n{\n  if (pView->inMemory()) {\n    return;\n  }\n\n  for (size_t i = 0; i < mFileMDs.size(); i++) {\n    mFileMDs[i].wait();\n  }\n\n  for (size_t i = 0; i < mContainerMDs.size(); i++) {\n    mContainerMDs[i].wait();\n  }\n\n  for (size_t i = 0; i < mUris.size(); i++) {\n    mUris[i].wait();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Prefetch FileMD by path and wait\n//------------------------------------------------------------------------------\nvoid Prefetcher::prefetchFileMDAndWait(IView* view, const std::string& path,\n                                       bool follow)\n{\n  Prefetcher prefetcher(view);\n  prefetcher.stageFileMD(path, follow);\n  prefetcher.wait();\n}\n\n//------------------------------------------------------------------------------\n// Prefetch FileMD by id and wait\n//------------------------------------------------------------------------------\nvoid Prefetcher::prefetchFileMDAndWait(IView* view, IFileMD::id_t id)\n{\n  Prefetcher prefetcher(view);\n  prefetcher.stageFileMD(id);\n  prefetcher.wait();\n}\n\n\n//------------------------------------------------------------------------------\n// Prefetch ContainerMD and wait\n//------------------------------------------------------------------------------\nvoid Prefetcher::prefetchContainerMDAndWait(IView* view,\n    const std::string& path, bool follow)\n{\n  Prefetcher prefetcher(view);\n  prefetcher.stageContainerMD(path, follow);\n  prefetcher.wait();\n}\n\n//------------------------------------------------------------------------------\n// Prefetch ContainerMD and wait\n//------------------------------------------------------------------------------\nvoid Prefetcher::prefetchContainerMDAndWait(IView* view, IContainerMD::id_t id)\n{\n  Prefetcher prefetcher(view);\n  prefetcher.stageContainerMD(id);\n  prefetcher.wait();\n}\n\n\n//------------------------------------------------------------------------------\n// Prefetch item and wait. We don't know if there's a file, or container under\n// that path.\n//------------------------------------------------------------------------------\nvoid Prefetcher::prefetchItemAndWait(IView* view, const std::string& path,\n                                     bool follow)\n{\n  Prefetcher prefetcher(view);\n  prefetcher.stageItem(path, follow);\n  prefetcher.wait();\n}\n\n//------------------------------------------------------------------------------\n// Prefetch ContainerMD, along with all its children, and wait\n//------------------------------------------------------------------------------\nvoid Prefetcher::prefetchContainerMDWithChildrenAndWait(IView* view,\n    const std::string& path, bool follow, bool onlyDirs, bool limitresult, uint64_t dir_limit, uint64_t file_limit)\n{\n  if (view->inMemory()) {\n    return;\n  }\n\n  folly::Future<IContainerMDPtr> fut = view->getContainerFut(path, follow);\n  fut.wait();\n\n  if (fut.hasException()) {\n    return;\n  }\n\n  IContainerMDPtr cmd = std::move(fut).get();\n\n  if(std::chrono::steady_clock::now() - cmd->getLastPrefetch() <= std::chrono::minutes(10)) {\n    return;\n  }\n\n  Prefetcher prefetcher(view);\n  std::vector<std::string> paths;\n\n  if (limitresult) {\n    uint64_t dirsfound=0;\n    for (auto dit = eos::ContainerMapIterator(cmd); dit.valid() && dirsfound<dir_limit; dit.next(),dirsfound++) {\n      paths.emplace_back(SSTR(path << \"/\" << dit.key()));\n    }\n  } else {\n    for (auto dit = eos::ContainerMapIterator(cmd); dit.valid(); dit.next()) {\n      paths.emplace_back(SSTR(path << \"/\" << dit.key()));\n    }\n  }\n\n  for (size_t i = 0; i < paths.size(); i++) {\n    prefetcher.stageContainerMD(paths[i], true);\n  }\n\n  paths.clear();\n\n  if(!onlyDirs) {\n\n    if (limitresult) {\n      uint64_t filesfound = 0;\n      for (auto dit = eos::FileMapIterator(cmd); dit.valid() && filesfound<file_limit; dit.next(),filesfound++) {\n        paths.emplace_back(SSTR(path << \"/\" << dit.key()));\n      }\n    } else {\n      for (auto dit = eos::FileMapIterator(cmd); dit.valid(); dit.next()) {\n        paths.emplace_back(SSTR(path << \"/\" << dit.key()));\n      }\n    }\n\n    for (size_t i = 0; i < paths.size(); i++) {\n      prefetcher.stageFileMD(paths[i], true);\n    }\n  }\n\n  prefetcher.wait();\n  cmd->setLastPrefetch(std::chrono::steady_clock::now());\n}\n\n//------------------------------------------------------------------------------\n// Prefetch inode metadata, automatically detect if it's a file or directory\n//------------------------------------------------------------------------------\nvoid Prefetcher::prefetchInodeAndWait(IView* view, uint64_t ino)\n{\n  if(view->inMemory() || ino == 0) {\n    return;\n  }\n\n  if(eos::common::FileId::IsFileInode(ino)) {\n    prefetchFileMDAndWait(view, eos::common::FileId::InodeToFid(ino));\n  }\n  else {\n    prefetchContainerMDAndWait(view, ino);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Prefetch inode metadata with all children (if any), automatically detect if\n// it's a file or directory\n//------------------------------------------------------------------------------\nvoid Prefetcher::prefetchInodeWithChildrenAndWait(IView* view, uint64_t ino)\n{\n  if(view->inMemory() || ino == 0) {\n    return;\n  }\n\n  if(eos::common::FileId::IsFileInode(ino)) {\n    prefetchFileMDAndWait(view, eos::common::FileId::InodeToFid(ino));\n  }\n  else {\n    prefetchContainerMDWithChildrenAndWait(view, ino);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Prefetch ContainerMD, along with all its children, and wait\n//------------------------------------------------------------------------------\nvoid Prefetcher::prefetchContainerMDWithChildrenAndWait(IView* view,\n    IContainerMD::id_t id, bool onlyDirs, bool limitresults, uint64_t dir_limit, uint64_t file_limit)\n{\n  if (view->inMemory()) {\n    return;\n  }\n\n  folly::Future<IContainerMDPtr> fut =\n    view->getContainerMDSvc()->getContainerMDFut(id);\n  fut.wait();\n\n  if (fut.hasException()) {\n    return;\n  }\n\n  IContainerMDPtr cmd = std::move(fut).get();\n\n  if(std::chrono::steady_clock::now() - cmd->getLastPrefetch() <= std::chrono::minutes(10)) {\n    return;\n  }\n\n  Prefetcher prefetcher(view);\n  std::vector<std::string> paths;\n\n  if (limitresults) {\n    uint64_t dirsfound=0;\n    for (auto dit = eos::ContainerMapIterator(cmd); dit.valid() && dirsfound<dir_limit; dit.next(),dirsfound++) {\n      prefetcher.stageContainerMD(dit.value());\n    }\n  } else {\n    for (auto dit = eos::ContainerMapIterator(cmd); dit.valid(); dit.next()) {\n      prefetcher.stageContainerMD(dit.value());\n    }\n  }\n\n  if(!onlyDirs) {\n    if (limitresults) {\n      uint64_t filesfound=0;\n      for (auto dit = eos::FileMapIterator(cmd); dit.valid() && filesfound<file_limit; dit.next(),filesfound++) {\n        prefetcher.stageFileMD(dit.value());\n      }\n    } else {\n      for (auto dit = eos::FileMapIterator(cmd); dit.valid(); dit.next()) {\n        prefetcher.stageFileMD(dit.value());\n      }\n    }\n  }\n\n  prefetcher.wait();\n  cmd->setLastPrefetch(std::chrono::steady_clock::now());\n}\n\n//------------------------------------------------------------------------------\n// Prefetch FileMD inode, along with all its parents, and wait\n//------------------------------------------------------------------------------\nvoid Prefetcher::prefetchFileMDWithParentsAndWait(IView* view,\n    IFileMD::id_t id)\n{\n  if (view->inMemory()) {\n    return;\n  }\n\n  Prefetcher prefetcher(view);\n  prefetcher.stageFileMDWithParents(id);\n  prefetcher.wait();\n}\n\n//------------------------------------------------------------------------------\n// Prefetch ContainerMD inode, along with all its parents, and wait\n//------------------------------------------------------------------------------\nvoid Prefetcher::prefetchContainerMDWithParentsAndWait(IView* view,\n    IContainerMD::id_t id)\n{\n  if (view->inMemory()) {\n    return;\n  }\n\n  Prefetcher prefetcher(view);\n  prefetcher.stageContainerMDWithParents(id);\n  prefetcher.wait();\n}\n\n//------------------------------------------------------------------------------\n// Prefetch FileList for the given filesystem ID\n//------------------------------------------------------------------------------\nvoid Prefetcher::prefetchFilesystemFileListAndWait(IView* view, IFsView* fsview,\n    IFileMD::location_t location)\n{\n  if (view->inMemory()) {\n    return;\n  }\n\n  auto it = fsview->getFileList(location);\n}\n\n//----------------------------------------------------------------------------\n// Prefetch unlinked FileList for the given filesystem ID, along with all\n// contained FileMDs.\n//----------------------------------------------------------------------------\nvoid Prefetcher::prefetchFilesystemUnlinkedFileListWithFileMDsAndWait(\n  IView* view, IFsView* fsview, IFileMD::location_t location)\n{\n  if (view->inMemory()) {\n    return;\n  }\n\n  Prefetcher prefetcher(view);\n\n  for (auto it = fsview->getUnlinkedFileList(location); it &&\n       it->valid(); it->next()) {\n    prefetcher.stageFileMD(it->getElement());\n  }\n\n  prefetcher.wait();\n}\n\n//------------------------------------------------------------------------------\n// Prefetch FileList for the given filesystem ID, along with all contained\n// FileMDs.\n//------------------------------------------------------------------------------\nvoid Prefetcher::prefetchFilesystemFileListWithFileMDsAndWait(IView* view,\n    IFsView* fsview, IFileMD::location_t location)\n{\n  if (view->inMemory()) {\n    return;\n  }\n\n  Prefetcher prefetcher(view);\n\n  for (auto it = fsview->getFileList(location); it && it->valid(); it->next()) {\n    prefetcher.stageFileMD(it->getElement());\n  }\n\n  prefetcher.wait();\n}\n\n//------------------------------------------------------------------------------\n// Prefetch FileList for the given filesystem ID, along with all contained\n// FileMDs, and all parents of those.\n//------------------------------------------------------------------------------\nvoid Prefetcher::prefetchFilesystemFileListWithFileMDsAndParentsAndWait(\n  IView* view, IFsView* fsview, IFileMD::location_t location)\n{\n  if (view->inMemory()) {\n    return;\n  }\n\n  Prefetcher prefetcher(view);\n\n  for (auto it = fsview->getFileList(location); it && it->valid(); it->next()) {\n    prefetcher.stageFileMDWithParents(it->getElement());\n  }\n\n  prefetcher.wait();\n}\n\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/Prefetcher.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Metadata prefetching engine\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include <folly/futures/Future.h>\n\nEOSNSNAMESPACE_BEGIN\n\nclass IContainerMDSvc;\nclass IFileMDSvc;\nclass IView;\nclass IFsView;\n\nclass Prefetcher\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  Prefetcher(IView* view);\n\n  //----------------------------------------------------------------------------\n  //! Declare an intent to access FileMD with the given id soon\n  //----------------------------------------------------------------------------\n  void stageFileMD(IFileMD::id_t id);\n\n  //----------------------------------------------------------------------------\n  //! Declare an intent to access FileMD with the given id soon, along with\n  //! its parents\n  //----------------------------------------------------------------------------\n  void stageFileMDWithParents(IFileMD::id_t id);\n\n  //----------------------------------------------------------------------------\n  //! Declare an intent to access FileMD with the given id soon, along with\n  //! its parents\n  //----------------------------------------------------------------------------\n  void stageContainerMDWithParents(IContainerMD::id_t id);\n\n  //----------------------------------------------------------------------------\n  //! Declare an intent to access FileMD with the given path soon\n  //----------------------------------------------------------------------------\n  void stageFileMD(const std::string& path, bool follow);\n\n  //----------------------------------------------------------------------------\n  //! Declare an intent to access ContainerMD with the given id soon\n  //----------------------------------------------------------------------------\n  void stageContainerMD(IContainerMD::id_t id);\n\n  //----------------------------------------------------------------------------\n  //! Declare an intent to access ContainerMD with the given path soon\n  //----------------------------------------------------------------------------\n  void stageContainerMD(const std::string& path, bool follow);\n\n  //------------------------------------------------------------------------------\n  //! Prefetch item and wait. We don't know if there's a file, or container\n  //! under that path.\n  //------------------------------------------------------------------------------\n  void stageItem(const std::string& path, bool follow);\n\n  //----------------------------------------------------------------------------\n  //! Wait until all staged requests have been loaded in cache.\n  //----------------------------------------------------------------------------\n  void wait();\n\n  //----------------------------------------------------------------------------\n  //! Prefetch FileMD and wait\n  //----------------------------------------------------------------------------\n  static void prefetchFileMDAndWait(IView* view, const std::string& path,\n                                    bool follow = true);\n  static void prefetchFileMDAndWait(IView* view, IFileMD::id_t id);\n\n  //------------------------------------------------------------------------------\n  //! Prefetch ContainerMD and wait\n  //------------------------------------------------------------------------------\n  static void prefetchContainerMDAndWait(IView* view, const std::string& path,\n                                         bool follow = true);\n  static void prefetchContainerMDAndWait(IView* view, IContainerMD::id_t id);\n\n  //------------------------------------------------------------------------------\n  //! Prefetch item and wait\n  //------------------------------------------------------------------------------\n  static void prefetchItemAndWait(IView* view, const std::string& path,\n                                  bool follow = true);\n\n  //----------------------------------------------------------------------------\n  //! Prefetch ContainerMD with children and wait\n  //----------------------------------------------------------------------------\n  static void\n  prefetchContainerMDWithChildrenAndWait(IView* view, const std::string& path,\n                                         bool follow = true, bool onlyDirs = false,\n                                         bool limitresult = false,\n                                         uint64_t dir_limit = -1,\n                                         uint64_t file_limit = -1);\n\n  static void\n  prefetchContainerMDWithChildrenAndWait(IView* view, IContainerMD::id_t id,\n                                         bool onlyDirs = false,\n                                         bool limitresult = false,\n                                         uint64_t dir_limit = -1,\n                                         uint64_t file_limit = -1);\n\n  //----------------------------------------------------------------------------\n  //! Prefetch FileMD inode, along with all its parents, and wait\n  //----------------------------------------------------------------------------\n  static void prefetchFileMDWithParentsAndWait(IView* view, IFileMD::id_t id);\n\n  //----------------------------------------------------------------------------\n  //! Prefetch ContainerMD inode, along with all its parents, and wait\n  //----------------------------------------------------------------------------\n  static void prefetchContainerMDWithParentsAndWait(IView* view,\n      IFileMD::id_t id);\n\n  //----------------------------------------------------------------------------\n  //! Prefetch inode metadata, automatically detect if it's a file or directory\n  //----------------------------------------------------------------------------\n  static void prefetchInodeAndWait(IView* view, uint64_t ino);\n\n  //----------------------------------------------------------------------------\n  //! Prefetch inode metadata with all children (if any), automatically detect\n  //! if it's a file or directory\n  //----------------------------------------------------------------------------\n  static void prefetchInodeWithChildrenAndWait(IView* view, uint64_t ino);\n\n  //----------------------------------------------------------------------------\n  //! Prefetch FileList for the given filesystem ID\n  //----------------------------------------------------------------------------\n  static void prefetchFilesystemFileListAndWait(IView* view, IFsView* fsview,\n      IFileMD::location_t location);\n\n  //----------------------------------------------------------------------------\n  //! Prefetch unlinked FileList for the given filesystem ID, along with all\n  //! contained FileMDs.\n  //----------------------------------------------------------------------------\n  static void prefetchFilesystemUnlinkedFileListWithFileMDsAndWait(IView* view,\n      IFsView* fsview, IFileMD::location_t location);\n\n  //----------------------------------------------------------------------------\n  //! Prefetch FileList for the given filesystem ID, along with all contained\n  //! FileMDs.\n  //----------------------------------------------------------------------------\n  static void prefetchFilesystemFileListWithFileMDsAndWait(IView* view,\n      IFsView* fsview, IFileMD::location_t location);\n\n  //----------------------------------------------------------------------------\n  //! Prefetch FileList for the given filesystem ID, along with all contained\n  //! FileMDs, and all parents of those.\n  //----------------------------------------------------------------------------\n  static void prefetchFilesystemFileListWithFileMDsAndParentsAndWait(IView* view,\n      IFsView* fsview, IFileMD::location_t location);\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Prefetch Uri of IFileMDPtr\n  //----------------------------------------------------------------------------\n  folly::Future<std::string> prefetchFileUri(IFileMDPtr file);\n\n  //----------------------------------------------------------------------------\n  //! Prefetch Uri of IContainerMDPtr\n  //----------------------------------------------------------------------------\n  folly::Future<std::string> prefetchContUri(IContainerMDPtr cont);\n\n  IView*           pView;\n  IFileMDSvc*      pFileMDSvc;\n  IContainerMDSvc* pContainerMDSvc;\n\n  std::vector<folly::Future<IFileMDPtr>> mFileMDs;\n  std::vector<folly::Future<IContainerMDPtr>> mContainerMDs;\n  std::vector<folly::Future<FileOrContainerMD>> mItems;\n  std::vector<folly::Future<std::string>> mUris;\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/Resolver.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Utility to resolve files and containers based on proto messages\n//------------------------------------------------------------------------------\n\n#include \"namespace/Resolver.hh\"\n#include \"common/ParseUtils.hh\"\n#include \"common/FileId.hh\"\n#include <XrdOuc/XrdOucString.hh>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Resolve a container specification message to a ContainerMD.\n// Assumes caller holds eosViewRWMutex.\n//------------------------------------------------------------------------------\nIContainerMDPtr Resolver::resolveContainer(IView* view,\n    const ContainerSpecificationProto& proto)\n{\n  ContainerSpecificationProto::ContainerCase type = proto.container_case();\n\n  switch (type) {\n  case ContainerSpecificationProto::kPath: {\n    return view->getContainer(proto.path());\n  }\n\n  case ContainerSpecificationProto::kCid: {\n    int64_t cid;\n\n    if (!eos::common::ParseInt64(proto.cid(), cid)) {\n      throw_mdexception(EINVAL, \"Unable to parse Container ID: \" << proto.cid());\n    }\n\n    return view->getContainerMDSvc()->getContainerMD(cid);\n  }\n\n  case ContainerSpecificationProto::kCxid: {\n    int64_t cid;\n\n    if (!eos::common::ParseInt64(proto.cxid(), cid, 16)) {\n      throw_mdexception(EINVAL, \"Unable to parse Container ID: \" << proto.cxid());\n    }\n\n    return view->getContainerMDSvc()->getContainerMD(cid);\n  }\n\n  default: {\n    throw_mdexception(EINVAL, \"Provided protobuf message is empty, \"\n                      \"unable to resolve container\");\n  }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Parse FileIdentifier based on an string.\n// Recognizes \"fid:\", \"fxid:\", \"ino:\"\n//------------------------------------------------------------------------------\nFileIdentifier Resolver::retrieveFileIdentifier(XrdOucString& str)\n{\n  uint64_t ret;\n\n  if (str.beginswith(\"fid:\")) {\n    return FileIdentifier(strtoull(str.c_str() + 4, 0, 10));\n  }\n\n  if (str.beginswith(\"fxid:\")) {\n    return FileIdentifier(strtoull(str.c_str() + 5, 0, 16));\n  }\n\n  if (str.beginswith(\"/.fxid:\")) {\n    return FileIdentifier(strtoull(str.c_str() + 7, 0, 16));\n  }\n\n  if (str.beginswith(\"ino:\")) {\n    ret = strtoull(str.c_str() + 4, 0, 16);\n\n    if (!eos::common::FileId::IsFileInode(ret)) {\n      return FileIdentifier(0);\n    }\n\n    return FileIdentifier(eos::common::FileId::InodeToFid(ret));\n  }\n\n  return FileIdentifier(0);\n}\n\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/Resolver.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Utility to resolve files and containers based on proto messages\n//------------------------------------------------------------------------------\n\n#pragma once\n#include <chrono>\n#include \"namespace/Namespace.hh\"\n#include \"namespace/MDException.hh\"\n#include \"namespace/interface/Identifiers.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"proto/Ns.pb.h\"\n\nclass XrdOucString;\n\nEOSNSNAMESPACE_BEGIN\n\nusing ContainerSpecificationProto =\n  eos::console::NsProto_ContainerSpecificationProto;\n\nclass Resolver\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Resolve a container specification message to a ContainerMD.\n  //! Assumes caller holds eosViewRWMutex.\n  //----------------------------------------------------------------------------\n  static IContainerMDPtr resolveContainer(IView* view,\n                                          const ContainerSpecificationProto& proto);\n\n  //----------------------------------------------------------------------------\n  //! Parse FileIdentifier based on an string.\n  //! Recognizes \"fid:\", \"fxid:\", \"ino:\"\n  //----------------------------------------------------------------------------\n  static FileIdentifier retrieveFileIdentifier(XrdOucString& str);\n\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/interface/ContainerIterators.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Container map iterators\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_CONTAINER_ITERATORS_HH\n#define EOS_NS_CONTAINER_ITERATORS_HH\n\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/MDLocking.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class FileMapIterator\n//------------------------------------------------------------------------------\nclass FileMapIterator\n{\npublic:\n  FileMapIterator(IContainerMDPtr cont)\n    : container(cont), iResized(false), iValid(false) {\n    iter = cont->filesBegin();\n    iGeneration = generation();\n    if (!iterEnd()) {\n      iValid = true;\n      iKey = iter->first;\n      iValue = iter->second;\n      iShown.insert(iKey);\n    }\n  }\n\n  bool valid() const {\n    return iValid;\n  }\n\n  bool iterEnd() const {\n    return (iter == container->filesEnd());\n  }\n\n  void next() {\n    eos::MDLocking::ContainerReadLock readLocker(container.get());\n\n    // check for a re-sized map\n    if (generation() != iGeneration) {\n      iResized = true;\n      // the hash_map has been re-organized\n      iter = container->filesBegin();\n      if (iterEnd()) {\n\tiValid = false;\n\treturn;\n      }\n\n      do {\n\tif (!iShown.count(iter->first)) {\n\t  break;\n\t} else {\n\t  iter++;\n\t  if (iterEnd()) {\n\t    iValid = false;\n\t    return;\n\t  }\n\t}\n      } while (1);\n\n      iGeneration = generation();\n    } else {\n      // check for a re-sized map\n      if (iResized) {\n\t// in this case we always have to check if a value was already shown\n\titer++;\n\tif (!iterEnd()) {\n\t  do {\n\t    if (!iShown.count(iter->first)) {\n\t      break;\n\t    } else {\n\t      iter++;\n\t      if (iterEnd()) {\n\t\tiValid = false;\n\t\treturn;\n\t      }\n\t    }\n\t  } while (1);\n\t} else {\n\t  iValid = false;\n\t  return;\n\t}\n      } else {\n\titer++;\n      }\n    }\n\n    if (!iterEnd()) {\n      iKey = iter->first;\n      iValue = iter->second;\n      iShown.insert(iKey);\n    } else {\n      iValid = false;\n    }\n  }\n\n  std::string key() const {\n    return iKey;\n  }\n\n  IFileMD::id_t value() const {\n    return iValue;\n  }\n\n  uint64_t generation() {\n    return container->getFileMapGeneration();\n  }\n\nprivate:\n\n\n  IContainerMDPtr container;\n  eos::IContainerMD::FileMap::const_iterator iter;\n  std::set<std::string> iShown;\n  std::string iKey;\n  uint64_t iValue;\n  uint64_t iGeneration;\n  bool iResized;\n  bool iValid;\n};\n\n//------------------------------------------------------------------------------\n//! Class ContainerIterator\n//------------------------------------------------------------------------------\nclass ContainerMapIterator\n{\npublic:\n  ContainerMapIterator(IContainerMDPtr cont)\n    : container(cont), iResized(false), iValid(false) {\n    iter = cont->subcontainersBegin();\n    iGeneration = generation();\n    if (!iterEnd()) {\n      iValid = true;\n      iKey = iter->first;\n      iValue = iter->second;\n      iShown.insert(iKey);\n    }\n  }\n\n  bool valid() const {\n    return iValid;\n  }\n\n  bool iterEnd() const {\n    return (iter == container->subcontainersEnd());\n  }\n\n  void next() {\n    eos::MDLocking::ContainerReadLock readLocker(container.get());\n\n    // check for a re-sized map\n    if (generation() != iGeneration) {\n      iResized = true;\n      // the hash_map has been re-organized\n      iter = container->subcontainersBegin();\n      if (iterEnd()) {\n\tiValid = false;\n\treturn;\n      }\n\n      do {\n\tif (!iShown.count(iter->first)) {\n\t  break;\n\t} else {\n\t  iter++;\n\t  if (iterEnd()) {\n\t    iValid = false;\n\t    return;\n\t  }\n\t}\n      } while (1);\n\n      iGeneration = generation();\n    } else {\n      // check for a re-sized map\n      if (iResized) {\n\t// in this case we always have to check if a value was already shown\n\titer++;\n\tif (!iterEnd()) {\n\t  do {\n\t    if (!iShown.count(iter->first)) {\n\t      break;\n\t    } else {\n\t      iter++;\n\t      if (iterEnd()) {\n\t\tiValid = false;\n\t\treturn;\n\t      }\n\t    }\n\t  } while (1);\n\t} else {\n\t  iValid = false;\n\t  return;\n\t}\n      } else {\n\titer++;\n      }\n    }\n\n    if (!iterEnd()) {\n      iKey = iter->first;\n      iValue = iter->second;\n      iShown.insert(iKey);\n    } else {\n      iValid = false;\n    }\n  }\n\n  std::string key() const {\n    return iKey;\n  }\n\n  IFileMD::id_t value() const {\n    return iValue;\n  }\n\n  uint64_t generation() {\n    return container->getContainerMapGeneration();\n  }\n\nprivate:\n\n  IContainerMDPtr container;\n  eos::IContainerMD::ContainerMap::const_iterator iter;\n  std::set<std::string> iShown;\n  std::string iKey;\n  uint64_t iValue;\n  uint64_t iGeneration;\n  bool iResized;\n  bool iValid;\n};\n\nEOSNSNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "namespace/interface/IContainerMD.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2015 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//! @brief  Class representing the container interface\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_ICONTAINER_MD_HH\n#define EOS_NS_ICONTAINER_MD_HH\n\n#include \"namespace/Namespace.hh\"\n#include \"namespace/utils/Buffer.hh\"\n#include \"namespace/utils/LocalityHint.hh\"\n#include \"namespace/interface/Identifiers.hh\"\n#include \"common/Murmur3.hh\"\n#include <stdint.h>\n#include <unistd.h>\n#include <memory>\n#include <string>\n#include <map>\n#include <set>\n#include <chrono>\n#include <sys/time.h>\n#include <google/dense_hash_map>\n#include <folly/futures/Future.h>\n#include \"namespace/locking/NSObjectLocker.hh\"\n#include \"namespace/MDLocking.hh\"\n\n//#include <folly/concurrency/ConcurrentHashMap.h>\n\nEOSNSNAMESPACE_BEGIN\n\n//! Forward declarations\nclass IContainerMDSvc;\nclass IFileMDSvc;\nclass IFileMD;\nclass IContainerMD;\nclass FileMapIterator;\nclass ContainerMapIterator;\n\n\n//------------------------------------------------------------------------------\n//! Holds either a FileMD or a ContainerMD. Only one of these are ever filled,\n//! the other will be nullptr. Both might be nullptr as well.\n//------------------------------------------------------------------------------\nstruct FileOrContainerMD {\n  IFileMDPtr file;\n  IContainerMDPtr container;\n};\n\n//------------------------------------------------------------------------------\n//! Class holding the interface to the metadata information concerning a\n//! single container\n//------------------------------------------------------------------------------\nclass IContainerMD : public LockableNSObjMD\n{\nprotected:\n  // Convenient using to avoid having to put MDLocking::\n  using ContainerReadLock = MDLocking::ContainerReadLock;\n  using ContainerWriteLock = MDLocking::ContainerWriteLock;\n  using ContainerReadLockPtr = MDLocking::ContainerReadLockPtr;\n  using ContainerWriteLockPtr = MDLocking::ContainerWriteLockPtr;\n  using FileReadLock = MDLocking::FileReadLock;\n  using FileWriteLock = MDLocking::FileWriteLock;\n\npublic:\n  //----------------------------------------------------------------------------\n  //! Type definitions\n  //----------------------------------------------------------------------------\n  typedef uint64_t id_t;\n  typedef struct timespec ctime_t;\n  typedef struct timespec mtime_t;\n  typedef struct timespec tmtime_t;\n  typedef std::map<std::string, std::string> XAttrMap;\n\n  using ContainerMap = google::dense_hash_map<std::string, IContainerMD::id_t,\n        Murmur3::MurmurHasher<std::string> >;\n  using FileMap = google::dense_hash_map<std::string, IContainerMD::id_t,\n        Murmur3::MurmurHasher<std::string> >;\n\n  template<typename ObjectMDPtr, typename LockType> friend class\n    NSObjectMDBaseLock;\n  template<typename ObjectMDPtr, typename LockType> friend class NSObjectMDLock;\n  template<typename ObjectMDPtr, typename LockType> friend class\n    NSObjectMDTryLock;\n  friend class LockableNSObjMD;\n\n  using identifier_t = ContainerIdentifier;\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  IContainerMD(): LockableNSObjMD(), mIsDeleted(false) {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~IContainerMD() {}\n\n  //----------------------------------------------------------------------------\n  //! Virtual copy constructor\n  //----------------------------------------------------------------------------\n  virtual IContainerMD* clone() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Virtual copy constructor\n  //----------------------------------------------------------------------------\n  virtual void InheritChildren(const IContainerMD& other) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Add container\n  //----------------------------------------------------------------------------\n  virtual void addContainer(IContainerMD* container) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove container\n  //----------------------------------------------------------------------------\n  virtual void removeContainer(const std::string& name) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Find sub container, asynchronous API\n  //----------------------------------------------------------------------------\n  virtual folly::Future<IContainerMDPtr>\n  findContainerFut(const std::string& name) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Find sub container\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IContainerMD>\n  findContainer(const std::string& name) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get number of containers\n  //----------------------------------------------------------------------------\n  virtual size_t getNumContainers() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Add file\n  //----------------------------------------------------------------------------\n  virtual void addFile(IFileMD* file) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove file\n  //----------------------------------------------------------------------------\n  virtual void removeFile(const std::string& name) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Find file, asynchronous API\n  //----------------------------------------------------------------------------\n  virtual folly::Future<IFileMDPtr> findFileFut(const std::string& name) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Find file\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IFileMD> findFile(const std::string& name) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Find item\n  //----------------------------------------------------------------------------\n  virtual folly::Future<FileOrContainerMD> findItem(const std::string& name) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get number of files\n  //----------------------------------------------------------------------------\n  virtual size_t getNumFiles() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get name\n  //----------------------------------------------------------------------------\n  virtual const std::string& getName() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set name\n  //----------------------------------------------------------------------------\n  virtual void setName(const std::string& name) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get container id\n  //----------------------------------------------------------------------------\n  virtual IContainerMD::id_t getId() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get container identifier\n  //----------------------------------------------------------------------------\n  virtual identifier_t getIdentifier() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get parent id\n  //----------------------------------------------------------------------------\n  virtual IContainerMD::id_t getParentId() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get parent identifier\n  //----------------------------------------------------------------------------\n  virtual identifier_t\n  getParentIdentifier() const\n  {\n    return identifier_t(getParentId());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set parent id\n  //----------------------------------------------------------------------------\n  virtual void setParentId(IContainerMD::id_t parentId) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get the flags\n  //----------------------------------------------------------------------------\n  virtual uint16_t getFlags() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set flags\n  //----------------------------------------------------------------------------\n  virtual void setFlags(uint16_t flags) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set creation time\n  //----------------------------------------------------------------------------\n  virtual void setMTime(mtime_t mtime) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set creation time to now\n  //----------------------------------------------------------------------------\n  virtual void setMTimeNow() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Trigger an mtime change event\n  //----------------------------------------------------------------------------\n  virtual void notifyMTimeChange(IContainerMDSvc* containerMDSvc) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get modification time\n  //----------------------------------------------------------------------------\n  virtual void getMTime(mtime_t& mtime) const  = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set propagated modification time (if newer)\n  //----------------------------------------------------------------------------\n  virtual bool setTMTime(tmtime_t tmtime) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set propagated modification time to now\n  //----------------------------------------------------------------------------\n  virtual void setTMTimeNow() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get creation time\n  //----------------------------------------------------------------------------\n  virtual void getTMTime(tmtime_t& tmtime) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get tree size\n  //----------------------------------------------------------------------------\n  virtual uint64_t getTreeSize() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set tree size\n  //----------------------------------------------------------------------------\n  virtual void setTreeSize(uint64_t treesize) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Update tree size\n  //!\n  //! @param delta can be negative or positive\n  //----------------------------------------------------------------------------\n  virtual uint64_t updateTreeSize(int64_t delta) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Update tree containers\n  //----------------------------------------------------------------------------\n  virtual uint64_t updateTreeContainers(int64_t delta) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Update tree files\n  //----------------------------------------------------------------------------\n  virtual uint64_t updateTreeFiles(int64_t delta) = 0;\n\n      //----------------------------------------------------------------------------\n  //! Set tree containers\n  //----------------------------------------------------------------------------\n  virtual void setTreeContainers(uint64_t treeContainers) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get tree containers\n  //----------------------------------------------------------------------------\n  virtual uint64_t getTreeContainers() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set file containers\n  //----------------------------------------------------------------------------\n  virtual void setTreeFiles(uint64_t treeFiles) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get file containers\n  //----------------------------------------------------------------------------\n  virtual uint64_t getTreeFiles() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get creation time\n  //----------------------------------------------------------------------------\n  virtual void getCTime(ctime_t& ctime) const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set creation time\n  //----------------------------------------------------------------------------\n  virtual void setCTime(ctime_t ctime) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set creation time to now\n  //----------------------------------------------------------------------------\n  virtual void setCTimeNow() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get uid\n  //----------------------------------------------------------------------------\n  virtual uid_t getCUid() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set uid\n  //----------------------------------------------------------------------------\n  virtual void setCUid(uid_t uid) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get gid\n  //----------------------------------------------------------------------------\n  virtual gid_t getCGid() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set gid\n  //----------------------------------------------------------------------------\n  virtual void setCGid(gid_t gid) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get cloneId\n  //----------------------------------------------------------------------------\n  virtual time_t getCloneId() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set cloneId\n  //----------------------------------------------------------------------------\n  virtual void setCloneId(time_t id) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get cloneFST\n  //----------------------------------------------------------------------------\n  virtual const std::string getCloneFST() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set set cloneFST\n  //----------------------------------------------------------------------------\n  virtual void setCloneFST(const std::string& data) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get mode\n  //----------------------------------------------------------------------------\n  virtual mode_t getMode() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set mode\n  //----------------------------------------------------------------------------\n  virtual void setMode(mode_t mode) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get the attribute\n  //----------------------------------------------------------------------------\n  virtual std::string getAttribute(const std::string& name) const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Add extended attribute\n  //----------------------------------------------------------------------------\n  virtual void setAttribute(const std::string& name,\n                            const std::string& value) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove attribute\n  //----------------------------------------------------------------------------\n  virtual void removeAttribute(const std::string& name) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Check if the attribute exist\n  //----------------------------------------------------------------------------\n  virtual bool hasAttribute(const std::string& name) const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Return number of attributes\n  //----------------------------------------------------------------------------\n  virtual size_t numAttributes() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get map copy of the extended attributes\n  //----------------------------------------------------------------------------\n  virtual XAttrMap getAttributes() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Check the access permissions\n  //!\n  //! @return true if all the requested rights are granted, false otherwise\n  //----------------------------------------------------------------------------\n  virtual bool access(uid_t uid, gid_t gid, int flags = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Serialize the object to a buffer\n  //----------------------------------------------------------------------------\n  virtual void serialize(Buffer& buffer) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Deserialize the class to a buffer\n  //----------------------------------------------------------------------------\n  virtual void deserialize(Buffer& buffer) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get value tracking changes to the metadata object\n  //----------------------------------------------------------------------------\n  virtual uint64_t getClock() const\n  {\n    return 0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get env representation of the container object\n  //!\n  //! @param env string where representation is stored\n  //! @param escapeAnd if true escape & with #AND# ...\n  //----------------------------------------------------------------------------\n  virtual void getEnv(std::string& env, bool escapeAnd = false) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Check if object is \"deleted\" - in the sense that it's not valid anymore\n  //----------------------------------------------------------------------------\n  virtual bool isDeleted() const\n  {\n    return mIsDeleted;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set file as \"deleted\" - in the sense that it's not valid anymore\n  //----------------------------------------------------------------------------\n  virtual void setDeleted()\n  {\n    mIsDeleted = true;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get locality hint for this container.\n  //----------------------------------------------------------------------------\n  virtual std::string getLocalityHint() const\n  {\n    return LocalityHint::build(identifier_t(getParentId()), getName());\n  }\n\n  std::chrono::steady_clock::time_point getLastPrefetch() const\n  {\n    std::shared_lock lock(mLastPrefetchMtx);\n    return mLastPrefetch;\n  }\n\n  void setLastPrefetch(std::chrono::steady_clock::time_point tp)\n  {\n    std::unique_lock lock(mLastPrefetchMtx);\n    mLastPrefetch = tp;\n  }\n\nprivate:\n  friend class FileMapIterator;\n  friend class ContainerMapIterator;\n\n  //----------------------------------------------------------------------------\n  //! Make copy constructor and assignment operator private to avoid \"slicing\"\n  //! when dealing with derived classes.\n  //----------------------------------------------------------------------------\n  IContainerMD(const IContainerMD& other) = delete;\n  IContainerMD& operator=(const IContainerMD& other) = delete;\n\n  mutable std::atomic<bool>\n  mIsDeleted; ///< Mark if object is still in cache but it was deleted\n\n  std::chrono::steady_clock::time_point mLastPrefetch;\n  mutable std::shared_mutex mLastPrefetchMtx;\n\n  //----------------------------------------------------------------------------\n  //! getMutex()\n  //----------------------------------------------------------------------------\n  std::shared_timed_mutex& getMutex() const override\n  {\n    return mMutex;\n  }\n\nprotected:\n  //----------------------------------------------------------------------------\n  //! Get iterator to the begining of the subcontainers map\n  //----------------------------------------------------------------------------\n  virtual eos::IContainerMD::ContainerMap::const_iterator\n  subcontainersBegin() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get iterator to the end of the subcontainers map\n  //----------------------------------------------------------------------------\n  virtual eos::IContainerMD::ContainerMap::const_iterator\n  subcontainersEnd() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get generation value to check interator validity\n  //----------------------------------------------------------------------------\n  virtual uint64_t getContainerMapGeneration() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get iterator to the begining of the files map\n  //----------------------------------------------------------------------------\n  virtual eos::IContainerMD::FileMap::const_iterator\n  filesBegin() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get iterator to the end of the files map\n  //----------------------------------------------------------------------------\n  virtual eos::IContainerMD::FileMap::const_iterator\n  filesEnd() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get generation value to check interator validity\n  //----------------------------------------------------------------------------\n  virtual uint64_t getFileMapGeneration() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get a copy of ContainerMap\n  //----------------------------------------------------------------------------\n  virtual IContainerMD::ContainerMap copyContainerMap() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get a copy of FileMap\n  //----------------------------------------------------------------------------\n  virtual IContainerMD::FileMap copyFileMap() const = 0;\n\n  mutable std::shared_timed_mutex mMutex;\n};\n\nEOSNSNAMESPACE_END\n\n#endif // EOS_NS_ICONTAINER_MD_HH\n"
  },
  {
    "path": "namespace/interface/IContainerMDSvc.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Lukasz Janyst <ljanyst@cern.ch>\n// desc:   ContainerMD service interface\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_I_CONTAINER_MD_SVC_HH\n#define EOS_NS_I_CONTAINER_MD_SVC_HH\n\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"namespace/MDException.hh\"\n#include \"namespace/MDLocking.hh\"\n#include <map>\n#include <string>\n\nEOSNSNAMESPACE_BEGIN\n\n//! Forward declaration\nclass IQuotaStats;\n\n//----------------------------------------------------------------------------\n//! Interface for the listener that is notified about all of the\n//! actions performed in a IContainerMDSvc\n//----------------------------------------------------------------------------\nclass IContainerMDChangeListener\n{\npublic:\n  enum Action {\n    Updated = 0,\n    Deleted,\n    Created,\n    MTimeChange\n  };\n\n  virtual ~IContainerMDChangeListener() {}\n  virtual void containerMDChanged(IContainerMD* obj, Action type) = 0;\n};\n\n//----------------------------------------------------------------------------\n//! Interface for class responsible for managing the metadata information\n//! concerning containers. It is responsible for assigning container IDs and\n//! managing storage of the metadata. Could be implemented as a change log or\n//! db based store or as an interface to memcached or some other caching\n//! system or key value store\n//----------------------------------------------------------------------------\nclass IContainerMDSvc\n{\npublic:\n  virtual ~IContainerMDSvc() {}\n\n  //------------------------------------------------------------------------\n  //! Initialize the container service\n  //------------------------------------------------------------------------\n  virtual void initialize() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Configure the container service\n  //!\n  //! @param config map of configuration parameters\n  //----------------------------------------------------------------------------\n  virtual void configure(const std::map<std::string, std::string>& config) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Finalize the container service\n  //----------------------------------------------------------------------------\n  virtual void finalize() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Asynchronously get the container metadata information for the given ID\n  //----------------------------------------------------------------------------\n  virtual folly::Future<IContainerMDPtr> getContainerMDFut(IContainerMD::id_t id) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get the container metadata information for the given ID\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IContainerMD>\n  getContainerMD(IContainerMD::id_t id) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get the container metadata information for the given ID and clock\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IContainerMD>\n  getContainerMD(IContainerMD::id_t id, uint64_t* clock) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Drop cached ContainerMD - return true if found\n  //----------------------------------------------------------------------------\n  virtual bool\n  dropCachedContainerMD(ContainerIdentifier id) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Create new container metadata object with an assigned id, the user has\n  //! to fill all the remaining fields\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IContainerMD> createContainer(IContainerMD::id_t id) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Update the contaienr metadata in the backing store after the\n  //! ContainerMD object has been changed\n  //----------------------------------------------------------------------------\n  virtual void updateStore(IContainerMD* obj) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove object from the store\n  //----------------------------------------------------------------------------\n  virtual void removeContainer(IContainerMD* obj) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get number of containers\n  //----------------------------------------------------------------------------\n  virtual uint64_t getNumContainers() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Add file listener that will be notified about all of the changes in\n  //! the store\n  //----------------------------------------------------------------------------\n  virtual void addChangeListener(IContainerMDChangeListener* listener) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set the QuotaStats object for the follower\n  //!\n  //! @param quota_stats object implementing the IQuotaStats interface\n  //----------------------------------------------------------------------------\n  virtual void setQuotaStats(IQuotaStats* quota_stats) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Notify all subscribed listener\n  //----------------------------------------------------------------------------\n  virtual void notifyListeners(IContainerMD* obj,\n                               IContainerMDChangeListener::Action a) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get the orphans container\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IContainerMD>\n  getLostFoundContainer(const std::string& name) = 0;\n\n  //----------------------------------------------------------------------------\n  // Create container in parent\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IContainerMD>\n  createInParent(const std::string& name, IContainerMD* parent) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set file metadata service\n  //----------------------------------------------------------------------------\n  virtual void setFileMDService(IFileMDSvc* file_svc) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set container accounting\n  //---------------------------------------------------------------------------\n  virtual void\n  setContainerAccounting(IFileMDChangeListener* containerAccounting) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get first free container id\n  //----------------------------------------------------------------------------\n  virtual IContainerMD::id_t getFirstFreeId() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Retrieve file metadata cache statistics\n  //----------------------------------------------------------------------------\n  virtual CacheStatistics getCacheStatistics() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Blacklist IDs below the given threshold\n  //----------------------------------------------------------------------------\n  virtual void blacklistBelow(ContainerIdentifier id) = 0;\n\n};\n\nEOSNSNAMESPACE_END\n\n#endif // EOS_NS_I_CONTAINER_MD_SVC_HH\n"
  },
  {
    "path": "namespace/interface/IFileMD.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2015 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//! @brief Class representing the file metadata interface\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_IFILE_MD_HH\n#define EOS_NS_IFILE_MD_HH\n\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/Identifiers.hh\"\n#include \"namespace/locking/NSObjectLocker.hh\"\n#include \"namespace/utils/Buffer.hh\"\n#include \"namespace/utils/LocalityHint.hh\"\n#include <memory>\n#include <stdint.h>\n#include <string>\n#include <sys/time.h>\n#include \"namespace/MDLocking.hh\"\n#include \"common/LayoutId.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\nclass IFileMDSvc;\n\n//------------------------------------------------------------------------------\n//! Interface to file metadata\n//------------------------------------------------------------------------------\nclass IFileMD : public LockableNSObjMD\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Type definitions\n  //----------------------------------------------------------------------------\n  typedef uint64_t id_t;\n  typedef uint32_t location_t;\n  typedef uint32_t layoutId_t;\n  typedef struct timespec ctime_t;\n  typedef std::vector<location_t> LocationVector;\n  typedef std::map<std::string, std::string> XAttrMap;\n  typedef std::map<std::string, std::string> QoSAttrMap;\n  using identifier_t = FileIdentifier;\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  IFileMD(): LockableNSObjMD(), mIsDeleted(false) {};\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~IFileMD() {};\n\n  //----------------------------------------------------------------------------\n  //! Virtual copy constructor\n  //----------------------------------------------------------------------------\n  virtual IFileMD* clone() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get file id\n  //----------------------------------------------------------------------------\n  virtual IFileMD::id_t getId() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get file identifier\n  //----------------------------------------------------------------------------\n  virtual identifier_t getIdentifier() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get creation time\n  //----------------------------------------------------------------------------\n  virtual void getCTime(ctime_t& ctime) const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set creation time\n  //----------------------------------------------------------------------------\n  virtual void setCTime(ctime_t ctime) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set creation time to now\n  //----------------------------------------------------------------------------\n  virtual void setCTimeNow() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get modification time\n  //----------------------------------------------------------------------------\n  virtual void getMTime(ctime_t& mtime) const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set modification time\n  //----------------------------------------------------------------------------\n  virtual void setMTime(ctime_t mtime) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set modification time to now\n  //----------------------------------------------------------------------------\n  virtual void setMTimeNow() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get access time\n  //----------------------------------------------------------------------------\n  virtual void getATime(ctime_t& ctime) const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set access time\n  //----------------------------------------------------------------------------\n  virtual void setATime(ctime_t atime) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set access time to now\n  //----------------------------------------------------------------------------\n  virtual bool setATimeNow(uint64_t olderthan) = 0;\n\n  //----------------------------------------------------------------------------\n  //! get sync time\n  //----------------------------------------------------------------------------\n  virtual void getSyncTime(ctime_t& stime) const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set sync time\n  //----------------------------------------------------------------------------\n  virtual void setSyncTime(ctime_t stime) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set sync time\n  //----------------------------------------------------------------------------\n  virtual void setSyncTimeNow() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get clone id\n  //----------------------------------------------------------------------------\n  virtual uint64_t getCloneId() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set clone id\n  //----------------------------------------------------------------------------\n  virtual void setCloneId(uint64_t id) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get cloneFST\n  //----------------------------------------------------------------------------\n  virtual const std::string getCloneFST() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set cloneFST\n  //----------------------------------------------------------------------------\n  virtual void setCloneFST(const std::string& data) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get size\n  //----------------------------------------------------------------------------\n  virtual uint64_t getSize() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set size - 48 bytes will be used\n  //----------------------------------------------------------------------------\n  virtual void setSize(uint64_t size) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get tag\n  //----------------------------------------------------------------------------\n  virtual IContainerMD::id_t getContainerId() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set tag\n  //----------------------------------------------------------------------------\n  virtual void setContainerId(IContainerMD::id_t containerId) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get checksum\n  //----------------------------------------------------------------------------\n  virtual const Buffer getChecksum() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set checksum\n  //----------------------------------------------------------------------------\n  virtual void setChecksum(const Buffer& checksum) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Clear checksum\n  //----------------------------------------------------------------------------\n  virtual void clearChecksum(uint8_t size = 20) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set checksum\n  //!\n  //! @param checksum address of a memory location string the checksum\n  //! @param size     size of the checksum in bytes\n  //----------------------------------------------------------------------------\n  virtual void setChecksum(const void* checksum, uint8_t size) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Add alternative checksum\n  //!\n  //! @param checksumType type of the checksum to store\n  //! @param checksum address of a memory location string the checksum\n  //! @param size     size of the checksum in bytes\n  //----------------------------------------------------------------------------\n  virtual void addAltXs(eos::common::LayoutId::eChecksum\n                        checksumType, const char* checksum, size_t size) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get all the alternative checksums stored for the file\n  //----------------------------------------------------------------------------\n  virtual std::map<eos::common::LayoutId::eChecksum, std::string>\n  getAltXs() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Clear the alternative checksums\n  //----------------------------------------------------------------------------\n  virtual void clearAltXs() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove the alternative checksum\n  //!\n  //! @param checksumType type of the checksum\n  //----------------------------------------------------------------------------\n  virtual void removeAltXs(eos::common::LayoutId::eChecksum type) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get name\n  //----------------------------------------------------------------------------\n  virtual const std::string getName() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set name\n  //----------------------------------------------------------------------------\n  virtual void setName(const std::string& name) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Add location\n  //----------------------------------------------------------------------------\n  virtual void addLocation(location_t location) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get vector with all the locations\n  //----------------------------------------------------------------------------\n  virtual LocationVector getLocations() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get location\n  //----------------------------------------------------------------------------\n  virtual location_t getLocation(unsigned int index) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove location that was previously unlinked\n  //----------------------------------------------------------------------------\n  virtual void removeLocation(location_t location) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove all locations that were previously unlinked\n  //----------------------------------------------------------------------------\n  virtual void removeAllLocations() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get vector with all unlinked locations\n  //----------------------------------------------------------------------------\n  virtual LocationVector getUnlinkedLocations() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Unlink location\n  //----------------------------------------------------------------------------\n  virtual void unlinkLocation(location_t location) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Unlink all locations\n  //----------------------------------------------------------------------------\n  virtual void unlinkAllLocations() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Clear unlinked locations without notifying the listeners\n  //----------------------------------------------------------------------------\n  virtual void clearUnlinkedLocations() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Test the unlinked location\n  //----------------------------------------------------------------------------\n  virtual bool hasUnlinkedLocation(location_t location) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get number of unlinked locations\n  //----------------------------------------------------------------------------\n  virtual size_t getNumUnlinkedLocation() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Clear locations without notifying the listeners\n  //----------------------------------------------------------------------------\n  virtual void clearLocations() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Test the location\n  //----------------------------------------------------------------------------\n  virtual bool hasLocation(location_t location) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get number of location\n  //----------------------------------------------------------------------------\n  virtual size_t getNumLocation() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get uid\n  //----------------------------------------------------------------------------\n  virtual uid_t getCUid() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set uid\n  //----------------------------------------------------------------------------\n  virtual void setCUid(uid_t uid) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get gid\n  //----------------------------------------------------------------------------\n  virtual gid_t getCGid() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set gid\n  //----------------------------------------------------------------------------\n  virtual void setCGid(gid_t gid) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get layout\n  //----------------------------------------------------------------------------\n  virtual layoutId_t getLayoutId() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set layout\n  //----------------------------------------------------------------------------\n  virtual void setLayoutId(layoutId_t layoutId) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get flags\n  //----------------------------------------------------------------------------\n  virtual uint16_t getFlags() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get the n-th flag\n  //----------------------------------------------------------------------------\n  virtual bool getFlag(uint8_t n) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set flags\n  //----------------------------------------------------------------------------\n  virtual void setFlags(uint16_t flags) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set the n-th flag\n  //----------------------------------------------------------------------------\n  virtual void setFlag(uint8_t n, bool flag) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set the FileMDSvc object\n  //----------------------------------------------------------------------------\n  virtual void setFileMDSvc(IFileMDSvc* fileMDSvc) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get the FileMDSvc object\n  //----------------------------------------------------------------------------\n  virtual IFileMDSvc* getFileMDSvc() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get symbolic link\n  //----------------------------------------------------------------------------\n  virtual std::string getLink() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set symbolic link\n  //----------------------------------------------------------------------------\n  virtual void setLink(std::string link) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Check if symbolic link\n  //----------------------------------------------------------------------------\n  virtual bool isLink() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Add extended attribute\n  //----------------------------------------------------------------------------\n  virtual void setAttribute(const std::string& name,\n                            const std::string& value) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove attribute\n  //----------------------------------------------------------------------------\n  virtual void removeAttribute(const std::string& name) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove all attributes\n  //----------------------------------------------------------------------------\n  virtual void clearAttributes() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Check if the attribute exist\n  //----------------------------------------------------------------------------\n  virtual bool hasAttribute(const std::string& name) const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Return number of attributes\n  //----------------------------------------------------------------------------\n  virtual size_t numAttributes() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get the attribute\n  //----------------------------------------------------------------------------\n  virtual std::string getAttribute(const std::string& name) const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get map copy of the extended attributes\n  //----------------------------------------------------------------------------\n  virtual XAttrMap getAttributes() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Serialize the object to a buffer\n  //----------------------------------------------------------------------------\n  virtual void serialize(Buffer& buffer) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Deserialize the class to a buffer\n  //----------------------------------------------------------------------------\n  virtual void deserialize(const Buffer& buffer) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get value tracking changes to the metadata object\n  //----------------------------------------------------------------------------\n  virtual uint64_t getClock() const\n  {\n    return 0;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if object is \"deleted\" - in the sense that it's not valid anymore\n  //----------------------------------------------------------------------------\n  virtual bool isDeleted() const\n  {\n    return mIsDeleted;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set file as \"deleted\" - in the sense that it's not valid anymore\n  //----------------------------------------------------------------------------\n  virtual void setDeleted()\n  {\n    mIsDeleted = true;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get locality hint for this file.\n  //----------------------------------------------------------------------------\n  virtual std::string getLocalityHint() const\n  {\n    return LocalityHint::build(ContainerIdentifier(getContainerId()), getName());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get env representation of the file object\n  //!\n  //! @param env string where representation is stored\n  //! @param escapeAnd if true escape & with #AND# ...\n  //----------------------------------------------------------------------------\n  virtual void getEnv(std::string& env, bool escapeAnd = false) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Delete copy constructor and assignment operator to avoid \"slicing\"\n  //! when dealing with derived classes.\n  //----------------------------------------------------------------------------\n  IFileMD(const IFileMD& other) = delete;\n\n  IFileMD& operator=(const IFileMD& other) = delete;\n\n  template<typename ObjectMDPtr, typename LockType> friend class\n    NSObjectMDBaseLock;\n  template<typename ObjectMDPtr, typename LockType> friend class NSObjectMDLock;\n  template<typename ObjectMDPtr, typename LockType> friend class\n    NSObjectMDTryLock;\n  friend class LockableNSObjMD;\n\nprotected:\n  mutable std::shared_timed_mutex mMutex;\n\nprivate:\n  std::atomic<bool>\n  mIsDeleted; ///< Mark if object is still in cache but it was deleted\n\n  //----------------------------------------------------------------------------\n  //! getMutex()\n  //----------------------------------------------------------------------------\n  std::shared_timed_mutex& getMutex() const override\n  {\n    return mMutex;\n  }\n};\n\nEOSNSNAMESPACE_END\n\n#endif // EOS_NS_IFILE_MD_HH\n"
  },
  {
    "path": "namespace/interface/IFileMDSvc.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Lukasz Janyst <ljanyst@cern.ch>\n// desc:   FileMD interface\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_I_FILE_MD_SVC_HH\n#define EOS_NS_I_FILE_MD_SVC_HH\n\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/MDException.hh\"\n#include \"namespace/interface/Identifiers.hh\"\n#include \"namespace/interface/Misc.hh\"\n#include \"namespace/MDLocking.hh\"\n#include <folly/futures/Future.h>\n#include <map>\n#include <string>\n\nEOSNSNAMESPACE_BEGIN\n\n//! Forward declaration\nclass IContainerMDSvc;\nclass IQuotaStats;\n\nstruct TreeInfos {\n  int64_t dsize; //Tree size to update (delta)\n  int64_t dtreefiles; //Tree files to update (delta)\n  int64_t dtreecontainers; //Tree containers to update (delta)\n\n  TreeInfos():dsize(0),dtreefiles(0),dtreecontainers(0) {}\n  TreeInfos(int64_t dtreeSize, int64_t dtreeFiles, int64_t dtreeContainers):dsize(dtreeSize),dtreefiles(dtreeFiles),dtreecontainers(dtreeContainers){}\n\n  TreeInfos operator-() const {\n    return {-dsize,-dtreefiles,-dtreecontainers};\n  }\n\n  TreeInfos & operator+=(const TreeInfos & treeInfos) {\n    dsize += treeInfos.dsize;\n    dtreefiles += treeInfos.dtreefiles;\n    dtreecontainers += treeInfos.dtreecontainers;\n    return *this;\n  }\n};\n\n//------------------------------------------------------------------------------\n//! Interface for a listener that is notified about all of the\n//! actions performed in a IFileMDSvc\n//------------------------------------------------------------------------------\nclass IFileMDChangeListener\n{\npublic:\n  enum Action {\n    Updated = 0,\n    Deleted,\n    Created,\n    LocationAdded,\n    LocationUnlinked,\n    LocationRemoved,\n    SizeChange\n  };\n\n  //---------------------------------------------------------------------------\n  //! Event sent to the listener\n  //----------------------------------------------------------------------------\n  struct Event {\n    Event(IFileMD* _file, Action _action,\n          IFileMD::location_t _location = 0,\n          TreeInfos _changed_tree = {0,0,0}):\n      file(_file),\n      action(_action),\n      treeChange(_changed_tree),\n      location(_location) {}\n\n    IFileMD*             file;\n    Action               action;\n    TreeInfos            treeChange;\n    IFileMD::location_t  location;\n\n  };\n\n  virtual ~IFileMDChangeListener() {}\n  virtual void fileMDChanged(Event* event) = 0;\n  virtual void fileMDRead(IFileMD* obj) = 0;\n  virtual bool fileMDCheck(IFileMD* obj) = 0;\n  virtual void AddTree(IContainerMD* obj , TreeInfos treeInfos) = 0;\n  virtual void RemoveTree(IContainerMD* obj , TreeInfos treeInfos) = 0;\n};\n\n//------------------------------------------------------------------------------\n//! Interface for a file visitor\n//------------------------------------------------------------------------------\nclass IFileVisitor\n{\npublic:\n  virtual void visitFile(IFileMD* file) = 0;\n};\n\n//----------------------------------------------------------------------------\n//! Interface for class responsible for managing the metadata information\n//! concerning files. It is responsible for assigning file IDs and managing\n//! storage of the metadata. Could be implemented as a change log or db based\n//! store or as an interface to memcached or some other caching system or\n//! key value store\n//----------------------------------------------------------------------------\nclass IFileMDSvc\n{\npublic:\n  //------------------------------------------------------------------------\n  //! Destructor\n  //------------------------------------------------------------------------\n  virtual ~IFileMDSvc() {}\n\n  //------------------------------------------------------------------------\n  //! Initialize the file service\n  //------------------------------------------------------------------------\n  virtual void initialize() = 0;\n\n  //------------------------------------------------------------------------\n  //! Configure the file service\n  //------------------------------------------------------------------------\n  virtual void configure(const std::map<std::string, std::string>& config) = 0;\n\n  //------------------------------------------------------------------------\n  //! Finalize the file service\n  //------------------------------------------------------------------------\n  virtual void finalize() = 0;\n\n  //------------------------------------------------------------------------\n  //! Asynchronously get the file metadata information for the given file ID\n  //------------------------------------------------------------------------\n  virtual folly::Future<IFileMDPtr> getFileMDFut(IFileMD::id_t id) = 0;\n\n  //------------------------------------------------------------------------\n  //! Get the file metadata information for the given file ID\n  //------------------------------------------------------------------------\n  virtual std::shared_ptr<IFileMD> getFileMD(IFileMD::id_t id) = 0;\n\n  //------------------------------------------------------------------------\n  //! Get the file metadata information for the given file ID and clock\n  //! value\n  //------------------------------------------------------------------------\n  virtual std::shared_ptr<IFileMD> getFileMD(IFileMD::id_t id,\n      uint64_t* clock) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Check if a FileMD with a given identifier exists - no caching\n  //----------------------------------------------------------------------------\n  virtual folly::Future<bool> hasFileMD(const eos::FileIdentifier id) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Drop cached FileMD - return true if found\n  //----------------------------------------------------------------------------\n  virtual bool\n  dropCachedFileMD(FileIdentifier id) = 0;\n\n  //------------------------------------------------------------------------\n  //! Create new file metadata object with an assigned id, the user has\n  //! to fill all the remaining fields\n  //------------------------------------------------------------------------\n  virtual std::shared_ptr<IFileMD> createFile(IFileMD::id_t id) = 0;\n\n  //------------------------------------------------------------------------\n  //! Update the file metadata in the backing store after the IFileMD object\n  //! has been changed\n  //------------------------------------------------------------------------\n  virtual void updateStore(IFileMD* obj) = 0;\n\n  //------------------------------------------------------------------------\n  //! Remove object from the store, you should write lock the file before calling this\n  //------------------------------------------------------------------------\n  virtual void removeFile(IFileMD* obj) = 0;\n\n  //------------------------------------------------------------------------\n  //! Get number of files\n  //------------------------------------------------------------------------\n  virtual uint64_t getNumFiles() = 0;\n\n  //------------------------------------------------------------------------\n  //! Add file listener that will be notified about all of the changes in\n  //! the store\n  //------------------------------------------------------------------------\n  virtual void addChangeListener(IFileMDChangeListener* listener) = 0;\n\n  //------------------------------------------------------------------------\n  //! Notify the listeners about the change\n  //------------------------------------------------------------------------\n  virtual void notifyListeners(IFileMDChangeListener::Event* event) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set the QuotaStats object for the follower\n  //!\n  //! @param quota_stats object implementing the IQuotaStats interface\n  //----------------------------------------------------------------------------\n  virtual void setQuotaStats(IQuotaStats* quota_stats) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set container service\n  //----------------------------------------------------------------------------\n  virtual void setContMDService(IContainerMDSvc* cont_svc) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Visit all the files\n  //----------------------------------------------------------------------------\n  virtual void visit(IFileVisitor* visitor) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get first free file id\n  //----------------------------------------------------------------------------\n  virtual IFileMD::id_t getFirstFreeId() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Retrieve file metadata cache statistics\n  //----------------------------------------------------------------------------\n  virtual CacheStatistics getCacheStatistics() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Blacklist IDs below the given threshold\n  //----------------------------------------------------------------------------\n  virtual void blacklistBelow(FileIdentifier id) = 0;\n\n};\n\nEOSNSNAMESPACE_END\n\n#endif // EOS_NS_I_FILE_MD_SVC_HH\n"
  },
  {
    "path": "namespace/interface/IFsView.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file IFsView.hh\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2015 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOS_NS_IFSVIEW_HH__\n#define __EOS_NS_IFSVIEW_HH__\n\n#include \"namespace/Namespace.hh\"\n#include \"namespace/MDException.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include <google/dense_hash_set>\n#include <set>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Container iterator abtract class\n//------------------------------------------------------------------------------\ntemplate<typename T>\nclass ICollectionIterator\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ICollectionIterator() {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~ICollectionIterator() {}\n\n  //----------------------------------------------------------------------------\n  //! Get current fsid\n  //----------------------------------------------------------------------------\n  virtual T getElement() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Check if iterator is valid\n  //----------------------------------------------------------------------------\n  virtual bool valid() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Progress iterator by 1 - only has any effect if iterator is valid\n  //----------------------------------------------------------------------------\n  virtual void next() = 0;\n};\n\n//------------------------------------------------------------------------------\n//! File System view abtract class\n//------------------------------------------------------------------------------\nclass IFsView: public IFileMDChangeListener\n{\npublic:\n\n  //------------------------------------------------------------------------\n  // Google sparse table is used for much lower memory overhead per item\n  // than a list and it's fragmented structure speeding up deletions.\n  // The filelists we keep are quite big - a list would be faster\n  // but more memory consuming, a vector would be slower but less\n  // memory consuming. We changed to dense hash set since it is much faster\n  // and the memory overhead is not visible in a million file namespace.\n  //------------------------------------------------------------------------\n  typedef google::dense_hash_set <IFileMD::id_t,\n          Murmur3::MurmurHasher<uint64_t> > FileList;\n\n  //----------------------------------------------------------------------------\n  //! Contructor\n  //----------------------------------------------------------------------------\n  IFsView() = default;\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~IFsView() = default;\n\n  //----------------------------------------------------------------------------\n  //! Configure\n  //!\n  //! @param config map of configuration parameters\n  //----------------------------------------------------------------------------\n  virtual void configure(const std::map<std::string, std::string>& config) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Notify me about the changes in the main view\n  //! IFileeMDChangeListener interface\n  //----------------------------------------------------------------------------\n  virtual void fileMDChanged(IFileMDChangeListener::Event* e) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Notify me about files when recovering from changelog\n  //! IFileeMDChangeListener interface\n  //----------------------------------------------------------------------------\n  virtual void fileMDRead(IFileMD* obj) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get iterator to list of files on a particular file system\n  //!\n  //! @param location file system id\n  //!\n  //! @return shared ptr to collection iterator\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n      getFileList(IFileMD::location_t location) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get streaming iterator to list of files on a particular file system\n  //!\n  //! @param location file system id\n  //!\n  //! @return shared ptr to collection iterator\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n      getStreamingFileList(IFileMD::location_t location) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Erase an entry from all filesystem view collections\n  //!\n  //! @param location where to remove\n  //! @param file id to remove\n  //!\n  //! @return\n  //----------------------------------------------------------------------------\n  virtual void eraseEntry(IFileMD::location_t location, IFileMD::id_t) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get an approximately random file residing within the given filesystem.\n  //!\n  //! @param location file system id\n  //! @param retval a file id residing within the given filesystem\n  //!\n  //! @return bool indicating whether the operation was successful\n  //----------------------------------------------------------------------------\n  virtual bool getApproximatelyRandomFileInFs(IFileMD::location_t location,\n      IFileMD::id_t& retval) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get number of files on the given file system\n  //!\n  //! @param fs_id file system id\n  //!\n  //! @return number of files\n  //----------------------------------------------------------------------------\n  virtual uint64_t getNumFilesOnFs(IFileMD::location_t fs_id) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get iterator to list of unlinked files on a particular file system\n  //!\n  //! @param location file system id\n  //!\n  //! @return shared ptr to collection iterator\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n      getUnlinkedFileList(IFileMD::location_t location) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get number of unlinked files on the given file system\n  //!\n  //! @param fs_id file system id\n  //!\n  //! @return number of files\n  //----------------------------------------------------------------------------\n  virtual uint64_t getNumUnlinkedFilesOnFs(IFileMD::location_t fs_id) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Clear unlinked files for filesystem\n  //!\n  //! @param location filssystem id\n  //!\n  //! @return True if cleanup done successfully, otherwise false.\n  //----------------------------------------------------------------------------\n  virtual bool clearUnlinkedFileList(IFileMD::location_t location) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get iterator to list of files without replicas\n  //!\n  //! @return shard ptr to collection iterator\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n      getNoReplicasFileList() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get streaming iterator to list of files without replicas\n  //!\n  //! @return shard ptr to collection iterator\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n      getStreamingNoReplicasFileList() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get number of files with no replicas\n  //----------------------------------------------------------------------------\n  virtual uint64_t getNumNoReplicasFiles() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get iterator object to run through all currently active filesystem IDs\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<ICollectionIterator<IFileMD::location_t>>\n      getFileSystemIterator() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Check if file system has file id\n  //!\n  //! @param fid file id\n  //! @param fs_id file system id\n  //!\n  //! @return true if file is on the provided file system, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool hasFileId(IFileMD::id_t fid, IFileMD::location_t fs_id) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Finalize\n  //----------------------------------------------------------------------------\n  virtual void finalize() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Shrink maps\n  //----------------------------------------------------------------------------\n  virtual void shrink() = 0;\n};\n\n//------------------------------------------------------------------------------\n// File System iterator implementation of a in-memory namespace\n// Trivial implementation, using the same \"logic\" to iterate over filesystems\n// as we did with \"getNumFileSystems\" before.\n//------------------------------------------------------------------------------\nclass StupidFileSystemIterator:\n  public ICollectionIterator<IFileMD::location_t>\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  StupidFileSystemIterator(IFileMD::location_t maxfs) :\n    pCurrentFS(0), pMaxFS(maxfs) {}\n\n  //----------------------------------------------------------------------------\n  //! Get current fsid\n  //----------------------------------------------------------------------------\n  IFileMD::location_t getElement() override\n  {\n    return pCurrentFS;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if iterator is valid\n  //----------------------------------------------------------------------------\n  bool valid() override\n  {\n    return pCurrentFS < pMaxFS;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Retrieve next fsid - returns false when no more filesystems exist\n  //----------------------------------------------------------------------------\n  void next() override\n  {\n    if (valid()) {\n      pCurrentFS++;\n    }\n  }\n\nprivate:\n  IFileMD::location_t pCurrentFS;\n  IFileMD::location_t pMaxFS;\n};\n\n//------------------------------------------------------------------------------\n//! Class FileIterator that can iteratoe through a list of files from the\n//! FileSystem class. Used to iterate through the files / unlinked files on a\n//! filesystem.\n//------------------------------------------------------------------------------\nclass FileIterator:\n  public ICollectionIterator<IFileMD::id_t>\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  FileIterator(const IFsView::FileList& list) :\n    mList(list), mIt(mList.begin()) {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~FileIterator()  = default;\n\n  //----------------------------------------------------------------------------\n  //! Get current file id\n  //----------------------------------------------------------------------------\n  IFileMD::id_t getElement() override\n  {\n    return *mIt;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if iterator is valid\n  //----------------------------------------------------------------------------\n  bool valid() override\n  {\n    return (mIt != mList.end());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Retrieve next file id\n  //----------------------------------------------------------------------------\n  void next() override\n  {\n    if (valid()) {\n      ++mIt;\n    }\n  }\n\nprivate:\n  const IFsView::FileList& mList; ///< Reference to list\n  IFsView::FileList::const_iterator mIt; ///< List iterator\n};\n\nEOSNSNAMESPACE_END\n\n#endif // __EOS_NS_IFSVIEW_HH__\n"
  },
  {
    "path": "namespace/interface/INamespaceGroup.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file INamespaceGroup.hh\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/INamespaceStats.hh\"\n#include <map>\n#include <string>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Forward declarations\n//------------------------------------------------------------------------------\nnamespace common {\n  class RWMutex;\n}\n\nclass IContainerMDSvc;\nclass IFileMDSvc;\nclass IView;\nclass IFsView;\nclass IContainerMDChangeListener;\nclass IQuotaStats;\nclass IFileMDChangeListener;\n\n//------------------------------------------------------------------------------\n//! Interface object to hold ownership of all namespace objects.\n//! Under construction..\n//------------------------------------------------------------------------------\nclass INamespaceGroup {\npublic:\n  //----------------------------------------------------------------------------\n  //! Virtual destructor\n  //----------------------------------------------------------------------------\n  virtual ~INamespaceGroup() {}\n\n  //----------------------------------------------------------------------------\n  //! Initialize with the given configuration - must be called before any\n  //! other function, and right after construction.\n  //!\n  //! Initialization may fail - in such case, \"false\" will be returned, and\n  //! \"err\" will be filled out.\n  //----------------------------------------------------------------------------\n  virtual bool initialize(eos::common::RWMutex* mtx,\n    const std::map<std::string, std::string> &config, std::string &err, INamespaceStats * namespaceStats) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Provide file service\n  //----------------------------------------------------------------------------\n  virtual IFileMDSvc* getFileService() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Provide container service\n  //----------------------------------------------------------------------------\n  virtual IContainerMDSvc* getContainerService() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Provide hierarchical view\n  //----------------------------------------------------------------------------\n  virtual IView* getHierarchicalView() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Provide filesystem view\n  //----------------------------------------------------------------------------\n  virtual IFsView* getFilesystemView() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Provide sync time accounting view\n  //----------------------------------------------------------------------------\n  virtual IContainerMDChangeListener* getSyncTimeAccountingView() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Provide container accounting view\n  //----------------------------------------------------------------------------\n  virtual IFileMDChangeListener* getContainerAccountingView() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Provide quota stats\n  //----------------------------------------------------------------------------\n  virtual IQuotaStats* getQuotaStats() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Is this in-memory namespace?\n  //----------------------------------------------------------------------------\n  virtual bool isInMemory() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Start cache refresh listener\n  //----------------------------------------------------------------------------\n  virtual void startCacheRefreshListener() = 0;\n\nprotected:\n  //----------------------------------------------------------------------------\n  //! Global namespace mutex - no ownership\n  //----------------------------------------------------------------------------\n  eos::common::RWMutex* mNsMutex;\n\n  //----------------------------------------------------------------------------\n  //! MGM Namespace stats object - no ownership\n  //----------------------------------------------------------------------------\n  eos::INamespaceStats * mNamespaceStats;\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/interface/INamespaceStats.hh",
    "content": "// ----------------------------------------------------------------------\n// File: INamespaceStats.hh\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_INAMESPACESTATS_HH\n#define EOS_INAMESPACESTATS_HH\n#include \"namespace/Namespace.hh\"\n#include <sys/types.h>\n\nEOSNSNAMESPACE_BEGIN\n\n/**\n * Interface to communicate stats of ns-operations to the MGM\n *\n * It's the same interface as the Stat class located in mgm/Stat.hh\n */\nclass INamespaceStats {\npublic:\n  virtual void Add(const char* tag, uid_t uid, gid_t gid, unsigned long val) = 0;\n\n  virtual void AddExec(const char* tag, float exectime) = 0;\n\n  virtual ~INamespaceStats() = default;\n};\n\nEOSNSNAMESPACE_END\n\n\n#endif // EOS_INAMESPACESTATS_HH\n"
  },
  {
    "path": "namespace/interface/IQuota.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file IQuota.hh\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2015 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/ns_quarkdb/accounting/QuotaNodeCore.hh\"\n#include <iostream>\n#include <memory>\n#include <map>\n#include <unordered_set>\n#include <vector>\n\nEOSNSNAMESPACE_BEGIN\n\n//! Forward declarations\nclass IQuotaStats;\n\n//------------------------------------------------------------------------------\n//! Placeholder for space occupancy statistics of an accounting node\n//------------------------------------------------------------------------------\nclass IQuotaNode\n{\npublic:\n  typedef std::map<uid_t, QuotaNodeCore::UsageInfo> UserMap;\n  typedef std::map<gid_t, QuotaNodeCore::UsageInfo> GroupMap;\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  IQuotaNode(IQuotaStats* quotaStats, eos::IContainerMD::id_t id):\n    pQuotaStats(quotaStats), pContainerId(id) {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~IQuotaNode() {};\n\n  //----------------------------------------------------------------------------\n  //! Get the container id of this node\n  //----------------------------------------------------------------------------\n  inline IContainerMD::id_t getId() const\n  {\n    return pContainerId;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the amount of space occupied by the given user\n  //----------------------------------------------------------------------------\n  virtual uint64_t getUsedSpaceByUser(uid_t uid)\n  {\n    return pCore.getUsedSpaceByUser(uid);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the amount of space occupied by the given group\n  //----------------------------------------------------------------------------\n  virtual uint64_t getUsedSpaceByGroup(gid_t gid)\n  {\n    return pCore.getUsedSpaceByGroup(gid);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the amount of space occupied by the given user\n  //----------------------------------------------------------------------------\n  virtual uint64_t getPhysicalSpaceByUser(uid_t uid)\n  {\n    return pCore.getPhysicalSpaceByUser(uid);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the amount of space occupied by the given group\n  //----------------------------------------------------------------------------\n  virtual uint64_t getPhysicalSpaceByGroup(gid_t gid)\n  {\n    return pCore.getPhysicalSpaceByGroup(gid);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the amount of space occupied by the given user\n  //----------------------------------------------------------------------------\n  virtual uint64_t getNumFilesByUser(uid_t uid)\n  {\n    return pCore.getNumFilesByUser(uid);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the amount of space occupied by the given group\n  //----------------------------------------------------------------------------\n  virtual uint64_t getNumFilesByGroup(gid_t gid)\n  {\n    return pCore.getNumFilesByGroup(gid);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Account a new file, adjust the size using the size mapping function\n  //----------------------------------------------------------------------------\n  virtual void addFile(const IFileMD* file) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove a file, adjust the size using the size mapping function\n  //----------------------------------------------------------------------------\n  virtual void removeFile(const IFileMD* file) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Meld in another quota node\n  //----------------------------------------------------------------------------\n  virtual void meld(const IQuotaNode* node) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get the set of uids for which information is stored in the current quota\n  //! node.\n  //!\n  //! @return set of uids\n  //----------------------------------------------------------------------------\n  virtual std::unordered_set<uint64_t> getUids()\n  {\n    return pCore.getUids();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the set of gids for which information is stored in the current quota\n  //! node.\n  //!\n  //! @return set of gids\n  //----------------------------------------------------------------------------\n  virtual std::unordered_set<uint64_t> getGids()\n  {\n    return pCore.getGids();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get underlying QuotaNodeCore object.\n  //----------------------------------------------------------------------------\n  const QuotaNodeCore& getCore() const\n  {\n    return pCore;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Replace underlying QuotaNodeCore obejct.\n  //----------------------------------------------------------------------------\n  virtual void replaceCore(const QuotaNodeCore& updated) = 0;\n\n\n  //----------------------------------------------------------------------------\n  //! Partial update of underlying QuotaNodeCore obejct.\n  //----------------------------------------------------------------------------\n  virtual void updateCore(const QuotaNodeCore &updated) = 0;\n\nprotected:\n  IQuotaStats* pQuotaStats;\n  IContainerMD::id_t pContainerId; ///< Id of the corresponding container\n  QuotaNodeCore pCore;\n};\n\n//----------------------------------------------------------------------------\n//! Manager of the quota nodes\n//----------------------------------------------------------------------------\nclass IQuotaStats\n{\npublic:\n  //----------------------------------------------------------------------------\n  // Type definitions\n  //----------------------------------------------------------------------------\n  typedef uint64_t (*SizeMapper)(const IFileMD* file);\n  typedef std::map<IContainerMD::id_t, IQuotaNode*> NodeMap;\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  IQuotaStats(): pSizeMapper(0) {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~IQuotaStats() {};\n\n  //----------------------------------------------------------------------------\n  //! Configure\n  //!\n  //! @param config map of configuration parameters\n  //----------------------------------------------------------------------------\n  virtual void configure(const std::map<std::string, std::string>& config) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get a quota node associated to the container id\n  //----------------------------------------------------------------------------\n  virtual IQuotaNode* getQuotaNode(IContainerMD::id_t nodeId) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Register a new quota node\n  //----------------------------------------------------------------------------\n  virtual IQuotaNode* registerNewNode(IContainerMD::id_t nodeId) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove quota node\n  //----------------------------------------------------------------------------\n  virtual void removeNode(IContainerMD::id_t nodeId) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get the set of all quota node ids. The quota node id corresponds to the\n  //! container id.\n  //!\n  //! @return set of quota node ids\n  //----------------------------------------------------------------------------\n  virtual std::unordered_set<IContainerMD::id_t> getAllIds() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Register a mapping function used to calculate the physical\n  //! space that the file occuppies (replicas, striping and so on)\n  //----------------------------------------------------------------------------\n  void registerSizeMapper(SizeMapper sizeMapper)\n  {\n    pSizeMapper = sizeMapper;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Calculate the physical size the file occupies\n  //----------------------------------------------------------------------------\n  uint64_t getPhysicalSize(const IFileMD* file)\n  {\n    if (!pSizeMapper) {\n      MDException e;\n      e.getMessage() << \"No size mapping function registered\" << std::endl;\n      throw (e);\n    }\n\n    return (*pSizeMapper)(file);\n  }\n\nprotected:\n  SizeMapper pSizeMapper;\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/interface/IView.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Lukasz Janyst <ljanyst@cern.ch>\n// desc:   View service interface\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_I_VIEW_HH\n#define EOS_NS_I_VIEW_HH\n\n#include <map>\n#include <string>\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include \"namespace/MDException.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/MDLocking.hh\"\n\nnamespace eos\n{\nclass IQuotaNode;\nclass IQuotaStats;\n\n//------------------------------------------------------------------------------\n//! Interface for the component responsible for the namespace.\n//! A concrete implementation could handle a hierarchical namespace,\n//! lists of files in the fileservers, lists of files belonging\n//! to users, container based store etc.\n//------------------------------------------------------------------------------\nclass IView\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Specify a pointer to the underlying container service\n  //----------------------------------------------------------------------------\n  virtual void setContainerMDSvc(IContainerMDSvc* containerSvc) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get the container svc pointer\n  //----------------------------------------------------------------------------\n  virtual IContainerMDSvc* getContainerMDSvc() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Specify a pointer to the underlying file service, that alocates the\n  //! actual files\n  //----------------------------------------------------------------------------\n  virtual void setFileMDSvc(IFileMDSvc* fileMDSvc) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get the FileMDSvc\n  //----------------------------------------------------------------------------\n  virtual IFileMDSvc* getFileMDSvc() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Configure the view\n  //----------------------------------------------------------------------------\n  virtual void configure(const std::map<std::string, std::string>& config) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Initialize the view\n  //----------------------------------------------------------------------------\n  virtual void initialize() = 0;\n\n  virtual void initialize1() = 0;\n  virtual void initialize2() = 0;\n  virtual void initialize3() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Finalizelize the view\n  //----------------------------------------------------------------------------\n  virtual void finalize() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Retrieve a file for given uri, asynchronously\n  //----------------------------------------------------------------------------\n  virtual folly::Future<IFileMDPtr> getFileFut(const std::string& uri,\n      bool follow = true) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Retrieve a file for given uri\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IFileMD> getFile(const std::string& uri,\n      bool follow = true,\n      size_t* link_depths = 0) = 0;\n\n\n  //----------------------------------------------------------------------------\n  //! Retrieve an item for given path. Could be either file or container, we\n  //! don't know.\n  //----------------------------------------------------------------------------\n  virtual folly::Future<FileOrContainerMD>\n  getItem(const std::string& uri, bool follow = true) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Update file store\n  //----------------------------------------------------------------------------\n  virtual void updateFileStore(IFileMD* file) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Create a file for given uri\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IFileMD> createFile(const std::string& uri,\n      uid_t uid = 0,\n      gid_t gid = 0,\n      uint64_t fid = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Create a link for given uri\n  //----------------------------------------------------------------------------\n  virtual void createLink(const std::string& uri,\n                          const std::string& linkuri,\n                          uid_t uid = 0, gid_t gid = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove the file - the pointer is not valid any more once the call\n  //! returns\n  //----------------------------------------------------------------------------\n  virtual void removeFile(IFileMD* file) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove a link\n  //----------------------------------------------------------------------------\n  virtual void removeLink(const std::string& uri) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove the file from the hierarchy so that it won't be accessible\n  //! by path anymore and unlink all of it's replicas. The file needs\n  //! to be manually removed (ie. using removeFile method) once it has\n  //! no valid replicas.\n  //!\n  //! @param uri full path to file to be unlinked\n  //----------------------------------------------------------------------------\n  virtual void unlinkFile(const std::string& uri) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove the file from the hierarchy so that it won't be accessible\n  //! by path anymore and unlink all of it's replicas. The file needs\n  //! to be manually removed (ie. using removeFile method) once it has\n  //! no valid replicas.\n  //!\n  //! @param file IFileMD object to be removed\n  //----------------------------------------------------------------------------\n  virtual void unlinkFile(eos::IFileMD* file) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get a container (directory) asynchronously\n  //----------------------------------------------------------------------------\n  virtual folly::Future<IContainerMDPtr> getContainerFut(const std::string& uri,\n      bool follow = true) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get a container (directory)\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IContainerMD> getContainer(const std::string& uri,\n      bool follow = true,\n      size_t* link_depths = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get parent container of a file\n  //----------------------------------------------------------------------------\n  virtual folly::Future<IContainerMDPtr> getParentContainer(\n    IFileMD* file) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Create a container (directory)\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IContainerMD> createContainer(const std::string& uri,\n      bool createParents = false,\n      uint64_t cid = 0) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Update container store\n  //----------------------------------------------------------------------------\n  virtual void updateContainerStore(IContainerMD* container) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove a container (directory)\n  //----------------------------------------------------------------------------\n  virtual void removeContainer(const std::string& uri) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get uri for the container\n  //----------------------------------------------------------------------------\n  virtual std::string getUri(const IContainerMD* container) const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get uri for the container - asynchronous version\n  //----------------------------------------------------------------------------\n  virtual folly::Future<std::string> getUriFut(ContainerIdentifier id) const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get uri for container id\n  //----------------------------------------------------------------------------\n  virtual std::string getUri(const IContainerMD::id_t cid) const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get uri for the file\n  //! CAUTION: DO NOT LOCK THE FILE IN PARAMETER BEFORE CALLING THIS IN THE RISK\n  //! OF CREATING DEADLOCKS !\n  //! Explanation: if the file is already locked, the underlying logic will also lock the parent container\n  //! which is against the locking order that needs to be respected: lock(Container), then lock(File)\n  //----------------------------------------------------------------------------\n  virtual std::string getUri(const IFileMD* file) const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get uri for the file - asynchronous version\n  //----------------------------------------------------------------------------\n  virtual folly::Future<std::string> getUriFut(FileIdentifier id) const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get real path translating existing symlink\n  //----------------------------------------------------------------------------\n  virtual std::string getRealPath(const std::string& path) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get quota node id concerning given container\n  //----------------------------------------------------------------------------\n  virtual IQuotaNode* getQuotaNode(const IContainerMD* container,\n                                   bool search = true) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Register the container to be a quota node\n  //----------------------------------------------------------------------------\n  virtual IQuotaNode* registerQuotaNode(IContainerMD* container) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Remove the quota node\n  //----------------------------------------------------------------------------\n  virtual void removeQuotaNode(IContainerMD* container) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Get the quota stats placeholder\n  //----------------------------------------------------------------------------\n  virtual IQuotaStats* getQuotaStats() = 0;\n\n  //----------------------------------------------------------------------------\n  //! Set the quota stats placeholder, currently associated object (if any)\n  //! won't beX deleted.\n  //----------------------------------------------------------------------------\n  virtual void setQuotaStats(IQuotaStats* quotaStats) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Rename container\n  //----------------------------------------------------------------------------\n  virtual void renameContainer(IContainerMD* container,\n                               const std::string& newName) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Rename file\n  //----------------------------------------------------------------------------\n  virtual void renameFile(IFileMD* file, const std::string& newName) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~IView() {}\n\n  //----------------------------------------------------------------------------\n  //! Return whether this is an in-memory namespace.\n  //----------------------------------------------------------------------------\n  virtual bool inMemory() = 0;\n};\n};\n\n#endif // EOS_NS_I_VIEW_HH\n"
  },
  {
    "path": "namespace/interface/Identifiers.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Georgios Bitzes <georgios.bitzes@cern.ch>\n// desc:   Phantom types for file and container identifiers\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_I_IDENTIFIERS_HH\n#define EOS_NS_I_IDENTIFIERS_HH\n\n#include \"common/Murmur3.hh\"\n#include \"namespace/Namespace.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Phantom types: strongly typed uint64_t, identifying files and containers.\n//!\n//! Unless explicitly asked with obj.(get/set)UnderlyingUInt64(), this will\n//! generate glorious compiler errors when you try to misuse, such as adding\n//! two FileIdentifiers together (which makes zero sense), accidentally store\n//! them as int32, or try to mix them up.\n//!\n//! Bugs which would previously be detectable only at runtime, will now generate\n//! compiler errors.\n//!\n//! Conversion to/from uint64_t should happen only when absolutely necessary,\n//! at the boundaries of serialization / deserialization.\n//!\n//! Any sensible compiler should generate the same machine code, as with a plain\n//! uint64_t - there should be no performance penalty.\n//------------------------------------------------------------------------------\n\n\n//------------------------------------------------------------------------------\n//! FileIdentifier class\n//------------------------------------------------------------------------------\nclass FileIdentifier\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Prevent implicit conversions between this type and uint64_t, by making\n  //! the constructor explicit.\n  //----------------------------------------------------------------------------\n  explicit FileIdentifier(uint64_t src) : val(src) {}\n\n  //----------------------------------------------------------------------------\n  //! Construct empty FileIdentifier.\n  //----------------------------------------------------------------------------\n  FileIdentifier() : val(0) {}\n\n  //----------------------------------------------------------------------------\n  //! Retrieve the underlying uint64_t. Use this only if you have to, ie\n  //! when serializing to disk.\n  //!\n  //! The name is long and ugly on purpose, to make you think twice before\n  //! using it. ;)\n  //----------------------------------------------------------------------------\n  uint64_t getUnderlyingUInt64() const\n  {\n    return val;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Comparison operator, so we can store those as keys in maps, etc.\n  //----------------------------------------------------------------------------\n  bool operator<(const FileIdentifier& other) const\n  {\n    return val < other.val;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Equality operator.\n  //----------------------------------------------------------------------------\n  bool operator==(const FileIdentifier& other) const\n  {\n    return val == other.val;\n  }\n\nprivate:\n  uint64_t val;\n};\n\n//------------------------------------------------------------------------------\n//! ContainerIdentifier class\n//------------------------------------------------------------------------------\nclass ContainerIdentifier\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Prevent implicit conversions between this type and uint64_t, by making\n  //! the constructor explicit.\n  //----------------------------------------------------------------------------\n  explicit ContainerIdentifier(uint64_t src) : val(src) {}\n\n  //----------------------------------------------------------------------------\n  //! Construct empty ContainerIdentifier.\n  //----------------------------------------------------------------------------\n  ContainerIdentifier() : val(0) {}\n\n  //----------------------------------------------------------------------------\n  //! Retrieve the underlying uint64_t. Use this only if you have to, ie\n  //! when serializing to disk.\n  //!\n  //! The name is long and ugly on purpose, to make you think twice before\n  //! using it. ;)\n  //----------------------------------------------------------------------------\n  uint64_t getUnderlyingUInt64() const\n  {\n    return val;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Comparison operator, so we can store those as keys in maps, etc.\n  //----------------------------------------------------------------------------\n  bool operator<(const ContainerIdentifier& other) const\n  {\n    return val < other.val;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Equality operator.\n  //----------------------------------------------------------------------------\n  bool operator==(const ContainerIdentifier& other) const\n  {\n    return val == other.val;\n  }\n\nprivate:\n  uint64_t val;\n};\n\n//------------------------------------------------------------------------------\n//! FileOrContainerIdentifier class - holds either FileIdentifer, or\n//! ContainerIdentifier, but not both.\n//!\n//! It can also be empty.\n//------------------------------------------------------------------------------\nclass FileOrContainerIdentifier {\npublic:\n  //----------------------------------------------------------------------------\n  //! Empty.\n  //----------------------------------------------------------------------------\n  FileOrContainerIdentifier() : val(0), isEmpty(true), file(false) {}\n\n  //----------------------------------------------------------------------------\n  //! Has a file\n  //----------------------------------------------------------------------------\n  FileOrContainerIdentifier(FileIdentifier file) :\n    val(file.getUnderlyingUInt64()), isEmpty(false), file(true) {}\n\n  //----------------------------------------------------------------------------\n  //! Has a container\n  //----------------------------------------------------------------------------\n  FileOrContainerIdentifier(ContainerIdentifier cont) :\n    val(cont.getUnderlyingUInt64()), isEmpty(false), file(false) {}\n\n  //----------------------------------------------------------------------------\n  //! Is it empty?\n  //----------------------------------------------------------------------------\n  bool empty() const {\n    return isEmpty;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Is it a file?\n  //----------------------------------------------------------------------------\n  bool isFile() const {\n    return !isEmpty && file;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Is it a container?\n  //----------------------------------------------------------------------------\n  bool isContainer() const {\n    return !isEmpty && !file;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get FileIdentifier - if empty, or this actually points to a container,\n  //! FileIdentifier(0) is returned\n  //----------------------------------------------------------------------------\n  FileIdentifier toFileIdentifier() const {\n    if(isEmpty || !file) {\n      return FileIdentifier(0);\n    }\n\n    return FileIdentifier(val);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get ContainerIdentifier - if empty, or this actually points to a file,\n  //! ContainerIdentifier(0) is returned\n  //----------------------------------------------------------------------------\n  ContainerIdentifier toContainerIdentifier() const {\n    if(isEmpty || file) {\n      return ContainerIdentifier(0);\n    }\n\n    return ContainerIdentifier(val);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Equality operators\n  //----------------------------------------------------------------------------\n  bool operator==(const FileOrContainerIdentifier& other) const\n  {\n    return val == other.val && isEmpty == other.isEmpty && file == other.file;\n  }\n\n  bool operator==(const FileIdentifier& other) const {\n    return *this == FileOrContainerIdentifier(other);\n  }\n\n  bool operator==(const ContainerIdentifier& other) const {\n    return *this == FileOrContainerIdentifier(other);\n  }\n\n\nprivate:\n  uint64_t val;\n  bool isEmpty;\n  bool file;\n};\n\nEOSNSNAMESPACE_END\n\nnamespace Murmur3 {\n\n  //----------------------------------------------------------------------------\n  //! MurmurHasher specialization for FileIdentifier.\n  //----------------------------------------------------------------------------\n  template<>\n  struct MurmurHasher<eos::FileIdentifier> {\n    MurmurHasher<uint64_t> hasher;\n\n    size_t operator()(const eos::FileIdentifier &key) const\n    {\n      return hasher(key.getUnderlyingUInt64());\n    }\n  };\n\n  //----------------------------------------------------------------------------\n  //! MurmurHasher specialization for ContainerIdentifier.\n  //----------------------------------------------------------------------------\n  template<>\n  struct MurmurHasher<eos::ContainerIdentifier> {\n    MurmurHasher<uint64_t> hasher;\n\n    size_t operator()(const eos::ContainerIdentifier &key) const\n    {\n      return hasher(key.getUnderlyingUInt64());\n    }\n  };\n}\n\n#endif\n"
  },
  {
    "path": "namespace/interface/LockableNSObject.hh",
    "content": "/************************************************************************\n* EOS - the CERN Disk Storage System                                   *\n* Copyright (C) 2022 CERN/Switzerland                                  *\n*                                                                      *\n* This program is free software: you can redistribute it and/or modify *\n* it under the terms of the GNU General Public License as published by *\n* the Free Software Foundation, either version 3 of the License, or    *\n* (at your option) any later version.                                  *\n*                                                                      *\n* This program is distributed in the hope that it will be useful,      *\n* but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n* GNU General Public License for more details.                         *\n*                                                                      *\n* You should have received a copy of the GNU General Public License    *\n* along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n************************************************************************/\n\n#ifndef EOS_LOCKABLENSOBJECT_HH\n#define EOS_LOCKABLENSOBJECT_HH\n\n#include <mutex>\n#include <shared_mutex>\n#include <memory>\n#include <map>\n#include <thread>\n\n#include \"namespace/Namespace.hh\"\n#include \"namespace/MDException.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\ntypedef std::unique_lock<std::shared_timed_mutex> MDWriteLock;\ntypedef std::shared_lock<std::shared_timed_mutex> MDReadLock;\n// To track if this thread has already a lock on a specific object MD\n// we map for each objectMDPtr the amount of locks taken by this thread.\n// How do we know about this thread? we use thread_local storage std::map\n// an instance of std::map will be instanciated at the beginning of this thread\n// and will be destroyed at the end of this thread, so no need to track the thread ID.\n// As a thread can have multiple NS File/ContainerMD tracked (bulk locks), this\n// map tracks the address of this object (std::uintptr_t) and the amount of time the lock\n// was acquired (uint64_t)\ntypedef std::map<std::uintptr_t , uint64_t> MapLockTracker;\ninline thread_local MapLockTracker mThreadIdWriteLockMap;\ninline thread_local MapLockTracker mThreadIdReadLockMap;\n\nclass LockableNSObjMD\n{\npublic:\n  template<typename ObjectMDPtr, typename LockType> friend class\n    NSObjectMDBaseLock;\n  template<typename ObjectMDPtr, typename LockType> friend class NSObjectMDLock;\n  template<typename ObjectMDPtr, typename LockType> friend class\n    NSObjectMDTryLock;\n  LockableNSObjMD() {}\n  LockableNSObjMD(const LockableNSObjMD& other) = delete;\n  LockableNSObjMD& operator=(const LockableNSObjMD&) = delete;\n\n  virtual ~LockableNSObjMD() = default;\n\nprotected:\n\n  /**\n   * Runs a write operation where the logic is located on the functor passed in parameter.\n   *\n   * If this instance already has a write-lock registered, no lock will be taken before running the functor,\n   * if not, a write-lock will be taken before running the functor\n   * @tparam Functor the function type to pass\n   * @param functor the function to run\n   * @return the return value of the functor\n   */\n  template<typename Functor>\n  auto runWriteOp(Functor&& functor) const -> decltype(functor())\n  {\n    if (!isLocked(MDWriteLock())) {\n      //Object mutex is not locked, lock it and run the functor\n      MDWriteLock lock(getMutex());\n      return functor();\n    } else {\n      //Object mutex is locked by this thread, run the functor\n      return functor();\n    }\n  }\n\n  template<typename Functor>\n  auto runWriteOp(Functor&& functor) -> decltype(functor())\n  {\n    return const_cast<const LockableNSObjMD*>(this)->runWriteOp(functor);\n  }\n\n  /**\n   * Runs a read operation where the logic is located on the functor passed in parameter.\n   *\n   * If this instance already has a read-lock (or write-lock) registered, no lock will be taken before running the functor,\n   * if not, a read-lock will be taken before running the functor\n   * @tparam Functor the function type to pass\n   * @param functor the function to run\n   * @return the return value of the functor\n   */\n  template<typename Functor>\n  auto runReadOp(Functor&& functor) const -> decltype(functor())\n  {\n    if (!isLocked(MDReadLock())) {\n      //Object mutex is not locked, lock it and run the functor\n      MDReadLock lock(getMutex());\n      return functor();\n    } else {\n      //Object mutex is locked by this thread, run the functor\n      return functor();\n    }\n  }\n\n  template<typename Functor>\n  auto runReadOp(Functor&& functor) -> decltype(functor())\n  {\n    return const_cast<const LockableNSObjMD*>(this)->runReadOp(functor);\n  }\n\n  bool isThisObjectInLockMap(const MapLockTracker& mapLockTracker) const\n  {\n    return (mapLockTracker.find(std::uintptr_t(this)) !=\n            mapLockTracker.end());\n  }\n\n  void registerLock(MapLockTracker& mapLockTracker)\n  {\n    auto thisPtr = std::uintptr_t(this);\n    auto thisPtrItor = mapLockTracker.find(thisPtr);\n\n    if (thisPtrItor == mapLockTracker.end()) {\n      mapLockTracker[thisPtr] = 0;\n    }\n\n    mapLockTracker[thisPtr] += 1;\n  }\n\n  //Assumes lock is taken for the map\n  void unregisterLock(MapLockTracker& mapLockTracker)\n  {\n    auto thisPtr = std::uintptr_t(this);\n    auto thisPtrItor = mapLockTracker.find(thisPtr);\n\n    if (thisPtrItor != mapLockTracker.end()) {\n      mapLockTracker[thisPtr] -= 1;\n\n      if (thisPtrItor->second == 0) {\n        mapLockTracker.erase(thisPtr);\n      }\n    }\n  }\n\n  template<typename LockType>\n  void lock(LockType& lock)\n  {\n    //Lock the object only if it is not already read-locked or write-locked\n    if (!isLocked(lock)) {\n      lock.lock();\n    }\n\n    registerLock(lock);\n  }\n\n  /**\n   * Lock the lock in parameter with a try-lock logic. If it could not lock it,\n   * it will return false and the caller will have to retry.\n   * @tparam LockType Container/FileMDRead/WriteLocker\n   * @param lock the lock that owns the mutex that will be tried-locked\n   * @return true if the lock could happen, false otherwise\n   */\n  template<typename LockType>\n  bool tryLock(LockType& lock)\n  {\n    //Lock the object only if it is not already read-locked or write-locked\n    if (!isLocked(lock)) {\n      bool wasLocked = lock.try_lock();\n\n      if (wasLocked) {\n        registerLock(lock);\n      }\n\n      return wasLocked;\n    }\n\n    registerLock(lock);\n    return true;\n  }\n\n  /**\n   * This method contains the logic that will check\n   * wether a lock is already taken by this thread before acquiring\n   * a read-lock\n   * @param mdLock the lock allowing the overloading to work, it is actually not used\n   * @return true if this thread already has the lock allowing the read operation to\n   * be performed, false otherwise\n   */\n  bool isLocked(const MDReadLock& mdLock) const {\n    // In case of a read, if this object is already locked by a write lock we consider it to be read-locked as well\n    // otherwise a deadlock will happen if the object is write locked and a getter method that will try to\n    // read lock the object is called...\n    return (isThisObjectInLockMap(mThreadIdWriteLockMap) ||\n            isThisObjectInLockMap(mThreadIdReadLockMap));\n  }\n\n  /**\n   * This method contains the logic that will check\n   * wether a lock is already taken by this thread before acquiring\n   * a write-lock\n   * @param mdLock the lock allowing the overloading to work, it is actually not used\n   * @return true if this thread already has the lock allowing the write operation to\n   * be performed, false otherwise\n   */\n  bool isLocked(const MDWriteLock& mdLock) const {\n    return isThisObjectInLockMap(mThreadIdWriteLockMap);\n  }\n\n  /**\n   * Registers the read lock\n   * @param mdLock the lock allowing the overloading of this member function to work. It\n   * is actually not used\n   */\n  virtual void registerLock(MDReadLock& mdLock)\n  {\n    registerLock(mThreadIdReadLockMap);\n  }\n\n  /**\n   * Registers the write lock\n   * @param mdLock the lock allowing the overloading of this member function to work. It\n   * is actually not used\n   */\n  virtual void registerLock(MDWriteLock& mdLock)\n  {\n    registerLock(mThreadIdWriteLockMap);\n    //A Write lock is also a readlock. If one tries to read\n    //lock after a write lock on the same thread, a deadlock will happen\n    registerLock(mThreadIdReadLockMap);\n  }\n\n  /**\n   * Unregisters the read lock\n   * @param mdLock the lock allowing the overloading of this member function to work. It\n   * is actually not used\n   */\n  virtual void unregisterLock(MDReadLock& mdLock)\n  {\n    unregisterLock(mThreadIdReadLockMap);\n  }\n\n  /**\n   * Unregisters the write lock\n   * @param mdLock the lock allowing the overloading of this member function to work. It\n   * is actually not used\n   */\n  virtual void unregisterLock(MDWriteLock& mdLock)\n  {\n    unregisterLock(mThreadIdWriteLockMap);\n    unregisterLock(mThreadIdReadLockMap);\n  }\n\n  std::shared_timed_mutex& getMutex()\n  {\n    return const_cast<const LockableNSObjMD*>(this)->getMutex();\n  }\n\n  virtual std::shared_timed_mutex& getMutex() const = 0;\n\n\n};\n\nEOSNSNAMESPACE_END\n\n#endif // EOS_NSOBJECTLOCKER_HH\n"
  },
  {
    "path": "namespace/interface/Misc.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Georgios Bitzes <georgios.bitzes@cern.ch>\n// desc:   Misc\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Struct to retrieve information about namespace caching\n//------------------------------------------------------------------------------\nstruct CacheStatistics {\n  bool enabled = false;\n  uint64_t maxNum = 0;\n  uint64_t occupancy = 0;\n  uint64_t inFlight = 0;\n  uint64_t numRequests = 0;\n  uint64_t numHits = 0;\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/locking/BulkNsObjectLocker.hh",
    "content": "/************************************************************************\n* EOS - the CERN Disk Storage System                                   *\n* Copyright (C) 2023 CERN/Switzerland                                  *\n*                                                                      *\n* This program is free software: you can redistribute it and/or modify *\n* it under the terms of the GNU General Public License as published by *\n* the Free Software Foundation, either version 3 of the License, or    *\n* (at your option) any later version.                                  *\n*                                                                      *\n* This program is distributed in the hope that it will be useful,      *\n* but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n* GNU General Public License for more details.                         *\n*                                                                      *\n* You should have received a copy of the GNU General Public License    *\n* along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n************************************************************************/\n\n#ifndef EOS_BULKNSOBJECTLOCKER_HH\n#define EOS_BULKNSOBJECTLOCKER_HH\n\n#include <map>\n#include <memory>\n#include <vector>\n\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/MDLocking.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n/**\n * This class is a helper class to help locking several IContainerMD or\n * IFileMD (pointed by ObjectMDPtr type)\n *\n * In order to avoid potential deadlock, the locking is made by the ascending order\n * of the identifier of the objects to lock.\n * The lock is done when the method \"lockAll\" is called.\n * @tparam ObjectMDPtr the IContainerMD or IFileMD object to lock later\n * @tparam TryLockerType the type of try-locker to be applied\n */\ntemplate<typename TryLockerType>\nclass BulkNsObjectLocker {\n  using ObjectMDPtrType = typename TryLockerType::ObjectMDPtrType;\n  using Identifier = typename ObjectMDPtrType::element_type::identifier_t;\npublic:\n  /**\n   * Inner class that represent the object returned by the BulkNsObjectLocker::lockAll() method\n   * It is just a wrapper around the std::vector. The particularity of this\n   * object is that it guarantees that the elements contained in the vector\n   * will be destructed in the reverse order of their insertion\n   */\n  class LocksVector {\n    using LockPtr = std::unique_ptr<TryLockerType>;\n    using Vector = std::vector<LockPtr>;\n  public:\n    LocksVector() = default;\n    LocksVector(const LocksVector & other) = delete;\n    LocksVector & operator = (const LocksVector & other) = delete;\n\n    LocksVector(LocksVector && other) {\n      mLocks.reserve(other.size());\n      mLocks = std::move(other.mLocks);\n    }\n    LocksVector & operator=(LocksVector && other) {\n      if(this != &other) {\n        mLocks.reserve(other.size());\n        mLocks = std::move(other.mLocks);\n      }\n      return *this;\n    }\n\n    void push_back(LockPtr && element){\n      mLocks.push_back(std::move(element));\n    }\n    typename Vector::const_iterator begin() const {\n      return mLocks.begin();\n    }\n    typename Vector::iterator begin() {\n      return mLocks.begin();\n    }\n    typename Vector::const_iterator end() const {\n      return mLocks.end();\n    }\n    typename Vector::iterator end() {\n      return mLocks.end();\n    }\n    size_t size() const {\n      return mLocks.size();\n    }\n    LockPtr & operator[](const size_t address) {\n      return mLocks[address];\n    }\n    const LockPtr & operator[](const size_t address) const {\n      return mLocks[address];\n    }\n    void releaseAllLocksAndClear() {\n      //Reset every unique_ptr in the reverse order of insertion\n      for(auto lockPtr = mLocks.rbegin(); lockPtr != mLocks.rend(); lockPtr++) {\n        lockPtr->reset(nullptr);\n      }\n      mLocks.clear();\n    }\n\n    ~LocksVector() {\n      releaseAllLocksAndClear();\n    }\n\n  private:\n    Vector mLocks;\n  };\n\n  BulkNsObjectLocker() = default;\n  ~BulkNsObjectLocker() = default;\n  /**\n   * Adds an object to be locked after lockAll() is called\n   * @param object the object to lock\n   */\n  void add(ObjectMDPtrType object) {\n    if(object != nullptr) {\n      mMapIdNSObject[object->getIdentifier()] = object;\n    }\n  }\n\n  bool lockAll(LocksVector & locks) {\n    for(auto idNsObject: mMapIdNSObject) {\n      std::unique_ptr<TryLockerType> lock = std::make_unique<TryLockerType>(idNsObject.second);\n      if(lock->locked()) {\n        locks.push_back(std::move(lock));\n      } else {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  /**\n   * Locks every objects previously added via the add() method\n   * @return the vector of locks\n   */\n  LocksVector lockAll() {\n    // copy-ellision here\n    LocksVector locks;\n    while(locks.size() != mMapIdNSObject.size()) {\n      locks.releaseAllLocksAndClear();\n      lockAll(locks);\n    }\n    return locks;\n  }\nprivate:\n  // This will be used to ensure that the locking of the\n  // ObjectMDPtr will be done in the ascending order of their Identifier\n  // By the lockAll() method\n  std::map<Identifier,ObjectMDPtrType> mMapIdNSObject;\n};\n\ntemplate<typename ContainerTryLockerType, typename FileTryLockerType>\nclass BulkMultiNsObjectLocker {\nprivate:\n  using ContainerMDPtrType = typename ContainerTryLockerType::ObjectMDPtrType;\n  using FileMDPtrType = typename FileTryLockerType::ObjectMDPtrType;\n  using ContainerBulkNsObjectLocker = BulkNsObjectLocker<ContainerTryLockerType>;\n  using FileBulkNsObjectLocker = BulkNsObjectLocker<FileTryLockerType>;\n  using ContainerLocksVector = typename ContainerBulkNsObjectLocker::LocksVector;\n  using FileLocksVector = typename FileBulkNsObjectLocker::LocksVector;\npublic:\n  class Locks {\n  public:\n    Locks() = default;\n    Locks(const Locks & locks) = delete;\n    Locks & operator=(const Locks & locks) = delete;\n\n    Locks(Locks&& locks) noexcept\n        : mContLocks(std::move(locks.mContLocks)),\n          mFileLocks(std::move(locks.mFileLocks)) {\n      // Clear the moved-from object's pointers to prevent double deletion\n      locks.mContLocks = nullptr;\n      locks.mFileLocks = nullptr;\n    }\n    Locks & operator=(Locks && locks) {\n      mContLocks = std::move(locks.mContLocks);\n      mFileLocks = std::move(locks.mFileLocks);\n      locks.mContLocks = nullptr;\n      locks.mFileLocks = nullptr;\n    }\n    virtual ~Locks() {\n      // Release files first\n      releaseAllFilesAndClear();\n      // Then release containers\n      releaseAllContainersAndClear();\n    }\n\n    void addContainerLocks(std::unique_ptr<ContainerLocksVector> && contLocks) {\n      mContLocks = std::move(contLocks);\n    }\n\n    void addFileLocks(std::unique_ptr<FileLocksVector> && fileLocks) {\n      mFileLocks = std::move(fileLocks);\n    }\n\n  private:\n    void releaseAllContainersAndClear() {\n      mContLocks.reset(nullptr);\n    }\n    void releaseAllFilesAndClear() {\n      mFileLocks.reset(nullptr);\n    }\n    std::unique_ptr<ContainerLocksVector> mContLocks;\n    std::unique_ptr<FileLocksVector> mFileLocks;\n  };\n\n  BulkMultiNsObjectLocker() = default;\n  virtual ~BulkMultiNsObjectLocker() = default;\n\n  void add(ContainerMDPtrType containerMDPtr) {\n    mContainerTryLocker.add(containerMDPtr);\n  }\n\n  void add(FileMDPtrType fileMDPtr) {\n    mFileTryLocker.add(fileMDPtr);\n  }\n\n  Locks lockAll() {\n    Locks locks;\n    auto backoffDuration = std::chrono::microseconds(10); // start with 10 microseconds\n    const auto maxBackoffDuration = std::chrono::milliseconds(10); // cap backoff at 10 milliseconds\n    while(true) {\n      std::unique_ptr<ContainerLocksVector> containerLocksVector = std::make_unique<ContainerLocksVector>();\n      std::unique_ptr<FileLocksVector> fileLocksVector = std::make_unique<FileLocksVector>();\n      // We first try to lock all the containers\n      bool containerLocked = mContainerTryLocker.lockAll(*containerLocksVector);\n      if(containerLocked){\n        // Then we try to lock all the files\n        bool filesLocked = mFileTryLocker.lockAll(*fileLocksVector);\n        if(filesLocked) {\n          locks.addContainerLocks(std::move(containerLocksVector));\n          locks.addFileLocks(std::move(fileLocksVector));\n          break;\n        } else {\n          // We did not manage to lock at least one File/ContainerMD, release all locks and retry...\n          // Release first the files, then the container to prevent deadlocks...\n          fileLocksVector->releaseAllLocksAndClear();\n          containerLocksVector->releaseAllLocksAndClear();\n        }\n      }\n      // Exponential backoff:\n      std::this_thread::sleep_for(backoffDuration);\n      // Double the backoff duration for the next iteration.\n      backoffDuration *= 2;\n      // Ensure the backoff does not exceed the maximum allowed.\n      if (backoffDuration > maxBackoffDuration) {\n        backoffDuration = maxBackoffDuration;\n      }\n    }\n    return locks;\n  }\n\nprivate:\n  ContainerBulkNsObjectLocker mContainerTryLocker;\n  FileBulkNsObjectLocker mFileTryLocker;\n};\n\nEOSNSNAMESPACE_END\n\n#endif // EOS_BULKNSOBJECTLOCKER_HH\n"
  },
  {
    "path": "namespace/locking/NSObjectLocker.hh",
    "content": "/************************************************************************\n* EOS - the CERN Disk Storage System                                   *\n* Copyright (C) 2022 CERN/Switzerland                                  *\n*                                                                      *\n* This program is free software: you can redistribute it and/or modify *\n* it under the terms of the GNU General Public License as published by *\n* the Free Software Foundation, either version 3 of the License, or    *\n* (at your option) any later version.                                  *\n*                                                                      *\n* This program is distributed in the hope that it will be useful,      *\n* but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n* GNU General Public License for more details.                         *\n*                                                                      *\n* You should have received a copy of the GNU General Public License    *\n* along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n************************************************************************/\n\n#ifndef EOS_NSOBJECTLOCKER_HH\n#define EOS_NSOBJECTLOCKER_HH\n\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/LockableNSObject.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n/**\n * Base class for locking File/ContainerMD\n * Do not use it directly, use either the NSObjectMDLock or the NSObjectMDTryLock\n * @tparam ObjectMDPtr The File/ContainerMD shared_ptr\n * @tparam LockType The type of lock to apply (read/write lock)\n */\ntemplate<typename ObjectMDPtr, typename LockType>\nclass NSObjectMDBaseLock {\npublic:\n  using ObjectMDPtrType = ObjectMDPtr;\n\n  NSObjectMDBaseLock(ObjectMDPtr objectMDPtr)\n  {\n    if (objectMDPtr) {\n      mLock = LockType(objectMDPtr->getMutex(), std::defer_lock);\n      mObjectMDPtr = objectMDPtr;\n    } else {\n      throw_mdexception(ENOENT, \"file/container does not exist\");\n    }\n  }\n\n  virtual ~NSObjectMDBaseLock() = default;\n\n  ObjectMDPtr operator->()\n  {\n    return mObjectMDPtr;\n  }\n  ObjectMDPtr getUnderlyingPtr()\n  {\n    return operator->();\n  }\n\nprotected:\n  //! KEEP THIS ORDER, THE SHARED_PTR NEEDS TO BE DESTROYED AFTER THE LOCK...\n  //! Otherwise you will have a deadlock!\n  ObjectMDPtr mObjectMDPtr;\n  LockType mLock;\n};\n\n/**\n * Locks a ContainerMD/FileMD\n * @tparam ObjectMDPtr the object to lock\n * @tparam LockType the type of lock (read/write)\n */\ntemplate<typename ObjectMDPtr, typename LockType>\nclass NSObjectMDLock : public NSObjectMDBaseLock<ObjectMDPtr, LockType> {\npublic:\n  NSObjectMDLock(ObjectMDPtr objectMDPtr) : NSObjectMDBaseLock<ObjectMDPtr, LockType>(objectMDPtr)\n  {\n    if (objectMDPtr) {\n      this->mObjectMDPtr->lock(this->mLock);\n    }\n  }\n\n  /**\n   * Will unregister the lock from the lock tracking of the objectMD.\n   * The lock itself will be released once this object is out of scope.\n   */\n  virtual ~NSObjectMDLock()\n  {\n    if (this->mObjectMDPtr) {\n      this->mObjectMDPtr->unregisterLock(this->mLock);\n    }\n  }\n};\n\n/**\n * ContainerMD/FileMD try lock mechanism\n * @tparam ObjectMDPtr the object to try lock\n * @tparam LockType the type of lock (read/write)\n */\ntemplate<typename ObjectMDPtr, typename LockType>\nclass NSObjectMDTryLock : public NSObjectMDBaseLock<ObjectMDPtr, LockType> {\npublic:\n  NSObjectMDTryLock(ObjectMDPtr objectMDPtr) : NSObjectMDBaseLock<ObjectMDPtr, LockType>(objectMDPtr)\n  {\n    if (objectMDPtr) {\n      mLocked = this->mObjectMDPtr->tryLock(this->mLock);\n    }\n  }\n\n  /**\n   * @return true if the objectMD could be locked, false otherwise\n   */\n  bool locked()\n  {\n    return mLocked;\n  }\n\n  /**\n   * Will unregister the lock from the lock tracking of the objectMD.\n   * The lock itself will be released once this object is out of scope.\n   */\n  virtual ~NSObjectMDTryLock()\n  {\n    if (this->mObjectMDPtr && mLocked) {\n      this->mObjectMDPtr->unregisterLock(this->mLock);\n    }\n  }\n\nprivate:\n  bool mLocked = false;\n};\n\n\nEOSNSNAMESPACE_END\n\n#endif // EOS_NSOBJECTLOCKER_HH\n"
  },
  {
    "path": "namespace/locking/RawPtr.hh",
    "content": "/************************************************************************\n* EOS - the CERN Disk Storage System                                   *\n* Copyright (C) 2025 CERN/Switzerland                                  *\n*                                                                      *\n* This program is free software: you can redistribute it and/or modify *\n* it under the terms of the GNU General Public License as published by *\n* the Free Software Foundation, either version 3 of the License, or    *\n* (at your option) any later version.                                  *\n*                                                                      *\n* This program is distributed in the hope that it will be useful,      *\n* but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n* GNU General Public License for more details.                         *\n*                                                                      *\n* You should have received a copy of the GNU General Public License    *\n* along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n************************************************************************/\n\n#ifndef EOS_RAWPTR_HH\n#define EOS_RAWPTR_HH\n\n#include \"namespace/Namespace.hh\"\n\nEOSNSNAMESPACE_BEGIN\n// a no-op “deleter” so you don't accidentally free anything\nstruct no_delete {\n  void operator()(void*) noexcept {}\n};\n\ntemplate<typename T>\nstruct raw_ptr {\n  using element_type = T;\n  using pointer = T*;\n\n  constexpr raw_ptr(pointer ptr = nullptr) noexcept : m_ptr(ptr) {}\n\n  constexpr pointer get() const noexcept       { return m_ptr; }\n  constexpr T&      operator*() const          { return *m_ptr; }\n  constexpr pointer operator->() const noexcept{ return m_ptr; }\n  constexpr operator bool() const noexcept                { return m_ptr != nullptr; }\n  constexpr bool operator !=(const raw_ptr &other) { return m_ptr != other.m_ptr; }\n\nprivate:\n  pointer m_ptr;\n};\n\nEOSNSNAMESPACE_END\n\n#endif // EOS_RAWPTR_HH\n"
  },
  {
    "path": "namespace/ns_quarkdb/CMakeLists.txt",
    "content": "#-------------------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Lukasz Janyst - CERN\n#-------------------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2016 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ninclude_directories(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR})\n\nadd_subdirectory(tests)\n\n#-------------------------------------------------------------------------------\n# EosNsQuarkdb library\n#-------------------------------------------------------------------------------\nadd_library(\n  EosNsQuarkdb MODULE\n  NsQuarkdbPlugin.cc     NsQuarkdbPlugin.hh)\n\nadd_dependencies(EosNsQuarkdb EosNsQuarkdbProto-Objects)\n\ntarget_link_libraries(EosNsQuarkdb PUBLIC\n  EosNsCommon\n  qclient\n  ROCKSDB::ROCKSDB\n  BZ2::BZ2\n  ${CMAKE_THREAD_LIBS_INIT})\n\ninstall(TARGETS EosNsQuarkdb\n  LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR}\n  ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\n\nadd_executable(eos-ns-convert-to-locality-hashes tools/EosConvertToLocalityHashes.cc)\n\ntarget_link_libraries(eos-ns-convert-to-locality-hashes PRIVATE\n  EosNsCommon-Static)\n\nadd_executable(eos-ns-inspect tools/InspectionTool.cc)\n\ntarget_link_libraries(eos-ns-inspect PRIVATE EosNsCommon-Static)\n\nadd_executable(eos-fid-to-path tools/Fid2PathTool.cc)\n\ntarget_link_libraries(eos-fid-to-path\n  EosNsCommon-Static)\n\nadd_executable(eos-inode-to-fid tools/InodeToFidTool.cc)\n\ntarget_link_libraries(eos-inode-to-fid\n  EosNsCommon-Static)\n\ninstall(TARGETS eos-ns-convert-to-locality-hashes eos-ns-inspect eos-fid-to-path eos-inode-to-fid\n  LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n  ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\n"
  },
  {
    "path": "namespace/ns_quarkdb/CacheRefreshListener.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/CacheRefreshListener.hh\"\n#include \"namespace/interface/Identifiers.hh\"\n#include \"namespace/ns_quarkdb/persistency/MetadataProvider.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"common/ParseUtils.hh\"\n#include <functional>\n#include <qclient/pubsub/Message.hh>\n\nusing std::placeholders::_1;\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nCacheRefreshListener::CacheRefreshListener(const QdbContactDetails &cd, MetadataProvider *provider)\n: mContactDetails(cd), mMetadataProvider(provider),\n  mSubscriber(cd.members, cd.constructSubscriptionOptions()) {\n\n  mFidSubscription = mSubscriber.subscribe(constants::sCacheInvalidationFidChannel);\n  mCidSubscription = mSubscriber.subscribe(constants::sCacheInvalidationCidChannel);\n\n  mFidSubscription->attachCallback(std::bind(&CacheRefreshListener::processIncomingFidInvalidation,\n    this, _1));\n\n  mCidSubscription->attachCallback(std::bind(&CacheRefreshListener::processIncomingCidInvalidation,\n    this, _1));\n}\n\n//------------------------------------------------------------------------------\n//! Destructor\n//------------------------------------------------------------------------------\nCacheRefreshListener::~CacheRefreshListener() {}\n\n//------------------------------------------------------------------------------\n// Process incoming fid invalidation\n//------------------------------------------------------------------------------\nvoid CacheRefreshListener::processIncomingFidInvalidation(qclient::Message &&msg) {\n  eos_static_info(\"Received invalidation message for fid=%s\", msg.getPayload().c_str());\n\n  uint64_t fid;\n\n  if(common::ParseUInt64(msg.getPayload(), fid)) {\n    mMetadataProvider->dropCachedFileID(FileIdentifier(fid));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Process incoming cid invalidation\n//------------------------------------------------------------------------------\nvoid CacheRefreshListener::processIncomingCidInvalidation(qclient::Message &&msg) {\n  eos_static_info(\"Received invalidation message for cid=%s\", msg.getPayload().c_str());\n\n  uint64_t cid;\n\n  if(common::ParseUInt64(msg.getPayload(), cid)) {\n    mMetadataProvider->dropCachedContainerID(ContainerIdentifier(cid));\n  }\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/CacheRefreshListener.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Class to listen for notifications about cache-invalidated MD entries\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_CACHE_REFRESH_LISTENER_HH\n#define EOS_NS_CACHE_REFRESH_LISTENER_HH\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"QdbContactDetails.hh\"\n#include <qclient/pubsub/Subscriber.hh>\n\nEOSNSNAMESPACE_BEGIN\n\nclass MetadataProvider;\n\n//------------------------------------------------------------------------------\n//! Class to listen for notifications (typically issued by eos-ns-inspect)\n//! about which metadata entries have been modified outside the MGM.\n//------------------------------------------------------------------------------\nclass CacheRefreshListener {\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  CacheRefreshListener(const QdbContactDetails &cd, MetadataProvider *provider);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~CacheRefreshListener();\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Process incoming fid invalidation\n  //----------------------------------------------------------------------------\n  void processIncomingFidInvalidation(qclient::Message &&msg);\n\n  //----------------------------------------------------------------------------\n  //! Process incoming cid invalidation\n  //----------------------------------------------------------------------------\n  void processIncomingCidInvalidation(qclient::Message &&msg);\n\n\n  QdbContactDetails mContactDetails;\n  MetadataProvider* mMetadataProvider;\n  qclient::Subscriber mSubscriber;\n\n  std::unique_ptr<qclient::Subscription> mFidSubscription;\n  std::unique_ptr<qclient::Subscription> mCidSubscription;\n};\n\nEOSNSNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "namespace/ns_quarkdb/ConfigurationParser.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Class for parsing QdbContactDetails out of a configuration map\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_QDB_CONFIGURATION_PARSER_HH\n#define EOS_NS_QDB_CONFIGURATION_PARSER_HH\n\n#include <chrono>\n\n#include <qclient/Members.hh>\n#include <qclient/Options.hh>\n#include <qclient/Handshake.hh>\n#include \"namespace/Namespace.hh\"\n#include \"namespace/MDException.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include <map>\n\nEOSNSNAMESPACE_BEGIN\n\nclass ConfigurationParser {\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Parse a configuration map, and extract a QdbContactDetails out of it.\n  //! Throws in case that's not possible.\n  //----------------------------------------------------------------------------\n  static QdbContactDetails parse(const\n    std::map<std::string, std::string> &configuration)\n  {\n    QdbContactDetails contactDetails;\n\n    const std::string key_cluster = \"qdb_cluster\";\n    const std::string key_password = \"qdb_password\";\n\n    auto it = configuration.find(key_cluster);\n    if(it == configuration.end()) {\n      throw_mdexception(EINVAL, \"Could not find qdb_cluster in NS configuration!\");\n    }\n\n    if(!contactDetails.members.parse(it->second)) {\n      throw_mdexception(EINVAL, \"Could not parse qdb_cluster\");\n    }\n\n    it = configuration.find(key_password);\n    if(it != configuration.end()) {\n      contactDetails.password = it->second;\n    }\n\n    return contactDetails;\n  }\n\n\n};\n\n\nEOSNSNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "namespace/ns_quarkdb/Constants.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Constants used for the namespace implementation on top of quarkdb\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include <string>\n\nEOSNSNAMESPACE_BEGIN\n\n//! Variables associated with the HierarchcalView\nnamespace constants\n{\n//! Key for container metadata locality hash.\nstatic const std::string sContainerKey {\"eos-container-md\"};\n//! Key for file metadata locality hash.\nstatic const std::string sFileKey {\"eos-file-md\"};\n//! Suffix for set of subcontainers in a container\nstatic const std::string sMapDirsSuffix{\":map_conts\"};\n//! Suffix for set of files in a container\nstatic const std::string sMapFilesSuffix{\":map_files\"};\n//! Key for map containing meta info\nstatic const std::string sMapMetaInfoKey{\"meta_map\"};\n//! Field last used file id in meta info map\nstatic const std::string sLastUsedFid{\"last_used_fid\"};\n//! Field last used container id in meta info map\nstatic const std::string sLastUsedCid{\"last_used_cid\"};\n//! Set of orphans files, which are not anymore attached to a container\n//! they were unlinked but not removed yet\nstatic const std::string sOrphanFiles{\"orphan_files\"};\n\nstatic const std::string sUseSharedInodes {\"use-shared-inodes\"};\n\n//! Suffix for container metadata in Redis\nstatic const std::string sContKeySuffix{\":c_bucket\"};\n//! Sufix for file metadata in Redis\nstatic const std::string sFileKeySuffix{\":f_bucket\"};\n\n//! Tag for max num of file entries cached at the MGM\nstatic const std::string sMaxNumCacheFiles {\"max_num_cache_files\"};\n//! Tag for max size (bytes) of file entries cached at the MGM\nstatic const std::string sMaxSizeCacheFiles {\"max_size_cache_files\"};\n//! Tag for max num of dir/container entries cached at the MGM\nstatic const std::string sMaxNumCacheDirs {\"max_num_cache_dirs\"};\n//! Tag for max size (bytes) of dir/container entries cached at the MGM\nstatic const std::string sMaxSizeCacheDirs {\"max_size_cache_dirs\"};\n\n//! Channel for incoming fid cache invalidation notifications\nstatic const std::string sCacheInvalidationFidChannel {\"eos-md-cache-invalidation-fid\"};\n//! Channel for incoming cid cache invalidation notifications\nstatic const std::string sCacheInvalidationCidChannel {\"eos-md-cache-invalidation-cid\"};\n\n//! Configuration key to trigger a refresh of the inode provider\nstatic const std::string sKeyInodeRefresh {\"qdb_inode_refresh\"};\n}\n\n//! Variable associated with the QuotaView\nnamespace quota\n{\n// QuotaStats\n//! Prefix for all quota related maps\nstatic const std::string sPrefix = \"quota:\";\n//! Quota hmap of uids suffix\nstatic const std::string sUidsSuffix = \"map_uid\";\n//! Quta hmap of gids suffix\nstatic const std::string sGidsSuffix = \"map_gid\";\n\n// QuotaNode\n//! Tag for space\nstatic const std::string sLogicalSize = \":logical_size\";\n//! Tag for physical space\nstatic const std::string sPhysicalSize = \":physical_size\";\n//! Tag for number of files\nstatic const std::string sNumFiles = \":files\";\n}\n\n// Variable associated with the FileSystemView\nnamespace fsview\n{\n//! Prefix for sets storing file ids\nstatic const std::string sPrefix = \"fsview:\";\n//! Set suffix for file ids on a fs\nstatic const std::string sFilesSuffix = \"files\";\n//! Set suffix for unlinked file ids on a fs\nstatic const std::string sUnlinkedSuffix = \"unlinked\";\n//! Set suffix for file ids with no replicas\nstatic const std::string sNoReplicaPrefix = \"fsview_noreplicas\";\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/ContainerMD.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/ContainerMD.hh\"\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"namespace/ns_quarkdb/persistency/ContainerMDSvc.hh\"\n#include \"namespace/utils/DataHelper.hh\"\n#include \"namespace/utils/StringConvertion.hh\"\n#include \"namespace/ns_quarkdb/persistency/Serialization.hh\"\n#include \"namespace/ns_quarkdb/persistency/MetadataFetcher.hh\"\n#include \"namespace/PermissionHandler.hh\"\n#include \"google/protobuf/io/zero_copy_stream_impl.h\"\n#include \"google/protobuf/io/zero_copy_stream_impl_lite.h\"\n#include \"common/Assert.hh\"\n#include \"common/StacktraceHere.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/Logging.hh\"\n#include \"common/Utils.hh\"\n#include <sys/stat.h>\n#include <algorithm>\n#include <chrono>\n#include <optional>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nQuarkContainerMD::QuarkContainerMD(IContainerMD::id_t id, IFileMDSvc* file_svc,\n                                   IContainerMDSvc* cont_svc)\n  : IContainerMD(),\n    pFilesKey(stringify(id) + constants::sMapFilesSuffix),\n    pDirsKey(stringify(id) + constants::sMapDirsSuffix)\n{\n  mSubcontainers->set_deleted_key(\"\");\n  mFiles->set_deleted_key(\"\");\n  mSubcontainers->set_empty_key(\"##_EMPTY_##\");\n  mFiles->set_empty_key(\"##_EMPTY_##\");\n  mCont.set_id(id);\n  mCont.set_mode(040755);\n  mClock = std::chrono::high_resolution_clock::now().time_since_epoch().count();\n\n  if (!cont_svc && !file_svc) {\n    // \"Standalone\" ContainerMD, without associated container service.\n    // Don't call functions which might modify metadata..\n    // This is a hack, it would be probably cleaner to remove the services\n    // from this class altogether.\n    return;\n  }\n\n  setServices(file_svc, cont_svc);\n}\n\n//------------------------------------------------------------------------------\n// Set namespace services\n//------------------------------------------------------------------------------\nvoid QuarkContainerMD::setServices(IFileMDSvc* file_svc,\n                                   IContainerMDSvc* cont_svc)\n{\n  eos_assert(pFileSvc == nullptr && pContSvc == nullptr);\n  eos_assert(file_svc != nullptr && cont_svc != nullptr);\n  pFileSvc = file_svc;\n  pContSvc = cont_svc;\n  QuarkContainerMDSvc* impl_cont_svc = dynamic_cast<QuarkContainerMDSvc*>\n                                       (cont_svc);\n\n  if (!impl_cont_svc) {\n    MDException e(EFAULT);\n    e.getMessage() << __FUNCTION__ << \" ContainerMDSvc dynamic cast failed\";\n    throw e;\n  }\n\n  pQcl = impl_cont_svc->pQcl;\n  pFlusher = impl_cont_svc->pFlusher;\n}\n\n//------------------------------------------------------------------------------\n// Virtual copy constructor\n//------------------------------------------------------------------------------\nQuarkContainerMD*\nQuarkContainerMD::clone() const\n{\n  return new QuarkContainerMD(*this);\n}\n\n//------------------------------------------------------------------------------\n// Copy constructor\n//------------------------------------------------------------------------------\nQuarkContainerMD::QuarkContainerMD(const QuarkContainerMD& other)\n{\n  mCont    = other.mCont;\n  pContSvc = other.pContSvc;\n  pFileSvc = other.pFileSvc;\n  pQcl     = other.pQcl;\n  mClock   = other.mClock;\n  pFlusher = other.pFlusher;\n  pDirsKey = other.pDirsKey;\n  pFilesKey = other.pFilesKey;\n}\n\n//------------------------------------------------------------------------------\n// Children inheritance\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::InheritChildren(const IContainerMD& other)\n{\n  QuarkContainerMD& otherContainer =\n    dynamic_cast<QuarkContainerMD&>(const_cast<IContainerMD&>(other));\n  mFiles.get() = otherContainer.copyFileMap();\n  mSubcontainers.get() = otherContainer.copyContainerMap();\n  setTreeSize(otherContainer.getTreeSize());\n}\n\n//------------------------------------------------------------------------------\n// Turn a ContainerMDPtr into FileOrContainerMD.\n//------------------------------------------------------------------------------\nstatic FileOrContainerMD wrapContainerMD(IContainerMDPtr ptr)\n{\n  return FileOrContainerMD {nullptr, ptr};\n}\n\n//------------------------------------------------------------------------------\n// Turn a FileMDPtr into FileOrContainerMD.\n//------------------------------------------------------------------------------\nstatic FileOrContainerMD wrapFileMD(IFileMDPtr ptr)\n{\n  return FileOrContainerMD {ptr, nullptr};\n}\n\n//------------------------------------------------------------------------------\n// Extract FileMDPtr out of FileOrContainerMD.\n//------------------------------------------------------------------------------\nstatic IFileMDPtr extractFileMD(FileOrContainerMD ptr)\n{\n  return ptr.file;\n}\n\n//------------------------------------------------------------------------------\n// Extract ContainerMDPtr out of FileOrContainerMD.\n//------------------------------------------------------------------------------\nstatic IContainerMDPtr extractContainerMD(FileOrContainerMD ptr)\n{\n  return ptr.container;\n}\n\n//------------------------------------------------------------------------------\n// Find item\n//------------------------------------------------------------------------------\nfolly::Future<FileOrContainerMD>\nQuarkContainerMD::findItem(const std::string& name)\n{\n  const IContainerMD::id_t id = getId();\n  std::optional<ContainerIdentifier> targetContainer;\n  std::optional<FileIdentifier> targetFile;\n  //Only the search within the files and the sub containers need to be read locked.\n  runReadOp([this, name, &targetContainer, &targetFile]() {\n    // We're looking for \"name\". Look inside subcontainer map to check if there's\n    // a container with such name.\n    auto iter = mSubcontainers->find(name);\n\n    if (iter != mSubcontainers->end()) {\n      targetContainer = ContainerIdentifier(iter->second);\n      return;\n    }\n\n    // This is not a ContainerMD.. maybe it's a FileMD?\n    auto iter2 = mFiles->find(name);\n\n    if (iter2 != mFiles->end()) {\n      targetFile = FileIdentifier(iter2->second);\n      return;\n    }\n  });\n\n  if (targetContainer) {\n    folly::Future<FileOrContainerMD> fut =\n      pContSvc->getContainerMDFut(\n        targetContainer->getUnderlyingUInt64())\n      .thenValue(wrapContainerMD)\n    .thenError([name, id](const folly::exception_wrapper & e) {\n      // Should not happen...\n      eos_static_crit(\"Exception occurred while looking up container with \"\n                      \"name %s in subcontainer with id %llu: %s\", name.c_str(),\n                      id, e.what().c_str());\n      return FileOrContainerMD {};\n    });\n    return fut;\n  }\n\n  if (targetFile) {\n    folly::Future<FileOrContainerMD> fut =\n      pFileSvc->getFileMDFut(\n        targetFile->getUnderlyingUInt64())\n      .thenValue(wrapFileMD)\n    .thenError([name, id](const folly::exception_wrapper & e) {\n      // Should not happen...\n      eos_static_crit(\"Exception occurred while looking up file with name %s \"\n                      \"in subcontainer with id %llu: %s\", name.c_str(), id,\n                      e.what().c_str());\n      return FileOrContainerMD {};\n    });\n    return fut;\n  }\n\n  // Nope, \"name\" doesn't exist in this container.\n  return FileOrContainerMD {};\n}\n\n//------------------------------------------------------------------------------\n// Remove container\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::removeContainer(const std::string& name)\n{\n  runWriteOp([this, &name]() {\n    auto it = mSubcontainers->find(name);\n\n    if (it == mSubcontainers->end()) {\n      MDException e(ENOENT);\n      e.getMessage()  << __FUNCTION__ << \" Container \" << name << \" not found\";\n      throw e;\n    }\n\n    mSubcontainers->erase(it);\n    // mSubcontainers->resize(0);\n    // Delete container also from KV backend\n    pFlusher->hdel(pDirsKey, name);\n  });\n  // NOTE: This is an ugly hack. There's no file object here\n  // and we hijack the \"location\" member of the Event\n  // class to pass in the container id.\n  IFileMDChangeListener::Event e(nullptr, IFileMDChangeListener::SizeChange,\n                                 mCont.id(),\n                                 // remove this container from the tree container counter\n  {0, 0, -1});\n  pFileSvc->notifyListeners(&e);\n}\n\n//------------------------------------------------------------------------------\n// Add container\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::addContainer(IContainerMD* container)\n{\n  runWriteOp([this, container]() {\n    if (container->getName().empty()) {\n      eos_static_crit(eos::common::getStacktrace().c_str());\n      throw_mdexception(EINVAL,\n                        \"Attempted to add container with empty name! ID: \" << container->getId() <<\n                        \", target container ID: \" << mCont.id());\n    }\n\n    auto containerConflict = mSubcontainers->find(container->getName());\n\n    if (containerConflict != mSubcontainers->end() &&\n        containerConflict->second != container->getId()) {\n      eos_static_crit(eos::common::getStacktrace().c_str());\n      throw_mdexception(EEXIST, \"Attempted to add container with name \"\n                        << container->getName()\n                        << \" while a different subcontainer exists already there.\");\n    }\n\n    auto fileConflict = mFiles->find(container->getName());\n\n    if (fileConflict != mFiles->end()) {\n      eos_static_crit(eos::common::getStacktrace().c_str());\n      throw_mdexception(EEXIST, \"Attempted to add container with name \"\n                        << container->getName()\n                        << \" while a file exists already there.\");\n    }\n\n    container->setParentId(mCont.id());\n    (void) mSubcontainers->insert(std::make_pair(container->getName(),\n                                  container->getId()));\n    // Add to new container to KV backend\n    pFlusher->hset(pDirsKey, container->getName(), stringify(container->getId()));\n  });\n  // NOTE: This is an ugly hack. There's no file object here\n  // and we hijack the \"location\" member of the Event\n  // class to pass in the container id.\n  IFileMDChangeListener::Event e(nullptr, IFileMDChangeListener::SizeChange,\n                                 mCont.id(),\n                                 //Add this container to the tree container counter\n  {0, 0, 1});\n  pFileSvc->notifyListeners(&e);\n}\n\n//------------------------------------------------------------------------------\n// Find file, asynchronous API\n//------------------------------------------------------------------------------\nfolly::Future<IFileMDPtr>\nQuarkContainerMD::findFileFut(const std::string& name)\n{\n  return this->findItem(name).thenValue(extractFileMD);\n}\n\n//------------------------------------------------------------------------------\n// Find file\n//------------------------------------------------------------------------------\nstd::shared_ptr<IFileMD>\nQuarkContainerMD::findFile(const std::string& name)\n{\n  return this->findItem(name).get().file;\n}\n\n//------------------------------------------------------------------------------\n// Find subcontainer, asynchronous API\n//------------------------------------------------------------------------------\nfolly::Future<IContainerMDPtr>\nQuarkContainerMD::findContainerFut(const std::string& name)\n{\n  return this->findItem(name).thenValue(extractContainerMD);\n}\n\n//------------------------------------------------------------------------------\n// Find subcontainer\n//------------------------------------------------------------------------------\nstd::shared_ptr<IContainerMD>\nQuarkContainerMD::findContainer(const std::string& name)\n{\n  return this->findItem(name).get().container;\n}\n\n//------------------------------------------------------------------------------\n// Add file\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::addFile(IFileMD* file)\n{\n  runWriteOp([this, file]() {\n    if (file->getName().empty()) {\n      eos_static_crit(eos::common::getStacktrace().c_str());\n      throw_mdexception(EINVAL,\n                        \"Attempted to add file with empty filename! ID: \" << file->getId() <<\n                        \", target container ID: \" << mCont.id());\n    }\n\n    auto containerConflict = mSubcontainers->find(file->getName());\n\n    if (containerConflict != mSubcontainers->end()) {\n      eos_static_crit(eos::common::getStacktrace().c_str());\n      throw_mdexception(EEXIST,\n                        \"Attempted to add file with name \" << file->getName() <<\n                        \" while a subcontainer exists already there.\");\n    }\n\n    auto fileConflict = mFiles->find(file->getName());\n\n    if (fileConflict != mFiles->end() && fileConflict->second != file->getId()) {\n      eos_static_crit(eos::common::getStacktrace().c_str());\n      throw_mdexception(EEXIST,\n                        \"Attempted to add file with name \" << file->getName() <<\n                        \" while a different file exists already there.\");\n    }\n\n    file->setContainerId(mCont.id());\n    (void)mFiles->insert(std::make_pair(file->getName(), file->getId()));\n    pFlusher->hset(pFilesKey, file->getName(), std::to_string(file->getId()));\n  });\n  // NOTE: We hijack the \"location\" member of the Event to pass in the container id.\n  IFileMDChangeListener::Event e(nullptr, IFileMDChangeListener::SizeChange, mCont.id(),\n                                 // add the file size and do +1 in the tree files counter\n                                 {static_cast<int64_t>(file->getSize()), 1, 0});\n  pFileSvc->notifyListeners(&e);\n}\n\n//------------------------------------------------------------------------------\n// Remove file\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::removeFile(const std::string& name)\n{\n  bool found = false;\n  IFileMD::id_t id;\n  runWriteOp([this, &name, &found, &id]() {\n    auto iter = mFiles->find(name);\n\n    if (iter != mFiles->end()) {\n      found = true;\n      id = iter->second;\n      mFiles->erase(iter);\n      // mFiles->resize(0);\n      pFlusher->hdel(pFilesKey, name);\n    }\n  });\n\n  if (found) {\n    try {\n      std::shared_ptr<IFileMD> file = pFileSvc->getFileMD(id);\n      // NOTE: We hijack the \"location\" member of the Event to pass in the cid\n      IFileMDChangeListener::Event e(\n          nullptr, IFileMDChangeListener::SizeChange, mCont.id(),\n          // remove the file size and do -1 in the tree files counter\n          {-static_cast<int64_t>(file->getSize()), -1, 0});\n      pFileSvc->notifyListeners(&e);\n    } catch (MDException& e) {\n      // File already removed\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get number of files\n//------------------------------------------------------------------------------\nsize_t\nQuarkContainerMD::getNumFiles()\n{\n  return runReadOp([this]() {\n    return mFiles->size();\n  });\n}\n\n//----------------------------------------------------------------------------\n// Get number of containers\n//----------------------------------------------------------------------------\nsize_t\nQuarkContainerMD::getNumContainers()\n{\n  return runReadOp([this]() {\n    return mSubcontainers->size();\n  });\n}\n\n//------------------------------------------------------------------------------\n// Check the access permissions\n//------------------------------------------------------------------------------\nbool\nQuarkContainerMD::access(uid_t uid, gid_t gid, int flags)\n{\n  // root can do everything\n  if (uid == 0) {\n    return true;\n  }\n\n  // daemon can read everything\n  if ((uid == 2) && ((flags & W_OK) == 0)) {\n    return true;\n  }\n\n  // Filter out based on sys.mask\n  mode_t filteredMode = PermissionHandler::filterWithSysMask(mCont.xattrs(),\n                        mCont.mode());\n  // Convert the flags\n  char convFlags = PermissionHandler::convertRequested(flags);\n  return runReadOp([this, uid, gid, filteredMode, convFlags]() {\n    // Check the perms\n    if (uid == mCont.uid()) {\n      char user = PermissionHandler::convertModetUser(filteredMode);\n      return PermissionHandler::checkPerms(user, convFlags);\n    }\n\n    if (gid == mCont.gid()) {\n      char group = PermissionHandler::convertModetGroup(filteredMode);\n      return PermissionHandler::checkPerms(group, convFlags);\n    }\n\n    char other = PermissionHandler::convertModetOther(filteredMode);\n    return PermissionHandler::checkPerms(other, convFlags);\n  });\n}\n\n//------------------------------------------------------------------------------\n// Set name\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::setName(const std::string& name)\n{\n  runWriteOp([this, name]() {\n    if (mCont.id() != 1 && name.find('/') != std::string::npos) {\n      eos_static_crit(\"msg=\\\"detected slashes in container name\\\" cxid=%08llx \"\n                      \"trace=\\\"%s\\\"\", mCont.id(),\n                      eos::common::getStacktrace().c_str());\n      throw_mdexception(EINVAL, \"Bug: detected slashes in container name: \" << name);\n    }\n\n    if (name.empty()) {\n      eos_static_crit(\"msg=\\\"detected empty container name\\\" cxid=%08llx \"\n                      \"trace=\\\"%s\\\"\", mCont.id(),\n                      eos::common::getStacktrace().c_str());\n      throw_mdexception(EINVAL, \"Bug: detected empty container name\");\n    }\n\n    // // Check that there is no clash with other subcontainers having the same name\n    // if (mCont.parent_id() != 0u) {\n    //   auto parent = pContSvc->getContainerMD(mCont.parent_id());\n    //   if (parent->findContainer(name)) {\n    //     eos::MDException e(EINVAL);\n    //     e.getMessage() << \"Container with name \\\"\" << name << \"\\\" already exists\";\n    //     throw e;\n    //   }\n    // }\n    mCont.set_name(name);\n  });\n}\n\n//------------------------------------------------------------------------------\n// Set creation time\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::setCTime(ctime_t ctime)\n{\n  runWriteOp([this, ctime]() {\n    mCont.set_ctime(&ctime, sizeof(ctime));\n  });\n}\n\n//------------------------------------------------------------------------------\n// Set creation time to now\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::setCTimeNow()\n{\n  struct timespec tnow;\n#ifdef __APPLE__\n  struct timeval tv;\n  gettimeofday(&tv, 0);\n  tnow.tv_sec = tv.tv_sec;\n  tnow.tv_nsec = tv.tv_usec * 1000;\n#else\n  clock_gettime(CLOCK_REALTIME, &tnow);\n#endif\n  setCTime(tnow);\n}\n\n//------------------------------------------------------------------------------\n// Get creation time\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::getCTime(ctime_t& ctime) const\n{\n  runReadOp([this, &ctime]() {\n    getCTimeNoLock(ctime);\n  });\n}\n\n//------------------------------------------------------------------------------\n// Get creation time, no locks\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::getCTimeNoLock(ctime_t& ctime) const\n{\n  (void) memcpy(&ctime, mCont.ctime().data(), sizeof(ctime));\n}\n\n//------------------------------------------------------------------------------\n// Set modification time\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::setMTime(mtime_t mtime)\n{\n  runWriteOp([this, mtime]() {\n    mCont.set_mtime(&mtime, sizeof(mtime));\n  });\n}\n\n//------------------------------------------------------------------------------\n// Set creation time to now\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::setMTimeNow()\n{\n  struct timespec tnow;\n#ifdef __APPLE__\n  struct timeval tv = {0};\n  gettimeofday(&tv, 0);\n  tnow.tv_sec = tv.tv_sec;\n  tnow.tv_nsec = tv.tv_usec * 1000;\n#else\n  clock_gettime(CLOCK_REALTIME, &tnow);\n#endif\n  setMTime(tnow);\n}\n\n//------------------------------------------------------------------------------\n// Get modification time\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::getMTime(mtime_t& mtime) const\n{\n  return runReadOp([this, &mtime]() {\n    getMTimeNoLock(mtime);\n  });\n}\n\n//------------------------------------------------------------------------------\n// Get modification time, no lock\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::getMTimeNoLock(mtime_t& mtime) const\n{\n  (void) memcpy(&mtime, mCont.mtime().data(), sizeof(mtime));\n}\n\n//------------------------------------------------------------------------------\n// Set propagated modification time (if newer)\n//------------------------------------------------------------------------------\nbool\nQuarkContainerMD::setTMTime(tmtime_t tmtime)\n{\n  return runWriteOp([this, &tmtime]() {\n    tmtime_t tmt;\n    getTMTimeNoLock(tmt);\n    tmtime_t now;\n    clock_gettime(CLOCK_REALTIME, &now);\n\n    if ((tmtime.tv_sec == 0) || (tmtime.tv_sec > now.tv_sec)) {\n      tmtime = now;\n    }\n\n    if (((tmt.tv_sec == 0) && (tmt.tv_nsec == 0)) ||\n        (tmtime.tv_sec > tmt.tv_sec) ||\n        ((tmtime.tv_sec == tmt.tv_sec) &&\n         (tmtime.tv_nsec > tmt.tv_nsec))) {\n      mCont.set_stime(&tmtime, sizeof(tmtime));\n      return true;\n    }\n\n    return false;\n  });\n}\n\n//------------------------------------------------------------------------------\n// Set propagated modification time to now\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::setTMTimeNow()\n{\n  tmtime_t tmtime = {0};\n  setTMTime(tmtime);\n}\n\n//------------------------------------------------------------------------------\n// Get propagated modification time, no locks\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::getTMTimeNoLock(tmtime_t& tmtime)\n{\n  tmtime = {};\n\n  if (mCont.stime().length()) {\n    (void) memcpy(&tmtime, mCont.stime().data(), sizeof(tmtime));\n  } else {\n    (void) memcpy(&tmtime, mCont.mtime().data(), sizeof(tmtime));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get propagated modification time\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::getTMTime(tmtime_t& tmtime)\n{\n  runReadOp([this, &tmtime]() {\n    getTMTimeNoLock(tmtime);\n  });\n}\n\n//------------------------------------------------------------------------------\n// Trigger an mtime change event\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::notifyMTimeChange(IContainerMDSvc* containerMDSvc)\n{\n  containerMDSvc->notifyListeners(this,\n                                  IContainerMDChangeListener::MTimeChange);\n}\n\n//------------------------------------------------------------------------------\n// Update tree size\n//------------------------------------------------------------------------------\nuint64_t\nQuarkContainerMD::updateTreeSize(int64_t delta)\n{\n  return runWriteOp([this, delta]() {\n    uint64_t sz = mCont.tree_size();\n    eos::common::ComputeSize(sz, delta);\n    mCont.set_tree_size(sz);\n    return sz;\n  });\n}\n\nuint64_t QuarkContainerMD::updateTreeContainers(int64_t delta)\n{\n  return runWriteOp([this, delta]() {\n    uint64_t sz = mCont.tree_containers();\n    eos::common::ComputeSize(sz, delta);\n    mCont.set_tree_containers(sz);\n    return sz;\n  });\n}\n\nuint64_t QuarkContainerMD::updateTreeFiles(int64_t delta)\n{\n  return runWriteOp([this, delta]() {\n    uint64_t sz = mCont.tree_files();\n    // Avoid negative tree size\n    eos::common::ComputeSize(sz, delta);\n    mCont.set_tree_files(sz);\n    return sz;\n  });\n}\n\n//------------------------------------------------------------------------------\n// Get the attribute\n//------------------------------------------------------------------------------\nstd::string\nQuarkContainerMD::getAttribute(const std::string& name) const\n{\n  return runReadOp([this, name]() {\n    auto it = mCont.xattrs().find(name);\n\n    if (it == mCont.xattrs().end()) {\n      MDException e(ENOENT);\n      e.getMessage()  << __FUNCTION__  << \" Attribute: \" << name << \" not found\";\n      throw e;\n    }\n\n    return it->second;\n  });\n}\n\n//------------------------------------------------------------------------------\n// Remove attribute\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::removeAttribute(const std::string& name)\n{\n  runWriteOp([this, name]() {\n    auto it = mCont.xattrs().find(name);\n\n    if (it != mCont.xattrs().end()) {\n      mCont.mutable_xattrs()->erase(it->first);\n    }\n  });\n}\n\n//------------------------------------------------------------------------------\n// Serialize the object to a buffer\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::serialize(Buffer& buffer)\n{\n  runReadOp([this, &buffer]() {\n    // Align the buffer to 4 bytes to efficiently compute the checksum\n    mClock = std::chrono::high_resolution_clock::now().time_since_epoch().count();\n#if GOOGLE_PROTOBUF_VERSION < 3004000\n    size_t obj_size = mCont.ByteSize();\n#else\n    size_t obj_size = mCont.ByteSizeLong();\n#endif\n    uint32_t align_size = (obj_size + 3) >> 2 << 2;\n    size_t sz = sizeof(align_size);\n    size_t msg_size = align_size + 2 * sz;\n    buffer.setSize(msg_size);\n    // Write the checksum value, size of the raw protobuf object and then the\n    // actual protobuf object serialized\n    const char* ptr = buffer.getDataPtr() + 2 * sz;\n    google::protobuf::io::ArrayOutputStream aos((void*)ptr, align_size);\n\n    if (!mCont.SerializeToZeroCopyStream(&aos)) {\n      MDException ex(EIO);\n      ex.getMessage() << \"Failed while serializing buffer\";\n      throw ex;\n    }\n\n    // Compute the CRC32C checksum\n    uint32_t cksum = DataHelper::computeCRC32C((void*)ptr, align_size);\n    cksum = DataHelper::finalizeCRC32C(cksum);\n    // Point to the beginning to fill in the checksum and size of useful data\n    ptr = buffer.getDataPtr();\n    (void) memcpy((void*)ptr, &cksum, sz);\n    ptr += sz;\n    (void) memcpy((void*)ptr, &obj_size, sz);\n  });\n}\n\n//------------------------------------------------------------------------------\n// Load children\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::loadChildren()\n{\n  // Requires lock be taken outside.\n  // Rebuild the file and subcontainer keys\n  pFilesKey = stringify(mCont.id()) + constants::sMapFilesSuffix;\n  pDirsKey = stringify(mCont.id()) + constants::sMapDirsSuffix;\n\n  if (pQcl) {\n    mFiles = MetadataFetcher::getFileMap(*pQcl,\n                                         ContainerIdentifier(mCont.id()));\n    mSubcontainers = MetadataFetcher::getContainerMap(*pQcl,\n                     ContainerIdentifier(mCont.id()));\n  } else {\n    // I think this case only happens inside some tests.. remove eventually?\n    mFiles->clear();\n    mSubcontainers->clear();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Deserialize from buffer\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::deserialize(Buffer& buffer)\n{\n  runWriteOp([this, &buffer]() {\n    Serialization::deserializeContainer(buffer, mCont);\n    loadChildren();\n  });\n}\n\n//------------------------------------------------------------------------------\n// Initialize, inject children\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::initialize(eos::ns::ContainerMdProto&& proto,\n                             IContainerMD::FileMap&& fileMap, IContainerMD::ContainerMap&& containerMap)\n{\n  runWriteOp([this, Proto = std::move(proto), FileMap = std::move(fileMap),\n        ContainerMap = std::move(containerMap)]() {\n    mCont = std::move(Proto);\n    mFiles.get() = std::move(FileMap);\n    mSubcontainers.get() = std::move(ContainerMap);\n    // Rebuild the file and subcontainer keys\n    pFilesKey = stringify(mCont.id()) + constants::sMapFilesSuffix;\n    pDirsKey = stringify(mCont.id()) + constants::sMapDirsSuffix;\n  });\n}\n\n//------------------------------------------------------------------------------\n// Initialize from a ContainerMdProto object, without loading children maps.\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::initializeWithoutChildren(eos::ns::ContainerMdProto&& proto)\n{\n  runWriteOp([this, Proto = std::move(proto)]() {\n    mCont = std::move(Proto);\n  });\n}\n\n//------------------------------------------------------------------------------\n// Get map copy of the extended attributes\n//------------------------------------------------------------------------------\neos::IFileMD::XAttrMap\nQuarkContainerMD::getAttributes() const\n{\n  return runReadOp([this]() {\n    XAttrMap xattrs;\n\n    for (const auto& elem : mCont.xattrs()) {\n      xattrs.insert(elem);\n    }\n\n    return xattrs;\n  });\n}\n\n//------------------------------------------------------------------------------\n// Get env representation of the container object\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMD::getEnv(std::string& env, bool escapeAnd)\n{\n  runReadOp([this, &env, escapeAnd]() {\n    env.clear();\n    std::ostringstream oss;\n    std::string saveName = mCont.name();\n\n    if (escapeAnd) {\n      if (!saveName.empty()) {\n        saveName = eos::common::StringConversion::SealXrdPath(saveName);\n      }\n    }\n\n    ctime_t ctime;\n    ctime_t mtime;\n    ctime_t stime;\n    (void) getCTimeNoLock(ctime);\n    (void) getMTimeNoLock(mtime);\n    (void) getTMTimeNoLock(stime);\n    oss << \"name=\" << saveName\n        << \"&id=\" << mCont.id()\n        << \"&uid=\" << mCont.uid() << \"&gid=\" << mCont.gid()\n        << \"&parentid=\" << mCont.parent_id()\n        << \"&mode=\" << std::oct << mCont.mode() << std::dec\n        << \"&flags=\" << std::oct << mCont.flags() << std::dec\n        << \"&treesize=\" << mCont.tree_size()\n        << \"&ctime=\" << ctime.tv_sec << \"&ctime_ns=\" << ctime.tv_nsec\n        << \"&mtime=\" << mtime.tv_sec << \"&mtime_ns=\" << mtime.tv_nsec\n        << \"&stime=\" << stime.tv_sec << \"&stime_ns=\" << stime.tv_nsec;\n\n    for (const auto& elem : mCont.xattrs()) {\n      oss << \"&\" << elem.first << \"=\" << elem.second;\n    }\n\n    env += oss.str();\n  });\n}\n\n//------------------------------------------------------------------------------\n// Get a copy of ContainerMap\n//------------------------------------------------------------------------------\nIContainerMD::ContainerMap\nQuarkContainerMD::copyContainerMap() const\n{\n  IContainerMD::ContainerMap retval;\n  retval.set_deleted_key(\"\");\n  retval.set_empty_key(\"##_EMPTY_##\");\n  return runReadOp([this, &retval]() {\n    for (auto it = mSubcontainers->begin(); it != mSubcontainers->end(); ++it) {\n      retval.insert(std::make_pair(it->first, it->second));\n    }\n\n    return retval;\n  });\n}\n\n//------------------------------------------------------------------------------\n// Get a copy of FileMap\n//------------------------------------------------------------------------------\nIContainerMD::FileMap\nQuarkContainerMD::copyFileMap() const\n{\n  IContainerMD::FileMap retval;\n  retval.set_deleted_key(\"\");\n  retval.set_empty_key(\"##_EMPTY_##\");\n  return runReadOp([this, &retval]() {\n    for (auto it = mFiles->begin(); it != mFiles->end(); ++it) {\n      retval.insert(std::make_pair(it->first, it->second));\n    }\n\n    return retval;\n  });\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/ContainerMD.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Class representing the container metadata\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_CONTAINER_MD_HH\n#define EOS_NS_CONTAINER_MD_HH\n\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n#include \"proto/ContainerMd.pb.h\"\n#include \"common/FutureWrapper.hh\"\n#include <sys/time.h>\n#include <cstdint>\n\n#define FRIEND_TEST(test_case_name, test_name)\\\nfriend class test_case_name##_##test_name##_Test\n\nEOSNSNAMESPACE_BEGIN\n\nclass IContainerMDSvc;\nclass IFileMDSvc;\n\n//------------------------------------------------------------------------------\n//! Class holding the metadata information concerning a single container\n//------------------------------------------------------------------------------\nclass QuarkContainerMD : public IContainerMD\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  QuarkContainerMD(IContainerMD::id_t id, IFileMDSvc* file_svc,\n                   IContainerMDSvc* cont_svc);\n\n  //----------------------------------------------------------------------------\n  //! Constructor used for testing and dump command\n  //----------------------------------------------------------------------------\n  QuarkContainerMD(): pContSvc(nullptr), pFileSvc(nullptr), pFlusher(nullptr),\n    pQcl(nullptr), mClock(1) {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~QuarkContainerMD() {};\n\n  //----------------------------------------------------------------------------\n  //! Copy constructor\n  //----------------------------------------------------------------------------\n  QuarkContainerMD(const QuarkContainerMD& other);\n\n  //----------------------------------------------------------------------------\n  //! Virtual copy constructor\n  //----------------------------------------------------------------------------\n  virtual QuarkContainerMD* clone() const override;\n\n  //----------------------------------------------------------------------------\n  //! Virtual copy constructor\n  //----------------------------------------------------------------------------\n  void InheritChildren(const IContainerMD& other) override;\n\n  //----------------------------------------------------------------------------\n  //! Set services\n  //----------------------------------------------------------------------------\n  void setServices(IFileMDSvc* file_svc, IContainerMDSvc* cont_svc);\n\n  //----------------------------------------------------------------------------\n  //! Add container\n  //----------------------------------------------------------------------------\n  void addContainer(IContainerMD* container) override;\n\n  //----------------------------------------------------------------------------\n  //! Remove container\n  //----------------------------------------------------------------------------\n  void removeContainer(const std::string& name) override;\n\n  //----------------------------------------------------------------------------\n  //! Find subcontainer, asynchronous API\n  //----------------------------------------------------------------------------\n  folly::Future<IContainerMDPtr> findContainerFut(const std::string& name)\n  override;\n\n  //----------------------------------------------------------------------------\n  //! Find subcontainer\n  //----------------------------------------------------------------------------\n  std::shared_ptr<IContainerMD> findContainer(const std::string& name) override;\n\n  //----------------------------------------------------------------------------\n  //! Find item\n  //----------------------------------------------------------------------------\n  folly::Future<FileOrContainerMD> findItem(const std::string& name) override;\n\n  //----------------------------------------------------------------------------\n  //! Get number of containers\n  //----------------------------------------------------------------------------\n  size_t getNumContainers() override;\n\n  //----------------------------------------------------------------------------\n  //! Add file\n  //----------------------------------------------------------------------------\n  void addFile(IFileMD* file) override;\n\n  //----------------------------------------------------------------------------\n  //! Remove file\n  //----------------------------------------------------------------------------\n  void removeFile(const std::string& name) override;\n\n  //----------------------------------------------------------------------------\n  //! Find file, asynchronous API.\n  //----------------------------------------------------------------------------\n  folly::Future<IFileMDPtr> findFileFut(const std::string& name) override;\n\n  //----------------------------------------------------------------------------\n  //! Find file\n  //----------------------------------------------------------------------------\n  IFileMDPtr findFile(const std::string& name) override;\n\n  //----------------------------------------------------------------------------\n  //! Get number of files\n  //----------------------------------------------------------------------------\n  size_t getNumFiles() override;\n\n  //----------------------------------------------------------------------------\n  //! Get container id\n  //----------------------------------------------------------------------------\n  inline IContainerMD::id_t getId() const override\n  {\n    return runReadOp([this](){\n      return mCont.id();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get container identifier\n  //----------------------------------------------------------------------------\n  inline identifier_t\n  getIdentifier() const override\n  {\n    return identifier_t(getId());\n  }\n\n\n  //----------------------------------------------------------------------------\n  //! Get parent id\n  //----------------------------------------------------------------------------\n  inline IContainerMD::id_t\n  getParentId() const override\n  {\n    return runReadOp([this](){\n      return mCont.parent_id();\n    });\n\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set parent id\n  //----------------------------------------------------------------------------\n  void\n  setParentId(IContainerMD::id_t parentId) override\n  {\n    runWriteOp([this,parentId](){\n      mCont.set_parent_id(parentId);\n    });\n\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the flags\n  //----------------------------------------------------------------------------\n  inline uint16_t\n  getFlags() const override\n  {\n    return runReadOp([this](){\n      return mCont.flags();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set flags\n  //----------------------------------------------------------------------------\n  virtual void setFlags(uint16_t flags) override\n  {\n    runWriteOp([this,flags](){\n      mCont.set_flags(0x00ff & flags);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set creation time\n  //----------------------------------------------------------------------------\n  void setCTime(ctime_t ctime) override;\n\n  //----------------------------------------------------------------------------\n  //! Set creation time to now\n  //----------------------------------------------------------------------------\n  void setCTimeNow() override;\n\n  //----------------------------------------------------------------------------\n  //! Get creation time\n  //----------------------------------------------------------------------------\n  void getCTime(ctime_t& ctime) const override;\n\n  //----------------------------------------------------------------------------\n  //! Set creation time\n  //----------------------------------------------------------------------------\n  void setMTime(mtime_t mtime) override;\n\n  //----------------------------------------------------------------------------\n  //! Set creation time to now\n  //----------------------------------------------------------------------------\n  void setMTimeNow() override;\n\n  //----------------------------------------------------------------------------\n  //! Get modification time\n  //----------------------------------------------------------------------------\n  void getMTime(mtime_t& mtime) const override;\n\n  //----------------------------------------------------------------------------\n  //! Set propagated modification time (if newer)\n  //----------------------------------------------------------------------------\n  bool setTMTime(tmtime_t tmtime) override;\n\n  //----------------------------------------------------------------------------\n  //! Set propagated modification time to now\n  //----------------------------------------------------------------------------\n  void setTMTimeNow() override;\n\n  //----------------------------------------------------------------------------\n  //! Get propagated modification time\n  //----------------------------------------------------------------------------\n  void getTMTime(tmtime_t& tmtime) override;\n\n  //----------------------------------------------------------------------------\n  //! Trigger an mtime change event\n  //----------------------------------------------------------------------------\n  void notifyMTimeChange(IContainerMDSvc* containerMDSvc) override;\n\n  //----------------------------------------------------------------------------\n  //! Get tree size\n  //----------------------------------------------------------------------------\n  inline uint64_t\n  getTreeSize() const override\n  {\n    return runReadOp([this](){\n      return mCont.tree_size();\n    });\n\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set tree size\n  //----------------------------------------------------------------------------\n  inline void\n  setTreeSize(uint64_t treesize) override\n  {\n    runWriteOp([this,treesize](){\n      mCont.set_tree_size(treesize);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Update to tree size\n  //----------------------------------------------------------------------------\n  uint64_t updateTreeSize(int64_t delta) override;\n\n  //----------------------------------------------------------------------------\n  //! Update tree containers\n  //----------------------------------------------------------------------------\n  uint64_t updateTreeContainers(int64_t delta) override;\n\n  //----------------------------------------------------------------------------\n  //! Update tree files\n  //----------------------------------------------------------------------------\n  uint64_t updateTreeFiles(int64_t delta) override;\n\n  //----------------------------------------------------------------------------\n  //! Set tree containers\n  //----------------------------------------------------------------------------\n  inline void setTreeContainers(uint64_t treeContainers) override {\n    runWriteOp([this,treeContainers](){\n      mCont.set_tree_containers(treeContainers);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get tree containers\n  //----------------------------------------------------------------------------\n  inline uint64_t getTreeContainers() const override {\n    return runReadOp([this](){\n      return mCont.tree_containers();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set file containers\n  //----------------------------------------------------------------------------\n  inline void setTreeFiles(uint64_t treeFiles) override {\n    runWriteOp([this,treeFiles](){\n      mCont.set_tree_files(treeFiles);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get file containers\n  //----------------------------------------------------------------------------\n  inline uint64_t getTreeFiles() const override {\n    return runReadOp([this](){\n      return mCont.tree_files();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get name\n  //----------------------------------------------------------------------------\n  inline const std::string&\n  getName() const override\n  {\n    //Specify the return type of the lambda to be a const string ref otherwise\n    //we will return a reference to the copy of the string returned by the lambda... (dangling reference when used)\n    return runReadOp([this]() -> const std::string & {\n      return mCont.name();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set name\n  //----------------------------------------------------------------------------\n  void setName(const std::string& name) override;\n\n  //----------------------------------------------------------------------------\n  //! Get uid\n  //----------------------------------------------------------------------------\n  inline uid_t\n  getCUid() const override\n  {\n    return runReadOp([this](){\n      return mCont.uid();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set uid\n  //----------------------------------------------------------------------------\n  inline void\n  setCUid(uid_t uid) override\n  {\n    runWriteOp([this,uid](){\n      mCont.set_uid(uid);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get gid\n  //----------------------------------------------------------------------------\n  inline gid_t\n  getCGid() const override\n  {\n    return runReadOp([this](){\n      return mCont.gid();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set gid\n  //----------------------------------------------------------------------------\n  inline void\n  setCGid(gid_t gid) override\n  {\n    runWriteOp([this,gid](){\n      mCont.set_gid(gid);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get cloneId\n  //----------------------------------------------------------------------------\n  inline time_t\n  getCloneId() const override\n  {\n    return runReadOp([this](){\n      return mCont.cloneid();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set cloneId\n  //----------------------------------------------------------------------------\n  inline void\n  setCloneId(time_t id) override\n  {\n    runWriteOp([this,id](){\n      mCont.set_cloneid(id);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get cloneFST\n  //----------------------------------------------------------------------------\n  inline const std::string\n  getCloneFST() const override\n  {\n    return runReadOp([this](){\n      return mCont.clonefst();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set cloneFST\n  //----------------------------------------------------------------------------\n  void setCloneFST(const std::string& data) override\n  {\n    runWriteOp([this,data](){\n      mCont.set_clonefst(data);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get mode\n  //----------------------------------------------------------------------------\n  inline mode_t\n  getMode() const override\n  {\n    return runReadOp([this](){\n      return mCont.mode();\n    });\n\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set mode\n  //----------------------------------------------------------------------------\n  inline void\n  setMode(mode_t mode) override\n  {\n    runWriteOp([this,mode](){\n      mCont.set_mode(mode);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Add extended attribute\n  //----------------------------------------------------------------------------\n  void\n  setAttribute(const std::string& name, const std::string& value) override\n  {\n    runWriteOp([this,name,value](){\n      (*mCont.mutable_xattrs())[name] = value;\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Remove attribute\n  //----------------------------------------------------------------------------\n  void removeAttribute(const std::string& name) override;\n\n  //----------------------------------------------------------------------------\n  //! Check if the attribute exist\n  //----------------------------------------------------------------------------\n  bool\n  hasAttribute(const std::string& name) const override\n  {\n    return runReadOp([this,name](){\n      return (mCont.xattrs().find(name) != mCont.xattrs().end());\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return number of attributes\n  //----------------------------------------------------------------------------\n  size_t\n  numAttributes() const override\n  {\n    return runReadOp([this](){\n      return mCont.xattrs().size();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  // Get the attribute\n  //----------------------------------------------------------------------------\n  std::string getAttribute(const std::string& name) const override;\n\n  //----------------------------------------------------------------------------\n  //! Get map copy of the extended attributes\n  //!\n  //! @return std::map containing all the extended attributes\n  //----------------------------------------------------------------------------\n  XAttrMap getAttributes() const override;\n\n  //------------------------------------------------------------------------------\n  //! Check the access permissions\n  //!\n  //! @return true if all the requested rights are granted, false otherwise\n  //------------------------------------------------------------------------------\n  bool access(uid_t uid, gid_t gid, int flags = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Serialize the object to a buffer\n  //----------------------------------------------------------------------------\n  void serialize(Buffer& buffer) override;\n\n  //----------------------------------------------------------------------------\n  //! Load children of container\n  //----------------------------------------------------------------------------\n  void loadChildren();\n\n  //----------------------------------------------------------------------------\n  //! Deserialize the class to a buffer and load its children\n  //----------------------------------------------------------------------------\n  void deserialize(Buffer& buffer) override;\n\n  //----------------------------------------------------------------------------\n  //! Initialize, and inject children\n  //----------------------------------------------------------------------------\n  void initialize(eos::ns::ContainerMdProto&& proto,\n                  IContainerMD::FileMap&& fileMap, IContainerMD::ContainerMap&& containerMap);\n\n  //----------------------------------------------------------------------------\n  //! Initialize, without loading children\n  //----------------------------------------------------------------------------\n  void initializeWithoutChildren(eos::ns::ContainerMdProto&& proto);\n\n  //----------------------------------------------------------------------------\n  //! Get value tracking changes to the metadata object\n  //----------------------------------------------------------------------------\n  virtual uint64_t getClock() const override\n  {\n    return runReadOp([this](){\n      return mClock;\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get env representation of the container object\n  //!\n  //! @param env string where representation is stored\n  //! @param escapeAnd if true escape & with #AND# ...\n  //----------------------------------------------------------------------------\n  void getEnv(std::string& env, bool escapeAnd = false) override;\n\n  //----------------------------------------------------------------------------\n  //! Get a copy of ContainerMap\n  //----------------------------------------------------------------------------\n  IContainerMD::ContainerMap copyContainerMap() const override;\n\n  //----------------------------------------------------------------------------\n  //! Get a copy of FileMap\n  //----------------------------------------------------------------------------\n  IContainerMD::FileMap copyFileMap() const override;\n\nprivate:\n  FRIEND_TEST(VariousTests, EtagFormattingContainer);\n\n  //----------------------------------------------------------------------------\n  //! Get propagated modification time, no locks\n  //----------------------------------------------------------------------------\n  void getTMTimeNoLock(tmtime_t& tmtime);\n\n  //----------------------------------------------------------------------------\n  //! Get creation time, no locks\n  //----------------------------------------------------------------------------\n  void getCTimeNoLock(ctime_t& ctime) const;\n\n  //----------------------------------------------------------------------------\n  //! Get modification time, no locks\n  //----------------------------------------------------------------------------\n  void getMTimeNoLock(mtime_t& mtime) const;\n\n  //----------------------------------------------------------------------------\n  //! Get iterator to the begining of the subcontainers map\n  //----------------------------------------------------------------------------\n  eos::IContainerMD::ContainerMap::const_iterator\n  subcontainersBegin() override\n  {\n    // No lock here, only ContainerMapIterator can call us, which locks the mutex.\n    return mSubcontainers->begin();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get iterator to the end of the subcontainers map\n  //----------------------------------------------------------------------------\n  virtual eos::IContainerMD::ContainerMap::const_iterator\n  subcontainersEnd() override\n  {\n    // No lock here, only ContainerMapIterator can call us, which locks the mutex.\n    return mSubcontainers->end();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get generation value to check iterator validity\n  //----------------------------------------------------------------------------\n  virtual uint64_t getContainerMapGeneration() override\n  {\n    const uint64_t bc = mSubcontainers->bucket_count();\n    return reinterpret_cast<std::uintptr_t>(&*mSubcontainers->end()) ^\n           ((bc << 48) | (bc >> 16));\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get iterator to the begining of the files map\n  //----------------------------------------------------------------------------\n  virtual eos::IContainerMD::FileMap::const_iterator\n  filesBegin() override\n  {\n    // No lock here, only FileMapIterator can call us, which locks the mutex.\n    return mFiles->begin();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get iterator to the end of the files map\n  //----------------------------------------------------------------------------\n  virtual eos::IContainerMD::FileMap::const_iterator\n  filesEnd() override\n  {\n    // No lock here, only FileMapIterator can call us, which locks the mutex.\n    return mFiles->end();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get generation value to check iterator validity\n  //----------------------------------------------------------------------------\n  virtual uint64_t getFileMapGeneration() override\n  {\n    const uint64_t bc = mFiles->bucket_count();\n    return reinterpret_cast<std::uintptr_t>(&*mFiles->end()) ^\n           ((bc << 48) | (bc >> 16));\n  }\n\n  eos::ns::ContainerMdProto mCont;      ///< Protobuf container representation\n  IContainerMDSvc* pContSvc = nullptr;  ///< Container metadata service\n  IFileMDSvc* pFileSvc = nullptr;       ///< File metadata service\n  MetadataFlusher* pFlusher = nullptr;  ///< Metadata flusher object\n  qclient::QClient* pQcl;               ///< QClient object\n  std::string pFilesKey;                ///< Map files key\n  std::string pDirsKey;                 ///< Map dir key\n  uint64_t mClock;                      ///< Value tracking changes\n\n  common::FutureWrapper<ContainerMap> mSubcontainers;\n  common::FutureWrapper<FileMap> mFiles;\n};\n\nEOSNSNAMESPACE_END\n\n#endif // __EOS_NS_CONTAINER_MD_HH__\n"
  },
  {
    "path": "namespace/ns_quarkdb/FileMD.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <sstream>\n#include <chrono>\n#include \"common/Logging.hh\"\n#include \"common/StacktraceHere.hh\"\n#include \"common/StringConversion.hh\"\n#include \"namespace/ns_quarkdb/FileMD.hh\"\n#include \"namespace/ns_quarkdb/persistency/Serialization.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"namespace/utils/DataHelper.hh\"\n#include \"namespace/utils/Checksum.hh\"\n#include \"google/protobuf/io/zero_copy_stream_impl.h\"\n#include \"google/protobuf/io/zero_copy_stream_impl_lite.h\"\n#include <optional>\n\n#define DBG(message) std::cerr << __FILE__ << \":\" << __LINE__ << \" -- \" << #message << \" = \" << message << std::endl\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Empty constructor\n//------------------------------------------------------------------------------\nQuarkFileMD::QuarkFileMD()\n{\n  pFileMDSvc = nullptr;\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nQuarkFileMD::QuarkFileMD(IFileMD::id_t id, IFileMDSvc* fileMDSvc):\n  pFileMDSvc(fileMDSvc)\n{\n  mFile.set_id(id);\n  mClock = std::chrono::high_resolution_clock::now().time_since_epoch().count();\n}\n\n//------------------------------------------------------------------------------\n// Virtual copy constructor\n//------------------------------------------------------------------------------\nQuarkFileMD*\nQuarkFileMD::clone() const\n{\n  return runWriteOp([this]() {\n    return new QuarkFileMD(*this);\n  });\n}\n\n//------------------------------------------------------------------------------\n// Copy constructor\n//------------------------------------------------------------------------------\nQuarkFileMD::QuarkFileMD(const QuarkFileMD& other)\n{\n  *this = other;\n}\n\n//------------------------------------------------------------------------------\n// Assignment operator\n//------------------------------------------------------------------------------\nQuarkFileMD&\nQuarkFileMD::operator = (const QuarkFileMD& other)\n{\n  return runWriteOp([this, &other]() -> QuarkFileMD& {\n    mFile = other.mFile;\n    mClock = other.mClock;\n    pFileMDSvc = 0;\n    return *this;\n  });\n}\n\n//------------------------------------------------------------------------------\n// Set name\n//------------------------------------------------------------------------------\nvoid QuarkFileMD::setName(const std::string& name)\n{\n  if (name.find('/') != std::string::npos) {\n    eos_static_crit(\"Detected slashes in filename: %s\",\n                    eos::common::getStacktrace().c_str());\n    throw_mdexception(EINVAL, \"Bug, detected slashes in file name: \" << name);\n  }\n\n  runWriteOp([this, &name]() {\n    mFile.set_name(name);\n  });\n}\n\n//------------------------------------------------------------------------------\n// Add location\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::addLocation(location_t location)\n{\n  this->runWriteOp([this, location]() {\n    if (hasLocationNoLock(location)) {\n      return;\n    }\n\n    mFile.add_locations(location);\n  });\n  IFileMDChangeListener::Event e(this, IFileMDChangeListener::LocationAdded,\n                                 location);\n  pFileMDSvc->notifyListeners(&e);\n}\n\n//------------------------------------------------------------------------------\n// Remove location that was previously unlinked\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::removeLocation(location_t location)\n{\n  bool locationRemoved = false;\n  {\n    this->runWriteOp([this, &locationRemoved, location]() {\n      for (auto it = mFile.mutable_unlink_locations()->cbegin();\n           it != mFile.mutable_unlink_locations()->cend(); ++it) {\n        if (*it == location) {\n          it = mFile.mutable_unlink_locations()->erase(it);\n          locationRemoved = true;\n          break;\n        }\n      }\n    });\n  }\n\n  if (locationRemoved) {\n    IFileMDChangeListener::Event\n    e(this, IFileMDChangeListener::LocationRemoved, location);\n    pFileMDSvc->notifyListeners(&e);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Remove all locations that were previously unlinked\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::removeAllLocations()\n{\n  bool stop = false;\n\n  while (!stop) {\n    std::optional<location_t> location;\n    {\n      stop = runReadOp([this, &location]() {\n        auto it = mFile.unlink_locations().cbegin();\n\n        if (it == mFile.unlink_locations().cend()) {\n          return true;\n        }\n\n        location = *it;\n        return false;\n      });\n    }\n\n    if (location) {\n      removeLocation(*location);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Unlink location\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::unlinkLocation(location_t location)\n{\n  {\n    this->runWriteOp([this, location]() {\n      for (auto it = mFile.mutable_locations()->cbegin();\n           it != mFile.mutable_locations()->cend(); ++it) {\n        if (*it == location) {\n          // If location is already unlink, skip adding it\n          if (!hasUnlinkedLocationNoLock(location)) {\n            mFile.add_unlink_locations(*it);\n          }\n\n          it = mFile.mutable_locations()->erase(it);\n          break;\n        }\n      }\n    });\n  }\n  IFileMDChangeListener::Event\n  e(this, IFileMDChangeListener::LocationUnlinked, location);\n  pFileMDSvc->notifyListeners(&e);\n}\n\n//------------------------------------------------------------------------------\n// Unlink all locations\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::unlinkAllLocations()\n{\n  while (true) {\n    std::optional<location_t> location;\n    this->runWriteOp([this, &location]() {\n      auto it = mFile.locations().cbegin();\n\n      if (it == mFile.locations().cend()) {\n        return;\n      }\n\n      location = *it;\n    });\n\n    if (location) {\n      unlinkLocation(*location);\n    } else {\n      return;\n    }\n  }\n}\n\n//------------------------------------------------------------------------\n//  Env Representation\n//------------------------------------------------------------------------\nvoid\nQuarkFileMD::getEnv(std::string& env, bool escapeAnd)\n{\n  runReadOp([this, &env, escapeAnd] {\n    env = \"\";\n    std::ostringstream oss;\n    std::string saveName = mFile.name();\n\n    if (escapeAnd)\n    {\n      if (!saveName.empty()) {\n        saveName = eos::common::StringConversion::SealXrdPath(saveName);\n      }\n    }\n\n    ctime_t ctime;\n    ctime_t mtime;\n    (void)getCTimeNoLock(ctime);\n    (void)getMTimeNoLock(mtime);\n    oss << \"name=\" << saveName << \"&id=\" << mFile.id()\n        << \"&ctime=\" << ctime.tv_sec << \"&ctime_ns=\" << ctime.tv_nsec\n        << \"&mtime=\" << mtime.tv_sec << \"&mtime_ns=\" << mtime.tv_nsec\n        << \"&size=\" << mFile.size() << \"&cid=\" << mFile.cont_id()\n        << \"&uid=\" << mFile.uid() << \"&gid=\" << mFile.gid()\n        << \"&lid=\" << mFile.layout_id() << \"&flags=\" << mFile.flags()\n        << \"&link=\" << mFile.link_name();\n    env += oss.str();\n    env += \"&location=\";\n    char locs[16];\n\n    for (const auto& elem : mFile.locations())\n    {\n      snprintf(static_cast<char*>(locs), sizeof(locs), \"%u\", elem);\n      env += static_cast<char*>(locs);\n      env += \",\";\n    }\n\n    for (const auto& elem : mFile.unlink_locations())\n    {\n      snprintf(static_cast<char*>(locs), sizeof(locs), \"!%u\", elem);\n      env += static_cast<char*>(locs);\n      env += \",\";\n    }\n\n    env += \"&checksum=\";\n    eos::appendChecksumOnStringProtobuf(mFile, env);\n  });\n}\n\n//------------------------------------------------------------------------------\n// Serialize the object to a std::string buffer\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::serialize(eos::Buffer& buffer)\n{\n  runReadOp([this, &buffer]() {\n    // Increase clock to mark that metadata file has suffered updates\n    mClock =\n      std::chrono::high_resolution_clock::now().time_since_epoch().count();\n    // Align the buffer to 4 bytes to efficiently compute the checksum\n#if GOOGLE_PROTOBUF_VERSION < 3004000\n    size_t obj_size = mFile.ByteSize();\n#else\n    size_t obj_size = mFile.ByteSizeLong();\n#endif\n    uint32_t align_size = (obj_size + 3) >> 2 << 2;\n    size_t sz = sizeof(align_size);\n    size_t msg_size = align_size + 2 * sz;\n    buffer.setSize(msg_size);\n    // Write the checksum value, size of the raw protobuf object and then the\n    // actual protobuf object serialized\n    const char* ptr = buffer.getDataPtr() + 2 * sz;\n    google::protobuf::io::ArrayOutputStream aos((void*)ptr, align_size);\n\n    if (!mFile.SerializeToZeroCopyStream(&aos)) {\n      MDException ex(EIO);\n      ex.getMessage() << \"Failed while serializing buffer\";\n      throw ex;\n    }\n\n    // Compute the CRC32C checksum\n    uint32_t cksum = DataHelper::computeCRC32C((void*)ptr, align_size);\n    cksum = DataHelper::finalizeCRC32C(cksum);\n    // Point to the beginning to fill in the checksum and size of useful data\n    ptr = buffer.getDataPtr();\n    (void)memcpy((void*)ptr, &cksum, sz);\n    ptr += sz;\n    (void)memcpy((void*)ptr, &obj_size, sz);\n  });\n}\n\n//------------------------------------------------------------------------------\n// Initialize from protobuf contents\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::initialize(eos::ns::FileMdProto&& proto)\n{\n  runWriteOp(\n    [this, prot = std::move(proto)]() mutable { mFile = std::move(prot); });\n}\n\n//------------------------------------------------------------------------------\n// Deserialize from buffer\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::deserialize(const eos::Buffer& buffer)\n{\n  runWriteOp(\n  [this, &buffer]() {\n    Serialization::deserializeFile(buffer, mFile);\n  });\n}\n\n//----------------------------------------------------------------------------\n// Get reference to underlying protobuf object\n//----------------------------------------------------------------------------\nconst eos::ns::FileMdProto&\nQuarkFileMD::getProto() const\n{\n  return mFile;\n}\n\n//------------------------------------------------------------------------------\n// Set size - 48 bytes will be used\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::setSize(uint64_t size)\n{\n  int64_t sizeChange = 0;\n  this->runWriteOp([this, size, &sizeChange]() {\n    sizeChange = (size & 0x0000ffffffffffff) - mFile.size();\n    mFile.set_size(size & 0x0000ffffffffffff);\n  });\n  IFileMDChangeListener::Event e(this, IFileMDChangeListener::SizeChange, 0,\n  {sizeChange, 0, 0});\n  pFileMDSvc->notifyListeners(&e);\n}\n\n//------------------------------------------------------------------------------\n// Get creation time, no lock\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::getCTimeNoLock(ctime_t& ctime) const\n{\n  if (mFile.ctime().length() == sizeof(ctime_t)) {\n    (void) memcpy(&ctime, mFile.ctime().data(), sizeof(ctime_t));\n  } else {\n    (void) memset(&ctime, 0, sizeof(ctime_t));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get creation time\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::getCTime(ctime_t& ctime) const\n{\n  return runReadOp([this, &ctime]() {\n    return getCTimeNoLock(ctime);\n  });\n}\n\n//------------------------------------------------------------------------------\n// Set creation time\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::setCTime(ctime_t ctime)\n{\n  runWriteOp([this, ctime]() {\n    mFile.set_ctime(&ctime, sizeof(ctime));\n  });\n}\n\n//----------------------------------------------------------------------------\n// Set creation time to now\n//----------------------------------------------------------------------------\nvoid\nQuarkFileMD::setCTimeNow()\n{\n  struct timespec tnow;\n#ifdef __APPLE__\n  struct timeval tv;\n  gettimeofday(&tv, 0);\n  tnow.tv_sec = tv.tv_sec;\n  tnow.tv_nsec = tv.tv_usec * 1000;\n#else\n  clock_gettime(CLOCK_REALTIME, &tnow);\n#endif\n  setCTime(tnow);\n}\n\n//------------------------------------------------------------------------------\n// Get modification time, no locks\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::getMTimeNoLock(ctime_t& mtime) const\n{\n  if (mFile.mtime().length() == sizeof(ctime_t)) {\n    (void) memcpy(&mtime, mFile.mtime().data(), sizeof(ctime_t));\n  } else {\n    (void) memset(&mtime, 0, sizeof(ctime_t));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get modification time\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::getMTime(ctime_t& mtime) const\n{\n  return runReadOp([this, &mtime]() {\n    return getMTimeNoLock(mtime);\n  });\n}\n\n//------------------------------------------------------------------------------\n// Set modification time\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::setMTime(ctime_t mtime)\n{\n  runWriteOp([this, mtime]() {\n    mFile.set_mtime(&mtime, sizeof(mtime));\n  });\n}\n\n//------------------------------------------------------------------------------\n// Set modification time to now\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::setMTimeNow()\n{\n  struct timespec tnow;\n#ifdef __APPLE__\n  struct timeval tv;\n  gettimeofday(&tv, 0);\n  tnow.tv_sec = tv.tv_sec;\n  tnow.tv_nsec = tv.tv_usec * 1000;\n#else\n  clock_gettime(CLOCK_REALTIME, &tnow);\n#endif\n  setMTime(tnow);\n  struct timespec default_ts = {0, 0};\n  setSyncTime(default_ts);\n}\n\n//------------------------------------------------------------------------------\n// Get access time, no locks\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::getATimeNoLock(ctime_t& atime) const\n{\n  if (mFile.atime().length() == sizeof(ctime_t)) {\n    (void) memcpy(&atime, mFile.atime().data(), sizeof(ctime_t));\n  } else {\n    (void) memset(&atime, 0, sizeof(ctime_t));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get access time\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::getATime(ctime_t& atime) const\n{\n  runReadOp([this, &atime] {\n    getATimeNoLock(atime);\n  });\n}\n\n//------------------------------------------------------------------------------\n// Set access time\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::setATime(ctime_t atime)\n{\n  runWriteOp([this, atime]() {\n    mFile.set_atime(&atime, sizeof(atime));\n  });\n}\n\n//------------------------------------------------------------------------------\n// Set access time to now\n//------------------------------------------------------------------------------\n\nbool\nQuarkFileMD::setATimeNow(uint64_t olderthan)\n{\n  struct timespec tnow;\n  struct timespec atime;\n#ifdef __APPLE__\n  struct timeval tv;\n  gettimeofday(&tv, 0);\n  tnow.tv_sec = tv.tv_sec;\n  tnow.tv_nsec = tv.tv_usec * 1000;\n#else\n  clock_gettime(CLOCK_REALTIME, &tnow);\n#endif\n  getATime(atime);\n\n  // only set the atime if it is older than olderthan\n  if (((tnow.tv_sec - atime.tv_sec) >= (time_t) olderthan)) {\n    setATime(tnow);\n    return true;\n  } else {\n    return false;\n  }\n}\n\n\n/* SyncTime: whenever the file is changed, SyncTime becomes MTime. It is only\n * when mtime is set explicitely that the two diverge. Hence. the logic\n * here is that if SyncTime is 0, use MTime. And reset SyncTime to\n * zero when MTime is set to now (thus logically setting them both).\n */\n\n//------------------------------------------------------------------------------\n// Get sync time, no lock\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::getSyncTimeNoLock(ctime_t& stime) const\n{\n  (void) memcpy(&stime, mFile.stime().data(), sizeof(stime));\n\n  if (stime.tv_sec == 0) {  /* fall back to mtime if default */\n    (void) memcpy(&stime, mFile.mtime().data(), sizeof(stime));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get sync time\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::getSyncTime(ctime_t& stime) const\n{\n  runReadOp([this, &stime]() {\n    getSyncTimeNoLock(stime);\n  });\n}\n\n//------------------------------------------------------------------------------\n// Set sync time\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::setSyncTime(ctime_t stime)\n{\n  runWriteOp([this, stime]() {\n    mFile.set_stime(&stime, sizeof(stime));\n  });\n}\n\n//------------------------------------------------------------------------------\n// Set sync time to now\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMD::setSyncTimeNow()\n{\n  struct timespec tnow;\n  clock_gettime(CLOCK_REALTIME, &tnow);\n  setSyncTime(tnow);\n}\n\n//------------------------------------------------------------------------------\n// Get map copy of the extended attributes\n//------------------------------------------------------------------------------\neos::IFileMD::XAttrMap\nQuarkFileMD::getAttributes() const\n{\n  return runReadOp([this]() {\n    std::map<std::string, std::string> xattrs;\n\n    for (const auto& elem : mFile.xattrs()) {\n      xattrs.insert(elem);\n    }\n\n    return xattrs;\n  });\n}\n\n//------------------------------------------------------------------------------\n// Test the unlinked location\n//------------------------------------------------------------------------------\nbool QuarkFileMD::hasUnlinkedLocation(IFileMD::location_t location)\n{\n  return this->runReadOp(\n  [this, location]() {\n    return hasUnlinkedLocationNoLock(location);\n  });\n}\n\n//------------------------------------------------------------------------------\n// Test the unlinked location, no locks\n//------------------------------------------------------------------------------\nbool QuarkFileMD::hasUnlinkedLocationNoLock(location_t location) const\n{\n  for (int i = 0; i < mFile.unlink_locations_size(); ++i) {\n    if (mFile.unlink_locations()[i] == location) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nvoid QuarkFileMD::addAltXs(eos::common::LayoutId::eChecksum\n                           checksumType, const char* checksum, size_t size)\n{\n  runWriteOp([this, checksumType, checksum, size]() {\n    mFile.mutable_altchecksums()->insert({static_cast<std::uint32_t>(checksumType),\n                                          std::string(checksum, size)});\n  });\n}\n\nvoid QuarkFileMD::clearAltXs()\n{\n  runWriteOp([this]() {\n    mFile.mutable_altchecksums()->clear();\n  });\n}\n\nstd::map<eos::common::LayoutId::eChecksum, std::string>\nQuarkFileMD::getAltXs() const\n{\n  return this->runReadOp([this]() {\n    std::map<eos::common::LayoutId::eChecksum, std::string> checksums;\n\n    for (auto [type, val] : mFile.altchecksums()) {\n      checksums.insert({static_cast<eos::common::LayoutId::eChecksum>(type), val});\n    }\n\n    return checksums;\n  });\n}\n\nvoid QuarkFileMD::removeAltXs(eos::common::LayoutId::eChecksum type)\n{\n  runWriteOp([this, type]() {\n    mFile.mutable_altchecksums()->erase(type);\n  });\n}\n\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/FileMD.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Class representing the file metadata\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_FILE_MD_HH\n#define EOS_NS_FILE_MD_HH\n\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/ns_quarkdb/persistency/FileMDSvc.hh\"\n#include \"proto/FileMd.pb.h\"\n#include <cstdint>\n#include <sys/time.h>\n\n#define FRIEND_TEST(test_case_name, test_name)\\\nfriend class test_case_name##_##test_name##_Test\n\nEOSNSNAMESPACE_BEGIN\n\n//! Forward declaration\nclass IFileMDSvc;\nclass IContainerMD;\n\n//------------------------------------------------------------------------------\n//! Class holding the metadata information concerning a single file\n//------------------------------------------------------------------------------\nclass QuarkFileMD : public IFileMD\n{\n  friend class FileSystemView;\n\npublic:\n  //----------------------------------------------------------------------------\n  //! Empty constructor\n  //----------------------------------------------------------------------------\n  QuarkFileMD();\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  QuarkFileMD(IFileMD::id_t id, IFileMDSvc* fileMDSvc);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~QuarkFileMD() {};\n\n  //----------------------------------------------------------------------------\n  //! Copy constructor\n  //----------------------------------------------------------------------------\n  QuarkFileMD(const QuarkFileMD& other);\n\n  //----------------------------------------------------------------------------\n  //! Virtual copy constructor\n  //----------------------------------------------------------------------------\n  virtual QuarkFileMD* clone() const override;\n\n  //----------------------------------------------------------------------------\n  //! Assignment operator\n  //----------------------------------------------------------------------------\n  QuarkFileMD& operator=(const QuarkFileMD& other);\n\n  //----------------------------------------------------------------------------\n  //! Get creation time\n  //----------------------------------------------------------------------------\n  void getCTime(ctime_t& ctime) const override;\n\n  //----------------------------------------------------------------------------\n  //! Set creation time\n  //----------------------------------------------------------------------------\n  void setCTime(ctime_t ctime) override;\n\n  //----------------------------------------------------------------------------\n  //! Set creation time to now\n  //----------------------------------------------------------------------------\n  void setCTimeNow() override;\n\n  //----------------------------------------------------------------------------\n  //! Get access time\n  //----------------------------------------------------------------------------\n  void getATime(ctime_t& atime) const override;\n\n  //----------------------------------------------------------------------------\n  //! Set access time\n  //----------------------------------------------------------------------------\n  void setATime(ctime_t atime) override;\n\n  //----------------------------------------------------------------------------\n  //! Set access time to now if older than olderthan\n  //----------------------------------------------------------------------------\n  bool setATimeNow(uint64_t olderthan) override;\n\n  //----------------------------------------------------------------------------\n  //! Get modification time\n  //----------------------------------------------------------------------------\n  void getMTime(ctime_t& mtime) const override;\n\n  //----------------------------------------------------------------------------\n  //! Set modification time\n  //----------------------------------------------------------------------------\n  void setMTime(ctime_t mtime) override;\n\n  //----------------------------------------------------------------------------\n  //! Set modification time to now\n  //----------------------------------------------------------------------------\n  void setMTimeNow() override;\n\n  //----------------------------------------------------------------------------\n  //! get sync time\n  //----------------------------------------------------------------------------\n  void getSyncTime(ctime_t& stime) const override;\n\n  //----------------------------------------------------------------------------\n  //! set sync time\n  //----------------------------------------------------------------------------\n  void setSyncTime(ctime_t stime) override;\n\n  //----------------------------------------------------------------------------\n  //! set sync time to now\n  //----------------------------------------------------------------------------\n  void setSyncTimeNow() override;\n\n  //----------------------------------------------------------------------------\n  //! Get file id\n  //----------------------------------------------------------------------------\n  inline IFileMD::id_t\n  getId() const override\n  {\n    return runReadOp([this]() {\n      return mFile.id();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get file identifier\n  //----------------------------------------------------------------------------\n  inline identifier_t\n  getIdentifier() const override\n  {\n    return this->runReadOp([this]() {\n      return identifier_t(mFile.id());\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get size\n  //----------------------------------------------------------------------------\n  inline uint64_t\n  getSize() const override\n  {\n    return this->runReadOp([this]() {\n      return mFile.size();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set size - 48 bytes will be used\n  //----------------------------------------------------------------------------\n  void setSize(uint64_t size) override;\n\n  //----------------------------------------------------------------------------\n  //! Get cloneId\n  //----------------------------------------------------------------------------\n  inline uint64_t\n  getCloneId() const override\n  {\n    return runReadOp([this]() {\n      return mFile.cloneid();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set cloneId\n  //----------------------------------------------------------------------------\n  void setCloneId(uint64_t id) override\n  {\n    return runWriteOp([this, id]() {\n      mFile.set_cloneid(id);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get cloneFST\n  //----------------------------------------------------------------------------\n  const std::string\n  getCloneFST() const override\n  {\n    return runReadOp([this]() {\n      return mFile.clonefst();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set cloneFST\n  //----------------------------------------------------------------------------\n  void setCloneFST(const std::string& data) override\n  {\n    runWriteOp([this, data]() {\n      mFile.set_clonefst(data);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get parent id\n  //----------------------------------------------------------------------------\n  inline IContainerMD::id_t\n  getContainerId() const override\n  {\n    return this->runReadOp([this]() {\n      return mFile.cont_id();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set parent id\n  //----------------------------------------------------------------------------\n  void\n  setContainerId(IContainerMD::id_t containerId) override\n  {\n    runWriteOp([this, containerId]() {\n      mFile.set_cont_id(containerId);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get checksum\n  //----------------------------------------------------------------------------\n  inline const Buffer\n  getChecksum() const override\n  {\n    return runReadOp([this]() {\n      Buffer buff(mFile.checksum().size());\n      buff.putData((void*)mFile.checksum().data(), mFile.checksum().size());\n      return buff;\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set checksum\n  //----------------------------------------------------------------------------\n  void\n  setChecksum(const Buffer& checksum) override\n  {\n    runWriteOp([this, &checksum]() {\n      mFile.set_checksum(checksum.getDataPtr(), checksum.getSize());\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Clear checksum\n  //----------------------------------------------------------------------------\n  void\n  clearChecksum(uint8_t size = 20) override\n  {\n    runWriteOp([this]() {\n      mFile.clear_checksum();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set checksum\n  //!\n  //! @param checksum address of a memory location storing the checksum\n  //! @param size     size of the checksum in bytes\n  //----------------------------------------------------------------------------\n  void\n  setChecksum(const void* checksum, uint8_t size) override\n  {\n    runWriteOp(\n    [this, checksum, size]() {\n      mFile.set_checksum(checksum, size);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get name\n  //----------------------------------------------------------------------------\n  inline const std::string\n  getName() const override\n  {\n    return runReadOp([this]() {\n      return mFile.name();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set name\n  //! Warning: Only calling this method to rename a file will not work as the\n  //! container containing it needs also to update its files cache\n  //----------------------------------------------------------------------------\n  void setName(const std::string& name) override;\n\n  //----------------------------------------------------------------------------\n  //! Add location\n  //----------------------------------------------------------------------------\n  void addLocation(location_t location) override;\n\n  //----------------------------------------------------------------------------\n  //! Get vector with all the locations\n  //----------------------------------------------------------------------------\n  inline LocationVector getLocations() const override\n  {\n    return this->runReadOp([this]() {\n      LocationVector locations(mFile.locations().begin(),\n                               mFile.locations().end());\n      return locations;\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get location\n  //----------------------------------------------------------------------------\n  location_t\n  getLocation(unsigned int index) override\n  {\n    return this->runReadOp([this, index]() {\n      if (index < (unsigned int)mFile.locations_size()) {\n        return mFile.locations(index);\n      }\n\n      return (location_t)0;\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Remove location that was previously unlinked\n  //----------------------------------------------------------------------------\n  void removeLocation(location_t location) override;\n\n  //----------------------------------------------------------------------------\n  //! Remove all locations that were previously unlinked\n  //----------------------------------------------------------------------------\n  void removeAllLocations() override;\n\n  //----------------------------------------------------------------------------\n  //! Clear locations without notifying the listeners\n  //----------------------------------------------------------------------------\n  void\n  clearLocations() override\n  {\n    this->runWriteOp([this]() {\n      mFile.clear_locations();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Test if location exists, without taking lock\n  //----------------------------------------------------------------------------\n  bool\n  hasLocationNoLock(location_t location)\n  {\n    for (int i = 0; i < mFile.locations_size(); i++) {\n      if (mFile.locations(i) == location) {\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Test if location exists\n  //----------------------------------------------------------------------------\n  bool\n  hasLocation(location_t location) override\n  {\n    return this->runReadOp(\n    [this, location]() {\n      return hasLocationNoLock(location);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get number of locations\n  //----------------------------------------------------------------------------\n  inline size_t\n  getNumLocation() const override\n  {\n    return runReadOp([this]() {\n      return mFile.locations_size();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get vector with all unlinked locations\n  //----------------------------------------------------------------------------\n  inline LocationVector getUnlinkedLocations() const override\n  {\n    return this->runReadOp([this]() {\n      LocationVector unlinked_locations(mFile.unlink_locations().begin(),\n                                        mFile.unlink_locations().end());\n      return unlinked_locations;\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Unlink location\n  //----------------------------------------------------------------------------\n  void unlinkLocation(location_t location) override;\n\n  //----------------------------------------------------------------------------\n  //! Unlink all locations\n  //----------------------------------------------------------------------------\n  void unlinkAllLocations() override;\n\n  //----------------------------------------------------------------------------\n  //! Clear unlinked locations without notifying the listeners\n  //----------------------------------------------------------------------------\n  inline void\n  clearUnlinkedLocations() override\n  {\n    this->runWriteOp([this]() {\n      mFile.clear_unlink_locations();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Test the unlinked location\n  //----------------------------------------------------------------------------\n  bool\n  hasUnlinkedLocation(location_t location) override;\n\n  //----------------------------------------------------------------------------\n  //! Get number of unlinked locations\n  //----------------------------------------------------------------------------\n  inline size_t\n  getNumUnlinkedLocation() const override\n  {\n    return runReadOp([this]() {\n      return mFile.unlink_locations_size();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get uid\n  //----------------------------------------------------------------------------\n  inline uid_t\n  getCUid() const override\n  {\n    return runReadOp([this]() {\n      return mFile.uid();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set uid\n  //----------------------------------------------------------------------------\n  inline void\n  setCUid(uid_t uid) override\n  {\n    runWriteOp([this, uid]() {\n      mFile.set_uid(uid);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get gid\n  //----------------------------------------------------------------------------\n  inline gid_t\n  getCGid() const override\n  {\n    return runReadOp([this]() {\n      return mFile.gid();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set gid\n  //----------------------------------------------------------------------------\n  inline void\n  setCGid(gid_t gid) override\n  {\n    runWriteOp([this, gid]() {\n      mFile.set_gid(gid);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get layout\n  //----------------------------------------------------------------------------\n  inline layoutId_t\n  getLayoutId() const override\n  {\n    return runReadOp([this]() {\n      return mFile.layout_id();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set layout\n  //----------------------------------------------------------------------------\n  inline void\n  setLayoutId(layoutId_t layoutId) override\n  {\n    runWriteOp([this, layoutId]() {\n      mFile.set_layout_id(layoutId);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get flags\n  //----------------------------------------------------------------------------\n  inline uint16_t\n  getFlags() const override\n  {\n    return runReadOp([this]() {\n      return mFile.flags();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the n-th flag\n  //----------------------------------------------------------------------------\n  inline bool\n  getFlag(uint8_t n) override\n  {\n    return runReadOp(\n    [this, n]() {\n      return (bool)(mFile.flags() & (0x0001 << n));\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set flags\n  //----------------------------------------------------------------------------\n  inline void\n  setFlags(uint16_t flags) override\n  {\n    return runWriteOp([this, flags]() {\n      mFile.set_flags(flags);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set the n-th flag\n  //----------------------------------------------------------------------------\n  void\n  setFlag(uint8_t n, bool flag) override\n  {\n    return runWriteOp([this, n, flag]() {\n      if (flag) {\n        mFile.set_flags(mFile.flags() | (1 << n));\n      } else {\n        mFile.set_flags(mFile.flags() & (~(1 << n)));\n      }\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Env Representation\n  //----------------------------------------------------------------------------\n  void getEnv(std::string& env, bool escapeAnd = false) override;\n\n  //----------------------------------------------------------------------------\n  //! Set the FileMDSvc object\n  //----------------------------------------------------------------------------\n  inline void\n  setFileMDSvc(IFileMDSvc* fileMDSvc) override\n  {\n    runWriteOp([this, fileMDSvc]() {\n      pFileMDSvc = static_cast<QuarkFileMDSvc*>(fileMDSvc);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the FileMDSvc object\n  //----------------------------------------------------------------------------\n  inline virtual IFileMDSvc*\n  getFileMDSvc() override\n  {\n    return runReadOp([this]() {\n      return pFileMDSvc;\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get symbolic link\n  //----------------------------------------------------------------------------\n  inline std::string\n  getLink() const override\n  {\n    return runReadOp([this]() {\n      return mFile.link_name();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set symbolic link\n  //----------------------------------------------------------------------------\n  inline void\n  setLink(std::string link_name) override\n  {\n    runWriteOp([this, link_name]() {\n      mFile.set_link_name(link_name);\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if symbolic link\n  //----------------------------------------------------------------------------\n  bool\n  isLink() const override\n  {\n    return runReadOp([this]() {\n      return !mFile.link_name().empty();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Add extended attribute\n  //----------------------------------------------------------------------------\n  void\n  setAttribute(const std::string& name, const std::string& value) override\n  {\n    runWriteOp(\n    [this, name, value]() {\n      (*mFile.mutable_xattrs())[name] = value;\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Remove attribute\n  //----------------------------------------------------------------------------\n  void\n  removeAttribute(const std::string& name) override\n  {\n    runWriteOp([this, name]() {\n      auto it = mFile.xattrs().find(name);\n\n      if (it != mFile.xattrs().end()) {\n        mFile.mutable_xattrs()->erase(it->first);\n      }\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Remove all attributes\n  //----------------------------------------------------------------------------\n  void clearAttributes() override\n  {\n    runWriteOp([this]() {\n      mFile.clear_xattrs();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if the attribute exist\n  //----------------------------------------------------------------------------\n  bool\n  hasAttribute(const std::string& name) const override\n  {\n    return runReadOp([this, name]() {\n      return (mFile.xattrs().find(name) != mFile.xattrs().end());\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return number of attributes\n  //----------------------------------------------------------------------------\n  inline size_t\n  numAttributes() const override\n  {\n    return runReadOp([this]() {\n      return mFile.xattrs().size();\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the attribute\n  //----------------------------------------------------------------------------\n  std::string\n  getAttribute(const std::string& name) const override\n  {\n    return runReadOp([this, &name] {\n      auto it = mFile.xattrs().find(name);\n\n      if (it == mFile.xattrs().end())\n      {\n        MDException e(ENOENT);\n        e.getMessage() << \"Attribute: \" << name << \" not found\";\n        throw e;\n      }\n\n      return it->second;\n    });\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get map copy of the extended attributes\n  //!\n  //! @return std::map containing all the extended attributes\n  //----------------------------------------------------------------------------\n  eos::IFileMD::XAttrMap getAttributes() const override;\n\n  //----------------------------------------------------------------------------\n  //! Serialize the object to a buffer\n  //----------------------------------------------------------------------------\n  void serialize(Buffer& buffer) override;\n\n  //----------------------------------------------------------------------------\n  //! Initialize from protobuf contents\n  //----------------------------------------------------------------------------\n  void initialize(eos::ns::FileMdProto&& proto);\n\n  //----------------------------------------------------------------------------\n  //! Deserialize the class to a buffer\n  //----------------------------------------------------------------------------\n  void deserialize(const Buffer& buffer) override;\n\n  //----------------------------------------------------------------------------\n  //! Get reference to underlying protobuf object\n  //----------------------------------------------------------------------------\n  const eos::ns::FileMdProto& getProto() const;\n\n  //----------------------------------------------------------------------------\n  //! Get value tracking changes to the metadata object\n  //----------------------------------------------------------------------------\n  virtual uint64_t getClock() const override\n  {\n    return runReadOp([this]() {\n      return mClock;\n    });\n  };\n\n  void addAltXs(eos::common::LayoutId::eChecksum\n                checksumType, const char* checksum, size_t size) override;\n\n  std::map<eos::common::LayoutId::eChecksum, std::string> getAltXs() const\n  override;\n\n  void clearAltXs() override;\n\n  void removeAltXs(eos::common::LayoutId::eChecksum type) override;\n\nprotected:\n  IFileMDSvc* pFileMDSvc;\n\nprivate:\n  FRIEND_TEST(VariousTests, EtagFormatting);\n\n  //----------------------------------------------------------------------------\n  //! Get modification time, no locks\n  //----------------------------------------------------------------------------\n  void getMTimeNoLock(ctime_t& mtime) const;\n\n  //----------------------------------------------------------------------------\n  //! Get access time, no locks\n  //----------------------------------------------------------------------------\n  void getATimeNoLock(ctime_t& atime) const;\n\n  //----------------------------------------------------------------------------\n  //! Get modification time, no locks\n  //----------------------------------------------------------------------------\n  void getSyncTimeNoLock(ctime_t& stime) const;\n\n  //----------------------------------------------------------------------------\n  //! Get creation time, no locks\n  //----------------------------------------------------------------------------\n  void getCTimeNoLock(ctime_t& ctime) const;\n\n  //----------------------------------------------------------------------------\n  //! Test the unlinked location, no locks\n  //----------------------------------------------------------------------------\n  bool hasUnlinkedLocationNoLock(location_t location) const;\n\n\n  eos::ns::FileMdProto mFile; ///< Protobuf file representation\n  uint64_t mClock; ///< Value tracking metadata changes\n};\n\nEOSNSNAMESPACE_END\n\n#endif // __EOS_NS_FILE_MD_HH__\n"
  },
  {
    "path": "namespace/ns_quarkdb/LRU.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief LRU cache for namespace objects making sure we never evict an entry\n//!        which is still referenced in other parts of the program.\n//------------------------------------------------------------------------------\n\n#ifndef __EOS_NS_LRU_HH__\n#define __EOS_NS_LRU_HH__\n\n#include \"common/AssistedThread.hh\"\n#include \"common/ConcurrentQueue.hh\"\n#include \"common/Murmur3.hh\"\n#include \"namespace/Namespace.hh\"\n#include <google/dense_hash_map>\n#include <cstdint>\n#include <list>\n#include <map>\n#include <memory>\n#include <mutex>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Helper struct to test if EntryT implements the getId method. If EntryT\n//! implements getId method then hasGetId::value will be true, otherwise false.\n//! This struct is to be used in other template definitions.\n//------------------------------------------------------------------------------\ntemplate <class EntryT>\nstruct hasGetId {\n  template <typename C>\n  static constexpr decltype(std::declval<C>().getId(), bool())\n  test(int)\n  {\n    return true;\n  }\n\n  template <typename C>\n  static constexpr bool\n  test(...)\n  {\n    return false;\n  }\n\n  // int is used to give precedence!\n  static constexpr bool value = test<EntryT>(int());\n};\n\n//------------------------------------------------------------------------------\n//! LRU cache for namespace entries\n//------------------------------------------------------------------------------\ntemplate <typename IdT, typename EntryT>\nclass LRU\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param maxSize maximum number of entries in the cache\n  //----------------------------------------------------------------------------\n  LRU(std::uint64_t maxSize);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~LRU();\n\n  //----------------------------------------------------------------------------\n  //! Get entry\n  //!\n  //! @param id entry id\n  //!\n  //! @return shared ptr to requested object or nullptr if not found\n  //----------------------------------------------------------------------------\n  std::shared_ptr<EntryT> get(IdT id);\n\n  //----------------------------------------------------------------------------\n  //! Put entry\n  //!\n  //! @param id entry id\n  //! @param entry entry object\n  //!\n  //! @return true if successfully added to the cache, false otherwise. If\n  //!         cache is full then the least recently used entry is evicted\n  //!         provided that it's not referenced anywhere else in the program.\n  //----------------------------------------------------------------------------\n  typename\n  std::enable_if<hasGetId<EntryT>::value, std::shared_ptr<EntryT>>::type\n      put(IdT id, std::shared_ptr<EntryT> obj);\n\n  //----------------------------------------------------------------------------\n  //! Remove entry from cache\n  //!\n  //! @param id entry id\n  //!\n  //! @return true if successfully removed from the cache, false otherwise\n  //----------------------------------------------------------------------------\n  bool remove(IdT id);\n\n  //----------------------------------------------------------------------------\n  //! Get cache size\n  //!\n  //! @return cache size\n  //----------------------------------------------------------------------------\n  inline std::uint64_t\n  size() const\n  {\n    std::unique_lock<std::mutex> lock(mMutex);\n    return mMap.size();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get maximim number of entries in the cache\n  //!\n  //! @return maximum cache num entries\n  //----------------------------------------------------------------------------\n  inline std::uint64_t\n  GetMaxNum() const\n  {\n    std::unique_lock<std::mutex> lock(mMutex);\n    return mMaxNum;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set max num entries\n  //!\n  //! @param max_num new maximum number of entries, if 0 then just drop the\n  //!                the current cache\n  //----------------------------------------------------------------------------\n  inline void\n  SetMaxNum(const std::uint64_t max_num)\n  {\n    std::unique_lock<std::mutex> lock(mMutex);\n\n    if (max_num == 0ull) {\n      // Flush and disable cache\n      Purge(0.0);\n      mMaxNum = 0ull;\n    } else if (max_num == UINT64_MAX) {\n      Purge(0.0); // Flush cache\n    } else {\n      mMaxNum = max_num;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get number of requests towards the cache\n  //----------------------------------------------------------------------------\n  inline uint64_t GetRequests() const {\n    return mRequests.load();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get number cache hits\n  //----------------------------------------------------------------------------\n  inline uint64_t GetHits() const {\n    return mHits.load();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Forbid copying or moving LRU objects\n  //----------------------------------------------------------------------------\n  LRU(const LRU& other) = delete;\n  LRU& operator=(const LRU& other) = delete;\n  LRU(LRU&& other) = delete;\n  LRU& operator=(LRU&& other) = delete;\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Cleaner job taking care of deallocating entries that are passed through\n  //! the queue to delete\n  //----------------------------------------------------------------------------\n  void CleanerJob(ThreadAssistant& assistant);\n\n  //----------------------------------------------------------------------------\n  //! Purge entries until stop ratio is achieved\n  //!\n  //! @param stop_ratio stop purge ratio\n  //! @note This method must be called with the mutex protecting the map and\n  //! the list locked.\n  //----------------------------------------------------------------------------\n  void Purge(double stop_ratio);\n\n  //! Percentage at which the cache purging stops\n  static constexpr double sPurgeStopRatio = 0.9;\n  using ListT = std::list<std::shared_ptr<EntryT>>;\n  typename std::list<std::shared_ptr<EntryT>>::iterator ListIterT;\n  //using MapT = std::map<IdT, decltype(ListIterT)>;\n  using MapT = google::dense_hash_map<IdT, decltype(ListIterT),\n        Murmur3::MurmurHasher<IdT>>;\n  MapT mMap;   ///< Internal map pointing to obj in list\n  //! Internal list of objects where new/used objects are at the\n  //! end of the list\n  ListT mList;\n  //! Mutext to protect access to the map and list\n  mutable std::mutex mMutex;\n  std::uint64_t mMaxNum; ///< Maximum number of entries\n  //! Number of hits in the cache\n  std::atomic<uint64_t> mHits {0};\n  //! Number of requests\n  std::atomic<uint64_t> mRequests {0};\n  eos::common::ConcurrentQueue< std::shared_ptr<EntryT> > mToDelete;\n  AssistedThread mCleanerThread; ///< Thread doing the deallocations\n};\n\n// Definition of class static member\ntemplate <typename IdT, typename EntryT>\nconstexpr double LRU<IdT, EntryT>::sPurgeStopRatio;\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\ntemplate <typename IdT, typename EntryT>\nLRU<IdT, EntryT>::LRU(std::uint64_t max_num) :\n  mMap(), mList(), mMutex(), mMaxNum(max_num), mToDelete()\n{\n  mMap.set_empty_key(IdT(UINT64_MAX - 1));\n  mMap.set_deleted_key(IdT(UINT64_MAX));\n  mCleanerThread.reset(&LRU::CleanerJob, this);\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\ntemplate <typename IdT, typename EntryT>\nLRU<IdT, EntryT>::~LRU()\n{\n  std::shared_ptr<EntryT> sentinel(nullptr);\n  mCleanerThread.stop();\n  mToDelete.push(sentinel);\n  mCleanerThread.join();\n  std::unique_lock<std::mutex> lock(mMutex);\n  mMap.clear();\n  mList.clear();\n}\n\n//------------------------------------------------------------------------------\n// Get object\n//------------------------------------------------------------------------------\ntemplate <typename IdT, typename EntryT>\nstd::shared_ptr<EntryT>\nLRU<IdT, EntryT>::get(IdT id)\n{\n  ++mRequests;\n  std::unique_lock<std::mutex> lock(mMutex);\n  auto iter_map = mMap.find(id);\n\n  if (iter_map == mMap.end()) {\n    return nullptr;\n  }\n\n  // Move object to the end of the list i.e. recently accessed\n  auto iter_new = mList.insert(mList.end(), *iter_map->second);\n  mList.erase(iter_map->second);\n  iter_map->second = iter_new;\n  ++mHits;\n  return *iter_new;\n}\n\n//------------------------------------------------------------------------------\n// Put object\n//------------------------------------------------------------------------------\ntemplate <typename IdT, typename EntryT>\ntypename std::enable_if<hasGetId<EntryT>::value, std::shared_ptr<EntryT>>::type\n    LRU<IdT, EntryT>::put(IdT id, std::shared_ptr<EntryT> obj)\n{\n  std::unique_lock<std::mutex> lock(mMutex);\n\n  if (mMaxNum == 0ull) {\n    return obj;\n  }\n\n  auto iter_map = mMap.find(id);\n\n  if (iter_map != mMap.end()) {\n    return *(iter_map->second);\n  }\n\n  // Check if map full and purge some entries if necessary 10% of max size\n  if (mMap.size() >= mMaxNum) {\n    Purge(sPurgeStopRatio);\n  }\n\n  // @todo (esindril): add time based and for a fixed number of entries purging\n  auto iter = mList.insert(mList.end(), obj);\n  mMap[id] = iter;\n  return *iter;\n}\n\n//------------------------------------------------------------------------------\n// Remove object\n//------------------------------------------------------------------------------\ntemplate <typename IdT, typename EntryT>\nbool\nLRU<IdT, EntryT>::remove(IdT id)\n{\n  std::unique_lock<std::mutex> lock(mMutex);\n  auto iter_map = mMap.find(id);\n\n  if (iter_map == mMap.end()) {\n    return false;\n  }\n\n  (void)mList.erase(iter_map->second);\n  mMap.erase(iter_map);\n  return true;\n}\n\n//----------------------------------------------------------------------------\n// Cleaner job taking care of deallocating entries that are passed through\n// the queue to delete\n//----------------------------------------------------------------------------\ntemplate <typename IdT, typename EntryT>\nvoid\nLRU<IdT, EntryT>::CleanerJob(ThreadAssistant& assistant)\n{\n  std::shared_ptr<EntryT> tmp;\n\n  while (!assistant.terminationRequested()) {\n    while (true) {\n      mToDelete.wait_pop(tmp);\n\n      if (tmp == nullptr) {\n        break;\n      } else {\n        tmp.reset();\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Purge entries until stop ratio is achieved\n//------------------------------------------------------------------------------\ntemplate <typename IdT, typename EntryT>\nvoid\nLRU<IdT, EntryT>::Purge(double stop_ratio)\n{\n  auto iter = mList.begin();\n\n  while ((iter != mList.end()) &&\n         (mMap.size() > stop_ratio * mMaxNum)) {\n    // If object is referenced also by someone else then skip it\n    if (iter->use_count() > 1) {\n      ++iter;\n      continue;\n    }\n\n    mMap.erase(IdT((*iter)->getId()));\n    mToDelete.push(*iter);\n    iter = mList.erase(iter);\n  }\n\n  mMap.resize(0); // compact after deletion\n}\n\nEOSNSNAMESPACE_END\n\n#endif // __EOS_NS_LRU_HH__\n"
  },
  {
    "path": "namespace/ns_quarkdb/NamespaceGroup.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Class representing the file metadata\n//------------------------------------------------------------------------------\n\n#include \"namespace/ns_quarkdb/NamespaceGroup.hh\"\n#include \"namespace/ns_quarkdb/persistency/ContainerMDSvc.hh\"\n#include \"namespace/ns_quarkdb/persistency/FileMDSvc.hh\"\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n#include \"namespace/ns_quarkdb/views/HierarchicalView.hh\"\n#include \"namespace/ns_quarkdb/accounting/FileSystemView.hh\"\n#include \"namespace/ns_quarkdb/accounting/SyncTimeAccounting.hh\"\n#include \"namespace/ns_quarkdb/accounting/QuotaStats.hh\"\n#include \"namespace/ns_quarkdb/accounting/ContainerAccounting.hh\"\n#include \"namespace/ns_quarkdb/CacheRefreshListener.hh\"\n#include \"namespace/ns_quarkdb/VersionEnforcement.hh\"\n#include <folly/executors/IOThreadPoolExecutor.h>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nQuarkNamespaceGroup::QuarkNamespaceGroup()\n{\n  mExecutor.reset(new folly::IOThreadPoolExecutor(48));\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nQuarkNamespaceGroup::~QuarkNamespaceGroup()\n{\n  eos_static_info(\"%s\", \"msg=\\\"shutting down cache refresh listener\\\"\");\n  mCacheRefreshListener.reset();\n  eos_static_info(\"%s\", \"msg=\\\"shutting down sync accounting\\\"\");\n  mSyncAccounting.reset();\n  eos_static_info(\"%s\", \"msg=\\\"shutting down container accounting\\\"\");\n  mContainerAccounting.reset();\n  eos_static_info(\"%s\", \"msg=\\\"shutting down file system view\\\"\");\n  mFilesystemView.reset();\n  eos_static_info(\"%s\", \"msg=\\\"shutting down hierarchcal view\\\"\");\n  mHierarchicalView.reset();\n  eos_static_info(\"%s\", \"msg=\\\"shutting down file service\\\"\");\n  mFileService.reset();\n  eos_static_info(\"%s\", \"msg=\\\"shutting down container service\\\"\");\n  mContainerService.reset();\n  eos_static_info(\"%s\", \"msg=\\\"shutting down metadata flusher\\\"\");\n  mMetadataFlusher.reset();\n  eos_static_info(\"%s\", \"msg=\\\"shutting down quota flusher\\\"\");\n  mQuotaFlusher.reset();\n  eos_static_info(\"%s\", \"msg=\\\"shutting down qclient\\\"\");\n  mQClient.reset();\n  eos_static_info(\"%s\", \"msg=\\\"shutting down folly executor\\\"\");\n  mExecutor.reset();\n  eos_static_info(\"%s\", \"msg=\\\"shutting down qcl performance monitor\\\"\");\n  mPerfMonitor.reset();\n  eos_static_info(\"%s\", \"msg=\\\"done shutting down namespace group\\\"\");\n}\n\n//------------------------------------------------------------------------------\n// Initialize with the given configuration - must be called before any\n// other function, and right after construction.\n//\n// Initialization may fail - in such case, \"false\" will be returned, and\n// \"err\" will be filled out.\n//------------------------------------------------------------------------------\nbool QuarkNamespaceGroup::initialize(eos::common::RWMutex* nsMtx,\n                                     const std::map<std::string, std::string>& config,\n                                     std::string& err,\n                                     INamespaceStats * namespaceStats)\n{\n  mNsMutex = nsMtx;\n  mNamespaceStats = namespaceStats;\n  // Mandatory configuration option: queue_path\n  auto it = config.find(\"queue_path\");\n\n  if (it == config.end()) {\n    err = \"configuration key queue_path not found!\";\n    return false;\n  }\n\n  queuePath = it->second;\n  // Mandatory configuration option: qdb_cluster\n  it = config.find(\"qdb_cluster\");\n\n  if (it == config.end()) {\n    err = \"configuration key qdb_cluster not found!\";\n    return false;\n  }\n\n  if (!contactDetails.members.parse(it->second)) {\n    err = \"could not parse qdb_cluster!\";\n    return false;\n  }\n\n  // Optional configuration option: qdb_password\n  it = config.find(\"qdb_password\");\n\n  if (it != config.end()) {\n    contactDetails.password = it->second;\n  }\n\n  // Mandatory configuration: qdb_flusher_md\n  it = config.find(\"qdb_flusher_md\");\n\n  if (it == config.end()) {\n    err = \"configuration key qdb_flusher_md not found!\";\n    return false;\n  }\n\n  flusherMDTag = it->second;\n  // Mandatory configuration: qdb_flusher_quota\n  it = config.find(\"qdb_flusher_quota\");\n\n  if (it == config.end()) {\n    err = \"configuration key qdb_flusher_quota not found!\";\n    return false;\n  }\n\n  flusherQuotaTag = it->second;\n  mPerfMonitor = std::make_shared<eos::QClPerfMonitor>();\n\n  if (!enforceQuarkDBVersion(getQClient())) {\n    err = \"QuarkDB is either down, or running an outdated version.\";\n    return false;\n  }\n\n  it = config.find(\"qclient_flusher_type\");\n  if (it != config.end()) {\n    flusherType = it->second;\n  }\n\n  it = config.find(\"qclient_rocksdb_options\");\n  if (it != config.end()) {\n    flusherRocksDBOptions = it->second;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Initialize file and container services\n//------------------------------------------------------------------------------\nvoid QuarkNamespaceGroup::initializeFileAndContainerServices()\n{\n  std::lock_guard<std::recursive_mutex> lock(mMutex);\n\n  if (!mFileService) {\n    mFileService.reset(new QuarkFileMDSvc(getQClient(), getMetadataFlusher()));\n  }\n\n  if (!mContainerService) {\n    mContainerService.reset(new QuarkContainerMDSvc(getQClient(),\n                            getMetadataFlusher()));\n  }\n\n  mContainerService->setFileMDService(mFileService.get());\n  mFileService->setContMDService(mContainerService.get());\n}\n\n//------------------------------------------------------------------------------\n// Provide file service\n//------------------------------------------------------------------------------\nIFileMDSvc* QuarkNamespaceGroup::getFileService()\n{\n  std::lock_guard<std::recursive_mutex> lock(mMutex);\n\n  if (!mFileService) {\n    initializeFileAndContainerServices();\n  }\n\n  return mFileService.get();\n}\n\n//------------------------------------------------------------------------------\n// Provide container service\n//------------------------------------------------------------------------------\nIContainerMDSvc* QuarkNamespaceGroup::getContainerService()\n{\n  std::lock_guard<std::recursive_mutex> lock(mMutex);\n\n  if (!mContainerService) {\n    initializeFileAndContainerServices();\n  }\n\n  return mContainerService.get();\n}\n\n//------------------------------------------------------------------------------\n// Provide hieararchical view\n//------------------------------------------------------------------------------\nIView* QuarkNamespaceGroup::getHierarchicalView()\n{\n  std::lock_guard<std::recursive_mutex> lock(mMutex);\n\n  if (!mHierarchicalView) {\n    mHierarchicalView.reset(new QuarkHierarchicalView(getQClient(),\n                            getQuotaFlusher()));\n    mHierarchicalView->setFileMDSvc(getFileService());\n    mHierarchicalView->setContainerMDSvc(getContainerService());\n  }\n\n  return mHierarchicalView.get();\n}\n\n//------------------------------------------------------------------------------\n// Provide filesystem view\n//------------------------------------------------------------------------------\nIFsView* QuarkNamespaceGroup::getFilesystemView()\n{\n  std::lock_guard<std::recursive_mutex> lock(mMutex);\n\n  if (!mFilesystemView) {\n    mFilesystemView.reset(new QuarkFileSystemView(getQClient(),\n                          getMetadataFlusher()));\n    getFileService()->addChangeListener(mFilesystemView.get());\n  }\n\n  return mFilesystemView.get();\n}\n\n//------------------------------------------------------------------------------\n// Provide container accounting view\n//------------------------------------------------------------------------------\nIFileMDChangeListener* QuarkNamespaceGroup::getContainerAccountingView()\n{\n  std::lock_guard<std::recursive_mutex> lock(mMutex);\n\n  if (!mContainerAccounting) {\n    mContainerAccounting.reset(new QuarkContainerAccounting(getContainerService()));\n    getFileService()->addChangeListener(mContainerAccounting.get());\n    getContainerService()->setContainerAccounting(mContainerAccounting.get());\n  }\n\n  return mContainerAccounting.get();\n}\n\n//------------------------------------------------------------------------------\n// Provide sync time accounting view\n//------------------------------------------------------------------------------\nIContainerMDChangeListener* QuarkNamespaceGroup::getSyncTimeAccountingView()\n{\n  std::lock_guard<std::recursive_mutex> lock(mMutex);\n\n  if (!mSyncAccounting) {\n    std::unique_ptr<QuarkSyncTimeAccounting> syncAccounting =\n      std::make_unique<QuarkSyncTimeAccounting>(getContainerService(), 5,\n                                                mNamespaceStats);\n    mSyncAccounting = std::move(syncAccounting);\n    getContainerService()->addChangeListener(mSyncAccounting.get());\n  }\n\n  return mSyncAccounting.get();\n}\n\n//------------------------------------------------------------------------------\n// Provide quota stats\n//------------------------------------------------------------------------------\nIQuotaStats* QuarkNamespaceGroup::getQuotaStats()\n{\n  return getHierarchicalView()->getQuotaStats();\n}\n\n//------------------------------------------------------------------------------\n// Get metadata flusher\n//------------------------------------------------------------------------------\nMetadataFlusher* QuarkNamespaceGroup::getMetadataFlusher()\n{\n  std::lock_guard<std::recursive_mutex> lock(mMutex);\n\n  if (!mMetadataFlusher) {\n    std::string path = SSTR(queuePath << \"/\" << flusherMDTag);\n    if (flusherType.empty()) {\n      mMetadataFlusher.reset(new MetadataFlusher(path, contactDetails));\n    } else {\n      mMetadataFlusher.reset(new MetadataFlusher(path, contactDetails,\n                                                 flusherType, flusherRocksDBOptions));\n    }\n  }\n\n  return mMetadataFlusher.get();\n}\n\n//------------------------------------------------------------------------------\n// Get quota flusher\n//------------------------------------------------------------------------------\nMetadataFlusher* QuarkNamespaceGroup::getQuotaFlusher()\n{\n  std::lock_guard<std::recursive_mutex> lock(mMutex);\n\n  if (!mQuotaFlusher) {\n    std::string path = SSTR(queuePath << \"/\" << flusherQuotaTag);\n    mQuotaFlusher.reset(new MetadataFlusher(path, contactDetails));\n  }\n\n  return mQuotaFlusher.get();\n}\n\n//------------------------------------------------------------------------------\n// Get generic qclient object for light-weight tasks\n//------------------------------------------------------------------------------\nqclient::QClient* QuarkNamespaceGroup::getQClient()\n{\n  std::lock_guard<std::recursive_mutex> lock(mMutex);\n\n  if (!mQClient) {\n    qclient::Options options = contactDetails.constructOptions();\n    options.mPerfCb = mPerfMonitor;\n    mQClient = std::make_unique<qclient::QClient>(contactDetails.members,\n               std::move(options));\n  }\n\n  return mQClient.get();\n}\n\n//------------------------------------------------------------------------------\n// Get folly executor\n//------------------------------------------------------------------------------\nfolly::Executor* QuarkNamespaceGroup::getExecutor()\n{\n  std::lock_guard<std::recursive_mutex> lock(mMutex);\n  return mExecutor.get();\n}\n\n//------------------------------------------------------------------------------\n// Start cache refresh listener\n//------------------------------------------------------------------------------\nvoid QuarkNamespaceGroup::startCacheRefreshListener()\n{\n  std::lock_guard<std::recursive_mutex> lock(mMutex);\n\n  if (!mCacheRefreshListener) {\n    mCacheRefreshListener.reset(new CacheRefreshListener(contactDetails,\n                                mFileService->getMetadataProvider()));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get qclient performance monitor\n//------------------------------------------------------------------------------\nstd::shared_ptr<QClPerfMonitor>\nQuarkNamespaceGroup::getPerformanceMonitor()\n{\n  return mPerfMonitor;\n}\n\nEOSNSNAMESPACE_END\n\n"
  },
  {
    "path": "namespace/ns_quarkdb/NamespaceGroup.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief  Class to hold ownership of all QuarkDB-namespace objects.\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/INamespaceGroup.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"namespace/ns_quarkdb/QClPerformance.hh\"\n#include <mutex>\n#include <memory>\n\nnamespace folly\n{\nclass Executor;\n}\n\nnamespace qclient\n{\nclass QClient;\n}\n\nEOSNSNAMESPACE_BEGIN\n\nclass QuarkContainerMDSvc;\nclass QuarkFileMDSvc;\nclass QuarkHierarchicalView;\nclass QuarkFileSystemView;\nclass QuarkContainerAccounting;\nclass QuarkSyncTimeAccounting;\nclass QuarkQuotaStats;\nclass MetadataFlusher;\nclass CacheRefreshListener;\n\n//------------------------------------------------------------------------------\n//! Class to hold ownership of all QuarkDB-namespace objects.\n//------------------------------------------------------------------------------\nclass QuarkNamespaceGroup : public INamespaceGroup\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  QuarkNamespaceGroup();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~QuarkNamespaceGroup();\n\n  //----------------------------------------------------------------------------\n  //! Initialize with the given configuration - must be called before any\n  //! other function, and right after construction.\n  //!\n  //! Initialization may fail - in such case, \"false\" will be returned, and\n  //! \"err\" will be filled out.\n  //----------------------------------------------------------------------------\n  virtual bool initialize(eos::common::RWMutex* nsMtx, const\n                          std::map<std::string, std::string>& config, std::string& err,INamespaceStats * namespaceStats) override final;\n\n  //----------------------------------------------------------------------------\n  //! Provide file service\n  //----------------------------------------------------------------------------\n  virtual IFileMDSvc* getFileService() override final;\n\n  //----------------------------------------------------------------------------\n  //! Provide container service\n  //----------------------------------------------------------------------------\n  virtual IContainerMDSvc* getContainerService() override final;\n\n  //----------------------------------------------------------------------------\n  //! Provide hieararchical view\n  //----------------------------------------------------------------------------\n  virtual IView* getHierarchicalView() override final;\n\n  //----------------------------------------------------------------------------\n  //! Provide filesystem view\n  //----------------------------------------------------------------------------\n  virtual IFsView* getFilesystemView() override final;\n\n  //----------------------------------------------------------------------------\n  //! Provide container accounting view\n  //----------------------------------------------------------------------------\n  virtual IFileMDChangeListener* getContainerAccountingView() override final;\n\n  //----------------------------------------------------------------------------\n  //! Provide sync time accounting view\n  //----------------------------------------------------------------------------\n  virtual IContainerMDChangeListener* getSyncTimeAccountingView() override final;\n\n  //----------------------------------------------------------------------------\n  //! Provide quota stats\n  //----------------------------------------------------------------------------\n  virtual IQuotaStats* getQuotaStats() override final;\n\n  //----------------------------------------------------------------------------\n  //! Is this in-memory namespace?\n  //----------------------------------------------------------------------------\n  virtual bool isInMemory() override final\n  {\n    return false;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get metadata flusher\n  //----------------------------------------------------------------------------\n  MetadataFlusher* getMetadataFlusher();\n\n  //----------------------------------------------------------------------------\n  //! Get quota flusher\n  //----------------------------------------------------------------------------\n  MetadataFlusher* getQuotaFlusher();\n\n  //----------------------------------------------------------------------------\n  //! Get quota flusher\n  //----------------------------------------------------------------------------\n  std::shared_ptr<QClPerfMonitor> getPerformanceMonitor();\n\n  //----------------------------------------------------------------------------\n  //! Get generic qclient object for light-weight tasks\n  //----------------------------------------------------------------------------\n  qclient::QClient* getQClient();\n\n  //----------------------------------------------------------------------------\n  //! Get folly executor\n  //----------------------------------------------------------------------------\n  folly::Executor* getExecutor();\n\n  //----------------------------------------------------------------------------\n  //! Start cache refresh listener\n  //----------------------------------------------------------------------------\n  void startCacheRefreshListener() override final;\n\nprivate:\n  //----------------------------------------------------------------------------\n  // Configuration\n  //----------------------------------------------------------------------------\n  QdbContactDetails contactDetails; //< QDB cluster contact details\n  std::string queuePath;            //< Namespace queue path\n  std::string flusherMDTag;         //< Tag for MD flusher\n  std::string flusherQuotaTag;      //< Tag for quota flusher\n  std::string flusherType;          //< Type of flusher\n  std::string flusherRocksDBOptions; //< RocksDB options for flusher\n  //----------------------------------------------------------------------------\n  // Initialize file and container services\n  //----------------------------------------------------------------------------\n  void initializeFileAndContainerServices();\n\n  std::recursive_mutex mMutex;\n\n  //----------------------------------------------------------------------------\n  //! CAUTION: The folly Executor must outlive qclient! If a continuation is\n  //! attached to a qclient-provided future, but the executor has been\n  //! destroyed, qclient will segfault when fulfilling the corresponding\n  //! promise.\n  //!\n  //! Once qclient is destroyed however, any pending promises will break, and\n  //! the executor can then be deleted safely.\n  //----------------------------------------------------------------------------\n  std::unique_ptr<folly::Executor> mExecutor;\n\n  std::unique_ptr<MetadataFlusher> mMetadataFlusher;  //< Flusher for metadata\n  std::unique_ptr<MetadataFlusher> mQuotaFlusher;     //< Flusher for quota\n\n  std::unique_ptr<qclient::QClient> mQClient;         //< Main qclient object\n  //< used for generic tasks\n\n  std::unique_ptr<QuarkContainerMDSvc> mContainerService;\n  std::unique_ptr<QuarkFileMDSvc> mFileService;\n  std::unique_ptr<QuarkHierarchicalView> mHierarchicalView;\n  std::unique_ptr<QuarkFileSystemView> mFilesystemView;\n  std::unique_ptr<QuarkContainerAccounting> mContainerAccounting;\n  std::unique_ptr<QuarkSyncTimeAccounting> mSyncAccounting;\n  std::unique_ptr<CacheRefreshListener> mCacheRefreshListener;\n  std::shared_ptr<QClPerfMonitor> mPerfMonitor; ///< QCl performance monitor\n};\n\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/NsQuarkdbPlugin.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/NsQuarkdbPlugin.hh\"\n#include \"namespace/ns_quarkdb/NamespaceGroup.hh\"\n#include <iostream>\n\n/*----------------------------------------------------------------------------*/\n#ifdef COVERAGE_BUILD\n// Forward declaration of gcov flush API\nextern \"C\" void __gcov_dump(void);\n\n//------------------------------------------------------------------------------\n// Profiling function flushing coverage data\n//------------------------------------------------------------------------------\nextern \"C\" void plugin_coverage()\n{\n  __gcov_dump();\n}\n#endif\n/*----------------------------------------------------------------------------*/\n\n//------------------------------------------------------------------------------\n// Plugin exit function called by the PluginManager when doing cleanup\n//------------------------------------------------------------------------------\nint32_t\nExitFunc()\n{\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Plugin registration entry point called by the PluginManager\n//------------------------------------------------------------------------------\nPF_ExitFunc\nPF_initPlugin(const PF_PlatformServices* services)\n{\n  std::cout << \"Register objects provided by NsQuarkdbPlugin ...\" << std::endl;\n  // Register namespace group\n  PF_RegisterParams param_group;\n  param_group.version.major = 0;\n  param_group.version.minor = 1;\n  param_group.CreateFunc = eos::NsQuarkdbPlugin::CreateGroup;\n  param_group.DestroyFunc = eos::NsQuarkdbPlugin::DestroyGroup;\n\n  std::map<std::string, PF_RegisterParams> map_obj = {\n    {\"NamespaceGroup\", param_group},\n  };\n\n  // Register all the provided object with the Plugin Manager\n  for (const auto& elem : map_obj) {\n    if (services->registerObject(elem.first.c_str(), &elem.second) != 0) {\n      std::cerr << \"Failed registering object \" << elem.first << std::endl;\n      return nullptr;\n    }\n  }\n\n  return ExitFunc;\n}\n\nEOSNSNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n//! Create namespace group\n//!\n//! @param services pointer to other services that the plugin manager might\n//!         provide\n//!\n//! @return pointer to namespace group\n//----------------------------------------------------------------------------\nvoid*\nNsQuarkdbPlugin::CreateGroup(PF_PlatformServices* services)\n{\n  return new QuarkNamespaceGroup();\n}\n\n//----------------------------------------------------------------------------\n//! Destroy namespace group\n//!\n//! @return 0 if successful, otherwise errno\n//----------------------------------------------------------------------------\nint32_t\nNsQuarkdbPlugin::DestroyGroup(void* obj)\n{\n  if(!obj) {\n    return -1;\n  }\n\n  delete static_cast<QuarkNamespaceGroup*>(obj);\n  return 0;\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/NsQuarkdbPlugin.hh",
    "content": "//------------------------------------------------------------------------------\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//! @brief  Namespace on Quarkdb plugin interface implementation\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"common/plugin_manager/Plugin.hh\"\n#include \"namespace/Namespace.hh\"\n\n//------------------------------------------------------------------------------\n//! Plugin exit function called by the PluginManager when doing cleanup\n//------------------------------------------------------------------------------\nextern \"C\" int32_t ExitFunc();\n\n//------------------------------------------------------------------------------\n//! Plugin registration entry point called by the PluginManager\n//------------------------------------------------------------------------------\nextern \"C\" PF_ExitFunc PF_initPlugin(const PF_PlatformServices* services);\n\nEOSNSNAMESPACE_BEGIN\n\n//! Forward declaration\nclass IContainerMDSvc;\n\n//------------------------------------------------------------------------------\n//! Class NsQuarkdbPlugin\n//------------------------------------------------------------------------------\nclass NsQuarkdbPlugin\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Create namespace group\n  //!\n  //! @param services pointer to other services that the plugin manager might\n  //!         provide\n  //!\n  //! @return pointer to namespace group\n  //----------------------------------------------------------------------------\n  static void* CreateGroup(PF_PlatformServices* services);\n\n  //----------------------------------------------------------------------------\n  //! Destroy namespace group\n  //!\n  //! @return 0 if successful, otherwise errno\n  //----------------------------------------------------------------------------\n  static int32_t DestroyGroup(void*);\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/QClPerformance.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//! @brief Class collecting qclient performance metrics\n//------------------------------------------------------------------------------\n\n#include \"namespace/ns_quarkdb/QClPerformance.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Send performance marker\n//------------------------------------------------------------------------------\nvoid\nQClPerfMonitor::SendPerfMarker(const std::string& name,\n                               unsigned long long value)\n{\n  using namespace std::chrono;\n\n  if (name == \"rtt_us\") {\n    if (mMinRtt > value) {\n      mMinRtt = value;\n    }\n\n    if (mMaxRtt < value) {\n      mMaxRtt = value;\n    }\n\n    if (mMinRtt) {\n      mAvgRtt = (mAvgRtt + value) / 2;\n    } else {\n      mAvgRtt += value;\n    }\n\n    bool found = false;\n    unsigned long long current_ts =\n      duration_cast<minutes>(system_clock::now().time_since_epoch()).count();\n    unsigned long long expire_ts = current_ts - 5;\n    std::unique_lock<std::mutex> lock(mMutex);\n\n    for (auto it = mMapTsToRtt.begin(); it != mMapTsToRtt.end(); /*none*/) {\n      if (it->first <= expire_ts) {\n        it = mMapTsToRtt.erase(it);\n      } else if (it->first == current_ts) {\n        if (value > it->second) {\n          it->second = value;\n        }\n\n        found = true;\n        break;\n      } else {\n        ++it;\n      }\n    }\n\n    if (!found) {\n      mMapTsToRtt[current_ts] = value;\n    }\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Collect performance metrics\n//------------------------------------------------------------------------------\nstd::map<std::string, unsigned long long>\nQClPerfMonitor::GetPerfMarkers() const\n{\n  using namespace std::chrono;\n  std::map<std::string, unsigned long long> resp;\n  resp[\"rtt_min\"] = mMinRtt;\n  resp[\"rtt_max\"] = mMaxRtt;\n  resp[\"rtt_avg\"] = mAvgRtt;\n  unsigned long long peak_1m {0ull};\n  unsigned long long peak_2m {0ull};\n  unsigned long long peak_5m {0ull};\n  unsigned long long current_ts =\n    duration_cast<minutes>(system_clock::now().time_since_epoch()).count();\n  {\n    std::unique_lock<std::mutex> lock(mMutex);\n\n    for (auto rit = mMapTsToRtt.rbegin(); rit != mMapTsToRtt.rend(); ++rit) {\n      if (rit->first == current_ts) {\n        peak_1m = rit->second;\n      }\n\n      if (rit->first >= current_ts - 1) {\n        if (rit->second > peak_2m) {\n          peak_2m = rit->second;\n        }\n      }\n\n      if (rit->first >= current_ts - 4) {\n        if (rit->second > peak_5m) {\n          peak_5m = rit->second;\n        }\n      }\n    }\n  }\n  resp[\"rtt_peak_1m\"] = peak_1m;\n  resp[\"rtt_peak_2m\"] = peak_2m;\n  resp[\"rtt_peak_5m\"] = peak_5m;\n  return resp;\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/QClPerformance.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//! @brief Class collecting qclient performance metrics\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"qclient/QCallback.hh\"\n#include <atomic>\n#include <limits>\n#include <mutex>\n#include <map>\n#include <string>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class QClPerfMonitor\n//------------------------------------------------------------------------------\nclass QClPerfMonitor: public qclient::QPerfCallback\n{\npublic:\n  QClPerfMonitor():\n    mMinRtt(std::numeric_limits<unsigned long long>::max()),\n    mMaxRtt(0ull), mAvgRtt(0ull)\n  {}\n\n  ~QClPerfMonitor() = default;\n\n  //--------------------------------------------------------------------------\n  //! Send performance marker\n  //!\n  //! @param desc string description of the marker\n  //! @param value value of the performance marker\n  //!\n  //! @note: any implementation of this needs to be fast not to block the\n  //!        qclient since this gets called from the main event loop\n  //--------------------------------------------------------------------------\n  virtual void SendPerfMarker(const std::string& name,\n                              unsigned long long value);\n\n  //----------------------------------------------------------------------------\n  //! Collect performance metrics\n  //!\n  //! @return get map with performance metrics and their values\n  //----------------------------------------------------------------------------\n  std::map<std::string, unsigned long long> GetPerfMarkers() const;\n\nprivate:\n  std::atomic<unsigned long long> mMinRtt;\n  std::atomic<unsigned long long> mMaxRtt;\n  std::atomic<unsigned long long> mAvgRtt;\n  //! Map of timestamps in minutes to peak value in microseconds\n  std::map<unsigned long long, unsigned long long> mMapTsToRtt;\n  mutable std::mutex mMutex;\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/QdbContactDetails.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Helper struct to specify all necessary details for contacting a\n//!        a QDB cluster\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_QDB_CONTACT_DETAILS_HH\n#define EOS_NS_QDB_CONTACT_DETAILS_HH\n\n#include \"namespace/Namespace.hh\"\n\n#include <qclient/Members.hh>\n#include <qclient/Options.hh>\n#include <qclient/Handshake.hh>\n#include <chrono>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Helper struct to specify all necessary details for contacting a QDB cluster\n//------------------------------------------------------------------------------\nclass QdbContactDetails\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Empty constructor\n  //----------------------------------------------------------------------------\n  QdbContactDetails() = default;\n\n  //----------------------------------------------------------------------------\n  //! Constructor taking qclient::Members and password\n  //----------------------------------------------------------------------------\n  QdbContactDetails(const qclient::Members& memb, const std::string& pw)\n  {\n    members = memb;\n    password = pw;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check whether object is empty.\n  //! It's valid that the password is empty, for now.\n  //! TODO(gbitzes): Maybe in the future, disallow contacting unsecured\n  //! QDB instances.\n  //----------------------------------------------------------------------------\n  bool empty() const\n  {\n    return members.empty();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Construct reasonable QClient options, using the password as handshake\n  //! if available.\n  //----------------------------------------------------------------------------\n  qclient::Options constructOptions() const\n  {\n    qclient::Options opts;\n    opts.transparentRedirects = true;\n    opts.retryStrategy = qclient::RetryStrategy::WithTimeout(\n                           std::chrono::minutes(2));\n\n    if (!password.empty()) {\n      opts.handshake.reset(new qclient::HmacAuthHandshake(password));\n    }\n\n    return opts;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Construct reasonable QClient subscription options, using the password as\n  //! handshake if available.\n  //----------------------------------------------------------------------------\n  qclient::SubscriptionOptions constructSubscriptionOptions() const\n  {\n    qclient::SubscriptionOptions opts;\n\n    if (!password.empty()) {\n      opts.handshake.reset(new qclient::HmacAuthHandshake(password));\n    }\n\n    opts.retryStrategy = qclient::RetryStrategy::WithTimeout(\n                           std::chrono::minutes(2));\n    opts.usePushTypes = true;\n    return opts;\n  }\n\n  qclient::Members members;\n  std::string password;\n};\n\nEOSNSNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "namespace/ns_quarkdb/VersionEnforcement.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief  Function to enforce a minimum version of QuarkDB\n//------------------------------------------------------------------------------\n\n#include \"namespace/ns_quarkdb/VersionEnforcement.hh\"\n#include \"common/Logging.hh\"\n#include <qclient/QClient.hh>\n#include <qclient/QuarkDBVersion.hh>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Check if quarkdb version is good enough\n//------------------------------------------------------------------------------\nbool enforceQuarkDBVersion(qclient::QClient* qcl)\n{\n  qclient::redisReplyPtr reply = qcl->exec(\"quarkdb-version\").get();\n  eos_static_info(\"QuarkDB version: %s\",\n                  qclient::describeRedisReply(reply).c_str());\n  std::string str = std::string(reply->str, reply->len);\n  qclient::QuarkDBVersion actual;\n\n  if (!qclient::QuarkDBVersion::fromString(str, actual)) {\n    eos_static_crit(\"Could not parse reply to quarkdb-version\");\n    return false;\n  }\n\n  qclient::QuarkDBVersion target(0, 4, 2, \"\");\n\n  if (target > actual) {\n    eos_static_crit(\"Outdated QuarkDB version (%s), we need at least %s. Update!\",\n                    actual.toString().c_str(), target.toString().c_str());\n    return false;\n  }\n\n  return true;\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/VersionEnforcement.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief  Function to enforce a minimum version of QuarkDB\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"qclient/QuarkDBVersion.hh\"\n\nnamespace qclient {\n  class QClient;\n}\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Check if quarkdb version is good enough\n//------------------------------------------------------------------------------\nbool enforceQuarkDBVersion(qclient::QClient *qcl);\n\nEOSNSNAMESPACE_END\n\n"
  },
  {
    "path": "namespace/ns_quarkdb/accounting/ContainerAccounting.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/accounting/ContainerAccounting.hh\"\n#include <iostream>\n#include <chrono>\n\nEOSNSNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n// Constructor\n//----------------------------------------------------------------------------\nQuarkContainerAccounting::QuarkContainerAccounting(IContainerMDSvc* svc, int32_t update_interval)\n  : mAccumulateIndx(0), mCommitIndx(1),\n    mUpdateIntervalSec(update_interval), mContainerMDSvc(svc)\n{\n  mBatch.resize(2);\n\n  // If update interval is 0 then we disable async updates\n  if (mUpdateIntervalSec) {\n    mThread.reset(&QuarkContainerAccounting::AssistedPropagateUpdates, this);\n    mQueueForUpdateThread.reset(&QuarkContainerAccounting::AssistedQueueForUpdate,this);\n  }\n}\n\n//----------------------------------------------------------------------------\n// Constructor\n//----------------------------------------------------------------------------\nQuarkContainerAccounting::~QuarkContainerAccounting()\n{\n  // Stop the AsyncQueueUpdate thread by queuing containerID = 0\n  mIdTreeInfosToUpdateQueue.emplace(0,TreeInfos{0,0,0});\n  if (mUpdateIntervalSec) {\n    mThread.join();\n    mQueueForUpdateThread.join();\n  }\n}\n\n//----------------------------------------------------------------------------\n// Notifications about changes in the main view\n//----------------------------------------------------------------------------\nvoid\nQuarkContainerAccounting::fileMDChanged(IFileMDChangeListener::Event* e)\n{\n  switch (e->action) {\n  // We are only interested in SizeChange events\n  case IFileMDChangeListener::SizeChange:\n    // e->file can be nullptr here\n    if ((e->file && e->file->getContainerId() == 0) || !e->file) {\n      // NOTE: This is an ugly hack. The file object has not reference to the\n      // container id, therefore we hijack the \"location\" member of the Event\n      // class to pass in the container id.\n      QueueForUpdate(e->location, e->treeChange);\n    } else {\n      QueueForUpdate(e->file->getContainerId(), e->treeChange);\n    }\n\n    break;\n\n  default:\n    break;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Add tree\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerAccounting::AddTree(IContainerMD* obj, TreeInfos treeAccounting)\n{\n  QueueForUpdate(obj->getId(), treeAccounting);\n}\n\n//-------------------------------------------------------------------------------\n// Remove tree\n//-------------------------------------------------------------------------------\nvoid\nQuarkContainerAccounting::RemoveTree(IContainerMD* obj, TreeInfos treeAccounting)\n{\n  QueueForUpdate(obj->getId(), -treeAccounting);\n}\n\n//------------------------------------------------------------------------------\n// Queue file info for update\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerAccounting::QueueForUpdate(IContainerMD::id_t id, TreeInfos treeAccounting)\n{\n  if(id) {\n    // The condition to stop the queueing thread is that the id = 0. The minimum container id is 1 and\n    // corresponds to \"/\"\n    // We therefore prevent users to queue the container id = 0 for update (which should not happen!)\n    mIdTreeInfosToUpdateQueue.emplace(id,treeAccounting);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Propagate updates in the hierarchical structure. Method ran by an\n// asynchronous thread.\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerAccounting::AssistedPropagateUpdates(ThreadAssistant& assistant)\nnoexcept\n{\n  PropagateUpdates(&assistant);\n}\n\n//------------------------------------------------------------------------------\n// Submits container and size changes in order to propagate them to the hierarchical structure later on.\n// Method ran by an asynchronous thread.\n//------------------------------------------------------------------------------\nvoid QuarkContainerAccounting::AssistedQueueForUpdate(ThreadAssistant& assistant) noexcept\n{\n  AsyncQueueForUpdate(&assistant);\n}\n\n//------------------------------------------------------------------------------\n// Propagate updates in the hierarchical structure\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerAccounting::PropagateUpdates(ThreadAssistant* assistant)\n{\n  while ((assistant && !assistant->terminationRequested()) || (!assistant)) {\n    {\n      // Update the indexes to have the async thread working on the batch to\n      // commit and the incoming updates to go to the batch to update\n      std::lock_guard<std::mutex> scope_lock(mMutexBatch);\n      std::swap(mAccumulateIndx, mCommitIndx);\n    }\n\n    auto& batch = mBatch[mCommitIndx];\n\n    if(!batch.mMap.empty()){\n      for (auto const& elem : batch.mMap) {\n        try {\n          auto cont = mContainerMDSvc->getContainerMD(elem.first);\n          eos::MDLocking::ContainerWriteLock contLock(cont.get());\n          cont->updateTreeSize(elem.second.dsize);\n          cont->updateTreeFiles(elem.second.dtreefiles);\n          cont->updateTreeContainers(elem.second.dtreecontainers);\n          mContainerMDSvc->updateStore(cont.get());\n        } catch (const MDException& e) {\n          // TODO: (esindril) error message using default logging\n          continue;\n        }\n      }\n    }\n\n    batch.mMap.clear();\n\n    if (mUpdateIntervalSec) {\n      if (assistant) {\n        assistant->wait_for(std::chrono::seconds(mUpdateIntervalSec));\n      } else {\n        std::this_thread::sleep_for(std::chrono::seconds(mUpdateIntervalSec));\n      }\n    } else {\n      break;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// For each containerId and its associated size modified, this function\n// will go up the tree from container to '/' and submit the entire tree for\n// modification in the PropagateUpdate thread\n//------------------------------------------------------------------------------\nvoid QuarkContainerAccounting::AsyncQueueForUpdate(ThreadAssistant* assistant)\n{\n  std::pair<eos::IContainerMD::id_t,TreeInfos> contIdTreeInfos;\n  std::vector<IContainerMD::id_t> idsToUpdate;\n\n  while ((assistant && !assistant->terminationRequested()) || (!assistant)) {\n    uint16_t deepness = 0;\n\n    mIdTreeInfosToUpdateQueue.wait_pop(contIdTreeInfos);\n    if(!contIdTreeInfos.first) {\n      // Container ID = 0 (see ~QuarkContainerAccounting()), we\n      // stop this thread\n      break;\n    }\n\n    IContainerMD::id_t id = contIdTreeInfos.first;\n    // Go up the tree and give the ids to update to the batch that will\n    // be taken by the PropagateUpdate thread\n    while ((id > 1) && (deepness < 255)) {\n      try {\n        idsToUpdate.push_back(id);\n        auto cont = mContainerMDSvc->getContainerMD(id);\n        // One operation, no need to lock the container\n        id = cont->getParentId();\n        ++deepness;\n      } catch (const MDException& e) {\n        // TODO (esindril): error message using default logging\n        break;\n      }\n    }\n\n    std::lock_guard<std::mutex> scope_lock(mMutexBatch);\n    auto& batch = mBatch[mAccumulateIndx];\n\n    for (auto idToUpdate : idsToUpdate) {\n      auto it_map = batch.mMap.find(idToUpdate);\n\n      if (it_map != batch.mMap.end()) {\n        it_map->second += contIdTreeInfos.second;\n      } else {\n        batch.mMap.emplace(idToUpdate, contIdTreeInfos.second);\n      }\n    }\n\n    idsToUpdate.clear();\n  }\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/accounting/ContainerAccounting.hh",
    "content": "//------------------------------------------------------------------------------\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Container subtree accounting\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"common/AssistedThread.hh\"\n#include <mutex>\n#include <thread>\n#include <vector>\n#include <utility>\n#include <unordered_map>\n#include <atomic>\n#include \"common/ConcurrentQueue.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Container subtree accounting listener\n//------------------------------------------------------------------------------\nclass QuarkContainerAccounting : public IFileMDChangeListener\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param svc container metadata service\n  //! @param update_interval interval in seconds when updates are propagated\n  //----------------------------------------------------------------------------\n  QuarkContainerAccounting(IContainerMDSvc* svc,\n                           int32_t update_interval = 5);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~QuarkContainerAccounting();\n\n  //----------------------------------------------------------------------------\n  //! Delete copy/move constructor and assignment operators\n  //----------------------------------------------------------------------------\n  QuarkContainerAccounting(const QuarkContainerAccounting& other) = delete;\n  QuarkContainerAccounting& operator=(const QuarkContainerAccounting& other) =\n    delete;\n  QuarkContainerAccounting(QuarkContainerAccounting&& other) = delete;\n  QuarkContainerAccounting& operator=(QuarkContainerAccounting&& other) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Notify me about the changes in the main view\n  //----------------------------------------------------------------------------\n  virtual void fileMDChanged(IFileMDChangeListener::Event* e);\n\n  //----------------------------------------------------------------------------\n  //! Notify me about files when recovering from changelog\n  //----------------------------------------------------------------------------\n  virtual void\n  fileMDRead(IFileMD* obj) {}\n\n  //----------------------------------------------------------------------------\n  //! Recheck the current file object and make any modifications necessary so\n  //! that the information is consistent in the back-end KV store.\n  //!\n  //! @param file file object to be checked\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool\n  fileMDCheck(IFileMD* file)\n  {\n    return true;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Add tree\n  //!\n  //! @param obj container where the tree information should be added\n  //! @param treeAccounting tree accounting information to be updated\n  //----------------------------------------------------------------------------\n  void AddTree(IContainerMD* obj, TreeInfos treeAccounting);\n\n  //----------------------------------------------------------------------------\n  //! Remove tree\n  //!\n  //! @param obj container where the tree should be removed from\n  //! @param dsize size of the subtree to be removed\n  //----------------------------------------------------------------------------\n  void RemoveTree(IContainerMD* obj, TreeInfos treeAccounting);\n\n  //----------------------------------------------------------------------------\n  //! Queue info for update\n  //!\n  //! @param pid container id\n  //! @param dsize size change\n  //----------------------------------------------------------------------------\n  void QueueForUpdate(IContainerMD::id_t pid, TreeInfos treeInfos);\n\n  //----------------------------------------------------------------------------\n  //! Propagate updates in the hierarchical structure\n  //!\n  //! @param assistant thread doing the propagation or null by default if the\n  //!        update should be done in the calling thread.\n  //----------------------------------------------------------------------------\n  void PropagateUpdates(ThreadAssistant* assistant = nullptr);\n\n  //----------------------------------------------------------------------------\n  //! Queue container ids and size to update the tree size\n  //!\n  //! @param assistant thread doing the queueing or null by default if the\n  //!        update should be done in the calling thread.\n  //----------------------------------------------------------------------------\n  void AsyncQueueForUpdate(ThreadAssistant * assistant = nullptr);\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Propagate updates in the hierarchical structure. Method ran by the\n  //! asynchronous thread.\n  //!\n  //! @param assistant thread doing the propagation\n  //----------------------------------------------------------------------------\n  void AssistedPropagateUpdates(ThreadAssistant& assistant) noexcept;\n\n  //----------------------------------------------------------------------------\n  //! Queue containerIds and size to update in thein the hierarchical structure. Method ran by the\n  //! asynchronous thread.\n  //!\n  //! @param assistant thread doing the propagation\n  //----------------------------------------------------------------------------\n  void AssistedQueueForUpdate(ThreadAssistant& assistant) noexcept;\n\n  //! Update structure containing the nodes that need an update. We try to\n  //! optimise the number of updates to the backend by computing the final\n  //! size deltas from a number of individual updates.\n  struct UpdateT {\n    std::unordered_map<IContainerMD::id_t, TreeInfos> mMap; ///< Map updates\n  };\n\n  //! Vector of two elements containing the batch which is currently being\n  //! accumulated and the batch which is being committed to the namespace by\n  //! the asynchronous thread\n  std::vector<UpdateT> mBatch;\n  std::mutex mMutexBatch; ///< Mutex protecting access to the updates batch\n  uint8_t mAccumulateIndx; ///< Index of the batch accumulating updates\n  uint8_t mCommitIndx; ///< Index o the batch committing updates\n  AssistedThread mThread; ///< Thread updating the namespace\n  AssistedThread mQueueForUpdateThread; ///< Thread update queueing thread\n  uint32_t mUpdateIntervalSec; ///< Interval in seconds when updates are pushed\n  IContainerMDSvc* mContainerMDSvc; ///< container MD service\n  eos::common::ConcurrentQueue<std::pair<IContainerMD::id_t, TreeInfos>>  mIdTreeInfosToUpdateQueue; ///< Queue containing containerIds and their corresponding infos to update\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/accounting/FileSystemHandler.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/persistency/RequestBuilder.hh\"\n#include \"namespace/ns_quarkdb/accounting/FileSystemHandler.hh\"\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n#include \"namespace/utils/FileListRandomPicker.hh\"\n#include \"common/Assert.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor.\n//------------------------------------------------------------------------------\nFileSystemHandler::FileSystemHandler(IFileMD::location_t loc,\n                                     folly::Executor* executor,\n                                     qclient::QClient* qcl,\n                                     MetadataFlusher* flusher,\n                                     bool unlinked,\n                                     bool fake_clock)\n  : location(loc), pExecutor(executor), pQcl(qcl), pFlusher(flusher),\n    mLastCacheLoadTS(0ull), mClock(fake_clock)\n{\n  if (unlinked) {\n    target = Target::kUnlinked;\n  } else {\n    target = Target::kRegular;\n  }\n\n  mContents.set_deleted_key(0);\n  mContents.set_empty_key(0xffffffffffffffffll);\n}\n\n//------------------------------------------------------------------------------\n// Constructor for the special case of \"no replica list\".\n//------------------------------------------------------------------------------\nFileSystemHandler::FileSystemHandler(folly::Executor* executor,\n                                     qclient::QClient* qcl,\n                                     MetadataFlusher* flusher,\n                                     IsNoReplicaListTag tag)\n  : location(0), pExecutor(executor), pQcl(qcl), pFlusher(flusher)\n{\n  target = Target::kNoReplicaList;\n  mContents.set_deleted_key(0);\n  mContents.set_empty_key(0xffffffffffffffffll);\n}\n\n//------------------------------------------------------------------------------\n// Ensure contents have been loaded into the cache. If so, returns\n// immediatelly. Otherwise, does requests to QDB to retrieve its contents.\n// Return value: \"this\" pointer.\n//------------------------------------------------------------------------------\nFileSystemHandler* FileSystemHandler::ensureContentsLoaded()\n{\n  using eos::common::SteadyClock;\n  mLastCacheLoadTS = SteadyClock::SecondsSinceEpoch(mClock.GetTime()).count();\n  return ensureContentsLoadedAsync().get();\n}\n\n//------------------------------------------------------------------------------\n// Ensure contents have been loaded into the cache. If so, returns\n// immediatelly. Otherwise, does requests to QDB to retrieve its contents.\n// Return value: \"this\" pointer.\n//------------------------------------------------------------------------------\nfolly::Future<FileSystemHandler*>\nFileSystemHandler::ensureContentsLoadedAsync()\n{\n  std::unique_lock<std::shared_timed_mutex> lock(mMutex);\n\n  if (mCacheStatus == CacheStatus::kNotLoaded) {\n    mChangeList.clear();\n    mCacheStatus = CacheStatus::kInFlight;\n    mSplitter = folly::FutureSplitter<FileSystemHandler*>(\n                  folly::via(pExecutor).then(&FileSystemHandler::triggerCacheLoad, this));\n    lock.unlock();\n    return mSplitter.getFuture();\n  }\n\n  return mSplitter.getFuture();\n}\n\n//------------------------------------------------------------------------------\n// Return redis key holding our target filesystem list.\n//------------------------------------------------------------------------------\nstd::string FileSystemHandler::getRedisKey() const\n{\n  if (target == Target::kRegular) {\n    return eos::RequestBuilder::keyFilesystemFiles(location);\n  } else if (target == Target::kUnlinked) {\n    return eos::RequestBuilder::keyFilesystemUnlinked(location);\n  }\n\n  eos_assert(target == Target::kNoReplicaList);\n  return fsview::sNoReplicaPrefix;\n}\n\n//------------------------------------------------------------------------------\n// Trigger load. Must only be called once.\n//------------------------------------------------------------------------------\nFileSystemHandler* FileSystemHandler::triggerCacheLoad()\n{\n  pFlusher->synchronize();\n  IFsView::FileList temporaryContents;\n  temporaryContents.set_deleted_key(0);\n  temporaryContents.set_empty_key(0xffffffffffffffffll);\n\n  for (auto it = getStreamingFileList(); it->valid(); it->next()) {\n    temporaryContents.insert(it->getElement());\n  }\n\n  // Now merge under lock, and additionally apply all entries we might have\n  // missed between triggering the cache load, and receiving the contents.\n  std::unique_lock<std::shared_timed_mutex> lock(mMutex);\n  eos_assert(mCacheStatus == CacheStatus::kInFlight);\n  mContents.swap(temporaryContents);\n  mChangeList.apply(mContents);\n  mChangeList.clear();\n  mCacheStatus = CacheStatus::kLoaded;\n  mContents.resize(0);\n  return this;\n}\n\n//------------------------------------------------------------------------------\n// Insert item.\n//------------------------------------------------------------------------------\nvoid FileSystemHandler::insert(FileIdentifier identifier)\n{\n  std::unique_lock<std::shared_timed_mutex> lock(mMutex);\n\n  if (mCacheStatus == CacheStatus::kNotLoaded) {\n    // discard, we're not storing the results in-memory at all\n  } else if (mCacheStatus == CacheStatus::kInFlight) {\n    // record into our ChangeList to apply later, once we've received the\n    // contents. This write is racing against cache loading, and may or may\n    // not be reflected in the contents.\n    mChangeList.push_back(identifier.getUnderlyingUInt64());\n  } else {\n    eos_assert(mCacheStatus == CacheStatus::kLoaded);\n    // Write directly into mContents\n    mContents.insert(identifier.getUnderlyingUInt64());\n  }\n\n  lock.unlock();\n  pFlusher->sadd(getRedisKey(), std::to_string(identifier.getUnderlyingUInt64()));\n}\n\n//------------------------------------------------------------------------------\n// Erase item.\n//------------------------------------------------------------------------------\nvoid FileSystemHandler::erase(FileIdentifier identifier)\n{\n  std::unique_lock<std::shared_timed_mutex> lock(mMutex);\n\n  if (mCacheStatus == CacheStatus::kNotLoaded) {\n    // discard, we're not storing the results in-memory at all\n  } else if (mCacheStatus == CacheStatus::kInFlight) {\n    // record into our ChangeList to apply later, once we've received the\n    // contents. This write is racing against cache loading, and may or may\n    // not be reflected in the contents.\n    mChangeList.erase(identifier.getUnderlyingUInt64());\n  } else {\n    eos_assert(mCacheStatus == CacheStatus::kLoaded);\n    // Write directly into mContents\n    mContents.erase(identifier.getUnderlyingUInt64());\n    mContents.resize(0);\n  }\n\n  lock.unlock();\n  pFlusher->srem(getRedisKey(), std::to_string(identifier.getUnderlyingUInt64()));\n}\n\n//------------------------------------------------------------------------------\n// Get number of file entries stored on this particular file system\n//------------------------------------------------------------------------------\nuint64_t FileSystemHandler::size()\n{\n  {\n    std::shared_lock<std::shared_timed_mutex> lock(mMutex);\n\n    if (mCacheStatus == CacheStatus::kLoaded) {\n      return mContents.size();\n    }\n  }\n  // Do direct call to the backend\n  qclient::redisReplyPtr reply = pQcl->exec(\"SCARD\", getRedisKey()).get();\n\n  if ((reply == nullptr) || (reply->type != REDIS_REPLY_INTEGER)) {\n    // Unexpected reply just return 0\n    return 0ull;\n  }\n\n  return reply->integer;\n}\n\n//------------------------------------------------------------------------------\n// Return iterator for this file system.\n//------------------------------------------------------------------------------\nstd::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n    FileSystemHandler::getFileList()\n{\n  ensureContentsLoaded();\n  return std::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n         (new eos::FileListIterator(mContents, mMutex));\n}\n\n//------------------------------------------------------------------------------\n// Return streaming iterator for this file system.\n//------------------------------------------------------------------------------\nstd::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n    FileSystemHandler::getStreamingFileList()\n{\n  return std::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n         (new eos::StreamingFileListIterator(*pQcl, getRedisKey()));\n}\n\n//------------------------------------------------------------------------------\n// Delete the entire filelist.\n//------------------------------------------------------------------------------\nvoid FileSystemHandler::nuke()\n{\n  std::unique_lock<std::shared_timed_mutex> lock(mMutex);\n  mContents.clear();\n  mContents.resize(0);\n  pFlusher->del(getRedisKey());\n}\n\n//------------------------------------------------------------------------------\n// Get an approximately random file in the filelist.\n//------------------------------------------------------------------------------\nbool FileSystemHandler::getApproximatelyRandomFile(IFileMD::id_t& res)\n{\n  ensureContentsLoaded();\n  std::shared_lock<std::shared_timed_mutex> lock(mMutex);\n  return pickRandomFile(mContents, res);\n}\n\n//------------------------------------------------------------------------------\n// Check whether a given id_t is contained in this filelist\n//------------------------------------------------------------------------------\nbool FileSystemHandler::hasFileId(IFileMD::id_t file)\n{\n  ensureContentsLoaded();\n  std::shared_lock<std::shared_timed_mutex> lock(mMutex);\n  return mContents.find(file) != mContents.end();\n}\n\n//------------------------------------------------------------------------------\n// Clear cache if it has been inactive during the given period\n//------------------------------------------------------------------------------\nvoid\nFileSystemHandler::clearCache(std::chrono::seconds inactive_timeout)\n{\n  using eos::common::SteadyClock;\n  using namespace std::chrono_literals;\n\n  if (inactive_timeout.count()) {\n    int64_t inactive_interval =\n      SteadyClock::SecondsSinceEpoch(mClock.GetTime()).count() - mLastCacheLoadTS;\n\n    if (inactive_timeout.count() > inactive_interval) {\n      return;\n    }\n  }\n\n  // Skip if mutex held by a long running operation\n  if (mMutex.try_lock_for(100ms)) {\n    if (mCacheStatus == CacheStatus::kLoaded) {\n      mContents.clear();\n      mContents.resize(0);\n      mCacheStatus = CacheStatus::kNotLoaded;\n    }\n\n    mMutex.unlock();\n  }\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/accounting/FileSystemHandler.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Class handling caching and access of individual filesystems\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_FILESYSTEM_HANDLER_HH\n#define EOS_NS_FILESYSTEM_HANDLER_HH\n\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/IFsView.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/ns_quarkdb/accounting/SetChangeList.hh\"\n#include \"qclient/structures/QSet.hh\"\n#include <folly/futures/FutureSplitter.h>\n#include <folly/executors/Async.h>\n#include \"common/Assert.hh\"\n#include \"common/SteadyClock.hh\"\n\nnamespace qclient\n{\nclass QClient;\n}\n\nEOSNSNAMESPACE_BEGIN\n\nclass MetadataFlusher;\nstruct IsNoReplicaListTag {};\n\n//------------------------------------------------------------------------------\n//! Iterator to go through the contents of a FileSystemHandler. Keeps\n//! the corresponding list read-locked during its lifetime.\n//------------------------------------------------------------------------------\nclass FileListIterator : public ICollectionIterator<IFileMD::id_t>\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor.\n  //----------------------------------------------------------------------------\n  FileListIterator(const IFsView::FileList& fileList,\n                   std::shared_timed_mutex& mtx)\n    : pFileList(fileList), mLock(mtx)\n  {\n    mIterator = pFileList.begin();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor.\n  //----------------------------------------------------------------------------\n  virtual ~FileListIterator() {}\n\n  //----------------------------------------------------------------------------\n  //! Check whether the iterator is still valid.\n  //----------------------------------------------------------------------------\n  virtual bool valid() override\n  {\n    return mIterator != pFileList.end();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get current element.\n  //----------------------------------------------------------------------------\n  virtual IFileMD::id_t getElement() override\n  {\n    return *mIterator;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Progress iterator.\n  //----------------------------------------------------------------------------\n  virtual void next() override\n  {\n    mIterator++;\n  }\n\nprivate:\n  const IFsView::FileList& pFileList;\n  std::shared_lock<std::shared_timed_mutex> mLock;\n  IFsView::FileList::const_iterator mIterator;\n};\n\n//------------------------------------------------------------------------------\n//! Streaming iterator to go through the contents of a FileSystemHandler.\n//!\n//! Elements which are added, or deleted while iteration is ongoing, may or\n//! may not be in the results.\n//!\n//! Also, watch out for races related to the flusher.. Use only if a weakly\n//! consistent view is acceptable.\n//------------------------------------------------------------------------------\nclass StreamingFileListIterator : public ICollectionIterator<IFileMD::id_t>\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor.\n  //----------------------------------------------------------------------------\n  StreamingFileListIterator(qclient::QClient& qcl, const std::string& key)\n    : mQSet(qcl, key), it(mQSet.getIterator()) {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor.\n  //----------------------------------------------------------------------------\n  virtual ~StreamingFileListIterator() {}\n\n  //----------------------------------------------------------------------------\n  //! Check whether the iterator is still valid.\n  //----------------------------------------------------------------------------\n  virtual bool valid() override\n  {\n    return it.valid();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get current element.\n  //----------------------------------------------------------------------------\n  virtual IFileMD::id_t getElement() override\n  {\n    return std::stoull(it.getElement());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Progress iterator.\n  //----------------------------------------------------------------------------\n  virtual void next() override\n  {\n    return it.next();\n  }\n\nprivate:\n  qclient::QSet mQSet;\n  qclient::QSet::Iterator it;\n};\n\n\nclass FileSystemHandler\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor.\n  //!\n  //! @param location file system ID\n  //! @param qcl QClient object to use for loading the view from QDB\n  //! @param flusher Flusher object for propagating updates to the backend\n  //! @param unlinked whether we want the unlinked file list, or the regular one\n  //! @param fake_clock if true is fake clock implementation for tests\n  //----------------------------------------------------------------------------\n  FileSystemHandler(IFileMD::location_t location, folly::Executor* pExecutor,\n                    qclient::QClient* qcl, MetadataFlusher* flusher,\n                    bool unlinked, bool fake_clock = false);\n\n  //----------------------------------------------------------------------------\n  //! Constructor for the special case of \"no replica list\".\n  //!\n  //! @param location file system ID\n  //! @param qcl QClient object to use for loading the view from QDB\n  //! @param flusher Flusher object for propagating updates to the backend\n  //! @param Tag for dispatching to this constructor overload\n  //----------------------------------------------------------------------------\n  FileSystemHandler(folly::Executor* pExecutor, qclient::QClient* qcl,\n                    MetadataFlusher* flusher, IsNoReplicaListTag tag);\n\n  //----------------------------------------------------------------------------\n  //! Ensure contents have been loaded into the cache. If so, returns\n  //! immediatelly. Otherwise, does requests to QDB to retrieve its contents.\n  //! Return value: \"this\" pointer.\n  //----------------------------------------------------------------------------\n  FileSystemHandler* ensureContentsLoaded();\n\n  //----------------------------------------------------------------------------\n  //! Async version of ensureContentsLoaded.\n  //----------------------------------------------------------------------------\n  folly::Future<FileSystemHandler*> ensureContentsLoadedAsync();\n\n  //----------------------------------------------------------------------------\n  //! Insert item.\n  //----------------------------------------------------------------------------\n  void insert(FileIdentifier identifier);\n\n  //----------------------------------------------------------------------------\n  //! Erase item.\n  //----------------------------------------------------------------------------\n  void erase(FileIdentifier identifier);\n\n  //----------------------------------------------------------------------------\n  //! Get number of file entries stored on this particular file system\n  //----------------------------------------------------------------------------\n  uint64_t size();\n\n  //----------------------------------------------------------------------------\n  //! Return redis key holding our target filesystem list.\n  //----------------------------------------------------------------------------\n  std::string getRedisKey() const;\n\n  //----------------------------------------------------------------------------\n  //! Return iterator for this file system. Note that the iterator keeps\n  //! this filesystem read-locked during its entire lifetime.\n  //----------------------------------------------------------------------------\n  std::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n      getFileList();\n\n  //----------------------------------------------------------------------------\n  //! Retrieve streaming iterator to go through the contents of a\n  //! FileSystemHandler.\n  //!\n  //! Elements which are added, or deleted while iteration is ongoing, may or\n  //! may not be in the results.\n  //!\n  //! Also, watch out for races related to the flusher.. Use only if a weakly\n  //! consistent view is acceptable.\n  //----------------------------------------------------------------------------\n  std::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n      getStreamingFileList();\n\n  //----------------------------------------------------------------------------\n  //! Delete the entire filelist.\n  //----------------------------------------------------------------------------\n  void nuke();\n\n  //----------------------------------------------------------------------------\n  //! Get an approximately random file in the filelist.\n  //----------------------------------------------------------------------------\n  bool getApproximatelyRandomFile(IFileMD::id_t& res);\n\n  //----------------------------------------------------------------------------\n  //! Check whether a given id_t is contained in this filelist\n  //----------------------------------------------------------------------------\n  bool hasFileId(IFileMD::id_t file);\n\n  //----------------------------------------------------------------------------\n  //! Clear cache if given timeout is exceeded\n  //!\n  //! @param inactive_timeout timeout in seconds since the last time there was\n  //!        a call that required the entries to be actually loaded in memory.\n  //!        If inactive timeout is 0 then the cache is cleared immediately. By\n  //!        default once every 30 minutes.\n  //----------------------------------------------------------------------------\n  void clearCache(std::chrono::seconds inactive_timeout =\n                    std::chrono::seconds(30 * 60));\n\nprivate:\n#ifdef IN_TEST_HARNESS\npublic:\n#endif\n  //----------------------------------------------------------------------------\n  //! Cache status states\n  //----------------------------------------------------------------------------\n  enum class CacheStatus {\n    kNotLoaded,\n    kInFlight,\n    kLoaded\n  };\n\n  CacheStatus mCacheStatus =\n    CacheStatus::kNotLoaded; ///< Stores caching status for this fs\n\n  enum class Target {\n    kRegular,\n    kUnlinked,\n    kNoReplicaList\n  };\n\n  Target target;                            ///< The filesystem list type this class is targetting.\n  IFileMD::location_t location;             ///< Filesystem ID, if available\n  folly::Executor* pExecutor;               ///< Folly executor\n  qclient::QClient* pQcl;                   ///< QClient object\n  MetadataFlusher* pFlusher;                ///< Metadata flusher object\n  mutable std::shared_timed_mutex mMutex;           ///< Object mutex\n  //! Actual contents. May be incomplete if mCacheStatus != kLoaded.\n  IFsView::FileList mContents;\n  //! ChangeList for what happens when cache loading is in progress.\n  SetChangeList<IFileMD::id_t> mChangeList;\n  folly::FutureSplitter<FileSystemHandler*> mSplitter;\n  //! Timestamp of the last mandatory cache load attempt. This value will be\n  //! used to decide when the cache contents can be dropped.\n  std::atomic<uint64_t> mLastCacheLoadTS;\n  eos::common::SteadyClock mClock;\n\n  //----------------------------------------------------------------------------\n  //! Trigger cache load. Must only be called once.\n  //----------------------------------------------------------------------------\n  FileSystemHandler* triggerCacheLoad();\n\n  //----------------------------------------------------------------------------\n  //! Get cache status\n  //----------------------------------------------------------------------------\n  inline CacheStatus getCacheStatus() const\n  {\n    std::unique_lock<std::shared_timed_mutex> lock(mMutex);\n    return mCacheStatus;\n  }\n};\n\nEOSNSNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "namespace/ns_quarkdb/accounting/FileSystemView.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/accounting/FileSystemView.hh\"\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n#include \"namespace/ns_quarkdb/persistency/RequestBuilder.hh\"\n#include \"namespace/ns_quarkdb/ConfigurationParser.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"namespace/ns_quarkdb/FileMD.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/Logging.hh\"\n#include \"qclient/structures/QScanner.hh\"\n#include \"qclient/structures/QSet.hh\"\n#include <iostream>\n#include <folly/executors/IOThreadPoolExecutor.h>\n\nEOSNSNAMESPACE_BEGIN\n\nconst std::chrono::minutes QuarkFileSystemView::sCacheCleanerTimeout\n{\n  45\n};\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nQuarkFileSystemView::QuarkFileSystemView(qclient::QClient* qcl,\n    MetadataFlusher* flusher)\n  : pFlusher(flusher), pQcl(qcl), mExecutor(new folly::IOThreadPoolExecutor(8))\n{ }\n\n//-----------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nQuarkFileSystemView::~QuarkFileSystemView()\n{\n  mCacheCleanerThread.join();\n}\n\n//------------------------------------------------------------------------------\n// Configure the container service\n//------------------------------------------------------------------------------\nvoid\nQuarkFileSystemView::configure(const std::map<std::string, std::string>& config)\n{\n  // No configuration to read, everything we need has been passed to the\n  // constructor already.\n  auto start = std::time(nullptr);\n  loadFromBackend();\n  auto end = std::time(nullptr);\n  std::chrono::seconds duration(end - start);\n  eos_static_info(\"msg=\\\"FileSystemView loadFromBackend\\\" duration=%llus\",\n                  duration.count());\n  mNoReplicas.reset(new FileSystemHandler(mExecutor.get(), pQcl, pFlusher,\n                                          IsNoReplicaListTag()));\n  mCacheCleanerThread.reset(&QuarkFileSystemView::CleanCacheJob, this);\n  mCacheCleanerThread.setName(\"NSCacheCleaner\");\n}\n\n\n//------------------------------------------------------------------------------\n// Run cache cleanup of the different FileSystemHandle objects tracked by\n// the FileSystemView in order to keep the memory overhead under control\n//------------------------------------------------------------------------------\nvoid\nQuarkFileSystemView::CleanCacheJob(ThreadAssistant& assistant) noexcept\n{\n  while (!assistant.terminationRequested()) {\n    assistant.wait_for(sCacheCleanerTimeout);\n\n    if (assistant.terminationRequested()) {\n      break;\n    }\n\n    eos_static_info(\"%s\", \"msg=\\\"running file system clean cache job\\\"\");\n    // Collect list of all file system ids being tracked\n    std::set<uint32_t> fsids;\n    {\n      std::unique_lock<std::mutex> lock(mMutex);\n\n      for (const auto& elem : mFiles) {\n        fsids.insert(elem.first);\n      }\n\n      for (const auto& elem : mUnlinkedFiles) {\n        fsids.insert(elem.first);\n      }\n    }\n\n    for (const auto& fsid : fsids) {\n      auto* handle = fetchRegularFilelistIfExists(fsid);\n\n      if (handle) {\n        handle->clearCache();\n      }\n\n      handle = fetchUnlinkedFilelistIfExists(fsid);\n\n      if (handle) {\n        handle->clearCache();\n      }\n    }\n\n    mNoReplicas->clearCache();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Notify the me about changes in the main view\n//------------------------------------------------------------------------------\nvoid\nQuarkFileSystemView::fileMDChanged(IFileMDChangeListener::Event* e)\n{\n  std::string key, val;\n  QuarkFileMD* file = static_cast<QuarkFileMD*>(e->file);\n  qclient::QSet fs_set;\n\n  switch (e->action) {\n  //----------------------------------------------------------------------------\n  // New file has been created\n  //----------------------------------------------------------------------------\n  case IFileMDChangeListener::Created:\n    if (!file->isLink()) {\n      mNoReplicas->insert(file->getIdentifier());\n    }\n\n    break;\n\n  //----------------------------------------------------------------------------\n  // File has been deleted\n  //----------------------------------------------------------------------------\n  case IFileMDChangeListener::Deleted: {\n    mNoReplicas->erase(file->getIdentifier());\n    break;\n  }\n\n  //----------------------------------------------------------------------------\n  // Add location\n  //----------------------------------------------------------------------------\n  case IFileMDChangeListener::LocationAdded: {\n    FileSystemHandler* handler = initializeRegularFilelist(e->location);\n    handler->insert(file->getIdentifier());\n    mNoReplicas->erase(file->getIdentifier());\n    break;\n  }\n\n  //----------------------------------------------------------------------------\n  // Remove location.\n  //\n  // Perform destructive actions (ie erase) at the end.\n  // This ensures that if we crash in the middle, we don't lose data, just\n  // become inconsistent.\n  //----------------------------------------------------------------------------\n  case IFileMDChangeListener::LocationRemoved: {\n    if (!file->getNumUnlinkedLocation() && !file->getNumLocation()) {\n      mNoReplicas->insert(file->getIdentifier());\n    }\n\n    FileSystemHandler* handlerUnlinked = fetchUnlinkedFilelistIfExists(e->location);\n\n    if (handlerUnlinked) {\n      handlerUnlinked->erase(file->getIdentifier());\n    }\n\n    break;\n  }\n\n  //----------------------------------------------------------------------------\n  // Unlink location.\n  //\n  // Perform destructive actions (ie erase) at the end.\n  // This ensures that if we crash in the middle, we don't lose data, just\n  // become inconsistent.\n  //----------------------------------------------------------------------------\n  case IFileMDChangeListener::LocationUnlinked: {\n    FileSystemHandler* handlerUnlinked = initializeUnlinkedFilelist(e->location);\n    handlerUnlinked->insert(file->getIdentifier());\n    FileSystemHandler* handlerRegular = fetchRegularFilelistIfExists(e->location);\n\n    if (handlerRegular) {\n      handlerRegular->erase(file->getIdentifier());\n    }\n\n    break;\n  }\n\n  default:\n    break;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Recheck the current file object and make any modifications necessary so\n// that the information is consistent in the back-end KV store.\n//------------------------------------------------------------------------------\nbool\nQuarkFileSystemView::fileMDCheck(IFileMD* file)\n{\n  std::string key;\n  IFileMD::LocationVector replica_locs = file->getLocations();\n  IFileMD::LocationVector unlink_locs = file->getUnlinkedLocations();\n  bool has_no_replicas = replica_locs.empty() && unlink_locs.empty();\n  std::string cursor {\"0\"};\n  std::pair<std::string, std::vector<std::string>> reply;\n  qclient::AsyncHandler ah;\n  qclient::QSet no_replica_set(*pQcl, fsview::sNoReplicaPrefix);\n\n  // If file has no replicas make sure it's accounted for\n  if (has_no_replicas) {\n    no_replica_set.sadd_async(std::to_string(file->getId()), &ah);\n  } else {\n    no_replica_set.srem_async(std::to_string(file->getId()), &ah);\n  }\n\n  // Make sure all active locations are accounted for\n  qclient::QSet replica_set(*pQcl, \"\");\n\n  for (IFileMD::location_t location : replica_locs) {\n    replica_set.setKey(eos::RequestBuilder::keyFilesystemFiles(location));\n    replica_set.sadd_async(std::to_string(file->getId()), &ah);\n  }\n\n  // Make sure all unlinked locations are accounted for.\n  qclient::QSet unlink_set(*pQcl, \"\");\n\n  for (IFileMD::location_t location : unlink_locs) {\n    unlink_set.setKey(eos::RequestBuilder::keyFilesystemUnlinked(location));\n    unlink_set.sadd_async(std::to_string(file->getId()), &ah);\n  }\n\n  // Make sure there's no other filesystems that erroneously contain this file.\n  for (auto it = this->getFileSystemIterator(); it->valid(); it->next()) {\n    IFileMD::location_t fsid = it->getElement();\n\n    if (std::find(replica_locs.begin(), replica_locs.end(),\n                  fsid) == replica_locs.end()) {\n      replica_set.setKey(eos::RequestBuilder::keyFilesystemFiles(fsid));\n      replica_set.srem_async(std::to_string(file->getId()), &ah);\n    }\n\n    if (std::find(unlink_locs.begin(), unlink_locs.end(),\n                  fsid) == unlink_locs.end()) {\n      unlink_set.setKey(eos::RequestBuilder::keyFilesystemUnlinked(fsid));\n      unlink_set.srem_async(std::to_string(file->getId()), &ah);\n    }\n  }\n\n  // Wait for all async responses\n  return ah.Wait();\n}\n\n//------------------------------------------------------------------------------\n// Get iterator object to run through all currently active filesystem IDs\n//------------------------------------------------------------------------------\nstd::shared_ptr<ICollectionIterator<IFileMD::location_t>>\n    QuarkFileSystemView::getFileSystemIterator()\n{\n  std::unique_lock<std::mutex> lock(mMutex);\n  return std::shared_ptr<ICollectionIterator<IFileMD::location_t>>\n         (new ListFileSystemIterator(mFiles));\n}\n\n//----------------------------------------------------------------------------\n// Get iterator to list of files on a particular file system\n//----------------------------------------------------------------------------\nstd::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n    QuarkFileSystemView::getFileList(IFileMD::location_t location)\n{\n  FileSystemHandler* handler = fetchRegularFilelistIfExists(location);\n\n  if (handler) {\n    return handler->getFileList();\n  }\n\n  return nullptr;\n}\n\n//----------------------------------------------------------------------------\n// Get streaming iterator to list of files on a particular file system\n//----------------------------------------------------------------------------\nstd::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n    QuarkFileSystemView::getStreamingFileList(IFileMD::location_t location)\n{\n  FileSystemHandler* handler = fetchRegularFilelistIfExists(location);\n\n  if (handler) {\n    return handler->getStreamingFileList();\n  }\n\n  return nullptr;\n}\n\n//------------------------------------------------------------------------------\n// Erase an entry from all filesystem view collections\n//------------------------------------------------------------------------------\nvoid\nQuarkFileSystemView::eraseEntry(IFileMD::location_t location, IFileMD::id_t fid)\n{\n  {\n    FileSystemHandler* handler = fetchRegularFilelistIfExists(location);\n\n    if (handler) {\n      handler->erase(FileIdentifier(fid));\n    }\n  }\n  {\n    FileSystemHandler* handler = fetchUnlinkedFilelistIfExists(location);\n\n    if (handler) {\n      handler->erase(FileIdentifier(fid));\n    }\n  }\n  mNoReplicas->erase(FileIdentifier(fid));\n  return ;\n}\n\n\n//----------------------------------------------------------------------------\n// Get an approximately random file residing within the given filesystem.\n//----------------------------------------------------------------------------\nbool QuarkFileSystemView::getApproximatelyRandomFileInFs(IFileMD::location_t\n    location,\n    IFileMD::id_t& retval)\n{\n  FileSystemHandler* handler = fetchRegularFilelistIfExists(location);\n\n  if (handler) {\n    return handler->getApproximatelyRandomFile(retval);\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Get iterator to list of unlinked files on a particular file system\n//------------------------------------------------------------------------------\nstd::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n    QuarkFileSystemView::getUnlinkedFileList(IFileMD::location_t location)\n{\n  FileSystemHandler* handlerUnlinked = fetchUnlinkedFilelistIfExists(location);\n\n  if (handlerUnlinked) {\n    return handlerUnlinked->getFileList();\n  }\n\n  return nullptr;\n}\n\n//------------------------------------------------------------------------------\n// Get iterator to list of files without replicas\n//------------------------------------------------------------------------------\nstd::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n    QuarkFileSystemView::getNoReplicasFileList()\n{\n  return mNoReplicas->getFileList();\n}\n\n//------------------------------------------------------------------------------\n// Get number of files with no replicas\n//------------------------------------------------------------------------------\nuint64_t\nQuarkFileSystemView::getNumNoReplicasFiles()\n{\n  return mNoReplicas->size();\n}\n\n//------------------------------------------------------------------------------\n// Get number of files on the given file system\n//------------------------------------------------------------------------------\nuint64_t\nQuarkFileSystemView::getNumFilesOnFs(IFileMD::location_t fs_id)\n{\n  FileSystemHandler* handler = fetchRegularFilelistIfExists(fs_id);\n\n  if (handler) {\n    return handler->size();\n  }\n\n  return 0ull;\n}\n\n//------------------------------------------------------------------------------\n// Get number of unlinked files on the given file system\n//------------------------------------------------------------------------------\nuint64_t\nQuarkFileSystemView::getNumUnlinkedFilesOnFs(IFileMD::location_t fs_id)\n{\n  FileSystemHandler* handlerUnlinked = fetchUnlinkedFilelistIfExists(fs_id);\n\n  if (handlerUnlinked) {\n    return handlerUnlinked->size();\n  }\n\n  return 0ull;\n}\n\n//------------------------------------------------------------------------------\n// Check if file system has file id\n//------------------------------------------------------------------------------\nbool\nQuarkFileSystemView::hasFileId(IFileMD::id_t fid, IFileMD::location_t fs_id)\n{\n  FileSystemHandler* handler = fetchRegularFilelistIfExists(fs_id);\n\n  if (handler) {\n    return handler->hasFileId(fid);\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Clear unlinked files for filesystem\n//------------------------------------------------------------------------------\nbool\nQuarkFileSystemView::clearUnlinkedFileList(IFileMD::location_t location)\n{\n  FileSystemHandler* handlerUnlinked = fetchUnlinkedFilelistIfExists(location);\n\n  if (!handlerUnlinked) {\n    return false;\n  }\n\n  handlerUnlinked->nuke();\n  return true;\n}\n\n//----------------------------------------------------------------------------\n// Parse an fs set key, returning its id and whether it points to \"files\" or\n// \"unlinked\"\n//----------------------------------------------------------------------------\nbool parseFsId(const std::string& str, IFileMD::location_t& fsid,\n               bool& unlinked)\n{\n  std::vector<std::string> parts =\n    eos::common::StringTokenizer::split<std::vector<std::string>>(str, ':');\n\n  if (parts.size() != 3) {\n    return false;\n  }\n\n  if (parts[0] + \":\" != fsview::sPrefix) {\n    return false;\n  }\n\n  fsid = std::stoull(parts[1]);\n\n  if (parts[2] == fsview::sFilesSuffix) {\n    unlinked = false;\n  } else if (parts[2] == fsview::sUnlinkedSuffix) {\n    unlinked = true;\n  } else {\n    return false;\n  }\n\n  return true;\n}\n\n//----------------------------------------------------------------------------\n// Get iterator object to run through all currently active filesystem IDs\n//----------------------------------------------------------------------------\nstd::shared_ptr<ICollectionIterator<IFileMD::location_t>>\n    QuarkFileSystemView::getQdbFileSystemIterator(const std::string& pattern)\n{\n  qclient::QScanner replicaSets(*pQcl, pattern);\n  std::set<IFileMD::location_t> uniqueFilesytems;\n\n  for (; replicaSets.valid(); replicaSets.next()) {\n    // Extract fsid from key\n    IFileMD::location_t fsid;\n    bool unused;\n\n    if (!parseFsId(replicaSets.getValue(), fsid, unused)) {\n      eos_static_crit(\"Unable to parse key: %s\", replicaSets.getValue().c_str());\n      continue;\n    }\n\n    uniqueFilesytems.insert(fsid);\n  }\n\n  return std::shared_ptr<ICollectionIterator<IFileMD::location_t>>\n         (new QdbFileSystemIterator(std::move(uniqueFilesytems)));\n}\n\n//------------------------------------------------------------------------------\n// Get iterator to list of files without replicas\n//------------------------------------------------------------------------------\nstd::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n    QuarkFileSystemView::getStreamingNoReplicasFileList()\n{\n  return mNoReplicas->getStreamingFileList();\n}\n\n//------------------------------------------------------------------------------\n// Load view from backend\n//------------------------------------------------------------------------------\nvoid\nQuarkFileSystemView::loadFromBackend()\n{\n  std::vector<std::string> patterns {\n    fsview::sPrefix + \"*:files\",\n    fsview::sPrefix + \"*:unlinked\" };\n\n  for (const auto& pattern : patterns) {\n    for (auto it = getQdbFileSystemIterator(pattern);\n         (it && it->valid()); it->next()) {\n      IFileMD::location_t fsid = it->getElement();\n\n      if (pattern.find(\"unlinked\") != std::string::npos) {\n        initializeUnlinkedFilelist(fsid);\n      } else {\n        initializeRegularFilelist(fsid);\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n//! Initialize FileSystemHandler for given filesystem ID, if not already\n//! initialized. Otherwise, do nothing.\n//!\n//! In any case, return pointer to the corresponding FileSystemHandler.\n//!\n//! @param fsid file system id\n//------------------------------------------------------------------------------\nFileSystemHandler* QuarkFileSystemView::initializeRegularFilelist(\n  IFileMD::location_t fsid)\n{\n  std::unique_lock<std::mutex> lock(mMutex);\n  auto iter = mFiles.find(fsid);\n\n  if (iter != mFiles.end()) {\n    // Found\n    return iter->second.get();\n  }\n\n  mFiles[fsid].reset(new FileSystemHandler(fsid, mExecutor.get(), pQcl, pFlusher,\n                     false));\n  return mFiles[fsid].get();\n}\n\n//------------------------------------------------------------------------------\n//! Fetch FileSystemHandler for a given filesystem ID, but do not initialize\n//! if it doesn't exist, give back nullptr.\n//!\n//! @param fsid file system id\n//------------------------------------------------------------------------------\nFileSystemHandler* QuarkFileSystemView::fetchRegularFilelistIfExists(\n  IFileMD::location_t fsid)\n{\n  std::unique_lock<std::mutex> lock(mMutex);\n  auto iter = mFiles.find(fsid);\n\n  if (iter == mFiles.end()) {\n    return nullptr;\n  }\n\n  return iter->second.get();\n}\n\n//------------------------------------------------------------------------------\n//! Initialize unlinked FileSystemHandler for given filesystem ID,\n//! if not already initialized. Otherwise, do nothing.\n//!\n//! In any case, return pointer to the corresponding FileSystemHandler.\n//!\n//! @param fsid file system id\n//------------------------------------------------------------------------------\nFileSystemHandler* QuarkFileSystemView::initializeUnlinkedFilelist(\n  IFileMD::location_t fsid)\n{\n  std::unique_lock<std::mutex> lock(mMutex);\n  auto iter = mUnlinkedFiles.find(fsid);\n\n  if (iter != mUnlinkedFiles.end()) {\n    // Found\n    return iter->second.get();\n  }\n\n  mUnlinkedFiles[fsid].reset(new FileSystemHandler(fsid, mExecutor.get(), pQcl,\n                             pFlusher, true));\n  return mUnlinkedFiles[fsid].get();\n}\n\n//------------------------------------------------------------------------------\n//! Fetch unlinked FileSystemHandler for a given filesystem ID, but do not\n//! initialize if it doesn't exist, give back nullptr.\n//!\n//! @param fsid file system id\n//------------------------------------------------------------------------------\nFileSystemHandler* QuarkFileSystemView::fetchUnlinkedFilelistIfExists(\n  IFileMD::location_t fsid)\n{\n  std::unique_lock<std::mutex> lock(mMutex);\n  auto iter = mUnlinkedFiles.find(fsid);\n\n  if (iter == mUnlinkedFiles.end()) {\n    return nullptr;\n  }\n\n  return iter->second.get();\n}\n\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/accounting/FileSystemView.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//! @brief The filesystem view stored in QuarkDB\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_FILESYSTEM_VIEW_HH\n#define EOS_NS_FILESYSTEM_VIEW_HH\n\n#include \"namespace/MDException.hh\"\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/IFsView.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"namespace/ns_quarkdb/accounting/FileSystemView.hh\"\n#include \"namespace/ns_quarkdb/accounting/FileSystemHandler.hh\"\n#include \"common/AssistedThread.hh\"\n#include \"qclient/QClient.hh\"\n#include <utility>\n\nEOSNSNAMESPACE_BEGIN\n\nclass MetadataFlusher;\n\n//------------------------------------------------------------------------------\n//! File System iterator implementation on top of QuarkDB.\n//! The proper solution would be that the object itself contacts redis running\n//! SCAN, but this should be fine for now.\n//------------------------------------------------------------------------------\nclass QdbFileSystemIterator:\n  public ICollectionIterator<IFileMD::location_t>\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  QdbFileSystemIterator(std::set<IFileMD::location_t>&& filesystems)\n  {\n    pFilesystems = std::move(filesystems);\n    iterator = pFilesystems.begin();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~QdbFileSystemIterator() = default;\n\n  //----------------------------------------------------------------------------\n  //! Get current fsid\n  //----------------------------------------------------------------------------\n  IFileMD::location_t getElement() override\n  {\n    return *iterator;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if iterator is valid\n  //----------------------------------------------------------------------------\n  bool valid() override\n  {\n    return iterator != pFilesystems.end();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Progress iterator by 1 - only has any effect if iterator is valid\n  //----------------------------------------------------------------------------\n  void next() override\n  {\n    if (valid()) {\n      iterator++;\n    }\n  }\n\nprivate:\n  std::set<IFileMD::location_t> pFilesystems;\n  std::set<IFileMD::location_t>::iterator iterator;\n};\n\n//------------------------------------------------------------------------------\n// File System iterator implementation of a in-memory namespace\n// Trivial implementation, using the same logic to iterate over filesystems\n// as we did with \"getNumFileSystems\" before.\n//------------------------------------------------------------------------------\nclass ListFileSystemIterator:\n  public ICollectionIterator<IFileMD::location_t>\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ListFileSystemIterator(\n    const std::map<IFileMD::location_t, std::unique_ptr<FileSystemHandler>>& map\n  )\n  {\n    for (const auto& pair : map) {\n      mList.push_back(pair.first);\n    }\n\n    mIt = mList.cbegin();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~ListFileSystemIterator() = default;\n\n  //----------------------------------------------------------------------------\n  //! Get current fsid\n  //----------------------------------------------------------------------------\n  IFileMD::location_t getElement() override\n  {\n    return *mIt;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Check if iterator is valid\n  //----------------------------------------------------------------------------\n  bool valid() override\n  {\n    return (mIt != mList.cend());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Retrieve next fsid - returns false when no more filesystems exist\n  //----------------------------------------------------------------------------\n  void next() override\n  {\n    if (valid()) {\n      ++mIt;\n    }\n  }\n\nprivate:\n  std::list<IFileMD::location_t> mList;\n  std::list<IFileMD::location_t>::const_iterator mIt;\n};\n\n\n//------------------------------------------------------------------------------\n//! FileSystemView implementation on top of QuarkDB\n//!\n//! This class keeps a mapping between filesystem ids and the actual file ids\n//! that reside on that particular filesystem. For each fsid we keep a set\n//! structure in Redis i.e. fs_id:fsview_files that holds the file ids. E.g.:\n//!\n//! fsview:1:files -->  fid4, fid87, fid1002 etc.\n//! fsview:2:files ...\n//! ...\n//! fsview:n:files ...\n//!\n//! Besides these data structures we also have:\n//!\n//! fsview_noreplicas - file ids that don't have any replicas on any fs\n//! fsview:x:unlinked - set of file ids that are unlinked on file system \"x\"\n//------------------------------------------------------------------------------\nclass QuarkFileSystemView : public IFsView\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  QuarkFileSystemView(qclient::QClient* qcl, MetadataFlusher* flusher);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~QuarkFileSystemView();\n\n  //----------------------------------------------------------------------------\n  //! Notify me about the changes in the main view\n  //----------------------------------------------------------------------------\n  virtual void fileMDChanged(IFileMDChangeListener::Event* e) override;\n\n  //----------------------------------------------------------------------------\n  //! Notify me about files when recovering from changelog - not used\n  //----------------------------------------------------------------------------\n  virtual void fileMDRead(IFileMD* obj) override {};\n\n  //----------------------------------------------------------------------------\n  //! Recheck the current file object and make any modifications necessary so\n  //! that the information is consistent in the back-end KV store.\n  //!\n  //! @param file file object to be checked\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  virtual bool fileMDCheck(IFileMD* file) override;\n\n  //----------------------------------------------------------------------------\n  //! Erase an entry from all filesystem view collections\n  //!\n  //! @param file id\n  //!\n  //! @return\n  //----------------------------------------------------------------------------\n  virtual void eraseEntry(IFileMD::location_t location, IFileMD::id_t) override;\n\n  //----------------------------------------------------------------------------\n  //! Get iterator to list of files on a particular file system\n  //!\n  //! @param location file system id\n  //!\n  //! @return shared ptr to collection iterator\n  //----------------------------------------------------------------------------\n  std::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n      getFileList(IFileMD::location_t location) override;\n\n  //----------------------------------------------------------------------------\n  //! Get streaming iterator to list of files on a particular file system\n  //!\n  //! @param location file system id\n  //!\n  //! @return shared ptr to collection iterator\n  //----------------------------------------------------------------------------\n  std::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n      getStreamingFileList(IFileMD::location_t location) override;\n\n  //----------------------------------------------------------------------------\n  //! Get an approximately random file residing within the given filesystem.\n  //!\n  //! @param location file system id\n  //! @param retval a file id residing within the given filesystem\n  //!\n  //! @return bool indicating whether the operation was successful\n  //----------------------------------------------------------------------------\n  bool getApproximatelyRandomFileInFs(IFileMD::location_t location,\n                                      IFileMD::id_t& retval) override;\n\n  //----------------------------------------------------------------------------\n  //! Get number of files on the given file system\n  //!\n  //! @param fs_id file system id\n  //!\n  //! @return number of files\n  //----------------------------------------------------------------------------\n  uint64_t getNumFilesOnFs(IFileMD::location_t fs_id) override;\n\n  //----------------------------------------------------------------------------\n  //! Get iterator to list of unlinked files on a particular file system\n  //!\n  //! @param location file system id\n  //!\n  //! @return shared ptr to collection iterator\n  //----------------------------------------------------------------------------\n  std::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n      getUnlinkedFileList(IFileMD::location_t location) override;\n\n  //----------------------------------------------------------------------------\n  //! Get number of unlinked files on the given file system\n  //!\n  //! @param fs_id file system id\n  //!\n  //! @return number of files\n  //----------------------------------------------------------------------------\n  uint64_t getNumUnlinkedFilesOnFs(IFileMD::location_t fs_id) override;\n\n  //----------------------------------------------------------------------------\n  //! Get iterator to list of files without replicas\n  //!\n  //! @return shard ptr to collection iterator\n  //----------------------------------------------------------------------------\n  std::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n      getNoReplicasFileList() override;\n\n  //----------------------------------------------------------------------------\n  //! Get streaming iterator to list of files without replicas\n  //!\n  //! @return shard ptr to collection iterator\n  //----------------------------------------------------------------------------\n  std::shared_ptr<ICollectionIterator<IFileMD::id_t>>\n      getStreamingNoReplicasFileList() override;\n\n  //----------------------------------------------------------------------------\n  //! Get number of files with no replicas\n  //----------------------------------------------------------------------------\n  uint64_t getNumNoReplicasFiles() override;\n\n  //----------------------------------------------------------------------------\n  //! Clear unlinked files for filesystem\n  //!\n  //! @param location filssystem id\n  //!\n  //! @return true if cleanup done successfully, otherwise false\n  //----------------------------------------------------------------------------\n  bool clearUnlinkedFileList(IFileMD::location_t location) override;\n\n  //----------------------------------------------------------------------------\n  //! Get iterator object to run through all currently active filesystem IDs\n  //----------------------------------------------------------------------------\n  std::shared_ptr<ICollectionIterator<IFileMD::location_t>>\n      getFileSystemIterator() override;\n\n  //----------------------------------------------------------------------------\n  //! Check if file system has file id\n  //!\n  //! @param fid file id\n  //! @param fs_id file system id\n  //!\n  //! @return true if file is on the provided file system, otherwise false\n  //----------------------------------------------------------------------------\n  bool hasFileId(IFileMD::id_t fid, IFileMD::location_t fs_id) override;\n\n  //----------------------------------------------------------------------------\n  //! Configure\n  //!\n  //! @param config map of configuration parameters\n  //----------------------------------------------------------------------------\n  void configure(const std::map<std::string, std::string>& config) override;\n\n  //----------------------------------------------------------------------------\n  //! Finalize - no-op for this type of view\n  //----------------------------------------------------------------------------\n  void finalize() override {};\n\n  //----------------------------------------------------------------------------\n  //! Shrink maps - no-op for this type of view\n  //----------------------------------------------------------------------------\n  void shrink() override {};\n\n  //----------------------------------------------------------------------------\n  //! Add tree - no-op for this type of view\n  //----------------------------------------------------------------------------\n  void AddTree(IContainerMD* obj, TreeInfos treeInfos) override {};\n\n  //----------------------------------------------------------------------------\n  //! Remove tree - no-op for this type of view\n  //----------------------------------------------------------------------------\n  void RemoveTree(IContainerMD* obj, TreeInfos treeInfos) override {};\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Load view from backend\n  //----------------------------------------------------------------------------\n  void loadFromBackend();\n\n  //----------------------------------------------------------------------------\n  //! Get iterator object to run through all the filesystem IDs stored in the\n  //! backend.\n  //----------------------------------------------------------------------------\n  std::shared_ptr<ICollectionIterator<IFileMD::location_t>>\n      getQdbFileSystemIterator(const std::string& pattern);\n\n  //----------------------------------------------------------------------------\n  //! Initialize FileSystemHandler for given filesystem ID, if not already\n  //! initialized. Otherwise, do nothing.\n  //!\n  //! In any case, return pointer to the corresponding FileSystemHandler.\n  //!\n  //! @param fsid file system id\n  //----------------------------------------------------------------------------\n  FileSystemHandler* initializeRegularFilelist(IFileMD::location_t fsid);\n\n  //----------------------------------------------------------------------------\n  //! Fetch FileSystemHandler for a given filesystem ID, but do not initialize\n  //! if it doesn't exist, give back nullptr.\n  //!\n  //! @param fsid file system id\n  //----------------------------------------------------------------------------\n  FileSystemHandler* fetchRegularFilelistIfExists(IFileMD::location_t fsid);\n\n  //----------------------------------------------------------------------------\n  //! Initialize unlinked FileSystemHandler for given filesystem ID,\n  //! if not already initialized. Otherwise, do nothing.\n  //!\n  //! In any case, return pointer to the corresponding FileSystemHandler.\n  //!\n  //! @param fsid file system id\n  //----------------------------------------------------------------------------\n  FileSystemHandler* initializeUnlinkedFilelist(IFileMD::location_t fsid);\n\n  //----------------------------------------------------------------------------\n  //! Fetch unlinked FileSystemHandler for a given filesystem ID, but do not\n  //! initialize if it doesn't exist, give back nullptr.\n  //!\n  //! @param fsid file system id\n  //----------------------------------------------------------------------------\n  FileSystemHandler* fetchUnlinkedFilelistIfExists(IFileMD::location_t fsid);\n\n  //----------------------------------------------------------------------------\n  //! Run cache cleanup of the different FileSystemHandle objects tracked by\n  //! the FileSystemView in order to keep the memory overhead under control\n  //!\n  //! @param assistand thread responsible for this activity\n  //----------------------------------------------------------------------------\n  void CleanCacheJob(ThreadAssistant& assistant) noexcept;\n\n  ///! Metadata flusher object\n  MetadataFlusher* pFlusher;\n  ///! QClient object\n  qclient::QClient* pQcl;\n\n  ///! Folly executor\n  std::unique_ptr<folly::Executor> mExecutor;\n\n  ///! No replicas handler\n  std::unique_ptr<FileSystemHandler> mNoReplicas;\n  ///! Regular filelists\n  std::map<IFileMD::location_t, std::unique_ptr<FileSystemHandler>> mFiles;\n  ///! Unlinked filelists\n  std::map<IFileMD::location_t, std::unique_ptr<FileSystemHandler>>\n      mUnlinkedFiles;\n  ///! Mutex protecting access to the maps. Not the contents of the maps,\n  ///! though.\n  std::mutex mMutex;\n  //! Thread cleaning the FileSystemHandler cache regularly\n  AssistedThread mCacheCleanerThread;\n  //! Period after which the thread cache cleaner will run. Default 45 min.\n  static const std::chrono::minutes sCacheCleanerTimeout;\n};\n\n//------------------------------------------------------------------------------\n//! Parse an fs set key, returning its id and whether it points to \"files\" or\n//! \"unlinked\"\n//!\n//! @param str input stirng\n//! @param fsid parsed fsids\n//! @param unlinked if true then this is an fsid from an unlinked list\n//!\n//! @return true if parsing successful, otherwise false\n//------------------------------------------------------------------------------\nbool parseFsId(const std::string& str, IFileMD::location_t& fsid,\n               bool& unlinked);\n\nEOSNSNAMESPACE_END\n\n#endif // __EOS_NS_FILESYSTEM_VIEW_HH__\n"
  },
  {
    "path": "namespace/ns_quarkdb/accounting/QuotaNodeCore.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/accounting/QuotaNodeCore.hh\"\n#include <mutex>\n#include <set>\n\nEOSNSNAMESPACE_BEGIN\n\n//----------------------------------------------------------------------------\n// Get the amount of space occupied by the given user\n//----------------------------------------------------------------------------\nuint64_t QuotaNodeCore::getUsedSpaceByUser(uid_t uid) const\n{\n  std::shared_lock<std::shared_timed_mutex> lock(mtx);\n  auto it = mUserInfo.find(uid);\n\n  if (it == mUserInfo.end()) {\n    return 0;\n  }\n\n  return it->second.space;\n}\n\n//------------------------------------------------------------------------------\n// Get the amount of space occupied by the given group\n//------------------------------------------------------------------------------\nuint64_t QuotaNodeCore::getUsedSpaceByGroup(gid_t gid) const\n{\n  std::shared_lock<std::shared_timed_mutex> lock(mtx);\n  auto it = mGroupInfo.find(gid);\n\n  if (it == mGroupInfo.end()) {\n    return 0;\n  }\n\n  return it->second.space;\n}\n\n//------------------------------------------------------------------------------\n// Get the amount of space occupied by the given user\n//------------------------------------------------------------------------------\nuint64_t QuotaNodeCore::getPhysicalSpaceByUser(uid_t uid) const\n{\n  std::shared_lock<std::shared_timed_mutex> lock(mtx);\n  auto it = mUserInfo.find(uid);\n\n  if (it == mUserInfo.end()) {\n    return 0;\n  }\n\n  return it->second.physicalSpace;\n}\n\n//------------------------------------------------------------------------------\n// Get the amount of space occupied by the given group\n//------------------------------------------------------------------------------\nuint64_t QuotaNodeCore::getPhysicalSpaceByGroup(gid_t gid) const\n{\n  std::shared_lock<std::shared_timed_mutex> lock(mtx);\n  auto it = mGroupInfo.find(gid);\n\n  if (it == mGroupInfo.end()) {\n    return 0;\n  }\n\n  return it->second.physicalSpace;\n}\n\n//------------------------------------------------------------------------------\n// Get the amount of space occupied by the given user\n//------------------------------------------------------------------------------\nuint64_t QuotaNodeCore::getNumFilesByUser(uid_t uid) const\n{\n  std::shared_lock<std::shared_timed_mutex> lock(mtx);\n  auto it = mUserInfo.find(uid);\n\n  if (it == mUserInfo.end()) {\n    return 0;\n  }\n\n  return it->second.files;\n}\n\n//------------------------------------------------------------------------------\n// Get the amount of space occupied by the given group\n//------------------------------------------------------------------------------\nuint64_t QuotaNodeCore::getNumFilesByGroup(gid_t gid) const\n{\n  std::shared_lock<std::shared_timed_mutex> lock(mtx);\n  auto it = mGroupInfo.find(gid);\n\n  if (it == mGroupInfo.end()) {\n    return 0;\n  }\n\n  return it->second.files;\n}\n\n//------------------------------------------------------------------------------\n// Account a new file.\n//------------------------------------------------------------------------------\nvoid QuotaNodeCore::addFile(uid_t uid, gid_t gid, uint64_t size,\n                            uint64_t physicalSize)\n{\n  std::unique_lock<std::shared_timed_mutex> lock(mtx);\n  UsageInfo& user  = mUserInfo[uid];\n  UsageInfo& group = mGroupInfo[gid];\n  user.physicalSpace  += physicalSize;\n  group.physicalSpace += physicalSize;\n  user.space  += size;\n  group.space += size;\n  user.files++;\n  group.files++;\n}\n\n//------------------------------------------------------------------------------\n// Remove a file.\n//------------------------------------------------------------------------------\nvoid QuotaNodeCore::removeFile(uid_t uid, gid_t gid, uint64_t size,\n                               uint64_t physicalSize)\n{\n  std::unique_lock<std::shared_timed_mutex> lock(mtx);\n  UsageInfo& user  = mUserInfo[uid];\n  UsageInfo& group = mGroupInfo[gid];\n  user.physicalSpace  -= physicalSize;\n  group.physicalSpace -= physicalSize;\n  user.space  -= size;\n  group.space -= size;\n  user.files--;\n  group.files--;\n}\n\n//------------------------------------------------------------------------------\n// Meld in another quota node core\n//------------------------------------------------------------------------------\nvoid QuotaNodeCore::meld(const QuotaNodeCore& other)\n{\n  std::lock(mtx, other.mtx);\n\n  for (auto it = other.mUserInfo.begin(); it != other.mUserInfo.end(); it++) {\n    mUserInfo[it->first] += it->second;\n  }\n\n  for (auto it = other.mGroupInfo.begin(); it != other.mGroupInfo.end(); it++) {\n    mGroupInfo[it->first] += it->second;\n  }\n\n  mtx.unlock();\n  other.mtx.unlock();\n}\n\n//----------------------------------------------------------------------------\n// Get the set of uids for which information is stored in the current quota\n// node.\n//----------------------------------------------------------------------------\nstd::unordered_set<uint64_t> QuotaNodeCore::getUids() const\n{\n  std::shared_lock<std::shared_timed_mutex> lock(mtx);\n  std::unordered_set<uint64_t> uids;\n\n  for (auto it = mUserInfo.begin(); it != mUserInfo.end(); ++it) {\n    uids.insert(it->first);\n  }\n\n  return uids;\n}\n\n//----------------------------------------------------------------------------\n// Get the set of gids for which information is stored in the current quota\n// node.\n//----------------------------------------------------------------------------\nstd::unordered_set<uint64_t> QuotaNodeCore::getGids() const\n{\n  std::shared_lock<std::shared_timed_mutex> lock(mtx);\n  std::unordered_set<uint64_t> gids;\n\n  for (auto it = mGroupInfo.begin(); it != mGroupInfo.end(); ++it) {\n    gids.insert(it->first);\n  }\n\n  return gids;\n}\n\n//------------------------------------------------------------------------------\n// operator=\n//------------------------------------------------------------------------------\nQuotaNodeCore& QuotaNodeCore::operator=(const QuotaNodeCore& other)\n{\n  std::lock(mtx, other.mtx);\n  mUserInfo = other.mUserInfo;\n  mGroupInfo = other.mGroupInfo;\n  mtx.unlock();\n  other.mtx.unlock();\n  return *this;\n}\n\n\n//------------------------------------------------------------------------------\n// operator<< (replacing all entries from update in core)\n//------------------------------------------------------------------------------\nQuotaNodeCore& QuotaNodeCore::operator<< (const QuotaNodeCore &other)\n{\n  std::lock(mtx, other.mtx);\n\n  for (auto it = other.mUserInfo.begin(); it != other.mUserInfo.end(); it++) {\n    mUserInfo[it->first] = it->second;\n  }\n\n  for (auto it = other.mGroupInfo.begin(); it != other.mGroupInfo.end(); it++) {\n    mGroupInfo[it->first] = it->second;\n  }\n\n  mtx.unlock();\n  other.mtx.unlock();\n  return *this;\n}\n\n//------------------------------------------------------------------------------\n// equality operator==\n//------------------------------------------------------------------------------\nbool QuotaNodeCore::operator==(const QuotaNodeCore& other) const\n{\n  std::lock(mtx, other.mtx);\n  bool result = mUserInfo == other.mUserInfo && mGroupInfo == other.mGroupInfo;\n  mtx.unlock();\n  other.mtx.unlock();\n  return result;\n}\n\n//----------------------------------------------------------------------------\n//! set usage info by uid\n//----------------------------------------------------------------------------\nvoid QuotaNodeCore::setByUid(uid_t uid, const UsageInfo& info)\n{\n  std::unique_lock<std::shared_timed_mutex> lock(mtx);\n  mUserInfo[uid] = info;\n}\n\n//----------------------------------------------------------------------------\n//! set usage info by gid\n//----------------------------------------------------------------------------\nvoid QuotaNodeCore::setByGid(gid_t gid, const UsageInfo& info)\\\n{\n  std::unique_lock<std::shared_timed_mutex> lock(mtx);\n  mGroupInfo[gid] = info;\n}\n\n//----------------------------------------------------------------------------\n//! filter usage info by uid\n//----------------------------------------------------------------------------\nvoid QuotaNodeCore::filterByUid(uid_t uid)\n{\n  std::unique_lock<std::shared_timed_mutex> lock(mtx);\n  std::set<uid_t> uidv;\n  for ( auto it : mUserInfo ) {\n    uidv.insert(it.first);\n  }\n  for ( auto it : uidv ) {\n    if ( it != uid ) {\n      mUserInfo.erase(it);\n    }\n  }\n}\n\n//----------------------------------------------------------------------------\n//! filter usage info by gid\n//----------------------------------------------------------------------------\nvoid QuotaNodeCore::filterByGid(gid_t gid)\n{\n  std::unique_lock<std::shared_timed_mutex> lock(mtx);\n  std::set<gid_t> gidv;\n  for ( auto it : mGroupInfo ) {\n    gidv.insert(it.first);\n  }\n  for ( auto it : gidv ) {\n    if ( it != gid ) {\n      mGroupInfo.erase(it);\n    }\n  }\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/accounting/QuotaNodeCore.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief  Quota node core logic\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/Identifiers.hh\"\n#include <map>\n#include <unordered_set>\n#include <shared_mutex>\n\nEOSNSNAMESPACE_BEGIN\n\nclass IQuotaNode;\nclass QuotaNode;\n\n//------------------------------------------------------------------------------\n//! QuotaNode core logic, which keeps track of user/group volume/inode use for\n//! a single quotanode.\n//------------------------------------------------------------------------------\nclass QuotaNodeCore\n{\npublic:\n  // UsageInfo struct holding various counters\n  struct UsageInfo {\n    UsageInfo(): space(0), physicalSpace(0), files(0) {}\n    UsageInfo& operator += (const UsageInfo& other)\n    {\n      space         += other.space;\n      physicalSpace += other.physicalSpace;\n      files         += other.files;\n      return *this;\n    }\n\n    bool operator==(const UsageInfo& other) const\n    {\n      return (space == other.space) &&\n             (physicalSpace == other.physicalSpace) &&\n             (files == other.files);\n    }\n\n    uint64_t space;\n    uint64_t physicalSpace;\n    uint64_t files;\n  };\n\n  //----------------------------------------------------------------------------\n  //! Constructor. The object is initially empty, no files whatsoever are being\n  //! accounted.\n  //----------------------------------------------------------------------------\n  QuotaNodeCore() {}\n\n  //----------------------------------------------------------------------------\n  //! Get the amount of space occupied by the given user\n  //----------------------------------------------------------------------------\n  uint64_t getUsedSpaceByUser(uid_t uid) const;\n\n  //----------------------------------------------------------------------------\n  //! Get the amount of space occupied by the given group\n  //----------------------------------------------------------------------------\n  uint64_t getUsedSpaceByGroup(gid_t gid) const;\n\n  //----------------------------------------------------------------------------\n  //! Get the amount of space occupied by the given user\n  //----------------------------------------------------------------------------\n  uint64_t getPhysicalSpaceByUser(uid_t uid) const;\n\n  //----------------------------------------------------------------------------\n  //! Get the amount of space occupied by the given group\n  //----------------------------------------------------------------------------\n  uint64_t getPhysicalSpaceByGroup(gid_t gid) const;\n\n  //----------------------------------------------------------------------------\n  //! Get the amount of space occupied by the given user\n  //----------------------------------------------------------------------------\n  uint64_t getNumFilesByUser(uid_t uid) const;\n\n  //----------------------------------------------------------------------------\n  //! Get the amount of space occupied by the given group\n  //----------------------------------------------------------------------------\n  uint64_t getNumFilesByGroup(gid_t gid) const;\n\n  //----------------------------------------------------------------------------\n  //! Account a new file.\n  //----------------------------------------------------------------------------\n  void addFile(uid_t uid, gid_t gid, uint64_t size, uint64_t physicalSize);\n\n  //----------------------------------------------------------------------------\n  //! Remove a file.\n  //----------------------------------------------------------------------------\n  void removeFile(uid_t uid, gid_t gid, uint64_t size, uint64_t physicalSize);\n\n  //----------------------------------------------------------------------------\n  //! Meld in another quota node core\n  //----------------------------------------------------------------------------\n  void meld(const QuotaNodeCore& other);\n\n  //----------------------------------------------------------------------------\n  //! Get the set of uids for which information is stored in the current quota\n  //! node.\n  //!\n  //! @return set of uids\n  //----------------------------------------------------------------------------\n  std::unordered_set<uint64_t> getUids() const;\n\n  //----------------------------------------------------------------------------\n  //! Get the set of gids for which information is stored in the current quota\n  //! node.\n  //!\n  //! @return set of gids\n  //----------------------------------------------------------------------------\n  std::unordered_set<uint64_t> getGids() const;\n\n  //----------------------------------------------------------------------------\n  //! operator=\n  //----------------------------------------------------------------------------\n  QuotaNodeCore& operator=(const QuotaNodeCore&);\n\n  //----------------------------------------------------------------------------\n  //! operator<< (replacing all entries from update in core)\n  //----------------------------------------------------------------------------\n  QuotaNodeCore& operator<< (const QuotaNodeCore& update);\n\n  //----------------------------------------------------------------------------\n  //! equality operator==\n  //----------------------------------------------------------------------------\n  bool operator==(const QuotaNodeCore& other) const;\n\n  //----------------------------------------------------------------------------\n  //! set usage info by uid\n  //----------------------------------------------------------------------------\n  void setByUid(uid_t uid, const UsageInfo& info);\n\n  //----------------------------------------------------------------------------\n  //! set usage info by gid\n  //----------------------------------------------------------------------------\n  void setByGid(gid_t gid, const UsageInfo& info);\n\n  //----------------------------------------------------------------------------\n  //! filter usage info by uid\n  //----------------------------------------------------------------------------\n  void filterByUid(uid_t uid);\n\n  //----------------------------------------------------------------------------\n  //! filter usage info by gid\n  //----------------------------------------------------------------------------\n  void filterByGid(gid_t gid);\n\nprivate:\n  friend class IQuotaNode;\n  friend class QuotaNode;\n  friend class QuarkQuotaNode;\n  mutable std::shared_timed_mutex mtx;\n  std::map<uid_t, UsageInfo> mUserInfo;\n  std::map<gid_t, UsageInfo> mGroupInfo;\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/accounting/QuotaStats.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n// @brief User quota accounting\n//------------------------------------------------------------------------------\n\n#include \"namespace/ns_quarkdb/accounting/QuotaStats.hh\"\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"namespace/ns_quarkdb/ConfigurationParser.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"qclient/structures/QScanner.hh\"\n#include \"qclient/structures/QHash.hh\"\n#include \"common/StringTokenizer.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// *** Class QuotaNode implementaion ***\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nQuarkQuotaNode::QuarkQuotaNode(IQuotaStats* quota_stats,\n                               IContainerMD::id_t node_id)\n  : IQuotaNode(quota_stats, node_id)\n{\n  std::string snode_id = std::to_string(node_id);\n  pQcl = static_cast<QuarkQuotaStats*>(quota_stats)->pQcl;\n  pFlusher = static_cast<QuarkQuotaStats*>(quota_stats)->pFlusher;\n  pQuotaUidKey = QuarkQuotaStats::KeyQuotaUidMap(snode_id);\n  pQuotaGidKey = QuarkQuotaStats::KeyQuotaGidMap(snode_id);\n}\n\n//------------------------------------------------------------------------------\n// Account a new file, adjust the size using the size mapping function\n//------------------------------------------------------------------------------\nvoid\nQuarkQuotaNode::addFile(const IFileMD* file)\n{\n  const std::string suid = std::to_string(file->getCUid());\n  const std::string sgid = std::to_string(file->getCGid());\n  const int64_t size = pQuotaStats->getPhysicalSize(file);\n  const std::string physicalSize = std::to_string(size);\n  const std::string logicalSize = std::to_string(file->getSize());\n  pFlusher->exec(\"HINCRBYMULTI\",\n                 pQuotaUidKey, suid + quota::sPhysicalSize, physicalSize,\n                 pQuotaGidKey, sgid + quota::sPhysicalSize, physicalSize,\n                 pQuotaUidKey, suid + quota::sLogicalSize,  logicalSize,\n                 pQuotaGidKey, sgid + quota::sLogicalSize,  logicalSize,\n                 pQuotaUidKey, suid + quota::sNumFiles,     \"1\",\n                 pQuotaGidKey, sgid + quota::sNumFiles,     \"1\"\n                );\n  // Update the cached information\n  pCore.addFile(\n    file->getCUid(),\n    file->getCGid(),\n    file->getSize(),\n    size\n  );\n}\n\n//------------------------------------------------------------------------------\n// Remove a file, adjust the size using the size mapping function\n//------------------------------------------------------------------------------\nvoid\nQuarkQuotaNode::removeFile(const IFileMD* file)\n{\n  const std::string suid = std::to_string(file->getCUid());\n  const std::string sgid = std::to_string(file->getCGid());\n  const int64_t size = pQuotaStats->getPhysicalSize(file);\n  const int64_t logicalSizeInt = file->getSize();\n  const std::string minusPhysicalSize = std::to_string(-size);\n  const std::string minusLogicalSize = std::to_string(-logicalSizeInt);\n  pFlusher->exec(\"HINCRBYMULTI\",\n                 pQuotaUidKey, suid + quota::sPhysicalSize, minusPhysicalSize,\n                 pQuotaGidKey, sgid + quota::sPhysicalSize, minusPhysicalSize,\n                 pQuotaUidKey, suid + quota::sLogicalSize,  minusLogicalSize,\n                 pQuotaGidKey, sgid + quota::sLogicalSize,  minusLogicalSize,\n                 pQuotaUidKey, suid + quota::sNumFiles,     \"-1\",\n                 pQuotaGidKey, sgid + quota::sNumFiles,     \"-1\"\n                );\n  // Update the cached information\n  pCore.removeFile(\n    file->getCUid(),\n    file->getCGid(),\n    file->getSize(),\n    size\n  );\n}\n\n//------------------------------------------------------------------------------\n// Meld in another quota node\n//------------------------------------------------------------------------------\nvoid\nQuarkQuotaNode::meld(const IQuotaNode* node)\n{\n  const QuarkQuotaNode* impl_node = static_cast<const QuarkQuotaNode*>(node);\n  // Meld in the uid map info\n  qclient::QHash hmap(*pQcl,\n                      QuarkQuotaStats::KeyQuotaUidMap(std::to_string(impl_node->getId())));\n  std::pair<std::string, std::map<std::string, std::string>> reply;\n  std::string cursor = \"0\";\n  constexpr int64_t count = 2000000;\n\n  do {\n    reply = hmap.hscan(cursor, count);\n    cursor = reply.first;\n\n    for (const auto& elem : reply.second) {\n      pFlusher->hincrby(pQuotaUidKey, elem.first, std::stoll(elem.second));\n    }\n  } while (cursor != \"0\");\n\n  // Meld in the gid map info\n  hmap.setKey(QuarkQuotaStats::KeyQuotaGidMap(std::to_string(\n                impl_node->getId())));\n  cursor = \"0\";\n\n  do {\n    reply = hmap.hscan(cursor, count);\n    cursor = reply.first;\n\n    for (const auto& elem : reply.second) {\n      pFlusher->hincrby(pQuotaGidKey, elem.first, std::stoll(elem.second));\n    }\n  } while (cursor != \"0\");\n\n  // Update the cached information\n  pCore.meld(node->getCore());\n}\n\n//------------------------------------------------------------------------------\n// Update with information from the backend\n//------------------------------------------------------------------------------\nvoid QuarkQuotaNode::updateFromBackend()\n{\n  std::string cursor = \"0\";\n  constexpr int64_t count = 2000000;\n  std::pair<std::string, std::map<std::string, std::string>> reply;\n  qclient::QHash uid_map(*pQcl, pQuotaUidKey);\n  qclient::QHash gid_map(*pQcl, pQuotaGidKey);\n  std::set<std::string> to_delete;\n\n  do {\n    reply = uid_map.hscan(cursor, count);\n    cursor = reply.first;\n\n    for (const auto& elem : reply.second) {\n      size_t pos = elem.first.find(':');\n      uint64_t uid = std::stoull(elem.first.substr(0, pos));\n      std::string type = elem.first.substr(pos + 1);\n      auto it_uid = pCore.mUserInfo.find(uid);\n\n      if (it_uid == pCore.mUserInfo.end()) {\n        auto pair = pCore.mUserInfo.emplace(uid, QuotaNodeCore::UsageInfo());\n        it_uid = pair.first;\n      }\n\n      QuotaNodeCore::UsageInfo& uinfo = it_uid->second;\n\n      if (type == \"logical_size\") {\n        uinfo.space = std::stoull(elem.second);\n      } else if (type == \"physical_size\") {\n        uinfo.physicalSpace = std::stoull(elem.second);\n      } else if (type == \"files\") {\n        uinfo.files = std::stoull(elem.second);\n      }\n\n      // If nothing is used we can drop the entry from the map\n      if ((uinfo.space == 0ull) && (uinfo.physicalSpace == 0ull) &&\n          (uinfo.files == 0ull)) {\n        to_delete.insert(elem.first);\n        pCore.mUserInfo.erase(it_uid);\n      }\n    }\n  } while (cursor != \"0\");\n\n  for (const auto& key_del : to_delete) {\n    uid_map.hdel(key_del);\n  }\n\n  to_delete.clear();\n  cursor = \"0\";\n\n  do {\n    reply = gid_map.hscan(cursor, count);\n    cursor = reply.first;\n\n    for (const auto& elem : reply.second) {\n      size_t pos = elem.first.find(':');\n      uint64_t uid = std::stoull(elem.first.substr(0, pos));\n      std::string type = elem.first.substr(pos + 1);\n      auto it_gid = pCore.mGroupInfo.find(uid);\n\n      if (it_gid == pCore.mGroupInfo.end()) {\n        auto pair = pCore.mGroupInfo.emplace(uid, QuotaNodeCore::UsageInfo());\n        it_gid = pair.first;\n      }\n\n      QuotaNodeCore::UsageInfo& ginfo = it_gid->second;\n\n      if (type == \"logical_size\") {\n        ginfo.space = std::stoull(elem.second);\n      } else if (type == \"physical_size\") {\n        ginfo.physicalSpace = std::stoull(elem.second);\n      } else if (type == \"files\") {\n        ginfo.files = std::stoull(elem.second);\n      }\n\n      // If nothing is used we can drop the entry from the map\n      if ((ginfo.space == 0ull) && (ginfo.physicalSpace == 0ull) &&\n          (ginfo.files == 0ull)) {\n        to_delete.insert(elem.first);\n        pCore.mGroupInfo.erase(it_gid);\n      }\n    }\n  } while (cursor != \"0\");\n\n  for (const auto& key_del : to_delete) {\n    gid_map.hdel(key_del);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Replace underlying QuotaNodeCore object.\n//------------------------------------------------------------------------------\nvoid\nQuarkQuotaNode::replaceCore(const QuotaNodeCore& updated)\n{\n  pCore = updated;\n  pFlusher->exec(\"DEL\", pQuotaUidKey);\n  pFlusher->exec(\"DEL\", pQuotaGidKey);\n\n  for (auto it = pCore.mUserInfo.begin(); it != pCore.mUserInfo.end(); it++) {\n    std::string suid = std::to_string(it->first);\n    pFlusher->exec(\"HSET\", pQuotaUidKey,\n                   suid + quota::sPhysicalSize,\n                   std::to_string(it->second.physicalSpace)\n                  );\n    pFlusher->exec(\"HSET\", pQuotaUidKey,\n                   suid + quota::sLogicalSize,\n                   std::to_string(it->second.space)\n                  );\n    pFlusher->exec(\"HSET\", pQuotaUidKey,\n                   suid + quota::sNumFiles,\n                   std::to_string(it->second.files)\n                  );\n  }\n\n  for (auto it = pCore.mGroupInfo.begin(); it != pCore.mGroupInfo.end(); it++) {\n    std::string sgid = std::to_string(it->first);\n    pFlusher->exec(\"HSET\", pQuotaGidKey,\n                   sgid + quota::sPhysicalSize,\n                   std::to_string(it->second.physicalSpace)\n                  );\n    pFlusher->exec(\"HSET\", pQuotaGidKey,\n                   sgid + quota::sLogicalSize,\n                   std::to_string(it->second.space)\n                  );\n    pFlusher->exec(\"HSET\", pQuotaGidKey,\n                   sgid + quota::sNumFiles,\n                   std::to_string(it->second.files)\n                  );\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Update underlying QuotaNodeCore object.\n//------------------------------------------------------------------------------\nvoid\nQuarkQuotaNode::updateCore(const QuotaNodeCore& updated)\n{\n  // replace all existing entries from updated and flush them\n  pCore << updated;\n\n  for (auto it = updated.mUserInfo.begin(); it != updated.mUserInfo.end(); it++) {\n    std::string suid = std::to_string(it->first);\n    pFlusher->exec(\"HSET\", pQuotaUidKey,\n                   suid + quota::sPhysicalSize,\n                   std::to_string(it->second.physicalSpace)\n                  );\n    pFlusher->exec(\"HSET\", pQuotaUidKey,\n                   suid + quota::sLogicalSize,\n                   std::to_string(it->second.space)\n                  );\n    pFlusher->exec(\"HSET\", pQuotaUidKey,\n                   suid + quota::sNumFiles,\n                   std::to_string(it->second.files)\n                  );\n  }\n\n  for (auto it = updated.mGroupInfo.begin(); it != updated.mGroupInfo.end(); it++) {\n    std::string sgid = std::to_string(it->first);\n    pFlusher->exec(\"HSET\", pQuotaGidKey,\n                   sgid + quota::sPhysicalSize,\n                   std::to_string(it->second.physicalSpace)\n                  );\n    pFlusher->exec(\"HSET\", pQuotaGidKey,\n                   sgid + quota::sLogicalSize,\n                   std::to_string(it->second.space)\n                  );\n    pFlusher->exec(\"HSET\", pQuotaGidKey,\n                   sgid + quota::sNumFiles,\n                   std::to_string(it->second.files)\n                  );\n  }\n}\n\n//------------------------------------------------------------------------------\n// *** Class QuotaStats implementaion ***\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nQuarkQuotaStats::QuarkQuotaStats(qclient::QClient* qcl,\n                                 MetadataFlusher* flusher):\n  pQcl(qcl), pFlusher(flusher) {}\n\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nQuarkQuotaStats::~QuarkQuotaStats()\n{\n  pNodeMap.clear();\n}\n\n//------------------------------------------------------------------------------\n// Configure the quota service\n//------------------------------------------------------------------------------\nvoid\nQuarkQuotaStats::configure(const std::map<std::string, std::string>& config)\n{\n  // Nothing to do, dependencies are passed through the constructor\n}\n\n//------------------------------------------------------------------------------\n// Get a quota node associated to the container id\n//------------------------------------------------------------------------------\nIQuotaNode*\nQuarkQuotaStats::getQuotaNode(IContainerMD::id_t node_id)\n{\n  auto it = pNodeMap.find(node_id);\n\n  if (it != pNodeMap.end()) {\n    return it->second.get();\n  }\n\n  std::string snode_id = std::to_string(node_id);\n\n  if ((pQcl->exists(KeyQuotaUidMap(snode_id)) == 1) ||\n      (pQcl->exists(KeyQuotaGidMap(snode_id)) == 1)) {\n    QuarkQuotaNode* ptr = new QuarkQuotaNode(this, node_id);\n    ptr->updateFromBackend();\n    pNodeMap[node_id].reset(ptr);\n    return ptr;\n  }\n\n  return nullptr;\n}\n\n//------------------------------------------------------------------------------\n// Register a new quota node\n//------------------------------------------------------------------------------\nIQuotaNode*\nQuarkQuotaStats::registerNewNode(IContainerMD::id_t node_id)\n{\n  std::string snode_id = std::to_string(node_id);\n\n  if (pNodeMap.count(node_id) ||\n      (pQcl->exists(KeyQuotaUidMap(snode_id)) == 1) ||\n      (pQcl->exists(KeyQuotaGidMap(snode_id)) == 1)) {\n    MDException e;\n    e.getMessage() << \"Quota node already exist: \" << snode_id;\n    throw e;\n  }\n\n  IQuotaNode* ptr = new QuarkQuotaNode(this, node_id);\n  pNodeMap[node_id].reset(ptr);\n  return ptr;\n}\n\n//------------------------------------------------------------------------------\n// Remove quota node\n//------------------------------------------------------------------------------\nvoid\nQuarkQuotaStats::removeNode(IContainerMD::id_t node_id)\n{\n  auto it = pNodeMap.find(node_id);\n\n  if (it != pNodeMap.end()) {\n    pNodeMap.erase(it);\n  }\n\n  std::string snode_id = std::to_string(node_id);\n  pFlusher->del(KeyQuotaUidMap(snode_id));\n  pFlusher->del(KeyQuotaGidMap(snode_id));\n}\n\n//------------------------------------------------------------------------------\n// Get the set of all quota node ids. The quota node id corresponds to the\n// container id.\n//------------------------------------------------------------------------------\nstd::unordered_set<IContainerMD::id_t>\nQuarkQuotaStats::getAllIds()\n{\n  std::unordered_set<IContainerMD::id_t> quota_ids;\n  qclient::QScanner quota_set(*pQcl, quota::sPrefix + \"*:*\");\n\n  for (; quota_set.valid(); quota_set.next()) {\n    // Extract quota node id\n    IContainerMD::id_t id = 0;\n\n    if (ParseQuotaId(quota_set.getValue(), id)) {\n      quota_ids.insert(id);\n    }\n  }\n\n  return quota_ids;\n}\n\n//------------------------------------------------------------------------------\n// Get quota node uid map key\n//------------------------------------------------------------------------------\nstd::string\nQuarkQuotaStats::KeyQuotaUidMap(const std::string& sid)\n{\n  return quota::sPrefix + sid + \":\" + quota::sUidsSuffix;\n}\n\n//------------------------------------------------------------------------------\n// Get quota node gid map key\n//------------------------------------------------------------------------------\nstd::string\nQuarkQuotaStats::KeyQuotaGidMap(const std::string& sid)\n{\n  return quota::sPrefix + sid + \":\" + quota::sGidsSuffix;\n}\n\n//------------------------------------------------------------------------------\n// Parse quota id from string\n//------------------------------------------------------------------------------\nbool\nQuarkQuotaStats::ParseQuotaId(const std::string& input, IContainerMD::id_t& id)\n{\n  std::vector<std::string> parts =\n    eos::common::StringTokenizer::split< std::vector<std::string> >(input, ':');\n\n  if (parts.size() != 3) {\n    return false;\n  }\n\n  if (parts[0] + \":\" != quota::sPrefix) {\n    return false;\n  }\n\n  if ((parts[2] != quota::sUidsSuffix) && (parts[2] != quota::sGidsSuffix)) {\n    return false;\n  }\n\n  id = std::stoull(parts[1]);\n  return true;\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/accounting/QuotaStats.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch.\n//! @brief Quota accounting\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/IQuota.hh\"\n\nnamespace qclient\n{\nclass QClient;\n}\n\nEOSNSNAMESPACE_BEGIN\n\n//! Forward declaration\nclass QuarkQuotaStats;\nclass MetadataFlusher;\n\n//------------------------------------------------------------------------------\n//! QuotaNode class which keeps track of user/group volume/inode use\n//!\n//! The class accounts the volume/inodes used by each user/group in the\n//! corresponding container. Each such object saves two HMAPs in the Redis\n//! instance using the following convention:\n//!\n//! 1. quota::id:map_uid - this is the HMAP key, where id is the id of the\n//!    corresponding container. It contains only information about the uids\n//!    of the users who have written to the container.\n//!\n//!    { uid1:logical_size   --> val1,\n//!      uid1:physical_size  --> val2,\n//!      uid1:files          --> val3,\n//!      ...\n//!      uidn:files          --> val3n }\n//!\n//! 2. quota:id_t:map_gid - the same for group ids\n//!\n//!   { gid1:logical_size   --> val1,\n//!     gid1:physical_size  --> val2,\n//!     gid1:files          --> val3,\n//!     ...\n//!     gidm:files          --> val3m }\n//------------------------------------------------------------------------------\nclass QuarkQuotaNode : public IQuotaNode\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param quotaStats quota stats object\n  //! @param node_id quota node id\n  //----------------------------------------------------------------------------\n  QuarkQuotaNode(IQuotaStats* quotaStats, IContainerMD::id_t node_id);\n\n  //----------------------------------------------------------------------------\n  //! Account a new file, adjust the size using the size mapping function\n  //----------------------------------------------------------------------------\n  void addFile(const IFileMD* file) override;\n\n  //----------------------------------------------------------------------------\n  //! Remove a file, adjust the size using the size mapping function\n  //----------------------------------------------------------------------------\n  void removeFile(const IFileMD* file) override;\n\n  //----------------------------------------------------------------------------\n  //! Meld in another quota node\n  //----------------------------------------------------------------------------\n  void meld(const IQuotaNode* node) override;\n\n  //----------------------------------------------------------------------------\n  //! Update with information from the backend\n  //----------------------------------------------------------------------------\n  void updateFromBackend();\n\n  //----------------------------------------------------------------------------\n  //! Replace underlying QuotaNodeCore object.\n  //----------------------------------------------------------------------------\n  void replaceCore(const QuotaNodeCore& updated) override;\n\n  //----------------------------------------------------------------------------\n  //! Partial update of underlying QuotaNodeCore object.\n  //----------------------------------------------------------------------------\n  void updateCore(const QuotaNodeCore &updated) override;\n\nprivate:\n  //! Quota quota node uid hash key e.g. quota_node:id:uid\n  std::string pQuotaUidKey;\n  //! Quota quota node gid hash key e.g. quota_node:id:gid\n  std::string pQuotaGidKey;\n  qclient::QClient* pQcl; ///< Backend client from QuotaStats\n  MetadataFlusher* pFlusher; ///< Metadata flusher object from QuotaStats\n};\n\n//------------------------------------------------------------------------------\n//! Manager of the quota nodes\n//------------------------------------------------------------------------------\nclass QuarkQuotaStats : public IQuotaStats\n{\n  friend class QuarkQuotaNode;\n\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  QuarkQuotaStats(qclient::QClient* qcl, MetadataFlusher* flusher);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~QuarkQuotaStats();\n\n  //----------------------------------------------------------------------------\n  //! Configure the quota service\n  //----------------------------------------------------------------------------\n  virtual void configure(const std::map<std::string, std::string>& config)\n  override;\n\n  //----------------------------------------------------------------------------\n  //! Get a quota node associated to the container id\n  //----------------------------------------------------------------------------\n  IQuotaNode* getQuotaNode(IContainerMD::id_t node_id) override;\n\n  //----------------------------------------------------------------------------\n  //! Register a new quota node\n  //----------------------------------------------------------------------------\n  IQuotaNode* registerNewNode(IContainerMD::id_t node_id) override;\n\n  //----------------------------------------------------------------------------\n  //! Remove quota node\n  //----------------------------------------------------------------------------\n  void removeNode(IContainerMD::id_t node_id) override;\n\n  //----------------------------------------------------------------------------\n  //! Get the set of all quota node ids. The quota node id corresponds to the\n  //! container id.\n  //!\n  //! @return set of quota node ids\n  //----------------------------------------------------------------------------\n  std::unordered_set<IContainerMD::id_t> getAllIds() override;\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Get quota node uid map key\n  //!\n  //! @param sid container id\n  //!\n  //! @return map key\n  //----------------------------------------------------------------------------\n  static std::string KeyQuotaUidMap(const std::string& sid);\n\n  //----------------------------------------------------------------------------\n  //! Get quota node gid map key\n  //!\n  //! @param sid container id\n  //!\n  //! @return map key\n  //----------------------------------------------------------------------------\n  static std::string KeyQuotaGidMap(const std::string& sid);\n\n  //----------------------------------------------------------------------------\n  //! Parse quota id from string\n  //!\n  //! @param inpurt input string in the form: <prefix>:id:<suffix>\n  //! @param id quota node id\n  //!\n  //! @return true if successful, otherwise false\n  //----------------------------------------------------------------------------\n  static bool ParseQuotaId(const std::string& input, IContainerMD::id_t& id);\n\n  std::map<IContainerMD::id_t, std::unique_ptr<IQuotaNode>>\n      pNodeMap; ///< Map of quota nodes\n  qclient::QClient* pQcl; ///< Backend client\n  MetadataFlusher* pFlusher; ///< Metadata flusher object\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/accounting/SetChangeList.hh",
    "content": "//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Small in-memory changelist for applying onto STL sets\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n\n#include \"namespace/Namespace.hh\"\n#include <list>\n#include <stdlib.h>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! A ChangeList to apply onto an STL set\n//------------------------------------------------------------------------------\ntemplate<typename T>\nclass SetChangeList\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  SetChangeList() {}\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~SetChangeList() {}\n\n  //----------------------------------------------------------------------------\n  //! Insert an item into the list.\n  //----------------------------------------------------------------------------\n  void push_back(const T& element)\n  {\n    mItems.emplace_back(OperationType::kInsertion, element);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Insert a tombstone into the list.\n  //----------------------------------------------------------------------------\n  void erase(const T& element)\n  {\n    mItems.emplace_back(OperationType::kDeletion, element);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get size.\n  //----------------------------------------------------------------------------\n  size_t size() const\n  {\n    return mItems.size();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Clear.\n  //----------------------------------------------------------------------------\n  void clear()\n  {\n    mItems.clear();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Apply change list to given container.\n  //----------------------------------------------------------------------------\n  template<typename Container>\n  void apply(Container& container) const\n  {\n    for (auto it = mItems.begin(); it != mItems.end(); it++) {\n      if (it->operationType == OperationType::kInsertion) {\n        container.insert(it->item);\n      } else if (it->operationType == OperationType::kDeletion) {\n        container.erase(it->item);\n      }\n    }\n  }\n\nprivate:\n  enum class OperationType {\n    kInsertion,\n    kDeletion\n  };\n\n  struct Item {\n    Item(OperationType op, const T& it) : operationType(op), item(it) {}\n    OperationType operationType;\n    T item;\n  };\n\n  std::list<Item> mItems;\n};\n\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/accounting/SyncTimeAccounting.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/accounting/SyncTimeAccounting.hh\"\n#include <iostream>\n#include <chrono>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nQuarkSyncTimeAccounting::QuarkSyncTimeAccounting(IContainerMDSvc* svc,\n                                                 uint32_t update_interval,\n                                                 INamespaceStats* ns_stats):\n  mAccumulateIndx(0), mCommitIndx(1), mShutdown(false),\n  mUpdateIntervalSec(update_interval), mContainerMDSvc(svc),\n  mNamespaceStats(ns_stats)\n{\n  mBatch.resize(2);\n\n  // Enable updates if update interval is not 0\n  if (mUpdateIntervalSec) {\n    mThread.reset(&QuarkSyncTimeAccounting::AssistedPropagateUpdates, this);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nQuarkSyncTimeAccounting::~QuarkSyncTimeAccounting()\n{\n  mShutdown = true;\n\n  if (mUpdateIntervalSec) {\n    mThread.join();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Notify the me about the changes in the main view\n//------------------------------------------------------------------------------\nvoid\nQuarkSyncTimeAccounting::containerMDChanged(IContainerMD* obj, Action type)\n{\n  switch (type) {\n  case IContainerMDChangeListener::MTimeChange:\n    QueueForUpdate(obj->getId());\n    break;\n\n  default:\n    break;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Queue container object for update\n//------------------------------------------------------------------------------\nvoid\nQuarkSyncTimeAccounting::QueueForUpdate(IContainerMD::id_t id)\n{\n  std::lock_guard<std::mutex> scope_lock(mMutexBatch);\n  auto& batch = mBatch[mAccumulateIndx];\n  auto it_map = batch.mMap.find(id);\n\n  if (it_map != batch.mMap.end()) {\n    auto& it_lst = it_map->second;\n    // Move it from current location to the end of the list (most recent)\n    batch.mLstUpd.splice(batch.mLstUpd.end(), batch.mLstUpd, it_lst);\n  } else {\n    auto it_new = batch.mLstUpd.emplace(batch.mLstUpd.end(), id);\n    batch.mMap[id] = it_new;\n  }\n}\n\nvoid QuarkSyncTimeAccounting::setNamespaceStats(INamespaceStats* namespaceStats) {\n  mNamespaceStats = namespaceStats;\n}\n\n//------------------------------------------------------------------------------\n// Propagate updates in the hierarchical structure. Method ran by an\n// asynchronous thread.\n//------------------------------------------------------------------------------\nvoid\nQuarkSyncTimeAccounting::AssistedPropagateUpdates(ThreadAssistant& assistant)\nnoexcept\n{\n  PropagateUpdates(&assistant);\n}\n\n//------------------------------------------------------------------------------\n// Propagate the sync time\n//------------------------------------------------------------------------------\nvoid\nQuarkSyncTimeAccounting::PropagateUpdates(ThreadAssistant* assistant)\n{\n  while ((assistant && !assistant->terminationRequested()) || (!assistant)) {\n    if (mShutdown) {\n      break;\n    }\n\n    {\n      // Update the indexes to have the async thread working on the batch to\n      // commit and the incoming updates to go to the batch to update\n      std::lock_guard<std::mutex> scope_lock(mMutexBatch);\n      std::swap(mAccumulateIndx, mCommitIndx);\n    }\n\n    uint16_t deepness = 0;\n    IContainerMD::id_t id = 0;\n    std::set<IContainerMD::id_t> upd_nodes;\n    auto& lst = mBatch[mCommitIndx].mLstUpd;\n\n    // Start updating form the last node (most recent) and also collect the\n    // nodes that we've updated so that older updates don't propagate further\n    // up than strictly necessary.\n    struct timeval start;\n    struct timeval stop;\n    struct timezone tz;\n    gettimeofday(&start, &tz);\n    for (auto it_id = lst.rbegin(); it_id != lst.rend(); ++it_id) {\n      deepness = 0;\n      id = *it_id;\n\n      if (id == 0u) {\n        continue;\n      }\n\n      eos_debug(\"Container_id=%lu sync time\", id);\n      IContainerMD::ctime_t mtime {0};\n\n      while ((id > 1) && (deepness < 255)) {\n        std::shared_ptr<IContainerMD> cont;\n\n        // If node is already in the set of updates then don't bother\n        // propagating this update\n        if (upd_nodes.count(id)) {\n          break;\n        }\n\n        try {\n          cont = mContainerMDSvc->getContainerMD(id);\n          eos::MDLocking::ContainerWriteLock locker(cont.get());\n\n          // Only traverse if there there is an attribute saying so\n          if (!cont->hasAttribute(\"sys.mtime.propagation\")) {\n            break;\n          }\n\n          // If there was a temporary ETAG this has not to be removed\n          if (cont->hasAttribute(\"sys.tmp.etag\")) {\n            cont->removeAttribute(\"sys.tmp.etag\");\n          }\n\n          if (deepness == 0u) {\n            cont->getMTime(mtime);\n          }\n\n          if (!cont->setTMTime(mtime) && deepness) {\n            break;\n          }\n\n          (void) upd_nodes.insert(id);\n          mContainerMDSvc->updateStore(cont.get());\n        } catch (MDException& e) {\n          cont = nullptr;\n          break;\n        }\n\n        id = cont->getParentId();\n        ++deepness;\n      }\n    }\n    gettimeofday(&stop, &tz);                                 \\\n    double execTime = ((stop.tv_sec-start.tv_sec)*1000.0) + ((stop.tv_usec-start.tv_usec)/1000.0);\n    if(mNamespaceStats != nullptr){\n      mNamespaceStats->Add(\"QuarkSyncTimeAccounting\",0,0,lst.size());\n      mNamespaceStats->AddExec(\"QuarkSyncTimeAccounting\",execTime);\n    }\n    // Clean up the batch\n    mBatch[mCommitIndx].Clean();\n\n    if (mUpdateIntervalSec) {\n      if (assistant) {\n        assistant->wait_for(std::chrono::seconds(mUpdateIntervalSec));\n      } else {\n        std::this_thread::sleep_for(std::chrono::seconds(mUpdateIntervalSec));\n      }\n    } else {\n      break;\n    }\n  }\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/accounting/SyncTimeAccounting.hh",
    "content": "//------------------------------------------------------------------------------\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Synchronous mtime propagation listener\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"namespace/MDException.hh\"\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include \"common/Logging.hh\"\n#include \"common/RWMutex.hh\"\n#include \"common/AssistedThread.hh\"\n#include <mutex>\n#include <list>\n#include <unordered_map>\n#include <atomic>\n#include \"namespace/interface/INamespaceStats.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Synchronous mtime propagation listener\n//------------------------------------------------------------------------------\nclass QuarkSyncTimeAccounting : public IContainerMDChangeListener,\n  public eos::common::LogId\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param svc container meta-data service\n  //! @param update_interval interval in seconds when updates are propagated\n  //----------------------------------------------------------------------------\n  QuarkSyncTimeAccounting(IContainerMDSvc* svc,\n                          uint32_t update_interval = 5,\n                          INamespaceStats* ns_stats = nullptr);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~QuarkSyncTimeAccounting();\n\n  //----------------------------------------------------------------------------\n  //! Delete copy constructor and assignment operator\n  //----------------------------------------------------------------------------\n  QuarkSyncTimeAccounting(const QuarkSyncTimeAccounting& other) = delete;\n  QuarkSyncTimeAccounting& operator=(const QuarkSyncTimeAccounting& other) =\n    delete;\n  QuarkSyncTimeAccounting(QuarkSyncTimeAccounting&& other) = delete;\n  QuarkSyncTimeAccounting& operator=(QuarkSyncTimeAccounting&& other) = delete;\n\n  //----------------------------------------------------------------------------\n  //! Notify me about the changes in the main view\n  //!\n  //! @param obj container object pointer\n  //! @param type action type\n  //----------------------------------------------------------------------------\n  void containerMDChanged(IContainerMD* obj, Action type);\n\n  //----------------------------------------------------------------------------\n  //! Propagate updates in the hierarchical structure. Method ran by the\n  //! asynchronous thread.\n  //!\n  //! @param assistant thread doing the propagation\n  //----------------------------------------------------------------------------\n  void PropagateUpdates(ThreadAssistant* assistant = nullptr);\n\n  //----------------------------------------------------------------------------\n  //! Queue container info for update\n  //!\n  //! @param obj container id\n  //----------------------------------------------------------------------------\n  void QueueForUpdate(IContainerMD::id_t id);\n\n  //----------------------------------------------------------------------------\n  //! Sets the object that allows to communicate some execution timing statistics\n  //!\n  //! @param namespaceStats the object that will allow to communicate some execution\n  //! timing statistics\n  //----------------------------------------------------------------------------\n  void setNamespaceStats(INamespaceStats * namespaceStats);\n\nprivate:\n\n  //----------------------------------------------------------------------------\n  //! Propagate updates in the hierarchical structure. Method ran by the\n  //! asynchronous thread.\n  //!\n  //! @param assistant thread doing the propagation\n  //----------------------------------------------------------------------------\n  void AssistedPropagateUpdates(ThreadAssistant& assistant) noexcept;\n\n  //! Update structure containing a list of the nodes that need an update in\n  //! the order that the updates need to be applied and also a map used for\n  //! filtering out multiple updates to the same container ID. Try to optimise\n  //! the number of updates to a container by keeping only the last one.\n  struct UpdateT {\n    std::list<IContainerMD::id_t> mLstUpd; ///< Ordered list of updates\n    //! Map used for fast search/insert operations\n    std::unordered_map<IContainerMD::id_t,\n        std::list<IContainerMD::id_t>::iterator > mMap;\n\n    void Clean()\n    {\n      mLstUpd.clear();\n      mMap.clear();\n    }\n  };\n\n  //! Vector of two elements containing the batch which is currently being\n  //! accumulated and the batch which is being committed to the namespace by the\n  //! asynchronous thread\n  std::vector<UpdateT> mBatch;\n  std::mutex mMutexBatch; ///< Mutex protecting access to the updates batch\n  uint8_t mAccumulateIndx; ///< Index of the batch accumulating updates\n  uint8_t mCommitIndx; ///< Index of the batch committing updates\n  AssistedThread mThread; ///< Thread updating the namespace\n  std::atomic<bool> mShutdown; ///< Flag to shutdown async thread\n  uint32_t mUpdateIntervalSec; ///< Interval in seconds when updates are pushed\n  IContainerMDSvc* mContainerMDSvc; ///< Container meta-data service\n  INamespaceStats * mNamespaceStats;\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/explorer/NamespaceExplorer.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/explorer/NamespaceExplorer.hh\"\n#include \"namespace/utils/PathProcessor.hh\"\n#include \"namespace/ns_quarkdb/persistency/MetadataFetcher.hh\"\n#include \"namespace/utils/Attributes.hh\"\n#include \"common/Assert.hh\"\n#include \"common/Path.hh\"\n#include <memory>\n#include <numeric>\n#include <folly/executors/IOThreadPoolExecutor.h>\n\n#define DBG(message) std::cerr << __FILE__ << \":\" << __LINE__ << \" -- \" << #message << \" = \" << message << std::endl\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nSearchNode::SearchNode(NamespaceExplorer& expl, ContainerIdentifier expectedP,\n                       ContainerIdentifier d, eos::SearchNode* prnt, folly::Executor* exec,\n                       bool ignoreF)\n  : explorer(expl), expectedParent(expectedP), id(d), qcl(explorer.qcl),\n    parent(prnt), executor(exec), ignoreFiles(ignoreF), fileCount(0),\n    containerMd(MetadataFetcher::getContainerFromId(qcl, id))\n{\n  containerMap = MetadataFetcher::getContainerMap(qcl, id);\n}\n\n//------------------------------------------------------------------------------\n// Can we visit this node? Possible only if:\n// - No errors occurred while retrieving the container's metadata.\n// - Has not been visited already.\n//------------------------------------------------------------------------------\nbool SearchNode::canVisit()\n{\n  if (visited) {\n    return false;\n  }\n\n  if (containerMd.hasException() || containerMap.hasException()) {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// This method is concerned with starting queries regarding child containers or\n// files in the current container.\n//   files:      Depending on ignoreFiles and expansionFilteredOut settings,\n//               a count of files or a query for the full list is started.\n//\n//   containers: Query for child containers is only started in case the\n//               containerMap is ready and we are not filtering expansion.\n// If search needs some result, it'll block.\n//------------------------------------------------------------------------------\nvoid SearchNode::handleAsync()\n{\n  if (!childrenLoaded && !expansionFilteredOut && containerMap.ready()) {\n    stageChildren();\n  }\n\n  if (!startFileQueryDone) {\n    startFileQueryDone = true;\n    if (ignoreFiles || expansionFilteredOut) {\n      auto counts = MetadataFetcher::countContents(qcl, id);\n      fileCount = std::move(counts.first);\n      haveFileMdsQuery = false;\n    } else {\n      pendingFileMds = MetadataFetcher::getFileMDsInContainer(qcl, id, executor);\n      haveFileMdsQuery = true;\n    }\n  }\n\n}\n\n//------------------------------------------------------------------------------\n// Get more subcontainers if available\n//------------------------------------------------------------------------------\nstd::unique_ptr<SearchNode> SearchNode::expand()\n{\n  if (containerMd.hasException()) {\n    return {};\n  }\n\n  NamespaceItem nodeItem;\n  nodeItem.isFile = false;\n  nodeItem.containerMd = getContainerInfo();\n  explorer.handleLinkedAttrs(nodeItem);\n\n  if (expansionFilteredOut) {\n    return {}; // nope, this node is being filtered out\n  }\n\n  if (nodeItem.containerMd.parent_id() != expectedParent.getUnderlyingUInt64()) {\n    std::cerr << \"WARNING: Container #\" << nodeItem.containerMd.id() <<\n              \" was expected to have #\" <<\n              expectedParent.getUnderlyingUInt64() << \" as parent; instead it has #\" <<\n              nodeItem.containerMd.parent_id()\n              << std::endl;\n  }\n\n  stageChildren();\n\n  if (children.empty()) {\n    return {}; // nullptr, node has no more children to expand\n  }\n\n  // Explicit transfer of ownership\n  std::unique_ptr<SearchNode> retval = std::move(children.front());\n  children.pop_front();\n  return retval;\n}\n\n//------------------------------------------------------------------------------\n// @todo (gbitzes): Remove this eventually, once we are confident the two find\n// implementations match, apart for the order.\n//------------------------------------------------------------------------------\nstruct FilesystemEntryComparator {\n  bool operator()(const std::string& lhs, const std::string& rhs) const\n  {\n    for (size_t i = 0; i < std::min(lhs.size(), rhs.size()); i++) {\n      if (lhs[i] != rhs[i]) {\n        return lhs[i] < rhs[i];\n      }\n    }\n\n    return lhs.size() > rhs.size();\n  }\n};\n\n//------------------------------------------------------------------------------\n// Unconditionally stage container mds, block if necessary. Call this only if:\n// - Search really needs the result.\n// - When prefetching, when you know containerMap is ready.\n//------------------------------------------------------------------------------\nvoid SearchNode::stageChildren()\n{\n  if (childrenLoaded) {\n    return;\n  }\n\n  childrenLoaded = true;\n  // containerMap is hashmap, thus unsorted... must sort first by filename.. sigh.\n  // storing into a vector and calling std::sort might be faster, TODO\n//  std::map<std::string, IContainerMD::id_t, FilesystemEntryComparator> sortedContainerMap; // Why not the std::less<string> comparator?\n//  for (auto it = containerMap->cbegin(); it != containerMap->cend(); ++it) {\n//    sortedContainerMap[it->first] = it->second;\n//  }\n  std::vector<std::pair<std::string, IContainerMD::id_t>> v;\n\n  for (auto it = containerMap->begin(); it != containerMap->end(); ++it) {\n    v.emplace_back(std::pair<std::string, IContainerMD::id_t> {it->first, it->second});\n  }\n\n  std::sort(v.begin(), v.end());\n\n  for (auto it = v.begin(); it != v.end(); ++it) {\n    children.emplace_back(new SearchNode(explorer, id,\n                                         ContainerIdentifier(it->second), this, executor, ignoreFiles));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Fetch a file entry\n//------------------------------------------------------------------------------\nbool SearchNode::fetchChild(eos::ns::FileMdProto& output)\n{\n  while (true) {\n    try {\n      return pendingFileMds.fetchNext(output);\n    } catch (MDException& exc) {}\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Get container md proto info\n//------------------------------------------------------------------------------\neos::ns::ContainerMdProto& SearchNode::getContainerInfo()\n{\n  return containerMd.get();\n}\n\n//------------------------------------------------------------------------------\n// Get file child count\n//------------------------------------------------------------------------------\nuint64_t SearchNode::getNumFiles()\n{\n  if (!haveFileMdsQuery) {\n    fileCount.wait();\n    return fileCount.value();\n  } else {\n    return pendingFileMds.size();\n  }\n}\n\nuint64_t SearchNode::getNumContainers()\n{\n  return containerMap->size();\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nNamespaceExplorer::NamespaceExplorer(const std::string& pth,\n                                     const ExplorationOptions& opts,\n                                     qclient::QClient& qclient,\n                                     folly::Executor* exec)\n  : path(pth), options(opts), qcl(qclient), executor(exec)\n{\n  if (options.populateLinkedAttributes && !opts.view) {\n    throw_mdexception(EINVAL,\n                      \"NamespaceExplorer: asked to populate linked attrs, but view not provided\");\n  }\n\n  std::vector<std::string> pathParts = eos::common::SplitPath(path);\n  // This part is synchronous by necessity.\n  staticPath.emplace_back(MetadataFetcher::getContainerFromId(qcl,\n                          ContainerIdentifier(1)).get());\n\n  if (pathParts.empty()) {\n    // We're running a search on the root node, expand.\n    dfsPath.emplace_back(new SearchNode(*this, ContainerIdentifier(1),\n                                        ContainerIdentifier(1), nullptr, executor, opts.ignoreFiles));\n  }\n\n  // TODO: This for loop looks like a useful primitive for MetadataFetcher,\n  // maybe move there?\n  ContainerIdentifier parentID {};\n  ContainerIdentifier nextId {};\n\n  for (size_t i = 0; i < pathParts.size(); i++) {\n    // We don't know if the last chunk of pathParts is supposed to be a container\n    // or name..\n    parentID = ContainerIdentifier(staticPath.back().id());\n    bool threw = false;\n\n    try {\n      nextId = MetadataFetcher::getContainerIDFromName(qcl, parentID,\n               pathParts[i]).get();\n    } catch (const MDException& exc) {\n      threw = true;\n      // Maybe the user called \"Find\" on a single file, and the last chunk is\n      // actually a file. Weird, but possible.\n\n      if (i != pathParts.size() - 1) {\n        // Nope, not last part.\n        throw;\n      }\n\n      if (exc.getErrno() != ENOENT) {\n        // Nope, different kind of error\n        throw;\n      }\n\n      if (exc.getErrno() == ENOENT) {\n        // This may throw again, propagate to caller if so\n        FileIdentifier nextId = MetadataFetcher::getFileIDFromName(qcl, parentID,\n                                pathParts[i]).get();\n        lastChunk = MetadataFetcher::getFileFromId(qcl, nextId).get();\n        searchOnFile = true;\n      }\n    }\n\n    if (!threw) {\n      if (i != pathParts.size() - 1) {\n        staticPath.emplace_back(MetadataFetcher::getContainerFromId(qcl, nextId).get());\n      } else {\n        // Final node, expand\n        dfsPath.emplace_back(new SearchNode(*this, parentID, nextId, nullptr, executor,\n                                            opts.ignoreFiles));\n      }\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Build static path\n//------------------------------------------------------------------------------\nstd::string NamespaceExplorer::buildStaticPath()\n{\n  if (staticPath.size() == 1) {\n    return \"/\";\n  }\n\n  // TODO: Cache this?\n  std::stringstream ss;\n\n  for (size_t i = 0; i < staticPath.size(); i++) {\n    if (i == 0) {\n      // Root node\n      ss << \"/\";\n    } else {\n      ss << staticPath[i].name() << \"/\";\n    }\n  }\n\n  return ss.str();\n}\n\n//------------------------------------------------------------------------------\n// Build depth-first-search path\n//------------------------------------------------------------------------------\nstd::string NamespaceExplorer::buildDfsPath()\n{\n  // TODO: cache this somehow?\n  std::stringstream ss;\n  ss << buildStaticPath();\n\n  for (size_t i = 0; i < dfsPath.size(); i++) {\n    if (dfsPath[i]->getContainerInfo().id() == 1) {\n      continue;\n    } else {\n      ss << dfsPath[i]->getContainerInfo().name() << \"/\";\n    }\n  }\n\n  return ss.str();\n}\n\n//------------------------------------------------------------------------------\n// Handle linked attributes\n//------------------------------------------------------------------------------\nvoid NamespaceExplorer::handleLinkedAttrs(NamespaceItem& result)\n{\n  result.attrs.clear();\n  //----------------------------------------------------------------------------\n  // Retrieve reference to linked attribute map\n  //----------------------------------------------------------------------------\n  google::protobuf::Map<std::string, std::string> const* attrMap = nullptr;\n\n  if (result.isFile) {\n    attrMap = &result.fileMd.xattrs();\n  } else {\n    attrMap = &result.containerMd.xattrs();\n  }\n\n  //----------------------------------------------------------------------------\n  // Copy stuff, unfortunately\n  //----------------------------------------------------------------------------\n  result.attrs = {attrMap->begin(), attrMap->end() };\n\n  //----------------------------------------------------------------------------\n  // Do we even have linked attrs?\n  //----------------------------------------------------------------------------\n  if (!options.populateLinkedAttributes) {\n    return;\n  }\n\n  auto link = attrMap->find(\"sys.attr.link\");\n\n  if (link == attrMap->end()) {\n    //--------------------------------------------------------------------------\n    // Nope, take fast path\n    //--------------------------------------------------------------------------\n    return;\n  }\n\n  //----------------------------------------------------------------------------\n  // Cached entry exists?\n  //----------------------------------------------------------------------------\n  auto cached = cachedAttrs.find(link->second);\n\n  if (cached != cachedAttrs.end()) {\n    //--------------------------------------------------------------------------\n    // Cache hit\n    //--------------------------------------------------------------------------\n    populateLinkedAttributes(cached->second, result.attrs, options.prefixLinks);\n    return;\n  }\n\n  //----------------------------------------------------------------------------\n  // Cache miss\n  //----------------------------------------------------------------------------\n  eos::IContainerMD::XAttrMap toStoreIntoCache;\n\n  try {\n    FileOrContainerMD item = options.view->getItem(link->second, true).get();\n\n    if (item.file) {\n      toStoreIntoCache = item.file->getAttributes();\n    } else {\n      toStoreIntoCache = item.container->getAttributes();\n    }\n  } catch (eos::MDException& e) {\n    // toStoreIntoCache remains empty\n  }\n\n  cachedAttrs[link->second] = toStoreIntoCache;\n  populateLinkedAttributes(toStoreIntoCache, result.attrs, options.prefixLinks);\n}\n\n//------------------------------------------------------------------------------\n// Fetch children under current path\n//------------------------------------------------------------------------------\nbool NamespaceExplorer::fetch(NamespaceItem& item)\n{\n  // Handle weird case: Search was called on a single file\n  if (searchOnFile) {\n    if (searchOnFileEnded) {\n      return false;\n    }\n\n    item.fullPath = buildStaticPath() + lastChunk.name();\n    item.isFile = true;\n    item.fileMd = lastChunk;\n    searchOnFileEnded = true;\n    return true;\n  }\n\n  while (!dfsPath.empty()) {\n\n    // Has top node been visited yet?\n    if (dfsPath.back()->canVisit()) {\n      dfsPath.back()->visit();\n      item.isFile = false;\n      item.fullPath = buildDfsPath();\n      item.containerMd = dfsPath.back()->getContainerInfo();\n      item.numContainers = dfsPath.back()->getNumContainers();\n      handleLinkedAttrs(item);\n      item.expansionFilteredOut = false;\n\n      if (options.expansionDecider) {\n        item.expansionFilteredOut = !options.expansionDecider->shouldExpandContainer(\n                                      item.containerMd, item.attrs, item.fullPath);\n      }\n\n      eos::common::Path cpath{item.fullPath};\n      item.expansionFilteredOut = (item.expansionFilteredOut\n                                   || (cpath.GetSubPathSize() >= options.depthLimit));\n      dfsPath.back()->expansionFilteredOut = item.expansionFilteredOut;\n      dfsPath.back()->handleAsync();\n      item.numFiles = dfsPath.back()->getNumFiles();\n      return true;\n    }\n\n    // Does the top node have any pending file children?\n    if (!dfsPath.back()->expansionFilteredOut &&\n        dfsPath.back()->fetchChild(item.fileMd)) {\n      item.isFile = true;\n      item.fullPath = buildDfsPath() + item.fileMd.name();\n      item.expansionFilteredOut = false;\n      handleLinkedAttrs(item);\n      return true;\n    }\n\n    // Can we expand this node?\n    std::unique_ptr<SearchNode> child = dfsPath.back()->expand();\n\n    if (child) {\n      dfsPath.push_back(std::move(child));\n      continue;\n    }\n\n    // Node has neither files, nor containers, pop.\n    dfsPath.pop_back();\n  }\n\n  // Search is over.\n  return false;\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/explorer/NamespaceExplorer.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Class for exploring the namespace\n//------------------------------------------------------------------------------\n\n#pragma once\n\n#include \"common/FutureWrapper.hh\"\n#include \"namespace/Namespace.hh\"\n#include \"proto/FileMd.pb.h\"\n#include \"proto/ContainerMd.pb.h\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/Identifiers.hh\"\n#include \"namespace/ns_quarkdb/utils/FutureVectorIterator.hh\"\n#include <string>\n#include <vector>\n#include <deque>\n#include <folly/futures/Future.h>\n\nnamespace folly\n{\nclass Executor;\n}\n\nnamespace qclient\n{\nclass QClient;\n}\n\nEOSNSNAMESPACE_BEGIN\n\nclass IView;\n\nclass ExpansionDecider\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Returns whether to expand the given container, or ignore it.\n  //! Useful to filter out certain parts of the namespace tree.\n  //----------------------------------------------------------------------------\n  virtual bool shouldExpandContainer(\n    const eos::ns::ContainerMdProto& containerMd,\n    const eos::IContainerMD::XAttrMap& linkedAttrs,\n    const std::string& fullPath) = 0;\n\n  virtual ~ExpansionDecider() = default;\n\n};\n\nstruct ExplorationOptions {\n  unsigned int depthLimit = 0;\n  std::shared_ptr<ExpansionDecider> expansionDecider;\n  bool populateLinkedAttributes = false;\n  bool prefixLinks = false; // only relevant if populateLinkedAttributes is true\n\n  //----------------------------------------------------------------------------\n  // You must supply the view if populateLinkedAttributes = true\n  //----------------------------------------------------------------------------\n  eos::IView* view = nullptr;\n\n  //----------------------------------------------------------------------------\n  // Ignore files?\n  //----------------------------------------------------------------------------\n  bool ignoreFiles = false;\n};\n\nstruct NamespaceItem {\n  // A simple string for now, we can extend this later.\n  std::string fullPath;\n\n  //----------------------------------------------------------------------------\n  //! Extended attributes map: Only filled out if populateLinkedAttributes was\n  //! set.\n  //----------------------------------------------------------------------------\n  eos::IContainerMD::XAttrMap attrs;\n  bool isFile;\n  bool expansionFilteredOut;\n\n  // Only one of these are actually filled out.\n  eos::ns::FileMdProto fileMd;\n  eos::ns::ContainerMdProto containerMd;\n\n  // Only in case of a container: Number of files and subcontainers contained\n  // directly within it (not recursively)\n  uint64_t numFiles = 0;\n  uint64_t numContainers = 0;\n};\n\nclass NamespaceExplorer;\n\n//------------------------------------------------------------------------------\n//! Represents a node in the search tree.\n//------------------------------------------------------------------------------\nclass SearchNode\n{\npublic:\n  SearchNode(NamespaceExplorer& explorer, ContainerIdentifier expectedParent,\n             ContainerIdentifier id, SearchNode* prnt, folly::Executor* exec,\n             bool ignoreFiles);\n\n  inline ContainerIdentifier getID() const\n  {\n    return id;\n  }\n\n  // Return false if this node has no more files to output\n  bool fetchChild(eos::ns::FileMdProto& output); // sync, block if not available\n\n  // Handle asynchronous operations - call this as often as possible!\n  void handleAsync();\n\n  // Explicit transfer of ownership\n  std::unique_ptr<SearchNode> expand();\n\n  // Activate\n  void activate();\n\n  // Clear children.\n  void prefetchChildren();\n\n  //----------------------------------------------------------------------------\n  //! Can we visit this node? Possible only if:\n  //! - No errors occurred while retrieving the container's metadata.\n  //! - Has not been visited already.\n  //----------------------------------------------------------------------------\n  bool canVisit();\n\n  inline void visit()\n  {\n    visited = true;\n  }\n\n  eos::ns::ContainerMdProto& getContainerInfo();\n  uint64_t getNumFiles();\n  uint64_t getNumContainers();\n\n  bool expansionFilteredOut = false;\n\nprivate:\n  NamespaceExplorer& explorer;\n  ContainerIdentifier expectedParent;\n  ContainerIdentifier id;\n  qclient::QClient& qcl;\n  SearchNode* parent = nullptr;\n  folly::Executor* executor = nullptr;\n  bool ignoreFiles;\n  folly::Future<uint64_t> fileCount;\n  bool visited = false;\n  bool haveFileMdsQuery = false;\n  bool startFileQueryDone = false;\n\n  common::FutureWrapper<eos::ns::ContainerMdProto> containerMd;\n  common::FutureWrapper<IContainerMD::ContainerMap> containerMap;\n\n  FutureVectorIterator<eos::ns::FileMdProto> pendingFileMds;\n\n  std::deque<std::unique_ptr<SearchNode>> children; // expanded containers\n  bool childrenLoaded = false;\n\n  eos::IContainerMD::XAttrMap attrs;\n\n  // @todo (gbitzes): Replace this mess with a nice iterator object which\n  // provides all children of a container, fully asynchronous with prefetching.\n  void stageChildren();\n};\n\n//------------------------------------------------------------------------------\n//! Class to recursively explore the QuarkDB namespace, starting from some path.\n//! Useful for \"Find\" commands - no consistency guarantees, if a write is in\n//! the flusher, it might not be seen here.\n//!\n//! Implemented by simple DFS on the namespace.\n//------------------------------------------------------------------------------\nclass NamespaceExplorer\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Inject the QClient to use directly in the constructor. No ownership of\n  //! underlying object.\n  //----------------------------------------------------------------------------\n  NamespaceExplorer(const std::string& path, const ExplorationOptions& options,\n                    qclient::QClient& qcl, folly::Executor* exec);\n\n  //----------------------------------------------------------------------------\n  //! Fetch next item.\n  //----------------------------------------------------------------------------\n  bool fetch(NamespaceItem& result);\n\nprivate:\n  friend class SearchNode;\n  std::string buildStaticPath();\n  std::string buildDfsPath();\n\n  //----------------------------------------------------------------------------\n  // Handle linked attributes\n  //----------------------------------------------------------------------------\n  void handleLinkedAttrs(NamespaceItem& result);\n\n  //----------------------------------------------------------------------------\n  // Retrieve linked container for  Handle linked attributes\n  //----------------------------------------------------------------------------\n  std::string path;\n  ExplorationOptions options;\n  qclient::QClient& qcl;\n  folly::Executor* executor;\n\n  std::vector<eos::ns::ContainerMdProto> staticPath;\n  eos::ns::FileMdProto lastChunk;\n  bool searchOnFile = false;\n  bool searchOnFileEnded = false;\n\n  std::vector<std::unique_ptr<SearchNode>> dfsPath;\n  std::map<std::string, eos::IContainerMD::XAttrMap> cachedAttrs;\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/flusher/MetadataFlusher.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <inttypes.h>\n#include <iostream>\n#include <list>\n#include <sstream>\n#include <memory>\n#include \"qclient/RocksDBPersistency.hh\"\n#include \"qclient/PersistencyLayerBuilder.hh\"\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"common/Logging.hh\"\n#include <iostream>\n#include <chrono>\n#include \"qclient/AssistedThread.hh\"\n\n#define __PRI64_PREFIX \"l\"\n#define PRId64         __PRI64_PREFIX \"d\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nMetadataFlusher::MetadataFlusher(const std::string& path,\n                                 const QdbContactDetails& contactDetails) :\n  id(basename(path.c_str())),\n  notifier(*this),\n  backgroundFlusher(contactDetails.members, contactDetails.constructOptions(),\n                    notifier, new qclient::RocksDBPersistency(path)),\n  sizePrinter(&MetadataFlusher::queueSizeMonitoring, this)\n{\n  synchronize();\n}\n\n\nMetadataFlusher::MetadataFlusher(const std::string& path,\n                                 const QdbContactDetails& contactDetails,\n                                 const std::string& flusher_type,\n                                 const std::string& rocksdb_options) :\n    id(basename(path.c_str())),\n    notifier(*this),\n    backgroundFlusher(qclient::BackgroundFlusherBuilder::makeFlusher(contactDetails.members,\n                                                                     contactDetails.constructOptions(),\n                                                                     notifier,\n                                                                     flusher_type,\n                                                                     qclient::RocksDBConfig(path, rocksdb_options))),\n    sizePrinter(&MetadataFlusher::queueSizeMonitoring, this)\n{\n  synchronize();\n}\n//------------\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nMetadataFlusher::~MetadataFlusher()\n{\n  sizePrinter.join();\n  synchronize();\n}\n\n//------------------------------------------------------------------------------\n// Regularly print queue statistics\n//------------------------------------------------------------------------------\nvoid MetadataFlusher::queueSizeMonitoring(qclient::ThreadAssistant& assistant)\n{\n  while (!assistant.terminationRequested()) {\n    if (backgroundFlusher.size()) {\n      eos_static_info(\"id=%s total-pending=%\" PRId64 \" enqueued=%\" PRId64\n                      \" acknowledged=%\" PRId64 \" starting-index=%\" PRId64\n                      \" ending-index=%\" PRId64,\n                      id.c_str(), backgroundFlusher.size(),\n                      backgroundFlusher.getEnqueuedAndClear(),\n                      backgroundFlusher.getAcknowledgedAndClear(),\n                      backgroundFlusher.getStartingIndex(),\n                      backgroundFlusher.getEndingIndex());\n    }\n\n    assistant.wait_for(std::chrono::seconds(10));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Queue an hset command\n//------------------------------------------------------------------------------\nvoid MetadataFlusher::hset(const std::string& key, const std::string& field,\n                           const std::string& value)\n{\n  backgroundFlusher.pushRequest({\"HSET\", key, field, value});\n}\n\n//------------------------------------------------------------------------------\n// Queue an hincrby command\n//------------------------------------------------------------------------------\nvoid MetadataFlusher::hincrby(const std::string& key, const std::string& field,\n                              int64_t value)\n{\n  backgroundFlusher.pushRequest({\"HINCRBY\", key, field, std::to_string(value)});\n}\n\n//------------------------------------------------------------------------------\n// Queue a del command\n//------------------------------------------------------------------------------\nvoid MetadataFlusher::del(const std::string& key)\n{\n  backgroundFlusher.pushRequest({\"DEL\", key});\n}\n\n//------------------------------------------------------------------------------\n// Queue an hdel command\n//------------------------------------------------------------------------------\nvoid MetadataFlusher::hdel(const std::string& key, const std::string& field)\n{\n  backgroundFlusher.pushRequest({\"HDEL\", key, field});\n}\n\n//------------------------------------------------------------------------------\n// Queue a sadd command\n//------------------------------------------------------------------------------\nvoid MetadataFlusher::sadd(const std::string& key, const std::string& field)\n{\n  backgroundFlusher.pushRequest({\"SADD\", key, field});\n}\n\n//------------------------------------------------------------------------------\n// Queue an srem command\n//------------------------------------------------------------------------------\nvoid MetadataFlusher::srem(const std::string& key, const std::string& field)\n{\n  backgroundFlusher.pushRequest({\"SREM\", key, field});\n}\n\n//------------------------------------------------------------------------------\n// Queue an srem command, use a list as contents\n//------------------------------------------------------------------------------\nvoid MetadataFlusher::srem(const std::string& key,\n                           const std::list<std::string>& items)\n{\n  std::vector<std::string> req = {\"SREM\", key};\n\n  for (auto it = items.begin(); it != items.end(); it++) {\n    req.emplace_back(*it);\n  }\n\n  backgroundFlusher.pushRequest(req);\n}\n\n//------------------------------------------------------------------------------\n// Sleep until given index has been flushed to the backend\n//------------------------------------------------------------------------------\nvoid MetadataFlusher::synchronize(ItemIndex targetIndex)\n{\n  if (targetIndex < 0) {\n    targetIndex = backgroundFlusher.getEndingIndex() - 1;\n  }\n\n  eos_static_info(\"starting-index=%\" PRId64 \" ending-index=%\" PRId64\n                  \" msg=\\\"waiting until \"\n                  \"queue item %\" PRId64 \" has been acknowledged..\\\"\",\n                  backgroundFlusher.getStartingIndex(),\n                  backgroundFlusher.getEndingIndex(), targetIndex);\n\n  while (!backgroundFlusher.waitForIndex(targetIndex, std::chrono::seconds(1))) {\n    eos_static_warning(\"starting-index=%\" PRId64 \" ending-index=%\" PRId64\n                       \" msg=\\\"queue item \"\n                       \"%\" PRId64 \" has not been acknowledged yet..\\\"\",\n                       backgroundFlusher.getStartingIndex(),\n                       backgroundFlusher.getEndingIndex(), targetIndex);\n  }\n\n  eos_static_info(\"starting-index=%\" PRId64 \" ending-index=%\" PRId64\n                  \" msg=\\\"queue item %\" PRId64\n                  \" has been acknowledged\\\"\", backgroundFlusher.getStartingIndex(),\n                  backgroundFlusher.getEndingIndex(), targetIndex);\n}\n\nstd::string MetadataFlusher::getPersistencyType() {\n  // This doesn't change at runtime, init once and cache forever\n  if (persistencyConfig.empty()) {\n    persistencyConfig = backgroundFlusher.getPersistencyType();\n  }\n\n  return persistencyConfig;\n}\n\n//------------------------------------------------------------------\n// Class to receive notifications from the BackgroundFlusher\n//------------------------------------------------------------------------------\nFlusherNotifier::FlusherNotifier(MetadataFlusher& flusher):\n  mFlusher(flusher)\n{\n  (void) mFlusher; // avoid compilation warning\n}\n\n//------------------------------------------------------------------------------\n// Record network events\n//------------------------------------------------------------------------------\nvoid FlusherNotifier::eventNetworkIssue(const std::string& err)\n{\n  eos_static_notice(\"Network issue when contacting the redis backend: %s\",\n                    err.c_str());\n}\n\n//------------------------------------------------------------------------------\n// Record unexpected responses\n//------------------------------------------------------------------------------\nvoid FlusherNotifier::eventUnexpectedResponse(const std::string& err)\n{\n  eos_static_crit(\"Unexpected response when contacting the redis backend: %s\",\n                  err.c_str());\n  // Maybe we should just std::terminate now?\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/flusher/MetadataFlusher.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Metadata flushing towards QuarkDB\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"qclient/BackgroundFlusher.hh\"\n#include \"qclient/AssistedThread.hh\"\n#include <list>\n#include <map>\n\nEOSNSNAMESPACE_BEGIN\n\nclass MetadataFlusher;\nclass QdbContactDetails;\n\n//------------------------------------------------------------------------------\n//! Class to receive notifications from the BackgroundFlusher\n//------------------------------------------------------------------------------\nclass FlusherNotifier : public qclient::Notifier\n{\npublic:\n  FlusherNotifier(MetadataFlusher& flusher);\n  virtual void eventNetworkIssue(const std::string& err) override;\n  virtual void eventUnexpectedResponse(const std::string& err) override;\nprivate:\n  MetadataFlusher& mFlusher;\n};\n\n//------------------------------------------------------------------------------\n//! Metadata flushing towards QuarkDB\n//------------------------------------------------------------------------------\nusing ItemIndex = int64_t;\nclass MetadataFlusher\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  MetadataFlusher(const std::string& path,\n                  const QdbContactDetails& contactDetails);\n\n  MetadataFlusher(const std::string& path,\n                  const QdbContactDetails& contactDetails,\n                  const std::string& flusher_type,\n                  const std::string& rocksdb_options);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~MetadataFlusher();\n\n  //----------------------------------------------------------------------------\n  //! Methods to stage redis commands for background flushing.\n  //----------------------------------------------------------------------------\n  template<typename... Args>\n  void exec(const Args... args)\n  {\n    backgroundFlusher.pushRequest(std::vector<std::string> {args...});\n  }\n\n  void del(const std::string& key);\n  void hdel(const std::string& key, const std::string& field);\n  void hset(const std::string& key, const std::string& field,\n            const std::string& value);\n  void hincrby(const std::string& kye, const std::string& field,\n               int64_t value);\n  void sadd(const std::string& key, const std::string& field);\n  void srem(const std::string& key, const std::string& field);\n  void srem(const std::string& key, const std::list<std::string>& items);\n\n  void execute(const std::vector<std::string>& req)\n  {\n    backgroundFlusher.pushRequest(req);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Block until the queue has flushed all pending entries at the time of\n  //! calling. Example: synchronize is called when pending items in the queue\n  //! are [1500, 2000]. The calling thread sleeps up to the point that entry\n  //! #2000 is flushed - of course, at that point other items might have been\n  //! added to the queue, but we don't wait.\n  //----------------------------------------------------------------------------\n  void synchronize(ItemIndex targetIndex = -1);\n\n  std::string getPersistencyType();\n\nprivate:\n  void queueSizeMonitoring(qclient::ThreadAssistant& assistant);\n  std::string id;\n  std::string persistencyConfig;\n  FlusherNotifier notifier;\n  qclient::BackgroundFlusher backgroundFlusher;\n  qclient::AssistedThread sizePrinter;\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/inspector/AttributeExtraction.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/inspector/AttributeExtraction.hh\"\n#include \"namespace/ns_quarkdb/inspector/Printing.hh\"\n#include \"namespace/utils/Checksum.hh\"\n#include \"common/StringUtils.hh\"\n#include <sstream>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Convert to octal string\n//------------------------------------------------------------------------------\nstatic std::string to_octal_string(uint32_t v) {\n  std::ostringstream ss;\n  ss << std::oct << v;\n  return ss.str();\n}\n\n//------------------------------------------------------------------------------\n// Serialize locations vector\n//------------------------------------------------------------------------------\ntemplate<typename T>\nstatic std::string serializeLocations(const T& vec) {\n  std::ostringstream stream;\n\n  for(int i = 0; i < vec.size(); i++) {\n    stream << vec[i];\n    if(i != vec.size() - 1) {\n      stream << \",\";\n    }\n  }\n\n  return stream.str();\n}\n\n//------------------------------------------------------------------------------\n// Extract the given attribute as string\n//------------------------------------------------------------------------------\nbool AttributeExtraction::asString(const eos::ns::FileMdProto &proto,\n    const std::string &attr, std::string &out) {\n\n  out.clear();\n\n  if(common::startsWith(attr, \"xattr.\")) {\n    std::string chopped = std::string(attr.begin()+6, attr.end());\n\n    auto it = proto.xattrs().find(chopped);\n    if(it != proto.xattrs().end()) {\n      out = it->second;\n    }\n\n    return true;\n  }\n\n  if(attr == \"fid\") {\n    out = std::to_string(proto.id());\n    return true;\n  }\n\n  if(attr == \"pid\") {\n    out = std::to_string(proto.cont_id());\n    return true;\n  }\n\n  if(attr == \"uid\") {\n    out = std::to_string(proto.uid());\n    return true;\n  }\n\n  if(attr == \"gid\") {\n    out = std::to_string(proto.gid());\n    return true;\n  }\n\n  if(attr == \"size\") {\n    out = std::to_string(proto.size());\n    return true;\n  }\n\n  if(attr == \"layout_id\") {\n    out = std::to_string(proto.layout_id());\n    return true;\n  }\n\n  if(attr == \"flags\") {\n    out = to_octal_string(proto.flags());\n    return true;\n  }\n\n  if(attr == \"name\") {\n    out = proto.name();\n    return true;\n  }\n\n  if(attr == \"link_name\") {\n    out = proto.link_name();\n    return true;\n  }\n\n  if(attr == \"ctime\") {\n    out = Printing::timespecToTimestamp(Printing::parseTimespec(proto.ctime()));\n    return true;\n  }\n\n  if(attr == \"mtime\") {\n    out = Printing::timespecToTimestamp(Printing::parseTimespec(proto.mtime()));\n    return true;\n  }\n\n  if(attr == \"xs\") {\n    std::string xs;\n    eos::appendChecksumOnStringProtobuf(proto, xs);\n    out = xs;\n    return true;\n  }\n\n  if(attr == \"locations\") {\n    out = serializeLocations(proto.locations());\n    return true;\n  }\n\n  if(attr == \"unlink_locations\") {\n    out = serializeLocations(proto.unlink_locations());\n    return true;\n  }\n\n  if(attr == \"stime\") {\n    out = Printing::timespecToTimestamp(Printing::parseTimespec(proto.stime()));\n    return true;\n  }\n\n  return false;\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/inspector/AttributeExtraction.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Helper utilities for extracting attributes out\n//!        of file and container MDs\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"proto/FileMd.pb.h\"\n\nEOSNSNAMESPACE_BEGIN\n\nclass AttributeExtraction {\npublic:\n  //----------------------------------------------------------------------------\n  //! Extract the given attribute as string. Return true if given attr name is\n  //! valid, false otherwise.\n  //----------------------------------------------------------------------------\n  static bool asString(const eos::ns::FileMdProto &proto,\n    const std::string &attr, std::string &out);\n\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/inspector/ContainerScanner.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/inspector/ContainerScanner.hh\"\n#include \"namespace/ns_quarkdb/persistency/Serialization.hh\"\n#include \"namespace/ns_quarkdb/persistency/MetadataFetcher.hh\"\n#include \"common/Assert.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nContainerScannerPrimitive::ContainerScannerPrimitive(qclient::QClient &qcl)\n: mIterator(&qcl, \"eos-container-md\") { }\n\n//------------------------------------------------------------------------------\n// Is the iterator valid?\n//------------------------------------------------------------------------------\nbool ContainerScannerPrimitive::valid() const {\n  if(!mError.empty()) {\n    return false;\n  }\n\n  return mIterator.valid();\n}\n\n//----------------------------------------------------------------------------\n// Advance iterator - only call when valid() == true\n//----------------------------------------------------------------------------\nvoid ContainerScannerPrimitive::next() {\n  mIterator.next();\n}\n\n//------------------------------------------------------------------------------\n// Is there an error?\n//------------------------------------------------------------------------------\nbool ContainerScannerPrimitive::hasError(std::string &err) const {\n  if(!mError.empty()) {\n    err = mError;\n    return true;\n  }\n\n  return mIterator.hasError(err);\n}\n\n//------------------------------------------------------------------------------\n// Get current element\n//------------------------------------------------------------------------------\nbool ContainerScannerPrimitive::getItem(eos::ns::ContainerMdProto &item) {\n  if(!valid()) {\n    return false;\n  }\n\n  std::string currentValue = mIterator.getValue();\n  eos::MDStatus status = Serialization::deserialize(currentValue.c_str(), currentValue.size(), item);\n\n  if(!status.ok()) {\n    mError = SSTR(\"Error while deserializing: \" << status.getError());\n    return false;\n  }\n\n  mScanned++;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Get number of elements scanned so far\n//------------------------------------------------------------------------------\nuint64_t ContainerScannerPrimitive::getScannedSoFar() const {\n  return mScanned;\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nContainerScanner::ContainerScanner(qclient::QClient &qcl, bool fullPaths, bool counts)\n: mScanner(qcl), mQcl(qcl), mFullPaths(fullPaths), mCounts(counts) {\n\n  mActive = mFullPaths || mCounts;\n\n  if(mActive) {\n    ensureItemDequeFull();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Is the iterator valid?\n//------------------------------------------------------------------------------\nbool ContainerScanner::valid() const {\n  if(mActive) {\n    return !mItemDeque.empty();\n  }\n  else {\n    return mScanner.valid();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Advance iterator - only call when valid() == true\n//------------------------------------------------------------------------------\nvoid ContainerScanner::next() {\n  if(mActive) {\n    if(valid()) {\n      mItemDeque.pop_front();\n      ensureItemDequeFull();\n    }\n  }\n  else {\n    return mScanner.next();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Ensure our item deque contains a sufficient number of pending items\n//------------------------------------------------------------------------------\nvoid ContainerScanner::ensureItemDequeFull() {\n  if(!mActive) return;\n\n  while(mScanner.valid() && mItemDeque.size() < 500) {\n    eos::ns::ContainerMdProto item;\n    if(mScanner.getItem(item)) {\n\n      folly::Future<std::string> fullPath = \"\";\n      folly::Future<uint64_t> fileCount = 0;\n      folly::Future<uint64_t> containerCount = 0;\n\n      if(mFullPaths) {\n        fullPath = MetadataFetcher::resolveFullPath(mQcl, ContainerIdentifier(item.id()));\n      }\n\n      if(mCounts) {\n        auto counts = MetadataFetcher::countContents(mQcl, ContainerIdentifier(item.id()));\n        fileCount = std::move(counts.first);\n        containerCount = std::move(counts.second);\n      }\n\n      mItemDeque.emplace_back(\n        std::move(item),\n        std::move(fullPath),\n        std::move(fileCount),\n        std::move(containerCount)\n      );\n    }\n\n    mScanner.next();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Is there an error?\n//------------------------------------------------------------------------------\nbool ContainerScanner::hasError(std::string &err) const {\n  if(mActive) {\n    return !mItemDeque.empty() && mScanner.hasError(err);\n  }\n  else {\n    return mScanner.hasError(err);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get number of elements scanned so far\n//------------------------------------------------------------------------------\nuint64_t ContainerScanner::getScannedSoFar() const {\n  if(mActive) {\n    return mScanned;\n  }\n  else{\n    return mScanner.getScannedSoFar();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get current element\n//------------------------------------------------------------------------------\nbool ContainerScanner::getItem(eos::ns::ContainerMdProto &proto, ContainerScanner::Item *item) {\n  if(mActive) {\n    if(!valid()) {\n      return false;\n    }\n\n    proto = mItemDeque.front().proto;\n\n    if(item != nullptr) {\n      *item = std::move(mItemDeque.front());\n    }\n\n    mScanned++;\n    return true;\n  }\n  else {\n    return mScanner.getItem(proto);\n  }\n}\n\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/inspector/ContainerScanner.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Class for scanning through all container metadata\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"proto/ContainerMd.pb.h\"\n#include <qclient/structures/QLocalityHash.hh>\n#include <folly/futures/Future.h>\n\nnamespace qclient {\n  class QClient;\n}\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! ContainerScannerPrimitive class: No support for full paths\n//------------------------------------------------------------------------------\nclass ContainerScannerPrimitive {\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ContainerScannerPrimitive(qclient::QClient &qcl);\n\n  //----------------------------------------------------------------------------\n  //! Is the iterator valid?\n  //----------------------------------------------------------------------------\n  bool valid() const;\n\n  //----------------------------------------------------------------------------\n  //! Advance iterator - only call when valid() == true\n  //----------------------------------------------------------------------------\n  void next();\n\n  //----------------------------------------------------------------------------\n  //! Is there an error?\n  //----------------------------------------------------------------------------\n  bool hasError(std::string &err) const;\n\n  //----------------------------------------------------------------------------\n  //! Get current element\n  //----------------------------------------------------------------------------\n  bool getItem(eos::ns::ContainerMdProto &item);\n\n  //----------------------------------------------------------------------------\n  //! Get number of elements scanned so far\n  //----------------------------------------------------------------------------\n  uint64_t getScannedSoFar() const;\n\nprivate:\n  qclient::QLocalityHash::Iterator mIterator;\n  std::string mError;\n  uint64_t mScanned = 0;\n};\n\n//------------------------------------------------------------------------------\n//! ContainerScanner class: Optional support for full paths\n//------------------------------------------------------------------------------\nclass ContainerScanner {\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ContainerScanner(qclient::QClient &qcl, bool fullPaths = false, bool counts = false);\n\n  //----------------------------------------------------------------------------\n  //! Is the iterator valid?\n  //----------------------------------------------------------------------------\n  bool valid() const;\n\n  //----------------------------------------------------------------------------\n  //! Advance iterator - only call when valid() == true\n  //----------------------------------------------------------------------------\n  void next();\n\n  //----------------------------------------------------------------------------\n  //! Is there an error?\n  //----------------------------------------------------------------------------\n  bool hasError(std::string &err) const;\n\n  //----------------------------------------------------------------------------\n  //! Return type of getItem\n  //----------------------------------------------------------------------------\n  struct Item {\n    eos::ns::ContainerMdProto proto;\n    folly::Future<std::string> fullPath;\n    folly::Future<uint64_t> fileCount;\n    folly::Future<uint64_t> containerCount;\n\n    Item() : proto(), fullPath(\"\"), fileCount(0), containerCount(0) {}\n\n    Item(eos::ns::ContainerMdProto &&pr, folly::Future<std::string> &&path,\n      folly::Future<uint64_t> &&filec, folly::Future<uint64_t> &&containerc)\n    : proto(std::move(pr)), fullPath(std::move(path)), fileCount(std::move(filec)),\n      containerCount(std::move(containerc)) {}\n  };\n\n  //----------------------------------------------------------------------------\n  //! Get current element\n  //----------------------------------------------------------------------------\n  bool getItem(eos::ns::ContainerMdProto &proto, Item *item = nullptr);\n\n  //----------------------------------------------------------------------------\n  //! Get number of elements scanned so far\n  //----------------------------------------------------------------------------\n  uint64_t getScannedSoFar() const;\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Ensure our item deque contanis a sufficient number of pending items\n  //----------------------------------------------------------------------------\n  void ensureItemDequeFull();\n\n  ContainerScannerPrimitive mScanner;\n  qclient::QClient &mQcl;\n  bool mFullPaths;\n  bool mCounts;\n  bool mActive;\n\n  std::deque<Item> mItemDeque;\n  uint64_t mScanned = 0;\n};\n\n\n\nEOSNSNAMESPACE_END"
  },
  {
    "path": "namespace/ns_quarkdb/inspector/FileMetadataFilter.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/inspector/FileMetadataFilter.hh\"\n#include \"namespace/ns_quarkdb/inspector/AttributeExtraction.hh\"\n#include \"common/Assert.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Empty constructor\n//------------------------------------------------------------------------------\nStringEvaluator::StringEvaluator()\n: mName(), mLiteral(true) {}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nStringEvaluator::StringEvaluator(const std::string &name, bool literal)\n: mName(name), mLiteral(literal) {}\n\n//------------------------------------------------------------------------------\n// Evaluate\n//------------------------------------------------------------------------------\nbool StringEvaluator::evaluate(const eos::ns::FileMdProto &proto, std::string &out) const {\n  if(mLiteral) {\n    out = mName;\n    return true;\n  }\n\n  return AttributeExtraction::asString(proto, mName, out);\n}\n\n//------------------------------------------------------------------------------\n// Describe\n//------------------------------------------------------------------------------\nstd::string StringEvaluator::describe() const {\n  if(mLiteral) return SSTR(\"'\" << mName << \"'\");\n  return mName;\n}\n\n//----------------------------------------------------------------------------\n//! Constructor\n//----------------------------------------------------------------------------\nEqualityFileMetadataFilter::EqualityFileMetadataFilter(const StringEvaluator &ev1, const StringEvaluator &ev2, bool reverse)\n: mEval1(ev1), mEval2(ev2), mReverse(reverse) {}\n\n//----------------------------------------------------------------------------\n// Does the given FileMdProto pass through the filter?\n//----------------------------------------------------------------------------\nbool EqualityFileMetadataFilter::check(const eos::ns::FileMdProto &proto) {\n  std::string val1, val2;\n\n  if(!mEval1.evaluate(proto, val1)) {\n    return false;\n  }\n\n  if(!mEval2.evaluate(proto, val2)) {\n    return false;\n  }\n\n  if(mReverse) {\n    return val1 != val2;\n  }\n  else {\n    return val1 == val2;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Is the object valid?\n//------------------------------------------------------------------------------\ncommon::Status EqualityFileMetadataFilter::isValid() const {\n  std::string tmp;\n  eos::ns::FileMdProto proto;\n\n  if(!mEval1.evaluate(proto, tmp)) {\n    return common::Status(EINVAL, SSTR(\"could not evaluate string expression \" << mEval1.describe()));\n  }\n\n  if(!mEval2.evaluate(proto, tmp)) {\n    return common::Status(EINVAL, SSTR(\"could not evaluate string expression \" << mEval2.describe()));\n  }\n\n  return common::Status();\n}\n\n//------------------------------------------------------------------------------\n// Describe object\n//------------------------------------------------------------------------------\nstd::string EqualityFileMetadataFilter::describe() const {\n  common::Status st = isValid();\n\n  if(!st) {\n    return SSTR(\"[\" << st.toString() << \"]\");\n  }\n\n  if(mReverse) {\n    return SSTR(mEval1.describe() << \" != \" << mEval2.describe());\n  }\n\n  return SSTR(mEval1.describe() << \" == \" << mEval2.describe());\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nLogicalMetadataFilter::LogicalMetadataFilter(std::unique_ptr<FileMetadataFilter> filt1,\n    std::unique_ptr<FileMetadataFilter> filt2, bool isOr) {\n\n  mFilter1 = std::move(filt1);\n  mFilter2 = std::move(filt2);\n  mIsOr = isOr;\n}\n\n//------------------------------------------------------------------------------\n//! Is the object valid?\n//------------------------------------------------------------------------------\ncommon::Status LogicalMetadataFilter::isValid() const {\n  if(!mFilter1->isValid()) return mFilter1->isValid();\n  return mFilter2->isValid();\n}\n\n//------------------------------------------------------------------------------\n// Does the given FileMdProto pass through the filter?\n//------------------------------------------------------------------------------\nbool LogicalMetadataFilter::check(const eos::ns::FileMdProto &proto) {\n  bool firstCondition = mFilter1->check(proto);\n\n  if(firstCondition && mIsOr) return true;\n  if(!firstCondition && !mIsOr) return false;\n\n  return mFilter2->check(proto);\n}\n\n//------------------------------------------------------------------------------\n// Describe object\n//------------------------------------------------------------------------------\nstd::string LogicalMetadataFilter::describe() const {\n  if(mIsOr) {\n    return SSTR(\"(\" << mFilter1->describe() << \" || \" << mFilter2->describe() << \")\");\n  }\n\n  return SSTR(\"(\" << mFilter1->describe() << \" && \" << mFilter2->describe() << \")\");\n}\n\n//------------------------------------------------------------------------------\n// Lex the given string\n//------------------------------------------------------------------------------\ncommon::Status FilterExpressionLexer::lex(const std::string &str, std::vector<ExpressionLexicalToken> &tokens) {\n  tokens.clear();\n\n  size_t pos = 0;\n  while(pos < str.size()) {\n    if(str[pos] == '(') {\n      tokens.emplace_back(ExpressionLexicalToken(TokenType::kLPAREN, \"(\"));\n      pos++;\n      continue;\n    }\n\n    if(str[pos] == ')') {\n      tokens.emplace_back(ExpressionLexicalToken(TokenType::kRPAREN, \")\"));\n      pos++;\n      continue;\n    }\n\n    if(isspace(str[pos])) {\n      pos++;\n      continue;\n    }\n\n    if(str[pos] == '\\'') {\n      size_t initialPos = pos;\n      pos++;\n\n      while(true) {\n        if(pos >= str.size()) {\n          return common::Status(EINVAL, \"lexing failed, mismatched quote: \\\"'\\\"\");\n        }\n\n        if(str[pos] == '\\'') {\n          tokens.emplace_back(ExpressionLexicalToken(TokenType::kLITERAL, std::string(str.begin()+initialPos+1, str.begin()+pos)));\n          break;\n        }\n\n        pos++;\n      }\n\n      pos++;\n      continue;\n    }\n\n    if(str[pos] == '=') {\n      pos++;\n\n      if(pos >= str.size() || str[pos] != '=') {\n        return common::Status(EINVAL, \"lexing failed, single stray '=' found (did you mean '=='?)\");\n      }\n\n      tokens.emplace_back(ExpressionLexicalToken(TokenType::kEQUALITY, \"==\"));\n\n      pos++;\n      continue;\n    }\n\n    if(str[pos] == '!') {\n      pos++;\n\n      if(pos >= str.size() || str[pos] != '=') {\n        return common::Status(EINVAL, \"lexing failed, single stray '!' found (did you mean '!='?)\");\n      }\n\n      tokens.emplace_back(ExpressionLexicalToken(TokenType::kINEQUALITY, \"!=\"));\n\n      pos++;\n      continue;\n    }\n\n    if(str[pos] == '&') {\n      pos++;\n\n      if(pos >= str.size() || str[pos] != '&') {\n        return common::Status(EINVAL, \"lexing failed, single stray '&' found (did you mean '&&'?)\");\n      }\n\n      tokens.emplace_back(ExpressionLexicalToken(TokenType::kAND, \"&&\"));\n\n      pos++;\n      continue;\n    }\n\n    if(str[pos] == '|') {\n      pos++;\n\n      if(pos >= str.size() || str[pos] != '|') {\n        return common::Status(EINVAL, \"lexing failed, single stray '||' found (did you mean '||'?)\");\n      }\n\n      tokens.emplace_back(ExpressionLexicalToken(TokenType::kOR, \"||\"));\n\n      pos++;\n      continue;\n    }\n\n    if(isalpha(str[pos])) {\n      size_t initialPos = pos;\n\n      while(true) {\n        if(pos >= str.size() || isspace(str[pos])) {\n          tokens.emplace_back(ExpressionLexicalToken(TokenType::kVAR, std::string(str.begin()+initialPos, str.begin()+pos)));\n          break;\n        }\n\n        pos++;\n      }\n    }\n    else {\n      return common::Status(EINVAL, SSTR(\"Parse error, unreconized character: \" << int(str[pos])));\n    }\n\n  }\n\n  return common::Status();\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFilterExpressionParser::FilterExpressionParser(const std::string &str, bool showDebug)\n: mDebug(showDebug) {\n\n  mStatus = FilterExpressionLexer::lex(str, mTokens);\n  mCurrent = 0;\n  if(!mStatus) {\n    fail(mStatus);\n    return;\n  }\n\n  consumeBlock(mFilter);\n}\n\n//------------------------------------------------------------------------------\n// Get status\n//------------------------------------------------------------------------------\ncommon::Status FilterExpressionParser::getStatus() const {\n  return mStatus;\n}\n\n//------------------------------------------------------------------------------\n// Get parsed filter -- call this only ONCE\n//------------------------------------------------------------------------------\nstd::unique_ptr<FileMetadataFilter> FilterExpressionParser::getFilter() {\n  return std::move(mFilter);\n}\n\n//------------------------------------------------------------------------------\n// Accept token\n//------------------------------------------------------------------------------\nbool FilterExpressionParser::accept(TokenType type, ExpressionLexicalToken *tk) {\n  if(mCurrent >= mTokens.size()) return false;\n  if(mTokens[mCurrent].mType != type) return false;\n  if(tk) *tk = mTokens[mCurrent];\n\n  mCurrent++;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Fail with the given error message\n//------------------------------------------------------------------------------\nbool FilterExpressionParser::fail(int errcode, const std::string &msg) {\n  return fail(common::Status(errcode, msg));\n}\n\n//------------------------------------------------------------------------------\n// Fail with the given status\n//------------------------------------------------------------------------------\nbool FilterExpressionParser::fail(const common::Status &st) {\n  mStatus = st;\n  mFilter.reset();\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Consume simple string expression\n//------------------------------------------------------------------------------\nbool FilterExpressionParser::consumeStringExpression(StringEvaluator &eval) {\n  ExpressionLexicalToken token;\n  if(accept(TokenType::kVAR, &token)) {\n    eval = StringEvaluator(token.mContents, false);\n    return true;\n  }\n\n  if(accept(TokenType::kLITERAL, &token)) {\n    eval = StringEvaluator(token.mContents, true);\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Has next lexical token?\n//------------------------------------------------------------------------------\nbool FilterExpressionParser::hasNextToken() const {\n  return mCurrent < mTokens.size();\n}\n\n//----------------------------------------------------------------------------\n// Look-ahead token, but don't consume\n//----------------------------------------------------------------------------\nbool FilterExpressionParser::isLookahead(TokenType type) const {\n  if(!hasNextToken()) return false;\n  return mTokens[mCurrent].mType == type;\n}\n\n//------------------------------------------------------------------------------\n// Consume parenthesied block\n//------------------------------------------------------------------------------\nbool FilterExpressionParser::consumeParenthesizedBlock(std::unique_ptr<FileMetadataFilter> &filter) {\n  if(!accept(TokenType::kLPAREN)) {\n    return fail(EINVAL, \"expected '(' token\");\n  }\n\n  if(!consumeBlock(filter)) {\n    return false;\n  }\n\n  if(!accept(TokenType::kRPAREN)) {\n    return fail(EINVAL, \"expected ')' token\");\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Consume block\n//------------------------------------------------------------------------------\nbool FilterExpressionParser::consumeBlock(std::unique_ptr<FileMetadataFilter> &filter) {\n  if(isLookahead(TokenType::kLPAREN)) {\n    return consumeParenthesizedBlock(filter);\n  }\n\n  std::unique_ptr<FileMetadataFilter> leftSide;\n  std::unique_ptr<FileMetadataFilter> rightSide;\n\n  if(!consumeBooleanExpression(leftSide)) {\n    return false;\n  }\n\n  if(!hasNextToken() || isLookahead(TokenType::kRPAREN)) {\n    filter = std::move(leftSide);\n    return true;\n  }\n\n  if(!accept(TokenType::kAND)) {\n    return fail(EINVAL, \"expected '&&' token\");\n  }\n\n  if(!consumeBlock(rightSide)) {\n    return false;\n  }\n\n  filter.reset(new LogicalMetadataFilter(std::move(leftSide), std::move(rightSide), false));\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Consume metadata filter\n//------------------------------------------------------------------------------\nbool FilterExpressionParser::consumeBooleanExpression(std::unique_ptr<FileMetadataFilter> &filter) {\n  StringEvaluator eval1;\n  StringEvaluator eval2;\n\n  if(!consumeStringExpression(eval1)) {\n    return fail(EINVAL, \"expected string expression\");\n  }\n\n  bool reversedEquality;\n\n  if(accept(TokenType::kEQUALITY)) {\n    reversedEquality = false;\n  }\n  else if(accept(TokenType::kINEQUALITY)) {\n    reversedEquality = true;\n  }\n  else {\n    return fail(EINVAL, \"expected '==' or '!=' token\");\n  }\n\n  if(!consumeStringExpression(eval2)) {\n    return fail(EINVAL, \"expected string expression\");\n  }\n\n  filter.reset(new EqualityFileMetadataFilter(eval1, eval2, reversedEquality));\n  return true;\n}\n\n\nEOSNSNAMESPACE_END\n\n"
  },
  {
    "path": "namespace/ns_quarkdb/inspector/FileMetadataFilter.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Class to filter out FileMDs\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"proto/FileMd.pb.h\"\n#include \"common/Status.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class to decide whether to show a particular FileMD\n//------------------------------------------------------------------------------\nclass FileMetadataFilter {\npublic:\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~FileMetadataFilter() {}\n\n  //----------------------------------------------------------------------------\n  //! Is the object valid?\n  //----------------------------------------------------------------------------\n  virtual common::Status isValid() const = 0;\n\n  //----------------------------------------------------------------------------\n  //! Does the given FileMdProto pass through the filter?\n  //----------------------------------------------------------------------------\n  virtual bool check(const eos::ns::FileMdProto &proto) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Describe object\n  //----------------------------------------------------------------------------\n  virtual std::string describe() const = 0;\n\n};\n\n//------------------------------------------------------------------------------\n//! String evaluator\n//------------------------------------------------------------------------------\nclass StringEvaluator {\npublic:\n  //----------------------------------------------------------------------------\n  //! Empty constructor\n  //----------------------------------------------------------------------------\n  StringEvaluator();\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  StringEvaluator(const std::string &name, bool literal);\n\n  //----------------------------------------------------------------------------\n  //! Evaluate\n  //----------------------------------------------------------------------------\n  bool evaluate(const eos::ns::FileMdProto &proto, std::string &out) const;\n\n  //----------------------------------------------------------------------------\n  //! Describe\n  //----------------------------------------------------------------------------\n  std::string describe() const;\n\nprivate:\n  std::string mName;\n  bool mLiteral;\n};\n\n//------------------------------------------------------------------------------\n//! Filter which checks a particular FileMdProto attribute for equality\n//------------------------------------------------------------------------------\nclass EqualityFileMetadataFilter : public FileMetadataFilter {\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  EqualityFileMetadataFilter(const StringEvaluator &ev1, const StringEvaluator &ev2, bool reverse);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~EqualityFileMetadataFilter() {}\n\n  //----------------------------------------------------------------------------\n  //! Is the object valid?\n  //----------------------------------------------------------------------------\n  virtual common::Status isValid() const override;\n\n  //----------------------------------------------------------------------------\n  //! Does the given FileMdProto pass through the filter?\n  //----------------------------------------------------------------------------\n  virtual bool check(const eos::ns::FileMdProto &proto) override;\n\n  //----------------------------------------------------------------------------\n  //! Describe object\n  //----------------------------------------------------------------------------\n  virtual std::string describe() const override;\n\nprivate:\n  StringEvaluator mEval1;\n  StringEvaluator mEval2;\n  bool mReverse;\n};\n\n//------------------------------------------------------------------------------\n//! && and || filter\n//------------------------------------------------------------------------------\nclass LogicalMetadataFilter : public FileMetadataFilter {\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  LogicalMetadataFilter(std::unique_ptr<FileMetadataFilter> filt1,\n    std::unique_ptr<FileMetadataFilter> filt2, bool isOR);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~LogicalMetadataFilter() {}\n\n  //----------------------------------------------------------------------------\n  //! Is the object valid?\n  //----------------------------------------------------------------------------\n  virtual common::Status isValid() const override;\n\n  //----------------------------------------------------------------------------\n  //! Does the given FileMdProto pass through the filter?\n  //----------------------------------------------------------------------------\n  virtual bool check(const eos::ns::FileMdProto &proto) override;\n\n  //----------------------------------------------------------------------------\n  //! Describe object\n  //----------------------------------------------------------------------------\n  virtual std::string describe() const override;\n\nprivate:\n  std::unique_ptr<FileMetadataFilter> mFilter1;\n  std::unique_ptr<FileMetadataFilter> mFilter2;\n  bool mIsOr;\n};\n\n//------------------------------------------------------------------------------\n//! Token type\n//------------------------------------------------------------------------------\nenum class TokenType {\n  kLPAREN, kRPAREN, kQUOTE, kLITERAL, kEQUALITY, kINEQUALITY, kAND, kOR, kVAR\n};\n\nstruct ExpressionLexicalToken {\n  TokenType mType;\n  std::string mContents;\n\n  ExpressionLexicalToken() {}\n\n  ExpressionLexicalToken(TokenType t, std::string c) : mType(t), mContents(c) {}\n\n  bool operator==(const ExpressionLexicalToken &other) const {\n    return mType == other.mType && mContents == other.mContents;\n  }\n};\n\n//------------------------------------------------------------------------------\n//! Filter expression lexer\n//------------------------------------------------------------------------------\nclass FilterExpressionLexer {\npublic:\n  //----------------------------------------------------------------------------\n  //! Lex the given string\n  //----------------------------------------------------------------------------\n  static common::Status lex(const std::string &str, std::vector<ExpressionLexicalToken> &tokens);\n};\n\n//------------------------------------------------------------------------------\n//! Filter expression parser\n//------------------------------------------------------------------------------\nclass FilterExpressionParser {\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  FilterExpressionParser(const std::string &str, bool showDebug);\n\n  //----------------------------------------------------------------------------\n  //! Get status\n  //----------------------------------------------------------------------------\n  common::Status getStatus() const;\n\n  //----------------------------------------------------------------------------\n  //! Get parsed filter -- call this only ONCE\n  //----------------------------------------------------------------------------\n  std::unique_ptr<FileMetadataFilter> getFilter();\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Accept token\n  //----------------------------------------------------------------------------\n  bool accept(TokenType type, ExpressionLexicalToken *token = nullptr);\n\n  //----------------------------------------------------------------------------\n  //! Look-ahead token, but don't consume\n  //----------------------------------------------------------------------------\n  bool isLookahead(TokenType type) const;\n\n  //----------------------------------------------------------------------------\n  //! Has next lexical token?\n  //----------------------------------------------------------------------------\n  bool hasNextToken() const;\n\n  //----------------------------------------------------------------------------\n  // Consume parenthesied block\n  //----------------------------------------------------------------------------\n  bool consumeParenthesizedBlock(std::unique_ptr<FileMetadataFilter> &filter);\n\n  //----------------------------------------------------------------------------\n  //! Consume block\n  //----------------------------------------------------------------------------\n  bool consumeBlock(std::unique_ptr<FileMetadataFilter> &filter);\n\n  //----------------------------------------------------------------------------\n  //! Consume metadata filter\n  //----------------------------------------------------------------------------\n  bool consumeBooleanExpression(std::unique_ptr<FileMetadataFilter> &filter);\n\n  //----------------------------------------------------------------------------\n  //! Consume simple string expression\n  //----------------------------------------------------------------------------\n  bool consumeStringExpression(StringEvaluator &eval);\n\n  //----------------------------------------------------------------------------\n  //! Fail with the given status\n  //----------------------------------------------------------------------------\n  bool fail(const common::Status &st);\n\n  //----------------------------------------------------------------------------\n  //! Fail with the given status\n  //----------------------------------------------------------------------------\n  bool fail(int errcode, const std::string &msg);\n\n  std::vector<ExpressionLexicalToken> mTokens;\n  size_t mCurrent;\n\n  common::Status mStatus;\n  bool mDebug;\n  std::unique_ptr<FileMetadataFilter> mFilter;\n};\n\n\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/inspector/FileScanner.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/inspector/FileScanner.hh\"\n#include \"namespace/ns_quarkdb/persistency/Serialization.hh\"\n#include \"namespace/ns_quarkdb/persistency/MetadataFetcher.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFileScannerPrimitive::FileScannerPrimitive(qclient::QClient &qcl)\n: mIterator(&qcl, \"eos-file-md\") { }\n\n//------------------------------------------------------------------------------\n// Is the iterator valid?\n//------------------------------------------------------------------------------\nbool FileScannerPrimitive::valid() const {\n  if(!mError.empty()) {\n    return false;\n  }\n\n  return mIterator.valid();\n}\n\n//----------------------------------------------------------------------------\n// Advance iterator - only call when valid() == true\n//----------------------------------------------------------------------------\nvoid FileScannerPrimitive::next() {\n  mIterator.next();\n}\n\n//------------------------------------------------------------------------------\n// Is there an error?\n//------------------------------------------------------------------------------\nbool FileScannerPrimitive::hasError(std::string &err) const {\n  if(!mError.empty()) {\n    err = mError;\n    return true;\n  }\n\n  return mIterator.hasError(err);\n}\n\n//------------------------------------------------------------------------------\n// Get current element\n//------------------------------------------------------------------------------\nbool FileScannerPrimitive::getItem(eos::ns::FileMdProto &item) {\n  if(!valid()) {\n    return false;\n  }\n\n  std::string currentValue = mIterator.getValue();\n  eos::MDStatus status = Serialization::deserialize(currentValue.c_str(), currentValue.size(), item);\n\n  if(!status.ok()) {\n    mError = SSTR(\"Error while deserializing: \" << status.getError());\n    return false;\n  }\n\n  mScanned++;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Get number of elements scanned so far\n//------------------------------------------------------------------------------\nuint64_t FileScannerPrimitive::getScannedSoFar() const {\n  return mScanned;\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFileScanner::FileScanner(qclient::QClient &qcl, bool fullPaths)\n: mScanner(qcl), mQcl(qcl), mFullPaths(fullPaths) {\n\n  mActive = mFullPaths;\n\n  if(mActive) {\n    ensureItemDequeFull();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Is the iterator valid?\n//------------------------------------------------------------------------------\nbool FileScanner::valid() const {\n  if(mActive) {\n    return !mItemDeque.empty();\n  }\n  else {\n    return mScanner.valid();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Advance iterator - only call when valid() == true\n//------------------------------------------------------------------------------\nvoid FileScanner::next() {\n  if(mActive) {\n    if(valid()) {\n      mItemDeque.pop_front();\n      ensureItemDequeFull();\n    }\n  }\n  else {\n    return mScanner.next();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Ensure our item deque contains a sufficient number of pending items\n//------------------------------------------------------------------------------\nvoid FileScanner::ensureItemDequeFull() {\n  if(!mActive) return;\n\n  while(mScanner.valid() && mItemDeque.size() < 500) {\n    eos::ns::FileMdProto item;\n    if(mScanner.getItem(item)) {\n\n      folly::Future<std::string> fullPath = \"\";\n\n      if(mFullPaths) {\n        fullPath = MetadataFetcher::resolveFullPath(mQcl, ContainerIdentifier(item.cont_id()));\n      }\n\n      mItemDeque.emplace_back(\n        std::move(item),\n        std::move(fullPath)\n      );\n    }\n\n    mScanner.next();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Is there an error?\n//------------------------------------------------------------------------------\nbool FileScanner::hasError(std::string &err) const {\n  if(mActive) {\n    return !mItemDeque.empty() && mScanner.hasError(err);\n  }\n  else {\n    return mScanner.hasError(err);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get number of elements scanned so far\n//------------------------------------------------------------------------------\nuint64_t FileScanner::getScannedSoFar() const {\n  if(mActive) {\n    return mScanned;\n  }\n  else{\n    return mScanner.getScannedSoFar();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get current element\n//------------------------------------------------------------------------------\nbool FileScanner::getItem(eos::ns::FileMdProto &proto, FileScanner::Item *item) {\n  if(mActive) {\n    if(!valid()) {\n      return false;\n    }\n\n    proto = mItemDeque.front().proto;\n\n    if(item != nullptr) {\n      *item = std::move(mItemDeque.front());\n    }\n\n    mScanned++;\n    return true;\n  }\n  else {\n    return mScanner.getItem(proto);\n  }\n}\n\n\n\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/inspector/FileScanner.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Class for scanning through all file metadata\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"proto/FileMd.pb.h\"\n#include <qclient/structures/QLocalityHash.hh>\n#include <folly/futures/Future.h>\n\nnamespace qclient {\n  class QClient;\n}\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! FileScannerPrimitive class - no support for full paths\n//------------------------------------------------------------------------------\nclass FileScannerPrimitive {\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  FileScannerPrimitive(qclient::QClient &qcl);\n\n  //----------------------------------------------------------------------------\n  //! Is the iterator valid?\n  //----------------------------------------------------------------------------\n  bool valid() const;\n\n  //----------------------------------------------------------------------------\n  //! Advance iterator - only call when valid() == true\n  //----------------------------------------------------------------------------\n  void next();\n\n  //----------------------------------------------------------------------------\n  //! Is there an error?\n  //----------------------------------------------------------------------------\n  bool hasError(std::string &err) const;\n\n  //----------------------------------------------------------------------------\n  //! Get current element\n  //----------------------------------------------------------------------------\n  bool getItem(eos::ns::FileMdProto &item);\n\n  //----------------------------------------------------------------------------\n  //! Get number of elements scanned so far\n  //----------------------------------------------------------------------------\n  uint64_t getScannedSoFar() const;\n\nprivate:\n  qclient::QLocalityHash::Iterator mIterator;\n  std::string mError;\n  uint64_t mScanned = 0;\n};\n\n//------------------------------------------------------------------------------\n//! FileScanner class - optional support for full paths\n//------------------------------------------------------------------------------\nclass FileScanner {\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  FileScanner(qclient::QClient &qcl, bool fullPaths = false);\n\n  //----------------------------------------------------------------------------\n  //! Is the iterator valid?\n  //----------------------------------------------------------------------------\n  bool valid() const;\n\n  //----------------------------------------------------------------------------\n  //! Advance iterator - only call when valid() == true\n  //----------------------------------------------------------------------------\n  void next();\n\n  //----------------------------------------------------------------------------\n  //! Is there an error?\n  //----------------------------------------------------------------------------\n  bool hasError(std::string &err) const;\n\n  //----------------------------------------------------------------------------\n  //! Return type of getItem\n  //----------------------------------------------------------------------------\n  struct Item {\n    eos::ns::FileMdProto proto;\n    folly::Future<std::string> fullPath;\n\n    Item() : proto(), fullPath(\"\") {}\n\n    Item(eos::ns::FileMdProto &&pr, folly::Future<std::string> &&path)\n    : proto(std::move(pr)), fullPath(std::move(path)) {}\n  };\n\n  //----------------------------------------------------------------------------\n  //! Get current element\n  //----------------------------------------------------------------------------\n  bool getItem(eos::ns::FileMdProto &proto, Item *item = nullptr);\n\n  //----------------------------------------------------------------------------\n  //! Get number of elements scanned so far\n  //----------------------------------------------------------------------------\n  uint64_t getScannedSoFar() const;\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Ensure our item deque contanis a sufficient number of pending items\n  //----------------------------------------------------------------------------\n  void ensureItemDequeFull();\n\n  FileScannerPrimitive mScanner;\n  qclient::QClient &mQcl;\n  bool mFullPaths;\n  bool mActive;\n\n  std::deque<Item> mItemDeque;\n  uint64_t mScanned = 0;\n};\n\n\n\n\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/inspector/Inspector.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/inspector/Inspector.hh\"\n#include \"namespace/ns_quarkdb/explorer/NamespaceExplorer.hh\"\n#include \"namespace/ns_quarkdb/persistency/MetadataFetcher.hh\"\n#include \"namespace/ns_quarkdb/inspector/ContainerScanner.hh\"\n#include \"namespace/ns_quarkdb/inspector/FileScanner.hh\"\n#include \"namespace/ns_quarkdb/inspector/Printing.hh\"\n#include \"namespace/ns_quarkdb/inspector/OutputSink.hh\"\n#include \"namespace/ns_quarkdb/inspector/FileMetadataFilter.hh\"\n#include \"namespace/ns_quarkdb/FileMD.hh\"\n#include \"namespace/ns_quarkdb/ContainerMD.hh\"\n#include \"namespace/ns_quarkdb/persistency/RequestBuilder.hh\"\n#include \"namespace/ns_quarkdb/persistency/FileSystemIterator.hh\"\n#include \"namespace/ns_quarkdb/accounting/FileSystemHandler.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"namespace/utils/Checksum.hh\"\n#include \"namespace/Constants.hh\"\n#include \"common/LayoutId.hh\"\n#include \"common/IntervalStopwatch.hh\"\n#include \"common/InodeTranslator.hh\"\n#include \"common/ParseUtils.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/RegexWrapper.hh\"\n#include \"common/config/ConfigParsing.hh\"\n#include <folly/executors/IOThreadPoolExecutor.h>\n#include <qclient/QClient.hh>\n#include <qclient/ResponseParsing.hh>\n#include <google/protobuf/util/json_util.h>\n#include <json/json.h>\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Escape non-printable string\n//------------------------------------------------------------------------------\nstatic std::string escapeNonPrintable(const std::string& str)\n{\n  std::stringstream ss;\n\n  for (size_t i = 0; i < str.size(); i++) {\n    if (isprint(str[i])) {\n      ss << str[i];\n    } else if (str[i] == '\\0') {\n      ss << \"\\\\x00\";\n    } else {\n      char buff[16];\n      snprintf(buff, 16, \"\\\\x%02X\", (unsigned char) str[i]);\n      ss << buff;\n    }\n  }\n\n  return ss.str();\n}\n\n//------------------------------------------------------------------------------\n// Turn bool to yes / no\n//------------------------------------------------------------------------------\nstatic std::string toYesOrNo(bool val)\n{\n  if (val) {\n    return \"Yes\";\n  }\n\n  return \"No\";\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nInspector::Inspector(qclient::QClient& qcl, OutputSink& sink)\n  : mQcl(qcl), mOutputSink(sink) { }\n\n//------------------------------------------------------------------------------\n// Load configuration\n//------------------------------------------------------------------------------\nbool Inspector::loadConfiguration()\n{\n  qclient::redisReplyPtr reply = mQcl.exec(\"HGETALL\", \"eos-config:default\").get();\n  qclient::HgetallParser parser(reply);\n\n  if (!parser.ok()) {\n    return false;\n  }\n\n  mgmConfiguration = parser.value();\n\n  for (auto it = mgmConfiguration.begin(); it != mgmConfiguration.end(); it++) {\n    if (eos::common::startsWith(it->first, \"fs:\")) {\n      std::map<std::string, std::string> fsConfig;\n\n      if (eos::common::ConfigParsing::parseFilesystemConfig(it->second, fsConfig)) {\n        if (fsConfig.find(\"id\") != fsConfig.end()) {\n          int64_t fsid;\n\n          if (common::ParseInt64(fsConfig[\"id\"], fsid)) {\n            validFsIds.insert(fsid);\n          }\n        }\n      }\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Activate the given metadata filter\n//------------------------------------------------------------------------------\nvoid Inspector::setMetadataFilter(std::unique_ptr<FileMetadataFilter> filter)\n{\n  mMetadataFilter = std::move(filter);\n}\n\n//------------------------------------------------------------------------------\n// Is the connection to QDB ok? If not, pointless to run anything else.\n//------------------------------------------------------------------------------\nbool Inspector::checkConnection(std::string& err)\n{\n  qclient::redisReplyPtr reply = mQcl.exec(\"PING\").get();\n\n  if (!reply) {\n    err = \"Could not connect to the given QDB cluster\";\n    return false;\n  }\n\n  if (reply->type != REDIS_REPLY_STATUS ||\n      std::string(reply->str, reply->len) != \"PONG\") {\n    err = SSTR(\"Received unexpected response in checkConnection: \" <<\n               qclient::describeRedisReply(reply));\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Construct path\n//------------------------------------------------------------------------------\nstatic std::string constructPath(const std::string& rootPath, const std::string\n                                 &fullPath, bool relative)\n{\n  if (relative) {\n    return fullPath.substr(rootPath.size());\n  }\n\n  return fullPath;\n}\n\nclass ExpansionTrim : public ExpansionDecider\n{\npublic:\n  ExpansionTrim(const std::string& pathsToTrim) :\n    trimRegex(pathsToTrim) {}\n\n  virtual bool shouldExpandContainer(const eos::ns::ContainerMdProto& proto,\n                                     const eos::IContainerMD::XAttrMap& attrs,\n                                     const std::string& fullPath) override\n  {\n    return !eos::common::eos_regex_search(fullPath, trimRegex);\n  }\nprivate:\n  std::string trimRegex;\n};\n\n//------------------------------------------------------------------------------\n// Scan contents of the given path.\n//------------------------------------------------------------------------------\nint Inspector::scan(const std::string& rootPath, bool relative, bool rawPaths,\n                    bool noDirs, bool noFiles, uint32_t maxDepth, const std::string& trimPaths)\n{\n  FilePrintingOptions filePrintingOpts;\n  ContainerPrintingOptions containerPrintingOpts;\n  ExplorationOptions explorerOpts;\n  explorerOpts.ignoreFiles = noFiles;\n  explorerOpts.depthLimit = maxDepth;\n\n  if (!trimPaths.empty()) {\n    try {\n      auto expT = new ExpansionTrim(trimPaths);\n      explorerOpts.expansionDecider.reset(expT);\n    } catch (const std::regex_error& e) {\n      std::cout << \"regex_error caught: \" << e.what() << '\\n';\n\n      if (e.code() == std::regex_constants::error_brack) {\n        std::cout << \"The code was error_brack\\n\";\n      }\n    }\n  }\n\n  NamespaceItem item;\n  std::unique_ptr<folly::Executor> executor(new folly::IOThreadPoolExecutor(4));\n  std::unique_ptr<NamespaceExplorer> explorer = nullptr;\n\n  try {\n    explorer = std::unique_ptr<NamespaceExplorer>(new NamespaceExplorer(rootPath,\n               explorerOpts, mQcl, executor.get()));\n  } catch (const eos::MDException& exc) {\n    mOutputSink.err(SSTR(\"NamespaceExplorer -- \" << exc.what()));\n    return 1;\n  }\n\n  while (explorer->fetch(item)) {\n    if (noDirs && !item.isFile) {\n      continue;\n    }\n\n    std::string outputPath = constructPath(rootPath, item.fullPath, relative);\n\n    if (rawPaths) {\n      mOutputSink.print(outputPath);\n      continue;\n    }\n\n    if (item.isFile) {\n      mOutputSink.printWithCustomPath(item.fileMd, filePrintingOpts, outputPath);\n      continue;\n    }\n\n    if (!item.isFile) {\n      mOutputSink.printWithCustomPath(item.containerMd, containerPrintingOpts,\n                                      outputPath);\n      continue;\n    }\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Dump contents of the given path. ERRNO-like integer return value, 0\n// means no error.\n//------------------------------------------------------------------------------\nint Inspector::dump(const std::string& dumpPath, bool relative, bool rawPaths,\n                    bool noDirs, bool noFiles, bool showSize, bool showMtime,\n                    const std::string& attrQuery, std::ostream& out)\n{\n  ExplorationOptions explorerOpts;\n  explorerOpts.ignoreFiles = noFiles;\n  std::unique_ptr<folly::Executor> executor(new folly::IOThreadPoolExecutor(4));\n  NamespaceItem item;\n  std::unique_ptr<NamespaceExplorer> explorer = nullptr;\n\n  try {\n    explorer = std::unique_ptr<NamespaceExplorer>(new NamespaceExplorer(dumpPath,\n               explorerOpts, mQcl, executor.get()));\n  } catch (const eos::MDException& exc) {\n    mOutputSink.err(SSTR(\"NamespaceExplorer -- \" << exc.what()));\n    return 1;\n  }\n\n  while (explorer->fetch(item)) {\n    if (noDirs && !item.isFile) {\n      continue;\n    }\n\n    if (!attrQuery.empty()) {\n      out << \" \" << attrQuery << \"=\";\n\n      if (!item.isFile) {\n        if (item.containerMd.xattrs().count(attrQuery) != 0) {\n          out << item.containerMd.xattrs().at(attrQuery) << \" \";\n        } else {\n          out << \" \";\n        }\n      } else {\n        if (item.fileMd.xattrs().count(attrQuery) != 0) {\n          out << item.fileMd.xattrs().at(attrQuery) << \" \";\n        } else {\n          out << \" \";\n        }\n      }\n    }\n\n    if (!rawPaths) {\n      out << \"path=\";\n    }\n\n    if (relative) {\n      out << item.fullPath.substr(dumpPath.size());\n    } else {\n      out << item.fullPath;\n    }\n\n    if (!rawPaths && item.isFile) {\n      out << \" id=\" << item.fileMd.id();\n      std::string xs;\n      eos::appendChecksumOnStringProtobuf(item.fileMd, xs);\n      out << \" xs=\" << xs;\n    }\n\n    if (showSize && item.isFile) {\n      out << \" size=\" << item.fileMd.size();\n    }\n\n    if (showMtime && item.isFile) {\n      out << \" mtime=\" << Printing::timespecToTimestamp(Printing::parseTimespec(\n            item.fileMd.mtime()));\n    }\n\n    out << std::endl;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Fetch path or name from a combination of ContainerMdProto +\n// ContainerScanner::Item, return as much information as is available\n//------------------------------------------------------------------------------\nstd::string fetchNameOrPath(const eos::ns::ContainerMdProto& proto,\n                            ContainerScanner::Item& item)\n{\n  item.fullPath.wait();\n\n  if (item.fullPath.hasException()) {\n    return proto.name();\n  }\n\n  std::string fullPath = std::move(item.fullPath).get();\n\n  if (fullPath.empty()) {\n    return proto.name();\n  }\n\n  return fullPath;\n}\n\n//------------------------------------------------------------------------------\n// Safe uint64_t get, without exceptions. Return 0 in case of exception.\n//------------------------------------------------------------------------------\nuint64_t safeGet(folly::Future<uint64_t>& fut)\n{\n  fut.wait();\n\n  if (fut.hasException()) {\n    return 0;\n  }\n\n  uint64_t val = std::move(fut).get();\n  fut = val;\n  return val;\n}\n\n//------------------------------------------------------------------------------\n// Scan all directories in the namespace, and print out some information\n// about each one. (even potentially unreachable directories)\n//------------------------------------------------------------------------------\nint Inspector::scanDirs(bool onlyNoAttrs, bool fullPaths, bool countContents,\n                        size_t countThreshold)\n{\n  if (countThreshold > 0) {\n    countContents = true;\n  }\n\n  ContainerPrintingOptions opts;\n  ContainerScanner containerScanner(mQcl, fullPaths, countContents);\n\n  while (containerScanner.valid()) {\n    eos::ns::ContainerMdProto proto;\n    ContainerScanner::Item item;\n\n    if (!containerScanner.getItem(proto, &item)) {\n      break;\n    }\n\n    if (onlyNoAttrs && !proto.xattrs().empty()) {\n      containerScanner.next();\n      continue;\n    }\n\n    if (countThreshold > 0 &&\n        (safeGet(item.fileCount) + safeGet(item.containerCount)) < countThreshold) {\n      containerScanner.next();\n      continue;\n    }\n\n    mOutputSink.print(proto, opts, item, countContents);\n    containerScanner.next();\n  }\n\n  std::string errorString;\n\n  if (containerScanner.hasError(errorString)) {\n    mOutputSink.err(errorString);\n    return 1;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Fetch path or name from a combination of FileMdProto +\n// FileScanner::Item, return as much information as is available\n//------------------------------------------------------------------------------\nstd::string fetchNameOrPath(const eos::ns::FileMdProto& proto,\n                            FileScanner::Item& item)\n{\n  item.fullPath.wait();\n\n  if (item.fullPath.hasException()) {\n    return proto.name();\n  }\n\n  std::string fullPath = std::move(item.fullPath).get();\n\n  if (fullPath.empty()) {\n    return proto.name();\n  }\n\n  return SSTR(fullPath << proto.name());\n}\n\n//------------------------------------------------------------------------------\n// Are all locations in the given set?\n//------------------------------------------------------------------------------\ntemplate<typename T>\nbool allInSet(const T& vec, const std::set<int64_t>& targetSet)\n{\n  for (auto it = vec.begin(); it != vec.end(); it++) {\n    if (targetSet.find(*it) == targetSet.end()) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Cross-check locations with MGM config\n//------------------------------------------------------------------------------\nstatic bool checkLocations(const eos::ns::FileMdProto& proto,\n                           const std::set<int64_t>& validFsIds)\n{\n  if (proto.cont_id() == 0) {\n    return true;\n  }\n\n  return allInSet(proto.locations(), validFsIds);\n}\n\n//------------------------------------------------------------------------------\n// Scan all file metadata in the namespace, and print out some information\n// about each one. (even potentially unreachable ones)\n//------------------------------------------------------------------------------\nint Inspector::scanFileMetadata(bool onlySizes, bool fullPaths,\n                                bool findUnknownFsids)\n{\n  if (findUnknownFsids && !loadConfiguration()) {\n    mOutputSink.err(\"could not load MGM configuration -- necessary when using --find-unknown-fsids\");\n    return -1;\n  }\n\n  FileScanner fileScanner(mQcl, fullPaths);\n  FilePrintingOptions opts;\n\n  while (fileScanner.valid()) {\n    FileScanner::Item item;\n    eos::ns::FileMdProto proto;\n\n    if (!fileScanner.getItem(proto, &item)) {\n      break;\n    }\n\n    if (findUnknownFsids && checkLocations(proto, validFsIds)) {\n      fileScanner.next();\n      continue;\n    }\n\n    if (mMetadataFilter && !mMetadataFilter->check(proto)) {\n      fileScanner.next();\n      continue;\n    }\n\n    if (onlySizes) {\n      mOutputSink.print(std::to_string(proto.size()));\n    } else {\n      mOutputSink.print(proto, opts, item);\n    }\n\n    fileScanner.next();\n  }\n\n  std::string errorString;\n\n  if (fileScanner.hasError(errorString)) {\n    mOutputSink.err(errorString);\n    return 1;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Scan all deathrow entries\n//------------------------------------------------------------------------------\nint Inspector::scanDeathrow(std::ostream& out, std::ostream& err)\n{\n  FileScanner fileScanner(mQcl);\n\n  while (fileScanner.valid()) {\n    FileScanner::Item item;\n    eos::ns::FileMdProto proto;\n\n    if (!fileScanner.getItem(proto, &item)) {\n      break;\n    }\n\n    if (proto.cont_id() != 0) {\n      break;\n    }\n\n    std::string xs;\n    eos::appendChecksumOnStringProtobuf(proto, xs);\n    out << \"fid=\" << proto.id() << \" name=\" << fetchNameOrPath(proto,\n        item) << \" pid=\" << proto.cont_id() << \" uid=\" << proto.uid() << \" size=\" <<\n        proto.size() << \" xs=\" << xs << std::endl;\n    fileScanner.next();\n  }\n\n  std::string errorString;\n\n  if (fileScanner.hasError(errorString)) {\n    err << errorString;\n    return 1;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Forcefully overwrite the given ContainerMD - USE WITH CAUTION\n//------------------------------------------------------------------------------\nint Inspector::overwriteContainerMD(bool dryRun, uint64_t id, uint64_t parentId,\n                                    const std::string& name, std::ostream& out, std::ostream& err)\n{\n  eos::ns::ContainerMdProto val;\n  val.set_id(id);\n  val.set_parent_id(parentId);\n  val.set_name(name);\n  QuarkContainerMD containerMD;\n  containerMD.initialize(std::move(val), IContainerMD::FileMap(),\n                         IContainerMD::ContainerMap());\n  std::vector<RedisRequest> requests;\n  requests.emplace_back(RequestBuilder::writeContainerProto(&containerMD));\n  executeRequestBatch(requests, {}, dryRun, out, err);\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Serialize locations vector\n//------------------------------------------------------------------------------\ntemplate<typename T>\nstatic std::string serializeLocations(const T& vec)\n{\n  std::ostringstream stream;\n\n  for (int i = 0; i < vec.size(); i++) {\n    stream << vec[i];\n\n    if (i != vec.size() - 1) {\n      stream << \",\";\n    }\n  }\n\n  return stream.str();\n}\n\n//------------------------------------------------------------------------------\n// Check if we should print based on name and internal filter\n//------------------------------------------------------------------------------\nbool shouldPrint(bool filterInternal, const std::string& fullPath)\n{\n  if (!filterInternal) {\n    return true;\n  }\n\n  //----------------------------------------------------------------------------\n  // Filter out aborted atomic uploads..\n  //----------------------------------------------------------------------------\n  if (fullPath.find(\"/.sys.a#.\") != std::string::npos) {\n    return false;\n  }\n\n  //----------------------------------------------------------------------------\n  // Filter out files under proc..\n  //----------------------------------------------------------------------------\n  if (common::startsWith(fullPath, \"/eos/\")) {\n    std::string chopped = std::string(fullPath.c_str() + 5, fullPath.size() - 5);\n    size_t nextSlash = chopped.find(\"/\");\n\n    if (nextSlash != std::string::npos) {\n      chopped = std::string(chopped.c_str() + nextSlash, chopped.size() - nextSlash);\n\n      if (common::startsWith(chopped, \"/proc/\")) {\n        return false;\n      }\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  // Filter out versioned files..\n  //----------------------------------------------------------------------------\n  if (fullPath.find(\"/.sys.v#.\") != std::string::npos) {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Find files with layout = 1 replica\n//------------------------------------------------------------------------------\nint Inspector::oneReplicaLayout(bool showName, bool showPaths,\n                                bool filterInternal, std::ostream& out, std::ostream& err, bool json = false)\n{\n  FileScanner fileScanner(mQcl, showPaths | filterInternal);\n  common::IntervalStopwatch stopwatch(std::chrono::seconds(10));\n  Json::StreamWriterBuilder builder;\n  builder[\"indentation\"] = \"\";  // or whatever you like\n  std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());\n\n  while (fileScanner.valid()) {\n    eos::ns::FileMdProto proto;\n    FileScanner::Item item;\n    Json::Value jsonObj;\n\n    if (!fileScanner.getItem(proto, &item)) {\n      break;\n    }\n\n    int64_t actual = proto.locations().size();\n    int64_t expected = eos::common::LayoutId::GetStripeNumber(\n                         proto.layout_id()) + 1;\n    int64_t unlinked = proto.unlink_locations().size();\n    int64_t size = proto.size();\n\n    if (!proto.link_name().empty() ||\n        proto.xattrs().count(\"sys.eos.mdino\")) {\n      expected = 0;\n    }\n\n    if (expected == 1 && size != 0 &&\n        shouldPrint(filterInternal, fetchNameOrPath(proto, item))) {\n      if (json) {\n        jsonObj[\"fid\"] = (Json::Value::UInt64) proto.id();\n\n        if (showName || showPaths) {\n          jsonObj[\"path\"] = fetchNameOrPath(proto, item);\n        }\n\n        jsonObj[\"pid\"] = (Json::Value::UInt64) proto.cont_id();\n        jsonObj[\"size\"] = (Json::Value::UInt64) size;\n        jsonObj[\"actual_stripes\"] = (Json::Value::UInt64) actual;\n        jsonObj[\"expected_stripes\"] = (Json::Value::UInt64) expected;\n        jsonObj[\"unlinked_stripes\"] = (Json::Value::UInt64) unlinked;\n        jsonObj[\"locations\"] = serializeLocations(proto.locations());\n        jsonObj[\"unlinked_locations\"] = serializeLocations(proto.unlink_locations());\n        jsonObj[\"mtime\"] = std::stod(Printing::timespecToTimestamp(\n                                       Printing::parseTimespec(proto.mtime())));\n        jsonObj[\"ctime\"] = std::stod(Printing::timespecToTimestamp(\n                                       Printing::parseTimespec(proto.ctime())));\n        writer->write(jsonObj, &out);\n        out << std::endl;\n      } else {\n        out << \"id=\" << proto.id();\n\n        if (showName || showPaths) {\n          out << \" name=\" << fetchNameOrPath(proto, item);\n        }\n\n        out << \" pid=\" << proto.cont_id() <<\n            \" size=\" << size <<\n            \" actual_stripes=\" << actual <<\n            \" expected_stripes=\" << expected <<\n            \" unlinked_stripes=\" << unlinked <<\n            \" locations=\" << serializeLocations(proto.locations()) <<\n            \" unlinked_locations=\" << serializeLocations(proto.unlink_locations()) <<\n            \" mtime=\" << Printing::timespecToTimestamp(Printing::parseTimespec(\n                  proto.mtime())) <<\n            \" ctime=\" << Printing::timespecToTimestamp(Printing::parseTimespec(\n                  proto.ctime())) << std::endl;\n      }\n    }\n\n    fileScanner.next();\n\n    if (stopwatch.restartIfExpired()) {\n      err << \"Progress: Processed \" << fileScanner.getScannedSoFar() <<\n          \" files so far...\" << std::endl;\n    }\n  }\n\n  std::string errorString;\n\n  if (fileScanner.hasError(errorString)) {\n    err << errorString;\n    return 1;\n  }\n\n  return 0;\n}\n\n//----------------------------------------------------------------------------\n// Find files with non-nominal number of stripes (replicas)\n//----------------------------------------------------------------------------\n\nint Inspector::stripediff(bool json = false, bool minimal = false)\n{\n  FilePrintingOptions filePrintingOpts;\n  FileScanner fileScanner(mQcl, !minimal);\n\n  while (fileScanner.valid()) {\n    FileScanner::Item item;\n    eos::ns::FileMdProto proto;\n\n    if (minimal) {\n      if (!fileScanner.getItem(proto)) {\n        break;\n      }\n    } else {\n      if (!fileScanner.getItem(proto, &item)) {\n        break;\n      }\n    }\n\n    int64_t actual = proto.locations().size();\n    int64_t expected = eos::common::LayoutId::GetStripeNumber(\n                         proto.layout_id()) + 1;\n    int64_t unlinked = proto.unlink_locations().size();\n    int64_t size = proto.size();\n    Json::Value jsonObj;\n\n    if (!proto.link_name().empty()) {\n      expected = 0;\n    }\n\n    if (actual != expected && size != 0) {\n      if (!minimal) {\n        // Use output sink for complete report / json\n        std::map<std::string, std::string> extended;\n        extended[\"path\"] =  fetchNameOrPath(proto, item);\n        extended[\"actual_stripes\"] = std::to_string(actual);\n        extended[\"expected_stripes\"] = std::to_string(expected);\n        extended[\"unlinked_stripes\"] = std::to_string(unlinked);\n        mOutputSink.printWithAdditionalFields(proto, filePrintingOpts, extended);\n      } else {\n        if (json) {\n          jsonObj[\"fid\"] = (Json::Value::UInt64) proto.id();\n          jsonObj[\"pid\"] = (Json::Value::UInt64) proto.cont_id();\n          jsonObj[\"size\"] = (Json::Value::UInt64) size;\n          jsonObj[\"actual_stripes\"] = (Json::Value::UInt64) actual;\n          jsonObj[\"expected_stripes\"] = (Json::Value::UInt64) expected;\n          jsonObj[\"unlinked_stripes\"] = (Json::Value::UInt64) unlinked;\n          jsonObj[\"locations\"] = serializeLocations(proto.locations());\n          jsonObj[\"unlinked_locations\"] = serializeLocations(proto.unlink_locations());\n          jsonObj[\"mtime\"] = std::stod(Printing::timespecToTimestamp(\n                                         Printing::parseTimespec(proto.mtime())));\n          jsonObj[\"ctime\"] = std::stod(Printing::timespecToTimestamp(\n                                         Printing::parseTimespec(proto.ctime())));\n          mOutputSink.print(jsonObj);\n          // writer->write(jsonObj,&out);\n          // out << std::endl;\n        } else {\n          std::stringstream out;\n          out << \"fid=\" << proto.id() << \" container=\" << proto.cont_id() << \" size=\" <<\n              size <<\n              \" actual_stripes=\" << actual << \" expected_stripes=\" << expected <<\n              \" unlinked_stripes=\" << unlinked <<  \" locations=\" << serializeLocations(\n                proto.locations()) <<\n              \" unlinked_locations=\" << serializeLocations(proto.unlink_locations()) <<\n              \" mtime=\" << Printing::timespecToTimestamp(Printing::parseTimespec(\n                    proto.mtime())) <<\n              \" ctime=\" << Printing::timespecToTimestamp(Printing::parseTimespec(\n                    proto.ctime()));\n          mOutputSink.print(out.str());\n        }\n      }\n    } else if (!minimal) {\n      (void) fetchNameOrPath(proto,\n                             item); //Not to leak when path resolution was triggered\n    }\n\n    fileScanner.next();\n  }\n\n  std::string errorString;\n\n  if (fileScanner.hasError(errorString)) {\n    mOutputSink.err(errorString);\n    return 1;\n  }\n\n  return 0;\n}\n\n\nclass ConflictSet\n{\npublic:\n  std::set<uint64_t> files;\n  std::set<uint64_t> containers;\n\n  bool hasConflict() const\n  {\n    return (files.size() + containers.size()) > 1;\n  }\n\n  std::string serializeFiles() const\n  {\n    return serialize(files);\n  }\n\n  std::string serializeContainers() const\n  {\n    return serialize(containers);\n  }\n\n  void printMultipleLines(const std::string& name, uint64_t parentContainer,\n                          std::ostream& out) const\n  {\n    if (!hasConflict()) {\n      return;\n    }\n\n    for (auto it = files.begin(); it != files.end(); it++) {\n      out << \"name=\" << name << \" under-container=\" << parentContainer <<\n          \" conflicting-file=\" << *it << std::endl;\n    }\n\n    for (auto it = containers.begin(); it != containers.end(); it++) {\n      out << \"name=\" << name << \"  under-container=\" << parentContainer <<\n          \" conflicting-container=\" << *it << std::endl;\n    }\n  }\n\n  void printSingleLine(const std::string& name, uint64_t parentContainer,\n                       std::ostream& out) const\n  {\n    if (!hasConflict()) {\n      return;\n    }\n\n    out << \"name=\" << name << \" under-container=\" << parentContainer;\n\n    if (!files.empty()) {\n      out << \" conflicting-files=\" << serializeFiles();\n    }\n\n    if (!containers.empty()) {\n      out << \" conflicting-containers=\" << serializeContainers();\n    }\n\n    out << std::endl;\n  }\n\nprivate:\n  static std::string serialize(const std::set<uint64_t>& target)\n  {\n    std::ostringstream ss;\n\n    for (auto it = target.begin(); it != target.end(); it++) {\n      if (std::next(it) == target.end()) {\n        ss << *it;\n      } else {\n        ss << *it << \",\";\n      }\n    }\n\n    return ss.str();\n  }\n};\n\n//------------------------------------------------------------------------------\n// Find conflicts\n//------------------------------------------------------------------------------\nvoid findConflicts(bool onePerLine, std::ostream& out, uint64_t parentContainer,\n                   const std::map<std::string, ConflictSet>& nameMapping)\n{\n  for (auto it = nameMapping.begin(); it != nameMapping.end(); it++) {\n    const ConflictSet& conflictSet = it->second;\n\n    if (!onePerLine) {\n      conflictSet.printSingleLine(it->first, parentContainer, out);\n    } else {\n      conflictSet.printMultipleLines(it->first, parentContainer, out);\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check intra-container conflicts, such as a container having two entries\n// with the name name.\n//------------------------------------------------------------------------------\nint Inspector::checkNamingConflicts(bool onePerLine, std::ostream& out,\n                                    std::ostream& err)\n{\n  std::string errorString;\n  ContainerScanner containerScanner(mQcl);\n  FileScanner fileScanner(mQcl);\n  common::IntervalStopwatch stopwatch(std::chrono::seconds(10));\n  eos::ns::FileMdProto fileProto;\n  fileProto.set_cont_id(0);\n\n  while (containerScanner.valid()) {\n    eos::ns::ContainerMdProto proto;\n\n    if (!containerScanner.getItem(proto)) {\n      break;\n    }\n\n    if (proto.parent_id() == 0) {\n      containerScanner.next();\n      continue;\n    }\n\n    uint64_t currentParentId = proto.parent_id();\n    std::map<std::string, ConflictSet> nameMapping;\n\n    while (containerScanner.valid() && proto.parent_id() == currentParentId) {\n      nameMapping[proto.name()].containers.insert(proto.id());\n      containerScanner.next();\n      containerScanner.getItem(proto);\n    }\n\n    while (fileScanner.valid() && fileProto.cont_id() <= currentParentId) {\n      if (fileProto.cont_id() == currentParentId) {\n        nameMapping[fileProto.name()].files.insert(fileProto.id());\n      }\n\n      fileScanner.next();\n      fileScanner.getItem(fileProto);\n    }\n\n    findConflicts(onePerLine, out, currentParentId, nameMapping);\n    nameMapping.clear();\n  }\n\n  if (containerScanner.hasError(errorString) ||\n      fileScanner.hasError(errorString)) {\n    err << errorString;\n    return 1;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Check if file / container name is cursed\n//------------------------------------------------------------------------------\nstatic bool isCursedName(const std::string& name)\n{\n  if (name == \"\" || name == \".\" || name == \"..\" ||\n      name.find(\"/\") != std::string::npos) {\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Search for files / containers with cursed names\n//------------------------------------------------------------------------------\nint Inspector::checkCursedNames(std::ostream& out, std::ostream& err)\n{\n  ContainerScanner containerScanner(mQcl);\n\n  while (containerScanner.valid()) {\n    eos::ns::ContainerMdProto proto;\n\n    if (!containerScanner.getItem(proto)) {\n      break;\n    }\n\n    if (proto.id() != 1 && isCursedName(proto.name())) {\n      out << \"cid=\" << proto.id() << \" cursed-name=\" << escapeNonPrintable(\n            proto.name()) << std::endl;\n    }\n\n    containerScanner.next();\n  }\n\n  FileScanner fileScanner(mQcl);\n\n  while (fileScanner.valid()) {\n    eos::ns::FileMdProto proto;\n\n    if (!fileScanner.getItem(proto)) {\n      break;\n    }\n\n    if (isCursedName(proto.name())) {\n      out << \"fid=\" << proto.id() << \" cursed-name=\" << escapeNonPrintable(\n            proto.name()) << std::endl;\n    }\n\n    fileScanner.next();\n  }\n\n  std::string errorString;\n\n  if (containerScanner.hasError(errorString) ||\n      fileScanner.hasError(errorString)) {\n    err << errorString;\n    return 1;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Helper struct used in checkOrphans\n//------------------------------------------------------------------------------\nstruct PendingFile {\n  folly::Future<bool> validParent;\n  eos::ns::FileMdProto proto;\n\n  PendingFile(folly::Future<bool>&& f, const eos::ns::FileMdProto& p)\n    : validParent(std::move(f)), proto(p) {}\n};\n\nvoid consumePendingEntries(std::deque<PendingFile>& futs, bool unconditional,\n                           std::ostream& out)\n{\n  while (!futs.empty() && (unconditional || futs.front().validParent.isReady())) {\n    PendingFile& entry = futs.front();\n    entry.validParent.wait();\n\n    if (entry.validParent.hasException()) {\n      out << \"ERROR: Exception occurred when fetching container \" <<\n          entry.proto.cont_id() <<\n          \" as part of checking existence of parent of container \" << entry.proto.id() <<\n          std::endl;\n    } else if (std::move(entry.validParent).get() == false) {\n      out << \"file-id=\" << entry.proto.id() << \" invalid-parent-id=\" <<\n          entry.proto.cont_id() << \" size=\" << entry.proto.size() << \" locations=\" <<\n          serializeLocations(entry.proto.locations()) << \" unlinked-locations=\" <<\n          serializeLocations(entry.proto.unlink_locations()) << std::endl;\n    }\n\n    futs.pop_front();\n  }\n}\n\nstruct PendingContainer {\n  folly::Future<bool> validParent;\n  eos::ns::ContainerMdProto proto;\n\n  PendingContainer(folly::Future<bool>&& f, const eos::ns::ContainerMdProto& p)\n    : validParent(std::move(f)), proto(p) {}\n};\n\nvoid consumePendingEntries(std::deque<PendingContainer>& futs,\n                           bool unconditional, std::ostream& out)\n{\n  while (!futs.empty() && (unconditional || futs.front().validParent.isReady())) {\n    PendingContainer& entry = futs.front();\n    entry.validParent.wait();\n\n    if (entry.validParent.hasException()) {\n      out << \"ERROR: Exception occurred when fetching container \" <<\n          entry.proto.parent_id() <<\n          \" as part of checking existence of parent of container \" << entry.proto.id() <<\n          std::endl;\n    } else if (std::move(entry.validParent).get() == false) {\n      out << \"container-id=\" << entry.proto.id() << \" invalid-parent-id=\" <<\n          entry.proto.parent_id() << std::endl;\n    }\n\n    futs.pop_front();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Find orphan files and orphan directories\n//------------------------------------------------------------------------------\nint Inspector::checkOrphans(std::ostream& out, std::ostream& err)\n{\n  //----------------------------------------------------------------------------\n  // Look for orphan containers..\n  //----------------------------------------------------------------------------\n  std::string errorString;\n  ContainerScanner containerScanner(mQcl);\n  common::IntervalStopwatch stopwatch(std::chrono::seconds(10));\n  std::deque<PendingContainer> containers;\n\n  while (containerScanner.valid()) {\n    consumePendingEntries(containers, false, out);\n    eos::ns::ContainerMdProto proto;\n\n    if (!containerScanner.getItem(proto)) {\n      break;\n    }\n\n    containers.emplace_back(\n      MetadataFetcher::doesContainerMdExist(mQcl,\n                                            ContainerIdentifier(proto.parent_id())),\n      proto\n    );\n\n    if (stopwatch.restartIfExpired()) {\n      err << \"Progress: Processed \" << containerScanner.getScannedSoFar() <<\n          \" containers so far...\" << std::endl;\n    }\n\n    containerScanner.next();\n  }\n\n  consumePendingEntries(containers, true, out);\n\n  if (containerScanner.hasError(errorString)) {\n    err << errorString;\n    return 1;\n  }\n\n  err << \"All containers processed, checking files...\" << std::endl;\n  //----------------------------------------------------------------------------\n  // Look for orphan files..\n  //----------------------------------------------------------------------------\n  FileScanner fileScanner(mQcl);\n  std::deque<PendingFile> files;\n\n  while (fileScanner.valid()) {\n    consumePendingEntries(files, false, out);\n    eos::ns::FileMdProto proto;\n\n    if (!fileScanner.getItem(proto)) {\n      break;\n    }\n\n    files.emplace_back(\n      MetadataFetcher::doesContainerMdExist(mQcl,\n                                            ContainerIdentifier(proto.cont_id())),\n      proto\n    );\n\n    if (stopwatch.restartIfExpired()) {\n      err << \"Progress: Processed \" << fileScanner.getScannedSoFar() <<\n          \" files so far...\" << std::endl;\n    }\n\n    fileScanner.next();\n  }\n\n  consumePendingEntries(files, true, out);\n\n  if (fileScanner.hasError(errorString)) {\n    err << errorString;\n    return 1;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Helper struct for checkFsViewMissing\n//------------------------------------------------------------------------------\nstruct FsViewItemExists {\n  folly::Future<bool> valid;\n  eos::ns::FileMdProto proto;\n  int64_t location;\n  bool unlinked;\n\n  FsViewItemExists(folly::Future<bool>&& v, const eos::ns::FileMdProto& pr,\n                   int64_t loc, bool unl)\n    : valid(std::move(v)), proto(pr), location(loc), unlinked(unl) {}\n};\n\nvoid consumeFsViewQueue(std::deque<FsViewItemExists>& futs, bool unconditional,\n                        std::ostream& out)\n{\n  while (!futs.empty() && (unconditional || futs.front().valid.isReady())) {\n    FsViewItemExists& entry = futs.front();\n    entry.valid.wait();\n\n    if (entry.valid.hasException()) {\n      out << \"ERROR: Exception occurred when checking validity of location \" <<\n          entry.location << \" (unlinked=\" << entry.unlinked << \") of FileMD \" <<\n          entry.proto.id() << std::endl;\n    } else if (std::move(entry.valid).get() == false) {\n      if (entry.unlinked) {\n        out << \"id=\" << entry.proto.id() << \" parent-id=\" << entry.proto.cont_id() <<\n            \" size=\" << entry.proto.size() << \" locations=\" << serializeLocations(\n              entry.proto.locations()) << \" unlinked-locations=\" << serializeLocations(\n              entry.proto.unlink_locations()) << \" missing-unlinked-location=\" <<\n            entry.location << std::endl;\n      } else {\n        out << \"id=\" << entry.proto.id() << \" parent-id=\" << entry.proto.cont_id() <<\n            \" size=\" << entry.proto.size() << \" locations=\" << serializeLocations(\n              entry.proto.locations()) << \" unlinked-locations=\" << serializeLocations(\n              entry.proto.unlink_locations()) << \" missing-location=\" << entry.location <<\n            std::endl;\n      }\n    }\n\n    futs.pop_front();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Search for holes in FsView: Items which should be in FsView according to\n// FMD locations / unlinked locations, but are not there.\n//------------------------------------------------------------------------------\nint Inspector::checkFsViewMissing(std::ostream& out, std::ostream& err)\n{\n  //----------------------------------------------------------------------------\n  // Search through all FileMDs..\n  //----------------------------------------------------------------------------\n  std::deque<FsViewItemExists> queue;\n  FileScanner fileScanner(mQcl);\n  common::IntervalStopwatch stopwatch(std::chrono::seconds(10));\n\n  while (fileScanner.valid()) {\n    consumeFsViewQueue(queue, false, out);\n    eos::ns::FileMdProto proto;\n\n    if (!fileScanner.getItem(proto)) {\n      break;\n    }\n\n    for (auto it = proto.locations().cbegin(); it != proto.locations().cend();\n         it++) {\n      queue.emplace_back(MetadataFetcher::locationExistsInFsView(mQcl,\n                         FileIdentifier(proto.id()),\n                         *it, false), proto, *it, false);\n    }\n\n    for (auto it = proto.unlink_locations().cbegin();\n         it != proto.unlink_locations().cend(); it++) {\n      queue.emplace_back(MetadataFetcher::locationExistsInFsView(mQcl,\n                         FileIdentifier(proto.id()),\n                         *it, true), proto, *it, true);\n    }\n\n    if (stopwatch.restartIfExpired()) {\n      err << \"Progress: Processed \" << fileScanner.getScannedSoFar() <<\n          \" files so far\" << std::endl;\n    }\n\n    fileScanner.next();\n  }\n\n  consumeFsViewQueue(queue, true, out);\n  std::string errorString;\n\n  if (fileScanner.hasError(errorString)) {\n    err << errorString;\n    return 1;\n  }\n\n  return 0;\n}\n\nstruct FsViewExpectInLocations {\n  folly::Future<eos::ns::FileMdProto> proto;\n  int64_t futureFid;\n  int64_t expectedLocation;\n  bool unlinked;\n\n  FsViewExpectInLocations(folly::Future<eos::ns::FileMdProto>&& p, int64_t fid,\n                          int64_t expected,\n                          bool unl) : proto(std::move(p)), futureFid(fid), expectedLocation(expected),\n    unlinked(unl) {}\n};\n\nvoid consumeFsViewQueue(std::deque<FsViewExpectInLocations>& futs,\n                        bool unconditional, std::ostream& out)\n{\n  while (!futs.empty() && (unconditional || futs.front().proto.isReady())) {\n    FsViewExpectInLocations& entry = futs.front();\n    entry.proto.wait();\n\n    if (entry.proto.hasException()) {\n      out << \"ERROR: Exception occurred when fetching file with id \" <<\n          entry.futureFid << std::endl;\n    } else if (!entry.unlinked) {\n      eos::ns::FileMdProto proto = std::move(entry.proto).get();\n      bool found = false;\n\n      for (auto it = proto.locations().cbegin(); it != proto.locations().cend();\n           it++) {\n        if (*it == entry.expectedLocation) {\n          found = true;\n          break;\n        }\n      }\n\n      if (!found) {\n        out << \"id=\" << proto.id() << \" parent-id=\" << proto.cont_id() << \" size=\" <<\n            proto.size() << \" locations=\" << serializeLocations(proto.locations()) <<\n            \" unlinked-locations=\" << serializeLocations(proto.unlink_locations()) <<\n            \" extra-location=\" << entry.expectedLocation << std::endl;\n      }\n    } else {\n      eos::ns::FileMdProto proto = std::move(entry.proto).get();\n      bool found = false;\n\n      for (auto it = proto.unlink_locations().cbegin();\n           it != proto.unlink_locations().cend(); it++) {\n        if (*it == entry.expectedLocation) {\n          found = true;\n          break;\n        }\n      }\n\n      if (!found) {\n        out << \"id=\" << proto.id() << \" parent-id=\" << proto.cont_id() << \" size=\" <<\n            proto.size() << \" locations=\" << serializeLocations(proto.locations()) <<\n            \" unlinked-locations=\" << serializeLocations(proto.unlink_locations()) <<\n            \" extra-unlink-location=\" << entry.expectedLocation << std::endl;\n      }\n    }\n\n    futs.pop_front();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Search for elements which are present in FsView, but not FMD locations\n//------------------------------------------------------------------------------\nint Inspector::checkFsViewExtra(std::ostream& out, std::ostream& err)\n{\n  //----------------------------------------------------------------------------\n  // Scan through the entire filesystem view..\n  //----------------------------------------------------------------------------\n  std::deque<FsViewExpectInLocations> queue;\n  FileSystemIterator fsIter(mQcl);\n\n  while (fsIter.valid()) {\n    StreamingFileListIterator fsScanner(mQcl, fsIter.getRedisKey());\n\n    while (fsScanner.valid()) {\n      consumeFsViewQueue(queue, false, out);\n      queue.emplace_back(MetadataFetcher::getFileFromId(mQcl,\n                         FileIdentifier(fsScanner.getElement())),\n                         fsScanner.getElement(),\n                         fsIter.getFileSystemID(),\n                         fsIter.isUnlinked()\n                        );\n      fsScanner.next();\n    }\n\n    fsIter.next();\n  }\n\n  consumeFsViewQueue(queue, true, out);\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Search for shadow directories\n//------------------------------------------------------------------------------\nint Inspector::checkShadowDirectories(std::ostream& out, std::ostream& err)\n{\n  ContainerScanner containerScanner(mQcl);\n  common::IntervalStopwatch stopwatch(std::chrono::seconds(10));\n  eos::ns::ContainerMdProto prevContainer;\n\n  while (containerScanner.valid()) {\n    eos::ns::ContainerMdProto proto;\n\n    if (!containerScanner.getItem(proto)) {\n      break;\n    }\n\n    if (proto.parent_id() != 0 && proto.name() == prevContainer.name() &&\n        proto.parent_id() == prevContainer.parent_id()) {\n      out << \"id=\" << proto.id()\n          << \" name=\" << proto.name()\n          << \" parent=\" << proto.parent_id()\n          << \" mtime=\" << Printing::timespecToTimestamp(Printing::parseTimespec(\n                proto.mtime()))\n          << \" ctime=\" << Printing::timespecToTimestamp(Printing::parseTimespec(\n                proto.ctime()))\n          << \" is-quotanode=\" << (proto.flags() & QUOTA_NODE_FLAG)\n          << \" conflicts-with=\" << prevContainer.id()\n          << std::endl;\n    }\n\n    prevContainer = std::move(proto);\n    containerScanner.next();\n  }\n\n  std::string errorString;\n\n  if (containerScanner.hasError(errorString)) {\n    err << errorString;\n    return 1;\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Helper class to run next on destruction\n//------------------------------------------------------------------------------\nclass NextGuard\n{\npublic:\n  NextGuard(FileScanner& sc) : scanner(sc) {}\n  ~NextGuard()\n  {\n    scanner.next();\n  }\n\nprivate:\n  FileScanner& scanner;\n};\n\n//------------------------------------------------------------------------------\n// Hardlink information\n//------------------------------------------------------------------------------\nstruct HardlinkInfo {\n  HardlinkInfo() {}\n  HardlinkInfo(uint64_t t, const std::string& n) : target(t), name(n) {}\n\n  uint64_t target;\n  std::string name;\n};\n\nstruct InodeUseCount {\n  InodeUseCount() : count(0) {}\n  InodeUseCount(int64_t c, const std::string& n) : count(c), name(n) {}\n\n  int64_t count;\n  std::string name;\n};\n\n//------------------------------------------------------------------------------\n// Cross-check inode use counts against hardlink mappings\n//------------------------------------------------------------------------------\nvoid crossCheckHardlinkMaps(std::map<uint64_t, InodeUseCount>& inodeUseCount,\n                            std::map<uint64_t, HardlinkInfo>& hardlinkMapping, uint64_t parent,\n                            std::ostream& out)\n{\n  std::set<uint64_t> zeroGroup;\n\n  for (auto it = inodeUseCount.begin(); it != inodeUseCount.end(); it++) {\n    if (it->second.count == 0) {\n      zeroGroup.insert(it->first);\n    }\n  }\n\n  for (auto it = hardlinkMapping.begin(); it != hardlinkMapping.end(); it++) {\n    auto useCount = inodeUseCount.find(it->second.target);\n\n    if (useCount == inodeUseCount.end()) {\n      out << \"id=\" << it->first << \" name=\" << it->second.name << \" parent=\" << parent\n          << \" invalid-target=\" << it->second.target << std::endl;\n    } else {\n      inodeUseCount[it->second.target].count--;\n    }\n  }\n\n  for (auto it = inodeUseCount.begin(); it != inodeUseCount.end(); it++) {\n    if (it->second.count != 0) {\n      out << \"id=\" << it->first << \" name=\" << it->second.name << \" parent=\" << parent\n          << \" reference-count-diff=\" << it->second.count << std::endl;\n      zeroGroup.erase(it->first);\n    }\n  }\n\n  for (auto it = zeroGroup.begin(); it != zeroGroup.end(); it++) {\n    out << \"id=\" << *it << \" name=\" << inodeUseCount[*it].name  << \" parent=\" <<\n        parent << \" true-zero-count\" << std::endl;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Check for corrupted ...eos.ino... hardlink-simulation files\n//------------------------------------------------------------------------------\nint Inspector::checkSimulatedHardlinks(std::ostream& out, std::ostream& err)\n{\n  FileScanner fileScanner(mQcl);\n  common::InodeTranslator translator;\n  std::map<uint64_t, InodeUseCount> inodeUseCount;\n  std::map<uint64_t, HardlinkInfo> hardlinkMapping;\n  uint64_t currentContainer = 0;\n\n  while (fileScanner.valid()) {\n    eos::ns::FileMdProto proto;\n\n    if (!fileScanner.getItem(proto)) {\n      break;\n    }\n\n    NextGuard nextGuard(fileScanner);\n\n    if (proto.cont_id() == 0) {\n      continue;\n    }\n\n    if (proto.cont_id() != currentContainer) {\n      crossCheckHardlinkMaps(inodeUseCount, hardlinkMapping, currentContainer, out);\n      inodeUseCount.clear();\n      hardlinkMapping.clear();\n    }\n\n    currentContainer = proto.cont_id();\n    auto it = proto.xattrs().find(\"sys.eos.mdino\");\n\n    if (it != proto.xattrs().end()) {\n      uint64_t inode = 0;\n\n      if (!common::ParseUInt64(it->second.c_str(), inode)) {\n        err << \"Could not parse sys.eos.mdino: \" << it->second.c_str() << std::endl;\n        continue;\n      }\n\n      uint64_t target = translator.InodeToFid(inode);\n      hardlinkMapping[proto.id()] = HardlinkInfo(target, proto.name());\n      continue;\n    }\n\n    it = proto.xattrs().find(\"sys.eos.nlink\");\n\n    if (it != proto.xattrs().end()) {\n      size_t count = atoi(it->second.c_str());\n      inodeUseCount[proto.id()] = InodeUseCount(count, proto.name());\n    }\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Print out _everything_ known about the given directory.\n//------------------------------------------------------------------------------\nint Inspector::printContainerMD(uint64_t cid, bool withParents,\n                                std::ostream& out, std::ostream& err)\n{\n  eos::ns::ContainerMdProto val;\n\n  try {\n    val = MetadataFetcher::getContainerFromId(mQcl, ContainerIdentifier(cid)).get();\n  } catch (const MDException& e) {\n    err << \"Error while fetching metadata for ContainerMD #\" << cid << \": \" <<\n        e.what()\n        << std::endl;\n  }\n\n  Printing::printMultiline(val, out);\n\n  try {\n    std::string fullPath = MetadataFetcher::resolveFullPath(mQcl,\n                           ContainerIdentifier(val.id())).get();\n    out << \"Full path: \" << fullPath << std::endl;\n  } catch (const MDException& e) {\n    err << \"Full path: Could not reconstruct\" << std::endl;\n  }\n\n  IContainerMD::FileMap fileMap;\n  IContainerMD::FileMap containerMap;\n\n  try {\n    fileMap = MetadataFetcher::getFileMap(mQcl, ContainerIdentifier(cid)).get();\n  } catch (const MDException& e) {\n    err << \"Error while fetching file map for ContainerMD #\" << cid << \": \" <<\n        e.what()\n        << std::endl;\n  }\n\n  try {\n    containerMap = MetadataFetcher::getContainerMap(mQcl,\n                   ContainerIdentifier(cid)).get();\n  } catch (const MDException& e) {\n    err << \"Error while fetching container map for ContainerMD #\" << cid << \": \" <<\n        e.what()\n        << std::endl;\n  }\n\n  out << \"------------------------------------------------\" << std::endl;\n  out << \"FileMap:\" << std::endl;\n\n  for (auto it = fileMap.begin(); it != fileMap.end(); ++it) {\n    out << it->first << \": \" << it->second << std::endl;\n  }\n\n  out << \"------------------------------------------------\" << std::endl;\n  out << \"ContainerMap:\" << std::endl;\n\n  for (auto it = containerMap.begin(); it != containerMap.end(); ++it) {\n    out << it->first << \": \" << it->second << std::endl;\n  }\n\n  if (withParents && val.parent_id() != 0 && val.id() != val.parent_id()) {\n    out << std::endl << std::endl << std::endl << std::endl << std::endl;\n    return printContainerMD(val.parent_id(), withParents, out, err);\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Print out _everything_ known about the given file.\n//------------------------------------------------------------------------------\nint Inspector::printFileMD(uint64_t fid, bool withParents, std::ostream& out,\n                           std::ostream& err)\n{\n  eos::ns::FileMdProto val;\n\n  try {\n    val = MetadataFetcher::getFileFromId(mQcl, FileIdentifier(fid)).get();\n  } catch (const MDException& e) {\n    err << \"Error while fetching metadata for FileMD #\" << fid << \": \" << e.what()\n        << std::endl;\n    return 1;\n  }\n\n  Printing::printMultiline(val, out);\n\n  try {\n    std::string fullPath = MetadataFetcher::resolveFullPath(mQcl,\n                           ContainerIdentifier(val.cont_id())).get();\n    out << \"Full path: \" << fullPath << val.name() << std::endl;\n  } catch (const MDException& e) {\n    err << \"Full path: Could not reconstruct\" << std::endl;\n  }\n\n  if (withParents && val.cont_id() != 0) {\n    out << std::endl << std::endl << std::endl << std::endl << std::endl;\n    return printContainerMD(val.cont_id(), withParents, out, err);\n  }\n\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Serialize RedisRequest\n//------------------------------------------------------------------------------\nstatic std::string serializeRequest(const RedisRequest& req)\n{\n  std::ostringstream ss;\n\n  for (size_t i = 0; i < req.size(); i++) {\n    ss << \"\\\"\" << escapeNonPrintable(req[i]) << \"\\\"\" << \" \";\n  }\n\n  return ss.str();\n}\n\n//------------------------------------------------------------------------------\n//! Check if given path is a good choice as a destination for repaired\n//! files / containers\n//------------------------------------------------------------------------------\nbool Inspector::isDestinationPathSane(const std::string& path,\n                                      ContainerIdentifier& cid, std::ostream& out)\n{\n  try {\n    FileOrContainerIdentifier id = MetadataFetcher::resolvePathToID(mQcl,\n                                   path).get();\n\n    if (id.isFile()) {\n      out << \"Destination path '\" << path << \"' is a file, not a directory.\" <<\n          std::endl;\n      return false;\n    }\n\n    cid = id.toContainerIdentifier();\n  } catch (const MDException& e) {\n    out << \"Destination path '\" << path << \"' does not exist.\" << std::endl;\n    return false;\n  }\n\n  if (cid == ContainerIdentifier(1) || cid == ContainerIdentifier(2) ||\n      cid == ContainerIdentifier(3)) {\n    out << \"Destination path '\" << path <<\n        \"' does not look like a good place, too top-level.\" << std::endl;\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Attempt to fix detached parent\n//------------------------------------------------------------------------------\nint Inspector::fixDetachedParentContainer(bool dryRun, uint64_t cid,\n    const std::string& destinationPath, std::ostream& out, std::ostream& err)\n{\n  //----------------------------------------------------------------------------\n  // Ensure given container exists\n  //----------------------------------------------------------------------------\n  if (!MetadataFetcher::doesContainerMdExist(mQcl,\n      ContainerIdentifier(cid)).get()) {\n    out << \"Container #\" << cid << \" does not exist.\" << std::endl;\n    return 1;\n  }\n\n  //----------------------------------------------------------------------------\n  // Ensure given destination path is sane\n  //----------------------------------------------------------------------------\n  ContainerIdentifier destination;\n\n  if (!isDestinationPathSane(destinationPath, destination, out)) {\n    return 1;\n  }\n\n  //----------------------------------------------------------------------------\n  // Try and figure out what the problem with the given container is\n  //----------------------------------------------------------------------------\n  out << \"Finding all parents of Container #\" << cid << \"...\" << std::endl;\n  uint64_t nextToCheck = cid;\n  eos::ns::ContainerMdProto val;\n\n  while (nextToCheck != 0 && nextToCheck != 1) {\n    try {\n      val = MetadataFetcher::getContainerFromId(mQcl,\n            ContainerIdentifier(nextToCheck)).get();\n      out << val.name() << \": #\" << val.id() << \" with parent #\" << val.parent_id() <<\n          std::endl;\n      nextToCheck = val.parent_id();\n    } catch (const MDException& e) {\n      break;\n    }\n  }\n\n  if (nextToCheck == 1 || nextToCheck == 0) {\n    err << \"Unable to continue - given container (\" << cid <<\n        \") looks fine? No changes have been made.\" << std::endl;\n    return 1;\n  }\n\n  //----------------------------------------------------------------------------\n  // One of its parents is detached from the main tree, rename\n  //----------------------------------------------------------------------------\n  out << std::endl << std::endl << \"Found detached container #\" << val.id() <<\n      \" since its parent #\" << val.parent_id() << \" does not exist.\" << std::endl;\n  // Paranoid check\n  eos_assert(!MetadataFetcher::doesContainerMdExist(mQcl,\n             ContainerIdentifier(val.parent_id())).get());\n  // Go\n  std::string newName = SSTR(\"recovered-dir___id=\" << val.id() << \"___name=\" <<\n                             val.name() << \"___detached-parent=\" << val.parent_id());\n  return renameCid(dryRun, val.id(), destination.getUnderlyingUInt64(), newName,\n                   out, err);\n}\n\n//------------------------------------------------------------------------------\n// Attempt to fix naming conflict\n//------------------------------------------------------------------------------\nint Inspector::fixShadowFile(bool dryRun, uint64_t fid,\n                             const std::string& destinationPath, std::ostream& out, std::ostream& err)\n{\n  //----------------------------------------------------------------------------\n  // Ensure given file exists..\n  //----------------------------------------------------------------------------\n  eos::ns::FileMdProto val;\n\n  try {\n    val = MetadataFetcher::getFileFromId(mQcl, FileIdentifier(fid)).get();\n  } catch (const MDException& e) {\n    err << \"Error while fetching metadata for FileMD #\" << fid << \": \" << e.what()\n        << std::endl;\n    return 1;\n  }\n\n  //----------------------------------------------------------------------------\n  // Ensure given destination path is sane\n  //----------------------------------------------------------------------------\n  ContainerIdentifier destination;\n\n  if (!isDestinationPathSane(destinationPath, destination, out)) {\n    return 1;\n  }\n\n  //----------------------------------------------------------------------------\n  // Ensure the given fid is indeed shadowed\n  //----------------------------------------------------------------------------\n  bool cidExists = MetadataFetcher::doesContainerMdExist(mQcl,\n                   ContainerIdentifier(val.cont_id())).get();\n  IContainerMD::FileMap cidFilemap = MetadataFetcher::getFileMap(mQcl,\n                                     ContainerIdentifier(val.cont_id())).get();\n  bool filemapEntryExists = cidFilemap.find(val.name()) != cidFilemap.end();\n  bool filemapEntryValid = (cidFilemap[val.name()] == val.id());\n  IContainerMD::ContainerMap cidContainermap = MetadataFetcher::getContainerMap(\n        mQcl, ContainerIdentifier(val.cont_id())).get();\n  bool containerMapConflict = cidContainermap.find(val.name()) !=\n                              cidContainermap.end();\n  out << \"Parent exists? \" << toYesOrNo(cidExists) << std::endl;\n  out << \"Filemap entry exists? \" << toYesOrNo(filemapEntryExists) << std::endl;\n  out << \"Filemap entry valid? \" << toYesOrNo(filemapEntryValid) << std::endl;\n  out << \"Containermap conflict? \" << toYesOrNo(containerMapConflict) <<\n      std::endl;\n\n  if (!cidExists) {\n    err << \"Parent container does not exist, use fix-detached-parent.\" << std::endl;\n    return 1;\n  }\n\n  if (filemapEntryExists && filemapEntryValid && !containerMapConflict) {\n    err << \"File looks fine? No naming conflict detected, nothing to be done.\" <<\n        std::endl;\n    return 1;\n  }\n\n  if (!filemapEntryExists) {\n    out << \"Detected problem: Filemap entry does not exist.\" << std::endl;\n  } else if (!filemapEntryValid) {\n    out << \"Detected problem: Filemap entry is not valid, and instead points to fid \"\n        << cidFilemap[val.name()] << std::endl;\n  }\n\n  if (containerMapConflict) {\n    out << \"Detected problem: Conflict with containermap entry, points to cid \" <<\n        cidContainermap[val.name()] << std::endl;\n  }\n\n  // Go\n  std::string newName = SSTR(\"recovered-file___id=\" << val.id() << \"___name=\" <<\n                             val.name() << \"___naming-conflict-in-parent=\" << val.cont_id());\n  return renameFid(dryRun, val.id(), destination.getUnderlyingUInt64(), newName,\n                   out, err);\n}\n\n//------------------------------------------------------------------------------\n// Attempt to fix detached parent\n//------------------------------------------------------------------------------\nint Inspector::fixDetachedParentFile(bool dryRun, uint64_t fid,\n                                     const std::string& destinationPath, std::ostream& out, std::ostream& err)\n{\n  //----------------------------------------------------------------------------\n  // Ensure given file exists..\n  //----------------------------------------------------------------------------\n  eos::ns::FileMdProto val;\n\n  try {\n    val = MetadataFetcher::getFileFromId(mQcl, FileIdentifier(fid)).get();\n  } catch (const MDException& e) {\n    err << \"Error while fetching metadata for FileMD #\" << fid << \": \" << e.what()\n        << std::endl;\n    return 1;\n  }\n\n  //----------------------------------------------------------------------------\n  // Ensure given destination path is sane\n  //----------------------------------------------------------------------------\n  ContainerIdentifier destination;\n\n  if (!isDestinationPathSane(destinationPath, destination, out)) {\n    return 1;\n  }\n\n  //----------------------------------------------------------------------------\n  // If immediate parent is not missing,\n  // switch over to fixDetachedParentContainer\n  //----------------------------------------------------------------------------\n  if (MetadataFetcher::doesContainerMdExist(mQcl,\n      ContainerIdentifier(val.cont_id())).get()) {\n    out << \"File #\" << val.id() << \" not detached, but one of its parents might be.\"\n        << std::endl;\n    out << \"Continuing search onto its parent, container #\" << val.cont_id() <<\n        \"...\" << std::endl;\n    return fixDetachedParentContainer(dryRun, val.cont_id(), destinationPath, out,\n                                      err);\n  }\n\n  //----------------------------------------------------------------------------\n  // Immediate parent is missing, rename fid itself.\n  //----------------------------------------------------------------------------\n  out << \"Found detached file #\" << val.id() << \", its direct parent #\" <<\n      val.cont_id() << \" is missing.\" << std::endl;\n  // Paranoid check\n  eos_assert(!MetadataFetcher::doesContainerMdExist(mQcl,\n             ContainerIdentifier(val.cont_id())).get());\n  // Go\n  std::string newName = SSTR(\"recovered-file___id=\" << val.id() << \"___name=\" <<\n                             val.name() << \"___detached-parent=\" << val.cont_id());\n  return renameFid(dryRun, val.id(), destination.getUnderlyingUInt64(), newName,\n                   out, err);\n}\n\n//------------------------------------------------------------------------------\n// Drop file currently stuck in deathrow. Please note that any pending\n// replicas, if they exist, are not deleted.\n//------------------------------------------------------------------------------\nint Inspector::dropFromDeathrow(bool dryRun, uint64_t fid, std::ostream& out,\n                                std::ostream& err)\n{\n  eos::ns::FileMdProto val;\n\n  try {\n    val = MetadataFetcher::getFileFromId(mQcl, FileIdentifier(fid)).get();\n  } catch (const MDException& e) {\n    err << \"Error while fetching metadata for FileMD #\" << fid << \": \" << e.what()\n        << std::endl;\n    return 1;\n  }\n\n  Printing::printMultiline(val, out);\n\n  if (val.cont_id() != 0) {\n    err << \"Parent is not 0 - the given file is not on deathrow, refusing to delete.\"\n        << std::endl;\n    return 1;\n  }\n\n  std::vector<RedisRequest> requests;\n  requests.emplace_back(RequestBuilder::deleteFileProto(FileIdentifier(fid)));\n  CacheNotifications notifications;\n  notifications.fids.emplace_back(fid);\n  executeRequestBatch(requests, notifications, dryRun, out, err);\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Drop empty container\n//------------------------------------------------------------------------------\nint Inspector::dropEmptyCid(bool dryRun, uint64_t cid)\n{\n  eos::ns::ContainerMdProto val;\n\n  try {\n    val = MetadataFetcher::getContainerFromId(mQcl, ContainerIdentifier(cid)).get();\n  } catch (const MDException& e) {\n    mOutputSink.err(SSTR(\"Error while fetching metadata for ContainerMD #\" << cid <<\n                         \": \" << e.what()));\n    return 1;\n  }\n\n  mOutputSink.print(val, {});\n  IContainerMD::ContainerMap parentContainermap =\n    MetadataFetcher::getContainerMap(mQcl,\n                                     ContainerIdentifier(val.parent_id())).get();\n  bool containermapEntryExists = parentContainermap.find(val.name()) !=\n                                 parentContainermap.end();\n  bool containermapEntryValid = (parentContainermap[val.name()] == val.id());\n  mOutputSink.print(SSTR(\"ContainerMap entry exists? \" << toYesOrNo(\n                           containermapEntryExists)));\n  mOutputSink.print(SSTR(\"ContainerMap entry valid? \" << toYesOrNo(\n                           containermapEntryValid)));\n  IContainerMD::ContainerMap targetContainerMap =\n    MetadataFetcher::getContainerMap(mQcl, ContainerIdentifier(val.id())).get();\n  IContainerMD::ContainerMap targetFileMap = MetadataFetcher::getFileMap(mQcl,\n      ContainerIdentifier(val.id())).get();\n  mOutputSink.print(SSTR(\"Target has containers? \" << toYesOrNo(\n                           !targetContainerMap.empty())));\n  mOutputSink.print(SSTR(\"Target has files? \" << toYesOrNo(\n                           !targetFileMap.empty())));\n\n  if (!targetContainerMap.empty() || !targetFileMap.empty()) {\n    mOutputSink.err(SSTR(\"Target contains \" << targetContainerMap.size() <<\n                         \" containers, and \" << targetFileMap.size() <<\n                         \" files. Not empty, aborting operation.\"));\n    return 1;\n  }\n\n  std::vector<RedisRequest> requests;\n  CacheNotifications notifications;\n  requests.emplace_back(RequestBuilder::deleteContainerProto(ContainerIdentifier(\n                          cid)));\n\n  if (containermapEntryValid) {\n    RedisRequest req { \"HDEL\", SSTR(val.parent_id() << constants::sMapDirsSuffix), val.name() };\n    requests.emplace_back(req);\n    notifications.cids.emplace_back(val.parent_id());\n  }\n\n  notifications.cids.emplace_back(val.id());\n  executeRequestBatch(requests, notifications, dryRun, std::cout, std::cerr);\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Change the given fid - USE WITH CAUTION\n//------------------------------------------------------------------------------\nint Inspector::changeFid(bool dryRun, uint64_t fid, uint64_t newParent,\n                         const std::string& newChecksum, int64_t newSize,\n                         uint64_t newLayoutId, std::ostream& out,\n                         std::ostream& err)\n{\n  eos::ns::FileMdProto val;\n\n  try {\n    val = MetadataFetcher::getFileFromId(mQcl, FileIdentifier(fid)).get();\n  } catch (const MDException& e) {\n    err << \"Error while fetching metadata for FileMD #\" << fid << \": \" << e.what()\n        << std::endl;\n    return 1;\n  }\n\n  Printing::printMultiline(val, out);\n  bool ok = false;\n  out << \"----- CHANGING THE FOLLOWING ATTRIBUTES:\" << std::endl;\n\n  if (newParent != 0) {\n    ok = true;\n    err << \"    Container ID: \" << val.cont_id() << \" --> \" << newParent <<\n        std::endl;\n    val.set_cont_id(newParent);\n  }\n\n  if (!newChecksum.empty()) {\n    std::string existingChecksum;\n    eos::appendChecksumOnStringProtobuf(val, existingChecksum);\n    std::string newChecksumBytes;\n\n    if (!eos::hexArrayToByteArray(newChecksum.c_str(), newChecksum.size(),\n                                  newChecksumBytes)) {\n      err << \"Error: Could not decode checksum, needs to be in hex: \" << newChecksum\n          << std::endl;\n      return 1;\n    }\n\n    ok = true;\n    err << \"    Checksum: \" << existingChecksum << \" --> \" << newChecksum <<\n        std::endl;\n    val.set_checksum(newChecksumBytes.c_str(), newChecksumBytes.size());\n  }\n\n  if (newSize >= 0) {\n    ok = true;\n    err << \"    Size: \" << val.size() << \" --> \" << newSize << std::endl;\n    val.set_size(newSize);\n  }\n\n  if (newLayoutId) {\n    ok = true;\n    err << \"    Layout id: \" << val.layout_id() << \" --> \" << newLayoutId <<\n        std::endl;\n    val.set_layout_id(newLayoutId);\n  }\n\n  if (!ok) {\n    err << \"Error: No attributes specified to update.\" << std::endl;\n    return 1;\n  }\n\n  QuarkFileMD fileMD;\n  fileMD.initialize(std::move(val));\n  std::vector<RedisRequest> requests;\n  requests.emplace_back(RequestBuilder::writeFileProto(&fileMD));\n  executeRequestBatch(requests, {}, dryRun, out, err);\n  CacheNotifications notifications;\n  notifications.fids.emplace_back(fid);\n  executeRequestBatch(requests, notifications, dryRun, out, err);\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Rename the given cid fully, taking care of the container maps as well.\n//------------------------------------------------------------------------------\nint Inspector::renameCid(bool dryRun, uint64_t cid, uint64_t newParent,\n                         const std::string& newName, std::ostream& out, std::ostream& err)\n{\n  eos::ns::ContainerMdProto val;\n  bool protoExists = false;\n\n  try {\n    val = MetadataFetcher::getContainerFromId(mQcl, ContainerIdentifier(cid)).get();\n    protoExists = true;\n  } catch (const MDException& e) {\n    val.set_id(cid);\n    err << \"Error while fetching metadata for ContainerMD #\" << cid << \": \" <<\n        e.what();\n  }\n\n  out << \"------------------------------------------------------ Container overview\"\n      << std::endl;\n  bool parentExists = false;\n  IContainerMD::ContainerMap parentContainermap;\n  bool containermapEntryExists = false;\n  bool containermapEntryValid = false;\n  std::string oldName = \"\";\n  uint64_t oldContainer = 0;\n\n  if (protoExists) {\n    Printing::printMultiline(val, out);\n    parentExists = MetadataFetcher::doesContainerMdExist(mQcl,\n                   ContainerIdentifier(val.parent_id())).get();\n    parentContainermap = MetadataFetcher::getContainerMap(mQcl,\n                         ContainerIdentifier(val.parent_id())).get();\n    containermapEntryExists = parentContainermap.find(val.name()) !=\n                              parentContainermap.end();\n    containermapEntryValid = (parentContainermap[val.name()] == val.id());\n    oldName = val.name();\n    oldContainer = val.parent_id();\n    out << \"------------------------------------------------------ Sanity check\" <<\n        std::endl;\n    out << \"Parent (\" << (val.parent_id()) << \") exists? \" << toYesOrNo(\n          parentExists) << std::endl;\n    out << \"Containermap entry exists? \" << toYesOrNo(containermapEntryExists) <<\n        std::endl;\n\n    if (containermapEntryExists) {\n      out << \"Containermap entry (\" << val.name() << \" -> \" <<\n          parentContainermap[val.name()] << \") valid? \" << toYesOrNo(\n            containermapEntryValid) << std::endl;\n    }\n  } else {\n    out << \"Protobuf for cid=\" << cid << \" does not exist!\" << std::endl;\n  }\n\n  if (!protoExists && newName.empty()) {\n    out << \"Name needs to be specified if original container did not exist! Aborting operation.\"\n        << std::endl;\n    return 1;\n  }\n\n  val.set_parent_id(newParent);\n\n  if (!newName.empty()) {\n    val.set_name(newName);\n  }\n\n  std::vector<RedisRequest> requests;\n  CacheNotifications notifications;\n  QuarkContainerMD containerMD;\n  containerMD.initialize(std::move(val), IContainerMD::FileMap(),\n                         IContainerMD::ContainerMap());\n  requests.emplace_back(RequestBuilder::writeContainerProto(&containerMD));\n\n  if (containermapEntryExists && containermapEntryValid) {\n    RedisRequest req = {\"HDEL\", SSTR(oldContainer << constants::sMapDirsSuffix), oldName};\n    notifications.cids.emplace_back(oldContainer);\n    requests.emplace_back(req);\n  }\n\n  RedisRequest req = {\"HSET\", SSTR(newParent << constants::sMapDirsSuffix), containerMD.getName(), SSTR(containerMD.getId()) };\n  notifications.cids.emplace_back(newParent);\n  notifications.cids.emplace_back(containerMD.getId());\n  requests.emplace_back(req);\n  executeRequestBatch(requests, notifications, dryRun, out, err);\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Rename the given fid fully, taking care of the container maps as well\n//------------------------------------------------------------------------------\nint Inspector::renameFid(bool dryRun, uint64_t fid, uint64_t newParent,\n                         const std::string& newName, std::ostream& out, std::ostream& err)\n{\n  eos::ns::FileMdProto val;\n\n  try {\n    val = MetadataFetcher::getFileFromId(mQcl, FileIdentifier(fid)).get();\n  } catch (const MDException& e) {\n    err << \"Error while fetching metadata for FileMD #\" << fid << \": \" << e.what()\n        << std::endl;\n    return 1;\n  }\n\n  out << \"------------------------------------------------------ FMD overview\" <<\n      std::endl;\n  Printing::printMultiline(val, out);\n  bool cidExists = MetadataFetcher::doesContainerMdExist(mQcl,\n                   ContainerIdentifier(val.cont_id())).get();\n  IContainerMD::FileMap cidFilemap = MetadataFetcher::getFileMap(mQcl,\n                                     ContainerIdentifier(val.cont_id())).get();\n  bool filemapEntryExists = cidFilemap.find(val.name()) != cidFilemap.end();\n  bool filemapEntryValid = (cidFilemap[val.name()] == val.id());\n  std::string oldName = val.name();\n  uint64_t oldContainer = val.cont_id();\n  out << \"------------------------------------------------------ Sanity check\" <<\n      std::endl;\n  out << \"Old container (\" << (val.cont_id()) << \") exists? \" << toYesOrNo(\n        cidExists) << std::endl;\n  out << \"Filemap entry exists? \" << toYesOrNo(filemapEntryExists) << std::endl;\n\n  if (filemapEntryExists) {\n    out << \"Filemap entry (\" << val.name() << \" -> \" << cidFilemap[val.name()] <<\n        \") valid? \" << toYesOrNo(filemapEntryValid) << std::endl;\n  }\n\n  out << \"------------------------------------------------------ FMD changes\" <<\n      std::endl;\n  out << \"    Parent ID: \" << val.cont_id() << \" --> \" << newParent << std::endl;\n  val.set_cont_id(newParent);\n\n  if (!newName.empty()) {\n    out << \"    Name: \" << val.name() << \" --> \" << newName << std::endl;\n    val.set_name(newName);\n  }\n\n  std::vector<RedisRequest> requests;\n  CacheNotifications notifications;\n  QuarkFileMD fileMD;\n  fileMD.initialize(std::move(val));\n  requests.emplace_back(RequestBuilder::writeFileProto(&fileMD));\n\n  if (filemapEntryExists && filemapEntryValid) {\n    RedisRequest req = {\"HDEL\", SSTR(oldContainer << constants::sMapFilesSuffix), oldName};\n    requests.emplace_back(req);\n    notifications.cids.emplace_back(oldContainer);\n  }\n\n  RedisRequest req = {\"HSET\", SSTR(newParent << constants::sMapFilesSuffix), fileMD.getName(), SSTR(fileMD.getId()) };\n  requests.emplace_back(req);\n  notifications.cids.emplace_back(newParent);\n  notifications.fids.emplace_back(fileMD.getId());\n  executeRequestBatch(requests, notifications, dryRun, out, err);\n  return 0;\n}\n\n//------------------------------------------------------------------------------\n// Run the given write batch towards QDB - print the requests, as well as the\n// output.\n//------------------------------------------------------------------------------\nvoid Inspector::executeRequestBatch(const std::vector<RedisRequest>& requests,\n                                    const CacheNotifications& notif, bool dryRun, std::ostream& out,\n                                    std::ostream& err)\n{\n  out << \"------------------------------------------------------ QDB commands to execute\"\n      << std::endl;\n\n  for (size_t i = 0; i < requests.size(); i++) {\n    out << i + 1 << \". \" << serializeRequest(requests[i]) << std::endl;\n  }\n\n  std::vector<RedisRequest> cacheNotifications;\n\n  if (notif.cids.size() + notif.fids.size() != 0) {\n    out << \"------------------------------------------------------ Cache notifications\"\n        << std::endl;\n\n    for (size_t i = 0; i < notif.cids.size(); i++) {\n      cacheNotifications.emplace_back(RequestBuilder::notifyCacheInvalidationCid(\n                                        ContainerIdentifier(notif.cids[i])));\n    }\n\n    for (size_t i = 0; i < notif.fids.size(); i++) {\n      cacheNotifications.emplace_back(RequestBuilder::notifyCacheInvalidationFid(\n                                        FileIdentifier(notif.fids[i])));\n    }\n\n    for (size_t i = 0; i < cacheNotifications.size(); i++) {\n      out << i + 1 << \". \" << serializeRequest(cacheNotifications[i]) << std::endl;\n    }\n  }\n\n  if (dryRun) {\n    out << \"------------------------------------------------------ DRY RUN, CHANGES NOT APPLIED\"\n        << std::endl;\n    return;\n  }\n\n  std::vector<std::future<qclient::redisReplyPtr>> replies;\n  std::vector<std::future<qclient::redisReplyPtr>> notificationReplies;\n\n  for (size_t i = 0; i < requests.size(); i++) {\n    replies.push_back(mQcl.execute(requests[i]));\n  }\n\n  for (size_t i = 0; i < cacheNotifications.size(); i++) {\n    notificationReplies.push_back(mQcl.execute(cacheNotifications[i]));\n  }\n\n  out << \"------------------------------------------------------ Replies\" <<\n      std::endl;\n\n  for (size_t i = 0; i < replies.size(); i++) {\n    out << i + 1 << \". \" << qclient::describeRedisReply(replies[i].get()) <<\n        std::endl;\n  }\n\n  if (!notificationReplies.empty()) {\n    out << \"------------------------------------------------------ Notification replies\"\n        << std::endl;\n\n    for (size_t i = 0; i < notificationReplies.size(); i++) {\n      out << i + 1 << \". \" << qclient::describeRedisReply(\n            notificationReplies[i].get()) << std::endl;\n    }\n  }\n}\n\nEOSNSNAMESPACE_END\n\n"
  },
  {
    "path": "namespace/ns_quarkdb/inspector/Inspector.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Class for inspecting namespace contents - talks directly to QDB\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"namespace/ns_quarkdb/persistency/RequestBuilder.hh\"\n#include \"proto/ContainerMd.pb.h\"\n#include <string>\n#include <map>\n#include <vector>\n#include <set>\n\nnamespace qclient\n{\nclass QClient;\n}\n\nEOSNSNAMESPACE_BEGIN\n\nclass ContainerScanner;\nclass FileScanner;\nclass OutputSink;\nclass FileMetadataFilter;\n\nstruct CacheNotifications {\n  CacheNotifications() {}\n\n  std::vector<uint64_t> fids;\n  std::vector<uint64_t> cids;\n};\n\n//------------------------------------------------------------------------------\n//! Inspector class\n//------------------------------------------------------------------------------\nclass Inspector\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  Inspector(qclient::QClient& qcl, OutputSink& sink);\n\n  //----------------------------------------------------------------------------\n  //! Is the connection to QDB ok? If not, pointless to run anything else.\n  //----------------------------------------------------------------------------\n  bool checkConnection(std::string& err);\n\n  //----------------------------------------------------------------------------\n  //! Dump contents of the given path. ERRNO-like integer return value, 0\n  //! means no error.\n  //----------------------------------------------------------------------------\n  int dump(const std::string& path, bool relative, bool rawPaths, bool noDirs,\n           bool noFiles, bool showSize, bool showMtime, const std::string& attrQuery,\n           std::ostream& out);\n\n  //----------------------------------------------------------------------------\n  //! Scan contents of the given path.\n  //----------------------------------------------------------------------------\n  int scan(const std::string& path, bool relative, bool rawPaths, bool noDirs,\n           bool noFiles, uint32_t maxDepth, const std::string& trimPaths = \"\");\n\n  //----------------------------------------------------------------------------\n  //! Scan all directories in the namespace, and print out some information\n  //! about each one. (even potentially unreachable directories)\n  //----------------------------------------------------------------------------\n  int scanDirs(bool onlyNoAttrs, bool fullPaths, bool countContents,\n               size_t countThreshold);\n\n  //----------------------------------------------------------------------------\n  //! Scan all file metadata in the namespace, and print out some information\n  //! about each one. (even potentially unreachable directories)\n  //----------------------------------------------------------------------------\n  int scanFileMetadata(bool onlySizes, bool fullPaths, bool onlyUnknownFsids);\n\n  //----------------------------------------------------------------------------\n  //! Scan all deathrow entries\n  //----------------------------------------------------------------------------\n  int scanDeathrow(std::ostream& out, std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  //! Forcefully overwrite the given ContainerMD - USE WITH CAUTION\n  //----------------------------------------------------------------------------\n  int overwriteContainerMD(bool dryRun, uint64_t id, uint64_t parentId,\n                           const std::string& name, std::ostream& out, std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  //! Check intra-container conflicts, such as a container having two entries\n  //! with the name name.\n  //----------------------------------------------------------------------------\n  int checkNamingConflicts(bool onePerLine, std::ostream& out, std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  //! Search for files / containers with cursed names\n  //----------------------------------------------------------------------------\n  int checkCursedNames(std::ostream& out, std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  //! Search for holes in FsView: Items which should be in FsView according to\n  //! FMD locations / unlinked locations, but are not there.\n  //----------------------------------------------------------------------------\n  int checkFsViewMissing(std::ostream& out, std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  //! Search for elements which are present in FsView, but not FMD locations\n  //----------------------------------------------------------------------------\n  int checkFsViewExtra(std::ostream& out, std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  //! Search for shadow directories\n  //----------------------------------------------------------------------------\n  int checkShadowDirectories(std::ostream& out, std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  //! Check for corrupted ...eos.ino... hardlink-simulation files\n  //----------------------------------------------------------------------------\n  int checkSimulatedHardlinks(std::ostream& out, std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  // Find files with layout = 1 replica\n  //----------------------------------------------------------------------------\n  int oneReplicaLayout(bool showName, bool showPaths, bool filterInternal,\n                       std::ostream& out, std::ostream& err, bool json);\n\n  //----------------------------------------------------------------------------\n  //! Find files with non-nominal number of stripes (replicas)\n  //----------------------------------------------------------------------------\n  int stripediff(bool json, bool minimal);\n\n  //----------------------------------------------------------------------------\n  //! Find orphan files and orphan directories\n  //----------------------------------------------------------------------------\n  int checkOrphans(std::ostream& out, std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  //! Fix detached parent\n  //----------------------------------------------------------------------------\n  int fixDetachedParentContainer(bool dryRun, uint64_t cid,\n                                 const std::string& destinationPath, std::ostream& out, std::ostream& err);\n  int fixDetachedParentFile(bool dryRun, uint64_t fid,\n                            const std::string& destinationPath, std::ostream& out, std::ostream& err);\n\n  //------------------------------------------------------------------------------\n  //! Fix naming conflict\n  //------------------------------------------------------------------------------\n  int fixShadowFile(bool dryRun, uint64_t fid, const std::string& destinationPath,\n                    std::ostream& out, std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  //! Drop file currently stuck in deathrow. Please note that any pending\n  //! replicas, if they exist, are not deleted.\n  //----------------------------------------------------------------------------\n  int dropFromDeathrow(bool dryRun, uint64_t fid, std::ostream& out,\n                       std::ostream& err);\n\n  //------------------------------------------------------------------------------\n  //! Drop empty container\n  //------------------------------------------------------------------------------\n  int dropEmptyCid(bool dryRun, uint64_t cid);\n\n  //----------------------------------------------------------------------------\n  //! Change the given fid - USE WITH CAUTION\n  //----------------------------------------------------------------------------\n  int changeFid(bool dryRun, uint64_t id, uint64_t newParent,\n                const std::string& newChecksum, int64_t newSize,\n                uint64_t newLayoutId, std::ostream& out, std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  //! Rename the given cid fully, taking care of the container maps as well\n  //----------------------------------------------------------------------------\n  int renameCid(bool dryRun, uint64_t cid, uint64_t newParent,\n                const std::string& newName, std::ostream& out, std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  //! Rename the given fid fully, taking care of the container maps as well\n  //----------------------------------------------------------------------------\n  int renameFid(bool dryRun, uint64_t id, uint64_t newParent,\n                const std::string& newName, std::ostream& out, std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  //! Print out _everything_ known about the given file.\n  //----------------------------------------------------------------------------\n  int printFileMD(uint64_t fid, bool withParents, std::ostream& out,\n                  std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  //! Print out _everything_ known about the given directory.\n  //----------------------------------------------------------------------------\n  int printContainerMD(uint64_t cid, bool withParents, std::ostream& out,\n                       std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  //! Load configuration\n  //----------------------------------------------------------------------------\n  bool loadConfiguration();\n\n  //----------------------------------------------------------------------------\n  //! Activate the given metadata filter\n  //----------------------------------------------------------------------------\n  void setMetadataFilter(std::unique_ptr<FileMetadataFilter> filter);\n\nprivate:\n  std::map<std::string, std::string> mgmConfiguration;\n  std::set<int64_t> validFsIds;\n\n  qclient::QClient& mQcl;\n  OutputSink& mOutputSink;\n\n  std::unique_ptr<FileMetadataFilter> mMetadataFilter;\n\n  //----------------------------------------------------------------------------\n  //! Check if given path is a good choice as a destination for repaired\n  //! files / containers\n  //----------------------------------------------------------------------------\n  bool isDestinationPathSane(const std::string& path, ContainerIdentifier& cid,\n                             std::ostream& out);\n\n  //----------------------------------------------------------------------------\n  //! Run the given write batch towards QDB - print the requests, as well as\n  //! the output.\n  //----------------------------------------------------------------------------\n  void executeRequestBatch(const std::vector<RedisRequest>& requestBatch,\n                           const CacheNotifications& notif, bool dryRun, std::ostream& out,\n                           std::ostream& err);\n\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/inspector/OutputSink.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/inspector/OutputSink.hh\"\n#include \"namespace/utils/Checksum.hh\"\n#include <sstream>\n#include <json/json.h>\n\n#define SSTR(message) static_cast<std::ostringstream&>(std::ostringstream().flush() << message).str()\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Convert to octal string\n//------------------------------------------------------------------------------\nstatic std::string to_octal_string(uint32_t v)\n{\n  std::ostringstream ss;\n  ss << std::oct << v;\n  return ss.str();\n}\n\n//------------------------------------------------------------------------------\n//! Return full path, if possible, otherwise empty\n//------------------------------------------------------------------------------\nstatic std::string populateFullPath(const eos::ns::FileMdProto& proto,\n                                    FileScanner::Item& item)\n{\n  item.fullPath.wait();\n\n  if (item.fullPath.hasException()) {\n    return std::string();\n  }\n\n  std::string fullPath = std::move(item.fullPath).get();\n\n  if (fullPath.empty()) {\n    return std::string();\n  }\n\n  return SSTR(fullPath << proto.name());\n}\n\n//------------------------------------------------------------------------------\n//! Return full path, if possible, otherwise empty\n//------------------------------------------------------------------------------\nstatic std::string populateFullPath(const eos::ns::ContainerMdProto& proto,\n                                    ContainerScanner::Item& item)\n{\n  item.fullPath.wait();\n\n  if (item.fullPath.hasException()) {\n    return std::string();\n  }\n\n  std::string fullPath = std::move(item.fullPath).get();\n\n  if (fullPath.empty()) {\n    return std::string();\n  }\n\n  return fullPath;\n}\n\n//------------------------------------------------------------------------------\n// Serialize locations vector\n//------------------------------------------------------------------------------\ntemplate<typename T>\nstatic std::string serializeLocations(const T& vec)\n{\n  std::ostringstream stream;\n\n  for (int i = 0; i < vec.size(); i++) {\n    stream << vec[i];\n\n    if (i != vec.size() - 1) {\n      stream << \",\";\n    }\n  }\n\n  return stream.str();\n}\n\n//------------------------------------------------------------------------------\n//! Populate map with container attributes\n//------------------------------------------------------------------------------\nstatic void populateMetadata(const eos::ns::ContainerMdProto& proto,\n                             const ContainerPrintingOptions& opts, std::map<std::string, std::string>& out)\n{\n  if (opts.showId) {\n    out[\"cid\"] = std::to_string(proto.id());\n  }\n\n  if (opts.showParent) {\n    out[\"parent_id\"] = std::to_string(proto.parent_id());\n  }\n\n  if (opts.showUid) {\n    out[\"uid\"] = std::to_string(proto.uid());\n  }\n\n  if (opts.showGid) {\n    out[\"gid\"] = std::to_string(proto.gid());\n  }\n\n  if (opts.showTreeSize) {\n    out[\"tree_size\"] = std::to_string(proto.tree_size());\n  }\n\n  if (opts.showMode) {\n    out[\"mode\"] = to_octal_string(proto.mode());\n  }\n\n  if (opts.showMode) {\n    out[\"flags\"] = to_octal_string(proto.flags());\n  }\n\n  if (opts.showName) {\n    out[\"name\"] = proto.name();\n  }\n\n  if (opts.showCTime) {\n    out[\"ctime\"] = Printing::timespecToTimestamp(Printing::parseTimespec(\n                     proto.ctime()));\n  }\n\n  if (opts.showMTime) {\n    out[\"mtime\"] = Printing::timespecToTimestamp(Printing::parseTimespec(\n                     proto.mtime()));\n  }\n\n  if (opts.showSTime) {\n    out[\"stime\"] = Printing::timespecToTimestamp(Printing::parseTimespec(\n                     proto.stime()));\n  }\n\n  if (opts.showXAttr) {\n    for (auto it = proto.xattrs().begin(); it != proto.xattrs().end(); it++) {\n      out[SSTR(\"xattr.\" << it->first)] = it->second;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n//! Print everything known about a ContainerMD\n//------------------------------------------------------------------------------\nvoid OutputSink::print(const eos::ns::ContainerMdProto& proto,\n                       const ContainerPrintingOptions& opts)\n{\n  std::map<std::string, std::string> out;\n  populateMetadata(proto, opts, out);\n  print(out);\n}\n\n//----------------------------------------------------------------------------\n//! Print everything known about a ContainerMD -- custom path\n//----------------------------------------------------------------------------\nvoid OutputSink::printWithCustomPath(const eos::ns::ContainerMdProto& proto,\n                                     const ContainerPrintingOptions& opts,\n                                     const std::string& customPath)\n{\n  std::map<std::string, std::string> out;\n  out[\"path\"] = customPath;\n  populateMetadata(proto, opts, out);\n  print(out);\n}\n\n//------------------------------------------------------------------------------\n// Get count as string\n//------------------------------------------------------------------------------\nstatic std::string countAsString(folly::Future<uint64_t>& fut)\n{\n  fut.wait();\n\n  if (fut.hasException()) {\n    return \"N/A\";\n  }\n\n  uint64_t val = std::move(fut).get();\n  fut = val;\n  return std::to_string(val);\n}\n\n//------------------------------------------------------------------------------\n//! Print everything known about a ContainerMD, including full path if available\n//------------------------------------------------------------------------------\nvoid OutputSink::print(const eos::ns::ContainerMdProto& proto,\n                       const ContainerPrintingOptions& opts, ContainerScanner::Item& item,\n                       bool showCounts)\n{\n  std::map<std::string, std::string> out;\n  populateMetadata(proto, opts, out);\n  std::string fullPath = populateFullPath(proto, item);\n\n  if (!fullPath.empty()) {\n    out[\"path\"] = fullPath;\n  }\n\n  if (showCounts) {\n    out[\"file-count\"] = countAsString(item.fileCount);\n    out[\"container-count\"] = countAsString(item.containerCount);\n  }\n\n  print(out);\n}\n\n//------------------------------------------------------------------------------\n//! Populate map with file attributes\n//------------------------------------------------------------------------------\nstatic void populateMetadata(const eos::ns::FileMdProto& proto,\n                             const FilePrintingOptions& opts, std::map<std::string, std::string>& out)\n{\n  if (opts.showId) {\n    out[\"fid\"] = std::to_string(proto.id());\n  }\n\n  if (opts.showContId) {\n    out[\"pid\"] = std::to_string(proto.cont_id());\n  }\n\n  if (opts.showUid) {\n    out[\"uid\"] = std::to_string(proto.uid());\n  }\n\n  if (opts.showGid) {\n    out[\"gid\"] = std::to_string(proto.gid());\n  }\n\n  if (opts.showSize) {\n    out[\"size\"] = std::to_string(proto.size());\n  }\n\n  if (opts.showLayoutId) {\n    out[\"layout_id\"] = std::to_string(proto.layout_id());\n  }\n\n  if (opts.showFlags) {\n    out[\"flags\"] = to_octal_string(proto.flags());\n  }\n\n  if (opts.showName) {\n    out[\"name\"] = proto.name();\n  }\n\n  if (opts.showLinkName) {\n    out[\"link_name\"] = proto.link_name();\n  }\n\n  if (opts.showCTime) {\n    out[\"ctime\"] = Printing::timespecToTimestamp(Printing::parseTimespec(\n                     proto.ctime()));\n  }\n\n  if (opts.showMTime) {\n    out[\"mtime\"] = Printing::timespecToTimestamp(Printing::parseTimespec(\n                     proto.mtime()));\n  }\n\n  if (opts.showChecksum) {\n    std::string xs;\n    eos::appendChecksumOnStringProtobuf(proto, xs);\n    out[\"xs\"] = xs;\n  }\n\n  if (opts.showLocations) {\n    out[\"locations\"] = serializeLocations(proto.locations());\n  }\n\n  if (opts.showLocations) {\n    out[\"unlink_locations\"] = serializeLocations(proto.unlink_locations());\n  }\n\n  if (opts.showXAttr) {\n    for (auto it = proto.xattrs().begin(); it != proto.xattrs().end(); it++) {\n      out[SSTR(\"xattr.\" << it->first)] = it->second;\n    }\n  }\n\n  if (opts.showSTime) {\n    out[\"stime\"] = Printing::timespecToTimestamp(Printing::parseTimespec(\n                     proto.stime()));\n  }\n\n  if(opts.showATime) {\n    out[\"atime\"] = Printing::timespecToTimestamp(Printing::parseTimespec(\n        proto.atime()));\n  }\n}\n\n//------------------------------------------------------------------------------\n//! Print everything known about a FileMD\n//------------------------------------------------------------------------------\nvoid OutputSink::print(const eos::ns::FileMdProto& proto,\n                       const FilePrintingOptions& opts)\n{\n  std::map<std::string, std::string> out;\n  populateMetadata(proto, opts, out);\n  print(out);\n}\n\n//----------------------------------------------------------------------------\n// Print everything known about a FileMD -- custom path\n//----------------------------------------------------------------------------\nvoid OutputSink::printWithCustomPath(const eos::ns::FileMdProto& proto,\n                                     const FilePrintingOptions& opts,\n                                     const std::string& customPath)\n{\n  std::map<std::string, std::string> out;\n  out[\"path\"] = customPath;\n  populateMetadata(proto, opts, out);\n  print(out);\n}\n\n//----------------------------------------------------------------------------\n//! Print everything known about a FileMD -- Additional fields to add\n//----------------------------------------------------------------------------\nvoid OutputSink::printWithAdditionalFields(const eos::ns::FileMdProto& proto,\n    const FilePrintingOptions& opts,\n    std::map<std::string, std::string>& extension)\n{\n  populateMetadata(proto, opts, extension);\n  print(extension);\n}\n\n//------------------------------------------------------------------------------\n//! Print everything known about a FileMD, including full path if available\n//------------------------------------------------------------------------------\nvoid OutputSink::print(const eos::ns::FileMdProto& proto,\n                       const FilePrintingOptions& opts, FileScanner::Item& item)\n{\n  std::map<std::string, std::string> out;\n  populateMetadata(proto, opts, out);\n  std::string fullPath = populateFullPath(proto, item);\n\n  if (!fullPath.empty()) {\n    out[\"path\"] = fullPath;\n  }\n\n  print(out);\n}\n\n//------------------------------------------------------------------------------\n// Class StreamSink\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nStreamSink::StreamSink(std::ostream& out, std::ostream& err)\n  : OutputSink(out, err)\n{}\n\n//------------------------------------------------------------------------------\n// Print implementation for map\n//------------------------------------------------------------------------------\nvoid StreamSink::print(const std::map<std::string, std::string>& line)\n{\n  for (auto it = line.begin(); it != line.end(); it++) {\n    if (it != line.begin()) {\n      mOut << \" \";\n    }\n\n    mOut << Printing::escapeNonPrintable(it->first) << \"=\" <<\n         Printing::escapeNonPrintable(it->second);\n  }\n\n  mOut << std::endl;\n}\n\n\n//------------------------------------------------------------------------------\n// Class JsonStreamSink\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nJsonStreamSink::JsonStreamSink(std::ostream& out, std::ostream& err)\n  : OutputSink(out, err), mFirst(true)\n{\n  mOut << \"[\" << std::endl;\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nJsonStreamSink::~JsonStreamSink()\n{\n  mOut << \"]\" << std::endl;\n}\n\n//------------------------------------------------------------------------------\n// Print implementation\n//------------------------------------------------------------------------------\nvoid JsonStreamSink::print(const std::map<std::string, std::string>& line)\n{\n  if (!mFirst) {\n    mOut << \",\" << std::endl;\n  }\n\n  mFirst = false;\n  Json::Value json;\n\n  for (auto it = line.begin(); it != line.end(); it++) {\n    json[it->first] = it->second;\n  }\n\n  mOut << json;\n}\n\n\n//------------------------------------------------------------------------------\n// Class JsonLinedStreamSink\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nJsonLinedStreamSink::JsonLinedStreamSink(std::ostream& out, std::ostream& err)\n  : OutputSink(out, err)\n{\n  mBuilder[\"indentation\"] = \"\";  // or whatever you like\n  mWriter.reset(mBuilder.newStreamWriter());\n}\n\n//------------------------------------------------------------------------------\n// Print implementation\n//------------------------------------------------------------------------------\nvoid JsonLinedStreamSink::print(const std::map<std::string, std::string>& line)\n{\n  Json::Value json;\n\n  for (auto it = line.begin(); it != line.end(); it++) {\n    json[it->first] = it->second;\n  }\n\n  print(json);\n}\n\n//------------------------------------------------------------------------------\n// Print JsonValue implementation\n//------------------------------------------------------------------------------\nvoid JsonLinedStreamSink::print(const Json::Value& jsonObj)\n{\n  mWriter->write(jsonObj, &mOut);\n  mOut << std::endl;\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/inspector/OutputSink.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Abstract class to send / format output\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"namespace/ns_quarkdb/inspector/FileScanner.hh\"\n#include \"namespace/ns_quarkdb/inspector/ContainerScanner.hh\"\n#include \"namespace/ns_quarkdb/inspector/Printing.hh\"\n#include \"proto/ContainerMd.pb.h\"\n#include \"proto/FileMd.pb.h\"\n#include <json/json.h>\n#include <map>\n\nEOSNSNAMESPACE_BEGIN\n\nstruct ContainerPrintingOptions;\nstruct FilePrintingOptions;\n\n//------------------------------------------------------------------------------\n//! Interface for printing output\n//------------------------------------------------------------------------------\nclass OutputSink\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  OutputSink(std::ostream& out, std::ostream& err):\n    mOut(out), mErr(err)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Virtual destructor\n  //----------------------------------------------------------------------------\n  virtual ~OutputSink() = default;\n\n  //----------------------------------------------------------------------------\n  //! Print interface\n  //----------------------------------------------------------------------------\n  virtual void print(const std::map<std::string, std::string>& out) = 0;\n\n  //----------------------------------------------------------------------------\n  //! Print interface for string\n  //----------------------------------------------------------------------------\n  virtual void print(const std::string& out)\n  {\n    mOut << Printing::escapeNonPrintable(out) << std::endl;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Print interface for json object\n  //----------------------------------------------------------------------------\n  virtual void print(const Json::Value& json_obj)\n  {\n    mOut << json_obj << std::endl;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Debug output\n  //----------------------------------------------------------------------------\n  virtual void err(const std::string& str)\n  {\n    mErr << Printing::escapeNonPrintable(str) << std::endl;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Print everything known about a ContainerMD\n  //----------------------------------------------------------------------------\n  void print(const eos::ns::ContainerMdProto& proto,\n             const ContainerPrintingOptions& opts);\n\n  //----------------------------------------------------------------------------\n  //! Print everything known about a ContainerMD -- custom path\n  //----------------------------------------------------------------------------\n  void printWithCustomPath(const eos::ns::ContainerMdProto& proto,\n                           const ContainerPrintingOptions& opts,\n                           const std::string& customPath);\n\n  //----------------------------------------------------------------------------\n  //! Print everything known about a ContainerMD, including\n  //! full path if available\n  //----------------------------------------------------------------------------\n  void print(const eos::ns::ContainerMdProto& proto,\n             const ContainerPrintingOptions& opts,\n             ContainerScanner::Item& item, bool showCounts);\n\n  //----------------------------------------------------------------------------\n  //! Print everything known about a FileMD\n  //----------------------------------------------------------------------------\n  void print(const eos::ns::FileMdProto& proto, const FilePrintingOptions& opts);\n\n  //----------------------------------------------------------------------------\n  //! Print everything known about a FileMD -- custom path\n  //----------------------------------------------------------------------------\n  void printWithCustomPath(const eos::ns::FileMdProto& proto,\n                           const FilePrintingOptions& opts,\n                           const std::string& customPath);\n\n  //----------------------------------------------------------------------------\n  //! Print everything known about a FileMD -- custom path\n  //----------------------------------------------------------------------------\n  void printWithAdditionalFields(const eos::ns::FileMdProto& proto,\n                                 const FilePrintingOptions& opts,\n                                 std::map<std::string, std::string>& extension);\n  //----------------------------------------------------------------------------\n  //! Print everything known about a FileMD, including full path if available\n  //----------------------------------------------------------------------------\n  void print(const eos::ns::FileMdProto& proto, const FilePrintingOptions& opts,\n             FileScanner::Item& item);\n\nprotected:\n  std::ostream& mOut;\n  std::ostream& mErr;\n};\n\n//------------------------------------------------------------------------------\n//! OutputSink implementation based on streams\n//------------------------------------------------------------------------------\nclass StreamSink : public OutputSink\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  StreamSink(std::ostream& out, std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  //! Print implementation\n  //----------------------------------------------------------------------------\n  virtual void print(const std::map<std::string, std::string>& line) override;\n};\n\n//------------------------------------------------------------------------------\n//! OutputSink implementation based on json streams\n//------------------------------------------------------------------------------\nclass JsonStreamSink : public OutputSink\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  JsonStreamSink(std::ostream& out, std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~JsonStreamSink();\n\n  //----------------------------------------------------------------------------\n  //! Print implementation\n  //----------------------------------------------------------------------------\n  virtual void print(const std::map<std::string, std::string>& line) override;\n\nprivate:\n  bool mFirst;\n};\n\n\nclass JsonLinedStreamSink : public OutputSink\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  JsonLinedStreamSink(std::ostream& out, std::ostream& err);\n\n  //----------------------------------------------------------------------------\n  //! Print implementation\n  //----------------------------------------------------------------------------\n  virtual void print(const std::map<std::string, std::string>& line) override;\n\n  //----------------------------------------------------------------------------\n  //! Print interface, single string implementation\n  //----------------------------------------------------------------------------\n  virtual void print(const Json::Value& jsonObj) override;\n\nprivate:\n  Json::StreamWriterBuilder mBuilder;\n  std::unique_ptr<Json::StreamWriter> mWriter;\n};\n\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/inspector/Printing.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/inspector/Printing.hh\"\n#include \"common/LayoutId.hh\"\n#include \"namespace/utils/Checksum.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/utils/Etag.hh\"\n#include \"common/StringConversion.hh\"\n#include <sstream>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Escape non-printable string\n//------------------------------------------------------------------------------\nstd::string Printing::escapeNonPrintable(const std::string &str) {\n  std::stringstream ss;\n\n  for(size_t i = 0; i < str.size(); i++) {\n    if(isprint(str[i])) {\n      ss << str[i];\n    }\n    else if(str[i] == '\\0') {\n      ss << \"\\\\x00\";\n    }\n    else {\n      char buff[16];\n      snprintf(buff, 16, \"\\\\x%02X\", (unsigned char) str[i]);\n      ss << buff;\n    }\n  }\n  \n  return ss.str();\n}\n\n//------------------------------------------------------------------------------\n// Serialize locations vector\n//------------------------------------------------------------------------------\ntemplate<typename T>\nstatic std::string serializeLocations(const T& vec) {\n  std::ostringstream stream;\n  stream << \"[\";\n\n  for(int i = 0; i < vec.size(); i++) {\n    stream << vec[i];\n    if(i != vec.size() - 1) {\n      stream << \", \";\n    }\n  }\n\n  stream << \"]\";\n  return stream.str();\n}\n\n//------------------------------------------------------------------------------\n// timespec to fileinfo: Convert a timespec into\n// \"1447252711.38412918\"\n//------------------------------------------------------------------------------\nstd::string Printing::timespecToTimestamp(const struct timespec &val) {\n  std::ostringstream ss;\n  ss << val.tv_sec << \".\" << val.tv_nsec;\n  return ss.str();\n}\n\n//------------------------------------------------------------------------------\n// timespec to fileinfo: Convert a timespec into\n// \"Wed Nov 11 15:38:31 2015 Timestamp: 1447252711.38412918\"\n//------------------------------------------------------------------------------\nvoid Printing::timespecToFileinfo(const struct timespec &val, std::ostream &stream) {\n  time_t tv_sec = (time_t) val.tv_sec;\n  char buffer[4096];\n\n  stream << ctime_r(&tv_sec, buffer);\n  stream.seekp(-1, std::ios_base::end);\n  stream << \" Timestamp: \" << timespecToTimestamp(val);\n}\n\nstd::string Printing::timespecToFileinfo(const struct timespec &val) {\n  std::ostringstream ss;\n  timespecToFileinfo(val, ss);\n  return ss.str();\n}\n\n//------------------------------------------------------------------------------\n// Serialize protobuf time\n//------------------------------------------------------------------------------\ntemplate<typename T>\nstatic std::string serializeTime(const T& bytes) {\n  std::ostringstream ss;\n  Printing::timespecToFileinfo(Printing::parseTimespec(bytes), ss);\n  return ss.str();\n}\n\n//----------------------------------------------------------------------------\n// Print the given ContainerMd protobuf using multiple lines, full information\n//----------------------------------------------------------------------------\nvoid Printing::printMultiline(const eos::ns::ContainerMdProto &proto, std::ostream &stream) {\n  stream << \"ID: \" << proto.id() << std::endl;\n  stream << \"Parent ID: \" << proto.parent_id() << std::endl;\n  stream << \"Name: \" << proto.name() << std::endl;\n  stream << \"uid: \" << proto.uid() << \", gid: \" << proto.gid() << std::endl;\n  stream << \"ctime: \" << serializeTime(proto.ctime()) << std::endl;\n  stream << \"mtime: \" << serializeTime(proto.mtime()) << std::endl;\n  stream << \"stime: \" << serializeTime(proto.stime()) << std::endl;\n  stream << \"Tree size: \" << proto.tree_size() << std::endl;\n  stream << \"Mode: \" << proto.mode() << std::endl;\n  stream << \"Flags: \" << proto.flags() << std::endl;\n  stream << \"Extended attributes (\" << proto.xattrs().size() << \"):\" << std::endl;\n  for(auto it = proto.xattrs().begin(); it != proto.xattrs().end(); it++) {\n    stream << \"    \" << it->first << \"=\" << it->second << std::endl;\n  }\n\n}\n\n//------------------------------------------------------------------------------\n// Print the given FileMd protobuf using multiple lines, full information\n//------------------------------------------------------------------------------\nvoid Printing::printMultiline(const eos::ns::FileMdProto &proto, std::ostream &stream) {\n  stream << \"ID: \" << proto.id() << std::endl;\n  stream << \"Name: \" << proto.name() << std::endl;\n  stream << \"Link name: \" << proto.link_name() << std::endl;\n  stream << \"Container ID: \" << proto.cont_id() << std::endl;\n  stream << \"uid: \" << proto.uid() << \", gid: \" << proto.gid() << std::endl;\n  stream << \"Size: \" << proto.size() << std::endl;\n  stream << \"Modify: \" << serializeTime(proto.mtime()) << std::endl;\n  stream << \"Change: \" << serializeTime(proto.ctime()) << std::endl;\n  stream << \"Access: \" << serializeTime(proto.atime()) << std::endl;\n  stream << \"Flags: \" << common::StringConversion::IntToOctal( (int) proto.flags(), 4) << std::endl;\n\n  std::string checksum;\n  appendChecksumOnStringProtobuf(proto, checksum);\n  stream << \"Checksum type: \" << common::LayoutId::GetChecksumString(proto.layout_id()) << \", checksum bytes: \" << checksum << std::endl;\n  stream << \"Expected number of replicas / stripes: \" << eos::common::LayoutId::GetStripeNumber(proto.layout_id()) + 1 << std::endl;\n\n  std::string etag;\n  eos::calculateEtag(proto, etag);\n  stream << \"Etag: \" << etag << std::endl;\n\n  stream << \"Locations: \" << serializeLocations(proto.locations()) << std::endl;\n  stream << \"Unlinked locations: \" << serializeLocations(proto.unlink_locations()) << std::endl;\n  stream << \"Extended attributes (\" << proto.xattrs().size() << \"):\" << std::endl;\n  for(auto it = proto.xattrs().begin(); it != proto.xattrs().end(); it++) {\n    stream << \"    \" << it->first << \"=\" << it->second << std::endl;\n  }\n\n}\n\nstd::string Printing::printMultiline(const eos::ns::FileMdProto &proto) {\n  std::ostringstream ss;\n  printMultiline(proto, ss);\n  return ss.str();\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/inspector/Printing.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Class for formatting and printing namespace protobuf objects\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"proto/FileMd.pb.h\"\n#include \"proto/ContainerMd.pb.h\"\n#include <ostream>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! File printing options\n//------------------------------------------------------------------------------\nstruct FilePrintingOptions {\n  bool showId = true;\n  bool showContId = true;\n  bool showUid = true;\n  bool showGid = true;\n  bool showSize = true;\n  bool showLayoutId = true;\n  bool showFlags = true;\n  bool showName = true;\n  bool showLinkName = true;\n  bool showCTime = true;\n  bool showMTime = true;\n  bool showChecksum = true;\n  bool showLocations = true;\n  bool showUnlinkLocations = true;\n  bool showXAttr = true;\n  bool showSTime = true;\n  bool showATime = true;\n};\n\n//------------------------------------------------------------------------------\n//! Container printing options\n//------------------------------------------------------------------------------\nstruct ContainerPrintingOptions {\n  bool showId = true;\n  bool showParent = true;\n  bool showUid = true;\n  bool showGid = true;\n  bool showTreeSize = true;\n  bool showMode = true;\n  bool showFlags = true;\n  bool showName = true;\n  bool showCTime = true;\n  bool showMTime = true;\n  bool showSTime = true;\n  bool showXAttr = true;\n};\n\n//------------------------------------------------------------------------------\n//! Printing class\n//------------------------------------------------------------------------------\nclass Printing\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Print the given FileMd protobuf using multiple lines, full information\n  //----------------------------------------------------------------------------\n  static void printMultiline(const eos::ns::FileMdProto& proto,\n                             std::ostream& stream);\n  static std::string printMultiline(const eos::ns::FileMdProto& proto);\n\n  //----------------------------------------------------------------------------\n  //! Print the given ContainerMd protobuf using multiple lines, full information\n  //----------------------------------------------------------------------------\n  static void printMultiline(const eos::ns::ContainerMdProto& proto,\n                             std::ostream& stream);\n\n  //----------------------------------------------------------------------------\n  //! timespec to fileinfo: Convert a timespec into\n  //! \"Wed Nov 11 15:38:31 2015 Timestamp: 1447252711.38412918\"\n  //----------------------------------------------------------------------------\n  static void timespecToFileinfo(const struct timespec& val,\n                                 std::ostream& stream);\n  static std::string timespecToFileinfo(const struct timespec& val);\n  static std::string timespecToTimestamp(const struct timespec& val);\n\n  //----------------------------------------------------------------------------\n  // Escape non-printable string\n  //----------------------------------------------------------------------------\n  static std::string escapeNonPrintable(const std::string& str);\n\n  //----------------------------------------------------------------------------\n  //! Parse timespec\n  //----------------------------------------------------------------------------\n  template<typename T>\n  static struct timespec parseTimespec(const T& bytes)\n  {\n    struct timespec spec = {};\n\n    if (bytes.length()) {\n      (void) memcpy(&spec, bytes.data(), sizeof(struct timespec));\n    }\n\n    return spec;\n  }\n\n\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/ContainerMDSvc.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/persistency/ContainerMDSvc.hh\"\n#include \"namespace/ns_quarkdb/persistency/MetadataFetcher.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/ns_quarkdb/ContainerMD.hh\"\n#include \"namespace/ns_quarkdb/FileMD.hh\"\n#include \"namespace/ns_quarkdb/persistency/MetadataProvider.hh\"\n#include \"namespace/utils/StringConvertion.hh\"\n#include \"namespace/ns_quarkdb/persistency/RequestBuilder.hh\"\n#include \"namespace/ns_quarkdb/ConfigurationParser.hh\"\n#include \"common/Assert.hh\"\n#include \"common/Logging.hh\"\n#include \"common/StacktraceHere.hh\"\n#include <memory>\n#include <numeric>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nQuarkContainerMDSvc::QuarkContainerMDSvc(qclient::QClient* qcl,\n    MetadataFlusher* flusher)\n  : pQuotaStats(nullptr), pFileSvc(nullptr), pQcl(qcl), pFlusher(flusher),\n    mMetaMap(), mMetadataProvider(nullptr), mNumConts(0ull) {}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nQuarkContainerMDSvc::~QuarkContainerMDSvc()\n{\n  if (pFlusher) {\n    pFlusher->synchronize();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Configure the container service\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMDSvc::configure(const std::map<std::string, std::string>& config)\n{\n  mMetaMap.setKey(constants::sMapMetaInfoKey);\n  mMetaMap.setClient(*pQcl);\n  mMetaMap.hset(\"EOS-NS-FORMAT-VERSION\", \"1\");\n\n  if (config.find(constants::sMaxNumCacheDirs) != config.end()) {\n    mCacheNum = config.at(constants::sMaxNumCacheDirs);\n\n    if (mMetadataProvider) {\n      mMetadataProvider->setContainerMDCacheNum(std::stoull(mCacheNum));\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Initialize the container service\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMDSvc::initialize()\n{\n  if (pFileSvc == nullptr) {\n    MDException e(EINVAL);\n    e.getMessage()  << __FUNCTION__  << \" No file metadata service set for \"\n                    << \"the container metadata service\";\n    throw e;\n  }\n\n  if (mMetadataProvider == nullptr) {\n    MDException e(EINVAL);\n    e.getMessage()  << __FUNCTION__  << \" No metadata provider set for \"\n                    << \"the container metadata service\";\n    throw e;\n  }\n\n  if (mUnifiedInodeProvider == nullptr) {\n    MDException e(EINVAL);\n    e.getMessage()  << __FUNCTION__  << \" No inode provider set for \"\n                    << \"the container metadata service\";\n    throw e;\n  }\n\n  if ((pQcl == nullptr) || (pFlusher == nullptr)) {\n    MDException e(EINVAL);\n    e.getMessage()  << __FUNCTION__ << \" No qclient/flusher initialized for \"\n                    << \"the container metadata service\";\n    throw e;\n  }\n\n  if (!mCacheNum.empty()) {\n    mMetadataProvider->setContainerMDCacheNum(std::stoull(mCacheNum));\n  }\n\n  SafetyCheck();\n  mNumConts.store(pQcl->execute(RequestBuilder::getNumberOfContainers())\n                  .get()->integer);\n}\n\n//------------------------------------------------------------------------------\n// Safety check to make sure there are no container entries in the backed\n// with ids bigger than the max container id.\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMDSvc::SafetyCheck()\n{\n  std::string blob;\n  IContainerMD::id_t free_id = getFirstFreeId();\n  std::vector<uint64_t> offsets  = {1, 10, 50, 100, 501, 1001, 11000, 50000,\n                                    100000, 150199, 200001, 1000002, 2000123\n                                   };\n  std::vector<folly::Future<eos::ns::ContainerMdProto>> futs;\n\n  for (auto incr : offsets) {\n    IContainerMD::id_t check_id = free_id + incr;\n    futs.emplace_back(MetadataFetcher::getContainerFromId(*pQcl,\n                      ContainerIdentifier(check_id)));\n  }\n\n  for (size_t i = 0; i < futs.size(); i++) {\n    try {\n      std::move(futs[i]).get();\n    } catch (eos::MDException& qdb_err) {\n      // All is good, we didn't find any container, as expected\n      continue;\n    }\n\n    // Uh-oh, this is bad.\n    MDException e(EEXIST);\n    e.getMessage()  << __FUNCTION__ << \" FATAL: Risk of data loss, found \"\n                    << \"container (\" << free_id + offsets[i] <<\n                    \") with id bigger than max container id (\" << free_id << \")\";\n    throw e;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Asynchronously get the container metadata information for the given ID\n//------------------------------------------------------------------------------\nfolly::Future<IContainerMDPtr>\nQuarkContainerMDSvc::getContainerMDFut(IContainerMD::id_t id)\n{\n  //----------------------------------------------------------------------------\n  // Short-circuit for container zero, avoid a pointless roundtrip by failing\n  // immediatelly. Happens for files which have been unlinked, but not removed\n  // yet.\n  //----------------------------------------------------------------------------\n  if (id == 0) {\n    return folly::makeFuture<IContainerMDPtr>(make_mdexception(ENOENT,\n           \"Container #0 not found\"));\n  }\n\n  return mMetadataProvider->retrieveContainerMD(ContainerIdentifier(id));\n}\n\n//------------------------------------------------------------------------------\n// Get the container metadata information\n//------------------------------------------------------------------------------\nIContainerMDPtr\nQuarkContainerMDSvc::getContainerMD(IContainerMD::id_t id, uint64_t* clock)\n{\n  IContainerMDPtr container = getContainerMDFut(id).get();\n\n  if (container && clock) {\n    *clock = container->getClock();\n  }\n\n  return container;\n}\n\n//------------------------------------------------------------------------------\n// Drop cached ContainerMD - return true if found\n//------------------------------------------------------------------------------\nbool\nQuarkContainerMDSvc::dropCachedContainerMD(ContainerIdentifier id)\n{\n  return mMetadataProvider->dropCachedContainerID(id);\n}\n\n//------------------------------------------------------------------------------\n// Create a new container metadata object\n//------------------------------------------------------------------------------\nstd::shared_ptr<IContainerMD>\nQuarkContainerMDSvc::createContainer(IContainerMD::id_t id)\n{\n  uint64_t free_id;\n\n  if (id > 0) {\n    mUnifiedInodeProvider->blacklistContainerId(id);\n    free_id = id;\n  } else {\n    free_id = mUnifiedInodeProvider->reserveContainerId();\n  }\n\n  std::shared_ptr<IContainerMD> cont\n  (new QuarkContainerMD(free_id, pFileSvc, static_cast<IContainerMDSvc*>(this)));\n  ++mNumConts;\n  mMetadataProvider->insertContainerMD(cont->getIdentifier(), cont);\n  return cont;\n}\n\n//----------------------------------------------------------------------------\n// Update backend store and notify listeners\n//----------------------------------------------------------------------------\nvoid\nQuarkContainerMDSvc::updateStore(IContainerMD* obj)\n{\n  if (obj->getName() == \"\") {\n    eos_static_crit(\"msg=\\\"call on container with empty name\\\" id=%llu \"\n                    \"parent=%llu, trace=\\\"%s\\\"\", obj->getId(),\n                    obj->getParentId(), common::getStacktrace().c_str());\n    // eventually throw, once we understand how this happens\n    // TODO: @ccaffy - this should throw an exception, but\n    // all the code calling this method should handle it properly\n    // by surrounding the calls with try/catch blocks\n    return;\n  }\n\n  pFlusher->execute(RequestBuilder::writeContainerProto(obj));\n}\n\n//----------------------------------------------------------------------------\n// Remove object from the store assuming it's already empty\n//----------------------------------------------------------------------------\nvoid\nQuarkContainerMDSvc::removeContainer(IContainerMD* obj)\n{\n  // Protection in case the container is not empty\n  if ((obj->getNumFiles() != 0) || (obj->getNumContainers() != 0)) {\n    MDException e(EINVAL);\n    e.getMessage()  << __FUNCTION__ << \" Failed to remove container #\"\n                    << obj->getId() << \" since it's not empty\";\n    throw e;\n  }\n\n  std::string sid = stringify(obj->getId());\n  pFlusher->execute(RequestBuilder::deleteContainerProto(ContainerIdentifier(\n                      obj->getId())));\n\n  // If this was the root container i.e. id=1 then drop also the meta map\n  if (obj->getId() == 1) {\n    pFlusher->del(constants::sMapMetaInfoKey);\n  }\n\n  obj->setDeleted();\n\n  if (mNumConts) {\n    --mNumConts;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Add change listener\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMDSvc::addChangeListener(IContainerMDChangeListener* listener)\n{\n  pListeners.push_back(listener);\n}\n\n//------------------------------------------------------------------------------\n// Create container in parent\n//------------------------------------------------------------------------------\nstd::shared_ptr<IContainerMD>\nQuarkContainerMDSvc::createInParent(const std::string& name,\n                                    IContainerMD* parent)\n{\n  std::shared_ptr<IContainerMD> container = createContainer(0);\n  container->setName(name);\n  parent->addContainer(container.get());\n  updateStore(container.get());\n  ++mNumConts;\n  return container;\n}\n\n//----------------------------------------------------------------------------\n// Get the lost+found container, create if necessary\n//----------------------------------------------------------------------------\nstd::shared_ptr<IContainerMD>\nQuarkContainerMDSvc::getLostFound()\n{\n  // Get root\n  std::shared_ptr<IContainerMD> root;\n\n  try {\n    root = getContainerMD(1);\n  } catch (MDException& e) {\n    root = createContainer(0);\n    root->setParentId(root->getId());\n    updateStore(root.get());\n  }\n\n  // Get or create lost+found if necessary\n  std::shared_ptr<IContainerMD> lostFound = root->findContainer(\"lost+found\");\n\n  if (lostFound == nullptr) {\n    lostFound = createInParent(\"lost+found\", root.get());\n  }\n\n  return lostFound;\n}\n\n//----------------------------------------------------------------------------\n// Get the orphans / name conflicts container\n//----------------------------------------------------------------------------\nstd::shared_ptr<IContainerMD>\nQuarkContainerMDSvc::getLostFoundContainer(const std::string& name)\n{\n  std::shared_ptr<IContainerMD> lostFound = getLostFound();\n\n  if (name.empty()) {\n    return lostFound;\n  }\n\n  std::shared_ptr<IContainerMD> cont = lostFound->findContainer(name);\n\n  if (cont == nullptr) {\n    cont = createInParent(name, lostFound.get());\n  }\n\n  return cont;\n}\n\n//------------------------------------------------------------------------------\n// Get number of containers which is sum(hlen(hash_i)) for i=0,128k\n//------------------------------------------------------------------------------\nuint64_t\nQuarkContainerMDSvc::getNumContainers()\n{\n  return mNumConts.load();\n}\n\n//------------------------------------------------------------------------------\n// Notify the listeners about the change\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMDSvc::notifyListeners(IContainerMD* obj,\n                                     IContainerMDChangeListener::Action a)\n{\n  for (const auto& elem : pListeners) {\n    elem->containerMDChanged(obj, a);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get first free container id\n//------------------------------------------------------------------------------\nIContainerMD::id_t\nQuarkContainerMDSvc::getFirstFreeId()\n{\n  return mUnifiedInodeProvider->getFirstFreeContainerId();\n}\n\n//------------------------------------------------------------------------------\n//! Retrieve MD cache statistics.\n//------------------------------------------------------------------------------\nCacheStatistics\nQuarkContainerMDSvc::getCacheStatistics()\n{\n  return mMetadataProvider->getContainerMDCacheStats();\n}\n\n//------------------------------------------------------------------------------\n// Blacklist IDs below the given threshold\n//------------------------------------------------------------------------------\nvoid\nQuarkContainerMDSvc::blacklistBelow(ContainerIdentifier id)\n{\n  mUnifiedInodeProvider->blacklistContainerId(id.getUnderlyingUInt64());\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/ContainerMDSvc.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Container metadata service based on QuarkDB\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_CONTAINER_MD_SVC_HH\n#define EOS_NS_CONTAINER_MD_SVC_HH\n\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"namespace/ns_quarkdb/accounting/QuotaStats.hh\"\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n#include \"namespace/ns_quarkdb/persistency/NextInodeProvider.hh\"\n#include \"namespace/ns_quarkdb/persistency/UnifiedInodeProvider.hh\"\n#include \"qclient/structures/QHash.hh\"\n#include <list>\n#include <map>\n\nnamespace qclient {\n  class QClient;\n}\n\nEOSNSNAMESPACE_BEGIN\n\nclass QuarkContainerMD;\nclass MetadataProvider;\n\n//------------------------------------------------------------------------------\n//! Container metadata service based on Redis\n//------------------------------------------------------------------------------\nclass QuarkContainerMDSvc : public IContainerMDSvc\n{\n  friend QuarkContainerMD;\n\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  QuarkContainerMDSvc(qclient::QClient *qcl, MetadataFlusher *flusher);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~QuarkContainerMDSvc();\n\n  //----------------------------------------------------------------------------\n  //! Initialize the container service\n  //----------------------------------------------------------------------------\n  virtual void initialize() override;\n\n  //----------------------------------------------------------------------------\n  //! Configure the container service\n  //----------------------------------------------------------------------------\n  virtual void configure(const std::map<std::string, std::string>& config)\n  override;\n\n  //----------------------------------------------------------------------------\n  //! Finalize the container service\n  //----------------------------------------------------------------------------\n  virtual void finalize() override {};\n\n  //----------------------------------------------------------------------------\n  //! Asynchronously get the container metadata information for the given ID\n  //----------------------------------------------------------------------------\n  virtual folly::Future<IContainerMDPtr> getContainerMDFut(\n    IContainerMD::id_t id) override;\n\n  //----------------------------------------------------------------------------\n  //! Get the container metadata information for the given container ID\n  //----------------------------------------------------------------------------\n  virtual IContainerMDPtr\n  getContainerMD(IContainerMD::id_t id) override\n  {\n    return getContainerMD(id, 0);\n  }\n\n  //------------------------------------------------------------------------\n  //! Get the container metadata information for the given ID and clock\n  //------------------------------------------------------------------------\n  virtual std::shared_ptr<IContainerMD>\n  getContainerMD(IContainerMD::id_t id, uint64_t* clock) override;\n\n  //----------------------------------------------------------------------------\n  //! Drop cached ContainerMD - return true if found\n  //----------------------------------------------------------------------------\n  virtual bool\n  dropCachedContainerMD(ContainerIdentifier id) override;\n\n  //----------------------------------------------------------------------------\n  //! Create new container metadata object with an assigned id, the user has\n  //! to fill all the remaining fields\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IContainerMD> createContainer(IContainerMD::id_t id) override;\n\n  //----------------------------------------------------------------------------\n  //! Update the contaienr metadata in the backing store after the\n  //! ContainerMD object has been changed\n  //----------------------------------------------------------------------------\n  virtual void updateStore(IContainerMD* obj) override;\n\n  //----------------------------------------------------------------------------\n  //! Remove object from the store, write lock the container before calling this!\n  //----------------------------------------------------------------------------\n  virtual void removeContainer(IContainerMD* obj) override;\n\n  //----------------------------------------------------------------------------\n  //! Get number of containers\n  //----------------------------------------------------------------------------\n  virtual uint64_t getNumContainers() override;\n\n  //----------------------------------------------------------------------------\n  //! Add file listener that will be notified about all of the changes in\n  //! the store\n  //----------------------------------------------------------------------------\n  virtual void addChangeListener(IContainerMDChangeListener* listener) override;\n\n  //----------------------------------------------------------------------------\n  //! Create container in parent\n  //----------------------------------------------------------------------------\n  std::shared_ptr<IContainerMD> createInParent(const std::string& name,\n      IContainerMD* parent) override;\n\n  //----------------------------------------------------------------------------\n  //! Get the lost+found container, create if necessary\n  //----------------------------------------------------------------------------\n  std::shared_ptr<IContainerMD> getLostFound();\n\n  //----------------------------------------------------------------------------\n  //! Get the orphans / name conflicts container\n  //----------------------------------------------------------------------------\n  std::shared_ptr<IContainerMD> getLostFoundContainer(const std::string& name)\n  override;\n\n  //----------------------------------------------------------------------------\n  //! Set file metadata service\n  //----------------------------------------------------------------------------\n  void\n  setFileMDService(IFileMDSvc* file_svc) override\n  {\n    pFileSvc = file_svc;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set metadata provider\n  //----------------------------------------------------------------------------\n  void\n  setMetadataProvider(MetadataProvider* provider)\n  {\n    mMetadataProvider = provider;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set inode provider\n  //----------------------------------------------------------------------------\n  void\n  setInodeProvider(UnifiedInodeProvider* provider)\n  {\n    mUnifiedInodeProvider = provider;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set the QuotaStats object for the follower\n  //----------------------------------------------------------------------------\n  void\n  setQuotaStats(IQuotaStats* quotaStats) override\n  {\n    pQuotaStats = quotaStats;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get first free container id\n  //----------------------------------------------------------------------------\n  IContainerMD::id_t getFirstFreeId() override;\n\n  //----------------------------------------------------------------------------\n  //! Retrieve MD cache statistics.\n  //----------------------------------------------------------------------------\n  virtual CacheStatistics getCacheStatistics() override;\n\n  //----------------------------------------------------------------------------\n  //! Blacklist IDs below the given threshold\n  //----------------------------------------------------------------------------\n  virtual void blacklistBelow(ContainerIdentifier id) override;\n\nprivate:\n  typedef std::list<IContainerMDChangeListener*> ListenerList;\n\n  //----------------------------------------------------------------------------\n  //! Set container accounting - no-op for this type of object since any type\n  //! of size accounting in done when manipulating files or using the\n  //! AddTree/RemoveTree interface of the continaer accounting view.\n  //----------------------------------------------------------------------------\n  void setContainerAccounting(IFileMDChangeListener* containerAccounting) override\n  {\n    return;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Notify the listeners about the change\n  //----------------------------------------------------------------------------\n  void notifyListeners(IContainerMD* obj, IContainerMDChangeListener::Action a)\n  override;\n\n  //----------------------------------------------------------------------------\n  //! Safety check to make sure there are no container entries in the backend\n  //! with ids bigger than the max container id. If there is any problem this\n  //! will throw an eos::MDException.\n  //----------------------------------------------------------------------------\n  void SafetyCheck();\n\n  ListenerList pListeners;              ///< List of listeners to be notified\n  IQuotaStats* pQuotaStats;             ///< Quota view\n  IFileMDSvc* pFileSvc;                 ///< File metadata service\n  qclient::QClient* pQcl;               ///< QClient object\n  MetadataFlusher* pFlusher;            ///< Metadata flusher object\n  //! Map holding metainfo about the namespace\n  qclient::QHash mMetaMap;\n  MetadataProvider* mMetadataProvider;  ///< Provider namespace metadata\n  UnifiedInodeProvider* mUnifiedInodeProvider; ///< Provide next free inode\n  std::atomic<uint64_t> mNumConts;      ///< Total number of containers\n  std::string\n  mCacheNum;                ///< Temporary workaround to store cache size\n};\n\nEOSNSNAMESPACE_END\n\n#endif // __EOS_CONTAINER_MD_SVC_HH__\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/FileMDSvc.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/persistency/FileMDSvc.hh\"\n#include \"common/StacktraceHere.hh\"\n#include \"namespace/ns_quarkdb/ConfigurationParser.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"namespace/ns_quarkdb/FileMD.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"namespace/ns_quarkdb/accounting/QuotaStats.hh\"\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n#include \"namespace/ns_quarkdb/persistency/ContainerMDSvc.hh\"\n#include \"namespace/ns_quarkdb/persistency/MetadataFetcher.hh\"\n#include \"namespace/ns_quarkdb/persistency/MetadataProvider.hh\"\n#include \"namespace/ns_quarkdb/persistency/RequestBuilder.hh\"\n#include \"namespace/utils/StringConvertion.hh\"\n#include <numeric>\n\nEOSNSNAMESPACE_BEGIN\n\nstd::chrono::seconds QuarkFileMDSvc::sFlushInterval(5);\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nQuarkFileMDSvc::QuarkFileMDSvc(qclient::QClient* qcl, MetadataFlusher* flusher)\n  : pQuotaStats(nullptr), pContSvc(nullptr), pFlusher(flusher),\n    pQcl(qcl), mMetaMap(), mNumFiles(0ull), mMetadataProvider(nullptr) {}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nQuarkFileMDSvc::~QuarkFileMDSvc()\n{\n  if (pFlusher) {\n    pFlusher->synchronize();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Configure the file service\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMDSvc::configure(const std::map<std::string, std::string>& config)\n{\n  std::string qdb_cluster;\n  std::string qdb_flusher_id;\n  const std::string key_flusher = \"qdb_flusher_md\";\n\n  if (config.find(key_flusher) != config.end()) {\n    // This should only be called once during booting but then the rest of the\n    // config values can be updated also while running\n    QdbContactDetails contactDetails = ConfigurationParser::parse(config);\n    mMetaMap.setKey(constants::sMapMetaInfoKey);\n    mMetaMap.setClient(*pQcl);\n    mUnifiedInodeProvider.configure(mMetaMap);\n    mMetadataProvider.reset(new MetadataProvider(contactDetails, pContSvc, this));\n    static_cast<QuarkContainerMDSvc*>(pContSvc)->setMetadataProvider\n    (mMetadataProvider.get());\n    static_cast<QuarkContainerMDSvc*>(pContSvc)->setInodeProvider\n    (&mUnifiedInodeProvider);\n  }\n\n  // Refresh the inode provider with the latest inode values from QDB\n  if (config.find(constants::sKeyInodeRefresh) != config.end()) {\n    mUnifiedInodeProvider.configure(mMetaMap);\n  }\n\n  if (config.find(constants::sMaxNumCacheFiles) != config.end()) {\n    std::string val = config.at(constants::sMaxNumCacheFiles);\n    mMetadataProvider->setFileMDCacheNum(std::stoull(val));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Initialize the file service\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMDSvc::initialize()\n{\n  if (pContSvc == nullptr) {\n    MDException e(EINVAL);\n    e.getMessage()  << __FUNCTION__ << \" FileMDSvc: container service not set\";\n    throw e;\n  }\n\n  if ((pQcl == nullptr) || (pFlusher == nullptr)) {\n    MDException e(EINVAL);\n    e.getMessage()  << __FUNCTION__ << \" No qclient/flusher initialized for \"\n                    << \"the container metadata service\";\n    throw e;\n  }\n\n  SafetyCheck();\n  mNumFiles.store(pQcl->execute(\n                    RequestBuilder::getNumberOfFiles()).get()->integer);\n}\n\n//------------------------------------------------------------------------------\n// Safety check to make sure there are no file entries in the backend with\n// ids bigger than the max file id.\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMDSvc::SafetyCheck()\n{\n  std::string blob;\n  IFileMD::id_t free_id = getFirstFreeId();\n  std::vector<uint64_t> offsets {1, 10, 50, 100, 501, 1001, 11000, 50000,\n                                 100000, 150199, 200001, 1000002, 2000123 };\n  std::vector<folly::Future<eos::ns::FileMdProto>> futs;\n\n  for (auto incr : offsets) {\n    IFileMD::id_t check_id = free_id + incr;\n    futs.emplace_back(MetadataFetcher::getFileFromId(*pQcl,\n                      FileIdentifier(check_id)));\n  }\n\n  for (size_t i = 0; i < futs.size(); i++) {\n    try {\n      std::move(futs[i]).get();\n    } catch (eos::MDException& qdb_err) {\n      // All is good, we didn't find any file, as expected\n      continue;\n    }\n\n    // Uh-oh, this is bad.\n    MDException e(EEXIST);\n    e.getMessage()  << __FUNCTION__ << \" FATAL: Risk of data loss, found \"\n                    << \"file (\" << free_id + offsets[i] << \") with id bigger than max file id (\" <<\n                    free_id << \")\";\n    throw e;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get the file metadata information for the given file id - asynchronous API.\n//------------------------------------------------------------------------------\nfolly::Future<IFileMDPtr>\nQuarkFileMDSvc::getFileMDFut(IFileMD::id_t id)\n{\n  return mMetadataProvider->retrieveFileMD(FileIdentifier(id));\n}\n\n//------------------------------------------------------------------------------\n// Get the file metadata information for the given file id\n//------------------------------------------------------------------------------\nstd::shared_ptr<IFileMD>\nQuarkFileMDSvc::getFileMD(IFileMD::id_t id, uint64_t* clock)\n{\n  IFileMDPtr file = mMetadataProvider->retrieveFileMD(FileIdentifier(id)).get();\n\n  if (file && clock) {\n    *clock = file->getClock();\n  }\n\n  return file;\n}\n\n//------------------------------------------------------------------------------\n//! Check if a FileMD with a given identifier exists\n//------------------------------------------------------------------------------\nfolly::Future<bool>\nQuarkFileMDSvc::hasFileMD(const eos::FileIdentifier id)\n{\n  return mMetadataProvider->hasFileMD(id);\n}\n\n//----------------------------------------------------------------------------\n// Drop cached FileMD - return true if found\n//----------------------------------------------------------------------------\nbool\nQuarkFileMDSvc::dropCachedFileMD(FileIdentifier id)\n{\n  return mMetadataProvider->dropCachedFileID(id);\n}\n\n//------------------------------------------------------------------------------\n// Create new file metadata object\n//------------------------------------------------------------------------------\nstd::shared_ptr<IFileMD>\nQuarkFileMDSvc::createFile(IFileMD::id_t id)\n{\n  uint64_t free_id;\n\n  if (id > 0) {\n    mUnifiedInodeProvider.blacklistFileId(id);\n    free_id = id;\n  } else {\n    free_id = mUnifiedInodeProvider.reserveFileId();\n  }\n\n  std::shared_ptr<IFileMD> file{new QuarkFileMD(free_id, this)};\n  mMetadataProvider->insertFileMD(file->getIdentifier(), file);\n  IFileMDChangeListener::Event e(file.get(), IFileMDChangeListener::Created);\n  notifyListeners(&e);\n  ++mNumFiles;\n  return file;\n}\n\n//------------------------------------------------------------------------------\n// Update backend store and notify all the listeners\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMDSvc::updateStore(IFileMD* obj)\n{\n  if (obj->getName() == \"\") {\n    eos_static_crit(\"updateFileStore called on file with empty name; id=%llu, parent=%llu, trace=%s\",\n                    obj->getId(), obj->getContainerId(), common::getStacktrace().c_str());\n    // eventually throw, once we understand how this happens\n    // TODO: @ccaffy - this should throw an exception, but\n    // all the code calling this method should handle it properly\n    // by surrounding the calls with try/catch blocks\n    return;\n  }\n\n  pFlusher->execute(RequestBuilder::writeFileProto(obj));\n\n  // If file is detached then add it to the list of orphans\n  if (obj->getContainerId() == 0) {\n    pFlusher->sadd(constants::sOrphanFiles, stringify(obj->getId()));\n  }\n}\n\n//------------------------------------------------------------------------\n//! Remove object from the store, you should write lock the file before calling this\n//------------------------------------------------------------------------\nvoid\nQuarkFileMDSvc::removeFile(IFileMD* obj)\n{\n  std::string sid = stringify(obj->getId());\n  pFlusher->execute(RequestBuilder::deleteFileProto(FileIdentifier(\n                      obj->getId())));\n  pFlusher->srem(constants::sOrphanFiles, sid);\n  IFileMDChangeListener::Event e(obj, IFileMDChangeListener::Deleted);\n  notifyListeners(&e);\n  obj->setDeleted();\n\n  if (mNumFiles) {\n    --mNumFiles;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get number of files\n//------------------------------------------------------------------------------\nuint64_t\nQuarkFileMDSvc::getNumFiles()\n{\n  return mNumFiles.load();\n}\n\n//------------------------------------------------------------------------------\n// Add file listener\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMDSvc::addChangeListener(IFileMDChangeListener* listener)\n{\n  pListeners.push_back(listener);\n}\n\n//------------------------------------------------------------------------------\n// Notify the listeners about the change\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMDSvc::notifyListeners(IFileMDChangeListener::Event* event)\n{\n  for (const auto& elem : pListeners) {\n    elem->fileMDChanged(event);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Set container service\n//------------------------------------------------------------------------------\nvoid\nQuarkFileMDSvc::setContMDService(IContainerMDSvc* cont_svc)\n{\n  QuarkContainerMDSvc* impl_cont_svc = dynamic_cast<eos::QuarkContainerMDSvc*>\n                                       (cont_svc);\n\n  if (!impl_cont_svc) {\n    MDException e(EFAULT);\n    e.getMessage() << __FUNCTION__ << \" ContainerMDSvc dynamic cast failed\";\n    throw e;\n  }\n\n  pContSvc = impl_cont_svc;\n}\n\n//------------------------------------------------------------------------------\n// Get first free file id\n//------------------------------------------------------------------------------\nIFileMD::id_t\nQuarkFileMDSvc::getFirstFreeId()\n{\n  return mUnifiedInodeProvider.getFirstFreeFileId();\n}\n\n//------------------------------------------------------------------------------\n//! Retrieve MD cache statistics.\n//------------------------------------------------------------------------------\nCacheStatistics QuarkFileMDSvc::getCacheStatistics()\n{\n  return mMetadataProvider->getFileMDCacheStats();\n}\n\n//------------------------------------------------------------------------------\n// Blacklist IDs below the given threshold\n//------------------------------------------------------------------------------\nvoid QuarkFileMDSvc::blacklistBelow(FileIdentifier id)\n{\n  mUnifiedInodeProvider.blacklistFileId(id.getUnderlyingUInt64());\n}\n\n//------------------------------------------------------------------------------\n// Get pointer to metadata provider\n//------------------------------------------------------------------------------\nMetadataProvider* QuarkFileMDSvc::getMetadataProvider()\n{\n  return mMetadataProvider.get();\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/FileMDSvc.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief File MD service based on quarkdb\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_FILE_MD_SVC_HH\n#define EOS_NS_FILE_MD_SVC_HH\n\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"namespace/ns_quarkdb/persistency/NextInodeProvider.hh\"\n#include \"namespace/ns_quarkdb/persistency/UnifiedInodeProvider.hh\"\n#include \"qclient/structures/QHash.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\nclass IQuotaStats;\nclass MetadataFlusher;\nclass MetadataProvider;\n\n//------------------------------------------------------------------------------\n//! FileMDSvc based on Redis\n//------------------------------------------------------------------------------\nclass QuarkFileMDSvc : public IFileMDSvc\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  QuarkFileMDSvc(qclient::QClient *qcl, MetadataFlusher *flusher);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~QuarkFileMDSvc();\n\n  //----------------------------------------------------------------------------\n  //! Initialize the file service\n  //----------------------------------------------------------------------------\n  virtual void initialize() override;\n\n  //----------------------------------------------------------------------------\n  //! Configure the file service\n  //!\n  //! @param config map holding configuration parameters\n  //----------------------------------------------------------------------------\n  virtual void configure(const std::map<std::string, std::string>& config)\n  override;\n\n  //----------------------------------------------------------------------------\n  //! Finalize the file service\n  //----------------------------------------------------------------------------\n  virtual void finalize() override {};\n\n  //----------------------------------------------------------------------------\n  //! Get the file metadata information for the given file ID - asynchronous\n  //! API.\n  //----------------------------------------------------------------------------\n  virtual folly::Future<IFileMDPtr> getFileMDFut(IFileMD::id_t id) override;\n\n  //----------------------------------------------------------------------------\n  //! Get the file metadata information for the given file ID\n  //!\n  //! @param id file id\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IFileMD> getFileMD(IFileMD::id_t id) override\n  {\n    return getFileMD(id, 0);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the file metadata information for the given file ID and clock\n  //! value\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IFileMD> getFileMD(IFileMD::id_t id,\n      uint64_t* clock) override;\n\n  //----------------------------------------------------------------------------\n  //! Check if a FileMD with a given identifier exists\n  //----------------------------------------------------------------------------\n  virtual folly::Future<bool> hasFileMD(const eos::FileIdentifier id) override;\n\n  //----------------------------------------------------------------------------\n  //! Drop cached FileMD - return true if found\n  //----------------------------------------------------------------------------\n  virtual bool dropCachedFileMD(FileIdentifier id) override;\n\n  //----------------------------------------------------------------------------\n  //! Create new file metadata object with an assigned id\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IFileMD> createFile(IFileMD::id_t id = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Update the file metadata in the backing store after the FileMD object\n  //! has been changed\n  //----------------------------------------------------------------------------\n  virtual void updateStore(IFileMD* obj) override;\n\n  //------------------------------------------------------------------------\n  //! Remove object from the store, you should write lock the file before calling this\n  //------------------------------------------------------------------------\n  virtual void removeFile(IFileMD* obj) override;\n\n  //----------------------------------------------------------------------------\n  //! Get number of files\n  //----------------------------------------------------------------------------\n  virtual uint64_t getNumFiles() override;\n\n  //----------------------------------------------------------------------------\n  //! Add file listener that will be notified about all of the changes in\n  //! the store\n  //----------------------------------------------------------------------------\n  virtual void addChangeListener(IFileMDChangeListener* listener) override;\n\n  //----------------------------------------------------------------------------\n  //! Notify the listeners about the change\n  //----------------------------------------------------------------------------\n  virtual void notifyListeners(IFileMDChangeListener::Event* event) override;\n\n  //----------------------------------------------------------------------------\n  //! Set container service\n  //!\n  //! @param cont_svc container service\n  //----------------------------------------------------------------------------\n  void setContMDService(IContainerMDSvc* cont_svc) override;\n\n  //----------------------------------------------------------------------------\n  //! Set the QuotaStats object for the follower\n  //!\n  //! @param quota_stats object implementing the IQuotaStats interface\n  //----------------------------------------------------------------------------\n  void setQuotaStats(IQuotaStats* quota_stats) override\n  {\n    pQuotaStats = quota_stats;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Visit all the files\n  //----------------------------------------------------------------------------\n  void visit(IFileVisitor* visitor) override {};\n\n  //----------------------------------------------------------------------------\n  //! Get first free file id\n  //----------------------------------------------------------------------------\n  IFileMD::id_t getFirstFreeId() override;\n\n  //----------------------------------------------------------------------------\n  //! Retrieve MD cache statistics.\n  //----------------------------------------------------------------------------\n  virtual CacheStatistics getCacheStatistics() override;\n\n  //----------------------------------------------------------------------------\n  //! Blacklist IDs below the given threshold\n  //----------------------------------------------------------------------------\n  virtual void blacklistBelow(FileIdentifier id) override;\n\n  //----------------------------------------------------------------------------\n  //! Get pointer to metadata provider\n  //----------------------------------------------------------------------------\n  MetadataProvider* getMetadataProvider();\n\n\nprivate:\n  typedef std::list<IFileMDChangeListener*> ListenerList;\n  //! Interval for backend flush of consistent file ids\n  static std::chrono::seconds sFlushInterval;\n\n //----------------------------------------------------------------------------\n  //! Safety check to make sure there are nofile entries in the backend with\n  //! ids bigger than the max file id. If there is any problem this will throw\n  //! an eos::MDException.\n  //----------------------------------------------------------------------------\n  void SafetyCheck();\n\n  ListenerList pListeners; ///< List of listeners to notify of changes\n  IQuotaStats* pQuotaStats; ///< Quota view\n  IContainerMDSvc* pContSvc; ///< Container metadata service\n  MetadataFlusher* pFlusher = nullptr; ///< Metadata flusher object\n  qclient::QClient* pQcl; ///< QClient object\n  qclient::QHash mMetaMap ; ///< Map holding metainfo about the namespace\n  std::atomic<uint64_t> mNumFiles; ///< Total number of fileso\n  std::unique_ptr<MetadataProvider>\n  mMetadataProvider; ///< Provides metadata from backend\n  UnifiedInodeProvider mUnifiedInodeProvider; ///< Provides next free inode\n};\n\nEOSNSNAMESPACE_END\n\n#endif // __EOS_NS_FILE_MD_SVC_HH__\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/FileSystemIterator.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Class to iterate through all available FileSystems, as found in\n//!        the namespace\n//------------------------------------------------------------------------------\n\n#include \"namespace/ns_quarkdb/persistency/FileSystemIterator.hh\"\n#include \"common/StringTokenizer.hh\"\n#include \"common/Logging.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFileSystemIterator::FileSystemIterator(qclient::QClient &qcl)\n: mScanner(qcl, \"fsview:*:*\") {\n\n  while(mScanner.valid() && !parseScannerKey()) {\n    mScanner.next();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get next filesystem ID\n//------------------------------------------------------------------------------\nIFileMD::location_t FileSystemIterator::getFileSystemID() const {\n  return mFilesystemID;\n}\n\n//------------------------------------------------------------------------------\n// What is the raw redis key this element is referring to?\n//------------------------------------------------------------------------------\nstd::string FileSystemIterator::getRedisKey() const {\n  return mRedisKey;\n}\n\n//------------------------------------------------------------------------------\n// Is this item referring to the unlinked view?\n//------------------------------------------------------------------------------\nbool FileSystemIterator::isUnlinked() const {\n  return mIsUnlinked;\n}\n\n//------------------------------------------------------------------------------\n// Is the iterator object valid?\n//------------------------------------------------------------------------------\nbool FileSystemIterator::valid() const {\n  return mScanner.valid();\n}\n\n//------------------------------------------------------------------------------\n// Advance iterator\n//------------------------------------------------------------------------------\nvoid FileSystemIterator::next() {\n  mScanner.next();\n  while(mScanner.valid() && !parseScannerKey()) {\n    mScanner.next();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Parse element that mScanner currently points to - return false on parse\n// error\n//------------------------------------------------------------------------------\nbool FileSystemIterator::parseScannerKey() {\n  bool ok = rawParseScannerKey();\n\n  if(!ok) {\n    eos_static_crit(\"Could not parse fsview redis key in FileSystemIterator: %s\", mRedisKey.c_str());\n  }\n\n  return ok;\n}\n\nbool FileSystemIterator::rawParseScannerKey() {\n  mRedisKey = mScanner.getValue();\n\n  std::vector<std::string> parts =\n    eos::common::StringTokenizer::split<std::vector<std::string>>(mRedisKey, ':');\n\n  if (parts.size() != 3) {\n    return false;\n  }\n\n  if (parts[0] != \"fsview\") {\n    return false;\n  }\n\n  mFilesystemID = std::stoull(parts[1]);\n\n  if (parts[2] == \"files\") {\n    mIsUnlinked = false;\n  } else if (parts[2] == \"unlinked\") {\n    mIsUnlinked = true;\n  } else {\n    return false;\n  }\n\n  return true;\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/FileSystemIterator.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Class to iterate through all available FileSystems, as found in\n//!        the namespace\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include <qclient/structures/QScanner.hh>\n\nnamespace qclient {\n  class QClient;\n}\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! FileSystemIterator class\n//------------------------------------------------------------------------------\nclass FileSystemIterator {\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  FileSystemIterator(qclient::QClient &qcl);\n\n  //----------------------------------------------------------------------------\n  //! Get next filesystem ID\n  //----------------------------------------------------------------------------\n  IFileMD::location_t getFileSystemID() const;\n\n  //----------------------------------------------------------------------------\n  //! Is this referring to the unlinked view?\n  //----------------------------------------------------------------------------\n  bool isUnlinked() const;\n\n  //----------------------------------------------------------------------------\n  //! What is the raw redis key this element is referring to?\n  //----------------------------------------------------------------------------\n  std::string getRedisKey() const;\n\n  //----------------------------------------------------------------------------\n  //! Is the iterator object valid?\n  //----------------------------------------------------------------------------\n  bool valid() const;\n\n  //----------------------------------------------------------------------------\n  //! Advance iterator\n  //----------------------------------------------------------------------------\n  void next();\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Parse element that mScanner currently points to - return false on parse\n  //! error\n  //----------------------------------------------------------------------------\n  bool parseScannerKey();\n  bool rawParseScannerKey();\n\n  qclient::QScanner mScanner;\n\n  std::string mRedisKey;\n  IFileMD::location_t mFilesystemID;\n  bool mIsUnlinked;\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/MetadataFetcher.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Class to retrieve metadata from the backend - no caching!\n//------------------------------------------------------------------------------\n\n#include <functional>\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/ns_quarkdb/persistency/MetadataFetcher.hh\"\n#include \"namespace/ns_quarkdb/persistency/ContainerMDSvc.hh\"\n#include \"namespace/ns_quarkdb/persistency/FileMDSvc.hh\"\n#include \"namespace/ns_quarkdb/persistency/Serialization.hh\"\n#include \"namespace/ns_quarkdb/persistency/RequestBuilder.hh\"\n#include \"namespace/utils/PathProcessor.hh\"\n#include \"qclient/QClient.hh\"\n\n#define SSTR(message) static_cast<std::ostringstream&>(std::ostringstream().flush() << message).str()\n#define DBG(message) std::cerr << __FILE__ << \":\" << __LINE__ << \" -- \" \\\n  << #message << \" = \" << message << std::endl\n\nusing redisReplyPtr = qclient::redisReplyPtr;\nusing std::placeholders::_1;\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Helper method to check that the redis reply is a string reply\n//!\n//! @param reply redis reply\n//------------------------------------------------------------------------------\nMDStatus ensureStringReply(redisReplyPtr& reply)\n{\n  if (!reply) {\n    return MDStatus(EFAULT, \"QuarkDB backend not available!\");\n  }\n\n  if ((reply->type == REDIS_REPLY_NIL) ||\n      (reply->type == REDIS_REPLY_STRING && reply->len == 0)) {\n    return MDStatus(ENOENT, \"Empty response\");\n  }\n\n  if (reply->type != REDIS_REPLY_STRING) {\n    return MDStatus(EFAULT,\n                    SSTR(\"Received unexpected response, was expecting string: \"\n                         << qclient::describeRedisReply(reply)));\n  }\n\n  return MDStatus();\n}\n\n//------------------------------------------------------------------------------\n// Helper method to check that a redis reply is 0/1\n//------------------------------------------------------------------------------\nMDStatus ensureBoolReply(redisReplyPtr& reply)\n{\n  if (!reply) {\n    return MDStatus(EFAULT, \"QuarkDB backend not available!\");\n  }\n\n  if (reply->type != REDIS_REPLY_INTEGER) {\n    return MDStatus(EFAULT,\n                    SSTR(\"Received unexpected response, was expecting integer: \"\n                         << qclient::describeRedisReply(reply)));\n  }\n\n  if (reply->integer != 0 && reply->integer != 1) {\n    return MDStatus(EFAULT,\n                    SSTR(\"Received unexpected integer, was expecting {0,1}: \"\n                         << qclient::describeRedisReply(reply)));\n  }\n\n  return MDStatus();\n}\n\n//------------------------------------------------------------------------------\n// Helper method to check that a redis reply is uint64_t\n//------------------------------------------------------------------------------\nMDStatus ensureUInt64Reply(redisReplyPtr& reply)\n{\n  if (!reply) {\n    return MDStatus(EFAULT, \"QuarkDB backend not available!\");\n  }\n\n  if (reply->type != REDIS_REPLY_INTEGER) {\n    return MDStatus(EFAULT,\n                    SSTR(\"Received unexpected response, was expecting integer: \"\n                         << qclient::describeRedisReply(reply)));\n  }\n\n  if (reply->integer < 0) {\n    return MDStatus(EFAULT,\n                    SSTR(\"Received unexpected value, was expecting a uint64_t: \"\n                         << qclient::describeRedisReply(reply)));\n  }\n\n  return MDStatus();\n}\n\n//------------------------------------------------------------------------------\n//! Struct MapFetcherFileTrait\n//------------------------------------------------------------------------------\nstruct MapFetcherFileTrait {\n  static std::string getKey(IContainerMD::id_t id)\n  {\n    return SSTR(id << constants::sMapFilesSuffix);\n  }\n\n  using ContainerType = IContainerMD::FileMap;\n};\n\n//------------------------------------------------------------------------------\n//! Struct MapFetcherContainerTrait\n//------------------------------------------------------------------------------\nstruct MapFetcherContainerTrait {\n  static std::string getKey(IFileMD::id_t id)\n  {\n    return SSTR(id << constants::sMapDirsSuffix);\n  }\n\n  using ContainerType = IContainerMD::ContainerMap;\n};\n\n\n//------------------------------------------------------------------------------\n//! Class MapFetcher - fetches maps (ContainerMap, FileMap) of a particular\n//! container.\n//------------------------------------------------------------------------------\ntemplate<typename Trait>\nclass MapFetcher : public qclient::QCallback\n{\npublic:\n  using ContainerType = typename Trait::ContainerType;\n  static constexpr size_t kCount = 250000;\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  MapFetcher()\n  {\n    mContents.set_deleted_key(\"\");\n    mContents.set_empty_key(\"##_EMPTY_##\");\n  }\n\n  //----------------------------------------------------------------------------\n  //! Initialize\n  //!\n  //! @param qcl qclient object\n  //! @param trg target container id\n  //----------------------------------------------------------------------------\n  folly::Future<ContainerType> initialize(qclient::QClient& qcl,\n                                          ContainerIdentifier trg)\n  {\n    mQcl = &qcl;\n    mTarget = trg;\n    folly::Future<ContainerType> fut = mPromise.getFuture();\n    // There's a particularly evil race condition here: From the point we call\n    // execCB and onwards, we must assume that the callback has arrived,\n    // and *this has been destroyed already.\n    // Not safe to access member variables after execCB, so we fetch the future\n    // beforehand.\n    mQcl->execCB(this, \"HSCAN\", Trait::getKey(mTarget.getUnderlyingUInt64()), \"0\",\n                 \"COUNT\", SSTR(kCount));\n    // fut is a stack object, safe to access.\n    return fut;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Handle response\n  //!\n  //! @param reply holds a redis reply object\n  //----------------------------------------------------------------------------\n  virtual void handleResponse(redisReplyPtr&& reply) override\n  {\n    if (!reply) {\n      return set_exception(EFAULT, \"QuarkDB backend not available!\");\n    }\n\n    if (reply->type != REDIS_REPLY_ARRAY || reply->elements != 2 ||\n        (reply->element[0]->type != REDIS_REPLY_STRING) ||\n        (reply->element[1]->type != REDIS_REPLY_ARRAY) ||\n        ((reply->element[1]->elements % 2) != 0)) {\n      return set_exception(EFAULT, SSTR(\"Received unexpected response: \"\n                                        << qclient::describeRedisReply(reply)));\n    }\n\n    std::string cursor = std::string(reply->element[0]->str,\n                                     reply->element[0]->len);\n\n    for (size_t i = 0; i < reply->element[1]->elements; i += 2) {\n      redisReply* element = reply->element[1]->element[i];\n\n      if (element->type != REDIS_REPLY_STRING) {\n        return set_exception(EFAULT, SSTR(\"Received unexpected response: \"\n                                          << qclient::describeRedisReply(reply)));\n      }\n\n      std::string filename = std::string(element->str, element->len);\n      element = reply->element[1]->element[i + 1];\n\n      if (element->type != REDIS_REPLY_STRING) {\n        return set_exception(EFAULT, SSTR(\"Received unexpected response: \"\n                                          << qclient::describeRedisReply(reply)));\n      }\n\n      int64_t value;\n      MDStatus st = Serialization::deserialize(element->str, element->len, value);\n\n      if (!st.ok()) {\n        return set_exception(st);\n      }\n\n      mContents.insert(std::make_pair(filename, value));\n    }\n\n    // Fire off next request?\n    if (cursor == \"0\") {\n      mPromise.setValue(std::move(mContents));\n      delete this;\n      return;\n    }\n\n    mQcl->execCB(this, \"HSCAN\", Trait::getKey(mTarget.getUnderlyingUInt64()),\n                 cursor, \"COUNT\",\n                 SSTR(kCount));\n  }\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Return exception by passing it to the promise\n  //!\n  //! @param status error return status\n  //----------------------------------------------------------------------------\n  void set_exception(const MDStatus& status)\n  {\n    return set_exception(status.getErrno(), status.getError());\n  }\n\n  //----------------------------------------------------------------------------\n  //! Return exception by passing it to the promise\n  //!\n  //! @param status error return status\n  //----------------------------------------------------------------------------\n  void set_exception(int err, const std::string& msg)\n  {\n    mPromise.setException(\n      make_mdexception(err, SSTR(\"Error while fetching file/container map for \"\n                                 \"container #\" << mTarget.getUnderlyingUInt64() << \" from QDB: \"\n                                 << msg)));\n    delete this; // harakiri\n  }\n\n  qclient::QClient* mQcl;\n  ContainerIdentifier mTarget;\n  ContainerType mContents;\n  folly::Promise<ContainerType> mPromise;\n};\n\n//------------------------------------------------------------------------------\n// Parse FileMDProto from a redis response, throw on error.\n//------------------------------------------------------------------------------\nstatic eos::ns::FileMdProto\nparseFileMdProtoResponse(redisReplyPtr reply, FileIdentifier id)\n{\n  ensureStringReply(reply).throwIfNotOk(SSTR(\"Error while fetching FileMD #\"\n                                        << id.getUnderlyingUInt64()\n                                        << \" protobuf from QDB: \"));\n  eos::ns::FileMdProto proto;\n  Serialization::deserialize(reply->str, reply->len, proto)\n  .throwIfNotOk(SSTR(\"Error while deserializing FileMD #\"\n                     << id.getUnderlyingUInt64()\n                     << \" protobuf: \"));\n  return proto;\n}\n\n//------------------------------------------------------------------------------\n// Fetch file metadata info for current id\n//------------------------------------------------------------------------------\nfolly::Future<eos::ns::FileMdProto>\nMetadataFetcher::getFileFromId(qclient::QClient& qcl, FileIdentifier id)\n{\n  return qcl.follyExec(RequestBuilder::readFileProto(id))\n         .thenValue(std::bind(parseFileMdProtoResponse, _1, id));\n}\n\n//----------------------------------------------------------------------------\n// Fetch file metadata info for current id\n//------------------------------------------------------------------------------\nstatic bool\ncheckFileMdProtoExistence(redisReplyPtr reply, FileIdentifier id)\n{\n  MDStatus st = ensureStringReply(reply);\n\n  if (st.getErrno() == ENOENT) {\n    return false;\n  }\n\n  st.throwIfNotOk(SSTR(\"Error while fetching FileMD #\"\n                       << id.getUnderlyingUInt64()\n                       << \" protobuf from QDB: \"));\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Fetch container metadata info for current id\n//------------------------------------------------------------------------------\nstatic bool\ncheckContainerMdProtoExistence(redisReplyPtr reply, ContainerIdentifier id)\n{\n  MDStatus st = ensureStringReply(reply);\n\n  if (st.getErrno() == ENOENT) {\n    return false;\n  }\n\n  st.throwIfNotOk(SSTR(\"Error while fetching ContainerMD #\"\n                       << id.getUnderlyingUInt64()\n                       << \" protobuf from QDB: \"));\n  return true;\n}\n\n//----------------------------------------------------------------------------\n// Check if given container id exists on the namespace\n//----------------------------------------------------------------------------\nfolly::Future<bool>\nMetadataFetcher::doesContainerMdExist(qclient::QClient& qcl,\n                                      ContainerIdentifier id)\n{\n  return qcl.follyExec(RequestBuilder::readContainerProto(id))\n         .thenValue(std::bind(checkContainerMdProtoExistence, _1, id));\n}\n\n//----------------------------------------------------------------------------\n// Check if given file id exists on the namespace\n//----------------------------------------------------------------------------\nfolly::Future<bool>\nMetadataFetcher::doesFileMdExist(qclient::QClient& qcl, FileIdentifier id)\n{\n  return qcl.follyExec(RequestBuilder::readFileProto(id))\n         .thenValue(std::bind(checkFileMdProtoExistence, _1, id));\n}\n\n//------------------------------------------------------------------------------\n// Parse ContainerMdProto from a redis response, throw on error.\n//------------------------------------------------------------------------------\nstatic eos::ns::ContainerMdProto\nparseContainerMdProtoResponse(redisReplyPtr reply, ContainerIdentifier id)\n{\n  ensureStringReply(reply).throwIfNotOk(SSTR(\"Error while fetching ContainerMD #\"\n                                        << id.getUnderlyingUInt64()\n                                        << \" protobuf from QDB: \"));\n  eos::ns::ContainerMdProto proto;\n  Serialization::deserialize(reply->str, reply->len, proto)\n  .throwIfNotOk(SSTR(\"Error while deserializing ContainerMd #\"\n                     << id.getUnderlyingUInt64()\n                     << \" protobuf: \"));\n  return proto;\n}\n\n//------------------------------------------------------------------------------\n// Fetch container metadata info for current id\n//------------------------------------------------------------------------------\nfolly::Future<eos::ns::ContainerMdProto>\nMetadataFetcher::getContainerFromId(qclient::QClient& qcl,\n                                    ContainerIdentifier id)\n{\n  return qcl.follyExec(RequestBuilder::readContainerProto(id))\n         .thenValue(std::bind(parseContainerMdProtoResponse, _1, id));\n}\n\n//------------------------------------------------------------------------------\n// Class MetadataFetcher\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Construct hmap key of subcontainers in container\n//------------------------------------------------------------------------------\nstd::string MetadataFetcher::keySubContainers(IContainerMD::id_t id)\n{\n  return SSTR(id << constants::sMapDirsSuffix);\n}\n\n//------------------------------------------------------------------------------\n// Construct hmap key of files in container\n//------------------------------------------------------------------------------\nstd::string MetadataFetcher::keySubFiles(IContainerMD::id_t id)\n{\n  return SSTR(id << constants::sMapFilesSuffix);\n}\n\n//------------------------------------------------------------------------------\n// Fetch all files for current id\n//------------------------------------------------------------------------------\nfolly::Future<IContainerMD::FileMap>\nMetadataFetcher::getFileMap(qclient::QClient& qcl,\n                            ContainerIdentifier container)\n{\n  MapFetcher<MapFetcherFileTrait>* fetcher = new\n  MapFetcher<MapFetcherFileTrait>();\n  return fetcher->initialize(qcl, container);\n}\n\n//------------------------------------------------------------------------------\n// Fetch all file metadata within the given container.\n//------------------------------------------------------------------------------\nfolly::Future<std::vector<folly::Future<eos::ns::FileMdProto>>>\nMetadataFetcher::getFileMDsInContainer(qclient::QClient& qcl,\n                                       ContainerIdentifier container, folly::Executor* executor)\n{\n  folly::Future<IContainerMD::FileMap> filemap = getFileMap(qcl, container);\n  return filemap.via(executor)\n         .thenValue(std::bind(MetadataFetcher::getFilesFromFilemapV, std::ref(qcl), _1));\n}\n\n//----------------------------------------------------------------------------\n// Fetch all container metadata within the given container.\n//----------------------------------------------------------------------------\nfolly::Future<std::vector<folly::Future<eos::ns::ContainerMdProto>>>\nMetadataFetcher::getContainerMDsInContainer(qclient::QClient& qcl,\n    ContainerIdentifier container, folly::Executor* executor)\n{\n  folly::Future<IContainerMD::ContainerMap> containerMap =\n    getContainerMap(qcl, container);\n  return containerMap.via(executor)\n         .thenValue(std::bind(MetadataFetcher::getContainersFromContainerMapV,\n                              std::ref(qcl), _1));\n}\n\n//------------------------------------------------------------------------------\n// Fetch all FileMDs contained within the given FileMap. Vector is sorted by\n// filename.\n//------------------------------------------------------------------------------\nstd::vector<folly::Future<eos::ns::FileMdProto>>\n    MetadataFetcher::getFilesFromFilemap(qclient::QClient& qcl,\n        const IContainerMD::FileMap& fileMap)\n{\n  // FileMap is a hashmap, thus unsorted.. We want the results to be sorted\n  // based on filename, though.\n  std::map<std::string, IFileMD::id_t> sortedFileMap;\n\n  for (auto it = fileMap.begin(); it != fileMap.end(); ++it) {\n    sortedFileMap[it->first] = it->second;\n  }\n\n  std::vector<folly::Future<eos::ns::FileMdProto>> retval;\n\n  for (auto it = sortedFileMap.begin(); it != sortedFileMap.end(); it++) {\n    retval.emplace_back(getFileFromId(qcl, FileIdentifier(it->second)));\n  }\n\n  return retval;\n}\n\n//------------------------------------------------------------------------------\n// Same as above, but fileMap is passed as a value.\n//------------------------------------------------------------------------------\nstd::vector<folly::Future<eos::ns::FileMdProto>>\n    MetadataFetcher::getFilesFromFilemapV(qclient::QClient& qcl,\n        IContainerMD::FileMap fileMap)\n{\n  return getFilesFromFilemap(qcl, fileMap);\n}\n\n//------------------------------------------------------------------------------\n// Fetch all ContainerMDs contained within the given ContainerMap.\n// Vector is sorted by filename.\n//------------------------------------------------------------------------------\nstd::vector<folly::Future<eos::ns::ContainerMdProto>>\n    MetadataFetcher::getContainersFromContainerMap(qclient::QClient& qcl,\n        const IContainerMD::ContainerMap& containerMap)\n{\n  // ContainerMap is a hashmap, thus unsorted.. We want the results to be\n  // sorted based on filename, though.\n  std::map<std::string , IContainerMD::id_t> sortedContainerMap;\n\n  for (auto it = containerMap.begin(); it != containerMap.end(); ++it) {\n    sortedContainerMap[it->first] = it->second;\n  }\n\n  std::vector<folly::Future<eos::ns::ContainerMdProto>> retval;\n\n  for (auto it = sortedContainerMap.cbegin(); it != sortedContainerMap.cend();\n       it++) {\n    retval.emplace_back(getContainerFromId(qcl, ContainerIdentifier(it->second)));\n  }\n\n  return retval;\n}\n\n//------------------------------------------------------------------------------\n// Same as above, but containerMap is passed as a value.\n//------------------------------------------------------------------------------\nstd::vector<folly::Future<eos::ns::ContainerMdProto>>\n    MetadataFetcher::getContainersFromContainerMapV(qclient::QClient& qcl,\n        IContainerMD::ContainerMap containerMap)\n{\n  return getContainersFromContainerMap(qcl, containerMap);\n}\n\n//------------------------------------------------------------------------------\n// Fetch all subcontainers for current id\n//------------------------------------------------------------------------------\nfolly::Future<IContainerMD::ContainerMap>\nMetadataFetcher::getContainerMap(qclient::QClient& qcl,\n                                 ContainerIdentifier container)\n{\n  MapFetcher<MapFetcherContainerTrait>* fetcher =\n    new MapFetcher<MapFetcherContainerTrait>();\n  return fetcher->initialize(qcl, container);\n}\n\n//------------------------------------------------------------------------------\n// Parse response when looking up a ContainerID / FileID from (parent id, name)\n//------------------------------------------------------------------------------\nstatic int64_t parseIDFromNameResponse(redisReplyPtr reply,\n                                       ContainerIdentifier parentID, const std::string& name)\n{\n  std::string errorPrefix =\n    SSTR(\"Error while fetching FileID / ContainerID out of (parent id, name) = \"\n         \"(\" << parentID.getUnderlyingUInt64() << \", \" << name << \"): \");\n  ensureStringReply(reply).throwIfNotOk(errorPrefix);\n  int64_t retval;\n  Serialization::deserialize(reply->str, reply->len, retval)\n  .throwIfNotOk(errorPrefix);\n  return retval;\n}\n\n//------------------------------------------------------------------------------\n// int64_t -> FileIdentifier\n//------------------------------------------------------------------------------\nstatic FileIdentifier convertInt64ToFileIdentifier(int64_t id)\n{\n  return FileIdentifier(id);\n}\n\n//------------------------------------------------------------------------------\n// int64_t -> ContainerIdentifier\n//------------------------------------------------------------------------------\nstatic ContainerIdentifier convertInt64ToContainerIdentifier(int64_t id)\n{\n  return ContainerIdentifier(id);\n}\n\n//------------------------------------------------------------------------------\n// Fetch a file id given its parent and its name\n//------------------------------------------------------------------------------\nfolly::Future<FileIdentifier>\nMetadataFetcher::getFileIDFromName(qclient::QClient& qcl,\n                                   ContainerIdentifier parent_id,\n                                   const std::string& name)\n{\n  return qcl.follyExec(\"HGET\",\n                       SSTR(parent_id.getUnderlyingUInt64() << constants::sMapFilesSuffix), name)\n         .thenValue(std::bind(parseIDFromNameResponse, _1, parent_id, name))\n         .thenValue(convertInt64ToFileIdentifier);\n}\n\n//------------------------------------------------------------------------------\n// Fetch a container id given its parent and its name\n//------------------------------------------------------------------------------\nfolly::Future<ContainerIdentifier>\nMetadataFetcher::getContainerIDFromName(qclient::QClient& qcl,\n                                        ContainerIdentifier parent_id,\n                                        const std::string& name)\n{\n  return qcl.follyExec(\"HGET\",\n                       SSTR(parent_id.getUnderlyingUInt64() << constants::sMapDirsSuffix), name)\n         .thenValue(std::bind(parseIDFromNameResponse, _1, parent_id, name))\n         .thenValue(convertInt64ToContainerIdentifier);\n}\n\n//------------------------------------------------------------------------------\n// Resolve FileMdProto from parent ID + name\n//------------------------------------------------------------------------------\nfolly::Future<eos::ns::FileMdProto>\nMetadataFetcher::getFileFromName(qclient::QClient& qcl,\n                                 ContainerIdentifier parent_id,\n                                 const std::string& name)\n{\n  return getFileIDFromName(qcl, parent_id, name)\n         .thenValue(std::bind(getFileFromId, std::ref(qcl), _1));\n}\n\n//----------------------------------------------------------------------------\n//! Resolve ContainerMdProto from parent ID + name\n//----------------------------------------------------------------------------\nfolly::Future<eos::ns::ContainerMdProto>\nMetadataFetcher::getContainerFromName(qclient::QClient& qcl,\n                                      ContainerIdentifier parent_id,\n                                      const std::string& name)\n{\n  return getContainerIDFromName(qcl, parent_id, name)\n         .thenValue(std::bind(getContainerFromId, std::ref(qcl), _1));\n}\n\n//------------------------------------------------------------------------------\n// Parse bool response\n//------------------------------------------------------------------------------\nbool parseBoolResponse(redisReplyPtr reply)\n{\n  ensureBoolReply(reply).throwIfNotOk(\"\");\n\n  if (reply->integer == 0) {\n    return false;\n  }\n\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Parse uint64_t response\n//------------------------------------------------------------------------------\nuint64_t parseUInt64Response(redisReplyPtr reply)\n{\n  ensureUInt64Reply(reply).throwIfNotOk(\"\");\n  return reply->integer;\n}\n\n//------------------------------------------------------------------------------\n// Is the given location of a FileID contained in the FsView?\n//------------------------------------------------------------------------------\nfolly::Future<bool>\nMetadataFetcher::locationExistsInFsView(qclient::QClient& qcl,\n                                        FileIdentifier id,\n                                        int64_t location, bool unlinked)\n{\n  std::string key;\n\n  if (!unlinked) {\n    key = SSTR(\"fsview:\" << location << \":files\");\n  } else {\n    key = SSTR(\"fsview:\" << location << \":unlinked\");\n  }\n\n  return qcl.follyExec(\"SISMEMBER\",\n                       key, SSTR(id.getUnderlyingUInt64())).thenValue(parseBoolResponse);\n}\n\n//------------------------------------------------------------------------------\n// Helper class to resolve a Container's full path\n//------------------------------------------------------------------------------\nclass FullPathResolver : public qclient::QCallback\n{\npublic:\n  //----------------------------------------------------------------------------\n  // Constructor\n  //----------------------------------------------------------------------------\n  FullPathResolver(qclient::QClient& qcl, ContainerIdentifier cont)\n    : mQcl(qcl), mContainerID(cont) {}\n\n  //----------------------------------------------------------------------------\n  // Initialize, fire off first request\n  //----------------------------------------------------------------------------\n  folly::Future<std::string> initialize()\n  {\n    folly::Future<std::string> fut = mPromise.getFuture();\n\n    if (mContainerID == ContainerIdentifier(1)) {\n      // Short-circuit lookup, return \"/\"\n      set_value();\n      return fut;\n    }\n\n    mQcl.execCB(this, RequestBuilder::readContainerProto(mContainerID));\n    return fut;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Handle response\n  //----------------------------------------------------------------------------\n  virtual void handleResponse(redisReplyPtr&& reply) override\n  {\n    if (!reply) {\n      return set_exception(EFAULT, \"QuarkDB backend not available!\");\n    }\n\n    if (reply->type != REDIS_REPLY_STRING) {\n      return set_exception(EFAULT, SSTR(\"Received unexpected response: \"\n                                        << qclient::describeRedisReply(reply)));\n    }\n\n    eos::ns::ContainerMdProto proto;\n    MDStatus status = Serialization::deserialize(reply->str, reply->len, proto);\n\n    if (!status.ok()) {\n      return set_exception(status);\n    }\n\n    mPathStack.emplace_front(proto.name());\n\n    if (proto.parent_id() == 1) {\n      // We're done\n      return set_value();\n    }\n\n    // Lookup next chunk\n    mQcl.execCB(this, RequestBuilder::readContainerProto(ContainerIdentifier(\n                  proto.parent_id())));\n  }\n\n  //----------------------------------------------------------------------------\n  // Return exception by passing it to the promise\n  //----------------------------------------------------------------------------\n  void set_exception(const MDStatus& status)\n  {\n    return set_exception(status.getErrno(), status.getError());\n  }\n\n  //----------------------------------------------------------------------------\n  // Return exception by passing it to the promise\n  //----------------------------------------------------------------------------\n  void set_exception(int err, const std::string& msg)\n  {\n    mPromise.setException(\n      make_mdexception(err, SSTR(\"Error while reconstructing full path of container #\"\n                                 << mContainerID.getUnderlyingUInt64()\n                                 << \" from QDB: \" << msg)));\n    delete this; // harakiri\n  }\n\n  //----------------------------------------------------------------------------\n  // Set value, we're done\n  //----------------------------------------------------------------------------\n  void set_value()\n  {\n    std::ostringstream ss;\n    ss << \"/\";\n\n    for (size_t i = 0; i < mPathStack.size(); i++) {\n      ss << mPathStack[i] << \"/\";\n    }\n\n    mPromise.setValue(ss.str());\n    delete this;\n  }\n\nprivate:\n  qclient::QClient& mQcl;\n  ContainerIdentifier mContainerID;\n  std::deque<std::string> mPathStack;\n  folly::Promise<std::string> mPromise;\n};\n\n\n\n//------------------------------------------------------------------------------\n// Resolve container's full path. Throws an exception if this is a container\n// detached from \"/\".\n//------------------------------------------------------------------------------\nfolly::Future<std::string>\nMetadataFetcher::resolveFullPath(qclient::QClient& qcl,\n                                 ContainerIdentifier containerID)\n{\n  FullPathResolver* resolver = new FullPathResolver(qcl, containerID);\n  return resolver->initialize();\n}\n\n//------------------------------------------------------------------------------\n// Helper class to resolve a path to an ID - no symlink support yet.\n//------------------------------------------------------------------------------\nclass ReversePathResolver\n{\npublic:\n  //----------------------------------------------------------------------------\n  // Constructor\n  //----------------------------------------------------------------------------\n  ReversePathResolver(qclient::QClient& qcl, const std::string& path)\n    : mQcl(qcl), mPath(path)\n  {\n    eos::PathProcessor::insertChunksIntoDeque(mPathStack, path);\n  }\n\n  //----------------------------------------------------------------------------\n  // Initialize, fire off first request\n  //----------------------------------------------------------------------------\n  folly::Future<FileOrContainerIdentifier> initialize()\n  {\n    folly::Future<FileOrContainerIdentifier> fut = mPromise.getFuture();\n\n    if (mPathStack.size() == 0) {\n      set_value(ContainerIdentifier(1));\n    } else {\n      startNextRound(ContainerIdentifier(1));\n    }\n\n    return fut;\n  }\n\n  void handleIncomingFile(eos::ns::FileMdProto proto)\n  {\n    return set_value(FileIdentifier(proto.id()));\n  }\n\n  void handleIncomingContainer(eos::ns::ContainerMdProto proto)\n  {\n    mPathStack.pop_front();\n\n    if (mPathStack.empty()) {\n      return set_value(ContainerIdentifier(proto.id()));\n    }\n\n    startNextRound(ContainerIdentifier(proto.id()));\n  }\n\n  void handleIncomingFileError(const folly::exception_wrapper& e)\n  {\n    mPromise.setException(e);\n    delete this; // harakiri\n  }\n\n  void handleIncomingContainerError(ContainerIdentifier parent,\n                                    const folly::exception_wrapper& e)\n  {\n    if (mPathStack.size() == 1) {\n      MetadataFetcher::getFileFromName(mQcl, parent, mPathStack.front())\n      .thenValue(std::bind(&ReversePathResolver::handleIncomingFile, this, _1))\n      .thenError([this](const folly::exception_wrapper & e) {\n        handleIncomingFileError(e);\n      });\n      return;\n    }\n\n    mPromise.setException(e);\n    delete this; // harakiri\n  }\n\n  //----------------------------------------------------------------------------\n  // Return exception by passing it to the promise\n  //----------------------------------------------------------------------------\n  void set_exception(int err, const std::string& msg)\n  {\n    mPromise.setException(\n      make_mdexception(err, SSTR(\"Error while resolving path \" << mPath <<\n                                 \" from QDB: \" << msg)));\n    delete this; // harakiri\n  }\n\n  //----------------------------------------------------------------------------\n  // Set value, we're done\n  //----------------------------------------------------------------------------\n  void set_value(FileOrContainerIdentifier outcome)\n  {\n    mPromise.setValue(outcome);\n    delete this;\n  }\n\nprivate:\n  //----------------------------------------------------------------------------\n  // Start next asynchronous round\n  //----------------------------------------------------------------------------\n  void startNextRound(ContainerIdentifier parent)\n  {\n    MetadataFetcher::getContainerFromName(mQcl, parent, mPathStack.front())\n    .thenValue(std::bind(&ReversePathResolver::handleIncomingContainer, this, _1))\n    .thenError([this, parent](const folly::exception_wrapper & e) {\n      handleIncomingContainerError(parent, e);\n    });\n  }\n\n  qclient::QClient& mQcl;\n  std::string mPath;\n  std::deque<std::string> mPathStack;\n  folly::Promise<FileOrContainerIdentifier> mPromise;\n};\n\n//------------------------------------------------------------------------------\n// Resolve a path to an ID.\n//------------------------------------------------------------------------------\nfolly::Future<FileOrContainerIdentifier>\nMetadataFetcher::resolvePathToID(qclient::QClient& qcl,\n                                 const std::string& path)\n{\n  ReversePathResolver* resolver = new ReversePathResolver(qcl, path);\n  return resolver->initialize();\n}\n\n//------------------------------------------------------------------------------\n// Count how many files and containers are in the given directory\n//------------------------------------------------------------------------------\nstd::pair<folly::Future<uint64_t>, folly::Future<uint64_t>>\n    MetadataFetcher::countContents(qclient::QClient& qcl,\n                                   ContainerIdentifier containerID)\n{\n  return std::make_pair<folly::Future<uint64_t>, folly::Future<uint64_t>>(\n           qcl.follyExec(\"HLEN\", keySubFiles(containerID.getUnderlyingUInt64()))\n           .thenValue(parseUInt64Response),\n           qcl.follyExec(\"HLEN\", keySubContainers(containerID.getUnderlyingUInt64()))\n           .thenValue(parseUInt64Response)\n         );\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/MetadataFetcher.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Class to retrieve metadata from the backend - no caching!\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/interface/Identifiers.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/Namespace.hh\"\n#include \"proto/FileMd.pb.h\"\n#include \"proto/ContainerMd.pb.h\"\n#include <future>\n#include <folly/futures/Future.h>\n\n//! Forward declaration\nnamespace qclient {\n  class QClient;\n}\n\nnamespace folly {\n  class Executor;\n}\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class MetadataFetcher\n//------------------------------------------------------------------------------\nclass MetadataFetcher\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Fetch file metadata info for current id\n  //!\n  //! @param qcl qclient object\n  //! @param id file id\n  //!\n  //! @return future holding the file metadata object\n  //----------------------------------------------------------------------------\n  static folly::Future<eos::ns::FileMdProto>\n  getFileFromId(qclient::QClient& qcl, FileIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Fetch container metadata info for current id\n  //!\n  //! @param qcl qclient object\n  //! @param id container id\n  //!\n  //! @return future holding the container metadata object\n  //----------------------------------------------------------------------------\n  static folly::Future<eos::ns::ContainerMdProto>\n  getContainerFromId(qclient::QClient& qcl, ContainerIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Check if given container id exists on the namespace\n  //!\n  //! @param qcl qclient object\n  //! @param id container id\n  //!\n  //! @return future holding whether given file exists\n  //----------------------------------------------------------------------------\n  static folly::Future<bool>\n  doesContainerMdExist(qclient::QClient& qcl, ContainerIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Check if given file id exists on the namespace\n  //!\n  //! @param qcl qclient object\n  //! @param id file id\n  //!\n  //! @return future holding whether given file exists\n  //----------------------------------------------------------------------------\n  static folly::Future<bool>\n  doesFileMdExist(qclient::QClient& qcl, FileIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Fetch file map for a container id\n  //!\n  //! @param qcl qclient object\n  //! @param id container id\n  //!\n  //! @return future the map of files\n  //----------------------------------------------------------------------------\n  static folly::Future<IContainerMD::FileMap>\n  getFileMap(qclient::QClient& qcl, ContainerIdentifier container);\n\n  //----------------------------------------------------------------------------\n  //! Fetch all FileMDs contained within the given FileMap. Vector is sorted\n  //! by filename.\n  //----------------------------------------------------------------------------\n  static std::vector<folly::Future<eos::ns::FileMdProto>>\n  getFilesFromFilemap(qclient::QClient& qcl, const IContainerMD::FileMap &fileMap);\n\n  //----------------------------------------------------------------------------\n  //! Same as above, but fileMap is passed as a value.\n  //----------------------------------------------------------------------------\n  static std::vector<folly::Future<eos::ns::FileMdProto>>\n  getFilesFromFilemapV(qclient::QClient& qcl, IContainerMD::FileMap fileMap);\n\n  //----------------------------------------------------------------------------\n  //! Fetch all file metadata within the given container.\n  //----------------------------------------------------------------------------\n  static folly::Future<std::vector<folly::Future<eos::ns::FileMdProto>>>\n  getFileMDsInContainer(qclient::QClient& qcl, ContainerIdentifier container,\n    folly::Executor *executor);\n\n  //----------------------------------------------------------------------------\n  //! Fetch all container metadata within the given container.\n  //----------------------------------------------------------------------------\n  static folly::Future<std::vector<folly::Future<eos::ns::ContainerMdProto>>>\n  getContainerMDsInContainer(qclient::QClient& qcl,\n    ContainerIdentifier container, folly::Executor *executor);\n\n  //----------------------------------------------------------------------------\n  //! Fetch subcontainers map for a container id\n  //!\n  //! @param qcl qclient object\n  //! @param id container id\n  //!\n  //! @return future the map of subcontainers\n  //----------------------------------------------------------------------------\n  static folly::Future<IContainerMD::ContainerMap>\n  getContainerMap(qclient::QClient& qcl, ContainerIdentifier container);\n\n  //----------------------------------------------------------------------------\n  //! Fetch all ContainerMDs contained within the given ContainerMap.\n  //! Vector is sorted by filename.\n  //----------------------------------------------------------------------------\n  static std::vector<folly::Future<eos::ns::ContainerMdProto>>\n  getContainersFromContainerMap(qclient::QClient& qcl,\n    const IContainerMD::ContainerMap &containerMap);\n\n  //----------------------------------------------------------------------------\n  //! Same as above, but containerMap is passed as a value.\n  //----------------------------------------------------------------------------\n  static std::vector<folly::Future<eos::ns::ContainerMdProto>>\n  getContainersFromContainerMapV(qclient::QClient& qcl,\n    IContainerMD::ContainerMap containerMap);\n\n  //----------------------------------------------------------------------------\n  //! Fetch a file id given its parent and its name\n  //!\n  //! @param qcl qclient object\n  //! @param parent_id parent container id\n  //! @param name file name\n  //!\n  //! @return future holding the id of the file\n  //----------------------------------------------------------------------------\n  static folly::Future<FileIdentifier>\n  getFileIDFromName(qclient::QClient& qcl, ContainerIdentifier parent_id,\n                    const std::string& name);\n\n  //----------------------------------------------------------------------------\n  //! Fetch a container id given its parent and its name\n  //!\n  //! @param qcl qclient object\n  //! @param parent_id parent container id\n  //! @param name subcontainer name\n  //!\n  //! @return future holding the id of the subcontainer\n  //----------------------------------------------------------------------------\n  static folly::Future<ContainerIdentifier>\n  getContainerIDFromName(qclient::QClient& qcl, ContainerIdentifier parent_id,\n                         const std::string& name);\n\n  //----------------------------------------------------------------------------\n  //! Resolve FileMdProto from parent ID + name\n  //----------------------------------------------------------------------------\n  static folly::Future<eos::ns::FileMdProto>\n  getFileFromName(qclient::QClient& qcl, ContainerIdentifier parent_id,\n                const std::string& name);\n\n  //----------------------------------------------------------------------------\n  //! Resolve ContainerMdProto from parent ID + name\n  //----------------------------------------------------------------------------\n  static folly::Future<eos::ns::ContainerMdProto>\n  getContainerFromName(qclient::QClient& qcl, ContainerIdentifier parent_id,\n                const std::string& name);\n\n  //----------------------------------------------------------------------------\n  //! Is the given location of a FileID contained in the FsView?\n  //----------------------------------------------------------------------------\n  static folly::Future<bool>\n  locationExistsInFsView(qclient::QClient& qcl, FileIdentifier id, int64_t location,\n                   bool unlinked);\n\n  //----------------------------------------------------------------------------\n  //! Resolve container's full path. Throws an exception if this is a container\n  //! detached from \"/\".\n  //----------------------------------------------------------------------------\n  static folly::Future<std::string>\n  resolveFullPath(qclient::QClient& qcl, ContainerIdentifier containerID);\n\n  //----------------------------------------------------------------------------\n  //! Resolve a path to an ID - no symlink support yet.\n  //----------------------------------------------------------------------------\n  static folly::Future<FileOrContainerIdentifier>\n  resolvePathToID(qclient::QClient& qcl, const std::string &path);\n\n  //----------------------------------------------------------------------------\n  //! Count how many files and containers are in the given directory\n  //----------------------------------------------------------------------------\n  static std::pair<folly::Future<uint64_t>, folly::Future<uint64_t>>\n  countContents(qclient::QClient& qcl, ContainerIdentifier containerID);\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Construct hmap key of subcontainers in container\n  //!\n  //! @param id container id\n  //!\n  //! @return string representing the key\n  //----------------------------------------------------------------------------\n  static std::string keySubContainers(IContainerMD::id_t id);\n\n  //----------------------------------------------------------------------------\n  //! Construct hmap key of files in container\n  //!\n  //! @param id container id\n  //!\n  //! @return string representing the key\n  //----------------------------------------------------------------------------\n  static std::string keySubFiles(IContainerMD::id_t id);\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/MetadataProvider.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// @author Georgios Bitzes <georgios.bitzes@cern.ch>\n// @brief Asynchronous metadata retrieval from QDB, with caching support.\n//------------------------------------------------------------------------------\n\n#include \"MetadataProvider.hh\"\n#include \"MetadataProviderShard.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include <folly/Executor.h>\n#include <folly/executors/IOThreadPoolExecutor.h>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nMetadataProvider::MetadataProvider(const QdbContactDetails& contactDetails,\n                                   IContainerMDSvc* contsvc, IFileMDSvc* filesvc)\n{\n  mExecutor.reset(new folly::IOThreadPoolExecutor(16));\n\n  for(size_t i = 0; i < kShards; i++) {\n    mQcl.emplace_back(std::make_unique<qclient::QClient>(contactDetails.members, contactDetails.constructOptions()));\n    mShards.emplace_back(new MetadataProviderShard(mQcl.back().get(), contsvc, filesvc, mExecutor.get()));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Retrieve ContainerMD by ID.\n//------------------------------------------------------------------------------\nfolly::Future<IContainerMDPtr>\nMetadataProvider::retrieveContainerMD(ContainerIdentifier id)\n{\n  return pickShard(id)->retrieveContainerMD(id);\n}\n\n//------------------------------------------------------------------------------\n// Retrieve FileMD by ID.\n//------------------------------------------------------------------------------\nfolly::Future<IFileMDPtr>\nMetadataProvider::retrieveFileMD(FileIdentifier id)\n{\n  return pickShard(id)->retrieveFileMD(id);\n}\n\n//------------------------------------------------------------------------------\n// Drop cached FileID - return true if found\n//------------------------------------------------------------------------------\nbool\nMetadataProvider::dropCachedFileID(FileIdentifier id)\n{\n  return pickShard(id)->dropCachedFileID(id);\n}\n\n//------------------------------------------------------------------------------\n// Drop cached ContainerID - return true if found\n//------------------------------------------------------------------------------\nbool\nMetadataProvider::dropCachedContainerID(ContainerIdentifier id)\n{\n  return pickShard(id)->dropCachedContainerID(id);\n}\n\n//----------------------------------------------------------------------------\n// Check if a FileMD exists with the given id\n//----------------------------------------------------------------------------\nfolly::Future<bool>\nMetadataProvider::hasFileMD(FileIdentifier id)\n{\n  return pickShard(id)->hasFileMD(id);\n}\n\n//------------------------------------------------------------------------------\n// Insert newly created item into the cache.\n//------------------------------------------------------------------------------\nvoid\nMetadataProvider::insertFileMD(FileIdentifier id, IFileMDPtr item)\n{\n  return pickShard(id)->insertFileMD(id, item);\n}\n\n//------------------------------------------------------------------------------\n// Insert newly created item into the cache.\n//------------------------------------------------------------------------------\nvoid\nMetadataProvider::insertContainerMD(ContainerIdentifier id,\n                                    IContainerMDPtr item)\n{\n  return pickShard(id)->insertContainerMD(id, item);\n}\n\n//------------------------------------------------------------------------------\n// Change file cache size.\n//------------------------------------------------------------------------------\nvoid MetadataProvider::setFileMDCacheNum(uint64_t max_num)\n{\n  uint64_t max_num_per_shard = max_num / kShards;\n\n  if(max_num == UINT64_MAX) {\n    max_num_per_shard = UINT64_MAX;\n  }\n\n  for(size_t i = 0; i < mShards.size(); i++) {\n    mShards[i]->setFileMDCacheNum(max_num_per_shard);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Change container cache size.\n//------------------------------------------------------------------------------\nvoid MetadataProvider::setContainerMDCacheNum(uint64_t max_num)\n{\n  uint64_t max_num_per_shard = max_num / kShards;\n\n  if(max_num == UINT64_MAX) {\n    max_num_per_shard = UINT64_MAX;\n  }\n\n  for(size_t i = 0; i < mShards.size(); i++) {\n    mShards[i]->setContainerMDCacheNum(max_num_per_shard);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Add a CacheStatistics object into another\n//------------------------------------------------------------------------------\nvoid aggregateStatistics(CacheStatistics &global, const CacheStatistics local) {\n  global.occupancy += local.occupancy;\n  global.maxNum += local.maxNum;\n  global.numRequests += local.numRequests;\n  global.numHits += local.numHits;\n  global.inFlight += local.inFlight;\n}\n\n//------------------------------------------------------------------------------\n// Get file cache statistics\n//------------------------------------------------------------------------------\nCacheStatistics MetadataProvider::getFileMDCacheStats()\n{\n  CacheStatistics globalStats;\n  globalStats.enabled = true;\n\n  for(size_t i = 0; i < mShards.size(); i++) {\n    aggregateStatistics(globalStats, mShards[i]->getFileMDCacheStats());\n  }\n\n  return globalStats;\n}\n\n//------------------------------------------------------------------------------\n// Get container cache statistics\n//------------------------------------------------------------------------------\nCacheStatistics MetadataProvider::getContainerMDCacheStats()\n{\n  CacheStatistics globalStats;\n  globalStats.enabled = true;\n\n  for(size_t i = 0; i < mShards.size(); i++) {\n    aggregateStatistics(globalStats, mShards[i]->getContainerMDCacheStats());\n  }\n\n  return globalStats;\n}\n\n//------------------------------------------------------------------------------\n//! Pick shard based on FileIdentifier\n//------------------------------------------------------------------------------\nMetadataProviderShard* MetadataProvider::pickShard(FileIdentifier id) {\n  return (mShards[id.getUnderlyingUInt64() % kShards]).get();\n}\n\n//------------------------------------------------------------------------------\n//! Pick shard based on ContainerIdentifier\n//------------------------------------------------------------------------------\nMetadataProviderShard* MetadataProvider::pickShard(ContainerIdentifier id) {\n  return (mShards[id.getUnderlyingUInt64() % kShards]).get();\n}\n\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/MetadataProvider.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Asynchronous metadata retrieval from QDB, with caching support.\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/interface/Identifiers.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/ns_quarkdb/persistency/MetadataProviderShard.hh\"\n#include \"namespace/Namespace.hh\"\n#include <folly/futures/Future.h>\n#include <folly/futures/FutureSplitter.h>\n\nnamespace folly\n{\nclass Executor;\n}\n\nEOSNSNAMESPACE_BEGIN\n\nclass IContainerMDSvc;\nclass IFileMDSvc;\nclass QdbContactDetails;\n\n//------------------------------------------------------------------------------\n//! Class MetadataProvider\n//------------------------------------------------------------------------------\nclass MetadataProvider\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  MetadataProvider(const QdbContactDetails& contactDetails, IContainerMDSvc* contsvc,\n                   IFileMDSvc* filemvc);\n\n  //----------------------------------------------------------------------------\n  //! Retrieve ContainerMD by ID\n  //----------------------------------------------------------------------------\n  folly::Future<IContainerMDPtr> retrieveContainerMD(ContainerIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Retrieve FileMD by ID\n  //----------------------------------------------------------------------------\n  folly::Future<IFileMDPtr> retrieveFileMD(FileIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Drop cached FileID - return true if found\n  //----------------------------------------------------------------------------\n  bool dropCachedFileID(FileIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Drop cached ContainerID - return true if found\n  //----------------------------------------------------------------------------\n  bool dropCachedContainerID(ContainerIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Check if a FileMD exists with the given id\n  //----------------------------------------------------------------------------\n  folly::Future<bool> hasFileMD(FileIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Insert newly created item into the cache\n  //----------------------------------------------------------------------------\n  void insertFileMD(FileIdentifier id, IFileMDPtr item);\n\n  //----------------------------------------------------------------------------\n  //! Insert newly created item into the cache\n  //----------------------------------------------------------------------------\n  void insertContainerMD(ContainerIdentifier id, IContainerMDPtr item);\n\n  //----------------------------------------------------------------------------\n  //! Change file cache size\n  //----------------------------------------------------------------------------\n  void setFileMDCacheNum(uint64_t max_num);\n\n  //----------------------------------------------------------------------------\n  //! Change container cache size\n  //----------------------------------------------------------------------------\n  void setContainerMDCacheNum(uint64_t max_num);\n\n  //----------------------------------------------------------------------------\n  //! Get file cache statistics\n  //----------------------------------------------------------------------------\n  CacheStatistics getFileMDCacheStats();\n\n  //----------------------------------------------------------------------------\n  //! Get container cache statistics\n  //----------------------------------------------------------------------------\n  CacheStatistics getContainerMDCacheStats();\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Pick shard based on FileIdentifier\n  //----------------------------------------------------------------------------\n  MetadataProviderShard* pickShard(FileIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Pick shard based on ContainerIdentifier\n  //----------------------------------------------------------------------------\n  MetadataProviderShard* pickShard(ContainerIdentifier id);\n\n\n  static constexpr size_t kShards = 16;\n\n  //----------------------------------------------------------------------------\n  //! CAUTION: The folly Executor must outlive qclient! If a continuation is\n  //! attached to a qclient-provided future, but the executor has been\n  //! destroyed, qclient will segfault when fulfilling the\n  //! corresponding promise.\n  //!\n  //! The order of these two members is very important - the executor must be\n  //! first.\n  //----------------------------------------------------------------------------\n  std::unique_ptr<folly::Executor> mExecutor;\n  std::vector<std::unique_ptr<qclient::QClient>> mQcl;\n\n  std::vector<std::unique_ptr<MetadataProviderShard>> mShards;\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/MetadataProviderShard.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// @author Georgios Bitzes <georgios.bitzes@cern.ch>\n// @brief Asynchronous metadata retrieval from QDB, with caching support.\n//------------------------------------------------------------------------------\n\n#include <folly/Executor.h>\n#include \"MetadataFetcher.hh\"\n#include \"MetadataProviderShard.hh\"\n#include \"namespace/ns_quarkdb/FileMD.hh\"\n#include \"namespace/ns_quarkdb/ContainerMD.hh\"\n#include \"namespace/MDException.hh\"\n#include \"common/Assert.hh\"\n#include <functional>\n\nusing std::placeholders::_1;\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nMetadataProviderShard::MetadataProviderShard(qclient::QClient* qcl,\n    IContainerMDSvc* contsvc, IFileMDSvc* filesvc, folly::Executor* exec)\n  : mContSvc(contsvc), mFileSvc(filesvc), mContainerCache(312500),\n    mFileCache(2500000)\n{\n  mExecutor = exec;\n  mQcl = qcl;\n}\n\n//------------------------------------------------------------------------------\n// Retrieve ContainerMD by ID.\n//------------------------------------------------------------------------------\nfolly::Future<IContainerMDPtr>\nMetadataProviderShard::retrieveContainerMD(ContainerIdentifier id)\n{\n  // Quick check without lock on the long-lived cache. LRU is locked internally,\n  // so this is thread-safe.\n  //\n  // If we get no hit, we have to check again under lock.\n  IContainerMDPtr result = mContainerCache.get(id);\n\n  if (result) {\n    // Handle special case where we're dealing with a tombstone.\n    if (result->isDeleted()) {\n      return folly::makeFuture<IContainerMDPtr>\n             (make_mdexception(ENOENT, \"Container #\" << id.getUnderlyingUInt64()\n                               << \" does not exist (found deletion tombstone)\"));\n    }\n\n    return folly::makeFuture<IContainerMDPtr>(std::move(result));\n  }\n\n  std::unique_lock<std::mutex> lock(mMutex);\n  // A ContainerMD can be in three states: Not in cache, inside in-flight cache,\n  // and cached. Is it inside in-flight cache?\n  auto it = mInFlightContainers.find(id);\n\n  if (it != mInFlightContainers.end()) {\n    // Cache hit: A container with such ID has been staged already. Once a\n    // response arrives, all futures tied to that container will be activated\n    // automatically, with the same IContainerMDPtr.\n    return it->second.getFuture();\n  }\n\n  // Nope.. is it inside the long-lived cache?\n  result = mContainerCache.get(id);\n\n  if (result) {\n    lock.unlock();\n\n    // Handle special case where we're dealing with a tombstone.\n    if (result->isDeleted()) {\n      return folly::makeFuture<IContainerMDPtr>\n             (make_mdexception(ENOENT, \"Container #\" << id.getUnderlyingUInt64()\n                               << \" does not exist (found deletion tombstone)\"));\n    }\n\n    return folly::makeFuture<IContainerMDPtr>(std::move(result));\n  }\n\n  // Nope, need to fetch, and insert into the in-flight staging area. Merge\n  // three asynchronous operations into one.\n  folly::Future<eos::ns::ContainerMdProto> protoFut =\n    MetadataFetcher::getContainerFromId(*mQcl, id);\n  folly::Future<IContainerMD::FileMap> fileMapFut =\n    MetadataFetcher::getFileMap(*mQcl, id);\n  folly::Future<IContainerMD::ContainerMap> containerMapFut =\n    MetadataFetcher::getContainerMap(*mQcl, id);\n  folly::Future<IContainerMDPtr> fut =\n    folly::collect(protoFut, fileMapFut, containerMapFut)\n    .via(mExecutor)\n    .thenValue(std::bind(&MetadataProviderShard::processIncomingContainerMD, this,\n                         id, _1))\n  .thenError([this, id](const folly::exception_wrapper & e) {\n    // If the operation failed, clear the in-flight cache.\n    std::lock_guard<std::mutex> lock(mMutex);\n    mInFlightContainers.erase(id);\n    return folly::makeFuture<IContainerMDPtr>(e);\n  });\n  mInFlightContainers[id] = folly::FutureSplitter<IContainerMDPtr>(std::move(\n                              fut));\n  return mInFlightContainers[id].getFuture();\n}\n\n//------------------------------------------------------------------------------\n// Retrieve FileMD by ID.\n//------------------------------------------------------------------------------\nfolly::Future<IFileMDPtr>\nMetadataProviderShard::retrieveFileMD(FileIdentifier id)\n{\n  // Quick check without lock on the long-lived cache. LRU is locked internally,\n  // so this is thread-safe.\n  //\n  // If we get no hit, we have to check again under lock.\n  // Nope.. is it inside the long-lived cache?\n  IFileMDPtr result = mFileCache.get(id);\n\n  if (result) {\n    // Handle special case where we're dealing with a tombstone.\n    if (result->isDeleted()) {\n      return folly::makeFuture<IFileMDPtr>\n             (make_mdexception(ENOENT, \"File #\" << id.getUnderlyingUInt64()\n                               << \" does not exist (found deletion tombstone)\"));\n    }\n\n    return folly::makeFuture<IFileMDPtr>(std::move(result));\n  }\n\n  std::unique_lock<std::mutex> lock(mMutex);\n\n  // Are we asking for fid=0? Illegal, short-circuit without even contacting\n  // QDB. Indicates possible bug elsewhere in the MGM.\n  if (id == FileIdentifier(0)) {\n    eos_static_warning(\"Attempted to retrieve fid=0!\");\n    return folly::makeFuture<IFileMDPtr>\n           (make_mdexception(ENOENT, \"File #\" << id.getUnderlyingUInt64()\n                             << \" does not exist (fid=0 is illegal)\"));\n  }\n\n  // A FileMD can be in three states: Not in cache, inside in-flight cache,\n  // and cached. Is it inside in-flight cache?\n  auto it = mInFlightFiles.find(id);\n\n  if (it != mInFlightFiles.end()) {\n    // Cache hit: A container with such ID has been staged already. Once a\n    // response arrives, all futures tied to that container will be activated\n    // automatically, with the same IContainerMDPtr.\n    return it->second.getFuture();\n  }\n\n  // Nope.. is it inside the long-lived cache?\n  result = mFileCache.get(id);\n\n  if (result) {\n    lock.unlock();\n\n    // Handle special case where we're dealing with a tombstone.\n    if (result->isDeleted()) {\n      return folly::makeFuture<IFileMDPtr>\n             (make_mdexception(ENOENT, \"File #\" << id.getUnderlyingUInt64()\n                               << \" does not exist (found deletion tombstone)\"));\n    }\n\n    return folly::makeFuture<IFileMDPtr>(std::move(result));\n  }\n\n  // Nope, need to fetch, and insert into the in-flight staging area.\n  folly::Future<IFileMDPtr> fut = MetadataFetcher::getFileFromId(*mQcl, id)\n                                  .via(mExecutor)\n                                  .thenValue(std::bind(&MetadataProviderShard::processIncomingFileMdProto, this,\n                                      id, _1))\n  .thenError([this, id](const folly::exception_wrapper & e) {\n    // If the operation failed, clear the in-flight cache.\n    std::lock_guard<std::mutex> lock(mMutex);\n    mInFlightFiles.erase(id);\n    return folly::makeFuture<IFileMDPtr>(e);\n  });\n  mInFlightFiles[id] = folly::FutureSplitter<IFileMDPtr>(std::move(fut));\n  return mInFlightFiles[id].getFuture();\n}\n\n//------------------------------------------------------------------------------\n// Drop cached FileID - return true if found\n//------------------------------------------------------------------------------\nbool\nMetadataProviderShard::dropCachedFileID(FileIdentifier id)\n{\n  std::unique_lock<std::mutex> lock(mMutex);\n  return mFileCache.remove(id);\n}\n\n//------------------------------------------------------------------------------\n// Drop cached ContainerID - return true if found\n//------------------------------------------------------------------------------\nbool\nMetadataProviderShard::dropCachedContainerID(ContainerIdentifier id)\n{\n  std::unique_lock<std::mutex> lock(mMutex);\n  return mContainerCache.remove(id);\n}\n\n//----------------------------------------------------------------------------\n// Check if a FileMD exists with the given id\n//----------------------------------------------------------------------------\nfolly::Future<bool>\nMetadataProviderShard::hasFileMD(FileIdentifier id)\n{\n  return MetadataFetcher::doesFileMdExist(*mQcl, id);\n}\n\n//------------------------------------------------------------------------------\n// Insert newly created item into the cache.\n//------------------------------------------------------------------------------\nvoid\nMetadataProviderShard::insertFileMD(FileIdentifier id, IFileMDPtr item)\n{\n  std::lock_guard<std::mutex> lock(mMutex);\n  mFileCache.put(id, item);\n}\n\n//------------------------------------------------------------------------------\n// Insert newly created item into the cache.\n//------------------------------------------------------------------------------\nvoid\nMetadataProviderShard::insertContainerMD(ContainerIdentifier id,\n    IContainerMDPtr item)\n{\n  std::lock_guard<std::mutex> lock(mMutex);\n  mContainerCache.put(id, item);\n}\n\n//------------------------------------------------------------------------------\n// Change file cache size.\n//------------------------------------------------------------------------------\nvoid MetadataProviderShard::setFileMDCacheNum(uint64_t max_num)\n{\n  std::lock_guard<std::mutex> lock(mMutex);\n  mFileCache.SetMaxNum(max_num);\n}\n\n//------------------------------------------------------------------------------\n// Change container cache size.\n//------------------------------------------------------------------------------\nvoid MetadataProviderShard::setContainerMDCacheNum(uint64_t max_num)\n{\n  std::lock_guard<std::mutex> lock(mMutex);\n  mContainerCache.SetMaxNum(max_num);\n}\n\n//------------------------------------------------------------------------------\n// Turn a (ContainerMDProto, FileMap, ContainerMap) triplet into a\n// ContainerMDPtr, and insert into the cache.\n//------------------------------------------------------------------------------\nIContainerMDPtr\nMetadataProviderShard::processIncomingContainerMD(ContainerIdentifier id,\n    std::tuple <\n    eos::ns::ContainerMdProto,\n    IContainerMD::FileMap,\n    IContainerMD::ContainerMap\n    > tup)\n{\n  std::lock_guard<std::mutex> lock(mMutex);\n  // Unpack tuple. (sigh)\n  eos::ns::ContainerMdProto& proto = std::get<0>(tup);\n  IContainerMD::FileMap& fileMap = std::get<1>(tup);\n  IContainerMD::ContainerMap& containerMap = std::get<2>(tup);\n  // Things look sane?\n  eos_assert(proto.id() == id.getUnderlyingUInt64());\n  // Yep, construct ContainerMD object..\n  QuarkContainerMD* containerMD = new QuarkContainerMD(0, mFileSvc, mContSvc);\n  containerMD->initialize(std::move(proto), std::move(fileMap),\n                          std::move(containerMap));\n  // Drop inFlightContainers future..\n  auto it = mInFlightContainers.find(id);\n  eos_assert(it != mInFlightContainers.end());\n  mInFlightContainers.erase(it);\n  // Insert into the cache ...\n  IContainerMDPtr item { containerMD };\n  mContainerCache.put(id, item);\n  return item;\n}\n\n//------------------------------------------------------------------------------\n// Turn an incoming FileMDProto into FileMD, removing from the inFlight\n// staging area, and inserting into the cache.\n//------------------------------------------------------------------------------\nIFileMDPtr\nMetadataProviderShard::processIncomingFileMdProto(FileIdentifier id,\n    eos::ns::FileMdProto proto)\n{\n  std::lock_guard<std::mutex> lock(mMutex);\n  // Things look sane?\n  eos_assert(proto.id() == id.getUnderlyingUInt64());\n  // Yep, construct FileMD object..\n  QuarkFileMD* fileMD = new QuarkFileMD(0, mFileSvc);\n  fileMD->initialize(std::move(proto));\n  // Drop inFlightFiles future..\n  auto it = mInFlightFiles.find(id);\n  eos_assert(it != mInFlightFiles.end());\n  mInFlightFiles.erase(it);\n  // Insert into the cache ...\n  IFileMDPtr item { fileMD };\n  mFileCache.put(id, item);\n  return item;\n}\n\n//------------------------------------------------------------------------------\n// Get file cache statistics\n//------------------------------------------------------------------------------\nCacheStatistics MetadataProviderShard::getFileMDCacheStats()\n{\n  CacheStatistics stats;\n  stats.enabled = true;\n  stats.occupancy = mFileCache.size();\n  stats.maxNum = mFileCache.GetMaxNum();\n  stats.numRequests = mFileCache.GetRequests();\n  stats.numHits = mFileCache.GetHits();\n  std::lock_guard<std::mutex> lock(mMutex);\n  stats.inFlight = mInFlightFiles.size();\n  return stats;\n}\n\n//------------------------------------------------------------------------------\n// Get container cache statistics\n//------------------------------------------------------------------------------\nCacheStatistics MetadataProviderShard::getContainerMDCacheStats()\n{\n  CacheStatistics stats;\n  stats.enabled = true;\n  stats.occupancy = mContainerCache.size();\n  stats.maxNum = mContainerCache.GetMaxNum();\n  stats.numRequests = mContainerCache.GetRequests();\n  stats.numHits = mContainerCache.GetHits();\n  std::lock_guard<std::mutex> lock(mMutex);\n  stats.inFlight = mInFlightContainers.size();\n  return stats;\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/MetadataProviderShard.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Asynchronous metadata retrieval from QDB, with caching support.\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/interface/Identifiers.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/Namespace.hh\"\n#include \"namespace/ns_quarkdb/LRU.hh\"\n#include \"namespace/interface/Misc.hh\"\n#include \"namespace/ns_quarkdb/FileMD.hh\"\n#include \"namespace/ns_quarkdb/ContainerMD.hh\"\n#include <qclient/QClient.hh>\n#include <folly/futures/Future.h>\n#include <folly/futures/FutureSplitter.h>\n\nnamespace folly\n{\nclass Executor;\n}\n\nEOSNSNAMESPACE_BEGIN\n\nclass IContainerMDSvc;\nclass IFileMDSvc;\nclass QdbContactDetails;\n\n//------------------------------------------------------------------------------\n//! Class MetadataProvider\n//------------------------------------------------------------------------------\nclass MetadataProviderShard\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  MetadataProviderShard(qclient::QClient *qcl,\n    IContainerMDSvc* contsvc, IFileMDSvc* filemvc, folly::Executor *exec);\n\n  //----------------------------------------------------------------------------\n  //! Retrieve ContainerMD by ID\n  //----------------------------------------------------------------------------\n  folly::Future<IContainerMDPtr> retrieveContainerMD(ContainerIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Retrieve FileMD by ID\n  //----------------------------------------------------------------------------\n  folly::Future<IFileMDPtr> retrieveFileMD(FileIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Drop cached FileID - return true if found\n  //----------------------------------------------------------------------------\n  bool dropCachedFileID(FileIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Drop cached ContainerID - return true if found\n  //----------------------------------------------------------------------------\n  bool dropCachedContainerID(ContainerIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Check if a FileMD exists with the given id\n  //----------------------------------------------------------------------------\n  folly::Future<bool> hasFileMD(FileIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Insert newly created item into the cache\n  //----------------------------------------------------------------------------\n  void insertFileMD(FileIdentifier id, IFileMDPtr item);\n\n  //----------------------------------------------------------------------------\n  //! Insert newly created item into the cache\n  //----------------------------------------------------------------------------\n  void insertContainerMD(ContainerIdentifier id, IContainerMDPtr item);\n\n  //----------------------------------------------------------------------------\n  //! Change file cache size\n  //----------------------------------------------------------------------------\n  void setFileMDCacheNum(uint64_t max_num);\n\n  //----------------------------------------------------------------------------\n  //! Change container cache size\n  //----------------------------------------------------------------------------\n  void setContainerMDCacheNum(uint64_t max_num);\n\n  //----------------------------------------------------------------------------\n  //! Get file cache statistics\n  //----------------------------------------------------------------------------\n  CacheStatistics getFileMDCacheStats();\n\n  //----------------------------------------------------------------------------\n  //! Get container cache statistics\n  //----------------------------------------------------------------------------\n  CacheStatistics getContainerMDCacheStats();\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Turn an incoming FileMDProto into FileMD, removing from the inFlight\n  //! staging area, and inserting into the cache\n  //----------------------------------------------------------------------------\n  IFileMDPtr processIncomingFileMdProto(FileIdentifier id,\n                                        eos::ns::FileMdProto proto);\n\n  //----------------------------------------------------------------------------\n  //! Turn a (ContainerMDProto, FileMap, ContainerMap) triplet into a\n  //! ContainerMDPtr and insert into the cache\n  //----------------------------------------------------------------------------\n  IContainerMDPtr processIncomingContainerMD(ContainerIdentifier id,\n      std::tuple <\n      eos::ns::ContainerMdProto,\n      IContainerMD::FileMap,\n      IContainerMD::ContainerMap\n      > tup);\n\n  qclient::QClient *mQcl; // no ownership\n  IContainerMDSvc* mContSvc; // no ownership\n  IFileMDSvc* mFileSvc; // no ownership\n  std::mutex mMutex;\n  std::map<ContainerIdentifier,\n      folly::FutureSplitter<IContainerMDPtr>> mInFlightContainers;\n  std::map<FileIdentifier, folly::FutureSplitter<IFileMDPtr>> mInFlightFiles;\n  LRU<ContainerIdentifier, IContainerMD> mContainerCache;\n  LRU<FileIdentifier, IFileMD> mFileCache;\n  folly::Executor *mExecutor; // no ownership\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/NextInodeProvider.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"common/Assert.hh\"\n#include \"namespace/ns_quarkdb/persistency/NextInodeProvider.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"qclient/structures/QHash.hh\"\n#include <memory>\n#include <numeric>\n\n#define __PRI64_PREFIX \"l\"\n#define PRId64         __PRI64_PREFIX \"d\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Inode block constructor\n//------------------------------------------------------------------------------\nInodeBlock::InodeBlock(int64_t start, int64_t len)\n: mStart(start), mLen(len) {\n\n  mNextId = mStart;\n}\n\n//------------------------------------------------------------------------------\n// Check if block has more inodes to give\n//------------------------------------------------------------------------------\nbool InodeBlock::empty() const {\n  return mStart + mLen <= mNextId;\n}\n\n//------------------------------------------------------------------------------\n// Reserve, only if there's enough space\n//------------------------------------------------------------------------------\nbool InodeBlock::reserve(int64_t &out) {\n  if(!empty()) {\n    out = mNextId;\n    mNextId++;\n    return true;\n  }\n\n  return false;\n}\n\n//------------------------------------------------------------------------------\n// Get first free ID - what reserve _would_ have returned, without actually\n// allocating the inode.\n//------------------------------------------------------------------------------\nbool InodeBlock::getFirstFreeID(int64_t &out) const {\n  if(empty()) {\n    return false;\n  }\n\n  out = mNextId;\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Blacklist all IDs below the given number, including the threshold itself.\n//------------------------------------------------------------------------------\nvoid InodeBlock::blacklistBelow(int64_t threshold) {\n  if(mNextId <= threshold) {\n    mNextId = threshold+1;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nNextInodeProvider::NextInodeProvider()\n  : pHash(nullptr), pField(\"\"), mInodeBlock(0, 0), mStepIncrease(1)\n{\n}\n\n//------------------------------------------------------------------------------\n// Get first free id\n//------------------------------------------------------------------------------\nint64_t NextInodeProvider::getFirstFreeId()\n{\n  std::lock_guard<std::mutex> lock(mMtx);\n\n  int64_t out;\n  if(mInodeBlock.getFirstFreeID(out)) {\n    return out;\n  }\n\n  return getDBValue() + 1;\n}\n\n//------------------------------------------------------------------------------\n// The hash contains the current largest *reserved* inode we've seen so far.\n// To obtain the next free one, we increment that counter and return its value.\n// We reserve inodes by blocks to avoid roundtrips to the db, increasing the\n// block-size slowly up to 5000 so as to avoid wasting lots of inodes if the MGM\n// is unstable and restarts often.\n//------------------------------------------------------------------------------\nint64_t NextInodeProvider::reserve()\n{\n  std::lock_guard<std::mutex> lock(mMtx);\n\n  int64_t out;\n  if(mInodeBlock.reserve(out)) {\n    return out;\n  }\n\n  // We're out if inodes, allocate next inode block\n  allocateInodeBlock();\n  eos_assert(mInodeBlock.reserve(out));\n  return out;\n}\n\n//------------------------------------------------------------------------------\n// Blacklist all IDs below the given number - from that point on, no IDs\n// less or equal to what is specified will be given out.\n//------------------------------------------------------------------------------\nvoid NextInodeProvider::blacklistBelow(int64_t threshold)\n{\n  std::lock_guard<std::mutex> lock(mMtx);\n\n  mInodeBlock.blacklistBelow(threshold);\n  if(mInodeBlock.empty()) {\n    // Our cached inode block has ran out of inodes - suspicious.\n    // We might need to touch the DB.\n    blacklistDBThreshold(threshold);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Blacklist DB threshold\n//------------------------------------------------------------------------------\nvoid NextInodeProvider::blacklistDBThreshold(int64_t threshold) {\n  int64_t currentValue = getDBValue();\n\n  if(currentValue < threshold) {\n    // Major event coming up, blacklisting inodes operation hitting the DB.\n    eos_static_notice(\"Inode blacklisting operation hitting QDB: \" PRId64 \" -> \" PRId64, currentValue, threshold);\n\n    // We need to set currentValue to \"threshold\". We use HINCRBY due to paranoia,\n    // to ensure we would **never** decrease the value in the DB.\n\n    int64_t diff = threshold - currentValue;\n    eos_assert(diff > 0);\n    eos_assert(pHash->hincrby(pField, diff) == threshold);\n    eos_assert(getDBValue() == threshold);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Configure hash and field\n//------------------------------------------------------------------------------\nvoid NextInodeProvider::configure(qclient::QHash& hash,\n                                  const std::string& field)\n{\n  std::lock_guard<std::mutex> lock(mMtx);\n  pHash = &hash;\n  pField = field;\n}\n\n//------------------------------------------------------------------------------\n// Get counter value stored in DB, no caching\n//------------------------------------------------------------------------------\nint64_t NextInodeProvider::getDBValue() {\n  int64_t id = 0;\n\n  std::string sval = pHash->hget(pField);\n  if (!sval.empty()) {\n    id = std::stoull(sval);\n  }\n\n  return id;\n}\n\n//------------------------------------------------------------------------------\n// Allocate new inode block\n//------------------------------------------------------------------------------\nvoid NextInodeProvider::allocateInodeBlock() {\n  int64_t blockEnd = pHash->hincrby(pField, mStepIncrease);\n  mInodeBlock = InodeBlock(blockEnd - mStepIncrease + 1, mStepIncrease);\n\n  // Increase step for next round\n  if (mStepIncrease <= 5000) {\n    mStepIncrease++;\n  }\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/NextInodeProvider.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Retrieve the next free container / file inode.\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include <mutex>\n#include <string>\n\nnamespace qclient {\n  class QHash;\n}\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class InodeBlock\n//------------------------------------------------------------------------------\nclass InodeBlock\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  InodeBlock(int64_t start, int64_t len);\n\n  //----------------------------------------------------------------------------\n  //! Reserve, only if there's enough space\n  //----------------------------------------------------------------------------\n  bool reserve(int64_t &out);\n\n  //----------------------------------------------------------------------------\n  //! Get first free ID - what reserve _would_ have returned, without actually\n  //! allocating the inode.\n  //----------------------------------------------------------------------------\n  bool getFirstFreeID(int64_t &out) const;\n\n  //----------------------------------------------------------------------------\n  //! Check if block has more inodes to give\n  //----------------------------------------------------------------------------\n  bool empty() const;\n\n  //----------------------------------------------------------------------------\n  //! Blacklist all IDs below the given number, including the threshold itself.\n  //----------------------------------------------------------------------------\n  void blacklistBelow(int64_t threshold);\n\nprivate:\n  int64_t mStart;\n  int64_t mLen;\n\n  int64_t mNextId;\n};\n\n//------------------------------------------------------------------------------\n//! Class NextInodeProvider\n//------------------------------------------------------------------------------\nclass NextInodeProvider\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  NextInodeProvider();\n\n  //----------------------------------------------------------------------------\n  //! Configuration method\n  //!\n  //! @param hash hash object to be used\n  //! @param filed filed that we use to get the first free id\n  //----------------------------------------------------------------------------\n  void configure(qclient::QHash& hash, const std::string& field);\n\n  //----------------------------------------------------------------------------\n  //! Get first free id\n  //----------------------------------------------------------------------------\n  int64_t getFirstFreeId();\n\n  //----------------------------------------------------------------------------\n  //! Blacklist all IDs below the given number - from that point on, no IDs\n  //! less or equal to what is specified will be given out.\n  //----------------------------------------------------------------------------\n  void blacklistBelow(int64_t threshold);\n\n  //----------------------------------------------------------------------------\n  //! Method used for reseving a batch of ids and return the first free one\n  //----------------------------------------------------------------------------\n  int64_t reserve();\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Get counter value stored in DB, no caching\n  //----------------------------------------------------------------------------\n  int64_t getDBValue();\n\n  //----------------------------------------------------------------------------\n  //! Allocate new inode block\n  //----------------------------------------------------------------------------\n  void allocateInodeBlock();\n\n  //----------------------------------------------------------------------------\n  //! Blacklist DB threshold\n  //----------------------------------------------------------------------------\n  void blacklistDBThreshold(int64_t threshold);\n\n  std::mutex mMtx;\n  qclient::QHash* pHash; ///< qclient hash - no ownership\n  std::string pField;\n\n  InodeBlock mInodeBlock;\n  int64_t mStepIncrease;\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/RequestBuilder.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Single class which builds redis requests towards the backend.\n//------------------------------------------------------------------------------\n\n#include \"RequestBuilder.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"namespace/utils/StringConvertion.hh\"\n#include \"namespace/utils/Buffer.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Write container protobuf metadata.\n//------------------------------------------------------------------------------\nRedisRequest\nRequestBuilder::writeContainerProto(IContainerMD* obj)\n{\n  eos::Buffer ebuff;\n  obj->serialize(ebuff);\n  std::string buffer(ebuff.getDataPtr(), ebuff.getSize());\n  return writeContainerProto(ContainerIdentifier(obj->getId()),\n                             obj->getLocalityHint(), buffer);\n}\n\n//------------------------------------------------------------------------------\n//! Write container protobuf metadata - low level API.\n//------------------------------------------------------------------------------\nRedisRequest\nRequestBuilder::writeContainerProto(ContainerIdentifier id,\n                                    const std::string& hint, const std::string& blob)\n{\n  std::string sid = stringify(id.getUnderlyingUInt64());\n  return { \"LHSET\", constants::sContainerKey, sid, hint, blob };\n}\n\n//------------------------------------------------------------------------------\n//! Write file protobuf metadata.\n//------------------------------------------------------------------------------\nRedisRequest\nRequestBuilder::writeFileProto(IFileMD* obj)\n{\n  eos::Buffer ebuff;\n  obj->serialize(ebuff);\n  std::string buffer(ebuff.getDataPtr(), ebuff.getSize());\n  return writeFileProto(FileIdentifier(obj->getId()), obj->getLocalityHint(),\n                        buffer);\n}\n\n//------------------------------------------------------------------------------\n//! Write file protobuf metadata - low level API.\n//------------------------------------------------------------------------------\nRedisRequest\nRequestBuilder::writeFileProto(FileIdentifier id, const std::string& hint,\n                               const std::string& blob)\n{\n  std::string sid = stringify(id.getUnderlyingUInt64());\n  return { \"LHSET\", constants::sFileKey, sid, hint, blob };\n}\n\n//------------------------------------------------------------------------------\n//! Read container protobuf metadata.\n//------------------------------------------------------------------------------\nRedisRequest\nRequestBuilder::readContainerProto(ContainerIdentifier id)\n{\n  // TODO(gbitzes): Pass locality hint when available.\n  return { \"LHGET\", constants::sContainerKey, SSTR(id.getUnderlyingUInt64()) };\n}\n\n//------------------------------------------------------------------------------\n//! Read file protobuf metadata.\n//------------------------------------------------------------------------------\nRedisRequest\nRequestBuilder::readFileProto(FileIdentifier id)\n{\n  // TODO(gbitzes): Pass locality hint when available.\n  return { \"LHGET\", constants::sFileKey, SSTR(id.getUnderlyingUInt64()) };\n}\n\n//------------------------------------------------------------------------------\n//! Delete container protobuf metadata.\n//------------------------------------------------------------------------------\nRedisRequest\nRequestBuilder::deleteContainerProto(ContainerIdentifier id)\n{\n  return { \"LHDEL\", constants::sContainerKey, SSTR(id.getUnderlyingUInt64()) };\n}\n\n//------------------------------------------------------------------------------\n//! Delete file protobuf metadata.\n//------------------------------------------------------------------------------\nRedisRequest\nRequestBuilder::deleteFileProto(FileIdentifier id)\n{\n  return { \"LHDEL\", constants::sFileKey, SSTR(id.getUnderlyingUInt64()) };\n}\n\n//------------------------------------------------------------------------------\n//! Calculate number of containers.\n//------------------------------------------------------------------------------\nRedisRequest RequestBuilder::getNumberOfContainers()\n{\n  return { \"LHLEN\", constants::sContainerKey };\n}\n\n//------------------------------------------------------------------------------\n//! Calculate number of files.\n//------------------------------------------------------------------------------\nRedisRequest RequestBuilder::getNumberOfFiles()\n{\n  return { \"LHLEN\", constants::sFileKey };\n}\n\n//------------------------------------------------------------------------------\n// Generate a cache-invalidation notification for a particular fid\n//------------------------------------------------------------------------------\nRedisRequest RequestBuilder::notifyCacheInvalidationFid(FileIdentifier id)\n{\n  return {\"PUBLISH\", constants::sCacheInvalidationFidChannel, SSTR(id.getUnderlyingUInt64()) };\n}\n\n//------------------------------------------------------------------------------\n// Generate a cache-invalidation notification for a particular cid\n//------------------------------------------------------------------------------\nRedisRequest RequestBuilder::notifyCacheInvalidationCid(ContainerIdentifier id)\n{\n  return {\"PUBLISH\", constants::sCacheInvalidationCidChannel, SSTR(id.getUnderlyingUInt64()) };\n}\n\n//------------------------------------------------------------------------------\n//! Get key for files contained within a filesystem.\n//------------------------------------------------------------------------------\nstd::string RequestBuilder::keyFilesystemFiles(IFileMD::location_t location)\n{\n  return fsview::sPrefix + std::to_string(location) + \":\" + fsview::sFilesSuffix;\n}\n\n//------------------------------------------------------------------------------\n//! Get key for unlinked files contained within a filesystem.\n//! (files pending deletion)\n//------------------------------------------------------------------------------\nstd::string RequestBuilder::keyFilesystemUnlinked(IFileMD::location_t location)\n{\n  return fsview::sPrefix + std::to_string(location) + \":\" +\n         fsview::sUnlinkedSuffix;\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/RequestBuilder.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Single class which builds redis requests towards the backend.\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/Identifiers.hh\"\n#include <vector>\n#include <string>\n\nEOSNSNAMESPACE_BEGIN\n\nclass IContainerMD;\nclass IFileMD;\n\nusing RedisRequest = std::vector<std::string>;\n\nclass RequestBuilder\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Write container protobuf metadata.\n  //----------------------------------------------------------------------------\n  static RedisRequest writeContainerProto(IContainerMD* obj);\n\n  //----------------------------------------------------------------------------\n  //! Write container protobuf metadata - low level API.\n  //----------------------------------------------------------------------------\n  static RedisRequest writeContainerProto(ContainerIdentifier id,\n                                          const std::string& hint, const std::string& blob);\n\n  //----------------------------------------------------------------------------\n  //! Write file protobuf metadata.\n  //----------------------------------------------------------------------------\n  static RedisRequest writeFileProto(IFileMD* obj);\n\n  //----------------------------------------------------------------------------\n  //! Write file protobuf metadata - low level API.\n  //----------------------------------------------------------------------------\n  static RedisRequest writeFileProto(FileIdentifier id, const std::string& hint,\n                                     const std::string& blob);\n\n  //----------------------------------------------------------------------------\n  //! Read container protobuf metadata.\n  //----------------------------------------------------------------------------\n  static RedisRequest readContainerProto(ContainerIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Read file protobuf metadata.\n  //----------------------------------------------------------------------------\n  static RedisRequest readFileProto(FileIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Delete container protobuf metadata.\n  //----------------------------------------------------------------------------\n  static RedisRequest deleteContainerProto(ContainerIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Delete file protobuf metadata.\n  //----------------------------------------------------------------------------\n  static RedisRequest deleteFileProto(FileIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Calculate number of containers.\n  //----------------------------------------------------------------------------\n  static RedisRequest getNumberOfContainers();\n\n  //----------------------------------------------------------------------------\n  //! Calculate number of files.\n  //----------------------------------------------------------------------------\n  static RedisRequest getNumberOfFiles();\n\n  //----------------------------------------------------------------------------\n  //! Generate a cache-invalidation notification for a particular fid\n  //----------------------------------------------------------------------------\n  static RedisRequest notifyCacheInvalidationFid(FileIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Generate a cache-invalidation notification for a particular cid\n  //----------------------------------------------------------------------------\n  static RedisRequest notifyCacheInvalidationCid(ContainerIdentifier id);\n\n  //----------------------------------------------------------------------------\n  //! Get key for files contained within a filesystem.\n  //----------------------------------------------------------------------------\n  static std::string keyFilesystemFiles(IFileMD::location_t location);\n\n  //----------------------------------------------------------------------------\n  //! Get key for unlinked files contained within a filesystem.\n  //! (files pending deletion)\n  //----------------------------------------------------------------------------\n  static std::string keyFilesystemUnlinked(IFileMD::location_t location);\n};\n\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/Serialization.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Class to retrieve metadata from the backend - no caching!\n//------------------------------------------------------------------------------\n\n#include <google/protobuf/io/zero_copy_stream_impl.h>\n\n#include \"namespace/ns_quarkdb/persistency/Serialization.hh\"\n#include \"namespace/utils/Buffer.hh\"\n#include \"namespace/utils/DataHelper.hh\"\n#include \"proto/ContainerMd.pb.h\"\n#include \"proto/FileMd.pb.h\"\n\nEOSNSNAMESPACE_BEGIN\n\nMDStatus\nSerialization::deserializeNoThrow(const Buffer& buffer, eos::ns::FileMdProto &proto)\n{\n  uint32_t cksum_expected = 0;\n  uint32_t obj_size = 0;\n  size_t sz = sizeof(cksum_expected);\n  const char* ptr = buffer.getDataPtr();\n  (void) memcpy(&cksum_expected, ptr, sz);\n  ptr += sz;\n  (void) memcpy(&obj_size, ptr, sz);\n  size_t msg_size = buffer.getSize();\n  uint32_t align_size = msg_size - 2 * sz;\n  ptr += sz; // now pointing to the serialized object\n  uint32_t cksum_computed = DataHelper::computeCRC32C((void*)ptr, align_size);\n  cksum_computed = DataHelper::finalizeCRC32C(cksum_computed);\n\n  if (cksum_expected != cksum_computed) {\n    return MDStatus(EIO, \"FileMD object checksum mismatch\");\n  }\n\n  google::protobuf::io::ArrayInputStream ais(ptr, obj_size);\n\n  if (!proto.ParseFromZeroCopyStream(&ais)) {\n    return MDStatus(EIO, \"Failed while deserializing FileMD buffer\");\n  }\n\n  return {};\n}\n\nMDStatus\nSerialization::deserializeNoThrow(const Buffer& buffer, eos::ns::ContainerMdProto &proto)\n{\n  uint32_t cksum_expected = 0;\n  uint32_t obj_size = 0;\n  size_t sz = sizeof(cksum_expected);\n  const char* ptr = buffer.getDataPtr();\n  (void) memcpy(&cksum_expected, ptr, sz);\n  ptr += sz;\n  (void) memcpy(&obj_size, ptr, sz);\n  size_t msg_size = buffer.getSize();\n  uint32_t align_size = msg_size - 2 * sz;\n  ptr += sz; // now pointing to the serialized object\n  uint32_t cksum_computed = DataHelper::computeCRC32C((void*)ptr, align_size);\n  cksum_computed = DataHelper::finalizeCRC32C(cksum_computed);\n\n  if (cksum_expected != cksum_computed) {\n    return MDStatus(EIO, \"ContainerMD object checksum mismatch\");\n  }\n\n  google::protobuf::io::ArrayInputStream ais(ptr, obj_size);\n\n  if (!proto.ParseFromZeroCopyStream(&ais)) {\n    return MDStatus(EIO, \"Failed while deserializing ContainerMD buffer\");\n  }\n\n  return {};\n}\n\nMDStatus\nSerialization::deserializeNoThrow(const Buffer& buffer, int64_t &ret) {\n  // Ensure there's a terminating null byte for strtoll.. :(\n  std::string str(buffer.getDataPtr(), buffer.getSize());\n\n  char *endptr = NULL;\n  ret = strtoll(str.c_str(), &endptr, 10);\n  if(endptr != str.c_str() + str.size() || ret == LLONG_MIN || ret == LONG_LONG_MAX) {\n    return MDStatus(EFAULT, SSTR(\"Unable to deserialize into int64_t (size = \" << str.size() << \"): '\" << str << \"'\"));\n  }\n  return {};\n}\n\n\n\nvoid Serialization::deserializeFile(const Buffer& buffer, eos::ns::FileMdProto &proto) {\n  MDStatus status = deserializeNoThrow(buffer, proto);\n  status.throwIfNotOk();\n}\n\nvoid Serialization::deserializeContainer(const Buffer& buffer, eos::ns::ContainerMdProto &proto) {\n  MDStatus status = deserializeNoThrow(buffer, proto);\n  status.throwIfNotOk();\n}\n\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/Serialization.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Class to serialize metadata to/from protobufs.\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"namespace/MDException.hh\"\n#include \"namespace/utils/Buffer.hh\"\n\nnamespace eos\n{\nnamespace ns\n{\nclass ContainerMdProto;\nclass FileMdProto;\n}\n}\n\nEOSNSNAMESPACE_BEGIN\n\nclass Buffer;\n\nclass Serialization\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Deserialize a FileMD protobuf\n  //----------------------------------------------------------------------------\n  static void deserializeFile(const Buffer& buffer, eos::ns::FileMdProto& proto);\n  static MDStatus deserializeNoThrow(const Buffer& buffer,\n                                     eos::ns::FileMdProto& proto);\n\n  //----------------------------------------------------------------------------\n  //! Deserialize a ContainerMD protobuf\n  //----------------------------------------------------------------------------\n  static void deserializeContainer(const Buffer& buffer,\n                                   eos::ns::ContainerMdProto& proto);\n\n  static MDStatus deserializeNoThrow(const Buffer& buffer,\n                                     eos::ns::ContainerMdProto& proto);\n\n  //----------------------------------------------------------------------------\n  //! Deserialize an int64_t\n  //----------------------------------------------------------------------------\n  static MDStatus deserializeNoThrow(const Buffer& buffer, int64_t& val);\n\n  //----------------------------------------------------------------------------\n  //! Deserialize any supported type.\n  //----------------------------------------------------------------------------\n  template<typename T>\n  static MDStatus deserialize(const char* str, size_t len, T& output)\n  {\n    eos::Buffer ebuff;\n    ebuff.setDataPtr((char*)str, len);\n    // Dispatch to appropriate overload\n    return Serialization::deserializeNoThrow(ebuff, output);\n  }\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/UnifiedInodeProvider.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief  Inode provider used both for directories and files.\n//------------------------------------------------------------------------------\n\n#include \"namespace/ns_quarkdb/persistency/UnifiedInodeProvider.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\nUnifiedInodeProvider::UnifiedInodeProvider() {\n}\n\nvoid UnifiedInodeProvider::configure(qclient::QHash &metamap) {\n\n  mMetaMap = &metamap;\n\n  std::string useSharedInodes = mMetaMap->hget(constants::sUseSharedInodes);\n\n  if(useSharedInodes == \"yes\") {\n    mSharedInodes = true;\n\n    mFileIdProvider.reset(new NextInodeProvider());\n    mFileIdProvider->configure(*mMetaMap, constants::sLastUsedFid);\n  }\n  else {\n    mFileIdProvider.reset(new NextInodeProvider());\n    mFileIdProvider->configure(*mMetaMap, constants::sLastUsedFid);\n\n    mContainerIdProvider.reset(new NextInodeProvider());\n    mContainerIdProvider->configure(*mMetaMap, constants::sLastUsedCid);\n  }\n}\n\nint64_t UnifiedInodeProvider::reserveFileId() {\n  return mFileIdProvider->reserve();\n}\n\nint64_t UnifiedInodeProvider::reserveContainerId() {\n  if(mSharedInodes) {\n    return mFileIdProvider->reserve();\n  }\n  return mContainerIdProvider->reserve();\n}\n\nvoid UnifiedInodeProvider::blacklistContainerId(int64_t inode) {\n  if(mSharedInodes) {\n    return mFileIdProvider->blacklistBelow(inode);\n  }\n  return mContainerIdProvider->blacklistBelow(inode);\n}\n\nvoid UnifiedInodeProvider::blacklistFileId(int64_t inode) {\n  return mFileIdProvider->blacklistBelow(inode);\n}\n\nint64_t UnifiedInodeProvider::getFirstFreeFileId() {\n  return mFileIdProvider->getFirstFreeId();\n}\n\nint64_t UnifiedInodeProvider::getFirstFreeContainerId() {\n  if(mSharedInodes) {\n    return mFileIdProvider->getFirstFreeId();\n  }\n  return mContainerIdProvider->getFirstFreeId();\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/persistency/UnifiedInodeProvider.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief  Inode provider used both for directories and files.\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/Namespace.hh\"\n#include \"namespace/ns_quarkdb/persistency/NextInodeProvider.hh\"\n#include <mutex>\n#include <qclient/structures/QHash.hh>\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class UnifiedInodeProvider\n//------------------------------------------------------------------------------\nclass UnifiedInodeProvider {\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  UnifiedInodeProvider();\n\n  //----------------------------------------------------------------------------\n  //! Configure\n  //----------------------------------------------------------------------------\n  void configure(qclient::QHash& hash);\n\n  //----------------------------------------------------------------------------\n  //! Reserve file id\n  //----------------------------------------------------------------------------\n  int64_t reserveFileId();\n\n  //----------------------------------------------------------------------------\n  //! Reserve container id\n  //----------------------------------------------------------------------------\n  int64_t reserveContainerId();\n\n  //----------------------------------------------------------------------------\n  //! Blacklist container ID\n  //----------------------------------------------------------------------------\n  void blacklistContainerId(int64_t inode);\n\n  //----------------------------------------------------------------------------\n  //! Blacklist file ID\n  //----------------------------------------------------------------------------\n  void blacklistFileId(int64_t inode);\n\n  //----------------------------------------------------------------------------\n  //! Get first free file ID\n  //----------------------------------------------------------------------------\n  int64_t getFirstFreeFileId();\n\n  //----------------------------------------------------------------------------\n  //! Get first free container ID\n  //----------------------------------------------------------------------------\n  int64_t getFirstFreeContainerId();\n\nprivate:\n  bool mSharedInodes = false;\n  qclient::QHash *mMetaMap = nullptr;\n\n  std::unique_ptr<NextInodeProvider> mFileIdProvider;\n  std::unique_ptr<NextInodeProvider> mContainerIdProvider;\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/tests/CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2016 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\ninclude_directories(\n  ${CMAKE_SOURCE_DIR})\n\n#-------------------------------------------------------------------------------\n# QuarkDB namespace unit tests\n#\n# Note: These tests require a running QuarkDB instance\n#-------------------------------------------------------------------------------\nadd_executable(\n  eos-ns-quarkdb-tests\n  ContainerMDSvcTest.cc\n  FileMDSvcTest.cc\n  FileSystemViewTest.cc\n  HierarchicalViewTest.cc\n  Main.cc\n  MetadataFiltering.cc\n  MetadataTests.cc\n  NextInodeProviderTest.cc\n  OtherTests.cc\n  NsTests.cc\n  VariousTests.cc)\n\ntarget_link_libraries(\n  eos-ns-quarkdb-tests\n  GTest::GTest\n  GTest::gmock_main\n  EosNsCommon-Static\n  FOLLY::FOLLY)\n\ninstall(TARGETS eos-ns-quarkdb-tests\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR})\n\n#-------------------------------------------------------------------------------\n# eosnsbench executable\n#-------------------------------------------------------------------------------\nadd_executable(eosnsbench EosNamespaceBenchmark.cc)\ntarget_compile_options(eosnsbench PUBLIC -DFILE_OFFSET_BITS=64)\ntarget_link_libraries(eosnsbench PRIVATE EosNsCommon-Static)\nadd_executable(eos-lru-benchmark LruBenchmark.cc)\ntarget_link_libraries(eos-lru-benchmark EosCommon)\n\ninstall(TARGETS eosnsbench eos-lru-benchmark\n  LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}\n  ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})\n"
  },
  {
    "path": "namespace/ns_quarkdb/tests/ContainerMDSvcTest.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief File metadata service tests\n//------------------------------------------------------------------------------\n\n#include \"namespace/ns_quarkdb/persistency/ContainerMDSvc.hh\"\n#include \"namespace/ns_quarkdb/persistency/FileMDSvc.hh\"\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n#include \"namespace/ns_quarkdb/views/HierarchicalView.hh\"\n#include \"namespace/ns_quarkdb/tests/TestUtils.hh\"\n#include <gtest/gtest.h>\n#include <memory>\n\nclass ContainerMDSvcF : public eos::ns::testing::NsTestsFixture {};\n\nTEST_F(ContainerMDSvcF, BasicSanity)\n{\n  std::shared_ptr<eos::IContainerMD> container1 = containerSvc()->getContainerMD(1);\n  std::shared_ptr<eos::IContainerMD> container2 = containerSvc()->createContainer(0);\n  std::shared_ptr<eos::IContainerMD> container3 = containerSvc()->createContainer(0);\n  std::shared_ptr<eos::IContainerMD> container4 = containerSvc()->createContainer(0);\n  std::shared_ptr<eos::IContainerMD> container5 = containerSvc()->createContainer(0);\n\n  eos::IContainerMD::id_t id = container1->getId();\n  container1->setName(\"root\");\n  container1->setParentId(container1->getId());\n  container2->setName(\"subContLevel1-1\");\n  container3->setName(\"subContLevel1-2\");\n  container4->setName(\"subContLevel2-1\");\n  container5->setName(\"subContLevel2-2\");\n  container5->setCUid(17);\n  container5->setCGid(17);\n  container5->setMode(0750);\n  ASSERT_TRUE(container5->access(17, 12, X_OK | R_OK | W_OK));\n  ASSERT_TRUE(container5->access(17, 12, X_OK | R_OK));\n  ASSERT_TRUE(!container5->access(12, 17, X_OK | R_OK | W_OK));\n  ASSERT_TRUE(!container5->access(12, 17, X_OK | W_OK));\n  ASSERT_TRUE(container5->access(12, 17, X_OK | R_OK));\n  ASSERT_TRUE(!container5->access(12, 12, X_OK | R_OK));\n  container1->addContainer(container2.get());\n  container1->addContainer(container3.get());\n  container3->addContainer(container4.get());\n  container3->addContainer(container5.get());\n  ASSERT_EQ((size_t)2, container1->getNumContainers());\n  ASSERT_EQ((size_t)0, container1->getNumFiles());\n  containerSvc()->updateStore(container1.get());\n  containerSvc()->updateStore(container2.get());\n  containerSvc()->updateStore(container3.get());\n  containerSvc()->updateStore(container4.get());\n  containerSvc()->updateStore(container5.get());\n  mdFlusher()->synchronize();\n\n  ASSERT_EQ((size_t)5, containerSvc()->getNumContainers());\n  container3->removeContainer(\"subContLevel2-2\");\n  containerSvc()->removeContainer(container5.get());\n\n  mdFlusher()->synchronize();\n  ASSERT_EQ((size_t)1, container3->getNumContainers());\n  ASSERT_EQ((size_t)4, containerSvc()->getNumContainers());\n  std::shared_ptr<eos::IContainerMD> container6 = containerSvc()->createContainer(0);\n  container6->setName(\"subContLevel2-3\");\n  container3->addContainer(container6.get());\n  containerSvc()->updateStore(container6.get());\n  eos::IContainerMD::id_t idAttr = container4->getId();\n  container4->setAttribute(\"test1\", \"test1\");\n  container4->setAttribute(\"test1\", \"test11\");\n  container4->setAttribute(\"test2\", \"test2\");\n  container4->setAttribute(\"test3\", \"test3\");\n  containerSvc()->updateStore(container4.get());\n  ASSERT_EQ((size_t)3, container4->numAttributes());\n  ASSERT_TRUE(container4->getAttribute(\"test1\") == \"test11\");\n  ASSERT_TRUE(container4->getAttribute(\"test3\") == \"test3\");\n  ASSERT_THROW(container4->getAttribute(\"test15\"), eos::MDException);\n\n  shut_down_everything();\n\n  std::shared_ptr<eos::IContainerMD> cont1 = containerSvc()->getContainerMD(id);\n  ASSERT_EQ(cont1->getName(), \"root\");\n  std::shared_ptr<eos::IContainerMD> cont2 = cont1->findContainer(\"subContLevel1-1\");\n  ASSERT_NE(cont2, nullptr);\n  ASSERT_EQ(cont2->getName(), \"subContLevel1-1\");\n  cont2 = cont1->findContainer(\"subContLevel1-2\");\n  ASSERT_NE(cont2, nullptr);\n  ASSERT_EQ(cont2->getName(), \"subContLevel1-2\");\n  cont1 = cont2->findContainer(\"subContLevel2-1\");\n  ASSERT_NE(cont1, nullptr);\n  ASSERT_EQ(cont1->getName(), \"subContLevel2-1\");\n  cont1 = cont2->findContainer(\"subContLevel2-2\");\n  ASSERT_EQ(cont1, nullptr);\n  cont1 = cont2->findContainer(\"subContLevel2-3\");\n  ASSERT_NE(cont1, nullptr);\n  ASSERT_EQ(cont1->getName(), \"subContLevel2-3\");\n  std::shared_ptr<eos::IContainerMD> contAttrs = containerSvc()->getContainerMD(idAttr);\n  ASSERT_EQ(contAttrs->numAttributes(), 3);\n  ASSERT_EQ(contAttrs->getAttribute(\"test1\"), \"test11\");\n  ASSERT_EQ(contAttrs->getAttribute(\"test3\"), \"test3\");\n  ASSERT_THROW(contAttrs->getAttribute(\"test15\"), eos::MDException);\n  // Clean up all the containers\n  container1 = containerSvc()->getContainerMD(1);\n  container2 = containerSvc()->getContainerMD(2);\n  container3 = containerSvc()->getContainerMD(3);\n  container4 = containerSvc()->getContainerMD(4);\n\n  container3->removeContainer(container6->getName());\n  container3->removeContainer(container4->getName());\n  container1->removeContainer(container3->getName());\n  container1->removeContainer(container2->getName());\n  containerSvc()->removeContainer(container6.get());\n  containerSvc()->removeContainer(container4.get());\n  containerSvc()->removeContainer(container3.get());\n  containerSvc()->removeContainer(container2.get());\n  containerSvc()->removeContainer(container1.get());\n  mdFlusher()->synchronize();\n  ASSERT_EQ((uint64_t)0, containerSvc()->getNumContainers());\n}\n\nTEST_F(ContainerMDSvcF, getContainerMDWhenContIsLockedShouldNotLock) {\n  auto cont = view()->createContainer(\"/root/\");\n  auto id = cont->getId();\n  eos::MDLocking::ContainerWriteLock contLock(cont.get());\n\n  std::thread t([this,id](){\n    // Here, we just check that the getContainerMD(id) does not lock\n    // despite having the container being locked\n    auto cont = containerSvc()->getContainerMD(id);\n  });\n  t.join();\n}"
  },
  {
    "path": "namespace/ns_quarkdb/tests/EosNamespaceBenchmark.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdSys/XrdSysPthread.hh>\n#include \"common/LinuxStat.hh\"\n#include \"common/LinuxMemConsumption.hh\"\n#include \"common/RWMutex.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/Timing.hh\"\n#include \"namespace/ns_quarkdb/persistency/ContainerMDSvc.hh\"\n#include \"namespace/ns_quarkdb/persistency/FileMDSvc.hh\"\n#include \"namespace/ns_quarkdb/views/HierarchicalView.hh\"\n#include <iostream>\n#include <string>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n\neos::common::RWMutex nslock;\n\n#if 0\n\n//------------------------------------------------------------------------------\n// File size mapping function\n//------------------------------------------------------------------------------\nstatic uint64_t\nmapSize(const eos::IFileMD* /*file*/)\n{\n  return 0u;\n}\n\n//------------------------------------------------------------------------------\n// Boot the namespace\n//------------------------------------------------------------------------------\neos::IView*\nbootNamespace(const std::map<std::string, std::string>& config)\n{\n  eos::IContainerMDSvc* contSvc = new eos::QuarkContainerMDSvc();\n  eos::IFileMDSvc* fileSvc = new eos::QuarkFileMDSvc();\n  eos::IView* view = new eos::QuarkHierarchicalView();\n  fileSvc->configure(config);\n  contSvc->configure(config);\n  fileSvc->setContMDService(contSvc);\n  contSvc->setFileMDService(fileSvc);\n  view->setContainerMDSvc(contSvc);\n  view->setFileMDSvc(fileSvc);\n  view->configure(config);\n  view->getQuotaStats()->registerSizeMapper(mapSize);\n  view->initialize();\n  return view;\n}\n\n//------------------------------------------------------------------------------\n// Close the namespace\n//------------------------------------------------------------------------------\nvoid\ncloseNamespace(eos::IView* view)\n{\n  eos::IContainerMDSvc* contSvc = view->getContainerMDSvc();\n  eos::IFileMDSvc* fileSvc = view->getFileMDSvc();\n  view->finalize();\n  delete view;\n  delete contSvc;\n  delete fileSvc;\n}\n\n//------------------------------------------------------------------------------\n// Print current namespace status\n//------------------------------------------------------------------------------\nvoid\nPrintStatus(eos::IView* view, eos::common::LinuxStat::linux_stat_t* st1,\n            eos::common::LinuxStat::linux_stat_t* st2,\n            eos::common::LinuxMemConsumption::linux_mem_t* /*mem1*/,\n            eos::common::LinuxMemConsumption::linux_mem_t* mem2,\n            const double& rate, bool print_total = false)\n{\n  XrdOucString sizestring;\n  XrdOucString stdOut;\n  eos::IContainerMDSvc* contSvc = view->getContainerMDSvc();\n  eos::IFileMDSvc* fileSvc = view->getFileMDSvc();\n  unsigned long long f = 0, d = 0;\n\n  if (print_total) {\n    f = static_cast<unsigned long long>(fileSvc->getNumFiles());\n    d = static_cast<unsigned long long>(contSvc->getNumContainers());\n  }\n\n  char files[256];\n  snprintf(static_cast<char*>(files), sizeof(files) - 1, \"%llu\", f);\n  char dirs[256];\n  snprintf(static_cast<char*>(dirs), sizeof(dirs) - 1, \"%llu\", d);\n  stdOut += \"# -------------------------------------------------------------\\n\";\n  stdOut += \"ALL      Files                            \";\n  stdOut += static_cast<char*>(files);\n  stdOut += \"\\n\";\n  stdOut += \"ALL      Directories                      \";\n  stdOut += static_cast<char*>(dirs);\n  stdOut += \"\\n\";\n  stdOut += \"# -------------------------------------------------------------\\n\";\n  stdOut += \"ALL      memory virtual                   \";\n  stdOut += eos::common::StringConversion::GetReadableSizeString(\n              sizestring, mem2->vmsize, \"B\");\n  stdOut += \"\\n\";\n  stdOut += \"ALL      memory resident                  \";\n  stdOut += eos::common::StringConversion::GetReadableSizeString(\n              sizestring, mem2->resident, \"B\");\n  stdOut += \"\\n\";\n  stdOut += \"ALL      memory share                     \";\n  stdOut += eos::common::StringConversion::GetReadableSizeString(\n              sizestring, mem2->share, \"B\");\n  stdOut += \"\\n\";\n  stdOut += \"ALL      memory growths                   \";\n  stdOut += eos::common::StringConversion::GetReadableSizeString(\n              sizestring, (st2->vsize - st1->vsize), \"B\");\n  stdOut += \"\\n\";\n  stdOut += \"# -------------------------------------------------------------\\n\";\n  stdOut += \"ALL      rate                             \";\n  char srate[256];\n  snprintf(static_cast<char*>(srate), sizeof(srate) - 1, \"%.02f\", rate);\n  stdOut += static_cast<char*>(srate);\n  stdOut += \"\\n\";\n  stdOut += \"# -------------------------------------------------------------\\n\";\n  fprintf(stderr, \"%s\", stdOut.c_str());\n}\n\nclass RThread\n{\npublic:\n  RThread() = default;\n  ~RThread() = default;\n  RThread(size_t a, size_t b, size_t c, size_t d, eos::IView* iview,\n          bool lock = false)\n  {\n    i = a;\n    n_j = b;\n    n_k = c;\n    n_files = d;\n    view = iview;\n    dolock = lock;\n  }\n\n  size_t i;\n  size_t n_j;\n  size_t n_k;\n  size_t n_files;\n  bool dolock;\n  eos::IView* view;\n};\n\n//----------------------------------------------------------------------------\n// Start namespace consumer thread\n//----------------------------------------------------------------------------\nstatic void*\nRunReader(void* tconf)\n{\n  RThread* r = static_cast<RThread*>(tconf);\n  size_t i = r->i;\n  size_t n_j = r->n_j;\n  size_t n_k = r->n_k;\n  size_t n_files = r->n_files;\n  eos::IView* view = r->view;\n  bool dolock = r->dolock;\n\n  try {\n    for (size_t j = 0; j < n_j; j++) {\n      for (size_t k = 0; k < n_k; k++) {\n        for (size_t n = 0; n < n_files; n++) {\n          char s_file_path[1024];\n          snprintf(static_cast<char*>(s_file_path), sizeof(s_file_path) - 1,\n                   \"/eos/nsbench/level_0_%08u/\"\n                   \"level_1_%08u/level_2_%08u/file____________________%08u\",\n                   static_cast<unsigned int>(i), static_cast<unsigned int>(j),\n                   static_cast<unsigned int>(k), static_cast<unsigned int>(n));\n          std::string file_path = static_cast<char*>(s_file_path);\n\n          if (dolock) {\n            nslock.LockRead();\n          }\n\n          std::shared_ptr<eos::IFileMD> fmd = view->getFile(file_path);\n\n          if (fmd) {\n            unsigned long long size = fmd->getSize();\n            (void) size;\n          }\n\n          if (dolock) {\n            nslock.UnLockRead();\n          }\n        }\n      }\n    }\n  } catch (eos::MDException& e) {\n    std::cerr << \"[!] Error: \" << e.getMessage().str() << std::endl;\n  }\n\n  return nullptr;\n}\n\n//------------------------------------------------------------------------------\n// Main function\n//----------------------------------------------------------------------------\nint\nmain(int argc, char** argv)\n{\n  // Check up the commandline params\n  if (argc != 5) {\n    std::cerr << \"Usage:\" << std::endl;\n    std::cerr << \"  eos-namespace-benchmark <qdb_host> <qdb_port> \"\n              << \"<level1-dirs> <level3-files> \" << std::endl;\n    return 1;\n  }\n\n  std::map<std::string, std::string> config = {{\"qdb_host\", argv[1]},\n    {\"qdb_port\", argv[2]}\n  };\n  size_t n_i = std::stoi(argv[3]);\n  size_t n_j = 64;\n  size_t n_k = 64;\n  size_t n_files = std::stoi(argv[4]);\n\n  // Create Namespace and populate dirs\n  try {\n    std::cerr << \"# ***********************************************************\"\n              << std::endl;\n    std::cerr << \"[i] Initialize Directory Namespace...\" << std::endl;\n    std::cerr << \"# ***********************************************************\"\n              << std::endl;\n    eos::IView* view = bootNamespace(config);\n    eos::common::LinuxStat::linux_stat_t st[10];\n    ;\n    eos::common::LinuxMemConsumption::linux_mem_t mem[10];\n    eos::common::LinuxStat::GetStat(st[0]);\n    eos::common::LinuxMemConsumption::GetMemoryFootprint(mem[0]);\n    eos::common::Timing tm(\"directories\");\n    COMMONTIMING(\"dir-start\", &tm);\n\n    for (size_t i = 0; i < n_i; i++) {\n      fprintf(stderr, \"# Level %02u\\n\", static_cast<unsigned int>(i));\n      XrdOucString l = \"dir-level-\";\n      l += static_cast<int>(i);\n      COMMONTIMING(l.c_str(), &tm);\n\n      for (size_t j = 0; j < n_j; j++) {\n        for (size_t k = 0; k < n_k; k++) {\n          char s_container_path[1024];\n          snprintf(static_cast<char*>(s_container_path), sizeof(s_container_path) - 1,\n                   \"/eos/nsbench/level_0_%08u/level_1_%08u/level_2_%08u/\",\n                   static_cast<unsigned int>(i), static_cast<unsigned int>(j),\n                   static_cast<unsigned int>(k));\n          std::string container_path = static_cast<char*>(s_container_path);\n          std::shared_ptr<eos::IContainerMD> cont =\n            view->createContainer(container_path, true);\n          cont->setAttribute(\"sys.forced.blocksize\", \"4k\");\n          cont->setAttribute(\"sys.forced.checksum\", \"adler\");\n          cont->setAttribute(\"sys.forced.layout\", \"replica\");\n          cont->setAttribute(\"sys.forced.nstripes\", \"2\");\n          cont->setAttribute(\"user.acl\",\n                             \"u:atlas003:rw,egroup:atlas-comp-cern-storage-support:rw\");\n          view->updateContainerStore(cont.get());\n        }\n      }\n    }\n\n    eos::common::LinuxStat::GetStat(st[1]);\n    eos::common::LinuxMemConsumption::GetMemoryFootprint(mem[1]);\n    COMMONTIMING(\"dir-stop\", &tm);\n    tm.Print();\n    double rate = (n_i * n_j * n_k) / tm.RealTime() * 1000.0;\n    PrintStatus(view, &st[0], &st[1], &mem[0], &mem[1], rate);\n    closeNamespace(view);\n  } catch (eos::MDException& e) {\n    std::cerr << \"[!] Error: \" << e.getMessage().str() << std::endl;\n    return 2;\n  }\n\n  // Fill namespace with files\n  try {\n    std::cerr << \"# ***********************************************************\"\n              << std::endl;\n    std::cerr << \"[i] Initialize File Namespace ...\" << std::endl;\n    std::cerr << \"# ***********************************************************\"\n              << std::endl;\n    eos::IView* view = bootNamespace(config);\n    eos::common::LinuxStat::linux_stat_t st[10];\n    ;\n    eos::common::LinuxMemConsumption::linux_mem_t mem[10];\n    eos::common::LinuxStat::GetStat(st[0]);\n    eos::common::LinuxMemConsumption::GetMemoryFootprint(mem[0]);\n    eos::common::Timing tm(\"files\");\n    COMMONTIMING(\"dir-start\", &tm);\n\n    for (size_t i = 0; i < n_i; i++) {\n      fprintf(stderr, \"# Level %02u\\n\", static_cast<unsigned int>(i));\n      XrdOucString l = \"dir-level-\";\n      l += static_cast<int>(i);\n      COMMONTIMING(l.c_str(), &tm);\n\n      for (size_t j = 0; j < n_j; j++) {\n        for (size_t k = 0; k < n_k; k++) {\n          for (size_t n = 0; n < n_files; n++) {\n            char s_file_path[1024];\n            snprintf(static_cast<char*>(s_file_path), sizeof(s_file_path) - 1,\n                     \"/eos/nsbench/level_0_%08u/\"\n                     \"level_1_%08u/level_2_%08u/file____________________%08u\",\n                     static_cast<unsigned int>(i), static_cast<unsigned int>(j),\n                     static_cast<unsigned int>(k), static_cast<unsigned int>(n));\n            std::string file_path = static_cast<char*>(s_file_path);\n            std::shared_ptr<eos::IFileMD> fmd =\n              view->createFile(file_path, 0, 0);\n            // add two locations\n            fmd->addLocation(k);\n            fmd->addLocation(k + 1);\n            // fmd->addLocation(k+2);\n            // fmd->addLocation(k+3);\n            // fmd->addLocation(k+4);\n            // fmd->addLocation(k+5);\n            fmd->setLayoutId(10);\n            view->updateFileStore(fmd.get());\n          }\n        }\n      }\n    }\n\n    eos::common::LinuxStat::GetStat(st[1]);\n    eos::common::LinuxMemConsumption::GetMemoryFootprint(mem[1]);\n    COMMONTIMING(\"dir-stop\", &tm);\n    tm.Print();\n    double rate = (n_files * n_i * n_j * n_k) / tm.RealTime() * 1000.0;\n    PrintStatus(view, &st[0], &st[1], &mem[0], &mem[1], rate);\n    closeNamespace(view);\n  } catch (eos::MDException& e) {\n    std::cerr << \"[!] Error: \" << e.getMessage().str() << std::endl;\n    return 2;\n  }\n\n  eos::IView* view = nullptr;\n  // Run a parallel consumer thread benchmark without locking\n  {\n    eos::common::LinuxStat::linux_stat_t st[10];\n    ;\n    eos::common::LinuxMemConsumption::linux_mem_t mem[10];\n    std::cerr << \"# ***********************************************************\"\n              << std::endl;\n    std::cerr << \"[i] Parallel reader benchmark without locking  ...\"\n              << std::endl;\n    std::cerr << \"# ***********************************************************\"\n              << std::endl;\n    view = bootNamespace(config);\n    eos::common::LinuxStat::GetStat(st[0]);\n    eos::common::LinuxMemConsumption::GetMemoryFootprint(mem[0]);\n    eos::common::Timing tm(\"reading\");\n    COMMONTIMING(\"read-start\", &tm);\n    pthread_t tid[1024];\n\n    // fire threads\n    for (size_t i = 0; i < n_i; i++) {\n      fprintf(stderr, \"# Level %02u\\n\", static_cast<unsigned int>(i));\n      RThread r(i, n_j, n_k, n_files, view);\n      XrdSysThread::Run(&tid[i], RunReader, static_cast<void*>(&r),\n                        XRDSYSTHREAD_HOLD, \"Reader Thread\");\n    }\n\n    // join them\n    for (size_t i = 0; i < n_i; i++) {\n      XrdSysThread::Join(tid[i], nullptr);\n    }\n\n    eos::common::LinuxStat::GetStat(st[1]);\n    eos::common::LinuxMemConsumption::GetMemoryFootprint(mem[1]);\n    COMMONTIMING(\"read-stop\", &tm);\n    tm.Print();\n    double rate = (n_files * n_i * n_j * n_k) / tm.RealTime() * 1000.0;\n    PrintStatus(view, &st[0], &st[1], &mem[0], &mem[1], rate);\n  }\n  // Run a parallel consumer thread benchmark with namespace locking\n  {\n    eos::common::LinuxStat::linux_stat_t st[10];\n    ;\n    eos::common::LinuxMemConsumption::linux_mem_t mem[10];\n    std::cerr << \"# ***********************************************************\"\n              << std::endl;\n    std::cerr << \"[i] Parallel reader benchmark with locking  ...\" << std::endl;\n    std::cerr << \"# ***********************************************************\"\n              << std::endl;\n    eos::common::LinuxStat::GetStat(st[0]);\n    eos::common::LinuxMemConsumption::GetMemoryFootprint(mem[0]);\n    eos::common::Timing tm(\"reading\");\n    COMMONTIMING(\"read-lock-start\", &tm);\n    pthread_t tid[1024];\n\n    // fire threads\n    for (size_t i = 0; i < n_i; i++) {\n      fprintf(stderr, \"# Level %02u\\n\", static_cast<unsigned int>(i));\n      RThread r(i, n_j, n_k, n_files, view, true);\n      XrdSysThread::Run(&tid[i], RunReader, static_cast<void*>(&r),\n                        XRDSYSTHREAD_HOLD, \"Reader Thread\");\n    }\n\n    // join them\n    for (size_t i = 0; i < n_i; i++) {\n      XrdSysThread::Join(tid[i], nullptr);\n    }\n\n    eos::common::LinuxStat::GetStat(st[1]);\n    eos::common::LinuxMemConsumption::GetMemoryFootprint(mem[1]);\n    COMMONTIMING(\"read-lock-stop\", &tm);\n    tm.Print();\n    double rate = (n_files * n_i * n_j * n_k) / tm.RealTime() * 1000.0;\n    PrintStatus(view, &st[0], &st[1], &mem[0], &mem[1], rate);\n  }\n  return 0;\n}\n\n#endif\n\nint main() {\n  return 0;\n}"
  },
  {
    "path": "namespace/ns_quarkdb/tests/FileMDSvcTest.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief File metadata service class test\n//------------------------------------------------------------------------------\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"namespace/ns_quarkdb/persistency/ContainerMDSvc.hh\"\n#include \"namespace/ns_quarkdb/persistency/FileMDSvc.hh\"\n#include \"namespace/ns_quarkdb/views/HierarchicalView.hh\"\n#include \"namespace/ns_quarkdb/tests/TestUtils.hh\"\n#include <memory>\n#include <gtest/gtest.h>\n\n// Hack to expose all members of FileSystemView to this test unit\n#define private public\n#include \"namespace/ns_quarkdb/accounting/FileSystemView.hh\"\n#undef private\n\nclass FileMDSvcF : public eos::ns::testing::NsTestsFixture {};\n\n//------------------------------------------------------------------------------\n// Tests implementation\n//------------------------------------------------------------------------------\nTEST_F(FileMDSvcF, LoadTest)\n{\n  std::shared_ptr<eos::IFileMD> file1 = fileSvc()->createFile(0);\n  std::shared_ptr<eos::IFileMD> file2 = fileSvc()->createFile(0);\n  std::shared_ptr<eos::IFileMD> file3 = fileSvc()->createFile(0);\n  std::shared_ptr<eos::IFileMD> file4 = fileSvc()->createFile(0);\n  std::shared_ptr<eos::IFileMD> file5 = fileSvc()->createFile(0);\n  ASSERT_TRUE(file1 != nullptr);\n  ASSERT_TRUE(file2 != nullptr);\n  ASSERT_TRUE(file3 != nullptr);\n  ASSERT_TRUE(file4 != nullptr);\n  ASSERT_TRUE(file5 != nullptr);\n  file1->setName(\"file1\");\n  file2->setName(\"file2\");\n  file3->setName(\"file3\");\n  file4->setName(\"file4\");\n  file5->setName(\"file5\");\n  eos::IFileMD::id_t id1 = file1->getId();\n  eos::IFileMD::id_t id2 = file2->getId();\n  eos::IFileMD::id_t id3 = file3->getId();\n  eos::IFileMD::id_t id4 = file4->getId();\n  eos::IFileMD::id_t id5 = file5->getId();\n  fileSvc()->updateStore(file1.get());\n  fileSvc()->updateStore(file2.get());\n  fileSvc()->updateStore(file3.get());\n  fileSvc()->updateStore(file4.get());\n  fileSvc()->updateStore(file5.get());\n  mdFlusher()->synchronize();\n  ASSERT_EQ(fileSvc()->getNumFiles(), 5);\n  fileSvc()->removeFile(file2.get());\n  fileSvc()->removeFile(file4.get());\n  mdFlusher()->synchronize();\n  ASSERT_EQ(fileSvc()->getNumFiles(), 3);\n  fileSvc()->finalize();\n  ASSERT_NO_THROW(fileSvc()->initialize());\n  shut_down_everything();\n  std::shared_ptr<eos::IFileMD> fileRec1 = fileSvc()->getFileMD(id1);\n  std::shared_ptr<eos::IFileMD> fileRec3 = fileSvc()->getFileMD(id3);\n  std::shared_ptr<eos::IFileMD> fileRec5 = fileSvc()->getFileMD(id5);\n  folly::Future<eos::IFileMDPtr> file1fut = fileSvc()->getFileMDFut(id1);\n  folly::Future<eos::IFileMDPtr> file1fut2 = fileSvc()->getFileMDFut(id1);\n  folly::Future<eos::IFileMDPtr> file1fut3 = fileSvc()->getFileMDFut(id1);\n  file1fut.wait();\n  file1fut2.wait();\n  file1fut3.wait();\n  // Ensure all futures point to the same underlying data in memory\n  ASSERT_TRUE(file1fut.value().get() == file1fut2.value().get());\n  ASSERT_TRUE(file1fut.value().get() == file1fut3.value().get());\n  ASSERT_THROW(fileSvc()->getFileMD(1337), eos::MDException);\n  ASSERT_TRUE(fileRec1 != nullptr);\n  ASSERT_TRUE(fileRec3 != nullptr);\n  ASSERT_TRUE(fileRec5 != nullptr);\n  ASSERT_TRUE(fileRec1->getName() == \"file1\");\n  ASSERT_TRUE(fileRec3->getName() == \"file3\");\n  ASSERT_TRUE(fileRec5->getName() == \"file5\");\n  ASSERT_THROW(fileSvc()->getFileMD(id2), eos::MDException);\n  ASSERT_THROW(fileSvc()->getFileMD(id4), eos::MDException);\n  ASSERT_NO_THROW(fileSvc()->removeFile(fileRec1.get()));\n  ASSERT_NO_THROW(fileSvc()->removeFile(fileRec3.get()));\n  ASSERT_NO_THROW(fileSvc()->removeFile(fileRec5.get()));\n  mdFlusher()->synchronize();\n  ASSERT_EQ(fileSvc()->getNumFiles(), 0);\n  fileSvc()->finalize();\n}\n\nTEST_F(FileMDSvcF,TreeInfos) {\n  eos::TreeInfos basicInfo;\n  ASSERT_EQ(0,basicInfo.dsize);\n  ASSERT_EQ(0,basicInfo.dtreefiles);\n  ASSERT_EQ(0,basicInfo.dtreecontainers);\n  eos::TreeInfos infos;\n  infos = -basicInfo;\n  ASSERT_EQ(0,infos.dsize);\n  ASSERT_EQ(0,infos.dtreefiles);\n  ASSERT_EQ(0,infos.dtreecontainers);\n  basicInfo.dsize = 1;\n  basicInfo.dtreefiles = -1;\n  basicInfo.dtreecontainers = 1;\n  infos = -basicInfo;\n  ASSERT_EQ(-1,infos.dsize);\n  ASSERT_EQ(1,infos.dtreefiles);\n  ASSERT_EQ(-1,infos.dtreecontainers);\n  basicInfo.dsize = 0;\n  basicInfo.dtreefiles = 0;\n  basicInfo.dtreecontainers = 0;\n  basicInfo += {1,1,1};\n  ASSERT_EQ(1,basicInfo.dsize);\n  ASSERT_EQ(1,basicInfo.dtreefiles);\n  ASSERT_EQ(1,basicInfo.dtreecontainers);\n}"
  },
  {
    "path": "namespace/ns_quarkdb/tests/FileSystemViewTest.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief FileSystemView test\n//------------------------------------------------------------------------------\n#define IN_TEST_HARNESS\n#include \"namespace/ns_quarkdb/accounting/SetChangeList.hh\"\n#include \"namespace/ns_quarkdb/accounting/FileSystemView.hh\"\n#include \"namespace/ns_quarkdb/accounting/FileSystemHandler.hh\"\n#include \"namespace/ns_quarkdb/persistency/ContainerMDSvc.hh\"\n#include \"namespace/ns_quarkdb/persistency/RequestBuilder.hh\"\n#include \"namespace/ns_quarkdb/persistency/FileMDSvc.hh\"\n#include \"namespace/ns_quarkdb/views/HierarchicalView.hh\"\n#include \"namespace/ns_quarkdb/tests/TestUtils.hh\"\n#include \"namespace/utils/RmrfHelper.hh\"\n#include <gtest/gtest.h>\n#include <cstdlib>\n#include <cstdint>\n#include <ctime>\n#include <sstream>\n#include <unistd.h>\n#include <folly/executors/IOThreadPoolExecutor.h>\n#include <chrono>\n#undef IN_TEST_HARNESS\n\n//------------------------------------------------------------------------------\n// Randomize a location\n//------------------------------------------------------------------------------\neos::IFileMD::location_t\ngetRandomLocation()\n{\n  return 1 + random() % 50;\n}\n\n//------------------------------------------------------------------------------\n// Count replicas\n//------------------------------------------------------------------------------\nsize_t\ncountReplicas(eos::IFsView* fs)\n{\n  size_t replicas = 0;\n\n  for (auto it = fs->getFileSystemIterator(); it->valid(); it->next()) {\n    replicas += fs->getNumFilesOnFs(it->getElement());\n  }\n\n  return replicas;\n}\n\n//------------------------------------------------------------------------------\n// Count unlinked\n//------------------------------------------------------------------------------\nsize_t\ncountUnlinked(eos::IFsView* fs)\n{\n  size_t unlinked = 0;\n\n  for (auto it = fs->getFileSystemIterator(); it->valid(); it->next()) {\n    unlinked += fs->getNumUnlinkedFilesOnFs(it->getElement());\n  }\n\n  return unlinked;\n}\n\n//------------------------------------------------------------------------------\n// Test utility classes\n//------------------------------------------------------------------------------\nTEST(FileSystemView, FileSetKey)\n{\n  ASSERT_EQ(eos::RequestBuilder::keyFilesystemFiles(50), \"fsview:50:files\");\n  ASSERT_EQ(eos::RequestBuilder::keyFilesystemFiles(123), \"fsview:123:files\");\n  ASSERT_EQ(eos::RequestBuilder::keyFilesystemUnlinked(10), \"fsview:10:unlinked\");\n  ASSERT_EQ(eos::RequestBuilder::keyFilesystemUnlinked(999),\n            \"fsview:999:unlinked\");\n}\n\nTEST(FileSystemView, ParseFsId)\n{\n  eos::IFileMD::location_t fsid;\n  bool unlinked;\n  ASSERT_TRUE(eos::parseFsId(\"fsview:1:files\", fsid, unlinked));\n  ASSERT_EQ(fsid, 1);\n  ASSERT_FALSE(unlinked);\n  ASSERT_TRUE(eos::parseFsId(\"fsview:999:unlinked\", fsid, unlinked));\n  ASSERT_EQ(fsid, 999);\n  ASSERT_TRUE(unlinked);\n  ASSERT_FALSE(eos::parseFsId(\"fsview:9:99:unlinked\", fsid, unlinked));\n  ASSERT_FALSE(eos::parseFsId(\"fsview:999:uNlinked\", fsid, unlinked));\n  ASSERT_FALSE(eos::parseFsId(\"fsVIew:1337:unlinked\", fsid, unlinked));\n}\n\n//------------------------------------------------------------------------------\n// Concrete implementation tests\n//------------------------------------------------------------------------------\nclass FileSystemViewF : public eos::ns::testing::NsTestsFixture {};\n\nTEST_F(FileSystemViewF, BasicSanity)\n{\n  srandom(time(nullptr));\n  view()->createContainer(\"/test/embed/embed1\", true);\n  std::shared_ptr<eos::IContainerMD> c =\n    view()->createContainer(\"/test/embed/embed2\", true);\n  view()->createContainer(\"/test/embed/embed3\", true);\n\n  // Create some files\n  for (int i = 0; i < 1000; ++i) {\n    std::ostringstream o;\n    o << \"file\" << i;\n    std::shared_ptr<eos::IFileMD> files[4];\n    files[0] = view()->createFile(std::string(\"/test/embed/\") + o.str());\n    files[1] = view()->createFile(std::string(\"/test/embed/embed1/\") + o.str());\n    files[2] = view()->createFile(std::string(\"/test/embed/embed2/\") + o.str());\n    files[3] = view()->createFile(std::string(\"/test/embed/embed3/\") + o.str());\n\n    for (int j = 0; j < 4; ++j) {\n      while (files[j]->getNumLocation() != 5) {\n        files[j]->addLocation(getRandomLocation());\n      }\n\n      view()->updateFileStore(files[j].get());\n    }\n  }\n\n  // Create some file without replicas assigned\n  for (int i = 0; i < 500; ++i) {\n    std::ostringstream o;\n    o << \"noreplicasfile\" << i;\n    view()->createFile(std::string(\"/test/embed/embed1/\") + o.str());\n  }\n\n  // Sum up all the locations\n  mdFlusher()->synchronize();\n  size_t numReplicas = countReplicas(fsview());\n  ASSERT_EQ(numReplicas, 20000);\n  size_t numUnlinked = countUnlinked(fsview());\n  ASSERT_EQ(numUnlinked, 0);\n  ASSERT_EQ(fsview()->getNumNoReplicasFiles(), 500);\n\n  // Unlink replicas\n  for (int i = 100; i < 500; ++i) {\n    std::ostringstream o;\n    o << \"file\" << i;\n    // Unlink some replicas\n    std::shared_ptr<eos::IFileMD> f = c->findFile(o.str());\n    f->unlinkLocation(f->getLocation(0));\n    f->unlinkLocation(f->getLocation(0));\n    view()->updateFileStore(f.get());\n  }\n\n  mdFlusher()->synchronize();\n  numReplicas = countReplicas(fsview());\n  ASSERT_EQ(numReplicas, 19200);\n  numUnlinked = countUnlinked(fsview());\n  ASSERT_EQ(numUnlinked, 800);\n  std::list<eos::IFileMD::id_t> file_ids;\n\n  for (int i = 500; i < 900; ++i) {\n    std::ostringstream o;\n    o << \"file\" << i;\n    // Unlink some replicas\n    std::shared_ptr<eos::IFileMD> f{c->findFile(o.str())};\n    f->unlinkAllLocations();\n    c->removeFile(o.str());\n    f->setContainerId(0);\n    file_ids.push_back(f->getId());\n    view()->updateFileStore(f.get());\n  }\n\n  mdFlusher()->synchronize();\n  numReplicas = countReplicas(fsview());\n  ASSERT_EQ(numReplicas, 17200);\n  numUnlinked = countUnlinked(fsview());\n  ASSERT_EQ(numUnlinked, 2800);\n  // Restart\n  shut_down_everything();\n  numReplicas = countReplicas(fsview());\n  ASSERT_EQ(numReplicas, 17200);\n  numUnlinked = countUnlinked(fsview());\n  ASSERT_EQ(numUnlinked, 2800);\n  ASSERT_EQ(fsview()->getNumNoReplicasFiles(), 500);\n  std::shared_ptr<eos::IFileMD> f{\n    view()->getFile(std::string(\"/test/embed/embed1/file1\"))};\n  ASSERT_EQ(view()->getUri(f.get()), \"/test/embed/embed1/file1\");\n  f->unlinkAllLocations();\n  mdFlusher()->synchronize();\n  numReplicas = countReplicas(fsview());\n  ASSERT_EQ(numReplicas, 17195);\n  numUnlinked = countUnlinked(fsview());\n  ASSERT_EQ(numUnlinked, 2805);\n  f->removeAllLocations();\n  mdFlusher()->synchronize();\n  numUnlinked = countUnlinked(fsview());\n  ASSERT_EQ(numUnlinked, 2800);\n  view()->updateFileStore(f.get());\n  ASSERT_EQ(fsview()->getNumNoReplicasFiles(), 501);\n  view()->removeFile(f.get());\n  mdFlusher()->synchronize();\n  ASSERT_EQ(fsview()->getNumNoReplicasFiles(), 500);\n  shut_down_everything();\n\n  // Cleanup - remove all files\n  for (int i = 0; i < 1000; ++i) {\n    std::ostringstream o;\n    o << \"file\" << i;\n    std::list<std::string> paths;\n    paths.push_back(\"/test/embed/\" + o.str());\n    paths.push_back(\"/test/embed/embed1/\" + o.str());\n    paths.push_back(\"/test/embed/embed2/\" + o.str());\n    paths.push_back(\"/test/embed/embed3/\" + o.str());\n\n    for (auto && elem : paths) {\n      // Skip the files that have already been removed\n      if ((elem == \"/test/embed/embed1/file1\") ||\n          (i >= 500 && i < 900 && elem.find(\"/test/embed/embed2/\") == 0)) {\n        continue;\n      }\n\n      std::shared_ptr<eos::IFileMD> file{view()->getFile(elem)};\n      ASSERT_EQ(view()->getUri(file.get()), elem);\n      view()->unlinkFile(file.get());\n      file->removeAllLocations();\n      view()->removeFile(file.get());\n    }\n  }\n\n  // Remove the files that were unlinked only\n  for (auto && id : file_ids) {\n    std::shared_ptr<eos::IFileMD> file = fileSvc()->getFileMD(id);\n    file->removeAllLocations();\n    view()->removeFile(file.get());\n  }\n\n  for (int i = 0; i < 500; ++i) {\n    std::ostringstream o;\n    o << \"noreplicasfile\" << i;\n    std::string path = \"/test/embed/embed1/\" + o.str();\n    std::shared_ptr<eos::IFileMD> file{view()->getFile(path)};\n    ASSERT_EQ(view()->getUri(file.get()), path);\n    view()->unlinkFile(file.get());\n    view()->removeFile(file.get());\n  }\n\n  // Remove all containers\n  eos::RmrfHelper::nukeDirectory(view(), \"/test/\");\n  // Remove the root container\n  std::shared_ptr<eos::IContainerMD> root{view()->getContainer(\"/\")};\n  containerSvc()->removeContainer(root.get());\n}\n\n//------------------------------------------------------------------------------\n// Test retrieval of random file ids in a filesystem view\n//------------------------------------------------------------------------------\nTEST_F(FileSystemViewF, RandomFilePicking)\n{\n  view()->createContainer(\"/test/\", true);\n\n  for (size_t i = 1; i < 200; i++) {\n    std::shared_ptr<eos::IFileMD> file = view()->createFile(SSTR(\"/test/\" << i));\n    ASSERT_EQ(view()->getUri(file.get()), SSTR(\"/test/\" << i));\n    ASSERT_EQ(view()->getUriFut(file->getIdentifier()).get(), SSTR(\"/test/\" << i));\n\n    // Even files go to fs #1, odd go to #2\n    if (i % 2 == 0) {\n      file->addLocation(1);\n    } else {\n      file->addLocation(2);\n    }\n\n    view()->updateFileStore(file.get());\n  }\n\n  mdFlusher()->synchronize();\n\n  for (size_t i = 0; i < 1000; i++) {\n    eos::IFileMD::id_t randomPick;\n    ASSERT_TRUE(fsview()->getApproximatelyRandomFileInFs(1, randomPick));\n    ASSERT_TRUE(randomPick % 2 == 0);\n\n    if (i < 10) {\n      std::cout << \"Random file in fs #1: \" << randomPick << std::endl;\n    }\n\n    ASSERT_TRUE(fsview()->getApproximatelyRandomFileInFs(2, randomPick));\n    ASSERT_TRUE(randomPick % 2 == 1);\n\n    if (i < 10) {\n      std::cout << \"Random file in fs #2: \" << randomPick << std::endl;\n    }\n  }\n\n  eos::IFileMD::id_t randomPick;\n  ASSERT_FALSE(fsview()->getApproximatelyRandomFileInFs(3, randomPick));\n  ASSERT_FALSE(fsview()->getApproximatelyRandomFileInFs(5, randomPick));\n  ASSERT_FALSE(fsview()->getApproximatelyRandomFileInFs(4, randomPick));\n}\n\n//------------------------------------------------------------------------------\n// Test file iterator on top of QHash object\n//------------------------------------------------------------------------------\nTEST_F(FileSystemViewF, FileIterator)\n{\n  std::unordered_set<eos::IFileMD::id_t> input_set;\n\n  for (std::uint64_t i = 0ull; i < 50000; ++i) {\n    double frac = eos::common::getRandom<uint64_t>(0, UINT64_MAX) / (double)UINT64_MAX;\n    (void)input_set.insert((uint64_t)(UINT64_MAX * frac));\n  }\n\n  // Push the set to QuarkDB\n  qclient::AsyncHandler ah;\n  const std::string key = \"set_iter_test\";\n  qclient::QSet set(qcl(), key);\n\n  for (auto elem : input_set) {\n    set.sadd_async(std::to_string(elem), &ah);\n  }\n\n  ASSERT_TRUE(ah.Wait());\n  std::unordered_set<eos::IFileMD::id_t> result_set;\n  auto iter = std::shared_ptr<eos::ICollectionIterator<eos::IFileMD::id_t>>\n              (new eos::StreamingFileListIterator(qcl(), key));\n\n  for (; (iter && iter->valid()); iter->next()) {\n    result_set.insert(iter->getElement());\n  }\n\n  ASSERT_EQ(input_set.size(), result_set.size());\n\n  for (auto elem : input_set) {\n    ASSERT_TRUE(result_set.find(elem) != result_set.end());\n  }\n\n  qcl().del(key);\n}\n\n//------------------------------------------------------------------------------\n// Test file list iterator\n//------------------------------------------------------------------------------\nTEST_F(FileSystemViewF, FileListIterator)\n{\n  view()->createContainer(\"/test/\", true);\n  eos::IFileMDPtr f1 = view()->createFile(\"/test/f1\");\n  ASSERT_EQ(f1->getIdentifier(), eos::FileIdentifier(1));\n  f1->addLocation(1);\n  f1->addLocation(2);\n  f1->addLocation(3);\n  eos::IFileMDPtr f2 = view()->createFile(\"/test/f2\");\n  ASSERT_EQ(f2->getIdentifier(), eos::FileIdentifier(2));\n  f2->addLocation(2);\n  eos::IFileMDPtr f3 = view()->createFile(\"/test/f3\");\n  ASSERT_EQ(f3->getIdentifier(), eos::FileIdentifier(3));\n  f3->addLocation(2);\n  f3->addLocation(3);\n  eos::IFileMDPtr f4 = view()->createFile(\"/test/f4\");\n  ASSERT_EQ(f4->getIdentifier(), eos::FileIdentifier(4));\n  f4->addLocation(4);\n  {\n    auto it = fsview()->getFileList(1);\n    ASSERT_TRUE(it->valid());\n    ASSERT_EQ(it->getElement(), 1u);\n    it->next();\n    ASSERT_FALSE(it->valid());\n  }\n  ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getFileList(2),\n              std::set<eos::IFileMD::id_t> { 1, 2, 3 }));\n  ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getFileList(3),\n              std::set<eos::IFileMD::id_t> { 1, 3 }));\n  ASSERT_FALSE(eos::ns::testing::verifyContents(fsview()->getFileList(3),\n               std::set<eos::IFileMD::id_t> { 1, 2, 3 }));\n  ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getFileList(4),\n              std::set<eos::IFileMD::id_t> { 4 }));\n  shut_down_everything();\n  ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getFileList(2),\n              std::set<eos::IFileMD::id_t> { 1, 2, 3 }));\n  ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getFileList(3),\n              std::set<eos::IFileMD::id_t> { 1, 3 }));\n  ASSERT_FALSE(eos::ns::testing::verifyContents(fsview()->getFileList(3),\n               std::set<eos::IFileMD::id_t> { 1, 2, 3 }));\n  ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getFileList(4),\n              std::set<eos::IFileMD::id_t> { 4 }));\n  ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getStreamingFileList(2),\n              std::set<eos::IFileMD::id_t> { 1, 2, 3 }));\n  ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getStreamingFileList(3),\n              std::set<eos::IFileMD::id_t> { 1, 3 }));\n  ASSERT_FALSE(eos::ns::testing::verifyContents(fsview()->getStreamingFileList(3),\n               std::set<eos::IFileMD::id_t> { 1, 2, 3 }));\n  ASSERT_TRUE(eos::ns::testing::verifyContents(fsview()->getStreamingFileList(4),\n              std::set<eos::IFileMD::id_t> { 4 }));\n}\n\n//------------------------------------------------------------------------------\n// Tests targetting FileSystemHandler\n//------------------------------------------------------------------------------\nTEST_F(FileSystemViewF, FileSystemHandler)\n{\n  // We're only using FileSystemHandler on its own in this test, don't spin\n  // up the rest of the namespace..\n  std::unique_ptr<folly::Executor> executor;\n  executor.reset(new folly::IOThreadPoolExecutor(16));\n  {\n    eos::FileSystemHandler fs1(1, executor.get(), &qcl(), mdFlusher(), false);\n    ASSERT_EQ(fs1.getRedisKey(), \"fsview:1:files\");\n    fs1.insert(eos::FileIdentifier(1));\n    fs1.insert(eos::FileIdentifier(8));\n    fs1.insert(eos::FileIdentifier(10));\n    fs1.insert(eos::FileIdentifier(20));\n    ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(),\n                std::set<eos::IFileMD::id_t> {1, 8, 10, 20}));\n    ASSERT_FALSE(eos::ns::testing::verifyContents(fs1.getFileList(),\n                 std::set<eos::IFileMD::id_t> {1, 8, 20}));\n    ASSERT_FALSE(eos::ns::testing::verifyContents(fs1.getFileList(),\n                 std::set<eos::IFileMD::id_t> {1, 8, 10, 20, 30}));\n    fs1.erase(eos::FileIdentifier(30));\n    ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(),\n                std::set<eos::IFileMD::id_t> {1, 8, 10, 20}));\n    fs1.erase(eos::FileIdentifier(20));\n    ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(),\n                std::set<eos::IFileMD::id_t> {1, 8, 10}));\n    fs1.insert(eos::FileIdentifier(20));\n    ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(),\n                std::set<eos::IFileMD::id_t> {1, 8, 10, 20}));\n  }\n  shut_down_everything();\n\n  // Make sure we pick up on any changes.\n  for (int i = 0; i < 3; i++) {\n    eos::FileSystemHandler fs1(1, executor.get(), &qcl(), mdFlusher(), false);\n    ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(),\n                std::set<eos::IFileMD::id_t> {1, 8, 10, 20}));\n    ASSERT_FALSE(eos::ns::testing::verifyContents(fs1.getFileList(),\n                 std::set<eos::IFileMD::id_t> {1, 8, 20}));\n    ASSERT_FALSE(eos::ns::testing::verifyContents(fs1.getFileList(),\n                 std::set<eos::IFileMD::id_t> {1, 8, 10, 20, 30}));\n  }\n\n  // Re-iterate just in case, this time verify contents directly from QDB.\n  qclient::QSet qset(qcl(), eos::RequestBuilder::keyFilesystemFiles(1));\n  {\n    auto it = qset.getIterator();\n    ASSERT_TRUE(eos::ns::testing::verifyContents(&it, std::set<std::string> { \"1\", \"8\", \"10\", \"20\" }));\n  }\n  {\n    auto it = qset.getIterator();\n    ASSERT_FALSE(eos::ns::testing::verifyContents(&it, std::set<std::string> { \"1\", \"8\", \"10\" }));\n  }\n  {\n    auto it = qset.getIterator();\n    ASSERT_FALSE(eos::ns::testing::verifyContents(&it, std::set<std::string> { \"1\", \"8\", \"10\", \"20\", \"30\" }));\n  }\n  shut_down_everything();\n  // Add item, make sure change is reflected in QDB.\n  {\n    eos::FileSystemHandler fs1(1, executor.get(), &qcl(), mdFlusher(), false);\n    ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(),\n                std::set<eos::IFileMD::id_t> {1, 8, 10, 20}));\n    fs1.insert(eos::FileIdentifier(99));\n    ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(),\n                std::set<eos::IFileMD::id_t> {1, 8, 10, 20, 99}));\n    mdFlusher()->synchronize();\n    // Try streaming iterator\n    ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getStreamingFileList(),\n                std::set<eos::IFileMD::id_t> {1, 8, 10, 20, 99}));\n  }\n  shut_down_everything();\n  {\n    qclient::QSet qset2(qcl(), eos::RequestBuilder::keyFilesystemFiles(1));\n    auto it = qset2.getIterator();\n    ASSERT_TRUE(eos::ns::testing::verifyContents(&it, std::set<std::string> { \"1\", \"8\", \"10\", \"20\", \"99\" }));\n  }\n  // Nuke filelist\n  {\n    eos::FileSystemHandler fs1(1, executor.get(), &qcl(), mdFlusher(), false);\n    ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(),\n                std::set<eos::IFileMD::id_t> {1, 8, 10, 20, 99}));\n    fs1.nuke();\n    ASSERT_TRUE(eos::ns::testing::verifyContents(fs1.getFileList(),\n                std::set<eos::IFileMD::id_t> { }));\n  }\n  mdFlusher()->synchronize();\n  // Ensure it's empty in the backend as well\n  {\n    qclient::QSet qset2(qcl(), eos::RequestBuilder::keyFilesystemFiles(1));\n    auto it = qset2.getIterator();\n    ASSERT_TRUE(eos::ns::testing::verifyContents(&it, std::set<std::string> { }));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Tests targetting FileSystemHandler cache clearing functionality\n//------------------------------------------------------------------------------\nTEST_F(FileSystemViewF, FileSystemHandlerCache)\n{\n  // We're only using FileSystemHandler on its own in this test, don't spin\n  // up the rest of the namespace..\n  std::unique_ptr<folly::Executor> executor;\n  executor.reset(new folly::IOThreadPoolExecutor(16));\n  {\n    eos::FileSystemHandler fs1(1, executor.get(), &qcl(),\n                               mdFlusher(), false, true);\n    ASSERT_EQ(fs1.getRedisKey(), \"fsview:1:files\");\n\n    for (int i = 100;  i < 400; ++i) {\n      if (i % 2 == 0) {\n        fs1.insert(eos::FileIdentifier(i));\n      }\n    }\n\n    mdFlusher()->synchronize();\n    ASSERT_EQ(150, fs1.size());\n    ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kNotLoaded,\n              fs1.getCacheStatus());\n    fs1.ensureContentsLoaded();\n    ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kLoaded, fs1.getCacheStatus());\n    ASSERT_EQ(150, fs1.size());\n\n    for (int i = 100; i < 200; ++i) {\n      if (i % 2 == 0) {\n        fs1.erase(eos::FileIdentifier(i));\n      }\n    }\n\n    ASSERT_EQ(100, fs1.size());\n    ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kLoaded, fs1.getCacheStatus());\n    fs1.clearCache(std::chrono::seconds(10));\n    ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kLoaded, fs1.getCacheStatus());\n    fs1.mClock.advance(std::chrono::seconds(30));\n    fs1.clearCache(std::chrono::seconds(10));\n    ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kNotLoaded,\n              fs1.getCacheStatus());\n\n    for (int i = 200; i < 300; ++i) {\n      if (i % 2 == 0) {\n        fs1.erase(eos::FileIdentifier(i));\n      }\n    }\n\n    mdFlusher()->synchronize();\n    ASSERT_EQ(50, fs1.size());\n    ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kNotLoaded,\n              fs1.getCacheStatus());\n    ASSERT_TRUE(fs1.hasFileId(300));\n    ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kLoaded, fs1.getCacheStatus());\n    fs1.clearCache(std::chrono::seconds(60));\n    ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kLoaded, fs1.getCacheStatus());\n    fs1.mClock.advance(std::chrono::seconds(54));\n    fs1.clearCache(std::chrono::seconds(53));\n    ASSERT_EQ(eos::FileSystemHandler::CacheStatus::kNotLoaded,\n              fs1.getCacheStatus());\n    fs1.nuke();\n  }\n  mdFlusher()->synchronize();\n  // Ensure it's empty in the backend as well\n  {\n    qclient::QSet qset2(qcl(), eos::RequestBuilder::keyFilesystemFiles(1));\n    auto it = qset2.getIterator();\n    ASSERT_TRUE(eos::ns::testing::verifyContents(&it, std::set<std::string> { }));\n  }\n}\n\nTEST(SetChangeList, BasicSanity)\n{\n  eos::IFsView::FileList contents;\n  contents.set_deleted_key(0);\n  contents.set_empty_key(0xffffffffffffffffll);\n  eos::SetChangeList<eos::IFileMD::id_t> changeList;\n  contents.insert(5);\n  contents.insert(9);\n  ASSERT_TRUE(eos::ns::testing::verifyContents(contents.begin(), contents.end(),\n              std::set<eos::IFileMD::id_t> { 5, 9 }));\n  changeList.push_back(10);\n  changeList.erase(5);\n  changeList.apply(contents);\n  ASSERT_TRUE(eos::ns::testing::verifyContents(contents.begin(), contents.end(),\n              std::set<eos::IFileMD::id_t> { 9, 10 }));\n  changeList.clear();\n  changeList.push_back(20);\n  changeList.clear();\n  changeList.apply(contents);\n  ASSERT_TRUE(eos::ns::testing::verifyContents(contents.begin(), contents.end(),\n              std::set<eos::IFileMD::id_t> { 9, 10 }));\n  changeList.push_back(99);\n  changeList.push_back(99);\n  changeList.push_back(12);\n  changeList.push_back(13);\n  changeList.erase(12);\n  changeList.apply(contents);\n  ASSERT_TRUE(eos::ns::testing::verifyContents(contents.begin(), contents.end(),\n              std::set<eos::IFileMD::id_t> { 9, 10, 13, 99 }));\n  changeList.clear();\n  changeList.push_back(15);\n  changeList.push_back(16);\n  changeList.erase(10);\n  changeList.apply(contents);\n  ASSERT_TRUE(eos::ns::testing::verifyContents(contents.begin(), contents.end(),\n              std::set<eos::IFileMD::id_t> { 9, 13, 15, 16, 99 }));\n  changeList.clear();\n  changeList.push_back(17);\n  changeList.push_back(17);\n  changeList.erase(17);\n  changeList.erase(17);\n  changeList.apply(contents);\n  ASSERT_EQ(changeList.size(), 4u);\n  ASSERT_TRUE(eos::ns::testing::verifyContents(contents.begin(), contents.end(),\n              std::set<eos::IFileMD::id_t> { 9, 13, 15, 16, 99 }));\n}\n"
  },
  {
    "path": "namespace/ns_quarkdb/tests/HierarchicalViewTest.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @breif HierarchicalView tests\n//------------------------------------------------------------------------------\n#include \"common/LayoutId.hh\"\n#include \"namespace/Resolver.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/locking/BulkNsObjectLocker.hh\"\n#include \"namespace/ns_quarkdb/accounting/QuotaStats.hh\"\n#include \"namespace/ns_quarkdb/persistency/ContainerMDSvc.hh\"\n#include \"namespace/ns_quarkdb/persistency/FileMDSvc.hh\"\n#include \"namespace/ns_quarkdb/tests/TestUtils.hh\"\n#include \"namespace/ns_quarkdb/utils/QuotaRecomputer.hh\"\n#include \"namespace/ns_quarkdb/views/HierarchicalView.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"namespace/utils/RenameSafetyCheck.hh\"\n#include \"namespace/utils/RmrfHelper.hh\"\n#include <algorithm>\n#include <cstdint>\n#include <folly/executors/IOThreadPoolExecutor.h>\n#include <gtest/gtest.h>\n#include <memory>\n#include <numeric>\n#include <pthread.h>\n#include <sstream>\n#include <unistd.h>\n\n#include <vector>\n\nclass HierarchicalViewF : public eos::ns::testing::NsTestsFixture {};\n\nTEST_F(HierarchicalViewF, LoadTest)\n{\n  std::shared_ptr<eos::IContainerMD> cont1 =\n    view()->createContainer(\"/test/embed/embed1\", true);\n  std::shared_ptr<eos::IContainerMD> cont2 =\n    view()->createContainer(\"/test/embed/embed2\", true);\n  std::shared_ptr<eos::IContainerMD> cont3 =\n    view()->createContainer(\"/test/embed/embed3\", true);\n  std::shared_ptr<eos::IContainerMD> cont4 =\n    view()->createContainer(\"/test/embed/embed4\", true);\n  std::shared_ptr<eos::IContainerMD> root = view()->getContainer(\"/\");\n  std::shared_ptr<eos::IContainerMD> test = view()->getContainer(\"/test\");\n  std::shared_ptr<eos::IContainerMD> embed = view()->getContainer(\"/test/embed\");\n  ASSERT_THROW(embed->setName(\"with/slashes\"), eos::MDException);\n  ASSERT_TRUE(root != nullptr);\n  ASSERT_TRUE(root->getId() == root->getParentId());\n  ASSERT_TRUE(test != nullptr);\n  ASSERT_TRUE(test->findContainer(\"embed\") != nullptr);\n  ASSERT_TRUE(embed != nullptr);\n  ASSERT_EQ(root->getId(), 1);\n  ASSERT_NE(test->getId(), 1);\n  ASSERT_NE(embed->getId(), 1);\n  ASSERT_TRUE(embed->findContainer(\"embed1\") != nullptr);\n  ASSERT_TRUE(embed->findContainer(\"embed2\") != nullptr);\n  ASSERT_TRUE(embed->findContainer(\"embed3\") != nullptr);\n  ASSERT_TRUE(cont1->getName() ==\n              embed->findContainer(\"embed1\")->getName());\n  ASSERT_TRUE(cont2->getName() ==\n              embed->findContainer(\"embed2\")->getName());\n  ASSERT_TRUE(cont3->getName() ==\n              embed->findContainer(\"embed3\")->getName());\n  view()->removeContainer(\"/test/embed/embed2\");\n  ASSERT_TRUE(embed->findContainer(\"embed2\") == nullptr);\n  view()->createFile(\"/test/embed/file1\");\n  view()->createFile(\"/test/embed/file2\");\n  view()->createFile(\"/test/embed/embed1/file1\");\n  view()->createFile(\"/test/embed/embed1/file2\");\n  view()->createFile(\"/test/embed/embed1/file3\");\n  std::shared_ptr<eos::IFileMD> fileR =\n    view()->createFile(\"/test/embed/embed1/fileR\");\n  ASSERT_THROW(fileR->setName(\"has/slashes\"), eos::MDException);\n  ASSERT_TRUE(view()->getFile(\"/test/embed/file1\"));\n  ASSERT_TRUE(view()->getFile(\"/test/embed/file2\"));\n  ASSERT_TRUE(view()->getFile(\"/test/embed/embed1/file1\"));\n  ASSERT_TRUE(view()->getFile(\"/test/embed/embed1/file2\"));\n  ASSERT_TRUE(view()->getFile(\"/test/embed/embed1/file3\"));\n  // Rename\n  view()->renameContainer(cont4.get(), \"embed4.renamed\");\n  ASSERT_TRUE(cont4->getName() == \"embed4.renamed\");\n  ASSERT_THROW(view()->renameContainer(cont4.get(), \"embed1\"),\n               eos::MDException);\n  ASSERT_THROW(view()->renameContainer(cont4.get(), \"embed1/asd\"),\n               eos::MDException);\n  view()->getContainer(\"/test/embed/embed4.renamed\");\n  view()->renameFile(fileR.get(), \"fileR.renamed\");\n  ASSERT_TRUE(fileR->getName() == \"fileR.renamed\");\n  ASSERT_THROW(view()->renameFile(fileR.get(), \"file1\"),\n               eos::MDException);\n  ASSERT_THROW(view()->renameFile(fileR.get(), \"file1/asd\"),\n               eos::MDException);\n  view()->getFile(\"/test/embed/embed1/fileR.renamed\");\n  ASSERT_THROW(view()->renameContainer(root.get(), \"rename\"),\n               eos::MDException);\n  // Test the \"reverse\" lookup\n  std::shared_ptr<eos::IFileMD> file =\n    view()->getFile(\"/test/embed/embed1/file3\");\n  std::shared_ptr<eos::IContainerMD> container =\n    view()->getContainer(\"/test/embed/embed1\");\n  ASSERT_EQ(view()->getUri(container.get()), \"/test/embed/embed1/\");\n  ASSERT_EQ(view()->getUriFut(container->getIdentifier()).get(),\n            \"/test/embed/embed1/\");\n  ASSERT_EQ(view()->getUri(file.get()), \"/test/embed/embed1/file3\");\n  ASSERT_EQ(view()->getUriFut(file->getIdentifier()).get(),\n            \"/test/embed/embed1/file3\");\n  ASSERT_THROW(view()->getUri((eos::IFileMD*)nullptr), eos::MDException);\n  ASSERT_THROW(view()->getUriFut(eos::FileIdentifier(9999999)).get(),\n               eos::MDException);\n  std::shared_ptr<eos::IFileMD> toBeDeleted =\n    view()->getFile(\"/test/embed/embed1/file2\");\n  toBeDeleted->addLocation(12);\n  // This should not succeed since the file should have a replica\n  ASSERT_THROW(view()->removeFile(toBeDeleted.get()), eos::MDException);\n  // We unlink the file - at this point the file should not be attached to the\n  // hierarchy but should still be accessible by id and thus the md pointer\n  // should stay valid\n  view()->unlinkFile(\"/test/embed/embed1/file2\");\n  ASSERT_THROW(view()->getFile(\"/test/embed/embed1/file2\"),\n               eos::MDException);\n  ASSERT_TRUE(cont1->findFile(\"file2\") == nullptr);\n  // We remove the replicas and the file but we need to reload the toBeDeleted\n  // pointer\n  eos::IFileMD::id_t id = toBeDeleted->getId();\n  toBeDeleted = fileSvc()->getFileMD(id);\n  toBeDeleted->clearUnlinkedLocations();\n  view()->removeFile(toBeDeleted.get());\n  ASSERT_THROW(fileSvc()->getFileMD(id), eos::MDException);\n  shut_down_everything();\n  ASSERT_TRUE(view()->getContainer(\"/\"));\n  ASSERT_TRUE(view()->getContainer(\"/test\"));\n  ASSERT_TRUE(view()->getContainer(\"/test/embed\"));\n  ASSERT_TRUE(view()->getContainer(\"/test/embed/embed1\"));\n  ASSERT_TRUE(view()->getFile(\"/test/embed/file1\"));\n  ASSERT_TRUE(view()->getFile(\"/test/embed/file2\"));\n  ASSERT_TRUE(view()->getFile(\"/test/embed/embed1/file1\"));\n  ASSERT_TRUE(view()->getFile(\"/test/embed/embed1/file3\"));\n  view()->getContainer(\"/test/embed/embed4.renamed\");\n  view()->getFile(\"/test/embed/embed1/fileR.renamed\");\n  // Cleanup\n  // Unlink files - need to do it in this order since the unlink removes the\n  // file from the container and then getFile by path won't work anymore\n  std::shared_ptr<eos::IFileMD> file1 = view()->getFile(\"/test/embed/file1\");\n  std::shared_ptr<eos::IFileMD> file2 = view()->getFile(\"/test/embed/file2\");\n  std::shared_ptr<eos::IFileMD> file11 =\n    view()->getFile(\"/test/embed/embed1/file1\");\n  std::shared_ptr<eos::IFileMD> file13 =\n    view()->getFile(\"/test/embed/embed1/file3\");\n  view()->unlinkFile(\"/test/embed/file1\");\n  view()->unlinkFile(\"/test/embed/file2\");\n  view()->unlinkFile(\"/test/embed/embed1/file1\");\n  view()->unlinkFile(\"/test/embed/embed1/file3\");\n  view()->unlinkFile(\"/test/embed/embed1/fileR.renamed\");\n  // Remove files\n  view()->removeFile(fileSvc()->getFileMD(file1->getId()).get());\n  view()->removeFile(fileSvc()->getFileMD(file2->getId()).get());\n  view()->removeFile(fileSvc()->getFileMD(file11->getId()).get());\n  view()->removeFile(fileSvc()->getFileMD(file13->getId()).get());\n  view()->removeFile(fileSvc()->getFileMD(fileR->getId()).get());\n  // Remove all containers\n  eos::RmrfHelper::nukeDirectory(view(), \"/test/\");\n}\n\n//------------------------------------------------------------------------------\n// File size mapping function\n//------------------------------------------------------------------------------\nstatic uint64_t\nmapSize(const eos::IFileMD* file)\n{\n  eos::IFileMD::layoutId_t lid = file->getLayoutId();\n\n  if (lid > 3) {\n    eos::MDException e(ENOENT);\n    e.getMessage() << \"Location does not exist\" << std::endl;\n    throw (e);\n  }\n\n  return lid * file->getSize();\n}\n\n//------------------------------------------------------------------------------\n// Create files at given path\n//------------------------------------------------------------------------------\nstatic void\ncreateFiles(const std::string& path, eos::IView* view,\n            std::map<uid_t, eos::QuotaNodeCore::UsageInfo>* users,\n            std::map<gid_t, eos::QuotaNodeCore::UsageInfo>* groups)\n{\n  eos::IQuotaNode* node = view->getQuotaNode(view->getContainer(path).get());\n\n  for (int i = 0; i < 1000; ++i) {\n    std::ostringstream p;\n    p << path << \"file\" << i;\n    std::shared_ptr<eos::IFileMD> file{view->createFile(p.str())};\n    file->setCUid(random() % 10 + 1);\n    file->setCGid(random() % 3 + 1);\n    file->setSize(random() % 1000000 + 1);\n    file->setLayoutId(random() % 3 + 1);\n    view->updateFileStore(file.get());\n    node->addFile(file.get());\n    uint64_t size = mapSize(file.get());\n    eos::QuotaNodeCore::UsageInfo& user = (*users)[file->getCUid()];\n    eos::QuotaNodeCore::UsageInfo& group = (*groups)[file->getCGid()];\n    user.space += file->getSize();\n    user.physicalSpace += size;\n    user.files++;\n    group.space += file->getSize();\n    group.physicalSpace += size;\n    group.files++;\n  }\n}\n\nTEST_F(HierarchicalViewF, ZeroSizedFilenames)\n{\n  eos::IContainerMDPtr cont1 = view()->createContainer(\"/test/dir1\", true);\n  eos::IContainerMDPtr cont2 = view()->createContainer(\"/dir2\", true);\n  eos::IFileMDPtr file1 = view()->createFile(\"/file1\", true);\n  file1->setName(\"\");\n  ASSERT_THROW(cont1->addFile(file1.get()), eos::MDException);\n  ASSERT_THROW(cont2->setName(\"\");, eos::MDException);\n}\n\n//------------------------------------------------------------------------------\n// Test namespace resolver based on (path, cid, cxid)\n//------------------------------------------------------------------------------\nTEST_F(HierarchicalViewF, Resolver)\n{\n  // Make a lot of containers\n  for (size_t i = 0; i < 50; i++) {\n    view()->createContainer(SSTR(\"/dir\" << i), true);\n  }\n\n  eos::ContainerSpecificationProto spec;\n  ASSERT_THROW(eos::Resolver::resolveContainer(view(), spec), eos::MDException);\n  spec.set_path(\"/dir49\");\n  eos::IContainerMDPtr cont = eos::Resolver::resolveContainer(view(), spec);\n  ASSERT_EQ(cont->getName(), \"dir49\");\n  spec.set_cid(\"48\");\n  cont = eos::Resolver::resolveContainer(view(), spec);\n  ASSERT_EQ(cont->getName(), \"dir46\");\n  spec.set_cxid(\"30\");\n  cont = eos::Resolver::resolveContainer(view(), spec);\n  ASSERT_EQ(cont->getName(), \"dir46\");\n  spec.set_path(\"/chicken\");\n  ASSERT_THROW(eos::Resolver::resolveContainer(view(), spec), eos::MDException);\n  spec.set_cid(\"chicken chicken\");\n  ASSERT_THROW(eos::Resolver::resolveContainer(view(), spec), eos::MDException);\n  spec.set_cxid(\"chicken\");\n  ASSERT_THROW(eos::Resolver::resolveContainer(view(), spec), eos::MDException);\n}\n\nTEST_F(HierarchicalViewF, QuotaTest)\n{\n  srandom(time(nullptr));\n  // Initialize the system\n  setSizeMapper(mapSize);\n  // Create some structures, insert quota nodes and test their correctness\n  std::shared_ptr<eos::IContainerMD> cont1{\n    view()->createContainer(\"/test/embed/embed1\", true)};\n  std::shared_ptr<eos::IContainerMD> cont2{\n    view()->createContainer(\"/test/embed/embed2\", true)};\n  std::shared_ptr<eos::IContainerMD> cont3{\n    view()->createContainer(\"/test/embed/embed3\", true)};\n  std::shared_ptr<eos::IContainerMD> cont4{view()->getContainer(\"/test/embed\")};\n  std::shared_ptr<eos::IContainerMD> cont5{view()->getContainer(\"/test\")};\n  eos::IQuotaNode* qnCreated1 = view()->registerQuotaNode(cont1.get());\n  eos::IQuotaNode* qnCreated2 = view()->registerQuotaNode(cont3.get());\n  eos::IQuotaNode* qnCreated3 = view()->registerQuotaNode(cont5.get());\n  ASSERT_THROW(view()->registerQuotaNode(cont1.get()), eos::MDException);\n  ASSERT_TRUE(qnCreated1);\n  ASSERT_TRUE(qnCreated2);\n  ASSERT_TRUE(qnCreated3);\n  eos::IQuotaNode* qn1 = view()->getQuotaNode(cont1.get());\n  eos::IQuotaNode* qn2 = view()->getQuotaNode(cont2.get());\n  eos::IQuotaNode* qn3 = view()->getQuotaNode(cont3.get());\n  eos::IQuotaNode* qn4 = view()->getQuotaNode(cont4.get());\n  eos::IQuotaNode* qn5 = view()->getQuotaNode(cont5.get());\n  ASSERT_TRUE(qn1);\n  ASSERT_TRUE(qn2);\n  ASSERT_TRUE(qn3);\n  ASSERT_TRUE(qn4);\n  ASSERT_TRUE(qn5);\n  ASSERT_TRUE(qn2 == qn5);\n  ASSERT_TRUE(qn4 == qn5);\n  ASSERT_TRUE(qn1 != qn5);\n  ASSERT_TRUE(qn3 != qn5);\n  ASSERT_TRUE(qn3 != qn2);\n  // Create some files\n  std::map<uid_t, eos::QuotaNodeCore::UsageInfo> users1;\n  std::map<gid_t, eos::QuotaNodeCore::UsageInfo> groups1;\n  std::string path1 = \"/test/embed/embed1/\";\n  createFiles(path1, view(), &users1, &groups1);\n  std::map<uid_t, eos::QuotaNodeCore::UsageInfo> users2;\n  std::map<gid_t, eos::QuotaNodeCore::UsageInfo> groups2;\n  std::string path2 = \"/test/embed/embed2/\";\n  createFiles(path2, view(), &users2, &groups2);\n  std::map<uid_t, eos::QuotaNodeCore::UsageInfo> users3;\n  std::map<gid_t, eos::QuotaNodeCore::UsageInfo> groups3;\n  std::string path3 = \"/test/embed/embed3/\";\n  createFiles(path3, view(), &users3, &groups3);\n  // Verify correctness\n  eos::IQuotaNode* node1 = view()->getQuotaNode(view()->getContainer(\n                             path1).get());\n  eos::IQuotaNode* node2 = view()->getQuotaNode(view()->getContainer(\n                             path2).get());\n\n  for (int i = 1; i <= 10; ++i) {\n    ASSERT_EQ(node1->getPhysicalSpaceByUser(i), users1[i].physicalSpace);\n    ASSERT_EQ(node2->getPhysicalSpaceByUser(i), users2[i].physicalSpace);\n    ASSERT_EQ(node1->getUsedSpaceByUser(i), users1[i].space);\n    ASSERT_EQ(node2->getUsedSpaceByUser(i), users2[i].space);\n    ASSERT_EQ(node1->getNumFilesByUser(i), users1[i].files);\n    ASSERT_EQ(node2->getNumFilesByUser(i), users2[i].files);\n  }\n\n  for (int i = 1; i <= 3; ++i) {\n    ASSERT_EQ(node1->getPhysicalSpaceByGroup(i),\n              groups1[i].physicalSpace);\n    ASSERT_EQ(node2->getPhysicalSpaceByGroup(i),\n              groups2[i].physicalSpace);\n    ASSERT_EQ(node1->getUsedSpaceByGroup(i), groups1[i].space);\n    ASSERT_EQ(node2->getUsedSpaceByGroup(i), groups2[i].space);\n    ASSERT_EQ(node1->getNumFilesByGroup(i), groups1[i].files);\n    ASSERT_EQ(node2->getNumFilesByGroup(i), groups2[i].files);\n  }\n\n  // Restart and check if the quota stats are reloaded correctly\n  shut_down_everything();\n  node1 = view()->getQuotaNode(view()->getContainer(path1).get());\n  node2 = view()->getQuotaNode(view()->getContainer(path2).get());\n  ASSERT_TRUE(node1);\n  ASSERT_TRUE(node2);\n\n  for (int i = 1; i <= 10; ++i) {\n    ASSERT_EQ(node1->getPhysicalSpaceByUser(i), users1[i].physicalSpace);\n    ASSERT_EQ(node2->getPhysicalSpaceByUser(i), users2[i].physicalSpace);\n    ASSERT_EQ(node1->getUsedSpaceByUser(i), users1[i].space);\n    ASSERT_EQ(node2->getUsedSpaceByUser(i), users2[i].space);\n    ASSERT_EQ(node1->getNumFilesByUser(i), users1[i].files);\n    ASSERT_EQ(node2->getNumFilesByUser(i), users2[i].files);\n  }\n\n  for (int i = 1; i <= 3; ++i) {\n    ASSERT_EQ(node1->getPhysicalSpaceByGroup(i), groups1[i].physicalSpace);\n    ASSERT_EQ(node2->getPhysicalSpaceByGroup(i), groups2[i].physicalSpace);\n    ASSERT_EQ(node1->getUsedSpaceByGroup(i), groups1[i].space);\n    ASSERT_EQ(node2->getUsedSpaceByGroup(i), groups2[i].space);\n    ASSERT_EQ(node1->getNumFilesByGroup(i), groups1[i].files);\n    ASSERT_EQ(node2->getNumFilesByGroup(i), groups2[i].files);\n  }\n\n  // Remove the quota nodes on /test/embed/embed1 and /dest/embed/embed2\n  // and check if the quota on /test has been updated\n  eos::IQuotaNode* parentNode = nullptr;\n  parentNode = view()->getQuotaNode(view()->getContainer(\"/test\").get());\n  view()->removeQuotaNode(view()->getContainer(path1).get());\n\n  for (int i = 1; i <= 10; ++i) {\n    ASSERT_EQ(parentNode->getPhysicalSpaceByUser(i),\n              users1[i].physicalSpace + users2[i].physicalSpace);\n    ASSERT_EQ(parentNode->getUsedSpaceByUser(i),\n              users1[i].space + users2[i].space);\n    ASSERT_EQ(parentNode->getNumFilesByUser(i),\n              users1[i].files + users2[i].files);\n  }\n\n  for (int i = 1; i <= 3; ++i) {\n    ASSERT_EQ(parentNode->getPhysicalSpaceByGroup(i),\n              groups1[i].physicalSpace + groups2[i].physicalSpace);\n    ASSERT_EQ(parentNode->getUsedSpaceByGroup(i),\n              groups1[i].space + groups2[i].space);\n    ASSERT_EQ(parentNode->getNumFilesByGroup(i),\n              groups1[i].files + groups2[i].files);\n  }\n\n  view()->removeQuotaNode(view()->getContainer(path3).get());\n  ASSERT_THROW(view()->removeQuotaNode(view()->getContainer(path3).get()),\n               eos::MDException);\n\n  for (int i = 1; i <= 10; ++i) {\n    ASSERT_EQ(parentNode->getPhysicalSpaceByUser(i),\n              users1[i].physicalSpace + users2[i].physicalSpace +\n              users3[i].physicalSpace);\n    ASSERT_EQ(parentNode->getUsedSpaceByUser(i),\n              users1[i].space + users2[i].space + users3[i].space);\n    ASSERT_EQ(parentNode->getNumFilesByUser(i),\n              users1[i].files + users2[i].files + users3[i].files);\n  }\n\n  for (int i = 1; i <= 3; ++i) {\n    ASSERT_EQ(parentNode->getPhysicalSpaceByGroup(i),\n              groups1[i].physicalSpace + groups2[i].physicalSpace +\n              groups3[i].physicalSpace);\n    ASSERT_EQ(parentNode->getUsedSpaceByGroup(i),\n              groups1[i].space + groups2[i].space + groups3[i].space);\n    ASSERT_EQ(parentNode->getNumFilesByGroup(i),\n              groups1[i].files + groups2[i].files + groups3[i].files);\n  }\n\n  // Clean up\n  // Remove all the quota nodes\n  ASSERT_THROW(view()->removeQuotaNode(view()->getContainer(path1).get()),\n               eos::MDException);\n  ASSERT_THROW(view()->removeQuotaNode(view()->getContainer(path2).get()),\n               eos::MDException);\n  ASSERT_THROW(view()->removeQuotaNode(view()->getContainer(path3).get()),\n               eos::MDException);\n  ASSERT_THROW(view()->removeQuotaNode(view()->getContainer(\"/test/embed\").get()),\n               eos::MDException);\n  view()->removeQuotaNode(cont5.get());\n  // Remove all the files\n  std::list<std::string> paths{path1, path2, path3};\n\n  for (auto&& path_elem : paths) {\n    for (int i = 0; i < 1000; ++i) {\n      std::ostringstream p;\n      p << path_elem << \"file\" << i;\n      std::shared_ptr<eos::IFileMD> file{view()->getFile(p.str())};\n      view()->unlinkFile(p.str());\n      view()->removeFile(fileSvc()->getFileMD(file->getId()).get());\n    }\n  }\n\n  // Remove all containers\n  ASSERT_NO_THROW(eos::RmrfHelper::nukeDirectory(view(), \"/test/\"));\n  // Remove the root container\n  std::shared_ptr<eos::IContainerMD> root{view()->getContainer(\"/\")};\n  ASSERT_NO_THROW(containerSvc()->removeContainer(root.get()));\n  ASSERT_NO_THROW(view()->finalize());\n}\n\nTEST_F(HierarchicalViewF, LostContainerTest)\n{\n  std::shared_ptr<eos::IContainerMD> cont1 =\n    view()->createContainer(\"/test/embed/embed1\", true);\n  std::shared_ptr<eos::IContainerMD> cont2 =\n    view()->createContainer(\"/test/embed/embed2\", true);\n  std::shared_ptr<eos::IContainerMD> cont3 =\n    view()->createContainer(\"/test/embed/embed3\", true);\n  std::shared_ptr<eos::IContainerMD> cont4 =\n    view()->createContainer(\"/test/embed/embed1/embedembed\", true);\n  std::shared_ptr<eos::IContainerMD> cont5 =\n    view()->createContainer(\"/test/embed/embed3.conflict\", true);\n\n  // Create some files\n  for (int i = 0; i < 1000; ++i) {\n    std::ostringstream s1;\n    s1 << \"/test/embed/embed1/file\" << i;\n    std::ostringstream s2;\n    s2 << \"/test/embed/embed2/file\" << i;\n    std::ostringstream s3;\n    s3 << \"/test/embed/embed3/file\" << i;\n    std::ostringstream s4;\n    s4 << \"/test/embed/embed1/embedembed/file\" << i;\n    std::ostringstream s5;\n    s5 << \"/test/embed/embed3.conflict/file\" << i;\n    std::ostringstream s6;\n    s6 << \"/test/embed/embed2/conflict_file\" << i;\n    eos::IFileMDPtr embed1F = view()->createFile(s1.str());\n    ASSERT_EQ(view()->getParentContainer(embed1F.get()).get(), cont1);\n    view()->createFile(s2.str());\n    view()->createFile(s3.str());\n    view()->createFile(s4.str());\n    view()->createFile(s5.str());\n    view()->createFile(s6.str());\n    std::shared_ptr<eos::IFileMD> file = view()->getFile(s6.str());\n\n    if (i != 0) {\n      ASSERT_THROW(view()->renameFile(file.get(), \"conflict_file\"),\n                   eos::MDException);\n    } else {\n      view()->renameFile(file.get(), \"conflict_file\");\n    }\n  }\n\n  // Trying to remove a non-empty container should result in an exception\n  ASSERT_THROW(view()->getContainerMDSvc()->removeContainer(cont1.get()),\n               eos::MDException);\n  // Trying to rename a container to an already existing one should result in\n  // an exception\n  ASSERT_NO_THROW(cont5->setName(\"embed3\"));\n\n  // Cleanup\n  for (int i = 0; i < 1000; ++i) {\n    std::ostringstream s1;\n    s1 << \"/test/embed/embed1/file\" << i;\n    std::ostringstream s2;\n    s2 << \"/test/embed/embed2/file\" << i;\n    std::ostringstream s3;\n    s3 << \"/test/embed/embed3/file\" << i;\n    std::ostringstream s4;\n    s4 << \"/test/embed/embed1/embedembed/file\" << i;\n    std::ostringstream s5;\n    s5 << \"/test/embed/embed3.conflict/file\" << i;\n    std::ostringstream s6;\n    s6 << \"/test/embed/embed2/conflict_file\" << i;\n    std::list<std::string> paths{s1.str(), s2.str(), s3.str(), s4.str(),\n                                 s5.str()};\n\n    if (i != 0) {\n      paths.insert(paths.end(), s6.str());\n    }\n\n    for (auto&& elem : paths) {\n      std::shared_ptr<eos::IFileMD> file = view()->getFile(elem);\n      view()->unlinkFile(elem);\n      view()->removeFile(fileSvc()->getFileMD(file->getId()).get());\n    }\n  }\n\n  // Remove the conflict_file\n  std::string path = \"test/embed/embed2/conflict_file\";\n  std::shared_ptr<eos::IFileMD> file = view()->getFile(path);\n  view()->unlinkFile(path);\n  view()->removeFile(fileSvc()->getFileMD(file->getId()).get());\n  // Remove all containers\n  // TODO(gbitzes): Something wrong is here, this should succeed, investigate.\n  // eos::RmrfHelper::nukeDirectory(view(), \"/test/\");\n}\n\nTEST_F(HierarchicalViewF, RenameDirectoryAsSubdirOfItself)\n{\n  std::shared_ptr<eos::IContainerMD> cont1 =\n    view()->createContainer(\"/eos/dev/my-dir\", true);\n  std::shared_ptr<eos::IContainerMD> cont2 =\n    view()->createContainer(\"/eos/dev/my-dir/subdir1\", true);\n  std::shared_ptr<eos::IContainerMD> cont3 =\n    view()->createContainer(\"/eos/dev/my-dir/subdir1/subdir2\", true);\n  ASSERT_TRUE(eos::isSafeToRename(view(), cont3.get(), cont1.get()));\n  ASSERT_FALSE(eos::isSafeToRename(view(), cont1.get(), cont3.get()));\n  ASSERT_TRUE(eos::isSafeToRename(view(), cont2.get(),\n                                  cont1.get())); // non-sensical to do, but safe (no-op)\n  ASSERT_FALSE(eos::isSafeToRename(view(), cont1.get(), cont2.get()));\n}\n\nTEST_F(HierarchicalViewF, AddFileWithConflicts)\n{\n  eos::IContainerMDPtr cont1 = view()->createContainer(\"/test/dir1\", true);\n  view()->createContainer(\"/test/dir1/dir2\", true);\n  eos::IContainerMDPtr cont2 = view()->createContainer(\"/dir1\", true);\n  eos::IFileMDPtr file1 = view()->createFile(\"/test/dir1/file1\", true);\n  eos::IFileMDPtr file2 = view()->createFile(\"/file1\", true);\n  ASSERT_THROW(cont1->addFile(file2.get()),\n               eos::MDException); // conflicts with file\n  file2->setName(\"dir2\");\n  ASSERT_THROW(cont1->addFile(file2.get()),\n               eos::MDException); // conflicts with directory\n  cont1->addFile(file1.get()); // conflicts with itself, thus, no conflict\n}\n\nTEST_F(HierarchicalViewF, AddContainerWithConflicts)\n{\n  eos::IContainerMDPtr cont1 = view()->createContainer(\"/test/\", true);\n  eos::IContainerMDPtr cont4 = view()->createContainer(\"/test/dir1\", true);\n  eos::IContainerMDPtr cont2 = view()->createContainer(\"/dir1\", true);\n  ASSERT_THROW(cont1->addContainer(cont2.get()),\n               eos::MDException); // conflicts with container\n  view()->createFile(\"/test/file1\", true);\n  eos::IContainerMDPtr cont3 = view()->createContainer(\"/file1\", true);\n  ASSERT_THROW(cont1->addContainer(cont3.get()),\n               eos::MDException); // conflicts with file\n  cont1->addContainer(cont4.get()); // conflicts with itself, thus, no conflict\n}\n\nTEST_F(HierarchicalViewF, QuotaRecomputation)\n{\n  eos::IContainerMDPtr quota1 = view()->createContainer(\"/quota1\", true);\n  eos::IContainerMDPtr quota2 = view()->createContainer(\"/quota2\", true);\n  eos::IContainerMDPtr quota3 = view()->createContainer(\"/quota1/quota3\", true);\n  eos::IContainerMDPtr notquota1 = view()->createContainer(\"/not-a-quota\", true);\n  eos::IContainerMDPtr notquota2 =\n    view()->createContainer(\"/quota1/not-a-quota-either\", true);\n  containerSvc()->updateStore(quota1.get());\n  containerSvc()->updateStore(quota2.get());\n  containerSvc()->updateStore(quota3.get());\n  unsigned long layoutId = eos::common::LayoutId::GetId(\n                             eos::common::LayoutId::kReplica,\n                             eos::common::LayoutId::kMD5,\n                             2,\n                             eos::common::LayoutId::k4k);\n\n  for (size_t i = 0; i < 10; i++) {\n    eos::IFileMDPtr file = view()->createFile(SSTR(\"/quota1/f\" << i), true);\n    file->setSize(1337);\n    file->setLayoutId(layoutId);\n    file->setCUid(i % 4);\n    file->setCGid(i % 2);\n    fileSvc()->updateStore(file.get());\n  }\n\n  layoutId = eos::common::LayoutId::GetId(\n               eos::common::LayoutId::kReplica,\n               eos::common::LayoutId::kMD5,\n               3,\n               eos::common::LayoutId::k4k);\n\n  for (size_t i = 0; i < 15; i++) {\n    eos::IFileMDPtr file = view()->createFile(SSTR(\"/quota1/quota3/f\" << i), true);\n    file->setSize(1338);\n    file->setLayoutId(layoutId);\n    file->setCUid(100);\n    file->setCGid(200);\n    fileSvc()->updateStore(file.get());\n  }\n\n  layoutId = eos::common::LayoutId::GetId(\n               eos::common::LayoutId::kReplica,\n               eos::common::LayoutId::kMD5,\n               5,\n               eos::common::LayoutId::k4k);\n\n  for (size_t i = 0; i < 17; i++) {\n    eos::IFileMDPtr file = view()->createFile(SSTR(\"/quota2/f\" << i), true);\n    file->setSize(133);\n    file->setLayoutId(layoutId);\n    file->setCUid(i);\n    file->setCGid(9000);\n    fileSvc()->updateStore(file.get());\n  }\n\n  mdFlusher()->synchronize();\n  eos::QuotaNodeCore qnc;\n  eos::QuotaRecomputer recomputer(&(qcl()), executor());\n  // Simple, non-nested case first: quota2\n  eos::IQuotaNode* qn2 = view()->registerQuotaNode(quota2.get());\n  ASSERT_NE(qn2, nullptr);\n  eos::MDStatus status = recomputer.recompute(view()->getUri(quota2.get()),\n                         quota2->getId(), qnc);\n  ASSERT_TRUE(status.ok());\n  ASSERT_EQ(status.getErrno(), 0);\n  ASSERT_EQ(status.getError(), \"\");\n\n  for (size_t i = 0; i < 17; i++) {\n    ASSERT_EQ(qnc.getUsedSpaceByUser(i), 133);\n    ASSERT_EQ(qnc.getPhysicalSpaceByUser(i), 133 * 5);\n    ASSERT_EQ(qnc.getNumFilesByUser(i), 1);\n    ASSERT_EQ(qnc.getUsedSpaceByGroup(i), 0);\n    ASSERT_EQ(qnc.getPhysicalSpaceByGroup(i), 0);\n    ASSERT_EQ(qnc.getNumFilesByGroup(i), 0);\n  }\n\n  ASSERT_EQ(qnc.getUsedSpaceByGroup(9000), 17 * 133);\n  ASSERT_EQ(qnc.getPhysicalSpaceByGroup(9000), 17 * 133 * 5);\n  ASSERT_EQ(qnc.getNumFilesByGroup(9000), 17);\n  // quota1 + quota3\n  eos::IQuotaNode* qn1p3 = view()->registerQuotaNode(quota1.get());\n  ASSERT_NE(qn1p3, nullptr);\n  status = recomputer.recompute(view()->getUri(quota1.get()),\n                                quota1->getId(), qnc);\n  ASSERT_TRUE(status.ok());\n  ASSERT_EQ(status.getErrno(), 0);\n  ASSERT_EQ(status.getError(), \"\");\n\n  // uid0 and uid1 have 3 files each\n  for (size_t i = 0; i < 2; i++) {\n    ASSERT_EQ(qnc.getUsedSpaceByUser(i), 1337 * 3);\n    ASSERT_EQ(qnc.getPhysicalSpaceByUser(i), 1337 * 3 * 2);\n    ASSERT_EQ(qnc.getNumFilesByUser(i), 3);\n  }\n\n  // uid2 and uid3 and 2 files each\n  for (size_t i = 2; i < 4; i++) {\n    ASSERT_EQ(qnc.getUsedSpaceByUser(i), 1337 * 2);\n    ASSERT_EQ(qnc.getPhysicalSpaceByUser(i), 1337 * 2 * 2);\n    ASSERT_EQ(qnc.getNumFilesByUser(i), 2);\n  }\n\n  // gid0 and gid1 have 5 each\n  for (size_t i = 0; i < 2; i++) {\n    ASSERT_EQ(qnc.getUsedSpaceByGroup(i), 1337 * 5);\n    ASSERT_EQ(qnc.getPhysicalSpaceByGroup(i), 1337 * 2 * 5);\n    ASSERT_EQ(qnc.getNumFilesByGroup(i), 5);\n  }\n\n  ASSERT_EQ(qnc.getUsedSpaceByUser(100), 1338 * 15);\n  ASSERT_EQ(qnc.getPhysicalSpaceByUser(100), 1338 * 15 * 3);\n  ASSERT_EQ(qnc.getNumFilesByUser(100), 15);\n  ASSERT_EQ(qnc.getUsedSpaceByGroup(200), 1338 * 15);\n  ASSERT_EQ(qnc.getPhysicalSpaceByGroup(200), 1338 * 15 * 3);\n  ASSERT_EQ(qnc.getNumFilesByGroup(200), 15);\n  // register quota3, measure\n  eos::IQuotaNode* qn3 = view()->registerQuotaNode(quota3.get());\n  ASSERT_NE(qn3, nullptr);\n  status = recomputer.recompute(view()->getUri(quota3.get()),\n                                quota3->getId(), qnc);\n  ASSERT_TRUE(status.ok());\n  ASSERT_EQ(status.getErrno(), 0);\n  ASSERT_EQ(status.getError(), \"\");\n  ASSERT_EQ(qnc.getUsedSpaceByUser(100), 1338 * 15);\n  ASSERT_EQ(qnc.getPhysicalSpaceByUser(100), 1338 * 15 * 3);\n  ASSERT_EQ(qnc.getNumFilesByUser(100), 15);\n  ASSERT_EQ(qnc.getUsedSpaceByGroup(200), 1338 * 15);\n  ASSERT_EQ(qnc.getPhysicalSpaceByGroup(200), 1338 * 15 * 3);\n  ASSERT_EQ(qnc.getNumFilesByGroup(200), 15);\n  // measure quota1 _on its own_, without embedded quota3\n  status = recomputer.recompute(view()->getUri(quota1.get()),\n                                quota1->getId(), qnc);\n  ASSERT_TRUE(status.ok());\n  ASSERT_EQ(status.getErrno(), 0);\n  ASSERT_EQ(status.getError(), \"\");\n\n  // uid0 and uid1 have 3 files each\n  for (size_t i = 0; i < 2; i++) {\n    ASSERT_EQ(qnc.getUsedSpaceByUser(i), 1337 * 3);\n    ASSERT_EQ(qnc.getPhysicalSpaceByUser(i), 1337 * 3 * 2);\n    ASSERT_EQ(qnc.getNumFilesByUser(i), 3);\n  }\n\n  // uid2 and uid3 and 2 files each\n  for (size_t i = 2; i < 4; i++) {\n    ASSERT_EQ(qnc.getUsedSpaceByUser(i), 1337 * 2);\n    ASSERT_EQ(qnc.getPhysicalSpaceByUser(i), 1337 * 2 * 2);\n    ASSERT_EQ(qnc.getNumFilesByUser(i), 2);\n  }\n\n  // gid0 and gid1 have 5 each\n  for (size_t i = 0; i < 2; i++) {\n    ASSERT_EQ(qnc.getUsedSpaceByGroup(i), 1337 * 5);\n    ASSERT_EQ(qnc.getPhysicalSpaceByGroup(i), 1337 * 2 * 5);\n    ASSERT_EQ(qnc.getNumFilesByGroup(i), 5);\n  }\n\n  ASSERT_EQ(qnc.getUsedSpaceByUser(100), 0);\n  ASSERT_EQ(qnc.getPhysicalSpaceByUser(100), 0);\n  ASSERT_EQ(qnc.getNumFilesByUser(100), 0);\n  ASSERT_EQ(qnc.getUsedSpaceByGroup(200), 0);\n  ASSERT_EQ(qnc.getPhysicalSpaceByGroup(200), 0);\n  ASSERT_EQ(qnc.getNumFilesByGroup(200), 0);\n}\n\nTEST_F(HierarchicalViewF, CustomContainerId)\n{\n  eos::IContainerMDPtr c32 = view()->createContainer(\"/c32\", false, 32);\n  ASSERT_EQ(c32->getId(), 32);\n  eos::IContainerMDPtr root = view()->getContainer(\"/\");\n  ASSERT_EQ(root->getId(), 1);\n  eos::IContainerMDPtr child = root->findContainer(\"c32\");\n  ASSERT_EQ(child.get(), c32.get());\n  eos::IContainerMDPtr c33 = view()->createContainer(\"/c33\", true);\n  ASSERT_EQ(c33->getId(), 33);\n}\n\nTEST_F(HierarchicalViewF, CustomFileId)\n{\n  eos::IFileMDPtr f999 = view()->createFile(\"/f999\", 5, 5, 999);\n  ASSERT_EQ(f999->getId(), 999);\n  eos::IFileMDPtr f1000 = view()->createFile(\"/f1000\", 0, 0, 0);\n  ASSERT_EQ(f1000->getId(), 1000);\n}\n\n//------------------------------------------------------------------------------\n// Tests targetting BulkNsObjectLocker\n//------------------------------------------------------------------------------\nTEST_F(HierarchicalViewF, BulkNsObjectLocker)\n{\n  {\n    auto container = view()->createContainer(\"/test/\", true);\n    auto container2 = view()->createContainer(\"/test/d1\", true);\n    eos::MDLocking::BulkContainerReadLock locker;\n    locker.add(container2.get());\n    locker.add(container.get());\n    auto locks = locker.lockAll();\n    // The order of the locks should be by ascending order of the container identifier\n    ASSERT_EQ(2, locks.size());\n    ASSERT_EQ(\"test\", locks[0]->getUnderlyingPtr()->getName());\n    ASSERT_EQ(\"d1\", locks[1]->getUnderlyingPtr()->getName());\n  }\n  {\n    auto file1 = view()->createFile(\"/test/f1\");\n    auto file2 = view()->createFile(\"/test/d1/f2\");\n    eos::MDLocking::BulkFileWriteLock locker;\n    locker.add(file2.get());\n    locker.add(file1.get());\n    auto locks = locker.lockAll();\n    ASSERT_EQ(2, locks.size());\n    ASSERT_EQ(\"f1\", locks[0]->getUnderlyingPtr()->getName());\n    ASSERT_EQ(\"f2\", locks[1]->getUnderlyingPtr()->getName());\n  }\n}\n\nTEST_F(HierarchicalViewF, BulkNsObjectLockerTryLock)\n{\n  // Thread 1 read locks one container while the thread 2 tries to bulk write lock them\n  // the locking done by the Thread 2 should wait that the thread 1 finishes\n  auto container = view()->createContainer(\"/test/\", true);\n  auto container2 = view()->createContainer(\"/test/d1\", true);\n  std::atomic<bool> containerLocked = false;\n  uint8_t sleepSeconds = 10;\n  auto threadReadLockingContainer = std::thread([&container, &containerLocked,\n              sleepSeconds]() {\n    eos::MDLocking::ContainerReadLock containerLocker(container.get());\n    containerLocked = true;\n    std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds + 0.1));\n  });\n  std::chrono::time_point<std::chrono::steady_clock> start;\n  std::chrono::time_point<std::chrono::steady_clock> stop;\n  auto threadBulkContainerLock = std::thread([&start, &stop, &container,\n          &container2, &containerLocked]() {\n    while (!containerLocked) {\n      std::this_thread::sleep_for(std::chrono::milliseconds(100));\n    }\n\n    eos::MDLocking::BulkContainerWriteLock locker;\n    locker.add(container2.get());\n    locker.add(container.get());\n    start = std::chrono::steady_clock::now();\n    auto locks = locker.lockAll();\n    stop = std::chrono::steady_clock::now();\n  });\n  threadBulkContainerLock.join();\n  threadReadLockingContainer.join();\n  auto diff = std::chrono::duration_cast<std::chrono::seconds>\n              (stop - start).count();\n  ASSERT_GE(sleepSeconds, diff);\n}\n\nTEST_F(HierarchicalViewF, BulkMDLockerTest)\n{\n  // Thread 1 read locks one file while the thread 2 tries to bulk write lock a container and that particular file\n  // the locking done by the Thread 2 should wait that the thread 1 finishes\n  auto container = view()->createContainer(\"/test/\", true);\n  auto container2 = view()->createContainer(\"/test/d1\", true);\n  auto file = view()->createFile(\"/test/d1/f1\", true);\n  std::atomic<bool> fileLocked = false;\n  uint8_t sleepSeconds = 10;\n  auto threadReadLockingFile = std::thread([&file, &fileLocked,\n         sleepSeconds]() {\n    eos::MDLocking::FileReadLock fileReadLocker(file.get());\n    fileLocked = true;\n    std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds + 0.1));\n  });\n  std::chrono::time_point<std::chrono::steady_clock> start;\n  std::chrono::time_point<std::chrono::steady_clock> stop;\n  auto threadBulkMultiNSObjLock = std::thread([&start, &stop, &container,\n          &container2, &file, &fileLocked]() {\n    while (!fileLocked) {\n      std::this_thread::sleep_for(std::chrono::milliseconds(100));\n    }\n\n    eos::MDLocking::BulkMDWriteLock locker;\n    locker.add(container.get());\n    locker.add(file.get());\n    start = std::chrono::steady_clock::now();\n    auto locks = locker.lockAll();\n    stop = std::chrono::steady_clock::now();\n  });\n  threadBulkMultiNSObjLock.join();\n  threadReadLockingFile.join();\n  auto diff = std::chrono::duration_cast<std::chrono::seconds>\n              (stop - start).count();\n  ASSERT_EQ(sleepSeconds, diff);\n}\n\nTEST_F(HierarchicalViewF, fileMDLockedSetSize)\n{\n  std::vector<std::thread> workers;\n  {\n    // Create 100 directories with each 10 files\n    for (int i = 0; i < 100; ++i) {\n      std::string dirName = \"/test/d\" + std::to_string(i);\n      view()->createContainer(dirName, true);\n\n      for (int j = 0; j < 10; j++) {\n        auto fmd = view()->createFile(dirName + \"/f\" + std::to_string(j));\n        fmd->setSize(10000);\n        view()->updateFileStore(fmd.get());\n      }\n    }\n\n    auto fmd = view()->createFile(\"/test/file.txt\");\n    fmd->setSize(10000);\n  }\n  {\n    // Tests a deadlock situation with the previous implementation of the accounting\n    for (int i = 0; i < 100; ++i) {\n      workers.emplace_back([this]() {\n        auto fmd = view()->getFile(\"/test/file.txt\");\n        auto fhLock = eos::MDLocking::writeLock(fmd.get());\n        fmd->setSize(10000);\n        view()->updateFileStore(fmd.get());\n      });\n    }\n\n    for (int i = 0; i < 100; ++i) {\n      workers.emplace_back([this]() {\n        auto cmd =  view()->getContainer(\"/test/\");\n        auto cmdLock = eos::MDLocking::writeLock(cmd.get());\n        auto fmd = view()->getFile(\"/test/file.txt\");\n        auto fhLock = eos::MDLocking::writeLock(fmd.get());\n        fmd->setSize(10000);\n        view()->updateFileStore(fmd.get());\n      });\n    }\n  }\n  {\n    for (int i = 0; i < 100; ++i) {\n      std::string dirName = \"/test/d\" + std::to_string(i);\n\n      for (int j = 0; j < 10; ++j) {\n        for (int k = 0; k < 10; ++k) {\n          workers.push_back(std::thread([this, i, j, k, dirName]() {\n            auto fmd = view()->getFile(dirName + \"/f\" + std::to_string(j));\n            auto fmdLock = eos::MDLocking::writeLock(fmd.get());\n\n            if (k % 2 == 0) {\n              fmd->setSize(fmd->getSize() + 1);\n            } else {\n              fmd->setSize(fmd->getSize() - 1);\n            }\n\n            view()->updateFileStore(fmd.get());\n          }));\n        }\n      }\n    }\n\n    for (auto& worker : workers) {\n      worker.join();\n    }\n\n    workers.clear();\n    // Sleep 6 seconds the time for the accounting thread to do its job\n    ::sleep(6);\n\n    for (int i = 0; i < 100; ++i) {\n      std::string dirName = \"/test/d\" + std::to_string(i);\n\n      for (int j = 0; j < 10; ++j) {\n        auto fmd = view()->getFile(dirName + \"/f\" + std::to_string(j));\n        ASSERT_EQ(10000, fmd->getSize());\n      }\n\n      auto dmd = view()->getContainer(dirName);\n      ASSERT_EQ(10 * 10000, dmd->getTreeSize());\n      ASSERT_EQ(10, dmd->getTreeFiles());\n      ASSERT_EQ(0, dmd->getTreeContainers());\n    }\n  }\n  ASSERT_EQ(10 * 100 + 1, view()->getContainer(\"/test/\")->getTreeFiles());\n  ASSERT_EQ(100, view()->getContainer(\"/test/\")->getTreeContainers());\n  {\n    for (int i = 0; i < 100; ++i) {\n      std::string dirName = \"/test/d\" + std::to_string(i);\n\n      for (int j = 0; j < 10; ++j) {\n        for (int k = 0; k < 10; ++k) {\n          workers.push_back(std::thread([this, i, j, k, dirName]() {\n            auto fmd = view()->getFile(dirName + \"/f\" + std::to_string(j));\n            auto fmdLock = eos::MDLocking::writeLock(fmd.get());\n\n            if (k % 2 == 0) {\n              fmd->setSize(fmd->getSize() + 1);\n            } else {\n              fmd->setSize(fmd->getSize() - 1);\n            }\n\n            view()->updateFileStore(fmd.get());\n          }));\n        }\n      }\n    }\n\n    for (auto& worker : workers) {\n      worker.join();\n    }\n\n    workers.clear();\n    // Sleep 6 seconds the time for the accounting thread to do its job\n    ::sleep(6);\n\n    for (int i = 0; i < 100; ++i) {\n      std::string dirName = \"/test/d\" + std::to_string(i);\n\n      for (int j = 0; j < 10; ++j) {\n        auto fmd = view()->getFile(dirName + \"/f\" + std::to_string(j));\n        ASSERT_EQ(10000, fmd->getSize());\n      }\n\n      auto dmd = view()->getContainer(dirName);\n      ASSERT_EQ(10 * 10000, dmd->getTreeSize());\n    }\n  }\n  // 100 directories * 10 files * 10000 bytes + test.txt (10000)\n  ASSERT_EQ(100 * 10 * 10000 + 10000,\n            view()->getContainer(\"/test/\")->getTreeSize());\n  ASSERT_EQ(100, view()->getContainer(\"/test/\")->getTreeContainers());\n  ASSERT_EQ(1001, view()->getContainer(\"/test/\")->getTreeFiles());\n}\n\nTEST_F(HierarchicalViewF, fileMDLockedClone)\n{\n  view()->createContainer(\"/test/\", true);\n  eos::IFileMDPtr f1 = view()->createFile(\"/test/f1\");\n  ASSERT_EQ(f1->getIdentifier(), eos::FileIdentifier(1));\n  eos::IFileMDPtr f2;\n  {\n    eos::MDLocking::FileWriteLock fileMDLocked(f1.get());\n    f2.reset(fileMDLocked->clone());\n    eos::MDLocking::FileReadLock file2MDLocked(f2.get());\n    ASSERT_EQ(f1->getIdentifier(), f2->getIdentifier());\n  }\n  ASSERT_EQ(eos::FileIdentifier(1), f1->getIdentifier());\n  ASSERT_EQ(f1->getIdentifier(), f2->getIdentifier());\n}\n\nTEST_F(HierarchicalViewF, fileMDLockedLocation)\n{\n  view()->createContainer(\"/test/\", true);\n  eos::IFileMDPtr f1 = view()->createFile(\"/test/f1\");\n  {\n    eos::MDLocking::FileWriteLock lock(f1.get());\n    f1->addLocation(1);\n    ASSERT_EQ(1, f1->getLocation(0));\n  }\n  std::vector<std::thread> addLocationWorkers;\n  {\n    //10 threads, each one adds a location\n    for (int i = 0; i < 10; ++i) {\n      addLocationWorkers.push_back(std::thread([this, i, &f1]() {\n        eos::MDLocking::FileWriteLock lock(f1.get());\n\n        for (int j = 0; j < 10; ++j) {\n          f1->addLocation((i * 10) + j);\n        }\n\n        view()->updateFileStore(f1.get());\n      }));\n    }\n  }\n  std::for_each(addLocationWorkers.begin(),\n  addLocationWorkers.end(), [](std::thread & t) {\n    t.join();\n  });\n  std::vector<std::thread> hasLocationWorkers;\n  {\n    for (int i = 0; i < 10; ++i) {\n      hasLocationWorkers.push_back(std::thread([i, &f1]() {\n        eos::MDLocking::FileWriteLock lock(f1.get());\n\n        for (int j = 0; j < 10; ++j) {\n          ASSERT_TRUE(f1->hasLocation((i * 10) + j));\n        }\n      }));\n    }\n  }\n  std::for_each(hasLocationWorkers.begin(),\n  hasLocationWorkers.end(), [](std::thread & t) {\n    t.join();\n  });\n  {\n    eos::MDLocking::FileWriteLock lock(f1.get());\n    ASSERT_EQ(100, f1->getNumLocation());\n    auto locations = f1->getLocations();\n    ASSERT_EQ(100, locations.size());\n  }\n  std::vector<std::thread> removeLocationWorkers;\n  {\n    //10 threads, each one adds a location\n    for (int i = 0; i < 10; ++i) {\n      removeLocationWorkers.push_back(std::thread([this, i, &f1]() {\n        eos::MDLocking::FileWriteLock lock(f1.get());\n\n        for (int j = 0; j < 10; ++j) {\n          f1->unlinkLocation((i * 10) + j);\n          f1->removeLocation((i * 10) + j);\n        }\n\n        view()->updateFileStore(f1.get());\n      }));\n    }\n  }\n  std::for_each(removeLocationWorkers.begin(),\n  removeLocationWorkers.end(), [](std::thread & t) {\n    t.join();\n  });\n  ASSERT_EQ(0, f1->getNumLocation());\n  //Add again 100 locations\n  addLocationWorkers.clear();\n\n  for (int i = 0; i < 10; ++i) {\n    addLocationWorkers.push_back(std::thread([this, i, &f1]() {\n      for (int j = 0; j < 10; ++j) {\n        f1->addLocation((i * 10) + j);\n        view()->updateFileStore(f1.get());\n      }\n    }));\n  }\n\n  std::for_each(addLocationWorkers.begin(),\n  addLocationWorkers.end(), [](std::thread & t) {\n    t.join();\n  });\n  //Try the removeAllLocation\n  {\n    eos::MDLocking::FileWriteLock lock(f1.get());\n    f1->unlinkAllLocations();\n    f1->removeAllLocations();\n    view()->updateFileStore(f1.get());\n  }\n  ASSERT_EQ(0, f1->getNumLocation());\n  addLocationWorkers.clear();\n\n  for (int i = 0; i < 10; ++i) {\n    addLocationWorkers.push_back(std::thread([this, i, &f1]() {\n      for (int j = 0; j < 10; ++j) {\n        f1->addLocation((i * 10) + j);\n      }\n\n      view()->updateFileStore(f1.get());\n    }));\n  }\n\n  std::for_each(addLocationWorkers.begin(),\n  addLocationWorkers.end(), [](std::thread & t) {\n    t.join();\n  });\n  f1->unlinkLocation(0);\n  ASSERT_EQ(1, f1->getUnlinkedLocations().size());\n  ASSERT_EQ(1, f1->getNumUnlinkedLocation());\n  f1->clearUnlinkedLocations();\n  view()->updateFileStore(f1.get());\n  ASSERT_EQ(0, f1->getNumUnlinkedLocation());\n}\n\nTEST_F(HierarchicalViewF, fileMDLockedRemoveLocation)\n{\n  view()->createContainer(\"/test/\", true);\n  eos::IFileMDPtr f1 = view()->createFile(\"/test/f1\");\n  std::vector<std::thread> addLocationWorkers;\n  std::vector<std::thread> removeLocationWorkers;\n\n  for (int i = 0; i < 10; ++i) {\n    addLocationWorkers.push_back(std::thread([this, i, &f1]() {\n      eos::MDLocking::FileWriteLock lock(f1.get());\n\n      for (int j = 0; j < 10; ++j) {\n        f1->addLocation((i * 10) + j);\n        view()->updateFileStore(f1.get());\n      }\n    }));\n  }\n\n  std::for_each(addLocationWorkers.begin(),\n  addLocationWorkers.end(), [](std::thread & t) {\n    t.join();\n  });\n\n  for (int i = 0; i < 10; ++i) {\n    removeLocationWorkers.push_back(std::thread([this, i, &f1]() {\n      eos::MDLocking::FileWriteLock lock(f1.get());\n\n      for (int j = 0; j < 10; ++j) {\n        f1->unlinkLocation((i * 10) + j);\n        f1->removeLocation((i * 10) + j);\n        view()->updateFileStore(f1.get());\n      }\n    }));\n  }\n\n  std::for_each(removeLocationWorkers.begin(),\n  removeLocationWorkers.end(), [](std::thread & t) {\n    t.join();\n  });\n  ASSERT_EQ(0, f1->getNumLocation());\n}\n\nTEST_F(HierarchicalViewF, containerMDFindItem)\n{\n  const int nbLoops = 10;\n  eos::IContainerMDPtr testCont = view()->createContainer(\"/test/\", true);\n\n  for (int i = 0; i < nbLoops; ++i) {\n    std::stringstream fileName;\n    std::stringstream contName;\n    fileName << \"/test/f\" << i;\n    contName << \"/test/c\" << i;\n    view()->createFile(fileName.str());\n    view()->createContainer(contName.str());\n  }\n\n  std::vector<std::thread> workers;\n\n  for (int i = 1; i < nbLoops; ++i) {\n    workers.push_back(std::thread([i, testCont]() {\n      {\n        std::stringstream fileName;\n        std::stringstream contName;\n        fileName << \"f\" << i;\n        contName << \"c\" << i;\n        eos::MDLocking::ContainerReadLock containerMDLocker(testCont.get());\n        {\n          auto contOrFile = testCont->findItem(fileName.str()).get();\n          ASSERT_TRUE(contOrFile.file != nullptr);\n        }\n        {\n          auto contOrFile = testCont->findItem(contName.str()).get();\n          ASSERT_TRUE(contOrFile.container != nullptr);\n        }\n      }\n    }));\n  }\n\n  {\n    eos::MDLocking::ContainerReadLock containerMDLocker(testCont.get());\n    {\n      auto contOrFile = testCont->findItem(\"f0\").get();\n      ASSERT_TRUE(contOrFile.file != nullptr);\n    }\n    {\n      auto contOrFile = testCont->findItem(\"c0\").get();\n      ASSERT_TRUE(contOrFile.container != nullptr);\n    }\n  }\n\n  for (auto& t : workers) {\n    t.join();\n  }\n}\n\nTEST_F(HierarchicalViewF, containerMDAddContainerThenRemove)\n{\n  auto rootContainer = view()->createContainer(\"/root/\", true);\n  auto rootContainerID = rootContainer->getId();\n  eos::MDLocking::ContainerWriteLock rootLocker(rootContainer.get());\n  auto testContainer = view()->createContainer(\"/test/\", true);\n  auto testContainerID = testContainer->getId();\n  rootContainer->addContainer(testContainer.get());\n  view()->updateContainerStore(rootContainer.get());\n  ASSERT_EQ(1, rootContainer->getNumContainers());\n  ASSERT_EQ(rootContainerID, testContainer->getParentId());\n  ASSERT_EQ(testContainerID,\n            testContainer->getIdentifier().getUnderlyingUInt64());\n  rootContainer->removeContainer(\"test\");\n  view()->updateContainerStore(rootContainer.get());\n  ASSERT_EQ(0, rootContainer->getNumContainers());\n}\n\nTEST_F(HierarchicalViewF, containerMDaddFileThenRemove)\n{\n  auto rootContainer = view()->createContainer(\"/root/\", true);\n  eos::MDLocking::ContainerWriteLock rootLocker(rootContainer.get());\n  auto testFile = view()->createFile(\"/root/test\");\n  rootContainer->addFile(testFile.get());\n  ASSERT_EQ(1, rootContainer->getNumFiles());\n  rootContainer->removeFile(\"test\");\n  ASSERT_EQ(0, rootContainer->getNumFiles());\n}\n\nTEST_F(HierarchicalViewF, containerMDGetSetName)\n{\n  auto rootContainer = view()->createContainer(\"/root/\", true);\n  ASSERT_EQ(\"root\", rootContainer->getName());\n  eos::MDLocking::ContainerWriteLock rootLocker(rootContainer.get());\n  rootContainer->setName(\"newname\");\n  ASSERT_EQ(\"newname\", rootContainer->getName());\n}\n\nTEST_F(HierarchicalViewF, containerMDBasicGettersSetters)\n{\n  auto rootContainer = view()->createContainer(\"/root/\", true);\n  eos::MDLocking::ContainerWriteLock rootLocker(rootContainer.get());\n  rootContainer->setCUid(2);\n  ASSERT_EQ(2, rootContainer->getCUid());\n  rootContainer->setCGid(23);\n  ASSERT_EQ(23, rootContainer->getCGid());\n  rootContainer->setCloneId(42);\n  ASSERT_EQ(42, rootContainer->getCloneId());\n  rootContainer->setCloneFST(\"clone_fst\");\n  ASSERT_EQ(\"clone_fst\", rootContainer->getCloneFST());\n  rootContainer->setMode(S_IRWXU);\n  ASSERT_EQ(S_IRWXU, rootContainer->getMode());\n  rootContainer->setTreeSize(64);\n  ASSERT_EQ(64, rootContainer->getTreeSize());\n  eos::IContainerMD::ctime_t tnow;\n  clock_gettime(CLOCK_REALTIME, &tnow);\n  rootContainer->setCTime(tnow);\n  eos::IContainerMD::ctime_t containerTime;\n  rootContainer->getCTime(containerTime);\n  ASSERT_EQ(tnow.tv_sec, containerTime.tv_sec);\n  rootContainer->setMTime(tnow);\n  rootContainer->getMTime(containerTime);\n  ASSERT_EQ(tnow.tv_sec, containerTime.tv_sec);\n  eos::IContainerMD::tmtime_t containerTMTime;\n  rootContainer->setTMTimeNow();\n  rootContainer->getTMTime(containerTMTime);\n  rootContainer->setTMTimeNow();\n  eos::IContainerMD::tmtime_t newContainerTMTime;\n  rootContainer->getTMTime(newContainerTMTime);\n  ASSERT_NE(containerTMTime.tv_nsec, newContainerTMTime.tv_nsec);\n}\n\nTEST_F(HierarchicalViewF, containerMDSyncTimeAccounting)\n{\n  auto containerSyncTimeAccounting =\n    view()->createContainer(\"/root/test/containersynctimeaccounting/\", true);\n  eos::IContainerMDPtr rootContainer;\n  eos::IContainerMD::ctime_t rootContainerTimeBeforeNotify,\n      rootContainerMTimeAfterNotify;\n  {\n    eos::MDLocking::ContainerWriteLock containerSyncTimeAccountingLocker(\n      containerSyncTimeAccounting.get());\n    containerSyncTimeAccounting->setAttribute(\"sys.mtime.propagation\", \"true\");\n    auto testContainer = view()->getContainer(\"/root/test/\");\n    testContainer->setAttribute(\"sys.mtime.propagation\", \"true\");\n    rootContainer = view()->getContainer(\"/root/\");\n    rootContainer->setAttribute(\"sys.mtime.propagation\", \"true\");\n    rootContainer->setMTimeNow();\n    rootContainer->getTMTime(rootContainerTimeBeforeNotify);\n    ::sleep(1);\n    containerSyncTimeAccounting->setMTimeNow();\n    containerSyncTimeAccounting->notifyMTimeChange(containerSvc());\n    view()->updateContainerStore(containerSyncTimeAccounting.get());\n  }\n  //Sleep 6 seconds the time the Container Accounting Thread does its job...\n  ::sleep(6);\n  rootContainer->getTMTime(rootContainerMTimeAfterNotify);\n  ASSERT_EQ(rootContainerTimeBeforeNotify.tv_sec + 1,\n            rootContainerMTimeAfterNotify.tv_sec);\n}\n\nTEST_F(HierarchicalViewF, containerMDAttributesOps)\n{\n  auto rootContainer = view()->createContainer(\"/root/\", true);\n  eos::MDLocking::ContainerWriteLock rootLocker(rootContainer.get());\n  rootContainer->setAttribute(\"attribute1\", \"value1\");\n  rootContainer->setAttribute(\"attribute2\", \"value2\");\n  ASSERT_TRUE(rootContainer->hasAttribute(\"attribute1\"));\n  ASSERT_EQ(\"value1\", rootContainer->getAttribute(\"attribute1\"));\n  ASSERT_THROW(rootContainer->getAttribute(\"DOES_NOT_EXIST\"), eos::MDException);\n  ASSERT_EQ(2, rootContainer->numAttributes());\n  ASSERT_EQ(2, rootContainer->getAttributes().size());\n  rootContainer->removeAttribute(\"attribute1\");\n  ASSERT_EQ(1, rootContainer->numAttributes());\n}\n\nTEST_F(HierarchicalViewF, getFileOrContainerLockedShouldThrow)\n{\n  ASSERT_THROW(eos::MDLocking::ContainerWriteLock(nullptr),\n               eos::MDException);\n  ASSERT_THROW(eos::MDLocking::FileWriteLock(nullptr), eos::MDException);\n}\n\nTEST_F(HierarchicalViewF, getFileWhileBeingWriteLocked)\n{\n  using namespace std::chrono_literals;\n  view()->createContainer(\"/root/\", true);\n  auto file = view()->createFile(\"/root/file1\");\n  //Create two threads, one will write lock the file and wait X seconds, one will\n  //try to retrieve the file from the view\n  std::atomic<bool> fileLocked = false;\n  uint8_t sleepSeconds = 3;\n  auto threadWriteLockingFile = std::thread([&file, &fileLocked, sleepSeconds]() {\n    eos::MDLocking::FileWriteLock fileLocker(file.get());\n    fileLocked = true;\n    std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds + 0.1));\n  });\n  std::chrono::time_point<std::chrono::steady_clock> start;\n  std::chrono::time_point<std::chrono::steady_clock> stop;\n  auto threadGetFile = std::thread([this, &start, &stop, &fileLocked]() {\n    while (!fileLocked) {\n      std::this_thread::sleep_for(std::chrono::milliseconds(100ms));\n    }\n\n    start = std::chrono::steady_clock::now();\n    view()->getFile(\"/root/file1\");\n    stop = std::chrono::steady_clock::now();\n  });\n  threadWriteLockingFile.join();\n  threadGetFile.join();\n  auto diff = std::chrono::duration_cast<std::chrono::seconds>\n              (stop - start).count();\n  ASSERT_EQ(sleepSeconds, diff);\n}\n\nTEST_F(HierarchicalViewF, getFileAfterBeingRenamed)\n{\n  auto root = view()->createContainer(\"/root/\", true);\n  auto file = view()->createFile(\"/root/file1\");\n  //Create two threads, one will write lock the file and rename it, one will\n  //wait X seconds, asks the view to retrieve it and see if it locks.\n  std::atomic<bool> fileRenamed = false;\n  std::atomic<bool> threadRenameStarted = false;\n  uint8_t sleepSeconds = 3;\n  auto threadWriteLockingFile = std::thread([this, &file, &root, &fileRenamed,\n        &threadRenameStarted, sleepSeconds]() {\n    threadRenameStarted = true;\n    eos::MDLocking::FileWriteLock fileLocker(file.get());\n    view()->renameFile(file.get(), \"file2\");\n    view()->updateContainerStore(root.get());\n    fileRenamed = true;\n    std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds + 0.1));\n  });\n  std::chrono::time_point<std::chrono::steady_clock> start;\n  std::chrono::time_point<std::chrono::steady_clock> stop;\n  auto threadGetFile = std::thread([this, &start, &stop, &fileRenamed,\n        &threadRenameStarted]() {\n    while (!threadRenameStarted) {}\n\n    while (!fileRenamed) {}\n\n    ASSERT_THROW(view()->getFile(\"/root/file1\"), eos::MDException);\n    start = std::chrono::steady_clock::now();\n    auto file2 = view()->getFile(\"/root/file2\");\n    auto file2Lock = eos::MDLocking::readLock(file2.get());\n    stop = std::chrono::steady_clock::now();\n    ASSERT_EQ(\"file2\", file2->getName());\n  });\n  threadWriteLockingFile.join();\n  threadGetFile.join();\n  auto diff = std::chrono::duration_cast<std::chrono::seconds>\n              (stop - start).count();\n  ASSERT_EQ(sleepSeconds, diff);\n}\n\nTEST_F(HierarchicalViewF,\n       NSObjectLockerNoDeadlockIfLockDestroyedAfterOwnedSharedPtr)\n{\n  eos::MDLocking::ContainerWriteLockPtr contLock;\n  {\n    auto cont = view()->createContainer(\"/root/\", true);\n    contLock = eos::MDLocking::writeLock(cont.get());\n    containerSvc()->removeContainer(cont.get());\n    cont.reset();\n    ASSERT_THROW(view()->getContainer(\"/root/\"), eos::MDException);\n  }\n  //If you have a deadlock here in the destructor of the contLock, then something is wrong...\n}\n\nTEST_F(HierarchicalViewF, getMDFollowsSymlinks)\n{\n  view()->createContainer(\"/eos/dest_symlink/dir1/\", true);\n  view()->createFile(\"/eos/dest_symlink/dir1/file.txt\", true);\n  view()->createContainer(\"/eos/dir2/\");\n  view()->createLink(\"/eos/dir2/dest_symlink\", \"/eos/dest_symlink/\");\n  auto file = view()->getFile(\"/eos/dest_symlink/dir1/file.txt\");\n  auto fileLock = eos::MDLocking::readLock(file.get());\n  ASSERT_EQ(\"file.txt\", file->getName());\n  auto container = view()->getContainer(\"/eos/dest_symlink/dir1/\");\n  auto containerReadLock = eos::MDLocking::readLock(container.get());\n  auto containerViaSymlink = view()->getContainer(\"/eos/dir2/dest_symlink/dir1/\");\n  ASSERT_EQ(container, containerViaSymlink);\n  auto fileGetItem = view()->getItem(\"/eos/dest_symlink/dir1/file.txt\").get();\n  ASSERT_EQ(file, fileGetItem.file);\n}\n\nTEST_F(HierarchicalViewF, getMDMultiThreaded)\n{\n  std::string dirPath = \"/eos/dir1/dir2/dir3/\";\n  std::string filePath = dirPath + \"file.txt\";\n  uint16_t loops = 100;\n  auto dir = view()->createContainer(dirPath, true);\n  auto file = view()->createFile(filePath);\n  auto fileId = file->getId();\n  auto dirId = dir->getId();\n  // Make sure the newly created directory and file are actually commited in the\n  // QDB backend otherwise the first thread that clears the cache and asks for\n  // the file might get an eos::MDException\n  mdFlusher()->synchronize();\n  std::vector<std::thread> workers;\n  workers.emplace_back([this, loops]() {\n    for (uint16_t i = 0; i < loops; ++i) {\n      cleanNSCache();\n      auto dh = view()->getContainer(\"/eos/\");\n      auto dhLock = eos::MDLocking::writeLock(dh.get());\n      dh->setTreeSize(i);\n      view()->updateContainerStore(dh.get());\n    }\n  });\n  workers.emplace_back([this, loops]() {\n    for (uint16_t i = 0; i < loops; ++i) {\n      cleanNSCache();\n      auto dh = view()->getContainer(\"/eos/dir1/\");\n      auto dhLock = eos::MDLocking::writeLock(dh.get());\n      dh->setTreeSize(i);\n      view()->updateContainerStore(dh.get());\n    }\n  });\n  workers.emplace_back([this, loops]() {\n    for (uint16_t i = 0; i < loops; ++i) {\n      auto dh = view()->getContainer(\"/eos/dir1/dir2\");\n      auto dhLock = eos::MDLocking::writeLock(dh.get());\n      dh->setTreeSize(i);\n      view()->updateContainerStore(dh.get());\n    }\n  });\n  workers.emplace_back([this, loops, filePath]() {\n    for (uint16_t i = 0; i < loops; ++i) {\n      auto fh = view()->getFile(filePath);\n      auto fhLock = eos::MDLocking::writeLock(fh.get());\n      fh->setSize(i);\n      view()->updateFileStore(fh.get());\n    }\n  });\n  workers.emplace_back([this, loops, dirId, dirPath, filePath]() {\n    for (uint16_t i = 0; i < loops; ++i) {\n      cleanNSCache();\n      auto fh = view()->getFile(filePath);\n      auto dh = view()->getContainer(dirPath);\n      eos::MDLocking::BulkMDWriteLock locker;\n      locker.add(dh.get());\n      locker.add(fh.get());\n      auto locks = locker.lockAll();\n      fh->setSize(i);\n      dh->addFile(fh.get());\n      view()->updateFileStore(fh.get());\n      view()->updateContainerStore(dh.get());\n    }\n  });\n  workers.emplace_back([this, loops, dirPath, filePath]() {\n    for (uint16_t i = 0; i < loops; ++i) {\n      auto fh = view()->getFile(filePath);\n      auto dh = view()->getContainer(dirPath);\n      eos::MDLocking::BulkMDReadLock locker;\n      locker.add(fh.get());\n      locker.add(dh.get());\n      auto locks = locker.lockAll();\n      auto fileId = fh->getId();\n      auto contId = dh->getId();\n      (void) fileId;\n      (void) contId;\n    }\n  });\n  workers.emplace_back([this, loops, fileId, dirId]() {\n    for (uint16_t i = 0; i < loops; ++i) {\n      cleanNSCache();\n      {\n        auto fh = view()->getFileMDSvc()->getFileMD(fileId);\n        eos::MDLocking::FileWriteLockPtr fhLock = eos::MDLocking::writeLock(fh.get());\n        std::string uri = view()->getUri(fh.get());\n      }\n      {\n        auto dh = view()->getContainerMDSvc()->getContainerMD(dirId);\n        eos::MDLocking::ContainerWriteLockPtr dhLock = eos::MDLocking::writeLock(\n              dh.get());\n        std::string uri = view()->getUri(dh.get());\n      }\n    }\n  });\n\n  for (auto& worker : workers) {\n    worker.join();\n  }\n}\n"
  },
  {
    "path": "namespace/ns_quarkdb/tests/LruBenchmark.cc",
    "content": "//------------------------------------------------------------------------------\n// @file LruBenchmark.cc\n// @author Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/CLI11.hpp\"\n#include \"namespace/ns_quarkdb/LRU.hh\"\n\n//! Global synchronization primitives\nstd::mutex gMutex;\nstd::condition_variable gCondVar;\nstd::atomic<unsigned long> gDoneWork {0};\n\n//------------------------------------------------------------------------------\n//! Dummy struct used to populate the LRU\n//------------------------------------------------------------------------------\nstruct Entry {\n  explicit Entry(std::uint64_t id) : id_(id) {}\n\n  ~Entry() = default;\n\n  std::uint64_t\n  getId() const\n  {\n    return id_;\n  }\n\n  std::uint64_t id_;\n};\n\n//------------------------------------------------------------------------------\n//! Populate LRU with the desired number of entries\n//------------------------------------------------------------------------------\nvoid Populate(eos::LRU<std::uint64_t, Entry>& lru, uint64_t size)\n{\n  for (std::uint64_t id = 1; id <= size; ++id) {\n    lru.put(id, std::make_shared<Entry>(id));\n  }\n}\n\n//------------------------------------------------------------------------------\n//! Work done by each individual thread\n//------------------------------------------------------------------------------\nvoid WokerThread(eos::LRU<std::uint64_t, Entry>& lru, std::uint64_t num_req,\n                 std::uint64_t max_size)\n{\n  // Pick a random start location between [1, max_size]\n\n  unsigned long long random_start =\n      eos::common::getRandom(1ull, (unsigned long long)max_size);\n  // Wait for notification from the main thread\n  std::unique_lock<std::mutex> lock(gMutex);\n  gCondVar.wait(lock);\n  lock.unlock();\n\n  while (num_req) {\n    lru.get(random_start);\n    random_start = (random_start + 1) % max_size + 1;\n    --num_req;\n  }\n\n  ++gDoneWork;\n  gCondVar.notify_one();\n}\n\n//------------------------------------------------------------------------------\n// Main programm\n//------------------------------------------------------------------------------\nint main(int argc, char* argv[])\n{\n  CLI::App app{\"LRU benchmark tool\"};\n  std::uint64_t max_size = 1000000;\n  std::uint32_t num_threads = 1;\n  std::uint64_t num_requests = max_size / 10;\n  app.add_option(\"-s,--size\", max_size, \"max size of the LRU\");\n  app.add_option(\"-t,--num_threads\", num_threads,\n                 \"number of threads for access operations\");\n  app.add_option(\"-r,--num_requests\", num_requests,\n                 \"number of requests per thread\");\n  CLI11_PARSE(app, argc, argv);\n  eos::LRU<std::uint64_t, Entry> lru{max_size + 10};\n  Populate(lru, max_size);\n  std::list<std::thread> workers;\n\n  for (auto i = 0ull; i < num_threads; ++i) {\n    workers.emplace_back(WokerThread, std::ref(lru), num_requests, max_size);\n  }\n\n  // Sleep a bit to allow all threads to start\n  std::this_thread::sleep_for(std::chrono::seconds(2));\n  auto start_ts = std::chrono::system_clock::now();\n  gCondVar.notify_all();\n  // Wait for all threads to finish\n  {\n    std::unique_lock<std::mutex> lock(gMutex);\n    gCondVar.wait(lock, [&] {return (gDoneWork == num_threads);});\n  }\n  auto end_ts = std::chrono::system_clock::now();\n  auto duration = std::chrono::duration_cast<std::chrono::microseconds>\n                  (end_ts - start_ts);\n  std::uint64_t total_req = num_threads * num_requests;\n  std::cout << \"Rate : \" << ((total_req * 1000000) / duration.count()) / 100 <<\n            \" kHz\\n\";\n\n  for (auto& thread : workers) {\n    thread.join();\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "namespace/ns_quarkdb/tests/Main.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Main.cc, test initialization\n//------------------------------------------------------------------------------\n\n#include <gtest/gtest.h>\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n\nint main(int argc, char** argv)\n{\n  const std::string testpath(\"/tmp/eos-ns-tests/\");\n  std::string rmrf = \"rm -rf \" + testpath;\n  (void) !system(rmrf.c_str());\n  mkdir(testpath.c_str(), 0755);\n  testing::InitGoogleTest(&argc, argv);\n  return RUN_ALL_TESTS();\n}\n"
  },
  {
    "path": "namespace/ns_quarkdb/tests/MetadataFiltering.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Metadata filtering tests\n//------------------------------------------------------------------------------\n\n#include \"namespace/ns_quarkdb/inspector/FileMetadataFilter.hh\"\n#include \"namespace/ns_quarkdb/inspector/AttributeExtraction.hh\"\n#include \"common/LayoutId.hh\"\n\n#include <gtest/gtest.h>\n\nusing namespace eos;\n\nTEST(StringEvaluator, Literal) {\n  eos::ns::FileMdProto proto;\n  std::string out;\n\n  StringEvaluator literal(\"some string literal\", true);\n  ASSERT_EQ(literal.describe(), \"'some string literal'\");\n  ASSERT_TRUE(literal.evaluate(proto, out));\n  ASSERT_EQ(out, \"some string literal\");\n\n  literal = StringEvaluator(\"\", true);\n  ASSERT_EQ(literal.describe(), \"''\");\n  ASSERT_TRUE(literal.evaluate(proto, out));\n  ASSERT_EQ(out, \"\");\n}\n\nTEST(StringEvaluator, VariableName) {\n  eos::ns::FileMdProto proto;\n  std::string out;\n\n  proto.set_size(5);\n\n  StringEvaluator literal(\"size\", false);\n  ASSERT_EQ(literal.describe(), \"size\");\n  ASSERT_TRUE(literal.evaluate(proto, out));\n  ASSERT_EQ(out, \"5\");\n\n  proto.set_size(555);\n  ASSERT_TRUE(literal.evaluate(proto, out));\n  ASSERT_EQ(out, \"555\");\n}\n\nTEST(StringEvaluator, InvalidVariableName) {\n  eos::ns::FileMdProto proto;\n  std::string out;\n\n  StringEvaluator literal(\"aaa\", false);\n  ASSERT_EQ(literal.describe(), \"aaa\");\n  ASSERT_FALSE(literal.evaluate(proto, out));\n  ASSERT_EQ(out, \"\");\n}\n\nTEST(AttributeExtraction, BasicSanity) {\n  eos::ns::FileMdProto proto;\n  std::string out;\n\n  ASSERT_FALSE(AttributeExtraction::asString(proto, \"aaa\", out));\n\n  ASSERT_TRUE(AttributeExtraction::asString(proto, \"xattr.aaa\", out));\n  ASSERT_TRUE(out.empty());\n\n  (*proto.mutable_xattrs())[\"user.test\"] = \"123\";\n  ASSERT_TRUE(AttributeExtraction::asString(proto, \"xattr.user.test\", out));\n  ASSERT_EQ(out, \"123\");\n\n  proto.set_id(1111);\n  ASSERT_TRUE(AttributeExtraction::asString(proto, \"fid\", out));\n  ASSERT_EQ(out, \"1111\");\n\n  proto.set_cont_id(22222);\n  ASSERT_TRUE(AttributeExtraction::asString(proto, \"pid\", out));\n  ASSERT_EQ(out, \"22222\");\n\n  proto.set_gid(333);\n  ASSERT_TRUE(AttributeExtraction::asString(proto, \"gid\", out));\n  ASSERT_EQ(out, \"333\");\n\n  proto.set_uid(444);\n  ASSERT_TRUE(AttributeExtraction::asString(proto, \"uid\", out));\n  ASSERT_EQ(out, \"444\");\n\n  proto.set_size(555);\n  ASSERT_TRUE(AttributeExtraction::asString(proto, \"size\", out));\n  ASSERT_EQ(out, \"555\");\n\n  unsigned long layout = eos::common::LayoutId::GetId(\n    eos::common::LayoutId::kReplica,\n    eos::common::LayoutId::kAdler,\n    2,\n    eos::common::LayoutId::k4k);\n\n  proto.set_layout_id(layout);\n  ASSERT_TRUE(AttributeExtraction::asString(proto, \"layout_id\", out));\n  ASSERT_EQ(out, \"1048850\");\n\n  proto.set_flags(0777);\n  ASSERT_TRUE(AttributeExtraction::asString(proto, \"flags\", out));\n  ASSERT_EQ(out, \"777\");\n\n  proto.set_name(\"aaaaa\");\n  ASSERT_TRUE(AttributeExtraction::asString(proto, \"name\", out));\n  ASSERT_EQ(out, \"aaaaa\");\n\n  proto.set_link_name(\"bbbbbb\");\n  ASSERT_TRUE(AttributeExtraction::asString(proto, \"link_name\", out));\n  ASSERT_EQ(out, \"bbbbbb\");\n\n  struct timespec ctime;\n  ctime.tv_sec = 1999;\n  ctime.tv_nsec = 8888;\n  proto.set_ctime(&ctime, sizeof(ctime));\n  ASSERT_TRUE(AttributeExtraction::asString(proto, \"ctime\", out));\n  ASSERT_EQ(out, \"1999.8888\");\n\n  struct timespec mtime;\n  mtime.tv_sec = 1998;\n  mtime.tv_nsec = 7777;\n  proto.set_mtime(&mtime, sizeof(mtime));\n  ASSERT_TRUE(AttributeExtraction::asString(proto, \"mtime\", out));\n  ASSERT_EQ(out, \"1998.7777\");\n\n  char buff[32];\n  buff[0] = 0x12; buff[1] = 0x23; buff[2] = 0x55; buff[3] = 0x99;\n  buff[4] = 0xAA; buff[5] = 0xDD; buff[6] = 0x00; buff[7] = 0x55;\n  proto.set_checksum(buff, 8);\n  ASSERT_TRUE(AttributeExtraction::asString(proto, \"xs\", out));\n  ASSERT_EQ(out, \"12235599\");\n\n  proto.add_locations(3);\n  proto.add_locations(2);\n  proto.add_locations(1);\n  ASSERT_TRUE(AttributeExtraction::asString(proto, \"locations\", out));\n  ASSERT_EQ(out, \"3,2,1\");\n\n  proto.add_unlink_locations(4);\n  proto.add_unlink_locations(5);\n  proto.add_unlink_locations(6);\n  ASSERT_TRUE(AttributeExtraction::asString(proto, \"unlink_locations\", out));\n  ASSERT_EQ(out, \"4,5,6\");\n\n  struct timespec stime;\n  stime.tv_sec = 1997;\n  stime.tv_nsec = 5555;\n  proto.set_stime(&stime, sizeof(stime));\n  ASSERT_TRUE(AttributeExtraction::asString(proto, \"stime\", out));\n  ASSERT_EQ(out, \"1997.5555\");\n}\n\nTEST(FileMetadataFilter, InvalidFilter) {\n  EqualityFileMetadataFilter invalidFilter(\n    StringEvaluator(\"invalid.attr\", false), StringEvaluator(\"aaa\", true), false);\n\n  ASSERT_FALSE(invalidFilter.isValid());\n  ASSERT_EQ(invalidFilter.describe(), \"[(22): could not evaluate string expression invalid.attr]\");\n}\n\nTEST(FileMetadataFilter, ZeroSizeFilter) {\n  EqualityFileMetadataFilter sizeFilter(StringEvaluator(\"size\", false), StringEvaluator(\"0\", true), false);\n  ASSERT_TRUE(sizeFilter.isValid());\n  ASSERT_EQ(sizeFilter.describe(), \"size == '0'\");\n\n  eos::ns::FileMdProto proto;\n\n  proto.set_size(33);\n  ASSERT_FALSE(sizeFilter.check(proto));\n\n  proto.set_size(0);\n  ASSERT_TRUE(sizeFilter.check(proto));\n}\n\nTEST(FileMetadataFilter, AndFilter) {\n  std::unique_ptr<FileMetadataFilter> sizeFilter(\n    new EqualityFileMetadataFilter(StringEvaluator(\"size\", false), StringEvaluator(\"0\", true), false));\n\n  std::unique_ptr<FileMetadataFilter> nameFilter(\n    new EqualityFileMetadataFilter(StringEvaluator(\"name\", false), StringEvaluator(\"chickens\", true), false));\n\n  LogicalMetadataFilter andFilter(\n    std::move(sizeFilter),\n    std::move(nameFilter),\n    false\n  );\n\n  ASSERT_EQ(andFilter.describe(), \"(size == '0' && name == 'chickens')\");\n\n  eos::ns::FileMdProto proto;\n\n  proto.set_size(33);\n  ASSERT_FALSE(andFilter.check(proto));\n\n  proto.set_size(0);\n  ASSERT_FALSE(andFilter.check(proto));\n\n  proto.set_name(\"chickens\");\n  ASSERT_TRUE(andFilter.check(proto));\n\n  proto.set_name(\"chickens-2\");\n  ASSERT_FALSE(andFilter.check(proto));\n}\n\nTEST(FileMetadataFilter, OrFilter) {\n  std::unique_ptr<FileMetadataFilter> sizeFilter(\n    new EqualityFileMetadataFilter(StringEvaluator(\"size\", false), StringEvaluator(\"0\", true), false));\n\n  std::unique_ptr<FileMetadataFilter> nameFilter(\n    new EqualityFileMetadataFilter(StringEvaluator(\"name\", false), StringEvaluator(\"chickens\", true), false));\n\n  LogicalMetadataFilter orFilter(\n    std::move(sizeFilter),\n    std::move(nameFilter),\n    true\n  );\n\n  ASSERT_EQ(orFilter.describe(), \"(size == '0' || name == 'chickens')\");\n\n  eos::ns::FileMdProto proto;\n\n  proto.set_size(33);\n  ASSERT_FALSE(orFilter.check(proto));\n\n  proto.set_size(0);\n  ASSERT_TRUE(orFilter.check(proto));\n\n  proto.set_name(\"chickens\");\n  ASSERT_TRUE(orFilter.check(proto));\n\n  proto.set_name(\"chickens-2\");\n  ASSERT_TRUE(orFilter.check(proto));\n\n  proto.set_size(22);\n  ASSERT_FALSE(orFilter.check(proto));\n}\n\nTEST(FilterExpressionLexer, BasicSanity) {\n  std::vector<ExpressionLexicalToken> tokens;\n  common::Status st = FilterExpressionLexer::lex(\"   (  'abc )( ' == ' cde' && || ) \", tokens);\n\n  ASSERT_TRUE(st);\n  ASSERT_EQ(tokens.size(), 7u);\n\n  ASSERT_EQ(tokens[0], ExpressionLexicalToken(TokenType::kLPAREN, \"(\"));\n  ASSERT_EQ(tokens[1], ExpressionLexicalToken(TokenType::kLITERAL, \"abc )( \"));\n  ASSERT_EQ(tokens[2], ExpressionLexicalToken(TokenType::kEQUALITY, \"==\"));\n  ASSERT_EQ(tokens[3], ExpressionLexicalToken(TokenType::kLITERAL, \" cde\"));\n  ASSERT_EQ(tokens[4], ExpressionLexicalToken(TokenType::kAND, \"&&\"));\n  ASSERT_EQ(tokens[5], ExpressionLexicalToken(TokenType::kOR, \"||\"));\n  ASSERT_EQ(tokens[6], ExpressionLexicalToken(TokenType::kRPAREN, \")\"));\n}\n\nTEST(FilterExpressionLexer, VariableEquality) {\n  std::vector<ExpressionLexicalToken> tokens;\n  common::Status st = FilterExpressionLexer::lex(\"   ( varName123 == 'abc' ) \", tokens);\n\n  ASSERT_TRUE(st);\n  ASSERT_EQ(tokens.size(), 5u);\n\n  ASSERT_EQ(tokens[0], ExpressionLexicalToken(TokenType::kLPAREN, \"(\"));\n\n  ASSERT_EQ(tokens[1].mType, TokenType::kVAR);\n  ASSERT_EQ(tokens[1].mContents, \"varName123\");\n\n  ASSERT_EQ(tokens[1], ExpressionLexicalToken(TokenType::kVAR, \"varName123\"));\n\n  ASSERT_EQ(tokens[2], ExpressionLexicalToken(TokenType::kEQUALITY, \"==\"));\n  ASSERT_EQ(tokens[3], ExpressionLexicalToken(TokenType::kLITERAL, \"abc\"));\n  ASSERT_EQ(tokens[4], ExpressionLexicalToken(TokenType::kRPAREN, \")\"));\n}\n\nTEST(FilterExpressionLexer, MismatchedQuote) {\n  std::vector<ExpressionLexicalToken> tokens;\n  common::Status st = FilterExpressionLexer::lex(\"     'abc )(  ) \", tokens);\n\n  ASSERT_FALSE(st);\n  ASSERT_EQ(st.toString(), \"(22): lexing failed, mismatched quote: \\\"'\\\"\");\n}\n\nTEST(FilterExpressionParser, SimpleEquality) {\n  FilterExpressionParser parser(\"size == '0'\", true);\n  ASSERT_TRUE(parser.getStatus());\n\n  std::unique_ptr<FileMetadataFilter> filter = parser.getFilter();\n  ASSERT_EQ(filter->describe(), \"size == '0'\");\n  ASSERT_TRUE(filter->isValid());\n\n  eos::ns::FileMdProto proto;\n\n  proto.set_size(33);\n  ASSERT_FALSE(filter->check(proto));\n\n  proto.set_size(0);\n  ASSERT_TRUE(filter->check(proto));\n}\n\nTEST(FilterExpressionParser, YodaEquality) {\n  FilterExpressionParser parser(\"'0' == size\", true);\n  ASSERT_TRUE(parser.getStatus());\n\n  std::unique_ptr<FileMetadataFilter> filter = parser.getFilter();\n  ASSERT_EQ(filter->describe(), \"'0' == size\");\n  ASSERT_TRUE(filter->isValid());\n}\n\nTEST(FilterExpressionParser, LiteralEquality) {\n  FilterExpressionParser parser(\"'0' == '1'\", true);\n  ASSERT_TRUE(parser.getStatus());\n\n  std::unique_ptr<FileMetadataFilter> filter = parser.getFilter();\n  ASSERT_EQ(filter->describe(), \"'0' == '1'\");\n  ASSERT_TRUE(filter->isValid());\n}\n\nTEST(FilterExpressionParser, AndExpression) {\n  FilterExpressionParser parser(\"size == '0' && name == 'chickens'\", true);\n  ASSERT_TRUE(parser.getStatus());\n\n  std::unique_ptr<FileMetadataFilter> filter = parser.getFilter();\n  ASSERT_EQ(filter->describe(), \"(size == '0' && name == 'chickens')\");\n  ASSERT_TRUE(filter->isValid());\n}\n\nTEST(FilterExpressionParser, TripleAndExpression) {\n  FilterExpressionParser parser(\"size == '0' && name == 'chickens' && pid == '0'\", true);\n  ASSERT_TRUE(parser.getStatus());\n\n  std::unique_ptr<FileMetadataFilter> filter = parser.getFilter();\n  ASSERT_EQ(filter->describe(), \"(size == '0' && (name == 'chickens' && pid == '0'))\");\n  ASSERT_TRUE(filter->isValid());\n}\n\nTEST(FilterExpressionParser, NotEquals) {\n  FilterExpressionParser parser(\"size != '0'\", true);\n  ASSERT_TRUE(parser.getStatus());\n\n  std::unique_ptr<FileMetadataFilter> filter = parser.getFilter();\n  ASSERT_EQ(filter->describe(), \"size != '0'\");\n  ASSERT_TRUE(filter->isValid());\n\n  eos::ns::FileMdProto proto;\n\n  proto.set_size(33);\n  ASSERT_TRUE(filter->check(proto));\n\n  proto.set_size(0);\n  ASSERT_FALSE(filter->check(proto));\n}\n\nTEST(FilterExpressionParser, EqualityWithParentheses) {\n  FilterExpressionParser parser(\"(size == '0')\", true);\n  ASSERT_TRUE(parser.getStatus());\n\n  std::unique_ptr<FileMetadataFilter> filter = parser.getFilter();\n  ASSERT_EQ(filter->describe(), \"size == '0'\");\n  ASSERT_TRUE(filter->isValid());\n}\n"
  },
  {
    "path": "namespace/ns_quarkdb/tests/MetadataTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: MetadataTests.cc\n// Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#include <gtest/gtest.h>\n#include \"MockFileMDSvc.hh\"\n#include \"MockContainerMDSvc.hh\"\n#include \"Namespace.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/ns_quarkdb/FileMD.hh\"\n#include \"namespace/ns_quarkdb/ContainerMD.hh\"\n#include <iostream>\n\nusing ::testing::_;\nusing ::testing::Return;\n\nEOSNSTESTING_BEGIN\n\n#if 0\n\n//------------------------------------------------------------------------------\n// Test FileMd serialisation, deserialization and checksumming\n//------------------------------------------------------------------------------\nTEST(NsQuarkdb, FileMd)\n{\n  MockFileMDSvc file_svc;\n  EXPECT_CALL(file_svc, notifyListeners(_)).WillRepeatedly(Return());\n  eos::IFileMD::id_t id = 12345;\n  eos::QuarkFileMD file(id, (eos::IFileMDSvc*)&file_svc);\n  file.setName(\"ns_test_file\");\n  eos::IContainerMD::id_t cont_id = 9876;\n  uint64_t size = 4 * 1024 * 1024;\n  eos::IFileMD::ctime_t tnow;\n  clock_gettime(CLOCK_REALTIME, &tnow);\n  file.setCTime(tnow);\n  file.setMTime(tnow);\n  file.setSize(size);\n  file.setContainerId(cont_id);\n  uid_t uid = 123;\n  file.setCUid(uid);\n  file.setCGid(uid);\n  eos::IFileMD::layoutId_t lid = 1243567;\n  file.setLayoutId(lid);\n  std::string file_cksum = \"abcdefgh\";\n  file.setChecksum(file_cksum.data(), file_cksum.size());\n  std::vector<eos::IFileMD::location_t> locations = {2, 23, 3736, 3871, 21, 47, 55, 76};\n\n  for (auto && elem : locations) {\n    file.addLocation(elem);\n  }\n\n  // Unlink all the even locations\n  for (auto && elem : locations) {\n    if (elem % 2 == 0) {\n      file.unlinkLocation(elem);\n    }\n  }\n\n  // Serialize\n  eos::Buffer buffer;\n  file.serialize(buffer);\n  // Deserialize\n  eos::QuarkFileMD rfile(0, (eos::IFileMDSvc*)&file_svc);\n  rfile.deserialize(buffer);\n  std::string orig_rep, new_rep;\n  file.getEnv(orig_rep);\n  rfile.getEnv(new_rep);\n  ASSERT_EQ(orig_rep, new_rep);\n  // Force a checksum corruption and check if it's detected\n  uint32_t cksum = 0;\n  (void) memcpy(&cksum, buffer.getDataPtr(), sizeof(cksum));\n  cksum += 11;\n  (void) memcpy(buffer.getDataPtr(), &cksum, sizeof(cksum));\n  ASSERT_THROW(rfile.deserialize(buffer), eos::MDException);\n}\n\n//------------------------------------------------------------------------------\n// Test ContainerMd serialisation, deserialization and checksumming\n//------------------------------------------------------------------------------\nTEST(NsQuarkdb, ContainerMd)\n{\n  MockFileMDSvc file_svc;\n  MockContainerMDSvc cont_svc;\n  EXPECT_CALL(file_svc, notifyListeners(_)).WillRepeatedly(Return());\n  EXPECT_CALL(cont_svc, notifyListeners(_, _)).WillRepeatedly(Return());\n  eos::IContainerMD::id_t id = 98765;\n  eos::QuarkContainerMD cont(id, (eos::IFileMDSvc*)&file_svc,\n                        (eos::IContainerMDSvc*)&cont_svc);\n  cont.setName(\"ns_test_cont\");\n  eos::IContainerMD::id_t parent_id = 34567;\n  cont.setParentId(parent_id);\n  eos::IContainerMD::ctime_t tnow;\n  clock_gettime(CLOCK_REALTIME, &tnow);\n  cont.setCTime(tnow);\n  cont.setMTime(tnow);\n  cont.setTMTime(tnow);\n  uid_t uid = 123;\n  cont.setCUid(uid);\n  cont.setCGid(uid);\n  int32_t mode = (1025 << 6);\n  cont.setMode(mode);\n  uint64_t tree_size = 3 * 1024 * 1024 * 1024ul + 12345 * 1024ul; // 3,... GB\n  cont.setTreeSize(tree_size);\n  std::map<std::string, std::string> xattrs = {\n    {\"attr_key1\", \"attr_val1\" },\n    {\"attr_key2\", \"attr_val2\" },\n    {\"attr_key3\", \"attr_val3\" },\n    {\"attr_key4\", \"attr_val4\" },\n    {\"attr_key5\", \"attr_val5\" },\n    {\"key\", \"val\" }\n  };\n\n  for (const auto& elem : xattrs) {\n    cont.setAttribute(elem.first, elem.second);\n  }\n\n  // Serialize\n  eos::Buffer buffer;\n  cont.serialize(buffer);\n  // Deserialize\n  eos::QuarkContainerMD rcont(0, (eos::IFileMDSvc*)&file_svc,\n                         (eos::IContainerMDSvc*)&cont_svc);\n  rcont.deserialize(buffer);\n  ASSERT_EQ(cont.getId(), rcont.getId());\n  ASSERT_EQ(cont.getName(), rcont.getName());\n  ASSERT_EQ(cont.getParentId(), rcont.getParentId());\n  ASSERT_EQ(cont.getFlags(), rcont.getFlags());\n  eos::IContainerMD::ctime_t texpected, treceived;\n  cont.getCTime(texpected);\n  rcont.getCTime(treceived);\n  ASSERT_EQ(texpected.tv_sec, treceived.tv_sec);\n  ASSERT_EQ(texpected.tv_nsec, treceived.tv_nsec);\n  cont.getMTime(texpected);\n  rcont.getMTime(treceived);\n  ASSERT_EQ(texpected.tv_sec, treceived.tv_sec);\n  ASSERT_EQ(texpected.tv_nsec, treceived.tv_nsec);\n  cont.getTMTime(texpected);\n  rcont.getTMTime(treceived);\n  ASSERT_EQ(texpected.tv_sec, treceived.tv_sec);\n  ASSERT_EQ(texpected.tv_nsec, treceived.tv_nsec);\n  ASSERT_EQ(cont.getTreeSize(), rcont.getTreeSize());\n  ASSERT_EQ(cont.getCUid(), rcont.getCUid());\n  ASSERT_EQ(cont.getCGid(), rcont.getCGid());\n  ASSERT_EQ(cont.getMode(), rcont.getMode());\n\n  for (const auto& elem : xattrs) {\n    ASSERT_TRUE(rcont.hasAttribute(elem.first));\n    ASSERT_EQ(elem.second, rcont.getAttribute(elem.first));\n  }\n\n  // Force a checksum corruption and check if it's detected\n  uint32_t cksum = 0;\n  (void) memcpy(&cksum, buffer.getDataPtr(), sizeof(cksum));\n  cksum += 11;\n  (void) memcpy(buffer.getDataPtr(), &cksum, sizeof(cksum));\n  ASSERT_THROW(rcont.deserialize(buffer), eos::MDException);\n}\n\n#endif\n\nEOSNSTESTING_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/tests/MockContainerMD.hh",
    "content": "//------------------------------------------------------------------------------\n// File: MockContainerMD.hh\n// Author: Cedric Caffy <ccaffy@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_MOCKCONTAINERMD_HH\n#define EOS_MOCKCONTAINERMD_HH\n\n#include \"namespace/ns_quarkdb/ContainerMD.hh\"\n#include <vector>\n\nEOSNSNAMESPACE_BEGIN\n/**\n * A MockContainerMD class allowing to do some unit tests with it...\n * It was mainly created to allow to unit test the new locking mechanism and the BulkNsObjectLocker\n */\nclass MockContainerMD : public QuarkContainerMD, public std::enable_shared_from_this<MockContainerMD> {\npublic:\n  //Vector to keep track of the order of write locking of the containers\n  static std::vector<eos::IContainerMDPtr> writeLockedContainers;\n  //Vector to keep track of the order of write unlocking of the containers\n  static std::vector<eos::IContainerMDPtr> writeUnlockedContainers;\n  //Vector to keep track of the order of read locking of the containers\n  static std::vector<eos::IContainerMDPtr> readLockedContainers;\n  //Vector to keep track of the order of read unlocking of the containers\n  static std::vector<eos::IContainerMDPtr> readUnlockedContainers;\n\n  static void clearVectors() {\n    writeLockedContainers.clear();\n    writeUnlockedContainers.clear();\n    readLockedContainers.clear();\n    readUnlockedContainers.clear();\n  }\n\n  MockContainerMD(uint64_t id):QuarkContainerMD(),mId(ContainerIdentifier(id)){\n    mId = ContainerIdentifier(id);\n  }\n\n  inline identifier_t getIdentifier() const override {\n    return mId;\n  }\n\n  void registerLock(MDWriteLock & lock) override {\n    QuarkContainerMD::registerLock(lock);\n    writeLockedContainers.push_back(shared_from_this());\n  }\n\n  void registerLock(MDReadLock & lock) override {\n    QuarkContainerMD::registerLock(lock);\n    readLockedContainers.push_back(shared_from_this());\n  }\n\n  void unregisterLock(MDWriteLock & lock) override {\n    QuarkContainerMD::unregisterLock(lock);\n    writeUnlockedContainers.push_back(shared_from_this());\n  }\n\n  void unregisterLock(MDReadLock & lock) override {\n    QuarkContainerMD::unregisterLock(lock);\n    readUnlockedContainers.push_back(shared_from_this());\n  }\n\n  static std::vector<eos::IContainerMDPtr> getWriteLockedContainers() {\n    return writeLockedContainers;\n  }\n\n  static std::vector<eos::IContainerMDPtr> getWriteUnlockedContainers() {\n    return writeUnlockedContainers;\n  }\n\n  static std::vector<eos::IContainerMDPtr> getReadLockedContainers() {\n    return readLockedContainers;\n  }\n\n  static std::vector<eos::IContainerMDPtr> getReadUnlockedContainers() {\n    return readUnlockedContainers;\n  }\n\nprivate:\n  identifier_t mId;\n};\n\nstd::vector<eos::IContainerMDPtr> MockContainerMD::writeLockedContainers = std::vector<eos::IContainerMDPtr>();\nstd::vector<eos::IContainerMDPtr> MockContainerMD::writeUnlockedContainers = std::vector<eos::IContainerMDPtr>();\nstd::vector<eos::IContainerMDPtr> MockContainerMD::readLockedContainers = std::vector<eos::IContainerMDPtr>();\nstd::vector<eos::IContainerMDPtr> MockContainerMD::readUnlockedContainers = std::vector<eos::IContainerMDPtr>();\n\nEOSNSNAMESPACE_END\n\n#endif // EOS_MOCKCONTAINERMD_HH\n"
  },
  {
    "path": "namespace/ns_quarkdb/tests/MockContainerMDSvc.hh",
    "content": "//------------------------------------------------------------------------------\n// File: MockContainerMDSvc.cc\n// Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#pragma once\n#include <gmock/gmock.h>\n#include \"Namespace.hh\"\n#include \"namespace/ns_quarkdb/persistency/ContainerMDSvc.hh\"\n\nEOSNSTESTING_BEGIN\n\n#if 0\n\n//------------------------------------------------------------------------------\n//! Class MockContainerMDSvc\n//------------------------------------------------------------------------------\nclass MockContainerMDSvc: public QuarkContainerMDSvc\n{\npublic:\n  MOCK_METHOD0(initialize, void());\n  MOCK_METHOD1(configure, void(const std::map<std::string, std::string>& config));\n  MOCK_METHOD0(finalize, void());\n  MOCK_METHOD1(getContainerMD,\n               std::shared_ptr<eos::IContainerMD>(eos::IContainerMD::id_t id));\n  MOCK_METHOD0(createContainer, std::shared_ptr<eos::IContainerMD>());\n  MOCK_METHOD1(updateStore, void(eos::IContainerMD* obj));\n  MOCK_METHOD1(removeContainer, void(eos::IContainerMD* obj));\n  MOCK_METHOD0(getNumContainers, uint64_t());\n  MOCK_METHOD1(addChangeListener,\n               void(eos::IContainerMDChangeListener* listener));\n  MOCK_METHOD1(setQuotaStats, void(eos::IQuotaStats* quota_stats));\n  MOCK_METHOD2(notifyListeners, void(eos::IContainerMD*,\n                                     eos::IContainerMDChangeListener::Action));\n  MOCK_METHOD1(getLostFoundContainer,\n               std::shared_ptr<eos::IContainerMD>(const std::string&));\n  MOCK_METHOD2(createInParent,\n               std::shared_ptr<eos::IContainerMD>(const std::string&,\n                   eos::IContainerMD*));\n  MOCK_METHOD1(setFileMDService, void(eos::IFileMDSvc* cont_svc));\n  MOCK_METHOD1(setContainerAccounting, void(eos::IFileMDChangeListener*));\n  MOCK_METHOD0(getFirstFreeId, eos::IContainerMD::id_t());\n};\n\n#endif\n\nEOSNSTESTING_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/tests/MockFileMDSvc.hh",
    "content": "//------------------------------------------------------------------------------\n// File: MockFileMDSvc.cc\n// Author: Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#pragma once\n#include <gmock/gmock.h>\n#include \"Namespace.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n\nEOSNSTESTING_BEGIN\n\n#if 0\n\n//------------------------------------------------------------------------------\n//! Class MockFileMDSvc\n//------------------------------------------------------------------------------\nclass MockFileMDSvc: public IFileMDSvc\n{\npublic:\n  MOCK_METHOD0(initialize, void());\n  MOCK_METHOD1(configure, void(const std::map<std::string, std::string>& config));\n  MOCK_METHOD0(finalize, void());\n  MOCK_METHOD1(getFileMDFut, folly::Future<eos::IFileMDPtr>(eos::IFileMD::id_t id));\n  MOCK_METHOD1(getFileMD, std::shared_ptr<eos::IFileMD>(eos::IFileMD::id_t id));\n  MOCK_METHOD1(hasFileMD, folly::Future<bool>(eos::FileIdentifier));\n  MOCK_METHOD2(getFileMD, std::shared_ptr<eos::IFileMD>(eos::IFileMD::id_t id,\n               uint64_t* clock));\n  MOCK_METHOD1(createFile, std::shared_ptr<eos::IFileMD>(eos::IFileMD::id_t id));\n  MOCK_METHOD1(updateStore, void(eos::IFileMD* obj));\n  MOCK_METHOD1(removeFile, void(eos::IFileMD* obj));\n  MOCK_METHOD0(getNumFiles, uint64_t());\n  MOCK_METHOD1(addChangeListener, void(eos::IFileMDChangeListener* listener));\n  MOCK_METHOD1(notifyListeners, void(eos::IFileMDChangeListener::Event* event));\n  MOCK_METHOD1(setQuotaStats, void(eos::IQuotaStats* quota_stats));\n  MOCK_METHOD1(setContMDService, void(eos::IContainerMDSvc* cont_svc));\n  MOCK_METHOD1(visit, void(eos::IFileVisitor* visitor));\n  MOCK_METHOD0(getFirstFreeId, eos::IFileMD::id_t());\n  MOCK_METHOD0(getCacheStatistics, eos::CacheStatistics());\n};\n\n#endif\n\nEOSNSTESTING_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/tests/Namespace.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Namespace.hh\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#pragma once\n#define USE_EOSNSTESTING using namespace eos::ns::testing;\n#define EOSNSTESTING_BEGIN namespace eos { namespace ns { namespace testing {\n#define EOSNSTESTING_END }}}\n\n"
  },
  {
    "path": "namespace/ns_quarkdb/tests/NextInodeProviderTest.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @breif NextInodeProvider tests\n//------------------------------------------------------------------------------\n#include \"namespace/ns_quarkdb/persistency/NextInodeProvider.hh\"\n#include \"TestUtils.hh\"\n#include \"qclient/structures/QHash.hh\"\n#include \"Namespace.hh\"\n#include <gtest/gtest.h>\n#include <vector>\n\nEOSNSTESTING_BEGIN\n\nclass NextInodeProviderTest : public NsTestsFixture {};\n\nTEST_F(NextInodeProviderTest, BasicSanity)\n{\n  std::unique_ptr<qclient::QClient> qcl = createQClient();\n\n  qclient::QHash myhash;\n  myhash.setKey(\"ns-tests-next-inode-provider\");\n  myhash.setClient(*qcl.get());\n  myhash.hdel(\"counter\");\n  constexpr size_t firstRunLimit = 50000;\n  constexpr size_t secondRunLimit = 100000;\n  {\n    NextInodeProvider inodeProvider;\n    inodeProvider.configure(myhash, \"counter\");\n\n    for (size_t i = 1; i < firstRunLimit; i++) {\n      ASSERT_EQ(inodeProvider.getFirstFreeId(), i);\n      ASSERT_EQ(inodeProvider.reserve(), i);\n    }\n  }\n  {\n    NextInodeProvider inodeProvider;\n    inodeProvider.configure(myhash, \"counter\");\n    size_t continuation = inodeProvider.getFirstFreeId();\n    ASSERT_TRUE(firstRunLimit <= continuation);\n    std::cerr << \"Wasted \" << continuation - firstRunLimit << \" inodes.\" <<\n              std::endl;\n\n    for (size_t i = continuation; i < secondRunLimit; i++) {\n      ASSERT_EQ(inodeProvider.getFirstFreeId(), i);\n      ASSERT_EQ(inodeProvider.reserve(), i);\n    }\n  }\n  qcl->del(\"ns-tests-next-inode-provider\");\n}\n\nTEST_F(NextInodeProviderTest, Blacklisting)\n{\n  std::unique_ptr<qclient::QClient> qcl = createQClient();\n\n  qclient::QHash myhash;\n  myhash.setKey(\"ns-tests-next-inode-provider\");\n  myhash.setClient(*qcl.get());\n  myhash.hdel(\"counter\");\n\n  NextInodeProvider inodeProvider;\n  inodeProvider.configure(myhash, \"counter\");\n\n  ASSERT_EQ(inodeProvider.reserve(), 1);\n  ASSERT_EQ(inodeProvider.reserve(), 2);\n  ASSERT_EQ(inodeProvider.reserve(), 3);\n\n  inodeProvider.blacklistBelow(4);\n  ASSERT_EQ(\"4\", myhash.hget(\"counter\"));\n\n  ASSERT_EQ(inodeProvider.reserve(), 5);\n  ASSERT_EQ(inodeProvider.reserve(), 6);\n  ASSERT_EQ(inodeProvider.reserve(), 7);\n\n  inodeProvider.blacklistBelow(1);\n  inodeProvider.blacklistBelow(6);\n  inodeProvider.blacklistBelow(7);\n\n  for(size_t i = 8; i < 5000; i++) {\n    ASSERT_EQ(inodeProvider.reserve(), i);\n  }\n\n  inodeProvider.blacklistBelow(10000);\n  ASSERT_EQ(\"10000\", myhash.hget(\"counter\"));\n\n  for(size_t i = 10001; i < 10100; i++) {\n    ASSERT_EQ(inodeProvider.reserve(), i);\n  }\n}\n\nTEST(InodeBlock, BasicSanity) {\n  int64_t ino = 0;\n\n  InodeBlock block(1, 0);\n  ASSERT_TRUE(block.empty());\n  ASSERT_FALSE(block.reserve(ino));\n  ASSERT_FALSE(block.getFirstFreeID(ino));\n\n  block = InodeBlock(1, -1);\n  ASSERT_TRUE(block.empty());\n  ASSERT_FALSE(block.reserve(ino));\n  ASSERT_FALSE(block.getFirstFreeID(ino));\n\n  block = InodeBlock(1, 1);\n  ASSERT_FALSE(block.empty());\n  ASSERT_TRUE(block.getFirstFreeID(ino));\n  ASSERT_EQ(ino, 1);\n  ASSERT_TRUE(block.reserve(ino));\n  ASSERT_EQ(ino, 1);\n  ASSERT_TRUE(block.empty());\n\n  block = InodeBlock(9, 3);\n  ASSERT_FALSE(block.empty());\n  for(int64_t i = 9; i < 9+3; i++) {\n    ASSERT_TRUE(block.getFirstFreeID(ino));\n    ASSERT_EQ(ino, i);\n\n    ASSERT_TRUE(block.reserve(ino));\n    ASSERT_EQ(ino, i);\n  }\n\n  ASSERT_TRUE(block.empty());\n}\n\nTEST(InodeBlock, Blacklisting) {\n  int64_t ino = 0;\n\n  InodeBlock block(10, 10);\n  ASSERT_FALSE(block.empty());\n  block.blacklistBelow(9);\n\n  ASSERT_TRUE(block.reserve(ino));\n  ASSERT_EQ(ino, 10);\n\n  block.blacklistBelow(11);\n  ASSERT_TRUE(block.reserve(ino));\n  ASSERT_EQ(ino, 12);\n\n  block.blacklistBelow(11);\n  ASSERT_TRUE(block.reserve(ino));\n  ASSERT_EQ(ino, 13);\n\n  block.blacklistBelow(18);\n  ASSERT_TRUE(block.reserve(ino));\n  ASSERT_EQ(ino, 19);\n\n  ASSERT_FALSE(block.reserve(ino));\n  ASSERT_TRUE(block.empty());\n}\n\nTEST(InodeBlock, BlacklistingAll) {\n  int64_t ino = 0;\n\n  InodeBlock block(10, 10);\n  ASSERT_FALSE(block.empty());\n  block.blacklistBelow(19);\n  ASSERT_TRUE(block.empty());\n\n  block = InodeBlock(10, 10);\n  ASSERT_FALSE(block.empty());\n  block.blacklistBelow(20);\n  ASSERT_TRUE(block.empty());\n\n  block = InodeBlock(10, 10);\n  ASSERT_FALSE(block.empty());\n  block.blacklistBelow(18);\n  ASSERT_TRUE(block.reserve(ino));\n  ASSERT_EQ(ino, 19);\n  ASSERT_TRUE(block.empty());\n}\n\nTEST_F(NextInodeProviderTest, BlacklistingOffByOne)\n{\n  std::unique_ptr<qclient::QClient> qcl = createQClient();\n\n  qclient::QHash myhash;\n  myhash.setKey(\"ns-tests-next-inode-provider\");\n  myhash.setClient(*qcl.get());\n  myhash.hdel(\"counter\");\n\n  std::unique_ptr<NextInodeProvider> inodeProvider;\n  inodeProvider.reset(new NextInodeProvider());\n  inodeProvider->configure(myhash, \"counter\");\n\n  inodeProvider->blacklistBelow(4294967296);\n  ASSERT_EQ(inodeProvider->reserve(), 4294967297);\n\n  inodeProvider.reset(new NextInodeProvider());\n  inodeProvider->configure(myhash, \"counter\");\n\n  inodeProvider->blacklistBelow(4294967296);\n  ASSERT_EQ(inodeProvider->reserve(), 4294967298);\n  ASSERT_EQ(inodeProvider->reserve(), 4294967299);\n  ASSERT_EQ(inodeProvider->reserve(), 4294967300);\n  ASSERT_EQ(inodeProvider->reserve(), 4294967301);\n\n  inodeProvider.reset(new NextInodeProvider());\n  inodeProvider->configure(myhash, \"counter\");\n  inodeProvider->blacklistBelow(4294967296);\n  ASSERT_EQ(inodeProvider->reserve(), 4294967304);\n}\n\nTEST_F(NextInodeProviderTest, MultipleResets)\n{\n  std::unique_ptr<qclient::QClient> qcl = createQClient();\n\n  qclient::QHash myhash;\n  myhash.setKey(\"ns-tests-next-inode-provider\");\n  myhash.setClient(*qcl.get());\n  myhash.hdel(\"counter\");\n\n  std::unique_ptr<NextInodeProvider> inodeProvider;\n  inodeProvider.reset(new NextInodeProvider());\n  inodeProvider->configure(myhash, \"counter\");\n\n  ASSERT_EQ(inodeProvider->reserve(), 1);\n  inodeProvider->blacklistBelow(-10);\n  ASSERT_EQ(inodeProvider->reserve(), 2);\n  inodeProvider->blacklistBelow(2);\n  ASSERT_EQ(inodeProvider->reserve(), 3);\n\n  ASSERT_EQ(\"3\", myhash.hget(\"counter\"));\n  inodeProvider.reset(new NextInodeProvider());\n  inodeProvider->configure(myhash, \"counter\");\n\n  inodeProvider->blacklistBelow(3);\n  ASSERT_EQ(\"3\", myhash.hget(\"counter\"));\n\n  ASSERT_EQ(inodeProvider->reserve(), 4);\n  ASSERT_EQ(inodeProvider->reserve(), 5);\n  ASSERT_EQ(inodeProvider->reserve(), 6);\n  ASSERT_EQ(inodeProvider->reserve(), 7);\n\n  inodeProvider->blacklistBelow(7);\n\n  ASSERT_EQ(\"9\", myhash.hget(\"counter\"));\n  inodeProvider.reset(new NextInodeProvider());\n  inodeProvider->configure(myhash, \"counter\");\n\n  ASSERT_EQ(\"9\", myhash.hget(\"counter\"));\n  inodeProvider->blacklistBelow(9);\n\n  ASSERT_EQ(inodeProvider->reserve(), 10);\n  ASSERT_EQ(\"10\", myhash.hget(\"counter\"));\n}\n\n\n\nEOSNSTESTING_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/tests/NsTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: NsTests.cc\n// Author: Cedric Caffy <cedric.caffy@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#include \"NsTests.hh\"\n#include \"namespace/ns_quarkdb/persistency/FileMDSvc.hh\"\n#include \"namespace/ns_quarkdb/persistency/RequestBuilder.hh\"\n#include \"namespace/ns_quarkdb/views/HierarchicalView.hh\"\n#include \"namespace/ns_quarkdb/accounting/FileSystemView.hh\"\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"namespace/ns_quarkdb/persistency/ContainerMDSvc.hh\"\n#include <sstream>\n#include <fstream>\n#include <qclient/QClient.hh>\n\nEOSNSTESTING_BEGIN\n\nFlushAllOnConstruction::FlushAllOnConstruction(const QdbContactDetails& cd)\n    : contactDetails(cd)\n{\n  qclient::QClient qcl(cd.members, cd.constructOptions());\n  qcl.exec(\"FLUSHALL\").get();\n  qcl.exec(\"SET\", \"QDB-INSTANCE-FOR-EOS-NS-TESTS\", \"YES\");\n}\n\nFlushAllOnConstruction::~FlushAllOnConstruction() { }\n\nNsTests::NsTests()\n{\n  srandom(time(nullptr));\n  // Connection parameters\n  std::string qdb_hostport = getenv(\"EOS_QUARKDB_HOSTPORT\") ?\n                             getenv(\"EOS_QUARKDB_HOSTPORT\") : \"localhost:9999\";\n  std::string qdb_passwd = getenv(\"EOS_QUARKDB_PASSWD\") ?\n                           getenv(\"EOS_QUARKDB_PASSWD\") : \"\";\n  std::string qdb_passwd_file = getenv(\"EOS_QUARKDB_PASSWD_FILE\") ?\n                                getenv(\"EOS_QUARKDB_PASSWD_FILE\") : \"/etc/eos.keytab\";\n\n  if (qdb_passwd.empty() && !qdb_passwd_file.empty()) {\n    // Read the password from the file\n    std::ifstream f(qdb_passwd_file);\n    std::stringstream buff;\n    buff << f.rdbuf();\n    qdb_passwd = buff.str();\n    // Right trim password, remove whitespace\n    qdb_passwd.erase(qdb_passwd.find_last_not_of(\" \\t\\n\\r\\f\\v\") + 1);\n  }\n\n  testconfig = {\n    {\"queue_path\", \"/tmp/eos-ns-tests/\"},\n    {\"qdb_cluster\", qdb_hostport},\n    {\"qdb_flusher_md\", \"tests_md\"},\n    {\"qdb_flusher_quota\", \"tests_quota\"},\n    {\"qdb_password\", qdb_passwd }\n  };\n  guard.reset(new eos::ns::testing::FlushAllOnConstruction(getContactDetails()));\n}\n\nNsTests::~NsTests()\n{\n  shut_down_everything();\n}\n\nQdbContactDetails NsTests::getContactDetails()\n{\n  return QdbContactDetails(getMembers(), testconfig[\"qdb_password\"]);\n}\n\nqclient::Members NsTests::getMembers()\n{\n  return qclient::Members::fromString(testconfig[\"qdb_cluster\"]);\n}\n\nvoid NsTests::setSizeMapper(IQuotaStats::SizeMapper mapper)\n{\n  sizeMapper = mapper;\n}\n\nvoid NsTests::initServices()\n{\n  if (namespaceGroupPtr) {\n    // Already initialized.\n    return;\n  }\n\n  namespaceGroupPtr.reset(new eos::QuarkNamespaceGroup());\n  std::string err;\n\n  if (!namespaceGroupPtr->initialize(&nsMutex, testconfig, err,nullptr)) {\n    std::cerr << \"Test error: could not initialize namespace group! Terminating.\" <<\n              std::endl;\n    std::terminate();\n  }\n\n  namespaceGroupPtr->getFileService()->configure(testconfig);\n  namespaceGroupPtr->getContainerService()->configure(testconfig);\n  namespaceGroupPtr->getContainerAccountingView();\n  namespaceGroupPtr->getSyncTimeAccountingView();\n  namespaceGroupPtr->getFilesystemView()->configure(testconfig);\n  namespaceGroupPtr->getHierarchicalView()->configure(testconfig);\n\n  if (sizeMapper) {\n    namespaceGroupPtr->getQuotaStats()->registerSizeMapper(sizeMapper);\n  }\n\n  namespaceGroupPtr->getHierarchicalView()->initialize();\n}\n\neos::IContainerMDSvc* NsTests::containerSvc()\n{\n  initServices();\n  return namespaceGroupPtr->getContainerService();\n}\n\neos::IFileMDSvc* NsTests::fileSvc()\n{\n  initServices();\n  return namespaceGroupPtr->getFileService();\n}\n\neos::IView* NsTests::view()\n{\n  initServices();\n  return namespaceGroupPtr->getHierarchicalView();\n}\n\neos::IFsView* NsTests::fsview()\n{\n  initServices();\n  return namespaceGroupPtr->getFilesystemView();\n}\n\nqclient::QClient& NsTests::qcl()\n{\n  initServices();\n  return *(namespaceGroupPtr->getQClient());\n}\n\nfolly::Executor* NsTests::executor()\n{\n  return namespaceGroupPtr->getExecutor();\n}\n\neos::MetadataFlusher* NsTests::mdFlusher()\n{\n  initServices();\n  return namespaceGroupPtr->getMetadataFlusher();\n}\n\neos::MetadataFlusher* NsTests::quotaFlusher()\n{\n  initServices();\n  return namespaceGroupPtr->getQuotaFlusher();\n}\n\nvoid NsTests::shut_down_everything()\n{\n  if (namespaceGroupPtr) {\n    namespaceGroupPtr->getHierarchicalView()->finalize();\n    namespaceGroupPtr->getFilesystemView()->finalize();\n  }\n\n  namespaceGroupPtr.reset();\n}\n\nstd::unique_ptr<qclient::QClient> NsTests::createQClient()\n{\n  QdbContactDetails cd = getContactDetails();\n  return std::unique_ptr<qclient::QClient>(\n           new qclient::QClient(cd.members, cd.constructOptions())\n         );\n}\n\nvoid NsTests::populateDummyData1()\n{\n  // Be careful when making changes! Lots of tests depend on this structure,\n  // you should probably create a new dummy dataset.\n  view()->createContainer(\"/eos/d1/d2/d3/d4/d5/d6/d7/d8/\", true);\n  view()->createContainer(\"/eos/d1/d2-1/\", true);\n  view()->createContainer(\"/eos/d1/d2-2/\", true);\n  view()->createContainer(\"/eos/d1/d2-3/\", true);\n  view()->createContainer(\"/eos/d1/d2/d3-1/\", true);\n  view()->createContainer(\"/eos/d1/d2/d3-2/\", true);\n  view()->createContainer(\"/eos/d2/d3-1\", true);\n  view()->createContainer(\"/eos/d2/d3-2\", true);\n  view()->createContainer(\"/eos/d3/\", true);\n  view()->createFile(\"/eos/d1/f1\", true);\n  view()->createFile(\"/eos/d1/f2\", true);\n  view()->createFile(\"/eos/d1/f3\", true);\n  view()->createFile(\"/eos/d1/f4\", true);\n  view()->createFile(\"/eos/d1/f5\", true);\n  view()->createFile(\"/eos/d2/d3-2/my-file\", true);\n  view()->createContainer(\"/eos/d2/d4/1/2/3/4/5/6/7/\", true);\n  view()->createFile(\"/eos/d2/d4/adsf\", true);\n  view()->createFile(\"/eos/d2/asdf1\", true);\n  view()->createFile(\"/eos/d2/asdf2\", true);\n  view()->createFile(\"/eos/d2/asdf3\", true);\n  view()->createFile(\"/eos/d2/b\", true);\n  view()->createFile(\"/eos/d2/zzzzz1\", true);\n  view()->createFile(\"/eos/d2/zzzzz2\", true);\n  view()->createFile(\"/eos/d2/zzzzz3\", true);\n  view()->createFile(\"/eos/d2/zzzzz4\", true);\n  view()->createFile(\"/eos/d2/zzzzz5\", true);\n  view()->createFile(\"/eos/d2/zzzzz6\", true);\n  mdFlusher()->synchronize();\n}\n\nvoid NsTests::cleanNSCache()\n{\n  using namespace eos::constants;\n  std::map<std::string, std::string> map_cfg;\n  map_cfg[sMaxNumCacheFiles] = std::to_string(UINT64_MAX);\n  map_cfg[sMaxSizeCacheFiles] = std::to_string(UINT64_MAX);\n  map_cfg[sMaxNumCacheDirs] = std::to_string(UINT64_MAX);\n  map_cfg[sMaxSizeCacheDirs] = std::to_string(UINT64_MAX);\n  view()->getFileMDSvc()->configure(map_cfg);\n  view()->getContainerMDSvc()->configure(map_cfg);\n}\n\nEOSNSTESTING_END"
  },
  {
    "path": "namespace/ns_quarkdb/tests/NsTests.hh",
    "content": "//------------------------------------------------------------------------------\n// File: NsTests.hh\n// Author: Cedric Caffy <cedric.caffy@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_NSTESTS_HH\n#define EOS_NSTESTS_HH\n\n#include \"Namespace.hh\"\n#include \"qclient/Members.hh\"\n#include \"namespace/ns_quarkdb/NamespaceGroup.hh\"\n#include \"common/RWMutex.hh\"\n#include <memory>\n\nnamespace qclient\n{\nclass QClient;\n}\n\nnamespace eos\n{\nclass IContainerMDSvc;\nclass IFileMDSvc;\nclass IView;\nclass IFsView;\nclass MetadataFlusher;\nclass IFileMD;\n}\n\nEOSNSTESTING_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class FlushAllOnConstruction\n//------------------------------------------------------------------------------\nclass FlushAllOnConstruction\n{\npublic:\n  FlushAllOnConstruction(const QdbContactDetails& cd);\n  ~FlushAllOnConstruction();\n\nprivate:\n  QdbContactDetails contactDetails;\n};\n\n//------------------------------------------------------------------------------\n//! Test fixture providing generic utilities and initialization / destruction\n//! boilerplate code\n//------------------------------------------------------------------------------\n\ntypedef uint64_t (*SizeMapper)(const IFileMD* file);\n\nclass NsTests {\npublic:\n  NsTests();\n  virtual ~NsTests();\n\n  // Lazy initialization.\n  eos::IContainerMDSvc* containerSvc();\n  eos::IFileMDSvc* fileSvc();\n  eos::IView* view();\n  eos::IFsView* fsview();\n\n  void shut_down_everything();\n\n  // explicit transfer of ownership\n  std::unique_ptr<qclient::QClient> createQClient();\n\n  // get a contact details object\n  QdbContactDetails getContactDetails();\n\n  // Return test cluster members\n  qclient::Members getMembers();\n\n  // Return default qclient instance. Use if you need just one qclient, and\n  // you're too lazy to call createQClient.\n  qclient::QClient& qcl();\n\n  // Return the namespaces' executor\n  folly::Executor* executor();\n\n  // Return flushers\n  eos::MetadataFlusher* mdFlusher();\n  eos::MetadataFlusher* quotaFlusher();\n\n  // Register size mapper\n  void setSizeMapper(SizeMapper sizeMapper);\n\n  // Populate namespace with dummy test data.\n  void populateDummyData1();\n\n  void cleanNSCache();\nprotected:\n  eos::common::RWMutex nsMutex;\n  void initServices();\n\n  std::map<std::string, std::string> testconfig;\n  std::unique_ptr<eos::ns::testing::FlushAllOnConstruction> guard;\n\n  std::unique_ptr<eos::QuarkNamespaceGroup> namespaceGroupPtr;\n\n  // Size mapper, if avaliable\n  SizeMapper sizeMapper = nullptr;\n};\n\n\nEOSNSTESTING_END\n\n#endif // EOS_NSTESTS_HH\n"
  },
  {
    "path": "namespace/ns_quarkdb/tests/OtherTests.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Lukasz Janyst <ljanyst@cern.ch>\n// desc:   Other tests\n//------------------------------------------------------------------------------\n\n#include \"namespace/locking/BulkNsObjectLocker.hh\"\n#include \"namespace/ns_quarkdb/ConfigurationParser.hh\"\n#include \"namespace/ns_quarkdb/LRU.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"namespace/ns_quarkdb/tests/MockContainerMD.hh\"\n#include \"namespace/utils/PathProcessor.hh\"\n#include \"namespace/MDLocking.hh\"\n#include <gtest/gtest.h>\n#include <sstream>\n#include <vector>\n\n//------------------------------------------------------------------------------\n// Check the path\n//------------------------------------------------------------------------------\nbool\ncheckPath(const std::vector<std::string>& elements, size_t depth)\n{\n  if (elements.size() < depth) {\n    return false;\n  }\n\n  for (size_t i = 1; i <= depth; ++i) {\n    std::ostringstream o;\n    o << \"test\" << i;\n\n    if (elements[i - 1] != o.str()) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nTEST(PathSplitter, BasicSanity)\n{\n  std::string path1 = \"/test1/test2/test3/test4/\";\n  std::string path2 = \"/test1/test2/test3/test4\";\n  std::string path3 = \"test1/test2/test3/test4/\";\n  std::string path4 = \"test1/test2/test3/test4\";\n  std::vector<std::string> elements;\n  elements = eos::common::SplitPath(path1);\n  ASSERT_TRUE(checkPath(elements, 4));\n  elements.clear();\n  elements = eos::common::SplitPath(path2);\n  ASSERT_TRUE(checkPath(elements, 4));\n  elements.clear();\n  elements = eos::common::SplitPath(path3);\n  ASSERT_TRUE(checkPath(elements, 4));\n  elements.clear();\n  elements = eos::common::SplitPath(path4);\n  ASSERT_TRUE(checkPath(elements, 4));\n  elements.clear();\n  elements = eos::common::SplitPath(\"/\");\n  ASSERT_TRUE(elements.empty());\n  elements.clear();\n  elements = eos::common::SplitPath(\"\");\n  ASSERT_TRUE(elements.empty());\n}\n\nTEST(PathSplitter, DequeTests)\n{\n  std::string path1 = \"/test1/test2/test3/test4/\";\n  std::string path2 = \"/test1/test2/test3/test4\";\n  std::string path3 = \"test1/test2/test3/test4/\";\n  std::string path4 = \"test1/test2/test3/test4\";\n  std::deque<std::string> dq1,dq2,dq3,dq4;\n  std::deque<std::string> expected {\n    \"test1\",\"test2\",\"test3\",\"test4\"\n  };\n  eos::PathProcessor::insertChunksIntoDeque(dq1,\n                                            path1);\n  eos::PathProcessor::insertChunksIntoDeque(dq2,\n                                            path2);\n  eos::PathProcessor::insertChunksIntoDeque(dq3,\n                                            path3);\n  eos::PathProcessor::insertChunksIntoDeque(dq4,\n                                            path4);\n\n  ASSERT_EQ(dq1, expected);\n  ASSERT_EQ(dq2, expected);\n  ASSERT_EQ(dq3, expected);\n  ASSERT_EQ(dq4, expected);\n}\n\nTEST(PathSplitter, DequeTestsNonEmpty)\n{\n  std::string path1 = \"/test1/test2/test3/test4/\";\n  std::string path2 = \"/test1/test2/test3/test4\";\n  std::string path3 = \"test1/test2/test3/test4/\";\n  std::string path4 = \"test1/test2/test3/test4\";\n  std::deque<std::string> dq1,dq2,dq3,dq4;\n  dq1 = dq2 = dq3 = dq4 = {\"foo\",\"bar\"};\n  std::deque<std::string> expected {\n    \"test1\",\"test2\",\"test3\",\"test4\",\"foo\",\"bar\"\n  };\n  eos::PathProcessor::insertChunksIntoDeque(dq1,\n                                            path1);\n  eos::PathProcessor::insertChunksIntoDeque(dq2,\n                                            path2);\n  eos::PathProcessor::insertChunksIntoDeque(dq3,\n                                            path3);\n  eos::PathProcessor::insertChunksIntoDeque(dq4,\n                                            path4);\n\n  ASSERT_EQ(dq1, expected);\n  ASSERT_EQ(dq2, expected);\n  ASSERT_EQ(dq3, expected);\n  ASSERT_EQ(dq4, expected);\n}\n\nTEST(LRU, BasicSanity)\n{\n  struct Entry {\n    explicit Entry(std::uint64_t id) : id_(id) {}\n\n    ~Entry() = default;\n\n    std::uint64_t\n    getId() const\n    {\n      return id_;\n    }\n\n    std::uint64_t id_;\n  };\n  std::uint64_t max_size = 1000;\n  std::uint64_t delta = 55;\n  eos::LRU<std::uint64_t, Entry> cache{max_size};\n\n  // Fill completely the cache\n  for (std::uint64_t id = 0; id < max_size; ++id) {\n    ASSERT_TRUE(cache.put(id, std::make_shared<Entry>(id)));\n  }\n\n  ASSERT_EQ(max_size, cache.size());\n\n  for (std::uint64_t id = 0; id < max_size; ++id) {\n    ASSERT_TRUE(cache.get(id)->getId() == id);\n  }\n\n  // This triggers a purge of the first 100 elements\n  for (auto extra_id = max_size; extra_id < max_size + delta; ++extra_id) {\n    ASSERT_TRUE(cache.put(extra_id, std::make_shared<Entry>(extra_id)));\n  }\n\n  ASSERT_EQ((std::uint64_t)955, cache.size());\n  std::shared_ptr<Entry> elem = cache.get(101);\n  ASSERT_TRUE(elem);\n\n  // Add another max_size elements\n  for (std::uint64_t id = 2 * max_size; id < 3 * max_size; ++id) {\n    ASSERT_TRUE(cache.put(id, std::make_shared<Entry>(id)));\n  }\n\n  // Object 101 should still be in cache as we hold a reference to it\n  ASSERT_TRUE(cache.get(101));\n  // Obect 102 should have been evicted from the cache\n  ASSERT_TRUE(!cache.get(100));\n}\n\nTEST(PathProcessor, AbsPathTest)\n{\n  std::string path = \"/a/b/c/d/\";\n  eos::PathProcessor::absPath(path);\n  EXPECT_EQ(\"/a/b/c/d\", path);\n  path = \"/a/./b/./c/././d\";\n  eos::PathProcessor::absPath(path);\n  EXPECT_EQ(\"/a/b/c/d\", path);\n  path = \"/a/./b/./c/././d/../d/../d/e/../\";\n  eos::PathProcessor::absPath(path);\n  EXPECT_EQ(\"/a/b/c/d\", path);\n  path = \"/\";\n  eos::PathProcessor::absPath(path);\n  EXPECT_EQ(\"/\", path);\n  path = \".././../../.\";\n  eos::PathProcessor::absPath(path);\n  EXPECT_EQ(\"/\", path);\n  path = \"/a/./b//./c/////./././d\";\n  eos::PathProcessor::absPath(path);\n  EXPECT_EQ(\"/a/b/c/d\", path);\n  path = \"/a/b/././././/../../c/d/.././../e/./f/\";\n  eos::PathProcessor::absPath(path);\n  EXPECT_EQ(\"/e/f\", path);\n}\n\nTEST(QdbContactDetails, BasicSanity)\n{\n  std::map<std::string, std::string> configuration;\n  ASSERT_THROW(eos::ConfigurationParser::parse(configuration), eos::MDException);\n  configuration[\"qdb_cluster\"] =\n    \"example1.cern.ch:1234 example2.cern.ch:2345 example3.cern.ch:3456\";\n  eos::QdbContactDetails cd = eos::ConfigurationParser::parse(configuration);\n  ASSERT_EQ(cd.members.toString(),\n            \"example1.cern.ch:1234,example2.cern.ch:2345,example3.cern.ch:3456\");\n  ASSERT_TRUE(cd.password.empty());\n  configuration[\"qdb_password\"] = \"turtles_turtles_etc\";\n  cd = eos::ConfigurationParser::parse(configuration);\n  ASSERT_EQ(cd.members.toString(),\n            \"example1.cern.ch:1234,example2.cern.ch:2345,example3.cern.ch:3456\");\n  ASSERT_EQ(cd.password, \"turtles_turtles_etc\");\n}\n\nTEST(BulkNSObjectLocker, testBulkNSObjectLocker) {\n  auto mockContainerMD1 = std::make_shared<eos::MockContainerMD>(1);\n  auto mockContainerMD2 = std::make_shared<eos::MockContainerMD>(2);\n  auto mockContainerMD3 = std::make_shared<eos::MockContainerMD>(3);\n  eos::MDLocking::BulkContainerReadLock bulkObjectLocker;\n  bulkObjectLocker.add(mockContainerMD1.get());\n  bulkObjectLocker.add(mockContainerMD2.get());\n  bulkObjectLocker.add(mockContainerMD3.get());\n  {\n    auto locks = bulkObjectLocker.lockAll();\n    ASSERT_EQ(3,locks.size());\n  }\n  auto lockedContainers = eos::MockContainerMD::getReadLockedContainers();\n  auto unlockedContainers = eos::MockContainerMD::getReadUnlockedContainers();\n  ASSERT_EQ(3,lockedContainers.size());\n  ASSERT_EQ(3,unlockedContainers.size());\n  for(uint64_t i = 1; i <= 3; i++) {\n    ASSERT_EQ(i,lockedContainers[i-1]->getIdentifier().getUnderlyingUInt64());\n  }\n  for(uint64_t i = 3; i > 0; i--) {\n    ASSERT_EQ(i,lockedContainers[i-1]->getIdentifier().getUnderlyingUInt64());\n  }\n  eos::MockContainerMD::clearVectors();\n}\n\nTEST(NSObjectLocker, testNoDeadlockNSObjectLocker) {\n  auto mockContainerMD1 = std::make_shared<eos::MockContainerMD>(1);\n  {\n    eos::MDLocking::ContainerWriteLock writeLock(mockContainerMD1.get());\n    eos::MDLocking::ContainerReadLock readLock(mockContainerMD1.get());\n    eos::MDLocking::ContainerReadLock readLock2(mockContainerMD1.get());\n    eos::MDLocking::ContainerWriteLock writeLock2(mockContainerMD1.get());\n    ASSERT_EQ(2,eos::MockContainerMD::getReadLockedContainers().size());\n    ASSERT_EQ(2,eos::MockContainerMD::getWriteLockedContainers().size());\n  }\n  ASSERT_EQ(2,eos::MockContainerMD::getReadUnlockedContainers().size());\n  ASSERT_EQ(2,eos::MockContainerMD::getWriteUnlockedContainers().size());\n  eos::MockContainerMD::clearVectors();\n}"
  },
  {
    "path": "namespace/ns_quarkdb/tests/README.md",
    "content": "## Running EOS QuarkDB Namespace tests\n\nIn order to run the `eos-ns-quarkdb-tests` executable,\nyou must have a running QuarkDB instance available.\n\nThe executable will connect to that instance using parameters provided\nin the following environment variables:\n\n```shell\nEOS_QUARKDB_HOSTPORT (defaults to localhost:9999)\nEOS_QUARKDB_PASSWD\n```\n\nYou must make sure that these variables contain the same values declared\nin the QuarkDB config file.\n\n\n### Installing and running QuarkDB\n\nThe QuarkDB library can be installed from:\nhttp://storage-ci.web.cern.ch/storage-ci/quarkdb/tag/el7/x86_64/\n\nSome setup is necessary before running for the first time.\nA complete documentation can be found [here][1].\n\nQuarkDB runs in a similar fashion to other XRootD plugins: `xroot -c config.file`\n\n### The QuarkDB config file\n\nPackaged with EOS, a simple QuarkDB configuration file is provided :\n`xrd.cf.quarkdb`.\n\nEnsure the same configuration values are used here\nas in the environment variables.\n\n## Build, setup and run demo\n\nA simple setup from build to running the tests is provided as guidance.\n\n```shell\n# EOS_QUARKDB_HOSTPORT=localhost:9999\n# EOS_QUARKDB_PASSWD=password_must_be_atleast_32_characters\n\n# Build executable\nmkdir build\ncd build/\ncmake3 ../\nmake eos-ns-quarkdb-tests -j4\n\n# Install, setup and run QuarkDB\nyum install -y quarkdb\nquarkdb-create --path /var/lib/quarkdb/node-1 --clusterID ns-test --nodes $EOS_QUARKDB_HOSTPORT\nchown -R daemon:daemon /var/lib/quarkdb\nxrootd -n quarkdb -c xrd.cf.quarkdb -l /var/log/quarkdb/xrdlog.quarkdb -Rdaemon &\n\n# Run tests\n./namespace/ns_quarkdb/tests/eos-ns-quarkdb-tests\n```\n\n[1]: http://quarkdb.web.cern.ch/quarkdb/docs/master/CONFIGURATION.html"
  },
  {
    "path": "namespace/ns_quarkdb/tests/TestUtils.hh",
    "content": "//------------------------------------------------------------------------------\n// File: TestUtils.hh\n// Author: Georgios Bitzes <georgios.bitzes@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#pragma once\n\n#include <gtest/gtest.h>\n#include \"Namespace.hh\"\n#include \"NsTests.hh\"\n\n#define DBG(message) std::cerr << __FILE__ << \":\" << __LINE__ << \" -- \" << #message << \" = \" << message << std::endl\n\nEOSNSTESTING_BEGIN\n\n//------------------------------------------------------------------------------\n// Verify contents of iterator (unordered)\n//------------------------------------------------------------------------------\ntemplate<typename T, typename Iterator>\nbool verifyContents(Iterator it, std::set<T> contents)\n{\n  while (true) {\n    if (!it->valid() && !contents.empty()) {\n      std::cerr << \"Iterator is no longer valid, but set contains more items!\" <<\n                std::endl;\n      return false;\n    }\n\n    if (!it->valid() && contents.empty()) {\n      // All done, everything looks good.\n      return true;\n    }\n\n    if (contents.count(it->getElement()) != 1u) {\n      std::cerr << \"Found item in iterator which is not in the set!\" << std::endl;\n      return false;\n    }\n\n    contents.erase(it->getElement());\n    it->next();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Verify contents of iterator (unordered)\n//------------------------------------------------------------------------------\ntemplate<typename T, typename Iterator>\nbool verifyContents(Iterator start, Iterator end, std::set<T> contents)\n{\n  for (auto it = start; it != end; it++) {\n    if (contents.count(*it) != 1u) {\n      std::cerr << \"Found item in iterator which is not in the set!\" << std::endl;\n      return false;\n    }\n\n    contents.erase(*it);\n  }\n\n  if (!contents.empty()) {\n    std::cerr << \"Iterator is no longer valid, but set contains more items!\" <<\n              std::endl;\n    return false;\n  }\n\n  // All done, everything looks good.\n  return true;\n}\n\nclass NsTestsFixture : public NsTests, public ::testing::Test\n{\npublic:\n  NsTestsFixture() = default;\n  ~NsTestsFixture() = default;\n};\n\n\n\n\nEOSNSTESTING_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/tests/VariousTests.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Various namespace tests\n//------------------------------------------------------------------------------\n\n#include <memory>\n#include <gtest/gtest.h>\n#include <cstring>\n\n#include \"namespace/interface/ContainerIterators.hh\"\n#include \"namespace/ns_quarkdb/explorer/NamespaceExplorer.hh\"\n#include \"namespace/ns_quarkdb/persistency/ContainerMDSvc.hh\"\n#include \"namespace/ns_quarkdb/persistency/FileMDSvc.hh\"\n#include \"namespace/ns_quarkdb/persistency/MetadataFetcher.hh\"\n#include \"namespace/ns_quarkdb/persistency/RequestBuilder.hh\"\n#include \"namespace/ns_quarkdb/views/HierarchicalView.hh\"\n#include \"namespace/ns_quarkdb/accounting/FileSystemView.hh\"\n#include \"namespace/ns_quarkdb/flusher/MetadataFlusher.hh\"\n#include \"namespace/ns_quarkdb/FileMD.hh\"\n#include \"namespace/ns_quarkdb/ContainerMD.hh\"\n#include \"namespace/ns_quarkdb/utils/FutureVectorIterator.hh\"\n#include \"namespace/ns_quarkdb/inspector/Printing.hh\"\n#include \"namespace/ns_quarkdb/persistency/FileSystemIterator.hh\"\n#include \"namespace/ns_quarkdb/inspector/AttributeExtraction.hh\"\n#include \"namespace/ns_quarkdb/inspector/FileMetadataFilter.hh\"\n#include \"namespace/ns_quarkdb/accounting/QuotaNodeCore.hh\"\n#include \"namespace/utils/Checksum.hh\"\n#include \"namespace/utils/Etag.hh\"\n#include \"namespace/utils/Attributes.hh\"\n#include \"namespace/PermissionHandler.hh\"\n#include \"namespace/Resolver.hh\"\n#include \"TestUtils.hh\"\n#include <folly/futures/Future.h>\n#include \"google/protobuf/util/message_differencer.h\"\n#include <folly/executors/IOThreadPoolExecutor.h>\n\n\nusing namespace eos;\n\nclass VariousTests : public eos::ns::testing::NsTestsFixture {};\nclass NamespaceExplorerF : public eos::ns::testing::NsTestsFixture {};\nclass FileMDFetching : public eos::ns::testing::NsTestsFixture {};\n\nbool validateReply(qclient::redisReplyPtr reply)\n{\n  if (reply->type != REDIS_REPLY_STRING) {\n    return false;\n  }\n\n  if (std::string(reply->str, reply->len) != \"ayy-lmao\") {\n    return false;\n  }\n\n  return true;\n}\n\nTEST_F(VariousTests, FollyWithGloriousContinuations)\n{\n  folly::Future<bool> ok = qcl().follyExec(\"PING\",\n                           \"ayy-lmao\").thenValue(validateReply);\n  ASSERT_TRUE(std::move(ok).get());\n}\n\nTEST_F(VariousTests, FileCacheInvalidation)\n{\n  ASSERT_THROW(view()->getFile(\"/dir/my-file.txt\", true), eos::MDException);\n  view()->createContainer(\"/dir\", true);\n  std::shared_ptr<eos::IFileMD> file1 = view()->createFile(\"/dir/my-file.txt\");\n  ASSERT_EQ(file1->getId(), 1);\n  mdFlusher()->synchronize();\n  std::cout << qclient::describeRedisReply(qcl().exec(\"hdel\", \"2:map_files\",\n            \"my-file.txt\").get()) << std::endl;\n  eos::IFileMDPtr file2 = view()->getFile(\"/dir/my-file.txt\");\n  // Cache not updated, view still thinks path is valid\n  ASSERT_EQ(file1.get(), file2.get());\n  file1.reset();\n  file2.reset();\n  fileSvc()->dropCachedFileMD(FileIdentifier(1));\n  containerSvc()->dropCachedContainerMD(ContainerIdentifier(2));\n  // cache dropped, should no longer be able to lookup file\n  ASSERT_THROW(view()->getFile(\"/dir/my-file.txt\", true), eos::MDException);\n}\n\nTEST_F(VariousTests, CheckLocationInFsView)\n{\n  std::shared_ptr<eos::IContainerMD> root = view()->getContainer(\"/\");\n  ASSERT_EQ(root->getId(), 1);\n  std::shared_ptr<eos::IFileMD> file = view()->createFile(\"/my-file.txt\", true);\n  ASSERT_EQ(file->getId(), 1);\n  ASSERT_EQ(file->getNumLocation(), 0u);\n  file->addLocation(99);\n  file->addLocation(77);\n  file->addLocation(11);\n  file->addLocation(22);\n  file->unlinkLocation(11);\n  file->unlinkLocation(22);\n  std::shared_ptr<eos::IFileMD> file2 = view()->createFile(\"/my-file-2.txt\",\n                                        true);\n  file2->addLocation(22);\n  mdFlusher()->synchronize();\n  ASSERT_TRUE(eos::MetadataFetcher::locationExistsInFsView(qcl(),\n              FileIdentifier(1), 99, false).get());\n  ASSERT_TRUE(eos::MetadataFetcher::locationExistsInFsView(qcl(),\n              FileIdentifier(1), 77, false).get());\n  ASSERT_FALSE(eos::MetadataFetcher::locationExistsInFsView(qcl(),\n               FileIdentifier(1), 11, false).get());\n  ASSERT_FALSE(eos::MetadataFetcher::locationExistsInFsView(qcl(),\n               FileIdentifier(1), 22, false).get());\n  ASSERT_FALSE(eos::MetadataFetcher::locationExistsInFsView(qcl(),\n               FileIdentifier(1), 33, false).get());\n  ASSERT_TRUE(eos::MetadataFetcher::locationExistsInFsView(qcl(),\n              FileIdentifier(1), 11, true).get());\n  ASSERT_TRUE(eos::MetadataFetcher::locationExistsInFsView(qcl(),\n              FileIdentifier(1), 22, true).get());\n  ASSERT_FALSE(eos::MetadataFetcher::locationExistsInFsView(qcl(),\n               FileIdentifier(1), 99, true).get());\n  ASSERT_FALSE(eos::MetadataFetcher::locationExistsInFsView(qcl(),\n               FileIdentifier(1), 77, true).get());\n  ASSERT_FALSE(eos::MetadataFetcher::locationExistsInFsView(qcl(),\n               FileIdentifier(1), 33, true).get());\n  // Try to confuse the iterator object\n  qcl().exec(\"SET\", \"fsview:22:pickles\", \"123\").get();\n  FileSystemIterator fsIter(qcl());\n  ASSERT_TRUE(fsIter.valid());\n  ASSERT_EQ(fsIter.getFileSystemID(), 11);\n  ASSERT_TRUE(fsIter.isUnlinked());\n  ASSERT_EQ(fsIter.getRedisKey(), \"fsview:11:unlinked\");\n  fsIter.next();\n  ASSERT_TRUE(fsIter.valid());\n  ASSERT_EQ(fsIter.getFileSystemID(), 22);\n  ASSERT_FALSE(fsIter.isUnlinked());\n  ASSERT_EQ(fsIter.getRedisKey(), \"fsview:22:files\");\n  fsIter.next();\n  ASSERT_TRUE(fsIter.valid());\n  ASSERT_EQ(fsIter.getFileSystemID(), 22);\n  ASSERT_TRUE(fsIter.isUnlinked());\n  ASSERT_EQ(fsIter.getRedisKey(), \"fsview:22:unlinked\");\n  fsIter.next();\n  ASSERT_TRUE(fsIter.valid());\n  ASSERT_EQ(fsIter.getFileSystemID(), 77);\n  ASSERT_FALSE(fsIter.isUnlinked());\n  ASSERT_EQ(fsIter.getRedisKey(), \"fsview:77:files\");\n  fsIter.next();\n  ASSERT_TRUE(fsIter.valid());\n  ASSERT_EQ(fsIter.getFileSystemID(), 99);\n  ASSERT_FALSE(fsIter.isUnlinked());\n  ASSERT_EQ(fsIter.getRedisKey(), \"fsview:99:files\");\n  fsIter.next();\n  ASSERT_FALSE(fsIter.valid());\n}\n\nTEST_F(VariousTests, ReconstructContainerPath)\n{\n  std::shared_ptr<eos::IContainerMD> cont =\n    view()->createContainer(\"/eos/a/b/c/d/e\", true);\n  std::shared_ptr<eos::IFileMD> file =\n    view()->createFile(\"/eos/a/b/c/d/e/my-file\");\n  ASSERT_EQ(cont->getId(), 7);\n  ASSERT_EQ(file->getId(), 1);\n  mdFlusher()->synchronize();\n  ASSERT_EQ(\"/\",  eos::MetadataFetcher::resolveFullPath(qcl(),\n            ContainerIdentifier(1)).get());\n  ASSERT_EQ(\"/eos/\",  eos::MetadataFetcher::resolveFullPath(qcl(),\n            ContainerIdentifier(2)).get());\n  ASSERT_EQ(\"/eos/a/\",  eos::MetadataFetcher::resolveFullPath(qcl(),\n            ContainerIdentifier(3)).get());\n  ASSERT_EQ(\"/eos/a/b/\",  eos::MetadataFetcher::resolveFullPath(qcl(),\n            ContainerIdentifier(4)).get());\n  ASSERT_EQ(\"/eos/a/b/c/\",  eos::MetadataFetcher::resolveFullPath(qcl(),\n            ContainerIdentifier(5)).get());\n  ASSERT_EQ(\"/eos/a/b/c/d/\",  eos::MetadataFetcher::resolveFullPath(qcl(),\n            ContainerIdentifier(6)).get());\n  ASSERT_EQ(\"/eos/a/b/c/d/e/\",  eos::MetadataFetcher::resolveFullPath(qcl(),\n            ContainerIdentifier(7)).get());\n  ASSERT_THROW(eos::MetadataFetcher::resolveFullPath(qcl(),\n               ContainerIdentifier(8)).get(), eos::MDException) ;\n  ASSERT_EQ(eos::MetadataFetcher::resolvePathToID(qcl(), \"/\").get(),\n            ContainerIdentifier(1));\n  ASSERT_EQ(eos::MetadataFetcher::resolvePathToID(qcl(), \"/eos\").get(),\n            ContainerIdentifier(2));\n  ASSERT_EQ(eos::MetadataFetcher::resolvePathToID(qcl(), \"/eos/a\").get(),\n            ContainerIdentifier(3));\n  ASSERT_EQ(eos::MetadataFetcher::resolvePathToID(qcl(), \"/eos/a/b\").get(),\n            ContainerIdentifier(4));\n  ASSERT_EQ(eos::MetadataFetcher::resolvePathToID(qcl(), \"/eos/a/b/c\").get(),\n            ContainerIdentifier(5));\n  ASSERT_EQ(eos::MetadataFetcher::resolvePathToID(qcl(), \"/eos/a/b/c/d\").get(),\n            ContainerIdentifier(6));\n  ASSERT_EQ(eos::MetadataFetcher::resolvePathToID(qcl(), \"/eos/a/b/c/d/e\").get(),\n            ContainerIdentifier(7));\n  ASSERT_EQ(eos::MetadataFetcher::resolvePathToID(qcl(),\n            \"/eos/a/b/c/d/e/my-file\").get(), FileIdentifier(1));\n  ASSERT_THROW(eos::MetadataFetcher::resolvePathToID(qcl(), \"/aaaaaaa\").get(),\n               eos::MDException);\n  ASSERT_THROW(eos::MetadataFetcher::resolvePathToID(qcl(), \"/eos/aaaaaaa\").get(),\n               eos::MDException);\n}\n\nTEST_F(VariousTests, BasicSanity)\n{\n  std::shared_ptr<eos::IContainerMD> root = view()->getContainer(\"/\");\n  ASSERT_EQ(root->getId(), 1);\n  ASSERT_EQ(view()->getUri(root.get()), \"/\");\n  ASSERT_EQ(view()->getUri(1), \"/\");\n  std::shared_ptr<eos::IContainerMD> cont1 = view()->createContainer(\"/eos/\",\n      true);\n  ASSERT_EQ(cont1->getId(), 2);\n  ASSERT_THROW(view()->createFile(\"/eos/\", true), eos::MDException);\n  ASSERT_EQ(view()->getUri(cont1.get()), \"/eos/\");\n  ASSERT_EQ(view()->getUri(cont1->getId()), \"/eos/\");\n  ASSERT_EQ(view()->getUri(cont1->getParentId()), \"/\");\n  ASSERT_EQ(view()->getUriFut(cont1->getIdentifier()).get(), \"/eos/\");\n  std::shared_ptr<eos::IFileMD> file1 = view()->createFile(\"/eos/my-file.txt\",\n                                        true);\n  ASSERT_EQ(file1->getId(), 1);\n  ASSERT_EQ(file1->getNumLocation(), 0u);\n  file1->addLocation(1);\n  file1->addLocation(7);\n  file1->setCUid(333);\n  file1->setCGid(999);\n  file1->setSize(555);\n  file1->setFlags((S_IRWXU | S_IRWXG | S_IRWXO));\n  char buff[32];\n  buff[0] = 0x12;\n  buff[1] = 0x23;\n  buff[2] = 0x55;\n  buff[3] = 0x99;\n  buff[4] = 0xAA;\n  buff[5] = 0xDD;\n  buff[6] = 0x00;\n  buff[7] = 0x55;\n  file1->setChecksum(buff, 8);\n  std::string out;\n  ASSERT_FALSE(eos::appendChecksumOnStringAsHex(file1.get(), out));\n  unsigned long layout = eos::common::LayoutId::GetId(\n                           eos::common::LayoutId::kReplica,\n                           eos::common::LayoutId::kMD5,\n                           2,\n                           eos::common::LayoutId::k4k);\n  file1->setLayoutId(layout);\n  ASSERT_EQ(file1->getNumLocation(), 2u);\n  ASSERT_EQ(view()->getUri(file1.get()), \"/eos/my-file.txt\");\n  ASSERT_EQ(view()->getUriFut(file1->getIdentifier()).get(), \"/eos/my-file.txt\");\n  struct timespec ctime;\n  ctime.tv_sec = 1999;\n  ctime.tv_nsec = 8888;\n  file1->setCTime(ctime);\n  struct timespec mtime;\n  mtime.tv_sec = 2000;\n  mtime.tv_nsec = 999;\n  file1->setMTime(mtime);\n  struct timespec atime;\n  mtime.tv_sec = 2000;\n  mtime.tv_nsec = 999;\n  file1->setATime(atime);\n  ASSERT_EQ(eos::Printing::printMultiline(static_cast<eos::QuarkFileMD*>\n                                          (file1.get())->getProto()),\n            SSTR(\"ID: 1\\n\"\n                 \"Name: my-file.txt\\n\"\n                 \"Link name: \\n\"\n                 \"Container ID: 2\\n\"\n                 \"uid: 333, gid: 999\\n\"\n                 \"Size: 555\\n\"\n                 \"Modify: \" << Printing::timespecToFileinfo(mtime) << \"\\n\"\n                 \"Change: \" << Printing::timespecToFileinfo(ctime) << \"\\n\"\n                 \"Access: \" << Printing::timespecToFileinfo(atime) << \"\\n\"\n                 \"Flags: 0777\\n\"\n                 \"Checksum type: md5, checksum bytes: 12235599aadd00550000000000000000\\n\"\n                 \"Expected number of replicas / stripes: 2\\n\"\n                 \"Etag: \\\"12235599aadd00550000000000000000\\\"\\n\"\n                 \"Locations: [1, 7]\\n\"\n                 \"Unlinked locations: []\\n\"\n                 \"Extended attributes (0):\\n\")\n           );\n  containerSvc()->updateStore(root.get());\n  containerSvc()->updateStore(cont1.get());\n  fileSvc()->updateStore(file1.get());\n  shut_down_everything();\n  file1 = view()->getFile(\"/eos/my-file.txt\");\n  ASSERT_EQ(view()->getUri(file1.get()), \"/eos/my-file.txt\");\n  ASSERT_EQ(view()->getUriFut(file1->getIdentifier()).get(), \"/eos/my-file.txt\");\n  ASSERT_EQ(file1->getId(), 1);\n  ASSERT_EQ(file1->getNumLocation(), 2u);\n  ASSERT_EQ(file1->getLocation(0), 1);\n  ASSERT_EQ(file1->getLocation(1), 7);\n  root = view()->getContainer(\"/\");\n  ASSERT_EQ(root->getId(), 1);\n  FileOrContainerMD item = view()->getItem(\"/\").get();\n  ASSERT_TRUE(item.container);\n  ASSERT_FALSE(item.file);\n  ASSERT_EQ(item.container->getId(), 1);\n  item = view()->getItem(\"/eos/my-file.txt\").get();\n  ASSERT_TRUE(item.file);\n  ASSERT_FALSE(item.container);\n  ASSERT_EQ(item.file->getId(), 1);\n  // Ensure fsview for location 1 contains file1\n  std::shared_ptr<eos::ICollectionIterator<eos::IFileMD::id_t>> it =\n        fsview()->getFileList(1);\n  ASSERT_TRUE(it->valid());\n  ASSERT_EQ(it->getElement(), file1->getId());\n  it->next();\n  ASSERT_FALSE(it->valid());\n  // Create some subdirectories\n  std::shared_ptr<eos::IContainerMD> subdir1 =\n    view()->createContainer(\"/eos/subdir1\", true);\n  std::shared_ptr<eos::IContainerMD> subdir2 =\n    view()->createContainer(\"/eos/subdir2\", true);\n  std::shared_ptr<eos::IContainerMD> subdir3 =\n    view()->createContainer(\"/eos/subdir3\", true);\n  ASSERT_LT(subdir1->getId(), subdir2->getId());\n  ASSERT_LT(subdir2->getId(), subdir3->getId());\n  mdFlusher()->synchronize();\n  ASSERT_EQ(ContainerIdentifier(subdir1->getId()),\n            eos::MetadataFetcher::getContainerIDFromName(qcl(), ContainerIdentifier(2),\n                \"subdir1\").get());\n  ASSERT_EQ(ContainerIdentifier(subdir2->getId()),\n            eos::MetadataFetcher::getContainerIDFromName(qcl(), ContainerIdentifier(2),\n                \"subdir2\").get());\n  ASSERT_EQ(ContainerIdentifier(subdir3->getId()),\n            eos::MetadataFetcher::getContainerIDFromName(qcl(), ContainerIdentifier(2),\n                \"subdir3\").get());\n  ASSERT_EQ(subdir1->getId(), eos::MetadataFetcher::getContainerFromName(qcl(),\n            ContainerIdentifier(2), \"subdir1\").get().id());\n  ASSERT_EQ(subdir2->getId(), eos::MetadataFetcher::getContainerFromName(qcl(),\n            ContainerIdentifier(2), \"subdir2\").get().id());\n  ASSERT_EQ(subdir3->getId(), eos::MetadataFetcher::getContainerFromName(qcl(),\n            ContainerIdentifier(2), \"subdir3\").get().id());\n  IContainerMD::ContainerMap containerMap = eos::MetadataFetcher::getContainerMap(\n        qcl(), ContainerIdentifier(subdir1->getId())).get();\n  IContainerMD::FileMap fileMap = eos::MetadataFetcher::getContainerMap(qcl(),\n                                  ContainerIdentifier(subdir1->getId())).get();\n  ASSERT_TRUE(containerMap.empty());\n  ASSERT_TRUE(fileMap.empty());\n  ASSERT_THROW(view()->getFile(\"/\"), eos::MDException);\n}\n\nTEST_F(VariousTests, FileMDGetEnv)\n{\n  std::shared_ptr<eos::IContainerMD> root = view()->getContainer(\"/\");\n  ASSERT_EQ(root->getId(), 1);\n  IFileMDPtr file1 = view()->createFile(\"/file1\", true);\n  struct timespec mtime;\n  mtime.tv_sec = 123;\n  mtime.tv_nsec = 345;\n  file1->setMTime(mtime);\n  file1->setCUid(999);\n  file1->setSize(1337);\n  std::string output;\n  file1->getEnv(output);\n}\n\nTEST_F(VariousTests, MkdirOnBrokenSymlink)\n{\n  std::shared_ptr<eos::IContainerMD> root = view()->getContainer(\"/\");\n  ASSERT_EQ(root->getId(), 1);\n  IFileMDPtr file1 = view()->createFile(\"/file1\", true);\n  file1->setLink(\"/not-existing\");\n  fileSvc()->updateStore(file1.get());\n  containerSvc()->updateStore(root.get());\n  ASSERT_THROW(view()->createContainer(\"/file1\", true), eos::MDException);\n}\n\nTEST_F(VariousTests, SymlinkExtravaganza)\n{\n  std::shared_ptr<eos::IContainerMD> root = view()->getContainer(\"/\");\n  ASSERT_EQ(root->getId(), 1);\n  // Basic symlink sanity checks.\n  IFileMDPtr file1 = view()->createFile(\"/file1\", true);\n  file1->setLink(\"/cont1\");\n  IContainerMDPtr cont1 = view()->createContainer(\"/cont1\", true);\n  IFileMDPtr awesomeFile = view()->createFile(\"/cont1/awesome-file\", true);\n  ASSERT_EQ(view()->getUri(cont1.get()), \"/cont1/\");\n  ASSERT_EQ(view()->getUri(cont1->getId()), \"/cont1/\");\n  ASSERT_EQ(view()->getUriFut(cont1->getIdentifier()).get(), \"/cont1/\");\n  fileSvc()->updateStore(file1.get());\n  fileSvc()->updateStore(awesomeFile.get());\n  containerSvc()->updateStore(cont1.get());\n  IContainerMDPtr cont2 = view()->getContainer(\"/file1\", true);\n  ASSERT_TRUE(cont2.get() != nullptr);\n  ASSERT_EQ(cont1.get(), cont2.get());\n  ASSERT_THROW(view()->getContainer(\"/file1\", false), MDException);\n  ASSERT_EQ(view()->getUri(cont2.get()), \"/cont1/\");\n  ASSERT_EQ(view()->getUri(cont2->getId()), \"/cont1/\");\n  ASSERT_EQ(view()->getUriFut(cont2->getIdentifier()).get(), \"/cont1/\");\n  IFileMDPtr file2 = view()->createFile(\"/file2\", true);\n  file2->setLink(\"/file1\");\n  fileSvc()->updateStore(file2.get());\n  // NOTE: The following does currently not work on citrine + old NS.\n  IContainerMDPtr cont3 = view()->getContainer(\"/file2\", true);\n  ASSERT_TRUE(cont3.get() != nullptr);\n  ASSERT_EQ(cont1.get(), cont3.get());\n  ASSERT_THROW(view()->getFile(\"/file2\", true),\n               MDException); // it actually points to a container\n  // Retrieve awesome-file through the symlink.\n  IFileMDPtr awesomeFile1 = view()->getFile(\"/file1/awesome-file\", true);\n  ASSERT_TRUE(awesomeFile1.get() != nullptr);\n  ASSERT_EQ(awesomeFile.get(), awesomeFile1.get());\n  ASSERT_EQ(view()->getUri(awesomeFile.get()), \"/cont1/awesome-file\");\n  ASSERT_EQ(view()->getUriFut(awesomeFile->getIdentifier()).get(),\n            \"/cont1/awesome-file\");\n  ASSERT_EQ(view()->getUri(awesomeFile->getContainerId()), \"/cont1/\");\n  // Retrieve awesome-file through two levels of symlinks.\n  // NOTE: The following does currently not work on citrine + old NS.\n  IFileMDPtr awesomeFile2 = view()->getFile(\"/file2/awesome-file\", true);\n  ASSERT_TRUE(awesomeFile2.get() != nullptr);\n  ASSERT_EQ(awesomeFile.get(), awesomeFile2.get());\n  ASSERT_THROW(view()->getContainer(\"/file2/awesome-file\", true), MDException);\n  // Let's create a symlink loop, composed of four files.\n  IFileMDPtr symlinkLoop1 = view()->createFile(\"/loop1\", true);\n  IFileMDPtr symlinkLoop2 = view()->createFile(\"/loop2\", true);\n  IFileMDPtr symlinkLoop3 = view()->createFile(\"/loop3\", true);\n  IFileMDPtr symlinkLoop4 = view()->createFile(\"/loop4\", true);\n  symlinkLoop1->setLink(\"/loop2\");\n  symlinkLoop2->setLink(\"/loop3\");\n  symlinkLoop3->setLink(\"/loop4\");\n  symlinkLoop4->setLink(\"/loop1\");\n  fileSvc()->updateStore(symlinkLoop1.get());\n  fileSvc()->updateStore(symlinkLoop2.get());\n  fileSvc()->updateStore(symlinkLoop3.get());\n  fileSvc()->updateStore(symlinkLoop4.get());\n  ASSERT_THROW(view()->getContainer(\"/loop1\", true), MDException);\n  ASSERT_THROW(view()->getContainer(\"/loop2\", true), MDException);\n  ASSERT_THROW(view()->getContainer(\"/loop3\", true), MDException);\n  ASSERT_THROW(view()->getContainer(\"/loop4\", true), MDException);\n  ASSERT_THROW(view()->getFile(\"/loop1\", true), MDException);\n  ASSERT_THROW(view()->getFile(\"/loop2\", true), MDException);\n  ASSERT_THROW(view()->getFile(\"/loop3\", true), MDException);\n  ASSERT_THROW(view()->getFile(\"/loop4\", true), MDException);\n  ASSERT_THROW(view()->getFile(\"/\", true), MDException);\n  // But: We should be able to retrieve the loop-files with follow = false.\n  ASSERT_EQ(view()->getFile(\"/loop1\", false), symlinkLoop1);\n  ASSERT_EQ(view()->getFile(\"/loop2\", false), symlinkLoop2);\n  ASSERT_EQ(view()->getFile(\"/loop3\", false), symlinkLoop3);\n  ASSERT_EQ(view()->getFile(\"/loop4\", false), symlinkLoop4);\n  // Try out the following ridiculous situation:\n  //   /folder1/f2   -> /folder2\n  //   /folder2/f3   -> /folder3\n  //   /folder3/f4   -> /folder4\n  //   /folder4/f1   -> /folder1\n  //   /folder1/target-file\n  //\n  // We should be able to access target-file through\n  // /folder1/f2/f3/f4/f1/target-file\n  IContainerMDPtr folder1 = view()->createContainer(\"/folder1\", true);\n  IContainerMDPtr folder2 = view()->createContainer(\"/folder2\", true);\n  IContainerMDPtr folder3 = view()->createContainer(\"/folder3\", true);\n  IContainerMDPtr folder4 = view()->createContainer(\"/folder4\", true);\n  IFileMDPtr f2 = view()->createFile(\"/folder1/f2\", true);\n  f2->setLink(\"/folder2\");\n  IFileMDPtr f3 = view()->createFile(\"/folder2/f3\", true);\n  f3->setLink(\"/folder3\");\n  IFileMDPtr f4 = view()->createFile(\"/folder3/f4\", true);\n  f4->setLink(\"/folder4\");\n  IFileMDPtr f1 = view()->createFile(\"/folder4/f1\", true);\n  f1->setLink(\"/folder1\");\n  IFileMDPtr targetFile1 = view()->createFile(\"/folder1/target-file\", true);\n  fileSvc()->updateStore(f1.get());\n  fileSvc()->updateStore(f2.get());\n  fileSvc()->updateStore(f3.get());\n  fileSvc()->updateStore(f4.get());\n  fileSvc()->updateStore(targetFile1.get());\n  IFileMDPtr targetFile2 = view()->getFile(\"/folder1/f2/f3/f4/f1/target-file\",\n                           true);\n  ASSERT_TRUE(targetFile2.get() != nullptr);\n  ASSERT_EQ(targetFile1.get(), targetFile2.get());\n  ASSERT_EQ(view()->getUri(targetFile2.get()), \"/folder1/target-file\");\n  ASSERT_EQ(view()->getUriFut(targetFile2->getIdentifier()).get(),\n            \"/folder1/target-file\");\n  IFileMDPtr symlinkFile = view()->getFile(\"/folder1/f2/f3/f4/f1\", false);\n  ASSERT_EQ(view()->getUri(symlinkFile.get()), \"/folder4/f1\");\n  ASSERT_TRUE(symlinkFile->isLink());\n  ASSERT_EQ(symlinkFile->getLink(), \"/folder1\");\n  // Use relative symlinks\n  IFileMDPtr ff1 = view()->createFile(\"/ff1\", true);\n  IFileMDPtr ff2 = view()->createFile(\"/ff2\", true);\n  ff2->setLink(\"./ff1\");\n  fileSvc()->updateStore(ff1.get());\n  fileSvc()->updateStore(ff2.get());\n  ASSERT_EQ(view()->getFile(\"/ff2\", true), ff1);\n  ASSERT_EQ(view()->getFile(\"/ff2\", false), ff2);\n  IFileMDPtr ff3 = view()->createFile(\"/folder1/ff3\", true);\n  ff3->setLink(\"../ff1\");\n  fileSvc()->updateStore(ff3.get());\n  ASSERT_EQ(view()->getFile(\"/folder1/ff3\", true), ff1);\n  ASSERT_EQ(view()->getFile(\"/folder1/ff3\", false), ff3);\n  // More relative symlinks\n  containerSvc()->updateStore(view()->createContainer(\"/eos\", true).get());\n  containerSvc()->updateStore(view()->createContainer(\"/eos/dev\", true).get());\n  containerSvc()->updateStore(view()->createContainer(\"/eos/dev/test\",\n                              true).get());\n  containerSvc()->updateStore(\n    view()->createContainer(\"/eos/dev/test/instancetest\", true).get());\n  containerSvc()->updateStore(\n    view()->createContainer(\"/eos/dev/test/instancetest/ref\", true).get());\n  IFileMDPtr touch = view()->createFile(\"/eos/dev/test/instancetest/ref/touch\",\n                                        true);\n  IFileMDPtr symdir = view()->createFile(\"/eos/dev/test/instancetest/symrel2\",\n                                         true);\n  symdir->setLink(\"../../test/instancetest/ref\");\n  fileSvc()->updateStore(touch.get());\n  fileSvc()->updateStore(symdir.get());\n  ASSERT_EQ(view()->getFile(\"/eos/dev/test/instancetest/symrel2/touch\", true),\n            touch);\n  ASSERT_EQ(view()->getRealPath(\"/eos/dev/test/instancetest/symrel2/touch\"),\n            \"/eos/dev/test/instancetest/ref/touch\");\n  ASSERT_EQ(view()->getRealPath(\"/eos/dev/test/instancetest/symrel2\"),\n            \"/eos/dev/test/instancetest/symrel2\");\n}\n\nTEST_F(VariousTests, MoreSymlinks)\n{\n  containerSvc()->updateStore(view()->createContainer(\"/eos/dev/user\",\n                              true).get());\n  IFileMDPtr myFile = view()->createFile(\"/eos/dev/user/my-file\", true);\n  fileSvc()->updateStore(myFile.get());\n  IFileMDPtr link = view()->createFile(\"/eos/dev/user/link\", true);\n  link->setLink(\"my-file\");\n  fileSvc()->updateStore(link.get());\n  ASSERT_EQ(view()->getFile(\"/eos/dev/user/link\", true), myFile);\n  ASSERT_EQ(view()->getFile(\"/eos/dev/user/link\", false), link);\n  containerSvc()->updateStore(view()->createContainer(\"/eos/dev/user/dir1\",\n                              true).get());\n  containerSvc()->updateStore(view()->createContainer(\"/eos/dev/user/dir1/dir2\",\n                              true).get());\n  IFileMDPtr myFile2 = view()->createFile(\"/eos/dev/user/dir1/dir2/my-file-2\",\n                                          true);\n  fileSvc()->updateStore(myFile2.get());\n  link->setLink(\"dir1/dir2/my-file-2\");\n  fileSvc()->updateStore(link.get());\n  ASSERT_EQ(view()->getFile(\"/eos/dev/user/link\", true), myFile2);\n  ASSERT_EQ(view()->getFile(\"/eos/dev/user/link\", false), link);\n}\n\nTEST_F(VariousTests, createFile)\n{\n  containerSvc()->updateStore(view()->createContainer(\"/eos/dev/user\",\n                              true).get());\n  IFileMDPtr myFile = view()->createFile(\"/eos/dev/user/my-file\");\n  fileSvc()->updateStore(myFile.get());\n  ASSERT_THROW(view()->createFile(\"/eos/dev/user/my-file\"), eos::MDException);\n  ASSERT_THROW(view()->createFile(\"/eos/dev/user\"), eos::MDException);\n  ASSERT_THROW(view()->createFile(\"/eos/dev/user/my-file/aaaa\"),\n               eos::MDException);\n}\n\nTEST_F(VariousTests, createContainerMadness)\n{\n  containerSvc()->updateStore(view()->createContainer(\"/eos/dev/../dev/\",\n                              true).get());\n  containerSvc()->updateStore(view()->createContainer(\n                                \"/eos/dev/./my-dir-1/./../my-dir-2/../my-dir-3/./my-dir-4/../my-dir-5\",\n                                true).get()); // MUAHAHAHAH\n  // This is how \"mkdir -p\" on Linux behaves, as well. We want to be compatible.\n  view()->getContainer(\"/eos\");\n  view()->getContainer(\"/eos/dev\");\n  view()->getContainer(\"/eos/dev/my-dir-1\");\n  view()->getContainer(\"/eos/dev/my-dir-2\");\n  view()->getContainer(\"/eos/dev/my-dir-3\");\n  view()->getContainer(\"/eos/dev/my-dir-3/my-dir-4\");\n  view()->getContainer(\"/eos/dev/my-dir-3/my-dir-5\");\n  shut_down_everything();\n  view()->getContainer(\"/eos\");\n  view()->getContainer(\"/eos/dev\");\n  view()->getContainer(\"/eos/dev/my-dir-1\");\n  view()->getContainer(\"/eos/dev/my-dir-2\");\n  view()->getContainer(\"/eos/dev/my-dir-3\");\n  view()->getContainer(\"/eos/dev/my-dir-3/my-dir-4\");\n  view()->getContainer(\"/eos/dev/my-dir-3/my-dir-5\");\n  ASSERT_THROW(view()->createContainer(\"/eos/dev/my-dir-1/aaa/bbb\", false),\n               eos::MDException);\n  IFileMDPtr file1 = view()->createFile(\"/eos/dev/my-dir-1/link\", true);\n  file1->setLink(\"/eos/dev/my-dir-3/my-dir-4\");\n  fileSvc()->updateStore(file1.get());\n  shut_down_everything();\n  ASSERT_THROW(view()->createContainer(\n                 \"/eos/dev/../dev/my-dir-1/./link/../my-dir-4/what-am-i-doing/aaaaaa/../bbbbbbb/../bbbbbbb/chicken\",\n                 false), eos::MDException);\n  containerSvc()->updateStore(view()->createContainer(\n                                \"/eos/dev/../dev/my-dir-1/./link/../my-dir-4/what-am-i-doing/aaaaaa/../bbbbbbb/../bbbbbbb/chicken\",\n                                true).get());\n  view()->getContainer(\"/eos/dev/my-dir-3/my-dir-4/what-am-i-doing\");\n  view()->getContainer(\"/eos/dev/my-dir-3/my-dir-4/what-am-i-doing/aaaaaa\");\n  view()->getContainer(\"/eos/dev/my-dir-3/my-dir-4/what-am-i-doing/bbbbbbb\");\n  auto chicken =\n    view()->getContainer(\"/eos/dev/my-dir-3/my-dir-4/what-am-i-doing/bbbbbbb/chicken\");\n  ASSERT_EQ(view()->getUri(chicken.get()),\n            \"/eos/dev/my-dir-3/my-dir-4/what-am-i-doing/bbbbbbb/chicken/\");\n  ASSERT_EQ(view()->getUri(chicken->getId()),\n            \"/eos/dev/my-dir-3/my-dir-4/what-am-i-doing/bbbbbbb/chicken/\");\n  ASSERT_EQ(view()->getUri(chicken->getParentId()),\n            \"/eos/dev/my-dir-3/my-dir-4/what-am-i-doing/bbbbbbb/\");\n}\n\nTEST_F(VariousTests, ChecksumFormatting)\n{\n  std::shared_ptr<eos::IContainerMD> root = view()->getContainer(\"/\");\n  ASSERT_EQ(root->getId(), 1);\n  std::shared_ptr<eos::IFileMD> file1 = view()->createFile(\"/my-file.txt\", true);\n  ASSERT_EQ(file1->getId(), 1);\n  char buff[32];\n  buff[0] = 0x12;\n  buff[1] = 0x23;\n  buff[2] = 0x55;\n  buff[3] = 0x99;\n  buff[4] = 0xAA;\n  buff[5] = 0xDD;\n  buff[6] = 0x00;\n  buff[7] = 0x55;\n  file1->setChecksum(buff, 8);\n  std::string out;\n  ASSERT_FALSE(eos::appendChecksumOnStringAsHex(file1.get(), out));\n  unsigned long layout = eos::common::LayoutId::GetId(\n                           eos::common::LayoutId::kReplica,\n                           eos::common::LayoutId::kMD5,\n                           2,\n                           eos::common::LayoutId::k4k);\n  file1->setLayoutId(layout);\n  ASSERT_TRUE(eos::appendChecksumOnStringAsHex(file1.get(), out));\n  ASSERT_EQ(out, \"12235599aadd00550000000000000000\");\n  layout = eos::common::LayoutId::GetId(\n             eos::common::LayoutId::kReplica,\n             eos::common::LayoutId::kCRC32,\n             2,\n             eos::common::LayoutId::k4k);\n  file1->setLayoutId(layout);\n  out.clear();\n  ASSERT_TRUE(eos::appendChecksumOnStringAsHex(file1.get(), out));\n  ASSERT_EQ(out, \"12235599\");\n  out.clear();\n  ASSERT_TRUE(eos::appendChecksumOnStringAsHex(file1.get(), out, ' '));\n  ASSERT_EQ(out, \"12 23 55 99\");\n  out.clear();\n  ASSERT_TRUE(eos::appendChecksumOnStringAsHex(file1.get(), out, '_'));\n  ASSERT_EQ(out, \"12_23_55_99\");\n  out.clear();\n  ASSERT_TRUE(eos::appendChecksumOnStringAsHex(file1.get(), out, '_', 20));\n  ASSERT_EQ(out, \"12_23_55_99_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00\");\n  ASSERT_FALSE(eos::appendChecksumOnStringAsHex(nullptr, out));\n}\n\nTEST(HexToByteString, EdgeCases)\n{\n  std::string byteArray;\n  ASSERT_FALSE(eos::hexArrayToByteArray(\"chickens\", byteArray));\n  ASSERT_TRUE(eos::hexArrayToByteArray(\"\", byteArray));\n  ASSERT_EQ(byteArray, \"\");\n  ASSERT_FALSE(eos::hexArrayToByteArray(\"deadbeeg\", byteArray));\n}\n\nTEST(HexToByteString, BasicSanity)\n{\n  std::string byteArray;\n  ASSERT_TRUE(eos::hexArrayToByteArray(\"deadbeef\", byteArray));\n  ASSERT_EQ(byteArray.size(), 4);\n  ASSERT_EQ(byteArray[0], '\\xde');\n  ASSERT_EQ(byteArray[1], '\\xad');\n  ASSERT_EQ(byteArray[2], '\\xbe');\n  ASSERT_EQ(byteArray[3], '\\xef');\n  std::string tmp;\n  ASSERT_TRUE(eos::hexArrayToByteArray(\"DEADBEEF\", tmp));\n  ASSERT_EQ(tmp, byteArray);\n  ASSERT_TRUE(eos::hexArrayToByteArray(\"DeAdbEEf\", tmp));\n  ASSERT_EQ(tmp, byteArray);\n}\n\nnamespace eos\n{\n\nTEST_F(VariousTests, EtagFormatting)\n{\n  std::shared_ptr<eos::IContainerMD> root = view()->getContainer(\"/\");\n  ASSERT_EQ(root->getId(), 1);\n  // Create a test file.\n  std::shared_ptr<eos::IFileMD> file1 = view()->createFile(\"/my-file.txt\", true);\n  ASSERT_EQ(file1->getId(), 1);\n  eos::IFileMD::ctime_t mtime;\n  mtime.tv_sec = 1537360812;\n  mtime.tv_nsec = 0;\n  file1->setCTime(mtime);\n  eos::QuarkFileMD* file1f = reinterpret_cast<QuarkFileMD*>(file1.get());\n  file1f->mFile.set_id(4697755903ull);\n  // File has no checksum, using inode + modification time.\n  std::string outcome;\n  eos::calculateEtag(file1.get(), outcome);\n  ASSERT_EQ(outcome, \"\\\"1261044247998496768:1537360812\\\"\");\n  // Force temporary etag\n  file1->setAttribute(\"sys.tmp.etag\", \"lmao\");\n  eos::calculateEtag(file1.get(), outcome);\n  ASSERT_EQ(outcome, \"lmao\");\n  // Remove temporary etag\n  file1->removeAttribute(\"sys.tmp.etag\");\n  // etag based on inode + mtime\n  char buff[4];\n  buff[0] = 0xa7;\n  buff[1] = 0x25;\n  buff[2] = 0x99;\n  buff[3] = 0x97;\n  file1->setChecksum(buff, 4);\n  file1f->mFile.set_id(4697755939ull);\n  unsigned long layout = eos::common::LayoutId::GetId(\n                           eos::common::LayoutId::kReplica,\n                           eos::common::LayoutId::kAdler,\n                           2,\n                           eos::common::LayoutId::k4k);\n  file1->setLayoutId(layout);\n  eos::calculateEtag(file1.get(), outcome);\n  ASSERT_EQ(outcome, \"\\\"1261044257662173184:a7259997\\\"\");\n  char buff2[32];\n  buff2[0] = 0x65;\n  buff2[1] = 0x01;\n  buff2[2] = 0xe9;\n  buff2[3] = 0xc7;\n  buff2[4] = 0xbf;\n  buff2[5] = 0x20;\n  buff2[6] = 0xb1;\n  buff2[7] = 0xdc;\n  buff2[8] = 0x56;\n  buff2[9] = 0xf0;\n  buff2[10] = 0x15;\n  buff2[11] = 0xe3;\n  buff2[12] = 0x41;\n  buff2[13] = 0xf7;\n  buff2[14] = 0x98;\n  buff2[15] = 0x33;\n  file1->setChecksum(buff2, 16);\n  layout = eos::common::LayoutId::GetId(\n             eos::common::LayoutId::kReplica,\n             eos::common::LayoutId::kMD5,\n             2,\n             eos::common::LayoutId::k4k);\n  file1->setLayoutId(layout);\n  eos::calculateEtag(file1.get(), outcome);\n  ASSERT_EQ(outcome, \"\\\"6501e9c7bf20b1dc56f015e341f79833\\\"\");\n}\n\nTEST_F(VariousTests, EtagFormattingContainer)\n{\n  std::shared_ptr<eos::IContainerMD> root = view()->getContainer(\"/\");\n  ASSERT_EQ(root->getId(), 1);\n  // Create a test directory.\n  std::shared_ptr<eos::IContainerMD> cont1 =\n    view()->createContainer(\"/my-file.txt\", true);\n  ASSERT_EQ(cont1->getId(), 2);\n  eos::IFileMD::ctime_t mtime;\n  mtime.tv_sec = 1534776794;\n  mtime.tv_nsec = 97343404;\n  cont1->setTMTime(mtime);\n  eos::QuarkContainerMD* cont1c = reinterpret_cast<QuarkContainerMD*>\n                                  (cont1.get());\n  cont1c->mCont.set_id(5734137);\n  std::string outcome;\n  cont1->setAttribute(\"sys.tmp.etag\", \"lmao\");\n  eos::calculateEtag(cont1.get(), outcome);\n  ASSERT_EQ(outcome, \"lmao\");\n  cont1->removeAttribute(\"sys.tmp.etag\");\n  eos::calculateEtag(cont1.get(), outcome);\n  ASSERT_EQ(outcome, \"577ef9:1534776794.097\");\n}\n\n}\n\nTEST_F(FileMDFetching, ExistenceTest)\n{\n  std::shared_ptr<eos::IContainerMD> root = view()->getContainer(\"/\");\n  ASSERT_EQ(root->getId(), 1);\n  std::shared_ptr<eos::IFileMD> file1 = view()->createFile(\"/my-file.txt\", true);\n  ASSERT_EQ(file1->getId(), 1);\n  mdFlusher()->synchronize();\n  ASSERT_TRUE(MetadataFetcher::doesFileMdExist(qcl(), FileIdentifier(1)).get());\n  ASSERT_FALSE(MetadataFetcher::doesFileMdExist(qcl(), FileIdentifier(2)).get());\n  ASSERT_TRUE(fileSvc()->hasFileMD(FileIdentifier(1)).get());\n  ASSERT_FALSE(fileSvc()->hasFileMD(FileIdentifier(2)).get());\n  ASSERT_TRUE(MetadataFetcher::doesContainerMdExist(qcl(),\n              ContainerIdentifier(1)).get());\n  ASSERT_FALSE(MetadataFetcher::doesContainerMdExist(qcl(),\n               ContainerIdentifier(2)).get());\n}\n\nTEST_F(FileMDFetching, FilemapToFutureVector)\n{\n  populateDummyData1();\n  eos::IContainerMDPtr cont = view()->getContainer(\"/eos/d1\");\n  ASSERT_EQ(cont->getId(), 3);\n  IContainerMD::FileMap filemap = MetadataFetcher::getFileMap(qcl(),\n                                  ContainerIdentifier(3)).get();\n  std::map<std::string, IFileMD::id_t> sorted;\n  std::map<std::string, IFileMD::id_t> expected = {\n    {\"f1\", 1}, {\"f2\", 2}, {\"f3\", 3}, {\"f4\", 4}, {\"f5\", 5}\n  };\n\n  for (auto it = filemap.begin(); it != filemap.end(); ++it) {\n    sorted[it->first] = it->second;\n  }\n\n  ASSERT_EQ(sorted, expected);\n  std::vector<folly::Future<eos::ns::FileMdProto>> mdvector =\n        MetadataFetcher::getFilesFromFilemap(qcl(), filemap);\n  ASSERT_EQ(mdvector.size(), 5u);\n  std::unique_ptr<folly::Executor> executor(new folly::IOThreadPoolExecutor(4));\n  std::vector<folly::Future<eos::ns::FileMdProto>> mdvector3 =\n        MetadataFetcher::getFileMDsInContainer(qcl(), ContainerIdentifier(3),\n            executor.get()).get();\n  ASSERT_EQ(mdvector3.size(), 5u);\n  eos::ns::FileMdProto f1 = std::move(mdvector[0]).get();\n  eos::ns::FileMdProto f2 = std::move(mdvector[1]).get();\n  eos::ns::FileMdProto f3 = std::move(mdvector[2]).get();\n  eos::ns::FileMdProto f4 = std::move(mdvector[3]).get();\n  eos::ns::FileMdProto f5 = std::move(mdvector[4]).get();\n  ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals(f1,\n              std::move(mdvector3[0]).get()));\n  ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals(f2,\n              std::move(mdvector3[1]).get()));\n  ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals(f3,\n              std::move(mdvector3[2]).get()));\n  ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals(f4,\n              std::move(mdvector3[3]).get()));\n  ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals(f5,\n              std::move(mdvector3[4]).get()));\n  ASSERT_EQ(f1.name(), \"f1\");\n  ASSERT_EQ(f1.id(), 1);\n  ASSERT_EQ(f2.name(), \"f2\");\n  ASSERT_EQ(f2.id(), 2);\n  ASSERT_EQ(f3.name(), \"f3\");\n  ASSERT_EQ(f3.id(), 3);\n  ASSERT_EQ(f4.name(), \"f4\");\n  ASSERT_EQ(f4.id(), 4);\n  ASSERT_EQ(f5.name(), \"f5\");\n  ASSERT_EQ(f5.id(), 5);\n  IContainerMD::FileMap containermap = MetadataFetcher::getContainerMap(qcl(),\n                                       ContainerIdentifier(3)).get();\n  std::map<std::string, IFileMD::id_t> sorted2;\n  std::map<std::string, IFileMD::id_t> expected2 = {\n    {\"d2\", 4}, {\"d2-1\", 11}, {\"d2-2\", 12}, {\"d2-3\", 13}\n  };\n\n  for (auto it = containermap.begin(); it != containermap.end(); ++it) {\n    sorted2[it->first] = it->second;\n  }\n\n  ASSERT_EQ(sorted2, expected2);\n  std::vector<folly::Future<eos::ns::ContainerMdProto>> mdvector2 =\n        MetadataFetcher::getContainersFromContainerMap(qcl(), containermap);\n  ASSERT_EQ(mdvector2.size(), 4u);\n  std::vector<folly::Future<eos::ns::ContainerMdProto>> mdvector5 =\n        MetadataFetcher::getContainerMDsInContainer(qcl(), ContainerIdentifier(3),\n            executor.get()).get();\n  ASSERT_EQ(mdvector5.size(), 4u);\n  eos::ns::ContainerMdProto d0 = std::move(mdvector2[0]).get();\n  eos::ns::ContainerMdProto d1 = std::move(mdvector2[1]).get();\n  eos::ns::ContainerMdProto d2 = std::move(mdvector2[2]).get();\n  eos::ns::ContainerMdProto d3 = std::move(mdvector2[3]).get();\n  ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals(d0,\n              std::move(mdvector5[0]).get()));\n  ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals(d1,\n              std::move(mdvector5[1]).get()));\n  ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals(d2,\n              std::move(mdvector5[2]).get()));\n  ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals(d3,\n              std::move(mdvector5[3]).get()));\n  ASSERT_EQ(d0.name(), \"d2\");\n  ASSERT_EQ(d0.id(), 4);\n  ASSERT_EQ(d1.name(), \"d2-1\");\n  ASSERT_EQ(d1.id(), 11);\n  ASSERT_EQ(d2.name(), \"d2-2\");\n  ASSERT_EQ(d2.id(), 12);\n  ASSERT_EQ(d3.name(), \"d2-3\");\n  ASSERT_EQ(d3.id(), 13);\n}\n\nTEST_F(FileMDFetching, CorruptionTest)\n{\n  std::shared_ptr<eos::IContainerMD> root = view()->getContainer(\"/\");\n  ASSERT_EQ(root->getId(), 1);\n  std::shared_ptr<eos::IFileMD> file1 = view()->createFile(\"/my-file.txt\", true);\n  ASSERT_EQ(file1->getId(), 1);\n  shut_down_everything();\n  qcl().execute(RequestBuilder::writeFileProto(FileIdentifier(1), \"hint\",\n                \"chicken_chicken_chicken_chicken\")).get();\n\n  try {\n    MetadataFetcher::getFileFromId(qcl(), FileIdentifier(1)).get();\n    FAIL();\n  } catch (const MDException& exc) {\n    ASSERT_STREQ(exc.what(),\n                 \"Error while deserializing FileMD #1 protobuf: FileMD object checksum mismatch\");\n  }\n\n  shut_down_everything();\n  qcl().exec(\"DEL\", constants::sFileKey).get();\n  qcl().exec(\"SADD\", constants::sFileKey, \"zzz\").get();\n\n  try {\n    MetadataFetcher::getFileFromId(qcl(), FileIdentifier(1)).get();\n    FAIL();\n  } catch (const MDException& exc) {\n    ASSERT_STREQ(exc.what(),\n                 \"Error while fetching FileMD #1 protobuf from QDB: Received unexpected response, was expecting string: (error) ERR Invalid argument: WRONGTYPE Operation against a key holding the wrong kind of value\");\n  }\n}\n\nTEST_F(NamespaceExplorerF, BasicSanity)\n{\n  populateDummyData1();\n  ExplorationOptions options;\n  options.depthLimit = 999;\n  // Invalid path\n  ASSERT_THROW(eos::NamespaceExplorer(\"/eos/invalid/path\", options, qcl(),\n                                      executor()), eos::MDException);\n  // Find on single file - weird, but possible\n  NamespaceExplorer explorer(\"/eos/d2/d3-2/my-file\", options, qcl(), executor());\n  NamespaceItem item;\n  ASSERT_TRUE(explorer.fetch(item));\n  ASSERT_EQ(item.fullPath, \"/eos/d2/d3-2/my-file\");\n  ASSERT_FALSE(explorer.fetch(item));\n  // Find on directory\n  NamespaceExplorer explorer2(\"/eos/d2\", options, qcl(), executor());\n  ASSERT_TRUE(explorer2.fetch(item));\n  ASSERT_FALSE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/eos/d2/\");\n\n  for (size_t i = 1; i <= 3; i++) {\n    ASSERT_TRUE(explorer2.fetch(item));\n    ASSERT_TRUE(item.isFile);\n    ASSERT_EQ(item.fullPath, SSTR(\"/eos/d2/asdf\" << i));\n  }\n\n  ASSERT_TRUE(explorer2.fetch(item));\n  ASSERT_TRUE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/eos/d2/b\");\n\n  for (size_t i = 1; i <= 6; i++) {\n    ASSERT_TRUE(explorer2.fetch(item));\n    ASSERT_TRUE(item.isFile);\n    ASSERT_EQ(item.fullPath, SSTR(\"/eos/d2/zzzzz\" << i));\n  }\n\n  ASSERT_TRUE(explorer2.fetch(item));\n  ASSERT_FALSE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/eos/d2/d3-1/\");\n  ASSERT_TRUE(explorer2.fetch(item));\n  ASSERT_FALSE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/eos/d2/d3-2/\");\n  ASSERT_TRUE(explorer2.fetch(item));\n  ASSERT_TRUE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/eos/d2/d3-2/my-file\");\n  ASSERT_TRUE(explorer2.fetch(item));\n  ASSERT_FALSE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/eos/d2/d4/\");\n  ASSERT_TRUE(explorer2.fetch(item));\n  ASSERT_TRUE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/eos/d2/d4/adsf\");\n  std::stringstream path;\n  path << \"/eos/d2/d4/\";\n\n  for (size_t i = 1; i <= 7; i++) {\n    path << i << \"/\";\n    ASSERT_TRUE(explorer2.fetch(item));\n    ASSERT_FALSE(item.isFile);\n    ASSERT_EQ(item.fullPath, path.str());\n  }\n\n  ASSERT_FALSE(explorer2.fetch(item));\n  ASSERT_FALSE(explorer2.fetch(item));\n  ASSERT_FALSE(explorer2.fetch(item));\n}\n\nTEST_F(NamespaceExplorerF, NoFiles)\n{\n  populateDummyData1();\n  ExplorationOptions options;\n  options.depthLimit = 999;\n  options.ignoreFiles = true;\n  // Find on directory\n  NamespaceExplorer explorer2(\"/eos/d2\", options, qcl(), executor());\n  NamespaceItem item;\n  ASSERT_TRUE(explorer2.fetch(item));\n  ASSERT_FALSE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/eos/d2/\");\n  ASSERT_TRUE(explorer2.fetch(item));\n  ASSERT_FALSE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/eos/d2/d3-1/\");\n  ASSERT_TRUE(explorer2.fetch(item));\n  ASSERT_FALSE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/eos/d2/d3-2/\");\n  ASSERT_TRUE(explorer2.fetch(item));\n  ASSERT_FALSE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/eos/d2/d4/\");\n  std::stringstream path;\n  path << \"/eos/d2/d4/\";\n\n  for (size_t i = 1; i <= 7; i++) {\n    path << i << \"/\";\n    ASSERT_TRUE(explorer2.fetch(item));\n    ASSERT_FALSE(item.isFile);\n    ASSERT_EQ(item.fullPath, path.str());\n  }\n\n  ASSERT_FALSE(explorer2.fetch(item));\n  ASSERT_FALSE(explorer2.fetch(item));\n  ASSERT_FALSE(explorer2.fetch(item));\n}\n\nTEST_F(NamespaceExplorerF, LinkedAttributes)\n{\n  std::shared_ptr<eos::IContainerMD> root = view()->getContainer(\"/\");\n  ASSERT_EQ(root->getId(), 1);\n  root->setAttribute(\"sys.chickens\", \"no\");\n  root->setAttribute(\"sys.qwerty\", \"asdf\");\n  containerSvc()->updateStore(root.get());\n  std::shared_ptr<eos::IFileMD> file1 = view()->createFile(\"/my-file.txt\", true);\n  ASSERT_EQ(file1->getId(), 1);\n  file1->setAttribute(\"sys.chickens\", \"yes\");\n  file1->setAttribute(\"sys.attr.link\", \"/some-file\");\n  fileSvc()->updateStore(file1.get());\n  mdFlusher()->synchronize();\n  // Find on single file - weird, but possible\n  ExplorationOptions options;\n  options.depthLimit = 999;\n  options.populateLinkedAttributes = true;\n  // attrs asked, but view not provided\n  ASSERT_THROW(eos::NamespaceExplorer(\"/\", options, qcl(), executor()),\n               eos::MDException);\n  options.view = view();\n  eos::NamespaceExplorer explorer(\"/\", options, qcl(), executor());\n  NamespaceItem item;\n  ASSERT_TRUE(explorer.fetch(item));\n  ASSERT_FALSE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/\");\n  ASSERT_TRUE(explorer.fetch(item));\n  ASSERT_TRUE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/my-file.txt\");\n  std::map<std::string, std::string> predictedAttrs {\n    {\"sys.chickens\", \"yes\" },\n    {\"sys.attr.link\", \"/some-file\"}\n  };\n  ASSERT_EQ(item.attrs, predictedAttrs);\n  file1->setAttribute(\"sys.attr.link\", \"/\");\n  fileSvc()->updateStore(file1.get());\n  mdFlusher()->synchronize();\n  eos::NamespaceExplorer explorer2(\"/\", options, qcl(), executor());\n  ASSERT_TRUE(explorer2.fetch(item));\n  ASSERT_FALSE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/\");\n  ASSERT_TRUE(explorer2.fetch(item));\n  ASSERT_TRUE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/my-file.txt\");\n  predictedAttrs = {\n    {\"sys.chickens\", \"yes\" },\n    {\"sys.attr.link\", \"/\"},\n    {\"sys.qwerty\", \"asdf\"},\n  };\n  ASSERT_EQ(item.attrs, predictedAttrs);\n}\n\nclass ContainerFilter : public eos::ExpansionDecider\n{\npublic:\n\n  virtual bool shouldExpandContainer(const eos::ns::ContainerMdProto& proto,\n                                     const eos::IContainerMD::XAttrMap& attrs,\n                                     const std::string& fullPath) override\n  {\n    if (proto.name() == \"d4\") {\n      std::cerr << \"INFO: Filtering out encountered container with name d4.\" <<\n                std::endl;\n      return false;\n    }\n\n    return true;\n  }\n};\n\nTEST_F(NamespaceExplorerF, ExpansionDecider)\n{\n  populateDummyData1();\n  ExplorationOptions options;\n  options.depthLimit = 999;\n  options.expansionDecider.reset(new ContainerFilter());\n  NamespaceExplorer explorer(\"/eos/d2\", options, qcl(), executor());\n  NamespaceItem item;\n  ASSERT_TRUE(explorer.fetch(item));\n  ASSERT_FALSE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/eos/d2/\");\n  ASSERT_FALSE(item.expansionFilteredOut);\n\n  for (size_t i = 1; i <= 3; i++) {\n    ASSERT_TRUE(explorer.fetch(item));\n    ASSERT_TRUE(item.isFile);\n    ASSERT_EQ(item.fullPath, SSTR(\"/eos/d2/asdf\" << i));\n    ASSERT_FALSE(item.expansionFilteredOut);\n  }\n\n  ASSERT_TRUE(explorer.fetch(item));\n  ASSERT_TRUE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/eos/d2/b\");\n  ASSERT_FALSE(item.expansionFilteredOut);\n\n  for (size_t i = 1; i <= 6; i++) {\n    ASSERT_TRUE(explorer.fetch(item));\n    ASSERT_TRUE(item.isFile);\n    ASSERT_EQ(item.fullPath, SSTR(\"/eos/d2/zzzzz\" << i));\n    ASSERT_FALSE(item.expansionFilteredOut);\n  }\n\n  ASSERT_TRUE(explorer.fetch(item));\n  ASSERT_FALSE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/eos/d2/d3-1/\");\n  ASSERT_FALSE(item.expansionFilteredOut);\n  ASSERT_TRUE(explorer.fetch(item));\n  ASSERT_FALSE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/eos/d2/d3-2/\");\n  ASSERT_FALSE(item.expansionFilteredOut);\n  ASSERT_TRUE(explorer.fetch(item));\n  ASSERT_TRUE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/eos/d2/d3-2/my-file\");\n  ASSERT_FALSE(item.expansionFilteredOut);\n  ASSERT_TRUE(explorer.fetch(item));\n  ASSERT_FALSE(item.isFile);\n  ASSERT_EQ(item.fullPath, \"/eos/d2/d4/\");\n  ASSERT_TRUE(item.expansionFilteredOut);\n  ASSERT_FALSE(explorer.fetch(item));\n  ASSERT_FALSE(explorer.fetch(item));\n  ASSERT_FALSE(explorer.fetch(item));\n}\n\nTEST_F(VariousTests, LinkedExtendedAttributes)\n{\n  IContainerMDPtr cont1 = view()->createContainer(\"/eos/dir1\", true);\n  IContainerMDPtr cont2 = view()->createContainer(\"/eos/dir1/dir2\", true);\n  cont1->setAttribute(\"sys.chickens\", \"yes\");\n  cont1->setAttribute(\"user.qwerty\", \"asdf\");\n  cont2->setAttribute(\"sys.chickens\", \"no\");\n  cont2->setAttribute(\"sys.attr.link\", \"/eos/dir4\");\n  eos::IContainerMD::XAttrMap out;\n  eos::listAttributes(view(), cont1.get(), out, false);\n  ASSERT_EQ(out.size(), 2u);\n  ASSERT_EQ(out[\"sys.chickens\"], \"yes\");\n  ASSERT_EQ(out[\"user.qwerty\"], \"asdf\");\n  eos::listAttributes(view(), cont2.get(), out, false);\n  ASSERT_EQ(out.size(), 2u);\n  ASSERT_EQ(out[\"sys.chickens\"], \"no\");\n  ASSERT_EQ(out[\"sys.attr.link\"], \"/eos/dir4 - not found\");\n  cont2->setAttribute(\"sys.attr.link\", \"/eos/dir1\");\n  eos::listAttributes(view(), cont2.get(), out, false);\n  ASSERT_EQ(out.size(), 3u);\n  ASSERT_EQ(out[\"sys.chickens\"], \"no\");\n  ASSERT_EQ(out[\"sys.attr.link\"], \"/eos/dir1\");\n  ASSERT_EQ(out[\"user.qwerty\"], \"asdf\");\n  eos::listAttributes(view(), cont2.get(), out, true);\n  ASSERT_EQ(out.size(), 3u);\n  ASSERT_EQ(out[\"sys.chickens\"], \"no\");\n  ASSERT_EQ(out[\"sys.attr.link\"], \"/eos/dir1\");\n  ASSERT_EQ(out[\"user.qwerty\"], \"asdf\");\n  cont2->removeAttribute(\"sys.chickens\");\n  eos::listAttributes(view(), cont2.get(), out, false);\n  ASSERT_EQ(out.size(), 3u);\n  ASSERT_EQ(out[\"sys.chickens\"], \"yes\");\n  ASSERT_EQ(out[\"sys.attr.link\"], \"/eos/dir1\");\n  ASSERT_EQ(out[\"user.qwerty\"], \"asdf\");\n  eos::listAttributes(view(), cont2.get(), out, true);\n  ASSERT_EQ(out.size(), 3u);\n  ASSERT_EQ(out[\"sys.link.chickens\"], \"yes\");\n  ASSERT_EQ(out[\"sys.attr.link\"], \"/eos/dir1\");\n  ASSERT_EQ(out[\"user.qwerty\"], \"asdf\");\n}\n\nTEST(OctalParsing, BasicSanity)\n{\n  mode_t mode;\n  ASSERT_TRUE(PermissionHandler::parseOctalMask(\"0700\", mode));\n  ASSERT_EQ(mode, 0700);\n  ASSERT_TRUE(PermissionHandler::parseOctalMask(\"700\", mode));\n  ASSERT_EQ(mode, 0700);\n  ASSERT_TRUE(PermissionHandler::parseOctalMask(\"744\", mode));\n  ASSERT_EQ(mode, 0744);\n  ASSERT_TRUE(PermissionHandler::parseOctalMask(\"777\", mode));\n  ASSERT_EQ(mode, 0777);\n  ASSERT_TRUE(PermissionHandler::parseOctalMask(\"000\", mode));\n  ASSERT_EQ(mode, 0000);\n  ASSERT_FALSE(PermissionHandler::parseOctalMask(\"chicken\", mode));\n  ASSERT_FALSE(PermissionHandler::parseOctalMask(\"700turtles\", mode));\n  ASSERT_FALSE(PermissionHandler::parseOctalMask(\"chicken777\", mode));\n  ASSERT_FALSE(PermissionHandler::parseOctalMask(\"999\", mode));\n  ASSERT_FALSE(PermissionHandler::parseOctalMask(\"0789\", mode));\n  ASSERT_FALSE(PermissionHandler::parseOctalMask(\"0709\", mode));\n  ASSERT_FALSE(PermissionHandler::parseOctalMask(\"0x123\", mode));\n}\n\nTEST(SysMask, BasicSanity)\n{\n  std::map<std::string, std::string> xattr;\n  xattr.emplace(\"chicken.chicken\", \"chicken chicken chicken chicken\");\n  ASSERT_EQ(0700, PermissionHandler::filterWithSysMask(xattr, 0700));\n  ASSERT_EQ(0770, PermissionHandler::filterWithSysMask(xattr, 0770));\n  ASSERT_EQ(0774, PermissionHandler::filterWithSysMask(xattr, 0774));\n  xattr.emplace(\"sys.mask\", \"700\");\n  ASSERT_EQ(0700, PermissionHandler::filterWithSysMask(xattr, 0777));\n  ASSERT_EQ(0700, PermissionHandler::filterWithSysMask(xattr, 0744));\n  ASSERT_EQ(0700, PermissionHandler::filterWithSysMask(xattr, 0755));\n  ASSERT_EQ(0400, PermissionHandler::filterWithSysMask(xattr, 0444));\n  xattr[\"sys.mask\"] = \"0700\";\n  ASSERT_EQ(0700, PermissionHandler::filterWithSysMask(xattr, 0777));\n  ASSERT_EQ(0700, PermissionHandler::filterWithSysMask(xattr, 0744));\n  ASSERT_EQ(0700, PermissionHandler::filterWithSysMask(xattr, 0755));\n  ASSERT_EQ(0400, PermissionHandler::filterWithSysMask(xattr, 0444));\n  xattr[\"sys.mask\"] = \"0400\";\n  ASSERT_EQ(0400, PermissionHandler::filterWithSysMask(xattr, 0777));\n  ASSERT_EQ(0400, PermissionHandler::filterWithSysMask(xattr, 0744));\n  ASSERT_EQ(0400, PermissionHandler::filterWithSysMask(xattr, 0755));\n  ASSERT_EQ(0400, PermissionHandler::filterWithSysMask(xattr, 0444));\n  xattr[\"sys.mask\"] = \"744\";\n  ASSERT_EQ(0744, PermissionHandler::filterWithSysMask(xattr, 0744));\n  ASSERT_EQ(0744, PermissionHandler::filterWithSysMask(xattr, 0757));\n  ASSERT_EQ(0404, PermissionHandler::filterWithSysMask(xattr, 0407));\n  xattr[\"sys.mask\"] = \"chicken\";\n  ASSERT_EQ(0700, PermissionHandler::filterWithSysMask(xattr, 0700));\n  ASSERT_EQ(0770, PermissionHandler::filterWithSysMask(xattr, 0770));\n  ASSERT_EQ(0774, PermissionHandler::filterWithSysMask(xattr, 0774));\n}\n\nTEST(QuotaNodeCore, BasicSanity)\n{\n  QuotaNodeCore qn;\n  std::unordered_set<uint64_t> uids;\n  std::unordered_set<uint64_t> gids;\n  ASSERT_EQ(qn.getNumFilesByUser(12), 0u);\n  ASSERT_EQ(qn.getNumFilesByGroup(12), 0u);\n  qn.addFile(12, 13, 1024, 2048);\n  ASSERT_EQ(qn.getNumFilesByUser(12), 1u);\n  ASSERT_EQ(qn.getNumFilesByUser(13), 0u);\n  ASSERT_EQ(qn.getNumFilesByGroup(12), 0u);\n  ASSERT_EQ(qn.getNumFilesByGroup(13), 1u);\n  ASSERT_EQ(qn.getPhysicalSpaceByUser(12), 2048);\n  ASSERT_EQ(qn.getPhysicalSpaceByGroup(12), 0);\n  ASSERT_EQ(qn.getPhysicalSpaceByGroup(13), 2048);\n  uids.emplace(12);\n  gids.emplace(13);\n  ASSERT_EQ(qn.getUids(), uids);\n  ASSERT_EQ(qn.getGids(), gids);\n  qn.addFile(12, 12, 1, 2);\n  ASSERT_EQ(qn.getPhysicalSpaceByUser(12), 2050);\n  ASSERT_EQ(qn.getPhysicalSpaceByGroup(12), 2);\n  ASSERT_EQ(qn.getPhysicalSpaceByGroup(13), 2048);\n  ASSERT_EQ(qn.getNumFilesByUser(12), 2u);\n  ASSERT_EQ(qn.getNumFilesByUser(13), 0u);\n  ASSERT_EQ(qn.getNumFilesByGroup(12), 1u);\n  ASSERT_EQ(qn.getNumFilesByGroup(13), 1u);\n  gids.emplace(12);\n  ASSERT_EQ(qn.getUids(), uids);\n  ASSERT_EQ(qn.getGids(), gids);\n  qn.removeFile(12, 13, 1024, 2048);\n  ASSERT_EQ(qn.getPhysicalSpaceByUser(12), 2);\n  ASSERT_EQ(qn.getPhysicalSpaceByGroup(12), 2);\n  ASSERT_EQ(qn.getPhysicalSpaceByGroup(13), 0);\n  // gids.erase(13);\n  ASSERT_EQ(qn.getUids(), uids);\n  ASSERT_EQ(qn.getGids(), gids);\n  qn.removeFile(12, 12, 1, 2);\n  ASSERT_EQ(qn.getPhysicalSpaceByUser(12), 0);\n  ASSERT_EQ(qn.getPhysicalSpaceByGroup(12), 0);\n  ASSERT_EQ(qn.getPhysicalSpaceByGroup(13), 0);\n  ASSERT_EQ(qn.getNumFilesByUser(12), 0u);\n  ASSERT_EQ(qn.getNumFilesByUser(13), 0u);\n  ASSERT_EQ(qn.getNumFilesByGroup(12), 0u);\n  ASSERT_EQ(qn.getNumFilesByGroup(13), 0u);\n  // uids.clear();\n  // gids.clear();\n  ASSERT_EQ(qn.getUids(), uids);\n  ASSERT_EQ(qn.getGids(), gids);\n}\n\nTEST(Resolver, FidParsing)\n{\n  XrdOucString str = \"fid:123\";\n  ASSERT_EQ(FileIdentifier(123), Resolver::retrieveFileIdentifier(str));\n  str = \"asdef234\";\n  ASSERT_EQ(FileIdentifier(0), Resolver::retrieveFileIdentifier(str));\n  str = \"fxid:0x12f\";\n  ASSERT_EQ(FileIdentifier(303), Resolver::retrieveFileIdentifier(str));\n  str = \"fxid:12f\";\n  ASSERT_EQ(FileIdentifier(303), Resolver::retrieveFileIdentifier(str));\n  str = \"ino:0x3e70000000\"; // fid: 999, old encoding\n  ASSERT_EQ(FileIdentifier(999), Resolver::retrieveFileIdentifier(str));\n  str = \"ino:zzzz\";\n  ASSERT_EQ(FileIdentifier(0), Resolver::retrieveFileIdentifier(str));\n  str = \"ino:123\"; // cid: 123\n  ASSERT_EQ(FileIdentifier(0), Resolver::retrieveFileIdentifier(str));\n  str = \"ino:0x80000000000003e7\"; // fid: 999, new encoding\n  ASSERT_EQ(FileIdentifier(999), Resolver::retrieveFileIdentifier(str));\n  str = \"ino:80000000000003e7\"; // fid: 999, new encoding\n  ASSERT_EQ(FileIdentifier(999), Resolver::retrieveFileIdentifier(str));\n}\n\nTEST(FileOrContainerIdentifier, BasicSanity)\n{\n  FileOrContainerIdentifier empty;\n  ASSERT_TRUE(empty.empty());\n  ASSERT_FALSE(empty.isFile());\n  ASSERT_FALSE(empty.isContainer());\n  ASSERT_EQ(empty.toFileIdentifier(), FileIdentifier(0));\n  ASSERT_EQ(empty.toContainerIdentifier(), ContainerIdentifier(0));\n  FileOrContainerIdentifier file(FileIdentifier(111));\n  ASSERT_FALSE(file.empty());\n  ASSERT_TRUE(file.isFile());\n  ASSERT_FALSE(file.isContainer());\n  ASSERT_EQ(file.toFileIdentifier(), FileIdentifier(111));\n  ASSERT_EQ(file.toContainerIdentifier(), ContainerIdentifier(0));\n  FileOrContainerIdentifier container(ContainerIdentifier(222));\n  ASSERT_FALSE(container.empty());\n  ASSERT_FALSE(container.isFile());\n  ASSERT_TRUE(container.isContainer());\n  ASSERT_EQ(container.toFileIdentifier(), FileIdentifier(0));\n  ASSERT_EQ(container.toContainerIdentifier(), ContainerIdentifier(222));\n}\n\nTEST(FutureVectorIterator, EmptyConstructor)\n{\n  FutureVectorIterator<int> fvi;\n  ASSERT_TRUE(fvi.isReady());\n  ASSERT_TRUE(fvi.isMainFutureReady());\n  int out;\n  ASSERT_FALSE(fvi.fetchNext(out));\n}\n\nTEST(FutureVectorIterator, BasicSanity)\n{\n  folly::Promise<std::vector<folly::Future<int>>> mainPromise;\n  FutureVectorIterator<int> fvi(mainPromise.getFuture());\n  ASSERT_FALSE(fvi.isReady());\n  ASSERT_FALSE(fvi.isMainFutureReady());\n  // Build our future vector\n  std::vector<folly::Future<int>> mainVector;\n  folly::Promise<int> p1;\n  folly::Promise<int> p2;\n  folly::Promise<int> p3;\n  mainVector.emplace_back(p1.getFuture());\n  mainVector.emplace_back(p2.getFuture());\n  mainVector.emplace_back(p3.getFuture());\n  mainPromise.setValue(std::move(mainVector));\n  ASSERT_FALSE(fvi.isReady());\n  ASSERT_TRUE(fvi.isMainFutureReady());\n  ASSERT_EQ(fvi.size(), 3u);\n  p1.setValue(9);\n  ASSERT_TRUE(fvi.isReady());\n  ASSERT_TRUE(fvi.isMainFutureReady());\n  int val;\n  ASSERT_TRUE(fvi.fetchNext(val));\n  ASSERT_EQ(val, 9);\n  ASSERT_FALSE(fvi.isReady());\n  p3.setValue(999);\n  ASSERT_FALSE(fvi.isReady());\n  p2.setValue(8);\n  ASSERT_TRUE(fvi.isReady());\n  ASSERT_TRUE(fvi.fetchNext(val));\n  ASSERT_EQ(val, 8);\n  ASSERT_TRUE(fvi.isReady());\n  ASSERT_TRUE(fvi.fetchNext(val));\n  ASSERT_EQ(val, 999);\n  ASSERT_TRUE(fvi.isReady());\n  ASSERT_FALSE(fvi.fetchNext(val));\n  ASSERT_TRUE(fvi.isReady());\n  ASSERT_FALSE(fvi.fetchNext(val));\n  ASSERT_TRUE(fvi.isReady());\n}\n\nTEST_F(VariousTests, QuotanodeCorruption)\n{\n  IContainerMDPtr cont = view()->createContainer(\"/a/b/c/d/e/f/g\", true);\n  ASSERT_EQ(cont->getId(), 8);\n  ASSERT_EQ(cont->getParentId(), 7);\n  ASSERT_EQ(view()->getQuotaNode(cont.get()), nullptr);\n  cont->setParentId(999);\n  containerSvc()->updateStore(cont.get());\n  ASSERT_EQ(view()->getQuotaNode(cont.get()), nullptr);\n  shut_down_everything();\n  cont = containerSvc()->getContainerMD(8);\n  ASSERT_EQ(cont->getParentId(), 999);\n  ASSERT_EQ(view()->getQuotaNode(cont.get()), nullptr);\n}\n\nTEST_F(VariousTests, UnlinkAllLocations)\n{\n  std::shared_ptr<eos::IFileMD> file1 = view()->createFile(\"/my-file.txt\");\n  ASSERT_EQ(file1->getId(), 1);\n  file1->addLocation(13);\n  file1->unlinkLocation(13);\n  file1->addLocation(13);\n  file1->unlinkAllLocations();\n  ASSERT_EQ(file1->getLocations().size(), 0u);\n  ASSERT_EQ(file1->getUnlinkedLocations().size(), 1u);\n}\n\nTEST_F(VariousTests, CountContents)\n{\n  eos::IContainerMDPtr cont1 = view()->createContainer(\"/dir-1/\");\n  eos::IContainerMDPtr cont2 = view()->createContainer(\"/dir-2/\");\n  ASSERT_EQ(cont1->getId(), 2);\n  ASSERT_EQ(cont2->getId(), 3);\n  eos::IFileMDPtr file1 = view()->createFile(\"/file-1\");\n  eos::IFileMDPtr file2 = view()->createFile(\"/file-2\");\n  eos::IFileMDPtr file3 = view()->createFile(\"/file-3\");\n  eos::IFileMDPtr file4 = view()->createFile(\"/file-4\");\n  ASSERT_EQ(file1->getId(), 1);\n  ASSERT_EQ(file2->getId(), 2);\n  mdFlusher()->synchronize();\n  std::pair<folly::Future<uint64_t>, folly::Future<uint64_t>> counts =\n        eos::MetadataFetcher::countContents(qcl(), ContainerIdentifier(1));\n  ASSERT_EQ(std::move(counts.first).get(), 4u);\n  ASSERT_EQ(std::move(counts.second).get(), 2u);\n  counts = eos::MetadataFetcher::countContents(qcl(), ContainerIdentifier(2));\n  ASSERT_EQ(std::move(counts.first).get(), 0u);\n  ASSERT_EQ(std::move(counts.second).get(), 0u);\n}\n\nTEST_F(VariousTests, basicObjectMDLockingOrderTest) {\n  eos::IContainerMDPtr cont1 = view()->createContainer(\"/dir-1/\");\n  eos::IFileMDPtr file1 = view()->createFile(\"/dir-1/file1.txt\");\n  uint16_t loops = 100;\n\n  std::vector<std::thread> workers;\n\n  workers.emplace_back([cont1,file1,loops](){\n    for(uint16_t i = 0; i < loops; ++i) {\n      eos::MDLocking::ContainerReadLock contRLock(cont1.get());\n      eos::MDLocking::FileReadLock fileRLock(file1.get());\n      cont1->getName();\n      file1->getName();\n    }\n  });\n\n  workers.emplace_back([cont1,file1,loops](){\n    for(uint16_t i = 0; i < loops; ++i) {\n      eos::MDLocking::ContainerReadLock contRLock(cont1.get());\n      eos::MDLocking::FileReadLock fileRLock(file1.get());\n      cont1->getName();\n      file1->getName();\n    }\n  });\n\n  workers.emplace_back([cont1,file1,loops](){\n    for(uint16_t i = 0; i < loops; ++i) {\n      eos::MDLocking::ContainerWriteLock contRLock(cont1.get());\n      eos::MDLocking::FileWriteLock fileRLock(file1.get());\n      cont1->getName();\n      file1->getName();\n    }\n  });\n\n  workers.emplace_back([cont1,file1,loops](){\n    for(uint16_t i = 0; i < loops; ++i) {\n      eos::MDLocking::ContainerWriteLock contRLock(cont1.get());\n      eos::MDLocking::FileWriteLock fileRLock(file1.get());\n      cont1->getName();\n      file1->getName();\n    }\n  });\n\n  /** DEADLOCKS IF UNCOMMENTED\n  workers.emplace_back([cont1,file1,loops](){\n    for(uint16_t i = 0; i < loops; ++i) {\n      eos::MDLocking::FileWriteLock fileRLock(file1);\n      eos::MDLocking::ContainerWriteLock contRLock(cont1);\n      cont1->getName();\n      file1->getName();\n    }\n  });\n  */\n  for(auto & worker: workers) {\n    worker.join();\n  }\n}\n\nTEST_F(VariousTests, ContainerIterator)\n{\n  eos::IContainerMDPtr cont1 = view()->createContainer(\"/dir-1/\");\n  ASSERT_EQ(cont1->getId(), 2);\n  eos::IFileMDPtr file1 = view()->createFile(\"/dir-1/file-1\");\n  eos::IFileMDPtr file2 = view()->createFile(\"/dir-1/file-2\");\n  eos::IFileMDPtr file3 = view()->createFile(\"/dir-1/file-3\");\n  eos::IFileMDPtr file4 = view()->createFile(\"/dir-1/file-4\");\n  eos::IContainerMDPtr subcont1 = view()->createContainer(\"/dir-1/dir-1/\");\n  eos::IContainerMDPtr subcont2 = view()->createContainer(\"/dir-1/dir-2/\");\n  eos::IContainerMDPtr subcont3 = view()->createContainer(\"/dir-1/dir-3/\");\n  eos::IContainerMDPtr subcont4 = view()->createContainer(\"/dir-1/dir-4/\");\n  ASSERT_EQ(cont1->getNumFiles(), 4);\n  ASSERT_EQ(cont1->getNumContainers(), 4);\n  eos::ContainerMapIterator cit(cont1);\n  eos::FileMapIterator fit(cont1);\n  // file iterator test\n  {\n    std::vector<eos::IFileMDPtr> fv;\n\n    for (auto i = 5 ; i <= 1024; ++i) {\n      std::string f = \"/dir-1/file-\" + std::to_string(i);\n      fv.push_back(view()->createFile(f));\n    }\n\n    size_t iterations = 1;\n\n    do {\n      fit.next();\n\n      if (fit.valid()) {\n        iterations++;\n      }\n    } while (fit.valid());\n\n    ASSERT_EQ(iterations, 1024);\n    eos::FileMapIterator fit1(cont1);\n    std::string f = \"/dir-1/\" + fit1.key();\n    view()->unlinkFile(f);\n\n    // delete one more file during iteration, diffrent from the 1st one\n    if (f != \"/dir-1/file-1024\") {\n      std::string f = \"/dir-1/file-1024\";\n      view()->unlinkFile(f);\n    } else {\n      std::string f = \"/dir-1/file-512\";\n      view()->unlinkFile(f);\n    }\n\n    iterations = 1;\n\n    do {\n      fit1.next();\n\n      if (fit1.valid()) {\n        iterations++;\n      }\n    } while (fit1.valid());\n\n    ASSERT_EQ(iterations, 1023);\n  }\n  // container iterator test\n  {\n    std::vector<eos::IContainerMDPtr> cv;\n\n    for (auto i = 5 ; i <= 1024; ++i) {\n      std::string f = \"/dir-1/dir-\" + std::to_string(i);\n      cv.push_back(view()->createContainer(f));\n    }\n\n    size_t iterations = 1;\n\n    do {\n      cit.next();\n\n      if (cit.valid()) {\n        iterations++;\n      }\n    } while (cit.valid());\n\n    ASSERT_EQ(iterations, 1024);\n    eos::ContainerMapIterator cit1(cont1);\n    std::string f = \"/dir-1/\" + cit1.key();\n    view()->removeContainer(f);\n\n    // delete one more file during iteration, diffrent from the 1st one\n    if (f != \"/dir-1/dir-1024\") {\n      std::string f = \"/dir-1/dir-1024\";\n      view()->removeContainer(f);\n    } else {\n      std::string f = \"/dir-1/dir-512\";\n      view()->removeContainer(f);\n    }\n\n    iterations = 1;\n\n    do {\n      cit1.next();\n\n      if (cit1.valid()) {\n        iterations++;\n      }\n    } while (cit1.valid());\n\n    ASSERT_EQ(iterations, 1023);\n  }\n}\n\nTEST_F(VariousTests, FileContainerIteratorConcurrentAccess)\n{\n  eos::IContainerMDPtr cont1 = view()->createContainer(\"/dir-1/\");\n  eos::IFileMDPtr file1 = view()->createFile(\"/dir-1/file-1\");\n  eos::IFileMDPtr file2 = view()->createFile(\"/dir-1/file-2\");\n  eos::IFileMDPtr file3 = view()->createFile(\"/dir-1/file-3\");\n  eos::IFileMDPtr file4 = view()->createFile(\"/dir-1/file-4\");\n  eos::IContainerMDPtr subcont1 = view()->createContainer(\"/dir-1/dir-1/\");\n  eos::IContainerMDPtr subcont2 = view()->createContainer(\"/dir-1/dir-2/\");\n  eos::IContainerMDPtr subcont3 = view()->createContainer(\"/dir-1/dir-3/\");\n  eos::IContainerMDPtr subcont4 = view()->createContainer(\"/dir-1/dir-4/\");\n\n  std::vector<std::thread> workers;\n  for(int i = 0;i < 100; i++) {\n    workers.emplace_back([this,cont1](){\n      for(auto cit = eos::ContainerMapIterator(cont1); cit.valid(); cit.next()){\n        auto cmd = cont1->findContainer(cit.key());\n        eos::MDLocking::ContainerWriteLock cmdLock(cmd.get());\n        cmd->setAttribute(\"test\",\"test\");\n        cont1->notifyMTimeChange(containerSvc());\n      }\n      for(auto fit = eos::FileMapIterator(cont1); fit.valid(); fit.next()) {\n        auto fmd = cont1->findFile(fit.key());\n        eos::MDLocking::FileWriteLock fmdLock(fmd.get());\n        fmd->setAttribute(\"test\",\"test\");\n      }\n    });\n  }\n\n  for(auto & w: workers) {\n    w.join();\n  }\n}\n\nTEST_F(VariousTests, FileIteratorInvalidation)\n{\n  // exercises the FileMapIterator when underlying file map's densehashtable\n  // is likely to have been reallocated at a different location but still have\n  // the same size.\n  eos::IContainerMDPtr cont1 = view()->createContainer(\"/dir-1/\");\n\n  for (size_t i = 1; i <= 17; ++i) {\n    std::string s = \"/dir-1/file-\" + std::to_string(i);\n    view()->createFile(s);\n  }\n\n  // expect 64 buckets now allocated in densehashtable\n  eos::FileMapIterator fit(cont1);\n\n  for (size_t i = 0; i < 8; ++i) {\n    ASSERT_TRUE(fit.valid());\n    fit.next();\n  }\n\n  ASSERT_TRUE(fit.valid());\n\n  // remove some files, but erasing does not cause reallocation\n  for (size_t i = 12; i <= 17; ++i) {\n    std::string s = \"/dir-1/file-\" + std::to_string(i);\n    view()->unlinkFile(s);\n  }\n\n  // expect number of buckets to be shrunk to 32 on next insert\n  view()->createFile(\"/dir-1/file-12\");\n  // allocate a few regions, each equal to the size of 64 buckets\n  const size_t sz = 64 * sizeof(std::pair<const std::string, IContainerMD::id_t>);\n  const size_t nalloc = 32;\n  void* p[nalloc];\n\n  for (size_t i = 0; i < nalloc; ++i) {\n    p[i] = malloc(sz);\n    memset(p[i], 0xcc, sz);\n  }\n\n  // add files, expect number of buckets to expand to 64 after last insert\n  for (size_t i = 13; i <= 17; ++i) {\n    std::string s = \"/dir-1/file-\" + std::to_string(i);\n    view()->createFile(s);\n  }\n\n  for (size_t i = 0; i < nalloc; ++i) {\n    free(p[i]);\n    p[i] = nullptr;\n  }\n\n  while (fit.valid()) {\n    fit.next();\n  }\n}\n\n\nTEST_F(NamespaceExplorerF, MissingFile)\n{\n  view()->createContainer(\"/dir-1/\");\n  view()->createContainer(\"/dir-2/\");\n  view()->createContainer(\"/dir-3/\");\n  view()->createContainer(\"/dir-4/\");\n  view()->createFile(\"/dir-1/file-1\");\n  view()->createFile(\"/dir-1/file-2\");\n  IFileMDPtr f = view()->createFile(\"/dir-1/file-3\");\n  ASSERT_EQ(f->getId(), 3);\n  view()->createFile(\"/dir-1/file-4\");\n  view()->createFile(\"/dir-1/file-5\");\n  mdFlusher()->synchronize();\n  ASSERT_EQ(\n    qclient::describeRedisReply(qcl().exec(\"lhdel\", \"eos-file-md\", \"3\").get()),\n    \"(integer) 1\"\n  );\n  mdFlusher()->synchronize();\n  ExplorationOptions options;\n  options.depthLimit = 999;\n  NamespaceExplorer explorer(\"/\", options, qcl(), executor());\n  NamespaceItem item;\n  ASSERT_TRUE(explorer.fetch(item));\n  ASSERT_EQ(item.fullPath, \"/\");\n  ASSERT_TRUE(explorer.fetch(item));\n  ASSERT_EQ(item.fullPath, \"/dir-1/\");\n  ASSERT_TRUE(explorer.fetch(item));\n  ASSERT_EQ(item.fullPath, \"/dir-1/file-1\");\n  ASSERT_TRUE(explorer.fetch(item));\n  ASSERT_EQ(item.fullPath, \"/dir-1/file-2\");\n  ASSERT_TRUE(explorer.fetch(item));\n  ASSERT_EQ(item.fullPath, \"/dir-1/file-4\");\n  ASSERT_TRUE(explorer.fetch(item));\n  ASSERT_EQ(item.fullPath, \"/dir-1/file-5\");\n}\n"
  },
  {
    "path": "namespace/ns_quarkdb/tests/utils/break-file.py",
    "content": "#!/usr/bin/env python\n#-------------------------------------------------------------------------------\n# author: Lukasz Janyst <ljanyst@cern.ch>\n# desc:   Overwrite random dwords in a file with random bytes\n#-------------------------------------------------------------------------------\n\nimport sys, os, random, array\n\n#-------------------------------------------------------------------------------\n# Print help\n#-------------------------------------------------------------------------------\ndef printHelp():\n    print \"Usage:\"\n    print \"  \", sys.argv[0], \" filename numRegions maxRegionSize\"\n    print \"       numRegions is defaulted to 100\"\n    print \"       maxRegionSize is defaulted to 10 bytes\"\n\n\n#-------------------------------------------------------------------------------\n# Break the stuff\n#-------------------------------------------------------------------------------\nif __name__ == '__main__':\n    #---------------------------------------------------------------------------\n    # Check the commandline\n    #---------------------------------------------------------------------------\n    maxRegionSize = 10  # maximas size of a broken region in bytes\n    numRegions    = 100 # number of regions to be broken\n\n    if len(sys.argv) < 2:\n        printHelp()\n        sys.exit(1)\n\n    if len(sys.argv) >= 3:\n        numRegions = int( sys.argv[2] )\n\n    if len(sys.argv) == 4:\n        numRegions = int( sys.argv[3] )\n\n    if len(sys.argv) > 4:\n        printHelp()\n        sys.exit()\n\n    #--------------------------------------------------------------------------\n    # Stat the file\n    #--------------------------------------------------------------------------\n    try:\n        size = os.stat( sys.argv[1] ).st_size\n    except OSError, e:\n        print \"[!] There's a problem with your file:\", e\n        sys.exit(2)\n\n    #---------------------------------------------------------------------------\n    # Break the stuff\n    #---------------------------------------------------------------------------\n    try:\n        f = file( sys.argv[1], \"r+\" )\n        for i in xrange( numRegions ):\n            offset  = int(random.random()*size)\n            regSize = int(random.random()*maxRegionSize)\n            if offset+regSize > size:\n                offset = size-regSize\n            f.seek( offset )\n\n            rBytes  = array.array( 'c' )\n            for j in range( regSize ):\n                rBytes.append( chr(int(random.random() *255)) )\n            f.write( rBytes.tostring() )\n        f.close()\n    except IOError, e:\n        print \"[!] There's a problem with your file:\", e\n        sys.exit(3)\n"
  },
  {
    "path": "namespace/ns_quarkdb/tools/EosConvertToLocalityHashes.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <getopt.h>\n#include <iostream>\n#include <qclient/Utils.hh>\n#include <qclient/Members.hh>\n#include <qclient/QClient.hh>\n#include <qclient/structures/QHash.hh>\n#include \"namespace/ns_quarkdb/persistency/MetadataFetcher.hh\"\n#include \"namespace/ns_quarkdb/persistency/RequestBuilder.hh\"\n#include \"namespace/ns_quarkdb/Constants.hh\"\n#include \"namespace/ns_quarkdb/persistency/Serialization.hh\"\n#include \"namespace/utils/LocalityHint.hh\"\n\nusing qclient::redisReplyPtr;\n\nvoid processFileBucket(qclient::QClient& qcl, uint64_t bucketId)\n{\n  std::string bucketString = SSTR(bucketId << eos::constants::sFileKeySuffix);\n  std::cout << \"Processing file bucket \" << bucketString << std::endl;\n  redisReplyPtr len = qcl.exec(\"HLEN\", bucketString).get();\n\n  if (len->type != REDIS_REPLY_INTEGER) {\n    std::cerr << \"Received unexpected response: \" << qclient::describeRedisReply(\n                len) << std::endl;\n    std::abort();\n  }\n\n  if (len->integer == 0u) {\n    std::cout << \"--- Bucket is empty!\" << std::endl;\n  } else {\n    std::cout << \"--- Found \" << len->integer << \" items, converting...\" <<\n              std::endl;\n  }\n\n  qclient::QHash bucket(qcl, bucketString);\n  size_t processed = 0;\n\n  for (auto it = bucket.getIterator(); it.valid(); it.next()) {\n    processed++;\n    std::cout << \"Key: \" << it.getKey() << std::endl;\n    std::string value = it.getValue();\n    eos::ns::FileMdProto fileProto;\n    eos::MDStatus status = eos::Serialization::deserialize(value.c_str(),\n                           value.size(), fileProto);\n\n    if (!status.ok()) {\n      std::cout << \"ERROR WHILE CONVERTING FileID \" << it.getKey() <<\n                \", COULD NOT PARSE\" << std::endl;\n      std::abort();\n    }\n\n    std::string localityHint = eos::LocalityHint::build(eos::ContainerIdentifier(\n                                 fileProto.cont_id()), fileProto.name());\n    qcl.exec(\"CONVERT-HASH-FIELD-TO-LHASH\", bucketString, it.getKey(),\n             eos::constants::sFileKey, it.getKey(), localityHint);\n\n    if (processed % 1024 == 0) {\n      std::cout << \"Processed \" << processed << std::endl;\n    }\n  }\n}\n\nvoid processContainerBucket(qclient::QClient& qcl, uint64_t bucketId)\n{\n  std::string bucketString = SSTR(bucketId << eos::constants::sContKeySuffix);\n  std::cout << \"Processing container bucket \" << bucketString << std::endl;\n  redisReplyPtr len = qcl.exec(\"HLEN\", bucketString).get();\n\n  if (len->type != REDIS_REPLY_INTEGER) {\n    std::cerr << \"Received unexpected response: \" << qclient::describeRedisReply(\n                len) << std::endl;\n    std::abort();\n  }\n\n  if (len->integer == 0u) {\n    std::cout << \"--- Bucket is empty!\" << std::endl;\n  } else {\n    std::cout << \"--- Found \" << len->integer << \" items, converting...\" <<\n              std::endl;\n  }\n\n  qclient::QHash bucket(qcl, bucketString);\n  size_t processed = 0;\n\n  for (auto it = bucket.getIterator(); it.valid(); it.next()) {\n    processed++;\n    std::cout << \"Key: \" << it.getKey() << std::endl;\n    std::string value = it.getValue();\n    eos::ns::ContainerMdProto containerProto;\n    eos::MDStatus status = eos::Serialization::deserialize(value.c_str(),\n                           value.size(), containerProto);\n\n    if (!status.ok()) {\n      std::cout << \"ERROR WHILE CONVERTING ContainerID \" << it.getKey() <<\n                \", COULD NOT PARSE\" << std::endl;\n      std::abort();\n    }\n\n    std::string localityHint = eos::LocalityHint::build(eos::ContainerIdentifier(\n                                 containerProto.parent_id()), containerProto.name());\n    qcl.exec(\"CONVERT-HASH-FIELD-TO-LHASH\", bucketString, it.getKey(),\n             eos::constants::sContainerKey, it.getKey(), localityHint);\n\n    if (processed % 1024 == 0) {\n      std::cout << \"Processed \" << processed << std::endl;\n    }\n  }\n}\n\n\nint main(int argc, char* argv[])\n{\n  // TODO(gbitzes): Remove this tool eventually in a couple of months, when no\n  // mixed-layout instances remain\n  if (argc != 2) {\n    std::cerr <<\n              \"This tools converts old EOS NS layouts using hash-buckets, to the new one using locality hashes.\"\n              << std::endl;\n    std::cerr <<\n              \"You most probably never need to run this tool. EOS instances created \" <<\n              std::endl;\n    std::cerr << \"after 18 May 2018 should have the new layout automatically.\" <<\n              std::endl;\n    std::cerr << \"Usage: \" << argv[0] <<\n              \" <quarkdb command-separated endpoints, such as localhost:7777,localhost:7778>\"\n              << std::endl;\n    exit(1);\n  }\n\n  qclient::Members members;\n\n  if (!members.parse(argv[1])) {\n    std::cerr << \"Cannot parse cluster members.\" << std::endl;\n    exit(1);\n  }\n\n  const std::uint64_t sNumContBuckets = 128 * 1024;\n  const std::uint64_t sNumFileBuckets = 1024 * 1024;\n  qclient::Options opts;\n  opts.transparentRedirects = true;\n  opts.retryStrategy = qclient::RetryStrategy::WithTimeout(std::chrono::seconds(\n                         20));\n  qclient::QClient qcl(members, std::move(opts));\n\n  for (uint64_t i = 0; i < sNumFileBuckets; i++) {\n    processFileBucket(qcl, i);\n    processFileBucket(qcl, i);\n  }\n\n  for (uint64_t i = 0; i < sNumContBuckets; i++) {\n    processContainerBucket(qcl, i);\n    processContainerBucket(qcl, i);\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "namespace/ns_quarkdb/tools/Fid2PathTool.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n  * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/CLI11.hpp\"\n#include \"common/FileId.hh\"\n#include <qclient/QClient.hh>\n\nint main(int argc, char* argv[]) {\n  CLI::App app(\"Tool to translate fids to FST paths\");\n\n  int64_t fid;\n  app.add_option(\"--fid\", fid, \"Specify the file ID\")->required();\n\n  //----------------------------------------------------------------------------\n  // Parse..\n  //----------------------------------------------------------------------------\n  try {\n    app.parse(argc, argv);\n  } catch (const CLI::ParseError& e) {\n    return app.exit(e);\n  }\n\n  //----------------------------------------------------------------------------\n  // Convert..\n  //----------------------------------------------------------------------------\n  std::string hex_fid = eos::common::FileId::Fid2Hex(fid);\n\n  std::string prefix = \"/data/\";\n  std::string path = eos::common::FileId::FidPrefix2FullPath(hex_fid.c_str(),\n                prefix.c_str());\n\n  std::cout << \"id: \" << fid << std::endl;\n  std::cout << \"fxid: \" << hex_fid << std::endl;\n  std::cout << \"path: \" << path << std::endl;\n  return 0;\n}\n\n"
  },
  {
    "path": "namespace/ns_quarkdb/tools/InodeToFidTool.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n  * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/CLI11.hpp\"\n#include \"common/FileId.hh\"\n\nint main(int argc, char* argv[]) {\n  CLI::App app(\"Tool to translate fids to FST paths\");\n\n  unsigned long long inode;\n  app.add_option(\"--inode\", inode, \"Specify the file inode\")->required();\n\n  //----------------------------------------------------------------------------\n  // Parse..\n  //----------------------------------------------------------------------------\n  try {\n    app.parse(argc, argv);\n  } catch (const CLI::ParseError& e) {\n    return app.exit(e);\n  }\n\n  //----------------------------------------------------------------------------\n  // Convert..\n  //----------------------------------------------------------------------------\n  unsigned long long fid = eos::common::FileId::InodeToFid(inode);\n  std::cout << \"fid: \" << fid << std::endl;\n  return 0;\n}\n\n"
  },
  {
    "path": "namespace/ns_quarkdb/tools/InspectionTool.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n  * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"namespace/ns_quarkdb/inspector/Inspector.hh\"\n#include \"namespace/ns_quarkdb/inspector/OutputSink.hh\"\n#include \"namespace/ns_quarkdb/inspector/FileMetadataFilter.hh\"\n\n#include \"common/PasswordHandler.hh\"\n#include \"common/ParseUtils.hh\"\n#include \"common/CLI11.hpp\"\n#include <qclient/QClient.hh>\n#include <stdint.h>\n\n#define DBG(message) std::cerr << __FILE__ << \":\" << __LINE__ << \" -- \" << #message << \" = \" << message << std::endl\n\nstruct MemberValidator : public CLI::Validator {\n  MemberValidator() : Validator(\"MEMBER\")\n  {\n    func_ = [](const std::string & str) {\n      qclient::Members members;\n\n      if (!members.parse(str)) {\n        return SSTR(\"Could not parse members: '\" << str <<\n                    \"'. Expected format is a comma-separated list of servers: example1:1111,example2:2222\");\n      }\n\n      return std::string();\n    };\n  }\n};\n\nstruct IdValidator : public CLI::Validator {\n  IdValidator() : Validator(\"ID\")\n  {\n    func_ = [](const std::string & str) {\n      uint64_t parsed;\n\n      if (!eos::common::ParseUInt64(str, parsed)) {\n        return SSTR(\"Could not parse id, was expecting uint64_t: '\" << str << \"'\");\n      }\n\n      return std::string();\n    };\n  }\n};\n\nusing namespace eos;\n\n//------------------------------------------------------------------------------\n// Given a subcommand, add common-to-all options such as --members\n// and --password\n//------------------------------------------------------------------------------\nvoid addClusterOptions(CLI::App* subcmd, std::string& membersStr,\n                       MemberValidator& memberValidator, std::string& password,\n                       std::string& passwordFile, unsigned int& connectionRetries)\n{\n  subcmd->add_option(\"--members\", membersStr,\n                     \"One or more members of the QDB cluster\")\n  ->required()\n  ->check(memberValidator);\n  subcmd->add_option(\"--connection-retries\", connectionRetries,\n                     \"Number of connection retries - default 5\");\n  auto passwordGroup = subcmd->add_option_group(\"Authentication\",\n                       \"Specify QDB authentication options\");\n  passwordGroup->add_option(\"--password\", password,\n                            \"The password for connecting to the QDB cluster - can be empty\");\n  passwordGroup->add_option(\"--password-file\", passwordFile,\n                            \"The passwordfile for connecting to the QDB cluster - can be empty\");\n  passwordGroup->require_option(0, 1);\n}\n\n//------------------------------------------------------------------------------\n// Control dry-run, common to all dangerous commands\n//----------------------------------------------------------------------------\nvoid addDryRun(CLI::App* subcmd, bool& noDryRun)\n{\n  subcmd->add_flag(\"--no-dry-run\", noDryRun,\n                   \"Execute changes for real.\\nIf not supplied, planned changes are only shown and not applied.\");\n}\n\nint main(int argc, char* argv[])\n{\n  CLI::App app(\"Tool to inspect contents of the QuarkDB-based EOS namespace.\");\n  app.require_subcommand();\n  //----------------------------------------------------------------------------\n  // Basic parameters, common to all subcommands\n  //----------------------------------------------------------------------------\n  std::string membersStr;\n  MemberValidator memberValidator;\n  std::string password;\n  std::string passwordFile;\n  bool noDryRun = false;\n  std::unique_ptr<FileMetadataFilter> metadataFilter;\n  std::string filterExpression;\n  unsigned int connectionRetries = 5;\n  //----------------------------------------------------------------------------\n  // Set-up dump subcommand..\n  //----------------------------------------------------------------------------\n  auto dumpSubcommand = app.add_subcommand(\"dump\",\n                        \"[DEPRECATED] Recursively dump entire namespace contents under a specific path\");\n  addClusterOptions(dumpSubcommand, membersStr, memberValidator, password,\n                    passwordFile, connectionRetries);\n  std::string dumpPath;\n  std::string trimPaths;\n  std::string attrQuery;\n  bool relativePaths = false;\n  bool rawPaths = false;\n  bool noDirs = false;\n  bool noFiles = false;\n  bool showSize = false;\n  bool showMtime = false;\n  bool withParents = false;\n  uint32_t maxDepth = UINT32_MAX;\n  bool json = false;\n  bool minimal = false;\n  dumpSubcommand->add_option(\"--path\", dumpPath, \"The target path to dump\")\n  ->required();\n  dumpSubcommand->add_option(\"--attr-query\", attrQuery,\n                             \"Print the specified extended attribute\");\n  dumpSubcommand->add_flag(\"--relative-paths\", relativePaths,\n                           \"Print paths relative to --path\");\n  dumpSubcommand->add_flag(\"--raw-paths\", rawPaths,\n                           \"Print the raw paths without path= in front, and nothing else\");\n  dumpSubcommand->add_flag(\"--no-dirs\", noDirs,\n                           \"Don't print directories, only files\");\n  dumpSubcommand->add_flag(\"--no-files\", noFiles,\n                           \"Don't print files, only directories\");\n  dumpSubcommand->add_flag(\"--show-size\", showSize, \"Show file size\");\n  dumpSubcommand->add_flag(\"--show-mtime\", showMtime,\n                           \"Show file modification time\");\n  //----------------------------------------------------------------------------\n  // Set-up scan subcommand..\n  //----------------------------------------------------------------------------\n  auto scanSubcommand = app.add_subcommand(\"scan\",\n                        \"Recursively scan and print entire namespace contents under a specific path\");\n  addClusterOptions(scanSubcommand, membersStr, memberValidator, password,\n                    passwordFile, connectionRetries);\n  scanSubcommand->add_option(\"--path\", dumpPath, \"The target path to scan\")\n  ->required();\n  scanSubcommand->add_option(\"--trim\", trimPaths,\n                             \"REGEX for paths to be excluded\");\n  scanSubcommand->add_flag(\"--relative-paths\", relativePaths,\n                           \"Print paths relative to --path\");\n  scanSubcommand->add_flag(\"--raw-paths\", rawPaths,\n                           \"Print the raw paths without path= in front, and nothing else\");\n  scanSubcommand->add_flag(\"--no-dirs\", noDirs,\n                           \"Don't print directories, only files\");\n  scanSubcommand->add_flag(\"--no-files\", noFiles,\n                           \"Don't print files, only directories\");\n  scanSubcommand->add_option(\"--maxdepth\", maxDepth,\n                             \"Descend only <maxdepth> levels.\");\n  scanSubcommand->add_flag(\"--json\", json, \"Use json output\");\n  //----------------------------------------------------------------------------\n  // Set-up print subcommand..\n  //----------------------------------------------------------------------------\n  auto printSubcommand = app.add_subcommand(\"print\",\n                         \"Print everything known about a given file, or container\");\n  addClusterOptions(printSubcommand, membersStr, memberValidator, password,\n                    passwordFile, connectionRetries);\n  printSubcommand->add_flag(\"--with-parents\", withParents,\n                            \"Show detailed information for each parent container as well\");\n  uint64_t fid = 0;\n  uint64_t cid = 0;\n  auto idGroup = printSubcommand->add_option_group(\"ID\", \"Specify what to print\");\n  idGroup->add_option(\"--fid\", fid,\n                      \"Specify the FileMD to print, through its ID (decimal form)\");\n  idGroup->add_option(\"--cid\", cid,\n                      \"Specify the ContainerMD to print, through its ID (decimal form)\");\n  idGroup->require_option(1, 1);\n  //----------------------------------------------------------------------------\n  // Set-up stripediff subcommand..\n  //----------------------------------------------------------------------------\n  auto stripediffSubcommand = app.add_subcommand(\"stripediff\",\n                              \"Find files which have non-nominal number of stripes (replicas)\");\n  addClusterOptions(stripediffSubcommand, membersStr, memberValidator, password,\n                    passwordFile, connectionRetries);\n  stripediffSubcommand->add_flag(\"--json\", json, \"Use json output\");\n  stripediffSubcommand->add_flag(\"-m\", minimal,\n                                 \"Minimal format (faster) that can be combined with json switch\");\n  //----------------------------------------------------------------------------\n  // Set-up one-replica-layout subcommand..\n  //----------------------------------------------------------------------------\n  auto oneReplicaLayoutSubcommand = app.add_subcommand(\"one-replica-layout\",\n                                    \"Find all files whose layout asks for a single replica\");\n  addClusterOptions(oneReplicaLayoutSubcommand, membersStr, memberValidator,\n                    password, passwordFile, connectionRetries);\n  bool showName = false;\n  bool fullPaths = false;\n  bool filterInternal = false;\n  oneReplicaLayoutSubcommand->add_flag(\"--show-name\", showName, \"Show filenames\");\n  oneReplicaLayoutSubcommand->add_flag(\"--full-paths\", fullPaths,\n                                       \"Show full paths, if possible\");\n  oneReplicaLayoutSubcommand->add_flag(\"--filter-internal\", filterInternal,\n                                       \"Filter internal entries, such as versioning, aborted atomic uploads, etc\");\n  oneReplicaLayoutSubcommand->add_flag(\"--json\", json, \"Use json output\");\n  //----------------------------------------------------------------------------\n  // Set-up scan-dirs subcommand..\n  //----------------------------------------------------------------------------\n  auto scanDirsSubcommand = app.add_subcommand(\"scan-dirs\",\n                            \"Dump the full list of container metadata across the entire namespace\");\n  addClusterOptions(scanDirsSubcommand, membersStr, memberValidator, password,\n                    passwordFile, connectionRetries);\n  bool onlyNoAttrs = false;\n  bool countContents = false;\n  size_t countThreshold = 0;\n  scanDirsSubcommand->add_flag(\"--only-no-attrs\", onlyNoAttrs,\n                               \"Only show directories which have no extended attributes whatsoever\");\n  scanDirsSubcommand->add_flag(\"--full-paths\", fullPaths,\n                               \"Show full container paths, if possible\");\n  scanDirsSubcommand->add_flag(\"--count-contents\", countContents,\n                               \"Count how many files and containers are in each directory (non-recursive)\");\n  scanDirsSubcommand->add_option(\"--count-threshold\", countThreshold,\n                                 \"Only print containers which contain more than the specified number of items. Useful for detecting huge containers on which 'ls' might hang\");\n  scanDirsSubcommand->add_flag(\"--json\", json, \"Use json output\");\n  //----------------------------------------------------------------------------\n  // Set-up scan-files subcommand..\n  //----------------------------------------------------------------------------\n  auto scanFilesSubcommand = app.add_subcommand(\"scan-files\",\n                             \"Dump the full list of file metadata across the entire namespace\");\n  addClusterOptions(scanFilesSubcommand, membersStr, memberValidator, password,\n                    passwordFile, connectionRetries);\n  bool onlySizes = false;\n  bool findUnknownFsids = false;\n  scanFilesSubcommand->add_flag(\"--only-sizes\", onlySizes,\n                                \"Only print file sizes, one per line.\");\n  scanFilesSubcommand->add_flag(\"--full-paths\", fullPaths,\n                                \"Show full file paths, if possible\");\n  scanFilesSubcommand->add_flag(\"--find-unknown-fsids\", findUnknownFsids,\n                                \"Only print files for which there is one or more unrecognized fsids in location vector.\");\n  scanFilesSubcommand->add_flag(\"--json\", json, \"Use json output\");\n  scanFilesSubcommand->add_option(\"--where\", filterExpression,\n                                  \"Filter results using the given expression.\\nNOTE: Filtering is done client side! All results still have to be streamed -- performance is the same.\");\n  //----------------------------------------------------------------------------\n  // Set-up scan-deathrow subcommand..\n  //----------------------------------------------------------------------------\n  auto scanDeathrowSubcommand = app.add_subcommand(\"scan-deathrow\",\n                                \"Show all files currently scheduled to be deleted\");\n  addClusterOptions(scanDeathrowSubcommand, membersStr, memberValidator, password,\n                    passwordFile, connectionRetries);\n  //----------------------------------------------------------------------------\n  // Set-up check-naming-conflicts subcommand..\n  //----------------------------------------------------------------------------\n  auto namingConflictsSubcommand = app.add_subcommand(\"check-naming-conflicts\",\n                                   \"Scan through the entire namespace looking for naming conflicts\");\n  addClusterOptions(namingConflictsSubcommand, membersStr, memberValidator,\n                    password, passwordFile, connectionRetries);\n  bool onePerLine = false;\n  namingConflictsSubcommand->add_flag(\"--one-per-line\", onePerLine,\n                                      \"Don't group results in a single line - useful to count how many conflicts there are in total\");\n  //----------------------------------------------------------------------------\n  // Set-up check-cursed-names subcommand..\n  //----------------------------------------------------------------------------\n  auto cursedNamesSubcommand = app.add_subcommand(\"check-cursed-names\",\n                               \"Scan through the namespace to find files / containers with invalid names\");\n  addClusterOptions(cursedNamesSubcommand, membersStr, memberValidator, password,\n                    passwordFile, connectionRetries);\n  //----------------------------------------------------------------------------\n  // Set-up check-orphans subcommand..\n  //----------------------------------------------------------------------------\n  auto checkOrphansSubcommand = app.add_subcommand(\"check-orphans\",\n                                \"Find files and directories with invalid parents\");\n  addClusterOptions(checkOrphansSubcommand, membersStr, memberValidator, password,\n                    passwordFile, connectionRetries);\n  //----------------------------------------------------------------------------\n  // Set-up check-fsview-missing subcommand..\n  //----------------------------------------------------------------------------\n  auto checkFsViewMissingSubcommand = app.add_subcommand(\"check-fsview-missing\",\n                                      \"Check which FileMDs have locations / unlinked locations not present in the filesystem view\");\n  addClusterOptions(checkFsViewMissingSubcommand, membersStr, memberValidator,\n                    password, passwordFile, connectionRetries);\n  //----------------------------------------------------------------------------\n  // Set-up check-fsview-extra subcommand..\n  //----------------------------------------------------------------------------\n  auto checkFsViewExtraSubcommand = app.add_subcommand(\"check-fsview-extra\",\n                                    \"Check whether there exist FsView entries without a corresponding FMD location\");\n  addClusterOptions(checkFsViewExtraSubcommand, membersStr, memberValidator,\n                    password, passwordFile, connectionRetries);\n  //----------------------------------------------------------------------------\n  // Set-up check-shadow-directories subcommand..\n  //----------------------------------------------------------------------------\n  auto checkShadowDirectories = app.add_subcommand(\"check-shadow-directories\",\n                                \"Check for naming conflicts between directories inside the same subdirectory\");\n  addClusterOptions(checkShadowDirectories, membersStr, memberValidator, password,\n                    passwordFile, connectionRetries);\n  //----------------------------------------------------------------------------\n  // Set-up check-simulated-hardlinks subcommand..\n  //----------------------------------------------------------------------------\n  auto checkSimulatedHardlinks = app.add_subcommand(\"check-simulated-hardlinks\",\n                                 \"Check for corruption in simulated hardlinks\");\n  addClusterOptions(checkSimulatedHardlinks, membersStr, memberValidator,\n                    password, passwordFile, connectionRetries);\n  //----------------------------------------------------------------------------\n  // Set-up fix-detached-parent subcommand..\n  //----------------------------------------------------------------------------\n  auto fixDetachedParent = app.add_subcommand(\"fix-detached-parent\",\n                           \"[CAUTION] Attempt to fix a detached parent of the given fid / cid,\\nby re-creating said parent in a given destination\");\n  addClusterOptions(fixDetachedParent, membersStr, memberValidator, password,\n                    passwordFile, connectionRetries);\n  addDryRun(fixDetachedParent, noDryRun);\n  std::string destinationPath;\n  fixDetachedParent->add_option(\"--destination-path\", destinationPath,\n                                \"Path in which the detached file / container will be stored.\")\n  ->required();\n  auto idGroup2 = fixDetachedParent->add_option_group(\"ID\",\n                  \"Specify what to fix\");\n  // idGroup2->add_option(\"--fid\", fid, \"Fix the parents of the given file ID (decimal form)\");\n  idGroup2->add_option(\"--cid\", cid,\n                       \"Fix the parents of the given container ID (decimal form)\");\n  idGroup2->add_option(\"--fid\", fid,\n                       \"Fix the parents of the given file ID (decimal form)\");\n  idGroup2->require_option(1, 1);\n  //----------------------------------------------------------------------------\n  // Set-up fix-shadow-file subcommand..\n  //----------------------------------------------------------------------------\n  auto fixShadowFileSubcommand = app.add_subcommand(\"fix-shadow-file\",\n                                 \"[CAUTION] Attempt to fix a shadowed file.\\nIf the given fid is indeed shadowed by a different fid / cid, it's moved to the given destination.\");\n  addClusterOptions(fixShadowFileSubcommand, membersStr, memberValidator,\n                    password, passwordFile, connectionRetries);\n  addDryRun(fixShadowFileSubcommand, noDryRun);\n  fixShadowFileSubcommand->add_option(\"--destination-path\", destinationPath,\n                                      \"Path in which the conflicting file will be stored.\")\n  ->required();\n  fixShadowFileSubcommand->add_option(\"--fid\", fid,\n                                      \"Specify the suspected shadowed file\")\n  ->required();\n  //----------------------------------------------------------------------------\n  // Set-up drop-from-deathrow subcommand..\n  //----------------------------------------------------------------------------\n  auto dropFromDeathrow = app.add_subcommand(\"drop-from-deathrow\",\n                          \"[CAUTION] Delete a FileMD which is currently on deathrow.\\nAny pending replicas on the FSTs will not be touched, potentially resulting in dark data!\");\n  addClusterOptions(dropFromDeathrow, membersStr, memberValidator, password,\n                    passwordFile, connectionRetries);\n  addDryRun(dropFromDeathrow, noDryRun);\n  dropFromDeathrow->add_option(\"--fid\", fid,\n                               \"Specify which file to drop - it should currently be stuck on deathrow\")\n  ->required();\n  //----------------------------------------------------------------------------\n  // Set-up drop-empty-cid subcommand..\n  //----------------------------------------------------------------------------\n  auto dropEmptyCid = app.add_subcommand(\"drop-empty-cid\",\n                                         \"[CAUTION] Drop an empty container. The command will fail if it appears the directory is not empty.\");\n  addClusterOptions(dropEmptyCid, membersStr, memberValidator, password,\n                    passwordFile, connectionRetries);\n  addDryRun(dropEmptyCid, noDryRun);\n  dropEmptyCid->add_option(\"--cid\", cid,\n                           \"Specify which container ID to drop\")\n  ->required();\n  //----------------------------------------------------------------------------\n  // Change fid protobuf properties\n  //----------------------------------------------------------------------------\n  auto changeFidSubcommand = app.add_subcommand(\"change-fid\",\n                             \"[DANGEROUS] Change specified properties of a single fid. Better know what you're doing before using this!\");\n  addClusterOptions(changeFidSubcommand, membersStr, memberValidator, password,\n                    passwordFile, connectionRetries);\n  addDryRun(changeFidSubcommand, noDryRun);\n  uint64_t newParent = 0ull;\n  std::string newChecksum;\n  int64_t newSize = -1;\n  uint64_t newLayoutId = 0ull;\n  changeFidSubcommand->add_option(\"--fid\", fid,\n                                  \"Specify the FileMD to print, through its ID (decimal form)\")\n  ->required();\n  changeFidSubcommand->add_option(\"--new-parent\", newParent,\n                                  \"Change the parent container of the specified fid. \"\n                                  \"This _DOES NOT_ modify the respective container maps, \"\n                                  \"only the protobuf FMD!\");\n  changeFidSubcommand->add_option(\"--new-checksum\", newChecksum,\n                                  \"Change the checksum of the specified fid.\");\n  changeFidSubcommand->add_option(\"--new-size\", newSize,\n                                  \"Change the size of the specified fid.\");\n  changeFidSubcommand->add_option(\"--new-layout-id\", newLayoutId,\n                                  \"Change the layout id of the specified fid.\");\n  //----------------------------------------------------------------------------\n  // Rename a fid from its current location\n  //----------------------------------------------------------------------------\n  std::string newName;\n  auto renameFidSubcommand = app.add_subcommand(\"rename-fid\",\n                             \"[DANGEROUS] Rename a file onto the specified container ID - the respective container maps are modified.\");\n  addClusterOptions(renameFidSubcommand, membersStr, memberValidator, password,\n                    passwordFile, connectionRetries);\n  addDryRun(renameFidSubcommand, noDryRun);\n  renameFidSubcommand->add_option(\"--fid\", fid, \"Specify the FileMD to rename\")\n  ->required();\n  renameFidSubcommand->add_option(\"--destination-cid\", newParent,\n                                  \"The destination container ID in which to put the FileMD\")\n  ->required();\n  renameFidSubcommand->add_option(\"--new-name\", newName,\n                                  \"The new name of the specified fid - must only contain alphanumeric characters, and can be left empty to preserve old name\");\n  //----------------------------------------------------------------------------\n  // Rename a cid from its current location\n  //----------------------------------------------------------------------------\n  auto renameCidSubcommand = app.add_subcommand(\"rename-cid\",\n                             \"[DANGEROUS] Rename a container onto the specified container ID - the respective container maps are modified.\");\n  addClusterOptions(renameCidSubcommand, membersStr, memberValidator, password,\n                    passwordFile, connectionRetries);\n  addDryRun(renameCidSubcommand, noDryRun);\n  renameCidSubcommand->add_option(\"--cid\", cid, \"Specify the FileMD to rename\")\n  ->required();\n  renameCidSubcommand->add_option(\"--destination-cid\", newParent,\n                                  \"The destination container ID in which to put the FileMD\")\n  ->required();\n  renameCidSubcommand->add_option(\"--new-name\", newName,\n                                  \"The new name of the specified cid - must only contain alphanumeric characters, and can be left empty to preserve old name\");\n  //----------------------------------------------------------------------------\n  // Set-up overwrite-container subcommand..\n  //----------------------------------------------------------------------------\n  auto overwriteContainerSubcommand = app.add_subcommand(\"overwrite-container\",\n                                      \"[DANGEROUS] Overwrite the given ContainerMD - USE WITH CAUTION\");\n  addClusterOptions(overwriteContainerSubcommand, membersStr, memberValidator,\n                    password, passwordFile, connectionRetries);\n  addDryRun(overwriteContainerSubcommand, noDryRun);\n  uint64_t parent;\n  std::string containerName;\n  overwriteContainerSubcommand->add_option(\"--cid\", cid,\n      \"Specify which container ID to overwrite\")\n  ->required();\n  overwriteContainerSubcommand->add_option(\"--parent-id\", parent,\n      \"Specify which ID to set as parent\")\n  ->required();\n  overwriteContainerSubcommand->add_option(\"--name\", containerName,\n      \"Specify the container's name\")\n  ->required();\n\n  //----------------------------------------------------------------------------\n  // Parse..\n  //----------------------------------------------------------------------------\n  try {\n    app.parse(argc, argv);\n  } catch (const CLI::ParseError& e) {\n    return app.exit(e);\n  }\n\n  bool dryRun = !noDryRun;\n\n  //----------------------------------------------------------------------------\n  // Validate --password and --password-file options..\n  //----------------------------------------------------------------------------\n  if (!passwordFile.empty()) {\n    if (!common::PasswordHandler::readPasswordFile(passwordFile, password)) {\n      std::cerr << \"Could not read passwordfile: '\" << passwordFile <<\n                \"'. Ensure the file exists, and its permissions are 400.\" << std::endl;\n      return 1;\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  // Parse any filter expressions\n  //----------------------------------------------------------------------------\n  if (!filterExpression.empty()) {\n    FilterExpressionParser parser(filterExpression, false);\n\n    if (!parser.getStatus()) {\n      std::cerr << parser.getStatus().toString() << std::endl;\n      return 1;\n    }\n\n    metadataFilter = parser.getFilter();\n  }\n\n  //----------------------------------------------------------------------------\n  // Set-up QClient object towards QDB, ensure sanity\n  //----------------------------------------------------------------------------\n  qclient::Members members = qclient::Members::fromString(membersStr);\n  QdbContactDetails contactDetails(members, password);\n  qclient::Options opts = contactDetails.constructOptions();\n\n  if (connectionRetries) {\n    opts.retryStrategy = qclient::RetryStrategy::NRetries(connectionRetries);\n  }\n\n  qclient::QClient qcl(contactDetails.members, std::move(opts));\n  //----------------------------------------------------------------------------\n  // Set-up Inspector object, ensure sanity\n  //----------------------------------------------------------------------------\n  std::unique_ptr<OutputSink> outputSink;\n\n  if (json) {\n    if (!minimal) {\n      outputSink.reset(new JsonStreamSink(std::cout, std::cerr));\n    } else {\n      outputSink.reset(new JsonLinedStreamSink(std::cout, std::cerr));\n    }\n  } else {\n    outputSink.reset(new StreamSink(std::cout, std::cerr));\n  }\n\n  Inspector inspector(qcl, *outputSink);\n  std::string connectionErr;\n\n  if (!inspector.checkConnection(connectionErr)) {\n    std::cerr << connectionErr << std::endl;\n    return 1;\n  }\n\n  inspector.setMetadataFilter(std::move(metadataFilter));\n\n  //----------------------------------------------------------------------------\n  // Dispatch subcommand\n  //----------------------------------------------------------------------------\n  if (dumpSubcommand->parsed()) {\n    return inspector.dump(dumpPath, relativePaths, rawPaths, noDirs, noFiles,\n                          showSize, showMtime, attrQuery, std::cout);\n  }\n\n  if (scanSubcommand->parsed()) {\n    return inspector.scan(dumpPath, relativePaths, rawPaths, noDirs, noFiles,\n                          maxDepth, trimPaths);\n  }\n\n  if (namingConflictsSubcommand->parsed()) {\n    return inspector.checkNamingConflicts(onePerLine, std::cout, std::cerr);\n  }\n\n  if (cursedNamesSubcommand->parsed()) {\n    return inspector.checkCursedNames(std::cout, std::cerr);\n  }\n\n  if (printSubcommand->parsed()) {\n    if (fid > 0) {\n      return inspector.printFileMD(fid, withParents, std::cout, std::cerr);\n    }\n\n    return inspector.printContainerMD(cid, withParents, std::cout, std::cerr);\n  }\n\n  if (scanDirsSubcommand->parsed()) {\n    return inspector.scanDirs(onlyNoAttrs, fullPaths, countContents,\n                              countThreshold);\n  }\n\n  if (stripediffSubcommand->parsed()) {\n    return inspector.stripediff(json, minimal);\n  }\n\n  if (oneReplicaLayoutSubcommand->parsed()) {\n    return inspector.oneReplicaLayout(showName, fullPaths, filterInternal,\n                                      std::cout, std::cerr, json);\n  }\n\n  if (scanFilesSubcommand->parsed()) {\n    return inspector.scanFileMetadata(onlySizes, fullPaths, findUnknownFsids);\n  }\n\n  if (scanDeathrowSubcommand->parsed()) {\n    return inspector.scanDeathrow(std::cout, std::cerr);\n  }\n\n  if (checkOrphansSubcommand->parsed()) {\n    return inspector.checkOrphans(std::cout, std::cerr);\n  }\n\n  if (checkFsViewMissingSubcommand->parsed()) {\n    return inspector.checkFsViewMissing(std::cout, std::cerr);\n  }\n\n  if (checkFsViewExtraSubcommand->parsed()) {\n    return inspector.checkFsViewExtra(std::cout, std::cerr);\n  }\n\n  if (checkShadowDirectories->parsed()) {\n    return inspector.checkShadowDirectories(std::cout, std::cerr);\n  }\n\n  if (checkSimulatedHardlinks->parsed()) {\n    return inspector.checkSimulatedHardlinks(std::cout, std::cerr);\n  }\n\n  if (fixDetachedParent->parsed()) {\n    if (cid > 0) {\n      return inspector.fixDetachedParentContainer(dryRun, cid, destinationPath,\n             std::cout, std::cerr);\n    } else {\n      return inspector.fixDetachedParentFile(dryRun, fid, destinationPath, std::cout,\n                                             std::cerr);\n    }\n  }\n\n  if (fixShadowFileSubcommand->parsed()) {\n    return inspector.fixShadowFile(dryRun, fid, destinationPath, std::cout,\n                                   std::cerr);\n  }\n\n  if (dropFromDeathrow->parsed()) {\n    return inspector.dropFromDeathrow(dryRun, fid, std::cout, std::cerr);\n  }\n\n  if (dropEmptyCid->parsed()) {\n    return inspector.dropEmptyCid(dryRun, cid);\n  }\n\n  if (changeFidSubcommand->parsed()) {\n    return inspector.changeFid(dryRun, fid, newParent, newChecksum, newSize,\n                               newLayoutId, std::cout, std::cerr);\n  }\n\n  if (renameFidSubcommand->parsed()) {\n    return inspector.renameFid(dryRun, fid, newParent, newName, std::cout,\n                               std::cerr);\n  }\n\n  if (renameCidSubcommand->parsed()) {\n    return inspector.renameCid(dryRun, cid, newParent, newName, std::cout,\n                               std::cerr);\n  }\n\n  if (overwriteContainerSubcommand->parsed()) {\n    return inspector.overwriteContainerMD(dryRun, cid, parent, containerName,\n                                          std::cout, std::cerr);\n  }\n\n  std::cerr << \"No subcommand was supplied - should never reach here\" <<\n            std::endl;\n  return 1;\n}\n"
  },
  {
    "path": "namespace/ns_quarkdb/utils/FutureVectorIterator.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief  Helper class to consume a future vector of futures,\n//!         and make iteration sane\n//------------------------------------------------------------------------------\n\n#pragma once\n\n#include \"namespace/Namespace.hh\"\n#include <vector>\n#include <folly/futures/Future.h>\n\nEOSNSNAMESPACE_BEGIN\n\ntemplate<typename T>\nclass FutureVectorIterator\n{\nprivate:\n  using FutureT = folly::Future<T>;\n  using VectorT = std::vector<FutureT>;\n  using FutureVectorT = folly::Future<VectorT>;\n\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Destructor, wait for all futures to be fulfilled\n  //----------------------------------------------------------------------------\n  ~FutureVectorIterator()\n  {\n    waitAll();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Construtor, consumes future vector of futures\n  //----------------------------------------------------------------------------\n  FutureVectorIterator(folly::Future<std::vector<folly::Future<T>>>&& vec)\n    : mainFuture(std::move(vec)), futureVectorPopulated(false) { }\n\n  //----------------------------------------------------------------------------\n  //! Move assignment operator\n  //----------------------------------------------------------------------------\n  FutureVectorIterator<T>& operator=(FutureVectorT&& vec)\n  {\n    waitAll();\n    futureVector.clear();\n    futureVectorNext = 0;\n    futureVectorPopulated = false;\n    mainFuture = std::move(vec);\n    return *this;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Construtor, consumes concrete vector of futures\n  //----------------------------------------------------------------------------\n  FutureVectorIterator(std::vector<folly::Future<T>>&& vec)\n    : mainFuture(folly::makeFuture<VectorT>(VectorT())),\n      futureVectorPopulated(true), futureVector(std::move(vec)) {}\n\n  //----------------------------------------------------------------------------\n  //! Null construtor, everything is ready, and we're already at EOF\n  //----------------------------------------------------------------------------\n  FutureVectorIterator()\n    : FutureVectorIterator(std::vector<folly::Future<T>>()) {}\n\n  //----------------------------------------------------------------------------\n  //! Is the top-level future ready?\n  //----------------------------------------------------------------------------\n  bool isMainFutureReady() const\n  {\n    if (futureVectorPopulated) {\n      return true;\n    }\n\n    return mainFuture.isReady();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get vector size. The function will block if isMainFutureReady is false!\n  //----------------------------------------------------------------------------\n  size_t size()\n  {\n    processMainFuture();\n    return futureVector.size();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Is the next element ready to fetch?\n  //! If we've reached the end, the answer will always be \"yes\".\n  //----------------------------------------------------------------------------\n  bool isReady()\n  {\n    if (!futureVectorPopulated) {\n      //------------------------------------------------------------------------\n      //! Still waiting to process the top-level future?\n      //------------------------------------------------------------------------\n      if (!mainFuture.isReady()) {\n        return false;\n      }\n\n      processMainFuture();\n    }\n\n    //--------------------------------------------------------------------------\n    //! EOF?\n    //--------------------------------------------------------------------------\n    if (futureVectorNext >= futureVector.size()) {\n      return true;\n    }\n\n    return futureVector[futureVectorNext].isReady();\n  }\n\n  //----------------------------------------------------------------------------\n  //! Fetch next element.\n  //! - If we've reached EOF, return false. In this case, \"out\" is NOT updated.\n  //! - Else, return true. In this case, out IS updated, and you should call\n  //!\n  //! fetchNext will block if isReady is false!\n  //----------------------------------------------------------------------------\n  bool fetchNext(T& out)\n  {\n    processMainFuture();\n\n    if (futureVectorNext >= futureVector.size()) {\n      return false;\n    }\n\n    futureVectorNext++;\n    out = std::move(futureVector[futureVectorNext - 1]).get();\n    return true;\n  }\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Process mainFuture - block if necessary\n  //----------------------------------------------------------------------------\n  void processMainFuture()\n  {\n    if (futureVectorPopulated) {\n      return;\n    }\n\n    futureVector = std::move(mainFuture).get();\n    futureVectorPopulated = true;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Wait for and discard all results from the future vector\n  //----------------------------------------------------------------------------\n  void waitAll()\n  {\n    if (mainFuture.valid()) {\n      try {\n        processMainFuture();\n      } catch (eos::MDException& e) {\n        // ignore not found entry\n      }\n    }\n\n    if (futureVectorPopulated) {\n      for (; futureVectorNext < futureVector.size(); futureVectorNext++) {\n        try {\n          T&& x = std::move(futureVector[futureVectorNext]).get();\n          (void) x;\n        } catch (eos::MDException& e) {\n          // ignore not found entry\n        }\n      }\n    }\n  }\n\n  folly::Future<std::vector<folly::Future<T>>> mainFuture;\n  bool futureVectorPopulated = false;\n\n  std::vector<folly::Future<T>> futureVector;\n  size_t futureVectorNext = 0;\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/utils/QuotaRecomputer.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Utility class to recompute a quotanode\n//------------------------------------------------------------------------------\n\n#include \"namespace/ns_quarkdb/utils/QuotaRecomputer.hh\"\n#include \"namespace/ns_quarkdb/views/HierarchicalView.hh\"\n#include \"namespace/Constants.hh\"\n#include \"namespace/ns_quarkdb/explorer/NamespaceExplorer.hh\"\n#include \"common/LayoutId.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nQuotaRecomputer::QuotaRecomputer(qclient::QClient* qcl,\n                                 folly::Executor* exec)\n  : mQcl(qcl), mExecutor(exec) {}\n\n//------------------------------------------------------------------------------\n// Filtering class for NamespaceExplorer to ignore sub-quotanodes when\n// recomputing a quotanode.\n//------------------------------------------------------------------------------\nclass QuotaNodeFilter : public ExpansionDecider\n{\npublic:\n  QuotaNodeFilter(uint64_t root) : rootContainer(root) {}\n\n  virtual bool shouldExpandContainer(const eos::ns::ContainerMdProto& proto,\n                                     const eos::IContainerMD::XAttrMap& attrs, \n                                     const std::string& fullPath) override\n  {\n    if (proto.id() == rootContainer) {\n      return true; // always expand root, no matter what\n    }\n\n    if ((proto.flags() & eos::QUOTA_NODE_FLAG) == 0) {\n      return true; // not a quota node, continue\n    }\n\n    return false; // quota node, ignore\n  }\n\nprivate:\n  uint64_t rootContainer;\n};\n\n//------------------------------------------------------------------------------\n// Given a quotanode, re-calculate the quota values,\n// store into QuotaNodeCore.\n//------------------------------------------------------------------------------\nMDStatus QuotaRecomputer::recompute(const std::string& cont_uri,\n                                    const eos::IContainerMD::id_t cont_id,\n                                    QuotaNodeCore& qnc)\n{\n  // Reset qnc contents\n  qnc = {};\n\n  if (cont_id == 0ull) {\n    return MDStatus(EINVAL, \"error: requested computation for cid=0\");\n  }\n\n  ExplorationOptions options;\n  options.depthLimit = 2048;\n  options.expansionDecider.reset(new QuotaNodeFilter(cont_id));\n  NamespaceExplorer explorer(cont_uri, options, *mQcl, mExecutor);\n  NamespaceItem item;\n\n  while (explorer.fetch(item)) {\n    if (item.isFile) {\n      // Calculate physical size\n      uint64_t logicalSize = item.fileMd.size();\n      uint64_t physicalSize = item.fileMd.size() *\n                              eos::common::LayoutId::GetSizeFactor(item.fileMd.layout_id());\n      // Account file.\n      qnc.addFile(item.fileMd.uid(), item.fileMd.gid(), logicalSize,\n                  physicalSize);\n    }\n  }\n\n  return MDStatus(); // OK\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/utils/QuotaRecomputer.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Utility class to recompute a quotanode\n//------------------------------------------------------------------------------\n\n#pragma once\n\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/Namespace.hh\"\n#include \"namespace/MDException.hh\"\n\nnamespace qclient\n{\nclass QClient;\n}\n\nnamespace folly\n{\nclass Executor;\n}\n\nEOSNSNAMESPACE_BEGIN\n\nclass QuotaNodeCore;\nclass IView;\n\n//------------------------------------------------------------------------------\n//! Utility class to recompute a quotanode\n//------------------------------------------------------------------------------\nclass QuotaRecomputer\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  QuotaRecomputer(qclient::QClient* qcl, folly::Executor* exec);\n\n  //----------------------------------------------------------------------------\n  //! Given a quotanode, re-calculate the quota values,\n  //! store into QuotaNodeCore.\n  //!\n  //! @param cont_uri quota node container uri\n  //! @param cont_id quota node container id\n  //! @param core quota node object info\n  //!\n  //! @return status\n  //----------------------------------------------------------------------------\n  MDStatus recompute(const std::string& cont_uri,\n                     const eos::IContainerMD::id_t cont_id, QuotaNodeCore& core);\n\nprivate:\n  qclient::QClient* mQcl;\n  folly::Executor* mExecutor;\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/views/HierarchicalView.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"common/Assert.hh\"\n#include \"namespace/ns_quarkdb/views/HierarchicalView.hh\"\n#include \"namespace/Constants.hh\"\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"namespace/ns_quarkdb/persistency/ContainerMDSvc.hh\"\n#include \"namespace/utils/PathProcessor.hh\"\n#include <cerrno>\n#include <ctime>\n#include <functional>\n#include <folly/executors/IOThreadPoolExecutor.h>\n\nusing std::placeholders::_1;\n\n#ifdef __APPLE__\n#define EBADFD 77\n#endif\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nQuarkHierarchicalView::QuarkHierarchicalView(qclient::QClient* qcl,\n    MetadataFlusher* flusher)\n  : pQcl(qcl), pQuotaFlusher(flusher), pContainerSvc(nullptr), pFileSvc(nullptr),\n    pQuotaStats(new QuarkQuotaStats(pQcl, pQuotaFlusher)), pRoot(nullptr)\n{\n  pExecutor.reset(new folly::IOThreadPoolExecutor(32));\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nQuarkHierarchicalView::~QuarkHierarchicalView()\n{\n  delete pQuotaStats;\n}\n\n//------------------------------------------------------------------------------\n// Configure the view\n//------------------------------------------------------------------------------\nvoid\nQuarkHierarchicalView::configure(const std::map<std::string, std::string>&\n                                 config)\n{\n  if (pContainerSvc == nullptr) {\n    MDException e(EINVAL);\n    e.getMessage() << \"Container MD Service was not set\";\n    throw e;\n  }\n\n  if (pFileSvc == nullptr) {\n    MDException e(EINVAL);\n    e.getMessage() << \"File MD Service was not set\";\n    throw e;\n  }\n\n  delete pQuotaStats;\n  pQuotaStats = new QuarkQuotaStats(pQcl, pQuotaFlusher);\n  pQuotaStats->configure(config);\n}\n\n//------------------------------------------------------------------------------\n// Initialize the view\n//------------------------------------------------------------------------------\nvoid\nQuarkHierarchicalView::initialize()\n{\n  initialize1();\n  initialize2();\n  initialize3();\n}\n\nvoid\nQuarkHierarchicalView::initialize1()\n{\n  pContainerSvc->initialize();\n\n  // Get root container\n  try {\n    pRoot = pContainerSvc->getContainerMD(1);\n  } catch (MDException& e) {\n    pRoot = pContainerSvc->createContainer(0);\n\n    if (pRoot->getId() != 1) {\n      eos_static_crit(\"Error when creating root '/' path - directory inode is not 1, but %d!\",\n                      pRoot->getId());\n      std::quick_exit(1);\n    }\n\n    pRoot->setName(\"/\");\n    pRoot->setParentId(pRoot->getId());\n    updateContainerStore(pRoot.get());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Initialize phase 2\n//------------------------------------------------------------------------------\nvoid\nQuarkHierarchicalView::initialize2()\n{\n  pFileSvc->initialize();\n}\n\n//------------------------------------------------------------------------------\n// Initialize phase 3\n//------------------------------------------------------------------------------\nvoid\nQuarkHierarchicalView::initialize3()\n{\n  //--------------------------------------------------------------------------\n  // Scan all the files to reattach them to containers - THIS SHOULD NOT\n  // BE DONE! THE INFO NEEDS TO BE STORED WITH CONTAINERS\n  //--------------------------------------------------------------------------\n  // FileVisitor visitor( pContainerSvc, pQuotaStats, this );\n  // pFileSvc->visit( &visitor );\n}\n\n//------------------------------------------------------------------------------\n// Finalize the view\n//------------------------------------------------------------------------------\nvoid\nQuarkHierarchicalView::finalize()\n{\n  pContainerSvc->finalize();\n  pFileSvc->finalize();\n  delete pQuotaStats;\n  pQuotaStats = nullptr;\n}\n\n//------------------------------------------------------------------------------\n// Extract FileMDPtr out of FileOrContainerMD.\n//------------------------------------------------------------------------------\nstatic folly::Future<IFileMDPtr> extractFileMD(FileOrContainerMD ptr)\n{\n  if (!ptr.file) {\n    return folly::makeFuture<IFileMDPtr>(make_mdexception(ENOENT,\n                                         \"No such file or directory\"));\n  }\n\n  return ptr.file;\n}\n\n//------------------------------------------------------------------------------\n// Extract ContainerMDPtr out of FileOrContainerMD.\n//------------------------------------------------------------------------------\nstatic folly::Future<IContainerMDPtr> extractContainerMD(FileOrContainerMD ptr)\n{\n  if (!ptr.container) {\n    return folly::makeFuture<IContainerMDPtr>(make_mdexception(ENOENT,\n           \"No such file or directory\"));\n  }\n\n  return ptr.container;\n}\n\n//------------------------------------------------------------------------------\n// Lookup a given path.\n//------------------------------------------------------------------------------\nfolly::Future<FileOrContainerMD>\nQuarkHierarchicalView::getItem(const std::string& uri, bool follow)\n{\n  //----------------------------------------------------------------------------\n  // Build our deque of pending chunks...\n  //----------------------------------------------------------------------------\n  std::deque<std::string> pendingChunks =\n    eos::PathProcessor::insertChunksIntoDeque(uri);\n  //----------------------------------------------------------------------------\n  // Initial state: We're at \"/\", and have to look up all chunks.\n  //----------------------------------------------------------------------------\n  FileOrContainerMD initialState {nullptr, pRoot};\n  return getPathInternal(initialState, pendingChunks, follow, 0);\n}\n\n//------------------------------------------------------------------------------\n// Convert a ContainerMDPtr to FileOrContainerMD.\n//------------------------------------------------------------------------------\nstatic FileOrContainerMD toFileOrContainerMD(IContainerMDPtr ptr)\n{\n  return {nullptr, ptr};\n}\n\n//------------------------------------------------------------------------------\n// Lookup a given path - deferred function.\n//------------------------------------------------------------------------------\nfolly::Future<FileOrContainerMD>\nQuarkHierarchicalView::getPathDeferred(folly::Future<FileOrContainerMD> fut,\n                                       std::deque<std::string> pendingChunks,\n                                       bool follow, size_t expendedEffort)\n{\n  //----------------------------------------------------------------------------\n  // We're blocked on a network request. \"Pause\" execution of getPathInternal\n  // for now, return a pending folly::Future to caller.\n  //\n  // The Executor pool will \"resume\" computation later, once the network\n  // request is completed.\n  //----------------------------------------------------------------------------\n  return fut.via(pExecutor.get())\n         .thenValue(std::bind(&QuarkHierarchicalView::getPathInternal, this, _1,\n                              pendingChunks,\n                              follow, expendedEffort));\n}\n\n//------------------------------------------------------------------------------\n// Lookup a given path - deferred function.\n//------------------------------------------------------------------------------\nfolly::Future<FileOrContainerMD>\nQuarkHierarchicalView::getPathDeferred(folly::Future<IContainerMDPtr> fut,\n                                       std::deque<std::string> pendingChunks,\n                                       bool follow, size_t expendedEffort)\n{\n  //----------------------------------------------------------------------------\n  // Same as getPathDeferred taking FileOrContainerMD.\n  //----------------------------------------------------------------------------\n  return fut.via(pExecutor.get())\n         .thenValue(toFileOrContainerMD)\n         .thenValue(std::bind(&QuarkHierarchicalView::getPathInternal, this, _1,\n                              pendingChunks,\n                              follow, expendedEffort));\n}\n\n//------------------------------------------------------------------------------\n// Lookup a given path - internal function.\n//------------------------------------------------------------------------------\nfolly::Future<FileOrContainerMD>\nQuarkHierarchicalView::getPathInternal(FileOrContainerMD state,\n                                       std::deque<std::string> pendingChunks,\n                                       bool follow, size_t expendedEffort)\n{\n  //----------------------------------------------------------------------------\n  // Our goal is to consume pendingChunks until it's empty.\n  // If everything we need is in memory, we keep executing.\n  //\n  // However, if a network request is necessary to continue lookup, we bail,\n  // and do it asynchronously. Execution of the function will \"resume\" as soon\n  // as the network request is complete.\n  //----------------------------------------------------------------------------\n  while (true) {\n    //--------------------------------------------------------------------------\n    // Protection against symbolic link loops.\n    //--------------------------------------------------------------------------\n    expendedEffort++;\n\n    if (expendedEffort > 255) {\n      return folly::makeFuture<FileOrContainerMD>(make_mdexception(\n               ELOOP, \"Too many symbolic links were encountered in translating the pathname\"));\n    }\n\n    if (!state.container && !state.file) {\n      //------------------------------------------------------------------------\n      // The previous iteration of the loop resulted in an empty state: Only one\n      // way to get here, looking up a non-existent chunk.\n      //------------------------------------------------------------------------\n      return folly::makeFuture<FileOrContainerMD>(make_mdexception(ENOENT,\n             \"No such file or directory\"));\n    }\n\n    if (pendingChunks.empty()) {\n      if (!(follow && state.file && state.file->isLink())) {\n        //------------------------------------------------------------------------\n        // We're done. Our current state contains the desired output.\n        //------------------------------------------------------------------------\n        return state;\n      } else {\n        //----------------------------------------------------------------------\n        // Edge case: State is actually a symlink we must follow, not done yet.\n        //----------------------------------------------------------------------\n      }\n    }\n\n    if (state.container) {\n      //------------------------------------------------------------------------\n      // Handle special cases, \".\" and \"..\"\n      //------------------------------------------------------------------------\n      if (pendingChunks.front() == \".\") {\n        pendingChunks.pop_front();\n        continue;\n      }\n\n      if (pendingChunks.front() == \"..\") {\n        pendingChunks.pop_front();\n        folly::Future<IContainerMDPtr> fut = pContainerSvc->getContainerMDFut(\n                                               state.container->getParentId());\n\n        if (!fut.isReady() || fut.hasException()) {\n          //--------------------------------------------------------------------\n          // We're blocked, \"pause\" execution, unblock caller.\n          //--------------------------------------------------------------------\n          return getPathDeferred(std::move(fut), pendingChunks, follow, expendedEffort);\n        }\n\n        state.container = std::move(fut).get();\n        continue;\n      }\n\n      //------------------------------------------------------------------------\n      // Normal case: Our current state contains a container, and we're simply\n      // looking up the next chunk.\n      //------------------------------------------------------------------------\n      folly::Future<FileOrContainerMD> next = state.container->findItem(\n          pendingChunks.front());\n      pendingChunks.pop_front();\n\n      //------------------------------------------------------------------------\n      // If we're lucky, the result is ready immediately. Update state, and\n      // carry on.\n      //------------------------------------------------------------------------\n      if (next.isReady() && !next.hasException()) {\n        state = std::move(next).get();\n        continue;\n      } else {\n        //----------------------------------------------------------------------\n        // We're blocked, \"pause\" execution, unblock caller.\n        //----------------------------------------------------------------------\n        return getPathDeferred(std::move(next), pendingChunks, follow, expendedEffort);\n      }\n    }\n\n    if (state.file) {\n      //------------------------------------------------------------------------\n      // This is unusual.. How come a file came up in the middle of a path\n      // lookup?\n      //\n      // 1. We've hit a symlink.\n      // 2. Caller is drunk, and doing \"ls /eos/dir1/file1/not/existing\".\n      //------------------------------------------------------------------------\n      if (!state.file->isLink()) {\n        return folly::makeFuture<FileOrContainerMD>(make_mdexception(ENOTDIR,\n               \"Not a directory\"));\n      }\n\n      //------------------------------------------------------------------------\n      // Ok, this is definitely a symlink. Should we follow it?\n      //------------------------------------------------------------------------\n      if (pendingChunks.size() == 0u && !follow) {\n        //----------------------------------------------------------------------\n        // Nope, we're interested in the symlink itself, we're done.\n        //----------------------------------------------------------------------\n        return state;\n      }\n\n      //------------------------------------------------------------------------\n      // Populate our pendingChunks with the updated target.\n      //------------------------------------------------------------------------\n      const std::string& symlinkTarget = state.file->getLink();\n      eos::PathProcessor::insertChunksIntoDeque(pendingChunks, symlinkTarget);\n\n      if (!symlinkTarget.empty() && symlinkTarget[0] == '/') {\n        //----------------------------------------------------------------------\n        // This is an absolute symlink: Our state becomes pRoot again.\n        //----------------------------------------------------------------------\n        state = FileOrContainerMD {nullptr, pRoot};\n      } else {\n        //----------------------------------------------------------------------\n        // This is a relative symlink: State becomes symlink's parent container.\n        //----------------------------------------------------------------------\n        folly::Future<IContainerMDPtr> fut = pContainerSvc->getContainerMDFut(\n                                               state.file->getContainerId());\n\n        if (!fut.isReady() || fut.hasException()) {\n          //--------------------------------------------------------------------\n          // We're blocked, \"pause\" execution, unblock caller.\n          //--------------------------------------------------------------------\n          return getPathDeferred(std::move(fut), pendingChunks, follow, expendedEffort);\n        }\n\n        state.container = std::move(fut).get();\n        state.file.reset();\n      }\n\n      continue;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Retrieve a file for given uri, asynchronously\n//------------------------------------------------------------------------------\nfolly::Future<IFileMDPtr>\nQuarkHierarchicalView::getFileFut(const std::string& uri, bool follow)\n{\n  return getItem(uri, follow).thenValue(extractFileMD);\n}\n\n//------------------------------------------------------------------------------\n// Retrieve a file for given uri\n//------------------------------------------------------------------------------\nstd::shared_ptr<IFileMD>\nQuarkHierarchicalView::getFile(const std::string& uri, bool follow,\n                               size_t* link_depths)\n{\n  return getFileFut(uri, follow).get();\n}\n\n//------------------------------------------------------------------------------\n// Create a file for given uri\n//------------------------------------------------------------------------------\nstd::shared_ptr<IFileMD>\nQuarkHierarchicalView::createFile(const std::string& uri, uid_t uid, gid_t gid,\n                                  IFileMD::id_t id)\n{\n  if (uri == \"/\") {\n    throw_mdexception(EEXIST, \"File exists\");\n  }\n\n  // Split the path and find the last container\n  std::deque<std::string> chunks = eos::PathProcessor::insertChunksIntoDeque(uri);\n\n  if (chunks.size() == 0u) {\n    throw_mdexception(EEXIST, \"File exists\");\n  }\n\n  std::string lastChunk = chunks.back();\n  chunks.pop_back();\n  FileOrContainerMD item = getPathInternal(FileOrContainerMD {nullptr, pRoot},\n                           chunks, true, 0).get();\n\n  if (item.file) {\n    throw_mdexception(ENOTDIR, \"Not a directory\");\n  }\n\n  IContainerMDPtr parent = item.container;\n  FileOrContainerMD potentialConflict = parent->findItem(lastChunk).get();\n\n  if (potentialConflict.file || potentialConflict.container) {\n    throw_mdexception(EEXIST, \"File exists\");\n  }\n\n  IFileMDPtr file = pFileSvc->createFile(id);\n\n  if (!file) {\n    eos_static_crit(\"File creation failed for %s\", uri.c_str());\n    throw_mdexception(EIO, \"File creation failed\");\n  }\n\n  file->setName(lastChunk);\n  file->setCUid(uid);\n  file->setCGid(gid);\n  file->setCTimeNow();\n  file->setATimeNow(0);\n  file->setMTimeNow();\n  file->clearChecksum(0);\n  parent->addFile(file.get());\n  updateFileStore(file.get());\n  return file;\n}\n\n//------------------------------------------------------------------------\n//! Create a link for given uri\n//------------------------------------------------------------------------\nvoid\nQuarkHierarchicalView::createLink(const std::string& uri,\n                                  const std::string& linkuri,\n                                  uid_t uid, gid_t gid)\n{\n  std::shared_ptr<IFileMD> file = createFile(uri, uid, gid);\n\n  if (file) {\n    file->setLink(linkuri);\n    file->setSize(linkuri.length());\n    updateFileStore(file.get());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Remove link\n//------------------------------------------------------------------------------\nvoid\nQuarkHierarchicalView::removeLink(const std::string& uri)\n{\n  return unlinkFile(uri);\n}\n\n//------------------------------------------------------------------------------\n// Unlink the file for given uri\n//------------------------------------------------------------------------------\nvoid\nQuarkHierarchicalView::unlinkFile(const std::string& uri)\n{\n  std::deque<std::string> chunks = eos::PathProcessor::insertChunksIntoDeque(uri);\n\n  if (chunks.size() == 0) {\n    MDException e(ENOENT);\n    e.getMessage() << \"Not a file\";\n    throw e;\n  }\n\n  std::string lastChunk = chunks[chunks.size() - 1];\n  chunks.pop_back();\n  IContainerMDPtr parent = getPathExpectContainer(chunks).get();\n  std::shared_ptr<IFileMD> file = parent->findFile(lastChunk);\n\n  if (!file) {\n    MDException e(ENOENT);\n    e.getMessage() << \"File does not exist\";\n    throw e;\n  }\n\n  unlinkFile(file.get());\n}\n\n//------------------------------------------------------------------------------\n// Unlink the file - this is only used for testing\n//------------------------------------------------------------------------------\nvoid\nQuarkHierarchicalView::unlinkFile(eos::IFileMD* file)\n{\n  std::shared_ptr<IContainerMD> cont =\n    pContainerSvc->getContainerMD(file->getContainerId());\n  file->setContainerId(0);\n  file->unlinkAllLocations();\n  cont->removeFile(file->getName());\n  updateFileStore(file);\n}\n\n//------------------------------------------------------------------------------\n// Remove the file\n//------------------------------------------------------------------------------\nvoid\nQuarkHierarchicalView::removeFile(IFileMD* file)\n{\n  // Check if the file can be removed\n  if (file->getNumLocation() != 0 || file->getNumUnlinkedLocation() != 0) {\n    MDException ex(EBADFD);\n    ex.getMessage() << \"Cannot remove the record. Unlinked replicas \";\n    ex.getMessage() << \"still exist\";\n    throw ex;\n  }\n\n  if (file->getContainerId() != 0) {\n    std::shared_ptr<IContainerMD> cont =\n      pContainerSvc->getContainerMD(file->getContainerId());\n    cont->removeFile(file->getName());\n  }\n\n  pFileSvc->removeFile(file);\n}\n\n//------------------------------------------------------------------------------\n// Get a container (directory) asynchronously\n//------------------------------------------------------------------------------\nfolly::Future<IContainerMDPtr>\nQuarkHierarchicalView::getContainerFut(const std::string& uri, bool follow)\n{\n  if (uri == \"/\") {\n    return std::shared_ptr<IContainerMD> {pContainerSvc->getContainerMD(1)};\n  }\n\n  return getItem(uri, follow).thenValue(extractContainerMD);\n}\n\n//------------------------------------------------------------------------------\n// Get a container (directory)\n//------------------------------------------------------------------------------\nIContainerMDPtr\nQuarkHierarchicalView::getContainer(const std::string& uri, bool follow,\n                                    size_t* link_depth)\n{\n  return getContainerFut(uri, follow).get();\n}\n\n//------------------------------------------------------------------------------\n// UpdateStoreGuard helper class\n//------------------------------------------------------------------------------\nclass UpdateStoreGuard\n{\npublic:\n  UpdateStoreGuard(eos::QuarkHierarchicalView* v) : view(v) {}\n\n  ~UpdateStoreGuard()\n  {\n    for (auto it = ptrs.begin(); it != ptrs.end(); it++) {\n      view->updateContainerStore((*it).get());\n    }\n  }\n\n  void add(IContainerMDPtr cont)\n  {\n    ptrs.insert(cont);\n  }\n\nprivate:\n  eos::QuarkHierarchicalView* view;\n  std::set<IContainerMDPtr> ptrs;\n};\n\n//------------------------------------------------------------------------------\n// Create container - method eventually consistent\n//------------------------------------------------------------------------------\nstd::shared_ptr<IContainerMD>\nQuarkHierarchicalView::createContainer(const std::string& uri,\n                                       bool createParents, uint64_t cid)\n{\n  // Split the path\n  if (uri == \"/\") {\n    throw_mdexception(EEXIST, uri << \": Container exists\");\n  }\n\n  std::deque<std::string> chunks = eos::PathProcessor::insertChunksIntoDeque(uri);\n\n  if (chunks.empty()) {\n    throw_mdexception(EEXIST, uri << \": File exists\");\n  }\n\n  // Resolve path chunks one by one\n  FileOrContainerMD state = {nullptr, pRoot};\n  UpdateStoreGuard updateGuard(this);\n  bool created = false;\n\n  while (true) {\n    if (state.file) {\n      throw_mdexception(ENOTDIR, uri << \": Not a directory\");\n    }\n\n    if (!state.container) {\n      throw_mdexception(ENOENT, uri << \": No such file or directory\");\n    }\n\n    if (chunks.empty()) {\n      if (created) {\n        return state.container;\n      } else {\n        throw_mdexception(EEXIST, uri << \": Container exists\");\n      }\n    }\n\n    std::string nextChunk = chunks.front();\n    std::deque<std::string> nextChunkDeque { nextChunk }; // yes, this is stupid\n    chunks.pop_front();\n\n    // Lookup next chunk ..\n    try {\n      state = getPathInternal(state, nextChunkDeque, true, 0).get();\n    } catch (const eos::MDException& e) {\n      if (e.getErrno() != ENOENT) {\n        // Something's wrong, rethrow\n        throw;\n      }\n\n      if (!createParents && !chunks.empty()) {\n        throw_mdexception(ENOENT, uri << \": No such file or directory\");\n      }\n\n      // Wait.. what if \"ENOENT\" is actually due to failed symlink lookup?\n      // We'd screw up namespace consistency if we attempt to add a container\n      // with the same name as the broken symlink.\n      FileOrContainerMD item = state.container->findItem(nextChunk).get();\n\n      if (item.file || item.container) {\n        throw_mdexception(ENOTDIR, uri << \": Not a directory\");\n      }\n\n      IContainerMDPtr newContainer = pContainerSvc->createContainer(\n                                       chunks.empty() ? cid : 0);\n      newContainer->setName(nextChunk);\n      newContainer->setCTimeNow();\n      state.container->addContainer(newContainer.get());\n      updateGuard.add(state.container);\n      updateGuard.add(newContainer);\n      state.container = newContainer;\n      created = true;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Lookup a given path, expect a container there.\n//------------------------------------------------------------------------------\nfolly::Future<IContainerMDPtr>\nQuarkHierarchicalView::getPathExpectContainer(const std::deque<std::string>&\n    chunks)\n{\n  if (chunks.size() == 0u) {\n    return pRoot;\n  }\n\n  return getPathInternal(FileOrContainerMD {nullptr, pRoot}, chunks, true, 0)\n         .thenValue(extractContainerMD);\n}\n\n//------------------------------------------------------------------------------\n// Remove container\n//------------------------------------------------------------------------------\nvoid\nQuarkHierarchicalView::removeContainer(const std::string& uri)\n{\n  // Find the container\n  if (uri == \"/\") {\n    MDException e(EPERM);\n    e.getMessage() << \"Permission denied.\";\n    throw e;\n  }\n\n  //----------------------------------------------------------------------------\n  // Lookup last container\n  //----------------------------------------------------------------------------\n  std::deque<std::string> chunks = eos::PathProcessor::insertChunksIntoDeque(uri);\n  eos_assert(chunks.size() != 0);\n  std::string lastChunk = chunks[chunks.size() - 1];\n  chunks.pop_back();\n  IContainerMDPtr parent = getPathExpectContainer(chunks).get();\n  // Check if the container exist and remove it\n  auto cont = parent->findContainer(lastChunk);\n\n  if (!cont) {\n    MDException e(ENOENT);\n    e.getMessage() << uri << \": No such file or directory\";\n    throw e;\n  }\n\n  if (cont->getNumContainers() != 0 || cont->getNumFiles() != 0) {\n    MDException e(ENOTEMPTY);\n    e.getMessage() << uri << \": Container is not empty\";\n    throw e;\n  }\n\n  // This is a two-step delete\n  pContainerSvc->removeContainer(cont.get());\n  parent->removeContainer(cont->getName());\n}\n\n//------------------------------------------------------------------------------\n// Concatenate a deque of chunks into a string\n//------------------------------------------------------------------------------\nstatic std::string concatenateDeque(std::deque<std::string>&& chunks)\n{\n  std::ostringstream ss;\n\n  for (size_t i = 0; i < chunks.size(); i++) {\n    ss << \"/\" << chunks[i];\n  }\n\n  return ss.str();\n}\n\n//------------------------------------------------------------------------------\n// Concatenate a deque of chunks into a string, with an ending slash\n//------------------------------------------------------------------------------\nstatic std::string concatenateDequeWithEndingSlash(std::deque<std::string>&&\n    chunks)\n{\n  std::ostringstream ss;\n\n  for (size_t i = 0; i < chunks.size(); i++) {\n    ss << \"/\" << chunks[i];\n  }\n\n  ss << \"/\";\n  return ss.str();\n}\n\n//------------------------------------------------------------------------------\n// Get uri for the container\n//------------------------------------------------------------------------------\nstd::string\nQuarkHierarchicalView::getUri(const IContainerMD* container) const\n{\n  // Check the input\n  if (container == nullptr) {\n    MDException ex;\n    ex.getMessage() << \"Invalid container (zero pointer)\";\n    throw ex;\n  }\n\n  return getUriFut(container->getIdentifier()).get();\n}\n\n//------------------------------------------------------------------------------\n// Get uri for the container - asynchronous version\n//------------------------------------------------------------------------------\nfolly::Future<std::string>\nQuarkHierarchicalView::getUriFut(ContainerIdentifier id) const\n{\n  return getUriInternalCid({}, id)\n         .thenValue(concatenateDequeWithEndingSlash);\n}\n\n//------------------------------------------------------------------------------\n// Build the URL of the given container, as a deque of chunks. Primary\n// \"resumable\" function.\n//------------------------------------------------------------------------------\nfolly::Future<std::deque<std::string>>\n                                    QuarkHierarchicalView::getUriInternal(std::deque<std::string> currentChunks,\n                                        IContainerMDPtr nextToLookup) const\n{\n  while (true) {\n    //--------------------------------------------------------------------------\n    // Null nextToLookup with an empty deque? ENOENT\n    //--------------------------------------------------------------------------\n    if (!nextToLookup && currentChunks.empty()) {\n      return folly::makeFuture<std::deque<std::string>>(\n               make_mdexception(ENOENT, \"No such file or directory\"));\n    }\n\n    //--------------------------------------------------------------------------\n    // Null nextToLookup with non-empty deque? Huh.. that shouldn't happen.\n    //--------------------------------------------------------------------------\n    if (!nextToLookup) {\n      std::string err =\n        SSTR(\"Potential namespace corruption, received null nextToLookup in getUri. \" <<\n             \"Current state: \" << concatenateDeque(std::move(currentChunks)));\n      eos_static_crit(err.c_str());\n      return folly::makeFuture<std::deque<std::string>>(make_mdexception(EFAULT,\n             err.c_str()));\n    }\n\n    //--------------------------------------------------------------------------\n    // Reached the end?\n    //--------------------------------------------------------------------------\n    if (nextToLookup->getIdentifier()  == ContainerIdentifier(1)) {\n      return std::move(currentChunks);\n    }\n\n    //--------------------------------------------------------------------------\n    // Potential cycle?\n    //--------------------------------------------------------------------------\n    if (currentChunks.size() > 255) {\n      std::string err =\n        SSTR(\"Potential namespace corruption, detected loop in getUri. Current container: \"\n             << nextToLookup->getId() << \", current state: \" << concatenateDeque(std::move(\n                   currentChunks)));\n      eos_static_crit(err.c_str());\n      return folly::makeFuture<std::deque<std::string>>(make_mdexception(EFAULT,\n             err.c_str()));\n    }\n\n    //--------------------------------------------------------------------------\n    // Add nextToLookup's name into the deque..\n    //--------------------------------------------------------------------------\n    currentChunks.emplace_front(nextToLookup->getName());\n    //--------------------------------------------------------------------------\n    // Lookup parent chunk..\n    //--------------------------------------------------------------------------\n    folly::Future<IContainerMDPtr> pending = pContainerSvc->getContainerMDFut(\n          nextToLookup->getParentId());\n\n    if (pending.isReady()) {\n      //------------------------------------------------------------------------\n      // Cache hit, carry on.\n      //------------------------------------------------------------------------\n      nextToLookup = std::move(pending).get();\n      continue;\n    }\n\n    //--------------------------------------------------------------------------\n    // Cache miss, \"pause\" execution until we receive the necessary metadata\n    // from QDB.\n    //--------------------------------------------------------------------------\n    return pending.via(pExecutor.get())\n           .thenValue(std::bind(&QuarkHierarchicalView::getUriInternal, this,\n                                std::move(currentChunks), _1));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Build the URL of the given container ID.\n//------------------------------------------------------------------------------\nfolly::Future<std::deque<std::string>>\n                                    QuarkHierarchicalView::getUriInternalCid(std::deque<std::string> currentChunks,\n                                        ContainerIdentifier cid) const\n{\n  folly::Future<IContainerMDPtr> pending = pContainerSvc->getContainerMDFut(\n        cid.getUnderlyingUInt64());\n\n  if (pending.isReady() && !pending.hasException()) {\n    //--------------------------------------------------------------------------\n    // Cache hit\n    //--------------------------------------------------------------------------\n    return getUriInternal(std::move(currentChunks), std::move(pending).get());\n  }\n\n  //----------------------------------------------------------------------------\n  // Pause execution, give back future.\n  //----------------------------------------------------------------------------\n  return pending.via(pExecutor.get())\n         .thenValue(std::bind(&QuarkHierarchicalView::getUriInternal, this,\n                              std::move(currentChunks), _1));\n}\n\n//------------------------------------------------------------------------------\n// Build the URL of the given file, as a deque of chunks.\n//------------------------------------------------------------------------------\nfolly::Future<std::deque<std::string>>\n                                    QuarkHierarchicalView::getUriInternalFmd(const IFileMD* fmd) const\n{\n  if (!fmd) {\n    //--------------------------------------------------------------------------\n    // ENOENT\n    //--------------------------------------------------------------------------\n    return folly::makeFuture<std::deque<std::string>>(\n             make_mdexception(ENOENT, \"No such file or directory\"));\n  }\n\n  std::deque<std::string> chunks;\n  chunks.emplace_front(fmd->getName());\n  return getUriInternalCid(chunks, ContainerIdentifier(fmd->getContainerId()));\n}\n\n//------------------------------------------------------------------------------\n// Build the URL of the given file, as a deque of chunks.\n//------------------------------------------------------------------------------\nfolly::Future<std::deque<std::string>>\n                                    QuarkHierarchicalView::getUriInternalFmdPtr(IFileMDPtr fmd) const\n{\n  return getUriInternalFmd(fmd.get());\n}\n\n//------------------------------------------------------------------------------\n// Build the URL of the given fid\n//------------------------------------------------------------------------------\nfolly::Future<std::deque<std::string>>\n                                    QuarkHierarchicalView::getUriInternalFid(FileIdentifier fid) const\n{\n  folly::Future<IFileMDPtr> pending = pFileSvc->getFileMDFut(\n                                        fid.getUnderlyingUInt64());\n\n  if (pending.isReady() && !pending.hasException()) {\n    //--------------------------------------------------------------------------\n    // Cache hit\n    //--------------------------------------------------------------------------\n    return getUriInternalFmdPtr(std::move(pending).get());\n  }\n\n  //----------------------------------------------------------------------------\n  // Pause execution, give back future.\n  //----------------------------------------------------------------------------\n  return pending.via(pExecutor.get())\n         .thenValue(std::bind(&QuarkHierarchicalView::getUriInternalFmdPtr, this, _1));\n}\n\n//------------------------------------------------------------------------------\n// Get uri for container id\n//------------------------------------------------------------------------------\nstd::string\nQuarkHierarchicalView::getUri(const IContainerMD::id_t cid) const\n{\n  return getUriFut(ContainerIdentifier(cid)).get();\n}\n\n//------------------------------------------------------------------------------\n// Get uri for the container - asynchronous version\n//------------------------------------------------------------------------------\nfolly::Future<std::string>\nQuarkHierarchicalView::getUriFut(FileIdentifier id) const\n{\n  return getUriInternalFid(id)\n         .thenValue(concatenateDeque);\n}\n\n//----------------------------------------------------------------------------\n//! Get uri for the file\n//! CAUTION: DO NOT LOCK THE FILE IN PARAMETER BEFORE CALLING THIS IN THE RISK\n//! OF CREATING DEADLOCKS !\n//! Explanation: if the file is already locked, the underlying logic will also lock the parent container\n//! which is against the locking order that needs to be respected: lock(Container), then lock(File)\n//----------------------------------------------------------------------------\nstd::string\nQuarkHierarchicalView::getUri(const IFileMD* file) const\n{\n  return getUriInternalFmd(file)\n         .thenValue(concatenateDeque)\n         .get();\n}\n\n//------------------------------------------------------------------------\n// Get real path translating existing symlink\n//------------------------------------------------------------------------\nstd::string QuarkHierarchicalView::getRealPath(const std::string& uri)\n{\n  if (uri == \"/\") {\n    MDException e(ENOENT);\n    e.getMessage() << \" is not a file\";\n    throw e;\n  }\n\n  std::deque<std::string> chunks = eos::PathProcessor::insertChunksIntoDeque(uri);\n  eos_assert(chunks.size() != 0);\n\n  if (chunks.size() == 1) {\n    return chunks[0];\n  }\n\n  //----------------------------------------------------------------------------\n  // Remove last chunk\n  //----------------------------------------------------------------------------\n  std::string lastChunk = chunks[chunks.size() - 1];\n  chunks.pop_back();\n  //----------------------------------------------------------------------------\n  // Lookup parent container..\n  //----------------------------------------------------------------------------\n  IContainerMDPtr cont = getPathExpectContainer(chunks).get();\n  return SSTR(getUri(cont.get()) << lastChunk);\n}\n\n//------------------------------------------------------------------------------\n// Get quota node id concerning given container\n//------------------------------------------------------------------------------\nIQuotaNode*\nQuarkHierarchicalView::getQuotaNode(const IContainerMD* container, bool search)\n{\n  // Initial sanity check\n  if (container == nullptr) {\n    MDException ex;\n    ex.getMessage() << \"Invalid container (zero pointer)\";\n    throw ex;\n  }\n\n  if (pQuotaStats == nullptr) {\n    MDException ex;\n    ex.getMessage() << \"No QuotaStats placeholder registered\";\n    throw ex;\n  }\n\n  std::shared_ptr<IContainerMD> current;\n\n  // Search for the node\n  try {\n    current = pContainerSvc->getContainerMD(container->getId());\n\n    if (search) {\n      while (current->getName() != pRoot->getName() &&\n             (current->getFlags() & QUOTA_NODE_FLAG) == 0) {\n        current = pContainerSvc->getContainerMD(current->getParentId());\n      }\n    }\n  } catch (...) {\n    eos_static_crit(\"Attempted to get quota node of possibly detached container with cid=%llu\",\n                    container->getId());\n    return nullptr;\n  }\n\n  // We have either found a quota node or reached root without finding one\n  // so we need to double check whether the current container has an\n  // associated quota node\n  if ((current->getFlags() & QUOTA_NODE_FLAG) == 0) {\n    return nullptr;\n  }\n\n  IQuotaNode* node = pQuotaStats->getQuotaNode(current->getId());\n\n  if (node != nullptr) {\n    return node;\n  }\n\n  return pQuotaStats->registerNewNode(current->getId());\n}\n\n//------------------------------------------------------------------------------\n// Register the container to be a quota node\n//------------------------------------------------------------------------------\nIQuotaNode*\nQuarkHierarchicalView::registerQuotaNode(IContainerMD* container)\n{\n  // Initial sanity check\n  if (container == nullptr) {\n    MDException ex;\n    ex.getMessage() << \"Invalid container (zero pointer)\";\n    throw ex;\n  }\n\n  if (pQuotaStats == nullptr) {\n    MDException ex;\n    ex.getMessage() << \"No QuotaStats placeholder registered\";\n    throw ex;\n  }\n\n  if ((container->getFlags() & QUOTA_NODE_FLAG) != 0) {\n    MDException ex;\n    ex.getMessage() << \"Already a quota node: \" << container->getId();\n    throw ex;\n  }\n\n  IQuotaNode* node = pQuotaStats->registerNewNode(container->getId());\n  container->setFlags(container->getFlags() | QUOTA_NODE_FLAG);\n  updateContainerStore(container);\n  return node;\n}\n\n//------------------------------------------------------------------------------\n// Remove the quota node\n//------------------------------------------------------------------------------\nvoid\nQuarkHierarchicalView::removeQuotaNode(IContainerMD* container)\n{\n  // Sanity checks\n  if (container == nullptr) {\n    MDException ex;\n    ex.getMessage() << \"Invalid container (zero pointer)\";\n    throw ex;\n  }\n\n  if (pQuotaStats == nullptr) {\n    MDException ex;\n    ex.getMessage() << \"No QuotaStats placeholder registered\";\n    throw ex;\n  }\n\n  if ((container->getFlags() & QUOTA_NODE_FLAG) == 0) {\n    MDException ex;\n    ex.getMessage() << \"Not a quota node: \" << container->getId();\n    throw ex;\n  }\n\n  // Get the quota node and meld it with the parent node if present\n  IQuotaNode* node = getQuotaNode(container);\n  IQuotaNode* parent = nullptr;\n\n  if (container->getId() != 1) {\n    parent = getQuotaNode(\n               pContainerSvc->getContainerMD(container->getParentId()).get(), true);\n  }\n\n  container->setFlags(container->getFlags() & ~QUOTA_NODE_FLAG);\n  updateContainerStore(container);\n\n  if (parent != nullptr) {\n    try {\n      parent->meld(node);\n    } catch (const std::runtime_error& e) {\n      MDException ex;\n      ex.getMessage() << \"Failed quota node meld: \" << e.what();\n      throw ex;\n    }\n  }\n\n  pQuotaStats->removeNode(container->getId());\n}\n\n//------------------------------------------------------------------------------\n// Rename container\n// The container and its parent must be write-locked\n//------------------------------------------------------------------------------\nvoid\nQuarkHierarchicalView::renameContainer(IContainerMD* container,\n                                       const std::string& newName)\n{\n  if (container == nullptr) {\n    MDException ex;\n    ex.getMessage() << \"Invalid container (zero pointer)\";\n    throw ex;\n  }\n\n  if (newName.empty()) {\n    MDException ex;\n    ex.getMessage() << \"Invalid new name (empty)\";\n    throw ex;\n  }\n\n  if (newName.find('/') != std::string::npos) {\n    MDException ex;\n    ex.getMessage() << \"Name cannot contain slashes: \" << newName;\n    throw ex;\n  }\n\n  if (container->getId() == container->getParentId()) {\n    MDException ex;\n    ex.getMessage() << \"Cannot rename /\";\n    throw ex;\n  }\n\n  std::shared_ptr<IContainerMD> parent{\n    pContainerSvc->getContainerMD(container->getParentId())};\n\n  if (parent->findContainer(newName) != nullptr) {\n    MDException ex;\n    ex.getMessage() << \"Container exists: \" << newName;\n    throw ex;\n  }\n\n  if (parent->findFile(newName) != nullptr) {\n    MDException ex;\n    ex.getMessage() << \"File exists: \" << newName;\n    throw ex;\n  }\n\n  parent->removeContainer(container->getName());\n  container->setName(newName);\n  parent->addContainer(container);\n  updateContainerStore(container);\n}\n\n//------------------------------------------------------------------------------\n// Rename file (the file's parent directory and the file should be write locked)\n//------------------------------------------------------------------------------\nvoid\nQuarkHierarchicalView::renameFile(IFileMD* file, const std::string& newName)\n{\n  if (file == nullptr) {\n    MDException ex;\n    ex.getMessage() << \"Invalid file (zero pointer)\";\n    throw ex;\n  }\n\n  if (newName.empty()) {\n    MDException ex;\n    ex.getMessage() << \"Invalid new name (empty)\";\n    throw ex;\n  }\n\n  if (newName.find('/') != std::string::npos) {\n    MDException ex;\n    ex.getMessage() << \"Name cannot contain slashes: \" << newName;\n    throw ex;\n  }\n\n  std::shared_ptr<IContainerMD> parent{\n    pContainerSvc->getContainerMD(file->getContainerId())};\n\n  if (parent->findContainer(newName) != nullptr) {\n    MDException ex;\n    ex.getMessage() << \"Container exists: \" << newName;\n    throw ex;\n  }\n\n  if (parent->findFile(newName) != nullptr) {\n    MDException ex;\n    ex.getMessage() << \"File exists: \" << newName;\n    throw ex;\n  }\n\n  parent->removeFile(file->getName());\n  file->setName(newName);\n  parent->addFile(file);\n  updateFileStore(file);\n}\n\n//------------------------------------------------------------------------------\n// Get parent container of a file\n//------------------------------------------------------------------------------\nfolly::Future<IContainerMDPtr> QuarkHierarchicalView::getParentContainer(\n  IFileMD* file)\n{\n  IContainerMD::id_t parentId = file->getContainerId();\n  return pContainerSvc->getContainerMDFut(parentId);\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/ns_quarkdb/views/HierarchicalView.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Hierarchical namespace implementation\n//------------------------------------------------------------------------------\n\n#ifndef __EOS_NS_REDIS_HIERARHICAL_VIEW_HH__\n#define __EOS_NS_REDIS_HIERARHICAL_VIEW_HH__\n\n#include \"namespace/Namespace.hh\"\n#include \"namespace/interface/IContainerMDSvc.hh\"\n#include \"namespace/interface/IFileMDSvc.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/ns_quarkdb/accounting/QuotaStats.hh\"\n\n#ifdef __clang__\n#pragma clang diagnostic ignored \"-Wunused-private-field\"\n#endif\n\nEOSNSNAMESPACE_BEGIN\n\nclass MetadataFlusher;\n\n//------------------------------------------------------------------------------\n//! Implementation of the hierarchical namespace\n//------------------------------------------------------------------------------\nclass QuarkHierarchicalView : public IView\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  QuarkHierarchicalView(qclient::QClient* qcl, MetadataFlusher* quotaFlusher);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~QuarkHierarchicalView();\n\n  //----------------------------------------------------------------------------\n  //! Specify a pointer to the underlying container service\n  //----------------------------------------------------------------------------\n  virtual void\n  setContainerMDSvc(IContainerMDSvc* containerSvc) override\n  {\n    pContainerSvc = containerSvc;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the container svc pointer\n  //----------------------------------------------------------------------------\n  virtual IContainerMDSvc*\n  getContainerMDSvc() override\n  {\n    return pContainerSvc;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Specify a pointer to the underlying file service that alocates the\n  //! actual files\n  //----------------------------------------------------------------------------\n  virtual void\n  setFileMDSvc(IFileMDSvc* fileMDSvc) override\n  {\n    pFileSvc = fileMDSvc;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get the FileMDSvc\n  //----------------------------------------------------------------------------\n  virtual IFileMDSvc*\n  getFileMDSvc() override\n  {\n    return pFileSvc;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Configure the view\n  //----------------------------------------------------------------------------\n  virtual void configure(const std::map<std::string, std::string>& config)\n  override;\n\n  //----------------------------------------------------------------------------\n  //! Initialize the view\n  //----------------------------------------------------------------------------\n  virtual void initialize()  override;\n  virtual void initialize1() override; // phase 1 - load & setup container\n  virtual void initialize2() override; // phase 2 - load files\n  virtual void initialize3() override; // phase 3 - register files in container\n\n  //----------------------------------------------------------------------------\n  //! Finalize the view\n  //----------------------------------------------------------------------------\n  virtual void finalize() override;\n\n  //----------------------------------------------------------------------------\n  //! Retrieve a file for given uri, asynchronously\n  //----------------------------------------------------------------------------\n  virtual folly::Future<IFileMDPtr>\n  getFileFut(const std::string& uri, bool follow = true) override;\n\n  //----------------------------------------------------------------------------\n  //! Retrieve a file for given uri\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IFileMD>\n  getFile(const std::string& uri, bool follow = true,\n          size_t* link_depths = 0) override;\n\n\n  //----------------------------------------------------------------------------\n  //! Retrieve an item for given path. Could be either file or container, we\n  //! don't know.\n  //----------------------------------------------------------------------------\n  virtual folly::Future<FileOrContainerMD>\n  getItem(const std::string& uri, bool follow = true) override;\n\n  //----------------------------------------------------------------------------\n  //! Create a file for given uri\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IFileMD> createFile(const std::string& uri,\n      uid_t uid = 0, gid_t gid = 0, IFileMD::id_t id = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Create a link for given uri\n  //----------------------------------------------------------------------------\n  virtual void createLink(const std::string& uri, const std::string& linkuri,\n                          uid_t uid = 0, gid_t gid = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Update file store\n  //----------------------------------------------------------------------------\n  virtual void\n  updateFileStore(IFileMD* file) override\n  {\n    pFileSvc->updateStore(file);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Remove a link\n  //----------------------------------------------------------------------------\n  virtual void removeLink(const std::string& uri) override;\n\n  //----------------------------------------------------------------------------\n  //! Unlink the file\n  //!\n  //! @param uri full path to file to be unlinked\n  //----------------------------------------------------------------------------\n  virtual void unlinkFile(const std::string& uri) override;\n\n  //----------------------------------------------------------------------------\n  //! Unlink the file\n  //!\n  //! @param file IFileMD object\n  //----------------------------------------------------------------------------\n  virtual void unlinkFile(eos::IFileMD* file) override;\n\n  //----------------------------------------------------------------------------\n  //! Remove the file\n  //----------------------------------------------------------------------------\n  virtual void removeFile(IFileMD* file) override;\n\n  //----------------------------------------------------------------------------\n  //! Get a container (directory) asynchronously\n  //----------------------------------------------------------------------------\n  virtual folly::Future<IContainerMDPtr> getContainerFut(const std::string& uri,\n      bool follow = true) override;\n\n  //----------------------------------------------------------------------------\n  //! Get a container (directory)\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IContainerMD> getContainer(const std::string& uri,\n      bool follow = true,\n      size_t* link_depth = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Create a container (directory)\n  //----------------------------------------------------------------------------\n  virtual std::shared_ptr<IContainerMD>\n  createContainer(const std::string& uri, bool createParents = false,\n                  uint64_t cid = 0) override;\n\n  //----------------------------------------------------------------------------\n  //! Update container store\n  //----------------------------------------------------------------------------\n  virtual void\n  updateContainerStore(IContainerMD* container) override\n  {\n    pContainerSvc->updateStore(container);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Remove a container (directory)\n  //----------------------------------------------------------------------------\n  virtual void removeContainer(const std::string& uri) override;\n\n  //----------------------------------------------------------------------------\n  //! Get uri for the container\n  //----------------------------------------------------------------------------\n  virtual std::string getUri(const IContainerMD* container) const override;\n\n  //----------------------------------------------------------------------------\n  //! Get uri for the container - asynchronous version\n  //----------------------------------------------------------------------------\n  virtual folly::Future<std::string> getUriFut(ContainerIdentifier id) const\n  override;\n\n  //------------------------------------------------------------------------\n  //! Get uri for container id\n  //------------------------------------------------------------------------\n  virtual std::string getUri(const IContainerMD::id_t cid) const override;\n\n  //----------------------------------------------------------------------------\n  //! Get uri for the file\n  //! CAUTION: DO NOT LOCK THE FILE IN PARAMETER BEFORE CALLING THIS IN THE RISK\n  //! OF CREATING DEADLOCKS !\n  //! Explanation: if the file is already locked, the underlying logic will also lock the parent container\n  //! which is against the locking order that needs to be respected: lock(Container), then lock(File)\n  //----------------------------------------------------------------------------\n  virtual std::string getUri(const IFileMD* file) const override;\n\n  //----------------------------------------------------------------------------\n  //! Get uri for the file - asynchronous version\n  //----------------------------------------------------------------------------\n  virtual folly::Future<std::string> getUriFut(FileIdentifier id) const override;\n\n  //------------------------------------------------------------------------\n  //! Get real path translating existing symlink\n  //------------------------------------------------------------------------\n  virtual std::string getRealPath(const std::string& path) override;\n\n  //----------------------------------------------------------------------------\n  //! Get quota node id concerning given container\n  //----------------------------------------------------------------------------\n  virtual IQuotaNode* getQuotaNode(const IContainerMD* container,\n                                   bool search = true) override;\n\n  //----------------------------------------------------------------------------\n  //! Register the container to be a quota node\n  //----------------------------------------------------------------------------\n  virtual IQuotaNode* registerQuotaNode(IContainerMD* container) override;\n\n  //----------------------------------------------------------------------------\n  //! Remove the quota node\n  //----------------------------------------------------------------------------\n  virtual void removeQuotaNode(IContainerMD* container) override;\n\n  //----------------------------------------------------------------------------\n  //! Get the quota stats placeholder\n  //----------------------------------------------------------------------------\n  virtual IQuotaStats*\n  getQuotaStats() override\n  {\n    return pQuotaStats;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Set the quota stats placeholder, currently associated object (if any)\n  //! won't beX deleted.\n  //----------------------------------------------------------------------------\n  virtual void\n  setQuotaStats(IQuotaStats* quotaStats) override\n  {\n    if (pQuotaStats) {\n      delete pQuotaStats;\n    }\n\n    pQuotaStats = quotaStats;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Rename container\n  //----------------------------------------------------------------------------\n  virtual void renameContainer(IContainerMD* container,\n                               const std::string& newName) override;\n\n  //----------------------------------------------------------------------------\n  //! Rename file\n  //----------------------------------------------------------------------------\n  virtual void renameFile(IFileMD* file, const std::string& newName) override;\n\n  //----------------------------------------------------------------------------\n  //! Return whether this is an in-memory namespace.\n  //----------------------------------------------------------------------------\n  virtual bool inMemory() override\n  {\n    return false;\n  }\n\n  //----------------------------------------------------------------------------\n  //! Get parent container of a file\n  //----------------------------------------------------------------------------\n  virtual folly::Future<IContainerMDPtr> getParentContainer(\n    IFileMD* file) override;\n\nprivate:\n  //----------------------------------------------------------------------------\n  //! Lookup a given path - internal function.\n  //----------------------------------------------------------------------------\n  folly::Future<FileOrContainerMD>\n  getPathInternal(FileOrContainerMD state, std::deque<std::string> pendingChunks,\n                  bool follow, size_t expendedEffort);\n\n  //----------------------------------------------------------------------------\n  //! Lookup a given path - deferred function.\n  //----------------------------------------------------------------------------\n  folly::Future<FileOrContainerMD>\n  getPathDeferred(folly::Future<FileOrContainerMD> fut,\n                  std::deque<std::string> pendingChunks,\n                  bool follow, size_t expendedEffort);\n\n  //----------------------------------------------------------------------------\n  //! Lookup a given path - deferred function.\n  //----------------------------------------------------------------------------\n  folly::Future<FileOrContainerMD>\n  getPathDeferred(folly::Future<IContainerMDPtr> fut,\n                  std::deque<std::string> pendingChunks,\n                  bool follow, size_t expendedEffort);\n\n  //----------------------------------------------------------------------------\n  //! Lookup a given path, expect a container there.\n  //----------------------------------------------------------------------------\n  folly::Future<IContainerMDPtr>\n  getPathExpectContainer(const std::deque<std::string>& chunks);\n\n  //----------------------------------------------------------------------------\n  //! Build the URL of the given container, as a deque of chunks. Primary\n  //! \"resumable\" function.\n  //----------------------------------------------------------------------------\n  folly::Future<std::deque<std::string>> getUriInternal(std::deque<std::string>\n                                      currentChunks, IContainerMDPtr nextToLookup) const;\n\n  //----------------------------------------------------------------------------\n  //! Build the URL of the given file, as a deque of chunks.\n  //----------------------------------------------------------------------------\n  folly::Future<std::deque<std::string>> getUriInternalFmdPtr(\n                                        IFileMDPtr fmd) const;\n  folly::Future<std::deque<std::string>> getUriInternalFmd(\n                                        const IFileMD* fmd) const;\n\n  //----------------------------------------------------------------------------\n  //! Build the URL of the given container ID.\n  //----------------------------------------------------------------------------\n  folly::Future<std::deque<std::string>> getUriInternalCid(\n                                        std::deque<std::string> currentChunks, ContainerIdentifier cid) const;\n\n  //----------------------------------------------------------------------------\n  //! Build the URL of the given fid\n  //----------------------------------------------------------------------------\n  folly::Future<std::deque<std::string>> getUriInternalFid(\n                                        FileIdentifier fid) const;\n\n  //----------------------------------------------------------------------------\n  //! Clean up contents of container\n  //!\n  //! @param cont container object\n  //----------------------------------------------------------------------------\n  void cleanUpContainer(IContainerMD* cont);\n\n  //----------------------------------------------------------------------------\n  // Data members\n  //----------------------------------------------------------------------------\n  qclient::QClient* pQcl;\n  MetadataFlusher* pQuotaFlusher;\n  IContainerMDSvc* pContainerSvc;\n  IFileMDSvc* pFileSvc;\n  IQuotaStats* pQuotaStats;\n  std::shared_ptr<IContainerMD> pRoot;\n  std::unique_ptr<folly::Executor> pExecutor;\n};\n\nEOSNSNAMESPACE_END\n\n#endif // __EOS_NS_REDIS_HIERARCHICAL_VIEW_HH__\n"
  },
  {
    "path": "namespace/utils/Attributes.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Attribute utilities\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/Prefetcher.hh\"\n#include \"common/StringUtils.hh\"\n#include \"common/Logging.hh\"\n#include \"namespace/MDLocking.hh\"\n#include <iostream>\n\nEOSNSNAMESPACE_BEGIN\n\nauto constexpr kAttrLinkKey = \"sys.attr.link\";\nauto constexpr kAttrTmpEtagKey = \"sys.tmp.etag\";\nauto constexpr kAttrObfuscateKey = \"user.obfuscate.key\";\n\n//------------------------------------------------------------------------------\n// Populate 'out' map with attributes found in linkedAttrs. Do not override\n// existing values.\n//------------------------------------------------------------------------------\ninline void\npopulateLinkedAttributes(const eos::IContainerMD::XAttrMap& linkedAttrs,\n                         eos::IContainerMD::XAttrMap& out, bool prefixLinks)\n{\n  for (auto it = linkedAttrs.begin(); it != linkedAttrs.end(); ++it) {\n    // Populate any linked extended attributes which don't exist yet\n    if (out.find(it->first) == out.end()) {\n      std::string key;\n\n      if (prefixLinks && common::startsWith(it->first, \"sys.\")) {\n        key = SSTR(\"sys.link.\" << it->first.substr(4));\n      } else {\n        key = it->first;\n      }\n\n      out[key] = it->second;\n    }\n  }\n}\n\n//------------------------------------------------------------------------------\n// Fill out the given map with any extended attributes found in given container,\n// but DO NOT override existing values.\n//------------------------------------------------------------------------------\ninline void\npopulateLinkedAttributes(IView* view, eos::IContainerMD::XAttrMap& out,\n                         bool prefixLinks)\n{\n  try {\n    auto linkedPath = out.find(kAttrLinkKey);\n\n    if (linkedPath == out.end() || linkedPath->second.empty()) {\n      return;\n    }\n\n    IContainerMDPtr dh = view->getContainer(linkedPath->second);\n    populateLinkedAttributes(dh->getAttributes(), out, prefixLinks);\n  } catch (eos::MDException& e) {\n    // Link does not exist, or is not a directory\n    out[kAttrLinkKey] = SSTR(out[kAttrLinkKey] << \" - not found\");\n    eos_static_debug(\"msg=\\\"exception\\\" ec=%d emsg=\\\"%s\\\"\\n\", e.getErrno(),\n                     e.getMessage().str().c_str());\n  }\n}\n\n//------------------------------------------------------------------------------\n// Retrieve list of extended attributes, including linked ones.\n//\n// If the same attribute exists in both the target, and the link, the most\n// specific one wins, ie the one on the target itself.\n//\n// Target is specified as IContainerMD.\n//------------------------------------------------------------------------------\ninline void\nlistAttributes(IView* view, IContainerMD* target,\n               eos::IContainerMD::XAttrMap& out, bool prefixLinks = false)\n{\n  out.clear();\n  out = target->getAttributes();\n  populateLinkedAttributes(view, out, prefixLinks);\n}\n\n//------------------------------------------------------------------------------\n// Retrieve list of extended attributes, including linked ones.\n//\n// If the same attribute exists in both the target, and the link, the most\n// specific one wins, ie the one on the target itself.\n//\n// Target is specified as IFileMD.\n//------------------------------------------------------------------------------\ninline void\nlistAttributes(IView* view, IFileMD* target,\n               eos::IContainerMD::XAttrMap& out, bool prefixLinks = false)\n{\n  out.clear();\n  out = target->getAttributes();\n  populateLinkedAttributes(view, out, prefixLinks);\n}\n\n//------------------------------------------------------------------------------\n// Retrieve list of extended attributes, including linked ones.\n//\n// If the same attribute exists in both the target, and the link, the most\n// specific one wins, ie the one on the target itself.\n//\n// Target is specified as FileOrContainerMD.\n//------------------------------------------------------------------------------\ninline void\nlistAttributes(IView* view, FileOrContainerMD target,\n               eos::IContainerMD::XAttrMap& out, bool prefixLinks = false)\n{\n  out.clear();\n\n  if (target.file) {\n    // Don't read lock the file as it just does a single operation in listAttributes()\n    listAttributes(view, target.file.get(), out, prefixLinks);\n  } else if (target.container) {\n    // Don't read lock the container as it just does a single operation in listAttributes()\n    listAttributes(view, target.container.get(), out, prefixLinks);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get extended attribute for a given md object - low-level API.\n//------------------------------------------------------------------------------\ntemplate<typename T>\nstatic bool getAttribute(IView* view, T& md, std::string key,\n                         std::string& rvalue)\n{\n  // First, check if the referenced object itself contains the attribute\n  if (md.hasAttribute(key)) {\n    rvalue = md.getAttribute(key);\n    return true;\n  }\n\n  if (!md.hasAttribute(kAttrLinkKey)) {\n    return false;\n  }\n\n  // It does, fetch linked container\n  std::string linkedContainer = md.getAttribute(kAttrLinkKey);\n  std::shared_ptr<eos::IContainerMD> dh;\n  eos::MDLocking::ContainerReadLockPtr dhLock;\n  eos::Prefetcher::prefetchContainerMDAndWait(view, linkedContainer);\n\n  try {\n    dh = view->getContainer(linkedContainer);\n    dhLock = eos::MDLocking::readLock(dh.get());\n  } catch (eos::MDException& e) {\n    errno = e.getErrno();\n    eos_static_err(\"msg=\\\"exception while following linked container\\\" ec=%d emsg=\\\"%s\\\"\\n\",\n                   e.getErrno(), e.getMessage().str().c_str());\n    return false;\n  }\n\n  // We have the linked container, lookup.\n  if (!dh->hasAttribute(key)) {\n    return false;\n  }\n\n  rvalue = dh->getAttribute(key);\n  return true;\n}\n\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/utils/BalanceCalculator.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Georgios Bitzes <georgios.bitzes@cern.ch>\n// desc:   Utility to calculate balance statistics over a set of files\n//------------------------------------------------------------------------------\n\n#pragma once\nnamespace eos\n{\n//------------------------------------------------------------------------------\n//! Class to calculate balance statistics over a set of files\n//------------------------------------------------------------------------------\nclass BalanceCalculator\n{\npublic:\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  BalanceCalculator()\n  {\n    filesystembalance.set_empty_key(0);\n    spacebalance.set_empty_key(\"\");\n    schedulinggroupbalance.set_empty_key(\"\");\n    sizedistribution.set_empty_key(-1);\n    sizedistributionn.set_empty_key(-1);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Take the given fmd into account for statistics calculations\n  //----------------------------------------------------------------------------\n  void account(const std::shared_ptr<eos::IFileMD>& fmd)\n  {\n    for (unsigned int i = 0; i < fmd->getNumLocation(); i++) {\n      auto loc = fmd->getLocation(i);\n      size_t size = fmd->getSize();\n\n      if (!loc) {\n        eos_static_err(\"fsid 0 found %s %llu\", fmd->getName().c_str(), fmd->getId());\n        continue;\n      }\n\n      filesystembalance[loc] += size;\n\n      if ((i == 0) && (size)) {\n        auto bin = (int) log10((double) size);\n        sizedistribution[ bin ] += size;\n        sizedistributionn[ bin ]++;\n      }\n\n      eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);\n      eos::common::FileSystem* filesystem = FsView::gFsView.mIdView.lookupByID(loc);\n\n      if (filesystem != nullptr) {\n        eos::common::FileSystem::fs_snapshot_t fs;\n\n        if (filesystem->SnapShotFileSystem(fs, true)) {\n          spacebalance[fs.mSpace] += size;\n          schedulinggroupbalance[fs.mGroup] += size;\n        }\n      }\n    }\n  }\n\n  //----------------------------------------------------------------------------\n  //! Print a summary into the given stream\n  //----------------------------------------------------------------------------\n  template<typename S>   // std::ofstream or std::stringstream\n  void printSummary(S& ss)\n  {\n    XrdOucString sizestring = \"\";\n\n    for (const auto& it : filesystembalance) {\n      ss << \"fsid=\" << it.first << \" \\tvolume=\";\n      ss << std::left << std::setw(12) <<\n         eos::common::StringConversion::GetReadableSizeString(sizestring, it.second,\n             \"B\");\n      ss << \" \\tnbytes=\" << it.second << std::endl;\n    }\n\n    for (const auto& its : spacebalance) {\n      ss << \"space=\" << its.first << \" \\tvolume=\";\n      ss << std::left << std::setw(12) <<\n         eos::common::StringConversion::GetReadableSizeString(sizestring, its.second,\n             \"B\");\n      ss << \" \\tnbytes=\" << its.second << std::endl;\n    }\n\n    for (const auto& itg : schedulinggroupbalance) {\n      ss << \"sched=\" << itg.first << \" \\tvolume=\";\n      ss << std::left << std::setw(12) <<\n         eos::common::StringConversion::GetReadableSizeString(sizestring, itg.second,\n             \"B\");\n      ss << \" \\tnbytes=\" << itg.second << std::endl;\n    }\n\n    for (const auto& itsd : sizedistribution) {\n      unsigned long long lowerlimit = 0;\n      unsigned long long upperlimit = 0;\n\n      if (((itsd.first) - 1) > 0) {\n        lowerlimit = pow(10, (itsd.first));\n      }\n\n      if ((itsd.first) > 0) {\n        upperlimit = pow(10, (itsd.first) + 1);\n      }\n\n      XrdOucString sizestring1;\n      XrdOucString sizestring2;\n      XrdOucString sizestring3;\n      XrdOucString sizestring4;\n      unsigned long long avgsize = (sizedistributionn[itsd.first]\n                                    ? itsd.second / sizedistributionn[itsd.first] : 0);\n      ss << \"sizeorder=\" << std::right << std::setfill('0') << std::setw(\n           2) << itsd.first;\n      ss << \" \\trange=[ \" << std::setfill(' ') << std::left << std::setw(12);\n      ss << eos::common::StringConversion::GetReadableSizeString(\n           sizestring1, lowerlimit, \"B\");\n      ss << \" ... \" << std::left << std::setw(12);\n      ss << eos::common::StringConversion::GetReadableSizeString(\n           sizestring2, upperlimit, \"B\") << \" ]\";\n      ss << \" volume=\" << std::left << std::setw(12);\n      ss << eos::common::StringConversion::GetReadableSizeString(\n           sizestring3, itsd.second, \"B\");\n      ss << \" \\tavgsize=\" << std::left << std::setw(12);\n      ss << eos::common::StringConversion::GetReadableSizeString(\n           sizestring4, avgsize, \"B\");\n      ss << \" \\tnbytes=\" << itsd.second;\n      ss << \" \\t avgnbytes=\" << avgsize;\n      ss << \" \\t nfiles=\" << sizedistributionn[itsd.first];\n    }\n  }\n\nprivate:\n  google::dense_hash_map<unsigned long, unsigned long long> filesystembalance;\n  google::dense_hash_map<std::string, unsigned long long> spacebalance;\n  google::dense_hash_map<std::string, unsigned long long> schedulinggroupbalance;\n  google::dense_hash_map<int, unsigned long long> sizedistribution;\n  google::dense_hash_map<int, unsigned long long> sizedistributionn;\n};\n\n}\n"
  },
  {
    "path": "namespace/utils/Buffer.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Lukasz Janyst <ljanyst@cern.ch>\n// desc:   Data Buffer\n//------------------------------------------------------------------------------\n\n#pragma once\n#include <cstring>\n#include <vector>\n#include <stdint.h>\n#include <zlib.h>\n#include \"namespace/MDException.hh\"\n\nnamespace eos\n{\n//----------------------------------------------------------------------------\n//! Data Buffer\n//----------------------------------------------------------------------------\nclass Buffer: public std::vector<char>\n{\npublic:\n  //------------------------------------------------------------------------\n  //! Constructor\n  //------------------------------------------------------------------------\n  Buffer(unsigned size = 512):\n    data(0), len(0)\n  {\n    reserve(size);\n  }\n\n  //------------------------------------------------------------------------\n  //! Destructor\n  //------------------------------------------------------------------------\n  virtual ~Buffer() {}\n\n  //------------------------------------------------------------------------\n  //! Copy constructor\n  //------------------------------------------------------------------------\n  Buffer(const Buffer& other) : std::vector<char>(other) {}\n\n  //------------------------------------------------------------------------\n  //! Assignment operator\n  //------------------------------------------------------------------------\n  Buffer& operator = (const Buffer& other)\n  {\n    if (this != &other) {\n      data = 0;\n      len = 0;\n      resize(other.getSize());\n      memcpy(getDataPtr(), other.getDataPtr(), other.getSize());\n    }\n\n    return *this;\n  };\n\n  //------------------------------------------------------------------------\n  //! Get data pointer\n  //------------------------------------------------------------------------\n  char* getDataPtr()\n  {\n    if (!data) {\n      return &operator[](0);\n    } else {\n      return data;\n    }\n  }\n\n  //------------------------------------------------------------------------\n  //! Get data pointer\n  //------------------------------------------------------------------------\n  const char* getDataPtr() const\n  {\n    if (!data) {\n      return &operator[](0);\n    } else {\n      return data;\n    }\n  }\n\n  //------------------------------------------------------------------------\n  //! Set data pointer\n  //------------------------------------------------------------------------\n  void setDataPtr(char* ptr, size_t size)\n  {\n    data = ptr;\n    len = size;\n  }\n\n  //------------------------------------------------------------------------\n  //! Get data padded (if we read over the size we get 0 as response)\n  //------------------------------------------------------------------------\n  char getDataPadded(size_t i) const\n  {\n    if (!data) {\n      if (i < size()) {\n        return (operator[](i));\n      }\n    } else {\n      if (i < len) {\n        return *(data + i);\n      }\n    }\n\n    return 0;\n  }\n\n  //------------------------------------------------------------------------\n  //! Get size\n  //------------------------------------------------------------------------\n  size_t getSize() const\n  {\n    if (!data) {\n      return size();\n    } else {\n      return len;\n    }\n  }\n\n  //------------------------------------------------------------------------\n  //! Set size\n  //------------------------------------------------------------------------\n  void setSize(size_t size)\n  {\n    resize(size);\n  }\n\n  //------------------------------------------------------------------------\n  //! Add data\n  //------------------------------------------------------------------------\n  void putData(const void* ptr, size_t dataSize)\n  {\n    if (!data) {\n      size_t currSize = size();\n      resize(currSize + dataSize);\n      memcpy(&operator[](currSize), ptr, dataSize);\n    } else {\n      MDException e(EINVAL);\n      e.getMessage() << \"Read only structure\";\n      throw e;\n    }\n  }\n\n  //------------------------------------------------------------------------\n  //! Copy data to pointed location\n  //------------------------------------------------------------------------\n  uint16_t grabData(uint16_t offset, void* ptr, size_t dataSize) const\n  {\n    if (offset + dataSize > getSize()) {\n      MDException e(EINVAL);\n      e.getMessage() << \"Not enough data to fulfil the request\";\n      throw e;\n    }\n\n    if (!data) {\n      memcpy(ptr, &operator[](offset), dataSize);\n    } else  {\n      memcpy(ptr, data + offset, dataSize);\n    }\n\n    return offset + dataSize;\n  }\n\n  //------------------------------------------------------------------------\n  //! Calculate the CRC32 checksum\n  //------------------------------------------------------------------------\n  uint32_t getCRC32() const\n  {\n    return crc32(crc32(0L, Z_NULL, 0), (const Bytef*)getDataPtr(), size());\n  }\n\nprotected:\n  char* data;\n  size_t len;\n};\n}\n"
  },
  {
    "path": "namespace/utils/Checksum.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Georgios Bitzes <georgios.bitzes@cern.ch>\n// desc:   Namespace checksum utilities\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_CHECKSUM_HH\n#define EOS_NS_CHECKSUM_HH\n\n#include \"common/Logging.hh\"\n#include \"common/LayoutId.hh\"\n#include \"proto/FileMd.pb.h\"\n#include \"namespace/utils/Buffer.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n\n\nnamespace eos\n{\n  //----------------------------------------------------------------------------\n  //! Append FileMD checksum onto the given string. Return false only if we're\n  //! not able to determine checksum type for given layout id.\n  //!\n  //! Use the given separator to separate each two hexadecimal digits.\n  //! i.e. \"b5 e1 70 20\" instead of \"b5e17020\"\n  //!\n  //! We use a template to support both std::string and XrdOucString...\n  //----------------------------------------------------------------------------\n  template<typename StringType>\n  bool appendChecksumOnStringAsHexNoFmd(IFileMD::layoutId_t layoutId,\n                                   const Buffer &buffer,\n                                   StringType &out,\n                                   char separator = 0x00,\n                                   int overrideLength = -1) {\n    // All this is to maintain backward compatibility in all places where\n    // we print checksums.. I'm not sure if we absolutely need to pad with\n    // zeroes, for example.\n\n    unsigned int nominalChecksumLength =\n      eos::common::LayoutId::GetChecksumLen(layoutId);\n    unsigned int targetChecksumLength;\n\n    if (overrideLength == -1) {\n      targetChecksumLength = nominalChecksumLength;\n    } else {\n      targetChecksumLength = overrideLength;\n    }\n\n    for (unsigned int i = 0; i < targetChecksumLength; i++) {\n      unsigned char targetCharacter = 0x00;\n      char hb[4];\n\n      if (i < nominalChecksumLength) {\n        targetCharacter = buffer.getDataPadded(i);\n      }\n\n      if (separator != 0x00 && i != (targetChecksumLength-1)) {\n        sprintf(hb, \"%02x%c\", targetCharacter, separator);\n        out += hb;\n      } else {\n        sprintf(hb, \"%02x\", targetCharacter);\n        out += hb;\n      }\n    }\n\n    return (nominalChecksumLength > 0);\n  }\n\n  template<typename StringType>\n  bool appendChecksumOnStringAsHex(const eos::IFileMD *fmd, StringType &out,\n                                   char separator = 0x00,\n                                   int overrideLength = -1) {\n    if(!fmd) return false;\n    return appendChecksumOnStringAsHexNoFmd(fmd->getLayoutId(), fmd->getChecksum(),\n      out, separator, overrideLength);\n  }\n\n  inline bool appendChecksumOnStringProtobuf(const eos::ns::FileMdProto &proto,\n    std::string &out, char separator = 0x00, int overrideLength = -1) {\n\n    Buffer checksumBuffer(proto.checksum().size());\n    checksumBuffer.putData((void*)proto.checksum().data(), proto.checksum().size());\n    return appendChecksumOnStringAsHexNoFmd(proto.layout_id(), checksumBuffer, out, separator, overrideLength);\n  }\n\n  inline bool hexArrayToByteArray(const char* hexArray, size_t sz, std::string &byteArray) {\n    byteArray.clear();\n\n    if(sz == 0) {\n      return true;\n    }\n\n    if(sz % 2 != 0) {\n      return false;\n    }\n\n    for(size_t i = 0; i < sz; i += 2) {\n      char *endptr = nullptr;\n\n      char tmpArray[3];\n      tmpArray[0] = hexArray[i];\n      tmpArray[1] = hexArray[i+1];\n      tmpArray[2] = 0x00;\n\n      char byte = static_cast<char>(strtol(tmpArray, &endptr, 16));\n      byteArray.push_back(byte);\n\n      if(endptr != tmpArray+2) {\n        byteArray.clear();\n        return false;\n      }\n    }\n\n    return true;\n  }\n\n  inline bool hexArrayToByteArray(const std::string &hexArray, std::string &byteArray) {\n    return hexArrayToByteArray(hexArray.c_str(), hexArray.size(), byteArray);\n  }\n\n}\n\n#endif\n"
  },
  {
    "path": "namespace/utils/DataHelper.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Lukasz Janyst <ljanyst@cern.ch>\n// desc:   Checksumming, data conversion and other stuff\n//------------------------------------------------------------------------------\n\n#include \"namespace/utils/DataHelper.hh\"\n#include \"namespace/MDException.hh\"\n#include \"common/crc32c/crc32c.h\"\n#include <zlib.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <cerrno>\n\nnamespace eos\n{\n\n//------------------------------------------------------------------------------\n// Compute crc32 checksum out of a buffer\n//------------------------------------------------------------------------------\nuint32_t DataHelper::computeCRC32(void* buffer, uint32_t len)\n{\n  return crc32(crc32(0L, Z_NULL, 0), (const Bytef*)buffer, len);\n}\n\n//------------------------------------------------------------------------------\n// Update a crc32 checksum\n//------------------------------------------------------------------------------\nuint32_t DataHelper::updateCRC32(uint32_t crc, void* buffer, uint32_t len)\n{\n  return crc32(crc, (const Bytef*)buffer, len);\n}\n\n//------------------------------------------------------------------------------\n// Compute crc32c checksum out of a buffer\n//------------------------------------------------------------------------------\nuint32_t DataHelper::computeCRC32C(void* buffer, uint32_t len)\n{\n  return checksum::crc32c(checksum::crc32cInit(), (const Bytef*)buffer, len);\n}\n\n//------------------------------------------------------------------------------\n// Update a crc32c checksum\n//------------------------------------------------------------------------------\nuint32_t DataHelper::updateCRC32C(uint32_t crc, void* buffer, uint32_t len)\n{\n  return checksum::crc32c(crc, (const Bytef*)buffer, len);\n}\n\n//------------------------------------------------------------------------------\n// Finalize crc32c checksum\n//------------------------------------------------------------------------------\nuint32_t DataHelper::finalizeCRC32C(uint32_t crc)\n{\n  return checksum::crc32cFinish(crc);\n}\n\n//----------------------------------------------------------------------------\n// Copy file ownership information\n//----------------------------------------------------------------------------\nvoid DataHelper::copyOwnership(const std::string& target,\n                               const std::string& source,\n                               bool ignoreNoPerm)\n{\n  //--------------------------------------------------------------------------\n  // Check the root-ness\n  //--------------------------------------------------------------------------\n  uid_t uid = getuid();\n\n  if (uid != 0 && ignoreNoPerm) {\n    return;\n  }\n\n  if (uid != 0) {\n    MDException e(EFAULT);\n    e.getMessage() << \"Only root can change ownership\";\n    throw e;\n  }\n\n  //--------------------------------------------------------------------------\n  // Get the thing done\n  //--------------------------------------------------------------------------\n  struct stat st;\n\n  if (stat(source.c_str(), &st) != 0) {\n    MDException e(errno);\n    e.getMessage() << \"Unable to stat source: \" << source;\n    throw e;\n  }\n\n  if (chown(target.c_str(), st.st_uid, st.st_gid) != 0) {\n    MDException e(errno);\n    e.getMessage() << \"Unable to change the ownership of the target: \";\n    e.getMessage() << target;\n    throw e;\n  }\n}\n}\n\n"
  },
  {
    "path": "namespace/utils/DataHelper.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Lukasz Janyst <ljanyst@cern.ch>\n// desc:   Checksumming, data conversion and other stuff\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_DATA_HELPER_HH\n#define EOS_NS_DATA_HELPER_HH\n\n#include <stdint.h>\n#include <string>\n\nnamespace eos\n{\nclass DataHelper\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Compute crc32 checksum out of a buffer\n  //----------------------------------------------------------------------------\n  static uint32_t computeCRC32(void* buffer, uint32_t len);\n\n  //----------------------------------------------------------------------------\n  //! Update a crc32 checksum\n  //----------------------------------------------------------------------------\n  static uint32_t updateCRC32(uint32_t crc, void* buffer, uint32_t len);\n\n  //----------------------------------------------------------------------------\n  //! Compute crc32c checksum out of a buffer\n  //----------------------------------------------------------------------------\n  static uint32_t computeCRC32C(void* buffer, uint32_t len);\n\n  //----------------------------------------------------------------------------\n  //! Update a crc32c checksum\n  //----------------------------------------------------------------------------\n  static uint32_t updateCRC32C(uint32_t crc, void* buffer, uint32_t len);\n\n  //----------------------------------------------------------------------------\n  //! Finalize crc32c checksum\n  //----------------------------------------------------------------------------\n  static uint32_t finalizeCRC32C(uint32_t crc);\n\n  //----------------------------------------------------------------------------\n  //! Copy file ownership information\n  //!\n  //! @param target           target file\n  //! @param source           source file\n  //! @param ignoreWhenNoPerm exit seamlesly when the caller has\n  //!                         insufficient permissions to carry out this\n  //!                         operation\n  //----------------------------------------------------------------------------\n  static void copyOwnership(const std::string& target,\n                            const std::string& source,\n                            bool ignoreNoPerm = true);\n};\n}\n\n#endif // EOS_NS_DATA_HELPER_HH\n"
  },
  {
    "path": "namespace/utils/Descriptor.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// Author: Lukasz Janyst <ljanyst@cern.ch>\n// Date:   07.07.2010\n// File:   Descriptor.cc\n//------------------------------------------------------------------------------\n\n#include \"namespace/utils/Descriptor.hh\"\n\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <arpa/inet.h>\n#include <netdb.h>\n#include <netinet/tcp.h>\n#include <cerrno>\n#include <cstring>\n#include <cstdlib>\n\nstatic void resolve(const char* address, sockaddr_in& addr)\n{\n  eos::DescriptorException ex;\n  //--------------------------------------------------------------------------\n  // Get the ip address\n  //--------------------------------------------------------------------------\n  hostent* hp;\n  //--------------------------------------------------------------------------\n  // Enlarge the buffer until the call succeeds\n  //--------------------------------------------------------------------------\n#ifdef __APPLE__\n  hp = gethostbyname(address);\n\n  if (!hp) {\n    ex.getMessage() << \"Socket: get host by name failed\";\n    throw ex;\n  }\n\n#else\n  hostent  hostbuf;\n  size_t   hstbuflen = 1024;\n  int      herr;\n  int      res;\n  char*    tmphstbuf = (char*)malloc(hstbuflen);\n\n  while ((res = gethostbyname_r(address, &hostbuf, tmphstbuf, hstbuflen,\n                                &hp, &herr)) == ERANGE) {\n    hstbuflen *= 2;\n    char* ptr  = (char*)realloc(tmphstbuf, hstbuflen);\n\n    if (ptr == nullptr) {\n      std::abort();\n    }\n\n    tmphstbuf = ptr;\n  }\n\n  if (res || !hp) {\n    free(tmphstbuf);\n    ex.getMessage() << \"Socket: get host by name failed\";\n    throw ex;\n  }\n\n#endif\n\n  if (hp->h_addr_list == 0) {\n    ex.getMessage() << \"Socket: host unknown\";\n    throw ex;\n  }\n\n  if (hp->h_addr_list[0] == 0) {\n    ex.getMessage() << \"Socket: host unknown\";\n    throw ex;\n  }\n\n  memcpy(&addr.sin_addr.s_addr, hp->h_addr_list[0], sizeof(in_addr));\n#ifndef __APPLE__\n  free(tmphstbuf);\n#endif\n}\n\nnamespace eos\n{\n//----------------------------------------------------------------------------\n// Initializer\n//----------------------------------------------------------------------------\nvoid Socket::init(Protocol proto)\n{\n  if (pFD != -1) {\n    DescriptorException ex;\n    ex.getMessage() << \"Socket: socket is already initialized\";\n    throw ex;\n  }\n\n  //--------------------------------------------------------------------------\n  // Create the socket\n  //--------------------------------------------------------------------------\n  int type = SOCK_STREAM;\n\n  if (proto == UDP) {\n    type = SOCK_DGRAM;\n  }\n\n  if ((pFD = socket(PF_INET, type, 0)) == -1) {\n    DescriptorException ex;\n    ex.getMessage() << \"Socket: Unable to create socket: \";\n    ex.getMessage() << strerror(errno);\n    throw ex;\n  }\n}\n\n//----------------------------------------------------------------------------\n// Connect the socket\n//----------------------------------------------------------------------------\nvoid Socket::connect(const char* address, unsigned port)\n{\n  DescriptorException ex;\n\n  if (pFD == -1) {\n    init(TCP);\n  }\n\n  //--------------------------------------------------------------------------\n  //! Resolve the hostname\n  //--------------------------------------------------------------------------\n  sockaddr_in addr = {0};\n  resolve(address, addr);\n  //--------------------------------------------------------------------------\n  // Set up the port\n  //--------------------------------------------------------------------------\n  addr.sin_family = AF_INET;\n  addr.sin_port   = htons((unsigned short)port);\n\n  //--------------------------------------------------------------------------\n  // Connect to the remote host\n  //--------------------------------------------------------------------------\n  if ((pFD < 0) || (::connect(pFD, (sockaddr*)&addr, sizeof(addr)) != 0)) {\n    if (pFD > 0) {\n      ::close(pFD);\n    }\n\n    ex.getMessage() << \"Socket: Connection failed: \";\n    ex.getMessage() << strerror(errno);\n    throw ex;\n  }\n}\n\n//----------------------------------------------------------------------------\n// Bind to the port\n//----------------------------------------------------------------------------\nvoid Socket::bind(const char* address, unsigned port)\n{\n  if (pFD == -1) {\n    init(TCP);\n  }\n\n  //--------------------------------------------------------------------------\n  // Resolve the address\n  //--------------------------------------------------------------------------\n  sockaddr_in         localAddr;\n  DescriptorException ex;\n  memset(&localAddr, 0, sizeof(localAddr));\n\n  if (address) {\n    resolve(address, localAddr);\n  } else {\n    localAddr.sin_addr.s_addr = INADDR_ANY;\n  }\n\n  localAddr.sin_family = AF_INET;\n  localAddr.sin_port   = htons((unsigned short)port);\n\n  //--------------------------------------------------------------------------\n  // Bind the socket\n  //--------------------------------------------------------------------------\n  if ((pFD < 0) || (::bind(pFD, (sockaddr*)&localAddr,\n                           sizeof(sockaddr_in)) == -1)) {\n    if (pFD >= 0) {\n      ::close(pFD);\n    }\n\n    ex.getMessage() << \"Socket: Unable to bind to port: \" << port << \" \";\n    ex.getMessage() << strerror(errno);\n    throw ex;\n  }\n}\n\n//----------------------------------------------------------------------------\n// Listen to the incomming connections\n//----------------------------------------------------------------------------\nvoid Socket::listen(unsigned queue)\n{\n  DescriptorException ex;\n\n  //--------------------------------------------------------------------------\n  // Listen to the incomming connections\n  //--------------------------------------------------------------------------\n  if (::listen(pFD, 20) == -1) {\n    ex.getMessage() << \"Socket: Unable to listen: \" << strerror(errno);\n    throw ex;\n  }\n}\n\nSocket* Socket::accept()\n{\n  DescriptorException ex;\n  //--------------------------------------------------------------------------\n  // Accept the connection\n  //--------------------------------------------------------------------------\n  socklen_t   sinSize = sizeof(sockaddr_in);\n  sockaddr_in remoteAddr;\n  int newSock = ::accept(pFD, (sockaddr*)&remoteAddr, &sinSize);\n\n  if (newSock == -1) {\n    ex.getMessage() << \"Socket: Error while accpeting connection: \";\n    ex.getMessage() << strerror(errno);\n    throw ex;\n  }\n\n  //--------------------------------------------------------------------------\n  // Create a new thread to handle this connection\n  //--------------------------------------------------------------------------\n  return new Socket(newSock);\n}\n\n//----------------------------------------------------------------------------\n// Close the socket\n//----------------------------------------------------------------------------\nvoid Descriptor::close()\n{\n  if (pFD != -1) {\n    ::close(pFD);\n    pFD = -1;\n  }\n}\n\n//----------------------------------------------------------------------------\n// Read the buffer from the blocking descriptor (socket, pipe), it won't\n// return untill all the requested data is read\n//----------------------------------------------------------------------------\nvoid Descriptor::readBlocking(char* buffer, unsigned len)\n{\n  if (len == 0) {\n    return;\n  }\n\n  int   ret;\n  int   left = len;\n  char* ptr  = buffer;\n\n  while (1) {\n    ret = ::read(pFD, ptr, left);\n\n    if (ret == -1 || ret == 0) {\n      DescriptorException ex;\n      ex.getMessage() << \"Descriptor: Unable to read \" << len << \" bytes: \";\n      ex.getMessage() << strerror(errno);\n      throw ex;\n    }\n\n    left -= ret;\n\n    if (!left) {\n      return;\n    }\n\n    ptr += ret;\n  }\n}\n\n//----------------------------------------------------------------------------\n// Read the buffer from the non-blocking descriptor (file, block device),\n// it won't return untill all the requested data is read.\n//----------------------------------------------------------------------------\nvoid Descriptor::readNonBlocking(char* buffer, unsigned len, unsigned poll)\n{\n  if (len == 0) {\n    return;\n  }\n\n  int   ret;\n  int   left = len;\n  char* ptr  = buffer;\n\n  while (1) {\n    ret = ::read(pFD, ptr, left);\n\n    if (ret == -1) {\n      DescriptorException ex;\n      ex.getMessage() << \"Descriptor: Unable to read \" << len << \" bytes: \";\n      ex.getMessage() << strerror(errno);\n      throw ex;\n    }\n\n    if (ret == 0) {\n      if (poll != 0) {\n        usleep(poll);\n      } else {\n        DescriptorException ex;\n        ex.getMessage() << \"Descriptor: Not enough data to fulfill the request\";\n        throw ex;\n      }\n    }\n\n    left -= ret;\n\n    if (!left) {\n      return;\n    }\n\n    ptr += ret;\n  }\n}\n\n//----------------------------------------------------------------------------\n// Read the buffer from the non-blocking descriptor (file, block device)\n// at given offset, it won't return untill all the requested data is read.\n//----------------------------------------------------------------------------\nvoid Descriptor::offsetReadNonBlocking(char* buffer, unsigned len,\n                                       off_t offset, unsigned poll)\n{\n  if (len == 0) {\n    return;\n  }\n\n  int   ret;\n  off_t off  = offset;\n  int   left = len;\n  char* ptr  = buffer;\n\n  while (1) {\n    ret = ::pread(pFD, ptr, left, off);\n\n    if (ret == -1) {\n      DescriptorException ex;\n      ex.getMessage() << \"Descriptor: Unable to read \" << len << \" bytes\";\n      ex.getMessage() << \"at offset \" << offset << \": \";\n      ex.getMessage() << strerror(errno);\n      throw ex;\n    }\n\n    if (ret == 0) {\n      if (poll != 0) {\n        usleep(poll);\n      } else {\n        DescriptorException ex;\n        ex.getMessage() << \"Descriptor: Not enough data to fulfill the request\";\n        throw ex;\n      }\n    }\n\n    left -= ret;\n    off  += ret;\n\n    if (!left) {\n      return;\n    }\n\n    ptr += ret;\n  }\n}\n\n//----------------------------------------------------------------------------\n// Try to read len bytes at offset\n//----------------------------------------------------------------------------\nunsigned Descriptor::tryRead(char* buffer, unsigned len, off_t offset)\n{\n  if (len == 0) {\n    return 0;\n  }\n\n  int   ret;\n  off_t off  = offset;\n  int   left = len;\n  char* ptr  = buffer;\n\n  while (1) {\n    ret = ::pread(pFD, ptr, left, off);\n\n    if (ret == -1) {\n      DescriptorException ex;\n      ex.getMessage() << \"Descriptor: Unable to read \" << len << \" bytes\";\n      ex.getMessage() << \"at offset \" << offset << \": \";\n      ex.getMessage() << strerror(errno);\n      throw ex;\n    }\n\n    if (ret == 0) {\n      return len - left;\n    }\n\n    left -= ret;\n    off  += ret;\n\n    if (!left) {\n      return len;\n    }\n\n    ptr += ret;\n  }\n\n  return len;\n}\n\n//----------------------------------------------------------------------------\n// Write data to the descriptor\n//----------------------------------------------------------------------------\nvoid Descriptor::write(const char* buffer, unsigned len)\n{\n  if (len == 0) {\n    return;\n  }\n\n  int         ret  = 0;\n  int         left = len;\n  const char* ptr  = buffer;\n\n  while (1) {\n    ret = ::write(pFD, ptr, left);\n\n    if (ret == -1  || ret == 0) {\n      DescriptorException ex;\n      ex.getMessage() << \"Descriptor: Unable to write \" << len << \" bytes: \";\n      ex.getMessage() << strerror(errno);\n      throw ex;\n    }\n\n    left -= ret;\n\n    if (!left) {\n      return;\n    }\n\n    ptr += ret;\n  }\n}\n\n//----------------------------------------------------------------------------\n// The same as the ones in the manual\n//----------------------------------------------------------------------------\nvoid Socket::setsockopt(int level, int name, void* value, socklen_t len)\n{\n  if (::setsockopt(pFD, level, name, value, len) == -1) {\n    DescriptorException ex;\n    ex.getMessage() << \"Socket: Unable to set socket option \";\n    ex.getMessage() << level << \"-\" << name << \": \";\n    ex.getMessage() << strerror(errno);\n    throw ex;\n  }\n}\n\n//----------------------------------------------------------------------------\n// The same as the ones in the manual\n//----------------------------------------------------------------------------\nvoid Socket::getsockopt(int level, int name, void* value, socklen_t& len)\n{\n  if (::getsockopt(pFD, level, name, value, &len) == -1) {\n    DescriptorException ex;\n    ex.getMessage() << \"Socket: Unable to set socket option\";\n    ex.getMessage() << level << \"-\" << name << \": \";\n    ex.getMessage() << strerror(errno);\n    throw ex;\n  }\n}\n}\n"
  },
  {
    "path": "namespace/utils/Descriptor.hh",
    "content": "\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// Author: Lukasz Janyst <ljanyst@cern.ch>\n// Date:   07.07.2010\n// File:   Descriptor.hh\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_DESCRIPTOR_HH\n#define EOS_NS_DESCRIPTOR_HH\n\n#include <exception>\n#include <sstream>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <unistd.h>\n\nnamespace eos\n{\n  //----------------------------------------------------------------------------\n  //! Socket exception\n  //----------------------------------------------------------------------------\n  class DescriptorException\n  {\n    public:\n      //------------------------------------------------------------------------\n      //! Constructor\n      //------------------------------------------------------------------------\n      DescriptorException()\n      {\n      }\n\n      //------------------------------------------------------------------------\n      //! Copy constructor\n      //------------------------------------------------------------------------\n      DescriptorException( const DescriptorException& ex )\n      {\n        pMsg << ex.pMsg.str();\n      }\n\n      //------------------------------------------------------------------------\n      //! Get the associated stream\n      //------------------------------------------------------------------------\n      std::ostringstream &getMessage()\n      {\n        return pMsg;\n      }\n\n    private:\n      std::ostringstream pMsg;\n  };\n\n  //----------------------------------------------------------------------------\n  //! A Descriptor\n  //----------------------------------------------------------------------------\n  class Descriptor\n  {\n    public:\n\n      //------------------------------------------------------------------------\n      //! Constructor\n      //------------------------------------------------------------------------\n      Descriptor():\n        pFD( -1 ) {}\n\n      //------------------------------------------------------------------------\n      //! Create a Descriptor from a file descriptor\n      //------------------------------------------------------------------------\n      explicit Descriptor( int fd ):\n        pFD( fd ) {}\n\n      //------------------------------------------------------------------------\n      //! Set descriptor\n      //------------------------------------------------------------------------\n      void setDescriptor( int fd )\n      {\n        pFD = fd;\n      }\n\n      //------------------------------------------------------------------------\n      //! Get descriptor\n      //------------------------------------------------------------------------\n      int getDescriptor()\n      {\n        return pFD;\n      }\n\n      //------------------------------------------------------------------------\n      //! Seek\n      //------------------------------------------------------------------------\n      off_t seek( off_t offset, int whence )\n      {\n        return ::lseek( pFD, offset, whence );\n      }\n\n      //------------------------------------------------------------------------\n      //! Close the descriptor\n      //------------------------------------------------------------------------\n      void close();\n\n      //------------------------------------------------------------------------\n      //! Read the buffer from the blocking descriptor (socket, pipe), it won't\n      //! return until all the requested data is read, or fail if it cannot\n      //! be read\n      //!\n      //! @param buffer buffer pointer\n      //! @param len    length of the buffer\n      //------------------------------------------------------------------------\n      void readBlocking( char *buffer, unsigned len );\n\n      //------------------------------------------------------------------------\n      //! Read the buffer from the non-blocking descriptor (file),\n      //! it won't return until all the requested data is read, when it reaches\n      //! the end it will wait for new data to be appended\n      //!\n      //! @param buffer buffer pointer\n      //! @param len    length of the buffer\n      //! @param poll   poll the descriptor every poll microseconds if the\n      //!               sufficient amount of data is unavailable\n      //!               an exception is thrown it poll is set to 0 and there\n      //!               is no data anymore\n      //------------------------------------------------------------------------\n      void readNonBlocking( char *buffer, unsigned len, unsigned poll = 0 );\n\n      //------------------------------------------------------------------------\n      //! Read the buffer from the non-blocking descriptor (file)\n      //! at given offset, it won't return until all the requested data is read,\n      //! when it reaches the end it will wait for new data to be appended\n      //!\n      //! @param buffer buffer pointer\n      //! @param len    length of the buffer\n      //! @param offset offset\n      //! @param poll   poll the descriptor every poll microseconds if the\n      //!               sufficient amount of data is unavailable\n      //!               an exception is thrown it poll is set to 0 and there\n      //!               is no data anymore\n      //------------------------------------------------------------------------\n      void offsetReadNonBlocking( char *buffer, unsigned len, off_t offset,\n                                  unsigned poll = 0 );\n\n      //------------------------------------------------------------------------\n      //! Try to read len bytes at offset\n      //!\n      //! @param  buffer buffer pointer\n      //! @param  len    length of the buffer\n      //! @param  offset offset\n      //! @return number of available bytes\n      //------------------------------------------------------------------------\n       unsigned tryRead( char *buffer, unsigned len, off_t offset );\n\n      //------------------------------------------------------------------------\n      //! Write data to the descriptor\n      //!\n      //! @param buffer buffer pointer\n      //! @param len    length of the buffer\n      //------------------------------------------------------------------------\n      void write( const char *buffer, unsigned len );\n\n    protected:\n      int pFD;\n  };\n\n  //----------------------------------------------------------------------------\n  //! A network socket\n  //----------------------------------------------------------------------------\n  class Socket: public Descriptor\n  {\n    public:\n      //------------------------------------------------------------------------\n      //! Protocol\n      //------------------------------------------------------------------------\n      enum Protocol\n        {\n          TCP = 0,\n          UDP\n        };\n\n      //------------------------------------------------------------------------\n      //! Constructor\n      //------------------------------------------------------------------------\n      Socket() {}\n\n      //------------------------------------------------------------------------\n      //! Create a socket from a descriptor\n      //------------------------------------------------------------------------\n      explicit Socket( int socket ):\n        Descriptor( socket ) {}\n\n      //------------------------------------------------------------------------\n      //! Initialize the socket\n      //!\n      //! @param proto   protocol type\n      //------------------------------------------------------------------------\n      void init( Protocol proto );\n\n      //------------------------------------------------------------------------\n      //! Connect the socket\n      //!\n      //! @param address hostname or ip address of a server\n      //! @param port    port number\n      //------------------------------------------------------------------------\n      void connect( const char *address, unsigned port );\n\n      //------------------------------------------------------------------------\n      //! Bind to the port\n      //------------------------------------------------------------------------\n      void bind( const char *address, unsigned port );\n\n      //------------------------------------------------------------------------\n      //! Listen to the incomming connections\n      //!\n      //! @param queue backlog queue size\n      //------------------------------------------------------------------------\n      void listen( unsigned queue = 20 );\n\n      //------------------------------------------------------------------------\n      //! Accept connections, allocates memory, the user takes ownership over\n      //! The socket object\n      //------------------------------------------------------------------------\n      Socket *accept();\n\n      //------------------------------------------------------------------------\n      //! Close the socket\n      //------------------------------------------------------------------------\n      void close();\n\n      //------------------------------------------------------------------------\n      //! The same as the ones in the manual\n      //------------------------------------------------------------------------\n      void setsockopt( int level, int name, void *value, socklen_t len );\n\n      //------------------------------------------------------------------------\n      //! The same as the ones in the manual\n      //------------------------------------------------------------------------\n      void getsockopt( int level, int name, void *value, socklen_t &len );\n  };\n};\n\n#endif // EOS_NS_DESCRIPTOR_HH\n"
  },
  {
    "path": "namespace/utils/Etag.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Namespace etag utilities\n//------------------------------------------------------------------------------\n\n#include \"namespace/utils/Etag.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"common/FileId.hh\"\n#include \"common/Fmd.hh\"\n#include \"namespace/utils/Checksum.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Compatibility hack: Use old inodes below 34B, and new above.\n//\n// The old inode scheme breaks down once it reaches 34B files.\n// Yes this is kinda awful.\n//------------------------------------------------------------------------------\nstatic uint64_t findInode(uint64_t fid) {\n  uint64_t threshold = 34'000'000'000ull;\n\n  if(fid < threshold) {\n    return common::FileId::LegacyFidToInode(fid);\n  }\n  else {\n    return common::FileId::NewFidToInode(fid);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Calculate etag - supply flag to indicate whether to use checksum or not.\n//------------------------------------------------------------------------------\nvoid calculateEtag(bool useChecksum, const fst::FmdBase &fmdBase, std::string &out) {\n  if(useChecksum) {\n    return calculateEtagInodeAndChecksum(fmdBase, out);\n  }\n  else {\n    return calculateEtagInodeAndMtime(fmdBase.fid(), fmdBase.mtime(), out);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Calculate etag based on checksum type + fst fmdproto.\n// TODO(gbitzes): Maybe checksumType is not needed? Maybe we can derive\n// checksum type from layout id of fmdproto?\n//------------------------------------------------------------------------------\nvoid calculateEtagInodeAndChecksum(const fst::FmdBase &fmdBase, std::string &out) {\n  if(eos::common::LayoutId::GetChecksum(fmdBase.lid()) != eos::common::LayoutId::kMD5) {\n    // use inode + checksum\n    char setag[256];\n    snprintf(setag, sizeof(setag) - 1, \"\\\"%llu:%s\\\"\",\n              (unsigned long long) findInode(fmdBase.fid()),\n              fmdBase.checksum().c_str());\n    out = setag;\n  } else {\n    // use checksum, S3 wants the pure MD5\n    char setag[256];\n    snprintf(setag, sizeof(setag) - 1, \"\\\"%s\\\"\",\n             fmdBase.checksum().c_str());\n    out = setag;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Calculate etag based on inode + mtime.\n//------------------------------------------------------------------------------\nvoid calculateEtagInodeAndMtime(uint64_t fid, uint64_t mtimeSec, std::string &out) {\n  char setag[256];\n  snprintf(setag, sizeof(setag) - 1, \"\\\"%llu:%llu\\\"\",\n          (unsigned long long) findInode(fid),\n          (unsigned long long) mtimeSec);\n  out = setag;\n}\n\n//------------------------------------------------------------------------------\n// Calculate etag for the given FileMD.\n//------------------------------------------------------------------------------\nvoid calculateEtag(const eos::ns::FileMdProto &proto, std::string &out) {\n  //----------------------------------------------------------------------------\n  // Forced etag?\n  //----------------------------------------------------------------------------\n  constexpr char tmpEtag[] = \"sys.tmp.etag\";\n  if(proto.xattrs().count(tmpEtag)) {\n    out = proto.xattrs().at(tmpEtag);\n    return;\n  }\n\n  //----------------------------------------------------------------------------\n  // Nope. Is there a checksum?\n  //----------------------------------------------------------------------------\n  size_t cxlen = eos::common::LayoutId::GetChecksumLen(proto.layout_id());\n\n  if(cxlen > 0) {\n    //--------------------------------------------------------------------------\n    // Yes, use inode + checksum for the etag.\n    // If MD5 checksums are used we omit the inode number, S3 wants that\n    //--------------------------------------------------------------------------\n    if (eos::common::LayoutId::GetChecksum(proto.layout_id()) == eos::common::LayoutId::kMD5) {\n      out = \"\\\"\";\n      eos::appendChecksumOnStringProtobuf(proto, out);\n      out += \"\\\"\";\n    }\n    else {\n      char setag[256];\n      snprintf(setag, sizeof(setag) - 1, \"\\\"%llu:\", (unsigned long long) findInode(proto.id()));\n      out = setag;\n      eos::appendChecksumOnStringProtobuf(proto, out);\n      out += \"\\\"\";\n    }\n\n    return;\n  }\n\n  //----------------------------------------------------------------------------\n  // Nope, fallback to inode + mtime.\n  //----------------------------------------------------------------------------\n  eos::IFileMD::ctime_t mtime;\n  (void) memcpy(&mtime, proto.mtime().data(), sizeof(eos::IFileMD::ctime_t));\n  calculateEtagInodeAndMtime(proto.id(), mtime.tv_sec, out);\n\n  return;\n}\n\n//------------------------------------------------------------------------------\n// Calculate etag for the given FileMD.\n//------------------------------------------------------------------------------\nvoid calculateEtag(const IFileMD *const fmd, std::string &out) {\n  //----------------------------------------------------------------------------\n  // Forced etag?\n  //----------------------------------------------------------------------------\n  constexpr char tmpEtag[] = \"sys.tmp.etag\";\n  if(fmd->hasAttribute(tmpEtag)) {\n    out = fmd->getAttribute(tmpEtag);\n    return;\n  }\n\n  //----------------------------------------------------------------------------\n  // Nope. Is there a checksum?\n  //----------------------------------------------------------------------------\n  size_t cxlen = eos::common::LayoutId::GetChecksumLen(fmd->getLayoutId());\n\n  if(cxlen > 0) {\n    //--------------------------------------------------------------------------\n    // Yes, use inode + checksum for the etag.\n    // If MD5 checksums are used we omit the inode number, S3 wants that\n    //--------------------------------------------------------------------------\n    if (eos::common::LayoutId::GetChecksum(fmd->getLayoutId()) == eos::common::LayoutId::kMD5) {\n      out = \"\\\"\";\n      eos::appendChecksumOnStringAsHex(fmd, out);\n      out += \"\\\"\";\n    }\n    else {\n      char setag[256];\n      snprintf(setag, sizeof(setag) - 1, \"\\\"%llu:\", (unsigned long long) findInode(fmd->getId()));\n      out = setag;\n      eos::appendChecksumOnStringAsHex(fmd, out);\n      out += \"\\\"\";\n    }\n\n    return;\n  }\n\n  //----------------------------------------------------------------------------\n  // Nope, fallback to inode + mtime.\n  //----------------------------------------------------------------------------\n  eos::IFileMD::ctime_t mtime;\n  fmd->getCTime(mtime);\n  calculateEtagInodeAndMtime(fmd->getId(), mtime.tv_sec, out);\n  return;\n}\n\n//----------------------------------------------------------------------------\n// Calculate etag for the given ContainerMD.\n//----------------------------------------------------------------------------\nvoid calculateEtag(IContainerMD *cmd, std::string &out) {\n  //----------------------------------------------------------------------------\n  // Forced etag?\n  //----------------------------------------------------------------------------\n  constexpr char tmpEtag[] = \"sys.tmp.etag\";\n  if(cmd->hasAttribute(tmpEtag)) {\n    out = cmd->getAttribute(tmpEtag);\n    return;\n  }\n\n  //----------------------------------------------------------------------------\n  // Use inode + tmtime\n  //----------------------------------------------------------------------------\n  eos::IFileMD::ctime_t mtime;\n  cmd->getTMTime(mtime);\n\n  char setag[256];\n  snprintf(setag, sizeof(setag) - 1, \"%llx:%llu.%03lu\",\n          (unsigned long long) cmd->getId(),\n          (unsigned long long) mtime.tv_sec,\n          (unsigned long) mtime.tv_nsec / 1000000);\n\n  out = setag;\n  return;\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/utils/Etag.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Georgios Bitzes <georgios.bitzes@cern.ch>\n// desc:   Namespace etag utilities\n//------------------------------------------------------------------------------\n\n#pragma once\n#include <string>\n#include \"proto/FileMd.pb.h\"\n\nnamespace eos\n{\nnamespace fst\n{\nclass FmdBase;\n}\n\nclass IFileMD;\nclass IContainerMD;\n\n//----------------------------------------------------------------------------\n//! Calculate etag for the given FileMD.\n//----------------------------------------------------------------------------\nvoid calculateEtag(const IFileMD* const fmd, std::string& out);\nvoid calculateEtag(const eos::ns::FileMdProto& proto, std::string& out);\n\n//----------------------------------------------------------------------------\n//! Calculate etag for the given ContainerMD.\n//! TODO(gbitzes): Make cmd const?\n//----------------------------------------------------------------------------\nvoid calculateEtag(IContainerMD* cmd, std::string& out);\n\n//----------------------------------------------------------------------------\n//! Calculate etag - supply flag to indicate whether to use checksum or not.\n//----------------------------------------------------------------------------\nvoid calculateEtag(bool useChecksum, const fst::FmdBase& fmdBase,\n                   std::string& out);\n\n//----------------------------------------------------------------------------\n//! Calculate etag based on fst fmdproto - assume checksum exists.\n//----------------------------------------------------------------------------\nvoid calculateEtagInodeAndChecksum(const fst::FmdBase& fmdBase,\n                                   std::string& out);\n\n//----------------------------------------------------------------------------\n//! Calculate etag based on inode + mtime.\n//----------------------------------------------------------------------------\nvoid calculateEtagInodeAndMtime(uint64_t fid, uint64_t mtimeSec,\n                                std::string& out);\n}\n"
  },
  {
    "path": "namespace/utils/FileListRandomPicker.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief File list random picker\n//------------------------------------------------------------------------------\n\n#include \"namespace/interface/IFsView.hh\"\n#include \"FileListRandomPicker.hh\"\n#include <mutex>\n#include \"common/utils/RandUtils.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\nbool pickRandomFile(const IFsView::FileList &filelist, eos::IFileMD::id_t &retval) {\n  if(filelist.empty()) {\n    return false;\n  }\n\n\n  while(true) {\n    eos::IFileMD::id_t randomPosition = eos::common::getRandom<uint64_t>(0, filelist.bucket_count() - 1);\n\n    // Is there an element at that location on the table?\n    auto it = filelist.begin(randomPosition);\n    if(it != filelist.end(randomPosition)) {\n      retval = *it;\n      return true;\n    }\n  }\n}\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/utils/FileListRandomPicker.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief File list random picker\n//------------------------------------------------------------------------------\n\n#include \"namespace/Namespace.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\nbool pickRandomFile(const IFsView::FileList &filelist, eos::IFileMD::id_t &retval);\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/utils/LocalityHint.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Helper class to generate locality hints\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_LOCALITY_HINT_HH\n#define EOS_NS_LOCALITY_HINT_HH\n\n#include <iostream>\n#include \"namespace/interface/Identifiers.hh\"\n#include \"namespace/Namespace.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\nclass LocalityHint {\npublic:\n\n  static std::string build(ContainerIdentifier parent, const std::string &name) {\n    std::ostringstream ss;\n    ss << unsignedIntToBinaryString(parent.getUnderlyingUInt64());\n    ss << \":\" << name;\n\n    return ss.str();\n  }\n\nprivate:\n  static inline void unsignedIntToBinaryString(uint64_t num, char* buff) {\n    uint64_t be = htobe64(num);\n    memcpy(buff, &be, sizeof(be));\n  }\n\n  static std::string unsignedIntToBinaryString(uint64_t num) {\n    char buff[sizeof(num)];\n    unsignedIntToBinaryString(num, buff);\n    return std::string(buff, sizeof(num));\n  }\n\n};\n\n\nEOSNSNAMESPACE_END\n\n#endif\n"
  },
  {
    "path": "namespace/utils/Mode.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Georgios Bitzes <georgios.bitzes@cern.ch>\n// desc:   Namespace mode utilities\n//------------------------------------------------------------------------------\n\n#pragma once\n#include <sys/stat.h>\n#include \"common/Logging.hh\"\n\n#define S_XATTR 0xa0000000\n\nnamespace eos\n{\n//------------------------------------------------------------------------------\n//! Convert mode to its corresponding character in a listing.\n//! eg S_IFDIR is converted to 'd'\n//------------------------------------------------------------------------------\ninline char modeToFileTypeChar(mode_t mode)\n{\n  unsigned int filetype = S_IFMT & mode;\n\n  switch (filetype) {\n  case S_IFIFO:\n    return 'p';\n\n  case S_IFCHR:\n    return 'c';\n\n  case S_IFDIR:\n    return 'd';\n\n  case S_IFBLK:\n    return 'b';\n\n  case S_IFREG:\n    return '-';\n\n  case S_IFLNK:\n    return 'l';\n\n  case S_IFSOCK:\n    return 's';\n\n  default: {\n    // TODO(gbitzes): Eventually convert this to an assertion?\n    eos_static_crit(\n      \"Unable to translate mode to filetype char. mode=%d, filetype=%d\",\n      mode, filetype);\n    return '-';\n  }\n  }\n}\n\ninline void modeToBuffer(mode_t mode, char* modestr)\n{\n  // TODO(gbitzes): This method is very branchy. It might make sense to put\n  // all possible values into a map, and simply look up the resulting string.\n  strcpy(modestr, \"----------\");\n  modestr[0] = modeToFileTypeChar(mode);\n\n  if (mode & S_IRUSR) {\n    modestr[1] = 'r';\n  }\n\n  if (mode & S_IWUSR) {\n    modestr[2] = 'w';\n  }\n\n  if (mode & S_IXUSR) {\n    modestr[3] = 'x';\n  }\n\n  if (mode & S_IRGRP) {\n    modestr[4] = 'r';\n  }\n\n  if (mode & S_IWGRP) {\n    modestr[5] = 'w';\n  }\n\n  if (mode & S_IXGRP) {\n    modestr[6] = 'x';\n  }\n\n  if (mode & S_IROTH) {\n    modestr[7] = 'r';\n  }\n\n  if (mode & S_IWOTH) {\n    modestr[8] = 'w';\n  }\n\n  if (mode & S_IXOTH) {\n    modestr[9] = 'x';\n  }\n\n  if (mode & S_ISVTX) {\n    if (mode & S_XATTR) {\n      modestr[9] = 'T';\n    } else {\n      modestr[9] = 't';\n    }\n  } else {\n    if (mode & S_XATTR) {\n      modestr[9] = '+';\n    }\n  }\n\n  if (mode & S_ISUID) {\n    modestr[3] = 's';\n  }\n\n  if (mode & S_ISGID) {\n    modestr[6] = 's';\n  }\n}\n}\n"
  },
  {
    "path": "namespace/utils/PathProcessor.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Lukasz Janyst <ljanyst@cern.ch>\n// desc:   ChangeLog like store\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_PATH_PROCESSOR_HH\n#define EOS_NS_PATH_PROCESSOR_HH\n\n#include <cstring>\n#include <vector>\n#include <string>\n#include <list>\n#include <sstream>\n#include \"common/StringSplit.hh\"\nnamespace eos\n{\n//----------------------------------------------------------------------------\n//! Helper class responsible for spliting the path\n//----------------------------------------------------------------------------\nclass PathProcessor\n{\npublic:\n\n  //------------------------------------------------------------------------\n  //! Split the path and prepend its elements into a deque.\n  //------------------------------------------------------------------------\n  static std::deque<std::string> insertChunksIntoDeque(std::string_view path)\n  {\n    return eos::common::SplitPath<std::deque<std::string>>(path);\n  }\n\n  static void insertChunksIntoDeque(std::deque<std::string>& elements,\n                                    std::string_view path)\n  {\n    if (elements.empty()) {\n      elements = insertChunksIntoDeque(path);\n      return;\n    }\n\n    auto vpath = eos::common::SplitPath(path);\n    // This is to ensure old_path = [a,b] new_path = [c,d,e] -> [c,d,e,a,b]\n    std::move(vpath.rbegin(), vpath.rend(), std::front_inserter(elements));\n  }\n\n  // The following function is only used in ns_in_memory and not preferred for\n  // newer apis\n  //------------------------------------------------------------------------\n  //!Split the path and put its element in a vector, the split is done !\n  //in-place and the buffer is overwritten\n  //------------------------------------------------------------------------\n  static void splitPath(std::vector<char*>& elements, char* buffer)\n  {\n    elements.clear();\n    elements.reserve(10);\n    char* cursor = buffer;\n    char* beg    = buffer;\n\n    //----------------------------------------------------------------------\n    // Go by the characters one by one\n    //----------------------------------------------------------------------\n    while (*cursor) {\n      if (*cursor == '/') {\n        *cursor = 0;\n\n        if (beg != cursor) {\n          elements.push_back(beg);\n        }\n\n        beg = cursor + 1;\n      }\n\n      ++cursor;\n    }\n\n    if (beg != cursor) {\n      elements.push_back(beg);\n    }\n  }\n\n  //------------------------------------------------------------------------\n  //! Absolute Path sanitizing all '/../' and '/./' entries\n  //------------------------------------------------------------------------\n  static void absPath(std::string& mypath)\n  {\n    std::vector<std::string> abs_path;\n    std::vector<std::string> elements = eos::common::SplitPath(mypath);\n    std::ostringstream oss;\n    int skip = 0;\n\n    for (auto it = elements.rbegin(); it != elements.rend(); ++it) {\n      if ((*it == \".\") || it->empty()) {\n        continue;\n      }\n\n      if (*it == \"..\") {\n        ++skip;\n        continue;\n      }\n\n      if (skip) {\n        --skip;\n        continue;\n      }\n\n      abs_path.push_back(*it);\n    }\n\n    for (auto it = abs_path.rbegin(); it != abs_path.rend(); ++it) {\n      oss << \"/\" << *it;\n    }\n\n    mypath = oss.str();\n\n    if (mypath.empty()) {\n      mypath = \"/\";\n    }\n  }\n};\n}\n\n#endif // EOS_NS_PATH_PROCESSOR_HH\n"
  },
  {
    "path": "namespace/utils/RenameSafetyCheck.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Helper function to check if it's safe to rename a directory into\n//!        another\n//------------------------------------------------------------------------------\n\n#ifndef EOS_NS_RENAME_SAFETY_CHECK_HH\n#define EOS_NS_RENAME_SAFETY_CHECK_HH\n\n#include <iostream>\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IView.hh\"\n#include \"namespace/Namespace.hh\"\n#include \"common/Logging.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Is it safe to make \"source\" directory a subdirectory of \"target\"?\n// Assumes source and target are at-least read-locked when calling this function OR\n// assumes that the eosViewRWMutex is at-least read-locked when calling this function.\n//------------------------------------------------------------------------------\nbool isSafeToRename(IView *view, IContainerMD *source, IContainerMD *target) {\n  if(source == target) return false;\n\n  IContainerMDSvc *svc = view->getContainerMDSvc();\n  IContainerMDPtr current = svc->getContainerMD(target->getParentId());\n\n  size_t iterations = 0;\n  while(true) {\n    iterations++;\n\n    if(iterations > 1024) {\n      std::string msg = SSTR(\"potential loop when scanning parents of container \"\n      << target->getId() << \" - serious namespace corruption\");\n\n      eos_static_crit(\"%s\", msg.c_str());\n      throw_mdexception(EFAULT, msg);\n    }\n\n    if(current.get() == source) {\n      return false; // Nope, sound alarm, this rename is not safe\n    }\n\n    if(current->getId() == source->getId()) {\n      // Should not happen.\n      eos_static_crit(\"%s\", SSTR(\"Two containers with the same ID ended up with different objects in memory - \" <<\n      current->getId() << \" == \" << source->getId() << \" - \" << current << \" vs \" << source).c_str());\n      return false;\n    }\n\n    if(current->getId() == 1) {\n      // We've reached root, this rename looks safe.\n      return true;\n    }\n\n    // Move up one step.\n    current = svc->getContainerMD(current->getParentId());\n  }\n}\n\nEOSNSNAMESPACE_END\n\n#endif"
  },
  {
    "path": "namespace/utils/RmrfHelper.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Georgios Bitzes <georgios.bitzes@cern.ch>\n// desc:   A helper function to run non-atomic \"rm -rf\" on a path\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/interface/IFileMD.hh\"\n#include \"namespace/interface/ContainerIterators.hh\"\n#include \"namespace/Namespace.hh\"\n\nEOSNSNAMESPACE_BEGIN\n\nclass RmrfHelper\n{\npublic:\n\n//------------------------------------------------------------------------------\n// Run rm -rf on a given directory path.\n//------------------------------------------------------------------------------\n  static void nukeDirectory(eos::IView* view, const std::string& path)\n  {\n    IFileMDSvc* fileSvc = view->getFileMDSvc();\n    std::shared_ptr<eos::IContainerMD> cont = view->getContainer(path);\n    std::shared_ptr<eos::IFileMD> file;\n\n    for (auto itf = eos::FileMapIterator(cont); itf.valid(); itf.next()) {\n      file = fileSvc->getFileMD(itf.value());\n\n      if (file) {\n        fileSvc->removeFile(file.get());\n      }\n    }\n\n    std::vector<std::string> subcontainers;\n\n    for (auto itc = eos::ContainerMapIterator(cont); itc.valid(); itc.next()) {\n      std::ostringstream newpath;\n      newpath << path;\n\n      if (path[path.size() - 1] != '/') {\n        newpath << \"/\";\n      }\n\n      newpath << itc.key();\n      subcontainers.emplace_back(newpath.str());\n    }\n\n    for (size_t i = 0; i < subcontainers.size(); i++) {\n      nukeDirectory(view, subcontainers[i]);\n    }\n\n    view->removeContainer(path);\n  }\n};\n\nEOSNSNAMESPACE_END\n"
  },
  {
    "path": "namespace/utils/Stat.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Georgios Bitzes <georgios.bitzes@cern.ch>\n// desc:   Namespace stat utilities\n//------------------------------------------------------------------------------\n\n#pragma once\n#include \"common/FileSystem.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"namespace/utils/Mode.hh\"\n\nnamespace eos\n{\n\n//----------------------------------------------------------------------------\n//! Helper function for deriving the mode_t from a ContainerMD.\n//----------------------------------------------------------------------------\ninline mode_t modeFromMetadataEntry(const std::shared_ptr<eos::IContainerMD> &cmd)\n{\n  mode_t retval = cmd->getMode();\n  \n  if (cmd->hasAttribute(\"sys.acl\") || cmd->hasAttribute(\"user.acl\")) {\n    retval |= S_XATTR;\n  }\n  \n  return retval;\n}\n\n//----------------------------------------------------------------------------\n//! Helper function for deriving the mode_t from a FileMD.\n//----------------------------------------------------------------------------\ninline mode_t modeFromMetadataEntry(const std::shared_ptr<eos::IFileMD>& fmd)\n{\n  mode_t retval;\n\n  // Symbolic link?\n  if (fmd->isLink()) {\n    retval = (S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO);\n    return retval;\n  }\n\n  // Not a symbolic link\n  retval = S_IFREG;\n  uint16_t flags = fmd->getFlags();\n\n  if (!flags) {\n    retval |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR);\n  } else {\n    retval |= flags;\n  }\n\n  if (fmd->hasLocation(EOS_TAPE_FSID)) {\n    retval |= EOS_TAPE_MODE_T;\n  }\n\n  return retval;\n}\n} // namespace eos\n"
  },
  {
    "path": "namespace/utils/StringConvertion.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//! @brief Function for doing fast convertion to string\n//------------------------------------------------------------------------------\n\n#ifndef __EOS_NS_STRINGCONVERTION_HH__\n#define __EOS_NS_STRINGCONVERTION_HH__\n\n#include \"namespace/Namespace.hh\"\n#include \"fmt/format.h\"\n\nEOSNSNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Fast convert element to string representation\n//!\n//! @param elem element to be converted\n//!\n//! @return string representation\n//------------------------------------------------------------------------------\ntemplate <typename T>\nstd::string stringify(const T& elem)\n{\n  return fmt::to_string(elem);\n}\n\nEOSNSNAMESPACE_END\n#endif // __EOS_NS_STRINGCONVERTION_HH__\n"
  },
  {
    "path": "nginx/README",
    "content": "#-------------------------------------------------------------------------------\n# Copyright (C) 2013 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n#-------------------------------------------------------------------------------\n\nCustom nginx build for EOS.\n\nKerberos support is enabled and pulled from the following github repository:\nhttp://github.com/stnoonan/spnego-http-auth-nginx-module\n\nThe nginx configure system has also been patched to allow it to be built into\nan RPM. \n\nTo build:\n\n    ./makesrpm.sh\n    rpmbuild --rebuild eos-nginx-1.0.15-1.src.rpm"
  },
  {
    "path": "nginx/etc/init.d/nginx.init",
    "content": "#!/bin/sh\n#\n# nginx - this script starts and stops the nginx daemon\n#\n# chkconfig:   - 85 15 \n# description: Nginx is an HTTP(S) server, HTTP(S) reverse \\\n#              proxy and IMAP/POP3 proxy server\n# processname: nginx\n# config:      /etc/nginx/nginx.conf\n# config:      /etc/sysconfig/nginx\n# pidfile:     /var/run/nginx.pid\n\n# Source function library.\n. /etc/rc.d/init.d/functions\n\n# Source networking configuration.\n. /etc/sysconfig/network\n\n# Check that networking is up.\n[ \"$NETWORKING\" = \"no\" ] && exit 0\n\nnginx=\"/usr/sbin/nginx\"\nprog=$(basename $nginx)\n\nNGINX_CONF_FILE=\"/etc/nginx/nginx.conf\"\n\n[ -f /etc/sysconfig/nginx ] && . /etc/sysconfig/nginx\n\nlockfile=/var/lock/subsys/nginx\n\nstart() {\n    [ -x $nginx ] || exit 5\n    [ -f $NGINX_CONF_FILE ] || exit 6\n    echo -n $\"Starting $prog: \"\n    daemon $nginx -c $NGINX_CONF_FILE\n    retval=$?\n    echo\n    [ $retval -eq 0 ] && touch $lockfile\n    return $retval\n}\n\nstop() {\n    echo -n $\"Stopping $prog: \"\n    killproc $prog -QUIT\n    retval=$?\n    echo\n    [ $retval -eq 0 ] && rm -f $lockfile\n    return $retval\n}\n\nrestart() {\n    configtest || return $?\n    stop\n    sleep 1\n    start\n}\n\nreload() {\n    configtest || return $?\n    echo -n $\"Reloading $prog: \"\n    killproc $nginx -HUP\n    RETVAL=$?\n    echo\n}\n\nforce_reload() {\n    restart\n}\n\nconfigtest() {\n  $nginx -t -c $NGINX_CONF_FILE\n}\n\nrh_status() {\n    status $prog\n}\n\nrh_status_q() {\n    rh_status >/dev/null 2>&1\n}\n\ncase \"$1\" in\n    start)\n        rh_status_q && exit 0\n        $1\n        ;;\n    stop)\n        rh_status_q || exit 0\n        $1\n        ;;\n    restart|configtest)\n        $1\n        ;;\n    reload)\n        rh_status_q || exit 7\n        $1\n        ;;\n    force-reload)\n        force_reload\n        ;;\n    status)\n        rh_status\n        ;;\n    condrestart|try-restart)\n        rh_status_q || exit 0\n\t    ;;\n    *)\n        echo $\"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload|configtest}\"\n        exit 2\nesac\n"
  },
  {
    "path": "nginx/etc/logrotate.d/nginx.logrotate",
    "content": "/var/log/nginx/*log {\n    daily\n    rotate 10\n    missingok\n    notifempty\n    compress\n    sharedscripts\n    postrotate\n        [ ! -f /var/run/nginx.pid ] || kill -USR1 `cat /var/run/nginx.pid`\n    endscript\n}\n\n"
  },
  {
    "path": "nginx/etc/nginx/nginx.eos.conf.template",
    "content": "#-------------------------------------------------------------------------------\n# Copyright (c) 2012-2013 by European Organization for Nuclear Research (CERN)\n#\n# File: nginx.eos.conf\n# Author: Justin Salmon <jsalmon@cern.ch>\n#-------------------------------------------------------------------------------\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Lesser General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public License\n# along with This program.  If not, see <http:#www.gnu.org/licenses/>.\n#-------------------------------------------------------------------------------\n\n#-------------------------------------------------------------------------------\n# The EOS nginx service uses two virtual hosts with different authentication\n# schemes to allow flexible authenticated access.\n#\n# The first vhost uses SSL with (optional) client certificate verification. If\n# the client provides no certificate (or an invalid one) then limited access\n# will still be granted (as user \"nobody\") and the traffic will still be \n# encrypted via SSL. The default port for this is 443 (in /etc/sysconfig/nginx)\n# \n# The second vhost runs Kerberos authentication over SSL. It does not ask for\n# or attempt to verify a client SSL certificate. The Kerberos credentials\n# must be supplied, otherwise access will be denied (the gateway will return a\n# 401 Authorization Required code). The default port for this is 8443.\n#-------------------------------------------------------------------------------\n\n\nuser              EOS_NGINX_USER;\nworker_processes  64;\nerror_log         /var/log/nginx/error.log info;\npid               /var/run/nginx.pid;\n\nenv OPENSSL_ALLOW_PROXY_CERTS;\n\nevents {\n    worker_connections  1024;\n}\n\nhttp {\n    default_type  application/octet-stream;\n\n    log_format    main '$remote_addr - $remote_user [$time_local] \"$request\" '\n                       '$status $body_bytes_sent \"$http_referer\" '\n                       '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n    access_log         /var/log/nginx/access.log \n                       main;\n\n    sendfile               on;\n    keepalive_timeout      65;\n    client_max_body_size   1000000m;\n    #tcp_nopush            on;\n    #gzip                  on;\n    #keepalive_timeout     0;\n    \n    auth_ldap_cache_enabled on;\n    auth_ldap_cache_expiration_time 10000;\n    auth_ldap_cache_size 1000;\n\n    # Load config files from the /etc/nginx/conf.d directory\n    # The default server is in conf.d/default.conf\n    # include /etc/nginx/conf.d/*.conf;\n\n    ldap_server auth_ldap_server {\n        url \"EOS_NGINX_LDAP_URL\";\n        binddn \"EOS_NGINX_LDAP_BINDDN_USER\";\n        binddn_passwd \"EOS_NGINX_LDAP_BINDDN_PASSWORD\";\n        require valid_user;\n\tsatisfy any;\n    }\n\n\n    # Virtual host 1: SSL with client certificate authentication\n    server {\n        server_name  EOS_NGINX_HOSTNAME;\n        listen       EOS_NGINX_CLIENT_SSL_PORT;\n\n        ssl                    on;\n        ssl_protocols          SSLv3 TLSv1 TLSv1.1 TLSv1.2;\n        ssl_certificate        EOS_NGINX_SSL_CERTIFICATE;\n        ssl_certificate_key    EOS_NGINX_SSL_KEY;\n        ssl_client_certificate EOS_NGINX_SSL_CLIENT_CERTIFICATE;\n        ssl_verify_client      optional;\n        ssl_verify_depth       10;\n\tssl_ciphers            ALL:!kEDH!ADH:RC4+RSA:+HIGH:+EXP;\t\n\tssl_session_cache      shared:SSL:10m;\n        ssl_session_timeout    10m;\n\t\n        location / {\n\n            proxy_pass         EOS_NGINX_MGM_URL;\n\n            proxy_set_header   Host               $host;\n\t    proxy_set_header   Auth-Type          \"x509\";\n            proxy_set_header   X-Real-IP          $remote_addr;\n            proxy_set_header   X-Forwarded-For    $proxy_add_x_forwarded_for;\n            proxy_set_header   SSL_CLIENT_S_DN    $ssl_client_s_dn;\n            proxy_set_header   SSL_CLIENT_I_DN    $ssl_client_i_dn;\n            proxy_set_header   SSL_CLIENT_VERIFY  $ssl_client_verify;\n\t    proxy_pass_request_body off;\n\t    \n            client_body_buffer_size     128k;\n\n            proxy_connect_timeout       90;\n            proxy_send_timeout          90;\n            proxy_read_timeout          90;\n\n            proxy_buffer_size           256k;\n            proxy_buffers               8 256k;\n            proxy_busy_buffers_size     512k;\n            proxy_temp_file_write_size  512k;\n        }\n\n        # Proxy download \n        location ~* ^/internal_redirect/(.*):(.*?)/(.*) {\n\n            # Do not allow people to mess with this location directly\n            # Only internal redirects are allowed\n            internal;\n            \n            # Necessary when using variable proxy_pass directive\n            resolver 127.0.0.1;\n\n            # Location-specific logging\n            access_log /var/log/nginx/x509.internal_redirect.access.log main;\n            error_log  /var/log/nginx/x509.internal_redirect.error.log  ;\n\n            # Extract download url from the request\n            set $download_host $1;\n            set $download_port $2;\n            set $download_uri  $3;\n\n            # Compose download url\n            set $download_url http://$download_host:$download_port/$download_uri?$args;\n\n            # Set download request headers\n            proxy_set_header Host          $download_host:$download_port;\n            proxy_set_header Authorization '';\n\n            # The next two lines could be used if your storage \n            # backend does not support Content-Disposition \n            # headers used to specify file name browsers use \n            # when save content to the disk\n            # proxy_hide_header Content-Disposition;\n            # add_header Content-Disposition 'attachment; filename=\"$args\"';\n\n            # Do not touch local disks when proxying \n            # content to clients\n            proxy_max_temp_file_size 0;\n\n\n            # If we are redirecting externally, return the redirect response\n            # back to the client.\n            set $redirect_externally EOS_NGINX_REDIRECT_EXTERNALLY;\n            if ($redirect_externally)\n            {\n                return 307 $download_url;\n            }\n\n            # Otherwise, download the file directly from the backend and stream\n            # it to the client.\n            proxy_pass $download_url;\n        }\n    }\n\n\n    # Virtual host 2: SSL with kerberos authentication\n    server {\n        server_name  EOS_NGINX_HOSTNAME;\n        listen       EOS_NGINX_GSS_PORT;\n\n        ssl                    on;\n        ssl_protocols          SSLv3 TLSv1 TLSv1.1 TLSv1.2;\n        ssl_certificate        EOS_NGINX_SSL_CERTIFICATE;\n        ssl_certificate_key    EOS_NGINX_SSL_KEY;\n        ssl_client_certificate EOS_NGINX_SSL_CLIENT_CERTIFICATE;\n        ssl_verify_client      off;\n        ssl_verify_depth       10;\n\tssl_ciphers            ALL:!kEDH!ADH:RC4+RSA:+HIGH:+EXP;\t\n\tssl_session_cache      shared:SSL:10m;\n        ssl_session_timeout    10m;\n\n        location / {\n\n            proxy_pass         EOS_NGINX_MGM_URL;\n\n            proxy_set_header   Host               $host;\n\t    proxy_set_header   Auth-Type          \"krb5\";\n            proxy_set_header   X-Real-IP          $remote_addr;\n            proxy_set_header   X-Forwarded-For    $proxy_add_x_forwarded_for;\n\t    proxy_pass_request_body off;\n\t    \n            client_body_buffer_size     128k;\n\n            proxy_connect_timeout       90;\n            proxy_send_timeout          90;\n            proxy_read_timeout          90;\n\n            proxy_buffer_size           256k;\n            proxy_buffers               8 256k;\n            proxy_busy_buffers_size     512k;\n            proxy_temp_file_write_size  512k;\n\n            # Kerberos\n            auth_gss              on;\n            auth_gss_keytab       EOS_NGINX_GSS_KEYTAB;\n            auth_gss_realm        EOS_NGINX_GSS_REALM;\n            auth_gss_service_name EOS_NGINX_GSS_SERVICE_NAME;\n\n            proxy_set_header      Remote-User $remote_user;\n        }\n\n        # Proxy download \n        location ~* ^/internal_redirect/(.*?)/(.*) {\n\n            # Do not allow people to mess with this location directly\n            # Only internal redirects are allowed\n            internal;\n            \n            # Necessary when using variable proxy_pass directive\n            resolver 127.0.0.1;\n\n            # Location-specific logging\n            access_log /var/log/nginx/krb5.internal_redirect.access.log main;\n            error_log  /var/log/nginx/krb5.internal_redirect.error.log  ;\n\n            # Extract download url from the request\n            set $download_uri  $2;\n            set $download_host $1;\n\n            # Compose download url\n            set $download_url http://$download_host/$download_uri?$args;\n\n            # Set download request headers\n            proxy_set_header Host          $download_host;\n            proxy_set_header Authorization '';\n\n            # The next two lines could be used if your storage \n            # backend does not support Content-Disposition \n            # headers used to specify file name browsers use \n            # when save content to the disk\n            # proxy_hide_header Content-Disposition;\n            # add_header Content-Disposition 'attachment; filename=\"$args\"';\n\n            # Do not touch local disks when proxying \n            # content to clients\n            proxy_max_temp_file_size 0;\n\n\n            # If we are redirecting externally, return the redirect response\n            # back to the client.\n            set $redirect_externally EOS_NGINX_REDIRECT_EXTERNALLY;\n            if ($redirect_externally)\n            {\n                return 307 $download_url;\n            }\n\n            # Otherwise, download the file directly from the backend and stream\n            # it to the client.\n            proxy_pass $download_url;\n        }\n    }\n\n    # Virtual host 3: SSL with ldap basic authentication\n    server {\n        server_name  EOS_NGINX_HOSTNAME;\n        listen       EOS_NGINX_LDAP_SSL_PORT;\n\n        ssl                    on;\n        ssl_protocols          SSLv3 TLSv1 TLSv1.1 TLSv1.2;\n        ssl_certificate        EOS_NGINX_SSL_CERTIFICATE;\n        ssl_certificate_key    EOS_NGINX_SSL_KEY;\n        ssl_client_certificate EOS_NGINX_SSL_CLIENT_CERTIFICATE;\n        ssl_verify_client      off;\n        ssl_verify_depth       10;\n\tssl_ciphers            ALL:!kEDH!ADH:RC4+RSA:+HIGH:+EXP;\t\n\tssl_session_cache      shared:SSL:10m;\n        ssl_session_timeout    10m;\n\n\n        # allow Owncloud clients to retrieve the server version without authentication\n\tlocation ~* /(status.php)$ {\n\n            proxy_pass         EOS_NGINX_MGM_URL;\n\n            proxy_set_header   Host               $host;\n\t    proxy_set_header   Auth-Type          \"ldap\";\n            proxy_set_header   X-Real-IP          $remote_addr;\n            proxy_set_header   X-Forwarded-For    $proxy_add_x_forwarded_for;\n            proxy_set_header   Remote-User nobody;\n            proxy_set_header   Authorization '';\n        }\n\n        location / {\n            proxy_pass         EOS_NGINX_MGM_URL;\n            proxy_set_header   Authorization '';\n\n            proxy_set_header   Host               $host;\n\t    proxy_set_header   Auth-Type          \"ldap\";\n            proxy_set_header   X-Real-IP          $remote_addr;\n            proxy_set_header   X-Forwarded-For    $proxy_add_x_forwarded_for;\n            proxy_set_header      Remote-User $remote_user;\n\t    proxy_pass_request_body off;\n            client_body_buffer_size     128k;\n\n            proxy_connect_timeout       90;\n            proxy_send_timeout          90;\n            proxy_read_timeout          90;\n\n            proxy_buffer_size           256k;\n            proxy_buffers               8 256k;\n            proxy_busy_buffers_size     512k;\n            proxy_temp_file_write_size  512k;\n   \n            auth_ldap \"Please provide your account information here:\";\n            auth_ldap_servers auth_ldap_server;\n        }\n\n        # Proxy download \n        location ~* ^/internal_redirect/(.*):(.*?)/(.*) {\n\n            # Do not allow people to mess with this location directly\n            # Only internal redirects are allowed\n            internal;\n            \n            # Necessary when using variable proxy_pass directive\n            resolver 127.0.0.1;\n\n            # Location-specific logging\n            access_log /var/log/nginx/ldap.internal_redirect.access.log main;\n            error_log  /var/log/nginx/ldap.internal_redirect.error.log  ;\n\n            # Extract download url from the request\n            set $download_host $1;\n            set $download_port $2;\n            set $download_uri  $3;\n\n            # Compose download url\n            set $download_url http://$download_host:$download_port/$download_uri?$args;\n\n            # Set download request headers\n            proxy_set_header Host          $download_host:$download_port;\n            proxy_set_header Authorization '';\n\n            # The next two lines could be used if your storage \n            # backend does not support Content-Disposition \n            # headers used to specify file name browsers use \n            # when save content to the disk\n            # proxy_hide_header Content-Disposition;\n            # add_header Content-Disposition 'attachment; filename=\"$args\"';\n\n            # Do not touch local disks when proxying \n            # content to clients\n            proxy_max_temp_file_size 0;\n\n\n            # If we are redirecting externally, return the redirect response\n            # back to the client.\n            set $redirect_externally EOS_NGINX_REDIRECT_EXTERNALLY;\n            if ($redirect_externally)\n            {\n                return 307 $download_url;\n            }\n\n            # Otherwise, download the file directly from the backend and stream\n            # it to the client.\n            proxy_pass $download_url;\n        }\n    }\n\n\n    # Virtual host to redirect all plain HTTP requests to HTTPS ones\n    server {\n       server_name    EOS_NGINX_HOSTNAME;\n       listen         80;\n       rewrite        ^ https://$server_name$request_uri? permanent;\n    }\n}\n"
  },
  {
    "path": "nginx/etc/sysconfig/nginx.sysconfig",
    "content": "# Configuration file for the nginx service\n\nNGINX_CONF_FILE=/etc/nginx/nginx.eos.conf\n\n# User to run nginx under\nexport EOS_NGINX_USER=daemon\n\n# Hostname to use for nginx vhosts\nexport EOS_NGINX_HOSTNAME=`hostname -f`\n\n# Proxy Forwarding Target e.g. MGM URL to proxy, don't end the URL with a '/' !\nexport EOS_NGINX_MGM_URL=http://localhost:8000\n\n# nginx SSL configuration\nexport EOS_NGINX_SSL_CERTIFICATE=/etc/grid-security/hostcert.pem\nexport EOS_NGINX_SSL_KEY=/etc/grid-security/hostkey.pem\n# nginx-bundle should contain CERN CA plus all default CAs (Verisign, etc)\nexport EOS_NGINX_SSL_CLIENT_CERTIFICATE=/etc/grid-security/certificates/nginx-bundle.pem\n\n# Port to enable client certificate authentication on\nexport EOS_NGINX_CLIENT_SSL_PORT=443\n# Enable openssl to use proxy certificates\nexport OPENSSL_ALLOW_PROXY_CERTS=1\n\n# Port to enable kerberos authentication on\nexport EOS_NGINX_GSS_PORT=8443\n# nginx kerberos configuration\nexport EOS_NGINX_GSS_KEYTAB=/etc/krb5.keytab\nexport EOS_NGINX_GSS_REALM=CERN.CH\nexport EOS_NGINX_GSS_SERVICE_NAME=host/$EOS_NGINX_HOSTNAME\n\n# nginx LDAP basic auth configuration\nexport EOS_NGINX_LDAP_URL=\"ldaps://cerndc.cern.ch/OU=Users,OU=Organic Units,DC=cern,DC=ch?samaccountname?sub?(objectClass=user)\"\nexport EOS_NGINX_LDAP_BINDDN_USER=\"TEST\"\nexport EOS_NGINX_LDAP_BINDDN_PASSWORD=\"TESTPWD\"\nexport EOS_NGINX_LDAP_SSL_PORT=4443\n\n# Set this to 0 to allow nginx to resolve redirects from the mgm internally,\n# i.e. not sending the 3xx response back to the client. This allows PUT and GET\n# to work with WebDAV clients who do not know how to handle the redirect.\nexport EOS_NGINX_REDIRECT_EXTERNALLY=0\n\n# Make a copy of the template config file\ncp /etc/nginx/nginx.eos.conf.template /etc/nginx/nginx.eos.conf\n\n# Replace all the thing\nperl -pi -e 's/EOS_NGINX_MGM_URL/$ENV{EOS_NGINX_MGM_URL}/g'                               /etc/nginx/nginx.eos.conf\nperl -pi -e 's/EOS_NGINX_USER/$ENV{EOS_NGINX_USER}/g'                                     /etc/nginx/nginx.eos.conf\nperl -pi -e 's/EOS_NGINX_HOSTNAME/$ENV{EOS_NGINX_HOSTNAME}/g'                             /etc/nginx/nginx.eos.conf\nperl -pi -e 's/EOS_NGINX_CLIENT_SSL_PORT/$ENV{EOS_NGINX_CLIENT_SSL_PORT}/g'               /etc/nginx/nginx.eos.conf\nperl -pi -e 's/EOS_NGINX_SSL_CERTIFICATE/$ENV{EOS_NGINX_SSL_CERTIFICATE}/g'               /etc/nginx/nginx.eos.conf\nperl -pi -e 's/EOS_NGINX_SSL_KEY/$ENV{EOS_NGINX_SSL_KEY}/g'                               /etc/nginx/nginx.eos.conf\nperl -pi -e 's/EOS_NGINX_SSL_CLIENT_CERTIFICATE/$ENV{EOS_NGINX_SSL_CLIENT_CERTIFICATE}/g' /etc/nginx/nginx.eos.conf\nperl -pi -e 's/EOS_NGINX_GSS_PORT/$ENV{EOS_NGINX_GSS_PORT}/g'                             /etc/nginx/nginx.eos.conf\nperl -pi -e 's/EOS_NGINX_GSS_KEYTAB/$ENV{EOS_NGINX_GSS_KEYTAB}/g'                         /etc/nginx/nginx.eos.conf\nperl -pi -e 's/EOS_NGINX_GSS_REALM/$ENV{EOS_NGINX_GSS_REALM}/g'                           /etc/nginx/nginx.eos.conf\nperl -pi -e 's/EOS_NGINX_GSS_SERVICE_NAME/$ENV{EOS_NGINX_GSS_SERVICE_NAME}/g'             /etc/nginx/nginx.eos.conf\nperl -pi -e 's/EOS_NGINX_REDIRECT_EXTERNALLY/$ENV{EOS_NGINX_REDIRECT_EXTERNALLY}/g'       /etc/nginx/nginx.eos.conf\nperl -pi -e 's/EOS_NGINX_LDAP_SSL_PORT/$ENV{EOS_NGINX_LDAP_SSL_PORT}/g'                   /etc/nginx/nginx.eos.conf\nperl -pi -e 's/EOS_NGINX_LDAP_URL/$ENV{EOS_NGINX_LDAP_URL}/g'                             /etc/nginx/nginx.eos.conf\nperl -pi -e 's/EOS_NGINX_LDAP_BINDDN_USER/$ENV{EOS_NGINX_LDAP_BINDDN_USER}/g'             /etc/nginx/nginx.eos.conf\nperl -pi -e 's/EOS_NGINX_LDAP_BINDDN_PASSWORD/$ENV{EOS_NGINX_LDAP_BINDDN_PASSWORD}/g'     /etc/nginx/nginx.eos.conf\n"
  },
  {
    "path": "nginx/etc/sysconfig/nginx.sysconfig.systemd",
    "content": "# Configuration file for the nginx service\n\nNGINX_CONF_FILE=/etc/nginx/nginx.eos.conf\n\n# User to run nginx under\nEOS_NGINX_USER=daemon\n\n# Hostname to use for nginx vhosts\nEOS_NGINX_HOSTNAME=`hostname -f`\n\n# Proxy Forwarding Target e.g. MGM URL to proxy, don't end the URL with a '/' !\nEOS_NGINX_MGM_URL=http://localhost:8000\n\n# nginx SSL configuration\nEOS_NGINX_SSL_CERTIFICATE=/etc/grid-security/hostcert.pem\nEOS_NGINX_SSL_KEY=/etc/grid-security/hostkey.pem\n# nginx-bundle should contain CERN CA plus all default CAs (Verisign, etc)\nEOS_NGINX_SSL_CLIENT_CERTIFICATE=/etc/grid-security/certificates/nginx-bundle.pem\n\n# Port to enable client certificate authentication on\nEOS_NGINX_CLIENT_SSL_PORT=443\n# Enable openssl to use proxy certificates\nOPENSSL_ALLOW_PROXY_CERTS=1\n\n# Port to enable kerberos authentication on\nEOS_NGINX_GSS_PORT=8443\n# nginx kerberos configuration\nEOS_NGINX_GSS_KEYTAB=/etc/krb5.keytab\nEOS_NGINX_GSS_REALM=CERN.CH\nEOS_NGINX_GSS_SERVICE_NAME=host/$EOS_NGINX_HOSTNAME\n\n# nginx LDAP basic auth configuration\nEOS_NGINX_LDAP_URL=\"ldaps://cerndc.cern.ch/OU=Users,OU=Organic Units,DC=cern,DC=ch?samaccountname?sub?(objectClass=user)\"\nEOS_NGINX_LDAP_BINDDN_USER=\"TEST\"\nEOS_NGINX_LDAP_BINDDN_PASSWORD=\"TESTPWD\"\nEOS_NGINX_LDAP_SSL_PORT=4443\n\n# Set this to 0 to allow nginx to resolve redirects from the mgm internally,\n# i.e. not sending the 3xx response back to the client. This allows PUT and GET\n# to work with WebDAV clients who do not know how to handle the redirect.\nEOS_NGINX_REDIRECT_EXTERNALLY=0\n\n# Make a copy of the template config file\ncp /etc/nginx/nginx.eos.conf.template /etc/nginx/nginx.eos.conf\n\n# Replace all the thing\nsed -i  \"s#EOS_NGINX_MGM_URL#$EOS_NGINX_MGM_URL#g\"                               /etc/nginx/nginx.eos.conf\nsed -i  \"s#EOS_NGINX_USER#$EOS_NGINX_USER#g\"                                     /etc/nginx/nginx.eos.conf\nsed -i  \"s#EOS_NGINX_HOSTNAME#$EOS_NGINX_HOSTNAME#g\"                             /etc/nginx/nginx.eos.conf\nsed -i  \"s#EOS_NGINX_CLIENT_SSL_PORT#$EOS_NGINX_CLIENT_SSL_PORT#g\"               /etc/nginx/nginx.eos.conf\nsed -i  \"s#EOS_NGINX_SSL_CERTIFICATE#$EOS_NGINX_SSL_CERTIFICATE#g\"               /etc/nginx/nginx.eos.conf\nsed -i  \"s#EOS_NGINX_SSL_KEY#$EOS_NGINX_SSL_KEY#g\"                               /etc/nginx/nginx.eos.conf\nsed -i  \"s#EOS_NGINX_SSL_CLIENT_CERTIFICATE#$EOS_NGINX_SSL_CLIENT_CERTIFICATE#g\" /etc/nginx/nginx.eos.conf\nsed -i  \"s#EOS_NGINX_GSS_PORT#$EOS_NGINX_GSS_PORT#g\"                             /etc/nginx/nginx.eos.conf\nsed -i  \"s#EOS_NGINX_GSS_KEYTAB#$EOS_NGINX_GSS_KEYTAB#g\"                         /etc/nginx/nginx.eos.conf\nsed -i  \"s#EOS_NGINX_GSS_REALM#$EOS_NGINX_GSS_REALM#g\"                           /etc/nginx/nginx.eos.conf\nsed -i  \"s#EOS_NGINX_GSS_SERVICE_NAME#$EOS_NGINX_GSS_SERVICE_NAME#g\"             /etc/nginx/nginx.eos.conf\nsed -i  \"s#EOS_NGINX_REDIRECT_EXTERNALLY#$EOS_NGINX_REDIRECT_EXTERNALLY#g\"       /etc/nginx/nginx.eos.conf\nsed -i  \"s#EOS_NGINX_LDAP_SSL_PORT#$EOS_NGINX_LDAP_SSL_PORT#g\"                   /etc/nginx/nginx.eos.conf\nsed -i  \"s#EOS_NGINX_LDAP_URL#$EOS_NGINX_LDAP_URL#g\"                             /etc/nginx/nginx.eos.conf\nsed -i  \"s#EOS_NGINX_LDAP_BINDDN_USER#$EOS_NGINX_LDAP_BINDDN_USER#g\"             /etc/nginx/nginx.eos.conf\nsed -i  \"s#EOS_NGINX_LDAP_BINDDN_PASSWORD#$EOS_NGINX_LDAP_BINDDN_PASSWORD#g\"     /etc/nginx/nginx.eos.conf\n"
  },
  {
    "path": "nginx/etc/systemd/nginx.service",
    "content": "[Unit]\nDescription=nginx - high performance web server\nDocumentation=http://nginx.org/en/docs/\nAfter=network.target remote-fs.target nss-lookup.target\n \n[Service]\nType=forking\nPIDFile=/run/nginx.pid\nEnvironmentFile=/etc/sysconfig/nginx\nExecStartPre=/bin/sh /etc/sysconfig/nginx\nExecStart=/usr/sbin/nginx -c $NGINX_CONF_FILE\nExecReload=/bin/kill -s HUP $MAINPID\nExecStop=/bin/kill -s QUIT $MAINPID\nPrivateTmp=true\n \n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "nginx/jenkins-build.sh",
    "content": "#!/bin/bash\n#-------------------------------------------------------------------------------\n# @author Elvin-Alin Sindrilaru - CERN\n# @brief Script used by Jenkins to build EOS Nginx rpms\n#-------------------------------------------------------------------------------\n\n#************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2016 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************/\n\n#-------------------------------------------------------------------------------\n# Print help\n#-------------------------------------------------------------------------------\nfunction printHelp()\n{\n  echo \"Usage:                                                               \" 1>&2\n  echo \"${0} <branch_or_tag> <xrootd_tag> <build_number> <dst_path>          \" 1>&2\n  echo \"  <branch_or_tag> branch name in the form of \\\"origin/master\\\" or tag\" 1>&2\n  echo \"                  name e.g. 1.0.0 for which to build the project     \" 1>&2\n  echo \"  <xrootd_tag>    XRootD tag version used for this build             \" 1>&2\n  echo \"  <build_number>  build number value passed in by Jenkins            \" 1>&2\n  echo \"  <platform>      build platform e.g. slc-6, el-7, fc-24             \" 1>&2\n  echo \"  <architecture>  build architecture e.g. x86_64, i386               \" 1>&2\n  echo \"  <dst_path>      destination path for the rpms built                \" 1>&2\n}\n\n#-------------------------------------------------------------------------------\n# Get the local branch name and dist tag for the rpms. For example local branch\n# name of branch 'origin/master' is master. The dist tag for Scientific Linux 5\n# can be 'slc5' or 'el5'.\n# Function sets two global variables BRANCH and DIST.\n#-------------------------------------------------------------------------------\nfunction getLocalBranchAndDistTag()\n{\n  if [[ ${#} -ne 2 ]]; then\n    echo \"Usage:                                                               \" 1>&2\n    echo \"${0} <branch_or_tag> <platform>                                      \" 1>&2\n    echo \"  <branch_or_tag> branch name in the form of \\\"origin/master\\\" or tag\" 1>&2\n    echo \"                  name e.g. 1.0.0 for which to build the project     \" 1>&2\n    echo \"  <platform>      build platform e.g. slc-6, el-7, fc-24             \" 1>&2\n    exit 1\n  fi\n\n  local BRANCH_OR_TAG=${1}\n  local PLATORM=${2}\n  local TAG_REGEX=\"^[04]+\\..*$\"\n  local TAG_REGEX_CITRINE=\"^4.*$\"\n\n  # If this is a tag get the branch it belogs to\n  if [[ \"${BRANCH_OR_TAG}\" =~ ${TAG_REGEX} ]]; then\n    if [[ \"${BRANCH_OR_TAG}\" =~ ${TAG_REGEX_CITRINE} ]]; then\n\t    BRANCH=\"citrine\"\n    fi\n  else\n    BRANCH=$(basename ${BRANCH_OR_TAG})\n    if [[ \"${BRANCH}\"  == \"master\" ]]; then\n\t    BRANCH=\"citrine\"\n    fi\n  fi\n\n  # For any other branch use the latest XRootD release\n  XROOTD_TAG=\"v4.3.0\"\n  DIST=\".${PLATFORM}\"\n\n  # Remove any \"-\" from the dist tag\n  DIST=\"${DIST//-}\"\n\n  echo \"Local branch:         ${BRANCH}\"\n  echo \"Dist tag:             ${DIST}\"\n}\n\n#-------------------------------------------------------------------------------\n# Main - when we are called the current BRANCH_OR_TAG is already checked-out and\n#        the script must be run from the **same directory** where it resides.\n#-------------------------------------------------------------------------------\nif [[ ${#} -ne 6 ]]; then\n    printHelp\n    exit 1\nfi\n\nBRANCH_OR_TAG=${1}\nXROOTD_TAG=${2}\nBUILD_NUMBER=${3}\nPLATFORM=${4}\nARCHITECTURE=${5}\nDST_PATH=${6}\n\necho \"Build number:         ${BUILD_NUMBER}\"\necho \"Branch or tag:        ${BRANCH_OR_TAG}\"\necho \"XRootD tag:           ${XROOTD_TAG}\"\necho \"Build platform:       ${PLATFORM}\"\necho \"Build architecture:   ${ARCHITECTURE}\"\necho \"Destination path:     ${DST_PATH}\"\necho \"Running in directory: $(pwd)\"\n\n# Get local branch and dist tag for the RPMS\ngetLocalBranchAndDistTag ${BRANCH_OR_TAG} ${PLATFORM}\n# Move to nginx directory and create the SRPMs\ncd nginx\n./makesrpm.sh\n# Get the mock configurations from gitlab\ngit clone ssh://git@gitlab.cern.ch:7999/dss/dss-ci-mock.git ../dss-ci-mock\n# Prepare the mock configuration\ncat ../dss-ci-mock/eos-templates/${PLATFORM}-${ARCHITECTURE}.cfg.in | sed \"s/__XROOTD_TAG__/${XROOTD_TAG}/\" | sed \"s/__BUILD_NUMBER__/${BUILD_NUMBER}/\" > eos.cfg\n# Build the RPMs\nmock --yum --init --uniqueext=\"eos-nginx01\" -r ./eos.cfg --rebuild ./eos-nginx*.src.rpm --resultdir ../rpms -D \"dist ${DIST}\"\n# List of branches for CI YUM repo\nBRANCH_LIST=('citrine')\n\n# If building one of the production branches then push rpms to YUM repo\nif [[ ${BRANCH_LIST[*]} =~ ${BRANCH} ]]; then\n  cd ../rpms/\n  # Make sure the directories are created and rebuild the YUM repo\n  YUM_REPO_PATH=\"${DST_PATH}/${BRANCH}/tag/${PLATFORM}/${ARCHITECTURE}\"\n  echo \"Save RPMs in YUM repo: ${YUM_REPO_PATH}\"\n  aklog\n  mkdir -p ${YUM_REPO_PATH}\n  cp -f *.rpm ${YUM_REPO_PATH}\n  createrepo --update -q ${YUM_REPO_PATH}\nelse\n  echo \"RPMs for branch ${BRANCH} are NOT saved in any YUM repository!\"\nfi\n"
  },
  {
    "path": "nginx/makesrpm.sh",
    "content": "#!/bin/bash\n#-------------------------------------------------------------------------------\n# Build an RPM for nginx, customised for EOS\n# Author: Justin Salmon <jsalmon@cern.ch> (25.07.2013)\n#-------------------------------------------------------------------------------\n\n#-------------------------------------------------------------------------------\n# Print help\n#-------------------------------------------------------------------------------\nfunction printHelp()\n{\n  echo \"Usage:\"                                              1>&2\n  echo \"${0} [--help] [--source PATH] [--output PATH]\"       1>&2\n  echo \"  --help        prints this message\"                 1>&2\n  echo \"  --source PATH specify the root of the source tree\" 1>&2\n  echo \"                defaults to .\"                       1>&2\n  echo \"  --output PATH the directory where the source rpm\"  1>&2\n  echo \"                should be stored, defaulting to .\"   1>&2\n}\n\n#-------------------------------------------------------------------------------\n# Parse the commandline\n#-------------------------------------------------------------------------------\nSOURCEPATH=\".\"\nOUTPUTPATH=\".\"\nPRINTHELP=0\n\nwhile test ${#} -ne 0; do\n  if test x${1} = x--help; then\n    PRINTHELP=1\n  elif test x${1} = x--source; then\n    if test ${#} -lt 2; then\n      echo \"--source parameter needs an argument\" 1>&2\n      exit 1\n    fi\n    SOURCEPATH=${2}\n    shift\n  elif test x${1} = x--output; then\n    if test ${#} -lt 2; then\n      echo \"--output parameter needs an argument\" 1>&2\n      exit 1\n    fi\n    OUTPUTPATH=${2}\n    shift\n  fi\n  shift\ndone\n\nif test $PRINTHELP -eq 1; then\n  printHelp\n  exit 0\nfi\n\necho \"[i] Working on: $SOURCEPATH\"\necho \"[i] Storing the output to: $OUTPUTPATH\"\n\n#-------------------------------------------------------------------------------\n# Create a tempdir and copy the files there\n#-------------------------------------------------------------------------------\n# exit on any error\nset -e\n\nTEMPDIR=`mktemp -d /tmp/eos-nginx.srpm.XXXXXXXXXX`\nRPMSOURCES=$TEMPDIR/rpmbuild/SOURCES\nmkdir -p $RPMSOURCES\nmkdir -p $TEMPDIR/rpmbuild/SRPMS\n\necho \"[i] Working in: $TEMPDIR\" 1>&2\n\ncp $SOURCEPATH/etc/init.d/*      $RPMSOURCES\ncp $SOURCEPATH/etc/logrotate.d/* $RPMSOURCES\ncp $SOURCEPATH/etc/nginx/*       $RPMSOURCES\ncp $SOURCEPATH/etc/sysconfig/*   $RPMSOURCES\ncp $SOURCEPATH/etc/systemd/*     $RPMSOURCES\ncp $SOURCEPATH/*.patch           $RPMSOURCES\ncp $SOURCEPATH/nginx.spec        $TEMPDIR\n\n#-------------------------------------------------------------------------------\n# Download the source tarball\n#-------------------------------------------------------------------------------\n# no more exiting on error\nset +e\n\necho \"[i] Downloading nginx source...\"\nVERSION=`cat $SOURCEPATH/nginx.spec | \\\n         egrep -e 'Version:\\s+([0-9\\.]*)' -o | \\\n         egrep -e '[0-9\\.]*' -o`\ncurl -sS http://nginx.org/download/nginx-$VERSION.tar.gz -O\nmv nginx-$VERSION.tar.gz $RPMSOURCES/\n\n#-------------------------------------------------------------------------------\n# Build the source RPM\n#-------------------------------------------------------------------------------\n\necho \"[i] Creating the source RPM...\"\n\n# Dirty, dirty hack!\necho \"%_sourcedir $RPMSOURCES\" >> $TEMPDIR/rpmmacros\nrpmbuild --define \"_topdir $TEMPDIR/rpmbuild\"    \\\n         --define \"%_sourcedir $RPMSOURCES\"      \\\n         --define \"%_srcrpmdir %{_topdir}/SRPMS\" \\\n         --define \"_source_filedigest_algorithm md5\" \\\n         --define \"_binary_filedigest_algorithm md5\" \\\n-bs $TEMPDIR/nginx.spec > $TEMPDIR/log\nif test $? -ne 0; then\n  echo \"[!] RPM creation failed\" 1>&2\n  exit 1\nfi\n\ncp $TEMPDIR/rpmbuild/SRPMS/eos-nginx*.src.rpm $OUTPUTPATH\nrm -rf $TEMPDIR\n\necho \"[i] Done.\"\n"
  },
  {
    "path": "nginx/nginx-allow-proxy-certs.patch",
    "content": "From 0bd39b781ee6053d53d219358c852bc2d433f581 Mon Sep 17 00:00:00 2001\nFrom: Guilherme Amadio <amadio@cern.ch>\nDate: Mon, 1 Jul 2024 16:49:28 +0200\nSubject: [PATCH] SSL: add ssl_allow_proxy_certs flag\n\n---\n src/event/ngx_event_openssl.c          | 21 +++++++++++++++++++++\n src/event/ngx_event_openssl.h          |  2 ++\n src/http/modules/ngx_http_ssl_module.c | 13 +++++++++++++\n src/http/modules/ngx_http_ssl_module.h |  1 +\n 4 files changed, 37 insertions(+)\n\ndiff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c\nindex c38aa27f1..2b785abb6 100644\n--- a/src/event/ngx_event_openssl.c\n+++ b/src/event/ngx_event_openssl.c\n@@ -1625,6 +1625,27 @@ ngx_ssl_conf_commands(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *commands)\n #endif\n }\n \n+ngx_int_t\n+ngx_ssl_allow_proxy_certs(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable)\n+{\n+    X509_STORE   *store;\n+\n+    if (!enable) {\n+        return NGX_OK;\n+    }\n+\n+    store = SSL_CTX_get_cert_store(ssl->ctx);\n+\n+    if (store == NULL) {\n+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,\n+                      \"SSL_CTX_get_cert_store() failed\");\n+        return NGX_ERROR;\n+    }\n+\n+    X509_STORE_set_flags(store, X509_V_FLAG_ALLOW_PROXY_CERTS);\n+\n+    return NGX_OK;\n+}\n \n ngx_int_t\n ngx_ssl_client_session_cache(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable)\ndiff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h\nindex c062f912c..f1bc6f24e 100644\n--- a/src/event/ngx_event_openssl.h\n+++ b/src/event/ngx_event_openssl.h\n@@ -209,6 +209,8 @@ ngx_int_t ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl,\n     ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify);\n ngx_int_t ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,\n     ngx_resolver_t *resolver, ngx_msec_t resolver_timeout);\n+ngx_int_t ngx_ssl_allow_proxy_certs(ngx_conf_t *cf, ngx_ssl_t *ssl,\n+    ngx_uint_t enable);\n ngx_int_t ngx_ssl_ocsp(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder,\n     ngx_uint_t depth, ngx_shm_zone_t *shm_zone);\n ngx_int_t ngx_ssl_ocsp_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,\ndiff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c\nindex d2ca475d3..c415d8ec8 100644\n--- a/src/http/modules/ngx_http_ssl_module.c\n+++ b/src/http/modules/ngx_http_ssl_module.c\n@@ -304,6 +304,13 @@ static ngx_command_t  ngx_http_ssl_commands[] = {\n       offsetof(ngx_http_ssl_srv_conf_t, reject_handshake),\n       NULL },\n \n+    { ngx_string(\"ssl_allow_proxy_certs\"),\n+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,\n+      ngx_conf_set_flag_slot,\n+      NGX_HTTP_SRV_CONF_OFFSET,\n+      offsetof(ngx_http_ssl_srv_conf_t, allow_proxy_certs),\n+      NULL },\n+\n       ngx_null_command\n };\n \n@@ -636,6 +643,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t *cf)\n     sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR;\n     sscf->stapling = NGX_CONF_UNSET;\n     sscf->stapling_verify = NGX_CONF_UNSET;\n+    sscf->allow_proxy_certs = NGX_CONF_UNSET;\n \n     return sscf;\n }\n@@ -711,6 +719,7 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)\n     ngx_conf_merge_str_value(conf->stapling_file, prev->stapling_file, \"\");\n     ngx_conf_merge_str_value(conf->stapling_responder,\n                          prev->stapling_responder, \"\");\n+    ngx_conf_merge_value(conf->allow_proxy_certs, prev->allow_proxy_certs, 1);\n \n     conf->ssl.log = cf->log;\n \n@@ -938,6 +947,10 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)\n         return NGX_CONF_ERROR;\n     }\n \n+    if (ngx_ssl_allow_proxy_certs(cf, &conf->ssl, conf->allow_proxy_certs) != NGX_OK) {\n+        return NGX_CONF_ERROR;\n+    }\n+\n     return NGX_CONF_OK;\n }\n \ndiff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h\nindex 7ab0f7eae..9c2280bec 100644\n--- a/src/http/modules/ngx_http_ssl_module.h\n+++ b/src/http/modules/ngx_http_ssl_module.h\n@@ -64,6 +64,7 @@ typedef struct {\n     ngx_flag_t                      stapling_verify;\n     ngx_str_t                       stapling_file;\n     ngx_str_t                       stapling_responder;\n+    ngx_flag_t                      allow_proxy_certs;\n \n     u_char                         *file;\n     ngx_uint_t                      line;\n-- \n2.45.2\n\n"
  },
  {
    "path": "nginx/nginx-allow-put-redirect.patch",
    "content": "--- a/src/http/ngx_http_upstream.c\n+++ b/src/http/ngx_http_upstream.c\n@@ -2856,10 +2856,11 @@ ngx_http_upstream_process_headers(ngx_http_request_t *r, ngx_http_upstream_t *u)\n                 return NGX_DONE;\n             }\n \n-            if (r->method != NGX_HTTP_HEAD) {\n-                r->method = NGX_HTTP_GET;\n-                r->method_name = ngx_http_core_get_method;\n-            }\n+            // @note(esindril) allow also PUT redirection for the upstream\n+            /* if (r->method != NGX_HTTP_HEAD) { */\n+            /*     r->method = NGX_HTTP_GET; */\n+            /*     r->method_name = ngx_http_core_get_method; */\n+            /* } */\n \n             ngx_http_internal_redirect(r, &uri, &args);\n         }\n"
  },
  {
    "path": "nginx/nginx-no-body-before-redirect.patch",
    "content": "--- a/src/http/modules/ngx_http_proxy_module.c\t2023-05-23 17:08:20.000000000 +0200\n+++ b/src/http/modules/ngx_http_proxy_module.c\t2024-07-01 11:15:57.667381571 +0200\n@@ -1570,8 +1570,7 @@\n             u->output.filter_ctx = r;\n         }\n \n-    } else if (plcf->body_values == NULL && plcf->upstream.pass_request_body) {\n-\n+    } else if (plcf->body_values == NULL && ( plcf->upstream.pass_request_body || (r->method != NGX_HTTP_PUT)) ) {\n         body = u->request_bufs;\n         u->request_bufs = cl;\n \n"
  },
  {
    "path": "nginx/nginx.spec",
    "content": "%define _unpackaged_files_terminate_build 0\n# distribution specific definitions\n%define use_systemd (0%{?fedora} && 0%{?fedora} >= 18) || (0%{?rhel} && 0%{?rhel} >= 7)\n%define debug_package %{nil}\n%define nginx_user      daemon\n%define nginx_group     %{nginx_user}\n%define nginx_home      %{_localstatedir}/lib/nginx\n%define nginx_home_tmp  /var/spool/nginx/tmp\n%define nginx_logdir    %{_localstatedir}/log/nginx\n%define nginx_confdir   %{_sysconfdir}/nginx\n%define nginx_datadir   %{_datadir}/nginx\n%define nginx_webroot   %{nginx_datadir}/html\n\nName:           eos-nginx\nVersion:        1.25.0\nRelease:        3%{dist}\nSummary:        Robust, small and high performance http and reverse proxy server\nGroup:          System Environment/Daemons\nPackager:       Justin Salmon <jsalmon@cern.ch>\n\n# BSD License (two clause)\n# http://www.freebsd.org/copyright/freebsd-license.html\nLicense:            BSD\nURL:                http://nginx.net/ \nBuildRoot:          %{_tmppath}/%{name}-%{version}-%{release}-%(%{__id_u} -n)\n\n%if %{use_systemd}\nBuildRequires:      systemd,pcre-devel,zlib-devel,openssl-devel,perl(ExtUtils::Embed),pam-devel,git,e2fsprogs-devel,openldap-devel,krb5-devel\nRequires:           systemd,zlib,openssl,openldap \n%endif\n\n%if 0%{?rhel} == 6 \nBuildRequires:      pcre-devel,zlib-devel,openssl-devel,perl(ExtUtils::Embed),pam-devel,git,e2fsprogs-devel,openldap-devel,krb5-devel\nRequires:           pcre,zlib,openssl,openldap\nRequires(post):     chkconfig\nRequires(preun):    chkconfig, initscripts\nRequires(postun):   initscripts\n%endif\n\n%if 0%{?rhel} == 5\nBuildRequires:      pcre-devel,zlib-devel,openssl-devel,perl(ExtUtils::Embed),pam-devel,git,e2fsprogs-devel,openldap24-libs-devel,krb5-devel\nRequires:           pcre,zlib,openssl,openldap24-libs\nRequires(post):     chkconfig\nRequires(preun):    chkconfig, initscripts\nRequires(postun):   initscripts\n%endif\n\nRequires(pre):      shadow-utils\n\nConflicts:          nginx\n\n#Source0:    %{name}-%{version}.tar.gz\nSource:     http://nginx.org/download/nginx-%{version}.tar.gz\nSource2:    nginx.init\nSource3:    nginx.logrotate\nSource4:    nginx.sysconfig\nSource5:    nginx.eos.conf.template\nSource6:    nginx.service\nSource7:    nginx.sysconfig.systemd\n\nPatch0:     nginx-allow-put-redirect.patch\nPatch1:     nginx-allow-proxy-certs.patch\nPatch2:     nginx-no-body-before-redirect.patch\n\n%description\nNginx [engine x] is an HTTP(S) server, HTTP(S) reverse proxy and IMAP/POP3\nproxy server written by Igor Sysoev.\n\nOne third party module, ngx_http_auth_spnego has been added.\nA second third party modul, nginx-auth-ldap has been added.\n\n%prep\n%setup -q -n nginx-%{version}\n\n%patch0 -p1\n%patch1 -p1\n%patch2 -p1\n%build\n\nrm -rf %{_builddir}/spnego-http-auth-nginx-module\ngit clone https://github.com/stnoonan/spnego-http-auth-nginx-module \\\n          %{_builddir}/spnego-http-auth-nginx-module\n\nrm -rf %{_builddir}/nginx-auth-ldap-module\ngit clone https://github.com/kvspb/nginx-auth-ldap.git \\\n          %{_builddir}/nginx-auth-ldap-module\n\nrm -rf %{_builddir}/nginx-auth-pam-module\ngit clone https://github.com/sto/ngx_http_auth_pam_module.git \\\n          %{_builddir}/nginx-auth-pam-module\n\n# nginx does not utilize a standard configure script.  It has its own\n# and the standard configure options cause the nginx configure script\n# to error out.  This is is also the reason for the DESTDIR environment\n# variable.  The configure script(s) have been patched (Patch1 and\n# Patch2) in order to support installing into a build environment.\n# --with-http_memcached_module\\\n# --with-http_ssi_module\\\n\n\nexport CFLAGS=\"-I/usr/include/et/ -I/usr/include/openldap24/\"\nexport CC=\"cc -L/usr/lib64/openldap24/\"\nexport DESTDIR=%{buildroot}\n./configure \\\n    --user=%{nginx_user} \\\n    --group=%{nginx_group} \\\n    --prefix=%{nginx_datadir} \\\n    --sbin-path=%{_sbindir}/nginx \\\n    --conf-path=%{nginx_confdir}/nginx.conf  \\\n    --error-log-path=%{nginx_logdir}/error.log \\\n    --http-log-path=%{nginx_logdir}/access.log \\\n    --http-client-body-temp-path=%{nginx_home_tmp}/client_body \\\n    --http-proxy-temp-path=%{nginx_home_tmp}/proxy \\\n    --http-fastcgi-temp-path=%{nginx_home_tmp}/fastcgi \\\n    --pid-path=/run/nginx.pid      \\\n    --lock-path=%{_localstatedir}/lock/subsys/nginx \\\n    --with-http_ssl_module              \\\n    --with-http_gzip_static_module      \\\n    --with-http_stub_status_module      \\\n    --with-debug                        \\\n    --with-ipv6                         \\\n    --without-select_module             \\\n    --without-poll_module               \\\n    --without-http_autoindex_module     \\\n    --without-http_geo_module           \\\n    --without-http_map_module           \\\n    --without-http_referer_module       \\\n    --without-http_empty_gif_module     \\\n    --without-http_browser_module       \\\n    --without-mail_imap_module          \\\n    --without-mail_smtp_module          \\\n    --with-http_auth_request_module     \\\n    --with-ipv6                         \\\n    --add-module=%{_builddir}/spnego-http-auth-nginx-module \\\n    --add-module=%{_builddir}/nginx-auth-ldap-module \\\n    --add-module=%{_builddir}/nginx-auth-pam-module \t\n    #--with-cc-opt=\"%{optflags} $(pcre-config --cflags)\" \\\n\nmake %{?_smp_mflags} \n\n%install\nrm -rf %{buildroot}\nmake install DESTDIR=%{buildroot} INSTALLDIRS=vendor\nfind %{buildroot} -type f -name .packlist -exec rm -f {} \\;\nfind %{buildroot} -type f -name perllocal.pod -exec rm -f {} \\;\nfind %{buildroot} -type f -empty -exec rm -f {} \\;\nfind %{buildroot} -type f -exec chmod 0644 {} \\;\nfind %{buildroot} -type f -name '*.so' -exec chmod 0755 {} \\;\nchmod 0755 %{buildroot}%{_sbindir}/nginx\nmkdir %{buildroot}%{nginx_confdir}/conf.d\n%if %{use_systemd}\n# install systemd-specific files\n%{__install} -p -D -m 0755 %{SOURCE6} %{buildroot}%{_unitdir}/nginx.service\n%else\n# install SYSV init stuff\n%{__install} -p -D -m 0755 %{SOURCE2} %{buildroot}%{_initrddir}/nginx\n%endif\n%{__install} -p -D -m 0644 %{SOURCE3} %{buildroot}%{_sysconfdir}/logrotate.d/nginx\n%if %{use_systemd}\n%{__install} -p -D -m 0644 %{SOURCE7} %{buildroot}%{_sysconfdir}/sysconfig/nginx\n%else\n%{__install} -p -D -m 0644 %{SOURCE5} %{buildroot}%{_sysconfdir}/sysconfig/nginx\n%endif\n%{__install} -p -D -m 0644 %{SOURCE5} %{buildroot}%{_sysconfdir}/nginx/nginx.eos.conf.template\n%{__install} -p -d -m 0755 %{buildroot}%{nginx_confdir}/vhosts\n%{__install} -p -d -m 0755 %{buildroot}%{nginx_home}\n%{__install} -p -d -m 0755 %{buildroot}%{nginx_home_tmp}\n%{__install} -p -d -m 0755 %{buildroot}%{nginx_logdir}\n%{__install} -p -d -m 0755 %{buildroot}%{nginx_webroot}\n\n# convert to UTF-8 all files that give warnings.\nfor textfile in CHANGES\ndo\n    mv $textfile $textfile.old\n    iconv --from-code ISO8859-1 --to-code UTF-8 --output $textfile $textfile.old\n    rm -f $textfile.old\ndone\n\n%clean\nrm -rf %{buildroot}\nrm -rf %{_builddir}/spnego-http-auth-nginx-module\nrm -rf %{_builddir}/nginx-auth-ldap-module\nrm -rf %{_builddir}/nginx-auth-pam-module\n\n%pre\n%{_sbindir}/useradd -c \"Nginx user\" -s /bin/false -r -d %{nginx_home} %{nginx_user} 2>/dev/null || :\n\n%post\nif [ $1 -eq 1 ]; then\n%if %{use_systemd}\n    /usr/bin/systemctl preset nginx.service >/dev/null 2>&1 ||:\n%else\n    /sbin/chkconfig --add nginx\n%endif\nfi\n\n%preun\nif [ $1 -eq 0 ]; then\n%if %use_systemd\n    /usr/bin/systemctl --no-reload disable nginx.service >/dev/null 2>&1 ||:\n    /usr/bin/systemctl stop nginx.service >/dev/null 2>&1 ||:\n%else\n    /sbin/service nginx stop > /dev/null 2>&1\n    /sbin/chkconfig --del nginx\n%endif\nfi\n\n%postun\n%if %use_systemd\n    /usr/bin/systemctl daemon-reload >/dev/null 2>&1 ||:\n%endif\nif [ $1 -ge 1 ]; then\n    /sbin/service nginx status  >/dev/null 2>&1 || exit 0\n    /sbin/service nginx upgrade >/dev/null 2>&1 || echo \\\n        \"Binary upgrade failed, please check nginx's error.log\"\nfi\n\n%files\n%defattr(-,root,root,-)\n%doc LICENSE CHANGES README\n%dir %{nginx_datadir}\n%{_datadir}/nginx/*/*\n%{_sbindir}/nginx\n#%{_mandir}/man3/nginx.3pm.gz\n%if %{use_systemd}\n%{_unitdir}/nginx.service\n%else\n%{_initrddir}/nginx\n%endif\n%dir %{nginx_confdir}\n%dir %{nginx_confdir}/conf.d\n%config(noreplace) %{nginx_confdir}/win-utf\n%config(noreplace) %{nginx_confdir}/fastcgi_params\n%config(noreplace) %{nginx_confdir}/fastcgi_params.default\n%config(noreplace) %{nginx_confdir}/mime.types.default\n%config(noreplace) %{nginx_confdir}/nginx.conf.default\n%config(noreplace) %{nginx_confdir}/fastcgi.conf\n%config(noreplace) %{nginx_confdir}/fastcgi.conf.default\n%config(noreplace) %{nginx_confdir}/scgi_params\n%config(noreplace) %{nginx_confdir}/scgi_params.default\n%config(noreplace) %{nginx_confdir}/uwsgi_params\n%config(noreplace) %{nginx_confdir}/uwsgi_params.default\n%config(noreplace) %{nginx_confdir}/koi-win\n%config(noreplace) %{nginx_confdir}/koi-utf\n%config(noreplace) %{nginx_confdir}/nginx.conf\n%config(noreplace) %{nginx_confdir}/nginx.eos.conf.template\n%config(noreplace) %{nginx_confdir}/mime.types\n%config(noreplace) %{_sysconfdir}/logrotate.d/nginx\n%config(noreplace) %{_sysconfdir}/sysconfig/nginx\n#%dir %{perl_vendorarch}/auto/nginx\n#%{perl_vendorarch}/nginx.pm\n#%{perl_vendorarch}/auto/nginx/nginx.so\n%attr(-,%{nginx_user},%{nginx_group}) %dir %{nginx_home}\n%attr(-,%{nginx_user},%{nginx_group}) %dir %{nginx_home_tmp}\n%attr(-,%{nginx_user},%{nginx_group}) %dir %{nginx_logdir}\n\n%changelog\n* Tue Sep 12 2023 Andreas-Joachim Peters <andreas.joachim.peters@cern.ch>\n- Switch to nginx version 1.24.0\n* Fri Jul 21 2017 Andrea Manzi <amanzi@cern.ch>\n- bump release number\n* Tue Feb 28 2017 Andrea Manzi <amanzi@cern.ch>\n- added systemd support\n- fix deps on el7\n* Thu Aug 28 2013 Justin Salmon <jsalmon@cern.ch>\n- Bump release version\n* Thu Jul 25 2013 Justin Salmon <jsalmon@cern.ch>\n- Switch to nginx version 1.4.2\n"
  },
  {
    "path": "proto/Audit.proto",
    "content": "syntax = \"proto3\";\npackage eos.audit;\n\n//------------------------------------------------------------------------------\n// Audit logging protobufs\n//------------------------------------------------------------------------------\n\nenum Operation {\n  OPERATION_UNSPECIFIED = 0;\n  CREATE = 1;\n  DELETE = 2;\n  RENAME = 3;\n  WRITE = 4;\n  TRUNCATE = 5;\n  SET_XATTR = 6;\n  SET_ACL = 7;\n  CHMOD = 8;\n  CHOWN = 9;\n  READ = 10;         // optional, if enabling read auditing\n  LIST = 11;         // optional, if enabling directory listing auditing\n  METADATA_UPDATE = 12;\n  MKDIR = 13;        // directory creation\n  RMDIR = 14;        // directory deletion\n  RM_XATTR = 15;     // remove extended attribute\n  SYMLINK = 16;      // create symbolic link\n  UPDATE = 17;       // open file for update (write on existing)\n}\n\nmessage AuthInfo {\n  // Authentication mechanism: sss, krb5, gsi, oauth, tokens, gateway, local, ...\n  string mechanism = 1;\n  // Additional per-authentication attributes (no secrets, no full tokens)\n  map<string, string> attributes = 2;\n}\n\nmessage AuthorizationInfo {\n  // Reasons/grants for authorization, e.g. \"uidgid\", \"egroup:<group>\", \"token-namespace:<ns>\"\n  repeated string reasons = 1;\n}\n\nmessage AttrChange {\n  string name = 1;\n  string before = 2;\n  string after = 3;\n}\n\nmessage AuditRecord {\n  // Seconds since UNIX epoch\n  int64 timestamp = 1;\n  string path = 2;\n  Operation operation = 3;\n  string client_ip = 4;\n  string account = 5;\n  AuthInfo auth = 6;\n  AuthorizationInfo authorization = 7;\n  // Optional trace identifier for correlation\n  string trace_id = 8;\n  // Optional target path for operations that involve a destination, e.g. rename/symlink\n  string target = 9;\n  // Unique request identifier (UUID)\n  string uuid = 10;\n  // Trace identifier (short token for correlating logs)\n  string tid = 11;\n  // Acting application (client), e.g. eoscp, xrdcp, gateway\n  string app = 12;\n  // Acting EOS service, e.g. mgm, fst, gateway\n  string svc = 13;\n  // State before operation for existing entries\n  Stat before = 14;\n  // State after operation (if still exists)\n  Stat after = 15;\n  // Attribute changes (if any)\n  repeated AttrChange attrs = 16;\n  // Source location and version metadata (format: \"filename:line@version\")\n  string software = 17;\n}\n\n// Basic stat-like information\nmessage Stat {\n  int64 ctime = 1; // seconds since epoch\n  int64 mtime = 2; // seconds since epoch\n  uint32 uid = 3;\n  uint32 gid = 4;\n  uint32 mode = 5; // unix mode bits\n  string mode_octal = 6; // e.g. \"0755\"\n  uint64 size = 7; // bytes\n  string checksum = 8; // hex encoded checksum\n  // Seconds.nanoseconds string representation for full resolution\n  string ctime_ns = 9;\n  string mtime_ns = 10;\n}\n\n\n"
  },
  {
    "path": "proto/CMakeLists.txt",
    "content": "#-------------------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Jozsef Makai - CERN\n# Author: Fabio Luchetti - CERN\n#-------------------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2018 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n#-------------------------------------------------------------------------------\n# Disable -Wsign-compare warnings due to\n# grpcpp/support/proto_buffer_reader.h:157:24: warning: comparison of\n# integer expressions of different signedness: ‘uint64_t’ {aka ‘long\n# unsigned int’} and ‘int’ [-Wsign-compare]\n#-------------------------------------------------------------------------------\nadd_compile_options(-Wno-sign-compare)\n\n#-------------------------------------------------------------------------------\n# Generate protocol buffer object for the Namespace\n#-------------------------------------------------------------------------------\nPROTOBUF_GENERATE_CPP(FMD_SRCS FMD_HDRS namespace/ns_quarkdb/FileMd.proto)\nPROTOBUF_GENERATE_CPP(CMD_SRCS CMD_HDRS namespace/ns_quarkdb/ContainerMd.proto)\nPROTOBUF_GENERATE_CPP(CHANGELOG_SRCS CHANGELOG_HDRS namespace/ns_quarkdb/ChangelogEntry.proto)\n\nset(NS_PROTO_SRCS ${FMD_SRCS} ${CMD_SRCS} ${CHANGELOG_SRCS})\nset(NS_PROTO_HDRS ${FMD_HDRS} ${CMD_HDRS} ${CHANGELOG_HDRS})\nset_source_files_properties(\n  ${NS_PROTO_SRCS}\n  ${NS_PROTO_HDRS}\n  PROPERTIES GENERATED TRUE)\n\nadd_library(EosNsQuarkdbProto-Objects OBJECT\n  ${NS_PROTO_SRCS}  ${NS_PROTO_HDRS})\n\ntarget_link_libraries(EosNsQuarkdbProto-Objects PUBLIC\n  PROTOBUF::PROTOBUF\n  ABSL::ABSL)\n\ntarget_include_directories(EosNsQuarkdbProto-Objects PUBLIC\n  $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)\n\nset_target_properties(EosNsQuarkdbProto-Objects PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\n#-------------------------------------------------------------------------------\n# Generate protocol buffer object for FST\n#-------------------------------------------------------------------------------\nPROTOBUF_GENERATE_CPP(FMDBASE_SRCS FMDBASE_HDRS fst/FmdBase.proto)\nPROTOBUF_GENERATE_CPP(DELETE_SRCS DELETE_HDRS fst/Delete.proto)\n\nadd_library(EosFstProto-Objects OBJECT\n  ${FMDBASE_SRCS} ${FMDBASE_HDRS}\n  ${DELETE_SRCS}  ${DELETE_HDRS})\n\ntarget_link_libraries(EosFstProto-Objects PUBLIC\n  PROTOBUF::PROTOBUF\n  ABSL::ABSL)\n\ntarget_include_directories(EosFstProto-Objects PUBLIC\n  $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>\n  $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)\n\nset_target_properties(EosFstProto-Objects PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\n#-------------------------------------------------------------------------------\n# Generate protocol buffer object for Audit logging\n#-------------------------------------------------------------------------------\nPROTOBUF_GENERATE_CPP(AUDIT_SRCS AUDIT_HDRS Audit.proto)\n\nadd_library(EosAuditProto-Objects OBJECT\n  ${AUDIT_SRCS} ${AUDIT_HDRS})\n\ntarget_link_libraries(EosAuditProto-Objects PUBLIC\n  PROTOBUF::PROTOBUF\n  ABSL::ABSL)\n\ntarget_include_directories(EosAuditProto-Objects PUBLIC\n  $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)\n\nset_target_properties(EosAuditProto-Objects PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\n#-------------------------------------------------------------------------------\n# Generate protocol buffer object for the CLI\n#-------------------------------------------------------------------------------\nset(Protobuf_IMPORT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/eos-protobuf-spec ${CMAKE_SOURCE_DIR}/common/grpc-proto)\nset(PROTOBUF3_IMPORT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/eos-protobuf-spec ${CMAKE_SOURCE_DIR}/common/grpc-proto)\n# common protos\nPROTOBUF_GENERATE_CPP(GRPC_SRCS GRPC_HDRS ${CMAKE_SOURCE_DIR}/common/grpc-proto/Rpc.proto)\nPROTOBUF_GENERATE_CPP(RECY_SRCS RECY_HDRS ${CMAKE_SOURCE_DIR}/common/grpc-proto/Recycle.proto)\nPROTOBUF_GENERATE_CPP(TRAFFIC_SHAPING_SRCS TRAFFIC_SHAPING_HDRS ${CMAKE_SOURCE_DIR}/common/grpc-proto/TrafficShaping.proto)\n# eos-protobuf-spec protos\nset(EOS_PROTO_ROOT eos-protobuf-spec)\nPROTOBUF_GENERATE_CPP(REQ_SRCS REQ_HDRS ${EOS_PROTO_ROOT}/ConsoleRequest.proto)\nPROTOBUF_GENERATE_CPP(REP_SRCS REP_HDRS ${EOS_PROTO_ROOT}/ConsoleReply.proto)\nPROTOBUF_GENERATE_CPP(ACL_SRCS ACL_HDRS ${EOS_PROTO_ROOT}/Acl.proto)\nPROTOBUF_GENERATE_CPP(NS_SRCS NS_HDRS ${EOS_PROTO_ROOT}/Ns.proto)\nPROTOBUF_GENERATE_CPP(FIND_SRCS FIND_HDRS ${EOS_PROTO_ROOT}/Find.proto)\nPROTOBUF_GENERATE_CPP(FS_SRCS FS_HDRS ${EOS_PROTO_ROOT}/Fs.proto)\nPROTOBUF_GENERATE_CPP(RM_SRCS RM_HDRS ${EOS_PROTO_ROOT}/Rm.proto)\nPROTOBUF_GENERATE_CPP(TOKEN_SRCS TOKEN_HDRS ${EOS_PROTO_ROOT}/Token.proto)\nPROTOBUF_GENERATE_CPP(EVICT_SRCS EVICT_HDRS ${EOS_PROTO_ROOT}/Evict.proto)\nPROTOBUF_GENERATE_CPP(ROUTE_SRCS ROUTE_HDRS ${EOS_PROTO_ROOT}/Route.proto)\nPROTOBUF_GENERATE_CPP(IO_SRCS IO_HDRS ${EOS_PROTO_ROOT}/Io.proto)\nPROTOBUF_GENERATE_CPP(GROUP_SRCS GROUP_HDRS ${EOS_PROTO_ROOT}/Group.proto)\nPROTOBUF_GENERATE_CPP(DEBUG_SRCS DEBUG_HDRS ${EOS_PROTO_ROOT}/Debug.proto)\nPROTOBUF_GENERATE_CPP(DF_SRCS DF_HDRS ${EOS_PROTO_ROOT}/Df.proto)\nPROTOBUF_GENERATE_CPP(DEVICES_SRCS DEVICES_HDRS ${EOS_PROTO_ROOT}/Devices.proto)\nPROTOBUF_GENERATE_CPP(NODE_SRCS NODE_HDRS ${EOS_PROTO_ROOT}/Node.proto)\nPROTOBUF_GENERATE_CPP(QUOTA_SRCS QUOTA_HDRS ${EOS_PROTO_ROOT}/Quota.proto)\nPROTOBUF_GENERATE_CPP(SPACE_SRCS SPACE_HDRS ${EOS_PROTO_ROOT}/Space.proto)\nPROTOBUF_GENERATE_CPP(CONFIG_SRCS CONFIG_HDRS ${EOS_PROTO_ROOT}/Config.proto)\nPROTOBUF_GENERATE_CPP(ACCESS_SRCS ACCESS_HDRS ${EOS_PROTO_ROOT}/Access.proto)\nPROTOBUF_GENERATE_CPP(FSCK_SRCS FSCK_HDRS ${EOS_PROTO_ROOT}/Fsck.proto)\nPROTOBUF_GENERATE_CPP(CONVERT_SRCS CONVERT_HDRS ${EOS_PROTO_ROOT}/Convert.proto)\nPROTOBUF_GENERATE_CPP(ATTR_SRCS ATTR_HDRS ${EOS_PROTO_ROOT}/Attr.proto)\nPROTOBUF_GENERATE_CPP(AUTH_SRCS AUTH_HDRS ${EOS_PROTO_ROOT}/Authentication.proto)\nPROTOBUF_GENERATE_CPP(CHMOD_SRCS CHMOD_HDRS ${EOS_PROTO_ROOT}/ChangeMode.proto)\nPROTOBUF_GENERATE_CPP(CHOWN_SRCS CHOWN_HDRS ${EOS_PROTO_ROOT}/Chown.proto)\nPROTOBUF_GENERATE_CPP(CP_SRCS CP_HDRS ${EOS_PROTO_ROOT}/Cp.proto)\nPROTOBUF_GENERATE_CPP(FILE_SRCS FILE_HDRS ${EOS_PROTO_ROOT}/File.proto)\nPROTOBUF_GENERATE_CPP(FILEINFO_SRCS FILEINFO_HDRS ${EOS_PROTO_ROOT}/Fileinfo.proto)\nPROTOBUF_GENERATE_CPP(LS_SRCS LS_HDRS ${EOS_PROTO_ROOT}/Ls.proto)\nPROTOBUF_GENERATE_CPP(METADATA_SRCS METADATA_HDRS ${EOS_PROTO_ROOT}/Metadata.proto)\nPROTOBUF_GENERATE_CPP(MKDIR_SRCS MKDIR_HDRS ${EOS_PROTO_ROOT}/MakeDirectory.proto)\nPROTOBUF_GENERATE_CPP(MOVE_SRCS MOVE_HDRS ${EOS_PROTO_ROOT}/Move.proto)\nPROTOBUF_GENERATE_CPP(RMDIR_SRCS RMDIR_HDRS ${EOS_PROTO_ROOT}/Rmdir.proto)\nPROTOBUF_GENERATE_CPP(STAT_SRCS STAT_HDRS ${EOS_PROTO_ROOT}/StatWnc.proto)\nPROTOBUF_GENERATE_CPP(TOUCH_SRCS TOUCH_HDRS ${EOS_PROTO_ROOT}/Touch.proto)\nPROTOBUF_GENERATE_CPP(VERSION_SRCS VERSION_HDRS ${EOS_PROTO_ROOT}/Version.proto)\nPROTOBUF_GENERATE_CPP(VID_SRCS VID_HDRS ${EOS_PROTO_ROOT}/Vid.proto)\nPROTOBUF_GENERATE_CPP(WHO_SRCS WHO_HDRS ${EOS_PROTO_ROOT}/Who.proto)\nPROTOBUF_GENERATE_CPP(WHOAMI_SRCS WHOAMI_HDRS ${EOS_PROTO_ROOT}/Whoami.proto)\nPROTOBUF_GENERATE_CPP(GEOSCHED_SRCS GEOSCHED_HDRS ${EOS_PROTO_ROOT}/Geosched.proto)\nPROTOBUF_GENERATE_CPP(HEALTH_SRCS HEALTH_HDRS ${EOS_PROTO_ROOT}/Health.proto)\nPROTOBUF_GENERATE_CPP(ARCHIVE_SRCS ARCHIVE_HDRS ${EOS_PROTO_ROOT}/Archive.proto)\nPROTOBUF_GENERATE_CPP(BACKUP_SRCS BACKUP_HDRS ${EOS_PROTO_ROOT}/Backup.proto)\nPROTOBUF_GENERATE_CPP(MAP_SRCS MAP_HDRS ${EOS_PROTO_ROOT}/Map.proto)\nPROTOBUF_GENERATE_CPP(MEMBER_SRCS MEMBER_HDRS ${EOS_PROTO_ROOT}/Member.proto)\nPROTOBUF_GENERATE_CPP(STATUS_SRCS STATUS_HDRS ${EOS_PROTO_ROOT}/Status.proto)\nPROTOBUF_GENERATE_CPP(SCHED_SRCS SCHED_HDRS ${EOS_PROTO_ROOT}/Sched.proto)\n\nset(CLI_PROTO_SRCS\n  ${REQ_SRCS} ${REP_SRCS} ${RECY_SRCS} ${TRAFFIC_SHAPING_SRCS} ${ACL_SRCS} ${NS_SRCS} ${FIND_SRCS}\n  ${FS_SRCS} ${RM_SRCS} ${TOKEN_SRCS} ${EVICT_SRCS} ${ROUTE_SRCS} ${IO_SRCS} ${GROUP_SRCS}\n  ${DEBUG_SRCS} ${DF_SRCS} ${DEVICES_SRCS} ${NODE_SRCS} ${QUOTA_SRCS} ${SPACE_SRCS} ${CONFIG_SRCS} ${ACCESS_SRCS}\n  ${FSCK_SRCS} ${SHARE_SRCS} ${GRPC_SRCS} ${CONVERT_SRCS} ${ATTR_SRCS}\n  ${AUTH_SRCS} ${CHMOD_SRCS} ${CHOWN_SRCS} ${CP_SRCS} ${FILE_SRCS} ${FILEINFO_SRCS} ${LS_SRCS}\n  ${METADATA_SRCS} ${MKDIR_SRCS} ${MOVE_SRCS} ${RMDIR_SRCS} ${STAT_SRCS}\n  ${TOUCH_SRCS} ${VERSION_SRCS} ${VID_SRCS} ${WHO_SRCS} ${WHOAMI_SRCS}\n  ${GEOSCHED_SRCS} ${HEALTH_SRCS} ${ARCHIVE_SRCS} ${BACKUP_SRCS} ${MAP_SRCS}\n  ${MEMBER_SRCS} ${STATUS_SRCS} ${SCHED_SRCS}\n)\n\nset(CLI_PROTO_HDRS\n  ${REQ_HDRS} ${REP_HDRS} ${RECY_HDRS} ${TRAFFIC_SHAPING_HDRS} ${ACL_HDRS} ${NS_HDRS} ${FIND_HDRS}\n  ${FS_HDRS} ${RM_HDRS} ${TOKEN_HDRS} ${EVICT_HDRS} ${ROUTE_HDRS} ${IO_HDRS} ${GROUP_HDRS}\n  ${DEBUG_HDRS} ${DF_HDRS} ${DEVICES_HDRS} ${NODE_HDRS} ${QUOTA_HDRS} ${SPACE_HDRS} ${CONFIG_HDRS} ${ACCESS_HDRS}\n  ${FSCK_HDRS} ${SHARE_HDRS} ${GRPC_HDRS} ${CONVERT_HDRS} ${ATTR_HDRS}\n  ${AUTH_HDRS} ${CHMOD_HDRS} ${CHOWN_HDRS} ${CP_HDRS} ${FILE_HDRS} ${FILEINFO_HDRS} ${LS_HDRS}\n  ${METADATA_HDRS} ${MKDIR_HDRS} ${MOVE_HDRS} ${RMDIR_HDRS} ${STAT_HDRS}\n  ${TOUCH_HDRS} ${VERSION_HDRS} ${VID_HDRS} ${WHO_HDRS} ${WHOAMI_HDRS}\n  ${GEOSCHED_HDRS} ${HEALTH_HDRS} ${ARCHIVE_HDRS} ${BACKUP_HDRS} ${MAP_HDRS}\n  ${MEMBER_HDRS} ${STATUS_HDRS} ${SCHED_HDRS}\n)\n\nset_source_files_properties(\n  ${CLI_PROTO_SRCS} ${CLI_PROTO_HDRS}\n  PROPERTIES GENERATED 1)\n\nadd_library(EosCliProto-Objects OBJECT\n  ${CLI_PROTO_SRCS} ${CLI_PROTO_HDRS})\n\ntarget_link_libraries(EosCliProto-Objects PUBLIC\n  PROTOBUF::PROTOBUF\n  ABSL::ABSL)\n\ntarget_include_directories(EosCliProto-Objects PUBLIC\n  $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)\n\nset_target_properties(EosCliProto-Objects PROPERTIES\n  POSITION_INDEPENDENT_CODE TRUE)\n\n#-------------------------------------------------------------------------------\n# Generate protocol buffer objects for GRPC\n#-------------------------------------------------------------------------------\nif (Linux)\n     add_custom_target(RpcFileGeneration DEPENDS\n    ${GRPC_SRCS} ${GRPC_HDRS})\n\n  set(GRPC_PROTOS ${CMAKE_SOURCE_DIR}/common/grpc-proto/Rpc.proto)\n  set(GRPC_PROTOBUF_PATH \"${CMAKE_BINARY_DIR}/proto/\")\n  grpc_generate_cpp(GRPC_SVC_SRCS GRPC_SVC_HDRS ${GRPC_PROTOBUF_PATH} ${GRPC_PROTOS})\n\n  set(GRPC_SVC_SRCS ${GRPC_SVC_SRCS} PARENT_SCOPE)\n  set(GRPC_SVC_HDRS ${GRPC_SVC_HDRS} PARENT_SCOPE)\n\n  set_source_files_properties(\n    ${GRPC_SVC_SRCS}\n    ${GRPC_SVC_HDRS}\n    PROPERTIES GENERATED TRUE)\n\n  add_library(EosGrpcProto-Objects OBJECT\n    ${GRPC_SVC_SRCS} ${GRPC_SVC_HDRS})\n\n  # @note see remark from RestGrpc-Objects\n  target_compile_options(EosGrpcProto-Objects PRIVATE -Wno-sign-compare)\n\n  add_dependencies(EosGrpcProto-Objects RpcFileGeneration)\n\n  target_link_libraries(EosGrpcProto-Objects PUBLIC\n    GRPC::grpc\n    GRPC::grpc++\n    PROTOBUF::PROTOBUF\n    ABSL::ABSL)\n\n  target_include_directories(EosGrpcProto-Objects PUBLIC\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)\n\n  set_target_properties(EosGrpcProto-Objects PROPERTIES\n    POSITION_INDEPENDENT_CODE TRUE)\n\n  #------------------------------------------------------------------------------\n  # Generate gRPC objects for the CLI\n  #------------------------------------------------------------------------------\n  grpc_generate_cpp(ACCESS_GRPC_SRCS ACCESS_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Access.proto)\n  grpc_generate_cpp(ACL_GRPC_SRCS ACL_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Acl.proto)\n  grpc_generate_cpp(ATTR_GRPC_SRCS ATTR_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Attr.proto)\n  grpc_generate_cpp(AUTH_GRPC_SRCS AUTH_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Authentication.proto)\n  grpc_generate_cpp(CHMOD_GRPC_SRCS CHMOD_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/ChangeMode.proto)\n  grpc_generate_cpp(CHOWN_GRPC_SRCS CHOWN_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Chown.proto)\n  grpc_generate_cpp(CONFIG_GRPC_SRCS CONFIG_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Config.proto)\n  grpc_generate_cpp(CP_GRPC_SRCS CP_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Cp.proto)\n  grpc_generate_cpp(DEBUG_GRPC_SRCS DEBUG_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Debug.proto)\n  grpc_generate_cpp(FILE_GRPC_SRCS FILE_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/File.proto)\n  grpc_generate_cpp(FILEINFO_GRPC_SRCS FILEINFO_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Fileinfo.proto)\n  grpc_generate_cpp(FIND_GRPC_SRCS FIND_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Find.proto)\n  grpc_generate_cpp(FS_GRPC_SRCS FS_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Fs.proto)\n  grpc_generate_cpp(FSCK_GRPC_SRCS FSCK_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Fsck.proto)\n  grpc_generate_cpp(GROUP_GRPC_SRCS GROUP_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Group.proto)\n  grpc_generate_cpp(IO_GRPC_SRCS IO_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Io.proto)\n  grpc_generate_cpp(LS_GRPC_SRCS LS_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Ls.proto)\n  grpc_generate_cpp(METADATA_GRPC_SRCS METADATA_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Metadata.proto)\n  grpc_generate_cpp(MKDIR_GRPC_SRCS MKDIR_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/MakeDirectory.proto)\n  grpc_generate_cpp(MOVE_GRPC_SRCS MOVE_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Move.proto)\n  grpc_generate_cpp(NODE_GRPC_SRCS NODE_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Node.proto)\n  grpc_generate_cpp(NS_GRPC_SRCS NS_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Ns.proto)\n  grpc_generate_cpp(QUOTAGRPC_SRCS QUOTA_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Quota.proto)\n  grpc_generate_cpp(RECY_GRPC_SRCS RECY_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${CMAKE_SOURCE_DIR}/common/grpc-proto/Recycle.proto)\n  grpc_generate_cpp(REP_GRPC_SRCS REP_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/ConsoleReply.proto)\n  grpc_generate_cpp(REQ_GRPC_SRCS REQ_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/ConsoleRequest.proto)\n  grpc_generate_cpp(RM_GRPC_SRCS RM_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Rm.proto)\n  grpc_generate_cpp(RMDIR_GRPC_SRCS RMDIR_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Rmdir.proto)\n  grpc_generate_cpp(ROUTE_GRPC_SRCS ROUTE_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Route.proto)\n  grpc_generate_cpp(SPACE_GRPC_SRCS SPACE_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Space.proto)\n  grpc_generate_cpp(STAT_GRPC_SRCS STAT_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/StatWnc.proto)\n  grpc_generate_cpp(TOUCH_GRPC_SRCS TOUCH_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Touch.proto)\n  grpc_generate_cpp(VERSION_GRPC_SRCS VERSION_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Version.proto)\n  grpc_generate_cpp(VID_GRPC_SRCS VID_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Vid.proto)\n  grpc_generate_cpp(WHO_GRPC_SRCS WHO_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Who.proto)\n  grpc_generate_cpp(WHOAMI_GRPC_SRCS WHOAMI_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Whoami.proto)\n  grpc_generate_cpp(GEOSCHED_GRPC_SRCS GEOSCHED_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Geosched.proto)\n  grpc_generate_cpp(HEALTH_GRPC_SRCS HEALTH_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Health.proto)\n  grpc_generate_cpp(ARCHIVE_GRPC_SRCS ARCHIVE_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Archive.proto)\n  grpc_generate_cpp(BACKUP_GRPC_SRCS BACKUP_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Backup.proto)\n  grpc_generate_cpp(MAP_GRPC_SRCS MAP_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Map.proto)\n  grpc_generate_cpp(MEMBER_GRPC_SRCS MEMBER_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Member.proto)\n  grpc_generate_cpp(STATUS_GRPC_SRCS STATUS_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Status.proto)\n  grpc_generate_cpp(SCHED_GRPC_SRCS SCHED_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Sched.proto)\n  grpc_generate_cpp(SHAPING_GRPC_SRCS SHAPING_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${EOS_PROTO_ROOT}/Shaping.proto)\n\n  set(CLI_GRPC_SRCS\n    ${ACCESS_GRPC_SRCS} ${ACL_GRPC_SRCS} ${ATTR_GRPC_SRCS} ${AUTH_GRPC_SRCS}\n    ${CHMOD_GRPC_SRCS} ${CHOWN_GRPC_SRCS} ${CONFIG_GRPC_SRCS} ${DEBUG_GRPC_SRCS}\n    ${FILE_GRPC_SRCS} ${FILEINFO_GRPC_SRCS} ${FIND_GRPC_SRCS}\n    ${FS_GRPC_SRCS} ${FSCK_GRPC_SRCS} ${GROUP_GRPC_SRCS} ${IO_GRPC_SRCS}\n    ${LS_GRPC_SRCS} ${METADATA_GRPC_SRCS} ${MKDIR_GRPC_SRCS}\n    ${MOVE_GRPC_SRCS} ${NODE_GRPC_SRCS} ${NS_GRPC_SRCS} ${QUOTA_GRPC_SRCS}\n    ${RECY_GRPC_SRCS} ${REP_GRPC_SRCS} ${REQ_GRPC_SRCS} ${RM_GRPC_SRCS}\n    ${RMDIR_GRPC_SRCS} ${ROUTE_GRPC_SRCS} ${SPACE_GRPC_SRCS} ${EVICT_GRPC_SRCS}\n    ${STAT_GRPC_SRCS} ${TOUCH_GRPC_SRCS} ${VERSION_GRPC_SRCS}\n    ${VID_GRPC_SRCS} ${WHO_GRPC_SRCS} ${WHOAMI_GRPC_SRCS}\n    ${CP_GRPC_SRCS} ${GEOSCHED_GRPC_SRCS} ${HEALTH_GRPC_SRCS} ${ARCHIVE_GRPC_SRCS}\n    ${BACKUP_GRPC_SRCS} ${MAP_GRPC_SRCS} ${MEMBER_GRPC_SRCS} ${STATUS_GRPC_SRCS}\n    ${SCHED_GRPC_SRCS})\n\n  set(CLI_GRPC_HDRS\n    ${ACCESS_GRPC_HDRS} ${ACL_GRPC_HDRS} ${ATTR_GRPC_HDRS} ${AUTH_GRPC_HDRS}\n    ${CHMOD_GRPC_HDRS} ${CHOWN_GRPC_HDRS} ${CONFIG_GRPC_HDRS} ${DEBUG_GRPC_HDRS}\n    ${FILE_GRPC_HDRS} ${FILEINFO_GRPC_HDRS} ${FIND_GRPC_HDRS}\n    ${FS_GRPC_HDRS} ${FSCK_GRPC_HDRS} ${GROUP_GRPC_HDRS} ${IO_GRPC_HDRS}\n    ${LS_GRPC_HDRS} ${METADATA_GRPC_HDRS} ${MKDIR_GRPC_HDRS}\n    ${MOVE_GRPC_HDRS} ${NODE_GRPC_HDRS} ${NS_GRPC_HDRS} ${QUOTA_GRPC_HDRS}\n    ${RECY_GRPC_HDRS} ${REP_GRPC_HDRS} ${REQ_GRPC_HDRS} ${RM_GRPC_HDRS}\n    ${RMDIR_GRPC_HDRS} ${ROUTE_GRPC_HDRS} ${SPACE_GRPC_HDRS} ${EVICT_GRPC_HDRS}\n    ${STAT_GRPC_HDRS} ${TOUCH_GRPC_HDRS} ${VERSION_GRPC_HDRS}\n    ${VID_GRPC_HDRS} ${WHO_GRPC_HDRS} ${WHOAMI_GRPC_HDRS}\n    ${CP_GRPC_HDRS} ${GEOSCHED_GRPC_HDRS} ${HEALTH_GRPC_HDRS} ${ARCHIVE_GRPC_HDRS}\n    ${BACKUP_GRPC_HDRS} ${MAP_GRPC_HDRS} ${MEMBER_GRPC_HDRS} ${STATUS_GRPC_HDRS}\n    ${SCHED_GRPC_SRCS})\n\n  set_source_files_properties(\n    ${CLI_GRPC_SRCS}\n    ${CLI_GRPC_HDRS}\n    PROPERTIES GENERATED TRUE)\n\n  add_library(EosCliGrpc-Objects OBJECT\n    ${CLI_GRPC_SRCS}\n    ${CLI_GRPC_HDRS})\n\n  # @note see remark from RestGrpc-Objects\n  target_compile_options(EosCliGrpc-Objects PRIVATE -Wno-sign-compare)\n\n  add_dependencies(EosCliGrpc-Objects EosCliProto-Objects)\n\n  target_link_libraries(EosCliGrpc-Objects PUBLIC\n    GRPC::grpc++\n    PROTOBUF::PROTOBUF\n    ABSL::ABSL)\n\n  target_include_directories(EosCliGrpc-Objects PUBLIC\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)\n\n  set_target_properties(EosCliGrpc-Objects PROPERTIES\n    POSITION_INDEPENDENT_CODE TRUE)\n\n  #------------------------------------------------------------------------------\n  # Generate protobuf and gRPC objects for EOS Windows native client\n  #------------------------------------------------------------------------------\n  set(WNC_PROTO ${EOS_PROTO_ROOT}/EosWnc.proto)\n  PROTOBUF_GENERATE_CPP(WNC_SRCS WNC_HDRS ${WNC_PROTO})\n  grpc_generate_cpp(WNC_GRPC_SRCS WNC_GRPC_HDRS ${GRPC_PROTOBUF_PATH} ${WNC_PROTO})\n\n  set_source_files_properties(\n    ${WNC_SRCS}\n    ${WNC_HDRS}\n    ${WNC_GRPC_SRCS}\n    ${WNC_GRPC_HDRS}\n    PROPERTIES GENERATED TRUE)\n\n  add_library(EosWncGrpcProto-Objects OBJECT\n    ${WNC_SRCS}\n    ${WNC_HDRS}\n    ${WNC_GRPC_SRCS}\n    ${WNC_GRPC_HDRS})\n\n  # @note see remark from RestGrpc-Objects\n  target_compile_options(EosWncGrpcProto-Objects PRIVATE -Wno-sign-compare)\n\n  add_dependencies(EosWncGrpcProto-Objects\n    EosCliProto-Objects\n    EosCliGrpc-Objects)\n\n  target_link_libraries(EosWncGrpcProto-Objects PUBLIC\n    GRPC::grpc++\n    PROTOBUF::PROTOBUF\n    ABSL::ABSL)\n\n  target_include_directories(EosWncGrpcProto-Objects PUBLIC\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)\n\n  set_target_properties(EosWncGrpcProto-Objects PROPERTIES\n    POSITION_INDEPENDENT_CODE TRUE)\n\n  #---------------------------------------------------------------------------------\n  # Generate HTTP GRPC REST gateway\n  #---------------------------------------------------------------------------------\n  set(REST_GW_PROTO eos_rest_gateway_service)\n  set(PROTO_PATH ${CMAKE_SOURCE_DIR}/proto/eos_rest_gateway/${REST_GW_PROTO}.proto)\n  set(REST_GW_PROTOS ${CMAKE_SOURCE_DIR}/proto/eos_rest_gateway/${REST_GW_PROTO}.proto)\n  set(REST_GW_SRCS ${CMAKE_BINARY_DIR}/proto/eos_rest_gateway/${REST_GW_PROTO}.pb.cc)\n  set(REST_GW_HDRS ${CMAKE_BINARY_DIR}/proto/eos_rest_gateway/${REST_GW_PROTO}.pb.h)\n  set(REST_GW_GRPC_SRCS  ${CMAKE_BINARY_DIR}/proto/eos_rest_gateway/${REST_GW_PROTO}.grpc.pb.cc)\n  set(REST_GW_GRPC_HDRS  ${CMAKE_BINARY_DIR}/proto/eos_rest_gateway/${REST_GW_PROTO}.grpc.pb.h)\n  set(GW_GO_GEN ${CMAKE_BINARY_DIR}/go/eos.rest.gateway.service/${REST_GW_PROTO}.pb.gw.go)\n  set(PROTO_GRPC_GO ${CMAKE_BINARY_DIR}/go/eos.rest.gateway.service/${REST_GW_PROTO}_grpc.pb.go)\n  set(PROTO_GO ${CMAKE_BINARY_DIR}/go/eos.rest.gateway.service/${REST_GW_PROTO}.pb.go)\n\n  set(ANN_PROTO1 annotations)\n  set(ANN_PROTO2 http)\n  set(ANN_PROTO1_PATH ${CMAKE_SOURCE_DIR}/proto/eos_rest_gateway/google/api/${ANN_PROTO1}.proto)\n  set(ANN_PROTO2_PATH ${CMAKE_SOURCE_DIR}/proto/eos_rest_gateway/google/api/${ANN_PROTO2}.proto)\n  set(ANN_PROTOS ${ANN_PROTO1_PATH} ${ANN_PROTO2_PATH})\n  set(ANN_SRCS ${CMAKE_BINARY_DIR}/proto/eos_rest_gateway/google/api/${ANN_PROTO1}.pb.cc\n               ${CMAKE_BINARY_DIR}/proto/eos_rest_gateway/google/api/${ANN_PROTO2}.pb.cc)\n  set(ANN_HDRS ${CMAKE_BINARY_DIR}/proto/eos_rest_gateway/google/api/${ANN_PROTO1}.pb.h\n               ${CMAKE_BINARY_DIR}/proto/eos_rest_gateway/google/api//${ANN_PROTO2}.pb.h)\n\n  set_source_files_properties(\n    ${ANN_SRCS} ${ANN_HDRS}\n    PROPERTIES GENERATED TRUE)\n\n  # protoc command for http.proto and annotations.proto\n  add_custom_command(\n    OUTPUT ${ANN_SRCS} ${ANN_HDRS}\n    COMMAND ${PROTOBUF3_PROTOC_EXECUTABLE} -I${CMAKE_SOURCE_DIR} -I${CMAKE_SOURCE_DIR}/common/grpc-proto/ -I${CMAKE_SOURCE_DIR}/proto/eos_rest_gateway -I${GRPC_INCLUDE_DIR}\n            --cpp_out=../ ${ANN_PROTOS})\n\n  add_library(RestAnnot-Objects OBJECT\n    ${ANN_SRCS} ${ANN_HDRS})\n\n  target_include_directories(RestAnnot-Objects PUBLIC\n    \"$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>\")\n\n  set_target_properties(RestAnnot-Objects PROPERTIES\n    POSITION_INDEPENDENT_CODE TRUE)\n\n  target_link_libraries(RestAnnot-Objects PUBLIC\n    GRPC::grpc++)\n\n  add_custom_target(\n    GoogleApis-Target\n    COMMAND echo \"Running protoc for annotations and http\"\n    DEPENDS RestAnnot-Objects)\n\n  # protoc command for echo_service.proto\n  add_custom_command(\n    OUTPUT ${REST_GW_SRCS} ${REST_GW_HDRS} ${REST_GW_GRPC_SRCS} ${REST_GW_GRPC_HDRS}\n    COMMAND ${CMAKE_COMMAND} -E env\n    \"LD_LIBRARY_PATH=${GRPC_LD_LIBRARY_PATH}:${LD_LIBRARY_PATH}\"\n    ${PROTOBUF3_PROTOC_EXECUTABLE}\n            -I${CMAKE_SOURCE_DIR}\n            -I${CMAKE_SOURCE_DIR}/common/grpc-proto/\n            -I${CMAKE_SOURCE_DIR}/proto/${EOS_PROTO_ROOT}\n            -I${GRPC_INCLUDE_DIR}\n            --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN}\n            --cpp_out=../\n            --grpc_out=../\n            ${REST_GW_PROTOS}\n    DEPENDS GoogleApis-Target EosCliProto-Objects)\n\n  set_source_files_properties(\n    ${REST_GW_SRCS} ${REST_GW_HDRS} ${REST_GW_GRPC_SRCS} ${REST_GW_GRPC_HDRS}\n    PROPERTIES GENERATED TRUE)\n\n  add_library(RestGrpc-Objects OBJECT\n    ${REST_GW_SRCS} ${REST_GW_HDRS}\n    ${REST_GW_GRPC_SRCS} ${REST_GW_GRPC_HDRS})\n\n  # @note avoid warning coming from from protobuf implementation\n  # /opt/eos/grpc/include/grpcpp/support/proto_buffer_reader.h:157:24:\n  # warning: comparison of integer expressions of different signedness:\n  # ‘uint64_t’ {aka ‘long unsigned int’} and ‘int’ [-Wsign-compare]\n  #       if (slice_length <= count) {\n  #           ~~~~~~~~~~~~~^~~~~~~~\n  target_compile_options(RestGrpc-Objects PRIVATE -Wno-sign-compare)\n\n  target_include_directories(RestGrpc-Objects PUBLIC\n    \"$<BUILD_INTERFACE:${CMAKE_BINARY_DIR};${CMAKE_CURRENT_BINARY_DIR}>;\")\n\n  target_link_libraries(RestGrpc-Objects PUBLIC\n    GRPC::grpc++)\n\n  set_target_properties(RestGrpc-Objects PROPERTIES\n    POSITION_INDEPENDENT_CODE TRUE)\nendif()\n"
  },
  {
    "path": "proto/eos_rest_gateway/eos_rest_gateway_service.proto",
    "content": "syntax = \"proto3\";\n\n// Eos Rest Gateway API provides HTTP access to EOS console commands\npackage eos.rest.gateway.service;\n\nimport \"proto/eos_rest_gateway/google/api/annotations.proto\";\nimport \"Access.proto\";\nimport \"Acl.proto\";\nimport \"Archive.proto\";\nimport \"Attr.proto\";\nimport \"Backup.proto\";\nimport \"ChangeMode.proto\";\nimport \"Chown.proto\";\nimport \"Config.proto\";\nimport \"ConsoleReply.proto\";\nimport \"Convert.proto\";\nimport \"Cp.proto\";\nimport \"Debug.proto\";\nimport \"Evict.proto\";\nimport \"File.proto\";\nimport \"Fileinfo.proto\";\nimport \"Find.proto\";\nimport \"Fs.proto\";\nimport \"Fsck.proto\";\nimport \"Geosched.proto\";\nimport \"Group.proto\";\nimport \"Health.proto\";\nimport \"Io.proto\";\nimport \"Ls.proto\";\nimport \"MakeDirectory.proto\";\nimport \"Map.proto\";\nimport \"Member.proto\";\nimport \"Move.proto\";\nimport \"Node.proto\";\nimport \"Ns.proto\";\nimport \"Quota.proto\";\nimport \"Recycle.proto\";\nimport \"Rm.proto\";\nimport \"Rmdir.proto\";\nimport \"Route.proto\";\nimport \"Space.proto\";\nimport \"StatWnc.proto\";\nimport \"Status.proto\";\nimport \"Token.proto\";\nimport \"Touch.proto\";\nimport \"Version.proto\";\nimport \"Vid.proto\";\nimport \"Who.proto\";\nimport \"Whoami.proto\";\n\noption go_package = \"eos.rest.gateway.service\";\n\nservice EosRestGatewayService {\n\t// AclRequest method receives a request for an eos acl command\n\t// and returns the result of the command\n\trpc AclRequest(eos.console.AclProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/acl_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// AccessRequest method receives a request for an eos access command\n\t// and returns the result of the command\n\trpc AccessRequest(eos.console.AccessProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/access_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// ArchiveRequest method receives a request for an eos archive command\n\t// and returns the result of the command\n\trpc ArchiveRequest(eos.console.ArchiveProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/archive_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// AttrRequest method receives a request for an eos command\n\t// and returns the result of the command\n\trpc AttrRequest(eos.console.AttrProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/attr_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// BackupRequest method receives a request for an eos backup command\n\t// and returns the result of the command\n\trpc BackupRequest(eos.console.BackupProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/backup_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// ChmodRequest method receives a request for an eos chmod command\n\t// and returns the result of the command\n\trpc ChmodRequest(eos.console.ChmodProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/chmod_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// ChownRequest method receives a request for an eos chown command\n\t// and returns the result of the command\n\trpc ChownRequest(eos.console.ChownProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/chown_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// ConfigRequest method receives a request for an eos config command\n\t// and returns the result of the command\n\trpc ConfigRequest(eos.console.ConfigProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/config_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// ConvertRequest method receives a request for an eos convert command\n\t// and returns the result of the command\n\trpc ConvertRequest(eos.console.ConvertProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/convert_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// CpRequest method receives a request for an eos cp command\n\t// and returns the result of the command\n\trpc CpRequest(eos.console.CpProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/cp_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// DebugRequest method receives a request for an eos debug command\n\t// and returns the result of the command\n\trpc DebugRequest(eos.console.DebugProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/debug_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// EvictRequest method receives a request for an eos evict command\n\t// and returns the result of the command\n\trpc EvictRequest(eos.console.EvictProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/evict_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// FileRequest method receives a request for an eos file command\n\t// and returns the result of the command\n\trpc FileRequest(eos.console.FileProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/file_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// FileinfoRequest method receives a request for an eos file info command\n\t// and returns the result of the command\n\trpc FileinfoRequest(eos.console.FileinfoProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/fileinfo_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// FindRequest method receives a request for an eos find command\n\t// and returns the result of the command\n\trpc FindRequest(eos.console.FindProto) returns (stream eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/find_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// FsRequest method receives a request for an eos fs command\n\t// and returns the result of the command\n\trpc FsRequest(eos.console.FsProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/fs_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// FsckRequest method receives a request for an eos fsck command\n\t// and returns the result of the command\n\trpc FsckRequest(eos.console.FsckProto) returns (stream eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/fsck_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// GeoschedRequest method receives a request for an eos geosched command\n\t// and returns the result of the command\n\trpc GeoschedRequest(eos.console.GeoschedProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/geosched_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// GroupRequest method receives a request for an eos group command\n\t// and returns the result of the command\n\trpc GroupRequest(eos.console.GroupProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/group_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// HealthRequest method receives a request for an eos health command\n\t// and returns the result of the command\n\trpc HealthRequest(eos.console.HealthProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/health_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// IoRequest method receives a request for an eos io command\n\t// and returns the result of the command\n\trpc IoRequest(eos.console.IoProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/io_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// LsRequest method receives a request for an eos ls command\n\t// and returns the result of the command\n\trpc LsRequest(eos.console.LsProto) returns (stream eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/ls_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// MapRequest method receives a request for an eos map command\n\t// and returns the result of the command\n\trpc MapRequest(eos.console.MapProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/map_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// MemberRequest method receives a request for an eos member command\n\t// and returns the result of the command\n\trpc MemberRequest(eos.console.MemberProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/member_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// MkdirRequest method receives a request for an eos mkdir command\n\t// and returns the result of the command\n\trpc MkdirRequest(eos.console.MkdirProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/mkdir_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// MvRequest method receives a request for an eos mv command\n\t// and returns the result of the command\n\trpc MvRequest(eos.console.MoveProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/mv_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// NodeRequest method receives a request for an eos node command\n\t// and returns the result of the command\n\trpc NodeRequest(eos.console.NodeProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/node_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// NsRequest method receives a request for an eos ns command\n\t// and returns the result of the command\n\trpc NsRequest(eos.console.NsProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/ns_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// QuotaRequest method receives a request for an eos quota command\n\t// and returns the result of the command\n\trpc QuotaRequest(eos.console.QuotaProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/quota_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// RecycleRequest method receives a request for an eos recycle command\n\t// and returns the result of the command\n\trpc RecycleRequest(eos.console.RecycleProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/recycle_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// RmRequest method receives a request for an eos rm command\n\t// and returns the result of the command\n\trpc RmRequest(eos.console.RmProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/rm_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// RmdirRequest method receives a request for an eos rmdir command\n\t// and returns the result of the command\n\trpc RmdirRequest(eos.console.RmdirProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/rmdir_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// RouteRequest method receives a request for an eos route command\n\t// and returns the result of the command\n\trpc RouteRequest(eos.console.RouteProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/route_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// SpaceRequest method receives a request for an eos space command\n\t// and returns the result of the command\n\trpc SpaceRequest(eos.console.SpaceProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/space_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// StatRequest method receives a request for an eos stat command\n\t// and returns the result of the command\n\trpc StatRequest(eos.console.StatProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/stat_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// StatusRequest method receives a request for an eos status command\n\t// and returns the result of the command\n\trpc StatusRequest(eos.console.StatusProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/status_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// TokenRequest method receives a request for an eos token command\n\t// and returns the result of the command\n\trpc TokenRequest(eos.console.TokenProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/token_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// TouchRequest method receives a request for an eos touch command\n\t// and returns the result of the command\n\trpc TouchRequest(eos.console.TouchProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/touch_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// VersionRequest method receives a request for an eos version command\n\t// and returns the result of the command\n\trpc VersionRequest(eos.console.VersionProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/version_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// VidRequest method receives a request for an eos vid command\n\t// and returns the result of the command\n\trpc VidRequest(eos.console.VidProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/vid_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// WhoRequest method receives a request for an eos who command\n\t// and returns the result of the command\n\trpc WhoRequest(eos.console.WhoProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/who_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n\n\t// WhoamiRequest method receives a request for an eos whoami command\n\t// and returns the result of the command\n\trpc WhoamiRequest(eos.console.WhoamiProto) returns (eos.console.ReplyProto) {\n\t\toption (google.api.http) = {\n\t\t\tpost: \"/v1/eos/rest/gateway/whoami_cmd\"\n\t\t\tbody: \"*\"\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "proto/eos_rest_gateway/google/api/annotations.proto",
    "content": "// Copyright (c) 2015, Google Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax = \"proto3\";\n\npackage google.api;\n\nimport \"proto/eos_rest_gateway/google/api/http.proto\";\nimport \"google/protobuf/descriptor.proto\";\n\noption go_package = \"google.golang.org/genproto/googleapis/api/annotations;annotations\";\noption java_multiple_files = true;\noption java_outer_classname = \"AnnotationsProto\";\noption java_package = \"com.google.api\";\noption objc_class_prefix = \"GAPI\";\n\nextend google.protobuf.MethodOptions {\n  // See `HttpRule`.\n  HttpRule http = 72295728;\n}\n"
  },
  {
    "path": "proto/eos_rest_gateway/google/api/http.proto",
    "content": "// Copyright 2016 Google Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax = \"proto3\";\n\npackage google.api;\n\noption cc_enable_arenas = true;\noption go_package = \"google.golang.org/genproto/googleapis/api/annotations;annotations\";\noption java_multiple_files = true;\noption java_outer_classname = \"HttpProto\";\noption java_package = \"com.google.api\";\noption objc_class_prefix = \"GAPI\";\n\n\n// Defines the HTTP configuration for a service. It contains a list of\n// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method\n// to one or more HTTP REST API methods.\nmessage Http {\n  // A list of HTTP configuration rules that apply to individual API methods.\n  //\n  // **NOTE:** All service configuration rules follow \"last one wins\" order.\n  repeated HttpRule rules = 1;\n}\n\n// `HttpRule` defines the mapping of an RPC method to one or more HTTP\n// REST APIs.  The mapping determines what portions of the request\n// message are populated from the path, query parameters, or body of\n// the HTTP request.  The mapping is typically specified as an\n// `google.api.http` annotation, see \"google/api/annotations.proto\"\n// for details.\n//\n// The mapping consists of a field specifying the path template and\n// method kind.  The path template can refer to fields in the request\n// message, as in the example below which describes a REST GET\n// operation on a resource collection of messages:\n//\n//\n//     service Messaging {\n//       rpc GetMessage(GetMessageRequest) returns (Message) {\n//         option (google.api.http).get = \"/v1/messages/{message_id}/{sub.subfield}\";\n//       }\n//     }\n//     message GetMessageRequest {\n//       message SubMessage {\n//         string subfield = 1;\n//       }\n//       string message_id = 1; // mapped to the URL\n//       SubMessage sub = 2;    // `sub.subfield` is url-mapped\n//     }\n//     message Message {\n//       string text = 1; // content of the resource\n//     }\n//\n// The same http annotation can alternatively be expressed inside the\n// `GRPC API Configuration` YAML file.\n//\n//     http:\n//       rules:\n//         - selector: <proto_package_name>.Messaging.GetMessage\n//           get: /v1/messages/{message_id}/{sub.subfield}\n//\n// This definition enables an automatic, bidrectional mapping of HTTP\n// JSON to RPC. Example:\n//\n// HTTP | RPC\n// -----|-----\n// `GET /v1/messages/123456/foo`  | `GetMessage(message_id: \"123456\" sub: SubMessage(subfield: \"foo\"))`\n//\n// In general, not only fields but also field paths can be referenced\n// from a path pattern. Fields mapped to the path pattern cannot be\n// repeated and must have a primitive (non-message) type.\n//\n// Any fields in the request message which are not bound by the path\n// pattern automatically become (optional) HTTP query\n// parameters. Assume the following definition of the request message:\n//\n//\n//     message GetMessageRequest {\n//       message SubMessage {\n//         string subfield = 1;\n//       }\n//       string message_id = 1; // mapped to the URL\n//       int64 revision = 2;    // becomes a parameter\n//       SubMessage sub = 3;    // `sub.subfield` becomes a parameter\n//     }\n//\n//\n// This enables a HTTP JSON to RPC mapping as below:\n//\n// HTTP | RPC\n// -----|-----\n// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | `GetMessage(message_id: \"123456\" revision: 2 sub: SubMessage(subfield: \"foo\"))`\n//\n// Note that fields which are mapped to HTTP parameters must have a\n// primitive type or a repeated primitive type. Message types are not\n// allowed. In the case of a repeated type, the parameter can be\n// repeated in the URL, as in `...?param=A&param=B`.\n//\n// For HTTP method kinds which allow a request body, the `body` field\n// specifies the mapping. Consider a REST update method on the\n// message resource collection:\n//\n//\n//     service Messaging {\n//       rpc UpdateMessage(UpdateMessageRequest) returns (Message) {\n//         option (google.api.http) = {\n//           put: \"/v1/messages/{message_id}\"\n//           body: \"message\"\n//         };\n//       }\n//     }\n//     message UpdateMessageRequest {\n//       string message_id = 1; // mapped to the URL\n//       Message message = 2;   // mapped to the body\n//     }\n//\n//\n// The following HTTP JSON to RPC mapping is enabled, where the\n// representation of the JSON in the request body is determined by\n// protos JSON encoding:\n//\n// HTTP | RPC\n// -----|-----\n// `PUT /v1/messages/123456 { \"text\": \"Hi!\" }` | `UpdateMessage(message_id: \"123456\" message { text: \"Hi!\" })`\n//\n// The special name `*` can be used in the body mapping to define that\n// every field not bound by the path template should be mapped to the\n// request body.  This enables the following alternative definition of\n// the update method:\n//\n//     service Messaging {\n//       rpc UpdateMessage(Message) returns (Message) {\n//         option (google.api.http) = {\n//           put: \"/v1/messages/{message_id}\"\n//           body: \"*\"\n//         };\n//       }\n//     }\n//     message Message {\n//       string message_id = 1;\n//       string text = 2;\n//     }\n//\n//\n// The following HTTP JSON to RPC mapping is enabled:\n//\n// HTTP | RPC\n// -----|-----\n// `PUT /v1/messages/123456 { \"text\": \"Hi!\" }` | `UpdateMessage(message_id: \"123456\" text: \"Hi!\")`\n//\n// Note that when using `*` in the body mapping, it is not possible to\n// have HTTP parameters, as all fields not bound by the path end in\n// the body. This makes this option more rarely used in practice of\n// defining REST APIs. The common usage of `*` is in custom methods\n// which don't use the URL at all for transferring data.\n//\n// It is possible to define multiple HTTP methods for one RPC by using\n// the `additional_bindings` option. Example:\n//\n//     service Messaging {\n//       rpc GetMessage(GetMessageRequest) returns (Message) {\n//         option (google.api.http) = {\n//           get: \"/v1/messages/{message_id}\"\n//           additional_bindings {\n//             get: \"/v1/users/{user_id}/messages/{message_id}\"\n//           }\n//         };\n//       }\n//     }\n//     message GetMessageRequest {\n//       string message_id = 1;\n//       string user_id = 2;\n//     }\n//\n//\n// This enables the following two alternative HTTP JSON to RPC\n// mappings:\n//\n// HTTP | RPC\n// -----|-----\n// `GET /v1/messages/123456` | `GetMessage(message_id: \"123456\")`\n// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: \"me\" message_id: \"123456\")`\n//\n// # Rules for HTTP mapping\n//\n// The rules for mapping HTTP path, query parameters, and body fields\n// to the request message are as follows:\n//\n// 1. The `body` field specifies either `*` or a field path, or is\n//    omitted. If omitted, it assumes there is no HTTP body.\n// 2. Leaf fields (recursive expansion of nested messages in the\n//    request) can be classified into three types:\n//     (a) Matched in the URL template.\n//     (b) Covered by body (if body is `*`, everything except (a) fields;\n//         else everything under the body field)\n//     (c) All other fields.\n// 3. URL query parameters found in the HTTP request are mapped to (c) fields.\n// 4. Any body sent with an HTTP request can contain only (b) fields.\n//\n// The syntax of the path template is as follows:\n//\n//     Template = \"/\" Segments [ Verb ] ;\n//     Segments = Segment { \"/\" Segment } ;\n//     Segment  = \"*\" | \"**\" | LITERAL | Variable ;\n//     Variable = \"{\" FieldPath [ \"=\" Segments ] \"}\" ;\n//     FieldPath = IDENT { \".\" IDENT } ;\n//     Verb     = \":\" LITERAL ;\n//\n// The syntax `*` matches a single path segment. It follows the semantics of\n// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String\n// Expansion.\n//\n// The syntax `**` matches zero or more path segments. It follows the semantics\n// of [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.3 Reserved\n// Expansion. NOTE: it must be the last segment in the path except the Verb.\n//\n// The syntax `LITERAL` matches literal text in the URL path.\n//\n// The syntax `Variable` matches the entire path as specified by its template;\n// this nested template must not contain further variables. If a variable\n// matches a single path segment, its template may be omitted, e.g. `{var}`\n// is equivalent to `{var=*}`.\n//\n// NOTE: the field paths in variables and in the `body` must not refer to\n// repeated fields or map fields.\n//\n// Use CustomHttpPattern to specify any HTTP method that is not included in the\n// `pattern` field, such as HEAD, or \"*\" to leave the HTTP method unspecified for\n// a given URL path rule. The wild-card rule is useful for services that provide\n// content to Web (HTML) clients.\nmessage HttpRule {\n  // Selects methods to which this rule applies.\n  //\n  // Refer to [selector][google.api.DocumentationRule.selector] for syntax details.\n  string selector = 1;\n\n  // Determines the URL pattern is matched by this rules. This pattern can be\n  // used with any of the {get|put|post|delete|patch} methods. A custom method\n  // can be defined using the 'custom' field.\n  oneof pattern {\n    // Used for listing and getting information about resources.\n    string get = 2;\n\n    // Used for updating a resource.\n    string put = 3;\n\n    // Used for creating a resource.\n    string post = 4;\n\n    // Used for deleting a resource.\n    string delete = 5;\n\n    // Used for updating a resource.\n    string patch = 6;\n\n    // Custom pattern is used for defining custom verbs.\n    CustomHttpPattern custom = 8;\n  }\n\n  // The name of the request field whose value is mapped to the HTTP body, or\n  // `*` for mapping all fields not captured by the path pattern to the HTTP\n  // body. NOTE: the referred field must not be a repeated field and must be\n  // present at the top-level of request message type.\n  string body = 7;\n\n  // Additional HTTP bindings for the selector. Nested bindings must\n  // not contain an `additional_bindings` field themselves (that is,\n  // the nesting may only be one level deep).\n  repeated HttpRule additional_bindings = 11;\n}\n\n// A custom pattern is used for defining custom HTTP verb.\nmessage CustomHttpPattern {\n  // The name of this custom HTTP verb.\n  string kind = 1;\n\n  // The path matched by this custom verb.\n  string path = 2;\n}\n"
  },
  {
    "path": "proto/fst/Delete.proto",
    "content": "syntax=\"proto3\";\npackage eos.fst;\n\nmessage DeletionsFsProto {\n  string path = 1;\n  uint64 fsid  = 2;\n  repeated uint64 fids = 3;\n}\n\nmessage DeletionsProto {\n  repeated DeletionsFsProto fs = 1;\n}\n\n"
  },
  {
    "path": "proto/fst/FmdBase.proto",
    "content": "syntax = \"proto2\";\npackage eos.fst;\n\nmessage FmdBase {\n  optional fixed64 fid = 1; //< fileid\n  optional fixed64 cid = 2; //< container id (e.g. directory id)\n  optional fixed32 fsid = 3; //< filesystem id\n  optional fixed32 ctime = 4; //< creation time\n  optional fixed32 ctime_ns = 5; //< ns of creation time\n  optional fixed32 mtime = 6; //< modification time | deletion time\n  optional fixed32 mtime_ns = 7; //< ns of modification time\n  optional fixed32 atime = 8; //< access time\n  optional fixed32 atime_ns = 9; //< ns of access time\n  optional fixed32 checktime = 10; //< time of last checksum scan\n  optional fixed64 size = 11; //< size                 - 0xfffffffffff1ULL means it is still undefined\n  optional fixed64 disksize = 12; //< size on disk     - 0xfffffffffff1ULL means it is still undefined\n  optional fixed64 mgmsize = 13; //< size on the MGM   - 0xfffffffffff1ULL means it is still undefined\n  optional string checksum = 14; //< checksum in hex representation\n  optional string diskchecksum = 15; //< checksum in hex representation\n  optional string mgmchecksum = 16; //< checksum in hex representation\n  optional fixed32 lid = 17; //< layout id\n  optional fixed32 uid = 18; //< user  id\n  optional fixed32 gid = 19; //< roup id\n  optional sint64 filecxerror = 20; //< indicator for file checksum error\n  optional sint64 blockcxerror = 21 ; //< indicator for block checksum error\n  optional sint64 layouterror = 22; //< indicator for resync errors e.g. the mgm layout information is inconsistent e.g. only 1 of 2 replicas exist\n  optional string locations = 23; //< fsid list with locations e.g. 1,2,3,4,10\n  repeated sint64 stripeerror = 24; //< fsid of broken stripes\n  optional string stripechecksum = 25; //< checksum of the stripe (if RAIN file)\n  repeated uint32 altxs = 30; //< local view of altxs configured in the MGM at dir level\n  repeated uint32 mgmaltxs = 31; //< altxs computed and stored in the MGM\n  optional fixed32 altxssync = 32; //< last sync of altxs opts from MGM\n}\n"
  },
  {
    "path": "proto/namespace/ns_quarkdb/ChangelogEntry.proto",
    "content": "syntax = \"proto3\";\npackage eos.mgm;\n\n//------------------------------------------------------------------------------\n// Container metadata protocol buffer object\n//------------------------------------------------------------------------------\nmessage ConfigModification {\n  string key = 1;\n  string previous_value = 2;\n  string new_value = 3;\n}\n\nmessage ConfigChangelogEntry {\n  repeated ConfigModification modifications = 1;\n  int64 timestamp = 2;\n  string comment = 3;\n}\n"
  },
  {
    "path": "proto/namespace/ns_quarkdb/ContainerMd.proto",
    "content": "syntax = \"proto3\";\npackage eos.ns;\n\n//------------------------------------------------------------------------------\n// Container metadata protocol buffer object\n//------------------------------------------------------------------------------\nmessage ContainerMdProto {\n  uint64 id = 1;\n  uint64 parent_id = 2;\n  uint64 uid = 3;\n  uint64 gid = 4;\n  int64 tree_size = 6;\n  uint32 mode = 5;\n  uint32 flags = 7;\n  bytes name = 8;\n  bytes ctime = 9;  // change time\n  bytes mtime = 10; // modification time\n  bytes stime = 11; // sync time\n  map<string, bytes> xattrs = 12;\n  uint64 tree_containers = 13;\n  uint64 tree_files = 14;\n\n  uint64 cloneid = 256;     // transient\n  bytes clonefst = 257;    // transient\n}\n"
  },
  {
    "path": "proto/namespace/ns_quarkdb/FileMd.proto",
    "content": "syntax = \"proto3\";\npackage eos.ns;\n\n//------------------------------------------------------------------------------\n// File metadata protocol buffer object\n//------------------------------------------------------------------------------\nmessage FileMdProto {\n  uint64 id = 1;\n  uint64 cont_id = 2;\n  uint64 uid = 3;\n  uint64 gid = 4;\n  uint64 size = 5;\n  uint32 layout_id = 6;\n  uint32 flags = 7;\n  bytes name = 8;\n  bytes link_name = 9;\n  bytes ctime = 10; // change time\n  bytes mtime = 11; // modification time\n  bytes checksum = 12;\n  repeated uint32 locations = 13;\n  repeated uint32 unlink_locations = 14;\n  map<string, bytes> xattrs = 15;\n  bytes stime = 16; // server sync time\n  bytes atime = 17; // server access time\n  map<uint32, bytes> altchecksums = 18; // alternative checksums\n  uint64 cloneid = 256;     // transient\n  bytes clonefst = 257;     // transient\n}\n"
  },
  {
    "path": "repo/eos-el7-dev.repo",
    "content": "[eos-diopside-dev]\nname=EOS 5.0 Development Version\nbaseurl=https://storage-ci.web.cern.ch/storage-ci/eos/diopside/commit/el-7/$basearch/\ngpgcheck=0\n\n[eos-diopside-dep]\nname=EOS Diopside Dependencies\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-7/$basearch/\ngpgcheck=0\n"
  },
  {
    "path": "repo/eos-el7.repo",
    "content": "[eos-diopside]\nname=EOS 5.0 Version\nbaseurl=https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/el-7/$basearch/\ngpgcheck=0\n\n[eos-diopside-dep]\nname=EOS Diopside Dependencies\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-7/$basearch/\ngpgcheck=0\n"
  },
  {
    "path": "repo/eos-el8-dev.repo",
    "content": "[eos-diopside-dev]\nname=EOS 5.0 Development Version\nbaseurl=https://storage-ci.web.cern.ch/storage-ci/eos/diopside/commit/el-8/$basearch/\ngpgcheck=0\n\n[eos-diopside-dep]\nname=EOS Diopside Dependencies\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-8/$basearch/\ngpgcheck=0\n"
  },
  {
    "path": "repo/eos-el8.repo",
    "content": "[eos-diopside]\nname=EOS 5.0 Version\nbaseurl=https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/el-8/$basearch/\ngpgcheck=0\n\n[eos-diopside-dep]\nname=EOS Diopside Dependencies\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-8/$basearch/\ngpgcheck=0\n"
  },
  {
    "path": "repo/eos-el8s-dev.repo",
    "content": "[eos-diopside-dev]\nname=EOS 5.0 Development Version\nbaseurl=https://storage-ci.web.cern.ch/storage-ci/eos/diopside/commit/el-8s/$basearch/\ngpgcheck=0\n\n[eos-diopside-dep]\nname=EOS Diopside Dependencies\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-8s/$basearch/\ngpgcheck=0\n"
  },
  {
    "path": "repo/eos-el8s.repo",
    "content": "[eos-diopside]\nname=EOS 5.0 Version\nbaseurl=https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/el-8s/$basearch/\ngpgcheck=0\n\n[eos-diopside-dep]\nname=EOS Diopside Dependencies\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-8s/$basearch/\ngpgcheck=0\n"
  },
  {
    "path": "repo/eos-el9s-dev.repo",
    "content": "[eos-diopside-dev]\nname=EOS 5.0 Development Version\nbaseurl=https://storage-ci.web.cern.ch/storage-ci/eos/diopside/commit/el-9s/$basearch/\ngpgcheck=0\n\n[eos-diopside-dep]\nname=EOS Diopside Dependencies\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-9s/$basearch/\ngpgcheck=0\n"
  },
  {
    "path": "repo/eos-el9s.repo",
    "content": "[eos-diopside]\nname=EOS 5.0 Version\nbaseurl=https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/el-9s/$basearch/\ngpgcheck=0\n\n[eos-diopside-dep]\nname=EOS Diopside Dependencies\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-9s/$basearch/\ngpgcheck=0\n"
  },
  {
    "path": "test/CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ninclude_directories(\n  ${CMAKE_BINARY_DIR}\n  ${CMAKE_SOURCE_DIR}/common/\n  ${CMAKE_SOURCE_DIR}/namespace/ns_quarkdb/\n  ${CMAKE_SOURCE_DIR}/namespace/ns_quarkdb/qclient/include\n  ${CMAKE_BINARY_DIR}/namespace/ns_quarkdb)  # for the generated protobuf\n\nadd_subdirectory(benchmark)\nadd_subdirectory(microbenchmarks)\n\nadd_executable(xrdstress.exe XrdStress.cc)\nset_target_properties(xrdstress.exe PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(xrdcpabort XrdCpAbort.cc)\nset_target_properties(xrdcpabort PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(xrdcprandom XrdCpRandom.cc)\nset_target_properties(xrdcprandom PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(xrdcppgread XrdCpPgRead.cc)\nset_target_properties(xrdcppgread PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(xrdcpextend XrdCpExtend.cc)\nset_target_properties(xrdcpextend PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(xrdcpappend XrdCpAppend.cc)\nset_target_properties(xrdcpappend PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(xrdcpappendoverlap XrdCpAppendOverlap.cc)\nset_target_properties(xrdcpappendoverlap PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(xrdcpposixcache XrdCpPosixCache.cc)\nset_target_properties(xrdcpposixcache PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(xrdcpholes XrdCpHoles.cc)\nset_target_properties(xrdcpholes PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(xrdcpbackward XrdCpBackward.cc)\nset_target_properties(xrdcpbackward PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(xrdcpdownloadrandom XrdCpDownloadRandom.cc)\nset_target_properties(xrdcpdownloadrandom PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(xrdcpshrink XrdCpShrink.cc)\nset_target_properties(xrdcpshrink PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(xrdcptruncate XrdCpTruncate.cc)\nset_target_properties(xrdcptruncate PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(xrdcppartial XrdCpPartial.cc)\nset_target_properties(xrdcppartial PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(xrdcpupdate XrdCpUpdate.cc)\nset_target_properties(xrdcpupdate PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(xrdcpslowwriter XrdCpSlowWriter.cc)\nset_target_properties(xrdcpslowwriter PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(xrdcpnonstreaming XrdCpNonStreaming.cc)\nset_target_properties(xrdcpnonstreaming PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(eos-udp-dumper EosUdpDumper.cc)\nset_target_properties(eos-udp-dumper PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(eos-mmap EosMmap.cc)\nset_target_properties(eos-mmap PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(eoshashbench EosHashBenchmark.cc)\nset_target_properties(eoshashbench PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(eoslogbench EosLoggingBenchmark.cc)\nset_target_properties(eoslogbench PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(eos-io-tool eos_io_tool.cc)\nset_target_properties(eos-io-tool PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(eos-open-trunc-update EosOpenTruncUpdate.cc)\nset_target_properties(eos-open-trunc-update PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(eos-crypto-timing-test EosCryptoTimingTest.cc)\nset_target_properties(eos-crypto-timing-test PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(testhmacsha256\n  TestHmacSha256.cc\n  ${CMAKE_SOURCE_DIR}/common/SymKeys.hh\n  ${CMAKE_SOURCE_DIR}/common/SymKeys.cc)\nset_target_properties(testhmacsha256 PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(eos-checksum-benchmark\n  EosChecksumBenchmark.cc\n  ${CMAKE_SOURCE_DIR}/fst/checksum/Adler.cc\n  ${CMAKE_SOURCE_DIR}/fst/checksum/CheckSum.cc)\nset_target_properties(eos-checksum-benchmark PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(threadpooltest ThreadPoolTest.cc)\nset_target_properties(threadpooltest PROPERTIES POSITION_INDEPENDENT_CODE TRUE)\nadd_executable(eos-idmap-benchmark EosIdMapBenchmark.cc)\n\ntarget_link_libraries(xrdcpabort PRIVATE XROOTD::POSIX XROOTD::UTILS)\ntarget_link_libraries(xrdcprandom PRIVATE XROOTD::POSIX XROOTD::UTILS)\ntarget_link_libraries(xrdcppgread PRIVATE XROOTD::CL XROOTD::UTILS)\ntarget_link_libraries(xrdcpextend PRIVATE XROOTD::POSIX XROOTD::UTILS)\ntarget_link_libraries(xrdcpappend PRIVATE XROOTD::POSIX XROOTD::UTILS)\ntarget_link_libraries(xrdcpappendoverlap PRIVATE XROOTD::POSIX XROOTD::UTILS XROOTD::CL)\ntarget_link_libraries(xrdcpholes PRIVATE XROOTD::POSIX XROOTD::UTILS)\ntarget_link_libraries(xrdcpbackward PRIVATE XROOTD::POSIX XROOTD::UTILS)\ntarget_link_libraries(xrdcpdownloadrandom PRIVATE XROOTD::POSIX XROOTD::UTILS)\ntarget_link_libraries(xrdcpshrink PRIVATE XROOTD::POSIX XROOTD::UTILS)\ntarget_link_libraries(xrdcptruncate PRIVATE XROOTD::POSIX XROOTD::UTILS)\ntarget_link_libraries(xrdcpposixcache PRIVATE XROOTD::POSIX XROOTD::UTILS)\ntarget_link_libraries(xrdcppartial PRIVATE XROOTD::POSIX XROOTD::UTILS)\ntarget_link_libraries(xrdcpupdate PRIVATE XROOTD::POSIX XROOTD::UTILS)\ntarget_link_libraries(xrdcpslowwriter PRIVATE XROOTD::CL)\ntarget_link_libraries(xrdcpnonstreaming PRIVATE XROOTD::CL)\ntarget_link_libraries(eoshashbench PRIVATE EosCommon)\ntarget_link_libraries(eoslogbench PRIVATE EosCommon)\ntarget_link_libraries(eos-crypto-timing-test PRIVATE EosCommon)\ntarget_link_libraries(testhmacsha256 PRIVATE EosCommon)\ntarget_link_libraries(eos-open-trunc-update PRIVATE XROOTD::CL XROOTD::POSIX XROOTD::UTILS)\ntarget_link_libraries(eos-io-tool PRIVATE EosFstIo XROOTD::SERVER)\ntarget_link_libraries(xrdstress.exe PRIVATE\n  UUID::UUID XROOTD::CL XROOTD::UTILS XROOTD::POSIX\n  ${CMAKE_THREAD_LIBS_INIT})\ntarget_link_libraries(eos-checksum-benchmark PRIVATE EosFstIo XROOTD::SERVER XROOTD::POSIX)\ntarget_link_libraries(eos-idmap-benchmark PRIVATE EosCommon)\ntarget_compile_definitions(xrdstress.exe PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_definitions(xrdcpabort PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_definitions(xrdcprandom PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_definitions(xrdcppgread PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_definitions(xrdcpextend PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_definitions(xrdcpappend PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_definitions(xrdcpappendoverlap PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_definitions(xrdcpholes PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_definitions(xrdcpbackward PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_definitions(xrdcpdownloadrandom PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_definitions(xrdcpshrink PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_definitions(xrdcptruncate PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_definitions(xrdcppartial PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_definitions(xrdcpupdate PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_definitions(xrdcpposixcache PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_definitions(eoshashbench PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_definitions(eoslogbench PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_definitions(eos-checksum-benchmark PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_compile_options(eos-checksum-benchmark PRIVATE ${CPU_ARCH_FLAGS})\ntarget_compile_definitions(eos-open-trunc-update PUBLIC -D_FILE_OFFSET_BITS=64)\ntarget_link_libraries(threadpooltest PRIVATE EosCommon)\n\n#-------------------------------------------------------------------------------\n# MQ test related executables\n#-------------------------------------------------------------------------------\nif (NOT CLIENT)\n  add_executable(eos-shared-hash-test mq/SharedHashLoadTest.cc)\n  target_link_libraries(eos-shared-hash-test PRIVATE EosCommonServer-Static EosCommon-Static qclient)\n\n  install(TARGETS eos-shared-hash-test\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR})\nendif()\n\ninstall(TARGETS xrdstress.exe xrdcpabort xrdcprandom xrdcppgread\n  xrdcpextend xrdcpshrink xrdcpappend xrdcpappendoverlap xrdcptruncate\n  xrdcpholes xrdcpbackward xrdcpdownloadrandom xrdcppartial xrdcpupdate\n  xrdcpposixcache xrdcpslowwriter xrdcpnonstreaming eos-checksum-benchmark\n  eos-udp-dumper eos-mmap eos-io-tool\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR})\n\ninstall(PROGRAMS xrdstress eos-instance-test eos-instance-test-ci eos-altxs-test\n  fuse/eos-fuse-test eos-rain-test eoscp-rain-test eos-io-test eos-accounting-test\n  eos-file-cont-detached-test eos-lru-test eos-oc-test eos-drain-test eos-groupdrain-test\n  eos-http-upload-test eos-https-functional-test eos-token-test eos-traffic-shaping-test\n  eos-fst-close-test eos-rename-test eos-grpc-test eos-fsck-test\n  eos-squash-test eos-backup eos-backup-browser eos-test-utils\n  eos-convert-test eos-balance-test eos-timestamp-test\n  eos-squash-test eos-defaultcc-test eos-quota-test eos-macaroon-init\n  eos-synctime-test eos-rclone-test eos-bash test-eos-iam-mapfile.py\n  fusex/eos-test-credential-bindings fusex/eos-fusex-functional-test\n  eos-acl-concurrent eos-recycle-test\n  DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR}\n  PERMISSIONS OWNER_READ OWNER_EXECUTE\n  GROUP_READ GROUP_EXECUTE\n  WORLD_READ WORLD_EXECUTE)\n"
  },
  {
    "path": "test/EosChecksumBenchmark.cc",
    "content": "// ----------------------------------------------------------------------\n// File: EosChecksumBenchmark.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*-----------------------------------------------------------------------------*/\n#include <sys/types.h>\n#include <sys/wait.h>\n/*-----------------------------------------------------------------------------*/\n#include \"common/LayoutId.hh\"\n#include \"common/Logging.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/Timing.hh\"\n#include \"common/utils/RandUtils.hh\"\n#include \"fst/checksum/ChecksumPlugins.hh\"\n/*-----------------------------------------------------------------------------*/\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdOuc/XrdOucString.hh>\n/*-----------------------------------------------------------------------------*/\n\nXrdPosixXrootd posixXrootd;\n\n// 1GB mem buffer\n#define MEMORYBUFFERSIZE 256ll*1024ll*1024ll\n\nint main(int argc, char* argv[])\n{\n  eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n  g_logging.SetUnit(\"eoschecksumbenchmark@localhost\");\n  g_logging.gShortFormat = true;\n  g_logging.SetLogPriority(LOG_DEBUG);\n  std::vector<std::string> checksumnames;\n  std::vector<unsigned long long> checksumids;\n  checksumnames.push_back(\"adler32\");\n  checksumnames.push_back(\"crc32\");\n  checksumnames.push_back(\"md5\");\n  checksumnames.push_back(\"crc32c\");\n  checksumnames.push_back(\"sha1\");\n  checksumnames.push_back(\"xxhash64\");\n  checksumnames.push_back(\"blake3\");\n  checksumnames.push_back(\"hwh64\");\n  checksumnames.push_back(\"crc64\");\n  checksumids.push_back(eos::common::LayoutId::kAdler);\n  checksumids.push_back(eos::common::LayoutId::kCRC32);\n  checksumids.push_back(eos::common::LayoutId::kMD5);\n  checksumids.push_back(eos::common::LayoutId::kCRC32C);\n  checksumids.push_back(eos::common::LayoutId::kSHA1);\n  checksumids.push_back(eos::common::LayoutId::kXXHASH64);\n  checksumids.push_back(eos::common::LayoutId::kBLAKE3);\n  checksumids.push_back(eos::common::LayoutId::kHWH64);\n  checksumids.push_back(eos::common::LayoutId::kCRC64);\n  size_t nforks = 1;\n\n  if (argc == 2) {\n    nforks = atoi(argv[1]);\n\n    if (nforks <= 0) {\n      fprintf(stdout, \"info: forcing the nforks=1\");\n      nforks = 1;\n    }\n  }\n\n  for (size_t foker = 0; foker < nforks; foker ++) {\n    if (!fork()) {\n      srandom(foker);\n      XrdOucString size;\n      eos::common::StringConversion::GetReadableSizeString(size, MEMORYBUFFERSIZE,\n          \"B\");\n      // allocate a block\n      eos_static_info(\"allocating %s\", size.c_str());\n      char* buffer   = (char*) malloc(MEMORYBUFFERSIZE);\n      char* xsbuffer = (char*) malloc(MEMORYBUFFERSIZE / 100);\n\n      if ((!buffer) || (!xsbuffer)) {\n        fprintf(stderr, \"error: failed to allocate reference buffers!\\n\");\n        exit(-1);\n      }\n\n      eos_static_info(\"write randomized contents into %s\", size.c_str());\n\n      for (off_t i = 0; i < MEMORYBUFFERSIZE; i++) {\n        buffer[i] = eos::common::getRandom(0, 255);\n      }\n\n      eos_static_info(\"write zeros into xs buffers\");\n\n      for (off_t i = 0; i < MEMORYBUFFERSIZE / 100; i++) {\n        xsbuffer[i] = 0;\n      }\n\n      eos_static_info(\"allocated %s\", size.c_str());\n      std::vector<unsigned long long> blocksize;\n      blocksize.push_back(4096);\n      blocksize.push_back(128 * 1024);\n      blocksize.push_back(1024 * 1024);\n      blocksize.push_back(4 * 1024 * 1024);\n      blocksize.push_back(128 * 1024 * 1024);\n\n      for (size_t bs = 0; bs < blocksize.size(); bs++) {\n        for (size_t i = 0; i < checksumnames.size(); i++) {\n          eos_static_info(\"benchmarking checksum algorithm %s [%d]\",\n                          checksumnames[i].c_str(), checksumids[i]);\n          std::unique_ptr<eos::fst::CheckSum> checksum =\n            eos::fst::ChecksumPlugins::GetChecksumObject(checksumids[i]);\n\n          if (!checksum) {\n            eos_static_err(\"failed to get checksum algorithm %s\", checksumnames[i].c_str());\n          } else {\n            eos::common::Timing tm(\"Checksumming\");\n            COMMONTIMING(\"START\", &tm);\n            char*  ptr = buffer;\n            off_t offset = 0;\n\n            for (size_t j = 0; j < MEMORYBUFFERSIZE / blocksize[bs]; j++) {\n              checksum->Add(ptr, blocksize[bs], offset);\n              offset += blocksize[bs];\n              ptr += blocksize[bs];\n            }\n\n            checksum->Finalize();\n            COMMONTIMING(\"STOP\", &tm);\n            XrdOucString sizestring;\n            eos::common::StringConversion::GetReadableSizeString(sizestring, blocksize[bs],\n                \"B\");\n            eos_static_info(\"checksum( %-10s ) = %s realtime=%.02f [ms] blocksize=%s rate=%.02f\",\n                            checksumnames[i].c_str(), checksum->GetHexChecksum(), tm.RealTime(),\n                            sizestring.c_str(), MEMORYBUFFERSIZE / tm.RealTime() / 1000.0);\n          }\n        }\n      }\n\n      exit(0);\n    }\n  }\n\n  for (size_t foker = 0; foker < nforks; foker ++) {\n    wait(0);\n  }\n}\n"
  },
  {
    "path": "test/EosCryptoTimingTest.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file EosCryptoTimingTest.cc\n//! @author: Elvin Sindrilaru - CERN\n//----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/SymKeys.hh\"\n#include \"common/Timing.hh\"\n#include <stdio.h>\n\nint main(int argc, char* argv[])\n{\n  using eos::common::SymKey;\n  eos::common::Timing tm(\"Symmetric Enc/Dec-Timing\");\n  COMMONTIMING(\"START\", &tm);\n  char* secretkey = (char*) \"12345678901234567890\";\n  XrdOucString textplain = \"this is a very secret message\";\n  XrdOucString textencrypted = \"\";\n  XrdOucString textdecrypted = \"\";\n\n  for (int i = 0; i < 1000; i++) {\n    if (!SymKey::SymmetricStringEncrypt(textplain, textencrypted,\n                                        secretkey)) {\n      fprintf(stderr, \"error: failed symmetric string encrypt\\n\");\n      exit(-1);\n    }\n\n    if (!SymKey::SymmetricStringDecrypt(textencrypted, textdecrypted,\n                                        secretkey)) {\n      fprintf(stderr, \"error: failed symmetric key decrypt\\n\");\n      exit(-1);\n    }\n\n    int inlen = strlen(secretkey);\n    std::string fout;\n\n    if (!SymKey::Base64Encode(secretkey, inlen, fout)) {\n      fprintf(stderr, \"error: cannot base64 encode\\n\");\n      exit(-1);\n    }\n\n    fprintf(stdout, \"%s\\n\", fout.c_str());\n    char* binout = 0;\n    ssize_t outlen;\n\n    if (!SymKey::Base64Decode((char*)fout.c_str(), binout, outlen)) {\n      fprintf(stderr, \"error: cannot base64 decode\\n\");\n      exit(-1);\n    }\n\n    binout[20] = 0;\n    fprintf(stdout, \"outlen is %zd - %s\\n\", outlen, binout);\n    // printf(\"a) |%s|\\nb) |%s|\\nc) |%s|\\n\\n\", textplain.c_str(),\n    // textencrypted.c_str(), textdecrypted.c_str());\n  }\n\n  COMMONTIMING(\"STOP\", &tm);\n  tm.Print();\n}\n"
  },
  {
    "path": "test/EosHashBenchmark.cc",
    "content": "//------------------------------------------------------------------------------\n// Copyright (c) 2012 by European Organization for Nuclear Research (CERN)\n// Author: Lukasz Janyst <ljanyst@cern.ch>\n//------------------------------------------------------------------------------\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with this program.  If not, see <http://www.gnu.org/licenses/>.\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n#include <iostream>\n#include \"common/Timing.hh\"\n#include \"common/LinuxMemConsumption.hh\"\n#include \"common/LinuxStat.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/RWMutex.hh\"\n#include \"common/ulib/hash_align.h\"\n\n//------------------------------------------------------------------------------\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\n#include <XrdOuc/XrdOucString.hh>\n#include <XrdSys/XrdSysPthread.hh>\n#include <google/dense_hash_map>\n#include <string>\n#include <map>\n#include <unordered_map>\n#include <random>\n\neos::common::RWMutex nslock;\nXrdSysMutex nsmutex;\n\nusing KeyType = long long;\nusing ValueType = long long;\n\nstd::map<KeyType, ValueType> stdmap;\ngoogle::dense_hash_map<KeyType, ValueType> googlemap;\nulib::align_hash_map<KeyType, ValueType> ulibmap;\nstd::unordered_map<KeyType, ValueType> stdumap;\n\nstd::map<std::string, double, std::less<>>\n                                        results; // Allow transparent compare so as to use string_view/strings\nstd::map<std::string, long long, std::less<>> results_mem;\n\nbool skip_ulib_bench = false;\n\nenum class MapType {\n  std_map = 0,\n  google_dense = 1,\n  ulib = 2,\n  std_umap = 3,\n  UNKNOWN\n};\n\nconstexpr uint8_t TOTAL_MAP_COUNT = static_cast<uint8_t>(MapType::UNKNOWN);\n\n// Here we have a factory that'll return the given global map given a type,\n// since we can't template based on enum types, do an index and use a fn to the\n// index conversion. We'll have a compile time failure if you supply a unknown map_index\ntemplate <std::size_t MapIdx>\nconstexpr auto* getMap()\n{\n  static_assert(MapIdx >= 0 && MapIdx <= TOTAL_MAP_COUNT,\n                \"Unknown Map Type!!!\");\n\n  if constexpr(MapIdx == static_cast<size_t>(MapType::std_map)) {\n    return &stdmap;\n  } else if constexpr(MapIdx == static_cast<size_t>(MapType::google_dense)) {\n    return &googlemap;\n  } else if constexpr(MapIdx == static_cast<size_t>(MapType::ulib)) {\n    return &ulibmap;\n  } else if constexpr(MapIdx == static_cast<size_t>(MapType::std_umap)) {\n    return &stdumap;\n  }\n}\n// Get the MapType given an id!\ntemplate <typename T>\nconstexpr size_t MapId(T t)\n{\n  return static_cast<size_t>(t);\n}\n\n// Apply processing function F on the given set of maps! Note that this function\n// will run from the given MapIndex downwards until 0 applying MapType and a\n// pointer to the hashmap as the first and second argument.\ntemplate < size_t idx = TOTAL_MAP_COUNT - 1, class F, class... Args >\nvoid ProcessOp(F && f, Args && ... args)\n{\n  // in an ideal world with constexpr function with a loop with fixed compile\n  // time arguments, the compiler should be able to unroll the loop, here we do\n  // it manually since getMap needs to know the index at compile time\n  // accomplish, so we should be able to give it a tuple/list of map_types and\n  // we should have this done.\n  f(static_cast<MapType>(idx), getMap<idx>(), std::forward<Args>(args)...);\n\n  if constexpr(idx > 0) {\n    ProcessOp < idx - 1 > (f, args...);\n  }\n}\n\nstd::string MapName(MapType t)\n{\n  std::string map_name;\n\n  switch (t) {\n  case MapType::std_map:\n    map_name = \"STL Hash\";\n    break;\n\n  case MapType::google_dense:\n    map_name = \"Google Dense Hash\";\n    break;\n\n  case MapType::ulib:\n    map_name = \"ULib Hash\";\n    break;\n\n  case MapType::std_umap:\n    map_name = \"STL Unordered Hash\";\n    break;\n\n  case MapType::UNKNOWN:\n  default:\n    map_name = \"UNKNOWN\";\n  }\n\n  return map_name;\n}\n\n//------------------------------------------------------------------------------\n// Print current status\n//------------------------------------------------------------------------------\nvoid PrintStatus(eos::common::LinuxStat::linux_stat_t& st1,\n                 eos::common::LinuxStat::linux_stat_t& st2,\n                 eos::common::LinuxMemConsumption::linux_mem_t& mem1,\n                 eos::common::LinuxMemConsumption::linux_mem_t& mem2, double& rate)\n{\n  XrdOucString stdOut;\n  XrdOucString sizestring;\n  stdOut += \"# ------------------------------------------------------------------------------------\\n\";\n  stdOut += \"# ------------------------------------------------------------------------------------\\n\";\n  stdOut += \"ALL      memory virtual                   \";\n  stdOut += eos::common::StringConversion::GetReadableSizeString(sizestring,\n            (unsigned long long)mem2.vmsize, \"B\");\n  stdOut += \"\\n\";\n  stdOut += \"ALL      memory resident                  \";\n  stdOut += eos::common::StringConversion::GetReadableSizeString(sizestring,\n            (unsigned long long)mem2.resident, \"B\");\n  stdOut += \"\\n\";\n  stdOut += \"ALL      memory share                     \";\n  stdOut += eos::common::StringConversion::GetReadableSizeString(sizestring,\n            (unsigned long long)mem2.share, \"B\");\n  stdOut += \"\\n\";\n  stdOut += \"ALL      memory growths                   \";\n  stdOut += eos::common::StringConversion::GetReadableSizeString(sizestring,\n            (unsigned long long)(st2.vsize - st1.vsize), \"B\");\n  stdOut += \"\\n\";\n  stdOut += \"# ------------------------------------------------------------------------------------\\n\";\n  stdOut += \"ALL      rate                             \";\n  char srate[256];\n  snprintf(srate, sizeof(srate) - 1, \"%.02f\", rate);\n  stdOut += srate;\n  stdOut += \"\\n\";\n  stdOut += \"# ------------------------------------------------------------------------------------\\n\";\n  fprintf(stderr, \"%s\", stdOut.c_str());\n}\n\n\nclass RThread\n{\npublic:\n  RThread(): i(0), n_files(0), type(MapType::UNKNOWN), threads(0),\n    dolock(false) {}\n\n  RThread(size_t a, size_t b, MapType t, size_t nt, bool lock = false):\n    i(a), n_files(b), type(t), threads(nt), dolock(lock) {}\n\n  ~RThread() {};\n\n  size_t i;\n  size_t n_files;\n  MapType type;\n  size_t threads;\n  bool dolock;\n};\n\n\n//----------------------------------------------------------------------------\n// start hash consumer thread\n//----------------------------------------------------------------------------\n\nstatic void* RunReader(void* tconf)\n{\n  RThread* r = (RThread*) tconf;\n  size_t i = r->i;\n  size_t n_files = r->n_files;\n  bool dolock = r->dolock;\n\n  for (size_t n = 1 + i; n <= n_files; n += r->threads) {\n    // if (dolock)nsmutex.Lock();\n    if (dolock) {\n      nslock.LockRead();\n    }\n\n    if (r->type == MapType::std_map) {\n      long long v = stdmap[n];\n\n      if (v) {\n        v = 1;\n      }\n    }\n\n    if (r->type == MapType::google_dense) {\n      long long v = googlemap[n];\n\n      if (v) {\n        v = 1;\n      }\n    }\n\n    if (r->type == MapType::ulib) {\n      long long v = ulibmap[n];\n\n      if (v) {\n        v = 1;\n      }\n    }\n\n    if (r->type == MapType::std_umap) {\n      long long v = stdumap[n];\n\n      if (v) {\n        v = 1;\n      }\n    }\n\n    // if (dolock)nsmutex.UnLock();\n    if (dolock) {\n      nslock.UnLockRead();\n    }\n  }\n\n  return 0;\n}\n\n\n\ntemplate <typename KeyType>\nstd::vector<KeyType> generate_keys(size_t sz, KeyType init = {}, bool randomize\n                                   = false)\n{\n  std::vector<KeyType> keys(sz);\n  std::iota(keys.begin(), keys.end(), init);\n\n  if (randomize) {\n    std::shuffle(keys.begin(), keys.end(),\n                 std::mt19937{std::random_device{}()});\n  }\n\n  return keys;\n}\n\ntemplate <typename HT, typename C>\nvoid InitSingleThreadWrite(MapType t, HT* ht, int& counter, const C& keys)\n{\n  if (t == MapType::ulib && skip_ulib_bench) {\n    return;\n  }\n\n  std::cerr <<\n            \"# **********************************************************************************\"\n            << std::endl;\n  std::cerr << \"[i] Initialize \" << MapName(t) << \" ...\" << std::endl;\n  std::cerr <<\n            \"# **********************************************************************************\"\n            << std::endl;\n  eos::common::LinuxStat::linux_stat_t st[10];;\n  eos::common::LinuxMemConsumption::linux_mem_t mem[10];\n  eos::common::LinuxStat::GetStat(st[0]);\n  eos::common::LinuxMemConsumption::GetMemoryFootprint(mem[0]);\n  eos::common::Timing tm(\"directories\");\n  COMMONTIMING(\"hash-start\", &tm);\n  size_t i = 0;\n\n  for (const auto& key : keys) {\n    if (!(i++ % 1000000)) {\n      XrdOucString l = \"level-\";\n      l += (int)i;\n      COMMONTIMING(l.c_str(), &tm);\n    }\n\n    // fill the hash\n    // Cross check if insert is defined, this is because ulibmap doesn't follow the same syntax!\n    if constexpr(!std::is_same_v<HT, decltype(ulibmap)>) {\n      // normal maps use a tuple as input\n      ht->insert({key, i});\n    } else {\n      // ulibmap needs a unpacked pair! also gets useless after > 500k keys\n      ht->insert(key, i);\n    }\n  }\n\n  eos::common::LinuxStat::GetStat(st[1]);\n  eos::common::LinuxMemConsumption::GetMemoryFootprint(mem[1]);\n  COMMONTIMING(\"dir-stop\", &tm);\n  tm.Print();\n  double rate = (keys.size()) / tm.RealTime() * 1000.0;\n  std::stringstream ss;\n  ss << std::setw(3) << std::setw(3) << std::setfill('0') <<\n     (counter + 100) << \" Fill \" << MapName(t);\n  std::string title_key = ss.str();\n  results.emplace(title_key, rate);\n  results_mem.emplace(title_key, st[1].vsize - st[0].vsize);\n  PrintStatus(st[0], st[1], mem[0], mem[1], rate);\n  counter++;\n}\n\ntemplate <typename HT>\nvoid DoReadTests(MapType t, HT* ht, int& counter, size_t n_i, size_t n_files,\n                 bool lock = false)\n{\n  if (t == MapType::ulib && skip_ulib_bench) {\n    return;\n  }\n\n  eos::common::LinuxStat::linux_stat_t st[10];;\n  eos::common::LinuxMemConsumption::linux_mem_t mem[10];\n  std::cerr <<\n            \"# **********************************************************************************\"\n            << std::endl;\n  std::cerr << \"Parallel reader benchmark without locking \";\n  std::cerr << MapName(t) << std::endl;\n  std::cerr <<\n            \"# **********************************************************************************\"\n            << std::endl;\n  eos::common::LinuxStat::GetStat(st[0]);\n  eos::common::LinuxMemConsumption::GetMemoryFootprint(mem[0]);\n  eos::common::Timing tm(\"reading\");\n  COMMONTIMING(\"read-start\", &tm);\n  pthread_t tid[1024];\n\n  // fire threads\n  for (size_t i = 0; i < n_i; i++) {\n#if DEBUG\n    fprintf(stderr, \"# Level %02u\\n\", (unsigned int)i);\n#endif\n    RThread r(i, n_files, t, n_i, lock);\n    XrdSysThread::Run(&tid[i], RunReader, static_cast<void*>(&r), XRDSYSTHREAD_HOLD,\n                      \"Reader Thread\");\n  }\n\n  // join them\n  for (size_t i = 0; i < n_i; i++) {\n    XrdSysThread::Join(tid[i], NULL);\n  }\n\n  eos::common::LinuxStat::GetStat(st[1]);\n  eos::common::LinuxMemConsumption::GetMemoryFootprint(mem[1]);\n  COMMONTIMING(\"read-stop\", &tm);\n  tm.Print();\n  double rate = (n_files) / tm.RealTime() * 1000.0;\n  std::stringstream ss;\n  std::string lock_str = lock ? \"lock \" : \"no lock \";\n  int r_id = lock ? counter + 200 : counter + 300;\n  ss << std::setw(3) << std::setw(3) << std::setfill('0') << r_id << \" Read \" <<\n     lock_str << MapName(t);\n  std::string title_key = ss.str();\n  results.emplace(title_key, rate);\n  PrintStatus(st[0], st[1], mem[0], mem[1], rate);\n  counter++;\n}\n\ntemplate <typename HT>\nvoid ClearMap(MapType t, HT* ht, int& counter)\n{\n  if (t == MapType::ulib && skip_ulib_bench) {\n    return;\n  }\n\n  std::cerr <<\n            \"# **********************************************************************************\"\n            << std::endl;\n  std::cerr << \"[i] Clear \" << MapName(t) << \" ...\" << std::endl;\n  std::cerr <<\n            \"# **********************************************************************************\"\n            << std::endl;\n  eos::common::Timing tm(\"clearing\");\n  COMMONTIMING(\"clear-start\", &tm);\n  ht->clear();\n  COMMONTIMING(\"clear-stop\", &tm);\n  tm.Print();\n  std::stringstream ss;\n  ss << std::setw(3) << std::setw(3) << std::setfill('0') <<\n     (counter + 400) << \" Clear \" << MapName(t);\n  results.emplace(ss.str(), tm.RealTime());\n  counter++;\n}\nint main(int argc, char** argv)\n{\n  googlemap.set_deleted_key(-1);\n  googlemap.set_empty_key(0);\n\n  //----------------------------------------------------------------------------\n  // Check up the commandline params\n  //----------------------------------------------------------------------------\n  if (argc < 3) {\n    std::cerr << \"Usage:\"                                << std::endl;\n    std::cerr << \"  eos-hash-benchmark <entries> <threads> [rs]\" << std::endl;\n    std::cerr << \"      option r controls randomizing insertion order\" << std::endl;\n    std::cerr <<\n              \"      option s forces all the operations on single map before trying next\" <<\n              std::endl;\n    return 1;\n  };\n\n  bool randomize = false;\n\n  bool single_test_mode = false;\n\n  size_t n_files = atoi(argv[1]);\n\n  size_t n_i = atoi(argv[2]);\n\n  if (argc == 4) {\n    std::string_view options = argv[3];\n    randomize = options.find('r') != options.npos;\n    single_test_mode = options.find('s') != options.npos;\n  }\n\n  if (n_files <= 0) {\n    std::cerr << \"Error: number of entries has to be > 0\" << std::endl;\n    return 1;\n  }\n\n  int counter = 0;\n  auto keys = generate_keys(n_files, 1, randomize);\n  // ulib insertions go to  < 0.02 MHz after ~500k random inserts\n  skip_ulib_bench = n_files > 8000000 && randomize;\n  // Basically process map will process one map type for the given operation So\n  // if you want to do entire set of ops on one map type just change the lambda\n  // to do this!\n  auto write_f = [&counter, &keys](auto && map_type, auto && map) {\n    InitSingleThreadWrite(map_type, map, counter, keys);\n  };\n  auto read_no_lock_f = [&counter, &n_i, &n_files](auto && map_type, auto &&\n  map) {\n    DoReadTests(map_type, map, counter, n_i, n_files);\n  };\n  auto read_lock_f = [&counter, &n_i, &n_files](auto && map_type, auto && map) {\n    DoReadTests(map_type, map, counter, n_i, n_files, true);\n  };\n  auto single_test_f = [&counter, &keys, &n_i, &n_files](auto && map_type, auto &&\n  map) {\n    InitSingleThreadWrite(map_type, map, counter, keys);\n    DoReadTests(map_type, map, counter, n_i, n_files);\n    DoReadTests(map_type, map, counter, n_i, n_files, true);\n    ClearMap(map_type, map, counter);\n  };\n\n  if (!single_test_mode) {\n    ProcessOp(write_f);\n    //----------------------------------------------------------------------------\n    // Run a parallel consumer thread benchmark without locking\n    //----------------------------------------------------------------------------\n    ProcessOp(read_no_lock_f);\n    //----------------------------------------------------------------------------\n    // Run a parallel consumer thread benchmark with namespace locking\n    //----------------------------------------------------------------------------\n    ProcessOp(read_lock_f);\n  } else {\n    ProcessOp(single_test_f);\n  }\n\n  fprintf(stdout,\n          \"=====================================================================\\n\");\n  fprintf(stdout,\n          \"--------------------- SUMMARY ---------------------------------------\\n\");\n  fprintf(stdout,\n          \"=====================================================================\\n\");\n  int i = 0;\n  int map_count = TOTAL_MAP_COUNT - (int)skip_ulib_bench;\n\n  for (auto it = results.begin(); it != results.end(); it++) {\n    if (!(i % map_count)) {\n      fprintf(stdout, \"----------------------------------------------------\\n\");\n    }\n\n    if (i < map_count) {\n      fprintf(stdout, \"%s rate: %.02f MHz mem-overhead: %.02f %%\\n\",\n              it->first.c_str(), it->second / 1000000.0,\n              1.0 * results_mem[it->first] / (n_files * 16));\n    } else if (it->first.find(\"Clear\") != std::string::npos) {\n      fprintf(stdout, \"%s time: %.02f ms\\n\", it->first.c_str(),\n              it->second);\n    } else {\n      fprintf(stdout, \"%s rate: %.02f MHz\\n\", it->first.c_str(),\n              it->second / 1000000.0);\n    }\n\n    i++;\n  }\n\n  fprintf(stdout, \"====================================================\\n\");\n}\n"
  },
  {
    "path": "test/EosIdMapBenchmark.cc",
    "content": "//------------------------------------------------------------------------------\n// File: EosIdMapBenchmark.cc\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Mapping.hh\"\n#include \"common/Logging.hh\"\n#include <XrdSec/XrdSecEntity.hh>\n#include <chrono>\n\nvoid IdMapClient(int n, int cache_factor=1){\n  auto vid = eos::common::VirtualIdentity::Nobody();\n  XrdSecEntity client(\"sss\");\n  char name[24] = \"foobar\";\n  client.tident = \"root\";\n  client.name = name;\n  std::stringstream base_ss;\n  base_ss << \"foo.bar:baz@bar\" << std::this_thread::get_id();\n  std::string tident_base = base_ss.str();\n  for (int j=0; j < cache_factor; ++j) {\n    for (int i=0; i < n/cache_factor; ++i) {\n      std::string client_name = \"testuser\" + std::to_string(i);\n      client.name = client_name.data();\n      std::string tident = tident_base + std::to_string(i);\n      eos::common::Mapping::IdMap(&client, nullptr, tident.c_str(), vid);\n    }\n  }\n}\n\nint main(int argc, const char* argv[]) {\n  if (argc < 2) {\n    std::cerr << \"Usage: \" << argv[0] << \" <num-entries> [num-threads] [cache_factor]\" << std::endl;\n    return 1;\n  }\n  std::chrono::steady_clock::time_point init = std::chrono::steady_clock::now();\n\n  eos::common::Mapping::Init();\n  eos::common::Mapping::gVirtualUidMap[\"sss:\\\"<pwd>\\\":uid\"]=0;\n  eos::common::Mapping::gVirtualGidMap[\"sss:\\\"<pwd>\\\":gid\"]=0;\n  int n_clients = 1;\n  int num_threads = 50;\n  int cache_factor = 1;\n\n  switch (argc) {\n  case 4:\n    cache_factor = atoi(argv[3]);\n    // fallthrough\n  case 3:\n    num_threads = atoi(argv[2]);\n    // fallthrough\n  case 2:\n    n_clients = atoi(argv[1]);\n    // fallthrough\n  }\n\n  /*\n  auto& g_logger = eos::common::Logging::GetInstance();\n  g_logger.SetLogPriority(LOG_INFO);\n  g_logger.SetUnit(\"EOSFileMD\");\n*/\n  std::vector<std::thread> threads;\n  threads.reserve(num_threads);\n  std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();\n\n  for (int i = 0; i < num_threads; ++i) {\n    threads.emplace_back(IdMapClient, n_clients, cache_factor);\n  }\n\n  for (auto& t : threads) {\n    t.join();\n  }\n\n  std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();\n  auto init_time = std::chrono::duration_cast<std::chrono::milliseconds>(begin - init).count();\n  auto ms_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();\n  std::cout << \"Init Time = \" << init_time << \" ms\"\n            << \" Time difference = \" << ms_elapsed << \" [ms] frequency = \"\n            << (n_clients*num_threads)/ms_elapsed << \" [kHz]\"\n            << \"\\n\";\n\n  eos::common::Mapping::Reset();\n  std::chrono::steady_clock::time_point reset = std::chrono::steady_clock::now();\n  auto reset_time = std::chrono::duration_cast<std::chrono::milliseconds>(reset - end).count();\n  std::cout << \"Reset time=\" << reset_time << \" ms\" << \"\\n\";\n  return 0;\n}\n"
  },
  {
    "path": "test/EosLoggingBenchmark.cc",
    "content": "// ----------------------------------------------------------------------\n// File: EosLoggingBenchmark.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*-----------------------------------------------------------------------------*/\n#include <sys/types.h>\n#include <sys/wait.h>\n/*-----------------------------------------------------------------------------*/\n#include \"common/Logging.hh\"\n#include \"common/Timing.hh\"\n#include \"common/StringConversion.hh\"\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n#include <string>\n#include <iostream>\n#include <thread>\n#include <stdio.h>\n/*-----------------------------------------------------------------------------*/\n\nusing namespace std;\n\n// 1GB mem buffer\n\n#define NTHREADS 1024\n#define NMESSAGES 2000\n\ndouble realtimes[NTHREADS][NMESSAGES];\n\nint nosaturation = 0;\n\nvoid threadlog(int id)\n{\n  std::string message;\n  message = \"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789_\";\n  message += std::to_string(id);\n\n  double realtime = 0.0;\n  for (size_t i=0; i< NMESSAGES; i++) {\n    eos::common::Timing tm(\"Checksumming\");\n    COMMONTIMING(\"START\", &tm);\n    eos_static_info(\"%.4f %s\", realtime, message.c_str());\n    COMMONTIMING(\"STOP\", &tm);\n    realtime = tm.RealTime();\n    realtimes[id][i] = realtime;\n    if (nosaturation) {\n      usleep(40000);\n    }\n  }\n}\n\nint main(int argc, char* argv[])\n{\n  eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n  g_logging.SetUnit(\"eoschecksumbenchmark@localhost\");\n  g_logging.gShortFormat = true;\n  g_logging.SetLogPriority(LOG_DEBUG);\n\n  if (argc==1) {\n    fprintf(stdout, \"#running in saturation mode\\n\");\n  } else {\n    nosaturation = true;\n    fprintf(stdout, \"#running in non-saturation mode\\n\");\n  }\n\n  FILE* fp = fopen(\"/var/tmp/eoslogbench.fan.log\", \"a+\");\n\n  if (fp) {\n    g_logging.AddFanOut(\"#\", fp);\n  }\n\n\n  FILE* fstderr = 0;\n\n  if (!(fstderr = freopen(\"/var/tmp/eoslogbench.log\", \"a+\", stderr))) {\n    fprintf(stderr,\"error: cannot open test log file /var/tmp/eoslogbench.log\");\n    exit(-1);\n  }\n  std::vector<std::thread*> threads;\n\n  eos::common::Timing tm(\"Messaging\");\n  COMMONTIMING(\"START\", &tm);\n\n  for (size_t i= 0; i< NTHREADS; i++) {\n    threads.push_back(new std::thread(threadlog, i));\n  }\n\n  for (size_t i =0 ; i< NTHREADS; i++) {\n    threads[i]->join();\n  }\n\n  for (size_t i =0 ; i< NTHREADS; i++) {\n    delete threads[i];\n  }\n\n  double min, max, avg;\n  min = 1000000;\n  max = 0;\n  avg = 0;\n\n  for (size_t i = 0; i< NTHREADS; i++) {\n    for (size_t m = 0 ; m<NMESSAGES; m++) {\n      if (realtimes[i][m] < min) {\n        min = realtimes[i][m];\n      }\n      if (realtimes[i][m] > max) {\n\tmax = realtimes[i][m];\n      }\n      avg += realtimes[i][m];\n    }\n  }\n  avg /= (NTHREADS*NMESSAGES);\n\n  COMMONTIMING(\"STOP\", &tm);\n\n  fprintf(stdout,\"duration: %.02f [s] min: %.04f [ms] max: %.04f [ms] avg: %.04f [ms] nmsg: %d rate: %.02f [Hz] \\n\", tm.RealTime()/1000.0, min, max, avg, NTHREADS*NMESSAGES, NTHREADS*NMESSAGES / tm.RealTime()*1000);\n\n  g_logging.shutDown(true);        /* gracefully, while files are still open */\n  fclose(fstderr);\n  fclose(fp);\n}\n"
  },
  {
    "path": "test/EosMmap.cc",
    "content": "// ----------------------------------------------------------------------\n// File: EosMmap.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <sys/mman.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n\nvoid usage() {\n  fprintf(stderr,\"usage: eos-mmap <file>\\n\");\n  exit(-1);\n}\n\nint main(int argc, char* argv[]) {\n  if (argc != 2) {\n    usage();\n  }\n\n  int fd = open(argv[1],0,0);\n  void *mapptr=0;\n  struct stat buf;\n  if (stat(argv[1],&buf)) {\n    usage();\n  }\n  \n\n  if (fd>0) {\n    printf(\"[eos-mmap] mapping %lld bytes ...\\n\", (unsigned long long) buf.st_size);\n    mapptr = mmap(0, buf.st_size, PROT_READ,MAP_SHARED, fd,0);\n    int percentage=0;\n    int last_percentage=0;\n    if (mapptr) {\n      for (off_t i =0; i < (off_t)(buf.st_size/sizeof(unsigned long long)); i+=10) {\n\tunsigned long long b = *((unsigned long long*)mapptr + i);\n\tif (b) b = 0; // compiler warning\n\tpercentage = 1+ (int) ( i * 100.0 / buf.st_size );\n\tif ( !(percentage%10)) {\n\t  if (last_percentage != percentage) {\n\t    printf(\"[eos-mmap] %03d %% cached\\r\",percentage);\n\t    last_percentage = percentage;\n\t  }\n\t}\n      }\n    }\n  }\n  printf(\"\\r[eos-mmap] file is fully mmaped\\n\");\n  sleep(10000000);\n}\n"
  },
  {
    "path": "test/EosOpenTruncUpdate.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file EosOpenTrunUpdate.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <fstream>\n#include <memory>\n#include <iostream>\n\n//------------------------------------------------------------------------------\n// Generate random data\n//------------------------------------------------------------------------------\nvoid\nGenerateRandomData(char* data, ssize_t length)\n{\n  std::ifstream urandom(\"/dev/urandom\", std::ios::in | std::ios::binary);\n  urandom.read(data, length);\n  urandom.close();\n}\n\nXrdPosixXrootd posixXrootd;\n//------------------------------------------------------------------------------\n//! Open truncate a file and write into it.\n//------------------------------------------------------------------------------\nint main(int argc, char* argv[])\n{\n  if (argc < 2 || (strncmp(\"-h\", argv[1], 2) == 0)) {\n    std::cerr << \"Usage: \" << argv[0] << \" <url> [<max_file_sz>]\" << std::endl;\n    exit(EINVAL);\n  }\n\n  size_t max_sz {64 * 1024 * 1024};\n\n  if (argc == 3) {\n    try {\n      max_sz = std::stoull(std::string(argv[2]));\n    } catch (...) {}\n  }\n\n  uint64_t off {0ull};\n  size_t sz {4 * 1024 * 1024};\n  uint64_t len = sz;\n  std::unique_ptr<char[]> buffer = std::make_unique<char[]>(sz);\n  GenerateRandomData(buffer.get(), sz);\n  std::string surl = argv[1];\n  XrdCl::URL url(surl);\n\n  if (!url.IsValid()) {\n    std::cerr << \"Usage: \" << argv[0] << \" <url> [<max_file_sz>]\" << std::endl;\n    exit(EINVAL);\n  }\n\n  int fd = XrdPosixXrootd::Open(surl.c_str(), O_RDWR,\n                                kXR_ur | kXR_uw | kXR_gw | kXR_gr | kXR_or);\n  XrdPosixXrootd::Ftruncate(fd, 0);\n\n  while (off < max_sz) {\n    if (max_sz - off < sz) {\n      len = max_sz - off;\n    }\n\n    // Ignore on purpose the return from pwrite\n    (void) XrdPosixXrootd::Pwrite(fd, buffer.get(), len, off);\n    off += len;\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "test/EosUdpDumper.cc",
    "content": "// ----------------------------------------------------------------------\n// File: EosUdpDumper.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <strings.h>\n/*-----------------------------------------------------------------------------*/\n\n// a UDP server listening by default on port 32.000 dumping UDP packets of max. 64k\n\nint main(int argc, char** argv)\n{\n  int sockfd, n;\n  struct sockaddr_in servaddr, cliaddr;\n  socklen_t len;\n  char mesg[65536];\n  int port = 32000;\n\n  if (argc > 1) {\n    if (atoi(argv[1]) && (argc == 2)) {\n      port = atoi(argv[1]);\n    } else {\n      fprintf(stderr, \"usage: eos-udp-dumper [port]\\n\");\n      exit(-1);\n    }\n  }\n\n  fprintf(stdout, \"[eos-udp-dumper]: listening on port %d (max_message_size\"\n          \"=64k)\\n\", port);\n  sockfd = socket(AF_INET, SOCK_DGRAM, 0);\n\n  if (sockfd == -1) {\n    fprintf(stderr, \"error: failed while calling socket\\n\");\n    return 1;\n  }\n\n  bzero(&servaddr, sizeof(servaddr));\n  servaddr.sin_family = AF_INET;\n  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);\n  servaddr.sin_port = htons(port);\n  bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));\n\n  for (;;) {\n    len = sizeof(cliaddr);\n    n = recvfrom(sockfd, mesg, 65536, 0, (struct sockaddr*)&cliaddr, &len);\n\n    if (n == -1) {\n      fprintf(stderr, \"error: failed while calling recvfrom\\n\");\n      return 1;\n    }\n\n    if (sendto(sockfd, mesg, n, 0, (struct sockaddr*)&cliaddr,\n               sizeof(cliaddr)) == -1) {\n      fprintf(stderr, \"error: failed while calling sendto\\n\");\n      return 1;\n    }\n\n    printf(\"-------------------------------------------------------\\n\");\n    mesg[n] = 0;\n    printf(\"%s\", mesg);\n    printf(\"-------------------------------------------------------\\n\");\n  }\n}\n"
  },
  {
    "path": "test/TestHmacSha256.cc",
    "content": "//------------------------------------------------------------------------------\n// File: TestHmacSha256.cc\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @file TestHmacSha256.cc\n//! @author Elvin-Alin Sindrilaru - CERN\n//! @brief Test unit for the HMAC SHA 256 implementation\n//------------------------------------------------------------------------------\n\n/*----------------------------------------------------------------------------*/\n#include \"common/SymKeys.hh\"\n/*----------------------------------------------------------------------------*/\n\nusing  eos::common::SymKey;\n\nint main(void)\n{\n  std::string key = \"key-to-encrypt\";\n  std::string data = \"This is just a plain simple example to test the basic \"\n                     \"functionality.\";\n  std::string expected = \"e44f11c53447641d0183ecf1a2ca07d77408176a116685802432f\"\n                         \"0dff74c2ab1\";\n\n  std::string result = SymKey::HmacSha256( key, data );  \n  unsigned char* ptrResult = ( unsigned char* ) result.c_str();\n\n  XrdOucString expectedBase64;\n  XrdOucString resultBase64;\n  \n  std::string readableStr;\n  char str[3];\n\n  for ( unsigned int i = 0; i < result.length(); ++i, ptrResult++ ) {\n    sprintf( str, \"%02x\", *ptrResult );\n    readableStr += str;\n  }\n\n  if ( !SymKey::Base64Encode( (char*) readableStr.c_str(),\n                              readableStr.length(),\n                              resultBase64 ) )\n  {\n    fprintf( stdout, \"Error while encoding the result. \\n\" );\n    exit(-1);\n  }\n\n\n  if ( !SymKey::Base64Encode( (char*) expected.c_str(),\n                              expected.length(),\n                              expectedBase64 ) )\n  {\n    fprintf( stdout, \"Error while expected string. \\n\" );\n    exit(-1);\n  }\n\n  //fprintf( stdout, \"Expected string is:%s \\nResult string is  :%s \\n\",\n  //         expectedBase64.c_str(), resultBase64.c_str() );\n  \n  if ( strncmp( expectedBase64.c_str(), resultBase64.c_str(), expectedBase64.length() ) != 0 ) {\n    fprintf( stdout, \"Test FAILED. \\n\" );\n    return -1;\n  }\n  else {\n    fprintf( stdout, \"Test SUCCEEDED. \\n\" );\n    return 0;\n  }\n}\n"
  },
  {
    "path": "test/ThreadPoolTest.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ThreadPoolTest.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/ThreadPool.hh\"\n#include <chrono>\n\nusing namespace eos::common;\n\nint main(int argc, char* argv[])\n{\n  ThreadPool pool(2, 8, 5, 5);\n  std::vector<std::future<int>> futures;\n\n  for (int i = 0; i < 200000; i++) {\n    auto future = pool.PushTask<int>(\n    [i] {\n      std::this_thread::sleep_for(std::chrono::milliseconds(20));\n      std::cout << i << \" from \" << std::this_thread::get_id() << std::endl;\n      return i;\n    }\n                  );\n    futures.emplace_back(std::move(future));\n  }\n\n  for (auto && future : futures) {\n    std::cout << future.get() << std::endl;\n  }\n\n  futures.clear();\n  std::this_thread::sleep_for(std::chrono::seconds(25));\n\n  for (int i = 60; i < 100; i++) {\n    auto future = pool.PushTask<int>(\n    [i] {\n      std::this_thread::sleep_for(std::chrono::seconds(3));\n      std::cout << i << \" from \" << std::this_thread::get_id() << std::endl;\n      return i;\n    }\n                  );\n    futures.emplace_back(std::move(future));\n  }\n\n  for (auto && future : futures) {\n    std::cout << future.get() << std::endl;\n  }\n\n  pool.Stop();\n  return 0;\n}\n"
  },
  {
    "path": "test/XrdCpAbort.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdCpAbort.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n\n/*-----------------------------------------------------------------------------*/\n\nXrdPosixXrootd posixXrootd;\n\nint main (int argc, char* argv[]) {\n  // create a 1k file but does not close it!\n  XrdOucString urlFile = argv[1];\n  if (!urlFile.length()) {\n    fprintf(stderr,\"usage: xrdcpabort <url>\\n\");\n    exit(EINVAL);\n  }\n\n\n  int fdWrite = XrdPosixXrootd::Open(urlFile.c_str(),\n\t\t\t\t     O_CREAT|O_RDWR|O_TRUNC,\n\t\t\t\t     kXR_ur | kXR_uw | kXR_gw | kXR_gr | kXR_or );\n\n  if (fdWrite>=0) {\n    char buffer[1024];\n    XrdPosixXrootd::Pwrite(fdWrite, buffer, sizeof(buffer),0);\n  } else {\n    exit(-1);\n  }\n  // self suicide\n  kill(getpid(),9);\n}\n"
  },
  {
    "path": "test/XrdCpAppend.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdCpAbort.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n\n/*-----------------------------------------------------------------------------*/\n\nXrdPosixXrootd posixXrootd;\n\nint main (int argc, char* argv[]) {\n  // update an existing file and append a 4k buffer;\n  XrdOucString urlFile = argv[1];\n  if (!urlFile.length()) {\n    fprintf(stderr,\"usage: xrdcpappend <url>\\n\");\n    exit(EINVAL);\n  }\n\n\n  int fdWrite = XrdPosixXrootd::Open(urlFile.c_str(),\n\t\t\t\t     O_RDWR,\n\t\t\t\t     kXR_ur | kXR_uw | kXR_gw | kXR_gr | kXR_or );\n\n  if (fdWrite>=0) {\n    char buffer[4096];\n    for (size_t i=0; i< sizeof(buffer); i++) {\n      buffer[i] = i%255;\n    }\n    struct stat buf;\n    if (!XrdPosixXrootd::Stat(urlFile.c_str(), &buf)) {\n      fprintf(stderr,\"offset=%llu\\n\", (unsigned long long)buf.st_size);\n      XrdPosixXrootd::Pwrite(fdWrite, buffer, sizeof(buffer),buf.st_size);\n    } else {\n      exit(-1);\n    }\n  } else {\n    exit(-1);\n  }\n}\n"
  },
  {
    "path": "test/XrdCpAppendOverlap.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdCpAppendOverlap.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <chrono>\n#include <thread>\n/*-----------------------------------------------------------------------------*/\n\nXrdPosixXrootd posixXrootd;\n\nint main(int argc, char* argv[])\n{\n  // update an existing file and append a 4k buffer;\n  if (argc != 3) {\n    fprintf(stderr, \"usage: xrdappendoverlap <url1> <url2>\\n\");\n    exit(EINVAL);\n  }\n\n  XrdOucString urlFile1 = argv[1];\n  XrdOucString urlFile2 = argv[2];\n\n  if (!urlFile1.length()) {\n    fprintf(stderr, \"usage: xrdappendoverlap <url1> <url2>\\n\");\n    exit(EINVAL);\n  }\n\n  if (!urlFile2.length()) {\n    fprintf(stderr, \"usage: xrdappendoverlap <url1> <url2>\\n\");\n    exit(EINVAL);\n  }\n\n  int fdWrite1 = XrdPosixXrootd::Open(urlFile1.c_str(),\n                                      O_RDWR,\n                                      kXR_ur | kXR_uw | kXR_gw | kXR_gr | kXR_or);\n  char buffer1[4096];\n  char buffer2[4096];\n  struct stat buf;\n\n  if (fdWrite1 >= 0) {\n    for (size_t i = 0; i < sizeof(buffer1); i++) {\n      buffer1[i] = i % 255;\n      buffer2[i] = (i + 1) % 255;\n    }\n\n    if (!XrdPosixXrootd::Stat(urlFile1.c_str(), &buf)) {\n      fprintf(stderr, \"offset=%llu\\n\", (unsigned long long)buf.st_size);\n      XrdPosixXrootd::Pwrite(fdWrite1, buffer1, sizeof(buffer1), buf.st_size);\n      XrdPosixXrootd::Stat(urlFile1.c_str(), &buf);\n      fprintf(stderr, \"offset=%llu\\n\", (unsigned long long)buf.st_size);\n    } else {\n      exit(-1);\n    }\n  }\n\n  std::this_thread::sleep_for(std::chrono::milliseconds(500));\n  int fdWrite2 = XrdPosixXrootd::Open(urlFile2.c_str(),\n                                      O_RDWR,\n                                      kXR_ur | kXR_uw | kXR_gw | kXR_gr | kXR_or);\n  std::this_thread::sleep_for(std::chrono::milliseconds(500));\n\n  if (fdWrite2 >= 0) {\n    if (!XrdPosixXrootd::Stat(urlFile2.c_str(), &buf)) {\n      fprintf(stderr, \"offset=%llu\\n\", (unsigned long long)buf.st_size + 4096);\n      XrdPosixXrootd::Pwrite(fdWrite2, buffer2, sizeof(buffer2), buf.st_size + 4096);\n    } else {\n      exit(-1);\n    }\n  } else {\n    exit(-1);\n  }\n}\n"
  },
  {
    "path": "test/XrdCpBackward.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdCpAbort.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <fcntl.h>\n\n\nXrdPosixXrootd posixXrootd;\n\nint main(int argc, char* argv[])\n{\n  // create a 1k file but does not close it!\n  XrdOucString urlFile = argv[1];\n\n  if (!urlFile.length()) {\n    fprintf(stderr, \"usage: xrdcpabort <url>\\n\");\n    exit(EINVAL);\n  }\n\n  struct stat buf;\n\n  if (!XrdPosixXrootd::Stat(urlFile.c_str(), &buf)) {\n    int fdRead = XrdPosixXrootd::Open(urlFile.c_str(), 0, 0);\n\n    if (fdRead >= 0) {\n      constexpr int bsz = 8192;\n      char buffer[bsz];\n      int nbytes = buf.st_size % bsz;\n      long long offset = 0;\n\n      if (buf.st_size > bsz) {\n        offset = buf.st_size - (buf.st_size % bsz);\n      }\n\n      do {\n        int rbytes = XrdPosixXrootd::Pread(fdRead, buffer, nbytes, offset);\n\n        if (rbytes != nbytes) {\n          fprintf(stderr, \"error: read failed at offset %lld\\n\", offset);\n          exit(-1);\n        }\n\n        nbytes = bsz;\n        offset -= nbytes;\n      } while (offset >= 0);\n\n      int rc = XrdPosixXrootd::Close(fdRead);\n\n      if (rc) {\n        fprintf(stderr, \"error: close failed with retc=%d errno=%i\", rc, errno);\n        exit(rc);\n      }\n    } else {\n      fprintf(stderr, \"error: failed to open %s\\n\", urlFile.c_str());\n    }\n  } else {\n    fprintf(stderr, \"error: file %s does not exist!\\n\", urlFile.c_str());\n    exit(-1);\n  }\n}\n"
  },
  {
    "path": "test/XrdCpDownloadRandom.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdCpAbort.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdOuc/XrdOucString.hh>\n/*-----------------------------------------------------------------------------*/\n\nXrdPosixXrootd posixXrootd;\n\nint main (int argc, char* argv[]) {\n  // create a 1k file but does not close it!\n  XrdOucString urlFile = argv[1];\n  if (!urlFile.length()) {\n    fprintf(stderr,\"usage: xrdcpabort <url>\\n\");\n    exit(EINVAL);\n  }\n\n  struct stat buf;\n\n  if (!XrdPosixXrootd::Stat(urlFile.c_str(), &buf)) {\n    int fdRead = XrdPosixXrootd::Open(urlFile.c_str(),0,0);\n\n    if (fdRead>=0) {\n      char* buffer = (char*)malloc(256 * 4096);\n\n      // download 1000 random chunks\n      for (int i=0 ;i< 1000; i++) {\n\toff_t offset = (off_t)(buf.st_size * random()/RAND_MAX);\n\tsize_t length = (size_t) ( (buf.st_size -offset) * random()/RAND_MAX);\n\n\twhile (length > sizeof(buffer)) {\n\t  length /= 2;\n\t}\n\n\tint rbytes = XrdPosixXrootd::Pread(fdRead, buffer, length, offset);\n\tif (rbytes != (int)length) {\n\t  fprintf(stderr,\"error: read failed at offset %lld length %lu \\n\", (unsigned long long)offset,(unsigned long)length);\n\t  exit(-1);\n\t}\n      }\n      int rc = XrdPosixXrootd::Close(fdRead);\n      if (rc) {\n\tfprintf(stderr,\"error: close failed\\nd with retc=%d\", rc);\n\texit(rc);\n      }\n    } else {\n      fprintf(stderr,\"error: failed to open %s\\n\", urlFile.c_str());\n    }\n  } else {\n    fprintf(stderr,\"error: file %s does not exist!\\n\", urlFile.c_str());\n    exit(-1);\n  }\n}\n"
  },
  {
    "path": "test/XrdCpExtend.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdCpAbort.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n/*-----------------------------------------------------------------------------*/\n\nXrdPosixXrootd posixXrootd;\n\nint main (int argc, char* argv[]) {\n  // create a 1k file but does not close it!\n  XrdOucString urlFile = argv[1];\n  if (!urlFile.length()) {\n    fprintf(stderr,\"usage: xrdcpextend <url>\\n\");\n    exit(EINVAL);\n  }\n\n\n  int fdWrite = XrdPosixXrootd::Open(urlFile.c_str(),\n\t\t\t\t     O_CREAT|O_RDWR|O_TRUNC,\n\t\t\t\t     kXR_ur | kXR_uw | kXR_gw | kXR_gr | kXR_or );\n\n  if (fdWrite>=0) {\n    char buffer[4096];\n    for (size_t i=0; i< sizeof(buffer); i++) {\n      buffer[i] = i%255;\n    }\n    XrdPosixXrootd::Pwrite(fdWrite, buffer, sizeof(buffer),0);\n    XrdPosixXrootd::Ftruncate(fdWrite,1000000);\n  } else {\n    exit(-1);\n  }\n}\n"
  },
  {
    "path": "test/XrdCpHoles.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdCpAbort.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n#include <fstream>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n/*-----------------------------------------------------------------------------*/\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdOuc/XrdOucString.hh>\n/*-----------------------------------------------------------------------------*/\n\nXrdPosixXrootd posixXrootd;\n\nint main (int argc, char* argv[]) {\n  // create a 1k file but does not close it!\n  XrdOucString urlFile = argv[1];\n  if (!urlFile.length()) {\n    fprintf(stderr,\"usage: xrdcpabort <url>\\n\");\n    exit(EINVAL);\n  }\n\n  int fdWrite = XrdPosixXrootd::Open(urlFile.c_str(),\n\t\t\t\t     O_CREAT|O_RDWR|O_TRUNC,\n\t\t\t\t     kXR_ur | kXR_uw | kXR_gw | kXR_gr\n\t\t\t\t     | kXR_or );\n\n  off_t offset = 0;\n  size_t sizeHeader = 4* 1024;\n  size_t sizeBuffer = 1024*1024;\n  char* buffer = (char*)malloc(sizeBuffer);\n  if (!buffer)\n    exit(-1);\n\n  std::ifstream urandom(\"/dev/urandom\", std::ios::in | std::ios::binary);\n  urandom.read(buffer, sizeBuffer);\n  urandom.close();\n\n  if (fdWrite >= 0) {\n    offset = 0;\n    XrdPosixXrootd::Pwrite(fdWrite, buffer, sizeHeader, offset);\n    XrdPosixXrootd::Ftruncate(fdWrite, sizeBuffer * 4 + sizeHeader);\n\n    offset = sizeHeader;\n    XrdPosixXrootd::Pwrite(fdWrite, buffer, sizeBuffer, offset);\n    XrdPosixXrootd::Ftruncate(fdWrite, sizeBuffer * 4 + sizeHeader);\n\n    offset = sizeHeader + sizeBuffer;\n    XrdPosixXrootd::Pwrite(fdWrite, buffer, sizeBuffer, offset);\n    XrdPosixXrootd::Ftruncate(fdWrite, sizeBuffer * 4 + sizeHeader);\n\n    offset = sizeHeader + 2 * sizeBuffer;\n    XrdPosixXrootd::Pwrite(fdWrite, buffer, sizeBuffer, offset);\n    XrdPosixXrootd::Ftruncate(fdWrite, sizeBuffer * 4 + sizeHeader);\n\n    offset = sizeHeader + 3 * sizeBuffer;\n    XrdPosixXrootd::Pwrite(fdWrite, buffer, sizeBuffer, offset);\n    XrdPosixXrootd::Ftruncate(fdWrite, sizeBuffer * 8 + sizeHeader);\n\n    offset = sizeHeader + 4 * sizeBuffer;\n    XrdPosixXrootd::Pwrite(fdWrite, buffer, 250*1024, offset);\n    XrdPosixXrootd::Ftruncate(fdWrite, sizeBuffer * 8 + sizeHeader);\n\n    XrdPosixXrootd::Close(fdWrite);\n    exit(0);\n  } else {\n    exit(-1);\n  }\n}\n"
  },
  {
    "path": "test/XrdCpNonStreaming.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file XrdCpNonStreaming.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <XrdCl/XrdClFile.hh>\n#include <iostream>\n#include <memory>\n#include <sys/stat.h>\n#include <stdio.h>\n#include <fcntl.h>\n\n//------------------------------------------------------------------------------\n//! This executable simulates a client that writes a file in non-streaming mode.\n//------------------------------------------------------------------------------\nint main(int argc, char* argv[])\n{\n  if (argc < 3) {\n    std::cerr << \"Usage: \" << argv[0] << \" <input_file> <xrd_url>\\n\"\n              << \"  <input_file> - local input file used as source of data\\n\"\n              << \"  <xrd_url> - XRootD URL where file is written\\n\"\n              << std::endl;\n    exit(EINVAL);\n  }\n\n  std::string fn_path = argv[1];\n  std::string surl = argv[2];\n  XrdCl::URL url(surl);\n\n  if (!url.IsValid()) {\n    std::cerr << \"error: given XRootD URL is not valid\" << std::endl;\n    exit(EINVAL);\n  }\n\n  struct stat info;\n\n  if (stat(fn_path.c_str(), &info)) {\n    std::cerr << \"error: failed to stat input file\" << std::endl;\n    exit(EINVAL);\n  }\n\n  // Allocate buffer used for transfer\n  uint32_t block_size = 1024 * 1024;\n  std::unique_ptr<char[]> buffer {new char[block_size + 3]};\n  // Open file and start writing\n  XrdCl::File file;\n  XrdCl::XRootDStatus status =\n    file.Open(surl, XrdCl::OpenFlags::Delete | XrdCl::OpenFlags::Write,\n              XrdCl::Access::UR | XrdCl::Access::UW);\n\n  if (!status.IsOK()) {\n    std::cerr << \"error: unable to open file for writing, errno=\"\n              << status.errNo << std::endl;\n    exit(status.errNo);\n  }\n\n  size_t sz;\n  int64_t offset = 0ull;\n  int fd = open(fn_path.c_str(), O_RDONLY);\n\n  if (fd == -1) {\n    std::cerr << \"error: failed to open input file \" << fn_path << std::endl;\n    exit(EIO);\n  }\n\n  // Write all the odd blocks\n  offset = block_size;\n\n  while (offset < info.st_size) {\n    sz = pread(fd, buffer.get(), block_size, offset);\n\n    if (!sz) {\n      break;\n    }\n\n    std::cout << \"offset = \" << offset << \" length = \" << sz << std::endl;\n    status = file.Write(offset, sz, buffer.get());\n\n    if (!status.IsOK()) {\n      std::cerr << \"error: failed write offset=\" << offset << \", lenght=\"\n                << block_size << std::endl;\n      exit(status.errNo);\n    }\n\n    offset += (2 * sz);\n  }\n\n  offset = 0;\n\n  // Write all the even blocks\n  while (offset < info.st_size) {\n    sz = pread(fd, buffer.get(), block_size + 3, offset);\n\n    if (!sz) {\n      break;\n    }\n\n    std::cout << \"offset = \" << offset << \" length = \" << sz << std::endl;\n    status = file.Write(offset, sz, buffer.get());\n\n    if (!status.IsOK()) {\n      std::cerr << \"error: failed write offset=\" << offset << \", lenght=\"\n                <<  block_size << std::endl;\n      exit(status.errNo);\n    }\n\n    offset += (2 * block_size);\n  }\n\n  if (!file.Close().IsOK()) {\n    std::cerr << \"error: failed to close file\" << std::endl;\n    exit(EIO);\n  }\n}\n"
  },
  {
    "path": "test/XrdCpPartial.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdCpAbort.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdOuc/XrdOucString.hh>\n/*-----------------------------------------------------------------------------*/\n\nXrdPosixXrootd posixXrootd;\n\nint main (int argc, char* argv[]) {\n  // read the first part of a file\n  XrdOucString urlFile = argv[1];\n  if (!urlFile.length()) {\n    fprintf(stderr,\"usage: xrdcppartial <url>\\n\");\n    exit(EINVAL);\n  }\n\n\n  int fdRead = XrdPosixXrootd::Open(urlFile.c_str(),0,0);\n\n\n  if (fdRead>=0) {\n    char buffer[4096];\n    for (size_t i=0; i< sizeof(buffer); i++) {\n      buffer[i] = i%255;\n    }\n    struct stat buf;\n    if (!XrdPosixXrootd::Stat(urlFile.c_str(),&buf)) {\n      ssize_t size= buf.st_size;\n      if (buf.st_size>1024) {\n\t// read 1k\n\tsize = 1024;\n      } else {\n\t// read half of the file\n\tsize = size/2;\n      }\n      ssize_t rs = XrdPosixXrootd::Pread(fdRead, buffer, size,0);\n      if (rs != size) {\n\tfprintf(stderr,\"error: read returned rc=%lld instead of %lld\\n\", (long long)rs, (long long)size);\n\texit(-3);\n      }\n      if (XrdPosixXrootd::Close(fdRead)) {\n\tfprintf(stderr,\"error: close failed\\n\");\n\texit(-2);\n      }\n    }\n  } else {\n    exit(-1);\n  }\n}\n"
  },
  {
    "path": "test/XrdCpPgRead.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file XrdCpPgRead.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"utils/RandUtils.hh\"\n#include <XrdCl/XrdClFile.hh>\n#include <iostream>\n#include <memory>\n//------------------------------------------------------------------------------\n//! This executable simulates a client that reads from a file using pgRead API.\n//------------------------------------------------------------------------------\nint main(int argc, char* argv[])\n{\n  constexpr uint32_t max_length = 10 * 1024 * 1024;\n  int retc = 0;\n\n  if (argc != 4) {\n    std::cerr << \"Usage: \" << argv[0] << \" <xrd_url> <offset> <length>\\n\"\n              << \"  <xrd_url> - XRootD URL of file read\\n\"\n              << \"  <offset>  - read offset\\n\"\n              << \"  <length>  - read offset\\n\";\n    exit(EINVAL);\n  }\n\n  std::string surl = argv[1];\n  XrdCl::URL url(surl);\n\n  if (!url.IsValid()) {\n    std::cerr << \"error: given XRootD URL is not valid\" << std::endl;\n    exit(EINVAL);\n  }\n\n  uint64_t offset = 0ull;\n  uint32_t length = 0ul;\n\n  try {\n    offset = std::stoull(std::string(argv[2]));\n    length = std::stoul(std::string(argv[3]));\n  } catch (...) {\n    std::cerr << \"error: failed to convert given input\" << std::endl;\n    exit(EINVAL);\n  }\n\n  if (length > max_length) {\n    std::cerr << \"error: length must be <= 10MB\" << std::endl;\n    exit(EINVAL);\n  }\n\n  // Allocate buffer used for transfer\n  std::unique_ptr<char[]> buffer {new char[length]};\n  XrdCl::File file;\n  XrdCl::XRootDStatus status = file.Open(surl, XrdCl::OpenFlags::Read,\n                                         XrdCl::Access::None);\n\n  if (!status.IsOK()) {\n    std::cerr << \"error: unable to open file for reading, errno=\"\n              << status.errNo << std::endl;\n    exit(status.errNo);\n  }\n\n  std::vector<uint32_t> cksums;\n  uint32_t bytes_read = 0ul;\n\n  if ((offset == 0) && (length == 0)) {\n    constexpr uint32_t max_buff = 4 * 1024 * 1024;\n    buffer.reset(new char[max_buff]);\n    uint32_t samples = 10000;\n\n    for (unsigned int i = 0; i < samples; ++i) {\n      cksums.clear();\n      uint64_t rand_off = eos::common::getRandom64(0ull, 1235676367ull);\n      uint32_t rand_len = eos::common::getRandom64(4096u, max_buff);\n      std::cout << \"index: \" << i\n                << \" pgread: rand_off=\" << rand_off\n                << \" rand_len=\" << rand_len << std::endl;\n      status = file.PgRead(rand_off, rand_len, buffer.get(), cksums, bytes_read);\n\n      if (!status.IsOK()) {\n        std::cerr << \"error: failed pgread rand_off=\" << rand_off\n                  << \" rand_len=\" << rand_len << std::endl;\n        exit(status.errNo);\n      }\n    }\n  } else {\n    std::cout << \" pgread: offset=\" << offset\n              << \" length=\" << length << std::endl;\n    status = file.PgRead(offset, length, buffer.get(), cksums, bytes_read);\n\n    if (!status.IsOK()) {\n      std::cerr << \"error: failed pgread offset=\" << offset\n                << \" length=\" << length << std::endl;\n      exit(status.errNo);\n    }\n  }\n\n  if (!file.Close().IsOK()) {\n    std::cerr << \"error: failed to close file\" << std::endl;\n    exit(EIO);\n  }\n\n  return retc;\n}\n"
  },
  {
    "path": "test/XrdCpPosixCache.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdCpPosixCache.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdOuc/XrdOucString.hh>\n/*-----------------------------------------------------------------------------*/\n\nXrdPosixXrootd posixXrootd;\n\nint main (int argc, char* argv[]) {\n  // download a file with the posix cache enabled\n  XrdOucString urlFile = argv[1];\n  if (!urlFile.length()) {\n    fprintf(stderr,\"usage: xrdcpposixcache <url>\\n\");\n    exit(EINVAL);\n  }\n\n  sleep(1);\n  fprintf(stderr,\"%s\\n\", getenv(\"XRDPOSIX_CACHE\"));\n  if (getenv(\"XRDPOSIX_CACHE\")) {\n    fprintf(stderr,\"INFO: using Xrd Posix Cache settings: %s\\n\", getenv(\"XRDPOSIX_CACHE\"));\n  } else {\n    fprintf(stderr,\"WARNING: please set the XRDPOSIX_CACHE variable e.g. export XRDPOSIX_CACHE=\\\"debug=3&mode=c&optpr=1&pagesz=128k&cachesz=1g&optlg=1&aprminp=128&aprtrig=256k&max2cache=200000\\\"\\n\");\n  }\n\n  for (int k=0; k<2; k++) {\n    fprintf(stderr,\"# RUN   %d ----------------------------------------------------\\n\", k);\n    int fdRead = XrdPosixXrootd::Open(urlFile.c_str(),0,0);\n\n    if (fdRead>0) {\n      off_t offset = 0;\n      size_t nread=0;\n      do {\n\tchar buffer[32*4096];\n\tnread = XrdPosixXrootd::Pread(fdRead, buffer, sizeof(buffer),offset);\n\tif (nread>0) {\n\t  offset += nread;\n\t}\n      } while (nread>0);\n    } else {\n      fprintf(stderr,\"ERROR: couldn't open url=%s\\n\", urlFile.c_str());\n      exit(-1);\n    }\n    fprintf(stderr,\"# CLOSE %d ----------------------------------------------------\\n\", k);\n    XrdPosixXrootd::Close(fdRead);\n  }\n\n  exit(0);\n}\n"
  },
  {
    "path": "test/XrdCpRandom.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdCpAbort.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <vector>\n#include <map>\n#include <stdio.h>\n#include <stdlib.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdOuc/XrdOucString.hh>\n\nXrdPosixXrootd posixXrootd;\n\nint main(int argc, char* argv[])\n{\n  // creates a 100M file written in random order\n  XrdOucString urlFile = argv[1];\n\n  if (!urlFile.length()) {\n    fprintf(stderr, \"usage: xrdcpabort <url>\\n\");\n    exit(EINVAL);\n  }\n\n  int fdWrite = XrdPosixXrootd::Open(urlFile.c_str(),\n                                     O_CREAT | O_RDWR | O_TRUNC,\n                                     kXR_ur | kXR_uw | kXR_gw | kXR_gr | kXR_or);\n  std::map<off_t, off_t> lmap;\n  std::vector<off_t> voff;\n  char* buffer = (char*) malloc(100000000);\n\n  for (size_t i = 0; i < 100000000; i++) {\n    buffer[i] = i % 255;\n  }\n\n  // create 100 pieces\n  for (size_t i = 0; i < 100; i++) {\n    off_t offset = (off_t)(100000000.0 * random() / RAND_MAX);\n    voff.push_back(offset);\n    lmap[offset] = 0;\n    //    fprintf(stderr,\"Chunk %u : %llu\\n\", (unsigned int) i, (unsigned long long)offset);\n  }\n\n  std::map<off_t, off_t>::const_iterator it1;\n  std::map<off_t, off_t>::const_iterator it2;\n  it2 = lmap.begin();\n  it2++;\n\n  for (it1 = lmap.begin(); (it1 != lmap.end()) && (it2 != lmap.end()) ; it1++) {\n    lmap[it1->first] = (it2->first - it1->first);\n    //    fprintf(stderr,\"%llu %llu\\n\", it1->first,lmap[it1->first]);\n    it2++;\n  }\n\n  if (fdWrite >= 0) {\n    for (size_t i = 0; i < 100 ; i++) {\n      //      fprintf(stderr,\"Writing %llu %llu\\n\", voff[i], lmap[voff[i]]);\n      XrdPosixXrootd::Pwrite(fdWrite, buffer + voff[i], lmap[voff[i]], voff[i]);\n    }\n  } else {\n    exit(-1);\n  }\n}\n"
  },
  {
    "path": "test/XrdCpShrink.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdCpAbort.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdOuc/XrdOucString.hh>\n\nXrdPosixXrootd posixXrootd;\n\nint main(int argc, char* argv[])\n{\n  // Create a 1k file but does not close it!\n  XrdOucString urlFile = argv[1];\n\n  if (!urlFile.length()) {\n    fprintf(stderr, \"usage: xrdcpabort <url>\\n\");\n    exit(EINVAL);\n  }\n\n  int fdWrite = XrdPosixXrootd::Open(urlFile.c_str(),\n                                     O_CREAT | O_TRUNC | O_RDWR,\n                                     kXR_ur | kXR_uw | kXR_gw | kXR_gr | kXR_or);\n\n  if (fdWrite >= 0) {\n    size_t len = 10000000;\n    char* buffer = (char*)malloc(len);\n\n    if (!buffer) {\n      exit(-1);\n    }\n\n    for (size_t i = 0; i < len; ++i) {\n      buffer[i] = i % 255;\n    }\n\n    XrdPosixXrootd::Pwrite(fdWrite, buffer, len - 1, 0);\n    XrdPosixXrootd::Ftruncate(fdWrite, 65536);\n  } else {\n    exit(-1);\n  }\n}\n"
  },
  {
    "path": "test/XrdCpSlowWriter.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file XrdCpSlowWriter.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <XrdCl/XrdClFile.hh>\n#include <thread>\n#include <chrono>\n#include <fstream>\n#include <iostream>\n\n//------------------------------------------------------------------------------\n//! This executable simulates a client which keeps the file open for more than\n//! 1 minutes are writes slowly blocks of data.\n//------------------------------------------------------------------------------\nint main(int argc, char* argv[])\n{\n  if (argc < 2) {\n    std::cerr << \"Usage: \" << argv[0] << \" <xrootd_url> [<transfer_time>]\"\n              << std::endl\n              << \"  <xrootd_url> - full XRootD URL where file is written\"\n              << std::endl\n              << \"  <transfer_time> - total time in seconds the transfer \"\n              << \" should take, default 80 seconds\"\n              << std::endl;\n    exit(EINVAL);\n  }\n\n  std::string surl = argv[1];\n  XrdCl::URL url(surl);\n\n  if (!url.IsValid()) {\n    std::cerr << \"error: given URL is not valid\" << std::endl;\n    exit(EINVAL);\n  }\n\n  uint32_t tx_time = 80;\n\n  if (argc == 3) {\n    try {\n      tx_time = std::stoul(argv[2]);\n    } catch (const std::exception& e) {\n      tx_time = 80;\n    }\n  }\n\n  // Allocate a random buffer used for writing\n  // Fill buffer with random characters\n  uint32_t block_size = 1024 * 1024;\n  std::unique_ptr<char[]> buffer = std::make_unique<char[]>(block_size);\n  std::ifstream urandom(\"/dev/urandom\", std::ios::in | std::ios::binary);\n  urandom.read(buffer.get(), block_size);\n  urandom.close();\n  // Open file and start writing\n  XrdCl::File file;\n  XrdCl::XRootDStatus status =\n    file.Open(surl, XrdCl::OpenFlags::Delete | XrdCl::OpenFlags::Write,\n              XrdCl::Access::UR | XrdCl::Access::UW);\n\n  if (!status.IsOK()) {\n    std::cerr << \"error: unable to open file for writing, errno=\"\n              << status.errNo << std::endl;\n    exit(status.errNo);\n  }\n\n  int count = 8;\n  uint64_t offset = 0ull;\n  int sleep_sec = (int) tx_time / count;\n\n  while (count) {\n    status = file.Write(offset, block_size, buffer.get());\n    std::cout << \"info: slow write at offset=\" << offset << std::endl;\n\n    if (!status.IsOK()) {\n      std::cerr << \"error: failed write offset=\" << offset << \", lenght=\"\n                << block_size << std::endl;\n      exit(status.errNo);\n    }\n\n    --count;\n    offset += block_size;\n    std::this_thread::sleep_for(std::chrono::seconds(sleep_sec));\n  }\n\n  if (!file.Close().IsOK()) {\n    std::cerr << \"error: failed to close file\" << std::endl;\n    exit(EIO);\n  }\n}\n"
  },
  {
    "path": "test/XrdCpTruncate.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdCpTruncate.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdOuc/XrdOucString.hh>\n\nXrdPosixXrootd posixXrootd;\n\nint main(int argc, char* argv[])\n{\n  XrdOucString urlFile = argv[1];\n\n  if (!urlFile.length()) {\n    fprintf(stderr, \"usage: xrdcpabort <url>\\n\");\n    exit(EINVAL);\n  }\n\n  int fd = XrdPosixXrootd::Open(urlFile.c_str(),\n                                O_CREAT | O_TRUNC | O_RDWR,\n                                kXR_ur | kXR_uw | kXR_gw | kXR_gr | kXR_or);\n\n  if (fd >= 0) {\n    size_t sz = 10000000;\n    char* buffer = (char*)malloc(sz);\n\n    if (!buffer) {\n      exit(-1);\n    }\n\n    for (size_t i = 0; i < sizeof(buffer); i++) {\n      buffer[i] = i % 255;\n    }\n\n    XrdPosixXrootd::Pwrite(fd, buffer, sz, 0);\n    XrdPosixXrootd::Ftruncate(fd, 2000000);\n    XrdPosixXrootd::Pwrite(fd, buffer, sz, 1024);\n    free(buffer);\n  } else {\n    exit(-1);\n  }\n}\n"
  },
  {
    "path": "test/XrdCpUpdate.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdCpAbort.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n/*-----------------------------------------------------------------------------*/\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdOuc/XrdOucString.hh>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n\n/*-----------------------------------------------------------------------------*/\n\nXrdPosixXrootd posixXrootd;\n\nint main (int argc, char* argv[]) {\n  // create a 1k file but does not close it!\n  XrdOucString urlFile = argv[1];\n  if (!urlFile.length()) {\n    fprintf(stderr,\"usage: xrdcpextend <url>\\n\");\n    exit(EINVAL);\n  }\n\n\n  int fdWrite = XrdPosixXrootd::Open(urlFile.c_str(),\n\t\t\t\t     O_RDWR,\n\t\t\t\t     kXR_ur | kXR_uw | kXR_gw | kXR_gr | kXR_or );\n\n  if (fdWrite>=0) {\n    char buffer[4096];\n    for (size_t i=0 ;i< 4096; i++) {\n      buffer[i]=i;\n    }\n    struct stat buf;\n    if (!XrdPosixXrootd::Stat(urlFile.c_str(), &buf)) {\n      size_t nwrite = XrdPosixXrootd::Pwrite(fdWrite, buffer, sizeof(buffer),2048);\n      fprintf(stderr,\"nwrite=%lu\\n\", nwrite);\n      if (nwrite != 2048) {\n\texit(-1);\n      }\n    } else {\n      fprintf(stderr,\"Stat failed\\n\");\n      exit(-1);\n    }\n  } else {\n    fprintf(stderr,\"open failed\\n\");\n    exit(-1);\n  }\n}\n"
  },
  {
    "path": "test/XrdStress.cc",
    "content": "//------------------------------------------------------------------------------\n// File: XrdStress.cc\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"XrdStress.hh\"\n\n#include <iostream>\n#include <sstream>\n#include <fstream>\n#include <vector>\n#include <set>\n#include <stdlib.h>\n#include <unistd.h>\n#include <uuid/uuid.h>\n#include <fcntl.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <sys/time.h>\n#include <math.h>\n#include <XrdPosix/XrdPosixXrootd.hh>\n#include <XrdCl/XrdClFileSystem.hh>\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nXrdStress::XrdStress(unsigned int nChilds,\n                     unsigned int nFiles,\n                     size_t       sBlock,\n                     off_t        sFile,\n                     std::string  pTest,\n                     std::string  op,\n                     bool         verb,\n                     bool         useProcess,\n                     bool         concurrent):\n  verbose(verb),\n  processMode(useProcess),\n  concurrentMode(concurrent),\n  sizeFile(sFile),\n  sizeBlock(sBlock),\n  numChilds(nChilds),\n  numFiles(nFiles),\n  pathTest(pTest),\n  opType(op)\n{\n  if (processMode) {\n    childType = \"process\";\n  } else {\n    childType = \"thread\";\n\n    for (unsigned int i = 0; i < numChilds; i++) {\n      vectChilds.emplace_back();\n    }\n  }\n\n  // Generate the name of the files only in WR or RDWR mode\n  if (opType == \"wr\" || opType == \"rdwr\") {\n    std::string gen_filename;\n    uuid_t genUuid;\n    char charUuid[40];\n    vectFilename.reserve(numChilds * numFiles);\n\n    if (concurrentMode) {\n      // Generate file names for first job\n      for (unsigned int idf = 0; idf < numFiles ; idf++) {\n        uuid_generate_time(genUuid);\n        uuid_unparse(genUuid, charUuid);\n        gen_filename = pathTest;\n        gen_filename += charUuid;\n        vectFilename.push_back(gen_filename);\n      }\n\n      // For the rest of the jobs copy the file names form the first one\n      for (unsigned int idj = 1; idj < numChilds; idj++) {\n        for (unsigned int idf = 0; idf < numFiles ; idf++) {\n          vectFilename.push_back(vectFilename[idf]);\n        }\n      }\n    } else {\n      // In non-concurrent mode all jobs operate on different files\n      for (unsigned int indx = 0; indx < (numChilds * numFiles); indx++) {\n        uuid_generate_time(genUuid);\n        uuid_unparse(genUuid, charUuid);\n        gen_filename = pathTest;\n        gen_filename += charUuid;\n        vectFilename.push_back(gen_filename);\n      }\n    }\n  } else if (opType == \"rd\") {\n    // If no files in vect. then read the files from the directory\n    unsigned int num_entries = GetListFilenames();\n\n    if (num_entries == 0) {\n      fprintf(stderr, \"error=no files in directory.\\n\");\n      exit(1);\n    }\n\n    if (concurrentMode) {\n      if (num_entries > numFiles) {\n        // Jobs will run (concurrently) only on the first numFiles\n        while (vectFilename.size() > numFiles) {\n          vectFilename.pop_back();\n        }\n      }\n\n      // Duplicate the file names form the first job to all the others\n      for (unsigned int idj = 1; idj < numChilds; idj++) {\n        for (unsigned int idf = 0; idf < numFiles ; idf++) {\n          vectFilename.push_back(vectFilename[idf]);\n        }\n      }\n    } else {\n      // If not in concurrent mode and not enough files in dir. set the new no.\n      // of files so that each job receives the same number of different files\n      // Each file is processed only once, by only one job.\n      if (((num_entries / numChilds) != numFiles)) {\n        numFiles = ceil(1.0 * num_entries / numChilds);\n      }\n    }\n  }\n\n  // Reserve space in vectors for statistics\n  avgRdRate.reserve(numChilds);\n  avgWrRate.reserve(numChilds);\n  avgOpen.reserve(numChilds);\n\n  // Set the type of the function call\n  if (opType == \"rd\") {\n    callback = XrdStress::RdProc;\n  } else if (opType == \"wr\") {\n    callback = XrdStress::WrProc;\n  } else if (opType == \"rdwr\") {\n    callback = XrdStress::RdWrProc;\n  } else {\n    callback = XrdStress::RdProc;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nXrdStress::~XrdStress()\n{\n  vectChilds.clear();\n  avgRdRate.clear();\n  avgWrRate.clear();\n  avgOpen.clear();\n  vectFilename.clear();\n}\n\n//------------------------------------------------------------------------------\n// Generic function to run tests in thread/process mode\n//------------------------------------------------------------------------------\nvoid\nXrdStress::RunTest()\n{\n  if (processMode) {\n    RunTestProcesses();\n  } else {\n    RunTestThreads();\n    WaitThreads();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Run tests using threads\n//------------------------------------------------------------------------------\nvoid\nXrdStress::RunTestThreads()\n{\n  for (unsigned int i = 0; i < numChilds; i++) {\n    ChildInfo* ti = (ChildInfo*) calloc(1, sizeof(ChildInfo));\n    ti->idChild = i;\n    ti->pXrdStress = this;\n    ThreadStart(vectChilds[i], (*callback), (void*) ti);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Run tests using processes\n//------------------------------------------------------------------------------\nvoid\nXrdStress::RunTestProcesses()\n{\n  // Use pipes to send back information to parent\n  int** pipefd = (int**) calloc(numChilds, sizeof(int*));\n  int rc = 0;      // return code of all children\n\n  for (unsigned int i = 0; i < numChilds; i++) {\n    pipefd[i] = (int*) calloc(2, sizeof(int));\n\n    if (pipe(pipefd[i]) == -1) {\n      fprintf(stderr, \"error=error opening pipe\\n\");\n      exit(1);\n    }\n  }\n\n  pid_t* cpid = (pid_t*) calloc(numChilds, sizeof(pid_t));\n\n  for (unsigned int i = 0; i < numChilds; i++) {\n    cpid[i] = fork();\n\n    if (cpid[i] == -1) {\n      fprintf(stdout, \"error=error in fork()\\n\");\n      exit(1);\n    }\n\n    if (cpid[i] == 0) {   //child process\n      char writebuffer[64] = \"\\0\";\n      close(pipefd[i][0]);      //close reading end\n      ChildInfo* info = (ChildInfo*) calloc(1, sizeof(ChildInfo));\n      info->pXrdStress = this;\n      info->idChild = i;\n      info->avgRdVal = 0;\n      info->avgWrVal = 0;\n      info->avgOpenVal = 0;\n      // Call function\n      (*callback)(info);\n\n      if (opType == \"rd\") {\n        sprintf(writebuffer, \"%g %g\\n\", info->avgRdVal, info->avgOpenVal);\n      } else if (opType == \"wr\") {\n        sprintf(writebuffer, \"%g %g\\n\", info->avgWrVal, info->avgOpenVal);\n      } else if (opType == \"rdwr\") {\n        sprintf(writebuffer, \"%g %g %g\\n\", info->avgWrVal, info->avgRdVal,\n                info->avgOpenVal);\n      }\n\n      ssize_t buflen = strlen(writebuffer);\n      ssize_t wrtlen = write(pipefd[i][1], writebuffer, strlen(writebuffer));\n\n      free(info);\n      close(pipefd[i][1]);    //close writing end\n\n      if (wrtlen != buflen) {\n        fprintf(stderr, \"error=error in write(): %s\\n\", strerror(errno));\n        exit(EXIT_FAILURE);\n      }\n\n      exit(EXIT_SUCCESS);\n    }\n  }\n\n  // Parent process\n  for (unsigned int i = 0; i < numChilds; i++) {\n    char readbuffer[30];\n    close(pipefd[i][1]);     //close writing end\n    int sz = read(pipefd[i][0], readbuffer, sizeof(readbuffer));\n\n    if (sz < 0) {\n      rc |= WaitProcess(cpid[i]);  //wait child process\n      close(pipefd[i][0]);         //close reading end\n      continue;\n    }\n\n    readbuffer[sz - 1] = '\\0';\n    std::stringstream ss(std::stringstream::in | std::stringstream::out);\n\n    if (opType == \"rd\") {\n      ss << readbuffer;\n      ss >> avgRdRate[i];\n      ss >> avgOpen[i];\n    } else if (opType == \"wr\") {\n      ss << readbuffer;\n      ss >> avgWrRate[i];\n      ss >> avgOpen[i];\n    } else if (opType == \"rdwr\") {\n      ss << readbuffer;\n      ss >> avgWrRate[i];\n      ss >> avgRdRate[i];\n      ss >> avgOpen[i];\n    }\n\n    rc |= WaitProcess(cpid[i]);  //wait child process\n    close(pipefd[i][0]);         //close reading end\n  }\n\n  // Free memory\n  for (unsigned int i = 0; i < numChilds; i++) {\n    free(pipefd[i]);\n  }\n\n  free(pipefd);\n  free(cpid);\n  ComputeStatistics();\n\n  if (rc != 0) {\n    exit(EXIT_FAILURE);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Wait for a process to finish\n//------------------------------------------------------------------------------\nint\nXrdStress::WaitProcess(pid_t pid)\n{\n  int status, rc = 0;\n\n  if (waitpid(pid, &status, 0) != -1) {\n    if (WIFEXITED(status)) {\n      rc = WEXITSTATUS(status);\n\n      //------------------------------------------------------------------------\n      // Mask the following error codes:\n      //\n      // ENOENT  - Concurrent Open-truncate calls for the same file may lead\n      //           to a state where the file has been deleted,\n      //           but not yet recreated, ending in ENOENT\n      // EEXIST  - MGM with 'all.export nolock' option will reply with EEXIST\n      //           to concurrent write calls for the same file\n      // EDEADLK - MGM without the 'nolock' option will reply with EDEADLK\n      //           for concurrent write calls for the same file. The reply\n      //           comes from the XRootd server file descriptor locks.\n      //------------------------------------------------------------------------\n      if ((rc == ENOENT) || (rc == EEXIST) || (rc == EDEADLK)) {\n        rc = 0;\n      }\n\n      if (rc != 0) {\n        fprintf(stderr, \"error=child process (%d) returned error code: %d\\n\", pid, rc);\n      }\n    } else {\n      fprintf(stderr, \"error=child process (%d) exited abnormally\\n\", pid);\n      exit(EXIT_FAILURE);\n    }\n  } else {\n    fprintf(stderr, \"error=error while waiting for process: %d\\n\", pid);\n    exit(EXIT_FAILURE);\n  }\n\n  return rc;\n}\n\n//------------------------------------------------------------------------------\n// Wait for all threads to finish\n//------------------------------------------------------------------------------\nvoid\nXrdStress::WaitThreads()\n{\n  for (unsigned int i = 0; i < numChilds; i++) {\n    ChildInfo* arg;\n    pthread_join(vectChilds[i], (void**)&arg);\n    free(arg);\n  }\n\n  ComputeStatistics();\n}\n\n//------------------------------------------------------------------------------\n// Start thread executing a particular function\n//------------------------------------------------------------------------------\nint\nXrdStress::ThreadStart(pthread_t& thread, TypeFunc func, void* arg)\n{\n  return pthread_create(&thread, NULL, func, arg);\n}\n\n//------------------------------------------------------------------------------\n// Compute statistics\n//------------------------------------------------------------------------------\nvoid\nXrdStress::ComputeStatistics()\n{\n  double rd_mean, wr_mean, open_mean = 0;\n  double rd_std, wr_std;\n\n  for (unsigned int i = 0; i < numChilds; i++) {\n    open_mean += avgOpen[i];\n  }\n\n  open_mean /= numChilds;\n\n  if (opType == \"rd\") {\n    rd_std = GetStdDev(avgRdRate, rd_mean);\n    fprintf(stdout,\n            \"info=\\\"all %s read info\\\" mean=%g MB/s, stddev=%g open/s=%g \\n\",\n            childType.c_str(), rd_mean, rd_std, open_mean);\n  } else if (opType == \"wr\") {\n    wr_std = GetStdDev(avgWrRate, wr_mean);\n    fprintf(stdout,\n            \"info=\\\"all %s write info\\\" mean=%g MB/s, stddev= %g open/s=%g \\n\",\n            childType.c_str(), wr_mean, wr_std, open_mean);\n  } else if (opType == \"rdwr\") {\n    rd_std = GetStdDev(avgRdRate, rd_mean);\n    wr_std = GetStdDev(avgWrRate, wr_mean);\n    fprintf(stdout, \"info=\\\"all %s read info\\\" mean=%g MB/s stddev=%g open/s=%g \\n\",\n            childType.c_str(), rd_mean, rd_std, open_mean);\n    fprintf(stdout,\n            \"info=\\\"all %s write info\\\" mean=%g MB/s stddev= %g open/s=%g \\n\",\n            childType.c_str(), wr_mean, wr_std, open_mean);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Compute standard deviation and mean for the input provided\n//------------------------------------------------------------------------------\ndouble\nXrdStress::GetStdDev(std::vector<double>& avg, double& mean)\n{\n  double std = 0;\n  mean = 0;\n\n  for (unsigned int i = 0; i < numChilds; i++) {\n    mean += avg[i];\n  }\n\n  mean = mean / numChilds;\n\n  for (unsigned int i = 0; i < numChilds; i++) {\n    std += pow((avg[i] - mean), 2);\n  }\n\n  std /= numChilds;\n  std = sqrt(std);\n  return std;\n}\n\n//------------------------------------------------------------------------------\n// Read the names of the files in the directory\n//------------------------------------------------------------------------------\nint\nXrdStress::GetListFilenames()\n{\n  std::string file_path(\"\");\n  std::stringstream ssFileName(std::stringstream::in | std::stringstream::out);\n  DIR* dir = XrdPosixXrootd::Opendir(pathTest.c_str());\n  struct dirent* dir_entry;\n  unsigned int no = 0;\n\n  while ((dir_entry = XrdPosixXrootd::Readdir(dir)) != NULL) {\n    file_path = pathTest;\n    file_path += dir_entry->d_name;\n    ssFileName << file_path;\n    ssFileName << \" \";\n    no++;\n  }\n\n  vectFilename.clear();\n  vectFilename.reserve(no);\n\n  for (unsigned int i = 0; i < no; i++) {\n    ssFileName >> file_path;\n    vectFilename.push_back(file_path);\n  }\n\n  return no;\n}\n\n//------------------------------------------------------------------------------\n// Read procedure\n//------------------------------------------------------------------------------\nvoid*\nXrdStress::RdProc(void* arg)\n{\n  bool change = true;\n  int sample = 0;\n  double rate = 0;\n  double open_per_sec = 0;\n  off_t sizeReadFile = 0;\n  off_t total_offset = 0;\n  unsigned int count_open = 0;\n  ChildInfo* pti = static_cast<ChildInfo*>(arg);\n  XrdStress* pxt = pti->pXrdStress;\n  char* buffer = new char[pxt->sizeBlock];\n  // Initialize time structures\n  int deltaTime = DELTATIME;\n  double duration = 0;\n  struct timeval start, end;\n  struct timeval time1, time2;\n  gettimeofday(&start, NULL);\n  gettimeofday(&time1, NULL);\n  unsigned int startIndx = pti->idChild * pxt->numFiles;\n  unsigned int endIndx = (pti->idChild + 1) * pxt->numFiles;\n\n  // Loop over all files corresponding to the current thread\n  for (unsigned int indx = startIndx; indx < endIndx ; indx++) {\n    std::string urlFile = pxt->vectFilename[indx];\n    struct stat buf;\n\n    if (XrdPosixXrootd::Stat(urlFile.c_str(), &buf)) {\n      fprintf(stderr, \"error=failed stat on file: %s\\n\", urlFile.c_str());\n      delete[] buffer;\n      free(arg);\n      exit(errno);\n    }\n\n    sizeReadFile = buf.st_size;\n    count_open++;\n    int fdRead = XrdPosixXrootd::Open(urlFile.c_str(), O_RDONLY,\n                                      kXR_ur | kXR_uw | kXR_gw | kXR_gr | kXR_or);\n\n    if (fdRead < 0) {\n      fprintf(stderr, \"error=error while opening for read file=%s errno=%d\\n\",\n              urlFile.c_str(), errno);\n      delete[] buffer;\n      free(arg);\n      exit(errno);\n    }\n\n    // Read from file\n    off_t offset = 0;\n    unsigned long long noBlocks = sizeReadFile / pxt->sizeBlock;\n    size_t lastRead = sizeReadFile % pxt->sizeBlock;\n\n    for (unsigned long long i = 0 ; i < noBlocks ; i++) {\n      XrdPosixXrootd::Pread(fdRead, buffer, pxt->sizeBlock, offset);\n      offset += pxt->sizeBlock;\n    }\n\n    if (lastRead) {\n      XrdPosixXrootd::Pread(fdRead, buffer, lastRead, offset);\n      offset += lastRead;\n    }\n\n    total_offset += offset;\n\n    if (pxt->verbose) {\n      if (change) {   //true\n        gettimeofday(&time2, NULL);\n        duration = (time2.tv_sec - time1.tv_sec) + ((time2.tv_usec - time1.tv_usec) /\n                   1e6);\n\n        if (duration > deltaTime) {\n          sample++;\n          change = !change;\n          duration = (time2.tv_sec - start.tv_sec) + ((time2.tv_usec - start.tv_usec) /\n                     1e6);\n          open_per_sec = (double)count_open / duration;\n          rate = ((double)total_offset / (1024 * 1024)) / duration;\n          fprintf(stdout, \"info=\\\"read partial\\\" %s=%i step=%i mean=%g MB/s open/s=%g \\n\",\n                  pxt->childType.c_str(), pti->idChild, sample, rate, open_per_sec);\n        }\n      } else {   //false\n        gettimeofday(&time1, NULL);\n        duration = (time1.tv_sec - time2.tv_sec) + ((time1.tv_usec - time2.tv_usec) /\n                   1e6);\n\n        if (duration > deltaTime) {\n          sample++;\n          change = !change;\n          duration = (time1.tv_sec - start.tv_sec) + ((time1.tv_usec - start.tv_usec) /\n                     1e6);\n          open_per_sec = (double)count_open / duration;\n          rate = ((double)total_offset / (1024 * 1024)) / duration;\n          fprintf(stdout, \"info=\\\"read partial\\\" %s=%i step=%i mean=%g MB/s open/s=%g \\n\",\n                  pxt->childType.c_str(), pti->idChild, sample, rate, open_per_sec);\n        }\n      }\n    }\n\n    XrdPosixXrootd::Close(fdRead);\n  }\n\n  delete[] buffer;\n  // Get overall values\n  gettimeofday(&end, NULL);\n  duration = (end.tv_sec - start.tv_sec) + ((end.tv_usec - start.tv_usec) / 1e6);\n  rate = ((double)total_offset / (1024 * 1024)) / duration;\n  open_per_sec = static_cast<double>(count_open / duration);\n\n  if (pxt->verbose) {\n    fprintf(stdout, \"info=\\\"read final\\\" %s=%i  mean=%g MB/s open/s=%g \\n\",\n            pxt->childType.c_str(), pti->idChild, rate, open_per_sec);\n  }\n\n  pti->avgRdVal = rate;\n  pxt->avgRdRate[pti->idChild] = rate;\n\n  if (pti->avgOpenVal != 0) {\n    pti->avgOpenVal = (pti->avgOpenVal + open_per_sec) / 2;\n  } else {\n    pti->avgOpenVal = open_per_sec;\n  }\n\n  pxt->avgOpen[pti->idChild] = pti->avgOpenVal;\n  return arg;\n}\n\n//------------------------------------------------------------------------------\n// Write procedure\n//------------------------------------------------------------------------------\nvoid*\nXrdStress::WrProc(void* arg)\n{\n  int sample = 0;\n  bool change = true;\n  double rate = 0;\n  double open_per_sec = 0;\n  unsigned int count_open = 0;\n  off_t total_offset = 0;\n  ChildInfo* pti = static_cast<ChildInfo*>(arg);\n  XrdStress* pxt = pti->pXrdStress;\n  // Fill buffer with random characters\n  char* buffer = new char[pxt->sizeBlock];\n  std::ifstream urandom(\"/dev/urandom\", std::ios::in | std::ios::binary);\n  urandom.read(buffer, pxt->sizeBlock);\n  urandom.close();\n  // Initialize time structures\n  float duration = 0;\n  int deltaTime = DELTATIME;\n  struct timeval start, end;\n  struct timeval time1, time2;\n  gettimeofday(&start, NULL);\n  gettimeofday(&time1, NULL);\n  unsigned int startIndx = pti->idChild * pxt->numFiles;\n  unsigned int endIndx = (pti->idChild + 1) * pxt->numFiles;\n\n  // Loop over all files corresponding to the current job\n  for (unsigned int indx = startIndx; indx < endIndx ; indx++) {\n    std::string urlFile = pxt->vectFilename[indx];\n    count_open++;\n    int fdWrite = XrdPosixXrootd::Open(urlFile.c_str(), O_CREAT | O_WRONLY |\n                                       kXR_async | kXR_mkpath | kXR_open_updt | kXR_new,\n                                       kXR_ur | kXR_uw | kXR_gw | kXR_gr | kXR_or);\n\n    if (fdWrite < 0) {\n      fprintf(stderr, \"error=error while opening for write file=%s errno=%d\\n\",\n              urlFile.c_str(), errno);\n      delete[] buffer;\n      free(arg);\n      exit(errno);\n    }\n\n    // Write to file\n    size_t offset = 0;\n    unsigned long long noBlocks = pxt->sizeFile / pxt->sizeBlock;\n    size_t lastWrite = pxt->sizeFile % pxt->sizeBlock;\n\n    for (unsigned long long i = 0 ; i < noBlocks ; i++) {\n      XrdPosixXrootd::Pwrite(fdWrite, buffer, pxt->sizeBlock, offset);\n      offset += pxt->sizeBlock;\n    }\n\n    if (lastWrite) {\n      XrdPosixXrootd::Pwrite(fdWrite, buffer, lastWrite, offset);\n      offset += lastWrite;\n    }\n\n    total_offset += offset;\n\n    if (pxt->verbose) {\n      if (change) {   //true\n        gettimeofday(&time2, NULL);\n        duration = (time2.tv_sec - time1.tv_sec) + ((time2.tv_usec - time1.tv_usec) /\n                   1e6);\n\n        if (duration > deltaTime) {\n          sample++;\n          change = !change;\n          duration = (time2.tv_sec - start.tv_sec) + ((time2.tv_usec - start.tv_usec) /\n                     1e6);\n          open_per_sec = (double)count_open / duration;\n          rate = ((double)total_offset / (1024 * 1024)) / duration;\n          fprintf(stdout,\n                  \"info=\\\"write partial\\\" %s=%i step=%i mean=%g MB/s open/s=%g \\n\",\n                  pxt->childType.c_str(), pti->idChild, sample, rate, open_per_sec);\n        }\n      } else {   //false\n        gettimeofday(&time1, NULL);\n        duration = (time1.tv_sec - time2.tv_sec) + ((time1.tv_usec - time2.tv_usec) /\n                   1e6);\n\n        if (duration > deltaTime) {\n          sample++;\n          change = !change;\n          duration = (time1.tv_sec - start.tv_sec) + ((time1.tv_usec - start.tv_usec) /\n                     1e6);\n          open_per_sec = (double)count_open / duration;\n          rate = ((double)total_offset / (1024 * 1024)) / duration;\n          fprintf(stdout,\n                  \"info=\\\"write partial\\\" %s=%i step=%i mean=%g MB/s open/s=%g \\n\",\n                  pxt->childType.c_str(), pti->idChild, sample, rate, open_per_sec);\n        }\n      }\n    }\n\n    XrdPosixXrootd::Close(fdWrite);\n  }\n\n  delete[] buffer;\n  // Get overall values\n  gettimeofday(&end, NULL);\n  duration = (end.tv_sec - start.tv_sec) + ((end.tv_usec - start.tv_usec) / 1e6);\n  open_per_sec = static_cast<double>(count_open / duration);\n  rate = ((double)total_offset / (1024 * 1024)) / duration;\n\n  if (pxt->verbose) {\n    fprintf(stdout, \"info=\\\"write final\\\" %s=%i mean=%g MB/s open/s=%g \\n\",\n            pxt->childType.c_str(), pti->idChild, rate, open_per_sec);\n  }\n\n  pxt->avgWrRate[pti->idChild] = rate;\n  pti->avgWrVal = rate;\n  pti->avgOpenVal = open_per_sec;\n  pxt->avgOpen[pti->idChild] = pti->avgOpenVal;\n  return arg;\n}\n\n//------------------------------------------------------------------------------\n// Read and write procedure\n//------------------------------------------------------------------------------\nvoid*\nXrdStress::RdWrProc(void* arg)\n{\n  WrProc(arg);\n  RdProc(arg);\n  return arg;\n}\n\n//------------------------------------------------------------------------------\n// Main function\n//------------------------------------------------------------------------------\nint main(int argc, char* argv[])\n{\n  int c;\n  bool verbose = false;\n  bool process_mode = false;\n  bool concurrent_mode = false;\n  unsigned int num_jobs = 0;\n  unsigned int num_files = 0;\n  std::string sTmp(\"\");\n  std::string path(\"\");\n  std::string op_type(\"\");\n  std::string testName(\"\");\n  size_t size_block = 1024 * 1024;         //1MB\n  size_t size_file = 100 * 1024 * 1024;    //100MB  -  default values\n  std::string usage = \"Usage:  xrdstress -d <dir path>\\\n                               \\n\\t\\t -o <rd/wr/rdwr>\\\n                               \\n\\t\\t -j <num_jobs>\\\n                               \\n\\t\\t -f <num_files>\\\n                               \\n\\t\\t [-b <size_block: 1KB, 1MB>]\\\n                               \\n\\t\\t [-s <size_file: 1KB, 1MB, 1GB>]\\\n                               \\n\\t\\t [-c run in concurrent mode]\\\n                               \\n\\t\\t [-n <testName>]\\\n                               \\n\\t\\t [-v verbose]\\\n                               \\n\\t\\t [-p use processes]\\\n                               \\n\\t\\t [-h display help] \\n\";\n  const std::string arrayOp[] = {\"rd\" , \"wr\", \"rdwr\"};\n  std::set<std::string> setOp(arrayOp, arrayOp + 3);\n\n  while ((c = getopt(argc, argv, \"d:o:j:f:s:b:n:vphc\")) != -1) {\n    switch (c) {\n    case 'h': { // display help information\n      std::cout << usage << std::endl;\n      exit(1);\n    }\n\n    case 'c': { // run in concurrent mode i.e. all jobs access the same files\n      concurrent_mode = true;\n      break;\n    }\n\n    case 'j': { //no. of jobs\n      num_jobs = static_cast<unsigned int>(atoi(optarg));\n      break;\n    }\n\n    case 'd': { //directory path\n      path = optarg;\n      // Check path to see if it exists\n      XrdCl::URL url(path);\n\n      if (!url.IsValid()) {\n        std::cerr << \"URL: \" << path << \" is not valid\" << std::endl;\n        exit(1);\n      }\n\n      XrdCl::FileSystem fs(url);\n      XrdCl::StatInfo* buff = 0;\n      XrdCl::Status st = fs.Stat(url.GetPath(), buff);\n\n      if (!st.IsOK()) {\n        std::cout << \"The path requested does not exists. XRootd::stat failed.\"\n                  << std::endl\n                  << usage << std::endl;\n        exit(1);\n      }\n\n      delete buff;\n      break;\n    }\n\n    case 'o': { //operation type\n      op_type = optarg;\n\n      if (setOp.find(op_type) == setOp.end()) {\n        std::cout << \"Type of operation unknown. \" << std::endl\n                  << usage << std::endl;\n        exit(1);\n      }\n\n      break;\n    }\n\n    case 'n': { //test name\n      testName = optarg;\n      break;\n    }\n\n    case 's': { //size file\n      sTmp = optarg;\n      std::string sNo = sTmp.substr(0, sTmp.size() - 2);\n      std::string sBytes = sTmp.substr(sTmp.size() - 2);\n\n      if (sBytes == \"KB\") {\n        size_file = atoi(sNo.c_str()) * 1024;\n      } else if (sBytes == \"MB\") {\n        size_file = atoi(sNo.c_str()) * 1024 * 1024;\n      } else if (sBytes == \"GB\") {\n        size_file = atoi(sNo.c_str()) * 1024 * 1024 * 1024;\n      }\n\n      break;\n    }\n\n    case 'b': { //size block\n      sTmp = optarg;\n      std::string sNo = sTmp.substr(0, sTmp.size() - 2);\n      std::string sBytes = sTmp.substr(sTmp.size() - 2);\n\n      if (sBytes == \"KB\") {\n        size_block = atoi(sNo.c_str()) * 1024;\n      } else if (sBytes == \"MB\") {\n        size_block = atoi(sNo.c_str()) * 1024 * 1024;\n      }\n\n      break;\n    }\n\n    case 'f': { //number of files\n      num_files = atoi(optarg);\n      break;\n    }\n\n    case 'v': { //verbose mode\n      verbose = true;\n      break;\n    }\n\n    case 'p': { //run with processes or threads\n      process_mode = true;\n      std::cout << \"Don't forget to set XRD_RUNFORKHANDLER=1 in process mode\"\n                << std::endl;\n      break;\n    }\n\n    case ':': {\n      std::cout << usage << std::endl;\n      exit(1);\n    }\n    }\n  }\n\n  // If one of the critical params. is missing exit\n  if ((path == \"\") || (op_type == \"\") || (num_jobs == 0) || (num_files == 0)) {\n    std::cout << usage << std::endl;\n    exit(1);\n  }\n\n  // Generate uuid for test name if none provided\n  if (testName == \"\") {\n    uuid_t genUuid;\n    char charUuid[40];\n    uuid_generate_time(genUuid);\n    uuid_unparse(genUuid, charUuid);\n    testName = charUuid;\n  }\n\n  // Construct full path\n  if (path.rfind(\"/\") != path.size()) {\n    path += \"/\";\n  }\n\n  XrdPosixXrootd posixXrootd;  ///< xrootd posix instance\n  path += testName;\n  path += \"/\";\n  std::cout << \"Directory path = \" << path <<\n            \" using block size for operations of: \"\n            << (size_block / 1024) << \" KB\" << std::endl <<  std::endl;\n  mode_t mode = kXR_ur | kXR_uw | kXR_gw | kXR_gr | kXR_or;\n  XrdPosixXrootd::Mkdir(path.c_str(), mode);\n  XrdStress* test = new XrdStress(num_jobs, num_files,\n                                  size_block, size_file,\n                                  path, op_type, verbose,\n                                  process_mode, concurrent_mode);\n  test->RunTest();\n  delete test;\n  return 0;\n}\n"
  },
  {
    "path": "test/XrdStress.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file XrdStress.cc\n//! @author Elvin-Alin Sindrilaru - CERN\n//! @brief Class capable of doing a stress test ( read/write operations) on the\n//!        files of a directory using either threads or processes.\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*-----------------------------------------------------------------------------*/\n#include <string>\n#include <vector>\n/*-----------------------------------------------------------------------------*/\n\n#define DELTATIME 10                ///< print statistics every 10 seconds\n\ntypedef void* ( *TypeFunc )( void* );\nclass XrdStress;\n\n\n//------------------------------------------------------------------------------\n//! Struct ChildInfo\n//------------------------------------------------------------------------------\nstruct ChildInfo {\n  int        idChild;       ///< child id\n  XrdStress* pXrdStress;    ///< pointer to the test class\n  double     avgRdVal;      ///< avg read value for current thread\n  double     avgWrVal;      ///< avg write value for current thread\n  double     avgOpenVal;    ///< avg open value for current thread\n};\n\n\n//------------------------------------------------------------------------------\n//! Class XrdStress\n//------------------------------------------------------------------------------\nclass XrdStress\n{\n  public:\n\n    //--------------------------------------------------------------------------\n    //! Constructor\n    //--------------------------------------------------------------------------\n    XrdStress( unsigned int nChilds,\n               unsigned int nFiles,\n               size_t       sBlock,\n               off_t        sFile,\n               std::string  pTest,\n               std::string  op,\n               bool         verb,\n               bool         processmode,\n               bool         concurrent );\n\n\n\n    //--------------------------------------------------------------------------\n    //! Destructor\n    //--------------------------------------------------------------------------\n    ~XrdStress();\n\n\n    //--------------------------------------------------------------------------\n    //! Entry function to start running tests\n    //--------------------------------------------------------------------------\n    void RunTest();\n\n  private:\n\n    bool         verbose;        ///< verbose mode on\n    bool         processMode;    ///< run test using processes, else threads\n    bool         concurrentMode; ///< all jobs process the same files\n    off_t        sizeFile;       ///< size of file used for testing\n    size_t       sizeBlock;      ///< block size for operations (rd/wr)\n    TypeFunc     callback;       ///< type of operation executed\n    unsigned int numChilds;      ///< number of children used ( threads/processes )\n    unsigned int numFiles;       ///< number of files used for the test per child\n    std::string  pathTest;       ///< directory where the testing takes place\n    std::string  opType;         ///< type of operation ( rd/wr/rdwr )\n    std::string  childType;      ///< type or children ( threads/processes )\n\n    std::vector<double>      avgRdRate;    ///< avg read rate per child\n    std::vector<double>      avgWrRate;    ///< avg write rate per child\n    std::vector<double>      avgOpen;      ///< avg open operations per child\n    std::vector<pthread_t>   vectChilds;   ///< vector of children\n    std::vector<std::string> vectFilename; ///< vector of all files used\n\n\n    //--------------------------------------------------------------------------\n    //! Run test using threads\n    //--------------------------------------------------------------------------\n    void RunTestThreads();\n\n\n    //--------------------------------------------------------------------------\n    //! Run test using processes\n    //--------------------------------------------------------------------------\n    void RunTestProcesses();\n\n\n    //--------------------------------------------------------------------------\n    //! Wait for a process to finish and evaluate the return code\n    //!\n    //! @param pid process id\n    //!\n    //! @return rc return code of process\n    //--------------------------------------------------------------------------\n    int WaitProcess(pid_t pid);\n\n\n    //--------------------------------------------------------------------------\n    //! Wait for all threads to finish\n    //--------------------------------------------------------------------------\n    void WaitThreads();\n\n\n    //--------------------------------------------------------------------------\n    //! Start thread executing a particular function\n    //!\n    //! @param thread thread to be started\n    //! @param func function to be executed by the thread\n    //! @param arg arguments to the function\n    //!\n    //! @return 0 if successful, errno otherwise\n    //!\n    //--------------------------------------------------------------------------\n    int ThreadStart( pthread_t& thread, TypeFunc func, void* arg );\n\n\n    //--------------------------------------------------------------------------\n    //!  Compute statistics for all jobs\n    //--------------------------------------------------------------------------\n    void ComputeStatistics();\n\n\n    //--------------------------------------------------------------------------\n    //! Compute standard deviation and mean for current input\n    //!\n    //! @param avg average value (rd/wr) per child\n    //! @param mean the mean value of all jobs\n    //!\n    //! @return std. dev of all jobs\n    //!\n    //--------------------------------------------------------------------------\n    double GetStdDev( std::vector<double>& avg, double& mean );\n\n\n    //--------------------------------------------------------------------------\n    //! Read the name of the files from the directory\n    //!\n    //! @return the number of files in the directory\n    //!\n    //--------------------------------------------------------------------------\n    int GetListFilenames();\n\n\n    //--------------------------------------------------------------------------\n    //! Read procedure\n    //!\n    //! @param arg pointer to child info structure passed around\n    //!\n    //! @return pointer to updated child info structure\n    //!\n    //--------------------------------------------------------------------------\n    static void* RdProc( void* arg );\n\n\n    //--------------------------------------------------------------------------\n    //! Write procedure\n    //!\n    //! @param arg pointer to child info structure passed around\n    //!\n    //! @return pointer to updated child info structure\n    //!\n    //--------------------------------------------------------------------------\n    static void* WrProc( void* arg );\n\n\n    //--------------------------------------------------------------------------\n    //! Read-write procedure\n    //!\n    //! @param arg pointer to child info structure passed around\n    //!\n    //! @return pointer to updated child info structure\n    //!\n    //--------------------------------------------------------------------------\n    static void* RdWrProc( void* arg );\n\n};\n"
  },
  {
    "path": "test/benchmark/CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Elvin-Alin Sindrilaru - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\ninclude_directories(${CMAKE_SOURCE_DIR})\n\n#-------------------------------------------------------------------------------\n# Generate protocol buffer files\n#-------------------------------------------------------------------------------\nPROTOBUF_GENERATE_CPP(CONFIG_SRCS CONFIG_HDRS ConfigProto.proto)\nPROTOBUF_GENERATE_CPP(RESULT_SRCS RESULT_HDRS ResultProto.proto)\nset_source_files_properties(\n  ${CONFIG_SRCS} ${CONFIG_HDRS} ${RESULT_SRCS} ${RESULT_HDRS}\n  PROPERTIES GENERATED 1)\n\n#-------------------------------------------------------------------------------\n# eos-io-benchmark executable\n#-------------------------------------------------------------------------------\nadd_executable(eos-io-benchmark\n  eosbenchmark.cc     eosbenchmark.hh\n  Configuration.cc    Configuration.hh\n  DirEos.cc           DirEos.hh\n  FileEos.cc          FileEos.hh\n  Result.cc           Result.hh\n  ProtoIo.cc          ProtoIo.hh\n  ${CONFIG_SRCS}      ${CONFIG_HDRS}\n  ${RESULT_SRCS}      ${RESULT_HDRS}\n  ${CMAKE_SOURCE_DIR}/common/StringConversion.cc\n  ${CMAKE_SOURCE_DIR}/common/StringConversion.hh\n  ${CMAKE_SOURCE_DIR}/fst/io/FileIoPlugin.cc)\n\ntarget_link_libraries(eos-io-benchmark PRIVATE\n  EosFstIo\n  XROOTD::SERVER\n  ${CMAKE_THREAD_LIBS_INIT})\n\ntarget_compile_definitions(eos-io-benchmark PUBLIC\n  -D_LARGE_FILE_SOURCE -D_LARGE64_FILE_SOURCE -D_FILE_OFFSET_BITS=64)\n\ninstall(TARGETS eos-io-benchmark\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR})\n"
  },
  {
    "path": "test/benchmark/ConfigProto.proto",
    "content": "syntax = \"proto2\";\npackage eos.benchmark;\n\n//------------------------------------------------------------------------------\n// Configuration which holds all the possible parameters for a benchmark run \n//------------------------------------------------------------------------------\n\nmessage ConfigProto {\n\t\n  // Type of operation requested \n  enum OperationType {\n    NOTYPE   = 0;\n    WRITE    = 1;\n    READ_GW  = 2;\n    READ_PIO = 3;\n    RDWR_GW  = 4;\n    RDWR_PIO = 5;\n  }\t\n\n  // Access mode options\n  enum AccessMode {\n    CONCURRENT = 0;\n    PARALLEL   = 1;\n  }\n\n  // Operation pattern type\n  enum PatternType {\n    NOPATTERN = 0;\n    FULL      = 1;\n    RANDOM    = 2;\n  }\t\n\n  // Type of job to be launched\n  enum JobType {\n    THREAD  = 0;\n    PROCESS = 1;\n  }\n\n  // Type of file layout \n  enum FileLayoutType {\n    NOLAYOUT = 0;\n    PLAIN    = 1;\n    REPLICA  = 2;\n    ARCHIVE  = 3;\n    RAIDDP   = 4;\n    RAID6    = 5;\n  }\n\n  required string         benchmarkInstance = 1; \n  required string         benchmarkDir      = 2;\n  required uint64         fileSize          = 3;\n  required uint32         numFiles          = 4;\n  required uint32         blockSize         = 5;\n  required OperationType  operation         = 6;\n  required FileLayoutType fileLayout        = 7;\n  optional uint32         noReplicas        = 8 [default=1];\n  required JobType        jobType           = 9;\n  required uint32         numJobs           = 10;\n  optional AccessMode     access            = 11 [default=PARALLEL];\n  optional PatternType    pattern           = 12 [default=FULL];\n  repeated uint64         offset            = 13 [packed=true];\n  repeated uint32         length            = 14 [packed=true];\n}  \n \n"
  },
  {
    "path": "test/benchmark/Configuration.cc",
    "content": "//------------------------------------------------------------------------------\n// File: Configuration.cc\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include <cstdint>\n#include <fstream>\n#include <functional>\n#include <iomanip>\n#include <iostream>\n#include <sstream>\n#include <string>\n/*----------------------------------------------------------------------------*/\n#include <uuid/uuid.h>\n/*----------------------------------------------------------------------------*/\n#include \"Configuration.hh\"\n#include \"DirEos.hh\"\n#include \"ProtoIo.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/utils/RandUtils.hh\"\n/*----------------------------------------------------------------------------*/\n\nusing namespace std;\n\nEOSBMKNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nConfiguration::Configuration():\n  eos::common::LogId()\n{\n  mPbConfig = new ConfigProto();\n}\n\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nConfiguration::~Configuration()\n{\n  mFileNames.clear();\n\n  if (mPbConfig)\n  {\n    delete mPbConfig;\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Set the low level configuration object taking the ownership\n//------------------------------------------------------------------------------\nvoid\nConfiguration::SetPbConfig(ConfigProto* pbConfig)\n{\n  if (mPbConfig) delete mPbConfig;\n\n  mPbConfig = pbConfig;\n}\n\n\n//------------------------------------------------------------------------------\n// Get the low level configuration object (ProtBuf object)\n//------------------------------------------------------------------------------\nConfigProto&\nConfiguration::GetPbConfig() const\n{\n  return *mPbConfig;\n}\n\n\n//------------------------------------------------------------------------------\n// Generate file names used for the write operations\n//------------------------------------------------------------------------------\nvoid\nConfiguration::GenerateFileNames()\n{\n  string gen_filename;\n  uuid_t gen_uuid;\n  char char_uuid[40];\n  uint32_t num_files = mPbConfig->numfiles();\n  uint32_t num_jobs = mPbConfig->numjobs();\n\n  if (mPbConfig->access() == ConfigProto_AccessMode_CONCURRENT)\n  {\n    // All jobs access the same files\n    mFileNames.reserve(num_files);\n\n    for (uint32_t i = 0; i < num_files; i++)\n    {\n      uuid_generate_time(gen_uuid);\n      uuid_unparse(gen_uuid, char_uuid);\n      gen_filename = mPbConfig->benchmarkdir();\n      gen_filename += char_uuid;\n      mFileNames.push_back(gen_filename);\n    }\n  }\n  else if (mPbConfig->access() == ConfigProto_AccessMode_PARALLEL)\n  {\n    // Each job gets a separate set of files on which it works\n    mFileNames.reserve(num_jobs * num_files);\n\n    for (uint32_t i = 0; i < num_jobs; i++)\n    {\n      for (uint32_t j = 0; j < num_files; j++)\n      {\n        uuid_generate_time(gen_uuid);\n        uuid_unparse(gen_uuid, char_uuid);\n        gen_filename = mPbConfig->benchmarkdir();\n        gen_filename += char_uuid;\n        mFileNames.push_back(gen_filename);\n      }\n    }\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Print configuration\n//------------------------------------------------------------------------------\nvoid\nConfiguration::Print()\n{\n  std::stringstream sstr;\n  sstr << left << setw(190) << setfill('*') << \"\" << endl;\n  std::string star_line = sstr.str();\n  sstr.str(\"\");\n  sstr << left << setw(190) << setfill('-') << \"\" << endl;\n  std::string minus_line =  sstr.str();\n  cout << endl << star_line\n       << setw(100) << right << \"C o n f i g u r a t i o n\" << endl\n       << star_line\n       << setw(30) << left << setfill('.') <<  \"EOS instance\" << setfill(' ')\n       << setw(40) << left << mPbConfig->benchmarkinstance()\n       << setw(30) << left << setfill('.') << \"Test path\" << setfill(' ')\n       << setw(40) << left << mPbConfig->benchmarkdir() << endl\n       << setw(30) << left << setfill('.') << \"File size\" << setfill(' ')\n       << setw(40) << left\n       << eos::common::StringConversion::GetPrettySize(mPbConfig->filesize())\n       << setw(30) << left << setfill('.') << \"Block size\" << setfill(' ')\n       << setw(40) << left\n       << eos::common::StringConversion::GetPrettySize(mPbConfig->blocksize())\n       << endl\n       << setw(30) << left << setfill('.') << \"File layout\" << setfill(' ')\n       << setw(40) << left << GetFileLayout(mPbConfig->filelayout())\n       << setw(30) << left << setfill('.') << \"Number of files\" << setfill(' ')\n       << setw(40) << left << mPbConfig->numfiles() << endl\n       << setw(30) << left << setfill('.') << \"Job type\" << setfill(' ')\n       << setw(40) << left << (mPbConfig->jobtype() ? \"process\" : \"thread\")\n       << setw(30) << left << setfill('.') << \"Number of jobs\" << setfill(' ')\n       << setw(40) << left << mPbConfig->numjobs() << endl\n       << setw(30) << left << setfill('.') << \"Operation\" << setfill(' ')\n       << setw(40) << left << GetOperation(mPbConfig->operation())\n       << setw(30) << left << setfill('.') << \"Access mode\" << setfill(' ')\n       << setw(40) << left << (mPbConfig->access() ? \"parallel\" : \"concurrent\")\n       << endl\n       << setw(30) << left << setfill('.') << \"Read pattern\" << setfill(' ')\n       << setw(40) << left << GetPattern(mPbConfig->pattern()) << endl;\n\n  if (mPbConfig->pattern() == ConfigProto_PatternType_RANDOM)\n  {\n    cout << \"Number of requests: \" << mPbConfig->offset_size() << endl;\n    cout << \"Requests (offset, length): \" << endl;\n\n    for (int i = 0; i < mPbConfig->offset_size(); i++)\n    {\n      cout << \"( \" << left << setw(10) << mPbConfig->offset(i)\n           << \",\"  << left << setw(10) << mPbConfig->length(i) << \" )   \";\n\n      if ((i + 1) % 4 == 0) cout << endl;\n    }\n\n    cout << endl;\n  }\n\n  cout << minus_line << endl << endl;\n}\n\n\n//------------------------------------------------------------------------------\n// Check whether directory path exists and has correct attributes, if it does\n// not exist then it is created and set the correct attributes.\n//------------------------------------------------------------------------------\nbool\nConfiguration::CheckDirAndFiles()\n{\n  bool ret = true;\n  DirEos* dir = new DirEos(mPbConfig->benchmarkdir(),\n                           mPbConfig->benchmarkinstance());\n\n  // Check if directory exists and if not create it\n  if (!dir->Exist())\n  {\n    if (!dir->Create())\n    {\n      eos_err(\"Could not create working directory\");\n      ret = false;\n    }\n\n    // Set directory attributes to match the required configuration\n    if (!dir->SetConfig(*mPbConfig))\n    {\n      eos_err(\"Error while trying to set attributes to the dir\");\n      ret = false;\n    }\n  }\n  else if (!dir->MatchConfig(*mPbConfig))\n  {\n    ret = false;\n  }\n\n  if (!ret)\n  {\n    delete dir;\n    return ret;\n  }\n\n  // If operation is read only then we have to check that we have enough files\n  // in the benchmark directoy, if not we abort\n  if ((mPbConfig->operation() == ConfigProto_OperationType_READ_GW) ||\n      (mPbConfig->operation() == ConfigProto_OperationType_READ_PIO))\n  {\n    uint32_t required_files = 0;\n    mFileNames = dir->GetMatchingFiles(mPbConfig->filesize());\n\n    if (mPbConfig->access() == ConfigProto_AccessMode_CONCURRENT)\n    {\n      required_files = mPbConfig->numfiles();\n    }\n    else if (mPbConfig->access() == ConfigProto_AccessMode_PARALLEL)\n    {\n      required_files = mPbConfig->numjobs() * mPbConfig->numfiles();\n    }\n\n    if (mFileNames.size() <= required_files)\n    {\n      eos_err(\"Not enough files in dir for the read operation\");\n      ret = false;\n    }\n  }\n  else\n  {\n    // Generate the file names used for the benchmark run\n    GenerateFileNames();\n  }\n\n  delete dir;\n  return ret;\n}\n\n\n//------------------------------------------------------------------------------\n// Create configuration file - accept input from the console and build up the\n// configuration object which is then written to the file supplied as an arg.\n//------------------------------------------------------------------------------\nbool\nConfiguration::CreateConfigFile(const string& outputFile)\n{\n  string input_value = \"\";\n  uint64_t block_size;\n  ConfigProto_OperationType op_type;\n\n  // Get benchmark instance\n  cout << \"Benchmarked instance: \";\n\n  while (input_value.empty())\n  {\n    if (!getline(cin, input_value))\n    {\n      return false;\n    }\n  }\n\n  mPbConfig->set_benchmarkinstance(input_value);\n\n  // Get benchmark directory where operations are done\n  input_value = \"\";\n  cout << \"Benchmark directory: \";\n\n  while (input_value.empty())\n  {\n    if (!getline(cin, input_value))\n    {\n      return false;\n    }\n  }\n\n  // Make sure that the directory ends with one \"/\"\n  if (*input_value.rbegin() != '/')\n  {\n    input_value += \"/\";\n  }\n\n  mPbConfig->set_benchmarkdir(input_value);\n\n  // Get the file size\n  while (1)\n  {\n    size_t pos;\n    uint64_t file_size = 0;\n    input_value = \"\";\n    cout << \"File size (KB|MB|GB): \";\n\n    while (input_value.empty())\n    {\n      if (!getline(cin, input_value))\n      {\n        return false;\n      }\n    }\n\n    if ((pos = input_value.rfind(\"KB\")) != string::npos)\n    {\n      // File size given in KB\n      input_value = input_value.erase(pos);\n      file_size = eos::common::KB;\n    }\n    else if ((pos = input_value.rfind(\"MB\")) != string::npos)\n    {\n      // File size given in MB\n      input_value = input_value.erase(pos);\n      file_size = eos::common::MB;\n    }\n    else if ((pos = input_value.rfind(\"GB\")) != string::npos)\n    {\n      // File size given in GB\n      input_value = input_value.erase(pos);\n      file_size = eos::common::GB;\n    }\n    else\n    {\n      cout << \"Input value is invalid! \" << endl;\n      continue;\n    }\n\n    char* pEnd;\n    float size;\n\n    if ((size = strtol(input_value.c_str(), &pEnd, 10)) == 0L)\n    {\n      cout << \"Input value is invalid! \" << endl;\n      continue;\n    }\n\n    file_size *= size;\n    mPbConfig->set_filesize(file_size);\n    break;\n  }\n\n  // Get the number of files\n  while (1)\n  {\n    input_value = \"\";\n    cout << \"Number of files: \";\n\n    while (input_value.empty())\n    {\n      if (!getline(cin, input_value))\n      {\n        return false;\n      }\n    }\n\n    char* pEnd;\n    uint32_t no_files;\n\n    if ((no_files = strtol(input_value.c_str(), &pEnd, 10)) == 0L)\n    {\n      cout << \"Input value is invalid! \" << endl;\n      continue;\n    }\n\n    mPbConfig->set_numfiles(no_files);\n    break;\n  }\n\n  // Get block size for rd/wr operations\n  while (1)\n  {\n    size_t pos;\n    input_value = \"\";\n    cout << \"Block size (KB|MB|GB): \";\n\n    while (input_value.empty())\n    {\n      if (!getline(cin, input_value))\n      {\n        return false;\n      }\n    }\n\n    if ((pos = input_value.rfind(\"KB\")) != string::npos)\n    {\n      // File size given in KB\n      input_value = input_value.erase(pos);\n      block_size = eos::common::KB;\n    }\n    else if ((pos = input_value.rfind(\"MB\")) != string::npos)\n    {\n      // File size given in MB\n      input_value = input_value.erase(pos);\n      block_size = eos::common::MB;\n    }\n    else if ((pos = input_value.rfind(\"GB\")) != string::npos)\n    {\n      // File size given in GB\n      input_value = input_value.erase(pos);\n      block_size = eos::common::GB;\n    }\n    else\n    {\n      cout << \"Input value is invalid! \" << endl;\n      continue;\n    }\n\n    char* pEnd;\n    uint64_t size;\n\n    if ((size = strtol(input_value.c_str(), &pEnd, 10)) == 0L)\n    {\n      cout << \"Input value is invalid! \" << endl;\n      continue;\n    }\n\n    block_size *= size;\n    mPbConfig->set_blocksize(block_size);\n    break;\n  }\n\n  // Get file layout for benchmark\n  while (1)\n  {\n    ConfigProto_FileLayoutType file_type;\n    input_value = \"\";\n    cout << \"File layout (plain|replica|raiddp|raid6|archive): \";\n\n    while (input_value.empty())\n    {\n      if (!getline(cin, input_value))\n      {\n        return false;\n      }\n    }\n\n    file_type = Configuration::GetFileLayout(input_value);\n\n    if (file_type == ConfigProto_FileLayoutType_NOLAYOUT)\n    {\n      cout << \"Input value is invalid!\" << endl;\n      continue;\n    }\n\n    mPbConfig->set_filelayout(file_type);\n    break;\n  }\n\n  // For the replica type of layaout get the number of replicas\n  if (mPbConfig->filelayout() == ConfigProto_FileLayoutType_REPLICA)\n  {\n    while (1)\n    {\n      input_value = \"\";\n      cout << \"Number of replicas: \";\n\n      while (input_value.empty())\n      {\n        if (!getline(cin, input_value))\n        {\n          return false;\n        }\n      }\n\n      char* pEnd;\n      uint32_t no_replicas;\n\n      if ((no_replicas = strtol(input_value.c_str(), &pEnd, 10)) == 0L)\n      {\n        cout << \"Input value is invalid! \" << endl;\n        continue;\n      }\n\n      mPbConfig->set_noreplicas(no_replicas);\n      break;\n    }\n  }\n\n  // Get type of execution task\n  while (1)\n  {\n    ConfigProto_JobType job_type;\n    input_value = \"\";\n    cout << \"Execution type (thread|process): \";\n\n    while (input_value.empty())\n    {\n      if (!getline(cin, input_value))\n      {\n        return false;\n      }\n    }\n\n    if (input_value.compare(\"thread\") == 0)\n    {\n      job_type = ConfigProto_JobType_THREAD;\n    }\n    else if (input_value.compare(\"process\") == 0)\n    {\n      job_type = ConfigProto_JobType_PROCESS;\n    }\n    else\n    {\n      cout << \"Input value is invalid!\" << endl;\n      continue;\n    }\n\n    mPbConfig->set_jobtype(job_type);\n    break;\n  }\n\n  // Get the number of jobs to be launched (threads/processes)\n  while (1)\n  {\n    input_value = \"\";\n    cout << \"Number of jobs: \";\n\n    while (input_value.empty())\n    {\n      if (!getline(cin, input_value))\n      {\n        return false;\n      }\n    }\n\n    char* pEnd;\n    uint32_t no_jobs;\n\n    if ((no_jobs = strtol(input_value.c_str(), &pEnd, 10)) == 0L)\n    {\n      cout << \"Input value is invalid! \" << endl;\n      continue;\n    }\n\n    mPbConfig->set_numjobs(no_jobs);\n    break;\n  }\n\n  // Get operation type\n  while (1)\n  {\n    input_value = \"\";\n    cout << \"Operation (write|read_gw|read_pio|rdwr_gw|rdwr_pio): \";\n\n    while (input_value.empty())\n    {\n      if (!getline(cin, input_value))\n      {\n        return false;\n      }\n    }\n\n    op_type = Configuration::GetOperation(input_value);\n\n    if (op_type == ConfigProto_OperationType_NOTYPE)\n    {\n      cout << \"Input value is invalid!\" << endl;\n      continue;\n    }\n\n    mPbConfig->set_operation(op_type);\n    break;\n  }\n\n  // Get pattern type and generate set of random requests if needed\n  if (op_type != ConfigProto_OperationType_WRITE)\n  {\n    while (1)\n    {\n      ConfigProto_PatternType pattern_type;\n      input_value = \"\";\n      cout << \"Read pattern (full|random): \";\n\n      while (input_value.empty())\n      {\n        if (!getline(cin, input_value))\n        {\n          return false;\n        }\n      }\n\n      pattern_type = Configuration::GetPattern(input_value);\n\n      if (pattern_type == ConfigProto_PatternType_NOPATTERN)\n      {\n        cout << \"Input value is invalid!\" << endl;\n        continue;\n      }\n\n      mPbConfig->set_pattern(pattern_type);\n\n      // Generate a set of random (offset, length) pairs\n      uint64_t offset;\n      uint64_t length;\n\n      if (pattern_type == ConfigProto_PatternType_RANDOM)\n      {\n        uint32_t no_requests;\n\n        while (1)\n        {\n          input_value = \"\";\n          cout << \"Number of requests: \";\n\n          while (input_value.empty())\n          {\n            if (!getline(cin, input_value))\n            {\n              return false;\n            }\n          }\n\n          char* pEnd;\n\n          if ((no_requests = strtol(input_value.c_str(), &pEnd, 10)) == 0L)\n          {\n            cout << \"Input value is invalid! \" << endl;\n            continue;\n          }\n\n          break;\n        }\n\n        // Generate randomly the read requests\n        uint64_t file_size = mPbConfig->filesize();\n\n        for (uint32_t i = 0; i < no_requests; i++)\n        {\n          offset = eos::common::getRandom<uint64_t>(1, file_size);\n          length = eos::common::getRandom<uint64_t>(1, file_size - offset);\n          mPbConfig->add_offset(offset);\n          mPbConfig->add_length(length);\n        }\n      }\n\n      break;\n    }\n  }\n\n  // If multiple jobs then decide on the type of access\n  if (mPbConfig->numjobs() > 1)\n  {\n    // Get the type of access (parallel/concurrent)\n    // parallel - no two jobs access the same file\n    // concurrent - all jobs access the same files\n    while (1)\n    {\n      ConfigProto_AccessMode access_type;\n      input_value = \"\";\n      cout << \"Access type (parallel/concurrent): \";\n\n      while (input_value.empty())\n      {\n        if (!getline(cin, input_value))\n        {\n          return false;\n        }\n      }\n\n      if (input_value.compare(\"parallel\") == 0)\n      {\n        access_type = ConfigProto_AccessMode_PARALLEL;\n      }\n      else if (input_value.compare(\"concurrent\") == 0)\n      {\n        access_type = ConfigProto_AccessMode_CONCURRENT;\n      }\n      else\n      {\n        cout << \"Input value is invalid!\" << endl;\n        continue;\n      }\n\n      mPbConfig->set_access(access_type);\n      break;\n    }\n  }\n\n  // Write the configuration to the supplied output file\n  ProtoWriter writer(outputFile);\n  if (!writer(*mPbConfig))\n  {\n    cout << \"Error while writing configuration to file\" << endl;\n    return false;\n  }\n\n  return true;\n}\n\n\n//------------------------------------------------------------------------------\n// Read in configuration from file\n//------------------------------------------------------------------------------\nbool\nConfiguration::ReadFromFile(const string& fileName)\n{\n  ProtoReader reader(fileName);\n  ConfigProto* tmp_conf = reader.ReadNext<ConfigProto>();\n\n  if (tmp_conf)\n  {\n    SetPbConfig(tmp_conf);\n    return true;\n  }\n\n  return false;\n}\n\n\n//------------------------------------------------------------------------------\n// Get string representation for the file layout\n//------------------------------------------------------------------------------\nstring\nConfiguration::GetFileLayout(ConfigProto_FileLayoutType fileType)\n{\n  if (fileType == ConfigProto_FileLayoutType_PLAIN)   return \"plain\";\n\n  if (fileType == ConfigProto_FileLayoutType_REPLICA) return \"replica\";\n\n  if (fileType == ConfigProto_FileLayoutType_RAIDDP)  return \"raiddp\";\n\n  if (fileType == ConfigProto_FileLayoutType_RAID6)   return \"raid6\";\n\n  if (fileType == ConfigProto_FileLayoutType_ARCHIVE) return \"archive\";\n\n  return \"\";\n}\n\n\n//------------------------------------------------------------------------------\n// Get int representation for the file layout\n//------------------------------------------------------------------------------\nConfigProto_FileLayoutType\nConfiguration::GetFileLayout(const string& fileType)\n{\n  if (fileType.compare(\"plain\") == 0)    return ConfigProto_FileLayoutType_PLAIN;\n\n  if (fileType.compare(\"replica\") == 0)  return ConfigProto_FileLayoutType_REPLICA;\n\n  if (fileType.compare(\"raiddp\") == 0)   return ConfigProto_FileLayoutType_RAIDDP;\n\n  if (fileType.compare(\"raid6\") == 0)    return ConfigProto_FileLayoutType_RAID6;\n\n  if (fileType.compare(\"archive\") == 0)  return ConfigProto_FileLayoutType_ARCHIVE;\n\n  return ConfigProto_FileLayoutType_NOLAYOUT;\n}\n\n\n//------------------------------------------------------------------------------\n// Get string representation for the operation layout\n//------------------------------------------------------------------------------\nstring\nConfiguration::GetOperation(ConfigProto_OperationType opType)\n{\n  if (opType == ConfigProto_OperationType_WRITE)    return \"write\";\n\n  if (opType == ConfigProto_OperationType_READ_GW)  return \"read_gw\";\n\n  if (opType == ConfigProto_OperationType_READ_PIO) return \"read_pio\";\n\n  if (opType == ConfigProto_OperationType_RDWR_GW)  return \"rdwr_gw\";\n\n  if (opType == ConfigProto_OperationType_RDWR_PIO) return \"rdwr_pio\";\n\n  return \"\";\n}\n\n\n//------------------------------------------------------------------------------\n// Get int representation for the operation type\n//------------------------------------------------------------------------------\nConfigProto_OperationType\nConfiguration::GetOperation(const string& opType)\n{\n  if (opType.compare(\"write\") == 0)    return ConfigProto_OperationType_WRITE;\n\n  if (opType.compare(\"read_gw\") == 0)  return ConfigProto_OperationType_READ_GW;\n\n  if (opType.compare(\"read_pio\") == 0) return ConfigProto_OperationType_READ_PIO;\n\n  if (opType.compare(\"rdwr_gw\") == 0)  return ConfigProto_OperationType_RDWR_GW;\n\n  if (opType.compare(\"rdwr_pio\") == 0) return ConfigProto_OperationType_RDWR_PIO;\n\n  return ConfigProto_OperationType_NOTYPE;\n}\n\n\n//------------------------------------------------------------------------------\n// Get string representation for the pattern type\n//------------------------------------------------------------------------------\nstring\nConfiguration::GetPattern(ConfigProto_PatternType patternType)\n{\n  if (patternType == ConfigProto_PatternType_FULL) return \"full\";\n\n  if (patternType == ConfigProto_PatternType_RANDOM) return \"random\";\n\n  return \"\";\n}\n\n\n//------------------------------------------------------------------------------\n// Get int representation for the pattern type\n//------------------------------------------------------------------------------\nConfigProto_PatternType\nConfiguration::GetPattern(const string& patternType)\n{\n  if (patternType.compare(\"full\") == 0)    return ConfigProto_PatternType_FULL;\n\n  if (patternType.compare(\"random\") == 0)  return ConfigProto_PatternType_RANDOM;\n\n  return ConfigProto_PatternType_NOPATTERN;\n}\n\n\n//------------------------------------------------------------------------------\n// Compute hash value for the current object as the hash value of a string\n// made up by concatenating some of the fields of the current object\n//------------------------------------------------------------------------------\nsize_t\nConfiguration::GetHash()\n{\n  size_t hash_value;\n  std::stringstream sstr;\n  sstr << mPbConfig->filesize() << mPbConfig->numfiles() << mPbConfig->blocksize()\n       << mPbConfig->operation() << mPbConfig->filelayout() << mPbConfig->noreplicas()\n       << mPbConfig->jobtype() << mPbConfig->numjobs() << mPbConfig->access()\n       << mPbConfig->pattern();\n\n  if (mPbConfig->pattern() == ConfigProto_PatternType_RANDOM)\n  {\n    for (int32_t i = 0; i < mPbConfig->offset_size(); i++)\n    {\n      sstr << mPbConfig->offset(i) << mPbConfig->length(i);\n    }\n  }\n\n  hash_value = std::hash<std::string>()(sstr.str());\n  return hash_value;\n}\n\n\nEOSBMKNAMESPACE_END\n"
  },
  {
    "path": "test/benchmark/Configuration.hh",
    "content": "//------------------------------------------------------------------------------\n// File: Configuration.hh\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSBMK_CONFIGURATION_HH__\n#define __EOSBMK_CONFIGURATION_HH__\n\n/*----------------------------------------------------------------------------*/\n#include \"Namespace.hh\"\n#include \"common/Logging.hh\"\n#include \"test/benchmark/ConfigProto.pb.h\"\n/*----------------------------------------------------------------------------*/\n\nEOSBMKNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class Configuration - extends the ConfigProto class obtained from compiling\n//! the protocol buffer ConfigProto.proto by adding additional methods for\n//! handling the information in such a configuration.\n//------------------------------------------------------------------------------\nclass Configuration: public eos::common::LogId\n{\n  public:\n\n    //--------------------------------------------------------------------------\n    //! Constructor\n    //--------------------------------------------------------------------------\n    Configuration();\n\n\n    //--------------------------------------------------------------------------\n    //! Destructor\n    //--------------------------------------------------------------------------\n    virtual ~Configuration();\n\n\n    //--------------------------------------------------------------------------\n    //! Disable copy constructor\n    //--------------------------------------------------------------------------\n    Configuration(const Configuration&) = delete;\n\n\n    //--------------------------------------------------------------------------\n    //! Disable copy operator\n    //--------------------------------------------------------------------------\n    Configuration& operator =(const Configuration&) = delete;\n\n\n    //--------------------------------------------------------------------------\n    //! Set the low level configuration object\n    //!\n    //! @param pbConfig low level config object (ProtoBuf object)\n    //!\n    //--------------------------------------------------------------------------\n    void SetPbConfig(ConfigProto* pbConfig);\n\n\n    //--------------------------------------------------------------------------\n    //! Get the low level configuration object (ProtBuf object)\n    //!\n    //! @return low level config object\n    //!\n    //--------------------------------------------------------------------------\n    ConfigProto& GetPbConfig() const;\n\n\n    //--------------------------------------------------------------------------\n    //! Print configuration saved in the supplied file\n    //--------------------------------------------------------------------------\n    void Print();\n\n\n    //--------------------------------------------------------------------------\n    //! Check directory path exists and has correct attributes, if it does not\n    //! exist then it is created and set the correct attributes. Also make sure\n    //! that the required files for the operations are in place and if not they\n    //! are generated.\n    //!\n    //! @return true if setup successful, otherwise false\n    //!\n    //--------------------------------------------------------------------------\n    bool CheckDirAndFiles();\n\n\n    //--------------------------------------------------------------------------\n    //! Create configuration file - accept input from the console and build up\n    //! the configuration object which is then written to the file supplied as\n    //! an arg.\n    //!\n    //! @param outputFile file to which the newly configuration is saved\n    //!\n    //! @return true if successful, otherwise false\n    //!\n    //--------------------------------------------------------------------------\n    bool CreateConfigFile(const std::string& outputFile);\n\n\n    //--------------------------------------------------------------------------\n    //! Read in configuration from file\n    //!\n    //! @param fileName file from which the configuration is read\n    //!\n    //! @return true if successful, otherwise false\n    //!\n    //--------------------------------------------------------------------------\n    bool ReadFromFile(const std::string& fileName);\n\n\n    //--------------------------------------------------------------------------\n    //! Get int representation for the pattern type\n    //!\n    //! @parameter patternType string representation of the pattern type\n    //!\n    //! @return int representation of the pattern type\n    //!\n    //--------------------------------------------------------------------------\n    static ConfigProto_PatternType GetPattern(const std::string& patternType);\n\n\n    //--------------------------------------------------------------------------\n    //! Get string representation for the pattern type\n    //!\n    //! @param patternType int representation of the pattern type\n    //!\n    //! @return string representation of the pattern type\n    //!\n    //--------------------------------------------------------------------------\n    static std::string GetPattern(ConfigProto_PatternType patternType);\n\n\n    //--------------------------------------------------------------------------\n    //! Get int representation for the operation type\n    //!\n    //! @param opType string representation of the operation type\n    //!\n    //! @return int representation of the operation type\n    //!\n    //--------------------------------------------------------------------------\n    static ConfigProto_OperationType GetOperation(const std::string& opType);\n\n\n    //--------------------------------------------------------------------------\n    //! Get string representation for the operation layout\n    //!\n    //! @param opType int representation of the operation type\n    //!\n    //! @return string representation of the operation type\n    //!\n    //--------------------------------------------------------------------------\n    static std::string GetOperation(ConfigProto_OperationType opType);\n\n\n    //--------------------------------------------------------------------------\n    //! Get int representation for the file layout\n    //!\n    //! @param fileType string representation of the file type\n    //!\n    //! @return int representation of the file type\n    //!\n    //--------------------------------------------------------------------------\n    static ConfigProto_FileLayoutType GetFileLayout(const std::string& fileType);\n\n\n    //--------------------------------------------------------------------------\n    //! Get string representation for the file layout\n    //!\n    //! @param fileType int representation of the file type\n    //!\n    //! @return string representation of the file type\n    //!\n    //--------------------------------------------------------------------------\n    static std::string GetFileLayout(ConfigProto_FileLayoutType fileType);\n\n\n    //--------------------------------------------------------------------------\n    //! Generate the absolute path to the files which will be used by the\n    //! threads/processes during the run\n    //--------------------------------------------------------------------------\n    void GenerateFileNames();\n\n\n    //--------------------------------------------------------------------------\n    //! Compute hash value for the current object as the hash value of a string\n    //! made up by concatenating some of the fields of the current object\n    //!\n    //! @return hash value\n    //!\n    //--------------------------------------------------------------------------\n    size_t GetHash();\n\n\n    //--------------------------------------------------------------------------\n    //! Get the file name at position indx from the vector of generated files\n    //!\n    //! @param indx index in the vector of file names\n    //!\n    //! @return file name\n    //!\n    //--------------------------------------------------------------------------\n    inline std::string GetFileName(uint32_t indx) const\n    {\n      return mFileNames[indx];\n    };\n\n  private:\n\n    std::vector<std::string> mFileNames; ///! generated file names\n    ConfigProto* mPbConfig;  ///< low level ConfigProto class holding all the info\n\n};\n\nEOSBMKNAMESPACE_END\n\n#endif // __EOSBMK_CONFIGURATION_HH__\n"
  },
  {
    "path": "test/benchmark/DirEos.cc",
    "content": "//------------------------------------------------------------------------------\n// File: DirEos.cc\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*----------------------------------------------------------------------------*/\n#include \"DirEos.hh\"\n#include \"Configuration.hh\"\n/*----------------------------------------------------------------------------*/\n#include \"common/LayoutId.hh\"\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdSfs/XrdSfsInterface.hh>\n/*----------------------------------------------------------------------------*/\n\n\nEOSBMKNAMESPACE_BEGIN\n\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nDirEos::DirEos(const std::string& dirPath, const std::string& eosInstance):\n  eos::common::LogId(),\n  mDirPath(dirPath),\n  mFs(NULL)\n{\n  XrdCl::URL url(eosInstance);\n\n  if (!url.IsValid()) {\n    eos_err(\"URL is not valid\");\n    exit(-1);\n  }\n\n  mFs = new XrdCl::FileSystem(url);\n\n  if (!mFs) {\n    eos_err(\"Error while trying to get XrdCl::FileSystem object\");\n    exit(-1);\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nDirEos::~DirEos()\n{\n  delete mFs;\n}\n\n\n//------------------------------------------------------------------------------\n// Check if directory exists\n//------------------------------------------------------------------------------\nbool\nDirEos::Exist()\n{\n  bool ret = true;\n  std::string request;\n  XrdCl::Buffer arg;\n  XrdCl::Buffer* response = 0;\n  request = mDirPath;\n  request += \"?\";\n  request += \"mgm.pcmd=stat\";\n  arg.FromString(request);\n  XrdCl::XRootDStatus status = mFs->Query(XrdCl::QueryCode::OpaqueFile, arg,\n                                          response);\n\n  if (status.IsOK()) {\n    unsigned long long sval[10];\n    unsigned long long ival[6];\n    char tag[1024];\n    int items = sscanf(response->GetBuffer(),\n                       \"%s %llu %llu %llu %llu %llu %llu %llu %llu \"\n                       \"%llu %llu %llu %llu %llu %llu %llu %llu\",\n                       tag, (unsigned long long*) &sval[0],\n                       (unsigned long long*) &sval[1],\n                       (unsigned long long*) &sval[2],\n                       (unsigned long long*) &sval[3],\n                       (unsigned long long*) &sval[4],\n                       (unsigned long long*) &sval[5],\n                       (unsigned long long*) &sval[6],\n                       (unsigned long long*) &sval[7],\n                       (unsigned long long*) &sval[8],\n                       (unsigned long long*) &sval[9],\n                       (unsigned long long*) &ival[0],\n                       (unsigned long long*) &ival[1],\n                       (unsigned long long*) &ival[2],\n                       (unsigned long long*) &ival[3],\n                       (unsigned long long*) &ival[4],\n                       (unsigned long long*) &ival[5]);\n\n    if ((items != 17) || (strcmp(tag, \"stat:\"))) {\n      ret = false;\n    }\n  } else {\n    ret = false;\n  }\n\n  delete response;\n  return ret;\n}\n\n\n//------------------------------------------------------------------------------\n// Set extended attribute\n//------------------------------------------------------------------------------\nbool\nDirEos::SetXattr(const std::string& attrName, const std::string& attrValue)\n{\n  bool retc = true;\n  std::string request;\n  XrdCl::Buffer arg;\n  XrdCl::Buffer* response = 0;\n  request = mDirPath;\n  request += \"?\";\n  request += \"mgm.pcmd=xattr&\";\n  request += \"mgm.subcmd=set&\";\n  request += \"mgm.xattrname=\";\n  request += attrName;\n  request += \"&\";\n  request += \"mgm.xattrvalue=\";\n  request += attrValue;\n  arg.FromString(request);\n  XrdCl::XRootDStatus status = mFs->Query(XrdCl::QueryCode::OpaqueFile, arg,\n                                          response);\n\n  if (status.IsOK()) {\n    int ret;\n    int items = 0;\n    char tag[1024];\n    // Parse output\n    items = sscanf(response->GetBuffer(), \"%s retc=%i\", tag, &ret);\n\n    if ((items != 2) || (strcmp(tag, \"setxattr:\"))) {\n      retc = false;\n    }\n  } else {\n    retc = false;\n  }\n\n  delete response;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Create directory\n//------------------------------------------------------------------------------\nbool\nDirEos::Create()\n{\n  mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP;\n  XrdCl::Access::Mode mode_xrdcl = eos::common::LayoutId::MapModeSfs2XrdCl(mode);\n  XrdCl::XRootDStatus status = mFs->MkDir(mDirPath,\n                                          XrdCl::MkDirFlags::MakePath,\n                                          mode_xrdcl);\n  return status.IsOK();\n}\n\n\n//------------------------------------------------------------------------------\n// Check that the extended attribute matched the reference value\n//------------------------------------------------------------------------------\nbool\nDirEos::CheckXattr(const std::string& attrName, const std::string& refValue)\n{\n  bool retc = true;\n  std::string request;\n  XrdCl::Buffer arg;\n  XrdCl::Buffer* response = 0;\n  request = mDirPath;\n  request += \"?\";\n  request += \"mgm.pcmd=xattr&\";\n  request += \"mgm.subcmd=get&\";\n  request += \"mgm.xattrname=\";\n  request += attrName;\n  arg.FromString(request);\n  XrdCl::XRootDStatus status = mFs->Query(XrdCl::QueryCode::OpaqueFile, arg,\n                                          response);\n\n  if (status.IsOK()) {\n    int ret;\n    int items = 0;\n    char tag[1024];\n    char rval[4096];\n    // Parse output\n    items = sscanf(response->GetBuffer(), \"%s retc=%i value=%s\", tag, &ret, rval);\n\n    if ((items != 3) || (strcmp(tag, \"getxattr:\"))) {\n      fprintf(stderr, \"[%s] Directory does not have the required xattr.\\n\",\n              __FUNCTION__);\n      retc = false;\n    } else {\n      std::string attr_value = rval;\n\n      if (attr_value.compare(refValue)) {\n        // Attr value is different from refValue\n        retc = false;\n      }\n    }\n  } else {\n    retc = false;\n  }\n\n  delete response;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Get files from benchmark directory having the requried file size\n//------------------------------------------------------------------------------\nstd::vector<std::string>\nDirEos::GetMatchingFiles(const uint64_t fileSize)\n{\n  std::vector<std::string> vect_filenames;\n  XrdCl::DirectoryList* response = 0;\n  XrdCl::DirListFlags::Flags flags = XrdCl::DirListFlags::Stat;\n  XrdCl::XRootDStatus status = mFs->DirList(mDirPath, flags, response);\n  std::string full_path;\n\n  if (status.IsOK()) {\n    for (XrdCl::DirectoryList::ConstIterator iter = response->Begin();\n         iter != response->End();\n         ++iter) {\n      XrdCl::DirectoryList::ListEntry* list_entry =\n        static_cast<XrdCl::DirectoryList::ListEntry*>(*iter);\n\n      if (list_entry->GetStatInfo()->GetSize() == fileSize) {\n        full_path = mDirPath;\n        full_path += list_entry->GetName();\n        vect_filenames.push_back(full_path);\n      }\n    }\n  }\n\n  delete response;\n  return vect_filenames;\n}\n\n\n//------------------------------------------------------------------------------\n// Check if directory matches with the supplied low level configuration\n//------------------------------------------------------------------------------\nbool\nDirEos::MatchConfig(const ConfigProto& llconfig)\n{\n  if (!CheckXattr(\"user.admin.forced.layout\",\n                  Configuration::GetFileLayout(llconfig.filelayout()))) {\n    eos_warning(\"Directory attributes do not match with configuration\");\n    return false;\n  }\n\n  // If this is a replica file type we check the number of preplicas\n  if (llconfig.filelayout() == ConfigProto_FileLayoutType_REPLICA) {\n    if (!CheckXattr(\"user.admin.forced.nstripes\",\n                    std::to_string((long long int)llconfig.noreplicas()))) {\n      eos_warning(\"Number of replicas does not match with configuration\");\n      return false;\n    }\n  }\n\n  return true;\n}\n\n\n//------------------------------------------------------------------------------\n// Set the extended attributes of the directory so that they match the config\n//------------------------------------------------------------------------------\nbool\nDirEos::SetConfig(const ConfigProto& llconfig)\n{\n  // OBS: These predefined configuration are the ones that we expect to be\n  //      used in production and therefore we set them like this\n  bool ret = true;\n\n  if (llconfig.filelayout() == ConfigProto_FileLayoutType_PLAIN) {\n    ret  = SetXattr(\"user.admin.forced.layout\", \"plain\");\n    ret |= SetXattr(\"user.admin.forced.checksum\", \"adler\");\n    ret |= SetXattr(\"user.admin.forced.blockchecksum\", \"crc32c\");\n    ret |= SetXattr(\"user.admin.forced.blocksize\", \"1MB\");\n  } else if (llconfig.filelayout() == ConfigProto_FileLayoutType_REPLICA) {\n    ret  = SetXattr(\"user.admin.forced.layout\", \"replica\");\n    ret |= SetXattr(\"user.admin.forced.nstripes\",\n                    std::to_string((long long int)llconfig.noreplicas()));\n    ret |= SetXattr(\"user.admin.forced.checksum\", \"adler\");\n    ret |= SetXattr(\"user.admin.forced.blockchecksum\", \"crc32c\");\n    ret |= SetXattr(\"user.admin.forced.blocksize\", \"1M\");\n  } else if (llconfig.filelayout() == ConfigProto_FileLayoutType_ARCHIVE) {\n    ret  = SetXattr(\"user.admin.forced.layout\", \"archive\");\n    ret |= SetXattr(\"user.admin.forced.blockchecksum\", \"crc32c\");\n    ret |= SetXattr(\"user.admin.forced.blocksize\", \"1M\");\n  } else if (llconfig.filelayout() == ConfigProto_FileLayoutType_RAIDDP) {\n    ret  = SetXattr(\"user.admin.forced.layout\", \"raiddp\");\n    ret |= SetXattr(\"user.admin.forced.nstripes\", \"6\");\n    ret |= SetXattr(\"user.admin.forced.blockchecksum\", \"crc32c\");\n    ret |= SetXattr(\"user.admin.forced.blocksize\", \"1M\");\n  } else if (llconfig.filelayout() == ConfigProto_FileLayoutType_RAID6) {\n    ret  = SetXattr(\"user.admin.forced.layout\", \"raid6\");\n    ret |= SetXattr(\"user.admin.forced.nstripes\", \"6\");\n    ret |= SetXattr(\"user.admin.forced.blockchecksum\", \"crc32c\");\n    ret |= SetXattr(\"user.admin.forced.blocksize\", \"1M\");\n  }\n\n  if (!ret) {\n    std::cerr << \"Error while trying to set extended attributes.\" << std::endl;\n    return ret;\n  }\n\n  return ret;\n}\n\n\n//------------------------------------------------------------------------------\n//! Remove directory\n//------------------------------------------------------------------------------\nbool\nDirEos::Remove()\n{\n  XrdCl::XRootDStatus status = mFs->RmDir(mDirPath);\n  return status.IsOK();\n}\n\nEOSBMKNAMESPACE_END\n\n"
  },
  {
    "path": "test/benchmark/DirEos.hh",
    "content": "//------------------------------------------------------------------------------\n// File: DirEos.hh\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSBMK_DIREOS_HH__\n#define __EOSBMK_DIREOS_HH__\n\n/*----------------------------------------------------------------------------*/\n#include <cstring>\n/*----------------------------------------------------------------------------*/\n#include \"Namespace.hh\"\n#include \"common/Logging.hh\"\n#include <XrdCl/XrdClFileSystem.hh>\n/*----------------------------------------------------------------------------*/\n\nEOSBMKNAMESPACE_BEGIN\n\n//! Forward declaration of low level config object\nclass ConfigProto;\n\n//------------------------------------------------------------------------------\n//! Class DirEos - used for doing operations on EOS directories\n//------------------------------------------------------------------------------\nclass DirEos: public eos::common::LogId\n{\n  public:\n\n    //--------------------------------------------------------------------------\n    //! Constructor\n    //!\n    //! @param dirPath path to the directory\n    //! @param eosInstance EOS instance to which to connect\n    //!\n    //--------------------------------------------------------------------------\n    DirEos(const std::string& dirPath, const std::string& eosInstance);\n\n\n    //--------------------------------------------------------------------------\n    //! Destructor\n    //--------------------------------------------------------------------------\n    virtual ~DirEos();\n\n\n    //--------------------------------------------------------------------------\n    //! Stat directory\n    //!\n    //! @return true if file exists, otherwise false\n    //!\n    //--------------------------------------------------------------------------\n    bool Exist();\n\n\n    //--------------------------------------------------------------------------\n    //! Create directory\n    //!\n    //! @return true if creation successful, otherwise false\n    //!\n    //--------------------------------------------------------------------------\n    bool Create();\n\n\n    //--------------------------------------------------------------------------\n    //! Set extended attribute\n    //!\n    //! @param attrName extended attribute name\n    //! @param attrValue extended attribute value\n    //!\n    //! @return true if attribute set successfully, otherwise false\n    //!\n    //--------------------------------------------------------------------------\n    bool SetXattr(const std::string& attrName, const std::string& attrValue);\n\n\n    //--------------------------------------------------------------------------\n    //! Get files form benchmark directory having the requried file size\n    //!\n    //! @param fileSize requried file size\n    //!\n    //! @return vector of files in directory matchin the requirements\n    //!\n    //--------------------------------------------------------------------------\n    std::vector<std::string> GetMatchingFiles(const uint64_t fileSize);\n\n\n    //--------------------------------------------------------------------------\n    //! Check extended attribute\n    //!\n    //! @param attrName extended attribute name\n    //! @param refValue reference value to which we compare\n    //!\n    //! @return true if attribute value matches the reference one,\n    //!         otherwise false\n    //!\n    //--------------------------------------------------------------------------\n    bool CheckXattr(const std::string& attrName, const std::string& refValue);\n\n\n    //--------------------------------------------------------------------------\n    //! Check if directory matches with the supplied configuration\n    //!\n    //! @param llconfig low level configuration object\n    //!\n    //! @return true directory matches with configuration, otherwise false\n    //!\n    //--------------------------------------------------------------------------\n    bool MatchConfig(const ConfigProto& llconfig);\n\n\n    //--------------------------------------------------------------------------\n    //! Set the extended attributes of the directory so that they match\n    //! the config\n    //!\n    //! @param llconfig low level configuration object\n    //!\n    //! @return true if ext. attr. were successfully set, otherwise false\n    //!\n    //--------------------------------------------------------------------------\n    bool SetConfig(const ConfigProto& llconfig);\n\n\n    //--------------------------------------------------------------------------\n    //! Remove directory\n    //!\n    //! @return true if successful, otherwise false\n    //!\n    //--------------------------------------------------------------------------\n    bool Remove();\n\n\n  private:\n\n    std::string mDirPath;    ///< path to the directory\n    XrdCl::FileSystem* mFs;  ///< XrdCl file system instance\n};\n\nEOSBMKNAMESPACE_END\n\n#endif // __EOSBMK_DIREOS_HH__\n"
  },
  {
    "path": "test/benchmark/FileEos.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FileEos.cc\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <fstream>\n#include <iostream>\n#include <sys/stat.h>\n#include \"FileEos.hh\"\n#include \"Result.hh\"\n#include <XrdSfs/XrdSfsInterface.hh>\n#include \"fst/io/FileIoPlugin.hh\"\n#include \"fst/io/AsyncMetaHandler.hh\"\n#include \"fst/layout/RainMetaLayout.hh\"\n#include \"fst/layout/RaidDpLayout.hh\"\n#include \"fst/layout/ReedSLayout.hh\"\n#include \"common/Timing.hh\"\n#include \"common/LayoutId.hh\"\n\nEOSBMKNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFileEos::FileEos(const std::string& filePath,\n                 const std::string& bmkInstance,\n                 uint64_t           fileSize,\n                 uint32_t           blockSize):\n  eos::common::LogId(),\n  mFilePath(filePath),\n  mBmkInstance(bmkInstance),\n  mFileSize(fileSize),\n  mBlockSize(blockSize)\n{\n  // empty\n}\n\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nFileEos::~FileEos()\n{\n  // empty\n}\n\n\n//------------------------------------------------------------------------------\n// Write file\n//------------------------------------------------------------------------------\nint\nFileEos::Write(Result*& result)\n{\n  eos_debug(\"Calling function\");\n  int retc = 0;\n  uint64_t file_size = mFileSize;\n  uint32_t block_size = mBlockSize;\n  eos::common::Timing wr_timing(\"write\");\n  // Fill buffer with random characters\n  char* buffer = new char[block_size];\n  std::ifstream urandom(\"/dev/urandom\", std::ios::in | std::ios::binary);\n  urandom.read(buffer, block_size);\n  urandom.close();\n  // Open the file for writing and get and XrdFileIo object\n  mode_t mode_sfs = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IROTH;\n  XrdSfsFileOpenMode flags_sfs = SFS_O_CREAT | SFS_O_RDWR;\n  COMMONTIMING(\"OPEN\", &wr_timing);\n  std::string full_path = mBmkInstance;\n  full_path += \"/\";\n  full_path += mFilePath;\n  eos::fst::FileIo* file = eos::fst::FileIoPlugin::GetIoObject(\n                             full_path.c_str());\n  retc = file->fileOpen(flags_sfs, mode_sfs, \"\");\n\n  if (retc) {\n    eos_err(\"Error while opening file: %s\", full_path.c_str());\n    delete file;\n    delete[] buffer;\n    return retc;\n  }\n\n  COMMONTIMING(\"WRITE\", &wr_timing);\n  // Do the actual writing\n  uint64_t offset = 0;\n  uint64_t length = 0;\n  int64_t nwrite;\n\n  while (file_size > 0) {\n    length = ((file_size > block_size) ?  block_size : file_size);\n    nwrite = file->fileWriteAsync(offset, buffer, length);\n\n    if (nwrite != (int64_t)length) {\n      eos_err(\"Failed while doing write at offset=%llu\", offset);\n      retc = -1;\n      break;\n    }\n\n    offset += nwrite;\n    file_size -= nwrite;\n  }\n\n  COMMONTIMING(\"WAIT_ASYNC\", &wr_timing);\n  // Collect all the write responses\n  eos::fst::AsyncMetaHandler* ptr_handler =\n    static_cast<eos::fst::AsyncMetaHandler*>(file->fileGetAsyncHandler());\n\n  if (ptr_handler && (!ptr_handler->WaitOK())) {\n    eos_err(\"Error while waiting for write async responses\");\n    retc = -1;\n  }\n\n  COMMONTIMING(\"CLOSE\", &wr_timing);\n  retc += file->fileClose();\n  COMMONTIMING(\"END\", &wr_timing);\n  // Collect statistics for this operation in the result object at job level\n  ResultProto& pb_result = result->GetPbResult();\n  float transaction_time = wr_timing.GetTagTimelapse(\"OPEN\", \"END\");\n  pb_result.add_timestamp(GetTimestamp());\n  pb_result.add_opentime(wr_timing.GetTagTimelapse(\"OPEN\", \"WRITE\"));\n  pb_result.add_readtime(0);\n  pb_result.add_readwaitasync(0);\n  pb_result.add_writetime(wr_timing.GetTagTimelapse(\"WRITE\", \"WAIT_ASYNC\"));\n  pb_result.add_writewaitasync(wr_timing.GetTagTimelapse(\"WAIT_ASYNC\", \"CLOSE\"));\n  pb_result.add_closetime(wr_timing.GetTagTimelapse(\"CLOSE\", \"END\"));\n  pb_result.add_transactiontime(wr_timing.GetTagTimelapse(\"OPEN\", \"END\"));\n  pb_result.add_readspeed(0);\n  pb_result.add_writespeed(Result::GetTransferSpeed((float)offset,\n                           transaction_time));\n  pb_result.add_readtotal(0);\n  pb_result.add_writetotal(offset);\n  delete file;\n  delete[] buffer;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Read a file in gateway mode\n//------------------------------------------------------------------------------\nint\nFileEos::ReadGw(Result*& result)\n{\n  eos_debug(\"Calling function\");\n  int retc = 0;\n  char* buffer;\n  std::vector<char*> vect_buff;\n  int32_t total_buffs = 64;\n  uint64_t file_size = mFileSize;\n  uint32_t block_size = mBlockSize;\n  eos::common::Timing rd_timing(\"rdgw\");\n\n  // Allocate a pool of read buffers and then do round-robin for reading in them\n  // so as to minimse the probability of two requests writing in the same buffer\n  // at the same time\n  for (int32_t i = 0; i < total_buffs; i++) {\n    buffer = new char[block_size];\n    vect_buff.push_back(buffer);\n  }\n\n  // Open the file for reading and get and XrdFileIo object\n  XrdSfsFileOpenMode flags_sfs = SFS_O_RDONLY;\n  COMMONTIMING(\"OPEN\", &rd_timing);\n  std::string full_path = mBmkInstance;\n  full_path += \"/\";\n  full_path += mFilePath;\n  eos::fst::FileIo* file = eos::fst::FileIoPlugin::GetIoObject(\n                             full_path.c_str());\n  retc = file->fileOpen(flags_sfs, 0, \"fst.readahead=true\");\n\n  if (retc) {\n    eos_err(\"Error while opening files: %s\", full_path.c_str());\n    delete file;\n    return retc;\n  }\n\n  COMMONTIMING(\"READ\", &rd_timing);\n  // Do the actual reading\n  int32_t indx_buff = 0;\n  uint64_t offset = 0;\n  uint64_t length = 0;\n  int64_t nread;\n\n  while (file_size > 0) {\n    length = ((file_size > block_size) ?  block_size : file_size);\n    nread = file->fileReadPrefetch(offset, vect_buff[indx_buff], length);\n    offset += nread;\n    file_size -= nread;\n    indx_buff = (indx_buff + 1) % total_buffs;\n  }\n\n  COMMONTIMING(\"WAIT_ASYNC\", &rd_timing);\n  // Collect all the read responses\n  eos::fst::AsyncMetaHandler* ptr_handler =\n    static_cast<eos::fst::AsyncMetaHandler*>(file->fileGetAsyncHandler());\n\n  if (ptr_handler && (!ptr_handler->WaitOK())) {\n    eos_err(\"Error while waiting for read async responses\");\n    retc = -1;\n  }\n\n  COMMONTIMING(\"CLOSE\", &rd_timing);\n  retc += file->fileClose();\n  COMMONTIMING(\"END\", &rd_timing);\n  // Collect statistics for this operation in the result object at thread level\n  ResultProto& pb_result = result->GetPbResult();\n  float transaction_time = rd_timing.GetTagTimelapse(\"OPEN\", \"END\");\n  pb_result.add_timestamp(GetTimestamp());\n  pb_result.add_opentime(rd_timing.GetTagTimelapse(\"OPEN\", \"READ\"));\n  pb_result.add_readtime(rd_timing.GetTagTimelapse(\"READ\", \"WAIT_ASYNC\"));\n  pb_result.add_readwaitasync(rd_timing.GetTagTimelapse(\"WAIT_ASYNC\", \"CLOSE\"));\n  pb_result.add_writetime(0);\n  pb_result.add_writewaitasync(0);\n  pb_result.add_closetime(rd_timing.GetTagTimelapse(\"CLOSE\", \"END\"));\n  pb_result.add_transactiontime(rd_timing.GetTagTimelapse(\"OPEN\", \"END\"));\n  pb_result.add_readspeed(Result::GetTransferSpeed((float)offset,\n                          transaction_time));\n  pb_result.add_writespeed(0);\n  pb_result.add_readtotal(offset);\n  pb_result.add_writetotal(0);\n\n  //Free allocated memory\n  for (uint32_t i = 0; i < vect_buff.size(); i++) {\n    delete[] vect_buff[i];\n  }\n\n  vect_buff.clear();\n  delete file;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Read a file in parallel IO mode\n//------------------------------------------------------------------------------\nint\nFileEos::ReadPio(Result*& result)\n{\n  eos_debug(\"Calling function\");\n  int retc = 0;\n  char* buffer;\n  std::vector<char*> vect_buff;\n  int32_t total_buffs = 64;\n  uint64_t file_size = mFileSize;\n  uint32_t block_size = mBlockSize;\n  eos::common::Timing rd_timing(\"rdpio\");\n  XrdCl::Buffer arg;\n  XrdCl::Buffer* response = 0;\n  XrdCl::XRootDStatus status;\n  eos::fst::RainMetaLayout* file = 0;\n  XrdSfsFileOpenMode flags_sfs = SFS_O_RDONLY; // open for read by default\n\n  // Allocate a pool of read buffers and then do round-robin for reading in them\n  // so as to minimse the probability of two requests writing in the same buffer\n  // at the same time\n  for (int32_t i = 0; i < total_buffs; i++) {\n    buffer = new char[block_size];\n    vect_buff.push_back(buffer);\n  }\n\n  // Create an XrdCl::FileSystem object and do PIO request\n  COMMONTIMING(\"OPEN\", &rd_timing);\n  XrdCl::URL url(mBmkInstance);\n\n  if (!url.IsValid()) {\n    std::cerr << \"URL is invalid.\" << std::endl;\n    return -1;\n  }\n\n  XrdCl::FileSystem* fs = new XrdCl::FileSystem(url);\n  std::string request = mFilePath;\n  request += \"?mgm.pcmd=open\";\n  arg.FromString(request);\n  status = fs->Query(XrdCl::QueryCode::OpaqueFile, arg, response);\n\n  if (status.IsOK()) {\n    //..........................................................................\n    // Parse output\n    //..........................................................................\n    XrdOucString tag;\n    XrdOucString stripePath;\n    std::vector<std::string> stripeUrls;\n    XrdOucString origResponse = response->GetBuffer();\n    XrdOucString stringOpaque = response->GetBuffer();\n\n    while (stringOpaque.replace(\"?\", \"&\")) {}\n\n    while (stringOpaque.replace(\"&&\", \"&\")) {}\n\n    XrdOucEnv* openOpaque = new XrdOucEnv(stringOpaque.c_str());\n    char* opaqueInfo = (char*) strstr(origResponse.c_str(), \"&&mgm.logid\");\n\n    if (opaqueInfo) {\n      opaqueInfo += 2;\n      eos::common::LayoutId::layoutid_t layout = openOpaque->GetInt(\"mgm.lid\");\n\n      for (unsigned int i = 0; i <= eos::common::LayoutId::GetStripeNumber(layout);\n           i++) {\n        tag = \"pio.\";\n        tag += static_cast<int>(i);\n        stripePath = \"root://\";\n        stripePath += openOpaque->Get(tag.c_str());\n        stripePath += \"/\";\n        stripePath += mFilePath.c_str();\n        stripeUrls.push_back(stripePath.c_str());\n      }\n\n      if (eos::common::LayoutId::GetLayoutType(layout) ==\n          eos::common::LayoutId::kRaidDP) {\n        file = new eos::fst::RaidDpLayout(NULL, layout, NULL, NULL, \"\", NULL);\n      } else if ((eos::common::LayoutId::IsRain(layout))) {\n        file = new eos::fst::ReedSLayout(NULL, layout, NULL, NULL, \"\", NULL);\n      } else {\n        eos_err(\"No such supported layout for PIO\");\n        file = 0;\n      }\n\n      if (file) {\n        retc = file->OpenPio(stripeUrls, flags_sfs, 0, opaqueInfo);\n\n        if (retc) {\n          eos_err(\"error=open PIO failed for path=%s\", mFilePath.c_str());\n\n          //Free allocated memory\n          for (uint32_t i = 0; i < vect_buff.size(); i++) {\n            delete vect_buff[i];\n          }\n\n          delete response;\n          delete openOpaque;\n          delete file;\n          return retc;\n        }\n      } else {\n        delete response;\n        delete openOpaque;\n        delete file;\n        std::cout << \"Falling back to read gw.(0)\" << std::endl;\n        return ReadGw(result);\n      }\n    } else {\n      eos_err(\"error=opaque info not what we expected\");\n      delete response;\n      delete openOpaque;\n      delete file;\n      std::cout << \"Falling back to read gw.(1)\" << std::endl;\n      return ReadGw(result);\n    }\n  } else {\n    eos_warning(\"Failed to get PIO request falling back to GW mode\");\n\n    //Free allocated memory\n    for (uint32_t i = 0; i < vect_buff.size(); i++) {\n      delete vect_buff[i];\n    }\n\n    delete response;\n    std::cout << \"Falling back to read gw.(2)\" << std::endl;\n    return ReadGw(result);\n  }\n\n  COMMONTIMING(\"READ\", &rd_timing);\n  // Do the actual reading\n  int32_t indx_buff = 0;\n  uint64_t offset = 0;\n  uint64_t length = 0;\n  int64_t nread;\n\n  while (file_size > 0) {\n    length = ((file_size > block_size) ?  block_size : file_size);\n    nread = file->Read(offset, vect_buff[indx_buff], length);\n    offset += nread;\n    file_size -= nread;\n    indx_buff = (indx_buff + 1) % total_buffs;\n  }\n\n  COMMONTIMING(\"CLOSE\", &rd_timing);\n  retc = file->Close();\n  COMMONTIMING(\"END\", &rd_timing);\n  // Collect statistics for this operation in the result object at thread level\n  ResultProto& pb_result = result->GetPbResult();\n  float transaction_time = rd_timing.GetTagTimelapse(\"OPEN\", \"END\");\n  pb_result.add_timestamp(GetTimestamp());\n  pb_result.add_opentime(rd_timing.GetTagTimelapse(\"OPEN\", \"READ\"));\n  pb_result.add_readtime(rd_timing.GetTagTimelapse(\"READ\", \"CLOSE\"));\n  pb_result.add_readwaitasync(0);\n  pb_result.add_writetime(0);\n  pb_result.add_writewaitasync(0);\n  pb_result.add_closetime(rd_timing.GetTagTimelapse(\"CLOSE\", \"END\"));\n  pb_result.add_transactiontime(rd_timing.GetTagTimelapse(\"OPEN\", \"END\"));\n  pb_result.add_readspeed(Result::GetTransferSpeed((float)offset,\n                          transaction_time));\n  pb_result.add_writespeed(0);\n  pb_result.add_readtotal(offset);\n  pb_result.add_writetotal(0);\n\n  //Free allocated memory\n  for (uint32_t i = 0; i < vect_buff.size(); i++) {\n    delete[] vect_buff[i];\n  }\n\n  vect_buff.clear();\n  delete file;\n  delete response;\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Write and read file in gateway mode\n//------------------------------------------------------------------------------\nint\nFileEos::ReadWriteGw(Result*& result)\n{\n  eos_debug(\"Calling function\");\n  int retc = 0;\n  retc += Write(result);\n  retc += ReadGw(result);\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Write and read file in parallel IO mode\n//------------------------------------------------------------------------------\nint\nFileEos::ReadWritePio(Result*& result)\n{\n  eos_debug(\"Calling function\");\n  int retc = 0;\n  retc += Write(result);\n  retc += ReadPio(result);\n  return retc;\n}\n\n\n//------------------------------------------------------------------------------\n// Get current timestamp as a string\n//------------------------------------------------------------------------------\nstd::string\nFileEos::GetTimestamp()\n{\n  std::string time_str;\n  char time_buff[1024];\n  time_t current_time;\n  struct tm* tm;\n  time(&current_time);\n  tm = localtime(&current_time);\n  sprintf(time_buff, \"%02d/%02d/%04d %02d:%02d:%02d\", tm->tm_mday, tm->tm_mon + 1,\n          tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec);\n  time_str = time_buff;\n  return time_str;\n}\n\nEOSBMKNAMESPACE_END\n"
  },
  {
    "path": "test/benchmark/FileEos.hh",
    "content": "//------------------------------------------------------------------------------\n// File: FileEos.hh\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSBMK_FILEEOS_HH__\n#define __EOSBMK_FILEEOS_HH__\n\n/*-----------------------------------------------------------------------------*/\n#include <string>\n/*-----------------------------------------------------------------------------*/\n#include \"Namespace.hh\"\n#include \"common/Logging.hh\"\n/*-----------------------------------------------------------------------------*/\n\nEOSBMKNAMESPACE_BEGIN\n\n//! Forward declaration\nclass Result;\n\n//------------------------------------------------------------------------------\n// Class FileEos\n//------------------------------------------------------------------------------\nclass FileEos: public eos::common::LogId\n{\n  public:\n\n    //--------------------------------------------------------------------------\n    //! Constructor\n    //!\n    //! @param filePath path of the file on which to do operations\n    //! @param bmkInstance benchmark instance name ex. eosdev.cern.ch\n    //! @param fileSize size of the file to be manipulated\n    //! @param blockSize block size used for operations\n    //!\n    //--------------------------------------------------------------------------\n    FileEos(const std::string& filePath,\n            const std::string& bmkInstance,\n            uint64_t           fileSize,\n            uint32_t           blockSize);\n\n\n    //--------------------------------------------------------------------------\n    //! Destructor\n    //--------------------------------------------------------------------------\n    virtual ~FileEos();\n\n\n    //--------------------------------------------------------------------------\n    //! Execute write operation\n    //!\n    //! @param result object in which the transfer statistics are saved\n    //!\n    //! @return 0 if successful, otherwise -1\n    //!\n    //--------------------------------------------------------------------------\n    int Write(Result*& result);\n\n  \n    //--------------------------------------------------------------------------\n    //! Read in gateway mode \n    //!\n    //! @param result object in which the transfer statistics are saved\n    //!\n    //! @return 0 if successful, otherwise -1\n    //!\n    //--------------------------------------------------------------------------\n    int ReadGw(Result*& result);\n  \n\n    //--------------------------------------------------------------------------\n    //! Read in parallel IO mode. If this is not possible then if fails over\n    //! to the gateway mode.\n    //!\n    //! @param result object in which the transfer statistics are saved\n    //!\n    //! @return 0 if successful, otherwise -1\n    //!\n    //--------------------------------------------------------------------------\n    int ReadPio(Result*& result);\n\n\n    //--------------------------------------------------------------------------\n    //! Do write and read operation in gateway mode (only read)\n    //!\n    //! @param result object in which the transfer statistics are saved\n    //!\n    //! @return 0 if successful, otherwise -1\n    //!\n    //--------------------------------------------------------------------------\n    int ReadWriteGw(Result*& result);\n\n\n    //--------------------------------------------------------------------------\n    //! Do write and read operation in parallel IO mode (only read)\n    //!\n    //! @param result object in which the transfer statistics are saved\n    //!\n    //! @return 0 if successful, otherwise -1\n    //!\n    //--------------------------------------------------------------------------\n    int ReadWritePio(Result*& result);\n\n  private:\n\n    std::string mFilePath;     ///< file path\n    std::string mBmkInstance;  ///< benchmark instance\n    uint64_t mFileSize;        ///< file size\n    uint32_t mBlockSize;       ///< block size for operations\n\n    //--------------------------------------------------------------------------\n    //! Get current timestamp as a string\n    //!\n    //! @return current timestamp as a string\n    //!\n    //--------------------------------------------------------------------------\n    std::string GetTimestamp();\n\n};\n\nEOSBMKNAMESPACE_END\n\n#endif // __EOSBMK_FILEEOS_HH__\n"
  },
  {
    "path": "test/benchmark/Namespace.hh",
    "content": "// ----------------------------------------------------------------------\n// File: Namespace.hh\n// Author: Elvin-Alin Sindrilaru - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSBMK_NAMESPACE_HH__\n#define __EOSBMK_NAMESPACE_HH__\n\n#define EOSBMKNAMESPACE_BEGIN namespace eos { namespace benchmark {\n#define EOSBMKNAMESPACE_END }}\n\n#endif\n"
  },
  {
    "path": "test/benchmark/ProtoIo.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ProtoIo.cc\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n/*----------------------------------------------------------------------------*/\n#include \"ProtoIo.hh\"\n/*----------------------------------------------------------------------------*/\n\nEOSBMKNAMESPACE_BEGIN\n\n/******************************************************************************/\n/*                            P r o t o W r i t e r                           */\n/******************************************************************************/\n\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nProtoWriter::ProtoWriter(const std::string& file):\n  mFs(file, std::ios::out | std::ios::binary | std::ios::app)\n{\n  assert(mFs.good());\n  _OstreamOutputStream = new OstreamOutputStream(&mFs);\n  _CodedOutputStream = new CodedOutputStream(_OstreamOutputStream);\n}\n\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nProtoWriter:: ~ProtoWriter()\n{\n  delete _CodedOutputStream;\n  delete _OstreamOutputStream;\n  mFs.close();\n}\n\n\n/******************************************************************************/\n/*                            P r o t o R e a d e r                           */\n/******************************************************************************/\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nProtoReader::ProtoReader(const std::string& file):\n  mFs(file, std::ios::in | std::ios::binary)\n{\n  assert(mFs.good());\n  _IstreamInputStream = new IstreamInputStream(&mFs);\n  _CodedInputStream = new CodedInputStream(_IstreamInputStream);\n}\n\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nProtoReader::~ProtoReader()\n{\n  delete _CodedInputStream;\n  delete _IstreamInputStream;\n  mFs.close();\n}\n\nEOSBMKNAMESPACE_END\n"
  },
  {
    "path": "test/benchmark/ProtoIo.hh",
    "content": "//------------------------------------------------------------------------------\n// File: ProtoIo.hh\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSBMK_PROTOIO_HH__\n#define __EOSBMK_PROTOIO_HH__\n\n/*-----------------------------------------------------------------------------*/\n#include <cstring>\n#include <fstream>\n#include <iostream>\n/*-----------------------------------------------------------------------------*/\n#include \"Namespace.hh\"\n#include <google/protobuf/message.h>\n#include <google/protobuf/io/zero_copy_stream_impl.h>\n#include <google/protobuf/io/coded_stream.h>\n/*-----------------------------------------------------------------------------*/\n\nusing namespace google::protobuf::io;\n\nEOSBMKNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class ProtoIo used to write or read Protocol Buffer objects from files. It\n//! allows to handle multiple ProtoBuf objects in the same file by first writing\n//! the size of the object and then the actual object information. Therefore, in\n//! the reading process we first read the size of the object and then the object\n//! information.\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n//! Class ProtoWriter\n//------------------------------------------------------------------------------\nclass ProtoWriter\n{\n\npublic:\n\n  //--------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param file file where the ProtocolBuffer objects are written\n  //!\n  //--------------------------------------------------------------------------\n  ProtoWriter(const std::string& file);\n\n\n  //--------------------------------------------------------------------------\n  //! Destructor\n  //--------------------------------------------------------------------------\n  ~ProtoWriter();\n\n\n  //--------------------------------------------------------------------------\n  //! Write the object to the file along with its size\n  //!\n  //! @param msg object to be written to the file\n  //!\n  //! @return true if successful, otherwise false\n  //!\n  //--------------------------------------------------------------------------\n  inline bool operator()(const ::google::protobuf::Message& msg)\n  {\n#if GOOGLE_PROTOBUF_VERSION < 3004000\n    auto sz = msg.ByteSize();\n#else\n    auto sz = msg.ByteSizeLong();\n#endif\n    _CodedOutputStream->WriteVarint32(sz);\n\n    if (!msg.SerializeToCodedStream(_CodedOutputStream)) {\n      std::cerr << \"SerializeToCodedStream error \" << std::endl;\n      return false;\n    }\n\n    return true;\n  }\n\nprivate:\n\n  std::ofstream mFs;                          ///< output file stream\n  OstreamOutputStream* _OstreamOutputStream;  ///<\n  CodedOutputStream* _CodedOutputStream;      ///<\n};\n\n\n//------------------------------------------------------------------------------\n//! Class ProtoReader\n//------------------------------------------------------------------------------\nclass ProtoReader\n{\n\npublic:\n\n  //--------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param file file from which ProtocolBuffer objects are read\n  //!\n  //--------------------------------------------------------------------------\n  ProtoReader(const std::string& file);\n\n\n  //--------------------------------------------------------------------------\n  //! Destructor\n  //--------------------------------------------------------------------------\n  ~ProtoReader();\n\n\n  //--------------------------------------------------------------------------\n  //! Read next object from file\n  //!\n  //! @return the ProtocolBuffer object read from the file\n  //!\n  //--------------------------------------------------------------------------\n  template<class T>\n  T* ReadNext()\n  {\n    T* msg = new T();\n    uint32_t size;\n    bool ret;\n\n    if ((ret = _CodedInputStream->ReadVarint32(&size))) {\n      CodedInputStream::Limit msgLimit = _CodedInputStream->PushLimit(size);\n\n      if ((ret = msg->ParseFromCodedStream(_CodedInputStream))) {\n        _CodedInputStream->PopLimit(msgLimit);\n      } else {\n        delete msg;\n        msg = 0;\n      }\n    } else {\n      delete msg;\n      msg = 0;\n    }\n\n    return msg;\n  }\n\nprivate:\n\n  std::ifstream mFs;                         ///< input file stream\n  IstreamInputStream* _IstreamInputStream;   ///<\n  CodedInputStream* _CodedInputStream;       ///<\n};\n\nEOSBMKNAMESPACE_END\n\n#endif // __EOSBMK_PROTOIO_HH__\n"
  },
  {
    "path": "test/benchmark/Result.cc",
    "content": "//------------------------------------------------------------------------------\n// File: Result.cc\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n/*-----------------------------------------------------------------------------*/\n#include <iostream>\n#include <iomanip>\n#include <cmath>\n/*-----------------------------------------------------------------------------*/\n#include \"Result.hh\"\n#include \"common/StringConversion.hh\"\n/*-----------------------------------------------------------------------------*/\n\nEOSBMKNAMESPACE_BEGIN\n\nusing namespace std;\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nResult::Result()\n{\n  mPbResult = new ResultProto();\n}\n\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nResult::~Result()\n{\n  if (mPbResult)  delete mPbResult;\n}\n\n\n//------------------------------------------------------------------------------\n// Set the low level result object\n//------------------------------------------------------------------------------\nvoid\nResult::SetPbResult(ResultProto* pbResult)\n{\n  if (mPbResult) delete mPbResult;\n\n  mPbResult = pbResult;\n}\n\n\n//------------------------------------------------------------------------------\n// Get low level result object (reference)\n//------------------------------------------------------------------------------\nResultProto&\nResult::GetPbResult() const\n{\n  return *mPbResult;\n}\n\n\n//------------------------------------------------------------------------------\n// Get transfer speed in MB/s\n//------------------------------------------------------------------------------\nfloat\nResult::GetTransferSpeed(float size, float duration)\n{\n  float speed = 0;\n  size /= eos::common::MB;\n  duration /= 1000.0;\n\n  if (duration)\n  {\n    speed = size / duration;\n  }\n\n  return speed;\n}\n\n\n//------------------------------------------------------------------------------\n// Print statistics\n//------------------------------------------------------------------------------\nvoid\nResult::Print() const\n{\n  std::stringstream sstr;\n  sstr << left << setw(190) << setfill('.') << \"\" << endl;\n  std::string dot_line =  sstr.str();\n  sstr.str(\"\");\n  sstr << left << setw(190) << setfill('-') << \"\" << endl;\n  std::string minus_line = sstr.str();\n  sstr.str(\"\");\n  sstr << left << setw(190) << setfill('*') << \"\" << endl;\n  std::string star_line = sstr.str();\n  sstr.str(\"\");\n  \n  cout << star_line\n       << setw(110) << right << \"I n d i v i d u a l   s t a t i s t i c s\" << endl\n       << star_line\n       << setw(20) << right << \"Timestamp\"\n       << setw(14) << right << \"Open time\"\n       << setw(14) << right << \"Read time\"\n       << setw(16) << right << \"Rd wait async\"\n       << setw(14) << right << \"Read total\"\n       << setw(14) << right << \"Read speed\"\n       << setw(14) << right << \"Write time\"\n       << setw(16) << right << \"Wr wait async\"\n       << setw(14) << right << \"Write total\"\n       << setw(14) << right << \"Write speed\"\n       << setw(14) << right << \"Close time\"\n       << setw(18) << right << \"Transaction time\"\n       << endl\n       << minus_line;\n\n  for (int32_t i = 0; i < mPbResult->opentime_size(); i++)\n  {\n    cout << setw(20) << right << mPbResult->timestamp(i)\n         << setw(14) << right << mPbResult->opentime(i)\n         << setw(14) << right << mPbResult->readtime(i)\n         << setw(16) << right << mPbResult->readwaitasync(i)\n         << setw(14) << right\n         << eos::common::StringConversion::GetPrettySize(mPbResult->readtotal(i))\n         << setw(14) << right << mPbResult->readspeed(i)\n         << setw(14) << right << mPbResult->writetime(i)\n         << setw(16) << right << mPbResult->writewaitasync(i)\n         << setw(14) << right\n         << eos::common::StringConversion::GetPrettySize(mPbResult->writetotal(i))\n         << setw(14) << right << mPbResult->writespeed(i)\n         << setw(14) << right << mPbResult->closetime(i)\n         << setw(18) << right << mPbResult->transactiontime(i)\n         << endl;\n  }\n\n  cout << minus_line << endl;\n  cout << endl << star_line\n       << setw(105) << \"G r o u p   s t a t i s t i c s\"  << endl\n       << star_line\n       << setw(10) << right << \"\"\n       << setw(14) << right << \"Open time\"\n       << setw(14) << right << \"Read time\"\n       << setw(16) << right << \"Rd wait async\"\n       << setw(14) << right << \"Read speed\"\n       << setw(14) << right << \"Write time\"\n       << setw(16) << right << \"Wr wait async\"\n       << setw(14) << right << \"Write speed\"\n       << setw(18) << right << \"Transaction time\"\n       << setw(14) << right << \"Close time\" << endl\n       << minus_line\n       << setw(10) << right << \"Average\"\n       << setw(14) << right << mPbResult->avgopentime()\n       << setw(14) << right << mPbResult->avgreadtime()\n       << setw(16) << right << mPbResult->avgreadwaitasync()\n       << setw(14) << right << mPbResult->avgreadspeed()\n       << setw(14) << right << mPbResult->avgwritetime()\n       << setw(16) << right << mPbResult->avgwritewaitasync()\n       << setw(14) << right << mPbResult->avgwritespeed()\n       << setw(18) << right << mPbResult->avgtransactiontime()\n       << setw(14) << right << mPbResult->avgclosetime() << endl\n       << dot_line\n       << setw(10) << right << \"Std. dev.\"\n       << setw(14) << right << mPbResult->stdopentime()\n       << setw(14) << right << mPbResult->stdreadtime()\n       << setw(16) << right << mPbResult->stdreadwaitasync()\n       << setw(14) << right << mPbResult->stdreadspeed()\n       << setw(14) << right << mPbResult->stdwritetime()\n       << setw(16) << right << mPbResult->stdwritewaitasync()\n       << setw(14) << right << mPbResult->stdwritespeed()\n       << setw(18) << right << mPbResult->stdtransactiontime()\n       << setw(14) << right << mPbResult->stdclosetime() << endl\n       << dot_line << dot_line << endl\n       << endl ;\n}\n\n\n//------------------------------------------------------------------------------\n// Merge partial result object into the current one\n//------------------------------------------------------------------------------\nvoid\nResult::Merge(const Result& partial)\n{\n  const ResultProto& pb_partial = partial.GetPbResult();\n\n  for (int32_t i = 0; i < pb_partial.opentime_size(); i++)\n  {\n    mPbResult->add_timestamp(pb_partial.timestamp(i));\n    mPbResult->add_opentime(pb_partial.opentime(i));\n    mPbResult->add_readtime(pb_partial.readtime(i));\n    mPbResult->add_readwaitasync(pb_partial.readwaitasync(i));\n    mPbResult->add_writetime(pb_partial.writetime(i));\n    mPbResult->add_writewaitasync(pb_partial.writewaitasync(i));\n    mPbResult->add_closetime(pb_partial.closetime(i));\n    mPbResult->add_transactiontime(pb_partial.transactiontime(i));\n    mPbResult->add_readspeed(pb_partial.readspeed(i));\n    mPbResult->add_writespeed(pb_partial.writespeed(i));\n    mPbResult->add_readtotal(pb_partial.readtotal(i));\n    mPbResult->add_writetotal(pb_partial.writetotal(i));\n  }\n\n  ComputeGroupStatistics();\n}\n\n\n//------------------------------------------------------------------------------\n// Compute group statistics like average value and standard deviation\n//------------------------------------------------------------------------------\nvoid\nResult::ComputeGroupStatistics()\n{\n  mPbResult->set_avgopentime(Average(mPbResult->opentime()));\n  mPbResult->set_stdopentime(StdDev(mPbResult->opentime(),\n                                    mPbResult->avgopentime()));\n\n  mPbResult->set_avgreadtime(Average(mPbResult->readtime()));\n  mPbResult->set_stdreadtime(StdDev(mPbResult->readtime(),\n                                    mPbResult->avgreadtime()));\n\n  mPbResult->set_avgreadwaitasync(Average(mPbResult->readwaitasync()));\n  mPbResult->set_stdreadwaitasync(StdDev(mPbResult->readwaitasync(),\n                                         mPbResult->avgreadwaitasync()));\n\n  mPbResult->set_avgwritetime(Average(mPbResult->writetime()));\n  mPbResult->set_stdwritetime(StdDev(mPbResult->writetime(),\n                                     mPbResult->avgwritetime()));\n\n  mPbResult->set_avgwritewaitasync(Average(mPbResult->writewaitasync()));\n  mPbResult->set_stdwritewaitasync(StdDev(mPbResult->writewaitasync(),\n                                          mPbResult->avgwritewaitasync()));\n\n  mPbResult->set_avgclosetime(Average(mPbResult->closetime()));\n  mPbResult->set_stdclosetime(StdDev(mPbResult->closetime(),\n                                     mPbResult->avgclosetime()));\n\n  mPbResult->set_avgtransactiontime(Average(mPbResult->transactiontime()));\n  mPbResult->set_stdtransactiontime(StdDev(mPbResult->transactiontime(),\n                                    mPbResult->avgtransactiontime()));\n\n  mPbResult->set_avgreadspeed(Average(mPbResult->readspeed()));\n  mPbResult->set_stdreadspeed(StdDev(mPbResult->readspeed(),\n                                     mPbResult->avgreadspeed()));\n\n  mPbResult->set_avgwritespeed(Average(mPbResult->writespeed()));\n  mPbResult->set_stdwritespeed(StdDev(mPbResult->writespeed(),\n                                      mPbResult->avgwritespeed()));\n}\n\n\n//------------------------------------------------------------------------------\n// Function used to compute the average for the supplied argument\n//------------------------------------------------------------------------------\nfloat\nResult::Average(const ::google::protobuf::RepeatedField< float >& input)\n{\n  float avg = 0;\n\n  for (int64_t i = 0; i < input.size(); i++)\n  {\n    avg += input.Get(i);\n  }\n\n  avg /= input.size();\n  return avg;\n}\n\n\n//------------------------------------------------------------------------------\n// Function used to compute the standard deviation for the supplied argument\n//------------------------------------------------------------------------------\nfloat\nResult::StdDev(const ::google::protobuf::RepeatedField<float>& input,\n               float mean)\n{\n  float std_dev = 0;\n\n  for (int64_t i = 0; i < input.size(); i++)\n  {\n    std_dev += pow((input.Get(i) - mean), 2);\n  }\n\n  std_dev /= input.size();\n  std_dev = sqrt(std_dev);\n  return std_dev;\n}\n\n\n//------------------------------------------------------------------------------\n// Function used to compute the sum of the elements in the container\n//------------------------------------------------------------------------------\nfloat\nResult::Sum(const ::google::protobuf::RepeatedField<float>& input)\n{\n  float sum = 0;\n\n  for (int64_t i = 0; i < input.size(); i++)\n  {\n    sum += input.Get(i);\n  }\n\n  return sum;\n}\n\nEOSBMKNAMESPACE_END\n"
  },
  {
    "path": "test/benchmark/Result.hh",
    "content": "//------------------------------------------------------------------------------\n// File: Result.hh\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSBMK_RESULT_HH__\n#define __EOSBMK_RESULT_HH__\n\n/*-----------------------------------------------------------------------------*/\n#include \"Namespace.hh\"\n#include \"test/benchmark/ResultProto.pb.h\"\n/*-----------------------------------------------------------------------------*/\n\nEOSBMKNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Class Result\n//------------------------------------------------------------------------------\nclass Result\n{\n\n  public:\n\n    //--------------------------------------------------------------------------\n    //! Constructor\n    //--------------------------------------------------------------------------\n    Result();\n\n\n    //--------------------------------------------------------------------------\n    //! Destructor\n    //--------------------------------------------------------------------------\n    virtual ~Result();\n\n\n    //--------------------------------------------------------------------------\n    //! Disable copy constructor\n    //--------------------------------------------------------------------------\n    Result(const Result&) = delete;\n\n\n    //--------------------------------------------------------------------------\n    //! Disable copy operator\n    //--------------------------------------------------------------------------\n    Result& operator =(const Result&) = delete;\n\n\n    //--------------------------------------------------------------------------\n    //! Print statistics\n    //--------------------------------------------------------------------------\n    void Print() const;\n\n\n    //--------------------------------------------------------------------------\n    //! Get transfer speed\n    //!\n    //! @param size transfer size in bytes\n    //! @param duration time duration in miliseconds\n    //!\n    //! @return transfer speed in MB/s\n    //!\n    //--------------------------------------------------------------------------\n    static float GetTransferSpeed(float size, float duration);\n\n\n    //--------------------------------------------------------------------------\n    //! Get low level result object (reference)\n    //!\n    //! @return low level result object (ProtoBuf object)\n    //!\n    //--------------------------------------------------------------------------\n    ResultProto& GetPbResult() const;\n\n\n    //--------------------------------------------------------------------------\n    //! Set the low level result object\n    //!\n    //! @param pbResult low level result object (ProtoBuf object)\n    //!\n    //--------------------------------------------------------------------------\n    void SetPbResult(ResultProto* pbResult);\n\n\n    //--------------------------------------------------------------------------\n    //! Merge result object into the current one\n    //!\n    //! @param partial partial result object to be merged\n    //!\n    //--------------------------------------------------------------------------\n    void Merge(const Result& partial);\n\n  private:\n\n    ResultProto* mPbResult; ///< pointer to low level result object\n\n    //--------------------------------------------------------------------------\n    //! Function used to compute the average for the supplied argument\n    //!\n    //! @param input container of float values to be averaged\n    //!\n    //! @return average value\n    //!\n    //--------------------------------------------------------------------------\n    static float Average(const ::google::protobuf::RepeatedField<float>& input);\n\n\n    //--------------------------------------------------------------------------\n    //! Function used to compute the standard deviation for the supplied argument\n    //!\n    //! @param input container of float values to be averaged\n    //! @param mean mean value\n    //!\n    //! @return standard deviation value\n    //!\n    //--------------------------------------------------------------------------\n    static float StdDev(const ::google::protobuf::RepeatedField<float>& input,\n                        float                                           mean);\n\n\n    //--------------------------------------------------------------------------\n    //! Function used to compute the sum of the elements in a container\n    //!\n    //! @param input container of float values\n    //!\n    //! @return sum of the values\n    //!\n    //--------------------------------------------------------------------------\n    static float Sum(const ::google::protobuf::RepeatedField<float>& input);\n\n\n    //--------------------------------------------------------------------------\n    //! Compute group statistics like average value and standard deviation\n    //--------------------------------------------------------------------------\n    void ComputeGroupStatistics();\n\n};\n\nEOSBMKNAMESPACE_END\n\n#endif // __EOSBMK_RESULT_HH__\n"
  },
  {
    "path": "test/benchmark/ResultProto.proto",
    "content": "syntax = \"proto2\";\npackage eos.benchmark;\n\n//------------------------------------------------------------------------------\n// Result message which holds all parameters computed during the run of the \n// benchmark\n//------------------------------------------------------------------------------\n\nmessage ResultProto {\n\n  repeated string timestamp      = 1;\n  repeated float openTime        = 2  [packed=true];  // all time values in miliseconds\n  repeated float readTime        = 3  [packed=true];\n  repeated float readWaitAsync   = 4  [packed=true];\t\n  repeated float writeTime       = 5  [packed=true];\n  repeated float writeWaitAsync  = 6  [packed=true];\n  repeated float closeTime       = 7  [packed=true];\n  repeated float transactionTime = 8  [packed=true];\n  repeated float readSpeed       = 9  [packed=true];  // in MB/s\n  repeated float writeSpeed      = 10 [packed=true];  // in MB/s\n  repeated uint64 readTotal      = 11 [packed=true];\n  repeated uint64 writeTotal     = 12 [packed=true];\n\n  // Group statistics\n  optional float avgOpenTime        = 13;\n  optional float avgReadTime        = 14;\n  optional float avgReadWaitAsync   = 15;\n  optional float avgWriteTime       = 16;\n  optional float avgWriteWaitAsync  = 17;\n  optional float avgCloseTime       = 18;\n  optional float avgTransactionTime = 19;\n  optional float avgReadSpeed       = 20;\n  optional float avgWriteSpeed      = 21;\n  \n  optional float stdOpenTime        = 22;\n  optional float stdReadTime        = 23;\n  optional float stdReadWaitAsync   = 24;\n  optional float stdWriteTime       = 25;\n  optional float stdWriteWaitAsync  = 26;\n  optional float stdCloseTime       = 27;\n  optional float stdTransactionTime = 28;\n  optional float stdReadSpeed       = 29;\n  optional float stdWriteSpeed      = 30;\n}\n"
  },
  {
    "path": "test/benchmark/eosbenchmark.cc",
    "content": "//------------------------------------------------------------------------------\n// File: eosbenchmark.cc\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <fstream>\n#include <iostream>\n#include <iomanip>\n#include <ctime>\n#include <map>\n#include <utility>\n#include <vector>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include \"eosbenchmark.hh\"\n#include \"ProtoIo.hh\"\n#include \"FileEos.hh\"\n#include \"Result.hh\"\n#include \"Configuration.hh\"\n#include \"common/Path.hh\"\n#include \"common/StringConversion.hh\"\n#include <XrdCl/XrdClDefaultEnv.hh>\n#include <getopt.h>\n\nusing namespace std;\n\nEOSBMKNAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Print help for command line\n//------------------------------------------------------------------------------\nvoid Usage()\n{\n  cout << \"Usage: eosbenchmark <OPTIONS> \" << endl\n       << left << setw(60) << \" --create-config <config.file>  \"\n       << left << setw(50) << \" Prompt for configuration values which will \" << endl\n       << left << setw(60) << \" \"\n       << left << setw(50) << \" be saved in the supplied configuration file \" << endl\n       << left << setw(60) << \" --list-config <config.file> \"\n       << left << setw(50) << \" List the configurations saved in the supplied file \" <<\n       endl\n       << left << setw(60) << \" --run-config <config.file> --output <results.file>\"\n       << left << setw(50) << \" Run configuration and write results in output file \" <<\n       endl\n       << left << setw(60) <<\n       \" --list-results <results.file> [--config <config.file>] \"\n       << left << setw(50) << \" List only runs matching the configuration. If config\"\n       << endl\n       << left << setw(60) << \" \"\n       << left << setw(50) << \" file is not present then it lists all runs \" << endl\n       << left << setw(60) << \" --help \"\n       << left << setw(50) << \" Print out this menu\" << endl;\n}\n\n\n//------------------------------------------------------------------------------\n// Start thread executing a particular function\n//------------------------------------------------------------------------------\nint\nThreadStart(pthread_t& thread, TypeFunc func, void* arg)\n{\n  return pthread_create(&thread, NULL, func, arg);\n}\n\n\n//------------------------------------------------------------------------------\n// Start routine executed by each job\n//------------------------------------------------------------------------------\nvoid*\nStartRoutine(void* arg)\n{\n  int (FileEos::*operation_callback)(Result*\n                                     &);  // define operation function pointer\n  struct ConfIdStruct* arg_thread = (struct ConfIdStruct*)(arg);\n  Configuration& config = static_cast<Configuration&>(arg_thread->config);\n  ConfigProto pb_config = config.GetPbConfig();\n  uint32_t id_thread = arg_thread->id;\n\n  // Decide on the type of operation to be done and save it as a callback\n  switch (pb_config.operation()) {\n  case ConfigProto_OperationType_WRITE:\n    operation_callback = &FileEos::Write;\n    break;\n\n  case ConfigProto_OperationType_READ_GW:\n    operation_callback = &FileEos::ReadGw;\n    break;\n\n  case ConfigProto_OperationType_READ_PIO:\n    operation_callback = &FileEos::ReadPio;\n    break;\n\n  case ConfigProto_OperationType_RDWR_GW:\n    operation_callback = &FileEos::ReadWriteGw;\n    break;\n\n  case ConfigProto_OperationType_RDWR_PIO:\n    operation_callback = &FileEos::ReadWritePio;\n    break;\n\n  default:\n    eos_static_err(\"No such supported operation.\");\n    delete arg_thread;\n    return NULL;\n  }\n\n  // Create file objects\n  uint32_t start_indx = 0;\n  uint32_t end_indx = 0;\n\n  if (pb_config.access() == ConfigProto_AccessMode_PARALLEL) {\n    start_indx = id_thread * pb_config.numfiles();\n    end_indx = (id_thread + 1) * pb_config.numfiles();\n  } else if (pb_config.access() == ConfigProto_AccessMode_CONCURRENT) {\n    start_indx = 0;\n    end_indx = pb_config.numfiles();\n  }\n\n  // Result object which collects all the partial results\n  Result* job_result = new Result();\n\n  for (uint32_t i = start_indx; i < end_indx; i++) {\n    eos_static_debug(\"Execute operation for file:%s, at index:%i \",\n                     config.GetFileName(i).c_str(), i);\n    int retc = -1;\n    FileEos* file = new FileEos(config.GetFileName(i),\n                                pb_config.benchmarkinstance(),\n                                pb_config.filesize(),\n                                pb_config.blocksize());\n    // Execute the required operation\n    retc = (*file.*operation_callback)(job_result);\n\n    if (retc) {\n      cerr << \"Error while executin operation on file\" << endl;\n      delete file;\n      delete job_result;\n      return NULL;\n    }\n\n    delete file;\n  }\n\n  // Delete the memory allocated for argument passing\n  delete arg_thread;\n  // Display the statistics collected by the current thread\n  return job_result;\n}\n\n\n//------------------------------------------------------------------------------\n// Run benchmark using threads\n//------------------------------------------------------------------------------\nvoid\nRunThreadConfig(Configuration& config, const string& outputFile)\n{\n  Result merged_result;\n  std::vector<pthread_t> vect_threads;\n  ConfigProto pb_config = config.GetPbConfig();\n  uint32_t num_jobs = pb_config.numjobs();\n\n  // Start all threads and run the proper operations on the files\n  for (uint32_t i = 0; i < num_jobs; i++) {\n    // Arguments passed to the thread are allocated dynamically and then\n    // deleted by the thread at the end of the StartRoutine method\n    pthread_t thread;\n    struct ConfIdStruct* arg_thread = new ConfIdStruct(config, i);\n    vect_threads.push_back(thread);\n    ThreadStart(vect_threads[i], StartRoutine, (void*) arg_thread);\n  }\n\n  // Join all threads and collect the results for the run\n  for (uint32_t i = 0; i < num_jobs; i++) {\n    Result* ret_result;\n    pthread_join(vect_threads[i], (void**) &ret_result);\n\n    if (ret_result != NULL) {\n      merged_result.Merge(*ret_result);\n      delete ret_result;\n    }\n  }\n\n  // Write the configuration and final result object to the file\n  ProtoWriter writer(outputFile);\n\n  if (!writer(config.GetPbConfig()) || !writer(merged_result.GetPbResult())) {\n    cerr << \"Errot while writing config and result objects to file. \" <<\n         endl;\n    exit(-1);\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Run benchmark using process\n//------------------------------------------------------------------------------\nvoid\nRunProcessConfig(Configuration& config, const string& outputFile)\n{\n  // Use pipes to send back information to parent\n  Result merged_result;\n  ConfigProto& ll_config = config.GetPbConfig();\n  uint32_t num_jobs = ll_config.numjobs();\n  int** pipefd = new int* [num_jobs];\n\n  for (unsigned int i = 0; i < num_jobs; i++) {\n    pipefd[i] = new int[2];\n\n    if (pipe(pipefd[i]) == -1) {\n      eos_static_err(\"error=error opening pipe\");\n      exit(-1);\n    }\n  }\n\n  size_t buff_size;\n  pid_t* cpid = new pid_t[num_jobs];\n\n  for (uint32_t i = 0; i < num_jobs; i++) {\n    cpid[i] = fork();\n\n    if (cpid[i] == -1) {\n      eos_static_err(\"error=error in fork\");\n      exit(-1);\n    }\n\n    if (cpid[i] == 0) {\n      //child process\n      close(pipefd[i][0]);    //close reading end\n      struct ConfIdStruct* arg_process = new ConfIdStruct(config, i);\n      Result* proc_result = static_cast<Result*>(StartRoutine(arg_process));\n      std::string str_result;\n      ResultProto& ll_result = proc_result->GetPbResult();\n#if GOOGLE_PROTOBUF_VERSION < 3004000\n      buff_size = ll_result.ByteSize();\n#else\n      buff_size = ll_result.ByteSizeLong();\n#endif\n      str_result.reserve(buff_size);\n      str_result = ll_result.SerializeAsString();\n      // Write first the size of the result object and then the object itself\n      (void) !write(pipefd[i][1], &buff_size, sizeof(buff_size));\n      (void) !write(pipefd[i][1], str_result.c_str(), buff_size);\n      (void) !close(pipefd[i][1]);  //close writing end\n      delete proc_result;\n      exit(EXIT_SUCCESS);\n    }\n  }\n\n  //............................................................................\n  // Parent process\n  //............................................................................\n  ssize_t nread;\n\n  for (unsigned int i = 0; i < num_jobs; i++) {\n    std::string read_buff;\n    close(pipefd[i][1]);   //close writing end\n    // Read first the size of the result object and then the object itself\n    nread = read(pipefd[i][0], &buff_size, sizeof(buff_size));\n\n    if (nread == -1) {\n      eos_static_err(\"error: failed to read buffer size\");\n      exit(-1);\n    }\n\n    read_buff.resize(buff_size);\n    nread = read(pipefd[i][0], &read_buff[0], buff_size);\n\n    if (nread == -1) {\n      eos_static_err(\"error: failed to read from pipe\");\n      exit(-1);\n    }\n\n    Result* proc_result = new Result();\n    ResultProto& ll_result = proc_result->GetPbResult();\n    ll_result.ParseFromString(read_buff);\n    merged_result.Merge(*proc_result);\n    delete proc_result;\n    waitpid(cpid[i], NULL, 0);   //wait child process\n    close(pipefd[i][0]);         //close reading end\n    cout << \"Finish parent wait\" << endl;\n  }\n\n  //............................................................................\n  // Free memory\n  //............................................................................\n  for (unsigned int i = 0; i < num_jobs; i++) {\n    delete pipefd[i];\n  }\n\n  delete[] pipefd;\n  delete[] cpid;\n  // Write the configuration and final result object to the file\n  ProtoWriter writer(outputFile);\n\n  if (!writer(config.GetPbConfig()) || !writer(merged_result.GetPbResult())) {\n    cerr << \"Error while trying to write config and result objects to file. \" <<\n         endl;\n    exit(-1);\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Do a run using the configuration supplied\n//------------------------------------------------------------------------------\nvoid\nRunConfiguration(const string& configFile, const string& outputFile)\n{\n  Configuration config;\n\n  if (!config.ReadFromFile(configFile)) {\n    cerr << \"Could not read configuration from the input file.\" << endl;\n    exit(-1);\n  }\n\n  ConfigProto pb_config = config.GetPbConfig();\n\n  // Check that the path and files exist\n  if (!config.CheckDirAndFiles()) {\n    cerr << \"Failed while checking dir and files.\" << endl;\n    exit(-1);\n  }\n\n  // Start processing using either threads or processes\n  if (pb_config.jobtype() == ConfigProto_JobType_THREAD) {\n    RunThreadConfig(config, outputFile);\n  } else if (pb_config.jobtype() == ConfigProto_JobType_PROCESS) {\n    RunProcessConfig(config, outputFile);\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Print results from file filtering by the configuration\n//------------------------------------------------------------------------------\nvoid\nPrintResults(const string& resultsFile, const string& configFile)\n{\n  if (resultsFile.empty()) {\n    cerr << \"Results file is empty.\" << endl;\n    return;\n  }\n\n  Configuration* reference_config = 0;\n  typedef std::map< size_t, std::pair< Configuration*, Result* > >\n  MapConfigResults;\n\n  if (!configFile.empty()) {\n    reference_config = new Configuration();\n\n    if (!reference_config->ReadFromFile(configFile)) {\n      cerr << \"Failed to read config from file.\" << endl;\n      delete reference_config;\n      return;\n    }\n\n    reference_config->Print();\n  }\n\n  size_t hash;\n  Configuration* current_config;\n  Result* current_result;\n  ConfigProto* pb_config;\n  ResultProto* pb_result;\n  MapConfigResults map_config;\n  ProtoReader reader(resultsFile);\n\n  do {\n    current_config = new Configuration();\n    current_result = new Result();\n    pb_config = reader.ReadNext<ConfigProto>();\n    pb_result = reader.ReadNext<ResultProto>();\n\n    if (!pb_config || !pb_result) {\n      delete current_config;\n      delete current_result;\n      break;\n    }\n\n    current_config->SetPbConfig(pb_config);\n    current_result->SetPbResult(pb_result);\n    hash = current_config->GetHash();\n\n    // If configuration already in map then just append the new result\n    if (map_config.count(hash)) {\n      Result* ptr_result = map_config[hash].second;\n      ptr_result->Merge(*current_result);\n      delete current_config;\n      delete current_result;\n    } else {\n      map_config[hash] = std::make_pair(current_config, current_result);\n    }\n  } while (1);\n\n  // Print the results matching the configuration supplied\n  if (reference_config) {\n    hash = reference_config->GetHash();\n\n    if (map_config.count(hash)) {\n      Result* ptr_result = map_config[hash].second;\n      ptr_result->Print();\n    } else {\n      cout << \"No matching configuration in the supplied file.\" << endl;\n    }\n  } else {\n    // If there is no reference config then we print all\n    for (MapConfigResults::iterator iter = map_config.begin();\n         iter != map_config.end(); iter++) {\n      current_config = iter->second.first;\n      current_result = iter->second.second;\n      current_config->Print();\n      current_result->Print();\n    }\n  }\n\n  // Free allocated memory\n  for (MapConfigResults::iterator iter = map_config.begin();\n       iter != map_config.end(); iter++) {\n    delete iter->second.first;\n    delete iter->second.second;\n  }\n\n  map_config.clear();\n  delete reference_config;\n  return;\n}\n\nEOSBMKNAMESPACE_END\n\nusing namespace eos::benchmark;\n\n//------------------------------------------------------------------------------\n// Main function\n//------------------------------------------------------------------------------\nint main(int argc, char* argv[])\n{\n  if (argc < 2) {\n    Usage();\n    exit(-1);\n  }\n\n  //..........................................................................\n  // When running in process mode, we have to set XRD_ENABLEFORKHANDLERS=1\n  // which amounts to using the below of the new XrdCl\n  //..........................................................................\n  XrdCl::Env* env = XrdCl::DefaultEnv::GetEnv();\n  env->PutInt(\"RunForkHandler\", 1);\n  bool done_work = false; // true when creating or listing a configurate\n  bool do_run    = false; // mark if we are doing a run on a configuration\n  bool do_print  = false; // mark if we are to print the results from a file\n  string configFile;      // file name holding the run configuration\n  string resultsFile;     // file name holding the run results\n  string outputFile;      // file name where the run results are to saved\n  // Set up the loggin infrastructure\n  eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n  g_logging.SetUnit(\"bmk@localhost\");\n  g_logging.gShortFormat = true;\n  XrdOucString bmk_debug = getenv(\"EOS_BMK_DEBUG\");\n\n  if ((getenv(\"EOS_BMK_DEBUG\")) && (bmk_debug != \"0\")) {\n    g_logging.SetLogPriority(LOG_DEBUG);\n  } else {\n    g_logging.SetLogPriority(LOG_INFO);\n  }\n\n  FILE* fstderr;\n\n  // Open log file\n  if (getuid()) {\n    char logfile[1024];\n    snprintf(logfile, sizeof(logfile) - 1, \"/tmp/eos-fuse.%d.log\", getuid());\n\n    // Running as a user ... we log into /tmp/eos-fuse.$UID.log\n    if (!(fstderr = freopen(logfile, \"a+\", stderr))) {\n      fprintf(stdout, \"error: cannot open bmk log file %s\\n\", logfile);\n    }\n  } else {\n    // Running as root ... we log into /var/log/eos/fuse\n    eos::common::Path cPath(\"/var/log/eos/bmk/bmk.log\");\n    cPath.MakeParentPath(S_IRWXU | S_IRGRP | S_IROTH);\n\n    if (!(fstderr = freopen(cPath.GetPath(), \"a+\", stderr))) {\n      fprintf(stderr, \"error: cannot open bmk log file %s\\n\", cPath.GetPath());\n    }\n  }\n\n  while (1) {\n    static struct option long_options[] = {\n      {\"create-config\", required_argument, 0, 'c'},\n      {\"list-config\",   required_argument, 0, 'l'},\n      {\"list-results\",  required_argument, 0, 'p'},\n      {\"config\",        required_argument, 0, 'f'},\n      {\"run-config\",    required_argument, 0, 'r'},\n      {\"output\",        required_argument, 0, 'o'},\n      {\"help\",          no_argument,       0, 'h'},\n      {0, 0, 0, 0}\n    };\n    // getopt_long stores the option index here\n    int option_index = 0;\n    int c = getopt_long(argc, argv, \"c:l:p:f:r:o:h\",\n                        long_options, &option_index);\n\n    // Detect the end of the options\n    if (c == -1) {\n      break;\n    }\n\n    switch (c) {\n    case 'c': {\n      configFile = optarg;\n      cout << \"Create configuration option with file: \" << configFile << endl;\n      Configuration config;\n      config.CreateConfigFile(configFile);\n      done_work = true;\n      break;\n    }\n\n    case 'l': {\n      configFile = optarg;\n      cout << \"Print configuration file: \" << configFile << endl;\n      Configuration config;\n\n      if (!config.ReadFromFile(configFile)) {\n        cerr << \"Failed to read configuration from file: \" << configFile << endl;\n        exit(-1);\n      }\n\n      config.Print();\n      done_work = true;\n      break;\n    }\n\n    case 'p': {\n      resultsFile = optarg;\n      do_print = true;\n      break;\n    }\n\n    case 'f': {\n      configFile = optarg;\n      cout << \"Filter only the ones matching configuration: \"\n           << configFile << endl;\n      break;\n    }\n\n    case 'r': {\n      configFile = optarg;\n      cout << \"Run configuration: \"\n           << configFile << endl;\n      do_run = true;\n      break;\n    }\n\n    case 'o': {\n      outputFile = optarg;\n      cout << \"Output file for the run : \"\n           << outputFile << endl;\n      break;\n    }\n\n    case '?': {\n      // getopt_long already printed an error message\n      break;\n    }\n\n    default:\n      exit(-1);\n    }\n  }\n\n  if (!done_work) {\n    if (do_run) {\n      if (outputFile.empty()) {\n        cout << \"No output file specified.\" << endl;\n        Usage();\n      } else {\n        // We are about to run a configuration\n        RunConfiguration(configFile, outputFile);\n      }\n    } else if (do_print) {\n      // Print the results from a file optionally matching the supplied config\n      PrintResults(resultsFile, configFile);\n    } else {\n      Usage();\n    }\n  }\n\n  if (fstderr) {\n    fclose(fstderr);\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "test/benchmark/eosbenchmark.hh",
    "content": "//------------------------------------------------------------------------------\n// File: eosbenchmark.hh\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __BMK_EOSBENCHMARK_HH__\n#define __BMK_EOSBENCHMARK_HH__\n\n/*-----------------------------------------------------------------------------*/\n#include <cstdint>\n/*-----------------------------------------------------------------------------*/\n#include \"Namespace.hh\"\n/*-----------------------------------------------------------------------------*/\n\nEOSBMKNAMESPACE_BEGIN\n\n//! Forward declaration\nclass Configuration;\nclass Result;\n\n//! Function signature to be excuted by the jobs\ntypedef void* (*TypeFunc)(void*);\n\n//------------------------------------------------------------------------------\n//! Structure containg the configuration to be excuted and the id of the thread\n//! responsible for the execution\n//------------------------------------------------------------------------------\nstruct ConfIdStruct\n{\n  Configuration& config;\n  uint32_t       id;\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  ConfIdStruct(Configuration& conf, uint32_t index) :\n    config(conf),\n    id(index)\n  {\n    // empty\n  }\n};\n\n\n//------------------------------------------------------------------------------\n//! Print the usage instructions for eosbenchmark command\n//------------------------------------------------------------------------------\nvoid Usage();\n\n\n//------------------------------------------------------------------------------\n//! Start thread executing a particular function\n//!\n//! @param thread thread to be started\n//! @param func function to be executed by the new thread\n//! @param arg arguments passed to the new function\n//!\n//! @return 0 if successfully created the new thread, otherwise return errno\n//!\n//------------------------------------------------------------------------------\nint ThreadStart(pthread_t& thread, TypeFunc func, void* arg);\n\n\n//------------------------------------------------------------------------------\n//! Start routine executed by each thread\n//!\n//! @param arg arguments passed to the start routine\n//!\n//! @return pointer to a data structure to be returned after the finish of the\n//!         excution\n//!\n//------------------------------------------------------------------------------\nvoid* StartRoutine(void* arg);\n\n\n\n//------------------------------------------------------------------------------\n//! Do a run using the configuration supplied specialising later depending on\n//! the configuration parameters\n//!\n//! @param configFile file path for the configuration\n//! @param outputFile file path where the results are saved\n//!\n//------------------------------------------------------------------------------\nvoid RunConfiguration(const std::string& configFile,\n                      const std::string& outputFile);\n\n\n\n//------------------------------------------------------------------------------\n//! Run benchmark using threads\n//!\n//! @param config configuration which is going to be run\n//! @param outputFile file path where the results are saved\n//!\n//------------------------------------------------------------------------------\nvoid RunThreadConfig(Configuration& config, const std::string& outputFile);\n\n\n//------------------------------------------------------------------------------\n//! Run benchmark using processes\n//!\n//! @param config configuration which is going to be run\n//! @param outputFile file path where the results are saved\n//!\n//------------------------------------------------------------------------------\nvoid RunProcessConfig(Configuration& config, const std::string& outputFile);\n\n\n//------------------------------------------------------------------------------\n//! Print results from file filtering by the configuration\n//!\n//! @param resultsFile file containing config<-->results paris from past runs\n//! @param configFile file containing a specific configuration to be displayed\n//!\n//------------------------------------------------------------------------------\nvoid PrintResults(const std::string& resultsFile,\n                  const std::string& configFile);\n\n\nEOSBMKNAMESPACE_END\n\n#endif // __BMK_EOSBENCHMARK_HH__\n"
  },
  {
    "path": "test/eos-accounting-test",
    "content": "#!/bin/bash\n\nset -x\n\nprefix=$1\nhost=${2-\"localhost\"}\nurl=root://$host\nEOS_ACCOUNTING_DIR=$prefix\n\necho \"prefix = $prefix\"\necho \"host = $host\"\necho \"url = $url\"\necho \"EOS_ACCOUNTING_DIR = $EOS_ACCOUNTING_DIR\"\n\ncleanup() {\n  echo \"Cleaning up the test directories\"\n  start_time=$(date +%s)\n  eos rm -rF --no-confirmation $EOS_ACCOUNTING_DIR/\n  end_time=$(date +%s)\n  elapsed=$((end_time - start_time))\n  echo \"Done, elapsed time = $elapsed seconds\"\n}\n\n# Convenient function to test the accounting variables\ntest_accounting() {\n  path=$1\n  expected_treecont=$2\n  expected_treefiles=$3\n  expected_treesize=$4\n  fileinfoRes=`eos fileinfo $path -m`\n  treecont=`echo $fileinfoRes | grep -o 'treecontainers=[^ ]\\+' | cut -d= -f2`\n  treefiles=`echo $fileinfoRes | grep -o 'treefiles=[^ ]\\+' | cut -d= -f2`\n  treesize=`echo $fileinfoRes | grep -o 'treesize=[^ ]\\+' | cut -d= -f2`\n  error=0\n  if [[ $treecont -ne $expected_treecont ]];\n  then\n    echo \"path: \"$path \" expected $expected_treecont treecontainers, got $treecont\"\n    error=1\n  fi\n  if [[ $treefiles -ne $expected_treefiles ]];\n  then\n    error=1\n    echo \"path: \"$path \" expected $expected_treefiles treefiles, got $treefiles\"\n  fi\n  if [[ $treesize -ne $expected_treesize ]];\n  then\n    error=1\n    echo \"path: \"$path \" expected $expected_treesize treesize, got $treesize\"\n  fi\n  if [[ $error -eq 1 ]];\n  then\n    exit 1\n  fi\n}\n\ntest_start_time=$(date +%s)\n\ncleanup\n\neos mkdir -p $EOS_ACCOUNTING_DIR/\n\ntest_accounting $EOS_ACCOUNTING_DIR 0 0 0\n\neos mkdir -p $EOS_ACCOUNTING_DIR/source\neos mkdir -p $EOS_ACCOUNTING_DIR/destination\n\nnDirs=20\nnFiles=10\n\necho \"Creating directory structures for the test...\"\nstart_time=$(date +%s)\n\n# create nDirs directories in each we will create another directory containing a 0-size file (touch)\n# then copy /etc/passwd to the nDirs directories\nfor (( i=0; i<$nDirs; i++ ));\ndo\n  eos mkdir -p $EOS_ACCOUNTING_DIR/source/$i &\ndone\nwait\nfor (( i=0; i<$nDirs; i++ ));\ndo\n  for (( j=0; j<$nFiles; j++));\n  do\n    eos mkdir -p $EOS_ACCOUNTING_DIR/source/$i/dir$j && eos touch $EOS_ACCOUNTING_DIR/source/$i/dir$j/touch$j &\n    eos cp /etc/passwd $url/$EOS_ACCOUNTING_DIR/source/$i/passwd$j &\n  done\n  wait\ndone\n\nend_time=$(date +%s)\nelapsed=$((end_time - start_time))\necho \"Done, elapsed time = $elapsed seconds\"\necho \"Waiting 10 seconds for the accounting to kick in...\"\n# Wait that the accounting thread does its job\nsleep 10\necho \"Done\"\n\ntotalNbFiles=$((2*nFiles*nDirs))\npasswdFileSize=`stat -c%s /etc/passwd`\n# Only size of passwd files (not the touched files!)\ntotalTreeSize=$(($nDirs*$nFiles*passwdFileSize))\n\necho \"Testing accounting of $EOS_ACCOUNTING_DIR...\"\n# Directory located in EOS_ACCOUNTING_DIR should have source+destination+all sources sub directories created above,\n# all the files and a tree size of all passwd files\ntest_accounting $EOS_ACCOUNTING_DIR $((nDirs+(nFiles*nDirs)+2)) $totalNbFiles $totalTreeSize\n# Source directory should have all directories created in the for loop above, all the files\n# and all the tree size of all passwd files\ntest_accounting $EOS_ACCOUNTING_DIR/source $((nDirs+(nFiles*nDirs))) $totalNbFiles $totalTreeSize\n# Destination has nothing in it\ntest_accounting $EOS_ACCOUNTING_DIR/destination 0 0 0\necho \"Done\"\n\n# Perform a parallel directory move from source to destination\necho \"Will perform directory move from $EOS_ACCOUNTING_DIR/source/\\$i to $EOS_ACCOUNTING_DIR/destination/\\$i ...\"\nstart_time=$(date +%s)\nfor (( i=0; i<$nDirs; i++ ));\ndo\n  eos mv $EOS_ACCOUNTING_DIR/source/$i $EOS_ACCOUNTING_DIR/destination/$i &\ndone\nwait\necho \"done move from $EOS_ACCOUNTING_DIR/source/$i to $EOS_ACCOUNTING_DIR/destination/$i\"\nend_time=$(date +%s)\nelapsed=$((end_time - start_time))\necho \"Elapsed time = $elapsed seconds\"\n\necho \"Waiting 10 seconds for the accounting to kick in...\"\n# Wait that the accounting thread does its job\nsleep 10\necho \"Done\"\n\necho \"Testing accounting of $EOS_ACCOUNTING_DIR...\"\n# EOS_ACCOUNTING_DIR's accounting should be the same as previously\ntest_accounting $EOS_ACCOUNTING_DIR $((nDirs+(nFiles*nDirs)+2)) $totalNbFiles $totalTreeSize\n\n# Source should have all counters updated to 0\ntest_accounting $EOS_ACCOUNTING_DIR/source 0 0 0\n# Destination should have the same accounting as the source one previously\ntest_accounting $EOS_ACCOUNTING_DIR/destination $((nDirs+(nFiles*nDirs))) $totalNbFiles $totalTreeSize\necho \"Done\"\n\n# Perform the deletion of half of the directories created in the destination\necho \"Performing the deletion of half of the directories created in the destination\"\nstart_time=$(date +%s)\nfor (( i=0; i<$nDirs; i+=2 ));\ndo\n  echo \"eos rm -rF --no-confirmation $EOS_ACCOUNTING_DIR/destination/$i\"\n  eos rm -rF --no-confirmation $EOS_ACCOUNTING_DIR/destination/$i &\ndone\nwait\nend_time=$(date +%s)\nelapsed=$((end_time - start_time))\necho \"Elapsed time = $elapsed seconds\"\n\n# Then move the destination directory\necho \"Moving $EOS_ACCOUNTING_DIR/destination/ to  $EOS_ACCOUNTING_DIR/source\"\nstart_time=$(date +%s)\neos mv $EOS_ACCOUNTING_DIR/destination/ $EOS_ACCOUNTING_DIR/source\nend_time=$(date +%s)\nelapsed=$((end_time - start_time))\necho \"Elapsed time = $elapsed seconds\"\n\n# Check the recompute_tree_size (we did not give the time for the accounting thread to do its job)\necho \"Launching eos ns recompute_tree_size $EOS_ACCOUNTING_DIR/\"\nstart_time=$(date +%s)\neos ns recompute_tree_size $EOS_ACCOUNTING_DIR/\nend_time=$(date +%s)\nelapsed=$((end_time - start_time))\necho \"Elapsed time = $elapsed seconds\"\n\necho \"Testing the accounting of $EOS_ACCOUNTING_DIR/source\"\n# Source directory has now the destination directory + all directories under it that got divided by two by the previous deletion\ntest_accounting $EOS_ACCOUNTING_DIR/source $(((nDirs+(nFiles*nDirs))/2+1)) $((totalNbFiles/2)) $((totalTreeSize/2))\necho \"Done\"\n\ncleanup\n\ntest_end_time=$(date +%s)\ntest_elapsed=$((test_end_time - test_start_time))\necho \"Test elapsed time = $test_elapsed seconds\"\nexit 0"
  },
  {
    "path": "test/eos-acl-concurrent",
    "content": "#!/bin/bash\nset -x\n\n#-------------------------------------------------------------------------------\n# File: eos-acl-concurrent\n# Author: Elvin-Alin Sindrilaru - CERN\n#-------------------------------------------------------------------------------\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2025 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\n#------------------------------------------------------------------------------\n# Description: Script testing concurrent recurssive modifications of ACLs\n#\n# Usage:\n# eos-acl-concurrent <eos_directory>\n#------------------------------------------------------------------------------\n\n# Helper cleanup method\ncleanup() {\n  for i in {1..5}; do\n    for j in {1..10}; do\n      eos rmdir ${EOS_ACL_DIR}/level1_$i/level2_$j &\n    done\n\n    wait\n    eos rmdir ${EOS_ACL_DIR}/level1_$i\n  done\n\n  eos rmdir ${EOS_ACL_DIR}\n}\n\n# Create directory structure used for testing\nfunction create_hierarchy() {\n  local EOS_DIR=$1\n\n  for i in {1..5}; do\n    for j in {1..10}; do\n      eos mkdir -p ${EOS_DIR}/level1_$i/level2_$j; eos chmod 2777 ${EOS_DIR}/level1_$i/level2_$j &\n    done\n\n    wait\n  done\n}\n\n# Method doing recursive ACL modifcations\nfunction acl_recursive_modify() {\n  local EOS_DIR=$1\n  local ACL_RULE=$2\n  eos acl --recursive ${ACL_RULE} ${EOS_DIR}\n}\n\n# Method doing recursive ACL modifcations\nfunction acl_check_expectations() {\n  local EOS_DIR=$1\n\n  for i in {1..5}; do\n    #ACTUAL_ACLS=$(eos acl -l ${EOS_DIR}/level1_$i | grep -v \"^#\")\n    ACTUAL_ACLS=$(eos attr get sys.acl ${EOS_DIR}/level1_$i)\n\n    for acl in \"${EXPECTED_ACLS[@]}\"; do\n      echo ${ACTUAL_ACLS} | grep ${acl}\n\n      if [[ $? -ne 0 ]]; then\n        echo \"error: directory ${EOS_DIR}/level1_$i unexpected ACLs\"\n        exit 1\n      fi\n    done\n\n    for j in {1..10}; do\n      #ACTUAL_ACLS=$(eos acl -l ${EOS_DIR}/level1_$i/level2_$j | grep -v \"^#\")\n      ACTUAL_ACLS=$(eos attr get sys.acl ${EOS_DIR}/level1_$i/level2_$j)\n\n      for acl in \"${EXPECTED_ACLS[@]}\"; do\n        echo ${ACTUAL_ACLS} | grep ${acl}\n\n        if [[ $? -ne 0 ]]; then\n          echo \"error: directory ${EOS_DIR}/level1_$i/level2_$j unexpected ACLs\"\n          exit 1\n        fi\n      done\n    done\n  done\n}\n\nif [[ $# -ne 1 ]]; then\n  echo \"Usage: $0 <eos_dir>\"\n  exit 1\nfi\n\ntrap cleanup EXIT\nEOS_ACL_DIR=$1\ncreate_hierarchy ${EOS_ACL_DIR}\nVECT_PIDS=()\nEXPECTED_ACLS=()\n\n# Launch 4 jobs in parallel\nfor i in {1..8}; do\n  acl_recursive_modify ${EOS_ACL_DIR} \"u:1000${i}=rwx\" &\n  VECT_PIDS+=($!)\n  EXPECTED_ACLS+=(\"u:1000${i}:rwx\")\ndone\n\n# Wait for all subrpcessed to finish\nfor pid in \"${VECT_PIDS[@]}\"; do\n  wait $pid\ndone\n\nacl_check_expectations ${EOS_ACL_DIR}\nexit 0\n"
  },
  {
    "path": "test/eos-altxs-test",
    "content": "#!/bin/bash\n\n#------------------------------------------------------------------------------\n# File: eos-altxs-test\n# Author: Gianmaria Del Monte - CERN\n#------------------------------------------------------------------------------\n\n#/************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2025 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************/\n\n# This script will test the alternative checksum computation\n# in different scenarios, testing both synchronous and asynchronous\n# computation when the file is uploaded, as well as changes in the\n# configuration in the namespace, and then its synchronization on\n# the FSTs.\n\nset -x\n\nSCRIPTPATH=\"$( cd \"$(dirname \"$0\")\" >/dev/null 2>&1 ; pwd -P )\"\nsource ${SCRIPTPATH}/eos-test-utils\n\nif [[ $# -ne 1 ]]; then\n  echo \"Usage: $0 <eos_mgm_hostname>\"\n  exit 1\nfi\n\nfunction compute_xs() {\n  local type=$1\n  local file=$2\n\n  case $type in\n    md5)\n      md5sum \"${file}\" | awk '{print $1}'\n      ;;\n\n    sha)\n      sha1sum \"${file}\" | awk '{print $1}'\n      ;;\n\n    sha256)\n      sha256sum \"${file}\" | awk '{print $1}'\n      ;;\n\n    adler)\n      eos-adler32 \"${file}\" | awk -F'adler32=' '{print $2}'\n      ;;\n\n    *)\n      echo \"Checksum type not recognised\"\n      exit 1\n      ;;\n  esac\n}\n\nfunction check_checksums() {\n  local eos_file=$1\n  local local_file=$2\n  local checksums=($3)\n\n  altchecksums=$(eos -j root://$EOS_MGM_HOSTNAME file info \"$eos_file\" | jq '.altchecksums')\n  if [[ $(jq 'length' <<< $altchecksums) -ne \"${#checksums[@]}\" ]]; then\n    return 1\n  fi\n\n  for xs in \"${checksums[@]}\"; do\n    stored=$(jq -r \".[] | select(.type==\\\"${xs}\\\") | .value\" <<< $altchecksums)\n    if [ -z \"${stored}\" ]; then\n      echo \"$xs checksum expected to be stored, but not found\"\n      return 1\n    fi\n\n    computed=$(compute_xs $xs \"${local_file}\")\n    if [[ \"${stored}\" != \"${computed}\" ]]; then\n      echo \"$xs checksums differ: stored=\\\"$stored\\\" computed=\\\"$computed\\\"\"\n      return 1\n    fi\n  done\n\n  return 0\n}\n\nfunction enable_frequent_scanning() {\n  local time=5\n  for fsid in $(eos -j fs ls | jq '.result[].id'); do\n    eos fs config ${fsid} scan_disk_interval=${time}\n  done\n}\n\nfunction disable_frequent_scanning() {\n  local time=14400\n  for fsid in $(eos -j fs ls | jq '.result[].id'); do\n    eos fs config ${fsid} scan_disk_interval=${time}\n  done\n}\n\nfunction enable_altxs_scan() {\n  local time=$1\n  for fsid in $(eos -j fs ls | jq '.result[].id'); do\n    eos fs config ${fsid} scan_altxs_interval=${time}\n  done\n}\n\nfunction enable_altxs_sync() {\n  local time=$1\n  for fsid in $(eos -j fs ls | jq '.result[].id'); do\n    eos fs config ${fsid} altxs_sync=1\n    eos fs config ${fsid} altxs_sync_interval=${time}\n  done\n}\n\nfunction disable_altxs_sync() {\n  for fsid in $(eos -j fs ls | jq '.result[].id'); do\n    eos fs config ${fsid} altxs_sync=0\n    eos fs config ${fsid} altxs_sync_interval=0\n  done\n}\n\nfunction disable_altxs_scan() {\n  enable_altxs_scan 0\n}\n\nfunction enable_altxs_upload() {\n  for space in $(eos -j space ls | jq -r '.result[].name'); do\n    eos space config $space space.altxs=on\n  done\n}\n\nfunction disable_altxs_upload() {\n  for space in $(eos -j space ls | jq -r '.result[].name'); do\n    eos space config $space space.altxs=off\n  done\n}\n\nEOS_MGM_HOSTNAME=$1\nEOS_ALTXS_TEST=/eos/dockertest/altxs_test\n\necho \"\"\necho \"###############################################################################\"\necho \"#  EOS Alternative Checksums Test - ${EOS_MGM_HOSTNAME}\"\necho \"###############################################################################\"\n\nfunction phase() {\n  local num=$1\n  local title=$2\n  echo \"\"\n  echo \"===============================================================================\"\n  echo \"  TEST ${num}: ${title}\"\n  echo \"===============================================================================\"\n}\n\n# Create dummy test files\nTEST_FILE=/var/tmp/file_altxs.dat\nTEST_FILE2=/var/tmp/file_altxs.dat\n\ndd if=/dev/urandom of=${TEST_FILE} bs=1M count=16 &> /dev/null\ndd if=/dev/urandom of=${TEST_FILE2} bs=1M count=64 &> /dev/null\n\n# Enable debug on FSTs\n#eos debug debug \"*\"\n\n# Enable frequent disk scanning\nenable_frequent_scanning\n\n# Test 1\nphase 1 \"Upload with altxs enabled - checksums computed on upload\"\n# Disable ScanDir thread for computing alternative checksums and\n# enable on the EOS directory the alternative checksums and check\n# if they were computed\ndisable_altxs_scan\ndisable_altxs_sync\neos mkdir -p ${EOS_ALTXS_TEST}/enabled\neos attr set sys.altxs=\"md5,sha1\" ${EOS_ALTXS_TEST}/enabled\nenable_altxs_upload\nxrdcp -f --nopbar ${TEST_FILE} root://${EOS_MGM_HOSTNAME}/${EOS_ALTXS_TEST}/enabled/altxs.dat\nsleep 5 # Wait the FST to commit the alt checksums\nfail_on_error check_checksums \"${EOS_ALTXS_TEST}/enabled/altxs.dat\" \"${TEST_FILE}\" \"md5 sha\"\n\n# Test 2\n# Enable alternative checksums after having uploaded the file\ndisable_altxs_scan\ndisable_altxs_sync\neos mkdir -p ${EOS_ALTXS_TEST}/disabled\neos attr rm sys.altxs ${EOS_ALTXS_TEST}/disabled || true\nxrdcp -f --nopbar ${TEST_FILE} root://${EOS_MGM_HOSTNAME}/${EOS_ALTXS_TEST}/disabled/altxs.dat\neos attr set sys.altxs=\"md5,sha1,sha256\" ${EOS_ALTXS_TEST}/disabled\nenable_altxs_sync 5\nenable_altxs_scan 5\ncheck_until_no_errors \"check_checksums \\\"${EOS_ALTXS_TEST}/disabled/altxs.dat\\\" \\\"${TEST_FILE}\\\" \\\"md5 sha sha256\\\"\" 300 \"disabled/altxs.dat checksums\"\n\n# Test 3\nphase 3 \"Overwrite file - checksums updated on re-upload\"\n# Check if after having overwritten the file, the alternative checksums are updated accordingly\ndisable_altxs_scan\ndisable_altxs_sync\neos mkdir -p ${EOS_ALTXS_TEST}/updated\neos attr set sys.altxs=\"sha1,sha256\" ${EOS_ALTXS_TEST}/updated\nenable_altxs_upload\nxrdcp -f --nopbar ${TEST_FILE} root://${EOS_MGM_HOSTNAME}/${EOS_ALTXS_TEST}/updated/altxs.dat\nxrdcp -f --nopbar ${TEST_FILE2} root://${EOS_MGM_HOSTNAME}/${EOS_ALTXS_TEST}/updated/altxs.dat\nfail_on_error check_checksums \"${EOS_ALTXS_TEST}/updated/altxs.dat\" \"${TEST_FILE2}\" \"sha sha256\"\n\n# Test 4\nphase 4 \"Verify command - manual trigger computes and commits checksums\"\n# Check that triggering the verify command, the alternative checksums are computed and committed\ndisable_altxs_sync\ndisable_altxs_scan\neos mkdir -p ${EOS_ALTXS_TEST}/triggered\neos attr rm sys.altxs ${EOS_ALTXS_TEST}/triggered || true\nenable_altxs_upload\nxrdcp -f --nopbar ${TEST_FILE} root://${EOS_MGM_HOSTNAME}/${EOS_ALTXS_TEST}/triggered/altxs.dat\neos attr set sys.altxs=\"md5,sha1,sha256\" ${EOS_ALTXS_TEST}/triggered\neos file verify ${EOS_ALTXS_TEST}/triggered/altxs.dat -checksum -commitchecksum\ncheck_until_no_errors \"check_checksums \\\"${EOS_ALTXS_TEST}/triggered/altxs.dat\\\" \\\"${TEST_FILE}\\\" \\\"md5 sha sha256\\\"\" 300 \"triggered/altxs.dat checksums\"\n\n# Test 5\nphase 5 \"Add checksum types - new types computed via sync/scan\"\n# Check that if there are already alternative checksums, adding others will compute the new ones\ndisable_altxs_sync\ndisable_altxs_scan\neos mkdir -p ${EOS_ALTXS_TEST}/added\neos attr set sys.altxs=\"md5\" ${EOS_ALTXS_TEST}/added\nenable_altxs_upload\nxrdcp -f --nopbar ${TEST_FILE} root://${EOS_MGM_HOSTNAME}/${EOS_ALTXS_TEST}/added/altxs.dat\neos attr set sys.altxs=\"md5,sha1,sha256\" ${EOS_ALTXS_TEST}/added\nenable_altxs_sync 5\nenable_altxs_scan 5\ncheck_until_no_errors \"check_checksums \\\"${EOS_ALTXS_TEST}/added/altxs.dat\\\" \\\"${TEST_FILE}\\\" \\\"md5 sha sha256\\\"\" 300 \"added/altxs.dat checksums\"\n\n# Test 6\nphase 6 \"Remove checksum types - removal reflected in namespace\"\n# Check that removing alternative checksums, the removal is reflected on namespace\ndisable_altxs_sync\ndisable_altxs_scan\neos mkdir -p ${EOS_ALTXS_TEST}/removed\neos attr set sys.altxs=\"md5,sha1\" ${EOS_ALTXS_TEST}/removed\nenable_altxs_upload\nxrdcp -f --nopbar ${TEST_FILE} root://${EOS_MGM_HOSTNAME}/${EOS_ALTXS_TEST}/removed/altxs.dat\neos attr -r set sys.altxs=\"sha1\" ${EOS_ALTXS_TEST}/removed\nenable_altxs_sync 5\nenable_altxs_scan 5\ncheck_until_no_errors \"check_checksums \\\"${EOS_ALTXS_TEST}/removed/altxs.dat\\\" \\\"${TEST_FILE}\\\" \\\"sha\\\"\" 300 \"removed/altxs.dat checksums\"\n\n# Test 7\nphase 7 \"Altxs disabled on upload - no checksums computed\"\n# Check that disabling the computation of the alternative checksums on file upload, this doesn't get computed\ndisable_altxs_sync\ndisable_altxs_scan\neos mkdir -p ${EOS_ALTXS_TEST}/disabled_on_upload\neos attr set sys.altxs=\"md5\" ${EOS_ALTXS_TEST}/disabled_on_upload\ndisable_altxs_upload\nxrdcp -f --nopbar ${TEST_FILE} root://${EOS_MGM_HOSTNAME}/${EOS_ALTXS_TEST}/disabled_on_upload/altxs.dat\nfail_on_error check_checksums \"${EOS_ALTXS_TEST}/disabled_on_upload/altxs.dat\" \"${TEST_FILE}\" \"\"\n\n# Restore disk scanning to the default value\ndisable_frequent_scanning\n"
  },
  {
    "path": "test/eos-backup",
    "content": "#!/usr/bin/env python3\n\n# Sample backup and restore utility that simply backs up EOS files iunder /eos/pathName\n# to local directories /BackupPrefix.something\n\n# eos-backup clone -B /BackupPrefix [-P <parentId>] /eos/pathName\n#   - performs the actual clone, based on parentId or full, creates catalogFile\n#     Outputs \"cloneId <cloneId> catalog <catalogFile>\"\n#\n# eos-backup backup -B /BackupPrefix [-F catalogFile] [-P <parentId>] /eos/pathName\n#   - unless specified with '-F' uses \"clone\" to produce a clone and catalogFile\n#     and then backs up files into /BackupPrefix/cloneId and deletes the clone.\n#     Outputs \"cloneId <cloneId> backup media <backupDir>\" followed by whatever\n#     the deletion of the clone says.\n#\n# eos-backup restore -F /inputCatalog1[,/inputCatalog2[,...]] /outputDirectory\n#   - performs a restore into outputDirectory based on 1 or more catalogFiles\n\n# for clone/backup the data are stored in /BackupPrefix/cloneId and /BackupPrefix/cloneId.catalog\n# the /eos pathnames are as seen by the MGM, not necessarily fuse-mounted paths in the local file system\n\n# environment:\n#   EOS_MGM_URL=root://eos-mgm-test.eoscluster.cern.ch must point to the MGM serving /eos/pathName\n\n# A primitive full_backup/make_changes/incremental_backup/restore example in bash:\n\n#   # full clone and backup in one go\n#   read xx cloneId1 xxx media1 <<<$(eos-backup backup -B /tmp/Backup /eos/dockertest/backuptest)\n#\n#   : make some changes, delete/add/modify files\n#\n#   # an incremental clone based on first backup\n#   read xx cloneId2 xxx cloneFile2 <<<$(eos-backup clone -B /tmp/Backup -P $cloneId1 /eos/dockertest/backuptest)\n#\n#   # back those files up\n#   read xx cloneId2 xxx media2 <<<$(eos-backup backup -B /tmp/Backup -F cloneFile2 /eos/dockertest/backuptest)\n#\n#   # restore the lot\n#   eos-backup restore -F $media1/catalog,$media2/catalog -B /tmp/Backup /tmp/Restore2\n\n   \n\nfrom __future__ import print_function\n\nimport getopt, json, os, re, signal, stat, subprocess, sys, tempfile, threading, time, traceback\nimport pdb\n\n    \n# allow for orderly termination\nstop_requested = False\ndef request_stop(signum, frame):\n    global stop_requested\n    stop_requested = True\n\nsignal.signal(signal.SIGINT, request_stop)\n\n\n# options\ntry: \n    subcmd = sys.argv[1]\n    opts, args = getopt.getopt(sys.argv[2:], \"B:F:P:U:bvS:\")\nexcept:\n    print(\"Unable to parse arguments\")\n    sys.exit(1)\n\n# default backupDirPrefix and parentId\ndflt = [('-B', \"/tmp/backupDir\"),('-P', 10),('-S',1024*1024*1024)]\n\n# resulting options\nopt = dict(dflt+opts)\n\nif '-U' not in opt:\n    opt['-U'] = os.getenv(\"EOS_MGM_URL\")\nif opt['-U'] is None:\n    print(\"specify a valid URL ('-U root://my_mgm')\", file=sys.stderr)\n    sys.exit(1)\n\ntry:\n    os.putenv(\"XRD_WORKERTHREADS\", \"50\")            # os.environ would not work here\n    from XRootD import client\n    from XRootD.client.flags import MkDirFlags, OpenFlags\n    canXroot = True\n\n    copyprocess = client.CopyProcess()\n    copyjobs = []\n    Ufs = client.FileSystem(opt['-U'])\nexcept:\n    canXroot = False\n\nif '-P' in opt:\n    try:\n        parentId = int(opt['-P'])\n    except ValueError:\n            print(\"invalid parent Id\", file=sys.stderr)\n            sys.exit(22)\n\nopt['-S'] = int(opt['-S'])\n\nif opt['-B'].startswith(\"root:\"):\n  if not canXroot:\n    print(\"backup media specify 'xroot://' but XRootD.client module did not load\", file=sys.stderr)\n    sys.exit(1)\n\n\neos_instance = os.popen(\"eos version\").readlines()[0].split('=')[1].rstrip()\n\ndef localpath(url):\n    xx = re.match(\"^root://\\S+/(/\\S+)\", url)\n    if xx:\n        return xx.group(1)\n    else:\n        return url\n\n# create the clone, results in a catalogFile\ndef do_clone(subcmd):\n    catalogFile = tempfile.mkstemp()\n    \n    if '-P' not in opt: opt['-P'] = 10\n    cmd = \"eos oldfind -j -x sys.clone=+%d %s > %s\" % (int(opt['-P']), args[0], catalogFile[1])\n    try:\n      os.system(cmd)\n    except:\n      print(\"Unexpected error:\", sys.exc_info()[0], file=sys.stderr)\n      print(\"cmd:\", cmd, file=sys.stderr)\n      raise\n\n    line1 = open(catalogFile[1]).readline()\n\n    try:\n      xx = json.loads(line1)\n      cloneId = xx['c']\n    except Exception as e:\n      print(e, \"catalogFile\", catalogFile[1], file=sys.stderr)\n      if hasattr(e, 'msg'): print(e.msg, file=sys.stderr)\n      if hasattr(e, 'message'): print(e.message, file=sys.stderr)\n      print(\"line #1: '%s'\" % line1, file=sys.stderr)\n      raise\n\n    tCatFile = \"%s.%s.clone\" % (catalogFile[1], cloneId)\n    os.rename(catalogFile[1], tCatFile)\n    if subcmd == \"clone\": print(\"cloneId %s catalog %s\" % (cloneId, tCatFile))\n\n    return (cloneId, tCatFile)\n\n# blob: holds a single BIG file, a flat set of files copied with xrdcp,\n# or a flat set of files copied asynchronously\nBLOBS = []\nblobCV = threading.Condition()\nblobq = []\nblob_slots = 6              # run slots\nblobs_active = []\n\nclass Blob:                 # the files where data are stored in\n    def __init__(self, prefix, isCopy=False):\n        # blob chain management\n        self.blob_num = len(BLOBS)\n        BLOBS.append(self)\n        self.prefix = prefix\n        self.isCopy = isCopy\n        self.closing = False\n        self.blobname = \"%sb.%d\" % (prefix, self.blob_num)\n        self.size = 0               # current file size\n        self.csize = 0              # current commited size\n\n        # files to be transferred into this blob (or placeholder for copy-blobs and xrdcp)\n        self.bufsz = 1<<24                          # 16 MB\n        self.items = []                             # list of (jsonTag, path, cpath, urlopts)\n        self.currItem = -1                          # item current being backed up\n        self.state = -1                             # stopped=-1, idle=0, copy_clone=1, copy_live=2, override=3, ...    in close=98, closed=99\n\n        self.blXroot = canXroot and prefix.startswith(\"root:\")\n        if self.blXroot:\n            if not self.isCopy:\n                self.bfile = client.File()\n                self.bfile.open(self.blobname, OpenFlags.NEW)\n\n                def cb(st, resp, hotlist):\n                    with blobCV:\n                        blobq.append((self, (st, resp, hotlist,)))\n                        blobCV.notify()\n\n                self.XrdCB = cb\n\n        else:\n            self.bfile = open(self.blobname, \"wb\")  # 'wb' or 'w' hardly matters, we don't write ourselves\n            os.lseek(self.bfile.fileno(), 0, 0)     # this may be implicit, but vital for the subprocess writes\n\n        print(\"blob\", self.blob_num, f\"created as {self.blobname} copy={self.isCopy}\", file=sys.stderr)\n\n\n    def getBlob(self, sz, noCopy=False):\n        if sz >= opt['-S']/10 and not noCopy:           # create a copyjob item, blob will be a single file\n            new_blob = Blob(self.prefix, isCopy=True)\n            new_blob.csize = sz                         # save projected size, we'll start at offset 0 anyway\n            return (self, new_blob)\n        elif sz > opt['-S']:\n            new_blob = Blob(self.prefix)\n            return (self, new_blob)\n        if self.csize > opt['-S']:\n            if \"-v\" in opt: print(f\"blob {self.blob_num} committed {self.csize} files {len(self.items)}\", file=sys.stderr)\n            new_blob = Blob(self.prefix)\n            new_blob.csize = sz\n            blobs_active.remove(self)\n            blobs_active.append(new_blob)\n            self.close()\n            return (new_blob, new_blob)\n\n        self.csize += sz\n        return (self, self)\n\n    def config_copyjob(self, src, altsrc):              # clone / live file\n        self.state = 0\n        self.src = (src, altsrc)\n        self.trg = \"%sb.%d\" % (self.prefix, self.blob_num)\n        if \"-v\" in opt: print(f\"*debug* copyjob {self.src} -> {self.trg}\", file=sys.stderr)\n        \n        self.srcIndex = -1\n        self.size = self.csize\n\n        # There is a complication here: Ufs.stat() on the clone works (if it exists);\n        # Ufs.stat() on the \"/.fxid:xxxx\" was not always supported in EOS. But since reading\n        # from the live file is plan-B anyway no stat is really needed - just read until csize\n        if self.srcIndex < 0:\n            st, info = Ufs.stat(self.src[1])                # typically tries the clone first\n            print(f\"copyjob st={st.ok}/{st.code} info={info}\", file=sys.stderr)   #debug\n            if st.ok:\n                self.srcIndex = 1\n            else:                                           # else the live file\n                self.srcIndex = 0\n                if \"-v\" in opt: print(f\"Copyjob live file {self.src[0]}\", file=sys.stderr)\n\n                st, info = Ufs.stat(self.src[0])           # this should be supported now\n                if \"-v\" in opt: print(f\"st {st}, info {info}\")\n\n            if st.ok:\n                self.size = info.size\n\n\n        if self.srcIndex < 0:\n            return False\n\n        copyjobs.append(self)\n\n        if \"-v\" in opt:\n            print(\"copy job\", self.src[self.srcIndex], self.blobname, \"size\", self.size, file=sys.stderr)\n        copyprocess.add_job(self.src[self.srcIndex], self.blobname, thirdparty=\"only\")      # could be  \"first\"\n        return True\n\n    def close(self):\n        global blob_slots\n        self.closing = True\n        if self.state == 0:\n            self.state_machine()\n\n        if self.state == 98:\n            self.state = 99\n            if not self.isCopy:\n                self.bfile.close()\n                blob_slots += 1\n                print(f\"blob {self.blobname} closed size {self.size} slots {blob_slots}\", file=sys.stderr)\n\n                for n in range(self.blob_num, len(BLOBS)):\n                    if BLOBS[n].state == -1:\n                        BLOBS[n].run()\n                        break\n\n    def addItem(self, json, path, cpath, urlopts=\"\"):\n        if path is not None:\n            path = \"/.fxid:\" + cpath.split('/')[-1]     # access by fxid, protects against renames\n\n        if self.isCopy:         # use a more efficient (3rd-party) xrootd copy into a single blob\n            json['b'] = \"%d:%d:%d\" % (self.blob_num, 0, self.csize)\n            self.items.append((json, None, None, None,))\n            self.config_copyjob(\"%s/%s\" % (opt['-U'], path), \"%s/%s\" % (opt['-U'], cpath))\n        else:\n            self.items.append((json, path, cpath, urlopts,))\n            self.run()\n\n    def run(self):\n        global blob_slots\n\n        if self.state == -1:\n            if blob_slots <= 0: return\n            blob_slots -= 1\n            self.state = 0                          # set to idle\n\n        if self.state == 0:                         # is idle\n            self.state_machine()\n\n    def state_machine(self, cbargs=()):\n        if len(cbargs) == 3 and self.state >= 0 and self.state <=9:\n            st, resp, hotlist = cbargs\n            i = self.items[self.currItem]\n            self.continue_read(*cbargs)\n\n        if self.state == 0:                             # idle\n            self.pick_next_element()\n\n        if self.state == 98:\n            self.close()\n\n    # This is the part that reads files or clones\n    def pick_next_element(self):                        # only called with self.state == 0!\n        item = None\n        for i in range(self.currItem+1, len(self.items)):\n            self.currItem = i\n            if self.items[i][1] is not None:            # this could be a non-File (e.g. Directory) entry\n                item = self.items[i]                    # (jsonTag, path, cpath, urlopts=\"\")\n                break\n\n        if item is None:                                \n            if self.closing: self.state = 98\n            return\n\n        self.sm_oldSize = self.size                     # remember where this file started in the blob\n\n        # try copy the clone first\n        self.sm_src = client.File()\n        self.state = 1                      # copy clone\n        path = \"%s/%s%s\" % (opt['-U'], item[2], item[3])\n        st, xx = self.sm_src.open(path)\n\n        if st.ok:\n            if \"-v\" in opt: print(f\"Copying clone {path} for {item[1]}\", file=sys.stderr)\n        else:       # copy live file\n            self.sm_src = client.File()\n            path = \"%s/%s%s\" % (opt['-U'], item[1], item[3])\n            st, xx = self.sm_src.open(path)\n            self.state = 2  # copying live file\n\n        # start the first read\n        self.sm_offset = 0\n        try:\n            st = self.sm_src.read(offset=self.sm_offset, size=self.bufsz, callback=self.XrdCB)\n            if \"-v\" in opt: print(f\"1st read blob {self.blob_num} file {path} st.ok {st.ok} st.code {st.code}\", file=sys.stderr)\n            if st.ok: return                # state machine continues after callback\n\n        except Exception as e:\n            exc_type, exc_obj, exc_tb = sys.exc_info()\n            print(f\"line {exc_tb.tb_lineno} error {e} reading {item[1]} @{self.sm_offset}:{self.bufsz}\", file=sys.stderr)\n            if hasattr(e, 'msg'): print(e.msg, file=sys.stderr)\n            if hasattr(e, 'message'): print(e.message, file=sys.stderr)\n            self.state = 0\n            self.state_machine()\n\n\n    def continue_read(self, st, buff, hl):              # handle callback with data\n        if not st.ok:\n            print(f\"failed read {st} blob {self.blob_num} file {self.currItem} '{self.items[self.currItem]}'\", file=sys.stderr)\n            self.sm_src.close()\n            self.state = 0\n        else:                               # read completed, buff holds data\n            # sanity checks\n            try:\n                sz = int(eval(self.items[self.currItem][0]['st'])[stat.ST_SIZE])\n                if len(buff) > sz:\n                    raise AssertionError(\"buffer exceeds file size\")\n                if self.sm_offset > sz:\n                    raise AssertionError(\"sm_offset %d exceeds file size: %s\" %(self.sm_offset, self.items[self.currItem][0]))\n                if self.size-self.sm_oldSize > sz:\n                    raise AssertionError(\"blob space used exceeds file size\")\n            except Exception as e:\n                exc_type, exc_obj, exc_tb = sys.exc_info()\n                print(f\"line {exc_tb.tb_lineno} error {e}\", file=sys.stderr)\n                pdb.set_trace()\n\n            if len(buff) > 0:               # write buffer, read next one\n\n                st2, xx = self.bfile.write(buff, offset=self.size)\n                self.size += len(buff)\n                self.sm_offset += len(buff)\n\n                # sanity check\n                try:\n                  sz = int(eval(self.items[self.currItem][0]['st'])[stat.ST_SIZE])\n                  if self.sm_offset > sz:\n                    raise AssertionError(\"sm_offset %d exceeds file size: %s\" %(self.sm_offset, self.items[self.currItem][0]))\n                except Exception as e:\n                    exc_type, exc_obj, exc_tb = sys.exc_info()\n                    print(f\"line {exc_tb.tb_lineno} error {e}\", file=sys.stderr)\n                    pdb.set_trace()\n\n                st2 = self.sm_src.read(offset=self.sm_offset, size=self.bufsz, callback=self.XrdCB)\n            else:                           # a zero length read signals EOF\n                item = self.items[self.currItem]\n                rc = st.errno\n                self.sm_src.close()\n\n                item[0]['b'] = \"%d:%d:%d\" % (self.blob_num, self.sm_oldSize, self.size-self.sm_oldSize)\n                \n                if self.state == 2:         # finished live file, try the clone again\n                    st2, xx = self.sm_src.open(\"%s/%s%s\" % (opt['-U'], item[2], item[3]))\n                    if not st2.ok:          # can't read clone, the live file be it then\n                        self.state = 0\n\n                    else:                   # clone should overwrite what we just copied\n                        self.state = 3      # override with clone\n                        self.sm_offset = 0\n                        self.size = self.sm_oldSize\n                        self.bfile.truncate(self.size)\n                        try:\n                            st2 = self.sm_src.read(offset=self.sm_offset, size=self.bufsz, callback=self.XrdCB)\n                            if \"-v\" in opt: print(\"1st read clone ok\", st.ok, \"code\", st.code, file=sys.stderr)\n                        except Exception as e:\n                            exc_type, exc_obj, exc_tb = sys.exc_info()\n                            print(f\"line {exc_tb.tb_lineno} error {e} reading {item[2]} @{self.sm_offset}:{self.bufsz}\", file=sys.stderr)\n                            if hasattr(e, 'msg'): print(e.msg, file=sys.stderr)\n                            if hasattr(e, 'message'): print(e.message, file=sys.stderr)\n                            self.state = 0\n                elif self.state == 3:\n                    self.state = 0\n                else:\n                    self.state = 0\n\n\n\n\ndef copy_xrdcp(path, cpath, blob):\n    startpos = blob.size        # blob.bfile.tell() would be useless, pertubated by subprocess writes\n    # try copy the clone first\n    p1 = subprocess.run([\"xrdcp\", \"-s\", \"%s/%s\" % (opt['-U'], cpath), \"-\"], stdout=blob.bfile.fileno(), stderr=subprocess.PIPE)\n    x1 = p1.stderr\n    rc = p1.returncode\n    if rc > 256: rc = rc >> 8\t\t# recover xrdcp return code\n    if rc == 0:                     # 54 would mean clone not found\n        blob.size = os.lseek(blob.bfile.fileno(), 0, 1) # get current (new) position\n    else:\n        # copy the live file\n        p2 = subprocess.run([\"xrdcp\", \"-s\", \"%s/%s\" % (opt['-U'], path), \"-\"], stdout=blob.bfile.fileno(), stderr=subprocess.PIPE)\n        x2 = p2.stderr\n        rc = p2.returncode\n        if rc > 256: rc = rc >> 8\t# recover xrdcp return code\n        copied_live = True\n        newpos = os.lseek(blob.bfile.fileno(), 0, 1)    # get current (new) position\n\n        # try the clone again\n        os.lseek(blob.bfile.fileno(), startpos, 0)      # rewind to previous pos\n        p3 = subprocess.run([\"xrdcp\", \"-s\", \"%s/%s\" % (opt['-U'], cpath), \"-\"], stdout=blob.bfile.fileno(), stderr=subprocess.PIPE)\n        x3 = p3.stderr\n        rc = p3.returncode\n        if rc > 256: rc = rc >> 8\t\t                    # recover xrdcp return code\n        if rc > 0:                                          # clone still does not exist\n            os.lseek(blob.bfile.fileno(), newpos, 0)        # rewind to previous pos\n            blob.size = newpos\n        else:\n            copied_live = False                             # correction: copied the clone after all\n            blob.size = os.lseek(blob.bfile.fileno(), 0, 1) # new position\n            # truncate whatever the live copy may have written\n            if blob.size < newpos:\n                os.ftruncate(blob.bfile.fileno(), blob.size)\n\n    return rc\n\n\n\n\nclass MyCopyProgressHandler(client.utils.CopyProgressHandler):\n    def begin(self, id, total, source, target):\n        if \"-v\" in opt: print(\"%s CopyProgress %s/%s\" % (time.strftime(\"%Y%m%d %H%M%S\"), id, total), source, target, file=sys.stderr)\n\n    def end(self, status):\n        if \"-v\" in opt: print(\"CopyProgress end\", file=sys.stderr)\n        #print('end status:', status, file=sys.stderr)\n\n    def update(self, processed, total):\n        if \"-v\" in opt: print(\"CopyProgress update\", file=sys.stderr)\n        #print('processed: %d, total: %d' % (processed, total), file=sys.stderr)\n\ndef myCopyprocess():\n    try:\n        if len(copyjobs) > 0:\n            if \"-v\" in opt: print(\"running %d xrootd copies\" % len(copyjobs), file=sys.stderr)\n            copyprocess.parallel(8)\n            copyprocess.prepare()\n            copies = [None]\n            try:\n                copies = copyprocess.run(MyCopyProgressHandler())\n            except Exception as e:\n                exc_type, exc_obj, exc_tb = sys.exc_info()\n                print(\"Exception %s %r running copy jobs in line %d:\" % (type(e), e, exc_tb.tb_lineno), file=sys.stderr)\n                traceback.print_tb(exc_tb, None, sys.stderr)\n\n            xst = copies[0]\n            if xst == None: raise NameError\n\n            if \"-v\" in opt: print(\"xrootd copies first pass finished, ok=%r message='%s'\" % (xst.ok, xst.message), file=sys.stderr)\n\n            copyjobs2=[]\n            copyprocess2 = client.CopyProcess()\n            try:\n                sts = copies[1]\n                for i in range(len(sts)):\n                    st = sts[i]['status']\n                    if not st.ok:\n                        blob = copyjobs[i]\n                        print(blob.src[blob.srcIndex], \"to\", blob.trg, \": ok\", st.ok, st.message, file=sys.stderr)\n                        blob.srcIndex = (blob.srcIndex+1) % len(blob.src)          # cycle through (=flip) sources\n                        copyjobs2.append(blob)\n                        print(\"retry as:\", blob.src[blob.srcIndex], \"to\", blob.trg, file=sys.stderr)\n                        copyprocess2.add_job(blob.src[blob.srcIndex], blob.trg, force=True)\n            except Exception as e:\n                exc_type, exc_obj, exc_tb = sys.exc_info()\n                print(\"Exception %s re-running copy jobs in line %d:\" % (type(e), exc_tb.tb_lineno), file=sys.stderr)\n                for cc in copies[1]: print(cc, file=sys.stderr)\n\n            if len(copyjobs2) > 0:\n                if \"-v\" in opt: print(\"re-running %d xrootd copies\" % len(copyjobs2), file=sys.stderr)\n                copyprocess2.prepare()\n                xst = None\n                try:\n                    copies2 = copyprocess2.run(MyCopyProgressHandler())\n                    xst = copies2[0]\n                except Exception as e:\n                    exc_type, exc_obj, exc_tb = sys.exc_info()\n                    print(\"Exception %s %r retrying copy jobs in line %d\" % (type(e), e, exc_tb.tb_lineno), file=sys.stderr)\n\n                if xst is None:\n                    for blob in copyjobs2:\n                        print(\"failed:\", blob.src[0], \"or\", blob.src[1], \"to\", blob.trg, file=sys.stderr)\n\n                elif not xst.ok:\n                    print(\"failed to re-run copy jobs\", xst, file=sys.stderr)\n                    try:\n                        for i in range(len(copyjobs2)):\n                            st = copies2[1][i]['status']\n                            if not st.ok:\n                                blob = copyjobs2[i]\n                                print(\"failed:\", blob.src[0], \"or\", blob.src[1], \"to\", blob.trg, file=sys.stderr)\n                    except Exception as e:\n                        exc_type, exc_obj, exc_tb = sys.exc_info()\n                        print(\"Exception %s running copy jobs in line %d:\" % (type(e), exc_tb.tb_lineno), file=sys.stderr)\n                        for cc in copies2[1]: print(cc, file=sys.stderr)\n\n    except Exception as e:\n        exc_type, exc_obj, exc_tb = sys.exc_info()\n        print(\"Oops %s %r running copy jobs in line %d:\" % (type(e), e, exc_tb.tb_lineno), file=sys.stderr)\n        traceback.print_tb(exc_tb, None, sys.stderr)\n\n\n# perform a clone unless a catalogFile has been given, then back up files\ndef do_backup():\n    xxxdebugxxx = True\n    b_Xroot = canXroot and opt['-B'].startswith(\"root:\")\n\n    if '-U' not in opt and not b_Xroot:\n        try:\n            opt['-U'] = os.environ['EOS_MGM_URL']\n            if opt['-U'] == '': raise KeyError\n        except KeyError:\n            print(\"need to specify 'U' or define EOS_MGM_URL in the environment\", file=sys.stderr)\n            sys.exit(1)\n\n    if '-F' in opt:\n        catalogFile = opt['-F']\n    else:\n        cloneId, catalogFile = do_clone(\"backup\")\n    \n    c_Xroot = canXroot and catalogFile.startswith(\"root:\")\n    try:\n        if c_Xroot:\n            f = client.File()\n            f.open(catalogFile)\n        else:\n            f = open(catalogFile, \"r\")\n    except IOError as e:\n        print(\"cannot open catalog: %s\" % e, file=sys.stderr)\n        sys.exit(5)\n\n    #findOut = f.readlines()\n    line1 = f.readline()\n\n    jj = json.loads(line1)\n    cloneId = jj['c']\n    rootPath = jj['n']\n\n    backupDir = \"%s/%s/\" % (opt['-B'].rstrip('/'), cloneId)\n    if b_Xroot:\n        b_fs = client.FileSystem(backupDir)\n        xx = re.match(\"^root://\\S+/(/\\S+)\", backupDir)\n        backupDirPath = xx.group(1)\n        b_fs.mkdir(backupDirPath, MkDirFlags.MAKEPATH)\n        newCatalog = client.File()\n        status, xxx = newCatalog.open(backupDir + \"catalog\", OpenFlags.NEW)\n        newCatalog.write(line1, offset=0)\n        newCatPos = len(line1)\n    else:\n        if not os.path.isdir(backupDir): os.mkdir(backupDir)\n\n        newCatalog = open(backupDir + \"catalog\", \"w\")\n        newCatalog.write(line1)\n\n    num_files = 0\n    for n in range(3): blobs_active.append(Blob(backupDir))\n\n    print(\"cloneId %s backup_media %s\" % (cloneId, backupDir[:-1]))\n\n    cloneDir = \"/eos/%s/proc/clone/\" % (eos_instance[3:] if eos_instance.startswith(\"eos\") else eos_instance)\n\n    while not stop_requested:\n\n        while True:\n            with blobCV:\n                tblob = None            # shouldn't be needed!\n                if len(blobq) == 0: break\n                tblob, cbargs = blobq.pop(0)\n\n            tblob.state_machine(cbargs)\n\n        l = f.readline()\n        if len(l) == 0: break\n\n        curr_blob = blobs_active[int(num_files/1) % len(blobs_active)]\n\n        jj = json.loads(l)\n        fCloneId = jj['c']              # id of clone the file belongs to, might be from an earlier dump\n        path = jj['n']\n        if not path.endswith('/'): \t\t# this is a file\n            clonePath = \"%s/%s\" % (fCloneId, jj['p'])\n        stime = jj['t']\n\n# jj['st']: \"(st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, st_size, st_atime, st_mtime, st_ctime)\"\n\n        # Insert catalog-only entry for a  dir, a file unmodified in incremental dump, or a symlink\n        if path.endswith('/') or fCloneId != cloneId or 'S' in jj:\n            if b_Xroot:\n                curr_blob.addItem(jj, None, None, \"\")\n            else:\n                newCatalog.write(l)\n            continue\t\n\n        # this is a plain file\n        num_files += 1\n        st = eval(jj['st'])\n        curr_blob, tblob = curr_blob.getBlob(st[stat.ST_SIZE], noCopy='H' in jj)\n        cpath = cloneDir + clonePath\n\n        o_opt = ''\n        if 'H' in jj:               # problem encountered: cksum on git hard-links not (always?) updated ??\n            o_opt = \"?eos.checksum=ignore\"\n            if \"-v\" in opt: print(\"Hard link %s copied with %s\" % (path, o_opt), file=sys.stderr)\n\n        if tblob.isCopy or tblob.blXroot:           # add file to the list\n            tblob.addItem(jj, path, cpath, o_opt)\n        else:\n            startpos = tblob.size        # blob.bfile.tell() would be useless, pertubated by subprocess writes\n            copy_xrdcp(path, cpath, tblob)\n\n            jj['b'] = \"%d:%d:%d\" % (tblob.blob_num, startpos, tblob.size-startpos)\n            jxx=json.dumps(jj) + \"\\n\"\n            newCatalog.write(jxx)\n\t    \n        if tblob is not curr_blob:                  # an individual blob for a BIG file\n            tblob.close()\n\t    \n    f.close()\n    for b in blobs_active: b.close()\n\n    copythread = threading.Thread(target=myCopyprocess)\n    copythread.start()\n\n    if b_Xroot:\n        for b in BLOBS:\n            while (not b.isCopy) and b.state != 99:\n                with blobCV:\n                    if blobCV.wait_for(lambda: len(blobq) > 0, timeout=5):\n                        tblob, cbargs = blobq.pop(0)\n                    else:\n                        tblob = None\n                if tblob:\n                    tblob.state_machine(cbargs)\n                elif \"-v\" in opt:\n                    print(f\"Waiting for close on blob {b.blob_num} closing {b.closing} state {b.state} current item {b.currItem+1} (of {len(b.items)})\", file=sys.stderr)\n\n            if \"-v\" in opt:\n                print(f\"blob {b.blob_num} state {b.state} items {len(b.items)}\", file=sys.stderr)\n            for i in b.items:\n                jxx = json.dumps(i[0]) + \"\\n\"\n                newCatalog.write(jxx, offset=newCatPos)\n                newCatPos += len(jxx)\n\n    copythread.join()\n    newCatalog.close()\n\n\n    cmd = \"eos oldfind -f -x sys.clone=-%s %s > /dev/null\" % (cloneId, args[0] if len(args) > 0 else rootPath)\n    os.system(cmd)\n\n# perform a restore based on multiple catalogFiles\ndef do_restore():\n    trg = args[0] + \"/\"\n    catalogFiles = opt['-F'].split(',')\n\n    cat = dict()\n    backupDirs = dict()\n\n    # build a dictionary of files to be restored, implementing the logic to select most recent files\n    for fn in catalogFiles:\n        lastCatFile = fn == catalogFiles[-1]\n\n        c_Xroot = canXroot and fn.startswith(\"root:\")\n        if c_Xroot:\n            cFile = client.File()\n            cFile.open(fn)\n        else: cFile = open(fn, \"r\")\n\n        line1 = cFile.readline()\n        jj = json.loads(line1)\n        cloneId = jj['c']\n        rootPath = jj['n']\n        backupDirs[cloneId] = os.path.dirname(fn)\n\n        lineNum = 0\n        while True:\n            if lineNum > 0:            # next line (except 1st)\n                l = cFile.readline()\n                if len(l) == 0: break\n                try:\n                    jj = json.loads(l)\n                except Exception as e:\n                    print(\"error %r line %d ='%s'\" % (e, lineNum, l), file=sys.stderr)\n                    sys.exit(1)\n\n            lineNum += 1\n            path = jj['n']\n            stime = jj['t']\n\n            rpath = path[len(rootPath):]\t# skip the \"root\" part\n            if rpath in cat and not path.endswith('/'):\n                if stime < cat[rpath]['stime'] or jj['c'] != cloneId:    # file exists but not part of this backup\n                    if lastCatFile: cat[rpath]['keep'] = True\n                    continue\t\t# restore latest version\n            else: cat[rpath] = dict()\n\n            cat[rpath]['stime'] = stime\n            cat[rpath]['c'] = cloneId\n            for k in ['b', 'S', 'H', 'L']:\n                if k in jj: cat[rpath][k] = jj[k]\n\n            if lastCatFile: cat[rpath]['keep'] = True\n\n        cFile.close()\n\n\n    # restore files\n    for p in sorted(cat.keys()):\t# sorted so that dirs come before their files\n        if stop_requested: break\n        if \"/...eos.ino...\" in p: continue   # not a plain file: a deleted hard link target\n        if not 'keep' in cat[p]: continue\n        if p.endswith('/') or p == '':  # a directory\n            os.makedirs(trg + p)\n        elif 'S' in cat[p]:             # this is a symlink\n            os.symlink(cat[p]['S'], f\"{trg}/{p}\")\n        else:\n          try:\n            bIndex, bOffset, bLen = map(int, cat[p]['b'].split(\":\"))\n            blobFn = \"%s/b.%d\" % (backupDirs[cat[p]['c']], bIndex)\n\n            bl_Xroot = canXroot and blobFn.startswith(\"root:\")\n            if bl_Xroot:\n                blobFile = client.File()\n                st, xx = blobFile.open(blobFn)\n                blobOff = bOffset\n                fileOff = 0\n                if not st.ok:\n                    print(f\"Failed to open blob {blobFn}: {st.message}\", file=sys.stderr)\n                    raise IOError\n            else:\n                blobFile = open(blobFn, \"rb\")\n                blobFile.seek(bOffset, 0)\n \n            trgFile = open(\"%s/%s\" % (trg, p), \"wb\")\n            bufsz = 1<<20           # 1 MB\n            while bLen > 0:\n                if bufsz > bLen: bufsz = bLen\n                bLen -= bufsz\n                if bl_Xroot:\n                    st, buff = blobFile.read(offset=blobOff, size=bufsz)\n                    trgFile.write(buff)\n                    blobOff += bufsz\n                    fileOff += bufsz\n                else:\n                    trgFile.write(blobFile.read(bufsz))\n            blobFile.close()\n            trgFile.close()\n          except Exception as e:\n            exc_type, exc_obj, exc_tb = sys.exc_info()\n            print(\"Exception %s in line %d:\" % (type(e), exc_tb.tb_lineno), file=sys.stderr)\n            print(f\"p {p} bl_Xroot {bl_Xroot} blobFn {blobFn}\", file=sys.stderr)\n            if bl_Xroot: print(f\"offset {blobOff} size {bufsz}\", file=sys.stderr)\n            if p in cat: print(f\"cat: {cat[p]}\", file=sys.stderr)\n\n# main\nif subcmd == 'clone':\n  do_clone(subcmd)\nelif subcmd == 'backup':\n  do_backup()\nelif subcmd == 'restore':\n  do_restore()\nelse:\n  print(\"incorrect argument\", subcmd, file=sys.stderr)\n  sys.exit(1)\n"
  },
  {
    "path": "test/eos-backup-browser",
    "content": "#!/usr/bin/env python3\n\n\nfrom __future__ import print_function\n\nimport os, posix, re, stat, sys, time\nimport pdb\nfrom errno import *\nfrom stat import *\nimport fcntl, json\n\nimport argparse\n\nimport fuse\nfrom fuse import FUSE, FuseOSError, Operations, LoggingMixIn\n\nfrom XRootD import client\nfrom XRootD.client.flags import MkDirFlags, OpenFlags\n\nctlg={}\n\ndef flag2mode(flags):\n    md = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}\n    m = md[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]\n\n    if flags | os.O_APPEND:\n        m = m.replace('w', 'a', 1)\n\n    return m\n\n\nclass Bck(LoggingMixIn, Operations):\n\n    def __init__(self, root=\"/\"):\n\n        # do stuff to set up your filesystem here, if you want\n        #import thread\n        #thread.start_new_thread(self.mythread, ())\n        self.root = root\n        self.fhs = {}\n\n    def getattr(self, path, fh=None):\n        if path not in ctlg: path += \"/\"\n        print(\"path=%r\" % path)\n        try:\n            st = ctlg[path]['s']\n            print(st)\n            return dict((k, getattr(st, k)) for k in ('st_atime', 'st_gid',\n                            'st_mode', 'st_mtime', 'st_size', 'st_uid'))\n        except Exception as e:\n            print(\"Exception %r\" % e)\n\n    def readlink(self, path):\n        pdb.set_trace()\n        return os.readlink(\".\" + path)\n\n    def readdir(self, path, offset):\n        if path != \"/\": path += \"/\"         # this is a dir\n        print(\"path=%r\" % path)\n        for e in filter(lambda a: type(a) is str and a.startswith(path), ctlg.keys()):\n            print(\"e1='%s'\" % e)\n            if len(e) > 0 and e[:len(path)] == path:\n                suffix = str(e[len(path):])\n                print(\"suffix='%s'\" % suffix)\n                if len(suffix) == 0: continue\n                #if '/' not in suffix or suffix.find(\"/\") == len(suffix)-1:\n                if '/' not in suffix:\n                    print(\"yield %r\" % suffix)\n                    yield suffix\n                elif len(suffix) > 1 and suffix.find(\"/\") == len(suffix)-1:\n                    print(\"yield %r\" % suffix[:-1])\n                    yield suffix[:-1]\n\n\n    def access(self, path, mode):\n        return\n        pdb.set_trace()\n\n\n    def utimens(self, path, times=None):\n        return None\n\n    def get_fe(self, path, fh):\n        try:\n            fe = self.fhs[path]\n        except KeyError:\n            # garbage collect\n            kk = list(self.fhs.keys())\n            if len(kk) > 1:\n                now = time.time()\n                for k in kk:\n                    if now - self.fhs[k]['ts'] > 1:\n                        print(\"closing\", self.fhs[k]['blobFn'])\n                        self.fhs[k]['blobFile'].close()\n                        del self.fhs[k]\n\n            fe = {}\n            self.fhs[path] = fe\n            \n            fe['bIndex'], fe['bOffset'], fe['bLen'] = map(int, ctlg[path]['b'].split(\":\"))\n            cloneId = ctlg[path]['c']\n            fe['blobFn'] = \"%s/b.%d\" % (ctlg[cloneId]['path'], fe['bIndex'])\n\n            fe['bl_Xroot'] = fe['blobFn'].startswith(\"root:\")\n            if fe['bl_Xroot']:\n                fe['blobFile'] = client.File()\n                st, xx = fe['blobFile'].open(fe['blobFn'])\n            else:\n                fe['blobFile'] = open(fe['blobFn'], \"rb\")\n                fe['blobFile'].seek(fe['bOffset']+offset, 0)\n\n        fe['ts'] = time.time()\n        return fe\n\n    def read(self, path, length, offset, fh):\n        fe = self.get_fe(path, fh)\n        if offset+length > fe['bLen']:\n            length = fe['bLen'] - offset\n        if length > 0:\n            st, buff = fe['blobFile'].read(offset=fe['bOffset']+offset, size=length)\n            if st.ok:\n               return buff\n\n        return None\n\n\ndef find_blobs(regs):\n    blobs = {}\n    \n    res = []\n    for regexp in regs:\n        print(\"check for '%s'\" % regexp)\n        res.append(re.compile(regexp))\n\n    for k in ctlg.keys():\n        if type(k) is str and 'b' in ctlg[k]:\n            for rx in res:\n                if rx.search(k):\n                    bIndex, bOffset, bLen = map(int, ctlg[k]['b'].split(\":\"))\n                    cloneId = ctlg[k]['c']\n                    blobFn = \"%s/b.%d\" % (ctlg[cloneId]['path'], bIndex)\n                    try:\n                        blobs[blobFn] += 1\n                    except KeyError:\n                        blobs[blobFn] = 1\n\n    for k in blobs:\n        print(k, blobs[k])\n\n\ndef parse_cat_files(s):\n    catFiles = []\n    for f in s.files:\n        catFiles += f.split(',')\n\n    # build a dictionary of files in the backup, implementing incremental logic\n    for fn in catFiles:\n        lastCatFile = fn == catFiles[-1]\n\n        ff = client.File()\n        st, xx = ff.open(fn)\n        line1 = ff.readline()\n        xx = json.loads(line1)\n        cloneId = int(xx['c'])                       # top dir has this dump's cloneId\n        rootPath = str(xx['n'])[:-1]                 # excluding final \"/\"\n        ctlg[cloneId] = dict(path=os.path.dirname(fn))\n\n        ff.close()\n        ff = client.File()\n        st, xx = ff.open(fn)\n\n        while True:\n            l = ff.readline()\n            if len(l) == 0: break\n\n            try:\n                xx = json.loads(l)\n            #except json.decoder.JSONDecodeError as e:          # python 3\n            except Exception as e:\n                pdb.set_trace()\n                print(\"json error %r l='%s'\" % (e, l), file=sys.stderr)\n                sys.exit(1)\n\n            path = str(xx['n'])                      # bloody unicode\n            #if not path.endswith('/'): clonePath = \"%s/%s\" % (xx['c'], xx['p'])\n\n            rpath = path[len(rootPath):]\t# skip the \"root\" part\n            if rpath in ctlg and not path.endswith('/'):        # this is a file which we've got already\n              try:\n                if cloneId != xx['c']:                          # file not part of this backup\n                    if lastCatFile: ctlg[rpath]['keep'] = True  # but it must survive\n                    continue                                 \n                if xx['t'] < ctlg[rpath]['t'] or xx['c'] == 0:    # ??? file exists but not part of this backup ?????\n                    if lastCatFile: ctlg[rpath]['keep'] = True\n                    continue\t\t# restore latest version\n              except Exception as e:\n                  pdb.set_trace()\n                  print(e)\n            else: ctlg[rpath] = dict()\n\n            mode = 0o777\n            if len(rpath) == 0 or rpath.endswith(\"/\"): mode |= stat.S_IFDIR\n            ctlg[rpath]['s'] = posix.stat_result(eval(xx['st']))\n            ctlg[rpath]['t'] = xx['t']                              # server sync time\n            if 'p' in xx:                                           # this is a file\n              ctlg[rpath]['cp'] = \"%d/%s\" % (xx['c'], str(xx['p'])) # clone path\n              ctlg[rpath]['b'] = xx['b']\n              ctlg[rpath]['c'] = xx['c']                            # cloneId needed for blob file path\n            if lastCatFile: ctlg[rpath]['keep'] = True\n\n\n    #pdb.set_trace()\n    # remove deleted files\n    for p in sorted(map(str,ctlg.keys())):\t# sorted so that dirs come before their files\n        if not p.isnumeric() and not 'keep' in ctlg[p]: del ctlg[p]\n\n\nif __name__ == '__main__':\n    usage = \"\"\"\nMount a R/O file system based on a series of eos-backup catalogs\n\n\"\"\" \n\n    parser = argparse.ArgumentParser(description=\"Mount a R/O file system based on a series of eos-backup catalogs\")\n\n    parser.add_argument(\"mnt\", metavar=\"mountpoint\", default='/mnt', help=\"mount point for the simulated file system\")\n\n    parser.add_argument(\"-F\", \"--files\", metavar='cat', dest=\"files\", nargs='*', help='list (optionally comma-separated) of eos-backup catalog files')\n    parser.add_argument(\"-L\", \"--locate\", dest=\"locate\", metavar='regexp', nargs='*', help='only display a list of backup media blobs needed for restoring files matched by a regexp')\n    s = parser.parse_args()\n\n# parse catalog Files\n    parse_cat_files(s)\n\n    if s.locate is not None:\n        find_blobs(s.locate)\n        sys.exit(0)\n\n    try:\n        os.chdir(s.mnt)\n    except OSError:\n        print(\"can't enter root of underlying filesystem\", file=sys.stderr)\n        sys.exit(1)\n\n    print(\"Mounting fuse file system on %s, browse it in a different window and ctrl-C or kill this program when finished\" % s.mnt)\n    fuse = FUSE(Bck(), s.mnt, foreground=True, nothreads=True)\n"
  },
  {
    "path": "test/eos-balance-test",
    "content": "#!/bin/bash\nset -x\n\n\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2022 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\n#------------------------------------------------------------------------------\n# Description: Script testing the balancer mechanism of EOS. It assumes that\n#              there are at least 7 FSTs available in the instance.\n#\n# Usage:\n# eos-balancer-test <eos_mgm_hostname>\n#------------------------------------------------------------------------------\n\nSCRIPTPATH=\"$( cd \"$(dirname \"$0\")\" >/dev/null 2>&1 ; pwd -P )\"\nsource ${SCRIPTPATH}/eos-test-utils\n\n# Helper cleanup method\ncleanup() {\n  eos rm -rF \"${EOS_BALANCE_DIR}/replica/*\"\n  eos rm -rF \"${EOS_BALANCE_DIR}/raiddp/*\"\n  eos rm -rF \"${EOS_BALANCE_DIR}/rain/*\"\n  eos rmdir \"${EOS_BALANCE_DIR}/replica/\"\n  eos rmdir \"${EOS_BALANCE_DIR}/raiddp/\"\n  eos rmdir \"${EOS_BALANCE_DIR}/rain/\"\n  eos rmdir \"${EOS_BALANCE_DIR}/\"\n  rm -rf ${TEST_FN1} ${TEST_FN2}\n  eos space config default space.balancer=off\n}\n\nif [[ $# -eq 0 || $# -gt 2 ]]; then\n      echo \"Usage: $0 <eos_mgm_hostname>\"\n      exit 1\nfi\n\nEOS_MGM_HOSTNAME=$1\nEOS_BALANCER_THRESHOLD=5\n\n# Check preconditions and make sure central balancer is enabled\nFST_ONLINE=$(eos fs ls | grep \"online\" | wc -l)\n\nif [[ ${FST_ONLINE} -lt 7 ]]; then\n  echo \"error: not enough FSTs configured\"\n  exit 1\nfi\n\n# Create dummy test files\nTEST_FN1=/var/tmp/200MB.dat\nTEST_FN2=/var/tmp/400MB.dat\n\ndd if=/dev/urandom of=${TEST_FN1} bs=1M count=200 &> /dev/null\ndd if=/dev/urandom of=${TEST_FN2} bs=1M count=400 &> /dev/null\n\n# Create eos directory for tests\nEOS_BALANCE_DIR=/eos/dockertest/balance_test\neos mkdir -p ${EOS_BALANCE_DIR}/replica/\neos mkdir -p ${EOS_BALANCE_DIR}/raiddp/\neos mkdir -p ${EOS_BALANCE_DIR}/rain/\neos chmod -r 2777 ${EOS_BALANCE_DIR}/\neos attr set default=replica ${EOS_BALANCE_DIR}/replica/\neos attr set default=raiddp ${EOS_BALANCE_DIR}/raiddp/\neos attr set default=raid6 ${EOS_BALANCE_DIR}/rain/\n\n# Put one file system in RO mode while we copy data in so that we end up\n# with an unbalanced group\neos fs config 1 configstatus=ro\n\nfor i in {1..3}; do\n  xrdcp -f --nopbar ${TEST_FN1} root://${EOS_MGM_HOSTNAME}/${EOS_BALANCE_DIR}/replica/200MB_file$i.dat\n  xrdcp -f --nopbar ${TEST_FN1} root://${EOS_MGM_HOSTNAME}/${EOS_BALANCE_DIR}/raiddp/200MB_file$i.dat\n  xrdcp -f --nopbar ${TEST_FN1} root://${EOS_MGM_HOSTNAME}/${EOS_BALANCE_DIR}/rain/200MB_file$i.dat\n  xrdcp -f --nopbar ${TEST_FN2} root://${EOS_MGM_HOSTNAME}/${EOS_BALANCE_DIR}/replica/400MB_file$i.dat\n  xrdcp -f --nopbar ${TEST_FN2} root://${EOS_MGM_HOSTNAME}/${EOS_BALANCE_DIR}/raiddp/400MB_file$i.dat\n  xrdcp -f --nopbar ${TEST_FN2} root://${EOS_MGM_HOSTNAME}/${EOS_BALANCE_DIR}/rain/400MB_file$i.dat\ndone\n# Allow for the publishing thread on the FSTs to report updated values\nsleep 10\n\neos fs config 1 configstatus=rw\neos node ls\neos fs ls\neos fs ls --io\neos group ls\n\nfor i in {1..8}; do\n  eos fs status $i\ndone\n\n# Enable the balancing at the space level\neos space config default space.balancer.threshold=${EOS_BALANCER_THRESHOLD}\neos space config default space.balancer.update.interval=5\neos space config default space.balancer=on\n\n# Get current dev of the group\nSTART_DEV=$(sudo eos group ls -m | grep \"default.0\" | awk '{print $20;}' | cut -d '=' -f2)\n\nif [[ $(echo \"${START_DEV} < ${EOS_BALANCER_THRESHOLD}\" | bc) -eq 1 ]]; then\n   echo \"error: start deviation is already less then threshold\"\n   cleanup\n   exit 1\nfi\n\nMAX_DELAY=300 # seconds\nSTART_TIME=$(date +%s)\n\nwhile\n  CURRENT_TIME=$(date +%s)\n  NEW_DEV=$(sudo eos group ls -m | grep \"default.0\" | awk '{print $20;}' | cut -d '=' -f2)\n\n  if [[ $(echo \"${NEW_DEV} < ${EOS_BALANCER_THRESHOLD}\" | bc) -eq 1 ]]; then\n    echo \"info: new deviation value is ${NEW_DEV}\"\n    false # exit the while loop\n  else\n    if (( $((${CURRENT_TIME} - ${START_TIME})) >= ${MAX_DELAY} )); then\n      echo \"error: balancing was not done within ${MAX_DELAY} seconds\"\n      cleanup\n      exit 1\n    else\n      echo \"info: new dev is ${NEW_DEV}, sleep for 5 seconds, `secs_to_human $((${CURRENT_TIME} - ${START_TIME}))` passed\"\n      sleep 5\n      eos group ls\n    fi\n  fi\ndo\n :\ndone\n\ncleanup\nexit 0\n"
  },
  {
    "path": "test/eos-bash",
    "content": "#!/bin/bash\neval $*\n"
  },
  {
    "path": "test/eos-convert-test",
    "content": "#!/bin/bash -e\n\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2020 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\nSCRIPTPATH=\"$( cd \"$(dirname \"$0\")\" >/dev/null 2>&1 ; pwd -P )\"\nsource ${SCRIPTPATH}/eos-test-utils\n\nfunction usage() {\n  echo \"usage: $(basename $0) [--max-delay <sec_delay>] [--root-dir <path>] --type docker/k8s <k8s_namespace>\"\n  echo \"  --max-delay : optional max delay in seconds\"\n  echo \"  --root-dir  : eos root test directory \"\n  echo \"       docker : script runs in a Docker based setup\"\n  echo \"       k8s    : script runs in a Kubernetes setup and requires a namespace argument\"\n}\n\nfunction cleanup() {\n  rm -rf /tmp/conv_test_file.dat\n  exec_cmd eos-cli1 \"eos -r 0 0 rm -rF $EOS_CONVERTER_DIR\"\n  exec_cmd eos-cli1 \"eos -r 0 0 rmdir $(dirname $EOS_CONVERTER_DIR)\"\n}\n\n# Set up global variables\nIS_DOCKER=false\nIS_KUBERNETES=false\nIS_LOCAL=true\nK8S_NAMESPACE=\"\"\nMAX_DELAY=120\nEOS_ROOT=/eos/dockertest/\n\nwhile [[ $# -gt 1 ]]; do\n  if [[ \"$1\" == \"--help\" || \"$1\" == \"-h\" ]]; then\n    usage\n    exit 0\n  elif [[ \"$1\" == \"--max-delay\" ]]; then\n    MAX_DELAY=$2\n    shift # past argument\n    shift # past value\n  elif [[ \"$1\" == \"--root-dir\" ]]; then\n    EOS_ROOT=$2\n    shift\n    shift\n  elif [[ \"$1\" == \"--type\" ]]; then\n    if [[ \"$2\" == \"docker\" ]]; then\n      IS_DOCKER=true\n      IS_LOCAL=false\n      shift\n    elif [[ \"$2\" == \"k8s\" ]]; then\n      IS_KUBERNETES=true\n      IS_LOCAL=false\n      # For the Kubernetes setup we also need a namespace argument\n      if [[ $# -lt 3 ]]; then\n        echo \"error: missing Kubernetes namespace argument\"\n        usage\n        exit 1\n      fi\n\n      K8S_NAMESPACE=\"$3\"\n      shift\n      shift\n    fi\n  else\n    echo \"error: unknown argument \\\"$1\\\"\"\n    usage\n    exit 1\n  fi\ndone\n\nEOS_CONVERTER_DIR=${EOS_ROOT}/converter_dir/test_dir/\nexec_cmd eos-cli1 \"dd if=/dev/urandom of=/tmp/conv_test_file.dat bs=1M count=30 &&\n                   eos -r 0 0 convert config set status=on &&\n                   eos -r 0 0 mkdir -p ${EOS_CONVERTER_DIR} &&\n                   eos -r 0 0 attr set default=replica ${EOS_CONVERTER_DIR} &&\n                   eos cp /tmp/conv_test_file.dat ${EOS_CONVERTER_DIR}/test_replica.dat &&\n                   eos cp /tmp/conv_test_file.dat ${EOS_CONVERTER_DIR}/test_raiddp.dat\"\n# Wait for the conversion to be done\nSTART_TIME=$(date +%s)\n\nfor TEST_FILE in ${EOS_CONVERTER_DIR}/test_replica.dat ${EOS_CONVERTER_DIR}/test_raiddp.dat; do\n  # Note we use xargs to trim the output\n  ORIG_FINFO=$(exec_cmd eos-cli1 \"eos fileinfo ${TEST_FILE} -m | \\\n    sed -e '{\n     s/fsid=[[:digit:]]//g;\n     s/fsdel=[[:digit:]]//g;\n     s/layout=[[:alnum:]]\\+[[:space:]]//g;\n     s/nstripes=[[:digit:]]\\+[[:space:]]//g;\n     s/lid=[[:digit:]]\\+[[:space:]]//g;\n     s/nrep=[[:digit:]]\\+[[:space:]]//g;\n     s/xattrn=sys.fs.tracking xattrv=[-/+[:digit:]]\\+[[:space:]]//g\n   }' | xargs\")\n  ORIG_LOCATIONS=$(exec_cmd eos-cli1 \"eos -j fileinfo ${TEST_FILE} | jq -r .locations[].fsid | tr '\\n' ' ' | xargs\")\n\n  if [[ \"${TEST_FILE}\" == *raiddp* ]]; then\n    exec_cmd eos-cli1 \"eos file convert ${TEST_FILE} raiddp:6\"\n  else\n    exec_cmd eos-cli1 \"eos file convert --rewrite ${TEST_FILE}\"\n  fi\n\n  while\n    CURRENT_TIME=$(date +%s)\n    NEW_LOCATIONS=$(exec_cmd eos-cli1 \"eos -j fileinfo ${TEST_FILE} | jq -r .locations[].fsid | tr '\\n' ' ' | xargs\")\n\n    if [[ \"${ORIG_LOCATIONS}\" != \"${NEW_LOCATIONS}\" ]]; then\n      echo \"info: file successfully converted\"\n      break\n    fi\n\n    if (( $((${CURRENT_TIME} - ${START_TIME})) >= ${MAX_DELAY} )); then\n      echo \"error: conversion failed\"\n      cleanup\n      exit 1\n    else\n      echo \"info: sleep for 2 seconds waiting for conversion, `secs_to_human $((${CURRENT_TIME} - ${START_TIME}))` passed\"\n      sleep 2\n    fi\n  do\n    :\n  done\n\n  NEW_FINFO=$(exec_cmd eos-cli1 \"eos fileinfo ${TEST_FILE} -m | \\\n    sed -e '{\n     s/fsid=[[:digit:]]//g;\n     s/fsdel=[[:digit:]]//g;\n     s/layout=[[:alnum:]]\\+[[:space:]]//g;\n     s/nstripes=[[:digit:]]\\+[[:space:]]//g;\n     s/lid=[[:digit:]]\\+[[:space:]]//g;\n     s/nrep=[[:digit:]]\\+[[:space:]]//g;\n     s/xattrn=sys.fs.tracking xattrv=[-/+[:digit:]]\\+[[:space:]]//g\n   }' | xargs\")\n\n  if [[ \"${ORIG_FINFO}\" != \"${NEW_FINFO}\" ]]; then\n    echo \"error: converted file metadata does not match the original one\"\n    echo \"original finfo: \\\"${ORIG_FINFO}\\\"\"\n    echo \"new  finfo    : \\\"${NEW_FINFO}\\\"\"\n    cleanup\n    exit 1\n  fi\ndone\n\ncleanup\nexit 0\n"
  },
  {
    "path": "test/eos-defaultcc-test",
    "content": "#!/bin/bash\noutput=`runuser -u daemon eosxd get eos.reconnect $1 | grep -A1 \"default paths\" | tail -1`\n\necho $output | grep KCM >& /dev/null && type=KCM\necho $output | grep KEYRING >& /dev/null && type=KEYRING\necho $output | grep /tmp/krb5 >& /dev/null && type=FILE\n\necho $type\n\nif [ \"$type\" = \"FILE\" ]; then\n  echo $output | grep /tmp/krb5cc_2 ||  exit -1\n  exit 0\nfi\n\nif [ \"$type\" = \"KEYRING\" ]; then\n  echo $output | grep KEYRING:persistent:2 || exit -1\n  exit 0\nfi\n\nif [ \"$type\" = \"KCM\" ]; then\n  exit 0\nfi\n\nexit -1\n"
  },
  {
    "path": "test/eos-drain-test",
    "content": "#!/bin/bash\n\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2018 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\n#------------------------------------------------------------------------------\n# Description: Script testing the draining mechanism of EOS. It assumes that\n#              there are at least 7 FSTs available in the instance.\n#\n# Usage:\n# eos-drain-test <eos_mgm_hostname>\n#------------------------------------------------------------------------------\n\n# Helper cleanup method\ncleanup() {\n  eos rm -rF \"${EOS_DRAIN_DIR}/replica/*\"\n  eos rm -rF \"${EOS_DRAIN_DIR}/raiddp/*\"\n  eos rm -rF \"${EOS_DRAIN_DIR}/rain/*\"\n  eos rmdir \"${EOS_DRAIN_DIR}/replica/\"\n  eos rmdir \"${EOS_DRAIN_DIR}/raiddp/\"\n  eos rmdir \"${EOS_DRAIN_DIR}/rain/\"\n  eos rmdir \"${EOS_DRAIN_DIR}/\"\n  rm -rf ${TEST_FN0} ${TEST_FN1} ${TEST_FN2}\n}\n\n\nif [[ $# -eq 0 || $# -gt 2 ]]; then\n  echo \"Usage: $0 <eos_mgm_hostname>\"\n  exit 1\nfi\n\nEOS_MGM_HOSTNAME=$1\n\n# Check preconditions and make sure central draining is enabled\nFST_ONLINE=$(eos fs ls | grep \"online\" | wc -l)\n\nif [[ ${FST_ONLINE} -lt 7 ]]; then\n  echo \"error: not enough FSTs configured\"\n  exit 1\nfi\n\n# Create dummy test files\nTEST_FN0=/var/tmp/zero.dat\nTEST_FN1=/var/tmp/32kb.dat\nTEST_FN2=/var/tmp/5mb.dat\n\ntouch ${TEST_FN0}\ndd if=/dev/urandom of=${TEST_FN1} bs=1k count=32 &> /dev/null\ndd if=/dev/urandom of=${TEST_FN2} bs=1M count=5  &> /dev/null\n\n# Create eos directory for tests and copy some files in\nEOS_DRAIN_DIR=/eos/dockertest/drain_test/\neos mkdir -p ${EOS_DRAIN_DIR}/replica/\neos mkdir -p ${EOS_DRAIN_DIR}/raiddp/\neos mkdir -p ${EOS_DRAIN_DIR}/rain/\neos chmod 2777 /eos/dockertest/\neos chmod 2777 ${EOS_DRAIN_DIR}/replica/\neos chmod 2777 ${EOS_DRAIN_DIR}/raiddp/\neos chmod 2777 ${EOS_DRAIN_DIR}/rain/\neos attr set default=replica ${EOS_DRAIN_DIR}/replica/\neos attr set default=raiddp ${EOS_DRAIN_DIR}/raiddp/\neos attr set default=raid6 ${EOS_DRAIN_DIR}/rain/\neos fs ls\n\nfor i in {1..4}; do\n  xrdcp -f --nopbar ${TEST_FN0} root://${EOS_MGM_HOSTNAME}/${EOS_DRAIN_DIR}/replica/0kb_file${i}.dat\n  xrdcp -f --nopbar ${TEST_FN0} root://${EOS_MGM_HOSTNAME}/${EOS_DRAIN_DIR}/raiddp/0kb_file${i}.dat\n  xrdcp -f --nopbar ${TEST_FN0} root://${EOS_MGM_HOSTNAME}/${EOS_DRAIN_DIR}/rain/0kb_file${i}.dat\n  xrdcp -f --nopbar ${TEST_FN1} root://${EOS_MGM_HOSTNAME}/${EOS_DRAIN_DIR}/replica/32kb_file${i}.dat\n  xrdcp -f --nopbar ${TEST_FN1} root://${EOS_MGM_HOSTNAME}/${EOS_DRAIN_DIR}/raiddp/32kb_file${i}.dat\n  xrdcp -f --nopbar ${TEST_FN1} root://${EOS_MGM_HOSTNAME}/${EOS_DRAIN_DIR}/rain/32kb_file${i}.dat\n  xrdcp -f --nopbar ${TEST_FN2} root://${EOS_MGM_HOSTNAME}/${EOS_DRAIN_DIR}/replica/5mb_file${i}.dat\n  xrdcp -f --nopbar ${TEST_FN2} root://${EOS_MGM_HOSTNAME}/${EOS_DRAIN_DIR}/raiddp/5mb_file${i}.dat\n  xrdcp -f --nopbar ${TEST_FN2} root://${EOS_MGM_HOSTNAME}/${EOS_DRAIN_DIR}/rain/5mb_file${i}.dat\ndone\n\n# Check that the file system is ready\neos fs ls | grep \" 1 \" | grep \"rw\" | grep \"nodrain\" | grep \"online\"\n\nif [[ $? -ne 0 ]]; then\n  echo \"error: file system 1 is not online  ...\"\n  eos fs ls\n  cleanup\n  exit 1\nfi\n\n# Set all file systems in RO mode except for fsid 1 and 2 - then start a long\n# running process which writes a file. The purpose is to test the draining of\n# files being written when the draining is started.\nfor (( i=3; i<=${FST_ONLINE}; i++ )); do\n  eos fs config $i configstatus=ro\ndone\n\n# Start long running write operation which should select fsts 1 and 2 as destination\nxrdcpslowwriter root://${EOS_MGM_HOSTNAME}/${EOS_DRAIN_DIR}/replica/slow_writer.dat &\nxrdcpslowwriter_pid=$!\nsleep 2\n\n# Put back the rest of the file systems in rw mode so that the draining can\n# select them as destination.\nfor (( i=3; i<=${FST_ONLINE}; i++ )); do\n  eos fs config $i configstatus=rw\ndone\n\neos file info ${EOS_DRAIN_DIR}/replica/slow_writer.dat\n\n# Start draining and wait for it to finish\neos fs config 1 configstatus=drain\neos fs ls | grep \" 1 \" | grep \"drain\" | grep \"prepare\\|draining\"\n\nwhile [[ $? -eq 0 ]]; do\n  sleep 2\n  eos fs ls | grep \" 1 \" | grep \"drain\" | grep \"prepare\\|draining\"\ndone\n\neos fs ls # debug print on what the status is right now;\neos fs ls | grep \" 1 \" | grep \"empty\" | grep \"drained\"\n\nif [[ $? -ne 0 ]]; then\n  echo \"error: file system 1 is not drained\"\n  echo \"Current metadata content\"\n  eos fs dumpmd 1\n  cleanup\n  exit 1\nelse\n  echo \"File system 1 successfully drained\"\nfi\n\n# Put back the file system in rw mode\neos fs config 1 configstatus=rw\n\nif [[ $? -ne 0 ]]; then\n  echo \"error: failed to put file system in rw mode\"\n  cleanup\n  exit 1\nfi\n\n# Wait for the slow writer to finish before clean-up\nwait ${xrdcpslowwriter_pid}\n\ncleanup\nexit 0\n"
  },
  {
    "path": "test/eos-file-cont-detached-test",
    "content": "#!/bin/bash\n# Output what this script will do\nset -x\n\nprefix=\"$1\"\nqdb_cluster=${2-\"localhost:7777\"}\nqdb_host=$(echo \"$qdb_cluster\" | cut -d':' -f1)\nqdb_port=$(echo \"$qdb_cluster\" | cut -d':' -f2)\nqdb_password=${3-\"/etc/eos.keytab\"}\nqdb=\"$qdb_cluster\"\nEOS_REMOVED_DETACHED_DIR=\"$prefix\"\n\nNB_DIRS=3\nNB_FILES_PER_DIR=100\n\ncleanup() {\n  eos ns cache drop\n  eos rm -rF --no-confirmation \"$EOS_REMOVED_DETACHED_DIR\"\n}\n\n# cleanup everything if needed\ncleanup\n\n# Create directories\nseq 1 $NB_DIRS | xargs -I{} -P 100 eos mkdir -p \"$EOS_REMOVED_DETACHED_DIR/{}\"\n\n# Remember the id of the test root directory\neosRemovedDetachedId=$(eos -j fileinfo \"$EOS_REMOVED_DETACHED_DIR\" | jq .id)\n\n# Remember every id of the directories created\ndirs=$(eos find -d \"$EOS_REMOVED_DETACHED_DIR\" | tail -n +2);\ndirIds=()\nfor dir in $dirs;\ndo\n  dirIds+=($(eos -j fileinfo \"$dir\" | jq .id))\ndone\n\n# Create files under each directory\nfor dir in $dirs\ndo\n  seq 1 $NB_FILES_PER_DIR | xargs -I{} -P 100 eos touch \"$dir/file{}\"\ndone\n\n# Remember file ids\nfiles=$(eos find -f \"$EOS_REMOVED_DETACHED_DIR\")\nfileIds=()\nfor file in $files;\ndo\n  fileIds+=($(eos -j fileinfo \"$file\" | jq .id))\ndone\n\n# Get available fsids\nfsids=$(eos fs ls | awk '/booted/ {print $3}')\n\n# Fake file replication on fsids and unlink the file from the fsids\nfor file in ${files[@]};\ndo\n  for fsid in $fsids;\n  do\n    eos file tag \"$file\" \"+$fsid\" &\n  done\n  wait\n  for fsid in $fsids;\n  do\n    eos file tag \"$file\" \"~$fsid\" &\n  done\n  wait\ndone\n\n# Change the parent of the files to one that does not exist (create detached files)\nfor fid in ${fileIds[@]};\ndo\n  eos-ns-inspect change-fid --password-file \"$qdb_password\" --members \"$qdb\" --fid \"$fid\" --new-parent 20000000 --no-dry-run\ndone\n\n# Do parallel detached file deletion\nfor fid in ${fileIds[@]};\ndo\n  eos rm -F \"fid:$fid\" &\ndone\nwait\n\n# Delete the file entries from the directories' file map (in quarkdb). Otherwise we will not\n# be able to delete the directories later on\nfor dirId in ${dirIds[@]};\ndo\n  seq 1 \"$NB_FILES_PER_DIR\" | xargs -P 100 -I{} redis-cli -h \"$qdb_host\" -p \"$qdb_port\" 'HDEL' \"$dirId:map_files\" \"file{}\"\ndone\n\n# Detach the directories\nfor dirId in ${dirIds[@]};\ndo\n  eos-ns-inspect overwrite-container --password-file \"$qdb_password\" --members \"$qdb\" --cid \"$dirId\" --parent-id 2000000000 --name test2 --no-dry-run\ndone\n\n# Drop the cache\neos ns cache drop\n\nfor dirId in ${dirIds[@]}\ndo\n  # Will display error in the output but it's OK, the detached directories will be deleted at the end\n  eos rm -rF \"cid:$dirId\" &\ndone\n\nwait\n\nfor i in $(seq 1 \"$NB_DIRS\");\ndo\n  # Delete the directories from the root directory's directory map\n  redis-cli -h \"$qdb_host\" -p \"$qdb_port\" 'HDEL' \"$eosRemovedDetachedId:map_conts\" \"$i\"\ndone\n\n# Cleanup everything\ncleanup\n\n# The test is successful if nothing is left\neos ls /eos/dev/detached_parent_test/\nif [[ $? -eq 2 ]];\nthen\n  echo \"removed-detached-test OK\"\n  exit 0\nfi\necho \"removed-detahed-test FAILED\"\nexit 1"
  },
  {
    "path": "test/eos-fsck-test",
    "content": "#!/bin/bash -ex\n\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2019 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\nSCRIPTPATH=\"$( cd \"$(dirname \"$0\")\" >/dev/null 2>&1 ; pwd -P )\"\nsource ${SCRIPTPATH}/eos-test-utils\n\nfunction usage() {\n  echo \"usage: $(basename $0) [--max-delay <sec_delay>] --type docker/local/k8s <k8s_namespace>\"\n  echo \"  --max-delay : optional max delay in seconds\"\n  echo \"       docker : script runs in a Docker based setup\"\n  echo \"       k8s    : script runs in a Kubernetes setup and requires a namespace argument\"\n  echo \"       local  : script runs locally, needs EOS_MGM_URL to be set\"\n}\n\n# Create and upload test files to the eos instance. We create a random file and\n# upload it multiple times to EOS one file per type of corruption.\nfunction create_test_files() {\n  EOS_ROOT=/eos/dockertest\n  EOS_RAIN_DIR=${EOS_ROOT}/fsck/rain\n  EOS_REPLICA_DIR=${EOS_ROOT}/fsck/replica\n\n # Create one file per type of fsck error and trim whitespaces\n  exec_cmd eos-cli1 \"export PATH=/opt/eos/xrootd/bin/:${PATH} &&\n                     dd if=/dev/urandom of=/tmp/test_file.dat bs=1M count=10 &&\n                     eos -r 0 0 mkdir -p ${EOS_RAIN_DIR} &&\n                     eos -r 0 0 mkdir -p ${EOS_REPLICA_DIR} &&\n                     eos -r 0 0 attr set default=replica ${EOS_REPLICA_DIR} &&\n                     eos -r 0 0 attr set default=raid6 ${EOS_RAIN_DIR} &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_REPLICA_DIR}/file_d_mem_sz_diff.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_REPLICA_DIR}/file_m_mem_sz_diff1.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_REPLICA_DIR}/file_m_mem_sz_diff2.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_REPLICA_DIR}/file_d_cx_diff.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_REPLICA_DIR}/file_m_cx_diff.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_REPLICA_DIR}/file_m_cx_diff1.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_REPLICA_DIR}/file_unreg.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_REPLICA_DIR}/file_rep_missing.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_REPLICA_DIR}/file_rep_diff_under.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_REPLICA_DIR}/file_rep_diff_over.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_REPLICA_DIR}/file_orphan.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_REPLICA_DIR}/best_effort.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_RAIN_DIR}/rain_blockxs_err.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_RAIN_DIR}/rain_stripe_diff.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_RAIN_DIR}/rain_stripe_sz_err.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_RAIN_DIR}/rain_invalid_stripe_err.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_RAIN_DIR}/rain_invalid_stripe_err2.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_RAIN_DIR}/rain_stripe_diff_over.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_RAIN_DIR}/rain_stripe_diff_over2.dat &&\n                     xrdcp -f /tmp/test_file.dat \\${EOS_MGM_URL}/${EOS_RAIN_DIR}/rain_invalid_header.dat\" \n  FXID_D_MEM_SZ_DIFF=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_REPLICA_DIR}/file_d_mem_sz_diff.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_M_MEM_SZ_DIFF1=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_REPLICA_DIR}/file_m_mem_sz_diff1.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_M_MEM_SZ_DIFF2=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_REPLICA_DIR}/file_m_mem_sz_diff2.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_D_CX_DIFF=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_REPLICA_DIR}/file_d_cx_diff.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_M_CX_DIFF=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_REPLICA_DIR}/file_m_cx_diff.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_M_CX_DIFF1=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_REPLICA_DIR}/file_m_cx_diff1.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_UNREG=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_REPLICA_DIR}/file_unreg.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_REP_MISSING=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_REPLICA_DIR}/file_rep_missing.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_REP_DIFF_UNDER=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_REPLICA_DIR}/file_rep_diff_under.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_REP_DIFF_OVER=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_REPLICA_DIR}/file_rep_diff_over.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_ORPHAN=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_REPLICA_DIR}/file_orphan.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_BEST_EFFORT=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_REPLICA_DIR}/best_effort.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_RAIN_BLOCKXS=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_RAIN_DIR}/rain_blockxs_err.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_RAIN_DIFF=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_RAIN_DIR}/rain_stripe_diff.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_RAIN_STRIPE_SZ=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_RAIN_DIR}/rain_stripe_sz_err.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_RAIN_INVALID_STRIPE=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_RAIN_DIR}/rain_invalid_stripe_err.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_RAIN_INVALID_STRIPE2=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_RAIN_DIR}/rain_invalid_stripe_err2.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_RAIN_DIFF_OVER=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_RAIN_DIR}/rain_stripe_diff_over.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_RAIN_DIFF_OVER2=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_RAIN_DIR}/rain_stripe_diff_over2.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n  FXID_RAIN_INVALID_HEADER=$(exec_cmd eos-cli1 \"eos fileinfo ${EOS_RAIN_DIR}/rain_invalid_header.dat -m | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '\\$1==\\\"fxid\\\" {print \\$2};' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*\\$//'\")\n\n\n  # If any of the FXID_* variables are empty then we have a problem\n  if [[ -z \"${FXID_D_MEM_SZ_DIFF}\"        ||\n        -z \"${FXID_M_MEM_SZ_DIFF1}\"       ||\n        -z \"${FXID_M_MEM_SZ_DIFF2}\"       ||\n        -z \"${FXID_D_CX_DIFF}\"            ||\n        -z \"${FXID_M_CX_DIFF}\"            ||\n        -z \"${FXID_M_CX_DIFF1}\"           ||\n        -z \"${FXID_UNREG}\"                ||\n        -z \"${FXID_REP_MISSING}\"          ||\n        -z \"${FXID_REP_DIFF_UNDER}\"       ||\n        -z \"${FXID_REP_DIFF_OVER}\"        ||\n        -z \"${FXID_ORPHAN}\"               ||\n        -z \"${FXID_BEST_EFFORT}\"          ||\n        -z \"${FXID_RAIN_BLOCKXS}\"         ||\n        -z \"${FXID_RAIN_DIFF}\"            ||\n        -z \"${FXID_RAIN_STRIPE_SZ}\"       ||\n        -z \"${FXID_RAIN_INVALID_STRIPE}\"  ||\n        -z \"${FXID_RAIN_INVALID_STRIPE2}\" ||\n        -z \"${FXID_RAIN_DIFF_OVER}\"       ||\n        -z \"${FXID_RAIN_DIFF_OVER2}\"      ||\n        -z \"${FXID_RAIN_INVALID_HEADER}\"  ]]; then\n    echo \"error: some of the fxids could not be retrieved\"\n    cleanup\n    exit 1\n  fi\n\n  # Cleanup generated test file\n  exec_cmd eos-cli1 \"rm -rf /tmp/test_file.dat\"\n}\n\n# Corrupt file to generate d_mem_sz_diff error\nfunction corrupt_d_mem_sz_diff() {\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_D_MEM_SZ_DIFF} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '{if (\\$1 ==\\\"fsid\\\" || \\$1 ==\\\"fullpath\\\") {print \\$2};}' | tail -n2\")\n  # Extract the fxid and local path, trim the input\n  local FSID=$(echo \"${CMD_OUT}\" | head -n1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n  local LPATH=$(echo \"${CMD_OUT}\" | tail -n1)\n  exec_cmd \"eos-fst${FSID}\" \"echo \\\"dummy\\\" >> ${LPATH}\"\n}\n\n# Corrupt file to generate m_mem_sz_diff - where repair just needs to update the ns size\nfunction corrupt_m_mem_sz_diff1() {\n  # Use the eos-ns-inspect tool to corrupt the MGM file size\n  local QDB_CLUSTER=$(exec_cmd eos-mgm1 \"cat /etc/xrd.cf.mgm | grep \"^mgmofs.qdbcluster\" | awk '{print \\$2;}'\")\n  local QDB_PWDFILE=$(exec_cmd eos-mgm1 \"cat /etc/xrd.cf.mgm | grep \"^mgmofs.qdbpassword_file\" | awk '{print \\$2;}'\")\n  exec_cmd eos-cli1 \"eos-ns-inspect change-fid --no-dry-run --members ${QDB_CLUSTER} --password-file ${QDB_PWDFILE} --fid $(( 16#${FXID_M_MEM_SZ_DIFF1} )) --new-size 1234568\"\n  exec_cmd eos-cli1 \"eos -r 0 0 ns cache drop-single-file $(( 16#${FXID_M_MEM_SZ_DIFF1} )) || true\"\n}\n\n# Corrupt file to generate m_mem_sz_diff - where repair involves dropping the\n# broken replica and triggering a replication of the correct one\nfunction corrupt_m_mem_sz_diff2() {\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_M_MEM_SZ_DIFF2} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '{if (\\$1 ==\\\"fsid\\\" || \\$1 ==\\\"fullpath\\\") {print \\$2};}' | tail -n2\")\n  # Extract the fxid and local path, trim the input\n  local FSID=$(echo \"${CMD_OUT}\" | head -n1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n  local LPATH=$(echo \"${CMD_OUT}\" | tail -n1)\n  # Corrupt the size and disksize values registered in the db by appending\n  # some data to the physical file and then triggering a file verify\n  exec_cmd \"eos-fst${FSID}\" \"echo \\\"dummy content\\\" >> ${LPATH}\"\n  exec_cmd \"eos-mgm1\" \"eos file verify fxid:${FXID_M_MEM_SZ_DIFF2}\"\n}\n\n# Corrupt file to generate m_cx_diff - this case considers a file with just\n# one replica registered that does not have a MGM checksum committed, namely we\n# set the MGM checksum to 0000000. The recovery procedure in this case consist of\n# issuing a file verify command from the MGM and then deal separately with the\n# under-replication state.\nfunction corrupt_m_cx_diff1() {\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_M_CX_DIFF1} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '{if (\\$1 ==\\\"fsid\\\" || \\$1 ==\\\"fullpath\\\") {print \\$2};}' | tail -n2\")\n  # Use the eos-ns-inspect tool to overwrite the MGM checksum to 00000000\n  local QDB_CLUSTER=$(exec_cmd eos-mgm1 \"cat /etc/xrd.cf.mgm | grep \"^mgmofs.qdbcluster\" | awk '{print \\$2;}'\")\n  local QDB_PWDFILE=$(exec_cmd eos-mgm1 \"cat /etc/xrd.cf.mgm | grep \"^mgmofs.qdbpassword_file\" | awk '{print \\$2;}'\")\n  exec_cmd eos-cli1 \"eos-ns-inspect change-fid --no-dry-run --members ${QDB_CLUSTER} --password-file ${QDB_PWDFILE} --fid $(( 16#${FXID_M_CX_DIFF1})) --new-checksum 00000000\"\n  exec_cmd eos-cli1 \"eos -r 0 0 ns cache drop-single-file $(( 16#${FXID_M_CX_DIFF1} )) || true\"\n  # Drop one the replicas so that we're left with only one correct replica\n  local FSID1=$(echo \"${CMD_OUT}\" | head -n1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n  exec_cmd \"eos-mgm1\" \"eos file drop fxid:${FXID_M_CX_DIFF1} ${FSID1}\"\n}\n\n# Corrupt file to generate d_cx_diff error\nfunction corrupt_d_cx_diff() {\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_D_CX_DIFF} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '{if (\\$1 ==\\\"fsid\\\" || \\$1 ==\\\"fullpath\\\") {print \\$2};}' | tail -n2\")\n  # Extract the fxid and local path, trim the input\n  local FSID=$(echo \"${CMD_OUT}\" | head -n1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n  local LPATH=$(echo \"${CMD_OUT}\" | tail -n1)\n  # Corrupt the checksum of the file by writing random bytes to it\n  exec_cmd \"eos-fst${FSID}\" \"dd if=/dev/urandom of=${LPATH} bs=1M count=10\"\n}\n\n# Corrupt file to generate m_cx_diff\nfunction corrupt_m_cx_diff() {\n  # Use the eos-ns-inspect tool to corrupt the MGM checksum value\n  local QDB_CLUSTER=$(exec_cmd eos-mgm1 \"cat /etc/xrd.cf.mgm | grep \"^mgmofs.qdbcluster\" | awk '{print \\$2;}'\")\n  local QDB_PWDFILE=$(exec_cmd eos-mgm1 \"cat /etc/xrd.cf.mgm | grep \"^mgmofs.qdbpassword_file\" | awk '{print \\$2;}'\")\n  exec_cmd eos-cli1 \"eos-ns-inspect change-fid --no-dry-run --members ${QDB_CLUSTER} --password-file ${QDB_PWDFILE} --fid $(( 16#${FXID_M_CX_DIFF} )) --new-checksum 12345678\"\n}\n\n# Corrupt file to generate rep_missing_n error\nfunction corrupt_rep_missing_n {\n local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_REP_MISSING} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '{if (\\$1 ==\\\"fsid\\\" || \\$1 ==\\\"fullpath\\\") {print \\$2};}' | tail -n2\")\n  # Extract the fxid and local path, trim the input\n  local FSID=$(echo \"${CMD_OUT}\" | head -n1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n  local LPATH=$(echo \"${CMD_OUT}\" | tail -n1)\n  exec_cmd \"eos-fst${FSID}\" \"rm -rf ${LPATH}\"\n}\n\n# Corrupt file to generate rep_diff_under error\nfunction corrupt_rep_diff_under() {\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_REP_DIFF_UNDER} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '{if (\\$1 ==\\\"fsid\\\" || \\$1 ==\\\"fullpath\\\") {print \\$2};}' | tail -n2\")\n  # Extract the fxid and local path, trim the input\n  local FSID=$(echo \"${CMD_OUT}\" | head -n1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n  local LPATH=$(echo \"${CMD_OUT}\" | tail -n1)\n  exec_cmd eos-cli1 \"eos -r 0 0 file drop fxid:${FXID_REP_DIFF_UNDER} ${FSID}\"\n}\n\n# Corrupt file to generate rep_diff_over error\nfunction corrupt_rep_diff_over() {\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_REP_DIFF_OVER} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '{if (\\$1 ==\\\"fsid\\\") {print \\$2};}' | tail -n2\")\n  # Extract the fxid and local path, trim the input\n  local VECT_FSID=( $(echo \"${CMD_OUT}\" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') )\n  echo \"Used locations: ${VECT_FSID[@]}\"\n  local NEW_FSID=\"\"\n  local FST_ONLINE=$(exec_cmd eos-mgm1 \"eos fs ls | grep \\\"online\\\" | wc -l\")\n\n  for (( i=1; i<=${FST_ONLINE}; i++ )); do\n    local found=false\n    for e in ${VECT_FSID[@]}; do\n      if [[ \"$i\" == \"$e\" ]]; then\n        found=true\n        break\n      fi\n    done\n    if [[ \"${found}\" == false ]]; then\n      NEW_FSID=$i\n      break\n    fi\n  done\n\n  if [[ \"${NEW_FSID}\" == \"\" ]]; then\n    echo \"error: no new FSID found for replication command\"\n    exit 1\n  fi\n\n  exec_cmd eos-cli1 \"eos -r 0 0 file replicate fxid:${FXID_REP_DIFF_OVER} ${VECT_FSID[0]} ${NEW_FSID}\"\n}\n\n# Corrupt file to generate file_unreg error\nfunction corrupt_unreg() {\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_UNREG} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '{if (\\$1 ==\\\"fsid\\\" || \\$1 ==\\\"fullpath\\\") {print \\$2};}' | tail -n2\")\n  # Extract the fxid and local path, trim the input\n  local FSID=$(echo \"${CMD_OUT}\" | head -n1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n  exec_cmd eos-cli1 \"eos -r 0 0 file drop fxid:${FXID_UNREG} ${FSID} -f\"\n}\n\n# Corrupt file to generate RAIN block checksum error\nfunction corrupt_rain_blocxs_err() {\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_RAIN_BLOCKXS} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '{if (\\$1 ==\\\"fsid\\\" || \\$1 ==\\\"fullpath\\\") {print \\$2};}' | tail -n2\")\n  # Extract the fxid and local path, trim the input\n  local FSID=$(echo \"${CMD_OUT}\" | head -n1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n  local LPATH=$(echo \"${CMD_OUT}\" | tail -n1)\n  # Corrupt the checksum of the file by writing random bytes at the beginning of the file\n  exec_cmd \"eos-fst${FSID}\" \"dd if=/dev/urandom of=${LPATH} bs=1 seek=4 count=3 conv=notrunc\"\n}\n\n# Corrupt file to generate rain stripe diff error\nfunction corrupt_rain_stripe_diff() {\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_RAIN_DIFF} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '{if (\\$1 ==\\\"fsid\\\" || \\$1 ==\\\"fullpath\\\") {print \\$2};}' | tail -n2\")\n  # Extract the fxid and local path, trim the input\n  local FSID=$(echo \"${CMD_OUT}\" | head -n1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n  exec_cmd eos-cli1 \"eos -r 0 0 file drop fxid:${FXID_RAIN_DIFF} ${FSID} -f\"\n}\n\n# Corrupt file to generate orphan_n error\nfunction corrupt_orphan() {\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_ORPHAN} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '{if (\\$1 ==\\\"fsid\\\" || \\$1 ==\\\"fullpath\\\") {print \\$2};}' | tail -n2\")\n  # Extract the fxid and local path, trim the input\n  local FSID=$(echo \"${CMD_OUT}\" | head -n1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n  local LPATH=$(echo \"${CMD_OUT}\" | tail -n1)\n  # Do it twice to force delete the filemd object and set the file system off\n  # so that the deletion message is not sent before we drop the list of\n  # deletions.\n  exec_cmd eos-mgm1 \"eos fs config ${FSID} configstatus=off\"\n  exec_cmd eos-mgm1 \"eos rm -F fxid:${FXID_ORPHAN}\"\n  exec_cmd eos-mgm1 \"eos rm -F fxid:${FXID_ORPHAN}\"\n  exec_cmd eos-mgm1 \"eos fs dropdeletion ${FSID}\"\n  exec_cmd eos-mgm1 \"eos fs config ${FSID} configstatus=rw\"\n}\n\n# Corrupt file that can only be fixed by best-effort\nfunction corrupt_best_effort() {\n  # Get the physical locations of the replicas\n  local CMD1_OUT=$(exec_cmd eos-cli1 \"eos -j fileinfo fxid:${FXID_BEST_EFFORT} | jq -r '.locations | .[] | (.fsid|tostring) + \\\" \\\" + .fstpath'\" | head -n1)\n  local CMD2_OUT=$(exec_cmd eos-cli1 \"eos -j fileinfo fxid:${FXID_BEST_EFFORT} | jq -r '.locations | .[] | (.fsid|tostring) + \\\" \\\" + .fstpath'\" | tail -n1)\n  count=0\n  set -x\n  for line in \"${CMD1_OUT}\" \"${CMD2_OUT}\"; do\n    local FSID=$(echo ${line} | cut -d ' ' -f 1)\n    local LPATH=$(echo ${line} | cut -d ' ' -f 2)\n    exec_cmd \"eos-fst${FSID}\" \"echo ${count} >> ${LPATH}\"\n    count=$((count + 10))\n  done\n  set +x\n}\n\n# Corrupt file to generate RAIN stripe size error\nfunction corrupt_rain_stripe_size() {\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_RAIN_STRIPE_SZ} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '{if (\\$1 ==\\\"fsid\\\" || \\$1 ==\\\"fullpath\\\") {print \\$2};}' | tail -n2\")\n  # Extract the fxid and local path, trim the input\n  local FSID=$(echo \"${CMD_OUT}\" | head -n1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n  local LPATH=$(echo \"${CMD_OUT}\" | tail -n1)\n  # Corrupt the size of the stripe by truncating to 0\n  exec_cmd \"eos-fst${FSID}\" \"truncate --size 0 ${LPATH}\"\n}\n\n# Corrupt stripe to generate RAIN stripe error\nfunction corrupt_rain_invalid_stripe() {\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_RAIN_INVALID_STRIPE} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -bF '=' '{if (\\$1 ==\\\"fsid\\\" || \\$1 ==\\\"fullpath\\\") {print \\$2};}' | tail -n2\")\n  # Extract the fxid and local path, trim the input\n  local FSID=$(echo \"${CMD_OUT}\" | head -n1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n  local LPATH=$(echo \"${CMD_OUT}\" | tail -n1)\n  # Corrupt data in the stripe then fix the checksum\n  exec_cmd \"eos-fst${FSID}\" \"dd if=/dev/urandom of=${LPATH} bs=1 seek=5000 count=10 conv=notrunc\"\n  exec_cmd \"eos-fst${FSID}\" \"eos-compute-blockxs ${LPATH}\"\n}\n\n# Corrupt 2 stripes to generate RAIN stripe error\nfunction corrupt_rain_invalid_stripe2() {\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_RAIN_INVALID_STRIPE2} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '{if (\\$1 ==\\\"fsid\\\" || \\$1 ==\\\"fullpath\\\") {print \\$2};}'\")\n  local FIRST_FST=$(echo \"${CMD_OUT}\" | head -n 4 | tail -n 2)\n  local LAST_FST=$(echo \"${CMD_OUT}\" | tail -n 2)\n\n  # Extract the fxid and local path, trim the input\n  local FSID=$(echo \"${FIRST_FST}\" | head -n1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n  local LPATH=$(echo \"${FIRST_FST}\" | tail -n1)\n  # Corrupt data in the stripe then fix the checksum\n  exec_cmd \"eos-fst${FSID}\" \"dd if=/dev/urandom of=${LPATH} bs=1 seek=5000 count=10 conv=notrunc\"\n  exec_cmd \"eos-fst${FSID}\" \"eos-compute-blockxs ${LPATH}\"\n\n  local FSID=$(echo \"${LAST_FST}\" | head -n1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n  local LPATH=$(echo \"${LAST_FST}\" | tail -n1)\n  exec_cmd \"eos-fst${FSID}\" \"dd if=/dev/urandom of=${LPATH} bs=1 seek=5000 count=10 conv=notrunc\"\n  exec_cmd \"eos-fst${FSID}\" \"eos-compute-blockxs ${LPATH}\"\n}\n\n# Duplicate stripe to generate RAIN stripe error\nfunction corrupt_rain_stripe_diff_over() {\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_RAIN_DIFF_OVER} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '{if (\\$1 ==\\\"fsid\\\") {print \\$2};}'\")\n  local VECT_FSID=( $(echo \"${CMD_OUT}\" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') )\n  echo \"Used locations: ${VECT_FSID[@]}\"\n  local NEW_FSID=\"\"\n  local FST_ONLINE=$(exec_cmd eos-mgm1 \"eos fs ls | grep \\\"online\\\" | wc -l\")\n\n  for (( i=1; i<=${FST_ONLINE}; i++ )); do\n    local found=false\n    for e in ${VECT_FSID[@]}; do\n      if [[ \"$i\" == \"$e\" ]]; then\n        found=true\n        break\n      fi\n    done\n    if [[ \"${found}\" == false ]]; then\n      NEW_FSID=$i\n      break\n    fi\n  done\n\n  if [[ \"${NEW_FSID}\" == \"\" ]]; then\n    echo \"error: no new FSID found for replication command\"\n    exit 1\n  fi\n\n  # Start by tagging the file on the new fst\n  exec_cmd eos-cli1 \"eos -r 0 0 file tag ${EOS_RAIN_DIR}/rain_stripe_diff_over.dat +${NEW_FSID}\"\n\n  # Get local path of source and local path of the destination directory\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_RAIN_DIFF_OVER} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g'\")\n  local SRC_LPATH=$(echo \"${CMD_OUT}\" | grep fsid=\"${VECT_FSID[0]}\" -A1 | tail -n1 | sed -r -e 's/^fullpath=//' -e 's/[[:space:]]*$//')\n  local SRC_LDIR=$(echo \"${SRC_LPATH}\" | sed -r 's/[[:alnum:]]+$//')\n  local SRC_LFILE=$(echo \"${SRC_LPATH}\" | grep -oE '[[:alnum:]]+$')\n  local DST_LDIR=$(echo \"${CMD_OUT}\" | grep fsid=\"${NEW_FSID}\" -A1 | tail -n1 | sed -r -e 's/^fullpath=//' -e 's/[[:alnum:]]+[[:space:]]*$//')\n  cp_file_with_xattr_cmd \"eos-fst${VECT_FSID[0]}\" \"${SRC_LDIR}\" \"${SRC_LFILE}\" \"eos-fst${NEW_FSID}\" \"${DST_LDIR}\"\n  cp_file_with_xattr_cmd \"eos-fst${VECT_FSID[0]}\" \"${SRC_LDIR}\" \"${SRC_LFILE}.xsmap\" \"eos-fst${NEW_FSID}\" \"${DST_LDIR}\"\n\n  # Force resync of fmd on new fst\n  exec_cmd eos-cli1 \"eos -r 0 0 file verify fxid:${FXID_RAIN_DIFF_OVER} ${NEW_FSID} -resync\"\n}\n\n# Duplicate stripe and corrupt another stripe to generate RAIN stripe error\nfunction corrupt_rain_stripe_diff_over2() {\n  # First corrupt one stripe\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_RAIN_DIFF_OVER2} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '{if (\\$1 ==\\\"fsid\\\" || \\$1 ==\\\"fullpath\\\") {print \\$2};}' | tail -n2\")\n  local FSID=$(echo \"${CMD_OUT}\" | head -n1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n  local LPATH=$(echo \"${CMD_OUT}\" | tail -n1)\n  exec_cmd \"eos-fst${FSID}\" \"dd if=/dev/urandom of=${LPATH} bs=1 seek=5000 count=10 conv=notrunc\"\n  exec_cmd \"eos-fst${FSID}\" \"eos-compute-blockxs ${LPATH}\"\n\n  # Then duplicate another one\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_RAIN_DIFF_OVER2} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '{if (\\$1 ==\\\"fsid\\\") {print \\$2};}'\")\n  # Extract the fxid and local path, trim the input\n  local VECT_FSID=( $(echo \"${CMD_OUT}\" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') )\n  echo \"Used locations: ${VECT_FSID[@]}\"\n  local NEW_FSID=\"\"\n  local FST_ONLINE=$(exec_cmd eos-mgm1 \"eos fs ls | grep \\\"online\\\" | wc -l\")\n\n  for (( i=1; i<=${FST_ONLINE}; i++ )); do\n    local found=false\n    for e in ${VECT_FSID[@]}; do\n      if [[ \"$i\" == \"$e\" ]]; then\n        found=true\n        break\n      fi\n    done\n    if [[ \"${found}\" == false ]]; then\n      NEW_FSID=$i\n      break\n    fi\n  done\n\n  if [[ \"${NEW_FSID}\" == \"\" ]]; then\n    echo \"error: no new FSID found for replication command\"\n    exit 1\n  fi\n\n  # Start by tagging the file on the new fst\n  exec_cmd eos-cli1 \"eos -r 0 0 file tag ${EOS_RAIN_DIR}/rain_stripe_diff_over2.dat +${NEW_FSID}\"\n\n  # Get local path of source and local path of the destination directory\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_RAIN_DIFF_OVER2} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g'\")\n  local SRC_LPATH=$(echo \"${CMD_OUT}\" | grep fsid=\"${VECT_FSID[0]}\" -A1 | tail -n1 | sed -r -e 's/^fullpath=//' -e 's/[[:space:]]*$//')\n  local SRC_LDIR=$(echo \"${SRC_LPATH}\" | sed -r 's/[[:alnum:]]+$//')\n  local SRC_LFILE=$(echo \"${SRC_LPATH}\" | grep -oE '[[:alnum:]]+$')\n  local DST_LDIR=$(echo \"${CMD_OUT}\" | grep fsid=\"${NEW_FSID}\" -A1 | tail -n1 | sed -r -e 's/^fullpath=//' -e 's/[[:alnum:]]+[[:space:]]*$//')\n  cp_file_with_xattr_cmd \"eos-fst${VECT_FSID[0]}\" \"${SRC_LDIR}\" \"${SRC_LFILE}\" \"eos-fst${NEW_FSID}\" \"${DST_LDIR}\"\n  cp_file_with_xattr_cmd \"eos-fst${VECT_FSID[0]}\" \"${SRC_LDIR}\" \"${SRC_LFILE}.xsmap\" \"eos-fst${NEW_FSID}\" \"${DST_LDIR}\"\n\n  # Force resync of fmd on new fst\n  exec_cmd eos-cli1 \"eos -r 0 0 file verify fxid:${FXID_RAIN_DIFF_OVER2} ${NEW_FSID} -resync\"\n}\n\nfunction corrupt_rain_invalid_header() {\n  local CMD_OUT=$(exec_cmd eos-cli1 \"eos fileinfo fxid:${FXID_RAIN_INVALID_HEADER} -m --fullpath | sed -r 's/[[:alnum:]]+=/\\n&/g' | awk -F '=' '{if (\\$1 ==\\\"fsid\\\" || \\$1 ==\\\"fullpath\\\") {print \\$2};}' | tail -n2\")\n  # Extract the fxid and local path, trim the input\n  local FSID=$(echo \"${CMD_OUT}\" | head -n1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\n  local LPATH=$(echo \"${CMD_OUT}\" | tail -n1)\n  # Corrupt the stripe header by writing random bytes\n  exec_cmd \"eos-fst${FSID}\" \"dd if=/dev/urandom of=${LPATH} bs=1 count=4096\"\n}\n\n# Configure fsck to run more often and reduce the scan times\nfunction configure_fsck() {\n  # First reduce the scan interval on the FSTs\n  local FST_ONLINE=$(exec_cmd eos-mgm1 \"eos fs ls | grep \\\"online\\\" | wc -l\")\n\n  for (( i=1; i<=${FST_ONLINE}; i++ )); do\n    exec_cmd eos-cli1  \"eos -r 0 0 fs config ${i} scan_disk_interval=20 &&\n                        eos -r 0 0 fs config ${i} scan_ns_interval=20 &&\n                        eos -r 0 0 fs config ${i} scaninterval=15 &&\n                        eos -r 0 0 fs config ${i} scan_rain_interval=15\"\n  done\n\n  # Reduce the interval when the fsck collection thread runs\n  exec_cmd eos-cli1  \"eos -r 0 0 fsck config collect-interval-min 0.25;\"\n  exec_cmd eos-cli1  \"eos -r 0 0 fsck config collect on;\"\n}\n\n# Check that we collected all the errors that we expect\nfunction check_all_errors_collected() {\n  # Allow for at most MAX_DELAY seconds to collect all the errors\n  local MAX_DELAY=${1:-\"300\"}\n  local START_TIME=$(date +%s)\n  while\n    local CURRENT_TIME=$(date +%s)\n    local FOUND_D_MEM_SZ_DIFF=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_D_MEM_SZ_DIFF}\")\n    local FOUND_M_MEM_SZ_DIFF1=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_M_MEM_SZ_DIFF1}\")\n    local FOUND_M_MEM_SZ_DIFF2=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_M_MEM_SZ_DIFF2}\")\n    local FOUND_D_CX_DIFF=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_D_CX_DIFF}\")\n    local FOUND_M_CX_DIFF=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_M_CX_DIFF}\")\n    local FOUND_M_CX_DIFF1=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_M_CX_DIFF1}\")\n    local FOUND_UNREG=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_UNREG}\")\n    local FOUND_REP_MISSING=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_REP_MISSING}\")\n    local FOUND_REP_DIFF_UNDER=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_REP_DIFF_UNDER}\")\n    local FOUND_REP_DIFF_OVER=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_REP_DIFF_OVER}\")\n    local FOUND_RAIN_BLOCKXS=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_RAIN_BLOCKXS}\")\n    local FOUND_RAIN_DIFF=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_RAIN_DIFF}\")\n    local FOUND_RAIN_STRIPE_SZ=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_RAIN_STRIPE_SZ}\")\n    local FOUND_ORPHAN=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_ORPHAN}\")\n    local FOUND_BEST_EFFORT=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_BEST_EFFORT}\")\n    local FOUND_RAIN_INVALID_STRIPE=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_RAIN_INVALID_STRIPE}\")\n    local FOUND_RAIN_INVALID_STRIPE2=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_RAIN_INVALID_STRIPE2}\")\n    local FOUND_RAIN_DIFF_OVER=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_RAIN_DIFF_OVER}\")\n    local FOUND_RAIN_DIFF_OVER2=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_RAIN_DIFF_OVER2}\")\n    local FOUND_RAIN_INVALID_HEADER=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i | grep ${FXID_RAIN_INVALID_HEADER}\")\n\n    if [[ -z \"${FOUND_D_MEM_SZ_DIFF}\"        ||\n          -z \"${FOUND_M_MEM_SZ_DIFF1}\"       ||\n          -z \"${FOUND_M_MEM_SZ_DIFF2}\"       ||\n          -z \"${FOUND_D_CX_DIFF}\"            ||\n          -z \"${FOUND_M_CX_DIFF}\"            ||\n          -z \"${FOUND_M_CX_DIFF1}\"           ||\n          -z \"${FOUND_UNREG}\"                ||\n          -z \"${FOUND_REP_MISSING}\"          ||\n          -z \"${FOUND_REP_DIFF_UNDER}\"       ||\n          -z \"${FOUND_REP_DIFF_OVER}\"        ||\n          -z \"${FOUND_RAIN_BLOCKXS}\"         ||\n          -z \"${FOUND_RAIN_DIFF}\"            ||\n          -z \"${FOUND_RAIN_STRIPE_SZ}\"       ||\n          -z \"${FOUND_ORPHAN}\"               ||\n          -z \"${FOUND_BEST_EFFORT}\"          ||\n          -z \"${FOUND_RAIN_INVALID_STRIPE}\"  ||\n          -z \"${FOUND_RAIN_INVALID_STRIPE2}\" ||\n          -z \"${FOUND_RAIN_DIFF_OVER}\"       ||\n          -z \"${FOUND_RAIN_DIFF_OVER2}\"      ]]\n          -z \"${FOUND_RAIN_INVALID_HEADER}\"  ]]; then\n      if (( $((${CURRENT_TIME} - ${START_TIME})) >= ${MAX_DELAY} )); then\n        echo \"error: some errors not discovered\"\n        exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i -a\"\n        cleanup\n        exit 1\n      else\n        echo \"info: sleep for 5 seconds waiting for error collection, `secs_to_human $((${CURRENT_TIME} - ${START_TIME}))` passed\"\n        sleep 5\n      fi\n\n      (( $((${CURRENT_TIME} - ${START_TIME})) < ${MAX_DELAY} ))\n    else\n     echo \"info: found all the errors we were expecting\"\n     false # to end the loop\n    fi\n  do\n   :\n  done\n}\n\n# Cleanup the files and directories at the MGM\nfunction cleanup() {\n  exec_cmd eos-cli1 \"eos -r 0 0 rm -rF \\\"${EOS_REPLICA_DIR}/*\\\" &&\n                     eos -r 0 0 rmdir  ${EOS_REPLICA_DIR}/ &&\n                     eos -r 0 0 rm -rF \\\"${EOS_RAIN_DIR}/*\\\" &&\n                     eos -r 0 0 rmdir  ${EOS_RAIN_DIR}/\"\n\n  exec_cmd eos-cli1 \"eos -r 0 0 fsck config collect off\"\n}\n\n\n# @todo the whole args parsing story should be rewritten, possibly\n# decoupling docker/kubernetes execution from the test itself\n\nMAX_DELAY=300\n\nif [[ \"$1\" == \"--max-delay\" ]]; then\n  MAX_DELAY=$2\n  shift # past argument\n  shift # past value\nfi\n\n# Set up global variables\nIS_DOCKER=false\nIS_LOCAL=false\nK8S_NAMESPACE=\"\"\n\nif [[ $# -lt 2 ]]; then\n  echo \"error: invalid number of arguments\"\n  usage\n  exit 1\nfi\n\nif [[ \"$1\" != \"--type\" ]]; then\n  echo \"error: unknown argument \\\"$1\\\"\"\n  usage\n  exit 1\nfi\n\nif [[ \"$2\" == \"docker\" ]]; then\n    IS_DOCKER=true\nelif [[ \"$2\" == \"local\" ]]; then\n    IS_LOCAL=true\nelif [[ \"$2\" == \"k8s\" ]]; then\n    IS_DOCKER=false\nelse\n  echo \"error: unknown type of executor \\\"$2\\\"\"\n  usage\n  exit 1\nfi\n\nif [[ \"${IS_LOCAL}\" == true && -z \"${EOS_MGM_URL}\" ]]; then\n   echo \"error: EOS_MGM_URL env needs to be set for local!\"\n   exit 1\nfi\n\nif [[ \"${IS_DOCKER}\" == false && \"${IS_LOCAL}\" == false ]]; then\n  # For the Kubernetes setup we also need a namespace argument\n  if [[ $# -lt 3 ]]; then\n    echo \"error: missing Kubernetes namespace argument\"\n    usage\n    exit 1\n  fi\n  K8S_NAMESPACE=\"$3\"\nfi\n\necho \"eos-fsck-test configuration:\"\necho \"MAX_DELAY=$MAX_DELAY\"\necho \"IS_DOCKER=$IS_DOCKER\"\necho \"IS_LOCAL=$IS_LOCAL\"\necho \"K8S_NAMESPACE=$K8S_NAMESPACE (if IS_DOCKER=false)\"\necho\n\n# Configure fsck to run more often and reduce scan times\nconfigure_fsck\n\n# Create test file\ncreate_test_files\n\n# Create different type of corruptions for different files\ncorrupt_d_mem_sz_diff\ncorrupt_m_mem_sz_diff1\ncorrupt_m_mem_sz_diff2\ncorrupt_d_cx_diff\ncorrupt_m_cx_diff\ncorrupt_m_cx_diff1\ncorrupt_rep_missing_n\ncorrupt_rep_diff_under\ncorrupt_rep_diff_over\ncorrupt_unreg\ncorrupt_rain_blocxs_err\ncorrupt_rain_stripe_diff\ncorrupt_rain_stripe_size\ncorrupt_orphan\ncorrupt_best_effort\ncorrupt_rain_invalid_stripe\ncorrupt_rain_invalid_stripe2\ncorrupt_rain_stripe_diff_over\ncorrupt_rain_stripe_diff_over2\ncorrupt_rain_invalid_header\n\n# Check that we are collecting all the expected errors\ncheck_all_errors_collected $MAX_DELAY\n\n# Enable the repair thread\nexec_cmd eos-cli1 \"eos -r 0 0 fsck config best-effort on\"\nexec_cmd eos-cli1 \"eos -r 0 0 fsck config repair on\"\n\n# Cleanup the orphan entries\nexec_cmd eos-cli1 \"eos -r 0 0 fsck clean_orphans\"\n\n# Wait for all the errors to be repaired\nSTART_TIME=$(date +%s)\n\nwhile\n  CURRENT_TIME=$(date +%s)\n  HAS_ERRORS=$(exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i\")\n\n  if [[ ! -z \"${HAS_ERRORS}\" ]]; then\n    if (( $((${CURRENT_TIME} - ${START_TIME})) >= ${MAX_DELAY} )); then\n      echo \"error: some errors where not repaired\"\n      exec_cmd eos-cli1 \"eos -r 0 0 fsck report -i -a\"\n      cleanup\n      exit 1\n    else\n      echo \"info: sleep for 5 seconds waiting for error repair, `secs_to_human $((${CURRENT_TIME} - ${START_TIME}))` passed\"\n      sleep 5\n    fi\n  else\n    echo \"info: all errors were repaired\"\n    false # to end the loop\n  fi\ndo\n  :\ndone\n\ncleanup\n"
  },
  {
    "path": "test/eos-fst-close-test",
    "content": "#!/bin/bash -ex\n\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2024 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\n# Bring in the utilities for handle docker and k8s deployments\nSCRIPTPATH=\"$( cd \"$(dirname \"$0\")\" >/dev/null 2>&1 ; pwd -P )\"\nsource ${SCRIPTPATH}/eos-test-utils\nexport PATH=/opt/eos/xrootd/bin/:${PATH}\n\n#-------------------------------------------------------------------------------\n# Description: this script tests various failure scenarios that happen during\n# the FST close operation of replica and rain layouts. It tries to check any\n# such failure leaves the original file in a consistent state and does not\n# remove or drop any replicas even during TPC transfers or RAIN recovery ops.\n#-------------------------------------------------------------------------------\n\nusage() {\n    echo ''' Usage $0 --type <local|docker|k8s>\n                      [--mgm <MGM endpoint, default root://localhost>]\n                      [--path <test root path in EOS, default /eos/dockertest]\n                      [--help] - usage and exit\n'''; }\n\n\n# Parser from: https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash\nusage_error () { echo >&2 \"$(basename \"$0\"):  $1\"; exit 2; }\nassert_argument () { test \"$1\" != \"$EOL\" || usage_error \"$2 requires an argument\"; }\n\nIS_LOCAL=false\nIS_DOCKER=false\nK8S_NAMESPACE=\"\"\nEOS_ROOT=/eos/dockertest\nEOS_RAIN_DIR=${EOS_ROOT}/fst-close-test/rain\nEOS_REPLICA_DIR=${EOS_ROOT}/fst-close-test/replica\nXRDCP_BIN=/opt/eos/xrootd/bin/xrdcp\n\n# One loop, nothing more.\nif [ \"$#\" != 0 ]; then\n  EOL=$(printf '\\1\\3\\3\\7')\n  set -- \"$@\" \"$EOL\"\n  while [ \"$1\" != \"$EOL\" ]; do\n    opt=\"$1\"; shift\n    case \"$opt\" in\n      # Your options go here.\n      --type)     assert_argument \"$1\" \"$opt\";\n                  if [[ \"${1}\" == \"local\" ]]; then\n                      IS_LOCAL=true;\n                  elif [[ \"${1}\" == \"docker\" ]]; then\n                      IS_DOCKER=true;\n                  elif [[ \"${1}\" == \"k8s\" ]]; then\n                       IS_LOCAL=false;\n                       IS_DOCKER=false;\n\n                       if [[ $# -lt 3 ]]; then\n                         echo \"error: missing Kubernetes namespace argument\"\n                         usage\n                         exit 1\n                       fi\n\n                       shift\n                       K8S_NAMESPACE=\"$1\"\n                  else\n                      usage_error \"unknown type option: '${1}'\";\n                  fi\n                  shift;;\n      --mgm)      assert_argument \"$1\" \"$opt\"; EOS_MGM_URL=${1}; shift;;\n      --path)     assert_argument \"$1\" \"$opt\"\n                  EOS_ROOT=${1}\n                  EOS_RAIN_DIR=${EOS_ROOT}/fst-close-test/rain\n                  EOS_REPLICA_DIR=${EOS_ROOT}/fst-close-test/replica\n                  shift;;\n      --help) usage $0; exit 0;;\n      # Arguments processing. You may remove any unneeded line after the 1st.\n      -|''|[!-]*) set -- \"$@\" \"$opt\";;                                          # positional argument, rotate to the end\n      --*=*)      set -- \"${opt%%=*}\" \"${opt#*=}\" \"$@\";;                        # convert '--name=arg' to '--name' 'arg'\n      -[!-]?*)    set -- \"$(echo \"${opt#-}\" | sed 's    /g')\" \"$@\";;            # convert '-abc' to '-a' '-b' '-c'\n      --)         while [ \"$1\" != \"$EOL\" ]; do set -- \"$@\" \"$1\"; shift; done;;  # process remaining arguments as positional\n      -*)         usage_error \"unknown option: '$opt'\";;                        # catch misspelled options\n      *)          usage_error \"this should NEVER happen ($opt)\";;               # sanity test for previous patterns\n    esac\n  done\n  shift  # $EOL\nfi\n\n#-------------------------------------------------------------------------------\n# Print the configuration used for running the tests\n#-------------------------------------------------------------------------------\nfunction print_config() {\n  echo \"\ninfo: environment configuration:\nIS_LOCAL=${IS_LOCAL}\nIS_DOCKER=${IS_DOCKER}\nEOS_MGM_URL=${EOS_MGM_URL}\nK8S_NAMESPACE=${K8S_NAMESPACE}\n\"\n}\n\n#-------------------------------------------------------------------------------\n# Perform cleanup of the files, directories and configured behaviours\n#-------------------------------------------------------------------------------\nfunction cleanup() {\n  echo \"info: running cleanup step\"\n  exec_cmd eos-mgm1 \"eos ns behaviour clear all\"\n  local FST_ONLINE=$(exec_cmd eos-mgm1 \"eos fs ls | grep \\\"online\\\" | wc -l\")\n\n  for (( i=1; i<=${FST_ONLINE}; i++ )); do\n    exec_cmd eos-mgm1 \"eos -r 0 0 fs config ${i} configstatus=rw\"\n  done\n\n  exec_cmd eos-cli1 \"eos -r 0 0 rm -rF ${EOS_REPLICA_DIR} &&\n                     eos -r 0 0 rm -rF ${EOS_RAIN_DIR} &&\n                     eos -r 0 0 rmdir \\\"${EOS_ROOT}/fst-close-test\\\" &&\n                     rm -rf /tmp/test_file.dat\"\n}\n\n#-------------------------------------------------------------------------------\n# Do the necessary MGM preparation before the tests\n#-------------------------------------------------------------------------------\nfunction prepare_mgm() {\n  echo \"info: prepare MGM configuration\"\n  exec_cmd eos-mgm1 \"eos ns behaviour set rain_min_fsid_entry on\"\n  exec_cmd eos-cli1 \"dd status=none if=/dev/urandom of=${TEST_FILE} bs=1M count=22 &&\n                     eos -r 0 0 mkdir -p ${EOS_RAIN_DIR} &&\n                     eos -r 0 0 mkdir -p ${EOS_REPLICA_DIR} &&\n                     eos -r 0 0 attr set default=replica ${EOS_REPLICA_DIR} &&\n                     eos -r 0 0 attr set default=raid6 ${EOS_RAIN_DIR}\"\n  # Add a delay for the configstatus change to propagate in the scheduling\n  # data structures otherwise we end up writing to file systems which are off\n  XS_TEST_FILE=$(exec_cmd eos-cli1 \"eos-adler32 ${TEST_FILE} | awk -F '[ =]' '{print \\$NF;}'\")\n  sleep 2\n}\n\n#-------------------------------------------------------------------------------\n# Test normal write and read functionality\n#-------------------------------------------------------------------------------\ntest_write_read_successful() {\n  local test_fn=${1}\n  local eos_fn=\"${EOS_REPLICA_DIR}/write_read_success.dat\"\n  echo \"info: test successful write and read\"\n  local xs_local=$(exec_cmd eos-cli1 \"eos-adler32 ${test_fn} | awk -F '[ =]' '{print \\$NF;}'\")\n  exec_cmd eos-cli1 \"${XRDCP_BIN} -f --nopbar ${test_fn} ${EOS_MGM_URL}/${eos_fn}\"\n  exec_cmd eos-cli1 \"eos -j fileinfo ${eos_fn} | jq '.locations | length' | grep 2 > /dev/null\"\n  local xs_eos=$(exec_cmd eos-cli1 \"eos -j fileinfo ${eos_fn} | jq '.checksumvalue' | tr -d '\\\"' \")\n  exec_cmd eos-cli1 \"${XRDCP_BIN} -f --nopbar ${EOS_MGM_URL}/${eos_fn} /tmp/test_file_copy.dat\"\n  local xs_copy=$(exec_cmd eos-cli1 \"eos-adler32 /tmp/test_file_copy.dat | awk -F '[ =]' '{print \\$NF;}'\")\n  exec_cmd eos-cli1 \"rm -rf /tmp/test_file_copy.dat\"\n  exec_cmd eos-cli1 \"eos -r 0 0 rm -rF ${eos_fn}\"\n\n  if [[ \"${xs_local}\" != \"${xs_eos}\" ]]; then\n    echo \"error: eos file checksum does not match the original\"\n    return 1\n  fi\n\n  if [[ \"${xs_copy}\" != \"${xs_local}\" ]]; then\n    echo \"error: copied file checksum does not match the original\"\n    return 1\n  fi\n  return 0\n}\n\n#-------------------------------------------------------------------------------\n# Test reading a file with a checksum corruption for the entry replica. This\n# should return an error on close with the following error message:\n# \"error: close failed with retc=-5\"\n#-------------------------------------------------------------------------------\ntest_read_xs_error() {\n  echo \"info: test read checksum error\"\n  local test_fn=${1}\n  local eos_fn=\"${EOS_REPLICA_DIR}/read_xs_err.dat\"\n  local error_msg=\"Unable to read file - wrong file checksum\"\n  exec_cmd eos-cli1 \"${XRDCP_BIN} -f --nopbar ${test_fn} ${EOS_MGM_URL}/${eos_fn}\"\n  local min_fsid=$(exec_cmd eos-cli1 \"eos -j fileinfo ${eos_fn} | jq '[.locations | .[] | .fsid] | sort | first'\")\n  local max_fsid=$(exec_cmd eos-cli1 \"eos -j fileinfo ${eos_fn} | jq '[.locations | .[] | .fsid] | sort | last'\")\n  local local_path=$(exec_cmd eos-cli1 \"eos -j fileinfo ${eos_fn} | jq '.locations | .[] | select(.fsid==${min_fsid}) | .fstpath' | tr -d '\\\"' \")\n  # Corrupt the raw file on disk for the min_fsid\n  local orig_xs=$(exec_cmd eos-fst${min_fsid} \"eos-adler32 ${local_path} | awk -F '[ =]' '{print \\$NF;}'\")\n  exec_cmd eos-fst${min_fsid} \"printf '\\xab' | dd status=none of=${local_path} bs=1 seek=$((0xbeef)) conv=notrunc\"\n  exec_cmd eos-fst${min_fsid} \"printf '\\x12' | dd status=none of=${local_path} bs=1 seek=$((0xdead)) conv=notrunc\"\n  local corrupt_xs=$(exec_cmd eos-fst${min_fsid} \"eos-adler32 ${local_path} | awk -F '[ =]' '{print \\$NF;}'\")\n\n  if [[ \"${orig_xs}\" == \"${corrupt_xs}\" ]]; then\n      echo \"error: really unlucky, the original and corrupted checksum match\"\n      exit 1\n  fi\n\n  # Disable the max_fsid file system to be sure we always read from the bad replica\n  exec_cmd eos-cli1 \"eos -r 0 0 fs config ${max_fsid} configstatus=off\"\n  local cp_output=$(exec_cmd eos-cli1 \"${XRDCP_BIN} -f --nopbar ${EOS_MGM_URL}/${eos_fn} /tmp/test_file_copy.dat 2>&1\")\n  local rc1=0\n  # Check if output contains the expected error message\n  if [[ ! ${cp_output} =~ ${error_msg} ]]; then\n    rc1=$?\n    echo \"error: command output is \\\"${cp_output}\\\"\"\n  fi\n\n  # Read the file in reverse so that the checksum check on close gets triggered\n  error_msg=\"error: close failed with retc=-1 errno=5\"\n  cp_output=$(exec_cmd eos-cli1 \"xrdcpbackward ${EOS_MGM_URL}/${eos_fn} 2>&1 || true\")\n  local rc2=0\n  # Check if output contains the expected error message\n  if [[ ! ${cp_output} =~ ${error_msg} ]]; then\n    rc2=$?\n    echo \"error: command output is \\\"${cp_output}\\\"\"\n  fi\n\n  exec_cmd eos-cli1 \"eos -r 0 0 fs config ${max_fsid} configstatus=rw\"\n  exec_cmd eos-cli1 \"eos -r 0 0 rm -rF ${eos_fn}\"\n  echo \"info: return value is ${rc1}${rc2}\"\n  return ${rc1}${rc2}\n}\n\n#-------------------------------------------------------------------------------\n# Test writing a file which is below the minsize policy. With xrdcp this should\n# return an error on open with the following error message:\n# Unable to open - bookingsize violates minimum allowed filesize\n#-------------------------------------------------------------------------------\ntest_write_min_filesize() {\n  echo \"info: test write with min file size\"\n  local test_fn=${1}\n  local eos_fn=\"${EOS_REPLICA_DIR}/xrdcp_min_filesize.dat\"\n  local error_msg=\"Unable to open - bookingsize violates minimum allowed filesize\"\n  exec_cmd eos-cli1 \"eos -r 0 0 attr set sys.forced.minsize=$((25*1024*1024)) ${EOS_REPLICA_DIR} >& /dev/null\"\n  output=$(exec_cmd eos-cli1 \"${XRDCP_BIN} -f --nopbar ${test_fn} ${EOS_MGM_URL}/${eos_fn} 2>&1 || true\")\n  local rc1=0\n\n  if [[ ! ${output} =~ ${error_msg} ]]; then\n    rc1=$?\n    echo \"error: message \\\"${output}\\\" different from expected \\\"${error_msg}\\\"\"\n  fi\n\n  error_msg=\"No such file or directory\"\n  output=$(exec_cmd eos-cli1 \"eos fileinfo ${eos_fn} 2>&1 || true\")\n  local rc2=0\n  if [[ ! ${output} =~ ${error_msg} ]]; then\n    rc2=$?\n    echo \"error: message \\\"${output}\\\" different from expected \\\"${error_msg}\\\"\"\n  fi\n\n  exec_cmd eos-cli1 \"eos -r 0 0 attr rm sys.forced.minsize ${EOS_REPLICA_DIR} >& /dev/null\"\n  return ${rc1}${rc2}\n}\n\n#-------------------------------------------------------------------------------\n# Test writing a file which is above the maxsize policy. With xrdcp this should\n# return an error on open with the following error message:\n# Unable to open - bookingsize violates maximum allowed filesize\n#-------------------------------------------------------------------------------\ntest_write_max_filesize() {\n  echo \"info: test write with max file size\"\n  local test_fn=${1}\n  local eos_fn=\"${EOS_REPLICA_DIR}/xrdcp_max_filesize.dat\"\n  local error_msg=\"Unable to open - bookingsize violates maximum allowed filesize\"\n  exec_cmd eos-cli1 \"eos -r 0 0 attr set sys.forced.maxsize=$((1*1024*1024)) ${EOS_REPLICA_DIR} >& /dev/null\"\n  output=$(exec_cmd eos-cli1 \"${XRDCP_BIN} -f --nopbar ${test_fn} ${EOS_MGM_URL}/${eos_fn} 2>&1 || true\")\n  local rc1=0\n\n  if [[ ! ${output} =~ ${error_msg} ]]; then\n    rc1=$?\n    echo \"error: message \\\"${output}\\\" different from expected \\\"${error_msg}\\\"\"\n  fi\n\n  error_msg=\"No such file or directory\"\n  output=$(exec_cmd eos-cli1 \"eos fileinfo ${eos_fn} 2>&1 || true\")\n  local rc2=0\n\n  if [[ ! ${output} =~ ${error_msg} ]]; then\n    rc3=$?\n    echo \"error: message \\\"${output}\\\" different from expected \\\"${error_msg}\\\"\"\n  fi\n\n  exec_cmd eos-cli1 \"eos -r 0 0 attr rm sys.forced.maxsize ${EOS_REPLICA_DIR} >& /dev/null\"\n  return ${rc1}${rc2}\n}\n\n#-------------------------------------------------------------------------------\n# Test writing a file with a different target size. With xrdcp this should\n# return an error on close with the following error message:\n# \"Unable to store file - file has been cleaned because the stored file does\n#  not match the provided targetsize\"\n#-------------------------------------------------------------------------------\ntest_write_targetsize() {\n  echo \"info: test write with targetsize\"\n  local test_fn=${1}\n  local eos_fn=\"${EOS_REPLICA_DIR}/xrdcp_targetsize.dat\"\n  local error_msg=\"Unable to store file - file has been cleaned because the stored file does not match the provided targetsize\"\n  output=$(exec_cmd eos-cli1 \"${XRDCP_BIN} -f --nopbar ${test_fn} ${EOS_MGM_URL}/${eos_fn}?eos.targetsize=10000 2>&1 || true\")\n  local rc1=0\n\n  if [[ ! ${output} =~ ${error_msg} ]]; then\n    rc1=$?\n    echo \"error: message \\\"${output}\\\" different from expected \\\"${error_msg}\\\"\"\n  fi\n\n  error_msg=\"No such file or directory\"\n  output=$(exec_cmd eos-cli1 \"eos fileinfo ${eos_fn} 2>&1 || true\")\n  local rc2=0\n\n  if [[ ! ${output} =~ ${error_msg} ]]; then\n    rc2=$?\n    echo \"error: message \\\"${output}\\\" different from expected \\\"${error_msg}\\\"\"\n  fi\n\n  return ${rc1}${rc2}\n}\n\n#-------------------------------------------------------------------------------\n# Test writing a file with a pre-set checksum value. With xrdcp this should\n# return an error on close with the following error message\n# \"Unable to store file - file has been cleaned because of a checksum error\"\n#-------------------------------------------------------------------------------\ntest_write_xs_preset() {\n  echo \"info: test file write with checksum pre-set\"\n  local test_fn=${1}\n  local eos_fn=\"${EOS_REPLICA_DIR}/xrdcp_xs_preset.dat\"\n  local error_msg=\"Unable to store file - file has been cleaned because of a checksum error\"\n  local output=$(exec_cmd eos-cli1 \"${XRDCP_BIN} -f --nopbar ${test_fn} ${EOS_MGM_URL}/${eos_fn}?eos.checksum=deadbeef 2>&1 || true\")\n  local rc1=0\n\n  if [[ ! ${output} =~ ${error_msg} ]]; then\n    rc1=$?\n    echo \"error: message \\\"${output}\\\" different from expected \\\"${error_msg}\\\"\"\n  fi\n\n  error_msg=\"No such file or directory\"\n  output=$(exec_cmd eos-cli1 \"eos fileinfo ${eos_fn} 2>&1 || true\")\n  local rc2=0\n  if [[ ! ${output} =~ ${error_msg} ]]; then\n    rc2=$?\n    echo \"error: message \\\"${output}\\\" different from expected \\\"${error_msg}\\\"\"\n  fi\n\n  return ${rc1}${rc2}\n}\n\n#-------------------------------------------------------------------------------\n# Test writing a file where the close on the second replica fails due to a\n# simulated write xs error i.e. the close will fail.\n#-------------------------------------------------------------------------------\ntest_write_replica_fail_close_2nd() {\n  echo \"info: test file write with fail on second replica close\"\n  local test_fn=${1}\n  local eos_fn=\"${EOS_REPLICA_DIR}/xrdcp_fail_close_2nd.dat\"\n  # Set all the FSes > 2 in RO mode before writing the file\n  local FST_ONLINE=$(exec_cmd eos-mgm1 \"eos -r 0 0 fs ls | grep \\\"online\\\" | wc -l\")\n\n  for (( i=3; i<=${FST_ONLINE}; i++ )); do\n    exec_cmd eos-mgm1 \"eos -r 0 0 fs config ${i} configstatus=ro\"\n  done\n\n  local node=$(exec_cmd eos-mgm1 \"eos -r 0 0 fs ls \\\" 2 \\\" | grep booted  | awk '{print \\$1\\\":\\\"\\$2;}'\")\n  # Set simulated write checksum error so that close fails\n  exec_cmd eos-mgm1 \"eos -r 0 0 node config ${node} error.simulation=xs_write > /dev/null\"\n  # Wait for the config to propagate to the FST\n  sleep 2\n  exec_cmd eos-cli1 \"${XRDCP_BIN} -f --nopbar ${test_fn} ${EOS_MGM_URL}/${eos_fn}\"\n  sleep 5\n  # Check that the file as only one replica in the namespace as the result of the\n  # repair on close that files since there are no other available file systems!\n  exec_cmd eos-cli1 \"eos -j fileinfo ${eos_fn} | jq '.locations | length' | grep 1 > /dev/null\"\n  # Redo the upload but add a delay for write xs error for FST 2 so that we\n  # can clear error during the close for the adjust replica to be successful\n  exec_cmd eos-mgm1 \"eos -r 0 0 node config ${node} error.simulation=xs_write_10 > /dev/null\"\n  sleep 4\n  exec_cmd eos-cli1 \"${XRDCP_BIN} -f --nopbar ${test_fn} ${EOS_MGM_URL}/${eos_fn} & sleep 4; eos -r 0 0 node config ${node} error.simulation=none > /dev/null\"\n  sleep 12\n  # Now we should have two replicas attached to the NS entry\n  exec_cmd eos-cli1 \"eos -j fileinfo ${eos_fn} | jq '.locations | length' | grep 2 > /dev/null\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 node config $node error.simulation=none > /dev/null\"\n  exec_cmd eos-cli1 \"eos -r 0 0 rm -rF ${eos_fn}\"\n\n  for (( i=3; i<=${FST_ONLINE}; i++ )); do\n    exec_cmd eos-mgm1 \"eos -r 0 0 fs config ${i} configstatus=rw\"\n  done\n  sleep 2\n}\n\n#-------------------------------------------------------------------------------\n# Test RAIN file recovery for which the TPC transfer timesout and which should\n# leave the file in a consistent state.\n#-------------------------------------------------------------------------------\ntest_rain_recovery_tpc_timeout() {\n  echo \"info: test rain file recovery where client TPC times out\"\n  local test_fn=${1}\n  local eos_fn=\"${EOS_RAIN_DIR}/xrdcp_recovery_timeout.dat\"\n  # Make sure the file lands on FSes 1-6\n  local FST_ONLINE=$(exec_cmd eos-mgm1 \"eos -r 0 0 fs ls | grep \\\"online\\\" | wc -l\")\n\n  if [[ ${FST_ONLINE} -ge 7 ]]; then\n    for (( i=7; i<=${FST_ONLINE}; i++ )); do\n      exec_cmd eos-mgm1 \"eos -r 0 0 fs config ${i} configstatus=ro\"\n    done\n  fi\n  # Wait for the file system status to propagate in the scheduling view\n  sleep 3\n  exec_cmd eos-cli1 \"${XRDCP_BIN} -f --nopbar ${test_fn} ${EOS_MGM_URL}/${eos_fn}\"\n  # Collect list of file systems that hold the file\n  local orig_loc=$(exec_cmd eos-cli1 \"eos -j fileinfo ${eos_fn} | jq '.locations | .[] | .fsid' | tr '\\n' ' '\")\n  local orig_xs=$(exec_cmd eos-cli1 \"eos -j fileinfo ${eos_fn} | jq '.checksumvalue' | tr -d '\\\"' \")\n  # Put back the FSes in rw mode to have available destinations for recovery\n  if [[ ${FST_ONLINE} -ge 7 ]]; then\n    for (( i=7; i<=${FST_ONLINE}; i++ )); do\n      exec_cmd eos-mgm1 \"eos -r 0 0 fs config ${i} configstatus=rw\"\n    done\n  fi\n  # Wait for the file system status to propagate in the scheduling view\n  sleep 3\n  # Add read delay of 20 seconds to a stripe that will be read (and not recovered!)\n  local node=$(exec_cmd eos-mgm1 \"eos -r 0 0 fs ls \\\" 2 \\\" | grep booted  | awk '{print \\$1\\\":\\\"\\$2;}'\")\n  exec_cmd eos-mgm1 \"eos -r 0 0 node config ${node} error.simulation=read_delay_20 > /dev/null\"\n  sleep 2\n  # Trigger TPC recovery but with a small timeout so that the transfer fails and for a\n  # file system where we don't have read_delay enabled\n  local output=$(exec_cmd eos-cli1 \"XRD_TIMEOUTRESOLUTION=1 XRD_CPTPCTIMEOUT=10 ${XRDCP_BIN} -f --tpc only \\\"${EOS_MGM_URL}/${eos_fn}?eos.pio.action=reconstruct&eos.pio.recfs=1\\\" ${EOS_MGM_URL}/${eos_fn}_copy 2>&1 || true\")\n  # Be careful about the contents of this since it's a regex used as pattern\n  local error_msg=\"Operation expired\"\n  local rc1=0\n\n  if [[ ! ${output} =~ ${error_msg} ]]; then\n    rc1=1\n    echo \"error: TPC transfer did not fail with operation expired\"\n    echo \"output=${output}\"\n  fi\n\n  # Remove the simulated read delay\n  exec_cmd eos-mgm1 \"eos -r 0 0 node config ${node} error.simulation=none > /dev/null\"\n  # Check that the checksum of a download still matches the original one\n  local new_xs=\"$(exec_cmd eos-cli1 \"${XRDCP_BIN} -f --nopbar ${EOS_MGM_URL}/${eos_fn} /var/tmp/temp; xs=\\$(eos-adler32 /var/tmp/temp | awk -F '[ =]' '{print \\$NF;}'); rm -rf /var/tmp/temp; echo \\${xs}\")\"\n  local rc2=0\n\n  if [[ \"${new_xs}\" != \"${orig_xs}\" ]]; then\n    rc2=1\n    echo \"error: downloaded copy checksum does not match the original\"\n  fi\n  # Check that we still have the same locations for the stripes as nothing\n  # should be touched since there was an error during the transfer\n  local new_loc=$(exec_cmd eos-cli1 \"eos -j fileinfo ${eos_fn} | jq '.locations | .[] | .fsid' | tr '\\n' ' '\")\n  local rc3=0\n\n  if [[ \"${orig_loc}\" != \"${new_loc}\" ]]; then\n    rc3=1\n    echo \"error: the file has new locations after the failed transfer, orig=${orig_loc[*]}, new=${new_loc[*]}\"\n  fi\n\n  # Do cleanup\n  exec_cmd eos-cli1 \"eos -r 0 0 rm -rF ${eos_fn}\"\n  return ${rc1}${rc2}${rc3}\n}\n\n#-------------------------------------------------------------------------------\n# Test RAIN file recovery that should be aborted and the original file should\n# be left untouched when there is an error on close hapenning for one of the\n# stripes that is not to be recovered.\n#-------------------------------------------------------------------------------\ntest_rain_recovery_close_timeout() {\n  echo \"info: test rain file recovery where a stripe close fails\"\n  local test_fn=${1}\n  local eos_fn=\"${EOS_RAIN_DIR}/xrdcp_recovery_close_timeout.dat\"\n  exec_cmd eos-cli1 \"${XRDCP_BIN} -f --nopbar ${test_fn} ${EOS_MGM_URL}/${eos_fn}\"\n  # Collect list of file systems that hold the file\n  local orig_loc=$(exec_cmd eos-cli1 \"eos -j fileinfo ${eos_fn} | jq '.locations | .[] | .fsid' | tr '\\n' ' '\")\n  local orig_xs=$(exec_cmd eos-cli1 \"eos -j fileinfo ${eos_fn} | jq '.checksumvalue' | tr -d '\\\"' \")\n  # File system for which we'll try to recover the stripe\n  local recover_fsid=$(echo ${orig_loc} | cut -d ' ' -f 1)\n  # File system where we will simulate a close error\n  local close_err_fsid=$(echo ${orig_loc} | cut -d ' ' -f 2)\n  local close_err_node=$(exec_cmd eos-mgm1 \"eos -r 0 0 fs ls \\\" ${close_err_fsid} \\\" | grep booted  | awk '{print \\$1\\\":\\\"\\$2;}'\")\n  exec_cmd eos-mgm1 \"eos -r 0 0 node config ${close_err_node} error.simulation=close > /dev/null\"\n  sleep 2\n  # Trigger TPC recovery that should fail due to the simulated close error\n  local output=$(exec_cmd eos-cli1 \"${XRDCP_BIN} -f --tpc only \\\"${EOS_MGM_URL}/${eos_fn}?eos.pio.action=reconstruct&eos.pio.recfs=${recover_fsid}\\\" ${EOS_MGM_URL}/${eos_fn}_copy 2>&1 || true\")\n  local error_msg=\"Unable to store file - file has been cleaned or recovery aborted because of an error on close\"\n  local rc1=0\n\n  if [[ ! ${output} =~ ${error_msg} ]]; then\n    rc1=1\n    echo \"error: TPC transfer did not fail with the expected error\"\n    echo \"output=${output}\"\n  fi\n\n  # Remove the simulated close error\n  exec_cmd eos-mgm1 \"eos -r 0 0 node config ${close_err_node} error.simulation=none > /dev/null\"\n  # Check that the checksum of a download still matches the original one\n  local new_xs=\"$(exec_cmd eos-cli1 \"${XRDCP_BIN} -f --nopbar ${EOS_MGM_URL}/${eos_fn} /var/tmp/temp; xs=\\$(eos-adler32 /var/tmp/temp | awk -F '[ =]' '{print \\$NF;}'); rm -rf /var/tmp/temp; echo \\${xs}\")\"\n  local rc2=0\n\n  if [[ \"${new_xs}\" != \"${orig_xs}\" ]]; then\n    rc2=1\n    echo \"error: downloaded copy checksum does not match the original\"\n  fi\n  # Check that we still have the same locations for the stripes as nothing\n  # should be touched since there was an error during the transfer\n  local new_loc=$(exec_cmd eos-cli1 \"eos -j fileinfo ${eos_fn} | jq '.locations | .[] | .fsid' | tr '\\n' ' '\")\n  local rc3=0\n\n  if [[ \"${orig_loc}\" != \"${new_loc}\" ]]; then\n    rc3=1\n    echo \"error: the file has new locations after the failed transfer\"\n  fi\n\n  # Do cleanup\n  exec_cmd eos-cli1 \"eos -r 0 0 rm -rF ${eos_fn}\"\n  return ${rc1}${rc2}${rc3}\n}\n\n\n# Make sure we always clean up on exit\ntrap cleanup EXIT\n\nTEST_FILE=\"/tmp/test_file.dat\"\nXS_TEST_FILE=\"\"\n\nprint_config\nprepare_mgm\ntest_write_read_successful ${TEST_FILE}\nwrite_read_rc=$?\ntest_read_xs_error ${TEST_FILE}\nread_xs_error_rc=$?\ntest_write_min_filesize ${TEST_FILE}\nmin_filesize_rc=$?\ntest_write_max_filesize ${TEST_FILE}\nmax_filesize_rc=$?\ntest_write_targetsize ${TEST_FILE}\nwrite_targetsize_rc=$?\ntest_write_xs_preset ${TEST_FILE}\nwrite_xs_preset_rc=$?\ntest_write_replica_fail_close_2nd ${TEST_FILE}\nwrite_replica_fail_close_2nd_rc=$?\ntest_rain_recovery_tpc_timeout ${TEST_FILE}\nrain_recovery_timeout_rc=$?\ntest_rain_recovery_close_timeout ${TEST_FILE}\nrain_recovery_close_timeout_rc=$?\n\nrc=$(( $read_xs_error_rc || $write_read_rc || $min_filesize_rc ||\n       $max_filesize_rc || $write_targetsize_rc || $write_xs_preset_rc ||\n       $write_replica_fail_close_2nd_rc || $rain_recovery_timeout_rc ||\n       $rain_recovery_close_timeout_rc))\n\nif [[ ${rc} -ne 0 ]];then\n  echo \"error: failed script with rc=${rc}\"\nfi\n\nexit ${rc}\n"
  },
  {
    "path": "test/eos-groupdrain-test",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: eos-groupdrain-test\n# Author: Abhishek Lekshmanan - CERN\n# ----------------------------------------------------------------------\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2022 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n#\n#------------------------------------------------------------------------------\n# Description: Script testing the draining mechanism of EOS. It assumes that\n#              there are at least 14 FSTs available in the instance.\n#\n# Usage:\n# eos-groupdrain-test <eos_mgm_hostname>\n#------------------------------------------------------------------------------\nset -x\n\nif [[ $# -eq 0 || $# -gt 2 ]]; then\n      echo \"Usage: $0 <eos_mgm_hostname>\"\n      exit 1\nfi\n\nEOS_MGM_HOSTNAME=$1\neos fs ls\n# Check preconditions and make sure draining is enabled\nFST_ONLINE=$(eos fs ls | grep \"online\" | wc -l)\n\nif [[ ${FST_ONLINE} -lt 14 ]]; then\n      echo \"error: not enough FSTs configured\"\n      exit 1\nfi\n\n# Create dummy test files\nTEST_FN0=/tmp/zero.dat\nTEST_FN1=/tmp/32kb.dat\nTEST_FN2=/tmp/5mb.dat\n\ntouch ${TEST_FN0}\ndd if=/dev/urandom of=${TEST_FN1} bs=1k count=32 &> /dev/null\ndd if=/dev/urandom of=${TEST_FN2} bs=1M count=5  &> /dev/null\n\neos convert config set status=on\neos space config default space.groupdrainer=on\neos space config default space.groupdrainer.threshold=0\neos space config default space.groupdrainer.group_refresh_interval=30\neos space config default space.groupdrainer.retry_interval=10\neos space config default space.groupdrainer.retry_count=3\n# Create eos directory for tests and copy some files in\neos mkdir -p /eos/dockertest/drain_test/replica/\neos mkdir -p /eos/dockertest/drain_test/raiddp/\neos mkdir -p /eos/dockertest/drain_test/rain/\neos chmod 2777 /eos/dockertest/\neos chmod 2777 /eos/dockertest/drain_test/replica/\neos chmod 2777 /eos/dockertest/drain_test/raiddp/\neos chmod 2777 /eos/dockertest/drain_test/rain/\neos attr set default=replica /eos/dockertest/drain_test/replica/\neos attr set default=raiddp /eos/dockertest/drain_test/raiddp/\neos attr set default=raid6 /eos/dockertest/drain_test/rain/\nFST_ONLINE=$(eos fs ls | grep \"online\" | wc -l)\n\nfor ((i=1;i<=FST_ONLINE;i++)); do\n    eos fs config $i headroom=100M\ndone\n\neos group ls | grep \"default.1\"\nif [[ $? -ne 0 ]]; then\n      echo \"error: group default.1 is not online  ...\"\n      eos fs ls\n      exit 1\nfi\n\n# disable default.0 for writes right now\necho \"disabling group default.0 for writes\"\neos fs ls default.0 | grep online | awk '{print $6}' | sort | uniq | xargs -I {} eos geosched disabled add {} plct default.0\neos geosched disabled show \\* \\* \\*\neos geosched disabled show \\* \\* \\* | grep -v \"default.1\"\n\nif [[ $? -ne 0 ]]; then\n      echo \"error: group default.1 is already disabled ...\"\n      eos fs ls\n      exit 1\nfi\n\n\nfor i in {1..4}; do\n      xrdcp -f --nopbar ${TEST_FN0} root://${EOS_MGM_HOSTNAME}//eos/dockertest/drain_test/replica/0kb_file${i}.dat\n      xrdcp -f --nopbar ${TEST_FN0} root://${EOS_MGM_HOSTNAME}//eos/dockertest/drain_test/raiddp/0kb_file${i}.dat\n      xrdcp -f --nopbar ${TEST_FN0} root://${EOS_MGM_HOSTNAME}//eos/dockertest/drain_test/rain/0kb_file${i}.dat\n      xrdcp -f --nopbar ${TEST_FN1} root://${EOS_MGM_HOSTNAME}//eos/dockertest/drain_test/replica/32kb_file${i}.dat\n      xrdcp -f --nopbar ${TEST_FN1} root://${EOS_MGM_HOSTNAME}//eos/dockertest/drain_test/raiddp/32kb_file${i}.dat\n      xrdcp -f --nopbar ${TEST_FN1} root://${EOS_MGM_HOSTNAME}//eos/dockertest/drain_test/rain/32kb_file${i}.dat\n      xrdcp -f --nopbar ${TEST_FN2} root://${EOS_MGM_HOSTNAME}//eos/dockertest/drain_test/replica/5mb_file${i}.dat\n      xrdcp -f --nopbar ${TEST_FN2} root://${EOS_MGM_HOSTNAME}//eos/dockertest/drain_test/raiddp/5mb_file${i}.dat\n      xrdcp -f --nopbar ${TEST_FN2} root://${EOS_MGM_HOSTNAME}//eos/dockertest/drain_test/rain/5mb_file${i}.dat\ndone\n\n# Dump file info of the 3 layouts\necho \"File info:\"\neos file info /eos/dockertest/drain_test/replica/5mb_file1.dat\neos file info /eos/dockertest/drain_test/raiddp/5mb_file1.dat\neos file info /eos/dockertest/drain_test/rain/5mb_file1.dat\n\n\necho \"re-enabling group default.0 for writes\"\neos fs ls default.0 | grep online | awk '{print $6}' | sort | uniq | xargs -I {} eos geosched disabled rm {} plct default.0\neos geosched disabled show \\* \\* \\*\n\neos group set default.1 drain\neos group ls\neos geosched disabled show \\* \\* \\* | grep \"default.1\"\nif [[ $? -ne 0 ]]; then\n      echo \"error: group default.1 is not disabled ...\"\n      exit 1\nfi\n\n\n\ncount=$(eos fs ls | grep -c \"drained\\|failed\")\n\nwhile [[ \"$count\" -lt \"7\" ]]; do\n      sleep 10\n      count=$(eos fs ls | grep -c \"drained\\|failed\")\n      eos space groupdrainer status default --detail\ndone\n\neos fs ls\ndrained_fsids=$(eos -j fs ls default.1 | jq .result[].id)\n\nfor fsid in $drained_fsids; do\n    eos fs ls | grep \" $fsid \" | grep \"empty\" | grep \"drained\"\n    if [[ $? -ne 0 ]]; then\n        echo \"error: file system $fsid is not drained\"\n        exit 1\n    else\n        echo \"File system $fsid successfully drained\"\n    fi\ndone\n\necho \"Drain Complete, FS Status:\"\neos fs ls\necho \"Drain Status: \"\neos space groupdrainer status default --detail\necho \"Group Status\"\neos group ls\necho \"Converter Status\"\neos convert status\n# Dump file info of the 3 layouts\necho \"File info:\"\neos file info /eos/dockertest/drain_test/replica/5mb_file1.dat\neos file info /eos/dockertest/drain_test/raiddp/5mb_file1.dat\neos file info /eos/dockertest/drain_test/rain/5mb_file1.dat\n\neos group ls | grep -i drained\n\nif [[ $? -ne 0 ]]; then\n    echo \"Error: group drain not complete\"\n    exit 1\nfi\n\necho \"Setting Group Back to RW\"\neos group set default.1 on\n\nif [[ $? -ne 0 ]]; then\n    echo \"error: failed to put group in rw mode\"\n    exit 1\nfi\n# Remove all the files and directories\neos rm -rF \"/eos/dockertest/drain_test/replica/*\"\neos rm -rF \"/eos/dockertest/drain_test/raiddp/*\"\neos rm -rF \"/eos/dockertest/drain_test/rain/*\"\neos rmdir \"/eos/dockertest/drain_test/replica/\"\neos rmdir \"/eos/dockertest/drain_test/raiddp/\"\neos rmdir \"/eos/dockertest/drain_test/rain/\"\neos rmdir \"/eos/dockertest/drain_test/\"\nexit 0\n"
  },
  {
    "path": "test/eos-grpc-test",
    "content": "#!/bin/bash\nset -x #echo on\n\nprefix=$1\nprefix=${prefix%/}\nhost=${2-\"localhost\"}\nurl=root://$host\n\ntest -z \"$1\" && exit -1\n\nauthkey=12345678\n\neos vid add gateway \"127.0.0.1\" grpc\neos vid add gateway \"[:1]\" grpc\neos vid add gateway \"[::1]\" grpc\neos vid set map -grpc key:$authkey vuid:11\neos vid set map -grpc key:$authkey vgid:11\neos vid set membership 11 +sudo\neos vid set membership 11 -uids 3\neos vid set membership 11 -gids 4\neos rm -r $prefix/t_grpc/\neos mkdir -p $prefix/t_grpc/\neos chown 11:11 $prefix/t_grpc/\neos chmod 700 $prefix/t_grpc/\n\n# fail by permission\neos-grpc-ns --uid 100 --token $authkey -p $prefix/t_grpc/grpc_dir mkdir && exit -1\neos-grpc-ns --uid 100 --token $authkey -p $prefix/t_grpc/grpc_dir rmdir && exit -1\neos-grpc-ns --uid 100 --token $authkey -p $prefix/t_grpc/grpc_file touch && exit -1\neos-grpc-ns --uid 100 --token $authkey -p $prefix/t_grpc/grpc_file unlink && exit -1\n\n# succeed\neos-grpc-ns --token $authkey -p $prefix/t_grpc/grpc_dir mkdir || exit -2\neos-grpc-ns --token $authkey -p $prefix/t_grpc/grpc_dir rmdir || exit -2\neos-grpc-ns --token $authkey -p $prefix/t_grpc/grpc_file touch || exit -2\neos-grpc-ns --token $authkey -p $prefix/t_grpc/grpc_file unlink || exit -2\n\neos-grpc-ns --token $authkey -p $prefix/t_grpc/level1/ mkdir || exit -3\neos-grpc-ns --token $authkey -p $prefix/t_grpc/level1/level2/ mkdir || exit -3\neos-grpc-ns --token $authkey -p $prefix/t_grpc/level1/level2/level3 touch || exit -3\n# fail by name\neos-grpc-ns --token $authkey -p $prefix/t_grpc/level -r rm && exit -3\n# fail by not empty\neos-grpc-ns --token $authkey -p $prefix/t_grpc/level1  rm && exit -3\neos-grpc-ns --token $authkey -p $prefix/t_grpc/level1 -r rm || exit -3\n\n# rename\neos-grpc-ns --token $authkey -p $prefix/t_grpc/grpc_file touch || exit -4\neos-grpc-ns --token $authkey --target $prefix/t_grpc/grpc_renamed -p $prefix/t_grpc/grpc_file rename || exit -4\neos-grpc-ns --token $authkey -p $prefix/t_grpc/grpc_file unlink && exit -4\neos-grpc-ns --token $authkey -p $prefix/t_grpc/grpc_renamed unlink || exit -4\n\n# symlink\neos-grpc-ns --token $authkey -p $prefix/t_grpc/grpc_dir mkdir || exit -5\neos-grpc-ns --token $authkey --target $prefix/t_grpc/grpc_symlink_dir -p $prefix/t_grpc/grpc_dir symlink && exit -6\neos-grpc-ns --token $authkey --target $prefix/t_grpc/grpc_dir -p $prefix/t_grpc/grpc_symlink_dir symlink || exit -6\neos-grpc-ns --token $authkey -p $prefix/t_grpc/grpc_symlink_dir unlink || exit -6\neos-grpc-ns --token $authkey -p $prefix/t_grpc/grpc_dir rmdir || exit -6\n\n# setxattr\neos-grpc-ns --token $authkey -p $prefix/t_grpc/grpc_file touch || exit -7\neos-grpc-ns --token $authkey --xattr user.foo=bar -p $prefix/t_grpc/grpc_file setxattr || exit -7\neos attr ls $prefix/t_grpc/grpc_file | grep user.foo | grep bar || exit -7\n# delete xattr\neos-grpc-ns --token $authkey --xattr \\!user.foo= -p $prefix/t_grpc/grpc_file setxattr || exit -7\neos attr ls $prefix/t_grpc/grpc_file | grep user.foo && exit -7\neos-grpc-ns --token $authkey -p $prefix/t_grpc/grpc_file unlink || exit -7\n\n#chown \neos-grpc-ns --token $authkey -p $prefix/t_grpc/chown mkdir || exit -8\neos-grpc-ns --uid 0 --gid 0 --token $authkey --owner-uid 1 --owner-gid 2 -p $prefix/t_grpc/chown chown || exit -8\neos ls -la $prefix/t_grpc/chown\neos-grpc-ns --token $authkey -p $prefix/t_grpc/chown rmdir || exit -8\n\n#chmod\neos-grpc-ns --token $authkey -p $prefix/t_grpc/chmod mkdir || exit -9\neos-grpc-ns --mode 644 --token $authkey -p $prefix/t_grpc/chmod chmod || exit -9\neos ls -la $prefix/t_grpc/chmod\neos-grpc-ns --token $authkey -p $prefix/t_grpc/chmod rmdir || exit -9\n\n#versioning\neos chmod 777 $prefix/t_grpc/ || exit -10\neos attr set sys.versioning=3 $prefix/t_grpc/\neos cp /etc/passwd $prefix/t_grpc/file || exit -10\nsleep 1.2\neos cp /etc/passwd $prefix/t_grpc/file || exit -10\neos cp /etc/passwd $prefix/t_grpc/file || exit -10\neos-grpc-ns --token $authkey -p $prefix/t_grpc/file create-version || exit -10\neos-grpc-ns --token $authkey -p $prefix/t_grpc/file create-version || exit -10\neos find $prefix/t_grpc/|| exit -10\neos-grpc-ns --token $authkey -p $prefix/t_grpc/file list-version || exit -10\neos-grpc-ns --token $authkey -p $prefix/t_grpc/file list-version | grep ino | wc -l | grep -w 5 || exit -10\neos-grpc-ns --token $authkey --max-version 99 -p $prefix/t_grpc/file create-version || exit -10\neos find $prefix/t_grpc/ || exit -10\neos-grpc-ns --token $authkey -p $prefix/t_grpc/file list-version | grep ino | wc -l | grep -w 6 || exit -10\neos-grpc-ns --token $authkey -p $prefix/t_grpc/file purge-version || exit -10\neos-grpc-ns --token $authkey -p $prefix/t_grpc/file list-version | grep ino | wc -l | grep -w 4 || exit -10\neos find $prefix/t_grpc/ || exit -10\neos-grpc-ns --token $authkey --max-version 1 -p $prefix/t_grpc/file purge-version || exit -10\neos find $prefix/t_grpc/ || exit -10\neos-grpc-ns --token $authkey -p $prefix/t_grpc/file list-version | grep ino | wc -l | grep -w 2 || exit -10\neos-grpc-ns --token $authkey --max-version 0 -p $prefix/t_grpc/file purge-version || exit -10\neos find $prefix/t_grpc/ || exit -10\neos-grpc-ns --token $authkey -p $prefix/t_grpc/file list-version | grep ino | wc -l | grep -w 1 || exit -10\neos-grpc-ns --token $authkey -p $prefix/t_grpc/file unlink || exit -10\n\n#acls\neos attr set sys.eval.useracl=1 $prefix/t_grpc/\neos-grpc-ns --token $authkey -p $prefix/t_grpc --sysacl --acl u:adm=rwx acl || exit -11\neos-grpc-ns --token $authkey -p $prefix/t_grpc --acl u:nobody=rwx acl || exit -11\neos-grpc-ns --token $authkey -p $prefix/t_grpc --sysacl acl | grep \"u:adm:rwx\" || exit -11\neos-grpc-ns --token $authkey -p $prefix/t_grpc acl |  grep \"u:nobody:rwx\" || exit -11\neos-grpc-ns --token $authkey -p $prefix/t_grpc --acl u:adm=rwx --front acl || exit -11\neos-grpc-ns --token $authkey -p $prefix/t_grpc acl |  grep \"\\\"rule\\\": \\\"u:adm:rwx,\" || exit -11\neos-grpc-ns --token $authkey -p $prefix/t_grpc --acl u:nobody=r --position 2 acl || exit -11\neos-grpc-ns --token $authkey -p $prefix/t_grpc acl |  grep \"\\\"rule\\\": \\\"u:adm:rwx,u:nobody:r\" || exit -11\neos-grpc-ns --token $authkey -p $prefix/t_grpc --acl u:1003=r --position 5 acl | grep \"position cannot be met\"  || exit -11\n\n\neos rmdir $prefix/t_grpc/\n\n#quota\n#eos-grpc-ns --token false quota | grep \"you are not a quota administrator\"  || exit -12\n#eos-grpc-ns --token $authkey quota | grep \"you are not a quota administrator\"  && exit -12\n\n#user quota\neos-grpc-ns --token $authkey -p $prefix --inodes 123 --volume 6000000000 --username nobody quota set | grep error && exit -13\neos-grpc-ns --token $authkey -p $prefix --username nobody --quota inode quota rm | grep error && exit -13\neos-grpc-ns --token $authkey -p $prefix --username nobody --quota volume quota rm | grep error && exit -13\n\n#project quota\neos-grpc-ns --token $authkey -p $prefix --inodes 321 --volume 6000000000 --groupname nobody quota set | grep error && exit -14\neos-grpc-ns --token $authkey -p $prefix --groupname nobody --quota inode quota rm | grep error && exit -14\neos-grpc-ns --token $authkey -p $prefix --groupname nobody --quota volume quota rm | grep error && exit -14\n\n#quota node deletion\neos vid set map -grpc key:$authkey vuid:0\neos vid set map -grpc key:$authkey vgid:0\neos-grpc-ns --uid 0 --gid 0 --token $authkey -p $prefix quota rmnode | grep error && exit -15\neos vid set map -grpc key:$authkey vuid:11\neos vid set map -grpc key:$authkey vgid:11\n#recycle\neos mkdir -p $prefix/t_grpc/\neos chown 11:11 $prefix/t_grpc/\neos chmod 700 $prefix/t_grpc/\neos-grpc-ns --token $authkey --uid 11 -r -p $prefix/t_grpc/recycle mkdir || exit -2\neos-grpc-ns --token $authkey --uid 11 --mode 700 -p $prefix/t_grpc/recycle chmod || exit -1\neos-grpc-ns --token $authkey --uid 99 recycle | grep recycles || exit -13\neos-grpc-ns --token $authkey --uid 99 recycle ls | grep recycles ||  exit -13\neos-grpc-ns --token $authkey --uid 99 recycle restore -p invalid | grep \"\\\"code\\\": \\\"22\\\"\" || exit -13\neos-grpc-ns --token $authkey --uid 99 recycle restore 2>&1 | grep \"failed to parse recycle command restore\" || exit -13\neos recycle config --add-bin $prefix/t_grpc/recycle\nxrdcp -f /etc/passwd \"$url/$prefix/t_grpc/recycle/file1.dat?eos.ruid=11&eos.rgid=11\" || exit -2\neos rm $prefix/t_grpc/recycle/file1.dat\neos-grpc-ns --token $authkey --uid 99 recycle ls --uid | grep \"$prefix/t_grpc/recycle/file1.dat\" && exit -15\neos-grpc-ns --token $authkey --uid 11 recycle ls --uid | grep \"$prefix/t_grpc/recycle/file1.dat\" || exit -16\neos-grpc-ns --token $authkey --uid 11 recycle ls --all | grep \"$prefix/t_grpc/recycle/file1.dat\" || exit -17\nrecycle_key=$(eos -r 11 11 recycle ls --uid -m | grep file1.dat | awk -F '[ =]' '{print $20;}')\neos-grpc-ns --token $authkey --uid 11 recycle restore $recycle_key | grep \"\\\"code\\\": \\\"0\\\"\" || exit -19\neos rm $prefix/t_grpc/recycle/file1.dat\neos-grpc-ns --token $authkey --uid 99 recycle purge -k $recycle_key | grep \"\\\"code\\\": \\\"1\\\"\" || exit -20\n# This won't work as long as we don't allow sudoer role via GRPC in GrpcNsInterface::Exec\n#eos-grpc-ns --token $authkey --uid 11 recycle purge -k $recycle_key | grep \"\\\"code\\\": \\\"0\\\"\" || exit -21\n\neos vid rm vid:grpc:\\\"key:12345678\\\":gid\neos vid rm vid:grpc:\\\"key:12345678\\\":uid\neos vid set membership 11 -sudo\neos vid rm membership 11\n\n\n"
  },
  {
    "path": "test/eos-http-upload-test",
    "content": "#!/bin/bash\n\n# ----------------------------------------------------------------------\n# File: eos-http-upload--test\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2018 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n# usage: eos-http-upload-test rangeupload testfile /eos/dev/upload-directory/\n\nport=\"${EOS_HTTPS_PORT:-443}\" \nhost=$(hostname -f)\ncacert=\"/etc/grid-security/certificates/rootCA.pem\"\ncapath=\"$(dirname \"${cacert}\")\"\ncert=\"/root/.globus/usercert.pem\"\nkey=\"/root/.globus/userkey.pem\"\n\n\n\nusage() { echo '''Usage: # File: eos-http-upload--test <method>[-h|--host <host>[:<port>| (-p|--port) <port>]]\n                           <method>  : only rangeupload for the time being\n                           <name>     : name of the file\n                           <dir>      : name of the destination directory\n                           [-c|--cert <x509 certificate>]\n                           [-k|--key <x509 key>]\n                           [-C|--cacert <CA certificate>]\n                           [-P|--capath <CA certificate path - derived from the certificate as well>]\n                           [-h|--help] - usage & exit\n'''; }\n\n# Parser from: https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash\nusage_error () { echo >&2 \"$(basename \"$0\"):  $1\"; exit 2; }\nassert_argument () { test \"$1\" != \"$EOL\" || usage_error \"${POSITIONAL_ARGS[1]} requires an argument\"; }\n\n\n# One loop, nothing more.\nPOSITIONAL_ARGS=()\nif [ \"$#\" != 0 ]; then\n  EOL=$(printf '\\1\\3\\3\\7')\n  set -- \"$@\" \"$EOL\"\n  while [ \"$1\" != \"$EOL\" ]; do\n    opt=\"$1\"; shift\n    case \"$opt\" in\n\n      -h|--host)    assert_argument \"$1\" \"$opt\"; host=\"${1%:*}\" && port=\"${1#*:}\"; shift;;\n      -p|--port)    assert_argument \"$1\" \"$opt\"; port=\"$1\"; shift;;\n      -c|--cert)    assert_argument \"$1\" \"$opt\"; cert=\"$1\"; shift;;\n      -k|--key)     assert_argument \"$1\" \"$opt\"; key=\"$1\"; shift;;\n      -C|--cacert)  assert_argument \"$1\" \"$opt\"; cacert=\"${1}\" && capath=\"$(dirname \"${cacert}\")\"; shift;;\n      -P|--capath)  assert_argument \"$1\" \"$opt\"; capath=\"${1}\"; shift;;\n       \n      -h|--help) usage; exit 0;;\n\n      # Arguments processing. You may remove any unneeded line after the 1st.\n      -|''|[!-]*) set -- \"$@\" \"$opt\";;                                          # positional argument, rotate to the end\n      --*=*)      set -- \"${opt%%=*}\" \"${opt#*=}\" \"$@\";;                        # convert '--name=arg' to '--name' 'arg'\n      -[!-]?*)    set -- \"$(echo \"${opt#-}\" | sed 's    /g')\" \"$@\";;     # convert '-abc' to '-a' '-b' '-c'\n      --)         while [ \"$1\" != \"$EOL\" ]; do set -- \"$@\" \"$1\"; shift; done;;  # process remaining arguments as positional\n      -*)         usage_error \"unknown option: '$opt'\";;                        # catch misspelled options\n      *)          usage_error \"this should NEVER happen ($opt)\";;               # sanity test for previous patterns\n\n    esac\n  done\n  shift  # $EOL\nfi\n\nrangeupload () {\n  echo \"# Testing HTTP range upload\"\n  NAME=$1\n  DIR=/tmp/X-UPLOAD/\n  mkdir -p $DIR\n  rm -f $DIR/$NAME*\n  CHUNK_NUMBER=4\n  dd if=/dev/zero of=$DIR/$NAME bs=1M count=32\n  split -b 10485760 -a 1 -d $DIR/$NAME $DIR/$NAME-chunking-`uuidgen | sed s/-//g`-$CHUNK_NUMBER-\n  echo \"# about to upload $DIR/$NAME\"\n  DEST_URL=https://${host}:${port}$2\n  echo \"# to $DEST_URL\"\n  let LAST_CHUNK_NUMBER=$CHUNK_NUMBER-1\n  let i=0\n  UUID=`echo $RANDOM`\n  ok=0\n  checksum=`eos-adler32 $DIR/$NAME | awk '{print $4}' | sed s/=/:/g`;\n\n  for f in `ls $DIR/$NAME-chunking*`; do\n    echo $f\n    EOS_FN=$NAME\n    let start=$i*10485760\n    let stop=$i+1;\n    let stop=$stop*10485760;\n    if [ $stop -ge 33554432 ]; then\n      let stop=33554431;\n    else \n      let stop=$stop-1;\n    fi \n    echo $checksum start=$start stop=$stop\n\n    curl --capath \"${capath}\" --cert \"${cert}\" --cacert \"${cacert}\" --key \"${key}\" --header \"x-upload-checksum: $checksum\" --header \"x-upload-totalsize: 33554432\" --header \"x-upload-mtime: 1533100000\" --header \"x-upload-range: bytes=$start-$stop\" -L -X PUT -T $f $DEST_URL$EOS_FN  >> $DIR/$NAME.log 2>&1\n\n  \n    let i=$i+1\n\n  done\n\n  sleep 1  \n  if [ $ok -eq 0 ]; then\n    curl --capath \"${capath}\" --cert \"${cert}\" --cacert \"${cacert}\" --key \"${key}\"  -i $DEST_URL$EOS_FN >> $DIR/$NAME.log 2>&1\n    ok=$?\n    echo HEAD request on $DIR/$NAME gave error=$ok\n  fi\n\n  if [ $ok -eq 0 ]; then \n    cks=`echo $checksum|sed s/adler32://g`;\n    cat $DIR/$NAME.log | grep ETag | grep $cks >& /dev/null\n    ok=$?\n    echo CHECKSUM verification on $DIR/$NAME gave error=$ok\n  fi\n\n  return $ok;\n}\necho \"${POSITIONAL_ARGS[@]}\" \"$@\" \"$#\"\nif [ \"$#\" -eq 3 ] && [ \"$1\" = \"rangeupload\" ]; then\n  rangeupload $2 $3;\n  exit $?\nelse\n  echo \"usage:  eos-http-upload-test rangeupload testfile /eos/dev/upload-directory/\"\nfi\n\nexit -1\n"
  },
  {
    "path": "test/eos-https-functional-test",
    "content": "#!/bin/bash\n\n# ----------------------------------------------------------------------\n# File: eos-https-functional-test\n# Author: Manuel Reis - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2018 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# This is a simple script that aims to check HTTP transfers with different authentication methods #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n\n\nusage() { echo '''Usage: eos-https-functional-test [-h|--host <host>[:<port>| (-p|--port) <port>]]\n                           [-c|--cert <x509 certificate>]\n                           [-k|--key <x509 key>]\n                           [-C|--cacert <CA certificate>]\n                           [-P|--capath <CA certificate path - derived from the certificate as well>]\n                           [-t|--dir <directory on which to run tests>]\n                           [-v|--verbose]\n\n                           [--help] - usage & exit\n'''; }\n\n# Parser from: https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash\nusage_error () { echo >&2 \"$(basename \"$0\"):  $1\"; exit 2; }\nassert_argument () { test \"$1\" != \"$EOL\" || usage_error \"$2 requires an argument\"; }\n\nport=\"${EOS_HTTPS_PORT:-443}\"\nhost=$(hostname -f)\ncacert=\"/etc/grid-security/certificates/rootCA.pem\"\ncapath=\"$(dirname \"${cacert}\")\"\ncert=\"/root/.globus/usercert.pem\"\nkey=\"/root/.globus/userkey.pem\"\n\n# One loop, nothing more.\nif [ \"$#\" != 0 ]; then\n  EOL=$(printf '\\1\\3\\3\\7')\n  set -- \"$@\" \"$EOL\"\n  while [ \"$1\" != \"$EOL\" ]; do\n    opt=\"$1\"; shift\n    case \"$opt\" in\n\n      # Your options go here.\n      --macaroons)  macaroons=1;;\n      --tpc)        tpc=1;;\n      -v|--verbose) verbose=\"-v\";;\n      -h|--host)    assert_argument \"$1\" \"$opt\"; host=\"${1%:*}\" && port=\"${1#*:}\"; shift;;\n      -p|--port)    assert_argument \"$1\" \"$opt\"; port=\"$1\"; shift;;\n      -c|--cert)    assert_argument \"$1\" \"$opt\"; cert=\"$1\"; shift;;\n      -k|--key)     assert_argument \"$1\" \"$opt\"; key=\"$1\"; shift;;\n      -C|--cacert)  assert_argument \"$1\" \"$opt\"; cacert=\"${1}\" && capath=\"$(dirname \"${cacert}\")\"; shift;;\n      -P|--capath)  assert_argument \"$1\" \"$opt\"; capath=\"${1}\"; shift;;\n      -t|--dir)     assert_argument \"$1\" \"$opt\"; testdir=\"${1}\"; shift;;\n\n      --help) usage $0; exit 0;;\n\n      # Arguments processing. You may remove any unneeded line after the 1st.\n      -|''|[!-]*) set -- \"$@\" \"$opt\";;                                          # positional argument, rotate to the end\n      --*=*)      set -- \"${opt%%=*}\" \"${opt#*=}\" \"$@\";;                        # convert '--name=arg' to '--name' 'arg'\n      -[!-]?*)    set -- \"$(echo \"${opt#-}\" | sed 's    /g')\" \"$@\";;            # convert '-abc' to '-a' '-b' '-c'\n      --)         while [ \"$1\" != \"$EOL\" ]; do set -- \"$@\" \"$1\"; shift; done;;  # process remaining arguments as positional\n      -*)         usage_error \"unknown option: '$opt'\";;                        # catch misspelled options\n      *)          usage_error \"this should NEVER happen ($opt)\";;               # sanity test for previous patterns\n\n    esac\n  done\n  shift  # $EOL\nfi\n\necho \"Results:\"\n### X509\n# Returns 0 if successful, 1 if upload failed, 2 if download failed, 3 if both failed\nx509() {\n  respUpload=\"$(curl -sS ${verbose} -I -L --capath \"${capath}\" --cert \"${cert}\" --cacert \"${cacert}\" --key \"${key}\" \"https://${host}:${port}/${testdir}/helloworld.txt\" --upload-file <(echo ola) | grep HTTP | tr -d '\\r' | paste -sd'+')\"\n  echo -e \"\\tx509 upload -> ${respUpload}\"\n  respDownload=\"$(curl -sS ${verbose} -I -L --capath \"${capath}\" --cert \"${cert}\" --cacert \"${cacert}\" --key \"${key}\" \"https://${host}:${port}/${testdir}/helloworld.txt\" |grep HTTP | tr -d '\\r' | paste -sd'+')\"\n  echo -e \"\\tx509 download -> ${respDownload}\" # should be 0\n\n  [[ $respUpload == \"HTTP/1.1 307 TEMPORARY_REDIRECT+HTTP/1.1 201 CREATED\" ]]\n  upcode=$?\n        [[ $respDownload == \"HTTP/1.1 200 OK\" ]]\n  downcode=$?\n\n  return  $((2#$downcode$upcode))\n}\n\n\n### Macaroon\n# Returns 0 if successful, 1 if upload failed, 2 if download failed, 3 if both failed\nmacaroon() {\n    # certificate and private key\n    export X509_USER_CERT=${cert}\n    export X509_USER_KEY=${key}\n\n    # Use the eos-macaroon-init tool to request a macaroon\n    eos-macaroon-init -h >/dev/null 2>&1 || { echo -e >&2 \"I require eos-macaroon-init but it's not installed.  Aborting.\\n\"\n    \"yum install https://repo.opensciencegrid.org/osg/3.4/osg-3.4-el$(uname -r | awk -F '.' '$0=$(NF-1)')-release-latest.rpm\\n\"\n    \"yum install --enablerepo=osg-development -y x509-scitokens-issuer-client\";\n    exit 256;\n    }\n\n    export MACAROON=\"$(eos-macaroon-init --activity UPLOAD,DOWNLOAD --validity 60 https://${host}:${port}/${testdir}/)\"\n    respMacaroonUpload=\"$(curl -I -sS ${verbose} -L -H \"Authorization: Bearer ${MACAROON}\" https://${host}:${port}${testdir}/helloworld-macaroon.txt --upload-file <(echo ola) | grep HTTP | tr -d '\\r' | paste -sd'+')\"\n    echo -e \"\\tmacaroon upload -> ${respMacaroonUpload}\"\n    respMacaroonDownload=\"$(curl -I -sS ${verbose} -L -H \"Authorization: Bearer ${MACAROON}\" https://${host}:${port}${testdir}/helloworld-macaroon.txt|grep HTTP | tr -d '\\r' | paste -sd'+')\"\n    echo -e \"\\tmacaroon download -> ${respMacaroonDownload}\"\n\n\n    [[ $respMacaroonUpload == \"HTTP/1.1 307 TEMPORARY_REDIRECT+HTTP/1.1 201 CREATED\" ]]\n    macaroonupcode=$?\n    [[ $respMacaroonDownload == \"HTTP/1.1 200 OK\" ]]\n    macaroondowncode=$?\n    return $(( 2#$macaroondowncode$macaroonupcode ))\n\n}\n\n### TPC\n# Returns 0 if successful, 1 if tcp pull failed, 2 if tcp push failed, 3 if both failed\ntcp() {\n    export SRC=https://${host}:${port}/${testdir}/helloworld.txt\n    export DST_PULL=https://${host}:${port}/${testdir}/helloworld-tcp-pull.txt\n    export DST_PUSH=https://${host}:${port}/${testdir}/helloworld-tcp-push.txt\n    export TSRC=$(curl -sS -4 --capath \"${capath}\" --cert \"${cert}\" --cacert \"${cacert}\" --key \"${key}\" -X POST -H 'Content-Type: application/macaroon-request' -d '{\"caveats\": [\"activity:DOWNLOAD\"], \"validity\": \"PT3000M\"}' \"$SRC\" | jq -r '.macaroon')\n    export TDST_PULL=$(curl -sS -4 --capath \"${capath}\" --cert \"${cert}\" --cacert \"${cacert}\" --key \"${key}\" -X POST -H 'Content-Type: application/macaroon-request' -d '{\"caveats\": [\"activity:UPLOAD,DELETE,LIST,MANAGE\"], \"validity\": \"PT3000M\"}' \"$DST_PULL\" | jq -r '.macaroon')\n    export TDST_PUSH=$(curl -sS -4 --capath \"${capath}\" --cert \"${cert}\" --cacert \"${cacert}\" --key \"${key}\" -X POST -H 'Content-Type: application/macaroon-request' -d '{\"caveats\": [\"activity:UPLOAD,DELETE,LIST,MANAGE\"], \"validity\": \"PT3000M\"}' \"$DST_PUSH\" | jq -r '.macaroon')\n\n    if [[ ${verbose} == \"-v\" ]]; then\n      echo -n \"Transfer Source Token: \"\n      echo \"${TSRC}\" | base64 -d\n\n      echo -n \"Pull Transfer Destination Token: \"\n      echo \"${TDST_PULL}\" | base64 -d\n\n      echo -n \"Push Transfer Destination Token: \"\n      echo \"${TDST_PUSH}\" | base64 -d\n    fi\n\n    respTpcPull=\"$(curl -I -sS -4 ${verbose} --capath \"${capath}\" -L \\\n      -X COPY \\\n      -H 'Secure-Redirection: 1' \\\n      -H 'X-No-Delegate: 1' \\\n      -H 'Credentials: none' \\\n      -H \"Authorization: Bearer $TDST_PULL\" \\\n      -H \"TransferHeaderAuthorization: Bearer $TSRC\" \\\n      -H \"TransferHeaderTest: Test\" \\\n      -H \"Source: $SRC\" \\\n      \"$DST_PULL\" |grep HTTP | tr -d '\\r' | paste -sd'+')\"\n    echo -e \"\\ttpc pull -> ${respTpcPull}\"\n\n\n    [[ ${respTpcPull} == \"HTTP/1.1 307 Temporary Redirect+HTTP/1.1 201 Created\" ]]\n    tpcpullcode=$?\n\n    respTpcPush=\"$(curl -I -sS -4 ${verbose} --capath \"${capath}\" -L \\\n        -X COPY \\\n        -H 'Secure-Redirection: 1' \\\n        -H 'X-No-Delegate: 1' \\\n        -H 'Credentials: none' \\\n        -H \"Authorization: Bearer $TSRC\" \\\n        -H \"TransferHeaderAuthorization: Bearer $TDST_PUSH\" \\\n        -H \"TransferHeaderTest: Test\" \\\n        -H \"Destination: $DST_PUSH\" \\\n        \"$SRC\" |grep HTTP | tr -d '\\r' | paste -sd'+')\"\n    echo -e \"\\ttpc push -> ${respTpcPush}\"\n\n\n    [[ ${respTpcPush} == \"HTTP/1.1 307 Temporary Redirect+HTTP/1.1 201 Created\" ]]\n    tpcpushcode=$?\n\n    return $((2#$tpcpushcode$tpcpullcode))\n}\n\n# EOS token\neostoken() {\n    # certificate and private key\n    export X509_USER_CERT=${cert}\n    export X509_USER_KEY=${key}\n    echo \"info: enable token generation\"\n    eos space config default space.token.generation=1\n    EXPIRE=`date +%s`; LATER=$(($EXPIRE+300));\n    echo \"info: create client proxy certificate\"\n    voms-proxy-init\n    # Make destination directory only accessible to eos-user\n    eos chmod 700 ${testdir}\n\n    export EOS_RW_TOKEN=`XRD_LOGLEVEL=Dump XrdSecPROTOCOL=gsi eos root://${host}/ token --path ${testdir}/ --tree --permissions rwx --expires $LATER`\n\n    if [[ $EOS_RW_TOKEN  != \"zteos64:\"* ]]; then\n        echo -e '\\terror: eos rw token command failed'\n        return 1\n    fi\n\n    export EOS_RO_TOKEN=`XrdSecPROTOCOL=gsi eos root://${host}/ token --path ${testdir}/ --tree --permissions rx --expires $LATER`\n\n    if [[ $EOS_RO_TOKEN  != \"zteos64:\"* ]]; then\n        echo -e '\\terror: eos ro token command failed'\n        return 1\n    fi\n\n    respEosTokenRwUpload=\"$(curl -I -sS ${verbose} -L -H \"Authorization: Bearer ${EOS_RW_TOKEN}\" https://${host}:${port}${testdir}/helloworld-eostoken.txt --upload-file <(echo ola-token) | grep HTTP | tr -d '\\r' | paste -sd'+')\"\n    echo -e \"\\teostoken rw upload -> ${respEosTokenRwUpload}\"\n    [[ $respEosTokenRwUpload == \"HTTP/1.1 307 TEMPORARY_REDIRECT+HTTP/1.1 201 CREATED\" ]]\n    eostoken_rw_up=$?\n\n    respEosTokenRwDownload=\"$(curl -I -sS ${verbose} -L -H \"Authorization: Bearer ${EOS_RW_TOKEN}\" https://${host}:${port}${testdir}/helloworld-eostoken.txt|grep HTTP | tr -d '\\r' | paste -sd'+')\"\n    echo -e \"\\teostoken rw download -> ${respEosTokenRwDownload}\"\n    [[ $respEosTokenRwDownload == \"HTTP/1.1 200 OK\" ]]\n    eostoken_rw_down=$?\n\n    respEosTokenRoDownload=\"$(curl -I -sS ${verbose} -L -H \"Authorization: Bearer ${EOS_RO_TOKEN}\" https://${host}:${port}${testdir}/helloworld-eostoken.txt|grep HTTP | tr -d '\\r' | paste -sd'+')\"\n    echo -e \"\\teostoken ro download -> ${respEosTokenRoDownload}\"\n    [[ $respEosTokenRoDownload == \"HTTP/1.1 200 OK\" ]]\n    eostoken_ro_down=$?\n\n    # Make destination directory read-only and try to upload with RO token\n    eos chmod 500 ${testdir}\n    respEosTokenRoUpload=\"$(curl -I -sS ${verbose} -L -H \"Authorization: Bearer ${EOS_RO_TOKEN}\" https://${host}:${port}${testdir}/helloworld-eostoken-ro.txt --upload-file <(echo ola-token) | grep HTTP | tr -d '\\r' | paste -sd'+')\"\n    echo -e \"\\teostoken ro upload -> ${respEosTokenRoUpload}\"\n    [[ $respEosTokenRoUpload == \"HTTP/1.1 403 FORBIDDEN\" ]]\n    eostoken_ro_up=$?\n\n    eos chmod 750 ${testdir}\n    echo \"info: destroy client proxy certificate\"\n    voms-proxy-destroy\n    return $((2#$eostoken_rw_up$eostoken_rw_down$eostoken_ro_down$eostoken_ro_up))\n}\n\necho \"info: prepare https test directory ${testdir}\"\neos mkdir -p ${testdir}\neos chown eos-user:eos-user ${testdir}\n\nx509\nx509code=$?\necho \"x509:\"$x509code\n\nmacaroon\nmacarooncode=$?\necho \"macaroon:\"$macarooncode\n\neostoken\neostokencode=$?\necho \"eostoken:\"$eostokencode\n\ntcp\ntcpcode=$?\necho \"tpc:\"$tcpcode\n\necho \"info: cleanup https test directory\"\neos ls -lrta ${testdir}\neos rm -rF ${testdir}\n\nexit $x509code$macarooncode$eostokencode$tpccode\n"
  },
  {
    "path": "test/eos-instance-test",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: eos-instance-test\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n################################################################################\n# Usage:\n#\n# - run full test suite:   bash> eos-instance\n# - list test id's:        bash> eos-instance list\n# - limit tests:           bash> eos-instance 243 258 # run 243-258 (prepare+cleanup always runs)\n# - limit tests:           bash> eos-instance fuse,rain  # run only fuse+rain tests\n#\n################################################################################\n\n# default values can be overwritten in /etc/sysconfig/eos\n#export EOS_TEST_MAILNOTIFY=apeters@mail.cern.ch\n#export EOS_TEST_GSMNOTIFY=\"0041764875002@mail2sms.cern.ch\"\n\nexport EOS_TEST_REDIRECTOR=localhost\nexport EOS_TEST_FULL_REDIRECTOR=$(hostname -f)\nexport EOS_TEST_TESTSYS=/tmp/eos-instance-test\nexport EOS_TEST_GSMLOCKTIME=3600\nexport EOS_TEST_TESTTIMESLICE=1600\n\nexport EOS_HTTPS_PORT=\"$(grep -oP 'XrdHttp:\\K(\\d+)' /etc/xrd.cf.mgm)\"\nfirsttest=$1\nlasttest=$2\nNOBODY_UID=$(id -u nobody)\nNOBODY_GID=$(id -g nobody)\n\n################################################################################\n# COLOR FUNCTIONS\n################################################################################\n\nBOOTUP=color\nRES_COL=60\nMOVE_TO_COL=\"echo -en \\\\033[${RES_COL}G\"\nSETCOLOR_SUCCESS=\"echo -en \\\\033[1;32m\"\nSETCOLOR_FAILURE=\"echo -en \\\\033[1;31m\"\nSETCOLOR_WARNING=\"echo -en \\\\033[1;33m\"\nSETCOLOR_NORMAL=\"echo -en \\\\033[0;39m\"\n\necho_success() {\n    [ \"$BOOTUP\" = \"color\" ] && $MOVE_TO_COL\n    echo -n \"[\"\n    [ \"$BOOTUP\" = \"color\" ] && $SETCOLOR_SUCCESS\n    echo -n $\"  OK  \"\n    [ \"$BOOTUP\" = \"color\" ] && $SETCOLOR_NORMAL\n    echo -n \"]\"\n    echo -ne \"\\r\"\n    return 0\n}\n\necho_failure() {\n    [ \"$BOOTUP\" = \"color\" ] && $MOVE_TO_COL\n    echo -n \"[\"\n    [ \"$BOOTUP\" = \"color\" ] && $SETCOLOR_FAILURE\n    echo -n $\"FAILED\"\n    [ \"$BOOTUP\" = \"color\" ] && $SETCOLOR_NORMAL\n    echo -n \"]\"\n    echo -ne \"\\r\"\n    return 1\n}\n\necho_passed() {\n    [ \"$BOOTUP\" = \"color\" ] && $MOVE_TO_COL\n    echo -n \"[\"\n    [ \"$BOOTUP\" = \"color\" ] && $SETCOLOR_WARNING\n    echo -n $\"PASSED\"\n    [ \"$BOOTUP\" = \"color\" ] && $SETCOLOR_NORMAL\n    echo -n \"]\"\n    echo -ne \"\\r\"\n    return 1\n}\n\necho_warning() {\n    [ \"$BOOTUP\" = \"color\" ] && $MOVE_TO_COL\n    echo -n \"[\"\n    [ \"$BOOTUP\" = \"color\" ] && $SETCOLOR_WARNING\n    echo -n $\"WARNING\"\n    [ \"$BOOTUP\" = \"color\" ] && $SETCOLOR_NORMAL\n    echo -n \"]\"\n    echo -ne \"\\r\"\n    return 1\n}\n\n# Log that something succeeded\nsuccess() {\n    [ \"$BOOTUP\" != \"verbose\" -a -z \"${LSB:-}\" ] && echo_success\n    return 0\n}\n\n# Log that something failed\nfailure() {\n    local rc=$?\n    [ \"$BOOTUP\" != \"verbose\" -a -z \"${LSB:-}\" ] && echo_failure\n    [ -x /bin/plymouth ] && /bin/plymouth --details\n    return $rc\n}\n\n# Log that something passed, but may have had errors. Useful for fsck\npassed() {\n    local rc=$?\n    [ \"$BOOTUP\" != \"verbose\" -a -z \"${LSB:-}\" ] && echo_passed\n    return $rc\n}\n\n# Log a warning\nwarning() {\n    local rc=$?\n    [ \"$BOOTUP\" != \"verbose\" -a -z \"${LSB:-}\" ] && echo_warning\n    return $rc\n}\n\n\n################################################################################\n# Check which version of XRootD we are using\n################################################################################\nXRD_VERS=$(xrootd -v 2>&1)\nXRD_VERS_MAJOR=${XRD_VERS:1:1}\n\nif [ -e /etc/sysconfig/eos ];  then\n. /etc/sysconfig/eos\nfi\n\nif [ -z \"$EOS_TEST_INSTANCE\" ]; then\n  if [ -n \"$EOS_INSTANCE_NAME\" ]; then\n    export EOS_TEST_INSTANCE=${EOS_INSTANCE_NAME#eos}\n  else\n    export EOS_TEST_INSTANCE=\"dev\"\n  fi\nfi\n################################################################################\nmkdir -p $EOS_TEST_TESTSYS\n################################################################################\n# don't touch\nexport failed=0\nexport success=0\nexport EOSLASTLOG=$EOS_TEST_TESTSYS/test-last.log\nexport EOSALLCERTLOG=$EOS_TEST_TESTSYS/test-result.log\nexport EOSCERTLOG=\"$EOS_TEST_TESTSYS/test-output.log\"\nexport GSMLOCKFILE=$EOS_TEST_TESTSYS/eos-gsm.lock\nexport EOSTESTPID=$EOS_TEST_TESTSYS/eos-pid\nexport FAILFILE=$EOS_TEST_TESTSYS/eos-failed\nexport TIMEOUTFILE=$EOS_TEST_TESTSYS/eos-timeout\nexport TESTSYSFILE0K=$EOS_TEST_TESTSYS/file.0K\nexport TESTSYSFILE1K=$EOS_TEST_TESTSYS/file.1K\nexport TESTSYSFILE1M=$EOS_TEST_TESTSYS/file.1M\nexport TESTSYSFILE50M=$EOS_TEST_TESTSYS/file.50M\nexport TESTFSTFILE32M=$EOS_TEST_TESTSYS/file.32M\nexport EOSLOGBOOK=/var/log/eos/mgm/logbook.log\n\n################################################################################\nif [ \"x$1\" != \"xlist\" ]; then\n\nif [ ! -e $TESTSYSFILE0K ]; then\necho \"####################################\"\necho \"### Creating Test Pattern File 0K\"\necho \"####################################\"\nrm -rf $TESTSYSFILE0K >& /dev/null\ntouch $TESTSYSFILE0K\nfi\n\nif [ ! -e $TESTSYSFILE1K ]; then\necho \"####################################\"\necho \"### Creating Test Pattern File 1K\"\necho \"####################################\"\nyes | dd of=$TESTSYSFILE1K bs=1k count=1\nfi\n\nCKS1K=`eos-adler32 $TESTSYSFILE1K | awk '{print $4}' | sed s/adler32=//`\necho \"adler32 (1k) = $CKS1K\"\n\nif [ ! -e $TESTSYSFILE1M ]; then\necho \"####################################\"\necho \"### Creating Test Pattern File 1M\"\necho \"####################################\"\nyes | dd of=$TESTSYSFILE1M bs=1k count=1000\nfi\n\nCKS1M=`eos-adler32 $TESTSYSFILE1M | awk '{print $4}' | sed s/adler32=//`\necho \"adler32 (1M) = $CKS1M\"\n\nif [ ! -e $TESTSYSFILE50M ]; then\necho \"####################################\"\necho \"### Creating Test Pattern File 50M\"\necho \"####################################\"\nyes | dd of=$TESTSYSFILE50M bs=1k count=50000\nfi\n\nCKS50M=`eos-adler32 $TESTSYSFILE50M | awk '{print $4}' | sed s/adler32=//`\necho \"adler32 (50M) = $CKS50M\"\n\nif [ ! -e $TESTFSTFILE32M ]; then\necho \"#######################################\"\necho \"### Creating FST Test Pattern File 32M\"\necho \"#######################################\"\ndd if=/dev/zero bs=1M count=32 | tr '\\000' '\\001' > $TESTFSTFILE32M\nfi\n\nexport EOS_TEST_RAIN_DIR=$EOS_TEST_TESTSYS/rain\n\nif [ ! -d $EOS_TEST_RAIN_DIR ]; then\necho \"############################################\"\necho \"### Creating RAIN Dir\"\necho \"############################################\"\n\nmkdir -p $EOS_TEST_RAIN_DIR\ndd if=/dev/urandom of=$EOS_TEST_RAIN_DIR/file1.rain bs=1552K count=1\ndd if=/dev/urandom of=$EOS_TEST_RAIN_DIR/file2.rain bs=18227K count=1\ndd if=/dev/urandom of=$EOS_TEST_RAIN_DIR/file3.rain bs=1M count=34\n\necho \"############################################\"\necho \"### Creating Files and Patterns for IO Test\"\necho \"############################################\"\ncp $EOS_TEST_RAIN_DIR/file1.rain $EOS_TEST_RAIN_DIR/file1.io\n(echo \"0 524288\"; echo \"409600 786432\"; echo \"614400 1048576\"; echo \"1024000 1589248\") >> $EOS_TEST_RAIN_DIR/file1.io.pattern\ncp $EOS_TEST_RAIN_DIR/file2.rain $EOS_TEST_RAIN_DIR/file2.io\n(echo \"0 4194304\"; echo \"1048576 2097152\"; echo \"3145728 7340032\"; echo \"5242880 6291456\"; echo \"7340032 15728640\"; echo \"14680064 17825792\"; echo \"16777216 18664448\") >> $EOS_TEST_RAIN_DIR/file2.io.pattern\ncp $EOS_TEST_RAIN_DIR/file3.rain $EOS_TEST_RAIN_DIR/file3.io\n(echo \"0 524288\"; echo \"409600 786432\"; echo \"614400 1048576\"; echo \"1024000 1589248\";\n echo \"1589248 20971520\"; echo \"20971520 20973568\"; echo \"20972544 20975616 \";\n echo \"20972544 20976640\"; echo \"20976640 35651584\") >> $EOS_TEST_RAIN_DIR/file3.io.pattern\nfi\n\nfi\n\n################################################################################\n. /etc/rc.d/init.d/functions\necho \"\" > $EOSALLCERTLOG\nrm -f $FAILFILE 2>/dev/null\nrm -f $TIMEOUTFILE 2>/dev/null\nrm -f $EOSCERTLOG 2>/dev/null\nkill_child_processes() {\n    pid=$(bash -c 'echo $PPID');\n    if [ $1 -gt 0 ]; then\n        pids=`pstree -p $1 | sed 's/(/\\n(/g' | grep '(' | sed 's/(\\(.*\\)).*/\\1/' | tr \"\\n\" \" \"`;\n        for name in $pids; do\n            if [ $name -ne $pid ]; then\n                kill -9 $name >& /dev/null\n                sleep 0.1\n            fi\n        done\n    fi\n}\n\nkill_processes() {\n    echo\n    echo\n    echo \"CONTORL-C received ... aborting ...\"\n    echo\n    pid=`cat $EOSTESTPID 2>/dev/null`;\n    if [ -z \"$pid\" ]; then\n            pid=$(bash -c 'echo $PPID');\n    fi\n    kill_child_processes $pid;\n    exit 255;\n}\n\n################################################################################\nmailnotify () {\n    OK=\"OK\" && test -e $FAILFILE && OK=\"FAILED\"\n\n    if [ $OK = \"FAILED\" ]; then\n        if [ -n \"$EOS_TEST_MAILNOTIFY\" ]; then\n          mutt -s \"$EOS_TEST_INSTANCE $OK\" $EOS_TEST_MAILNOTIFY < $EOSALLCERTLOG ; fi\n        if [ ! -e $GSMLOCKFILE ]; then\n            if [ -n \"$EOS_TEST_GSMNOTIFY\" ]; then\n              echo \"$EOS_TEST_INSTANCE failed at `date`\" | mail -s \"$EOS_TEST_INSTANCE $OK\" $EOS_TEST_GSMNOTIFY ; \\\n              touch $GSMLOCKFILE; ( sleep EOS_TEST_GSMLOCKTIME; rm -rf $GSMLOCKFILE; ) >& /dev/null &\n            fi;\n        fi\n        exit 255;\n    fi\n    exit 0;\n}\n################################################################################\n(\nexport XROOTSYS=/usr/\nexport LD_LIBRARY_PATH=$XROOTSYS/lib64/:$LD_LIBRARY_PATH\nexport PATH=$XROOTSYS/bin:$PATH\nexport EOS_XROOTSYS=/opt/eos/xrootd/\nexport LD_LIBRARY_PATH=$EOS_XROOTSYS/lib64/:$LD_LIBRARY_PATH\nexport PATH=$EOS_XROOTSYS/bin:$PATH\npid=$(bash -c 'echo $PPID');\necho -n $pid >& $EOSTESTPID\n################################################################################\n# Library\n################################################################################\nshowtoken () {\n    echo \"############### KRB 5 ##############\"\n    /usr/kerberos/bin/klist -5\n    echo \"############### X509  ##############\"\n    xrdgsiproxy info\n    echo \"####################################\"\n}\n################################################################################\nlogoutput () {\n    echo \"----------------- Error Output --------------------\";cat $EOSCERTLOG ;echo \"---------------------------------------------------\"\n}\n################################################################################\nupload () {\n    src=$1\n    dst=$2\n    shift\n    shift\n    echo xrdcp -np -v -f $* $src root://$EOS_TEST_REDIRECTOR/$dst >& $EOSLASTLOG ;eval command xrdcp -f $* $src root://$EOS_TEST_REDIRECTOR/$dst >> $EOSCERTLOG 2>&1\n}\n################################################################################\ndownload () {\n    src=$1\n    dst=$2\n    shift\n    shift\n    echo xrdcp -np -v -f $* root://$EOS_TEST_REDIRECTOR/$dst $src  >& $EOSLASTLOG ;eval command xrdcp -f $* root://$EOS_TEST_REDIRECTOR/$dst $src >> $EOSCERTLOG 2>&1\n}\n################################################################################\ndownloadh () {\n    src=$1\n    dst=$2\n    shift\n    shift\n    echo xrdcp -np -v -f $* root://$EOS_TEST_FULL_REDIRECTOR/$dst $src  >& $EOSLASTLOG ;eval command xrdcp -f $* root://$EOS_TEST_FULL_REDIRECTOR/$dst $src >> $EOSCERTLOG 2>&1\n}\n################################################################################\ntimeout() {\n    echo \"## TIMEOUT ##\";\n    exit 0;\n}\n\nmeta() {\n    echo xrdfs $EOS_TEST_REDIRECTOR $* >& $EOSLASTLOG ; eval command xrdfs $EOS_TEST_REDIRECTOR $* >> $EOSCERTLOG 2>&1\n}\n\neos() {\n    echo eos -b $EOSROLE $EOSAPP root://$EOS_TEST_REDIRECTOR $* >& $EOSLASTLOG; eval command eos -b $EOSROLE $EOSAPP root://$EOS_TEST_REDIRECTOR $* >> $EOSCERTLOG 2>&1\n}\n\neosj() {\n    echo eos -j -b $EOSROLE $EOSAPP root://$EOS_TEST_REDIRECTOR $* >& $EOSLASTLOG; eval command eos -j -b $EOSROLE $EOSAPP root://$EOS_TEST_REDIRECTOR $* >> $EOSCERTLOG 2>&1\n}\n\ntpc1() {\n    src=$1\n    dst=$2\n    echo xrdcp --tpc only root://$EOS_TEST_REDIRECTOR/$src root://$EOS_TEST_REDIRECTOR/$dst >& $EOSLASTLOG; eval command xrdcp -f --tpc only root://$EOS_TEST_REDIRECTOR/$src root://$EOS_TEST_REDIRECTOR/$dst >> $EOSCERTLOG 2>&1\n}\n\ntpc2() {\n    src=$1\n    dst=$2\n    echo xrdcopy --tpc only root://$EOS_TEST_REDIRECTOR/$src root://$EOS_TEST_REDIRECTOR/$dst >& $EOSLASTLOG; eval command xrdcopy --tpc only root://$EOS_TEST_REDIRECTOR/$src root://$EOS_TEST_REDIRECTOR/$dst >> $EOSCERTLOG 2>&1\n}\n\ntgrep() {\n        a=$1\n        shift\n        echo \"eval $* | grep $a\" >& $EOSLASTLOG; eval $* | grep $a >> $EOSCERTLOG 2>&1\n}\n\nstress() {\n    echo xrdstress $* >& $EOSLASTLOG; eval XRD_RUNFORKHANDLER=1 xrdstress $* >> $EOSCERTLOG 2>&1\n}\n\nappend() {\n    echo xrdcpappend root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcpappend root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\noverlap() {\n    echo xrdcpappendoverlap root://123@$EOS_TEST_REDIRECTOR/$1 root://124@$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcpappendoverlap root://123@$EOS_TEST_REDIRECTOR/$1 root://124@$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\nabort() {\n    echo xrdcpabort root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcpabort root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\nextend() {\n    echo xrdcpextend root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcpextend root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\ndelay() {\n    echo sleep $1 >& $EOSLASTLOG ; eval sleep $1 >> $EOSCERTLOG 2>&1\n}\n\nshrink() {\n    echo xrdcpshrink root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcpshrink root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\nrandom() {\n    echo xrdcprandom root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcprandom root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\ntruncate() {\n    echo xrdcptruncate root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcptruncate root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\ncp_nonstreaming() {\n    echo xrdcpnonstreaming $1 root://$EOS_TEST_REDIRECTOR/$2 >& $EOSLASTLOG; eval xrdcpnonstreaming $1 root://$EOS_TEST_REDIRECTOR/$2 >> $EOSCERTLOG 2>&1\n}\n\nholes() {\n    echo xrdcpholesroot://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcpholes root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\nbackward() {\n    echo xrdcpbackward root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcpbackward root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\npartial() {\n    echo xrdcppartial root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcppartial root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\ndownloadrandom() {\n    echo xrdcpdownloadrandom root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcpdownloadrandom root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\neos_rain() {\n    echo eos-rain-test $1 $2 root://$EOS_TEST_REDIRECTOR/$3 >& $EOSLASTLOG; eval eos-rain-test $1 $2 root://$EOS_TEST_REDIRECTOR/$3 >> $EOSCERTLOG 2>&1\n}\n\neos_quota() {\n    echo eos-quota-test $1 $2 root://$EOS_TEST_REDIRECTOR/$3 >& $EOSLASTLOG; eval eos-quota-test $1 $2 root://$EOS_TEST_REDIRECTOR/$3 >> $EOSCERTLOG 2>&1\n}\n\neos_defaultcc() {\n    echo eos-defaultcc-test $1 $2 root://$EOS_TEST_REDIRECTOR/$3 >& $EOSLASTLOG; eval eos-defaultcc-test $1 $2 root://$EOS_TEST_REDIRECTOR/$3 >> $EOSCERTLOG 2>&1\n}\n\neos_bash() {\n    echo env EOS_FUSE_SECRET=${EOS_FUSE_SECRET} eos-bash $* >& $EOSLASTLOG; env EOS_FUSE_SECRET=${EOS_FUSE_SECRET} eos-bash $* >> $EOSCERTLOG 2>&1\n}\n\neos_synctime() {\n    echo eos-synctime-test $1 $2 root://$EOS_TEST_REDIRECTOR/$3 >& $EOSLASTLOG; eval eos-synctime-test $1 $2 root://$EOS_TEST_REDIRECTOR/$3 >> $EOSCERTLOG 2>&1\n}\n\neos_rclone() {\n    echo eos-rclone-test $1 $2 root://$EOS_TEST_REDIRECTOR/$3 >& $EOSLASTLOG; eval eos-rclone-test $1 $2 root://$EOS_TEST_REDIRECTOR/$3 >> $EOSCERTLOG 2>&1\n}\n\neoscp_rain() {\n    echo eoscp-rain-test $1 root://$EOS_TEST_REDIRECTOR/$2 >& $EOSLASTLOG; eval eoscp-rain-test $1 root://$EOS_TEST_REDIRECTOR/$2 >> $EOSCERTLOG 2>&1\n}\n\neos_io() {\n   echo eos-io-test root://$EOS_TEST_REDIRECTOR/$1 $2 >& $EOSLASTLOG; eval eos-io-test root://$EOS_TEST_REDIRECTOR/$1 $2 >> $EOSCERTLOG 2>&1\n}\n\neos_rename() {\n   echo eos-rename-test $1 $EOS_TEST_REDIRECTOR; eval eos-rename-test $1 $EOS_TEST_REDIRECTOR >> $EOSCERTLOG 2>&1\n}\n\neos_acl_concurrent() {\n   echo eos-acl-concurrent $1; eval eos-acl-concurrent $1 >> $EOSCERTLOG 2>&1\n}\n\neos_grpc() {\n   echo eos-grpc-test $1 ; eval eos-grpc-test $1 >> $EOSCERTLOG 2>&1\n}\n\neos_token() {\n   echo eos-token-test $1 ; eval eos-token-test $1 ${EOS_TEST_FULL_REDIRECTOR} >> $EOSCERTLOG 2>&1\n}\n\neos_squash() {\n   echo eos-squash-test $1 ; eval eos-squash-test $1 ${EOS_TEST_FULL_REDIRECTOR} >> $EOSCERTLOG 2>&1\n}\n\neos_accounting() {\n  echo eos-accounting-test \"$1\" ; eval eos-accounting-test \"$1\" ${EOS_TEST_REDIRECTOR} >> $EOSCERTLOG 2>&1\n}\n\neos_file_cont_detached() {\n  local QDB_CLUSTER=$(cat /etc/xrd.cf.mgm | grep \"^mgmofs.qdbcluster\" | awk '{print $2;}')\n  local QDB_PWDFILE=$(cat /etc/xrd.cf.mgm | grep \"^mgmofs.qdbpassword_file\" | awk '{print $2;}')\n  echo eos-file-cont-detached-test \"$1\"; eval eos-file-cont-detached-test \"$1\" \"$QDB_CLUSTER\" \"$QDB_PWDFILE\" >> \"$EOSCERTLOG\" 2>&1\n}\n\neos_lru() {\n  echo eos-lru-test $1 ; eval eos-lru-test $1 ${EOS_TEST_REDIRECTOR} >> $EOSCERTLOG 2>&1\n}\n\nshell() {\n    echo $* >& $EOSLASTLOG; eval $* >> $EOSCERTLOG 2>&1\n}\n\n################################################################################\ntestout () {\n    echo Failed: $failed Success: $success\n    if [ $failed -gt 0 ]; then touch $FAILFILE; fi\n}\n################################################################################\nfile_is_deleted () {\n    local fxid=$1\n    local output\n    output=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo \"fxid:${fxid}\" 2>&1)\n    local rc=$?\n    if [ $rc -ne 0 ] || echo \"$output\" | grep -q \"pending_deletion\\|tombstone\\|does not exist\"; then\n        return 0\n    fi\n    return 1\n}\n################################################################################\n# Count total number of runtest calls in this script\nif [ \"$1\" != \"list\" ]; then\n  echo \"Counting total number of tests...\"\n  total_tests=$(grep -c \"^runtest \" \"$0\")\n  echo \"Total tests found: $total_tests\"\n  echo\nelse\n  total_tests=0\nfi\n\ntestcnt=0;\nruntest () {\n    testcnt=$((testcnt+1));\n\n    if [ -e $TIMEOUTFILE ]; then\n        echo -n $(date +\"%x %X\") timeout\n        failure\n    else\n        echo \"-------------------------------------------------------------\" >> $EOSCERTLOG 2>&1\n        echo \"# Test $testcnt: $*\" >> $EOSCERTLOG 2>&1\n        echo \"-------------------------------------------------------------\" >> $EOSCERTLOG 2>&1\n\n        if [ \"$firsttest\" = \"list\" ]; then\n          printf \"# [ %-7s ]\" $categorie ;\n          printf \" ID %02d\" $testcnt;\n          echo \" $*\"\n          return\n        fi\n\n        redir=$EOS_TEST_REDIRECTOR\n        if [ \"$1\" = H ]; then\n          EOS_TEST_REDIRECTOR=$EOS_TEST_FULL_REDIRECTOR\n          shift\n        fi\n\n        START=$(date +%s.%N)\n        if [ $total_tests -gt 0 ]; then\n          printf \"%04d/%04d \" $testcnt $total_tests\n        else\n          printf \"%04d \" $testcnt\n        fi\n        echo -n $(date +\"%x %X\") \"$1\"\n        auth=$2\n        res=$3\n        renv=$4\n        shift\n        echo -n $*\n        shift\n        shift\n        shift\n        skip=0\n        if [ $categorie != \"prep\" ] && [ $categorie != \"clean\" ]; then\n          # --- if $firsttest is set and $lasttest not we assume it is a categorie list\n          if [ \"x$firsttest\" != \"x\" ] && [ \"x$lasttest\" == \"x\" ]; then\n            if [ \"x$firsttest\" = \"x${firsttest/$categorie}\" ] ; then\n              echo\n              warning \"skipped\" ; echo -n \" ... skipped\"\n              echo\n              skip=1\n            fi\n          else\n\n          # --- skip if start-range was given\n          if [ \"x$firsttest\" != \"x\" ] ; then\n            if [ $testcnt -lt $firsttest ]; then\n              echo\n              warning \"skipped\" ; echo -n \" ... skipped\"\n              echo\n              skip=1\n            fi\n          fi\n          # --- skip if end-range was given\n          if [ \"x$lasttest\" != \"x\" ] ; then\n            if [ $testcnt -gt $lasttest ]; then\n              echo\n              warning \"skipped\" ; echo -n \" ... skipped\"\n              echo\n              skip=1\n            fi\n          fi\n          fi\n        fi\n\n        if [ $skip = 0 ] ; then\n          # Record the current size of the log file before running the test\n          log_size_before=0\n          if [ -f \"$EOSCERTLOG\" ]; then\n            log_size_before=$(wc -l < \"$EOSCERTLOG\")\n          fi\n\n          if [ $auth = \"krb5\" ] ; then ( eval export X509_USER_CERT=/tmp/illegal X509_USER_KEY=/tmp/illegal $renv; $* ) ; ret=$?; fi\n          if [ $auth = \"gsi\"  ] ; then ( eval export KRB5CCNAME=/tmp/illegal                                $renv; $* ) ; ret=$?; fi\n          if [ $auth = \"unix\" ] ; then ( eval export XrdSecPROTOCOL=unix                                    $renv; $* ) ; ret=$?; fi\n          if [ $auth = \"sss\"  ] ; then ( eval export XrdSecPROTOCOL=sss                                     $renv; $* ) ; ret=$?; fi\n          echo\n\n          # Check if test failed unexpectedly and print output\n          test_failed=0\n\n          case \"$res\" in\n            \"SUCCESS\")\n              if [ \"$ret\" -eq 0 ]; then\n                success; ((success++))\n              else\n                failure; ((failed++)); echo; test_failed=1\n              fi\n              ;;\n\n            \"ERROR\")\n              if [ \"$ret\" -ne 0 ]; then\n                success; ((success++))\n              else\n                failure; ((failed++)); echo; test_failed=1\n              fi\n              ;;\n\n            \"IGNORE\")\n              success; ((success++))\n              ;;\n\n            *)\n              # If $res is a number (like 21), check for an exact match with $ret\n              if [ \"$ret\" -eq \"$res\" ] 2>/dev/null; then\n                success; ((success++))\n              else\n                failure; ((failed++)); echo; test_failed=1\n                echo \"Expected error $res, but got $ret\"\n              fi\n              ;;\n          esac\n\n          # Print full output for failed tests in indented block with red text on white background\n          if [ $test_failed -eq 1 ]; then\n            echo\n            # ANSI color codes: red text (31m), white background (47m), reset (0m)\n            # Keep indentation transparent, only color the text content\n            echo -e \"    \\033[31;47mFull test output:\\033[0m\"\n            echo -e \"    \\033[31;47m===================\\033[0m\"\n            if [ -f \"$EOSCERTLOG\" ]; then\n              log_size_after=$(wc -l < \"$EOSCERTLOG\")\n              if [ $log_size_after -gt $log_size_before ]; then\n                # Extract the lines added during this test\n                tail -n +$((log_size_before + 1)) \"$EOSCERTLOG\" | while IFS= read -r line; do\n                  echo -e \"    \\033[31;47m$line\\033[0m\"\n                done\n              else\n                echo -e \"    \\033[31;47m(No new output in log file)\\033[0m\"\n              fi\n            else\n              echo -e \"    \\033[31;47m(Log file not found)\\033[0m\"\n            fi\n            echo -e \"    \\033[31;47m===================\\033[0m\"\n            echo\n          fi\n\n          END=$(date +%s.%N)\n          DIFF=$(echo \"$END - $START\" | bc)\n          echo \"                         in $DIFF seconds ($ret)\"\n        fi\n        echo \"--------------------------------------------------------------------------------------------------------------------\"\n        EOS_TEST_REDIRECTOR=$redir\n    fi\n}\n################################################################################\n\necho \"===================================================\"\necho \"### Testing $EOS_TEST_REDIRECTOR\"\necho \"===================================================\"\n\n################################################################################\n# define your tests here\n# runtest <info> <auth> <exports> <expect> <mode> <srcpath> <dstpath> <param>\n#      <info>                  : describes what you are testing\n#      <auth>                  : 'krb5','gsi','unix','sss' - choose krb5 or X509 authentication [ you have to create both tokens beforehand ]\n#      <env>                   : can be used to set the execution environment env <env> \"\n#      <expect>                : 0 or 1 => 0 - you expect the command works , 1 - you expect that the command failes, 2 - you run the command but it can fail or not, that is expected\n#      <mode>                  : upload|download|downloadh|meta|eos|stress|abort|extend|shrink|random|truncate|holes|backward|partial|downloadrandom|tpc1|tpc2|shell|eoscp_rain|eos_rain|eos_io :\n#                                self explaining - meta executes command via xrd shell, eos via eos shell, stress runs xrdstress, abort runs xrdcpabort ...\n#      <srcpath,dstpath>       : is a local '/...' or remote '/eos/...' path\n#      <param>                 : opaque information passed ... '&' has to be escaped !!!!\n################################################################################\n\n################################################################################\n#xrdgsiproxy init >>& $EOSCERTLOG\nshowtoken >> $EOSCERTLOG 2>&1\n\n\n################################################################################\n# Preparation\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"prep\"\n# ------------------------------------------------------------------------------\nruntest \"### Access    \" unix IGNORE  \"\" eos \"access rm stall r\"\nruntest \"### Access    \" unix IGNORE  \"\" eos \"access rm redirect\"\nruntest \"### Access    \" unix IGNORE  \"\" eos \"access unban user nobody\"\nruntest \"### Stat      \" unix SUCCESS \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/\"\nruntest \"### Cleanup   \" unix IGNORE  \"\" eos rm \"-r\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/\"\nruntest \"### Cleanup   \" unix IGNORE  \"\" eos rm \"-r\" \"/eos/$EOS_TEST_INSTANCE/test/stress/\"\nruntest \"### Cleanup   \" unix IGNORE  \"\" eos attr rm sys.forced.blockchecksum /eos/$EOS_TEST_INSTANCE/test/\nruntest \"### Cleanup   \" unix IGNORE  \"\" eos attr rm sys.forced.blocksize /eos/$EOS_TEST_INSTANCE/test/\nruntest \"### Cleanup   \" unix IGNORE  \"\" eos attr rm sys.forced.checksum /eos/$EOS_TEST_INSTANCE/test/\nruntest \"### Cleanup   \" unix IGNORE  \"\" eos attr rm sys.forced.layout /eos/$EOS_TEST_INSTANCE/test/\nruntest \"### Cleanup   \" unix IGNORE  \"\" eos attr rm sys.forced.nstripes /eos/$EOS_TEST_INSTANCE/test/\nruntest \"### Cleanup   \" unix IGNORE  \"\" eos attr rm sys.forced.space /eos/$EOS_TEST_INSTANCE/test/\nruntest \"### Cleanup   \" unix IGNORE  \"\" eos attr rm sys.forced.atomic /eos/$EOS_TEST_INSTANCE/test/\nruntest \"### Cleanup   \" unix IGNORE  \"\" eos attr rm sys.versioning /eos/$EOS_TEST_INSTANCE/test\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"-p\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/\"\nruntest \"### Quota     \" unix SUCCESS \"\" eos quota set -u ${NOBODY_UID} -p /eos/$EOS_TEST_INSTANCE/test -v 1T -i 1M\nruntest \"### Quota     \" unix ERROR \"\" eos quota set -u ${NOBODY_UID} /eos/$EOS_TEST_INSTANCE/test -v 1T -i 1M\nruntest \"### Quota     \" unix SUCCESS \"\" eos quota set -u 11 -p /eos/$EOS_TEST_INSTANCE/test -v 1T -i 1M\nruntest \"### Quota     \" unix SUCCESS \"\" eos quota set -u 100 -p /eos/$EOS_TEST_INSTANCE/test -v 1T -i 1M\nruntest \"### Quota     \" unix SUCCESS \"\" eos quota set -u 2 -p /eos/$EOS_TEST_INSTANCE/test -v 1T -i 1M\nruntest \"### Quota     \" unix SUCCESS \"\" eos quota set -u 3 -p /eos/$EOS_TEST_INSTANCE/test -v 1T -i 1M\nruntest \"### Quota     \" unix SUCCESS \"\" eos quota set -u 2000 -p /eos/$EOS_TEST_INSTANCE/test -v 1T -i 1M\n# Add quota for eos-user\nruntest \"### Quota     \" unix SUCCESS \"\" eos quota set -u 1000 -p /eos/$EOS_TEST_INSTANCE/test -v 1T -i 1M\n# Add quota for s3user\nruntest \"### Quota     \" unix SUCCESS \"\" eos quota set -u 1004 -p /eos/$EOS_TEST_INSTANCE/test -v 1T -i 1M\nruntest \"### Rm        \" unix SUCCESS \"\" eos rm \"-r\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod \"755\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/\"\nruntest \"### Motd      \" unix SUCCESS \"\" eos motd\nruntest \"### Help      \" unix SUCCESS \"\" eos \"help 2>&1| grep \\\"Display this text\\\"\"\n# Define huge nominal space\nruntest \"### NomianlSp \" unix SUCCESS \"\" eos space config default space.nominalsize=100T\n################################################################################\n# Upload,Download,Stat,Rename\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"core\"\n# ------------------------------------------------------------------------------\nruntest \"### Upload    \" unix ERROR \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd\" \"-ODeos.space=default\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod \"777\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd\" \"-ODeos.space=default\"\nruntest \"### Stat      \" unix SUCCESS \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd\"\nruntest \"### Stat      \" unix ERROR \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd.1\"\nruntest \"### Download  \" unix SUCCESS \"\" download  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd\"\nruntest \"### Part.Dwld \" unix SUCCESS \"\" partial   \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd\"\nruntest \"### Download  \" unix ERROR \"\" download  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd.1\"\nruntest \"### Dowl Lfn  \" unix SUCCESS \"\" download  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/enoent\" \"-OSeos.lfn=/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd\"\nruntest \"### Dowl Lfn  \" unix ERROR \"\" download  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/enoent\" \"-OSeos.lfn=/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd.1\"\nruntest \"### Dowl Pfx  \" unix SUCCESS \"\" download  \"$EOS_TEST_TESTSYS/dumpit\" \"/test/instancetest/passwd\" \"-OSeos.prefix=/eos/$EOS_TEST_INSTANCE/\"\nruntest \"### Dowl Pfx  \" unix ERROR \"\" download  \"$EOS_TEST_TESTSYS/dumpit\" \"/test/instancetest/passwd\" \"-OSeos.prefix=/eos/faulty/\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find \"-f --count /eos/$EOS_TEST_INSTANCE/test/ | grep nfiles=1\"\nruntest \"### Rem       \" unix ERROR \"\" eos rm    \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd.1\"\nruntest \"### FileInfo  \" unix ERROR \"\" eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd.1\"\nruntest \"### Stat      \" unix SUCCESS \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd\"\nruntest \"### Whoami    \" unix SUCCESS \"\" eos \"whoami | grep authz:unix | grep uid=0\"\nruntest \"### Who       \" unix SUCCESS \"\" eos \"who | grep root\"\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd\"\nruntest \"### Rename    \" unix ERROR \"\" eos \"file rename /eos/$EOS_TEST_INSTANCE/test/instancetest/passwd /eos/$EOS_TEST_INSTANCE/test/instancetest/passwd.rename\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd\" \"-ODeos.space=default\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.meta4\" \"-ODeos.space=default\"\nruntest \"### Upload    \" unix SUCCESS \"XRD_METALINKPROCESSING=0\" download  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.meta4\"\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.meta4\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod \"755\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest\"\nruntest \"### Rename    \" unix ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos file rename /eos/$EOS_TEST_INSTANCE/test/instancetest/passwd /eos/$EOS_TEST_INSTANCE/test/instancetest/passwd.rename\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod \"777\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest\"\nruntest \"### Rename    \" unix SUCCESS \"\" eos file rename /eos/$EOS_TEST_INSTANCE/test/instancetest/passwd /eos/$EOS_TEST_INSTANCE/test/instancetest/passwd.rename\n\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos \"mkdir /eos/$EOS_TEST_INSTANCE/test/instancetest/rename.dir\"\nruntest \"### Rename    \" unix ERROR \"\" eos \"file rename /eos/$EOS_TEST_INSTANCE/test/instancetest/rename.dir /eos/$EOS_TEST_INSTANCE/test/instancetest/rename.dir/myself\"\nruntest \"### Rename    \" unix ERROR \"\" eos \"file rename /eos/$EOS_TEST_INSTANCE/test/instancetest/rename.dir /eos/$EOS_TEST_INSTANCE/test/instancetest/passwd.rename\"\nruntest \"### Rename    \" unix ERROR \"\" eos \"file rename /eos/$EOS_TEST_INSTANCE/test/instancetest/rename.dir.missing /eos/$EOS_TEST_INSTANCE/test/instancetest/rename\"\nruntest \"### Rename    \" unix SUCCESS \"\" eos \"file rename /eos/$EOS_TEST_INSTANCE/test/instancetest/rename.dir /eos/$EOS_TEST_INSTANCE/test/instancetest/rename\"\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd.rename\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd\" \"-ODeos.space=default\"\nruntest \"### Stat      \" unix SUCCESS \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd\"\nruntest \"### Stat      \" unix ERROR \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd/\"\nruntest \"### Upload    \" unix ERROR \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd/\" \"-ODeos.space=default\"\nruntest \"### Upload    \" unix ERROR \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd/\" \"-f\"\nruntest \"### Rmdir     \" unix SUCCESS \"\" eos rmdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/rename\"\n\n################################################################################\n# File/Directory Rename by Identifier (fxid/cxid)\n################################################################################=\nruntest \"### Upload     \" unix SUCCESS \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file_for_fxid.txt\" \"-ODeos.space=default\"\nruntest \"### Mkdir      \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/dir_for_cxid\"\nfid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file_for_fxid.txt\" | jq -r .id)\nfxid=$(printf \"fxid:%08x\" $fid)\nruntest \"### RenameFxid \" unix SUCCESS \"\" eos file rename ${fxid} /eos/$EOS_TEST_INSTANCE/test/instancetest/renamed_by_fxid.txt\nruntest \"### StatRenam  \" unix SUCCESS \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/renamed_by_fxid.txt\"\nruntest \"### StatOld    \" unix ERROR \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file_for_fxid.txt\"\nruntest \"### Upload     \" unix SUCCESS \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file_for_fid.txt\" \"-ODeos.space=default\"\nfid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file_for_fid.txt\" | jq -r .id)\nruntest \"### RenameFid  \" unix SUCCESS \"\" eos file rename fid:${fid} /eos/$EOS_TEST_INSTANCE/test/instancetest/renamed_by_fid.txt\nruntest \"### StatRenam  \" unix SUCCESS \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/renamed_by_fid.txt\"\ncid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/dir_for_cxid\" | jq -r .id)\ncxid=$(printf \"cxid:%08x\" $cid)\nruntest \"### RenameCxid \" unix SUCCESS \"\" eos file rename ${cxid} /eos/$EOS_TEST_INSTANCE/test/instancetest/renamed_dir_by_cxid\nruntest \"### StatRenam  \" unix SUCCESS \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/renamed_dir_by_cxid\"\nruntest \"### StatOld    \" unix ERROR \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/dir_for_cxid\"\nruntest \"### Upload     \" unix SUCCESS \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file_for_symlink.txt\" \"-ODeos.space=default\"\nruntest \"### Mkdir      \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/symlink_archive\"\nfid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file_for_symlink.txt\" | jq -r .id)\nfxid=$(printf \"fxid:%08x\" $fid)\ncid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/symlink_archive\" | jq -r .id)\ncxid=$(printf \"cxid:%08x\" $cid)\nruntest \"### RenSymFxid \" unix SUCCESS \"\" eos file rename_with_symlink ${fxid} ${cxid}\nruntest \"### ChkSymlink \" unix SUCCESS \"\" eos \"ls -l /eos/$EOS_TEST_INSTANCE/test/instancetest/ | grep \\\"file_for_symlink.txt ->\\\"\"\nruntest \"### StatTarget \" unix SUCCESS \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/symlink_archive/file_for_symlink.txt\"\nruntest \"### Upload     \" unix SUCCESS \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/src_file_for_identifier_symlink.txt\" \"-ODeos.space=default\"\nfid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/src_file_for_identifier_symlink.txt\" | jq -r .id)\nfxid=$(printf \"fxid:%08x\" $fid)\nruntest \"### SymlinkFx  \" unix SUCCESS \"\" eos file symlink /eos/$EOS_TEST_INSTANCE/test/instancetest/identifier_symlink_test.txt ${fxid}\nruntest \"### ChkLink    \" unix SUCCESS \"\" eos \"ls -l /eos/$EOS_TEST_INSTANCE/test/instancetest/ | grep \\\"identifier_symlink_test.txt ->\\\"\"\nruntest \"### StatLnkTgt \" unix SUCCESS \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/identifier_symlink_test.txt\"\n\nruntest \"### RenInvFxid \" unix ERROR \"\" eos \"file rename fxid:invalid /eos/$EOS_TEST_INSTANCE/test/instancetest/test.txt\"\nruntest \"### RenInvCxid \" unix ERROR \"\" eos \"file rename cxid:999999999 /eos/$EOS_TEST_INSTANCE/test/instancetest/test\"\nruntest \"### RenInvFid  \" unix ERROR \"\" eos \"file rename fid:abc /eos/$EOS_TEST_INSTANCE/test/instancetest/test.txt\"\n\nruntest \"### Cleanup    \" unix SUCCESS \"\" eos rm -rf \"/eos/$EOS_TEST_INSTANCE/test/instancetest/renamed_dir_by_cxid\"\nruntest \"### Cleanup    \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/renamed_by_fxid.txt\"\nruntest \"### Cleanup    \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/renamed_by_fid.txt\"\nruntest \"### Cleanup    \" unix SUCCESS \"\" eos rm -rf \"/eos/$EOS_TEST_INSTANCE/test/instancetest/symlink_archive\"\nruntest \"### Cleanup    \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/src_file_for_identifier_symlink.txt\"\nruntest \"### Cleanup    \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/identifier_symlink_test.txt\"\n\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/a\\&b\"\nruntest \"### Rmdir     \" unix SUCCESS \"\" eos rmdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/a\\&b\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/a\\&b\"\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm -rf \"/eos/$EOS_TEST_INSTANCE/test/instancetest/a\\&b\"\n# Tests for rename_with_symlink functionality\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/mv_with_symlink\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/mv_with_symlink/archive\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/mv_with_symlink/file.dat\" \"-ODeos.space=default\"\n# Fail if destination directory matches source directory\nruntest \"### RenameSym \" unix ERROR \"\" eos \"file rename_with_symlink /eos/$EOS_TEST_INSTANCE/test/instancetest/mv_with_symlink/file.dat /eos/$EOS_TEST_INSTANCE/test/instancetest/mv_with_symlink\"\nruntest \"### RenameSym \" unix SUCCESS \"\" eos \"file rename_with_symlink /eos/$EOS_TEST_INSTANCE/test/instancetest/mv_with_symlink/file.dat /eos/$EOS_TEST_INSTANCE/test/instancetest/mv_with_symlink/archive\"\nruntest \"### RenameSym \" unix SUCCESS \"\" eos \"ls -l /eos/$EOS_TEST_INSTANCE/test/instancetest/mv_with_symlink/ | grep \\\"file.dat -> /eos/$EOS_TEST_INSTANCE/test/instancetest/mv_with_symlink/archive/file.dat\\\"\"\nruntest \"### Stat      \" unix SUCCESS \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/mv_with_symlink/archive/file.dat\"\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm -rf \"/eos/$EOS_TEST_INSTANCE/test/instancetest/mv_with_symlink\"\n\nruntest \"### MkSpace   \" unix SUCCESS \"\" eos \"space define __test__\"\nruntest \"### SetSpace  \" unix SUCCESS \"\" eos \"space set __test__ on \"\nruntest \"### RmSpace   \" unix SUCCESS \"\" eos \"space rm __test__\"\n\nruntest \"### MkGroup   \" unix SUCCESS \"\" eos \"group set __test__ on\"\nruntest \"### RmGroup   \" unix SUCCESS \"\" eos \"group rm __test__\"\n\nruntest \"### MkNode    \" unix SUCCESS \"\" eos \"node set __test__ on\"\nruntest \"### RmNode    \" unix SUCCESS \"\" eos \"node rm __test__\"\nruntest \"### eos-rename\" unix SUCCESS \"\" eos_rename \"/eos/$EOS_TEST_INSTANCE/test/instancetest/rename-test\"\nruntest \"### Rm        \" unix SUCCESS \"\" eos rm -rf \"/eos/$EOS_TEST_INSTANCE/test/instancetest/rename-test\"\n\n# --------------------------\n# Constrain minimum file size\n# --------------------------\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos \"mkdir /eos/$EOS_TEST_INSTANCE/test/instancetest/minsize\"\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.minsize=1000000 /eos/$EOS_TEST_INSTANCE/test/instancetest/minsize\nruntest \"### Upload    \" unix ERROR \"\" upload   \"/etc/group\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/minsize/f1\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload   \"$TESTSYSFILE1M\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/minsize/f1\"\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm -r \"/eos/$EOS_TEST_INSTANCE/test/instancetest/minsize\"\n\n# --------------------------\n# Constrain maximum file size\n# --------------------------\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos \"mkdir /eos/$EOS_TEST_INSTANCE/test/instancetest/maxsize\"\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.maxsize=1000000 /eos/$EOS_TEST_INSTANCE/test/instancetest/maxsize\nruntest \"### Upload    \" unix ERROR \"\" upload   \"$TESTSYSFILE1M\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/maxsize/f1\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload   \"/etc/group\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/maxsize/f1\"\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm -r \"/eos/$EOS_TEST_INSTANCE/test/instancetest/maxsize\"\n\n# --------------------------\n# Control-C\n# --------------------------\nruntest \"### Abort     \" unix ERROR \"\" abort /eos/$EOS_TEST_INSTANCE/test/instancetest/abort\nruntest \"### FileInfo  \" unix ERROR \"\" eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/abort\"\nruntest \"### Rem       \" unix IGNORE  \"\" eos rm    \"/eos/$EOS_TEST_INSTANCE/test/instancetest/abort\"\n# --------------------------\n# Extend a file via truncate\n# --------------------------\nruntest \"### Extend    \" unix SUCCESS \"\" extend /eos/$EOS_TEST_INSTANCE/test/instancetest/extend\nruntest \"### Download  \" unix SUCCESS \"\" download  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/extend\"\nruntest \"### Rem       \" unix IGNORE  \"\" eos rm    \"/eos/$EOS_TEST_INSTANCE/test/instancetest/extend\"\n# --------------------------\n# Shrink a file via truncate\n# --------------------------\nruntest \"### Shrink    \" unix SUCCESS \"\" shrink /eos/$EOS_TEST_INSTANCE/test/instancetest/shrink\nruntest \"### Download  \" unix SUCCESS \"\" download  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/shrink\"\nruntest \"### Rem       \" unix IGNORE  \"\" eos rm    \"/eos/$EOS_TEST_INSTANCE/test/instancetest/shrink\"\n# --------------------------\n# Write file in random order\n# --------------------------\nruntest \"### Random    \" unix SUCCESS \"\" random /eos/$EOS_TEST_INSTANCE/test/instancetest/random\nruntest \"### Download  \" unix SUCCESS \"\" download  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/random\"\nruntest \"### Rem       \" unix IGNORE  \"\" eos rm    \"/eos/$EOS_TEST_INSTANCE/test/instancetest/random\"\n# --------------------------\n# Extend file and rewrite the beginning\n# --------------------------\nruntest \"### Truncate  \" unix SUCCESS \"\" truncate /eos/$EOS_TEST_INSTANCE/test/instancetest/truncate\nruntest \"### Download  \" unix SUCCESS \"\" download  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/truncate\"\nruntest \"### Rem       \" unix IGNORE  \"\" eos rm    \"/eos/$EOS_TEST_INSTANCE/test/instancetest/truncate\"\n# --------------------------\n# Expand file several times\n# --------------------------\nruntest \"### holes     \" unix SUCCESS \"\" holes /eos/$EOS_TEST_INSTANCE/test/instancetest/holes\nruntest \"### Download  \" unix SUCCESS \"\" download  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/holes\"\nruntest \"### backward  \" unix SUCCESS \"\" backward  /eos/$EOS_TEST_INSTANCE/test/instancetest/holes\nruntest \"### downrnd   \" unix SUCCESS \"\" downloadrandom  /eos/$EOS_TEST_INSTANCE/test/instancetest/holes\nruntest \"### Rem       \" unix IGNORE  \"\" eos rm    \"/eos/$EOS_TEST_INSTANCE/test/instancetest/holes\"\n# --------------------------\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.checksum=adler /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### UploadCks \" unix SUCCESS \"\" upload    \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/faulty-checksum1k\" \"-ODeos.checksum=$CKS1K\"\nruntest \"### FileInfo  \" unix SUCCESS \"\" eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/faulty-checksum1k -m | grep nrep=1\"\nruntest \"### UploadCks \" unix SUCCESS \"\" upload    \"$TESTSYSFILE0K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/good-checksum0k\" \"-ODeos.checksum=00000001\"\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm    \"/eos/$EOS_TEST_INSTANCE/test/instancetest/good-checksum0k\"\nruntest \"### UploadCks \" unix ERROR \"\" upload    \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/faulty-checksum1k\" \"-ODeos.checksum=00000000\"\nruntest \"### FileInfo  \" unix ERROR \"\" eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/faulty-checksum1k -m | grep nrep=0\"\nruntest \"### Rem       \" unix IGNORE  \"\" eos rm    \"/eos/$EOS_TEST_INSTANCE/test/instancetest/faulty-checksum1k\"\nruntest \"### UploadCks \" unix ERROR \"\" upload    \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/faulty-checksum1k\" \"-ODeos.checksum=00000000\"\nruntest \"### FileInfo  \" unix ERROR \"\" eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/faulty-checksum1k -m | grep nrep=0\"\nruntest \"### Rem       \" unix IGNORE  \"\" eos rm    \"/eos/$EOS_TEST_INSTANCE/test/instancetest/faulty-checksum1k\"\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr rm sys.forced.checksum /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### UploadSz  \" unix SUCCESS \"\" upload    \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/faulty-size1k\" \"-ODeos.targetsize=1024\"\nruntest \"### FileInfo  \" unix SUCCESS \"\" eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/faulty-size1k -m | grep nrep=1\"\nruntest \"### UploadSz  \" unix ERROR \"\" upload    \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/faulty-size1k\" \"-ODeos.targetsize=1000\"\nruntest \"### FileInfo  \" unix ERROR \"\" eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/faulty-size1k -m | grep nrep=0\"\nruntest \"### Rem       \" unix IGNORE  \"\" eos rm    \"/eos/$EOS_TEST_INSTANCE/test/instancetest/faulty-size1k\"\n\n# --------------------------\n# Alternative Space test\n# --------------------------\nruntest \"### MkSpace   \" unix SUCCESS \"\" eos \"space define nothing\"\nruntest \"### SetSpace  \" unix SUCCESS \"\" eos \"space set nothing on \"\nruntest \"### AltSpace  \" unix SUCCESS \"\" eos \"space config nothing space.policy.altspaces=default\"\nruntest \"### AltSpace  \" unix SUCCESS \"\" eos \"space config default space.policy.space=nothing\"\nruntest \"### AltSpace  \" unix SUCCESS \"\" eos \"space config default space.policy.nstripes=1\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos \"mkdir /eos/$EOS_TEST_INSTANCE/test/instancetest/forced-altspace\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos \"mkdir /eos/$EOS_TEST_INSTANCE/test/instancetest/default-altspace\"\nruntest \"### Force     \" unix SUCCESS \"\" eos \"attr set sys.forced.space=nothing /eos/$EOS_TEST_INSTANCE/test/instancetest/forced-altspace\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload   \"$TESTSYSFILE1M\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/forced-altspace/1M\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload   \"$TESTSYSFILE1M\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/default-altspace/1M\"\nruntest \"### RmSpace   \" unix SUCCESS \"\" eos \"space rm nothing\"\nruntest \"### AltSpace  \" unix SUCCESS \"\" eos \"space config default space.policy.space=default\"\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm -r \"/eos/$EOS_TEST_INSTANCE/test/instancetest/forced-altspace\"\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm -r \"/eos/$EOS_TEST_INSTANCE/test/instancetest/default-altspace\"\n#-------------------------------------------------------------------------------\n# Verify sys.owner.auth behaviour\n#-------------------------------------------------------------------------------\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos \"mkdir /eos/$EOS_TEST_INSTANCE/test/instancetest/owner-auth/\"\nruntest \"### Attr      \" unix SUCCESS \"\" eos \"attr set sys.owner.auth=* /eos/$EOS_TEST_INSTANCE/test/instancetest/owner-auth\"\nruntest \"### Chown     \" unix SUCCESS \"\" eos chown 2000:2001 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/owner-auth\"\nruntest \"### Upload-L3 \" unix SUCCESS \"\" upload   \"/etc/group\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/owner-auth/l1/l2/l3/f1\"\nruntest \"### Vfy-Uid   \" unix SUCCESS \"\" eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/owner-auth/l1/l2/l3/f1 -m | grep uid=2000\"\nruntest \"### Vfy-Gid   \" unix SUCCESS \"\" eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/owner-auth/l1/l2/l3/f1 -m | grep gid=2001\"\nruntest \"### Upload-L1 \" unix SUCCESS \"\" upload   \"/etc/group\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/owner-auth/f1\"\nruntest \"### Vfy-Uid   \" unix SUCCESS \"\" eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/owner-auth/f1 -m | grep uid=2000\"\nruntest \"### Vfy-Gid   \" unix SUCCESS \"\" eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/owner-auth/f1 -m | grep gid=2001\"\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm -r \"/eos/$EOS_TEST_INSTANCE/test/instancetest/owner-auth/\"\n# Make sure permission checks are also applied higher up in the hierarchy when\n# the direct parent of the path does not exist\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/auth/\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 700 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/auth/\"\nruntest \"### Chown     \" unix SUCCESS \"\" eos chown daemon:daemon \"/eos/$EOS_TEST_INSTANCE/test/instancetest/auth/\"\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.owner.auth=\"*\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/auth\"\nruntest \"### Mkdir     \" unix ERROR \"EOSROLE='-r 3 4'\" eos mkdir -p \"/eos/$EOS_TEST_INSTANCE/test/instancetest/auth/d1/d2\"\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm -r \"/eos/$EOS_TEST_INSTANCE/test/instancetest/auth/\"\n\n################################################################################\n# VERSION\n################################################################################\nruntest \"### Version   \" unix SUCCESS \"\" eos \"version | grep EOS_INSTANCE\"\nruntest \"### Version   \" unix SUCCESS \"\" eos \"version | grep EOS_CLIENT_VERSION\"\nruntest \"### Version   \" unix SUCCESS \"\" eos \"version | grep EOS_SERVER_VERSION\"\nruntest \"### Version   \" unix SUCCESS \"\" eos \"version -m | grep xrootd.version\"\nruntest \"### Tui Ver   \" unix SUCCESS \"\" eos \"tui --version\"\n################################################################################\n# COMMENT\n################################################################################\nruntest \"### Comment         \" unix SUCCESS \"\" eos \"whoami --comment \\\"Hello World\\\"\"\nruntest \"### Comment         \" unix SUCCESS \"\" shell grep $EOSLOGBOOK -e \\\"Hello World\\\"\nruntest \"### Comment Proto   \" unix SUCCESS \"\" eos \"fs ls --comment \\\"Hello Proto World\\\"\"\nruntest \"### Comment Proto   \" unix SUCCESS \"\" shell grep $EOSLOGBOOK -e \\\"Hello Proto World\\\"\n################################################################################\n# ACCESS\n################################################################################\n# Access ban\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access ban user nobody\"\n# takes long\n#runtest \"### Download  \" unix SUCCESS \"\" download  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd\"\n#runtest \"### Download  \" unix ERROR \"\" download  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd\" \"-OSeos.ruid=99\" \"-DITransactionTimeout 1\"\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access ls | grep nobody\"\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access unban user nobody\"\nruntest \"### Access    \" unix ERROR \"\" eos \"access ls | grep nobody\"\n# Access stall\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access set stall 1 r\"\nruntest \"### Download  \" unix SUCCESS \"\" download  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd\"\nruntest \"### Download  \" unix ERROR \"\" eosssh-timeout -t 3 download  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd\" \"-OSeos.ruid=${NOBODY_UID}\"\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access ls | grep \\\"r:\\* => 1\\\"\"\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access rm stall r\"\nruntest \"### Access    \" unix ERROR \"\" eos \"access ls | grep \\\"r:\\* => 1\\\"\"\n# Access redirect\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access set redirect foo.senf\"\nruntest \"### Download  \" unix ERROR \"\" downloadh  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd\" \"-DIFirstConnectTimeout 2 -DIConnectTimeout 2 -DIReconnectWait 1 -DIMaxRedirectCnt 1\"\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access ls | grep foo\"\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access rm redirect\"\nruntest \"### Access    \" unix ERROR \"\" eos \"access ls | grep foo\"\nruntest \"### Rm        \" unix SUCCESS \"\" eos \"rm /eos/$EOS_TEST_INSTANCE/test/instancetest/passwd\"\n# Access whitelist domain\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access allow user bin\"\nruntest \"### Ls        \" unix SUCCESS \"EOSROLE='-r 1 1'\" eos ls /eos/$EOS_TEST_INSTANCE/\nruntest \"### Ls        \" unix ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos ls /eos/$EOS_TEST_INSTANCE/\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access ls | grep bin\"\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access unallow user bin\"\nruntest \"### Ls        \" unix SUCCESS \"EOSROLE='-r 1 1'\" eos ls /eos/$EOS_TEST_INSTANCE/test\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access allow user bin\"\nruntest \"### Ls        \" unix ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos ls /eos/$EOS_TEST_INSTANCE/\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access allow domain nobody@localdomain\"\nruntest \"### Ls        \" unix ERROR shell runuser -u nobody eos ls /eos/$EOS_TEST_INSTANCE/\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access allow domain -\"\nruntest \"### Ls        \" unix SUCCESS shell runuser -u nobody eos ls /eos/$EOS_TEST_INSTANCE/\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access ls\"\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access unallow user bin\"\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access unallow domain -\"\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access unallow domain nobody@localdomain\"\n# root, admins and sudoers only can `eos access`\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access allow user nobody\"\nruntest \"### Access    \" unix ERROR shell runuser -u nobody eos access ls\nruntest \"### Access    \" unix SUCCESS \"\" eos \"access unallow user nobody\"\n\n################################################################################\n# Quota Tests\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"quota\"\n# ------------------------------------------------------------------------------\ncommand eos -b space status default | grep quota | grep on >& /dev/null 2>&1\nif [ $? -eq 0 ]; then\n# --------------------------\n# if quota is enabled\n# --------------------------\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod \"777\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest\"\nruntest \"### Quota     \" unix SUCCESS \"\" eos quota set -u ${NOBODY_UID} -p /eos/$EOS_TEST_INSTANCE/test -v 1T -i 1\nruntest \"### Upload    \" unix SUCCESS \"\" upload \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd.quota\" \"-ODeos.ruid=${NOBODY_UID}\" \"-DITransactionTimeout 30\"\nruntest \"### Quota     \" unix SUCCESS \"\" eos quota set -u ${NOBODY_UID} -p /eos/$EOS_TEST_INSTANCE/test -v 1T -i 1M\nruntest \"### Upload    \" unix SUCCESS \"\" upload \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd.quota\" \"-ODeos.ruid=${NOBODY_UID}\" \"-DITransactionTimeout 30\"\nruntest \"### Rem       \" unix IGNORE  \"\" eos rm    \"/eos/$EOS_TEST_INSTANCE/test/instancetest/passwd.quota\"\nruntest \"### Quota     \" unix SUCCESS \"\" eos quota set -u ${NOBODY_UID} -p /eos/$EOS_TEST_INSTANCE/test -v 1T -i 1M\nruntest \"### Quota     \" unix ERROR \"\" eos quota set -u ${NOBODY_UID} -p /doesnotexist -v 1T -i 1M\nruntest \"### Quota     \" unix SUCCESS \"\" eos quota set -u 3 -p /eos/$EOS_TEST_INSTANCE/test -v 1T -i 1M\nruntest \"### Upload    \" unix SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/1k\" \"-ODeos.ruid=3\" \"-DITransactionTimeout 30\"\nruntest \"### QUpdate   \" unix SUCCESS \"\" eos_quota \"/eos/$EOS_TEST_INSTANCE/test/\" \"uid=adm\"\nruntest \"### Rem       \" unix IGNORE  \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/1k\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir /eos/$EOS_TEST_INSTANCE/test/testproj\nruntest \"### Quota     \" unix SUCCESS \"\" eos quota set -g 99 -p /eos/$EOS_TEST_INSTANCE/test/testproj -v 1T -i 1M\nruntest \"### Upload    \" unix SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/testproj/1k\" \"-ODeos.ruid=3\"\nruntest \"### QUpdate   \" unix SUCCESS \"\" eos quota ls -m \"/eos/$EOS_TEST_INSTANCE/test/testproj\" | grep \"gid=project space=/eos/$EOS_TEST_INSTANCE/test/testproj\"\nruntest \"### Rem       \" unix IGNORE  \"\" eos rm  \"/eos/$EOS_TEST_INSTANCE/test/testproj/1k\"\nruntest \"### RmdQuota  \" unix SUCCESS \"\" eos quota rmnode --really-want -p \"/eos/$EOS_TEST_INSTANCE/test/testproj/\"\nruntest \"### Rmdir     \" unix SUCCESS \"\" eos rmdir \"/eos/$EOS_TEST_INSTANCE/test/testproj/\"\nfi\n################################################################################\n# RECYCLE BIN TEST\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"recycle\"\n# ------------------------------------------------------------------------------\nrecyclesize=`command eos -b $EOSROLE root://$EOS_TEST_REDIRECTOR recycle -m | grep maxbytes | awk '{print $3}' | sed s/maxbytes=//`\nrecycletime=`command eos -b $EOSROLE root://$EOS_TEST_REDIRECTOR recycle -m | grep lifetime | awk '{print $6}' | sed s/lifetime=//`\ntest -z $recyclesize && recyclesize=0\ntest -z $recycletime && recycletime=0\n\nif [ $recyclesize -eq 0 ] ; then\nruntest \"### Recycle   \" unix SUCCESS \"\" eos quota set -g 99 -v 100G -i 10M /eos/$EOS_TEST_INSTANCE/proc/recycle/\nfi\n\nif [ $recycletime -eq 0 ]; then\nruntest \"### Recycle   \" unix SUCCESS \"\" eos recycle config --lifetime 86400\nfi\n\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod \"777\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"-p\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/recycle\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod \"777\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/recycle\"\nruntest \"### Recycle   \" unix SUCCESS \"\" eos recycle config --add-bin /eos/$EOS_TEST_INSTANCE/test/instancetest/recycle\nruntest \"### Upload    \" unix SUCCESS \"\" upload \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/recycle/passwd.recycle\"\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/recycle/passwd.recycle\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -f /eos/$EOS_TEST_INSTANCE/proc/recycle/ | grep passwd.recycle | wc -l |grep -w 1\nruntest \"### Recycle-Ls\" unix SUCCESS \"\" eos recycle ls | grep /eos/$EOS_TEST_INSTANCE/test/instancetest/recycle/passwd.recycle | wc -l | grep -w 1\nruntest \"### Restore   \" unix ERROR   \"\" eos recycle restore `command eos -b root://$EOS_TEST_REDIRECTOR recycle ls --all | grep /eos/$EOS_TEST_INSTANCE/test/instancetest/recycle/passwd.recycle | awk '{print $10}' | tail -1`\nruntest \"### Restore   \" unix SUCCESS \"EOSROLE='-r 3 4'\" eos recycle restore `command eos -b root://$EOS_TEST_REDIRECTOR recycle ls --all | grep /eos/$EOS_TEST_INSTANCE/test/instancetest/recycle/passwd.recycle | awk '{print $10}' | tail -1`\nruntest \"### Download  \" unix SUCCESS \"\" download  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/recycle/passwd.recycle\"\nrm -rf \"$EOS_TEST_TESTSYS/dumpit\"\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/recycle/passwd.recycle\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/recycle/passwd.recycle\"\nruntest \"### Restore   \" unix ERROR   \"EOSROLE='-r 3 4'\" eos recycle restore `command eos -b  root://$EOS_TEST_REDIRECTOR recycle ls --all | grep /eos/$EOS_TEST_INSTANCE/test/instancetest/recycle/passwd.recycle | awk '{print $10}' | tail -1`\nruntest \"### Restore   \" unix SUCCESS \"EOSROLE='-r 3 4'\" eos recycle restore -f `command eos -b root://$EOS_TEST_REDIRECTOR recycle ls --all | grep /eos/$EOS_TEST_INSTANCE/test/instancetest/recycle/passwd.recycle | awk '{print $10}' | tail -1`\n# Directory recycle restor\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"-p\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/recycle/dir\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/recycle/dir/passwd.recycle\"\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm -r \"/eos/$EOS_TEST_INSTANCE/test/instancetest/recycle/dir/\"\nruntest \"### List      \" unix 2       \"\" eos ls -l \"/eos/$EOS_TEST_INSTANCE/test/instancetest/recycle/dir\"\nruntest \"### Restore   \" unix SUCCESS   \"EOSROLE='-r 0 0'\" eos recycle restore `command eos -b  root://$EOS_TEST_REDIRECTOR recycle ls --all | grep /eos/$EOS_TEST_INSTANCE/test/instancetest/recycle/dir/ | awk '{print $10}' | tail -1`\nruntest \"### List      \" unix SUCCESS \"\" eos ls -l \"/eos/$EOS_TEST_INSTANCE/test/instancetest/recycle/dir\"\nruntest \"### Recycle   \" unix SUCCESS \"\" eos recycle config --lifetime 2d\nruntest \"### Recycle   \" unix SUCCESS \"\" eos recycle -m | grep -q \"lifetime=172800 \"\nruntest \"### Recycle   \" unix SUCCESS \"\" eos recycle config --remove-bin /eos/$EOS_TEST_INSTANCE/test/instancetest/recycle\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm -r /eos/$EOS_TEST_INSTANCE/test/instancetest/recycle\n# Test project recycle functionality\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"-p\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/precycle\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod \"777\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/precycle\"\nruntest \"### Recycle   \" unix SUCCESS \"\" eos recycle config --add-bin /eos/$EOS_TEST_INSTANCE/test/instancetest/precycle\nruntest \"### Setup     \" unix SUCCESS \"\" eos recycle project --path /eos/$EOS_TEST_INSTANCE/test/instancetest/precycle\nrid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo /eos/$EOS_TEST_INSTANCE/test/instancetest/precycle | jq -r .id)\nruntest \"### Upload    \" unix SUCCESS \"\" upload \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/precycle/pfile\"\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/precycle/pfile\"\nruntest \"### NoFile    \" unix SUCCESS \"\" eos recycle ls --rid | grep /eos/$EOS_TEST_INSTANCE/test/instancetest/precycle/pfile\nruntest \"### Restore   \" unix ERROR \"EOSROLE='-r 99 99'\" eos recycle restore `command eos -b root://$EOS_TEST_REDIRECTOR recycle ls --all | grep /eos/$EOS_TEST_INSTANCE/test/instancetest/precycle/pfile | awk '{print $10}' | tail -1`\nruntest \"### Restore   \" unix SUCCESS \"EOSROLE='-r 3 4'\" eos recycle restore `command eos -b root://$EOS_TEST_REDIRECTOR recycle ls --all | grep /eos/$EOS_TEST_INSTANCE/test/instancetest/precycle/pfile | awk '{print $10}' | tail -1`\nruntest \"### Download  \" unix SUCCESS \"\" download  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/precycle/pfile\"\nrm -rf \"$EOS_TEST_TESTSYS/dumpit\"\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/precycle/pfile\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/precycle/pfile\"\nruntest \"### Restore   \" unix ERROR \"EOSROLE='-r 3 4'\" eos recycle restore `command eos -b root://$EOS_TEST_REDIRECTOR recycle ls --all | grep /eos/$EOS_TEST_INSTANCE/test/instancetest/precycle/pfile | awk '{print $10}' | tail -1`\nruntest \"### Restore   \" unix SUCCESS \"EOSROLE='-r 3 4'\" eos recycle restore -f `command eos -b root://$EOS_TEST_REDIRECTOR recycle ls --all | grep /eos/$EOS_TEST_INSTANCE/test/instancetest/precycle/pfile | awk '{print $10}' | tail -1`\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/precycle/pfile\"\nruntest \"### Purge     \" unix SUCCESS \"EOSROLE='-r 3 4'\" eos recycle purge --rid $rid -k `command eos -b root://$EOS_TEST_REDIRECTOR recycle ls --all | grep /eos/$EOS_TEST_INSTANCE/test/instancetest/precycle/pfile | awk '{print $10}' | tail -1`\nruntest \"### NoFile    \" unix ERROR \"\" eos recycle ls --all | grep /eos/$EOS_TEST_INSTANCE/test/instancetest/precycle/pfile\nruntest \"### Recycle   \" unix SUCCESS \"\" eos recycle config --remove-bin /eos/$EOS_TEST_INSTANCE/test/instancetest/precycle\nruntest \"### Rem       \" unix SUCCESS \"\" eos rm -r /eos/$EOS_TEST_INSTANCE/test/instancetest/precycle\n\n################################################################################\n# STRESS\n################################################################################\n## -----------------------------------------------------------------------------\ncategorie=\"stress\"\n# ------------------------------------------------------------------------------\nruntest \"### Chmod      \" unix SUCCESS \"\" eos chmod \"777\" \"/eos/$EOS_TEST_INSTANCE/test/\"\nruntest \"### Attr Set   \" unix SUCCESS \"\" eos attr set default=replica \"/eos/$EOS_TEST_INSTANCE/test\"\nruntest \"### Stress Wpp \" unix SUCCESS \"\" stress \"-d root://localhost//eos/$EOS_TEST_INSTANCE/test -o wr -f 50 -s 1KB -n stress -j 10 -c -p\"\nruntest \"### Stress Wp  \" unix SUCCESS \"\" stress \"-d root://localhost//eos/$EOS_TEST_INSTANCE/test -o wr -f 50 -s 1KB -n stress -j 10 -c \"\nruntest \"### Find       \" unix SUCCESS \"\" eos find \"-f --count /eos/$EOS_TEST_INSTANCE/test/ | grep nfiles=100\"\nruntest \"### Stress Rp  \" unix SUCCESS \"\" stress \"-d root://localhost//eos/$EOS_TEST_INSTANCE/test -o rd -f 50 -s 1KB -n stress -j 10 -c\"\nruntest \"### Stress Rpp \" unix SUCCESS \"\" stress \"-d root://localhost//eos/$EOS_TEST_INSTANCE/test -o rd -f 50 -s 1KB -n stress -j 10 -c -p\"\nruntest \"### Cleanup    \" unix IGNORE  \"\" eos rm \"-r\" \"/eos/$EOS_TEST_INSTANCE/test/stress/*\"\nruntest \"### Ls         \" unix SUCCESS \"\" eos ls \"/eos/$EOS_TEST_INSTANCE/test | grep [a-z] | wc -l | grep -w 2\"\nruntest \"### Ls         \" unix SUCCESS \"\" eos ls -l \"/eos/$EOS_TEST_INSTANCE/test | grep ^d| wc -l | grep -w 2\"\nruntest \"### Ls         \" unix SUCCESS \"\" eos ls -la \"/eos/$EOS_TEST_INSTANCE/test | grep ^d| wc -l | grep -w 4\"\nruntest \"### Find       \" unix SUCCESS \"\" eos find -d \"/eos/$EOS_TEST_INSTANCE/test | grep eos | wc -l | grep -w 3\"\n\n################################################################################\n# ATTRIBUTES\n###################################################A############################\ncategorie=\"attr\"\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set default=replica \"/eos/$EOS_TEST_INSTANCE/test\"\nruntest \"### Attr Ls   \" unix SUCCESS \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test | grep sys | wc -l | grep -w 6\"\nruntest \"### Attr Get  \" unix SUCCESS \"\" eos attr get \"sys.forced.space /eos/$EOS_TEST_INSTANCE/test  | grep default\"\nruntest \"### Attr Rm   \" unix SUCCESS \"\" eos attr rm  \"sys.forced.blocksize /eos/$EOS_TEST_INSTANCE/test\"\nruntest \"### Attr Rm   \" unix SUCCESS \"\" eos attr rm  \"sys.forced.checksum /eos/$EOS_TEST_INSTANCE/test\"\nruntest \"### Attr Rm   \" unix SUCCESS \"\" eos attr rm  \"sys.forced.layout /eos/$EOS_TEST_INSTANCE/test\"\nruntest \"### Attr Rm   \" unix SUCCESS \"\" eos attr rm  \"sys.forced.nstripes /eos/$EOS_TEST_INSTANCE/test\"\nruntest \"### File Touch\" unix SUCCESS \"\" eos file touch \"/eos/$EOS_TEST_INSTANCE/test/file\"\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set sys.forced.atomic=1 \"/eos/$EOS_TEST_INSTANCE/test\"\nruntest \"### File Touch\" unix SUCCESS \"\" eos file touch \"/eos/$EOS_TEST_INSTANCE/test/file.atomic\"\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr rm sys.forced.atomic \"/eos/$EOS_TEST_INSTANCE/test\"\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/file.atomic\"\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set user.attr.test=foo \"/eos/$EOS_TEST_INSTANCE/test/file\"\nruntest \"### Attr Get  \" unix SUCCESS \"\" eos attr get user.attr.test \"/eos/$EOS_TEST_INSTANCE/test/file | grep foo\"\nruntest \"### Attr Get  \" unix ERROR \"\" eos attr get user.attr.bar \"/eos/$EOS_TEST_INSTANCE/test/file | grep foo\"\nruntest \"### Attr Ls   \" unix SUCCESS \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/file | grep foo\"\nruntest \"### Attr Rm   \" unix SUCCESS \"\" eos attr rm user.attr.test \"/eos/$EOS_TEST_INSTANCE/test/file\"\nruntest \"### Attr Rm   \" unix ERROR \"\" eos attr rm user.attr.test \"/eos/$EOS_TEST_INSTANCE/test/file\"\nruntest \"### SpaceAttr \" unix SUCCESS \"\" eos space config default space.attr.sys.foo=bar\nruntest \"### SpaceAttr \" unix SUCCESS \"\" eos attr get sys.foo \"/eos/$EOS_TEST_INSTANCE/test/  | grep bar\"\nruntest \"### SpaceAttr \" unix SUCCESS \"\" eos space config rm default space.attr.sys.foo\nruntest \"### SpaceAttr \" unix ERROR \"\" eos attr get sys.foo \"/eos/$EOS_TEST_INSTANCE/test/  | grep bar\"\n#----------------------------------------------------------------------------\n# Test JSON output for attributes\n#----------------------------------------------------------------------------\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set user.attr.json='value' \"/eos/$EOS_TEST_INSTANCE/test/file\"\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set user.attr.json2='2ndvalue' \"/eos/$EOS_TEST_INSTANCE/test/file\"\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set user.attr.json3='3' \"/eos/$EOS_TEST_INSTANCE/test/file\"\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set user.attr.json4='value\\\"quote' \"/eos/$EOS_TEST_INSTANCE/test/file\"\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set user.attr.json5='value1\\&value2\\&value3' \"/eos/$EOS_TEST_INSTANCE/test/file\"\nruntest \"### Attr Get  \" unix SUCCESS \"\" eosj attr get user.attr.json \"/eos/$EOS_TEST_INSTANCE/test/file | jq -r '.attr.get[].user.attr.json' | grep -w 'value'\"\nruntest \"### Attr Get  \" unix SUCCESS \"\" eosj attr get user.attr.json2 \"/eos/$EOS_TEST_INSTANCE/test/file | jq -r '.attr.get[].user.attr.json2' | grep -w '2ndvalue'\"\nruntest \"### Attr Get  \" unix SUCCESS \"\" eosj attr get user.attr.json3 \"/eos/$EOS_TEST_INSTANCE/test/file | jq -r '.attr.get[].user.attr.json3' | grep -w '3'\"\nruntest \"### Attr Get  \" unix SUCCESS \"\" eosj attr get user.attr.json4 \"/eos/$EOS_TEST_INSTANCE/test/file | jq -r '.attr.get[].user.attr.json4' | grep -w 'value\\\"quote'\"\nruntest \"### Attr Get  \" unix SUCCESS \"\" eosj attr get user.attr.json5 \"/eos/$EOS_TEST_INSTANCE/test/file | jq -r '.attr.get[].user.attr.json5' | grep -w 'value1\\&value2\\&value3'\"\nruntest \"### Attr Ls   \" unix ERROR \"\" eosj attr ls \"/eos/$EOS_TEST_INSTANCE/test/file | jq -r '.attr.ls[].user.attr.json5' | grep -w 'nullvalues'\"\nruntest \"### Attr Rm   \" unix SUCCESS \"\" eos attr rm user.attr.json \"/eos/$EOS_TEST_INSTANCE/test/file\"\nruntest \"### Attr Rm   \" unix SUCCESS \"\" eos attr rm user.attr.json2 \"/eos/$EOS_TEST_INSTANCE/test/file\"\nruntest \"### Attr Rm   \" unix SUCCESS \"\" eos attr rm user.attr.json3 \"/eos/$EOS_TEST_INSTANCE/test/file\"\nruntest \"### Attr Rm   \" unix SUCCESS \"\" eos attr rm user.attr.json4 \"/eos/$EOS_TEST_INSTANCE/test/file\"\nruntest \"### Attr Rm   \" unix SUCCESS \"\" eos attr rm user.attr.json5 \"/eos/$EOS_TEST_INSTANCE/test/file\"\n#----------------------------------------------------------------------------\n# Test fid|fxid|pid|pxid identifiers for attributes\n#----------------------------------------------------------------------------\nfid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo \"/eos/$EOS_TEST_INSTANCE/test/file\" | jq -r .id)\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set user.attr.key='value' \"fid:${fid}\"\nruntest \"### Attr Get  \" unix SUCCESS \"\" eos attr get user.attr.key \"fid:${fid} | grep -w 'value'\"\nruntest \"### Attr Get  \" unix SUCCESS \"\" eosj attr get user.attr.key \"fid:${fid} | jq -r '.attr.get[].user.attr.key' | grep -w 'value'\"\nruntest \"### Attr Rm   \" unix SUCCESS \"\" eos attr rm user.attr.key \"fid:${fid}\"\nruntest \"### Attr Ls   \" unix SUCCESS \"\" eos attr ls \"fid:${fid} | wc -l | grep -w 4\"\nruntest \"### Attr Get  \" unix ERROR \"\" eos attr get user.attr.key \"fid:invalid\"\nruntest \"### Attr Get  \" unix ERROR \"\" eos attr get user.attr.key \"fxid:123456\"\nruntest \"### Attr Get  \" unix ERROR \"\" eos attr get user.attr.key \"pxid:\"\nruntest \"### Rm        \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/file\"\n\n#-------------------------------------------------------------------------------\n# Xattr locks\n#-------------------------------------------------------------------------------\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos \"mkdir /eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod \"777\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock\"\nruntest \"### File Lck  \" unix SUCCESS \"EOSAPP='-a app1' EOSROLE='-r 2 2'\" eos file touch -l \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1\"\nruntest \"### File Lck  \" unix SUCCESS \"EOSAPP='-a app1' EOSROLE='-r 2 2'\" eos file touch -l \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1\"\nruntest \"### File Lck  \" unix ERROR \"EOSAPP='-a app2' EOSROLE='-r 2 2'\" eos file touch -l \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1\"\nruntest \"### eoscp     \" unix ERROR  \"EOSROLE='-r 2 2'\" eos cp /eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1 /dev/null\nruntest \"### eoscp     \" unix SUCCESS  \"EOSAPP='-a app1' EOSROLE='-r 2 2'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1\" /dev/null\nruntest \"### eoscp     \" unix ERROR  \"EOSAPP='-a app2' EOSROLE='-r 2 2'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1\" /dev/null\nruntest \"### File ULck \" unix ERROR \"EOSAPP='-a app2' EOSROLE='-r 2 2'\" eos file touch -u \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1\"\nruntest \"### File ULck \" unix SUCCESS \"EOSAPP='-a app1' EOSROLE='-r 2 2'\" eos file touch -u \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1\"\nruntest \"### File Lck  \" unix SUCCESS \"EOSAPP='-a app1' EOSROLE='-r 2 2'\" eos file touch -l \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1\" 0\nruntest \"### Delay     \" unix SUCCESS \"\" delay 1\nruntest \"### File Lck  \" unix SUCCESS \"EOSAPP='-a app2' EOSROLE='-r 2 2'\" eos file touch -l \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1\"\nruntest \"### File Lck  \" unix ERROR \"EOSAPP='-a app1' EOSROLE='-r 2 2'\" eos file touch -l \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1\"\nruntest \"### File Lck  \" unix SUCCESS \"EOSAPP='-a app2' EOSROLE='-r 2 2'\" eos file touch -u \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1\"\n# user audience\nruntest \"### File Lck  \" unix SUCCESS \"EOSAPP='-a app1' EOSROLE='-r 2 2'\" eos file touch -l \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1 1000 user\"\nruntest \"### eoscp     \" unix SUCCESS  \"EOSAPP='-a app2' EOSROLE='-r 2 2'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1\" /dev/null\nruntest \"### eoscp     \" unix ERROR  \"EOSAPP='-a app2' EOSROLE='-r 3 3'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1\" /dev/null\n# app audience\nruntest \"### File Lck  \" unix SUCCESS \"EOSAPP='-a app1' EOSROLE='-r 2 2'\" eos file touch -l \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1 1000 app\"\nruntest \"### eoscp     \" unix ERROR  \"EOSAPP='-a app2' EOSROLE='-r 2 2'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1\" /dev/null\nruntest \"### eoscp     \" unix SUCCESS  \"EOSAPP='-a app1' EOSROLE='-r 2 2'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1\" /dev/null\nruntest \"### eoscp     \" unix SUCCESS  \"EOSAPP='-a app1' EOSROLE='-r 3 3'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1\" /dev/null\nruntest \"### Rm        \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock/lock1\"\nruntest \"### Rmdir     \" unix SUCCESS \"\" eos rmdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/xattr-lock\"\n\n################################################################################\n# FILEINFO\n###################################################A############################\ncategorie=\"fileinfo\"\nruntest \"### Mkdir      \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/\"\nruntest \"### Rm_recycle \" unit 0 \"\" eos recycle config --remove-bin \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo\"\nruntest \"### Mkdir      \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/dirA/\"\nruntest \"### Mkdir      \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/dirB/\"\nruntest \"### Chmod      \" unix SUCCESS \"\" eos chmod -r \"777\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/\"\nruntest \"### Attr Set   \" unix SUCCESS \"\" eos attr set default=replica \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/dirA/\"\nruntest \"### Attr Set   \" unix SUCCESS \"\" eos attr set user.attr.key='value' \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/dirB/\"\nruntest \"### Upload     \" unix SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/file\\&symbol\"\nruntest \"### Upload     \" unix SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/dirA/fileA\"\n#----------------------------------------------------------------------------\n# Test basic functionality (including JSON)\n#----------------------------------------------------------------------------\nruntest \"### Fileinfo   \" unix SUCCESS \"\" eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/ -m | grep container=2 | grep files=1\"\nruntest \"### Fileinfo   \" unix SUCCESS \"\" eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/dirB/ -m | grep 'xattrn=user.attr.key' | grep xattrv=value\"\nruntest \"### Fileinfo   \" unix SUCCESS \"\" eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/dirA/fileA -m | grep nrep=2\"\nruntest \"### Fileinfo   \" unix SUCCESS \"\" eosj fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/file\\&symbol | jq -r '.name' | grep -w 'file\\&symbol'\"\nruntest \"### Fileinfo   \" unix SUCCESS \"\" eosj fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/dirB/ | jq -r '.xattr' | grep -w 'user.attr.key' | grep -w value\"\nruntest \"### Fileinfo   \" unix SUCCESS \"\" eosj fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/dirA/fileA -m | jq -r '.nstripes' | grep -w 2\"\n#----------------------------------------------------------------------------\n# Test fid|fxid|pid|pxid identifiers\n#----------------------------------------------------------------------------\npid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/\" | jq -r .id)\npxid=$(printf \"%x\" $pid)\nfid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/file&symbol\" | jq -r .id)\nfxid=$(printf \"%x\" $fid)\n#----------------------------------------------------------------------------\nruntest \"### Fileinfo   \" unix SUCCESS \"\" eosj fileinfo \"pid:${pid} | jq -r '.name' | grep -w fileinfo\"\nruntest \"### Fileinfo   \" unix SUCCESS \"\" eosj fileinfo \"pxid:${pxid} | jq -r '.name' | grep -w fileinfo\"\nruntest \"### Fileinfo   \" unix SUCCESS \"\" eosj fileinfo \"fid:${fid} | jq -r '.name' | grep -w 'file\\&symbol'\"\nruntest \"### Fileinfo   \" unix SUCCESS \"\" eosj fileinfo \"fxid:${fxid} | jq -r '.name' | grep -w 'file\\&symbol'\"\n#----------------------------------------------------------------------------\nruntest \"### Rm         \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/file\\&symbol\"\nruntest \"### Rm         \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/dirA/fileA\"\nruntest \"### Rmdir      \" unix SUCCESS \"\" eos rmdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/dirA/\"\nruntest \"### Rmdir      \" unix SUCCESS \"\" eos rmdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/dirB/\"\n#----------------------------------------------------------------------------\n# Test detached flag\n#----------------------------------------------------------------------------\nruntest \"### Upload     \" unix SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/file1\"\nruntest \"### Upload     \" unix SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/file2\"\nruntest \"### Fileinfo   \" unix SUCCESS \"\" eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/file1 -m | grep -w detached=0\"\nruntest \"### Fileinfo   \" unix SUCCESS \"\" eosj fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/file1 | jq -r '.detached' | grep false\"\n#----------------------------------------------------------------------------\nfid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/file1\" | jq -r .id)\nruntest \"### Rm         \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/file1\"\n# The complete deletion of the file could happen by the time we issue the next command\n# so the file info command will file with \"found deletion tombstone\" error. The test is\n# still considered successful in this case.\nruntest \"### Fileinfo   \" unix SUCCESS \"\" eos fileinfo \"fid:${fid} -m 2>&1 | grep -w 'detached=1\\|tombstone'\"\n#----------------------------------------------------------------------------\nfid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/file2\" | jq -r .id)\nruntest \"### Rm         \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/file2\"\nruntest \"### Fileinfo   \" unix SUCCESS \"\" eosj fileinfo \"fid:${fid} | jq -r '.detached' | grep true\"\n#----------------------------------------------------------------------------\nruntest \"### Rmdir      \" unix SUCCESS \"\" eos rmdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fileinfo/\"\n\n################################################################################\n# Find - using the protobuf implementation\n################################################################################\ncategorie=\"find\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/a\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/b\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/c\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos touch \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/a.txt\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos touch \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/b.txt\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos touch \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/c.txt\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod \"750\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/c\"\nruntest \"### Chown     \" unix SUCCESS \"\" eos chown 0:0 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest\"\nruntest \"### Chown     \" unix SUCCESS \"\" eos chown 0:0 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/a\"\nruntest \"### Chown     \" unix SUCCESS \"\" eos chown 0:0 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/b\"\nruntest \"### Chown     \" unix SUCCESS \"\" eos chown ${NOBODY_UID}:${NOBODY_GID} \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/c\"\nruntest \"### Chown     \" unix SUCCESS \"\" eos chown 0:0 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/a.txt\"\nruntest \"### Chown     \" unix SUCCESS \"\" eos chown 0:0 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/b.txt\"\nruntest \"### Chown     \" unix SUCCESS \"\" eos chown ${NOBODY_UID}:${NOBODY_GID} \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/c.txt\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -d -flag 750 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 1\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -d -nflag 750 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 3\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -d -uid ${NOBODY_UID} \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 1\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -d -nuid ${NOBODY_UID} \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 3\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -d -gid ${NOBODY_GID} \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 1\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -d -ngid ${NOBODY_GID} \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 3\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -f -uid ${NOBODY_UID} \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 1\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -f -nuid ${NOBODY_UID} \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 2\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -f -gid ${NOBODY_GID} \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 1\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -f -ngid ${NOBODY_GID} \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 2\"\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set sys.test=abc \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/a.txt\"\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set sys.test=def \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/b.txt\"\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set sys.test=def \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/c.txt\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -f -x sys.test=abc \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 1\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/links/\"\nruntest \"### Symlink   \" unix SUCCESS \"\" eos file symlink \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/links/a.ln\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/a.txt\"\nruntest \"### Symlink   \" unix SUCCESS \"\" eos file symlink -f \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/links/a.ln\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/a.txt\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/links/ | grep -w \\\"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/links/a.ln -> /eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/a.txt\\\"\"\nruntest \"### Rm        \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/links/a.ln\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload   \"$TESTSYSFILE1M\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/f1\" \"-ODeos.ruid=0\"\nruntest \"### Delay     \" unix SUCCESS \"\" delay 7\nruntest \"### Du -s     \" unix SUCCESS \"\" eos du -s \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/ | grep 1024000\"\nruntest \"### Du -s     \" unix SUCCESS \"\" eos du -s -h \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/ | grep 1.02 | grep MB\"\nruntest \"### Du -s     \" unix SUCCESS \"\" eos du -s -h --si \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/ | grep 1000.00 | grep kiB\"\nruntest \"### Rm        \" unix SUCCESS \"\" eos rm -rF \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest\"\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set sys.versioning=3 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/a.txt\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/b.txt\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/c.txt\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/c.txt\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/c.txt\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest/c.txt\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -d \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 2\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -d --skip-version-dirs \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 1\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -d --cache --skip-version-dirs \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 1\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -f \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 6\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -f --skip-version-dirs \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 3\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find -f --cache --skip-version-dirs \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 3\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 8\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find --skip-version-dirs \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 4\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find --cache --skip-version-dirs \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest | wc -l | grep -w 4\"\nruntest \"### Rm        \" unix SUCCESS \"\" eos rm -rF \"/eos/$EOS_TEST_INSTANCE/test/instancetest/findtest\"\n################################################################################\n# WFE\n################################################################################\ncategorie=\"wfe\"\nruntest \"### Space     \" unix SUCCESS \"\" eos space config \"default space.wfe=on\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/wfe\"\nruntest \"### Touch     \" unix SUCCESS \"\" eos touch \"/eos/$EOS_TEST_INSTANCE/test/instancetest/wfe/test1.dat\"\nruntest \"### Prepare   \" unix SUCCESS \"\" meta prepare \"/eos/$EOS_TEST_INSTANCE/test/instancetest/wfe/test1.dat\\?eos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID} | wc -l | grep -w 0\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod \"755\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/wfe\"\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.acl=u:${NOBODY_UID}:p \"/eos/$EOS_TEST_INSTANCE/test/instancetest/wfe\"\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.workflow.sync::prepare.default=test \"/eos/$EOS_TEST_INSTANCE/test/instancetest/wfe\"\nruntest \"### Prepare   \" unix SUCCESS \"\" meta prepare \"/eos/$EOS_TEST_INSTANCE/test/instancetest/wfe/test1.dat\\?eos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod \"777\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/wfe\"\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.acl=u:${NOBODY_UID}:p \"/eos/$EOS_TEST_INSTANCE/test/instancetest/wfe\"\nruntest \"### Prepare   \" unix SUCCESS \"\" meta prepare \"/eos/$EOS_TEST_INSTANCE/test/instancetest/wfe/test1.dat\\?eos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr rm sys.acl \"/eos/$EOS_TEST_INSTANCE/test/instancetest/wfe\"\nruntest \"### Prepare   \" unix SUCCESS \"\" meta prepare \"/eos/$EOS_TEST_INSTANCE/test/instancetest/wfe/test1.dat\\?eos.ruid=0\\&eos.rgid=0\"\nruntest \"### Space     \" unix SUCCESS \"\" eos space config \"default space.wfe=off\"\n\n################################################################################\n# ACL\n###################################################A############################\ncategorie=\"acl\"\nruntest \"### Mkdir   \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### Mkdir   \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/acl/dir1\"\nruntest \"### Mkdir   \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/acl/dir1/dir2\"\nruntest \"### Acl set \" unix SUCCESS \"\" eos acl --recursive u:daemon=rwx \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### Acl set \" unix SUCCESS \"\" eos acl --recursive u:${NOBODY_UID}=rwx \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### Acl ls  \" unix SUCCESS \"\" \"eos attr -r ls \\\"/eos/$EOS_TEST_INSTANCE/test/acl\\\" | grep -o -c 'sys.acl=\\\"u:2:rwx,u:65534:rwx\\\"' | grep '3'\"\nruntest \"### Acl ls  \" unix SUCCESS \"\" eos attr -r rm sys.acl \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### RmDir   \" unix SUCCESS \"\" eos rmdir \"/eos/$EOS_TEST_INSTANCE/test/acl/dir1/dir2\"\nruntest \"### RmDir   \" unix SUCCESS \"\" eos rmdir \"/eos/$EOS_TEST_INSTANCE/test/acl/dir1/\"\nruntest \"### Acl set \" unix ERROR \"\" eos acl u:daemon=\\!x /eos/does_not_exist\nruntest \"### Acl set \" unix ERROR \"\" eos acl --recursive u:daemon=\\!x /eos/does_not_exist\nruntest \"### Acl ls  \" unix ERROR \"\" eos acl -l \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### Acl set \" unix SUCCESS \"\" eos acl u:daemon=rwx \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### Acl ls  \" unix SUCCESS \"\" eos acl -l \"/eos/$EOS_TEST_INSTANCE/test/acl\" | grep \"u:daemon:rwx\"\nruntest \"### Acl upd \" unix SUCCESS \"\" eos acl u:daemon:+\\!d+\\!u+q+c \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### Acl ls  \" unix SUCCESS \"\" eos acl -l \"/eos/$EOS_TEST_INSTANCE/test/acl\" | grep \"u:daemon:rwx!d!uqc\"\nruntest \"### Acl upd \" unix SUCCESS \"\" eos acl u:daemon:-x-\\!d-\\!u-c \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### Acl ls  \" unix SUCCESS \"\" eos acl -l \"/eos/$EOS_TEST_INSTANCE/test/acl\" | grep \"u:daemon:rwq\"\nruntest \"### Acl set \" unix SUCCESS \"\" eos acl u:daemon=rw+d+u \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### Acl ls  \" unix SUCCESS \"\" eos acl -l \"/eos/$EOS_TEST_INSTANCE/test/acl\" | grep \"u:daemon:rw+d+u\"\nruntest \"### Acl upd \" unix SUCCESS \"\" eos acl u:daemon:-r-w-+d-+u \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### Acl ls  \" unix ERROR \"\" eos acl -l \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### dir ls  \" unix SUCCESS \"EOSROLE='-r daemon daemon'\" eos ls \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### Acl deny ls\" unix SUCCESS \"\" eos acl u:daemon=\\!x \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### dir ls  \" unix ERROR \"EOSROLE='-r daemon daemon'\" eos ls \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### Acl set \" unix SUCCESS \"\" eos acl u:daemon=rwx \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### dir ls  \" unix SUCCESS \"EOSROLE='-r daemon daemon'\" eos ls \"/eos/$EOS_TEST_INSTANCE/test/acl\"\n# Test the fact that both directory and file level sys.acl are taken into account\nruntest \"### Rmdir   \" unix SUCCESS \"\" eos rmdir \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### Mkdir   \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### Chown   \" unix SUCCESS \"\" eos chown 2:2   \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### Chmod   \" unix SUCCESS \"\" eos chmod \"700\" \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### Upload  \" unix SUCCESS \"EOSROLE='-r 2 2'\"   eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/acl/file.dat\"\nruntest \"### Download\" unix SUCCESS \"EOSROLE='-r 2 2'\"   eos cp \"/eos/$EOS_TEST_INSTANCE/test/acl/file.dat\" -\nruntest \"### Download\" unix ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/acl/file.dat\" -\nruntest \"### Acl set \" unix SUCCESS \"\" eos acl u:${NOBODY_UID}=rw \"/eos/$EOS_TEST_INSTANCE/test/acl\"\nruntest \"### Download\" unix SUCCESS \"EOSROLE='-r  ${NOBODY_UID}  ${NOBODY_GID}'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/acl/file.dat\" -\nruntest \"### Download\" unix ERROR \"EOSROLE='-r 985 979'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/acl/file.dat\" -\nruntest \"### Acl set \" unix SUCCESS \"\" eos acl u:985=rw \"/eos/$EOS_TEST_INSTANCE/test/acl/file.dat\"\nruntest \"### Download\" unix SUCCESS \"EOSROLE='-r 985 970'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/acl/file.dat\" -\nruntest \"### Download\" unix SUCCESS \"EOSROLE='-r  ${NOBODY_UID}  ${NOBODY_GID}'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/acl/file.dat\" -\n# Test the file rename ACL !d\nruntest \"### Mkdir   \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src\"\nruntest \"### Mkdir   \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_dst\"\nruntest \"### Acl set \" sss 0 \"\" eos acl u:2=rwx!d \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src\"\nruntest \"### Acl set \" sss 0 \"\" eos acl u:2=rwx!d \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_dst\"\nruntest \"### Upload  \" unix SUCCESS \"EOSROLE='-r 2 2'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/uid2.dat\"\nruntest \"### Upload  \" unix SUCCESS \"EOSROLE='-r 2 2'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/nobody.dat\"\nruntest \"### Chown   \" unix SUCCESS  \"\" eos chown \"${NOBODY_UID}:${NOBODY_GID}\" \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/nobody.dat\"\n# uid 2 should be able to move their file despite the !d permission as it is their file\nruntest \"### Move    \" unix SUCCESS \"EOSROLE='-r 2 2'\" eos file rename \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/uid2.dat\" \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_dst/uid2.dat\"\n# uid 2 should not be able to move the NOBODY file as they don't own it\nruntest \"### Move    \" unix ERROR \"EOSROLE='-r 2 2'\" eos file rename \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/nobody.dat\" \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_dst/nobody.dat\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos space config default space.attr.sys.acl=u:1000:rwx\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/ | grep u:1000:rwx\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos attr get sys.acl \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/ | grep u:1000:rwx\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos space config default \"space.attr.sys.acl=\\|u:1000:rwx\"\nruntest \"### SpaceACL\" unix ERROR \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/ | grep u:1000:rwx\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos space config default \"space.attr.sys.acl=\\<u:1000:rwx\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/ | grep -F 'u:1000:rwx,u:65534:rw,u:2:rwx!d'\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos space config default \"space.attr.sys.acl=\\>u:1000:rwx\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/ | grep -F 'u:65534:rw,u:2:rwx!d,u:1000:rwx'\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos space config rm default \"space.attr.sys.acl\"\n# Test that if directory already has the space acl then we don't see duplicates\nruntest \"### SpaceACL\" sss SUCCESS \"\" eos acl u:1000=rx \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/ | grep -F 'u:65534:rw,u:2:rwx!d,u:1000:rx'\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos space config default space.attr.sys.acl=u:1000:rx\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/ | grep u:1000:rx | grep -v u:65534:rw\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos space config default \"space.attr.sys.acl=\\|u:1000:rx\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/ | grep u:1000:rx | grep u:65534:rw\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos space config default \"space.attr.sys.acl=\\<u:1000:rx\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/ | grep -F 'u:1000:rx,u:65534:rw,u:2:rwx!d'\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos space config default \"space.attr.sys.acl=\\>u:1000:rx\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/ | grep -F 'u:65534:rw,u:2:rwx!d,u:1000:rx'\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos space config rm default \"space.attr.sys.acl\"\nruntest \"### SpaceACL\" unix SUCCESS \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/acl/nobody_src/ | grep u:1000:rx\"\nruntest \"### Rmdir   \" unix SUCCESS \"\" eos rm -rf \"/eos/$EOS_TEST_INSTANCE/test/acl\"\n# Test concurrent ACL recursive modifications\nruntest \"### Mkdir   \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/acl_concurrent\"\nruntest \"### Acl rec \" unix SUCCESS \"\" eos_acl_concurrent \"/eos/$EOS_TEST_INSTANCE/test/acl_concurrent\"\n\n################################################################################\n# CHECKSUMS\n###################################################A############################\ncategorie=\"checksum\"\nruntest \"### adler32 \" unix SUCCESS \"\" shell echo -n \"123456789 |  eos-checksum adler /dev/stdin | grep 091e01de\"\nruntest \"### crc32   \" unix SUCCESS \"\" shell echo -n \"123456789 |  eos-checksum crc32 /dev/stdin | grep cbf43926\"\nruntest \"### crc32c  \" unix SUCCESS \"\" shell echo -n \"123456789 |  eos-checksum crc32c /dev/stdin | grep e3069283\"\nruntest \"### crc64   \" unix SUCCESS \"\" shell echo -n \"123456789 |  eos-checksum crc64 /dev/stdin | grep 6c40df5f0b497347\"\nruntest \"### md5     \" unix SUCCESS \"\" shell echo -n \"123456789 |  eos-checksum md5 /dev/stdin  | grep 25f9e794323b453885f5181f1b624d0b\"\nruntest \"### xxhash64\" unix SUCCESS \"\" shell echo -n \"123456789 |  eos-checksum xxhash64 /dev/stdin | grep 8cb841db40e6ae83\"\nruntest \"### sha1    \" unix SUCCESS \"\" shell echo -n \"123456789 |  eos-checksum sha /dev/stdin | grep f7c3bc1d808e04732adf679965ccc34ca7ae3441\"\nruntest \"### sha256  \" unix SUCCESS \"\" shell echo -n \"123456789 |  eos-checksum sha256 /dev/stdin | grep 15e2b0d3c33891ebb0f1ef609ec419420c20e320ce94c65fbc8c3312448eb225\"\n\n################################################################################\n# race\n###################################################A############################\ncategorie=\"race\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"-p\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/race\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 /eos/$EOS_TEST_INSTANCE/test/instancetest/race/\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.checksum=adler /eos/$EOS_TEST_INSTANCE/test/instancetest/race/\nruntest \"### Upload    \" unix SUCCESS \"\" upload    \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/race/file.1k\" \"\"\nruntest \"### Checksum  \" unix SUCCESS \"\" eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/race/file.1k --checksum | grep 14c30610\"\nruntest \"### AppendOL  \" unix SUCCESS \"\" overlap \"/eos/$EOS_TEST_INSTANCE/test/instancetest/race/file.1k\"\nruntest \"### Delay     \" unix SUCCESS \"\" delay 1\nruntest \"### Checksum  \" unix SUCCESS \"\" eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/race/file.1k --checksum | grep fc46d811\"\nruntest \"### Rm        \" unix SUCCESS \"\" eos rm -r /eos/$EOS_TEST_INSTANCE/test/instancetest/race/\n\n################################################################################\n# Public access\n###################################################A############################\ncategorie=\"publicaccess\"\nruntest \"### Mkdir    \" unix SUCCESS \"\" eos mkdir -p /eos/$EOS_TEST_INSTANCE/test/instancetest/public/\nruntest \"### Chmod    \" unix SUCCESS \"\" eos chmod \"777\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/public/\"\nruntest \"### Upload   \" unix SUCCESS \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/public/p1\" \"-ODeos.space=default\"\nruntest \"### Vid      \" unix SUCCESS \"\" eos vid publicaccesslevel 2\nruntest \"### Rm       \" unix ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/public/p1\"\nruntest \"### Rm       \" unix SUCCESS \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/public/p1\"\nruntest \"### Rmdir    \" unix ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos rmdir /eos/$EOS_TEST_INSTANCE/test/instancetest/public\nruntest \"### Ls       \" unix ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos ls \"/eos/$EOS_TEST_INSTANCE/test/\"\nruntest \"### Ls       \" unix SUCCESS \"\" eos ls -l \"/eos/$EOS_TEST_INSTANCE/test/\"\nruntest \"### Find     \" unix ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos find \"/eos/$EOS_TEST_INSTANCE/test/\"\nruntest \"### Vid      \" unix SUCCESS \"\" eos vid publicaccesslevel 1024\nruntest \"### Ls       \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos ls \"/eos/$EOS_TEST_INSTANCE/test/\"\nruntest \"### Find     \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos find \"/eos/$EOS_TEST_INSTANCE/test/\"\nruntest \"### Rmdir    \" unix SUCCESS \"\" eos rmdir /eos/$EOS_TEST_INSTANCE/test/instancetest/public\n\n\n################################################################################\n# eos token\n################################################################################\nruntest \"### Token    \" sss SUCCESS \"\" eos_token /eos/$EOS_TEST_INSTANCE/test/instancetest/tokens/\n\n################################################################################\n# eosnobody\n###################################################A############################\ncategorie=\"eosnobody\"\nSERVERKEY=$(grep \"sec.protocol sss\" /etc/xrd.cf.mgm | grep -o '\\-s.*' | cut -d' ' -f2)\ngrep eosnobody $SERVERKEY > /tmp/eos.nobody.keytab\nif [ $? -eq 0 ]; then\n    chmod 400 /tmp/eos.nobody.keytab\n    runtest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir -p /eos/$EOS_TEST_INSTANCE/test/instancetest/eosnobody\n    runtest \"### Chmod     \" unix SUCCESS \"\" eos chmod \"777\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/eosnobody/\"\n    runtest \"### Upload    \" unix SUCCESS \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/eosnobody/p1\"\n    runtest \"### Upload    \" unix SUCCESS \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/eosnobody/.p1.sqsh\"\n    runtest \"### Chmod     \" unix SUCCESS \"\" eos chmod \"700\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/eosnobody/\"\n    runtest \"### Ls        \" unix ERROR \"XrdSecSSSKT='/tmp/eos.nobody.keytab'\" eosr ls -la /eos/$EOS_TEST_INSTANCE/test/instancetest/eosnobody/\n    runtest \"### Ls        \" sss ERROR \"XrdSecSSSKT='/tmp/eos.nobody.keytab'\" eosr ls -la /eos/$EOS_TEST_INSTANCE/test/instancetest/eosnobody/\n    runtest \"### Download  \" sss ERROR \"XrdSecSSSKT='/tmp/eos.nobody.keytab'\" downloadh  \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/eosnobody/p1\"\n    runtest \"### Download  \" sss SUCCESS \"XrdSecSSSKT='/tmp/eos.nobody.keytab'\" eos_squash /eos/$EOS_TEST_INSTANCE/test/instancetest/eosnobody/.p1.sqsh\n    runtest \"### Rm        \" unix SUCCESS \"\" eos rm -r /eos/$EOS_TEST_INSTANCE/test/instancetest/eosnobody/\n    chmod 700 /tmp/eos.nobody.keytab\n    unlink /tmp/eos.nobody.keytab\nfi\n\n################################################################################\n# POLICIES\n###################################################A############################\ncategorie=\"policy\"\nruntest \"### Cd        \" unix SUCCESS \"\" eos cd /eos/$EOS_TEST_INSTANCE/test\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir -p /eos/$EOS_TEST_INSTANCE/test/instancetest/policies/\nruntest \"### Attr      \" unix IGNORE  \"\" eos attr rm sys.forced.layout /eos/$EOS_TEST_INSTANCE/test/instancetest/policies/\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod \"777\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/policies\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/policies/p1\" \"-ODeos.space=default\"\nruntest \"### Grep      \" unix SUCCESS \"\" eos file info \"/eos/$EOS_TEST_INSTANCE/test/instancetest/policies/p1\"\n\nruntest \"### Grep      \" unix SUCCESS \"\" eos file info \"/eos/$EOS_TEST_INSTANCE/test/instancetest/policies/p1| grep plain | wc -l | grep -w 1\"\nruntest \"### Policy    \" unix SUCCESS \"\" eos space config default space.policy.layout=replica\nruntest \"### Policy    \" unix SUCCESS \"\" eos space config default space.policy.nstripes=2\nruntest \"### Grep      \" unix SUCCESS \"\" eos space status \"default | grep policy.layout | wc -l | grep -w 1\"\nruntest \"### Grep      \" unix SUCCESS \"\" eos space status \"default | grep policy.nstripes | wc -l | grep -w 1\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/policies/p2\" \"-ODeos.space=default\"\nruntest \"### Grep      \" unix SUCCESS \"\" eos file info \"/eos/$EOS_TEST_INSTANCE/test/instancetest/policies/p2| grep replica | wc -l | grep -w 1\"\nruntest \"### Policy    \" unix SUCCESS \"\" eos space config default space.policy.checksum=adler\nruntest \"### Grep      \" unix SUCCESS \"\" eos space status \"default | grep policy.checksum | wc -l | grep -w 1\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/policies/p3\" \"-ODeos.space=default\"\nruntest \"### Grep      \" unix SUCCESS \"\" eos file info \"/eos/$EOS_TEST_INSTANCE/test/instancetest/policies/p3| grep adler | wc -l | grep -w 1\"\nruntest \"### Policy    \" unix SUCCESS \"\" eos space config default space.policy.blocksize=1M\nruntest \"### Grep      \" unix SUCCESS \"\" eos space status \"default | grep policy.blocksize | wc -l | grep -w 1\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/policies/p4\" \"-ODeos.space=default\"\nruntest \"### Grep      \" unix SUCCESS \"\" eos file info \"/eos/$EOS_TEST_INSTANCE/test/instancetest/policies/p4| grep 1M | wc -l | grep -w 1\"\nruntest \"### Policy    \" unix SUCCESS \"\" eos space config default space.policy.blockchecksum=crc32c\nruntest \"### Grep      \" unix SUCCESS \"\" eos space status \"default | grep policy.blockchecksum | wc -l | grep -w 1\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload    \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/policies/p5\" \"-ODeos.space=default\"\nruntest \"### Policy    \" unix SUCCESS \"\" eos space config default space.policy.layout=remove\nruntest \"### Policy    \" unix SUCCESS \"\" eos space config default space.policy.nstripes=remove\nruntest \"### Policy    \" unix SUCCESS \"\" eos space config default space.policy.checksum=remove\nruntest \"### Policy    \" unix SUCCESS \"\" eos space config default space.policy.blockchecksum=remove\nruntest \"### Policy    \" unix SUCCESS \"\" eos space config default space.policy.blocksize=remove\nruntest \"### Policy    \" unix ERROR \"\" eos space config default space.policy.blocksize=remove\nruntest \"### Grep      \" unix SUCCESS \"\" eos space status \"default | grep policy.layout | wc -l | grep -w 0\"\nruntest \"### Grep      \" unix SUCCESS \"\" eos space status \"default | grep policy.nstripes | wc -l | grep -w 0\"\nruntest \"### Grep      \" unix SUCCESS \"\" eos space status \"default | grep policy.checksum | wc -l | grep -w 0\"\nruntest \"### Grep      \" unix SUCCESS \"\" eos space status \"default | grep policy.blockchecksum | wc -l | grep -w 0\"\nruntest \"### Grep      \" unix SUCCESS \"\" eos space status \"default | grep policy.blocksize | wc -l | grep -w 0\"\nruntest \"### Policy    \" unix SUCCESS \"\" eos space config default space.policy.iopriority=be:6\nruntest \"### Grep      \" unix SUCCESS \"\" eos space status \"default | grep policy.iopriority | wc -l | grep -w 1\"\nruntest \"### Policy    \" unix SUCCESS \"\" eos space config rm default space.policy.iopriority\nruntest \"### Rm        \" unix SUCCESS \"\" eos rm -r /eos/$EOS_TEST_INSTANCE/test/instancetest/policies\n\n################################################################################\n# NAMESPACE\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"ns\"\n# ------------------------------------------------------------------------------\nruntest \"### Cd        \" unix ERROR \"\" eos cd /__dont_exists\nruntest \"### Cd        \" unix SUCCESS \"\" eos cd /eos/$EOS_TEST_INSTANCE/test\nruntest \"### Ls        \" unix ERROR \"\" eos ls /__dont_exist\n\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 700 /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Ls        \" unix ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos ls /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Ls        \" unix SUCCESS \"\" eos ls /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Attr      \" unix ERROR \"\" eos attr set sys.acl=illegal /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.acl=u:nobody:rx /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Ls        \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos ls /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Upload    \" unix ERROR \"\" upload    \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\" \"-ODeos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.acl=u:nobody:rwox /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Upload    \" unix SUCCESS \"\" upload    \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\" \"-ODeos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"\nruntest \"### Upload    \" unix ERROR \"\" upload    \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\" \"-ODeos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"\nruntest \"### Rm        \" unix ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos rm /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.acl=u:nobody:rwx,u:100:rwx /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Upload    \" unix SUCCESS \"\" upload    \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\" \"-ODeos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload    \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.100.1k\" \"-ODeos.ruid=100\\&eos.rgid=100\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.acl=u:nobody:rwox /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Rm        \" unix ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos rm /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.acl=u:nobody:rwx!d /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Rm        \" unix ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos rm /eos/$EOS_TEST_INSTANCE/test/instancetest/file.100.1k\nruntest \"### Rm        \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos rm /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.acl=u:100:rwx+d,g:nobody:rwx!d /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Rm        \" unix SUCCESS \"EOSROLE='-r 100 ${NOBODY_GID}'\" eos rm /eos/$EOS_TEST_INSTANCE/test/instancetest/file.100.1k\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr rm sys.acl /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.acl=u:nobody:rwx!u /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest \"### Upload    \" unix SUCCESS \"\" upload    \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\" \"-ODeos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"\nruntest \"### Update    \" unix ERROR \"\" append    \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\\?eos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 755 /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr rm sys.acl /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.eval.useracl=1 /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir /eos/$EOS_TEST_INSTANCE/new\nruntest \"### Mkdir     \" unix ERROR \"\" eos mkdir /eos/$EOS_TEST_INSTANCE/new\n\n# \"runtest H\" with sss-auth secures \"not sudoer\" and group list contains ${NOBODY_UID} where needed... without H the former fails, with unix-auth the latter (gids=[2] hence hasAcl=0)\n# ACL impact: no ACL or user neither granted nor denied -> dir mode bits\n# ACL denials: override except for u/d for file owner\n\nruntest   \"### Attr      \" unix SUCCESS \"\" eos debug debug '\\*'\nruntest   \"### Chown     \" unix SUCCESS \"\" eos chown daemon /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest   \"### Chown     \" unix SUCCESS \"\" eos chown daemon /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\n# most frequent case, file/dir without ACL mode bits decide\nruntest   \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest H \"### Update    \" sss SUCCESS \"\" append \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\\?eos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"   # no ACL, dir mode bits allow (o=rw)\nruntest   \"### Chmod     \" unix SUCCESS \"\" eos chmod 755 /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest H \"### Update    \" sss ERROR \"\" append \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\\?eos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"   # fail: no ACL, dir mode bits deny\nruntest   \"### Chmod     \" unix SUCCESS \"\" eos chmod 775 /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest H \"### Update    \" sss ERROR \"\" append \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\\?eos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"   # fail: no ACL, dir mode bits deny (g=rw but not group)\nruntest   \"### Chown     \" unix SUCCESS \"\" eos chown 2:${NOBODY_GID} /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest H \"### Update    \" sss SUCCESS \"\" append \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\\?eos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"   # no ACL, dir mode bits allow (g=rw same group)\nruntest   \"### Chown     \" unix SUCCESS \"\" eos chown ${NOBODY_UID}:2 /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest H \"### Update    \" sss SUCCESS \"\" append \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\\?eos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"   # no ACL, dir mode bits allow (u=rw same user)\n\nruntest   \"### Chown     \" unix SUCCESS \"\" eos chown 2:2 /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest H \"### Rm        \" sss ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos rm /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k     # fail: dir not owned, no ACL\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set user.acl=u:nobody:rwx!d /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest H \"### Rm        \" sss ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos rm /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k     # fail: dir not owned, ACL denies delete\nruntest H \"### Attr      \" unix SUCCESS \"\" eos whoami         # running as user 2:2\nruntest   \"### Mkdir     \" unix SUCCESS \"\" eos mkdir /eos/$EOS_TEST_INSTANCE/test/instancetest/testAcl\nruntest   \"### Chown     \" unix SUCCESS \"\" eos chown 1:1 /eos/$EOS_TEST_INSTANCE/test/instancetest/testAcl\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set user.acl=u:2:rwxc /eos/$EOS_TEST_INSTANCE/test/instancetest/testAcl       # the 'c'-right should be ignored on user.acl\nruntest H \"### Chown     \" sss ERROR \"\" eos chown ${NOBODY_UID} /eos/$EOS_TEST_INSTANCE/test/instancetest/testAcl                         # fail: 'c' must only effective in sys.acl\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set sys.acl=u:2:c /eos/$EOS_TEST_INSTANCE/test/instancetest/testAcl\nruntest H \"### Chown     \" sss SUCCESS \"\" eos chown ${NOBODY_UID} /eos/$EOS_TEST_INSTANCE/test/instancetest/testAcl                         # should work with 'c' in sys.acl\nruntest   \"### Chown     \" unix SUCCESS \"\" eos chown root /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set user.acl=u:${NOBODY_UID}:rwx /eos/$EOS_TEST_INSTANCE/test/instancetest               # the 'c'-right should be ignored on user.acl\n\n# dir not owned\nruntest H \"### Rm        \" sss SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos rm /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k             # dir not owned, but ACL allows write\nruntest H \"### Upload    \" sss SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\" \"-ODeos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"  # ACL allows write, new file owned\nruntest H \"### Update    \" sss SUCCESS \"\" append \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\\?eos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"   # dir not owned, ACL allows write\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set user.acl=u:nobody:rx /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest H \"### Update    \" sss ERROR \"\" append \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\\?eos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"   # fail: dir not owned, ACL does not grant write\n\nruntest   \"### Chown     \" unix SUCCESS \"\" eos chown root /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\nruntest H \"### Update    \" sss ERROR \"\" append \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\\?eos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"   # fail: not owner, ACL allows rx only\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set user.acl=u:nobody:rwx\\!u /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest H \"### Update    \" sss ERROR \"\" append \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\\?eos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"   # fail: not owner, ACL allows RW but denies update\n\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set user.acl=u:1:rwx /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest H \"### Update    \" sss ERROR \"\" append \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\\?eos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"   # fail: not owner, not in ACL\n\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set user.acl=u:${NOBODY_UID}:rwx\\!d /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest H \"### Rm        \" sss ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos rm /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k             # fail: dir not owned, ACL denies delete\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set user.acl=u:${NOBODY_UID}:rwx\\!d,g:${NOBODY_GID}:rwx+d /eos/$EOS_TEST_INSTANCE/test/instancetest # +d for group ${NOBODY_GID}, but should be ignored in user.acl\nruntest H \"### Rm        \" sss ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos rm /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k             # fail: dir not owned, ACL denies delete\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set sys.acl=g:${NOBODY_GID}:rwx+d /eos/$EOS_TEST_INSTANCE/test/instancetest              # +d for nobody, this should work in sys.acl\nruntest H \"### Rm        \" sss SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos rm /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k             # ACL allows delete\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr rm sys.acl /eos/$EOS_TEST_INSTANCE/test/instancetest\n\n# dir owned\nruntest   \"### Chown     \" unix SUCCESS \"\" eos chown ${NOBODY_UID}:${NOBODY_GID} \"/eos/$EOS_TEST_INSTANCE/test/instancetest\"\nruntest H \"### Upload    \" sss SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\" \"-ODeos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"  # ACL allows write, new file owned\nruntest H \"### Update    \" sss SUCCESS \"\" append \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\\?eos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"   # dir owned\nruntest H \"### Upload    \" sss SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp -a \"$TESTSYSFILE1K\" /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k # dir owned\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set user.acl=u:nobody:r!wx /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest H \"### Update    \" sss ERROR \"\" append \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\\?eos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"     # writes are denied, and updates are note allowed explicitly\nruntest H \"### Upload    \" sss ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp -a \"$TESTSYSFILE1K\" /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k  # writes are denied, and updates are not allowed explicitly\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set user.acl=u:${NOBODY_UID}:rwx!d /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest   \"### Chown     \" unix SUCCESS \"\" eos chown ${NOBODY_UID}:${NOBODY_GID} \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\"                    # make sure file owned\nruntest H \"### Rm        \" sss SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos rm /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k             # ACL denies delete but file owned\n\n# chown on file\nruntest H \"### Upload    \" sss SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\" \"-ODeos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"  # owned by ${NOBODY_GID}\nruntest   \"### Chmod     \" unix SUCCESS \"\" eos chmod 775 /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set user.acl=g:${NOBODY_GID}:rwxc /eos/$EOS_TEST_INSTANCE/test/instancetest              # 'c' (chown) in user.acl\nruntest H \"### Chown     \" sss SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos chown 98 /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k       # fail: user.acl cannot grant this chown but ${NOBODY_UID} is the owner\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set sys.acl=g:${NOBODY_GID}:c /eos/$EOS_TEST_INSTANCE/test/instancetest                  # 'c' (chown) in sys.ascl\nruntest H \"### Chown     \" sss SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos chown 98 /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr rm sys.acl /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest H \"### Chown     \" sss SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos chown ${NOBODY_UID} /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k       # chown to self should work in user.acl\n\n# chown on directory - the ACL that applies is that of the directory itself\nruntest   \"### Chown     \" unix SUCCESS \"\" eos chown ${NOBODY_UID}:${NOBODY_GID} /eos/$EOS_TEST_INSTANCE/test/instancetest/testAcl                      # same tests with a directory\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set user.acl=g:${NOBODY_GID}:rwxc /eos/$EOS_TEST_INSTANCE/test/instancetest/testAcl      # 'c' (chown) in user.acl\nruntest H \"### Chown     \" sss ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos chown 98 /eos/$EOS_TEST_INSTANCE/test/instancetest/testAcl       # fail: user.acl cannot grant this chown\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set sys.acl=g:${NOBODY_GID}:c /eos/$EOS_TEST_INSTANCE/test/instancetest/testAcl          # 'c' (chown) in sys.ascl\nruntest H \"### Chown     \" sss SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos chown 98 /eos/$EOS_TEST_INSTANCE/test/instancetest/testAcl\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set sys.acl= /eos/$EOS_TEST_INSTANCE/test/instancetest/testAcl                # rm sys.acl, user.acl remains\nruntest H \"### Chown     \" sss SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos chown ${NOBODY_UID} /eos/$EOS_TEST_INSTANCE/test/instancetest/testAcl       # chown to self should work in user.acl\n\n# test inheritance of file acls\n\nruntest H \"### Upload    \" sss SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1\" \"-ODeos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set sys.acl=g:${NOBODY_GID}:rwx /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1\nruntest H \"### Upload    \" sss SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1\" \"-ODeos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"  # should have now sys.acl\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1 | grep sys.acl | wc -l | grep -w 0 \"\nruntest H \"### Upload    \" sss SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.2\" \"-ODeos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set user.acl=g:${NOBODY_GID}:rwx /eos/$EOS_TEST_INSTANCE/test/instancetest/file.2\nruntest H \"### Upload    \" sss SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.2\" \"-ODeos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"  # should have no user.acl\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.2 | grep user.acl | wc -l | grep -w 0 \"\nruntest H \"### Upload    \" sss SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.3\" \"-ODeos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set user.acl=g:${NOBODY_GID}:rwx /eos/$EOS_TEST_INSTANCE/test/instancetest/file.3\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set sys.eval.useracl=1 /eos/$EOS_TEST_INSTANCE/test/instancetest/file.3\nruntest H \"### Upload    \" sss SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.3\" \"-ODeos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"  # should have user.acl and sys.eval.useracl\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.3 | grep user.acl | wc -l | grep -w 1 \"\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.3 | grep sys.eval.useracl | wc -l | grep -w 1 \"\n\n# test sys file acl\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr rm user.acl /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest   \"### Chmod     \" sss SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/\"\nruntest   \"### Chmod     \" sss SUCCESS \"\" eos chown root:root \"/eos/$EOS_TEST_INSTANCE/test/instancetest/\"\n\nruntest H \"### Upload    \" sss SUCCESS \"\" upload \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.4\"\nruntest   \"### Chmod     \" sss SUCCESS \"\" eos chmod 700 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/\"\nruntest   \"### Ls        \" sss SUCCESS \"\" eos ls -la \"/eos/$EOS_TEST_INSTANCE/test/instancetest/\"\nruntest   \"### Download  \" unix SUCCESS \"EOSROLE='-r 0 0'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.4\" $EOS_TEST_TESTSYS/null.txt\nruntest   \"### Download  \" unix ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.4\" $EOS_TEST_TESTSYS/null.txt\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set sys.acl=u:${NOBODY_UID}:rx \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.4\"\nruntest   \"### Download  \" unix SUCCESS \"EOSROLE='-r 0 0'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.4\" $EOS_TEST_TESTSYS/null.txt\nruntest   \"### Download  \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.4\" $EOS_TEST_TESTSYS/null.txt\nruntest   \"### Chmod     \" sss SUCCESS \"\" eos chmod 700 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/\"\nruntest   \"### Attr      \" unix SUCCESS \"\" eos attr set sys.acl=u:${NOBODY_UID}:w \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.4\"\nruntest   \"### Download  \" unix SUCCESS \"EOSROLE='-r 0 0'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.4\" $EOS_TEST_TESTSYS/null.txt\nruntest   \"### Download  \" unix ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.4\" $EOS_TEST_TESTSYS/null.txt\nruntest   \"### Chmod     \" sss SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/\"\n\n# date +%s.%N;exit\n\n#cleanup\nruntest \"### Attr      \" unix SUCCESS \"\" eos debug info '\\*'\nruntest \"### Rm        \" unix IGNORE  \"\" eos rm /eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set user.acl=u:nobody:rwx /eos/$EOS_TEST_INSTANCE/test/instancetest\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" upload    \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.1k\" \"-ODeos.ruid=${NOBODY_UID}\\&eos.rgid=${NOBODY_GID}\"\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr rm user.acl /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Chown     \" unix SUCCESS \"\" eos chown 0:0 \"/eos/$EOS_TEST_INSTANCE/test/instancetest\"\n\n#-------------------------------------------------------------------------------\n# Ns map functionality\n#-------------------------------------------------------------------------------\nruntest \"### Map ls    \" unix SUCCESS \"\" eos map ls\nruntest \"### Map mkdir \" unix SUCCESS \"\" eos mkdir /eos/$EOS_TEST_INSTANCE/test/map_test/\nruntest \"### Touch     \" unix SUCCESS \"\" eos touch /eos/$EOS_TEST_INSTANCE/test/map_test/file1.dat\nruntest \"### Stat      \" unix SUCCESS \"\" eos stat /eos/$EOS_TEST_INSTANCE/test/map_test/file1.dat\nruntest \"### Ls        \" unix SUCCESS \"\" eos ls /eos/$EOS_TEST_INSTANCE/test/map_test/file1.dat\nruntest \"### Map link  \" unix SUCCESS \"\" eos map link /store/ /eos/$EOS_TEST_INSTANCE/test/map_test/\nruntest \"### Stat      \" unix SUCCESS \"\" eos stat /store/file1.dat\nruntest \"### Ls        \" unix SUCCESS \"\" eos ls /store/file1.dat\nruntest \"### Rm        \" unix SUCCESS \"\" eos rm /store/file1.dat\nruntest \"### Map unlink\" unix SUCCESS \"\" eos map unlink /store/\nruntest \"### Rmdir     \" unix SUCCESS \"\" eos rm -rf /eos/$EOS_TEST_INSTANCE/test/map_test/\n\n# ------------------------------------------------------------------------------\ncategorie=\"symlink\"\n# ------------------------------------------------------------------------------\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir /eos/$EOS_TEST_INSTANCE/test/instancetest/ref/\nruntest \"### Touch     \" unix SUCCESS \"\" eos touch /eos/$EOS_TEST_INSTANCE/test/instancetest/ref/touch\nruntest \"### Symlink-d \" unix SUCCESS \"\" eos ln /eos/$EOS_TEST_INSTANCE/test/instancetest/symdir /eos/$EOS_TEST_INSTANCE/test/instancetest/ref\nruntest \"### Symlink-r \" unix SUCCESS \"\" eos ln /eos/$EOS_TEST_INSTANCE/test/instancetest/symrel2 ../../test/instancetest/ref/\nruntest \"### Symlink   \" unix SUCCESS \"\" eos stat /eos/$EOS_TEST_INSTANCE/test/instancetest/symrel2/touch\nruntest \"### Symlink-r \" unix SUCCESS \"\" eos ln /eos/$EOS_TEST_INSTANCE/test/instancetest/symrel1 ../instancetest/ref/\nruntest \"### Symlink   \" unix SUCCESS \"\" eos stat /eos/$EOS_TEST_INSTANCE/test/instancetest/symrel1/touch\nruntest \"### Symlink-f \" unix SUCCESS \"\" eos ln /eos/$EOS_TEST_INSTANCE/test/instancetest/whoami /eos/$EOS_TEST_INSTANCE/test/instancetest/ref/touch\nruntest \"### Symlink-E \" unix ERROR \"\" eos ln /eos/$EOS_TEST_INSTANCE/test/instancetest/whoami /eos/$EOS_TEST_INSTANCE/proc/whoami\nruntest \"### Symlink-E \" unix ERROR \"\" eos ln /eos/$EOS_TEST_INSTANCE/test/instancetest/symdir /eos/$EOS_TEST_INSTANCE/test/instancetest/ref\nruntest \"### Symlink   \" unix SUCCESS \"\" eos ls /eos/$EOS_TEST_INSTANCE/test/instancetest/symdir/touch\nruntest \"### Symlink   \" unix SUCCESS \"\" eos stat /eos/$EOS_TEST_INSTANCE/test/instancetest/whoami\nruntest \"### Symlink   \" unix SUCCESS \"\" eos ls /eos/$EOS_TEST_INSTANCE/test/instancetest/whoami\nruntest \"### Symlink-L \" unix SUCCESS \"\" eos ln /eos/$EOS_TEST_INSTANCE/test/instancetest/fwd-loop /eos/$EOS_TEST_INSTANCE/bwd-loop\nruntest \"### Symlink-L \" unix SUCCESS \"\" eos ln /eos/$EOS_TEST_INSTANCE/test/instancetest/bwd-loop /eos/$EOS_TEST_INSTANCE/fwd-loop\nruntest \"### Symlink-L \" unix ERROR \"\" eos ls /eos/$EOS_TEST_INSTANCE/test/instancetest/bwd-loop\nruntest \"### Symlink-L \" unix ERROR \"\" eos ls /eos/$EOS_TEST_INSTANCE/test/instancetest/fwd-loop\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir /eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir /eos/$EOS_TEST_INSTANCE/test/instancetest/link-sniffer/\nruntest \"### Chown     \" unix SUCCESS \"\" eos chown ${NOBODY_UID}:${NOBODY_GID} \"/eos/$EOS_TEST_INSTANCE/test/instancetest/link-sniffer\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r 0 0'\" eos cp /etc/passwd /eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/passwd\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 700 /eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/\nruntest \"### Symlink-S \" unix SUCCESS \"\" eos ln /eos/$EOS_TEST_INSTANCE/test/instancetest/link-sniffer/passwd /eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/passwd\nruntest \"### Chown     \" unix SUCCESS \"\" eos chown -h ${NOBODY_UID}:${NOBODY_GID} \"/eos/$EOS_TEST_INSTANCE/test/instancetest/link-sniffer/passwd\"\nruntest \"### Download  \" unix SUCCESS \"EOSROLE='-r 0 0'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/link-sniffer/passwd\" -\nruntest \"### Download  \" unix SUCCESS \"EOSROLE='-r 0 0'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/passwd\" -\nruntest \"### Download  \" unix ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/link-sniffer/passwd\" -\nruntest \"### Download  \" unix ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/passwd\" -\nruntest \"### Rm        \" unix SUCCESS \"\" eos rm /eos/$EOS_TEST_INSTANCE/test/instancetest/link-sniffer/passwd\nruntest \"### Rm        \" unix SUCCESS \"\" eos rm /eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/passwd\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir /eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/dir\nruntest \"### Symlink-S \" unix SUCCESS \"\" eos ln /eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/link-dir /eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/dir\nruntest \"### Touch     \" unix SUCCESS \"\" eos touch /eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/file\nruntest \"### Symlink-S \" unix SUCCESS \"\" eos ln /eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/link-file /eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/file\nruntest \"### Ownership \" unix SUCCESS \"\" eos ls -l \"/eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/ | grep -w root | wc -l | grep -w 4\"\nruntest \"### Ownership \" unix SUCCESS \"\" eos chown -h bin \"/eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/link-file\"\nruntest \"### Ownership \" unix SUCCESS \"\" eos ls -l \"/eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/ | grep -w bin | wc -l | grep -w 1\"\nruntest \"### Ownership \" unix SUCCESS \"\" eos chown -h root \"/eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/link-file\"\nruntest \"### Ownership \" unix SUCCESS \"\" eos ls -l \"/eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/ | grep -w root | wc -l | grep -w 4\"\nruntest \"### Ownership \" unix SUCCESS \"\" eos chown -r bin \"/eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/\"\nruntest \"### Ownership \" unix SUCCESS \"\" eos ls -la \"/eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/ | grep -w bin | wc -l | grep -w 3\"\nruntest \"### Ownership \" unix SUCCESS \"\" eos chown -r -h bin \"/eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/\"\nruntest \"### Ownership \" unix SUCCESS \"\" eos ls -la \"/eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/ | grep -w bin | wc -l | grep -w 5\"\nruntest \"### Rmdir     \" unix SUCCESS \"\" eos rm -rf /eos/$EOS_TEST_INSTANCE/test/instancetest/link-source/\nruntest \"### Rmdir     \" unix SUCCESS \"\" eos rm -rf /eos/$EOS_TEST_INSTANCE/test/instancetest/link-sniffer/\n\n# ------------------------------------------------------------------------------\ncategorie=\"tag\"\n# ------------------------------------------------------------------------------\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir /eos/$EOS_TEST_INSTANCE/test/instancetest/tag/\nruntest \"### Touch     \" unix SUCCESS \"\" eos touch -n /eos/$EOS_TEST_INSTANCE/test/instancetest/tag/touch\nruntest \"### Tag       \" unix SUCCESS \"\" eos file tag /eos/$EOS_TEST_INSTANCE/test/instancetest/tag/touch +123456\nruntest \"### Tag       \" unix ERROR \"\" eos file tag /eos/$EOS_TEST_INSTANCE/test/instancetest/tag/touch +123456\nruntest \"### Tag       \" unix SUCCESS \"\" eos file tag /eos/$EOS_TEST_INSTANCE/test/instancetest/tag/touch ~123456\nruntest \"### Tag       \" unix SUCCESS \"\" eos file tag /eos/$EOS_TEST_INSTANCE/test/instancetest/tag/touch ~123456\nruntest \"### Tag       \" unix SUCCESS \"\" eos file tag /eos/$EOS_TEST_INSTANCE/test/instancetest/tag/touch -123456\nruntest \"### Tag       \" unix ERROR \"\" eos file tag /eos/$EOS_TEST_INSTANCE/test/instancetest/tag/touch -123456\ntag_fxid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo /eos/$EOS_TEST_INSTANCE/test/instancetest/tag/touch | jq -r .fxid)\nruntest \"### Tag       \" unix SUCCESS \"\" eos file tag fxid:${tag_fxid} +98765\nruntest \"### Tag       \" unix ERROR \"\" eos file tag fxid:${tag_fxid} +98765\nruntest \"### Tag       \" unix SUCCESS \"\" eos file tag fxid:${tag_fxid} -98765\nruntest \"### Tag       \" unix ERROR \"\" eos file tag fxid:${tag_fxid} -98765\nruntest \"### Rm        \" unix SUCCESS \"\" eos rm /eos/$EOS_TEST_INSTANCE/test/instancetest/tag/touch\nruntest \"### Rmdir     \" unix SUCCESS \"\" eos rmdir /eos/$EOS_TEST_INSTANCE/test/instancetest/tag/\n\n# ------------------------------------------------------------------------------\ncategorie=\"rm\"\n# ------------------------------------------------------------------------------\n# These tests assume that the polling interval for deletions on the FST side is\n# less than 7 seconds. Set the EOS_FST_DELETE_QUERY_INTERVAL=5.\n# First test that the removal of a detached file will retrigger the deletion on\n# the diskserver and will wait for the confirmation before dropping the ns entry\nRM_TEST_FILE=\"/eos/$EOS_TEST_INSTANCE/test/instancetest/rm_test.dat\"\nruntest \"### Upload      \" unix SUCCESS \"\" eos cp /etc/passwd ${RM_TEST_FILE}\nrm_fxid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo ${RM_TEST_FILE} | jq -r .fxid)\nrm_fsid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo ${RM_TEST_FILE} | jq -r .locations[].fsid | head -n 1)\nruntest \"### Rm          \" unix SUCCESS \"\" eos rm -F ${RM_TEST_FILE}\nruntest \"### Dropdel     \" unix SUCCESS \"\" eos fs dropdeletion \"${rm_fsid}\"\nruntest \"### Delay       \" unix SUCCESS \"\" delay 2\nif ! file_is_deleted \"$rm_fxid\"; then\n    runtest \"### Fileinfo    \" unix SUCCESS \"\" eos fileinfo \"fxid:${rm_fxid}\"\n    runtest \"### Rm detached \" unix SUCCESS \"\" eos rm \"fxid:${rm_fxid}\"\n    runtest \"### Delay       \" unix SUCCESS \"\" delay 2\nfi\nruntest \"### File Del    \" unix SUCCESS \"\" file_is_deleted \"$rm_fxid\"\n# Secondly test that rm -F on a detached file will clean up the namespace\n# without waiting for a confirmation from the diskservers\nruntest \"### Upload      \" unix SUCCESS \"\" eos cp /etc/passwd ${RM_TEST_FILE}\nrm_fxid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo ${RM_TEST_FILE} | jq -r .fxid)\nrm_fsid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo ${RM_TEST_FILE} | jq -r .locations[].fsid | head -n 1)\nruntest \"### Rm          \" unix SUCCESS \"\" eos rm -F ${RM_TEST_FILE}\nruntest \"### Dropdel     \" unix SUCCESS \"\" eos fs dropdeletion \"${rm_fsid}\"\nruntest \"### Delay       \" unix SUCCESS \"\" delay 2\nif ! file_is_deleted \"$rm_fxid\"; then\n    runtest \"### Fileinfo    \" unix SUCCESS \"\" eos fileinfo \"fxid:${rm_fxid}\"\n    runtest \"### Rm force    \" unix SUCCESS \"\" eos rm -F \"fxid:${rm_fxid}\"\nfi\nruntest \"### File Del    \" unix SUCCESS \"\" file_is_deleted \"$rm_fxid\"\n# Create a directory with 10 files in it\nrmTestDir=\"/eos/$EOS_TEST_INSTANCE/test/instancetest/rm_test\"\neos mkdir -p $rmTestDir\nfor ((i=1; i <= 10; i++));\ndo\n  eos cp /etc/passwd $rmTestDir/passwd$i\ndone\n# Deletion with no globbing should not work\nruntest \"### Rm noglob   \" unix ERROR \"\" eos rm --no-globbing $rmTestDir/*\nruntest \"### Rm r noglob \" unix ERROR \"\" eos rm -rF --no-globbing $rmTestDir/*\n\n# The deletion of a path that contains the \"*\" character in the middle should not work\nruntest \"### Rm glob dir \" unix ERROR \"\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/*/rm_test\"\nruntest \"### Rm r glob dir\" unix ERROR \"\" eos rm -rF \"/eos/$EOS_TEST_INSTANCE/test/*/rm_test\"\n\n# The deletion of the test directory and the 10 files should work by default\nruntest \"### Rm all glob \" unix SUCCESS \"\" eos rm $rmTestDir/*\nruntest \"### Empty       \" unix SUCCESS \"\" eos ls $rmTestDir/ | wc -l\n\n# Create the same directory with extra non empty directories in it\nfor((i = 1; i <= 10; i++));\ndo\n  eos cp /etc/passwd $rmTestDir/passwd$i\n  eos mkdir $rmTestDir/$i\n  eos cp /etc/passwd $rmTestDir/$i/passwd$i\ndone\n\n# The deletion with globbing enabled by default should not work as we have non-empty directories\nruntest \"### Rm all glob \" unix ERROR \"\" eos rm $rmTestDir/*\nruntest \"### Cleanup Fail\" unix ERROR \"\" eos rm -rF $rmTestDir/*\n\nfor((i = 1; i <= 10; i++));\ndo\n  eos touch $rmTestDir/$((i+10))\ndone\n\n# Put paths with globbing characters in the namespace\neos touch $rmTestDir/Perso[2ou52]_dgdia.pdf\ncommand eos touch \"$rmTestDir/regexx*p.tt\\.ern\"\n\neos touch $rmTestDir/number1pattern\neos touch $rmTestDir/number2pattern\n# No file matches the globbing, the deletion should not work\nruntest \"### Rm file     \" unix ERROR \"\" eos rm $rmTestDir/Perso[2ou52]_dgdia.pdf\nruntest \"### Stat file\" unix SUCCESS \"\" eos stat $rmTestDir/Perso[2ou52]_dgdia.pdf\n# Explicitely disabling the globbing should delete the Perso[2ou52]_dgdia.pdf file\nruntest \"### Rm file glob\" unix SUCCESS \"\" eos rm --no-globbing $rmTestDir/Perso[2ou52]_dgdia.pdf\nruntest \"### Stat file glob\" unix ERROR \"\" eos stat $rmTestDir/Perso[2ou52]_dgdia.pdf\n# Deleting with wrong globbing pattern (curly braces inside) should not work as it is not a valid\n# globbing character\nruntest \"### Bad glob rm \" unix ERROR \"\" eos rm \"$rmTestDir/number\\{1..9\\}pattern\"\nruntest \"### Stat file   \" unix SUCCESS \"\" eos stat $rmTestDir/number1pattern\nruntest \"### Stat file   \" unix SUCCESS \"\" eos stat $rmTestDir/number2pattern\nruntest \"### Stat file   \" unix SUCCESS \"\" command eos stat \"$rmTestDir/regexx*p.tt\\.ern\"\n# Deleting number[1-10]pattern without globbing should not work\nruntest \"### Rm noglob   \" unix ERROR \"\" eos rm --no-globbing \"$rmTestDir/number[1-9]pattern\"\n# Deleting number[1-10]pattern with globbing should work\nruntest \"### Rm glob ok  \" unix SUCCESS \"\" eos rm \"$rmTestDir/number[1-9]pattern\"\nruntest \"### Stat file   \" unix ERROR \"\" eos stat $rmTestDir/number1pattern\nruntest \"### Stat file   \" unix ERROR \"\" eos stat $rmTestDir/number2pattern\nruntest \"### Stat file   \" unix SUCCESS \"\" command eos stat \"$rmTestDir/regexx*p.tt\\.ern\"\n# Files $rmTestDir/{11..20} should not have been deleted\nruntest \"### Stat dir    \" unix SUCCESS \"\" eos stat $rmTestDir/11\nruntest \"### Stat dir    \" unix SUCCESS \"\" eos stat $rmTestDir/20\n# The deletion of the regexx* should not work\nruntest \"### Rm regexx*  \" unix ERROR \"\" command eos rm \"$rmTestDir/regexx*p.tt\\.ern\"\nruntest \"### Stat file   \" unix SUCCESS \"\" command eos stat \"$rmTestDir/regexx*p.tt\\.ern\"\n# The deletion of the regexx* should work with disabled globbing\nruntest \"### Rm file     \" unix SUCCESS \"\" command eos rm --no-globbing \"$rmTestDir/regexx*p.tt\\.ern\"\nruntest \"### Stat file   \" unix ERROR \"\" command eos stat \"$rmTestDir/regexx*p.tt\\.ern\"\n\n# ------------------------------------------------------------------------------\n# Test multiple file removal\n# ------------------------------------------------------------------------------\nRM_MULTI1=\"/eos/$EOS_TEST_INSTANCE/test/instancetest/multi 1.txt\"\nRM_MULTI2=\"/eos/$EOS_TEST_INSTANCE/test/instancetest/multi 2.txt\"\nRM_MULTI3=\"/eos/$EOS_TEST_INSTANCE/test/instancetest/ multi3 .txt\"\n\nruntest \"### CP          \" unix SUCCESS \"\" eos cp /etc/passwd \"'${RM_MULTI1}'\"\nruntest \"### CP          \" unix SUCCESS \"\" eos cp /etc/passwd \"'${RM_MULTI2}'\"\nruntest \"### CP          \" unix SUCCESS \"\" eos cp /etc/passwd \"'${RM_MULTI3}'\"\nruntest \"### Delay       \" unix SUCCESS \"\" delay 7\nruntest \"### Rm multi    \" unix SUCCESS \"\" eos rm \"'${RM_MULTI1}'\" \"'${RM_MULTI2}'\" \"'${RM_MULTI3}'\"\nruntest \"### Delay       \" unix SUCCESS \"\" delay 7\nruntest \"### Stat        \" unix ERROR \"\" eos stat \"'${RM_MULTI1}'\"\nruntest \"### Stat        \" unix ERROR \"\" eos stat \"'${RM_MULTI2}'\"\nruntest \"### Stat        \" unix ERROR \"\" eos stat \"'${RM_MULTI3}'\"\n\n# ------------------------------------------------------------------------------\n# Test multiple recursive directory removal\n# ------------------------------------------------------------------------------\nRM_DIR1=\"/eos/$EOS_TEST_INSTANCE/test/instancetest/rmdir1\"\nRM_DIR2=\"/eos/$EOS_TEST_INSTANCE/test/instancetest/rmdir2\"\nRM_DIR3=\"/eos/$EOS_TEST_INSTANCE/test/instancetest/rmdir3\"\n\nruntest \"### Mkdir       \" unix SUCCESS \"\" eos mkdir -p ${RM_DIR1}\nruntest \"### Mkdir       \" unix SUCCESS \"\" eos mkdir -p ${RM_DIR2}\nruntest \"### Mkdir       \" unix SUCCESS \"\" eos mkdir -p ${RM_DIR3}\nruntest \"### Touch       \" unix SUCCESS \"\" eos touch ${RM_DIR1}/file1.txt\nruntest \"### Touch       \" unix SUCCESS \"\" eos touch ${RM_DIR1}/file2.txt\nruntest \"### Touch       \" unix SUCCESS \"\" eos touch ${RM_DIR2}/file1.txt\nruntest \"### Touch       \" unix SUCCESS \"\" eos touch ${RM_DIR2}/file2.txt\nruntest \"### Touch       \" unix SUCCESS \"\" eos touch ${RM_DIR3}/file1.txt\nruntest \"### Touch       \" unix SUCCESS \"\" eos touch ${RM_DIR3}/file2.txt\nruntest \"### Rm -r multi \" unix SUCCESS \"\" eos rm -r ${RM_DIR1} ${RM_DIR2} ${RM_DIR3}\nruntest \"### Delay       \" unix SUCCESS \"\" delay 7\nruntest \"### Stat        \" unix ERROR \"\" eos stat ${RM_DIR1}\nruntest \"### Stat        \" unix ERROR \"\" eos stat ${RM_DIR2}\nruntest \"### Stat        \" unix ERROR \"\" eos stat ${RM_DIR3}\n\n# Cleanup everything\nruntest \"### Cleanup     \" unix SUCCESS \"\" eos rm -rF $rmTestDir/\n\n# Rm detached\nruntest \"### eos-file-cont-detached-test  \" unix SUCCESS \"\" eos_file_cont_detached \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file_cont_detached\"\n\n# ------------------------------------------------------------------------------\ncategorie=\"ls\"\n# ------------------------------------------------------------------------------\nlsTestDir=\"/eos/$EOS_TEST_INSTANCE/test/instancetest/ls_test\"\nruntest \"### Mkdir       \" unix SUCCESS \"\" eos mkdir -p \"$lsTestDir\"\n# General case\nruntest \"### Ls          \" unix SUCCESS \"\" eos ls \"$lsTestDir\"\nruntest \"### Ls          \" unix SUCCESS \"\" eos ls \"$lsTestDir | wc -l | grep 0\"\nruntest \"### Touch       \" unix SUCCESS \"\" eos touch \"$lsTestDir/fic1.txt\"\nruntest \"### Touch       \" unix SUCCESS \"\" eos touch \"$lsTestDir/fic2.txt\"\nruntest \"### Touch       \" unix SUCCESS \"\" eos touch \"$lsTestDir/fic3.txt\"\nruntest \"### Mkdir       \" unix SUCCESS \"\" eos mkdir \"$lsTestDir/dir1\"\nruntest \"### Mkdir       \" unix SUCCESS \"\" eos mkdir \"$lsTestDir/dir2\"\nruntest \"### Mkdir       \" unix SUCCESS \"\" eos mkdir \"$lsTestDir/dir3\"\nruntest \"### Ls          \" unix SUCCESS \"\" eos ls \"$lsTestDir/ | wc -l | grep 6\"\nruntest \"### Ls          \" unix SUCCESS \"\" eos ls \"$lsTestDir/fic*.txt | wc -l | grep 3\"\nruntest \"### Ls          \" unix ERROR \"\" eos ls -N \"$lsTestDir/fic*.txt\"\n# Globbing cases\nruntest \"### Touch       \" unix SUCCESS \"\" command eos touch \"$lsTestDir/regexx*p.tt\\.ern\"\nruntest \"### Touch       \" unix SUCCESS \"\" command eos touch \"$lsTestDir/\"'number{1..3}test.txt'\nruntest \"### Ls          \" unix SUCCESS \"\" eos ls \"$lsTestDir/number\\{1..3\\}test.txt | wc -l | grep 1\"\nruntest \"### Ls          \" unix ERROR \"\" command eos ls \"$lsTestDir/regexx*p.tt\\.ern\"\nruntest \"### Ls          \" unix SUCCESS \"\" command eos ls --no-globbing \"$lsTestDir/regexx*p.tt\\.ern\"\nruntest \"### Ls          \" unix SUCCESS \"\" command eos ls -N \"$lsTestDir/regexx*p.tt\\.ern\"\n# Cleanup everything\nruntest \"### Cleanup     \" unix SUCCESS \"\" eos rm -rF $lsTestDir/\n\n# ------------------------------------------------------------------------------\ncategorie=\"touch\"\n# ------------------------------------------------------------------------------\ntouchTestDir=\"/eos/$EOS_TEST_INSTANCE/test/instancetest/touch_test\"\nruntest \"### Mkdir       \" unix SUCCESS \"\" eos mkdir -p \"$touchTestDir\"\nruntest \"### Stat        \" unix 14 \"\" eos stat \"$touchTestDir/dir1/\" # touch directory does not exist yet\nruntest \"### Touch       \" unix 21 \"\" eos touch \"$touchTestDir/dir1/\"\nruntest \"### Mkdir       \" unix SUCCESS \"\" eos mkdir \"$touchTestDir/dir1/\"\nruntest \"### Stat        \" unix SUCCESS \"\" eos stat \"$touchTestDir/dir1/\"\n# touch on the dir and file uri should return in the same error\nruntest \"### Touch       \" unix 21 \"\" eos touch \"$touchTestDir/dir1\"\nruntest \"### Touch       \" unix 21 \"\" eos touch \"$touchTestDir/dir1/\"\n# touch a file in a directory that does not exist\nruntest \"### Touch       \" unix 2 \"\" eos touch \"$touchTestDir/dir2/file2\"\n# touch a file that does not previously exist\nruntest \"### Stat       \" unix 14 \"\" eos stat \"$touchTestDir/dir1/file1\"\nruntest \"### Touch      \" unix SUCCESS \"\" eos touch \"$touchTestDir/dir1/file1\"\nruntest \"### Stat       \" unix SUCCESS \"\" eos stat \"$touchTestDir/dir1/file1\"\n# touch a file that already exists\nruntest \"### Touch      \" unix SUCCESS \"\" eos touch \"$touchTestDir/dir1/file1\"\n# cleanup\nruntest \"### Cleanup     \" unix SUCCESS \"\" eos rm -rF \"$touchTestDir\"\n\n# ------------------------------------------------------------------------------\ncategorie=\"adjustreplica\"\n# ------------------------------------------------------------------------------\nADJUST_TEST_DIR=\"/eos/$EOS_TEST_INSTANCE/test/instancetest/adjustreplica\"\nADJUST_TEST_FILE=\"${ADJUST_TEST_DIR}/file.dat\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir ${ADJUST_TEST_DIR}\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos attr set default=replica  ${ADJUST_TEST_DIR}\nruntest \"### Upload    \" unix SUCCESS \"\" eos cp ${TESTSYSFILE1K} ${ADJUST_TEST_FILE}\nadj_fsid=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo ${ADJUST_TEST_FILE} | jq -r .locations[].fsid | head -n 1)\nruntest \"### Drop fsid \" unix SUCCESS \"\" eos file drop ${ADJUST_TEST_FILE} ${adj_fsid}\nruntest \"### Adjust -  \" unix SUCCESS \"\" eos file adjustreplica ${ADJUST_TEST_FILE}\nruntest \"### Delay     \" unix SUCCESS \"\" delay 8\nruntest \"### Check     \" unix SUCCESS \"\" eosj fileinfo \"${ADJUST_TEST_FILE} | jq -r .locations[].fsid | wc -l | grep 2\"\n# Find a new file system where to over-replicate the current file\nlist_fsids=$(command eos -j -b root://$EOS_TEST_REDIRECTOR fileinfo ${ADJUST_TEST_FILE} | jq -r .locations[].fsid)\narray_fsids=($list_fsids)\nsource_fsid=${array_fsids[0]}\ntarget_fsid=0\nNUM_FST=$(command eos -b $EOSROLE root://$EOS_TEST_REDIRECTOR fs ls | awk '{ if (($6 ~ /booted/) || ($7 ~ /booted/)) c++} END {print c}')\nfor ((i=1; i<=${NUM_FST}; i++)); do if [[ ! \" ${array_fsids[*]} \" =~ \" $i \" ]]; then target_fsid=$i; break; fi done\nruntest \"### Replicate \" unix SUCCESS \"\" eos file replicate ${ADJUST_TEST_FILE} ${source_fsid} ${target_fsid}\nruntest \"### Delay     \" unix SUCCESS \"\" delay 8\nruntest \"### Check     \" unix SUCCESS \"\" eosj fileinfo \"${ADJUST_TEST_FILE} | jq -r .locations[].fsid | wc -l | grep 3\"\nruntest \"### Adjust +  \" unix SUCCESS \"\" eos file adjustreplica ${ADJUST_TEST_FILE}\nruntest \"### Check     \" unix SUCCESS \"\" eosj fileinfo \"${ADJUST_TEST_FILE} | jq -r .locations[].fsid | wc -l | grep 2\"\nruntest \"### Rm        \" unix SUCCESS \"\" eos rm -F ${ADJUST_TEST_FILE}\nruntest \"### Rmdir     \" unix SUCCESS \"\" eos rmdir ${ADJUST_TEST_DIR}\n\n# --------------------------\n# Parallel sockets (does not work currently with 3.1.0 clients)\n# --------------------------\n#runtest \"### UploadP   \" unix SUCCESS \"\" upload  \"$TESTSYSFILE50M\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.50k\" \"-S10\"\n#runtest \"### Download  \" unix SUCCESS \"\" download \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.50k\"\n#runtest \"### DownloadP \" unix SUCCESS \"\" download \"$EOS_TEST_TESTSYS/dumpit\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/file.50k\" \"-S10\"\n\n################################################################################\n# eoscp tests\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"eoscp\"\n# ------------------------------------------------------------------------------\nruntest \"### Setup     \" unix SUCCESS \"\" shell ln -s /etc/passwd /tmp/passwd.ln\nruntest \"### eoscp     \" unix SUCCESS \"EOSROLE='-r 0 0'\" eos cp -d /etc/passwd /eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp/passwd.eoscp\nruntest \"### eoscp     \" unix SUCCESS \"EOSROLE='-r 0 0'\" eos cp /eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp/passwd.eoscp -\nruntest \"### eoscp     \" unix SUCCESS \"EOSROLE='-r 0 0'\" eos cp /eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp/passwd.eoscp /eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp/passwd.eoscp.1\nruntest \"### eoscp     \" unix SUCCESS \"EOSROLE='-r 0 0'\" eos cp root://$EOS_TEST_REDIRECTOR//eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp/passwd.eoscp.1 root://$EOS_TEST_REDIRECTOR//eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp/passwd.eoscp.2\nruntest \"### eoscp     \" unix SUCCESS \"EOSROLE='-r 0 0'\" eos cp root://$EOS_TEST_REDIRECTOR//eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp/passwd.eoscp.2 /tmp/\nruntest \"### eoscp     \" unix ERROR \"EOSROLE='-r 0 0'\" eos cp /etc/passwd /eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp/a/b/c/\nruntest \"### eoscp     \" unix SUCCESS \"EOSROLE='-r 0 0'\" eos cp -p /etc/passwd /eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp/a/b/c/\nruntest \"### eoscp     \" unix SUCCESS \"EOSROLE='-r 0 0'\" eos cp -p /eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp/* /eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp2\nruntest \"### eoscp     \" unix SUCCESS \"EOSROLE='-r 0 0'\" eos cp -r /eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp /eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp3\nruntest \"### eoscp     \" unix SUCCESS \"EOSROLE='-r 0 0'\" eos cp /tmp/passwd.ln /eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp/passwd.eoscp3\nruntest \"### Symlink-S \" unix SUCCESS \"\" eos ln /eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp/passwd.eoscp.ln /eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp/passwd.eoscp3\nruntest \"### eoscp     \" unix SUCCESS \"EOSROLE='-r 0 0'\" eos cp /eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp/passwd.eoscp.ln /tmp/\nruntest \"### Cleanup   \" unix SUCCESS \"\" shell rm /tmp/passwd.ln /tmp/passwd.eoscp.2 /tmp/passwd.eoscp.ln\nruntest \"### Cleanup   \" unix SUCCESS \"\" eos rm -r /eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp\nruntest \"### Cleanup   \" unix SUCCESS \"\" eos rm -r /eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp2\nruntest \"### Cleanup   \" unix SUCCESS \"\" eos rm -r /eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp3\n\n################################################################################\n# eos FUSE tests\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"fuse\"\n# ------------------------------------------------------------------------------\nif [ -e \"/eos/$EOS_TEST_INSTANCE\" ];\nthen\necho \"### Running FUSE tests ... disabling krb5/x509 ...\"\nexport X509_USER_PROXY=/tmp/invalid\nexport KRB5CCNAME=/tmp/invalid\n\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"-p\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 755 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse\"\nruntest \"### FUSE cp   \" unix ERROR \"\" shell cp \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/file.1k\"\nruntest \"### FUSE touch\" unix SUCCESS \"\" shell stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/touch\"\nruntest \"### FUSE cat  \" unix SUCCESS \"\" shell stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/touch\"\nruntest \"### FUSE rm   \" unix SUCCESS \"\" shell stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/touch\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse\"\nruntest \"### FUSE cp   \" unix SUCCESS \"\" shell cp \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/file.1k\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 755 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse\"\nruntest \"### FUSE mkdr \" unix ERROR \"\" shell mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.forbidden\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse\"\nruntest \"### FUSE mkdr \" unix SUCCESS \"\" shell mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed\"\nruntest \"### FUSE mkdr \" unix SUCCESS \"\" shell mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed2\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 755 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse\"\nruntest \"### FUSE mkdr \" unix ERROR \"\" shell rmdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse\"\nruntest \"### FUSE mkdr \" unix SUCCESS \"\" shell rmdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/\"\nruntest \"### FUSE chmd \" unix ERROR \"\" shell chmod 755 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/\"\nruntest \"### Chown     \" unix SUCCESS \"\" eos chown 2:2 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse\"\nruntest \"### FUSE chmd \" unix SUCCESS \"\" shell chmod 555 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse\"\nruntest \"### FUSE mv   \" unix ERROR \"\" shell mv \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/file.1k\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/file.1k.forbidden\"\nruntest \"### FUSE chmd \" unix SUCCESS \"\" shell chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse\"\nruntest \"### FUSE mv   \" unix SUCCESS \"\" shell mv \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/file.1k\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/file.1k.forbidden\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 555 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed2\"\nruntest \"### FUSE mv   \" unix ERROR \"\" shell mv \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/file.1k.forbidden\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed2\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed2\"\nruntest \"### FUSE mv   \" unix SUCCESS \"\" shell mv \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/file.1k.forbidden\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed2\"\nruntest \"### FUSE mv   \" unix SUCCESS \"\" shell mv \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed2\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 555 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 555 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/\"\nruntest \"### rm        \" unix ERROR \"\" shell rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed\"\nruntest \"### rm        \" unix ERROR \"\" shell rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed/file.1k.forbidden\"\nruntest \"### rmdir     \" unix ERROR \"\" shell rmdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed/\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed\"\nruntest \"### rmdir     \" unix ERROR \"\" shell rmdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed/\"\nruntest \"### rm        \" unix SUCCESS \"\" shell rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed/file.1k.forbidden\"\nruntest \"### rmdir     \" unix SUCCESS \"\" shell rmdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir.allowed/\"\nruntest \"### touch sp  \" unix SUCCESS \"\" shell touch \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/file\\ 1\"\nruntest \"### mv sp     \" unix SUCCESS \"\" shell mv \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/file\\ 1\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/file\\ 2\"\nruntest \"### stat sp   \" unix ERROR \"\" shell stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/file\\ 1\"\nruntest \"### stat sp   \" unix SUCCESS \"\" shell stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/file\\ 2\"\nruntest \"### rm sp     \" unix SUCCESS \"\" shell rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/file\\ 2\"\nruntest \"### FUSE mkdr \" unix SUCCESS \"\" eos mkdir -p \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/\"\nruntest \"### FUSE mkdr \" unix SUCCESS \"\" mkdir -p \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir1/\"\nruntest \"### FUSE lnk  \" unix SUCCESS \"\" shell ln -s \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir1\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/link1\"\nruntest \"### FUSE lnk  \" unix SUCCESS \"\" shell ln -s \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir1\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/link1\"\nruntest \"### FUSE lnk  \" unix ERROR \"\" shell ln -s \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir1\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/link1\"\nruntest \"### FUSE mkdr \" unix SUCCESS \"\" shell mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/link1/dir2\"\nruntest \"### FUSE stat \" unix SUCCESS \"\" shell stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/link1/dir2\"\nruntest \"### FUSE stat \" unix SUCCESS \"\" shell stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fuse/dir1/dir2\"\nfi\n\nunset X509_USER_PROXY\nunset KRB5CCNAME\n################################################################################\n# eos FUSE tests\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"fusex\"\n# ------------------------------------------------------------------------------\n\necho \"### Running FUSEX tests ... disabling krb5/x509 ...\"\nexport X509_USER_PROXY=/tmp/invalid\nexport KRB5CCNAME=/tmp/invalid\n\ntest -d /test-fusex/ && umount -fl /test-fusex/\ntest -d /test-fusex/ || mkdir /test-fusex/\n\ntest -e /etc/eos/fuse.sss.keytab && mv -f /etc/eos/fuse.sss.keytab /etc/eos/fuse.sss.keytab.save\ngrep eosnobody $SERVERKEY > /etc/eos/fuse.sss.keytab\nchmod 444 /etc/eos/fuse.sss.keytab\n\n# make sure /var/run/eod is ensured everytime a daemon is running... at least\nrm -rfv /var/run/eos\neosxd -ofsname=localhost.localdomain:/eos /test-fusex/\nls -la /test-fusex/\n\nruntest \"### Stat /run/eos \" unix SUCCESS \"\" test \"$(stat -c '%a' /var/run/eos/credentials)\" = 1777 -a \"$(stat -c '%a' /var/run/eos/credentials/store)\" = 1777\n\nif [ -e \"/test-fusex/$EOS_TEST_INSTANCE\" ];\nthen\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"-p\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex\"\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set user.fusex.rename.version=1 /eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set sys.versioning=3 /eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"-p\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir1/\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"-p\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir2/\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/f1\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/f1\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/group\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/f2\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/group\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/f2\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/group\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/f2\"\nruntest \"### Fusex-Stat\" unix SUCCESS \"\" shell stat \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex\"\nruntest \"### Fusex-Stat\" unix SUCCESS \"\" shell stat \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/f1\"\nruntest \"### Fusex-Stat\" unix SUCCESS \"\" shell stat \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/f2\"\nruntest \"### Versions  \" unix SUCCESS \"\" eos file versions \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/f1 | wc -l | grep -w 1\"\nruntest \"### Versions  \" unix SUCCESS \"\" eos file versions \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/f2 | wc -l | grep -w 2\"\nruntest \"### Rename    \" unix SUCCESS \"\" shell mv -f /test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/f1 /test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/f2\nruntest \"### Versions  \" unix ERROR \"\" eos file versions \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/f1 | wc -l | grep -w 1\"\nruntest \"### Versions  \" unix SUCCESS \"\" eos file versions \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/f2 | wc -l | grep -w 1\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir1/f1\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir1/f1\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir1/f1\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir2/f1\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir2/f1\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir1/f2\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir2/f2\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir1/r1\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir1/r1\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir1/r1\"\n# version overwriting\nruntest \"### Versions  \" unix SUCCESS \"\" eos file versions \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir1/f1 | wc -l | grep -w 2\"\nruntest \"### Versions  \" unix SUCCESS \"\" eos file versions \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir2/f1 | wc -l | grep -w 1\"\nruntest \"### Rename    \" unix SUCCESS \"\" shell mv -f /test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir1/f1 /test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir2/f1\nruntest \"### Rename    \" unix SUCCESS \"\" shell mv -f /test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir1/f2 /test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir2/f2\nruntest \"### Delay       \" unix SUCCESS \"\" delay 1\nruntest \"### Versions  \" unix SUCCESS \"\" eos file versions \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir2/f2 | wc -l | grep -w 1\"\nruntest \"### Versions  \" unix SUCCESS \"\" eos file versions \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir2/f1 | wc -l | grep -w 2\"\n# rename including versions\nruntest \"### Versions  \" unix SUCCESS \"\" eos file versions \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir1/r1 | wc -l | grep -w 2\"\nruntest \"### Rename    \" unix SUCCESS \"\" shell mv -f /test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir1/r1 /test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir1/r2\nruntest \"### Delay       \" unix SUCCESS \"\" delay 1\nruntest \"### Versions  \" unix SUCCESS \"\" eos file versions \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir1/r2 | wc -l | grep -w 2\"\n# move including versions\nruntest \"### Rename    \" unix SUCCESS \"\" shell mv -f /test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir1/r2 /test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir2/r1\nruntest \"### Delay       \" unix SUCCESS \"\" delay 1\nruntest \"### Versions  \" unix SUCCESS \"\" eos file versions \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/dir2/r1 | wc -l | grep -w 2\"\n#\nruntest \"### Upload    \" unix SUCCESS \"\" shell cp /etc/group \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/perm\"\nruntest \"### Chmod     \" unix SUCCESS \"\" shell chmod 444 \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/perm\"\nruntest \"### test -x   \" unix ERROR \"\" shell test -x \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/perm\"\nruntest \"### Chmod     \" unix SUCCESS \"\" shell chmod 744 \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/perm\"\nruntest \"### test -x   \" unix SUCCESS \"\" shell test -x \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/perm\"\nruntest \"### Chmod     \" unix SUCCESS \"\" shell chmod 474 \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/perm\"\nruntest \"### test -x   \" unix SUCCESS \"\" shell test -x \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/perm\"\nruntest \"### Chmod     \" unix SUCCESS \"\" shell chmod 447 \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/perm\"\nruntest \"### test -x   \" unix SUCCESS \"\" shell test -x \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/perm\"\nruntest \"### symlink   \" unix SUCCESS \"\" shell ln -s \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/target\" \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/symlink.1\"\nruntest \"### symlink   \" unix SUCCESS \"\" shell ln -s \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/target\" \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/symlink.2\"\nruntest \"### syj-mkd   \" unix SUCCESS \"\" shell mkdir \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/symdir/\"\nruntest \"### Delay       \" unix SUCCESS \"\" delay 3\nruntest \"### stat l1   \" unix SUCCESS \"\" eos stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/symlink.1\"\nruntest \"### stat l2   \" unix SUCCESS \"\" eos stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/symlink.2\"\nruntest \"### sym-mv    \" unix SUCCESS \"\" shell mv -f \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/symlink.1\" \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/symlink.3\"\nruntest \"### sym-mv    \" unix SUCCESS \"\" shell mv -f \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/symlink.2\" \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/symdir/symlink.4\"\nruntest \"### Delay       \" unix SUCCESS \"\" delay 3\nruntest \"### stat l1   \" unix ERROR \"\" eos stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/symlink.1\"\nruntest \"### stat l2   \" unix ERROR \"\" eos stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/symlink.2\"\nruntest \"### stat l1   \" unix SUCCESS \"\" eos stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/symlink.3\"\nruntest \"### stat l2   \" unix SUCCESS \"\" eos stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/symdir/symlink.4\"\n#hardlinks\nruntest \"### Recycle   \" unix SUCCESS \"\" eos recycle config --add-bin /eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/hardlink\nruntest \"### mkdir     \" unix SUCCESS \"\" shell mkdir -p \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/hardlink/\"\nruntest \"### cp        \" unix SUCCESS \"\" shell cp /etc/group \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/hardlink/file\"\nruntest \"### link      \" unix SUCCESS \"\" shell cd \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/hardlink/ && ln file file.hardlink\"\ndd of=\"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/hardlink/file.hardlink\" oflag=nocache conv=notrunc,fdatasync count=0\nruntest \"### cp        \" unix SUCCESS \"\" shell cp /etc/passwd \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/hardlink/newfile\"\nruntest \"### cp        \" unix SUCCESS \"\" shell mv -f \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/hardlink/newfile\" \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/hardlink/file\"\nruntest \"### Diff      \" unix SUCCESS \"\" shell diff /etc/group \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/hardlink/file.hardlink\"\nruntest \"### Rm        \" unix SUCCESS \"\" eos rm -r /eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/hardlink\n\n#obfuscation\nruntest \"### Obfuscate \" unix SUCCESS \"\" shell mkdir \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/\"\nruntest \"### Obfuscate \" unix SUCCESS \"\" shell eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/\"\nruntest \"### Obfuscate \" unix SUCCESS \"\" shell eos attr set sys.file.obfuscate=1 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/\"\nruntest \"### Upload    \" unix SUCCESS \"\" shell cp /etc/group \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/group\"\nruntest \"### Upload    \" unix SUCCESS \"\" shell cp /etc/group \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/group\"\nruntest \"### Download  \" unix SUCCESS \"\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/group?eos.obfuscate=0\" \"/var/tmp/obfuscate.raw\"\nruntest \"### Download  \" unix SUCCESS \"\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/group\" \"/var/tmp/obfuscate\"\nruntest \"### Diff      \" unix SUCCESS \"\" shell diff /etc/group  \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/group\"\nruntest \"### Diff      \" unix ERROR \"\" shell diff /etc/group  \"/var/tmp/obfuscate.raw\"\nruntest \"### Diff      \" unix SUCCESS \"\" shell diff /etc/group  \"/var/tmp/obfuscate\"\nruntest \"### Diff      \" unix SUCCESS \"\" shell rm  \"/var/tmp/obfuscate\"\nruntest \"### Cleanup\"    unix SUCCESS \"\" eos rm  \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/group\"\n#encryption\nruntest \"### Id        \" unix SUCCESS \"\" shell env EOS_FUSE_SECRET=bla eosxd get eos.reconnect \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/\"\nruntest \"### Upload    \" unix SUCCESS \"EOS_FUSE_SECRET=12e629ee-e68a-4a5b-9743-33ba8f794e07\" eos_bash cp /etc/group \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/encrypted\"\nruntest \"### Download  \" unix SUCCESS \"EOS_FUSE_SECRET=12e629ee-e68a-4a5b-9743-33ba8f794e07\" eos cp /etc/group \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/encrypted.cp\"\nruntest \"### Dowload   \" unix SUCCESS \"EOS_FUSE_SECRET=12e629ee-e68a-4a5b-9743-33ba8f794e07\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/encrypted /var/tmp/encrypted.ok\"\nruntest \"### Dowload   \" unix SUCCESS \"EOS_FUSE_SECRET=false\" eos cp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/encrypted /var/tmp/encrypted.false\"\nruntest \"### Diff      \" unix SUCCESS \"\" eos_bash diff /etc/group  /var/tmp/encrypted.ok\nruntest \"### Diff      \" unix ERROR \"\" eos_bash diff /etc/group  /var/tmp/encrypted.false\n\n# drop from buffer cache\ndd of=\"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/encrypted\" oflag=nocache conv=notrunc,fdatasync count=0\nruntest \"### Diff      \" unix SUCCESS \"EOS_FUSE_SECRET=12e629ee-e68a-4a5b-9743-33ba8f794e07\" eos_bash diff /etc/group  \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/encrypted\"\nruntest \"### Diff      \" unix SUCCESS \"EOS_FUSE_SECRET=12e629ee-e68a-4a5b-9743-33ba8f794e07\" eos_bash diff /etc/group  \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/encrypted.cp\"\ndd of=\"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/encrypted\" oflag=nocache conv=notrunc,fdatasync count=0\nruntest \"### Diff      \" unix ERROR \"EOS_FUSE_SECRET=false\" eos_bash diff /etc/group  \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/encrypted\"\ndd of=\"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/encrypted\" oflag=nocache conv=notrunc,fdatasync count=0\nruntest \"### Diff      \" unix SUCCESS \"EOS_FUSE_SECRET=12e629ee-e68a-4a5b-9743-33ba8f794e07\" eos_bash diff /etc/group  \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/encrypted\"\ndd of=\"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/encrypted\" oflag=nocache conv=notrunc,fdatasync count=0\nruntest \"### Diff      \" unix ERROR \"\" eos_bash diff /etc/group  \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/obfuscate/encrypted\"\nruntest \"### defaultcc \" unix SUCCESS \"\" eos_defaultcc \"/test-fusex/\"\nruntest \"### sycntime  \" unix SUCCESS \"\" eos_synctime \"/test-fusex/\" \"$EOS_TEST_INSTANCE/test/instancetest/fusex/\"\n\n# enable sss now\nchmod 400 /etc/eos/fuse.sss.keytab\nruntest \"### EnableTok \" unix SUCCESS \"\" eos space config default space.token.generation=1\nexport EXPIRE=`date +%s`; let LATER=$EXPIRE+9000\nexport EOSTOKENR=`/usr/bin/eos token --path /eos/ --permission rx --tree --owner nobody --group nobody --expires $LATER`\nexport EOSTOKENW=`/usr/bin/eos token --path /eos/ --permission rwx --tree --owner nobody --group nobody --expires $LATER`\nruntest \"### Id        \" unix SUCCESS \"\" shell eosxd get eos.reconnect \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/\"\n# tokensudo arg tests\nruntest \"### tokensudo \" unix SUCCESS \"\" eos vid tokensudo always\nruntest \"### tokensudo \" unix SUCCESS \"\" eos vid tokensudo encrypted\nruntest \"### tokensudo \" unix SUCCESS \"\" eos vid tokensudo strong\nruntest \"### tokensudo \" unix SUCCESS \"\" eos vid tokensudo never\nruntest \"### tokensudo \" unix ERROR \"\" eos vid tokensudo bla\nruntest \"### tokensudo \" unix SUCCESS \"\" eos vid tokensudo always\n\n# token via fusex tests\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/token\"\nruntest \"### Chown     \" unix SUCCESS \"\" eos chown root:root \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/token\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 700 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/token\"\nruntest \"### LsFuse    \" unix ERROR \"\" shell ls -la \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/token/\"\nruntest \"### LsRoot    \" unix SUCCESS \"\" eos ls -la \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/token/\"\nruntest \"### LsNobody  \" unix ERROR \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos ls -la \"/eos/$EOS_TEST_INSTANCE/test/instancetest/fusex/token/\"\nruntest \"### EosToken  \" unix SUCCESS \"XrdSecsssENDORSEMENT=$EOSTOKENR\" shell eosxd get eos.reconnect \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/token\"\nruntest \"### WhoToken  \" unix SUCCESS \"XrdSecsssENDORSEMENT=$EOSTOKENR\" shell cat \"/test-fusex/$EOS_TEST_INSTANCE/proc/whoami\"\nruntest \"### LsToken   \" unix SUCCESS \"XrdSecsssENDORSEMENT=$EOSTOKENR\" shell ls -la \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/token/\"\nruntest \"### MkToken   \" unix ERROR \"XrdSecsssENDORSEMENT=$EOSTOKENR\" shell mkdir \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/token/forbidden\"\nruntest \"### ConToken  \" unix SUCCESS \"XrdSecsssENDORSEMENT=$EOSTOKENW\" shell eosxd get eos.reconnect \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/token\"\nruntest \"### WhoToken  \" unix SUCCESS \"XrdSecsssENDORSEMENT=$EOSTOKENR\" shell cat \"/test-fusex/$EOS_TEST_INSTANCE/proc/whoami\"\nruntest \"### MkToken   \" unix SUCCESS \"XrdSecsssENDORSEMENT=$EOSTOKENW\" shell mkdir \"/test-fusex/$EOS_TEST_INSTANCE/test/instancetest/fusex/token/allowed\"\n\ntest -d /test-fusex/ && umount -fl /test-fusex/\ntest -d /test-fusex/ && rmdir /test-fusex/\ntest -e /etc/eos/fuse.sss.keytab && unlink /etc/eos/fuse.sss.keytab\nfi\n\nunset X509_USER_PROXY\nunset KRB5CCNAME\n\n################################################################################\n# eos rclone test\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"rclone\"\n# ------------------------------------------------------------------------------\nruntest \"### rclone    \" unix SUCCESS \"\" eos_rclone \"/var/tmp/eos-instance-test/\" \"$EOS_TEST_INSTANCE/test/instancetest/rclone/\"\n\n################################################################################\n# eos squashfs tests\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"squashfs\"\n# ------------------------------------------------------------------------------\n\nexport X509_USER_PROXY=/tmp/invalid\nexport KRB5CCNAME=/tmp/invalid\n\n\ntest -d /eos/ && umount -fl /eos/\ntest -d /eos/ || mkdir /eos/\n\neosxd -ofsname=localhost.localdomain:/eos /eos/\nls -la /eos/\n\nif [ -e \"/eos/$EOS_TEST_INSTANCE\" ]; then\n# simple sqash packages\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/squash\"\nruntest \"### Recycle   \" unix SUCCESS \"\" eos recycle config --add-bin /eos/$EOS_TEST_INSTANCE/test/instancetest/\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/squash\"\nruntest \"### New SQFS  \" unix SUCCESS \"\" eos squash new \"/eos/$EOS_TEST_INSTANCE/test/instancetest/squash/package1\"\nruntest \"### cp        \" unix SUCCESS \"\" shell cp /etc/passwd /eos/$EOS_TEST_INSTANCE/test/instancetest/squash/package1\nruntest \"### Pack SQFS \" unix SUCCESS \"\" eos squash pack \"/eos/$EOS_TEST_INSTANCE/test/instancetest/squash/package1\"\nruntest \"### Pack SQFS \" unix ERROR \"\" eos squash pack \"/eos/$EOS_TEST_INSTANCE/test/instancetest/squash/package1\"\nruntest \"### Pack SQFS \" unix ERROR \"\" eos squash pack \"/eos/$EOS_TEST_INSTANCE/test/instancetest/squash/package2\"\nruntest \"### info SQFS \" unix SUCCESS \"\" eos squash info \"/eos/$EOS_TEST_INSTANCE/test/instancetest/squash/package1\"\nruntest \"### rm label  \" unix SUCCESS \"\" eos rm /eos/$EOS_TEST_INSTANCE/test/instancetest/squash/package1\nruntest \"### label SQFS\" unix SUCCESS \"\" eos squash relabel /eos/$EOS_TEST_INSTANCE/test/instancetest/squash/package1\nruntest \"### label SQFS\" unix ERROR \"\" eos squash relabel /eos/$EOS_TEST_INSTANCE/test/instancetest/squash/package2\nruntest \"### rm SQFS   \" unix SUCCESS \"\" eos squash rm /eos/$EOS_TEST_INSTANCE/test/instancetest/squash/package1\nruntest \"### stat SQFS \" unix ERROR \"\" eos stat /eos/$EOS_TEST_INSTANCE/test/instancetest/squash/package1\nruntest \"### stat SQFS \" unix ERROR \"\" eos stat /eos/$EOS_TEST_INSTANCE/test/instancetest/squash/.package1.sqsh\n\n# squash releases\nruntest \"### New SQFSR \" unix SUCCESS \"\" eos squash new-release \"/eos/$EOS_TEST_INSTANCE/test/instancetest/squash/release1\"\nruntest \"### cp        \" unix SUCCESS \"\" shell cp /etc/passwd /eos/$EOS_TEST_INSTANCE/test/instancetest/squash/release1/next\nruntest \"### Pack SQFSR\" unix SUCCESS \"\" eos squash pack-release \"/eos/$EOS_TEST_INSTANCE/test/instancetest/squash/release1\"\nruntest \"### Delay       \" unix SUCCESS \"\" delay 1\nruntest \"### New SQFSR \" unix SUCCESS \"\" eos squash new-release \"/eos/$EOS_TEST_INSTANCE/test/instancetest/squash/release1 1.0.1\"\nruntest \"### cp        \" unix SUCCESS \"\" shell cp /etc/passwd /eos/$EOS_TEST_INSTANCE/test/instancetest/squash/release1/next\nruntest \"### Pack SQFSR\" unix SUCCESS \"\" eos squash pack-release \"/eos/$EOS_TEST_INSTANCE/test/instancetest/squash/release1\"\nruntest \"### info SQFSR\" unix SUCCESS \"\" eos squash info-release \"/eos/$EOS_TEST_INSTANCE/test/instancetest/squash/release1\"\nruntest \"### info SQFSR\" unix ERROR \"\" eos squash info-release \"/eos/$EOS_TEST_INSTANCE/test/instancetest/squash/release2\"\nruntest \"### trim SQFSR\" unix SUCCESS \"\" eos squash trim-release \"/eos/$EOS_TEST_INSTANCE/test/instancetest/squash/release1 1 1 \"\nruntest \"### trim SQFSR\" unix ERROR \"\" eos squash trim-release \"/eos/$EOS_TEST_INSTANCE/test/instancetest/squash/release1 0 0 \"\nruntest \"### rm SQFSR\"   unix SUCCESS \"\" eos squash rm-release \"/eos/$EOS_TEST_INSTANCE/test/instancetest/squash/release1\"\nruntest \"### rm SQFSR\"   unix ERROR \"\" eos squash rm-release \"/eos/$EOS_TEST_INSTANCE/test/instancetest/squash/release1\"\nruntest \"### Rm        \" unix SUCCESS \"\" eos rm -r /eos/$EOS_TEST_INSTANCE/test/instancetest/squash/\nfi\n\ntest -d /eos/ && umount -fl /eos/\ntest -d /eos/ && rmdir /eos/\n\ntest -e /etc/eos/fuse.sss.keytab.save && mv -f /etc/eos/fuse.sss.keytab.save /etc/eos/fuse.sss.keytab\n\nunset X509_USER_PROXY\nunset KRB5CCNAME\n\n################################################################################\n# Third party copy tests\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"tpc\"\n# ------------------------------------------------------------------------------\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/tpc\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/tpc\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload    \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/tpc/file.tpc.1\"\nif [ ${XRD_VERS_MAJOR} -eq 5 ]; then\nruntest \"### TPC 1     \" unix SUCCESS \"\" tpc1 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/tpc/file.tpc.1\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/tpc/file.tpc.2\"\nelif [ ${XRD_VERS_MAJOR} -eq 4 ]; then\nruntest \"### TPC 1     \" unix SUCCESS \"\" tpc1 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/tpc/file.tpc.1\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/tpc/file.tpc.2\"\nelse\nruntest \"### TPC 1     \" unix SUCCESS \"\" tpc2 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/tpc/file.tpc.1\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/tpc/file.tpc.2\"\nfi\nruntest \"### TPC 2     \" unix SUCCESS \"\" tpc2 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/tpc/file.tpc.1\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/tpc/file.tpc.3\"\nruntest \"### Rm        \" unix SUCCESS \"\" eos rm -r /eos/$EOS_TEST_INSTANCE/test/instancetest/tpc\n\n################################################################################\n# Versioning tests\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"versioning\"\n# ------------------------------------------------------------------------------\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod \"777\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning\"\nruntest \"### Mkdir     \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/.sys.v#.bla\"\nruntest \"### Attr Ls   \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos attr ls \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/.sys.v#.bla | wc -l | grep -w 1\"\nruntest \"### Rmdir     \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos rmdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/.sys.v#.bla\"\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set sys.versioning=3 /eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/passwd\"\nsleep 1.1 # force the first file to fall in a different tv_sec interval\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/passwd\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/passwd\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/passwd\"\nruntest \"### Ls        \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos ls \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/.sys.v#.passwd | wc -l | grep -w 3\"\nruntest \"### File Vers.\" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos file version /eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/passwd 99\nruntest \"### File Vers.\" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos file version /eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/passwd 99\nruntest \"### Ls        \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos ls \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/.sys.v#.passwd | wc -l | grep -w 5\"\nruntest \"### File Vers.\" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos file purge /eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/passwd 4\nruntest \"### Ls        \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos ls \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/.sys.v#.passwd | wc -l | grep -w 4\"\nruntest \"### File Vers.\" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos file purge /eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/passwd -1\nruntest \"### Ls        \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos ls \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/.sys.v#.passwd | wc -l | grep -w 3\"\nruntest \"### File Vers.\" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos file purge /eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/passwd 0\nruntest \"### Stat      \" unix ERROR \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/.sys.v#.passwd\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/passwd\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/passwd\"\nruntest \"### Stat      \" unix SUCCESS \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/.sys.v#.passwd\"\nruntest \"### Rm        \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos rm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/passwd\"\nruntest \"### Stat      \" unix ERROR \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/.sys.v#.passwd\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/passwd\"\nsleep 1.1 # force the first file to fall in a different tv_sec interval\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/passwd\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/passwd\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/passwd\"\nruntest \"### Find Purge\" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos find --purge -1 /eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/\nruntest \"### Ls        \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos ls \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/.sys.v#.passwd | wc -l | grep -w 3\"\nruntest \"### Find Purge\" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos find --purge 1 /eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/\nruntest \"### Ls        \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos ls \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/.sys.v#.passwd | wc -l | grep -w 1\"\nruntest \"### Find Purge\" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos find --purge 0 /eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/\nruntest \"### Stat      \" unix ERROR \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/.sys.v#.passwd\"\n# Versioning test when acls control the permission for the user on the folder and\n# implicitly also for the creation of the versions directory.\nruntest \"### Mkdir     \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/acl_versions/\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod \"2550\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/acl_versions/\"\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set sys.acl=\"u:${NOBODY_UID}:rwx\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/acl_versions/\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/acl_versions/file.dat\"\nruntest \"### Upload    \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos cp \"/etc/passwd\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/acl_versions/file.dat\"\nruntest \"### Ls        \" unix SUCCESS \"EOSROLE='-r ${NOBODY_UID} ${NOBODY_GID}'\" eos ls \"/eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/acl_versions/.sys.v#.file.dat | wc -l | grep -w 1\"\nruntest \"### Cleanup\"    unix IGNORE  \"\" eos rm  \"-r /eos/$EOS_TEST_INSTANCE/test/instancetest/versioning/acl_versions/\"\nruntest \"### Cleanup\"    unix IGNORE  \"\" eos rm  \"-r /eos/$EOS_TEST_INSTANCE/test/instancetest/versioning\"\n\n################################################################################\n# HTTP tests\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"http\"\n# ------------------------------------------------------------------------------\ncacert=\"/etc/grid-security/certificates/rootCA.pem\"\ncapath=\"$(dirname \"${cacert}\")\"\ncert=\"/root/.globus/usercert.pem\"\nkey=\"/root/.globus/userkey.pem\"\n# ------------------------------------------------------------------------------\nHTTPDIR=eos/$EOS_TEST_INSTANCE/test/instancetest/http.dir\nruntest \"### Mkdir        \" unix SUCCESS \"\" eos mkdir $HTTPDIR\nruntest \"### Chmod        \" unix SUCCESS \"\" eos chmod 777 $HTTPDIR\nruntest \"### Http PUT     \" unix SUCCESS \"\" curl -v --capath \"${capath}\" --cert \"${cert}\" --cacert \"${cacert}\" --key \"${key}\" --location --fail --silent --upload-file $TESTSYSFILE50M https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/$HTTPDIR/file.50m\nruntest \"### Http GET     \" unix SUCCESS \"\" curl -v --capath \"${capath}\" --cert \"${cert}\" --cacert \"${cacert}\" --key \"${key}\" --location --fail --silent https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/$HTTPDIR/file.50m --output $EOS_TEST_TESTSYS/dumpit\nruntest \"### Chmod        \" unix SUCCESS \"\" eos chmod 644 $HTTPDIR\nruntest \"### Http PUT     \" unix ERROR \"\" curl -v --capath \"${capath}\" --cert \"${cert}\" --cacert \"${cacert}\" --key \"${key}\" --location --fail --silent --upload-file $TESTSYSFILE50M https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/$HTTPDIR/file.50m\nruntest \"### Http GET     \" unix ERROR \"\" curl -v --capath \"${capath}\" --cert \"${cert}\" --cacert \"${cacert}\" --key \"${key}\" --location --fail --silent https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/$HTTPDIR/file.50m --output $EOS_TEST_TESTSYS/dumpit\nruntest \"### Chmod        \" unix SUCCESS \"\" eos chmod 777 $HTTPDIR\n\n# --------------------------\n# Range requests\n# --------------------------\nruntest \"### Http GET     \" unix SUCCESS \"\" curl -v --capath \"${capath}\" --cert \"${cert}\" --cacert \"${cacert}\" --key \"${key}\" --location --fail --silent --range 0-499 https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/$HTTPDIR/file.50m --output $EOS_TEST_TESTSYS/dumpit\nruntest \"### Http GET     \" unix SUCCESS \"\" curl -v --capath \"${capath}\" --cert \"${cert}\" --cacert \"${cacert}\" --key \"${key}\" --location --fail --silent --range 0-499,500-999 https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/$HTTPDIR/file.50m --output $EOS_TEST_TESTSYS/dumpit\nruntest \"### Http GET     \" unix SUCCESS \"\" curl -v --capath \"${capath}\" --cert \"${cert}\" --cacert \"${cacert}\" --key \"${key}\" --location --fail --silent --range 0-499,1000-1499 https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/$HTTPDIR/file.50m --output $EOS_TEST_TESTSYS/dumpit\nruntest \"### Http GET     \" unix SUCCESS \"\" curl -v --capath \"${capath}\" --cert \"${cert}\" --cacert \"${cacert}\" --key \"${key}\" --location --fail --silent --range 50000000- https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/$HTTPDIR/file.50m --output $EOS_TEST_TESTSYS/dumpit\nruntest \"### Http GET     \" unix ERROR \"\" curl -v --capath \"${capath}\" --cert \"${cert}\" --cacert \"${cacert}\" --key \"${key}\" --location --fail --silent --range 51200000-512000000 https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/$HTTPDIR/file.50m --output $EOS_TEST_TESTSYS/dumpit\nruntest \"### HttpXUpload  \" unix SUCCESS \"\" eos attr set sys.forced.checksum=adler $HTTPDIR\nruntest \"### HttpXUpload  \" unix SUCCESS \"\" shell eos-http-upload-test rangeupload multipartfile /$HTTPDIR/\nruntest \"### Cleanup      \" unix SUCCESS \"\" eos rm \"-r\" \"/$HTTPDIR\"\n\n# ------------------------------------------------------------------------------\ncategorie=\"gateway\"\n# ------------------------------------------------------------------------------\n# Create structure\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload    \"$TESTSYSFILE1K\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway/file.gateway.1\"\n# We will use the \"bin user\"\nruntest \"### Chown     \" unix SUCCESS \"\" eos chown -r bin:bin \"/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 700 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway\"\nruntest \"### Access    \" unix SUCCESS \"\" eos access allow user bin\nruntest \"### Vid       \" unix SUCCESS \"\" eos vid set membership 1 -sudo\n# Create a dummy key so that the gateway authentication can work\nruntest \"### Vid       \" unix SUCCESS \"\" eos vid set map -https key:1234 vuid:1 vgid:1\n# Try to read the file without impersonation, it should fail\nruntest \"### Http GET  \" unix ERROR \"\" curl -v -H X-Gateway-Authorization:1234 -H X-Forwarded-For:dummy --location --fail https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway/file.gateway.1 --output $EOS_TEST_TESTSYS/dumpit\n# As the bin user (uid=1) is not sudoer, the file should not be readable\nruntest \"### Http GET  \" unix ERROR \"\" curl -v -H X-Gateway-Authorization:1234 -H X-Forwarded-For:dummy -H Remote-User:1 --location --fail https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway/file.gateway.1 --output $EOS_TEST_TESTSYS/dumpit\n# Set the bin user as a sudoer\nruntest \"### Vid       \" unix SUCCESS \"\" eos vid set membership 1 +sudo\n# Impersonate by himself should allow to read the file\nruntest \"### Http GET  \" unix SUCCESS \"\" curl -v -H X-Gateway-Authorization:1234 -H X-Forwarded-For:dummy -H Remote-User:1 --location --fail https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway/file.gateway.1 --output $EOS_TEST_TESTSYS/dumpit\n# Impersonate to a non-authorized user will not allow to read the file\nruntest \"### Http GET  \" unix ERROR \"\" curl -v -H X-Gateway-Authorization:1234 -H X-Forwarded-For:dummy -H Remote-User:3 --location --fail https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway/file.gateway.1--output $EOS_TEST_TESTSYS/dumpit\n# Change the owner of the directory to the adm user (uid=3)\nruntest \"### Chown     \" unix SUCCESS \"\" eos chown -r adm:adm \"/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway\"\n# The bin user should not be able to access the file anymore\nruntest \"### Http GET  \" unix ERROR \"\" curl -v -H X-Gateway-Authorization:1234 -H X-Forwarded-For:dummy -H Remote-User:1  --location --fail https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway/file.gateway.1 --output $EOS_TEST_TESTSYS/dumpit\n# Allow adm to access the instance\nruntest \"### Access    \" unix SUCCESS \"\" eos access allow user adm\n# Remove the bin user from the sudoer, we should not be able to read the file as we cannot impersonate\nruntest \"### Vid       \" unix SUCCESS \"\" eos vid set membership 1 -sudo\nruntest \"### Http GET  \" unix ERROR \"\" curl -v -H X-Gateway-Authorization:1234 -H X-Forwarded-For:dummy -H Remote-User:3 --location --fail https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway/file.gateway.1 --output $EOS_TEST_TESTSYS/dumpit\n# Add the bin user to the sudoer and verity the file can be read if we impersonate adm (uid=3)\nruntest \"### Vid       \" unix SUCCESS \"\" eos vid set membership 1 +sudo\nruntest \"### Http GET  \" unix SUCCESS \"\" curl -v -H X-Gateway-Authorization:1234 -H X-Forwarded-For:dummy -H Remote-User:3 --location --fail https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway/file.gateway.1 --output $EOS_TEST_TESTSYS/dumpit\n# Read the file again without any impersonation\nruntest \"### Http GET  \" unix ERROR \"\" curl -v -H X-Gateway-Authorization:1234 -H X-Forwarded-For:dummy --location --fail https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway/file.gateway.1 --output $EOS_TEST_TESTSYS/dumpit\n# Impersonate with the header 'Remote-User' and the username instead of the user id\nruntest \"### Http GET  \" unix SUCCESS \"\" curl -v -H X-Gateway-Authorization:1234 -H X-Forwarded-For:dummy -H Remote-User:adm --location --fail https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway/file.gateway.1 --output $EOS_TEST_TESTSYS/dumpit\nruntest \"### Http GET  \" unix ERROR \"\" curl -v -H X-Gateway-Authorization:1234 -H X-Forwarded-For:dummy -H Remote-User:bin --location --fail https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway/file.gateway.1 --output $EOS_TEST_TESTSYS/dumpit\n# Remove sudoer and retry the impersonation with the 'Remote-User'\nruntest \"### Cleanup   \" unix SUCCESS \"\" eos vid set membership 1 -sudo\nruntest \"### Http GET  \" unix ERROR \"\" curl -v -H X-Gateway-Authorization:1234 -H X-Forwarded-For:dummy -H Remote-User:adm --location --fail https://$EOS_TEST_FULL_REDIRECTOR:${EOS_HTTPS_PORT}/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway/file.gateway.1 --output $EOS_TEST_TESTSYS/dumpit\n# Cleanup\nruntest \"### Cleanup   \" unix SUCCESS \"\" eos access unallow user bin\nruntest \"### Cleanup   \" unix SUCCESS \"\" eos access unallow user adm\nruntest \"### Cleanup   \" unix SUCCESS \"\" eos vid rm 'vid:https:\\\"key:1234\\\":gid'\nruntest \"### Cleanup   \" unix SUCCESS \"\" eos vid rm 'vid:https:\\\"key:1234\\\":uid'\nruntest \"### Cleanup   \" unix SUCCESS \"\" eos rm -r \"/eos/$EOS_TEST_INSTANCE/test/instancetest/gateway\"\n\n################################################################################\n# WebDAV tests\n################################################################################\n# # ------------------------------------------------------------------------------\n# categorie=\"dav\"\n# # ------------------------------------------------------------------------------\n# DAVDIR=eos/$EOS_TEST_INSTANCE/test/instancetest/dav.dir\n# runtest \"### Mkdir        \" unix SUCCESS \"\" eos mkdir /$DAVDIR\n# runtest \"### Chmod        \" unix SUCCESS \"\" eos chmod 777 /$DAVDIR\n# runtest \"### Upload       \" unix SUCCESS \"\" upload \"$TESTSYSFILE50M\" \"/$DAVDIR/file.50m\"\n#\n# bundle=\"$(dirname ${cert})/userbundle.p12\"\n# # generate user bundle as cadaver seems to have replacee cert and key options in favor of client-cert\n# openssl pkcs12 -export -out \"${bundle}\" -inkey \"${key}\" -in \"${cert}\" -passout pass:\n#\n# # dirty hack to get cadaver https to work:\n# # - passing the hostname -f to the command fails\n# # - interactively opening a connection to the same address works?!?!\n# echo \"set client-cert ${bundle}\"                                             > $EOS_TEST_TESTSYS/.cadaverrc\n# echo \"open https://$(hostname -f):${EOS_HTTPS_PORT}/${DAVDIR}\"              >> $EOS_TEST_TESTSYS/.cadaverrc\n# runtest \"### WebDAV Ls    \" unix SUCCESS \"\" tgrep \"succeeded\" \"cadaver --rcfile=$EOS_TEST_TESTSYS/.cadaverrc       <<< ls\"\n# runtest \"### WebDAV Ls    \" unix SUCCESS \"\" tgrep \"404\"       \"cadaver --rcfile=$EOS_TEST_TESTSYS/.cadaverrc       <<< \\\"ls foo\\\"\"\n# # open the connection to $DAVDIR/foo\n# runtest \"### WebDAV Ls    \" unix SUCCESS \"\" tgrep \"404\"       \"cadaver --rcfile=<(sed '$ s/$/\\/foo/' $EOS_TEST_TESTSYS/.cadaverrc)  <<< ls\"\n# runtest \"### WebDAV Mkdir \" unix SUCCESS \"\" tgrep \"succeeded\" \"cadaver --rcfile=$EOS_TEST_TESTSYS/.cadaverrc       <<< \\\"mkdir sub.dir\\\"\"\n# runtest \"### WebDAV Ls    \" unix SUCCESS \"\" tgrep \"sub.dir\"   \"cadaver --rcfile=$EOS_TEST_TESTSYS/.cadaverrc       <<< ls\"\n# runtest \"### WebDAV Mkdir \" unix SUCCESS \"\" tgrep \"409\"       \"cadaver --rcfile=$EOS_TEST_TESTSYS/.cadaverrc       <<< \\\"mkdir foo/bar/sub.dir\\\"\"\n#\n# # Disable overwrite\n# runtest \"### WebDAV Copy  \" unix SUCCESS \"\" tgrep \"succeeded\" \"cadaver --rcfile=$EOS_TEST_TESTSYS/.cadaverrc <<< \\\"copy file.50m file.50m.copy\\\"\"\n# runtest \"### WebDAV Copy  \" unix SUCCESS \"\" tgrep \"succeeded\" \"cadaver --rcfile=$EOS_TEST_TESTSYS/.cadaverrc <<< \\\"copy file.50m file.50m.copy\\\"\"\n# runtest \"### WebDAV Copy  \" unix SUCCESS \"\" tgrep \"403\"       \"cadaver --rcfile=$EOS_TEST_TESTSYS/.cadaverrc <<< \\\"copy file.50m file.50m\\\"\"\n# runtest \"### WebDAV Copy  \" unix SUCCESS \"\" tgrep \"409\"       \"cadaver --rcfile=$EOS_TEST_TESTSYS/.cadaverrc <<< \\\"copy file.50m foo/bar/file.50m\\\"\"\n# runtest \"### WebDAV Copy  \" unix SUCCESS \"\" tgrep \"412\"       \"cadaver --rcfile=<(sed '$a unset overwrite' $EOS_TEST_TESTSYS/.cadaverrc) <<< \\\"copy file.50m file.50m.copy\\\"\"\n# runtest \"### WebDAV Move  \" unix SUCCESS \"\" tgrep \"succeeded\" \"cadaver --rcfile=$EOS_TEST_TESTSYS/.cadaverrc <<< \\\"move file.50m file.50m.copy\\\"\"\n# runtest \"### WebDAV Copy  \" unix SUCCESS \"\" tgrep \"succeeded\" \"cadaver --rcfile=$EOS_TEST_TESTSYS/.cadaverrc <<< \\\"copy file.50m.copy file.50m\\\"\"\n# runtest \"### WebDAV Move  \" unix SUCCESS \"\" tgrep \"succeeded\" \"cadaver --rcfile=$EOS_TEST_TESTSYS/.cadaverrc <<< \\\"move file.50m file.50m.copy\\\"\"\n# runtest \"### WebDAV Move  \" unix SUCCESS \"\" tgrep \"403\"       \"cadaver --rcfile=$EOS_TEST_TESTSYS/.cadaverrc <<< \\\"move file.50m file.50m\\\"\"\n# runtest \"### WebDAV Move  \" unix SUCCESS \"\" tgrep \"409\"       \"cadaver --rcfile=$EOS_TEST_TESTSYS/.cadaverrc <<< \\\"move file.50m foo/bar/file.50m\\\"\"\n# runtest \"### WebDAV Copy  \" unix SUCCESS \"\" tgrep \"succeeded\" \"cadaver --rcfile=$EOS_TEST_TESTSYS/.cadaverrc <<< \\\"copy file.50m.copy file.50m\\\"\"\n# runtest \"### WebDAV Move  \" unix SUCCESS \"\" tgrep \"412\"       \"cadaver --rcfile=<(sed '$aunset overwrite' $EOS_TEST_TESTSYS/.cadaverrc) <<< \\\"move file.50m.copy file.50m\\\"\"\n# runtest \"### WebDAV Rm    \" unix SUCCESS \"\" tgrep \"succeeded\" \"cadaver --rcfile=$EOS_TEST_TESTSYS/.cadaverrc <<< \\\"rm file.50m\\\"\"\n# runtest \"### WebDAV Rm    \" unix SUCCESS \"\" tgrep \"succeeded\" \"cadaver --rcfile=$EOS_TEST_TESTSYS/.cadaverrc <<< \\\"rmcol sub.dir\\\"\"\n# runtest \"### Cleanup      \" unix SUCCESS \"\" eos rm \"-r\" \"/$DAVDIR\"\n################################################################################\n# Amazon S3 tests\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"s3\"\n# ------------------------------------------------------------------------------\nexport S3_ACCESS_KEY_ID=\"s3user\"\nexport S3_SECRET_ACCESS_KEY=\"0123456789\"\nexport S3_HOSTNAME=\"$(hostname -f)\"\n\n#----------------------------------------------------------------------------\n# Prepare the setup for S3 tests\n#----------------------------------------------------------------------------\nruntest \"### Prepare user \" unix SUCCESS \"\" shell adduser -M s3user\nruntest \"### Prepare dir  \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/s3.dir\"\nruntest \"### Prepare mod  \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/s3.dir\"\n\nruntest \"### S3 Attr      \" unix SUCCESS \"\" eos attr set sys.s3.bucket.s3user=\"test\" /eos/$EOS_TEST_INSTANCE/proc\nruntest \"### S3 Attr      \" unix SUCCESS \"\" eos attr set sys.s3.id.s3user=\"0123456789\" /eos/$EOS_TEST_INSTANCE/proc\nruntest \"### S3 Attr      \" unix SUCCESS \"\" eos attr set sys.s3.path.test=\"/eos/$EOS_TEST_INSTANCE/test/instancetest/s3.dir\" /eos/$EOS_TEST_INSTANCE/proc\n# ------------------------------------------------------------------------------\nruntest \"### S3 List      \" unix SUCCESS \"\" shell davix-ls  --s3accesskey s3user --s3secretkey 0123456789 --s3alternate s3://${EOS_TEST_REDIRECTOR}:${EOS_HTTPS_PORT}/test/\nruntest \"### S3 Upload    \" unix SUCCESS \"\" shell davix-put --s3accesskey s3user --s3secretkey 0123456789 --s3alternate $TESTSYSFILE50M s3://${EOS_TEST_REDIRECTOR}:${EOS_HTTPS_PORT}/test/file.50m\nruntest \"### S3 List      \" unix SUCCESS \"\" shell davix-ls  --s3accesskey s3user --s3secretkey 0123456789 --s3alternate s3://${EOS_TEST_REDIRECTOR}:${EOS_HTTPS_PORT}/test\nruntest \"### S3 Download  \" unix SUCCESS \"\" shell davix-get --s3accesskey s3user --s3secretkey 0123456789 --s3alternate s3://${EOS_TEST_REDIRECTOR}:${EOS_HTTPS_PORT}/test/file.50m file.50m\nruntest \"### S3 Rm        \" unix SUCCESS \"\" shell davix-rm  --s3accesskey s3user --s3secretkey 0123456789 --s3alternate s3://${EOS_TEST_REDIRECTOR}:${EOS_HTTPS_PORT}/test/file.50m\nruntest \"### S3 List      \" unix SUCCESS \"\" shell davix-ls  --s3accesskey s3user --s3secretkey 0123456789 --s3alternate s3://${EOS_TEST_REDIRECTOR}:${EOS_HTTPS_PORT}/test/\nruntest \"### S3 Download  \" unix ERROR \"\" shell davix-get --s3accesskey s3user --s3secretkey 0123456789 --s3alternate s3://${EOS_TEST_REDIRECTOR}:${EOS_HTTPS_PORT}/test/file.50m\nruntest \"### S3 List      \" unix ERROR \"\" shell davix-ls  --s3accesskey s3user --s3secretkey 0123456789 --s3alternate s3://${EOS_TEST_REDIRECTOR}:${EOS_HTTPS_PORT}/otherdir/\n\n#----------------------------------------------------------------------------\n# Clean up the files created in S3 tests\n#----------------------------------------------------------------------------\nruntest \"### Cleanup   \" unix SUCCESS \"\" shell userdel s3user\nruntest \"### Cleanup   \" unix IGNORE  \"\" eos rm \"-r\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/s3.dir\"\nruntest \"### Cleanup   \" unix IGNORE  \"\" shell rm -f file.50m\n\n################################################################################\n# Owncloud tests\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"owncloud\"\n# ------------------------------------------------------------------------------\nruntest \"### Cleanup   \" unix IGNORE  \"\" eos rm \"-r\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/oc\"\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/oc\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/oc\"\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set default=replica \"/eos/$EOS_TEST_INSTANCE/test/instancetest/oc\"\n\n# upload with chunking\nruntest \"### OcChunk   \" unix SUCCESS \"\" shell eos-oc-test chunkedupload chunked /eos/$EOS_TEST_INSTANCE/test/instancetest/oc/\n\n# check it exists\nruntest \"### Stat      \" unix SUCCESS \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/oc/chunked\"\nruntest \"### OcChunk   \" unix ERROR \"\" shell eos-oc-test chunkedupload chunked\n# upload again\nruntest \"### OcChunk   \" unix SUCCESS \"\" shell eos-oc-test chunkedupload chunked /eos/$EOS_TEST_INSTANCE/test/instancetest/oc/\n# check that there is no version\nruntest \"### Stat      \" unix ERROR \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/oc/.sys.v#.chunked/\"\nruntest \"### Stat      \" unix SUCCESS \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/oc/chunked\"\n# enable versioning\nruntest \"### Attr Set  \" unix SUCCESS \"\" eos attr set sys.versioning=2 /eos/$EOS_TEST_INSTANCE/test/instancetest/oc/\nruntest \"### OcChunk   \" unix SUCCESS \"\" shell eos-oc-test chunkedupload chunked /eos/$EOS_TEST_INSTANCE/test/instancetest/oc/\nruntest \"### OcChunk   \" unix SUCCESS \"\" shell eos-oc-test chunkedupload chunked /eos/$EOS_TEST_INSTANCE/test/instancetest/oc/\n# check that versions are kept as expected\nruntest \"### Stat      \" unix SUCCESS \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/test/instancetest/oc/.sys.v#.chunked/\"\nruntest \"### Find      \" unix SUCCESS \"\" eos find \"-f --count /eos/$EOS_TEST_INSTANCE/test/instancetest/oc | grep nfiles=3\"\n\n#----------------------------------------------------------------------------\n# Clean up the files created in owncloud tests\n#----------------------------------------------------------------------------\nruntest \"### Cleanup   \" unix IGNORE  \"\" eos rm \"-r\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/oc\"\n\n\n################################################################################\n# GRPC tests\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"grpc\"\n# ------------------------------------------------------------------------------\nruntest \"### eos-grpc  \" unix SUCCESS \"\" eos_grpc \"/eos/$EOS_TEST_INSTANCE/test/instancetest/grpc.dir/\"\n\n################################################################################\n# Accounting tests\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"accounting\"\n# ------------------------------------------------------------------------------\nruntest \"### eos-accounting  \" unix SUCCESS \"\" eos_accounting \"/eos/$EOS_TEST_INSTANCE/test/instancetest/accounting_test\"\n\n################################################################################\n# lru tests\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"lru\"\n# ------------------------------------------------------------------------------\nruntest \"### eos-lru  \" unix SUCCESS \"\" eos_lru \"/eos/$EOS_TEST_INSTANCE/test/instancetest/lru_test\"\n\n################################################################################\n# Token tests\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"token\"\n# ------------------------------------------------------------------------------\nruntest \"### eos-token  \" unix SUCCESS \"\" eos_token \"/eos/$EOS_TEST_INSTANCE/test/instancetest/token.dir/\"\n\ncategorie=\"traffic_shaping\"\n\n# --- Basic Engine & Stats Tests ---\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping ls\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping ls --window 5\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping ls --window 60 --sys\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping ls --window 300 --json\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping disable\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping enable\n\n# --- Application Policy Tests ---\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy set --app test --limit-write 5M\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy set --app test --limit-write 10M --disable\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy set --app test --enable\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy set --app test --limit-write 10M --limit-read 5M\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy set --app test --reservation-write 2M --reservation-read 1M\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy set --app test --disable\n\n# --- User (UID) Policy Tests ---\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy set --uid 1000 --limit-write 20M\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy set --uid 1000 --limit-write 20M --limit-read 10M\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy set --uid 1000 --reservation-write 5M\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy set --uid 1000 --disable\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy set --uid 1000 --enable\n\n# --- Group (GID) Policy Tests ---\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy set --gid 2000 --limit-write 30M\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy set --gid 2000 --limit-write 30M --limit-read 15M\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy set --gid 2000 --reservation-read 10M\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy set --gid 2000 --disable\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy set --gid 2000 --enable\n\n# --- Listing & Serialization Tests ---\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy ls\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy ls --json\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy ls --controller\n\n# --- Teardown / Cleanup Tests ---\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy rm --app test\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy rm --uid 1000\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy rm --gid 2000\nruntest \"### Traffic Shaping \" unix SUCCESS \"\" eos io shaping policy ls\n\n################################################################################\n# RAIN-LIKE LAYOUT TESTS FOR WR/RD AND RECOVERY\n# !!!RUN ONLY IF AT LEAST 6 FSTs PRESENT!!!\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"rain\"\n# ------------------------------------------------------------------------------\nNUM_FST=$(command eos -b $EOSROLE root://$EOS_TEST_REDIRECTOR fs ls | awk '{ if (($6 ~ /booted/) || ($7 ~ /booted/)) c++} END {print c}')\nif [ $NUM_FST -ge 6 ]; then\n   # Create temporary RAIN test file outside EOS\n   export EOS_TEST_RAIN_TMP=/tmp/rain_test\n   mkdir -p $EOS_TEST_RAIN_TMP\n\n   # Create the RAIDDP directory and set the proper attributes\n   runtest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raiddp\"\n   runtest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raiddp\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.checksum=crc32c \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raiddp\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.blockchecksum=crc32c \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raiddp\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.blocksize=1M \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raiddp\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.layout=raiddp \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raiddp\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.nstripes=6 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raiddp\"\n\n   # Create the RAID6 directory and set the proper attributes\n   runtest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6\"\n   runtest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.checksum=crc32c \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.blockchecksum=crc32c \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.blocksize=1M \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.layout=raid6 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.nstripes=6 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6\"\n\n   # Create the RAID6 directory with 4MB stripe block size\n   runtest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6_4M\"\n   runtest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6_4M\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.checksum=crc32c \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6_4M\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.blockchecksum=crc32c \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6_4M\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.blocksize=4M \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6_4M\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.layout=raid6 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6_4M\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.nstripes=6 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6_4M\"\n\n   # Qrain test (4 parity stripes )\n   runtest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/qrain\"\n   runtest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/qrain\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.checksum=crc32c \"/eos/$EOS_TEST_INSTANCE/test/instancetest/qrain\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.blockchecksum=crc32c \"/eos/$EOS_TEST_INSTANCE/test/instancetest/qrain\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.blocksize=1M \"/eos/$EOS_TEST_INSTANCE/test/instancetest/qrain\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.layout=qrain \"/eos/$EOS_TEST_INSTANCE/test/instancetest/qrain\"\n   runtest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.nstripes=6 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/qrain\"\n\n   runtest \"### RaiddpTest \" unix SUCCESS \"\" eos_rain \"raiddp\" \"$EOS_TEST_RAIN_DIR\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raiddp\" \"$EOS_TEST_RAIN_TMP\"\n   # Sleep two seconds between the tests as disabling/enabling a FST is not that fast\n   runtest \"### Delay      \" unix SUCCESS \"\" delay 2\n   runtest \"### Raid6Test  \" unix SUCCESS \"\" eos_rain \"raid6\" \"$EOS_TEST_RAIN_DIR\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6\" \"$EOS_TEST_RAIN_TMP\"\n   runtest \"### Delay      \" unix SUCCESS \"\" delay 2\n   runtest \"### QrainTest  \" unix SUCCESS \"\" eos_rain \"qrain\" \"$EOS_TEST_RAIN_DIR\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/qrain\" \"$EOS_TEST_RAIN_TMP\"\n   runtest \"### Delay      \" unix SUCCESS \"\" delay 2\n\n   # Run the eos-io-test for sequential and non-streaming IO against the RAIN\n   # directories\n   runtest \"### EosIoRaiddp  \" unix SUCCESS \"\" eos_io \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raiddp\" \"$EOS_TEST_RAIN_DIR\"\n   runtest \"### EosIoRaid6   \" unix SUCCESS \"\" eos_io \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6\" \"$EOS_TEST_RAIN_DIR\"\n   runtest \"### EosIoQrain   \" unix SUCCESS \"\" eos_io \"/eos/$EOS_TEST_INSTANCE/test/instancetest/qrain\" \"$EOS_TEST_RAIN_DIR\"\n\n   # Run xrdcpnonstreaming test to make sure the final file checksum which\n   # requires re-reading the entire file for computation is correct\n   TESTFILE_NONSTREAMING=$EOS_TEST_TESTSYS/cp_non_streaming.dat\n   dd if=/dev/urandom of=$TESTFILE_NONSTREAMING bs=1M count=40 > /dev/null 2>&1\n   CKS_NONSTREAMING=`eos-checksum crc32c $TESTFILE_NONSTREAMING | awk '{print $4}' | sed s/crc32c=//`\n   runtest \"### NStreamDp     \" unix SUCCESS \"\" cp_nonstreaming  $TESTFILE_NONSTREAMING \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raiddp/eos_non_stream.dat\"\n   eos fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raiddp/eos_non_stream.dat\"\n   runtest \"### XsRaiddp      \" unix SUCCESS \"\" eosj fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raiddp/eos_non_stream.dat | jq -r '.checksumvalue' | grep $CKS_NONSTREAMING\"\n   runtest \"### NStream6      \" unix SUCCESS \"\" cp_nonstreaming $TESTFILE_NONSTREAMING \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6/eos_non_stream.dat\"\n   runtest \"### XsRaid6       \" unix SUCCESS \"\" eosj fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6/eos_non_stream.dat | jq -r '.checksumvalue' | grep $CKS_NONSTREAMING\"\n   runtest \"### NStream6_4M   \" unix SUCCESS \"\" cp_nonstreaming $TESTFILE_NONSTREAMING \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6_4M/eos_non_stream.dat\"\n   runtest \"### XsRaid6_4M    \" unix SUCCESS \"\" eosj fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6_4M/eos_non_stream.dat | jq -r '.checksumvalue' | grep $CKS_NONSTREAMING\"\n   runtest \"### NStreamQ      \" unix SUCCESS \"\" cp_nonstreaming $TESTFILE_NONSTREAMING \"/eos/$EOS_TEST_INSTANCE/test/instancetest/qrain/eos_non_stream.dat\"\n   runtest \"### XsQrain       \" unix SUCCESS \"\" eosj fileinfo \"/eos/$EOS_TEST_INSTANCE/test/instancetest/qrain/eos_non_stream.dat | jq -r '.checksumvalue' | grep $CKS_NONSTREAMING\"\n\n   # Run simple test to check the RAIN PIO mode which is by default enabled when doing eoscp\n   runtest \"### eoscp pio raiddp      \" unix SUCCESS \"\" shell eoscp \"root://$EOS_TEST_REDIRECTOR//eos/$EOS_TEST_INSTANCE/test/instancetest/raiddp/eos_non_stream.dat\" \"$EOS_TEST_RAIN_TMP/pio_raiddp_check.dat\"\n   runtest \"### eoscp pio xs raiddp   \" unix SUCCESS \"\" shell eos-checksum crc32 \"$EOS_TEST_RAIN_TMP/pio_raiddp_check.dat\" | grep $CKS_NONSTREAMING\n   runtest \"### eoscp pio raid6       \" unix SUCCESS \"\" shell eoscp \"root://$EOS_TEST_REDIRECTOR//eos/$EOS_TEST_INSTANCE/test/instancetest/raid6/eos_non_stream.dat\" \"$EOS_TEST_RAIN_TMP/pio_raid6_check.dat\"\n   runtest \"### eoscp pio xs raid6    \" unix SUCCESS \"\" shell eos-checksum crc32 \"$EOS_TEST_RAIN_TMP/pio_raid6_check.dat\" | grep $CKS_NONSTREAMING\n   runtest \"### eoscp pio raid6_4M    \" unix SUCCESS \"\" shell eoscp \"root://$EOS_TEST_REDIRECTOR//eos/$EOS_TEST_INSTANCE/test/instancetest/raid6_4M/eos_non_stream.dat\" \"$EOS_TEST_RAIN_TMP/pio_raid6_4M_check.dat\"\n   runtest \"### eoscp pio xs raid6_4M \" unix SUCCESS \"\" shell eos-checksum crc32 \"$EOS_TEST_RAIN_TMP/pio_raid6_4M_check.dat\" | grep $CKS_NONSTREAMING\n   runtest \"### eoscp pio qrain       \" unix SUCCESS \"\" shell eoscp \"root://$EOS_TEST_REDIRECTOR//eos/$EOS_TEST_INSTANCE/test/instancetest/qrain/eos_non_stream.dat\" \"$EOS_TEST_RAIN_TMP/pio_qrain_check.dat\"\n   runtest \"### eoscp pio xs qrain    \" unix SUCCESS \"\" shell eos-checksum crc32 \"$EOS_TEST_RAIN_TMP/pio_qrain_check.dat\" | grep $CKS_NONSTREAMING\n\n   # Clean up the files used for the RAIN test\n   runtest \"### Cleanup   \" unix IGNORE  \"\" eos rm \"-r\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raiddp\"\n   runtest \"### Cleanup   \" unix IGNORE  \"\" eos rm \"-r\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/raid6\"\n   runtest \"### Cleanup   \" unix IGNORE  \"\" eos rm \"-r\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/qrain\"\n   rm -rf $EOS_TEST_RAIN_TMP\nelse\n  echo \"Can not run RAIN test - not enough FSTs!\"\nfi\n\n#-------------------------------------------------------------------------------\n# Create PLAIN directory and set the attributes for the eoscp-rain-test\n# Add a file used for testing - this test can be done with just one FST\n#-------------------------------------------------------------------------------\nruntest \"### Mkdir     \" unix SUCCESS \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp_rain_dir\"\nruntest \"### Chmod     \" unix SUCCESS \"\" eos chmod 777 \"/eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp_rain_dir\"\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.checksum=crc32c \"/eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp_rain_dir\"\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.blockchecksum=crc32c \"/eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp_rain_dir\"\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.blocksize=1M \"/eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp_rain_dir\"\nruntest \"### Attr      \" unix SUCCESS \"\" eos attr set sys.forced.layout=plain \"/eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp_rain_dir\"\nruntest \"### Upload    \" unix SUCCESS \"\" upload \"$EOS_TEST_RAIN_DIR/file3.rain\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp_rain_dir/file.dat\"\nruntest \"### EoscpRaiddp \" unix SUCCESS \"\" eoscp_rain \"raiddp\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp_rain_dir/file.dat\"\nruntest \"### EoscpRaid6  \" unix SUCCESS \"\" eoscp_rain \"reeds\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp_rain_dir/file.dat\"\nruntest \"### Cleanup     \" unix IGNORE  \"\" eos rm \"-r\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest/eoscp_rain_dir\"\n\n################################################################################\n# OTHER TESTS\n################################################################################\nruntest \"### Ls max input \" unix ERROR \"\" eos ls /file1/file2/file3/file4/file5/file6/file7/file8/file9/file10/file11/file12/file13/file14/file15/file16/file17/file18/file19/file20/file21/file22/file23/file24/file25/file26/file27/file28/file29/file30/file31/file32/file33/file34/file35/file36/file37/file38/file39/file40/file41/file42/file43/file44/file45/file46/file47/file48/file49/file50/file51/file52/file53/file54/file55/file56/file57/file58/file59/file60/file61/file62/file63/file64/file65/file66/file67/file68/file69/file70/file71/file72/file73/file74/file75/file76/file77/file78/file79/file80/file81/file82/file83/file84/file85/file86/file87/file88/file89/file90/file91/file92/file93/file94/file95/file96/file97/file98/file99/file100/file101/file102/file103/file104/file105/file106/file107/file108/file109/file110/file111/file112/file113/file114/file115/file116/file117/file118/file119/file120/file121/file122/file123/file124/file125/file126/file127/file128/file129/file130/file131/file132/file133/file134/file135/file136/file137/file138/file139/file140/file141/file142/file143/file144/file145/file146/file147/file148/file149/file150/file151/file152/file153/file154/file155/file156/file157/file158/file159/file160/file161/file162/file163/file164/file165/file166/file167/file168/file169/file170/file171/file172/file173/file174/file175/file176/file177/file178/file179/file180/file181/file182/file183/file184/file185/file186/file187/file188/file189/file190/file191/file192/file193/file194/file195/file196/file197/file198/file199/file200/file201/file202/file203/file204/file205/file206/file207/file208/file209/file210/file211/file212/file213/file214/file215/file216/file217/file218/file219/file220/file221/file222/file223/file224/file225/file226/file227/file228/file229/file230/file231/file232/file233/file234/file235/file236/file237/file238/file239/file240/file241/file242/file243/file244/file245/file246/file247/file248/file249/file250/file251/file252/file253/file254/file255/file256/file257/file258/file259/file260/file261/file262/file263/file264/file265/file266/file267/file268/file269/file270/file271/file272/file273/file274/file275/file276/file277/file278/file279/file280/file281/file282/file283/file284/file285/file286/file287/file288/file289/file290/file291/file292/file293/file294/file295/file296/file297/file298/file299/file300\nexport LONG_NAME=\"$(head -c 4500 < /dev/zero | tr '\\0' '\\141')\"\nruntest \"### Touch long name  \" unix SUCCESS \"\" eos file touch \"/eos/$EOS_TEST_INSTANCE/test/${LONG_NAME}\"\nruntest \"### Ls long name     \" unix SUCCESS \"\" eos ls -l \"/eos/$EOS_TEST_INSTANCE/test/\"\nruntest \"### Remove long name \" unix SUCCESS \"\" eos rm -F \"/eos/$EOS_TEST_INSTANCE/test/${LONG_NAME}\"\nunset LONG_NAME\n\n################################################################################\n# HEALTH\n################################################################################\nruntest \"### Health    \" unix SUCCESS \"\" eos health \"-m | grep \\\"default\\.0\\\"\"\n\n################################################################################\n# FINAL CLEANUP\n################################################################################\n# ------------------------------------------------------------------------------\ncategorie=\"clean\"\n# ------------------------------------------------------------------------------\nruntest \"### Cleanup   \" unix IGNORE  \"\" eos rm \"-r\" \"/eos/$EOS_TEST_INSTANCE/test/stress\"\nruntest \"### Cleanup   \" unix IGNORE  \"\" eos rm \"-r\" \"/eos/$EOS_TEST_INSTANCE/test/instancetest\"\nruntest \"### Cleanup   \" unix IGNORE  \"\" eos rm \"-r\" \"/eos/$EOS_TEST_INSTANCE/test/rain\"\n################################################################################\n# show test summary\ntestout\nunlink $EOSTESTPID\nexit;\n################################################################################\n) | tee -a $EOSALLCERTLOG &\n\ntrap 'kill_processes' TERM\ntrap 'kill_processes' QUIT\ntrap 'kill_processes' INT\n\nsleep 1\ntestpid=`cat $EOSTESTPID 2>/dev/null`;\nfor wait in `seq 1 $EOS_TEST_TESTTIMESLICE`; do\n    kill -0 $testpid >& /dev/null\n    if [ $? -eq 0 ]; then\n        sleep 1;\n    else\n        break;\n    fi\ndone\n\nkill -0 $testpid >& /dev/null\n\nif [ $? -eq 0 ]; then\n touch $TIMEOUTFILE\n kill_child_processes $testpid;\nfi\n\n[ -e \"$TIMEOUTFILE\" ] && ( echo >> $EOSALLCERTLOG; echo \"error: timeout after $EOS_TEST_TESTTIMESLICE seconds\" >> $EOSALLCERTLOG; touch $FAILFILE; echo \"error: timeout after $EOS_TEST_TESTTIMESLICE seconds\";)\n\nunlink $TIMEOUTFILE >& /dev/null\n\n################################################################################\necho \"#--------------------------------------------------------------\"\necho \"# Log of the test results         : $EOSALLCERTLOG\"\necho \"# Log with individual test output : $EOSCERTLOG\"\necho \"#--------------------------------------------------------------\"\n################################################################################\nmailnotify\n################################################################################\n"
  },
  {
    "path": "test/eos-instance-test-ci",
    "content": "#!/bin/bash\n\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2019 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\n#-------------------------------------------------------------------------------\n# Run a given test script and check the return code is successful.\n# Arguments: $1  - text description of the test\n#            $*  - the script to execute + arguments\n#-------------------------------------------------------------------------------\nfunction run_test_script() {\n  local description=$1\n  shift\n\n  echo \"Running ${description} tests\"\n  eval \"$@\"\n\n  if [[ $? -ne 0 ]]; then\n    echo \"error: failed ${description} tests\"\n    exit 1\n  fi\n}\n\n#-------------------------------------------------------------------------------\n# Execute specific EOS testing scripts\n#-------------------------------------------------------------------------------\nif [[ -n \"${EOS_SCHEDULER_TYPE}\" ]]; then\n    echo \"Setting scheduler.type=${EOS_SCHEDULER_TYPE}\"\n    eos space config default space.scheduler.type=\"${EOS_SCHEDULER_TYPE}\"\nfi\n\n\nrun_test_script \"drain\" eos-drain-test localhost\nrun_test_script \"balance\" eos-balance-test localhost\nrun_test_script \"altxs\" eos-altxs-test localhost\n\necho \"Running instance tests ...\"\neos space quota default on\neos-instance-test\n\nif [[ $? -ne 0 ]]; then\n  cat /tmp/eos-instance-test/test-output.log\n  cat /tmp/eos-instance-test/test-result.log\n  exit 1\nfi\n\necho \"Final ns stats\"\neos ns stat\n\n#-------------------------------------------------------------------------------\n# Functional tests upload/download x509/macaroons/tpc/eos token\n#-------------------------------------------------------------------------------\nHTTPS_DIR=\"/eos/dockertest/test/https/\"\nrun_test_script \"HTTPS\" eos-https-functional-test -p 8443 --dir ${HTTPS_DIR}\n\neos space quota default off\nexit 0\n"
  },
  {
    "path": "test/eos-io-test",
    "content": "#!/usr/bin/env bash\n\n#------------------------------------------------------------------------------\n# File: eos-io-test\n# Author: Elvin-Alin Sindrilaru - CERN\n#------------------------------------------------------------------------------\n\n#/************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************/\n\n#------------------------------------------------------------------------------\n# Description: The script tests the IO layer in EOS by reading/writing files in \n#              sequential mode or using various pattern files i.e. patterns for \n#              reading/writing. It uses the eos-io-tool for doing all the IO \n#              operations. For a description of how the pattern files should\n#              look like see the eos-io-tool.\n# Usage: \n# eos-io-test root://host//eos_dir /ext_dir/test_files\n#\n#------------------------------------------------------------------------------\n\n. /etc/init.d/functions\n\nif [[ \"$LOGNAME\" != \"root\" ]]; then\n   SUDO=\"sudo\"\nelse \n  SUDO=\"\"\nfi\n\n#-------------------------------------------------------------------------------\n# Do sequential operations on each file in the source location by first writing\n# it to EOS, then reading it back and in the end verifying that the checksum of \n# the initial file matches the one of the file read back from EOS.\n#\n# @param $1 test directory in EOS, e.g. root://host//eos_dir (can be any layout)\n# @param $2 external directory containing the files to be used for the test \n#          e.g /local_dir/file.io. Only files ending in \".io\" will be used for\n#          testing.\n#\n#-------------------------------------------------------------------------------\nfunction write_read_sequential() \n{\n  echo \"Calling the write_read_sequential\"\n  local DEST=$1\n  local SRC=$2\n\n  # Loop through all the *.io files in the source directory and do test\n  for FPATH in $SRC/*.io; do\n    if [[ -f $FPATH ]]; then\n      local TMP_FILE=\"$FPATH.tmp\"\n      local FILE=$(basename $FPATH)\n      echo \"Do write_read_sequential for $FILE\"\n  \n      $SUDO eos-io-tool --operation wrsequ --eosfile $DEST/$FILE --extfile $FPATH --async\n\n      # Exit immediately if previous operation failed\n      if [[ $? -ne 0 ]]; then\n        echo_failure\n        exit 1\n      fi\n\n      $SUDO eos-io-tool --operation rdsequ --eosfile $DEST/$FILE --extfile $TMP_FILE --async\n\n      # Exit immediately if previous operation failed\n      if [[ $? -ne 0 ]]; then\n        echo_failure\n        exit 1\n      fi\n\n      # Compute the checksum of the initial file and the one read back\n      local XSINIT=$($SUDO sum $FPATH)\n      local XSCOPY=$($SUDO sum $TMP_FILE)\n\n      test \"$XSINIT\" = \"$XSCOPY\" \n      if [[ $? -ne 0 ]]; then\n        echo_failure\n       exit 1\n      fi \n    fi\n  done\n\n  # Clean up all the tmp files \n  $SUDO rm -rf $SRC/*.tmp\n}\n\n\n#-------------------------------------------------------------------------------\n# Do sparse IO operations on a file by first writing it to EOS, then reading\n# it back and in the end verifying that the checksum of the initial file matches\n# the one of the file read back from EOS. We use a special file which contains\n# the pattern used for reading and writing. e.g if the file is called file1.io\n# then we look of the pattern file file1.io.pattern\n#\n# @param $1 test directory in EOS, e.g. root://host//eos_dir (can be any layout)\n# @param $2 external directory containing the files to be used for the test and \n#           also the associated pattern files, e.g /local_dir/. Only files \n#           ending in \".io\" will be used for testing\n#\n#-------------------------------------------------------------------------------\nfunction write_read_pattern() \n{\n  echo \"Calling the write_read_pattern\"\n  local DEST=$1\n  local SRC=$2\n  \n  # Loop through all the *.io files in the source directory and do test\n  for FPATH in $SRC/*.io; do\n    if [[ -f $FPATH ]]; then\n      local TMP_FILE=\"$FPATH.tmp\"\n      local PATTERN_FILE=\"$FPATH.pattern\"\n\n      # If not associated pattern file then we continue\n      if [ ! -f $PATTERN_FILE ]; then\n        continue\n      fi\n\n      local FILE=$(basename $FPATH)\n      echo \"Do write_read_pattern for $FILE\"\n\n      $SUDO eos-io-tool --operation wrpatt --eosfile $DEST/$FILE --extfile $FPATH --patternfile $PATTERN_FILE --async\n      \n      # Exit immediately if previous operation failed\n      if [[ $? -ne 0 ]]; then\n        echo_failure\n        exit 1\n      fi\n\n      $SUDO eos-io-tool --operation rdpatt --eosfile $DEST/$FILE --extfile $TMP_FILE --patternfile $PATTERN_FILE --async\n\n      # Exit immediately if previous operation failed\n      if [[ $? -ne 0 ]]; then\n        echo_failure\n        exit 1\n      fi\n\n      # Compute the checksum of the initial file and the one read back\n      local XSINIT=$($SUDO sum $FPATH)\n      local XSCOPY=$($SUDO sum $TMP_FILE)\n\n      test \"$XSINIT\" = \"$XSCOPY\" \n      if [[ $? -ne 0 ]]; then\n        echo \"Wr pattern - checksum mismatch\"\n        echo_failure\n        exit 1\n      fi\n    fi\n  done\n\n  # Clean up all the tmp files \n  $SUDO rm -rf $SRC/*.tmp\n}\n\n\n#-------------------------------------------------------------------------------\n# Main part\n#\n# @param the first parameter of the script needs to be the location of the test\n#        directory in EOS. It can have any layout type. e.g root://host//eos_dir.\n# @param the second parameter is the external directory which contains the test\n#        files used by the eos-io-tool. The user needs to make sure that the \n#        pattern files needed for the non-sequential operations are present in \n#        in the same directory. If this is not the case, then the PATTERN test \n#        is skipped. Only files ending in \".io\" will be used for testing. The\n#        pattern files need to end in \".io.pattern\" for each corresponding test\n#        file.\n#-------------------------------------------------------------------------------\n\nif [[ $# -eq 0 || $# -gt 2 ]]; then \n  echo \"Usage: $0 root://host//eos_dir /local/test/dir\"\n  exit 1\nfi\n\nEOS_URL=$1\nLOCAL_DIR=$2\n\nwrite_read_sequential $EOS_URL $LOCAL_DIR\nwrite_read_pattern $EOS_URL $LOCAL_DIR\n\nexit 0\n"
  },
  {
    "path": "test/eos-lru-test",
    "content": "#!/bin/bash\n\n# Usage: eos-lru-test <eos_test_dir> <mgm_hostname>\n# Example: eos-lru-test /eos/dev/test/lru_test_dir localhost\n\nprefix=$1\nhost=${2-\"localhost\"}\nurl=root://$host\nEOS_LRU_DIR=$prefix\nwatermarkLruDir=\"$EOS_LRU_DIR/watermarkLruDir\"\n\ncleanup() {\n  eos quota rmnode --really-want \"$watermarkLruDir/\"\n  eos rm -rF --no-confirmation \"$EOS_LRU_DIR/\"\n}\n\nassert_eq() {\n  if [[ \"$1\" != \"$2\" ]];\n  then\n    echo \"error: $3, expected value is $1 but received $2\"\n    exit 1\n  fi\n}\n\ncleanup\n\n# Prepare the instance\neos convert clear\neos convert config set status=on\neos space config default space.lru=on\neos space config default space.lru.interval=5\n\n# --- Test the wildcard '*' as an expression for expiration ---\nwildcardLruDir=\"$EOS_LRU_DIR/wildcardLruDir\"\neos mkdir -p \"$wildcardLruDir\"\n# Use the pure wildcard to match everything older than 5s\neos attr set 'sys.lru.expire.match=*:5s' \"$wildcardLruDir\"\neos touch \"$wildcardLruDir/file1\"\neos touch \"$wildcardLruDir/file2\"\neos touch \"$wildcardLruDir/random_name\"\nassert_eq 3 \"$(eos ls \"$wildcardLruDir\" | wc -l)\" \"wildcard test: files should exist initially\"\n\nsleep 10\n# Everything should be gone if '*' is working\nassert_eq 0 \"$(eos ls \"$wildcardLruDir\" | wc -l)\" \"wildcard test: '*' expression failed to match and expire files\"\n\n# --- Test the LRU expire empty directory ---\neos mkdir -p \"$EOS_LRU_DIR/\" && eos touch \"$EOS_LRU_DIR/file.touched\"\neos attr set sys.lru.expire.empty=5 \"$EOS_LRU_DIR/\"\nemptyDir=\"$EOS_LRU_DIR/empty_dir\"\neos mkdir -p \"$emptyDir\"\nsleep 10\nassert_eq 0 \"$(eos ls \"$EOS_LRU_DIR\" | grep -c 'empty_dir')\" \"empty_dir test: $emptyDir should be removed\"\n\n# --- Test match with prefix ---\naccessTimeLruDir=\"$EOS_LRU_DIR/accessTimeLruDir\"\neos mkdir -p \"$accessTimeLruDir\"\neos attr set 'sys.lru.expire.match=touched*:5s' \"$accessTimeLruDir\"\nseq 1 100 | xargs -P 30 -I{} eos touch \"$accessTimeLruDir/touched{}\"\neos touch \"$accessTimeLruDir/should_not_be_deleted\"\nsleep 10\nassert_eq 1 \"$(eos ls \"$accessTimeLruDir\" | grep -c 'should_not_be_deleted')\" \"prefix match test failed\"\n\n# --- Test size-based match with wildcard ---\nsizeLruDir=\"$EOS_LRU_DIR/sizeLruDir\"\neos mkdir -p \"$sizeLruDir\"\neos attr set 'sys.lru.expire.match=*:5s:<1M' \"$sizeLruDir\"\neos touch \"$sizeLruDir/small_file\"\nsleep 10\nassert_eq 0 \"$(eos ls \"$sizeLruDir\" | wc -l)\" \"wildcard size-based LRU test: '*' with size constraint failed\"\n\n# --- Test the conversion LRU with wildcard ---\nconvertLruDir=\"$EOS_LRU_DIR/convertLruDir\"\neos mkdir -p \"$convertLruDir\"\noneKBFile=\"/tmp/1KB\"\nthreeKBFile=\"/tmp/3KB\"\nhead -c 1k < /dev/urandom > \"$oneKBFile\"\nhead -c 3k < /dev/urandom > \"$threeKBFile\"\neos attr set 'sys.conversion.*=20640542' \"$convertLruDir\"\n\n# Explicitly test the wildcard with size-based match for conversion LRU\neos attr set 'sys.lru.convert.match=*:4s:>2k' \"$convertLruDir\"\n\nseq 1 50 | xargs -I{} -P 30 eos cp $oneKBFile \"$url/$convertLruDir/1KB{}\"\nseq 1 50 | xargs -I{} -P 30 eos cp $threeKBFile \"$url/$convertLruDir/3KB{}\"\n\nTARGET_VALUE=50 # Only the 50 files > 2k should be queued\nTIMEOUT=25\nINTERVAL=1\nELAPSED=0\nSUCCESS=false\n\nwhile (( $(echo \"$ELAPSED < $TIMEOUT\" | bc -l) )); do\n    CURRENT_COUNT=$(eos convert list | grep -c '#')\n    if [ \"$CURRENT_COUNT\" -eq \"$TARGET_VALUE\" ]; then\n        SUCCESS=true\n        break\n    fi\n    sleep $INTERVAL\n    ELAPSED=$(echo \"$ELAPSED + $INTERVAL\" | bc -l)\ndone\n\nif [ \"$SUCCESS\" = false ]; then\n    echo \"Error: convert LRU test: wildcard '*' in conversion failed (count: $CURRENT_COUNT)\"\n    exit 1\nfi\n\n# --- Test watermark ---\nwatermarkLruDir=\"$EOS_LRU_DIR/watermarkLruDir\"\neos mkdir -p \"$watermarkLruDir\"\neos quota set -p \"$watermarkLruDir\" -v 11G -i 1M -g 99\neos attr set 'sys.lru.lowwatermark=1' \"$watermarkLruDir\"\neos attr set 'sys.lru.highwatermark=2' \"$watermarkLruDir\"\nhead -c 250M < /dev/urandom > /tmp/watermark_file\neos cp /tmp/watermark_file \"$url/$watermarkLruDir/will-be-deleted\"\nsleep 10\nassert_eq 0 \"$(eos ls \"$watermarkLruDir\" | grep -c 'will-be-deleted')\" \"watermark test failed\"\n\ncleanup\necho \"ALL LRU TESTS PASSED\"\nexit 0\n"
  },
  {
    "path": "test/eos-macaroon-init",
    "content": "#!/usr/bin/env python3\n\n\"\"\"\nGiven an X509 proxy, generate a dCache-style macaroon.\n\"\"\"\n\nfrom __future__ import print_function\n\nimport os\nimport sys\nimport json\nimport argparse\n\nimport requests\n\n\ndef parse_args():\n    \"\"\"\n    Parse command line arguments to this tool\n    \"\"\"\n    parser = argparse.ArgumentParser(\n                        description=\"Generate a macaroon for authorized transfers\")\n    parser.add_argument(\"url\", metavar=\"URL\",\n                        help=\"URL to generate macaroon for.\")\n    parser.add_argument(\"--activity\", nargs=\"+\", help=\"Activity for authorization (LIST,\"\n                        \"DOWNLOAD,UPLOAD, etc)\")\n    parser.add_argument(\"--validity\", type=int, default=10, help=\"Time,\"\n                        \"in minutes, the resulting macaroon should be valid.\",\n                        required=False)\n    return parser.parse_args()\n\n\ndef configure_authenticated_session():\n    \"\"\"\n    Generate a new session object for use with requests to the issuer.\n\n    Configures TLS appropriately to work with a GSI environment.\n    \"\"\"\n    euid = os.geteuid()\n    if euid == 0:\n        cert = '/etc/grid-security/hostcert.pem'\n        key = '/etc/grid-security/hostkey.pem'\n    else:\n        cert = '/tmp/x509up_u%d' % euid\n        key = '/tmp/x509up_u%d' % euid\n\n    cert = os.environ.get('X509_USER_PROXY', cert)\n    key = os.environ.get('X509_USER_PROXY', key)\n\n    session = requests.Session()\n\n    if os.path.exists(cert):\n        session.cert = cert\n    if os.path.exists(key):\n        session.cert = (cert, key)\n    session.verify = '/etc/grid-security/certificates'\n\n    return session\n\n\ndef generate_token(url, validity, activity):\n    \"\"\"\n    Call out to the macaroon issuer, using the specified validity and activity,\n    and receive a resulting token.\n    \"\"\"\n    print(\"Querying %s for new token.\" % url, file=sys.stderr)\n    validity = \"PT%dM\" % validity\n    print(\"Validity: %s, activities: %s.\" % (validity, \", \".join(activity)),\n          file=sys.stderr)\n    data_json = {\"caveats\": [\"activity:%s\" % \",\".join(activity)],\n                 \"validity\": validity}\n    with configure_authenticated_session() as session:\n        response = session.post(url,\n                                headers={\"Content-Type\": \"application/macaroon-request\"},\n                                data=json.dumps(data_json)\n                               )\n\n    if response.status_code == requests.codes.ok:\n        print(\"Successfully generated a new token:\", file=sys.stderr)\n        return response.text\n    else:\n        print(\"Issuer failed request (status %d): %s\" % (response.status_code, response.text[:2048]), file=sys.stderr)\n        sys.exit(1)\n\n\ndef main():\n    args = parse_args()\n    token = generate_token(args.url, args.validity, args.activity)\n    print(token)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "test/eos-manila-test",
    "content": "#!/bin/bash\n\ninstance=${1:=dev}\nauthkey=`uuidgen`;\ncreator=\"nobody\"\ndescription=\"this is a manila test share\"\nid=\"99\";\ngroup_id=\"99\";\negroup=\"eos-admins.cern.ch\"\nadmin_egroup=\"eos-admins.cern.ch\"\nlocation=\"localhost\"\nquota=\"100\"\nname=\"share-1\"\nprotocol=\"eos\"\nfailed=0\n\necho \"----------------------------------------------------------------------\"\necho \"# NO AUTH KEY\"\necho \"----------------------------------------------------------------------\"\neos-grpc-manila --command create --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 1 && failed=1\necho \"# response:$response - expect 1\"\n\neos vid set map -grpc key:$authkey vuid:11\neos vid ls\n\necho \"----------------------------------------------------------------------\"\necho \"# NO SUDO\"\necho \"----------------------------------------------------------------------\"\neos-grpc-manila --command create --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 1 && failed=1\necho \"# response:$response - expect 1\"\n\neos vid set membership 11 +sudo\n\necho \"----------------------------------------------------------------------\"\necho \"# NO OPENSTACK DIR\"\necho \"----------------------------------------------------------------------\"\neos-grpc-manila --command create --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 195 && failed=1\necho \"# response:$response - expect 195\"\n\neos mkdir /eos/$instance/proc/openstack/\n\necho \"----------------------------------------------------------------------\"\necho \"# NO SHARE DIRECTORY SETTING\"\necho \"----------------------------------------------------------------------\"\neos-grpc-manila --command create --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 237 && failed=1\necho \"# response:$response - expect 237\"\n\n\neos attr set manila.prefix=/eos/$instance/openstack/manila-test/ /eos/$instance/proc/openstack/\n\necho \"----------------------------------------------------------------------\"\necho \"# NO SHARE DIRECTORY \"\necho \"----------------------------------------------------------------------\"\neos-grpc-manila --command create --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 254 && failed=1\necho \"# response:$response - expect 254\"\n\neos mkdir -p /eos/$instance/openstack/manila-test/\n\necho \"----------------------------------------------------------------------\"\necho \"# EXISTING SHARE DIRECTORY - missing args\"\necho \"----------------------------------------------------------------------\"\neos-grpc-manila --command create --params \"authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 234 && failed=1\necho \"# response:$response - expect 234\"\n\neos-grpc-manila --command create --params \"creator:$creator,authkey:$authkey,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 234 && failed=1\necho \"# response:$response - expect 234\"\n\neos-grpc-manila --command create --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 234 && failed=1\necho \"# response:$response - expect 234\"\n\neos-grpc-manila --command create --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 234 && failed=1\necho \"# response:$response - expect 234\"\n\neos-grpc-manila --command create --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,egroup:$egroup,admin_egroup:$admin_egroup\"\n\nresponse=$?\ntest $response -ne 234 && failed=1\necho \"# response:$response - expect 234\"\n\necho \"----------------------------------------------------------------------\"\necho \"# CREATE SHARE DIRECTORY\"\necho \"----------------------------------------------------------------------\"\neos-grpc-manila --command create --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 1 && failed=1\necho \"# response:$response - expect 1\"\n\neos attr ls /eos/$instance/openstack/manila-test/n/nobody/share-1\neos find -d /eos/$instance/openstack/\neos quota ls /eos/$instance/openstack/manila-test/\n\necho \"----------------------------------------------------------------------\"\necho \"# DELETE SHARE DIRECTORY - forbidden\"\necho \"----------------------------------------------------------------------\"\n\neos-grpc-manila --command delete --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 255 && failed=1\necho \"# response:$response - expect 255\"\n\necho \"----------------------------------------------------------------------\"\necho \"# DELETE SHARE DIRECTORY - allowed\"\necho \"----------------------------------------------------------------------\"\n\neos attr set manila.deletion=1 /eos/$instance/proc/openstack/\n\neos-grpc-manila --command delete --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 1 && failed=1\necho \"# response:$response - expect 1\"\n\necho \"----------------------------------------------------------------------\"\necho \"# CREATE NEXT SHARE DIRECTORY\"\necho \"----------------------------------------------------------------------\"\ncreator=\"adm\"\nid=\"2\"\ngroup_id=\"2\"\nname=\"share-2\"\n\nquota=\"50\"\neos-grpc-manila --command create --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 1 && failed=1\necho \"# response:$response - expect 1\"\n\neos quota ls /eos/$instance/openstack/manila-test/\neos find -d /eos/$instance/openstack/\n\necho \"----------------------------------------------------------------------\"\necho \"# EXTEND SHARE DIRECTORY - allowed\"\necho \"----------------------------------------------------------------------\"\n\nquota=\"100\"\neos-grpc-manila --command extend --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 1 && failed=1\necho \"# response:$response - expect 1\"\n\neos quota ls /eos/$instance/openstack/manila-test/\n\necho \"----------------------------------------------------------------------\"\necho \"# SHRINK SHARE DIRECTORY - allowed\"\necho \"----------------------------------------------------------------------\"\n\nquota=\"75\"\neos-grpc-manila --command shrink --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 1 && failed=1\necho \"# response:$response - expect 1\"\n\neos quota ls /eos/$instance/openstack/manila-test/\n\necho \"----------------------------------------------------------------------\"\necho \"# UNMANAGE SHARE DIRECTORY\"\necho \"----------------------------------------------------------------------\"\n\neos-grpc-manila --command unmanage --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 1 && failed=1\necho \"# response:$response - expect 1\"\n\necho \"----------------------------------------------------------------------\"\necho \"# SHRINK ON UNMANAGE SHARE DIRECTORY - not allowed\"\necho \"----------------------------------------------------------------------\"\neos attr ls /eos/$instance/openstack/manila-test/a/adm/share-2\neos-grpc-manila --command shrink --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 237 && failed=1\necho \"# response:$response - expect 237\"\n\necho \"----------------------------------------------------------------------\"\necho \"# EXTEND ON UNMANAGE SHARE DIRECTORY - not allowed\"\necho \"----------------------------------------------------------------------\"\neos attr ls /eos/$instance/openstack/manila-test/a/adm/share-2\neos-grpc-manila --command extend --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 237 && failed=1\necho \"# response:$response - expect 237\"\n\necho \"----------------------------------------------------------------------\"\necho \"# UNMANAGE SHARE DIRECTORY - not managed anymore\"\necho \"----------------------------------------------------------------------\"\n\neos-grpc-manila --command unmanage --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 234 && failed=1\necho \"# response:$response - expect 234\"\n\necho \"----------------------------------------------------------------------\"\necho \"# MANAGE SHARE DIRECTORY\"\necho \"----------------------------------------------------------------------\"\n\neos-grpc-manila --command manage --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 1 && failed=1\necho \"# response:$response - expect 1\"\n\nquota=10\n\necho \"----------------------------------------------------------------------\"\necho \"# SHRINK ON MANAGEG SHARE DIRECTORY - allowed\"\necho \"----------------------------------------------------------------------\"\neos attr ls /eos/$instance/openstack/manila-test/a/adm/share-2\neos-grpc-manila --command shrink --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 1 && failed=1\necho \"# response:$response - expect 1\"\n\neos quota ls /eos/$instance/openstack/manila-test/\n\n\necho \"----------------------------------------------------------------------\"\necho \"# CAPACITY ON MANAGEG SHARE DIRECTORY - allowed\"\necho \"----------------------------------------------------------------------\"\neos attr ls /eos/$instance/openstack/manila-test/a/adm/share-2\neos-grpc-manila --command capacity --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 1 && failed=1\necho \"# response:$response - expect 1\"\n\necho \"----------------------------------------------------------------------\"\necho \"# DELETE SHARE DIRECTORY - allowed\"\necho \"----------------------------------------------------------------------\"\n\neos-grpc-manila --command delete --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 1 && failed=1\necho \"# response:$response - expect 1\"\n\necho \"----------------------------------------------------------------------\"\necho \"# EXTEND SHARE DIRECTORY - non existing\"\necho \"----------------------------------------------------------------------\"\n\nquota=\"200\"\nname=\"missing\"\neos-grpc-manila --command extend --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 254 && failed=1\necho \"# response:$response - expect 254\"\n\necho \"----------------------------------------------------------------------\"\necho \"# SHRINK SHARE DIRECTORY - non existing\"\necho \"----------------------------------------------------------------------\"\n\nquota=\"25\"\nname=\"missing\"\neos-grpc-manila --command shrink --params \"creator:$creator,authkey:$authkey,protocol:$protocol,description:$description,name:$name,id:$id,group_id:$group_id,quota:$quota,egroup:$egroup,admin_egroup:$admin_egroup,location:$location\"\n\nresponse=$?\ntest $response -ne 254 && failed=1\necho \"# response:$response - expect 254\"\n\n# cleanup\necho \"----------------------------------------------------------------------\"\neos rmdir /eos/$instance/proc/openstack/\neos quota rmnode --really-want -p /eos/$instance/openstack/manila-test/\neos rm -r /eos/$instance/openstack/manila-test/\neos rmdir /eos/$instance/openstack/\neos vid set membership 11 -sudo\neos vid rm grpc:\\\"key:$authkey\\\":uid\neos vid ls\n\necho \"----------------------------------------------------------------------\"\ntest $failed -eq 1 && echo \"RESULT: some manila tests failed\" || echo \"RESULT: all manila tests passwd\"\necho \"----------------------------------------------------------------------\"\ntest $failed -eq 1 && exit -1\nexit 0\n"
  },
  {
    "path": "test/eos-oc-test",
    "content": "#!/bin/bash\n\n# ----------------------------------------------------------------------\n# File: eos-oc-test\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n\n\nusage() { echo '''Usage: # File: eos-oc-test <method> <name> <dir> [-h|--host <host>[:<port>| (-p|--port) <port>]]\n                           <methodd>  : only chunkedupload for the time being\n                           <name>     : name of the file\n                           <dir>      : name of the destination directory\n\n                           [-h|--help] - usage & exit\n'''; }\n\n# Parser from: https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash\nusage_error () { echo >&2 \"$(basename \"$0\"):  $1\"; exit 2; }\nassert_argument () { test \"$1\" != \"$EOL\" || usage_error \"$2 requires an argument\"; }\n\nport=\"${EOS_HTTPS_PORT:-443}\"\nhost=$(hostname -f)\ncacert=\"/etc/grid-security/certificates/rootCA.pem\"\ncapath=\"$(dirname \"${cacert}\")\"\ncert=\"/root/.globus/usercert.pem\"\nkey=\"/root/.globus/userkey.pem\"\n\n# One loop, nothing more.\nPOSITIONAL_ARGS=()\nif [ \"$#\" != 0 ]; then\n  EOL=$(printf '\\1\\3\\3\\7')\n  set -- \"$@\" \"$EOL\"\n  while [ \"$1\" != \"$EOL\" ]; do\n    opt=\"$1\"; shift\n    case \"$opt\" in\n\n      -h|--host)    assert_argument \"$1\" \"$opt\"; host=\"${1%:*}\" && port=\"${1#*:}\"; shift;;\n      -p|--port)    assert_argument \"$1\" \"$opt\"; port=\"$1\"; shift;;\n      -c|--cert)    assert_argument \"$1\" \"$opt\"; cert=\"$1\"; shift;;\n      -k|--key)     assert_argument \"$1\" \"$opt\"; key=\"$1\"; shift;;\n      -C|--cacert)  assert_argument \"$1\" \"$opt\"; cacert=\"${1}\" && capath=\"$(dirname \"${cacert}\")\"; shift;;\n      -P|--capath)  assert_argument \"$1\" \"$opt\"; capath=\"${1}\"; shift;;\n\n      -h|--help) usage; exit 0;;\n\n      # Arguments processing. You may remove any unneeded line after the 1st.\n      -|''|[!-]*) set -- \"$@\" \"$opt\";;                                          # positional argument, rotate to the end\n      --*=*)      set -- \"${opt%%=*}\" \"${opt#*=}\" \"$@\";;                        # convert '--name=arg' to '--name' 'arg'\n      -[!-]?*)    set -- \"$(echo \"${opt#-}\" | sed 's    /g')\" \"$@\";;     # convert '-abc' to '-a' '-b' '-c'\n      --)         while [ \"$1\" != \"$EOL\" ]; do set -- \"$@\" \"$1\"; shift; done;;  # process remaining arguments as positional\n      -*)         usage_error \"unknown option: '$opt'\";;                        # catch misspelled options\n      *)          usage_error \"this should NEVER happen ($opt)\";;               # sanity test for previous patterns\n\n    esac\n  done\n  shift  # $EOL\nfi\n\necho\n\nchunkedupload () {\n  echo \"# Testing Chunked upload\"\n  NAME=$1\n  DIR=/tmp/OC_CHUNK\n  mkdir -p $DIR\n  rm -f $DIR/$NAME*\n  CHUNK_NUMBER=4\n  dd if=/dev/zero of=$DIR/$NAME bs=1M count=32\n  split -b 10485760 -a 1 -d $DIR/$NAME $DIR/$NAME-chunking-`uuidgen | sed s/-//g`-$CHUNK_NUMBER-\n  echo \"# about to upload $DIR/$NAME\"\n  DEST_URL=https://${host}:${port}$2\n  echo \"# to $DEST_URL\"\n  let LAST_CHUNK_NUMBER=$CHUNK_NUMBER-1\n  let i=0\n  UUID=`echo $RANDOM`\n  ok=0\n  for f in `ls $DIR/$NAME-chunking*`; do\n    echo $f\n    EOS_FN=`basename $f`\n    curl  --capath \"${capath}\" --cert \"${cert}\" --cacert \"${cacert}\" --key \"${key}\" -L --verbose -k --header \"Oc-Chunk-Uuid:$UUID\" --header \"Oc-Chunk-n:$i\" --header \"Oc-Chunk-Max:4\" --header \"OC-Chunked:1\" --header \"X-OC-Mtime:1402061782\" --header \"OC-Total-Length:33554432\" -T $f $DEST_URL$EOS_FN 2> $f.log\n\n    if (( i < $LAST_CHUNK_NUMBER )); then\n      grep -q 'X-OC-Mtime: accepted' $f.log && echo \"err: redundant reply header 'X-OC-Mtime: accepted'\"\n      grep -q 'X-OC-Mtime: accepted' $f.log && ok=1\n      # We are only interested in the final reply from the server if it contains\n      # the etag or not.\n      grep -A 100 'CREATED' $f.log | grep -q 'ETag: ' && echo \"err: redundant reply header 'ETag: '\"\n      grep -A 100 'CREATED' $f.log | grep -q 'ETag: ' && ok=1\n    else\n      grep -q 'X-OC-Mtime: accepted' $f.log || echo \"err: missing required header 'X-OC-Mtime: accepted'\"\n      grep -q 'X-OC-Mtime: accepted' $f.log || ok=1\n      # We are only interested in the final reply from the server if it contains\n      # the etag or not.\n      grep -A 100 'CREATED' $f.log | grep -q 'ETag: ' || echo \"err: missing required reply header 'ETag: '\"\n      grep -A 100 'CREATED' $f.log | grep -q 'ETag: ' || ok=1\n    fi\n\n    let i=$i+1\n\n  done\n\n  if [ $ok -eq 0 ]; then\n    curl -v -i HEAD $DEST_URL >& $DIR/$NAME.log\n    ok=$?\n  fi\n\n  return $ok;\n}\n\nif [ \"$#\" -eq 3 ] && [ \"$1\" = \"chunkedupload\" ]; then\n  chunkedupload $2 $3;\n  exit $?\nelse\n  usage\nfi\n\nexit -1\n"
  },
  {
    "path": "test/eos-quota-test",
    "content": "\n#!/bin/bash\n# the sleep statemens are required because quota ls caching ...\necho path=$1\ngrep_str=${2:-\"uid=adm\"}\necho grep_str=$grep_str\nsleep 6\na=`eos quota ls -m $1 | grep $grep_str`\neos ns update_quotanode $1 uid:3 inodes:1000 bytes:2000 physicalbytes:3000\nsleep 6\nc=`eos quota ls -m $1 | grep $grep_str`\necho $c\neos ns update_quotanode $1 uid:3\nsleep 6\nd=`eos quota ls -m $1 | grep $grep_str`\necho $d\n\nif [ \"$a\" != \"$d\" ]; then\n  exit -1\nfi\n\necho $d | grep \"usedbytes=1024 usedlogicalbytes=1024 usedfiles=1\"\nif [ $? -eq 0 ]; then\n  echo result: $?\nelse\n  echo $d\n  exit -2\nfi\n\n\necho $c | grep \"usedbytes=3000 usedlogicalbytes=2000 usedfiles=1000\"\nif [ $? -eq 0 ]; then\n  exit 0\nelse\n  echo $c\n  exit -2\nfi\n"
  },
  {
    "path": "test/eos-rain-test",
    "content": "#!/bin/bash\n\n#------------------------------------------------------------------------------\n# File: eos-rain-test\n# Author: Elvin-Alin Sindrilaru - CERN\n#------------------------------------------------------------------------------\n\n#/************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************/\n\n#------------------------------------------------------------------------------\n# Description: The script is used to test the RAIN-like layouts from EOS.\n# Usage:\n# eos-rain-test raiddp/raid6/archive/plain dir_test_files root://host//eos_raid_dir tmp_location_dir\n#  - first argument is the type of test made\n#  - second arguments specifies the directory from which all files corresponding\n#     to the pattern *.rain will be used in the test\n#  - third argument represents the full path to a directory in EOS which has\n#     the proper extended attributes for the type of test specified in the\n#     first argument\n#  - fourth argument is a temporary directory in which files read out from EOS\n#     are stored. The files put in this directory  will be deleted at the end\n#     of the test.\n#\n# The script copies the files in the EOS directories which are using some form\n# of RAIN-layout and then tries to read them back and compares the checksum of\n# the initial file put in EOS with the one read back. In the second stage, each\n# file system in the EOS configuration is shutdown one by one and the file is\n# read again from EOS to verify if the single stripe recovery mechanism is\n# working properly. In the last stage, each two file systems are shutdown and\n# again the file is read out of EOS and the match with the original one is tested.\n#------------------------------------------------------------------------------\n\n. /etc/init.d/functions\n\nSCRIPTPATH=\"$( cd \"$(dirname \"$0\")\" >/dev/null 2>&1 ; pwd -P )\"\nsource ${SCRIPTPATH}/eos-test-utils\n\nexport ERR_MSG1=\"The provided directory does not have the extended attributes set correctly.\"\n\n# Set the path to the xrdcp and eos commands, considering it may come from eos-xrootd\nexport EOS_XROOTSYS=/opt/eos/xrootd/\nexport LD_LIBRARY_PATH=$EOS_XROOTSYS/lib64/:$LD_LIBRARY_PATH\nexport PATH=$EOS_XROOTSYS/bin:$PATH\nXRDCP=$(command -v xrdcp)\nXRDCP=\"$XRDCP --nopbar\"\nEOS=$(command -v eos)\n\nif [ \"$(whoami)\" != \"root\" ]; then\n   SUDO=\"sudo\"\nelse\n  SUDO=\"\"\nfi\n\n#-------------------------------------------------------------------------------\n# Write a file to EOS, read it back and compare the checksum\n#-------------------------------------------------------------------------------\nfunction write_and_read()\n{\n    local SRC=$1\n    local DEST=$2\n    local FNAME=$3\n    local XS_REF=$4\n    echo \"Write, read and check test, with file: $FNAME\"\n    # write the file in EOS\n    $XRDCP $SRC/$FNAME $DEST/$FNAME -f\n    # read it back in a tmp folder\n    $XRDCP $DEST/$FNAME $TMP_LOCATION/$FNAME.tmp -f\n    # compute transferred file checksum\n    local XS_COPY=$(sum $TMP_LOCATION/$FNAME.tmp | cut -d' ' -f1)\n    # clean up the files in the TMP_LOCATION\n    rm -rf $TMP_LOCATION/$FNAME.tmp\n    echo \"info: compare xs_ref=$XS_REF with xs_copy=$XS_COPY\"\n    test \"$XS_REF\" = \"$XS_COPY\"\n\n    if [[ $? -ne 0 ]]; then\n        echo_failure\n        exit 1\n    fi\n}\n\n#-------------------------------------------------------------------------------\n# Write a file to EOS, read it back and compare the checksum\n#-------------------------------------------------------------------------------\nfunction write_and_check_stripe_checksum()\n{\n    local SRC=$1\n    local DEST=$2\n    local FNAME=$3\n\n    echo \"Write and check stripe checksum with file: $FNAME\"\n    # Write the file in EOS\n    $XRDCP $SRC/$FNAME $DEST/$FNAME -f\n    \n    $EOS -j file info $DEST/$FSNAME | jq -c '.locations[]' | while IFS= read -r line\n    do\n        fsid=$(echo \"$line\" | jq -r '.fsid')\n        fstpath=$(echo \"$line\" | jq -r '.fstpath')\n\n        cmd=\"eos-checksum adler <(dd if=$fstpath bs=4096 skip=1 status=none) | sed -n 's/.*adler=\\([^ ]*\\).*/\\1/p'\"\n        expected_xs=$(exec_cmd \"eos-fst${fsid}\" \"$cmd\")\n\n        cmd=\"eos-fmd-tool inspect --path $fstpath | grep -oP 'stripechecksum: \\\"\\K[^\\\"]+'\"\n        stored_xs=$(exec_cmd \"eos-fst${fsid}\" \"$cmd\")\n\n        test \"$expected_xs\" = \"$stored_xs\"\n        if [[ $? -ne 0 ]]; then\n            echo_failure\n            exit 1\n        fi \n    done\n}\n\n\n#-------------------------------------------------------------------------------\n# Read a file from EOS it back and compare the checksum\n#-------------------------------------------------------------------------------\nfunction read_and_check()\n{\n    local SRC=$1\n    local EOS_SRC=$2\n    local FNAME=$3\n    local XS_REF=$4\n    #echo \"Read and check test, with file: $FNAME\"\n    # read it back in a tmp folder\n    $XRDCP $EOS_SRC/$FNAME $TMP_LOCATION/$FNAME.tmp -f\n    # compute transferred file checksum\n    local XS_COPY=$(sum $TMP_LOCATION/$FNAME.tmp | cut -d' ' -f1)\n    # clean up the files in the TMP_LOCATION\n    rm -rf $TMP_LOCATION/$FNAME.tmp\n    test \"$XS_REF\" = \"$XS_COPY\"\n\n    if [[ $? -ne 0 ]]; then\n        echo_failure\n        exit 1\n    fi\n}\n\n\n#-------------------------------------------------------------------------------\n# Get the file system ids on which the file has stripes\n#-------------------------------------------------------------------------------\nfunction get_file_fs()\n{\n    local eos_file=$1\n    local rec=$($SUDO $EOS -b file info $eos_file)\n    VECT_FS=( $(echo \"$rec\" | awk '{ if (($6 ~ /booted/) || ($7 ~ /booted/)) print $2 }') )\n}\n\n\n#-------------------------------------------------------------------------------\n# Main part\n#-------------------------------------------------------------------------------\n\nif [[ $# -eq 0 && $# -gt 5 ]];\nthen\n    echo \"Usage: $0 raiddp/raid6/archive/plain dir_test_files root://host//eos_rain_dir tmp_dir\"\n    exit\nfi\n\n# Type of test to be conducted\nARG=$1\n\n# Location where the initial files reside\nROOT_PATH=$2\n\n# EOS directory with the correct RAIN extended attributes\nEOS_DEST=$3\n\n# Tmp location where files are saved when read from EOS\nTMP_LOCATION=$4\n\n#Extract the EOS directory name\nINDX=`awk -v a=\"$EOS_DEST\" -v b=\"//eos\" 'BEGIN{print index(a,b)}'`\nEOS_DIR=\"${EOS_DEST:$INDX}\"\n\nif [ \"$ARG\" = \"raiddp\" ]; then\n    $SUDO $EOS -b attr ls \"$EOS_DIR\" | grep -q 'sys.forced.layout=\"raiddp\"'\n    if [ $? -ne 0 ]; then\n        echo \"$ERR_MSG1\"\n        echo_failure\n        exit 1\n    fi\nelif [ \"$ARG\" = \"raid6\" ];\nthen\n    $SUDO $EOS -b attr ls \"$EOS_DIR\" | grep -q 'sys.forced.layout=\"raid6\"'\n    if [ $? -ne 0 ]; then\n        echo \"$ERR_MSG1\"\n        echo_failure\n        exit 1\n    fi\nelif [ \"$ARG\" = \"archive\" ];\nthen\n    $SUDO $EOS -b attr ls \"$EOS_DIR\" | grep -q 'sys.forced.layout=\"archive\"'\n    if [ $? -ne 0 ]; then\n        echo \"$ERR_MSG1\"\n        echo_failure\n        exit 1\n    fi\nelif [ \"$ARG\" = \"plain\" ];\nthen\n    $SUDO $EOS -b attr ls \"$EOS_DIR\" | grep -q 'sys.forced.layout=\"plain\"'\n    if [ $? -ne 0 ]; then\n        echo \"$ERR_MSG1\"\n        echo_failure\n        exit 1\n    fi\nelif [ \"$ARG\" = \"qrain\" ];\nthen\n    $SUDO $EOS -b attr ls \"$EOS_DIR\" | grep -q 'sys.forced.layout=\"qrain\"'\n    if [ $? -ne 0 ]; then\n        echo \"$ERR_MSG1\"\n        echo_failure\n        exit 1\n    fi\nelse\n    echo \"Usage: $0 raiddp/raid6/archive/plain dir_test_files eos_rain_dir tmp_dir\"\n    exit 1\nfi\n\n\n#...............................................................................\n# Check that there are at least 6 FST in the EOS setup so that we can run the test\n#...............................................................................\nret=$( $SUDO $EOS -b fs ls | grep booted | wc -l )\n\nif [[ $ret -le 5 ]]; then\n    echo \"The EOS instance needs at least 6 FST's to run this test.\"\n    exit 1\nfi\n\n# Create a map between reference test file names and their corresponding\n# checksum to speed-up the execution time of this test\ndeclare -A MAP_XS\n\n#...............................................................................\n# Do all write-read-check tests in: $EOS_DEST ...\n#...............................................................................\necho \"Do all write-read-check tests in: $EOS_DEST\"\ndate\n# loop over all files in the pattern and test them in EOS\nfor file in $ROOT_PATH/*.rain; do\n    if [[ -f $file ]]; then\n        file=$(basename $file)\n        MAP_XS[$file]=$(sum $ROOT_PATH/$file | cut -d' ' -f1)\n        write_and_read $ROOT_PATH $EOS_DEST $file ${MAP_XS[$file]}\n    fi\ndone\n\n#...............................................................................\n# Do all write/checksum tests in $EOS_DEST\n#...............................................................................\necho \"Do all write and per stripe checksum tests in: $EOS_DEST\"\ndate\n# loop over all files in the pattern and test them in EOS\nfor file in $ROOT_PATH/*.rain; do\n    if [[ -f $file ]]; then\n        file=$(basename $file)\n        write_and_check_stripe_checksum $ROOT_PATH $EOS_DEST $file\n    fi\ndone\n\n#...............................................................................\n# Disable one FST and redo the read test...\n#...............................................................................\necho \"Disable one FST and redo the read test...\"\ndate\n\n# loop over all files in the pattern and test them in EOS\nfor file in $ROOT_PATH/*.rain; do\n    if [[ -f $file ]]; then\n        file=$(basename $file)\n        # get the fs on which this files resides\n        get_file_fs \"$EOS_DIR/$file\"\n        echo \"Process file $file ...\"\n\n        for fst in \"${VECT_FS[@]}\"; do\n            #echo \"Disable FST $fst and redo read test...\"\n\n            # disable one FST in EOS and test read\n            $SUDO $EOS -b fs config $fst configstatus=off\n            sleep 1\n\n            read_and_check $ROOT_PATH $EOS_DEST $file ${MAP_XS[$file]}\n\n            # restore the configuraton in EOS\n            $SUDO $EOS -b fs config $fst configstatus=rw\n        done\n    fi\ndone\n\n\n#...............................................................................\n# Disable each two FSTs and redo the read tests...\n#...............................................................................\necho \"Disable each two FSTs and redo the read tests...\"\ndate\n# loop over all files in the pattern and test them in EOS\nfor file in $ROOT_PATH/*.rain; do\n    if [[ -f $file ]]; then\n        file=$(basename $file)\n        # get the filesystems on which this file resides\n        get_file_fs \"$EOS_DIR/$file\"\n        echo \"Process file $file ...\"\n        idx1=0\n\n        for fst1 in \"${VECT_FS[@]}\"; do\n            idx2=0\n            $SUDO $EOS -b fs config $fst1 configstatus=off\n\n            for fst2 in \"${VECT_FS[@]}\"; do\n                if [ \"$idx2\" -gt \"$idx1\" ]; then\n                    #echo \"Disable FST $fst1 and $fst2 and redo read test...\"\n                    $SUDO $EOS -b fs config $fst2 configstatus=off\n                    sleep 1\n                    read_and_check $ROOT_PATH $EOS_DEST $file ${MAP_XS[$file]}\n                    $SUDO $EOS -b fs config $fst2 configstatus=rw\n                fi\n                idx2=$((idx2 + 1))\n            done\n            \n            # restore the configuraton in EOS\n            $SUDO $EOS -b fs config $fst1 configstatus=rw\n            idx1=$((idx1 + 1))\n        done\n    fi\ndone\n\n#...............................................................................\n# Clean up all the files written in EOS\n#...............................................................................\necho \"Delete files in EOS\"\ndate\n# loop over all files in the pattern and test them in EOS\nfor file in $ROOT_PATH/*.rain; do\n    if [[ -f $file ]]; then\n        file=$(basename $file)\n        $SUDO eos -b rm $EOS_DIR/$file\n    fi\ndone\n\necho_success\nexit 0\n"
  },
  {
    "path": "test/eos-rclone-test",
    "content": "#!/bin/bash\nset -e\nlocaldir=$1\neosdir=$2\n\necho \"eos rclone $eosdir => $localdir\"\n\neos mkdir -p /eos/$eosdir/cl/\neos chmod 777 /eos/$eosdir/cl/\neos rm -r /eos/$eosdir/cl/\neos mkdir -p /eos/$eosdir/cl/dir1/dir2/\ntest -d $localdir/cl/ && rm -rf $localdir/cl/\nmkdir -p $localdir/cl/\n\n# create some files\nfor name in `seq 1 100`; do \n    eos cp ${TESTSYSFILE1K-\"/etc/passwd\"} /eos/$eosdir/cl/dir1/dir2/file.$name\ndone\neos cp ${TESTSYSFILE1K-\"/etc/passwd\"} /eos/$eosdir/cl/file.0\n\n# rclone to a local dir\neos -b rclone copy /eos/$eosdir/cl/ $localdir/cl/ --dryrun || exit -2\neos -b rclone copy /eos/$eosdir/cl/ $localdir/cl/ || exit -3\necho \nnfiles=`find /var/tmp/eos-instance-test/cl/ -type f | wc -l`\nndirs=`find /var/tmp/eos-instance-test/cl/ -type d | wc -l`\n\ntest $nfiles = \"101\" || exit -3\ntest $ndirs = \"3\" || exit -3\n\n# rclone copy a symlink\neos file symlink /eos/$eosdir/cl/link.0 file.0 \neos -b rclone copy /eos/$eosdir/cl/ $localdir/cl/ || exit -4\nnlinks=`find /var/tmp/eos-instance-test/cl/ -type l | wc -l`\ntest $nlinks = \"1\" || exit -4\n\neos file info /eos/$eosdir/cl\n\n# remove the symlink and sync it back\neos rm /eos/$eosdir/cl/link.0\neos file info /eos/$eosdir/cl\neos -b rclone sync /eos/$eosdir/cl/ $localdir/cl/ --verbose || exit -5\nnlinks=`find /var/tmp/eos-instance-test/cl/ -type l | wc -l`\ntest $nlinks = \"1\" || exit -5\neos stat /eos/$eosdir/cl/link.0 || exit -5\n\n# add some new files locally\nmkdir -p $localdir/cl/subtree/\nfor name in `seq 1 50`; do \n    eos cp ${TESTSYSFILE1K-\"/etc/passwd\"} $localdir/cl/subtree/f.$name\ndone\n\neos -b rclone sync /eos/$eosdir/cl/ $localdir/cl/ --verbose || exit -6\nnfiles=`eos newfind -f /eos/$eosdir/cl/ | wc -l`\n\ntest $nfiles = \"152\" || exit -6\n\n\n# remove a target file and try one-way sync with delete open\n\neos rm /eos/$eosdir/cl/file.0\neos -b rclone copy /eos/$eosdir/cl/ $localdir/cl/ --verbose --delete || exit -7\n\n# local find does not find symlinks, so we have one less\nnfiles=`find $localdir/cl/ -type f | wc -l`\n\ntest $nfiles = \"150\" || exit -7\n\necho\necho \"eos-rclone: all tests passwd OK\"\n"
  },
  {
    "path": "test/eos-recycle-test",
    "content": "#!/bin/bash -ex\n\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2025 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\nSCRIPTPATH=\"$( cd \"$(dirname \"$0\")\" >/dev/null 2>&1 ; pwd -P )\"\nsource ${SCRIPTPATH}/eos-test-utils\nEOS_ROOT=/eos/dockertest\nEOS_RECYCLE=\"${EOS_ROOT}/proc/recycle/uid:0\"\nCURRENT_YEAR=$(date +%Y)\nPREVIOUS_YEAR=$(($CURRENT_YEAR - 1))\nCURRENT_MONTH=$(date +%m)\nCURRENT_DAY=$(date +%d)\nRECYCLE_REMOVE_INTERVAL=10\nRECYCLE_COLLECT_INTERVAL=20\n\n#-------------------------------------------------------------------------------\n# Description: this script tests the recycle bin functionality by making sure\n# that the correct entries from the recycle bin are properly cleaned up based\n# and the given configuration.\n#-------------------------------------------------------------------------------\nfunction usage() {\n  echo \"usage: $(basename $0) --root /eos/instance --type docker/local/k8s <k8s_namespace>\"\n  echo \"       local  : script runs locally, needs EOS_MGM_URL to be set\"\n  echo \"       docker : script runs in a Docker based setup\"\n  echo \"       k8s    : script runs in a Kubernetes setup and requires a namespace argument\"\n}\n\n#-------------------------------------------------------------------------------\n# Configure the recycle thread to run more often for testing purposes\n#-------------------------------------------------------------------------------\nfunction configure_recycler() {\n  echo \"info: configure recycler\"\n  # Set lifetime to one month 31 * 24 * 3600\n  exec_cmd eos-mgm1 \"eos -r 0 0 recycle config --lifetime 2678400\"\n  # Set keep ratio to a very low value so we make sure it's triggered\n  exec_cmd eos-mgm1 \"eos -r 0 0 recycle config --ratio 0.005\"\n  # Set maximum quota for the recycle bin\n  exec_cmd eos-mgm1 \"eos -r 0 0 recycle config --size 4G\"\n  # Update the remove and collect intervals\n  exec_cmd eos-mgm1 \"eos -r 0 0 recycle config --remove-interval ${RECYCLE_REMOVE_INTERVAL}\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 recycle config --collect-interval ${RECYCLE_COLLECT_INTERVAL}\"\n}\n\n\n#-------------------------------------------------------------------------------\n# Create recycle directory structure for scenario 1 - here we have several\n# directories\n#-------------------------------------------------------------------------------\nfunction scenario_1_create_hierarchy() {\n  echo \"info: create recycle hierachy\"\n  # Timestamp of 6 months ago - create some dirs and files that should be\n  # cleaned by the recycle thread\n  local OLD_DATE_TS=$(($(date +%s) - 6 * 31 * 86400))\n  local OLD_YEAR=$(date -d @${OLD_DATE_TS} +%Y)\n  local OLD_MONTH=$(date -d @${OLD_DATE_TS} +%m)\n  local OLD_DAY=$(date -d @${OLD_DATE_TS} +%d)\n  local OLD_DATE=${OLD_YEAR}/${OLD_MONTH}/${OLD_DAY}\n  exec_cmd eos-mgm1 \"eos -r 0 0 mkdir -p ${EOS_RECYCLE}/${OLD_YEAR}/${OLD_MONTH}/${OLD_DAY}/0\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 mkdir -p ${EOS_RECYCLE}/${OLD_YEAR}/${OLD_MONTH}/${OLD_DAY}/1\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 mkdir -p ${EOS_RECYCLE}/${OLD_YEAR}/${OLD_MONTH}/${OLD_DAY}/2\"\n  # Need to create the file outside the recycle bin and move them in\n  exec_cmd eos-mgm1 \"eos -r 0 0 file touch ${EOS_ROOT}/del_file0.dat\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 mv ${EOS_ROOT}/del_file0.dat ${EOS_RECYCLE}/${OLD_YEAR}/${OLD_MONTH}/${OLD_DAY}/0/\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 file touch ${EOS_ROOT}/del_file1.dat\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 mv ${EOS_ROOT}/del_file1.dat ${EOS_RECYCLE}/${OLD_YEAR}/${OLD_MONTH}/${OLD_DAY}/1/\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 file touch ${EOS_ROOT}/del_file2.dat\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 mv ${EOS_ROOT}/del_file2.dat ${EOS_RECYCLE}/${OLD_YEAR}/${OLD_MONTH}/${OLD_DAY}/2/\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 ls -lrta ${EOS_RECYCLE}/${OLD_YEAR}/${OLD_MONTH}/${OLD_DAY}\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 ls -lrta ${EOS_RECYCLE}/${OLD_YEAR}/${OLD_MONTH}/${OLD_DAY}/0/\"\n  # Timestamp is 2 weeks ago - create some dirs and files that should not be\n  # touched by the recycle thread\n  OLD_DATE_TS=$(($(date +%s) - 14 * 86400))\n  OLD_YEAR=$(date -d @${OLD_DATE_TS} +%Y)\n  OLD_MONTH=$(date -d @${OLD_DATE_TS} +%m)\n  OLD_DAY=$(date -d @${OLD_DATE_TS} +%d)\n  OLD_DATE=${OLD_YEAR}/${OLD_MONTH}/${OLD_DAY}\n  exec_cmd eos-mgm1 \"eos -r 0 0 mkdir -p ${EOS_RECYCLE}/${OLD_YEAR}/${OLD_MONTH}/${OLD_DAY}/0\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 mkdir -p ${EOS_RECYCLE}/${OLD_YEAR}/${OLD_MONTH}/${OLD_DAY}/1\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 mkdir -p ${EOS_RECYCLE}/${OLD_YEAR}/${OLD_MONTH}/${OLD_DAY}/2\"\n  # Need to create the file outside the recycle bin and move them in\n  exec_cmd eos-mgm1 \"eos -r 0 0 file touch ${EOS_ROOT}/file0.dat\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 mv ${EOS_ROOT}/file0.dat ${EOS_RECYCLE}/${OLD_YEAR}/${OLD_MONTH}/${OLD_DAY}/0/\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 file touch ${EOS_ROOT}/file1.dat\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 mv ${EOS_ROOT}/file1.dat ${EOS_RECYCLE}/${OLD_YEAR}/${OLD_MONTH}/${OLD_DAY}/1/\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 file touch ${EOS_ROOT}/file2.dat\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 mv ${EOS_ROOT}/file2.dat ${EOS_RECYCLE}/${OLD_YEAR}/${OLD_MONTH}/${OLD_DAY}/2/\"\n}\n\n\n#-------------------------------------------------------------------------------\n# Check if scenario 1 expectation are met after the recycle thread has run.\n# We expect only the files from the second batch to still be in the namespace.\n#-------------------------------------------------------------------------------\nfunction scenario_1_check_expectations() {\n  echo \"info: check expectations\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 find --count ${EOS_RECYCLE} | grep \\\"nfiles=3 ndirectories=7\\\"\"\n\n  if [[ $? -ne 0 ]]; then\n    echo \"error: expectations not met\";\n    exit 1\n  fi\n}\n\n#-------------------------------------------------------------------------------\n# Perform cleanup of used files and revert the recycler configuration to the\n# default values.\n#-------------------------------------------------------------------------------\nfunction cleanup() {\n  echo \"info: recycle cleanup\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 find /eos/dockertest/proc/recycle/uid:0\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 recycle config --collect-interval 86400\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 recycle config --remove-interval 3600\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 recycle config --size 100G\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 recycle config --ratio 0.95\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 rm -rF ${EOS_RECYCLE}/${CURRENT_YEAR}/ || true\"\n  exec_cmd eos-mgm1 \"eos -r 0 0 rm -rF ${EOS_RECYCLE}/${PREVIOUS_YEAR}/ || true\"\n}\n\n# Make sure we always clean up on exit\ntrap cleanup EXIT\n\nif [[ $# -lt 2 ]]; then\n  echo \"error: invalid number of arguments\"\n  usage\n  exit 1\nfi\n\n# Parse EOS instance root location\nif [[ \"$1\" == \"--root\" ]]; then\n    EOS_ROOT=$2\n    EOS_RECYCLE=${EOS_ROOT}/proc/recycle/uid:0\n    shift # past argument\n    shift # past value\nfi\n\n# Set up global variables\nIS_DOCKER=false\nIS_LOCAL=false\nK8S_NAMESPACE=\"\"\n\n# Parse type of environment for the command wrapper\nif [[ \"$1\" != \"--type\" ]]; then\n  echo \"error: unknown argument \\\"$1\\\"\"\n  usage\n  exit 1\nfi\n\nif [[ \"$2\" == \"docker\" ]]; then\n    IS_DOCKER=true\nelif [[ \"$2\" == \"local\" ]]; then\n    IS_LOCAL=true\nelif [[ \"$2\" == \"k8s\" ]]; then\n    IS_DOCKER=false\nelse\n  echo \"error: unknown type of executor \\\"$2\\\"\"\n  usage\n  exit 1\nfi\n\nif [[ \"${IS_LOCAL}\" == true && -z \"${EOS_MGM_URL}\" ]]; then\n   echo \"error: EOS_MGM_URL env needs to be set for local!\"\n   exit 1\nfi\n\nif [[ \"${IS_DOCKER}\" == false && \"${IS_LOCAL}\" == false ]]; then\n  # For the Kubernetes setup we also need a namespace argument\n  if [[ $# -lt 3 ]]; then\n    echo \"error: missing Kubernetes namespace argument\"\n    usage\n    exit 1\n  fi\n  K8S_NAMESPACE=\"$3\"\nfi\n\n# Initial configuration of the recycler\nconfigure_recycler\n\n# Create recycle directory structure\nscenario_1_create_hierarchy\n\n# Wait for the recycle thread to run at least twice\n\necho \"info: wait for recycle run\"\ndate\nsleep $((2*${RECYCLE_COLLECT_INTERVAL}))\ndate\n\n# Check that the expectation are met\nscenario_1_check_expectations\n\necho \"info: all expectations matched\"\nexit 0\n"
  },
  {
    "path": "test/eos-rename-test",
    "content": "#!/bin/bash\nset -x #echo on\n\nprefix=$1\nhost=${2-\"localhost\"}\nurl=root://$host\n\ntest -z \"$1\" && exit -1\n\neos quota rmnode --really-want -p $prefix/t_source/\neos quota rmnode --really-want -p $prefix/t_target/\n\neos rm -r $prefix/t_source/\neos rm -r $prefix/t_target/\neos mkdir -p $prefix/\neos chown adm:adm $prefix/\nxrdfs $url mkdir $prefix/t_source/ || exit -2\neos quota set -g 99 -v 1TB -i 1M $prefix/t_source/ || exit -2\nxrdfs $url mkdir $prefix/t_source/d1 || exit -2\nxrdfs $url mkdir $prefix/t_source/d1/c1 || exit -2\nxrdfs $url mkdir $prefix/t_source/d1c1 || exit -2\nxrdfs $url mkdir $prefix/t_source/d2 || exit -2\nxrdfs $url mkdir $prefix/t_source/d3 || exit -2\nxrdfs $url mkdir $prefix/t_source/d4 || exit -2\nxrdfs $url mkdir $prefix/t_source/d5 || exit -2\nxrdfs $url mkdir $prefix/t_target/ || exit -2\neos quota set -g 99 -v 1TB -i 1M $prefix/t_target/ || exit -2\nxrdfs $url mkdir $prefix/t_target/d1 || exit -2\nxrdfs $url mkdir $prefix/t_target/d1c1 || exit -2\nxrdfs $url mkdir $prefix/t_target/d1c1/c1 || exit -2\nxrdfs $url mkdir $prefix/t_target/d2 || exit -2\nxrdfs $url mkdir $prefix/t_target/d3 || exit -2\nxrdfs $url mkdir $prefix/t_target/e1 || exit -2\n\nxrdcp /etc/passwd $url/$prefix/t_source/f1 || exit -3\nxrdcp /etc/passwd $url/$prefix/t_source/f2 || exit -3\nxrdcp /etc/passwd $url/$prefix/t_source/f3 || exit -3\nxrdcp /etc/passwd $url/$prefix/t_source/f4 || exit -3\nxrdcp /etc/passwd $url/$prefix/t_source/f5 || exit -3\nxrdcp /etc/passwd $url/$prefix/t_source/f6 || exit -3\nxrdcp /etc/passwd $url/$prefix/t_target/t1 || exit -3\nxrdcp /etc/passwd $url/$prefix/t_target/t2 || exit -3\n\n\n\n# dir to dir\nxrdfs $url/ mv $prefix/t_source/_d1 $prefix/t_target/ && exit -4\nxrdfs $url/ mv $prefix/t_source/_d1 $prefix/t_target/nodir/ && exit -4\n\nxrdfs $url/ mv $prefix/t_source/d1 $prefix/t_target/ || exit -4\nxrdfs $url/ mv $prefix/t_source/d1c1 $prefix/t_target/ && exit -4\nxrdfs $url/ mv $prefix/t_source/d2 $prefix/t_target/d1/ || exit -4\nxrdfs $url/ mv $prefix/t_source/d3 $prefix/t_target/d2 || exit -4\nxrdfs $url/ mv $prefix/t_source/d4 $prefix/t_target/n4 || exit -4\neos find $prefix\n\n# dir to file\nxrdfs $url/ mv $prefix/t_source/d5 $prefix/t_target/t1 && exit -5\nxrdfs $url/ mv $prefix/t_source/d5 $prefix/t_target/t1/ && exit -5\neos find $prefix\n\n# file to dir\nxrdfs $url/ mv $prefix/t_source/f1 $prefix/t_target/ || exit -6\nxrdfs $url/ mv $prefix/t_source/f2 $prefix/t_target || exit -6\nxrdfs $url/ mv $prefix/t_source/f3 $prefix/t_target/_d3 || exit -6\nxrdfs $url/ mv $prefix/t_source/f4 $prefix/t_target/_d3/ && exit -6\neos find $prefix\n\n# file to file\nxrdfs $url/ mv $prefix/t_source/f5 $prefix/t_target/t1 || exit -7 \nxrdfs $url/ mv $prefix/t_source/f6 $prefix/t_target/t2/ && exit -7\neos find $prefix\n\neos quota rmnode --really-want -p $prefix/t_source/\neos quota rmnode --really-want -p $prefix/t_target/\n\nexit 0\n"
  },
  {
    "path": "test/eos-squash-test",
    "content": "#!/bin/bash\nset -x #echo on\npath=$1\nhost=${2-\"localhost\"}\nurl=root://$host\n\ntest -z \"$1\" && exit -1\n\nexport XRD_REDIRECTLIMIT=0\n# we get a redirection, but we cannot download the file because the FST does not use this sss key\neoscp ${url}/$path - 2>&1 | grep Redirect\n"
  },
  {
    "path": "test/eos-synctime-test",
    "content": "#!/bin/bash\nset -e\nfusedir=$1\neosdir=$2\n# Sync time propgation happens every 5 seconds, set the sleep_time\n# to 7 seconds to make sure sync info is propagated.\nsleep_time=7\n\n# this test script creates a file/direcotry, chmod, rename's it and verifies that the sync times are updated accordingly\n\neos mkdir -p /eos/$eosdir/synctime/\neos attr set sys.mtime.propagation=1 /eos/$eosdir/synctime/\neos chmod 777 /eos/$eosdir/synctime/\n# Sleep a bit so that the changes above propagated\nsleep ${sleep_time}\n\n# Check sync time propagation for both files and directories\nGET_SYNCTIME_CMD=\"eos -j fileinfo /eos/$eosdir/synctime/ | jq -r '(.tmtime|tostring) + \\\".\\\" + (.tmtime_ns|tostring)'\"\nstime0=$(eval ${GET_SYNCTIME_CMD})\n\n# New file creation should trigger an update\ntouch $fusedir/$eosdir/synctime/f.1\nsleep ${sleep_time}\nstime1=$(eval ${GET_SYNCTIME_CMD})\n\n# New directory creation should trigger an update\nmkdir -p $fusedir/$eosdir/synctime/d.1\nsleep ${sleep_time}\nstime2=$(eval ${GET_SYNCTIME_CMD})\n\n# Chmod on files or dirs does not update the synctime\nchmod 700 $fusedir/$eosdir/synctime/f.1\nchmod 777 $fusedir/$eosdir/synctime/d.1\nsleep ${sleep_time}\nstime3=$(eval ${GET_SYNCTIME_CMD})\n\n# Move of a file should trigger an update\nmv $fusedir/$eosdir/synctime/f.1 $fusedir/$eosdir/synctime/f.2\nsleep ${sleep_time}\nstime4=$(eval ${GET_SYNCTIME_CMD})\n\n# Move of a directory should trigger an update\nmv $fusedir/$eosdir/synctime/d.1 $fusedir/$eosdir/synctime/d.2\nsleep ${sleep_time}\nstime5=$(eval ${GET_SYNCTIME_CMD})\n\necho \"Timestamps: $stime0 $stime1 $stime2 $stime3 $stime4 $stime5\"\ntest \"1\" = $(  printf '%F < %F\\n' \"$stime0\" \"$stime1\" | bc -l  )\ntest \"1\" = $(  printf '%F < %F\\n' \"$stime1\" \"$stime2\" | bc -l  )\ntest \"1\" = $(  printf '%F == %F\\n' \"$stime2\" \"$stime3\" | bc -l  )\ntest \"1\" = $(  printf '%F < %F\\n' \"$stime3\" \"$stime4\" | bc -l  )\ntest \"1\" = $(  printf '%F < %F\\n' \"$stime4\" \"$stime5\" | bc -l  )\n"
  },
  {
    "path": "test/eos-test-utils",
    "content": "#!/bin/bash -e\n\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2019 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\nsecs_to_human() {\n  echo \"$(( ${1} / 3600 ))h $(( (${1} / 60) % 60 ))m $(( ${1} % 60 ))s\"\n}\n\n# Forward the given command to the proper executor Docker or Kubernetes. Gets\n# as argument a container name and a shell command to be executed\nfunction exec_cmd() {\n  if [[ \"${IS_DOCKER}\" == true ]]; then\n      exec_cmd_docker \"$@\"\n  elif [[ \"${IS_LOCAL}\" == true ]]; then\n      exec_cmd_local \"$@\"\n  else\n      exec_cmd_k8s \"$@\"\n  fi\n}\n\n# Execute command in Docker setup where the first argument is the name of the container\n# and the rest is the command to be executed\nfunction exec_cmd_docker() {\n  set -o xtrace\n  docker exec -i $1 /bin/bash -l -c \"${@:2}\"\n  set +o xtrace\n}\n\n# Execute command in Kubernetes setup where the first argument is the name of the Pod\n# and the rest is the command to be executed\nfunction exec_cmd_k8s() {\n  set -o xtrace\n  kubectl exec --namespace=$K8S_NAMESPACE $(kubectl get pods --namespace=$K8S_NAMESPACE --no-headers -o custom-columns=\":metadata.name\" -l app=$1) -- /bin/bash -l -c \"${@:2}\"\n  set +o xtrace\n}\n\nfunction exec_cmd_local() {\n  /bin/bash -l -c \"EOS_MGM_URL=${EOS_MGM_URL} ${@:2}\"\n}\n\n# Copy a file preserving xattrs. The containers need to have `tar` for this to work\n# Arguments: `src_container` `src_dir` `src_file` `dest_container` `dest_dir`\nfunction cp_file_with_xattr_cmd() {\n  if [[ \"${IS_DOCKER}\" == true ]]; then\n      cp_file_with_xattr_cmd_docker \"$@\"\n  elif [[ \"${IS_LOCAL}\" == true ]]; then\n      cp_file_with_xattr_cmd_local \"$@\"\n  else\n      cp_file_with_xattr_cmd_k8s \"$@\"\n  fi\n}\n\nfunction cp_file_with_xattr_cmd_docker() {\n  set -o xtrace\n  docker exec -i \"${1}\" tar cf - --xattrs -C \"${2}\" \"${3}\" | docker exec -i \"${4}\" tar xf - -C \"${5}\" --xattrs\n  set +o xtrace\n}\n\nfunction cp_file_with_xattr_cmd_k8s() {\n  set -o xtrace\n  kubectl exec -n \"$K8S_NAMESPACE\" \"$(get_podname \"${1}\")\" -- tar cf - --xattrs -C \"${2}\" \"${3}\" | kubectl exec -i -n \"$K8S_NAMESPACE\" \"$(get_podname \"${4}\")\" -- tar xf - -C \"${5}\" --xattrs\n  set +o xtrace\n}\n\nfunction cp_file_with_xattr_cmd_local() {\n  tar cf - --xattrs -C \"${2}\" \"${3}\" | tar xf - -C \"${5}\" --xattrs\n}\n\nfunction get_podname () {\n  kubectl get pods -n \"$K8S_NAMESPACE\" --no-headers -o custom-columns=\":metadata.name\" -l app=\"${1}\"\n}\n\nfunction check_until_no_errors() {\n  local func=$1\n  local max_delay=$2\n  local description=${3:-\"check\"}\n\n  local start_time=$(date +%s)\n  local attempt=0\n\n  while\n    attempt=$((attempt + 1))\n    local current_time=$(date +%s)\n    local elapsed=$((current_time - start_time))\n    local remaining=$((max_delay - elapsed))\n    local check_output check_rc\n    set +e\n    check_output=$(eval \"${func}\" 2>&1)\n    check_rc=$?\n    set -e\n    if [[ ${check_rc} -ne 0 ]]; then\n       if (( elapsed >= max_delay )); then\n        echo \"\"\n        echo \"ERROR: ${description} failed after $(secs_to_human ${elapsed}) (timeout)\"\n        echo \"  Last failure output:\"\n        echo \"${check_output}\" | sed 's/^/    /'\n        exit 1\n      else\n        echo \"  [attempt ${attempt}] Waiting... elapsed=$(secs_to_human ${elapsed}) remaining=$(secs_to_human ${remaining})\"\n        if [[ -n \"${check_output}\" ]]; then\n          echo \"${check_output}\" | head -5 | sed 's/^/    /'\n        fi\n        sleep 5\n      fi\n    else\n      echo \"  OK after ${attempt} attempt(s), $(secs_to_human ${elapsed})\"\n      false # to end the loop\n    fi\n  do\n    :\n  done\n}\n\nfunction fail_on_error() {\n  \"$@\"\n  if [[ $? -ne 0 ]]; then\n    exit 1\n  fi\n}\n"
  },
  {
    "path": "test/eos-timestamp-test",
    "content": "#!/usr/bin/env bash\nset -e\n\n# ----------------------------------------------------------------------\n# File: eos-timestamp-test\n# Author: Aritz Brosa Iartza - CERN\n#         Mihai Patrascoiu - CERN\n# ----------------------------------------------------------------------\n\n# ******************************************************************************\n# EOS - the CERN Disk Storage System\n# Copyright (C) 2020 CERN/Switzerland\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n# ******************************************************************************\n\n#------------------------------------------------------------------------------\n# Description: Script testing timestamp functionality of EOS.\n#              The following test scenarios are executed:\n#                - File is created     => mtime, ctime and btime are identical\n#                - Content is modified => mtime and ctime change\n#                - Metadata is changed => only ctime changes\n#                - Touch is performed  => mtime and ctime change\n#\n# Usage: eos-timestamp-test <eos_instance_name> [<eos_mgm_hostname>]\n#------------------------------------------------------------------------------\n\n# Global timestamp variables used by utility function\nmtime=0\nctime=0\nbtime=0\n\n# Global return code\nrc=0\n\n#------------------------------------------------------------------------------\n# Helper functions\n#------------------------------------------------------------------------------\n\nfunction usage() {\n  echo \"Usage: $0 <eos_instance_name> [<eos_mgm_hostname>]\"\n  echo \"       <eos_instance_name> - the /eos/<instance_name>/ to access\"\n  echo \"       <eos_mgm_hostname>  - optional MGM hostname (will use EOS_MGM_URL env variable otherwise)\"\n  echo \"\"\n\n  exit 1\n}\n\nfunction retrieve_timestamps() {\n  local fileinfo=`eos -j fileinfo $1`\n  \n  mtime=`echo ${fileinfo} | jq '(.mtime|tostring) + \".\" + (.mtime_ns|tostring)'`\n  ctime=`echo ${fileinfo} | jq '(.ctime|tostring) + \".\" + (.ctime_ns|tostring)'`\n  btime=`echo ${fileinfo} | jq '(.btime|tostring) + \".\" + (.btime_ns|tostring)'`\n\n  echo \"mtime=${mtime} ctime=${ctime} btime=${btime}\"\n}\n\n#------------------------------------------------------------------------------\n# Global setup\n#------------------------------------------------------------------------------\n\nif [[ $# -eq 0 || $# -gt 2 ]]; then\n  usage\nfi\n\n# Setup EOS instance variables\nEOS_INSTANCE_NAME=$1\nEOS_MGM_HOSTNAME=$2\n\nif [[ -z \"$EOS_MGM_HOSTNAME\" ]]; then\n  if [[ -n \"$EOS_MGM_URL\" ]]; then\n    EOS_MGM_HOSTNAME=${EOS_MGM_URL#root://}\n    EOS_MGM_HOSTNAME=${EOS_MGM_HOSTNAME%:1094/}\n  else\n    EOS_MGM_HOSTNAME=localhost\n  fi\nfi\n\nexport EOS_MGM_URL=${EOS_MGM_HOSTNAME}\necho \"EOS_INSTANCE_NAME=${EOS_INSTANCE_NAME}\"\necho \"EOS_MGM_HOSTNAME=${EOS_MGM_HOSTNAME}\"\n\n#------------------------------------------------------------------------------\n# Testing scenario\n#------------------------------------------------------------------------------\n\n# Initial setup\neos mkdir -p /eos/${EOS_INSTANCE_NAME}/timestamp_tests/\ntouch /eos1/${EOS_INSTANCE_NAME}/timestamp_tests/file\n\nprintf \"\\nRetrieving initial timestamps\\n\"\nretrieve_timestamps /eos/${EOS_INSTANCE_NAME}/timestamp_tests/file\nmtime_init=$mtime\nctime_init=$ctime\nbtime_init=$btime\n\n# TEST: All timestamps should be identical\n# Note: Compare only the integer part of the timestamp for birthtime\n#       (btime has a slight btime_ns difference)\nif [[ ${mtime_init} != ${ctime_init} || ${mtime_init%.*} != ${btime_init%.*} ]]; then\n  echo \"Timestamps are not equal after test file creation\" >&2\n  rc=1\nfi\n\n## File content modification scenario\nsleep 1\nprintf \"\\nRetrieving timestamps after content modification\\n\"\necho 'start' > /eos1/${EOS_INSTANCE_NAME}/timestamp_tests/file\nretrieve_timestamps /eos/${EOS_INSTANCE_NAME}/timestamp_tests/file\nmtime_cont=$mtime\nctime_cont=$ctime\nbtime_cont=$btime\n\n# TEST: Content modification should affect mtime and ctime\nif [[ ${mtime_init} == ${mtime_cont} || ${ctime_init} == ${ctime_cont} || ${btime_init} != ${btime_cont} ]]; then\n  echo \"Content modification timestamps not behaving as expected\" >&2\n  if [[ ${mtime_init} == ${mtime_cont} ]]; then echo \"-- mtime did not change\" >&2 ; fi\n  if [[ ${ctime_init} == ${ctime_cont} ]]; then echo \"-- ctime did not change\" >&2 ; fi\n  if [[ ${btime_init} != ${btime_cont} ]]; then echo \"-- btime changed but it should not have\" >&2 ; fi\n  rc=1\nfi\n\n## File metadata change scenario\nsleep 1\nprintf \"\\nRetrieving timestamps after metadata-only modification\\n\"\neos attr set user.eos.dummy=\"value\" /eos/${EOS_INSTANCE_NAME}/timestamp_tests/file\nretrieve_timestamps /eos/${EOS_INSTANCE_NAME}/timestamp_tests/file\nmtime_metad=$mtime\nctime_metad=$ctime\nbtime_metad=$btime\n\n# TEST: Metadata-only change should affect only ctime\nif [[ ${mtime_cont} != ${mtime_metad} || ${ctime_cont} == ${ctime_metad} || ${btime_cont} != ${btime_metad} ]]; then\n  echo \"Metadata modification timestamps not behaving as expected\" >&2\n  if [[ ${mtime_cont} != ${mtime_metad} ]]; then echo \"-- mtime changed but it should not have\" >&2 ; fi\n  if [[ ${ctime_cont} == ${ctime_metad} ]]; then echo \"-- ctime did not change\" >&2 ; fi\n  if [[ ${btime_cont} != ${btime_metad} ]]; then echo \"-- btime changed but it should not have\" >&2 ; fi\n  rc=1\nfi\n\n## Touch scenario\nsleep 1\nprintf \"\\nRetrieving timestamps after touch\\n\"\neos touch /eos/${EOS_INSTANCE_NAME}/timestamp_tests/file > /dev/null\nretrieve_timestamps /eos/${EOS_INSTANCE_NAME}/timestamp_tests/file\nmtime_touch=$mtime\nctime_touch=$ctime\nbtime_touch=$btime\n\n# TEST: Touch should affect mtime and ctime\nif [[ ${mtime_metad} == ${mtime_touch} || ${ctime_metad} == ${ctime_touch} || ${btime_metad} != ${btime_touch} ]]; then\n  echo \"Touch command timestamps not behaving as expected\" >&2\n  if [[ ${mtime_metad} == ${mtime_touch} ]]; then echo \"-- mtime did not change\" >&2 ; fi\n  if [[ ${ctime_metad} == ${ctime_touch} ]]; then echo \"-- ctime did not change\" >&2 ; fi\n  if [[ ${btime_metad} != ${btime_touch} ]]; then echo \"-- btime changed but it should not have\" >&2 ; fi\n  rc=1\nfi\n\n# Clean-up\neos rm /eos/dockertest/timestamp_tests/*\neos rmdir /eos/dockertest/timestamp_tests/\nexit ${rc}\n"
  },
  {
    "path": "test/eos-token-test",
    "content": "#!/bin/bash\n\nprefix=$1\nhost=${2-\"localhost\"}\nurl=root://$host\n\nGREEN=\"\\033[0;32m✅\"\nRED=\"\\033[0;31m❌\"\nNC=\"\\033[0m\"\n\nfunction run_step() {\n    local description=\"$1\"\n    shift\n    echo -ne \"⏳ $description... $@\"\n    if \"$@\"; then\n        echo -e \"${GREEN} SUCCESS${NC}\"\n    else\n        echo -e \"${RED} FAIL${NC}\"\n        exit 1\n    fi\n}\n\ntest -z \"$prefix\" && { echo -e \"${RED}Prefix not provided. Exiting.${NC}\"; exit 1; }\n\nnow=$(date +%s)\nlater=$((now + 300))\n\necho -e \"Starting EOS token tests for prefix: $prefix on host: $host\"\n\n### Cleanup\nrun_step \"Cleaning up test directory\" true\neos rm -r $prefix/t_token/ >/dev/null 2>&1\n\n### Disable token generation and test failure\nrun_step \"Disabling token generation\" eos space config default space.token.generation=0\n! eos token --path $prefix/t_token/file.1 --expires $later && echo -e \"${GREEN}Expected failure on disabled token generation${NC}\"\n\n### Re-enable token generation\nrun_step \"Enabling token generation\" eos space config default space.token.generation=1\n\n### Origin regexp tests\n#run_step \"Testing wildcard origin\" eos token --path $prefix/t_token/file.1 --expires $later --origin \\*#\\*#\\*\nrun_step \"Testing regex origin\" eos token --path $prefix/t_token/file.1 --expires $later --origin \\.*#\\.*#\\.*\n\n### Test 1: Single file token\neos mkdir -p $prefix/t_token/ > /dev/null 2>&1\nFILETOKEN=$(eos token --path $prefix/t_token/file.1 --expires $later)\n! xrdcp /etc/passwd \"$url/$prefix/t_token/file.1?eos.ruid=2&eos.rgid=2&authz=$FILETOKEN\" && echo -e \"${GREEN}Expected failure due to read-only token${NC}\"\n\nFILETOKEN=$(eos token --path $prefix/t_token/file.1 --expires $later --permission rwx)\n! xrdcp /etc/passwd \"$url/$prefix/t_token/file.wrong?eos.ruid=2&eos.rgid=2&authz=$FILETOKEN\" && echo -e \"${GREEN}Expected failure due to path mismatch${NC}\"\n! xrdcp /etc/passwd \"$url/$prefix/t_token/?eos.ruid=2&eos.rgid=2authz=$FILETOKEN\" && echo -e \"${GREEN}Expected failure due to path mismatch${NC}\"\n\nrun_step \"Upload file using CGI token\" xrdcp /etc/passwd \"$url/$prefix/t_token/file.1?eos.ruid=2&eos.rgid=2&authz=$FILETOKEN\"\nrun_step \"Overwrite file using CGI token\" xrdcp /etc/passwd \"$url/$prefix/t_token/file.1?eos.ruid=2&eos.rgid=2&authz=$FILETOKEN\" -f\nrun_step \"Upload using token as path\" xrdcp /etc/passwd \"$url//$FILETOKEN&eos.ruid=2&eos.rgid=2\" -f\n\n### Test 2: Single file token with specific user\nFILETOKEN=$(eos token --path $prefix/t_token/file.2 --expires $later --owner nobody --group nobody --permission rwx)\nrun_step \"Remove file.1\" eos rm $prefix/t_token/file.1\n! xrdcp /etc/passwd \"$url/$prefix/t_token/file.1?authz=$FILETOKEN\" && echo -e \"${GREEN}Expected failure on wrong file${NC}\"\nrun_step \"Upload file.2 using CGI token\" xrdcp /etc/passwd \"$url/$prefix/t_token/file.2?authz=$FILETOKEN\"\nrun_step \"Upload file.2 using token path\" xrdcp /etc/passwd \"$url//$FILETOKEN\" -f\n\nrun_step \"List contents\" eos ls -la $prefix/t_token/\n\n### Test 3: Directory token\nDIRTOKEN=$(eos token --path $prefix/t_token/ --permission rwx --expires $later)\n! xrdcp /etc/passwd \"$url/$prefix/file.3?authz=$DIRTOKEN\" -f && echo -e \"${GREEN}Expected path failure${NC}\"\n! xrdcp /etc/passwd \"$url/$prefix/t_token/file.3?authz=$DIRTOKEN\" -f && echo -e \"${GREEN}Expected tree flag failure${NC}\"\n! xrdcp /etc/passwd \"$url/$prefix/t_token/dir.1/file.3?authz=$DIRTOKEN\" -f && echo -e \"${GREEN}Expected tree flag failure${NC}\"\n\nDIRTOKEN=$(eos token --path $prefix/t_token/ --permission rwx --expires $later --tree)\neos token --token $DIRTOKEN\n\nrun_step \"Upload file.3 with tree token\" xrdcp /etc/passwd \"$url/$prefix/t_token/file.3?eos.ruid=2&eos.rgid=2&authz=$DIRTOKEN\" -f\nrun_step \"Upload nested file.3 with tree token\" xrdcp /etc/passwd \"$url/$prefix/t_token/dir.1/file.3?eos.ruid=2&eos.rgid=2&authz=$DIRTOKEN\" -f\nrun_step \"Upload deep nested file.4 with tree token\" xrdcp /etc/passwd \"$url/$prefix/t_token/dir.2/dir.2.1/file.4?eos.ruid=2&eos.rgid=2&authz=$DIRTOKEN\" -f\n\n### Test 4: Directory token for specific user\nDIRTOKEN=$(eos token --path $prefix/t_token/ --permission rwx --expires $later --owner nobody --group nobody)\n! xrdcp /etc/passwd \"$url/$prefix/file.4?eos.ruid=2&eos.rgid=2&authz=$DIRTOKEN\" -f && echo -e \"${GREEN}Expected permission fail${NC}\"\n! xrdcp /etc/passwd \"$url/$prefix/t_token/file.4?eos.ruid=2&eos.rgid=2&authz=$DIRTOKEN\" -f && echo -e \"${GREEN}Expected tree flag fail${NC}\"\n! xrdcp /etc/passwd \"$url/$prefix/t_token/dir.3/file.4?eos.ruid=2&eos.rgid=2&authz=$DIRTOKEN\" -f && echo -e \"${GREEN}Expected tree flag fail${NC}\"\n\nDIRTOKEN=$(eos token --path $prefix/t_token/ --permission rwx --expires $later --tree --owner nobody --group nobody)\neos token --token $DIRTOKEN\n\nrun_step \"Upload file.5\" xrdcp /etc/passwd \"$url/$prefix/t_token/file.5?eos.ruid=2&eos.rgid=2&authz=$DIRTOKEN\" -f\nrun_step \"Upload dir.3/file.5\" xrdcp /etc/passwd \"$url/$prefix/t_token/dir.3/file.5?eos.ruid=2&eos.rgid=2&authz=$DIRTOKEN\" -f\nrun_step \"Upload dir.4/dir.2.1/file.6\" xrdcp /etc/passwd \"$url/$prefix/t_token/dir.4/dir.2.1/file.6?eos.ruid=2&eos.rgid=2&authz=$DIRTOKEN\" -f\n\neos find $prefix/t_token/\neos ls -la $prefix/t_token/\neos ls -la $prefix/t_token/dir.1/\neos ls -la $prefix/t_token/dir.2/\neos ls -la $prefix/t_token/dir.3/\neos ls -la $prefix/t_token/dir.4/\n\n### Test 5: Directory token for mkdir/ls/rmdir\nDIRTOKEN=$(eos token --path $prefix/t_token/ --permission rx --expires $later --owner nobody --group nobody --tree)\n! env EOSAUTHZ=$DIRTOKEN eos mkdir -p $prefix/t_token/tree.1/tree.2 && echo -e \"${GREEN}Expected mkdir permission fail${NC}\"\n\nDIRTOKEN=$(eos token --path $prefix/t_token/ --permission rwx --expires $later --owner nobody --group nobody --tree)\nrun_step \"Create tree.1/tree.2\" env EOSAUTHZ=$DIRTOKEN eos mkdir -p $prefix/t_token/tree.1/tree.2\n\nDIRTOKEN=$(eos token --path $prefix/t_token/tree.1/ --permission rx --expires $later --owner nobody --group nobody)\n! env EOSAUTHZ=$DIRTOKEN eos ls -l $prefix/t_token/ && echo -e \"${GREEN}Expected list fail at base${NC}\"\nrun_step \"List tree.1\" env EOSAUTHZ=$DIRTOKEN eos ls -l $prefix/t_token/tree.1\n! env EOSAUTHZ=$DIRTOKEN eos ls -l $prefix/t_token/tree.1/tree.2 && echo -e \"${GREEN}Expected list fail deeper${NC}\"\n\nDIRTOKEN=$(eos token --path $prefix/t_token/tree.1/ --permission rx --expires $later --owner nobody --group nobody --tree)\n! env EOSAUTHZ=$DIRTOKEN eos ls -l $prefix/t_token/ && echo -e \"${GREEN}Expected list fail parent${NC}\"\nrun_step \"Recursive list inside tree.1\" env EOSAUTHZ=$DIRTOKEN eos ls -l $prefix/t_token/tree.1\nrun_step \"Recursive list tree.2\" env EOSAUTHZ=$DIRTOKEN eos ls -l $prefix/t_token/tree.1/tree.2\n\nDIRTOKEN=$(eos token --path $prefix/t_token/ --permission rx --expires $later --owner nobody --group nobody --tree)\n! env EOSAUTHZ=$DIRTOKEN eos rmdir $prefix/t_token/tree.1/tree.2 && echo -e \"${GREEN}Expected rmdir fail with rx only${NC}\"\n\nDIRTOKEN=$(eos token --path $prefix/t_token/ --permission rwx --expires $later --owner nobody --group nobody --tree)\nrun_step \"Remove directory tree.2\" env EOSAUTHZ=$DIRTOKEN eos rmdir $prefix/t_token/tree.1/tree.2\n\n### Test 6: TokenACL\nrun_step \"TokenAcl Directory\" eos mkdir -p $prefix/t_token/tokenacl/\nrun_step \"TokenAcl Permission\" eos chmod 700 $prefix/t_token/tokenacl/\n! env XrdSecPROTOCOL=unix eos token --path $prefix/t_token/tokenacl/ --permission rx && echo -e \"${GREEN}Expected token creation failure without ACL entry${NC}\"\nrun_step \"TokenAcl Setting\" eos attr set sys.acl=u:65534:trx $prefix/t_token/tokenacl/\n! env XrdSecPROTOCOL=unix eos token --path $prefix/t_token/tokenacl/ --permission rwx && echo -e \"${GREEN}Expected token creation failure due to missing write permission ${NC}\"\nrun_step \"TokenAcl GetToken\" env XrdSecPROTOCOL=unix eos token --path $prefix/t_token/tokenacl/\n\n### Test 7: Multipath token\nrun_step \"Multipath Token\" eos mkdir -p $prefix/t_token/multipath\nSINGLEPATHTOKEN=$(eos token --path $prefix/t_token/singlepath/ --permission rwx --expires $later --owner nobody --group nobody --tree)\nMULTIPATHTOKEN=$(eos token --path \"$prefix/t_token/singlepath/://:$prefix/t_token/multipath/\" --permission rwx --expires $later --owner nobody --group nobody --tree)\n! xrdcp /etc/passwd \"$url/$prefix/t_token/multipath/file?eos.ruid=2&eos.rgid=2&authz=$SINGLEPATHTOKEN\" -f && echo -e \"${GREEN}Expected to fail with single path token${NC}\"\nrun_step \"Upload Multipath\" xrdcp /etc/passwd \"$url/$prefix/t_token/multipath/file?eos.ruid=2&eos.rgid=2&authz=$MULTIPATHTOKEN\" -f\neos token --token $MULTIPATHTOKEN\n\n### Test 8: Directory listing with token\neos chmod -R 700 $prefix/t_token/\nDIRTOKEN=$(eos token --path $prefix/t_token/ --permission rx --expires $later --owner daemon --group daemon --tree)\nrun_step \"Listing directory\" xrdfs $url ls -l \"$prefix/t_token/?authz=$DIRTOKEN&xrd.wantprot=unix\"\n\n### Cleanup\nrun_step \"Final cleanup of test directory\" eos rm -r $prefix/t_token/\n\necho -e \"${GREEN}All tests passed successfully.${NC}\"\n"
  },
  {
    "path": "test/eos-traffic-shaping-test",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: eos-traffic-shaping-test\n# Author: Luis Antonio Obis Aparicio - CERN\n# ----------------------------------------------------------------------\n\nif [[ $# -ne 2 ]]; then\n  echo \"Error: Missing arguments.\"\n  echo \"Usage:    $0 <eos_mgm_hostname> <eos_instance_name>\"\n  echo \"Examples:\"\n  echo \"  $0 example.cern.ch example\"\n  echo \"  $0 localhost dev\"\n  exit 1\nfi\n\nEOS_MGM_HOSTNAME=$1\nEOS_INSTANCE_NAME=$2\nFAILURES=0\n\necho \"Configuring MGM Host: $EOS_MGM_HOSTNAME\"\necho \"Instance Name: $EOS_INSTANCE_NAME\"\n\nif ! command -v jq &> /dev/null; then\n  echo \"Error: 'jq' is required but not installed.\"\n  exit 1\nfi\n\nexport XrdSecPROTOCOL=sss\n\n# Enable traffic shaping\neos io shaping enable\n\neos io shaping config set --estimators-period 100 --report-period 100 --policy-period 100 --system-window 15\nsleep 8\n\necho \"Creating dummy files (10MB and 200MB)...\"\nTEST_FILE_10MB=/tmp/10mb.dat\ndd if=/dev/urandom of=${TEST_FILE_10MB} bs=1M count=10 iflag=fullblock &> /dev/null\n\nTEST_FILE_200MB=/tmp/200mb_throttle.dat\ndd if=/dev/urandom of=${TEST_FILE_200MB} bs=1M count=200 iflag=fullblock &> /dev/null\n\nDEST_URL=\"root://${EOS_MGM_HOSTNAME}//eos/${EOS_INSTANCE_NAME}/traffic_shaping\"\n\neos mkdir -p /eos/${EOS_INSTANCE_NAME}/traffic_shaping/\neos chmod 2777 /eos/${EOS_INSTANCE_NAME}/\neos chmod 2777 /eos/${EOS_INSTANCE_NAME}/traffic_shaping/\n\n# ±20% lower, ±35% upper for 1 MB/s target\nLOWER_BOUND=800000\nUPPER_BOUND=1350000\n\n# ---------------------------------------------------------\n# Test: Baseline System and Reporting Engine\n# ---------------------------------------------------------\necho \"\"\necho \"--- Testing Baseline System and Reporting Engine ---\"\nxrdcp ${TEST_FILE_10MB} \"${DEST_URL}/10mb.dat?eos.app=traffic-shaping-test-app\" -f > /dev/null\nsleep 1\n\nJSON_RESULT=$(eos io shaping ls --window 15 --sys --json)\n\nif echo \"$JSON_RESULT\" | jq -e '.[] | select(.id == \"traffic-shaping-test-app\") | (.write_rate_bps > 0)' > /dev/null; then\n  echo \"✅ Success: Baseline traffic detected.\"\nelse\n  echo \"❌ Error: Baseline traffic missing.\"\n  echo \"Debug Info: $JSON_RESULT\"\n  FAILURES=$((FAILURES + 1))\nfi\n\n# ---------------------------------------------------------\n# Test: App Throttling Enforcement\n# ---------------------------------------------------------\nTHROTTLE_APP=\"throttle-test-app\"\necho \"\"\necho \"--- Testing App Throttling ---\"\n\necho \"Priming feedback loop...\"\nxrdcp -f ${TEST_FILE_200MB} \"${DEST_URL}/prime.dat?eos.app=${THROTTLE_APP}\" > /dev/null 2>&1\n\necho \"Applying 1 MB/s write limit...\"\neos io shaping policy set --app ${THROTTLE_APP} --limit-write 1M\nsleep 3\n\necho \"Starting throttled background transfer...\"\nxrdcp -f ${TEST_FILE_200MB} \"${DEST_URL}/test.dat?eos.app=${THROTTLE_APP}\" > /dev/null 2>&1 &\nPID=$!\n\necho \"Waiting 22 seconds for strict EMA stabilization...\"\nsleep 22\nJSON_RESULT=$(eos io shaping ls --window 5 --json)\nkill -9 $PID 2>/dev/null; wait $PID 2>/dev/null || true\neos io shaping policy rm --app ${THROTTLE_APP}\n\nif echo \"$JSON_RESULT\" | jq -e --argjson l \"$LOWER_BOUND\" --argjson u \"$UPPER_BOUND\" '.[] | select(.id == \"throttle-test-app\") | (.write_rate_bps > $l) and (.write_rate_bps < $u)' > /dev/null; then\n  RATE=$(echo \"$JSON_RESULT\" | jq -r '.[] | select(.id == \"throttle-test-app\") | .write_rate_bps')\n  echo \"✅ Success: App throttled properly ($RATE bps).\"\nelse\n  RATE=$(echo \"$JSON_RESULT\" | jq -r '.[] | select(.id == \"throttle-test-app\") | .write_rate_bps // 0')\n  echo \"❌ Error: Expected ~1MB/s, got $RATE bps.\"\n  echo \"Debug Info: $JSON_RESULT\"\n  FAILURES=$((FAILURES + 1))\nfi\n\n# ---------------------------------------------------------\n# Test: UID Throttling Enforcement\n# ---------------------------------------------------------\nTEST_UID=\"19999\"\necho \"\"\necho \"--- Testing UID Throttling ---\"\n\necho \"Priming feedback loop...\"\nxrdcp -f ${TEST_FILE_200MB} \"${DEST_URL}/uid_prime.dat?eos.ruid=${TEST_UID}\" > /dev/null 2>&1\n\neos io shaping policy set --uid ${TEST_UID} --limit-write 1M\nsleep 3\n\nxrdcp -f ${TEST_FILE_200MB} \"${DEST_URL}/uid_test.dat?eos.ruid=${TEST_UID}\" > /dev/null 2>&1 &\nPID=$!\n\necho \"Waiting 22 seconds for strict EMA stabilization...\"\nsleep 22\nJSON_RESULT=$(eos io shaping ls --window 5 --users --json)\nkill -9 $PID 2>/dev/null; wait $PID 2>/dev/null || true\neos io shaping policy rm --uid ${TEST_UID}\n\nif echo \"$JSON_RESULT\" | jq -e --argjson l \"$LOWER_BOUND\" --argjson u \"$UPPER_BOUND\" '.[] | select(.id == \"19999\") | (.write_rate_bps > $l) and (.write_rate_bps < $u)' > /dev/null; then\n  echo \"✅ Success: UID throttled properly.\"\nelse\n  RATE=$(echo \"$JSON_RESULT\" | jq -r '.[] | select(.id == \"19999\") | .write_rate_bps // 0')\n  echo \"❌ Error: Expected ~1MB/s, got $RATE bps.\"\n  echo \"Debug Info: $JSON_RESULT\"\n  FAILURES=$((FAILURES + 1))\nfi\n\n# ---------------------------------------------------------\n# Test: GID Throttling Enforcement\n# ---------------------------------------------------------\nTEST_GID=\"18888\"\necho \"\"\necho \"--- Testing GID Throttling ---\"\n\necho \"Priming feedback loop...\"\nxrdcp -f ${TEST_FILE_200MB} \"${DEST_URL}/gid_prime.dat?eos.rgid=${TEST_GID}\" > /dev/null 2>&1\n\neos io shaping policy set --gid ${TEST_GID} --limit-write 1M\nsleep 3\n\nxrdcp -f ${TEST_FILE_200MB} \"${DEST_URL}/gid_test.dat?eos.rgid=${TEST_GID}\" > /dev/null 2>&1 &\nPID=$!\n\necho \"Waiting 22 seconds for strict EMA stabilization...\"\nsleep 22\nJSON_RESULT=$(eos io shaping ls --window 5 --groups --json)\nkill -9 $PID 2>/dev/null; wait $PID 2>/dev/null || true\neos io shaping policy rm --gid ${TEST_GID}\n\nif echo \"$JSON_RESULT\" | jq -e --argjson l \"$LOWER_BOUND\" --argjson u \"$UPPER_BOUND\" '.[] | select(.id == \"18888\") | (.write_rate_bps > $l) and (.write_rate_bps < $u)' > /dev/null; then\n  echo \"✅ Success: GID throttled properly.\"\nelse\n  RATE=$(echo \"$JSON_RESULT\" | jq -r '.[] | select(.id == \"18888\") | .write_rate_bps // 0')\n  echo \"❌ Error: Expected ~1MB/s, got $RATE bps.\"\n  echo \"Debug Info: $JSON_RESULT\"\n  FAILURES=$((FAILURES + 1))\nfi\n\n# ---------------------------------------------------------\n# Test: App Read Throttling Enforcement\n# ---------------------------------------------------------\nREAD_APP=\"read-throttle-app\"\necho \"\"\necho \"--- Testing App Read Throttling ---\"\n\nxrdcp -f ${TEST_FILE_200MB} \"${DEST_URL}/read_test.dat\" > /dev/null 2>&1\necho \"Priming read feedback loop...\"\nxrdcp -f \"${DEST_URL}/read_test.dat?eos.app=${READ_APP}\" /dev/null > /dev/null 2>&1\n\neos io shaping policy set --app ${READ_APP} --limit-read 1M\nsleep 3\n\nxrdcp -f \"${DEST_URL}/read_test.dat?eos.app=${READ_APP}\" /dev/null > /dev/null 2>&1 &\nPID=$!\n\necho \"Waiting 22 seconds for strict EMA stabilization...\"\nsleep 22\nJSON_RESULT=$(eos io shaping ls --window 5 --json)\nkill -9 $PID 2>/dev/null; wait $PID 2>/dev/null || true\neos io shaping policy rm --app ${READ_APP}\n\nif echo \"$JSON_RESULT\" | jq -e --argjson l \"$LOWER_BOUND\" --argjson u \"$UPPER_BOUND\" '.[] | select(.id == \"read-throttle-app\") | (.read_rate_bps > $l) and (.read_rate_bps < $u)' > /dev/null; then\n  echo \"✅ Success: App read throttled properly.\"\nelse\n  RATE=$(echo \"$JSON_RESULT\" | jq -r '.[] | select(.id == \"read-throttle-app\") | .read_rate_bps // 0')\n  echo \"❌ Error: Expected ~1MB/s, got $RATE bps.\"\n  echo \"Debug Info: $JSON_RESULT\"\n  FAILURES=$((FAILURES + 1))\nfi\n\n# ---------------------------------------------------------\n# Test: Policy Enable / Disable Toggle\n# ---------------------------------------------------------\nTOGGLE_APP=\"toggle-test-app\"\necho \"\"\necho \"--- Testing Policy Toggle ---\"\n\neos io shaping policy set --app ${TOGGLE_APP} --limit-write 1M\neos io shaping policy set --app ${TOGGLE_APP} --disable\nsleep 3\n\nSTART_MS=$(date +%s%3N)\nxrdcp -f ${TEST_FILE_10MB} \"${DEST_URL}/toggle_disabled.dat?eos.app=${TOGGLE_APP}\" > /dev/null\nEND_MS=$(date +%s%3N)\necho \"Transfer with disabled policy took $((END_MS - START_MS)) ms.\"\n\neos io shaping policy set --app ${TOGGLE_APP} --enable\necho \"Priming feedback loop...\"\nxrdcp -f ${TEST_FILE_200MB} \"${DEST_URL}/toggle_prime.dat?eos.app=${TOGGLE_APP}\" > /dev/null 2>&1\nsleep 3\n\nxrdcp -f ${TEST_FILE_200MB} \"${DEST_URL}/toggle_test.dat?eos.app=${TOGGLE_APP}\" > /dev/null 2>&1 &\nPID=$!\n\necho \"Waiting 22 seconds for strict EMA stabilization...\"\nsleep 22\nJSON_RESULT=$(eos io shaping ls --window 5 --json)\nkill -9 $PID 2>/dev/null; wait $PID 2>/dev/null || true\neos io shaping policy rm --app ${TOGGLE_APP}\n\nif echo \"$JSON_RESULT\" | jq -e --argjson l \"$LOWER_BOUND\" --argjson u \"$UPPER_BOUND\" '.[] | select(.id == \"toggle-test-app\") | (.write_rate_bps > $l) and (.write_rate_bps < $u)' > /dev/null; then\n  echo \"✅ Success: Policy re-enabled correctly.\"\nelse\n  RATE=$(echo \"$JSON_RESULT\" | jq -r '.[] | select(.id == \"toggle-test-app\") | .write_rate_bps // 0')\n  echo \"❌ Error: Policy toggle failed. Got $RATE bps.\"\n  echo \"Debug Info: $JSON_RESULT\"\n  FAILURES=$((FAILURES + 1))\nfi\n\n# ---------------------------------------------------------\n# Test: Dynamic Limit Change (PID Convergence)\n# ---------------------------------------------------------\nDYN_APP=\"dynamic-limit-app\"\necho \"\"\necho \"--- Testing Dynamic Limit Change ---\"\n\necho \"Applying 1 MB/s limit...\"\neos io shaping policy set --app ${DYN_APP} --limit-write 1M\n\necho \"Priming feedback loop...\"\nxrdcp -f ${TEST_FILE_200MB} \"${DEST_URL}/dyn_prime.dat?eos.app=${DYN_APP}\" > /dev/null 2>&1\nsleep 2\n\necho \"Starting background transfer...\"\nxrdcp -f ${TEST_FILE_200MB} \"${DEST_URL}/dyn_test.dat?eos.app=${DYN_APP}\" > /dev/null 2>&1 &\nPID_DYN=$!\n\necho \"Waiting 20 seconds for PID to stabilize at 1 MB/s...\"\nsleep 20\n\necho \"Changing limit dynamically to 2 MB/s...\"\neos io shaping policy set --app ${DYN_APP} --limit-write 2M\n\necho \"Waiting 22 seconds for PID to release and converge to new 2 MB/s limit...\"\nsleep 22\n\nJSON_RESULT=$(eos io shaping ls --window 5 --json)\nkill -9 $PID_DYN 2>/dev/null; wait $PID_DYN 2>/dev/null || true\neos io shaping policy rm --app ${DYN_APP}\n\n# Adjusted bounds to match the new strict ±20% tolerance for 2 MB/s (1.6M to 2.4M)\nif echo \"$JSON_RESULT\" | jq -e --argjson l 1600000 --argjson u 2400000 '.[] | select(.id == \"dynamic-limit-app\") | (.write_rate_bps > $l) and (.write_rate_bps < $u)' > /dev/null; then\n  RATE=$(echo \"$JSON_RESULT\" | jq -r '.[] | select(.id == \"dynamic-limit-app\") | .write_rate_bps')\n  echo \"✅ Success: PID converged to new limit ($RATE bps).\"\nelse\n  RATE=$(echo \"$JSON_RESULT\" | jq -r '.[] | select(.id == \"dynamic-limit-app\") | .write_rate_bps // 0')\n  echo \"❌ Error: PID convergence failed. Got $RATE bps.\"\n  echo \"Debug Info: $JSON_RESULT\"\n  FAILURES=$((FAILURES + 1))\nfi\n\n# ---------------------------------------------------------\n# Test: Multiple SMA/EMA Windows Consistency\n# ---------------------------------------------------------\necho \"\"\necho \"--- Testing Multiple Windows Consistency ---\"\n\nWINDOW_APP=\"window-test-app\"\n( while true; do xrdcp -f ${TEST_FILE_10MB} \"${DEST_URL}/window_test.dat?eos.app=${WINDOW_APP}\" -s > /dev/null 2>&1; done ) &\nPID=$!\n\nsleep 5\nRESULT_1S=$(eos io shaping ls --window 1 --json)\nRESULT_5S=$(eos io shaping ls --window 5 --json)\n\nkill -9 $PID 2>/dev/null; wait $PID 2>/dev/null || true\n\nRATE_1S=$(echo \"$RESULT_1S\" | jq -r \".[] | select(.id == \\\"${WINDOW_APP}\\\") | .write_rate_bps // 0\")\nRATE_5S=$(echo \"$RESULT_5S\" | jq -r \".[] | select(.id == \\\"${WINDOW_APP}\\\") | .write_rate_bps // 0\")\n\nif (( $(echo \"$RATE_1S > 0\" | bc -l) )) && (( $(echo \"$RATE_5S > 0\" | bc -l) )); then\n  echo \"✅ Success: Both windows report non-zero rates (1s: $RATE_1S bps, 5s: $RATE_5S bps).\"\nelse\n  echo \"❌ Error: Zero rate detected. 1s: $RATE_1S bps, 5s: $RATE_5S bps.\"\n  echo \"Debug Info: 1s: $RESULT_1S, 5s: $RESULT_5S\"\n  FAILURES=$((FAILURES + 1))\nfi\n\n# ---------------------------------------------------------\n# Test: Simultaneous Read and Write Limits\n# ---------------------------------------------------------\nRW_APP=\"rw-limit-app\"\necho \"\"\necho \"--- Testing Simultaneous R/W Limits ---\"\n\nxrdcp -f ${TEST_FILE_200MB} \"${DEST_URL}/rw_test.dat?eos.app=${RW_APP}\" > /dev/null 2>&1\n\necho \"Priming feedback loops...\"\nxrdcp -f ${TEST_FILE_200MB} \"${DEST_URL}/rw_prime.dat?eos.app=${RW_APP}\" > /dev/null 2>&1\nxrdcp -f \"${DEST_URL}/rw_test.dat?eos.app=${RW_APP}\" /dev/null > /dev/null 2>&1\n\neos io shaping policy set --app ${RW_APP} --limit-read 1M --limit-write 1M\nsleep 3\n\nxrdcp -f ${TEST_FILE_200MB} \"${DEST_URL}/rw_write.dat?eos.app=${RW_APP}\" > /dev/null 2>&1 &\nPID_W=$!\nxrdcp -f \"${DEST_URL}/rw_test.dat?eos.app=${RW_APP}\" /dev/null > /dev/null 2>&1 &\nPID_R=$!\n\necho \"Waiting 22 seconds for strict EMA stabilization...\"\nsleep 22\nJSON_RESULT=$(eos io shaping ls --window 5 --json)\n\nkill -9 $PID_W $PID_R 2>/dev/null; wait $PID_W $PID_R 2>/dev/null || true\neos io shaping policy rm --app ${RW_APP}\n\nRW_W_RATE=$(echo \"$JSON_RESULT\" | jq -r \".[] | select(.id == \\\"${RW_APP}\\\") | .write_rate_bps // 0\")\nRW_R_RATE=$(echo \"$JSON_RESULT\" | jq -r \".[] | select(.id == \\\"${RW_APP}\\\") | .read_rate_bps // 0\")\n\nif (( $(echo \"$RW_W_RATE > $LOWER_BOUND\" | bc -l) )) && (( $(echo \"$RW_R_RATE > $LOWER_BOUND\" | bc -l) )); then\n  echo \"✅ Success: Both independently throttled (R: $RW_R_RATE, W: $RW_W_RATE).\"\nelse\n  echo \"❌ Error: R/W failure. R: $RW_R_RATE, W: $RW_W_RATE.\"\n  echo \"Debug Info: $JSON_RESULT\"\n  FAILURES=$((FAILURES + 1))\nfi\n\necho \"\"\nif [[ $FAILURES -eq 0 ]]; then\n  echo \"🎉 All validations passed perfectly!\"\n  exit 0\nelse\n  echo \"❌ Completed with $FAILURES errors. Failing the pipeline.\"\n  exit 1\nfi\n"
  },
  {
    "path": "test/eos_io_tool.cc",
    "content": "//------------------------------------------------------------------------------\n// File: eos-io-tool.cc\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n/*!\n@details This program is used to test out various IO operations done on EOS files.\n  One can write, read files in sequential mode or using a certain pattern defined\n  in a separate file. The file outside EOS is read according to the pattern and\n  then written in EOS using the same sequence of blocks. The same is valid for\n  reading.\n*/\n\n#include <map>\n#include <cstdlib>\n#include <cstdio>\n#include <string>\n#include <fstream>\n#include <iostream>\n#include <memory>\n#include <sys/time.h>\n#include <getopt.h>\n#include <XrdCl/XrdClFile.hh>\n#include <XrdSfs/XrdSfsInterface.hh>\n#include \"fst/io/FileIoPlugin.hh\"\n#include \"fst/io/FileIo.hh\"\n#include \"fst/io/AsyncMetaHandler.hh\"\n#include <XrdCl/XrdClDefaultEnv.hh>\n#include \"common/Logging.hh\"\n#include \"common/Mapping.hh\"\n\n//! Type of operations supported\nenum OperationType {\n  RD_SEQU,\n  RD_PATT,\n  WR_SEQU,\n  WR_PATT,\n  OP_NONE\n};\n\nuint64_t block_size = 1048576;  ///< block size of rd/wr operations default 1MB\nuint32_t prefetch_size = block_size;   ///< prefetch size reading, default 1MB\nint timeout = 60;         ///< asynchronous timeout value, default 60 seconds\nbool do_async = false;    ///< by default do sync operations\nbool do_update = false;   ///< write a file trying to update it\nstd::string pattern_file = \"\"; ///< file which contains the rd/wr pattern\nint debug = 0; ///< flag to enable debug\n\n//------------------------------------------------------------------------------\n// Read the whole file sequentially in sync/async mode\n//------------------------------------------------------------------------------\nbool\nReadSequentially(XrdCl::URL& url, std::string& ext_file)\n{\n  int status;\n  bool ret = true;\n  char* buffer = new char[block_size];\n  XrdSfsFileOpenMode flags_sfs =  SFS_O_RDONLY;\n  eos::fst::AsyncMetaHandler* ptr_handler = NULL;\n  eos::fst::FileIo* eosf = eos::fst::FileIoPlugin::GetIoObject(\n                             url.GetURL().c_str());\n  XrdOucString open_opaque = \"\";\n\n  if (do_async) {\n    // Enable readahead\n    std::ostringstream osstr;\n    osstr << \"fst.readahead=true&fst.blocksize=\"\n          << static_cast<int>(prefetch_size);\n    open_opaque = osstr.str().c_str();\n  }\n\n  // Open the file for reading from EOS\n  status = eosf->fileOpen(flags_sfs, 0, open_opaque.c_str());\n\n  if (status == SFS_ERROR) {\n    eos_static_err(\"Failed to open EOS file in rd mode:%s\", url.GetURL().c_str());\n    delete eosf;\n    delete[] buffer;\n    return false;;\n  }\n\n  // Do stat to find out the file size\n  struct stat buf;\n  status = eosf->fileStat(&buf);\n\n  if (status) {\n    eos_static_err(\"Error doing stat on the EOS file\");\n    delete eosf;\n    delete[] buffer;\n    return false;\n  }\n\n  eos_static_debug(\"EOS file size: %zu\", buf.st_size);\n  // Open file outside EOS, where the data is written\n  FILE* extf = fopen(ext_file.c_str(), \"w+\");\n\n  if (!extf) {\n    eos_static_err(\"Failed to open ext file:%s in rd mode\", ext_file.c_str());\n    delete eosf;\n    delete[] buffer;\n    return false;\n  }\n\n  uint64_t eos_fsize = buf.st_size;\n  uint64_t offset = 0;\n  int64_t nread = 0;\n  int64_t nwrite = 0;\n\n  if (do_async) {\n    ptr_handler = static_cast<eos::fst::AsyncMetaHandler*>\n                  (eosf->fileGetAsyncHandler());\n  }\n\n  // Read the whole file sequentially\n  uint64_t total_read = 0ull;\n\n  while (total_read < eos_fsize) {\n    if (do_async) {\n      nread = eosf->fileReadPrefetch(offset, buffer, block_size, timeout);\n    } else {\n      nread = eosf->fileRead(offset, buffer, block_size);\n    }\n\n    if (nread == SFS_ERROR) {\n      eos_static_err(\"Error while reading at offset:%llu\", offset);\n      ret = false;\n      break;\n    }\n\n    offset += nread;\n    total_read += nread;\n\n    if (do_async) {\n      // Wait async request to be satisfied\n      uint16_t error_type = ptr_handler->WaitOK();\n\n      if (error_type != XrdCl::errNone) {\n        eos_static_err(\"Error while doing an async write operation\");\n        ret = false;\n        break;\n      }\n    }\n\n    // Write data to the file outside EOS\n    nwrite = fwrite(static_cast<void*>(buffer), sizeof(char), nread, extf);\n\n    if (nwrite != nread) {\n      eos_static_err(\"Error while writing to file outside EOS\");\n      ret = false;\n      break;\n    }\n  }\n\n  // Close files\n  eosf->fileClose(timeout);\n  fclose(extf);\n  // Free memory\n  delete eosf;\n  delete[] buffer;\n  return ret;\n}\n\n\n//------------------------------------------------------------------------------\n// Read the pattern map from the provided pattern_file\n//------------------------------------------------------------------------------\nvoid\nLoadPattern(std::string& pattern_file,\n            std::multimap<uint64_t, uint32_t>& map_pattern)\n{\n  // Open file containing the pattern to read\n  uint64_t off_start;\n  uint32_t off_end;\n  std::string line;\n  std::ifstream ifpattern(pattern_file.c_str());\n\n  // Read pattern from file\n  if (ifpattern.is_open()) {\n    while (std::getline(ifpattern, line)) {\n      eos_static_debug(\"Line:%s\", line.c_str());\n      std::istringstream iss(line);\n\n      // Ignore comment lines\n      if (!(line.find(\"#\") == 0)) {\n        if (!(iss >> off_start >> off_end)) {\n          eos_static_err(\"Error while parsing the pattern file\");\n          return;\n        }\n\n        map_pattern.insert(std::make_pair(off_start, (off_end - off_start)));\n      }\n    }\n\n    // Close pattern file\n    ifpattern.close();\n  } else {\n    eos_static_err(\"Error while opening the pattern file\");\n    return;\n  }\n\n  // Print the pattern map\n  eos_static_debug(\"The pattern map is:\");\n\n  for (auto iter = map_pattern.begin(); iter != map_pattern.end(); ++iter) {\n    eos_static_debug(\"off:%ju len:%ju\", iter->first, (uint64_t)iter->second);\n  }\n}\n\n\n//------------------------------------------------------------------------------\n// Read the file in sync/async mode using a certain patter specified in the\n// patter file - list of offset <-> length pieces to be read from the EOS file\n// and written to the external file\n//------------------------------------------------------------------------------\nbool\nReadPattern(XrdCl::URL& url,\n            std::string& ext_file,\n            std::string& pattern_file)\n{\n  int status;\n  bool ret = true;\n  char* buffer = new char[block_size];\n  XrdSfsFileOpenMode flags_sfs = SFS_O_RDONLY;\n  eos::fst::AsyncMetaHandler* ptr_handler = NULL;\n  eos::fst::FileIo* eosf = eos::fst::FileIoPlugin::GetIoObject(\n                             url.GetURL());\n  std::multimap<uint64_t, uint32_t> map_pattern;\n  XrdOucString open_opaque = \"\";\n\n  // Enable the readahead in async mode\n  if (do_async) {\n    std::ostringstream osstr;\n    osstr << \"fst.readahead=true&fst.blocksize=\"\n          << static_cast<int>(prefetch_size);\n    open_opaque = osstr.str().c_str();\n  }\n\n  // Open the file for reading from EOS\n  status = eosf->fileOpen(flags_sfs, 0, open_opaque.c_str(), timeout);\n\n  if (status == SFS_ERROR) {\n    eos_static_err(\"Failed to open EOS file:%s in rd mode\", url.GetURL().c_str());\n    delete eosf;\n    delete[] buffer;\n    return false;;\n  }\n\n  // Do stat to find out the file size\n  struct stat buf;\n  status = eosf->fileStat(&buf);\n\n  if (status) {\n    eos_static_err(\"Error while doing the stat on the EOS file.\");\n    delete eosf;\n    delete[] buffer;\n    return false;\n  }\n\n  eos_static_debug(\"EOS file size:%zu\", buf.st_size);\n  // Load the pattern used for reading\n  LoadPattern(pattern_file, map_pattern);\n\n  if (map_pattern.empty()) {\n    eos_static_err(\"Error the pattern map is empty\");\n    delete eosf;\n    delete[] buffer;\n    return false;\n  }\n\n  // Open file outside EOS, where the data is written\n  int ext_fd = open(ext_file.c_str(), O_CREAT | O_WRONLY | O_LARGEFILE, S_IRWXU);\n\n  if (ext_fd < 0) {\n    eos_static_err(\"Failed to open ext file:%s in wr mode: \", ext_file.c_str());\n    delete eosf;\n    delete[] buffer;\n    return false;\n  }\n\n  uint64_t piece_off;\n  uint32_t piece_len;\n  int64_t nread = 0;\n  int64_t nwrite = 0;\n  int32_t length;\n\n  if (do_async) {\n    ptr_handler = static_cast<eos::fst::AsyncMetaHandler*>\n                  (eosf->fileGetAsyncHandler());\n  }\n\n  // Read each of the pieces from the pattern\n  for (auto iter = map_pattern.begin(); iter != map_pattern.end(); ++iter) {\n    piece_off = iter->first;\n    piece_len = iter->second;\n    eos_static_debug(\"Piece off:%ju len:%jd \", piece_off, piece_len);\n\n    // Read a piece which can be bigger than the block size\n    while (piece_len > 0) {\n      length = (int32_t)((piece_len < block_size) ? piece_len : block_size);\n      eos_static_debug(\"Reading at off:%ju, length:%jd\", piece_off, (int64_t)length);\n\n      // Read from the EOS file\n      if (do_async) {\n        nread = eosf->fileReadPrefetch(piece_off, buffer, length, timeout);\n      } else {\n        nread = eosf->fileRead(piece_off, buffer, length);\n      }\n\n      if (nread == SFS_ERROR) {\n        eos_static_err(\"Error while reading at offset:%zu\", piece_off);\n        ret = false;\n        break;\n      }\n\n      if (do_async) {\n        // Wait async request to be satisfied\n        uint16_t error_type = ptr_handler->WaitOK();\n\n        if (error_type != XrdCl::errNone) {\n          eos_static_err(\"Error while doing async write operation\");\n          ret = false;\n          break;\n        }\n      }\n\n      // Write data to the file outside EOS at the same offset\n      nwrite = pwrite(ext_fd, static_cast<const void*>(buffer), nread, piece_off);\n\n      if (nwrite != nread) {\n        eos_static_err(\"Error while writing to file outside EOS\");\n        ret = false;\n        break;\n      }\n\n      piece_off += nread;\n      piece_len -= nread;\n    }\n  }\n\n  // Close files\n  eosf->fileClose(timeout);\n  close(ext_fd);\n  // Free memory\n  delete eosf;\n  delete[] buffer;\n  return ret;\n}\n\n\n//------------------------------------------------------------------------------\n// Write file sequentially to EOS in sync/async mode\n//------------------------------------------------------------------------------\nbool\nWriteSequentially(XrdCl::URL& url,\n                  std::string& ext_file)\n{\n  int status;\n  bool ret = true;\n  char* buffer = new char[block_size];\n  eos::fst::AsyncMetaHandler* ptr_handler = NULL;\n  eos::fst::FileIo* eosf = eos::fst::FileIoPlugin::GetIoObject(\n                             url.GetURL().c_str());\n  XrdOucString open_opaque = \"\";\n  XrdSfsFileOpenMode flags_sfs;\n  mode_t mode_sfs = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH  ;\n\n  // Open the file for update or truncate it\n  if (do_update) {\n    eos_static_debug(\"EOS file opened for update\");\n    flags_sfs = SFS_O_RDWR;\n  } else {\n    eos_static_debug(\"EOS file opened for creation\");\n    flags_sfs = SFS_O_CREAT | SFS_O_RDWR;\n  }\n\n  // Open the file for writing/update from EOS\n  status = eosf->fileOpen(flags_sfs, mode_sfs, open_opaque.c_str());\n\n  if (status == SFS_ERROR) {\n    eos_static_err(\"Failed to open EOS file:%s in wr/upd mode\",\n                   url.GetURL().c_str());\n    delete eosf;\n    delete[] buffer;\n    return false;;\n  }\n\n  // Open file outside EOS, from where the data is read\n  int ext_fd = open(ext_file.c_str(), O_RDONLY | O_LARGEFILE);\n\n  if (ext_fd < 0) {\n    eos_static_err(\"Failed to open ext file:%s in rd mode\", ext_file.c_str());\n    delete eosf;\n    delete[] buffer;\n    return false;\n  }\n\n  // Do stat to find out the size of the file to be written\n  struct stat buf;\n\n  if (fstat(ext_fd, &buf)) {\n    eos_static_err(\"Error while trying to stat external file\");\n    close(ext_fd);\n    delete eosf;\n    delete[] buffer;\n    return false;\n  }\n\n  eos_static_debug(\"External file size:%zu\", buf.st_size);\n  uint64_t ext_fsize = buf.st_size;\n  uint64_t offset = 0;\n  int64_t nread = 0;\n  int64_t nwrite = 0;\n  int32_t length;\n\n  if (do_async) {\n    ptr_handler = static_cast<eos::fst::AsyncMetaHandler*>\n                  (eosf->fileGetAsyncHandler());\n  }\n\n  // Read the whole file sequentially\n  while (ext_fsize > 0) {\n    length = ((ext_fsize < block_size) ? ext_fsize : block_size);\n    eos_static_debug(\"Current file size:%llu\", ext_fsize);\n    // Read from the external file\n    nread = pread(ext_fd, buffer, length, offset);\n\n    if (nread != length) {\n      eos_static_err(\"Error while reading at offset: %llu\", offset);\n      ret = false;\n      break;\n    }\n\n    // Write data to the EOS file\n    if (do_async) {\n      nwrite = eosf->fileWriteAsync(offset, buffer, nread, timeout);\n    } else {\n      nwrite = eosf->fileWrite(offset, buffer, nread);\n    }\n\n    if (nwrite != nread) {\n      eos_static_err(\"Error while writing to EOS file\");\n      ret = false;\n      break;\n    }\n\n    if (do_async) {\n      // Wait async request to be satisfied\n      uint16_t error_type = ptr_handler->WaitOK();\n\n      if (error_type != XrdCl::errNone) {\n        eos_static_err(\"Error while doing an async write operation\");\n        ret = false;\n        break;\n      }\n    }\n\n    offset += nread;\n    ext_fsize -= nread;\n  }\n\n  // Close files\n  eosf->fileClose(timeout);\n  close(ext_fd);\n  // Free memory\n  delete eosf;\n  delete[] buffer;\n  return ret;\n}\n\n\n//------------------------------------------------------------------------------\n// Write the file in sync/async mode using a certain pattern specified in the\n// pattern file - list of offset <-> length pieces to be read from the external\n// file and written to the EOS file\n//------------------------------------------------------------------------------\nbool\nWritePattern(XrdCl::URL& url,\n             std::string& ext_file,\n             std::string& pattern_file)\n{\n  int status;\n  bool ret = true;\n  char* buffer = new char[block_size];\n  eos::fst::AsyncMetaHandler* ptr_handler = NULL;\n  eos::fst::FileIo* eosf = eos::fst::FileIoPlugin::GetIoObject(\n                             url.GetURL().c_str());\n  XrdOucString open_opaque = \"\";\n  XrdSfsFileOpenMode flags_sfs;\n  XrdSfsMode mode_sfs = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH  ;\n  std::multimap<uint64_t, uint32_t> map_pattern;\n\n  // Open the file for update or truncate it\n  if (do_update) {\n    eos_static_debug(\"EOS file opened for update\");\n    flags_sfs = SFS_O_RDWR;\n  } else {\n    eos_static_debug(\"EOS file opend for creation\");\n    flags_sfs = SFS_O_CREAT | SFS_O_RDWR;\n  }\n\n  // Open the file for writing/update from EOS\n  status = eosf->fileOpen(flags_sfs, mode_sfs, open_opaque.c_str());\n\n  if (status == SFS_ERROR) {\n    eos_static_err(\"Failed to open EOS file:%s in wr/upd mode\",\n                   url.GetURL().c_str());\n    delete eosf;\n    delete[] buffer;\n    return false;;\n  }\n\n  // Open file outside EOS, from where the data is read\n  int ext_fd = open(ext_file.c_str(), O_RDONLY | O_LARGEFILE);\n\n  if (ext_fd < 0) {\n    eos_static_err(\"Failed to open ext file:%s in rd mode: \", ext_file.c_str());\n    delete eosf;\n    delete[] buffer;\n    return false;\n  }\n\n  // Load the pattern used for reading\n  LoadPattern(pattern_file, map_pattern);\n\n  if (map_pattern.empty()) {\n    eos_static_err(\"Error the pattern map is empty\");\n    close(ext_fd);\n    delete eosf;\n    delete[] buffer;\n    return false;\n  }\n\n  uint64_t piece_off;\n  uint32_t piece_len;\n  int64_t nread = 0;\n  int64_t nwrite = 0;\n  int32_t length;\n\n  if (do_async) {\n    ptr_handler = static_cast<eos::fst::AsyncMetaHandler*>\n                  (eosf->fileGetAsyncHandler());\n  }\n\n  // Read the pieces specified in the pattern map\n  for (auto iter = map_pattern.begin(); iter != map_pattern.end(); ++iter) {\n    piece_off = iter->first;\n    piece_len = iter->second;\n    eos_static_debug(\"Piece off:%llu len:%lu\", piece_off, piece_len);\n\n    while (piece_len > 0) {\n      length = ((piece_len < block_size) ? piece_len : block_size);\n      // Read from the external file\n      nread = pread(ext_fd, buffer, length, piece_off);\n\n      if (nread != length) {\n        eos_static_err(\"Error while reading at offset:%llu\", piece_off);\n        ret = false;\n        break;\n      }\n\n      // Write data to the EOS file\n      if (do_async) {\n        nwrite = eosf->fileWriteAsync(piece_off, buffer, nread, timeout);\n      } else {\n        eos_static_debug(\"wrpatt piece_off=%llu, piece_len=%llu\", piece_off, piece_len);\n        nwrite = eosf->fileWrite(piece_off, buffer, nread);\n      }\n\n      if (nwrite != nread) {\n        eos_static_err(\"Error while writing to EOS file\");\n        ret = false;\n        break;\n      }\n\n      if (do_async) {\n        // Wait async request to be satisfied\n        uint16_t error_type = ptr_handler->WaitOK();\n\n        if (error_type != XrdCl::errNone) {\n          eos_static_err(\"Error while doing an async write operation\");\n          ret = false;\n          break;\n        }\n      }\n\n      piece_off += nread;\n      piece_len -= nread;\n    }\n  }\n\n  // Close files\n  eosf->fileClose(timeout);\n  close(ext_fd);\n  // Free memory\n  delete eosf;\n  delete[] buffer;\n  return ret;\n}\n\n\n\n//------------------------------------------------------------------------------\n// Main function\n//------------------------------------------------------------------------------\nint main(int argc, char* argv[])\n{\n  // Set the TimeoutResolution to 1 for XrdCl\n  XrdCl::Env* env = XrdCl::DefaultEnv::GetEnv();\n  env->PutInt(\"TimeoutResolution\", 1);\n  // Build the usage string\n  std::ostringstream usage_sstr;\n  usage_sstr << \"Usage: \" << std::endl\n             << \"eos-io-tool --operation <rdsequ/rdpatt/wrsequ/wrpatt> \"\n             << std::endl\n             << \"            --eosfile <eos_file> \" << std::endl\n             << \"            --extfile <ext_file> \" << std::endl\n             << \"            [--patternfile <pf>]\" << std::endl\n             << \"            [--blocksize <bs>] \" << std::endl\n             << \"            [--filesize <fs>] \" << std::endl\n             << \"            [--timeout <val>] \" << std::endl\n             << \"            [--prefetchsize <bytes>]\" << std::endl\n             << \"            [--logfile <logfile>] \" << std::endl\n             << \"            [--async] [--update] [--help]\" << std::endl;\n  // Initialise the logging\n  eos::common::LogId logId;\n  eos::common::Logging& g_logging = eos::common::Logging::GetInstance();\n  g_logging.SetLogPriority(LOG_INFO);\n  g_logging.SetUnit(\"eosio@local\");\n  // Log only messages from functions in this file\n  g_logging.SetFilter(\"PASS:ReadSequentially,WriteSequentially,\"\n                      \"LoadPattern,ReadPattern,WritePattern,main\");\n\n  if (argc < 2) {\n    std::cout << usage_sstr.str() << std::endl;\n    return 1;\n  }\n\n  XrdCl::URL url_file;\n  std::string ext_file;\n  OperationType op_type = OP_NONE;\n  static int async_op = 0;\n  static int update_op = 0;\n\n  // Parse the argument options\n  while (1) {\n    static struct option long_options[] = {\n      {\"operation\",    required_argument, 0, 'a'},\n      {\"eosfile\",      required_argument, 0, 'b'},\n      {\"extfile\",      required_argument, 0, 'c'},\n      {\"blocksize\",    required_argument, 0, 'd'},\n      {\"timeout\",      required_argument, 0, 'f'},\n      {\"logfile\",      required_argument, 0, 'e'},\n      {\"prefetchsize\", required_argument, 0, 'g'},\n      {\"patternfile\",  required_argument, 0, 'i'},\n      {\"async\",        no_argument, &async_op,  1},\n      {\"update\",       no_argument, &update_op, 1},\n      {\"debug\",        no_argument, &debug,     1},\n      {\"help\",         no_argument,       0, 'h'},\n      {0, 0, 0, 0}\n    };\n    // getopt_long stores the option index here\n    int option_index = 0;\n    std::string val;\n    int c = getopt_long(argc, argv, \"a:b:c:d:e:f:g:h\",\n                        long_options, &option_index);\n\n    // Detect the end of the options\n    if (c == -1) {\n      break;\n    }\n\n    switch (c) {\n    case 0: {\n      // If this option set a flag, do nothing now\n      if (long_options[option_index].flag != 0) {\n        break;\n      }\n    }\n\n    // fallthrough\n\n    case 'a': {\n      val = optarg;\n\n      if (val == \"rdsequ\") {\n        op_type = RD_SEQU;\n      } else if (val == \"rdpatt\") {\n        op_type = RD_PATT;\n      } else if (val == \"wrsequ\") {\n        op_type = WR_SEQU;\n      } else if (val == \"wrpatt\") {\n        op_type = WR_PATT;\n      }\n\n      if (op_type == OP_NONE) {\n        std::cerr << \"No such operation type\" << std::endl;\n        exit(1);\n      }\n\n      break;\n    }\n\n    case 'b': {\n      val = optarg;\n      url_file.FromString(val);\n\n      if (!url_file.IsValid()) {\n        std::cerr << \"EOS file URL is no valid\" << std::endl;\n        return 1;\n      }\n\n      break;\n    }\n\n    case 'c': {\n      ext_file = optarg;\n      break;\n    }\n\n    case 'd': {\n      block_size = atoi(optarg);\n      break;\n    }\n\n    case 'f': {\n      timeout = atoi(optarg);\n      break;\n    }\n\n    case 'e': {\n      val = optarg; // log file name/path\n      FILE* fp = fopen(val.c_str(), \"a+\");\n\n      if (fp) {\n        // Redirect stdout and stderr to the log file\n        dup2(fileno(fp), fileno(stdout));\n        dup2(fileno(fp), fileno(stderr));\n      } else {\n        std::cerr << \"Failed to open logging file\" << std::endl;\n      }\n\n      break;\n    }\n\n    case 'g': {\n      prefetch_size = atoi(optarg);\n      break;\n    }\n\n    case 'i': {\n      pattern_file = optarg;\n      break;\n    }\n\n    case 'h': {\n      std::cout << usage_sstr.str() << std::endl;\n      exit(1);\n    }\n\n    default: {\n      std::cerr << \"No such option\" << std::endl;\n      break;\n    }\n    }\n  }\n\n  // Decide if the operations are to be async or not\n  if (async_op == 1) {\n    do_async = true;\n  }\n\n  if (update_op == 1) {\n    do_update = true;\n  }\n\n  if (debug == 1) {\n    g_logging.SetLogPriority(LOG_DEBUG);\n  }\n\n  // Print the running configuration\n  if (debug) {\n    std::cout << \"-----------------------------------------------------------\"\n              << std::endl\n              << \"Default block size: \" << block_size << std::endl\n              << \"Default timeout: \" << timeout << std::endl\n              << \"Default prefetch size: \" << prefetch_size << std::endl\n              << \"Default async: \" << do_async << std::endl\n              << \"Default debug: \" << debug << std::endl\n              << \"-----------------------------------------------------------\"\n              << std::endl;\n  }\n\n  // Execute the required operation\n  if (op_type == RD_SEQU) {\n    if (!url_file.IsValid() || ext_file.empty()) {\n      eos_static_err(\"Set EOS file and output file name\");\n      return 1; // error\n    }\n\n    if (ReadSequentially(url_file, ext_file)) {\n      eos_static_debug(\"Operation successful\");\n      return 0;\n    } else {\n      eos_static_debug(\"Operation failed\");\n      return 1; // error\n    }\n  } else if (op_type == RD_PATT) {\n    if (!url_file.IsValid() || ext_file.empty() || pattern_file.empty()) {\n      eos_static_err(\"Set EOS file, pattern file and output file name\");\n      return 1; // error\n    }\n\n    if (ReadPattern(url_file, ext_file, pattern_file)) {\n      eos_static_debug(\"Operation successful\");\n      return 0;\n    } else {\n      eos_static_debug(\"Operation failed\");\n      return 1; // error\n    }\n  } else if (op_type == WR_SEQU) {\n    if (!url_file.IsValid() || ext_file.empty()) {\n      eos_static_err(\"Set EOS file and external file name\");\n      return 1; // error\n    }\n\n    if (WriteSequentially(url_file, ext_file)) {\n      eos_static_debug(\"Operation successful\");\n      return 0;\n    } else {\n      eos_static_debug(\"Operation failed\");\n      return 1; // error\n    }\n  } else if (op_type == WR_PATT) {\n    if (!url_file.IsValid() || ext_file.empty() || pattern_file.empty()) {\n      eos_static_err(\"Set EOS file, pattern file and output file name\");\n      return 1; // error\n    }\n\n    if (WritePattern(url_file, ext_file, pattern_file)) {\n      eos_static_debug(\"Operation successful\");\n      return 0;\n    } else {\n      eos_static_debug(\"Operation failed\");\n      return 1; // error\n    }\n  }\n}\n\n"
  },
  {
    "path": "test/eoscp-rain-test",
    "content": "#! /bin/bash\n\n#------------------------------------------------------------------------------\n# File: eoscp-rain-test\n# Author: Elvin-Alin Sindrilaru - CERN 2013\n#------------------------------------------------------------------------------\n\n#/************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2013 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************/\n\n#------------------------------------------------------------------------------\n# Description: Script used to test the RAIN-like layout using the eoscp command\n# Usage:\n# eoscp-rain-test raiddp/reeds root://host//eos_plain_dir/test_file\n# - first argument is the type of RAIN layout to be used\n# - second argument will be a directory in EOS which has a **PLAIN** layout with\n#   block level checksum enabled where the stripes of the RAIN representation\n#   will be saved. It also contains the initial test files and the reconstructed\n#   version of the initial file.\n#\n# The script does the following:\n#  - transforms the file into a RAIN layout\n#  - reconstructs the initial file from the stripes and checks using sum to see\n#    if the initial file and the reconstructed one match\n#  - disable one by one each of the stripes i.e. use an empty dummy.rain file\n#    and reconstruct the initial file without any recovery of the stripe file\n#  - the same as in the previous step but with the recovery of the stripe file.\n#    Note the \"-f\" flag passed to the eoscp command. This is also followed\n#    by a check of the original file and the reconstructed one but also of the\n#    original stripe and the reconstructed one i.e. the dummy.rain file\n#\n#------------------------------------------------------------------------------\n\n. /etc/init.d/functions\n\n# Create error message\nexport ERR_MSG1=\"The provided directory does not have the extended attributes set correctly.\"\nexport USAGE_MSG=\"Usage: $0 raiddp/reeds root://host//eos_plain_dir/test_file \"\n\n# Set the path to eoscp, eos and others\nEOSCP=\"$(which eoscp) -s\"\nEOS=$(which eos)\n# Check if we are already root\nid | grep -q \"uid=0\"\n\nif [[ $? -ne 0 ]]; then\n  SUDO=\"sudo\"\nelse\n  SUDO=\"\"\nfi\n\n\n#-------------------------------------------------------------------------------\n# Check if two files are identical by computing the sum\n#\n# @param first file\n# @param second file\n#\n#-------------------------------------------------------------------------------\nfunction check_identical()\n{\n  local XRD_PATH1=$1\n  local XRD_PATH2=$2\n\n  # Extract physical file name from EOS using the file info function\n  local INDX=`awk -v a=\"$XRD_PATH1\" -v b=\"//eos\" 'BEGIN{print index(a,b)}'`\n  local EOS_FILE1=\"${XRD_PATH1:$INDX}\"\n\n  local INDX=`awk -v a=\"$XRD_PATH2\" -v b=\"//eos\" 'BEGIN{print index(a,b)}'`\n  local EOS_FILE2=\"${XRD_PATH1:$INDX}\"\n\n  local PFN1=`$SUDO $EOS file info $EOS_FILE1 --fullpath | grep \"online\" | awk '{print $NF};'`\n  local PFN2=`$SUDO $EOS file info $EOS_FILE2 --fullpath | grep \"online\" | awk '{print $NF};'`\n\n  if [[ -f $PFN1 && -f $PFN2 ]]; then\n    local XS1=$(sum $PFN1)\n    local XS2=$(sum $PFN2)\n\n    test \"$XS1\" = \"$XS2\"\n    if [[ $? -ne 0 ]]; then\n      echo_failure\n      exit 1\n    fi\n  else\n    echo \"WARNING: Skipping physical file comparison - no files on local disk\"\n  fi\n}\n\n\n#-------------------------------------------------------------------------------\n# Main part\n#-------------------------------------------------------------------------------\n\nif [[ $# -ne 2 ]];\nthen\n    echo $USAGE_MSG\n    exit 1\nfi\n\n#...............................................................................\n# Check configuration\n#...............................................................................\n# The layout type can be either raiddp or raid6 or archive\nLAYOUT=$1\n\nif [[ \"$LAYOUT\" != \"raiddp\" && \"$LAYOUT\" != \"reeds\" ]]; then\n    echo \"No such layout for RAIN\"\n    echo $USAGE_MSG\n    exit 1\nfi\n\n# Source file to be converted to a RAIN layout\nEOS_SRC=$2\n\n# Extract the EOS destination directory name\nEOS_DST_URL=\"`awk -F '/[^/]*$' '{print $1}' <<< $EOS_SRC`\"\nEOS_DST_DIR=\"`awk -v input=\"$EOS_DST_URL\" -v tok=\"//eos\" 'BEGIN {indx=index(input,tok); print substr(input,indx+1)}'`\"\necho \"Destination url dir is: $EOS_DST_URL\"\necho \"Destination dir is: $EOS_DST_DIR\"\n\n# Check that the destination directory has a PLAIN layout type\necho \"Check that the EOS directory has the proper attributes ...\"\n$SUDO $EOS -b attr ls \"$EOS_DST_DIR\" | grep -q 'sys.forced.layout=\"plain\"'\nif [ $? -ne 0 ]; then\n    echo \"$ERR_MSG1\"\n    echo_failure\n    exit 1\nfi\n\n# Check that the destination directory has blockchecksum enabled\n$SUDO $EOS -b attr ls \"$EOS_DST_DIR\" | grep -q 'sys.forced.blockchecksum=\"crc32c\"'\nif [ $? -ne 0 ]; then\n    echo \"$ERR_MSG1\"\n    echo_failure\n    exit 1\nfi\n\n#...............................................................................\n# Initial RAIN conversion\n#...............................................................................\necho \"Convert the file to a RAIN layout ...\"\n$EOSCP -e $LAYOUT -S 1 -D 6 -P 2 $EOS_SRC \\\n    $EOS_DST_URL/stripe1.rain \\\n    $EOS_DST_URL/stripe2.rain \\\n    $EOS_DST_URL/stripe3.rain \\\n    $EOS_DST_URL/stripe4.rain \\\n    $EOS_DST_URL/stripe5.rain \\\n    $EOS_DST_URL/stripe6.rain\n\n\n#...............................................................................\n# Rebuild initial file\n#...............................................................................\necho \"Rebuild the file from the new stripes ...\"\n$EOSCP -e $LAYOUT -S 6 -D 1 -P 2  \\\n    $EOS_DST_URL/stripe1.rain \\\n    $EOS_DST_URL/stripe2.rain \\\n    $EOS_DST_URL/stripe3.rain \\\n    $EOS_DST_URL/stripe4.rain \\\n    $EOS_DST_URL/stripe5.rain \\\n    $EOS_DST_URL/stripe6.rain \\\n    $EOS_DST_URL/result.rain\n\necho \"Check if files are identical\"\ncheck_identical $EOS_SRC $EOS_DST_URL/result.rain\n\n\n#...............................................................................\n# Test recovery of RAIN layouts\n#...............................................................................\necho \"Delete the file stripes one by one and reconstruct the file\"\nALL_STRIPES=( 1 2 3 4 5 6 )\nVECT_STRIPES=( 1 2 3 4 5 6 )\n\nfor i in \"${VECT_STRIPES[@]}\"; do\n    FILE_NAMES=()\n\n    for j in \"${ALL_STRIPES[@]}\"; do\n       if [ \"$i\" == \"$j\" ]; then\n          FILE_NAMES+=(\"$EOS_DST_URL/dummy.rain\")\n       else\n          FILE_NAMES+=(\"$EOS_DST_URL/stripe$j.rain\")\n       fi\n    done\n\n    $SUDO $EOS -b rm \"$EOS_DST_DIR/result.rain\"\n\n    echo \"Do on-the-fly recovery ...\"\n    $EOSCP -e $LAYOUT -S 6 -D 1 -P 2  \\\n    ${FILE_NAMES[0]} \\\n    ${FILE_NAMES[1]} \\\n    ${FILE_NAMES[2]} \\\n    ${FILE_NAMES[3]} \\\n    ${FILE_NAMES[4]} \\\n    ${FILE_NAMES[5]} \\\n    $EOS_DST_URL/result.rain\n\n    echo \"Check if result files are identical\"\n    check_identical $EOS_SRC $EOS_DST_URL/result.rain\n\n    echo \"Recover also the missing stripe...\"\n    $EOSCP -f -e $LAYOUT -S 6 -D 1 -P 2 -c \\\n    ${FILE_NAMES[0]} \\\n    ${FILE_NAMES[1]} \\\n    ${FILE_NAMES[2]} \\\n    ${FILE_NAMES[3]} \\\n    ${FILE_NAMES[4]} \\\n    ${FILE_NAMES[5]} \\\n    $EOS_DST_URL/result.rain\n\n    echo \"Check if recovered stripes are identical\"\n    check_identical $EOS_DST_URL/stripe$i.rain $EOS_DST_URL/dummy.rain\n\n    $SUDO $EOS -b rm \"$EOS_DST_DIR/dummy.rain\"\n    unset \"FILE_NAMES\"\n\ndone\n\n#...............................................................................\n# Clean up\n#...............................................................................\n$SUDO $EOS -b rm \"$EOS_DST_DIR/stripe1.rain\"\n$SUDO $EOS -b rm \"$EOS_DST_DIR/stripe2.rain\"\n$SUDO $EOS -b rm \"$EOS_DST_DIR/stripe3.rain\"\n$SUDO $EOS -b rm \"$EOS_DST_DIR/stripe4.rain\"\n$SUDO $EOS -b rm \"$EOS_DST_DIR/stripe5.rain\"\n$SUDO $EOS -b rm \"$EOS_DST_DIR/stripe6.rain\"\n$SUDO $EOS -b rm \"$EOS_DST_DIR/result.rain\"\n\necho_success\nexit 0\n"
  },
  {
    "path": "test/fuse/eos-fuse-test",
    "content": "#!/bin/bash\n\n# ----------------------------------------------------------------------\n# File: eos-fuse-test\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n################################################################################################################\n# Usage:                                                                                                       #\n# - run full test suite:   bash> eos-fuse-test                                                                 #\n# - list test id's:        bash> eos-fuse-test list                                                            #\n# - limit tests:           bash> eos-fuse-test 243 258               # run 243-258 (prepare+cleanup always run)#\n# - limit tests:           bash> eos-fuse-test xrootd,tar            # run only xrootd & tar test              #\n################################################################################################################\n\n# default values can be overwritten in /etc/sysconfig/eos\n#export EOS_TEST_MAILNOTIFY=apeters@mail.cern.ch\n#export EOS_TEST_GSMNOTIFY=\"0041764875002@mail2sms.cern.ch\"\n\nexport EOS_TEST_REDIRECTOR=localhost\nexport EOS_TEST_FULL_REDIRECTOR=`hostname -f`\nexport EOS_TEST_FUSESYS=/tmp/eos-fuse-test\nexport EOS_TEST_GSMLOCKTIME=3600\nexport EOS_TEST_TESTTIMESLICE=600\n\nfirsttest=$1\nlasttest=$2\n\n################################################################################################################\n# check if on SLC5\n################################################################################################################\nuname -a | grep el5 >& /dev/null\n\nif [ $? -eq 0 ]; \nthen\n  EL5=1\nelse\n  EL5=0\nfi\n\nif [ -e /etc/sysconfig/eos ];  then\n. /etc/sysconfig/eos\nfi\n\nif [ -z \"$EOS_TEST_INSTANCE\" ]; then\n\tif [ -n \"$EOS_INSTANCE_NAME\" ]; then\n  \t  export EOS_TEST_INSTANCE=${EOS_INSTANCE_NAME#eos}\n\telse \n          export EOS_TEST_INSTANCE=\"dev\"\n        fi\nfi\n################################################################################################################\nmkdir -p $EOS_TEST_FUSESYS\n################################################################################################################\n# don't touch\nexport failed=0\nexport success=0\nexport EOSLASTLOG=$EOS_TEST_FUSESYS/test-last.log\nexport EOSALLCERTLOG=$EOS_TEST_FUSESYS/test-result.log\nexport EOSCERTLOG=\"$EOS_TEST_FUSESYS/test-output.log\"\nexport GSMLOCKFILE=$EOS_TEST_FUSESYS/eos-gsm.lock\nexport EOSTESTPID=$EOS_TEST_FUSESYS/eos-pid\nexport FAILFILE=$EOS_TEST_FUSESYS/eos-failed\nexport TIMEOUTFILE=$EOS_TEST_FUSESYS/eos-timeout\n\nexport FUSESYSFILE0K=$EOS_TEST_FUSESYS/file.0K\nexport FUSESYSFILE1K=$EOS_TEST_FUSESYS/file.1K\nexport FUSESYSFILE1M=$EOS_TEST_FUSESYS/file.1M\nexport FUSESYSFILE50M=$EOS_TEST_FUSESYS/file.50M\n\n################################################################################################################\n\nif [ \"x$1\" != \"xlist\" ]; then \n\nif [ ! -e $FUSESYSFILE0K ]; then\necho \"####################################\"\necho \"### Creating Test Pattern File 0K\"\necho \"####################################\"\nrm -rf $FUSESYSFILE0K >& /dev/null\ntouch $FUSESYSFILE0K\nfi\n\nif [ ! -e $FUSESYSFILE1K ]; then\necho \"####################################\"\necho \"### Creating Test Pattern File 1K\"\necho \"####################################\"\nyes | dd of=$FUSESYSFILE1K bs=1k count=1\nfi\n\nCKS1K=`eos-adler32 $FUSESYSFILE1K | awk '{print $4'} | sed s/adler32=//`\necho \"adler32 (1k) = $CKS1K\"\n\nif [ ! -e $FUSESYSFILE1M ]; then\necho \"####################################\"\necho \"### Creating Test Pattern File 1M\"\necho \"####################################\"\nyes | dd of=$FUSESYSFILE1M bs=1k count=1000\nfi \n\nCKS1M=`eos-adler32 $FUSESYSFILE1M | awk '{print $4'} | sed s/adler32=//`\necho \"adler32 (1M) = $CKS1M\"\n\nif [ ! -e $FUSESYSFILE50M ]; then\necho \"####################################\"\necho \"### Creating Test Pattern File 50M\"\necho \"####################################\"\nyes | dd of=$FUSESYSFILE50M bs=1k count=50000\nfi\n\nCKS50M=`eos-adler32 $FUSESYSFILE50M | awk '{print $4'} | sed s/adler32=//`\necho \"adler32 (50M) = $CKS50M\"\n\nfi\n\n. /etc/rc.d/init.d/functions\necho \"\" > $EOSALLCERTLOG\nrm -f $FAILFILE 2>/dev/null\nrm -f $TIMEOUTFILE 2>/dev/null\nrm -f $EOSCERTLOG 2>/dev/null\nkill_child_processes() {\n    pid=$(bash -c 'echo $PPID');\n    if [ $1 -gt 0 ]; then\n\tpids=`pstree -p $1 | sed 's/(/\\n(/g' | grep '(' | sed 's/(\\(.*\\)).*/\\1/' | tr \"\\n\" \" \"`;\n\tfor name in $pids; do \n\t    if [ $name -ne $pid ]; then\n\t\tkill -9 $name >& /dev/null\n\t\tsleep 0.1\n\t    fi\n\tdone\n    fi\n}\n\nkill_processes() {\n    echo\n    echo \t\n    echo \"CONTORL-C received ... aborting ...\"\t\n    echo\n    pid=`cat $EOSTESTPID 2>/dev/null`;\t\n    if [ -z \"$pid\" ]; then\n\t    pid=$(bash -c 'echo $PPID');\n    fi\n    kill_child_processes $pid;\n    exit -1;\n}\n\n################################################################################################################\nmailnotify () {\n    OK=\"OK\" && test -e $FAILFILE && OK=\"FAILED\"\n\n    if [ $OK = \"FAILED\" ]; then\n\tif [ -n \"$EOS_TEST_MAILNOTIFY\" ]; then  mutt -s \"$EOS_TEST_INSTANCE $OK\" $EOS_TEST_MAILNOTIFY < $EOSALLCERTLOG ; fi\n\tif [ ! -e $GSMLOCKFILE ]; then \n\t    if [ -n \"$EOS_TEST_GSMNOTIFY\" ]; then echo \"$EOS_TEST_INSTANCE failed at `date`\" | mail -s \"$EOS_TEST_INSTANCE $OK\" $EOS_TEST_GSMNOTIFY ; touch $GSMLOCKFILE; ( sleep EOS_TEST_GSMLOCKTIME; rm -rf $GSMLOCKFILE; ) >& /dev/null & \n\t    fi; \n\tfi\n\texit -1\n    fi\t\n    exit 0\n}\n################################################################################################################\n(\nexport XROOTSYS=/usr/\nexport LD_LIBRARY_PATH=$XROOTSYS/lib64/:$LD_LIBRARY_PATH; export PATH=$XROOTSYS/bin:$PATH\npid=$(bash -c 'echo $PPID');\necho -n $pid >& $EOSTESTPID\n################################################################################################################\n# Library\n################################################################################################################\nshowtoken () {\n    echo \"############### KRB 5 ##############\"\n    /usr/kerberos/bin/klist -5\n    echo \"############### X509  ##############\"\n    xrdgsiproxy info\n    echo \"####################################\"\n}\n################################################################################################################\nlogoutput () {\n    echo \"----------------- Error Output --------------------\";cat $EOSCERTLOG ;echo \"---------------------------------------------------\"\n}\n################################################################################################################\nupload () {\n    src=$1\n    dst=$2\n    shift\n    shift\n    echo xrdcp -np -v -f $* $src root://$EOS_TEST_REDIRECTOR/$dst >& $EOSLASTLOG ;eval $XROOTSYS/bin/xrdcp -f $* $src root://$EOS_TEST_REDIRECTOR/$dst >> $EOSCERTLOG 2>&1\n}\n################################################################################################################\ndownload () {\n    src=$1\n    dst=$2\n    shift\n    shift\n    echo xrdcp -np -v -f $* root://$EOS_TEST_REDIRECTOR/$dst $src  >& $EOSLASTLOG ;eval $XROOTSYS/bin/xrdcp -f $* root://$EOS_TEST_REDIRECTOR/$dst $src >> $EOSCERTLOG 2>&1\n}\n################################################################################################################\ndownloadh () {\n    src=$1\n    dst=$2\n    shift\n    shift\n    echo xrdcp -np -v -f $* root://$EOS_TEST_FULL_REDIRECTOR/$dst $src  >& $EOSLASTLOG ;eval $XROOTSYS/bin/xrdcp -f $* root://$EOS_TEST_FULL_REDIRECTOR/$dst $src >> $EOSCERTLOG 2>&1\n}\n################################################################################################################\ntimeout() {\n    echo \"## TIMEOUT ##\";\n    exit 0;\n}\n\nmeta() {\n    echo xrdfs $EOS_TEST_REDIRECTOR $1 $2\\?$3 >& $EOSLASTLOG ; $XROOTSYS/bin/xrdfs $EOS_TEST_REDIRECTOR $1 $2\\?$3 >> $EOSCERTLOG 2>&1\n}\n\neos() {\n    echo eos -b $EOSROLE root://$EOS_TEST_REDIRECTOR $* >& $EOSLASTLOG; eval $XROOTSYS/bin/eos -b $EOSROLE root://$EOS_TEST_REDIRECTOR $* >> $EOSCERTLOG 2>&1\n}\n\ntpc1() {\n    src=$1\n    dst=$2\n    echo xrdcp --tpc only root://$EOS_TEST_REDIRECTOR/$src root://$EOS_TEST_REDIRECTOR/$dst >& $EOSLASTLOG; eval $XROOTSYS/bin/xrdcp -f --tpc only root://$EOS_TEST_REDIRECTOR/$src root://$EOS_TEST_REDIRECTOR/$dst >> $EOSCERTLOG 2>&1\n}\n\ntpc2() {\n    src=$1\n    dst=$2\n    echo xrdcopy --tpc only root://$EOS_TEST_REDIRECTOR/$src root://$EOS_TEST_REDIRECTOR/$dst >& $EOSLASTLOG; eval $XROOTSYS/bin/xrdcopy --tpc only root://$EOS_TEST_REDIRECTOR/$src root://$EOS_TEST_REDIRECTOR/$dst >> $EOSCERTLOG 2>&1\n}\n\ntgrep() {\n\ta=$1\n\tshift\n\techo \"eval $* | grep $a\" >& $EOSLASTLOG; eval $* | grep $a >> $EOSCERTLOG 2>&1\n}\n\nstress() {\n    echo xrdstress $* >& $EOSLASTLOG; eval xrdstress $* >> $EOSCERTLOG 2>&1\n}\n\nappend() {\n    echo xrdcpappend root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcpappend root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\nabort() {\n    echo xrdcpabort root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcpabort root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\nextend() {\n    echo xrdcpextend root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcpextend root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\nshrink() {\n    echo xrdcpshrink root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcpshrink root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\nrandom() {\n    echo xrdcprandom root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcprandom root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\ntruncate() {\n    echo xrdcptruncate root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcptruncate root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\nholes() {\n    echo xrdcpholesroot://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcpholes root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\nbackward() {\n    echo xrdcpbackward root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcpbackward root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\npartial() {\n    echo xrdcppartial root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcppartial root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\ndownloadrandom() {\n    echo xrdcpdownloadrandom root://$EOS_TEST_REDIRECTOR/$1 >& $EOSLASTLOG; eval xrdcpdownloadrandom root://$EOS_TEST_REDIRECTOR/$1 >> $EOSCERTLOG 2>&1\n}\n\neos_rain() {\n    echo eos-rain-test $1 $2 root://$EOS_TEST_REDIRECTOR/$3 >& $EOSLASTLOG; eval eos-rain-test $1 $2 root://$EOS_TEST_REDIRECTOR/$3 >> $EOSCERTLOG 2>&1\n}\n\neoscp_rain() {\n    echo eoscp-rain-test $1 root://$EOS_TEST_REDIRECTOR/$2 >& $EOSLASTLOG; eval eoscp-rain-test $1 root://$EOS_TEST_REDIRECTOR/$2 >> $EOSCERTLOG 2>&1\n}\n\neos_io() {\n   echo eos-io-test root://$EOS_TEST_REDIRECTOR/$1 $2 >& $EOSLASTLOG; eval eos-io-test root://$EOS_TEST_REDIRECTOR/$1 $2 >> $EOSCERTLOG 2>&1\n}\n\nshell() {\n    echo $* >& $EOSLASTLOG; eval $* >> $EOSCERTLOG 2>&1\n}\n\n################################################################################################################\ntestout () {\n    echo Failed: $failed Success: $success\n    if [ $failed -gt 0 ]; then touch $FAILFILE; fi\n}\n################################################################################################################\ntestcnt=0;\nruntest () {\n    testcnt=$[$testcnt+1];\n\n    if [ -e $TIMEOUTFILE ]; then\n\techo -n `date +\"%x %X\"` timeout\n\tfailure\n    else \n\techo \"-------------------------------------------------------------\" >> $EOSCERTLOG 2>&1\n\techo \"# Test $testcnt: $*\" >> $EOSCERTLOG 2>&1\t\t\n\techo \"-------------------------------------------------------------\" >> $EOSCERTLOG 2>&1\n\n\tif [ \"$firsttest\" = \"list\" ]; then\n\t  printf \"# [ %-7s ]\" $categorie ;\n\t  printf \" ID %02d\" $testcnt;\n   \t  echo \" $*\"\n\t  return\n        fi\n\n\tSTART=$(date +%s.%N)\n\techo $testcnt | awk '{printf(\"%04d \",$1);}'\n\techo -n `date +\"%x %X\"` \"$1\"\n\tauth=$2\n\tres=$3\n\trenv=$4\n\tshift \n\techo -n $*\n\tshift\n\tshift\n\tshift\n        skip=0\n        if [ $categorie != \"prep\" ] && [ $categorie != \"clean\" ]; then\n          # --- if $firsttest is set and $lasttest not we assume it is a categorie list\n          if [ \"x$firsttest\" != \"x\" ] && [ \"x$lasttest\" == \"x\" ]; then\n\t    if [ \"x$firsttest\" = \"x${firsttest/$categorie}\" ] ; then\n\t      echo\n              warning \"skipped\" ; echo -n \" ... skipped\"\n     \t      echo\n\t      skip=1\n            fi\n          else\n\n  \t  # --- skip if start-range was given\n \t  if [ \"x$firsttest\" != \"x\" ] ; then\n\t    if [ $testcnt -lt $firsttest ]; then\n\t      echo\n              warning \"skipped\" ; echo -n \" ... skipped\"\n     \t      echo\n\t      skip=1\n\t    fi\n\t  fi  \n\t  # --- skip if end-range was given\n\t  if [ \"x$lasttest\" != \"x\" ] ; then\n\t    if [ $testcnt -gt $lasttest ]; then\n\t      echo\n              warning \"skipped\" ; echo -n \" ... skipped\"\n     \t      echo\n\t      skip=1\n\t    fi\n\t  fi  \n          fi\n\tfi\n\n    \tif [ $skip = 0 ] ; then\n\t  if [ $auth = \"krb5\" ] ; then ( eval export X509_USER_CERT=/tmp/illegal X509_USER_KEY=/tmp/illegal $renv; $* ) ; ret=$?; fi\n\t  if [ $auth = \"gsi\"  ] ; then ( eval export KRB5CCNAME=/tmp/illegal                                $renv; $* ) ; ret=$?; fi\n\t  if [ $auth = \"unix\" ] ; then ( eval export XrdSecPROTOCOL=unix                                    $renv; $* ) ; ret=$?; fi\n\t  if [ $auth = \"sss\"  ] ; then ( eval export XrdSecPROTOCOL=sss                                     $renv; $* ) ; ret=$?; fi\n\t  echo\n\t  if [ $res -eq 0 ] ; then if [ $ret -eq 0 ]; then success; ((success++));else failure ; ((failed++)); echo ; fi; fi\n\t  if [ $res -eq 1 ] ; then if [ $ret -ne 0 ]; then success; ((success++));else failure ; ((failed++)); echo ; fi; fi\n\t  if [ $res -eq 2 ] ; then success; ((success++)); fi\n\t  END=$(date +%s.%N)\n\t  DIFF=$(echo \"$END - $START\" | bc)\n\t  echo \"                         in $DIFF seconds\"\n        fi\n\techo \"--------------------------------------------------------------------------------------------------------------------\"\n    fi\n}\n################################################################################################################\n\necho \"===================================================\"\necho \"### Testing $EOS_TEST_REDIRECTOR\"\necho \"===================================================\"\n\n################################################################################################################\n# define your tests here\n# runtest <info> <auth> <exports> <expect> <mode> <srcpath> <dstpath> <param>\n#      <info>                  : describes what you are testing\n#      <auth>                  : 'krb5','gsi','unix','sss' - choose krb5 or X509 authentication [ you have to create both tokens beforehand ]\n#      <env>                   : can be used to set the execution environment env <env> \n#      <expect>                : 0 or 1 => 0 - you expect the command works , 1 - you expect that the command failes, 2 - you run the command but it can fail or not, that is expected\n#      <mode>                  : upload|download|downloadh|meta|eos|stress|abort|extend|shrink|random|truncate|holes|backward|partial|downloadrandom|tpc1|tpc2|shell|eoscp_rain|eos_rain|eos_io : \n#                                self explaining - meta executes command via xrd shell, eos via eos shell, stress runs xrdstress, abort runs xrdcpabort ...\n#      <srcpath,dstpath>       : is a local '/...' or remote '/eos/...' path\n#      <param>                 : opaque information passed ... '&' has to be escaped !!!!\n################################################################################################################\n\n################################################################################################################\n#xrdgsiproxy init >>& $EOSCERTLOG\nshowtoken >> $EOSCERTLOG 2>&1\n\n\n################################################################################################################\n# Preparation\n################################################################################################################\n# --------------------------------------------------------------------------------------------------------------\ncategorie=\"prep\"\n# --------------------------------------------------------------------------------------------------------------\nruntest \"### Stat      \" unix 0 \"\" meta stat \"/eos/$EOS_TEST_INSTANCE/\"\nruntest \"### Cleanup   \" unix 2 \"\" eos rm \"-r\" \"/eos/$EOS_TEST_INSTANCE/fusetest/workspace\"\nruntest \"### Cleanup   \" unix 2 \"\" eos attr rm sys.forced.blockchecksum /eos/$EOS_TEST_INSTANCE/fusetest/\nruntest \"### Cleanup   \" unix 2 \"\" eos attr rm sys.forced.blocksize /eos/$EOS_TEST_INSTANCE/fusetest/\nruntest \"### Cleanup   \" unix 2 \"\" eos attr rm sys.forced.checksum /eos/$EOS_TEST_INSTANCE/fusetest/\nruntest \"### Cleanup   \" unix 2 \"\" eos attr rm sys.forced.layout /eos/$EOS_TEST_INSTANCE/fusetest/\nruntest \"### Cleanup   \" unix 2 \"\" eos attr rm sys.forced.nstripes /eos/$EOS_TEST_INSTANCE/fusetest/\nruntest \"### Cleanup   \" unix 2 \"\" eos attr rm sys.forced.space /eos/$EOS_TEST_INSTANCE/fusetest/\nruntest \"### Cleanup   \" unix 2 \"\" eos attr rm sys.versioning /eos/$EOS_TEST_INSTANCE/fusetest/\nruntest \"### Layout    \" unix 2 \"\" eos attr set default=replica /eos/$EOS_TEST_INSTANCE/fusetest/\nruntest \"### Quota     \" unix 0 \"\" eos quota set -u 99 -p /eos/$EOS_TEST_INSTANCE/fusetest -v 1T -i 1M\nruntest \"### Quota     \" unix 0 \"\" eos quota set -u 100 -p /eos/$EOS_TEST_INSTANCE/fusetest -v 1T -i 1M\nruntest \"### Quota     \" unix 0 \"\" eos quota set -u 2 -p /eos/$EOS_TEST_INSTANCE/fusetest -v 1T -i 1M\nruntest \"### Quota     \" unix 0 \"\" eos quota set -u 3 -p /eos/$EOS_TEST_INSTANCE/fusetest -v 1T -i 1M\nruntest \"### Mkdir     \" unix 0 \"\" eos mkdir -p \"/eos/$EOS_TEST_INSTANCE/fusetest/workspace/\"\nruntest \"### Chmod     \" unix 0 \"\" eos chmod \"777\" \"/eos/$EOS_TEST_INSTANCE/fusetest/workspace/\"\nruntest \"### Motd      \" unix 0 \"\" eos motd \n\n\n################################################################################################################\n# truncate\n################################################################################################################\n# --------------------------------------------------------------------------------------------------------------\ncategorie=\"truncate\"\n# --------------------------------------------------------------------------------------------------------------\nruntest \"### Cp        \" unix 0 \"\" shell \"cp /etc/passwd /eos/$EOS_TEST_INSTANCE/fusetest/workspace/passwd\"\nruntest \"### Truncate  \" unix 0 \"\" shell \"/usr/bin/truncate -s 0 /eos/$EOS_TEST_INSTANCE/fusetest/workspace/passwd\"\nruntest \"### Stat      \" unix 0 \"\" shell \"stat /eos/$EOS_TEST_INSTANCE/fusetest/workspace/passwd | grep Size\"\nruntest \"### Stat      \" unix 0 \"\" shell \"stat /eos/$EOS_TEST_INSTANCE/fusetest/workspace/passwd | grep Size | awk '{print \\$2}' | grep -w 0\"\nruntest \"### Rm        \" unix 0 \"\" shell \"rm -f /eos/$EOS_TEST_INSTANCE/fusetest/workspace/passwd\"\n\n################################################################################################################\n# untar\n################################################################################################################\n# --------------------------------------------------------------------------------------------------------------\ncategorie=\"untar\"\n# --------------------------------------------------------------------------------------------------------------\nruntest \"### Mkdir     \" unix 0 \"\" eos mkdir \"/eos/$EOS_TEST_INSTANCE/fusetest/workspace/untar\"\nruntest \"### Untar300  \" unix 0 \"\" shell \"tar -xvzf /var/eos/test/fuse/untar/untar.tgz -C /eos/$EOS_TEST_INSTANCE/fusetest/workspace/untar\"\nruntest \"### Find-f    \" unix 0 \"\" tgrep \"279\" \"find /eos/$EOS_TEST_INSTANCE/fusetest/workspace/untar/compile-autoconf/ -type f | wc -l\"\nruntest \"### Find-d    \" unix 0 \"\" tgrep \"21\" \"find /eos/$EOS_TEST_INSTANCE/fusetest/workspace/untar/compile-autoconf/ -type d | wc -l\"\nruntest \"### Rm-rf     \" unix 0 \"\" shell \"rm -rf /eos/$EOS_TEST_INSTANCE/fusetest/workspace/untar\"\nruntest \"### Stat      \" unix 1 \"\" shell \"stat /eos/$EOS_TEST_INSTANCE?fusetest/workspace/untar\"\n\n# --------------------------------------------------------------------------------------------------------------\ncategorie=\"rsync\"\n# -------------------------------------------------------------------------------------------------------------\nruntest \"### Mkdir     \" unix 0 \"\" shell \"mkdir /eos/$EOS_TEST_INSTANCE/fusetest/workspace/rsync/\"\nruntest \"### Mkdir     \" unix 0 \"\" shell \"mkdir /eos/$EOS_TEST_INSTANCE/fusetest/workspace/rsync-a/\"\nruntest \"### Mkdir     \" unix 0 \"\" shell \"mkdir /eos/$EOS_TEST_INSTANCE/fusetest/workspace/rsync-delay/\"\nruntest \"### Untar300  \" unix 0 \"\" shell \"tar -xvzf /var/eos/test/fuse/untar/untar.tgz -C /eos/$EOS_TEST_INSTANCE/fusetest/workspace/rsync/\"\nruntest \"### rsync-a   \" unix 0 \"\" shell \"rsync -av /eos/$EOS_TEST_INSTANCE/fusetest/workspace/rsync/ /eos/$EOS_TEST_INSTANCE/fusetest/workspace/rsync-a/\"\nruntest \"### rsync-dly \" unix 0 \"\" shell \"rsync -av /eos/$EOS_TEST_INSTANCE/fusetest/workspace/rsync/ /eos/$EOS_TEST_INSTANCE/fusetest/workspace/rsync-delay//\"\nruntest \"### Find-f    \" unix 0 \"\" tgrep \"279\" \"find /eos/$EOS_TEST_INSTANCE/fusetest/workspace/rsync-a/compile-autoconf -type f | wc -l\"\nruntest \"### Find-d    \" unix 0 \"\" tgrep \"21\" \"find /eos/$EOS_TEST_INSTANCE/fusetest/workspace/rsync-a/compile-autoconf -type d | wc -l\"\nruntest \"### Find-f    \" unix 0 \"\" tgrep \"279\" \"find /eos/$EOS_TEST_INSTANCE/fusetest/workspace/rsync-delay/compile-autoconf -type f | wc -l\"\nruntest \"### Find-d    \" unix 0 \"\" tgrep \"21\" \"find /eos/$EOS_TEST_INSTANCE/fusetest/workspace/rsync-delay/compile-autoconf -type d | wc -l\"\n#runtest \"### Rm-rf     \" unix 0 \"\" shell \"rm -rf /eos/$EOS_TEST_INSTANCE/fusetest/workspace/rsync/\"\n#runtest \"### Rm-rf     \" unix 0 \"\" shell \"rm -rf /eos/$EOS_TEST_INSTANCE/fusetest/workspace/rsync-a/\"\n#runtest \"### Rm-rf     \" unix 0 \"\" shell \"rm -rf /eos/$EOS_TEST_INSTANCE/fusetest/workspace/rsync-delay/\"\nruntest \"### Stat      \" unix 1 \"\" shell \"stat /eos/$EOS_TEST_INSTANCE?fusetest/workspace/rsync/\"\nruntest \"### Stat      \" unix 1 \"\" shell \"stat /eos/$EOS_TEST_INSTANCE?fusetest/workspace/rsync-a/\"\nruntest \"### Stat      \" unix 1 \"\" shell \"stat /eos/$EOS_TEST_INSTANCE?fusetest/workspace/rsync-delay/\"\n\n\n# --------------------------------------------------------------------------------------------------------------\ncategorie=\"compile-autoconf\"\n# --------------------------------------------------------------------------------------------------------------\n#runtest \"### untar300  \" unix 0 \"\" shell \"tar -xvzf /var/eos/test/fuse/untar/untar.tgz -C /eos/$EOS_TEST_INSTANCE/fusetest/workspace/\"\n#runtest \"### configure \" unix 0 \"\" shell \"cd /eos/$EOS_TEST_INSTANCE/fusetest/workspace/compile-autoconf/; ./configure\"\n#runtest \"### make-auto \" unix 0 \"\" shell \"cd /eos/$EOS_TEST_INSTANCE/fusetest/workspace/compile-autoconf/; make -j 4\"\n#runtest \"### make-clean\" unix 0 \"\" shell \"cd /eos/$EOS_TEST_INSTANCE/fusetest/workspace/compile-autoconf/; make clean\"\n#runtest \"### make-par  \" unix 0 \"\" shell \"cd /eos/$EOS_TEST_INSTANCE/fusetest/workspace/compile-autoconf/; make -j 16\"\n#runtest \"### Rm-rf     \" unix 0 \"\" shell \"rm -rf /eos/$EOS_TEST_INSTANCE/fusetest/workspace/compile-autoconf\"\n#runtest \"### Stat      \" unix 1 \"\" shell \"stat /eos/$EOS_TEST_INSTANCE?fusetest/workspace/compile-autoconf\"\n\n\n# --------------------------------------------------------------------------------------------------------------\ncategorie=\"compile-cmake\"\n# --------------------------------------------------------------------------------------------------------------\nruntest \"### untar1000 \" unix 0 \"\" shell \"tar -xvzf /var/eos/test/fuse/untar/xrootd.tgz -C /eos/$EOS_TEST_INSTANCE/fusetest/workspace/\"\nruntest \"### Mkdir     \" unix 0 \"\" shell \"mkdir /eos/$EOS_TEST_INSTANCE/fusetest/workspace/xrootd/build/\"\nruntest \"### cmake     \" unix 0 \"\" shell \"cd /eos/$EOS_TEST_INSTANCE/fusetest/workspace/xrootd/build; cmake ../\"\nruntest \"### make-cmake\" unix 0 \"\" shell \"cd /eos/$EOS_TEST_INSTANCE/fusetest/workspace/xrootd/build; make -j 4\"\nruntest \"### Rm-rf     \" unix 0 \"\" shell \"rm -rf /eos/$EOS_TEST_INSTANCE/fusetest/workspace/xrootd\"\nruntest \"### Stat      \" unix 1 \"\" shell \"stat /eos/$EOS_TEST_INSTANCE?fusetest/workspace/xrootd\"\n\n# --------------------------------------------------------------------------------------------------------------\ncategorie=\"fio\"\n# --------------------------------------------------------------------------------------------------------------\nruntest \"### Mkdir     \" unix 0 \"\" shell \"mkdir /eos/$EOS_TEST_INSTANCE/fusetest/workspace/fio/\"\nruntest \"### fio-1b-wr \" unix 0 \"\" shell \"fio --rw=write --name=64k-1b-wr  --size=64k --direct=1 --bs=1    --directory=/eos/$EOS_TEST_INSTANCE/fusetest/workspace/fio/\"\nruntest \"### fio-4k-wr \" unix 0 \"\" shell \"fio --rw=write --name=16m-4k-wr  --size=16M --direct=1 --bs=4k   --directory=/eos/$EOS_TEST_INSTANCE/fusetest/workspace/fio/\"\nruntest \"### fio-64k-wr\" unix 0 \"\" shell \"fio --rw=write --name=16m-64k-wr --size=16M --direct=1 --bs=64k  --directory=/eos/$EOS_TEST_INSTANCE/fusetest/workspace/fio/\"\nruntest \"### fio-1b-rw \" unix 0 \"\" shell \"fio --rw=readwrite --name=1k-1b-rw  --size=1k --direct=1 --bs=1    --directory=/eos/$EOS_TEST_INSTANCE/fusetest/workspace/fio/\"\nruntest \"### fio-4k-rw \" unix 0 \"\" shell \"fio --rw=readwrite --name=16m-4k-rw  --size=16M --direct=1 --bs=4k   --directory=/eos/$EOS_TEST_INSTANCE/fusetest/workspace/fio/\"\nruntest \"### fio-64k-rw\" unix 0 \"\" shell \"fio --rw=readwrite --name=16m-64k-rw --size=16M --direct=1 --bs=64k  --directory=/eos/$EOS_TEST_INSTANCE/fusetest/workspace/fio/\"\nruntest \"### fio-16M-rd2\" unix 0 \"\" shell \"fio --rw=read --name=16m-64k-2-rd --size=16M --direct=1 --bs=64k  --directory=/eos/$EOS_TEST_INSTANCE/fusetest/workspace/fio/ --numjobs=2 --group_reporting\"\nruntest \"### fio-1M-rd32\" unix 0 \"\" shell \"fio --rw=randwrite --name=1m-4k-32-rd --size=1M --direct=1 --bs=4k  --directory=/eos/$EOS_TEST_INSTANCE/fusetest/workspace/fio/ --numjobs=32 --group_reporting\"\nruntest \"### Rm-rf     \" unix 0 \"\" shell \"rm -rf /eos/$EOS_TEST_INSTANCE/fusetest/workspace/fio\"\nruntest \"### Stat      \" unix 1 \"\" shell \"stat /eos/$EOS_TEST_INSTANCE?fusetest/workspace/fio\"\n\n# --------------------------------------------------------------------------------------------------------------\ncategorie=\"xattr\"\n# --------------------------------------------------------------------------------------------------------------\nruntest \"### Mkdir     \" unix 0 \"\" shell \"mkdir -p /var/tmp/fuse-test-in/xattr\"\nruntest \"### Mkdir     \" unix 0 \"\" shell \"mkdir -p /var/tmp/fuse-test-out/\"\nruntest \"### Cp        \" unix 0 \"\" shell \"cp /etc/passwd /var/tmp/fuse-test-in/xattr/\"\nruntest \"### Set-Xattr \" unix 0 \"\" shell \"setfattr -n user.dirfoo -v bar /var/tmp/fuse-test-in/xattr\"\nruntest \"### Set-Xattr \" unix 0 \"\" shell \"setfattr -n user.filefoo -v bar /var/tmp/fuse-test-in/xattr/passwd\"\nruntest \"### Mkdir     \" unix 0 \"\" shell \"mkdir -p /eos/$EOS_TEST_INSTANCE/fusetest/workspace/xattr/\"\nruntest \"### Cp-a-fw   \" unix 0 \"\" shell \"cp -a /var/tmp/fuse-test-in/xattr /eos/$EOS_TEST_INSTANCE/fusetest/workspace/xattr/\"\nruntest \"### Cp-a-bw   \" unix 0 \"\" shell \"cp -a /eos/$EOS_TEST_INSTANCE/fusetest/workspace/xattr/xattr /var/tmp/fuse-test-out/\"\nruntest \"### Get-Xattr \" unix 0 \"\" shell \"getfattr -n user.dirfoo /eos/$EOS_TEST_INSTANCE/fusetest/workspace/xattr/xattr\" \nruntest \"### Get-Xattr \" unix 0 \"\" shell \"getfattr -n user.filefoo /eos/$EOS_TEST_INSTANCE/fusetest/workspace/xattr/xattr/passwd\"\nruntest \"### Get-Xattr \" unix 0 \"\" shell \"getfattr -n user.dirfoo /var/tmp/fuse-test-in/xattr\" \nruntest \"### Get-Xattr \" unix 0 \"\" shell \"getfattr -n user.filefoo /var/tmp/fuse-test-in/xattr/passwd\" \nruntest \"### Get-Xattr \" unix 0 \"\" shell \"getfattr -n user.dirfoo /var/tmp/fuse-test-out/xattr\" \nruntest \"### Get-Xattr \" unix 0 \"\" shell \"getfattr -n user.filefoo /var/tmp/fuse-test-out/xattr/passwd\" \nruntest \"### Rm-rf     \" unix 0 \"\" shell \"rm -rf /eos/$EOS_TEST_INSTANCE/fusetest/workspace/xattr/\"\nruntest \"### Rm-rf     \" unix 0 \"\" shell \"rm -rf /var/tmp/fuse-test/in/\"\nruntest \"### Rm-rf     \" unix 0 \"\" shell \"rm -rf /var/tmp/fuse-test/out/\"\n\n################################################################################################################\n# FINAL CLEANUP \n################################################################################################################\n# --------------------------------------------------------------------------------------------------------------\ncategorie=\"clean\"\n# --------------------------------------------------------------------------------------------------------------\n#runtest \"### Cleanup   \" unix 2 \"\" eos rm \"-r\" \"/eos/$EOS_TEST_INSTANCE/fusetest/workspace\"\n################################################################################################################\n# show test summary\ntestout\nunlink $EOSTESTPID\nexit\n################################################################################################################\n) | tee -a $EOSALLCERTLOG &\n\n\ntrap 'kill_processes' TERM\ntrap 'kill_processes' QUIT\ntrap 'kill_processes' INT\n\nsleep 1\ntestpid=`cat $EOSTESTPID 2>/dev/null`;\nfor wait in `seq 1 $EOS_TEST_TESTTIMESLICE`; do \n    kill -0 $testpid >& /dev/null\n    if [ $? -eq 0 ]; then\n\tsleep 1;\n    else \n\tbreak;\n    fi\ndone\n\nkill -0 $testpid >& /dev/null\n\nif [ $? -eq 0 ]; then\n touch $TIMEOUTFILE\n kill_child_processes $testpid;\nfi\n\n[ -e \"$TIMEOUTFILE\" ] && ( echo >> $EOSALLCERTLOG; echo \"error: timeout after $EOS_TEST_TESTTIMESLICE seconds\" >> $EOSALLCERTLOG; touch $FAILFILE; echo \"error: timeout after $EOS_TEST_TESTTIMESLICE seconds\";) \n\nunlink $TIMEOUTFILE >& /dev/null\n\n################################################################################################################\necho \"#--------------------------------------------------------------\"\necho \"# Log of the test results         : $EOSALLCERTLOG\"\necho \"# Log with individual test output : $EOSCERTLOG\"\necho \"#--------------------------------------------------------------\"\n################################################################################################################\nmailnotify\n################################################################################################################\n"
  },
  {
    "path": "test/fusex/eos-fusex-functional-test",
    "content": "#!/bin/bash\n\n# ----------------------------------------------------------------------\n# File: eos-fusex-functonal-test\n# Author: Manuel Reis\n# ----------------------------------------------------------------------\n\nusage() { echo '''Usage: eos-https-func-tests --samba <localdir> <fsname>\n                           [-h|--help] - usage & exit\n\n                           <localdir>   - Directory to be created and on which the mount will take place.\n                                          By default /eos/dockertest/test\n                           <fsname>     - Base fusex configuration name. By default eosdockertest for\n                                          /etc/eos/fuse.eosdockertest.conf\n'''; }\n\n# Parser from: https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash\nusage_error () { echo >&2 \"$(basename \"$0\"):  $1\"; exit 2; }\nassert_argument () { test \"$1\" != \"$EOL\" || usage_error \"$2 requires an argument\"; }\n\nsamba=0\n# One loop, nothing more.\nif [ \"$#\" != 0 ]; then\n  EOL=$(printf '\\1\\3\\3\\7')\n  set -- \"$@\" \"$EOL\"\n  while [ \"$1\" != \"$EOL\" ]; do\n    opt=\"$1\"; shift\n    case \"$opt\" in\n\n      # Your options go here.\n      --samba)    samba=1;;\n      -h|--help) usage; exit 0;;\n\n      # Arguments processing. You may remove any unneeded line after the 1st.\n      -|''|[!-]*) set -- \"$@\" \"$opt\";;                                          # positional argument, rotate to the end\n      --*=*)      set -- \"${opt%%=*}\" \"${opt#*=}\" \"$@\";;                        # convert '--name=arg' to '--name' 'arg'\n      -[!-]?*)    set -- \"$(echo \"${opt#-}\" | sed 's    /g')\" \"$@\";;     # convert '-abc' to '-a' '-b' '-c'\n      --)         while [ \"$1\" != \"$EOL\" ]; do set -- \"$@\" \"$1\"; shift; done;;  # process remaining arguments as positional\n      -*)         usage_error \"unknown option: '$opt'\";;                        # catch misspelled options\n      *)          usage_error \"this should NEVER happen ($opt)\";;               # sanity test for previous patterns\n\n    esac\n  done\n  shift  # $EOL\nfi\n\nFUSEDIR=${1:-\"/eos-samba\"}\nFSNAME=${2:-\"eosdockertest\"}\n\numountfuse () {\n    umount -f ${FUSEDIR} &> /dev/null || true\n}\ntrap umountfuse EXIT\n\n# umount whatever is mounted on this local\numountfuse\n\ntest-samba-config(){\n    umountfuse\n    # Samba relies on Unix (for users) SSS (for root, which maps to eossambabot/eosdev)\n\n    # merge eosxd json config with auth field used in samba\n    jq '.*{\"auth\":{\"krb5\":0, \"unix\":1, \"sss\":1, \"ssskeytab\":\"/etc/sss.samba.keytab\", \"oauth2\":0}}' /etc/eos/fuse.${FSNAME}.conf > /etc/eos/fuse.${FSNAME}samba.conf\n    grep eosdev /etc/eos.keytab > /etc/sss.samba.keytab\n    chmod 400 /etc/sss.samba.keytab\n\n    # prepare mountpoints\n    mkdir -m 777 -vp \"${FUSEDIR}\";\n    cat /etc/eos/fuse.${FSNAME}samba.conf\n    eosxd -ofsname=\"${FSNAME}\"samba \"${FUSEDIR}/\";\n\n    # Root should create a folder with the\n    mkdir -m 777 -vp \"${FUSEDIR}/dockertest/sambatests/\"\n    touch \"${FUSEDIR}/dockertest/sambatests/filefromroot\"\n    test \"$(stat -c \"%U:%G\" ${FUSEDIR}/dockertest/sambatests/filefromroot)\" == \"eosdev:eosdev\"\n    rc=$?\n    runuser eos-user -c \"touch ${FUSEDIR}/dockertest/sambatests/filefromeos-user\"\n    test \"$(stat -c \"%U:%G\"  ${FUSEDIR}/dockertest/sambatests/filefromeos-user)\" == \"eos-user:eos-user\"\n    rc=$(( $rc<<1  | $? ))\n    runuser eos-user -c \"ls -la ${FUSEDIR}/dockertest/sambatests/\"\n    return $rc\n}\n\nif [ $samba == 1 ]; then\n  test-samba-config\n  exit $?\nfi\n"
  },
  {
    "path": "test/fusex/eos-test-credential-bindings",
    "content": "#!/bin/bash\n\n# ----------------------------------------------------------------------\n# File: eos-test-credential-bindings\n# Author: Manuel Reis\n# ----------------------------------------------------------------------\n################################################################################################################\n# Usage:                                                                                                       #\n# bash> eos-test-credential-bindings  <localmount directory to test>\t\t\t\t\t\t\t\t\t \t   #\n################################################################################################################\n\nFUSEDIR=${1:-\"/eos/dockertest/test\"}\nUSER=\"$(id -un)\"\nCURRENT_KRB5CCNAME=${KRB5CCNAME}\nrestore_krb5ccname () {\n\texport KRB5CCNAME=${CURRENT_KRB5CCNAME}\n\t\n\t# Remove global binding\n\tfind /var/run/eos/credentials/ -type f -user ${USER} -exec rm -rf {} + \n}\ntrap restore_krb5ccname EXIT\n\n\ntestglobalbinding () {\n\techo \"${USER} is testing access to local mount point: ${FUSEDIR}\"\n    kdestroy # destroy valid (default credentials?!)\n    export KRB5CCNAME=/tmp/nonstandard.krb5\n    kinit eos-user@TEST.EOS -k -t /home/eos-user/eos-user.keytab || kinit\n        if [[ $? -ne 0 ]]; then\n        echo \"Unable to obtain valid credentials in the first place... Aborting\"\n        exit 1\n    fi\n    \n\t\n    mkdir -m 700 -p ${FUSEDIR}/${USER} && touch ${FUSEDIR}/${USER}/ola && /bin/ls ${FUSEDIR}/${USER}/ > /dev/null\n    if [[ $? -ne 0 ]]; then\n        echo \"Failed initial test (mkdir + touch + ls) : $?\"\n        exit 2\n    fi\n\n\t# should have permission denied, if mount point SSS configuration is disabled\n    env -u KRB5CCNAME /bin/ls ${FUSEDIR}/${USER}/ 2> /dev/null\n    if [[ $? -ne 2 ]]; then\n        echo \"Permission denied is expected but ls returned: $?\"\n        exit 3\n    fi\n    \n    # binding credentials so that the subprocess can access\n    eosfusebind -g\n\n    env -u KRB5CCNAME /bin/ls ${FUSEDIR}/${USER}/ > /dev/null\n    if [[ $? -ne 0 ]]; then\n        echo \"Should have permissions in credentials directory: $?\"\n        exit 4\n    fi\n\n    rm -rf ${FUSEDIR}/${USER}\n}\n\ntestglobalbinding"
  },
  {
    "path": "test/microbenchmarks/CMakeLists.txt",
    "content": "#-------------------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Abhishek Lekshmanan CERN\n#-------------------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2022 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\noption(USE_SYSTEM_GBENCH \"Use Google benchmark installed in the system if found\" ON)\n\nif(USE_SYSTEM_GBENCH)\n  find_package(benchmark)\nelse()\n  if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/benchmark)\n    set(BENCHMARK_FOUND TRUE)\n    set(BENCHMARK_ENABLE_GTEST_TESTS OFF)\n    add_subdirectory(benchmark EXCLUDE_FROM_ALL)\n    # benchmark redefines VERSION and fails with -Werror which is set internally\n    # override this, we don't care about warnings in the benchmark build itself\n    target_compile_options(benchmark PRIVATE -w)\n  endif()\nendif()\n\nif(NOT TARGET benchmark::benchmark)\n  return()\nendif()\n\nadd_executable(eos-microbenchmarks main.cc)\nadd_executable(eos-idmap-microbenchmark common/BM_IdMap.cc)\nadd_executable(eos-atomic-ptr-microbenchmark common/BM_AtomicPtr.cc)\nadd_executable(eos-random-microbenchmark common/BM_Random.cc)\nadd_executable(eos-nslocking-microbenchmark namespace/ns_quarkdb/BM_NSLocking.cc\n        ${CMAKE_SOURCE_DIR}/namespace/ns_quarkdb/tests/NsTests.cc)\nadd_executable(eos-rrseed-microbenchmark mgm/BM_RRSeed.cc\n        ${CMAKE_SOURCE_DIR}/mgm/placement/ThreadLocalRRSeed.cc)\nadd_executable(eos-threadid-microbenchmark common/BM_ThreadId.cc)\n\ntarget_link_libraries(eos-microbenchmarks PRIVATE\n  benchmark::benchmark\n  XROOTD::UTILS)\n\ntarget_link_libraries(eos-idmap-microbenchmark PRIVATE\n  benchmark::benchmark\n  EosCommon-Static)\n\ntarget_link_libraries(eos-atomic-ptr-microbenchmark PRIVATE\n  benchmark::benchmark\n  EosCommon-Static\n  ${CMAKE_THREAD_LIBS_INIT})\n\ntarget_link_libraries(eos-rrseed-microbenchmark PRIVATE\n  benchmark::benchmark\n  ${CMAKE_THREAD_LIBS_INIT}\n  EosCommon-Static)\n\ntarget_link_libraries(eos-random-microbenchmark PRIVATE\n  benchmark::benchmark)\n\ntarget_link_libraries(eos-threadid-microbenchmark PRIVATE\n  benchmark::benchmark EosCommon-Static)\n\nif (NOT CLIENT AND Linux)\n  add_executable(eos-flatscheduler-microbenchmark mgm/BM_FlatScheduler.cc\n    ${CMAKE_SOURCE_DIR}/mgm/placement/ClusterMap.cc\n    ${CMAKE_SOURCE_DIR}/mgm/placement/FlatScheduler.cc\n    ${CMAKE_SOURCE_DIR}/mgm/placement/RoundRobinPlacementStrategy.cc\n    ${CMAKE_SOURCE_DIR}/mgm/placement/WeightedRandomStrategy.cc\n    ${CMAKE_SOURCE_DIR}/mgm/placement/WeightedRoundRobinStrategy.cc\n    ${CMAKE_SOURCE_DIR}/mgm/placement/ThreadLocalRRSeed.cc)\n\n  target_link_libraries(eos-flatscheduler-microbenchmark PRIVATE\n    benchmark::benchmark\n    EosCommonServer-Static\n    EosCommon-Static\n    XXHASH::XXHASH)\nendif()\n\ntarget_link_libraries(eos-nslocking-microbenchmark PRIVATE\n  benchmark::benchmark\n  EosNsCommon-Static\n  FOLLY::FOLLY\n)\n"
  },
  {
    "path": "test/microbenchmarks/README.md",
    "content": "# Microbenchmarks\n\nThis area provides a simple way to benchmark code snippets, the idea is to use\nthem to measure what snippet might be faster in a head to head comparison. We\nuse google benchmark library for doing this, which provides all the scaffolding\nnecessary to run microbenchmarks.\n"
  },
  {
    "path": "test/microbenchmarks/common/BM_AtomicPtr.cc",
    "content": "#include \"common/concurrency/AtomicUniquePtr.h\"\n#include \"common/concurrency/RCULite.hh\"\n#include \"common/RWMutex.hh\"\n#include \"benchmark/benchmark.h\"\n#include <memory>\n#include <shared_mutex>\n#include <mutex>\n\nusing benchmark::Counter;\n\nstatic void BM_AtomicUniquePtrGet(benchmark::State& state)\n{\n  eos::common::atomic_unique_ptr<std::string> p(new std::string(\"foobar\"));\n  std::string *x;\n  for (auto _ : state) {\n    benchmark::DoNotOptimize(x = p.get());\n    benchmark::ClobberMemory();\n  }\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\nstatic void BM_UniquePtrGet(benchmark::State& state)\n{\n  std::unique_ptr<int> p(new int(1));\n  int *x;\n  for (auto _ : state) {\n    benchmark::DoNotOptimize( x = p.get());\n    benchmark::ClobberMemory();\n  }\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\nstatic void BM_SharedPtrCopy(benchmark::State& state)\n{\n  std::shared_ptr<std::string> p(new std::string(\"foobar\"));\n  for (auto _ : state) {\n    std::shared_ptr<std::string> p_copy=p;\n    benchmark::ClobberMemory();\n  }\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\nstatic void BM_AtomicSharedPtrGet(benchmark::State& state)\n{\n  std::shared_ptr<std::string> p(new std::string(\"foobar\"));\n  for (auto _ : state) {\n    std::shared_ptr<std::string> p_copy = std::atomic_load_explicit(&p, std::memory_order_acquire);\n    benchmark::ClobberMemory();\n  }\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\nstatic void BM_MutexLock(benchmark::State& state)\n{\n  std::mutex m;\n  std::unique_ptr<std::string> p(new std::string(\"foobar\"));\n  std::string *x;\n  for (auto _ : state) {\n    std::lock_guard<std::mutex> lock(m);\n    benchmark::DoNotOptimize(x=p.get());\n  }\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\nstatic void BM_SharedMutexLock(benchmark::State& state)\n{\n  std::shared_mutex m;\n  std::unique_ptr<std::string> p(new std::string(\"foobar\"));\n  std::string *x;\n  for (auto _ : state) {\n    std::shared_lock<std::shared_mutex> lock(m);\n    benchmark::DoNotOptimize(x=p.get());\n  }\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\nstatic void BM_RCUVersionReadLock(benchmark::State& state)\n{\n  eos::common::VersionedRCUDomain rcu_domain;\n  std::unique_ptr<std::string> p(new std::string(\"foobar\"));\n  std::string *x;\n  for (auto _ : state) {\n    eos::common::RCUReadLock rlock(rcu_domain);\n    benchmark::DoNotOptimize(x=p.get());\n  }\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n\n}\n\nstatic void BM_RCUEpochReadLock(benchmark::State& state)\n{\n  eos::common::EpochRCUDomain rcu_domain;\n  std::unique_ptr<std::string> p(new std::string(\"foobar\"));\n  std::string *x;\n  for (auto _ : state) {\n    eos::common::RCUReadLock rlock(rcu_domain);\n    benchmark::DoNotOptimize(x=p.get());\n  }\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n\n}\n\n\n\nstatic void BM_EOSReadLock(benchmark::State& state)\n{\n  eos::common::RWMutex m;\n  std::unique_ptr<std::string> p(new std::string(\"foobar\"));\n  std::string *x;\n  for (auto _ : state) {\n    eos::common::RWMutexReadLock lock(m);\n    benchmark::DoNotOptimize(x=p.get());\n  }\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\nstatic void BM_MutexRWLock(benchmark::State& state)\n{\n  std::mutex m;\n  std::unique_ptr<std::string> p(new std::string(\"foobar\"));\n  std::string *x;\n  auto writer_fn = [&p, &m] {\n    for (int i = 0; i < 10000; i++) {\n      std::lock_guard<std::mutex> lock(m);\n      p.reset(new std::string(\"foobar2\"));\n    }\n  };\n\n  std::thread writer;\n  if (state.thread_index() == 0) {\n    writer = std::thread(writer_fn);\n  }\n\n  for (auto _ : state) {\n    std::lock_guard<std::mutex> lock(m);\n    benchmark::DoNotOptimize(x=p.get());\n  }\n\n  if (state.thread_index() == 0) {\n    writer.join();\n  }\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\nstatic void BM_SharedMutexRWLock(benchmark::State& state)\n{\n  std::shared_mutex m;\n  std::unique_ptr<std::string> p(new std::string(\"foobar\"));\n  std::string *x;\n  auto writer_fn = [&p, &m] {\n    for (int i = 0; i < 10000; i++) {\n      std::unique_lock<std::shared_mutex> lock(m);\n      p.reset(new std::string(\"foobar2\"));\n    }\n  };\n\n  std::thread writer;\n  if (state.thread_index() == 0) {\n    writer = std::thread(writer_fn);\n  }\n\n  for (auto _ : state) {\n    std::shared_lock<std::shared_mutex> lock(m);\n    benchmark::DoNotOptimize(x=p.get());\n  }\n\n  if (state.thread_index() == 0) {\n    writer.join();\n  }\n\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\nstatic void BM_RCUVersionedReadWriteLock(benchmark::State& state)\n{\n  eos::common::RCUMutexT<> rcu_mutex;\n  eos::common::atomic_unique_ptr<std::string> p(new std::string(\"foobar\"));\n  std::string *x;\n  auto writer_fn = [&p, &rcu_mutex] {\n    for (int i=0; i < 10000; ++i) {\n\n      // First do a write lock to swap the pointer\n      std::string* x;\n      {\n        std::unique_lock lk(rcu_mutex);\n        x = p.reset(new std::string(\"foobar2\"));\n      }\n      delete x;\n\n      eos::common::ScopedRCUWrite w(rcu_mutex, p,\n                                    new std::string(\"foobar\" + std::to_string(i)));\n    }\n  };\n\n  std::thread writer;\n  if (state.thread_index()==0) {\n    writer = std::thread(writer_fn);\n  }\n\n  for (auto _ : state) {\n    std::shared_lock rlock(rcu_mutex);\n    benchmark::DoNotOptimize(x=p.get());\n  }\n\n  if (state.thread_index()==0) {\n    if (writer.joinable())\n      writer.join();\n  }\n\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\nstatic void BM_RCUEpochReadWriteLock(benchmark::State& state)\n{\n  eos::common::RCUMutexT<> rcu_mutex;\n  eos::common::atomic_unique_ptr<std::string> p(new std::string(\"foobar\"));\n  std::string *x;\n  auto writer_fn = [&p, &rcu_mutex] {\n    std::string* tmp;\n    for (int i=0; i < 10000; ++i) {\n      {\n        std::unique_lock lk(rcu_mutex);\n        tmp = p.reset(new std::string(\"foobar2\"));\n      }\n      delete tmp;\n\n      eos::common::ScopedRCUWrite w(rcu_mutex, p,\n                                    new std::string(\"foobar\" + std::to_string(i)));\n    }\n  };\n\n  std::thread writer;\n  if (state.thread_index() == 0) {\n    writer = std::thread(writer_fn);\n  }\n\n  for (auto _ : state) {\n    std::shared_lock rlock(rcu_mutex);\n    benchmark::DoNotOptimize(x=p.get());\n  }\n\n  if (state.thread_index() == 0) {\n    if (writer.joinable())\n      writer.join();\n  }\n\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\nstatic void BM_EOSReadWriteLock(benchmark::State& state)\n{\n  eos::common::RWMutex mtx;\n  std::unique_ptr<std::string> p(new std::string(\"foobar\"));\n  std::string *x;\n  auto writer_fn = [&p, &mtx] {\n    for (int i = 0; i < 10000; i++) {\n      eos::common::RWMutexWriteLock wlock(mtx);\n      p.reset(new std::string(\"foobar2\"));\n    }\n  };\n\n  std::thread writer;\n  if (state.thread_index() == 0) {\n    writer = std::thread(writer_fn);\n  }\n\n  for (auto _ : state) {\n    eos::common::RWMutexReadLock rlock(mtx);\n    benchmark::DoNotOptimize(x=p.get());\n  }\n\n  if (state.thread_index() == 0) {\n    writer.join();\n  }\n\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\n// Adapted from Abseil's Mutex benchmarks, which is under an Apache License\n// While the benchmarks above benchmarked the pure cost of a lock/unlock operation\n// unless the critical section is exceedingly small, this won't benchmark the\n// contention caused which usually happens when you have multiple threads trying to\n// get a lock and when the activity inside the lock is more or less realistic.\nstatic void DelayNs(int64_t _ns, int* data) {\n  using namespace std::chrono_literals;\n  auto end = std::chrono::system_clock::now() + std::chrono::nanoseconds(_ns);\n  int l;\n  while (std::chrono::system_clock::now() < end) {\n    l = (*data)+1;\n    (void) l; // make compiler happy\n    benchmark::DoNotOptimize(*data);\n  }\n}\n\ntemplate <typename MutexType>\nclass RaiiLocker {\npublic:\n  explicit RaiiLocker(MutexType* mu) : mu_(mu) { mu_->lock(); }\n  ~RaiiLocker() { mu_->unlock(); }\nprivate:\n  MutexType* mu_;\n};\n\ntemplate <>\nclass RaiiLocker<std::shared_mutex> {\npublic:\n  explicit RaiiLocker(std::shared_mutex* mu) : mu_(mu) { mu_->lock_shared(); }\n  ~RaiiLocker() { mu_->unlock_shared(); }\nprivate:\n  std::shared_mutex* mu_;\n};\n\ntemplate <>\nclass RaiiLocker<eos::common::EpochRCUDomain>\n{\npublic:\n  explicit RaiiLocker(eos::common::EpochRCUDomain* domain): domain_(domain) {\n    epoch = domain_->get_current_epoch();\n    tag = domain_->rcu_read_lock(epoch);\n  }\n\n  ~RaiiLocker() {\n    domain_->rcu_read_unlock(epoch, tag);\n  }\n private:\n  eos::common::EpochRCUDomain* domain_;\n  uint64_t epoch;\n  uint64_t tag;\n};\n\ntemplate <>\nclass RaiiLocker<eos::common::RWMutex>\n{\npublic:\n  explicit RaiiLocker(eos::common::RWMutex* mutex): mutex_(mutex) {\n    mutex_->LockRead();\n  }\n\n  ~RaiiLocker() {\n    mutex_->UnLockRead();\n  }\nprivate:\n  eos::common::RWMutex* mutex_;\n};\n\ntemplate <typename MutexType>\nvoid BM_Contended(benchmark::State& state) {\n\n  struct Shared {\n    MutexType mu;\n    int data = 0;\n  };\n  static auto* shared = new Shared;\n  int local = 0;\n  for (auto _ : state) {\n    // Here we model both local work outside of the critical section as well as\n    // some work inside of the critical section. The idea is to capture some\n    // more or less realisitic contention levels.\n    // If contention is too low, the benchmark won't measure anything useful.\n    // If contention is unrealistically high, the benchmark will favor\n    // bad mutex implementations that block and otherwise distract threads\n    // from the mutex and shared state for as much as possible.\n    // To achieve this amount of local work is multiplied by number of threads\n    // to keep ratio between local work and critical section approximately\n    // equal regardless of number of threads.\n    DelayNs(100 * state.threads(), &local);\n    RaiiLocker<MutexType> locker(&shared->mu);\n    DelayNs(state.range(0), &shared->data);\n  }\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n\n}\n\nvoid SetupBenchmarkArgs(benchmark::internal::Benchmark* bm) {\n  bm->UseRealTime()\n      // ThreadPerCpu poorly handles non-power-of-two CPU counts.\n      ->Threads(1)\n      ->Threads(2)\n      ->Threads(4)\n      ->Threads(6)\n      ->Threads(8)\n      ->Threads(12)\n      ->Threads(16)\n      ->Threads(24)\n      ->Threads(32)\n      ->Threads(48)\n      ->Threads(64)\n      ->Threads(96)\n      ->Threads(128)\n      ->Threads(192)\n      ->Threads(256)\n    ->ArgNames({\"cs_ns\"});\n  // Some empirically chosen amounts of work in critical section.\n  // 1 is low contention, 2000 is high contention and few values in between.\n  for (int critical_section_ns : {1, 20, 50, 200, 2000}) {\n      bm->Arg(critical_section_ns);\n  }\n}\n\nBENCHMARK(BM_AtomicUniquePtrGet)->ThreadRange(1, 256)->UseRealTime();\nBENCHMARK(BM_UniquePtrGet)->ThreadRange(1, 256)->UseRealTime();\nBENCHMARK(BM_SharedPtrCopy)->ThreadRange(1, 256)->UseRealTime();\nBENCHMARK(BM_AtomicSharedPtrGet)->ThreadRange(1, 256)->UseRealTime();\nBENCHMARK(BM_MutexLock)->ThreadRange(1, 256)->UseRealTime();\nBENCHMARK(BM_SharedMutexLock)->ThreadRange(1, 256)->UseRealTime();\nBENCHMARK(BM_RCUVersionReadLock)->ThreadRange(1,256)->UseRealTime();\nBENCHMARK(BM_RCUEpochReadLock)->ThreadRange(1,256)->UseRealTime();\nBENCHMARK(BM_EOSReadLock)->ThreadRange(1, 256)->UseRealTime();\n\nBENCHMARK(BM_MutexRWLock)->ThreadRange(1, 256)->UseRealTime();\nBENCHMARK(BM_SharedMutexRWLock)->ThreadRange(1, 256)->UseRealTime();\nBENCHMARK(BM_RCUVersionedReadWriteLock)->ThreadRange(1,256)->UseRealTime();\nBENCHMARK(BM_RCUEpochReadWriteLock)->ThreadRange(1,256)->UseRealTime();\nBENCHMARK(BM_EOSReadWriteLock)->ThreadRange(1, 256)->UseRealTime();\n\nBENCHMARK_TEMPLATE(BM_Contended, std::mutex)\n->Apply([](benchmark::internal::Benchmark* bm) {\n  SetupBenchmarkArgs(bm);\n });\nBENCHMARK_TEMPLATE(BM_Contended, std::shared_mutex)\n->Apply([](benchmark::internal::Benchmark* bm) {\n  SetupBenchmarkArgs(bm);\n });\nBENCHMARK_TEMPLATE(BM_Contended, eos::common::RWMutex)\n->Apply([](benchmark::internal::Benchmark* bm) {\n  SetupBenchmarkArgs(bm);\n });\nBENCHMARK_TEMPLATE(BM_Contended, eos::common::EpochRCUDomain)\n->Apply([](benchmark::internal::Benchmark* bm) {\n  SetupBenchmarkArgs(bm);\n });\n\nBENCHMARK_MAIN();\n"
  },
  {
    "path": "test/microbenchmarks/common/BM_IdMap.cc",
    "content": "//------------------------------------------------------------------------------\n// File: BM_IdMap.cc\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Mapping.hh\"\n#include <XrdSec/XrdSecEntity.hh>\n#include \"benchmark/benchmark.h\"\n#include <sstream>\n#include <thread>\n\nusing benchmark::Counter;\n\nclass MappingFixture: public benchmark::Fixture\n{\npublic:\n  void SetUp(const ::benchmark::State& state)\n  {\n    eos::common::Mapping::Init();\n  }\n\n  void TearDown(const ::benchmark::State& state)\n  {\n    eos::common::Mapping::Reset();\n  }\n\n};\n\nstatic void BM_IdMap(benchmark::State& state)\n{\n  using namespace eos::common;\n  std::atomic<uint64_t> ctr = 0;\n\n  if (state.thread_index() == 0) {\n    eos::common::Mapping::Reset();\n    eos::common::Mapping::Init();\n    eos::common::Mapping::gVirtualUidMap[\"sss:\\\"<pwd>\\\":uid\"] = 0;\n    eos::common::Mapping::gVirtualGidMap[\"sss:\\\"<pwd>\\\":gid\"] = 0;\n  }\n\n  for (auto _ : state) {\n    state.PauseTiming();\n    XrdSecEntity client(\"test\");\n    eos::common::VirtualIdentity vid;\n    vid.prot = \"sss\";\n    client.tident = \"root\";\n    std::stringstream base_ss;\n    base_ss << \"foo.bar:baz@bar\" << std::this_thread::get_id();\n    std::string tident_base = base_ss.str();\n    std::string client_name = \"client\" + std::to_string(ctr);\n    vid.uid = ctr % 2147483646;\n    vid.gid = ctr % 2147483646;\n    client.name = client_name.data();\n    std::string tident = tident_base + std::to_string(ctr);\n    state.ResumeTiming();\n    eos::common::Mapping::IdMap(&client, nullptr, tident.c_str(), vid);\n    state.PauseTiming();\n    ctr++;\n  }\n\n  if (state.thread_index() == 0) {\n    eos::common::Mapping::Reset();\n  }\n}\n\nstatic void BM_ReduceTident(benchmark::State& state)\n{\n  for (auto _ : state) {\n    //state.PauseTiming();\n    for (int j = 0; j < state.range(0); ++j) {\n      std::string tident = \"foo.bar:baz@bar\" + std::to_string(j);\n      std::string wildcardtident, mytident, myhost;\n      // state.ResumeTiming();\n      mytident = eos::common::Mapping::ReduceTident(tident, wildcardtident, myhost);\n    }\n  }\n\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\nstatic void BM_ReduceTidentXrd(benchmark::State& state)\n{\n  for (auto _ : state) {\n    //state.PauseTiming();\n    for (int j = 0; j < state.range(0); ++j) {\n      std::string tident = \"foo.bar:baz@bar\" + std::to_string(j);\n      XrdOucString tident_xrd(tident.c_str());\n      XrdOucString wildcardtident, mytident, myhost;\n      // state.ResumeTiming();\n      eos::common::Mapping::ReduceTident(tident_xrd, wildcardtident, mytident,\n                                         myhost);\n    }\n  }\n\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\nBENCHMARK(BM_IdMap)\n->Range(1 << 10, 1 << 20)->ThreadRange(1, 128)->UseRealTime()\n->Unit(benchmark::kMicrosecond);\n\nBENCHMARK(BM_ReduceTident)->Range(1, 1 << 20);\nBENCHMARK(BM_ReduceTidentXrd)->Range(1, 1 << 20);\nBENCHMARK_MAIN();\n"
  },
  {
    "path": "test/microbenchmarks/common/BM_Random.cc",
    "content": "#include \"benchmark/benchmark.h\"\n#include \"common/utils/RandUtils.hh\"\n#include <stdlib.h>\nusing benchmark::Counter;\n\nstatic constexpr uint32_t MAX_RAND = 60;\n\nstatic void BM_CRand(benchmark::State& state)\n{\n  uint32_t x;\n  for (auto _: state) {\n    benchmark::DoNotOptimize(x = rand() % MAX_RAND);\n    benchmark::ClobberMemory();\n  }\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\nstatic void BM_getRandom(benchmark::State& state)\n{\n  uint64_t x;\n  for (auto _: state) {\n    benchmark::DoNotOptimize(x = eos::common::getRandom((uint32_t)0,MAX_RAND));\n    benchmark::ClobberMemory();\n  }\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\nBENCHMARK(BM_CRand)->ThreadRange(1,256)->UseRealTime();\nBENCHMARK(BM_getRandom)->ThreadRange(1,256)->UseRealTime();\n\nBENCHMARK_MAIN();\n"
  },
  {
    "path": "test/microbenchmarks/common/BM_StringUtils.cc",
    "content": "#include \"benchmark/benchmark.h\"\n#include \"common/StringUtils.hh\"\n\nusing benchmark::Counter;\n\nstatic void BM_StringToNumeric(benchmark::State& state)\n{\n  int val;\n  std::string s = std::to_string(state.range(0));\n  using namespace eos::common;\n\n  // Since the call is very small, do a few cycles to avoid jitters\n  for (auto _ : state) {\n    for (int i = 0; i < 100; ++i) {\n      benchmark::DoNotOptimize(StringToNumeric(s, val));\n    }\n  }\n\n  state.counters[\"frequency\"] = Counter(100 * state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\n\nstatic void BM_atoi(benchmark::State& state)\n{\n  int val;\n  std::string s = std::to_string(state.range(0));\n\n  for (auto _ : state) {\n    for (int i = 0; i < 100; ++i) {\n      benchmark::DoNotOptimize(val = atoi(s.c_str()));\n    }\n  }\n\n  state.counters[\"frequency\"] = Counter(100 * state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\nint64_t start = 8;\nint64_t end = 1UL << 24;\nBENCHMARK(BM_StringToNumeric)->Range(start, end);\nBENCHMARK(BM_atoi)->Range(start, end);\n"
  },
  {
    "path": "test/microbenchmarks/common/BM_ThreadId.cc",
    "content": "#include \"benchmark/benchmark.h\"\n#include \"common/concurrency/ThreadEpochCounter.hh\"\n#include \"common/thread_id.hh\"\n#include <sys/syscall.h>\n#include <thread>\n#include <unistd.h>\n\nusing benchmark::Counter;\n\nstatic void BM_ThreadId(benchmark::State& state)\n{\n  for (auto _: state) {\n    benchmark::DoNotOptimize(std::this_thread::get_id());\n  }\n  state.counters[\"frequency\"]=Counter(state.iterations(),\n                                      benchmark::Counter::kIsRate);\n}\n\nstatic void BM_SysTID(benchmark::State& state)\n{\n  for (auto _: state) {\n    benchmark::DoNotOptimize(eos::common::thread_id());\n  }\n  state.counters[\"frequency\"]=Counter(state.iterations(),\n                                      benchmark::Counter::kIsRate);\n\n}\n\nstatic void BM_tlTID(benchmark::State& state)\n{\n  for (auto _: state) {\n    benchmark::DoNotOptimize(eos::common::tlocalID.get());\n  }\n  state.counters[\"freqeuncy\"]=Counter(state.iterations(),\n                                      benchmark::Counter::kIsRate);\n}\nBENCHMARK(BM_ThreadId)->ThreadRange(1,4096)->UseRealTime();\nBENCHMARK(BM_SysTID)->ThreadRange(1,4096)->UseRealTime();\nBENCHMARK(BM_tlTID)->ThreadRange(1,4096)->UseRealTime();\nBENCHMARK_MAIN();\n"
  },
  {
    "path": "test/microbenchmarks/common/BM_XrdString.cc",
    "content": "#include <XrdOuc/XrdOucString.hh>\n#include <string>\n#include <cstring>\n#include \"benchmark/benchmark.h\"\n\nusing benchmark::Counter;\n\nstatic void BM_StringCreate(benchmark::State& state)\n{\n  std::string _s(state.range(0), 'a');\n  const char* s = _s.c_str();\n\n  for (auto _ : state) {\n    benchmark::DoNotOptimize(std::string(s));\n  }\n\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\nstatic void BM_XrdStringCreate(benchmark::State& state)\n{\n  std::string _s(state.range(0), 'a');\n  const char* s = _s.c_str();\n\n  for (auto _ : state) {\n    benchmark::DoNotOptimize(XrdOucString(s));\n  }\n\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\n\nstatic void BM_StringAppend(benchmark::State& state)\n{\n  std::string s(\"This is a line\");\n\n  for (auto _ : state) {\n    for (auto i = 0; i < state.range(0); ++i) {\n      benchmark::DoNotOptimize(s += \"a\");\n    }\n  }\n\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\nstatic void BM_XrdStringAppend(benchmark::State& state)\n{\n  XrdOucString s(\"This is a line\");\n\n  for (auto _ : state) {\n    for (auto i = 0; i < state.range(0); ++i) {\n      benchmark::DoNotOptimize(s += \"a\");\n    }\n  }\n\n  state.counters[\"frequency\"] = Counter(state.iterations(),\n                                        benchmark::Counter::kIsRate);\n}\n\n\nBENCHMARK(BM_StringCreate)->RangeMultiplier(2)->Range(8, 1 << 9);\nBENCHMARK(BM_XrdStringCreate)->RangeMultiplier(2)->Range(8, 1 << 9);\nBENCHMARK(BM_StringAppend)->RangeMultiplier(2)->Range(8, 1 << 9);\nBENCHMARK(BM_XrdStringAppend)->RangeMultiplier(2)->Range(8, 1 << 9);\n"
  },
  {
    "path": "test/microbenchmarks/main.cc",
    "content": "#include \"benchmark/benchmark.h\"\n#include \"common/BM_StringUtils.cc\"\n#include \"common/BM_XrdString.cc\"\n\nBENCHMARK_MAIN();\n"
  },
  {
    "path": "test/microbenchmarks/mgm/BM_FlatScheduler.cc",
    "content": "// ----------------------------------------------------------------------\n// File: BM_FlatScheduler.cc\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#include \"benchmark/benchmark.h\"\n#include \"common/utils/ContainerUtils.hh\"\n#include \"mgm/placement/PlacementStrategy.hh\"\n#include \"mgm/placement/ClusterMap.hh\"\n#include \"mgm/placement/FlatScheduler.hh\"\n\n\nstatic void BM_Scheduler(benchmark::State& state) {\n  using namespace eos::mgm::placement;\n  auto n_groups = state.range(0);\n  auto n_elements = n_groups + 101;\n  const int n_disks_per_group = 16;\n  ClusterMgr mgr;\n  {\n\n    auto sh = mgr.getStorageHandler(n_elements);\n    sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0);\n    sh.addBucket(get_bucket_type(StdBucketType::SITE), -1, 0);\n\n    for (int i=0; i< n_groups; ++i) {\n      sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100-i, -1);\n    }\n\n    for (int i=0; i < n_groups*n_disks_per_group; i++) {\n      sh.addDisk(Disk(i+1, ConfigStatus::kRW, ActiveStatus::kOnline, 1),\n                 -100 - i/n_disks_per_group);\n    }\n\n  }\n  FlatScheduler flat_scheduler(PlacementStrategyT::kRoundRobin, n_elements);\n\n\n  for (auto _: state) {\n    auto cluster_data_ptr = mgr.getClusterData();\n    benchmark::DoNotOptimize(flat_scheduler.schedule(cluster_data_ptr(),state.range(1)));\n  }\n  state.counters[\"frequency\"] = benchmark::Counter(state.iterations(),\n                                                   benchmark::Counter::kIsRate);\n}\n\nstatic void BM_ThreadLocalRRScheduler(benchmark::State& state) {\n  using namespace eos::mgm::placement;\n  auto n_groups = state.range(0);\n  auto n_elements = 1024;\n  const int n_disks_per_group = 16;\n  ClusterMgr mgr;\n  {\n\n    auto sh = mgr.getStorageHandler(n_elements);\n    sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0);\n    //sh.addBucket(get_bucket_type(StdBucketType::SITE), -1, 0);\n\n    for (int i=0; i< n_groups; ++i) {\n      sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100-i, 0);\n    }\n\n    for (int i=0; i < n_groups*n_disks_per_group; i++) {\n      sh.addDisk(Disk(i+1, ConfigStatus::kRW, ActiveStatus::kOnline, 1),\n                 -100 - i/n_disks_per_group);\n    }\n\n  }\n  FlatScheduler flat_scheduler(PlacementStrategyT::kThreadLocalRoundRobin, n_elements);\n\n\n  for (auto _: state) {\n    auto cluster_data_ptr = mgr.getClusterData();\n    benchmark::DoNotOptimize(flat_scheduler.schedule(cluster_data_ptr(),state.range(1)));\n  }\n  state.counters[\"frequency\"] = benchmark::Counter(state.iterations(),\n                                                   benchmark::Counter::kIsRate);\n}\n\nstatic void BM_RandomScheduler(benchmark::State& state) {\n  using namespace eos::mgm::placement;\n  auto n_groups = state.range(0);\n  auto n_elements = 1024;\n  const int n_disks_per_group = 16;\n  ClusterMgr mgr;\n  {\n\n    auto sh = mgr.getStorageHandler(n_elements);\n    sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0);\n    //sh.addBucket(get_bucket_type(StdBucketType::SITE), -1, 0);\n\n    for (int i=0; i< n_groups; ++i) {\n      sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100-i, 0);\n    }\n\n    for (int i=0; i < n_groups*n_disks_per_group; i++) {\n      sh.addDisk(Disk(i+1, ConfigStatus::kRW, ActiveStatus::kOnline, 1),\n                 -100 - i/n_disks_per_group);\n    }\n\n  }\n  FlatScheduler flat_scheduler(PlacementStrategyT::kRandom, n_elements);\n\n\n  for (auto _: state) {\n    auto cluster_data_ptr = mgr.getClusterData();\n    benchmark::DoNotOptimize(flat_scheduler.schedule(cluster_data_ptr(),state.range(1)));\n  }\n  state.counters[\"frequency\"] = benchmark::Counter(state.iterations(),\n                                                   benchmark::Counter::kIsRate);\n}\n\nstatic void BM_FidScheduler(benchmark::State& state) {\n  using namespace eos::mgm::placement;\n  auto n_groups = state.range(0);\n  auto n_elements = 1024;\n  const int n_disks_per_group = 16;\n  ClusterMgr mgr;\n  {\n\n    auto sh = mgr.getStorageHandler(n_elements);\n    sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0);\n    //sh.addBucket(get_bucket_type(StdBucketType::SITE), -1, 0);\n\n    for (int i=0; i< n_groups; ++i) {\n      sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100-i, 0);\n    }\n\n    for (int i=0; i < n_groups*n_disks_per_group; i++) {\n      sh.addDisk(Disk(i+1, ConfigStatus::kRW, ActiveStatus::kOnline, 1),\n                 -100 - i/n_disks_per_group);\n    }\n\n  }\n  FlatScheduler flat_scheduler(PlacementStrategyT::kFidRandom, n_elements);\n\n\n  PlacementArguments args(state.range(1));\n  args.fid=1;\n  for (auto _: state) {\n    auto cluster_data_ptr = mgr.getClusterData();\n    benchmark::DoNotOptimize(flat_scheduler.schedule(cluster_data_ptr(), args));\n    args.fid++;\n  }\n  state.counters[\"frequency\"] = benchmark::Counter(state.iterations(),\n                                                   benchmark::Counter::kIsRate);\n}\n\nstatic void BM_WeightedRandomScheduler(benchmark::State& state) {\n  using namespace eos::mgm::placement;\n  auto n_groups = state.range(0);\n  auto n_elements = 1024;\n  const int n_disks_per_group = 16;\n  std::vector<int> weights = {4,8,16};\n  ClusterMgr mgr;\n  {\n\n    auto sh = mgr.getStorageHandler(n_elements);\n    sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0);\n    //sh.addBucket(get_bucket_type(StdBucketType::SITE), -1, 0);\n\n    for (int i=0; i< n_groups; ++i) {\n      sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100-i, 0);\n    }\n\n    for (int i=0; i < n_groups*n_disks_per_group; i++) {\n      sh.addDisk(Disk(i+1, ConfigStatus::kRW, ActiveStatus::kOnline,\n                      eos::common::pickIndexRR(weights, i)),\n                 -100 - i/n_disks_per_group);\n    }\n\n  }\n  FlatScheduler flat_scheduler(PlacementStrategyT::kWeightedRandom, n_elements);\n\n\n  PlacementArguments args(state.range(1));\n  args.fid=1;\n  for (auto _: state) {\n    auto cluster_data_ptr = mgr.getClusterData();\n    benchmark::DoNotOptimize(flat_scheduler.schedule(cluster_data_ptr(), args));\n    args.fid++;\n  }\n  state.counters[\"frequency\"] = benchmark::Counter(state.iterations(),\n                                                   benchmark::Counter::kIsRate);\n}\n\nstatic void BM_WeightedRRScheduler(benchmark::State& state) {\n  using namespace eos::mgm::placement;\n  auto n_groups = state.range(0);\n  auto n_elements = 1024;\n  const int n_disks_per_group = 16;\n  std::vector<int> weights = {4,8,16};\n  ClusterMgr mgr;\n  {\n\n    auto sh = mgr.getStorageHandler(n_elements);\n    sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0);\n    //sh.addBucket(get_bucket_type(StdBucketType::SITE), -1, 0);\n\n    for (int i=0; i< n_groups; ++i) {\n      sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100-i, 0);\n    }\n\n    for (int i=0; i < n_groups*n_disks_per_group; i++) {\n      sh.addDisk(Disk(i+1, ConfigStatus::kRW, ActiveStatus::kOnline,\n                      eos::common::pickIndexRR(weights, i)),\n                 -100 - i/n_disks_per_group);\n    }\n\n  }\n  FlatScheduler flat_scheduler(PlacementStrategyT::kWeightedRoundRobin, n_elements);\n\n\n  PlacementArguments args(state.range(1));\n  args.fid=1;\n  for (auto _: state) {\n    auto cluster_data_ptr = mgr.getClusterData();\n    benchmark::DoNotOptimize(flat_scheduler.schedule(cluster_data_ptr(), args));\n    args.fid++;\n  }\n  state.counters[\"frequency\"] = benchmark::Counter(state.iterations(),\n                                                   benchmark::Counter::kIsRate);\n}\n\n\n\n\nBENCHMARK(BM_Scheduler)->Threads(1)->Threads(8)->Threads(64)->Threads(128)->Threads(256)\n    ->ArgsProduct({{32, 64, 128, 256, 512},\n                   {2,3,6}})->UseRealTime();\n\nBENCHMARK(BM_ThreadLocalRRScheduler)->Threads(1)->Threads(8)->Threads(64)->Threads(128)->Threads(256)\n    ->ArgsProduct({{32, 64, 128, 256, 512},\n                   {2,3,6}})->UseRealTime();\n\nBENCHMARK(BM_RandomScheduler)->Threads(1)->Threads(8)->Threads(64)->Threads(128)->Threads(256)\n    ->ArgsProduct({{32, 64, 128, 256, 512},\n                   {2,3,6}})->UseRealTime();\n\nBENCHMARK(BM_FidScheduler)->Threads(1)->Threads(8)->Threads(64)->Threads(128)->Threads(256)\n->ArgsProduct({{32, 64, 128, 256, 512},\n               {2,3,6}})->UseRealTime();\n\nBENCHMARK(BM_WeightedRandomScheduler)->Threads(1)->Threads(8)->Threads(64)->Threads(128)->Threads(256)\n->ArgsProduct({{32, 64, 128, 256, 512},\n               {2,3,6}})->UseRealTime();\n\nBENCHMARK(BM_WeightedRRScheduler)->Threads(1)->Threads(8)->Threads(64)->Threads(128)->Threads(256)\n->ArgsProduct({{32, 64, 128, 256, 512},\n               {2,3,6}})->UseRealTime();\n\n\nBENCHMARK_MAIN();\n"
  },
  {
    "path": "test/microbenchmarks/mgm/BM_RRSeed.cc",
    "content": "// ----------------------------------------------------------------------\n// File: BM_RRSeed.cc\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"benchmark/benchmark.h\"\n#include \"mgm/placement/RRSeed.hh\"\n#include \"mgm/placement/ThreadLocalRRSeed.hh\"\n\nusing benchmark::Counter;\n\nstatic void BM_RRSeed(benchmark::State& state) {\n  eos::mgm::placement::RRSeed seed(10);\n  for (auto _ : state) {\n    for (int i=0;i<10; ++i)\n    benchmark::DoNotOptimize(seed.get(1,0));\n  }\n  state.counters[\"frequency\"] = Counter(state.iterations()*10,\n                                        benchmark::Counter::kIsRate);\n}\n\nstatic void BM_ThreadLocalRRSeed(benchmark::State& state) {\n  using namespace eos::mgm::placement;\n  ThreadLocalRRSeed::init(10);\n  for (auto _ : state) {\n    for (int i=0;i<10; ++i)\n    benchmark::DoNotOptimize(ThreadLocalRRSeed::get(1,0));\n  }\n  state.counters[\"frequency\"] = Counter(state.iterations()*10,\n                                        benchmark::Counter::kIsRate);\n}\n\nBENCHMARK(BM_RRSeed)->ThreadRange(1,64)->UseRealTime();\nBENCHMARK(BM_ThreadLocalRRSeed)->ThreadRange(1,64)->UseRealTime();\nBENCHMARK_MAIN();"
  },
  {
    "path": "test/microbenchmarks/namespace/ns_quarkdb/BM_NSLocking.cc",
    "content": "// ----------------------------------------------------------------------\n// File: BM_BulkNSLocking.cc\n// Author: Cedric Caffy - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"benchmark/benchmark.h\"\n#include \"namespace/MDLocking.hh\"\n#include \"namespace/locking/BulkNsObjectLocker.hh\"\n#include \"namespace/ns_quarkdb/tests/NsTests.hh\"\n#include \"namespace/ns_quarkdb/views/HierarchicalView.hh\"\n\n// Fixture for initializing services.\nclass BulkNSObjectLockFixture : public benchmark::Fixture {\npublic:\n  BulkNSObjectLockFixture() {\n\n  }\n  void SetUp(const benchmark::State &) override {\n\n  }\n  void TearDown(const benchmark::State &) override {\n\n  }\n  ~BulkNSObjectLockFixture() {\n  }\n};\n\nvoid simulateWork(size_t iterations) {\n  volatile size_t dummy = 0;\n  for (size_t i = 0; i < iterations; ++i) {\n    dummy += i;\n  }\n}\n\n\nstd::unique_ptr<eos::ns::testing::NsTests> nsTests;\nstd::shared_ptr<eos::IContainerMD> container1;\nstd::shared_ptr<eos::IContainerMD> container2;\nstd::shared_ptr<eos::IFileMD> file;\n\nBENCHMARK_DEFINE_F(BulkNSObjectLockFixture, ContainerMDLock)(benchmark::State& state)\n{\n  if (state.thread_index() == 0) {\n    nsTests = std::make_unique<eos::ns::testing::NsTests>();\n    container1 = nsTests->view()->createContainer(\"/test\",true);\n  }\n  for (auto _ : state) {\n    eos::MDLocking::ContainerWriteLock contLock(container1.get());\n    container1->setAttribute(\"test1\",\"test2\");\n    nsTests->view()->updateContainerStore(container1.get());\n  }\n  if (state.thread_index() == 0) {\n    nsTests.reset();\n  }\n}\n\n\nBENCHMARK_DEFINE_F(BulkNSObjectLockFixture, BulkNSObjectLocker)(benchmark::State& state)\n{\n  if (state.thread_index() == 0) {\n    nsTests = std::make_unique<eos::ns::testing::NsTests>();\n    container1 = nsTests->view()->createContainer(\"/test\",true);\n    container2 = nsTests->view()->createContainer(\"/test/test2\",true);\n    file = nsTests->view()->createFile(\"/test/test1\");\n  }\n  for (auto _ : state) {\n    eos::MDLocking::BulkMDWriteLock bulkLocker;\n    bulkLocker.add(container1.get());\n    bulkLocker.add(file.get());\n    bulkLocker.add(container2.get());\n    auto locks = bulkLocker.lockAll();\n    // Simulate work while holding the locks.\n    simulateWork(500000);\n  }\n  if (state.thread_index() == 0) {\n    nsTests.reset();\n  }\n}\n\nBENCHMARK_REGISTER_F(BulkNSObjectLockFixture, ContainerMDLock)->ThreadRange(1,5000)->UseRealTime();\nBENCHMARK_REGISTER_F(BulkNSObjectLockFixture, BulkNSObjectLocker)->ThreadRange(1,5000)->UseRealTime();\nBENCHMARK_MAIN();\n\n"
  },
  {
    "path": "test/microbenchmarks/namespace/ns_quarkdb/README.md",
    "content": "# How to run the ns_quarkdb microbenchmark\n\n## CMake flag + EOS Compilation\n\nIt's important to build EOS as `RelWithDebInfo` otherwise Google Benchmark will tell you that the benchmark may not be accurate.\nIn order to use the provided Google Benchmark, add the flag `-DUSE_SYSTEM_GBENCH=\"OFF\"`.\n\nE.g:\n```bash\ncmake ../ -G Ninja -DUSE_SYSTEM_GBENCH=\"OFF\" -DCMAKE_BUILD_TYPE=\"RelWithDebInfo\" -DCMAKE_INSTALL_PREFIX=/usr/ -Wno-dev\n```\n\n## Create a new quarkdb instance\n\nCreate a new quarkdb instance under `/var/lib/quarkdb/quarkdb-unit-tests`.\n\n\n```bash\n# Create the following configuration file:\n $ cat /etc/xrd.cf.quarkdb-unit-tests \nxrd.port 7778\nxrd.protocol redis:7778 libXrdQuarkDB.so\n\nredis.mode standalone\nredis.database /var/lib/quarkdb/quarkdb-unit-tests\n\n#----------------------------------------------------------\n# $EOS_QUARKDB_HOSTPORT environment variable must be set\n# with the same value used for redis.myself\n#----------------------------------------------------------\n# redis.myself localhost:9999\n\n#----------------------------------------------------------\n# $EOS_QUARKDB_PASSWD environment variable must be set\n# with the same value used for redis.password\n#----------------------------------------------------------\nredis.password_file /etc/eos.keytab\n\n```\n\n```bash\nUUID=eostest-$(uuidgen); echo $UUID; sudo runuser daemon -s /bin/bash -c \"quarkdb-create --path /var/lib/quarkdb/quarkdb-unit-tests --clusterID $UUID --nodes localhost:7778\"\n```\n\n```bash\n# Create this configuration file too:\n$ cat /etc/systemd/system/eos@quarkdb-unit-tests.service.d/custom.conf\n[Service]\nUser=daemon\nGroup=daemon\n```\n\n```bash\nsystemctl start eos@quarkdb-unit-tests\n```\n\n## Run the benchmark\n\n```bash\nexport EOS_QUARKDB_HOSTPORT='localhost:7778'\ncd /eos/build/dir/\n/test/microbenchmarks/eos-nslocking-microbenchmark\n```\n\n"
  },
  {
    "path": "test/mq/SharedHashLoadTest.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n  * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/CLI11.hpp\"\n#include \"common/Locators.hh\"\n#include \"common/PasswordHandler.hh\"\n#include \"common/utils/RandUtils.hh\"\n#include \"mq/MessagingRealm.hh\"\n#include \"mq/SharedHashWrapper.hh\"\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include \"qclient/shared/SharedHashSubscription.hh\"\n#include \"qclient/shared/SharedManager.hh\"\n#include \"utils/RandUtils.hh\"\n#include <functional>\n#include <sstream>\n#include <stdexcept>\n#include <string>\n\n#define SSTR(message) static_cast<std::ostringstream&>(std::ostringstream().flush() << message).str()\n\nusing eos::common::PasswordHandler;\nusing eos::common::SharedHashLocator;\nstd::unique_ptr<eos::mq::MessagingRealm> gMsgRealm {nullptr};\nstd::unique_ptr<eos::mq::SharedHashWrapper> gSharedHash {nullptr};\nstd::mutex gMutexTerminate;\nstd::condition_variable gCvTerminate;\nbool gSignalTerminate {false};\n\n//! Structure modelling the type of shared has updates:\n//! kPersistent - this is stored in the raft journal and persisted in QDB\n//! kTransient  - this is kept only in memory of QDB and set to potential\n//!               interested subsribers\n//! kLocal      - this never leaves the current client memory and is never\n//!               send or persisted in QDB\nenum UpdateType {\n  kPersistent = 0,\n  kTransient,\n  kLocal\n};\n\n//------------------------------------------------------------------------------\n// QDB cluster member validator\n//------------------------------------------------------------------------------\nstruct MemberValidator: public CLI::Validator {\n  MemberValidator(): Validator(\"MEMBER\")\n  {\n    func_ = [](const std::string & str) {\n      qclient::Members members;\n\n      if (!members.parse(str)) {\n        return SSTR(\"Failed parsing members: \" << str << \".\"\n                    << \"Expected format is a comma-separated list of servers.\");\n      }\n\n      return std::string();\n    };\n  }\n};\n\n//------------------------------------------------------------------------------\n// Logging object\n//------------------------------------------------------------------------------\nclass Logger\n{\npublic:\n  //------------------------------------------------------------------------------\n  // Log method\n  //------------------------------------------------------------------------------\n  void log(std::string_view data)\n  {\n    std::unique_lock<std::mutex> lock(mMutex);\n    fprintf(stderr, \"%s\\n\", data.data());\n  }\n\nprivate:\n  std::mutex mMutex;\n\n};\n\n//------------------------------------------------------------------------------\n// Generate a random alpha-numeric string of the given length\n//------------------------------------------------------------------------------\nstd::string RandomString(std::string::size_type length)\n{\n  static const std::string chrs = \"0123456789\"\n                                  \"abcdefghijklmnopqrstuvwxyz\"\n                                  \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n  std::string s;\n  s.reserve(length);\n\n  while (length--) {\n    s += chrs[eos::common::getRandom(0ul, chrs.length() - 2)];\n  }\n\n  return s;\n}\n\n//------------------------------------------------------------------------------\n// Generate list of random keys of given length\n//------------------------------------------------------------------------------\nstd::vector<std::string>\nGenerateStrings(unsigned int num_keys, unsigned int key_length,\n                const std::string prefix = \"\")\n{\n  std::vector<std::string> keys;\n\n  while (num_keys--) {\n    keys.push_back(prefix + RandomString(key_length));\n  }\n\n  return keys;\n}\n\n//------------------------------------------------------------------------------\n// Pseudo random picker [0, lenght - 1]\n//------------------------------------------------------------------------------\nunsigned int RandomPick(unsigned int length)\n{\n  return (eos::common::getRandom<unsigned int>(0, length - 1));\n}\n\n//------------------------------------------------------------------------------\n// Handle producer functionality\n//------------------------------------------------------------------------------\nvoid HandleProducer(unsigned int num_keys, unsigned int key_length,\n                    unsigned int value_length, unsigned int batch_size,\n                    unsigned long timeout_sec, UpdateType upd_type,\n                    Logger* logger)\n{\n  using namespace std::chrono;\n  std::vector<std::string> keys = GenerateStrings(num_keys, key_length, \"key_\");\n  std::vector<std::string> values = GenerateStrings(num_keys * 2, value_length,\n                                    \"val_\");\n  uint64_t count = 0ull;\n  unsigned int tmp_batch = 0ul;\n  auto start_ts = system_clock::now();\n  auto deadline =  start_ts + seconds(timeout_sec);\n\n  while (true) {\n    if (timeout_sec && ((count & 0x03ff) == 0) &&\n        (system_clock::now() > deadline)) {\n      break;\n    }\n\n    eos::mq::SharedHashWrapper::Batch batch;\n    tmp_batch = batch_size;\n\n    while (tmp_batch--) {\n      switch (upd_type) {\n      case UpdateType::kLocal:\n        batch.SetLocal(keys[RandomPick(keys.size())],\n                       values[RandomPick(values.size())]);\n        break;\n\n      case UpdateType::kTransient:\n        batch.SetTransient(keys[RandomPick(keys.size())],\n                           values[RandomPick(values.size())]);\n        break;\n\n      case UpdateType::kPersistent:\n        batch.SetDurable(keys[RandomPick(keys.size())],\n                         values[RandomPick(values.size())]);\n        break;\n\n      default:\n        throw std::runtime_error(\"unknown update type\");\n        break;\n      }\n    }\n\n    gSharedHash->set(batch);\n    ++count;\n  }\n\n  auto finish_ts = system_clock::now();\n  auto duration_ms = duration_cast<milliseconds>(finish_ts - start_ts);\n  // Compute some statistics and log summary\n  std::ostringstream oss;\n  oss << \"INFO: Producer statistics tid=\" << std::this_thread::get_id() << \"\\n\"\n      << \"      Number of updates: \" << count << \"\\n\"\n      << \"      Update rate:       \"\n      << (1000.0 * count) / duration_ms.count() << \" Hz\";\n  logger->log(oss.str());\n}\n\n//------------------------------------------------------------------------------\n// Class Stats - collecting statistics about requests handled by the consumer\n//------------------------------------------------------------------------------\nclass Stats\n{\npublic:\n  //------------------------------------------------------------------------------\n  // Callback to be used by the subcription\n  //------------------------------------------------------------------------------\n  void Callback(qclient::SharedHashUpdate&& upd)\n  {\n    Collect();\n  }\n\n  //------------------------------------------------------------------------------\n  // Trigger stats collection\n  //------------------------------------------------------------------------------\n  void Collect()\n  {\n    auto ts = std::chrono::seconds(std::time(nullptr)).count();\n    std::unique_lock<std::mutex> lock(mMutex);\n    mFreqMap[ts]++;\n  }\n\n  //------------------------------------------------------------------------------\n  // Dump summary of the collected statistics\n  //------------------------------------------------------------------------------\n  void DumpSummary(Logger* logger)\n  {\n    uint64_t total = 0ull;\n    double min_freq = 0, avg_freq = 0, max_freq = 0;\n    {\n      std::unique_lock<std::mutex> lock(mMutex);\n\n      for (const auto& elem : mFreqMap) {\n        total += elem.second;\n\n        if (min_freq) {\n          if (min_freq > elem.second) {\n            min_freq = elem.second;\n          }\n        } else {\n          min_freq = elem.second;\n        }\n\n        if (max_freq) {\n          if (max_freq < elem.second) {\n            max_freq = elem.second;\n          }\n        } else {\n          max_freq = elem.second;\n        }\n      }\n\n      if (mFreqMap.size() > 2) {\n        avg_freq = total * 1.0 /\n                   (mFreqMap.rbegin()->first - mFreqMap.begin()->first);\n      }\n    }\n    std::ostringstream oss;\n    oss << \"info: consumer statistics\\n\"\n        << \" total upd: \" << total << \"\\n\"\n        << \" min freq:  \" << min_freq << \" Hz\\n\"\n        << \" avg freq:  \" << avg_freq << \" Hz\\n\"\n        << \" max freq:  \" << max_freq << \" Hz\\n\";\n    logger->log(oss.str());\n  }\n\n  //------------------------------------------------------------------------------\n  // Get timestamp of the last received request\n  //------------------------------------------------------------------------------\n  uint64_t GetLastTs() const\n  {\n    uint64_t ts = 0ull;\n    std::unique_lock<std::mutex> lock(mMutex);\n\n    if (!mFreqMap.empty()) {\n      ts = mFreqMap.rbegin()->first;\n    }\n\n    return ts;\n  }\n\nprivate:\n  mutable std::mutex mMutex;\n  //! Map timestamp values to number of requests\n  std::map<uint64_t, uint64_t> mFreqMap;\n};\n\n//------------------------------------------------------------------------------\n// Register signal handler\n//------------------------------------------------------------------------------\nvoid ConsumerSignalHandler(int sig)\n{\n  (void) signal(SIGINT, SIG_IGN);\n  std::cout << \"info: calling signal handler\" << std::endl;\n  {\n    std::unique_lock<std::mutex> lock(gMutexTerminate);\n    gSignalTerminate = true;\n  }\n  gCvTerminate.notify_one();\n}\n\n//------------------------------------------------------------------------------\n// Handle consumer functionality\n//------------------------------------------------------------------------------\nvoid HandleConsumer(unsigned long timeout_sec, Logger* logger)\n{\n  using std::placeholders::_1;\n  Stats stats;\n  std::unique_ptr<qclient::SharedHashSubscription> sub = gSharedHash->subscribe();\n  sub->attachCallback(std::bind(&Stats::Callback, &stats, _1));\n\n  while (true) {\n    std::unique_lock<std::mutex> lock(gMutexTerminate);\n\n    if (timeout_sec) {\n      if (gCvTerminate.wait_for(lock, std::chrono::seconds(timeout_sec),\n      [&]() {\n      return gSignalTerminate;\n    })) {\n        //std::cout << \"info: consumer signalled to terminate\" << std::endl;\n      } else {\n        //std::cout << \"info: consumer timeout expired\" << std::endl;\n      }\n    } else {\n      gCvTerminate.wait(lock, [&]() {\n        return gSignalTerminate;\n      });\n    }\n\n    break;\n  }\n\n  stats.DumpSummary(logger);\n}\n\n//------------------------------------------------------------------------------\n// Given a sub-command, these are common options that need to be present\n//------------------------------------------------------------------------------\nvoid AddClusterOptions(CLI::App* subcmd, std::string& membersStr,\n                       MemberValidator& memberValidator, std::string& password,\n                       std::string& passwordFile, unsigned int& connection_retries,\n                       unsigned long& timeout)\n{\n  subcmd->add_option(\"--members\", membersStr,\n                     \"One or more members of the QDB cluster\")\n  ->required()\n  ->check(memberValidator);\n  subcmd->add_option(\"--timeout\", timeout,\n                     \"Execution timeout - default infinite i.e 0\");\n  subcmd->add_option(\"--connection-retries\", connection_retries,\n                     \"Number of connection retries - default infinite\");\n  auto passwordGroup = subcmd->add_option_group(\"Authentication\",\n                       \"Specify QDB authentication options\");\n  passwordGroup->add_option(\"--password\", password,\n                            \"The password for connecting to the QDB cluster - can be empty\");\n  passwordGroup->add_option(\"--password-file\", passwordFile,\n                            \"The passwordfile for connecting to the QDB cluster - can be empty\");\n  passwordGroup->require_option(0, 1);\n}\n\n//------------------------------------------------------------------------------\n// Main\n//------------------------------------------------------------------------------\nint main(int argc, char* argv[])\n{\n  Logger logger;\n  CLI::App app(\"Tool to generate load for a SharedHash object stored in QDB\");\n  app.require_subcommand();\n  // Basic parameters\n  std::string members_input;\n  MemberValidator member_validator;\n  std::string password_data;\n  std::string password_file;\n  unsigned int connection_retries = 0;\n  unsigned long timeout_sec = 0ul;\n  // Producer subcommand\n  auto producer_subcmd = app.add_subcommand(\"producer\",\n                         \"Add producer that updates shared hash values\");\n  AddClusterOptions(producer_subcmd, members_input, member_validator,\n                    password_data, password_file, connection_retries, timeout_sec);\n  std::string hash_name {\"hash-load-test\"};\n  producer_subcmd->add_option(\"--target-hash\", hash_name,\n                              \"Target hash name\")->default_val(hash_name);\n  unsigned int num_keys {10};\n  producer_subcmd->add_option(\"--num-keys\", num_keys,\n                              \"Number of keys to target\")->default_val(std::to_string(num_keys));\n  unsigned int key_length {65};\n  producer_subcmd->add_option(\"--key-length\", key_length,\n                              \"Size of the keys\")->default_val(std::to_string(key_length));\n  unsigned int value_length {65};\n  producer_subcmd->add_option(\"--value-length\", value_length,\n                              \"Size of the values\")->default_val(std::to_string(value_length));\n  unsigned int concurrency {1};\n  producer_subcmd->add_option(\"--concurrency\", concurrency,\n                              \"Number of concurrent threads\")->default_val(std::to_string(concurrency));\n  unsigned int batch_size {1};\n  producer_subcmd->add_option(\"--batch-upd-size\", batch_size,\n                              \"Number of keys updated in one batch\")->default_val(std::to_string(batch_size));\n  // Lambda that converts string to internal update types\n  auto upd_transformer = [](const std::string & input) -> std::string {\n    if (input == \"persistent\")\n    {\n      return std::to_string(UpdateType::kPersistent);\n    } else if (input == \"transient\")\n    {\n      return std::to_string(UpdateType::kTransient);\n    } else if (input == \"local\")\n    {\n      return std::to_string(UpdateType::kLocal);\n    } else\n    {\n      throw CLI::ValidationError(SSTR(\"unknown update type \" << input));\n    }\n  };\n  UpdateType upd_type = kLocal;\n  producer_subcmd->add_option(\"--update-type\", upd_type,\n                              \"Update type: persistent, transient or local\")->transform(\n                                upd_transformer)->default_str(\"local\");\n  // Consumer subcommand\n  auto consumer_subcmd = app.add_subcommand(\"consumer\",\n                         \"Add consumer of shared hash updates\");\n  AddClusterOptions(consumer_subcmd, members_input, member_validator,\n                    password_data, password_file, connection_retries, timeout_sec);\n  consumer_subcmd->add_option(\"--target-hash\", hash_name,\n                              \"Target hash name\")->default_val(hash_name);\n\n  // Do the command line parsing\n  try {\n    app.parse(argc, argv);\n  } catch (const CLI::ParseError& e) {\n    std::cerr << \"error: \" << e.what() << std::endl;\n    return app.exit(e);\n  }\n\n  // Handle password file option\n  if (!password_file.empty()) {\n    if (!PasswordHandler::readPasswordFile(password_file, password_data)) {\n      std::cerr << \"Could not read passwordfile: '\" << password_file\n                << \"'. Ensure the file exists, and its permissions are 400.\"\n                << std::endl;\n      return 1;\n    }\n  }\n\n  // Setup qclient to be used for QDB connection\n  qclient::Members members = qclient::Members::fromString(members_input);\n  eos::QdbContactDetails contact_details(members, password_data);\n  qclient::Options opts = contact_details.constructOptions();\n\n  if (connection_retries) {\n    opts.retryStrategy = qclient::RetryStrategy::NRetries(connection_retries);\n  }\n\n  // Build the shared hash object\n  std::unique_ptr<qclient::SharedManager> qsm {\n    new qclient::SharedManager(contact_details.members,\n                               contact_details.constructSubscriptionOptions())};\n  gMsgRealm.reset(new eos::mq::MessagingRealm(qsm.get()));\n  eos::common::SharedHashLocator hash_locator(\"dummy\",\n      SharedHashLocator::Type::kNode,\n      hash_name);\n  gSharedHash.reset(new eos::mq::SharedHashWrapper(gMsgRealm.get(),\n                    hash_locator));\n\n  if (producer_subcmd->parsed()) {\n    std::cout << \"info: handle producer\" << std::endl;\n    std::list<std::thread*> pool_threads;\n\n    for (unsigned int i = 0; i < concurrency; ++i) {\n      pool_threads.push_back(new std::thread(&HandleProducer, num_keys, key_length,\n                                             value_length, batch_size, timeout_sec,\n                                             upd_type, &logger));\n    }\n\n    for (auto* ptr_thread : pool_threads) {\n      ptr_thread->join();\n      delete ptr_thread;\n    }\n  } else if (consumer_subcmd->parsed()) {\n    // Add signal handler for Control-C\n    (void) signal(SIGINT, ConsumerSignalHandler);\n    HandleConsumer(timeout_sec, &logger);\n  }\n\n  gSharedHash.reset();\n  gMsgRealm.reset();\n  qsm.reset();\n  return 0;\n}\n"
  },
  {
    "path": "test/mq/XrdMqClientMaster.cc",
    "content": "//------------------------------------------------------------------------------\n// File: XrdMqClientMaster.cc\n//------------------------------------------------------------------------------o\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mq/XrdMqClient.hh\"\n#include \"mq/XrdMqTiming.hh\"\n#include <stdio.h>\n#include <chrono>\n\nint main(int argc, char* argv[])\n{\n  uint64_t num_loops = 1000;\n\n  if (argc == 2) {\n    num_loops = std::stoi(argv[1]);\n  }\n\n  XrdMqClient mqc;\n\n  if (!mqc.AddBroker(\"root://localhost:1097//eos/localhost/master\", true, true)) {\n    std::cerr << \"error: failed to add broker\" << std::endl;\n    exit(-1);\n  }\n\n  mqc.Subscribe();\n  mqc.SetDefaultReceiverQueue(\"/eos/*/worker\");\n  XrdMqMessage message(\"Hello Worker\");\n  message.Configure();\n  message.Encode();\n  message.Print();\n  XrdMqTiming mq(\"send\");\n  TIMING(\"START\", &mq);\n\n  do {\n    for (uint64_t i = 0; i < num_loops; ++i) {\n      message.NewId();\n      message.kMessageHeader.kDescription = \"Hello Worker Test \";\n      message.kMessageHeader.kDescription += (int)i;\n      (mqc << message);\n\n      for (int j = 0; j < 10; j++) {\n        std::unique_ptr<XrdMqMessage> new_msg {mqc.RecvMessage()};\n\n        if (new_msg == nullptr) {\n          std::this_thread::sleep_for(std::chrono::seconds(2));\n          continue;\n        }\n\n        if ((new_msg->kMessageHeader.kType == XrdMqMessageHeader::kStatusMessage) ||\n            (new_msg->kMessageHeader.kType == XrdMqMessageHeader::kQueryMessage)) {\n          std::unique_ptr<XrdAdvisoryMqMessage> adv_msg {\n            XrdAdvisoryMqMessage::Create(new_msg->GetMessageBuffer())};\n          // adv_msg->Print();\n        } else {\n          new_msg->Print();\n\n          if (new_msg->kMessageHeader.kDescription != \"Hello Master Test\") {\n            std::terminate();\n          }\n        }\n\n        do {\n          new_msg.reset(mqc.RecvFromInternalBuffer());\n\n          if (new_msg == nullptr) {\n            break;\n          } else {\n            if ((new_msg->kMessageHeader.kType == XrdMqMessageHeader::kStatusMessage) ||\n                (new_msg->kMessageHeader.kType == XrdMqMessageHeader::kQueryMessage)) {\n              std::unique_ptr<XrdAdvisoryMqMessage> adv_msg {\n                XrdAdvisoryMqMessage::Create(new_msg->GetMessageBuffer())};\n              // adv_msg->Print();\n            } else {\n              new_msg->Print();\n\n              if (new_msg->kMessageHeader.kDescription != \"Hello Master Test\") {\n                std::terminate();\n              }\n            }\n          }\n        } while (true);\n      }\n    }\n  } while (true);\n\n  TIMING(\"SEND+RECV\", &mq);\n  mq.Print();\n}\n"
  },
  {
    "path": "test/mq/XrdMqClientTest.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdMqClientTest.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mq/XrdMqClient.hh\"\n#include \"mq/XrdMqTiming.hh\"\n#include <XrdSys/XrdSysLogger.hh>\n#include <stdio.h>\n\nint main(int argc, char* argv[])\n{\n  uint64_t num_loops = 1000;\n\n  if (argc == 2) {\n    num_loops = std::stoi(argv[1]);\n  }\n\n  XrdMqMessage::Logger = new XrdSysLogger();\n  XrdMqMessage::Eroute.logger(XrdMqMessage::Logger);\n  XrdMqClient mqc;\n  std::string broker_url = \"root://localhost:1097//xmessage/\";\n\n  if (!mqc.AddBroker(broker_url.c_str())) {\n    std::cerr << \"error: failed to add broker \" << broker_url << std::endl;\n    exit(-1);\n  }\n\n  if (mqc.AddBroker(\"root://localhost:1097//xmessage/\")) {\n    std::cerr << \"error: added twice the same broker \" << broker_url << std::endl;\n    exit(-1);\n  }\n\n  mqc.Subscribe();\n  mqc.SetDefaultReceiverQueue(\"/xmessage/*\");\n  XrdMqMessage message(\"TestMessage\");\n  message.Print();\n  XrdMqTiming mq(\"send\");\n  TIMING(\"START\", &mq);\n\n  for (uint64_t i = 0; i < num_loops; ++i) {\n    message.NewId();\n    message.kMessageHeader.kDescription = \"Test\";\n    message.kMessageHeader.kDescription += (int)i;\n    (mqc << message);\n    std::unique_ptr<XrdMqMessage> newmessage {mqc.RecvMessage()};\n\n    if (newmessage) {\n      if (i == 0ull)  {\n        newmessage->Print();\n      }\n    }\n  }\n\n  TIMING(\"SEND+RECV\", &mq);\n  mq.Print();\n}\n"
  },
  {
    "path": "test/mq/XrdMqClientWorker.cc",
    "content": "//------------------------------------------------------------------------------\n// File: XrdMqClientWorker.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mq/XrdMqClient.hh\"\n#include \"mq/XrdMqTiming.hh\"\n#include <stdio.h>\n\nint main(int argc, char* argv[])\n{\n  if (argc != 2) {\n    std::cerr << \"error: at least one argument neeeds to be provided\"\n              << std::endl;\n    exit(-1);\n  }\n\n  XrdOucString myid = \"root://localhost:1097//eos/\";\n  myid += argv[1];\n  myid += \"/worker\";\n  XrdMqClient mqc;\n\n  if (!mqc.AddBroker(myid.c_str())) {\n    std::cerr << \"error: failed to add broker \" << myid.c_str() << std::endl;\n    exit(-1);\n  }\n\n  mqc.Subscribe();\n  mqc.SetDefaultReceiverQueue(\"/eos/*/master\");\n  XrdMqMessage message(\"Msg for master\");\n  message.Configure();\n  message.Encode();\n  XrdMqTiming mq(\"send\");\n  TIMING(\"START\", &mq);\n  uint64_t count = 0ull;\n\n  while (true) {\n    message.NewId();\n    message.kMessageHeader.kDescription = \"Hello Master Test\";\n    (mqc << message);\n    std::unique_ptr<XrdMqMessage> new_msg {mqc.RecvMessage()};\n\n    if (new_msg) {\n      new_msg->Print();\n      std::string expected = \"Hello Worker Test \" + std::to_string(count);\n\n      if (new_msg->kMessageHeader.kDescription != expected.c_str()) {\n        std::cerr << \"expected: \" << expected << \" received: \" <<\n                  new_msg->kMessageHeader.kDescription << std::endl;\n        std::terminate();\n      }\n\n      ++count;\n    }\n\n    do {\n      new_msg.reset(mqc.RecvFromInternalBuffer());\n\n      if (new_msg == nullptr) {\n        break;\n      } else {\n        new_msg->Print();\n        std::string expected = \"Hello Worker Test \" + std::to_string(count);\n\n        if (new_msg->kMessageHeader.kDescription != expected.c_str()) {\n          std::cerr << \"expected: \" << expected << \" received: \" <<\n                    new_msg->kMessageHeader.kDescription << std::endl;\n          std::terminate();\n        }\n\n        ++count;\n      }\n    } while (true);\n  }\n\n  TIMING(\"SEND+RECV\", &mq);\n  mq.Print();\n}\n"
  },
  {
    "path": "test/mq/XrdMqQueueDumper.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdMqQueueDumper.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mq/XrdMqClient.hh\"\n#include <stdio.h>\n#include <chrono>\n\nint main(int argc, char* argv[])\n{\n  uint64_t max_dumps = 0ull;\n  uint64_t max_timeout = 0ull;\n  uint64_t ms_sleep = 1000;\n  bool debug = false;\n  std::chrono::time_point<std::chrono::steady_clock> deadline;\n\n  if ((argc < 2) || (argc > 6)) {\n    std::cerr << \"Usage: \" << argv[0] << \" <broker_url>/<queue> num_dumps \"\n              << \" sleep_between_dumps_ms max_timeout_sec debug\" << std::endl;\n    exit(-1);\n  }\n\n  if (argc >= 3) {\n    max_dumps = std::stoll(argv[2]);\n  }\n\n  if (argc >= 4) {\n    ms_sleep = std::stoll(argv[3]);\n  }\n\n  if (argc >= 5) {\n    max_timeout = std::stoull(argv[4]);\n\n    if (max_timeout) {\n      deadline = std::chrono::steady_clock::now() +\n                 std::chrono::seconds(max_timeout);\n    }\n  }\n\n  if (argc >= 6) {\n    debug = std::stoll(argv[5]) ? true : false;\n  }\n\n  XrdOucString broker = argv[1];\n\n  if (!broker.beginswith(\"root://\")) {\n    std::cerr << \"error: <borkerurl> must have the following format \"\n              << \"root://host[:port]/<queue>\" << std::endl;\n    exit(-1);\n  }\n\n  XrdMqClient mqc;\n\n  if (!mqc.AddBroker(broker.c_str())) {\n    std::cerr << \"error: failed to add broker \" << broker.c_str() << std::endl;\n    exit(-1);\n  }\n\n  mqc.Subscribe();\n  XrdMqMessage message(\"\");\n  message.Configure(0); // Creates a logger object for the message\n  uint64_t dumped = 0ull;\n\n  while (true) {\n    std::unique_ptr<XrdMqMessage> new_msg {mqc.RecvMessage()};\n\n    if (new_msg) {\n      ++dumped;\n\n      if (!debug) {\n        std::cout << \"info: msg #\" << dumped << \" contents: \"\n                  << new_msg->GetBody() << std::endl;\n      } else {\n        std::cout << \"info: \" << dumped << \"/\" << max_dumps << \", msg size:\"\n                  << strlen(new_msg->GetBody()) << std::endl;\n      }\n    } else {\n      std::this_thread::sleep_for(std::chrono::milliseconds(ms_sleep));\n    }\n\n    // Exit after max_dumps messages\n    if (max_dumps && (dumped >= max_dumps)) {\n      exit(0);\n    }\n\n    // Exist if deadline given and expired\n    if (max_timeout && (std::chrono::steady_clock::now() > deadline)) {\n      exit(ETIME);\n    }\n  }\n}\n"
  },
  {
    "path": "test/mq/XrdMqQueueFeeder.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdMqQueueFeeder.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mq/XrdMqClient.hh\"\n#include <stdio.h>\n#include <chrono>\n\nint main(int argc, char* argv[])\n{\n  uint64_t max_feeds = 0ull;\n  uint64_t num_feeds = 0ull;\n  uint64_t ms_sleep = 0ull;\n  uint64_t msg_size = 10;\n\n  if ((argc < 2) || (argc > 5)) {\n    std::cerr << \"Usage: \" << argv[0] << \" <broker_url>/<queue> num_feeds \"\n              << \" ms_sleep_between_feeds msg_size\" << std::endl;\n    exit(-1);\n  }\n\n  if (argc >= 3) {\n    max_feeds = std::stoll(argv[2]);\n  }\n\n  if (argc >= 4) {\n    ms_sleep = strtoll(argv[3], 0, 10);\n  }\n\n  if (argc >= 5) {\n    msg_size = strtoll(argv[4], 0, 10);\n  }\n\n  XrdOucString broker = argv[1];\n\n  if (!broker.beginswith(\"root://\")) {\n    std::cerr << \"error: <borkerurl> must have the following format \"\n              << \"root://host[:port]/<queue>\" << std::endl;\n    exit(-1);\n  }\n\n  XrdMqClient mqc;\n\n  if (!mqc.AddBroker(broker.c_str())) {\n    std::cerr << \"error: failed to add broker \" << broker.c_str() << std::endl;\n    exit(-1);\n  }\n\n  XrdOucString queue = broker;\n  int apos = broker.find(\"//\");\n\n  if (apos == STR_NPOS) {\n    std::cerr << \"error: <borkerurl> must have the following format \"\n              << \"root://host[:port]/<queue>\" << std::endl;\n    exit(-1);\n  }\n\n  int bpos = broker.find(\"/\", apos + 2);\n\n  if (bpos == STR_NPOS) {\n    std::cerr << \"error: <borkerurl> must have the following format \"\n              << \"root://host[:port]/<queue>\" << std::endl;\n    exit(-1);\n  }\n\n  queue.erase(0, bpos + 1);\n  std::cout << \"info: feeding into queue: \" << queue.c_str() << std::endl;\n  mqc.SetDefaultReceiverQueue(queue.c_str());\n  XrdMqMessage message(\"HelloDumper\");\n  message.Configure(0); // Creates a logger object for the message\n  std::string body;\n  uint64_t successful_feeds = 0ull;\n\n  for (uint64_t i = 0; i < msg_size; ++i) {\n    body += \"a\";\n  }\n\n  while (true) {\n    message.NewId();\n    message.kMessageHeader.kDescription = \"Hello Dumper \";\n    message.kMessageHeader.kDescription += (int)num_feeds;\n    message.SetBody(body.c_str());\n    ++num_feeds;\n\n    if (!(mqc << message)) {\n      std::cerr << \"error: failed to send msg #\" << num_feeds  << std::endl;\n    } else {\n      std::cout << \"info: feeding msg #\" << num_feeds << std::endl;\n      ++successful_feeds;\n    }\n\n    // Exit after max_feeds messages\n    if (max_feeds && (num_feeds >= max_feeds)) {\n      std::cout << \"info: successfully sent \" << successful_feeds\n                << \"/\" << num_feeds << \" feeds\" << std::endl;\n      exit(0);\n    }\n\n    std::this_thread::sleep_for(std::chrono::milliseconds(ms_sleep));\n  }\n}\n"
  },
  {
    "path": "test/mq/XrdMqQueueInjection.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdMqQueueInjection.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#define TRACE_debug 0xffff\n#include \"mq/XrdMqClient.hh\"\n#include <stdio.h>\n\n\nint main(int argc, char* argv[])\n{\n  XrdMqClient mqc;\n\n  if ((argc < 3) || (argc > 3)) {\n    fprintf(stderr, \"usage: xrdmqinjection <brokerurl>/<queue> <injection file>\\n\");\n    exit(-1);\n  }\n\n  XrdOucString injectionfile = argv[2];\n  XrdOucString broker = argv[1];\n\n  if (!broker.beginswith(\"root://\")) {\n    fprintf(stderr,\n            \"error: <borkerurl> has to be like root://host[:port]/<queue>\\n\");\n    exit(-1);\n  }\n\n  if (!mqc.AddBroker(broker.c_str())) {\n    fprintf(stderr, \"error: failed to add broker %s\\n\", broker.c_str());\n    exit(-1);\n  }\n\n  XrdOucString queue = broker;\n  int apos = broker.find(\"//\");\n\n  if (apos == STR_NPOS) {\n    fprintf(stderr,\n            \"error: <brokerurl> has to be like root://host[:port]/<queue>\\n\");\n    exit(-1);\n  }\n\n  int bpos = broker.find(\"/\", apos + 2);\n\n  if (bpos == STR_NPOS) {\n    fprintf(stderr,\n            \"error: <brokerurl> has to be like root://host[:port]/<queue>\\n\");\n    exit(-1);\n  }\n\n  queue.erase(0, bpos + 1);\n  fprintf(stdout, \"=> feeding into %s\\n\", queue.c_str());\n  mqc.SetDefaultReceiverQueue(queue.c_str());\n  FILE* fd = fopen(injectionfile.c_str(), \"r\");\n\n  if (!fd) {\n    fprintf(stderr, \"error: unable to open injection file <%s>\\n\",\n            injectionfile.c_str());\n    exit(-1);\n  }\n\n  ssize_t linein = 0;\n  char* lineptr = (char*) malloc(4096);\n  size_t lineptr_n = 4096;\n  int injected = 0;\n\n  while ((linein = getline(&lineptr, &lineptr_n, fd)) > 0) {\n    fprintf(stdout, \"< %s >\\n\", lineptr);\n    XrdMqMessage message(\"Injection\");\n    message.Configure(0);\n    XrdOucString body = lineptr;\n    body.erase(body.length() - 1);\n    message.NewId();\n    message.MarkAsMonitor();\n    message.kMessageHeader.kDescription = \"Monitor Injection\";\n    message.SetBody(body.c_str());\n\n    if (!(mqc << message)) {\n      fprintf(stderr, \"error: failed to send message\\n\");\n    } else {\n      ++injected;\n    }\n  }\n\n  fprintf(stdout, \"info: injected %i messages\\n\", injected);\n}\n"
  },
  {
    "path": "test/mq/XrdMqSharedObjectBroadCastClient.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdMqSharedObjectBroadCastClient.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#define TRACE_debug 0xffff\n#include \"mq/XrdMqTiming.hh\"\n#include \"mq/XrdMqMessaging.hh\"\n#include \"mq/XrdMqSharedObject.hh\"\n#include <XrdSys/XrdSysLogger.hh>\n#include <stdio.h>\n#include <thread>\n\nint main(int argc, char* argv[])\n{\n  int nhash = 1;\n  XrdMqMessage::Configure(\"\");\n\n  if (argc != 2) {\n    exit(-1);\n  }\n\n  std::string hostname = argv[1];\n  XrdOucString myid = \"root://lxbra0301.cern.ch:1097//eos/\";\n  myid += argv[1];\n  myid += \"/worker\";\n  XrdMqSharedObjectManager ObjectManager;\n  ObjectManager.SetDebug(true);\n  XrdMqMessage message(\"MasterMessage\");\n  XrdMqMessaging messaging(myid.c_str(), \"/eos/*/worker\", false, false,\n                           &ObjectManager);\n  messaging.StartListenerThread();\n  XrdMqTiming mq(\"send\");\n\n  for (int i = 0; i < nhash; i++) {\n    XrdOucString str = \"statistics\";\n    str += i;\n    ObjectManager.CreateSharedHash(str.c_str(), \"/eos/*/worker\");\n  }\n\n  TIMING(\"START\", &mq);\n\n  for (int i = 0; i < 10000; i++) {\n    ObjectManager.HashMutex.LockRead();\n\n    for (int v = 0; v < nhash; v++) {\n      XrdOucString str = \"statistics\";\n      str += v;\n      XrdMqSharedHash* hash = ObjectManager.GetHash(str.c_str());\n      hash->BroadcastRequest(\"/eos/*/worker\");\n    }\n\n    ObjectManager.HashMutex.UnLockRead();\n    std::this_thread::sleep_for(std::chrono::seconds(1));\n\n    for (int v = 0; v < nhash; v++) {\n      XrdOucString str = \"statistics\";\n      str += v;\n      XrdMqSharedHash* hash = ObjectManager.GetHash(str.c_str());\n      XrdOucString out;\n      out += \"---------------------------\\n\";\n      out += \"subject=\";\n      out += str.c_str();\n      out += \"\\n\";\n      hash->Dump(out);\n      printf(\"%s\", out.c_str());\n    }\n\n    std::this_thread::sleep_for(std::chrono::seconds(1));\n  }\n\n  TIMING(\"SEND+RECV\", &mq);\n  mq.Print();\n}\n"
  },
  {
    "path": "test/mq/XrdMqSharedObjectClient.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdMqSharedObjectClient.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#define TRACE_debug 0xffff\n#include \"common/utils/RandUtils.hh\"\n#include \"mq/XrdMqClient.hh\"\n#include \"mq/XrdMqMessaging.hh\"\n#include \"mq/XrdMqSharedObject.hh\"\n#include \"mq/XrdMqTiming.hh\"\n#include <XrdSys/XrdSysLogger.hh>\n#include <stdio.h>\n\nint main(int argc, char* argv[])\n{\n  int nhash = 1;\n  XrdMqMessage::Configure(\"\");\n\n  if (argc != 2) {\n    exit(-1);\n  }\n\n  std::string hostname = argv[1];\n  XrdOucString myid = \"root://lxbra0301.cern.ch:1097//eos/\";\n  myid += argv[1];\n  myid += \"/worker\";\n  XrdMqSharedObjectManager ObjectManager;\n  ObjectManager.SetDebug(true);\n  XrdMqMessage message(\"MasterMessage\");\n  XrdMqMessaging messaging(myid.c_str(), \"/eos/*/worker\", false, false,\n                           &ObjectManager);\n  messaging.StartListenerThread();\n  XrdMqTiming mq(\"send\");\n\n  for (int i = 0; i < nhash; i++) {\n    XrdOucString str = \"statistics\";\n    str += i;\n    ObjectManager.CreateSharedHash(str.c_str(), \"/eos/*/worker\");\n  }\n\n  TIMING(\"START\", &mq);\n\n  for (int i = 0; i < 10000; i++) {\n    ObjectManager.HashMutex.LockRead();\n\n    for (int v = 0; v < nhash; v++) {\n      XrdOucString str = \"statistics\";\n      str += v;\n      XrdMqSharedHash* hash = ObjectManager.GetObject(str.c_str(), \"hash\");\n\n      if (!hash) {\n        std::cerr << \"Error: Unable to get hash object!\" << std::endl;\n        continue;\n      }\n\n      hash->OpenTransaction();\n\n      for (int j = 0; j < 50; j++) {\n        XrdOucString var = \"var\";\n        var += j;\n        unsigned long long r = random();\n        fprintf(stderr, \"Set %s %s %llu\\n\", str.c_str(), var.c_str(), r);\n        hash->Set(var.c_str(), r);\n      }\n\n      hash->Set(\"hostname\", hostname.c_str());\n\n      if (!eos::common::getRandom(0, 9)) {\n        //      fprintf(stderr,\"Clearing Hash!\\n\");\n        hash->Clear();\n      }\n\n      hash->CloseTransaction();\n      XrdOucString out;\n      out += \"---------------------------\\n\";\n      out += \"subject=\";\n      out += str.c_str();\n      out += \"\\n\";\n    }\n\n    ObjectManager.HashMutex.UnLockRead();\n    usleep(5000000);\n  }\n\n  TIMING(\"SEND+RECV\", &mq);\n  mq.Print();\n}\n"
  },
  {
    "path": "test/mq/XrdMqSharedObjectQueueClient.cc",
    "content": "// ----------------------------------------------------------------------\n// File: XrdMqSharedObjectQueueClient.cc\n// Author: Andreas-Joachim Peters - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#define TRACE_debug 0xffff\n#include \"mq/XrdMqClient.hh\"\n#include \"mq/XrdMqTiming.hh\"\n#include \"mq/XrdMqMessaging.hh\"\n#include \"mq/XrdMqSharedObject.hh\"\n#include <XrdSys/XrdSysLogger.hh>\n#include <stdio.h>\n\nint main(int argc, char* argv[])\n{\n  int nhash = 1;\n  XrdMqMessage::Configure(\"\");\n\n  if (argc != 2) {\n    exit(-1);\n  }\n\n  std::string hostname = argv[1];\n  XrdOucString myid = \"root://lxbra0301.cern.ch:1097//eos/\";\n  myid += argv[1];\n  myid += \"/worker\";\n  XrdMqSharedObjectManager ObjectManager;\n  ObjectManager.SetDebug(true);\n  XrdMqMessage message(\"MasterMessage\");\n  XrdMqMessaging messaging(myid.c_str(), \"/eos/*/worker\", false, false,\n                           &ObjectManager);\n  messaging.StartListenerThread();\n  XrdMqTiming mq(\"send\");\n\n  for (int i = 0; i < nhash; i++) {\n    XrdOucString str = \"statistics\";\n    str += i;\n    ObjectManager.CreateSharedObject(str.c_str(), \"/eos/*/worker\", \"queue\");\n  }\n\n  TIMING(\"START\", &mq);\n\n  for (int i = 0; i < 10000; i++) {\n    ObjectManager.HashMutex.LockRead();\n\n    for (int v = 0; v < nhash; v++) {\n      XrdOucString str = \"statistics\";\n      str += v;\n      XrdMqSharedQueue* queue =  dynamic_cast<XrdMqSharedQueue*>\n                                 (ObjectManager.GetObject(str.c_str(), \"queue\"));\n\n      if (!queue) {\n        fprintf(stderr, \"error: queue get failed\\n\");\n        exit(-1);\n      }\n\n      if (i == 0) {\n        queue->BroadcastRequest(\"/eos/*/worker\");\n        sleep(3);\n      }\n\n      XrdOucString var = \"var\";\n      var += v;\n      queue->OpenTransaction();\n      queue->PushBack(\"\", var.c_str());\n      queue->CloseTransaction();\n      XrdOucString out;\n      out += \"---------------------------\\n\";\n      out += \"subject=\";\n      out += str.c_str();\n      out += \"\\n\";\n      queue->Dump(out);\n      printf(\"%s\", out.c_str());\n      printf(\"QUEUE [%d]: \\n\", (int)queue->GetSize());\n      std::vector<std::string> keys = queue->GetKeys();\n\n      for (auto it = keys.begin(); it != keys.end(); ++it) {\n        printf(\"%s \", it->c_str());\n      }\n\n      printf(\"\\n\");\n\n      if (!(i %= 10)) {\n        fprintf(stderr, \"==>clearing queue\\n\");\n        queue->Clear();\n      }\n    }\n\n    ObjectManager.HashMutex.UnLockRead();\n    usleep(1000000);\n  }\n\n  TIMING(\"SEND+RECV\", &mq);\n  mq.Print();\n}\n"
  },
  {
    "path": "test/test-eos-iam-mapfile.py",
    "content": "#!/usr/bin/python3\n# ----------------------------------------------------------------------\n# File: test-eos-iam-mapfile.py\n# Author: Manuel Reis - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2021 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nfrom unittest import TestCase\nfrom importlib.machinery import SourceFileLoader\nimport types\nloader = SourceFileLoader('eos-iam-mapfile', '/usr/sbin/eos-iam-mapfile')\niam = types.ModuleType(loader.name)\nloader.exec_module(iam)\n\n# Disable iam logging\niam.logging.disable(iam.logging.WARNING)\n\ndef make_user(id='123',fname='dwight',lname='shrutte', role='sales', cert=None, fancyCN=False):\n    ret = {\n        'id': f'{id}',\n        'meta': {\n            'created': '2022-01-01T01:01:01.000+01:00',\n            'lastModified': '2022-01-22T01:01:47.000+01:00',\n            'location': f'https://example/scim/Users/{id}',\n            'resourceType': 'User'\n        },\n        'schemas': [\n            'urn:ietf:params:scim:schemas:core:2.0:User',\n            'urn:indigo-dc:scim:schemas:IndigoUser'\n        ],\n        'userName': f'{fname.lower()[0]}{lname.lower()}',\n        'name': {\n            'familyName': lname.upper(),\n            'formatted': f'{fname.upper()} {lname.upper()}',\n            'givenName': lname.upper()\n        },\n        'displayName': f'{fname.upper()[0]}{fname.lower()[1:]} {lname.upper()[0]}{lname.lower()[1:]}',\n        'active': True,\n        'emails': [\n            {\n                'type': 'work',\n                'value': f'{fname.lower()[0]}{lname.lower()}@example.com',\n                'primary': True\n                }\n        ],\n        'groups': [\n            {\n                'display': role,\n                'value': f'{id}',\n                '$ref': f'https://example.com/scim/Groups/{id}'\n            }\n        ]\n    }\n    if cert is not None:\n        dn = \"DC=dunder,DC=us\"\n        issuer = \"CN=Dunder Auth,C=us\"\n        if cert.lower() == 'cern':\n            dn = \"DC=cern,DC=ch\"\n            issuer=\"CN=CERN Grid Certification Authority,DC=cern,DC=ch\"\n\n        if cert.lower() == 'upper':\n            dn = \"DC=cern,DC=ch\".upper()\n            issuer=\"CN=CERN Grid Certification Authority,DC=cern,DC=ch\"\n        elif cert.lower() == 'lower':\n            dn = dn.lower()\n            issuer=\"CN=CERN Grid Certification Authority,DC=cern,DC=ch\"\n\n        if fancyCN:\n            dn = f\"CN={ret['displayName'].replace(' ',', ')}, {ret['emails'][0]['value']},OU={role[0].upper()}{role[1:]},{dn}\"\n            print(dn)\n        else:\n            dn = f\"CN={ret['displayName']},OU={role[0].upper()}{role[1:]},{dn}\"\n\n        ret[\"urn:indigo-dc:scim:schemas:IndigoUser\"] = {\n            \"oidcIds\": [\n                {\n                    \"issuer\": \"https:/example.com/auth/realms/cern\",\n                    \"subject\": ret[\"userName\"]\n                }\n            ],\n            \"certificates\": [\n                {\n                    \"primary\": True,\n                    \"subjectDn\": dn,\n                    \"issuerDn\": issuer,\n                    \"pemEncodedCertificate\": \"-----BEGIN CERTIFICATE-----\\n THIS IS A VALID VERTIFICATE!?==\\n-----END CERTIFICATE-----\",\n                    \"display\": \"cert-0\",\n                    \"created\": \"2022-01-21T17:32:31.000+01:00\",\n                    \"lastModified\": \"2022-01-21T17:32:31.000+01:00\",\n                    \"hasProxyCertificate\": False\n                }\n            ]\n        }\n\n    return ret\n\n\nclass FiltersTestCase(TestCase):\n\n    def test_dn_filter(self):\n        dwight  = make_user()\n        jim     = make_user(id='23123123-2312-2312-2312-231231231231',fname='jim',lname='Halpert', role='sales',cert='dunder')\n        pam     = make_user(id='31231231-3123-3123-3123-312312312312',fname='Pam',lname='Dawber',role=\"reception\",cert='cern')\n        kevin   = make_user(id='41231231-3123-3123-3123-312312312312', fname='Kevin', lname='Malone', role='accounting', cert='upper')\n        creed   = make_user(id='51231231-3123-3123-3123-312312312312', fname='Creed', lname='Bratton', role='accounting', cert='lower')\n        michael = make_user(id='61231231-3123-3123-3123-312312312312',fname='Michael',lname='Scott',role=\"management\",cert='cern')\n\n        pamSubDN='/DC=ch/DC=cern/OU=Reception/CN=Pam Dawber'\n        jimSubDN='/DC=us/DC=dunder/OU=Sales/CN=Jim Halpert'\n        kevinSubDN='/DC=CH/DC=CERN/OU=Accounting/CN=Kevin Malone'\n        creedSubDN='/dc=us/dc=dunder/OU=Accounting/CN=Creed Bratton'\n        michaelSubDN='/DC=ch/DC=cern/OU=Management/CN=Michael Scott'\n\n        # Only Dwight doesn't have a certificate\n        assert iam.dn_filter(dwight,pam,jim,kevin,creed,michael) == {pamSubDN,jimSubDN,kevinSubDN,creedSubDN,michaelSubDN}\n\n        # Pam , Michael and Kevin certificates were issued by CERN but Kevin's DN is lowcase\n        assert iam.dn_filter(dwight,pam,jim,kevin,creed,michael, prefer_cern=True) == {pamSubDN,michaelSubDN}\n\n        import re\n        p = re.compile(r'Pam')\n        assert iam.dn_filter(dwight,pam,jim,kevin,creed,michael, prefer_cern=True, pattern=p) == {pamSubDN}\n\n        import re\n        p = re.compile(r'michael', flags=re.IGNORECASE)\n        assert iam.dn_filter(dwight,pam,jim,kevin,creed,michael, prefer_cern=True, pattern=p) == {michaelSubDN}\n\n    def test_dn_filter_with_commas_CN(self):\n        # Test commas on the CN\n        angela  = make_user(id='71231231-3123-3123-3123-312312312312',fname='Angela',lname='Kinsey', role='accounting',cert='cern', fancyCN=True)\n        angelaSubDn='/DC=ch/DC=cern/OU=Accounting/CN=Angela, Kinsey, akinsey@example.com'\n        assert iam.dn_filter(angela) == {angelaSubDn}\n\n    def test_name_map_filter(self):\n        user1 = make_user(id='123')\n        user2 = make_user(id=1234)\n        user3 = make_user(id=12345)\n        user4 = make_user(id=123456)\n        user5 = {'ID':321} # not valid\n\n        assert iam.name_map_filter(user1, user2, user3, user4, user5) == {'123','1234','12345','123456'}\n\n        assert iam.name_map_filter(user5) == set()\n"
  },
  {
    "path": "test/xrdstress",
    "content": "#!/bin/bash\n\n# ----------------------------------------------------------------------\n# File: xrdstress\n# Author: Elvin-Alin Sindrilaru - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n# created this script file to set the \n# XRD_ENABLEFORKHANDLERS before calling the c++ program\n# @path is the location of the xrdstress c++ executable\n# Elvin-Alin Sindrilaru - CERN 2011\n\nexport XRD_ENABLEFORKHANDLERS=1\nexec /usr/sbin/xrdstress.exe $@\n"
  },
  {
    "path": "test.cmake",
    "content": "cmake_minimum_required(VERSION 3.16...3.31 FATAL_ERROR)\n\nset(ENV{LANG} \"C\")\nset(ENV{LC_ALL} \"C\")\nset(CTEST_USE_LAUNCHERS TRUE)\n\nif(EXISTS \"/etc/os-release\")\n  file(STRINGS \"/etc/os-release\" OS_NAME REGEX \"^ID=.*$\")\n  string(REGEX REPLACE \"ID=[\\\"']?([^\\\"']*)[\\\"']?$\" \"\\\\1\" OS_NAME \"${OS_NAME}\")\n  file(STRINGS \"/etc/os-release\" OS_VERSION REGEX \"^VERSION_ID=.*$\")\n  string(REGEX REPLACE \"VERSION_ID=[\\\"']?([^\\\"'.]*).*$\" \"\\\\1\" OS_VERSION \"${OS_VERSION}\")\n  file(STRINGS \"/etc/os-release\" OS_FULL_NAME REGEX \"^PRETTY_NAME=.*$\")\n  string(REGEX REPLACE \"PRETTY_NAME=[\\\"']?([^\\\"']*)[\\\"']?$\" \"\\\\1\" OS_FULL_NAME \"${OS_FULL_NAME}\")\n  string(REGEX REPLACE \"[ ]*\\\\(.*\\\\)\" \"\" OS_FULL_NAME \"${OS_FULL_NAME}\")\nelseif(APPLE)\n  set(OS_NAME \"macOS\")\n  execute_process(COMMAND sw_vers -productVersion\n    OUTPUT_VARIABLE OS_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)\n  set(OS_FULL_NAME \"${OS_NAME} ${OS_VERSION}\")\nelse()\n  cmake_host_system_information(RESULT OS_NAME QUERY OS_NAME)\n  cmake_host_system_information(RESULT OS_VERSION QUERY OS_VERSION)\n  set(OS_FULL_NAME \"${OS_NAME} ${OS_VERSION}\")\nendif()\n\ncmake_host_system_information(RESULT\n  NCORES QUERY NUMBER_OF_PHYSICAL_CORES)\ncmake_host_system_information(RESULT\n  NTHREADS QUERY NUMBER_OF_LOGICAL_CORES)\n\nif(NOT DEFINED ENV{CMAKE_BUILD_PARALLEL_LEVEL})\n  set(ENV{CMAKE_BUILD_PARALLEL_LEVEL} ${NTHREADS})\nendif()\n\nif(NOT DEFINED ENV{CTEST_PARALLEL_LEVEL})\n  set(ENV{CTEST_PARALLEL_LEVEL} ${NCORES})\nendif()\n\nif(NOT DEFINED CTEST_CONFIGURATION_TYPE)\n  if(DEFINED ENV{CMAKE_BUILD_TYPE})\n    set(CTEST_CONFIGURATION_TYPE $ENV{CMAKE_BUILD_TYPE})\n  else()\n    set(CTEST_CONFIGURATION_TYPE RelWithDebInfo)\n  endif()\nendif()\n\nexecute_process(COMMAND ${CMAKE_COMMAND} --system-information\n  OUTPUT_VARIABLE CMAKE_SYSTEM_INFORMATION ERROR_VARIABLE ERROR)\n\nif(ERROR)\n  message(FATAL_ERROR \"Cannot detect system information\")\nendif()\n\nstring(REGEX REPLACE \".+CMAKE_CXX_COMPILER_ID \\\"([-0-9A-Za-z ]+)\\\".*$\" \"\\\\1\"\n  COMPILER_ID \"${CMAKE_SYSTEM_INFORMATION}\")\nstring(REPLACE \"GNU\" \"GCC\" COMPILER_ID \"${COMPILER_ID}\")\n\nstring(REGEX REPLACE \".+CMAKE_CXX_COMPILER_VERSION \\\"([^\\\"]+)\\\".*$\" \"\\\\1\"\n  COMPILER_VERSION \"${CMAKE_SYSTEM_INFORMATION}\")\n\nset(CTEST_BUILD_NAME \"${OS_FULL_NAME}\")\nstring(APPEND CTEST_BUILD_NAME \" ${COMPILER_ID} ${COMPILER_VERSION}\")\nstring(APPEND CTEST_BUILD_NAME \" ${CTEST_CONFIGURATION_TYPE}\")\n\nif(DEFINED ENV{CMAKE_GENERATOR})\n  set(CTEST_CMAKE_GENERATOR $ENV{CMAKE_GENERATOR})\nelse()\n  string(REGEX REPLACE \".+CMAKE_GENERATOR \\\"([-0-9A-Za-z ]+)\\\".*$\" \"\\\\1\"\n    CTEST_CMAKE_GENERATOR \"${CMAKE_SYSTEM_INFORMATION}\")\nendif()\n\nif(NOT CTEST_CMAKE_GENERATOR MATCHES \"Makefile\")\n  string(APPEND CTEST_BUILD_NAME \" ${CTEST_CMAKE_GENERATOR}\")\nendif()\n\nif(NOT CMAKE_SYSTEM_PROCESSOR MATCHES \"x86_64\")\n  string(APPEND CTEST_BUILD_NAME \" ${CMAKE_SYSTEM_PROCESSOR}\")\nendif()\n\nif(DEFINED ENV{GITHUB_ACTIONS})\n  set(CTEST_SITE \"GitHub Actions ($ENV{GITHUB_REPOSITORY_OWNER})\")\n\n  if(\"$ENV{GITHUB_REPOSITORY_OWNER}\" STREQUAL \"cern-eos\")\n    set(CDASH TRUE)\n    set(MODEL \"Continuous\")\n  endif()\n\n  if(\"$ENV{GITHUB_EVENT_NAME}\" MATCHES \"pull_request\")\n    set(GROUP \"Pull Requests\")\n    set(ENV{BASE_REF} $ENV{GITHUB_SHA}^1)\n    set(ENV{HEAD_REF} $ENV{GITHUB_SHA}^2)\n    string(REGEX REPLACE \"/merge\" \"\" PR_NUMBER \"$ENV{GITHUB_REF_NAME}\")\n    string(PREPEND CTEST_BUILD_NAME \"#${PR_NUMBER} ($ENV{GITHUB_ACTOR}) \")\n  else()\n    set(ENV{HEAD_REF} $ENV{GITHUB_SHA})\n    string(APPEND CTEST_BUILD_NAME \" ($ENV{GITHUB_REF_NAME})\")\n  endif()\n\n  if(\"$ENV{GITHUB_RUN_ATTEMPT}\" GREATER 1)\n    string(APPEND CTEST_BUILD_NAME \" #$ENV{GITHUB_RUN_ATTEMPT}\")\n  endif()\n\n  macro(section title)\n      message(\"::group::${title}\")\n  endmacro()\n\n  macro(endsection)\n      message(\"::endgroup::\")\n  endmacro()\nelse()\n  macro(section title)\n  endmacro()\n  macro(endsection)\n  endmacro()\nendif()\n\nif(NOT DEFINED CTEST_SITE)\n  site_name(CTEST_SITE)\nendif()\n\nif(NOT DEFINED CTEST_SOURCE_DIRECTORY)\n  set(CTEST_SOURCE_DIRECTORY \"${CMAKE_CURRENT_LIST_DIR}\")\nendif()\n\nif(NOT DEFINED CTEST_BINARY_DIRECTORY)\n  get_filename_component(CTEST_BINARY_DIRECTORY \"build\" REALPATH)\nendif()\n\nif(NOT DEFINED MODEL)\n  if(DEFINED CTEST_SCRIPT_ARG)\n    set(MODEL ${CTEST_SCRIPT_ARG})\n  else()\n    set(MODEL Experimental)\n  endif()\nendif()\n\nif(NOT DEFINED GROUP)\n  set(GROUP ${MODEL})\nendif()\n\nset(CMAKE_ARGS $ENV{CMAKE_ARGS} ${CMAKE_ARGS})\n\nif(COVERAGE)\n  find_program(CTEST_COVERAGE_COMMAND NAMES gcov)\n  set(COVERAGE_FLAGS \"--coverage -fprofile-update=atomic -DENABLE_COVERAGE\")\n  list(PREPEND CMAKE_ARGS \"-DCMAKE_C_FLAGS=${COVERAGE_FLAGS}\")\n  list(PREPEND CMAKE_ARGS \"-DCMAKE_CXX_FLAGS=${COVERAGE_FLAGS}\")\nendif()\n\nif(MEMCHECK)\n  find_program(CTEST_MEMORYCHECK_COMMAND NAMES valgrind)\nendif()\n\nif(STATIC_ANALYSIS)\n  find_program(CMAKE_CXX_CLANG_TIDY NAMES clang-tidy)\n  list(PREPEND CMAKE_ARGS \"-DCMAKE_CXX_CLANG_TIDY=${CMAKE_CXX_CLANG_TIDY}\")\nendif()\n\nforeach(FILENAME ${OS_NAME}${OS_VERSION}.cmake ${OS_NAME}.cmake config.cmake)\n  if(EXISTS \"${CTEST_SOURCE_DIRECTORY}/.ctest/${FILENAME}\")\n    message(STATUS \"Using CMake cache file ${FILENAME}\")\n    list(PREPEND CMAKE_ARGS -C ${CTEST_SOURCE_DIRECTORY}/.ctest/${FILENAME})\n    list(APPEND CTEST_NOTES_FILES ${CTEST_SOURCE_DIRECTORY}/.ctest/${FILENAME})\n    break()\n  endif()\nendforeach()\n\nif(IS_DIRECTORY \"${CTEST_BINARY_DIRECTORY}\")\n  ctest_empty_binary_directory(\"${CTEST_BINARY_DIRECTORY}\")\nendif()\n\nctest_read_custom_files(\"${CTEST_SOURCE_DIRECTORY}\")\n\nif(IS_DIRECTORY ${CTEST_SOURCE_DIRECTORY}/.git)\n  find_program(CTEST_GIT_COMMAND NAMES git)\nendif()\n\nif(EXISTS \"${CTEST_GIT_COMMAND}\" AND DEFINED ENV{HEAD_REF} AND NOT DEFINED ENV{BASE_REF})\n  set(CTEST_CHECKOUT_COMMAND\n    \"${CTEST_GIT_COMMAND} --git-dir ${CTEST_SOURCE_DIRECTORY}/.git checkout -f $ENV{HEAD_REF}\")\nendif()\n\nctest_start(${MODEL} GROUP \"${GROUP}\")\n\nif(EXISTS \"${CTEST_GIT_COMMAND}\")\n  if(DEFINED ENV{BASE_REF})\n    execute_process(COMMAND ${CTEST_GIT_COMMAND} checkout -f $ENV{BASE_REF}\n      WORKING_DIRECTORY ${CTEST_SOURCE_DIRECTORY} ERROR_QUIET RESULT_VARIABLE GIT_STATUS)\n    if(NOT ${GIT_STATUS} EQUAL 0)\n      message(FATAL_ERROR \"Could not checkout base ref: $ENV{BASE_REF}\")\n    endif()\n  endif()\n  if(DEFINED ENV{HEAD_REF})\n    set(CTEST_GIT_UPDATE_CUSTOM\n      ${CTEST_GIT_COMMAND} --git-dir ${CTEST_SOURCE_DIRECTORY}/.git checkout -f $ENV{HEAD_REF})\n  else()\n    set(CTEST_GIT_UPDATE_CUSTOM ${CTEST_GIT_COMMAND} --git-dir ${CTEST_SOURCE_DIRECTORY}/.git diff)\n  endif()\n\n  ctest_update()\nendif()\n\nsection(\"Configure\")\nctest_configure(OPTIONS \"${CMAKE_ARGS}\" RETURN_VALUE CONFIG_RESULT)\n\nctest_read_custom_files(\"${CTEST_BINARY_DIRECTORY}\")\nlist(APPEND CTEST_NOTES_FILES ${CTEST_BINARY_DIRECTORY}/CMakeCache.txt)\n\nif(NOT ${CONFIG_RESULT} EQUAL 0)\n  if(CDASH OR (DEFINED ENV{CDASH} AND \"$ENV{CDASH}\"))\n    ctest_submit()\n  endif()\n  message(FATAL_ERROR \"Configuration failed\")\nendif()\nendsection()\n\nsection(\"Build\")\nctest_build(RETURN_VALUE BUILD_RESULT)\n\nif(NOT ${BUILD_RESULT} EQUAL 0)\n  if(CDASH OR (DEFINED ENV{CDASH} AND \"$ENV{CDASH}\"))\n    ctest_submit()\n  endif()\n  message(FATAL_ERROR \"Build failed\")\nendif()\n\nif(INSTALL)\n  set(ENV{DESTDIR} \"${CTEST_BINARY_DIRECTORY}/install\")\n  ctest_build(TARGET install)\nendif()\nendsection()\n\nsection(\"Test\")\nctest_test(PARALLEL_LEVEL $ENV{CTEST_PARALLEL_LEVEL} RETURN_VALUE TEST_RESULT)\n\nif(NOT ${TEST_RESULT} EQUAL 0)\n  message(SEND_ERROR \"Tests failed\")\nendif()\nendsection()\n\nif(DEFINED CTEST_COVERAGE_COMMAND)\n  section(\"Coverage\")\n  find_program(GCOVR NAMES gcovr)\n  if(EXISTS ${GCOVR})\n    execute_process(COMMAND\n      ${GCOVR} --gcov-executable ${CTEST_COVERAGE_COMMAND}\n        -r ${CTEST_SOURCE_DIRECTORY} ${CTEST_BINARY_DIRECTORY}\n        --html-details ${CTEST_BINARY_DIRECTORY}/coverage/ ERROR_VARIABLE ERROR)\n    if(ERROR)\n      message(SEND_ERROR \"Failed to generate coverage report\")\n    endif()\n  endif()\n  ctest_coverage()\n  endsection()\nendif()\n\nif(DEFINED CTEST_MEMORYCHECK_COMMAND)\n  section(\"Memcheck\")\n  ctest_memcheck()\n  endsection()\nendif()\n\nif(CDASH OR (DEFINED ENV{CDASH} AND \"$ENV{CDASH}\"))\n  ctest_submit()\nendif()\n"
  },
  {
    "path": "unit_tests/CMakeLists.txt",
    "content": "#-------------------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Jozsef Makai <jmakai@cern.ch> CERN\n#-------------------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2017 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\ninclude_directories(\n  ${CMAKE_BINARY_DIR}\n  ${CMAKE_SOURCE_DIR}/common/\n  ${CMAKE_SOURCE_DIR}/namespace/ns_quarkdb/\n  ${CMAKE_SOURCE_DIR}/namespace/ns_quarkdb/qclient/include\n  ${CMAKE_BINARY_DIR}/namespace/ns_quarkdb)\n\nset(EOS_UT_RPATH\n  \"${CMAKE_BINARY_DIR}/fst/;${CMAKE_BINARY_DIR}/mgm/;\"\n  \"${CMAKE_BINARY_DIR}/common/;${CMAKE_BINARY_DIR}/mq/;\"\n  \"${CMAKE_BINARY_DIR}/auth_plugin/;${CMAKE_BINARY_DIR}/namespace/ns_quarkdb/;\"\n  \"${CMAKE_BINARY_DIR}/namespace/;${CMAKE_BINARY_DIR}/proto/;\")\n\nset(CONSOLE_UT_SRCS\n  console/AclCmdTest.cc\n  console/ConsoleCompletionTest.cc\n  console/ParseCommentTest.cc\n  console/RegexUtilTest.cc\n  console/ConsoleUtilTests.cc\n  console/CmdsTests.cc)\n\nset(MGM_UT_SRCS\n  mgm/AccessTests.cc\n  mgm/AclCmdTests.cc\n  mgm/ConversionInfoTests.cc\n  mgm/EgroupTests.cc\n  mgm/FileSystemRegistryTests.cc\n  mgm/FsViewTests.cc\n  mgm/HttpTests.cc\n  mgm/IostatTests.cc\n  mgm/LockTrackerTests.cc\n  mgm/LRUTests.cc\n  mgm/ProcFsTests.cc\n  mgm/RoutingTests.cc\n  mgm/IdTrackerTests.cc\n  mgm/FsckEntryTests.cc\n  mgm/FusexCastBatchTests.cc\n  mgm/CapsTests.cc\n  mgm/CommitHelperTests.cc\n  mgm/QuarkDBConfigTests.cc\n  mgm/groupbalancer/BalancerEngineTypeTests.cc\n  mgm/groupbalancer/FreeSpaceBalancerTests.cc\n  mgm/groupbalancer/StdDevBalancerEngineTests.cc\n  mgm/groupbalancer/StdDrainerTests.cc\n  mgm/groupbalancer/MinMaxBalancerEngineTests.cc\n  mgm/groupbalancer/GroupBalancerUtilsTests.cc\n  mgm/groupbalancer/GroupsInfoFetcherTests.cc\n  mgm/groupdrainer/GroupDrainerRetry.cc\n  mgm/groupdrainer/GroupDrainerTests.cc\n  mgm/groupdrainer/DrainProgressTrackerTests.cc\n  mgm/tgc/CachedValueTests.cc\n  mgm/tgc/FreedBytesHistogramTests.cc\n  mgm/tgc/LruTests.cc\n  mgm/tgc/MultiSpaceTapeGcTests.cc\n  mgm/tgc/SmartSpaceStatsTests.cc\n  mgm/tgc/SpaceToTapeGcMapTests.cc\n  mgm/tgc/TapeGcTests.cc\n  mgm/CtaUtilsTests.cc\n  mgm/XrdMgmOfsTests.cc\n  mgm/XrdMgmOfsFileTests.cc\n  mgm/bulk-request/PrepareManagerTest.cc\n  mgm/bulk-request/BulkRequestPrepareManagerTest.cc\n  mgm/bulk-request/MockPrepareMgmFSInterface.cc\n  mgm/http/HttpServerTests.cc\n  mgm/http/rest-api/tape/RestApiTest.cc\n  mgm/http/rest-api/tape/JsonCPPTapeModelBuilderTest.cc\n  mgm/utils/AttrHelperTests.cc\n  mgm/PolicyTests.cc\n  mgm/RecycleTests.cc\n  mgm/RecyclePolicyTests.cc\n  mgm/placement/ClusterMapTests.cc\n  mgm/placement/RRSeedTests.cc\n  mgm/placement/SchedulerTests.cc\n  mgm/placement/ThreadLocalRRSeedTests.cc\n  mgm/placement/FsSchedulerTests.cc\n  mgm/placement/PlacementStrategyTests.cc)\n\nset(COMMON_UT_SRCS\n  common/UtilsTests.cc\n  common/FileIdTests.cc\n  common/FileMapTests.cc\n  common/FutureWrapperTests.cc\n  common/InodeTests.cc\n  common/LoggingTests.cc\n  common/LoggingTestsUtils.cc\n  common/AuditTests.cc\n  common/MappingTests.cc\n  common/PathTests.cc\n  common/GlobTests.cc\n  common/RWMutexTest.cc\n  common/RegexWrapperTests.cc\n  common/StringConversionTests.cc\n  common/StringTokenizerTests.cc\n  common/StringSplitTests.cc\n  common/StringUtilsTests.cc\n  common/SymKeysTests.cc\n  common/ThreadPoolTest.cc\n  common/TimingTests.cc\n  common/VariousTests.cc\n  common/XrdConnPoolTests.cc\n  common/RateLimitTests.cc\n  common/EosTokenTests.cc\n  common/UriCapCipherTests.cc\n  common/ConfigTests.cc\n  common/BufferManagerTests.cc\n  common/ConcurrentQueueTests.cc\n  common/ContainerUtilsTests.cc\n  common/ObserverMgrTests.cc\n  common/ConfigStoreTests.cc\n  common/async/OpaqueFutureTests.cc\n  common/async/ExecutorMgrTests.cc\n  common/CounterTests.cc\n  common/ShardedCacheTests.cc\n  common/concurrency/AlignedAtomicArrayTests.cc\n  common/concurrency/AtomicUniquePtrTests.cc\n  common/concurrency/ThreadEpochCounterTests.cc\n  common/concurrency/RCUTests.cc\n  common/RandTests.cc\n  common/SciTokensTests.cc\n  common/WebNotifyTests.cc\n)\n\nset(FUSEX_UT_SRCS\n  fusex/StatTests.cc\n  ${CMAKE_SOURCE_DIR}/fusex/stat/Stat.cc # We'll consider linking directly to fusex later\n)\n\nset(FST_UT_SRCS\n  fst/XrdFstOfsTests.cc\n  fst/XrdFstOssFileTest.cc\n  fst/HealthTest.cc\n  fst/UtilsTest.cc\n  fst/XrdFstOfsFileInternalTest.cc\n  fst/ScanDirTests.cc\n  ${CMAKE_SOURCE_DIR}/fst/ScanDir.cc # To pass on the NOOFS flag explicitly\n  fst/LoadTests.cc\n  fst/MonitorVarPartitionTest.cc\n  fst/ResponseCollectorTests.cc\n  fst/WalkDirTreeTests.cc\n  fst/HttpHandlerFstFileCacheTests.cc\n  fst/NfsIoTests.cc)\n\n#-------------------------------------------------------------------------------\n# unit tests source files\n#-------------------------------------------------------------------------------\nset(UT_SRCS\n  ${CONSOLE_UT_SRCS}\n  ${MGM_UT_SRCS}\n  ${COMMON_UT_SRCS}\n  ${FUSEX_UT_SRCS})\n\nset(UT_FST_SRCS\n  ${FST_UT_SRCS})\n\n#-------------------------------------------------------------------------------\n# eos-unit-tests executable\n#-------------------------------------------------------------------------------\nadd_executable(eos-unit-tests ${UT_SRCS})\n\ntarget_link_libraries(eos-unit-tests PRIVATE\n  EosConsoleHelpers-Objects\n  EosConsoleCommands-Objects\n  EosSciToken-Objects\n  EosMgmHttp-Objects\n  GTest::Main\n  GTest::gmock\n  qclient\n  XROOTD::POSIX\n  XROOTD::SERVER\n  RestAnnot-Objects\n  XrdEosMgm-Static)\n\n#-------------------------------------------------------------------------------\n# eos-unit-tests-fst executable\n#-------------------------------------------------------------------------------\nadd_executable(eos-unit-tests-fst ${UT_FST_SRCS})\ntarget_compile_definitions(eos-unit-tests-fst PUBLIC -D_NOOFS=1)\n\ntarget_include_directories(eos-unit-tests-fst PUBLIC\n  ${CMAKE_SOURCE_DIR}/common)\n\ntarget_link_libraries(eos-unit-tests-fst PRIVATE\n  GTest::Main\n  GTest::gmock\n  EosFstOss-Static\n  XrdEosFst-Static\n  XXHASH::XXHASH)\n\n#-------------------------------------------------------------------------------\n# eos-unit-tests-with-instance executable\n#-------------------------------------------------------------------------------\nadd_executable(eos-unit-tests-with-instance\n  fst/main_fst.cc\n  fst/TestEnv.cc\n  fst/XrdFstOfsFileTest.cc\n  fst/XrdIoTests.cc\n  fst/NfsIoTests.cc)\n\ntarget_link_libraries(eos-unit-tests-with-instance PRIVATE\n  GTest::GTest\n  XrdEosFst-Static)\n\n#-------------------------------------------------------------------------------\n# eos-unit-tests-with-qdb executable\n#-------------------------------------------------------------------------------\nadd_executable(eos-unit-tests-with-qdb\n  with_qdb/Main.cc\n  with_qdb/configuration.cc\n  with_qdb/TestUtils.cc)\n\ntarget_link_libraries(eos-unit-tests-with-qdb\n  GTest::Main\n  GTest::gmock\n  qclient\n  RestAnnot-Objects\n  XrdEosMgm-Static\n  READLINE::READLINE\n  JSONCPP::JSONCPP)\n\ninstall(TARGETS\n  eos-unit-tests eos-unit-tests-with-instance eos-unit-tests-fst\n  eos-unit-tests-with-qdb\n  RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR})\n"
  },
  {
    "path": "unit_tests/README.md",
    "content": "## EOS Unit Tests\n\nIn development, unit tests are run upon every commit in the CI pipeline.  \nOnce per day, in the nightly build, a version compiled \nwith AddressSanitizer is being executed as well.\n\n### Building and running unit tests\n\nUnit tests are built by default, in the `all` target.  \nTo build them individually, the following targets are available:  \n\n```bash\nmake eos-unit-tests \nmake eos-unit-tests-fst\n```\nThey are provided as executables, with the EOS dependencies linked statically.  \n\nTo run the unit tests, simply start the executable.\n\n### Unit tests with Address Sanitizer \n\nTo compile unit tests using Address Sanitizer, \nenable the CMAKE `ASAN` flag:\n\n```bash\ncmake3 ../ -DASAN=1\nmake eos-unit-tests eos-unit-tests-fst\n```\nTo run unit tests with Address Sanitizer, we recommend using \nthe following suppression file `LeakSanitizer.supp`,\nin order to ignore known memory leaks.\n\nUpon installation, the file is placed in `/var/eos/test/`.\n\n```bash\nLSAN_OPTIONS=suppressions=/var/eos/test/LeakSanitizer.supp eos-unit-tests-fst\n```\n\n#### Known memory leaks\n\nKnown memory leaks are marked in the `LeakSanitizer.supp` suppression file.  \nThe file is located within the repo in `misc/var/eos/test`.  \n\nAny _accepted_ memory leak should be registered.\n\nMore information on suppressions can be found [here][1].\n\n### Installing from RPMs\n\nUnit tests are provided in the `eos-test` RPM.  \n\nIf the Address Sanitizer test package is installed, \nthe sanitizer suppression file will also be placed under `/var/eos/test/`. \n\n\n[1]: https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer#suppressions\n"
  },
  {
    "path": "unit_tests/auth_plugin/AuthFsTest.cc",
    "content": "//------------------------------------------------------------------------------\n// File: AuthFsTest.cc\n// Author: Elvin Sindrilaru  <esindril@cern.ch> CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <gtest/gtest.h>\n#include <XrdCl/XrdClFileSystem.hh>\n#include <XrdCl/XrdClFile.hh>\n#include <XrdOuc/XrdOucErrInfo.hh>\n#include <XrdSec/XrdSecEntity.hh>\n#include \"TestEnv.hh\"\n\n//------------------------------------------------------------------------------\n//! AuthFsTest class\n//------------------------------------------------------------------------------\nclass AuthFsTest_F: public ::testing::Test\n{\n  protected::\n  //----------------------------------------------------------------------------\n  //! setUp function called before each test is done\n  //----------------------------------------------------------------------------\n  void SetUp(void) override\n  {\n    // Initialise\n    mEnv = new eos::auth::test::TestEnv();\n    std::string address = \"root://root@\" + mEnv->GetMapping(\"server\");\n    XrdCl::URL url(address);\n    mFs = new XrdCl::FileSystem(url);\n  }\n\n  //----------------------------------------------------------------------------\n  //! tearDown function after each test is done\n  //----------------------------------------------------------------------------\n  void TearDown(void) override\n  {\n    delete mEnv;\n    delete mFs;\n    mFs = 0;\n  }\n\nprivate:\n  XrdCl::FileSystem* mFs; ///< XrdCl::FileSystem instance used in the tests\n  eos::auth::test::TestEnv* mEnv; ///< testing envirionment object\n};\n\n\n//------------------------------------------------------------------------------\n// File stat test - for a file which exists in EOS\n//------------------------------------------------------------------------------\nTEST_F(AuthFsTest_F, StatTest)\n{\n  XrdCl::StatInfo* stat {nullptr};\n  uint64_t file_size = atoi(mEnv->GetMapping(\"file_size\").c_str());\n  std::string file_path = mEnv->GetMapping(\"file_path\");\n  XrdCl::XRootDStatus status = mFs->Stat(file_path, stat);\n  ASSERT_TRUE(status.IsOK());\n  ASSERT_TRUE(stat != nullptr);\n  ASSERT_TRUE(stat->GetSize() == file_size); // 1MB\n  ASSERT_TRUE(stat->TestFlags(XrdCl::StatInfo::IsReadable)) ;\n  delete stat;\n  stat = 0;\n}\n\n//------------------------------------------------------------------------------\n// File stat test - for a file which does not exits in EOS\n//------------------------------------------------------------------------------\nTEST_F(AuthFsTest_F, : StatFailTest)\n{\n  XrdCl::StatInfo* stat {nullptr};\n  std::string file_path = mEnv->GetMapping(\"file_missing\");\n  XrdCl::XRootDStatus status = mFs->Stat(file_path, stat);\n  ASSERT_FALSE(status.IsOK());\n  ASSERT_TRUE(stat == 0);\n  delete stat;\n  stat = 0;\n}\n\n//------------------------------------------------------------------------------\n// StatVFS test - this request goes to the XrdMgmOfs::fsctl with command id\n// SFS_FSCTL_STATFS = 2 and is not supported by EOS i.e. returns an error\n//------------------------------------------------------------------------------\nTEST_F(AuthFsTest_F, StatVFSTest)\n{\n  XrdCl::StatInfoVFS* statvfs {nullptr};\n  XrdCl::XRootDStatus status = mFs->StatVFS(\"/\", statvfs);\n  ASSERT_TRUE(status.IsError());\n  ASSERT_TRUE(status.code == XrdCl::errErrorResponse);\n  delete statvfs;\n}\n\n//------------------------------------------------------------------------------\n// Truncate test\n//------------------------------------------------------------------------------\nTEST_F(AuthFsTest_F, TruncateTest)\n{\n  std::string file_path = mEnv->GetMapping(\"file_path\");\n  XrdCl::XRootDStatus status = mFs->Truncate(file_path, 1024);\n  ASSERT_TRUE(status.IsError());\n  ASSERT_TRUE(status.code == XrdCl::errErrorResponse);\n}\n\n//------------------------------------------------------------------------------\n// Rename test\n//------------------------------------------------------------------------------\nTEST_F(AuthFsTest_F, RenameTest)\n{\n  uint64_t file_size = atoi(mEnv->GetMapping(\"file_size\").c_str());\n  std::string file_path = mEnv->GetMapping(\"file_path\");\n  std::string rename_path = mEnv->GetMapping(\"file_rename\");\n  XrdCl::XRootDStatus status = mFs->Mv(file_path, rename_path);\n  ASSERT_TRUE(status.IsOK());\n  // Stat the renamed file\n  XrdCl::StatInfo* stat {nullptr};\n  status = mFs->Stat(rename_path, stat);\n  ASSERT_TRUE(status.IsOK());\n  ASSERT_TRUE(stat != nullptr);\n  ASSERT_TRUE(stat->GetSize() == file_size);\n  ASSERT_TRUE(stat->TestFlags(XrdCl::StatInfo::IsReadable)) ;\n  delete stat;\n  stat = 0;\n  // Rename back to initial file name\n  status = mFs->Mv(rename_path, file_path);\n  ASSERT_TRUE(status.IsOK());\n  // Stat again the initial file name\n  status = mFs->Stat(file_path, stat);\n  ASSERT_TRUE(status.IsOK());\n  ASSERT_TRUE(stat != nullptr);\n  ASSERT_TRUE(stat->GetSize() == file_size);\n  ASSERT_TRUE(stat->TestFlags(XrdCl::StatInfo::IsReadable)) ;\n  delete stat;\n  stat = 0;\n}\n\n//------------------------------------------------------------------------------\n// Rem test. This also tests the normal writing mode in EOS i.e. the redirection\n// to the FST node.\n//------------------------------------------------------------------------------\nTEST_F(AuthFsTest_F, RemTest)\n{\n  using namespace XrdCl;\n  // Create a dummy file\n  std::string address = \"root://root@\" + mEnv->GetMapping(\"server\") + \"/\";\n  XrdCl::URL url(address);\n  ASSERT_TRUE(url.IsValid());\n  // Construct the file path\n  std::string file_path = mEnv->GetMapping(\"dir_name\") + \"/to_delete.dat\";\n  std::string file_url = address + file_path;\n  // Fill 1MB buffer with random content\n  int buff_size = atoi(mEnv->GetMapping(\"file_size\").c_str());\n  char* buffer = new char[buff_size];\n  std::fstream urand(\"/dev/urandom\");\n  urand.read(buffer, buff_size);\n  urand.close();\n  // Create and write a 1MB file\n  File file;\n  ASSERT_TRUE(file.Open(file_url, OpenFlags::Delete | OpenFlags::Update,\n                        Access::UR | Access::UW | Access::GR | Access::OR).IsOK());\n  ASSERT_TRUE(file.Write(0, buff_size, buffer).IsOK());\n  ASSERT_TRUE(file.Sync().IsOK());\n  ASSERT_TRUE(file.Close().IsOK());\n  // Delete the newly created file\n  ASSERT_TRUE(mFs->Rm(file_path).IsOK());\n  delete[] buffer;\n}\n\n//------------------------------------------------------------------------------\n// Prepare test - EOS does not support this, it just returns SFS_OK. It seems\n// no one knows what it should do exactly ... :)\n//------------------------------------------------------------------------------\nTEST_F(AuthFsTest_F, PrepareTest)\n{\n  using namespace XrdCl;\n  std::string file_path = mEnv->GetMapping(\"file_path\");\n  std::vector<std::string> file_list;\n  file_list.push_back(file_path);\n  Buffer* response = 0;\n  XRootDStatus status = mFs->Prepare(file_list, PrepareFlags::Flags::WriteMode,\n                                     3, response);\n  ASSERT_TRUE(status.IsOK());\n  delete response;\n}\n\n//------------------------------------------------------------------------------\n// Mkdir test\n//------------------------------------------------------------------------------\nTEST_F(AuthFsTest_F, MkRemDirTest)\n{\n  using namespace XrdCl;\n  std::string dir_path = mEnv->GetMapping(\"dir_new\");\n  MkDirFlags::Flags flags = MkDirFlags::Flags::MakePath;\n  Access::Mode mode = Access::Mode::UR | Access::Mode::UW |\n                      Access::Mode::GR | Access::Mode::OR;\n  XRootDStatus status = mFs->MkDir(dir_path, flags, mode);\n  ASSERT_TRUE(status.IsOK());\n  // Delete the newly created directory\n  status = mFs->RmDir(dir_path);\n  ASSERT_TRUE(status.IsOK());\n}\n\n//------------------------------------------------------------------------------\n// fsctl test - in XRootD this is called for the following: query for space,\n// locate, stats or xattr. In practice in EOS we only support locate and stats\n//------------------------------------------------------------------------------\nTEST_F(AuthFsTest_F, fsctlTest)\n{\n  using namespace XrdCl;\n  Buffer* response = 0;\n  Buffer arg;\n  arg.FromString(\"/\");\n  // SFS_FSCTL_STATLS is supported\n  XRootDStatus status = mFs->Query(QueryCode::Code::Space, arg, response);\n  ASSERT_TRUE(status.IsOK());\n  ASSERT_TRUE(response->GetSize() != 0);\n  delete response;\n  response = 0;\n  // This calls getStats() on the EosAutOfs\n  status = mFs->Query(QueryCode::Code::Stats, arg, response);\n  ASSERT_TRUE(status.IsOK());\n  delete response;\n  response = 0;\n  // This is not supported - should return an error\n  status = mFs->Query(QueryCode::Code::XAttr, arg, response);\n  ASSERT_TRUE(status.IsError());\n  delete response;\n  response = 0;\n  // Test xattr query which calls fsctl with cmd = SFS_FSCTL_STATXS on the\n  // server side which is not supported in EOS\n  status = mFs->Query(QueryCode::Code::XAttr, arg, response);\n  ASSERT_TRUE(status.IsError());\n  delete response;\n  response = 0;\n  // Test Locate which calls fsctl with cmd = SFS_FSCTL_LOCATE on the server size\n  std::string file_path = mEnv->GetMapping(\"file_path\");\n  LocationInfo* location;\n  status = mFs->Locate(file_path, OpenFlags::Flags::Read, location);\n  ASSERT_TRUE(status.IsOK());\n  ASSERT_TRUE(location);\n  delete location;\n}\n\n//------------------------------------------------------------------------------\n// FSctl test - this is called only when we do an opaque query\n//\n// Note: QueryCode::Code::Opaque     -> SFS_FSCTL_PLUGIO\n//       QueryCode::Code::OpaqueFile -> SFS_FSCTL_PLUGIN\n//\n//------------------------------------------------------------------------------\nTEST_F(AuthFsTest_F, FSctlTest)\n{\n  using namespace XrdCl;\n  Buffer* response = 0;\n  Buffer arg;\n  // SFS_FSCTL_PLUGIN not supported - we expect an error\n  XRootDStatus status = mFs->Query(QueryCode::Code::Opaque, arg, response);\n  ASSERT_TRUE(status.IsError());\n  arg.Release();\n  delete response;\n  response = 0;\n  // Do stat on a file - which is an SFS_FSCTL_PLUGIO and is supported\n  std::ostringstream sstr;\n  sstr << \"/?mgm.pcmd=stat&mgm.path=\" << mEnv->GetMapping(\"file_path\");\n  arg.FromString(sstr.str());\n  status = mFs->Query(QueryCode::Code::OpaqueFile, arg, response);\n  ASSERT_TRUE(status.IsOK());\n  ASSERT_TRUE(response->GetSize());\n  delete response;\n}\n\n//------------------------------------------------------------------------------\n// Chksum test\n//------------------------------------------------------------------------------\nTEST_F(AuthFsTest_F, ChksumTest)\n{\n  using namespace XrdCl;\n  std::string file_chksum = mEnv->GetMapping(\"file_chksum\");\n  Buffer* response = 0;\n  Buffer arg;\n  arg.FromString(mEnv->GetMapping(\"file_path\"));\n  XRootDStatus status = mFs->Query(QueryCode::Code::Checksum, arg, response);\n  ASSERT_TRUE(status.IsOK());\n  ASSERT_TRUE(response != nullptr);\n  ASSERT_TRUE(response->GetSize() != 0);\n  ASSERT_TRUE(response->ToString() == file_chksum);\n  delete response;\n}\n\n//------------------------------------------------------------------------------\n// Chmod test - only works on directories in EOS\n//------------------------------------------------------------------------------\nTEST_F(AuthFsTest_F, ChmodTest)\n{\n  using namespace XrdCl;\n  std::string dir_path = mEnv->GetMapping(\"dir_new\");\n  std::string file_path = mEnv->GetMapping(\"file_path\");\n  //Create dummy directory\n  MkDirFlags::Flags flags = MkDirFlags::Flags::MakePath;\n  Access::Mode mode = Access::Mode::UR | Access::Mode::UW |\n                      Access::Mode::GR | Access::Mode::OR;\n  XRootDStatus status = mFs->MkDir(dir_path, flags, mode);\n  ASSERT_TRUE(status.IsOK());\n  // Chmod dir\n  status = mFs->ChMod(dir_path,\n                      Access::Mode::UR | Access::Mode::UW | Access::Mode::UX |\n                      Access::Mode::GR | Access::Mode::GW | Access::Mode::GX |\n                      Access::Mode::OR | Access::Mode::OW | Access::Mode::OX);\n  ASSERT_TRUE(status.IsOK());\n  // Delete the newly created directory\n  status = mFs->RmDir(dir_path);\n  ASSERT_TRUE(status.IsOK());\n  // Chmod file\n  status = mFs->ChMod(file_path,\n                      Access::Mode::UR | Access::Mode::UW | Access::Mode::UX |\n                      Access::Mode::GR | Access::Mode::GW | Access::Mode::GX |\n                      Access::Mode::OR | Access::Mode::OW | Access::Mode::OX);\n  ASSERT_TRUE(status.IsOK());\n}\n\n//------------------------------------------------------------------------------\n// Directory listing test - the initial directory should contain only the\n// initial test file: file1MB.dat\n//------------------------------------------------------------------------------\nTEST_F(AuthFsTest_F, DirListTest)\n{\n  using namespace XrdCl;\n  std::string dir_path = mEnv->GetMapping(\"dir_name\");\n  DirectoryList* list_dirs = 0;\n  XRootDStatus status = mFs->DirList(dir_path, DirListFlags::None, list_dirs);\n  ASSERT_TRUE(status.IsOK());\n  ASSERT_TRUE(list_dirs->GetSize() == 1);\n  ASSERT_TRUE(list_dirs->GetParentName() == dir_path);\n  delete list_dirs;\n}\n\n//------------------------------------------------------------------------------\n// Proc command test which actually tests the File implementation. Try to do an\n// \"fs ls\" command as an administrator.\n//------------------------------------------------------------------------------\nTEST_F(AuthFsTest_F, ProcCommandTest)\n{\n  using namespace XrdCl;\n  std::string address = \"root://root@\" + mEnv->GetMapping(\"server\") + \"/\";\n  XrdCl::URL url(address);\n  ASSERT_TRUE(url.IsValid());\n  // Construct the file path\n  std::string command = \"mgm.cmd=fs&mgm.subcmd=ls&eos.ruid=0&eos.rgid=0\";\n  std::string file_path = \"/proc/admin/?\" + command;\n  std::string file_url = address + file_path + command;\n  // Open the file for reading - which triggers the command to be executed and\n  // then we just need to read the result of the command from the same file\n  File file;\n  ASSERT_TRUE(file.Open(file_url, OpenFlags::Read).IsOK());\n  // Prepare buffer which contains the result\n  std::string output(\"\");\n  uint64_t offset = 0;\n  uint32_t nread = 0;\n  char buffer[4096 + 1];\n  XRootDStatus status = file.Read(offset, 4096, buffer, nread);\n\n  while (status.IsOK() && (nread > 0)) {\n    buffer[nread] = '\\0';\n    output += buffer;\n    offset += nread;\n    status = file.Read(offset, 4096, buffer, nread);\n  }\n\n  ASSERT_TRUE(output.length() != 0);\n  ASSERT_TRUE(file.Close().IsOK());\n}\n"
  },
  {
    "path": "unit_tests/auth_plugin/Namespace.hh",
    "content": "//------------------------------------------------------------------------------\n// File: Namespace.hh\n// Author: Elvin Sindrilaru <esindril@cern.ch> CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef __EOSAUTHTEST_NAMESPACE_HH__\n#define __EOSAUTHTEST_NAMESPACE_HH__\n\n#define EOSAUTHTEST_NAMESPACE_BEGIN namespace eos{  namespace auth { namespace test {\n#define EOSAUTHTEST_NAMESPACE_END }}}\n\n#endif // __EOSAUTHTEST_NAMESPACE_HH__\n"
  },
  {
    "path": "unit_tests/auth_plugin/TestEnv.cc",
    "content": "//------------------------------------------------------------------------------\n// File: TestEnv.hh\n// Author: Elvin Sindrilaru <esindril@cern.ch> CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"TestEnv.hh\"\n#include <iostream>\n\nEOSAUTHTEST_NAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//\n// Notice:\n// File /eos/dev/test/auth/file1MB.dat is created as follows:\n// dd if=/dev/zero count=1024 bs=1024 | tr '\\000' '\\001' > /eos/dev/file1MB.dat\n//\n// And the extended attributes on the /eos/dev/test/auth directory are:\n// sys.forced.checksum=\"adler\"\n// sys.forced.space=\"default\"\n//\n//------------------------------------------------------------------------------\nTestEnv::TestEnv()\n{\n  mMapParam.insert(std::make_pair(\"server\", \"localhost:1099\"));\n  mMapParam.insert(std::make_pair(\"file_path\", \"/eos/dev/test/auth/file1MB.dat\"));\n  mMapParam.insert(std::make_pair(\"file_size\", \"1048576\")); // 1MB\n  mMapParam.insert(std::make_pair(\"file_chksum\", \"adler32 71e800f1\"));\n  mMapParam.insert(std::make_pair(\"file_missing\",\n                                  \"/eos/dev/test/auth/file_unknown.dat\"));\n  mMapParam.insert(std::make_pair(\"file_rename\",\n                                  \"/eos/dev/test/auth/file1MB.dat_rename\"));\n  mMapParam.insert(std::make_pair(\"dir_name\", \"/eos/dev/test/auth/\"));\n  mMapParam.insert(std::make_pair(\"dir_new\", \"/eos/dev/test/auth/dummy\"));\n}\n\n//------------------------------------------------------------------------------\n// Set key value mapping\n//------------------------------------------------------------------------------\nvoid\nTestEnv::SetMapping(const std::string& key, const std::string& value)\n{\n  auto pair_res = mMapParam.insert(std::make_pair(key, value));\n\n  if (!pair_res.second) {\n    std::cerr << \"Mapping already exists, key=\" << key\n              << \" value=\" << value << std::endl;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get key value mapping\n//------------------------------------------------------------------------------\nstd::string\nTestEnv::GetMapping(const std::string& key) const\n{\n  auto iter = mMapParam.find(key);\n\n  if (iter != mMapParam.end()) {\n    return iter->second;\n  } else {\n    return std::string(\"\");\n  }\n}\n\nEOSAUTHTEST_NAMESPACE_END\n"
  },
  {
    "path": "unit_tests/auth_plugin/TestEnv.hh",
    "content": "//------------------------------------------------------------------------------\n// File: TestEnv.hh\n// Author: Elvin Sindrilaru <esindril@cern.ch> CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <map>\n#include <string>\n#include \"Namespace.hh\"\n\nEOSAUTHTEST_NAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! TestEnv class - not thread safe\n//------------------------------------------------------------------------------\nclass TestEnv\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  TestEnv();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~TestEnv() = default;\n\n\n  //----------------------------------------------------------------------------\n  //! Add new entry to the map of parameters\n  //!\n  //! @param key key to be inserted\n  //! @param value value to the inserted\n  //!\n  //----------------------------------------------------------------------------\n  void SetMapping(const std::string& key, const std::string& value);\n\n  //----------------------------------------------------------------------------\n  //! Get value corresponding to the key from the map\n  //!\n  //! @param key key to be searched in the map\n  //!\n  //! @return value stored in the map\n  //!\n  //----------------------------------------------------------------------------\n  std::string GetMapping(const std::string& key) const;\n\nprivate:\n  std::map<std::string, std::string> mMapParam; ///< map testing parameters\n};\n\nEOSAUTHTEST_NAMESPACE_END\n"
  },
  {
    "path": "unit_tests/common/AuditTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: AuditTests.cc\n// Author: EOS Team - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Audit.hh\"\n#include \"proto/Audit.pb.h\"\n\n#include \"Namespace.hh\"\n#include \"gtest/gtest.h\"\n\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <dirent.h>\n#include <unistd.h>\n#include <string.h>\n#include <errno.h>\n\n#include <string>\n#include <set>\n#include <chrono>\n#include <thread>\n#include <vector>\n\nEOSCOMMONTESTING_BEGIN\n\nnamespace {\nstd::string makeTempDir()\n{\n  std::string base = \"/tmp/eos_audit_test_XXXXXX\";\n  std::vector<char> buf(base.begin(), base.end());\n  buf.push_back('\\0');\n  char* r = ::mkdtemp(buf.data());\n  if (!r) {\n    throw std::runtime_error(\"mkdtemp failed\");\n  }\n  return std::string(r);\n}\n\nstd::set<std::string> listZst(const std::string& dir)\n{\n  std::set<std::string> out;\n  DIR* d = ::opendir(dir.c_str());\n  if (!d) return out;\n  struct dirent* ent;\n  while ((ent = ::readdir(d)) != nullptr) {\n    if (ent->d_name[0] == '.') continue;\n    std::string name = ent->d_name;\n    if (name.size() >= 4 && name.substr(name.size() - 4) == \".zst\") {\n      out.insert(name);\n    }\n  }\n  ::closedir(d);\n  return out;\n}\n\nstd::string readSymlink(const std::string& path)\n{\n  char buf[PATH_MAX];\n  ssize_t n = ::readlink(path.c_str(), buf, sizeof(buf) - 1);\n  if (n < 0) return \"\";\n  buf[n] = '\\0';\n  return std::string(buf);\n}\n}\n\nTEST(Audit, BasicWriteRotateAndSymlink)\n{\n  using namespace eos::common;\n  std::string dir = makeTempDir();\n\n  Audit audit(dir, /*rotationSeconds*/1, /*compressionLevel*/1);\n\n  eos::audit::AuditRecord rec1;\n  rec1.set_timestamp(::time(nullptr));\n  rec1.set_path(\"/eos/test/file1\");\n  rec1.set_operation(eos::audit::Operation::CREATE);\n  rec1.set_client_ip(\"127.0.0.1\");\n  rec1.set_account(\"root\");\n  rec1.mutable_auth()->set_mechanism(\"local\");\n  rec1.mutable_authorization()->add_reasons(\"uidgid\");\n\n  audit.audit(rec1);\n\n  auto files1 = listZst(dir);\n  ASSERT_GE(files1.size(), 1u);\n\n  std::string linkPath = dir + \"/audit.zstd\";\n  struct stat st{};\n  ASSERT_EQ(::lstat(linkPath.c_str(), &st), 0);\n  ASSERT_TRUE(S_ISLNK(st.st_mode));\n  std::string firstTarget = readSymlink(linkPath);\n  ASSERT_FALSE(firstTarget.empty());\n\n  std::this_thread::sleep_for(std::chrono::seconds(2));\n\n  eos::audit::AuditRecord rec2;\n  rec2.set_timestamp(::time(nullptr));\n  rec2.set_path(\"/eos/test/file2\");\n  rec2.set_operation(eos::audit::Operation::RENAME);\n  rec2.set_target(\"/eos/test/file2.new\");\n  rec2.set_client_ip(\"127.0.0.1\");\n  rec2.set_account(\"root\");\n  rec2.mutable_auth()->set_mechanism(\"local\");\n  rec2.mutable_authorization()->add_reasons(\"uidgid\");\n\n  audit.audit(rec2);\n\n  auto files2 = listZst(dir);\n  ASSERT_GE(files2.size(), 2u);\n\n  std::string secondTarget = readSymlink(linkPath);\n  ASSERT_FALSE(secondTarget.empty());\n  ASSERT_NE(firstTarget, secondTarget);\n}\n\nTEST(Audit, BenchmarkWrite10000)\n{\n  using namespace eos::common;\n  std::string dir = makeTempDir();\n\n  // Use a long rotation to avoid rotation overhead in benchmark\n  Audit audit(dir, /*rotationSeconds*/3600, /*compressionLevel*/1);\n\n  const int N = 100000;\n  eos::audit::AuditRecord rec;\n  rec.set_timestamp(::time(nullptr));\n  rec.set_path(\"/eos/bench/file\");\n  rec.set_operation(eos::audit::WRITE);\n  rec.set_client_ip(\"127.0.0.1\");\n  rec.set_account(\"bench\");\n  rec.set_svc(\"test\");\n\n  auto t0 = std::chrono::steady_clock::now();\n  for (int i = 0; i < N; ++i) {\n    rec.set_path(std::string(\"/eos/bench/file_\") + std::to_string(i));\n    audit.audit(rec);\n  }\n  auto t1 = std::chrono::steady_clock::now();\n  auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count();\n\n  std::cout << \"Audit benchmark: wrote \" << N << \" records in \" << ms << \" ms\\n\";\n}\n\nTEST(Audit, BenchmarkWrite100kConcurrent)\n{\n  using namespace eos::common;\n  std::string dir = makeTempDir();\n\n  // Long rotation to avoid rotation overhead skewing results\n  Audit audit(dir, /*rotationSeconds*/3600, /*compressionLevel*/1);\n\n  const int numThreads = 100;\n  const int perThread = 1000; // 100 * 1000 = 100000\n\n  eos::audit::AuditRecord base;\n  base.set_timestamp(::time(nullptr));\n  base.set_operation(eos::audit::WRITE);\n  base.set_client_ip(\"127.0.0.1\");\n  base.set_account(\"bench\");\n  base.set_svc(\"test\");\n\n  std::vector<std::thread> threads;\n  threads.reserve(numThreads);\n\n  auto t0 = std::chrono::steady_clock::now();\n  for (int t = 0; t < numThreads; ++t) {\n    threads.emplace_back([&, t]() {\n      eos::audit::AuditRecord rec = base;\n      for (int i = 0; i < perThread; ++i) {\n        rec.set_path(std::string(\"/eos/bench/concurrent_\") + std::to_string(t) + \"_\" + std::to_string(i));\n        audit.audit(rec);\n      }\n    });\n  }\n  for (auto& th : threads) th.join();\n  auto t1 = std::chrono::steady_clock::now();\n  auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count();\n\n  std::cout << \"Audit concurrent benchmark: wrote \" << (numThreads * perThread)\n            << \" records in \" << ms << \" ms\\n\";\n}\n\nEOSCOMMONTESTING_END\n\n\n"
  },
  {
    "path": "unit_tests/common/BackOffInvokerTests.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file BackOffInvokerTests.cc\n//! @author Abhishek Lekshmanan <abhishek.lekshmanan@cern.ch>\n//-----------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <gtest/gtest.h>\n#include \"common/utils/BackOffInvoker.hh\"\n\nTEST(BackOffInvoker, simple)\n{\n  eos::common::BackOffInvoker backoff;\n  int counter = 0;\n  auto fn = [&counter]() { counter++; };\n  for (int i = 0; i < 10; i++) {\n    backoff.invoke(fn);\n  }\n  ASSERT_EQ(counter, 4);\n}\n\nTEST(BackOffInvoker, uint8_lt_half_limit)\n{\n  eos::common::BackOffInvoker<uint8_t> backoff;\n  int counter = 0;\n  auto fn = [&counter]() { counter++; };\n  for (int i = 0; i < 127; i++) {\n    backoff.invoke(fn);\n  }\n  ASSERT_EQ(counter, 7);\n}\n\nTEST(BackOffInvoker, uint8_half_limit)\n{\n  eos::common::BackOffInvoker<uint8_t> backoff;\n  int counter = 0;\n  auto fn = [&counter]() { counter++; };\n  for (int i = 0; i < 128; i++) {\n    backoff.invoke(fn);\n  }\n  ASSERT_EQ(counter, 8);\n}\n\nTEST(BackOffInvoker, uint8_full_limit)\n{\n  eos::common::BackOffInvoker<uint8_t,true> backoff;\n  int counter = 0;\n  auto fn = [&counter]() { counter++; };\n  for (int i = 0; i < 256; i++) {\n    backoff.invoke(fn);\n  }\n  EXPECT_EQ(counter, 8);\n  backoff.invoke(fn);\n  EXPECT_EQ(counter, 9);\n}\n\nTEST(BackOffInvoker, uint8_wrap_around)\n{\n  eos::common::BackOffInvoker<uint8_t> backoff;\n  int counter = 0;\n  auto fn = [&counter]() { counter++; };\n  for (int i = 0; i < 512; i++) {\n    backoff.invoke(fn);\n  }\n  EXPECT_EQ(counter, 16);\n}\n\nTEST(BackOffInvoker, uint8_no_wrap_around)\n{\n  eos::common::BackOffInvoker<uint8_t,false> backoff;\n  int counter = 0;\n  auto fn = [&counter]() { counter++; };\n  for (int i = 0; i < 256; i++) {\n    backoff.invoke(fn);\n  }\n  // When you don't wrap around; we continue at 256 intervals;\n  EXPECT_EQ(counter, 9);\n}\n\n\nTEST(BackOffInvoker, uint8_no_wrap_around_twice)\n{\n  eos::common::BackOffInvoker<uint8_t,false> backoff;\n  int counter = 0;\n  auto fn = [&counter]() { counter++; };\n  for (int i = 0; i < 511; i++) {\n    backoff.invoke(fn);\n  }\n  // When you don't wrap around; we continue at 256 intervals;\n  EXPECT_EQ(counter, 9);\n  backoff.invoke(fn);\n  EXPECT_EQ(counter, 10);\n}\n\n\n\nTEST(BackOffInvoker, uint16_t_full_limit)\n{\n  eos::common::BackOffInvoker<uint16_t> backoff;\n  int counter = 0;\n  auto fn = [&counter]() { counter++; };\n  for (int i = 0; i < 65536; i++) {\n    backoff.invoke(fn);\n  }\n  EXPECT_EQ(counter, 16);\n}\n\nTEST(BackOffInvoker, uint16_t_wrap_around)\n{\n  eos::common::BackOffInvoker<uint16_t> backoff;\n  int counter = 0;\n  auto fn = [&counter]() { counter++; };\n  for (int i = 0; i < 65536*2; i++) {\n    backoff.invoke(fn);\n  }\n  EXPECT_EQ(counter, 32);\n}\n"
  },
  {
    "path": "unit_tests/common/BufferManagerTests.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file BufferManagerTests.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/utils/RandUtils.hh\"\n#include \"utils/RandUtils.hh\"\n#include \"gtest/gtest.h\"\n#define IN_TEST_HARNESS\n#include \"common/BufferManager.hh\"\n#undef IN_TEST_HARNESS\n#include \"common/StringConversion.hh\"\n#include <thread>\n#include <chrono>\n\n\nTEST(BufferManager, PowerCeil)\n{\n  ASSERT_EQ(1024, eos::common::GetPowerCeil(1));\n  ASSERT_EQ(1024, eos::common::GetPowerCeil(1000));\n  ASSERT_EQ(1024, eos::common::GetPowerCeil(1024));\n  ASSERT_EQ(2048, eos::common::GetPowerCeil(1025));\n  ASSERT_EQ(4096, eos::common::GetPowerCeil(2049));\n  ASSERT_EQ(8192, eos::common::GetPowerCeil(5000));\n  ASSERT_EQ(16384, eos::common::GetPowerCeil(9001));\n  ASSERT_EQ(2048, eos::common::GetPowerCeil(1, 2048));\n  ASSERT_EQ(4096, eos::common::GetPowerCeil(2049, 2048));\n  ASSERT_EQ(16384, eos::common::GetPowerCeil(1, 16384));\n  ASSERT_EQ(32768, eos::common::GetPowerCeil(16385, 16384));\n}\n\nTEST(BufferManager, MatchingSizes)\n{\n  using namespace eos::common;\n  eos::common::BufferManager buff_mgr(20 * MB);\n  uint64_t buff_sz = 512 * KB;\n  auto buffer = buff_mgr.GetBuffer(buff_sz);\n  ASSERT_NE(buffer, nullptr);\n  ASSERT_EQ(buffer->mCapacity, 1 * MB);\n  buff_sz = 1 * MB;\n  buffer = buff_mgr.GetBuffer(buff_sz);\n  ASSERT_NE(buffer, nullptr);\n  ASSERT_EQ(buffer->mCapacity, 1 * MB);\n  buff_sz = 1;\n  buffer = buff_mgr.GetBuffer(buff_sz);\n  ASSERT_NE(buffer, nullptr);\n  ASSERT_EQ(buffer->mCapacity, 1 * MB);\n  buff_sz = 1 * MB + 22 * KB;\n  buffer = buff_mgr.GetBuffer(buff_sz);\n  ASSERT_NE(buffer, nullptr);\n  ASSERT_EQ(buffer->mCapacity, 2 * MB);\n  buff_sz = 1 * MB + 44 * KB;\n  buffer = buff_mgr.GetBuffer(buff_sz);\n  ASSERT_NE(buffer, nullptr);\n  ASSERT_EQ(buffer->mCapacity, 2 * MB);\n  buff_sz = 3 * MB + 11 * KB;\n  buffer = buff_mgr.GetBuffer(buff_sz);\n  ASSERT_NE(buffer, nullptr);\n  ASSERT_EQ(buffer->mCapacity, 4 * MB);\n  buff_sz = 512 * MB + 33 * KB;\n  buffer = buff_mgr.GetBuffer(buff_sz);\n  ASSERT_EQ(buffer, nullptr);\n  uint64_t total_size {0ull};\n  auto slot_sizes = buff_mgr.GetSortedSlotSizes(total_size);\n  ASSERT_EQ(11 * MB, total_size);\n  // By default there are 6 slots populated like this\n  // index: 0 slot: 3 size: 0\n  // index: 1 slot: 4 size: 0\n  // index: 2 slot: 5 size: 0\n  // index: 3 slot: 6 size: 0\n  // index: 4 slot: 0 size: 3145728\n  // index: 5 slot: 1 size: 4194304\n  // index: 6 slot: 2 size: 4194304\n  ASSERT_EQ(3, slot_sizes[0].first);\n  ASSERT_EQ(0, slot_sizes[0].second);\n  ASSERT_EQ(4, slot_sizes[1].first);\n  ASSERT_EQ(0, slot_sizes[1].second);\n  ASSERT_EQ(5, slot_sizes[2].first);\n  ASSERT_EQ(0, slot_sizes[2].second);\n  ASSERT_EQ(6, slot_sizes[3].first);\n  ASSERT_EQ(0, slot_sizes[3].second);\n  ASSERT_EQ(0, slot_sizes[4].first);\n  ASSERT_EQ(3145728, slot_sizes[4].second);\n  ASSERT_EQ(1,       slot_sizes[5].first);\n  ASSERT_EQ(4194304, slot_sizes[5].second);\n  ASSERT_EQ(2,       slot_sizes[6].first);\n  ASSERT_EQ(4194304, slot_sizes[6].second);\n}\n\nTEST(BufferManager, RecycleSingleBuffer)\n{\n  using namespace eos::common;\n  eos::common::BufferManager buff_mgr(20 * MB);\n\n  for (int i = 0; i < 100; ++i) {\n    auto buffer = buff_mgr.GetBuffer(1 * MB);\n    buff_mgr.Recycle(buffer);\n  }\n\n  uint64_t total_size {0ull};\n  auto sorted_slots = buff_mgr.GetSortedSlotSizes(total_size);\n  ASSERT_EQ(total_size, 1 * MB);\n}\n\nTEST(BufferManager, PoolFull)\n{\n  using namespace eos::common;\n  constexpr uint64_t pool_sz {20 * MB};\n  eos::common::BufferManager buff_mgr(pool_sz);\n  std::list<std::shared_ptr<eos::common::Buffer>> lst_buffs;\n\n  // Add 2MB, 4MB and 8MB blocks\n  for (int i = 0; i < 6; ++i) {\n    lst_buffs.push_back(buff_mgr.GetBuffer((1 << ((i % 3) + 1)) * MB));\n  }\n\n  // We allocated more than the buffer pool size\n  ASSERT_EQ(28 * MB, buff_mgr.mAllocatedSize);\n  // First buffer in the list is a 2MB one and the last one is 8MB\n  auto buff_8MB = lst_buffs.back();\n  lst_buffs.pop_back();\n  auto buff_4MB = lst_buffs.back();\n  lst_buffs.pop_back();\n  buff_mgr.Recycle(buff_4MB);\n  ASSERT_EQ(24 * MB, buff_mgr.mAllocatedSize);\n  auto buff_2MB = lst_buffs.back();\n  lst_buffs.pop_back();\n  buff_mgr.Recycle(buff_2MB);\n  ASSERT_EQ(22 * MB, buff_mgr.mAllocatedSize);\n  buff_mgr.Recycle(buff_8MB);\n  ASSERT_EQ(14 * MB, buff_mgr.mAllocatedSize);\n  buff_8MB = lst_buffs.back();\n  lst_buffs.pop_back();\n  buff_mgr.Recycle(buff_8MB);\n  ASSERT_EQ(14 * MB, buff_mgr.mAllocatedSize);\n  buff_4MB = lst_buffs.back();\n  lst_buffs.pop_back();\n  lst_buffs.push_back(buff_mgr.GetBuffer(4 * MB));\n  ASSERT_EQ(18 * MB, buff_mgr.mAllocatedSize);\n  lst_buffs.push_back(buff_mgr.GetBuffer(4 * MB));\n  ASSERT_EQ(22 * MB, buff_mgr.mAllocatedSize);\n  buff_mgr.Recycle(buff_4MB);\n  // Free 8MB and 4MB blocks\n  ASSERT_EQ(10 * MB, buff_mgr.mAllocatedSize);\n  buff_4MB = lst_buffs.back();\n  lst_buffs.pop_back();\n  buff_mgr.Recycle(buff_4MB);\n  // We have one 4MB buffer available in the pool\n  ASSERT_EQ(10 * MB, buff_mgr.mAllocatedSize);\n  lst_buffs.push_back(buff_mgr.GetBuffer(8 * MB));\n  ASSERT_EQ(18 * MB, buff_mgr.mAllocatedSize);\n  lst_buffs.push_back(buff_mgr.GetBuffer(8 * MB));\n  ASSERT_EQ(26 * MB, buff_mgr.mAllocatedSize);\n  buff_8MB = lst_buffs.back();\n  lst_buffs.pop_back();\n  buff_mgr.Recycle(buff_8MB);\n  // Free 4MB and 8MB blocks\n  ASSERT_EQ(14 * MB, buff_mgr.mAllocatedSize);\n}\n\nTEST(BufferManager, AdjustCachedSizes)\n{\n  using namespace eos::common;\n  eos::common::BufferManager buff_mgr(20 * MB);\n  std::list<std::shared_ptr<eos::common::Buffer>> lst_buffs;\n\n  // Recycle a 1MB blocks in a loop\n  for (int i = 0; i < 20; ++i) {\n    lst_buffs.push_back(buff_mgr.GetBuffer(1 * MB));\n    // do some work with the buffer\n    buff_mgr.Recycle(lst_buffs.back());\n    lst_buffs.pop_back();\n  }\n\n  uint64_t total_size {0ull};\n  auto sorted_slots = buff_mgr.GetSortedSlotSizes(total_size);\n  ASSERT_EQ(total_size, 1 * MB);\n\n  // Fill cache with 1MB blocks\n  for (int i = 0; i < 20; ++i) {\n    lst_buffs.push_back(buff_mgr.GetBuffer(1 * MB));\n  }\n\n  for (int i = 0; i < 20; ++i) {\n    buff_mgr.Recycle(lst_buffs.back());\n    lst_buffs.pop_back();\n  }\n\n  sorted_slots = buff_mgr.GetSortedSlotSizes(total_size);\n  ASSERT_EQ(total_size, 20 * MB);\n  auto buffer = buff_mgr.GetBuffer(3 * MB);\n  ASSERT_NE(buffer, nullptr);\n  ASSERT_EQ(buffer->mCapacity, 4 * MB);\n  buff_mgr.Recycle(buffer);\n  sorted_slots = buff_mgr.GetSortedSlotSizes(total_size);\n  ASSERT_EQ(total_size, 16 * MB);\n}\n\nTEST(BufferManager, MultipleThreads)\n{\n  using namespace eos::common;\n  auto work = [](eos::common::BufferManager & buff_mgr, int num_blocks,\n  float mean, float stddev) {\n    for (int i = 0; i < num_blocks; ++i) {\n      uint64_t max_buff_sz = (1 << buff_mgr.GetNumSlots()) * MB;\n      uint64_t value = std::round(std::abs(eos::common::getRandomNormal(mean, stddev)));\n\n      // Make sure the generated value is within limits\n      if (value <= 0) {\n        value = 1;\n      } else if (value > max_buff_sz) {\n        value = max_buff_sz;\n      }\n\n      auto buffer = buff_mgr.GetBuffer(value);\n      ASSERT_NE(buffer, nullptr);\n      std::this_thread::sleep_for(std::chrono::milliseconds(50));\n      buff_mgr.Recycle(buffer);\n    }\n  };\n  int num_threads = 32;\n  int num_blocks = 24;\n  std::list<std::thread> lst_threads;\n  std::list<std::pair<float, float>> uniform_dist_params;\n  uniform_dist_params.emplace_back(500 * KB, 200 * KB);\n  uniform_dist_params.emplace_back(1500 * KB, 200 * KB);\n  uniform_dist_params.emplace_back(3500 * KB, 400 * KB);\n\n  for (auto& dis_params : uniform_dist_params) {\n    eos::common::BufferManager buff_mgr(100 * MB);\n\n    for (int i = 0; i < num_threads; ++i) {\n      lst_threads.emplace_back(work, std::ref(buff_mgr), num_blocks,\n                               dis_params.first, dis_params.second);\n    }\n\n    for (auto& job : lst_threads) {\n      job.join();\n    }\n\n    lst_threads.clear();\n    uint64_t total_size {0ull};\n    auto sorted_slots = buff_mgr.GetSortedSlotSizes(total_size);\n    ASSERT_LE(total_size, buff_mgr.GetMaxSize());\n    // Get the most used slot according to the distribution mean\n    uint32_t slot {UINT32_MAX};\n\n    for (auto i = 0ull; i <= buff_mgr.GetNumSlots(); ++i) {\n      if (dis_params.first <= (1 << (i + 20))) {\n        slot = i;\n        break;\n      }\n    }\n\n    // The slot with the largest allocated size should be \"slot\"\n    // for (auto& elem: sorted_slots) {\n    //   std::cerr << \"Slot: \" << elem.first << \" size: \" << elem.second << std::endl;\n    // }\n    ASSERT_EQ(sorted_slots.rbegin()->first, slot);\n  }\n}\n"
  },
  {
    "path": "unit_tests/common/ConcurrentQueueTests.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file BufferManager.hh\n//! @author Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"common/ConcurrentQueue.hh\"\n\nTEST(ConcurrentQueue, BasicFunctionality)\n{\n  eos::common::ConcurrentQueue<int> queue;\n\n  for (int i = 0; i < 100; ++i) {\n    queue.push(i);\n  }\n\n  ASSERT_EQ(100, queue.size());\n  int random = 12345;\n  ASSERT_FALSE(queue.push_size(random, 99));\n  int val;\n\n  for (int j = 0; j < 100; ++j) {\n    ASSERT_TRUE(queue.try_pop(val));\n    ASSERT_EQ(j, val);\n  }\n\n  ASSERT_TRUE(queue.empty());\n}\n"
  },
  {
    "path": "unit_tests/common/ConfigStoreTests.cc",
    "content": "#include \"unit_tests/common/MemConfigStore.hh\"\n#include \"gtest/gtest.h\"\n\nusing std::string_literals::operator \"\"s;\nconst std::string default_val = \"DEFAULT\";\n\nTEST(ConfigStore, strkeys)\n{\n  MemConfigStore mconf;\n  EXPECT_TRUE(mconf.save(\"key1\", \"val1\"));\n  EXPECT_EQ(\"val1\", mconf.get(\"key1\"s, default_val));\n  float pi = 3.1428;\n  EXPECT_TRUE(mconf.save(\"pi\", std::to_string(pi)));\n  // std::to_string defaults to 6 decimal places though this might be impl defined,\n  // recommended to use the other overload\n  EXPECT_EQ(\"3.142800\", mconf.get(\"pi\"s, default_val));\n  EXPECT_FLOAT_EQ(pi, mconf.get(\"pi\", (float)0));\n  uint32_t nthreads = 1000;\n  EXPECT_TRUE(mconf.save(\"nthreads\", std::to_string(nthreads)));\n  EXPECT_EQ(\"1000\", mconf.get(\"nthreads\", default_val));\n  EXPECT_EQ(nthreads, mconf.get(\"nthreads\", (uint32_t)0));\n}\n\nTEST(ConfigStore, nullkeys)\n{\n  MemConfigStore mconf;\n  EXPECT_EQ(default_val, mconf.get(\"somekey\", default_val));\n  EXPECT_EQ(1000, mconf.get(\"nthreads\", 1000));\n  EXPECT_DOUBLE_EQ(3.1, mconf.get(\"pi\", 3.1));\n}"
  },
  {
    "path": "unit_tests/common/ConfigTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ConfigTests.cc\n// Author: Andreas-Joachim Peters <andreas.joachim.peters@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"common/Config.hh\"\n#include \"common/StringConversion.hh\"\n\n//------------------------------------------------------------------------------\n// Test Tokens\n//------------------------------------------------------------------------------\nTEST(ConfigTests, Configs)\n{\n  (void) !system(\"mkdir -p /etc/eos/config/test/\");\n  std::string configname = \"/etc/eos/config/test/default\";\n  std::string s;\n  s = \"[global]\\nfirst line\\nsecond line\\nthird line\\n[test]\\nverify\";\n  eos::common::StringConversion::SaveStringIntoFile(configname.c_str(), s);\n  eos::common::Config cfg;\n  cfg.Load(\"failing\", \"default\");\n  ASSERT_TRUE(cfg.getErrc() == 2);\n  ASSERT_EQ(cfg.getMsg(),\n            \"error: unable to load '/etc/eos/config/failing/default' : No such file or directory\");\n  ASSERT_TRUE(!cfg.ok());\n  cfg.Load(\"test\", \"default\");\n  ASSERT_TRUE(cfg.ok());\n  ASSERT_TRUE((cfg[\"test\"].size() == 1) && (cfg[\"test\"][0] == \"verify\"));\n  ASSERT_TRUE(cfg[\"global\"].size() == 3);\n  eos::common::Config cfgenoent;\n  cfgenoent.Load(\"test\", \"faulty\");\n  ASSERT_TRUE(!cfgenoent.ok());\n  eos::common::Config cfgempty;\n  s = \"line without chapter\";\n  eos::common::StringConversion::SaveStringIntoFile(configname.c_str(), s);\n  cfgempty.Load(\"test\", \"default\");\n  ASSERT_TRUE(!cfgempty.ok());\n  eos::common::Config cfgsub;\n  s = \"[sysconfig]\\na=100\\nb=$a\\nc=$b\\n[xconf]\\n$a $b c d\";\n  eos::common::StringConversion::SaveStringIntoFile(configname.c_str(), s);\n  cfgsub.Load(\"test\", \"default\");\n  std::string r = cfgsub.Dump(\"xconf\", true);\n  ASSERT_TRUE(r == \"100 100 c d\\n\");\n  char** env = cfgsub.Env(\"sysconfig\");\n  ASSERT_TRUE(std::string(env[0]) == \"a=100\");\n  ASSERT_TRUE(std::string(env[1]) == \"b=100\");\n  ASSERT_TRUE(std::string(env[2]) == \"c=100\");\n  ASSERT_TRUE(env[3] == 0);\n  std::string s1 = \"ASDF\";\n  std::string s2 = \"$ASDF\";\n  std::string s3 = \"${ASDF}\";\n  std::string s4 = \"1234${ASDF}1234\";\n  std::string s5 = \"1234${ASDF\";\n  std::string s6 = \"123456${ASDF}\";\n  std::string s7 = \"123456$ASDF 1234\";\n  size_t p1, p2;\n  s = cfgsub.ParseVariable(s1, p1, p2);\n  ASSERT_TRUE(s.empty());\n  ASSERT_EQ(p1, 0);\n  ASSERT_EQ(p2, 0);\n  s = cfgsub.ParseVariable(s2, p1, p2);\n  ASSERT_TRUE(s == \"ASDF\");\n  ASSERT_EQ(p1, 0);\n  ASSERT_EQ(p2, 5);\n  s = cfgsub.ParseVariable(s3, p1, p2);\n  ASSERT_TRUE(s == \"ASDF\");\n  ASSERT_EQ(p1, 0);\n  ASSERT_EQ(p2, 7);\n  s = cfgsub.ParseVariable(s4, p1, p2);\n  ASSERT_TRUE(s == \"ASDF\");\n  ASSERT_EQ(p1, 4);\n  ASSERT_EQ(p2, 11);\n  s = cfgsub.ParseVariable(s5, p1, p2);\n  ASSERT_TRUE(s.empty());\n  ASSERT_EQ(p1, 0);\n  ASSERT_EQ(p2, 0);\n  s = cfgsub.ParseVariable(s6, p1, p2);\n  ASSERT_TRUE(s == \"ASDF\");\n  ASSERT_EQ(p1, 6);\n  ASSERT_EQ(p2, 13);\n  s = cfgsub.ParseVariable(s7, p1, p2);\n  ASSERT_TRUE(s == \"ASDF\");\n  ASSERT_EQ(p1, 6);\n  ASSERT_EQ(p2, 11);\n}\n"
  },
  {
    "path": "unit_tests/common/ContainerUtilsTests.cc",
    "content": "\n#include \"gtest/gtest.h\"\n#include \"common/utils/ContainerUtils.hh\"\n#include <map>\n#include <unordered_map>\n#include <set>\n#include <unordered_set>\n#include <vector>\n#include <list>\n#include <algorithm>\n\nbool is_even(int i)\n{\n  return i % 2 == 0;\n}\n\nTEST(EraseIf, Map)\n{\n  std::map<int, std::string> m = { {1, \"one\"},\n    {2, \"two\"},\n    {3, \"three\"},\n    {4, \"four\"}\n  };\n  auto m2 = m; // copy\n  std::map<int, std::string> expected = {{1, \"one\"}, {3, \"three\"}};\n  eos::common::erase_if(m, [](const auto & p) {\n    return is_even(p.first);\n  });\n  ASSERT_EQ(expected, m);\n  eos::common::erase_if(m2, [](const auto & p) {\n    return !is_even(p.first);\n  });\n  std::map<int, std::string> expected2 = {{2, \"two\"}, {4, \"four\"}};\n  ASSERT_EQ(expected2, m2);\n}\n\nTEST(EraseIf, UnorderedMap)\n{\n  std::unordered_map<int, std::string> m = { {1, \"one\"},\n    {2, \"two\"},\n    {3, \"three\"},\n    {4, \"four\"}\n  };\n  auto m2 = m; // copy\n  std::unordered_map<int, std::string> expected = {{1, \"one\"}, {3, \"three\"}};\n  eos::common::erase_if(m, [](const auto & p) {\n    return is_even(p.first);\n  });\n  ASSERT_EQ(expected, m);\n  eos::common::erase_if(m2, [](const auto & p) {\n    return !is_even(p.first);\n  });\n  std::unordered_map<int, std::string> expected2 = {{2, \"two\"}, {4, \"four\"}};\n  ASSERT_EQ(expected2, m2);\n}\n\nTEST(EraseIf, Set)\n{\n  std::set<int> s = {1, 2, 3, 4};\n  eos::common::erase_if(s, is_even);\n  //s.erase(std::remove_if(s.begin(),s.end(),is_even));\n  std::set<int> expected = {1, 3};\n  ASSERT_EQ(expected, s);\n}\n\nTEST(EraseIf, UnorderedSet)\n{\n  std::unordered_set<int> s = {1, 2, 3, 4};\n  eos::common::erase_if(s, is_even);\n  //s.erase(std::remove_if(s.begin(),s.end(),is_even));\n  std::unordered_set<int> expected = {1, 3};\n  ASSERT_EQ(expected, s);\n}\n\nTEST(ContainerUtils, get_msb)\n{\n  ASSERT_EQ(eos::common::get_msb(1),0);\n  ASSERT_EQ(eos::common::get_msb(2),1);\n  ASSERT_EQ(eos::common::get_msb(3),1);\n  ASSERT_EQ(eos::common::get_msb(4),2);\n  ASSERT_EQ(eos::common::get_msb(1023),9);\n  ASSERT_EQ(eos::common::get_msb(1024),10);\n  ASSERT_EQ(eos::common::get_msb(0xFFFFFFFFFFFFFFFF),63);\n  ASSERT_EQ(eos::common::get_msb(1UL<<32),32);\n}\n\nTEST(ContainerUtils, clamp_index)\n{\n  ASSERT_EQ(eos::common::clamp_index(4,10), 4);\n  ASSERT_EQ(eos::common::clamp_index(10,10), 0);\n  ASSERT_EQ(eos::common::clamp_index(24,8), 0);\n  ASSERT_EQ(eos::common::clamp_index(24,16), 8);\n  ASSERT_EQ(eos::common::clamp_index(25,8), 1);\n  ASSERT_EQ(eos::common::clamp_index(1ULL<<32, 0xFFFFFFFF), 1);\n  ASSERT_EQ(eos::common::clamp_index(1ULL<<48, 1ULL<<32), 0);\n  ASSERT_EQ(eos::common::clamp_index((1ULL<<32) + (1UL<<16), 1ULL<<32), 1UL<<16);\n}\n\nusing eos::common::pickIndexRR;\n\nTEST(pickIndexRR, list)\n{\n  using Cont = std::list<int>;\n  Cont C {1, 2, 3};\n  // repeat this 4 times\n  Cont expected {\n    1, 2, 3,\n    1, 2, 3,\n    1, 2, 3,\n    1, 2, 3\n  };\n  Cont actual;\n\n  for (int i = 0; i < 12; ++i) {\n    actual.emplace_back(pickIndexRR(C, i));\n  }\n\n  ASSERT_EQ(expected, actual);\n  Cont C2{1};\n\n  for (int i = 0; i < 12; ++i) {\n    EXPECT_EQ(pickIndexRR(C2, i), 1);\n  }\n}\n\nTEST(pickIndexRR, exception)\n{\n  std::list<int> C;\n  EXPECT_THROW(pickIndexRR(C, 0), std::out_of_range);\n  std::vector<int> V{};\n  EXPECT_THROW(pickIndexRR(V, 0), std::out_of_range);\n  std::vector<int> v(1);\n  ASSERT_EQ(v.size(), 1);\n  EXPECT_NO_THROW(pickIndexRR(v, 1));\n  EXPECT_EQ(pickIndexRR(v, 2), 0);\n}\n\nTEST(pickIndexRR, Set)\n{\n  using Cont = std::set<int>;\n  Cont C {1, 2, 3};\n  // repeat this 4 times\n  std::vector<int> expected {\n    1, 2, 3,\n    1, 2, 3,\n    1, 2, 3,\n    1, 2, 3\n  };\n  std::vector<int> actual;\n\n  for (int i = 0; i < 12; ++i) {\n    actual.emplace_back(pickIndexRR(C, i));\n  }\n\n  ASSERT_EQ(expected, actual);\n}\n\nTEST(pickIndexRR, UnorderedSet)\n{\n  using Cont = std::unordered_set<int>;\n  Cont C {1, 2, 3};\n  std::vector<int> base;\n  base.insert(base.end(), C.begin(), C.end());\n  ASSERT_EQ(base.size(), 3);\n  ASSERT_EQ(C.size(), 3);\n  // Copy contents back to another hash set to check that we are equal ie\n  // base == C\n  Cont base_set;\n\n  for (const auto& item : base) {\n    base_set.emplace(item);\n  }\n\n  ASSERT_EQ(C, base_set);\n  // repeat this 4 times for our good ol RR\n  std::vector<int> expected;\n\n  for (int i = 0; i < 4; i++) {\n    expected.insert(std::end(expected), std::begin(base), std::end(base));\n  }\n\n  std::vector<int> actual;\n\n  for (int i = 0; i < 12; ++i) {\n    actual.emplace_back(pickIndexRR(C, i));\n  }\n\n  ASSERT_EQ(expected, actual);\n}\n\n// TESTING for remove_if\nTEST(StdEraseIf, vector)\n{\n  std::vector<int> v = {1, 2, 3, 4};\n  //eos::common::erase_if(v, is_even); will not compile\n  v.erase(std::remove_if(v.begin(), v.end(), is_even));\n  std::vector<int> expected = {1, 3, 4}; // remove_if only does [first, last);\n  ASSERT_EQ(expected, v);\n}\n\n\nTEST(splice, simple_vector_move_append)\n{\n  std::vector<int> v {1, 2, 3, 4};\n  std::vector<int> v2 {5, 6, 7};\n  eos::common::splice(v, std::move(v2));\n  std::vector<int> expected {1, 2, 3, 4, 5, 6, 7};\n  ASSERT_EQ(expected, v);\n}\n\nTEST(ContainerUtils, next_power2)\n{\n  ASSERT_EQ(eos::common::next_power2(0), 1);\n  ASSERT_EQ(eos::common::next_power2(1), 1);\n  ASSERT_EQ(eos::common::next_power2(2), 2);\n  ASSERT_EQ(eos::common::next_power2(3), 4);\n  ASSERT_EQ(eos::common::next_power2(4), 4);\n  ASSERT_EQ(eos::common::next_power2(5), 8);\n  ASSERT_EQ(eos::common::next_power2(9), 16);\n  ASSERT_EQ(eos::common::next_power2(1025), 2048);\n  ASSERT_EQ(eos::common::next_power2(0xFFFFFFFF), 0x100000000);\n}\n"
  },
  {
    "path": "unit_tests/common/CounterTests.cc",
    "content": "// ----------------------------------------------------------------------\n// File: CounterTests.cc\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Counter.hh\"\n#include <gtest/gtest.h>\n\nTEST(Counter, Init) {\n  using namespace std::chrono;\n\n  eos::common::Counter counter;\n  counter.Init();\n  auto now = eos::common::SteadyClock::now(nullptr);\n  auto diff = duration_cast<seconds>(counter.GetStartTime() - now).count();\n  EXPECT_EQ(diff,0);\n  EXPECT_EQ(counter.GetFrequency(), 0);\n  EXPECT_EQ(counter.GetLastFrequency(), 0);\n}\n\nTEST(Counter, GetFrequency)\n{\n  eos::common::SteadyClock fake_clock(true);\n  eos::common::Counter counter(&fake_clock);\n  counter.Init();\n  fake_clock.advance(std::chrono::seconds(1));\n  counter.Increment(100);\n  EXPECT_EQ(counter.GetFrequency(), 100);\n  EXPECT_EQ(counter.GetLastFrequency(), 100);\n}\n\nTEST(Counter, GetLastFrequency)\n{\n  eos::common::SteadyClock fake_clock(true);\n  eos::common::Counter counter(&fake_clock);\n  counter.Init();\n  fake_clock.advance(std::chrono::seconds(1));\n  counter.Increment(100);\n  EXPECT_EQ(counter.GetLastFrequency(), 100);\n  EXPECT_EQ(counter.GetFrequency(),100);\n\n  fake_clock.advance(std::chrono::seconds(1));\n  counter.Increment(300);\n  EXPECT_EQ(counter.GetLastFrequency(), 300);\n  EXPECT_EQ(counter.GetFrequency(), 200);\n}\n"
  },
  {
    "path": "unit_tests/common/EosTokenTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: EosTokenTests.cc\n// Author: Andreas-Joachim Peters <andreas.joachim.peters@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"common/token/EosTok.hh\"\n\n//------------------------------------------------------------------------------\n// Test Tokens\n//------------------------------------------------------------------------------\nTEST(EosTokenTests, Tokens) {\n  eos::common::EosTok token;\n  std::string path = \"/eos/token/test/\";\n  token.SetPath(path, true);\n  std::string perm = \"rwx\";\n  token.SetPermission(perm);\n  std::string owner = \"myuser\";\n  std::string group = \"mygroup\";\n  token.SetOwner(owner);\n  token.SetGroup(group);\n  time_t now = time(NULL);\n  token.SetExpires(now + 10);\n  token.SetGeneration(0);\n  std::string dump;\n  token.Dump(dump);\n  token.AddOrigin(\"*\",\"*\",\"*\");\n  \n  //  std::cout << dump.c_str() << std::endl;\n  std::string key = \"1234567890\";\n  std::string btoken = token.Write(key);\n\n  token.Dump(dump);\n  //  std::cout << dump << btoken << std::endl;\n\n  eos::common::EosTok reversetoken;\n  \n  // Test Token writing\n  ASSERT_TRUE(reversetoken.Read(btoken, key, 0)==0);\n\n  std::string reversedump;\n  reversetoken.Dump(reversedump);\n  //  std::cout << reversedump << std::endl;\n\n  // Test Token reading\n  ASSERT_EQ(dump, reversedump);\n\n  reversetoken.Reset();\n  reversetoken.Dump(reversedump);\n  //  std::cout << reversedump << std::endl;\n\n  std::string expectreversedump = \n    \"{\\n\"\n    \" \\\"signature\\\": \\\"\\\",\\n\"\n    \" \\\"serialized\\\": \\\"\\\",\\n\"\n    \" \\\"seed\\\": 0\\n\"\n    \"}\\n\";\n\n\n  // Test Token Reset\n  ASSERT_EQ(reversedump, expectreversedump);\n\n  // Test Token Reading with new generation\n  ASSERT_TRUE(reversetoken.Read(btoken, key, 1)!=0);\n\n  // Test Token Reading with wrong key\n  ASSERT_TRUE(reversetoken.Read(btoken, key+\"z\", 0)!=0);\n\n  // Test Token Reading with wrong token\n  std::string faultytoken = btoken.erase(10,1);\n  ASSERT_TRUE(reversetoken.Read(faultytoken, key, 0)!=0);\n\n\n  // Test Token expired;\n  now = time(NULL);\n  token.SetExpires(now+1);\n  btoken = token.Write(key);\n  ASSERT_TRUE(reversetoken.Read(btoken, key, 0)==0);\n  sleep(2);\n  ASSERT_TRUE(reversetoken.Read(btoken, key, 0)!=0);\n}\n\n\nTEST(EosTokenTests, Origins) {\n  eos::common::EosTok token;\n\n  ASSERT_TRUE(!token.VerifyOrigin(\"eos.cern.ch\",\"admin\",\"sss\"));\n  token.AddOrigin(\"(.*)\",\"(.*)\",\"(.*)\");\n\n  ASSERT_TRUE(!token.VerifyOrigin(\"eos.cern.ch\",\"root\",\"sss\"));\n  \n  token.Reset();\n  \n  token.AddOrigin(\"host(.*)\", \"(.*)\",\"(.*)\");\n  ASSERT_TRUE(!token.VerifyOrigin(\"host.cern.ch\",\"root\",\"sss\"));\n  ASSERT_TRUE(!token.VerifyOrigin(\"hosty.cern.ch\",\"root\",\"sss\"));\n  ASSERT_TRUE(token.VerifyOrigin(\"tosty.cern.ch\",\"root\",\"sss\"));\n}\n"
  },
  {
    "path": "unit_tests/common/FileIdTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FileIdTests.cc\n// Author: Elvin-Alin Sindrilaru <esindril at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#define IN_TEST_HARNESS\n#include \"common/FileId.hh\"\n#undef IN_TEST_HARNESS\n\nconst uint64_t GB = (uint64_t)std::pow(2, 30);\n\nTEST(FileId, EstimateTpcTimeout)\n{\n  using eos::common::FileId;\n  ASSERT_EQ(FileId::EstimateTpcTimeout(1).count(), 1800);\n  ASSERT_EQ(FileId::EstimateTpcTimeout(50 * GB).count(), 2048);\n  ASSERT_EQ(FileId::EstimateTpcTimeout(60 * GB, 30).count(), 2048);\n  ASSERT_EQ(FileId::EstimateTpcTimeout(100 * GB, 100).count(), 1800);\n  ASSERT_EQ(FileId::EstimateTpcTimeout(250 * GB, 100).count(), 2560);\n}\n\n\nTEST(FileId, DefaultConstruction)\n{\n  eos::common::FileId::fileid_t fid{0};\n  ASSERT_EQ(fid, 0);\n}\n"
  },
  {
    "path": "unit_tests/common/FileMapTests.cc",
    "content": "\n//------------------------------------------------------------------------------\n// File: FileMapTests.cc\n// Author: Georgios Bitzes <georgios.bitzes@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"common/FileMap.hh\"\n\nTEST(FileMap, BasicSanity) {\n  eos::common::FileMap map;\n\n  map.Set(\"k1\", \"v1\");\n  map.Set(\"k2\", \"v2\");\n  map.Set(\"k3\", \"v3\");\n\n\n  ASSERT_EQ(map.Trim(),\n    \"+ azE= djE=\\n\"\n    \"+ azI= djI=\\n\"\n    \"+ azM= djM=\\n\");\n}"
  },
  {
    "path": "unit_tests/common/FutureWrapperTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FutureWrapperTests.cc\n// Author: Georgios Bitzes <georgios.bitzes@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"Namespace.hh\"\n#include \"common/FutureWrapper.hh\"\n\nEOSCOMMONTESTING_BEGIN\n\nTEST(FutureWrapper, BasicSanity) {\n  folly::Promise<int> promise;\n  common::FutureWrapper<int> fut(promise.getFuture());\n  ASSERT_FALSE(fut.ready());\n\n  promise.setValue(5);\n  ASSERT_TRUE(fut.ready());\n  ASSERT_EQ(fut.get(), 5);\n}\n\nTEST(FutureWrapper, Exception) {\n  folly::Promise<int> promise;\n  common::FutureWrapper<int> fut(promise.getFuture());\n  ASSERT_FALSE(fut.ready());\n\n  promise.setException(folly::exception_wrapper(std::runtime_error(\"something terrible happened\")));\n  ASSERT_TRUE(fut.ready());\n\n  try {\n    fut.get();\n    FAIL(); // should never reach here\n  }\n  catch(const std::runtime_error &exc) { // yes, you can use strings as exceptions\n    ASSERT_STREQ(exc.what(), \"something terrible happened\");\n  }\n}\n\nEOSCOMMONTESTING_END\n"
  },
  {
    "path": "unit_tests/common/GlobTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: GlobTests.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Glob.hh\"\n#include \"gtest/gtest.h\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\nTEST(Glob, BasicFunctionality)\n{\n  using eos::common::Glob;\n  Glob glob;\n  ASSERT_TRUE(glob.Match(\"asdf*.txt\", \"asdf1.txt\"));\n  ASSERT_FALSE(glob.Match(\"asdf*.txt\", \"bsdf1.txt\"));\n  ASSERT_TRUE(glob.Match(\"*.txt\", \"asdf1.txt\"));\n  ASSERT_TRUE(glob.Match(\"a?df1.txt\", \"asdf1.txt\"));\n  ASSERT_FALSE(glob.Match(\"asdf?.txt\", \"bsdf1.txt\"));\n  ASSERT_FALSE(glob.Match(\"number{1..9}pattern\",\"10\"));\n  ASSERT_FALSE(glob.Match(\"test\",\"\"));\n  ASSERT_FALSE(glob.Match(\"regexx*p.tt\\\\.ern\",\"regexx*p.tt\\\\.ern\"));\n}\n\nTEST(Glob, Performance)\n{\n  using eos::common::Glob;\n  Glob glob;\n  char a[2];\n  a[1]=0;\n  for (auto it=0; it < 100000; it++) {\n    a[0]=it%256;\n    ASSERT_FALSE(glob.Match(\"asdf*.txt\", a)) ;\n    ASSERT_TRUE(glob.Match(\"asdf*.txt\", \"asdf1.txt\"));\n  }\n}\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "unit_tests/common/InodeTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: InodeTests.cc\n// Author: Georgios Bitzes <georgios.bitzes@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"Namespace.hh\"\n#include \"common/FileId.hh\"\n#include \"common/InodeTranslator.hh\"\n#include \"gtest/gtest.h\"\n#include <iostream>\n#include <random>\n\n#define DBG(message) std::cerr << __FILE__ << \":\" << __LINE__ << \" -- \" << #message << \" = \" << message << std::endl\n\nEOSCOMMONTESTING_BEGIN\n\nusing namespace eos::common;\n\nTEST(Inode, ValidateLegacyEncodingRange) {\n  ASSERT_EQ(FileId::LegacyIsFileInode(1), false);\n  ASSERT_EQ(FileId::LegacyIsFileInode(2), false);\n  ASSERT_EQ(FileId::LegacyIsFileInode(268435454), false);\n  ASSERT_EQ(FileId::LegacyIsFileInode(268435455), false);\n\n  ASSERT_EQ(FileId::LegacyIsFileInode(268435456), true);\n  ASSERT_EQ(FileId::LegacyIsFileInode(268435457), true);\n  ASSERT_EQ(FileId::LegacyIsFileInode(268435458), true);\n  ASSERT_EQ(FileId::LegacyIsFileInode(20000000000), true);\n\n  // From this point on, the legacy scheme only uses 1 inode per 256M ...\n  ASSERT_EQ(FileId::LegacyFidToInode(1), 268435456);\n  ASSERT_EQ(FileId::LegacyFidToInode(2), 536870912);\n  ASSERT_EQ(FileId::LegacyFidToInode(3), 805306368);\n  ASSERT_EQ(FileId::LegacyFidToInode(4), 1073741824);\n  ASSERT_EQ(FileId::LegacyFidToInode(5), 1342177280);\n  ASSERT_EQ(FileId::LegacyFidToInode(6), 1610612736);\n}\n\nTEST(Inode, ValidateNewEncodingRange) {\n  ASSERT_EQ(FileId::NewIsFileInode(1), false);\n  ASSERT_EQ(FileId::NewIsFileInode(2), false);\n\n  ASSERT_EQ(FileId::LegacyIsFileInode(1), false);\n  ASSERT_EQ(FileId::LegacyIsFileInode(2), false);\n  ASSERT_EQ(FileId::LegacyIsFileInode(268435454), false);\n  ASSERT_EQ(FileId::LegacyIsFileInode(268435455), false);\n\n  ASSERT_EQ(FileId::NewIsFileInode(268435456), false);\n  ASSERT_EQ(FileId::NewIsFileInode(268435457), false);\n  ASSERT_EQ(FileId::NewIsFileInode(268435458), false);\n  ASSERT_EQ(FileId::NewIsFileInode(268435459), false);\n  ASSERT_EQ(FileId::NewIsFileInode(268435460), false);\n\n  ASSERT_EQ(FileId::NewFidToInode(1), 9223372036854775809ull);\n  ASSERT_EQ(FileId::NewFidToInode(2), 9223372036854775810ull);\n  ASSERT_EQ(FileId::NewFidToInode(3), 9223372036854775811ull);\n  ASSERT_EQ(FileId::NewFidToInode(4), 9223372036854775812ull);\n  ASSERT_EQ(FileId::NewFidToInode(5), 9223372036854775813ull);\n  ASSERT_EQ(FileId::NewFidToInode(6), 9223372036854775814ull);\n\n  ASSERT_FALSE(FileId::NewIsFileInode(1ull));\n  ASSERT_FALSE(FileId::NewIsFileInode(2ull));\n  ASSERT_FALSE(FileId::NewIsFileInode(3ull));\n  ASSERT_FALSE(FileId::NewIsFileInode(4ull));\n\n  ASSERT_FALSE(FileId::NewIsFileInode(9223372036854775807ull));\n  ASSERT_TRUE(FileId::NewIsFileInode(9223372036854775808ull));\n  ASSERT_TRUE(FileId::NewIsFileInode(9223372036854775809ull));\n  ASSERT_TRUE(FileId::NewIsFileInode(9223372036854775810ull));\n  ASSERT_TRUE(FileId::NewIsFileInode(9223372036854775811ull));\n  ASSERT_TRUE(FileId::NewIsFileInode(9223372036854775812ull));\n}\n\nTEST(Inode, ValidateCollisionsBetweenLegacyAndNew) {\n  // For the first 256M directories, the two implementations of IsFileInode\n  // are compatible.\n  ASSERT_EQ(FileId::NewIsFileInode(1ull), FileId::LegacyIsFileInode(1ull));\n  ASSERT_EQ(FileId::NewIsFileInode(2ull), FileId::LegacyIsFileInode(2ull));\n  ASSERT_EQ(FileId::NewIsFileInode(3ull), FileId::LegacyIsFileInode(3ull));\n  ASSERT_EQ(FileId::NewIsFileInode(4ull), FileId::LegacyIsFileInode(4ull));\n  ASSERT_EQ(FileId::NewIsFileInode(5ull), FileId::LegacyIsFileInode(5ull));\n  ASSERT_EQ(FileId::NewIsFileInode(6ull), FileId::LegacyIsFileInode(6ull));\n  ASSERT_EQ(FileId::NewIsFileInode(7ull), FileId::LegacyIsFileInode(7ull));\n  ASSERT_EQ(FileId::NewIsFileInode(268435454ull), FileId::LegacyIsFileInode(268435454ull));\n  ASSERT_EQ(FileId::NewIsFileInode(268435455ull), FileId::LegacyIsFileInode(268435455ull));\n\n  // Compatibility breaks down after 256M directories, as expected\n  ASSERT_NE(FileId::NewIsFileInode(268435456ull), FileId::LegacyIsFileInode(268435456ull));\n  ASSERT_NE(FileId::NewIsFileInode(268435457ull), FileId::LegacyIsFileInode(268435457ull));\n  ASSERT_NE(FileId::NewIsFileInode(268435458ull), FileId::LegacyIsFileInode(268435458ull));\n\n  // At which point file inodes collide?\n  ASSERT_EQ(FileId::NewFidToInode(1), 9223372036854775809ull);\n\n  // 2^35 is the highest safe number of files we can go and maintain compatibility\n  // between the two schemes.\n  ASSERT_EQ(FileId::LegacyFidToInode(34359738368ull), FileId::NewFidToInode(1) - 1);\n\n  // LegacyFidToInode works for 34359738368ull - what about LegacyInodeToFid?\n  ASSERT_EQ(FileId::LegacyInodeToFid(FileId::LegacyInodeToFid(34359738368ull)), 0ull);\n\n  // Nope! It overflows at exactly the same point where the new encoding scheme\n  // takes effect. (wasting one more bit of its theoretical capacity)\n  // There's zero collisions for files between old encoding scheme and new one.\n\n  // At exactly 2^36 (68B files), the legacy encoding scheme breaks down completely,\n  // including FidToInode.\n  ASSERT_EQ(FileId::LegacyFidToInode(68719476735ull), 18446744073441116160ull); // 2^36 - 1 files\n  ASSERT_EQ(FileId::LegacyFidToInode(68719476736ull), 0ull); // 2^36 files\n}\n\nTEST(Inode, InodeToFidCompatibility) {\n  // InodeToFid dispatches to the appropriate function, depending which scheme\n  // is being used. Validate it's able to handle both encodings.\n\n  ASSERT_EQ(FileId::InodeToFid(FileId::NewFidToInode(1ull)), 1ull);\n  ASSERT_EQ(FileId::InodeToFid(FileId::LegacyFidToInode(1ull)), 1ull);\n\n  ASSERT_EQ(FileId::InodeToFid(FileId::NewFidToInode(2ull)), 2ull);\n  ASSERT_EQ(FileId::InodeToFid(FileId::LegacyFidToInode(2ull)), 2ull);\n\n  ASSERT_EQ(FileId::InodeToFid(FileId::NewFidToInode(3ull)), 3ull);\n  ASSERT_EQ(FileId::InodeToFid(FileId::LegacyFidToInode(3ull)), 3ull);\n\n  ASSERT_EQ(FileId::InodeToFid(FileId::NewFidToInode(4ull)), 4ull);\n  ASSERT_EQ(FileId::InodeToFid(FileId::LegacyFidToInode(4ull)), 4ull);\n\n  ASSERT_EQ(FileId::InodeToFid(FileId::NewFidToInode(5ull)), 5ull);\n  ASSERT_EQ(FileId::InodeToFid(FileId::LegacyFidToInode(5ull)), 5ull);\n\n  ASSERT_EQ(FileId::InodeToFid(FileId::NewFidToInode(6ull)), 6ull);\n  ASSERT_EQ(FileId::InodeToFid(FileId::LegacyFidToInode(6ull)), 6ull);\n\n  ASSERT_EQ(FileId::InodeToFid(FileId::NewFidToInode(34359738367ull)), 34359738367ull);\n  ASSERT_EQ(FileId::InodeToFid(FileId::LegacyFidToInode(34359738367ull)), 34359738367ull);\n\n  // Randomize testing by generating random numbers between 1 and the maximum\n  // valid inode supported by legacy encoding: 34359738368ull.\n\n  std::mt19937_64 gen(12345678);\n  std::uniform_int_distribution<uint64_t> dist(1ull, 34359738368ull);\n\n  for(size_t i = 0; i < 10'000'000; i++) { // 10M rounds\n    uint64_t randomID = dist(gen);\n    ASSERT_EQ(FileId::InodeToFid(FileId::NewFidToInode(randomID)), randomID);\n    ASSERT_EQ(FileId::InodeToFid(FileId::LegacyFidToInode(randomID)), randomID);\n\n    ASSERT_TRUE(FileId::IsFileInode(FileId::NewFidToInode(randomID)));\n    ASSERT_TRUE(FileId::IsFileInode(FileId::LegacyFidToInode(randomID)));\n  }\n}\n\nTEST(InodeTranslator, BasicSanity) {\n  InodeTranslator oldTranslator;\n  ASSERT_EQ(oldTranslator.InodeToFid(FileId::LegacyFidToInode(1)), 1);\n  ASSERT_EQ(oldTranslator.FidToInode(1), FileId::LegacyFidToInode(1));\n\n  InodeTranslator newTranslator;\n  ASSERT_EQ(newTranslator.InodeToFid(FileId::NewFidToInode(1)), 1);\n  ASSERT_EQ(newTranslator.FidToInode(1), FileId::NewFidToInode(1));\n}\n\n// TEST(InodeTranslatorDeathTest, NoFidToInodeBeforeKnowingEncodingScheme) {\n//   InodeTranslator translator;\n//   ASSERT_DEATH(translator.FidToInode(133), \"\");\n// }\n\n// TEST(InodeTranslatorDeathTest, NoLegacyThenNew) {\n//   InodeTranslator translator;\n//   ASSERT_EQ(translator.InodeToFid(FileId::LegacyFidToInode(5)), 5);\n//   ASSERT_DEATH(translator.InodeToFid(FileId::NewFidToInode(6)), \"\");\n// }\n\n// TEST(InodeTranslatorDeathTest, NoNewThenLegacy) {\n//   InodeTranslator translator;\n//   ASSERT_EQ(translator.InodeToFid(FileId::NewFidToInode(5)), 5);\n//   ASSERT_DEATH(translator.InodeToFid(FileId::LegacyFidToInode(6)), \"\");\n// }\n\nEOSCOMMONTESTING_END\n"
  },
  {
    "path": "unit_tests/common/LoggingTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: LoggingTests.cc\n// Author: Elvin-Alin Sindrilaru <esindril at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n#include \"Namespace.hh\"\n#include \"gtest/gtest.h\"\n\n//------------------------------------------------------------------------------\n// Test the proper static allocation and destruction of the global logging\n// object in Logging.hh\n//------------------------------------------------------------------------------\n\nextern void function_using_logging();\n\nEOSCOMMONTESTING_BEGIN\n\nTEST(Logging, Log)\n{\n  using namespace eos::common;\n  gLogging.SetLogPriority(LOG_INFO);\n  eos_static_info(\"%s %s\", __FUNCTION__, \"print a test log line\");\n  function_using_logging();\n}\n\nEOSCOMMONTESTING_END\n"
  },
  {
    "path": "unit_tests/common/LoggingTestsUtils.cc",
    "content": "//------------------------------------------------------------------------------\n// File: LoggingTestsUtils.cc\n// Author: Elvin-Alin Sindrilaru <esindril at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Logging.hh\"\n\nvoid function_using_logging()\n{\n  eos_static_info(\"%s %s\", __FUNCTION__, \"print a test log line\");\n  return;\n}\n"
  },
  {
    "path": "unit_tests/common/MappingTestFixture.hh",
    "content": "// ----------------------------------------------------------------------\n// File: MappingTestFixture.hh\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n/************************************************************************\n  * EOS - the CERN Disk Storage System                                   *\n  * Copyright (C) 2022 CERN/Switzerland                           *\n  *                                                                      *\n  * This program is free software: you can redistribute it and/or modify *\n  * it under the terms of the GNU General Public License as published by *\n  * the Free Software Foundation, either version 3 of the License, or    *\n  * (at your option) any later version.                                  *\n  *                                                                      *\n  * This program is distributed in the hope that it will be useful,      *\n  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n  * GNU General Public License for more details.                         *\n  *                                                                      *\n  * You should have received a copy of the GNU General Public License    *\n  * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n  ************************************************************************/\n\n#ifndef EOS_MAPPINGTESTFIXTURE_HH\n#define EOS_MAPPINGTESTFIXTURE_HH\n#include \"common/Mapping.hh\"\n#include \"gtest/gtest.h\"\n\nclass MappingTestF : public ::testing::Test {\nprotected:\n\n  virtual void SetUp() {\n    eos::common::Mapping::Init();\n  }\n\n  virtual void TearDown() {\n    eos::common::Mapping::Reset();\n  }\n};\n\n#endif // EOS_MAPPINGTESTFIXTURE_HH\n"
  },
  {
    "path": "unit_tests/common/MappingTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: MappingTests.cc\n// Author: Elvin-Alin Sindrilaru <esindril at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"gmock/gmock.h\"\n#include \"Namespace.hh\"\n\n#define IN_TEST_HARNESS\n#include \"common/Mapping.hh\"\n#undef IN_TEST_HARNESS\n\n#include <XrdSec/XrdSecEntity.hh>\n#include <memory>\n#include \"common/UnixGroupsFetcher.hh\"\n\n\nEOSCOMMONTESTING_BEGIN\n\nvoid FreeXrdSecEntity(XrdSecEntity* client)\n{\n  free(client->name);\n  free(client->host);\n  free(client->vorg);\n  free(client->role);\n  free(client->grps);\n  free(client->caps);\n  free(client->endorsements);\n  free(client->moninfo);\n  free(client->creds);\n  free((char*)client->tident);\n}\n\nTEST(Mapping, VidAssignOperator)\n{\n  using namespace eos::common;\n  VirtualIdentity vid;\n  vid.uid = 99;\n  vid.gid = 99;\n  vid.uid_string = \"99\";\n  vid.gid_string = \"99\";\n  vid.allowed_uids = {2, 3, 99};\n  vid.allowed_gids = {2, 4, 99};\n  vid.tident = \"client:process_id:sockd_fd\";\n  vid.name = \"dummy_user\";\n  vid.prot = \"xrootd\";\n  vid.host = \"localhost.localdomain\";\n  vid.grps = \"some_random_grps\";\n  vid.role = \"some_random_role\";\n  vid.dn = \"some_random_dn\";\n  vid.geolocation = \"some_random_geoloacation\";\n  vid.app = \"some_random_app\";\n  vid.sudoer = true;\n  VirtualIdentity copy_vid = vid;\n  ASSERT_EQ(vid.uid, copy_vid.uid);\n  ASSERT_EQ(vid.gid, copy_vid.gid);\n  ASSERT_EQ(vid.uid_string, copy_vid.uid_string);\n  ASSERT_EQ(vid.gid_string, copy_vid.gid_string);\n  ASSERT_EQ(vid.allowed_uids, copy_vid.allowed_uids);\n  ASSERT_EQ(vid.allowed_gids, copy_vid.allowed_gids);\n  ASSERT_STREQ(vid.tident.c_str(), copy_vid.tident.c_str());\n  ASSERT_STREQ(vid.name.c_str(),  copy_vid.name.c_str());\n  ASSERT_STREQ(vid.prot.c_str(), copy_vid.prot.c_str());\n  ASSERT_EQ(vid.host, copy_vid.host);\n  ASSERT_EQ(vid.grps, copy_vid.grps);\n  ASSERT_EQ(vid.role, copy_vid.role);\n  ASSERT_EQ(vid.dn, copy_vid.dn);\n  ASSERT_EQ(vid.geolocation, copy_vid.geolocation);\n  ASSERT_EQ(vid.app, copy_vid.app);\n  ASSERT_EQ(vid.sudoer, copy_vid.sudoer);\n  ASSERT_TRUE(vid.hasUid(2));\n  ASSERT_TRUE(copy_vid.hasUid(2));\n  ASSERT_TRUE(vid.hasUid(3));\n  ASSERT_TRUE(copy_vid.hasUid(3));\n  ASSERT_TRUE(vid.hasUid(99));\n  ASSERT_TRUE(copy_vid.hasUid(99));\n  ASSERT_FALSE(vid.hasUid(4));\n  ASSERT_FALSE(copy_vid.hasUid(4));\n  ASSERT_TRUE(vid.hasGid(4));\n  ASSERT_TRUE(copy_vid.hasGid(4));\n  ASSERT_FALSE(vid.hasGid(3));\n  ASSERT_FALSE(copy_vid.hasGid(3));\n}\n\nTEST(VirtualIdentity, IsLocalhost)\n{\n  VirtualIdentity vid;\n  vid.host = \"localhost\";\n  ASSERT_TRUE(vid.isLocalhost());\n  vid.host = \"localhost6\";\n  ASSERT_TRUE(vid.isLocalhost());\n  vid.host = \"localhost.localdomain\";\n  ASSERT_TRUE(vid.isLocalhost());\n  vid.host = \"localhost6.localdomain6\";\n  ASSERT_TRUE(vid.isLocalhost());\n  vid.host = \"pickles\";\n  ASSERT_FALSE(vid.isLocalhost());\n  vid.host = \"asdf\";\n  ASSERT_FALSE(vid.isLocalhost());\n}\n\n\nTEST(VirtualIdentity, HandleKEYS)\n{\n  using namespace eos::common;\n  const std::string secret_key = \"xyz_my_secret_key_xyz\";\n  VirtualIdentity vid;\n  auto EntityDeleter = [](XrdSecEntity * client) {\n    FreeXrdSecEntity(client);\n    delete client;\n  };\n  std::unique_ptr<XrdSecEntity, decltype(EntityDeleter)>\n  client(new XrdSecEntity(\"gsi\"), EntityDeleter);\n  client->name = strdup(\"random\");\n  client->host = strdup(\"[::ffff:172.24.76.44]\");\n  client->vorg = strdup(\"cms cms cms\");\n  client->role = strdup(\"production NULL NULL NULL\");\n  client->grps = strdup(\"/cms /cms /cms/country /cms/country/us /cms/uscms\");\n  client->caps = nullptr;\n  client->endorsements = strdup(secret_key.c_str());\n  client->moninfo = nullptr;\n  client->creds = nullptr;\n  client->credslen = 0;\n  client->ueid = 0xdead;\n  client->addrInfo = nullptr;\n  client->tident = strdup(\"http\");\n  client->pident = nullptr;\n  client->sessvar = nullptr;\n  client->uid = 0;\n  client->gid = 0;\n  // This is happenndin in the IdMap for sss/grpc/https\n  vid.key = client->endorsements;\n  // Add VOMS mapping\n  const std::string_view uid_voms = \"voms:\\\"/cms:production\\\":uid\";\n  const std::string_view gid_voms = \"voms:\\\"/cms:production\\\":gid\";\n  uid_t mapped_uid = 81;\n  gid_t mapped_gid = 81;\n  Mapping::gVirtualUidMap.emplace(uid_voms, mapped_uid);\n  Mapping::gVirtualGidMap.emplace(gid_voms, mapped_gid);\n  // Add specific EOS KEY mapping\n  std::string uid_key = \"https:\\\"key:abbabeefdeadabba\\\":uid\";\n  std::string gid_key = \"https:\\\"key:abbabeefdeadabba\\\":gid\";\n  mapped_uid = 32;\n  mapped_gid = 32;\n  Mapping::gVirtualUidMap.emplace(uid_key, mapped_uid);\n  Mapping::gVirtualGidMap.emplace(gid_key, mapped_gid);\n  Mapping::HandleVOMS(client.get(), vid);\n  ASSERT_EQ(81, vid.uid);\n  ASSERT_EQ(81, vid.gid);\n  // The key should not match so there should be no change\n  Mapping::HandleKEYS(client.get(), vid);\n  ASSERT_EQ(81, vid.uid);\n  ASSERT_EQ(81, vid.gid);\n  // Add a new key mapping to match the given endorsements\n  uid_key = \"https:\\\"key:\";\n  uid_key += secret_key;\n  uid_key += \"\\\":uid\";\n  gid_key = \"https:\\\"key:\";\n  gid_key += secret_key;\n  gid_key += \"\\\":gid\";\n  Mapping::gVirtualUidMap.emplace(uid_key, mapped_uid);\n  Mapping::gVirtualGidMap.emplace(gid_key, mapped_gid);\n  // The key matches so the mapped identity should also match\n  Mapping::HandleKEYS(client.get(), vid);\n  ASSERT_EQ(32, vid.uid);\n  ASSERT_EQ(32, vid.gid);\n}\n\n\nstruct MockGroupListProvider : UnixGroupListFetcher {\n  MOCK_METHOD(std::vector<gid_t>, getGroups, (const std::string&, gid_t), (override));\n};\n\nclass SecondaryGroupF: public ::testing::Test {\nprotected:\n  void SetUp() override {\n    Mapping::gSecondaryGroups = true;\n    Mapping::gGroupsFetcher = std::make_unique<MockGroupListProvider>();\n  }\n  void TearDown() override {\n    Mapping::gGroupsFetcher.reset();\n  }\n};\n\nvoid printvid(const VirtualIdentity& vid) {\n  std::cerr << vid.getTrace() << \"\\nallowed gids: \";\n  for (const auto& gid: vid.allowed_gids) {\n    std::cerr << gid << \",\";\n  }\n}\n\nTEST_F(SecondaryGroupF, AddSecondaryGroups)\n{\n  VirtualIdentity vid;\n  const std::string name = \"dummy_user\";\n  const gid_t gid = 99;\n  ASSERT_NE(Mapping::gGroupsFetcher, nullptr);\n  MockGroupListProvider* mock = dynamic_cast<MockGroupListProvider*>(Mapping::gGroupsFetcher.get());\n  ASSERT_NE(mock, nullptr);\n\n  EXPECT_CALL(*mock, getGroups(name, gid))\n  .WillOnce(::testing::Return(std::vector<gid_t>{2, 3, 4}));\n  Mapping::addSecondaryGroups(vid, name, gid);\n  printvid(vid);\n  EXPECT_TRUE(vid.hasGid(2));\n  EXPECT_TRUE(vid.hasGid(3));\n  EXPECT_TRUE(vid.hasGid(4));\n  EXPECT_FALSE(vid.hasGid(5));\n}\n\nEOSCOMMONTESTING_END\n"
  },
  {
    "path": "unit_tests/common/MemConfigStore.hh",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_MEMCONFIGSTORE_HH\n#define EOS_MEMCONFIGSTORE_HH\n\n#include \"common/config/ConfigStore.hh\"\n\nclass MemConfigStore : public eos::common::ConfigStore\n{\npublic:\n  bool save(const std::string& key, const std::string& val) override\n  {\n    kvstore[key] = val;\n    return true;\n  }\n\n  std::string load(const std::string& key) override\n  {\n    if (auto kv = kvstore.find(key);\n        kv != kvstore.end()) {\n      return kv->second;\n    }\n\n    return {};\n  }\n\nprivate:\n  std::map<std::string, std::string> kvstore;\n};\n\n\n#endif // EOS_MEMCONFIGSTORE_HH\n"
  },
  {
    "path": "unit_tests/common/Namespace.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Namespace.hh\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#pragma once\n#define USE_EOSCOMMONTESTING using namespace eos::common::testing;\n#define EOSCOMMONTESTING_BEGIN namespace eos { namespace common { namespace testing {\n#define EOSCOMMONTESTING_END }}}\n\n"
  },
  {
    "path": "unit_tests/common/ObserverMgrTests.cc",
    "content": "#include \"common/ObserverMgr.hh\"\n#include \"common/StringUtils.hh\"\n#include \"gtest/gtest.h\"\n#include <iostream>\n#include <folly/executors/IOThreadPoolExecutor.h>\n\nusing eos::common::ObserverMgr;\nusing eos::common::observer_tag_t;\n\nTEST(ObserverMgr, NotifyChangeSync)\n{\n  ObserverMgr<int> mgr;\n  int gval = 0;\n  int gval2 = 0;\n  ASSERT_NO_THROW(mgr.notifyChangeSync(0));\n  auto tag1 = mgr.addObserver([&gval](int i) {\n    gval += i;\n  });\n  auto tag2 = mgr.addObserver([&gval2](int i) {\n    gval2 += 2 * i;\n  });\n  auto tag3 = mgr.addObserver([](int i) {\n    std::cout << \"You've a new message: \" << i << \"\\n\";\n  });\n  mgr.notifyChangeSync(1);\n  ASSERT_EQ(gval, 1);\n  ASSERT_EQ(gval2, 2);\n  mgr.notifyChangeSync(2);\n  ASSERT_EQ(gval, 3);\n  ASSERT_EQ(gval2, 6);\n  mgr.rmObserver(tag2);\n  mgr.notifyChangeSync(3);\n  ASSERT_EQ(gval, 6);\n  ASSERT_EQ(gval2, 6);\n  mgr.rmObserver(tag1);\n  ASSERT_NO_THROW(mgr.notifyChangeSync(100));\n  ASSERT_EQ(gval, 6);\n  ASSERT_EQ(gval2, 6);\n  mgr.rmObserver(tag3);\n  ASSERT_NO_THROW(mgr.notifyChangeSync(101));\n}\n\nTEST(ObserverMgr, SimpleAsync)\n{\n  ObserverMgr<int> mgr;\n  std::atomic<int> gval {0};\n  std::atomic<int> gval2 {0};\n  ASSERT_NO_THROW(mgr.notifyChange(0));\n  auto tag1 = mgr.addObserver([&gval](int i) {\n    gval += i;\n  });\n  auto tag2 = mgr.addObserver([&gval2](int i) {\n    gval2 += 2 * i;\n  });\n  auto tag3 = mgr.addObserver([](int i) {\n    std::cout << \"You've a new message: \" << i << \"\\n\";\n  });\n  mgr.notifyChange(1);\n  mgr.notifyChange(2);\n  // NOTE: This is not meant to be called in normal code unless really necessary\n  // to drain all pending jobs in the ObserverMgr. This is a blocking call.\n  // We only do this in tests to ensure that we can see the values\n  mgr.syncAllNotifications();\n  ASSERT_EQ(gval, 3);\n  ASSERT_EQ(gval2, 6);\n  mgr.rmObserver(tag2);\n  mgr.notifyChange(3);\n  mgr.rmObserver(tag1);\n  ASSERT_NO_THROW(mgr.notifyChange(100));\n  mgr.syncAllNotifications();\n  ASSERT_EQ(gval.load(), 6);\n  ASSERT_EQ(gval2.load(), 6);\n  mgr.rmObserver(tag3);\n  ASSERT_NO_THROW(mgr.notifyChange(101));\n}\n\nTEST(ObserverMgr, observer_tag_t)\n{\n  observer_tag_t default_tag {};\n  ASSERT_FALSE(default_tag);\n}\n\nTEST(ObserverMgr, moveArguments)\n{\n  ObserverMgr<std::string> mgr;\n  // A simple string observers that expect 9 character messages! \"message 1,2... \"\n  // We use std::string as we'll know in case of multiple observers that we'll\n  // end up passing the argument by value correctly as otherwise the string will be\n  // emptied in case of a move\n  auto obs_strlen = mgr.addObserver([](std::string s) {\n    EXPECT_EQ(s.size(), 9);\n    std::cout << \"Observer 1 (value) received msg=\" << s << std::endl;\n  });\n  auto obs_startswith = mgr.addObserver([](std::string s) {\n    EXPECT_TRUE(eos::common::startsWith(s, \"message \"));\n    std::cout << \"Observer 2 (value) received msg=\" << s << std::endl;\n  });\n  int ctr {0};\n  auto gen_string = [&ctr]() {\n    std::string s = \"message \" + std::to_string(ctr++);\n    return s;\n  };\n  // 2 Observers\n  std::cout << \"Starting with 2 observers!\" << std::endl;\n  ASSERT_NO_THROW(mgr.notifyChange(gen_string()));\n  auto tag3 = mgr.addObserver([](std::string && s) {\n    EXPECT_EQ(s.size(), 9);\n    std::cout << \"Observer 3 (r-value ref) received msg=\" << s << std::endl;\n  });\n  // This is likely std::function's magic? ideally conversions shouldn't be allowed\n  // however it looks like if the arg. is constructable from the arg_type\n  // it might just work\n  auto tag4 = mgr.addObserver([](std::string_view s) {\n    EXPECT_EQ(s.size(), 9);\n    std::cout << \"Observer 4 (stringview) received msg=\" << s << std::endl;\n  });\n  mgr.syncAllNotifications();\n  std::cout << \"Adding 2 more observers! Total: 4\" << std::endl;\n  // 4 Observers\n  ASSERT_NO_THROW(mgr.notifyChange(gen_string()));\n  auto tag5 = mgr.addObserver([](const std::string & s) {\n    EXPECT_EQ(s.size(), 9);\n    std::cout << \"Observer 5 (c-ref) recieved msg=\" << s << std::endl;\n  });\n  mgr.syncAllNotifications();\n  std::cout << \"Adding 1 more observer! Total: 5\" << std::endl;\n  // 5 Observers\n  ASSERT_NO_THROW(mgr.notifyChange(gen_string()));\n  mgr.rmObserver(obs_startswith);\n  mgr.syncAllNotifications();\n  std::cout << \"Dropping startswith observer! Total: 4; sending testermsg twice\"\n            << std::endl;\n  std::string msg = \"testermsg\";\n  ASSERT_NO_THROW(mgr.notifyChange(msg));\n  ASSERT_NO_THROW(mgr.notifyChange(std::move(msg)));\n  // msg is now moved! While impl defined, all implementations usually empty the string on move\n  EXPECT_TRUE(msg.empty());\n  mgr.rmObserver(obs_strlen);\n  mgr.syncAllNotifications();\n  std::cout << \"Dropping 1 observer! Total: 3\" << std::endl;\n  // 3 Observers\n  ASSERT_NO_THROW(mgr.notifyChange(gen_string()));\n  ASSERT_NO_THROW(mgr.notifyChange(\"randommsg\"));\n  mgr.syncAllNotifications();\n  std::cout << \"Dropping 1 observer! Total: 2\" << std::endl;\n  // 2 Observers\n  mgr.rmObserver(tag4);\n  ASSERT_NO_THROW(mgr.notifyChange(gen_string()));\n  mgr.rmObserver(tag3);\n  // 1 Observer\n  mgr.syncAllNotifications();\n  std::cout << \"Dropping 1 observer! Total: 1\" << std::endl;\n  ASSERT_NO_THROW(mgr.notifyChange(\"some9char\"));\n  mgr.rmObserver(tag5);\n  // Now there should be no one listening! Should hit the 9 char violation in\n  // case anyone listens\n  ASSERT_NO_THROW(mgr.notifyChange(\"A tree fell in a forest!!!\"));\n}\n\nTEST(ObserverMgr, notifyMultiThreaded)\n{\n  // This test will crash before EOS-6357 is merged\n   ObserverMgr<std::string> mgr;\n   auto obs_startswith = mgr.addObserver([](std::string s) {\n      ASSERT_TRUE(eos::common::startsWith(s, \"message \"));\n   });\n  std::atomic<int> ctr {0};\n  auto gen_string = [&ctr]() {\n    std::string s = \"message \" + std::to_string(ctr++);\n    return s;\n  };\n  std::vector<std::thread> threads;\n  for (int i=0; i< 100; ++i) {\n    threads.emplace_back([&mgr, &gen_string]() {\n      for (int i=0; i< 100; ++i) {\n        mgr.notifyChange(gen_string());\n      }\n    });\n  }\n  for (auto &t: threads) {\n    t.join();\n  }\n\n  ASSERT_EQ(ctr,10000);\n  mgr.rmObserver(obs_startswith);\n}\n\nTEST(ObserverMgr, moveArgumentsSync)\n{\n  ObserverMgr<std::string> mgr;\n  // A simple string observers that expect 9 character messages! \"message 1,2... \"\n  // We use std::string as we'll know in case of multiple observers that we'll\n  // end up passing the argument by value correctly as otherwise the string will be\n  // emptied in case of a move\n  auto obs_strlen = mgr.addObserver([](std::string s) {\n    EXPECT_EQ(s.size(), 9);\n  });\n  auto obs_startswith = mgr.addObserver([](std::string s) {\n    EXPECT_TRUE(eos::common::startsWith(s, \"message \"));\n  });\n  int ctr {0};\n  auto gen_string = [&ctr]() {\n    std::string s = \"message \" + std::to_string(ctr++);\n    return s;\n  };\n  // 2 Observers\n  ASSERT_NO_THROW(mgr.notifyChangeSync(gen_string()));\n  auto tag3 = mgr.addObserver([](std::string && s) {\n    EXPECT_EQ(s.size(), 9);\n  });\n  // This is likely std::function's magic? ideally conversions shouldn't be allowed\n  // however it looks like if the arg. is constructable from the arg_type\n  // it might just work\n  auto tag4 = mgr.addObserver([](std::string_view s) {\n    EXPECT_EQ(s.size(), 9);\n  });\n  mgr.syncAllNotifications();\n  // 4 Observers\n  ASSERT_NO_THROW(mgr.notifyChangeSync(gen_string()));\n  auto tag5 = mgr.addObserver([](const std::string & s) {\n    EXPECT_EQ(s.size(), 9);\n  });\n  // 5 Observers\n  ASSERT_NO_THROW(mgr.notifyChangeSync(gen_string()));\n  mgr.rmObserver(obs_startswith);\n  std::string msg = \"testermsg\";\n  ASSERT_NO_THROW(mgr.notifyChangeSync(msg));\n  ASSERT_NO_THROW(mgr.notifyChangeSync(std::move(msg)));\n  // msg is now moved! While impl defined, all implementations usually empty the string on move\n  EXPECT_TRUE(msg.empty());\n  mgr.rmObserver(obs_strlen);\n  // 3 Observers\n  ASSERT_NO_THROW(mgr.notifyChangeSync(gen_string()));\n  ASSERT_NO_THROW(mgr.notifyChangeSync(\"randommsg\"));\n  // 2 Observers\n  mgr.rmObserver(tag4);\n  ASSERT_NO_THROW(mgr.notifyChangeSync(gen_string()));\n  mgr.rmObserver(tag3);\n  // 1 Observer\n  ASSERT_NO_THROW(mgr.notifyChangeSync(\"some9char\"));\n  mgr.rmObserver(tag5);\n  // Now there should be no one listening! Should hit the 9 char violation in\n  // case anyone listens\n  ASSERT_NO_THROW(mgr.notifyChangeSync(\"A tree fell in a forest!!!\"));\n}\n"
  },
  {
    "path": "unit_tests/common/PathTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: PathTests.cc\n// Author: Mihai Patrascoiu - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Path.hh\"\n#include \"common/ParseUtils.hh\"\n#include \"Namespace.hh\"\n#include \"gtest/gtest.h\"\n\nEOSCOMMONNAMESPACE_BEGIN\n\nTEST(Path, BasicFunctionality)\n{\n  using eos::common::Path;\n  Path path(\"/eos/example/file\");\n  ASSERT_STREQ(path.GetName(), \"file\");\n  ASSERT_STREQ(path.GetPath(), \"/eos/example/file\");\n  ASSERT_STRNE(path.GetParentPath(), \"/eos/example\");\n  ASSERT_STREQ(path.GetParentPath(), \"/eos/example/\");\n  ASSERT_STREQ(path.GetFullPath().c_str(), \"/eos/example/file\");\n  ASSERT_STREQ(path.GetContractedPath().c_str(), \"..eos..example..file\");\n  ASSERT_EQ(path.GetSubPathSize(), 3);\n  ASSERT_STREQ(path.GetSubPath(2), \"/eos/example/\");\n  ASSERT_STREQ(path.GetSubPath(1), \"/eos/\");\n  ASSERT_STREQ(path.GetSubPath(0), \"/\");\n  ASSERT_STREQ(path.GetSubPath(5), 0);\n  path = std::string(\"/eos/example/otherfile\");\n  ASSERT_STREQ(path.GetPath(), \"/eos/example/otherfile\");\n  path = std::string(\"/eos/example/\");\n  ASSERT_STREQ(path.GetName(), \"example\");\n  ASSERT_STREQ(path.GetPath(), \"/eos/example\");\n  ASSERT_STREQ(path.GetParentPath(), \"/eos/\");\n  ASSERT_STREQ(path.GetFullPath().c_str(), \"/eos/example\");\n}\n\nTEST(Path, EmptyOrRootPath)\n{\n  using eos::common::Path;\n  Path empty_path;\n  ASSERT_STREQ(empty_path.GetName(), \"\");\n  ASSERT_STREQ(empty_path.GetPath(), \"\");\n  ASSERT_STREQ(empty_path.GetParentPath(), \"/\");\n  ASSERT_STREQ(empty_path.GetFullPath().c_str(), \"\");\n  ASSERT_EQ(empty_path.GetSubPathSize(), 0);\n}\n\nTEST(Path, RootPath)\n{\n  using eos::common::Path;\n  Path root_path;\n  ASSERT_STREQ(root_path.GetName(), \"\");\n  ASSERT_STREQ(root_path.GetPath(), \"\");\n  ASSERT_STREQ(root_path.GetParentPath(), \"/\");\n  ASSERT_STREQ(root_path.GetFullPath().c_str(), \"\");\n  ASSERT_EQ(root_path.GetSubPathSize(), 0);\n}\n\nTEST(Path, RelativePath)\n{\n  using eos::common::Path;\n  Path path(\"eos/example/file\");\n  ASSERT_STREQ(path.GetName(), \"eos/example/file\");\n  ASSERT_STREQ(path.GetPath(), \"eos/example/file\");\n  ASSERT_STREQ(path.GetParentPath(), \"/\");\n  ASSERT_STREQ(path.GetFullPath().c_str(), \"eos/example/file\");\n  ASSERT_EQ(path.GetSubPathSize(), 0);\n  path = std::string(\"eos/example/file/\");\n  ASSERT_STREQ(path.GetName(), \"eos/example/file\");\n  ASSERT_STREQ(path.GetPath(), \"eos/example/file\");\n  ASSERT_STREQ(path.GetParentPath(), \"/\");\n  ASSERT_STREQ(path.GetFullPath().c_str(), \"eos/example/file\");\n  ASSERT_EQ(path.GetSubPathSize(), 0);\n}\n\nTEST(Path, PathParsing)\n{\n  using eos::common::Path;\n  // Only dotted paths\n  ASSERT_STREQ(Path(\"/.\").GetPath(), \"/\");\n  ASSERT_STREQ(Path(\"/./\").GetPath(), \"/\");\n  ASSERT_STREQ(Path(\"/..\").GetPath(), \"/\");\n  ASSERT_STREQ(Path(\"/../\").GetPath(), \"/\");\n  ASSERT_STREQ(Path(\"/../../\").GetPath(), \"/\");\n  ASSERT_STREQ(Path(\"/../../../\").GetPath(), \"/\");\n  // Mix of dots and directories\n  ASSERT_STREQ(Path(\"/../eos/\").GetPath(), \"/eos\");\n  ASSERT_STREQ(Path(\"/./eos/../\").GetPath(), \"/\");\n  ASSERT_STREQ(Path(\"/eos/../unit/test/\").GetPath(), \"/unit/test\");\n  ASSERT_STREQ(Path(\"/eos/../unit/./test/./\").GetPath(), \"/unit/test\");\n  ASSERT_STREQ(Path(\"/eos/../unit/./test/../\").GetPath(), \"/unit/\");\n  // Trailing dots\n  ASSERT_STREQ(Path(\"/eos/test/.\").GetPath(), \"/eos/test\");\n  ASSERT_STREQ(Path(\"/eos/test/..\").GetPath(), \"/eos/\");\n  ASSERT_STREQ(Path(\"/eos/test/dir/../../\").GetPath(), \"/eos/\");\n  ASSERT_STREQ(Path(\"/eos/test/dir/../../../\").GetPath(), \"/\");\n  ASSERT_STREQ(Path(\"/eos/test/dir/../../../../\").GetPath(), \"/\");\n  ASSERT_STREQ(Path(\"/eos/test/dir/.././../\").GetPath(), \"/eos/\");\n  ASSERT_STREQ(Path(\"/eos/test/dir/.././../../\").GetPath(), \"/\");\n  ASSERT_STREQ(Path(\"/eos/test/dir/.././.././../\").GetPath(), \"/\");\n  ASSERT_STREQ(Path(\"/eos/test/dir/subdir/.././.././../\").GetPath(), \"/eos/\");\n  Path path(\"//eos//example//file\");\n  ASSERT_STREQ(path.GetName(), \"file\");\n  ASSERT_STREQ(path.GetPath(), \"/eos/example/file\");\n  ASSERT_STREQ(path.GetParentPath(), \"/eos/example/\");\n}\n\nTEST(ParseUtils, ParseHostNamePort)\n{\n  int port = 0;\n  std::string host;\n  std::string input = \"eospps.cern.ch\";\n  ASSERT_TRUE(eos::common::ParseHostNamePort(input, host, port));\n  ASSERT_STREQ(host.c_str(), \"eospps.cern.ch\");\n  ASSERT_EQ(port, 1094);\n  input = \"eospps.cern.ch:2020\";\n  ASSERT_TRUE(eos::common::ParseHostNamePort(input, host, port));\n  ASSERT_STREQ(host.c_str(), \"eospps.cern.ch\");\n  ASSERT_EQ(port, 2020);\n}\n\nTEST(ParseUtils, ValidHostnameOrIP)\n{\n  ASSERT_TRUE(eos::common::ValidHostnameOrIP(\"eos.cern.ch\"));\n  ASSERT_TRUE(eos::common::ValidHostnameOrIP(\"eos1.cern.ch\"));\n  ASSERT_TRUE(eos::common::ValidHostnameOrIP(\"eos-test1.cern.ch\"));\n  ASSERT_TRUE(eos::common::ValidHostnameOrIP(\"128.141.194.200\"));\n  ASSERT_TRUE(eos::common::ValidHostnameOrIP(\"2001:1458:202:229::100:3f\"));\n  ASSERT_FALSE(eos::common::ValidHostnameOrIP(\"*eos.cern.ch\"));\n  ASSERT_FALSE(eos::common::ValidHostnameOrIP(\"eos&.cern.ch\"));\n}\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "unit_tests/common/RWMutexTest.cc",
    "content": "//------------------------------------------------------------------------------\n// File: RWMutexTests.cc\n// Author: Elvin Sindrilaru <esindril at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"common/RWMutex.hh\"\n#include \"common/StacktraceHere.hh\"\n#include <cstdio>\n\n#ifdef EOS_INSTRUMENTED_RWMUTEX\n//------------------------------------------------------------------------------\n// Check stacktrace generation\n//------------------------------------------------------------------------------\nTEST(StacktraceHere, BasicSanity)\n{\n  std::cout << eos::common::getStacktrace() << std::endl;\n}\n\n//------------------------------------------------------------------------------\n// Double write lock\n//------------------------------------------------------------------------------\nTEST(RWMutex, WriteDeadlockTest)\n{\n  eos::common::RWMutex mutex;\n  mutex.SetBlocking(true);\n  mutex.SetDeadlockCheck(true);\n  mutex.LockWrite();\n  ASSERT_THROW(mutex.LockWrite(), std::runtime_error);\n}\n\n//------------------------------------------------------------------------------\n// Interleaved write lock with re-entrant read lock with a mutex that doesn't\n// give preference to the readers.\n//------------------------------------------------------------------------------\nTEST(RWMutex, RdWrRdDeadlockTest)\n{\n  eos::common::RWMutex mutex(false);\n  mutex.SetBlocking(true);\n  mutex.SetDeadlockCheck(true);\n  mutex.LockRead();\n  std::thread t([&]() {\n    std::this_thread::sleep_for(std::chrono::milliseconds(100));\n    mutex.LockWrite();\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    mutex.UnLockWrite();\n  });\n  std::this_thread::sleep_for(std::chrono::milliseconds(200));\n  ASSERT_THROW(mutex.LockRead(), std::runtime_error);\n  mutex.UnLockRead();\n  t.join();\n}\n\n//------------------------------------------------------------------------------\n// As above but with preference given to the readers. Writers are starved.\n//------------------------------------------------------------------------------\nTEST(RWMutex, RdWrRdNoDeadlockTest)\n{\n  eos::common::RWMutex mutex(true);\n  mutex.SetBlocking(true);\n  mutex.SetDeadlockCheck(true);\n  mutex.LockRead();\n  std::thread t([&]() {\n    std::this_thread::sleep_for(std::chrono::milliseconds(100));\n    mutex.LockWrite();\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    mutex.UnLockWrite();\n  });\n  std::this_thread::sleep_for(std::chrono::milliseconds(200));\n  //  ASSERT_NO_THROW(mutex.LockRead());\n  // mutex.UnLockRead();\n  mutex.UnLockRead();\n  t.join();\n}\n\n//------------------------------------------------------------------------------\n// Multiple reads from different threads should never deadlock\n//------------------------------------------------------------------------------\nTEST(RWMutex, MultiRdLockTest)\n{\n  eos::common::RWMutex mutex(true);\n  mutex.SetBlocking(true);\n  mutex.SetDeadlockCheck(true);\n  mutex.LockRead();\n  std::thread t([&]() {\n    std::this_thread::sleep_for(std::chrono::milliseconds(100));\n    mutex.LockRead();\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    mutex.UnLockRead();\n  });\n  std::this_thread::sleep_for(std::chrono::milliseconds(200));\n  ASSERT_NO_THROW(mutex.UnLockRead());\n  ASSERT_NO_THROW(mutex.LockRead());\n  ASSERT_NO_THROW(mutex.UnLockRead());\n  t.join();\n}\n\n//------------------------------------------------------------------------------\n// Write locks from different threads should never deadlock\n//------------------------------------------------------------------------------\nTEST(RWMutex, MultiWrLockTest)\n{\n  eos::common::RWMutex mutex(true);\n  mutex.SetBlocking(true);\n  mutex.SetDeadlockCheck(true);\n  mutex.LockWrite();\n  std::thread t([&]() {\n    std::this_thread::sleep_for(std::chrono::milliseconds(100));\n    mutex.LockWrite();\n    std::this_thread::sleep_for(std::chrono::milliseconds(200));\n    mutex.UnLockWrite();\n  });\n  std::this_thread::sleep_for(std::chrono::milliseconds(200));\n  ASSERT_NO_THROW(mutex.UnLockWrite());\n  t.join();\n}\n\n//------------------------------------------------------------------------------\n// Failed timed lock should not trigger a lock violation\n//------------------------------------------------------------------------------\nTEST(RWMutex, LockOrder)\n{\n  using eos::common::RWMutex;\n  struct capture_out {\n    capture_out(FILE* file):\n      mFile(file)\n    {\n      fflush(mFile);\n      mOldFd = fileno(mFile);\n      mNewFd = dup(mOldFd);\n      freopen(\"/dev/null\", \"a\", mFile);\n      setvbuf(mFile, mBuffer, _IOFBF, 1024 * 1024);\n    }\n\n    ~capture_out()\n    {\n      freopen(\"/dev/null\", \"a\", mFile);\n      dup2(mNewFd, mOldFd);\n      setvbuf(mFile, nullptr, _IONBF, 0);\n    }\n\n    FILE* mFile;\n    int mNewFd;\n    int mOldFd;\n    char mBuffer[1024 * 1024];\n  };\n  RWMutex mutex1;\n  RWMutex mutex2;\n  RWMutex::SetOrderCheckingGlobal(true);\n  std::vector<RWMutex*> order;\n  order.push_back(&mutex1);\n  order.push_back(&mutex2);\n  RWMutex::AddOrderRule(\"rule1\", order);\n  auto correct_lock_order = [&]() {\n    mutex1.LockRead();\n    mutex2.LockRead();\n    ASSERT_NO_THROW(mutex2.UnLockRead());\n    ASSERT_NO_THROW(mutex1.UnLockRead());\n  };\n  correct_lock_order();\n#ifdef NDEBUG\n  // @note This crashes if run with abseil compiled in debug mode\n  auto lock_order_violation = [&]() {\n    capture_out cap_err(stderr);\n    mutex2.LockRead();\n    mutex1.LockRead();\n    std::string output = cap_err.mBuffer;\n    ASSERT_TRUE(output.find(\"Order Checking Error\") != std::string::npos);\n    ASSERT_NO_THROW(mutex2.UnLockRead());\n    ASSERT_NO_THROW(mutex1.UnLockRead());\n  };\n  lock_order_violation();\n#endif\n  /*\n  // @note SharedMutex implemetation using absl::Mutex uses simple ReadLock\n  // for the TimedRdLock implementation so this check does not apply\n  auto failed_timed_no_order_violation = [&]() {\n    capture_out cap_err(stderr);\n    std::thread t([&]() {\n      mutex1.LockWrite();\n      std::this_thread::sleep_for(std::chrono::seconds(1));\n      mutex1.UnLockWrite();\n    });\n    std::this_thread::sleep_for(std::chrono::milliseconds(500));\n    ASSERT_FALSE(mutex1.TimedRdLock(100000));\n    t.join();\n    std::this_thread::sleep_for(std::chrono::seconds(1));\n    ASSERT_TRUE(mutex1.TimedRdLock(100000));\n    std::string output = cap_err.mBuffer;\n    ASSERT_TRUE(output.find(\"Order Checking Error\") == std::string::npos);\n    mutex1.UnLockRead();\n  };\n\n  failed_timed_no_order_violation();\n  */\n}\n#endif\n"
  },
  {
    "path": "unit_tests/common/RandTests.cc",
    "content": "#include <set>\n#include \"common/utils/RandUtils.hh\"\n#include \"gtest/gtest.h\"\n\n// This is not an actual test of random distributions\n// it is simply a test to confirm that both the [start,end]\n// ranges are generated\nTEST(getRandom, Limits)\n{\n  std::set<int> numbers;\n  for (int i=0; i < 1000; i++) {\n    int n = eos::common::getRandom(1,6);\n    numbers.insert(n);\n  }\n  ASSERT_EQ(numbers.size(),6);\n  for (int i=1;i<7;i++) {\n    ASSERT_FALSE(numbers.find(i) == numbers.end());\n  }\n}\n"
  },
  {
    "path": "unit_tests/common/RateLimitTests.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file RateLimitTests.cc\n//! @author Elvin-Alin Sindrilaru <esindril at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#define IN_TEST_HARNESS\n#include \"common/RateLimit.hh\"\n#undef IN_TEST_HARNESS\n#include <thread>\n#include <vector>\n#include <list>\n\nusing namespace std::chrono;\n\n//------------------------------------------------------------------------------\n// Test rate limiter basic functionality\n//------------------------------------------------------------------------------\nTEST(RequestRateLimit, BasicFunctionality)\n{\n  using namespace eos::common;\n  int initial_delay = 5;\n  RequestRateLimit rlimit(true);\n  rlimit.SetRatePerSecond(1000001);\n  ASSERT_EQ(0ull, rlimit.GetRatePerSecond());\n  ASSERT_NO_THROW(rlimit.SetRatePerSecond(1));\n  auto& clock = rlimit.GetClock();\n  clock.advance(seconds(initial_delay)); // fake clock starts at 0\n\n  // With time passing we should be able to submit one request per second\n  for (int i = 0; i < 10; ++i) {\n    ASSERT_EQ(0, rlimit.Allow());\n    clock.advance(std::chrono::seconds(1));\n  }\n\n  // Without time passing allow should return non-zero values\n  ASSERT_EQ(0, rlimit.Allow());\n\n  for (int i = 0; i < 10; ++i) {\n    auto delay = rlimit.Allow();\n    ASSERT_NE(0, delay);\n    clock.advance(std::chrono::microseconds(delay));\n  }\n}\n\n//------------------------------------------------------------------------------\n// Test rate limiter in multithreaded mode\n//------------------------------------------------------------------------------\nTEST(RequestRateLimit, MultiThread)\n{\n  using namespace std::chrono;\n  using namespace eos::common;\n  std::vector<std::thread> threads;\n  std::list<int> rates {5, 10, 100};\n\n  for (auto& rate : rates) {\n    std::unique_ptr<RequestRateLimit> rlimit(new RequestRateLimit(true));\n    auto& clock = rlimit->GetClock();\n    clock.advance(std::chrono::seconds(5));  // fake clock starts at 0\n    ASSERT_NO_THROW(rlimit->SetRatePerSecond(rate));\n    uint64_t start_us = duration_cast<microseconds>\n                        (clock.GetTime().time_since_epoch()).count();\n    auto func = [&](int indx) noexcept {\n      for (int i = 0; i < rate; ++i) {\n        (void)rlimit->Allow();\n      }\n    };\n\n    // With time passing we should be able to submit one request per second per\n    // thread ---> \"rate\" seconds to submit everything from everyone\n    for (int i = 0; i < rate; ++i) {\n      threads.push_back(std::thread(func, i));\n    }\n\n    for (auto& t : threads) {\n      if (t.joinable()) {\n        t.join();\n      }\n    }\n\n    uint64_t dur_ms = (rlimit->mLastTimestampUs - start_us) / 1000;\n    // With x slots per second and x threads submitting x requests it should take\n    // around x seconds to run this. Add +/-5% seconds tolerance.\n    ASSERT_GE(dur_ms, (int)(rate * 1000 * 0.95));\n    ASSERT_LE(dur_ms, (int)(rate * 1000 * 1.05));\n    std::cout << \"Run took: \" << dur_ms << \" (fake)ms\" << std::endl;\n  }\n}\n"
  },
  {
    "path": "unit_tests/common/RegexWrapperTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: RegexWrapperTests.cc\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/RegexWrapper.hh\"\n#include <gtest/gtest.h>\n//#include <regex>\n\nusing namespace eos::common;\n\nTEST(RegexWrapper, BasicTests)\n{\n  std::string sregex {\"v[0-9]+(\\\\.[0-9]+)+\"};\n  ASSERT_TRUE(eos_regex_match(\"v5.3.11\", sregex));\n  ASSERT_FALSE(eos_regex_match(\"some.other.string\", sregex));\n  ASSERT_FALSE(eos_regex_match(\"Partial match of v5.3.10 is not good!\", sregex));\n  ASSERT_TRUE(eos_regex_search(\"Partial search of v5.3.10 is good!\", sregex));\n  ASSERT_FALSE(eos_regex_search(\"Partial search of random string is not good!\",\n                                sregex));\n  // std::regex std_regex(\"[/\\\\w.]+\");\n  // ASSERT_TRUE(std::regex_search(\"path/to/file/test.exe\", std_regex));\n  // ASSERT_TRUE(std::regex_match(\"path/to/file/test.exe\", std_regex));\n  // ASSERT_TRUE(std::regex_match(\"/some_more_exec.\", std_regex));\n  // ASSERT_TRUE(std::regex_match(\"someword\", std_regex));\n  // ASSERT_TRUE(std::regex_match(\"/some_exec\", std_regex));\n  // ASSERT_FALSE(std::regex_match(\"not!a#good*word!\", std_regex));\n  // @note according to https://www.regular-expressions.info/gnu.html\n  // shorthand character classes can not be used inside bracket expressions!\n  // This means that the std::regex pattern above would fail below.\n  sregex = \"[/[:alnum:]_.]+\";\n  ASSERT_TRUE(eos_regex_search(\"path/to/file/test.exe\", sregex));\n  ASSERT_TRUE(eos_regex_match(\"path/to/file/test.exe\", sregex));\n  ASSERT_TRUE(eos_regex_match(\"/some_more_exec.\", sregex));\n  ASSERT_TRUE(eos_regex_match(\"someword\", sregex));\n  ASSERT_TRUE(eos_regex_match(\"/some_exec\", sregex));\n  ASSERT_FALSE(eos_regex_match(\"not!a#good*word!\", sregex));\n  sregex = \"(lxplus)(.*)(.cern.ch)\";\n  ASSERT_TRUE(eos_regex_match(\"lxplus.cern.ch\", sregex));\n  ASSERT_TRUE(eos_regex_match(\"lxplus1234.cern.ch\", sregex));\n  ASSERT_FALSE(eos_regex_match(\"not_lxplus1234.cern.ch\", sregex));\n  ASSERT_FALSE(eos_regex_match(\"justmyhost.cern.ch\", sregex));\n  ASSERT_FALSE(eos_regex_match(\"lxplus1234.mytest.com\", sregex));\n  sregex = \"(b)[789](.*)(.cern.ch)\";\n  ASSERT_TRUE(eos_regex_match(\"b9pgpun004.cern.ch\", sregex));\n  ASSERT_TRUE(eos_regex_match(\"b9p28p3894.cern.ch\", sregex));\n  ASSERT_FALSE(eos_regex_match(\"nonbatchhost.cern.ch\", sregex));\n  ASSERT_FALSE(eos_regex_match(\"b9p28p3ad.mytest.com\", sregex));\n}\n"
  },
  {
    "path": "unit_tests/common/SciTokensTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: SciTokensTests.cc\n// Author: Andreas-Joachim Peters <andreas.joachim.peters@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"unit_tests/common/Namespace.hh\"\n#include \"common/token/SciToken.hh\"\n#include \"common/token/scitoken.h\"\n#include <memory>\n#include <fstream>\n#include <time.h>\n#include <sys/stat.h>\n\n\nEOSCOMMONTESTING_BEGIN\n\nusing namespace eos::common;\n\nTEST(SciToken, Factory)\n{\n  std::unique_ptr<SciToken> issuer;\n  // create keys\n  std::ofstream(\"/tmp/.eosunit.sci.cred\") << \"-----BEGIN PUBLIC KEY-----\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9lFITZoMpmdgPN2rRFT3EUEYXybj\\nzRoTSdF6P5I9eyCj42n/OASfE+jMB2FtpV8FrwIk7D8xqWAJ9KbHTZPKag==\\n-----END PUBLIC KEY-----\\n\";\n  std::ofstream(\"/tmp/.eosunit.sci.key\") << \"-----BEGIN PRIVATE KEY-----\\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgRnmkbjzf5uE5INQR\\n4XBA973ioI7vuAMgfV8MFcnzP36hRANCAAT2UUhNmgymZ2A83atEVPcRQRhfJuPN\\nGhNJ0Xo/kj17IKPjaf84BJ8T6MwHYW2lXwWvAiTsPzGpYAn0psdNk8pq\\n-----END PRIVATE KEY-----\\n\";\n  // make key secure\n  ::chmod(\"/tmp/.eosunit.sci.key\",S_IRUSR);\n  issuer.reset(eos::common::SciToken::Factory(\"/tmp/.eosunit.sci.cred\", \"/tmp/.eosunit.sci.key\", \"eos\",\"localhost\"));\n  ASSERT_NE(issuer, nullptr);\n\n  std::string token;\n  time_t expires = time(NULL) + 3600;\n  std::set<std::string> claims;\n  for ( size_t i = 0 ; i< 10000; i++) {\n    claims.clear();\n    std::string scope = \"scope=storage.read:\\\"/eos/\";\n    scope += std::to_string(i);\n    scope += \"\\\"\";\n    claims.insert(scope);\n    int rc = issuer->CreateToken(token, expires, claims);\n    ASSERT_EQ(rc, 0);\n  }\n\n  ::unlink(\"/tmp/.eosunit.sci.key\");\n  ::unlink(\"/tmp/.eosunit.sci.cred\");\n}\n\nTEST(SciToken, CFactory)\n{\n  std::unique_ptr<SciToken> issuer;\n  // create keys\n  std::ofstream(\"/tmp/.eosunit.sci.cred\") << \"-----BEGIN PUBLIC KEY-----\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9lFITZoMpmdgPN2rRFT3EUEYXybj\\nzRoTSdF6P5I9eyCj42n/OASfE+jMB2FtpV8FrwIk7D8xqWAJ9KbHTZPKag==\\n-----END PUBLIC KEY-----\\n\";\n  std::ofstream(\"/tmp/.eosunit.sci.key\") << \"-----BEGIN PRIVATE KEY-----\\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgRnmkbjzf5uE5INQR\\n4XBA973ioI7vuAMgfV8MFcnzP36hRANCAAT2UUhNmgymZ2A83atEVPcRQRhfJuPN\\nGhNJ0Xo/kj17IKPjaf84BJ8T6MwHYW2lXwWvAiTsPzGpYAn0psdNk8pq\\n-----END PRIVATE KEY-----\\n\";\n  // make key secure\n  ::chmod(\"/tmp/.eosunit.sci.key\",S_IRUSR);\n\n  void* sci_ctx = c_scitoken_factory_init(\"/tmp/.eosunit.sci.cred\", \"/tmp/.eosunit.sci.key\", \"eos\",\"localhost\");\n  ASSERT_NE((long long)sci_ctx,0);\n\n  std::string token;\n  time_t expires = time(NULL) + 3600;\n  for ( size_t i = 0 ; i< 10000; i++) {\n    token=\"\";\n    token.resize(4096);\n    std::string scope = \"scope=storage.read:\\\"/eos/\";\n    scope += std::to_string(i);\n    scope += \"\\\"\";\n    int rc = c_scitoken_create((char*)token.c_str(), token.size(), expires, scope.c_str());\n    ASSERT_EQ(rc, 0);\n  }\n\n  ::unlink(\"/tmp/.eosunit.sci.key\");\n  ::unlink(\"/tmp/.eosunit.sci.cred\");\n}\n\nEOSCOMMONTESTING_END\n"
  },
  {
    "path": "unit_tests/common/ShardedCacheTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ShardedCacheTests.cc\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <gtest/gtest.h>\n#include \"common/ShardedCache.hh\"\n\nTEST(ShardedCache, Construction)\n{\n  ShardedCache<std::string, int> cache(8, 100);\n  ASSERT_EQ(cache.num_shards(), 256);\n  ASSERT_EQ(cache.num_content_shards(), 256);\n  EXPECT_EQ(cache.num_entries(), 0);\n}\n\nTEST(ShardedCache, CalculateShard)\n{\n  ShardedCache<std::string, int> cache(8, 100);\n  std::hash<std::string> hasher{};\n  ASSERT_GE(cache.calculateShard(\"hello\"), 0);\n  ASSERT_LT(cache.calculateShard(\"hello\"), 256);\n  EXPECT_EQ(cache.calculateShard(\"hello\"),\n            hasher(\"hello\") % 256);\n}\n\nTEST(ShardedCache, NoGC)\n{\n  ShardedCache<std::string, int> cache(8);\n  ASSERT_EQ(cache.num_shards(), 256);\n  ASSERT_EQ(cache.num_content_shards(), 256);\n  EXPECT_EQ(cache.num_entries(), 0);\n  std::hash<std::string> hasher{};\n  ASSERT_GE(cache.calculateShard(\"hello\"), 0);\n  ASSERT_LT(cache.calculateShard(\"hello\"), 256);\n  EXPECT_EQ(cache.calculateShard(\"hello\"),\n            hasher(\"hello\") % 256);\n}\n\nTEST(ShardedCache, EmptyRetrieve)\n{\n  ShardedCache<std::string, int> cache(8, 100);\n  auto result = cache.retrieve(\"hello\");\n  EXPECT_EQ(result, nullptr);\n}\n\nTEST(ShardedCache, ValueRetrieve)\n{\n  ShardedCache<std::string, int> cache(8, 100);\n  auto val = std::make_unique<int>(5);\n  ASSERT_TRUE(cache.store(\"hello\", std::move(val)));\n  auto result = cache.retrieve(\"hello\");\n  ASSERT_NE(result, nullptr);\n  EXPECT_EQ(*result, 5);\n}\n\nTEST(ShardedCache, ValueNonExpiry)\n{\n  ShardedCache<std::string, int> cache(8, 10);\n  ASSERT_TRUE(cache.store(\"hello\", std::make_unique<int>(5)));\n  auto result = cache.retrieve(\"hello\");\n  ASSERT_NE(result, nullptr);\n  EXPECT_EQ(*result, 5);\n  // Since we hold a valid reference to the entry it never expires\n  std::this_thread::sleep_for(std::chrono::milliseconds(30));\n  auto result2 = cache.retrieve(\"hello\");\n  EXPECT_EQ(result2, result);\n  // now release ownership. The entry should expire\n  result.reset();\n  result2.reset();\n  std::this_thread::sleep_for(std::chrono::milliseconds(30));\n  auto result3 = cache.retrieve(\"hello\");\n  EXPECT_EQ(result3, nullptr);\n}\n\nTEST(ShardedCache, ValueExpiry)\n{\n  ShardedCache<std::string, int> cache(8, 10);\n  ASSERT_TRUE(cache.store(\"hello\", std::make_unique<int>(5)));\n  auto result = cache.retrieve(\"hello\");\n  ASSERT_NE(result, nullptr);\n  EXPECT_EQ(*result, 5);\n  result.reset();\n  // The entry takes between 2*ttl and 3*ttl to completely expire\n  // as the first round just marks the entry as expired and the second round\n  // actually deletes it.\n  std::this_thread::sleep_for(std::chrono::milliseconds(50));\n  auto result2 = cache.retrieve(\"hello\");\n  EXPECT_EQ(result2, nullptr);\n}\n\nTEST(ShardedCache, NoGCRetrieve)\n{\n  ShardedCache<std::string, int> cache(8);\n  ASSERT_TRUE(cache.store(\"hello\", std::make_unique<int>(5)));\n  auto result = cache.retrieve(\"hello\");\n  ASSERT_NE(result, nullptr);\n  EXPECT_EQ(*result, 5);\n}\n\nTEST(ShardedCache, LateGCRun)\n{\n  ShardedCache<std::string, int> cache(8);\n  ASSERT_TRUE(cache.store(\"hello\", std::make_unique<int>(5)));\n  auto result = cache.retrieve(\"hello\");\n  ASSERT_NE(result, nullptr);\n  EXPECT_EQ(*result, 5);\n  result.reset();\n  cache.reset_cleanup_thread(10);\n  std::this_thread::sleep_for(std::chrono::milliseconds(30));\n  ASSERT_EQ(cache.num_entries(), 0);\n  auto result2 = cache.retrieve(\"hello\");\n  EXPECT_EQ(result2, nullptr);\n}\n\nTEST(ShardedCache, get_shard_except)\n{\n  ShardedCache<std::string, int> cache(8, 10);\n  std::unordered_map<std::string, int> empty_map;\n\n  for (unsigned int i = 0; i < cache.num_shards(); i++) {\n    ASSERT_EQ(cache.get_shard(i), empty_map);\n  }\n\n  ASSERT_THROW(cache.get_shard(cache.num_shards()), std::out_of_range);\n  ASSERT_THROW(cache.get_shard(cache.num_shards() + 1), std::out_of_range);\n  ASSERT_THROW(cache.get_shard(-1), std::out_of_range);\n}\n\nTEST(ShardedCache, get_shard)\n{\n  ShardedCache<std::string, int> cache(8, 10);\n  ASSERT_TRUE(cache.store(\"hello\", std::make_unique<int>(5)));\n  ASSERT_EQ(cache.num_entries(), 1);\n  auto shard_copy = cache.get_shard(cache.calculateShard(\"hello\"));\n  ASSERT_EQ(shard_copy.size(), 1);\n  ASSERT_EQ(shard_copy[\"hello\"], 5);\n  std::this_thread::sleep_for(std::chrono::milliseconds(50));\n  ASSERT_EQ(cache.num_entries(), 0);\n}\n\nTEST(ShardedCache, clear)\n{\n  ShardedCache<std::string, int> cache(8, 10);\n  ASSERT_TRUE(cache.store(\"hello\", std::make_unique<int>(5)));\n  ASSERT_EQ(cache.num_entries(), 1);\n  cache.clear();\n  ASSERT_EQ(cache.num_entries(), 0);\n  auto result = cache.retrieve(\"hello\");\n  EXPECT_EQ(result, nullptr);\n}\n\nTEST(ShardedCache, reuse_after_clear)\n{\n  ShardedCache<std::string, int> cache(8, 10);\n  ASSERT_TRUE(cache.store(\"hello\", std::make_unique<int>(5)));\n  ASSERT_EQ(cache.num_entries(), 1);\n  cache.clear();\n  ASSERT_EQ(cache.num_entries(), 0);\n  auto result = cache.retrieve(\"hello\");\n  EXPECT_EQ(result, nullptr);\n  ASSERT_TRUE(cache.store(\"hello\", std::make_unique<int>(5)));\n  ASSERT_EQ(cache.num_entries(), 1);\n  result = cache.retrieve(\"hello\");\n  ASSERT_NE(result, nullptr);\n  EXPECT_EQ(*result, 5);\n}\n\nTEST(ShardedCache, value_after_clear)\n{\n  ShardedCache<std::string, int> cache(8, 10);\n  ASSERT_TRUE(cache.store(\"hello\", std::make_unique<int>(5)));\n  ASSERT_EQ(cache.num_entries(), 1);\n  auto result = cache.retrieve(\"hello\");\n  ASSERT_NE(result, nullptr);\n  EXPECT_EQ(*result, 5);\n  ASSERT_EQ(result.use_count(), 2);\n  cache.clear();\n  // result is a shared_ptr, so regardless it is still valid\n  // however no longer present in the cache map\n  ASSERT_EQ(cache.num_entries(), 0);\n  ASSERT_EQ(result.use_count(), 1);\n  ASSERT_NE(result, nullptr);\n  EXPECT_EQ(*result, 5);\n}\n\nTEST(ShardedCache, fetch_add)\n{\n  ShardedCache<std::string, int> cache(8, 10);\n  ASSERT_TRUE(cache.store(\"hello\", std::make_unique<int>(5)));\n  auto result = cache.retrieve(\"hello\");\n  ASSERT_NE(result, nullptr);\n  EXPECT_EQ(*result, 5);\n  auto old_val = cache.fetch_add(\"hello\", 1);\n  EXPECT_EQ(old_val, 5);\n  auto result2 = cache.retrieve(\"hello\");\n  ASSERT_NE(result2, nullptr);\n  EXPECT_EQ(*result2, 6);\n\n}\n\nTEST(ShardedCache, fetch_add_empty_key)\n{\n  ShardedCache<std::string, int> cache(8, 10);\n  auto null_result = cache.retrieve(\"hello\");\n  ASSERT_EQ(null_result, nullptr);\n  cache.fetch_add(\"hello\", 1);\n  auto result3 = cache.retrieve(\"hello\");\n  ASSERT_NE(result3, nullptr);\n  EXPECT_EQ(*result3, 1);\n}\n\nTEST(ShardedCache, fetch_add_multithreaded)\n{\n  // Give a minute of GC instead of 10ms, so that we'd not expire any caches\n  // before the function completes! Otherwise with 10ms it can so happen that\n  // in small env by the time all the threads are run, GC would just run before we\n  // retrieve the cache.\n  ShardedCache<std::string, int> cache(8, 60*1000);\n  std::vector<std::thread> threads;\n  for (int i=0; i<200; i++) {\n    threads.emplace_back([&cache]() {\n      cache.fetch_add(\"mykey\",1);\n    });\n  }\n  for (auto& t : threads) {\n    t.join();\n  }\n  ASSERT_EQ(cache.num_entries(), 1);\n  auto result = cache.retrieve(\"mykey\");\n  ASSERT_EQ(*result, 200);\n}\n\nTEST(ShardedCache, invalidate)\n{\n  ShardedCache<std::string, int> cache(8, 10);\n  ASSERT_TRUE(cache.store(\"hello\", std::make_unique<int>(5)));\n  ASSERT_TRUE(cache.store(\"hello2\", std::make_unique<int>(6)));\n  ASSERT_EQ(cache.num_entries(), 2);\n  bool ret = cache.invalidate(\"hello\");\n  ASSERT_EQ(ret, true);\n  ASSERT_EQ(cache.num_entries(), 1);\n  ret = cache.invalidate(\"hello\");\n  ASSERT_EQ(ret, false);\n  auto result = cache.retrieve(\"hello\");\n  EXPECT_EQ(result, nullptr);\n}\n\nTEST(ShardedCache, force_expiry)\n{\n  ShardedCache<std::string, int> cache(8,10);\n  ASSERT_TRUE(cache.store(\"hello\", std::make_unique<int>(5)));\n  ASSERT_TRUE(cache.store(\"delete-me\", std::make_unique<int>(5)));\n  // We hold a shared ptr, so ideally the GC shouldn't clear this!\n  auto result = cache.retrieve(\"hello\");\n  ASSERT_NE(result, nullptr);\n  EXPECT_EQ(*result, 5);\n  std::this_thread::sleep_for(std::chrono::milliseconds(50));\n  ASSERT_TRUE(cache.contains(\"hello\"));\n  ASSERT_FALSE(cache.contains(\"delete-me\"));\n  // Now force the reset cycle!\n  cache.set_force_expiry(true, 2);\n\n  std::this_thread::sleep_for(std::chrono::milliseconds(30));\n  ASSERT_FALSE(cache.contains(\"hello\"));\n}\n"
  },
  {
    "path": "unit_tests/common/StringConversionTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: StringConversionTests.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"Namespace.hh\"\n#include \"common/StringConversion.hh\"\n#include \"common/RegexWrapper.hh\"\n\nEOSCOMMONTESTING_BEGIN\n\nusing namespace eos::common;\n\nTEST(StringConversion, Seal_Unseal_Operation)\n{\n  std::string input = \"/eos/dev/pic_and_poc\";\n  ASSERT_TRUE(input == StringConversion::SealXrdPath(input));\n  ASSERT_TRUE(input == StringConversion::UnsealXrdPath(input));\n  input = \"/eos/dev/pic&poc\";\n  std::string expected = \"/eos/dev/pic#AND#poc\";\n  ASSERT_TRUE(expected == StringConversion::SealXrdPath(input));\n  ASSERT_TRUE(input == StringConversion::UnsealXrdPath(expected));\n  input = \"/eos/dev/&pic&and&poc&&\";\n  expected = \"/eos/dev/#AND#pic#AND#and#AND#poc#AND##AND#\";\n  ASSERT_STREQ(expected.c_str(), StringConversion::SealXrdPath(input).c_str());\n  ASSERT_STREQ(input.c_str(), StringConversion::UnsealXrdPath(expected).c_str());\n}\n\nTEST(StringConversion, ChecksumTranslations)\n{\n  char buff[4];\n  buff[0] = 0xc2;\n  buff[1] = 0x3b;\n  buff[2] = 0x91;\n  buff[3] = 0x38;\n  ASSERT_TRUE(StringConversion::BinData2HexString(buff, 4, 4) == \"c23b9138\");\n  std::string in_buf;\n  in_buf.resize(4);\n  memcpy(&in_buf[0], &buff[0], 4);\n  ASSERT_TRUE(StringConversion::BinData2HexString\n              (in_buf.c_str(), in_buf.capacity(), 4) == \"c23b9138\");\n  ASSERT_TRUE(StringConversion::BinData2HexString\n              (in_buf.c_str(), in_buf.capacity(), 4, ' ') == \"c2 3b 91 38\");\n  size_t out_sz;\n  auto new_buff = StringConversion::Hex2BinDataChar(\"c23b9138\", out_sz);\n  ASSERT_EQ(out_sz, 4);\n\n  for (size_t i = 0; i < out_sz; ++i) {\n    ASSERT_EQ(buff[i], new_buff.get()[i]);\n  }\n\n  // Wrongly specified checksum should be converted only partially\n  buff[0] = 0x2a;\n  buff[1] = 0x38;\n  buff[2] = 0x17;\n  buff[3] = 0x4b;\n  std::string wrong_xs {\"2a38174be\"}; // has 9 chars!\n  new_buff = StringConversion::Hex2BinDataChar(wrong_xs, out_sz);\n  ASSERT_EQ(out_sz, 4);\n\n  for (size_t i = 0; i < out_sz; ++i) {\n    ASSERT_EQ(buff[i], new_buff.get()[i]);\n  }\n\n  ASSERT_FALSE(StringConversion::BinData2HexString(&buff[0], 4, 4) == wrong_xs);\n  ASSERT_TRUE(StringConversion::BinData2HexString(&buff[0], 4, 4) == \"2a38174b\");\n}\n\nTEST(StringConversion, timebased_uuidstring)\n{\n  std::string uuid;\n  uuid = eos::common::StringConversion::timebased_uuidstring();\n  std::string\n  regexUuid(\"[0-9a-fA-F]{8}\\\\-[0-9a-fA-F]{4}\\\\-[0-9a-fA-F]{4}\\\\-[0-9a-fA-F]{4}\\\\-[0-9a-fA-F]{12}\");\n  ASSERT_TRUE(eos::common::eos_regex_match(uuid, regexUuid));\n}\n\nTEST(StringConversion, GetSizeFromString)\n{\n  uint64_t out;\n  ASSERT_TRUE(StringConversion::GetSizeFromString(\"5\", out));\n  ASSERT_EQ(out, 5);\n  ASSERT_TRUE(StringConversion::GetSizeFromString(\"5M\", out));\n  ASSERT_EQ(out, 5000000);\n  ASSERT_TRUE(StringConversion::GetSizeFromString(\"9k\", out));\n  ASSERT_EQ(out, 9000);\n  // Bug, this should be false :(\n  ASSERT_TRUE(StringConversion::GetSizeFromString(\"pickles\", out));\n  ASSERT_EQ(out, 0);\n}\n\nTEST(StringConversion, GetSizeString)\n{\n  unsigned long long size_int = 999;\n  ASSERT_STREQ(\"999\", StringConversion::GetSizeString(size_int).c_str());\n  size_int = 1234568910;\n  ASSERT_STREQ(\"1234568910\", StringConversion::GetSizeString(size_int).c_str());\n  double size_double = 123.456;\n  ASSERT_STREQ(\"123.46\", StringConversion::GetSizeString(size_double).c_str());\n  size_double = 98765.432;\n  ASSERT_STREQ(\"98765.43\", StringConversion::GetSizeString(size_double).c_str());\n  size_double = 11.010;\n  ASSERT_STREQ(\"11.01\", StringConversion::GetSizeString(size_double).c_str());\n  size_double = 12.990;\n  ASSERT_STREQ(\"12.99\", StringConversion::GetSizeString(size_double).c_str());\n  size_double = 56.789;\n  ASSERT_STREQ(\"56.79\", StringConversion::GetSizeString(size_double).c_str());\n  ASSERT_STREQ(\"\", StringConversion::GetSizeString(\"random_stuff\").c_str());\n}\n\nTEST(StringConversion, GetReadableSizeString)\n{\n  unsigned long long size = 999;\n  ASSERT_STREQ(\"999\", StringConversion::GetReadableSizeString(size).c_str());\n  ASSERT_STREQ(\"999 B\", StringConversion::GetReadableSizeString(size,\n               \"B\").c_str());\n  size = 10000;\n  ASSERT_STREQ(\"10.00 k\", StringConversion::GetReadableSizeString(size).c_str());\n  ASSERT_STREQ(\"10.00 kB\", StringConversion::GetReadableSizeString(size,\n               \"B\").c_str());\n  size = 10200000;\n  ASSERT_STREQ(\"10.20 M\", StringConversion::GetReadableSizeString(size).c_str());\n  ASSERT_STREQ(\"10.20 MB\", StringConversion::GetReadableSizeString(size,\n               \"B\").c_str());\n  size = 1500000000;\n  ASSERT_STREQ(\"1.50 G\", StringConversion::GetReadableSizeString(size).c_str());\n  ASSERT_STREQ(\"1.50 GB\", StringConversion::GetReadableSizeString(size,\n               \"B\").c_str());\n  size = 1090000000000;\n  ASSERT_STREQ(\"1.09 T\", StringConversion::GetReadableSizeString(size).c_str());\n  ASSERT_STREQ(\"1.09 TB\", StringConversion::GetReadableSizeString(size,\n               \"B\").c_str());\n  size = 1340000000000000;\n  ASSERT_STREQ(\"1.34 P\", StringConversion::GetReadableSizeString(size).c_str());\n  ASSERT_STREQ(\"1.34 PB\", StringConversion::GetReadableSizeString(size,\n               \"B\").c_str());\n  size = 2310000000000000000;\n  ASSERT_STREQ(\"2.31 E\", StringConversion::GetReadableSizeString(size).c_str());\n  ASSERT_STREQ(\"2.31 EB\", StringConversion::GetReadableSizeString(size,\n               \"B\").c_str());\n}\n\nTEST(StringConversion, ReplaceStringInPlace)\n{\n  std::string ref = \"aabbccdd\\\"eeffgg\\\"hhiijj\\\"\";\n  std::string input = ref;\n  StringConversion::ReplaceStringInPlace(input, \"\", \"\");\n  ASSERT_STREQ(ref.c_str(), input.c_str());\n  input = ref;\n  StringConversion::ReplaceStringInPlace(input, \"x\", \"\");\n  ASSERT_STREQ(ref.c_str(), input.c_str());\n  input = ref;\n  StringConversion::ReplaceStringInPlace(input, \"x\", \"y\");\n  ASSERT_STREQ(ref.c_str(), input.c_str());\n  input = ref;\n  StringConversion::ReplaceStringInPlace(input, \"xyz\", \"x\");\n  ASSERT_STREQ(ref.c_str(), input.c_str());\n  input = ref;\n  StringConversion::ReplaceStringInPlace(input, \"xyz\", \"zyx\");\n  ASSERT_STREQ(ref.c_str(), input.c_str());\n  input = ref;\n  StringConversion::ReplaceStringInPlace(input, \"a\", \"\");\n  ASSERT_STREQ(\"bbccdd\\\"eeffgg\\\"hhiijj\\\"\", input.c_str());\n  input = ref;\n  StringConversion::ReplaceStringInPlace(input, \"a\", \"x\");\n  ASSERT_STREQ(\"xxbbccdd\\\"eeffgg\\\"hhiijj\\\"\", input.c_str());\n  input = ref;\n  StringConversion::ReplaceStringInPlace(input, \"a\", \"xyz\");\n  ASSERT_STREQ(\"xyzxyzbbccdd\\\"eeffgg\\\"hhiijj\\\"\", input.c_str());\n  input = ref;\n  StringConversion::ReplaceStringInPlace(input, \"\\\"\", \"\");\n  ASSERT_STREQ(\"aabbccddeeffgghhiijj\", input.c_str());\n  input = ref;\n  StringConversion::ReplaceStringInPlace(input, \"\\\"\", \"y\");\n  ASSERT_STREQ(\"aabbccddyeeffggyhhiijjy\", input.c_str());\n  input = ref;\n  StringConversion::ReplaceStringInPlace(input, \"\\\"\", \"xy\");\n  ASSERT_STREQ(\"aabbccddxyeeffggxyhhiijjxy\", input.c_str());\n}\n\nTEST(StringConversion, TokenizeQuoted)\n{\n  std::vector<std::string> tokens;\n\n  StringConversion::TokenizeQuoted(\"\", tokens, \" \");\n  ASSERT_EQ(tokens.size(), 0u);\n  tokens.clear();\n\n  StringConversion::TokenizeQuoted(\"hello world test\", tokens, \" \");\n  ASSERT_EQ(tokens.size(), 3u);\n  ASSERT_STREQ(tokens[0].c_str(), \"hello\");\n  ASSERT_STREQ(tokens[1].c_str(), \"world\");\n  ASSERT_STREQ(tokens[2].c_str(), \"test\");\n  tokens.clear();\n\n  StringConversion::TokenizeQuoted(\"user.reva.overleaf.name=\\\"my first project\\\"\", tokens,\n                                   \" \");\n  ASSERT_EQ(tokens.size(), 1u);\n  ASSERT_STREQ(tokens[0].c_str(), \"user.reva.overleaf.name=my first project\");\n\n  std::vector<std::string> kv;\n  StringConversion::TokenizeQuoted(tokens[0], kv, \"=\");\n  ASSERT_EQ(kv.size(), 2u);\n  ASSERT_STREQ(kv[0].c_str(), \"user.reva.overleaf.name\");\n  ASSERT_STREQ(kv[1].c_str(), \"my first project\");\n  tokens.clear();\n  kv.clear();\n\n  std::string attr_line = \"sys.acl=\\\"u:12345:rwxm+dq\\\" sys.versioning=\\\"10\\\" \"\n                          \"user.reva.overleaf.name=\\\"my first project\\\"\";\n  StringConversion::TokenizeQuoted(attr_line, tokens, \" \");\n  ASSERT_EQ(tokens.size(), 3u);\n  ASSERT_STREQ(tokens[0].c_str(), \"sys.acl=u:12345:rwxm+dq\");\n  ASSERT_STREQ(tokens[1].c_str(), \"sys.versioning=10\");\n  ASSERT_STREQ(tokens[2].c_str(), \"user.reva.overleaf.name=my first project\");\n  tokens.clear();\n\n  StringConversion::TokenizeQuoted(\"key=\\\"value with \\\\\\\"quoted\\\\\\\" text\\\"\", tokens,\n                                   \" =\");\n  ASSERT_EQ(tokens.size(), 2u);\n  ASSERT_STREQ(tokens[0].c_str(), \"key\");\n  ASSERT_STREQ(tokens[1].c_str(), \"value with \\\"quoted\\\" text\");\n  tokens.clear();\n\n  StringConversion::TokenizeQuoted(\"hello   world\", tokens, \" \");\n  ASSERT_EQ(tokens.size(), 2u);\n  ASSERT_STREQ(tokens[0].c_str(), \"hello\");\n  ASSERT_STREQ(tokens[1].c_str(), \"world\");\n  tokens.clear();\n}\n\nEOSCOMMONTESTING_END\n"
  },
  {
    "path": "unit_tests/common/StringSplitTests.cc",
    "content": "#include \"gtest/gtest.h\"\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wmaybe-uninitialized\"\n#pragma GCC diagnostic ignored \"-Wuninitialized\"\n#endif\n#include \"common/StringSplit.hh\"\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif\n#include <unordered_set>\n\nusing namespace eos::common;\nusing sv_vector = std::vector<std::string_view>;\nusing sv_ilist = std::initializer_list<std::string_view>;\nusing s_vector = std::vector<std::string>;\n\nTEST(StringSplit, Empty)\n{\n  sv_vector emptyv;\n  ASSERT_EQ(StringSplit(\"\", \"\"), emptyv);\n  ASSERT_EQ(StringSplit(\"////\", \"/\"), emptyv);\n  ASSERT_EQ(StringSplit(\"abcd\", \"\"), sv_vector({\"abcd\"}));\n  ASSERT_EQ(CharSplitIt(\"abcd\", '/'), sv_vector({\"abcd\"}));\n  char un_init;\n  ASSERT_EQ(CharSplitIt(\"abcd\", un_init), sv_vector({\"abcd\"}));\n  ASSERT_EQ(CharSplitIt(\"abcd\", '\\0'), sv_vector({\"abcd\"}));\n}\n\nTEST(StringSplit, BasicIt)\n{\n  sv_vector expect_v{\"eos\", \"foo\", \"bar\"};\n  ASSERT_EQ(StringSplitIt(\"/eos/foo/bar/\", \"/\"),\n            expect_v);\n  ASSERT_EQ(StringSplitIt(\"/////eos//foo//bar////\", \"/\"),\n            expect_v);\n  ASSERT_EQ(StringSplitIt(\"eos//foo//bar\", \"/\"),\n            expect_v);\n  ASSERT_EQ(CharSplitIt(\"/eos/foo/bar/\", '/'),\n            expect_v);\n  ASSERT_EQ(CharSplitIt(\"/////eos//foo//bar////\", '/'),\n            expect_v);\n  ASSERT_EQ(CharSplitIt(\"eos//foo//bar\", '/'),\n            expect_v);\n}\n\nTEST(StringSplit, BasicStdString)\n{\n  s_vector expect_v{\"eos\", \"foo\", \"bar\"};\n  ASSERT_EQ(StringSplit<s_vector>(\"/eos/foo/bar/\", \"/\"),\n            expect_v);\n  ASSERT_EQ(StringSplit<s_vector>(\"/////eos//foo//bar////\", \"/\"),\n            expect_v);\n  ASSERT_EQ(StringSplit<s_vector>(\"eos//foo//bar\", \"/\"),\n            expect_v);\n}\n\nTEST(StringSplit, NullSplit)\n{\n  std::string null_string;\n  null_string += '\\0';\n  ASSERT_EQ(CharSplitIt(null_string, '\\0'), sv_vector());\n  // Verify this works with std::string also\n  ASSERT_EQ(StringSplitIt(null_string, null_string), sv_vector());\n  s_vector expect_v{\"eos\", \"foo\", \"bar\"};\n  std::string s = \"eos\";\n  s += '\\0';\n  s += \"foo\";\n  s += '\\0';\n  s += \"bar\";\n  ASSERT_EQ(CharSplitIt(s, '\\0'), expect_v);\n  ASSERT_EQ(StringSplitIt(s, null_string), expect_v);\n  std::string s2 = s;\n  s2 += '\\0';\n  ASSERT_EQ(CharSplitIt(s2, '\\0'), expect_v);\n  ASSERT_EQ(StringSplitIt(s2, null_string), expect_v);\n  std::string s3 = null_string + s2;\n  ASSERT_EQ(CharSplitIt(s3, '\\0'), expect_v);\n  ASSERT_EQ(StringSplitIt(s3, null_string), expect_v);\n  std::string s4;\n\n  for (int i = 0; i < 1024; i++) {\n    s4 += '\\0';\n  }\n\n  s4 += s2;\n  ASSERT_EQ(CharSplitIt(s4, '\\0'), expect_v);\n  ASSERT_EQ(StringSplitIt(s4, null_string), expect_v);\n  ASSERT_EQ(StringSplitIt(s4, \"/\"), sv_vector({s4}));\n}\n\nTEST(StringSplit, EmptyIter)\n{\n  auto empty = StringSplit(\"////\", \"/\");\n  ASSERT_EQ(empty.begin(), empty.end());\n  const auto segments = StringSplit(\"/eos/foo/bar/\", \",\");\n  ASSERT_NE(segments.begin(), segments.end());\n}\n\nTEST(StringSplit, Iterator)\n{\n  const auto segments = StringSplit(\"/eos/foo/bar/\", \"/\");\n  auto iter = segments.begin();\n  ASSERT_EQ(\"eos\", *iter);\n  ASSERT_EQ(3, iter->size());\n  ASSERT_EQ(\"eos\", *iter++);\n  ASSERT_EQ(\"foo\", *iter);\n  ASSERT_EQ(\"bar\", *++iter);\n  ASSERT_EQ(segments.end(), ++iter);\n}\n\nTEST(StringSplit, IteratorCopy)\n{\n  const auto segments = StringSplitIt(\"/eos/foo/bar\", \"/\");\n  auto start_segment = segments.begin();\n  ASSERT_NE(start_segment, segments.end());\n  auto start_copy = start_segment;\n  ASSERT_EQ(start_copy, start_segment);\n  ASSERT_EQ(\"eos\", *start_segment);\n  ASSERT_NE(start_copy, ++start_segment);\n  ASSERT_EQ(\"foo\", *start_segment++);\n  ASSERT_EQ(segments.end(), ++start_segment);\n  ASSERT_EQ(\"eos\", *start_copy);\n  ASSERT_EQ(\"foo\", *++start_copy);\n}\n\nTEST(StringSplit, StrCopy)\n{\n  std::vector<std::string> expected = {\"eos\", \"foo\", \"bar\"};\n  std::vector<std::string> actual;\n\n  for (std::string_view part : StringSplit(\"/eos/foo/bar/\", \"/\")) {\n    actual.emplace_back(part);\n  }\n\n  ASSERT_EQ(expected, actual);\n}\n\nTEST(StringSplit, MultiSplit)\n{\n  sv_vector expect_v {\"key1\", \"val1\", \"key2\", \"val2\"};\n  ASSERT_EQ(StringSplit(\"key1=val1;\\nkey2=val2\", \";=\\n\"), expect_v);\n  std::string k = \"?key1=val1\";\n  k += '\\0';\n  k += \";key2=val2;\";\n  std::string delim = \"?=;\\n\";\n  delim += '\\0';\n  ASSERT_EQ(StringSplit(k, delim), expect_v);\n  // Check that a delim starting with null doesn't break parsing\n  std::string delim2;\n  delim2 += '\\0';\n  delim2 += delim;\n  ASSERT_EQ(StringSplit(k, delim2), expect_v);\n  std::string k2;\n\n  for (int i = 0; i <= 10; i++) {\n    k2 += '\\0';\n  }\n\n  k2 += k;\n  ASSERT_EQ(StringSplit(k2, delim), expect_v);\n}\n\nTEST(StringSplit, CommaList)\n{\n  sv_vector expect_v {\"group1\", \"group2\", \"group3\", \"group4\", \"group5\"};\n  ASSERT_EQ(StringSplit(\"group1, group2, group3 \\n, group4,group5\", \", \\n\"),\n            expect_v);\n  std::unordered_set<std::string> expect_s {\"group2\", \"group1\", \"group3\", \"group5\", \"group4\"};\n  ASSERT_EQ(\n    StringSplit<std::unordered_set<std::string>>(\"group1, group2, group3 \\n, group4,group5\",\n        \", \\n\"),\n    expect_s);\n}\n\nTEST(StringSplit, get_delim_p)\n{\n  using namespace std::literals;\n  // pos search is left inclusive [start,end)\n  ASSERT_EQ(detail::get_delim_p(\"foo;;bar;baz\"sv, \";\"sv, 0), 3);\n  ASSERT_EQ(detail::get_delim_p(\"foo;;bar;baz\"sv, \";\"sv, 3), 3);\n  ASSERT_EQ(detail::get_delim_p(\"foo;;bar;baz\"sv, \";\"sv, 4), 4);\n  ASSERT_EQ(detail::get_delim_p(\"foo;;bar;baz\"sv, \";\"sv, 5), 8);\n  // Test the char variant\n  ASSERT_EQ(detail::get_delim_p(\"foo;;bar;baz\"sv, ';', 0), 3);\n  ASSERT_EQ(detail::get_delim_p(\"foo;;bar;baz\"sv, ';', 3), 3);\n  ASSERT_EQ(detail::get_delim_p(\"foo;;bar;baz\"sv, ';', 4), 4);\n  ASSERT_EQ(detail::get_delim_p(\"foo;;bar;baz\"sv, ';', 5), 8);\n}\n\nTEST(StringSplit, PathSplitter)\n{\n  s_vector expect_v{\"eos\", \"foo\", \"bar\"};\n  s_vector empty;\n  ASSERT_EQ(SplitPath(\"\"), empty);\n  ASSERT_EQ(SplitPath(\"/\"), empty);\n  ASSERT_EQ(SplitPath(\"///\"), empty);\n  ASSERT_EQ(SplitPath(\"/eos/foo/bar/\"), expect_v);\n  ASSERT_EQ(SplitPath(\"/////eos//foo//bar////\"), expect_v);\n  ASSERT_EQ(SplitPath(\"eos//foo//bar\"), expect_v);\n}\n\nTEST(StringSplitIt, GetRootPath)\n{\n  ASSERT_EQ(GetRootPath(\"/eos\"), \"eos\");\n  ASSERT_EQ(GetRootPath(\"\"), \"\");\n  ASSERT_EQ(GetRootPath(\"/////root\"), \"root\");\n  ASSERT_EQ(GetRootPath(\"no-split-string\"), \"no-split-string\");\n}\n"
  },
  {
    "path": "unit_tests/common/StringTokenizerTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: StringTokenizerTests.cc\n// Author: Mihai Patrascoiu <mihai.patrascoiu@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include <XrdOuc/XrdOucString.hh>\n\n#include \"Namespace.hh\"\n#include \"common/StringTokenizer.hh\"\n#include <memory>\n\n\nEOSCOMMONTESTING_BEGIN\n\nusing namespace eos::common;\n\n/* The StringTokenizer class performs a 2-step tokenizing process.\n * Initially, lines are extracted from the input, with '\\n' as the delimiter.\n * Afterwards, each line is tokenized into words, using ' ' as the delimiter.\n *\n * If the delimiters are found within quotes, tokenization will not happen\n * and they will be part of the same unit (line or token).\n */\n\nTEST(StringTokenizer, EmptyInput)\n{\n  std::string empty;\n  XrdOucString sempty;\n  std::unique_ptr<StringTokenizer> tokenizer;\n  tokenizer.reset(new StringTokenizer(0));\n  ASSERT_EQ(tokenizer->GetLine(), nullptr);\n  tokenizer.reset(new StringTokenizer(\"\"));\n  ASSERT_EQ(tokenizer->GetLine(), nullptr);\n  tokenizer.reset(new StringTokenizer(empty));\n  ASSERT_EQ(tokenizer->GetLine(), nullptr);\n  tokenizer.reset(new StringTokenizer(sempty));\n  ASSERT_EQ(tokenizer->GetLine(), nullptr);\n}\n\nTEST(StringTokenizer, GetLine)\n{\n  std::string input;\n  std::unique_ptr<StringTokenizer> tokenizer;\n  // Simple lines input\n  input = \"Hello Line 1\\n\"\n          \"Hello Line 2\\n\"\n          \"Hello Line 3\";\n  tokenizer.reset(new StringTokenizer(input));\n  ASSERT_STREQ(tokenizer->GetLine(), \"Hello Line 1\");\n  ASSERT_STREQ(tokenizer->GetLine(), \"Hello Line 2\");\n  ASSERT_STREQ(tokenizer->GetLine(), \"Hello Line 3\");\n  ASSERT_EQ(tokenizer->GetLine(), nullptr);\n  // Lines containing '\\n' delimiter within quotes\n  input = \"Hello Line 1 \\\"Quoted Line 1\\nQuoted Line2\\\"\\n\"\n          \"Hello Line 2\";\n  tokenizer.reset(new StringTokenizer(input));\n  ASSERT_STREQ(tokenizer->GetLine(),\n               \"Hello Line 1 \\\"Quoted Line 1\\nQuoted Line2\\\"\");\n  ASSERT_STREQ(tokenizer->GetLine(), \"Hello Line 2\");\n  ASSERT_EQ(tokenizer->GetLine(), nullptr);\n}\n\nTEST(StringTokenizer, GetToken)\n{\n  std::string input;\n  std::unique_ptr<StringTokenizer> tokenizer;\n  // Simple tokens\n  input = \"Input line\";\n  tokenizer.reset(new StringTokenizer(input));\n  tokenizer->GetLine();\n  ASSERT_STREQ(tokenizer->GetToken(), \"Input\");\n  ASSERT_STREQ(tokenizer->GetToken(), \"line\");\n  ASSERT_EQ(tokenizer->GetToken(), nullptr);\n  // Quoted tokens\n  // -- Tokens should be returned without enclosing quotes\n  input = \"\\\"Quoted\\\" \\\"arguments\\\"\";\n  tokenizer.reset(new StringTokenizer(input));\n  tokenizer->GetLine();\n  ASSERT_STREQ(tokenizer->GetToken(), \"Quoted\");\n  ASSERT_STREQ(tokenizer->GetToken(), \"arguments\");\n  ASSERT_EQ(tokenizer->GetToken(), nullptr);\n  // Edge case quoted tokens\n  // -- Escaped quotes should be left untouched\n  input =\n    \"\\\\\\\"Double\\\\\\\" \\\"\\\\\\\"escaped\\\\\\\"\\\" \\\\\\\"\\\"quoted\\\"\\\\\\\" \\\"simple\\\" argument\";\n  tokenizer.reset(new StringTokenizer(input));\n  tokenizer->GetLine();\n  ASSERT_STREQ(tokenizer->GetToken(), \"\\\\\\\"Double\\\\\\\"\");\n  ASSERT_STREQ(tokenizer->GetToken(), \"\\\\\\\"escaped\\\\\\\"\");\n  ASSERT_STREQ(tokenizer->GetToken(), \"\\\\\\\"\\\"quoted\\\"\\\\\\\"\");\n  ASSERT_STREQ(tokenizer->GetToken(), \"simple\");\n  ASSERT_STREQ(tokenizer->GetToken(), \"argument\");\n  ASSERT_EQ(tokenizer->GetToken(), nullptr);\n  // Tokens containing space delimiter and escaped quotes within quotes\n  // -- Tokens should contain spaces and the escaped quotes\n  input = \"\\\"Token with \\\\\\\"quotes\\\\\\\" and spaces\\\" argument\";\n  tokenizer.reset(new StringTokenizer(input));\n  tokenizer->GetLine();\n  ASSERT_STREQ(tokenizer->GetToken(), \"Token with \\\\\\\"quotes\\\\\\\" and spaces\");\n  ASSERT_STREQ(tokenizer->GetToken(), \"argument\");\n  ASSERT_EQ(tokenizer->GetToken(), nullptr);\n  // Null line sanity check\n  ASSERT_EQ(tokenizer->GetLine(), nullptr);\n}\n\nTEST(StringTokenizer, GetTokenUnquoted)\n{\n  std::string input;\n  std::unique_ptr<StringTokenizer> tokenizer;\n  // Simple tokens\n  input = \"Input line\";\n  tokenizer.reset(new StringTokenizer(input));\n  tokenizer->GetLine();\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(), \"Input\");\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(), \"line\");\n  ASSERT_EQ(tokenizer->GetTokenUnquoted(), nullptr);\n  // Quoted tokens\n  // -- Tokens should be returned without enclosing quotes\n  input = \"\\\"Quoted\\\" \\\"arguments\\\"\";\n  tokenizer.reset(new StringTokenizer(input));\n  tokenizer->GetLine();\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(), \"Quoted\");\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(), \"arguments\");\n  ASSERT_EQ(tokenizer->GetTokenUnquoted(), nullptr);\n  // Edge case quoted tokens\n  // -- Full quote unescaping should happen\n  input =\n    \"\\\\\\\"Double\\\\\\\" \\\"\\\\\\\"escaped\\\\\\\"\\\" \\\\\\\"\\\"quoted\\\"\\\\\\\" \\\"simple\\\" argument\";\n  tokenizer.reset(new StringTokenizer(input));\n  tokenizer->GetLine();\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(), \"\\\\\\\"Double\\\\\\\"\");\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(), \"\\\"escaped\\\"\");\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(), \"\\\\\\\"\\\"quoted\\\"\\\\\\\"\");\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(), \"simple\");\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(), \"argument\");\n  ASSERT_EQ(tokenizer->GetTokenUnquoted(), nullptr);\n  // Tokens containing space delimiter and escaped quotes within quotes\n  // -- Tokens should contain spaces and the unescaped quotes\n  input = \"\\\"Token with \\\\\\\"quotes\\\\\\\" and spaces\\\" argument\";\n  tokenizer.reset(new StringTokenizer(input));\n  tokenizer->GetLine();\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(), \"Token with \\\"quotes\\\" and spaces\");\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(), \"argument\");\n  ASSERT_EQ(tokenizer->GetTokenUnquoted(), nullptr);\n  // Null line sanity check\n  ASSERT_EQ(tokenizer->GetLine(), nullptr);\n}\n\nTEST(StringTokenizer, GetTokenEscapeAndFlag)\n{\n  std::string input;\n  std::unique_ptr<StringTokenizer> tokenizer;\n  // GetToken() with EscapeAnd flag\n  input = \"&Symbol& & \\\\& escaped\";\n  tokenizer.reset(new StringTokenizer(input));\n  tokenizer->GetLine();\n  ASSERT_STREQ(tokenizer->GetToken(), \"#AND#Symbol#AND#\");\n  ASSERT_STREQ(tokenizer->GetToken(), \"#AND#\");\n  ASSERT_STREQ(tokenizer->GetToken(), \"\\\\&\");\n  ASSERT_STREQ(tokenizer->GetToken(), \"escaped\");\n  ASSERT_EQ(tokenizer->GetToken(), nullptr);\n  // GetTokenUnquoted() with EscapeAnd flag\n  tokenizer.reset(new StringTokenizer(input));\n  tokenizer->GetLine();\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(), \"#AND#Symbol#AND#\");\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(), \"#AND#\");\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(), \"\\\\&\");\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(), \"escaped\");\n  ASSERT_EQ(tokenizer->GetTokenUnquoted(), nullptr);\n  // Get Token() without EscapeAnd flag\n  tokenizer.reset(new StringTokenizer(input));\n  tokenizer->GetLine();\n  ASSERT_STREQ(tokenizer->GetToken(false), \"&Symbol&\");\n  ASSERT_STREQ(tokenizer->GetToken(false), \"&\");\n  ASSERT_STREQ(tokenizer->GetToken(false), \"\\\\&\");\n  ASSERT_STREQ(tokenizer->GetToken(false), \"escaped\");\n  ASSERT_EQ(tokenizer->GetToken(false), nullptr);\n  // Get TokenUnquoted() without EscapeAnd flag\n  tokenizer.reset(new StringTokenizer(input));\n  tokenizer->GetLine();\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(false), \"&Symbol&\");\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(false), \"&\");\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(false), \"\\\\&\");\n  ASSERT_STREQ(tokenizer->GetTokenUnquoted(false), \"escaped\");\n  ASSERT_EQ(tokenizer->GetTokenUnquoted(false), nullptr);\n}\n\nTEST(StringTokenizer, NextToken)\n{\n  std::string token;\n  XrdOucString stoken;\n  std::unique_ptr<StringTokenizer> tokenizer;\n  std::string input = \"Line to tokenize\";\n  tokenizer.reset(new StringTokenizer(input));\n  // Parse using std::string token\n  ASSERT_STREQ(tokenizer->GetLine(), \"Line to tokenize\");\n  ASSERT_TRUE(tokenizer->NextToken(token));\n  ASSERT_STREQ(token.c_str(), \"Line\");\n  ASSERT_TRUE(tokenizer->NextToken(token));\n  ASSERT_STREQ(token.c_str(), \"to\");\n  ASSERT_TRUE(tokenizer->NextToken(token));\n  ASSERT_STREQ(token.c_str(), \"tokenize\");\n  ASSERT_FALSE(tokenizer->NextToken(token));\n  tokenizer.reset(new StringTokenizer(input));\n  // Parse using XrdOucString\n  ASSERT_STREQ(tokenizer->GetLine(), \"Line to tokenize\");\n  ASSERT_TRUE(tokenizer->NextToken(stoken));\n  ASSERT_STREQ(stoken.c_str(), \"Line\");\n  ASSERT_TRUE(tokenizer->NextToken(stoken));\n  ASSERT_STREQ(stoken.c_str(), \"to\");\n  ASSERT_TRUE(tokenizer->NextToken(stoken));\n  ASSERT_STREQ(stoken.c_str(), \"tokenize\");\n  ASSERT_FALSE(tokenizer->NextToken(stoken));\n}\n\nTEST(StringTokenizer, IsUnsignedNumber)\n{\n  // Valid numbers\n  ASSERT_TRUE(StringTokenizer::IsUnsignedNumber(\"100\"));\n  ASSERT_TRUE(StringTokenizer::IsUnsignedNumber(\"0\"));\n  ASSERT_FALSE(StringTokenizer::IsUnsignedNumber(\"-100\"));\n  ASSERT_FALSE(StringTokenizer::IsUnsignedNumber(\"0100\"));\n  // Empty string\n  std::string empty;\n  ASSERT_FALSE(StringTokenizer::IsUnsignedNumber(\"\"));\n  ASSERT_FALSE(StringTokenizer::IsUnsignedNumber(empty));\n  // Alphanumeric strings\n  ASSERT_FALSE(StringTokenizer::IsUnsignedNumber(\"abc10\"));\n  ASSERT_FALSE(StringTokenizer::IsUnsignedNumber(\"10abc\"));\n  ASSERT_FALSE(StringTokenizer::IsUnsignedNumber(\"1bc1\"));\n}\n\nTEST(StringTokenizer, Split)\n{\n  std::string path = \"/eos/foo/bar/baz/\";\n  std::vector<std::string> v{\"eos\", \"foo\", \"bar\", \"baz\"};\n  ASSERT_EQ(StringTokenizer::split<std::vector<std::string>>(path, '/'),\n            v);\n  ASSERT_EQ(StringTokenizer::split<std::vector<std::string>>(\"eos/foo/bar/baz\",\n            '/'),\n            v);\n  ASSERT_EQ(\n    StringTokenizer::split<std::vector<std::string>>(\"///eos//foo/bar/baz///\",\n        '/'),\n    v);\n  std::string path_null = \"/eos/foo\";\n  path_null += '\\0';\n  path_null += \"bar\";\n  path_null += '\\0';\n  std::vector<std::string> path_null_v = {\"/eos/foo\", \"bar\"};\n  ASSERT_EQ(StringTokenizer::split<std::vector<std::string>>(path_null, '\\0'),\n            path_null_v);\n  std::string path_null2;\n  path_null2 += '\\0';\n  path_null2 += path_null;\n  ASSERT_EQ(StringTokenizer::split<std::vector<std::string>>(path_null2, '\\0'),\n            path_null_v);\n  // We explicitly want to test that passing an unitialized char variable\n  // does not cause any issues - therefore deactivate the warning for\n  // this piece of code.\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wmaybe-uninitialized\"\n#pragma GCC diagnostic ignored \"-Wuninitialized\"\n  char empty;\n  ASSERT_EQ(StringTokenizer::split<std::vector<std::string>>(\"abcd\", empty),\n            std::vector<std::string>({\"abcd\"}));\n#pragma GCC diagnostic pop\n}\n\nEOSCOMMONTESTING_END\n"
  },
  {
    "path": "unit_tests/common/StringUtilsTests.cc",
    "content": "#include \"common/StringUtils.hh\"\n#include <string_view>\n#include \"gtest/gtest.h\"\n\nusing eos::common::StringToNumeric;\nusing std::string_literals::operator \"\"s;\nusing std::string_view_literals::operator \"\"sv;\n\nTEST(StringUtils, GetNumericBasic)\n{\n  uint32_t test_u32;\n  ASSERT_TRUE(StringToNumeric(\"100\"sv, test_u32));\n  EXPECT_EQ(test_u32, 100);\n  ASSERT_TRUE(StringToNumeric(\"0\"s, test_u32));\n  EXPECT_EQ(test_u32, 0);\n  ASSERT_TRUE(StringToNumeric(\"10units\"s, test_u32));\n  EXPECT_EQ(test_u32, 10);\n  // Test end ranges\n  char test_c;\n  ASSERT_TRUE(StringToNumeric(\"127\"sv, test_c));\n  EXPECT_EQ(test_c, 127);\n  ASSERT_FALSE(StringToNumeric(\"128\"sv, test_c));\n  ASSERT_TRUE(StringToNumeric(\"4294967295\"sv, test_u32));\n  EXPECT_EQ(test_u32, 4294967295u);\n  ASSERT_FALSE(StringToNumeric(\"4294967296\"sv, test_u32));\n  uint64_t test_u64;\n  ASSERT_TRUE(StringToNumeric(\"4294967296\"sv, test_u64));\n  EXPECT_EQ(test_u64, 4294967296);\n  ASSERT_TRUE(\n    StringToNumeric(\"9007199254740993\"sv, test_u64));\n  EXPECT_EQ(test_u64, 9007199254740993);\n  // Now for some\n  ASSERT_FALSE(StringToNumeric(\"pickles\"sv, test_u32));\n  ASSERT_FALSE(StringToNumeric(\"value=10\"sv, test_u32));\n}\n\nTEST(StringUtils, GetNumericNegative)\n{\n  using std::string_literals::operator \"\"s;\n  int test;\n  ASSERT_TRUE(StringToNumeric(\"-10\"sv, test));\n  EXPECT_EQ(test, -10);\n  char testc;\n  ASSERT_TRUE(StringToNumeric(\"-128\"sv, testc));\n  EXPECT_EQ(testc, -128);\n  ASSERT_FALSE(StringToNumeric(\"-129\"sv, testc));\n  EXPECT_EQ(testc, 0);\n  // Test default val\n  char default_c = -100;\n  ASSERT_FALSE(StringToNumeric(\"-129\"sv, testc, default_c));\n  EXPECT_EQ(testc, -100);\n}\n\n\nTEST(StringUtils, GetNumericDouble)\n{\n  float testf;\n  ASSERT_TRUE(StringToNumeric(\"1.0f\"s, testf));\n  EXPECT_FLOAT_EQ(testf, 1.0);\n  ASSERT_TRUE(StringToNumeric(\"1e5\"s, testf));\n  EXPECT_FLOAT_EQ(testf, 100000);\n  ASSERT_TRUE(StringToNumeric(\"3.14159265359\"s, testf));\n  // you get ~7 decimal places beyond which it is approximated\n  // Note that FLOAT_EQ will pass once you're 4e away, so any further\n  // decimal places will more or less pass\n  // Some of these may be platform specific, esp failure tests at\n  // limits is compiler + machine dependant\n  EXPECT_FLOAT_EQ(testf, 3.1415927);\n  std::string err;\n  float defaultf = 10;\n  ASSERT_FALSE(\n    StringToNumeric(\"1e129\"s, testf, defaultf, &err));\n  EXPECT_FLOAT_EQ(testf, 10);\n#if __cpp_lib_to_chars < 201611\n  EXPECT_EQ(err.find(\"\\\"msg=Failed float\"), 0);\n#else\n  EXPECT_EQ(err.find(\"\\\"msg=Failed Numeric\"), 0);\n#endif\n  ASSERT_FALSE(\n    StringToNumeric(\"garbage\"s, testf, defaultf, &err));\n  EXPECT_FLOAT_EQ(testf, 10);\n  double testd;\n  ASSERT_TRUE(StringToNumeric(\"3.14159265358979\"s, testd));\n  EXPECT_DOUBLE_EQ(testd, 3.14159265358979);\n  ASSERT_TRUE(StringToNumeric(\"9007199254740992\"s, testd));\n  EXPECT_DOUBLE_EQ(testd, 9007199254740992);\n  // And we're going to the approximation territory from this point on\n  ASSERT_TRUE(StringToNumeric(\"9007199254740993\"s, testd));\n  EXPECT_DOUBLE_EQ(testd, 9007199254740992);\n  ASSERT_TRUE(StringToNumeric(\"1.023e129\"s, testd));\n  EXPECT_DOUBLE_EQ(testd, 1.023e129);\n  ASSERT_TRUE(StringToNumeric(\"1e308\"s, testd));\n  EXPECT_DOUBLE_EQ(testd, 1e308);\n  double defaultd = 3.14;\n  ASSERT_FALSE(StringToNumeric(\"1e309\"s, testd, defaultd));\n  EXPECT_DOUBLE_EQ(testd, 3.14);\n  long double testld;\n  ASSERT_TRUE(StringToNumeric(\"3.1415926535897932384626433832795028\"s,\n                              testld));\n  EXPECT_EQ(testld, 3.1415926535897932384626433832795028L);\n}\n\n\nTEST(StringUtils, StringToNumericErrorMessage)\n{\n  uint32_t test_u32;\n  std::string err_msg;\n  ASSERT_FALSE(\n    StringToNumeric(\"pickles\"sv, test_u32, 0u, &err_msg));\n  ASSERT_TRUE(!err_msg.empty());\n  // FIXME: not sure if cpp guarantees error message strings will be maintained\n  // across standards/ OSes, so probably better to search for invalid arg/ out of\n  // range etc in case this test keeps failing on update\n  EXPECT_EQ(err_msg,\n            \"\\\"msg=Failed Numeric conversion\\\" key=pickles error_msg=Invalid argument\");\n  EXPECT_EQ(test_u32, 0);\n  err_msg.clear();\n  char test_c;\n  ASSERT_FALSE(StringToNumeric(\"128\"sv, test_c, (char)0, &err_msg));\n  ASSERT_TRUE(!err_msg.empty());\n  EXPECT_EQ(err_msg,\n            \"\\\"msg=Failed Numeric conversion\\\" key=128 error_msg=Numerical result out of range\");\n}\n\nusing eos::common::replace_all;\n\nTEST(StringUtils, ReplaceAllEmpty) {\n  std::string str = \"the quick brown fox jumps over the lazy dog\";\n  std::string orig_str {str};\n\n  replace_all(orig_str, \"\",\"\");\n  EXPECT_EQ(orig_str, str);\n\n  replace_all(orig_str, \"\", \"foo\");\n  EXPECT_EQ(orig_str, str);\n\n  replace_all(orig_str, \"fox\", \"charlie\", 1000,2000);\n  EXPECT_EQ(orig_str, str);\n\n  replace_all(orig_str, \"fox\", \"charlie\", 10,5);\n  EXPECT_EQ(orig_str, str);\n\n}\n\nTEST(StringUtils, ReplaceAllBasic) {\n  std::string str = \"the quick brown fox jumps over the lazy dog\";\n\n  replace_all(str, \"fox\", \"charlie\");\n  EXPECT_EQ(str, \"the quick brown charlie jumps over the lazy dog\");\n\n  str = \"the quick brown fox jumps over the lazy dog\";\n  replace_all(str, \"the\", \"a\");\n  EXPECT_EQ(str, \"a quick brown fox jumps over a lazy dog\");\n\n  str = \"the quick brown fox jumps over the lazy dog\";\n  replace_all(str, \"o\", \"O\");\n  EXPECT_EQ(str, \"the quick brOwn fOx jumps Over the lazy dOg\");\n\n  str = \"the quick brown fox jumps over the lazy dog\";\n  replace_all(str, \" \", \"_\");\n  EXPECT_EQ(str, \"the_quick_brown_fox_jumps_over_the_lazy_dog\");\n\n  str = \"the quick brown fox jumps over the lazy dog\";\n  replace_all(str, \"the\", \"a\", 10, 43);\n  EXPECT_EQ(str, \"the quick brown fox jumps over a lazy dog\");\n\n  str = \"the quick brown fox jumps over the lazy dog\";\n  replace_all(str, \"o\", \"O\", 12, 25);\n  EXPECT_EQ(str, \"the quick brOwn fOx jumps over the lazy dog\");\n}\n\nTEST(StringUtils, ReplaceAllReduce) {\n  std::string str = \"aaaaaa\";\n  replace_all(str, \"aa\", \"b\");\n  EXPECT_EQ(str, \"bbb\");\n\n  str = \"aaaaa\";\n  replace_all(str, \"aa\",\"b\");\n  EXPECT_EQ(str, \"bba\");\n}\n\nTEST(StringUtils, ReplaceAllEmptyIn) {\n  std::string str = \"hello world\";\n  replace_all(str, \"hello\",\"\");\n  EXPECT_EQ(str, \" world\");\n}\n\n// Test with 'from' parameter\nTEST(StringUtils, ReplaceAllFromParameter) {\n    std::string str = \"abc def abc ghi abc\";\n    replace_all(str, \"abc\", \"xyz\", 4); // Start from position 4\n    EXPECT_EQ(str, \"abc def xyz ghi xyz\");\n}\n\n// Test with 'to' parameter\nTEST(StringUtils, ReplaceAllToParameter) {\n    std::string str = \"abc def abc ghi abc\";\n    replace_all(str, \"abc\", \"xyz\", 0, 10); // Only replace up to position 10\n    EXPECT_EQ(str, \"xyz def xyz ghi abc\");\n}\n\n// Test with both 'from' and 'to' parameters\nTEST(StringUtils, ReplaceAllFromAndToParameters) {\n    std::string str = \"abc def abc ghi abc jkl abc\";\n    replace_all(str, \"abc\", \"xyz\", 4, 15); // Replace from pos 4 to pos 15\n    EXPECT_EQ(str, \"abc def xyz ghi abc jkl abc\");\n}\n\n// Test invalid range parameters\nTEST(StringUtils, ReplaceAllInvalidRangeParameters) {\n    std::string str = \"hello world hello\";\n    std::string original = str;\n\n    // from >= string size\n    replace_all(str, \"hello\", \"hi\", 20);\n    EXPECT_EQ(str, original);\n\n    // from > to\n    std::string str2 = \"hello world hello\";\n    replace_all(str2, \"hello\", \"hi\", 10, 5);\n    EXPECT_EQ(str2, original);\n}\n\n// Test overlapping patterns\nTEST(StringUtils, ReplaceAllOverlappingPatterns) {\n    std::string str = \"aaaa\";\n    replace_all(str, \"aa\", \"b\");\n    // Should replace non-overlapping occurrences: \"aaaa\" -> \"bb\"\n    EXPECT_EQ(str, \"bb\");\n}\n\n// Test single character replacement\nTEST(StringUtils, ReplaceAllSingleCharacterReplacement) {\n    std::string str = \"a b a c a\";\n    replace_all(str, \"a\", \"x\");\n    EXPECT_EQ(str, \"x b x c x\");\n}\n\n// Test replacement at string boundaries\nTEST(StringUtils, ReplaceAllBoundaryReplacement) {\n    // At beginning\n    std::string str1 = \"hello world\";\n    replace_all(str1, \"hello\", \"hi\");\n    EXPECT_EQ(str1, \"hi world\");\n\n    // At end\n    std::string str2 = \"world hello\";\n    replace_all(str2, \"hello\", \"hi\");\n    EXPECT_EQ(str2, \"world hi\");\n\n    // Entire string\n    std::string str3 = \"hello\";\n    replace_all(str3, \"hello\", \"hi\");\n    EXPECT_EQ(str3, \"hi\");\n}\n\n// Test with special characters\nTEST(StringUtils, ReplaceAllSpecialCharacters) {\n    std::string str = \"a\\nb\\ta\\nc\";\n    replace_all(str, \"a\", \"x\");\n    EXPECT_EQ(str, \"x\\nb\\tx\\nc\");\n}\n\n// Test case sensitivity\nTEST(StringUtils, ReplaceAllCaseSensitivity) {\n    std::string str = \"Hello world HELLO\";\n    replace_all(str, \"Hello\", \"Hi\");\n    EXPECT_EQ(str, \"Hi world HELLO\"); // Should not replace \"HELLO\"\n}\n\n// Test with long strings and patterns\nTEST(StringUtils, ReplaceAllLongStringsAndPatterns) {\n    std::string str = \"this is a long pattern and this is another long pattern\";\n    replace_all(str, \"long pattern\", \"short\");\n    EXPECT_EQ(str, \"this is a short and this is another short\");\n}\n\n// Test performance with many replacements\nTEST(StringUtils, ReplaceAllManyReplacements) {\n    std::string str;\n    for (int i = 0; i < 1000; ++i) {\n        str += \"a \";\n    }\n    replace_all(str, \"a\", \"bb\");\n\n    // Verify the result\n    std::string expected;\n    for (int i = 0; i < 1000; ++i) {\n        expected += \"bb \";\n    }\n    EXPECT_EQ(str, expected);\n}\n\n// Test with substring that appears within the replacement\nTEST(StringUtils, ReplaceAllSubstringInReplacement) {\n    std::string str = \"abc abc abc\";\n    replace_all(str, \"abc\", \"abcdef\");\n    EXPECT_EQ(str, \"abcdef abcdef abcdef\");\n}\n\n// Test edge case where search pattern is larger than available range\nTEST(StringUtils, ReplaceAllSearchPatternLargerThanRange) {\n    std::string str = \"hello world\";\n    std::string original = str;\n    replace_all(str, \"hello world!\", \"hi\", 0, 5); // Pattern longer than range\n    EXPECT_EQ(str, original); // Should not change\n}\n\n// Test replacement with exact range boundaries\nTEST(StringUtils, ReplaceAllExactRangeBoundaries) {\n    std::string str = \"abcdefabc\";\n    replace_all(str, \"abc\", \"x\", 0, 2); // Range exactly matches first \"abc\"\n    EXPECT_EQ(str, \"xdefabc\");\n}\n\n// Test string_view parameters\nTEST(StringUtils, ReplaceAllStringViewParameters) {\n    std::string str = \"hello world hello\";\n    std::string_view search = \"hello\";\n    std::string_view replacement = \"hi\";\n    replace_all(str, search, replacement);\n    EXPECT_EQ(str, \"hi world hi\");\n}\n\n// Test with to parameter as string::npos (default)\nTEST(StringUtils, ReplaceAllDefaultToParameter) {\n    std::string str = \"abc def abc ghi abc\";\n    replace_all(str, \"abc\", \"xyz\", 4, std::string::npos);\n    EXPECT_EQ(str, \"abc def xyz ghi xyz\");\n}\n\n// Test contraction that would make 'to' go below search pattern length\nTEST(StringUtils, ReplaceAllContractionBelowPatternLength) {\n    std::string str = \"abcabcabc\";\n    replace_all(str, \"abc\", \"x\", 0, 8); // This should handle the contraction properly\n    EXPECT_EQ(str, \"xxx\");\n}\n"
  },
  {
    "path": "unit_tests/common/SymKeysTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: SymKeysTests.cc\n// Author: Elvin Sindrilaru <esindril at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"common/SymKeys.hh\"\n#include <fstream>\n#include <list>\n\n//------------------------------------------------------------------------------\n// Cipher encoding and decoding test\n//------------------------------------------------------------------------------\nTEST(SymKeys, CipherTest)\n{\n  using namespace eos::common;\n  char* key = (char*)\"12345678901234567890\";\n  std::list<ssize_t> set_lengths {1, 10, 100, 1024, 4096, 5746};\n\n  for (auto it = set_lengths.begin(); it != set_lengths.end(); ++it) {\n    std::unique_ptr<char[]> data {new char[*it]};\n    // Generate random data\n    std::ifstream urandom(\"/dev/urandom\", std::ios::in | std::ios::binary);\n    urandom.read(data.get(), (ssize_t)*it);\n    urandom.close();\n    // Encrypt data\n    char* encrypted_data;\n    ssize_t encrypted_length = 0;\n    ASSERT_TRUE(SymKey::CipherEncrypt(data.get(), *it, encrypted_data,\n                                      encrypted_length, key));\n    // Decrypt data\n    char* decrypted_data;\n    ssize_t decrypted_length = 0;\n    ASSERT_TRUE(SymKey::CipherDecrypt(encrypted_data, encrypted_length,\n                                      decrypted_data, decrypted_length,\n                                      key));\n    ASSERT_TRUE(*it == decrypted_length)\n        << \"Expected:\" << *it << \", obtained:\" << decrypted_length << std::endl;\n    ASSERT_TRUE(memcmp(data.get(), decrypted_data, decrypted_length) == 0);\n    free(encrypted_data);\n    free(decrypted_data);\n  }\n}\n\n//------------------------------------------------------------------------------\n// Base64 test\n//------------------------------------------------------------------------------\nTEST(SymKeys, Base64Test)\n{\n  std::map<std::string, std::string> map_tests = {\n    {\"\",  \"\"},\n    {\"f\", \"Zg==\"},\n    {\"fo\", \"Zm8=\"},\n    {\"foo\", \"Zm9v\"},\n    {\"foob\", \"Zm9vYg==\"},\n    {\"fooba\", \"Zm9vYmE=\"},\n    {\"foobar\", \"Zm9vYmFy\"},\n    {\"testtest\", \"dGVzdHRlc3Q=\"}\n  };\n\n  for (auto elem = map_tests.begin(); elem != map_tests.end(); ++elem) {\n    // Check encoding\n    std::string encoded;\n    ASSERT_TRUE(eos::common::SymKey::Base64Encode((char*)elem->first.c_str(),\n                elem->first.length(), encoded));\n    ASSERT_TRUE(elem->second == encoded)\n        << \"Expected:\" << elem->second << \", obtained:\" << encoded << std::endl;\n    // Check decoding\n    char* decoded_bytes;\n    ssize_t decoded_length;\n    ASSERT_TRUE(eos::common::SymKey::Base64Decode(encoded.c_str(), decoded_bytes,\n                decoded_length));\n    ASSERT_TRUE(elem->first.length() == (size_t)decoded_length)\n        << \"Expected:\" << elem->first.length() << \", obtained:\" << decoded_length;\n    ASSERT_TRUE(elem->first == decoded_bytes)\n        << \"Expected:\" << elem->first << \", obtained:\" << decoded_bytes;\n    free(decoded_bytes);\n  }\n}\n"
  },
  {
    "path": "unit_tests/common/ThreadPoolTest.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ThreadPoolTest.cc\n// Author: Jozsef Makai - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"common/ThreadPool.hh\"\n\nusing namespace eos::common;\n\nTEST(ThreadPoolTest, PoolSizeTest)\n{\n  ThreadPool pool(3, 3);\n  std::vector<std::future<std::thread::id>> futures;\n\n  for (int i = 0; i < 10; i++) {\n    auto future = pool.PushTask<std::thread::id>(\n    [] {\n      std::this_thread::sleep_for(std::chrono::milliseconds(20));\n      return std::this_thread::get_id();\n    }\n                  );\n    futures.emplace_back(std::move(future));\n  }\n\n  std::set<std::thread::id> threadIds;\n\n  for (auto && future : futures) {\n    threadIds.insert(future.get());\n  }\n\n  // Check if we have exactly 3 different thread ids\n  ASSERT_EQ(3, threadIds.size());\n}\n\nTEST(ThreadPoolTest, ScaleUpAndDownTest)\n{\n  ThreadPool pool(2, 4, 2, 1, 1);\n  std::vector<std::future<std::thread::id>> futures;\n\n  for (int i = 0; i < 500; i++) {\n    auto future = pool.PushTask<std::thread::id>(\n    [] {\n      std::this_thread::sleep_for(std::chrono::milliseconds(20));\n      return std::this_thread::get_id();\n    }\n                  );\n    futures.emplace_back(std::move(future));\n  }\n\n  std::set<std::thread::id> threadIds;\n\n  for (auto && future : futures) {\n    threadIds.insert(future.get());\n  }\n\n  // Check if we have scaled up to 4 threads\n  ASSERT_EQ(4, threadIds.size());\n  std::this_thread::sleep_for(std::chrono::seconds(2));\n  futures.clear();\n  threadIds.clear();\n\n  for (int i = 0; i < 10; i++) {\n    auto future = pool.PushTask<std::thread::id>(\n    [] {\n      std::this_thread::sleep_for(std::chrono::milliseconds(10));\n      return std::this_thread::get_id();\n    }\n                  );\n    futures.emplace_back(std::move(future));\n  }\n\n  for (auto && future : futures) {\n    threadIds.insert(future.get());\n  }\n\n  // Check if we have scaled down to 2 threads\n  ASSERT_EQ(2, threadIds.size());\n}\n\n// Test dynamically scalling the min/max size of the thread pool\nTEST(ThreadPoolTest, MaxMinDynamicScalling)\n{\n  ThreadPool pool(2, 4, 2, 1, 1);\n  std::vector<std::future<std::thread::id>> futures;\n\n  for (int i = 0; i < 1000; i++) {\n    auto future = pool.PushTask<std::thread::id>(\n    [] {\n      std::this_thread::sleep_for(std::chrono::milliseconds(20));\n      return std::this_thread::get_id();\n    }\n                  );\n    futures.emplace_back(std::move(future));\n  }\n\n  std::this_thread::sleep_for(std::chrono::seconds(3));\n  // Check if we have scaled up to 4 threads\n  ASSERT_EQ(4, pool.GetSize());\n  // Update the max number of threads\n  pool.SetMaxThreads(6);\n  std::this_thread::sleep_for(std::chrono::seconds(3));\n  // Check if we have scaled up to 6 threads\n  ASSERT_EQ(6, pool.GetSize());\n  // Update the max number of threads\n  pool.SetMaxThreads(2);\n  std::this_thread::sleep_for(std::chrono::seconds(3));\n  ASSERT_EQ(2, pool.GetSize());\n}\n"
  },
  {
    "path": "unit_tests/common/TimingTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: TimingTest.cc\n// Author: Elvin-Alin Sindrilaru <esindril at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"Namespace.hh\"\n#include \"common/Timing.hh\"\n#include \"common/SteadyClock.hh\"\n#include \"common/IntervalStopwatch.hh\"\n\nEOSCOMMONTESTING_BEGIN\n\nTEST(Timing, LsFormat)\n{\n  using eos::common::Timing;\n  time_t now = time(0);\n  struct tm utc;\n  struct tm* tm = gmtime_r(&now, &utc);\n  std::string output;\n  output = Timing::ToLsFormat(tm);\n  // Should contain the hour:minute\n  ASSERT_TRUE(output.find(':') != std::string::npos);\n  // 1 year ago\n  tm->tm_year--;\n  output = Timing::ToLsFormat(tm);\n  // Should contain only the year at the end\n  ASSERT_TRUE(output.find(':') == std::string::npos);\n}\n\nTEST(Timing, TimespecFromTimespecString)\n{\n  using eos::common::Timing;\n  struct timespec ts;\n  int rc;\n  // Extract timespec from predefined timespec string\n  rc = Timing::Timespec_from_TimespecStr(\"1550061572.9528439045\", ts);\n  ASSERT_EQ(rc, 0);\n  ASSERT_EQ(ts.tv_sec, 1550061572);\n  ASSERT_EQ(ts.tv_nsec, 952843904);\n  rc = Timing::Timespec_from_TimespecStr(\"1550061572\", ts);\n  ASSERT_EQ(rc, 0);\n  ASSERT_EQ(ts.tv_sec, 1550061572);\n  ASSERT_EQ(ts.tv_nsec, 0);\n  // Convert current time into timespec string\n  // Extract timespec from previously generated string\n  struct timespec now;\n  char buff[64];\n  Timing::GetTimeSpec(now);\n  sprintf(buff, \"%ld.%ld\", now.tv_sec, now.tv_nsec);\n  Timing::Timespec_from_TimespecStr(buff, ts);\n  ASSERT_EQ(ts.tv_sec, now.tv_sec);\n  ASSERT_EQ(ts.tv_nsec, now.tv_nsec);\n  // Incomplete strings\n  ASSERT_EQ(Timing::Timespec_from_TimespecStr(\"100.no_digits\", ts), -1);\n  ASSERT_EQ(ts.tv_sec, 0);\n  ASSERT_EQ(ts.tv_nsec, 0);\n  ASSERT_EQ(Timing::Timespec_from_TimespecStr(\"no_digits.100\", ts), -1);\n  ASSERT_EQ(ts.tv_sec, 0);\n  ASSERT_EQ(ts.tv_nsec, 0);\n  // Invalid strings\n  ASSERT_EQ(Timing::Timespec_from_TimespecStr(\"no digits\", ts), -1);\n  ASSERT_EQ(Timing::Timespec_from_TimespecStr(\"...\", ts), -1);\n  ASSERT_EQ(Timing::Timespec_from_TimespecStr(\"\", ts), -1);\n  ASSERT_EQ(ts.tv_sec, 0);\n  ASSERT_EQ(ts.tv_nsec, 0);\n}\n\nTEST(Timing, NsFromTimespecString)\n{\n  using eos::common::Timing;\n  long long nanoseconds;\n  // Extract nanoseconds from predefined timespec string\n  nanoseconds = Timing::Ns_from_TimespecStr(\"1550061572.9528439045\");\n  ASSERT_EQ(nanoseconds, 1550061572952843904ULL);\n  nanoseconds = Timing::Ns_from_TimespecStr(\"1550061572\");\n  ASSERT_EQ(nanoseconds, 1550061572000000000ULL);\n  // Convert current time into timespec string\n  // Extract nanoseconds from previously generated string\n  struct timespec now;\n  char buff[64];\n  Timing::GetTimeSpec(now);\n  sprintf(buff, \"%ld.%ld\", now.tv_sec, now.tv_nsec);\n  nanoseconds = Timing::Ns_from_TimespecStr(buff);\n  ASSERT_EQ(nanoseconds, Timing::GetAgeInNs(0LL, &now));\n  // Invalid strings\n  ASSERT_EQ(Timing::Ns_from_TimespecStr(\"no digits\"), -1);\n  ASSERT_EQ(Timing::Ns_from_TimespecStr(\"...\"), -1);\n  ASSERT_EQ(Timing::Ns_from_TimespecStr(\"\"), -1);\n}\n\nTEST(SteadyClock, FakeTests)\n{\n  eos::common::SteadyClock sc(true);\n  ASSERT_EQ(sc.GetTime(), std::chrono::steady_clock::time_point());\n  std::chrono::steady_clock::time_point startOfTime;\n  startOfTime += std::chrono::seconds(5);\n  sc.advance(std::chrono::seconds(5));\n  ASSERT_EQ(sc.GetTime(), startOfTime);\n}\n\nTEST(SteadyClock, constructor)\n{\n  // Yes the 50 different ways to initialize!\n  auto clock1 = eos::common::SteadyClock();\n  ASSERT_FALSE(clock1.IsFake());\n  auto clock2 = eos::common::SteadyClock(false);\n  ASSERT_FALSE(clock2.IsFake());\n  eos::common::SteadyClock default_clock;\n  ASSERT_FALSE(default_clock.IsFake());\n  eos::common::SteadyClock default_clock2 {};\n  ASSERT_FALSE(default_clock2.IsFake());\n  eos::common::SteadyClock clock3(false);\n  ASSERT_FALSE(clock3.IsFake());\n  eos::common::SteadyClock clock4{false};\n  ASSERT_FALSE(clock4.IsFake());\n  auto fake_clock = eos::common::SteadyClock(true);\n  ASSERT_TRUE(fake_clock.IsFake());\n}\n\nTEST(Timing, TimespecToString)\n{\n  struct timespec ts;\n  ts.tv_sec = 11111111;\n  ts.tv_nsec = 111111111;\n  ASSERT_STREQ(eos::common::Timing::TimespecToString(ts).c_str(),\n               \"11111111.111111111\");\n  ts.tv_sec = 121212;\n  ts.tv_nsec = 87654321;\n  ASSERT_STREQ(eos::common::Timing::TimespecToString(ts).c_str(),\n               \"121212.087654321\");\n  ts.tv_sec = 123;\n  ts.tv_nsec = 321;\n  ASSERT_STREQ(eos::common::Timing::TimespecToString(ts).c_str(),\n               \"123.000000321\");\n}\n\nTEST(IntervalStopwatch, BasicSanity)\n{\n  common::SteadyClock sc(true);\n  IntervalStopwatch stopwatch(std::chrono::milliseconds(0), &sc);\n  ASSERT_EQ(stopwatch.timeIntoCycle(), std::chrono::milliseconds(0));\n  ASSERT_EQ(stopwatch.timeRemainingInCycle(), std::chrono::milliseconds(0));\n  ASSERT_EQ(stopwatch.getCycleStart(),\n            std::chrono::steady_clock::time_point());\n  sc.advance(std::chrono::milliseconds(999));\n  ASSERT_EQ(stopwatch.timeIntoCycle(), std::chrono::milliseconds(999));\n  ASSERT_EQ(stopwatch.timeRemainingInCycle(), std::chrono::milliseconds(0));\n  ASSERT_EQ(stopwatch.getCycleStart(),\n            std::chrono::steady_clock::time_point());\n  stopwatch = IntervalStopwatch(std::chrono::milliseconds(3), &sc);\n  ASSERT_EQ(stopwatch.timeIntoCycle(), std::chrono::milliseconds(0));\n  ASSERT_EQ(stopwatch.timeRemainingInCycle(), std::chrono::milliseconds(3));\n  ASSERT_EQ(stopwatch.getCycleStart(),\n            std::chrono::steady_clock::time_point() + std::chrono::milliseconds(999));\n  sc.advance(std::chrono::milliseconds(1));\n  ASSERT_EQ(stopwatch.timeIntoCycle(), std::chrono::milliseconds(1));\n  ASSERT_EQ(stopwatch.timeRemainingInCycle(), std::chrono::milliseconds(2));\n  ASSERT_EQ(stopwatch.getCycleStart(),\n            std::chrono::steady_clock::time_point() + std::chrono::milliseconds(999));\n  stopwatch.startCycle(std::chrono::milliseconds(10));\n  ASSERT_EQ(stopwatch.timeIntoCycle(), std::chrono::milliseconds(0));\n  ASSERT_EQ(stopwatch.timeRemainingInCycle(), std::chrono::milliseconds(10));\n  ASSERT_EQ(stopwatch.getCycleStart(),\n            std::chrono::steady_clock::time_point() + std::chrono::milliseconds(1000));\n  sc.advance(std::chrono::milliseconds(1));\n  ASSERT_EQ(stopwatch.timeIntoCycle(), std::chrono::milliseconds(1));\n  ASSERT_EQ(stopwatch.timeRemainingInCycle(), std::chrono::milliseconds(9));\n  sc.advance(std::chrono::milliseconds(1));\n  ASSERT_EQ(stopwatch.timeIntoCycle(), std::chrono::milliseconds(2));\n  ASSERT_EQ(stopwatch.timeRemainingInCycle(), std::chrono::milliseconds(8));\n  sc.advance(std::chrono::milliseconds(7));\n  ASSERT_EQ(stopwatch.timeIntoCycle(), std::chrono::milliseconds(9));\n  ASSERT_EQ(stopwatch.timeRemainingInCycle(), std::chrono::milliseconds(1));\n  sc.advance(std::chrono::milliseconds(1));\n  ASSERT_EQ(stopwatch.timeIntoCycle(), std::chrono::milliseconds(10));\n  ASSERT_EQ(stopwatch.timeRemainingInCycle(), std::chrono::milliseconds(0));\n  sc.advance(std::chrono::milliseconds(10));\n  ASSERT_EQ(stopwatch.timeIntoCycle(), std::chrono::milliseconds(20));\n  ASSERT_EQ(stopwatch.timeRemainingInCycle(), std::chrono::milliseconds(0));\n}\n\nTEST(IntervalStopwatch, RestartIfExpired)\n{\n  common::SteadyClock sc(true);\n  IntervalStopwatch stopwatch(std::chrono::milliseconds(100), &sc);\n  ASSERT_EQ(stopwatch.timeIntoCycle(), std::chrono::milliseconds(0));\n  ASSERT_FALSE(stopwatch.restartIfExpired());\n  sc.advance(std::chrono::milliseconds(99));\n  ASSERT_EQ(stopwatch.timeIntoCycle(), std::chrono::milliseconds(99));\n  ASSERT_EQ(stopwatch.timeRemainingInCycle(), std::chrono::milliseconds(1));\n  ASSERT_FALSE(stopwatch.restartIfExpired());\n  sc.advance(std::chrono::milliseconds(2));\n  ASSERT_EQ(stopwatch.timeIntoCycle(), std::chrono::milliseconds(101));\n  ASSERT_EQ(stopwatch.timeRemainingInCycle(), std::chrono::milliseconds(0));\n  ASSERT_TRUE(stopwatch.restartIfExpired());\n  ASSERT_EQ(stopwatch.timeIntoCycle(), std::chrono::milliseconds(0));\n  ASSERT_EQ(stopwatch.timeRemainingInCycle(), std::chrono::milliseconds(100));\n  ASSERT_FALSE(stopwatch.restartIfExpired());\n  sc.advance(std::chrono::milliseconds(50));\n  ASSERT_EQ(stopwatch.timeIntoCycle(), std::chrono::milliseconds(50));\n  ASSERT_EQ(stopwatch.timeRemainingInCycle(), std::chrono::milliseconds(50));\n  ASSERT_FALSE(stopwatch.restartIfExpired());\n}\n\nTEST(Timing, gtime)\n{\n  time_t t = 0;\n  ASSERT_STREQ(\"Thu Jan  1 00:00:00 1970\", eos::common::Timing::gtime(t).c_str());\n  t = 1613646201;\n  ASSERT_STREQ(\"Thu Feb 18 11:03:21 2021\", eos::common::Timing::gtime(t).c_str());\n}\n\nEOSCOMMONTESTING_END\n"
  },
  {
    "path": "unit_tests/common/UriCapCipherTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: UriCapCipherTests.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/UriCapCipher.hh\"\n#include \"gtest/gtest.h\"\n\n#include <atomic>\n#include <chrono>\n#include <fstream>\n#include <random>\n#include <string>\n#include <thread>\n#include <unistd.h>\n#include <vector>\n\nTEST(UriCapCipher, EncodeDecodePerf)\n{\n  using eos::common::UriCapCipher;\n  constexpr size_t kPayloadSize = 4096;\n  constexpr int kIters = 10000;\n\n  std::mt19937_64 rng_pw(0xC0FFEEULL);\n  std::uniform_int_distribution<int> dist_pw(0, 255);\n  std::string password;\n  password.resize(32);\n  for (size_t i = 0; i < password.size(); ++i) {\n    password[i] = static_cast<char>(dist_pw(rng_pw));\n  }\n\n  UriCapCipher cipher(UriCapCipher::PasswordTag{},\n                      UriCapCipher::FixedSaltTag{},\n                      password);\n\n  std::mt19937_64 rng(0xBADC0DEULL);\n  std::uniform_int_distribution<int> dist(0, 255);\n  std::string payload;\n  payload.resize(kPayloadSize);\n  for (size_t i = 0; i < payload.size(); ++i) {\n    payload[i] = static_cast<char>(dist(rng));\n  }\n\n  std::vector<std::string> encoded;\n  encoded.reserve(kIters);\n\n  auto t0 = std::chrono::high_resolution_clock::now();\n  for (int i = 0; i < kIters; ++i) {\n    encoded.push_back(cipher.encryptToCgiFields(payload));\n  }\n  auto t1 = std::chrono::high_resolution_clock::now();\n\n  auto enc_sec =\n      std::chrono::duration_cast<std::chrono::duration<double>>(t1 - t0)\n          .count();\n  double enc_khz = (kIters / enc_sec) / 1000.0;\n\n  auto t2 = std::chrono::high_resolution_clock::now();\n  for (int i = 0; i < kIters; ++i) {\n    std::string decoded = cipher.decryptFromCgiFields(encoded[i]);\n    ASSERT_EQ(decoded.size(), payload.size());\n    ASSERT_EQ(decoded, payload);\n  }\n  auto t3 = std::chrono::high_resolution_clock::now();\n\n  auto dec_sec =\n      std::chrono::duration_cast<std::chrono::duration<double>>(t3 - t2)\n          .count();\n  double dec_khz = (kIters / dec_sec) / 1000.0;\n\n  std::cout << \"UriCapCipher encode rate: \" << enc_khz << \" kHz\\n\";\n  std::cout << \"UriCapCipher decode rate: \" << dec_khz << \" kHz\\n\";\n\n}\n\nTEST(UriCapCipher, EncodeDecodeConcurrent)\n{\n  using eos::common::UriCapCipher;\n  constexpr int kThreads = 100;\n  constexpr int kItersPerThread = 100;\n  constexpr size_t kPayloadSize = 4096;\n\n  std::mt19937_64 rng_pw(0xC0FFEEULL);\n  std::uniform_int_distribution<int> dist_pw(0, 255);\n  std::string password;\n  password.resize(32);\n  for (size_t i = 0; i < password.size(); ++i) {\n    password[i] = static_cast<char>(dist_pw(rng_pw));\n  }\n\n  UriCapCipher cipher(UriCapCipher::PasswordTag{},\n                      UriCapCipher::FixedSaltTag{},\n                      password);\n\n  std::vector<std::string> payloads(kThreads);\n  std::vector<std::vector<std::string>> encoded(kThreads);\n  std::atomic<int> failures{0};\n  std::vector<std::thread> threads;\n  threads.reserve(kThreads);\n\n  auto t0 = std::chrono::high_resolution_clock::now();\n  for (int t = 0; t < kThreads; ++t) {\n    threads.emplace_back([t, &cipher, &failures, &payloads, &encoded]() {\n      std::mt19937_64 rng(0xBADC0DEULL + static_cast<uint64_t>(t));\n      std::uniform_int_distribution<int> dist(0, 255);\n      std::string payload;\n      payload.resize(kPayloadSize);\n      for (size_t i = 0; i < payload.size(); ++i) {\n        payload[i] = static_cast<char>(dist(rng));\n      }\n\n      payloads[t] = std::move(payload);\n      encoded[t].reserve(kItersPerThread);\n      for (int i = 0; i < kItersPerThread; ++i) {\n        encoded[t].push_back(cipher.encryptToCgiFields(payloads[t]));\n      }\n    });\n  }\n\n  for (auto& th : threads) {\n    th.join();\n  }\n  auto t1 = std::chrono::high_resolution_clock::now();\n\n  threads.clear();\n  threads.reserve(kThreads);\n  auto t2 = std::chrono::high_resolution_clock::now();\n  for (int t = 0; t < kThreads; ++t) {\n    threads.emplace_back([t, &cipher, &failures, &payloads, &encoded]() {\n      for (int i = 0; i < kItersPerThread; ++i) {\n        std::string dec = cipher.decryptFromCgiFields(encoded[t][i]);\n        if (dec != payloads[t]) {\n          failures.fetch_add(1, std::memory_order_relaxed);\n        }\n      }\n    });\n  }\n\n  for (auto& th : threads) {\n    th.join();\n  }\n  auto t3 = std::chrono::high_resolution_clock::now();\n\n  const double enc_sec =\n      std::chrono::duration_cast<std::chrono::duration<double>>(t1 - t0)\n          .count();\n  const double dec_sec =\n      std::chrono::duration_cast<std::chrono::duration<double>>(t3 - t2)\n          .count();\n  const double total_ops = static_cast<double>(kThreads) *\n                           static_cast<double>(kItersPerThread);\n  const double enc_khz = (total_ops / enc_sec) / 1000.0;\n  const double dec_khz = (total_ops / dec_sec) / 1000.0;\n\n  std::cout << \"UriCapCipher concurrent encode rate: \" << enc_khz << \" kHz\\n\";\n  std::cout << \"UriCapCipher concurrent decode rate: \" << dec_khz << \" kHz\\n\";\n\n  EXPECT_EQ(failures.load(), 0);\n}\n"
  },
  {
    "path": "unit_tests/common/UtilsTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: UtilsTests.hh\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"unit_tests/common/Namespace.hh\"\n#include \"common/Utils.hh\"\n#include \"gtest/gtest.h\"\n#include <fstream>\n\nEOSCOMMONTESTING_BEGIN\n\nTEST(ParseUtils, SanitizeGeoTag)\n{\n  std::string geotag, sanitized;\n  sanitized = eos::common::SanitizeGeoTag(geotag);\n  ASSERT_TRUE(sanitized != geotag);\n  geotag = \"a:b\";\n  sanitized = eos::common::SanitizeGeoTag(geotag);\n  ASSERT_TRUE(sanitized != geotag);\n  geotag = \"a::b\";\n  sanitized = eos::common::SanitizeGeoTag(geotag);\n  ASSERT_TRUE(geotag == sanitized);\n  geotag = \"a::b::c::d::e::f::\";\n  sanitized = eos::common::SanitizeGeoTag(geotag);\n  ASSERT_TRUE(sanitized != geotag);\n  geotag = \"abcd::efgh::ijkl\";\n  sanitized = eos::common::SanitizeGeoTag(geotag);\n  ASSERT_TRUE(geotag == sanitized);\n  geotag = \"abcd::ef::::gh::ijk\";\n  sanitized = eos::common::SanitizeGeoTag(geotag);\n  ASSERT_TRUE(sanitized != geotag);\n  geotag = \"abcd::ef::spa ce::gh::ijk\";\n  sanitized = eos::common::SanitizeGeoTag(geotag);\n  ASSERT_TRUE(sanitized != geotag);\n  geotag = \"abcd::ef_gh::ijk\";\n  sanitized = eos::common::SanitizeGeoTag(geotag);\n  ASSERT_TRUE(sanitized != geotag);\n  geotag = \"abcd::ef::123456789::gh\";\n  sanitized = eos::common::SanitizeGeoTag(geotag);\n  ASSERT_TRUE(sanitized != geotag);\n  geotag = \"::\";\n  sanitized = eos::common::SanitizeGeoTag(geotag);\n  ASSERT_TRUE(sanitized != geotag);\n}\n\nTEST(ParseUtils, GetFileAdlerXs)\n{\n  // Create temporary file with some contents\n  std::string fn_pattern = \"/tmp/eos.unittest.XXXXXX\";\n  const std::string fn = MakeTemporaryFile(fn_pattern);\n  ASSERT_TRUE(!fn.empty());\n  std::ofstream file(fn);\n  const std::string data = \"Just some random input to compute adler checksum\";\n  file << data;\n  file.close();\n  std::string adler_xs;\n  ASSERT_TRUE(eos::common::GetFileAdlerXs(adler_xs, fn));\n  ASSERT_STREQ(\"b8601227\", adler_xs.c_str());\n  (void) unlink(fn.c_str());\n}\n\nTEST(ParseUtils, GetFileHexSha1)\n{\n  // Create temporary file with some contents\n  std::string fn_pattern = \"/tmp/eos.unittest.XXXXXX\";\n  const std::string fn = MakeTemporaryFile(fn_pattern);\n  ASSERT_TRUE(!fn.empty());\n  std::ofstream file(fn);\n  const std::string data = \"Just some random input to compute adler checksum\";\n  file << data;\n  file.close();\n  std::string hex_sha1;\n  ASSERT_TRUE(eos::common::GetFileHexSha1(hex_sha1, fn));\n  ASSERT_STREQ(\"5213647b3c1386dd91b768809aeb9dea7b2f9c28\", hex_sha1.c_str());\n  (void) unlink(fn.c_str());\n}\n\nTEST(ParseUtils, ComputeSize) {\n  uint64_t size = 0;\n  ComputeSize(size,0);\n  ASSERT_EQ(0,size);\n  ComputeSize(size,5);\n  ASSERT_EQ(5,size);\n  ComputeSize(size,-10);\n  ASSERT_EQ(0,size);\n}\n\nTEST(ParseUtils, AddEosApp) {\n  std::string origPath = \"/eos/test/file\";\n  std::string path = origPath;\n  AddEosApp(path,\"http\");\n  ASSERT_EQ(origPath + \"?eos.app=http\",path);\n\n  path = origPath + \"?test1=test2\";\n  AddEosApp(path,\"srm\");\n  ASSERT_EQ(origPath + \"?test1=test2&eos.app=srm\",path);\n\n  path = origPath + \"?test1=test2&eos.app=hello&test2=test3&eos.app=world\";\n  AddEosApp(path,\"srm\");\n  ASSERT_EQ(origPath + \"?test1=test2&eos.app=hello&test2=test3&eos.app=srm/world\",path);\n\n  path = origPath + \"?eos.app=http\";\n  AddEosApp(path,\"http\");\n  ASSERT_EQ(origPath + \"?eos.app=http\",path);\n\n  path = origPath + \"?eos.app=http/test\";\n  AddEosApp(path,\"http\");\n  ASSERT_EQ(origPath + \"?eos.app=http/test\",path);\n\n  path = origPath + \"?eos.app=http/\";\n  AddEosApp(path,\"http\");\n  ASSERT_EQ(origPath + \"?eos.app=http\",path);\n\n  path = origPath + \"?\";\n  AddEosApp(path,\"xrootd\");\n  ASSERT_EQ(origPath + \"?eos.app=xrootd\",path);\n\n  path = origPath + \"?eos.app=test\";\n  AddEosApp(path,\"xrootd\");\n  ASSERT_EQ(origPath + \"?eos.app=xrootd/test\",path);\n\n  path = origPath + \"?eos.app=verylongapplicationname\";\n  AddEosApp(path,\"xrootd\");\n  ASSERT_EQ(origPath + \"?eos.app=xrootd/verylongapplicationname\",path);\n\n  path = origPath + \"?eos.app=test&test1=test2\";\n  AddEosApp(path,\"xrootd\");\n  ASSERT_EQ(origPath + \"?eos.app=xrootd/test&test1=test2\",path);\n\n  path = origPath + \"?test1=test2&eos.app=test\";\n  AddEosApp(path,\"xrootd\");\n  ASSERT_EQ(origPath + \"?test1=test2&eos.app=xrootd/test\",path);\n\n  path = origPath + \"?test1=test2&eos.app=verylongapplicationname\";\n  AddEosApp(path,\"xrootd\");\n  ASSERT_EQ(origPath + \"?test1=test2&eos.app=xrootd/verylongapplicationname\",path);\n\n  path = origPath + \"?test1=test2&eos.app=verylongapplicationname&test3=test4\";\n  AddEosApp(path,\"xrootd\");\n  ASSERT_EQ(origPath + \"?test1=test2&eos.app=xrootd/verylongapplicationname&test3=test4\",path);\n\n  path = origPath + \"?test1=test2&eos.app=xrootd/&test3=test4\";\n  AddEosApp(path,\"xrootd\");\n  ASSERT_EQ(origPath + \"?test1=test2&eos.app=xrootd&test3=test4\",path);\n\n  path = origPath + \"?test1=test2&eos.app=https&test3=test4\";\n  AddEosApp(path,\"http\");\n  ASSERT_EQ(origPath + \"?test1=test2&eos.app=http/https&test3=test4\",path);\n\n  std::string opaque = \"\";\n  AddEosApp(opaque,\"xrootd\");\n  ASSERT_EQ(\"eos.app=xrootd\",opaque);\n\n  std::string origOpaque = \"test=1&test=2\";\n  opaque = origOpaque;\n  AddEosApp(opaque,\"http\");\n  ASSERT_EQ(origOpaque + \"&eos.app=http\",opaque);\n\n  origOpaque = \"&test=1&test=2\";\n  opaque = origOpaque;\n  AddEosApp(opaque,\"http\");\n  ASSERT_EQ(origOpaque + \"&eos.app=http\",opaque);\n\n  origOpaque = \"?test=1&test=2\";\n  opaque = origOpaque;\n  AddEosApp(opaque,\"http\");\n  ASSERT_EQ(origOpaque + \"&eos.app=http\",opaque);\n}\n\nEOSCOMMONTESTING_END\n"
  },
  {
    "path": "unit_tests/common/VariousTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: VariousTests.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/Statfs.hh\"\n#include \"common/FileSystem.hh\"\n#include \"common/Locators.hh\"\n#include \"common/InstanceName.hh\"\n#include \"common/LayoutId.hh\"\n#include \"Namespace.hh\"\n#include \"gtest/gtest.h\"\n#include <list>\n\nEOSCOMMONTESTING_BEGIN\n\nTEST(StatFs, BasicSanity)\n{\n  std::unique_ptr<eos::common::Statfs> statfs =\n    eos::common::Statfs::DoStatfs(\"/\");\n  ASSERT_NE(statfs, nullptr);\n  statfs = eos::common::Statfs::DoStatfs(\"aaaaaaaa\");\n  ASSERT_EQ(statfs, nullptr);\n}\n\nTEST(FileSystemLocator, BasicSanity)\n{\n  FileSystemLocator locator;\n  ASSERT_TRUE(\n    FileSystemLocator::fromQueuePath(\"/eos/somehost.cern.ch:1095/fst/data05\",\n                                     locator));\n  ASSERT_EQ(locator.getHost(), \"somehost.cern.ch\");\n  ASSERT_EQ(locator.getPort(), 1095);\n  ASSERT_EQ(locator.getStoragePath(), \"/data05\");\n  ASSERT_EQ(locator.getStorageType(), FileSystemLocator::StorageType::Local);\n  ASSERT_TRUE(locator.isLocal());\n  ASSERT_EQ(locator.getHostPort(), \"somehost.cern.ch:1095\");\n  ASSERT_EQ(locator.getQueuePath(), \"/eos/somehost.cern.ch:1095/fst/data05\");\n  ASSERT_EQ(locator.getFSTQueue(), \"/eos/somehost.cern.ch:1095/fst\");\n  SharedHashLocator hashLocator(locator, true);\n  ASSERT_EQ(hashLocator.getQDBKey(),\n            \"eos-hash||fs||somehost.cern.ch:1095||/data05\");\n}\n\nTEST(FileSystemLocator, ParseStorageType)\n{\n  ASSERT_EQ(FileSystemLocator::parseStorageType(\"/data\"),\n            FileSystemLocator::StorageType::Local);\n  ASSERT_EQ(\n    FileSystemLocator::parseStorageType(\"root://root.example.cern.ch:1094//\"),\n    FileSystemLocator::StorageType::Xrd);\n  ASSERT_EQ(FileSystemLocator::parseStorageType(\"s3://s3.example.cern.ch//\"),\n            FileSystemLocator::StorageType::S3);\n  ASSERT_EQ(FileSystemLocator::parseStorageType(\"dav://webdav.example.cern.ch/\"),\n            FileSystemLocator::StorageType::WebDav);\n  ASSERT_EQ(FileSystemLocator::parseStorageType(\"http://web.example.cern.ch/\"),\n            FileSystemLocator::StorageType::HTTP);\n  ASSERT_EQ(FileSystemLocator::parseStorageType(\"https://webs.example.cern.ch/\"),\n            FileSystemLocator::StorageType::HTTPS);\n  ASSERT_EQ(FileSystemLocator::parseStorageType(\"root:/invalid.example\"),\n            FileSystemLocator::StorageType::Unknown);\n  ASSERT_EQ(FileSystemLocator::parseStorageType(\"local/path\"),\n            FileSystemLocator::StorageType::Unknown);\n}\n\nTEST(FileSystemLocator, ParsingFailure)\n{\n  FileSystemLocator locator;\n  ASSERT_FALSE(\n    FileSystemLocator::fromQueuePath(\"/fst/somehost.cern.ch:1095/fst/data05\",\n                                     locator));\n  ASSERT_FALSE(\n    FileSystemLocator::fromQueuePath(\"/eos/somehost.cern.ch:1095/mgm/data07\",\n                                     locator));\n  ASSERT_FALSE(\n    FileSystemLocator::fromQueuePath(\"/eos/somehost.cern.ch:1095/mgm/data07\",\n                                     locator));\n  ASSERT_FALSE(\n    FileSystemLocator::fromQueuePath(\"/eos/somehost.cern.ch/fst/data05\", locator));\n  ASSERT_FALSE(FileSystemLocator::fromQueuePath(\"/eos/fst:999/data05\", locator));\n  ASSERT_FALSE(FileSystemLocator::fromQueuePath(\"/eos/somehost.cern.ch:1096/fst/\",\n               locator));\n}\n\nTEST(FileSystemLocator, RemoteFileSystem)\n{\n  FileSystemLocator locator;\n  ASSERT_TRUE(\n    FileSystemLocator::fromQueuePath(\"/eos/example-host.cern.ch:1095/fsthttps://remote.example.cern.ch/path/\",\n                                     locator));\n  ASSERT_EQ(locator.getHost(), \"example-host.cern.ch\");\n  ASSERT_EQ(locator.getPort(), 1095);\n  ASSERT_EQ(locator.getStoragePath(), \"https://remote.example.cern.ch/path/\");\n  ASSERT_EQ(locator.getStorageType(), FileSystemLocator::StorageType::HTTPS);\n  ASSERT_FALSE(locator.isLocal());\n  ASSERT_EQ(locator.getHostPort(), \"example-host.cern.ch:1095\");\n  ASSERT_EQ(locator.getQueuePath(),\n            \"/eos/example-host.cern.ch:1095/fsthttps://remote.example.cern.ch/path/\");\n  ASSERT_EQ(locator.getFSTQueue(), \"/eos/example-host.cern.ch:1095/fst\");\n  SharedHashLocator hashLocator(locator, true);\n  ASSERT_EQ(hashLocator.getQDBKey(),\n            \"eos-hash||fs||example-host.cern.ch:1095||https://remote.example.cern.ch/path/\");\n}\n\nTEST(GroupLocator, BasicSanity)\n{\n  GroupLocator locator;\n  ASSERT_TRUE(GroupLocator::parseGroup(\"default.1337\", locator));\n  ASSERT_EQ(locator.getSpace(), \"default\");\n  ASSERT_EQ(locator.getIndex(), 1337);\n  ASSERT_TRUE(GroupLocator::parseGroup(\"spare\", locator));\n  ASSERT_EQ(locator.getSpace(), \"spare\");\n  ASSERT_EQ(locator.getGroup(), \"spare\");\n  ASSERT_EQ(locator.getIndex(), 0);\n  ASSERT_FALSE(GroupLocator::parseGroup(\"aaa.bbb\", locator));\n  ASSERT_EQ(locator.getSpace(), \"aaa\");\n  ASSERT_EQ(locator.getGroup(), \"aaa.bbb\");\n  ASSERT_EQ(locator.getIndex(), 0);\n  ASSERT_TRUE(GroupLocator::parseGroup(\"default.0\", locator));\n  ASSERT_EQ(locator.getSpace(), \"default\");\n  ASSERT_EQ(locator.getGroup(), \"default.0\");\n  ASSERT_EQ(locator.getIndex(), 0);\n  ASSERT_FALSE(GroupLocator::parseGroup(\"onlyspace\", locator));\n  ASSERT_EQ(locator.getSpace(), \"onlyspace\");\n  ASSERT_EQ(locator.getGroup(), \"onlyspace\");\n  ASSERT_EQ(locator.getIndex(), 0);\n  ASSERT_FALSE(GroupLocator::parseGroup(\"\", locator));\n  ASSERT_EQ(locator.getSpace(), \"\");\n  ASSERT_EQ(locator.getGroup(), \"\");\n  ASSERT_EQ(locator.getIndex(), 0);\n}\n\nTEST(FstLocator, BasicSanity)\n{\n  FstLocator locator(\"example.com\", 999);\n  ASSERT_EQ(locator.getHost(), \"example.com\");\n  ASSERT_EQ(locator.getPort(), 999);\n  ASSERT_EQ(locator.getHostPort(), \"example.com:999\");\n  ASSERT_EQ(locator.getQueuePath(), \"/eos/example.com:999/fst\");\n}\n\nTEST(FstLocator, FromQueuePath)\n{\n  FstLocator locator;\n  ASSERT_TRUE(FstLocator::fromQueuePath(\"/eos/example.com:1111/fst\", locator));\n  ASSERT_EQ(locator.getHost(), \"example.com\");\n  ASSERT_EQ(locator.getPort(), 1111);\n  ASSERT_EQ(locator.getHostPort(), \"example.com:1111\");\n  ASSERT_EQ(locator.getQueuePath(), \"/eos/example.com:1111/fst\");\n}\n\nTEST(SharedHashLocator, BasicSanity)\n{\n  SharedHashLocator locator(\"eosdev\", SharedHashLocator::Type::kSpace, \"default\");\n  ASSERT_FALSE(locator.empty());\n  ASSERT_EQ(locator.getConfigQueue(), \"/config/eosdev/space/default\");\n  ASSERT_EQ(locator.getBroadcastQueue(), \"/eos/*/mgm\");\n  ASSERT_EQ(locator.getQDBKey(), \"eos-hash||space||default\");\n  locator = SharedHashLocator(\"eosdev\", SharedHashLocator::Type::kGroup,\n                              \"default.0\");\n  ASSERT_FALSE(locator.empty());\n  ASSERT_EQ(locator.getConfigQueue(), \"/config/eosdev/group/default.0\");\n  ASSERT_EQ(locator.getBroadcastQueue(), \"/eos/*/mgm\");\n  ASSERT_EQ(locator.getQDBKey(), \"eos-hash||group||default.0\");\n  locator = SharedHashLocator(\"eosdev\", SharedHashLocator::Type::kNode,\n                              \"/eos/example.com:3003/fst\");\n  ASSERT_FALSE(locator.empty());\n  ASSERT_EQ(locator.getConfigQueue(), \"/config/eosdev/node/example.com:3003\");\n  ASSERT_EQ(locator.getBroadcastQueue(), \"/eos/example.com:3003/fst\");\n  ASSERT_EQ(locator.getQDBKey(), \"eos-hash||node||example.com:3003\");\n}\n\nTEST(SharedHashLocator, NodeWithHostport)\n{\n  SharedHashLocator locator(\"eosdev\", SharedHashLocator::Type::kNode,\n                            \"example.com:3003\");\n  ASSERT_FALSE(locator.empty());\n  ASSERT_EQ(locator.getConfigQueue(), \"/config/eosdev/node/example.com:3003\");\n  ASSERT_EQ(locator.getBroadcastQueue(), \"/eos/example.com:3003/fst\");\n  ASSERT_EQ(locator.getQDBKey(), \"eos-hash||node||example.com:3003\");\n}\n\nTEST(InstanceName, BasicSanity)\n{\n  ASSERT_TRUE(common::InstanceName::empty());\n  common::InstanceName::set(\"eosdev\");\n  ASSERT_FALSE(common::InstanceName::empty());\n  ASSERT_EQ(common::InstanceName::get(), \"eosdev\");\n  common::InstanceName::clear();\n  ASSERT_TRUE(common::InstanceName::empty());\n}\n\nTEST(SharedHashLocator, AutoInstanceName)\n{\n  common::InstanceName::set(\"eosdev\");\n  SharedHashLocator locator(SharedHashLocator::Type::kSpace, \"default\");\n  ASSERT_FALSE(locator.empty());\n  ASSERT_EQ(locator.getConfigQueue(), \"/config/eosdev/space/default\");\n  ASSERT_EQ(locator.getBroadcastQueue(), \"/eos/*/mgm\");\n  ASSERT_EQ(locator.getQDBKey(), \"eos-hash||space||default\");\n  common::InstanceName::clear();\n}\n\nTEST(SharedHashLocator, GlobalMgmHash)\n{\n  SharedHashLocator locator(\"eostest\", SharedHashLocator::Type::kGlobalConfigHash,\n                            \"\");\n  ASSERT_FALSE(locator.empty());\n  ASSERT_EQ(locator.getConfigQueue(), \"/config/eostest/mgm/\");\n  ASSERT_EQ(locator.getBroadcastQueue(), \"/eos/*/mgm\");\n  ASSERT_EQ(locator.getQDBKey(), \"eos-global-config-hash\");\n}\n\nTEST(SharedHashLocator, ForFilesystem)\n{\n  FileSystemLocator fsLocator;\n  ASSERT_TRUE(\n    FileSystemLocator::fromQueuePath(\"/eos/somehost.cern.ch:1095/fst/data05\",\n                                     fsLocator));\n  SharedHashLocator hashLocator(fsLocator, true);\n  ASSERT_FALSE(hashLocator.empty());\n  ASSERT_EQ(hashLocator.getConfigQueue(),\n            \"/eos/somehost.cern.ch:1095/fst/data05\");\n  ASSERT_EQ(hashLocator.getBroadcastQueue(), \"/eos/*/mgm\");\n  hashLocator = SharedHashLocator(fsLocator, false);\n  ASSERT_FALSE(hashLocator.empty());\n  ASSERT_EQ(hashLocator.getConfigQueue(),\n            \"/eos/somehost.cern.ch:1095/fst/data05\");\n  ASSERT_EQ(hashLocator.getBroadcastQueue(), \"/eos/somehost.cern.ch:1095/fst\");\n  ASSERT_EQ(hashLocator.getQDBKey(),\n            \"eos-hash||fs||somehost.cern.ch:1095||/data05\");\n}\n\nTEST(SharedHashLocator, Initialization)\n{\n  SharedHashLocator locator;\n  ASSERT_TRUE(locator.empty());\n}\n\nTEST(SharedHashLocator, Parsing)\n{\n  SharedHashLocator locator;\n  ASSERT_TRUE(SharedHashLocator::fromConfigQueue(\"/config/eosdev/space/default\",\n              locator));\n  ASSERT_EQ(locator.getConfigQueue(), \"/config/eosdev/space/default\");\n  ASSERT_EQ(locator.getBroadcastQueue(), \"/eos/*/mgm\");\n  ASSERT_EQ(locator.getQDBKey(), \"eos-hash||space||default\");\n  ASSERT_FALSE(\n    SharedHashLocator::fromConfigQueue(\"/config/eosdev/space/default/aa\", locator));\n  ASSERT_FALSE(SharedHashLocator::fromConfigQueue(\"/config/eosdev/space\",\n               locator));\n  ASSERT_TRUE(SharedHashLocator::fromConfigQueue(\"/config/eosdev/group/default.0\",\n              locator));\n  ASSERT_EQ(locator.getConfigQueue(), \"/config/eosdev/group/default.0\");\n  ASSERT_EQ(locator.getBroadcastQueue(), \"/eos/*/mgm\");\n  ASSERT_EQ(locator.getQDBKey(), \"eos-hash||group||default.0\");\n  ASSERT_TRUE(\n    SharedHashLocator::fromConfigQueue(\"/config/eosdev/node/example.com:3003\",\n                                       locator));\n  ASSERT_EQ(locator.getConfigQueue(), \"/config/eosdev/node/example.com:3003\");\n  ASSERT_EQ(locator.getBroadcastQueue(), \"/eos/example.com:3003/fst\");\n  ASSERT_EQ(locator.getQDBKey(), \"eos-hash||node||example.com:3003\");\n  ASSERT_TRUE(SharedHashLocator::fromConfigQueue(\"/config/eosdev/mgm/\", locator));\n  ASSERT_EQ(locator.getConfigQueue(), \"/config/eosdev/mgm/\");\n  ASSERT_EQ(locator.getBroadcastQueue(), \"/eos/*/mgm\");\n  ASSERT_EQ(locator.getQDBKey(), \"eos-global-config-hash\");\n}\n\nTEST(LayoutId, RainStripeSize)\n{\n  using eos::common::LayoutId;\n  // std::map<expected_stripe_size, std::pair<layout_id, file_size>\n  std::map<uint64_t, std::pair<std::string, uint32_t>> map_tests {\n    // 0KB file size RAIDDP with 1MB block size\n    {4 * KB, {\"0x20640532\", 0}},\n    // 4KB file size RAIDDP with 1MB block size\n    {4 * MB + 4 * KB , {\"0x20640532\", 4 * KB}},\n    // 10MB file size RAIDDP with 1MB block size\n    {4 * MB + 4 * KB, {\"0x20640532\", 10 * MB}},\n    // 17MB file size RAIDDP with 1MB block size\n    {8 * MB + 4 * KB, {\"0x20640532\", 17 * MB}},\n    // 17MB file size RAIDDP with 1MB block size\n    {40 * MB + 4 * KB, {\"0x20640532\", 151 * MB}},\n    // 1 bytes file size RAIN 4+2 with 1MB block size\n    {1 * MB + 4 * KB, {\"0x20640542\", 1}},\n    // 7MB + 5 file size RAIN 4+2 with 1MB block size\n    {2 * MB + 4 * KB, {\"0x20640542\", 7 * MB + 5}},\n    // 33MB + 8KB file size RAIN 4+2 with 1MB block size\n    {9 * MB + 4 * KB, {\"0x20640542\", 33 * MB + 8 * KB}},\n    // 33MB + 8KB file size RAIN 4+2 with 1MB block size\n    {69 * MB + 4 * KB, {\"0x20640542\", 273 * MB}}\n  };\n\n  for (const auto& test : map_tests) {\n    ASSERT_EQ(test.first,\n              LayoutId::ExpectedStripeSize(std::stoul(test.second.first, nullptr, 16),\n                                           test.second.second));\n  }\n}\n\nEOSCOMMONTESTING_END\n"
  },
  {
    "path": "unit_tests/common/WebNotifyTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: WebNotifyTests.cc\n// Author: Andreas-Joachim Peters - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"Namespace.hh\"\n#include \"common/WebNotify.hh\"\n#include \"gtest/gtest.h\"\n#include <iostream>\n#include <string>\n#include <vector>\n#include <thread>\n#include <mutex>\n#include <condition_variable>\n#include <atomic>\n#include <cstring>\n#include <chrono>\n#include <unistd.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n\nclass SimpleTCPServer\n{\npublic:\n  explicit SimpleTCPServer(int port) : port_(port), ready_(false),\n    running_(false)\n  {\n    Start();\n    std::this_thread::sleep_for(std::chrono::seconds(2));\n  }\n\n  ~SimpleTCPServer()\n  {\n    Shutdown();\n\n    if (server_thread_.joinable()) {\n      server_thread_.join();  // Avoid std::terminate()\n    }\n  }\n\n  void Start()\n  {\n    if (running_) {\n      return;  // Don't start twice\n    }\n\n    running_ = true;\n    server_thread_ = std::thread(&SimpleTCPServer::RunServer, this);\n  }\n\n  std::vector<char> GetMessage()\n  {\n    std::unique_lock<std::mutex> lock(msg_mutex_);\n    cv_.wait(lock, [&ready = ready_] { return ready.load(); });\n    return message_;\n  }\n\nprivate:\n  void RunServer()\n  {\n    int server_fd, client_fd;\n    struct sockaddr_in address {};\n    socklen_t addrlen = sizeof(address);\n    const int buffer_size = 4096;\n    char buffer[buffer_size];\n    server_fd = socket(AF_INET, SOCK_STREAM, 0);\n\n    if (server_fd < 0) {\n      perror(\"Socket failed\");\n      return;\n    }\n\n    server_fd_ = server_fd;  // Save for shutdown\n    int opt = 1;\n    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));\n    address.sin_family = AF_INET;\n    address.sin_addr.s_addr = INADDR_ANY;\n    address.sin_port = htons(port_);\n\n    if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {\n      perror(\"Bind failed\");\n      close(server_fd);\n      return;\n    }\n\n    if (listen(server_fd, 1) < 0) {\n      perror(\"Listen failed\");\n      close(server_fd);\n      return;\n    }\n\n    std::cout << \"Server listening on port \" << port_ << \"\\n\";\n    client_fd = accept(server_fd, (struct sockaddr*)&address, &addrlen);\n\n    if (client_fd < 0) {\n      if (running_) {\n        perror(\"Accept failed\");\n      }\n\n      close(server_fd);\n      return;\n    }\n\n    std::vector<char> local_msg;\n    ssize_t bytes_read;\n\n    while ((bytes_read = read(client_fd, buffer, buffer_size)) > 0) {\n      local_msg.insert(local_msg.end(), buffer, buffer + bytes_read);\n      break;\n    }\n\n    {\n      std::lock_guard<std::mutex> lock(msg_mutex_);\n      message_ = std::move(local_msg);\n      ready_ = true;\n    }\n\n    close(client_fd);\n    close(server_fd);\n    running_ = false;\n    cv_.notify_all();\n  }\n\n  void Shutdown()\n  {\n    if (running_) {\n      running_ = false;\n\n      if (server_fd_ != -1) {\n        shutdown(server_fd_, SHUT_RDWR);\n        close(server_fd_);\n        server_fd_ = -1;\n      }\n\n      if (server_thread_.joinable()) {\n        server_thread_.join();\n      }\n    }\n  }\n\n  int port_;\n  std::thread server_thread_;\n  std::vector<char> message_;\n  std::mutex msg_mutex_;\n  std::condition_variable cv_;\n  std::atomic<bool> ready_;\n  std::atomic<bool> running_;\n  int server_fd_ = -1;\n};\n\nstd::string to_visible_string(const std::vector<char>& in)\n{\n  std::string out;\n  char buf[5];\n\n  for (unsigned char c : in)\n    out += (c == '\\n') ? \"\\\\n\" :\n           (c == '\\r') ? \"\\\\r\" :\n           (c == '\\t') ? \"\\\\t\" :\n           (c == '\\\\') ? \"\\\\\\\\\" :\n           (c == '\\\"') ? \"\\\\\\\"\" :\n           std::isprint(c) ? std::string(1, c) :\n           (std::snprintf(buf, sizeof(buf), \"\\\\x%02x\", c), buf);\n\n  return out;\n}\n\n\nEOSCOMMONNAMESPACE_BEGIN\n\nTEST(WebNotifyTimeoutTests, HttpPostNotification_TimesOut)\n{\n  WebNotify notifier;\n  std::string url =\n    \"http://10.255.255.1:12345\"; // Non-routable IP (used for timeout testing)\n  std::string message = \"{\\\"event\\\":\\\"timeout_test\\\"}\";\n  EXPECT_FALSE(notifier.sendHttpPostNotification(url, message, 250));\n}\n\n\nTEST(WebNotifyTimeoutTests, HttpPostNotification_OK)\n{\n  WebNotify notifier;\n  std::string url = \"http://localhost:12345\";\n  std::string message = \"{\\\"event\\\":\\\"ok_test\\\"}\";\n  SimpleTCPServer server(12345);\n  EXPECT_FALSE(notifier.sendHttpPostNotification(url, message, 250));\n  auto response_message =\n    server.GetMessage();  // Blocks until message is received\n  std::cerr << to_visible_string(response_message) << std::endl;\n  EXPECT_EQ(\"POST / HTTP/1.1\\r\\nHost: localhost:12345\\r\\nAccept: */*\\r\\nContent-Type: application/json\\r\\nContent-Length: 19\\r\\n\\r\\n{\\\"event\\\":\\\"ok_test\\\"}\",\n            std::string(response_message.begin(), response_message.end()));\n}\n\nTEST(WebNotifyTimeoutTests, GrpcNotification_TimesOut)\n{\n  WebNotify notifier;\n  std::string target = \"10.255.255.1:50051\"; // Unreachable IP\n  std::string message = \"gRPC timeout check\";\n  EXPECT_FALSE(notifier.sendGrpcNotification(target, message, 250));\n}\n\nTEST(WebNotifyTimeoutTests, GrpcNotification_Ok)\n{\n  WebNotify notifier;\n  std::string target = \"localhost:12345\";\n  std::string message = \"gRPC timeout check\";\n  SimpleTCPServer server(12345);\n  EXPECT_FALSE(notifier.sendGrpcNotification(target, message, 250));\n}\n\nTEST(WebNotifyTimeoutTests, QClientNotification_TimesOut)\n{\n  WebNotify notifier;\n  std::string target = \"10.255.255.1\"; // Unreachable IP\n  int port = 50051;\n  std::string message = \"QCLient timeout check\";\n  std::string channel = \"Notification\";\n  EXPECT_FALSE(notifier.sendQClientNotification(target, port, channel, message,\n               250));\n}\n\nTEST(WebNotifyTimeoutTests, QClientNotification_Ok)\n{\n  WebNotify notifier;\n  std::string target = \"localhost\";\n  int port = 12345;\n  std::string message = \"QClient timeout check\";\n  std::string channel = \"Notification\";\n  SimpleTCPServer server(12345);\n  EXPECT_FALSE(notifier.sendQClientNotification(target, port, channel, message,\n               250));\n  auto response_message =\n    server.GetMessage();  // Blocks until message is received\n  std::cerr << to_visible_string(response_message) << std::endl;\n  EXPECT_EQ(\"*2\\r\\n$4\\r\\nPING\\r\\n$33\\r\\nqclient-connection-initialization\\r\\n\",\n            std::string(response_message.begin(), response_message.end()));\n}\n\nTEST(WebNotifyTimeoutTests, ActiveMQNotification_TimesOut)\n{\n  WebNotify notifier;\n  std::string brokerURI = \"tcp://10.255.255.1:61616\"; // Unreachable\n  std::string queue = \"timeout_test\";\n  std::string message = \"ActiveMQ timeout check\";\n  EXPECT_FALSE(notifier.sendActiveMQNotification(brokerURI, queue, message, 250));\n}\n\nTEST(WebNotifyTimeoutTests, ActiveMQNotification_Ok)\n{\n  WebNotify notifier;\n  std::string brokerURI = \"tcp://localhost:12345\";\n  std::string queue = \"timeout_test\";\n  std::string message = \"ActiveMQ timeout check\";\n  SimpleTCPServer server(12345);  // Non-blocking\n  EXPECT_FALSE(notifier.sendActiveMQNotification(brokerURI, queue, message, 250));\n  auto response_message =\n    server.GetMessage();  // Blocks until message is received\n  auto containsActiveMQ = [](const std::vector<char>& response) {\n    const std::string target = \"ActiveMQ\";\n    std::string sresponse(response.begin(), response.end());\n    return (sresponse.find(target) != std::string::npos);\n  };\n  EXPECT_TRUE(containsActiveMQ(response_message));\n}\n\n\nEOSCOMMONNAMESPACE_END\n"
  },
  {
    "path": "unit_tests/common/XrdConnPoolTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: XrdConnPoolTests.cc\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"Namespace.hh\"\n#include \"common/XrdConnPool.hh\"\n#include <list>\n\nEOSCOMMONTESTING_BEGIN\n\nTEST(XrdConnPool, DefaultDisabled)\n{\n  XrdCl::URL url;\n  std::string surl = \"root://eospps.cern.ch:1094//path/test.dat\";\n  url.FromString(surl);\n  eos::common::XrdConnPool pool;\n  ASSERT_EQ(pool.AssignConnection(url), 0);\n  ASSERT_EQ(url.GetURL(), surl);\n  // No id should be allocated and the url should stay the same\n  eos::common::XrdConnIdHelper id_helper(pool, url);\n  ASSERT_EQ(id_helper.GetId(), 0);\n  ASSERT_FALSE(id_helper.HasNewConnection());\n  ASSERT_EQ(url.GetURL(), surl);\n}\n\nTEST(XrdConnPool, EvenDistribuion)\n{\n  XrdCl::URL url(\"root://eospps.cern.ch:1094/path/test.dat\");\n  uint32_t max_size = 10;\n  eos::common::XrdConnPool pool(true, max_size);\n  std::ostringstream oss;\n\n  // Add one user per connection id\n  for (uint32_t i = 0; i < max_size; ++i) {\n    ASSERT_EQ(pool.AssignConnection(url), i + 1);\n    oss << \"root://\" << i + 1 << \"@eospps.cern.ch:1094/path/test.dat\";\n    ASSERT_EQ(url.GetURL(), oss.str());\n    oss.str(\"\");\n  }\n\n  // Add two more users per connection id\n  for (int j = 0; j < 2; ++j) {\n    for (uint32_t i = 0; i < max_size; ++i) {\n      ASSERT_EQ(pool.AssignConnection(url), i + 1);\n      oss << \"root://\" << i + 1 << \"@eospps.cern.ch:1094/path/test.dat\";\n      ASSERT_EQ(url.GetURL(), oss.str());\n      oss.str(\"\");\n    }\n  }\n\n  // Free one connection id 5 at a time and allocate a new one\n  oss.str(\"root://5@eospps.cern.ch:1094/path/test.dat\");\n\n  for (int i = 0; i < 3; ++i) {\n    pool.ReleaseConnection(oss.str());\n    ASSERT_EQ(pool.AssignConnection(url), 5);\n  }\n\n  // Free all connection ids 5 in one go\n  for (int i = 0; i < 3; ++i) {\n    pool.ReleaseConnection(oss.str());\n  }\n\n  ASSERT_EQ(pool.AssignConnection(url), 5);\n  ASSERT_EQ(pool.AssignConnection(url), 5);\n  ASSERT_EQ(pool.AssignConnection(url), 5);\n  // Now the pool should allocate from id 1 since they all have 3 clients\n  // per id\n  ASSERT_EQ(pool.AssignConnection(url), 1);\n}\n\nTEST(XrdConnPool, ConnIdHelper)\n{\n  XrdCl::URL url(\"root://eospps.cern.ch:1094/path/test.dat\");\n  uint32_t max_size = 10;\n  eos::common::XrdConnPool pool(true, max_size);\n  std::ostringstream oss;\n\n  // Each gets the same id since it's released at the end of each loop\n  for (uint32_t i = 0; i < max_size; ++i) {\n    XrdConnIdHelper id_helper(pool, url);\n    ASSERT_TRUE(id_helper.HasNewConnection());\n    ASSERT_EQ(id_helper.GetId(), 1);\n  }\n\n  // Allocate them dynamically\n  std::list<XrdConnIdHelper*> lst;\n\n  for (uint32_t i = 0; i < max_size; ++i) {\n    auto elem = new XrdConnIdHelper(pool, url);\n    lst.push_back(elem);\n    ASSERT_EQ(elem->GetId(), i + 1);\n  }\n\n  // std::string out;\n  // pool.Dump(out);\n  // std::cout << out << std::endl;\n\n  // Release the last two ids and try new assignments\n  for (int i = 0; i < 2; ++i) {\n    auto elem = lst.back();\n    delete elem;\n    lst.pop_back();\n  }\n\n  for (int i = 0; i < 2; ++i) {\n    auto elem = new XrdConnIdHelper(pool, url);\n    lst.push_back(elem);\n    ASSERT_EQ(elem->GetId(), max_size - 1 + i);\n  }\n\n  for (auto& elem : lst) {\n    delete elem;\n  }\n\n  lst.clear();\n  // Add one connection it should get the id 1\n  auto elem = new XrdConnIdHelper(pool, url);\n  lst.push_back(elem);\n  ASSERT_EQ(elem->GetId(), 1);\n  delete elem;\n  lst.clear();\n}\n\nEOSCOMMONTESTING_END\n"
  },
  {
    "path": "unit_tests/common/async/ExecutorMgrTests.cc",
    "content": "// /************************************************************************\n//  * EOS - the CERN Disk Storage System                                   *\n//  * Copyright (C) 2022 CERN/Switzerland                           *\n//  *                                                                      *\n//  * This program is free software: you can redistribute it and/or modify *\n//  * it under the terms of the GNU General Public License as published by *\n//  * the Free Software Foundation, either version 3 of the License, or    *\n//  * (at your option) any later version.                                  *\n//  *                                                                      *\n//  * This program is distributed in the hope that it will be useful,      *\n//  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n//  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n//  * GNU General Public License for more details.                         *\n//  *                                                                      *\n//  * You should have received a copy of the GNU General Public License    *\n//  * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n//  ************************************************************************\n//\n\n#include \"common/async/ExecutorMgr.hh\"\n#include \"unit_tests/common/async/FollyExecutorFixture.hh\"\n#include <gtest/gtest.h>\n\n\nTEST(ExecutorMgr, Construction)\n{\n  eos::common::ExecutorMgr mgr(\"std\", 2);\n  ASSERT_TRUE(mgr.IsThreadPool());\n  ASSERT_FALSE(mgr.IsFollyExecutor());\n  eos::common::ExecutorMgr mgr2(\"folly\", 2);\n  ASSERT_FALSE(mgr2.IsThreadPool());\n  ASSERT_TRUE(mgr2.IsFollyExecutor());\n}\n\n\nTEST(ExecutorMgr, ThreadPool)\n{\n  eos::common::ExecutorMgr mgr(\"std\", 3, 3);\n  ASSERT_TRUE(mgr.IsThreadPool());\n  std::vector<eos::common::OpaqueFuture<std::thread::id>> futures;\n\n  for (int i = 0; i < 10; i++) {\n    auto future = mgr.PushTask(\n        [] {\n          std::this_thread::sleep_for(std::chrono::milliseconds(20));\n          return std::this_thread::get_id();\n        }\n    );\n    futures.emplace_back(std::move(future));\n  }\n\n  std::set<std::thread::id> threadIds;\n\n  for (auto && future : futures) {\n    threadIds.insert(future.getValue());\n  }\n\n  // Check if we have exactly 3 different thread ids\n  ASSERT_EQ(3, threadIds.size());\n}\n\nTEST_F(FollyExecutor_F, IOThreadPoolExecutorTests)\n{\n  eos::common::ExecutorMgr mgr(folly_io_executor);\n  ASSERT_TRUE(mgr.IsFollyExecutor());\n  std::vector<eos::common::OpaqueFuture<std::thread::id>> futures;\n\n  for (int i = 0; i < 10; i++) {\n    auto future = mgr.PushTask(\n        [] {\n          std::this_thread::sleep_for(std::chrono::milliseconds(20));\n          return std::this_thread::get_id();\n        }\n    );\n    futures.emplace_back(std::move(future));\n  }\n\n  std::set<std::thread::id> threadIds;\n\n  for (auto && future : futures) {\n    threadIds.insert(future.getValue());\n  }\n\n  // Check if we have exactly 4 different thread ids\n  ASSERT_EQ(kNumThreads, threadIds.size());\n}\n\nTEST_F(FollyExecutor_F, CPUThreadPoolExecutorTests)\n{\n  eos::common::ExecutorMgr mgr(folly_cpu_executor);\n  ASSERT_TRUE(mgr.IsFollyExecutor());\n  std::vector<eos::common::OpaqueFuture<std::thread::id>> futures;\n\n  for (int i = 0; i < 10; i++) {\n    auto future = mgr.PushTask(\n        [] {\n          std::this_thread::sleep_for(std::chrono::milliseconds(20));\n          return std::this_thread::get_id();\n        }\n    );\n    futures.emplace_back(std::move(future));\n  }\n\n  std::set<std::thread::id> threadIds;\n\n  for (auto && future : futures) {\n    threadIds.insert(future.getValue());\n  }\n\n  // Check if we have exactly 4 different thread ids\n  ASSERT_EQ(kNumThreads, threadIds.size());\n}\n\nTEST(ExecutorMgr, ThreadPoolShutdown)\n{\n  eos::common::ExecutorMgr mgr(\"std\",2,4);\n  ASSERT_TRUE(mgr.IsThreadPool());\n  std::atomic<int> counter{0};\n\n  for (int i = 0; i < 100; i++) {\n    mgr.PushTask(\n                 [&counter] {\n                   std::this_thread::sleep_for(std::chrono::milliseconds(20));\n                   counter++;\n                 }\n                 );\n  }\n  // currently the tasks shouldn't complete just yet!\n  ASSERT_GT(100, counter);\n  mgr.Shutdown();\n  ASSERT_EQ(100, counter);\n  std::cout << \"common::ThreadPool executed \" << counter << \" tasks\" << std::endl;\n}\n\nTEST_F(FollyExecutor_F, IOThreadPoolShutdown)\n{\n  eos::common::ExecutorMgr mgr(folly_io_executor);\n  ASSERT_TRUE(mgr.IsFollyExecutor());\n  std::atomic<int> counter{0};\n\n  for (int i = 0; i < 100; i++) {\n    mgr.PushTask(\n                 [&counter] {\n                   std::this_thread::sleep_for(std::chrono::milliseconds(20));\n                   counter++;\n                 }\n                 );\n  }\n  ASSERT_GT(100, counter);\n  mgr.Shutdown();\n  // There is no stopping the IOThreadPoolExecutor!!!\n  ASSERT_EQ(100, counter); // 100 tasks should have been executed\n  std::cout << \"folly::IOThreadPoolExecutor executed \" << counter << \" tasks\" << std::endl;\n}\n\n\nTEST_F(FollyExecutor_F, CPUThreadPoolShutdown)\n{\n  eos::common::ExecutorMgr mgr(folly_cpu_executor);\n  ASSERT_TRUE(mgr.IsFollyExecutor());\n  std::atomic<int> counter{0};\n  for (int i = 0; i < 100; i++) {\n    mgr.PushTask(\n                 [&counter] {\n                   std::this_thread::sleep_for(std::chrono::milliseconds(20));\n                   counter++;\n                 }\n                 );\n  }\n  ASSERT_GT(100, counter);\n  mgr.Shutdown();\n  // CPU ThreadPool supports true cancellation!\n  ASSERT_GT(100, counter);\n  std::cout << \"folly::CPUThreadPoolExecutor executed \" << counter << \" tasks\" << std::endl;\n}\n\n"
  },
  {
    "path": "unit_tests/common/async/FollyExecutorFixture.hh",
    "content": "// ----------------------------------------------------------------------\n// File: FollyExecutorFixture.hh\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#pragma once\n\n#include <folly/executors/IOThreadPoolExecutor.h>\n#include <folly/executors/CPUThreadPoolExecutor.h>\n#include <gtest/gtest.h>\n\nclass FollyExecutor_F : public ::testing::Test\n{\nprotected:\n  void SetUp() override\n  {\n    folly_io_executor = std::make_shared<folly::IOThreadPoolExecutor>(kNumThreads);\n    folly_cpu_executor = std::make_shared<folly::CPUThreadPoolExecutor>(kNumThreads);\n  }\n\n  void TearDown() override\n  {\n    folly_io_executor.reset();\n    folly_cpu_executor.reset();\n  }\n\n  std::shared_ptr<folly::IOThreadPoolExecutor> folly_io_executor;\n  std::shared_ptr<folly::CPUThreadPoolExecutor> folly_cpu_executor;\n  constexpr static size_t kNumThreads = 4;\n};\n"
  },
  {
    "path": "unit_tests/common/async/OpaqueFutureTests.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#include \"common/async/OpaqueFuture.hh\"\n#include \"unit_tests/common/async/FollyExecutorFixture.hh\"\n#include <gtest/gtest.h>\n#include <folly/executors/CPUThreadPoolExecutor.h>\nusing eos::common::OpaqueFuture;\n\nTEST(OpaqueFuture, BasicStdFuture)\n{\n  std::promise<int> p;\n  auto f = p.get_future();\n  eos::common::OpaqueFuture<int> of(std::move(f));\n  ASSERT_TRUE(of.valid());\n  EXPECT_FALSE(of.ready());\n  p.set_value(42);\n  EXPECT_TRUE(of.ready());\n  EXPECT_EQ(of.getValue(), 42);\n}\n\nTEST(OpaqueFuture, VoidStdFuture)\n{\n  std::promise<void> p;\n  auto f = p.get_future();\n  eos::common::OpaqueFuture<void> of(std::move(f));\n  ASSERT_TRUE(of.valid());\n  EXPECT_FALSE(of.ready());\n  p.set_value();\n  EXPECT_TRUE(of.ready());\n  of.getValue();\n}\n\n// We sneak in a folly::Unit as a void future!\nTEST(OpaqueFuture, VoidFollyFuture)\n{\n  folly::Promise<folly::Unit> p;\n  auto f = p.getFuture();\n  static_assert(std::is_same_v<decltype(f),\n                               folly::Future<folly::Unit>>);\n  eos::common::OpaqueFuture<void> of(std::move(f));\n  ASSERT_TRUE(of.valid());\n  EXPECT_FALSE(of.ready());\n  p.setValue();\n  EXPECT_TRUE(of.ready());\n  of.getValue();\n}\n\nTEST(OpaqueFuture, BasicfollyFuture)\n{\n  folly::Promise<int> p;\n  auto f = p.getFuture();\n  static_assert(std::is_same_v<decltype(f), folly::Future<int>>);\n  eos::common::OpaqueFuture<int> of(std::move(f));\n  ASSERT_TRUE(of.valid());\n  EXPECT_FALSE(of.ready());\n  p.setValue(42);\n  EXPECT_TRUE(of.ready());\n  EXPECT_EQ(of.getValue(), 42);\n}\n\nTEST(OpaqueFuture, BasicfollySemiFuture)\n{\n  folly::Promise<int> p;\n  auto f = p.getSemiFuture();\n  static_assert(std::is_same_v<decltype(f), folly::SemiFuture<int>>);\n  eos::common::OpaqueFuture<int> of(std::move(f));\n  ASSERT_TRUE(of.valid());\n  EXPECT_FALSE(of.ready());\n  p.setValue(42);\n  EXPECT_TRUE(of.ready());\n  EXPECT_EQ(of.getValue(), 42);\n}\n\n// Shamelessly borrowed from FutureWrapperTests\nTEST(OpaqueFuture, stdExceptions)\n{\n  std::promise<int> promise;\n  OpaqueFuture<int> fut(promise.get_future());\n  ASSERT_FALSE(fut.ready());\n  promise.set_exception(std::make_exception_ptr(\n                          std::runtime_error(\"something terrible happened\")));\n  ASSERT_TRUE(fut.ready());\n\n  try {\n    fut.getValue();\n    FAIL(); // should never reach here\n  } catch (const std::runtime_error&\n             exc) { // yes, you can use strings as exceptions\n    ASSERT_STREQ(exc.what(), \"something terrible happened\");\n  }\n}\n\nTEST(OpaqueFuture, follyExceptions)\n{\n  folly::Promise<int> promise;\n  OpaqueFuture<int> fut(promise.getFuture());\n  ASSERT_FALSE(fut.ready());\n  promise.setException(folly::exception_wrapper(\n                         std::runtime_error(\"something terrible happened\")));\n  ASSERT_TRUE(fut.ready());\n\n  try {\n    fut.getValue();\n    FAIL(); // should never reach here\n  } catch (const std::runtime_error&\n             exc) { // yes, you can use strings as exceptions\n    ASSERT_STREQ(exc.what(), \"something terrible happened\");\n  }\n}\n\n\nint fib(int n)\n{\n  if (n < 3) {\n    return 1;\n  } else {\n    return fib(n - 1) + fib(n - 2);\n  }\n}\n\nTEST(OpaqueFuture, StdFutureWait)\n{\n  // This executes a std:future asynchronously in a new thread and we wrap the\n  // resulting std::future in our OpaqueFuture. Since this takes a few 100ms\n  // the result shouldn't be immediately seen as ready.\n  OpaqueFuture<int> f(std::async(std::launch::async, []() {\n    return fib(40);\n  }));\n  // This usually takes a few 100 msec.\n  EXPECT_FALSE(f.ready());\n  f.wait();\n  EXPECT_TRUE(f.ready());\n  // GetValue actually does a wait call so the f.ready() calls are redundant, however\n  // this is just to demonstrate wait() functionality that makes a future value \"ready\"\n  EXPECT_EQ(f.getValue(), 102334155);\n}\n\nTEST_F(FollyExecutor_F, follyOpaqueFutureWait)\n{\n  auto f = folly::makeFuture().via(folly_io_executor.get()).then([](auto&&) {\n    return fib(40);\n  });\n  OpaqueFuture<int> of(std::move(f));\n  EXPECT_FALSE(of.ready());\n  of.wait();\n  EXPECT_TRUE(of.ready());\n  EXPECT_EQ(of.getValue(), 102334155);\n}\n"
  },
  {
    "path": "unit_tests/common/concurrency/AlignedAtomicArrayTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: AlignedAtomicArrayTests.cc\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n  * EOS - the CERN Disk Storage System                                   *\n  * Copyright (C) 2023 CERN/Switzerland                           *\n  *                                                                      *\n  * This program is free software: you can redistribute it and/or modify *\n  * it under the terms of the GNU General Public License as published by *\n  * the Free Software Foundation, either version 3 of the License, or    *\n  * (at your option) any later version.                                  *\n  *                                                                      *\n  * This program is distributed in the hope that it will be useful,      *\n  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n  * GNU General Public License for more details.                         *\n  *                                                                      *\n  * You should have received a copy of the GNU General Public License    *\n  * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n  ************************************************************************/\n\n\n#include \"common/concurrency/AlignedArray.hh\"\n#include \"gtest/gtest.h\"\n\nTEST(AlignedAtomicArray, ZeroInitialization)\n{\n  eos::common::AlignedAtomicArray<int64_t, 8> arr_signed{};\n  eos::common::AlignedAtomicArray<int64_t, 8> arr_unsigned{};\n  for (size_t i = 0; i < 8; ++i) {\n    EXPECT_EQ(0, arr_signed[i].load());\n    EXPECT_EQ(0, arr_unsigned[i].load());\n  }\n}\n\nTEST(AlignedAtomicArray, NonZeroInitialization)\n{\n  // This will not work with std::array\n  // eos::common::AlignedAtomicArray<int64_t, 8> arr{{1}}; // only first element is set\n  eos::common::AlignedAtomicArray<int64_t, 8> arr{1,1,1,1,1,1,1,1};\n  for (size_t i = 0; i < 8; ++i) {\n    EXPECT_EQ(1, arr[i].load());\n  }\n}\n"
  },
  {
    "path": "unit_tests/common/concurrency/AtomicUniquePtrTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: AtomicUniquePtrTests.hh\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/concurrency/AtomicUniquePtr.h\"\n#include <thread>\n#include <mutex>\n#include <gtest/gtest.h>\n\nTEST(AtomicUniquePtr, Basic)\n{\n  eos::common::atomic_unique_ptr<int> p(new int(1));\n  EXPECT_EQ(*p.get(), 1);\n  auto old_ptr = p.release();\n  EXPECT_EQ(*old_ptr, 1);\n  EXPECT_EQ(p.get(), nullptr);\n  std::unique_ptr<int> old_ptr_guard(old_ptr);\n}\n\nTEST(AtomicUniquePtr, Reset)\n{\n  eos::common::atomic_unique_ptr<int> p(new int(1));\n  EXPECT_EQ(*p.get(), 1);\n  auto old_ptr = p.reset(new int(2));\n  std::shared_ptr<int> old_ptr_guard(old_ptr);\n  EXPECT_EQ(*p.get(), 2);\n  EXPECT_EQ(*old_ptr_guard.get(), 1);\n}\n\nTEST(AtomicUniquePtr, MoveCtor)\n{\n  eos::common::atomic_unique_ptr<int> p1(new int(1));\n  eos::common::atomic_unique_ptr<int> p2(std::move(p1));\n  EXPECT_EQ(*p2.get(), 1);\n  EXPECT_EQ(p1.get(), nullptr);\n}\n\nTEST(AtomicUniquePtr, reset_from_null)\n{\n  eos::common::atomic_unique_ptr<int> p;\n  EXPECT_EQ(p.get(), nullptr);\n  p.reset_from_null(new int(1));\n  EXPECT_EQ(*p.get(), 1);\n}\n\nTEST(AtomicUniquePtr, MemberAccessOperator)\n{\n  struct A  {\n    std::string data;\n    explicit A(std::string d) : data(d) {}\n  };\n  eos::common::atomic_unique_ptr<A> p(new A(\"hello\"));\n  EXPECT_EQ(p->data, \"hello\");\n}\n\nTEST(AtomicUniquePtr, VectorOfAtomics)\n{\n  std::vector<eos::common::atomic_unique_ptr<int>> v;\n  v.emplace_back(new int(1));\n  v.emplace_back(new int(2));\n  v.emplace_back(new int(3));\n  EXPECT_EQ(*v[0].get(), 1);\n  EXPECT_EQ(*v[1].get(), 2);\n  EXPECT_EQ(*v[2].get(), 3);\n}\n\nTEST(AtomicUniquePtr, SimpleGC)\n{\n  std::vector<eos::common::atomic_unique_ptr<int>> v;\n  eos::common::atomic_unique_ptr p(new int(1));\n  int * p_ = p.reset(new int(2));   // p_ points to 1\n  v.emplace_back(p_);\n  EXPECT_EQ(*p.get(), 2);\n  EXPECT_EQ(*v[0].get(), 1); // v[0] points to 1\n}\n\n\nTEST(AtomicUniquePtr, multireadwrite)\n{\n\n  std::mutex gc_mtx;\n  std::vector<std::unique_ptr<std::string>> old_ptrs;\n  old_ptrs.reserve(20'000'000);\n  eos::common::atomic_unique_ptr<std::string> p(new std::string(\"start\"));\n  auto writer_fn = [&p, &old_ptrs, &gc_mtx]() {\n    auto tid_hash = std::hash<std::thread::id>{}(std::this_thread::get_id());\n    for (int i = 0; i<100'000; ++i) {\n      std::string new_str = \"greetings from thread\" + std::to_string(tid_hash);\n      auto old_ptr = p.reset(new std::string(new_str));\n      std::scoped_lock lock(gc_mtx);\n      old_ptrs.emplace_back(old_ptr);\n    }\n    //std::cout << \"Done with writer=\"<<std::this_thread::get_id() << \"\\n\";\n  };\n\n  auto reader_fn = [&p] {\n    for (int i=0; i<1'000'000; ++i) {\n      auto* q = p.get();\n      ASSERT_TRUE(q);\n    }\n    //std::cout << \"Done with reader=\"<< std::this_thread::get_id() << \"\\n\";\n  };\n\n  std::vector<std::thread> reader_threads;\n  std::vector<std::thread> writer_threads;\n  for (int i=0;i<200;i++) {\n    reader_threads.emplace_back(reader_fn);\n    if (i%10==0) {\n      writer_threads.emplace_back(writer_fn);\n    }\n  }\n\n  for (auto& t: reader_threads) {\n    t.join();\n  }\n\n  for (auto& t: writer_threads) {\n    t.join();\n  }\n\n}\n\nTEST(SharedPtrNonTSSEGV, multireadwrite)\n{\n  GTEST_FLAG_SET(death_test_style, \"threadsafe\");\n  ASSERT_DEATH(\n  {\n    std::shared_ptr<std::string> p(new std::string(\"start\"));\n    auto writer_fn = [&p]() {\n      auto tid_hash = std::hash<std::thread::id>{}(std::this_thread::get_id());\n      for (int i = 0; i < 100'000; ++i) {\n        std::string new_str =\n            \"greetings from thread\" + std::to_string(tid_hash);\n        p.reset(new std::string(new_str));\n      }\n      // std::cout << \"Done with writer=\"<<std::this_thread::get_id() << \"\\n\";\n    };\n\n    auto reader_fn = [&p] {\n      for (int i = 0; i < 1'000'000; ++i) {\n        auto q = p;\n        ASSERT_TRUE(q);\n      }\n      // std::cout << \"Done with reader=\"<< std::this_thread::get_id() << \"\\n\";\n    };\n\n    std::vector<std::thread> reader_threads;\n    std::vector<std::thread> writer_threads;\n    for (int i = 0; i < 200; i++) {\n      reader_threads.emplace_back(reader_fn);\n      if (i % 10 == 0) {\n        writer_threads.emplace_back(writer_fn);\n      }\n    }\n\n    for (auto& t : reader_threads) {\n      t.join();\n    }\n\n    for (auto& t : writer_threads) {\n      t.join();\n    }\n  }\n      , \".*\");\n}\n\nstruct MyDataSP {\n  std::shared_ptr<std::string> get_data() {\n    std::shared_ptr<std::string> data_copy(data);\n    return data_copy;\n  }\n\n  void reset(std::string* new_val) {\n    std::scoped_lock wlock(mtx);\n    data.reset(new_val);\n  }\n\n  MyDataSP(std::string&& val) : data(std::make_shared<std::string>(val)) {}\n\nprivate:\n  std::shared_ptr<std::string> data;\n  std::mutex mtx;\n};\n\n\n\nTEST(SharedPtrNonTS2SEGV, multireadwrite)\n{\n  GTEST_FLAG_SET(death_test_style, \"threadsafe\");\n  ASSERT_DEATH({\n    MyDataSP p(\"start\");\n    auto writer_fn = [&p]() {\n      auto tid_hash = std::hash<std::thread::id>{}(std::this_thread::get_id());\n      for (int i = 0; i < 100'000; ++i) {\n        std::string new_str =\n            \"greetings from thread\" + std::to_string(tid_hash);\n        p.reset(new std::string(new_str));\n      }\n      // std::cout << \"Done with writer=\"<<std::this_thread::get_id() << \"\\n\";\n    };\n\n    auto reader_fn = [&p] {\n      for (int i = 0; i < 1'000'000; ++i)\n        ASSERT_TRUE(p.get_data());\n      // std::cout << \"Done with reader=\"<< std::this_thread::get_id() << \"\\n\";\n    };\n\n    std::vector<std::thread> reader_threads;\n    std::vector<std::thread> writer_threads;\n    for (int i = 0; i < 200; i++) {\n      reader_threads.emplace_back(reader_fn);\n      if (i % 10 == 0) {\n        writer_threads.emplace_back(writer_fn);\n      }\n    }\n\n    for (auto& t : reader_threads) {\n      t.join();\n    }\n\n    for (auto& t : writer_threads) {\n      t.join();\n    }\n               }, \".*\");\n}\n\nstruct MyDataAtomicSP {\n  std::shared_ptr<std::string> get_data() {\n    return std::atomic_load_explicit(&data, std::memory_order_acquire);\n  }\n\n  void reset(std::string* new_val) {\n    std::shared_ptr<std::string> new_data(new_val);\n    std::atomic_store_explicit(&data, new_data, std::memory_order_release);\n  }\n  MyDataAtomicSP(std::string&& val) : data(std::make_shared<std::string>(val)) {}\n\nprivate:\n  std::shared_ptr<std::string> data;\n};\n\nTEST(SharedPtrTS, multireadwrite)\n{\n\n  MyDataAtomicSP p(\"start\");\n  auto writer_fn = [&p]() {\n    auto tid_hash = std::hash<std::thread::id>{}(std::this_thread::get_id());\n    for (int i = 0; i<100'000; ++i) {\n      std::string new_str = \"greetings from thread\" + std::to_string(tid_hash);\n      p.reset(new std::string(new_str));\n    }\n    //std::cout << \"Done with writer=\"<<std::this_thread::get_id() << \"\\n\";\n  };\n\n  auto reader_fn = [&p] {\n    for (int i=0; i<1'000'000; ++i)\n      ASSERT_TRUE(p.get_data());\n    //std::cout << \"Done with reader=\"<< std::this_thread::get_id() << \"\\n\";\n  };\n\n  std::vector<std::thread> reader_threads;\n  std::vector<std::thread> writer_threads;\n  for (int i=0;i<200;i++) {\n    reader_threads.emplace_back(reader_fn);\n    if (i%10==0) {\n      writer_threads.emplace_back(writer_fn);\n    }\n  }\n\n  for (auto& t: reader_threads) {\n    t.join();\n  }\n\n  for (auto& t: writer_threads) {\n    t.join();\n  }\n\n}\n"
  },
  {
    "path": "unit_tests/common/concurrency/RCUTests.cc",
    "content": "// /************************************************************************\n//  * EOS - the CERN Disk Storage System                                   *\n//  * Copyright (C) 2023 CERN/Switzerland                           *\n//  *                                                                      *\n//  * This program is free software: you can redistribute it and/or modify *\n//  * it under the terms of the GNU General Public License as published by *\n//  * the Free Software Foundation, either version 3 of the License, or    *\n//  * (at your option) any later version.                                  *\n//  *                                                                      *\n//  * This program is distributed in the hope that it will be useful,      *\n//  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n//  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n//  * GNU General Public License for more details.                         *\n//  *                                                                      *\n//  * You should have received a copy of the GNU General Public License    *\n//  * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n//  ************************************************************************\n//\n\n#include \"common/concurrency/RCULite.hh\"\n#include \"common/concurrency/AtomicUniquePtr.h\"\n#include \"gtest/gtest.h\"\n#include <shared_mutex>\n\nTEST(RCUTests, Basic)\n{\n  using namespace eos::common;\n  // Test that we can create an RCU object\n  RCUDomain<ThreadEpochCounter> rcu_domain;\n  atomic_unique_ptr<int> ptr(new int(0));\n  int i{0};\n  // Test that we can create an RCU read lock\n  auto read_fn = [&rcu_domain, &ptr](int index) {\n    auto tid = tlocalID.get();\n    std::cout << \"Starting reader at index=\" << index << \"tid=\" << tid\n              <<  std::endl;\n\n    for (int j = 0; j < 100; ++j) {\n      RCUReadLock rlock(rcu_domain);\n      ASSERT_TRUE(ptr);\n    }\n\n    std::cout << \"Done with reader at index= \" << index << \" tid=\" << tid <<\n              std::endl;\n  };\n  std::thread writer([&rcu_domain, &ptr, &i]() {\n    std::cout << \"Starting writer\";\n\n    for (int j = 0; j < 5000; ++j) {\n      int* old_ptr(nullptr);\n      {\n        RCUWriteLock wlock(rcu_domain);\n        old_ptr = ptr.reset(new int(i++));\n      }\n      std::cout << \".\";\n      delete old_ptr;\n      std::this_thread::sleep_for(std::chrono::nanoseconds(1));\n    }\n  });\n  std::vector<std::thread> readers;\n\n  for (int k = 0; k < 100; ++k) {\n    readers.emplace_back(read_fn, k);\n  }\n\n  for (int i = 0; i < 100; ++i) {\n    readers[i].join();\n  }\n\n  writer.join();\n}\n\nTEST(RCUTests, BasicVersionCounter)\n{\n  using namespace eos::common;\n  // Test that we can create an RCU object\n  VersionedRCUDomain rcu_domain;\n  atomic_unique_ptr<int> ptr(new int(0));\n  int i{0};\n  // Test that we can create an RCU read lock\n  auto read_fn = [&rcu_domain, &ptr](int index) {\n    auto tid = std::hash<std::thread::id> {}(std::this_thread::get_id()) % 4096;\n    std::cout << \"Starting reader at index=\" << index << \"tid=\" << tid\n              <<  std::endl;\n\n    for (int j = 0; j < 100; ++j) {\n      RCUReadLock rlock(rcu_domain);\n      ASSERT_TRUE(ptr);\n    }\n\n    std::cout << \"Done with reader at index= \" << index << \" tid=\" << tid <<\n              std::endl;\n  };\n  std::thread writer([&rcu_domain, &ptr, &i]() {\n    std::cout << \"Starting writer\";\n\n    for (int j = 0; j < 5000; ++j) {\n      rcu_domain.rcu_write_lock();\n      auto old_ptr = ptr.reset(new int(i++));\n      rcu_domain.rcu_write_unlock();\n      std::cout << \".\";\n      delete old_ptr;\n      std::this_thread::sleep_for(std::chrono::nanoseconds(1));\n    }\n  });\n  std::vector<std::thread> readers;\n\n  for (int k = 0; k < 100; ++k) {\n    readers.emplace_back(read_fn, k);\n  }\n\n  for (int i = 0; i < 100; ++i) {\n    readers[i].join();\n  }\n  std::cout << \"Joining writer\" << std::endl;\n  writer.join();\n} // namespace eos::common\n"
  },
  {
    "path": "unit_tests/common/concurrency/ThreadEpochCounterTests.cc",
    "content": "#include \"common/concurrency/ThreadEpochCounter.hh\"\n#include <gtest/gtest.h>\n\nTEST(ThreadEpochCounter, Basic)\n{\n  eos::common::ThreadEpochCounter counter;\n  ASSERT_FALSE(counter.epochHasReaders(0));\n  int epoch=1;\n  auto tid = counter.increment(epoch, 1);\n  ASSERT_TRUE(counter.epochHasReaders(epoch));\n  ASSERT_EQ(counter.getReaders(tid), 1);\n  counter.decrement();\n  ASSERT_FALSE(counter.epochHasReaders(epoch));\n}\n\nTEST(ThreadEpochCounter, HashCollision)\n{\n  eos::common::ThreadEpochCounter counter;\n  std::cout << \"My local TID=\" << eos::common::tlocalID.get() << \"\\n\";\n  ASSERT_FALSE(counter.epochHasReaders(0));\n  std::array<std::atomic<int>, 4096> epoch_counter {0};\n  std::vector<std::thread> threads;\n  for (int i=0; i < 100; ++i) {\n    threads.emplace_back([&counter, &epoch_counter, i](){\n      int epoch = (i & 1);\n      auto tid = counter.increment(epoch, 1);\n      // sleep for a bit so that all threads run and we actually get different TIDs,\n      // otherwise most of the threads\n      // should complete before the other threads start, getting only a TID:1\n      std::this_thread::sleep_for(std::chrono::milliseconds(10));\n\n      std::cout << \"Got TID=\" << tid << \" local tid= \"\n                << eos::common::tlocalID.get() << \"\\n\";\n      epoch_counter[tid]++;\n      ASSERT_EQ(counter.getReaders(tid), epoch_counter[tid]);\n    });\n  }\n\n  for (auto& t: threads) {\n    t.join();\n  }\n\n}\n\nTEST(ThreadEpochCounter, HashCollision2)\n{\n  eos::common::ThreadEpochCounter counter;\n  std::cout << \"My local TID=\" << eos::common::tlocalID.get() << \"\\n\";\n  ASSERT_FALSE(counter.epochHasReaders(0));\n  std::array<std::atomic<int>, 4096> epoch_counter {0};\n  std::vector<std::thread> threads;\n  for (int i=0; i < 1024; ++i) {\n    threads.emplace_back([&counter, &epoch_counter, i](){\n      int epoch = (i & 1);\n      auto tid = counter.increment(epoch, 1);\n      // sleep for a bit so that all threads run and we actually get different TIDs,\n      // otherwise most of the threads\n      // should complete before the other threads start, getting only a TID:1\n      std::this_thread::sleep_for(std::chrono::milliseconds(10));\n\n      std::cout << \"Got TID=\" << tid << \" local tid= \"\n                << eos::common::tlocalID.get() << \"\\n\";\n      epoch_counter[tid]++;\n      ASSERT_EQ(counter.getReaders(tid), epoch_counter[tid]);\n    });\n  }\n\n  for (auto& t: threads) {\n    t.join();\n  }\n\n}\n\n\nTEST(VersionEpochCounter, Basic)\n{\n  eos::common::experimental::VersionEpochCounter counter;\n  ASSERT_FALSE(counter.epochHasReaders(0));\n  int epoch=1;\n  auto tid = counter.increment(epoch, 1);\n  ASSERT_TRUE(counter.epochHasReaders(epoch));\n  ASSERT_EQ(counter.getReaders(tid), 1);\n  counter.decrement(epoch);\n  ASSERT_FALSE(counter.epochHasReaders(epoch));\n}\n\nTEST(VersionEpochCounter, MultiThreaded)\n{\n  eos::common::experimental::VersionEpochCounter<2> counter;\n  ASSERT_FALSE(counter.epochHasReaders(0));\n  std::array<std::atomic<int>, 2> epoch_counter = {0,0};\n  std::vector<std::thread> threads;\n  for (int i=0; i < 100; ++i) {\n    threads.emplace_back([&counter, &epoch_counter, i](){\n      int epoch = (i & 1);\n      auto tid = counter.increment(epoch, 1);\n      epoch_counter[tid]++;\n    });\n  }\n\n  for (auto& t: threads) {\n    t.join();\n  }\n\n  ASSERT_EQ(epoch_counter[0], counter.getReaders(0));\n  ASSERT_EQ(epoch_counter[1], counter.getReaders(1));\n}\n"
  },
  {
    "path": "unit_tests/console/AclCmdTest.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file AclCmdTest.cc\n//! @author Stefan Isidorovic <stefan.isidorovic@comtrade.com>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"mgm/proc/user/AclCmd.hh\"\n#define IN_TEST_HARNESS\n#include \"console/commands/helpers/AclHelper.hh\"\n#undef IN_TEST_HARNESS\n\nEOSMGMNAMESPACE_BEGIN\n\nGlobalOptions opts;\n\nTEST(ICmdHelper, ResponseParsingFull)\n{\n  // Any helper would do\n  AclHelper exec(opts);\n  ASSERT_EQ(\n    exec.ProcessResponse(\"mgm.proc.stdout=123&mgm.proc.stderr=345&mgm.proc.retc=3\"),\n    3);\n  ASSERT_EQ(exec.GetResult(), \"123\\n\");\n  ASSERT_EQ(exec.GetError(), \"345\\n\");\n  ASSERT_EQ(exec.GetErrc(), 3);\n}\n\nTEST(ICmdHelper, ResponseParsingStdoutAndErrc)\n{\n  AclHelper exec(opts);\n  ASSERT_EQ(exec.ProcessResponse(\"mgm.proc.stdout=123&mgm.proc.retc=999\"), 999);\n  ASSERT_EQ(exec.GetResult(), \"123\\n\");\n  ASSERT_EQ(exec.GetError(), \"\\n\");\n  ASSERT_EQ(exec.GetErrc(), 999);\n}\n\nTEST(ICmdHelper, ResponseParsingStderrAndErrc)\n{\n  AclHelper exec(opts);\n  ASSERT_EQ(\n    exec.ProcessResponse(\"&mgm.proc.stderr=this is stderr&mgm.proc.retc=2\"), 2);\n  ASSERT_EQ(exec.GetResult(), \"\\n\");\n  ASSERT_EQ(exec.GetError(), \"this is stderr\\n\");\n  ASSERT_EQ(exec.GetErrc(), 2);\n}\n\nTEST(ICmdHelper, ResponseParsingEmptyStdout)\n{\n  AclHelper exec(opts);\n  ASSERT_EQ(\n    exec.ProcessResponse(\"mgm.proc.stdout=&mgm.proc.stderr=345&mgm.proc.retc=3\"),\n    3);\n  ASSERT_EQ(exec.GetResult(), \"\\n\");\n  ASSERT_EQ(exec.GetError(), \"345\\n\");\n  ASSERT_EQ(exec.GetErrc(), 3);\n}\n\nTEST(ICmdHelper, ResponseParsingEmptyStderr)\n{\n  AclHelper exec(opts);\n  ASSERT_EQ(\n    exec.ProcessResponse(\"mgm.proc.stdout=123&mgm.proc.stderr=&mgm.proc.retc=3\"),\n    3);\n  ASSERT_EQ(exec.GetResult(), \"123\\n\");\n  ASSERT_EQ(exec.GetError(), \"\\n\");\n  ASSERT_EQ(exec.GetErrc(), 3);\n}\n\nTEST(ICmdHelper, ResponseParsingPlain)\n{\n  AclHelper exec(opts);\n  ASSERT_EQ(exec.ProcessResponse(\"aaaaaaa\"), 0);\n  ASSERT_EQ(exec.GetResult(), \"aaaaaaa\\n\");\n  ASSERT_EQ(exec.GetError(), \"\\n\");\n  ASSERT_EQ(exec.GetErrc(), 0);\n}\n\nTEST(ICmdHelper, SimpleSimulation)\n{\n  // Note: This only tests the faking capabilities of ICmdHelper.\n  AclHelper exec(opts);\n  std::string message;\n  exec.InjectSimulated(\"mgm.cmd=ayy&mgm.subcmd=lmao\", {\"12345\"});\n  ASSERT_FALSE(exec.CheckSimulationSuccessful(message));\n  ASSERT_EQ(exec.RawExecute(\"mgm.cmd=ayy&mgm.subcmd=lmao\"), 0);\n  ASSERT_EQ(exec.GetResult(), \"12345\\n\");\n  ASSERT_EQ(exec.GetError(), \"\\n\");\n  ASSERT_EQ(exec.GetErrc(), 0);\n  ASSERT_TRUE(exec.CheckSimulationSuccessful(message));\n}\n\nTEST(ICmdHelper, ComplexSimulation)\n{\n  // Note: This only tests the faking capabilities of ICmdHelper.\n  AclHelper exec(opts);\n  std::string message;\n  exec.InjectSimulated(\"mgm.cmd=ayy1&mgm.subcmd=lmao1\", {\"12345\", \"some error\"});\n  exec.InjectSimulated(\"mgm.cmd=ayy2&mgm.subcmd=lmao2\", {\"23456\"});\n  exec.InjectSimulated(\"mgm.cmd=ayy2&mgm.subcmd=lmao2\", {\"999\", \"error 2\"});\n  exec.InjectSimulated(\"mgm.cmd=ayy3&mgm.subcmd=lmao3\", {\"888\", \"error 3\", 987});\n  exec.InjectSimulated(\"mgm.cmd=ayy1&mgm.subcmd=lmao1\", {\"234567\"});\n  ASSERT_FALSE(exec.CheckSimulationSuccessful(message));\n  ASSERT_EQ(exec.RawExecute(\"mgm.cmd=ayy1&mgm.subcmd=lmao1\"), 0);\n  ASSERT_EQ(exec.GetResult(), \"12345\\n\");\n  ASSERT_EQ(exec.GetError(), \"some error\\n\");\n  ASSERT_EQ(exec.GetErrc(), 0);\n  ASSERT_EQ(exec.RawExecute(\"mgm.cmd=ayy2&mgm.subcmd=lmao2\"), 0);\n  ASSERT_EQ(exec.GetResult(), \"23456\\n\");\n  ASSERT_EQ(exec.GetError(), \"\\n\");\n  ASSERT_EQ(exec.GetErrc(), 0);\n  ASSERT_EQ(exec.RawExecute(\"mgm.cmd=ayy2&mgm.subcmd=lmao2\"), 0);\n  ASSERT_EQ(exec.GetResult(), \"999\\n\");\n  ASSERT_EQ(exec.GetError(), \"error 2\\n\");\n  ASSERT_EQ(exec.GetErrc(), 0);\n  ASSERT_EQ(exec.RawExecute(\"mgm.cmd=ayy3&mgm.subcmd=lmao3\"), 987);\n  ASSERT_EQ(exec.GetResult(), \"888\\n\");\n  ASSERT_EQ(exec.GetError(), \"error 3\\n\");\n  ASSERT_EQ(exec.GetErrc(), 987);\n  ASSERT_FALSE(exec.CheckSimulationSuccessful(message));\n  ASSERT_EQ(exec.RawExecute(\"mgm.cmd=ayy1&mgm.subcmd=lmao1\"), 0);\n  ASSERT_EQ(exec.GetResult(), \"234567\\n\");\n  ASSERT_EQ(exec.GetError(), \"\\n\");\n  ASSERT_EQ(exec.GetErrc(), 0);\n  ASSERT_TRUE(exec.CheckSimulationSuccessful(message));\n}\n\nTEST(ICmdHelper, FailedSimulation)\n{\n  // Note: This only tests the faking capabilities of ICmdHelper.\n  AclHelper exec(opts);\n  std::string message;\n  exec.InjectSimulated(\"mgm.cmd=ayy1&mgm.subcmd=lmao1\", {\"12345\", \"some error\"});\n  exec.InjectSimulated(\"mgm.cmd=ayy2&mgm.subcmd=lmao2\", {\"23456\"});\n  ASSERT_EQ(exec.RawExecute(\"mgm.cmd=ayy1&mgm.subcmd=lmao1\"), 0);\n  ASSERT_EQ(exec.GetResult(), \"12345\\n\");\n  ASSERT_EQ(exec.GetError(), \"some error\\n\");\n  ASSERT_EQ(exec.GetErrc(), 0);\n  ASSERT_EQ(exec.RawExecute(\"mgm.cmd=ayy3&mgm.subcmd=lmao3\"), EIO);\n  std::cout << message << std::endl;\n  ASSERT_FALSE(exec.CheckSimulationSuccessful(message));\n}\n\nTEST(AclCmd, CheckId)\n{\n  eos::console::RequestProto req;\n  eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n  AclCmd test(std::move(req), vid);\n  ASSERT_TRUE(test.CheckCorrectId(\"u:user\"));\n  ASSERT_TRUE(test.CheckCorrectId(\"g:group\"));\n  ASSERT_TRUE(test.CheckCorrectId(\"egroup:gssroup\"));\n  ASSERT_FALSE(test.CheckCorrectId(\"gr:gro@up\"));\n  ASSERT_FALSE(test.CheckCorrectId(\"ug:group\"));\n  ASSERT_FALSE(test.CheckCorrectId(\":a$4uggroup\"));\n  ASSERT_FALSE(test.CheckCorrectId(\"egro:gro\"));\n}\n\nTEST(AclCmd, GetRuleBitmask)\n{\n  eos::console::RequestProto req;\n  eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n  AclCmd test(std::move(req), vid);\n  ASSERT_TRUE(test.GetRuleBitmask(\"wr!u+d-!u\", true));\n  ASSERT_EQ(test.GetAddRule(), 67u);\n  ASSERT_EQ(test.GetRmRule(), 128u);\n  ASSERT_FALSE(test.GetRuleBitmask(\"+++++++d!urwxxxxxx!u\", true));\n  ASSERT_TRUE(test.GetRuleBitmask(\"+rw+d-!u\", false));\n  ASSERT_EQ(test.GetAddRule(), 67u);\n  ASSERT_EQ(test.GetRmRule(), 128u);\n  ASSERT_TRUE(test.GetRuleBitmask(\"+rw+d-!u\", false));\n  ASSERT_EQ(test.GetAddRule(), 67u);\n  ASSERT_EQ(test.GetRmRule(), 128u);\n  ASSERT_FALSE(test.GetRuleBitmask(\"+rw!u+d-!u$%@\", false));\n  ASSERT_FALSE(test.GetRuleBitmask(\"rw!u+d-!u\", false));\n}\n\nTEST(AclCmd, AclRuleFromString)\n{\n  // Method AclRuleFromString is called to parse acl data which MGM Node\n  // sends. So string in incorrect format is not possible, hence there is\n  // no checking for that.\n  std::optional<Rule> temp;\n  eos::console::RequestProto req;\n  eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n  AclCmd test(std::move(req), vid);\n  temp = test.GetRuleFromString(\"u:user1:rwx!u\");\n  ASSERT_EQ(temp->first, \"u:user1\");\n  ASSERT_EQ(temp->second, 135);\n  temp = test.GetRuleFromString(\"g:group1:wx!u\");\n  ASSERT_EQ(temp->first, \"g:group1\");\n  ASSERT_EQ(temp->second, 134);\n  temp = test.GetRuleFromString(\"egroup:group1:rx!u\");\n  ASSERT_EQ(temp->first, \"egroup:group1\");\n  ASSERT_EQ(temp->second, 133);\n}\n\nTEST(AclHelper, TestParseCommand)\n{\n  AclHelper acl(opts);\n  ASSERT_EQ(acl.ParseCommand(\"--sys u:1001:-w /eos/test\"), true);\n  ASSERT_EQ(acl.ParseCommand(\"--user u:1001:-w /eos/test\"), true);\n  ASSERT_EQ(acl.ParseCommand(\"--sys -l /eos/test\"), true);\n  ASSERT_EQ(acl.ParseCommand(\"--user -lR /eos/test\"), true);\n  ASSERT_EQ(acl.ParseCommand(\"--sys u:1001:-w /eos/test\"), true);\n  ASSERT_EQ(acl.ParseCommand(\"--user -R --recursive u:1001:-w /eos/test\"), true);\n  ASSERT_EQ(acl.ParseCommand(\"-FD --recursive u:1001:-w /eos/test\"), false);\n  ASSERT_EQ(acl.ParseCommand(\"-Rgg --recursive u:1001:-w /eos/test\"), false);\n}\n\nEOSMGMNAMESPACE_END\n"
  },
  {
    "path": "unit_tests/console/CmdsTests.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file CmdsTests.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"console/commands/helpers/AclHelper.hh\"\n#include \"console/commands/helpers/RecycleHelper.hh\"\n#include \"unistd.h\"\n\nGlobalOptions opts;\n\nTEST(AclHelper, RouteFromPathAppended)\n{\n  AclHelper acl(opts);\n  acl.ParseCommand(\"--user u:1001=rwx /eos/devtest/\");\n  const std::string proto_msg = (!isatty(STDOUT_FILENO) ||\n                                 !isatty(STDERR_FILENO)) ?\n                                \"Eh8IAiABKgp1OjEwMDE9cnd4Mg0vZW9zL2RldnRlc3Qv+AEB\" :\n                                \"Eh8IAiABKgp1OjEwMDE9cnd4Mg0vZW9zL2RldnRlc3Qv\";\n  acl.InjectSimulated(\"//proc/user/?mgm.cmd.proto=\" + proto_msg +\n                      \"&eos.route=/eos/devtest/\", {\"\", \"\", 0});\n  ASSERT_EQ(acl.Execute(true, true), 0);\n  // Setting EOSHOME env variable should make no difference\n  setenv(\"EOSHOME\", \"/eos/home/test/\", 1);\n  acl.InjectSimulated(\"//proc/user/?mgm.cmd.proto=\" + proto_msg +\n                      \"&eos.route=/eos/devtest/\", {\"\", \"\", 0});\n  ASSERT_EQ(acl.Execute(true, true), 0);\n  unsetenv(\"EOSHOME\");\n}\n\nTEST(RecycleHelper, RouteFromEnvAppended)\n{\n  // By default /eos/user/username[0]/username is added to the eos.route\n  const std::string username = cuserid(nullptr);\n  std::ostringstream oss_route;\n  oss_route << \"/eos/user/\" << username[0] << \"/\" << username << \"/\";\n  RecycleHelper recycle(opts);\n  recycle.ParseCommand(\"ls\");\n  const std::string proto_msg = (!isatty(STDOUT_FILENO) ||\n                                 !isatty(STDERR_FILENO)) ?\n                                \"UgQKAggB+AEB\" : \"UgQKAggB\";\n\n  if (getenv(\"USER\")) {\n    recycle.InjectSimulated(\"//proc/user/?mgm.cmd.proto=\" + proto_msg\n                            + \"&eos.route=\" + oss_route.str(),\n    {\"\", \"\", 0});\n  } else {\n    // Inside the docker container the USER env is not set\n    recycle.InjectSimulated(\"//proc/user/?mgm.cmd.proto=\" + proto_msg, {\"\", \"\", 0});\n  }\n\n  ASSERT_EQ(recycle.Execute(false, true), 0);\n  // Setting EOSHOME env variable should update the eos.route\n  setenv(\"EOSHOME\", \"/eos/home/test/\", 1);\n  recycle.InjectSimulated(\"//proc/user/?mgm.cmd.proto=\" + proto_msg\n                          + \"&eos.route=/eos/home/test/\", {\"\", \"\", 0});\n  ASSERT_EQ(recycle.Execute(false, true), 0);\n  unsetenv(\"EOSHOME\");\n  // Setting EOSUSER env variable should update eos.route to point to the old\n  // /eos/user/username[0]/username/ where username=getenv(\"EOSUSER\")\n  setenv(\"EOSUSER\", \"dummy\", 1);\n  unsetenv(\"USER\"); // otherwise USER has precedence\n  recycle.InjectSimulated(\"//proc/user/?mgm.cmd.proto=\" + proto_msg +\n                          \"&eos.route=/eos/user/d/dummy/\", {\"\", \"\", 0});\n  ASSERT_EQ(recycle.Execute(false, true), 0);\n  unsetenv(\"EOSUSER\");\n  // The same should happend if USER is set\n  setenv(\"USER\", \"other_dummy\", 1);\n  recycle.InjectSimulated(\"//proc/user/?mgm.cmd.proto=\" + proto_msg +\n                          \"&eos.route=/eos/user/o/other_dummy/\", {\"\", \"\", 0});\n  ASSERT_EQ(recycle.Execute(false, true), 0);\n  unsetenv(\"USER\");\n}\n"
  },
  {
    "path": "unit_tests/console/ConsoleCompletionTest.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file ConsoleCompletionTest.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2026 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"console/CommandFramework.hh\"\n#include \"console/ConsoleCompletion.hh\"\n#include \"gtest/gtest.h\"\n\n#include <algorithm>\n\nnamespace {\nbool\ncontains(const std::vector<std::string>& values, const std::string& needle)\n{\n  return std::find(values.begin(), values.end(), needle) != values.end();\n}\n} // namespace\n\nTEST(ConsoleCompletion, TokenizePrefixHandlesQuotedArguments)\n{\n  const auto tokens = eos_completion_tokenize_prefix(\"file info \\\"/eos/project alpha\\\" \");\n\n  ASSERT_EQ(tokens.size(), 3u);\n  EXPECT_EQ(tokens[0], \"file\");\n  EXPECT_EQ(tokens[1], \"info\");\n  EXPECT_EQ(tokens[2], \"/eos/project alpha\");\n}\n\nTEST(ConsoleCompletion, TokenizePrefixHandlesBackslashEscapedArguments)\n{\n  const auto tokens = eos_completion_tokenize_prefix(\"file info /eos/project\\\\ alpha\");\n\n  ASSERT_EQ(tokens.size(), 3u);\n  EXPECT_EQ(tokens[0], \"file\");\n  EXPECT_EQ(tokens[1], \"info\");\n  EXPECT_EQ(tokens[2], \"/eos/project alpha\");\n}\n\nTEST(ConsoleCompletion, NativeCommandRegistryInitializationIsIdempotent)\n{\n  EnsureNativeCommandRegistryInitialized();\n  const auto before = CommandRegistry::instance().all().size();\n\n  RegisterNativeConsoleCommands();\n  EnsureNativeCommandRegistryInitialized();\n\n  EXPECT_EQ(CommandRegistry::instance().all().size(), before);\n}\n\nTEST(ConsoleCompletion, GroupCommandCompletesDrainState)\n{\n  EnsureNativeCommandRegistryInitialized();\n  auto* cmd = CommandRegistry::instance().find(\"group\");\n\n  ASSERT_NE(cmd, nullptr);\n  const auto suggestions = cmd->complete({\"set\", \"default.0\"});\n\n  EXPECT_TRUE(contains(suggestions, \"on\"));\n  EXPECT_TRUE(contains(suggestions, \"drain\"));\n  EXPECT_TRUE(contains(suggestions, \"off\"));\n}\n\nTEST(ConsoleCompletion, GroupCommandUsesHelpTextForCompletion)\n{\n  EnsureNativeCommandRegistryInitialized();\n  auto* cmd = CommandRegistry::instance().find(\"group\");\n\n  ASSERT_NE(cmd, nullptr);\n  EXPECT_NE(cmd->helpText().find(\"on|drain|off\"), std::string::npos);\n}\n\nTEST(ConsoleCompletion, SpaceCommandCompletesGroupDrainerSubcommands)\n{\n  EnsureNativeCommandRegistryInitialized();\n  auto* cmd = CommandRegistry::instance().find(\"space\");\n\n  ASSERT_NE(cmd, nullptr);\n  const auto suggestions = cmd->complete({\"groupdrainer\"});\n\n  EXPECT_TRUE(contains(suggestions, \"status\"));\n  EXPECT_TRUE(contains(suggestions, \"reset\"));\n  EXPECT_FALSE(contains(suggestions, \"--detail\"));\n  EXPECT_FALSE(contains(suggestions, \"--failed\"));\n}\n\nTEST(ConsoleCompletion, FileCommandCompletesSubcommands)\n{\n  EnsureNativeCommandRegistryInitialized();\n  auto* cmd = CommandRegistry::instance().find(\"file\");\n\n  ASSERT_NE(cmd, nullptr);\n  const auto suggestions = cmd->complete({});\n\n  EXPECT_TRUE(contains(suggestions, \"info\"));\n  EXPECT_TRUE(contains(suggestions, \"touch\"));\n  EXPECT_TRUE(contains(suggestions, \"verify\"));\n}\n\nTEST(ConsoleCompletion, FileCommandCompletesLeafOptionsFromHelp)\n{\n  EnsureNativeCommandRegistryInitialized();\n  auto* cmd = CommandRegistry::instance().find(\"file\");\n\n  ASSERT_NE(cmd, nullptr);\n  const auto suggestions = cmd->complete({\"verify\", \"/eos/dev/file\"});\n\n  EXPECT_TRUE(contains(suggestions, \"-checksum\"));\n  EXPECT_TRUE(contains(suggestions, \"-commitchecksum\"));\n  EXPECT_TRUE(contains(suggestions, \"-commitsize\"));\n  EXPECT_TRUE(contains(suggestions, \"-commitfmd\"));\n  EXPECT_TRUE(contains(suggestions, \"-rate\"));\n  EXPECT_TRUE(contains(suggestions, \"-resync\"));\n}\n\nTEST(ConsoleCompletion, FsConfigCompletesKeysFromHelp)\n{\n  EnsureNativeCommandRegistryInitialized();\n  auto* cmd = CommandRegistry::instance().find(\"fs\");\n\n  ASSERT_NE(cmd, nullptr);\n  const auto suggestions = cmd->complete({\"config\", \"123\"});\n\n  EXPECT_TRUE(contains(suggestions, \"configstatus=\"));\n  EXPECT_TRUE(contains(suggestions, \"headroom=\"));\n}\n\nTEST(ConsoleCompletion, IoShapingPrefersSubcommandsOverLeafOptions)\n{\n  EnsureNativeCommandRegistryInitialized();\n  auto* cmd = CommandRegistry::instance().find(\"io\");\n\n  ASSERT_NE(cmd, nullptr);\n  const auto suggestions = cmd->complete({\"shaping\"});\n\n  EXPECT_TRUE(contains(suggestions, \"ls\"));\n  EXPECT_TRUE(contains(suggestions, \"enable\"));\n  EXPECT_TRUE(contains(suggestions, \"disable\"));\n  EXPECT_TRUE(contains(suggestions, \"policy\"));\n  EXPECT_TRUE(contains(suggestions, \"config\"));\n  EXPECT_FALSE(contains(suggestions, \"set\"));\n  EXPECT_FALSE(contains(suggestions, \"--apps\"));\n  EXPECT_FALSE(contains(suggestions, \"--window\"));\n}\n\nTEST(ConsoleCompletion, IoShapingConfigPrefersSubcommandsOverLeafOptions)\n{\n  EnsureNativeCommandRegistryInitialized();\n  auto* cmd = CommandRegistry::instance().find(\"io\");\n\n  ASSERT_NE(cmd, nullptr);\n  const auto suggestions = cmd->complete({\"shaping\", \"config\"});\n\n  EXPECT_TRUE(contains(suggestions, \"ls\"));\n  EXPECT_TRUE(contains(suggestions, \"set\"));\n  EXPECT_FALSE(contains(suggestions, \"--estimators-period\"));\n}\n\nTEST(ConsoleCompletion, IoShapingConfigSetCompletesOptionsFromHelp)\n{\n  EnsureNativeCommandRegistryInitialized();\n  auto* cmd = CommandRegistry::instance().find(\"io\");\n\n  ASSERT_NE(cmd, nullptr);\n  const auto suggestions = cmd->complete({\"shaping\", \"config\", \"set\"});\n\n  EXPECT_TRUE(contains(suggestions, \"--estimators-period\"));\n  EXPECT_TRUE(contains(suggestions, \"--policy-period\"));\n  EXPECT_TRUE(contains(suggestions, \"--report-period\"));\n  EXPECT_TRUE(contains(suggestions, \"--system-window\"));\n}\n\nTEST(ConsoleCompletion, ShellCompletionTopLevelCommands)\n{\n  const auto suggestions = eos_shell_completion_candidates({}, \"gr\");\n\n  EXPECT_TRUE(contains(suggestions, \"group\"));\n}\n\nTEST(ConsoleCompletion, ShellCompletionSubcommands)\n{\n  const auto suggestions = eos_shell_completion_candidates({\"group\"}, \"s\");\n\n  EXPECT_TRUE(contains(suggestions, \"set\"));\n}\n\nTEST(ConsoleCompletion, ShellCompletionGlobalFlags)\n{\n  const auto suggestions = eos_shell_completion_candidates({}, \"--j\");\n\n  EXPECT_TRUE(contains(suggestions, \"--json\"));\n}\n\nTEST(ConsoleCompletion, ShellCompletionIoShapingSubcommands)\n{\n  const auto suggestions = eos_shell_completion_candidates({\"io\"}, \"sh\");\n\n  EXPECT_TRUE(contains(suggestions, \"shaping\"));\n}\n\nTEST(ConsoleCompletion, ShellCompletionIoShapingPolicyActions)\n{\n  const auto suggestions = eos_shell_completion_candidates({\"io\", \"shaping\"}, \"po\");\n\n  EXPECT_TRUE(contains(suggestions, \"policy\"));\n}\n\nTEST(ConsoleCompletion, PathCompletionModeUsesRootedPathContexts)\n{\n  EXPECT_EQ(eos_shell_path_completion_mode(\"ls\", {}, \"\"),\n            EosShellPathCompletionMode::Any);\n  EXPECT_EQ(eos_shell_path_completion_mode(\"ls\", {}, \"/eos/user\"),\n            EosShellPathCompletionMode::Any);\n  EXPECT_EQ(eos_shell_path_completion_mode(\"stat\", {}, \"/eos/user/file\"),\n            EosShellPathCompletionMode::Any);\n  EXPECT_EQ(eos_shell_path_completion_mode(\"cd\", {}, \"\"),\n            EosShellPathCompletionMode::Directories);\n  EXPECT_EQ(eos_shell_path_completion_mode(\"group\", {}, \"\"),\n            EosShellPathCompletionMode::None);\n  EXPECT_EQ(eos_shell_path_completion_mode(\"ls\", {}, \"user/al\"),\n            EosShellPathCompletionMode::None);\n  EXPECT_EQ(eos_shell_path_completion_mode(\"cp\", {}, \"./local\"),\n            EosShellPathCompletionMode::None);\n}\n\nTEST(ConsoleCompletion, ResolveRootedPathInputForEmptyWordStartsAtRoot)\n{\n  std::string lookupDir;\n  std::string displayPrefix;\n  std::string basename;\n\n  eos_shell_resolve_rooted_path_input(\"\", lookupDir, displayPrefix, basename);\n\n  EXPECT_EQ(lookupDir, \"/eos/\");\n  EXPECT_EQ(displayPrefix, \"/eos/\");\n  EXPECT_EQ(basename, \"\");\n}\n\nTEST(ConsoleCompletion, ResolveRootedPathInputPreservesTypedPrefix)\n{\n  std::string lookupDir;\n  std::string displayPrefix;\n  std::string basename;\n\n  eos_shell_resolve_rooted_path_input(\"/eos/user/al\", lookupDir, displayPrefix, basename);\n\n  EXPECT_EQ(lookupDir, \"/eos/user/\");\n  EXPECT_EQ(displayPrefix, \"/eos/user/\");\n  EXPECT_EQ(basename, \"al\");\n}\n\nTEST(ConsoleCompletion, ResolveRootedPathInputCompletesEosPrefix)\n{\n  std::string lookupDir;\n  std::string displayPrefix;\n  std::string basename;\n\n  eos_shell_resolve_rooted_path_input(\"/eos\", lookupDir, displayPrefix, basename);\n\n  EXPECT_EQ(lookupDir, \"/\");\n  EXPECT_EQ(displayPrefix, \"/\");\n  EXPECT_EQ(basename, \"eos\");\n}\n"
  },
  {
    "path": "unit_tests/console/ConsoleUtilTests.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file ConsoleUtilTests.cc\n//! @author Mihai Patrascoiu <mihai.patrascoiu@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"console/ConsoleMain.hh\"\n\n//------------------------------------------------------------------------------\n// Test parsing of keyword identifiers (e.g.: fid, fxid)\n//------------------------------------------------------------------------------\nTEST(PathIdentifier, KeyWords)\n{\n  // Check the keywords\n  ASSERT_STREQ(PathIdentifier(\"fid:100\").c_str(), \"fid:100\");\n  ASSERT_STREQ(PathIdentifier(\"fxid:100a\").c_str(), \"fxid:100a\");\n  ASSERT_STREQ(PathIdentifier(\"pid:100\").c_str(), \"pid:100\");\n  ASSERT_STREQ(PathIdentifier(\"cid:100\").c_str(), \"cid:100\");\n  ASSERT_STREQ(PathIdentifier(\"pxid:bc1\").c_str(), \"pxid:bc1\");\n  ASSERT_STREQ(PathIdentifier(\"cxid:abba\").c_str(), \"cxid:abba\");\n  // Check the encode flag doesn't affect keywords\n  ASSERT_STREQ(PathIdentifier(\"fid:100\", true).c_str(), \"fid:100\");\n  // Check keyword-similar relative path\n  ASSERT_STREQ(PathIdentifier(\"fid100\").c_str(), \"/fid100\");\n  ASSERT_STREQ(PathIdentifier(\"pxidbc1\").c_str(), \"/pxidbc1\");\n}\n\n//------------------------------------------------------------------------------\n// Test parsing of absolute path identifier\n//------------------------------------------------------------------------------\nTEST(PathIdentifier, AbsolutePath)\n{\n  // Absolute path\n  ASSERT_STREQ(PathIdentifier(\"/eos/instance/user/file\").c_str(),\n               \"/eos/instance/user/file\");\n  // Absolute path with &\n  ASSERT_STREQ(PathIdentifier(\"/eos/instance/user/file&with&symbols\").c_str(),\n               \"/eos/instance/user/file&with&symbols\");\n  // Absolute path with &, encoded\n  ASSERT_STREQ(PathIdentifier(\"/eos/instance/user/file&with&symbols\",\n                              true).c_str(),\n               \"/eos/instance/user/file#AND#with#AND#symbols\");\n}\n\n//------------------------------------------------------------------------------\n// Test parsing of relative path identifier\n//------------------------------------------------------------------------------\nTEST(PathIdentifier, RelativePath)\n{\n  gPwd = \"/\";\n  // Relative path\n  ASSERT_STREQ(PathIdentifier(\"file\").c_str(), \"/file\");\n  // Relative path with &\n  ASSERT_STREQ(PathIdentifier(\"file&with&symbols\").c_str(),\n               \"/file&with&symbols\");\n  // Relative path with &, encoded\n  ASSERT_STREQ(PathIdentifier(\"file&with&symbols\", true).c_str(),\n               \"/file#AND#with#AND#symbols\");\n  gPwd = \"/eos/dir&with&symbols/\";\n  // Relative path with &\n  ASSERT_STREQ(PathIdentifier(\"file&with&symbols\").c_str(),\n               \"/eos/dir&with&symbols/file&with&symbols\");\n  // Relative path with &, encoded\n  ASSERT_STREQ(PathIdentifier(\"file&with&symbols\", true).c_str(),\n               \"/eos/dir#AND#with#AND#symbols/file#AND#with#AND#symbols\");\n}\n"
  },
  {
    "path": "unit_tests/console/ParseCommentTest.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file ParseCommentTest.cc\n//! @author Mihai Patrascoiu <mihai.patrascoiu@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"console/ConsoleMain.hh\"\n\n//------------------------------------------------------------------------------\n// Test valid comment syntax\n//------------------------------------------------------------------------------\nTEST(ParseComment, ValidSyntax)\n{\n  std::string comment;\n  char* line;\n  // Arguments as they are\n  line = (char*) \"eos version --comment \\\"Hello Comment\\\"\";\n  std::string cmd = parse_comment(line, comment);\n  ASSERT_STRNE(line, 0);\n  ASSERT_STREQ(comment.c_str(), \"\\\"Hello Comment\\\"\");\n  // Arguments quote-encased\n  line = (char*) \"eos \\\"version\\\" \\\"--comment\\\" \\\"Hello Comment\\\"\";\n  cmd = parse_comment(line, comment);\n  ASSERT_FALSE(cmd.empty());\n  ASSERT_STREQ(comment.c_str(), \"\\\"Hello Comment\\\"\");\n}\n\n//------------------------------------------------------------------------------\n// Test invalid comment syntax\n//------------------------------------------------------------------------------\nTEST(ParseComment, InvalidSyntax)\n{\n  std::string comment;\n  char* line;\n  // Missing comment text\n  line = (char*) \"eos version --comment\";\n  std::string cmd = parse_comment(line, comment);\n  ASSERT_TRUE(cmd.empty());\n  ASSERT_TRUE(comment.empty());\n  // Empty comment text\n  line = (char*) \"eos version --comment \\\"\\\"\";\n  cmd = parse_comment(line, comment);\n  ASSERT_TRUE(cmd.empty());\n  ASSERT_TRUE(comment.empty());\n  // Missing starting quote for comment text\n  line = (char*) \"eos version --comment Hello Comment\\\"\";\n  cmd = parse_comment(line, comment);\n  ASSERT_TRUE(cmd.empty());\n  ASSERT_TRUE(comment.empty());\n  // Missing ending quote for comment text\n  line = (char*) \"eos version --comment \\\"Hello Comment\";\n  cmd = parse_comment(line, comment);\n  ASSERT_TRUE(cmd.empty());\n  ASSERT_TRUE(comment.empty());\n}\n\n//------------------------------------------------------------------------------\n// Test comment extraction\n//------------------------------------------------------------------------------\nTEST(ParseComment, CommentExtraction)\n{\n  std::string comment;\n  char* line = (char*) \"eos --comment \\\"Hello Comment\\\" version\";\n  std::string cmd = parse_comment(line, comment);\n  ASSERT_STREQ(cmd.c_str(), \"eos  version\");\n  ASSERT_STREQ(comment.c_str(), \"\\\"Hello Comment\\\"\");\n}\n\n//------------------------------------------------------------------------------\n// Test no comment present\n//------------------------------------------------------------------------------\nTEST(ParseComment, NoCommentPresent)\n{\n  std::string comment;\n  char* line;\n  // Comment flag missing completely\n  line = (char*) \"eos version\";\n  std::string cmd = parse_comment(line, comment);\n  ASSERT_STREQ(cmd.c_str(), \"eos version\");\n  ASSERT_TRUE(comment.empty());\n  // Similar flag containing --comment text\n  line = (char*) \"eos config dump --comments\";\n  cmd = parse_comment(line, comment);\n  ASSERT_STREQ(cmd.c_str(), \"eos config dump --comments\");\n  ASSERT_TRUE(comment.empty());\n}\n"
  },
  {
    "path": "unit_tests/console/RegexUtilTest.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file RegexUtilTest.cc\n//! @author Stefan Isidorovic <stefan.isidorovic@comtrade.com>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2016 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"console/RegexUtil.hh\"\n\nTEST(RegexUtil, BasicSanity)\n{\n  RegexUtil test;\n  std::string origin(\"asdfasfsssstest12kksdjftestossskso\");\n  std::string temp;\n  // Pass case\n  test.SetOrigin(origin);\n  test.SetRegex(\"test[0-9]+\");\n  test.initTokenizerMode();\n  temp = test.Match();\n  ASSERT_EQ(temp, \"test12\");\n  temp = test.Match();\n  ASSERT_EQ(temp, \"test12\");\n}\n\nTEST(RegexUtil, FailCases)\n{\n  std::string origin(\"asdfasfsssstest12kksdjftestossskso\");\n  RegexUtil test;\n  (test.SetOrigin(origin));\n  ASSERT_THROW(test.SetRegex(\"test[0-9\"),  std::string);\n\n  RegexUtil test2;\n  ASSERT_THROW(test2.SetRegex(\"test[0-9\"),  std::string);\n  ASSERT_THROW(test2.initTokenizerMode(),  std::string);\n}\n"
  },
  {
    "path": "unit_tests/fst/HealthTest.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#define IN_TEST_HARNESS\n#include \"fst/Health.hh\"\n#undef IN_TEST_HARNESS\n#include <vector>\n\nusing eos::fst::DiskHealth;\n\nstd::vector<std::string> demo_raid {\n  \"Personalities : [raid1] [raid6] [raid5] [raid4] [raid0] \\n\\\n\\n\\\nmd125 : active raid6 sdx[3] sdae[7] sdw[2] sdy[4] sdz[6] sdv[1]\\n\\\n      15627549952 blocks super 1.2 level 6, 32k chunk, algorithm 2 [6/6] [UUUUUU]\\n\\\n      bitmap: 0/30 pages [0KB], 65536KB chunk\\n\\\n      \\n\\\nmd1 : active raid1 sdb2[1] sda2[0]\\n\\\n      1952333824 blocks super 1.2 [2/2] [UU]\\n\\\n      bitmap: 6/15 pages [24KB], 65536KB chunk\\n\\\n\\n\\\nmd0 : active raid1 sda1[0] sdb1[1]\\n\\\n      1048512 blocks super 1.0 [2/2] [UU]\\n\\\n      bitmap: 0/1 pages [0KB], 65536KB chunk\\n\\\n\\n\\\nunused devices: <none>\",\n  \"Personalities : [raid1] [raid0] \\n\\\nmd96 : active raid0 md109[0] md105[2] md121[1]\\n\\\n      17580781056 blocks super 1.2 128k chunks\\n\\\n      \\n\\\nmd97 : active raid0 md108[0] md123[1] md126[2]\\n\\\n      17580781056 blocks super 1.2 128k chunks\\n\\\n      \\n\\\nmd99 : active raid0 md104[1] md118[2] md115[0]\\n\\\n      17580781056 blocks super 1.2 128k chunks\\n\\\n\\n\\\nmd106 : active raid1 sdaf[1] sdae[0]\\n\\\n      5860391488 blocks super 1.2 [2/2] [UU]\\n\\\n      bitmap: 0/44 pages [0KB], 65536KB chunk\\n\"\n};\n\nTEST(HealthTest, ParseRaidStatus)\n{\n  for (size_t i = 0; i < demo_raid.size(); ++i) {\n    std::string tmp_path = \"/tmp/eos.health.XXXXXX\";\n    int fd = mkstemp((char*)tmp_path.c_str());\n    ASSERT_EQ(demo_raid[i].length(),\n              write(fd, demo_raid[i].data(), demo_raid[i].length()));\n    ASSERT_EQ(0, close(fd));\n    DiskHealth dh;\n    std::vector<std::string> devices {\"md1\", \"dummy_md0\", \"md125\", \"md96\"};\n\n    for (const auto& dev : devices) {\n      auto mstatus = dh.parse_mdstat(dev, tmp_path);\n\n      if (i == 0) {\n        if (dev == \"md1\") {\n          ASSERT_EQ(\"0\", mstatus[\"drives_failed\"]);\n          ASSERT_EQ(\"2\", mstatus[\"drives_healthy\"]);\n          ASSERT_EQ(\"2\", mstatus[\"drives_total\"]);\n          ASSERT_EQ(\"0\", mstatus[\"indicator\"]);\n          ASSERT_EQ(\"1\", mstatus[\"redundancy_factor\"]);\n          ASSERT_EQ(\"2/2 (+1)\", mstatus[\"summary\"]);\n        } else if (dev == \"dummy_md0\") {\n          ASSERT_EQ(\"no mdstat\", mstatus[\"summary\"]);\n        } else if (dev == \"md125\") {\n          ASSERT_EQ(\"0\", mstatus[\"drives_failed\"]);\n          ASSERT_EQ(\"6\", mstatus[\"drives_healthy\"]);\n          ASSERT_EQ(\"6\", mstatus[\"drives_total\"]);\n          ASSERT_EQ(\"0\", mstatus[\"indicator\"]);\n          ASSERT_EQ(\"2\", mstatus[\"redundancy_factor\"]);\n          ASSERT_EQ(\"6/6 (+2)\", mstatus[\"summary\"]);\n        }\n      } else if (i == 1) {\n        if (dev == \"md96\") {\n          ASSERT_EQ(\"no mdstat\", mstatus[\"summary\"]);\n        }\n      }\n    }\n\n    unlink(tmp_path.c_str());\n  }\n}\n"
  },
  {
    "path": "unit_tests/fst/HttpHandlerFstFileCacheTests.cc",
    "content": "// ----------------------------------------------------------------------\n// File: HttpHandlerFstFileCacheTests\n// Author: David Smith - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"TestEnv.hh\"\n#include \"fst/http/HttpHandlerFstFileCache.hh\"\n#include \"fst/XrdFstOfsFile.hh\"\n#include <XrdSfs/XrdSfsInterface.hh>\n#include <gtest/gtest.h>\n#include <unistd.h>\n#include <atomic>\n#include <memory>\n\nstd::atomic<uint64_t> nFakeClose = 0;\nstd::atomic<uint64_t> nFakeDest  = 0;\nstd::atomic<uint64_t> nCtr       = 0;\n\nclass FakeOfsFile : public eos::fst::XrdFstOfsFile\n{\npublic:\n  FakeOfsFile(const char* user, int MonID = 0) : XrdFstOfsFile(user, MonID) { }\n  virtual ~FakeOfsFile()\n  {\n    oh = 0;\n    nFakeDest++;\n  }\n  int close() override\n  {\n    nFakeClose++;\n    return 0;\n  }\n  int open(const char* fileName, XrdSfsFileOpenMode openMode,\n           mode_t createMode, const XrdSecEntity* client,\n           const char* opaque = 0) override\n  {\n    mFn = fileName;\n    return 0;\n  }\n\n  std::string mFn;\n};\n\nTEST(FstFileCacheTest, StoreFetch)\n{\n  XrdSfsFileOpenMode open_mode = 0;\n  eos::fst::HttpHandlerFstFileCache fc;\n  eos::fst::HttpHandlerFstFileCache::Entry entry;\n  std::unique_ptr<eos::fst::XrdFstOfsFile> fpP =\n    std::make_unique<FakeOfsFile>(\"\");\n  eos::fst::XrdFstOfsFile* fp = fpP.get();\n  const int dc1 = nFakeDest;\n  {\n    eos::fst::HttpHandlerFstFileCache::Key\n    cachekey(\"clientname\", \"/myurl1\", \"data=val1\", open_mode);\n    entry.set(cachekey, fp);\n    bool iret = fc.insert(entry);\n    ASSERT_TRUE(iret);\n    entry.clear();\n    entry = fc.remove(cachekey);\n    ASSERT_TRUE(entry);\n    ASSERT_TRUE(entry.getfp() == fp);\n  }\n  const int dc2 = nFakeDest;\n  ASSERT_TRUE(dc2 == dc1);\n}\n\nTEST(FstFileCacheTest, StoreFetchMultiSameFile)\n{\n  XrdSfsFileOpenMode open_mode = 0;\n  eos::fst::HttpHandlerFstFileCache fc;\n  eos::fst::HttpHandlerFstFileCache::Entry entry;\n  std::unique_ptr<eos::fst::XrdFstOfsFile> fp1P =\n    std::make_unique<FakeOfsFile>(\"\");\n  std::unique_ptr<eos::fst::XrdFstOfsFile> fp2P =\n    std::make_unique<FakeOfsFile>(\"\");\n  std::unique_ptr<eos::fst::XrdFstOfsFile> fp3P =\n    std::make_unique<FakeOfsFile>(\"\");\n  eos::fst::XrdFstOfsFile* fp1 = fp1P.get();\n  eos::fst::XrdFstOfsFile* fp2 = fp2P.get();\n  eos::fst::XrdFstOfsFile* fp3 = fp3P.get();\n  const int dc1 = nFakeDest;\n  {\n    eos::fst::HttpHandlerFstFileCache::Key\n    cachekey(\"clientname\", \"/myurl1\", \"data=val1\", open_mode);\n    bool iret;\n    entry.set(cachekey, fp1);\n    iret = fc.insert(entry);\n    ASSERT_TRUE(iret);\n    entry.set(cachekey, fp3);\n    iret = fc.insert(entry);\n    ASSERT_TRUE(iret);\n    entry.set(cachekey, fp2);\n    iret = fc.insert(entry);\n    ASSERT_TRUE(iret);\n    // fetch them with most recently used (inserted) first\n    entry.clear();\n    entry = fc.remove(cachekey);\n    ASSERT_TRUE(entry);\n    ASSERT_TRUE(entry.getfp() == fp2);\n    entry = fc.remove(cachekey);\n    ASSERT_TRUE(entry);\n    ASSERT_TRUE(entry.getfp() == fp3);\n    entry = fc.remove(cachekey);\n    ASSERT_TRUE(entry);\n    ASSERT_TRUE(entry.getfp() == fp1);\n  }\n  const int dc2 = nFakeDest;\n  ASSERT_TRUE(dc2 == dc1);\n}\n\nTEST(FstFileCacheTest, StoreFetchMultiDifferentFiles)\n{\n  XrdSfsFileOpenMode open_mode = 0;\n  eos::fst::HttpHandlerFstFileCache fc;\n  eos::fst::HttpHandlerFstFileCache::Entry entry;\n  std::unique_ptr<eos::fst::XrdFstOfsFile> fp1P =\n    std::make_unique<FakeOfsFile>(\"\");\n  std::unique_ptr<eos::fst::XrdFstOfsFile> fp2P =\n    std::make_unique<FakeOfsFile>(\"\");\n  std::unique_ptr<eos::fst::XrdFstOfsFile> fp3P =\n    std::make_unique<FakeOfsFile>(\"\");\n  std::unique_ptr<eos::fst::XrdFstOfsFile> fp4P =\n    std::make_unique<FakeOfsFile>(\"\");\n  std::unique_ptr<eos::fst::XrdFstOfsFile> fp5P =\n    std::make_unique<FakeOfsFile>(\"\");\n  std::unique_ptr<eos::fst::XrdFstOfsFile> fp6P =\n    std::make_unique<FakeOfsFile>(\"\");\n  eos::fst::XrdFstOfsFile* fp1 = fp1P.get();\n  eos::fst::XrdFstOfsFile* fp2 = fp2P.get();\n  eos::fst::XrdFstOfsFile* fp3 = fp3P.get();\n  eos::fst::XrdFstOfsFile* fp4 = fp4P.get();\n  eos::fst::XrdFstOfsFile* fp5 = fp5P.get();\n  eos::fst::XrdFstOfsFile* fp6 = fp6P.get();\n  const int dc1 = nFakeDest;\n  {\n    eos::fst::HttpHandlerFstFileCache::Key\n    cachekey1(\"clientname\", \"/myurl1\", \"data=val1\", open_mode);\n    eos::fst::HttpHandlerFstFileCache::Key\n    cachekey2(\"clientname\", \"/myurl2\", \"data=val1\", open_mode);\n    bool iret;\n    entry.set(cachekey1, fp1);\n    iret = fc.insert(entry);\n    ASSERT_TRUE(iret);\n    entry.set(cachekey1, fp3);\n    iret = fc.insert(entry);\n    ASSERT_TRUE(iret);\n    entry.set(cachekey2, fp2);\n    iret = fc.insert(entry);\n    ASSERT_TRUE(iret);\n    entry.set(cachekey1, fp4);\n    iret = fc.insert(entry);\n    ASSERT_TRUE(iret);\n    entry.set(cachekey2, fp5);\n    iret = fc.insert(entry);\n    ASSERT_TRUE(iret);\n    entry.set(cachekey2, fp6);\n    iret = fc.insert(entry);\n    ASSERT_TRUE(iret);\n    // fetch them with most recently used (inserted) first\n    entry.clear();\n    entry = fc.remove(cachekey1);\n    ASSERT_TRUE(entry);\n    ASSERT_TRUE(entry.getfp() == fp4);\n    entry = fc.remove(cachekey2);\n    ASSERT_TRUE(entry);\n    ASSERT_TRUE(entry.getfp() == fp6);\n    entry = fc.remove(cachekey2);\n    ASSERT_TRUE(entry);\n    ASSERT_TRUE(entry.getfp() == fp5);\n    entry = fc.remove(cachekey1);\n    ASSERT_TRUE(entry);\n    ASSERT_TRUE(entry.getfp() == fp3);\n    entry = fc.remove(cachekey2);\n    ASSERT_TRUE(entry);\n    ASSERT_TRUE(entry.getfp() == fp2);\n    entry = fc.remove(cachekey1);\n    ASSERT_TRUE(entry);\n    ASSERT_TRUE(entry.getfp() == fp1);\n  }\n  const int dc2 = nFakeDest;\n  ASSERT_TRUE(dc2 == dc1);\n}\n\nTEST(FstFileCacheTest, StoreFetchDifferentOpaque)\n{\n  XrdSfsFileOpenMode open_mode = 0;\n  eos::fst::HttpHandlerFstFileCache fc;\n  eos::fst::HttpHandlerFstFileCache::Entry entry;\n  std::unique_ptr<eos::fst::XrdFstOfsFile> fp1P =\n    std::make_unique<FakeOfsFile>(\"\");\n  std::unique_ptr<eos::fst::XrdFstOfsFile> fp2P =\n    std::make_unique<FakeOfsFile>(\"\");\n  std::unique_ptr<eos::fst::XrdFstOfsFile> fp3P =\n    std::make_unique<FakeOfsFile>(\"\");\n  eos::fst::XrdFstOfsFile* fp1 = fp1P.get();\n  eos::fst::XrdFstOfsFile* fp2 = fp2P.get();\n  eos::fst::XrdFstOfsFile* fp3 = fp3P.get();\n  const int dc1 = nFakeDest;\n  {\n    eos::fst::HttpHandlerFstFileCache::Key\n    cachekey1(\"clientname\", \"/myurl1\", \"data=val1\", open_mode);\n    eos::fst::HttpHandlerFstFileCache::Key\n    cachekey2(\"clientname\", \"/myurl1\", \"data=val2\", open_mode);\n    bool iret;\n    entry.set(cachekey1, fp1);\n    iret = fc.insert(entry);\n    ASSERT_TRUE(iret);\n    entry.set(cachekey1, fp3);\n    iret = fc.insert(entry);\n    ASSERT_TRUE(iret);\n    entry.set(cachekey2, fp2);\n    iret = fc.insert(entry);\n    ASSERT_TRUE(iret);\n    // fetch them with most recently used (inserted) first\n    entry.clear();\n    entry = fc.remove(cachekey2);\n    ASSERT_TRUE(entry);\n    ASSERT_TRUE(entry.getfp() == fp2);\n    entry = fc.remove(cachekey1);\n    ASSERT_TRUE(entry.getfp() == fp3);\n    entry = fc.remove(cachekey2);\n    ASSERT_FALSE(entry);\n    entry = fc.remove(cachekey1);\n    ASSERT_TRUE(entry);\n    ASSERT_TRUE(entry.getfp() == fp1);\n  }\n  const int dc2 = nFakeDest;\n  ASSERT_TRUE(dc2 == dc1);\n}\n\n\nTEST(FstFileCacheTest, CacheExpire)\n{\n  setenv(\"EOS_FST_HTTP_FHCACHE_IDLETIME\", \"0.2\", 1);\n  setenv(\"EOS_FST_HTTP_FHCACHE_IDLERES\", \"0.001\", 1);\n  XrdSfsFileOpenMode open_mode = 0;\n  eos::fst::HttpHandlerFstFileCache fc;\n  eos::fst::HttpHandlerFstFileCache::Entry entry;\n  eos::fst::XrdFstOfsFile* fp = 0;\n  {\n    eos::fst::HttpHandlerFstFileCache::Key\n    cachekey(\"clientname\", \"/myurl1\", \"data=val1\", open_mode);\n    bool iret;\n    fp = new FakeOfsFile{\"clientname\"};\n    const int cc1 = nFakeClose;\n    const int dc1 = nFakeDest;\n    entry.set(cachekey, fp);\n    iret = fc.insert(entry);\n    ASSERT_TRUE(iret);\n    // entry should timeout and be deleted\n    usleep(350 * 1E3);\n    entry = fc.remove(cachekey);\n    ASSERT_FALSE(entry);\n    const int cc2 = nFakeClose;\n    const int dc2 = nFakeDest;\n    ASSERT_TRUE(cc2 == cc1 + 1);\n    ASSERT_TRUE(dc2 == dc1 + 1);\n    // new OfsFile\n    fp = new FakeOfsFile{\"clientname\"};\n    entry.set(cachekey, fp);\n    iret = fc.insert(entry);\n    ASSERT_TRUE(iret);\n    // entry should not timeout\n    usleep(10 * 1E3);\n    entry = fc.remove(cachekey);\n    ASSERT_TRUE(entry);\n    ASSERT_TRUE(nFakeClose == cc2);\n    delete entry.getfp();\n    ASSERT_TRUE(nFakeClose == cc2);\n    ASSERT_TRUE(nFakeDest == dc2 + 1);\n  }\n  unsetenv(\"EOS_FST_HTTP_FHCACHE_IDLETIME\");\n  unsetenv(\"EOS_FST_HTTP_FHCACHE_IDLERES\");\n}\n\nTEST(FstFileCacheTest, ConcurrentCacheUse)\n{\n  constexpr int nth = 10;\n  constexpr int nloop = 5'000;\n  eos::fst::HttpHandlerFstFileCache fc;\n  std::vector<std::thread> tv;\n  const int cc1 = nFakeClose;\n  const int dc1 = nFakeDest;\n  tv.reserve(nth);\n\n  for (int i = 0; i < nth; i++) {\n    tv.emplace_back([&fc]() {\n      for (uint64_t l = 0; l < nloop; ++l) {\n        FakeOfsFile* fp = new FakeOfsFile{\"clientname\"};\n        char fnbuf[256];\n        const uint64_t n = nCtr++;\n        sprintf(fnbuf, \"/file%lu\", n % (nth / 3));\n        XrdSfsFileOpenMode open_mode = 0;\n        eos::fst::HttpHandlerFstFileCache::Key\n        cachekey(\"clientname\", fnbuf, \"data=val1\", open_mode);\n        eos::fst::HttpHandlerFstFileCache::Entry entry;\n        fp->open(fnbuf, open_mode, 0, 0, 0);\n        entry.set(cachekey, fp);\n        bool iret = fc.insert(entry);\n        ASSERT_TRUE(iret);\n        entry.clear();\n        fp = nullptr;\n        // we should get back an fp for a file with our\n        // filename, but not necessarily the same object\n        // we inserted\n        entry = fc.remove(cachekey);\n        ASSERT_TRUE(entry);\n        fp = (FakeOfsFile*)entry.getfp();\n        ASSERT_TRUE(fp->mFn == fnbuf);\n        fp->close();\n        delete fp;\n      }\n    });\n  }\n\n  for (int i = 0; i < nth; i++) {\n    tv[i].join();\n  }\n\n  ASSERT_TRUE(nFakeClose == cc1 + nth * nloop);\n  ASSERT_TRUE(nFakeDest == dc1 + nth * nloop);\n}\n\nTEST(FstFileCacheTest, CacheDestrTest)\n{\n  const int cc1 = nFakeClose;\n  const int dc1 = nFakeDest;\n  {\n    eos::fst::HttpHandlerFstFileCache fc;\n    FakeOfsFile* fp = new FakeOfsFile{\"clientname\"};\n    XrdSfsFileOpenMode open_mode = 0;\n    eos::fst::HttpHandlerFstFileCache::Key\n    cachekey(\"clientname\", \"/file\", \"data=val1\", open_mode);\n    eos::fst::HttpHandlerFstFileCache::Entry entry;\n    entry.set(cachekey, fp);\n    bool iret = fc.insert(entry);\n    ASSERT_TRUE(iret);\n    // we allow the FileCache object to go out of scope while\n    // containing an entry. We expect the etry to be closed\n    // and destroyed.\n  }\n  ASSERT_TRUE(nFakeClose == cc1 + 1);\n  ASSERT_TRUE(nFakeDest == dc1 + 1);\n}\n"
  },
  {
    "path": "unit_tests/fst/LoadTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: LoadTests.cc\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gmock/gmock.h\"\n#include \"gtest/gtest.h\"\n#define IN_TEST_HARNESS\n#include \"fst/Load.hh\"\n#undef IN_TEST_HARNESS\n#include <fstream>\n#include <cstdio>\n\nnamespace\n{\nvoid CreateCentOS7InputFile(const std::string& path)\n{\n  std::string test_input {\"  70       0 sdcs 124571 170 7182956 42686 22398716 9146198 607011590 8079794 0 1871023 8112828\\n\"\n                          \"  70       1 sdcs1 382 110 29037 172 339 152 7750 1180 0 415 1352\\n\"\n                          \"  70       2 sdcs2 478 0 30970 191 1622 812 273656 7489 0 1802 7675\\n\"\n                          \"  70       3 sdcs3 117464 59 6292546 35156 225861 1055753 23811784 829923 0 51053 865280\\n\"\n                          \"  70       4 sdcs4 479 0 8634 92 8517693 7663207 135817760 385225 0 262544 380895\\n\"\n                          \"  70       5 sdcs5 5617 1 818433 7056 9342414 426274 447100640 6491379 0 1285415 6493505\\n\"\n                          \"  70      16 sdct 51537 0 2853072 20365 0 0 0 0 0 8677 20337\\n\"\n                          \"   8       0 sda 24898 0 24000401 400573 43640 260124 15018512 651076 0 240307 1051343\\n\"\n                          \"   8       1 sda1 24668 0 23984057 399775 41573 8 12921152 56148 0 235511 455617\\n\"\n                          \"   8      16 sdb 24973 0 24009513 388694 43706 260128 15017720 646223 0 227788 1034598\\n\"\n                          \"   8      17 sdb1 24669 0 23986289 387029 41639 12 12920360 54514 0 222660 441224\\n\"\n                          \"   8      64 sde 24930 0 23999057 417676 43647 260127 15017128 651445 0 240014 1068802\\n\"\n                          \"   8      65 sde1 24655 0 23978681 415947 41580 11 12919768 63516 0 234896 479144\\n\"\n                          \"   8      32 sdc 26119 0 25056833 459243 43702 260127 15017672 640526 0 250839 1099511\\n\"\n                          \"   8      33 sdc1 25739 0 25024497 457203 41635 11 12920312 54773 0 245605 511720\\n\"\n                          \"   8     176 sdl 26168 0 25054537 427576 43671 260124 15017456 647736 0 244483 1074988\\n\"\n                          \"   8     177 sdl1 25816 0 25030825 426352 41604 8 12920096 64060 0 239437 490089\\n\"\n                          \"   8     112 sdh 25063 0 24005161 454962 43708 260124 15017816 664354 0 259738 1119001\\n\"\n                          \"   8     113 sdh1 24704 0 23981473 453574 41641 8 12920456 63896 0 254530 517157\\n\"\n                          \"  65     128 sdy 25086 0 24010561 397401 43657 260124 15015312 636254 0 220940 1033312\\n\"\n                          \"  65     129 sdy1 24649 0 23973129 395836 41590 8 12917952 51106 0 215757 446600\\n\"\n                          \"   8      48 sdd 25009 0 24003273 419821 43665 260127 15017440 648834 0 234443 1068330\\n\"\n                          \"   8      49 sdd1 24626 0 23975153 417569 41598 11 12920080 56601 0 229143 473847\\n\"\n                          \"  67      64 sdba 26110 0 25046537 418532 43654 260127 15017296 637665 0 223418 1055890\\n\"\n                          \"  67      65 sdba1 25798 0 25028737 417131 41587 11 12919936 48074 0 218215 464898\\n\"\n                          \"  67      96 sdbc 25046 0 24005097 389485 43671 260124 15017456 630291 0 210333 1019466\\n\"\n                          \"  67      97 sdbc1 24687 0 23981409 388145 41604 8 12920096 48373 0 205315 436210\\n\"\n                          \"   8      80 sdf 26055 0 25047033 439238 43649 260127 15017144 647190 0 240421 1086108\\n\"\n                          \"   8      81 sdf1 25751 0 25028305 437977 41582 11 12919784 55055 0 235301 492716\\n\"\n                          \"  67     112 sdbd 25004 0 24002121 392144 43646 260124 15017032 638482 0 212397 1030326\\n\"\n                          \"  67     113 sdbd1 24689 0 23979369 390699 41579 8 12919672 49581 0 207233 439982\\n\"\n                          \"   8     128 sdi 26157 0 25061441 412592 43687 260124 15017640 655536 0 227463 1067810\\n\"\n                          \"   8     129 sdi1 25812 0 25036313 411120 41620 8 12920280 55887 0 222252 466689\\n\"\n                          \"  67      32 sday 24991 0 24000977 439389 43685 260127 15017488 632277 0 237513 1071356\\n\"\n                          \"  67      33 sday1 24683 0 23982065 438059 41618 11 12920128 49938 0 232457 487689\\n\"\n                          \"  65      32 sds 24929 0 23999041 404269 43698 260123 15017776 632673 0 219102 1036640\\n\"\n                          \"  65      33 sds1 24654 0 23978665 402404 41631 7 12920416 48588 0 213945 450691\\n\"\n                          \"   8      96 sdg 25008 0 24002649 588711 43662 260124 15017328 663959 0 320216 1252336\\n\"\n                          \"   8      97 sdg1 24699 0 23980737 587187 41595 8 12919968 58339 0 315018 645194\\n\"\n                          \"  67      16 sdax 26175 0 25055665 402542 43700 260127 15017552 644708 0 222536 1046921\\n\"\n                          \"  67      17 sdax1 25816 0 25030825 401179 41633 11 12920192 56272 0 217401 457122\\n\"\n                          \"   8     240 sdp 25025 0 24006121 407314 43658 260124 15017352 647138 0 221652 1054107\\n\"\n                          \"   8     241 sdp1 24675 0 23981057 405845 41591 8 12919992 49647 0 216445 455149\\n\"\n                          \"   8     160 sdk 25039 0 24015121 421186 43652 260127 15017384 664389 0 235533 1085207\\n\"\n                          \"   8     161 sdk1 24636 0 23975137 419088 41585 11 12920024 57512 0 230137 476235\"};\n  std::ofstream ostream(path);\n  ostream << test_input;\n}\n\nvoid CreateCentOS8InputFile(const std::string& path)\n{\n  std::string test_input {\"   8       0 sda 62814337 524435 4646361308 90413178 210006603 133937217 4752049454 123891868 0 88396457 144498889 0 0 0 0\\n\"\n                          \"   8       1 sda1 44357 160 3875270 20003 582 33 8791 5948 0 19549 13843 0 0 0 0\\n\"\n                          \"   8       2 sda2 55651 98 10212258 74098 3545 1567 822360 25536 0 40697 80694 0 0 0 0\\n\"\n                          \"   8       3 sda3 21694274 458256 1383181930 22325828 1009165 3572894 108689320 3562076 0 8763302 19768104 0 0 0 0\\n\"\n                          \"   8       4 sda4 2633369 2408 23361866 869268 144471520 126339714 2185019688 52003183 0 49234388 17950954 0 0 0 0\\n\"\n                          \"   8       5 sda5 38382439 63513 3225666938 67121603 54725758 4023009 2457509295 67917046 0 38529063 106676223 0 0 0 0\\n\"\n                          \"   8      16 sdb 280004 0 12825118 101274 0 0 0 0 0 63540 17551 0 0 0 0\\n\"\n                          \"  65     240 sdaf 4835915 71763 5288681580 213862896 11211921 357170 10782583736 2916512244 0 54571161 3122380621 0 0 0 0\\n\"\n                          \"  65     241 sdaf1 4665421 71763 5288491985 213657950 11211893 357170 10782583736 2916512128 0 54348315 3122281218 0 0 0 0\\n\"\n                          \"  67       0 sdaw 5132690 72110 5664992628 229793587 11628206 319287 11219297672 3239071197 0 58688049 3460542577 0 0 0 0\\n\"\n                          \"  67       1 sdaw1 4962120 72110 5664802969 229590667 11628185 319287 11219297672 3239071093 0 58468389 3460444505 0 0 0 0\\n\"\n                          \"  65     208 sdad 26117135 74997 28147876639 1065616901 57907254 2875406 59112697693 2610814266 0 260350218 3634551765 0 0 0 0\\n\"\n                          \"  65     209 sdad1 25946626 74997 28147687156 1065340405 57850821 2875406 59112697693 2610111396 3 259511576 3633701039 0 0 0 0\\n\"\n                          \"   8     208 sdn 17334830 70516 18458122564 560353447 48941970 2436967 49802632536 2823531355 0 193566784 3350896123 0 0 0 0\\n\"\n                          \"   8     209 sdn1 17164413 70516 18457933017 560106935 48939128 2436967 49802632536 2823452930 0 193329473 3350676821 0 0 0 0\\n\"\n                          \"   8      32 sdc 22108273 66528 23280243569 743613786 53533892 2717832 54317033842 2541659795 0 219933553 3247514864 0 0 0 0\\n\"\n                          \"   8      33 sdc1 21937698 66528 23280054081 743332159 53529704 2717832 54317033842 2541586060 0 219693757 3247266128 0 0 0 0\\n\"\n                          \"  66     160 sdaq 8094319 81223 10852891035 461439208 15135086 447325 16835996519 3361101232 0 102462698 3811146326 0 0 0 0\\n\"\n                          \"  66     161 sdaq1 7923786 81223 10852701472 461195871 15084443 447325 16835996519 3360501796 0 101676359 3810430379 0 0 0 0\\n\"\n                          \"  66      96 sdam 4817434 62698 5295964796 194913778 11357679 318771 10952944704 3060699399 0 56002536 3247589757 0 0 0 0\\n\"\n                          \"  66      97 sdam1 4646991 62698 5295775441 194710559 11357654 318771 10952944704 3060699092 0 55784529 3247491599 0 0 0 0\\n\"\n                          \"  68      48 sdbp 4911712 73960 5311779060 214483981 12516969 325938 12150744688 3470401361 0 58675202 3676212304 0 0 0 0\\n\"\n                          \"  68      49 sdbp1 4741209 73960 5311589897 214278578 12516939 325938 12150744688 3470400970 0 58458296 3676111101 0 0 0 0\\n\"\n                          \"  66       0 sdag 4835854 66406 5314653772 190893832 11240706 318482 10844132432 2923637639 0 54370426 3106530380 0 0 0 0\\n\"\n                          \"  66       1 sdag1 4665378 66406 5314464553 190691599 11240683 318482 10844132432 2923637381 0 54150225 3106432951 0 0 0 0\\n\"\n                          \"  65       0 sdq 26246305 54194 28111458331 808617361 52603906 3000779 53274265737 2100604593 0 227312235 2869914567 0 0 0 0\\n\"\n                          \"  65       1 sdq1 26076215 54194 28111269064 808357702 52600398 3000779 53274265737 2100559853 0 227071761 2869715833 0 0 0 0\\n\"\n                          \"  66     128 sdao 4550123 59510 4933290700 156350825 11537898 333383 11097470448 2997782846 0 53945735 3146164821 0 0 0 0\\n\"\n                          \"  66     129 sdao1 4379645 59510 4933101481 156151412 11537878 333383 11097470448 2997782705 0 53723385 3146070643 0 0 0 0\\n\"\n                          \"  68     144 sdbv 5164776 73942 5615039540 218744601 12755370 321120 12349366672 3637308163 0 60468784 3847162423 0 0 0 0\"};\n  std::ofstream ostream(path);\n  ostream << test_input;\n}\n}\n\nTEST(Load, DiskStats)\n{\n  eos::fst::DiskStat ds_unkown;\n  ASSERT_FALSE(ds_unkown.Measure(\"/unkown/path\"));\n  time_t s = time(NULL);\n  // Check CentOS7 file format\n  std::string tmp_path_c7 = \"/tmp/.c7.\";\n  tmp_path_c7 += std::to_string(s);\n  CreateCentOS7InputFile(tmp_path_c7);\n  eos::fst::DiskStat ds_c7;\n  ASSERT_TRUE(ds_c7.Measure(tmp_path_c7));\n  ASSERT_TRUE(\"24898\" == ds_c7.values_t1[\"sda\"][\"readReq\"]);\n  ASSERT_EQ(0, unlink(tmp_path_c7.c_str()));\n  // Check CentOS8 file format\n  std::string tmp_path_c8 = \"/tmp/.c8.\";\n  tmp_path_c8 += std::to_string(s);\n  CreateCentOS8InputFile(tmp_path_c8);\n  eos::fst::DiskStat ds_c8;\n  ASSERT_TRUE(ds_c8.Measure(tmp_path_c8));\n  ASSERT_TRUE(\"5132690\" == ds_c8.values_t1[\"sdaw\"][\"readReq\"]);\n  ASSERT_EQ(0, unlink(tmp_path_c8.c_str()));\n}\n"
  },
  {
    "path": "unit_tests/fst/MonitorVarPartitionTest.cc",
    "content": "//------------------------------------------------------------------------------\n// File: MonitorVarPartitionTest.cc\n// Author: Jozsef Makai <jmakai@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"Namespace.hh\"\n#include \"TestEnv.hh\"\n#include \"common/RWMutex.hh\"\n#include \"common/FileSystem.hh\"\n#include \"fst/storage/MonitorVarPartition.hh\"\n\neos::fst::test::GTest_Logger gLogger(false);\n\nEOSFSTTEST_NAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! Mock class implementing only relevant methods related to unit testing\n//------------------------------------------------------------------------------\nstruct MockFileSystem {\n  eos::common::ConfigStatus status;\n\n  MockFileSystem() : status(eos::common::ConfigStatus::kRW) {}\n\n  void SetString(const std::string& key, const std::string& val)\n  {\n    this->status = eos::common::FileSystem::GetConfigStatusFromString(val.c_str());\n  }\n\n  eos::common::ConfigStatus GetConfigStatus(bool cached = false)\n  {\n    return this->status;\n  }\n};\n\nusing VarMonitorT = eos::fst::MonitorVarPartition<std::vector<MockFileSystem*>>;\n\n//------------------------------------------------------------------------------\n//! Class MonitorVarPartitionTest\n//------------------------------------------------------------------------------\nclass MonitorVarPartitionTest : public ::testing::Test\n{\npublic:\n  static constexpr std::int32_t mMonitorInterval = 1;\n  std::ofstream fill;\n  VarMonitorT monitor;\n  std::thread monitor_thread;\n  eos::common::RWMutex mFsMutex;\n  std::vector<MockFileSystem*> fsVector;\n\n  //----------------------------------------------------------------------------\n  //! Method starting the monitoring thread\n  //----------------------------------------------------------------------------\n  static void StartFstPartitionMonitor(MonitorVarPartitionTest* storage)\n  {\n    storage->monitor.Monitor(storage->fsVector, storage->mFsMutex);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  MonitorVarPartitionTest():\n    monitor(10.0, MonitorVarPartitionTest::mMonitorInterval, \"/mnt/var_test/\")\n  {}\n\n  virtual void SetUp() override\n  {\n    // Initialize partition\n    (void) !system(\"mkdir -p /mnt/var_test\");\n    (void) ! system(\"mount -t tmpfs -o size=100m tmpfs /mnt/var_test/\");\n    // Add few fileSystems in the vector\n    this->fsVector.push_back(new MockFileSystem());\n    this->fsVector.push_back(new MockFileSystem());\n    this->fsVector.push_back(new MockFileSystem());\n    this->fsVector.push_back(new MockFileSystem());\n    fill.open(\"/mnt/var_test/fill.temp\");\n    // Start monitoring\n    this->monitor_thread =\n      std::thread(MonitorVarPartitionTest::StartFstPartitionMonitor, this);\n  }\n\n  virtual void TearDown() override\n  {\n    // Clean resources\n    delete this->fsVector[0];\n    delete this->fsVector[1];\n    delete this->fsVector[2];\n    delete this->fsVector[3];\n    // Stop monitoring\n    this->monitor.StopMonitoring();\n    this->monitor_thread.join();\n    (void) !system(\"umount /mnt/var_test/\");\n    (void) !system(\"rmdir /mnt/var_test/\");\n  }\n};\n\n//------------------------------------------------------------------------------\n// MonitorVarPartition Test\n//------------------------------------------------------------------------------\nTEST_F(MonitorVarPartitionTest, MonitorVarPartition)\n{\n  // Fill partition to more than 90%\n  GLOG << \"Filling partition to 90%\" << std::endl;\n  std::string megabyte_line(1024 * 1024, 'a');\n\n  for (int i = 1; i <= 90; i++) {\n    fill << megabyte_line << std::endl;\n  }\n\n  // Wait and check\n  usleep(mMonitorInterval * 1000 * 1000);\n  mFsMutex.LockRead();\n\n  for (auto fs = fsVector.begin(); fs != fsVector.end(); ++fs) {\n    ASSERT_EQ((*fs)->GetConfigStatus(), eos::common::ConfigStatus::kRO);\n  }\n\n  mFsMutex.UnLockRead();\n  // Setting status of filesystems to RW\n  GLOG << \"Setting status to RW -- should revert to RO\" << std::endl;\n  mFsMutex.LockWrite();\n\n  for (auto fs = fsVector.begin(); fs != fsVector.end(); ++fs) {\n    (*fs)->SetString(\"configstatus\", \"rw\");\n  }\n\n  mFsMutex.UnLockWrite();\n  // Check if status has returned to read-only\n  usleep(mMonitorInterval * 1000 * 1000);\n  mFsMutex.LockRead();\n\n  for (auto fs = fsVector.begin(); fs != fsVector.end(); ++fs) {\n    ASSERT_EQ((*fs)->GetConfigStatus(), eos::common::ConfigStatus::kRO);\n  }\n\n  mFsMutex.UnLockRead();\n  // Close and delete file\n  GLOG << \"Deleting file: /mnt/var_test/fill.temp\" << std::endl;\n  fill.close();\n  (void) !system(\"rm /mnt/var_test/fill.temp\");\n  // Setting status of filesystems to RW\n  GLOG << \"Setting status to RW -- should stay at RW\" << std::endl;\n  mFsMutex.LockWrite();\n\n  for (auto fs = fsVector.begin(); fs != fsVector.end(); ++fs) {\n    (*fs)->SetString(\"configstatus\", \"rw\");\n  }\n\n  mFsMutex.UnLockWrite();\n  // Check if status remains as read/write\n  usleep(mMonitorInterval * 1000 * 1000);\n  mFsMutex.LockRead();\n\n  for (auto fs = fsVector.begin(); fs != fsVector.end(); ++fs) {\n    ASSERT_EQ((*fs)->GetConfigStatus(), eos::common::ConfigStatus::kRW);\n  }\n\n  mFsMutex.UnLockRead();\n}\n\nEOSFSTTEST_NAMESPACE_END\n"
  },
  {
    "path": "unit_tests/fst/Namespace.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file Namespace.hh\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//! @brief eos::fst::test namespace definition\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#define EOSFSTTEST_NAMESPACE_BEGIN namespace eos{  namespace fst { namespace test {\n#define EOSFSTTEST_NAMESPACE_END }}}\n"
  },
  {
    "path": "unit_tests/fst/NfsIoTests.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file NfsIoTests.cc\n//! @author Robert-Paul Pasca - CERN\n//! @brief Unit tests for NfsIo class\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#define IN_TEST_HARNESS\n#ifdef HAVE_NFS\n#include \"fst/io/nfs/NfsIo.hh\"\n#endif\n#undef IN_TEST_HARNESS\n#include \"TestEnv.hh\"\n#include <string>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <errno.h>\n\n#ifdef HAVE_NFS\n\n//------------------------------------------------------------------------------\n// Test fixture for NfsIo tests\n//------------------------------------------------------------------------------\nclass NfsIoTest : public ::testing::Test\n{\nprotected:\n  void SetUp() override\n  {\n    mTestDir = \"/tmp/nfsio_test_\" + std::to_string(getpid());\n    ASSERT_EQ(0, mkdir(mTestDir.c_str(), 0755));\n    \n    mTestFile = mTestDir + \"/test_file.dat\";\n    mTestPath = \"nfs://\" + mTestFile;\n  }\n\n  void TearDown() override\n  {\n    unlink(mTestFile.c_str());\n    std::string xattr_file = mTestDir + \"/.test_file.dat.xattr\";\n    unlink(xattr_file.c_str());\n    rmdir(mTestDir.c_str());\n  }\n\n  std::string mTestDir;\n  std::string mTestFile;\n  std::string mTestPath;\n};\n\n//------------------------------------------------------------------------------\n// Test NfsIo constructor and basic setup\n//------------------------------------------------------------------------------\nTEST_F(NfsIoTest, Constructor)\n{\n  eos::fst::NfsIo nfsio(mTestPath, nullptr, nullptr);\n\n  char buffer[10];\n  EXPECT_EQ(-1, nfsio.fileRead(0, buffer, sizeof(buffer)));\n  EXPECT_EQ(EBADF, errno);\n}\n\n//------------------------------------------------------------------------------\n// Test file creation and opening\n//------------------------------------------------------------------------------\nTEST_F(NfsIoTest, FileOpen)\n{\n  eos::fst::NfsIo nfsio(mTestPath, nullptr, nullptr);\n  \n  int result = nfsio.fileOpen(SFS_O_CREAT | SFS_O_RDWR, 0644, \"\", 0);\n  EXPECT_EQ(SFS_OK, result);\n  \n  std::string test_data = \"test\";\n  EXPECT_GT(nfsio.fileWrite(0, test_data.c_str(), test_data.size()), 0);\n  EXPECT_EQ(0, nfsio.fileClose());\n  \n  char buffer[10];\n  EXPECT_EQ(-1, nfsio.fileRead(0, buffer, sizeof(buffer)));\n  EXPECT_EQ(EBADF, errno);\n}\n\n//------------------------------------------------------------------------------\n// Test file write operations\n//------------------------------------------------------------------------------\nTEST_F(NfsIoTest, FileWrite)\n{\n  eos::fst::NfsIo nfsio(mTestPath, nullptr, nullptr);\n  ASSERT_EQ(SFS_OK, nfsio.fileOpen(SFS_O_CREAT | SFS_O_RDWR, 0644, \"\", 0));\n  \n  std::string test_data = \"Hello, NFS World!\";\n  int64_t bytes_written = nfsio.fileWrite(0, test_data.c_str(), test_data.size());\n  \n  EXPECT_EQ(test_data.size(), bytes_written);\n  nfsio.fileClose();\n  \n  struct stat st;\n  ASSERT_EQ(0, stat(mTestFile.c_str(), &st));\n  EXPECT_EQ(test_data.size(), st.st_size);\n}\n\n//------------------------------------------------------------------------------\n// Test file read operations\n//------------------------------------------------------------------------------\nTEST_F(NfsIoTest, FileRead)\n{\n  std::string test_data = \"Test string for NFS read\";\n\n  eos::fst::NfsIo nfsio_write(mTestPath, nullptr, nullptr);\n  ASSERT_EQ(SFS_OK, nfsio_write.fileOpen(SFS_O_CREAT | SFS_O_RDWR, 0644, \"\", 0));\n  ASSERT_EQ(test_data.size(), nfsio_write.fileWrite(0, test_data.c_str(), test_data.size()));\n  nfsio_write.fileClose();\n  \n  eos::fst::NfsIo nfsio_read(mTestPath, nullptr, nullptr);\n  ASSERT_EQ(SFS_OK, nfsio_read.fileOpen(SFS_O_RDONLY, 0, \"\", 0));\n  \n  char buffer[1024];\n  int64_t bytes_read = nfsio_read.fileRead(0, buffer, sizeof(buffer));\n  \n  EXPECT_EQ(test_data.size(), bytes_read);\n  EXPECT_EQ(0, memcmp(test_data.c_str(), buffer, test_data.size()));\n  nfsio_read.fileClose();\n}\n\n//------------------------------------------------------------------------------\n// Test file stat operations\n//------------------------------------------------------------------------------\nTEST_F(NfsIoTest, FileStat)\n{\n  std::string test_data = \"Test file for nfs stat\";\n  \n  eos::fst::NfsIo nfsio(mTestPath, nullptr, nullptr);\n  ASSERT_EQ(SFS_OK, nfsio.fileOpen(SFS_O_CREAT | SFS_O_RDWR, 0644, \"\", 0));\n  ASSERT_EQ(test_data.size(), nfsio.fileWrite(0, test_data.c_str(), test_data.size()));\n  \n  struct stat st;\n  EXPECT_EQ(0, nfsio.fileStat(&st));\n  EXPECT_EQ(test_data.size(), st.st_size);\n  \n  nfsio.fileClose();\n}\n\n//------------------------------------------------------------------------------\n// Test file truncate operations\n//------------------------------------------------------------------------------\nTEST_F(NfsIoTest, FileTruncate)\n{\n  std::string test_data = \"This is a longer test string for nfs truncation\";\n  eos::fst::NfsIo nfsio(mTestPath, nullptr, nullptr);\n  ASSERT_EQ(SFS_OK, nfsio.fileOpen(SFS_O_CREAT | SFS_O_RDWR, 0644, \"\", 0));\n  ASSERT_EQ(test_data.size(), nfsio.fileWrite(0, test_data.c_str(), test_data.size()));\n  \n  // Sync the file before truncating to ensure data is written\n  EXPECT_EQ(0, nfsio.fileSync());\n  EXPECT_EQ(0, nfsio.fileTruncate(10));\n\n  struct stat st;\n  ASSERT_EQ(0, nfsio.fileStat(&st));\n  EXPECT_EQ(10, st.st_size);\n  nfsio.fileClose();\n}\n\n//------------------------------------------------------------------------------\n// Test attribute operations\n//------------------------------------------------------------------------------\nTEST_F(NfsIoTest, AttributeOperations)\n{\n  eos::fst::NfsIo nfsio(mTestPath, nullptr, nullptr);\n  ASSERT_EQ(SFS_OK, nfsio.fileOpen(SFS_O_CREAT | SFS_O_RDWR, 0644, \"\", 0));\n  \n  // Test setting an attribute\n  std::string attr_name = \"user.test.key\";\n  std::string attr_value = \"test_value_123\";\n  \n  EXPECT_EQ(0, nfsio.attrSet(attr_name, attr_value));\n  \n  // Test getting the attribute\n  std::string retrieved_value;\n  EXPECT_EQ(0, nfsio.attrGet(attr_name, retrieved_value));\n  EXPECT_EQ(attr_value, retrieved_value);\n  \n  // Test listing attributes\n  std::vector<std::string> attr_list;\n  EXPECT_EQ(0, nfsio.attrList(attr_list));\n  EXPECT_TRUE(std::find(attr_list.begin(), attr_list.end(), attr_name) != attr_list.end());\n  \n  // Test deleting an attribute\n  EXPECT_EQ(0, nfsio.attrDelete(attr_name.c_str()));\n  EXPECT_NE(0, nfsio.attrGet(attr_name, retrieved_value));\n  EXPECT_EQ(ENOATTR, errno);\n  \n  nfsio.fileClose();\n}\n\n//------------------------------------------------------------------------------\n// Test error conditions\n//------------------------------------------------------------------------------\nTEST_F(NfsIoTest, ErrorConditions)\n{\n  eos::fst::NfsIo nfsio(mTestPath, nullptr, nullptr);\n  \n  char buffer[100];\n  EXPECT_EQ(-1, nfsio.fileRead(0, buffer, sizeof(buffer)));\n  EXPECT_EQ(EBADF, errno);\n  \n  std::string test_data = \"test\";\n  EXPECT_EQ(-1, nfsio.fileWrite(0, test_data.c_str(), test_data.size()));\n  EXPECT_EQ(EBADF, errno);\n  \n  ASSERT_EQ(SFS_OK, nfsio.fileOpen(SFS_O_CREAT | SFS_O_RDWR, 0644, \"\", 0));\n  EXPECT_EQ(-1, nfsio.fileWrite(100, test_data.c_str(), test_data.size()));\n  EXPECT_EQ(ENOTSUP, errno);\n  \n  nfsio.fileClose();\n}\n\n//------------------------------------------------------------------------------\n// Test path parsing\n//------------------------------------------------------------------------------\nTEST_F(NfsIoTest, PathParsing)\n{\n  // nfs:// prefix removal\n  std::string nfs_path = \"nfs:///tmp/test/file.dat\";\n  eos::fst::NfsIo nfsio(nfs_path, nullptr, nullptr);\n  \n  EXPECT_EQ(SFS_OK, nfsio.fileOpen(SFS_O_CREAT | SFS_O_RDWR, 0644, \"\", 0));\n  nfsio.fileClose();\n  \n  unlink(\"/tmp/test/file.dat\");\n  rmdir(\"/tmp/test\");\n}\n\n//------------------------------------------------------------------------------\n// Test sequential write requirement\n//------------------------------------------------------------------------------\nTEST_F(NfsIoTest, SequentialWriteRequirement)\n{\n  eos::fst::NfsIo nfsio(mTestPath, nullptr, nullptr);\n  ASSERT_EQ(SFS_OK, nfsio.fileOpen(SFS_O_CREAT | SFS_O_RDWR, 0644, \"\", 0));\n  \n  std::string data1 = \"First chunk\";\n  std::string data2 = \"Second chunk\";\n  \n  EXPECT_EQ(data1.size(), nfsio.fileWrite(0, data1.c_str(), data1.size()));\n  EXPECT_EQ(data2.size(), nfsio.fileWrite(data1.size(), data2.c_str(), data2.size()));\n  \n  // Non-sequential write should fail\n  EXPECT_EQ(-1, nfsio.fileWrite(0, data1.c_str(), data1.size()));\n  EXPECT_EQ(ENOTSUP, errno);\n  \n  nfsio.fileClose();\n}\n\n#endif // HAVE_NFS\n\n//------------------------------------------------------------------------------\n// Test that runs when NFS is not available\n//------------------------------------------------------------------------------\nTEST(NfsIoTestNoNfs, UnavailableWhenNoNfs)\n{\n#ifndef HAVE_NFS\n  GTEST_SKIP() << \"NFS support not compiled in, test skipped\";\n#else\n  GTEST_SKIP() << \"NFS support available, this test is for when NFS is not available\";\n#endif\n}\n"
  },
  {
    "path": "unit_tests/fst/ResponseCollectorTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ResponseCollectorTests.hh\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"fst/io/xrd/ResponseCollector.hh\"\n#include <thread>\n//------------------------------------------------------------------------------\n// Test successful run\n//------------------------------------------------------------------------------\nTEST(ResponseCollector, SuccessfulRun)\n{\n  eos::fst::ResponseCollector collector;\n  std::list<std::promise<XrdCl::XRootDStatus>> lst_promises;\n\n  for (int i = 0; i < 100; ++i) {\n    auto& promise = lst_promises.emplace_back();\n    collector.CollectFuture(promise.get_future());\n  }\n\n  for (auto& promise : lst_promises) {\n    promise.set_value(XrdCl::XRootDStatus());\n  }\n\n  ASSERT_TRUE(collector.CheckResponses(true));\n}\n\n//------------------------------------------------------------------------------\n// Test run with failures\n//------------------------------------------------------------------------------\nTEST(ResponseCollector, FailedRun)\n{\n  int count = 0;\n  eos::fst::ResponseCollector collector;\n  std::list<std::promise<XrdCl::XRootDStatus>> lst_promises;\n\n  for (int i = 0; i < 100; ++i) {\n    auto& promise = lst_promises.emplace_back();\n    collector.CollectFuture(promise.get_future());\n  }\n\n  for (auto& promise : lst_promises) {\n    ++count;\n\n    if (count % 10 == 0) {\n      promise.set_value(XrdCl::XRootDStatus(XrdCl::stError,\n                                            XrdCl::errUnknown, EINVAL));\n    } else {\n      promise.set_value(XrdCl::XRootDStatus());\n    }\n  }\n\n  ASSERT_FALSE(collector.CheckResponses(true));\n}\n\n//------------------------------------------------------------------------------\n// Test collection of partial successful responses\n//------------------------------------------------------------------------------\nTEST(ResponseCollector, PartialSuccessfulRun)\n{\n  eos::fst::ResponseCollector collector;\n  uint32_t num_requests = 100;\n  std::list<std::promise<XrdCl::XRootDStatus>> lst_promises;\n\n  for (unsigned int i = 0; i < num_requests; ++i) {\n    auto& promise = lst_promises.emplace_back();\n    collector.CollectFuture(promise.get_future());\n  }\n\n  // Set reponses for the first half\n  unsigned int count = 0;\n\n  for (auto& promise : lst_promises) {\n    ++count;\n\n    if (count < lst_promises.size() / 2) {\n      promise.set_value(XrdCl::XRootDStatus());\n    }\n  }\n\n  std::thread t([&]() {\n    unsigned int c = 0;\n    std::this_thread::sleep_for(std::chrono::milliseconds(500));\n\n    for (auto& promise : lst_promises) {\n      ++c;\n\n      if (c >= lst_promises.size() / 2) {\n        promise.set_value(XrdCl::XRootDStatus());\n      }\n    }\n  });\n  // First half should all be successful, no waiting\n  ASSERT_TRUE(collector.CheckResponses(false));\n  // Second half successful, wait for all\n  ASSERT_TRUE(collector.CheckResponses(true));\n  t.join();\n}\n\n//------------------------------------------------------------------------------\n// Test collection of partial failed responses\n//----------------------------------------=--------------------------------------\nTEST(ResponseCollector, PartialFailedRun)\n{\n  eos::fst::ResponseCollector collector;\n  uint32_t num_requests = 100;\n  std::list<std::promise<XrdCl::XRootDStatus>> lst_promises;\n\n  for (unsigned int i = 0; i < num_requests; ++i) {\n    auto& promise = lst_promises.emplace_back();\n    collector.CollectFuture(promise.get_future());\n  }\n\n  // Set reponses for the first half\n  unsigned int count = 0;\n\n  for (auto& promise : lst_promises) {\n    ++count;\n\n    if (count < lst_promises.size() / 2) {\n      promise.set_value(XrdCl::XRootDStatus());\n    }\n  }\n\n  std::thread t([&]() {\n    unsigned int c = 0;\n    std::this_thread::sleep_for(std::chrono::milliseconds(500));\n\n    for (auto& promise : lst_promises) {\n      ++c;\n\n      if (c >= lst_promises.size() / 2) {\n        if (c % 2 == 0) {\n          promise.set_value(XrdCl::XRootDStatus(XrdCl::stError,\n                                                XrdCl::errUnknown, EINVAL));\n        } else {\n          promise.set_value(XrdCl::XRootDStatus());\n        }\n      }\n    }\n  });\n  // First half should all be successful, no waiting\n  ASSERT_TRUE(collector.CheckResponses(false, num_requests));\n  // Second half has errors, wait for all\n  ASSERT_FALSE(collector.CheckResponses(true, num_requests));\n  t.join();\n}\n\n//------------------------------------------------------------------------------\n// Test collection when max_pending applies\n//----------------------------------------=--------------------------------------\nTEST(ResponseCollector, MaxPending)\n{\n  eos::fst::ResponseCollector collector;\n  uint32_t num_requests = 50;\n  uint32_t max_pending = 10;\n  std::list<std::promise<XrdCl::XRootDStatus>> lst_promises;\n\n  for (unsigned int i = 0; i < num_requests; ++i) {\n    auto& promise = lst_promises.emplace_back();\n    collector.CollectFuture(promise.get_future());\n  }\n\n  std::thread t([&]() {\n    unsigned int c = 0;\n    std::this_thread::sleep_for(std::chrono::milliseconds(500));\n\n    for (auto& promise : lst_promises) {\n      ++c;\n\n      if (c <= (num_requests - (max_pending / 2))) {\n        promise.set_value(XrdCl::XRootDStatus());\n      } else {\n        static bool one_off = true;\n\n        if (one_off) {\n          std::this_thread::sleep_for(std::chrono::milliseconds(500));\n          one_off = false;\n        }\n\n        if (c != num_requests) {\n          promise.set_value(XrdCl::XRootDStatus());\n        } else {\n          promise.set_value(XrdCl::XRootDStatus(XrdCl::stError,\n                                                XrdCl::errUnknown, EINVAL));\n        }\n      }\n    }\n  });\n  // First batch should all be successful - 45 replies\n  ASSERT_TRUE(collector.CheckResponses(false, max_pending));\n  ASSERT_EQ(max_pending / 2, collector.GetNumResponses());\n  // Second batch has an error at the last reply\n  ASSERT_FALSE(collector.CheckResponses(true, max_pending));\n  t.join();\n}\n"
  },
  {
    "path": "unit_tests/fst/ScanDirTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ScanDirTests.cc\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gmock/gmock.h\"\n#include \"gtest/gtest.h\"\n#define IN_TEST_HARNESS\n#include \"fst/ScanDir.hh\"\n#undef IN_TEST_HARNESS\n#include \"fst/Load.hh\"\n#include \"fst/utils/ScanRate.hh\"\n#include \"common/Constants.hh\"\n#include \"unit_tests/fst/TmpDirTree.hh\"\n//------------------------------------------------------------------------------\n// Helper method to convert current timestamp to string microseconds\n// representation\n//------------------------------------------------------------------------------\nint64_t GetTimestampSec(eos::common::SteadyClock& clock)\n{\n  using namespace std::chrono;\n  return duration_cast<seconds>\n         (clock.GetTime().time_since_epoch()).count();\n}\n\n//------------------------------------------------------------------------------\n// MockLoad class\n//------------------------------------------------------------------------------\nclass MockLoad: public eos::fst::Load\n{\npublic:\n  MOCK_METHOD2(GetDiskRate, double(const char*, const char*));\n};\n\nTEST(ScanDir, RescanTiming)\n{\n  using namespace std::chrono;\n  std::string path {\"/\"};\n  eos::common::FileSystem::fsid_t fsid = 1;\n  // Scanner completely disabled\n  eos::fst::ScanDir sd(path.c_str(), fsid, nullptr, false, 0, 50, true);\n  auto& clock = sd.GetClock();\n  auto sinit_ts = GetTimestampSec(clock);\n  ASSERT_FALSE(sd.DoRescan(seconds(-1)));\n  clock.advance(seconds(65));\n  ASSERT_FALSE(sd.DoRescan(seconds(sinit_ts)));\n  // Configure the scan interval to 60 seconds\n  sd.SetConfig(\"scaninterval\", 60);\n  // First time the file should be scanned\n  ASSERT_TRUE(sd.DoRescan(seconds(-1)));\n  // Update initial timestamp\n  sinit_ts = GetTimestampSec(clock);\n  ASSERT_FALSE(sd.DoRescan(seconds(sinit_ts)));\n  clock.advance(seconds(59));\n  ASSERT_FALSE(sd.DoRescan(seconds(sinit_ts)));\n  clock.advance(seconds(2));\n  ASSERT_TRUE(sd.DoRescan(seconds(sinit_ts)));\n}\n\nTEST(ScanDir, TimestampSmeared)\n{\n  using namespace std::chrono;\n  std::string path {\"/\"};\n  eos::common::FileSystem::fsid_t fsid = 1;\n  eos::fst::ScanDir sd(path.c_str(), fsid, nullptr, false, 0, 50, true);\n  int interval = 300;\n  sd.SetConfig(eos::common::SCAN_ENTRY_INTERVAL_NAME, interval);\n  auto& clock = sd.GetClock();\n  clock.advance(seconds(5000));\n\n  for (int count = 0; count < 100; ++count) {\n    uint64_t ts_sec = duration_cast<seconds>\n                      (clock.GetTime().time_since_epoch()).count();\n    auto sts = sd.GetTimestampSmearedSec();\n    ASSERT_TRUE(std::stoull(sts) >= ts_sec - interval);\n    ASSERT_TRUE(std::stoull(sts) <= ts_sec + interval);\n    clock.advance(seconds(1000));\n  }\n}\n\nTEST(ScanDir, AdjustScanRate)\n{\n  using namespace std::chrono;\n  using ::testing::_;\n  using ::testing::Return;\n  // Mock load class to return first a value for the disk rate below the\n  // threshold and then only values above the threshold to trigger the\n  // adjustment of the scan_rate but not lower then 5 MB/s\n  MockLoad load;\n  EXPECT_CALL(load, GetDiskRate(_, _)\n             ).WillOnce(Return(500.0)).WillRepeatedly(Return(800.0));\n  std::string path {\"/var/\"};\n  eos::common::FileSystem::fsid_t fsid = 1;\n  off_t offset = 0;\n  int rate = 75;  // MB/s\n  eos::fst::ScanDir sd(path.c_str(), fsid, &load, false, 0, rate, true);\n  const auto open_ts = std::chrono::system_clock::now();\n  int old_rate = rate;\n  eos::fst::utils::EnforceAndAdjustScanRate(offset, open_ts, rate, &load,\n      path.c_str());\n  ASSERT_EQ(rate, old_rate);\n\n  while (rate > 5) {\n    old_rate = rate;\n    eos::fst::utils::EnforceAndAdjustScanRate(offset, open_ts, rate,\n        &load, path.c_str());\n    ASSERT_EQ(rate, (int)(old_rate * 0.9));\n  }\n\n  ASSERT_LE(rate, 5);\n}\n\nTEST_F(TmpDirTree, ScanDirSetConfig)\n{\n  MockLoad load;\n  eos::common::FileSystem::fsid_t fsid = 1;\n  eos::fst::ScanDir sd(TMP_DIR_ROOT.c_str(), fsid, &load, false, 0, 100, true);\n  ASSERT_EQ(TMP_DIR_ROOT, \"/tmp/fstest\");\n  ASSERT_EQ(sd.mDirPath, TMP_DIR_ROOT);\n  ASSERT_EQ(sd.mDiskInterval.get(), eos::fst::DEFAULT_DISK_INTERVAL);\n#ifdef _NOOFS\n  sd.SetConfig(eos::common::SCAN_DISK_INTERVAL_NAME, 3000);\n  ASSERT_EQ(sd.mDiskInterval.get(), 3000);\n  // This toggle logic is to ensure that CAS functions correctly\n  sd.SetConfig(eos::common::SCAN_DISK_INTERVAL_NAME,\n               eos::fst::DEFAULT_DISK_INTERVAL);\n  ASSERT_EQ(sd.mDiskInterval.get(), eos::fst::DEFAULT_DISK_INTERVAL);\n  sd.SetConfig(eos::common::SCAN_DISK_INTERVAL_NAME, 2500);\n  ASSERT_EQ(sd.mDiskInterval.get(), 2500);\n#endif\n}\n"
  },
  {
    "path": "unit_tests/fst/TestEnv.cc",
    "content": "//------------------------------------------------------------------------------\n// File: TestEnv.hh\n// Author: Elvin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"TestEnv.hh\"\n#include <XrdCl/XrdClURL.hh>\n#include <sstream>\n#include <cstdlib>\n#include <libgen.h>\n\n#define SSTR(message) static_cast<std::ostringstream&>(std::ostringstream().flush() << message).str()\n\nEOSFSTTEST_NAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Constructor\n//\n// Notice:\n// File file32MB.dat is created as follows:\n// dd if=/dev/zero count=32 bs=1M | tr '\\000' '\\001' > /eos/dev/test/fst/replica/file32MB.dat\n//\n//------------------------------------------------------------------------------\nTestEnv::TestEnv(const std::string& endpoint)\n{\n  XrdCl::URL url(endpoint);\n\n  if (!url.IsValid()) {\n    std::cerr << \"error: invalid endpoint - \" << endpoint << std::endl;\n    exit(1);\n  }\n\n  mPathPrefix = url.GetPath();\n\n  if (*mPathPrefix.rbegin() != '/') {\n    mPathPrefix += '/';\n  }\n\n  mPathPrefix += \"fst_unit_tests/dirs/\";\n  mHostName = url.GetHostName();\n  // Note the yes and tr errors are \"acceptable\"\n  system(\"rm -rf /tmp/file32MB.dat; rm -rf /tmp/file_prefetch.dat\");\n  system(\"yes '\\\\xDE\\\\xAD\\\\xBE\\\\xEF' | tr -d \\\\\\\\n | dd of=/tmp/file32MB.dat count=32 bs=1M iflag=fullblock\");\n\n  for (int i = 0; i < 4; i++) {\n    system(\"yes '\\\\xDE\\\\xAD\\\\xBE\\\\xEF' | tr -d \\\\\\\\n | dd of=/tmp/file_prefetch.dat count=3 bs=1M iflag=fullblock oflag=append conv=notrunc\");\n    system(\"yes '\\\\xAD\\\\xAA\\\\xDA\\\\xAD' | tr -d \\\\\\\\n | dd of=/tmp/file_prefetch.dat count=3 bs=1M iflag=fullblock oflag=append conv=notrunc\");\n    system(\"yes '\\\\xAB\\\\xCD\\\\xAB\\\\xCD' | tr -d \\\\\\\\n | dd of=/tmp/file_prefetch.dat count=3 bs=1M iflag=fullblock oflag=append conv=notrunc\");\n  }\n\n  // Add one last bit to the file so that it has a \"random\" size\n  system(\"yes '\\\\xFE\\\\xDC\\\\xCB\\\\xBA' | tr -d \\\\\\\\n | dd of=/tmp/file_prefetch.dat count=1 bs=213 iflag=fullblock oflag=append conv=notrunc\");\n  system(SSTR(\"eos mkdir -p \" << mPathPrefix << \"replica \").c_str());\n  system(SSTR(\"eos attr set default=replica \" << mPathPrefix <<\n              \"replica > /dev/null 2>&1\").c_str());\n  system(SSTR(\"eos attr rm sys.recycle \" << mPathPrefix <<\n              \"replica > /dev/null 2>&1\").c_str());\n  system(SSTR(\"eos mkdir -p \" << mPathPrefix << \"raiddp\").c_str());\n  system(SSTR(\"eos attr set default=raiddp \" << mPathPrefix <<\n              \"raiddp > /dev/null 2>&1\").c_str());\n  system(SSTR(\"eos attr rm sys.recycle \" << mPathPrefix <<\n              \"raiddp > /dev/null 2>&1\").c_str());\n  system(SSTR(\"eos mkdir -p \" << mPathPrefix << \"raid6\").c_str());\n  system(SSTR(\"eos attr set default=raid6 \" << mPathPrefix <<\n              \"raid6  > /dev/null 2>&1\").c_str());\n  system(SSTR(\"eos attr rm sys.recycle \" << mPathPrefix <<\n              \"raid6 > /dev/null 2>&1\").c_str());\n  system(SSTR(\"xrdcp -f /tmp/file32MB.dat root://\" << mHostName << \"/\" <<\n              mPathPrefix << \"replica/ > /dev/null 2>&1\").c_str());\n  system(SSTR(\"xrdcp -f /tmp/file32MB.dat root://\" << mHostName << \"/\" <<\n              mPathPrefix << \"raiddp/ > /dev/null 2>&1\").c_str());\n  system(SSTR(\"xrdcp -f /tmp/file32MB.dat root://\" << mHostName << \"/\" <<\n              mPathPrefix << \"raid6/ > /dev/null 2>&1\").c_str());\n  system(SSTR(\"xrdcp -f /tmp/file_prefetch.dat root://\" << mHostName << \"/\" <<\n              mPathPrefix << \"replica/ > /dev/null 2>&1\").c_str());\n  mMapParam.insert(std::make_pair(\"server\", mHostName));\n  mMapParam.insert(std::make_pair(\"dummy_file\",\n                                  mPathPrefix + \"replica/dummy.dat\"));\n  mMapParam.insert(std::make_pair(\"replica_file\",\n                                  mPathPrefix + \"replica/file32MB.dat\"));\n  mMapParam.insert(std::make_pair(\"prefetch_file\",\n                                  mPathPrefix + \"replica/file_prefetch.dat\"));\n  mMapParam.insert(std::make_pair(\"raiddp_file\",\n                                  mPathPrefix + \"raiddp/file32MB.dat\"));\n  mMapParam.insert(std::make_pair(\"reeds_file\",\n                                  mPathPrefix + \"raid6/file32MB.dat\"));\n  mMapParam.insert(std::make_pair(\"file_size\", \"33554432\")); // 32MB\n  // ReadV sequences used for testing\n  // Test set 1 - 4KB read out of each MB\n  mMapParam.insert(std::make_pair(\"off1\",\n                                  \"0 1048576 2097152 3145728 4194304 5242880 \"));\n  mMapParam.insert(std::make_pair(\"len1\", \"4096 4096 4096 4096 4096 4096\"));\n  // Correct responses for the set 1\n  mMapParam.insert(std::make_pair(\"off1_stripe0\", \"0 1048576\"));\n  mMapParam.insert(std::make_pair(\"len1_stripe0\", \"4096 4096\"));\n  mMapParam.insert(std::make_pair(\"off1_stripe1\", \"0 1048576\"));\n  mMapParam.insert(std::make_pair(\"len1_stripe1\", \"4096 4096\"));\n  mMapParam.insert(std::make_pair(\"off1_stripe2\", \"0\"));\n  mMapParam.insert(std::make_pair(\"len1_stripe2\", \"4096\"));\n  mMapParam.insert(std::make_pair(\"off1_stripe3\", \"0\"));\n  mMapParam.insert(std::make_pair(\"len1_stripe3\", \"4096\"));\n  // Test set 2 - 16KB read around each MB\n  mMapParam.insert(std::make_pair(\"off2\",\n                                  \"1040384 2088960 3137536 4186112 5234688 \"\n                                  \"6283264 7331840 8380416 9428992 10477568\"));\n  mMapParam.insert(std::make_pair(\"len2\",\n                                  \"16384 16384 16384 16384 16384 16384 16384 \"\n                                  \"16384 16384 16384\"));\n  // Correct responses for set 2\n  mMapParam.insert(std::make_pair(\"off2_stripe0\",\n                                  \"1040384 1048576 2088960 2097152 3137536\"));\n  mMapParam.insert(std::make_pair(\"len2_stripe0\", \"8192 8192 8192 8192 8192\"));\n  mMapParam.insert(std::make_pair(\"off2_stripe1\",\n                                  \"0 1040384 1048576 2088960 2097152 3137536\"));\n  mMapParam.insert(std::make_pair(\"len2_stripe1\",\n                                  \"8192 8192 8192 8192 8192 8192\"));\n  mMapParam.insert(std::make_pair(\"off2_stripe2\",\n                                  \"0 1040384 1048576 2088960 2097152\"));\n  mMapParam.insert(std::make_pair(\"len2_stripe2\", \"8192 8192 8192 8192 8192\"));\n  mMapParam.insert(std::make_pair(\"off2_stripe3\", \"0 1040384 1048576 2088960\"));\n  mMapParam.insert(std::make_pair(\"len2_stripe3\", \"8192 8192 8192 8192\"));\n  // Test set 3\n  mMapParam.insert(std::make_pair(\"off3\", \"1048576\"));\n  mMapParam.insert(std::make_pair(\"len3\", \"2097169\"));\n  // Correct responses for set 3\n  mMapParam.insert(std::make_pair(\"off3_stripe0\", \"\"));\n  mMapParam.insert(std::make_pair(\"len3_stripe0\", \"\"));\n  mMapParam.insert(std::make_pair(\"off3_stripe1\", \"0\"));\n  mMapParam.insert(std::make_pair(\"len3_stripe1\", \"1048576\"));\n  mMapParam.insert(std::make_pair(\"off3_stripe2\", \"0\"));\n  mMapParam.insert(std::make_pair(\"len3_stripe2\", \"1048576\"));\n  mMapParam.insert(std::make_pair(\"off3_stripe3\", \"0\"));\n  mMapParam.insert(std::make_pair(\"len3_stripe3\", \"17\"));\n}\n\n//------------------------------------------------------------------------------\n// Destructor\n//------------------------------------------------------------------------------\nTestEnv::~TestEnv()\n{\n  system(SSTR(\"eos \" << \"root://\" << mHostName << \" rm -rF \" <<\n              mPathPrefix).c_str());\n  const std::string dir_to_rm = dirname((char*)std::string(mPathPrefix).c_str());\n  system(SSTR(\"eos \" << \"root://\" << mHostName << \" rmdir \" <<\n              dir_to_rm).c_str());\n}\n\n//------------------------------------------------------------------------------\n// Set key value mapping\n//------------------------------------------------------------------------------\nvoid\nTestEnv::SetMapping(const std::string& key, const std::string& value)\n{\n  auto pair_res = mMapParam.insert(std::make_pair(key, value));\n\n  if (!pair_res.second) {\n    std::cerr << \"Mapping already exists, key=\" << key\n              << \" value=\" << value << std::endl;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get key value mapping\n//------------------------------------------------------------------------------\nstd::string\nTestEnv::GetMapping(const std::string& key) const\n{\n  auto iter = mMapParam.find(key);\n\n  if (iter != mMapParam.end()) {\n    return iter->second;\n  } else {\n    return std::string(\"\");\n  }\n}\n\nEOSFSTTEST_NAMESPACE_END\n"
  },
  {
    "path": "unit_tests/fst/TestEnv.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file TestEnv.hh\n//! @author Elvin Sindrilaru <esindril@cern.ch>\n//! @bried Class containing all the variables need for the test done on the FST\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2013 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#pragma once\n#include \"Namespace.hh\"\n#include <map>\n#include <string>\n#include <iostream>\n#include <memory>\n\nEOSFSTTEST_NAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n//! TestEnv class - not thread safe\n//------------------------------------------------------------------------------\nclass TestEnv\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //!\n  //! @param EOS endpoint\n  //----------------------------------------------------------------------------\n  TestEnv(const std::string& endpoint);\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  virtual ~TestEnv();\n\n  //----------------------------------------------------------------------------\n  //! Add new entry to the map of parameters\n  //!\n  //! @param key key to be inserted\n  //! @param value value to the inserted\n  //----------------------------------------------------------------------------\n  void SetMapping(const std::string& key, const std::string& value);\n\n  //----------------------------------------------------------------------------\n  //! Get value corresponding to the key from the map\n  //!\n  //! @param key key to be searched in the map\n  //!\n  //! @return value stored in the map\n  //----------------------------------------------------------------------------\n  std::string GetMapping(const std::string& key) const;\n\nprivate:\n  std::map<std::string, std::string> mMapParam; ///< map testing parameters\n  std::string mPathPrefix; ///< Path prefix inside the instate for tests\n  std::string mHostName; ///< EOS instance hostname\n};\n\n//------------------------------------------------------------------------------\n//! Logging class\n//------------------------------------------------------------------------------\nclass GTest_Logger\n{\npublic:\n  GTest_Logger(bool enabled) : mEnabled(enabled) {}\n  bool isEnabled()\n  {\n    return mEnabled;\n  }\n\n  template<typename T> GTest_Logger& operator<<(T const& t)\n  {\n    if (mEnabled) {\n      std::cout << t;\n    }\n\n    return *this;\n  }\n\n  GTest_Logger& operator<<(std::ostream & (*manipulator)(std::ostream&))\n  {\n    if (mEnabled) {\n      std::cout << manipulator;\n    }\n\n    return *this;\n  }\n\n  void SetEnabled(bool enable)\n  {\n    mEnabled = enable;\n  }\n\nprivate:\n  bool mEnabled;\n};\n\nEOSFSTTEST_NAMESPACE_END\n\nextern std::unique_ptr<eos::fst::test::TestEnv> gEnv;\nextern eos::fst::test::GTest_Logger gLogger;\n\n// Macro to print GTest similar output\n// Uses the GTest_Logger gLogger variable available in the FstFileTest fixtures\n#define GLOG if (gLogger.isEnabled()) { std::cout << \"[ INFO     ] \"; } gLogger\n"
  },
  {
    "path": "unit_tests/fst/TmpDirTree.hh",
    "content": "// ----------------------------------------------------------------------\n// File: TmpDirTree\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#pragma once\n\n\n#if defined(__clang__) && __clang_major__ < 6\n#include <experimental/filesystem>\n#else\n#include <filesystem>\n#endif\n#include <unordered_set>\n\n\n#if defined(__clang__) && __clang_major__ < 6\nnamespace fs = std::experimental::filesystem::v1;\n#else\nnamespace fs = std::filesystem;\n#endif\n\n\n\nstatic const std::string BASE_DIR = \"fstest\";\n\n// This file has a useful test fixture TmpDirTree that\n// creates the following tree\n// fstest\n//        a0\n//           a1\n//              file0\n//              [..] file5\n// ...\n\nstatic void create_files(std::string path, int count)\n{\n  for (int i = 0; i < count; ++i) {\n    std::string filename = path + \"/file\" + std::to_string(i);\n    std::ofstream f(filename.c_str());\n  }\n}\n\nstatic std::string TMP_DIR_ROOT = fs::temp_directory_path().native() +\n                                  \"/fstest\";\n\nclass TmpDirTree: public ::testing::Test\n{\nprotected:\n  void SetUp() override\n  {\n    fs::current_path(fs::temp_directory_path());\n    fs::create_directories(\"fstest/a0/a1\");\n    fs::create_directories(\"fstest/b0/b1\");\n    fs::create_directories(\"fstest/c0/c1\");\n    create_files(\"fstest/a0/a1\", 3);\n    create_files(\"fstest/b0/b1\", 3);\n    create_files(\"fstest/c0/c1\", 3);\n    create_files(\"fstest\", 3);\n    std::ofstream f(TMP_DIR_ROOT + \"/test.xsmap\");\n    fs::create_directories(\"fstest/.hidden/hidden0\");\n    create_files(\"fstest/.hidden/hidden0\", 3);\n    create_files(\"fstest/.hidden\", 3);\n  }\n\n  void TearDown() override\n  {\n    fs::remove_all(\"fstest\");\n  }\n};\n\nstatic std::unordered_set<std::string> expected_files = {\n  \"/tmp/fstest/a0/a1/file0\",\n  \"/tmp/fstest/a0/a1/file1\",\n  \"/tmp/fstest/a0/a1/file2\",\n  \"/tmp/fstest/b0/b1/file0\",\n  \"/tmp/fstest/b0/b1/file1\",\n  \"/tmp/fstest/b0/b1/file2\",\n  \"/tmp/fstest/c0/c1/file0\",\n  \"/tmp/fstest/c0/c1/file1\",\n  \"/tmp/fstest/c0/c1/file2\",\n  \"/tmp/fstest/file0\",\n  \"/tmp/fstest/file1\",\n  \"/tmp/fstest/file2\"\n};\n\n"
  },
  {
    "path": "unit_tests/fst/UtilsTest.cc",
    "content": "//------------------------------------------------------------------------------\n// File: UtilsTest.cc\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"TestEnv.hh\"\n#include \"fst/utils/OpenFileTracker.hh\"\n#include \"gtest/gtest.h\"\n\nTEST(OpenFileTracker, BasicSanity)\n{\n  eos::fst::OpenFileTracker oft;\n  ASSERT_FALSE(oft.isAnyOpen());\n  // fsid=1, fid=99\n  ASSERT_FALSE(oft.isOpen(1, 99));\n  oft.up(1, 99);\n  ASSERT_TRUE(oft.isAnyOpen());\n  ASSERT_TRUE(oft.isOpen(1, 99));\n  ASSERT_EQ(oft.getUseCount(1, 99), 1);\n  ASSERT_EQ(oft.getOpenOnFilesystem(1), 1);\n  ASSERT_EQ(oft.getOpenOnFilesystem(2), 0);\n  oft.down(1, 99);\n  ASSERT_FALSE(oft.isOpen(1, 99));\n  ASSERT_EQ(oft.getUseCount(1, 99), 0);\n  ASSERT_FALSE(oft.isAnyOpen());\n  ASSERT_EQ(oft.getOpenOnFilesystem(1), 0);\n  ASSERT_EQ(oft.getOpenOnFilesystem(2), 0);\n  oft.up(2, 100); // fsid=2, fid=100\n  ASSERT_TRUE(oft.isAnyOpen());\n  oft.up(3, 101); // fsid=3, fid=101\n  oft.up(3, 101);\n  oft.up(3, 101);\n  oft.up(9, 102); // fsid=9, fid=102\n  ASSERT_EQ(oft.getOpenOnFilesystem(2), 1);\n  ASSERT_EQ(oft.getOpenOnFilesystem(3), 1);\n  ASSERT_EQ(oft.getOpenOnFilesystem(9), 1);\n  ASSERT_FALSE(oft.isOpen(2, 101));\n  ASSERT_TRUE(oft.isOpen(2, 100));\n  ASSERT_TRUE(oft.isOpen(3, 101));\n  ASSERT_TRUE(oft.isOpen(9, 102));\n  ASSERT_EQ(oft.getUseCount(2, 100), 1);\n  ASSERT_EQ(oft.getUseCount(3, 101), 3);\n  ASSERT_EQ(oft.getUseCount(9, 102), 1);\n  oft.down(3, 101);\n  ASSERT_EQ(oft.getUseCount(3, 101), 2);\n  oft.down(3, 101);\n  ASSERT_EQ(oft.getUseCount(3, 101), 1);\n  oft.down(3, 101);\n  ASSERT_EQ(oft.getUseCount(3, 101), 0);\n  ASSERT_FALSE(oft.isOpen(3, 101));\n  ASSERT_EQ(oft.getOpenOnFilesystem(3), 0);\n  // invalid operation, as (3, 101) is already at 0\n  // prints error in the log\n  oft.down(3, 101);\n  ASSERT_FALSE(oft.isOpen(3, 101));\n  ASSERT_EQ(oft.getUseCount(3, 101), 0);\n  ASSERT_TRUE(oft.isOpen(9, 102));\n}\n\nTEST(OpenFileTracker, SortedByUseCount)\n{\n  eos::fst::OpenFileTracker oft;\n  auto sorted = oft.getSortedByUsecount(3);\n  ASSERT_TRUE(sorted.empty());\n  oft.up(3, 101);\n  oft.up(3, 101);\n  oft.up(3, 101);\n  ASSERT_EQ(oft.getOpenOnFilesystem(3), 1);\n  oft.up(3, 102);\n  oft.up(3, 102);\n  ASSERT_EQ(oft.getOpenOnFilesystem(3), 2);\n  oft.up(3, 103);\n  ASSERT_EQ(oft.getOpenOnFilesystem(3), 3);\n  oft.up(3, 104);\n  oft.up(3, 104);\n  oft.up(3, 104);\n  ASSERT_EQ(oft.getOpenOnFilesystem(3), 4);\n  oft.up(3, 105);\n  ASSERT_EQ(oft.getOpenOnFilesystem(3), 5);\n  sorted = oft.getSortedByUsecount(3);\n  ASSERT_EQ(sorted.size(), 3);\n  std::set<uint64_t> contents;\n  contents = {101, 104};\n  ASSERT_EQ(sorted[3], contents);\n  contents = {102};\n  ASSERT_EQ(sorted[2], contents);\n  contents = {103, 105};\n  ASSERT_EQ(sorted[1], contents);\n\n  for (size_t i = 0; i < 5; i++) {\n    oft.up(3, 100);\n  }\n\n  sorted = oft.getSortedByUsecount(3);\n  ASSERT_EQ(sorted.size(), 4);\n  contents = {100};\n  ASSERT_EQ(sorted[5], contents);\n  auto hotFiles = oft.getHotFiles(3, 1);\n  ASSERT_EQ(hotFiles.size(), 1u);\n  eos::fst::OpenFileTracker::HotEntry entry;\n  entry = {3, 100, 5};\n  ASSERT_EQ(hotFiles[0], entry);\n  hotFiles = oft.getHotFiles(3, 2);\n  ASSERT_EQ(hotFiles.size(), 2u);\n  entry = {3, 100, 5};\n  ASSERT_EQ(hotFiles[0], entry);\n  entry = {3, 101, 3};\n  ASSERT_EQ(hotFiles[1], entry);\n  hotFiles = oft.getHotFiles(3, 3);\n  ASSERT_EQ(hotFiles.size(), 3u);\n  entry = {3, 100, 5};\n  ASSERT_EQ(hotFiles[0], entry);\n  entry = {3, 101, 3};\n  ASSERT_EQ(hotFiles[1], entry);\n  entry = {3, 104, 3};\n  ASSERT_EQ(hotFiles[2], entry);\n  hotFiles = oft.getHotFiles(3, 4);\n  ASSERT_EQ(hotFiles.size(), 4u);\n  entry = {3, 100, 5};\n  ASSERT_EQ(hotFiles[0], entry);\n  entry = {3, 101, 3};\n  ASSERT_EQ(hotFiles[1], entry);\n  entry = {3, 104, 3};\n  ASSERT_EQ(hotFiles[2], entry);\n  ASSERT_NE(hotFiles[3], entry);\n  entry = {3, 102, 2};\n  ASSERT_EQ(hotFiles[3], entry);\n  hotFiles = oft.getHotFiles(3, 5);\n  ASSERT_EQ(hotFiles.size(), 5u);\n  entry = {3, 100, 5};\n  ASSERT_EQ(hotFiles[0], entry);\n  entry = {3, 101, 3};\n  ASSERT_EQ(hotFiles[1], entry);\n  entry = {3, 104, 3};\n  ASSERT_EQ(hotFiles[2], entry);\n  ASSERT_NE(hotFiles[3], entry);\n  entry = {3, 102, 2};\n  ASSERT_EQ(hotFiles[3], entry);\n  entry = {3, 103, 1};\n  ASSERT_EQ(hotFiles[4], entry);\n  hotFiles = oft.getHotFiles(3, 6);\n  ASSERT_EQ(hotFiles.size(), 6u);\n  entry = {3, 100, 5};\n  ASSERT_EQ(hotFiles[0], entry);\n  entry = {3, 101, 3};\n  ASSERT_EQ(hotFiles[1], entry);\n  entry = {3, 104, 3};\n  ASSERT_EQ(hotFiles[2], entry);\n  ASSERT_NE(hotFiles[3], entry);\n  entry = {3, 102, 2};\n  ASSERT_EQ(hotFiles[3], entry);\n  entry = {3, 103, 1};\n  ASSERT_EQ(hotFiles[4], entry);\n  entry = {3, 105, 1};\n  ASSERT_EQ(hotFiles[5], entry);\n  // Only have 6 items in total\n  auto hotFiles2 = oft.getHotFiles(3, 7);\n  ASSERT_EQ(hotFiles, hotFiles2);\n  hotFiles2 = oft.getHotFiles(3, 1000000);\n  ASSERT_EQ(hotFiles, hotFiles2);\n  auto hotFiles3 = oft.getHotFiles(3, 0);\n  ASSERT_TRUE(hotFiles3.empty());\n}\n"
  },
  {
    "path": "unit_tests/fst/WalkDirTreeTests.cc",
    "content": "// ----------------------------------------------------------------------\n// File: WalkDirTreeTests\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include <gtest/gtest.h>\n#include <fstream>\n#include <unordered_set>\n#include \"fst/utils/StdFSWalkTree.hh\"\n#include \"fst/utils/FTSWalkTree.hh\"\n#include \"TmpDirTree.hh\"\n\nusing eos::fst::stdfs::IsRegularFile;\n\nTEST_F(TmpDirTree, WalkFSTree)\n{\n  std::unordered_set<std::string> files;\n  auto process_fn  = [&files](std::string p) {\n    std::cout << \"Accessing path=\" << p << std::endl;\n    files.emplace(p);\n  };\n  std::error_code ec;\n  auto count = eos::fst::stdfs::WalkFSTree(\"/tmp/fstest\", process_fn, ec);\n  ASSERT_FALSE(ec);\n  ASSERT_EQ(count, 12);\n  EXPECT_EQ(files, expected_files);\n}\n\n\nTEST_F(TmpDirTree, FTSWalkTree)\n{\n  std::unordered_set<std::string> files;\n  auto process_fn  = [&files](std::string p) {\n    std::cout << \"Accessing path=\" << p << std::endl;\n    files.emplace(p);\n  };\n  std::error_code ec;\n  auto ret = eos::fst::WalkFSTree(\"/tmp/fstest\", process_fn, ec);\n  ASSERT_FALSE(ec);\n  ASSERT_EQ(ret, 12);\n  EXPECT_EQ(files, expected_files);\n}\n\nTEST_F(TmpDirTree, FTSWalkTreeInvalid)\n{\n  std::unordered_set<std::string> files;\n  auto process_fn  = [&files](const char* p) {\n    files.emplace(p);\n  };\n  std::error_code ec;\n  auto r = eos::fst::WalkFSTree(\"\", process_fn, ec);\n  ASSERT_EQ(r, 0);\n  ASSERT_TRUE(ec);\n  ASSERT_EQ(ec.value(), ENOENT);\n  ASSERT_FALSE(ec.message().empty());\n}\n"
  },
  {
    "path": "unit_tests/fst/XrdFstOfsFileInternalTest.cc",
    "content": "//------------------------------------------------------------------------------\n// File: XrdFstOfsFileTest.cc\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#define IN_TEST_HARNESS\n#include \"fst/XrdFstOfsFile.hh\"\n#include \"fst/XrdFstOfs.hh\"\n#undef IN_TEST_HARNESS\n#include <memory>\n#include \"gtest/gtest.h\"\n\nusing namespace eos::fst;\n\nTEST(XrdFstOfsFileTest, FilterTags)\n{\n  std::set<std::string> tags {\"xrdcl.secuid\", \"xrdcl.secgid\"};\n  std::string opaque =\n    \"eos.app=demo&oss.size=13&xrdcl.secuid=2134&xrdcl.secgid=99\";\n  eos::fst::XrdFstOfsFile::FilterTagsInPlace(opaque, tags);\n  ASSERT_STREQ(opaque.c_str(), \"eos.app=demo&oss.size=13\");\n  opaque = \"eos.app=demo&oss.size=13\";\n  eos::fst::XrdFstOfsFile::FilterTagsInPlace(opaque, tags);\n  ASSERT_STREQ(opaque.c_str(), \"eos.app=demo&oss.size=13\");\n  opaque = \"eos.app=demo&oss.size=13&xrdcl.secuid=2134&xrdcl.secgid=99&\"\n           \"xrdcl.other=tag&eos.lfn=/some/dummy/path&\";\n  eos::fst::XrdFstOfsFile::FilterTagsInPlace(opaque, tags);\n  ASSERT_STREQ(opaque.c_str(),\n               \"eos.app=demo&oss.size=13&xrdcl.other=tag&eos.lfn=/some/dummy/path\");\n  opaque = \"\";\n  eos::fst::XrdFstOfsFile::FilterTagsInPlace(opaque, tags);\n  ASSERT_STREQ(opaque.c_str(), \"\");\n}\n\nTEST(XrdFstOfsFileTest, GetHostFromTident)\n{\n  std::string hostname;\n  std::string tident = \"root.1.2@eospps.cern.ch\";\n  ASSERT_TRUE(XrdFstOfsFile::GetHostFromTident(tident, hostname));\n  ASSERT_STREQ(hostname.c_str(), \"eospps\");\n  tident = \"root@eospps.ipv6.cern.ch\";\n  ASSERT_TRUE(XrdFstOfsFile::GetHostFromTident(tident, hostname));\n  ASSERT_STREQ(hostname.c_str(), \"eospps\");\n  tident = \"root.1.1@eospps.dyndns.some.other.ipv6.cern.ch\";\n  ASSERT_TRUE(XrdFstOfsFile::GetHostFromTident(tident, hostname));\n  ASSERT_STREQ(hostname.c_str(), \"eospps\");\n  tident = \"root.1.1@eospps\";\n  ASSERT_TRUE(XrdFstOfsFile::GetHostFromTident(tident, hostname));\n  ASSERT_STREQ(hostname.c_str(), \"eospps\");\n  tident = \"root.1.1_eospps.dyndns.some.other.ipv6.cern.ch\";\n  ASSERT_FALSE(XrdFstOfsFile::GetHostFromTident(tident, hostname));\n  ASSERT_STREQ(hostname.c_str(), \"\");\n  tident = \"root.1.1@\";\n  ASSERT_FALSE(XrdFstOfsFile::GetHostFromTident(tident, hostname));\n  ASSERT_STREQ(hostname.c_str(), \"\");\n  tident = \"root.1.1\";\n  ASSERT_FALSE(XrdFstOfsFile::GetHostFromTident(tident, hostname));\n  ASSERT_STREQ(hostname.c_str(), \"\");\n}\n\n//------------------------------------------------------------------------------\n// Test tpc.ttl value enforcement\n//------------------------------------------------------------------------------\nTEST(XrdFstOfsFileTest, GetTpcKeyExpireTS)\n{\n  time_t now_test = time(nullptr);\n  ASSERT_EQ(now_test + 120,\n            eos::fst::XrdFstOfsFile::GetTpcKeyExpireTS(\"\", now_test));\n  ASSERT_EQ(now_test + 120,\n            eos::fst::XrdFstOfsFile::GetTpcKeyExpireTS(\"61\",  now_test));\n  ASSERT_EQ(now_test + 200,\n            eos::fst::XrdFstOfsFile::GetTpcKeyExpireTS(\"200\", now_test));\n  ASSERT_EQ(now_test + 900,\n            eos::fst::XrdFstOfsFile::GetTpcKeyExpireTS(\"1000\", now_test));\n  setenv(\"EOS_FST_TPC_KEY_MIN_VALIDITY_SEC\", \"30\", true);\n  setenv(\"EOS_FST_TPC_KEY_MAX_VALIDITY_SEC\", \"4000\", true);\n  gOFS.UpdateTpcKeyValidity();\n  ASSERT_STREQ(\"30\", getenv(\"EOS_FST_TPC_KEY_MIN_VALIDITY_SEC\"));\n  ASSERT_STREQ(\"4000\", getenv(\"EOS_FST_TPC_KEY_MAX_VALIDITY_SEC\"));\n  ASSERT_EQ(60, gOFS.mTpcKeyMinValidity.count());\n  ASSERT_EQ(3600, gOFS.mTpcKeyMaxValidity.count());\n  ASSERT_EQ(now_test + 60,\n            eos::fst::XrdFstOfsFile::GetTpcKeyExpireTS(\"\", now_test));\n  ASSERT_EQ(now_test + 61,\n            eos::fst::XrdFstOfsFile::GetTpcKeyExpireTS(\"61\", now_test));\n  ASSERT_EQ(now_test + 200,\n            eos::fst::XrdFstOfsFile::GetTpcKeyExpireTS(\"200\", now_test));\n  ASSERT_EQ(now_test + 1000,\n            eos::fst::XrdFstOfsFile::GetTpcKeyExpireTS(\"1000\", now_test));\n  ASSERT_EQ(now_test + 3600,\n            eos::fst::XrdFstOfsFile::GetTpcKeyExpireTS(\"4000\", now_test));\n}\n"
  },
  {
    "path": "unit_tests/fst/XrdFstOfsFileTest.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file XrdFstOfsFileTest.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"TestEnv.hh\"\n#include \"fst/layout/RainMetaLayout.hh\"\n#include \"fst/checksum/CRC32C.hh\"\n#include \"fst/layout/RaidDpLayout.hh\"\n#include \"fst/io/xrd/XrdIo.hh\"\n#include <XrdOuc/XrdOucTokenizer.hh>\n#include <string>\n\nEOSFSTTEST_NAMESPACE_BEGIN\n\n//------------------------------------------------------------------------------\n// Write test - not useful at the moment\n//------------------------------------------------------------------------------\nTEST(FstFileTest, DISABLED_WriteTest)\n{\n  using namespace XrdCl;\n  std::unique_ptr<File> mFile(new File());\n  // Initialize\n  uint32_t file_size = (uint32_t) atoi(gEnv->GetMapping(\"file_size\").c_str());\n  std::string address = \"root://root@\" + gEnv->GetMapping(\"server\");\n  std::string file_path = gEnv->GetMapping(\"replica_file\");\n  // Validate URL\n  URL url(address);\n  ASSERT_TRUE(url.IsValid());\n  // Open file\n  std::string file_url = address + \"/\" + file_path;\n  GLOG << \"Opening file: \" << file_url << std::endl;\n  XRootDStatus status = mFile->Open(file_url,\n                                    OpenFlags::Update | OpenFlags::Delete,\n                                    Access::Mode::None);\n  ASSERT_TRUE(status.IsOK());\n  // Prepare 1MB chunk\n  uint32_t size_chunk = 1024 * 1024;\n  int num_chunks = file_size / size_chunk;\n  char* buff_write = new char[size_chunk];\n  std::ifstream urandom(\"/dev/urandom\", std::ios::in | std::ios::binary);\n  urandom.read(buff_write, size_chunk);\n  urandom.close();\n  // Write the file in 1MB chunks\n  GLOG << \"Performing write operation\" << std::endl;\n\n  for (int i = 0; i < num_chunks; i++) {\n    uint64_t offset = (uint64_t) i * size_chunk ;\n    status = mFile->Write(offset, size_chunk, buff_write);\n\n    if (!status.IsOK()) {\n      std::cerr << \"Error while writing at offset:\" << offset << std::endl;\n      std::terminate();\n    }\n  }\n\n  // Close file\n  status = mFile->Close();\n  ASSERT_TRUE(status.IsOK());\n  delete[] buff_write;\n}\n\n//------------------------------------------------------------------------------\n// Vector read test\n//------------------------------------------------------------------------------\nTEST(FstFileTest, ReadVTest)\n{\n  using namespace XrdCl;\n  std::unique_ptr<File> mFile(new File());\n  uint32_t nread;\n  // Initialize\n  uint32_t file_size = (uint32_t) atoi(gEnv->GetMapping(\"file_size\").c_str());\n  std::string address = \"root://root@\" + gEnv->GetMapping(\"server\");\n  std::string file_path = gEnv->GetMapping(\"raiddp_file\");\n  // Validate URL\n  URL url(address);\n  ASSERT_TRUE(url.IsValid());\n  // Open file\n  std::string file_url = address + \"/\" + file_path;\n  GLOG << \"Opening file: \" << file_url << std::endl;\n  XRootDStatus status = mFile->Open(file_url, OpenFlags::Read,\n                                    Access::Mode::None);\n  ASSERT_TRUE(status.IsOK());\n  // Check file has the proper size\n  StatInfo* stat = nullptr;\n  status = mFile->Stat(false, stat);\n  ASSERT_TRUE(status.IsOK());\n  GLOG << \"Stat size: \" << stat->GetSize() << std::endl;\n  ASSERT_EQ(stat->GetSize(), file_size);\n  delete stat;\n  // Read the first 4KB out of each MB\n  uint32_t size_chunk = 4096;\n  uint32_t size_gap = 1024 * 1024;\n  int num_chunks = file_size / size_gap;\n  char* ptr_readv;\n  char* ptr_read;\n  char* buff_readv = new char[num_chunks * size_chunk];\n  char* buff_read = new char[num_chunks * size_chunk];\n  ChunkList readv_list;\n  ChunkList read_list;\n\n  // Create the readV list and the list for normal reads\n  for (int i = 0; i < num_chunks; i++) {\n    uint64_t off = (uint64_t) i * size_gap ;\n    ptr_readv = buff_readv + i * size_chunk;\n    readv_list.push_back(ChunkInfo(off, size_chunk, ptr_readv));\n    ptr_read = buff_read + i * size_chunk;\n    read_list.push_back(ChunkInfo(off, size_chunk, ptr_read));\n  }\n\n  // Issue the readV request\n  GLOG << \"Performing readV operation\" << std::endl;\n  VectorReadInfo* vread_info = nullptr;\n  status = mFile->VectorRead(readv_list, 0, vread_info);\n  ASSERT_TRUE(status.IsOK());\n  ASSERT_EQ(num_chunks * size_chunk, vread_info->GetSize());\n  delete vread_info;\n  // Issue the normal read requests\n  GLOG << \"Performing normal read operation\" << std::endl;\n\n  for (auto chunk = read_list.begin(); chunk != read_list.end(); ++chunk) {\n    status = mFile->Read(chunk->offset, chunk->length, chunk->buffer, nread);\n\n    if (!status.IsOK() || (nread != chunk->length)) {\n      std::cerr << \"Error while reading at offset:\" << chunk->offset\n                << \" len:\" << chunk->length << std::endl;\n      std::terminate();\n    }\n  }\n\n  // Compute CRC32C checksum for the readV buffer\n  eos::fst::CRC32C* chksumv = new eos::fst::CRC32C();\n\n  if (!chksumv->Add(buff_readv, num_chunks * size_chunk, 0)) {\n    std::cerr << \"Checksum error: offset unaligned - skip computation\" << std::endl;\n    std::terminate();\n  }\n\n  // Compute CRC32C checksum for the normal read buffer\n  eos::fst::CRC32C* chksum = new eos::fst::CRC32C();\n\n  if (!chksum->Add(buff_read, num_chunks * size_chunk, 0)) {\n    std::cerr << \"Checksum error: offset unaligned - skip computation\" << std::endl;\n    std::terminate();\n  }\n\n  // Compare checksums\n  std::string schksumv = chksumv->GetHexChecksum();\n  std::string schksum = chksum->GetHexChecksum();\n  GLOG << \"ChecksumV: \" << schksumv << std::endl;\n  GLOG << \"Checksum:  \" << schksum << std::endl;\n  ASSERT_STREQ(schksum.c_str(), schksumv.c_str());\n  // Close file\n  status = mFile->Close();\n  ASSERT_TRUE(status.IsOK());\n  delete[] buff_readv;\n  delete[] buff_read;\n}\n\n//------------------------------------------------------------------------------\n// Split vector read test\n//------------------------------------------------------------------------------\nTEST(FstFileTest, SplitReadVTest)\n{\n  using namespace eos::common;\n  using namespace eos::fst;\n  unsigned long layout_id = LayoutId::GetId(LayoutId::kRaid6, 1,\n                            6,\n                            LayoutId::k1M,\n                            LayoutId::kCRC32);\n  std::unique_ptr<RaidDpLayout> file(new RaidDpLayout(NULL, layout_id, NULL, NULL,\n                                     \"root://localhost//dummy\", NULL));\n  // Create readV request\n  int num_datasets = 4;\n  char* buff = new char[1024 * 1024];\n  XrdCl::ChunkList readV;\n  XrdCl::ChunkList correct_rdv;\n  std::ostringstream sstr;\n  std::string str_off;\n  std::string str_len;\n  char* ptr_off;\n  char* ptr_len;\n\n  // Loop through all the sets of data in the test environment\n  for (int i = 1; i < num_datasets; i++) {\n    // Read the initial offsets\n    sstr.str(\"\");\n    sstr << \"off\" << i;\n    str_off = gEnv->GetMapping(sstr.str());\n    XrdOucTokenizer tok_off = XrdOucTokenizer((char*) str_off.c_str());\n    // Read the initial lengths\n    sstr.str(\"\");\n    sstr << \"len\" << i;\n    str_len = gEnv->GetMapping(sstr.str());\n    XrdOucTokenizer tok_len = XrdOucTokenizer((char*) str_len.c_str());\n    ptr_off = tok_off.GetLine();\n    ptr_len = tok_len.GetLine();\n\n    while ((ptr_off = tok_off.GetToken()) && (ptr_len = tok_len.GetToken())) {\n      readV.push_back(XrdCl::ChunkInfo((uint64_t) atoi(ptr_off),\n                                       (uint32_t) atoi(ptr_len),\n                                       (void*) 0));\n    }\n\n    int indx = 0;\n    std::vector<XrdCl::ChunkList> result = ((RainMetaLayout*)\n                                            file.get())->SplitReadV(readV);\n\n    // Loop through the answers for each stripe and compare with the correct values\n    for (auto it_stripe = result.begin(); it_stripe != result.end(); ++it_stripe) {\n      //Read the correct answer to compare with\n      sstr.str(\"\");\n      sstr << \"off\" << i << \"_stripe\" << indx;\n      str_off = gEnv->GetMapping(sstr.str());\n      tok_off = XrdOucTokenizer((char*)str_off.c_str());\n      sstr.str(\"\");\n      sstr << \"len\" << i << \"_stripe\" << indx;\n      str_len = gEnv->GetMapping(sstr.str());\n      tok_len = XrdOucTokenizer((char*)str_len.c_str());\n      ptr_off = tok_off.GetLine();\n      ptr_len = tok_len.GetLine();\n      correct_rdv.clear();\n\n      while ((ptr_off = tok_off.GetToken()) && (ptr_len = tok_len.GetToken())) {\n        correct_rdv.push_back(XrdCl::ChunkInfo((uint64_t) atoi(ptr_off),\n                                               (uint32_t) atoi(ptr_len),\n                                               buff));\n      }\n\n      // Test same length\n      ASSERT_EQ(correct_rdv.size(), it_stripe->size());\n\n      // Test each individual chunk\n      for (auto it_chunk = it_stripe->begin(), it_resp = correct_rdv.begin();\n           it_chunk != it_stripe->end(); ++it_chunk, ++it_resp) {\n        ASSERT_EQ(it_resp->offset, it_chunk->offset);\n        ASSERT_EQ(it_resp->length, it_chunk->length);\n      }\n\n      indx++;\n    }\n\n    readV.clear();\n  }\n\n  // Free memory\n  delete[] buff;\n}\n\n//----------------------------------------------------------------------------\n//! Test the deletion of a file to which the delete flag is sent using the\n//! Fcntl function on the file object\n//----------------------------------------------------------------------------\nTEST(FstFileTest, DeleteFlagTest)\n{\n  using namespace XrdCl;\n  std::unique_ptr<File> mFile(new File());\n  // Initialize\n  std::string address = \"root://root@\" + gEnv->GetMapping(\"server\");\n  std::string file_path = gEnv->GetMapping(\"dummy_file\");\n  // Validate URL\n  URL url(address);\n  ASSERT_TRUE(url.IsValid());\n  // Open file\n  std::string file_url = address + \"/\" + file_path;\n  GLOG << \"Opening file: \" << file_url << std::endl;\n  XRootDStatus status = mFile->Open(file_url,\n                                    OpenFlags::Delete | OpenFlags::Update,\n                                    Access::Mode::UR | Access::Mode::UW);\n  ASSERT_TRUE(status.IsOK());\n  // Fill buffer with random characters\n  uint32_t block_size = 4 * 1024;\n  char* buffer = new char[block_size];\n  std::ifstream urandom(\"/dev/urandom\", std::ios::in | std::ios::binary);\n  urandom.read(buffer, block_size);\n  urandom.close();\n  // Write data to the file\n  GLOG << \"Performing write operation\" << std::endl;\n\n  for (int i = 0; i < 10; i++) {\n    uint64_t offset = (uint64_t) i * block_size;\n    status = mFile->Write(offset, block_size, buffer);\n\n    if (!status.IsOK()) {\n      std::cerr << \"Error while writing at offset: \" << offset << std::endl;\n      std::terminate();\n    }\n  }\n\n  // Send the delete command using Fcntl\n  XrdCl::Buffer* response;\n  XrdCl::Buffer arg;\n  arg.FromString(\"delete\");\n  GLOG << \"Sending delete command using Fcntl\" << std::endl;\n  status = mFile->Fcntl(arg, response);\n  ASSERT_TRUE(status.IsOK());\n  delete response;\n  // Close the file and then test for its existence\n  GLOG << \"Attempt to reopen deleted file\" << std::endl;\n  status = mFile->Close();\n  ASSERT_FALSE(status.IsOK());\n  status = mFile->Open(file_url, OpenFlags::Read, Access::Mode::None);\n  ASSERT_FALSE(status.IsOK());\n  delete[] buffer;\n}\n\n//------------------------------------------------------------------------------\n// Read async test\n//------------------------------------------------------------------------------\nTEST(FstFileTest, ReadAsyncTest)\n{\n  // Initialize\n  std::string address = \"root://root@\" + gEnv->GetMapping(\"server\");\n  std::string file_path = gEnv->GetMapping(\"replica_file\");\n  // Validate URL\n  XrdCl::URL url(address);\n  ASSERT_TRUE(url.IsValid());\n  // Open file\n  std::string file_url = address + \"/\" + file_path;\n  GLOG << \"Opening file: \" << file_url << std::endl;\n  std::unique_ptr<eos::fst::XrdIo> file(new eos::fst::XrdIo(file_url));\n  ASSERT_EQ(file->fileOpen(SFS_O_RDONLY), 0);\n  // Get file size\n  struct stat buff = {0};\n  ASSERT_EQ(file->fileStat(&buff), 0);\n  GLOG << \"Stat size: \" << buff.st_size << std::endl;\n  uint64_t file_size = (uint64_t) buff.st_size;\n  off_t buff_size = 4 * 1024;\n  char* buffer = new char[buff_size];\n  uint64_t offset = 0;\n  GLOG << \"Performing async read operation\" << std::endl;\n\n  while (offset < file_size) {\n    off_t read_size = file->fileReadAsync(offset, buffer, buff_size);\n    ASSERT_EQ(buff_size, read_size);\n    offset += buff_size;\n  }\n\n  off_t read_size = file->fileRead(offset, buffer, buff_size);\n  ASSERT_EQ(read_size, 0);\n  ASSERT_EQ(file->fileClose(), 0);\n  delete[] buffer;\n}\n\nEOSFSTTEST_NAMESPACE_END\n"
  },
  {
    "path": "unit_tests/fst/XrdFstOfsTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: XrdFstOfsTests.cc\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#define IN_TEST_HARNESS\n#include \"fst/XrdFstOfs.hh\"\n#undef IN_TEST_HARNESS\n\n//------------------------------------------------------------------------------\n// Test parsing of simulation error offset\n//------------------------------------------------------------------------------\nTEST(XrdFstOfs, ParseSimulationErrOffset)\n{\n  eos::fst::XrdFstOfs ofs;\n  ASSERT_EQ(ofs.GetSimulationErrorOffset(\"dummy\"), 0);\n  ASSERT_EQ(ofs.GetSimulationErrorOffset(\"io_read\"), 0);\n  ASSERT_EQ(ofs.GetSimulationErrorOffset(\"io_read_10\"), 10);\n  ASSERT_EQ(ofs.GetSimulationErrorOffset(\"io_read_10B\"), 10);\n  ASSERT_EQ(ofs.GetSimulationErrorOffset(\"io_read_10k\"), 10000);\n  ASSERT_EQ(ofs.GetSimulationErrorOffset(\"io_write_10M\"), 10000000);\n  ASSERT_EQ(ofs.GetSimulationErrorOffset(\"io_write_4G\"), 4000000000);\n  ofs.SetSimulationError(\"dummy\");\n  ASSERT_FALSE(ofs.mSimIoReadErr);\n  ASSERT_EQ(ofs.mSimErrIoReadOff, 0ull);\n  ASSERT_FALSE(ofs.mSimIoWriteErr);\n  ASSERT_EQ(ofs.mSimErrIoWriteOff, 0ull);\n  ASSERT_FALSE(ofs.mSimXsReadErr);\n  ASSERT_FALSE(ofs.mSimXsWriteErr);\n  ofs.SetSimulationError(\"io_read_4M\");\n  ASSERT_TRUE(ofs.mSimIoReadErr);\n  ASSERT_EQ(ofs.mSimErrIoReadOff, 4000000);\n  ofs.SetSimulationError(\"io_write_5B\");\n  ASSERT_TRUE(ofs.mSimIoWriteErr);\n  ASSERT_EQ(ofs.mSimErrIoWriteOff, 5);\n  ofs.SetSimulationError(\"xs_read\");\n  ASSERT_TRUE(ofs.mSimXsReadErr);\n  ofs.SetSimulationError(\"xs_write\");\n  ASSERT_TRUE(ofs.mSimXsWriteErr);\n  ofs.SetSimulationError(\"fmd_open\");\n  ASSERT_TRUE(ofs.mSimFmdOpenErr);\n}\n"
  },
  {
    "path": "unit_tests/fst/XrdFstOssFileTest.cc",
    "content": "//------------------------------------------------------------------------------\n// File: XrdFstOssFileTest.cc\n// Author: Jozsef Makai <jmakai@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#define IN_TEST_HARNESS\n#include \"fst/XrdFstOssFile.hh\"\n#undef IN_TEST_HARNESS\n\n#include \"gtest/gtest.h\"\n#include \"unit_tests/fst/TestEnv.hh\"\n#include <XrdOuc/XrdOucTokenizer.hh>\n\nusing namespace eos::fst;\nusing namespace eos::fst::test;\n\nclass XrdFstOssFileTest : public ::testing::Test\n{\npublic:\n  XrdFstOssFile* ossfile = nullptr;\n  std::map<std::string, std::string> mMapParam;\n\n  virtual void SetUp() override\n  {\n    // Test sequences for the AlingBuffer method\n    // Test set 1\n    mMapParam.insert(std::make_pair(\"align1_off\", \"4095\"));\n    mMapParam.insert(std::make_pair(\"align1_len\", \"8194\"));\n    mMapParam.insert(std::make_pair(\"align1_resp_off\", \"0, 4096, 12288\"));\n    mMapParam.insert(std::make_pair(\"align1_resp_len\", \"4096, 8192, 4096\"));\n    // Test set 2\n    mMapParam.insert(std::make_pair(\"align2_off\", \"4095\"));\n    mMapParam.insert(std::make_pair(\"align2_len\", \"1048576\"));\n    mMapParam.insert(std::make_pair(\"align2_resp_off\", \"0 4096 1048576\"));\n    mMapParam.insert(std::make_pair(\"align2_resp_len\", \"4096 1044480 4096\"));\n    // Test set 3\n    mMapParam.insert(std::make_pair(\"align3_off\", \"4096\"));\n    mMapParam.insert(std::make_pair(\"align3_len\", \"1048576\"));\n    mMapParam.insert(std::make_pair(\"align3_resp_off\", \"4096\"));\n    mMapParam.insert(std::make_pair(\"align3_resp_len\", \"1048576\"));\n    // Test set 4\n    mMapParam.insert(std::make_pair(\"align4_off\", \"20971520\"));\n    mMapParam.insert(std::make_pair(\"align4_len\", \"2048\"));\n    mMapParam.insert(std::make_pair(\"align4_resp_off\", \"20971520\"));\n    mMapParam.insert(std::make_pair(\"align4_resp_len\", \"4096\"));\n    // Test set 5\n    mMapParam.insert(std::make_pair(\"align5_off\", \"20972544\"));\n    mMapParam.insert(std::make_pair(\"align5_len\", \"3072\"));\n    mMapParam.insert(std::make_pair(\"align5_resp_off\", \"20971520\"));\n    mMapParam.insert(std::make_pair(\"align5_resp_len\", \"4096\"));\n    // Test set 6\n    mMapParam.insert(std::make_pair(\"align6_off\", \"20972544\"));\n    mMapParam.insert(std::make_pair(\"align6_len\", \"4096\"));\n    mMapParam.insert(std::make_pair(\"align6_resp_off\", \"20971520 20975616\"));\n    mMapParam.insert(std::make_pair(\"align6_resp_len\", \"4096 4096\"));\n    // Test set 7\n    mMapParam.insert(std::make_pair(\"align7_off\", \"20972544\"));\n    mMapParam.insert(std::make_pair(\"align7_len\", \"9216\"));\n    mMapParam.insert(std::make_pair(\"align7_resp_off\",\n                                    \"20971520 20975616 20979712\"));\n    mMapParam.insert(std::make_pair(\"align7_resp_len\", \"4096 4096 4096\"));\n    // Test set 8\n    mMapParam.insert(std::make_pair(\"align8_off\", \"10\"));\n    mMapParam.insert(std::make_pair(\"align8_len\", \"1025\"));\n    mMapParam.insert(std::make_pair(\"align8_resp_off\", \"0\"));\n    mMapParam.insert(std::make_pair(\"align8_resp_len\", \"4096\"));\n    ossfile = new XrdFstOssFile(\"test_id\");\n  }\n\n  virtual void TearDown() override\n  {\n    delete ossfile;\n    ossfile = nullptr;\n  }\n};\n\nTEST_F(XrdFstOssFileTest, AlignBufferTest)\n{\n  int num_datasets = 9;\n  char* ptr_off, *ptr_len;\n  size_t len_req;\n  off_t off_req;\n  std::string str_off;\n  std::string str_len;\n  std::stringstream sstr;\n  std::vector<XrdOucIOVec> expect_resp;\n  std::shared_ptr<eos::common::Buffer> start_piece, end_piece;\n\n  for (int set = 1; set < num_datasets; ++set) {\n    // Read in the offset and length of the request\n    sstr.str(\"\");\n    sstr << \"align\" << set << \"_off\";\n    off_req = (off_t) atoi(mMapParam[sstr.str()].c_str());\n    sstr.str(\"\");\n    sstr << \"align\" << set << \"_len\";\n    len_req = (size_t) atoi(mMapParam[sstr.str()].c_str());\n    char* buffer = new char[len_req];\n    // Read the correct answer to compare with\n    sstr.str(\"\");\n    sstr << \"align\" << set << \"_resp_off\";\n    str_off = mMapParam[sstr.str()];\n    XrdOucTokenizer tok_off = XrdOucTokenizer((char*) str_off.c_str());\n    sstr.str(\"\");\n    sstr << \"align\" << set << \"_resp_len\";\n    str_len = mMapParam[sstr.str()];\n    XrdOucTokenizer tok_len = XrdOucTokenizer((char*) str_len.c_str());\n    ptr_off = tok_off.GetLine();\n    ptr_len = tok_len.GetLine();\n    expect_resp.clear();\n\n    while ((ptr_off = tok_off.GetToken()) && (ptr_len = tok_len.GetToken())) {\n      XrdOucIOVec expect_piece = {atol(ptr_off), atoi(ptr_len), 0, 0};\n      expect_resp.push_back(expect_piece);\n    }\n\n    // Compute the alignment\n    std::vector<XrdOucIOVec> resp =\n      ossfile->AlignBuffer(buffer, off_req, len_req, start_piece, end_piece);\n    EXPECT_EQ(expect_resp.size(), resp.size());\n\n    for (uint32_t indx = 0; indx < resp.size(); ++indx) {\n      EXPECT_EQ(expect_resp[indx].offset, resp[indx].offset);\n      EXPECT_EQ(expect_resp[indx].size, resp[indx].size);\n    }\n\n    delete[] buffer;\n  }\n}\n"
  },
  {
    "path": "unit_tests/fst/XrdIoTests.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file XrdIoTests.cc\n//! @author Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#define IN_TEST_HARNESS\n#include \"fst/io/xrd/XrdIo.hh\"\n#undef IN_TEST_HARNESS\n#include \"fst/checksum/Adler.hh\"\n#include \"TestEnv.hh\"\n#include <string.h>\n\n//------------------------------------------------------------------------------\n// MockSimpleHandler that can throw errors at different offsets\n//------------------------------------------------------------------------------\nclass MockSimpleHandler: public eos::fst::SimpleHandler\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  MockSimpleHandler(uint64_t fail_offset = 0, uint64_t offset = 0,\n                    int32_t length = 0, bool isWrite = false):\n    SimpleHandler(offset, length, isWrite),\n    mFailOffset(fail_offset)\n  {}\n\n  //----------------------------------------------------------------------------\n  //! Handle response\n  //!\n  //! @param pStatus status of the response\n  //! @param pResponse object containing extra info about the response\n  //----------------------------------------------------------------------------\n  virtual void HandleResponse(XrdCl::XRootDStatus* pStatus,\n                              XrdCl::AnyObject* pResponse)\n  {\n    if (mFailOffset &&\n        (mOffset <= mFailOffset) &&\n        (mOffset + mLength > mFailOffset)) {\n      // Do some extra check for the read case\n      if ((mIsWrite == false) && (pResponse)) {\n        XrdCl::ChunkInfo* chunk = 0;\n        pResponse->Get(chunk);\n        mRespLength = chunk->length;\n      }\n\n      GLOG << \"Failing at offset \" << mOffset << \" and length: \" << mLength\n           << \" fail_offset: \" << mFailOffset << std::endl;\n      mCond.Lock();\n      // Note: we return false on purpose\n      mRespOK = false;\n      mReqDone = true;\n      mCond.Signal(); //signal\n      mCond.UnLock();\n      delete pStatus;\n\n      if (pResponse) {\n        delete pResponse;\n      }\n\n      return;\n    } else {\n      return SimpleHandler::HandleResponse(pStatus, pResponse);\n    }\n  }\n\n  uint64_t mFailOffset {0ull};\n};\n\nTEST(XrdIo, BasicPrefetch)\n{\n  //auto& logging = eos::common::Logging::GetInstance();\n  //logging.SetLogPriority(LOG_DEBUG);\n  using namespace eos::common;\n  std::set<int64_t> read_sizes {11, 23, 4 * KB, 99999, 1 * MB};\n  std::string address = \"root://root@\" + gEnv->GetMapping(\"server\");\n  std::string file_path = gEnv->GetMapping(\"prefetch_file\");\n  // Validate URL\n  XrdCl::URL url(address);\n  ASSERT_TRUE(url.IsValid());\n  std::string file_url = address + \"/\" + file_path + \"?fst.readahead=true\";\n  std::unique_ptr<eos::fst::XrdIo> file {new eos::fst::XrdIo(file_url)};\n  struct stat info;\n  ASSERT_EQ(file->fileOpen(SFS_O_RDONLY), 0);\n  ASSERT_EQ(file->fileStat(&info), 0);\n  int64_t offset {0ll};\n  std::unique_ptr<char[]> buffer {new char[1 * MB]};\n  std::unique_ptr<char[]> file_in_mem {new char[info.st_size]};\n\n  for (const auto length : read_sizes) {\n    while (offset < info.st_size) {\n      if (offset + length > info.st_size) {\n        break;\n      }\n\n      ASSERT_EQ(file->fileReadPrefetch(offset, buffer.get(), length), length);\n      memcpy(file_in_mem.get() + offset, buffer.get(), length);\n      offset += length;\n    }\n\n    if (offset < info.st_size) {\n      ASSERT_EQ(file->fileReadPrefetch(offset, buffer.get(), length),\n                info.st_size - offset);\n      memcpy(file_in_mem.get() + offset, buffer.get(), info.st_size - offset);\n    }\n\n    eos::fst::Adler checksum;\n    checksum.Add(file_in_mem.get(), info.st_size, 0);\n    checksum.Finalize();\n    memset(file_in_mem.get(), 0, info.st_size);\n    offset = 0ull;\n    GLOG << \"Read block size: \" << length << std::endl;\n    GLOG << \"Prefetched blocks: \" << file->mPrefetchBlocks << std::endl;\n    GLOG << \"Prefech hits: \" << file->mPrefetchHits << std::endl;\n    GLOG << \"Checksum: \" << checksum.GetHexChecksum() << std::endl;\n    ASSERT_EQ(file->mPrefetchBlocks,\n              std::ceil((info.st_size - length + 1) * 1.0 / file->mBlocksize));\n    ASSERT_EQ(file->mPrefetchHits,\n              std::ceil((info.st_size - length + 1) * 1.0 / length));\n    ASSERT_STREQ(checksum.GetHexChecksum(), \"b25bae07\");\n    ASSERT_TRUE(file->mDoReadahead);\n    // Reset prefetch counters\n    file->mPrefetchHits = 0ull;\n    file->mPrefetchBlocks = 0ull;\n    ASSERT_EQ(file->fileWaitAsyncIO(), 0);\n  }\n}\n\nTEST(XrdIo, FailPrefetchInFlight)\n{\n  //auto& logging = eos::common::Logging::GetInstance();\n  //logging.SetLogPriority(LOG_DEBUG);\n  using namespace eos::common;\n  std::set<int64_t> read_sizes {1171, 4 * KB, 99999};\n  std::string address = \"root://root@\" + gEnv->GetMapping(\"server\");\n  std::string file_path = gEnv->GetMapping(\"prefetch_file\");\n  // Validate URL\n  XrdCl::URL url(address);\n  ASSERT_TRUE(url.IsValid());\n  std::string file_url = address + \"/\" + file_path + \"?fst.readahead=true\";\n  std::unique_ptr<eos::fst::XrdIo> file {new eos::fst::XrdIo(file_url)};\n  struct stat info;\n  ASSERT_EQ(file->fileOpen(SFS_O_RDONLY), 0);\n  ASSERT_EQ(file->fileStat(&info), 0);\n  int64_t offset {0ll};\n  std::unique_ptr<char[]> buffer {new char[1 * MB]};\n  std::unique_ptr<char[]> file_in_mem {new char[info.st_size]};\n  // Pre-fill the prefetch block with a custom handler that returns an error\n  // for the given offset\n  std::list<int64_t> err_offsets {8 * MB, 9 * MB + 123, 14 * MB};\n\n  for (const auto err_off : err_offsets) {\n    // Clear any readahead blocks\n    while (!file->mQueueBlocks.empty()) {\n      delete file->mQueueBlocks.front();\n      file->mQueueBlocks.pop();\n    }\n\n    // Add new readahead blocks with custom error at offset\n    for (unsigned int i = 0; i < file->mNumRdAheadBlocks; i++) {\n      file->mQueueBlocks.push(new eos::fst::ReadaheadBlock(file->mBlocksize,\n                              nullptr, new MockSimpleHandler(err_off)));\n    }\n\n    // Run test with different read size requests\n    for (const auto length : read_sizes) {\n      while (offset < info.st_size) {\n        if (offset + length > info.st_size) {\n          break;\n        }\n\n        ASSERT_EQ(file->fileReadPrefetch(offset, buffer.get(), length), length);\n        memcpy(file_in_mem.get() + offset, buffer.get(), length);\n        offset += length;\n      }\n\n      if (offset < info.st_size) {\n        ASSERT_EQ(file->fileReadPrefetch(offset, buffer.get(), length),\n                  info.st_size - offset);\n        memcpy(file_in_mem.get() + offset, buffer.get(), info.st_size - offset);\n      }\n\n      ASSERT_EQ(file->fileWaitAsyncIO(), 0);\n      eos::fst::Adler checksum;\n      checksum.Add(file_in_mem.get(), info.st_size, 0);\n      checksum.Finalize();\n      memset(file_in_mem.get(), 0, info.st_size);\n      offset = 0ull;\n      GLOG << \"Read block size: \" << length << std::endl;\n      GLOG << \"Prefetched blocks: \" << file->mPrefetchBlocks << std::endl;\n      GLOG << \"Prefech hits: \" << file->mPrefetchHits << std::endl;\n      GLOG << \"Checksum: \" << checksum.GetHexChecksum() << std::endl;\n      ASSERT_EQ(file->mPrefetchBlocks,\n                std::ceil((err_off - length + 1) * 1.0 / file->mBlocksize));\n      ASSERT_EQ(file->mPrefetchHits,\n                std::ceil((err_off - length - file->mBlocksize + 1) * 1.0 / length));\n      ASSERT_STREQ(checksum.GetHexChecksum(), \"b25bae07\");\n      ASSERT_FALSE(file->mDoReadahead);\n      // Reset prefetch counters and prefetch flag\n      file->mPrefetchHits = 0ull;\n      file->mPrefetchBlocks = 0ull;\n      file->mDoReadahead = true;\n    }\n  }\n}\n"
  },
  {
    "path": "unit_tests/fst/main_fst.cc",
    "content": "//------------------------------------------------------------------------------\n// File: main.cc\n// Author: Mihai Patrascoiu <mihai.patrascoiu@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"TestEnv.hh\"\n#include <XrdCl/XrdClURL.hh>\n\nstd::unique_ptr<eos::fst::test::TestEnv> gEnv {nullptr};\neos::fst::test::GTest_Logger gLogger(false);\n\n//------------------------------------------------------------------------------\n// Main function for the eos-fst-test executable.\n// Performs write and read operations of a file on a plain and raiddp setup.\n// Also tests the partition monitoring functionality.\n//\n// Note: a running EOS instance is required for this test to run successfully\n//------------------------------------------------------------------------------\nint main(int argc, char* argv[])\n{\n  using namespace eos::fst::test;\n  // Initialize GTest\n  testing::InitGoogleTest(&argc, argv);\n  int c;\n  bool verbose = false;\n  std::string endpoint;\n  std::string usage =\n    \"Usage: eos-unit-tests-with-instance [-v] [-h] [-n <endpoint>]\\\n                           \\nTests the writing and downloading of a file on a plain and raiddp setup. \\\n                           \\nTests the partition monitoring functionality. \\\n                           \\nNote: a running EOS instance is required for this test to run successfully \\\n                           \\n\\t\\t            -v : verbose mode          \\\n                           \\n\\t\\t            -h : display help          \\\n                           \\n\\t\\t -n <endpoint> : EOS endpoint where tests are run (e.g root://localhost//eos/dev/test/) \\\n                           \\n\";\n\n  // Parse remaining arguments\n  while ((c = getopt(argc, argv, \"n:vh\")) != -1) {\n    switch (c) {\n    case 'n': { // Register endpoint\n      endpoint = optarg;\n      break;\n    }\n\n    case 'v': { // Enable verbose mode\n      verbose = true;\n      break;\n    }\n\n    case 'h': { // Display help text\n      std::cout << usage << std::endl;\n      exit(1);\n    }\n\n    case ':': {\n      std::cout << usage << std::endl;\n      exit(1);\n    }\n    }\n  }\n\n  // Trim starting and trailing '/'\n  XrdCl::URL url(endpoint);\n\n  if (!url.IsValid()) {\n    std::cerr << \"error: Invalid endpoint - \" << endpoint << std::endl;\n    exit(1);\n  }\n\n  // Prepare global environment\n  gEnv.reset(new TestEnv(endpoint));\n  gLogger.SetEnabled(verbose);\n  return RUN_ALL_TESTS();\n}\n"
  },
  {
    "path": "unit_tests/fusex/StatTests.cc",
    "content": "#include \"fmt/printf.h\"\n#include \"gtest/gtest.h\"\n#include \"fusex/stat/Stat.hh\"\n\n// This file only tests parts of the output, consider adding more test cases\n// when you touch the code to validate that we don't break outputs\nTEST(fusex_stats, default_header)\n{\n  double avg {0};\n  double sig {0};\n  size_t ops  {0};\n  double TotalExec {0};\n  std::string outline =\n    fmt::sprintf(\"%-7s %-32s %3.02f +- %3.02f = %.02fs (%lu ops)\\n\", \"ALL\",\n                 \"Execution Time\", avg, sig,\n                 TotalExec / 1000.0,\n                 ops);\n  EXPECT_EQ(outline,\n            \"ALL     Execution Time                   0.00 +- 0.00 = 0.00s (0 ops)\\n\");\n}\n\nTEST(fusex_stats, header)\n{\n  double avg = 1.232;\n  double sig = 1.658;\n  size_t ops = 1 << 20;\n  double TotalExec = 3.14;\n  std::string outline =\n    fmt::sprintf(\"%-7s %-32s %3.02f +- %3.02f = %.02fs (%lu ops)\\n\", \"ALL\",\n                 \"Execution Time\", avg, sig,\n                 TotalExec / 1000.0,\n                 ops);\n  EXPECT_EQ(outline,\n            \"ALL     Execution Time                   1.23 +- 1.66 = 0.00s (1048576 ops)\\n\");\n  avg = 12878.34;\n  sig = 2167081.76;\n  TotalExec = 36651521.02;\n  ops = 1375618;\n  outline = fmt::sprintf(\"%-7s %-32s %3.02f +- %3.02f = %.02fs (%lu ops)\\n\",\n                         \"ALL\",\n                         \"Execution Time\", avg, sig, TotalExec / 1000.0, ops);\n  std::string expected = \"ALL     Execution Time                   12878.34 +- \"\n                         \"2167081.76 = 36651.52s (1375618 ops)\\n\";\n  EXPECT_EQ(expected, outline);\n}\n\nTEST(fusex_stats, simple_float_printf)\n{\n  Stat s;\n  const char* tag = \"list\";\n\n  for (int i = 0; i < 5; i++) {\n    s.Add(tag, 0, 0, 0);\n  }\n\n  constexpr std::string_view TOTAL_AVG_FMT = \"%3.02f\";\n  char c_a5[1024];\n  sprintf(c_a5, \"%3.02f\", s.GetTotalAvg5(tag));\n  std::string a5 = fmt::sprintf(TOTAL_AVG_FMT, s.GetTotalAvg5(tag));\n  EXPECT_STREQ(c_a5, a5.c_str());\n  double pi = 3.142857;\n  sprintf(c_a5, \"%3.02f\", pi);\n  EXPECT_STREQ(c_a5, fmt::sprintf(TOTAL_AVG_FMT, pi).c_str());\n}\n\n\nTEST(fusex_stats, cmd_stats)\n{\n  Stat s;\n  const char* tag = \"list\";\n\n  for (int i = 0; i < 5; i++) {\n    s.Add(tag, 0, 0, 0);\n  }\n\n  constexpr std::string_view TOTAL_AVG_FMT = \"%3.02f\";\n  std::string a5 = fmt::sprintf(TOTAL_AVG_FMT, s.GetTotalAvg5(tag));\n  std::string a60 = fmt::sprintf(TOTAL_AVG_FMT, s.GetTotalAvg60(tag));\n  std::string a300 = fmt::sprintf(TOTAL_AVG_FMT, s.GetTotalAvg300(tag));\n  std::string a3600 = fmt::sprintf(TOTAL_AVG_FMT, s.GetTotalAvg3600(tag));\n  float avg{0}, sig{0}, total{0};\n  uint64_t total_s {0};\n  std::string outline = fmt::sprintf(\"uid=all gid=all cmd=%s total=%llu 5s=%s \"\n                                     \"60s=%s 300s=%s \"\n                                     \"3600s=%s exec=%f execsig=%f cumulated=%f\\n\",\n                                     tag, total_s, a5, a60, a300, a3600, avg, sig,\n                                     total);\n  EXPECT_EQ(outline,\n            \"uid=all gid=all cmd=list total=0 5s=0.00 60s=0.00 300s=0.00\"\n            \" 3600s=0.00 exec=0.000000 execsig=0.000000 cumulated=0.000000\\n\");\n  std::string aexec = fmt::sprintf(\"%3.05f\", 0.0);\n  std::string aexecsig = fmt::sprintf(\"%3.05f\", 0.0);\n  std::string atotal = fmt::sprintf(\"%04.02f\", 0.0);\n  std::string out2 =\n    fmt::sprintf(\"ALL     %-32s %12llu %8s %8s %8s %8s %8s +- %-10s = %-10s\\n\",\n                 tag, total_s, a5, a60, a300, a3600, aexec, aexecsig, atotal);\n  EXPECT_EQ(out2,\n            \"ALL     list                                        \"\n            \"0     0.00     0.00     0.00     0.00  0.00000 +- 0.00000    = 0.00      \\n\");\n}\n"
  },
  {
    "path": "unit_tests/mgm/AccessTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: AccessTests.cc\n// Author: Elvin-Alin Sindrilaru <esindril at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"mgm/access/Access.hh\"\n#include \"mgm/acl/Acl.hh\"\n#include \"mgm/proc/admin/AccessCmd.hh\"\n#include \"mgm/auth/AccessChecker.hh\"\n#include \"common/Definitions.hh\"\n#include \"namespace/ns_quarkdb/ContainerMD.hh\"\n#include \"namespace/ns_quarkdb/FileMD.hh\"\n#include \"unit_tests/common/MappingTestFixture.hh\"\nusing namespace eos;\n\n//------------------------------------------------------------------------------\n// Test basic access functionality\n//------------------------------------------------------------------------------\nTEST_F(MappingTestF, Access_SetRule)\n{\n  using namespace eos::mgm;\n  Access::StallInfo old_stall;\n  Access::StallInfo new_stall(\"*\", \"60\", \"test stall\", true);\n  ASSERT_EQ(false, Access::gStallGlobal);\n  // Set new stall state\n  Access::SetStallRule(new_stall, old_stall);\n  // Do checks without taking the lock as this is just for test purposes\n  ASSERT_STREQ(\"60\", Access::gStallRules[new_stall.mType].c_str());\n  ASSERT_STREQ(\"test stall\", Access::gStallComment[new_stall.mType].c_str());\n  ASSERT_EQ(new_stall.mIsGlobal, Access::gStallGlobal);\n  Access::StallInfo empty_stall;\n  // Setting an empty stall should not change anything\n  Access::SetStallRule(empty_stall, old_stall);\n  ASSERT_STREQ(\"60\", Access::gStallRules[new_stall.mType].c_str());\n  ASSERT_STREQ(\"test stall\", Access::gStallComment[new_stall.mType].c_str());\n  ASSERT_EQ(new_stall.mIsGlobal, Access::gStallGlobal);\n  // Revert to initial state\n  Access::StallInfo tmp_stall;\n  Access::SetStallRule(old_stall, tmp_stall);\n  ASSERT_TRUE(Access::gStallRules.count(old_stall.mType) == 0);\n  ASSERT_TRUE(Access::gStallComment.count(old_stall.mType) == 0);\n  ASSERT_EQ(old_stall.mIsGlobal, Access::gStallGlobal);\n}\n\nIContainerMDPtr makeContainer(uid_t uid, gid_t gid, int mode)\n{\n  IContainerMDPtr cont(new eos::QuarkContainerMD());\n  cont->setCUid(uid);\n  cont->setCGid(gid);\n  cont->setMode(mode);\n  return cont;\n}\n\nIFileMDPtr makeFile(uid_t uid, gid_t gid, int mode)\n{\n  IFileMDPtr file(new eos::QuarkFileMD());\n  file->setCUid(uid);\n  file->setCGid(gid);\n  file->setFlags(mode);\n  return file;\n}\n\neos::common::VirtualIdentity makeIdentity(uid_t uid, gid_t gid)\n{\n  eos::common::VirtualIdentity vid;\n  vid.uid = uid;\n  vid.gid = gid;\n  return vid;\n}\n\nTEST_F(MappingTestF, AccessChecker_UserRWX)\n{\n  IContainerMDPtr cont = makeContainer(1234, 9999,\n                                       S_IFDIR | S_IRWXU);\n  // No access for \"other\"\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(\n                 cont.get(), mgm::Acl(), R_OK, makeIdentity(3333, 3333)));\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(\n                 cont.get(), mgm::Acl(), W_OK, makeIdentity(3333, 3333)));\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(\n                 cont.get(), mgm::Acl(), X_OK, makeIdentity(3333, 3333)));\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(\n                 cont.get(), mgm::Acl(), R_OK | W_OK | X_OK, makeIdentity(3333, 3333)));\n  // No access for \"group\"\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(\n                 cont.get(), mgm::Acl(), R_OK, makeIdentity(3333, 9999)));\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(\n                 cont.get(), mgm::Acl(), W_OK, makeIdentity(3333, 9999)));\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(\n                 cont.get(), mgm::Acl(), X_OK, makeIdentity(3333, 9999)));\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(\n                 cont.get(), mgm::Acl(), R_OK | W_OK | X_OK, makeIdentity(3333, 9999)));\n  // Allow access for user\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), mgm::Acl(), R_OK, makeIdentity(1234, 8888)));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), mgm::Acl(), W_OK, makeIdentity(1234, 8888)));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), mgm::Acl(), X_OK, makeIdentity(1234, 8888)));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), mgm::Acl(), R_OK | W_OK | X_OK, makeIdentity(1234, 8888)));\n}\n\nTEST_F(MappingTestF, AccessChecker_rwxrwxrx)\n{\n  IContainerMDPtr cont = makeContainer(1234, 9999,\n                                       S_IFDIR | S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);\n  // rwx for user\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), mgm::Acl(), R_OK, makeIdentity(1234, 8888)));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), mgm::Acl(), W_OK, makeIdentity(1234, 8888)));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), mgm::Acl(), X_OK, makeIdentity(1234, 8888)));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), mgm::Acl(), R_OK | W_OK | X_OK, makeIdentity(1234, 8888)));\n  // rwx for group\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), mgm::Acl(), R_OK, makeIdentity(3333, 9999)));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), mgm::Acl(), W_OK, makeIdentity(3333, 9999)));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), mgm::Acl(), X_OK, makeIdentity(3333, 9999)));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), mgm::Acl(), R_OK | W_OK | X_OK, makeIdentity(3333, 9999)));\n  // rx for other\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), mgm::Acl(), R_OK, makeIdentity(3333, 3333)));\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(\n                 cont.get(), mgm::Acl(), W_OK, makeIdentity(3333, 3333)));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), mgm::Acl(), X_OK, makeIdentity(3333, 3333)));\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(\n                 cont.get(), mgm::Acl(), R_OK | W_OK | X_OK, makeIdentity(3333, 3333)));\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(\n                 cont.get(), mgm::Acl(), R_OK | W_OK, makeIdentity(3333, 3333)));\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(\n                 cont.get(), mgm::Acl(), X_OK | W_OK, makeIdentity(3333, 3333)));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), mgm::Acl(), R_OK | X_OK, makeIdentity(3333, 3333)));\n}\n\nTEST_F(MappingTestF, AccessChecker_WithAclUserRWX)\n{\n  IContainerMDPtr cont = makeContainer(5555, 9999,\n                                       S_IFDIR | S_IRWXU);\n  // no access for other\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(\n                 cont.get(), mgm::Acl(), R_OK, makeIdentity(1234, 8888)));\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(\n                 cont.get(), mgm::Acl(), W_OK, makeIdentity(1234, 8888)));\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(\n                 cont.get(), mgm::Acl(), X_OK, makeIdentity(1234, 8888)));\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(\n                 cont.get(), mgm::Acl(), R_OK | W_OK | X_OK, makeIdentity(1234, 8888)));\n  // .. unless we have an acl\n  eos::common::VirtualIdentity vid1 = makeIdentity(1234, 8888);\n  vid1.allowed_gids = { 8888 };\n  eos::mgm::Acl acl(\"u:1234:rwx\", \"\", vid1, true);\n  ASSERT_TRUE(acl.HasAcl());\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), acl, R_OK, makeIdentity(1234, 8888)));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), acl, W_OK, makeIdentity(1234, 8888)));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), acl, X_OK, makeIdentity(1234, 8888)));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), acl, R_OK | W_OK | X_OK, makeIdentity(1234, 8888)));\n  // .. try passing the extended attributes, instead of the Acl object\n  eos::IContainerMD::XAttrMap xattrmap;\n  xattrmap[\"sys.acl\"] = \"u:1234:rwx\";\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), xattrmap, R_OK, vid1));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), xattrmap, W_OK, vid1));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), xattrmap, X_OK, vid1));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), xattrmap, R_OK | W_OK | X_OK, vid1));\n  // try a group acl ...\n  vid1.allowed_gids = { 8888 };\n  eos::mgm::Acl acl2(\"g:8888:rwx\", \"\", vid1, true);\n  ASSERT_TRUE(acl.HasAcl());\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), acl, R_OK, makeIdentity(1234, 8888)));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), acl, W_OK, makeIdentity(1234, 8888)));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), acl, X_OK, makeIdentity(1234, 8888)));\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), acl, R_OK | W_OK | X_OK, makeIdentity(1234, 8888)));\n}\n\nTEST_F(MappingTestF, AccessChecker_WithPrepare)\n{\n  IContainerMDPtr cont = makeContainer(19229, 9999,\n                                       S_IFDIR | S_IRWXU);\n  eos::common::VirtualIdentity vid1 = makeIdentity(19229, 1489);\n  vid1.allowed_gids = {1489};\n  eos::mgm::Acl acl(\"u:19227:rwx+d,u:19229:rwx+dp,u:19230:rwx+dp\", \"\", vid1,\n                    true);\n  ASSERT_TRUE(acl.HasAcl());\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(\n                cont.get(), acl, P_OK, vid1));\n  // no prepare flag for uid 19229\n  acl = eos::mgm::Acl(\"u:19227:rwx+d,u:19229:rwx+d,u:19230:rwx+dp\", \"\", vid1,\n                      true);\n  ASSERT_TRUE(acl.HasAcl());\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(\n                 cont.get(), acl, P_OK, vid1));\n}\n\nTEST_F(MappingTestF, AccessChecker_FileUserRWX)\n{\n  int dh_mode = 0;\n  IFileMDPtr file = makeFile(5555, 9999, S_IRWXU);\n  ASSERT_TRUE(mgm::AccessChecker::checkFile(file.get(), X_OK, dh_mode,\n              makeIdentity(5555, 1111)));\n  ASSERT_TRUE(mgm::AccessChecker::checkFile(file.get(), R_OK | X_OK, dh_mode,\n              makeIdentity(5555, 1111)));\n  ASSERT_TRUE(mgm::AccessChecker::checkFile(file.get(), W_OK | X_OK, dh_mode,\n              makeIdentity(5555, 1111)));\n  ASSERT_TRUE(mgm::AccessChecker::checkFile(file.get(), R_OK | W_OK | X_OK,\n                                            dh_mode, makeIdentity(5555, 1111)));\n  // different uid than the file in question\n  ASSERT_FALSE(mgm::AccessChecker::checkFile(file.get(), X_OK, dh_mode,\n               makeIdentity(9999, 1111)));\n  ASSERT_FALSE(mgm::AccessChecker::checkFile(file.get(), R_OK | X_OK, dh_mode,\n               makeIdentity(9999, 1111)));\n  ASSERT_FALSE(mgm::AccessChecker::checkFile(file.get(), W_OK | X_OK, dh_mode,\n               makeIdentity(9999, 1111)));\n  ASSERT_FALSE(mgm::AccessChecker::checkFile(file.get(), R_OK | W_OK | X_OK,\n                                             dh_mode, makeIdentity(9999, 1111)));\n  // different uid, same gid, still deny\n  ASSERT_FALSE(mgm::AccessChecker::checkFile(file.get(), X_OK, dh_mode,\n               makeIdentity(9999, 9999)));\n  ASSERT_FALSE(mgm::AccessChecker::checkFile(file.get(), R_OK | X_OK, dh_mode,\n               makeIdentity(9999, 9999)));\n  ASSERT_FALSE(mgm::AccessChecker::checkFile(file.get(), W_OK | X_OK, dh_mode,\n               makeIdentity(9999, 9999)));\n  ASSERT_FALSE(mgm::AccessChecker::checkFile(file.get(), R_OK | W_OK | X_OK,\n                                             dh_mode, makeIdentity(9999, 9999)));\n}\n\nTEST_F(MappingTestF, AccessChecker_FileGroupRWX)\n{\n  // file only allows group access\n  int dh_mode = 0;\n  IFileMDPtr file = makeFile(5555, 9999, S_IRWXG);\n  // file has same uid, and group as file - allow\n  ASSERT_TRUE(mgm::AccessChecker::checkFile(file.get(), X_OK, dh_mode,\n              makeIdentity(5555, 9999)));\n  // file has same uid - deny\n  ASSERT_FALSE(mgm::AccessChecker::checkFile(file.get(), X_OK, dh_mode,\n               makeIdentity(5555, 8888)));\n  // others - deny\n  ASSERT_FALSE(mgm::AccessChecker::checkFile(file.get(), X_OK, dh_mode,\n               makeIdentity(1111, 2222)));\n}\n\nTEST_F(MappingTestF, AccessChecker_FileOtherRWX)\n{\n  // file only allows group access - weird, but possible\n  int dh_mode = 0;\n  IFileMDPtr file = makeFile(5555, 9999, S_IRWXO);\n  // file has same uid/gid, deny\n  ASSERT_FALSE(mgm::AccessChecker::checkFile(file.get(), X_OK, dh_mode,\n               makeIdentity(5555, 9999)));\n  // file has same uid, deny\n  ASSERT_FALSE(mgm::AccessChecker::checkFile(file.get(), X_OK, dh_mode,\n               makeIdentity(5555, 8888)));\n  // file has same gid, deny\n  ASSERT_FALSE(mgm::AccessChecker::checkFile(file.get(), X_OK, dh_mode,\n               makeIdentity(6666, 9999)));\n  // different uid, different gid, grant\n  ASSERT_TRUE(mgm::AccessChecker::checkFile(file.get(), X_OK, dh_mode,\n              makeIdentity(2222, 3333)));\n}\n\nTEST_F(MappingTestF, AccessChecker_FileRename)\n{\n  uid_t uid = 5555;\n  gid_t gid = 9999;\n  IContainerMDPtr cont = makeContainer(uid, gid, S_IFDIR | S_IRWXU);\n  eos::common::VirtualIdentity vid1 = makeIdentity(uid, gid);\n  eos::common::VirtualIdentity vid2 = makeIdentity(uid + 1, gid + 2);\n  eos::mgm::Acl acl(\"\", \"\", vid1, true);\n  int req_mode = W_OK | D_OK;\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(cont.get(), acl, req_mode, vid1));\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(cont.get(), acl, req_mode, vid2));\n  // ACL object is interpreted relative to the vid identity\n  // User vid1 (owner of the file) should be able to delete despite ACL saying otherwise\n  acl.Set(\"u:5555:!d,u:5556:rwx\", \"\", \"\", vid1, true);\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(cont.get(), acl, req_mode, vid1));\n  // vid2 should be able to delete as it has rwx\n  acl.Set(\"u:5555:!d,u:5556:rwx\", \"\", \"\", vid2, true);\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(cont.get(), acl, req_mode, vid2));\n  // Forbid deletion to vid2\n  acl.Set(\"u:5555:!d,u:5556:rwx!d\", \"\", \"\", vid2, true);\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(cont.get(), acl, req_mode, vid2));\n  acl.Set(\"u:5555:!d,u:5556:rwx\", \"\", \"\", vid2, true);\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(cont.get(), acl, req_mode, vid2));\n  // Set directory S_ISVTX bit (sticky bit) which means only owner is\n  // allowed to delete irrespective of the ACLs\n  cont->setMode(S_IFDIR | S_IRWXU | S_ISVTX);\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(cont.get(), acl, req_mode, vid2));\n  acl.Set(\"u:5555:!d,u:5556:rwx\", \"\", \"\", vid1, true);\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(cont.get(), acl, req_mode, vid1));\n  // Reset S_ISVTX bit\n  cont->setMode(S_IFDIR | S_IRWXU);\n  // Throw in the file object into the mix first owned by vid1\n  IFileMDPtr file = makeFile(uid, gid, S_IRWXU);\n  acl.Set(\"\", \"\", \"\", vid1, true);\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(cont.get(), acl, req_mode, vid1) &&\n              mgm::AccessChecker::checkFile(file.get(), req_mode, cont->getMode(), vid1));\n  // vid1 is the owner of the file and directory --> can delete despite ACL saying otherwise\n  acl.Set(\"u:5555:!d\", \"\", \"\", vid1, true);\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(cont.get(), acl, req_mode, vid1) &&\n               mgm::AccessChecker::checkFile(file.get(), req_mode, cont->getMode(), vid1));\n  acl.Set(\"\", \"\", \"\", vid2, true);\n  // vid2 cannot delete as they are not the owner of the file nor the container\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(cont.get(), acl, req_mode, vid2) &&\n               mgm::AccessChecker::checkFile(file.get(), req_mode, cont->getMode(), vid2));\n  acl.Set(\"u:5555:!d,u:5556:rwx\", \"\", \"\", vid2, true);\n  // vid2 is not the owner of the container but has the rwx ACL --> can delete\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(cont.get(), acl, req_mode, vid2) &&\n              mgm::AccessChecker::checkFile(file.get(), req_mode, cont->getMode(), vid2));\n  acl.Set(\"u:5555:!d,u:5556:rwx!d\", \"\", \"\", vid2, true);\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(cont.get(), acl, req_mode, vid2) &&\n              mgm::AccessChecker::checkFile(file.get(), req_mode, cont->getMode(), vid2));\n  // Set directory S_ISVTX bit (sticky bit) which means only owner is\n  // allowed to delete irrespective of the ACLs\n  cont->setMode(S_IFDIR | S_IRWXU | S_ISVTX);\n  acl.Set(\"\", \"\", \"\", vid1, true);\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(cont.get(), acl, req_mode, vid1) &&\n              mgm::AccessChecker::checkFile(file.get(), req_mode, cont->getMode(), vid1));\n  acl.Set(\"u:5555:!d\", \"\", \"\", vid1, true);\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(cont.get(), acl, req_mode, vid1) &&\n              mgm::AccessChecker::checkFile(file.get(), req_mode, cont->getMode(), vid1));\n  acl.Set(\"u:5555:!d,u:5556:rwx\", \"\", \"\", vid2, true);\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(cont.get(), acl, req_mode, vid2) &&\n               mgm::AccessChecker::checkFile(file.get(), req_mode, cont->getMode(), vid2));\n  acl.Set(\"u:5555:!d,u:5556:rwx+d\", \"\", \"\", vid2, true);\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(cont.get(), acl, req_mode, vid2) &&\n               mgm::AccessChecker::checkFile(file.get(), req_mode, cont->getMode(), vid2));\n  // A user owning the file should be able to delete/rename a file regardless of the ACL\n  // provided that the parent container has \"w\" permission\n  cont->setMode(S_IFDIR | S_IRWXU);\n  acl.Set(\"u:5555:!d\",\"\",\"\",vid1,true);\n  ASSERT_TRUE(mgm::AccessChecker::checkContainer(cont.get(),acl,req_mode,vid1));\n  cont->setMode(S_IFDIR);\n  // Container read-only but no ACL, vid1 cannot delete\n  acl.Set(\"\",\"\",\"\",vid1,true);\n  ASSERT_FALSE(mgm::AccessChecker::checkContainer(cont.get(),acl,req_mode,vid1));\n}\n\n\nTEST(Access, ProcessRuleKey)\n{\n  ASSERT_STREQ(\"\", eos::mgm::ProcessRuleKey(\"threads:\").c_str());\n  ASSERT_STREQ(\"threads:max\", eos::mgm::ProcessRuleKey(\"threads:max\").c_str());\n  ASSERT_STREQ(\"threads:*\", eos::mgm::ProcessRuleKey(\"threads:*\").c_str());\n  ASSERT_STREQ(\"threads:99\", eos::mgm::ProcessRuleKey(\"threads:99\").c_str());\n  ASSERT_STREQ(\"threads:0\", eos::mgm::ProcessRuleKey(\"threads:root\").c_str());\n  ASSERT_STREQ(\"\", eos::mgm::ProcessRuleKey(\"threads:some_random\").c_str());\n  ASSERT_STREQ(\"rate:user:daemon\",\n               eos::mgm::ProcessRuleKey(\"rate:user:daemon\").c_str());\n  ASSERT_STREQ(\"rate:group:daemon\",\n               eos::mgm::ProcessRuleKey(\"rate:group:daemon\").c_str());\n}\n"
  },
  {
    "path": "unit_tests/mgm/AclCmdTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: AclTests.cc\n// Author: Elvin-Alin Sindrilaru <esindril at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"mgm/proc/user/AclCmd.hh\"\n\nusing namespace eos::mgm;\n\nTEST(AclCmd, RuleMap)\n{\n  RuleMap expect_map = {\n    { \"u:99\", 0b011111111111}, { \"u:0\", 0b01010101010}\n  };\n  RuleMap result_map;\n  const std::string acl = \"u:99:rwxm!m!d+d!u+uqc,u:0:wm!d!uq\";\n  AclCmd::GenerateRuleMap(acl, result_map);\n  ASSERT_EQ(result_map.size(), expect_map.size());\n  ASSERT_EQ(result_map, expect_map);\n}\n\nTEST(AclCmd, key_position)\n{\n  RuleMap input_map {\n    {\"u:99\", 0b011111111111},\n    {\"u:1001\",0b01},\n    {\"g:123\",0b101},\n    {\"u:100\",0b11}\n  };\n\n  auto begin_it = input_map.begin();\n  auto end_it = input_map.end();\n  EXPECT_EQ(key_position(input_map, \"u:123\"),end_it);\n  EXPECT_EQ(key_position(input_map, \"g:123\"),std::next(begin_it,2));\n}\n\nTEST(AclCmd, insert_or_assign_simple)\n{\n  RuleMap input_map {\n    {\"u:99\", 0b011111111111},\n    {\"u:1001\",0b01},\n    {\"g:123\",0b101},\n    {\"u:100\",0b11}\n  };\n\n  RuleMap expected_map {\n    {\"u:99\", 0b011111111111},\n    {\"u:1001\",0b01},\n    {\"g:123\",0b101},\n    {\"u:100\",0b11},\n    {\"u:123\",0b100}\n  };\n\n  insert_or_assign(input_map, \"u:123\", 0b100);\n  EXPECT_EQ(input_map, expected_map);\n\n  insert_or_assign(input_map, \"u:1001\", 0b1001);\n\n  expected_map = {{\"u:99\", 0b011111111111},\n                  {\"u:1001\",0b1001},\n                  {\"g:123\",0b101},\n                  {\"u:100\",0b11},\n                  {\"u:123\",0b100}\n  };\n\n  EXPECT_EQ(input_map, expected_map);\n\n  {\n    std::string key1 = \"u:9001\";\n    unsigned short i = 100;\n    insert_or_assign(input_map, key1, i);\n    // Check that the values haven't moved\n    std::string key2 = \"u:9001\";\n    EXPECT_EQ(key1,key2);\n    expected_map.emplace_back(key1,i);\n    EXPECT_EQ(input_map, expected_map);\n  }\n\n  {\n    std::string key1 = \"u:9002\";\n    unsigned short val = 101;\n    insert_or_assign(input_map, std::move(key1), std::move(val));\n    // key was moved\n    EXPECT_EQ(key1,std::string());\n    EXPECT_EQ(val, 101);\n\n    expected_map.emplace_back(\"u:9002\",val);\n    EXPECT_EQ(input_map, expected_map);\n  }\n\n  {\n    std::string key1 = \"u:9002\";\n    unsigned short val = 102;\n    insert_or_assign(input_map, std::move(key1), std::move(val));\n    // key will not be moved as it already exists\n    EXPECT_EQ(key1, \"u:9002\");\n    EXPECT_EQ(val, 102);\n\n    // There is no easy way to do this than one of our functions again ;)\n    expected_map.pop_back();\n    expected_map.emplace_back(key1, val);\n    EXPECT_EQ(input_map, expected_map);\n  }\n}\n\nTEST(AclCmd, get_iterator)\n{\n  RuleMap input_map = {{\"u:99\", 0b011111111111},\n                        {\"u:1001\",0b1001},\n                        {\"g:123\",0b101},\n                        {\"u:100\",0b11},\n                        {\"u:123\",0b100}\n  };\n\n  {\n    auto [it, err] = get_iterator(input_map, 1);\n    EXPECT_EQ(it, input_map.begin());\n  }\n\n  {\n    auto [it, err] = get_iterator(input_map, 6);\n    EXPECT_EQ(err, EINVAL);\n  }\n\n  {\n    auto [it, err] = get_iterator(input_map, 5);\n    ASSERT_EQ(err, 0);\n    EXPECT_EQ(it->first,\"u:123\");\n  }\n}\n\nTEST(AclCmd, insert_or_assign_iter)\n{\n  RuleMap input_map = {{\"u:99\", 0b011111111111},\n                       {\"u:1001\",0b1001},\n                       {\"g:123\",0b101},\n                       {\"u:100\",0b11},\n                       {\"u:123\",0b100}\n  };\n\n  {\n    auto [it, _] = get_iterator(input_map, 1);\n    insert_or_assign(input_map, \"u:9001\",0b1010, it);\n\n    RuleMap expected_map = {{\"u:9001\",0b1010},\n                            {\"u:99\", 0b011111111111},\n                            {\"u:1001\",0b1001},\n                            {\"g:123\",0b101},\n                            {\"u:100\",0b11},\n                            {\"u:123\",0b100}\n    };\n\n    EXPECT_EQ(expected_map, input_map);\n  }\n\n  {\n    // No movement of keys as we dont override move_existing\n    auto [it, _] = get_iterator(input_map, 3);\n    insert_or_assign(input_map, \"u:9001\",0b1011, it);\n\n    RuleMap expected_map = {{\"u:9001\",0b1011},\n                            {\"u:99\", 0b011111111111},\n                            {\"u:1001\",0b1001},\n                            {\"g:123\",0b101},\n                            {\"u:100\",0b11},\n                            {\"u:123\",0b100}\n    };\n\n    EXPECT_EQ(expected_map, input_map);\n  }\n\n  {\n    // In this case we move the keys as we are passing true, so we're promoting\n    // this val up one place\n    auto [it, _] = get_iterator(input_map, 4);\n    insert_or_assign(input_map, \"u:100\",0b11011, it, true);\n\n    RuleMap expected_map = {{\"u:9001\",0b1011},\n                            {\"u:99\", 0b011111111111},\n                            {\"u:1001\",0b1001},\n                            {\"u:100\",0b11011},\n                            {\"g:123\",0b101},\n                            {\"u:123\",0b100}\n    };\n\n    EXPECT_EQ(expected_map, input_map);\n  }\n  {\n    // we are demoting an element\n    auto [it, _] = get_iterator(input_map, 5);\n    insert_or_assign(input_map, \"u:99\",0b11011, it, true);\n\n    RuleMap expected_map = {{\"u:9001\",0b1011},\n                            {\"u:1001\",0b1001},\n                            {\"u:100\",0b11011},\n                            {\"g:123\",0b101},\n                            {\"u:99\", 0b11011},\n                            {\"u:123\",0b100}\n    };\n\n    EXPECT_EQ(expected_map, input_map);\n\n  }\n  {\n    // we are demoting the first element to the last\n    auto [it, _] = get_iterator(input_map, 6);\n    insert_or_assign(input_map, \"u:9001\",0b10011, it, true);\n\n    RuleMap expected_map = {{\"u:1001\",0b1001},\n                            {\"u:100\",0b11011},\n                            {\"g:123\",0b101},\n                            {\"u:99\", 0b11011},\n                            {\"u:123\",0b100},\n                            {\"u:9001\",0b10011}\n    };\n\n    EXPECT_EQ(expected_map, input_map);\n  }\n\n  {\n    // we are demoting a middle element to the last\n    auto [it, _] = get_iterator(input_map, 6);\n    insert_or_assign(input_map, \"u:99\",0b11011, it, true);\n\n    RuleMap expected_map = {{\"u:1001\",0b1001},\n                            {\"u:100\",0b11011},\n                            {\"g:123\",0b101},\n                            {\"u:123\",0b100},\n                            {\"u:9001\",0b10011},\n                            {\"u:99\", 0b11011}\n    };\n\n    EXPECT_EQ(expected_map, input_map);\n\n  }\n\n  {\n    // we are demoting the penultimate element to the last\n    auto [it, _] = get_iterator(input_map, 6);\n    insert_or_assign(input_map, \"u:9001\",0b10011, it, true);\n\n    RuleMap expected_map = {{\"u:1001\",0b1001},\n                            {\"u:100\",0b11011},\n                            {\"g:123\",0b101},\n                            {\"u:123\",0b100},\n                            {\"u:99\", 0b11011},\n                            {\"u:9001\",0b10011}\n    };\n\n    EXPECT_EQ(expected_map, input_map);\n\n  }\n}\n\nTEST(AclCmd, GetRulePosition)\n{\n  // first arg rule_map_sz, second_arg position\n  EXPECT_EQ(AclCmd::GetRulePosition(0,0), std::pair(0,0UL));\n  EXPECT_EQ(AclCmd::GetRulePosition(0,1), std::pair(0,0UL));\n  EXPECT_EQ(AclCmd::GetRulePosition(0,2), std::pair(EINVAL,0UL));\n  //no position argument was passed, so position should be 0\n  // regardless of map size\n\n  EXPECT_EQ(AclCmd::GetRulePosition(1,0), std::pair(0,0UL));\n  EXPECT_EQ(AclCmd::GetRulePosition(10,0), std::pair(0,0UL));\n\n  // For all the cases where there are acls and we're within boundaries\n  // we should return the second arg\n  EXPECT_EQ(AclCmd::GetRulePosition(2,2), std::pair(0,2UL));\n  EXPECT_EQ(AclCmd::GetRulePosition(2,1), std::pair(0,1UL));\n  EXPECT_EQ(AclCmd::GetRulePosition(8,4), std::pair(0,4UL));\n\n  // except in case of error\n  EXPECT_EQ(AclCmd::GetRulePosition(2,3), std::pair(EINVAL,3UL));\n}\n"
  },
  {
    "path": "unit_tests/mgm/CapsTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: CapsTests.cc\n// Author: Abhishek Lekshmanan <abhishek.lekshmanan@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2021 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#include \"gtest/gtest.h\"\n#include \"mgm/FuseServer/Caps.hh\"\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n#include \"mgm/zmq/ZMQ.hh\"\n\nusing namespace eos::mgm::FuseServer;\nusing namespace std::chrono_literals;\n\nclass FakeStats {\npublic:\n  template <typename... Args>\n  void Add(Args&&... args) {\n  }\n};\n\nclass FakeXrdMgmOFS: public XrdMgmOfs\n{\npublic:\n  FakeXrdMgmOFS(XrdSysError* lp) : XrdMgmOfs(lp)\n  {\n  }\n  ~FakeXrdMgmOFS() {\n    // Delete the ZMQ context, TODO future try to mock this class!\n    if (mZmqContext) {\n      mZmqContext->close();\n      delete mZmqContext;\n    }\n    mDoneOrderlyShutdown = true;\n  }\n  // TODO shadow + mock?\n  FakeStats MgmStats;\n};\n\nclass EnvMgr {\n  std::unordered_map<std::string, char*> env;\n  std::vector<std::string> keys;\npublic:\n  void save_env(const char* key) {\n    env[key]=getenv(key);\n  }\n\n  void load_env(const char* key) {\n    if(auto kv = env.find(key);\n       kv != env.end()) {\n      kv->second ? setenv(key, kv->second, 1) : unsetenv(key);\n    }\n  }\n\n  void modify_env(const char* key, const char* val = \"0\", int force = 1)\n  {\n    setenv(key, val, force);\n  }\n\n  EnvMgr(std::vector<std::string>&& _keys) : keys(std::move(_keys)) {\n    std::for_each(keys.begin(), keys.end(), [this](const std::string& key) { save_env(key.c_str()); });\n    std::for_each(keys.begin(), keys.end(), [this](const std::string& key) { modify_env(key.c_str()); });\n  }\n\n  ~EnvMgr() {\n    std::for_each(keys.begin(), keys.end(), [this](const std::string& key) { load_env(key.c_str()); });\n  }\n};\n\n// use a fixture, we'll retain the same Caps across the various tests\nclass CapsTest : public ::testing::Test\n{\nprotected:\n  // TODO: When you update gtest remember to change this to SetUpTestSuite\n  // We need these resources shared across all the test suites!\n  static void SetUpTestCase() {\n    // We're doing this because initializing the public vars like the http port\n    // won't yet help our case because the base class constructor is passed which already\n    // sets defaults for these. We want to avoid construction of the service objects which\n    // happens for 0 values.\n    test_env = new EnvMgr({\"EOS_MGM_HTTP_PORT\", \"EOS_MGM_GRPC_PORT\"});\n    fake_sys_error = new XrdSysError(nullptr, \"fake\");\n    fake_ofs = new FakeXrdMgmOFS(fake_sys_error);\n    gOFS = fake_ofs;\n  }\n\n  static void TearDownTestCase() {\n    delete fake_sys_error;\n    delete fake_ofs;\n    delete test_env;\n    gOFS = nullptr;\n  }\n\n  static XrdSysError* fake_sys_error;\n  static XrdMgmOfs* fake_ofs;\n  static EnvMgr* test_env;\n  Caps mCaps;\n};\n\nXrdSysError* CapsTest::fake_sys_error = nullptr;\nXrdMgmOfs* CapsTest::fake_ofs = nullptr;\nEnvMgr* CapsTest::test_env = nullptr;\n\n\neos::fusex::cap make_cap(int id,\n                         std::string clientid,\n                         std::string authid,\n                         uint64_t vtime = 0)\n{\n  eos::fusex::cap c;\n  c.set_id(id);\n  c.set_clientid(std::move(clientid));\n  c.set_authid(std::move(authid));\n  vtime ? c.set_vtime(vtime) :\n          c.set_vtime(static_cast<uint64_t>(time(nullptr)));\n  return c;\n}\n\neos::fusex::cap make_cap(int id,\n                         std::string clientid,\n                         std::string authid,\n                         std::string uuid,\n                         uint64_t vtime=0)\n{\n  eos::fusex::cap c;\n  c.set_id(id);\n  c.set_clientid(std::move(clientid));\n  c.set_authid(std::move(authid));\n  c.set_clientuuid(std::move(uuid));\n  vtime ? c.set_vtime(vtime) :\n    c.set_vtime(static_cast<uint64_t>(time(nullptr)));\n  return c;\n}\n\neos::common::VirtualIdentity make_vid(uid_t uid, gid_t gid)\n{\n  eos::common::VirtualIdentity vid;\n  vid.uid = uid;\n  vid.gid = gid;\n  return vid;\n}\n\nTEST_F(CapsTest, EmptyCapsInit) {\n  EXPECT_EQ(mCaps.ncaps(), 0);\n}\n\nTEST_F(CapsTest, StoreBasic) {\n  eos::common::VirtualIdentity vid;\n  eos::fusex::cap c;\n  mCaps.Store(c,&vid);\n  EXPECT_EQ(mCaps.ncaps(), 1);\n}\n\nTEST_F(CapsTest, StoreUpdate) {\n  auto vid1 = make_vid(1234, 1234);\n  auto c1 = make_cap(123,\"cid1\",\"authid1\");\n  mCaps.Store(c1,&vid1);\n  EXPECT_EQ(mCaps.ncaps(), 1);\n  std::string authid {\"authid1\"};\n  auto k = mCaps.Get(authid);\n  EXPECT_EQ((*k)()->id(),123);\n  EXPECT_EQ((*k)()->clientid(), \"cid1\");\n\n  // now update this cap\n  c1.set_clientid(\"clientid_1\");\n  mCaps.Store(c1,&vid1);\n  EXPECT_EQ(mCaps.ncaps(), 2); // new vtime() -> new cap ?\n  EXPECT_EQ(mCaps.GetCaps().size(), 1);\n  auto k2 = mCaps.Get(authid);\n  EXPECT_EQ((*k2)()->id(),123);\n  EXPECT_EQ((*k2)()->clientid(), \"clientid_1\");\n}\n\nTEST_F(CapsTest, StoreUpdateClientID) {\n  auto vid1 = make_vid(1234, 1234);\n  auto c1 = make_cap(123,\"cid1\",\"authid1\");\n  mCaps.Store(c1,&vid1);\n  EXPECT_EQ(mCaps.ncaps(), 1);\n  std::string authid {\"authid1\"};\n\n  auto k = mCaps.Get(authid);\n  EXPECT_EQ((*k)()->id(),123);\n  EXPECT_EQ((*k)()->clientid(), \"cid1\");\n  // Test the 3 different views\n  auto& client_caps = mCaps.ClientCaps();\n  auto& ino_caps = mCaps.ClientInoCaps();\n  const auto& mcaps = mCaps.GetCaps();\n\n  EXPECT_EQ(client_caps[\"cid1\"].count(\"authid1\"), 1);\n  EXPECT_EQ(ino_caps[\"cid1\"][123].count(\"authid1\"),1);\n  const auto it = mcaps.find(\"authid1\");\n  EXPECT_EQ(it->second, k);\n\n  //EXPECT_EQ(mcaps[\"authid1\"], k);\n  // now update this cap\n  c1.set_clientid(\"clientid_1\");\n  // If only the clientid is updated without changing the id the other views do\n  //not get deleted\n  mCaps.Store(c1,&vid1);\n  EXPECT_EQ(mCaps.ncaps(), 2); // new vtime -> more mTimeOrderedCap entry.\n\n  auto k2 = mCaps.Get(authid);\n  EXPECT_EQ((*k2)()->id(),123);\n  EXPECT_EQ((*k2)()->clientid(), \"clientid_1\");\n\n  EXPECT_EQ(client_caps[\"cid1\"].count(\"authid1\"), 1);\n  EXPECT_EQ(ino_caps[\"cid1\"][123].count(\"authid1\"),1);\n  // now check the updated values\n  EXPECT_EQ(client_caps[\"clientid_1\"].count(\"authid1\"), 1);\n  EXPECT_EQ(ino_caps[\"clientid_1\"][123].count(\"authid1\"),1);\n  const auto it2 = mcaps.find(\"authid1\");\n  EXPECT_EQ(it2->second, k2);\n\n}\n\nTEST_F(CapsTest, StoreUpdateID) {\n  auto vid1 = make_vid(1234, 1234);\n  auto c1 = make_cap(123,\"cid1\",\"authid1\");\n  mCaps.Store(c1,&vid1);\n  EXPECT_EQ(mCaps.ncaps(), 1);\n  std::string authid {\"authid1\"};\n\n  auto k = mCaps.Get(authid);\n  EXPECT_EQ((*k)()->id(),123);\n  EXPECT_EQ((*k)()->clientid(), \"cid1\");\n  // Test the 3 different views\n  auto& client_caps = mCaps.ClientCaps();\n  auto& ino_caps = mCaps.ClientInoCaps();\n  const auto& mcaps = mCaps.GetCaps();\n\n  EXPECT_EQ(client_caps[\"cid1\"].count(\"authid1\"), 1);\n  EXPECT_EQ(ino_caps[\"cid1\"][123].count(\"authid1\"),1);\n  const auto it = mcaps.find(\"authid1\");\n  EXPECT_EQ(it->second, k);\n\n  // now update this cap\n  c1.set_clientid(\"clientid_1\");\n  c1.set_id(1234);\n  // client_caps & ino_caps will now drop the old client entries, however TimeOrderedCaps will not drop the cap\n  mCaps.Store(c1,&vid1);\n  EXPECT_EQ(mCaps.ncaps(), 2);\n\n  auto k2 = mCaps.Get(authid);\n  EXPECT_EQ((*k2)()->id(),1234);\n  EXPECT_EQ((*k2)()->clientid(), \"clientid_1\");\n\n  EXPECT_EQ(client_caps[\"cid1\"].size(), 0);\n  EXPECT_EQ(ino_caps[\"cid1\"][123].size(),0);\n  // now check the updated values\n  EXPECT_EQ(client_caps[\"clientid_1\"].count(\"authid1\"), 1);\n  EXPECT_EQ(ino_caps[\"clientid_1\"][1234].count(\"authid1\"),1);\n  const auto it2 = mcaps.find(\"authid1\");\n  EXPECT_EQ(it2->second, k2);\n\n}\n\nTEST_F(CapsTest, PopCaps) {\n  auto vid1 = make_vid(1,1);\n  auto vid2 = make_vid(2,2);\n\n  mCaps.Store(make_cap(1,\"client1\",\"auth1\"), &vid1);\n  mCaps.Store(make_cap(2,\"client2\",\"auth2\"), &vid2);\n\n  EXPECT_EQ(mCaps.ncaps(), 2);\n  mCaps.pop();\n  EXPECT_EQ(mCaps.ncaps(), 1);\n\n  // pop only pops from time ordered caps, the other cap should still be present\n  EXPECT_TRUE(mCaps.HasCap(\"auth1\"));\n  EXPECT_TRUE(mCaps.HasCap(\"auth2\"));\n\n  std::string filter;\n  std::string option{\"t\"};\n\n  std::string out = mCaps.Print(option, filter);\n  EXPECT_TRUE(out.find(\"client2\") != std::string::npos);\n  EXPECT_TRUE(out.find(\"client1\") == std::string::npos);\n}\n\nTEST_F(CapsTest, ExpireCaps)\n{\n  auto vid1 = make_vid(1,1);\n  auto vid2 = make_vid(2,2);\n\n  uint64_t time20 = static_cast<uint64_t>(time(nullptr)) - 20;\n  mCaps.Store(make_cap(1,\"client1\",\"auth1\", time20), &vid1);\n  mCaps.Store(make_cap(2,\"client2\",\"auth2\"), &vid2);\n\n  EXPECT_EQ(mCaps.ncaps(),2);\n  EXPECT_EQ(mCaps.GetCaps().size(), 2);\n\n  // Now expire the caps\n  EXPECT_TRUE(mCaps.expire());\n  EXPECT_EQ(mCaps.GetCaps().size(), 1);\n  EXPECT_TRUE(mCaps.HasCap(\"auth2\"));\n  EXPECT_FALSE(mCaps.HasCap(\"auth1\"));\n}\n\nTEST_F(CapsTest, DropCaps)\n{\n  std::string uuid1{\"uuid1\"};\n  auto c1 = make_cap(1,\"client1\",\"auth1\");\n  c1.set_clientuuid(uuid1);\n  auto c2 = make_cap(2, \"client2\", \"auth2\");\n  c2.set_clientuuid(\"uuid2\");\n  auto c3 = make_cap(3,\"client1\", \"auth3\");\n  c3.set_clientuuid(uuid1);\n\n  auto vid1 = make_vid(1,1);\n  auto vid2 = make_vid(2,2);\n  auto vid3 = make_vid(3,3);\n\n  mCaps.Store(c1, &vid1);\n  mCaps.Store(c2, &vid2);\n  mCaps.Store(c3, &vid3);\n\n  EXPECT_EQ(mCaps.GetCaps().size(), 3);\n\n  mCaps.dropCaps(uuid1);\n\n  EXPECT_EQ(mCaps.GetCaps().size(),1);\n  EXPECT_TRUE(mCaps.HasCap(\"auth2\"));\n}\n\nTEST_F(CapsTest, Remove)\n{\n  auto vid1 = make_vid(1,1);\n  auto vid2 = make_vid(2,2);\n\n  mCaps.Store(make_cap(1,\"client1\",\"auth1\"), &vid1);\n  mCaps.Store(make_cap(2,\"client2\",\"auth2\"), &vid2);\n\n  const auto& client_caps = mCaps.ClientCaps();\n  const auto& ino_caps = mCaps.ClientInoCaps();\n  const auto& mcaps = mCaps.GetCaps();\n\n  EXPECT_EQ(client_caps.size(), 2);\n  EXPECT_EQ(ino_caps.size(), 2);\n  EXPECT_EQ(mcaps.size(), 2);\n\n  EXPECT_TRUE(mCaps.Remove(mCaps.Get(\"auth1\")));\n  EXPECT_FALSE(mCaps.Remove(mCaps.Get(\"foo\")));\n  EXPECT_EQ(client_caps.size(), 1);\n  EXPECT_EQ(ino_caps.size(), 1);\n  EXPECT_EQ(mcaps.size(), 1);\n\n}\n\n\nTEST_F(CapsTest, Delete)\n{\n  auto vid1 = make_vid(1,1);\n  auto vid2 = make_vid(2,2);\n  auto vid3 = make_vid(3,3);\n  mCaps.Store(make_cap(1,\"client1\",\"auth1\"), &vid1);\n  mCaps.Store(make_cap(2,\"client2\",\"auth2\"), &vid2);\n  mCaps.Store(make_cap(1,\"client3\",\"auth3\"), &vid3);\n\n  const auto& client_caps = mCaps.ClientCaps();\n  const auto& ino_caps = mCaps.ClientInoCaps();\n  const auto& mcaps = mCaps.GetCaps();\n\n  EXPECT_EQ(client_caps.size(), 3);\n  EXPECT_EQ(ino_caps.size(), 3);\n  EXPECT_EQ(mcaps.size(), 3);\n\n  EXPECT_EQ(mCaps.Delete(1), 0);\n  EXPECT_EQ(mCaps.Delete(123), ENONET);\n\n  EXPECT_TRUE(mCaps.HasCap(\"auth2\"));\n  EXPECT_EQ(client_caps.size(), 1);\n  EXPECT_EQ(ino_caps.size(), 1);\n  EXPECT_EQ(mcaps.size(), 1);\n\n}\n\nTEST_F(CapsTest, BCCap)\n{\n  auto vid1 = make_vid(1,1);\n  auto vid2 = make_vid(2,2);\n  std::string auth1 {\"auth1\"};\n  mCaps.Store(make_cap(1,\"client1\",auth1), &vid1);\n  mCaps.Store(make_cap(2,\"client2\",\"auth2\"), &vid2);\n\n\n  auto k = mCaps.Get(auth1);\n  EXPECT_EQ((*k)()->id(), 1);\n  int ret = mCaps.BroadcastCap(k);\n  // This function returns -1 regardless whether cap exists or not\n  EXPECT_EQ(ret, -1);\n}\n\n\nTEST_F(CapsTest, GetBroadcastCapsTS)\n{\n  auto vid1 = make_vid(1,1);\n  auto vid2 = make_vid(2,2);\n  auto vid3 = make_vid(3,3);\n  std::string auth1 {\"auth1\"};\n  std::string auth2 {\"auth2\"};\n  std::string auth3 {\"auth3\"};\n\n  // Use a unordered_set with the same type as the vector elements returned from\n  // GetBroadcastCapsTS; the underlying types may not have ordering as they fill\n  // the vector from unordered_maps, so this is done to make sure that tests are\n  // deterministic\n  using bc_result_t = decltype(mCaps.GetBroadcastCapsTS(0));\n  using shared_cap_t = bc_result_t::value_type;\n  using result_set_t = std::unordered_set<shared_cap_t>;\n\n  mCaps.Store(make_cap(1,\"client1\",auth1,\"uuid1\"), &vid1);\n  mCaps.Store(make_cap(2,\"client2\",auth2,\"uuid2\"), &vid2);\n  mCaps.Store(make_cap(1,\"client3\",auth3,\"uuid3\"), &vid3);\n\n\n  {\n    auto empty_result = mCaps.GetBroadcastCapsTS(9999,\n                                                 mCaps.Get(\"foo\"));\n    EXPECT_EQ(empty_result.size(), 0);\n  }\n\n  {\n\n    auto result = mCaps.GetBroadcastCapsTS(1,\n                                           mCaps.Get(auth1));\n    EXPECT_EQ(result.size(), 2);\n\n    result_set_t actual (std::make_move_iterator(result.begin()),\n                         std::make_move_iterator(result.end()));\n    result_set_t expected = { mCaps.Get(auth1), mCaps.Get(auth3)};\n    EXPECT_EQ(expected, actual);\n  }\n\n  {\n    // this will skip own caps\n    eos::fusex::md m;\n    m.set_authid(auth1);\n    m.set_clientuuid(\"uuid1\");\n    auto actual = mCaps.GetBroadcastCapsTS(1,\n                                           mCaps.Get(auth1),\n                                           &m);\n    bc_result_t expected = { mCaps.Get(auth3) };\n    EXPECT_EQ(expected, actual);\n  }\n\n}\n\nTEST_F(CapsTest, MonitorCapsSimple)\n{\n  auto vid1 = make_vid(1,1);\n  auto vid2 = make_vid(2,2);\n\n  uint64_t time20 = static_cast<uint64_t>(time(nullptr)) - 20;\n  mCaps.Store(make_cap(1,\"client1\",\"auth1\", time20), &vid1);\n  mCaps.Store(make_cap(2,\"client2\",\"auth2\", time20), &vid2);\n\n  EXPECT_EQ(mCaps.ncaps(),2);\n  EXPECT_EQ(mCaps.GetCaps().size(), 2);\n\n  // Now expire the caps\n  while (true) {\n    if (mCaps.expire()) {\n      mCaps.pop();\n    } else {\n      break;\n    }\n  }\n\n  EXPECT_EQ(mCaps.GetCaps().size(), 0);\n  EXPECT_EQ(mCaps.ncaps(), 0);\n}\n\nTEST_F(CapsTest, MonitorCapsUpdate)\n{\n  auto vid1 = make_vid(1,1);\n  auto vid2 = make_vid(2,2);\n  auto vid3 = make_vid(123,123);\n\n  uint64_t time20 = static_cast<uint64_t>(time(nullptr)) - 20;\n  uint64_t time8 = static_cast<uint64_t>(time(nullptr)) - 8;\n  auto ucap = make_cap(123, \"client123\",\"auth123\", time8);\n\n\n  mCaps.Store(make_cap(1,\"client1\",\"auth1\", time20), &vid1);\n  mCaps.Store(make_cap(2,\"client2\",\"auth2\", time20), &vid2);\n  mCaps.Store(ucap, &vid3);\n\n  EXPECT_EQ(mCaps.ncaps(),3);\n  EXPECT_EQ(mCaps.GetCaps().size(), 3);\n\n  // Now expire the caps\n  while (true) {\n    if (mCaps.expire()) {\n      mCaps.pop();\n    } else {\n      break;\n    }\n  }\n\n  EXPECT_EQ(mCaps.GetCaps().size(), 1);\n  EXPECT_EQ(mCaps.ncaps(), 1);\n\n  // update the cap vtime\n  ucap.set_vtime(static_cast<uint64_t>(time(nullptr)) - 7);\n  mCaps.Store(ucap, &vid3);\n  EXPECT_EQ(mCaps.GetCaps().size(), 1);\n  EXPECT_EQ(mCaps.ncaps(), 2);   // should this be 2 as we've a new time?\n\n  std::this_thread::sleep_for(2s);\n\n  // Now expire the caps\n  while (true) {\n    if (mCaps.expire()) {\n      mCaps.pop();\n    } else {\n      break;\n    }\n  }\n\n  EXPECT_EQ(mCaps.GetCaps().size(),1);\n  EXPECT_EQ(mCaps.ncaps(), 1);\n\n  std::this_thread::sleep_for(1s);\n  // Now expire the caps\n  while (true) {\n    if (mCaps.expire()) {\n      mCaps.pop();\n    } else {\n      break;\n    }\n  }\n\n\n  EXPECT_EQ(mCaps.GetCaps().size(), 0) ;\n  EXPECT_EQ(mCaps.ncaps(), 0);\n}\n\nTEST_F(CapsTest, GetAllCaps)\n{\n  auto vid1 = make_vid(1,1);\n  auto vid2 = make_vid(2,2);\n  auto vid3 = make_vid(3,3);\n  auto vid4 = make_vid(4,4);\n  mCaps.Store(make_cap(1,\"client1\",\"auth1\"), &vid1);\n  mCaps.Store(make_cap(2,\"client2\",\"auth2\"), &vid2);\n  mCaps.Store(make_cap(1,\"client3\",\"auth3\"), &vid3);\n  //mCaps.Store(make_cap(4,\"client4\", \"auth1\"), &vid4);\n\n  EXPECT_EQ(mCaps.ncaps(),3);\n  EXPECT_EQ(mCaps.GetCaps().size(),3);\n\n  {\n    auto results = mCaps.GetAllCaps();\n    EXPECT_EQ(results.size(),3);\n\n    std::unordered_set<std::string> expected_ids = {\"client1\", \"client2\", \"client3\"};\n    std::unordered_set<std::string> actual_ids;\n    for (const auto& it: results) {\n      actual_ids.emplace((*it)()->clientid());\n    }\n    EXPECT_EQ(expected_ids, actual_ids);\n  }\n\n  // Update authid1 -> authid4\n  mCaps.Store(make_cap(4,\"client4\",\"auth1\"), &vid4);\n  EXPECT_EQ(mCaps.ncaps(),4); // TimeCaps will still have entry corresponding to unexpiredtimes\n  EXPECT_EQ(mCaps.GetCaps().size(),3);\n\n  {\n    auto results = mCaps.GetAllCaps();\n    EXPECT_EQ(results.size(),3);\n\n    std::unordered_set<std::string> expected_ids = {\"client4\", \"client2\", \"client3\"};\n    std::unordered_set<std::string> actual_ids;\n    for (const auto& it: results) {\n      actual_ids.emplace((*it)()->clientid());\n    }\n    EXPECT_EQ(expected_ids, actual_ids);\n  }\n\n}\n\nTEST_F(CapsTest, GetInodeCapAuthIds)\n{\n  auto vid1 = make_vid(1,1);\n  auto vid2 = make_vid(2,2);\n  auto vid3 = make_vid(3,3);\n  auto vid4 = make_vid(4,4);\n  mCaps.Store(make_cap(1,\"client1\",\"auth1\"), &vid1);\n  mCaps.Store(make_cap(2,\"client2\",\"auth2\"), &vid2);\n  mCaps.Store(make_cap(1,\"client3\",\"auth3\"), &vid3);\n  mCaps.Store(make_cap(4,\"client1\",\"auth1\"), &vid1);\n  mCaps.Store(make_cap(2,\"client4\",\"auth4\"), &vid4);\n\n  Caps::authid_set_t results;\n  Caps::authid_set_t actual = {\"auth2\"};\n  {\n    EXPECT_EQ(mCaps.ncaps(), 5);\n    EXPECT_EQ(mCaps.ClientInoCaps()[\"client2\"][2], actual);\n    auto r = mCaps.GetInodeCapAuthIds(\"client2\",2);\n    results = std::move(r);\n    // This  move is not necessary, but we do this in\n    // Clients.cc as the results struct has different\n    // results in a if else branch, so we just simply simulate this\n  }\n  EXPECT_EQ(results, actual);\n}\n\nTEST_F(CapsTest, ImplyCaps)\n{\n  auto vid1 = make_vid(1,1);\n  auto vid2 = make_vid(2,2);\n  auto vid3 = make_vid(3,3);\n  mCaps.Store(make_cap(1,\"client1\",\"auth1\"), &vid1);\n  mCaps.Store(make_cap(2,\"client2\",\"auth2\"), &vid2);\n  mCaps.Store(make_cap(3,\"client3\",\"auth3\"), &vid3);\n\n  auto c = mCaps.Get(\"auth3\");\n  EXPECT_EQ((*c)()->clientid(), \"client3\");\n\n  EXPECT_FALSE(mCaps.Imply(3,\"auth_foo\",\"auth1\"));\n  EXPECT_FALSE(mCaps.Imply(3,\"auth1\",\"\"));\n\n  EXPECT_TRUE(mCaps.Imply(3,\"auth3\",\"auth1\"));\n  auto c3 = mCaps.Get(\"auth3\");\n  EXPECT_EQ((*c3)()->clientid(), \"client3\");\n  auto c1 = mCaps.Get(\"auth1\");\n  EXPECT_EQ((*c1)()->clientid(),\"client3\");\n}\n\nTEST_F(CapsTest, ImplyCapsMulti)\n{\n  int limit = 10000;\n  for (int i=0; i < limit; i++){\n    auto vid = make_vid(i,i);\n    mCaps.Store(make_cap(i,\"client\"+std::to_string(i),\"auth\"+std::to_string(i)),&vid);\n  }\n\n  std::thread deleter([&](){ for(int i=0; i < limit;i++) { EXPECT_EQ(mCaps.Delete(i),0);}});\n  std::thread implier([&](){ for(int i=0; i < limit;i++) { std::string indp = std::to_string(i-1);\n                                                           std::string indc = std::to_string(i+2);\n                                                           ASSERT_NO_THROW(mCaps.Imply(i,\"auth\"+indc,\"auth\"+indp));}});\n  deleter.join();\n  implier.join();\n}\n"
  },
  {
    "path": "unit_tests/mgm/CommitHelperTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: CommitHelperTests.cc\n// Author: Elvin-Alin Sindrilaru <esindril at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2024 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#define IN_TEST_HARNESS\n#include \"mgm/ofs/fsctl/CommitHelper.hh\"\n#undef IN_TEST_HARNESS\n\nusing eos::mgm::CommitHelper;\n\n//------------------------------------------------------------------------------\n// Test incrementing the timestamp for versioned files\n//------------------------------------------------------------------------------\nTEST(CommitHelperTest, IncTsVerFn)\n{\n  std::string fn = \"1724758410.00001111\";\n  ASSERT_STREQ(\"1724758411.00001111\", CommitHelper::IncrementTsForVersionFn(fn).c_str());\n  fn = \"dummy.test\";\n  ASSERT_STREQ(fn.c_str(), CommitHelper::IncrementTsForVersionFn(fn).c_str());\n  fn = \"dummy\";\n  ASSERT_STREQ(fn.c_str(), CommitHelper::IncrementTsForVersionFn(fn).c_str());\n  fn = \"1724758410.\";\n  ASSERT_STREQ(fn.c_str(), CommitHelper::IncrementTsForVersionFn(fn).c_str());\n  fn = \"abcf.0001111\";\n  ASSERT_STREQ(fn.c_str(), CommitHelper::IncrementTsForVersionFn(fn).c_str());\n  fn = \"1724758420.aabbccdd\";\n  ASSERT_STREQ(\"1724758421.aabbccdd\", CommitHelper::IncrementTsForVersionFn(fn).c_str());\n}\n\n\n"
  },
  {
    "path": "unit_tests/mgm/ConversionInfoTests.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file ConversionInfoTests.cc\n//! @author Elvin-Alin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"mgm/convert/ConversionInfo.hh\"\n#include \"namespace/MDException.hh\"\n\n//------------------------------------------------------------------------------\n// Test object construction\n//------------------------------------------------------------------------------\nTEST(ConversionInfo, Construction)\n{\n  using namespace eos::mgm;\n  eos::common::GroupLocator grp_loc;\n  ASSERT_TRUE(eos::common::GroupLocator::parseGroup(\"default.3\", grp_loc));\n  ASSERT_EQ(nullptr, ConversionInfo::parseConversionString(\"\"));\n  std::string input = \"000000000000000a:default.3#00100002\";\n  ASSERT_EQ(input, ConversionInfo::parseConversionString(input)->ToString());\n  input = \"000000000000000b:default.3#00100002~gathered:tag1\";\n  ASSERT_EQ(input, ConversionInfo::parseConversionString(input)->ToString());\n  input = \"000000000000000c:default.3#00100002~scattered:tag1::tag2\";\n  ASSERT_EQ(input, ConversionInfo::parseConversionString(input)->ToString());\n  input = \"000000000000000d:default.3#00100002+\";\n  ASSERT_EQ(input, ConversionInfo::parseConversionString(input)->ToString());\n  input = \"000000000000000d:default.3#00100002~hybrid:tag1::tag3+\";\n  ASSERT_EQ(input, ConversionInfo::parseConversionString(input)->ToString());\n  input = \"000000000000000d:default.3#00100002~hybrid:tag1::tag3^someapp^+\";\n  ASSERT_EQ(input, ConversionInfo::parseConversionString(input)->ToString());\n  // Test false conditions\n  input = \"dummy0000000000d:default.3#00100002~hybrid:tag1::tag3+\";\n  ASSERT_EQ(nullptr, ConversionInfo::parseConversionString(input));\n  input = \"000000000000000d:default.3#00xyz02~hybrid:tag1::tag3+\";\n  ASSERT_EQ(nullptr, ConversionInfo::parseConversionString(input));\n  input = \"000000000000000d:default.3#00100002~hybrid:tag1::tag3^someapp+\";\n  ASSERT_EQ(nullptr, ConversionInfo::parseConversionString(input));\n  input = \"000000000000000d:default.3#00100002^someapp~hybrid:tag1::tag3+\";\n  ASSERT_EQ(nullptr, ConversionInfo::parseConversionString(input));\n}\n\nTEST(ConversionInfo, OptionalMembers)\n{\n  using namespace eos::mgm;\n  std::string input;\n  {\n    // make sure that we didn't bring in any reserved chars+\n    input = \"000000000000000d:default.3#00100002~hybrid:tag1::tag3^eos/someapp^+\";\n    auto info = ConversionInfo::parseConversionString(input);\n    EXPECT_EQ(1048578, info->mLid); // 100002 x to d\n    EXPECT_EQ(\"eos/someapp\", info->mAppTag);\n    EXPECT_EQ(\"hybrid:tag1::tag3\", info->mPlctPolicy);\n    EXPECT_EQ(input, info->ToString());\n    EXPECT_TRUE(info->mUpdateCtime);\n  }\n  {\n    // AppTag need not be in the tail position however output will always align to tail\n    input = \"000000000000000d:default.3#00100002^eos/someapp^~hybrid:tag1::tag3+\";\n    std::string expected =\n      \"000000000000000d:default.3#00100002~hybrid:tag1::tag3^eos/someapp^+\";\n    auto info = ConversionInfo::parseConversionString(input);\n    EXPECT_EQ(1048578, info->mLid); // 100002 x to d\n    EXPECT_EQ(expected, info->ToString());\n    EXPECT_EQ(\"eos/someapp\", info->mAppTag);\n    EXPECT_EQ(\"hybrid:tag1::tag3\", info->mPlctPolicy);\n  }\n  {\n    // Have only placement tag at tail\n    input = \"000000000000000d:default.3#00100002^eos/someapp^+\";\n    auto info = ConversionInfo::parseConversionString(input);\n    EXPECT_EQ(1048578, info->mLid); // 100002 x to d\n    EXPECT_EQ(input, info->ToString());\n    EXPECT_EQ(\"eos/someapp\", info->mAppTag);\n    EXPECT_EQ(\"\", info->mPlctPolicy);\n    EXPECT_TRUE(info->mUpdateCtime);\n  }\n  {\n    // Have only placement tag at tail\n    input = \"000000000000000d:default.3#00100002~hybrid::tag1::tag3\";\n    auto info = ConversionInfo::parseConversionString(input);\n    EXPECT_EQ(1048578, info->mLid); // 100002 x to d\n    EXPECT_EQ(input, info->ToString());\n    EXPECT_EQ(\"\", info->mAppTag);\n    EXPECT_EQ(\"hybrid::tag1::tag3\", info->mPlctPolicy);\n    EXPECT_FALSE(info->mUpdateCtime);\n  }\n}\n\nTEST(ConversionInfo, URLPath)\n{\n  using namespace eos::mgm;\n  std::string input =\n    \"000000000000000d:default.3#00100002~hybrid:tag1::tag3^someapp^+\";\n  auto info = ConversionInfo::parseConversionString(input);\n  ASSERT_NE(nullptr, info);\n  XrdOucString proc_path = \"/eos/test/proc\";\n  std::string result = SSTR(proc_path << \"/\" << info->ToString());\n  std::string expected =\n    \"/eos/test/proc/000000000000000d:default.3#00100002~hybrid:tag1::tag3^someapp^+\";\n  EXPECT_EQ(expected, result);\n}"
  },
  {
    "path": "unit_tests/mgm/CtaUtilsTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: TgcUtilsTests.cc\n// Author: Steven Murray <smurray at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/CtaUtils.hh\"\n\n#include <gtest/gtest.h>\n#include <limits>\n#include <unistd.h>\n\nclass TgcUtilsTest : public ::testing::Test\n{\nprotected:\n\n  virtual void SetUp()\n  {\n  }\n\n  virtual void TearDown()\n  {\n  }\n};\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcUtilsTest, toUint64)\n{\n  using namespace eos::mgm;\n  const std::string str = \"12345\";\n  ASSERT_EQ(12345, CtaUtils::toUint64(str));\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcUtilsTest, toUint64_max)\n{\n  using namespace eos::mgm;\n  const std::string str = \"18446744073709551615\";\n  ASSERT_EQ(std::numeric_limits<std::uint64_t>::max(), CtaUtils::toUint64(str));\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcUtilsTest, toUint64_empty_string)\n{\n  using namespace eos::mgm;\n  const std::string str;\n  ASSERT_THROW(CtaUtils::toUint64(str), CtaUtils::EmptyString);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcUtilsTest, toUint64_non_numerioc)\n{\n  using namespace eos::mgm;\n  const std::string str = \"12345a\";\n  ASSERT_THROW(CtaUtils::toUint64(str), CtaUtils::NonNumericChar);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcUtilsTest, toUint64_out_of_range)\n{\n  using namespace eos::mgm;\n  const std::string str = \"18446744073709551616\";\n  ASSERT_THROW(CtaUtils::toUint64(str), CtaUtils::ParsedValueOutOfRange);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcUtilsTest, divideAndRoundToNearest)\n{\n  using namespace eos::mgm;\n  ASSERT_EQ(1, CtaUtils::divideAndRoundToNearest(1, 1));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundToNearest(2, 1));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundToNearest(3, 1));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundToNearest(1, 2));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundToNearest(2, 2));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundToNearest(3, 2));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundToNearest(4, 2));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundToNearest(5, 2));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundToNearest(6, 2));\n  ASSERT_EQ(0, CtaUtils::divideAndRoundToNearest(1, 3));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundToNearest(2, 3));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundToNearest(3, 3));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundToNearest(4, 3));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundToNearest(5, 3));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundToNearest(6, 3));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundToNearest(7, 3));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundToNearest(8, 3));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundToNearest(9, 3));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundToNearest(10, 3));\n  ASSERT_EQ(0, CtaUtils::divideAndRoundToNearest(1, 4));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundToNearest(2, 4));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundToNearest(3, 4));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundToNearest(4, 4));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundToNearest(5, 4));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundToNearest(6, 4));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundToNearest(7, 4));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundToNearest(8, 4));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundToNearest(9, 4));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundToNearest(10, 4));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundToNearest(11, 4));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundToNearest(12, 4));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundToNearest(13, 4));\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcUtilsTest, divideAndRoundUp)\n{\n  using namespace eos::mgm;\n  ASSERT_EQ(1, CtaUtils::divideAndRoundUp(1, 1));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundUp(2, 1));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundUp(3, 1));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundUp(1, 2));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundUp(2, 2));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundUp(3, 2));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundUp(4, 2));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundUp(5, 2));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundUp(6, 2));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundUp(1, 3));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundUp(2, 3));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundUp(3, 3));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundUp(4, 3));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundUp(5, 3));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundUp(6, 3));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundUp(7, 3));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundUp(8, 3));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundUp(9, 3));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundUp(1, 4));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundUp(2, 4));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundUp(3, 4));\n  ASSERT_EQ(1, CtaUtils::divideAndRoundUp(4, 4));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundUp(5, 4));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundUp(6, 4));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundUp(7, 4));\n  ASSERT_EQ(2, CtaUtils::divideAndRoundUp(8, 4));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundUp(9, 4));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundUp(10, 4));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundUp(11, 4));\n  ASSERT_EQ(3, CtaUtils::divideAndRoundUp(12, 4));\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcUtilsTest, bufToTimespec)\n{\n  using namespace eos::mgm;\n  timespec src;\n  src.tv_sec = 1234;\n  src.tv_nsec = 5678;\n  std::string buf((char*)&src, sizeof(src));\n  const timespec result = CtaUtils::bufToTimespec(buf);\n  ASSERT_EQ(src.tv_sec, result.tv_sec);\n  ASSERT_EQ(src.tv_nsec, result.tv_nsec);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcUtilsTest, bufToTimespec_BufSizeMismatch)\n{\n  using namespace eos::mgm;\n  timespec src;\n  src.tv_sec = 1234;\n  src.tv_nsec = 5678;\n  std::string buf((char*)&src, sizeof(src) - 1);\n  ASSERT_THROW(CtaUtils::bufToTimespec(buf), CtaUtils::BufSizeMismatch);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcUtilsTest, readFdIntoStr)\n{\n  using namespace eos::mgm;\n  int pipeFds[2] = {0, 0};\n  ASSERT_NE(-1, pipe(pipeFds));\n  const char msg[] = \"1234\";\n  const std::string msgStr = msg;\n  ASSERT_EQ(sizeof(msg), ::write(pipeFds[1], msg, sizeof(msg)));\n  const ssize_t maxStrLen = sizeof(msg) - 1;\n  const std::string resultStr = CtaUtils::readFdIntoStr(pipeFds[0], maxStrLen);\n  ASSERT_EQ(msgStr, resultStr);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcUtilsTest, readFdIntoStr_write_gt_maxStrLen)\n{\n  using namespace eos::mgm;\n  int pipeFds[2] = {0, 0};\n  ASSERT_NE(-1, pipe(pipeFds));\n  const char msg[] = \"1234\";\n  ASSERT_GE(sizeof(msg), 2);\n  ASSERT_EQ(sizeof(msg), ::write(pipeFds[1], msg, sizeof(msg)));\n  const ssize_t maxStrLen = sizeof(msg) - 2; // Drop one char off the end\n  const std::string resultStr = CtaUtils::readFdIntoStr(pipeFds[0], maxStrLen);\n  const std::string expectedStr = \"123\";\n  ASSERT_EQ(expectedStr, resultStr);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcUtilsTest, readFdIntoStr_write_lt_maxStrLen)\n{\n  using namespace eos::mgm;\n  int pipeFds[2] = {0, 0};\n  ASSERT_NE(-1, pipe(pipeFds));\n  const char msg[] = \"1234\";\n  const std::string msgStr = msg;\n  ASSERT_EQ(sizeof(msg), ::write(pipeFds[1], msg, sizeof(msg)));\n  const ssize_t maxStrLen = sizeof(msg) + 1;\n  const std::string resultStr = CtaUtils::readFdIntoStr(pipeFds[0], maxStrLen);\n  ASSERT_EQ(msgStr, resultStr);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcUtilsTest, readFdIntoStr_out_of_range)\n{\n  using namespace eos::mgm;\n  EXPECT_THROW(CtaUtils::readFdIntoStr(0, 1LL<<33), std::out_of_range);\n}\n"
  },
  {
    "path": "unit_tests/mgm/EgroupTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: EgroupTests.cc\n// Author: Georgios Bitzes <georgios.bitzes@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"mgm/egroup/Egroup.hh\"\n\nusing namespace eos::mgm;\nusing namespace eos::common;\n\n//------------------------------------------------------------------------------\n// Test basic Egroup functionality\n//------------------------------------------------------------------------------\nTEST(Egroup, Functional)\n{\n  //----------------------------------------------------------------------------\n  // Yeah, yeah, this is not a unit test.. and maybe not appropriate to contact\n  // the CERN LDAP server from here, who knows where this might be executing.\n  // Feel free to delete the entire test if it creates problems.\n  //----------------------------------------------------------------------------\n  Egroup egroup;\n  ASSERT_TRUE(egroup.Member(\"esindril\", \"it-dep-sd\"));\n  ASSERT_FALSE(egroup.Member(\"esindril\", \"be-dep\"));\n  ASSERT_FALSE(egroup.Member(\"this-user-does-not-exist\", \"it-dep\"));\n  ASSERT_FALSE(egroup.Member(\"esindril\", \"this-group-does-not-exist\"));\n  ASSERT_TRUE(egroup.Member(\"esindril\", \"cern-accounts-primary\"));\n}\n\nTEST(Egroup, BasicSanity)\n{\n  SteadyClock clock(true);\n  Egroup egroup(&clock);\n  egroup.inject(\"user1\", \"awesome-users\", Egroup::Status::kMember);\n  egroup.inject(\"user2\", \"groovy-users\", Egroup::Status::kMember);\n  egroup.inject(\"user3\", \"awesome-users\", Egroup::Status::kMember);\n  egroup.inject(\"user3\", \"groovy-users\", Egroup::Status::kMember);\n  ASSERT_EQ(egroup.DumpMember(\"user1\", \"awesome-users\"),\n            \"egroup=awesome-users user=user1 member=true lifetime=1800\");\n  ASSERT_EQ(egroup.DumpMember(\"user1\", \"groovy-users\"),\n            \"egroup=groovy-users user=user1 member=false lifetime=1800\");\n  ASSERT_EQ(egroup.DumpMember(\"user2\", \"groovy-users\"),\n            \"egroup=groovy-users user=user2 member=true lifetime=1800\");\n  ASSERT_EQ(egroup.DumpMember(\"user2\", \"awesome-users\"),\n            \"egroup=awesome-users user=user2 member=false lifetime=1800\");\n  ASSERT_EQ(egroup.DumpMember(\"user3\", \"groovy-users\"),\n            \"egroup=groovy-users user=user3 member=true lifetime=1800\");\n  ASSERT_EQ(egroup.DumpMember(\"user3\", \"awesome-users\"),\n            \"egroup=awesome-users user=user3 member=true lifetime=1800\");\n  clock.advance(std::chrono::seconds(10));\n  ASSERT_EQ(egroup.DumpMember(\"user3\", \"awesome-users\"),\n            \"egroup=awesome-users user=user3 member=true lifetime=1790\");\n  clock.advance(std::chrono::seconds(1789));\n  ASSERT_EQ(egroup.DumpMember(\"user3\", \"awesome-users\"),\n            \"egroup=awesome-users user=user3 member=true lifetime=1\");\n  clock.advance(std::chrono::seconds(1));\n  ASSERT_EQ(egroup.DumpMember(\"user3\", \"awesome-users\"),\n            \"egroup=awesome-users user=user3 member=true lifetime=0\");\n  clock.advance(std::chrono::seconds(1));\n  // cache update, wait until the asynchronous thread makes progress\n  ASSERT_EQ(egroup.DumpMember(\"user3\", \"awesome-users\"),\n            \"egroup=awesome-users user=user3 member=true lifetime=-1\");\n\n  while (egroup.getPendingQueueSize() != 0) {}\n\n  ASSERT_EQ(egroup.DumpMember(\"user3\", \"awesome-users\"),\n            \"egroup=awesome-users user=user3 member=true lifetime=1800\");\n  // By official decree, user3 is no longer awesome. The cache will take a\n  // while to reflect this, though.\n  egroup.inject(\"user3\", \"awesome-users\", Egroup::Status::kNotMember);\n  clock.advance(std::chrono::seconds(100));\n  ASSERT_EQ(egroup.DumpMember(\"user3\", \"awesome-users\"),\n            \"egroup=awesome-users user=user3 member=true lifetime=1700\");\n  clock.advance(std::chrono::seconds(10000));\n  ASSERT_EQ(egroup.DumpMember(\"user3\", \"awesome-users\"),\n            \"egroup=awesome-users user=user3 member=true lifetime=-8300\");\n\n  while (egroup.getPendingQueueSize() != 0) { }\n\n  ASSERT_EQ(egroup.DumpMember(\"user3\", \"awesome-users\"),\n            \"egroup=awesome-users user=user3 member=false lifetime=1800\");\n  ASSERT_EQ(egroup.DumpMembers(),\n            \"egroup=awesome-users user=user1 member=true lifetime=-10101\\n\"\n            \"egroup=awesome-users user=user2 member=false lifetime=-10101\\n\"\n            \"egroup=awesome-users user=user3 member=false lifetime=1800\\n\"\n            \"egroup=groovy-users user=user1 member=false lifetime=-10101\\n\"\n            \"egroup=groovy-users user=user2 member=true lifetime=-10101\\n\"\n            \"egroup=groovy-users user=user3 member=true lifetime=-10101\\n\");\n}\n\nTEST(Egroup, ExplicitRefresh)\n{\n  SteadyClock clock(true);\n  Egroup egroup(&clock);\n  egroup.inject(\"user1\", \"awesome-users\", Egroup::Status::kNotMember);\n  ASSERT_EQ(egroup.DumpMember(\"user1\", \"awesome-users\"),\n            \"egroup=awesome-users user=user1 member=false lifetime=1800\");\n  clock.advance(std::chrono::seconds(10));\n  ASSERT_EQ(egroup.DumpMember(\"user1\", \"awesome-users\"),\n            \"egroup=awesome-users user=user1 member=false lifetime=1790\");\n  egroup.inject(\"user1\", \"awesome-users\", Egroup::Status::kMember);\n  clock.advance(std::chrono::seconds(10));\n  ASSERT_EQ(egroup.DumpMember(\"user1\", \"awesome-users\"),\n            \"egroup=awesome-users user=user1 member=false lifetime=1780\");\n  egroup.refresh(\"user1\", \"awesome-users\");\n  ASSERT_EQ(egroup.DumpMember(\"user1\", \"awesome-users\"),\n            \"egroup=awesome-users user=user1 member=true lifetime=1800\");\n}\n\n"
  },
  {
    "path": "unit_tests/mgm/FileSystemRegistryTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FileSystemRegistryTests.cc\n// Author: Georgios Bitzes <georgios.bitzes@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"mgm/utils/FileSystemRegistry.hh\"\n\nusing namespace eos::mgm;\nusing namespace eos::common;\n\n//------------------------------------------------------------------------------\n// Test basic FileSystemRegistry functionality\n//------------------------------------------------------------------------------\nTEST(FileSystemRegistry, BasicSanity) {\n  FileSystemRegistry registry;\n\n  FileSystemLocator locator1(\"example.com\", 1111, \"/path1\");\n  FileSystemLocator locator2(\"example.com\", 1111, \"/path2\");\n  FileSystemLocator locator3(\"example.com\", 1111, \"/path3\");\n  FileSystemLocator locator4(\"example.com\", 1111, \"/path4\");\n\n  ASSERT_TRUE(registry.registerFileSystem(locator1, 1, (eos::mgm::FileSystem*) 0x01));\n\n  // No duplicates\n  ASSERT_FALSE(registry.registerFileSystem(locator1, 1, (eos::mgm::FileSystem*) 0x01));\n  ASSERT_FALSE(registry.registerFileSystem(locator2, 2, (eos::mgm::FileSystem*) 0x01));\n  ASSERT_FALSE(registry.registerFileSystem(locator1, 1, (eos::mgm::FileSystem*) 0x02));\n\n  ASSERT_EQ(registry.lookupByID(1), (eos::mgm::FileSystem*) 0x01);\n  ASSERT_EQ(registry.lookupByID(2), nullptr);\n\n  ASSERT_EQ(registry.lookupByPtr( (eos::mgm::FileSystem*) 0x01), 1);\n  ASSERT_EQ(registry.lookupByPtr( (eos::mgm::FileSystem*) 0x02), 0);\n  ASSERT_EQ(registry.lookupByPtr(nullptr), 0);\n\n  ASSERT_EQ(registry.lookupByQueuePath(\"/eos/example.com:1111/fst/path1\"), (eos::mgm::FileSystem*) 0x01);\n  ASSERT_EQ(registry.lookupByQueuePath(\"/eos/example.com:1111/fst/path2\"), nullptr);\n\n  ASSERT_EQ(registry.size(), 1u);\n  ASSERT_FALSE(registry.eraseById(2));\n  ASSERT_FALSE(registry.eraseByPtr( (eos::mgm::FileSystem*) 0x02));\n\n  ASSERT_TRUE(registry.registerFileSystem(locator2,  2, (eos::mgm::FileSystem*) 0x02));\n  ASSERT_TRUE(registry.registerFileSystem(locator3, 3, (eos::mgm::FileSystem*) 0x03));\n  ASSERT_TRUE(registry.registerFileSystem(locator4, 4, (eos::mgm::FileSystem*) 0x04));\n\n  ASSERT_EQ(registry.size(), 4u);\n\n  ASSERT_EQ(registry.lookupByID(3), (eos::mgm::FileSystem*) 0x03);\n  ASSERT_EQ(registry.lookupByPtr( (eos::mgm::FileSystem*) 0x03), 3);\n\n  ASSERT_TRUE(registry.eraseById(3));\n\n  ASSERT_EQ(registry.size(), 3u);\n  ASSERT_EQ(registry.lookupByID(3), nullptr);\n  ASSERT_EQ(registry.lookupByPtr( (eos::mgm::FileSystem*) 0x03), 0);\n\n  ASSERT_TRUE(registry.eraseByPtr( (eos::mgm::FileSystem*) 0x04));\n\n  ASSERT_EQ(registry.size(), 2u);\n  ASSERT_EQ(registry.lookupByID(4), nullptr);\n  ASSERT_EQ(registry.lookupByPtr( (eos::mgm::FileSystem*) 0x04), 0);\n\n  registry.clear();\n\n  ASSERT_EQ(registry.size(), 0u);\n  ASSERT_EQ(registry.lookupByID(2), nullptr);\n  ASSERT_EQ(registry.lookupByPtr( (eos::mgm::FileSystem*) 0x02), 0);\n}\n\nTEST(FileSystemRegistry, QueuepathCollision) {\n  FileSystemRegistry registry;\n\n  FileSystemLocator locator(\"example.com\", 1111, \"/path1\");\n\n  // No duplicate queuepath\n  ASSERT_TRUE(registry.registerFileSystem(locator, 1, (eos::mgm::FileSystem*) 0x01));\n  ASSERT_FALSE(registry.registerFileSystem(locator, 2, (eos::mgm::FileSystem*) 0x01));\n}\n"
  },
  {
    "path": "unit_tests/mgm/FsViewTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FsViewsTests.cc\n// Author: Elvin-Alin Sindrilaru <esindril at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gmock/gmock.h\"\n#include \"gtest/gtest.h\"\n#include \"mgm/fsview/FsView.hh\"\n#define IN_TEST_HARNESS\n#include \"mgm/balancer/FsBalancerStats.hh\"\n#include \"mgm/balancer/FsBalancer.hh\"\n#undef IN_TEST_HARNESS\n#include \"mgm/utils/FilesystemUuidMapper.hh\"\n#include \"common/config/ConfigParsing.hh\"\n#include \"common/StringUtils.hh\"\n\n//------------------------------------------------------------------------------\n// Test const_iterator implementation\n//------------------------------------------------------------------------------\nTEST(FsView, ConstIteratorTest)\n{\n  using namespace eos::mgm;\n  GeoTree geo_tree;\n\n  for (int i = 0; i < 100; ++i) {\n    ASSERT_TRUE(geo_tree.insert(i));\n  }\n\n  for (int i = 0; i < 10; ++i) {\n    ASSERT_FALSE(geo_tree.insert(i));\n  }\n\n  for (auto iter = geo_tree.begin(); iter != geo_tree.end(); ++iter) {\n    ASSERT_TRUE(*iter >= 0);\n    ASSERT_TRUE(*iter < 100);\n  }\n\n  auto iter = geo_tree.begin();\n  --iter;\n  ASSERT_TRUE(iter == geo_tree.begin());\n\n  while (iter != geo_tree.end()) {\n    ++iter;\n  }\n\n  ++iter;\n  ASSERT_TRUE(iter == geo_tree.end());\n}\n\n//------------------------------------------------------------------------------\n// Test FilesystemUuidMapper\n//------------------------------------------------------------------------------\nTEST(FilesystemUuidMapper, BasicSanity)\n{\n  eos::mgm::FilesystemUuidMapper mapper;\n  ASSERT_FALSE(mapper.injectMapping(0, \"test\"));\n  ASSERT_EQ(mapper.size(), 0u);\n  ASSERT_FALSE(mapper.injectMapping(0, \"aaa\"));\n  ASSERT_EQ(mapper.size(), 0u);\n  ASSERT_FALSE(mapper.injectMapping(1, \"\"));\n  ASSERT_EQ(mapper.size(), 0u);\n  ASSERT_TRUE(mapper.injectMapping(1, \"fs-1\"));\n  ASSERT_EQ(mapper.size(), 1u);\n  // conflict with fsid \"1\"\n  ASSERT_FALSE(mapper.injectMapping(1, \"fs-2\"));\n  ASSERT_EQ(mapper.size(), 1u);\n  // conflict with uuid \"fs-1\"\n  ASSERT_FALSE(mapper.injectMapping(2, \"fs-1\"));\n  ASSERT_EQ(mapper.size(), 1u);\n  // conflict with itself, fine, nothing changes\n  ASSERT_TRUE(mapper.injectMapping(1, \"fs-1\"));\n  ASSERT_EQ(mapper.size(), 1u);\n  // accessor tests\n  ASSERT_TRUE(mapper.hasFsid(1));\n  ASSERT_FALSE(mapper.hasFsid(2));\n  ASSERT_TRUE(mapper.hasUuid(\"fs-1\"));\n  ASSERT_FALSE(mapper.hasUuid(\"fs-2\"));\n  ASSERT_EQ(mapper.lookup(\"fs-1\"), 1);\n  ASSERT_EQ(mapper.lookup(\"fs-2\"), 0);\n  ASSERT_EQ(mapper.lookup(1), \"fs-1\");\n  ASSERT_EQ(mapper.lookup(2), \"\");\n  // Removal tests\n  ASSERT_FALSE(mapper.remove(2));\n  ASSERT_TRUE(mapper.remove(1));\n  ASSERT_EQ(mapper.size(), 0u);\n  ASSERT_FALSE(mapper.hasFsid(1));\n  ASSERT_FALSE(mapper.hasUuid(\"fs-1\"));\n  ASSERT_FALSE(mapper.remove(1));\n  ASSERT_FALSE(mapper.remove(\"fs-1\"));\n  ASSERT_TRUE(mapper.injectMapping(2, \"fs-2\"));\n  ASSERT_TRUE(mapper.injectMapping(3, \"fs-3\"));\n  ASSERT_TRUE(mapper.injectMapping(4, \"fs-4\"));\n  ASSERT_FALSE(mapper.injectMapping(5, \"fs-4\"));\n  ASSERT_FALSE(mapper.injectMapping(3, \"fs-5\"));\n  ASSERT_TRUE(mapper.injectMapping(3, \"fs-3\")); // exists already\n  ASSERT_EQ(mapper.size(), 3u);\n  ASSERT_FALSE(mapper.remove(\"fs-5\"));\n  ASSERT_TRUE(mapper.remove(\"fs-3\"));\n  ASSERT_EQ(mapper.size(), 2u);\n  ASSERT_FALSE(mapper.hasUuid(\"fs-3\"));\n  ASSERT_FALSE(mapper.hasFsid(3));\n  // Try to allocate existing uuid\n  ASSERT_EQ(mapper.allocate(\"fs-2\"), 2);\n  ASSERT_EQ(mapper.allocate(\"fs-4\"), 4);\n  ASSERT_EQ(mapper.size(), 2u);\n  ASSERT_EQ(mapper.allocate(\"fs-5\"), 5);\n  ASSERT_EQ(mapper.allocate(\"fs-6\"), 6);\n  ASSERT_EQ(mapper.allocate(\"fs-7\"), 7);\n  ASSERT_EQ(mapper.size(), 5u);\n  ASSERT_TRUE(mapper.injectMapping(63999, \"fs-63999\"));\n  ASSERT_EQ(mapper.allocate(\"fs-64000\"), 64000);\n  ASSERT_EQ(mapper.allocate(\"fs-1\"), 1);\n  ASSERT_EQ(mapper.allocate(\"fs-3\"), 3);\n  ASSERT_EQ(mapper.allocate(\"fs-8\"), 8);\n  ASSERT_EQ(mapper.allocate(\"fs-9\"), 9);\n  ASSERT_EQ(mapper.lookup(\"fs-8\"), 8);\n  ASSERT_EQ(mapper.lookup(8), \"fs-8\");\n}\n\nTEST(ConfigParsing, FilesystemEntry)\n{\n  std::map<std::string, std::string> results;\n  ASSERT_TRUE(eos::common::ConfigParsing::parseFilesystemConfig(\n                \"bootcheck=0 bootsenttime=1480576520 configstatus=empty drainperiod=86400 drainstatus=drained graceperiod=3600 headroom=25000000000 host=p05798818d95041.cern.ch hostport=p05798818d95041.cern.ch:1095 id=7259 path=/data46 port=1095 queue=/eos/p05798818d95041.cern.ch:1095/fst queuepath=/eos/p05798818d95041.cern.ch:1095/fst/data46 scaninterval=604800 schedgroup=spare uuid=62dce94a-71de-4904-8105-534c61ce2eaa\",\n                results));\n  ASSERT_EQ(results[\"bootcheck\"], \"0\");\n  ASSERT_EQ(results[\"bootsenttime\"], \"1480576520\");\n  ASSERT_EQ(results[\"configstatus\"], \"empty\");\n  ASSERT_EQ(results[\"drainperiod\"], \"86400\");\n  ASSERT_EQ(results[\"drainstatus\"], \"drained\");\n  ASSERT_EQ(results[\"graceperiod\"], \"3600\");\n  ASSERT_EQ(results[\"headroom\"], \"25000000000\");\n  ASSERT_EQ(results[\"host\"], \"p05798818d95041.cern.ch\");\n  ASSERT_EQ(results[\"hostport\"], \"p05798818d95041.cern.ch:1095\");\n  ASSERT_EQ(results[\"id\"], \"7259\");\n  ASSERT_EQ(results[\"path\"], \"/data46\");\n  ASSERT_EQ(results[\"port\"], \"1095\");\n  ASSERT_EQ(results[\"queue\"], \"/eos/p05798818d95041.cern.ch:1095/fst\");\n  ASSERT_EQ(results[\"queuepath\"], \"/eos/p05798818d95041.cern.ch:1095/fst/data46\");\n  ASSERT_EQ(results[\"scaninterval\"], \"604800\");\n  ASSERT_EQ(results[\"schedgroup\"], \"spare\");\n  ASSERT_EQ(results[\"uuid\"], \"62dce94a-71de-4904-8105-534c61ce2eaa\");\n  ASSERT_EQ(results.size(), 17u);\n}\n\nTEST(ConfigParsing, ParseAndJoin)\n{\n  std::string entry =\n    \"configstatus=rw drainperiod=86400 graceperiod=86400 host=example.cern.ch hostport=example.cern.ch:3001 id=1 path=/volume1/fst-space/1 port=3001 queue=/eos/example.cern.ch:3001/fst queuepath=/eos/example.cern.ch:3001/fst/volume1/fst-space/1 scan_disk_interval=14400 scan_ns_interval=259200 scan_ns_rate=50 scaninterval=604800 scanrate=100 schedgroup=default.0 uuid=fst-1\";\n  std::map<std::string, std::string> configEntry;\n  ASSERT_TRUE(eos::common::ConfigParsing::parseFilesystemConfig(entry,\n              configEntry));\n  ASSERT_EQ(eos::common::joinMap(configEntry, \" \"), entry);\n}\n\nTEST(ConfigParsing, RelocateFilesystem)\n{\n  std::map<std::string, std::string> configEntry;\n  ASSERT_TRUE(eos::common::ConfigParsing::parseFilesystemConfig(\n                \"configstatus=rw drainperiod=86400 graceperiod=86400 host=example.cern.ch hostport=example.cern.ch:3001 id=1 path=/volume1/fst-space/1 port=3001 queue=/eos/example.cern.ch:3001/fst queuepath=/eos/example.cern.ch:3001/fst/volume1/fst-space/1 scan_disk_interval=14400 scan_ns_interval=259200 scan_ns_rate=50 scaninterval=604800 scanrate=100 schedgroup=default.0 uuid=fst-1\",\n                configEntry));\n  ASSERT_TRUE(eos::common::ConfigParsing::relocateFilesystem(\"example-2.cern.ch\",\n              5001, configEntry));\n  ASSERT_EQ(eos::common::joinMap(configEntry, \" \"),\n            \"configstatus=rw drainperiod=86400 graceperiod=86400 host=example-2.cern.ch hostport=example-2.cern.ch:5001 id=1 path=/volume1/fst-space/1 port=5001 queue=/eos/example-2.cern.ch:5001/fst queuepath=/eos/example-2.cern.ch:5001/fst/volume1/fst-space/1 scan_disk_interval=14400 scan_ns_interval=259200 scan_ns_rate=50 scaninterval=604800 scanrate=100 schedgroup=default.0 uuid=fst-1\"\n           );\n}\n\n//------------------------------------------------------------------------------\n// MockFsGroup class\n//------------------------------------------------------------------------------\nclass MockFsGroup: public eos::mgm::FsGroup\n{\npublic:\n  MockFsGroup(const char* name):\n    eos::mgm::FsGroup(name)\n  {}\n\n  MOCK_METHOD3(MaxAbsDeviation, double(const char*, bool,\n                                       const std::set<eos::common::FileSystem::fsid_t>*));\n};\n\n//------------------------------------------------------------------------------\n// Test GetUnbalancedGroups\n//------------------------------------------------------------------------------\nTEST(FsBalancerStats, Update)\n{\n  using eos::mgm::FsView;\n  using ::testing::_;\n  using ::testing::Return;\n  int num_groups = 9;\n  const std::string space = \"default\";\n  eos::common::InstanceName::set(\"unitest\");\n  eos::mgm::FsView fs_view;\n  auto& set_grps = fs_view.mSpaceGroupView[space];\n  std::map<int, eos::mgm::FsGroup*> map_groups;\n\n  for (int i = 1; i <= num_groups; ++i) {\n    MockFsGroup* mock = new MockFsGroup(SSTR(space << \".\" << i).c_str());\n    EXPECT_CALL(*mock, MaxAbsDeviation(_, _, _)).WillRepeatedly(Return(i * 10));\n    set_grps.emplace(mock);\n    map_groups.emplace(i, mock);\n  }\n\n  // Map threshold values to number of unbalanced groups\n  std::map<double, int> map_results {\n    {90, 0}, {80, 1}, {70, 2}, {60, 3}, {50, 4}, {40, 5}\n  };\n\n  // This part actually check the GetUnbalancerdGroups functionality\n  for (const auto& pair : map_results) {\n    ASSERT_EQ(pair.second, fs_view.GetUnbalancedGroups(space, pair.first).size());\n  }\n\n  // Now test the FsBalancerSTats::Update\n  double threshold = 50;\n  eos::mgm::FsBalancerStats fsb_stats(space);\n  fsb_stats.UpdateInfo(&fs_view, threshold);\n  ASSERT_EQ(4, fsb_stats.mGrpToMaxDev.size());\n  fsb_stats.UpdateInfo(&fs_view, threshold + 10);\n  ASSERT_EQ(3, fsb_stats.mGrpToMaxDev.size());\n  fsb_stats.UpdateInfo(&fs_view, threshold - 10);\n  ASSERT_EQ(5, fsb_stats.mGrpToMaxDev.size());\n\n  for (auto& elem : set_grps) {\n    delete elem;\n  }\n\n  eos::common::InstanceName::clear();\n}\n\n//------------------------------------------------------------------------------\n// Test GetRandomIter\n//------------------------------------------------------------------------------\nTEST(FsBalancer, GetRandomIter)\n{\n  std::vector<int> vect {11, 21, 35, 43, 59};\n  std::set<int> set_expected(vect.begin(), vect.end());\n  std::set<int> set_obtained;\n  int attempts = vect.size() * 2;\n\n  for (int i = 0; i < attempts; ++i) {\n    auto it = eos::mgm::FsBalancer::GetRandomIter(vect);\n    ASSERT_FALSE(it == vect.end());\n    set_obtained.insert(*it);\n  }\n\n  for (const auto& elem : set_obtained) {\n    ASSERT_TRUE(set_expected.find(elem) != set_expected.end());\n  }\n}\n"
  },
  {
    "path": "unit_tests/mgm/FsckEntryTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FsckEntryTests.cc\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"gmock/gmock.h\"\n#include \"common/LayoutId.hh\"\n#define IN_TEST_HARNESS\n#include \"mgm/fsck/FsckEntry.hh\"\n#undef IN_TEST_HARNESS\n\nusing ::testing::Return;\nusing eos::common::LayoutId;\nusing eos::common::FileSystem;\nstatic constexpr uint64_t kTimestampSec {1560331003};\nstatic constexpr uint64_t kFileSize {256256};\nstatic std::string kChecksum {\"74d77c3a\"};\n\n//------------------------------------------------------------------------------\n// MockRepairJob that doesn't trigger any TPC transfer\n//------------------------------------------------------------------------------\nclass MockRepairJob: public eos::mgm::FsckRepairJob\n{\npublic:\n  MockRepairJob(eos::common::FileId::fileid_t fid,\n                FileSystem::fsid_t fsid_src,\n                FileSystem::fsid_t fsid_trg = 0,\n                std::set<FileSystem::fsid_t> exclude_srcs = {},\n                std::set<FileSystem::fsid_t> exclude_dsts = {},\n                bool drop_src = true,\n                const std::string& app_tag = \"fsck_mock\"):\n    eos::mgm::FsckRepairJob(fid, fsid_src, fsid_trg, exclude_srcs,\n                            exclude_dsts, drop_src, app_tag)\n  {}\n\n  MOCK_METHOD0(DoItNoExcept, void());\n  virtual void DoIt() noexcept\n  {\n    return DoItNoExcept();\n  }\n  MOCK_CONST_METHOD0(GetStatus, eos::mgm::FsckRepairJob::Status());\n};\n\n//------------------------------------------------------------------------------\n// Test fixture for the FsckEntry\n//------------------------------------------------------------------------------\nclass FsckEntryTest: public ::testing::Test\n{\nprotected:\n  //----------------------------------------------------------------------------\n  //! Set up\n  //------------------------------------------------------------------------------\n  void SetUp() override\n  {\n    mRepairJob = nullptr;\n    mFsckEntry = std::unique_ptr<eos::mgm::FsckEntry>\n      (new eos::mgm::FsckEntry(1234567, {3}, \"none\", false, nullptr));\n    PopulateMgmFmd();\n\n    for (auto fsid : mFsckEntry->mMgmFmd.locations()) {\n      PopulateFstFmd(fsid);\n    }\n\n    // Redefine the repair factory to return a MockRepairJob\n    mFsckEntry->mRepairFactory =\n      [&](eos::common::FileId::fileid_t fid,\n          FileSystem::fsid_t fsid_src,\n          FileSystem::fsid_t fsid_trg ,\n          std::set<FileSystem::fsid_t> exclude_srcs,\n          std::set<FileSystem::fsid_t> exclude_dsts,\n          bool drop_src,\n    const std::string & app_tag,\n          bool repair_excluded) {\n      if (mRepairJob) {\n        return mRepairJob;\n      } else {\n        mRepairJob.reset(new MockRepairJob(fid, fsid_src, fsid_trg, exclude_srcs,\n                                           exclude_dsts, drop_src, app_tag));\n      }\n\n      return mRepairJob;\n    };\n  }\n\n  //----------------------------------------------------------------------------\n  //! Tear down - not needed as everything is already handled by destructor\n  //----------------------------------------------------------------------------\n  // void TearDown() override;\n\n  //----------------------------------------------------------------------------\n  //! Populate with dummy data the MGM fmd structure\n  //----------------------------------------------------------------------------\n  void PopulateMgmFmd()\n  {\n    auto& fmd = mFsckEntry->mMgmFmd;\n    // Populate FileMd.proto\n    fmd.set_id(1234567);\n    fmd.set_cont_id(199991);\n    fmd.set_uid(1001);\n    fmd.set_gid(2002);\n    fmd.set_size(kFileSize);\n    // Layout with two replicas, adler checksum\n    fmd.set_layout_id(std::stoul(\"0x0100112\", nullptr, 16));\n    fmd.set_name(\"test_file.dat\");\n    // Date: 06/12/2019 @ 9:16am\n    struct timespec ts;\n    ts.tv_sec = kTimestampSec;\n    ts.tv_nsec = 0;\n    fmd.set_ctime(&ts, sizeof(ts));\n    fmd.set_mtime(&ts, sizeof(ts));\n    size_t xs_sz;\n    auto xs_buff = eos::common::StringConversion::Hex2BinDataChar(kChecksum, xs_sz);\n    fmd.set_checksum(xs_buff.get(), xs_sz);\n    fmd.add_locations(3);\n    fmd.add_locations(5);\n  }\n\n  //----------------------------------------------------------------------------\n  //! Populate with dummy data the FST fmd structure\n  //----------------------------------------------------------------------------\n  void PopulateFstFmd(FileSystem::fsid_t fsid)\n  {\n    std::unique_ptr<eos::mgm::FstFileInfoT> finfo  {\n      new eos::mgm::FstFileInfoT(\"/data01/00000000/0012d687\", eos::mgm::FstErr::None)};\n    finfo->mDiskSize = kFileSize;\n    auto& proto_fmd = finfo->mFstFmd.mProtoFmd;\n    // Populate FmdBase.proto\n    proto_fmd.set_fid(123456);\n    proto_fmd.set_cid(199991);\n    proto_fmd.set_fsid(fsid);\n    proto_fmd.set_ctime(kTimestampSec);\n    proto_fmd.set_ctime_ns(0);\n    proto_fmd.set_mtime(kTimestampSec);\n    proto_fmd.set_mtime_ns(0);\n    proto_fmd.set_atime(kTimestampSec);\n    proto_fmd.set_atime_ns(0);\n    // proto_fmd.set_checktime() unset\n    proto_fmd.set_size(kFileSize);\n    proto_fmd.set_disksize(kFileSize);\n    proto_fmd.set_mgmsize(kFileSize);\n    proto_fmd.set_checksum(kChecksum);\n    proto_fmd.set_diskchecksum(kChecksum);\n    proto_fmd.set_mgmchecksum(kChecksum);\n    proto_fmd.set_lid(std::stoul(\"0x0100112\", nullptr, 16));\n    proto_fmd.set_uid(1001);\n    proto_fmd.set_gid(2002);\n    proto_fmd.set_filecxerror(0);\n    proto_fmd.set_blockcxerror(0);\n    proto_fmd.set_layouterror(0);\n    proto_fmd.set_locations(\"3,5,\");\n    mFsckEntry->mFstFileInfo.insert(std::make_pair(fsid, std::move(finfo)));\n  }\n\n  std::unique_ptr<eos::mgm::FsckEntry> mFsckEntry;\n  std::shared_ptr<eos::mgm::FsckRepairJob> mRepairJob;\n};\n\n//------------------------------------------------------------------------------\n// MGM checksum difference\n//------------------------------------------------------------------------------\nTEST_F(FsckEntryTest, MgmXsDiff)\n{\n  using eos::common::StringConversion;\n  mFsckEntry->mReportedErr = eos::common::FsckErr::MgmXsDiff;\n  size_t xs_sz;\n  auto xs_buff = eos::common::StringConversion::Hex2BinDataChar(\"aabbccdd\",\n                 xs_sz);\n  auto& mgm_fmd = mFsckEntry->mMgmFmd;\n  mgm_fmd.set_checksum(xs_buff.get(), xs_sz);\n  // The new MGM FMD chechsum should be different from the initial one\n  ASSERT_STRNE(kChecksum.c_str(),\n               StringConversion::BinData2HexString\n               (mgm_fmd.checksum().c_str(),\n                SHA256_DIGEST_LENGTH,\n                LayoutId::GetChecksumLen(mgm_fmd.layout_id())).c_str());\n  ASSERT_TRUE(mFsckEntry->Repair());\n  // After a successful repair the checksum should match the original one\n  ASSERT_STREQ(kChecksum.c_str(),\n               StringConversion::BinData2HexString\n               (mgm_fmd.checksum().c_str(),\n                SHA256_DIGEST_LENGTH,\n                LayoutId::GetChecksumLen(mgm_fmd.layout_id())).c_str());\n}\n\n//------------------------------------------------------------------------------\n// MGM checksum difference and one FST replica can not be contacted\n//------------------------------------------------------------------------------\nTEST_F(FsckEntryTest, MgmXsDiffFstNoContact)\n{\n  using eos::common::StringConversion;\n  mFsckEntry->mReportedErr = eos::common::FsckErr::MgmXsDiff;\n  size_t xs_sz;\n  auto xs_buff = eos::common::StringConversion::Hex2BinDataChar(\"aabbccdd\",\n                 xs_sz);\n  auto& mgm_fmd = mFsckEntry->mMgmFmd;\n  mgm_fmd.set_checksum(xs_buff.get(), xs_sz);\n  // The new MGM FMD chechsum should be different from the initial one\n  ASSERT_STRNE(kChecksum.c_str(),\n               StringConversion::BinData2HexString\n               (mgm_fmd.checksum().c_str(),\n                SHA256_DIGEST_LENGTH,\n                LayoutId::GetChecksumLen(mgm_fmd.layout_id())).c_str());\n  // Mark one of the FST replicas as NoContact\n  auto& finfo = mFsckEntry->mFstFileInfo.begin()->second;\n  finfo->mFstErr = eos::mgm::FstErr::NoContact;\n  ASSERT_FALSE(mFsckEntry->Repair());\n}\n\n//------------------------------------------------------------------------------\n// MGM size difference\n//------------------------------------------------------------------------------\nTEST_F(FsckEntryTest, MgmSzDiff)\n{\n  mFsckEntry->mReportedErr = eos::common::FsckErr::MgmSzDiff;\n  auto& mgm_fmd = mFsckEntry->mMgmFmd;\n  mgm_fmd.set_size(123456789);\n  // The new MGM FMD size should be different from the initial one\n  ASSERT_NE(kFileSize, mgm_fmd.size());\n  ASSERT_TRUE(mFsckEntry->Repair());\n  // After a successful repair the size should match the original one\n  ASSERT_EQ(kFileSize, mgm_fmd.size());\n}\n\n//------------------------------------------------------------------------------\n// FST size difference\n//------------------------------------------------------------------------------\nTEST_F(FsckEntryTest, FstSzDiff)\n{\n  // Set the desired type of error\n  mFsckEntry->mReportedErr = eos::common::FsckErr::FstSzDiff;\n  // All FST sizes match, repair succeeds - no bad replicas\n  ASSERT_TRUE(mFsckEntry->Repair());\n\n  // All FST fmd sizes are different, repair fails - no good replicas\n  for (auto& pair : mFsckEntry->mFstFileInfo) {\n    auto& finfo = pair.second;\n    finfo->mFstFmd.mProtoFmd.set_disksize(1);\n  }\n\n  ASSERT_FALSE(mFsckEntry->Repair());\n  // Set the first FST fmd disksize to the correct one - repair successful\n  std::shared_ptr<eos::mgm::FsckRepairJob> repair_job =\n    mFsckEntry->mRepairFactory(0, 0, 0, {}, {}, true, \"none\", false);\n  MockRepairJob* mock_job = static_cast<MockRepairJob*>(repair_job.get());\n  EXPECT_CALL(*mock_job, DoItNoExcept);\n  EXPECT_CALL(*mock_job, GetStatus).\n  WillOnce(Return(eos::mgm::FsckRepairJob::Status::OK));\n  auto& finfo = mFsckEntry->mFstFileInfo.begin()->second;\n  finfo->mFstFmd.mProtoFmd.set_disksize(finfo->mFstFmd.mProtoFmd.size());\n  ASSERT_TRUE(mFsckEntry->Repair());\n}\n\n//------------------------------------------------------------------------------\n// FST xs difference\n//------------------------------------------------------------------------------\nTEST_F(FsckEntryTest, FstXsDiff)\n{\n  // Set the desired type of error\n  mFsckEntry->mReportedErr = eos::common::FsckErr::FstXsDiff;\n  // All FST xs match, repair succeeds - no bad replicas\n  ASSERT_TRUE(mFsckEntry->Repair());\n\n  // All FST fmd xs are different, repair failes - no good replicas\n  for (auto& pair : mFsckEntry->mFstFileInfo) {\n    auto& finfo = pair.second;\n    finfo->mFstFmd.mProtoFmd.set_diskchecksum(\"abcdefab\");\n  }\n\n  ASSERT_FALSE(mFsckEntry->Repair());\n  // Set the first FST fmd xs to the correct one - repair successful\n  // @note the repair factory always returns the same repair job object so that\n  // we can easily set expecteations on it\n  std::shared_ptr<eos::mgm::FsckRepairJob> repair_job =\n    mFsckEntry->mRepairFactory(0, 0, 0, {}, {}, true, \"none\", false);\n  MockRepairJob* mock_job = static_cast<MockRepairJob*>(repair_job.get());\n  EXPECT_CALL(*mock_job, DoItNoExcept);\n  EXPECT_CALL(*mock_job, GetStatus).\n  WillOnce(Return(eos::mgm::FsckRepairJob::Status::OK));\n  auto& finfo = mFsckEntry->mFstFileInfo.begin()->second;\n  finfo->mFstFmd.mProtoFmd.set_diskchecksum(kChecksum);\n  ASSERT_TRUE(mFsckEntry->Repair());\n}\n\n//------------------------------------------------------------------------------\n// Unregistered replica when file has enough replicas gets dropped\n// Begin:                  Final:\n// MGM: 3 5                MGM: 3 5\n// FST: 3 5 101(u)         FST: 3 5\n//------------------------------------------------------------------------------\nTEST_F(FsckEntryTest, UnregReplicaDrop)\n{\n  FileSystem::fsid_t unreg_fsid = 101;\n  // Set the desired type of error\n  mFsckEntry->mReportedErr = eos::common::FsckErr::UnregRepl;\n  // Add one more FST replica which is unregistered\n  PopulateFstFmd(unreg_fsid);\n  ASSERT_TRUE(mFsckEntry->Repair());\n  // The replica on FS 101 should be dropped from the map\n  ASSERT_TRUE(mFsckEntry->mFstFileInfo.find(unreg_fsid) ==\n              mFsckEntry->mFstFileInfo.end());\n  ASSERT_TRUE(mFsckEntry->mFstFileInfo.size() ==\n              LayoutId::GetStripeNumber(mFsckEntry->mMgmFmd.layout_id()) + 1);\n}\n\n//------------------------------------------------------------------------------\n// Unregistered replica when file doesn't have enough replicas gets added\n// Begin:                Final:\n// MGM: 5                MGM: 5 101\n// FST: 5 101(u)         FST: 5 101\n//------------------------------------------------------------------------------\nTEST_F(FsckEntryTest, UnregReplicaAdd)\n{\n  FileSystem::fsid_t unreg_fsid = 101;\n  // Set the desired type of error\n  mFsckEntry->mReportedErr = eos::common::FsckErr::UnregRepl;\n  // Add one more FST replica which is unregistered\n  PopulateFstFmd(unreg_fsid);\n  // Drop the replica on fsid 3\n  FileSystem::fsid_t drop_fsid = 3;\n  ASSERT_EQ(1, mFsckEntry->mFstFileInfo.erase(drop_fsid));\n  auto locations = mFsckEntry->mMgmFmd.mutable_locations();\n\n  for (auto it = locations->begin(); it != locations->end(); ++it) {\n    if (*it == drop_fsid) {\n      locations->erase(it);\n      break;\n    }\n  }\n\n  ASSERT_TRUE(mFsckEntry->Repair());\n  // The replica on FS 101 should be added to the map and MGM meta data info\n  ASSERT_TRUE(mFsckEntry->mFstFileInfo.find(unreg_fsid) !=\n              mFsckEntry->mFstFileInfo.end());\n  ASSERT_TRUE(mFsckEntry->mFstFileInfo.size() ==\n              LayoutId::GetStripeNumber(mFsckEntry->mMgmFmd.layout_id()) + 1);\n}\n\n//------------------------------------------------------------------------------\n// Over-replicated files should drop some of their replicas to reach the\n// nominal number of replicas of the layout\n// Begin:                Final:\n// MGM: 3 5 6 7          MGM: 3 5\n// FST: 3 5 6 7          FST: 3 5\n//------------------------------------------------------------------------------\nTEST_F(FsckEntryTest, FileOverReplicated)\n{\n  // Set the desired type of error\n  mFsckEntry->mReportedErr = eos::common::FsckErr::DiffRepl;\n\n  for (const auto& elem : {\n  6, 7\n}) {\n    PopulateFstFmd(elem);\n    mFsckEntry->mMgmFmd.add_locations(elem);\n  }\n  // Over-replicated\n  ASSERT_TRUE(mFsckEntry->mFstFileInfo.size() >\n              LayoutId::GetStripeNumber(mFsckEntry->mMgmFmd.layout_id()) + 1);\n  ASSERT_TRUE(mFsckEntry->Repair());\n  ASSERT_TRUE(mFsckEntry->mFstFileInfo.size() ==\n              LayoutId::GetStripeNumber(mFsckEntry->mMgmFmd.layout_id()) + 1);\n}\n\n//------------------------------------------------------------------------------\n// Under-replicated files should trigger new FsckRepair jobs that create new\n// replicas up to the nominal number of replicas of the layout\n// Begin:                Final:\n// MGM: 3                MGM: 3 x\n// FST: 3                FST: 3 x\n//------------------------------------------------------------------------------\nTEST_F(FsckEntryTest, FileUnderReplicated)\n{\n  // Set the desired type of error\n  mFsckEntry->mReportedErr = eos::common::FsckErr::DiffRepl;\n  // Drop the replica on fsid 5\n  FileSystem::fsid_t drop_fsid = 5;\n  ASSERT_EQ(1, mFsckEntry->mFstFileInfo.erase(drop_fsid));\n  auto locations = mFsckEntry->mMgmFmd.mutable_locations();\n\n  for (auto it = locations->begin(); it != locations->end(); ++it) {\n    if (*it == drop_fsid) {\n      locations->erase(it);\n      break;\n    }\n  }\n\n  // Under-replicated\n  ASSERT_TRUE(mFsckEntry->mFstFileInfo.size() <\n              LayoutId::GetStripeNumber(mFsckEntry->mMgmFmd.layout_id()) + 1);\n  // Set the expectations\n  // @note the repair factory always returns the same repair job object so that\n  // we can easily set expecteations on it\n  std::shared_ptr<eos::mgm::FsckRepairJob> repair_job =\n    mFsckEntry->mRepairFactory(0, 0, 0, {}, {}, false, \"none\", false);\n  MockRepairJob* mock_job = static_cast<MockRepairJob*>(repair_job.get());\n  EXPECT_CALL(*mock_job, DoItNoExcept).Times(1);\n  EXPECT_CALL(*mock_job, GetStatus).\n  WillOnce(Return(eos::mgm::FsckRepairJob::Status::OK));\n  ASSERT_TRUE(mFsckEntry->Repair());\n}\n\n//------------------------------------------------------------------------------\n// Missgin replica should be dropped from the MGM file metadata and a repair\n// job shoudl bring the number of replicas back up to nominal number\n// Begin:                Final:\n// MGM: 3 5              MGM: 3 y\n// FST: 3                FST: 3 y\n//------------------------------------------------------------------------------\nTEST_F(FsckEntryTest, FileMissingReplica)\n{\n  // Set the desired type of error\n  mFsckEntry->mReportedErr = eos::common::FsckErr::MissRepl;\n  // Mark replica on file system 5 as not on disk\n  FileSystem::fsid_t miss_fsid = 5;\n  auto it = mFsckEntry->mFstFileInfo.find(miss_fsid);\n  it->second->mFstErr = eos::mgm::FstErr::NotOnDisk;\n  // Set the expectations\n  // @note the repair factory always returns the same repair job object so that\n  // we can easily set expecteations on it\n  std::shared_ptr<eos::mgm::FsckRepairJob> repair_job =\n    mFsckEntry->mRepairFactory(0, 0, 0, {}, {}, false, \"none\", false);\n  MockRepairJob* mock_job = static_cast<MockRepairJob*>(repair_job.get());\n  EXPECT_CALL(*mock_job, DoItNoExcept).Times(1);\n  EXPECT_CALL(*mock_job, GetStatus).\n  WillOnce(Return(eos::mgm::FsckRepairJob::Status::OK));\n  ASSERT_TRUE(mFsckEntry->Repair());\n  // The missing replicas should no longer be registered with the MGM fmd\n  bool found = false;\n\n  for (const auto& fsid : mFsckEntry->mMgmFmd.locations()) {\n    if (fsid == miss_fsid) {\n      found = true;\n      break;\n    }\n  }\n\n  ASSERT_FALSE(found);\n}\n"
  },
  {
    "path": "unit_tests/mgm/FusexCastBatchTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: FusexCastBatchTests.cc\n// Author: Elvin-Alin Sindrilaru <esindril at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"mgm/FuseServer/FusexCastBatch.hh\"\n\nTEST(FusexCastBatch, BasicFunctionality)\n{\n  eos::mgm::FusexCastBatch batch;\n  int value = 0;\n  batch.Register([ = ]() mutable { value++;});\n  batch.Register([ = ]() mutable { value = +2;});\n  ASSERT_EQ(2, batch.GetSize());\n  batch.Execute();\n  ASSERT_EQ(0, value);\n  batch.Register([&]() {\n    value++;\n  });\n  batch.Register([&]() {\n    value += 2;\n  });\n  batch.Register([&]() {\n    value += 3;\n  });\n  ASSERT_EQ(3, batch.GetSize());\n  batch.Execute();\n  ASSERT_EQ(6, value);\n}\n"
  },
  {
    "path": "unit_tests/mgm/HttpTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: HttpTests.cc\n// Author: Elvin-Alin Sindrilaru <esindril at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#define IN_TEST_HARNESS\n#include \"mgm/http/HttpServer.hh\"\n#undef IN_TEST_HARNESS\n\n//------------------------------------------------------------------------------\n// Test clientDN formatting according to different standards\n//------------------------------------------------------------------------------\nTEST(Http, FormatClientDN)\n{\n  eos::mgm::HttpServer http;\n  std::string ref =\n    \"/DC=ch/DC=cern/OU=Organic Units/OU=Users/CN=hroussea/CN=660542/CN=Herve Rousseau\";\n  std::string old_cnd =\n    \"/DC=ch/DC=cern/OU=Organic Units/OU=Users/CN=hroussea/CN=660542/CN=Herve Rousseau\";\n  ASSERT_TRUE(ref == http.ProcessClientDN(old_cnd));\n  std::string new_cnd =\n    \"CN=Herve Rousseau,CN=660542,CN=hroussea,OU=Users,OU=Organic Units,DC=cern,DC=ch\";\n  ASSERT_TRUE(ref == http.ProcessClientDN(new_cnd));\n}\n"
  },
  {
    "path": "unit_tests/mgm/IdTrackerTests.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file IdTrackerWithValidityTests.cc\n//! @author Elvin-Alin Sindrilaru <esindril at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"mgm/misc/IdTrackerWithValidity.hh\"\n\n//------------------------------------------------------------------------------\n// Test tracker basic functionality\n//------------------------------------------------------------------------------\nTEST(IdTrackerWithValidity, BasicFunctionality)\n{\n  using namespace eos::mgm;\n  IdTrackerWithValidity<uint64_t> tracker(std::chrono::seconds(10),\n                                          std::chrono::seconds(60), true);\n  auto& clock = tracker.GetClock();\n\n  for (uint64_t i = 11; i < 100; i += 10) {\n    tracker.AddEntry(i, TrackerType::Drain);\n    clock.advance(std::chrono::seconds(5));\n  }\n\n  for (uint64_t i = 11; i < 100; i += 10) {\n    ASSERT_TRUE(tracker.HasEntry(i));\n  }\n\n  for (uint64_t i = 12; i < 100; i += 10) {\n    ASSERT_FALSE(tracker.HasEntry(i));\n  }\n\n  clock.advance(std::chrono::seconds(16)); // Should expire the first entry\n  tracker.DoCleanup(TrackerType::Drain);\n  ASSERT_FALSE(tracker.HasEntry(11));\n  ASSERT_TRUE(tracker.HasEntry(21));\n  clock.advance(std::chrono::seconds(100)); // Should expire all entries\n  tracker.DoCleanup(TrackerType::Drain);\n\n  for (uint64_t i = 11; i < 100; i += 10) {\n    ASSERT_FALSE(tracker.HasEntry(i));\n  }\n\n  tracker.AddEntry(121, TrackerType::Drain);\n  ASSERT_TRUE(tracker.HasEntry(121));\n  tracker.RemoveEntry(121);\n  ASSERT_FALSE(tracker.HasEntry(121));\n\n  // Add enties with custom expiration time\n  for (uint64_t i = 13; i < 100; i += 10) {\n    tracker.AddEntry(i, TrackerType::Drain, std::chrono::seconds(i));\n  }\n\n  clock.advance(std::chrono::seconds(90));\n  tracker.DoCleanup(TrackerType::Drain);\n  // All but the last entry should be expired\n  ASSERT_TRUE(tracker.HasEntry(93));\n\n  for (uint64_t i = 13; i < 90; i += 10) {\n    ASSERT_FALSE(tracker.HasEntry(i));\n  }\n\n  tracker.Clear(TrackerType::All);\n  ASSERT_FALSE(tracker.HasEntry(93));\n}\n"
  },
  {
    "path": "unit_tests/mgm/IostatTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: IostatTests.cc\n// Author: Jaroslav Guenther <jaroslav dot guenther at cern dot ch> - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#define IN_TEST_HARNESS\n#include \"mgm/iostat/Iostat.hh\"\n#undef IN_TEST_HARNESS\n#include \"mgm/fsview/FsView.hh\"\n#include <map>\n#include <random>\n\nusing namespace eos::mgm;\n\n// to avoid assertion of empty instance name we set one here\n//eos::common::InstanceName::set(\"eosdevtest\");\n\n// using Google Test Fixtures (TEST_T); a new instance\n// of the following class will get created before each test\nPeriod LAST_DAY = Period::DAY;\nPeriod LAST_HOUR = Period::HOUR;\nPeriod LAST_5MIN = Period::FIVEMIN;\nPeriod LAST_1MIN = Period::ONEMIN;\nPercentComplete P90 = PercentComplete::p90;\nPercentComplete P95 = PercentComplete::p95;\nPercentComplete P99 = PercentComplete::p99;\nPercentComplete ALL = PercentComplete::p100;\n\nclass IostatTest : public :: testing::Test\n{\nprotected:\n  Iostat iostat;\n};\n\nclass MockFsView: public FsView\n{\npublic:\n  std::map<std::string, std::string> kvdict = {\n    {\"iostat::collect\", \"\"},\n    {\"iostat::report\", \"\"},\n    {\"iostat::reportnamespace\", \"\"},\n    {\"iostat::popularity\", \"\"},\n    {\"iostat::udptargets\", \"\"}\n  };\n  std::string GetGlobalConfig(const std::string& key) override\n  {\n    return kvdict.find(key)->second;\n  };\n  bool SetGlobalConfig(const std::string& key, const std::string& value) override\n  {\n    auto it = kvdict.find(key);\n\n    if (it != kvdict.end()) {\n      kvdict.erase(it);\n    }\n\n    kvdict[key] = value;\n    return true;\n  }\n};\n\n//------------------------------------------------------------------------------\n// Test basic Iostat functionality\n//------------------------------------------------------------------------------\nTEST_F(IostatTest, InitConfig)\n{\n  EXPECT_STREQ(\"iostat::collect\", iostat.gIostatCollect);\n  EXPECT_STREQ(\"iostat::report\", iostat.gIostatReportSave);\n  EXPECT_STREQ(\"iostat::reportnamespace\", iostat.gIostatReportNamespace);\n  EXPECT_STREQ(\"iostat::popularity\", iostat.gIostatPopularity);\n  EXPECT_STREQ(\"iostat::udptargets\", iostat.gIostatUdpTargetList);\n  ASSERT_EQ(0, iostat.gOpenReportFD);\n  ASSERT_EQ(false, iostat.mRunning);\n}\n\nTEST_F(IostatTest, StartStop)\n{\n  ASSERT_EQ(false, iostat.mRunning);\n  iostat.StartCollection();\n  ASSERT_EQ(true, iostat.mRunning);\n  iostat.StopCollection();\n  ASSERT_EQ(false, iostat.mRunning);\n}\n\nTEST_F(IostatTest, StoreApplyConfig)\n{\n  MockFsView mock_fsview;\n  std::string udplist = mock_fsview.GetGlobalConfig(iostat.gIostatUdpTargetList);\n  std::string iocollect = mock_fsview.GetGlobalConfig(iostat.gIostatCollect);\n  std::string ioreport = mock_fsview.GetGlobalConfig(iostat.gIostatReportSave);\n  std::string ioreportns = mock_fsview.GetGlobalConfig(\n                             iostat.gIostatReportNamespace);\n  std::string iopopularity = mock_fsview.GetGlobalConfig(\n                               iostat.gIostatPopularity);\n  ASSERT_EQ(\"\", udplist);\n  ASSERT_EQ(\"\", iocollect);\n  ASSERT_EQ(\"\", ioreport);\n  ASSERT_EQ(\"\", ioreportns);\n  ASSERT_EQ(\"\", iopopularity);\n  iostat.StoreIostatConfig(&mock_fsview);\n  // we check that all the remaining global config values were saved as expected\n  udplist = mock_fsview.GetGlobalConfig(iostat.gIostatUdpTargetList);\n  iocollect = mock_fsview.GetGlobalConfig(iostat.gIostatCollect);\n  ioreport = mock_fsview.GetGlobalConfig(iostat.gIostatReportSave);\n  ioreportns = mock_fsview.GetGlobalConfig(\n                 iostat.gIostatReportNamespace);\n  iopopularity = mock_fsview.GetGlobalConfig(\n                   iostat.gIostatPopularity);\n  ASSERT_EQ(\"\", udplist);\n  ASSERT_EQ(\"false\", iocollect);\n  ASSERT_EQ(\"true\", ioreport);\n  ASSERT_EQ(\"false\", ioreportns);\n  ASSERT_EQ(\"true\", iopopularity);\n  mock_fsview.kvdict[\"iostat::udptargets\"] = \"udptarget1\";\n  std::string out = iostat.EncodeUdpPopularityTargets();\n  ASSERT_EQ(\"\", out);\n  //EXPECT_CALL(mock_fsview, mock_fsview::GetGlobalConfig).Times(AtLeast(5));\n  iostat.ApplyConfig(&mock_fsview);\n  // Start collection should not get called\n  ASSERT_EQ(false, iostat.mRunning);\n  // AddUdpTarget gets called\n  out = iostat.EncodeUdpPopularityTargets();\n  ASSERT_EQ(\"udptarget1\", out);\n}\n\nTEST_F(IostatTest, AddRemoveUdpTargets)\n{\n  std::string expected = \"\";\n  std::string out = iostat.EncodeUdpPopularityTargets();\n  ASSERT_EQ(expected, out);\n  iostat.AddUdpTarget(\"target_1\", false);\n  iostat.AddUdpTarget(\"target_2\", false);\n  iostat.AddUdpTarget(\"target_3\", false);\n  expected = \"target_1|target_2|target_3\";\n  out = iostat.EncodeUdpPopularityTargets();\n  ASSERT_EQ(expected, out);\n  iostat.RemoveUdpTarget(\"target_2\");\n  expected = \"target_1|target_3\";\n  out = iostat.EncodeUdpPopularityTargets();\n  ASSERT_EQ(expected, out);\n}\n\nTEST(IostatPeriods, GetAddBufferData)\n{\n  using namespace std::chrono;\n  IostatPeriods iostattbins;\n  time_t now = 86400;\n  ASSERT_EQ(0, iostattbins.GetDataInPeriod(86400, 0, now));\n  ASSERT_EQ(0, iostattbins.GetDataInPeriod(86400, 86400, now));\n  ASSERT_EQ(0, iostattbins.GetDataInPeriod(0, 86400, now));\n  ASSERT_EQ(0, iostattbins.GetDataInPeriod(0, 0, now));\n  ASSERT_EQ(0, iostattbins.GetLongestTransferTime());\n  time_t stop = 86399;\n  time_t start = 0;\n  iostattbins.Add(86400, start, stop, now);\n\n  for (int i = 0; i < 86401; i++) {\n    ASSERT_EQ(i, iostattbins.GetDataInPeriod(i, 0, now));\n\n    if (i > 43200) {\n      ASSERT_EQ(86400 - i, iostattbins.GetDataInPeriod(i, i, now));\n    } else {\n      ASSERT_EQ(i, iostattbins.GetDataInPeriod(i, i, now));\n    }\n  }\n\n  // UpdateTransferSampleInfo called every 5 min\n  iostattbins.UpdateTransferSampleInfo(now);\n  ASSERT_EQ(77760, iostattbins.GetTimeToPercComplete(P90));\n  ASSERT_EQ(82080, iostattbins.GetTimeToPercComplete(P95));\n  ASSERT_EQ(85536, iostattbins.GetTimeToPercComplete(P99));\n  ASSERT_EQ(86400, iostattbins.GetTimeToPercComplete(ALL));\n  ASSERT_EQ(86400, iostattbins.GetLongestTransferTime());\n\n  // Test stamping zero all bins\n  for (int i = 0; i < 86400; i++) {\n    time_t stamp = now + i;\n    iostattbins.StampBufferZero(stamp);\n    ASSERT_EQ(86400 - i - 1, iostattbins.GetDataInPeriod(86400, 0, stamp));\n\n    if (i < 86399) {\n      ASSERT_EQ(1, iostattbins.GetDataInPeriod(1, i, stamp));\n    }\n\n    if (i < 86400 && i > 0) {\n      ASSERT_EQ(86400 - i - 1, iostattbins.GetDataInPeriod(86399, 1, stamp));\n    }\n\n    if (i < 86340) {\n      ASSERT_EQ(60, iostattbins.GetDataInPeriod(60, i, stamp));\n    }\n\n    if (i >= 86340) {\n      ASSERT_EQ(86400 - i - 1, iostattbins.GetDataInPeriod(60, i, stamp));\n    }\n  }\n\n  // Test adding and getting transfers of the same length and size\n  // but different start and stop times (adding integer value per bin)\n  double total = 0;\n  now = 2 * 86400;\n\n  for (int i = 0; i < 2 * 86400 - 99; i++) {\n    stop = start + 99;\n    iostattbins.Add(2000, start, stop, now);\n\n    if (stop < 86400) {\n      if (i % 100 == 0) {\n        // modulo operation to speed up the testing\n        ASSERT_EQ(0, iostattbins.GetDataInPeriod(86400, 0, now));\n      }\n    } else {\n      int out = (86400 - start + 1);\n\n      if (out > 0) {\n        total += (100 - out) * 20;\n      } else {\n        total += 2000;\n      }\n\n      if (i % 100 == 0) {\n        // modulo operation to speed up the testing\n        ASSERT_EQ(std::ceil(total), iostattbins.GetDataInPeriod(86400, 0, now));\n      }\n\n      start += 1;\n    }\n  }\n\n  // Zero the data buffer\n  for (int i = 0; i < 86401; i++) {\n    time_t stamp = now + i;\n    iostattbins.StampBufferZero(stamp);\n  }\n\n  auto ts_start = std::chrono::system_clock::now();\n  ASSERT_EQ(0, iostattbins.GetDataInPeriod(86400, 0, now));\n  auto ts_end = std::chrono::system_clock::now();\n  auto duration = std::chrono::duration_cast<std::chrono::microseconds>\n                  (ts_end - ts_start);\n  std::cerr << \"Time to GetDataInPeriod for all bins: \" << duration.count() <<\n            \" us\\n\";\n  // Test adding and getting transfers of the same length and size\n  // but different start and stop times (adding double value per bin)\n  total = 0;\n  now = 2 * 86400;\n  start = 0;\n\n  for (int i = 0; i < 2 * 86400 - 9; i++) {\n    stop = start + 9;\n    iostattbins.Add(1, start, stop, now);\n\n    if (stop < 86400) {\n      if (i % 100 == 0) {\n        // modulo operation to speed up the testing\n        ASSERT_EQ(0, iostattbins.GetDataInPeriod(86400, 0, now));\n      }\n    } else {\n      int out = (86400 - start + 1);\n\n      if (out > 0) {\n        total += (10 - out) * 0.1;\n      } else {\n        total += 1;\n      }\n\n      //std::cout << \"out\" << out << \" start\" << start << \" stop\" << stop << \" total\" <<\n      //          total << std::endl;\n      if (i % 100 == 0) {\n        // modulo operation to speed up the testing\n        ASSERT_EQ(std::ceil(total), iostattbins.GetDataInPeriod(86400, 0, now));\n      }\n\n      start += 1;\n    }\n  }\n\n  // Zero the data buffer\n  for (int i = 0; i < 86401; i++) {\n    time_t stamp = now + i;\n    iostattbins.StampBufferZero(stamp);\n  }\n\n  ASSERT_EQ(0, iostattbins.GetDataInPeriod(86400, 0, now));\n  // Test integral of the buffer for 4 transfers with same rate 1B/s,\n  // but with different length and start time\n  now = 86400;\n  iostattbins.UpdateTransferSampleInfo(now);\n\n  for (int i = 1; i < 5; i++) {\n    start = i * 10000;\n    stop = start + i * 10000 - 1;\n    //4*10000 + 3*10000 + 2*10000 + 1*10000\n    iostattbins.Add(i * 10000, start, stop, now);\n  }\n\n  iostattbins.UpdateTransferSampleInfo(now);\n  ASSERT_EQ(30000, iostattbins.GetTimeToPercComplete(P90));\n  ASSERT_EQ(35000, iostattbins.GetTimeToPercComplete(P95));\n  ASSERT_EQ(39000, iostattbins.GetTimeToPercComplete(P99));\n  ASSERT_EQ(40000, iostattbins.GetTimeToPercComplete(ALL));\n}\n\n//------------------------------------------------------------------------------\n// Generate sequential 1GB transfers taking between 1 and 5 min with\n// uniform probability. There are no overlapping transfers by design.\n//------------------------------------------------------------------------------\nTEST(IostatPeriods, SequentialTx)\n{\n  IostatPeriods iop;\n  time_t now = IostatPeriods::sBins * IostatPeriods::sBinWidth;\n  time_t start_ts = 0;\n  time_t stop_ts = 0;\n  unsigned long long val = 1024 * 1025 * 1024;\n  int count = 0;\n\n  while (stop_ts < now) {\n    stop_ts = start_ts + eos::common::getRandom(1, 5) * 60;\n    iop.Add(val, start_ts, stop_ts, now);\n    start_ts = stop_ts;\n    ++count;\n  }\n\n  ASSERT_GE(301, iop.GetLongestTransferTime());\n  ASSERT_EQ(count * val, iop.GetTotalSum());\n}\n"
  },
  {
    "path": "unit_tests/mgm/LRUTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: LRUTests.cc\n// Author: Georgios Bitzes <georgios.bitzes@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2019 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"mgm/lru/LRU.hh\"\n\n//------------------------------------------------------------------------------\n// Test \"sys.lru.expire.match\" policy parsing, just one entry\n//------------------------------------------------------------------------------\nTEST(LRUTests, ExpireMatchParsingSingle) {\n  std::map<std::string, time_t> results, expected;\n\n  ASSERT_TRUE(eos::mgm::LRU::parseExpireMatchPolicy(\"*:1d\", results));\n  expected = { {\"*\", 86400 } };\n  ASSERT_EQ(results, expected);\n\n  ASSERT_TRUE(eos::mgm::LRU::parseExpireMatchPolicy(\"*:1mo\", results));\n  expected = { {\"*\", 31 * 86400 } };\n  ASSERT_EQ(results, expected);\n}\n\n//------------------------------------------------------------------------------\n// Test \"sys.lru.expire.match\" policy parsing, multiple entries\n//------------------------------------------------------------------------------\nTEST(LRUTests, ExpireMatchParsingMultiple) {\n  std::map<std::string, time_t> results, expected;\n\n  ASSERT_TRUE(eos::mgm::LRU::parseExpireMatchPolicy(\"*.root:1mo,*.tgz:1w\", results));\n  expected = { {\"*.root\", 31 * 86400 }, {\"*.tgz\", 7 * 86400} };\n  ASSERT_EQ(results, expected);\n\n  ASSERT_TRUE(eos::mgm::LRU::parseExpireMatchPolicy(\"*.root:1mo,*.tgz:1w,*.txt:77d\", results));\n  expected = { {\"*.root\", 31 * 86400 }, {\"*.tgz\", 7 * 86400}, {\"*.txt\", 77 * 86400} };\n  ASSERT_EQ(results, expected);\n}\n\n//------------------------------------------------------------------------------\n// Test age and size policy parsing\n//------------------------------------------------------------------------------\nTEST(LRUTests, ExtractTimeSizeCriterias)\n{\n  std::ostringstream errMsg;\n  ssize_t size;\n  time_t age;\n  ASSERT_FALSE(eos::mgm::LRU::extractTimeSizeCriterias(\"\", age, size, errMsg));\n  ASSERT_TRUE(eos::mgm::LRU::extractTimeSizeCriterias(\"1\", age, size, errMsg));\n  ASSERT_EQ(0, size);\n  ASSERT_EQ(1, age);\n  ASSERT_TRUE(eos::mgm::LRU::extractTimeSizeCriterias(\"1mo:\", age, size, errMsg));\n  ASSERT_EQ(0, size);\n  ASSERT_EQ(31 * 86400, age);\n  ASSERT_FALSE(eos::mgm::LRU::extractTimeSizeCriterias(\"1w:1G\", age, size, errMsg));\n  ASSERT_TRUE(eos::mgm::LRU::extractTimeSizeCriterias(\"1mo:>1K\", age, size, errMsg));\n  ASSERT_EQ(1000, size);\n  ASSERT_EQ(31 * 86400, age);\n  ASSERT_TRUE(eos::mgm::LRU::extractTimeSizeCriterias(\"1mo:<1K\", age, size, errMsg));\n  ASSERT_EQ(-1000, size);\n  ASSERT_EQ(31 * 86400, age);\n}\n\nstatic inline const std::pair<std::string, eos::mgm::LRU::PolicyRules>\n    expectedPolicies[] = {\n        {\"*:1mo:>4M\", {eos::mgm::LRU::PolicyRule(\"*\", 86400 * 31, 4000000, 0)}},\n        {\"*:1d:<1G\", {eos::mgm::LRU::PolicyRule(\"*\", 86400, -1000000000, 0)}},\n        {\"*:1d:<1M,*.root:1d\",\n         {eos::mgm::LRU::PolicyRule(\"*\", 86400, -1000000, 0),\n          eos::mgm::LRU::PolicyRule(\"*.root\", 86400, 0, 0)}},\n        {\"*:1d:<1M,*.root:1d,\",\n         {eos::mgm::LRU::PolicyRule(\"*\", 86400, -1000000, 0),\n          eos::mgm::LRU::PolicyRule(\"*.root\", 86400, 0, 0)}},\n        {\"*:1d:<1M,,*.root:1d,\",\n         {eos::mgm::LRU::PolicyRule(\"*\", 86400, -1000000, 0),\n          eos::mgm::LRU::PolicyRule(\"*.root\", 86400, 0, 0)}},\n        {\"*:1d\", {eos::mgm::LRU::PolicyRule(\"*\", 86400, 0, 0)}},\n\n};\n\nstatic inline const std::string wrongPolicies[] = {\"*\", \"*:<1d\", \"azewrty\", \"\"};\n\n//------------------------------------------------------------------------------\n// Test \"sys.lru.expire.match\" policy parsing, multiple entries\n//------------------------------------------------------------------------------\nTEST(LRUTests, ParseExpireSizeMatchPolicy)\n{\n  eos::mgm::LRU::PolicyRules results;\n  std::ostringstream errMsg;\n  for (const auto& validationItem : expectedPolicies) {\n    ASSERT_TRUE(\n        eos::mgm::LRU::parseExpireSizeMatchPolicy(validationItem.first, results, errMsg));\n    size_t i = 0;\n    for (const auto& policyRule : validationItem.second) {\n      ASSERT_EQ(policyRule, results[i]);\n      i++;\n    }\n  }\n\n  for (const auto& wrongPolicy : wrongPolicies) {\n    ASSERT_FALSE(eos::mgm::LRU::parseExpireSizeMatchPolicy(wrongPolicy, results, errMsg));\n  }\n}\n"
  },
  {
    "path": "unit_tests/mgm/LockTrackerTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: LockTrackerTests.cc\n// Author: Georgios Bitzes <georgios.bitzes@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"mgm/fuse-locks/LockTracker.hh\"\n#include <fcntl.h>\n\nusing namespace eos::mgm;\n\n#define ASSERT_OVERLAP(RANGE1, RANGE2) { ASSERT_TRUE(RANGE1.overlap(RANGE2)); ASSERT_TRUE(RANGE2.overlap(RANGE1)); }\n#define ASSERT_NOT_OVERLAP(RANGE1, RANGE2) { ASSERT_FALSE(RANGE1.overlap(RANGE2)); ASSERT_FALSE(RANGE2.overlap(RANGE1)); }\n\nstd::vector<ByteRange> vec(const ByteRange &b1) { return {b1}; }\nstd::vector<ByteRange> vec(const ByteRange &b1, const ByteRange &b2) { return {b1, b2}; }\nstd::vector<ByteRange> vec() { return {}; }\n\nTEST(ByteRange, overlap) {\n  ByteRange b1(4, 3); // [4, 6]\n  ByteRange b2(5, 1); // [5, 5)\n  ASSERT_OVERLAP(b1, b2);\n\n  b1 = ByteRange(5, -1); // [5, inf)\n  b2 = ByteRange(1, 3);  // [1, 3]\n  ASSERT_NOT_OVERLAP(b1, b2);\n\n  b2 = ByteRange(1, 4);  // [1, 4]\n  ASSERT_NOT_OVERLAP(b1, b2);\n\n  b2 = ByteRange(1, 5);  // [1, 5]\n  ASSERT_OVERLAP(b1, b2);\n\n  b1 = ByteRange(10, 3); // [10, 12]\n  ASSERT_NOT_OVERLAP(b1, b2);\n\n  b2 = ByteRange(14, -1); // [14, inf)\n  ASSERT_NOT_OVERLAP(b1, b2);\n\n  b1 = ByteRange(10, 4); // [10, 13]\n  ASSERT_NOT_OVERLAP(b1, b2);\n\n  b1 = ByteRange(10, 5); // [10, 14]\n  ASSERT_OVERLAP(b1, b2);\n}\n\nTEST(ByteRange, overlapOrTouch) {\n  ByteRange b1(4, 3); // [4, 6]\n  ByteRange b2(1, 3); // [1, 3]\n\n  ASSERT_TRUE(b1.overlapOrTouch(b2));\n  ASSERT_FALSE(b1.overlap(b2));\n\n  ASSERT_TRUE(b2.overlapOrTouch(b1));\n  ASSERT_FALSE(b2.overlap(b1));\n\n  ByteRange b3(7, 2); // [7, 8]\n\n  ASSERT_TRUE(b1.overlapOrTouch(b3));\n  ASSERT_FALSE(b1.overlap(b3));\n\n  ASSERT_TRUE(b3.overlapOrTouch(b1));\n  ASSERT_FALSE(b3.overlap(b1));\n}\n\nTEST(ByteRange, absorb) {\n  ByteRange b1(4, 3); // [4, 6]\n  ByteRange b2(5, 1); // [5, 5)\n\n  ASSERT_TRUE(b2.absorb(b1));\n  ASSERT_EQ(b2.start(), 4);\n  ASSERT_EQ(b2.end(), 7);\n\n  ByteRange b3(9, -1);\n  ASSERT_FALSE(b2.absorb(b3));\n\n  b3 = ByteRange(5, -1);\n  ASSERT_TRUE(b2.absorb(b3));\n  ASSERT_EQ(b2.start(), 4);\n  ASSERT_EQ(b2.len(), -1);\n}\n\nTEST(ByteRange, absorb2) {\n  ByteRange b1(10, 5); // [10, 14]\n  ByteRange b2(15, 6); // [15, 20]\n\n  ASSERT_TRUE(b1.absorb(b2));\n  ASSERT_EQ(b1.start(), 10);\n  ASSERT_EQ(b1.end(), 21);\n}\n\nTEST(ByteRange, absorb3) {\n  ByteRange b1(10, 5); // [10, 14]\n  ByteRange b2(15, 6); // [15, 20]\n\n  ASSERT_TRUE(b2.absorb(b1));\n  ASSERT_EQ(b2.start(), 10);\n  ASSERT_EQ(b2.end(), 21);\n}\n\nTEST(ByteRange, contains) {\n  ByteRange b1(4, 3); // [4, 6]\n  ByteRange b2(4, 4); // [4, 7]\n\n  ASSERT_FALSE(b1.contains(b2));\n  ASSERT_TRUE(b2.contains(b1));\n\n  ASSERT_TRUE(b1.contains(b1));\n  ASSERT_TRUE(b2.contains(b2));\n\n  b1 = ByteRange(4, -1); // [4, inf)\n  b2 = ByteRange(3, 3);  // [3, 5]\n\n  ASSERT_TRUE(b1.contains(b1));\n  ASSERT_TRUE(b2.contains(b2));\n\n  ASSERT_FALSE(b1.contains(b2));\n  ASSERT_FALSE(b2.contains(b1));\n\n  b2 = ByteRange(3, -1); // [3, inf)\n  ASSERT_TRUE(b2.contains(b2));\n  ASSERT_FALSE(b1.contains(b2));\n  ASSERT_TRUE(b2.contains(b1));\n}\n\nTEST(ByteRange, minus_all_cases) {\n  ByteRange b1(4, 3); // [4, 6]\n  ByteRange b2(1, 2); // [1, 3]\n  ByteRange b3(5, 4); // [5, 8]\n  ByteRange b4(3, 3); // [3, 5]\n  ByteRange b5(6, 3); // [6, 8]\n  ByteRange b6(6, 4); // [6, 9]\n  ByteRange b7(6, 1); // [6, 6]\n\n  ASSERT_EQ(b1.minus(b2), vec(b1)); // b2 fully to the right\n  ASSERT_EQ(b2.minus(b1), vec(b2)); // b1 fully to the left\n\n  ASSERT_EQ(b3.minus(b4), vec(ByteRange(6, 3))); // b4 eats the start\n\n  ASSERT_EQ(b3.minus(b5), vec(ByteRange(5, 1))); // b5 eats the end\n  ASSERT_EQ(b3.minus(b6), vec(ByteRange(5, 1))); // b6 eats the end\n\n  ASSERT_EQ(b3.minus(b7), vec(ByteRange(5, 1), ByteRange(7, 2))); // b7 eats the middle\n}\n\nTEST(ByteRange, minus_case_eat_whole) {\n  ByteRange b1(100, 50); // [100, 149]\n  ByteRange b2(99, 51);  // [99, 149]\n  ByteRange b3(0, 200);  // [0, 199]\n  ByteRange b4(100, 51); // [100, 150]\n  ByteRange b5(99, 52);  // [99, 150]\n  ByteRange b6(50, 300); // [50, 349]\n\n  ASSERT_EQ(b1.minus(b2), vec());\n  ASSERT_EQ(b1.minus(b3), vec());\n  ASSERT_EQ(b1.minus(b4), vec());\n  ASSERT_EQ(b1.minus(b5), vec());\n  ASSERT_EQ(b1.minus(b6), vec());\n}\n\nTEST(ByteRange, minus_case_to_the_left) {\n  ByteRange b1(100, 50); // [100, 149]\n  ByteRange b2(50, 10);  // [50, 59]\n  ByteRange b3(90, 10);  // [90, 99]\n  ByteRange b4(0, 100);  // [0, 99]\n  ByteRange b5(99, 1);   // [99, 99]\n\n  ASSERT_EQ(b1.minus(b2), vec(b1));\n  ASSERT_EQ(b1.minus(b3), vec(b1));\n  ASSERT_EQ(b1.minus(b4), vec(b1));\n  ASSERT_EQ(b1.minus(b5), vec(b1));\n}\n\nTEST(ByteRange, minus_case_to_the_right) {\n  ByteRange b1(100, 50); // [100, 149]\n  ByteRange b2(200, 10); // [200, 209]\n  ByteRange b3(150, 10); // [150, 159]\n  ByteRange b4(150, 20); // [150, 169]\n  ByteRange b5(150, 1);  // [150, 150]\n  ByteRange b6(300, 1);  // [300, 300]\n\n  ASSERT_EQ(b1.minus(b2), vec(b1));\n  ASSERT_EQ(b1.minus(b3), vec(b1));\n  ASSERT_EQ(b1.minus(b4), vec(b1));\n  ASSERT_EQ(b1.minus(b5), vec(b1));\n  ASSERT_EQ(b1.minus(b6), vec(b1));\n}\n\nTEST(ByteRange, minus_case_eat_middle) {\n  ByteRange b1(100, 50); // [100, 149]\n  ByteRange b2(120, 10); // [120, 129]\n  ByteRange b3(101, 48); // [101, 148]\n  ByteRange b4(101, 1);  // [101, 101]\n  ByteRange b5(148, 1);  // [148, 148]\n  ByteRange b6(110, 10); // [110, 119]\n\n  ASSERT_EQ(b1.minus(b2), vec(ByteRange(100, 20), ByteRange(130, 20)));\n  ASSERT_EQ(b1.minus(b3), vec(ByteRange(100, 1), ByteRange(149, 1)));\n  ASSERT_EQ(b1.minus(b4), vec(ByteRange(100, 1), ByteRange(102, 48)));\n  ASSERT_EQ(b1.minus(b5), vec(ByteRange(100, 48), ByteRange(149, 1)));\n  ASSERT_EQ(b1.minus(b6), vec(ByteRange(100, 10), ByteRange(120, 30)));\n}\n\nTEST(ByteRange, minus_case_eat_start) {\n  ByteRange b1(100, 50); // [100, 149]\n  ByteRange b2(100, 1);  // [100, 100]\n  ByteRange b3(99, 2);   // [99, 100]\n  ByteRange b4(99, 3);   // [99, 101]\n  ByteRange b5(100, 10); // [100, 109]\n  ByteRange b6(90, 30);  // [90, 119]\n\n  ASSERT_EQ(b1.minus(b2), vec(ByteRange(101, 49)));\n  ASSERT_EQ(b1.minus(b3), vec(ByteRange(101, 49)));\n  ASSERT_EQ(b1.minus(b4), vec(ByteRange(102, 48)));\n  ASSERT_EQ(b1.minus(b5), vec(ByteRange(110, 40)));\n  ASSERT_EQ(b1.minus(b6), vec(ByteRange(120, 30)));\n}\n\nTEST(ByteRange, minus_case_eat_end) {\n  ByteRange b1(100, 50); // [100, 149]\n  ByteRange b2(149, 1);  // [149, 149]\n  ByteRange b3(149, 2);  // [149, 150]\n  ByteRange b4(148, 2);  // [148, 149]\n  ByteRange b5(148, 10); // [148, 157]\n  ByteRange b6(120, 50); // [120, 169]\n\n  ASSERT_EQ(b1.minus(b2), vec(ByteRange(100, 49)));\n  ASSERT_EQ(b1.minus(b3), vec(ByteRange(100, 49)));\n  ASSERT_EQ(b1.minus(b4), vec(ByteRange(100, 48)));\n  ASSERT_EQ(b1.minus(b5), vec(ByteRange(100, 48)));\n  ASSERT_EQ(b1.minus(b6), vec(ByteRange(100, 20)));\n}\n\nTEST(Lock, absorb) {\n  Lock l1(ByteRange(2, 2), 1);\n  Lock l2(ByteRange(3, 2), 2);\n  Lock l3(ByteRange(3, 2), 1);\n\n  ASSERT_FALSE(l1.absorb(l2)); // pids don't match\n\n  ASSERT_TRUE(l1.absorb(l3)); // pids match and there's overlap\n  ASSERT_EQ(l1.range().start(), 2);\n  ASSERT_EQ(l1.range().end(), 5);\n\n  Lock l4(ByteRange(1, 2), 3);\n  Lock l5(ByteRange(3, 2), 3);\n  ASSERT_TRUE(l4.absorb(l5));\n  ASSERT_EQ(l4, Lock(ByteRange(1, 4), 3));\n}\n\nTEST(LockSet, various) {\n  LockSet set;\n\n  set.add(Lock(ByteRange(10, 5), 1)); // [10, 14]\n  set.add(Lock(ByteRange(14, 3), 2)); // [14, 16]\n  set.add(Lock(ByteRange(15, 6), 1)); // [15, 20]\n\n  ASSERT_EQ(set.nlocks(2), 1u);\n  ASSERT_EQ(set.nlocks(1), 1u);\n\n  ASSERT_TRUE(set.overlap(Lock(ByteRange(10, 1), 1)));\n  ASSERT_TRUE(set.overlap(Lock(ByteRange(10, 4), 1)));\n  ASSERT_TRUE(set.overlap(Lock(ByteRange(10, 100), 1)));\n\n  ASSERT_TRUE(set.overlap(Lock(ByteRange(10, 100), 2)));\n  ASSERT_FALSE(set.overlap(Lock(ByteRange(10, 100), 3)));\n\n  ASSERT_TRUE(set.overlap(ByteRange(20, 1)));\n  ASSERT_FALSE(set.overlap(ByteRange(21, 1)));\n\n  ASSERT_FALSE(set.overlap(ByteRange(9, 1)));\n  ASSERT_TRUE(set.overlap(ByteRange(9, 2)));\n  ASSERT_TRUE(set.overlap(ByteRange(10, 1)));\n\n  set.remove(Lock(ByteRange(13, 3), 1)); // split range for pid \"1\" into two\n  ASSERT_EQ(set.nlocks(2), 1u);\n\n  // Now, for pid 1 we have\n  // [10, 12]\n  // [16, 20]\n\n  ASSERT_TRUE(set.overlap(Lock(ByteRange(11, 2), 1)));\n  ASSERT_TRUE(set.overlap(Lock(ByteRange(12, 2), 1)));\n  ASSERT_FALSE(set.overlap(Lock(ByteRange(13, 2), 1)));\n  ASSERT_FALSE(set.overlap(Lock(ByteRange(14, 2), 1)));\n  ASSERT_TRUE(set.overlap(Lock(ByteRange(15, 2), 1)));\n  ASSERT_TRUE(set.overlap(Lock(ByteRange(20, 1), 1)));\n  ASSERT_FALSE(set.overlap(Lock(ByteRange(21, 1), 1)));\n  ASSERT_TRUE(set.overlap(Lock(ByteRange(19, 3), 1)));\n\n  ASSERT_TRUE(set.conflict(Lock(ByteRange(19, 3), 2)));\n  ASSERT_FALSE(set.conflict(Lock(ByteRange(19, 3), 1)));\n}\n\nTEST(LockTracker, various) {\n  LockTracker tracker;\n\n  // Write lock [1, 100] by PID 1\n  struct flock lock;\n  lock.l_start = 1;\n  lock.l_len = 100;\n  lock.l_type = F_WRLCK;\n\n  ASSERT_TRUE(tracker.setlk(2, &lock, 0, \"owner\"));\n  ASSERT_FALSE(tracker.setlk(3, &lock, 0, \"owner\"));\n  ASSERT_TRUE(tracker.setlk(2, &lock, 0, \"owner\")); // lock again by same pid, should be no-op\n\n  // Release [5, 10]\n  lock.l_start = 5;\n  lock.l_len = 6;\n  lock.l_type = F_UNLCK;\n\n  ASSERT_TRUE(tracker.setlk(2, &lock, 0, \"owner\"));\n\n  // Lock [5, 10], by pid 3\n  lock.l_type = F_WRLCK;\n  ASSERT_TRUE(tracker.setlk(3, &lock, 0, \"owner\"));\n  ASSERT_FALSE(tracker.setlk(2, &lock, 0, \"owner\")); // pid 2 should not be able to reclaim it\n\n  // Convert [5, 6] into read lock\n  lock.l_start = 5;\n  lock.l_len = 2;\n  lock.l_type = F_RDLCK;\n  ASSERT_TRUE(tracker.setlk(3, &lock, 0, \"owner\"));\n\n  // Add read lock from a different process\n  ASSERT_TRUE(tracker.setlk(4, &lock, 0, \"owner\"));\n\n  // Make sure no write locks are allowed\n  lock.l_type = F_WRLCK;\n  ASSERT_FALSE(tracker.setlk(5, &lock, 0, \"owner\"));\n\n  // Even if coming from a process which has a read lock already, in case there\n  // are other readers\n  ASSERT_FALSE(tracker.setlk(4, &lock, 0, \"owner\"));\n\n  // Remove read lock from pid 3\n  lock.l_type = F_UNLCK;\n  ASSERT_TRUE(tracker.setlk(3, &lock, 0, \"owner\"));\n\n  // Now it should be possible to convert it into a write lock, since pid 4 is\n  // the only reader\n  lock.l_type = F_WRLCK;\n  ASSERT_TRUE(tracker.setlk(4, &lock, 0, \"owner\"));\n}\n"
  },
  {
    "path": "unit_tests/mgm/PolicyTests.cc",
    "content": "#include \"mgm/policy/Policy.hh\"\n#include \"mgm/misc/Constants.hh\"\n#include \"gtest/gtest.h\"\n\nusing namespace eos::mgm;\n\nTEST(Policy, GetConfigKeys)\n{\n  ASSERT_EQ(Policy::gBasePolicyKeys, Policy::GetConfigKeys());\n}\n\nTEST(Policy, RWParams)\n{\n  Policy::RWParams params(\"user1\", \"group1\", \"app1\", true);\n  ASSERT_EQ(params.user_key, \".user:user1\");\n  ASSERT_EQ(params.group_key, \".group:group1\");\n  ASSERT_EQ(params.app_key, \".app:app1\");\n  ASSERT_EQ(params.rw_marker, \":w\");\n}\n\nTEST(Policy, RWParams_rw_marker)\n{\n  Policy::RWParams params(\"\", \"\", \"\", true);\n  ASSERT_EQ(params.rw_marker, \":w\");\n  Policy::RWParams params2(\"\", \"\", \"\", false);\n  ASSERT_EQ(params2.rw_marker, \":r\");\n}\n\nTEST(Policy, RWParams_get_key)\n{\n  Policy::RWParams params(\"\", \"\", \"\", true);\n  ASSERT_EQ(params.getKey(\"test\"), \"test:w\");\n  Policy::RWParams params4(\"\", \"\", \"\", false);\n  ASSERT_EQ(params4.getKey(\"test\"), \"test:r\");\n}\n\nTEST(Policy, GetRWConfigKey)\n{\n  Policy::RWParams params(\"user1\", \"group1\", \"eoscp\", false);\n  std::vector<std::string> expected {\n    \"policy:bandwidth:r.app:eoscp\",\n    \"policy:bandwidth:r.user:user1\",\n    \"policy:bandwidth:r.group:group1\",\n    \"policy:bandwidth:r\"};\n  ASSERT_EQ(params.getKeys(\"policy:bandwidth\"),\n            expected);\n}\n"
  },
  {
    "path": "unit_tests/mgm/ProcFsTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: ProcFsTest.cc\n// Author: Elvin-Alin Sindrilaru <esindril at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"mgm/proc/proc_fs.hh\"\n\n//------------------------------------------------------------------------------\n// Test entity classification\n//------------------------------------------------------------------------------\nTEST(ProcFs, EntityClassification)\n{\n  using namespace eos::mgm;\n  XrdOucString out, err;\n  ASSERT_TRUE(EntityType::FS == get_entity_type(\"1234\", out, err));\n  ASSERT_TRUE(EntityType::GROUP == get_entity_type(\"default.3\", out, err));\n  ASSERT_TRUE(EntityType::SPACE == get_entity_type(\"default\", out, err));\n  ASSERT_TRUE(EntityType::UNKNOWN == get_entity_type(\"2.default\", out, err));\n  ASSERT_TRUE(EntityType::UNKNOWN == get_entity_type(\"spare.default\", out, err));\n  ASSERT_TRUE(EntityType::UNKNOWN == get_entity_type(\"spare.4default\", out, err));\n}\n\n//------------------------------------------------------------------------------\n// Test fs mv operation classification\n//------------------------------------------------------------------------------\nTEST(ProcFs, MvOpClassification)\n{\n  using namespace eos::mgm;\n  XrdOucString out, err;\n  ASSERT_TRUE(MvOpType::FS_2_GROUP == get_operation_type(\"1234\", \"default.23\",\n              out, err));\n  ASSERT_TRUE(MvOpType::FS_2_SPACE == get_operation_type(\"3214\", \"default\", out,\n              err));\n  ASSERT_TRUE(MvOpType::GRP_2_SPACE == get_operation_type(\"meyrin.65\", \"default\",\n              out, err));\n  ASSERT_TRUE(MvOpType::SPC_2_SPACE == get_operation_type(\"meyrin\", \"default\",\n              out, err));\n  ASSERT_TRUE(MvOpType::UNKNOWN == get_operation_type(\"meyrin.65\", \"default.12\",\n              out, err));\n  ASSERT_TRUE(MvOpType::UNKNOWN == get_operation_type(\"meyrin\", \"default.78\", out,\n              err));\n  ASSERT_TRUE(MvOpType::UNKNOWN == get_operation_type(\"meyrin.53\", \"78\", out,\n              err));\n  ASSERT_TRUE(MvOpType::UNKNOWN == get_operation_type(\"meyrin\", \"8176\", out,\n              err));\n}\n"
  },
  {
    "path": "unit_tests/mgm/QuarkDBConfigTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: QuarkDBConfigTests.cc\n// Author: Elvin-Alin Sindrilaru <esindril at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#define IN_TEST_HARNESS\n#include \"mgm/config/QuarkDBConfigEngine.hh\"\n#undef IN_TEST_HARNESS\n\nusing namespace eos::mgm;\n\nTEST(QuarkDBConfig, BasicTests)\n{\n  QuarkDBConfigEngine cfg;\n  setenv(\"EOS_MGM_CONFIG_CLEANUP\", \"1\", 1);\n  cfg.sConfigDefinitions = {};\n  ASSERT_FALSE(cfg.RemoveUnusedNodes());\n  cfg.sConfigDefinitions = {\n    {\"global:/config/eos/space/default#atime\", \"604800\"},\n    {\"global:/config/eos/space/default#autorepair\", \"off\"},\n    {\"global:/config/eos/space/default#balancer\", \"on\"}\n  };\n  ASSERT_FALSE(cfg.RemoveUnusedNodes());\n  // A node with status of is not removed!\n  cfg.sConfigDefinitions =  {\n    {\"global:/config/eos/space/default#atime\", \"604800\"},\n    {\"global:/config/eos/space/default#autorepair\", \"off\"},\n    {\"global:/config/eos/space/default#balancer\", \"on\"},\n    {\"global:/config/eos/node/st-096-100gb-ip315-0f706.cern.ch:1095#stat.hostport\", \"st-096-100gb-ip315-0f706.cern.ch:1095\"},\n    {\"global:/config/eos/node/st-096-100gb-ip315-0f706.cern.ch:1095#status\", \"on\"}\n  };\n  ASSERT_FALSE(cfg.RemoveUnusedNodes());\n  // A node with status of and no file systems should be removed\n  cfg.sConfigDefinitions[\"global:/config/eos/node/st-096-100gb-ip315-0f706.cern.ch:1095#status\"]\n    = \"off\";\n  ASSERT_TRUE(cfg.RemoveUnusedNodes());\n  // A node with file systems and status off or on should not be removed\n  cfg.sConfigDefinitions =  {\n    {\"global:/config/eos/space/default#atime\", \"604800\"},\n    {\"global:/config/eos/space/default#autorepair\", \"off\"},\n    {\"global:/config/eos/space/default#balancer\", \"on\"},\n    {\"global:/config/eos/node/st-096-100gb-ip315-0f706.cern.ch:1095#stat.hostport\", \"st-096-100gb-ip315-0f706.cern.ch:1095\"},\n    {\"global:/config/eos/node/st-096-100gb-ip315-0f706.cern.ch:1095#status\", \"on\"},\n    {\"fs:/eos/st-096-100gb-ip315-0f706.cern.ch:1095/fst/data95\", \"bootcheck=0 configstatus=rw\"},\n    {\"fs:/eos/st-096-100gb-ip315-0f706.cern.ch:1095/fst/data96\", \"bootcheck=0 configstatus=rw\"},\n  };\n  ASSERT_FALSE(cfg.RemoveUnusedNodes());\n  cfg.sConfigDefinitions[\"global:/config/eos/node/st-096-100gb-ip315-0f706.cern.ch:1095#status\"]\n    = \"off\";\n  ASSERT_FALSE(cfg.RemoveUnusedNodes());\n  // Add some new entries in the configuration map\n  cfg.sConfigDefinitions[\"global:/config/eos/space/default#lru\"] = \"off\";\n  cfg.sConfigDefinitions[\"global:/config/eos/space/default#lru.interval\"] =\n    \"14400\";\n  ASSERT_FALSE(cfg.RemoveUnusedNodes());\n  unsetenv(\"EOS_MGM_CONFIG_CLEANUP\");\n}\n"
  },
  {
    "path": "unit_tests/mgm/RecyclePolicyTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: RecyclePolicyTests.cc\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gmock/gmock.h\"\n#include \"gtest/gtest.h\"\n#define IN_TEST_HARNESS\n#include \"mgm/recycle/RecyclePolicy.hh\"\n#undef IN_TEST_HARNESS\n#include \"mgm/quota/Quota.hh\"\n#include <map>\n\nusing ::testing::Return;\nusing ::testing::NiceMock;\n\n//------------------------------------------------------------------------------\n// MockRecyclePolicy\n//------------------------------------------------------------------------------\nclass MockRecyclePolicy: public eos::mgm::RecyclePolicy\n{\npublic:\n  MOCK_METHOD((std::map<int, unsigned long long>), GetQuotaStats, (), (override));\n  MOCK_METHOD(bool, StoreConfig, (), (override));\n};\n\n//------------------------------------------------------------------------------\n// Recycle policy with no configuration\n//------------------------------------------------------------------------------\nTEST(RecyclePolicyTests, NoLimits)\n{\n  NiceMock<MockRecyclePolicy> mock_policy;\n  ASSERT_FALSE(mock_policy.mEnforced.load());\n  EXPECT_CALL(mock_policy, GetQuotaStats)\n  .WillOnce(Return(std::map<int, unsigned long long>()));\n  mock_policy.RefreshWatermarks();\n  ASSERT_EQ(mock_policy.mLowInodeWatermark.load(), 0ull);\n  ASSERT_EQ(mock_policy.mLowSpaceWatermark.load(),  0ull);\n  // There are no space limits yet so clean up should be performed\n  ASSERT_FALSE(mock_policy.IsWithinLimits());\n}\n\n//------------------------------------------------------------------------------\n// Recycle policy above the watermark limits\n//------------------------------------------------------------------------------\nTEST(RecyclePolicyTests, AboveWatermark)\n{\n  // Update the space keep ratio and the quota information\n  // so that the limits are overrun\n  NiceMock<MockRecyclePolicy> mock_policy;\n  mock_policy.mSpaceKeepRatio = 0.4;\n  EXPECT_CALL(mock_policy, GetQuotaStats)\n  .Times(2)\n  .WillRepeatedly(Return(std::map<int, unsigned long long> {\n    {eos::mgm::SpaceQuota::kGroupLogicalBytesIs, 5000},\n    {eos::mgm::SpaceQuota::kGroupLogicalBytesTarget, 10000},\n    {eos::mgm::SpaceQuota::kGroupFilesIs, 100},\n    {eos::mgm::SpaceQuota::kGroupFilesTarget, 200}\n  }));\n  mock_policy.RefreshWatermarks();\n  ASSERT_DOUBLE_EQ((mock_policy.mSpaceKeepRatio.load() - 0.1) * 10000,\n                   mock_policy.mLowSpaceWatermark.load());\n  ASSERT_DOUBLE_EQ((mock_policy.mSpaceKeepRatio.load() - 0.1) * 200,\n                   mock_policy.mLowInodeWatermark.load());\n  ASSERT_FALSE(mock_policy.IsWithinLimits());\n}\n\n//------------------------------------------------------------------------------\n// Recycle policy below the watermark limits\n//------------------------------------------------------------------------------\nTEST(RecyclePolicyTests, BelowWatermark)\n{\n  // Update the quota information so that we are within the limits\n  NiceMock<MockRecyclePolicy> mock_policy;\n  mock_policy.mSpaceKeepRatio = 0.4;\n  EXPECT_CALL(mock_policy, GetQuotaStats)\n  .Times(2)\n  .WillRepeatedly(Return(std::map<int, unsigned long long> {\n    {eos::mgm::SpaceQuota::kGroupLogicalBytesIs, 5000},\n    {eos::mgm::SpaceQuota::kGroupLogicalBytesTarget, 10000},\n    {eos::mgm::SpaceQuota::kGroupFilesIs, 100},\n    {eos::mgm::SpaceQuota::kGroupFilesTarget, 200}\n  }));\n  mock_policy.RefreshWatermarks();\n  ASSERT_DOUBLE_EQ((mock_policy.mSpaceKeepRatio.load() - 0.1) * 10000,\n                   mock_policy.mLowSpaceWatermark.load());\n  ASSERT_DOUBLE_EQ((mock_policy.mSpaceKeepRatio.load() - 0.1) * 200,\n                   mock_policy.mLowInodeWatermark.load());\n  ASSERT_FALSE(mock_policy.IsWithinLimits());\n  // Update the quota information so that we are back withing the limits\n  EXPECT_CALL(mock_policy, GetQuotaStats)\n  .WillOnce(Return(std::map<int, unsigned long long> {\n    {eos::mgm::SpaceQuota::kGroupLogicalBytesIs, 3000},\n    {eos::mgm::SpaceQuota::kGroupLogicalBytesTarget, 10000},\n    {eos::mgm::SpaceQuota::kGroupFilesIs, 50},\n    {eos::mgm::SpaceQuota::kGroupFilesTarget, 200}\n  }));\n  ASSERT_TRUE(mock_policy.IsWithinLimits());\n}\n\n//------------------------------------------------------------------------------\n// Recycle policy configuration tests\n//------------------------------------------------------------------------------\nTEST(RecyclePolicyTests, ConfigTest)\n{\n  NiceMock<MockRecyclePolicy> policy;\n  EXPECT_CALL(policy, StoreConfig).WillRepeatedly(Return(true));\n  std::string msg;\n  // Test valid configurations\n  ASSERT_TRUE(policy.Config(eos::mgm::RecyclePolicy::sEnforceKey, \"on\", msg));\n  ASSERT_TRUE(policy.mEnforced.load());\n  ASSERT_TRUE(policy.Config(eos::mgm::RecyclePolicy::sEnforceKey, \"off\", msg));\n  ASSERT_FALSE(policy.mEnforced.load());\n  ASSERT_TRUE(policy.Config(eos::mgm::RecyclePolicy::sEnforceKey, \"on\", msg));\n  ASSERT_TRUE(policy.Config(eos::mgm::RecyclePolicy::sKeepTimeKey, \"3600\", msg));\n  ASSERT_EQ(policy.mKeepTimeSec.load(), 3600ull);\n  ASSERT_TRUE(policy.mEnforced.load());\n  ASSERT_TRUE(policy.Config(eos::mgm::RecyclePolicy::sRatioKey, \"0.5\", msg));\n  ASSERT_DOUBLE_EQ(policy.mSpaceKeepRatio.load(), 0.5);\n  ASSERT_TRUE(policy.mEnforced.load());\n  ASSERT_TRUE(policy.Config(eos::mgm::RecyclePolicy::sCollectKey, \"300\", msg));\n  ASSERT_EQ(policy.mCollectInterval.load().count(), 300);\n  ASSERT_TRUE(policy.Config(eos::mgm::RecyclePolicy::sRemoveKey, \"60\", msg));\n  ASSERT_EQ(policy.mRemoveInterval.load().count(), 60);\n  ASSERT_TRUE(policy.Config(eos::mgm::RecyclePolicy::sDryRunKey, \"yes\", msg));\n  ASSERT_TRUE(policy.mDryRun.load());\n  ASSERT_TRUE(policy.Config(eos::mgm::RecyclePolicy::sDryRunKey, \"no\", msg));\n  ASSERT_FALSE(policy.mDryRun.load());\n  // Verify that turning it off disables enforcement even with other configs set\n  ASSERT_TRUE(policy.Config(eos::mgm::RecyclePolicy::sEnforceKey, \"off\", msg));\n  ASSERT_FALSE(policy.mEnforced.load());\n  // Test invalid configurations\n  ASSERT_FALSE(policy.Config(eos::mgm::RecyclePolicy::sKeepTimeKey, \"invalid\",\n                             msg));\n  ASSERT_FALSE(policy.Config(eos::mgm::RecyclePolicy::sRatioKey, \"invalid\", msg));\n  ASSERT_FALSE(policy.Config(eos::mgm::RecyclePolicy::sCollectKey, \"invalid\",\n                             msg));\n  ASSERT_FALSE(policy.Config(eos::mgm::RecyclePolicy::sRemoveKey, \"invalid\",\n                             msg));\n  // Test reset/unenforce\n  ASSERT_TRUE(policy.Config(eos::mgm::RecyclePolicy::sEnforceKey, \"off\", msg));\n  ASSERT_FALSE(policy.mEnforced.load());\n  ASSERT_TRUE(policy.Config(eos::mgm::RecyclePolicy::sKeepTimeKey, \"0\", msg));\n  ASSERT_TRUE(policy.Config(eos::mgm::RecyclePolicy::sRatioKey, \"0.0\", msg));\n  // mEnforced should still be false because we explicitly set it off\n  ASSERT_FALSE(policy.mEnforced.load());\n}\n\n"
  },
  {
    "path": "unit_tests/mgm/RecycleTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: RecycleTests.cc\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2025 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gmock/gmock.h\"\n#include \"gtest/gtest.h\"\n#define IN_TEST_HARNESS\n#include \"mgm/recycle/Recycle.hh\"\n#undef IN_TEST_HARNESS\n\nusing ::testing::Return;\nusing ::testing::NiceMock;\nusing namespace eos::mgm;\n\nTEST(Recycle, ComputeCutOffDate)\n{\n  Recycle recycle(true);\n  auto& clock = recycle.mClock;\n  // Set the clock to Tue Sep 30 03:46:40 PM CEST 2025\n  clock.advance(std::chrono::seconds(1759240000));\n  recycle.mPolicy.mKeepTimeSec = 6 * 31 * 24 * 3600; // 6 months\n  ASSERT_STREQ(\"2025/03/27\", recycle.GetCutOffDate().c_str());\n  recycle.mPolicy.mKeepTimeSec =  31 * 24 * 3600; // 1 month\n  ASSERT_STREQ(\"2025/08/29\", recycle.GetCutOffDate().c_str());\n  recycle.mPolicy.mKeepTimeSec =  7 * 24 * 3600; // 1 week\n  ASSERT_STREQ(\"2025/09/22\", recycle.GetCutOffDate().c_str());\n}\n\nTEST(Recycle, DemangleTest)\n{\n  // Recycle path should never contain '/'\n  ASSERT_STREQ(\"\", Recycle::DemanglePath(\"/some/real/path/\").c_str());\n  ASSERT_STREQ(\"\", Recycle::DemanglePath(\"\").c_str());\n  ASSERT_STREQ(\"/eos/top/dir/path\",\n               Recycle::DemanglePath(\"#:#eos#:#top#:#dir#:#path.000000000000000a\").c_str());\n  ASSERT_STREQ(\"/eos/top/with_funny_chars!#?/file\",\n               Recycle::DemanglePath(\"#:#eos#:#top#:#with_funny_chars!#?#:#file.000000000000000b\").c_str());\n}\n"
  },
  {
    "path": "unit_tests/mgm/RoutingTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: RoutingTests.cc\n// Author: Elvin-Alin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include \"mgm/routeendpoint/RouteEndpoint.hh\"\n#include \"mgm/pathrouting/PathRouting.hh\"\n\n//------------------------------------------------------------------------------\n// Test basic RouteEndpoint construction and parsing\n//------------------------------------------------------------------------------\nTEST(Routing, Construction)\n{\n  using eos::mgm::RouteEndpoint;\n  using eos::mgm::PathRouting;\n  eos::mgm::PathRouting route(std::chrono::seconds(0));\n  std::vector<std::string> inputs {\n    \"eos-dummy1.cern.ch:1094:8000\",\n    \"eos-dummy2.cern.ch:2094:9000\",\n    \"eos-dummy3.cern.ch:3094:1000\",\n    \"eos-dummy4.cern.ch:4094:11000\"};\n  {\n    // Check parsing and equality operators\n    RouteEndpoint endpoint1, endpoint2;\n    ASSERT_FALSE(endpoint1.ParseFromString(\"wrong.cern.ch\"));\n    ASSERT_FALSE(endpoint1.ParseFromString(\"wrong.cern.ch:94\"));\n    ASSERT_FALSE(endpoint1.ParseFromString(\"wrong.cern.ch:94:number\"));\n    ASSERT_FALSE(endpoint1.ParseFromString(\"wrong.cern.ch:number:number\"));\n    ASSERT_FALSE(endpoint1.ParseFromString(\"*hostwrong.cern.ch:1094:8000\"));\n    ASSERT_TRUE(endpoint1.ParseFromString(inputs[0]));\n    ASSERT_TRUE(endpoint2.ParseFromString(inputs[1]));\n    ASSERT_NE(endpoint1, endpoint2);\n    ASSERT_TRUE(endpoint2.ParseFromString(inputs[0]));\n    ASSERT_EQ(endpoint1, endpoint2);\n  }\n\n  for (const auto& input : inputs) {\n    RouteEndpoint endpoint;\n    ASSERT_TRUE(endpoint.ParseFromString(input));\n    ASSERT_TRUE(route.Add(\"/eos/\", std::move(endpoint)));\n    RouteEndpoint endpoint1;\n    ASSERT_TRUE(endpoint1.ParseFromString(input));\n    ASSERT_FALSE(route.Add(\"/eos/\", std::move(endpoint1)));\n  }\n\n  ASSERT_TRUE(route.Remove(\"/eos/\"));\n  ASSERT_FALSE(route.Remove(\"/eos/unknown/dir/\"));\n  route.Clear();\n}\n\n//------------------------------------------------------------------------------\n// Test routing functionality\n//------------------------------------------------------------------------------\nTEST(Routing, Functionality)\n{\n  using eos::mgm::RouteEndpoint;\n  using eos::mgm::PathRouting;\n  // Routing without async updates\n  eos::mgm::PathRouting route(std::chrono::seconds(0));\n  std::vector<std::string> inputs {\n    \"eos-dummy1.cern.ch:1094:8000\",\n    \"eos-dummy2.cern.ch:2094:9000\",\n    \"eos-dummy3.cern.ch:3094:10000\",\n    \"eos-dummy4.cern.ch:4094:11000\"};\n  int count = 0;\n\n  // Add several routes to test out the routing\n  for (const auto& input : inputs) {\n    RouteEndpoint endpoint;\n    endpoint.mIsOnline.store(true);\n    ASSERT_TRUE(endpoint.ParseFromString(input));\n    ASSERT_TRUE(route.Add(\"/eos/dir\" + std::to_string(++count) + \"/\",\n                          std::move(endpoint)));\n  }\n\n  eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n  std::string stat_info;\n  std::string host;\n  int port;\n  ASSERT_TRUE(PathRouting::Status::NOROUTING ==\n              route.Reroute(\"\", nullptr, vid, host, port, stat_info));\n  ASSERT_TRUE(PathRouting::Status::NOROUTING ==\n              route.Reroute(\"/\", nullptr, vid, host, port, stat_info));\n  ASSERT_TRUE(PathRouting::Status::NOROUTING ==\n              route.Reroute(\"/unknown\", nullptr, vid, host, port, stat_info));\n  ASSERT_TRUE(PathRouting::Status::NOROUTING ==\n              route.Reroute(\"/eos/\", nullptr, vid, host, port, stat_info));\n  ASSERT_TRUE(PathRouting::Status::NOROUTING ==\n              route.Reroute(\"/\", \"&mgm.fsid=3452&mgm.fid=0e98cc49&mgm.localprefix=/data13\",\n                            vid, host, port, stat_info));\n  // Test http/https redirection\n  vid.prot = \"http\";\n  ASSERT_TRUE(PathRouting::Status::REROUTE ==\n              route.Reroute(\"/eos/dir1/\", nullptr, vid, host, port, stat_info));\n  ASSERT_TRUE(PathRouting::Status::REROUTE ==\n              route.Reroute(\"/eos/dir1\", nullptr, vid, host, port, stat_info));\n  ASSERT_TRUE(host == \"eos-dummy1.cern.ch\");\n  ASSERT_TRUE(port == 8000);\n  vid.prot = \"https\";\n  ASSERT_TRUE(PathRouting::Status::REROUTE ==\n              route.Reroute(\"/eos/dir1\", nullptr, vid, host, port, stat_info));\n  ASSERT_TRUE(host == \"eos-dummy1.cern.ch\");\n  ASSERT_TRUE(port == 8000);\n  // Test xrd redirection\n  vid.prot = \"\";\n  ASSERT_TRUE(PathRouting::Status::REROUTE ==\n              route.Reroute(\"/eos/dir2\", nullptr, vid, host, port, stat_info));\n  ASSERT_TRUE(host == \"eos-dummy2.cern.ch\");\n  ASSERT_TRUE(port == 2094);\n  // Test redirection given a longer path\n  ASSERT_TRUE(PathRouting::Status::REROUTE ==\n              route.Reroute(\"/eos/dir3/subdir1/subdir2\", nullptr, vid, host,\n                            port, stat_info));\n  ASSERT_TRUE(host == \"eos-dummy3.cern.ch\");\n  ASSERT_TRUE(port == 3094);\n\n  // Put all endpoints as offline and not master to trigger stall response\n  for (const auto& input : inputs) {\n    RouteEndpoint endpoint;\n    endpoint.mIsOnline.store(false);\n    endpoint.mIsMaster.store(false);\n    ASSERT_TRUE(endpoint.ParseFromString(input));\n    ASSERT_TRUE(route.Add(\"/eos/dir/multi_ep/\", std::move(endpoint)));\n  }\n\n  ASSERT_TRUE(PathRouting::Status::STALL ==\n              route.Reroute(\"/eos/dir/multi_ep/\", nullptr, vid, host, port,\n                            stat_info));\n  // Add online endpoint to trigger rerouting\n  RouteEndpoint endpoint;\n  endpoint.mIsOnline.store(true);\n  endpoint.mIsMaster.store(true);\n  ASSERT_TRUE(endpoint.ParseFromString(\"eos-dummy5.cern.ch:5094:12000\"));\n  ASSERT_TRUE(route.Add(\"/eos/dir/multi_ep/\", std::move(endpoint)));\n  ASSERT_TRUE(PathRouting::Status::REROUTE ==\n              route.Reroute(\"/eos/dir/multi_ep/\", nullptr, vid, host, port,\n                            stat_info));\n  ASSERT_STREQ(\"eos-dummy5.cern.ch\", host.c_str());\n  ASSERT_EQ(5094, port);\n  // Assert empty routing\n  route.Clear();\n  ASSERT_TRUE(PathRouting::Status::NOROUTING ==\n              route.Reroute(\"/eos/dir1\", nullptr, vid, host, port, stat_info));\n  std::string listing = \"not-empty\";\n  route.GetListing(\"\", listing);\n  ASSERT_STREQ(\"\", listing.c_str());\n}\n\n//------------------------------------------------------------------------------\n// Test routing functionality\n//------------------------------------------------------------------------------\nTEST(Routing, SpecialPaths)\n{\n  using eos::mgm::RouteEndpoint;\n  using eos::mgm::PathRouting;\n  eos::mgm::PathRouting route(std::chrono::seconds(0));\n  eos::common::VirtualIdentity vid = eos::common::VirtualIdentity::Root();\n  std::string stat_info;\n  std::string host;\n  int port;\n  RouteEndpoint endpoint1;\n  endpoint1.mIsOnline.store(true);\n  ASSERT_TRUE(endpoint1.ParseFromString(\"eos-instance.cern.ch:1094:8000\"));\n  ASSERT_TRUE(route.Add(\"/eos/instance/\", std::move(endpoint1)));\n  RouteEndpoint endpoint2;\n  endpoint2.mIsOnline.store(true);\n  ASSERT_TRUE(endpoint2.ParseFromString(\"eos-specific.cern.ch:1094:8000\"));\n  ASSERT_TRUE(route.Add(\"/eos/instance/a/atest/\", std::move(endpoint2)));\n  ASSERT_TRUE(PathRouting::Status::REROUTE ==\n              route.Reroute(\"/eos/instance/a/atest/.\", nullptr, vid, host, port,\n                            stat_info));\n  ASSERT_STREQ(\"eos-specific.cern.ch\", host.c_str());\n  ASSERT_TRUE(PathRouting::Status::REROUTE ==\n              route.Reroute(\"/eos/instance/a/atest/subdir/.\", nullptr, vid, host, port,\n                            stat_info));\n  ASSERT_STREQ(\"eos-specific.cern.ch\", host.c_str());\n  ASSERT_TRUE(PathRouting::Status::REROUTE ==\n              route.Reroute(\"/eos/instance/a/./atest/\", nullptr, vid, host, port,\n                            stat_info));\n  ASSERT_STREQ(\"eos-specific.cern.ch\", host.c_str());\n  ASSERT_TRUE(PathRouting::Status::REROUTE ==\n              route.Reroute(\"/eos/instance/a/atest/subdir/..\", nullptr, vid, host, port,\n                            stat_info));\n  ASSERT_STREQ(\"eos-specific.cern.ch\", host.c_str());\n  ASSERT_TRUE(PathRouting::Status::REROUTE ==\n              route.Reroute(\"/eos/instance/a/atest/..\", nullptr, vid, host, port,\n                            stat_info));\n  ASSERT_STREQ(\"eos-instance.cern.ch\", host.c_str());\n  ASSERT_TRUE(PathRouting::Status::REROUTE ==\n              route.Reroute(\"/eos/instance/a/../atest/..\", nullptr, vid, host, port,\n                            stat_info));\n  ASSERT_STREQ(\"eos-instance.cern.ch\", host.c_str());\n  ASSERT_TRUE(PathRouting::Status::NOROUTING ==\n              route.Reroute(\"/eos/instance/../a/atest/\", nullptr, vid, host, port,\n                            stat_info));\n  route.Clear();\n}\n"
  },
  {
    "path": "unit_tests/mgm/XrdMgmOfsFileTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: XrdMgmFileOfsTests.cc\n// Author: Elvin Sindrilaru - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"namespace/interface/IFileMD.hh\"\n#include <XrdSec/XrdSecEntity.hh>\n#include <XrdSec/XrdSecEntityAttr.hh>\n#define IN_TEST_HARNESS\n#include \"mgm/ofs/XrdMgmOfsFile.hh\"\n#undef IN_TEST_HARNESS\n#include \"gtest/gtest.h\"\n\nusing namespace eos::mgm;\n\nTEST(XrdMgmOfsFile, ParsingExcludFsids)\n{\n  XrdMgmOfsFile f;\n  std::string opaque_info =\n    \"eos.excludefsid=2,4,6,8,10,144&eos.ruid=0&eos.rgid=0\";\n  f.openOpaque = new XrdOucEnv(opaque_info.c_str());\n  std::vector<unsigned int> expect_result {2, 4, 6, 8, 10, 144};\n  auto result = f.GetExcludedFsids();\n  ASSERT_EQ(result.size(), expect_result.size());\n\n  for (const auto& elem : expect_result) {\n    ASSERT_TRUE(std::find(result.begin(), result.end(), elem) != result.end());\n  }\n}\n\nTEST(XrdMgmOfsFile, GetClientApplicationName)\n{\n  ASSERT_STREQ(\"\", XrdMgmOfsFile::GetClientApplicationName(nullptr, nullptr).c_str());\n  std::string opaque_str = \"&key1=val1&key2=val2&key3=val3\";\n  XrdOucEnv env(opaque_str.c_str());\n  XrdSecEntity client(\"test\");\n  ASSERT_STREQ(\"\",\n               XrdMgmOfsFile::GetClientApplicationName(&env, &client).c_str());\n  client.eaAPI->Add(\"xrd.appname\", \"xrd_tag\");\n  ASSERT_STREQ(\"xrd_tag\",\n               XrdMgmOfsFile::GetClientApplicationName(&env, &client).c_str());\n  opaque_str = \"&key1=val1&key2=val2&key3=val3&eos.app=eos_tag\";\n  XrdOucEnv env1(opaque_str.c_str());\n  ASSERT_STREQ(\"eos_tag\",\n               XrdMgmOfsFile::GetClientApplicationName(&env1, &client).c_str());\n  ASSERT_STREQ(\"eos_tag\",\n               XrdMgmOfsFile::GetClientApplicationName(&env1, nullptr).c_str());\n}\n"
  },
  {
    "path": "unit_tests/mgm/XrdMgmOfsTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: XrdMgmOfsTests.cc\n// Author: Steven Murray <smurray at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n\n#include <gtest/gtest.h>\n#include <XrdVersion.hh>\n\nclass XrdMgmOfsTest : public ::testing::Test {\nprotected:\n\n  virtual void SetUp() {\n  }\n\n  virtual void TearDown() {\n  }\n};\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(XrdMgmOfsTest, prepareOptsToString)\n{\n  using namespace eos::mgm;\n\n  {\n    const int opts = Prep_PRTY0;\n    ASSERT_EQ(\"PRTY0\", XrdMgmOfs::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_PRTY1;\n    ASSERT_EQ(\"PRTY1\", XrdMgmOfs::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_PRTY2;\n    ASSERT_EQ(\"PRTY2\", XrdMgmOfs::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_PRTY3;\n    ASSERT_EQ(\"PRTY3\", XrdMgmOfs::prepareOptsToString(opts));\n  }\n\n  {\n    const int opts = Prep_SENDAOK;\n    ASSERT_EQ(\"PRTY0,SENDAOK\", XrdMgmOfs::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_SENDERR;\n    ASSERT_EQ(\"PRTY0,SENDERR\", XrdMgmOfs::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_SENDACK;\n    ASSERT_EQ(\"PRTY0,SENDACK\", XrdMgmOfs::prepareOptsToString(opts));\n  }\n\n  {\n    const int opts = Prep_WMODE;\n    ASSERT_EQ(\"PRTY0,WMODE\", XrdMgmOfs::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_STAGE;\n    ASSERT_EQ(\"PRTY0,STAGE\", XrdMgmOfs::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_COLOC;\n    ASSERT_EQ(\"PRTY0,COLOC\", XrdMgmOfs::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_FRESH;\n    ASSERT_EQ(\"PRTY0,FRESH\", XrdMgmOfs::prepareOptsToString(opts));\n  }\n#if (XrdMajorVNUM(XrdVNUMBER) == 4 && XrdMinorVNUM(XrdVNUMBER) >= 10) || XrdMajorVNUM(XrdVNUMBER) >= 5\n  {\n    const int opts = Prep_CANCEL;\n    ASSERT_EQ(\"PRTY0,CANCEL\", XrdMgmOfs::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_QUERY;\n    ASSERT_EQ(\"PRTY0,QUERY\", XrdMgmOfs::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_EVICT;\n    ASSERT_EQ(\"PRTY0,EVICT\", XrdMgmOfs::prepareOptsToString(opts));\n  }\n#endif\n}\n"
  },
  {
    "path": "unit_tests/mgm/bulk-request/BulkRequestPrepareManagerTest.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file BulkRequestPrepareManagerTest.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"unit_tests/mgm/bulk-request/PrepareManagerTest.hh\"\n#include \"unit_tests/mgm/bulk-request/MockPrepareMgmFSInterface.hh\"\n#include \"mgm/bulk-request/prepare/manager/BulkRequestPrepareManager.hh\"\n#include \"mgm/bulk-request/BulkRequestFactory.hh\"\n#include \"mgm/Namespace.hh\"\n#include \"common/VirtualIdentity.hh\"\n\nTEST_F(BulkRequestPrepareManagerTest, bulkRequestTest)\n{\n  USE_EOSBULKNAMESPACE\n  std::unique_ptr<StageBulkRequest> request =\n    BulkRequestFactory::createStageBulkRequest(\"requestId\",\n        eos::common::VirtualIdentity::Root());\n  //Let's add 10 files\n  uint8_t nbFiles = 10;\n\n  for (uint8_t i = 0; i < nbFiles; ++i) {\n    std::stringstream ss;\n    ss << \"path\" << i;\n    std::unique_ptr<File> fileToAdd = std::make_unique<File>(ss.str());\n    request->addFile(std::move(fileToAdd));\n  }\n\n  ASSERT_EQ(nbFiles, request->getFiles()->size());\n}\n\nusing ::testing::MatchesRegex;\n\nTEST_F(BulkRequestPrepareManagerTest, stagePrepareFilesWorkflow)\n{\n  int nbFiles = 3;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<MockPrepareMgmFSInterface> mgmOfsPtr =\n    std::make_unique<MockPrepareMgmFSInterface>();\n  MockPrepareMgmFSInterface& mgmOfs = *mgmOfsPtr;\n  //addStats should be called only two times\n  EXPECT_CALL(mgmOfs, addStats).Times(2);\n  //isTapeEnabled should not be called as we are in the case where everything is fine\n  EXPECT_CALL(mgmOfs, isTapeEnabled).Times(0);\n  // Set default value for getReqIdMaxCount\n  EXPECT_CALL(mgmOfs, getReqIdMaxCount()).Times(nbFiles).WillRepeatedly(Return(64));\n  //As everything is fine, no Emsg should be called\n  EXPECT_CALL(mgmOfs, Emsg).Times(0);\n  //Everything is fine, all the files exist\n  ON_CALL(mgmOfs, _exists(_, _, _, _, _, _)).WillByDefault(Invoke(\n        MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA));\n  //the _exists method should be called for all files\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles);\n  ON_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _))\n  .WillByDefault(Invoke(\n                   MockPrepareMgmFSInterface::_ATTR_LS_STAGE_PREPARE_LAMBDA\n                 ));\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(2 * nbFiles);\n  EXPECT_CALL(mgmOfs, Emsg).Times(0);\n  EXPECT_CALL(mgmOfs, _access).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, FSctl).Times(nbFiles);\n  // EOS-CTA reporter\n  EXPECT_CALL(mgmOfs, get_logId()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, get_host()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, writeEosReportRecord(MatchesRegex(\n                MockPrepareMgmFSInterface::EOS_REPORT_STR_FORMAT))).Times(nbFiles);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_STAGE, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::BulkRequestPrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  ASSERT_EQ(nbFiles, pm.getBulkRequest()->getFiles()->size());\n  ASSERT_EQ(SFS_DATA, retPrepare);\n}\n\nTEST_F(BulkRequestPrepareManagerTest, stagePrepareFileWithNoPath)\n{\n  //prepare stage should be idempotent https://its.cern.ch/jira/projects/EOS/issues/EOS-4739\n  std::unique_ptr<NiceMock<MockPrepareMgmFSInterface>> mgmOfsPtr =\n        std::make_unique<NiceMock<MockPrepareMgmFSInterface>>();\n  NiceMock<MockPrepareMgmFSInterface>& mgmOfs = *mgmOfsPtr;\n  //No path exist, but Emsg should not be called\n  EXPECT_CALL(mgmOfs, Emsg).Times(0);\n  //No path are set, no mgmOfs method should be called\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(0);\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(0);\n  EXPECT_CALL(mgmOfs, _access).Times(0);\n  EXPECT_CALL(mgmOfs, FSctl).Times(0);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_STAGE, {}, {});\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::BulkRequestPrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  //The bulk-request is created, 0 files are supposed to be there\n  ASSERT_EQ(0, pm.getBulkRequest()->getFiles()->size());\n  //The prepare manager returns SFS_DATA\n  ASSERT_EQ(SFS_DATA, retPrepare);\n}\n\nTEST_F(BulkRequestPrepareManagerTest, stagePrepareFileWithEmptyStringPaths)\n{\n  //prepare stage should be idempotent https://its.cern.ch/jira/projects/EOS/issues/EOS-4739\n  std::unique_ptr<NiceMock<MockPrepareMgmFSInterface>> mgmOfsPtr =\n        std::make_unique<NiceMock<MockPrepareMgmFSInterface>>();\n  NiceMock<MockPrepareMgmFSInterface>& mgmOfs = *mgmOfsPtr;\n  //No path exist, but Emsg should not be called\n  EXPECT_CALL(mgmOfs, Emsg).Times(1);\n  //No path are set, no mgmOfs method should be called\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(0);\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(0);\n  EXPECT_CALL(mgmOfs, _access).Times(0);\n  EXPECT_CALL(mgmOfs, FSctl).Times(0);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_STAGE, {\"\"}, {\"\"});\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::BulkRequestPrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  //The bulk-request is created, 0 files are supposed to be there\n  ASSERT_EQ(0, pm.getBulkRequest()->getFiles()->size());\n  //The prepare manager returns SFS_DATA\n  ASSERT_EQ(SFS_DATA, retPrepare);\n}\n\nTEST_F(BulkRequestPrepareManagerTest, stagePrepareAllFilesDoNotExist)\n{\n  /**\n   * If all files do not exist, the prepare should succeed\n   */\n  int nbFiles = 3;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<NiceMock<MockPrepareMgmFSInterface>> mgmOfsPtr =\n        std::make_unique<NiceMock<MockPrepareMgmFSInterface>>();\n  NiceMock<MockPrepareMgmFSInterface>& mgmOfs = *mgmOfsPtr;\n  //One file does not exist, Emsg should be called once\n  EXPECT_CALL(mgmOfs, Emsg).Times(nbFiles);\n  ON_CALL(mgmOfs, _exists(_, _, _, _, _, _)).WillByDefault(Invoke(\n        MockPrepareMgmFSInterface::_EXISTS_VID_FILE_DOES_NOT_EXIST_LAMBDA));\n  //The current behaviour is that the prepare logic returns an error if at least one file does not exist.\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(3);\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(0);\n  EXPECT_CALL(mgmOfs, _access).Times(0);\n  EXPECT_CALL(mgmOfs, FSctl).Times(0);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_STAGE, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::BulkRequestPrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  auto bulkRequest = pm.getBulkRequest();\n  ASSERT_EQ(3, bulkRequest->getFiles()->size());\n  ASSERT_EQ(3, bulkRequest->getAllFilesInError()->size());\n  auto filesInError = bulkRequest->getAllFilesInError();\n\n  for (auto fileInError : *filesInError) {\n    ASSERT_EQ(0,\n              fileInError.getError().value().find(\"prepare - file does not exist or is not accessible to you\"));\n  }\n\n  ASSERT_EQ(SFS_DATA, retPrepare);\n}\n\nTEST_F(BulkRequestPrepareManagerTest,\n       stagePrepareOneFileDoNotExistReturnsSfsData)\n{\n  /**\n   * If one file does not exist, the prepare should succeed\n   */\n  int nbFiles = 3;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<NiceMock<MockPrepareMgmFSInterface>> mgmOfsPtr =\n        std::make_unique<NiceMock<MockPrepareMgmFSInterface>>();\n  NiceMock<MockPrepareMgmFSInterface>& mgmOfs = *mgmOfsPtr;\n  //isTapeEnabled should not be called\n  EXPECT_CALL(mgmOfs, isTapeEnabled).Times(0);\n  // Set default value for getReqIdMaxCount\n  EXPECT_CALL(mgmOfs, getReqIdMaxCount()).Times(nbFiles - 1).WillRepeatedly(Return(64));\n  //One file does not exist, Emsg should be called once\n  EXPECT_CALL(mgmOfs, Emsg).Times(1);\n  //Exist will first return true for the existing file, then return false,\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles).WillOnce(\n    Invoke(MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA)\n  ).WillOnce(Invoke(\n               MockPrepareMgmFSInterface::_EXISTS_VID_FILE_DOES_NOT_EXIST_LAMBDA)\n            ).WillRepeatedly(\n              Invoke(MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA)\n            );\n  //Attr ls should work for the files that exist\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(2 * (nbFiles - 1))\n  .WillRepeatedly(Invoke(\n                    MockPrepareMgmFSInterface::_ATTR_LS_STAGE_PREPARE_LAMBDA\n                  ));\n  EXPECT_CALL(mgmOfs, _access).Times(nbFiles - 1);\n  EXPECT_CALL(mgmOfs, FSctl).Times(nbFiles - 1);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_STAGE, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::BulkRequestPrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  //All the files should be in the bulk-request, even the one that does not exist\n  auto bulkRequest = pm.getBulkRequest();\n  const auto& bulkReqPaths = bulkRequest->getFiles();\n  ASSERT_EQ(nbFiles, bulkReqPaths->size());\n\n  for (uint8_t i = 0; i < bulkReqPaths->size(); ++i) {\n    ASSERT_EQ(paths[i], (*bulkReqPaths)[i]->getPath());\n  }\n\n  //Prepare is a success\n  ASSERT_EQ(SFS_DATA, retPrepare);\n}\n\nTEST_F(BulkRequestPrepareManagerTest, stagePrepareNoPreparePermission)\n{\n  int nbFiles = 3;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<MockPrepareMgmFSInterface> mgmOfsPtr =\n    std::make_unique<MockPrepareMgmFSInterface>();\n  MockPrepareMgmFSInterface& mgmOfs = *mgmOfsPtr;\n  //addStats should be called only two times\n  EXPECT_CALL(mgmOfs, addStats).Times(2);\n  //isTapeEnabled should not be called as we are in the case where everything is fine\n  EXPECT_CALL(mgmOfs, isTapeEnabled).Times(0);\n  //As there is no prepare permission, Emsg should be called  for each file\n  EXPECT_CALL(mgmOfs, Emsg).Times(nbFiles);\n  //Everything is fine, all the files exist\n  ON_CALL(mgmOfs, _exists(_, _, _, _, _, _)).WillByDefault(Invoke(\n        MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA));\n  //the _exists method should be called for all files\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles);\n  ON_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _))\n  .WillByDefault(Invoke(\n                   MockPrepareMgmFSInterface::_ATTR_LS_STAGE_PREPARE_LAMBDA\n                 ));\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(nbFiles);\n  //Access should\n  EXPECT_CALL(mgmOfs, _access).Times(nbFiles).WillRepeatedly(Return(SFS_ERROR));\n  EXPECT_CALL(mgmOfs, FSctl).Times(0);\n  // EOS-CTA reporter\n  EXPECT_CALL(mgmOfs, get_logId()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, get_host()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, writeEosReportRecord(MatchesRegex(\n                MockPrepareMgmFSInterface::EOS_REPORT_STR_FORMAT))).Times(nbFiles);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_STAGE, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::BulkRequestPrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  ASSERT_EQ(nbFiles, pm.getBulkRequest()->getFiles()->size());\n  ASSERT_EQ(SFS_DATA, retPrepare);\n}\n\nTEST_F(BulkRequestPrepareManagerTest, abortPrepareFilesWorkflow)\n{\n  int nbFiles = 3;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<MockPrepareMgmFSInterface> mgmOfsPtr =\n    std::make_unique<MockPrepareMgmFSInterface>();\n  MockPrepareMgmFSInterface& mgmOfs = *mgmOfsPtr;\n  //addStats should be called only two times\n  EXPECT_CALL(mgmOfs, addStats).Times(2);\n  //isTapeEnabled should not be called as we are in the case where everything is fine\n  EXPECT_CALL(mgmOfs, isTapeEnabled).Times(0);\n  // Set default value for getReqIdMaxCount\n  EXPECT_CALL(mgmOfs, getReqIdMaxCount()).Times(0);\n  //As everything is fine, no Emsg should be called\n  EXPECT_CALL(mgmOfs, Emsg).Times(0);\n  //Everything is fine, all the files exist\n  ON_CALL(mgmOfs, _exists(_, _, _, _, _, _)).WillByDefault(Invoke(\n        MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA));\n  //the _exists method should be called for all files\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles);\n  ON_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _))\n  .WillByDefault(Invoke(\n                   MockPrepareMgmFSInterface::_ATTR_LS_ABORT_PREPARE_LAMBDA\n                 ));\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, Emsg).Times(0);\n  EXPECT_CALL(mgmOfs, _access).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, FSctl).Times(nbFiles);\n  // EOS-CTA reporter\n  EXPECT_CALL(mgmOfs, get_logId()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, get_host()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, writeEosReportRecord(MatchesRegex(\n                MockPrepareMgmFSInterface::EOS_REPORT_STR_FORMAT))).Times(nbFiles);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_CANCEL, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::BulkRequestPrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  //Abort prepare generates a bulk-request\n  ASSERT_NE(nullptr, pm.getBulkRequest());\n  //Abort prepare returns SFS_OK\n  ASSERT_EQ(SFS_OK, retPrepare);\n}\n\nTEST_F(BulkRequestPrepareManagerTest, abortPrepareOnFileExistsOtherDoNotExist)\n{\n  int nbFiles = 3;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<NiceMock<MockPrepareMgmFSInterface>> mgmOfsPtr =\n        std::make_unique<NiceMock<MockPrepareMgmFSInterface>>();\n  NiceMock<MockPrepareMgmFSInterface>& mgmOfs = *mgmOfsPtr;\n  //isTapeEnabled should not be called\n  EXPECT_CALL(mgmOfs, isTapeEnabled).Times(0);\n  // Set default value for getReqIdMaxCount\n  EXPECT_CALL(mgmOfs, getReqIdMaxCount()).Times(0);\n  //One file does not exist, but as we are idempotent, no error should be returned\n  EXPECT_CALL(mgmOfs, Emsg).Times(nbFiles - 1);\n  //Exist will first return true for the existing file, then return false\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles).WillOnce(Invoke(\n        MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA)).WillRepeatedly(\n          Invoke(\n            MockPrepareMgmFSInterface::_EXISTS_VID_FILE_DOES_NOT_EXIST_LAMBDA));\n  //Attr ls should work for the file that exists\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(1)\n  .WillRepeatedly(Invoke(\n                    MockPrepareMgmFSInterface::_ATTR_LS_ABORT_PREPARE_LAMBDA\n                  ));\n  EXPECT_CALL(mgmOfs, _access).Times(1);\n  EXPECT_CALL(mgmOfs, FSctl).Times(1);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_CANCEL, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::BulkRequestPrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  ASSERT_EQ(SFS_OK, retPrepare);\n}\n\nTEST_F(BulkRequestPrepareManagerTest, evictPrepareFilesWorkflow)\n{\n  int nbFiles = 3;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<MockPrepareMgmFSInterface> mgmOfsPtr =\n    std::make_unique<MockPrepareMgmFSInterface>();\n  MockPrepareMgmFSInterface& mgmOfs = *mgmOfsPtr;\n  //addStats should be called only two times\n  EXPECT_CALL(mgmOfs, addStats).Times(2);\n  //isTapeEnabled should not be called as we are in the case where everything is fine\n  EXPECT_CALL(mgmOfs, isTapeEnabled).Times(0);\n  // Set default value for getReqIdMaxCount\n  EXPECT_CALL(mgmOfs, getReqIdMaxCount()).Times(0);\n  //As everything is fine, no Emsg should be called\n  EXPECT_CALL(mgmOfs, Emsg).Times(0);\n  //Everything is fine, all the files exist\n  ON_CALL(mgmOfs, _exists(_, _, _, _, _, _)).WillByDefault(Invoke(\n        MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA));\n  //the _exists method should be called for all files\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles);\n  ON_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _))\n  .WillByDefault(Invoke(\n                   MockPrepareMgmFSInterface::_ATTR_LS_EVICT_PREPARE_LAMBDA\n                 ));\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, Emsg).Times(0);\n  EXPECT_CALL(mgmOfs, _access).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, FSctl).Times(nbFiles);\n  // EOS-CTA reporter\n  EXPECT_CALL(mgmOfs, get_logId()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, get_host()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, writeEosReportRecord(MatchesRegex(\n                MockPrepareMgmFSInterface::EOS_REPORT_STR_FORMAT))).Times(nbFiles);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_EVICT, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::BulkRequestPrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  //Evict prepare does not generate a bulk-request, so the bulk-request should be equal to nullptr\n  std::unique_ptr<BulkRequest> bulkRequest = pm.getBulkRequest();\n  ASSERT_EQ(nullptr, bulkRequest);\n  //Evict prepare returns SFS_OK\n  ASSERT_EQ(SFS_OK, retPrepare);\n}\n\nTEST_F(BulkRequestPrepareManagerTest, evictPrepareOneFileExistsOtherDoNotExist)\n{\n  /**\n   * If one file does not exist, the prepare evict should succeed\n   * Prepare is now idempotent (https://its.cern.ch/jira/projects/EOS/issues/EOS-4739)\n   */\n  int nbFiles = 3;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<NiceMock<MockPrepareMgmFSInterface>> mgmOfsPtr =\n        std::make_unique<NiceMock<MockPrepareMgmFSInterface>>();\n  NiceMock<MockPrepareMgmFSInterface>& mgmOfs = *mgmOfsPtr;\n  //isTapeEnabled should not be called\n  EXPECT_CALL(mgmOfs, isTapeEnabled).Times(0);\n  // Set default value for getReqIdMaxCount\n  EXPECT_CALL(mgmOfs, getReqIdMaxCount()).Times(0);\n  //One file does not exist, Emsg should not be called as we are idempotent\n  EXPECT_CALL(mgmOfs, Emsg).Times(nbFiles - 1);\n  //Exist will first return true for the existing file, then return false\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles).WillOnce(Invoke(\n        MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA)).WillRepeatedly(\n          Invoke(\n            MockPrepareMgmFSInterface::_EXISTS_VID_FILE_DOES_NOT_EXIST_LAMBDA));\n  //Attr ls should work for the files that exist\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(1)\n  .WillRepeatedly(Invoke(\n                    MockPrepareMgmFSInterface::_ATTR_LS_EVICT_PREPARE_LAMBDA\n                  ));\n  EXPECT_CALL(mgmOfs, _access).Times(1);\n  EXPECT_CALL(mgmOfs, FSctl).Times(1);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_EVICT, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::BulkRequestPrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  ASSERT_EQ(SFS_OK, retPrepare);\n}\n"
  },
  {
    "path": "unit_tests/mgm/bulk-request/MockPrepareMgmFSInterface.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file MockPrepareMgmFSInterface.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"MockPrepareMgmFSInterface.hh\"\n#include <XrdSfs/XrdSfsFlags.hh>\n#include \"common/Constants.hh\"\n\nEOSBULKNAMESPACE_BEGIN\n\nstd::function<int(const char* path, XrdSfsFileExistence& file_exists, XrdOucErrInfo& error, const XrdSecEntity* client, const char* ininfo)>\nMockPrepareMgmFSInterface::_EXISTS_FILE_EXISTS_LAMBDA =\n  [](const char* path, XrdSfsFileExistence& file_exists, XrdOucErrInfo& error,\n     const XrdSecEntity* client, const char* ininfo)\n{\n  file_exists = XrdSfsFileExistIsFile;\n  return SFS_OK;\n};\n\nstd::function<int(const char* path, XrdSfsFileExistence& file_exists, XrdOucErrInfo& error, const XrdSecEntity* client, const char* ininfo)>\nMockPrepareMgmFSInterface::_EXISTS_FILE_DOES_NOT_EXIST_LAMBDA =\n  [](const char* path, XrdSfsFileExistence& file_exists, XrdOucErrInfo& error,\n     const XrdSecEntity* client, const char* ininfo)\n{\n  file_exists = XrdSfsFileExistNo;\n  return SFS_ERROR;\n};\n\nstd::function<int(const char* path, XrdSfsFileExistence& file_exists, XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* opaque, bool take_lock)>\nMockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA =\n  [](const char* path, XrdSfsFileExistence& file_exists, XrdOucErrInfo& error,\n     eos::common::VirtualIdentity& vid, const char* opaque, bool take_lock)\n{\n  file_exists = XrdSfsFileExistIsFile;\n  return SFS_OK;\n};\n\nstd::function<int(const char* path, XrdSfsFileExistence& file_exists, XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* opaque, bool take_lock)>\nMockPrepareMgmFSInterface::_EXISTS_VID_FILE_DOES_NOT_EXIST_LAMBDA =\n  [](const char* path, XrdSfsFileExistence& file_exists, XrdOucErrInfo& error,\n     eos::common::VirtualIdentity& vid, const char* opaque, bool take_lock)\n{\n  file_exists = XrdSfsFileExistNo;\n  return SFS_ERROR;\n};\n\nstd::function<int(const char* path, XrdOucErrInfo& out_error, const eos::common::VirtualIdentity& vid, const char* opaque, eos::IContainerMD::XAttrMap& map, bool links)>\nMockPrepareMgmFSInterface::_ATTR_LS_STAGE_PREPARE_LAMBDA =\n  [](const char* path, XrdOucErrInfo& out_error,\n     const eos::common::VirtualIdentity& vid, const char* opaque,\n     eos::IContainerMD::XAttrMap& map, bool links)\n{\n  map[\"sys.workflow.sync::prepare\"] = \"\";\n  return SFS_OK;\n};\n\nstd::function<int(const char* path, XrdOucErrInfo& out_error, const eos::common::VirtualIdentity& vid, const char* opaque, eos::IContainerMD::XAttrMap& map, bool links)>\nMockPrepareMgmFSInterface::_ATTR_LS_ABORT_PREPARE_LAMBDA =\n  [](const char* path, XrdOucErrInfo& out_error,\n     const eos::common::VirtualIdentity& vid, const char* opaque,\n     eos::IContainerMD::XAttrMap& map, bool links)\n{\n  map[\"sys.workflow.sync::abort_prepare\"] = \"\";\n  return SFS_OK;\n};\n\nstd::function<int(const char* path, XrdOucErrInfo& out_error, const eos::common::VirtualIdentity& vid, const char* opaque, eos::IContainerMD::XAttrMap& map, bool links)>\nMockPrepareMgmFSInterface::_ATTR_LS_EVICT_PREPARE_LAMBDA =\n  [](const char* path, XrdOucErrInfo& out_error,\n     const eos::common::VirtualIdentity& vid, const char* opaque,\n     eos::IContainerMD::XAttrMap& map, bool links)\n{\n  map[\"sys.workflow.sync::evict_prepare\"] = \"\";\n  return SFS_OK;\n};\n\nstd::function<int(const char* path, XrdOucErrInfo& out_error, const eos::common::VirtualIdentity& vid, const char* opaque, eos::IContainerMD::XAttrMap& map, bool links)>\nMockPrepareMgmFSInterface::_ATTR_LS_QUERY_PREPARE_NO_ERROR_LAMBDA =\n  [](const char* path, XrdOucErrInfo& out_error,\n     const eos::common::VirtualIdentity& vid, const char* opaque,\n     eos::IContainerMD::XAttrMap& map, bool links)\n{\n  map[common::RETRIEVE_ERROR_ATTR_NAME] = \"\";\n  map[common::ARCHIVE_ERROR_ATTR_NAME] = \"\";\n  return SFS_OK;\n};\n\nstd::function<int(const char* path, XrdOucErrInfo& out_error, const eos::common::VirtualIdentity& vid, const char* opaque, eos::IContainerMD::XAttrMap& map, bool links)>\nMockPrepareMgmFSInterface::_ATTR_LS_RETRIEVE_ERROR_LAMBDA =\n  [](const char* path, XrdOucErrInfo& out_error,\n     const eos::common::VirtualIdentity& vid, const char* opaque,\n     eos::IContainerMD::XAttrMap& map, bool links)\n{\n  map[common::RETRIEVE_ERROR_ATTR_NAME] = ERROR_RETRIEVE_STR;\n  map[common::ARCHIVE_ERROR_ATTR_NAME] = \"\";\n  map[common::RETRIEVE_REQID_ATTR_NAME] = RETRIEVE_REQ_ID;\n  map[common::RETRIEVE_REQTIME_ATTR_NAME] = RETRIEVE_REQ_TIME;\n  return SFS_OK;\n};\n\nstd::function<int(const char* path, XrdOucErrInfo& out_error, const eos::common::VirtualIdentity& vid, const char* opaque, eos::IContainerMD::XAttrMap& map, bool links)>\nMockPrepareMgmFSInterface::_ATTR_LS_ARCHIVE_ERROR_LAMBDA =\n  [](const char* path, XrdOucErrInfo& out_error,\n     const eos::common::VirtualIdentity& vid, const char* opaque,\n     eos::IContainerMD::XAttrMap& map, bool links)\n{\n  //No retrieve error if archive error\n  map[common::ARCHIVE_ERROR_ATTR_NAME] = ERROR_ARCHIVE_STR;\n  return SFS_OK;\n};\n\nstd::function<int(const char* path, XrdOucErrInfo& out_error, const eos::common::VirtualIdentity& vid, const char* opaque, eos::IContainerMD::XAttrMap& map, bool links)>\nMockPrepareMgmFSInterface::_ATTR_LS_ARCHIVE_RETRIEVE_ERROR_LAMBDA =\n  [](const char* path, XrdOucErrInfo& out_error,\n     const eos::common::VirtualIdentity& vid, const char* opaque,\n     eos::IContainerMD::XAttrMap& map, bool links)\n{\n  map[common::RETRIEVE_ERROR_ATTR_NAME] = ERROR_RETRIEVE_STR;\n  map[common::ARCHIVE_ERROR_ATTR_NAME] = ERROR_ARCHIVE_STR;\n  return SFS_OK;\n};\nstd::function<int(const char* Name, struct stat* buf, XrdOucErrInfo& out_error, eos::common::VirtualIdentity& vid, const char* opaque, std::string* etag, bool follow, std::string* uri)>\nMockPrepareMgmFSInterface::_STAT_FILE_ON_TAPE_ONLY =\n  [](const char* Name, struct stat* buf, XrdOucErrInfo& out_error,\n     eos::common::VirtualIdentity& vid, const char* opaque, std::string* etag,\n     bool follow, std::string* uri)\n{\n  //File is on tape\n  buf->st_rdev |= XRDSFS_HASBKUP;\n  //File is not on disk\n  buf->st_rdev |= XRDSFS_OFFLINE;\n  return SFS_OK;\n};\n\nstd::function<int(const char* Name, struct stat* buf, XrdOucErrInfo& out_error, eos::common::VirtualIdentity& vid, const char* opaque, std::string* etag, bool follow, std::string* uri)>\nMockPrepareMgmFSInterface::_STAT_FILE_ON_DISK_ONLY =\n  [](const char* Name, struct stat* buf, XrdOucErrInfo& out_error,\n     eos::common::VirtualIdentity& vid, const char* opaque, std::string* etag,\n     bool follow, std::string* uri)\n{\n  //File is on disk\n  buf->st_rdev &= ~XRDSFS_OFFLINE;\n  //File is not on tape\n  buf->st_rdev &= ~XRDSFS_HASBKUP;\n  return SFS_OK;\n};\n\nstd::function<int(const char* Name, struct stat* buf, XrdOucErrInfo& out_error, eos::common::VirtualIdentity& vid, const char* opaque, std::string* etag, bool follow, std::string* uri)>\nMockPrepareMgmFSInterface::_STAT_FILE_ON_DISK_AND_TAPE =\n  [](const char* Name, struct stat* buf, XrdOucErrInfo& out_error,\n     eos::common::VirtualIdentity& vid, const char* opaque, std::string* etag,\n     bool follow, std::string* uri)\n{\n  //File is on tape\n  buf->st_rdev |= XRDSFS_HASBKUP;\n  //File is on disk\n  buf->st_rdev &= ~XRDSFS_OFFLINE;\n  return SFS_OK;\n};\n\nstd::function<int(const char* Name, struct stat* buf, XrdOucErrInfo& out_error, eos::common::VirtualIdentity& vid, const char* opaque, std::string* etag, bool follow, std::string* uri)>\nMockPrepareMgmFSInterface::_STAT_ERROR =\n  [](const char* Name, struct stat* buf, XrdOucErrInfo& out_error,\n     eos::common::VirtualIdentity& vid, const char* opaque, std::string* etag,\n     bool follow, std::string* uri)\n{\n  out_error.setErrInfo(666, ERROR_STAT_STR.c_str());\n  return SFS_ERROR;\n};\n\nstd::function<int(const char* path, int mode, XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* info)>\nMockPrepareMgmFSInterface::_ACCESS_FILE_NO_PREPARE_PERMISSION_LAMBDA =\n  [](const char* path, int mode, XrdOucErrInfo& error,\n     eos::common::VirtualIdentity& vid, const char* info)\n{\n  return SFS_ERROR;\n};\n\nstd::function<int(const char* path, int mode, XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* info)>\nMockPrepareMgmFSInterface::_ACCESS_FILE_PREPARE_PERMISSION_LAMBDA =\n  [](const char* path, int mode, XrdOucErrInfo& error,\n     eos::common::VirtualIdentity& vid, const char* info)\n{\n  return SFS_OK;\n};\nEOSBULKNAMESPACE_END"
  },
  {
    "path": "unit_tests/mgm/bulk-request/MockPrepareMgmFSInterface.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file MockPrepareMgmFSInterface.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_MOCKPREPAREMGMFSINTERFACE_HH\n#define EOS_MOCKPREPAREMGMFSINTERFACE_HH\n\n#include \"mgm/Namespace.hh\"\n#include <XrdOuc/XrdOucErrInfo.hh>\n#include <XrdSfs/XrdSfsInterface.hh>\n#include <XrdSec/XrdSecEntity.hh>\n#include \"common/VirtualIdentity.hh\"\n#include \"namespace/interface/IContainerMD.hh\"\n#include \"mgm/bulk-request/interface/IMgmFileSystemInterface.hh\"\n#include <sys/types.h>\n#include <gmock/gmock.h>\n\nEOSBULKNAMESPACE_BEGIN\n\nclass MockPrepareMgmFSInterface : public IMgmFileSystemInterface\n{\npublic:\n  MOCK_METHOD4(addStats, void(const char* tag, uid_t uid, gid_t gid,\n                              unsigned long val));\n  MOCK_METHOD0(isTapeEnabled, bool());\n  MOCK_METHOD0(getReqIdMaxCount, int());\n  MOCK_METHOD5(Emsg, int(const char* pfx, XrdOucErrInfo& einfo, int ecode,\n                         const char* op, const char* target));\n  MOCK_METHOD5(_exists, int(const char* path, XrdSfsFileExistence& file_exists,\n                            XrdOucErrInfo& error, const XrdSecEntity* client, const char* ininfo));\n  MOCK_METHOD6(_exists, int(const char* path, XrdSfsFileExistence& file_exists,\n                            XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* opaque,\n                            bool take_lock));\n  MOCK_METHOD6(_attr_ls, int(const char* path, XrdOucErrInfo& out_error,\n                             const eos::common::VirtualIdentity& vid, const char* opaque,\n                             eos::IContainerMD::XAttrMap& map, bool links));\n  MOCK_METHOD5(_access, int(const char* path, int mode, XrdOucErrInfo& error,\n                            eos::common::VirtualIdentity& vid, const char* info));\n  MOCK_METHOD4(FSctl, int(const int cmd, XrdSfsFSctl& args, XrdOucErrInfo& error,\n                          const XrdSecEntity* client));\n  MOCK_METHOD8(_stat, int(const char* Name, struct stat* buf,\n                          XrdOucErrInfo& out_error, eos::common::VirtualIdentity& vid, const char* opaque,\n                          std::string* etag, bool follow, std::string* uri));\n  MOCK_METHOD1(_stat_set_flags, void(struct stat* buf));\n  MOCK_METHOD0(get_logId, std::string());\n  MOCK_METHOD0(get_host, std::string());\n  MOCK_METHOD1(writeEosReportRecord, void(const std::string& record));\n  ~MockPrepareMgmFSInterface() {}\n\n  /**\n   * Lambdas that will be passed to the Invoke methods\n   */\n  //Lambda that will be called by the mock method _exists. This lambda will return that the file exists\n  static std::function<int(const char* path, XrdSfsFileExistence& file_exists, XrdOucErrInfo& error, const XrdSecEntity* client, const char* ininfo)>\n  _EXISTS_FILE_EXISTS_LAMBDA;\n  //Lambda that will be called by the mock method _exists. This lambda will return that the file does not exist\n  static std::function<int(const char* path, XrdSfsFileExistence& file_exists, XrdOucErrInfo& error, const XrdSecEntity* client, const char* ininfo)>\n  _EXISTS_FILE_DOES_NOT_EXIST_LAMBDA;\n\n  //Lambda that will be called by the mock method _exists. This lambda will return that the file exists\n  static std::function<int(const char* path, XrdSfsFileExistence& file_exists, XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* opaque, bool take_lock)>\n  _EXISTS_VID_FILE_EXISTS_LAMBDA;\n  //Lambda that will be called by the mock method _exists. This lambda will return that the file does not exist\n  static std::function<int(const char* path, XrdSfsFileExistence& file_exists, XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* opaque, bool take_lock)>\n  _EXISTS_VID_FILE_DOES_NOT_EXIST_LAMBDA;\n\n  //Lambda that will be called by the mock method _attr_ls on the files' parent directory in the case of stage prepare\n  static std::function<int(const char* path, XrdOucErrInfo& out_error, const eos::common::VirtualIdentity& vid, const char* opaque, eos::IContainerMD::XAttrMap& map, bool links)>\n  _ATTR_LS_STAGE_PREPARE_LAMBDA;\n  //Lambda that will be called by the mock method _attr_ls on the files' parent directory in the case of abort prepare\n  static std::function<int(const char* path, XrdOucErrInfo& out_error, const eos::common::VirtualIdentity& vid, const char* opaque, eos::IContainerMD::XAttrMap& map, bool links)>\n  _ATTR_LS_ABORT_PREPARE_LAMBDA;\n  //Lambda that will be called by the mock method _attr_ls on the files' parent directory in the case of evict prepare\n  static std::function<int(const char* path, XrdOucErrInfo& out_error, const eos::common::VirtualIdentity& vid, const char* opaque, eos::IContainerMD::XAttrMap& map, bool links)>\n  _ATTR_LS_EVICT_PREPARE_LAMBDA;\n  //Lambda that will be called by the mock method _attr_ls on the files and will return empty RETRIEVE and empty ARCHIVE errors\n  static std::function<int(const char* path, XrdOucErrInfo& out_error, const eos::common::VirtualIdentity& vid, const char* opaque, eos::IContainerMD::XAttrMap& map, bool links)>\n  _ATTR_LS_QUERY_PREPARE_NO_ERROR_LAMBDA;\n  //Lambda that will be called by the mock method _attr_ls on the files and will return a RETRIEVE ERROR\n  static std::function<int(const char* path, XrdOucErrInfo& out_error, const eos::common::VirtualIdentity& vid, const char* opaque, eos::IContainerMD::XAttrMap& map, bool links)>\n  _ATTR_LS_RETRIEVE_ERROR_LAMBDA;\n  //Lambda that will be called by the mock method _attr_ls on the files and will return a ARCHIVE ERROR\n  static std::function<int(const char* path, XrdOucErrInfo& out_error, const eos::common::VirtualIdentity& vid, const char* opaque, eos::IContainerMD::XAttrMap& map, bool links)>\n  _ATTR_LS_ARCHIVE_ERROR_LAMBDA;\n  //Lambda that will be called by the mock method _attr_ls on the files and will return a ARCHIVE AND RETRIEVE ERROR\n  static std::function<int(const char* path, XrdOucErrInfo& out_error, const eos::common::VirtualIdentity& vid, const char* opaque, eos::IContainerMD::XAttrMap& map, bool links)>\n  _ATTR_LS_ARCHIVE_RETRIEVE_ERROR_LAMBDA;\n  //Lambda that will be called by the mock method _stat on the file. This lambda will fill the stat buffer to fake the fact that the file is on tape only\n  static std::function<int(const char* Name, struct stat* buf, XrdOucErrInfo& out_error, eos::common::VirtualIdentity& vid, const char* opaque, std::string* etag, bool follow, std::string* uri)>\n  _STAT_FILE_ON_TAPE_ONLY;\n  //Lambda that will be called by the mock method _stat on the file. This lambda will fill the stat buffer to fake the fact that the file is on disk only\n  static std::function<int(const char* Name, struct stat* buf, XrdOucErrInfo& out_error, eos::common::VirtualIdentity& vid, const char* opaque, std::string* etag, bool follow, std::string* uri)>\n  _STAT_FILE_ON_DISK_ONLY;\n  //Lambda that will be called by the mock method _stat on the file. This lambda will fill the stat buffer to fake the fact that the file is on disk AND on tape\n  static std::function<int(const char* Name, struct stat* buf, XrdOucErrInfo& out_error, eos::common::VirtualIdentity& vid, const char* opaque, std::string* etag, bool follow, std::string* uri)>\n  _STAT_FILE_ON_DISK_AND_TAPE;\n  //Lambda that will be called by the mock method _stat on the file. This lambda will fake an error of the stat method\n  static std::function<int(const char* Name, struct stat* buf, XrdOucErrInfo& out_error, eos::common::VirtualIdentity& vid, const char* opaque, std::string* etag, bool follow, std::string* uri)>\n  _STAT_ERROR;\n\n  static std::function<int(const char* path, int mode, XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* info)>\n  _ACCESS_FILE_PREPARE_PERMISSION_LAMBDA;\n  static std::function<int(const char* path, int mode, XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* info)>\n  _ACCESS_FILE_NO_PREPARE_PERMISSION_LAMBDA;\n\n  static inline const std::string ERROR_RETRIEVE_STR = \"ERROR_RETRIEVE\";\n  static inline const std::string ERROR_ARCHIVE_STR = \"ERROR_ARCHIVE\";\n  static inline const std::string RETRIEVE_REQ_ID = \"RETRIEVE_REQ_ID\";\n  static inline const std::string RETRIEVE_REQ_TIME = \"RETRIEVE_REQ_TIME\";\n  static inline const std::string ERROR_STAT_STR = \"ERROR_STAT\";\n  static inline const std::string EOS_REPORT_STR_FORMAT =\n    \"(([^&=]+)=([^&]*))(&(([^&=]+)=([^&]*)))*\";\n};\n\nEOSBULKNAMESPACE_END\n\n#endif // EOS_MOCKPREPAREMGMFSINTERFACE_HH\n"
  },
  {
    "path": "unit_tests/mgm/bulk-request/PrepareManagerTest.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file PrepareManagerTest.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"unit_tests/mgm/bulk-request/MockPrepareMgmFSInterface.hh\"\n#include \"unit_tests/mgm/bulk-request/PrepareManagerTest.hh\"\n#include \"mgm/bulk-request/prepare/manager/PrepareManager.hh\"\n\nusing ::testing::Return;\nusing ::testing::_;\nusing ::testing::Invoke;\nusing ::testing::NiceMock;\nusing ::testing::MatchesRegex;\n\nUSE_EOSBULKNAMESPACE\n\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(PrepareManagerTest, PrepareUtilsPrepareOptsToString)\n{\n  using namespace eos::mgm;\n  {\n    const int opts = Prep_PRTY0;\n    ASSERT_EQ(\"PRTY0\", PrepareUtils::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_PRTY1;\n    ASSERT_EQ(\"PRTY1\", PrepareUtils::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_PRTY2;\n    ASSERT_EQ(\"PRTY2\", PrepareUtils::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_PRTY3;\n    ASSERT_EQ(\"PRTY3\", PrepareUtils::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_SENDAOK;\n    ASSERT_EQ(\"PRTY0,SENDAOK\", PrepareUtils::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_SENDERR;\n    ASSERT_EQ(\"PRTY0,SENDERR\", PrepareUtils::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_SENDACK;\n    ASSERT_EQ(\"PRTY0,SENDACK\", PrepareUtils::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_WMODE;\n    ASSERT_EQ(\"PRTY0,WMODE\", PrepareUtils::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_STAGE;\n    ASSERT_EQ(\"PRTY0,STAGE\", PrepareUtils::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_COLOC;\n    ASSERT_EQ(\"PRTY0,COLOC\", PrepareUtils::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_FRESH;\n    ASSERT_EQ(\"PRTY0,FRESH\", PrepareUtils::prepareOptsToString(opts));\n  }\n#if (XrdMajorVNUM(XrdVNUMBER) == 4 && XrdMinorVNUM(XrdVNUMBER) >= 10) || XrdMajorVNUM(XrdVNUMBER) >= 5\n  {\n    const int opts = Prep_CANCEL;\n    ASSERT_EQ(\"PRTY0,CANCEL\", PrepareUtils::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_QUERY;\n    ASSERT_EQ(\"PRTY0,QUERY\", PrepareUtils::prepareOptsToString(opts));\n  }\n  {\n    const int opts = Prep_EVICT;\n    ASSERT_EQ(\"PRTY0,EVICT\", PrepareUtils::prepareOptsToString(opts));\n  }\n#endif\n}\n\nTEST_F(PrepareManagerTest, pargsWrapperTest)\n{\n  PrepareArgumentsWrapper pargs(\"reqid\", Prep_CANCEL);\n\n  for (int i = 0; i < 10; ++i) {\n    pargs.addFile(std::to_string(i), std::to_string(i));\n  }\n\n  ASSERT_EQ(10, pargs.getNbFiles());\n  ASSERT_NE(nullptr, pargs.getPrepareArguments());\n}\n\nTEST_F(PrepareManagerTest, stagePrepareFilesWorkflow)\n{\n  int nbFiles = 3;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  // Add twice the same file to verify that the prepare workflow will be triggered\n  // for duplicated path\n  paths.push_back(\"a\");\n  paths.push_back(\"b\");\n  paths.push_back(\"a\");\n  oinfos.push_back(\"\");\n  oinfos.push_back(\"\");\n  oinfos.push_back(\"\");\n  nbFiles += 3;\n  std::unique_ptr<MockPrepareMgmFSInterface> mgmOfsPtr =\n    std::make_unique<MockPrepareMgmFSInterface>();\n  MockPrepareMgmFSInterface& mgmOfs = *mgmOfsPtr;\n  //addStats should be called only two times\n  EXPECT_CALL(mgmOfs, addStats).Times(2);\n  //isTapeEnabled should not be called as we are in the case where everything is fine\n  EXPECT_CALL(mgmOfs, isTapeEnabled).Times(0);\n  // Set default value for getReqIdMaxCount\n  EXPECT_CALL(mgmOfs, getReqIdMaxCount()).Times(nbFiles).WillRepeatedly(Return(64));\n  //As everything is fine, no Emsg should be called\n  EXPECT_CALL(mgmOfs, Emsg).Times(0);\n  //Everything is fine, all the files exist\n  ON_CALL(mgmOfs, _exists(_, _, _, _, _, _)).WillByDefault(Invoke(\n        MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA));\n  //the _exists method should be called for all files\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles);\n  ON_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _))\n  .WillByDefault(Invoke(\n                   MockPrepareMgmFSInterface::_ATTR_LS_STAGE_PREPARE_LAMBDA\n                 ));\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(2 * nbFiles);\n  EXPECT_CALL(mgmOfs, Emsg).Times(0);\n  EXPECT_CALL(mgmOfs, _access).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, FSctl).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, get_logId()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, get_host()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, writeEosReportRecord(MatchesRegex(\n                MockPrepareMgmFSInterface::EOS_REPORT_STR_FORMAT))).Times(nbFiles);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_STAGE, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::PrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  ASSERT_EQ(SFS_DATA, retPrepare);\n}\n\nTEST_F(PrepareManagerTest, stagePrepareFileWithNoPath)\n{\n  //prepare stage should be idempotent https://its.cern.ch/jira/projects/EOS/issues/EOS-4739\n  std::unique_ptr<NiceMock<MockPrepareMgmFSInterface>> mgmOfsPtr =\n        std::make_unique<NiceMock<MockPrepareMgmFSInterface>>();\n  NiceMock<MockPrepareMgmFSInterface>& mgmOfs = *mgmOfsPtr;\n  //No path exist, but Emsg should not be called\n  EXPECT_CALL(mgmOfs, Emsg).Times(0);\n  //No path are set, no mgmOfs method should be called\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(0);\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(0);\n  EXPECT_CALL(mgmOfs, _access).Times(0);\n  EXPECT_CALL(mgmOfs, FSctl).Times(0);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_STAGE, {}, {});\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::PrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  //The prepare manager returns SFS_ERROR\n  ASSERT_EQ(SFS_ERROR, retPrepare);\n}\n\nTEST_F(PrepareManagerTest, stagePrepareAllFilesDoNotExist)\n{\n  /**\n   * If all files do not exist, the prepare should not succeed\n   */\n  int nbFiles = 3;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<NiceMock<MockPrepareMgmFSInterface>> mgmOfsPtr =\n        std::make_unique<NiceMock<MockPrepareMgmFSInterface>>();\n  NiceMock<MockPrepareMgmFSInterface>& mgmOfs = *mgmOfsPtr;\n  //One file does not exist, Emsg should be called once\n  EXPECT_CALL(mgmOfs, Emsg).Times(nbFiles);\n  ON_CALL(mgmOfs, _exists(_, _, _, _, _, _)).WillByDefault(Invoke(\n        MockPrepareMgmFSInterface::_EXISTS_VID_FILE_DOES_NOT_EXIST_LAMBDA));\n  //The current behaviour is that the prepare logic returns an error if at least one file does not exist.\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(3);\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(0);\n  EXPECT_CALL(mgmOfs, _access).Times(0);\n  EXPECT_CALL(mgmOfs, FSctl).Times(0);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_STAGE, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::PrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  ASSERT_EQ(SFS_ERROR, retPrepare);\n}\n\nTEST_F(PrepareManagerTest, stagePrepareOneFileDoNotExistReturnsSfsData)\n{\n  /**\n   * If all files do not exist, the prepare should succeed\n   * Prepare is now idempotent (https://its.cern.ch/jira/projects/EOS/issues/EOS-4739)\n   */\n  int nbFiles = 3;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<NiceMock<MockPrepareMgmFSInterface>> mgmOfsPtr =\n        std::make_unique<NiceMock<MockPrepareMgmFSInterface>>();\n  NiceMock<MockPrepareMgmFSInterface>& mgmOfs = *mgmOfsPtr;\n  //isTapeEnabled should not be called\n  EXPECT_CALL(mgmOfs, isTapeEnabled).Times(0);\n  // Set default value for getReqIdMaxCount\n  EXPECT_CALL(mgmOfs, getReqIdMaxCount()).Times(nbFiles - 1).WillRepeatedly(Return(64));\n  //One file does not exist, Emsg should be called once\n  EXPECT_CALL(mgmOfs, Emsg).Times(1);\n  //Exist will first return true for the existing file, then return false,\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles).WillOnce(\n    Invoke(MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA)\n  ).WillOnce(Invoke(\n               MockPrepareMgmFSInterface::_EXISTS_VID_FILE_DOES_NOT_EXIST_LAMBDA)\n            ).WillRepeatedly(\n              Invoke(MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA)\n            );\n  //Attr ls should work for the files that exist\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(2 * (nbFiles - 1))\n  .WillRepeatedly(Invoke(\n                    MockPrepareMgmFSInterface::_ATTR_LS_STAGE_PREPARE_LAMBDA\n                  ));\n  EXPECT_CALL(mgmOfs, _access).Times(nbFiles - 1);\n  EXPECT_CALL(mgmOfs, FSctl).Times(nbFiles - 1);\n  EXPECT_CALL(mgmOfs, get_logId()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, get_host()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, writeEosReportRecord(MatchesRegex(\n                MockPrepareMgmFSInterface::EOS_REPORT_STR_FORMAT))).Times(nbFiles);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_STAGE, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::PrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  //We failed the second file, the prepare is a success\n  ASSERT_EQ(SFS_DATA, retPrepare);\n}\n\nTEST_F(PrepareManagerTest, abortPrepareFilesWorkflow)\n{\n  int nbFiles = 3;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<MockPrepareMgmFSInterface> mgmOfsPtr =\n    std::make_unique<MockPrepareMgmFSInterface>();\n  MockPrepareMgmFSInterface& mgmOfs = *mgmOfsPtr;\n  //addStats should be called only two times\n  EXPECT_CALL(mgmOfs, addStats).Times(nbFiles - 1);\n  //isTapeEnabled should not be called as we are in the case where everything is fine\n  EXPECT_CALL(mgmOfs, isTapeEnabled).Times(0);\n  // Set default value for getReqIdMaxCount\n  EXPECT_CALL(mgmOfs, getReqIdMaxCount()).Times(0);\n  //As everything is fine, no Emsg should be called\n  EXPECT_CALL(mgmOfs, Emsg).Times(0);\n  //Everything is fine, all the files exist\n  ON_CALL(mgmOfs, _exists(_, _, _, _, _, _)).WillByDefault(Invoke(\n        MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA));\n  //the _exists method should be called for all files\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles);\n  ON_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _))\n  .WillByDefault(Invoke(\n                   MockPrepareMgmFSInterface::_ATTR_LS_ABORT_PREPARE_LAMBDA\n                 ));\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, Emsg).Times(0);\n  EXPECT_CALL(mgmOfs, _access).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, FSctl).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, get_logId()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, get_host()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, writeEosReportRecord(MatchesRegex(\n                MockPrepareMgmFSInterface::EOS_REPORT_STR_FORMAT))).Times(nbFiles);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_CANCEL, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::PrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  //Abort prepare returns SFS_OK\n  ASSERT_EQ(SFS_OK, retPrepare);\n}\n\nTEST_F(PrepareManagerTest, abortPrepareOneFileExistsOthersDoNotExist)\n{\n  /**\n   * If one file does not exist, the prepare abort should fail\n   */\n  int nbFiles = 3;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<NiceMock<MockPrepareMgmFSInterface>> mgmOfsPtr =\n        std::make_unique<NiceMock<MockPrepareMgmFSInterface>>();\n  NiceMock<MockPrepareMgmFSInterface>& mgmOfs = *mgmOfsPtr;\n  //isTapeEnabled should not be called\n  EXPECT_CALL(mgmOfs, isTapeEnabled).Times(0);\n  // Set default value for getReqIdMaxCount\n  EXPECT_CALL(mgmOfs, getReqIdMaxCount()).Times(0);\n  EXPECT_CALL(mgmOfs, Emsg).Times(nbFiles - 1);\n  //Exist will first return true for the existing file, then return false\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles).WillOnce(Invoke(\n        MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA)).WillRepeatedly(\n          Invoke(\n            MockPrepareMgmFSInterface::_EXISTS_VID_FILE_DOES_NOT_EXIST_LAMBDA));\n  //Attr ls should work for the file that exists\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(1)\n  .WillRepeatedly(Invoke(\n                    MockPrepareMgmFSInterface::_ATTR_LS_ABORT_PREPARE_LAMBDA\n                  ));\n  EXPECT_CALL(mgmOfs, _access).Times(1);\n  EXPECT_CALL(mgmOfs, FSctl).Times(1);\n  EXPECT_CALL(mgmOfs, get_logId()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, get_host()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, writeEosReportRecord(MatchesRegex(\n                MockPrepareMgmFSInterface::EOS_REPORT_STR_FORMAT))).Times(nbFiles);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_CANCEL, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::PrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  ASSERT_EQ(SFS_ERROR, retPrepare);\n}\n\nTEST_F(PrepareManagerTest, evictPrepareFilesWorkflow)\n{\n  int nbFiles = 3;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<MockPrepareMgmFSInterface> mgmOfsPtr =\n    std::make_unique<MockPrepareMgmFSInterface>();\n  MockPrepareMgmFSInterface& mgmOfs = *mgmOfsPtr;\n  //addStats should be called only two times\n  EXPECT_CALL(mgmOfs, addStats).Times(2);\n  //isTapeEnabled should not be called as we are in the case where everything is fine\n  EXPECT_CALL(mgmOfs, isTapeEnabled).Times(0);\n  // Set default value for getReqIdMaxCount\n  EXPECT_CALL(mgmOfs, getReqIdMaxCount()).Times(0);\n  //As everything is fine, no Emsg should be called\n  EXPECT_CALL(mgmOfs, Emsg).Times(0);\n  //Everything is fine, all the files exist\n  ON_CALL(mgmOfs, _exists(_, _, _, _, _, _)).WillByDefault(Invoke(\n        MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA));\n  //the _exists method should be called for all files\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles);\n  ON_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _))\n  .WillByDefault(Invoke(\n                   MockPrepareMgmFSInterface::_ATTR_LS_EVICT_PREPARE_LAMBDA\n                 ));\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, Emsg).Times(0);\n  EXPECT_CALL(mgmOfs, _access).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, FSctl).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, get_logId()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, get_host()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, writeEosReportRecord(MatchesRegex(\n                MockPrepareMgmFSInterface::EOS_REPORT_STR_FORMAT))).Times(nbFiles);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_EVICT, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::PrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  //Evict prepare returns SFS_OK\n  ASSERT_EQ(SFS_OK, retPrepare);\n}\n\nTEST_F(PrepareManagerTest, evictPrepareOneFileExistsOthersDoNotExist)\n{\n  /**\n   * If one file does not exist, the prepare evict should succeed\n   * Prepare is now idempotent (https://its.cern.ch/jira/projects/EOS/issues/EOS-4739)\n   */\n  int nbFiles = 3;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<NiceMock<MockPrepareMgmFSInterface>> mgmOfsPtr =\n        std::make_unique<NiceMock<MockPrepareMgmFSInterface>>();\n  NiceMock<MockPrepareMgmFSInterface>& mgmOfs = *mgmOfsPtr;\n  //isTapeEnabled should not be called\n  EXPECT_CALL(mgmOfs, isTapeEnabled).Times(0);\n  // Set default value for getReqIdMaxCount\n  EXPECT_CALL(mgmOfs, getReqIdMaxCount()).Times(0);\n  EXPECT_CALL(mgmOfs, Emsg).Times(nbFiles - 1);\n  //Exist will first return true for the existing file, then return false\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles).WillOnce(Invoke(\n        MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA)).WillRepeatedly(\n          Invoke(\n            MockPrepareMgmFSInterface::_EXISTS_VID_FILE_DOES_NOT_EXIST_LAMBDA));\n  //Attr ls should work for the files that exist\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(1)\n  .WillRepeatedly(Invoke(\n                    MockPrepareMgmFSInterface::_ATTR_LS_EVICT_PREPARE_LAMBDA\n                  ));\n  EXPECT_CALL(mgmOfs, _access).Times(1);\n  EXPECT_CALL(mgmOfs, FSctl).Times(1);\n  EXPECT_CALL(mgmOfs, get_logId()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, get_host()).Times(nbFiles);\n  EXPECT_CALL(mgmOfs, writeEosReportRecord(MatchesRegex(\n                MockPrepareMgmFSInterface::EOS_REPORT_STR_FORMAT))).Times(nbFiles);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  PrepareArgumentsWrapper pargs(\"testReqId\", Prep_EVICT, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::PrepareManager pm(std::move(mgmOfsPtr));\n  int retPrepare = pm.prepare(*(pargs.getPrepareArguments()), *error,\n                              client.getClient());\n  ASSERT_EQ(SFS_ERROR, retPrepare);\n}\n\nTEST_F(PrepareManagerTest, queryPrepare)\n{\n  int nbFiles = 2;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  //Add twice the same file to verify that query prepare will return the result twice for the same file\n  paths.push_back(\"a\");\n  //One file in the middle to test that the order will be preserved\n  paths.push_back(\"b\");\n  paths.push_back(\"a\");\n  oinfos.push_back(\"\");\n  oinfos.push_back(\"\");\n  oinfos.push_back(\"\");\n  nbFiles += 3;\n  std::unique_ptr<NiceMock<MockPrepareMgmFSInterface>> mgmOfsPtr =\n        std::make_unique<NiceMock<MockPrepareMgmFSInterface>>();\n  NiceMock<MockPrepareMgmFSInterface>& mgmOfs = *mgmOfsPtr;\n  //Exist will first return true for the first file, then return false\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles).WillOnce(Invoke(\n        MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA)).WillRepeatedly(\n          Invoke(\n            MockPrepareMgmFSInterface::_EXISTS_VID_FILE_DOES_NOT_EXIST_LAMBDA));\n  //stat with one file on disk, on file on disk and tape\n  EXPECT_CALL(mgmOfs, _stat(_, _, _, _, _, _, _, _)).Times(1).WillOnce(Invoke(\n        MockPrepareMgmFSInterface::_STAT_FILE_ON_DISK_AND_TAPE));\n  //Attr ls should work for the files that exist\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(1)\n  .WillRepeatedly(Invoke(\n                    MockPrepareMgmFSInterface::_ATTR_LS_STAGE_PREPARE_LAMBDA\n                  ));\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  std::string requestId = \"testReqId\";\n  PrepareArgumentsWrapper pargs(requestId, Prep_QUERY, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::PrepareManager pm(std::move(mgmOfsPtr));\n  std::unique_ptr<eos::mgm::bulk::QueryPrepareResult> retQueryPrepare =\n    pm.queryPrepare(*(pargs.getPrepareArguments()), *error, client.getClient());\n  const auto& response = retQueryPrepare->getResponse();\n  ASSERT_EQ(requestId, response->request_id);\n  const auto& existingFile = response->responses.front();\n  ASSERT_TRUE(existingFile.is_online);\n  ASSERT_TRUE(existingFile.is_on_tape);\n  ASSERT_TRUE(existingFile.is_exists);\n  ASSERT_EQ(paths.front(), existingFile.path);\n  const auto& notExistingFile = response->responses.back();\n  ASSERT_FALSE(notExistingFile.is_online);\n  ASSERT_FALSE(notExistingFile.is_on_tape);\n  ASSERT_FALSE(notExistingFile.is_exists);\n  ASSERT_EQ(\"USER ERROR: file does not exist or is not accessible to you\",\n            notExistingFile.error_text);\n  ASSERT_EQ(paths.back(), notExistingFile.path);\n\n  //Test that the files are returned in the same order as they were submitted by the client\n  for (int i = 0; i < nbFiles; ++i) {\n    ASSERT_EQ(paths[i], retQueryPrepare->getResponse()->responses[i].path);\n  }\n\n  ASSERT_EQ(SFS_DATA, retQueryPrepare->getReturnCode());\n}\n\nTEST_F(PrepareManagerTest, queryPrepareFileDoesNotExist)\n{\n  int nbFiles = 1;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<NiceMock<MockPrepareMgmFSInterface>> mgmOfsPtr =\n        std::make_unique<NiceMock<MockPrepareMgmFSInterface>>();\n  NiceMock<MockPrepareMgmFSInterface>& mgmOfs = *mgmOfsPtr;\n  //First file does not exist\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles).WillRepeatedly(\n    Invoke(\n      MockPrepareMgmFSInterface::_EXISTS_VID_FILE_DOES_NOT_EXIST_LAMBDA)\n  );\n  EXPECT_CALL(mgmOfs, _stat(_, _, _, _, _, _, _, _)).Times(0);\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(0);\n  EXPECT_CALL(mgmOfs, _access(_, _, _, _, _)).Times(0);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  std::string requestId = \"testReqId\";\n  PrepareArgumentsWrapper pargs(requestId, Prep_QUERY, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::PrepareManager pm(std::move(mgmOfsPtr));\n  std::unique_ptr<eos::mgm::bulk::QueryPrepareResult> retQueryPrepare =\n    pm.queryPrepare(*(pargs.getPrepareArguments()), *error, client.getClient());\n  const auto& response = retQueryPrepare->getResponse();\n  ASSERT_EQ(requestId, response->request_id);\n  const auto& fileDoesNotExist = response->responses[0];\n  ASSERT_FALSE(fileDoesNotExist.is_online);\n  ASSERT_FALSE(fileDoesNotExist.is_on_tape);\n  ASSERT_FALSE(fileDoesNotExist.is_exists);\n  ASSERT_FALSE(fileDoesNotExist.is_reqid_present);\n  ASSERT_FALSE(fileDoesNotExist.is_requested);\n  ASSERT_FALSE(fileDoesNotExist.error_text.empty());\n  ASSERT_TRUE(fileDoesNotExist.request_time.empty());\n  ASSERT_EQ(paths[0], fileDoesNotExist.path);\n}\n\nTEST_F(PrepareManagerTest, queryPrepareFileStatFails)\n{\n  int nbFiles = 1;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<NiceMock<MockPrepareMgmFSInterface>> mgmOfsPtr =\n        std::make_unique<NiceMock<MockPrepareMgmFSInterface>>();\n  NiceMock<MockPrepareMgmFSInterface>& mgmOfs = *mgmOfsPtr;\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles).WillRepeatedly(\n    Invoke(\n      MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA)\n  );\n  EXPECT_CALL(mgmOfs, _stat(_, _, _, _, _, _, _, _)).Times(nbFiles).\n  WillRepeatedly(\n    Invoke(\n      //Stat fails for one file\n      MockPrepareMgmFSInterface::_STAT_ERROR\n    ));\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(0);\n  EXPECT_CALL(mgmOfs, _access(_, _, _, _, _)).Times(0);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  std::string requestId = \"testReqId\";\n  PrepareArgumentsWrapper pargs(requestId, Prep_QUERY, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::PrepareManager pm(std::move(mgmOfsPtr));\n  std::unique_ptr<eos::mgm::bulk::QueryPrepareResult> retQueryPrepare =\n    pm.queryPrepare(*(pargs.getPrepareArguments()), *error, client.getClient());\n  const auto& response = retQueryPrepare->getResponse();\n  ASSERT_EQ(requestId, response->request_id);\n  const auto& file = response->responses[0];\n  ASSERT_FALSE(file.is_online);\n  ASSERT_FALSE(file.is_on_tape);\n  ASSERT_TRUE(file.is_exists);\n  ASSERT_FALSE(file.is_reqid_present);\n  ASSERT_FALSE(file.is_requested);\n  ASSERT_EQ(MockPrepareMgmFSInterface::ERROR_STAT_STR, file.error_text);\n  ASSERT_TRUE(file.request_time.empty());\n  ASSERT_EQ(paths[0], file.path);\n}\n\nTEST_F(PrepareManagerTest, queryPrepareFileOnTapeRetrieveError)\n{\n  int nbFiles = 1;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<NiceMock<MockPrepareMgmFSInterface>> mgmOfsPtr =\n        std::make_unique<NiceMock<MockPrepareMgmFSInterface>>();\n  NiceMock<MockPrepareMgmFSInterface>& mgmOfs = *mgmOfsPtr;\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles).WillRepeatedly(\n    Invoke(\n      MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA)\n  );\n  EXPECT_CALL(mgmOfs, _stat(_, _, _, _, _, _, _, _)).WillRepeatedly(\n    Invoke(\n      //File could not be recalled\n      MockPrepareMgmFSInterface::_STAT_FILE_ON_TAPE_ONLY\n    ));\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(nbFiles).WillRepeatedly(\n                                 Invoke(\n                                   MockPrepareMgmFSInterface::_ATTR_LS_RETRIEVE_ERROR_LAMBDA\n                                 ));\n  EXPECT_CALL(mgmOfs, _access(_, _, _, _, _)).Times(nbFiles);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  std::string requestId = MockPrepareMgmFSInterface::RETRIEVE_REQ_ID;\n  PrepareArgumentsWrapper pargs(requestId, Prep_QUERY, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::PrepareManager pm(std::move(mgmOfsPtr));\n  std::unique_ptr<eos::mgm::bulk::QueryPrepareResult> retQueryPrepare =\n    pm.queryPrepare(*(pargs.getPrepareArguments()), *error, client.getClient());\n  const auto& response = retQueryPrepare->getResponse();\n  ASSERT_EQ(requestId, response->request_id);\n  const auto& file = response->responses[0];\n  ASSERT_FALSE(file.is_online);\n  ASSERT_TRUE(file.is_on_tape);\n  ASSERT_TRUE(file.is_exists);\n  ASSERT_TRUE(file.is_reqid_present);\n  ASSERT_TRUE(file.is_requested);\n  ASSERT_EQ(MockPrepareMgmFSInterface::ERROR_RETRIEVE_STR, file.error_text);\n  ASSERT_EQ(MockPrepareMgmFSInterface::RETRIEVE_REQ_TIME, file.request_time);\n  ASSERT_EQ(paths[0], file.path);\n}\n\nTEST_F(PrepareManagerTest, queryPrepareFileOnDiskArchiveError)\n{\n  int nbFiles = 1;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<NiceMock<MockPrepareMgmFSInterface>> mgmOfsPtr =\n        std::make_unique<NiceMock<MockPrepareMgmFSInterface>>();\n  NiceMock<MockPrepareMgmFSInterface>& mgmOfs = *mgmOfsPtr;\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles).WillRepeatedly(\n    Invoke(\n      MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA)\n  );\n  EXPECT_CALL(mgmOfs, _stat(_, _, _, _, _, _, _, _)).WillRepeatedly(\n    Invoke(\n      //File could not be recalled\n      MockPrepareMgmFSInterface::_STAT_FILE_ON_DISK_ONLY\n    ));\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(nbFiles).WillRepeatedly(\n                                 Invoke(\n                                   MockPrepareMgmFSInterface::_ATTR_LS_ARCHIVE_ERROR_LAMBDA\n                                 ));\n  EXPECT_CALL(mgmOfs, _access(_, _, _, _, _)).Times(nbFiles);\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  std::string requestId = \"testReqId\";\n  PrepareArgumentsWrapper pargs(requestId, Prep_QUERY, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::PrepareManager pm(std::move(mgmOfsPtr));\n  std::unique_ptr<eos::mgm::bulk::QueryPrepareResult> retQueryPrepare =\n    pm.queryPrepare(*(pargs.getPrepareArguments()), *error, client.getClient());\n  const auto& response = retQueryPrepare->getResponse();\n  ASSERT_EQ(requestId, response->request_id);\n  const auto& file = response->responses[0];\n  ASSERT_TRUE(file.is_online);\n  ASSERT_FALSE(file.is_on_tape);\n  ASSERT_TRUE(file.is_exists);\n  ASSERT_FALSE(file.is_reqid_present);\n  ASSERT_FALSE(file.is_requested);\n  ASSERT_EQ(MockPrepareMgmFSInterface::ERROR_ARCHIVE_STR, file.error_text);\n  ASSERT_TRUE(file.request_time.empty());\n  ASSERT_EQ(paths[0], file.path);\n}\n\nTEST_F(PrepareManagerTest, queryPrepareFileNoPreparePermissionOnDirectory)\n{\n  int nbFiles = 1;\n  std::vector<std::string> paths = PrepareManagerTest::generateDefaultPaths(\n                                     nbFiles);\n  std::vector<std::string> oinfos = PrepareManagerTest::generateEmptyOinfos(\n                                      nbFiles);\n  std::unique_ptr<NiceMock<MockPrepareMgmFSInterface>> mgmOfsPtr =\n        std::make_unique<NiceMock<MockPrepareMgmFSInterface>>();\n  NiceMock<MockPrepareMgmFSInterface>& mgmOfs = *mgmOfsPtr;\n  EXPECT_CALL(mgmOfs, _exists(_, _, _, _, _, _)).Times(nbFiles).WillRepeatedly(\n    Invoke(\n      MockPrepareMgmFSInterface::_EXISTS_VID_FILE_EXISTS_LAMBDA)\n  );\n  EXPECT_CALL(mgmOfs, _stat(_, _, _, _, _, _, _, _)).WillRepeatedly(\n    Invoke(\n      //File could not be recalled\n      MockPrepareMgmFSInterface::_STAT_FILE_ON_TAPE_ONLY\n    ));\n  EXPECT_CALL(mgmOfs, _attr_ls(_, _, _, _, _, _)).Times(nbFiles).WillRepeatedly(\n                                 Invoke(\n                                   //Set all possible errors, we want to see the \"USER ERROR: you don't have prepare permission\" error message in all cases.\n                                   MockPrepareMgmFSInterface::_ATTR_LS_ARCHIVE_RETRIEVE_ERROR_LAMBDA\n                                 ));\n  EXPECT_CALL(mgmOfs, _access(_, _, _, _, _)).Times(nbFiles).WillRepeatedly(\n    Invoke(\n      MockPrepareMgmFSInterface::_ACCESS_FILE_NO_PREPARE_PERMISSION_LAMBDA\n    )\n  );\n  ClientWrapper client = PrepareManagerTest::getDefaultClient();\n  std::string requestId = \"testReqId\";\n  PrepareArgumentsWrapper pargs(requestId, Prep_QUERY, paths, oinfos);\n  ErrorWrapper errorWrapper = PrepareManagerTest::getDefaultError();\n  XrdOucErrInfo* error = errorWrapper.getError();\n  eos::mgm::bulk::PrepareManager pm(std::move(mgmOfsPtr));\n  std::unique_ptr<eos::mgm::bulk::QueryPrepareResult> retQueryPrepare =\n    pm.queryPrepare(*(pargs.getPrepareArguments()), *error, client.getClient());\n  const auto& response = retQueryPrepare->getResponse();\n  ASSERT_EQ(requestId, response->request_id);\n  const auto& file = response->responses[0];\n  ASSERT_FALSE(file.is_online);\n  ASSERT_TRUE(file.is_on_tape);\n  ASSERT_TRUE(file.is_exists);\n  ASSERT_FALSE(file.is_reqid_present);\n  ASSERT_FALSE(file.is_requested);\n  ASSERT_FALSE(file.error_text.empty());\n  ASSERT_EQ(\"USER ERROR: you don't have prepare permission\", file.error_text);\n  ASSERT_TRUE(file.request_time.empty());\n  ASSERT_EQ(paths[0], file.path);\n}\n"
  },
  {
    "path": "unit_tests/mgm/bulk-request/PrepareManagerTest.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file PrepareManagerTest.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_PREPAREMANAGERTEST_HH\n#define EOS_PREPAREMANAGERTEST_HH\n\n#include \"mgm/ofs/XrdMgmOfs.hh\"\n\n#include <XrdVersion.hh>\n#include <gtest/gtest.h>\n#include <gmock/gmock.h>\n#include \"mgm/bulk-request/prepare/PrepareUtils.hh\"\n#include \"auth_plugin/ProtoUtils.hh\"\n#include \"mgm/bulk-request/utils/PrepareArgumentsWrapper.hh\"\n\nusing ::testing::Return;\nusing ::testing::_;\nusing ::testing::Invoke;\nusing ::testing::NiceMock;\n\nUSE_EOSBULKNAMESPACE\n\n/**\n * RAII class to hold the XrdSecEntity client pointer\n * Avoids memory leaks for valgrind\n */\nclass ClientWrapper\n{\npublic:\n  ClientWrapper(const std::string& prot, const std::string& name,\n                const std::string& host, const std::string& tident)\n  {\n    eos::auth::XrdSecEntityProto clientProto;\n    clientProto.set_prot(prot);\n    clientProto.set_name(name);\n    clientProto.set_host(host);\n    clientProto.set_tident(tident);\n    mClient = eos::auth::utils::GetXrdSecEntity(clientProto);\n  }\n  ~ClientWrapper()\n  {\n    eos::auth::utils::DeleteXrdSecEntity(mClient);\n  }\n  XrdSecEntity* getClient()\n  {\n    return mClient;\n  }\nprivate:\n  XrdSecEntity* mClient;\n};\n\n/**\n * RAII class to hold the XrdOucErrInfo object related to errors.\n * Avoids memory leaks for valgrind\n */\nclass ErrorWrapper\n{\npublic:\n  ErrorWrapper(const std::string user, const int code, const std::string& message)\n  {\n    eos::auth::XrdOucErrInfoProto errorProto;\n    errorProto.set_user(user);\n    errorProto.set_code(code);\n    errorProto.set_message(message);\n    mError = eos::auth::utils::GetXrdOucErrInfo(errorProto);\n  }\n\n  XrdOucErrInfo* getError()\n  {\n    return mError;\n  }\n\n  ~ErrorWrapper()\n  {\n    if (mError != nullptr) {\n      delete mError;\n      mError = nullptr;\n    }\n  }\nprivate:\n  XrdOucErrInfo* mError;\n};\n\n/**\n * Test suite class to test the PrepareManager\n */\nclass PrepareManagerTest : public ::testing::Test\n{\nprotected:\n\n  virtual void SetUp()\n  {\n    eos::common::Mapping::Init();\n  }\n\n  virtual void TearDown()\n  {\n    eos::common::Mapping::Reset();\n  }\n\n  static ClientWrapper getDefaultClient()\n  {\n    return ClientWrapper(\"krb5\", \"clientName\", \"localhost\", \"clientTident\");\n  }\n\n  static ErrorWrapper getDefaultError()\n  {\n    return ErrorWrapper(\"\", 0, \"\");\n  }\n\n  static std::vector<std::string> generateDefaultPaths(const uint64_t nbFiles)\n  {\n    std::vector<std::string> paths;\n\n    for (uint64_t i = nbFiles; i > 0; --i) {\n      std::stringstream ss;\n      ss << \"path\" << i;\n      paths.push_back(ss.str());\n    }\n\n    return paths;\n  }\n\n  static std::vector<std::string> generateEmptyOinfos(const uint64_t nbFiles)\n  {\n    std::vector<std::string> oinfos;\n\n    for (uint64_t i = 0; i < nbFiles; ++i) {\n      oinfos.push_back(\"\");\n    }\n\n    return oinfos;\n  }\n};\n\n/**\n * Test suite class to test the BulkRequestPrepareManager\n */\nclass BulkRequestPrepareManagerTest : public PrepareManagerTest\n{\n\n};\n\n\n#endif // EOS_PREPAREMANAGERTEST_HH\n"
  },
  {
    "path": "unit_tests/mgm/groupbalancer/BalancerEngineTypeTests.cc",
    "content": "#include \"gtest/gtest.h\"\n#include \"mgm/groupbalancer/BalancerEngineTypes.hh\"\n\n// Check at compile time\nusing namespace eos::mgm::group_balancer;\nusing namespace std::string_view_literals;\nstatic_assert(getGroupStatus(\"on\"sv) == GroupStatus::ON);\nstatic_assert(getGroupStatus(\"drain\"sv) == GroupStatus::DRAIN);\nstatic_assert(getGroupStatus(\"foo\"sv) == GroupStatus::OFF);\n\nTEST(GroupBalancerTypes, Status)\n{\n  ASSERT_EQ(getGroupStatus(\"on\"), GroupStatus::ON);\n  std::string off = \"off\";\n  // Test the various char/str -> sv conversions\n  ASSERT_EQ(getGroupStatus(off.c_str()), GroupStatus::OFF);\n  ASSERT_EQ(getGroupStatus(off), GroupStatus::OFF);\n  const char* drain = \"drain\";\n  ASSERT_EQ(getGroupStatus(drain), GroupStatus::DRAIN);\n}\n"
  },
  {
    "path": "unit_tests/mgm/groupbalancer/FreeSpaceBalancerTests.cc",
    "content": "#include \"gtest/gtest.h\"\n#include \"mgm/groupbalancer/FreeSpaceBalancerEngine.hh\"\n#include <memory>\n\nusing namespace eos::mgm::group_balancer;\n\nTEST(FreeSpaceBalancerEngine, simple)\n{\n  auto engine = std::make_unique<FreeSpaceBalancerEngine>();\n  // This is not a realistic scenario, since we have so small bytes as the total\n  // space, but easy to do the mental math to understand\n  engine->populateGroupsInfo({{\"group1\", {GroupStatus::ON, 800, 1000}},\n    {\"group2\", {GroupStatus::ON, 1800, 2000}},\n    {\"group3\", {GroupStatus::ON, 500, 1000}},\n    {\"group4\", {GroupStatus::ON, 700, 1500}},\n    {\"group5\", {GroupStatus::ON, 1200, 1500}}\n  });\n  EXPECT_EQ(400, engine->getGroupFreeSpace());\n  EXPECT_EQ(408, engine->getFreeSpaceULimit());\n  EXPECT_EQ(392, engine->getFreeSpaceLLimit());\n  threshold_group_set expected_targets = {\"group3\", \"group4\"};  // Freebytes > 400\n  threshold_group_set expected_sources = {\"group1\", \"group2\", \"group5\"};\n  auto d = engine->get_data();\n  EXPECT_EQ(d.mGroupSizes.size(), 5);\n  EXPECT_EQ(d.mGroupsOverThreshold.size(), 3);\n  EXPECT_EQ(d.mGroupsUnderThreshold.size(), 2);\n  EXPECT_EQ(d.mGroupsUnderThreshold, expected_targets);\n  EXPECT_EQ(d.mGroupsOverThreshold, expected_sources);\n}\n\nTEST(FreeSpaceBalancerEngine, blocklisting)\n{\n  auto engine = std::make_unique<FreeSpaceBalancerEngine>();\n  engine->populateGroupsInfo({{\"group1\", {GroupStatus::ON, 800, 1000}},\n    {\"group2\", {GroupStatus::ON, 1800, 2000}},\n    {\"group3\", {GroupStatus::ON, 500, 1000}},\n    {\"group4\", {GroupStatus::ON, 700, 1500}},\n    {\"group5\", {GroupStatus::ON, 1200, 1500}}\n  });\n  EXPECT_EQ(400, engine->getGroupFreeSpace());\n  EXPECT_EQ(408, engine->getFreeSpaceULimit());\n  EXPECT_EQ(392, engine->getFreeSpaceLLimit());\n  threshold_group_set expected_targets = {\"group3\", \"group4\"};  // Freebytes > 400\n  threshold_group_set expected_sources = {\"group1\", \"group2\", \"group5\"};\n  auto d = engine->get_data();\n  EXPECT_EQ(d.mGroupSizes.size(), 5);\n  EXPECT_EQ(d.mGroupsOverThreshold.size(), 3);\n  EXPECT_EQ(d.mGroupsUnderThreshold.size(), 2);\n  EXPECT_EQ(d.mGroupsUnderThreshold, expected_targets);\n  EXPECT_EQ(d.mGroupsOverThreshold, expected_sources);\n  engine_conf_t conf {{\"blocklisted_groups\", \"group3, group2\"}};\n  threshold_group_set expected_targets2 = {\"group4\"};\n  threshold_group_set expected_sources2 = {\"group1\", \"group5\"};\n  engine->configure(conf);\n  engine->recalculate();\n  engine->updateGroups();\n  EXPECT_EQ(433, engine->getGroupFreeSpace());\n  EXPECT_EQ(441, engine->getFreeSpaceULimit());\n  EXPECT_EQ(424, engine->getFreeSpaceLLimit());\n  auto d2 = engine->get_data();\n  EXPECT_EQ(d2.mGroupSizes.size(), 5);\n  EXPECT_EQ(d2.mGroupsOverThreshold.size(), 2);\n  EXPECT_EQ(d2.mGroupsUnderThreshold.size(), 1);\n  EXPECT_EQ(d2.mGroupsUnderThreshold, expected_targets2);\n  EXPECT_EQ(d2.mGroupsOverThreshold, expected_sources2);\n}\n"
  },
  {
    "path": "unit_tests/mgm/groupbalancer/GroupBalancerUtilsTests.cc",
    "content": "#include \"gtest/gtest.h\"\n#include \"mgm/groupbalancer/BalancerEngineUtils.hh\"\n#include \"mgm/groupbalancer/ConverterUtils.hh\"\n\nusing namespace eos::mgm::group_balancer;\n\nTEST(GroupBalancerUtils, Avg)\n{\n  EXPECT_DOUBLE_EQ(0, calculateAvg({}));\n  group_size_map m {{\"group1\", {75, 100}},\n    {\"group2\", {81, 100}},\n    {\"group3\", {85, 100}},\n    {\"group4\", {89, 100}},\n    {\"group5\", {95, 100}}};\n  EXPECT_DOUBLE_EQ(0.85, calculateAvg(m));\n  // Div By 0 for capacity isn't a concern as GroupBalancerInfo::fetch()\n  // validates capacity before filling\n  m.insert_or_assign(\"group2\", GroupSizeInfo{80, 100});\n  EXPECT_DOUBLE_EQ(.848, calculateAvg(m));\n  m.insert_or_assign(\"group4\", GroupSizeInfo{90, 100});\n  EXPECT_DOUBLE_EQ(0.85, calculateAvg(m));\n  m.insert_or_assign(\"group6\", GroupSizeInfo{85, 100});\n  EXPECT_DOUBLE_EQ(0.85, calculateAvg(m));\n  m.insert_or_assign(\"group7\", GroupSizeInfo{92, 100});\n  EXPECT_DOUBLE_EQ(0.86, calculateAvg(m));\n}\n\n\n\nTEST(GroupBalancerUtils, threshold)\n{\n  EXPECT_TRUE(is_valid_threshold(\"1\"));\n  EXPECT_TRUE(is_valid_threshold(\"0.01\"));\n  EXPECT_TRUE(is_valid_threshold(\"10.0\"));\n  EXPECT_FALSE(is_valid_threshold(\"-1\"));\n  EXPECT_FALSE(is_valid_threshold(\"0\"));\n  EXPECT_FALSE(is_valid_threshold(\"0.0f\"));\n  EXPECT_FALSE(is_valid_threshold(\"kitchensink\"));\n}\n\nTEST(GroupBalancerUtils, threshold_multi)\n{\n  EXPECT_TRUE(is_valid_threshold(\"1\", \"2\"));\n  EXPECT_TRUE(is_valid_threshold(\"0.01\", \"1\"));\n  EXPECT_TRUE(is_valid_threshold(\"10.0\", \"90.0\", \"1\"));\n  EXPECT_TRUE(is_valid_threshold(\"1\", \"2\", \"3\"));\n  EXPECT_FALSE(is_valid_threshold(\"1\", \"-1\"));\n  EXPECT_FALSE(is_valid_threshold(\"0\", \"1\"));\n  EXPECT_FALSE(is_valid_threshold(\"2\", \"0.0f\"));\n  EXPECT_FALSE(is_valid_threshold(\"10\", \"2\", \"kitchensink\"));\n}\n\n\nTEST(GroupBalancerUtils, extract_percent_value_simple)\n{\n  engine_conf_t conf;\n  conf[\"min_threshold\"]=\"5\";\n  EXPECT_DOUBLE_EQ(extract_percent_value(conf,\"min_threshold\"),0.05);\n}\n\nTEST(GroupBalancerUtils, extract_percent_value_null)\n{\n  engine_conf_t conf;\n  EXPECT_DOUBLE_EQ(extract_percent_value(conf,\"min_threshold\"),0.0);\n}\n\nTEST(GroupBalancerUtils, extract_percent_value_default)\n{\n  engine_conf_t conf;\n  EXPECT_DOUBLE_EQ(extract_percent_value(conf,\"min_threshold\",5.0),0.05);\n}\n\nTEST(GroupBalancerUtils, extract_commalist_value)\n{\n  engine_conf_t conf;\n  conf[\"blocklist_groups\"] = \"group1,group2, group3, group4\";\n  std::unordered_set<std::string> expected {\"group2\",\"group1\",\"group3\",\"group4\"};\n  EXPECT_EQ(expected,\n            extract_commalist_value(conf, \"blocklist_groups\"));\n\n  std::unordered_set<std::string> empty;\n  EXPECT_EQ(empty,\n            extract_commalist_value(conf, \"some key\"));\n}\n\n// a function that behaves like the skipFiles call in\n// getProcTransferNameAndSize\nstd::string fakeSkipFile(const SkipFileFn& skip_file_fn,\n                         std::string_view path)\n{\n  if (skip_file_fn && skip_file_fn(path)) {\n    return std::string(\"\");\n  }\n  return std::string(path);\n}\n\nTEST(GroupBalancerUtils, SkipFilesNullFilter)\n{\n  EXPECT_FALSE(NullFilter);\n  EXPECT_EQ(\"/proc/foo\",fakeSkipFile(NullFilter, \"/proc/foo\"));\n  EXPECT_EQ(\"/00001/bar\",fakeSkipFile(NullFilter, \"/00001/bar\"));\n}\n\nTEST(GroupBalancerUtils, SkipFiles)\n{\n  PrefixFilter procFilter{\"/proc/\"};\n  EXPECT_EQ(\"\", fakeSkipFile(procFilter, \"/proc/foo\"));\n  EXPECT_EQ(\"/000001/bar\",fakeSkipFile(procFilter, \"/000001/bar\"));\n}\n"
  },
  {
    "path": "unit_tests/mgm/groupbalancer/GroupsInfoFetcherTests.cc",
    "content": "#include \"gtest/gtest.h\"\n#include \"mgm/groupbalancer/GroupsInfoFetcher.hh\"\n\nusing eos::mgm::group_balancer::eosGroupsInfoFetcher;\nusing eos::mgm::group_balancer::GroupStatus;\n\nTEST(GroupsInfoFetcher, default_is_valid_status)\n{\n  eosGroupsInfoFetcher fetcher(\"default\");\n  ASSERT_TRUE(fetcher.is_valid_status(GroupStatus::ON));\n  ASSERT_FALSE(fetcher.is_valid_status(GroupStatus::DRAIN));\n  ASSERT_FALSE(fetcher.is_valid_status(GroupStatus::OFF));\n}\n\nTEST(GroupsInfoFetcher, drain_status)\n{\n  struct DrainStatusFilter {\n    bool operator()(GroupStatus status)\n    {\n      return status == GroupStatus::DRAIN || status == GroupStatus::ON;\n    }\n  };\n  eosGroupsInfoFetcher fetcher(\"default\", DrainStatusFilter{});\n  ASSERT_TRUE(fetcher.is_valid_status(GroupStatus::DRAIN));\n  ASSERT_TRUE(fetcher.is_valid_status(GroupStatus::ON));\n  ASSERT_FALSE(fetcher.is_valid_status(GroupStatus::OFF));\n}\n\nTEST(GroupsInfoFetcher, lambda_status)\n{\n  eosGroupsInfoFetcher fetcher(\"default\", [](GroupStatus status) {\n    return status == GroupStatus::DRAIN || status == GroupStatus::ON;\n  });\n  ASSERT_TRUE(fetcher.is_valid_status(GroupStatus::DRAIN));\n  ASSERT_TRUE(fetcher.is_valid_status(GroupStatus::ON));\n  ASSERT_FALSE(fetcher.is_valid_status(GroupStatus::OFF));\n}\n\n"
  },
  {
    "path": "unit_tests/mgm/groupbalancer/MinMaxBalancerEngineTests.cc",
    "content": "#include \"gtest/gtest.h\"\n#include \"mgm/groupbalancer/BalancerEngine.hh\"\n#include \"mgm/groupbalancer/MinMaxBalancerEngine.hh\"\n#include <memory>\n\n\nusing namespace eos::mgm::group_balancer;\n\nTEST(MinMaxBalancerEngine, configure)\n{\n  std::unique_ptr<BalancerEngine> engine =\n    std::make_unique<MinMaxBalancerEngine>();\n  engine->configure({{\"min_threshold\", \"5\"}});\n  auto e = static_cast<MinMaxBalancerEngine*>(engine.get());\n  EXPECT_DOUBLE_EQ(e->get_min_threshold(), 0.05);\n}\n\nTEST(MinMaxBalancerEngine, simple)\n{\n  std::unique_ptr<BalancerEngine> engine =\n    std::make_unique<MinMaxBalancerEngine>();\n  engine->configure({{\"min_threshold\", \"80\"}, {\"max_threshold\", \"90\"}});\n  engine->populateGroupsInfo({{\"group1\", {75, 100}},\n    {\"group2\", {81, 100}},\n    {\"group3\", {85, 100}},\n    {\"group4\", {89, 100}},\n    {\"group5\", {95, 100}}\n  });\n  auto d = engine->get_data();\n  EXPECT_EQ(d.mGroupSizes.size(), 5);\n  EXPECT_EQ(d.mGroupsOverThreshold.size(), 1);\n  EXPECT_EQ(d.mGroupsUnderThreshold.size(), 1);\n  auto [over, under] = engine->pickGroupsforTransfer();\n  EXPECT_EQ(over, \"group5\");\n  EXPECT_EQ(under, \"group1\");\n}\n\n\nTEST(MinMaxBalancerEngine, updatethreshold)\n{\n  std::unique_ptr<BalancerEngine> engine =\n    std::make_unique<MinMaxBalancerEngine>();\n  // If we pick at the point of item,\n  engine->configure({{\"min_threshold\", \"81\"}, {\"max_threshold\", \"89\"}});\n  engine->populateGroupsInfo({{\"group1\", {75, 100}},\n    {\"group2\", {81, 100}},\n    {\"group3\", {85, 100}},\n    {\"group4\", {89, 100}},\n    {\"group5\", {95, 100}}\n  });\n  {\n    auto d = engine->get_data();\n    EXPECT_EQ(d.mGroupSizes.size(), 5);\n    EXPECT_EQ(d.mGroupsOverThreshold.size(), 1);\n    EXPECT_EQ(d.mGroupsUnderThreshold.size(), 1);\n  }\n  engine->configure({{\"min_threshold\", \"82\"}, {\"max_threshold\", \"88\"}});\n  engine->updateGroups();\n  auto e = static_cast<MinMaxBalancerEngine*>(engine.get());\n  EXPECT_DOUBLE_EQ(e->get_min_threshold(), 0.82);\n  {\n    auto d = engine->get_data();\n    EXPECT_EQ(d.mGroupSizes.size(), 5);\n    EXPECT_EQ(d.mGroupsOverThreshold.size(), 2);\n    EXPECT_EQ(d.mGroupsUnderThreshold.size(), 2);\n    // FIXME this actually happens because we do a floating point compare in\n    // this case using diffWithAvg will be slightly greater than threshold at\n    // boundary values when doing the subtraction\n    threshold_group_set grps_over = {\"group5\", \"group4\"};\n    threshold_group_set grps_under = {\"group1\", \"group2\"};\n    EXPECT_EQ(d.mGroupsOverThreshold, grps_over);\n    EXPECT_EQ(d.mGroupsUnderThreshold, grps_under);\n  }\n}\n"
  },
  {
    "path": "unit_tests/mgm/groupbalancer/StdDevBalancerEngineTests.cc",
    "content": "#include \"gtest/gtest.h\"\n#include \"mgm/groupbalancer/BalancerEngine.hh\"\n#include \"mgm/groupbalancer/StdDevBalancerEngine.hh\"\n#include \"mgm/groupbalancer/BalancerEngineUtils.hh\"\n#include <memory>\n\nusing namespace eos::mgm::group_balancer;\n\nTEST(StdDevBalancerEngine, configure)\n{\n  std::unique_ptr<BalancerEngine> engine =\n    std::make_unique<StdDevBalancerEngine>();\n  engine->configure({{\"min_threshold\", \"5\"}, {\"max_threshold\", \"5\"}});\n  auto e = static_cast<StdDevBalancerEngine*>(engine.get());\n  EXPECT_DOUBLE_EQ(e->get_min_threshold(), 0.05);\n}\n\nTEST(StdDevBalancerEngine, simple)\n{\n  std::unique_ptr<BalancerEngine> engine =\n    std::make_unique<StdDevBalancerEngine>();\n  engine->configure({{\"min_threshold\", \"5\"}, {\"max_threshold\", \"5\"}});\n  engine->populateGroupsInfo({{\"group1\", {75, 100}},\n    {\"group2\", {81, 100}},\n    {\"group3\", {85, 100}},\n    {\"group4\", {89, 100}},\n    {\"group5\", {95, 100}}\n  });\n  auto d = engine->get_data();\n  EXPECT_NEAR(calculateAvg(d.mGroupSizes), 0.85, 0.0000001);\n  EXPECT_EQ(d.mGroupSizes.size(), 5);\n  EXPECT_EQ(d.mGroupsOverThreshold.size(), 1);\n  EXPECT_EQ(d.mGroupsUnderThreshold.size(), 1);\n  auto [over, under] = engine->pickGroupsforTransfer();\n  EXPECT_EQ(over, \"group5\");\n  EXPECT_EQ(under, \"group1\");\n}\n\n\nTEST(StdDevBalancerEngine, updatethreshold)\n{\n  std::unique_ptr<BalancerEngine> engine =\n    std::make_unique<StdDevBalancerEngine>();\n  engine->configure({{\"min_threshold\", \"5\"}, {\"max_threshold\", \"5\"}});\n  engine->populateGroupsInfo({{\"group1\", {75, 100}},\n    {\"group2\", {81, 100}},\n    {\"group3\", {85, 100}},\n    {\"group4\", {89, 100}},\n    {\"group5\", {95, 100}}\n  });\n  {\n    auto d = engine->get_data();\n    EXPECT_NEAR(calculateAvg(d.mGroupSizes), 0.85, 0.0000001);\n    EXPECT_EQ(d.mGroupSizes.size(), 5);\n    EXPECT_EQ(d.mGroupsOverThreshold.size(), 1);\n    EXPECT_EQ(d.mGroupsUnderThreshold.size(), 1);\n  }\n  // If we pick at the point of item,\n  engine->configure({{\"min_threshold\", \"4\"}, {\"max_threshold\", \"4\"}});\n  engine->updateGroups();\n  auto e = static_cast<StdDevBalancerEngine*>(engine.get());\n  EXPECT_DOUBLE_EQ(e->get_min_threshold(), 0.04);\n  EXPECT_DOUBLE_EQ(e->get_max_threshold(), 0.04);\n  {\n    auto d = engine->get_data();\n    EXPECT_NEAR(calculateAvg(d.mGroupSizes), 0.85, 0.0000001);\n    EXPECT_EQ(d.mGroupSizes.size(), 5);\n    EXPECT_EQ(d.mGroupsOverThreshold.size(), 2);\n    EXPECT_EQ(d.mGroupsUnderThreshold.size(), 1);\n    // FIXME this actually happens because we do a floating point compare in\n    // this case using diffWithAvg will be slightly greater than threshold at\n    // boundary values when doing the subtraction\n    threshold_group_set grps_over = {\"group5\", \"group4\"};\n    threshold_group_set grps_under = {\"group1\"};\n    EXPECT_EQ(d.mGroupsOverThreshold, grps_over);\n    EXPECT_EQ(d.mGroupsUnderThreshold, grps_under);\n  }\n}\n\nTEST(StdDevBalancerEngine, multithreshold)\n{\n  std::unique_ptr<BalancerEngine> engine =\n    std::make_unique<StdDevBalancerEngine>();\n  engine->configure({{\"min_threshold\", \"5\"}, {\"max_threshold\", \"5\"}});\n  engine->populateGroupsInfo({{\"group1\", {75, 100}},\n    {\"group2\", {81, 100}},\n    {\"group3\", {85, 100}},\n    {\"group4\", {89, 100}},\n    {\"group5\", {95, 100}}\n  });\n  {\n    auto d = engine->get_data();\n    EXPECT_NEAR(calculateAvg(d.mGroupSizes), 0.85, 0.0000001);\n    EXPECT_EQ(d.mGroupSizes.size(), 5);\n    EXPECT_EQ(d.mGroupsOverThreshold.size(), 1);\n    EXPECT_EQ(d.mGroupsUnderThreshold.size(), 1);\n  }\n  // If we pick at the point of item,\n  engine->configure({{\"min_threshold\", \"4\"}, {\"max_threshold\", \"5\"}});\n  engine->updateGroups();\n  auto e = static_cast<StdDevBalancerEngine*>(engine.get());\n  EXPECT_DOUBLE_EQ(e->get_min_threshold(), 0.04);\n  EXPECT_DOUBLE_EQ(e->get_max_threshold(), 0.05);\n  {\n    auto d = engine->get_data();\n    EXPECT_NEAR(calculateAvg(d.mGroupSizes), 0.85, 0.0000001);\n    EXPECT_EQ(d.mGroupSizes.size(), 5);\n    EXPECT_EQ(d.mGroupsOverThreshold.size(), 1);\n    EXPECT_EQ(d.mGroupsUnderThreshold.size(), 1);\n    threshold_group_set grps_over = {\"group5\"};\n    threshold_group_set grps_under = {\"group1\"};\n    EXPECT_EQ(d.mGroupsOverThreshold, grps_over);\n    EXPECT_EQ(d.mGroupsUnderThreshold, grps_under);\n  }\n}\n"
  },
  {
    "path": "unit_tests/mgm/groupbalancer/StdDrainerTests.cc",
    "content": "#include \"common/utils/ContainerUtils.hh\"\n#include \"mgm/groupbalancer/BalancerEngine.hh\"\n#include \"mgm/groupbalancer/BalancerEngineUtils.hh\"\n#include \"mgm/groupbalancer/StdDrainerEngine.hh\"\n#include \"gtest/gtest.h\"\n#include <memory>\n\nusing namespace eos::mgm::group_balancer;\n\nTEST(StdDrainerEngine, defaultconf)\n{\n  auto engine = std::make_unique<StdDrainerEngine>();\n  engine->configure({});\n  EXPECT_DOUBLE_EQ(static_cast<StdDrainerEngine*>(engine.get())->get_threshold(),\n                   0.0001);\n}\n\n\nTEST(StdDrainerEngine, simple)\n{\n  auto engine = std::make_unique<StdDrainerEngine>();\n  engine_conf_t conf {{\"threshold\", \"2\"}};\n  engine->configure(conf);\n  engine->populateGroupsInfo({{\"group0\", {GroupStatus::DRAIN, 95, 100}},\n    {\"group1\", {GroupStatus::ON, 80, 100}},\n    {\"group2\", {GroupStatus::ON, 86, 100}},\n    {\"group4\", {GroupStatus::ON, 80, 100}}\n  });\n  ASSERT_EQ(static_cast<StdDrainerEngine*>(engine.get())->get_threshold(), 0.02);\n  threshold_group_set expected_targets = {\"group1\", \"group4\"};\n  auto d = engine->get_data();\n  EXPECT_EQ(d.mGroupSizes.size(), 4);\n  EXPECT_EQ(d.mGroupsOverThreshold.size(), 1);\n  EXPECT_EQ(d.mGroupsUnderThreshold.size(), 2);\n  EXPECT_EQ(d.mGroupsUnderThreshold, expected_targets);\n}\n\nTEST(StdDrainerEngine, RRTestsimple)\n{\n  auto engine = std::make_unique<StdDrainerEngine>();\n  engine_conf_t conf {{\"threshold\", \"0\"}};\n  engine->configure(conf);\n  engine->populateGroupsInfo({{\"group0\", {GroupStatus::DRAIN, 95, 100}},\n    {\"group1\", {GroupStatus::ON, 80, 100}},\n    {\"group2\", {GroupStatus::ON, 86, 100}},\n    {\"group3\", {GroupStatus::ON, 80, 100}},\n    {\"group4\", {GroupStatus::DRAIN, 99, 100}},\n    {\"group5\", {GroupStatus::DRAIN, 20, 100}}\n  });\n  // These are some basic assumptions to ensure that we're consistent\n  ASSERT_EQ(static_cast<StdDrainerEngine*>(engine.get())->get_threshold(), 0);\n  threshold_group_set expected_sources = {\"group0\", \"group4\", \"group5\"};\n  threshold_group_set expected_targets = {\"group1\", \"group2\", \"group3\"};\n  auto d = engine->get_data();\n  EXPECT_EQ(d.mGroupSizes.size(), 6);\n  EXPECT_EQ(d.mGroupsOverThreshold.size(), 3);\n  EXPECT_EQ(d.mGroupsUnderThreshold.size(), 3);\n  EXPECT_EQ(d.mGroupsUnderThreshold, expected_targets);\n  EXPECT_EQ(d.mGroupsOverThreshold, expected_sources);\n  auto [src, tgt] = engine->pickGroupsforTransfer(0);\n  EXPECT_EQ(\"group0\", src);\n  EXPECT_EQ(\"group1\", tgt);\n  std::tie(src, tgt) = engine->pickGroupsforTransfer(2);\n  EXPECT_EQ(\"group5\", src);\n  EXPECT_EQ(\"group3\", tgt);\n}\n\n\nTEST(StdDrainerEngine, RRTestsLoop)\n{\n  auto engine = std::make_unique<StdDrainerEngine>();\n  engine_conf_t conf {{\"threshold\", \"0\"}};\n  engine->configure(conf);\n  engine->populateGroupsInfo({{\"group0\", {GroupStatus::DRAIN, 95, 100}},\n    {\"group1\", {GroupStatus::ON, 80, 100}},\n    {\"group2\", {GroupStatus::ON, 86, 100}},\n    {\"group3\", {GroupStatus::ON, 80, 100}},\n    {\"group4\", {GroupStatus::DRAIN, 99, 100}},\n    {\"group5\", {GroupStatus::DRAIN, 20, 100}}\n  });\n  std::string src, tgt;\n  std::vector<std::string> sources = {\"group0\", \"group4\", \"group5\"};\n  std::vector<std::string> targets = {\"group1\", \"group2\", \"group3\"};\n  uint8_t seed {0};\n\n  // Simulate a logic similar to the inf. loop of prepareTransfers\n  // We'll loop continously as soon as we find some free slots to push transfers to\n  // So we need to ensure that we constantly wrap around and not fixate on 0-indices\n  // between the various runs of the inner loop!\n  for (int i = 0; i < 10; i++) {\n    for (int j = 0; j < 500; j++) {\n      uint8_t curr_seed = seed;\n      std::tie(src, tgt) = engine->pickGroupsforTransfer(seed++);\n      EXPECT_EQ(eos::common::pickIndexRR(sources, curr_seed), src);\n      EXPECT_EQ(eos::common::pickIndexRR(targets, curr_seed), tgt);\n    }\n  }\n\n  uint64_t max_uint8_t = std::numeric_limits<uint8_t>::max() + 1;\n  EXPECT_EQ(seed, 5000 % max_uint8_t);\n}\n\nTEST(StdDrainerTests, pickFS)\n{\n  auto engine = std::make_unique<StdDrainerEngine>();\n  engine_conf_t conf {{\"threshold\", \"0\"}};\n  engine->configure(conf);\n  engine->populateGroupsInfo({{\"group0\", {GroupStatus::DRAIN, 95, 100}},\n    {\"group1\", {GroupStatus::ON, 80, 100}},\n    {\"group2\", {GroupStatus::ON, 86, 100}},\n    {\"group3\", {GroupStatus::ON, 80, 100}},\n    {\"group4\", {GroupStatus::DRAIN, 99, 100}},\n    {\"group5\", {GroupStatus::DRAIN, 20, 100}}\n  });\n  std::string src, tgt;\n  std::vector<std::string> sources = {\"group0\", \"group4\", \"group5\"};\n  std::vector<std::string> targets = {\"group1\", \"group2\", \"group3\"};\n  uint8_t seed {0};\n  std::map<std::string, std::vector<uint64_t>> FSMap {\n    {\"group0\", {1, 2, 3, 10}},\n    {\"group4\", {4, 5, 6, 11}},\n    {\"group5\", {7, 9, 9, 12}}};\n  std::map<std::string, uint8_t> mGroupFSSeed;\n  std::tie(src, tgt) = engine->pickGroupsforTransfer(seed++);\n  EXPECT_EQ(eos::common::pickIndexRR(FSMap[src],\n                                     mGroupFSSeed[src]++), 1);\n  std::tie(src, tgt) = engine->pickGroupsforTransfer(seed++);\n  EXPECT_EQ(eos::common::pickIndexRR(FSMap[src],\n                                     mGroupFSSeed[src]++), 4);\n  std::tie(src, tgt) = engine->pickGroupsforTransfer(4);\n  ASSERT_EQ(\"group4\", src);\n  EXPECT_EQ(eos::common::pickIndexRR(FSMap[src],\n                                     mGroupFSSeed[src]++), 5);\n  // current seed is at 6, add by 1 more to increment by 2,\n  // and go to the last element of group4 map\n  EXPECT_EQ(eos::common::pickIndexRR(FSMap[src],\n                                     mGroupFSSeed[src] + 1), 11);\n  //incr by 1 again, loop back\n  EXPECT_EQ(eos::common::pickIndexRR(FSMap[src],\n                                     mGroupFSSeed[src] + 2), 4);\n}\n"
  },
  {
    "path": "unit_tests/mgm/groupdrainer/DrainProgressTrackerTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: DrainProgressTrackerTests.cc\n// Author: Abhishek Lekshmanan - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/groupdrainer/DrainProgressTracker.hh\"\n#include \"gtest/gtest.h\"\n\nusing eos::mgm::DrainProgressTracker;\n\nTEST(DrainProgressTracker, SetTotalFiles)\n{\n  DrainProgressTracker tracker;\n  int fsid = 1;\n  tracker.setTotalFiles(fsid, 100);\n  EXPECT_EQ(100, tracker.getTotalFiles(fsid));\n  EXPECT_EQ(0, tracker.getFileCounter(fsid));\n  tracker.increment(fsid);\n  EXPECT_FLOAT_EQ(1, tracker.getDrainStatus(fsid));\n  EXPECT_EQ(1, tracker.getFileCounter(fsid));\n  // set to a lower value, will be ignored, this happens as the drain progresses\n  tracker.setTotalFiles(fsid, 50);\n  EXPECT_EQ(100, tracker.getTotalFiles(fsid));\n  EXPECT_FLOAT_EQ(1, tracker.getDrainStatus(fsid));\n  // Set to a higher value, will be set\n  tracker.setTotalFiles(fsid, 200);\n  EXPECT_EQ(200, tracker.getTotalFiles(fsid));\n  EXPECT_FLOAT_EQ(0.5, tracker.getDrainStatus(fsid));\n  tracker.increment(fsid); // Now 2 files out of 200!\n  EXPECT_EQ(2, tracker.getFileCounter(fsid));\n  EXPECT_FLOAT_EQ(1, tracker.getDrainStatus(fsid));\n}\n\nTEST(DrainProgressTracker, Deletions)\n{\n  DrainProgressTracker tracker;\n  int fsid = 1;\n  tracker.setTotalFiles(fsid, 100);\n  // No file entry yet, should return 0;\n  EXPECT_FLOAT_EQ(0, tracker.getDrainStatus(fsid));\n  tracker.increment(fsid);\n  EXPECT_FLOAT_EQ(1, tracker.getDrainStatus(fsid));\n  // set to a lower value, will be ignored, this happens as the drain progresses\n  tracker.setTotalFiles(fsid, 50);\n  tracker.dropFsid(fsid);\n  EXPECT_FLOAT_EQ(0, tracker.getDrainStatus(fsid));\n}\n\nTEST(DrainProgressTracker, NullTests)\n{\n  DrainProgressTracker tracker;\n  int fsid=1;\n  tracker.setTotalFiles(fsid,0);\n  tracker.increment(fsid);\n  EXPECT_FLOAT_EQ(0, tracker.getDrainStatus(fsid));\n  tracker.setTotalFiles(fsid, 1);\n  EXPECT_FLOAT_EQ(100, tracker.getDrainStatus(fsid));\n}\n\nTEST(DrainProgressTracker, InvalidFS)\n{\n  DrainProgressTracker tracker;\n  int fsid = 100;\n  EXPECT_FLOAT_EQ(0, tracker.getDrainStatus(fsid));\n  tracker.increment(fsid);\n  EXPECT_FLOAT_EQ(0, tracker.getDrainStatus(fsid));\n  tracker.setTotalFiles(fsid, 0);\n  EXPECT_FLOAT_EQ(0, tracker.getDrainStatus(fsid));\n  tracker.setTotalFiles(fsid, 1);\n  EXPECT_FLOAT_EQ(100, tracker.getDrainStatus(fsid));\n}"
  },
  {
    "path": "unit_tests/mgm/groupdrainer/GroupDrainerRetry.cc",
    "content": "#include \"gtest/gtest.h\"\n#include \"mgm/groupdrainer/RetryTracker.hh\"\n\n\nTEST(RetryTracker, basic)\n{\n  using namespace eos::mgm;\n  using namespace std::chrono_literals;\n  RetryTracker tracker;\n  std::chrono::time_point<std::chrono::steady_clock> time0 {};\n  ASSERT_EQ(tracker.count, 0);\n  EXPECT_EQ(tracker.last_run_time, time0);\n  ASSERT_TRUE(tracker.need_update());\n  tracker.update();\n  ASSERT_EQ(tracker.count, 1);\n  EXPECT_NE(tracker.last_run_time, time0);\n  eos::common::SteadyClock test_clock(true);\n  ASSERT_FALSE(tracker.need_update(900));\n  test_clock.advance(eos::common::SteadyClock::SecondsSinceEpoch(\n                       std::chrono::steady_clock::now()));\n  test_clock.advance(std::chrono::seconds(902));\n  ASSERT_TRUE(tracker.need_update(900, &test_clock));\n}\n"
  },
  {
    "path": "unit_tests/mgm/groupdrainer/GroupDrainerTests.cc",
    "content": "#include \"gtest/gtest.h\"\n#include \"mgm/groupdrainer/GroupDrainer.hh\"\n\nusing eos::mgm::GroupDrainer;\nusing eos::mgm::GroupStatus;\nusing eos::mgm::fsutils::FsidStatus;\nusing eos::mgm::fsutils::fs_status_map_t;\nusing drain_fs_map_t = eos::mgm::GroupDrainer::drain_fs_map_t;\n\nTEST(GroupDrainerStatus, DrainComplete)\n{\n  fs_status_map_t fsmap {{\n      1, FsidStatus{\n        eos::common::ActiveStatus::kOnline,\n        eos::common::DrainStatus::kDrained}\n    },\n    {\n      2, FsidStatus{\n        eos::common::ActiveStatus::kOnline,\n        eos::common::DrainStatus::kDrained}\n    }\n  };\n  auto status = GroupDrainer::checkGroupDrainStatus(fsmap);\n  ASSERT_EQ(status, GroupStatus::DRAINCOMPLETE);\n  // Populate with a single offline drained entity\n  fsmap.insert_or_assign(4, FsidStatus{eos::common::ActiveStatus::kOffline,\n                                       eos::common::DrainStatus::kDrained});\n  ASSERT_EQ(GroupStatus::OFF,\n            GroupDrainer::checkGroupDrainStatus(fsmap));\n  // Further entries won't change the offline outcome\n  fsmap.insert_or_assign(3, FsidStatus{eos::common::ActiveStatus::kOnline,\n                                       eos::common::DrainStatus::kDrainFailed});\n  ASSERT_EQ(GroupStatus::OFF,\n            GroupDrainer::checkGroupDrainStatus(fsmap));\n  // Bring fs back online, since we've a single failed and rest drained, it\n  // is a failed drain\n  fsmap.insert_or_assign(4, FsidStatus{eos::common::ActiveStatus::kOnline,\n                                       eos::common::DrainStatus::kDrained});\n  ASSERT_EQ(GroupStatus::DRAINFAILED,\n            GroupDrainer::checkGroupDrainStatus(fsmap));\n}\n\nTEST(GroupDrainerStatus, Offline)\n{\n  fs_status_map_t fsmap {{\n      1, FsidStatus{\n        eos::common::ActiveStatus::kOnline,\n        eos::common::DrainStatus::kDrained}\n    },\n    {\n      2, FsidStatus{\n        eos::common::ActiveStatus::kOnline,\n        eos::common::DrainStatus::kDrained}\n    },\n    {\n      3, FsidStatus{\n        eos::common::ActiveStatus::kOffline,\n        eos::common::DrainStatus::kDrained}\n    }\n  };\n  auto status = GroupDrainer::checkGroupDrainStatus(fsmap);\n  ASSERT_EQ(status, GroupStatus::OFF);\n  fsmap.insert_or_assign(4, FsidStatus{eos::common::ActiveStatus::kOnline,\n                                       eos::common::DrainStatus::kDrainFailed});\n  fsmap.insert_or_assign(5, FsidStatus{eos::common::ActiveStatus::kUndefined,\n                                       eos::common::DrainStatus::kDrainExpired});\n  ASSERT_EQ(GroupStatus::OFF,\n            GroupDrainer::checkGroupDrainStatus(fsmap));\n}\n\nTEST(GroupDrainerStatus, Failed)\n{\n  fs_status_map_t fsmap {{\n      1, FsidStatus{\n        eos::common::ActiveStatus::kOnline,\n        eos::common::DrainStatus::kDrained}\n    },\n    {\n      2, FsidStatus{\n        eos::common::ActiveStatus::kOnline,\n        eos::common::DrainStatus::kDrained}\n    },\n    {\n      3, FsidStatus{\n        eos::common::ActiveStatus::kOnline,\n        eos::common::DrainStatus::kDrainFailed}\n    }\n  };\n  auto status = GroupDrainer::checkGroupDrainStatus(fsmap);\n  ASSERT_EQ(status, GroupStatus::DRAINFAILED);\n  // Not sure whether this is practical, but currently the method doesn't\n  // specially parse this state\n  fsmap.insert_or_assign(4, FsidStatus{eos::common::ActiveStatus::kUndefined,\n                                       eos::common::DrainStatus::kDrained});\n  ASSERT_EQ(GroupStatus::DRAINFAILED,\n            GroupDrainer::checkGroupDrainStatus(fsmap));\n}\n\nTEST(GroupDrainerStatus, Online)\n{\n  // Catchall for everything else\n  fs_status_map_t fsmap {{\n      1, FsidStatus{\n        eos::common::ActiveStatus::kOnline,\n        eos::common::DrainStatus::kDrained}\n    },\n    {\n      2, FsidStatus{\n        eos::common::ActiveStatus::kOnline,\n        eos::common::DrainStatus::kDrained}\n    },\n    {\n      3, FsidStatus{\n        eos::common::ActiveStatus::kOnline,\n        eos::common::DrainStatus::kDrainFailed}\n    }\n  };\n  auto status = GroupDrainer::checkGroupDrainStatus(fsmap);\n  ASSERT_EQ(status, GroupStatus::DRAINFAILED);\n  // Introduce one of the unknown drain states\n  fsmap.insert_or_assign(4, FsidStatus{eos::common::ActiveStatus::kOnline,\n                                       eos::common::DrainStatus::kDrainExpired});\n  ASSERT_EQ(GroupStatus::ON,\n            GroupDrainer::checkGroupDrainStatus(fsmap));\n}\n\nTEST(GroupDrainer, isDrainFSMapEmpty)\n{\n  ASSERT_TRUE(GroupDrainer::isDrainFSMapEmpty({}));\n  drain_fs_map_t m = {{\"group1\", {}},\n    {\"group2\", {}}\n  };\n  ASSERT_TRUE(GroupDrainer::isDrainFSMapEmpty(m));\n  drain_fs_map_t m2 = {{\"group1\", {}},\n    {\"group2\", {}},\n    {\"group3\", {10}}\n  };\n  ASSERT_FALSE(GroupDrainer::isDrainFSMapEmpty(m2));\n}"
  },
  {
    "path": "unit_tests/mgm/http/HttpServerTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: HttpServerTests.cc\n// Author: Elvin Sindrilaru <esindril@cern.ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2022 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"gtest/gtest.h\"\n#include <XrdOuc/XrdOucEnv.hh>\n#define IN_TEST_HARNESS\n#include \"mgm/http/HttpServer.hh\"\n#undef IN_TEST_HARNESS\n\n//------------------------------------------------------------------------------\n// Test parsing for HTTP requests where path might contain opaque data which\n// represents the authorization token or the HTTP headers include this info\n//------------------------------------------------------------------------------\nTEST(HttpServer, ParsePathAndToken)\n{\n  using eos::mgm::HttpServer;\n  std::string path;\n  std::unique_ptr<XrdOucEnv> env_opaque;\n  std::map<std::string, std::string> norm_hdrs;\n  ASSERT_FALSE(HttpServer::BuildPathAndEnvOpaque(norm_hdrs, path, env_opaque));\n  ASSERT_TRUE(path.empty());\n  ASSERT_EQ(nullptr, env_opaque);\n  norm_hdrs = {{\"dummy\", \"test\"}};\n  ASSERT_FALSE(HttpServer::BuildPathAndEnvOpaque(norm_hdrs, path, env_opaque));\n  ASSERT_TRUE(path.empty());\n  ASSERT_EQ(nullptr, env_opaque);\n  norm_hdrs = {{\"xrd-http-fullresource\", \"/eos/dev/file.dat\"}};\n  ASSERT_TRUE(HttpServer::BuildPathAndEnvOpaque(norm_hdrs, path, env_opaque));\n  ASSERT_FALSE(path.empty());\n  ASSERT_TRUE(env_opaque != nullptr);\n  ASSERT_EQ(nullptr, env_opaque->Get(\"authz\"));\n  // Authorization appeneded as opaque information\n  norm_hdrs = {{\"xrd-http-fullresource\", \"/eos/dev/file1.dat?authz=deadbeef\"}};\n  ASSERT_TRUE(HttpServer::BuildPathAndEnvOpaque(norm_hdrs, path, env_opaque));\n  EXPECT_EQ(\"/eos/dev/file1.dat\", path);\n  ASSERT_STREQ(\"deadbeef\", env_opaque->Get(\"authz\"));\n  // Authorization appened as part of the http headers\n  norm_hdrs = {{\"xrd-http-fullresource\", \"/eos/dev/file2.dat\"},\n    {\"authorization\", \"dabadaba\"}\n  };\n  ASSERT_TRUE(HttpServer::BuildPathAndEnvOpaque(norm_hdrs, path, env_opaque));\n  EXPECT_EQ(\"/eos/dev/file2.dat\", path);\n  ASSERT_STREQ(\"dabadaba\", env_opaque->Get(\"authz\"));\n  // Fail if both http header and opaque info with authorization present\n  norm_hdrs = {{\"xrd-http-fullresource\", \"/eos/dev/file3.dat?authz=abbaabba\"},\n    {\"authorization\", \"dabadaba\"}\n  };\n  ASSERT_FALSE(HttpServer::BuildPathAndEnvOpaque(norm_hdrs, path, env_opaque));\n  // Authorization appeneded as opaque information plus extra opaque data\n  norm_hdrs = {{\"xrd-http-fullresource\", \"/eos/dev/file4.dat?authz=deadbeef&test=dummy\"}};\n  ASSERT_TRUE(HttpServer::BuildPathAndEnvOpaque(norm_hdrs, path, env_opaque));\n  EXPECT_EQ(\"/eos/dev/file4.dat\", path);\n  ASSERT_STREQ(\"deadbeef\", env_opaque->Get(\"authz\"));\n  ASSERT_STREQ(\"dummy\", env_opaque->Get(\"test\"));\n  ASSERT_STREQ(\"http\",env_opaque->Get(\"eos.app\"));\n  // eos.app provided by client via opaque infos, should either be http or http/xyz\n  norm_hdrs = {{\"xrd-http-fullresource\", \"/eos/dev/file4.dat?authz=deadbeef&test=dummy&eos.app=wizz\"}};\n  ASSERT_TRUE(HttpServer::BuildPathAndEnvOpaque(norm_hdrs, path, env_opaque));\n  ASSERT_STREQ(\"http/wizz\", env_opaque->Get(\"eos.app\"));\n  // eos.app provided by client via opaque infos, should either be http or http/xyz\n  norm_hdrs = {{\"xrd-http-fullresource\", \"/eos/dev/file4.dat?eos.app=test&authz=deadbeef&test=dummy&eos.app=wizz\"}};\n  ASSERT_TRUE(HttpServer::BuildPathAndEnvOpaque(norm_hdrs, path, env_opaque));\n  ASSERT_STREQ(\"http/wizz\", env_opaque->Get(\"eos.app\"));\n}\n\nstatic std::map<std::string,std::pair<std::string,std::string>> fullPathToPathAndOpaque = {\n    {\"\",{\"\",\"\"}},\n    {\"/eos/file.dat\",{\"/eos/file.dat\",\"\"}},\n    {\"/eos/file.dat?\",{\"/eos/file.dat\",\"\"}},\n    {\"/eos/file.dat?testopaque=1\",{\"/eos/file.dat\",\"testopaque=1\"}},\n    {\"/eos/file.dat?testopaque=1&authz=qwerty&test=2\",{\"/eos/file.dat\",\"testopaque=1&authz=qwerty&test=2\"}},\n};\n\nTEST(HttpServer, ExtractPathAndOpaque) {\n  for(const auto & [fullpath, pathAndOpaquePair]: fullPathToPathAndOpaque) {\n    std::string extractedPath;\n    std::string extractedOpaque;\n    eos::mgm::HttpServer::extractPathAndOpaque(fullpath,extractedPath, extractedOpaque);\n    ASSERT_EQ(pathAndOpaquePair.first,extractedPath);\n    ASSERT_EQ(pathAndOpaquePair.second,extractedOpaque);\n  }\n}\n\nstatic std::map<std::string,std::string> fullPathToOpaque = {\n    {\"\",\"\"},\n    {\"/eos/lhcb/test/?eos.ruid=0\",\"eos.ruid=0\"},\n    {\"/eos/lhcb/\",\"\"},\n    {\"/eos/file.dat?\",\"\"},\n    {\"/eos/lhcb/passwd.txt?eos.test=0&oss.test=18&test=3\",\"eos.test=0&oss.test=18&test=3\"},\n    {\"/eos/lhcb/passwd.txt?authz=azerty&eos.test=0&oss.test=18&test=3\",\"eos.test=0&oss.test=18&test=3\"},\n    {\"/eos/lhcb/passwd.txt?eos.test=0&oss.test=18&authz=azerty&test=3\",\"eos.test=0&oss.test=18&test=3\"},\n    {\"/eos/lhcb/passwd.txt?eos.test=0&oss.test=18&test=3&authz=azerty\",\"eos.test=0&oss.test=18&test=3\"}\n};\n\nTEST(HttpServer, ExtractOpaqueWithoutAuthz) {\n  for (const auto & [fullpath,opaque]: fullPathToOpaque) {\n    std::string extractedOpaque;\n    eos::mgm::HttpServer::extractOpaqueWithoutAuthz(fullpath,extractedOpaque);\n    ASSERT_EQ(opaque,extractedOpaque);\n  }\n}\n"
  },
  {
    "path": "unit_tests/mgm/http/rest-api/tape/JsonCPPTapeModelBuilderTest.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file JsonCPPTapeModelBuilderTest.cc\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"JsonCPPTapeModelBuilderTest.hh\"\n#include \"mgm/http/rest-api/exception/JsonValidationException.hh\"\n#include \"mgm/http/rest-api/json/tape/TapeModelBuilders.hh\"\n\nusing namespace eos::mgm::rest;\n\nstd::string JsonCPPTapeModelBuilderTest::restApiEndpointID = \"REST_API_ENDPOINT_ID\";\n\nTEST_F(JsonCPPTapeModelBuilderTest, createStageRequestModelBuilderTest_jsonNotValid) {\n  CreateStageRequestModelBuilder builder(restApiEndpointID);\n  std::string json = \"jsonNotValid\";\n  ASSERT_THROW(builder.buildFromJson(json), JsonValidationException);\n}\n\nTEST_F(JsonCPPTapeModelBuilderTest, createStageRequestModelBuilderTest_emptyJson) {\n  CreateStageRequestModelBuilder builder(restApiEndpointID);\n  std::string json = \"{}\";\n  ASSERT_THROW(builder.buildFromJson(json), JsonValidationException);\n}\n\nTEST_F(JsonCPPTapeModelBuilderTest, createStageRequestModelBuilderTest_wrongField)\n{\n  CreateStageRequestModelBuilder builder(restApiEndpointID);\n  std::string json = \"{\\\"wrong_field\\\":[]}\";\n  ASSERT_THROW(builder.buildFromJson(json), JsonValidationException);\n}\n\nTEST_F(JsonCPPTapeModelBuilderTest, createStageRequestModelBuilderTest_wrongFormat1)\n{\n  CreateStageRequestModelBuilder builder(restApiEndpointID);\n  std::string json = \"{\\\"\" + CreateStageRequestModelBuilder::FILES_KEY_NAME + \"\\\":12345}\";\n  ASSERT_THROW(builder.buildFromJson(json), JsonValidationException);\n}\n\nTEST_F(JsonCPPTapeModelBuilderTest, createStageRequestModelBuilderTest_wrongFormat2)\n{\n  CreateStageRequestModelBuilder builder(restApiEndpointID);\n  std::string json = \"{\\\"\" + CreateStageRequestModelBuilder::FILES_KEY_NAME + \"\\\":[]}\";\n  ASSERT_THROW(builder.buildFromJson(json), JsonValidationException);\n}\n\nTEST_F(JsonCPPTapeModelBuilderTest, createStageRequestModelBuilderTest_wrongFormat3)\n{\n  CreateStageRequestModelBuilder builder(restApiEndpointID);\n  std::string json = \"{\\\"\" + CreateStageRequestModelBuilder::FILES_KEY_NAME + \"\\\":[1,2,3]}\";\n  ASSERT_THROW(builder.buildFromJson(json), JsonValidationException);\n}\n\nTEST_F(JsonCPPTapeModelBuilderTest, createStageRequestModelBuilderTest_wrongFormat4)\n{\n  CreateStageRequestModelBuilder builder(restApiEndpointID);\n  std::string json = \"{\\\"\" + CreateStageRequestModelBuilder::FILES_KEY_NAME + \"\\\":[{\\\"\" +\n         CreateStageRequestModelBuilder::PATH_KEY_NAME + \"\\\":1234}]\";\n  ASSERT_THROW(builder.buildFromJson(json), JsonValidationException);\n}\n\nTEST_F(JsonCPPTapeModelBuilderTest, createStageRequestModelBuilderTest_correctFormat)\n{\n  CreateStageRequestModelBuilder builder(restApiEndpointID);\n  std::string json = \"{\\\"\" + CreateStageRequestModelBuilder::FILES_KEY_NAME + \"\\\":[{\\\"\" +\n         CreateStageRequestModelBuilder::PATH_KEY_NAME +\n         \"\\\":\\\"/path/to/file.txt\\\"},{\\\"\" +\n         CreateStageRequestModelBuilder::PATH_KEY_NAME +\n         \"\\\":\\\"/path/to/file2.txt\\\"}]}\";\n  ASSERT_NO_THROW(builder.buildFromJson(json));\n}\n\nTEST_F(JsonCPPTapeModelBuilderTest, createStageRequestModelBuilderTest_activity_defaultEndpoint)\n{\n  CreateStageRequestModelBuilder builder(restApiEndpointID);\n  std::string activityValue_default = \"activityTest_default\";\n  std::ostringstream oss;\n  oss << \"{\\\"\" << CreateStageRequestModelBuilder::FILES_KEY_NAME << \"\\\": [\"\n      << \"{\"\n      << \"\\\"\" << CreateStageRequestModelBuilder::PATH_KEY_NAME\n      << \"\\\": \\\"/path/to/file.txt\\\",\"\n      << \"\\\"\" << CreateStageRequestModelBuilder::TARGETED_METADATA_KEY_NAME\n      << \"\\\": {\"\n      << \"\\\"default\\\" : {\"\n      << \"\\\"activity\\\":\\\"\" << activityValue_default << \"\\\"\"\n      << \"}\"\n      << \"}\"\n      << \"}\"\n      << \"]}\";\n  std::string json = oss.str();\n  auto createStageRequestModel = builder.buildFromJson(json);\n  std::string expectedActivityOpaque = \"activity=\" + activityValue_default;\n  ASSERT_EQ(expectedActivityOpaque,\n            createStageRequestModel->getFiles().getOpaqueInfos().at(0));\n}\n\nTEST_F(JsonCPPTapeModelBuilderTest, createStageRequestModelBuilderTest_activity_normalEndpoint)\n{\n  CreateStageRequestModelBuilder builder(restApiEndpointID);\n  std::string activityValue = \"activityTest\";\n  std::string activityValue_default = \"activityTest_default\";\n  std::ostringstream oss;\n  oss << \"{\\\"\" << CreateStageRequestModelBuilder::FILES_KEY_NAME << \"\\\": [\"\n      << \"{\"\n      << \"\\\"\" << CreateStageRequestModelBuilder::PATH_KEY_NAME\n      << \"\\\": \\\"/path/to/file.txt\\\",\"\n      << \"\\\"\" << CreateStageRequestModelBuilder::TARGETED_METADATA_KEY_NAME\n      << \"\\\": {\"\n      << \"\\\"default\\\" : {\"\n      << \"\\\"activity\\\":\\\"\" << activityValue_default << \"\\\"\"\n      << \"},\"\n      << \"\\\"\" << restApiEndpointID << \"\\\" : {\"\n      << \"\\\"activity\\\":\\\"\" << activityValue << \"\\\"\"\n      << \"}\"\n      << \"}\"\n      << \"}\"\n      << \"]}\";\n  std::string json = oss.str();\n  auto createStageRequestModel = builder.buildFromJson(json);\n  std::string expectedActivityOpaque = \"activity=\" + activityValue;\n  ASSERT_EQ(expectedActivityOpaque,\n            createStageRequestModel->getFiles().getOpaqueInfos().at(0));\n}"
  },
  {
    "path": "unit_tests/mgm/http/rest-api/tape/JsonCPPTapeModelBuilderTest.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file JsonCPPTapeModelBuilderTest.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#ifndef EOS_JSONCPPTAPEMODELBUILDERTEST_HH\n#define EOS_JSONCPPTAPEMODELBUILDERTEST_HH\n\n#include \"mgm/Namespace.hh\"\n#include <gtest/gtest.h>\n\nclass JsonCPPTapeModelBuilderTest : public ::testing::Test\n{\nprotected:\n\n  virtual void SetUp()\n  {\n  }\n\n  virtual void TearDown()\n  {\n  }\n\n  static std::string restApiEndpointID;\n};\n\n#endif // EOS_JSONCPPTAPEMODELBUILDERTEST_HH\n"
  },
  {
    "path": "unit_tests/mgm/http/rest-api/tape/RestApiTest.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file RestApiTest.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"RestApiTest.hh\"\n#include \"mgm/http/rest-api/exception/RestException.hh\"\n#include \"mgm/http/rest-api/handler/tape/TapeRestHandler.hh\"\n#include \"mgm/http/rest-api/utils/URLParser.hh\"\n#include \"common/http/HttpResponse.hh\"\n\nTEST_F(RestApiTest, RestHandlerConstructorShouldThrowIfProgrammerGaveWrongURL)\n{\n  std::unique_ptr<TapeRestHandler> restHandler;\n  ASSERT_THROW(restHandler.reset(new TapeRestHandler(\n                                   createConfig(\"WRONG_URL\").get())), RestException);\n  ASSERT_THROW(restHandler.reset(new TapeRestHandler(\n                                   createConfig(\"//test.fr\").get())),\n               RestException);\n  ASSERT_THROW(restHandler.reset(new TapeRestHandler(\n                                   createConfig(\"/api/v1/\").get())),\n               RestException);\n  ASSERT_THROW(restHandler.reset(new TapeRestHandler(createConfig(\"//\").get())),\n               RestException);\n  ASSERT_THROW(restHandler.reset(new TapeRestHandler(createConfig(\"/ /\").get())),\n               RestException);\n  ASSERT_NO_THROW(restHandler.reset(new TapeRestHandler(\n                                      createConfig(\"/rest-api-entry-point/\").get())));\n}\n\nTEST_F(RestApiTest, RestHandlerHandleRequestNoResource)\n{\n  eos::common::VirtualIdentity vid;\n  std::unique_ptr<TapeRestHandler> restHandler;\n  restHandler.reset(new TapeRestHandler(\n                      createConfig(\"/rest-api-entry-point/\").get()));\n  std::unique_ptr<eos::common::HttpRequest> request(\n    createHttpRequestWithEmptyBody(\"/rest-api-entry-point/\"));\n  std::unique_ptr<eos::common::HttpResponse> response(restHandler->handleRequest(\n        request.get(), &vid));\n  ASSERT_EQ(eos::common::HttpResponse::ResponseCodes::NOT_FOUND,\n            response->GetResponseCode());\n  request = createHttpRequestWithEmptyBody(\"/rest-api-entry-point/v1\");\n  response.reset(restHandler->handleRequest(request.get(), &vid));\n  ASSERT_EQ(eos::common::HttpResponse::ResponseCodes::NOT_FOUND,\n            response->GetResponseCode());\n}\n\nTEST_F(RestApiTest, RestHandlerHandleRequestResourceButNoVersion)\n{\n  eos::common::VirtualIdentity vid;\n  std::unique_ptr<TapeRestHandler> restHandler;\n  restHandler.reset(new TapeRestHandler(\n                      createConfig(\"/rest-api-entry-point/\").get()));\n  std::unique_ptr<eos::common::HttpRequest> request(\n    createHttpRequestWithEmptyBody(\"/rest-api-entry-point/tape/\"));\n  std::unique_ptr<eos::common::HttpResponse> response(restHandler->handleRequest(\n        request.get(), &vid));\n  ASSERT_EQ(eos::common::HttpResponse::ResponseCodes::NOT_FOUND,\n            response->GetResponseCode());\n}\n\nTEST_F(RestApiTest, RestHandlerHandleRequestResourceDoesNotExist)\n{\n  eos::common::VirtualIdentity vid;\n  std::unique_ptr<TapeRestHandler> restHandler;\n  restHandler.reset(new TapeRestHandler(\n                      createConfig(\"/rest-api-entry-point/\").get()));\n  std::unique_ptr<eos::common::HttpRequest> request(\n    createHttpRequestWithEmptyBody(\"/rest-api-entry-point/v1/NOT_EXIST_RESOURCE\"));\n  std::unique_ptr<eos::common::HttpResponse> response(restHandler->handleRequest(\n        request.get(), &vid));\n  ASSERT_EQ(eos::common::HttpResponse::ResponseCodes::NOT_FOUND,\n            response->GetResponseCode());\n}\n\nTEST_F(RestApiTest, RestHandlerHandleRequestResourceAndVersionExist)\n{\n  eos::common::VirtualIdentity vid;\n  std::unique_ptr<TapeRestHandler> restHandler;\n  restHandler.reset(new TapeRestHandler(\n                      createConfig(\"/rest-api-entry-point/\").get()));\n  std::unique_ptr<eos::common::HttpRequest> request(\n    createHttpRequestWithEmptyBody(\"/rest-api-entry-point/v1/stage/\"));\n  std::unique_ptr<eos::common::HttpResponse> response(restHandler->handleRequest(\n        request.get(), &vid));\n  // Posting to stage without a valid body should yield a bad request\n  ASSERT_EQ(eos::common::HttpResponse::ResponseCodes::BAD_REQUEST,\n            response->GetResponseCode());\n}\n\nTEST_F(RestApiTest, URLParserTestMatchesBegin)\n{\n  std::string urlClient = \"/api/v1/stage/\";\n  std::unique_ptr<URLParser> urlParser(new URLParser(urlClient));\n  ASSERT_TRUE(urlParser->startsBy(\"/api/v1/stage/\"));\n  ASSERT_TRUE(urlParser->startsBy(\"/api/v1/stage\"));\n  urlClient = \"/api/v1/\";\n  urlParser.reset(new URLParser(urlClient));\n  ASSERT_FALSE(urlParser->startsBy(\"/api/v1/stage/\"));\n  urlClient = \"/api/v1/stage/request-id/cancel\";\n  urlParser.reset(new URLParser(urlClient));\n  ASSERT_TRUE(urlParser->startsBy(\"/api/v1/stage/\"));\n  ASSERT_TRUE(urlParser->startsBy(\"/api/v1/stage\"));\n}\n\nTEST_F(RestApiTest, URLParserTestMatchesAndExtractParameters)\n{\n  std::string urlClient = \"/api/v1/stage/request-id/cancel\";\n  std::unique_ptr<URLParser> urlParser(new URLParser(urlClient));\n  std::map<std::string, std::string> params;\n  ASSERT_TRUE(urlParser->matchesAndExtractParameters(\"/api/v1/stage/{id}/cancel\",\n              params));\n  ASSERT_EQ(\"request-id\", params.at(\"{id}\"));\n  ASSERT_FALSE(urlParser->matchesAndExtractParameters(\"/api/v1/stage/\", params));\n  ASSERT_EQ(0, params.size());\n  ASSERT_FALSE(urlParser->matchesAndExtractParameters(\"/api/v1/stage/id/cancel\",\n               params));\n  ASSERT_EQ(0, params.size());\n  urlClient = \"/api/v1/{id}/stage/\";\n  urlParser.reset(new URLParser(urlClient));\n  ASSERT_FALSE(urlParser->matchesAndExtractParameters(\"/api/v1/id/stage\",\n               params));\n  ASSERT_EQ(0, params.size());\n}\n\nTEST_F(RestApiTest, URLBuilderTest)\n{\n  auto urlBuilder = URLBuilder::getInstance();\n  std::string hostname = \"hostname.cern.ch\";\n  uint16_t port = 1234;\n  std::string url1 = urlBuilder->setHttpsProtocol()->setHostname(\n                       hostname)->setPort(port)->build();\n  ASSERT_EQ(std::string(\"https://\")  + hostname + \":\" + std::to_string(port),\n            url1);\n  auto urlBuilder2 = URLBuilder::getInstance();\n  std::string urlstage = urlBuilder2->setHttpsProtocol()->setHostname(\n                           hostname)->setPort(port)->add(\"/api/\")->add(\"v1\")->add(\"stage\")->build();\n  ASSERT_EQ(std::string(\"https://\")  + hostname + \":\" + std::to_string(\n              port) + \"/api/v1/stage\", urlstage);\n}\n"
  },
  {
    "path": "unit_tests/mgm/http/rest-api/tape/RestApiTest.hh",
    "content": "//------------------------------------------------------------------------------\n//! @file RestApiTest.hh\n//! @author Cedric Caffy - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2017 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#ifndef EOS_RESTAPITEST_HH\n#define EOS_RESTAPITEST_HH\n\n#include \"mgm/Namespace.hh\"\n#include \"mgm/http/rest-api/handler/RestHandler.hh\"\n#include \"mgm/http/rest-api/config/tape/TapeRestApiConfig.hh\"\n#include <gtest/gtest.h>\n\nUSE_EOSMGMRESTNAMESPACE;\n\nclass RestApiTest : public ::testing::Test\n{\nprotected:\n  std::unique_ptr<TapeRestApiConfig> mDefaultConfig;\n\n  RestApiTest(): testing::Test(),\n    mDefaultConfig(std::make_unique<TapeRestApiConfig>()) {}\n\n  virtual void SetUp()\n  {\n  }\n\n  virtual void TearDown()\n  {\n  }\n\n  static std::unique_ptr<eos::common::HttpRequest> createHttpRequestWithEmptyBody(\n    const std::string& url)\n  {\n    eos::common::HttpRequest::HeaderMap headers;\n    size_t dataSize = 0;\n    eos::common::HttpRequest::HeaderMap cookies;\n    std::unique_ptr<eos::common::HttpRequest> request(new eos::common::HttpRequest(\n          headers, \"POST\", url, \"\", \"\", &dataSize, cookies));\n    return request;\n  }\n\n  static std::unique_ptr<TapeRestApiConfig> createConfig(const std::string&\n      accessURL, const std::string& siteName)\n  {\n    std::unique_ptr<TapeRestApiConfig> ret = std::make_unique<TapeRestApiConfig>\n        (accessURL);\n    ret->setSiteName(siteName);\n    return ret;\n  }\n\n  static std::unique_ptr<TapeRestApiConfig> createConfig(const std::string&\n      accessURL)\n  {\n    std::unique_ptr<TapeRestApiConfig> ret = std::make_unique<TapeRestApiConfig>\n        (accessURL);\n    return ret;\n  }\n\n  static const std::unique_ptr<TapeRestApiConfig> createDefaultConfig()\n  {\n    return createConfig(\"/api/\", \"sitename\");\n  }\n};\n\n#endif // EOS_RESTAPITEST_HH\n"
  },
  {
    "path": "unit_tests/mgm/placement/ClusterMapFixture.hh",
    "content": "// ----------------------------------------------------------------------\n// File: ClusterMapFixture.hh\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#ifndef EOS_CLUSTERMAPFIXTURE_HH\n#define EOS_CLUSTERMAPFIXTURE_HH\n\n#include \"mgm/placement/ClusterMap.hh\"\n#include \"gtest/gtest.h\"\n\nclass SimpleClusterF : public ::testing::Test {\nprotected:\n  void SetUp() override {\n    using namespace eos::mgm::placement;\n    auto sh = mgr.getStorageHandler();\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::SITE), -1, 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::SITE), -2, 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100, -1));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -101, -1));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -102, -2));\n\n    // Every group has 10 disks!\n    for (int i=0; i < 30; i++) {\n      ASSERT_TRUE(sh.addDisk(Disk(i+1, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100 - i/10));\n    }\n\n  }\n\n  eos::mgm::placement::ClusterMgr mgr;\n};\n\n\n\n#endif // EOS_CLUSTERMAPFIXTURE_HH\n"
  },
  {
    "path": "unit_tests/mgm/placement/ClusterMapTests.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ClusterMapTests\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#include \"mgm/placement/ClusterMap.hh\"\n#include \"gtest/gtest.h\"\n\nTEST(ClusterMgr, default)\n{\n  eos::mgm::placement::ClusterMgr mgr;\n  //EXPECT_EQ(mgr.getCurrentEpoch(), 0);\n  EXPECT_FALSE(mgr.getClusterData());\n}\n\nTEST(ClusterMgr, addDummyData)\n{\n  eos::mgm::placement::ClusterMgr mgr;\n  eos::mgm::placement::ClusterData data;\n  mgr.addClusterData(std::move(data));\n  //EXPECT_EQ(mgr.getCurrentEpoch(), 1);\n  EXPECT_TRUE(mgr.getClusterData());\n  auto d = mgr.getClusterData();\n  EXPECT_EQ(d->buckets.size(), 0);\n  EXPECT_EQ(d->disks.size(), 0);\n}\n\nTEST(ClusterMgr, addDummyDataTwice)\n{\n  eos::mgm::placement::ClusterMgr mgr;\n  eos::mgm::placement::ClusterData data1, data2;\n  mgr.addClusterData(std::move(data1));\n  mgr.addClusterData(std::move(data2));\n  //EXPECT_EQ(mgr.getCurrentEpoch(), 2);\n  EXPECT_TRUE(mgr.getClusterData());\n  auto d = mgr.getClusterData();\n  EXPECT_EQ(d->buckets.size(), 0);\n  EXPECT_EQ(d->disks.size(), 0);\n}\n\nTEST(ClusterMgr, StorageHandlerSeq)\n{\n\n  using namespace eos::mgm::placement;\n  eos::mgm::placement::ClusterMgr mgr;\n  {\n    auto sh = mgr.getStorageHandler();\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::SITE), -1, 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::SITE), -2, 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100, -1));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -101, -1));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -102, -2));\n\n    ASSERT_TRUE(sh.addDiskSequential(Disk(1), -100));\n    ASSERT_TRUE(sh.addDiskSequential(Disk(2), -100));\n    ASSERT_TRUE(sh.addDiskSequential(Disk(3), -101));\n    ASSERT_TRUE(sh.addDiskSequential(Disk(4), -101));\n    ASSERT_TRUE(sh.addDiskSequential(Disk(5), -102));\n  }\n\n  //ASSERT_EQ(mgr.getCurrentEpoch(), 1);\n\n  auto cluster_data = mgr.getClusterData();\n\n  EXPECT_EQ(cluster_data->disks.size(), 5);\n  EXPECT_EQ(cluster_data->buckets.size(), 256);\n\n  auto root_bucket = cluster_data->buckets[0];\n  std::vector<int32_t> root_items {-1,-2};\n  EXPECT_EQ(root_bucket.id, 0);\n  EXPECT_EQ(root_bucket.bucket_type, get_bucket_type(StdBucketType::ROOT));\n  EXPECT_EQ(root_bucket.items, root_items);\n\n  auto site_bucket1 = cluster_data->buckets[1];\n  std::vector<int32_t> site_items1 {-100,-101};\n  EXPECT_EQ(site_bucket1.id, -1);\n  EXPECT_EQ(site_bucket1.bucket_type, get_bucket_type(StdBucketType::SITE));\n  EXPECT_EQ(site_bucket1.items, site_items1);\n\n  auto site_bucket2 = cluster_data->buckets[2];\n  std::vector<int32_t> site_items2 {-102};\n  EXPECT_EQ(site_bucket2.id, -2);\n  EXPECT_EQ(site_bucket2.bucket_type, get_bucket_type(StdBucketType::SITE));\n  EXPECT_EQ(site_bucket2.items, site_items2);\n\n  auto group_bucket1 = cluster_data->buckets[100];\n  std::vector<int32_t> group_items1 {1,2};\n  EXPECT_EQ(group_bucket1.id, -100);\n  EXPECT_EQ(group_bucket1.bucket_type, get_bucket_type(StdBucketType::GROUP));\n  EXPECT_EQ(group_bucket1.items, group_items1);\n\n  auto group_bucket3 = cluster_data->buckets[102];\n  std::vector<int32_t> group_items3 {5};\n  EXPECT_EQ(group_bucket3.id, -102);\n  EXPECT_EQ(group_bucket3.bucket_type, get_bucket_type(StdBucketType::GROUP));\n  EXPECT_EQ(group_bucket3.items, group_items3);\n\n  for (int i=0; i < 5; i++) {\n    auto disk = cluster_data->disks[i];\n    EXPECT_EQ(disk.id, i+1);\n  }\n}\n\nTEST(ClusterMgr, StorageHandlerDiskInOrder)\n{\n\n  using namespace eos::mgm::placement;\n  eos::mgm::placement::ClusterMgr mgr;\n  {\n    auto sh = mgr.getStorageHandler();\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::SITE), -1, 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::SITE), -2, 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100, -1));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -101, -1));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -102, -2));\n\n    ASSERT_TRUE(sh.addDisk(Disk(1), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(2), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(3), -101));\n    ASSERT_TRUE(sh.addDisk(Disk(4), -101));\n    ASSERT_TRUE(sh.addDisk(Disk(5), -102));\n  }\n\n  //ASSERT_EQ(mgr.getCurrentEpoch(), 1);\n\n  auto cluster_data = mgr.getClusterData();\n\n  EXPECT_EQ(cluster_data->disks.size(), 5);\n  EXPECT_EQ(cluster_data->buckets.size(), 256);\n\n  auto root_bucket = cluster_data->buckets[0];\n  std::vector<int32_t> root_items {-1,-2};\n  EXPECT_EQ(root_bucket.id, 0);\n  EXPECT_EQ(root_bucket.bucket_type, get_bucket_type(StdBucketType::ROOT));\n  EXPECT_EQ(root_bucket.items, root_items);\n\n  auto site_bucket1 = cluster_data->buckets[1];\n  std::vector<int32_t> site_items1 {-100,-101};\n  EXPECT_EQ(site_bucket1.id, -1);\n  EXPECT_EQ(site_bucket1.bucket_type, get_bucket_type(StdBucketType::SITE));\n  EXPECT_EQ(site_bucket1.items, site_items1);\n\n  auto site_bucket2 = cluster_data->buckets[2];\n  std::vector<int32_t> site_items2 {-102};\n  EXPECT_EQ(site_bucket2.id, -2);\n  EXPECT_EQ(site_bucket2.bucket_type, get_bucket_type(StdBucketType::SITE));\n  EXPECT_EQ(site_bucket2.items, site_items2);\n\n  auto group_bucket1 = cluster_data->buckets[100];\n  std::vector<int32_t> group_items1 {1,2};\n  EXPECT_EQ(group_bucket1.id, -100);\n  EXPECT_EQ(group_bucket1.bucket_type, get_bucket_type(StdBucketType::GROUP));\n  EXPECT_EQ(group_bucket1.items, group_items1);\n\n  auto group_bucket3 = cluster_data->buckets[102];\n  std::vector<int32_t> group_items3 {5};\n  EXPECT_EQ(group_bucket3.id, -102);\n  EXPECT_EQ(group_bucket3.bucket_type, get_bucket_type(StdBucketType::GROUP));\n  EXPECT_EQ(group_bucket3.items, group_items3);\n\n  for (int i=0; i < 5; i++) {\n    auto disk = cluster_data->disks[i];\n    EXPECT_EQ(disk.id, i+1);\n  }\n}\n\nTEST(ClusterMgr, StorageHandlerDisksOutOfOrder)\n{\n\n  using namespace eos::mgm::placement;\n  eos::mgm::placement::ClusterMgr mgr;\n  {\n    auto sh = mgr.getStorageHandler();\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::SITE), -1, 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::SITE), -2, 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100, -1));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -101, -1));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -102, -2));\n\n    ASSERT_TRUE(sh.addDisk(Disk(110), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(100), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(104), -101));\n    ASSERT_TRUE(sh.addDisk(Disk(121), -101));\n    ASSERT_TRUE(sh.addDisk(Disk(150), -102));\n  }\n\n  //ASSERT_EQ(mgr.getCurrentEpoch(), 1);\n\n  auto cluster_data = mgr.getClusterData();\n\n  EXPECT_EQ(cluster_data->disks.size(), 150);\n  EXPECT_EQ(cluster_data->buckets.size(), 256);\n\n  auto root_bucket = cluster_data->buckets[0];\n  std::vector<int32_t> root_items{-1, -2};\n  EXPECT_EQ(root_bucket.id, 0);\n  EXPECT_EQ(root_bucket.bucket_type, get_bucket_type(StdBucketType::ROOT));\n  EXPECT_EQ(root_bucket.items, root_items);\n\n  auto site_bucket1 = cluster_data->buckets[1];\n  std::vector<int32_t> site_items1{-100, -101};\n  EXPECT_EQ(site_bucket1.id, -1);\n  EXPECT_EQ(site_bucket1.bucket_type, get_bucket_type(StdBucketType::SITE));\n  EXPECT_EQ(site_bucket1.items, site_items1);\n\n  auto site_bucket2 = cluster_data->buckets[2];\n  std::vector<int32_t> site_items2{-102};\n  EXPECT_EQ(site_bucket2.id, -2);\n  EXPECT_EQ(site_bucket2.bucket_type, get_bucket_type(StdBucketType::SITE));\n  EXPECT_EQ(site_bucket2.items, site_items2);\n\n  auto group_bucket1 = cluster_data->buckets[100];\n  std::vector<int32_t> group_items1{110,100};\n  EXPECT_EQ(group_bucket1.id, -100);\n  EXPECT_EQ(group_bucket1.bucket_type, get_bucket_type(StdBucketType::GROUP));\n  EXPECT_EQ(group_bucket1.items, group_items1);\n\n  auto group_bucket2 = cluster_data->buckets[101];\n  std::vector<int32_t> group_items2{104,121};\n  EXPECT_EQ(group_bucket2.id, -101);\n  EXPECT_EQ(group_bucket2.bucket_type, get_bucket_type(StdBucketType::GROUP));\n  EXPECT_EQ(group_bucket2.items, group_items2);\n\n  auto group_bucket3 = cluster_data->buckets[102];\n  std::vector<int32_t> group_items3{150};\n  EXPECT_EQ(group_bucket3.id, -102);\n  EXPECT_EQ(group_bucket3.bucket_type, get_bucket_type(StdBucketType::GROUP));\n  EXPECT_EQ(group_bucket3.items, group_items3);\n}\n\nTEST(ClusterMgr, BM_Layout)\n{\n  using namespace eos::mgm::placement;\n  eos::mgm::placement::ClusterMgr mgr;\n  int n_elements = 1024;\n  int n_disks_per_group = 16;\n  int n_groups = 32;\n  {\n\n    auto sh = mgr.getStorageHandler(n_elements);\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0));\n    //ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::SITE), -1, 0));\n\n    for (int i=0; i< n_groups; ++i) {\n      ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100-i, 0));\n    }\n\n    for (int i=0; i < n_groups*n_disks_per_group; i++) {\n      ASSERT_TRUE(sh.addDisk(Disk(i+1, ConfigStatus::kRW, ActiveStatus::kOnline, 1),\n                             -100 - i/n_disks_per_group));\n    }\n\n  }\n  auto cluster_data = mgr.getClusterData();\n  EXPECT_EQ(cluster_data->disks.size(), 32*16);\n  EXPECT_EQ(cluster_data->buckets.size(), n_elements);\n  auto root_bucket = cluster_data->buckets[0];\n  EXPECT_EQ(root_bucket.items.size(), n_groups);\n  for (auto it: root_bucket.items) {\n    EXPECT_EQ(cluster_data->buckets.at(-it).items.size(), n_disks_per_group);\n  }\n}\n"
  },
  {
    "path": "unit_tests/mgm/placement/FsSchedulerTests.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/placement/FsScheduler.hh\"\n#include \"gtest/gtest.h\"\n\nusing eos::mgm::placement::ClusterMgr;\n\nstruct TestClusterMgrHandler : public eos::mgm::placement::ClusterMgrHandler\n{\n\n  std::unique_ptr<ClusterMgr> make_cluster_mgr(const std::string& spacename) override {\n    auto mgr = std::make_unique<ClusterMgr>();\n    int n_disks_per_group = 16;\n    int n_groups = 32;\n    {\n      using namespace eos::mgm::placement;\n      auto sh = mgr->getStorageHandler(2048);\n      sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0);\n      for (int i=0; i< n_groups; ++i) {\n        sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100-i, 0);\n      }\n\n      for (int i=0; i < n_groups*n_disks_per_group; i++) {\n        sh.addDisk(Disk(i+1, ConfigStatus::kRW, ActiveStatus::kOnline, 1),\n                   -100 - i/n_disks_per_group);\n      }\n    }\n    return mgr;\n  }\n\n\n  eos::mgm::placement::ClusterMapT make_cluster_mgr() override {\n    eos::mgm::placement::ClusterMapT cluster_map;\n    cluster_map.insert_or_assign(\"default\", make_cluster_mgr(\"default\"));\n    return cluster_map;\n  }\n};\n\nusing eos::mgm::placement::FSScheduler;\n\nTEST(FSScheduler, construction)\n{\n  FSScheduler fs_scheduler(2048, std::make_unique<TestClusterMgrHandler>());\n  fs_scheduler.updateClusterData();\n}\n\nTEST(FSScheduler, null_handler)\n{\n  FSScheduler null_scheduler(2048, nullptr);\n  null_scheduler.updateClusterData();\n}\n\nTEST(FSScheduler, default_scheduler)\n{\n  FSScheduler fs_scheduler(2048, std::make_unique<TestClusterMgrHandler>());\n  auto strategy = fs_scheduler.getPlacementStrategy();\n  ASSERT_EQ(strategy, eos::mgm::placement::PlacementStrategyT::kGeoScheduler);\n}\n\nTEST(FSScheduler, geo_sched_err)\n{\n  FSScheduler fs_scheduler(2048, std::make_unique<TestClusterMgrHandler>());\n  fs_scheduler.updateClusterData();\n  auto result = fs_scheduler.schedule(\"default\",2);\n  ASSERT_FALSE(result);\n  EXPECT_EQ(result.ret_code, EINVAL);\n  EXPECT_EQ(result.error_string(),\n           \"Not a valid PlacementStrategy\");\n}\n\nTEST(FSScheduler, round_robin)\n{\n  FSScheduler fs_scheduler(2048, std::make_unique<TestClusterMgrHandler>());\n  fs_scheduler.updateClusterData();\n  fs_scheduler.setPlacementStrategy(\"roundrobin\");\n  auto strategy = fs_scheduler.getPlacementStrategy();\n  ASSERT_EQ(strategy, eos::mgm::placement::PlacementStrategyT::kRoundRobin);\n  auto result = fs_scheduler.schedule(\"default\",2);\n  ASSERT_TRUE(result);\n}\n\nTEST(FSScheduler, setPlacementStrategy)\n{\n  FSScheduler fs_scheduler(2048, std::make_unique<TestClusterMgrHandler>());\n  fs_scheduler.updateClusterData();\n  fs_scheduler.setPlacementStrategy(\"roundrobin\");\n  EXPECT_EQ(fs_scheduler.getPlacementStrategy(),\n                eos::mgm::placement::PlacementStrategyT::kRoundRobin);\n  EXPECT_EQ(fs_scheduler.getPlacementStrategy(\"default\"),\n                eos::mgm::placement::PlacementStrategyT::kRoundRobin);\n  EXPECT_EQ(fs_scheduler.getPlacementStrategy(\"foobar\"),\n                eos::mgm::placement::PlacementStrategyT::kRoundRobin);\n}\n\nTEST(FSScheduler, setPlacementStrategySpace)\n{\n  FSScheduler fs_scheduler(2048, std::make_unique<TestClusterMgrHandler>());\n  fs_scheduler.updateClusterData();\n  fs_scheduler.setPlacementStrategy(\"default\", \"weightedrandom\");\n  EXPECT_EQ(fs_scheduler.getPlacementStrategy(),\n            eos::mgm::placement::PlacementStrategyT::kGeoScheduler);\n  EXPECT_EQ(fs_scheduler.getPlacementStrategy(\"default\"),\n            eos::mgm::placement::PlacementStrategyT::kWeightedRandom);\n  EXPECT_EQ(fs_scheduler.getPlacementStrategy(\"tape\"),\n            eos::mgm::placement::PlacementStrategyT::kGeoScheduler);\n}"
  },
  {
    "path": "unit_tests/mgm/placement/PlacementStrategyTests.cc",
    "content": "//------------------------------------------------------------------------------\n//! @file PlacementStrategy.hh\n//! @author Abhishek Lekshmanan <abhishek.lekshmanan@cern.ch>\n//-----------------------------------------------------------------------------\n\n/************************************************************************\n  * EOS - the CERN Disk Storage System                                   *\n  * Copyright (C) 2024 CERN/Switzerland                           *\n  *                                                                      *\n  * This program is free software: you can redistribute it and/or modify *\n  * it under the terms of the GNU General Public License as published by *\n  * the Free Software Foundation, either version 3 of the License, or    *\n  * (at your option) any later version.                                  *\n  *                                                                      *\n  * This program is distributed in the hope that it will be useful,      *\n  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n  * GNU General Public License for more details.                         *\n  *                                                                      *\n  * You should have received a copy of the GNU General Public License    *\n  * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n  ************************************************************************/\n\n\n#include \"mgm/placement/PlacementStrategy.hh\"\n#include \"gtest/gtest.h\"\n\nTEST(PlacementResult, default)\n{\n  eos::mgm::placement::PlacementResult result;\n  EXPECT_EQ(result.ret_code, -1);\n  EXPECT_EQ(result.error_string(), \"\");\n  EXPECT_FALSE(result.is_valid_placement(2));\n}\n\nTEST(PlacementResult, is_valid_placement)\n{\n  eos::mgm::placement::PlacementResult result(2);\n  result.ids =  {1,2};\n  EXPECT_TRUE(result.is_valid_placement(2));\n\n  eos::mgm::placement::PlacementResult result2(2);\n  result2.ids = {1,-1};\n  EXPECT_FALSE(result2.is_valid_placement(2));\n}\n\nTEST(PlacementResult, contains)\n{\n  eos::mgm::placement::PlacementResult result(2);\n  result.ids = {1,2};\n  EXPECT_TRUE(result.contains(1));\n  EXPECT_TRUE(result.contains(2));\n  EXPECT_FALSE(result.contains(3));\n}\n\nTEST(PlacementResult, contains_invalid)\n{\n  eos::mgm::placement::PlacementResult result(2);\n  result.ids = {4,3,2,1}; // anything beyond 2nd slot is irrelevant\n  EXPECT_FALSE(result.contains(2));\n  EXPECT_FALSE(result.contains(1));\n  EXPECT_TRUE(result.contains(4));\n  EXPECT_TRUE(result.contains(3));\n}"
  },
  {
    "path": "unit_tests/mgm/placement/RRSeedTests.cc",
    "content": "// ----------------------------------------------------------------------\n// File: RRSeedTests\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n\n#include \"mgm/placement/RRSeed.hh\"\n#include \"gtest/gtest.h\"\n#include <thread>\n\nusing RRSeed = eos::mgm::placement::RRSeed<uint64_t>;\n\nTEST(RRSeed, Construction)\n{\n  RRSeed seed(10);\n  EXPECT_EQ(seed.getNumSeeds(), 10);\n}\n\nTEST(RRSeed, out_of_bounds)\n{\n  RRSeed seed(10);\n  // 0-indexing, 10 should be offby1\n  EXPECT_THROW(seed.get(10,0), std::out_of_range);\n}\n\nTEST(RRSeed, single_thread)\n{\n  RRSeed seed(10);\n  EXPECT_EQ(seed.getNumSeeds(), 10);\n\n  // Trivial No op case - base condition, we don't change anything here\n  EXPECT_EQ(seed.get(0,0),0);\n\n  // We ask for the next seed, we'd get the starting seed ie. 0 and internally\n  // the counter is 1\n  EXPECT_EQ(seed.get(0,1),0);\n\n  // No op - check internal counter\n  EXPECT_EQ(seed.get(0,0),1);\n  // Repeat No op!\n  EXPECT_EQ(seed.get(0,0),1);\n\n  // Now we ask for the next seed, we'd still get 1, internal counter = 2\n  EXPECT_EQ(seed.get(0,1),1);\n\n  // No op - check internal counter\n  EXPECT_EQ(seed.get(0,0),2);\n\n  // ask for 10 seeds\n  EXPECT_EQ(seed.get(0,10),2);\n\n  // No op - check internal counter\n  EXPECT_EQ(seed.get(0,0),12);\n}\n\nTEST(RRSeed, multithread)\n{\n  RRSeed seed(10);\n\n  auto f = [&seed]() {\n    for (int i = 0; i < 1000; i++) {\n      seed.get(0, 1);\n    }\n  };\n\n  std::vector<std::thread> threads;\n  for (int i=0;i<16;++i){\n    threads.emplace_back(f);\n  }\n\n  for (int i=0;i<16;++i){\n    threads[i].join();\n  }\n\n  EXPECT_EQ(seed.get(0,0),16000);\n  // Get at a different index, we only modified index 0 seed,\n  // rest should all be 0\n  EXPECT_EQ(seed.get(1,0),0);\n}\n\nTEST(RRSeed, wrap_around)\n{\n  eos::mgm::placement::RRSeed<uint8_t> seed(10);\n  // no op - check initial state\n  EXPECT_EQ(seed.get(0,0),0);\n  for (int i = 0; i < 255; i++) {\n    EXPECT_EQ(seed.get(0,1),i);\n  }\n  // no op - final value at 255\n  EXPECT_EQ(seed.get(0,0),255);\n\n  // now increment!\n  EXPECT_EQ(seed.get(0,1),255);\n\n  // wrap aroound! check reusability\n  EXPECT_EQ(seed.get(0,1),0);\n  EXPECT_EQ(seed.get(0,1),1);\n  EXPECT_EQ(seed.get(0,1),2);\n\n}"
  },
  {
    "path": "unit_tests/mgm/placement/SchedulerTests.cc",
    "content": "// ----------------------------------------------------------------------\n// File: SchedulerTests.cc\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"common/utils/ContainerUtils.hh\"\n#include \"mgm/placement/FlatScheduler.hh\"\n#include \"mgm/placement/PlacementStrategy.hh\"\n#include \"mgm/placement/RoundRobinPlacementStrategy.hh\"\n#include \"mgm/placement/WeightedRandomStrategy.hh\"\n#include \"unit_tests/mgm/placement/ClusterMapFixture.hh\"\n#include \"gtest/gtest.h\"\nusing eos::mgm::placement::item_id_t;\n\nTEST_F(SimpleClusterF, RoundRobinBasic)\n{\n  eos::mgm::placement::RoundRobinPlacement rr_placement(eos::mgm::placement::PlacementStrategyT::kRoundRobin,\n                                                        256);\n\n  auto cluster_data_ptr = mgr.getClusterData();\n\n  // TODO: write a higher level function to do recursive descent\n  // Choose 1 site - from ROOT\n  auto res = rr_placement.placeFiles(cluster_data_ptr(), {0, 1});\n  ASSERT_TRUE(res);\n  EXPECT_EQ(res.n_replicas, 1);\n  EXPECT_EQ(res.ids[0], -1);\n\n  // Choose 1 group from SITE\n  auto site_id = res.ids[0];\n  auto group_res = rr_placement.placeFiles(cluster_data_ptr(), {site_id, 1});\n  ASSERT_TRUE(group_res);\n  EXPECT_EQ(group_res.n_replicas, 1);\n\n\n  // choose 2 disks from group!\n  auto disks_res =\n      rr_placement.placeFiles(cluster_data_ptr(), {group_res.ids[0], 2});\n  ASSERT_TRUE(disks_res);\n  EXPECT_EQ(disks_res.n_replicas, 2);\n\n}\n\nTEST_F(SimpleClusterF, RandomBasic)\n{\n  eos::mgm::placement::RoundRobinPlacement rand_placement(eos::mgm::placement::PlacementStrategyT::kRandom,\n                                                          256);\n\n  auto cluster_data_ptr = mgr.getClusterData();\n\n  auto res = rand_placement.placeFiles(cluster_data_ptr(), {0, 1});\n  ASSERT_TRUE(res);\n  EXPECT_EQ(res.n_replicas, 1);\n\n  auto site_id = res.ids[0];\n  auto group_res = rand_placement.placeFiles(cluster_data_ptr(), {site_id, 1});\n  ASSERT_TRUE(group_res);\n  EXPECT_EQ(group_res.n_replicas, 1);\n\n  auto disks_res =\n      rand_placement.placeFiles(cluster_data_ptr(), {group_res.ids[0], 2});\n  ASSERT_TRUE(disks_res);\n  std::cout << disks_res << \"\\n\";\n  EXPECT_EQ(disks_res.n_replicas, 2);\n}\n\nTEST_F(SimpleClusterF, TLRoundRobinBasic)\n{\n  eos::mgm::placement::RoundRobinPlacement rr_placement(eos::mgm::placement::PlacementStrategyT::kThreadLocalRoundRobin,\n                                                        256);\n\n  auto cluster_data_ptr = mgr.getClusterData();\n\n  // TODO: write a higher level function to do recursive descent\n  // Choose 1 site - from ROOT\n  auto res = rr_placement.placeFiles(cluster_data_ptr(), {0, 1});\n  ASSERT_TRUE(res);\n  EXPECT_EQ(res.n_replicas, 1);\n  // We cannot assert on the id here because the thread local round robin would\n  // have a random starting point, only the looping behaviour is easier to reason\n\n\n  // Choose 1 group from SITE\n  auto site_id = res.ids[0];\n  auto group_res = rr_placement.placeFiles(cluster_data_ptr(), {site_id, 1});\n  ASSERT_TRUE(group_res);\n  EXPECT_EQ(group_res.n_replicas, 1);\n\n\n  // choose 2 disks from group!\n  auto disks_res =\n      rr_placement.placeFiles(cluster_data_ptr(), {group_res.ids[0], 2});\n  ASSERT_TRUE(disks_res);\n  EXPECT_EQ(disks_res.n_replicas, 2);\n\n}\n\nTEST_F(SimpleClusterF, RoundRobinBasicLoop)\n{\n  eos::mgm::placement::RoundRobinPlacement rr_placement(eos::mgm::placement::PlacementStrategyT::kRoundRobin,\n                                                        256);\n\n  auto cluster_data_ptr = mgr.getClusterData();\n\n  std::map<int32_t,uint32_t> site_id_ctr;\n  std::map<int32_t,uint32_t> group_id_ctr;\n  std::map<int32_t,uint32_t> disk_id_ctr;\n  std::vector<int32_t> disk_ids_vec;\n  // TODO: write a higher level function to do recursive descent\n  // Choose 1 site - from ROOT\n  // Loop over 30 times, which is the total size of the disks to ensure that all\n  // elements are chosen\n  for (int i = 0; i < 30; i++)\n  {\n    auto res = rr_placement.placeFiles(cluster_data_ptr(), {0, 1});\n\n    ASSERT_TRUE(res);\n    ASSERT_EQ(res.n_replicas, 1);\n\n    site_id_ctr[res.ids[0]]++;\n\n    // Choose 1 group from SITE\n    auto site_id = res.ids[0];\n    auto group_res = rr_placement.placeFiles(cluster_data_ptr(), {site_id, 1});\n\n    ASSERT_TRUE(group_res);\n    ASSERT_EQ(group_res.n_replicas, 1);\n    group_id_ctr[group_res.ids[0]]++;\n\n\n    // choose 2 disks from group!\n    auto disks_res =\n        rr_placement.placeFiles(cluster_data_ptr(), {group_res.ids[0], 2});\n\n    ASSERT_TRUE(disks_res);\n    ASSERT_EQ(disks_res.n_replicas, 2);\n    disk_id_ctr[disks_res.ids[0]]++;\n    disk_id_ctr[disks_res.ids[1]]++;\n\n\n    disk_ids_vec.push_back(disks_res.ids[0]);\n    disk_ids_vec.push_back(disks_res.ids[1]);\n\n  }\n\n  // SITE1 gets 15 requests, SITE2 gets 15 requests;\n  ASSERT_EQ(site_id_ctr[-1], 15);\n  ASSERT_EQ(site_id_ctr[-2], 15);\n\n\n  // 30 items chosen in site1 among 20 disks\n  // 30 items chosen in site2 among 10 disks\n  ASSERT_EQ(group_id_ctr[-102], 15);\n\n  // This is a bit more involved to reason, actually just a consequence of an\n  // empty starting cluster, where we'd expect roundrobin to start from the initial\n  // elements, hence, group1 is chosen first, and thus gets a request extra\n  // if you do the LCM you'd be able to reach a point where you'd schedule equally\n  // group1 & group2; group3 would still have 2X requests if you RR over the sites first\n\n  EXPECT_EQ(group_id_ctr[-100], 8);\n  EXPECT_EQ(group_id_ctr[-101], 7);\n  // All the disks are chosen at least once, due to the non uniform nature here,\n  // site 2 would have its disks chosen twice as often as site 1\n  ASSERT_EQ(disk_ids_vec.size(), 60);\n  ASSERT_EQ(disk_id_ctr.size(), 30);\n\n\n\n  // Check SITE1 ctr, at least 1; initial disks would be twice as filled as latter\n  for (int i=1; i <=20; i++) {\n    ASSERT_GE(disk_id_ctr[i], 1);\n  }\n\n  // Check SITE2 ctr, all disks would've been scheduled twice, initial disks twice often as the others\n\n  for (int i=21; i <=30; i++) {\n    ASSERT_GE(disk_id_ctr[i],2);\n  }\n}\n\nTEST_F(SimpleClusterF, TLRoundRobinBasicLoop)\n{\n  eos::mgm::placement::RoundRobinPlacement rr_placement(eos::mgm::placement::PlacementStrategyT::kThreadLocalRoundRobin,\n                                                        256);\n\n  auto cluster_data_ptr = mgr.getClusterData();\n\n  std::map<int32_t,uint32_t> site_id_ctr;\n  std::map<int32_t,uint32_t> group_id_ctr;\n  std::map<int32_t,uint32_t> disk_id_ctr;\n  std::vector<int32_t> disk_ids_vec;\n  // TODO: write a higher level function to do recursive descent\n  // Choose 1 site - from ROOT\n  // Loop over 30 times, which is the total size of the disks to ensure that all\n  // elements are chosen\n  for (int i = 0; i < 30; i++)\n  {\n    auto res = rr_placement.placeFiles(cluster_data_ptr(), {0, 1});\n\n    ASSERT_TRUE(res);\n    ASSERT_EQ(res.n_replicas, 1);\n\n    site_id_ctr[res.ids[0]]++;\n\n    // Choose 1 group from SITE\n    auto site_id = res.ids[0];\n    auto group_res = rr_placement.placeFiles(cluster_data_ptr(), {site_id, 1});\n\n    ASSERT_TRUE(group_res);\n    ASSERT_EQ(group_res.n_replicas, 1);\n    group_id_ctr[group_res.ids[0]]++;\n\n\n    // choose 2 disks from group!\n    auto disks_res =\n        rr_placement.placeFiles(cluster_data_ptr(), {group_res.ids[0], 2});\n\n    ASSERT_TRUE(disks_res);\n    ASSERT_EQ(disks_res.n_replicas, 2);\n    disk_id_ctr[disks_res.ids[0]]++;\n    disk_id_ctr[disks_res.ids[1]]++;\n\n\n    disk_ids_vec.push_back(disks_res.ids[0]);\n    disk_ids_vec.push_back(disks_res.ids[1]);\n\n  }\n\n  // SITE1 gets 15 requests, SITE2 gets 15 requests;\n  ASSERT_EQ(site_id_ctr[-1], 15);\n  ASSERT_EQ(site_id_ctr[-2], 15);\n\n\n  // 30 items chosen in site1 among 20 disks\n  // 30 items chosen in site2 among 10 disks\n  ASSERT_EQ(group_id_ctr[-102], 15);\n  // All the disks are chosen at least once, due to the non uniform nature here,\n  // site 2 would have its disks chosen twice as often as site 1\n  ASSERT_EQ(disk_ids_vec.size(), 60);\n  ASSERT_EQ(disk_id_ctr.size(), 30);\n\n  // Check SITE1 ctr, at least 1; initial disks would be twice as filled as latter\n  for (int i=1; i <=20; i++) {\n    ASSERT_GE(disk_id_ctr[i], 1);\n  }\n\n  // Check SITE2 ctr, all disks would've been scheduled twice, initial disks twice often as the others\n  for (int i=21; i <=30; i++) {\n    ASSERT_GE(disk_id_ctr[i],2);\n  }\n}\n\n\nTEST_F(SimpleClusterF, FlatSchedulerBasic)\n{\n  using eos::mgm::placement::PlacementStrategyT;\n\n  eos::mgm::placement::FlatScheduler flat_scheduler(\n      eos::mgm::placement::PlacementStrategyT::kRoundRobin,\n                                                    256);\n\n  auto cluster_data_ptr = mgr.getClusterData();\n\n  auto result = flat_scheduler.schedule(cluster_data_ptr(),\n                                        {2});\n  eos::mgm::placement::PlacementResult expected_result;\n  expected_result.ids = {1,2};\n  expected_result.ret_code = 0;\n  ASSERT_TRUE(result);\n\n  ASSERT_TRUE(result.is_valid_placement(2));\n  EXPECT_EQ(result, expected_result);\n\n  auto result2 = flat_scheduler.schedule(cluster_data_ptr(),\n                                         {2});\n  ASSERT_TRUE(result.is_valid_placement(2));\n}\n\n\n\nTEST_F(SimpleClusterF, FlatSchedulerBasicLoop)\n{\n  using eos::mgm::placement::PlacementStrategyT;\n\n  eos::mgm::placement::FlatScheduler flat_scheduler(\n      eos::mgm::placement::PlacementStrategyT::kRoundRobin,\n                                                    256);\n\n  auto cluster_data_ptr = mgr.getClusterData();\n\n  std::map<int32_t,uint32_t> disk_id_ctr;\n  std::vector<int32_t> disk_ids_vec;\n\n  for (int i=0; i <30; ++i) {\n    auto result = flat_scheduler.schedule(cluster_data_ptr(),\n                                          {2});\n    ASSERT_TRUE(result);\n    ASSERT_TRUE(result.is_valid_placement(2));\n    disk_id_ctr[result.ids[0]]++;\n    disk_id_ctr[result.ids[1]]++;\n    disk_ids_vec.push_back(result.ids[0]);\n    disk_ids_vec.push_back(result.ids[1]);\n  }\n  // All the disks are chosen at least once, due to the non uniform nature here,\n  // site 2 would have its disks chosen twice as often as site 1\n  ASSERT_EQ(disk_ids_vec.size(), 60);\n  ASSERT_EQ(disk_id_ctr.size(), 30);\n\n  // Check SITE1 ctr, at least 1; initial disks would be twice as filled as latter\n  for (int i=1; i <=20; i++) {\n    ASSERT_GE(disk_id_ctr[i], 1);\n  }\n\n  // Check SITE2 ctr, all disks would've been scheduled twice,\n  // initial disks twice often as the others\n\n  for (int i=21; i <=30; i++) {\n    ASSERT_GE(disk_id_ctr[i],2);\n  }\n\n}\n\nTEST_F(SimpleClusterF, TLFlatSchedulerBasicLoop)\n{\n  using eos::mgm::placement::PlacementStrategyT;\n\n  eos::mgm::placement::FlatScheduler flat_scheduler(\n      eos::mgm::placement::PlacementStrategyT::kThreadLocalRoundRobin,\n                                                    256);\n\n  auto cluster_data_ptr = mgr.getClusterData();\n\n  std::map<int32_t,uint32_t> disk_id_ctr;\n  std::vector<int32_t> disk_ids_vec;\n\n  for (int i=0; i <30; ++i) {\n    auto result = flat_scheduler.schedule(cluster_data_ptr(),\n                                          {2});\n    ASSERT_TRUE(result);\n    ASSERT_TRUE(result.is_valid_placement(2));\n    disk_id_ctr[result.ids[0]]++;\n    disk_id_ctr[result.ids[1]]++;\n    disk_ids_vec.push_back(result.ids[0]);\n    disk_ids_vec.push_back(result.ids[1]);\n  }\n  // All the disks are chosen at least once, due to the non uniform nature here,\n  // site 2 would have its disks chosen twice as often as site 1\n  ASSERT_EQ(disk_ids_vec.size(), 60);\n  ASSERT_EQ(disk_id_ctr.size(), 30);\n\n  // Check SITE1 ctr, at least 1; initial disks would be twice as filled as latter\n  for (int i=1; i <=20; i++) {\n    ASSERT_GE(disk_id_ctr[i], 1);\n  }\n\n  // Check SITE2 ctr, all disks would've been scheduled twice,\n  // initial disks twice often as the others\n\n  for (int i=21; i <=30; i++) {\n    ASSERT_GE(disk_id_ctr[i],2);\n  }\n\n}\n\n\nTEST(FlatScheduler, SingleSite)\n{\n  using namespace eos::mgm::placement;\n  ClusterMgr mgr;\n  using eos::mgm::placement::PlacementStrategyT;\n\n  eos::mgm::placement::FlatScheduler flat_scheduler(PlacementStrategyT::kRoundRobin,\n                                                    2048);\n\n  {\n    auto sh = mgr.getStorageHandler(1024);\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::SITE), -1, 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100, -1));\n\n    ASSERT_TRUE(sh.addDisk(Disk(1, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(2, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(3, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(4, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(5, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n  }\n\n  auto data = mgr.getClusterData();\n  std::vector<int32_t> disk_ids_vec {-1};\n  std::vector<int32_t> site_ids_vec {-100};\n  std::vector<int32_t> group_ids_vec {1,2,3,4,5};\n  ASSERT_EQ(data->buckets[0].items, disk_ids_vec);\n  ASSERT_EQ(data->buckets[1].items, site_ids_vec);\n  ASSERT_EQ(data->buckets[100].items, group_ids_vec);\n\n  auto cluster_data_ptr = mgr.getClusterData();\n  auto result = flat_scheduler.schedule(cluster_data_ptr(),\n                                        {2});\n  std::cout << result.err_msg.value_or(\"\") << \", \" << result << std::endl;\n  ASSERT_TRUE(result);\n  ASSERT_TRUE(result.is_valid_placement(2));\n}\n\nTEST(FlatScheduler, TLSingleSite)\n{\n  using namespace eos::mgm::placement;\n  ClusterMgr mgr;\n  using eos::mgm::placement::PlacementStrategyT;\n\n  eos::mgm::placement::FlatScheduler flat_scheduler(PlacementStrategyT::kThreadLocalRoundRobin,\n                                                    2048);\n\n  {\n    auto sh = mgr.getStorageHandler(1024);\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::SITE), -1, 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100, -1));\n\n    ASSERT_TRUE(sh.addDisk(Disk(1, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(2, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(3, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(4, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(5, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n  }\n\n  auto data = mgr.getClusterData();\n  std::vector<int32_t> disk_ids_vec {-1};\n  std::vector<int32_t> site_ids_vec {-100};\n  std::vector<int32_t> group_ids_vec {1,2,3,4,5};\n  ASSERT_EQ(data->buckets[0].items, disk_ids_vec);\n  ASSERT_EQ(data->buckets[1].items, site_ids_vec);\n  ASSERT_EQ(data->buckets[100].items, group_ids_vec);\n\n  auto cluster_data_ptr = mgr.getClusterData();\n  auto result = flat_scheduler.schedule(cluster_data_ptr(),\n                                        {2});\n  std::cout << result.err_msg.value_or(\"\") << std::endl;\n  ASSERT_TRUE(result);\n  ASSERT_TRUE(result.is_valid_placement(2));\n}\n\nTEST(FlatScheduler, TLSingleSiteWeighted)\n{\n  using namespace eos::mgm::placement;\n  ClusterMgr mgr;\n  using eos::mgm::placement::PlacementStrategyT;\n  PlacementStrategyT strategy = PlacementStrategyT::kWeightedRandom;\n  eos::mgm::placement::FlatScheduler flat_scheduler(strategy,\n                                                    2048);\n\n  {\n    auto sh = mgr.getStorageHandler(1024);\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::SITE), -1, 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100, -1));\n\n    ASSERT_TRUE(sh.addDisk(Disk(1, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(2, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(3, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(4, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(5, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n  }\n\n  auto data = mgr.getClusterData();\n  std::vector<int32_t> disk_ids_vec {-1};\n  std::vector<int32_t> site_ids_vec {-100};\n  std::vector<int32_t> group_ids_vec {1,2,3,4,5};\n  ASSERT_EQ(data->buckets[0].items, disk_ids_vec);\n  ASSERT_EQ(data->buckets[1].items, site_ids_vec);\n  ASSERT_EQ(data->buckets[100].items, group_ids_vec);\n\n  auto cluster_data_ptr = mgr.getClusterData();\n  auto result = flat_scheduler.schedule(cluster_data_ptr(),\n                                        {2});\n  std::cout << result.err_msg.value_or(\"\") << std::endl;\n  ASSERT_TRUE(result);\n  ASSERT_TRUE(result.is_valid_placement(2));\n  size_t index {std::numeric_limits<size_t>::max()};\n  std::vector<uint32_t> result_vector (result.ids.begin(), result.ids.end());\n  AccessArguments access_args {index, strategy, result_vector};\n  auto status = flat_scheduler.access(cluster_data_ptr(), access_args);\n  ASSERT_EQ(status, 0);\n  ASSERT_LE(index, result_vector.size());\n  \n}\n\nTEST(FlatScheduler, TLNoSite)\n{\n  using namespace eos::mgm::placement;\n  eos::mgm::placement::ClusterMgr mgr;\n  int n_elements = 1024;\n  int n_disks_per_group = 16;\n  int n_groups = 32;\n  eos::mgm::placement::FlatScheduler flat_scheduler(PlacementStrategyT::kThreadLocalRoundRobin,\n                                                    2048);\n\n  {\n\n    auto sh = mgr.getStorageHandler(n_elements);\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0));\n    for (int i=0; i< n_groups; ++i) {\n      ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100-i, 0));\n    }\n\n    for (int i=0; i < n_groups*n_disks_per_group; i++) {\n      ASSERT_TRUE(sh.addDisk(Disk(i+1, ConfigStatus::kRW, ActiveStatus::kOnline, 1),\n                             -100 - i/n_disks_per_group));\n    }\n\n  }\n  auto cluster_data = mgr.getClusterData();\n  EXPECT_EQ(cluster_data->disks.size(), 32*16);\n  EXPECT_EQ(cluster_data->buckets.size(), n_elements);\n  auto root_bucket = cluster_data->buckets[0];\n  EXPECT_EQ(root_bucket.items.size(), n_groups);\n  for (auto it: root_bucket.items) {\n    EXPECT_EQ(cluster_data->buckets.at(-it).items.size(), n_disks_per_group);\n  }\n\n  for (int i = 0; i < 1000; i++) {\n    auto result = flat_scheduler.schedule(cluster_data(), {2});\n    ASSERT_TRUE(result);\n    ASSERT_TRUE(result.is_valid_placement(2));\n  }\n}\n\nTEST(FlatScheduler, TLNoSiteExcludeFsids)\n{\n  using namespace eos::mgm::placement;\n  eos::mgm::placement::ClusterMgr mgr;\n  int n_elements = 1024;\n  int n_disks_per_group = 16;\n  int n_groups = 32;\n  eos::mgm::placement::FlatScheduler flat_scheduler(2048);\n\n  {\n\n    auto sh = mgr.getStorageHandler(n_elements);\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0));\n    for (int i=0; i< n_groups; ++i) {\n      ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100-i, 0));\n    }\n\n    for (int i=0; i < n_groups*n_disks_per_group; i++) {\n      ASSERT_TRUE(sh.addDisk(Disk(i+1, ConfigStatus::kRW, ActiveStatus::kOnline, 1),\n                             -100 - i/n_disks_per_group));\n    }\n\n  }\n  auto cluster_data = mgr.getClusterData();\n  EXPECT_EQ(cluster_data->disks.size(), 32*16);\n  EXPECT_EQ(cluster_data->buckets.size(), n_elements);\n  auto root_bucket = cluster_data->buckets[0];\n  EXPECT_EQ(root_bucket.items.size(), n_groups);\n  for (auto it: root_bucket.items) {\n    EXPECT_EQ(cluster_data->buckets.at(-it).items.size(), n_disks_per_group);\n  }\n  uint8_t n_replicas = 12;\n  for (auto t: {PlacementStrategyT::kWeightedRoundRobin,\n                PlacementStrategyT::kRoundRobin,\n                PlacementStrategyT::kThreadLocalRoundRobin,\n                PlacementStrategyT::kWeightedRandom,\n                }) {\n\n    PlacementArguments args{n_replicas};\n    args.excludefs = {1};\n    args.strategy = t;\n    auto strategy_str = eos::mgm::placement::strategy_to_str(t);\n    std::cerr << \"\\nTesting using strategy=\" << strategy_str;\n    for (int i = 0; i < 10000; i++) {\n      if (i%500 == 0) {\n        std::cerr << \".\";\n      }\n      auto result = flat_scheduler.schedule(cluster_data(), args);\n      if (!result) {\n        std::cerr << \"Iteration \" << i << \" failed: err=\"\n                  << result.err_msg.value_or(\"\")\n                  << \" strategy=\" << strategy_str\n                    << \" result=\" << result << std::endl;\n      }\n\n      EXPECT_TRUE(result);\n      EXPECT_TRUE(result.is_valid_placement(n_replicas));\n\n     for (int i=0; i<n_replicas; ++i) {\n        EXPECT_NE(result.ids[i],1);\n      }\n    }\n  }\n}\n\nTEST(FlatScheduler, ForcedGroup)\n{\n  using namespace eos::mgm::placement;\n  using namespace eos::mgm::placement;\n  eos::mgm::placement::ClusterMgr mgr;\n  int n_elements = 1024;\n  int n_disks_per_group = 16;\n  int n_groups = 32;\n  eos::mgm::placement::FlatScheduler flat_scheduler(2048);\n\n  {\n\n    auto sh = mgr.getStorageHandler(n_elements);\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0));\n    for (int i=0; i< n_groups; ++i) {\n      ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP),\n                               kBaseGroupOffset-i, 0));\n    }\n\n    for (int i=0; i < n_groups*n_disks_per_group; i++) {\n      ASSERT_TRUE(sh.addDisk(Disk(i+1, ConfigStatus::kRW, ActiveStatus::kOnline, 1),\n                             kBaseGroupOffset - i/n_disks_per_group));\n    }\n\n  }\n  auto cluster_data = mgr.getClusterData();\n  for (int i=0; i<n_groups;i++) {\n    for (auto strategy :{PlacementStrategyT::kRoundRobin,\n                          PlacementStrategyT::kThreadLocalRoundRobin,\n                          PlacementStrategyT::kRandom,\n                          PlacementStrategyT::kWeightedRandom,\n                          PlacementStrategyT::kWeightedRoundRobin}) {\n      PlacementArguments args {2, ConfigStatus::kRW, strategy};\n      args.forced_group_index = i;\n      auto result = flat_scheduler.schedule(cluster_data(),\n                                            args);\n      EXPECT_TRUE(result);\n      EXPECT_TRUE(result.is_valid_placement(2));\n      auto bucket = cluster_data().buckets.at(-kBaseGroupOffset+i);\n      auto bucket_contains = [&bucket](int id) {\n        return std::find(bucket.items.begin(), bucket.items.end(), id) != bucket.items.end();\n      };\n      for (int i = 0; i <2; i++) {\n        EXPECT_TRUE(bucket_contains(result.ids[i]));\n      }\n    }\n  }\n}\n\nTEST(FlatScheduler, ForcedGroupOutofRange)\n{\n  using namespace eos::mgm::placement;\n  using namespace eos::mgm::placement;\n  eos::mgm::placement::ClusterMgr mgr;\n  int n_elements = 1024;\n  int n_disks_per_group = 16;\n  int n_groups = 32;\n  eos::mgm::placement::FlatScheduler flat_scheduler(2048);\n\n  {\n\n    auto sh = mgr.getStorageHandler(n_elements);\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0));\n    for (int i=0; i< n_groups; ++i) {\n      ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP),\n                               kBaseGroupOffset-i, 0));\n    }\n\n    for (int i=0; i < n_groups*n_disks_per_group; i++) {\n      ASSERT_TRUE(sh.addDisk(Disk(i+1, ConfigStatus::kRW, ActiveStatus::kOnline, 1),\n                             kBaseGroupOffset - i/n_disks_per_group));\n    }\n\n  }\n  auto cluster_data = mgr.getClusterData();\n  for (auto strategy :{PlacementStrategyT::kRoundRobin,\n                       PlacementStrategyT::kThreadLocalRoundRobin,\n                       PlacementStrategyT::kRandom,\n                       PlacementStrategyT::kWeightedRandom,\n                       PlacementStrategyT::kWeightedRoundRobin}) {\n    PlacementArguments args {2, ConfigStatus::kRW, strategy};\n    args.forced_group_index = 4000;\n    auto result = flat_scheduler.schedule(cluster_data(),\n                                          args);\n\n    EXPECT_FALSE(result);\n    EXPECT_EQ(result.error_string(),\"Invalid forced group index\");\n  }\n}\n\nTEST(FlatScheduler, TLNoSiteUniformWeighted)\n{\n  using namespace eos::mgm::placement;\n  eos::mgm::placement::ClusterMgr mgr;\n  int n_elements = 1024;\n  int n_disks_per_group = 16;\n  int n_groups = 32;\n  eos::mgm::placement::FlatScheduler flat_scheduler(PlacementStrategyT::kWeightedRandom,\n                                                    2048);\n\n  {\n\n    auto sh = mgr.getStorageHandler(n_elements);\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0));\n    for (int i=0; i< n_groups; ++i) {\n      ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100-i, 0));\n    }\n\n    for (int i=0; i < n_groups*n_disks_per_group; i++) {\n      ASSERT_TRUE(sh.addDisk(Disk(i+1, ConfigStatus::kRW, ActiveStatus::kOnline, 1),\n                             -100 - i/n_disks_per_group));\n    }\n\n  }\n  auto cluster_data = mgr.getClusterData();\n  EXPECT_EQ(cluster_data->disks.size(), 32*16);\n  EXPECT_EQ(cluster_data->buckets.size(), n_elements);\n  auto root_bucket = cluster_data->buckets[0];\n  EXPECT_EQ(root_bucket.items.size(), n_groups);\n  for (auto it: root_bucket.items) {\n    EXPECT_EQ(cluster_data->buckets.at(-it).items.size(), n_disks_per_group);\n  }\n\n  for (int i = 0; i < 1000; i++) {\n    auto result = flat_scheduler.schedule(cluster_data(), {2});\n    ASSERT_TRUE(result);\n    ASSERT_TRUE(result.is_valid_placement(2));\n  }\n}\n\nTEST(FlatScheduler, TLNoSiteUniformWeightedRR)\n{\n  using namespace eos::mgm::placement;\n  eos::mgm::placement::ClusterMgr mgr;\n  int n_elements = 1024;\n  int n_disks_per_group = 16;\n  int n_groups = 32;\n  eos::mgm::placement::FlatScheduler flat_scheduler(PlacementStrategyT::kWeightedRoundRobin,\n                                                    2048);\n\n  {\n\n    auto sh = mgr.getStorageHandler(n_elements);\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0));\n    for (int i=0; i< n_groups; ++i) {\n      ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100-i, 0));\n    }\n\n    for (int i=0; i < n_groups*n_disks_per_group; i++) {\n      ASSERT_TRUE(sh.addDisk(Disk(i+1, ConfigStatus::kRW, ActiveStatus::kOnline, 1),\n                             -100 - i/n_disks_per_group));\n    }\n\n  }\n  auto cluster_data = mgr.getClusterData();\n  EXPECT_EQ(cluster_data->disks.size(), 32*16);\n  EXPECT_EQ(cluster_data->buckets.size(), n_elements);\n  auto root_bucket = cluster_data->buckets[0];\n  EXPECT_EQ(root_bucket.items.size(), n_groups);\n  for (auto it: root_bucket.items) {\n    EXPECT_EQ(cluster_data->buckets.at(-it).items.size(), n_disks_per_group);\n  }\n\n  for (int i = 0; i < 1000; i++) {\n    auto result = flat_scheduler.schedule(cluster_data(), {2});\n    ASSERT_TRUE(result);\n    ASSERT_TRUE(result.is_valid_placement(2));\n  }\n}\n\n\nTEST(FlatScheduler, TLNoSiteWeighted)\n{\n  using namespace eos::mgm::placement;\n  eos::mgm::placement::ClusterMgr mgr;\n  int n_elements = 1024;\n  int n_disks_per_group = 32;\n  int n_groups = 32;\n  eos::mgm::placement::FlatScheduler flat_scheduler(PlacementStrategyT::kWeightedRandom,\n                                                    2048);\n  std::map<int, int> disk_wt_map;\n  std::map<int, int> disk_wt_count;\n  {\n    std::vector<int> weights = {4, 8, 16, 32};\n    auto sh = mgr.getStorageHandler(n_elements);\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0));\n    for (int i=0; i< n_groups; ++i) {\n      ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100-i, 0));\n    }\n\n    for (int i=0; i < n_groups*n_disks_per_group; i++) {\n      auto weight = eos::common::pickIndexRR(weights, i);\n      disk_wt_map[i+1] = weight;\n      disk_wt_count[weight]++;\n      ASSERT_TRUE(sh.addDisk(Disk(i+1, ConfigStatus::kRW, ActiveStatus::kOnline,\n                                  weight),\n                             -100 - i/n_disks_per_group));\n    }\n\n  }\n  auto cluster_data = mgr.getClusterData();\n  EXPECT_EQ(cluster_data->disks.size(), 32*32);\n  EXPECT_EQ(cluster_data->buckets.size(), n_elements);\n  auto root_bucket = cluster_data->buckets[0];\n  EXPECT_EQ(root_bucket.items.size(), n_groups);\n  for (auto it: root_bucket.items) {\n    EXPECT_EQ(cluster_data->buckets.at(-it).items.size(), n_disks_per_group);\n  }\n  std::map<int, int> weight_counter;\n\n  for (int i = 0; i < 1024; i++) {\n    auto result = flat_scheduler.schedule(cluster_data(), {2});\n    ASSERT_TRUE(result);\n    ASSERT_TRUE(result.is_valid_placement(2));\n    weight_counter[disk_wt_map[result.ids[0]]]++;\n    weight_counter[disk_wt_map[result.ids[1]]]++;\n  }\n  ASSERT_TRUE(weight_counter[4] < weight_counter[8]);\n  ASSERT_TRUE(weight_counter[8] < weight_counter[16]);\n  ASSERT_TRUE(weight_counter[16] < weight_counter[32]);\n  std::cout << \"Cluster Disk weight count: \" << std::endl;\n  for (const auto &kv: disk_wt_count) {\n    std::cout << kv.first << \" : \" << kv.second << std::endl;\n  }\n\n  std::cout << \"Scheduling Disk Weight distribution: \" << std::endl;\n  for (const auto &kv: weight_counter) {\n    std::cout << kv.first << \" : \" << kv.second << std::endl;\n  }\n}\n\n\nTEST(FlatScheduler, TLNoSiteWeightedRR)\n{\n  using namespace eos::mgm::placement;\n  eos::mgm::placement::ClusterMgr mgr;\n  int n_elements = 1024;\n  int n_disks_per_group = 32;\n  int n_groups = 32;\n  eos::mgm::placement::FlatScheduler flat_scheduler(PlacementStrategyT::kWeightedRoundRobin,\n                                                    2048);\n  std::map<int, int> disk_wt_map;\n  std::map<int, int> disk_wt_count;\n  {\n    std::vector<int> weights = {4, 8, 16, 32};\n    auto sh = mgr.getStorageHandler(n_elements);\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0));\n    for (int i=0; i< n_groups; ++i) {\n      ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100-i, 0));\n    }\n\n    for (int i=0; i < n_groups*n_disks_per_group; i++) {\n      auto weight = eos::common::pickIndexRR(weights, i);\n      disk_wt_map[i+1] = weight;\n      disk_wt_count[weight]++;\n      ASSERT_TRUE(sh.addDisk(Disk(i+1, ConfigStatus::kRW, ActiveStatus::kOnline,\n                                  weight),\n                             -100 - i/n_disks_per_group));\n    }\n\n  }\n  auto cluster_data = mgr.getClusterData();\n  EXPECT_EQ(cluster_data->disks.size(), 32*32);\n  EXPECT_EQ(cluster_data->buckets.size(), n_elements);\n  auto root_bucket = cluster_data->buckets[0];\n  EXPECT_EQ(root_bucket.items.size(), n_groups);\n  for (auto it: root_bucket.items) {\n    EXPECT_EQ(cluster_data->buckets.at(-it).items.size(), n_disks_per_group);\n  }\n  std::map<int, int> weight_counter;\n  // with Interleaved Weighted RR, you'd need at least weight*n_items to show the\n  // distribution at lower numbers below the full wt of a category, you'd end up\n  // with uniform ie. for the previous case of 1024 schedulings, you'd end up\n  // with equal distribution as you've not finished a full round of each weights yet\n  for (int i = 0; i < 60*256; i++) {\n    auto result = flat_scheduler.schedule(cluster_data(), {2});\n    ASSERT_TRUE(result);\n    ASSERT_TRUE(result.is_valid_placement(2));\n    weight_counter[disk_wt_map[result.ids[0]]]++;\n    weight_counter[disk_wt_map[result.ids[1]]]++;\n  }\n  ASSERT_TRUE(weight_counter[4] < weight_counter[8]);\n  ASSERT_TRUE(weight_counter[8] < weight_counter[16]);\n  ASSERT_TRUE(weight_counter[16] < weight_counter[32]);\n\n  std::cout << \"Cluster Disk weight count: \" << std::endl;\n  for (const auto &kv: disk_wt_count) {\n    std::cout << kv.first << \" : \" << kv.second << std::endl;\n  }\n\n  std::cout << \"Scheduling Disk Weight distribution: \" << std::endl;\n  for (const auto &kv: weight_counter) {\n    std::cout << kv.first << \" : \" << kv.second << std::endl;\n  }\n\n  // schedule one more for offby1 errors\n  ASSERT_TRUE(flat_scheduler.schedule(cluster_data(), {2}));\n}\n\nvoid printProcessMemoryUsage() {\n  std::ifstream status_file(\"/proc/self/status\");\n  std::string line;\n  while (std::getline(status_file, line)) {\n    if (line.find(\"VmRSS\") == 0 || line.find(\"VmSize\") == 0) {\n      std::cout << line << std::endl;\n    }\n  }\n}\n\nTEST(ClusterMap, Concurrency)\n{\n  using namespace eos::mgm::placement;\n  ClusterMgr mgr;\n  using eos::mgm::placement::PlacementStrategyT;\n\n  std::mutex log_mtx;\n  printProcessMemoryUsage();\n  eos::mgm::placement::FlatScheduler flat_scheduler(PlacementStrategyT::kRoundRobin,\n                                                    2048);\n\n  {\n    auto sh = mgr.getStorageHandler(1024);\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::ROOT), 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::SITE), -1, 0));\n    ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP), -100, -1));\n\n    ASSERT_TRUE(sh.addDisk(Disk(1, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(2, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(3, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(4, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n    ASSERT_TRUE(sh.addDisk(Disk(5, ConfigStatus::kRW, ActiveStatus::kOnline, 1), -100));\n  }\n\n  printProcessMemoryUsage();\n  auto mgr_ptr = &mgr;\n\n  auto add_fn = [mgr_ptr, &log_mtx]() {\n    for (int i=0; i < 10; i++) {\n      std::cout << \"Writer thread: \" << std::this_thread::get_id() << \" ctr\"\n                << i << std::endl;\n      {\n        auto sh = mgr_ptr->getStorageHandlerWithData();\n        auto group_id = -101 - i;\n        std::cout << \"Adding group with id=\" << group_id << std::endl;\n        ASSERT_TRUE(sh.addBucket(get_bucket_type(StdBucketType::GROUP),\n                                 group_id, -1));\n        for (int k = 0; k < 10; k++) {\n          ASSERT_TRUE(sh.addDisk(Disk((i+1)*10 + k + 1, ConfigStatus::kRW, ActiveStatus::kOnline, 1), group_id));\n        }\n      }\n    }\n    {\n      std::scoped_lock log_lock(log_mtx);\n      printProcessMemoryUsage();\n      std::cout << \"Done with writer at \" << std::this_thread::get_id() << std::endl;\n    }\n  };\n\n\n\n  auto read_fn = [&flat_scheduler, mgr_ptr, &log_mtx]() {\n    for (int i=0; i < 1000; i++) {\n      auto data = mgr_ptr->getClusterData();\n\n      ASSERT_TRUE(data->buckets.size());\n      ASSERT_TRUE(data->disks.size());\n      auto result = flat_scheduler.schedule(data(), {2});\n\n      ASSERT_TRUE(result);\n      ASSERT_TRUE(result.is_valid_placement(2));\n    }\n    {\n      std::scoped_lock log_lock(log_mtx);\n      printProcessMemoryUsage();\n      std::cout << \"Done with reader at \" << std::this_thread::get_id() << std::endl;\n    }\n  };\n\n  std::vector<std::thread> reader_threads;\n  for (int i=0; i<100;i++) {\n    reader_threads.emplace_back(read_fn);\n  }\n\n  std::vector<std::thread> writer_threads;\n  for (int i=0; i < 5; i++) {\n    writer_threads.emplace_back(add_fn);\n  }\n\n  for (auto& t: writer_threads) {\n    t.join();\n  }\n\n  for (auto& t: reader_threads) {\n    t.join();\n  }\n\n}\n"
  },
  {
    "path": "unit_tests/mgm/placement/ThreadLocalRRSeedTests.cc",
    "content": "// ----------------------------------------------------------------------\n// File: ThreadLocalRRSeedTests.cc\n// Author: Abhishek Lekshmanan - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2023 CERN/Switzerland                           *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/placement/ThreadLocalRRSeed.hh\"\n#include \"gtest/gtest.h\"\n\nusing eos::mgm::placement::ThreadLocalRRSeed;\n\nTEST(ThreadLocalRRSeed, random)\n{\n  if (ThreadLocalRRSeed::getNumSeeds() < 10) {\n    std::cout << \"Not enough seeds, initializing with 10 seeds\";\n  } else {\n    std::cout << \"Already initialized!\";\n  }\n\n   std::vector<uint64_t> seeds;\n  for (auto i = 0; i < 10; i++) {\n    std::cout << ThreadLocalRRSeed::gRRSeeds[i] << \" \";\n    seeds.push_back(ThreadLocalRRSeed::gRRSeeds[i]);\n  }\n  std::cout << \"\\n\";\n\n  EXPECT_EQ(ThreadLocalRRSeed::get(0, 0), seeds[0]);\n  EXPECT_EQ(ThreadLocalRRSeed::get(0, 1), seeds[0]);\n  EXPECT_EQ(ThreadLocalRRSeed::get(0, 0), seeds[0] + 1);\n}\n"
  },
  {
    "path": "unit_tests/mgm/tgc/CachedValueTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: TgcCachedValueTests.cc\n// Author: Steven Murray <smurray at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/tgc/CachedValue.hh\"\n\n#include <gtest/gtest.h>\n#include <stdint.h>\n\nclass TgcCachedValueTest : public ::testing::Test {\nprotected:\n\n  virtual void SetUp() {\n  }\n\n  virtual void TearDown() {\n  }\n};\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcCachedValueTest, get_value_changed_no_cache)\n{\n  using namespace eos::mgm::tgc;\n\n  uint64_t sourceValue = 1234;\n  auto getter = [&sourceValue]()->uint64_t{return sourceValue;};\n  const time_t maxAgeSecs = 0; // No cache\n  CachedValue<uint64_t> cachedValue(getter, maxAgeSecs);\n\n  const auto value1 = cachedValue.get();\n\n  sourceValue = 5678;\n  const auto value2 = cachedValue.get();\n\n  ASSERT_EQ(1234, value1);\n  ASSERT_EQ(5678, value2);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcCachedValueTest, get_value_changed_cached)\n{\n  using namespace eos::mgm::tgc;\n\n  uint64_t sourceValue = 1234;\n  auto getter = [&sourceValue]()->uint64_t{return sourceValue;};\n  const time_t maxAgeSecs = 1000; // Cached\n  CachedValue<uint64_t> cachedValue(getter, maxAgeSecs);\n\n  const auto value1 = cachedValue.get();\n\n  sourceValue = 5678;\n  const auto value2 = cachedValue.get();\n\n  ASSERT_EQ(1234, value1);\n  ASSERT_EQ(1234, value2);\n}\n"
  },
  {
    "path": "unit_tests/mgm/tgc/FreedBytesHistogramTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: TgcFreedBytesHistogramTests.cc\n// Author: Steven Murray <smurray at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/tgc/Constants.hh\"\n#include \"mgm/tgc/DummyClock.hh\"\n#include \"mgm/tgc/FreedBytesHistogram.hh\"\n#include \"mgm/tgc/RealClock.hh\"\n\n#include <gtest/gtest.h>\n\nclass TgcFreedBytesHistogramTest : public ::testing::Test {\nprotected:\n\n  virtual void SetUp() {\n  }\n\n  virtual void TearDown() {\n  }\n};\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcFreedBytesHistogramTest, constructor)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::size_t nbBins = 600;\n  const std::uint32_t binWidthSecs = 1;\n\n  RealClock clock;\n  FreedBytesHistogram histogram(nbBins, binWidthSecs, clock);\n\n  ASSERT_EQ(nbBins, histogram.getNbBins());\n  ASSERT_EQ(binWidthSecs, histogram.getBinWidthSecs());\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  const time_t historyLimitSecs = nbBins * binWidthSecs;\n  for (time_t lastNbSecs = 0; lastNbSecs <= historyLimitSecs; lastNbSecs++) {\n    ASSERT_EQ(0, histogram.getNbBytesFreedInLastNbSecs(lastNbSecs));\n  }\n  ASSERT_THROW(histogram.getNbBytesFreedInLastNbSecs(historyLimitSecs + 1), FreedBytesHistogram::TooFarBackInTime);\n\n  for(std::uint32_t binIndex = 0; binIndex < nbBins; binIndex++) {\n    ASSERT_EQ(0, histogram.getFreedBytesInBin(binIndex));\n  }\n  ASSERT_THROW(histogram.getFreedBytesInBin(nbBins), FreedBytesHistogram::InvalidBinIndex);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcFreedBytesHistogramTest, getTotalBytesFreed_default)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::size_t nbBins = TGC_FREED_BYTES_HISTOGRAM_NB_BINS;\n  const std::uint32_t binWidthSecs = TGC_DEFAULT_FREED_BYTES_HISTOGRAM_BIN_WIDTH_SECS;\n\n  DummyClock clock(1000);\n  FreedBytesHistogram histogram(nbBins, binWidthSecs, clock);\n\n  ASSERT_EQ(nbBins, histogram.getNbBins());\n  ASSERT_EQ(binWidthSecs, histogram.getBinWidthSecs());\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  const std::uint32_t historyLimitSecs = nbBins * binWidthSecs;\n  std::uint64_t totalFreedBytes = 0;\n  for (std::uint32_t i = 1; i <= historyLimitSecs; i++) {\n    clock.setTime(999 + i);\n    histogram.bytesFreed(i);\n    totalFreedBytes += i;\n  }\n  ASSERT_THROW(histogram.getNbBytesFreedInLastNbSecs(historyLimitSecs + 1), FreedBytesHistogram::TooFarBackInTime);\n\n  ASSERT_EQ(totalFreedBytes, histogram.getTotalBytesFreed());\n\n  for (std::uint32_t i = 1; i <= historyLimitSecs; i++) {\n    const std::uint32_t binIndex = i - 1;\n    const std::uint32_t expectedFreedBytes = historyLimitSecs - binIndex;\n    ASSERT_EQ(expectedFreedBytes, histogram.getFreedBytesInBin(binIndex));\n  }\n\n  ASSERT_THROW(histogram.getFreedBytesInBin(historyLimitSecs), FreedBytesHistogram::InvalidBinIndex);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcFreedBytesHistogramTest, constructor_nbBins_0)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::size_t nbBins = 0;\n  const std::uint32_t binWidthSecs = 1;\n\n  RealClock clock;\n  ASSERT_THROW(FreedBytesHistogram(nbBins, binWidthSecs, clock), FreedBytesHistogram::InvalidNbBins);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcFreedBytesHistogramTest, constructor_nbBins_too_big)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::size_t nbBins = TGC_FREED_BYTES_HISTOGRAM_MAX_NB_BINS + 1;\n  const std::uint32_t binWidthSecs = 1;\n\n  RealClock clock;\n  ASSERT_THROW(FreedBytesHistogram(nbBins, binWidthSecs, clock), FreedBytesHistogram::InvalidNbBins);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcFreedBytesHistogramTest, constructor_binWidthSecs_0)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::size_t nbBins = 600;\n  const std::uint32_t binWidthSecs = 0;\n\n  RealClock clock;\n  ASSERT_THROW(FreedBytesHistogram(nbBins, binWidthSecs, clock), FreedBytesHistogram::InvalidBinWidth);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcFreedBytesHistogramTest, constructor_binWidthSecs_too_big)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::size_t nbBins = 600;\n  const std::uint32_t binWidthSecs = TGC_FREED_BYTES_HISTOGRAM_MAX_BIN_WIDTH_SECS + 1;\n\n  RealClock clock;\n  ASSERT_THROW(FreedBytesHistogram(nbBins, binWidthSecs, clock), FreedBytesHistogram::InvalidBinWidth);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcFreedBytesHistogramTest, setBinWidthSecs_0)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::size_t nbBins = 600;\n  const std::uint32_t binWidthSecs = 1;\n\n  RealClock clock;\n  FreedBytesHistogram histogram(nbBins, binWidthSecs, clock);\n\n  ASSERT_THROW(histogram.setBinWidthSecs(0), FreedBytesHistogram::InvalidBinWidth);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcFreedBytesHistogramTest, setBinWidthSecs_too_big)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::size_t nbBins = 600;\n  const std::uint32_t binWidthSecs = 1;\n\n  RealClock clock;\n  FreedBytesHistogram histogram(nbBins, binWidthSecs, clock);\n\n  ASSERT_THROW(histogram.setBinWidthSecs(TGC_FREED_BYTES_HISTOGRAM_MAX_BIN_WIDTH_SECS + 1),\n    FreedBytesHistogram::InvalidBinWidth);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcFreedBytesHistogramTest, bytesFreed)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::size_t nbBins = 3;\n  const std::uint32_t binWidthSecs = 3;\n  const time_t historyLimitSecs = nbBins * binWidthSecs;\n  const std::time_t initialTime = 1000;\n  DummyClock clock(initialTime);\n  FreedBytesHistogram histogram(nbBins, binWidthSecs, clock);\n\n  ASSERT_EQ(nbBins, histogram.getNbBins());\n  ASSERT_EQ(binWidthSecs, histogram.getBinWidthSecs());\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  for (time_t lastNbSecs = 0; lastNbSecs <= historyLimitSecs; lastNbSecs++) {\n    ASSERT_EQ(0, histogram.getNbBytesFreedInLastNbSecs(historyLimitSecs));\n  }\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  clock.setTime(1000); histogram.bytesFreed(1);\n  clock.setTime(1003); histogram.bytesFreed(2);\n  clock.setTime(1006); histogram.bytesFreed(3);\n\n  ASSERT_EQ(6, histogram.getTotalBytesFreed());\n\n  ASSERT_EQ(3, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(2, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(1, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n\n  ASSERT_EQ(0, histogram.getNbBytesFreedInLastNbSecs(0));\n  ASSERT_EQ(3, histogram.getNbBytesFreedInLastNbSecs(1));\n  ASSERT_EQ(3, histogram.getNbBytesFreedInLastNbSecs(2));\n  ASSERT_EQ(3, histogram.getNbBytesFreedInLastNbSecs(3));\n  ASSERT_EQ(5, histogram.getNbBytesFreedInLastNbSecs(4));\n  ASSERT_EQ(5, histogram.getNbBytesFreedInLastNbSecs(5));\n  ASSERT_EQ(5, histogram.getNbBytesFreedInLastNbSecs(6));\n  ASSERT_EQ(6, histogram.getNbBytesFreedInLastNbSecs(7));\n  ASSERT_EQ(6, histogram.getNbBytesFreedInLastNbSecs(8));\n  ASSERT_EQ(6, histogram.getNbBytesFreedInLastNbSecs(9));\n  ASSERT_THROW(histogram.getNbBytesFreedInLastNbSecs(10), FreedBytesHistogram::TooFarBackInTime);\n\n  clock.setTime(1009); histogram.bytesFreed(4);\n  clock.setTime(1012); histogram.bytesFreed(5);\n  clock.setTime(1015); histogram.bytesFreed(6);\n\n  ASSERT_EQ(15, histogram.getTotalBytesFreed());\n\n  ASSERT_EQ(6, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(5, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(4, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n\n  ASSERT_EQ( 0, histogram.getNbBytesFreedInLastNbSecs(0));\n  ASSERT_EQ( 6, histogram.getNbBytesFreedInLastNbSecs(1));\n  ASSERT_EQ( 6, histogram.getNbBytesFreedInLastNbSecs(2));\n  ASSERT_EQ( 6, histogram.getNbBytesFreedInLastNbSecs(3));\n  ASSERT_EQ(11, histogram.getNbBytesFreedInLastNbSecs(4));\n  ASSERT_EQ(11, histogram.getNbBytesFreedInLastNbSecs(5));\n  ASSERT_EQ(11, histogram.getNbBytesFreedInLastNbSecs(6));\n  ASSERT_EQ(15, histogram.getNbBytesFreedInLastNbSecs(7));\n  ASSERT_EQ(15, histogram.getNbBytesFreedInLastNbSecs(8));\n  ASSERT_EQ(15, histogram.getNbBytesFreedInLastNbSecs(9));\n  ASSERT_THROW(histogram.getNbBytesFreedInLastNbSecs(10), FreedBytesHistogram::TooFarBackInTime);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcFreedBytesHistogramTest, setBinWidth_from_3_to_4)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::size_t nbBins = 3;\n  const std::uint32_t binWidthSecs = 3;\n  const time_t historyLimitSecs = nbBins * binWidthSecs;\n  const std::time_t initialTime = 1000;\n  DummyClock clock(initialTime);\n  FreedBytesHistogram histogram(nbBins, binWidthSecs, clock);\n\n  ASSERT_EQ(nbBins, histogram.getNbBins());\n  ASSERT_EQ(binWidthSecs, histogram.getBinWidthSecs());\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  for (time_t lastNbSecs = 0; lastNbSecs <= historyLimitSecs; lastNbSecs++) {\n    ASSERT_EQ(0, histogram.getNbBytesFreedInLastNbSecs(historyLimitSecs));\n  }\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  clock.setTime(1000); histogram.bytesFreed(1);\n  clock.setTime(1003); histogram.bytesFreed(2);\n  clock.setTime(1006); histogram.bytesFreed(3);\n\n  ASSERT_EQ(3, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(2, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(1, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n\n  const std::uint32_t newBinWidthSecs = 4;\n  histogram.setBinWidthSecs(newBinWidthSecs);\n\n  ASSERT_EQ(nbBins, histogram.getNbBins());\n  ASSERT_EQ(newBinWidthSecs, histogram.getBinWidthSecs());\n\n  ASSERT_EQ(4, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(2, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(0, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcFreedBytesHistogramTest, setBinWidth_from_3_to_5)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::size_t nbBins = 3;\n  const std::uint32_t binWidthSecs = 3;\n  const time_t historyLimitSecs = nbBins * binWidthSecs;\n  const std::time_t initialTime = 1000;\n  DummyClock clock(initialTime);\n  FreedBytesHistogram histogram(nbBins, binWidthSecs, clock);\n\n  ASSERT_EQ(nbBins, histogram.getNbBins());\n  ASSERT_EQ(binWidthSecs, histogram.getBinWidthSecs());\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  for (time_t lastNbSecs = 0; lastNbSecs <= historyLimitSecs; lastNbSecs++) {\n    ASSERT_EQ(0, histogram.getNbBytesFreedInLastNbSecs(historyLimitSecs));\n  }\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  clock.setTime(1000); histogram.bytesFreed(1);\n  clock.setTime(1003); histogram.bytesFreed(2);\n  clock.setTime(1006); histogram.bytesFreed(3);\n\n  ASSERT_EQ(3, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(2, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(1, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n\n  const std::uint32_t newBinWidthSecs = 5;\n  histogram.setBinWidthSecs(newBinWidthSecs);\n\n  ASSERT_EQ(nbBins, histogram.getNbBins());\n  ASSERT_EQ(newBinWidthSecs, histogram.getBinWidthSecs());\n\n  ASSERT_EQ(5, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(1, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(0, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcFreedBytesHistogramTest, setBinWidth_from_3_to_6)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::size_t nbBins = 3;\n  const std::uint32_t binWidthSecs = 3;\n  const time_t historyLimitSecs = nbBins * binWidthSecs;\n  const std::time_t initialTime = 1000;\n  DummyClock clock(initialTime);\n  FreedBytesHistogram histogram(nbBins, binWidthSecs, clock);\n\n  ASSERT_EQ(nbBins, histogram.getNbBins());\n  ASSERT_EQ(binWidthSecs, histogram.getBinWidthSecs());\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  for (time_t lastNbSecs = 0; lastNbSecs <= historyLimitSecs; lastNbSecs++) {\n    ASSERT_EQ(0, histogram.getNbBytesFreedInLastNbSecs(historyLimitSecs));\n  }\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  clock.setTime(1000); histogram.bytesFreed(1);\n  clock.setTime(1003); histogram.bytesFreed(2);\n  clock.setTime(1006); histogram.bytesFreed(3);\n\n  ASSERT_EQ(3, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(2, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(1, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n\n  const std::uint32_t newBinWidthSecs = 6;\n  histogram.setBinWidthSecs(newBinWidthSecs);\n\n  ASSERT_EQ(nbBins, histogram.getNbBins());\n  ASSERT_EQ(newBinWidthSecs, histogram.getBinWidthSecs());\n\n  ASSERT_EQ(6, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(0, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(0, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcFreedBytesHistogramTest, setBinWidth_from_3_to_2)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::size_t nbBins = 3;\n  const std::uint32_t binWidthSecs = 3;\n  const time_t historyLimitSecs = nbBins * binWidthSecs;\n  const std::time_t initialTime = 1000;\n  DummyClock clock(initialTime);\n  FreedBytesHistogram histogram(nbBins, binWidthSecs, clock);\n\n  ASSERT_EQ(nbBins, histogram.getNbBins());\n  ASSERT_EQ(binWidthSecs, histogram.getBinWidthSecs());\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  for (time_t lastNbSecs = 0; lastNbSecs <= historyLimitSecs; lastNbSecs++) {\n    ASSERT_EQ(0, histogram.getNbBytesFreedInLastNbSecs(historyLimitSecs));\n  }\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  clock.setTime(1000); histogram.bytesFreed(1);\n  clock.setTime(1003); histogram.bytesFreed(2);\n  clock.setTime(1006); histogram.bytesFreed(3);\n\n  ASSERT_EQ(3, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(2, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(1, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n\n  const std::uint32_t newBinWidthSecs = 2;\n  histogram.setBinWidthSecs(newBinWidthSecs);\n\n  ASSERT_EQ(nbBins, histogram.getNbBins());\n  ASSERT_EQ(newBinWidthSecs, histogram.getBinWidthSecs());\n\n  ASSERT_EQ(2, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(2, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(2, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcFreedBytesHistogramTest, setBinWidth_from_3_to_1)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::size_t nbBins = 3;\n  const std::uint32_t binWidthSecs = 3;\n  const time_t historyLimitSecs = nbBins * binWidthSecs;\n  const std::time_t initialTime = 1000;\n  DummyClock clock(initialTime);\n  FreedBytesHistogram histogram(nbBins, binWidthSecs, clock);\n\n  ASSERT_EQ(nbBins, histogram.getNbBins());\n  ASSERT_EQ(binWidthSecs, histogram.getBinWidthSecs());\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  for (time_t lastNbSecs = 0; lastNbSecs <= historyLimitSecs; lastNbSecs++) {\n    ASSERT_EQ(0, histogram.getNbBytesFreedInLastNbSecs(historyLimitSecs));\n  }\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  clock.setTime(1000); histogram.bytesFreed(1);\n  clock.setTime(1003); histogram.bytesFreed(2);\n  clock.setTime(1006); histogram.bytesFreed(3);\n\n  ASSERT_EQ(3, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(2, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(1, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n\n  const std::uint32_t newBinWidthSecs = 1;\n  histogram.setBinWidthSecs(newBinWidthSecs);\n\n  ASSERT_EQ(nbBins, histogram.getNbBins());\n  ASSERT_EQ(newBinWidthSecs, histogram.getBinWidthSecs());\n\n  ASSERT_EQ(1, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(1, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(1, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcFreedBytesHistogramTest, multiple_passes)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::size_t nbBins = 3;\n  const std::uint32_t binWidthSecs = 3;\n  const time_t historyLimitSecs = nbBins * binWidthSecs;\n  const std::time_t initialTime = 1000;\n  DummyClock clock(initialTime);\n  FreedBytesHistogram histogram(nbBins, binWidthSecs, clock);\n\n  ASSERT_EQ(nbBins, histogram.getNbBins());\n  ASSERT_EQ(binWidthSecs, histogram.getBinWidthSecs());\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  for (time_t lastNbSecs = 0; lastNbSecs <= historyLimitSecs; lastNbSecs++) {\n    ASSERT_EQ(0, histogram.getNbBytesFreedInLastNbSecs(historyLimitSecs));\n  }\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  clock.setTime(1000); histogram.bytesFreed(1);\n  clock.setTime(1003); histogram.bytesFreed(2);\n  clock.setTime(1006); histogram.bytesFreed(3);\n\n  ASSERT_EQ(3, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(2, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(1, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n\n  clock.setTime(1009); histogram.bytesFreed(4);\n  clock.setTime(1012); histogram.bytesFreed(5);\n  clock.setTime(1015); histogram.bytesFreed(6);\n\n  ASSERT_EQ(6, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(5, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(4, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n\n  clock.setTime(1018); histogram.bytesFreed(7);\n  clock.setTime(1021); histogram.bytesFreed(8);\n  clock.setTime(1024); histogram.bytesFreed(9);\n\n  ASSERT_EQ(9, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(8, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(7, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcFreedBytesHistogramTest, bytesFreed_many_times_same_bin)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::size_t nbBins = 3;\n  const std::uint32_t binWidthSecs = 3;\n  const time_t historyLimitSecs = nbBins * binWidthSecs;\n  const std::time_t initialTime = 1000;\n  DummyClock clock(initialTime);\n  FreedBytesHistogram histogram(nbBins, binWidthSecs, clock);\n\n  ASSERT_EQ(nbBins, histogram.getNbBins());\n  ASSERT_EQ(binWidthSecs, histogram.getBinWidthSecs());\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  for (time_t lastNbSecs = 0; lastNbSecs <= historyLimitSecs; lastNbSecs++) {\n    ASSERT_EQ(0, histogram.getNbBytesFreedInLastNbSecs(historyLimitSecs));\n  }\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  for (int i = 0; i < 100; i++) {\n    histogram.bytesFreed(1);\n  }\n\n  ASSERT_EQ(100, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(0, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(0, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcFreedBytesHistogramTest, time_gap_larger_than_histogram)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::size_t nbBins = 3;\n  const std::uint32_t binWidthSecs = 3;\n  const time_t historyLimitSecs = nbBins * binWidthSecs;\n  const std::time_t initialTime = 1000;\n  DummyClock clock(initialTime);\n  FreedBytesHistogram histogram(nbBins, binWidthSecs, clock);\n\n  ASSERT_EQ(nbBins, histogram.getNbBins());\n  ASSERT_EQ(binWidthSecs, histogram.getBinWidthSecs());\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  for (time_t lastNbSecs = 0; lastNbSecs <= historyLimitSecs; lastNbSecs++) {\n    ASSERT_EQ(0, histogram.getNbBytesFreedInLastNbSecs(historyLimitSecs));\n  }\n\n  ASSERT_EQ(0, histogram.getTotalBytesFreed());\n\n  clock.setTime(1000); histogram.bytesFreed(1);\n  clock.setTime(1003); histogram.bytesFreed(2);\n  clock.setTime(1006); histogram.bytesFreed(3);\n\n  ASSERT_EQ(3, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(2, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(1, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n\n  clock.setTime(9009); histogram.bytesFreed(4);\n  clock.setTime(9012); histogram.bytesFreed(5);\n  clock.setTime(9015); histogram.bytesFreed(6);\n\n  ASSERT_EQ(6, histogram.getFreedBytesInBin(0));\n  ASSERT_EQ(5, histogram.getFreedBytesInBin(1));\n  ASSERT_EQ(4, histogram.getFreedBytesInBin(2));\n  ASSERT_THROW(histogram.getFreedBytesInBin(4), FreedBytesHistogram::InvalidBinIndex);\n}\n"
  },
  {
    "path": "unit_tests/mgm/tgc/LruTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: LruTests.cc\n// Author: Steven Murray <smurray at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/tgc/Lru.hh\"\n#include \"mgm/tgc/MaxLenExceeded.hh\"\n\n#include <gtest/gtest.h>\n\nclass TgcLruTest : public ::testing::Test {\nprotected:\n\n  virtual void SetUp() {\n  }\n\n  virtual void TearDown() {\n  }\n};\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcLruTest, Construction_maxQueueSize_greater_than_zero)\n{\n  using namespace eos::mgm::tgc;\n\n  const Lru::FidQueue::size_type maxQueueSize = 5;\n  Lru lru(maxQueueSize);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcLruTest, Construction_maxQueueSize_zero)\n{\n  using namespace eos::mgm::tgc;\n\n  const Lru::FidQueue::size_type maxQueueSize = 0;\n  ASSERT_THROW(Lru lru(maxQueueSize),\n    Lru::MaxQueueSizeIsZero);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcLruTest, getAndPopFidOfLeastUsedFile_empty_queue)\n{\n  using namespace eos::mgm::tgc;\n\n  const Lru::FidQueue::size_type maxQueueSize = 5;\n  Lru lru(maxQueueSize);\n  ASSERT_THROW(lru.getAndPopFidOfLeastUsedFile(), Lru::QueueIsEmpty);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcLruTest, fids_1_2_3_4_5)\n{ \n  using namespace eos;\n  using namespace eos::mgm::tgc;\n\n  const std::list<IFileMD::id_t> fidsIn = {1, 2, 3, 4, 5};\n\n  const std::list<IFileMD::id_t> fidsOut = fidsIn;\n\n  const Lru::FidQueue::size_type maxQueueSize = fidsOut.size();\n  Lru lru(maxQueueSize);\n\n  for(const auto fid: fidsIn) {\n    lru.fileAccessed(fid);\n  }\n\n  ASSERT_EQ(fidsOut.size(), lru.size());\n\n  for(const auto fid: fidsOut) {\n    ASSERT_FALSE(lru.empty());\n    ASSERT_EQ(fid, lru.getAndPopFidOfLeastUsedFile());\n  }\n\n  ASSERT_TRUE(lru.empty());\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcLruTest, fids_1_2_3_4_5_2)\n{ \n  using namespace eos;\n  using namespace eos::mgm::tgc;\n\n  const std::list<IFileMD::id_t> fidsIn = {1, 2, 3, 4, 5, 2};\n\n  const std::list<IFileMD::id_t> fidsOut = {1, 3, 4, 5, 2};\n\n  const Lru::FidQueue::size_type maxQueueSize = fidsOut.size();\n  Lru lru(maxQueueSize);\n\n  for(const auto fid: fidsIn) {\n    lru.fileAccessed(fid);\n  }\n\n  ASSERT_EQ(fidsOut.size(), lru.size());\n\n  for(const auto fid: fidsOut) {\n    ASSERT_FALSE(lru.empty());\n    ASSERT_EQ(fid, lru.getAndPopFidOfLeastUsedFile());\n  }\n\n  ASSERT_TRUE(lru.empty());\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcLruTest, fileDeletedFromNamespace)\n{ \n  using namespace eos;\n  using namespace eos::mgm::tgc;\n\n  // Emulate deleting the file with ID 4 from the EOS namespace\n  const std::list<IFileMD::id_t> fidsIn = {1, 2, 3, 4, 5};\n  const std::list<IFileMD::id_t> fidsOut = {1, 2, 3, 5};\n\n  const Lru::FidQueue::size_type maxQueueSize = fidsIn.size();\n  Lru lru(maxQueueSize);\n\n  for(const auto fid: fidsIn) {\n    lru.fileAccessed(fid);\n  }\n\n  ASSERT_EQ(fidsIn.size(), lru.size());\n\n  lru.fileDeletedFromNamespace(4);\n\n  ASSERT_EQ(fidsOut.size(), lru.size());\n\n  for(const auto fid: fidsOut) {\n    ASSERT_FALSE(lru.empty());\n    ASSERT_EQ(fid, lru.getAndPopFidOfLeastUsedFile());\n  }\n\n  ASSERT_TRUE(lru.empty());\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcLruTest, exceed_maxQueueSize_max_size_1)\n{\n  using namespace eos;\n  using namespace eos::mgm::tgc;\n\n  const Lru::FidQueue::size_type maxQueueSize = 1;\n  Lru lru(maxQueueSize);\n\n  ASSERT_TRUE(lru.empty());\n  ASSERT_EQ(0, lru.size());\n  ASSERT_FALSE(lru.maxQueueSizeExceeded());\n\n  lru.fileAccessed(1);\n\n  ASSERT_FALSE(lru.empty());\n  ASSERT_EQ(1, lru.size());\n  ASSERT_FALSE(lru.maxQueueSizeExceeded());\n\n  lru.fileAccessed(2);\n\n  ASSERT_FALSE(lru.empty());\n  ASSERT_EQ(1, lru.size());\n  ASSERT_TRUE(lru.maxQueueSizeExceeded());\n\n  ASSERT_EQ(1, lru.getAndPopFidOfLeastUsedFile());\n\n  ASSERT_TRUE(lru.empty());\n  ASSERT_EQ(0, lru.size());\n  ASSERT_FALSE(lru.maxQueueSizeExceeded());\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcLruTest, exceed_maxQueueSize_5_fids_vs_max_size_2)\n{\n  using namespace eos;\n  using namespace eos::mgm::tgc;\n  const std::list<IFileMD::id_t> fidsIn = {1, 2, 3, 4, 5};\n\n  const std::list<IFileMD::id_t> fidsOut = {1, 2};\n\n  const Lru::FidQueue::size_type maxQueueSize = fidsOut.size();\n  Lru lru(maxQueueSize);\n\n  ASSERT_TRUE(lru.empty());\n  ASSERT_EQ(0, lru.size());\n  ASSERT_FALSE(lru.maxQueueSizeExceeded());\n\n  for(const auto fid: fidsIn) {\n    lru.fileAccessed(fid);\n\n    ASSERT_FALSE(lru.empty());\n\n    if(fid <= maxQueueSize) {\n      ASSERT_EQ(fid, lru.size());\n      ASSERT_FALSE(lru.maxQueueSizeExceeded());\n    } else {\n      ASSERT_EQ(maxQueueSize, lru.size());\n      ASSERT_TRUE(lru.maxQueueSizeExceeded());\n    }\n  }\n\n  ASSERT_EQ(maxQueueSize, lru.size());\n\n  for(const auto fid: fidsOut) {\n    ASSERT_FALSE(lru.empty());\n    ASSERT_EQ(fid, lru.getAndPopFidOfLeastUsedFile());\n    ASSERT_FALSE(lru.maxQueueSizeExceeded());\n  }\n\n  ASSERT_TRUE(lru.empty());\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcLruTest, DISABLED_performance_500000_files) {\n  using namespace eos;\n  using namespace eos::mgm::tgc;\n\n  const Lru::FidQueue::size_type maxQueueSize = 500000;\n  Lru lru(maxQueueSize);\n\n  for(Lru::FidQueue::size_type fid = 0; fid < maxQueueSize; fid++) {\n    const auto start = std::chrono::high_resolution_clock::now();\n    lru.fileAccessed(fid);\n    const auto end = std::chrono::high_resolution_clock::now();\n    std::chrono::duration<double> elapsed = end - start;\n    std::cout << \"Time elapsed for fid \" << fid << \" = \" << elapsed.count() << \" seconds\" << std::endl;\n  }\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcLruTest, toJson)\n{\n  using namespace eos;\n  using namespace eos::mgm::tgc;\n\n  const std::list<IFileMD::id_t> fidsIn = {1, 2};\n\n  const Lru::FidQueue::size_type maxQueueSize = fidsIn.size();\n  Lru lru(maxQueueSize);\n\n  for(const auto fid: fidsIn) {\n    lru.fileAccessed(fid);\n  }\n\n  ASSERT_EQ(fidsIn.size(), lru.size());\n\n  const std::string expectedJson =\n    \"{\\\"size\\\":\\\"2\\\",\\\"fids_from_MRU_to_LRU\\\":[\\\"0x0000000000000002\\\",\\\"0x0000000000000001\\\"]}\";\n\n  std::ostringstream json;\n  lru.toJson(json);\n  ASSERT_EQ(expectedJson, json.str());\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcLruTest, toJson_exceed_maxLen)\n{\n  using namespace eos;\n  using namespace eos::mgm::tgc;\n\n  const Lru::FidQueue::size_type maxQueueSize = 1;\n  Lru lru(maxQueueSize);\n\n  std::ostringstream json;\n  const std::string::size_type maxLen = 1;\n  ASSERT_THROW(lru.toJson(json, maxLen), MaxLenExceeded);\n}\n"
  },
  {
    "path": "unit_tests/mgm/tgc/MultiSpaceTapeGcTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: MultiSpaceTapeGcTests.cc\n// Author: Steven Murray <smurray at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/tgc/DummyTapeGcMgm.hh\"\n#include \"mgm/tgc/MaxLenExceeded.hh\"\n#include \"mgm/tgc/MultiSpaceTapeGc.hh\"\n\n#include <ctime>\n#include <gtest/gtest.h>\n\nclass TgcMultiSpaceTapeGcTest : public ::testing::Test {\nprotected:\n\n  void SetUp() override {\n  }\n\n  void TearDown() override {\n  }\n};\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcMultiSpaceTapeGcTest, constructor)\n{\n  using namespace eos::mgm::tgc;\n\n  DummyTapeGcMgm mgm;\n  MultiSpaceTapeGc gc(mgm);\n\n  const auto stats = gc.getStats();\n  ASSERT_TRUE(stats.empty());\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcMultiSpaceTapeGcTest, start_with_one_eos_space)\n{\n  using namespace eos::mgm::tgc;\n\n  DummyTapeGcMgm mgm;\n  MultiSpaceTapeGc gc(mgm);\n\n  const std::string space = \"space\";\n  std::set<std::string> spaces;\n  spaces.insert(space);\n  gc.setTapeEnabled(spaces);\n  gc.start();\n \n  const auto now = std::time(nullptr);\n  const auto stats = gc.getStats();\n  ASSERT_EQ(1, stats.size());\n\n  auto itor = stats.begin();\n  ASSERT_EQ(space, itor->first);\n  ASSERT_EQ(0, itor->second.nbEvicts);\n  ASSERT_EQ(0, itor->second.lruQueueSize);\n  ASSERT_TRUE(now <= itor->second.queryTimestamp && itor->second.queryTimestamp <= (now + 5));\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcMultiSpaceTapeGcTest, start_with_two_eos_spaces)\n{\n  using namespace eos::mgm::tgc;\n\n  DummyTapeGcMgm mgm;\n  MultiSpaceTapeGc gc(mgm);\n\n  const std::string space1 = \"space1\";\n  const std::string space2 = \"space2\";\n  std::set<std::string> spaces;\n  spaces.insert(space1);\n  spaces.insert(space2);\n  gc.setTapeEnabled(spaces);\n  gc.start();\n\n  const auto stats = gc.getStats();\n  ASSERT_EQ(2, stats.size());\n\n  auto itor = stats.begin();\n  ASSERT_EQ(space1, itor->first);\n  ASSERT_EQ(0, itor->second.nbEvicts);\n  ASSERT_EQ(0, itor->second.lruQueueSize);\n\n  itor++;\n  ASSERT_EQ(space2, itor->first);\n  ASSERT_EQ(0, itor->second.nbEvicts);\n  ASSERT_EQ(0, itor->second.lruQueueSize);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcMultiSpaceTapeGcTest, start_and_stop_with_one_eos_space)\n{\n  using namespace eos::mgm::tgc;\n\n  DummyTapeGcMgm mgm;\n  MultiSpaceTapeGc gc(mgm);\n\n  const std::string space = \"space\";\n  std::set<std::string> spaces;\n  spaces.insert(space);\n  gc.setTapeEnabled(spaces);\n  gc.start();\n \n  const auto statsBefore = gc.getStats();\n  ASSERT_EQ(1, statsBefore.size());\n\n  gc.stop();\n\n  const auto statsAfter = gc.getStats();\n  ASSERT_EQ(0, statsAfter.size());\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcMultiSpaceTapeGcTest, start_and_restart_with_one_eos_space)\n{\n  using namespace eos::mgm::tgc;\n\n  DummyTapeGcMgm mgm;\n  MultiSpaceTapeGc gc(mgm);\n\n  const std::string space = \"space\";\n  std::set<std::string> spaces;\n  spaces.insert(space);\n  gc.setTapeEnabled(spaces);\n  gc.start();\n \n  gc.stop();\n  gc.start();\n\n  const auto now = std::time(nullptr);\n  const auto stats = gc.getStats();\n  ASSERT_EQ(1, stats.size());\n\n  auto itor = stats.begin();\n  ASSERT_EQ(space, itor->first);\n  ASSERT_EQ(0, itor->second.nbEvicts);\n  ASSERT_EQ(0, itor->second.lruQueueSize);\n  ASSERT_TRUE(now <= itor->second.queryTimestamp && itor->second.queryTimestamp <= (now + 5));\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcMultiSpaceTapeGcTest, start_and_stop_with_two_eos_spaces)\n{\n  using namespace eos::mgm::tgc;\n\n  DummyTapeGcMgm mgm;\n  MultiSpaceTapeGc gc(mgm);\n\n  const std::string space1 = \"space1\";\n  const std::string space2 = \"space2\";\n  std::set<std::string> spaces;\n  spaces.insert(space1);\n  spaces.insert(space2);\n  gc.setTapeEnabled(spaces);\n  gc.start();\n\n  const auto statsBefore = gc.getStats();\n  ASSERT_EQ(2, statsBefore.size());\n\n  gc.stop();\n\n  const auto statsAfter = gc.getStats();\n  ASSERT_EQ(0, statsAfter.size());\n}\n\n"
  },
  {
    "path": "unit_tests/mgm/tgc/SmartSpaceStatsTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: SmartSpaceStatsTests.cc\n// Author: Steven Murray <smurray at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/tgc/DummyTapeGcMgm.hh\"\n#include \"mgm/tgc/SmartSpaceStats.hh\"\n\n#include <gtest/gtest.h>\n\nclass SmartSpaceStatsTest : public ::testing::Test {\nprotected:\n\n  virtual void SetUp() {\n  }\n\n  virtual void TearDown() {\n  }\n};\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(SmartSpaceStatsTest, Constructor) {\n  using namespace eos::mgm::tgc;\n\n  const std::string spaceName = \"test\";\n  DummyTapeGcMgm mgm;\n  SpaceConfig spaceConfig;\n  spaceConfig.availBytes = 10;\n  spaceConfig.totalBytes = 20;\n  spaceConfig.freeBytesScript = \"\";\n  spaceConfig.queryPeriodSecs = 1;\n  std::function<SpaceConfig()> spaceConfigGetter = [&] { return spaceConfig; };\n  const std::time_t spaceConfigMaxAgeSecs = 0;\n  CachedValue<SpaceConfig> cachedSpaceConfig(spaceConfigGetter, spaceConfigMaxAgeSecs);\n  SmartSpaceStats stats(spaceName, mgm, cachedSpaceConfig);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(SmartSpaceStatsTest, get_without_freebytesscript_set)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::string spaceName = \"test\";\n  DummyTapeGcMgm mgm;\n  SpaceConfig spaceConfig;\n  spaceConfig.availBytes = 10;\n  spaceConfig.totalBytes = 20;\n  spaceConfig.freeBytesScript = \"\";\n  spaceConfig.queryPeriodSecs = 1;\n  std::function<SpaceConfig()> spaceConfigGetter = [&]{return spaceConfig;};\n  const std::time_t spaceConfigMaxAgeSecs = 0;\n  CachedValue<SpaceConfig> cachedSpaceConfig(spaceConfigGetter, spaceConfigMaxAgeSecs);\n  SmartSpaceStats stats(spaceName, mgm, cachedSpaceConfig);\n\n  SpaceStats dummyMgmStats;\n  dummyMgmStats.totalBytes = 100;\n  dummyMgmStats.availBytes = 90;\n  mgm.setSpaceStats(spaceName, dummyMgmStats);\n\n  ASSERT_EQ(0, mgm.getNbCallsToGetSpaceStats());\n  const auto result = stats.get();\n  ASSERT_EQ(1, mgm.getNbCallsToGetSpaceStats());\n\n  ASSERT_EQ(dummyMgmStats, result.stats);\n  ASSERT_EQ(SmartSpaceStats::Src::INTERNAL_BECAUSE_SCRIPT_PATH_EMPTY, result.availBytesSrc);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(SmartSpaceStatsTest, get_with_freebytesscript_set)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::string spaceName = \"test\";\n\n  DummyTapeGcMgm mgm;\n  SpaceStats internalStats;\n  internalStats.totalBytes = 100;\n  internalStats.availBytes = 90;\n  mgm.setSpaceStats(spaceName, internalStats);\n  const std::string scriptAvailBytesString = \"80\";\n  const std::uint64_t scriptAvailBytesUint64 = 80;\n  mgm.setStdoutFromShellCmd(scriptAvailBytesString);\n\n  SpaceConfig spaceConfig;\n  spaceConfig.availBytes = 10;\n  spaceConfig.totalBytes = 20;\n  spaceConfig.freeBytesScript = \"test\";\n  spaceConfig.queryPeriodSecs = 1;\n  std::function<SpaceConfig()> spaceConfigGetter = [&]{return spaceConfig;};\n  const std::time_t spaceConfigMaxAgeSecs = 0;\n  CachedValue<SpaceConfig> cachedSpaceConfig(spaceConfigGetter, spaceConfigMaxAgeSecs);\n  SmartSpaceStats stats(spaceName, mgm, cachedSpaceConfig);\n\n  {\n    const auto result = stats.get();\n    switch(result.availBytesSrc) {\n    case SmartSpaceStats::Src::INTERNAL_BECAUSE_SCRIPT_PENDING_AND_NO_PREVIOUS_VALUE:\n      ASSERT_EQ(internalStats.availBytes, result.stats.availBytes);\n      break;\n    case SmartSpaceStats::Src::SCRIPT_VALUE_BECAUSE_SCRIPT_JUST_FINISHED:\n      ASSERT_EQ(scriptAvailBytesUint64, result.stats.availBytes);\n      break;\n    default:\n      FAIL() << \"Unexpected value for result.availBytesSrc: value=\" << SmartSpaceStats::srcToStr(result.availBytesSrc);\n    }\n  }\n\n  {\n    const auto result = stats.get();\n    switch(result.availBytesSrc) {\n    case SmartSpaceStats::Src::INTERNAL_BECAUSE_SCRIPT_PENDING_AND_NO_PREVIOUS_VALUE:\n      ASSERT_EQ(internalStats.availBytes, result.stats.availBytes);\n      break;\n    case SmartSpaceStats::Src::SCRIPT_VALUE_BECAUSE_SCRIPT_JUST_FINISHED:\n      ASSERT_EQ(scriptAvailBytesUint64, result.stats.availBytes);\n      break;\n    case SmartSpaceStats::Src::SCRIPT_PREVIOUS_VALUE_BECAUSE_SCRIPT_PENDING:\n      ASSERT_EQ(scriptAvailBytesUint64, result.stats.availBytes);\n      break;\n    default:\n      FAIL() << \"Unexpected value for result.availBytesSrc: value=\" << SmartSpaceStats::srcToStr(result.availBytesSrc);\n    }\n  }\n}"
  },
  {
    "path": "unit_tests/mgm/tgc/SpaceToTapeGcMapTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: SpaceToTapeGcMapTests.cc\n// Author: Steven Murray <smurray at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/tgc/DummyTapeGcMgm.hh\"\n#include \"mgm/tgc/MaxLenExceeded.hh\"\n#include \"mgm/tgc/SpaceToTapeGcMap.hh\"\n\n#include <gtest/gtest.h>\n\nclass TgcSpaceToTapeGcMapTest : public ::testing::Test {\nprotected:\n\n  virtual void SetUp() {\n  }\n\n  virtual void TearDown() {\n  }\n};\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcSpaceToTapeGcMapTest, Constructor)\n{\n  using namespace eos::mgm::tgc;\n\n  DummyTapeGcMgm mgm;\n  SpaceToTapeGcMap map(mgm);\n\n  const auto spaces = map.getSpaces();\n  ASSERT_TRUE(spaces.empty());\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcSpaceToTapeGcMapTest, getGc_unknown_eos_space)\n{\n  using namespace eos::mgm::tgc;\n\n  DummyTapeGcMgm mgm;\n  SpaceToTapeGcMap map(mgm);\n\n  const auto spaces = map.getSpaces();\n  ASSERT_TRUE(spaces.empty());\n\n  const std::string space = \"space\";\n\n  ASSERT_THROW(map.getGc(space), SpaceToTapeGcMap::UnknownEOSSpace);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcSpaceToTapeGcMapTest, createGc)\n{\n  using namespace eos::mgm::tgc;\n\n  DummyTapeGcMgm mgm;\n  SpaceToTapeGcMap map(mgm);\n  const std::string space = \"space\";\n\n  TapeGc &gc1 = map.createGc(space);\n  TapeGc &gc2 = map.getGc(space);\n  ASSERT_EQ(&gc1, &gc2);\n\n  const auto spaces = map.getSpaces();\n  ASSERT_EQ(1, spaces.size());\n  ASSERT_EQ(1, spaces.count(space));\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcSpaceToTapeGcMapTest, createGc_already_exists)\n{\n  using namespace eos::mgm::tgc;\n\n  DummyTapeGcMgm mgm;\n  SpaceToTapeGcMap map(mgm);\n  const std::string space = \"space\";\n\n  map.createGc(space);\n\n  const auto spaces = map.getSpaces();\n  ASSERT_EQ(1, spaces.size());\n  ASSERT_EQ(1, spaces.count(space));\n\n  ASSERT_THROW(map.createGc(space), SpaceToTapeGcMap::GcAlreadyExists);\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcSpaceToTapeGcMapTest, createAndDestroyAllGc)\n{\n  using namespace eos::mgm::tgc;\n\n  DummyTapeGcMgm mgm;\n  SpaceToTapeGcMap map(mgm);\n  const std::string space1 = \"space1\";\n  const std::string space2 = \"space2\";\n\n  map.createGc(space1);\n  map.createGc(space2);\n\n  const auto spacesBefore = map.getSpaces();\n  ASSERT_EQ(2, spacesBefore.size());\n  ASSERT_EQ(1, spacesBefore.count(space1));\n  ASSERT_EQ(1, spacesBefore.count(space2));\n\n  map.destroyAllGc();\n  const auto spacesAfter = map.getSpaces();\n  ASSERT_EQ(0, spacesAfter.size());\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcSpaceToTapeGcMapTest, toJson)\n{\n  using namespace eos::mgm::tgc;\n\n  DummyTapeGcMgm mgm;\n  SpaceToTapeGcMap map(mgm);\n  map.createGc(\"space1\");\n  map.createGc(\"space2\");\n\n  const auto spaces = map.getSpaces();\n  ASSERT_EQ(2, spaces.size());\n  ASSERT_EQ(1, spaces.count(\"space1\"));\n  ASSERT_EQ(1, spaces.count(\"space2\"));\n\n  {\n    auto &gc = map.getGc(\"space1\");\n    for (eos::IFileMD::id_t fid = 1; fid <= 2; fid++) {\n      gc.fileAccessed(fid);\n    }\n  }\n  {\n    auto &gc = map.getGc(\"space2\");\n    for (eos::IFileMD::id_t fid = 3; fid <= 4; fid++) {\n      gc.fileAccessed(fid);\n    }\n  }\n\n  const std::string expectedJson =\n    \"{\"\n    \"\\\"space1\\\":{\\\"spaceName\\\":\\\"space1\\\",\\\"lruQueue\\\":{\\\"size\\\":\\\"2\\\",\"\n    \"\\\"fids_from_MRU_to_LRU\\\":[\\\"0x0000000000000002\\\",\\\"0x0000000000000001\\\"]}},\"\n    \"\\\"space2\\\":{\\\"spaceName\\\":\\\"space2\\\",\\\"lruQueue\\\":{\\\"size\\\":\\\"2\\\",\"\n    \"\\\"fids_from_MRU_to_LRU\\\":[\\\"0x0000000000000004\\\",\\\"0x0000000000000003\\\"]}}\"\n    \"}\";\n  std::ostringstream json;\n  map.toJson(json);\n  ASSERT_EQ(expectedJson, json.str());\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcSpaceToTapeGcMapTest, toJson_exceed_maxLen)\n{\n  using namespace eos::mgm::tgc;\n\n  DummyTapeGcMgm mgm;\n  SpaceToTapeGcMap map(mgm);\n  map.createGc(\"space1\");\n  map.createGc(\"space2\");\n\n  const auto spaces = map.getSpaces();\n  ASSERT_EQ(2, spaces.size());\n  ASSERT_EQ(1, spaces.count(\"space1\"));\n  ASSERT_EQ(1, spaces.count(\"space2\"));\n\n  const std::string::size_type maxLen = 1;\n  std::ostringstream json;\n  ASSERT_THROW(map.toJson(json, maxLen), MaxLenExceeded);\n}\n"
  },
  {
    "path": "unit_tests/mgm/tgc/TapeGcTests.cc",
    "content": "//------------------------------------------------------------------------------\n// File: TapeGcTests.cc\n// Author: Steven Murray <smurray at cern dot ch>\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2018 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n#include \"mgm/tgc/DummyTapeGcMgm.hh\"\n#include \"mgm/tgc/MaxLenExceeded.hh\"\n#include \"mgm/tgc/TestingTapeGc.hh\"\n\n#include <gtest/gtest.h>\n#include <ctime>\n\nclass TgcTapeGcTest : public ::testing::Test {\nprotected:\n\n  virtual void SetUp() {\n  }\n\n  virtual void TearDown() {\n  }\n};\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcTapeGcTest, constructor)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::string space = \"space\";\n  const std::time_t maxConfigCacheAgeSecs = 0; // Always renew cached value\n\n  DummyTapeGcMgm mgm;\n  TapeGc gc(mgm, space, maxConfigCacheAgeSecs);\n\n  const auto now = std::time(nullptr);\n  const auto stats = gc.getStats();\n\n  ASSERT_EQ(0, stats.nbEvicts);\n  ASSERT_EQ(0, stats.lruQueueSize);\n  ASSERT_EQ(0, stats.spaceStats.totalBytes);\n  ASSERT_EQ(0, stats.spaceStats.availBytes);\n  ASSERT_TRUE(now <= stats.queryTimestamp && stats.queryTimestamp <= (now +5));\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcTapeGcTest, startWorkerThread)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::string space = \"space\";\n  const std::time_t maxConfigCacheAgeSecs = 0; // Always renew cached value\n\n  DummyTapeGcMgm mgm;\n  TapeGc gc(mgm, space, maxConfigCacheAgeSecs);\n\n  gc.startWorkerThread();\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcTapeGcTest, tryToGarbageCollectASingleFile)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::string space = \"space\";\n  const std::time_t maxConfigCacheAgeSecs = 0; // Always renew cached value\n\n  DummyTapeGcMgm mgm;\n  TestingTapeGc gc(mgm, space, maxConfigCacheAgeSecs);\n\n  ASSERT_EQ(0, mgm.getNbCallsToGetTapeGcSpaceConfig());\n\n  SpaceStats initialSpaceStats;\n  initialSpaceStats.availBytes = 10;\n  initialSpaceStats.totalBytes = 100;\n  mgm.setSpaceStats(space, initialSpaceStats);\n\n  {\n    const auto spaceStats = mgm.getSpaceStats(space);\n    ASSERT_EQ(initialSpaceStats.availBytes, spaceStats.availBytes);\n    ASSERT_EQ(initialSpaceStats.totalBytes, spaceStats.totalBytes);\n  }\n\n  gc.tryToGarbageCollectASingleFile();\n\n  ASSERT_EQ(2, mgm.getNbCallsToGetTapeGcSpaceConfig());\n  ASSERT_EQ(0, mgm.getNbCallsToFileInNamespaceAndNotScheduledForDeletion());\n  ASSERT_EQ(0, mgm.getNbCallsToGetFileSizeBytes());\n  ASSERT_EQ(0, mgm.getNbCallsToEvictAsRoot());\n\n  eos::IFileMD::id_t fid = 1;\n  gc.fileAccessed(fid);\n\n  gc.tryToGarbageCollectASingleFile();\n\n  ASSERT_EQ(4, mgm.getNbCallsToGetTapeGcSpaceConfig());\n  ASSERT_EQ(0, mgm.getNbCallsToFileInNamespaceAndNotScheduledForDeletion());\n  ASSERT_EQ(0, mgm.getNbCallsToGetFileSizeBytes());\n  ASSERT_EQ(0, mgm.getNbCallsToEvictAsRoot());\n\n\n  {\n    SpaceConfig config;\n    config.availBytes = initialSpaceStats.availBytes + 1;\n    mgm.setTapeGcSpaceConfig(space, config);\n  }\n\n  gc.tryToGarbageCollectASingleFile();\n\n  ASSERT_EQ(6, mgm.getNbCallsToGetTapeGcSpaceConfig());\n  ASSERT_EQ(0, mgm.getNbCallsToFileInNamespaceAndNotScheduledForDeletion());\n  ASSERT_EQ(0, mgm.getNbCallsToGetFileSizeBytes());\n  ASSERT_EQ(0, mgm.getNbCallsToEvictAsRoot());\n\n  {\n    SpaceConfig config;\n    config.availBytes = initialSpaceStats.availBytes + 1;\n    config.totalBytes = initialSpaceStats.totalBytes - 1;\n    mgm.setTapeGcSpaceConfig(space, config);\n  }\n\n  gc.tryToGarbageCollectASingleFile();\n\n  ASSERT_EQ(8, mgm.getNbCallsToGetTapeGcSpaceConfig());\n  ASSERT_EQ(0, mgm.getNbCallsToFileInNamespaceAndNotScheduledForDeletion());\n  ASSERT_EQ(1, mgm.getNbCallsToGetFileSizeBytes());\n  ASSERT_EQ(1, mgm.getNbCallsToEvictAsRoot());\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcTapeGcTest, toJson)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::string space = \"space\";\n  const std::time_t maxConfigCacheAgeSecs = 0; // Always renew cached value\n\n  DummyTapeGcMgm mgm;\n  TestingTapeGc gc(mgm, space, maxConfigCacheAgeSecs);\n\n  for (eos::IFileMD::id_t fid = 1; fid <= 3; fid++) {\n    gc.fileAccessed(fid);\n  }\n\n  const std::string expectedJson =\n    \"{\\\"spaceName\\\":\\\"space\\\",\\\"lruQueue\\\":{\\\"size\\\":\\\"3\\\",\"\n    \"\\\"fids_from_MRU_to_LRU\\\":[\\\"0x0000000000000003\\\",\\\"0x0000000000000002\\\",\\\"0x0000000000000001\\\"]}}\";\n  std::ostringstream json;\n  gc.toJson(json);\n  ASSERT_EQ(expectedJson, json.str());\n}\n\n//------------------------------------------------------------------------------\n// Test\n//------------------------------------------------------------------------------\nTEST_F(TgcTapeGcTest, toJson_exceed_maxLen)\n{\n  using namespace eos::mgm::tgc;\n\n  const std::string space = \"space\";\n  const std::time_t maxConfigCacheAgeSecs = 0; // Always renew cached value\n\n  DummyTapeGcMgm mgm;\n  TestingTapeGc gc(mgm, space, maxConfigCacheAgeSecs);\n\n  std::ostringstream json;\n  const std::string::size_type maxLen = 1;\n  ASSERT_THROW(gc.toJson(json, maxLen), MaxLenExceeded);\n}\n"
  },
  {
    "path": "unit_tests/mgm/utils/AttrHelperTests.cc",
    "content": "/************************************************************************\n  * EOS - the CERN Disk Storage System                                   *\n  * Copyright (C) 2022 CERN/Switzerland                           *\n  *                                                                      *\n  * This program is free software: you can redistribute it and/or modify *\n  * it under the terms of the GNU General Public License as published by *\n  * the Free Software Foundation, either version 3 of the License, or    *\n  * (at your option) any later version.                                  *\n  *                                                                      *\n  * This program is distributed in the hope that it will be useful,      *\n  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n  * GNU General Public License for more details.                         *\n  *                                                                      *\n  * You should have received a copy of the GNU General Public License    *\n  * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n  ************************************************************************\n*/\n\n#include \"mgm/utils/AttrHelper.hh\"\n#include \"mgm/misc/Constants.hh\"\n#include \"gtest/gtest.h\"\n\nusing namespace eos::mgm;\nusing eos::mgm::attr::checkDirOwner;\n\nTEST(checkDirOwner, EmptyMap)\n{\n  eos::common::VirtualIdentity vid;\n  bool sticky_owner;\n  ASSERT_FALSE(checkDirOwner({}, {}, {}, vid, sticky_owner, nullptr));\n}\n\nTEST(checkDirOwner, StickyOwner)\n{\n  eos::common::VirtualIdentity vid;\n  eos::IContainerMD::XAttrMap xattrs {{SYS_OWNER_AUTH, \"*\"}};\n  bool sticky_owner;\n  ASSERT_TRUE(checkDirOwner(xattrs, {}, {}, vid, sticky_owner, nullptr));\n  ASSERT_TRUE(sticky_owner);\n}\n\nTEST(checkDirOwner, dirOwner)\n{\n  eos::common::VirtualIdentity vid;\n  vid.uid = 23;\n  vid.gid = 23;\n  uid_t dir_uid = 46;\n  gid_t dir_gid = 46;\n  vid.prot = \"krb5\";\n  vid.uid_string = \"testuser\";\n  bool sticky_owner;\n  eos::IContainerMD::XAttrMap  xattrs {{SYS_OWNER_AUTH, \"sss:operator,krb5:testuser\"}};\n  EXPECT_TRUE(\n    checkDirOwner(xattrs, dir_uid, dir_gid, vid, sticky_owner, nullptr));\n  ASSERT_FALSE(sticky_owner);\n  ASSERT_EQ(vid.uid, dir_uid);\n  ASSERT_EQ(vid.gid, dir_gid);\n}\n\nTEST(checkAtomicUpload, EmptyMap)\n{\n  ASSERT_FALSE(attr::checkAtomicUpload({}));\n}\n\nTEST(checkAtomicUpload, sys)\n{\n  eos::IContainerMD::XAttrMap xattrs {{SYS_FORCED_ATOMIC, \"1\"}};\n  ASSERT_TRUE(attr::checkAtomicUpload(xattrs));\n  xattrs[SYS_FORCED_ATOMIC] = \"0\";\n  ASSERT_FALSE(attr::checkAtomicUpload(xattrs));\n  xattrs[SYS_FORCED_ATOMIC] = \"-1\";\n  ASSERT_TRUE(attr::checkAtomicUpload(xattrs));\n  xattrs[SYS_FORCED_ATOMIC] = \"garbage\";\n  ASSERT_FALSE(attr::checkAtomicUpload(xattrs));\n}\n\nTEST(checkAtomicUpload, user)\n{\n  eos::IContainerMD::XAttrMap xattrs {{USER_FORCED_ATOMIC, \"1\"}};\n  ASSERT_TRUE(attr::checkAtomicUpload(xattrs));\n  xattrs[USER_FORCED_ATOMIC] = \"0\";\n  ASSERT_FALSE(attr::checkAtomicUpload(xattrs));\n  xattrs[USER_FORCED_ATOMIC] = \"-1\";\n  ASSERT_TRUE(attr::checkAtomicUpload(xattrs));\n  xattrs[USER_FORCED_ATOMIC] = \"garbage\";\n  ASSERT_FALSE(attr::checkAtomicUpload(xattrs));\n}\n\nTEST(checkAtomicUpload, cgi)\n{\n  eos::IContainerMD::XAttrMap xattrs{};\n  ASSERT_TRUE(attr::checkAtomicUpload(xattrs, \"foo\"));\n  // sys overrides everything!\n  xattrs[SYS_FORCED_ATOMIC] = \"0\";\n  ASSERT_FALSE(attr::checkAtomicUpload(xattrs, \"foo\"));\n  // usr overrides cgi\n  xattrs.clear();\n  xattrs[USER_FORCED_ATOMIC] = \"0\";\n  ASSERT_FALSE(attr::checkAtomicUpload(xattrs, \"foo\"));\n}\n\nTEST(checkAtomicUpload, sysoverride)\n{\n  eos::IContainerMD::XAttrMap xattrs;\n  xattrs[SYS_FORCED_ATOMIC] = \"0\";\n  xattrs[USER_FORCED_ATOMIC] = \"1\";\n  ASSERT_FALSE(attr::checkAtomicUpload(xattrs));\n  ASSERT_FALSE(attr::checkAtomicUpload(xattrs, \"foo\"));\n}\n\nTEST(getVersioning, cgi)\n{\n  eos::IContainerMD::XAttrMap xattrs;\n  std::string version {\"1\"};\n  ASSERT_EQ(attr::getVersioning(xattrs, version), 1);\n  version = \"2\";\n  ASSERT_EQ(attr::getVersioning(xattrs, version), 2);\n}\n\nTEST(getVersioning, invalid_cgi)\n{\n  ASSERT_FALSE(attr::getVersioning({}, \"garbage\"));\n  eos::IContainerMD::XAttrMap xattrs {{SYS_VERSIONING, \"0\"},\n    {USER_VERSIONING, \"1\"}};\n  ASSERT_FALSE(attr::getVersioning(xattrs, \"garbage\"));\n  xattrs.clear();\n  xattrs[SYS_VERSIONING] = \"1\";\n  xattrs[USER_VERSIONING] = \"0\";\n  ASSERT_FALSE(attr::getVersioning(xattrs, \"garbage\"));\n}\n\nTEST(getVersioning, cgi_overrides)\n{\n  eos::IContainerMD::XAttrMap xattrs {{SYS_VERSIONING, \"0\"},\n    {USER_VERSIONING, \"1\"}};\n  std::string version {\"2\"};\n  ASSERT_EQ(attr::getVersioning(xattrs, version), 2);\n}\n\nTEST(getVersioning, sys_overrides)\n{\n  eos::IContainerMD::XAttrMap xattrs;\n  xattrs[SYS_VERSIONING] = \"1\";\n  xattrs[USER_VERSIONING] = \"0\";\n  ASSERT_TRUE(attr::getVersioning(xattrs));\n  xattrs[SYS_VERSIONING] = \"10\";\n  ASSERT_EQ(attr::getVersioning(xattrs), 10);\n  // sys overrides usr, so a garbage sys value will mean 0 versions!\n  xattrs[SYS_VERSIONING] = \"garbage\";\n  xattrs[USER_VERSIONING] = \"1\";\n  ASSERT_FALSE(attr::getVersioning(xattrs));\n}\n\nTEST(getVersioning, user)\n{\n  eos::IContainerMD::XAttrMap xattrs {{USER_VERSIONING, \"1\"}};\n  ASSERT_TRUE(attr::getVersioning(xattrs));\n  xattrs[USER_VERSIONING] = \"10\";\n  ASSERT_EQ(attr::getVersioning(xattrs), 10);\n}"
  },
  {
    "path": "unit_tests/with_qdb/Main.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes <georgios.bitzes@cern.ch>\n//! @brief Main.cc, test initialization\n//------------------------------------------------------------------------------\n\n#include <gtest/gtest.h>\n\nint main(int argc, char **argv) {\n  testing::InitGoogleTest(&argc, argv);\n  return RUN_ALL_TESTS();\n}\n"
  },
  {
    "path": "unit_tests/with_qdb/TestUtils.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n// author: Georgios Bitzes <georgios.bitzes@cern.ch>\n// desc:   Test utilities\n//------------------------------------------------------------------------------\n\n#include \"TestUtils.hh\"\n#include \"mq/MessagingRealm.hh\"\n#include <qclient/QClient.hh>\n#include <qclient/shared/SharedManager.hh>\n#include <fstream>\n\nnamespace eos\n{\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nFlushAllOnConstruction::FlushAllOnConstruction(const QdbContactDetails& cd)\n  : contactDetails(cd)\n{\n  qclient::QClient qcl(cd.members, cd.constructOptions());\n  qcl.exec(\"FLUSHALL\").get();\n  qcl.exec(\"SET\", \"QDB-INSTANCE-FOR-EOS-NS-TESTS\", \"YES\");\n}\n\n//------------------------------------------------------------------------------\n// Constructor\n//------------------------------------------------------------------------------\nUnitTestsWithQDBFixture::UnitTestsWithQDBFixture()\n{\n  // Connection parameters\n  std::string qdb_hostport = getenv(\"EOS_QUARKDB_HOSTPORT\") ?\n                             getenv(\"EOS_QUARKDB_HOSTPORT\") : \"localhost:9999\";\n  std::string qdb_passwd = getenv(\"EOS_QUARKDB_PASSWD\") ?\n                           getenv(\"EOS_QUARKDB_PASSWD\") : \"\";\n  std::string qdb_passwd_file = getenv(\"EOS_QUARKDB_PASSWD_FILE\") ?\n                                getenv(\"EOS_QUARKDB_PASSWD_FILE\") : \"/etc/eos.keytab\";\n\n  if (qdb_passwd.empty() && !qdb_passwd_file.empty()) {\n    // Read the password from the file\n    std::ifstream f(qdb_passwd_file);\n    std::stringstream buff;\n    buff << f.rdbuf();\n    qdb_passwd = buff.str();\n  }\n\n  mContactDetails = QdbContactDetails(qclient::Members::fromString(qdb_hostport),\n                                      qdb_passwd);\n  mFlushGuard.reset(new FlushAllOnConstruction(mContactDetails));\n}\n\n//------------------------------------------------------------------------------\n//! Destructor\n//------------------------------------------------------------------------------\nUnitTestsWithQDBFixture::~UnitTestsWithQDBFixture() {}\n\n//------------------------------------------------------------------------------\n//! Make QClient object\n//------------------------------------------------------------------------------\nstd::unique_ptr<qclient::QClient> UnitTestsWithQDBFixture::makeQClient() const\n{\n  return std::unique_ptr<qclient::QClient>(\n           new qclient::QClient(mContactDetails.members,\n                                mContactDetails.constructOptions())\n         );\n}\n\n//------------------------------------------------------------------------------\n// Get MessagingRealm object, lazy init\n//------------------------------------------------------------------------------\nmq::MessagingRealm* UnitTestsWithQDBFixture::getMessagingRealm(int tag)\n{\n  auto it = mMessagingRealms.find(tag);\n\n  if (it == mMessagingRealms.end()) {\n    mMessagingRealms[tag].reset(new mq::MessagingRealm(getSharedManager(tag)));\n    return mMessagingRealms[tag].get();\n  } else {\n    return it->second.get();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Get SharedManager object, lazy init\n//------------------------------------------------------------------------------\nqclient::SharedManager* UnitTestsWithQDBFixture::getSharedManager(int tag)\n{\n  auto it = mSharedManagers.find(tag);\n\n  if (it == mSharedManagers.end()) {\n    mSharedManagers[tag].reset(new qclient::SharedManager(mContactDetails.members,\n                               mContactDetails.constructSubscriptionOptions()));\n    return mSharedManagers[tag].get();\n  } else {\n    return it->second.get();\n  }\n}\n\n//------------------------------------------------------------------------------\n// Retrieve contact details\n//------------------------------------------------------------------------------\nQdbContactDetails UnitTestsWithQDBFixture::getContactDetails() const\n{\n  return mContactDetails;\n}\n\n}\n"
  },
  {
    "path": "unit_tests/with_qdb/TestUtils.hh",
    "content": "//------------------------------------------------------------------------------\n// File: TestUtils.hh\n// Author: Georgios Bitzes - CERN\n//------------------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n#pragma once\n\n#include \"namespace/ns_quarkdb/QdbContactDetails.hh\"\n#include <gtest/gtest.h>\n#include <memory>\n#include <chrono>\n#include <thread>\n\n#define RETRY_ASSERT_TRUE_3(cond, retry, waitInterval) { \\\n  bool ok = false; \\\n  size_t nretries = 0; \\\n  while(nretries++ < retry) { \\\n    std::this_thread::sleep_for(std::chrono::milliseconds(waitInterval)); \\\n    if((cond)) { \\\n      std::cerr << \"Condition '\" << #cond << \"' is true after \" << nretries << \" attempts\" << std::endl; \\\n      ok = true; \\\n      break; \\\n    } \\\n  } \\\n  if(!ok) { ASSERT_TRUE(cond) << \" - failure after \" << nretries << \" retries \"; } \\\n}\n\n// retry every 1 ms, 5000 max retries\n#define RETRY_ASSERT_TRUE(cond) RETRY_ASSERT_TRUE_3(cond, 5000, 1)\n\nnamespace qclient {\n  class QClient;\n  class SharedManager;\n}\n\nnamespace eos\n{\n\nnamespace mq {\n  class MessagingRealm;\n}\n\n//------------------------------------------------------------------------------\n//! Class FlushAllOnConstruction\n//------------------------------------------------------------------------------\nclass FlushAllOnConstruction\n{\npublic:\n  FlushAllOnConstruction(const QdbContactDetails& cd);\n\nprivate:\n  QdbContactDetails contactDetails;\n};\n\n//------------------------------------------------------------------------------\n//! Test fixture providing generic utilities and initialization / destruction\n//! boilerplate code\n//------------------------------------------------------------------------------\nclass UnitTestsWithQDBFixture : public ::testing::Test\n{\npublic:\n  //----------------------------------------------------------------------------\n  //! Constructor\n  //----------------------------------------------------------------------------\n  UnitTestsWithQDBFixture();\n\n  //----------------------------------------------------------------------------\n  //! Destructor\n  //----------------------------------------------------------------------------\n  ~UnitTestsWithQDBFixture();\n\n  //----------------------------------------------------------------------------\n  //! Make QClient object\n  //----------------------------------------------------------------------------\n  std::unique_ptr<qclient::QClient> makeQClient() const;\n\n  //----------------------------------------------------------------------------\n  //! Get MessagingRealm object, lazy init\n  //----------------------------------------------------------------------------\n  mq::MessagingRealm* getMessagingRealm(int tag = 0);\n\n  //----------------------------------------------------------------------------\n  //! Get SharedManager object, lazy init\n  //----------------------------------------------------------------------------\n  qclient::SharedManager* getSharedManager(int tag = 0);\n\n  //----------------------------------------------------------------------------\n  //! Retrieve contact details\n  //----------------------------------------------------------------------------\n  QdbContactDetails getContactDetails() const;\n\n\nprivate:\n  QdbContactDetails mContactDetails;\n  std::unique_ptr<FlushAllOnConstruction> mFlushGuard;\n\n  std::map<int, std::unique_ptr<mq::MessagingRealm>> mMessagingRealms;\n  std::map<int, std::unique_ptr<qclient::SharedManager>> mSharedManagers;\n\n};\n\n}\n\n"
  },
  {
    "path": "unit_tests/with_qdb/configuration.cc",
    "content": "/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2020 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n//------------------------------------------------------------------------------\n//! @author Georgios Bitzes - CERN\n//! @brief Tests related to the configuration\n//------------------------------------------------------------------------------\n\n#include \"unit_tests/with_qdb/TestUtils.hh\"\n#include \"mgm/config/QuarkConfigHandler.hh\"\n#include \"common/Assert.hh\"\n\n#include <qclient/QClient.hh>\n\nusing namespace eos;\nclass VariousTests : public eos::UnitTestsWithQDBFixture {};\nclass ConfigurationTests : public eos::UnitTestsWithQDBFixture {};\n\nTEST_F(VariousTests, Ping)\n{\n  std::unique_ptr<qclient::QClient> qcl = makeQClient();\n  qclient::redisReplyPtr reply = qcl->exec(\"PING\").get();\n  ASSERT_EQ(qclient::describeRedisReply(reply),\n            \"PONG\");\n}\n\nTEST_F(ConfigurationTests, BasicFetch)\n{\n  std::unique_ptr<qclient::QClient> qcl = makeQClient();\n  qclient::redisReplyPtr reply = qcl->exec(\"HSET\", \"eos-config:default\", \"a\",\n                                 \"b\").get();\n  ASSERT_EQ(qclient::describeRedisReply(reply), \"(integer) 1\");\n  eos::mgm::QuarkConfigHandler ch(getContactDetails());\n  std::map<std::string, std::string> cfmap;\n  ASSERT_TRUE(ch.fetchConfiguration(\"default\", cfmap));\n  ASSERT_EQ(cfmap.size(), 1u);\n  ASSERT_EQ(cfmap[\"a\"], \"b\");\n  bool exists = false;\n  ASSERT_TRUE(ch.checkExistence(\"default\", exists));\n  ASSERT_TRUE(exists);\n  ASSERT_TRUE(ch.checkExistence(\"default-2\", exists));\n  ASSERT_FALSE(exists);\n  qclient::redisReplyPtr reply2 = qcl->exec(\"SADD\", \"eos-config:default-3\", \"a\",\n                                  \"b\").get();\n  ASSERT_EQ(qclient::describeRedisReply(reply2), \"(integer) 2\");\n  common::Status st = ch.checkExistence(\"default-3\", exists);\n  ASSERT_FALSE(st);\n  ASSERT_EQ(st.toString(),\n            \"(22): Received unexpected response in HLEN existence check: Unexpected reply type; was expecting INTEGER, received (error) ERR Invalid argument: WRONGTYPE Operation against a key holding the wrong kind of value\");\n}\n\nTEST_F(ConfigurationTests, Listing)\n{\n  std::unique_ptr<qclient::QClient> qcl = makeQClient();\n  qclient::redisReplyPtr reply = qcl->exec(\"HSET\", \"eos-config:default\", \"a\",\n                                 \"b\").get();\n  ASSERT_EQ(qclient::describeRedisReply(reply), \"(integer) 1\");\n  qclient::redisReplyPtr reply2 = qcl->exec(\"HSET\", \"eos-config:default-2\", \"a\",\n                                  \"b\").get();\n  ASSERT_EQ(qclient::describeRedisReply(reply2), \"(integer) 1\");\n  qclient::redisReplyPtr reply3 = qcl->exec(\"HSET\", \"eos-config-backup:default-1\",\n                                  \"a\", \"b\").get();\n  ASSERT_EQ(qclient::describeRedisReply(reply3), \"(integer) 1\");\n  eos::mgm::QuarkConfigHandler ch(getContactDetails());\n  std::vector<std::string> configs, backups;\n  ASSERT_TRUE(ch.listConfigurations(configs, backups));\n  ASSERT_EQ(configs.size(), 2u);\n  ASSERT_EQ(configs[0], \"default\");\n  ASSERT_EQ(configs[1], \"default-2\");\n  ASSERT_EQ(backups.size(), 1u);\n  ASSERT_EQ(backups[0], \"default-1\");\n}\n\nTEST_F(ConfigurationTests, TrimBackups)\n{\n  std::unique_ptr<qclient::QClient> qcl = makeQClient();\n  qclient::redisReplyPtr rep;\n  rep = qcl->exec(\"HSET\", \"eos-config-backup:default-1a\", \"a\", \"b\").get();\n  ASSERT_EQ(qclient::describeRedisReply(rep), \"(integer) 1\");\n  rep = qcl->exec(\"HSET\", \"eos-config-backup:default-2b\", \"a\", \"b\").get();\n  ASSERT_EQ(qclient::describeRedisReply(rep), \"(integer) 1\");\n  rep = qcl->exec(\"HSET\", \"eos-config-backup:default-3c\", \"a\", \"b\").get();\n  ASSERT_EQ(qclient::describeRedisReply(rep), \"(integer) 1\");\n  rep = qcl->exec(\"HSET\", \"eos-config-backup:default-4d\", \"a\", \"b\").get();\n  ASSERT_EQ(qclient::describeRedisReply(rep), \"(integer) 1\");\n  rep = qcl->exec(\"HSET\", \"eos-config-backup:aaaaaa-1\", \"a\", \"b\").get();\n  ASSERT_EQ(qclient::describeRedisReply(rep), \"(integer) 1\");\n  rep = qcl->exec(\"HSET\", \"eos-config-backup:zzzzz-1\", \"a\", \"b\").get();\n  ASSERT_EQ(qclient::describeRedisReply(rep), \"(integer) 1\");\n  eos::mgm::QuarkConfigHandler ch(getContactDetails());\n  size_t deleted;\n  common::Status st = ch.trimBackups(\"default\", 2, deleted);\n  ASSERT_TRUE(st);\n  ASSERT_EQ(deleted, 2);\n  std::vector<std::string> configs, backups;\n  ASSERT_TRUE(ch.listConfigurations(configs, backups));\n  std::vector<std::string> expectedConfigs = {};\n  std::vector<std::string> expectedBackups = {\"aaaaaa-1\", \"default-3c\", \"default-4d\", \"zzzzz-1\"};\n  ASSERT_EQ(configs, expectedConfigs);\n  ASSERT_EQ(backups, expectedBackups);\n}\n\nstd::string padZeroes(const std::string& str, size_t len)\n{\n  if (str.size() < len) {\n    std::ostringstream ss;\n\n    for (size_t i = 0; i < len - str.size(); i++) {\n      ss << \"0\";\n    }\n\n    ss << str;\n    return ss.str();\n  }\n\n  return str;\n}\n\nTEST_F(ConfigurationTests, TrimBackupsHit200Limit)\n{\n  std::unique_ptr<qclient::QClient> qcl = makeQClient();\n  qclient::redisReplyPtr rep;\n  ASSERT_EQ(padZeroes(SSTR(1), 3), \"001\");\n  ASSERT_EQ(padZeroes(SSTR(11), 3), \"011\");\n  ASSERT_EQ(padZeroes(SSTR(111), 3), \"111\");\n\n  for (size_t i = 0; i < 300; i++) {\n    std::string key = padZeroes(SSTR(i), 3);\n    rep = qcl->exec(\"HSET\", SSTR(\"eos-config-backup:default-\" << key), \"a\",\n                    \"b\").get();\n    ASSERT_EQ(qclient::describeRedisReply(rep), \"(integer) 1\");\n  }\n\n  eos::mgm::QuarkConfigHandler ch(getContactDetails());\n  size_t deleted;\n  common::Status st = ch.trimBackups(\"default\", 10, deleted);\n  ASSERT_TRUE(st);\n  ASSERT_EQ(deleted, 200);\n  std::vector<std::string> configs, backups;\n  ASSERT_TRUE(ch.listConfigurations(configs, backups));\n  ASSERT_EQ(backups.size(), 100u);\n\n  for (size_t i = 0; i < backups.size(); i++) {\n    ASSERT_EQ(backups[i], SSTR(\"default-\" << padZeroes(SSTR(i + 200), 3)));\n  }\n}\n\nTEST_F(ConfigurationTests, WriteRead)\n{\n  eos::mgm::QuarkConfigHandler ch(getContactDetails());\n  ASSERT_TRUE(ch.checkConnection(std::chrono::seconds(1)));\n  std::map<std::string, std::string> configuration, configuration2;\n  configuration[\"a\"] = \"b\";\n  configuration[\"c\"] = \"d\";\n  ASSERT_TRUE(ch.writeConfiguration(\"default\", configuration, false).get());\n  ASSERT_TRUE(ch.fetchConfiguration(\"default\", configuration2));\n  ASSERT_EQ(configuration, configuration2);\n  ASSERT_FALSE(ch.writeConfiguration(\"default\", configuration, false).get());\n  configuration[\"d\"] = \"e\";\n  ASSERT_TRUE(ch.writeConfiguration(\"default\", configuration, true).get());\n  ASSERT_FALSE(configuration == configuration2);\n  ASSERT_TRUE(ch.fetchConfiguration(\"default\", configuration2));\n  ASSERT_EQ(configuration, configuration2);\n}\n\nTEST_F(ConfigurationTests, HashKeys)\n{\n  ASSERT_EQ(eos::mgm::QuarkConfigHandler::FormHashKey(\"default\"),\n            \"eos-config:default\");\n  ASSERT_EQ(eos::mgm::QuarkConfigHandler::FormBackupHashKey(\"default\",\n            1588936606), \"eos-config-backup:default-20200508111646\");\n}\n\nTEST_F(ConfigurationTests, TailLog)\n{\n  std::unique_ptr<qclient::QClient> qcl = makeQClient();\n  qclient::redisReplyPtr reply = qcl->exec(\"deque-push-back\",\n                                 \"eos-config-changelog\", \"aaa\", \"bbb\", \"ccc\", \"ddd\", \"eee\").get();\n  ASSERT_EQ(qclient::describeRedisReply(reply), \"(integer) 5\");\n  std::vector<std::string> changelog;\n  eos::mgm::QuarkConfigHandler ch(getContactDetails());\n  ASSERT_TRUE(ch.tailChangelog(100, changelog));\n  ASSERT_EQ(changelog.size(), 5u);\n  ASSERT_EQ(changelog[0], \"aaa\");\n  ASSERT_EQ(changelog[1], \"bbb\");\n  ASSERT_EQ(changelog[2], \"ccc\");\n  ASSERT_EQ(changelog[3], \"ddd\");\n  ASSERT_EQ(changelog[4], \"eee\");\n  ASSERT_TRUE(ch.tailChangelog(2, changelog));\n  ASSERT_EQ(changelog.size(), 2u);\n  ASSERT_EQ(changelog[0], \"ddd\");\n  ASSERT_EQ(changelog[1], \"eee\");\n}\n\nTEST_F(ConfigurationTests, AppendChangelog)\n{\n  eos::mgm::ConfigChangelogEntry entry;\n  eos::mgm::ConfigModification* modif = entry.add_modifications();\n  modif->set_key(\"aa\");\n  modif->set_previous_value(\"b\");\n  modif->set_new_value(\"c\");\n  eos::mgm::QuarkConfigHandler ch(getContactDetails());\n  common::Status st = ch.appendChangelog(entry).get();\n  ASSERT_TRUE(st) << st.toString();\n}\n"
  },
  {
    "path": "utils/CMakeLists.txt",
    "content": "# ----------------------------------------------------------------------\n# File: CMakeLists.txt\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n# Build zstdtail executable\nadd_executable(zstdtail zstdtail.cc)\ntarget_link_libraries(zstdtail PRIVATE ZSTD::ZSTD)\n\ninstall(TARGETS zstdtail\n  DESTINATION ${CMAKE_INSTALL_FULL_BINDIR})\n\ninstall(\n  PROGRAMS eos-log-clean eos-mgm-clean eos-fst-clean eos-uninstall eos-ports-block eos-tty-broadcast filter-trace/eos-filter-stacktrace flamegraph/eos-util-flamegraph flamegraph/eos-util-stackcollapse flamegraph/eos-make-flamegraph\n  DESTINATION ${CMAKE_INSTALL_FULL_SBINDIR})\n"
  },
  {
    "path": "utils/README.osx",
    "content": "\nUsage EOS shell\n===============\nPlease see the general documentation at http://cern.ch/eos-docs/clicommands.html\n$ eos whoami\n$ eos ls -la /eos/\n\nTo change the default EOS instance, please set the following before using the shell:\n\nexport EOS_MGM_URL=root://<mgm-host-name>\n\nor use the syntax:\n$ eos root://<mgm-host-name> <command>\n\n\nUsage Fuse Mount\n================\nPlease note that EOS FUSE access on OS-X may work, but is not supported (due to\nexternal dependencies and complex configuration).\n"
  },
  {
    "path": "utils/astylerc",
    "content": "# Indent with 2 spaces\n--indent=spaces=2\n\n# Style\n--style=otbs\n\n# Break long logical conditionals\n--break-after-logical\n\n# Delete empty lines within a method\n--delete-empty-lines\n\n# Unpad all\n--unpad-paren\n\n# Pad empty lines around header blocks\n--break-blocks\n\n# Pad header blocks like if, while, for etc.\n--pad-header\n\n# Pad operators\n--pad-oper\n\n# Convert tabs to spaces\n--convert-tabs\n\n# Align pointers\n--align-pointer=type\n\n# Align references\n--align-reference=type\n\n# Set the maximum line length to 80\n--max-code-length=80\n\n# Add brackets to one liners\n--add-brackets\n"
  },
  {
    "path": "utils/centos7-dev-environment.sh",
    "content": "#!/bin/bash\n\ndie() {\n  echo \"$@\" 1>&2\n  test -z $TAILPID || kill ${TAILPID} &> /dev/null\n  exit 1\n}\n\ncreateXrootdRepo() {\necho \"Creating the xrootd.repo and adding it to the yum repository directory\"\nsudo cat > /etc/yum.repos.d/xrootd.repo <<'EOF'\n[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\nEOF\n}\n\ncreateEosAndEosDiopsideRepo() {\necho \"Creating the eos.repo and adding it to the yum repository directory\"\nsudo cat > /etc/yum.repos.d/eos.repo <<'EOF'\n[eos-diopside]\nname=EOS 5.0 Version\nbaseurl=https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/testing/el-7/x86_64/\ngpgcheck=0\n\n[eos-diopside-dep]\nname=EOS 5.0 Dependencies\nbaseurl=https://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-7/x86_64/\ngpgcheck=0\n\nEOF\n}\n\ncreateQuarkDbRepo(){\necho \"Creating the quarkdb.repo and adding it to the yum repository directory\"\nsudo cat > /etc/yum.repos.d/quarkdb.repo <<'EOF'\n[quarkdb-stable]\nname=QuarkDB repository [stable]\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/quarkdb/tag/el7/x86_64/\nenabled=1\ngpgcheck=False\n\nEOF\n}\n\nenableDevToolSetIfNecessary() {\n  devtoolSet8EnableCommand='source /opt/rh/devtoolset-8/enable'\n  if ! grep -Fxq \"$devtoolSet8EnableCommand\" ~/.bashrc\n  then\n    echo \"Adding the line '$devtoolSet8EnableCommand' in ~/.bashrc\"\n    echo $devtoolSet8EnableCommand >> ~/.bashrc\n    echo \"Rebooting the VM\"\n    sudo reboot -h now\n  fi\n}\n\necho \"Installing necessary utilities\"\nsudo yum install --nogpg -y ccache centos-release-scl-rh cmake3 gcc-c++ gdb make rpm-build rpm-sign yum-plugin-priorities gnutls && yum clean all || die \"Error while installing necessary utilities\"\n\nEOS_PROJECT_ROOT_DIR=\"$(git rev-parse --show-toplevel)\"\ncd $EOS_PROJECT_ROOT_DIR\nmkdir -p build\ncd build\ncmake3 ../ -DPACKAGEONLY=1 && make srpm || die \"Unable to make the srpm\"\n\ncreateXrootdRepo\n\ncreateEosAndEosDiopsideRepo\n\ncreateQuarkDbRepo\n\nsudo yum clean all\n\necho \"Running yum-builddep to build the dependencies of EOS\"\nsudo yum-builddep --nogpgcheck --setopt=\"cern*.exclude=xrootd*\" -y SRPMS/* || die 'ERROR while building the dependencies'\n\necho \"Installing quarkdb\"\nsudo yum install -y quarkdb quarkdb-debuginfo redis || die 'ERROR while installing quarkdb packages'\n\nenableDevToolSetIfNecessary\n"
  },
  {
    "path": "utils/centos8-dev-environment.sh",
    "content": "#!/bin/bash\n\nPATH_CMAKE_DIR='/opt/eos/cmake/bin'\n\ndie() {\n  echo \"$@\" 1>&2\n  test -z $TAILPID || kill ${TAILPID} &>/dev/null\n  exit 1\n}\n\n## Usage: hasMinRequiredVersion currentVersion requiredVersion\nhasMinRequiredVersion() {\n  currentver=$1\n  requiredver=$2\n  if [ \"$(printf '%s\\n' \"$requiredver\" \"$currentver\" | sort -V | head -n1)\" = \"$requiredver\" ]; then\n    # 0 = true\n    return 0\n  else\n    # 1 = false\n    return 1\n  fi\n}\n\naddEOSCmakeDirToPATHBashrc() {\n  pathCMakeDir=$PATH_CMAKE_DIR\n  pathCopy=$PATH\n  echo $pathCopy | grep -q \"$pathCMakeDir\"\n  if [[ $? -ne 0 ]]; then\n    # The path of cmake is not in $PATH, we add it\n    pathCopy=\"$pathCopy:$pathCMakeDir\"\n  fi\n  #Check if there is a PATH configuration with the pathCMakeDir setup in the .bashrc file\n  cat ~/.bashrc | egrep -q 'PATH=.*'\"$pathCMakeDir\"'.*'\n  if [[ $? -ne 0 ]]; then\n    # No PATH configuration, add it to persist the PATH change\n    echo \"Adding CMake from the eos-cmake package to the PATH in the .bashrc file\"\n    echo 'export PATH=$PATH:'\"$pathCMakeDir\" >> ~/.bashrc\n  fi\n  export PATH=$pathCopy\n}\n\ninstallEosCMake() {\n  echo \"Installing EOS CMake\"\n  sudo yum install -y eos-cmake --disablerepo=* --enablerepo=eos-diopside-dep\n  sudo ln -fs $PATH_CMAKE_DIR/cmake $PATH_CMAKE_DIR/cmake3\n}\n\ninstallCMakeIfNecessary() {\n  # We will install cmake if it does not exist or if the installed\n  # version is not above the 3.14 (see CMakeLists.txt)\n  hash -r\n  necessaryCMakeVersion=3.14\n  cmakeExists=$(command -v cmake)\n  if [ ! -z $cmakeExists ]; then\n    # CMake exists, check its version\n    cmakeVersion=$(cmake --version | awk 'NR==1{print $3}')\n    if hasMinRequiredVersion $cmakeVersion $necessaryCMakeVersion; then\n      echo \"CMake has the good version, no need to install another one\"\n    else\n      sudo yum remove -y cmake\n      installEosCMake || die \"Unable to install CMake\"\n    fi\n  else\n    installEosCMake || die \"Unable to install CMake\"\n  fi\n}\n\ncreateXrootdRepo() {\n  echo \"Creating the xrootd.repo and adding it to the yum repository directory\"\n  sudo cat >/etc/yum.repos.d/xrootd.repo <<'EOF'\n[xrootd-stable]\nname=XRootD Stable repository\nbaseurl=http://xrootd.org/binaries/stable/slc/8/$basearch http://xrootd.cern.ch/sw/repos/stable/slc/8/$basearch\ngpgcheck=1\nenabled=1\nprotect=0\ngpgkey=http://xrootd.cern.ch/sw/releases/RPM-GPG-KEY.txt\n\nEOF\n}\n\ncreateEosAndEosDiopsideRepo() {\n  echo \"Creating the eos.repo and adding it to the yum repository directory\"\n  sudo cat >/etc/yum.repos.d/eos.repo <<'EOF'\n[eos-diopside]\nname=EOS 5.0 Version\nbaseurl=https://storage-ci.web.cern.ch/storage-ci/eos/diopside/tag/testing/el-8/x86_64/\ngpgcheck=0\n\n[eos-diopside-dep]\nname=EOS 5.0 Dependencies\nbaseurl=https://storage-ci.web.cern.ch/storage-ci/eos/diopside-depend/el-8/x86_64/\ngpgcheck=0\n\nEOF\n}\n\ncreateQuarkdbRepo() {\n  echo \"Creating the quarkdb.repo and adding it to the yum repository directory\"\n  sudo cat >/etc/yum.repos.d/quarkdb.repo <<'EOF'\n[quarkdb-stable]\nname=QuarkDB repository [stable]\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/quarkdb/tag/el8/x86_64/\nenabled=1\ngpgcheck=False\n\nEOF\n}\n\necho \"Installing necessary utilities\"\nsudo yum install --nogpg -y python3 wget ccache gcc-c++ gdb make rpm-build rpm-sign gnutls && yum clean all || die \"Error while installing necessary utilities\"\n\ncreateXrootdRepo\n\ncreateEosAndEosDiopsideRepo\n\ncreateQuarkdbRepo\n\ninstallCMakeIfNecessary || die \"Unable to install CMake\"\naddEOSCmakeDirToPATHBashrc\n\nEOS_PROJECT_ROOT_DIR=\"$(git rev-parse --show-toplevel)\"\ncd $EOS_PROJECT_ROOT_DIR\nmkdir -p build\ncd build\ncmake ../ -DPACKAGEONLY=1 -Wno-dev && make srpm || die \"Unable to create the SRPMS.\"\ncd $EOS_PROJECT_ROOT_DIR\n\nsudo yum clean all\n\necho \"Running yum-builddep to build the EOS dependencies\"\nsudo yum-builddep --nogpgcheck --allowerasing -y ./build/SRPMS/* || die 'ERROR while building the dependencies'\n\nsudo yum install -y quarkdb quarkdb-debuginfo redis || die 'ERROR while installing quarkdb packages'\nhash -r\n"
  },
  {
    "path": "utils/clang-format-diff.py",
    "content": "#!/usr/bin/env python3\n#\n# ===- clang-format-diff.py - ClangFormat Diff Reformatter ----*- python -*--===#\n#\n# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.\n# See https://llvm.org/LICENSE.txt for license information.\n# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception\n#\n# ===------------------------------------------------------------------------===#\n\n\"\"\"\nThis script reads input from a unified diff and reformats all the changed\nlines. This is useful to reformat all the lines touched by a specific patch.\nExample usage for git/svn users:\n\n  git diff -U0 --no-color --relative HEAD^ | {clang_format_diff} -p1 -i\n  svn diff --diff-cmd=diff -x-U0 | {clang_format_diff} -i\n\nIt should be noted that the filename contained in the diff is used unmodified\nto determine the source file to update. Users calling this script directly\nshould be careful to ensure that the path in the diff is correct relative to the\ncurrent working directory.\n\"\"\"\nfrom __future__ import absolute_import, division, print_function\n\nimport argparse\nimport difflib\nimport re\nimport subprocess\nimport sys\n\nif sys.version_info.major >= 3:\n    from io import StringIO\nelse:\n    from io import BytesIO as StringIO\n\n\ndef main():\n    parser = argparse.ArgumentParser(\n        description=__doc__.format(clang_format_diff=\"%(prog)s\"),\n        formatter_class=argparse.RawDescriptionHelpFormatter,\n    )\n    parser.add_argument(\n        \"-i\",\n        action=\"store_true\",\n        default=False,\n        help=\"apply edits to files instead of displaying a diff\",\n    )\n    parser.add_argument(\n        \"-p\",\n        metavar=\"NUM\",\n        default=0,\n        help=\"strip the smallest prefix containing P slashes\",\n    )\n    parser.add_argument(\n        \"-regex\",\n        metavar=\"PATTERN\",\n        default=None,\n        help=\"custom pattern selecting file paths to reformat \"\n        \"(case sensitive, overrides -iregex)\",\n    )\n    parser.add_argument(\n        \"-iregex\",\n        metavar=\"PATTERN\",\n        default=r\".*\\.(?:cpp|cc|c\\+\\+|cxx|cppm|ccm|cxxm|c\\+\\+m|c|cl|h|hh|hpp\"\n        r\"|hxx|m|mm|inc|js|ts|proto|protodevel|java|cs|json|ipynb|s?vh?)\",\n        help=\"custom pattern selecting file paths to reformat \"\n        \"(case insensitive, overridden by -regex)\",\n    )\n    parser.add_argument(\n        \"-sort-includes\",\n        action=\"store_true\",\n        default=False,\n        help=\"let clang-format sort include blocks\",\n    )\n    parser.add_argument(\n        \"-v\",\n        \"--verbose\",\n        action=\"store_true\",\n        help=\"be more verbose, ineffective without -i\",\n    )\n    parser.add_argument(\n        \"-style\",\n        help=\"formatting style to apply (LLVM, GNU, Google, Chromium, \"\n        \"Microsoft, Mozilla, WebKit)\",\n    )\n    parser.add_argument(\n        \"-fallback-style\",\n        help=\"The name of the predefined style used as a\"\n        \"fallback in case clang-format is invoked with\"\n        \"-style=file, but can not find the .clang-format\"\n        \"file to use.\",\n    )\n    parser.add_argument(\n        \"-binary\",\n        default=\"clang-format\",\n        help=\"location of binary to use for clang-format\",\n    )\n    args = parser.parse_args()\n\n    # Extract changed lines for each file.\n    filename = None\n    lines_by_file = {}\n    for line in sys.stdin:\n        match = re.search(r\"^\\+\\+\\+\\ (.*?/){%s}(.+)\" % args.p, line.rstrip())\n        if match:\n            filename = match.group(2)\n        if filename is None:\n            continue\n\n        if args.regex is not None:\n            if not re.match(\"^%s$\" % args.regex, filename):\n                continue\n        else:\n            if not re.match(\"^%s$\" % args.iregex, filename, re.IGNORECASE):\n                continue\n\n        match = re.search(r\"^@@.*\\+(\\d+)(?:,(\\d+))?\", line)\n        if match:\n            start_line = int(match.group(1))\n            line_count = 1\n            if match.group(2):\n                line_count = int(match.group(2))\n                # The input is something like\n                #\n                # @@ -1, +0,0 @@\n                #\n                # which means no lines were added.\n                if line_count == 0:\n                    continue\n            # Also format lines range if line_count is 0 in case of deleting\n            # surrounding statements.\n            end_line = start_line\n            if line_count != 0:\n                end_line += line_count - 1\n            lines_by_file.setdefault(filename, []).extend(\n                [\"--lines\", str(start_line) + \":\" + str(end_line)]\n            )\n\n    # Reformat files containing changes in place.\n    has_diff = False\n    for filename, lines in lines_by_file.items():\n        if args.i and args.verbose:\n            print(\"Formatting {}\".format(filename))\n        command = [args.binary, filename]\n        if args.i:\n            command.append(\"-i\")\n        if args.sort_includes:\n            command.append(\"--sort-includes\")\n        command.extend(lines)\n        if args.style:\n            command.extend([\"--style\", args.style])\n        if args.fallback_style:\n            command.extend([\"--fallback-style\", args.fallback_style])\n\n        try:\n            p = subprocess.Popen(\n                command,\n                stdout=subprocess.PIPE,\n                stderr=None,\n                stdin=subprocess.PIPE,\n                universal_newlines=True,\n            )\n        except OSError as e:\n            # Give the user more context when clang-format isn't\n            # found/isn't executable, etc.\n            raise RuntimeError(\n                'Failed to run \"%s\" - %s\"' % (\" \".join(command), e.strerror)\n            )\n\n        stdout, _stderr = p.communicate()\n        if p.returncode != 0:\n            return p.returncode\n\n        if not args.i:\n            with open(filename) as f:\n                code = f.readlines()\n            formatted_code = StringIO(stdout).readlines()\n            diff = difflib.unified_diff(\n                code,\n                formatted_code,\n                filename,\n                filename,\n                \"(before formatting)\",\n                \"(after formatting)\",\n            )\n            diff_string = \"\".join(diff)\n            if len(diff_string) > 0:\n                has_diff = True\n                sys.stdout.write(diff_string)\n\n    if has_diff:\n        return 1\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "utils/el7-packages.sh",
    "content": "yum install --disablerepo=epel -y xrootd-server-devel xrootd-private-devel \\\n    xrootd-client xrootd-client-devel\n\nyum install -y centos-release-scl\nyum install -y cmake3 sparsehash-devel \\\n    ncurses ncurses-devel ncurses-static openssl openssl-devel openssl-static \\\n    readline readline-devel libuuid libuuid-devel zeromq zeromq-devel \\\n    eos-protobuf3 eos-protobuf3-devel eos-protobuf3-compiler \\\n    eos-protobuf3-debuginfo leveldb leveldb-devel cppunit cppunit-devel \\\n    fuse-libs fuse fuse-devel libattr libattr-devel openldap openldap-devel \\\n    zlib zlib-devel zlib-static xfsprogs xfsprogs-devel e2fsprogs-devel \\\n    perl-Time-HiRes json-c json-c-devel jsoncpp jsoncpp-devel libcurl libcurl-devel \\\n    libevent libevent-devel bzip2-devel bzip2-libs jemalloc jemalloc-devel \\\n    eos-rocksdb devtoolset-8 gtest gtest-devel binutils-devel eos-folly \\\n    libmacaroons libmacaroons-devel scitokens-cpp scitokens-cpp-devel \\\n    eos-grpc eos-grpc-devel eos-grpc-plugins libatomic\n\nsource /opt/rh/devtoolset-8/enable\n"
  },
  {
    "path": "utils/el9-dev-environment.sh",
    "content": "#!/bin/bash\n\nEOS_PROJECT_ROOT_DIR=\"$(git rev-parse --show-toplevel)\"\n\n\ndie() {\n    echo \"$@\" 1>&2\n    test -z $TAILPID || kill ${TAILPID} &>/dev/null\n    exit 1\n}\n\n\nsetuprepos()\n{\n\n    local EOS_CODENAME=\"${1:-diopside}\"\n    dnf install -y dnf-plugins-core &&\n        echo -e \"[eos-depend]\\nname=EOS dependencies\\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/eos/${EOS_CODENAME}-depend/el-9/$(uname -m)/\\ngpgcheck=0\\nenabled=1\\npriority=4\" > /etc/yum.repos.d/eos-depend.repo\n\n    dnf install -y ccache cmake gcc-c++ make rpm-build rpm-sign which moreutils ninja-build\n}\n\ninstalldeps()\n{\n    cd $EOS_PROJECT_ROOT_DIR\n    mkdir -p build\n    cd build\n    cmake ../ -DPACKAGEONLY=1 -Wno-dev && make srpm || die \"Unable to create the SRPMS.\"\n    dnf builddep -y SRPMS/*\n}\n\nsetuprepos\ninstalldeps\n"
  },
  {
    "path": "utils/eos-cdmi-setup.sh",
    "content": "#!/usr/bin/env bash\n\n#-------------------------------------------------------------------------\n# File: eos-cdmi-setup.sh\n# Author: Mihai Patrascoiu - CERN\n#-------------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2019 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\n#------------------------------------------------------------------------------\n# Description: The script produces the necessary file interface within /proc/\n#              in order to facilitate CDMI metadata query of QoS classes.\n#              Each file will be virtual, meaning upon an open request,\n#              an associated QoS command will be executed.\n#\n# Context: A CDMI server, equiped with a remote-endpoint cdmi-qos-plugin\n#          (in this case, the cdmi-dcache-qos plugin) would be able\n#          to query EOS via the HTTP interface and gather necessary\n#          metadata about the QoS classes provided.\n#------------------------------------------------------------------------------\n\n#------------------------------------------------------------------------------\n# Utility functions\n#------------------------------------------------------------------------------\n\nfunction usage() {\n  filename=$(basename $0)\n   echo \"usage: $filename [<endpoint>] [-h|--help]\"\n   echo \"                <endpoint> -- MGM endpoint\"\n   echo \"                -h|--help  -- print this message\"\n   echo \"\"\n\n   exit 1\n}\n\nfunction wants_help() {\n  for arg in \"$*\"; do\n    if [ \"$arg\" = \"-h\" ] || [ \"$arg\" = \"--help\" ]; then\n      usage\n    fi\n  done\n}\n\n# Identify MGM endpoint\nfunction identify_endpoint() {\n  endpoint=\"root://localhost:1094/\"\n\n  if [ -n \"$EOS_MGM_URL\" ]; then\n    endpoint=$EOS_MGM_URL\n  elif [ -n \"$1\" ]; then\n    endpoint=$1\n  fi\n\n  # Check if endpoint has URL format\n  if [[ ! $endpoint =~ ^root:// ]]; then\n    endpoint=\"root://$endpoint\"\n\n    if [[ ! $endpoint =~ .*:[0-9]+ ]]; then\n      endpoint=\"$endpoint:1094\"\n    fi\n  fi\n\n  # Validate endpoint\n  local regex=\"root://[a-zA-Z._-]+(:[0-9]+)?[/]?$\"\n  if [[ ! $endpoint =~ $regex ]]; then\n    echo \"error: invalid endpoint '$endpoint'. Must have following format: root://<endpoint>[:<port>]/\"\n    exit 1\n  fi\n}\n\n# Identify instance name\nfunction identify_instance() {\n  if [ -n \"$EOS_INSTANCE_NAME\" ]; then\n    instance=${EOS_INSTANCE_NAME#eos}\n  else\n    instance=$(eos $endpoint version | grep EOS_INSTANCE | cut -d= -f2)\n    instance=${instance#eos}\n  fi\n\n  if [ -z \"$instance\" ]; then\n    echo \"error: could not identify instance name\"\n    exit 1\n  fi\n}\n\n#------------------------------------------------------------------------------\n# Global setup\n#------------------------------------------------------------------------------\n\n# Search for help request\nwants_help $@\n\nendpoint=\"\"\nidentify_endpoint $*\nidentify_instance\n\nexport EOS_MGM_URL=$endpoint\necho \"endpoint=$endpoint\"\necho \"instance=$instance\"\n\nset -e\neos rm -rf \"/eos/$instance/proc/qos-management/\" > /dev/null 2>&1\neos mkdir -p \"/eos/$instance/proc/qos-management/qos/\"\n\n#-------------------------------------------------------------------------\n# The virtual files need to have a 'sys.proc=<command>' extended attribute\n# in order to execute a command on open.\n#\n# When the eos console is executed in debug mode, it prints the request\n# sent to the server, which has the following format:\n# root://<endpoint>//proc/user/?mgm.cmd.proto=<protob64>&<opaque>\n#\n# From the console printed request, the <protob64> string will be captured.\n# Later on, the captured string is set as extended attribute:\n# 'sys.proc=\"mgm.cmd.proto=<protob64>\"'\n#------------------------------------------------------------------------\n\n# The pattern will start matching after it finds the \"mgm.cmd.proto=\" delimiter.\n# It will lazily match everything until the end delimiter: & or end of line\ngrep_pattern=\"(?<=mgm.cmd.proto=).*?(?=&|$)\"\n\nfor entry in \"file\" \"directory\"; do\n  protob64=$(EOS_CONSOLE_DEBUG=1 eos -j $endpoint qos list | grep -Po $grep_pattern)\n\n  eos touch \"/eos/$instance/proc/qos-management/qos/$entry.api\"\n  eos attr set \"sys.proc=mgm.cmd.proto=$protob64\" \"/eos/$instance/proc/qos-management/qos/$entry.api\"\n\n  eos mkdir \"/eos/$instance/proc/qos-management/qos/$entry/\"\n\n  eos -j qos list | jq -r '.[]' | while read qos_class ; do\n    protob64=$(EOS_CONSOLE_DEBUG=1 eos -j $endpoint qos list $qos_class | grep -Po $grep_pattern)\n\n    eos touch \"/eos/$instance/proc/qos-management/qos/$entry/$qos_class.api\"\n    eos attr set \"sys.proc=mgm.cmd.proto=$protob64\" \"/eos/$instance/proc/qos-management/qos/$entry/$qos_class.api\"\n  done\ndone\n"
  },
  {
    "path": "utils/eos-fst-clean",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: eos-fst-clean\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\nruntest=\"yes\";\n\nif [ \"x$1\" = \"x--do-cleanup\" ]; then\n  runtest=\"no\";\nfi\n\necho \"# [ to execute the cleaning of the FST give '--do-cleanup' as option ] ...\";\nfor name in `cat /var/eos/md/so.fst.dump | grep \"key=path\" | awk '{print $2}' | sed s/value://`; do \nnfiles=`find $name -type f  | wc -l`;\nndirs=`find $name -type d  | wc -l`;\n  echo \"# (test=$runtest) FST Disk under path=$name nfiles=$nfiles ndirs=$ndirs\";\ndone;\n\nECHO=\"echo\"\nif [ \"$runtest\" = \"no\" ]; then\n ECHO=\"\"\nfi\n\necho \"# Cleaning FST disk ...\"\nfor name in `cat /var/eos/md/so.fst.dump | grep \"key=path\" | awk '{print $2}' | sed s/value://`; do\n   $ECHO rm -rf /$name 2> /dev/null\ndone\necho \"# Cleaning /var/eos ...\"\nfor name in auth config html md report stage ; do \n$ECHO rm -rf /var/eos/$name 2> /dev/null\ndone\necho \"# Removing /etc/xrd.cf.fst ...\"\n$ECHO rm -rf /etc/xrd.cf.fst 2> /dev/null\necho \"# Removing /etc/sysconfig/eos ...\"\n$ECHO rm -rf /etc/sysconfig/eos 2> /dev/null\necho \"# Removing /var/log/eos/fst ...\"\n$ECHO rm -rf /var/log/eos/fst 2> /dev/null\necho \"# Removing /var/spool/eos/fst ...\"\n$ECHO rm -rf /var/spool/eos/fst 2> /dev/null\n\n\n"
  },
  {
    "path": "utils/eos-log-clean",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: eos-log-clean\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\n\nruntest=\"yes\";\n\nif [ \"x$1\" = \"x--do-cleanup\" ]; then\n  runtest=\"no\";\nfi\n\necho \"# [ to execute the cleaning of logs+spool dirs give '--do-cleanup' as option ] ...\";\n\nECHO=\"echo\"\n\nif [ \"$runtest\" = \"no\" ]; then\n  ECHO=\"\"\nfi\n\necho \"# Cleaning /var/log/eos/ ...\"\n$ECHO rm -rf /var/log/eos/* 2> /dev/null\necho \"# Cleaning /var/spool/eos/ ...\"\n$ECHO rm -rf /var/spool/eos/* 2> /dev/null\n\n\n"
  },
  {
    "path": "utils/eos-mgm-clean",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: eos-mgm-clean\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\nruntest=\"yes\";\n\nif [ \"x$1\" = \"x--do-cleanup\" ]; then\n  runtest=\"no\";\nfi\n\necho \"# [ to execute the cleaning of the MGM give '--do-cleanup' as option ] ...\";\n\nECHO=\"echo\"\n\nif [ \"$runtest\" = \"no\" ]; then\n ECHO=\"\"\nfi\n\necho \"# Cleaning /var/eos ...\"\n$ECHO rm -rf /var/eos/*\necho \"# Removing /etc/xrd.cf.mgm /etc/xrd.cf.mq ...\"\n$ECHO rm -rf /etc/xrd.cf.mgm /etc/xrd.cf.mq 2>/dev/null\necho \"# Removing /etc/sysconfig/eos ...\"\n$ECHO rm -rf /etc/sysconfig/eos 2>/dev/null\necho \"# Removing /var/log/eos/mgm/ /var/log/eos/mq/ ...\"\n$ECHO rm -rf /var/log/eos/mgm /var/log/eos/mq/ 2>/dev/null\necho \"# Removing /var/spool/eos/mgm /var/spool/eos/mq ...\"\n$ECHO rm -rf /var/spool/eos/mgm /var/spool/eos/mq 2>/dev/null\n\n\n"
  },
  {
    "path": "utils/eos-osx-package-prepare.sh",
    "content": "#!/bin/bash\n# Build XRootD\nif [ $# -ne 1 ]; then\n  echo \"Usage: $0 xrootd_version\"\n  exit 1\nfi\n\nXROOTD_VERSION=$1\nmkdir xrootd_src\ngit clone https://github.com/xrootd/xrootd.git xrootd_src\ncd xrootd_src\ngit checkout $XROOTD_VERSION\nmkdir build; mkdir build_install\ncd build\ncmake .. -DCMAKE_INSTALL_PREFIX=../build_install/ -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl/\nmake\nmake install\n"
  },
  {
    "path": "utils/eos-osx-package.sh",
    "content": "#!/bin/bash\n\ncreate_dmg_with_icon() {\n    set -e\n    VOLNAME=\"$1\"\n    DMG=\"$2\"\n    SRC_DIR=\"$3\"\n    ICON_FILE=\"$4\"\n    CODESIGN_IDENTITY=\"$5\"\n\n    TMP_DMG=\"$(mktemp -u -t XXXXXXX)\"\n    trap 'RESULT=$?; rm -f \"$TMP_DMG\"; exit $RESULT' INT QUIT TERM EXIT\n    hdiutil create -srcfolder \"$SRC_DIR\" -volname \"$VOLNAME\" -fs HFS+ \\\n\t    -fsargs \"-c c=64,a=16,e=16\" -format UDRW \"$TMP_DMG\"\n    TMP_DMG=\"${TMP_DMG}.dmg\" # because OSX appends .dmg\n    DEVICE=\"$(hdiutil attach -readwrite -noautoopen \"$TMP_DMG\" | awk 'NR==1{print$1}')\"\n    VOLUME=\"$(mount | grep \"$DEVICE\" | sed 's/^[^ ]* on //;s/ ([^)]*)$//')\"\n    # start of DMG changes\n    cp \"$ICON_FILE\" \"$VOLUME/.VolumeIcon.icns\"\n    SetFile -c icnC \"$VOLUME/.VolumeIcon.icns\"\n    SetFile -a C \"$VOLUME\"\n    # end of DMG changes\n    hdiutil detach \"$DEVICE\"\n    hdiutil convert \"$TMP_DMG\" -format UDZO -imagekey zlib-level=9 -o \"$DMG\"\n    if [ -n \"$CODESIGN_IDENTITY\" ]; then\n\tcodesign -s \"$CODESIGN_IDENTITY\" -v \"$DMG\"\n    fi\n}\n\n\nif [ $# -lt 1 ]; then\n    echo \"Usage: $0 eos_version <cmake_opts>\"\n    exit 1\nfi\n\nGIT_TOP_PATH=\"$(git rev-parse --show-toplevel)\"\nCWD=\"$(pwd)\"\n\nif [ \"${GIT_TOP_PATH}\" != \"${CWD}\" ]; then\n    echo \"error: script must be launched from the root of the project\"\n    exit 1\nfi\n\nEOS_VERSION=$1\nTMP_BUILD=\"${GIT_TOP_PATH}/build/\"\nTMP_DEST=/tmp/eos.dst\nrm -rf ${TMP_BUILD}\nmkdir -p ${TMP_BUILD}\nrm -rf ${TMP_DEST}\nmkdir -p ${TMP_DEST}/usr/local/bin/\nmkdir -p ${TMP_DEST}/usr/local/lib/\nCMAKE_OPT=\"\"\n\nif [ $# -eq 2 ]; then\n   CMAKE_OPT=$2\nfi\n\n# Build eos\ncd ${TMP_BUILD}\ncmake -DCLIENT=1 -DCMAKE_INSTALL_PREFIX=${TMP_DEST}/usr/local -DZLIB_ROOT=/usr/local/opt/zlib/ -DOPENSSL_ROOT=/usr/local/opt/openssl/ -DNCURSES_ROOT=/usr/local/opt/ncurses/ -DZMQ_ROOT=/usr/local/opt/zeromq/ -DXROOTD_ROOT=/usr/local/opt/xrootd/ -DUUID_ROOT=/usr/local/opt/ossp-uuid -DSPARSEHASH_ROOT=/usr/local/opt/google-sparsehash/ ${CMAKE_OPT} ..\n\nif [ $? -ne 0 ]; then\n    echo \"error: cmake configuration failed\"\n    exit 1\nfi\n\n# Build and install\nmake -j 4 && make install\n\nif [ $? -ne 0 ]; then\n    echo \"error: build/install failed\"\n    exit 1\nfi\n\n# Copy non-XRootD dependencies e.g openssl, ncurses for eos, eosd and eosxd\nfor EOS_EXEC in \"${TMP_DEST}/usr/local/bin/eosd\" \"${TMP_DEST}/usr/local/bin/eosxd\" \"${TMP_DEST}/usr/local/bin/eos\"; do\n  for NAME in `otool -L $EOS_EXEC | grep -v rpath | grep /usr/local/ | awk '{print $1}' | grep -v \":\" | grep -v libosxfuse | grep -v libXrd`; do\n    echo $NAME\n    if [ -n \"$NAME\" ];  then\n      sn=`echo $NAME | awk -F \"/\" '{print $NF}'`\n      cp -v $NAME /tmp/eos.dst/usr/local/lib/$sn\n    fi\n  done\ndone\n\n# Copy XRootD dependencies and executables\ncp -v /usr/local/opt/xrootd/lib/libXrd* /tmp/eos.dst/usr/local/lib/\ncp -v /usr/local/opt/xrootd/bin/* /tmp/eos.dst/usr/local/bin/\n\n# Exchange the eosx script with the eos binary\nmv ${TMP_DEST}/usr/local/bin/eos ${TMP_DEST}/usr/local/bin/eos.exe\ncp -v ${GIT_TOP_PATH}/utils/eosx ${TMP_DEST}/usr/local/bin/eos\nchmod ugo+rx ${TMP_DEST}/usr/local/bin/eos\npkgbuild --install-location / --version $EOS_VERSION --identifier com.eos.pkg.app --root /tmp/eos.dst EOS.pkg\n\nrm -rf dmg\nmkdir dmg\ncp EOS.pkg dmg/\ncp ${GIT_TOP_PATH}/utils/README.osx dmg/README.txt\n# cp ../var/eos/html/EOS-logo.jpg dmg/\nunlink eos-osx-$EOS_VERSION.dmg >& /dev/null\ncreate_dmg_with_icon eos-osx-${EOS_VERSION} eos-osx-${EOS_VERSION}.dmg dmg ../icons/EOS.icns\n# create_dmg_with_icon Frobulator Frobulator.dmg path/to/frobulator/dir path/to/someicon.icns [ 'Andreas-Joachim Peters' ]\ncd ..\n"
  },
  {
    "path": "utils/eos-ports-block",
    "content": "#!/bin/bash\n\n# Function to display help message\ndisplay_help() {\n    cat << EOF\nUsage: $0 --ports PORT [PORT ...]\n\nBlock the ports passed in parameters for incoming traffic\n\nThis script will flush the nft list ruleset and will create nft rules\nto accept any incoming connection. Consequently to this, it will create rules\nto block any incoming traffic to the ports passed in parameters.\n\nNote: these rules are NOT persisted!\nTo rollback all the nft rules to the previous states, please run \"$0 --reset-defaults\". This will\ntrigger a systemctl restart nftables and will apply all the rules that are under /etc/nftables.\n\nOptions:\n  --ports           Specify one or more valid port numbers (1-65535) to block.\n  --reset-defaults  Reset defaults nft list (does not accept any arguments).\n  --help            Display this help message.\nExample:\n  $0 --ports 443 8444 1094\n  $0 --reset-defaults\nEOF\n}\n\n# Check for flags\ncase \"$1\" in\n    --help)\n        display_help\n        exit 0\n        ;;\n    --reset-defaults)\n        # Check if there are any extra arguments.\n        if [ $# -gt 1 ]; then\n            echo \"Error: --reset-defaults does not accept any arguments.\" >&2\n            display_help\n            exit 1\n        else\n            echo \"Resetting defaults value for nftables...\"\n            echo \"    systemctl restart nftables\"\n            systemctl restart nftables\n            echo \"Done.\"\n            exit 0\n        fi\n        ;;\n    --ports)\n        # Remove the --ports flag from the arguments list.\n        shift\n        # Ensure at least one port number is provided.\n        if [ $# -eq 0 ]; then\n            echo \"Error: no port number provided!\"\n            display_help\n            exit 1\n        fi\n        # Validate each provided port number.\n        for port in \"$@\"; do\n            # Check if the port is a number.\n            if ! [[ \"$port\" =~ ^[0-9]+$ ]]; then\n                echo \"Error: '$port' is not a valid port number.\" >&2\n                exit 1\n            fi\n            # Check if the port is within the valid range (1 to 65535)\n            if [ \"$port\" -le 0 ] || [ \"$port\" -gt 65535 ]; then\n                echo \"Error: Port '$port' is out of valid range (1-65535).\" >&2\n                exit 1\n            fi\n        done\n        # If all ports are valid, block them\n        echo \"Flushing nft ruleset\"\n        cmd=\"nft flush ruleset\"\n        echo \"   $cmd\"\n        bash -c \"$cmd\"\n        echo \"Done.\"\n        echo \"Accept all incoming traffic\"\n        cmd=\"nft add table inet filter\"\n        echo \"   $cmd\"\n        bash -c \"$cmd\"\n        cmd=\"nft add chain inet filter input { type filter hook input priority 0 \\; policy accept \\; }\"\n        echo \"   $cmd\"\n        bash -c \"$cmd\"\n        echo \"Done.\"\n        echo \"Blocking all provided ports\"\n        for port in \"$@\"; do\n            echo \"   Blocking port $port\"\n            cmd=\"nft add rule inet filter input tcp dport $port drop\"\n            echo \"       $cmd\"\n            bash -c \"$cmd\"\n            echo \"   Done.\"\n        done\n        echo \"Done.\"\n        exit 0\n        ;;\n    *)\n        # For any other flag, display help.\n        display_help\n        exit 1\n        ;;\nesac\n\nexit 0\n"
  },
  {
    "path": "utils/eos-tty-broadcast",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: eos-tty-broadcast\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\ntail -f $1 | while read line || [[ -n \"$line\" ]];do \n  msg=`echo $line | egrep \"$2\"`   \n  if [ -n \"$msg\" ]; then \n    for name in `ls /dev/pts/[0-9]*`; do \n    alrt=`echo $line | egrep ALERT`;\n    progress=`echo $line | egrep PROGRESS`;\n    if [ -n \"$alrt\" ]; then \n\techo -n -e \"\\n\\033[47;34m\\e[5m\" \"$msg\" \"\\033[0m \" > $name\n    else\n\tif [ -n \"$progress\" ]; then\n\t  echo -n -e \"\\n\\033[47;32m\\e[5m\" \"$msg\" \"\\033[0m \" > $name\n\telse\n\t  echo -n -e \"\\n\\033[47;31m\\e[5m\" \"$msg\" \"\\033[0m \" > $name\n\tfi \n    fi\n\n    done\n  fi\ndone\n"
  },
  {
    "path": "utils/eos-uninstall",
    "content": "#!/bin/bash\n# ----------------------------------------------------------------------\n# File: eos-uninstall\n# Author: Andreas-Joachim Peters - CERN\n# ----------------------------------------------------------------------\n\n# ************************************************************************\n# * EOS - the CERN Disk Storage System                                   *\n# * Copyright (C) 2011 CERN/Switzerland                                  *\n# *                                                                      *\n# * This program is free software: you can redistribute it and/or modify *\n# * it under the terms of the GNU General Public License as published by *\n# * the Free Software Foundation, either version 3 of the License, or    *\n# * (at your option) any later version.                                  *\n# *                                                                      *\n# * This program is distributed in the hope that it will be useful,      *\n# * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n# * GNU General Public License for more details.                         *\n# *                                                                      *\n# * You should have received a copy of the GNU General Public License    *\n# * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n# ************************************************************************\nruntest=\"yes\";\n\nif [ \"x$1\" = \"x--do-cleanup\" ]; then\n  runtest=\"no\";\nfi\n\necho \"# [ to execute the cleaning of the MGM give '--do-cleanup' as option ] ...\";\n\nECHO=\"echo\"\nCLEANUP=\"\"\n\nif [ \"$runtest\" = \"no\" ]; then\n  ECHO=\"\"\n  CLEANUP=\"--do-cleanup\"\nfi\n\nfor name in `rpm -qa | grep ^eos- | grep -v eos-cleanup` ; do \n  echo \"# Uninstalling RPM $name ...\";\n  $ECHO rpm -e $name\ndone\n\n/usr/sbin/eos-mgm-clean $CLEANUP\n/usr/sbin/eos-fst-clean $CLEANUP\n/usr/sbin/eos-log-clean $CLEANUP\n\nfor name in `rpm -qa | grep ^eos- ` ; do \n  echo \"# Uninstalling RPM $name ...\";\n  $ECHO rpm -e $name\ndone\n"
  },
  {
    "path": "utils/eos-xrootd-install.sh",
    "content": "#!/bin/bash\nset -e\nVERSION=$1\ncurl http://xrootd.org/download/v$VERSION/xrootd-$VERSION.tar.gz -o xrootd-$VERSION.tar.gz\ntar xvzf *.tar.gz\ncd xrootd-$VERSION\nmkdir build\ncd build\ncmake3 ../ -DCMAKE_INSTALL_PREFIX=/opt/eos/xrootd/ -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_SKIP_BUILD_RPATH=false -DCMAKE_BUILD_WITH_INSTALL_RPATH=false -DCMAKE_INSTALL_RPATH=/opt/eos/xrootd/lib64/ -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=true\nmake -j 4 install\ncd ../\nrm -rf ./xrootd-$VERSION*\n\n"
  },
  {
    "path": "utils/eosx",
    "content": "#!/bin/bash\nexport EOS_MGM_URL=${EOS_MGM_URL-root://eosuser.cern.ch}\nklist >& /dev/null\nif [ $? -ne 0 ]; then\n  echo WARNING: please create a kerberos token first!\n  exit -1\nfi\n\nmyeos=$0\nmyinstall=`dirname $myeos`\nif [ \"$1x\" = \"fusex\" ]; \nthen \n  if [ ! -e \"/usr/local/lib/libosxfuse.2.dylib\" ]; then\n    echo WARNING: please install OSXFUSE 3.5.6 https://osxfuse.github.io/2017/03/15/OSXFUSE-3.5.6.html to use FUSE!\n    exit -1\n  fi\nfi\n$myinstall/eos.exe $@\n\n"
  },
  {
    "path": "utils/filter-trace/.gitignore",
    "content": "__pycache__\n*.pyc\ntmp\n.cache\n"
  },
  {
    "path": "utils/filter-trace/eos-filter-stacktrace",
    "content": "#!/usr/bin/python3\n\n################################################################################\n## Script to parse and filter EOS stacktraces.                                ##\n## Author: Georgios Bitzes - CERN                                             ##\n##                                                                            ##\n## Copyright (C) 2018 CERN/Switzerland                                        ##\n## This program is free software: you can redistribute it and/or modify       ##\n## it under the terms of the GNU General Public License as published by       ##\n## the Free Software Foundation, either version 3 of the License, or          ##\n## (at your option) any later version.                                        ##\n##                                                                            ##\n## This program is distributed in the hope that it will be useful,            ##\n## but WITHOUT ANY WARRANTY; without even the implied warranty of             ##\n## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              ##\n## GNU General Public License for more details.                               ##\n##                                                                            ##\n## You should have received a copy of the GNU General Public License          ##\n## along with this program.  If not, see <http://www.gnu.org/licenses/>.      ##\n################################################################################\n\nimport argparse\nimport re\n\nVERBOSE=False\n\ndef debug(string):\n    global VERBOSE\n    if VERBOSE:\n        print(string)\n\nclass ExclusionFilter:\n    def __init__(self, match):\n        self.eliminations = 0\n        self.description = \"exclude ''\".format(match)\n        self.match = match\n\n    def check(self, thread):\n        for i in range(0, thread.getNumberOfFrames()):\n            if self.match in thread.getFrame(i):\n                self.eliminations += 1\n                return True\n\n        return False\n\nclass BlockedOnLockFilter:\n    def __init__(self):\n        self.eliminations = 0\n        self.description = \"threads blocked on acquiring some lock\"\n\n        self.readLockFilter = ExclusionFilter(\"pthread_rwlock_rdlock\")\n        self.writeLockFilter = ExclusionFilter(\"pthread_rwlock_wrlock\")\n\n    def check(self, thread):\n        if self.readLockFilter.check(thread) or self.writeLockFilter.check(thread):\n            self.eliminations += 1\n            return True\n\n        return False\n\nclass ZmqFilter:\n    def __init__(self):\n        self.eliminations = 0\n        self.description = \"zmq poller\"\n\n    def check(self, thread):\n        if thread.getNumberOfFrames() <= 2:\n            return False\n\n        if (\"epoll_wait\" in thread.getFrame(0) and\n            \"zmq::epoll_t::loop\" in thread.getFrame(1)):\n\n            self.eliminations += 1\n            return True\n\n        if (\"poll\" in thread.getFrame(0) and\n            \"zmq::signaler_t::wait\" in thread.getFrame(1)):\n\n            self.eliminations += 1\n            return True\n\n        if (\"poll\" in thread.getFrame(0) and\n            \"zmq_poll\" in thread.getFrame(1) ):\n\n            self.eliminations += 1\n            return True\n\n        return False\n\nclass QClientFilter:\n    def __init__(self):\n        self.eliminations = 0\n        self.description = \"qclient pollers\"\n\n    def check(self, thread):\n        if thread.getNumberOfFrames() <= 3:\n            return False\n\n        if \"pthread_cond_timedwait\" in thread.getFrame(0):\n            for i in range(0, thread.getNumberOfFrames()):\n                if \"qclient::WriterThread::eventLoop\" in thread.getFrame(i):\n                    self.eliminations += 1\n                    return True\n\n        if (\"poll\" in thread.getFrame(0) and\n            \"qclient::QClient::eventLoop\" in thread.getFrame(1) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"pthread_cond_timedwait\" in thread.getFrame(0) and\n            ExclusionFilter(\"qclient::BackgroundFlusher::monitorAckReception\").check(thread) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"pthread_cond_timedwait\" in thread.getFrame(0) and\n            ExclusionFilter(\"qclient::BackgroundFlusher::processPipeline\").check(thread) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"pthread_cond_timedwait\" in thread.getFrame(0) and\n            ExclusionFilter(\"qclient::BackgroundFlusher::main\").check(thread) ):\n            self.eliminations += 1\n            return True\n\n        if (\"pthread_cond_timedwait\" in thread.getFrame(0) and\n            ExclusionFilter(\"qclient::CallbackExecutorThread::main\").check(thread) ):\n            self.eliminations += 1\n            return True\n\n        if (\"pthread_cond_wait\" in thread.getFrame(0) and\n            \"std::condition_variable::wait\" in thread.getFrame(1) and\n            \"qclient::CallbackExecutorThread::blockUntilStaged\" in thread.getFrame(2) ):\n            self.eliminations += 1\n            return True\n\n        return False\n\nclass XrdSleeperFilter:\n    def __init__(self):\n        self.eliminations = 0\n        self.description = \"xrootd sleeping background threads\"\n\n    def check(self, thread):\n        if thread.getNumberOfFrames() <= 4:\n            return False\n\n        if (\"clock_nanosleep\" in thread.getFrame(0) and\n            \"XrdSysTimer::Wait4Midnight\" in thread.getFrame(1) and\n            \"XrdSysLogger::zHandler\" in thread.getFrame(2) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"pthread_cond_timedwait\" in thread.getFrame(0) and\n            \"XrdSysCondVar::WaitMS\" in thread.getFrame(1) and\n            \"XrdSysCondVar::Wait\" in thread.getFrame(2) and\n            \"XrdBuffManager::Reshape\" in thread.getFrame(3) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"pthread_cond_timedwait\" in thread.getFrame(0) and\n            \"XrdSysCondVar::WaitMS\" in thread.getFrame(1) and\n            \"XrdSysCondVar::Wait\" in thread.getFrame(2) and\n            \"XrdScheduler::TimeSched\" in thread.getFrame(3) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"epoll_wait\" in thread.getFrame(0) and\n            \"XrdPollE::Start\" in thread.getFrame(1) and\n            \"XrdStartPolling\" in thread.getFrame(2) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"nanosleep\" in thread.getFrame(0) and\n            \"XrdSecsssKTRefresh\" in thread.getFrame(1) and\n            \"XrdSysThread_Xeq\" in thread.getFrame(2) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"accept4\" in thread.getFrame(0) and\n            \"XrdSysFD_Accept\" in thread.getFrame(1) and\n            \"XrdNetSocket::Accept\" in thread.getFrame(2) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"accept4\" in thread.getFrame(0) and\n            \"XrdSysFD_Accept\" in thread.getFrame(1) and\n            \"XrdNet::do_Accept_TCP\" in thread.getFrame(2) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"nanosleep\" in thread.getFrame(0) and\n            \"XrdSysTimer::Wait\" in thread.getFrame(1) and\n            \"XrdCl::TaskManager::RunTasks\" in thread.getFrame(2) and\n            \"RunRunnerThread\" in thread.getFrame(3) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"syscall\" in thread.getFrame(0) and\n            \"Wait\" in thread.getFrame(1) and\n            \"Get\" in thread.getFrame(2) and\n            \"XrdCl::JobManager::RunJobs\" in thread.getFrame(3) and\n            \"RunRunnerThread\" in thread.getFrame(4) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"do_futex_wait\" in thread.getFrame(0) and\n            \"__new_sem_wait_slow\" in thread.getFrame(1) and\n            \"sem_wait\" in thread.getFrame(2) and\n            \"Wait\" in thread.getFrame(3) and\n            \"XrdScheduler::Run\" in thread.getFrame(4) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"epoll_wait\" in thread.getFrame(0) and\n            \"XrdSys::IOEvents::PollE::Begin\" in thread.getFrame(1) and\n            \"XrdSys::IOEvents::BootStrap::Start\" in thread.getFrame(2) and\n            \"XrdSysThread_Xeq\" in thread.getFrame(3) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"poll\" in thread.getFrame(0) and\n            \"XrdLink::Recv\" in thread.getFrame(1) and\n            \"XrdXrootdProtocol::getData\" in thread.getFrame(2) and\n            \"XrdXrootdProtocol::Process\" in thread.getFrame(3) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"do_futex_wait.constprop.1\" in thread.getFrame(0) and\n            \"__new_sem_wait_slow.constprop.0\" in thread.getFrame(1) and\n            \"sem_wait\" in thread.getFrame(2) and\n            \"Wait\" in thread.getFrame(3) and\n            \"mainAccept\" in thread.getFrame(4) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"do_futex_wait.constprop.1\" in thread.getFrame(0) and\n            \"__new_sem_wait_slow.constprop.0\" in thread.getFrame(1) and\n            \"sem_wait\" in thread.getFrame(2) and\n            \"Wait\" in thread.getFrame(3) and\n            \"XrdPosixFile::DelayedDestroy\" in thread.getFrame(4) ):\n\n            self.eliminations += 1\n            return True\n\n        return False\n\nclass BackgroundSleeperFilter:\n    def __init__(self):\n        self.eliminations = 0\n        self.description = \"mgm sleeping background threads\"\n\n    def check(self, thread):\n        if thread.getNumberOfFrames() <= 3:\n            return False\n\n        # LRU background threads\n        if (\"nanosleep\" in thread.getFrame(0) and\n            \"XrdSysTimer::Snooze\" in thread.getFrame(1) and\n            \"eos::mgm::LRU::LRUr\" in thread.getFrame(2) ):\n\n            self.eliminations += 1\n            return True\n\n        # Drainer\n        if (\"nanosleep\" in thread.getFrame(0) and\n            \"XrdSysTimer::Wait\" in thread.getFrame(1) and\n            \"eos::mgm::Drainer::Drain\" in thread.getFrame(2) ):\n\n            self.eliminations += 1\n            return True\n\n        # Drainer, again\n        if (\"nanosleep\" in thread.getFrame(0) and\n            \"eos::mgm::Drainer::Drain\" in thread.getFrame(1) ):\n\n            self.eliminations += 1\n            return True\n\n        # Heartbeat check\n        if (\"nanosleep\" in thread.getFrame(0) and\n            \"XrdSysTimer::Snooze\" in thread.getFrame(1) and\n            \"eos::mgm::FsView::HeartBeatCheck\" in thread.getFrame(2) ):\n\n            self.eliminations += 1\n            return True\n\n        # Heartbeat check\n        if (\"nanosleep\" in thread.getFrame(0) and\n            \"sleep_for\" in thread.getFrame(1) and\n            \"eos::mgm::FsView::HeartBeatCheck\" in thread.getFrame(2) ):\n\n            self.eliminations += 1\n            return True\n\n        # Heartbeat check\n        if (\"nanosleep\" in thread.getFrame(0) and\n            \"sleep_for\" in thread.getFrame(1) and\n            \"eos::ContainerAccounting::PropagateUpdates\" in thread.getFrame(2) ):\n\n            self.eliminations += 1\n            return True\n\n        # Heartbeat check\n        if (\"nanosleep\" in thread.getFrame(0) and\n            \"sleep_for\" in thread.getFrame(1) and\n            \"eos::SyncTimeAccounting::PropagateUpdates\" in thread.getFrame(2) ):\n\n            self.eliminations += 1\n            return True\n\n        # Fuse server monitor heartbeat\n        if ( \"nanosleep\" in thread.getFrame(0) and\n             \"XrdSysTimer::Snooze\" in thread.getFrame(1) and\n             \"eos::mgm::FuseServer::Clients::MonitorHeartBeat\" in thread.getFrame(2) ):\n\n             self.eliminations += 1\n             return True\n\n        if ( \"nanosleep\" in thread.getFrame(0) and\n             \"XrdSysTimer::Snooze\" in thread.getFrame(1) and\n             \"eos::mgm::FuseServer::MonitorCaps\" in thread.getFrame(2) ):\n\n             self.eliminations += 1\n             return True\n\n        if ( \"nanosleep\" in thread.getFrame(0) and\n             \"XrdSysTimer::Wait\" in thread.getFrame(1) and\n             \"eos::mgm::GeoBalancer::GeoBalance\" in thread.getFrame(2) ):\n\n             self.eliminations += 1\n             return True\n\n        if ( \"nanosleep\" in thread.getFrame(0) and\n             \"XrdSysTimer::Snooze\" in thread.getFrame(1) and\n             \"eos::mgm::GroupBalancer::GroupBalance\" in thread.getFrame(2) ):\n\n             self.eliminations += 1\n             return True\n\n        if ( \"nanosleep\" in thread.getFrame(0) and\n             \"XrdSysTimer::Snooze\" in thread.getFrame(1) and\n             \"eos::mgm::Balancer::Balance\" in thread.getFrame(2) ):\n\n             self.eliminations += 1\n             return True\n\n        if ( \"nanosleep\" in thread.getFrame(0) and\n             \"XrdSysTimer::Wait\" in thread.getFrame(1) and\n             \"eos::mgm::Stat::Circulate\" in thread.getFrame(2) ):\n\n             self.eliminations += 1\n             return True\n\n        if ( \"nanosleep\" in thread.getFrame(0) and\n             \"XrdSysTimer::Wait\" in thread.getFrame(1) and\n             \"eos::mgm::TransferEngine::Watch\" in thread.getFrame(2) ):\n\n             self.eliminations += 1\n             return True\n\n        if ( \"nanosleep\" in thread.getFrame(0) and\n             \"XrdSysTimer::Wait\" in thread.getFrame(1) and\n             \"eos::mgm::TransferEngine::Scheduler\" in thread.getFrame(2) ):\n\n             self.eliminations += 1\n             return True\n\n        if ( \"nanosleep\" in thread.getFrame(0) and\n             \"XrdSysTimer::Snooze\" in thread.getFrame(1) and\n             \"eos::mgm::Iostat::Receive\" in thread.getFrame(2) ):\n\n             self.eliminations += 1\n             return True\n\n        if ( \"nanosleep\" in thread.getFrame(0) and\n             \"XrdSysTimer::Wait\" in thread.getFrame(1) and\n             \"XrdMgmOfs::ArchiveSubmitter\" in thread.getFrame(2) ):\n\n             self.eliminations += 1\n             return True\n\n        if ( \"nanosleep\" in thread.getFrame(0) and\n             \"XrdSysTimer::Wait\" in thread.getFrame(1) and\n             \"eos::mgm::Master::Supervisor\" in thread.getFrame(2) ):\n\n             self.eliminations += 1\n             return True\n\n        if ( \"nanosleep\" in thread.getFrame(0) and\n             \"XrdSysTimer::Wait\" in thread.getFrame(1) and\n             \"eos::mgm::GeoTreeEngine::listenFsChange\" in thread.getFrame(2) ):\n\n             self.eliminations += 1\n             return True\n\n        if ( \"nanosleep\" in thread.getFrame(0) and\n             \"XrdSysTimer::Wait\" in thread.getFrame(1) and\n             \"eos::mgm::Iostat::Circulate\" in thread.getFrame(2) ):\n\n             self.eliminations += 1\n             return True\n\n        if (\"pthread_cond_timedwait\" in thread.getFrame(0) and\n            ExclusionFilter(\"eos::MetadataFlusher::queueSizeMonitoring\").check(thread) ):\n            return True\n\n        if (\"pthread_cond_wait\" in thread.getFrame(0) and\n            \"XrdSysCondVar::Wait\" in thread.getFrame(1) and\n            \"eos::mgm::Egroup::Refresh\" in thread.getFrame(2) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"pthread_cond_timedwait\" in thread.getFrame(0) and\n            \"XrdSysCondVar::WaitMS\" in thread.getFrame(1) and\n            \"XrdSysCondVar::Wait\" in thread.getFrame(2) and\n            \"eos::common::LvDbDbLogInterface::archiveThread\" in thread.getFrame(3) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"pause\" in thread.getFrame(0) and\n            \"eos::common::HttpServer::Run\" in thread.getFrame(1) and\n            \"XrdSysThread_Xeq\" in thread.getFrame(2) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"pthread_cond_wait\" in thread.getFrame(0) and\n            \"eos::common::ConcurrentQueue\" in thread.getFrame(1) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"pthread_cond_timedwait\" in thread.getFrame(0) and\n            ExclusionFilter(\"eos::common::ThreadPool::ThreadPool\").check(thread) ):\n            self.eliminations += 1\n            return True\n\n        if (\"nanosleep\" in thread.getFrame(0) and\n            \"XrdSysTimer::Wait\" in thread.getFrame(1) and\n            \"eos::mgm::Messaging::Listen\" in thread.getFrame(2) ):\n            self.eliminations += 1\n            return True\n\n        if (\"pthread_cond_wait\" in thread.getFrame(0) and\n            \"XrdSysCondVar::Wait\" in thread.getFrame(1) and\n            \"Wait\" in thread.getFrame(2) and\n            \"XrdMgmOfs::FsConfigListener\" in thread.getFrame(3) and\n            \"XrdMgmOfs::StartMgmFsConfigListener\" in thread.getFrame(4) ):\n            self.eliminations += 1\n            return True\n\n        if (\"pthread_cond_wait\" in thread.getFrame(0) and\n            \"XrdSysCondVar::Wait\" in thread.getFrame(1) and\n            \"XrdMgmOfs::FsConfigListener\" in thread.getFrame(2) and\n            \"XrdMgmOfs::StartMgmFsConfigListener\" in thread.getFrame(3) ):\n            self.eliminations += 1\n            return True\n\n        if (\"pthread_cond_timedwait\" in thread.getFrame(0) and\n            \"XrdSysCondVar::WaitMS\" in thread.getFrame(1) and\n            \"XrdSysCondVar::Wait\" in thread.getFrame(2) and\n            \"eos::mgm::Converter::Convert\" in thread.getFrame(3) ):\n            self.eliminations += 1\n            return True\n\n        if (\"pthread_cond_wait\" in thread.getFrame(0) and\n            \"XrdSysCondVar::Wait\" in thread.getFrame(1) and\n            \"Wait\" in thread.getFrame(2) and\n            \"XrdMqSharedObjectChangeNotifier::SomListener\" in thread.getFrame(3) ):\n            self.eliminations += 1\n            return True\n\n        if (\"nanosleep\" in thread.getFrame(0) and\n            \"XrdSysTimer::Snooze\" in thread.getFrame(1) and\n            \"eos::mgm::Recycle::Recycler\" in thread.getFrame(2) ):\n            self.eliminations += 1\n            return True\n\n        if (\"nanosleep\" in thread.getFrame(0) and\n            \"XrdSysTimer::Snooze\" in thread.getFrame(1) and\n            \"eos::mgm::WFE::WFEr\" in thread.getFrame(2) ):\n            self.eliminations += 1\n            return True\n\n        if (\"nanosleep\" in thread.getFrame(0) and\n            \"XrdSysTimer::Wait\" in thread.getFrame(1) and\n            \"XrdMqSharedObjectManager::FileDumper\" in thread.getFrame(2) and\n            \"XrdMqSharedObjectManager::StartHashDumper\" in thread.getFrame(3) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"nanosleep\" in thread.getFrame(0) and\n            \"XrdSysTimer::Wait\" in thread.getFrame(1) and\n            \"eos::mgm::WFE::WFEr\" in thread.getFrame(2) ):\n\n            self.eliminations += 1\n            return True\n\n        if (\"pthread_cond_wait\" in thread.getFrame(0) and\n            \"wait_pop\" in thread.getFrame(1) and\n            ExclusionFilter(\"_ZNSt6thread11_State_implISt12_Bind_simpleIFZNSt13__future_base17_Async_state_implIS1_IFZN3eos6common10ThreadPoolC4EjjjjjEUlvE_vEEvEC4EOS9_EUlvE_vEEE6_M_runEv\").check(thread) ):\n\n            self.eliminations += 1\n            return True\n\n        if (thread.getNumberOfFrames() == 12 and\n            \"syscall\" in thread.getFrame(0) and\n            \"std::__atomic_futex_unsigned_base::_M_futex_wait_until\" in thread.getFrame(1) and\n            \"_ZZN3eos6common10ThreadPoolC4EjjjjjENKUlvE0_clEv\" in thread.getFrame(8) ):\n\n            self.eliminations += 1\n            return True\n\n        return False\n\nclass RocksLevelDBFilter:\n    def __init__(self):\n        self.eliminations = 0\n        self.description = \"rocksdb/leveldb sleeping background threads\"\n\n    def check(self, thread):\n        if thread.getNumberOfFrames() <= 3:\n            return False\n\n        if (\"pthread_cond_wait\" in thread.getFrame(0) and\n            \"std::condition_variable::wait\" in thread.getFrame(1) and\n            \"rocksdb::ThreadPoolImpl::Impl::BGThread\" in thread.getFrame(2) and\n            \"rocksdb::ThreadPoolImpl::Impl::BGThreadWrapper\" in thread.getFrame(3) ):\n            self.eliminations += 1\n            return True\n\n        if (\"pthread_cond_wait\" in thread.getFrame(0) and\n            \"leveldb::(anonymous namespace)::PosixEnv::BGThreadWrapper\" in thread.getFrame(1) ):\n            self.eliminations += 1\n            return True\n\n        return False\n\nclass FollyFilter:\n    def __init__(self):\n        self.eliminations = 0\n        self.description = \"folly sleeping background threads\"\n\n    def check(self, thread):\n        if (thread.getNumberOfFrames() >= 5 and\n           \"epoll_wait\" in thread.getFrame(0) and\n            \"epoll_dispatch\" in thread.getFrame(1) and\n            \"event_base_loop\" in thread.getFrame(2) and\n            \"folly::EventBase::loopBody\" in thread.getFrame(3) and\n            \"folly::EventBase::loop\" in thread.getFrame(4) ):\n            self.eliminations += 1\n            return True\n\n        return False\n\nclass ThreadStack:\n    def __init__(self, text):\n        self.text = text\n\n        for i in range(len(self.text)):\n            self.text[i] = self.text[i].strip()\n\n        while self.text[0] == \"\":\n            self.text = self.text[1:]\n\n        match = re.search(r'^Thread (\\d+)', self.text[0])\n\n        if not match:\n            raise Exception(\"Cannot parse thread stack, couldn't extract thread id: {}\".format(self.text))\n\n        self.header = self.text[0]\n        self.threadId = int(match.group(1))\n\n        self.frames = []\n\n        currentLine = \"\"\n        for line in self.text[1:]:\n            match = re.search(r'^#([0-9]+)[ ]+', line)\n\n            # Frame spans two lines\n            if not match:\n                if not currentLine.strip() == \"\" and not line.strip() == \"\":\n                    currentLine = currentLine + \" \" + line\n                else:\n                    currentLine += line\n                continue\n\n            # Empty line?\n            if line.strip() == \"\":\n                continue\n\n            # Legit new frame - retire previous line\n            if currentLine.strip() != \"\":\n                self.frames += [currentLine]\n\n            # Replace currentLine\n            currentLine = line\n\n        self.frames += [currentLine]\n        debug(\"Parsed thread stack with id = {} and {} frames\".format(self.threadId, len(self.frames)))\n\n    def getThreadID(self):\n        return self.threadId\n\n    def getFrame(self, i):\n        return self.frames[i]\n\n    def getNumberOfFrames(self):\n        return len(self.frames)\n\n    def tostr(self):\n        return self.header + \"\\n\" + \"\\n\".join(self.frames)\n\n    def getHeader(self):\n        return self.header\n\nclass StackTrace:\n    def __init__(self, text):\n        self.text = text\n        self.threads = []\n\n        # Split thread boundaries\n        tmp = []\n        for line in self.text:\n            if line.strip() == \"\": continue\n\n            # New thread boundary? Clear tmp.\n            if line.startswith(\"Thread \"):\n                if len(tmp) != 0:\n                    self.threads += [ThreadStack(tmp)]\n                tmp = []\n\n            tmp += [line]\n\n        # Final thread?\n        if len(tmp) != 0:\n            self.threads += [ThreadStack(tmp)]\n\n    def getThread(self, i):\n        return self.threads[i]\n\n    def getNumberOfThreads(self):\n        return len(self.threads)\n\ndef build_filters(args):\n    filters = []\n\n    if not args.want_zmq:\n        filters += [ZmqFilter()]\n\n    if not args.want_qcl:\n        filters += [QClientFilter()]\n\n    if not args.want_xrd_sleepers:\n        filters += [XrdSleeperFilter()]\n\n    if not args.want_mgm_sleepers:\n        filters += [BackgroundSleeperFilter()]\n\n    if not args.want_rocksdb_sleepers:\n        filters += [RocksLevelDBFilter()]\n\n    if not args.want_folly_sleepers:\n        filters += [FollyFilter()]\n\n    if args.hide_blocked_on_locks:\n        filters += [BlockedOnLockFilter()]\n\n    if args.exclusions:\n        for excl in args.exclusions:\n            print(excl)\n            filters += [ExclusionFilter(excl)]\n\n    return filters\n\ndef parseTrace(args):\n    threads = []\n\n    with open(args.path, \"r\") as f:\n        content = f.readlines()\n\n    while content[0].strip() == \"\":\n        content = content[1:]\n\n    trace = StackTrace(content)\n    filters = build_filters(args)\n\n    for thread in trace.threads:\n        eliminated = False\n\n        for filt in filters:\n            if filt.check(thread):\n                eliminated = True\n                break\n\n        if not eliminated:\n            print(thread.tostr())\n            print(\"\")\n\ndef getargs():\n    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter,\n                                     description=\"Goes through an MGM stacktrace and weeds out un-interesting functions.\\n\")\n\n    parser.add_argument('--path', type=str, required=True, help=\"Stacktrace path\")\n    parser.add_argument('--verbose', dest='verbose', action='store_true', help=\"Verbose output\")\n\n    group = parser.add_argument_group('Built-in filters enabled by default', 'A certain number of filters are enabled by default to greatly reduce the initial noise, pass these flags to disable them. Only sleeping threads will be filtered out - if some poller, for example, is doing work at the time of capturing the stacktrace, it will be shown by default, unless you filter it out manually using a custom filter.')\n    group.add_argument('--show-zmq-pollers', dest='want_zmq', action='store_true', help=\"Show sleeping ZMQ background threads.\")\n    group.add_argument('--show-qclient-pollers', dest='want_qcl', action='store_true', help=\"Show sleeping QClient background threads.\")\n    group.add_argument('--show-xrd-threads', dest='want_xrd_sleepers', action='store_true', help=\"Show sleeping XRootD background threads.\")\n    group.add_argument('--show-background-mgm-threads', dest='want_mgm_sleepers', action='store_true', help=\"Show sleeping MGM background threads. (Balancer, GroupBalancer, GeoGalancer, Converter, etc)\")\n    group.add_argument('--show-rocksdb-threads', dest='want_rocksdb_sleepers', action='store_true', help=\"Show sleeping rocksdb / leveldb background threads.\")\n    group.add_argument('--show-folly-threads', dest='want_folly_sleepers', action='store_true', help=\"Show sleeping folly threads.\")\n\n\n    group = parser.add_argument_group('Custom filters', '')\n    group.add_argument('--hide-blocked-on-locks', dest='hide_blocked_on_locks', action='store_true', help=\"Hide threads blocked on acquiring a lock.\")\n    group.add_argument('--exclude', dest='exclusions', nargs='+', help=\"Exclude threads whose stacktraces contain the passed string. You can pass multiple arguments.\")\n\n    args = parser.parse_args()\n\n    global VERBOSE\n    VERBOSE = args.verbose\n    return args\n\ndef main():\n    args = getargs()\n    parseTrace(args)\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "utils/filter-trace/test-eos-filter-stacktrace.py",
    "content": "################################################################################\n## Unit tests for eos-filter-stacktrace.py                                    ##\n## Author: Georgios Bitzes - CERN                                             ##\n##                                                                            ##\n## Copyright (C) 2018 CERN/Switzerland                                        ##\n## This program is free software: you can redistribute it and/or modify       ##\n## it under the terms of the GNU General Public License as published by       ##\n## the Free Software Foundation, either version 3 of the License, or          ##\n## (at your option) any later version.                                        ##\n##                                                                            ##\n## This program is distributed in the hope that it will be useful,            ##\n## but WITHOUT ANY WARRANTY; without even the implied warranty of             ##\n## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              ##\n## GNU General Public License for more details.                               ##\n##                                                                            ##\n## You should have received a copy of the GNU General Public License          ##\n## along with this program.  If not, see <http://www.gnu.org/licenses/>.      ##\n################################################################################\n\nfrom __future__ import absolute_import\nstackfilter = __import__('eos-filter-stacktrace')\nimport pytest\n\ndef test_parseThreadStack():\n    zmqFilter = stackfilter.ZmqFilter()\n\n    stack = stackfilter.ThreadStack([\n      'Thread 76 (Thread 0x7f7f0a6c1700 (LWP 118449)):\\n',\n      '#0  0x00007f80596242ae in pthread_rwlock_wrlock () from /lib64/libpthread.so.0\\n',\n      '#1  0x00007f805161ef85 in eos::common::RWMutex::LockWrite (this=0x7f8051ffacd8 <XrdSfsGetFileSystem::myFS+6200>) at ../../common/RWMutex.cc:339\\n',\n      '#2  0x00007f8051b2075a in XrdMgmOfs::FSctl (this=0x7f8051ff94a0 <XrdSfsGetFileSystem::myFS>, cmd=<optimized out>, args=..., error=..., \\n',\n      '    client=0x7f7faad8f488) at ../../mgm/XrdMgmOfs/fsctl/Drop.cc:47\\n',\n      '#3  0x00007f8059d27344 in XrdXrootdProtocol::do_Qopaque (this=0x7f7fc18b2e80, qopt=<optimized out>)\\n', '    at /usr/src/debug/xrootd/xrootd/src/XrdXrootd/XrdXrootdXeq.cc:1779\\n',\n      '#4  0x00007f8059aa1149 in XrdLink::DoIt (this=0x7f7faf3ea2d8) at /usr/src/debug/xrootd/xrootd/src/Xrd/XrdLink.cc:435\\n',\n      '#5  0x00007f8059aa453f in XrdScheduler::Run (this=0x610e78 <XrdMain::Config+440>) at /usr/src/debug/xrootd/xrootd/src/Xrd/XrdScheduler.cc:357\\n',\n      '#6  0x00007f8059aa4689 in XrdStartWorking (carg=<optimized out>) at /usr/src/debug/xrootd/xrootd/src/Xrd/XrdScheduler.cc:87\\n',\n      '#7  0x00007f8059a640f7 in XrdSysThread_Xeq (myargs=0x7f7faf3a1600) at /usr/src/debug/xrootd/xrootd/src/XrdSys/XrdSysPthread.cc:86\\n',\n      '#8  0x00007f8059620e25 in start_thread () from /lib64/libpthread.so.0\\n',\n      '#9  0x00007f805892634d in clone () from /lib64/libc.so.6\\n',\n      '\\n'\n    ])\n\n    assert stack.getThreadID() == 76\n    assert not zmqFilter.check(stack)\n\n    print(stack.getFrame(0))\n    assert stack.getFrame(0) == \"#0  0x00007f80596242ae in pthread_rwlock_wrlock () from /lib64/libpthread.so.0\"\n    assert stack.getFrame(1) == \"#1  0x00007f805161ef85 in eos::common::RWMutex::LockWrite (this=0x7f8051ffacd8 <XrdSfsGetFileSystem::myFS+6200>) at ../../common/RWMutex.cc:339\"\n    assert stack.getFrame(2) == \"#2  0x00007f8051b2075a in XrdMgmOfs::FSctl (this=0x7f8051ff94a0 <XrdSfsGetFileSystem::myFS>, cmd=<optimized out>, args=..., error=..., client=0x7f7faad8f488) at ../../mgm/XrdMgmOfs/fsctl/Drop.cc:47\"\n    assert stack.getFrame(3) == \"#3  0x00007f8059d27344 in XrdXrootdProtocol::do_Qopaque (this=0x7f7fc18b2e80, qopt=<optimized out>) at /usr/src/debug/xrootd/xrootd/src/XrdXrootd/XrdXrootdXeq.cc:1779\"\n    assert stack.getFrame(4) == \"#4  0x00007f8059aa1149 in XrdLink::DoIt (this=0x7f7faf3ea2d8) at /usr/src/debug/xrootd/xrootd/src/Xrd/XrdLink.cc:435\"\n    assert stack.getFrame(5) == \"#5  0x00007f8059aa453f in XrdScheduler::Run (this=0x610e78 <XrdMain::Config+440>) at /usr/src/debug/xrootd/xrootd/src/Xrd/XrdScheduler.cc:357\"\n    assert stack.getFrame(6) == \"#6  0x00007f8059aa4689 in XrdStartWorking (carg=<optimized out>) at /usr/src/debug/xrootd/xrootd/src/Xrd/XrdScheduler.cc:87\"\n    assert stack.getFrame(7) == \"#7  0x00007f8059a640f7 in XrdSysThread_Xeq (myargs=0x7f7faf3a1600) at /usr/src/debug/xrootd/xrootd/src/XrdSys/XrdSysPthread.cc:86\"\n    assert stack.getFrame(8) == \"#8  0x00007f8059620e25 in start_thread () from /lib64/libpthread.so.0\"\n    assert stack.getFrame(9) == \"#9  0x00007f805892634d in clone () from /lib64/libc.so.6\"\n\n    assert stack.getNumberOfFrames() == 10\n    assert not zmqFilter.check(stack)\n\ndef test_parseThreadStack2():\n    zmqFilter = stackfilter.ZmqFilter()\n    stack = stackfilter.ThreadStack([\n        \"Thread 1132 (Thread 0x7fa4d93fc700 (LWP 114706)):\\n\",\n        \"#0  0x00007fa5947dd923 in epoll_wait () from /lib64/libc.so.6\\n\",\n        \"#1  0x00007fa58ccd4309 in zmq::epoll_t::loop() () from /lib64/libzmq.so.5\\n\",\n        \"#2  0x00007fa58cd083a6 in thread_routine () from /lib64/libzmq.so.5\\n\",\n        \"#3  0x00007fa5954d7e25 in start_thread () from /lib64/libpthread.so.0\\n\",\n        \"#4  0x00007fa5947dd34d in clone () from /lib64/libc.so.6\\n\"\n    ])\n\n    assert stack.getNumberOfFrames() == 5\n    assert zmqFilter.check(stack)\n\n    assert stack.tostr() == (\n      \"Thread 1132 (Thread 0x7fa4d93fc700 (LWP 114706)):\\n\" +\n      \"#0  0x00007fa5947dd923 in epoll_wait () from /lib64/libc.so.6\\n\" +\n      \"#1  0x00007fa58ccd4309 in zmq::epoll_t::loop() () from /lib64/libzmq.so.5\\n\" +\n      \"#2  0x00007fa58cd083a6 in thread_routine () from /lib64/libzmq.so.5\\n\" +\n      \"#3  0x00007fa5954d7e25 in start_thread () from /lib64/libpthread.so.0\\n\" +\n      \"#4  0x00007fa5947dd34d in clone () from /lib64/libc.so.6\"\n    )\n\n\ndef test_parseStackTrace():\n    zmqFilter = stackfilter.ZmqFilter()\n\n    trace = stackfilter.StackTrace([\n      'Thread 99 (Thread 0x7f7f0bdd8700 (LWP 118421)):\\n',\n      '#0  0x00007f8059624094 in pthread_rwlock_rdlock () from /lib64/libpthread.so.0\\n',\n      '#1  0x00007f805161e82e in eos::common::RWMutex::LockRead (this=0x7f8051ffacd8 <XrdSfsGetFileSystem::myFS+6200>) at ../../common/RWMutex.cc:258\\n',\n      '#2  0x00007f8051b30742 in XrdMgmOfs::FSctl (this=0x7f8051ff94a0 <XrdSfsGetFileSystem::myFS>, cmd=<optimized out>, args=..., error=..., \\n',\n      '    client=<optimized out>) at ../../mgm/XrdMgmOfs/fsctl/Schedule2Delete.cc:67\\n',\n      '#3  0x00007f8059d27344 in XrdXrootdProtocol::do_Qopaque (this=0x7f7fb2b4f600, qopt=<optimized out>)\\n',\n      '    at /usr/src/debug/xrootd/xrootd/src/XrdXrootd/XrdXrootdXeq.cc:1779\\n',\n      '#4  0x00007f8059aa1149 in XrdLink::DoIt (this=0x7f7f8c17dac8) at /usr/src/debug/xrootd/xrootd/src/Xrd/XrdLink.cc:435\\n',\n      '#5  0x00007f8059aa453f in XrdScheduler::Run (this=0x610e78 <XrdMain::Config+440>) at /usr/src/debug/xrootd/xrootd/src/Xrd/XrdScheduler.cc:357\\n',\n      '#6  0x00007f8059aa4689 in XrdStartWorking (carg=<optimized out>) at /usr/src/debug/xrootd/xrootd/src/Xrd/XrdScheduler.cc:87\\n',\n      '#7  0x00007f8059a640f7 in XrdSysThread_Xeq (myargs=0x7f7f83cfc0c0) at /usr/src/debug/xrootd/xrootd/src/XrdSys/XrdSysPthread.cc:86\\n',\n      '#8  0x00007f8059620e25 in start_thread () from /lib64/libpthread.so.0\\n', '#9  0x00007f805892634d in clone () from /lib64/libc.so.6\\n',\n      '\\n',\n      'Thread 98 (Thread 0x7f7f0bcd7700 (LWP 118422)):\\n',\n      '#0  0x00007f80596242ae in pthread_rwlock_wrlock () from /lib64/libpthread.so.0\\n',\n      '#1  0x00007f805161ef85 in eos::common::RWMutex::LockWrite (this=0x7f8051ffacd8 <XrdSfsGetFileSystem::myFS+6200>) at ../../common/RWMutex.cc:339\\n',\n      '#2  0x00007f8051b2075a in XrdMgmOfs::FSctl (this=0x7f8051ff94a0 <XrdSfsGetFileSystem::myFS>, cmd=<optimized out>, args=..., error=..., \\n',\n      '    client=0x7f7fb2b77148) at ../../mgm/XrdMgmOfs/fsctl/Drop.cc:47\\n',\n      '#3  0x00007f8059d27344 in XrdXrootdProtocol::do_Qopaque (this=0x7f7fb26dd600, qopt=<optimized out>)\\n',\n      '    at /usr/src/debug/xrootd/xrootd/src/XrdXrootd/XrdXrootdXeq.cc:1779\\n',\n      '#4  0x00007f8059aa1149 in XrdLink::DoIt (this=0x7f7f8c17dd78) at /usr/src/debug/xrootd/xrootd/src/Xrd/XrdLink.cc:435\\n',\n      '#5  0x00007f8059aa453f in XrdScheduler::Run (this=0x610e78 <XrdMain::Config+440>) at /usr/src/debug/xrootd/xrootd/src/Xrd/XrdScheduler.cc:357\\n',\n      '#6  0x00007f8059aa4689 in XrdStartWorking (carg=<optimized out>) at /usr/src/debug/xrootd/xrootd/src/Xrd/XrdScheduler.cc:87\\n',\n      '#7  0x00007f8059a640f7 in XrdSysThread_Xeq (myargs=0x7f7f8c13f3a0) at /usr/src/debug/xrootd/xrootd/src/XrdSys/XrdSysPthread.cc:86\\n',\n      '#8  0x00007f8059620e25 in start_thread () from /lib64/libpthread.so.0\\n',\n      '#9  0x00007f805892634d in clone () from /lib64/libc.so.6\\n',\n      '\\n',\n    ])\n\n    assert trace.getNumberOfThreads() == 2\n    assert trace.getThread(0).getFrame(4) == \"#4  0x00007f8059aa1149 in XrdLink::DoIt (this=0x7f7f8c17dac8) at /usr/src/debug/xrootd/xrootd/src/Xrd/XrdLink.cc:435\"\n\n    assert not zmqFilter.check(trace.getThread(0))\n    assert not zmqFilter.check(trace.getThread(1))\n\n    assert trace.getThread(0).getFrame(3) != trace.getThread(1).getFrame(3)\n    assert trace.getThread(1).getFrame(3) == \"#3  0x00007f8059d27344 in XrdXrootdProtocol::do_Qopaque (this=0x7f7fb26dd600, qopt=<optimized out>) at /usr/src/debug/xrootd/xrootd/src/XrdXrootd/XrdXrootdXeq.cc:1779\"\n\ndef test_parseThreadStack3():\n\n    trace = stackfilter.StackTrace([\n        \"Thread 4 (Thread 0x7fd9fd5ed700 (LWP 200301)):\\n\",\n        \"#0  0x00007fdab647f945 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0\\n\",\n        \"#1  0x00007fdab68bf12d in XrdSysCondVar::Wait (this=this@entry=0x7fd9fd5e93c8) at /usr/src/debug/xrootd/xrootd/src/XrdSys/XrdSysPthread.cc:106\\n\",\n        \"#2  0x00007fdaad0588c8 in WaitForResponse (this=0x7fd9fd5e93b0) at /usr/src/debug/xrootd/xrootd/src/XrdCl/XrdClMessageUtils.hh:94\\n\",\n        \"#3  WaitForStatus (handler=0x7fd9fd5e93b0) at /usr/src/debug/xrootd/xrootd/src/XrdCl/XrdClMessageUtils.hh:151\\n\",\n        \"#4  XrdCl::File::Close (this=this@entry=0x7fd9fd5e9a40, timeout=<optimized out>) at /usr/src/debug/xrootd/xrootd/src/XrdCl/XrdClFile.cc:150\\n\",\n        \"#5  0x00007fdaad0839c2 in XrdCl::ThirdPartyCopyJob::Run (this=0x7fda71c3ae00, progress=<optimized out>) at /usr/src/debug/xrootd/xrootd/src/XrdCl/XrdClThirdPartyCopyJob.cc:374\\n\",\n        \"#6  0x00007fdaad09792c in XrdCl::TPFallBackCopyJob::Run (this=0x7fda71c3d140, progress=0x0) at /usr/src/debug/xrootd/xrootd/src/XrdCl/XrdClTPFallBackCopyJob.cc:86\\n\",\n        \"#7  0x00007fdaad068c47 in (anonymous namespace)::QueuedCopyJob::Run (this=this@entry=0x7fd9fd5eac00) at /usr/src/debug/xrootd/xrootd/src/XrdCl/XrdClCopyProcess.cc:84\\n\",\n        \"#8  0x00007fdaad069e3a in XrdCl::CopyProcess::Run (this=this@entry=0x7fd9fd5eb380, progress=progress@entry=0x0) at /usr/src/debug/xrootd/xrootd/src/XrdCl/XrdClCopyProcess.cc:361\\n\",\n        \"#9  0x00007fdaae8dd772 in eos::mgm::ConverterJob::DoIt (this=0x7fda82014350) at ../../mgm/Converter.cc:215\\n\",\n        \"#10 0x00007fdab68ff53f in XrdScheduler::Run (this=0x7fda96f8d3c0) at /usr/src/debug/xrootd/xrootd/src/Xrd/XrdScheduler.cc:357\\n\",\n        \"#11 0x00007fdab68ff689 in XrdStartWorking (carg=<optimized out>) at /usr/src/debug/xrootd/xrootd/src/Xrd/XrdScheduler.cc:87\\n\",\n        \"#12 0x00007fdab68bf0f7 in XrdSysThread_Xeq (myargs=0x7fda83c0fc80) at /usr/src/debug/xrootd/xrootd/src/XrdSys/XrdSysPthread.cc:86\\n\",\n        \"#13 0x00007fdab647be25 in start_thread () from /lib64/libpthread.so.0\\n\",\n        \"#14 0x00007fdab578134d in clone () from /lib64/libc.so.6\\n\",\n    ])\n\n    assert trace.getNumberOfThreads() == 1\n    assert trace.getThread(0).getFrame(0) == \"#0  0x00007fdab647f945 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0\"\n    assert trace.getThread(0).getFrame(1) == \"#1  0x00007fdab68bf12d in XrdSysCondVar::Wait (this=this@entry=0x7fd9fd5e93c8) at /usr/src/debug/xrootd/xrootd/src/XrdSys/XrdSysPthread.cc:106\"\n    assert trace.getThread(0).getFrame(2) == \"#2  0x00007fdaad0588c8 in WaitForResponse (this=0x7fd9fd5e93b0) at /usr/src/debug/xrootd/xrootd/src/XrdCl/XrdClMessageUtils.hh:94\"\n    assert trace.getThread(0).getFrame(3) == \"#3  WaitForStatus (handler=0x7fd9fd5e93b0) at /usr/src/debug/xrootd/xrootd/src/XrdCl/XrdClMessageUtils.hh:151\"\n    assert trace.getThread(0).getFrame(4) == \"#4  XrdCl::File::Close (this=this@entry=0x7fd9fd5e9a40, timeout=<optimized out>) at /usr/src/debug/xrootd/xrootd/src/XrdCl/XrdClFile.cc:150\"\n    assert trace.getThread(0).getFrame(5) == \"#5  0x00007fdaad0839c2 in XrdCl::ThirdPartyCopyJob::Run (this=0x7fda71c3ae00, progress=<optimized out>) at /usr/src/debug/xrootd/xrootd/src/XrdCl/XrdClThirdPartyCopyJob.cc:374\"\n    assert trace.getThread(0).getFrame(6) == \"#6  0x00007fdaad09792c in XrdCl::TPFallBackCopyJob::Run (this=0x7fda71c3d140, progress=0x0) at /usr/src/debug/xrootd/xrootd/src/XrdCl/XrdClTPFallBackCopyJob.cc:86\"\n    assert trace.getThread(0).getFrame(7) == \"#7  0x00007fdaad068c47 in (anonymous namespace)::QueuedCopyJob::Run (this=this@entry=0x7fd9fd5eac00) at /usr/src/debug/xrootd/xrootd/src/XrdCl/XrdClCopyProcess.cc:84\"\n    assert trace.getThread(0).getFrame(8) == \"#8  0x00007fdaad069e3a in XrdCl::CopyProcess::Run (this=this@entry=0x7fd9fd5eb380, progress=progress@entry=0x0) at /usr/src/debug/xrootd/xrootd/src/XrdCl/XrdClCopyProcess.cc:361\"\n    assert trace.getThread(0).getFrame(9) == \"#9  0x00007fdaae8dd772 in eos::mgm::ConverterJob::DoIt (this=0x7fda82014350) at ../../mgm/Converter.cc:215\"\n    assert trace.getThread(0).getFrame(10) == \"#10 0x00007fdab68ff53f in XrdScheduler::Run (this=0x7fda96f8d3c0) at /usr/src/debug/xrootd/xrootd/src/Xrd/XrdScheduler.cc:357\"\n    assert trace.getThread(0).getFrame(11) == \"#11 0x00007fdab68ff689 in XrdStartWorking (carg=<optimized out>) at /usr/src/debug/xrootd/xrootd/src/Xrd/XrdScheduler.cc:87\"\n    assert trace.getThread(0).getFrame(12) == \"#12 0x00007fdab68bf0f7 in XrdSysThread_Xeq (myargs=0x7fda83c0fc80) at /usr/src/debug/xrootd/xrootd/src/XrdSys/XrdSysPthread.cc:86\"\n    assert trace.getThread(0).getFrame(13) == \"#13 0x00007fdab647be25 in start_thread () from /lib64/libpthread.so.0\"\n    assert trace.getThread(0).getFrame(14) == \"#14 0x00007fdab578134d in clone () from /lib64/libc.so.6\"\n"
  },
  {
    "path": "utils/flamegraph/eos-make-flamegraph",
    "content": "#!/bin/bash\nexport `eos version`;\n\npid1=`ps aux | grep \"eos-mgm -n mgm\" | grep -v grep | awk '{print $2}'`\npid2=`ps aux | grep \"xrootd -n mgm\" | grep -v grep | head -1  | awk '{print $2}'`\nunlink flame.done 2> /dev/null\n\nflamefile=\"eos.${EOS_SERVER_RELEASE}.`date +%s`.svg\"\nflamereport=${flamefile}.log\n\necho Flamegraph: $flamefile\n\nif [ -n \"$pid1\" ]; then\n    eos debug notice\n    eos ns stat --reset > /dev/null\n    (\n\tperf record -F 99 -p $pid1 -g -- sleep 60 > perf.log\n\tperf script > out.perf\n\teos-util-stackcollapse --addrs out.perf > out.folded\n\teos-util-flamegraph out.folded > $flamefile\n\tunlink perf.log\n\tunlink out.perf\n\tunlink out.folded\n\tmv perf.data $flamefile.data\n\ttouch flame.done\n    ) &\n\n    eos ns benchmark 100 1 500 > $flamereport\n    eos ns stat >> $flamereport\n    eos debug info\nfi\n\nif [ -n \"$pid2\" ]; then\n    eos debug notice\n    eos ns stat --reset > /dev/null\n    (\n\tperf record -F 99 -p $pid2 -g -- sleep 60 > perf.log\n\tperf script > out.perf\n\teos-util-stackcollapse --addrs out.perf > out.folded\n\teos-util-flamegraph out.folded > $flamefile\n\tunlink perf.log\n\tunlink out.perf\n\tunlink out.folded\n\tmv perf.data $flamefile.data\n\ttouch flame.done\n    ) &\n\n    eos ns benchmark 1000 1 50 > $flamereport\n    eos ns stat >> $flamereport\n    eos debug info\nfi\n\nwhile [ ! -f flame.done ]; do\n    sleep 1\ndone\n\nunlink flame.done\necho \"[ makeflame ] Wrote flamegraph svg file $flamefile\"\necho \"[ makeflame ] Wrote benchmark report file $flamereport\"\necho \"[ makeflame ] Wrote perf data file $flamefile.data\"\n      \n\t  \n\n"
  },
  {
    "path": "utils/flamegraph/eos-util-flamegraph",
    "content": "#!/usr/bin/perl -w\n#\n# flamegraph.pl\t\tflame stack grapher.\n#\n# This takes stack samples and renders a call graph, allowing hot functions\n# and codepaths to be quickly identified.  Stack samples can be generated using\n# tools such as DTrace, perf, SystemTap, and Instruments.\n#\n# USAGE: ./flamegraph.pl [options] input.txt > graph.svg\n#\n#        grep funcA input.txt | ./flamegraph.pl [options] > graph.svg\n#\n# Then open the resulting .svg in a web browser, for interactivity: mouse-over\n# frames for info, click to zoom, and ctrl-F to search.\n#\n# Options are listed in the usage message (--help).\n#\n# The input is stack frames and sample counts formatted as single lines.  Each\n# frame in the stack is semicolon separated, with a space and count at the end\n# of the line.  These can be generated for Linux perf script output using\n# stackcollapse-perf.pl, for DTrace using stackcollapse.pl, and for other tools\n# using the other stackcollapse programs.  Example input:\n#\n#  swapper;start_kernel;rest_init;cpu_idle;default_idle;native_safe_halt 1\n#\n# An optional extra column of counts can be provided to generate a differential\n# flame graph of the counts, colored red for more, and blue for less.  This\n# can be useful when using flame graphs for non-regression testing.\n# See the header comment in the difffolded.pl program for instructions.\n#\n# The input functions can optionally have annotations at the end of each\n# function name, following a precedent by some tools (Linux perf's _[k]):\n# \t_[k] for kernel\n#\t_[i] for inlined\n#\t_[j] for jit\n#\t_[w] for waker\n# Some of the stackcollapse programs support adding these annotations, eg,\n# stackcollapse-perf.pl --kernel --jit. They are used merely for colors by\n# some palettes, eg, flamegraph.pl --color=java.\n#\n# The output flame graph shows relative presence of functions in stack samples.\n# The ordering on the x-axis has no meaning; since the data is samples, time\n# order of events is not known.  The order used sorts function names\n# alphabetically.\n#\n# While intended to process stack samples, this can also process stack traces.\n# For example, tracing stacks for memory allocation, or resource usage.  You\n# can use --title to set the title to reflect the content, and --countname\n# to change \"samples\" to \"bytes\" etc.\n#\n# There are a few different palettes, selectable using --color.  By default,\n# the colors are selected at random (except for differentials).  Functions\n# called \"-\" will be printed gray, which can be used for stack separators (eg,\n# between user and kernel stacks).\n#\n# HISTORY\n#\n# This was inspired by Neelakanth Nadgir's excellent function_call_graph.rb\n# program, which visualized function entry and return trace events.  As Neel\n# wrote: \"The output displayed is inspired by Roch's CallStackAnalyzer which\n# was in turn inspired by the work on vftrace by Jan Boerhout\".  See:\n# https://blogs.oracle.com/realneel/entry/visualizing_callstacks_via_dtrace_and\n#\n# Copyright 2016 Netflix, Inc.\n# Copyright 2011 Joyent, Inc.  All rights reserved.\n# Copyright 2011 Brendan Gregg.  All rights reserved.\n#\n# CDDL HEADER START\n#\n# The contents of this file are subject to the terms of the\n# Common Development and Distribution License (the \"License\").\n# You may not use this file except in compliance with the License.\n#\n# You can obtain a copy of the license at docs/cddl1.txt or\n# http://opensource.org/licenses/CDDL-1.0.\n# See the License for the specific language governing permissions\n# and limitations under the License.\n#\n# When distributing Covered Code, include this CDDL HEADER in each\n# file and include the License file at docs/cddl1.txt.\n# If applicable, add the following below this CDDL HEADER, with the\n# fields enclosed by brackets \"[]\" replaced with your own identifying\n# information: Portions Copyright [yyyy] [name of copyright owner]\n#\n# CDDL HEADER END\n#\n# 11-Oct-2014\tAdrien Mahieux\tAdded zoom.\n# 21-Nov-2013   Shawn Sterling  Added consistent palette file option\n# 17-Mar-2013   Tim Bunce       Added options and more tunables.\n# 15-Dec-2011\tDave Pacheco\tSupport for frames with whitespace.\n# 10-Sep-2011\tBrendan Gregg\tCreated this.\n\nuse strict;\n\nuse Getopt::Long;\n\nuse open qw(:std :utf8);\n\n# tunables\nmy $encoding;\nmy $fonttype = \"Verdana\";\nmy $imagewidth = 1200;          # max width, pixels\nmy $frameheight = 16;           # max height is dynamic\nmy $fontsize = 12;              # base text size\nmy $fontwidth = 0.59;           # avg width relative to fontsize\nmy $minwidth = 0.1;             # min function width, pixels\nmy $nametype = \"Function:\";     # what are the names in the data?\nmy $countname = \"samples\";      # what are the counts in the data?\nmy $colors = \"hot\";             # color theme\nmy $bgcolors = \"\";              # background color theme\nmy $nameattrfile;               # file holding function attributes\nmy $timemax;                    # (override the) sum of the counts\nmy $factor = 1;                 # factor to scale counts by\nmy $hash = 0;                   # color by function name\nmy $palette = 0;                # if we use consistent palettes (default off)\nmy %palette_map;                # palette map hash\nmy $pal_file = \"palette.map\";   # palette map file name\nmy $stackreverse = 0;           # reverse stack order, switching merge end\nmy $inverted = 0;               # icicle graph\nmy $flamechart = 0;             # produce a flame chart (sort by time, do not merge stacks)\nmy $negate = 0;                 # switch differential hues\nmy $titletext = \"\";             # centered heading\nmy $titledefault = \"Flame Graph\";\t# overwritten by --title\nmy $titleinverted = \"Icicle Graph\";\t#   \"    \"\nmy $searchcolor = \"rgb(230,0,230)\";\t# color for search highlighting\nmy $notestext = \"\";\t\t# embedded notes in SVG\nmy $subtitletext = \"\";\t\t# second level title (optional)\nmy $help = 0;\n\nsub usage {\n\tdie <<USAGE_END;\nUSAGE: $0 [options] infile > outfile.svg\\n\n\t--title TEXT     # change title text\n\t--subtitle TEXT  # second level title (optional)\n\t--width NUM      # width of image (default 1200)\n\t--height NUM     # height of each frame (default 16)\n\t--minwidth NUM   # omit smaller functions (default 0.1 pixels)\n\t--fonttype FONT  # font type (default \"Verdana\")\n\t--fontsize NUM   # font size (default 12)\n\t--countname TEXT # count type label (default \"samples\")\n\t--nametype TEXT  # name type label (default \"Function:\")\n\t--colors PALETTE # set color palette. choices are: hot (default), mem,\n\t                 # io, wakeup, chain, java, js, perl, red, green, blue,\n\t                 # aqua, yellow, purple, orange\n\t--bgcolors COLOR # set background colors. gradient choices are yellow\n\t                 # (default), blue, green, grey; flat colors use \"#rrggbb\"\n\t--hash           # colors are keyed by function name hash\n\t--cp             # use consistent palette (palette.map)\n\t--reverse        # generate stack-reversed flame graph\n\t--inverted       # icicle graph\n\t--flamechart     # produce a flame chart (sort by time, do not merge stacks)\n\t--negate         # switch differential hues (blue<->red)\n\t--notes TEXT     # add notes comment in SVG (for debugging)\n\t--help           # this message\n\n\teg,\n\t$0 --title=\"Flame Graph: malloc()\" trace.txt > graph.svg\nUSAGE_END\n}\n\nGetOptions(\n\t'fonttype=s'  => \\$fonttype,\n\t'width=i'     => \\$imagewidth,\n\t'height=i'    => \\$frameheight,\n\t'encoding=s'  => \\$encoding,\n\t'fontsize=f'  => \\$fontsize,\n\t'fontwidth=f' => \\$fontwidth,\n\t'minwidth=f'  => \\$minwidth,\n\t'title=s'     => \\$titletext,\n\t'subtitle=s'  => \\$subtitletext,\n\t'nametype=s'  => \\$nametype,\n\t'countname=s' => \\$countname,\n\t'nameattr=s'  => \\$nameattrfile,\n\t'total=s'     => \\$timemax,\n\t'factor=f'    => \\$factor,\n\t'colors=s'    => \\$colors,\n\t'bgcolors=s'  => \\$bgcolors,\n\t'hash'        => \\$hash,\n\t'cp'          => \\$palette,\n\t'reverse'     => \\$stackreverse,\n\t'inverted'    => \\$inverted,\n\t'flamechart'  => \\$flamechart,\n\t'negate'      => \\$negate,\n\t'notes=s'     => \\$notestext,\n\t'help'        => \\$help,\n) or usage();\n$help && usage();\n\n# internals\nmy $ypad1 = $fontsize * 3;      # pad top, include title\nmy $ypad2 = $fontsize * 2 + 10; # pad bottom, include labels\nmy $ypad3 = $fontsize * 2;      # pad top, include subtitle (optional)\nmy $xpad = 10;                  # pad lefm and right\nmy $framepad = 1;\t\t# vertical padding for frames\nmy $depthmax = 0;\nmy %Events;\nmy %nameattr;\n\nif ($flamechart && $titletext eq \"\") {\n\t$titletext = \"Flame Chart\";\n}\n\nif ($titletext eq \"\") {\n\tunless ($inverted) {\n\t\t$titletext = $titledefault;\n\t} else {\n\t\t$titletext = $titleinverted;\n\t}\n}\n\nif ($nameattrfile) {\n\t# The name-attribute file format is a function name followed by a tab then\n\t# a sequence of tab separated name=value pairs.\n\topen my $attrfh, $nameattrfile or die \"Can't read $nameattrfile: $!\\n\";\n\twhile (<$attrfh>) {\n\t\tchomp;\n\t\tmy ($funcname, $attrstr) = split /\\t/, $_, 2;\n\t\tdie \"Invalid format in $nameattrfile\" unless defined $attrstr;\n\t\t$nameattr{$funcname} = { map { split /=/, $_, 2 } split /\\t/, $attrstr };\n\t}\n}\n\nif ($notestext =~ /[<>]/) {\n\tdie \"Notes string can't contain < or >\"\n}\n\n# background colors:\n# - yellow gradient: default (hot, java, js, perl)\n# - green gradient: mem\n# - blue gradient: io, wakeup, chain\n# - gray gradient: flat colors (red, green, blue, ...)\nif ($bgcolors eq \"\") {\n\t# choose a default\n\tif ($colors eq \"mem\") {\n\t\t$bgcolors = \"green\";\n\t} elsif ($colors =~ /^(io|wakeup|chain)$/) {\n\t\t$bgcolors = \"blue\";\n\t} elsif ($colors =~ /^(red|green|blue|aqua|yellow|purple|orange)$/) {\n\t\t$bgcolors = \"grey\";\n\t} else {\n\t\t$bgcolors = \"yellow\";\n\t}\n}\nmy ($bgcolor1, $bgcolor2);\nif ($bgcolors eq \"yellow\") {\n\t$bgcolor1 = \"#eeeeee\";       # background color gradient start\n\t$bgcolor2 = \"#eeeeb0\";       # background color gradient stop\n} elsif ($bgcolors eq \"blue\") {\n\t$bgcolor1 = \"#eeeeee\"; $bgcolor2 = \"#e0e0ff\";\n} elsif ($bgcolors eq \"green\") {\n\t$bgcolor1 = \"#eef2ee\"; $bgcolor2 = \"#e0ffe0\";\n} elsif ($bgcolors eq \"grey\") {\n\t$bgcolor1 = \"#f8f8f8\"; $bgcolor2 = \"#e8e8e8\";\n} elsif ($bgcolors =~ /^#......$/) {\n\t$bgcolor1 = $bgcolor2 = $bgcolors;\n} else {\n\tdie \"Unrecognized bgcolor option \\\"$bgcolors\\\"\"\n}\n\n# SVG functions\n{ package SVG;\n\tsub new {\n\t\tmy $class = shift;\n\t\tmy $self = {};\n\t\tbless ($self, $class);\n\t\treturn $self;\n\t}\n\n\tsub header {\n\t\tmy ($self, $w, $h) = @_;\n\t\tmy $enc_attr = '';\n\t\tif (defined $encoding) {\n\t\t\t$enc_attr = qq{ encoding=\"$encoding\"};\n\t\t}\n\t\t$self->{svg} .= <<SVG;\n<?xml version=\"1.0\"$enc_attr standalone=\"no\"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n<svg version=\"1.1\" width=\"$w\" height=\"$h\" onload=\"init(evt)\" viewBox=\"0 0 $w $h\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n<!-- Flame graph stack visualization. See https://github.com/brendangregg/FlameGraph for latest version, and http://www.brendangregg.com/flamegraphs.html for examples. -->\n<!-- NOTES: $notestext -->\nSVG\n\t}\n\n\tsub include {\n\t\tmy ($self, $content) = @_;\n\t\t$self->{svg} .= $content;\n\t}\n\n\tsub colorAllocate {\n\t\tmy ($self, $r, $g, $b) = @_;\n\t\treturn \"rgb($r,$g,$b)\";\n\t}\n\n\tsub group_start {\n\t\tmy ($self, $attr) = @_;\n\n\t\tmy @g_attr = map {\n\t\t\texists $attr->{$_} ? sprintf(qq/$_=\"%s\"/, $attr->{$_}) : ()\n\t\t} qw(id class);\n\t\tpush @g_attr, $attr->{g_extra} if $attr->{g_extra};\n\t\tif ($attr->{href}) {\n\t\t\tmy @a_attr;\n\t\t\tpush @a_attr, sprintf qq/xlink:href=\"%s\"/, $attr->{href} if $attr->{href};\n\t\t\t# default target=_top else links will open within SVG <object>\n\t\t\tpush @a_attr, sprintf qq/target=\"%s\"/, $attr->{target} || \"_top\";\n\t\t\tpush @a_attr, $attr->{a_extra}                           if $attr->{a_extra};\n\t\t\t$self->{svg} .= sprintf qq/<a %s>\\n/, join(' ', (@a_attr, @g_attr));\n\t\t} else {\n\t\t\t$self->{svg} .= sprintf qq/<g %s>\\n/, join(' ', @g_attr);\n\t\t}\n\n\t\t$self->{svg} .= sprintf qq/<title>%s<\\/title>/, $attr->{title}\n\t\t\tif $attr->{title}; # should be first element within g container\n\t}\n\n\tsub group_end {\n\t\tmy ($self, $attr) = @_;\n\t\t$self->{svg} .= $attr->{href} ? qq/<\\/a>\\n/ : qq/<\\/g>\\n/;\n\t}\n\n\tsub filledRectangle {\n\t\tmy ($self, $x1, $y1, $x2, $y2, $fill, $extra) = @_;\n\t\t$x1 = sprintf \"%0.1f\", $x1;\n\t\t$x2 = sprintf \"%0.1f\", $x2;\n\t\tmy $w = sprintf \"%0.1f\", $x2 - $x1;\n\t\tmy $h = sprintf \"%0.1f\", $y2 - $y1;\n\t\t$extra = defined $extra ? $extra : \"\";\n\t\t$self->{svg} .= qq/<rect x=\"$x1\" y=\"$y1\" width=\"$w\" height=\"$h\" fill=\"$fill\" $extra \\/>\\n/;\n\t}\n\n\tsub stringTTF {\n\t\tmy ($self, $id, $x, $y, $str, $extra) = @_;\n\t\t$x = sprintf \"%0.2f\", $x;\n\t\t$id =  defined $id ? qq/id=\"$id\"/ : \"\";\n\t\t$extra ||= \"\";\n\t\t$self->{svg} .= qq/<text $id x=\"$x\" y=\"$y\" $extra>$str<\\/text>\\n/;\n\t}\n\n\tsub svg {\n\t\tmy $self = shift;\n\t\treturn \"$self->{svg}</svg>\\n\";\n\t}\n\t1;\n}\n\nsub namehash {\n\t# Generate a vector hash for the name string, weighting early over\n\t# later characters. We want to pick the same colors for function\n\t# names across different flame graphs.\n\tmy $name = shift;\n\tmy $vector = 0;\n\tmy $weight = 1;\n\tmy $max = 1;\n\tmy $mod = 10;\n\t# if module name present, trunc to 1st char\n\t$name =~ s/.(.*?)`//;\n\tforeach my $c (split //, $name) {\n\t\tmy $i = (ord $c) % $mod;\n\t\t$vector += ($i / ($mod++ - 1)) * $weight;\n\t\t$max += 1 * $weight;\n\t\t$weight *= 0.70;\n\t\tlast if $mod > 12;\n\t}\n\treturn (1 - $vector / $max)\n}\n\nsub color {\n\tmy ($type, $hash, $name) = @_;\n\tmy ($v1, $v2, $v3);\n\n\tif ($hash) {\n\t\t$v1 = namehash($name);\n\t\t$v2 = $v3 = namehash(scalar reverse $name);\n\t} else {\n\t\t$v1 = rand(1);\n\t\t$v2 = rand(1);\n\t\t$v3 = rand(1);\n\t}\n\n\t# theme palettes\n\tif (defined $type and $type eq \"hot\") {\n\t\tmy $r = 205 + int(50 * $v3);\n\t\tmy $g = 0 + int(230 * $v1);\n\t\tmy $b = 0 + int(55 * $v2);\n\t\treturn \"rgb($r,$g,$b)\";\n\t}\n\tif (defined $type and $type eq \"mem\") {\n\t\tmy $r = 0;\n\t\tmy $g = 190 + int(50 * $v2);\n\t\tmy $b = 0 + int(210 * $v1);\n\t\treturn \"rgb($r,$g,$b)\";\n\t}\n\tif (defined $type and $type eq \"io\") {\n\t\tmy $r = 80 + int(60 * $v1);\n\t\tmy $g = $r;\n\t\tmy $b = 190 + int(55 * $v2);\n\t\treturn \"rgb($r,$g,$b)\";\n\t}\n\n\t# multi palettes\n\tif (defined $type and $type eq \"java\") {\n\t\t# Handle both annotations (_[j], _[i], ...; which are\n\t\t# accurate), as well as input that lacks any annotations, as\n\t\t# best as possible. Without annotations, we get a little hacky\n\t\t# and match on java|org|com, etc.\n\t\tif ($name =~ m:_\\[j\\]$:) {\t# jit annotation\n\t\t\t$type = \"green\";\n\t\t} elsif ($name =~ m:_\\[i\\]$:) {\t# inline annotation\n\t\t\t$type = \"aqua\";\n\t\t} elsif ($name =~ m:^L?(java|javax|jdk|net|org|com|io|sun)/:) {\t# Java\n\t\t\t$type = \"green\";\n\t\t} elsif ($name =~ /:::/) {      # Java, typical perf-map-agent method separator\n\t\t\t$type = \"green\";\t              \n\t\t} elsif ($name =~ /::/) {\t# C++\n\t\t\t$type = \"yellow\";\n\t\t} elsif ($name =~ m:_\\[k\\]$:) {\t# kernel annotation\n\t\t\t$type = \"orange\";\n\t\t} elsif ($name =~ /::/) {\t# C++\n\t\t\t$type = \"yellow\";\n\t\t} else {\t\t\t# system\n\t\t\t$type = \"red\";\n\t\t}\n\t\t# fall-through to color palettes\n\t}\n\tif (defined $type and $type eq \"perl\") {\n\t\tif ($name =~ /::/) {\t\t# C++\n\t\t\t$type = \"yellow\";\n\t\t} elsif ($name =~ m:Perl: or $name =~ m:\\.pl:) {\t# Perl\n\t\t\t$type = \"green\";\n\t\t} elsif ($name =~ m:_\\[k\\]$:) {\t# kernel\n\t\t\t$type = \"orange\";\n\t\t} else {\t\t\t# system\n\t\t\t$type = \"red\";\n\t\t}\n\t\t# fall-through to color palettes\n\t}\n\tif (defined $type and $type eq \"js\") {\n\t\t# Handle both annotations (_[j], _[i], ...; which are\n\t\t# accurate), as well as input that lacks any annotations, as\n\t\t# best as possible. Without annotations, we get a little hacky,\n\t\t# and match on a \"/\" with a \".js\", etc.\n\t\tif ($name =~ m:_\\[j\\]$:) {\t# jit annotation\n\t\t\tif ($name =~ m:/:) {\n\t\t\t\t$type = \"green\";\t# source\n\t\t\t} else {\n\t\t\t\t$type = \"aqua\";\t\t# builtin\n\t\t\t}\n\t\t} elsif ($name =~ /::/) {\t# C++\n\t\t\t$type = \"yellow\";\n\t\t} elsif ($name =~ m:/.*\\.js:) {\t# JavaScript (match \"/\" in path)\n\t\t\t$type = \"green\";\n\t\t} elsif ($name =~ m/:/) {\t# JavaScript (match \":\" in builtin)\n\t\t\t$type = \"aqua\";\n\t\t} elsif ($name =~ m/^ $/) {\t# Missing symbol\n\t\t\t$type = \"green\";\n\t\t} elsif ($name =~ m:_\\[k\\]:) {\t# kernel\n\t\t\t$type = \"orange\";\n\t\t} else {\t\t\t# system\n\t\t\t$type = \"red\";\n\t\t}\n\t\t# fall-through to color palettes\n\t}\n\tif (defined $type and $type eq \"wakeup\") {\n\t\t$type = \"aqua\";\n\t\t# fall-through to color palettes\n\t}\n\tif (defined $type and $type eq \"chain\") {\n\t\tif ($name =~ m:_\\[w\\]:) {\t# waker\n\t\t\t$type = \"aqua\"\n\t\t} else {\t\t\t# off-CPU\n\t\t\t$type = \"blue\";\n\t\t}\n\t\t# fall-through to color palettes\n\t}\n\n\t# color palettes\n\tif (defined $type and $type eq \"red\") {\n\t\tmy $r = 200 + int(55 * $v1);\n\t\tmy $x = 50 + int(80 * $v1);\n\t\treturn \"rgb($r,$x,$x)\";\n\t}\n\tif (defined $type and $type eq \"green\") {\n\t\tmy $g = 200 + int(55 * $v1);\n\t\tmy $x = 50 + int(60 * $v1);\n\t\treturn \"rgb($x,$g,$x)\";\n\t}\n\tif (defined $type and $type eq \"blue\") {\n\t\tmy $b = 205 + int(50 * $v1);\n\t\tmy $x = 80 + int(60 * $v1);\n\t\treturn \"rgb($x,$x,$b)\";\n\t}\n\tif (defined $type and $type eq \"yellow\") {\n\t\tmy $x = 175 + int(55 * $v1);\n\t\tmy $b = 50 + int(20 * $v1);\n\t\treturn \"rgb($x,$x,$b)\";\n\t}\n\tif (defined $type and $type eq \"purple\") {\n\t\tmy $x = 190 + int(65 * $v1);\n\t\tmy $g = 80 + int(60 * $v1);\n\t\treturn \"rgb($x,$g,$x)\";\n\t}\n\tif (defined $type and $type eq \"aqua\") {\n\t\tmy $r = 50 + int(60 * $v1);\n\t\tmy $g = 165 + int(55 * $v1);\n\t\tmy $b = 165 + int(55 * $v1);\n\t\treturn \"rgb($r,$g,$b)\";\n\t}\n\tif (defined $type and $type eq \"orange\") {\n\t\tmy $r = 190 + int(65 * $v1);\n\t\tmy $g = 90 + int(65 * $v1);\n\t\treturn \"rgb($r,$g,0)\";\n\t}\n\n\treturn \"rgb(0,0,0)\";\n}\n\nsub color_scale {\n\tmy ($value, $max) = @_;\n\tmy ($r, $g, $b) = (255, 255, 255);\n\t$value = -$value if $negate;\n\tif ($value > 0) {\n\t\t$g = $b = int(210 * ($max - $value) / $max);\n\t} elsif ($value < 0) {\n\t\t$r = $g = int(210 * ($max + $value) / $max);\n\t}\n\treturn \"rgb($r,$g,$b)\";\n}\n\nsub color_map {\n\tmy ($colors, $func) = @_;\n\tif (exists $palette_map{$func}) {\n\t\treturn $palette_map{$func};\n\t} else {\n\t\t$palette_map{$func} = color($colors, $hash, $func);\n\t\treturn $palette_map{$func};\n\t}\n}\n\nsub write_palette {\n\topen(FILE, \">$pal_file\");\n\tforeach my $key (sort keys %palette_map) {\n\t\tprint FILE $key.\"->\".$palette_map{$key}.\"\\n\";\n\t}\n\tclose(FILE);\n}\n\nsub read_palette {\n\tif (-e $pal_file) {\n\topen(FILE, $pal_file) or die \"can't open file $pal_file: $!\";\n\twhile ( my $line = <FILE>) {\n\t\tchomp($line);\n\t\t(my $key, my $value) = split(\"->\",$line);\n\t\t$palette_map{$key}=$value;\n\t}\n\tclose(FILE)\n\t}\n}\n\nmy %Node;\t# Hash of merged frame data\nmy %Tmp;\n\n# flow() merges two stacks, storing the merged frames and value data in %Node.\nsub flow {\n\tmy ($last, $this, $v, $d) = @_;\n\n\tmy $len_a = @$last - 1;\n\tmy $len_b = @$this - 1;\n\n\tmy $i = 0;\n\tmy $len_same;\n\tfor (; $i <= $len_a; $i++) {\n\t\tlast if $i > $len_b;\n\t\tlast if $last->[$i] ne $this->[$i];\n\t}\n\t$len_same = $i;\n\n\tfor ($i = $len_a; $i >= $len_same; $i--) {\n\t\tmy $k = \"$last->[$i];$i\";\n\t\t# a unique ID is constructed from \"func;depth;etime\";\n\t\t# func-depth isn't unique, it may be repeated later.\n\t\t$Node{\"$k;$v\"}->{stime} = delete $Tmp{$k}->{stime};\n\t\tif (defined $Tmp{$k}->{delta}) {\n\t\t\t$Node{\"$k;$v\"}->{delta} = delete $Tmp{$k}->{delta};\n\t\t}\n\t\tdelete $Tmp{$k};\n\t}\n\n\tfor ($i = $len_same; $i <= $len_b; $i++) {\n\t\tmy $k = \"$this->[$i];$i\";\n\t\t$Tmp{$k}->{stime} = $v;\n\t\tif (defined $d) {\n\t\t\t$Tmp{$k}->{delta} += $i == $len_b ? $d : 0;\n\t\t}\n\t}\n\n        return $this;\n}\n\n# parse input\nmy @Data;\nmy @SortedData;\nmy $last = [];\nmy $time = 0;\nmy $delta = undef;\nmy $ignored = 0;\nmy $line;\nmy $maxdelta = 1;\n\n# reverse if needed\nforeach (<>) {\n\tchomp;\n\t$line = $_;\n\tif ($stackreverse) {\n\t\t# there may be an extra samples column for differentials\n\t\t# XXX todo: redo these REs as one. It's repeated below.\n\t\tmy($stack, $samples) = (/^(.*)\\s+?(\\d+(?:\\.\\d*)?)$/);\n\t\tmy $samples2 = undef;\n\t\tif ($stack =~ /^(.*)\\s+?(\\d+(?:\\.\\d*)?)$/) {\n\t\t\t$samples2 = $samples;\n\t\t\t($stack, $samples) = $stack =~ (/^(.*)\\s+?(\\d+(?:\\.\\d*)?)$/);\n\t\t\tunshift @Data, join(\";\", reverse split(\";\", $stack)) . \" $samples $samples2\";\n\t\t} else {\n\t\t\tunshift @Data, join(\";\", reverse split(\";\", $stack)) . \" $samples\";\n\t\t}\n\t} else {\n\t\tunshift @Data, $line;\n\t}\n}\n\nif ($flamechart) {\n\t# In flame chart mode, just reverse the data so time moves from left to right.\n\t@SortedData = reverse @Data;\n} else {\n\t@SortedData = sort @Data;\n}\n\n# process and merge frames\nforeach (@SortedData) {\n\tchomp;\n\t# process: folded_stack count\n\t# eg: func_a;func_b;func_c 31\n\tmy ($stack, $samples) = (/^(.*)\\s+?(\\d+(?:\\.\\d*)?)$/);\n\tunless (defined $samples and defined $stack) {\n\t\t++$ignored;\n\t\tnext;\n\t}\n\n\t# there may be an extra samples column for differentials:\n\tmy $samples2 = undef;\n\tif ($stack =~ /^(.*)\\s+?(\\d+(?:\\.\\d*)?)$/) {\n\t\t$samples2 = $samples;\n\t\t($stack, $samples) = $stack =~ (/^(.*)\\s+?(\\d+(?:\\.\\d*)?)$/);\n\t}\n\t$delta = undef;\n\tif (defined $samples2) {\n\t\t$delta = $samples2 - $samples;\n\t\t$maxdelta = abs($delta) if abs($delta) > $maxdelta;\n\t}\n\n\t# for chain graphs, annotate waker frames with \"_[w]\", for later\n\t# coloring. This is a hack, but has a precedent (\"_[k]\" from perf).\n\tif ($colors eq \"chain\") {\n\t\tmy @parts = split \";--;\", $stack;\n\t\tmy @newparts = ();\n\t\t$stack = shift @parts;\n\t\t$stack .= \";--;\";\n\t\tforeach my $part (@parts) {\n\t\t\t$part =~ s/;/_[w];/g;\n\t\t\t$part .= \"_[w]\";\n\t\t\tpush @newparts, $part;\n\t\t}\n\t\t$stack .= join \";--;\", @parts;\n\t}\n\n\t# merge frames and populate %Node:\n\t$last = flow($last, [ '', split \";\", $stack ], $time, $delta);\n\n\tif (defined $samples2) {\n\t\t$time += $samples2;\n\t} else {\n\t\t$time += $samples;\n\t}\n}\nflow($last, [], $time, $delta);\n\nwarn \"Ignored $ignored lines with invalid format\\n\" if $ignored;\nunless ($time) {\n\twarn \"ERROR: No stack counts found\\n\";\n\tmy $im = SVG->new();\n\t# emit an error message SVG, for tools automating flamegraph use\n\tmy $imageheight = $fontsize * 5;\n\t$im->header($imagewidth, $imageheight);\n\t$im->stringTTF(undef, int($imagewidth / 2), $fontsize * 2,\n\t    \"ERROR: No valid input provided to flamegraph.pl.\");\n\tprint $im->svg;\n\texit 2;\n}\nif ($timemax and $timemax < $time) {\n\twarn \"Specified --total $timemax is less than actual total $time, so ignored\\n\"\n\tif $timemax/$time > 0.02; # only warn is significant (e.g., not rounding etc)\n\tundef $timemax;\n}\n$timemax ||= $time;\n\nmy $widthpertime = ($imagewidth - 2 * $xpad) / $timemax;\nmy $minwidth_time = $minwidth / $widthpertime;\n\n# prune blocks that are too narrow and determine max depth\nwhile (my ($id, $node) = each %Node) {\n\tmy ($func, $depth, $etime) = split \";\", $id;\n\tmy $stime = $node->{stime};\n\tdie \"missing start for $id\" if not defined $stime;\n\n\tif (($etime-$stime) < $minwidth_time) {\n\t\tdelete $Node{$id};\n\t\tnext;\n\t}\n\t$depthmax = $depth if $depth > $depthmax;\n}\n\n# draw canvas, and embed interactive JavaScript program\nmy $imageheight = (($depthmax + 1) * $frameheight) + $ypad1 + $ypad2;\n$imageheight += $ypad3 if $subtitletext ne \"\";\nmy $titlesize = $fontsize + 5;\nmy $im = SVG->new();\nmy ($black, $vdgrey, $dgrey) = (\n\t$im->colorAllocate(0, 0, 0),\n\t$im->colorAllocate(160, 160, 160),\n\t$im->colorAllocate(200, 200, 200),\n    );\n$im->header($imagewidth, $imageheight);\nmy $inc = <<INC;\n<defs>\n\t<linearGradient id=\"background\" y1=\"0\" y2=\"1\" x1=\"0\" x2=\"0\" >\n\t\t<stop stop-color=\"$bgcolor1\" offset=\"5%\" />\n\t\t<stop stop-color=\"$bgcolor2\" offset=\"95%\" />\n\t</linearGradient>\n</defs>\n<style type=\"text/css\">\n\ttext { font-family:$fonttype; font-size:${fontsize}px; fill:$black; }\n\t#search, #ignorecase { opacity:0.1; cursor:pointer; }\n\t#search:hover, #search.show, #ignorecase:hover, #ignorecase.show { opacity:1; }\n\t#subtitle { text-anchor:middle; font-color:$vdgrey; }\n\t#title { text-anchor:middle; font-size:${titlesize}px}\n\t#unzoom { cursor:pointer; }\n\t#frames > *:hover { stroke:black; stroke-width:0.5; cursor:pointer; }\n\t.hide { display:none; }\n\t.parent { opacity:0.5; }\n</style>\n<script type=\"text/ecmascript\">\n<![CDATA[\n\t\"use strict\";\n\tvar details, searchbtn, unzoombtn, matchedtxt, svg, searching, currentSearchTerm, ignorecase, ignorecaseBtn;\n\tfunction init(evt) {\n\t\tdetails = document.getElementById(\"details\").firstChild;\n\t\tsearchbtn = document.getElementById(\"search\");\n\t\tignorecaseBtn = document.getElementById(\"ignorecase\");\n\t\tunzoombtn = document.getElementById(\"unzoom\");\n\t\tmatchedtxt = document.getElementById(\"matched\");\n\t\tsvg = document.getElementsByTagName(\"svg\")[0];\n\t\tsearching = 0;\n\t\tcurrentSearchTerm = null;\n\n\t\t// use GET parameters to restore a flamegraphs state.\n\t\tvar params = get_params();\n\t\tif (params.x && params.y)\n\t\t\tzoom(find_group(document.querySelector('[x=\"' + params.x + '\"][y=\"' + params.y + '\"]')));\n                if (params.s) search(params.s);\n\t}\n\n\t// event listeners\n\twindow.addEventListener(\"click\", function(e) {\n\t\tvar target = find_group(e.target);\n\t\tif (target) {\n\t\t\tif (target.nodeName == \"a\") {\n\t\t\t\tif (e.ctrlKey === false) return;\n\t\t\t\te.preventDefault();\n\t\t\t}\n\t\t\tif (target.classList.contains(\"parent\")) unzoom(true);\n\t\t\tzoom(target);\n\t\t\tif (!document.querySelector('.parent')) {\n\t\t\t\t// we have basically done a clearzoom so clear the url\n\t\t\t\tvar params = get_params();\n\t\t\t\tif (params.x) delete params.x;\n\t\t\t\tif (params.y) delete params.y;\n\t\t\t\thistory.replaceState(null, null, parse_params(params));\n\t\t\t\tunzoombtn.classList.add(\"hide\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// set parameters for zoom state\n\t\t\tvar el = target.querySelector(\"rect\");\n\t\t\tif (el && el.attributes && el.attributes.y && el.attributes._orig_x) {\n\t\t\t\tvar params = get_params()\n\t\t\t\tparams.x = el.attributes._orig_x.value;\n\t\t\t\tparams.y = el.attributes.y.value;\n\t\t\t\thistory.replaceState(null, null, parse_params(params));\n\t\t\t}\n\t\t}\n\t\telse if (e.target.id == \"unzoom\") clearzoom();\n\t\telse if (e.target.id == \"search\") search_prompt();\n\t\telse if (e.target.id == \"ignorecase\") toggle_ignorecase();\n\t}, false)\n\n\t// mouse-over for info\n\t// show\n\twindow.addEventListener(\"mouseover\", function(e) {\n\t\tvar target = find_group(e.target);\n\t\tif (target) details.nodeValue = \"$nametype \" + g_to_text(target);\n\t}, false)\n\n\t// clear\n\twindow.addEventListener(\"mouseout\", function(e) {\n\t\tvar target = find_group(e.target);\n\t\tif (target) details.nodeValue = ' ';\n\t}, false)\n\n\t// ctrl-F for search\n\t// ctrl-I to toggle case-sensitive search\n\twindow.addEventListener(\"keydown\",function (e) {\n\t\tif (e.keyCode === 114 || (e.ctrlKey && e.keyCode === 70)) {\n\t\t\te.preventDefault();\n\t\t\tsearch_prompt();\n\t\t}\n\t\telse if (e.ctrlKey && e.keyCode === 73) {\n\t\t\te.preventDefault();\n\t\t\ttoggle_ignorecase();\n\t\t}\n\t}, false)\n\n\t// functions\n\tfunction get_params() {\n\t\tvar params = {};\n\t\tvar paramsarr = window.location.search.substr(1).split('&');\n\t\tfor (var i = 0; i < paramsarr.length; ++i) {\n\t\t\tvar tmp = paramsarr[i].split(\"=\");\n\t\t\tif (!tmp[0] || !tmp[1]) continue;\n\t\t\tparams[tmp[0]]  = decodeURIComponent(tmp[1]);\n\t\t}\n\t\treturn params;\n\t}\n\tfunction parse_params(params) {\n\t\tvar uri = \"?\";\n\t\tfor (var key in params) {\n\t\t\turi += key + '=' + encodeURIComponent(params[key]) + '&';\n\t\t}\n\t\tif (uri.slice(-1) == \"&\")\n\t\t\turi = uri.substring(0, uri.length - 1);\n\t\tif (uri == '?')\n\t\t\turi = window.location.href.split('?')[0];\n\t\treturn uri;\n\t}\n\tfunction find_child(node, selector) {\n\t\tvar children = node.querySelectorAll(selector);\n\t\tif (children.length) return children[0];\n\t}\n\tfunction find_group(node) {\n\t\tvar parent = node.parentElement;\n\t\tif (!parent) return;\n\t\tif (parent.id == \"frames\") return node;\n\t\treturn find_group(parent);\n\t}\n\tfunction orig_save(e, attr, val) {\n\t\tif (e.attributes[\"_orig_\" + attr] != undefined) return;\n\t\tif (e.attributes[attr] == undefined) return;\n\t\tif (val == undefined) val = e.attributes[attr].value;\n\t\te.setAttribute(\"_orig_\" + attr, val);\n\t}\n\tfunction orig_load(e, attr) {\n\t\tif (e.attributes[\"_orig_\"+attr] == undefined) return;\n\t\te.attributes[attr].value = e.attributes[\"_orig_\" + attr].value;\n\t\te.removeAttribute(\"_orig_\"+attr);\n\t}\n\tfunction g_to_text(e) {\n\t\tvar text = find_child(e, \"title\").firstChild.nodeValue;\n\t\treturn (text)\n\t}\n\tfunction g_to_func(e) {\n\t\tvar func = g_to_text(e);\n\t\t// if there's any manipulation we want to do to the function\n\t\t// name before it's searched, do it here before returning.\n\t\treturn (func);\n\t}\n\tfunction update_text(e) {\n\t\tvar r = find_child(e, \"rect\");\n\t\tvar t = find_child(e, \"text\");\n\t\tvar w = parseFloat(r.attributes.width.value) -3;\n\t\tvar txt = find_child(e, \"title\").textContent.replace(/\\\\([^(]*\\\\)\\$/,\"\");\n\t\tt.attributes.x.value = parseFloat(r.attributes.x.value) + 3;\n\n\t\t// Smaller than this size won't fit anything\n\t\tif (w < 2 * $fontsize * $fontwidth) {\n\t\t\tt.textContent = \"\";\n\t\t\treturn;\n\t\t}\n\n\t\tt.textContent = txt;\n\t\tvar sl = t.getSubStringLength(0, txt.length);\n\t\t// check if only whitespace or if we can fit the entire string into width w\n\t\tif (/^ *\\$/.test(txt) || sl < w)\n\t\t\treturn;\n\n\t\t// this isn't perfect, but gives a good starting point\n\t\t// and avoids calling getSubStringLength too often\n\t\tvar start = Math.floor((w/sl) * txt.length);\n\t\tfor (var x = start; x > 0; x = x-2) {\n\t\t\tif (t.getSubStringLength(0, x + 2) <= w) {\n\t\t\t\tt.textContent = txt.substring(0, x) + \"..\";\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tt.textContent = \"\";\n\t}\n\n\t// zoom\n\tfunction zoom_reset(e) {\n\t\tif (e.attributes != undefined) {\n\t\t\torig_load(e, \"x\");\n\t\t\torig_load(e, \"width\");\n\t\t}\n\t\tif (e.childNodes == undefined) return;\n\t\tfor (var i = 0, c = e.childNodes; i < c.length; i++) {\n\t\t\tzoom_reset(c[i]);\n\t\t}\n\t}\n\tfunction zoom_child(e, x, ratio) {\n\t\tif (e.attributes != undefined) {\n\t\t\tif (e.attributes.x != undefined) {\n\t\t\t\torig_save(e, \"x\");\n\t\t\t\te.attributes.x.value = (parseFloat(e.attributes.x.value) - x - $xpad) * ratio + $xpad;\n\t\t\t\tif (e.tagName == \"text\")\n\t\t\t\t\te.attributes.x.value = find_child(e.parentNode, \"rect[x]\").attributes.x.value + 3;\n\t\t\t}\n\t\t\tif (e.attributes.width != undefined) {\n\t\t\t\torig_save(e, \"width\");\n\t\t\t\te.attributes.width.value = parseFloat(e.attributes.width.value) * ratio;\n\t\t\t}\n\t\t}\n\n\t\tif (e.childNodes == undefined) return;\n\t\tfor (var i = 0, c = e.childNodes; i < c.length; i++) {\n\t\t\tzoom_child(c[i], x - $xpad, ratio);\n\t\t}\n\t}\n\tfunction zoom_parent(e) {\n\t\tif (e.attributes) {\n\t\t\tif (e.attributes.x != undefined) {\n\t\t\t\torig_save(e, \"x\");\n\t\t\t\te.attributes.x.value = $xpad;\n\t\t\t}\n\t\t\tif (e.attributes.width != undefined) {\n\t\t\t\torig_save(e, \"width\");\n\t\t\t\te.attributes.width.value = parseInt(svg.width.baseVal.value) - ($xpad * 2);\n\t\t\t}\n\t\t}\n\t\tif (e.childNodes == undefined) return;\n\t\tfor (var i = 0, c = e.childNodes; i < c.length; i++) {\n\t\t\tzoom_parent(c[i]);\n\t\t}\n\t}\n\tfunction zoom(node) {\n\t\tvar attr = find_child(node, \"rect\").attributes;\n\t\tvar width = parseFloat(attr.width.value);\n\t\tvar xmin = parseFloat(attr.x.value);\n\t\tvar xmax = parseFloat(xmin + width);\n\t\tvar ymin = parseFloat(attr.y.value);\n\t\tvar ratio = (svg.width.baseVal.value - 2 * $xpad) / width;\n\n\t\t// XXX: Workaround for JavaScript float issues (fix me)\n\t\tvar fudge = 0.0001;\n\n\t\tunzoombtn.classList.remove(\"hide\");\n\n\t\tvar el = document.getElementById(\"frames\").children;\n\t\tfor (var i = 0; i < el.length; i++) {\n\t\t\tvar e = el[i];\n\t\t\tvar a = find_child(e, \"rect\").attributes;\n\t\t\tvar ex = parseFloat(a.x.value);\n\t\t\tvar ew = parseFloat(a.width.value);\n\t\t\tvar upstack;\n\t\t\t// Is it an ancestor\n\t\t\tif ($inverted == 0) {\n\t\t\t\tupstack = parseFloat(a.y.value) > ymin;\n\t\t\t} else {\n\t\t\t\tupstack = parseFloat(a.y.value) < ymin;\n\t\t\t}\n\t\t\tif (upstack) {\n\t\t\t\t// Direct ancestor\n\t\t\t\tif (ex <= xmin && (ex+ew+fudge) >= xmax) {\n\t\t\t\t\te.classList.add(\"parent\");\n\t\t\t\t\tzoom_parent(e);\n\t\t\t\t\tupdate_text(e);\n\t\t\t\t}\n\t\t\t\t// not in current path\n\t\t\t\telse\n\t\t\t\t\te.classList.add(\"hide\");\n\t\t\t}\n\t\t\t// Children maybe\n\t\t\telse {\n\t\t\t\t// no common path\n\t\t\t\tif (ex < xmin || ex + fudge >= xmax) {\n\t\t\t\t\te.classList.add(\"hide\");\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tzoom_child(e, xmin, ratio);\n\t\t\t\t\tupdate_text(e);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tsearch();\n\t}\n\tfunction unzoom(dont_update_text) {\n\t\tunzoombtn.classList.add(\"hide\");\n\t\tvar el = document.getElementById(\"frames\").children;\n\t\tfor(var i = 0; i < el.length; i++) {\n\t\t\tel[i].classList.remove(\"parent\");\n\t\t\tel[i].classList.remove(\"hide\");\n\t\t\tzoom_reset(el[i]);\n\t\t\tif(!dont_update_text) update_text(el[i]);\n\t\t}\n\t\tsearch();\n\t}\n\tfunction clearzoom() {\n\t\tunzoom();\n\n\t\t// remove zoom state\n\t\tvar params = get_params();\n\t\tif (params.x) delete params.x;\n\t\tif (params.y) delete params.y;\n\t\thistory.replaceState(null, null, parse_params(params));\n\t}\n\n\t// search\n\tfunction toggle_ignorecase() {\n\t\tignorecase = !ignorecase;\n\t\tif (ignorecase) {\n\t\t\tignorecaseBtn.classList.add(\"show\");\n\t\t} else {\n\t\t\tignorecaseBtn.classList.remove(\"show\");\n\t\t}\n\t\treset_search();\n\t\tsearch();\n\t}\n\tfunction reset_search() {\n\t\tvar el = document.querySelectorAll(\"#frames rect\");\n\t\tfor (var i = 0; i < el.length; i++) {\n\t\t\torig_load(el[i], \"fill\")\n\t\t}\n\t\tvar params = get_params();\n\t\tdelete params.s;\n\t\thistory.replaceState(null, null, parse_params(params));\n\t}\n\tfunction search_prompt() {\n\t\tif (!searching) {\n\t\t\tvar term = prompt(\"Enter a search term (regexp \" +\n\t\t\t    \"allowed, eg: ^ext4_)\"\n\t\t\t    + (ignorecase ? \", ignoring case\" : \"\")\n\t\t\t    + \"\\\\nPress Ctrl-i to toggle case sensitivity\", \"\");\n\t\t\tif (term != null) search(term);\n\t\t} else {\n\t\t\treset_search();\n\t\t\tsearching = 0;\n\t\t\tcurrentSearchTerm = null;\n\t\t\tsearchbtn.classList.remove(\"show\");\n\t\t\tsearchbtn.firstChild.nodeValue = \"Search\"\n\t\t\tmatchedtxt.classList.add(\"hide\");\n\t\t\tmatchedtxt.firstChild.nodeValue = \"\"\n\t\t}\n\t}\n\tfunction search(term) {\n\t\tif (term) currentSearchTerm = term;\n\n\t\tvar re = new RegExp(currentSearchTerm, ignorecase ? 'i' : '');\n\t\tvar el = document.getElementById(\"frames\").children;\n\t\tvar matches = new Object();\n\t\tvar maxwidth = 0;\n\t\tfor (var i = 0; i < el.length; i++) {\n\t\t\tvar e = el[i];\n\t\t\tvar func = g_to_func(e);\n\t\t\tvar rect = find_child(e, \"rect\");\n\t\t\tif (func == null || rect == null)\n\t\t\t\tcontinue;\n\n\t\t\t// Save max width. Only works as we have a root frame\n\t\t\tvar w = parseFloat(rect.attributes.width.value);\n\t\t\tif (w > maxwidth)\n\t\t\t\tmaxwidth = w;\n\n\t\t\tif (func.match(re)) {\n\t\t\t\t// highlight\n\t\t\t\tvar x = parseFloat(rect.attributes.x.value);\n\t\t\t\torig_save(rect, \"fill\");\n\t\t\t\trect.attributes.fill.value = \"$searchcolor\";\n\n\t\t\t\t// remember matches\n\t\t\t\tif (matches[x] == undefined) {\n\t\t\t\t\tmatches[x] = w;\n\t\t\t\t} else {\n\t\t\t\t\tif (w > matches[x]) {\n\t\t\t\t\t\t// overwrite with parent\n\t\t\t\t\t\tmatches[x] = w;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tsearching = 1;\n\t\t\t}\n\t\t}\n\t\tif (!searching)\n\t\t\treturn;\n\t\tvar params = get_params();\n\t\tparams.s = currentSearchTerm;\n\t\thistory.replaceState(null, null, parse_params(params));\n\n\t\tsearchbtn.classList.add(\"show\");\n\t\tsearchbtn.firstChild.nodeValue = \"Reset Search\";\n\n\t\t// calculate percent matched, excluding vertical overlap\n\t\tvar count = 0;\n\t\tvar lastx = -1;\n\t\tvar lastw = 0;\n\t\tvar keys = Array();\n\t\tfor (k in matches) {\n\t\t\tif (matches.hasOwnProperty(k))\n\t\t\t\tkeys.push(k);\n\t\t}\n\t\t// sort the matched frames by their x location\n\t\t// ascending, then width descending\n\t\tkeys.sort(function(a, b){\n\t\t\treturn a - b;\n\t\t});\n\t\t// Step through frames saving only the biggest bottom-up frames\n\t\t// thanks to the sort order. This relies on the tree property\n\t\t// where children are always smaller than their parents.\n\t\tvar fudge = 0.0001;\t// JavaScript floating point\n\t\tfor (var k in keys) {\n\t\t\tvar x = parseFloat(keys[k]);\n\t\t\tvar w = matches[keys[k]];\n\t\t\tif (x >= lastx + lastw - fudge) {\n\t\t\t\tcount += w;\n\t\t\t\tlastx = x;\n\t\t\t\tlastw = w;\n\t\t\t}\n\t\t}\n\t\t// display matched percent\n\t\tmatchedtxt.classList.remove(\"hide\");\n\t\tvar pct = 100 * count / maxwidth;\n\t\tif (pct != 100) pct = pct.toFixed(1)\n\t\tmatchedtxt.firstChild.nodeValue = \"Matched: \" + pct + \"%\";\n\t}\n]]>\n</script>\nINC\n$im->include($inc);\n$im->filledRectangle(0, 0, $imagewidth, $imageheight, 'url(#background)');\n$im->stringTTF(\"title\", int($imagewidth / 2), $fontsize * 2, $titletext);\n$im->stringTTF(\"subtitle\", int($imagewidth / 2), $fontsize * 4, $subtitletext) if $subtitletext ne \"\";\n$im->stringTTF(\"details\", $xpad, $imageheight - ($ypad2 / 2), \" \");\n$im->stringTTF(\"unzoom\", $xpad, $fontsize * 2, \"Reset Zoom\", 'class=\"hide\"');\n$im->stringTTF(\"search\", $imagewidth - $xpad - 100, $fontsize * 2, \"Search\");\n$im->stringTTF(\"ignorecase\", $imagewidth - $xpad - 16, $fontsize * 2, \"ic\");\n$im->stringTTF(\"matched\", $imagewidth - $xpad - 100, $imageheight - ($ypad2 / 2), \" \");\n\nif ($palette) {\n\tread_palette();\n}\n\n# draw frames\n$im->group_start({id => \"frames\"});\nwhile (my ($id, $node) = each %Node) {\n\tmy ($func, $depth, $etime) = split \";\", $id;\n\tmy $stime = $node->{stime};\n\tmy $delta = $node->{delta};\n\n\t$etime = $timemax if $func eq \"\" and $depth == 0;\n\n\tmy $x1 = $xpad + $stime * $widthpertime;\n\tmy $x2 = $xpad + $etime * $widthpertime;\n\tmy ($y1, $y2);\n\tunless ($inverted) {\n\t\t$y1 = $imageheight - $ypad2 - ($depth + 1) * $frameheight + $framepad;\n\t\t$y2 = $imageheight - $ypad2 - $depth * $frameheight;\n\t} else {\n\t\t$y1 = $ypad1 + $depth * $frameheight;\n\t\t$y2 = $ypad1 + ($depth + 1) * $frameheight - $framepad;\n\t}\n\n\tmy $samples = sprintf \"%.0f\", ($etime - $stime) * $factor;\n\t(my $samples_txt = $samples) # add commas per perlfaq5\n\t\t=~ s/(^[-+]?\\d+?(?=(?>(?:\\d{3})+)(?!\\d))|\\G\\d{3}(?=\\d))/$1,/g;\n\n\tmy $info;\n\tif ($func eq \"\" and $depth == 0) {\n\t\t$info = \"all ($samples_txt $countname, 100%)\";\n\t} else {\n\t\tmy $pct = sprintf \"%.2f\", ((100 * $samples) / ($timemax * $factor));\n\t\tmy $escaped_func = $func;\n\t\t# clean up SVG breaking characters:\n\t\t$escaped_func =~ s/&/&amp;/g;\n\t\t$escaped_func =~ s/</&lt;/g;\n\t\t$escaped_func =~ s/>/&gt;/g;\n\t\t$escaped_func =~ s/\"/&quot;/g;\n\t\t$escaped_func =~ s/_\\[[kwij]\\]$//;\t# strip any annotation\n\t\tunless (defined $delta) {\n\t\t\t$info = \"$escaped_func ($samples_txt $countname, $pct%)\";\n\t\t} else {\n\t\t\tmy $d = $negate ? -$delta : $delta;\n\t\t\tmy $deltapct = sprintf \"%.2f\", ((100 * $d) / ($timemax * $factor));\n\t\t\t$deltapct = $d > 0 ? \"+$deltapct\" : $deltapct;\n\t\t\t$info = \"$escaped_func ($samples_txt $countname, $pct%; $deltapct%)\";\n\t\t}\n\t}\n\n\tmy $nameattr = { %{ $nameattr{$func}||{} } }; # shallow clone\n\t$nameattr->{title}       ||= $info;\n\t$im->group_start($nameattr);\n\n\tmy $color;\n\tif ($func eq \"--\") {\n\t\t$color = $vdgrey;\n\t} elsif ($func eq \"-\") {\n\t\t$color = $dgrey;\n\t} elsif (defined $delta) {\n\t\t$color = color_scale($delta, $maxdelta);\n\t} elsif ($palette) {\n\t\t$color = color_map($colors, $func);\n\t} else {\n\t\t$color = color($colors, $hash, $func);\n\t}\n\t$im->filledRectangle($x1, $y1, $x2, $y2, $color, 'rx=\"2\" ry=\"2\"');\n\n\tmy $chars = int( ($x2 - $x1) / ($fontsize * $fontwidth));\n\tmy $text = \"\";\n\tif ($chars >= 3) { # room for one char plus two dots\n\t\t$func =~ s/_\\[[kwij]\\]$//;\t# strip any annotation\n\t\t$text = substr $func, 0, $chars;\n\t\tsubstr($text, -2, 2) = \"..\" if $chars < length $func;\n\t\t$text =~ s/&/&amp;/g;\n\t\t$text =~ s/</&lt;/g;\n\t\t$text =~ s/>/&gt;/g;\n\t}\n\t$im->stringTTF(undef, $x1 + 3, 3 + ($y1 + $y2) / 2, $text);\n\n\t$im->group_end($nameattr);\n}\n$im->group_end();\n\nprint $im->svg;\n\nif ($palette) {\n\twrite_palette();\n}\n\n# vim: ts=8 sts=8 sw=8 noexpandtab\n"
  },
  {
    "path": "utils/flamegraph/eos-util-stackcollapse",
    "content": "#!/usr/bin/perl -w\n#\n# stackcollapse-perf.pl\tcollapse perf samples into single lines.\n#\n# Parses a list of multiline stacks generated by \"perf script\", and\n# outputs a semicolon separated stack followed by a space and a count.\n# If memory addresses (+0xd) are present, they are stripped, and resulting\n# identical stacks are colased with their counts summed.\n#\n# USAGE: ./stackcollapse-perf.pl [options] infile > outfile\n#\n# Run \"./stackcollapse-perf.pl -h\" to list options.\n#\n# Example input:\n#\n#  swapper     0 [000] 158665.570607: cpu-clock:\n#         ffffffff8103ce3b native_safe_halt ([kernel.kallsyms])\n#         ffffffff8101c6a3 default_idle ([kernel.kallsyms])\n#         ffffffff81013236 cpu_idle ([kernel.kallsyms])\n#         ffffffff815bf03e rest_init ([kernel.kallsyms])\n#         ffffffff81aebbfe start_kernel ([kernel.kallsyms].init.text)\n#  [...]\n#\n# Example output:\n#\n#  swapper;start_kernel;rest_init;cpu_idle;default_idle;native_safe_halt 1\n#\n# Input may be created and processed using:\n#\n#  perf record -a -g -F 997 sleep 60\n#  perf script | ./stackcollapse-perf.pl > out.stacks-folded\n#\n# The output of \"perf script\" should include stack traces. If these are missing\n# for you, try manually selecting the perf script output; eg:\n#\n#  perf script -f comm,pid,tid,cpu,time,event,ip,sym,dso,trace | ...\n#\n# This is also required for the --pid or --tid options, so that the output has\n# both the PID and TID.\n#\n# Copyright 2012 Joyent, Inc.  All rights reserved.\n# Copyright 2012 Brendan Gregg.  All rights reserved.\n#\n# CDDL HEADER START\n#\n# The contents of this file are subject to the terms of the\n# Common Development and Distribution License (the \"License\").\n# You may not use this file except in compliance with the License.\n#\n# You can obtain a copy of the license at docs/cddl1.txt or\n# http://opensource.org/licenses/CDDL-1.0.\n# See the License for the specific language governing permissions\n# and limitations under the License.\n#\n# When distributing Covered Code, include this CDDL HEADER in each\n# file and include the License file at docs/cddl1.txt.\n# If applicable, add the following below this CDDL HEADER, with the\n# fields enclosed by brackets \"[]\" replaced with your own identifying\n# information: Portions Copyright [yyyy] [name of copyright owner]\n#\n# CDDL HEADER END\n#\n# 02-Mar-2012\tBrendan Gregg\tCreated this.\n# 02-Jul-2014\t   \"\t  \"\tAdded process name to stacks.\n\nuse strict;\nuse Getopt::Long;\n\nmy %collapsed;\n\nsub remember_stack {\n\tmy ($stack, $count) = @_;\n\t$collapsed{$stack} += $count;\n}\nmy $annotate_kernel = 0; # put an annotation on kernel function\nmy $annotate_jit = 0;   # put an annotation on jit symbols\nmy $annotate_all = 0;   # enale all annotations\nmy $include_pname = 1;\t# include process names in stacks\nmy $include_pid = 0;\t# include process ID with process name\nmy $include_tid = 0;\t# include process & thread ID with process name\nmy $include_addrs = 0;\t# include raw address where a symbol can't be found\nmy $tidy_java = 1;\t# condense Java signatures\nmy $tidy_generic = 1;\t# clean up function names a little\nmy $target_pname;\t# target process name from perf invocation\nmy $event_filter = \"\";    # event type filter, defaults to first encountered event\nmy $event_defaulted = 0;  # whether we defaulted to an event (none provided)\nmy $event_warning = 0;\t  # if we printed a warning for the event\n\nmy $show_inline = 0;\nmy $show_context = 0;\n\nmy $srcline_in_input = 0; # if there are extra lines with source location (perf script -F+srcline)\nGetOptions('inline' => \\$show_inline,\n           'context' => \\$show_context,\n           'srcline' => \\$srcline_in_input,\n           'pid' => \\$include_pid,\n           'kernel' => \\$annotate_kernel,\n           'jit' => \\$annotate_jit,\n           'all' => \\$annotate_all,\n           'tid' => \\$include_tid,\n           'addrs' => \\$include_addrs,\n           'event-filter=s' => \\$event_filter)\nor die <<USAGE_END;\nUSAGE: $0 [options] infile > outfile\\n\n\t--pid\t\t# include PID with process names [1]\n\t--tid\t\t# include TID and PID with process names [1]\n\t--inline\t# un-inline using addr2line\n\t--all\t\t# all annotations (--kernel --jit)\n\t--kernel\t# annotate kernel functions with a _[k]\n\t--jit\t\t# annotate jit functions with a _[j]\n\t--context\t# adds source context to --inline\n\t--srcline\t# parses output of 'perf script -F+srcline' and adds source context\n\t--addrs\t\t# include raw addresses where symbols can't be found\n\t--event-filter=EVENT\t# event name filter\\n\n[1] perf script must emit both PID and TIDs for these to work; eg, Linux < 4.1:\n\tperf script -f comm,pid,tid,cpu,time,event,ip,sym,dso,trace\n    for Linux >= 4.1:\n\tperf script -F comm,pid,tid,cpu,time,event,ip,sym,dso,trace\n    If you save this output add --header on Linux >= 3.14 to include perf info.\nUSAGE_END\n\nif ($annotate_all) {\n\t$annotate_kernel = $annotate_jit = 1;\n}\n\nmy %inlineCache;\n\nmy %nmCache;\n\nsub inlineCacheAdd {\n\tmy ($pc, $mod, $result) = @_;\n   if (defined($inlineCache{$pc})) {\n      $inlineCache{$pc}{$mod} = $result;\n   } else {\n      $inlineCache{$pc} = {$mod => $result};\n   }\n}\n\n# for the --inline option\nsub inline {\n\tmy ($pc, $rawfunc, $mod) = @_;\n\n\treturn $inlineCache{$pc}{$mod} if defined($inlineCache{$pc}{$mod});\n\n\t# capture addr2line output\n\tmy $a2l_output = `addr2line -a $pc -e $mod -i -f -s -C`;\n\n\t# remove first line\n\t$a2l_output =~ s/^(.*\\n){1}//;\n\n\tif ($a2l_output =~ /\\?\\?\\n\\?\\?:0/) {\n\t\t# if addr2line fails and rawfunc is func+offset, then fall back to it\n\t\tif ($rawfunc =~ /^(.+)\\+0x([0-9a-f]+)$/) {\n\t\t\tmy $func = $1;\n\t\t\tmy $addr = hex $2;\n\n\t\t\t$nmCache{$mod}=`nm $mod` unless defined $nmCache{$mod};\n\n\t\t\tif ($nmCache{$mod} =~ /^([0-9a-f]+) . \\Q$func\\E$/m) {\n\t\t\t   my $base = hex $1;\n\t\t\t\tmy $newPc = sprintf \"0x%x\", $base+$addr;\n\t\t\t\tmy $result = inline($newPc, '', $mod);\n\t\t\t\tinlineCacheAdd($pc, $mod, $result);\n\t\t\t\treturn $result;\n\t\t\t}\n\t\t}\n\t}\n\n\tmy @fullfunc;\n\tmy $one_item = \"\";\n\tfor (split /^/, $a2l_output) {\n\t\tchomp $_;\n\n\t\t# remove discriminator info if exists\n\t\t$_ =~ s/ \\(discriminator \\S+\\)//;\n\n\t\tif ($one_item eq \"\") {\n\t\t\t$one_item = $_;\n\t\t} else {\n\t\t\tif ($show_context == 1) {\n\t\t\t\tunshift @fullfunc, $one_item . \":$_\";\n\t\t\t} else {\n\t\t\t\tunshift @fullfunc, $one_item;\n\t\t\t}\n\t\t\t$one_item = \"\";\n\t\t}\n\t}\n\n\tmy $result = join \";\" , @fullfunc;\n\n\tinlineCacheAdd($pc, $mod, $result);\n\n\treturn $result;\n}\n\nmy @stack;\nmy $pname;\nmy $m_pid;\nmy $m_tid;\nmy $m_period;\n\n#\n# Main loop\n#\nwhile (defined($_ = <>)) {\n\n\t# find the name of the process launched by perf, by stepping backwards\n\t# over the args to find the first non-option (no dash):\n\tif (/^# cmdline/) {\n\t\tmy @args = split ' ', $_;\n\t\tforeach my $arg (reverse @args) {\n\t\t\tif ($arg !~ /^-/) {\n\t\t\t\t$target_pname = $arg;\n\t\t\t\t$target_pname =~ s:.*/::;  # strip pathname\n\t\t\t\tlast;\n\t\t\t}\n\t\t}\n\t}\n\n\t# skip remaining comments\n\tnext if m/^#/;\n\tchomp;\n\n\t# end of stack. save cached data.\n\tif (m/^$/) {\n\t\t# ignore filtered samples\n\t\tnext if not $pname;\n\n\t\tif ($include_pname) {\n\t\t\tif (defined $pname) {\n\t\t\t\tunshift @stack, $pname;\n\t\t\t} else {\n\t\t\t\tunshift @stack, \"\";\n\t\t\t}\n\t\t}\n\t\tremember_stack(join(\";\", @stack), $m_period) if @stack;\n\t\tundef @stack;\n\t\tundef $pname;\n\t\tnext;\n\t}\n\n\t#\n\t# event record start\n\t#\n\tif (/^(\\S.+?)\\s+(\\d+)\\/*(\\d+)*\\s+/) {\n\t\t# default \"perf script\" output has TID but not PID\n\t\t# eg, \"java 25607 4794564.109216: 1 cycles:\"\n\t\t# eg, \"java 12688 [002] 6544038.708352: 235 cpu-clock:\"\n\t\t# eg, \"V8 WorkerThread 25607 4794564.109216: 104345 cycles:\"\n\t\t# eg, \"java 24636/25607 [000] 4794564.109216: 1 cycles:\"\n\t\t# eg, \"java 12688/12764 6544038.708352: 10309278 cpu-clock:\"\n\t\t# eg, \"V8 WorkerThread 24636/25607 [000] 94564.109216: 100 cycles:\"\n\t\t# other combinations possible\n\t\tmy ($comm, $pid, $tid, $period) = ($1, $2, $3, \"\");\n\t\tif (not $tid) {\n\t\t\t$tid = $pid;\n\t\t\t$pid = \"?\";\n\t\t}\n\n\t\tif (/:\\s*(\\d+)*\\s+(\\S+):\\s*$/) {\n\t\t\t$period = $1;\n\t\t\tmy $event = $2;\n\n\t\t\tif ($event_filter eq \"\") {\n\t\t\t\t# By default only show events of the first encountered\n\t\t\t\t# event type. Merging together different types, such as\n\t\t\t\t# instructions and cycles, produces misleading results.\n\t\t\t\t$event_filter = $event;\n\t\t\t\t$event_defaulted = 1;\n\t\t\t} elsif ($event ne $event_filter) {\n\t\t\t\tif ($event_defaulted and $event_warning == 0) {\n\t\t\t\t\t# only print this warning if necessary:\n\t\t\t\t\t# when we defaulted and there was\n\t\t\t\t\t# multiple event types.\n\t\t\t\t\tprint STDERR \"Filtering for events of type: $event\\n\";\n\t\t\t\t\t$event_warning = 1;\n\t\t\t\t}\n\t\t\t\tnext;\n\t\t\t}\n\t\t}\n\n\t\tif (not $period) {\n\t\t\t$period = 1\n\t\t}\n\t\t($m_pid, $m_tid, $m_period) = ($pid, $tid, $period);\n\n\t\tif ($include_tid) {\n\t\t\t$pname = \"$comm-$m_pid/$m_tid\";\n\t\t} elsif ($include_pid) {\n\t\t\t$pname = \"$comm-$m_pid\";\n\t\t} else {\n\t\t\t$pname = \"$comm\";\n\t\t}\n\t\t$pname =~ tr/ /_/;\n\n\t#\n\t# stack line\n\t#\n\t} elsif (/^\\s*(\\w+)\\s*(.+) \\((\\S*)\\)/) {\n\t\t# ignore filtered samples\n\t\tnext if not $pname;\n\n\t\tmy ($pc, $rawfunc, $mod) = ($1, $2, $3);\n\n\t\tif ($show_inline == 1 && $mod !~ m/(perf-\\d+.map|kernel\\.|\\[[^\\]]+\\])/) {\n\t\t\tmy $inlineRes = inline($pc, $rawfunc, $mod);\n\t\t\t# - empty result this happens e.g., when $mod does not exist or is a path to a compressed kernel module\n\t\t\t#   if this happens, the user will see error message from addr2line written to stderr\n\t\t\t# - if addr2line results in \"??\" , then it's much more sane to fall back than produce a '??' in graph\n\t\t\tif($inlineRes ne \"\" and $inlineRes ne \"??\" and $inlineRes ne \"??:??:0\" ) {\n\t\t\t\tunshift @stack, $inlineRes;\n\t\t\t\tnext;\n\t\t\t}\n\t\t}\n\n\t\t# Linux 4.8 included symbol offsets in perf script output by default, eg:\n\t\t# 7fffb84c9afc cpu_startup_entry+0x800047c022ec ([kernel.kallsyms])\n\t\t# strip these off:\n\t\t$rawfunc =~ s/\\+0x[\\da-f]+$//;\n\n\t\tnext if $rawfunc =~ /^\\(/;\t\t# skip process names\n\n\t\tmy $is_unknown=0;\n\t\tmy @inline;\n\t\tfor (split /\\->/, $rawfunc) {\n\t\t\tmy $func = $_;\n\n\t\t\tif ($func eq \"[unknown]\") {\n\t\t\t\tif ($mod ne \"[unknown]\") { # use module name instead, if known\n\t\t\t\t\t$func = $mod;\n\t\t\t\t\t$func =~ s/.*\\///;\n\t\t\t\t} else {\n\t\t\t\t\t$func = \"unknown\";\n\t\t\t\t\t$is_unknown=1;\n\t\t\t\t}\n\n\t\t\t\tif ($include_addrs) {\n\t\t\t\t\t$func = \"\\[$func \\<$pc\\>\\]\";\n\t\t\t\t} else {\n\t\t\t\t\t$func = \"\\[$func\\]\";\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ($tidy_generic) {\n\t\t\t\t$func =~ s/;/:/g;\n\t\t\t\tif ($func !~ m/\\.\\(.*\\)\\./) {\n\t\t\t\t\t# This doesn't look like a Go method name (such as\n\t\t\t\t\t# \"net/http.(*Client).Do\"), so everything after the first open\n\t\t\t\t\t# paren (that is not part of an \"(anonymous namespace)\") is\n\t\t\t\t\t# just noise.\n\t\t\t\t\t$func =~ s/\\((?!anonymous namespace\\)).*//;\n\t\t\t\t}\n\t\t\t\t# now tidy this horrible thing:\n\t\t\t\t# 13a80b608e0a RegExp:[&<>\\\"\\'] (/tmp/perf-7539.map)\n\t\t\t\t$func =~ tr/\"\\'//d;\n\t\t\t\t# fall through to $tidy_java\n\t\t\t}\n\n\t\t\tif ($tidy_java and $pname eq \"java\") {\n\t\t\t\t# along with $tidy_generic, converts the following:\n\t\t\t\t#\tLorg/mozilla/javascript/ContextFactory;.call(Lorg/mozilla/javascript/ContextAction;)Ljava/lang/Object;\n\t\t\t\t#\tLorg/mozilla/javascript/ContextFactory;.call(Lorg/mozilla/javascript/C\n\t\t\t\t#\tLorg/mozilla/javascript/MemberBox;.<init>(Ljava/lang/reflect/Method;)V\n\t\t\t\t# into:\n\t\t\t\t#\torg/mozilla/javascript/ContextFactory:.call\n\t\t\t\t#\torg/mozilla/javascript/ContextFactory:.call\n\t\t\t\t#\torg/mozilla/javascript/MemberBox:.init\n\t\t\t\t$func =~ s/^L// if $func =~ m:/:;\n\t\t\t}\n\n\t\t\t#\n\t\t\t# Annotations\n\t\t\t#\n\t\t\t# detect inlined from the @inline array\n\t\t\t# detect kernel from the module name; eg, frames to parse include:\n\t\t\t#          ffffffff8103ce3b native_safe_halt ([kernel.kallsyms]) \n\t\t\t#          8c3453 tcp_sendmsg (/lib/modules/4.3.0-rc1-virtual/build/vmlinux)\n\t\t\t#          7d8 ipv4_conntrack_local+0x7f8f80b8 ([nf_conntrack_ipv4])\n\t\t\t# detect jit from the module name; eg:\n\t\t\t#          7f722d142778 Ljava/io/PrintStream;::print (/tmp/perf-19982.map)\n\t\t\tif (scalar(@inline) > 0) {\n\t\t\t\t$func .= \"_[i]\";\t# inlined\n\t\t\t} elsif ($annotate_kernel == 1 && $mod =~ m/(^\\[|vmlinux$)/ && $mod !~ /unknown/) {\n\t\t\t\t$func .= \"_[k]\";\t# kernel\n\t\t\t} elsif ($annotate_jit == 1 && $mod =~ m:/tmp/perf-\\d+\\.map:) {\n\t\t\t\t$func .= \"_[j]\";\t# jitted\n\t\t\t}\n\n\t\t\t#\n\t\t\t# Source lines\n\t\t\t#\n\t\t\t#\n\t\t\t# Sample outputs:\n\t\t\t#   | a.out 35081 252436.005167:     667783 cycles:\n\t\t\t#   |                   408ebb some_method_name+0x8b (/full/path/to/a.out)\n\t\t\t#   |   uniform_int_dist.h:300\n\t\t\t#   |                   4069f5 main+0x935 (/full/path/to/a.out)\n\t\t\t#   |   file.cpp:137\n\t\t\t#   |             7f6d2148eb25 __libc_start_main+0xd5 (/lib64/libc-2.33.so)\n\t\t\t#   |   libc-2.33.so[27b25]\n\t\t\t#\n\t\t\t#   | a.out 35081 252435.738165:     306459 cycles:\n\t\t\t#   |             7f6d213c2750 [unknown] (/usr/lib64/libkmod.so.2.3.6)\n\t\t\t#   |   libkmod.so.2.3.6[6750]\n\t\t\t#\n\t\t\t#   | a.out 35081 252435.738373:     315813 cycles:\n\t\t\t#   |             7f6d215ca51b __strlen_avx2+0x4b (/lib64/libc-2.33.so)\n\t\t\t#   |   libc-2.33.so[16351b]\n\t\t\t#   |             7ffc71ee9580 [unknown] ([unknown])\t\t\t\n\t\t\t#   |\n\t\t\t#\n\t\t\t#   | a.out 35081 252435.718940:     247984 cycles:\n\t\t\t#   |         ffffffff814f9302 up_write+0x32 ([kernel.kallsyms])\n\t\t\t#   |   [kernel.kallsyms][ffffffff814f9302]\n\t\t\tif($srcline_in_input and not $is_unknown){\n\t\t\t\t$_ = <>;\n\t\t\t\tchomp;\n\t\t\t\ts/\\[.*?\\]//g;\n\t\t\t\ts/^\\s*//g;\n\t\t\t\ts/\\s*$//g;\n\t\t\t\t$func.=':'.$_ unless $_ eq \"\";\n\t\t\t}\n\n\t\t\tpush @inline, $func;\n\t\t}\n\n\t\tunshift @stack, @inline;\n\t} else {\n\t\twarn \"Unrecognized line: $_\";\n\t}\n}\n\nforeach my $k (sort { $a cmp $b } keys %collapsed) {\n\tprint \"$k $collapsed{$k}\\n\";\n}\n"
  },
  {
    "path": "utils/get-xrootd-git-master.sh",
    "content": "#!/bin/bash\nif [ -e xrootd ]; then\n    cd xrootd\n    git pull\nelse\n    git clone -b master http://xrootd.cern.ch/repos/xrootd.git xrootd;\n    cd xrootd/src\n    mkdir include/\n    ln -s ../../src/ include/xrootd\nfi\n"
  },
  {
    "path": "utils/make-keytab",
    "content": "#!/bin/bash\n# arg 1 is keytab output file\n\ntest -n \"$1\" || exit\necho \"0 u:nobody g:nobody n:eos-test N:5506672669367468033 c:1282122142 e:0 k:9ae1ada78699e0ecd0320060e33d0bfa6b60c5c911e67b9b1b89e01dacf4d5b0\" > $1\nchmod 600 $1\nxrdsssadmin -g daemon -u daemon -k eos-test add $1\necho \"\" >> $1\n"
  },
  {
    "path": "utils/replace-in-sources",
    "content": "#!/bin/bash\n# replaces in all .hh and .cc the string $1 with $2\n\ntest -n \"$1\" || exit -1\ntest -n \"$2\" || exit -2\n\necho \"replace $1 => $2\"\n\nfor name in `find . -name \"*.hh\"`; do \n    echo $name\n    perl -pi -e \"s/$1/$2/\" $name\ndone\n\nfor name in `find . -name \"*.cc\"`; do \n    echo $name\n    perl -pi -e \"s/$1/$2/\" $name\ndone\n"
  },
  {
    "path": "utils/route-http",
    "content": "iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8000\niptables -t nat -I OUTPUT -p tcp -d 127.0.0.1 --dport 80 -j REDIRECT --to-ports 8000\n"
  },
  {
    "path": "utils/zstdtail.cc",
    "content": "// ----------------------------------------------------------------------\n// File: zstdtail.cc\n// Author: EOS Team - CERN\n// ----------------------------------------------------------------------\n\n/************************************************************************\n * EOS - the CERN Disk Storage System                                   *\n * Copyright (C) 2011 CERN/Switzerland                                  *\n *                                                                      *\n * This program is free software: you can redistribute it and/or modify *\n * it under the terms of the GNU General Public License as published by *\n * the Free Software Foundation, either version 3 of the License, or    *\n * (at your option) any later version.                                  *\n *                                                                      *\n * This program is distributed in the hope that it will be useful,      *\n * but WITHOUT ANY WARRANTY; without even the implied warranty of       *\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *\n * GNU General Public License for more details.                         *\n *                                                                      *\n * You should have received a copy of the GNU General Public License    *\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.*\n ************************************************************************/\n\n// zstdtail.cpp — \"tail -f\" semantics for .zst files (frame-aware)\n// Build: g++ -std=c++17 -O2 zstdtail.cpp -o zstdtail -lzstd\n//\n// Usage:\n//   zstdtail /path/to/file-or-symlink.zst           # default: follow-only (do not read existing content)\n//   zstdtail -f /path/to/file-or-symlink.zst        # same as default: follow-only\n//   zstdtail -100 /path/to/file-or-symlink.zst      # print last 100 decompressed lines, then exit\n//   zstdtail -100f /path/to/file-or-symlink.zst     # print last 100 decompressed lines, then follow\n//   zstdtail -n 200 -f /path/to/file-or-symlink.zst # print last 200 lines, then follow (alternate form)\n//\n// Notes:\n// - Follow-only mode intentionally does NOT read or decompress the current file’s existing content.\n//   Due to ZSTD frame structure, decoding cannot start mid-frame. Therefore, follow-only will wait\n//   for rotation (new file / new symlink target) and start from the beginning of the new segment.\n// - Tail-N modes (-N or -n N) must decompress from the beginning to find the last N lines. A ring\n//   buffer is used to bound memory, and nothing is printed until the initial scan reaches EOF.\n//\n// Behavior:\n// - Decompresses all complete frames currently in the file.\n// - If the file grows: attempts to decode newly appended frames.\n// - If inside-frame and more bytes are needed, waits for more data.\n// - If the symlink retargets or file rotates (inode changes): starts from the beginning of the new file.\n// Limitations:\n// - Like any zstd decoder, it cannot decode an *incomplete* frame. It will stall until the frame is closed.\n// - If the writer appends in-place to an existing frame, we must re-feed from the beginning of that frame.\n//   (This program handles that by retaining any \"leftover\" bytes between reads and waiting for more.)\n\n#include <zstd.h>\n\n#include <cerrno>\n#include <csignal>\n#include <cstdint>\n#include <cstdio>\n#include <cstring>\n#include <fcntl.h>\n#include <iostream>\n#include <string>\n#include <string_view>\n#include <sys/param.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <thread>\n#include <unistd.h>\n#include <vector>\n#include <deque>\n#include <cctype>\n\nnamespace {\n\nvolatile std::sig_atomic_t g_stop = 0;\n\nvoid on_signal(int) { g_stop = 1; }\n\nstruct DevIno {\n    dev_t dev{};\n    ino_t ino{};\n    bool operator==(const DevIno& o) const { return dev == o.dev && ino == o.ino; }\n    bool operator!=(const DevIno& o) const { return !(*this == o); }\n};\n\nbool lstat_dev_ino(const std::string& path, DevIno& outDI, std::string* resolved = nullptr) {\n    // Resolve symlink each time to follow retargets.\n    char buf[PATH_MAX];\n    ssize_t n = readlink(path.c_str(), buf, sizeof(buf)-1);\n    std::string targetPath = path;\n    if (n >= 0) {\n        buf[n] = '\\0';\n        if (resolved) *resolved = std::string(buf);\n        // If relative symlink, interpret relative to dirname(path)\n        if (buf[0] != '/') {\n            auto slash = path.find_last_of('/');\n            std::string dir = (slash == std::string::npos) ? \".\" : path.substr(0, slash);\n            targetPath = dir + \"/\" + buf;\n        } else {\n            targetPath = std::string(buf);\n        }\n    } else {\n        if (resolved) *resolved = path;\n    }\n\n    struct stat st{};\n    if (stat(targetPath.c_str(), &st) != 0) {\n        return false;\n    }\n    outDI.dev = st.st_dev;\n    outDI.ino = st.st_ino;\n    if (resolved) *resolved = targetPath;\n    return true;\n}\n\n// Open file for reading (no O_APPEND). Return fd or -1.\nint open_follow(const std::string& path) {\n    // Follow symlink here; if it disappears, we’ll retry.\n    std::string resolved;\n    DevIno di{};\n    if (!lstat_dev_ino(path, di, &resolved)) return -1;\n    int fd = ::open(resolved.c_str(), O_RDONLY | O_CLOEXEC);\n    return fd;\n}\n\n// (intentionally no unused helpers)\n\n} // namespace\n\nint main(int argc, char** argv) {\n    if (argc < 2) {\n        std::fprintf(stderr,\n                     \"usage:\\n\"\n                     \"  %s [-f] <file.zst>\\n\"\n                     \"  %s -N[f] <file.zst>         (e.g. -100 or -100f)\\n\"\n                     \"  %s -n N [-f] <file.zst>\\n\",\n                     argv[0], argv[0], argv[0]);\n        return 2;\n    }\n    // Parse args\n    bool wantFollow = false;          // follow after initial action\n    bool followOnly = false;          // do not read existing content; follow new segments/frames only\n    long tailLines = -1;              // -1 => no tail-N priming; >=0 => keep last N lines then (maybe) follow\n    std::string path;\n    bool showHelp = false;\n\n    auto parseInt = [](const char* s, long& out) -> bool {\n        char* end = nullptr;\n        long v = std::strtol(s, &end, 10);\n        if (end == s || v < 0) return false;\n        out = v;\n        return true;\n    };\n\n    for (int i = 1; i < argc; ++i) {\n        std::string a = argv[i];\n        if (a == \"-f\") { wantFollow = true; continue; }\n        if (a == \"-h\" || a == \"--help\") { showHelp = true; continue; }\n        if (a == \"-n\") {\n            if (i + 1 >= argc) {\n                std::fprintf(stderr, \"-n requires a number\\n\");\n                return 2;\n            }\n            long n = -1;\n            if (!parseInt(argv[i + 1], n)) {\n                std::fprintf(stderr, \"invalid number for -n: %s\\n\", argv[i + 1]);\n                return 2;\n            }\n            tailLines = n;\n            ++i;\n            continue;\n        }\n        if (!a.empty() && a[0] == '-' && a.size() > 1 && std::isdigit(static_cast<unsigned char>(a[1]))) {\n            // Compact -N or -Nf form\n            size_t pos = 1;\n            while (pos < a.size() && std::isdigit(static_cast<unsigned char>(a[pos]))) pos++;\n            long n = -1;\n            if (!parseInt(a.substr(1, pos - 1).c_str(), n)) {\n                std::fprintf(stderr, \"invalid number in %s\\n\", a.c_str());\n                return 2;\n            }\n            tailLines = n;\n            if (pos < a.size() && a[pos] == 'f' && pos + 1 == a.size()) {\n                wantFollow = true;\n            } else if (pos < a.size()) {\n                std::fprintf(stderr, \"invalid suffix in %s (only 'f' allowed)\\n\", a.c_str());\n                return 2;\n            }\n            continue;\n        }\n        // First non-option is the path\n        path = a;\n        // Remaining args ignored\n        break;\n    }\n    if (showHelp || path.empty()) {\n        std::fprintf(stderr,\n                     \"zstdtail - stream decompressed tail of ZSTD files\\n\"\n                     \"\\n\"\n                     \"Usage:\\n\"\n                     \"  zstdtail [-f] <file.zst>\\n\"\n                     \"      Follow only: do not print existing content; print new data as it arrives.\\n\"\n                     \"  zstdtail -N[f] <file.zst>\\n\"\n                     \"      Print last N lines, then exit (or follow if 'f' is appended, e.g. -100f).\\n\"\n                     \"  zstdtail -n N [-f] <file.zst>\\n\"\n                     \"      Same as -N form.\\n\"\n                     \"\\n\"\n                     \"Notes:\\n\"\n                     \"- Decoding starts at frame boundaries. For already-open segments, -f will scan\\n\"\n                     \"  from the beginning but suppress historical output (equivalent to tail -n 0 -f).\\n\"\n                     \"- On rotation (new segment), decoding continues from the beginning of the new file.\\n\");\n        return path.empty() ? 2 : 0;\n    }\n    if (path.empty()) {\n        std::fprintf(stderr, \"missing <file.zst> argument\\n\");\n        return 2;\n    }\n    // Default behavior: follow-only if no -n/-N given\n    followOnly = (tailLines < 0);\n    if (followOnly) wantFollow = true; // follow-only implies follow loop\n\n    std::signal(SIGINT, on_signal);\n    std::signal(SIGTERM, on_signal);\n#ifdef SIGQUIT\n    std::signal(SIGQUIT, on_signal);\n#endif\n\n    // Decoder stream\n    ZSTD_DStream* dstream = ZSTD_createDStream();\n    if (!dstream) {\n        std::fprintf(stderr, \"zstd: failed to create DStream\\n\");\n        return 1;\n    }\n    size_t zr = ZSTD_initDStream(dstream);\n    if (ZSTD_isError(zr)) {\n        std::fprintf(stderr, \"zstd: init error: %s\\n\", ZSTD_getErrorName(zr));\n        ZSTD_freeDStream(dstream);\n        return 1;\n    }\n\n    const size_t inChunk = ZSTD_DStreamInSize();   // recommended\n    const size_t outChunk = ZSTD_DStreamOutSize(); // recommended\n    std::vector<char> inBuf(inChunk * 4);          // allow some carry-over\n    std::vector<char> outBuf(outChunk);\n\n    size_t inSize = 0;      // bytes valid in inBuf\n    size_t inPos  = 0;      // current read position in inBuf\n    bool priming = (tailLines >= 0); // in tail-N mode, delay printing until initial EOF\n    bool suppressDuringPriming = false; // used for followOnly: suppress historical output\n    std::string lineBuf;\n    std::deque<std::string> lastN;\n    auto emit_line = [&](const char* data, size_t len) {\n        if (priming) {\n            if (tailLines >= 0) {\n                lastN.emplace_back(data, len);\n                while (static_cast<long>(lastN.size()) > tailLines) lastN.pop_front();\n                return;\n            }\n            if (suppressDuringPriming) {\n                // drop\n                return;\n            }\n        }\n        (void)fwrite(data, 1, len, stdout);\n        fflush(stdout);\n    };\n    auto flush_lastN = [&]() {\n        for (const auto& s : lastN) {\n            (void)fwrite(s.data(), 1, s.size(), stdout);\n        }\n        fflush(stdout);\n        lastN.clear();\n    };\n\n    DevIno lastDI{};\n    bool haveLast = false;\n    int fd = -1;\n\n    auto reopen = [&]() -> bool {\n        if (fd != -1) { ::close(fd); fd = -1; }\n        // reset decoder state when we switch files\n        ZSTD_freeDStream(dstream);\n        dstream = ZSTD_createDStream();\n        if (!dstream) return false;\n        size_t zr2 = ZSTD_initDStream(dstream);\n        if (ZSTD_isError(zr2)) return false;\n\n        // Reset input buffer\n        inSize = 0;\n        inPos = 0;\n        // Reset tailing state for new file if we are following after priming has completed\n        lineBuf.clear();\n        // In follow mode keep priming=false after initial dump\n\n        // Open (follow symlink)\n        for (;;) {\n            if (g_stop) return false;\n            fd = open_follow(path);\n            if (fd >= 0) break;\n            std::this_thread::sleep_for(std::chrono::milliseconds(100));\n        }\n\n        // record dev:ino of the resolved target\n        std::string dummy;\n        DevIno di{};\n        if (!lstat_dev_ino(path, di, &dummy)) return false;\n        lastDI = di; haveLast = true;\n        return true;\n    };\n\n    // First open: always open current file. For followOnly, suppress historical output until first EOF.\n    if (!reopen()) {\n        std::fprintf(stderr, \"error: cannot open %s\\n\", path.c_str());\n        ZSTD_freeDStream(dstream);\n        return 1;\n    }\n    if (followOnly) {\n        priming = true;\n        suppressDuringPriming = true;\n    }\n\n    // Main follow loop\n    while (!g_stop) {\n        // Check rotation: if symlink retargets or file replaced (inode change), reopen from beginning\n        std::string resolvedNow;\n        DevIno diNow{};\n        if (lstat_dev_ino(path, diNow, &resolvedNow)) {\n            if (haveLast && diNow != lastDI) {\n                std::fprintf(stderr, \"== rotation detected: %s ==\\n\", resolvedNow.c_str());\n                if (!reopen()) break;\n                priming = false; // after rotation, stream continues live\n                continue; // start reading new file from beginning\n            }\n        }\n\n        // If follow-only and we have not yet opened (waiting for rotation), just sleep briefly\n        if (followOnly && fd == -1) {\n            std::this_thread::sleep_for(std::chrono::milliseconds(100));\n            continue;\n        }\n\n        // If input buffer is consumed, try to read more bytes\n        if (inPos >= inSize) {\n            inPos = inSize = 0;\n            // Read some more bytes (non-blocking tail-ish logic)\n            // For regular files, read() blocks only until kernel has data or EOF; we poll on size increase.\n            ssize_t r = ::read(fd, inBuf.data(), inBuf.size());\n            if (r < 0 && errno == EINTR) continue;\n            if (r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {\n                std::this_thread::sleep_for(std::chrono::milliseconds(50));\n                continue;\n            }\n            if (r < 0) {\n                std::perror(\"read\");\n                std::this_thread::sleep_for(std::chrono::milliseconds(100));\n                continue;\n            }\n            if (r == 0) {\n                // EOF: no new bytes *right now*. Wait briefly and retry.\n                // If we were priming for tail-N and reached EOF the first time, flush the ring and switch to live mode.\n                if (priming) {\n                    if (tailLines >= 0) {\n                        flush_lastN();\n                    }\n                    priming = false;\n                    suppressDuringPriming = false;\n                    if (!wantFollow) {\n                        break; // exit after printing last N lines (no -f)\n                    }\n                }\n                std::this_thread::sleep_for(std::chrono::milliseconds(80));\n                continue;\n            }\n            inSize = static_cast<size_t>(r);\n        }\n\n        // Decompress whatever we have; if we run out mid-frame, we’ll loop and read more.\n        ZSTD_inBuffer zin{ inBuf.data(), inSize, inPos };\n        for (;;) {\n            ZSTD_outBuffer zout{ outBuf.data(), outBuf.size(), 0 };\n            size_t ret = ZSTD_decompressStream(dstream, &zout, &zin);\n            if (ZSTD_isError(ret)) {\n                // Likely we hit the middle of an incomplete frame or corrupted input.\n                // Strategy: if we have no more input, wait for more; otherwise, treat as fatal corruption.\n                std::fprintf(stderr, \"zstd decode error: %s\\n\", ZSTD_getErrorName(ret));\n                // Try to resync by waiting for more data.\n                break;\n            }\n\n            // Assemble lines from decompressed bytes\n            if (zout.pos > 0) {\n                size_t start = 0;\n                const char* data = outBuf.data();\n                for (size_t i = 0; i < zout.pos; ++i) {\n                    if (data[i] == '\\n') {\n                        // complete line = (lineBuf if any) + data[start..i] + '\\n'\n                        if (!lineBuf.empty()) {\n                            std::string combined;\n                            combined.reserve(lineBuf.size() + (i - start + 1));\n                            combined.append(lineBuf);\n                            combined.append(data + start, i - start + 1);\n                            emit_line(combined.data(), combined.size());\n                            lineBuf.clear();\n                        } else {\n                            emit_line(data + start, i - start + 1);\n                        }\n                        start = i + 1;\n                    }\n                }\n                // remainder (partial line)\n                if (start < zout.pos) {\n                    lineBuf.append(data + start, zout.pos - start);\n                }\n            }\n\n            inPos = zin.pos;\n\n            if (ret == 0) {\n                // ret==0 means a frame ended and the decoder is ready for the next frame.\n                // Keep looping: there might be another frame already buffered (zstd file concatenation).\n                if (zin.pos >= zin.size) break; // need more input to proceed\n                // else: continue with next frame in current buffer\n            } else {\n                // ret > 0 means the number of bytes expected to end the current frame (upper bound).\n                // If we still have input, keep feeding; if not, break to read more.\n                if (zin.pos >= zin.size) break; // need more data\n            }\n        }\n\n        // If buffer fully consumed, next loop iteration will read more.\n    }\n\n    // On exit: if we were priming and have a partial line buffered, treat it as a line\n    if (tailLines >= 0 && !lineBuf.empty()) {\n        (void)fwrite(lineBuf.data(), 1, lineBuf.size(), stdout);\n        fflush(stdout);\n    }\n\n    if (fd != -1) ::close(fd);\n    ZSTD_freeDStream(dstream);\n    return 0;\n}\n"
  }
]